diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index aae7a741a..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = { - "env": { - "browser": true, - "es2021": true - }, - extends: [ - 'semistandard' - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "ignorePatterns" : ["modules/base/zstd.mjs", "modules/three.mjs", "modules/three_addons.mjs", "modules/d3.mjs", "modules/gui/lil-gui.mjs"], - "rules": { - "quotes": ["warn", "single"], - "indent": "off", - "camelcase": "off", - "space-before-function-paren": ["warn", "never"], - "comma-spacing": "warn", - "keyword-spacing": "warn", - "prefer-const": "warn", - "eqeqeq": "warn", - "spaced-comment": "warn", - "array-bracket-spacing": "warn", - "key-spacing": "warn", - "space-in-parens": "warn", - "computed-property-spacing": "warn", - "object-curly-spacing": "warn", - "semi-spacing": "warn", - "no-floating-decimal": "warn", - "semi": "warn", - "object-curly-newline": ["warn", { - "ObjectExpression": { "consistent": true }, - "ObjectPattern": { "consistent": true }, - "ImportDeclaration": "never", - "ExportDeclaration": "never" - }], - "curly": ["warn", "multi-or-nest"], - "one-var": ["warn", "consecutive"], - "space-infix-ops": "off", - "no-multi-spaces": ["warn", { ignoreEOLComments: true }], - "no-multiple-empty-lines": "off", - "object-property-newline": "off", - "promise/param-names": ["warn", { resolvePattern: "^resolve*", rejectPattern: "^reject*" } ], - "no-new-func": "off", - "padded-blocks": ["warn", { "blocks": "never", "classes": "always", "switches": "never" }] - } -}; diff --git a/.github/workflows/jsroot-ci.yml b/.github/workflows/jsroot-ci.yml index 6472d2f9a..ca4e8dcaa 100644 --- a/.github/workflows/jsroot-ci.yml +++ b/.github/workflows/jsroot-ci.yml @@ -2,9 +2,12 @@ name: JSROOT CI on: push: - branches: [ "master" ] + branches: + - "master" pull_request: - branches: [ "master" ] + branches: + - "master" + - "dev" jobs: build-ubuntu: @@ -12,11 +15,12 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] - cxx: [g++-11, g++-12, g++-13] + node-version: [20.x, 22.x, 24.x] + cxx: [g++-13, g++-14] steps: - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: @@ -30,6 +34,7 @@ jobs: sudo apt-get install -y xutils-dev libxi-dev libxxf86vm-dev x11proto-xf86vidmode-dev mesa-utils xvfb libgl1-mesa-dri libglapi-mesa libosmesa6 musl-dev libgl1-mesa-dev sudo apt-get install -y build-essential libxi-dev libglu1-mesa-dev libglew-dev pkg-config echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + echo "LIBGL_ALWAYS_SOFTWARE=1" >> $GITHUB_ENV - name: Install dependencies run: | @@ -50,32 +55,68 @@ jobs: node demo/node/tree_dump.js node demo/node/tree_draw.js xvfb-run -s "-ac -screen 0 1280x1024x24" node demo/node/geomsvg.js - node demo/node/selector.js - cd demo/node; xvfb-run -s "-ac -screen 0 1280x1024x24" node make_image.js + node demo/node/tree_selector.js + node demo/node/tree_staged.js + xvfb-run -s "-ac -screen 0 1280x1024x24" node demo/node/make_image.js demo/node/ + wget https://root.cern/js/files/hsimple.root + node demo/node/file_proxy.js sync ./hsimple.root + node demo/node/file_proxy.js promise ./hsimple.root + node demo/node/file_proxy.js multi ./hsimple.root + node demo/node/file_proxy.js buffer ./hsimple.root + node demo/node/buffer_test.js + node demo/node/rntuple_test.js demo/node/rntuple_test.root + node demo/node/build3d.js + tests_ubuntu: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x, 24.x] + cxx: [g++-13, g++-14] + steps: + - name: Checkout jsroot + uses: actions/checkout@v4 + with: + path: 'jsroot' + - name: Checkout jsroot-test repo uses: actions/checkout@v4 with: repository: 'linev/jsroot-test' path: 'jsroot-test' - - name: Clone JSROOT Repository - run: git clone https://github.com/root-project/jsroot.git + - name: Show jsroot status + run: | + cd jsroot + git status + + - name: Show jsroot-test status + run: | + cd jsroot-test + git status + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + cache-dependency-path: jsroot/package-lock.json + node-version: ${{ matrix.node-version }} + cache: 'npm' - - name: Install System Dependencies for headless-gl + - name: Set up C++ compiler (Ubuntu) run: | sudo apt-get update - sudo apt-get install -y build-essential libxi-dev libglu1-mesa-dev libglew-dev xvfb + sudo apt-get install -y ${{ matrix.cxx }} + sudo apt-get install -y xutils-dev libxi-dev libxxf86vm-dev x11proto-xf86vidmode-dev mesa-utils xvfb libgl1-mesa-dri libglapi-mesa libosmesa6 musl-dev libgl1-mesa-dev + sudo apt-get install -y build-essential libxi-dev libglu1-mesa-dev libglew-dev pkg-config + echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + echo "LIBGL_ALWAYS_SOFTWARE=1" >> $GITHUB_ENV - name: Dependencies jsroot run: | cd jsroot - npm install + npm ci - name: Dependencies jsroot-test run: | @@ -87,24 +128,12 @@ jobs: cd jsroot-test xvfb-run -s "-ac -screen 0 1280x1024x24" node test.js -c -m -p - #Special Cases: Tests,which are not running properly on ubuntu - #TH1/optdate.svg - #TH1/optdate2.svg - #TH1/optdate3.svg - #TH2/image.png - #Candle/plot.svg - #Candle/stack.svg - #TCanvas/time.svg - #TGeo/image.png - #Misc/taxis.svg - #RCanvas/raxis.svg - build-macos: runs-on: macos-latest strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4 @@ -142,15 +171,24 @@ jobs: node demo/node/makesvg.js node demo/node/tree_dump.js node demo/node/tree_draw.js - node demo/node/selector.js - cd demo/node; node make_image.js + node demo/node/tree_selector.js + node demo/node/tree_staged.js + node demo/node/make_image.js demo/node/ + wget https://root.cern/js/files/hsimple.root + node demo/node/file_proxy.js sync ./hsimple.root + node demo/node/file_proxy.js promise ./hsimple.root + node demo/node/file_proxy.js multi ./hsimple.root + node demo/node/file_proxy.js buffer ./hsimple.root + node demo/node/buffer_test.js + node demo/node/rntuple_test.js demo/node/rntuple_test.root + node demo/node/build3d.js build-windows: runs-on: windows-latest strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4 @@ -166,7 +204,7 @@ jobs: - name: Install dependencies run: | - npm ci + npm install - name: Run eslint run: | @@ -180,8 +218,19 @@ jobs: run: | cd demo/node; npm install; cd ../.. node demo/node/makesvg.js + node demo/node/geomsvg.js node demo/node/tree_dump.js node demo/node/tree_draw.js - node demo/node/geomsvg.js - node demo/node/selector.js - cd demo/node; node make_image.js + node demo/node/tree_selector.js + node demo/node/tree_staged.js + node demo/node/make_image.js demo/node/ + curl https://root.cern/js/files/hsimple.root --output hsimple.root + node demo/node/file_proxy.js sync ./hsimple.root + node demo/node/file_proxy.js promise ./hsimple.root + node demo/node/file_proxy.js multi ./hsimple.root + node demo/node/file_proxy.js buffer ./hsimple.root + node demo/node/buffer_test.js + node demo/node/rntuple_test.js demo/node/rntuple_test.root + node demo/node/build3d.js + + diff --git a/.gitignore b/.gitignore index d17715f2a..94b775154 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ docs/jsdocfull/ *.root *.json *.html +*.svg +*.png demo/node/node_modules demo/node/*.svg demo/node/*.png diff --git a/LICENSE b/LICENSE index 1f796956b..a6d9f2471 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright � 2013-2024 JavaScript ROOT authors +Copyright � 2013-2026 JavaScript ROOT authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/api.htm b/api.htm index 701c58183..26c3c1589 100644 --- a/api.htm +++ b/api.htm @@ -355,9 +355,11 @@

JSROOT
import { httpRequest, version, version_id, source_dir } from './modules/core.mjs'; - let jsroot_path = './', filepath = '../files/'; + let jsroot_path = './', + filepath = 'https://root.cern/js/files/', + demopath = ''; - document.getElementById('version').innerHTML = version_id != 'dev' ? version_id : version; + document.getElementById('version').innerText = version_id != 'dev' ? version_id : version; document.getElementById('version').title = `${version}, ${source_dir}`; document.getElementById('expandButton').addEventListener('click', event => { @@ -370,43 +372,43 @@

JSROOT
JSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOT { - if ((value === null) || (value === undefined)) return; + if ((value === null) || (value === undefined)) + return; if (isStr(value)) { - if (newfmt || (value.length < 6) || (value.indexOf('$ref:') !== 0)) return; + if (newfmt || (value.length < 6) || value.indexOf('$ref:')) + return; const ref = parseInt(value.slice(5)); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) return; + if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) + return; newfmt = false; return map[ref]; } - if (typeof value !== 'object') return; + if (typeof value !== 'object') + return; const proto = Object.prototype.toString.apply(value); @@ -659,7 +690,8 @@ function parse(json) { if (isArrayProto(proto) > 0) { for (let i = 0; i < value.length; ++i) { const res = unref_value(value[i]); - if (res !== undefined) value[i] = res; + if (res !== undefined) + value[i] = res; } return; } @@ -668,7 +700,8 @@ function parse(json) { if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { const ref = parseInt(value.$ref); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) return; + if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) + return; newfmt = true; return map[ref]; } @@ -697,8 +730,8 @@ function parse(json) { const buf = atob_func(value.b); if (arr.buffer) { const dv = new DataView(arr.buffer, value.o || 0), - len = Math.min(buf.length, dv.byteLength); - for (let k = 0; k < len; ++k) + blen = Math.min(buf.length, dv.byteLength); + for (let k = 0; k < blen; ++k) dv.setUint8(k, buf.charCodeAt(k)); } else throw new Error('base64 coding supported only for native arrays with binary data'); @@ -706,8 +739,10 @@ function parse(json) { // compressed coding let 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`); + 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`); const v = value[ks[nkey++]]; // value if (typeof v === 'object') { for (let k = 0; k < v.length; ++k) @@ -716,7 +751,8 @@ function parse(json) { arr[p++] = v; if ((nkey < len) && (ks[nkey][0] === 'n')) { let cnt = value[ks[nkey++]]; // counter - while (--cnt) arr[p++] = v; + while (--cnt) + arr[p++] = v; } } } @@ -729,25 +765,30 @@ function parse(json) { newfmt = true; const f1 = unref_value(value.first), s1 = unref_value(value.second); - if (f1 !== undefined) value.first = f1; - if (s1 !== undefined) value.second = s1; + 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 } // prevent endless loop - if (map.indexOf(value) >= 0) return; + if (map.indexOf(value) >= 0) + return; // add object to object map map.push(value); // add methods to all objects, where _typename is specified - if (value._typename) addMethods(value); + if (value._typename) + exports.addMethods(value); for (let k = 0; k < len; ++k) { const i = ks[k], res = unref_value(value[i]); - if (res !== undefined) value[i] = res; + if (res !== undefined) + value[i] = res; } }; @@ -761,18 +802,19 @@ function parse(json) { * @param {string} json string to parse * @return {Array} array of parsed elements */ function parseMulti(json) { - if (!json) return null; + if (!json) + return null; const arr = JSON.parse(json); if (arr?.length) { for (let i = 0; i < arr.length; ++i) - arr[i] = parse(arr[i]); + arr[i] = parse$1(arr[i]); } return arr; } /** @summary Method converts JavaScript object into ROOT-like JSON - * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html#a2ecf0daacdad801e60b8093a404c897d} method to read data back with C++ - * Or one can again parse json with {@link parse} function + * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html} + * method to read data back with C++. Or one can again parse json with {@link parse} function * @param {object} obj - JavaScript object to convert * @param {number} [spacing] - optional line spacing in JSON * @return {string} produced JSON code @@ -783,13 +825,16 @@ function parseMulti(json) { * obj.fTitle = 'New histogram title'; * let json = toJSON(obj); */ function toJSON(obj, spacing) { - if (!isObject(obj)) return ''; + if (!isObject(obj)) + return ''; const map = [], // map of stored objects copy_value = value => { - if (isFunc(value)) return undefined; + if (isFunc(value)) + return undefined; - if ((value === undefined) || (value === null) || !isObject(value)) return value; + if ((value === undefined) || (value === null) || !isObject(value)) + return value; // typed array need to be converted into normal array, otherwise looks strange if (isArrayProto(Object.prototype.toString.apply(value)) > 0) { @@ -801,7 +846,8 @@ function toJSON(obj, spacing) { // this is how reference is code const refid = map.indexOf(value); - if (refid >= 0) return { $ref: refid }; + if (refid >= 0) + return { $ref: refid }; const ks = Object.keys(value), len = ks.length, tgt = {}; @@ -843,27 +889,42 @@ function decodeUrl(url) { const res = { opts: {}, has(opt) { return this.opts[opt] !== undefined; }, - get(opt, dflt) { const v = this.opts[opt]; return v !== undefined ? v : dflt; } + get(opt, dflt) { return this.opts[opt] ?? dflt; } }; if (!url || !isStr(url)) { - if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) return res; + if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) + return res; url = document.URL; } res.url = url; const p1 = url.indexOf('?'); - if (p1 < 0) return res; - url = decodeURI(url.slice(p1+1)); + if (p1 < 0) + return res; + url = decodeURI(url.slice(p1 + 1)); while (url) { // try to correctly handle quotes in the URL let pos = 0, nq = 0, eq = -1, firstq = -1; - while ((pos < url.length) && ((nq !== 0) || ((url[pos] !== '&') && (url[pos] !== '#')))) { + while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) { switch (url[pos]) { - case '\'': if (nq >= 0) nq = (nq+1) % 2; if (firstq < 0) firstq = pos; break; - case '"': if (nq <= 0) nq = (nq-1) % 2; if (firstq < 0) firstq = pos; break; - case '=': if ((firstq < 0) && (eq < 0)) eq = pos; break; + case '\'': + if (nq >= 0) + nq = (nq + 1) % 2; + if (firstq < 0) + firstq = pos; + break; + case '"': + if (nq <= 0) + nq = (nq - 1) % 2; + if (firstq < 0) + firstq = pos; + break; + case '=': + if ((firstq < 0) && (eq < 0)) + eq = pos; + break; } pos++; } @@ -872,13 +933,15 @@ function decodeUrl(url) { res.opts[url.slice(0, pos)] = ''; else if (eq > 0) { let val = url.slice(eq + 1, pos); - if (((val[0] === '\'') || (val[0] === '"')) && (val[0] === val[val.length-1])) val = val.slice(1, val.length-1); + if (((val[0] === '\'') || (val[0] === '"')) && (val.at(0) === val.at(-1))) + val = val.slice(1, val.length - 1); res.opts[url.slice(0, eq)] = val; } - if ((pos >= url.length) || (url[pos] === '#')) break; + if ((pos >= url.length) || (url[pos] === '#')) + break; - url = url.slice(pos+1); + url = url.slice(pos + 1); } return res; @@ -887,8 +950,10 @@ function decodeUrl(url) { /** @summary Find function with given name * @private */ function findFunction(name) { - if (isFunc(name)) return name; - if (!isStr(name)) return null; + if (isFunc(name)) + return name; + if (!isStr(name)) + return null; const names = name.split('.'); let elem = globalThis; @@ -903,19 +968,36 @@ function findFunction(name) { function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise) { function configureXhr(xhr) { xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {}; - xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) { console.warn(err.message); this.http_callback(null); }.bind(xhr); + xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) { + console.warn(err.message); + this.http_callback(null); + }.bind(xhr); - if (!kind) kind = 'buf'; + if (!kind) + kind = 'buf'; let method = 'GET', is_async = true; const p = kind.indexOf(';sync'); - if (p > 0) { kind = kind.slice(0, p); is_async = false; } + if (p > 0) { + kind = kind.slice(0, p); + is_async = false; + } switch (kind) { - case 'head': method = 'HEAD'; break; - case 'posttext': method = 'POST'; kind = 'text'; break; - case 'postbuf': method = 'POST'; kind = 'buf'; break; + case 'head': + method = 'HEAD'; + break; + case 'posttext': + method = 'POST'; + kind = 'text'; + break; + case 'postbuf': + method = 'POST'; + kind = 'buf'; + break; case 'post': - case 'multi': method = 'POST'; break; + case 'multi': + method = 'POST'; + break; } xhr.kind = kind; @@ -934,37 +1016,39 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback } xhr.onreadystatechange = function() { - if (this.did_abort) return; + if (this.did_abort) + return; if ((this.readyState === 2) && this.expected_size) { const len = parseInt(this.getResponseHeader('Content-Length')); if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) { - this.did_abort = true; + this.did_abort = 'large'; this.abort(); return this.error_callback(Error(`Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`), 599); } } - if (this.readyState !== 4) return; + if (this.readyState !== 4) + return; - if ((this.status !== 200) && (this.status !== 206) && !browser.qt5 && + if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 && // in these special cases browsers not always set status !((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0)))) return this.error_callback(Error(`Fail to load url ${url}`), this.status); if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) { - // special handling of gzipped JSON objects in Node.js + // special handling of gzip JSON objects in Node.js return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => { const res = handle.unzipSync(Buffer.from(this.response)), obj = JSON.parse(res); // zlib returns Buffer, use JSON to parse it - return this.http_callback(parse(obj)); + return this.http_callback(parse$1(obj)); }); } switch (this.kind) { case 'xml': return this.http_callback(this.responseXML); case 'text': return this.http_callback(this.responseText); - case 'object': return this.http_callback(parse(this.responseText)); + case 'object': return this.http_callback(parse$1(this.responseText)); case 'multi': return this.http_callback(parseMulti(this.responseText)); case 'head': return this.http_callback(this); } @@ -1001,7 +1085,6 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback if (isNodeJs()) { if (!use_promise) throw Error('Not allowed to create http requests in node.js without promise'); - // eslint-disable-next-line new-cap return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h => configureXhr(new h.default())); } @@ -1009,7 +1092,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback return use_promise ? Promise.resolve(xhr) : xhr; } -/** @summary Submit asynchronoues http request +/** @summary Submit asynchronous http request * @desc Following requests kind can be specified: * - 'bin' - abstract binary data, result as string * - 'buf' - abstract binary data, result as ArrayBuffer (default) @@ -1019,7 +1102,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback * - 'xml' - returns req.responseXML * - 'head' - returns request itself, uses 'HEAD' request method * - 'post' - creates post request, submits req.send(post_data) - * - 'postbuf' - creates post request, expectes binary data as response + * - 'postbuf' - creates post request, expects binary data as response * @param {string} url - URL for the request * @param {string} kind - kind of requested data * @param {string} [post_data] - data submitted with post kind of request @@ -1035,8 +1118,160 @@ async function httpRequest(url, kind, post_data) { }); } +/** @summary Inject javascript code + * @desc Replacement for eval + * @return {Promise} when code is injected + * @private */ +async function injectCode(code) { + if (nodejs) { + let name, fs; + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(tmp => { + name = tmp.tmpNameSync() + '.js'; + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); + }).then(_fs => { + fs = _fs; + fs.writeFileSync(name, code); + return import(/* webpackIgnore: true */ 'file://' + name); + }).finally(() => fs.unlinkSync(name)); + } + + if (typeof document !== 'undefined') { + // check if code already loaded - to avoid duplication + const scripts = document.getElementsByTagName('script'); + for (let n = 0; n < scripts.length; ++n) { + if (scripts[n].innerText === code) + return true; + } + + // try to detect if code includes import and must be treated as module + const is_v6 = code.indexOf('JSROOT.require') >= 0, + is_mjs = !is_v6 && (code.indexOf('import {') > 0) && (code.indexOf('} from \'') > 0), + is_batch = !is_v6 && !is_mjs && (code.indexOf('JSROOT.ObjectPainter') >= 0), + promise = (is_v6 ? exports._ensureJSROOT() : Promise.resolve(true)); + + if (is_batch && !globalThis.JSROOT) + globalThis.JSROOT = internals.jsroot; + + return promise.then(() => { + const element = document.createElement('script'); + element.setAttribute('type', is_mjs ? 'module' : 'text/javascript'); + element.innerText = code; + document.head.appendChild(element); + // while onload event not fired, just postpone resolve + return isBatchMode() ? true : postponePromise(true, 10); + }); + } + + return false; +} + +/** @summary Load ES6 modules + * @param {String} arg - single URL or array of URLs + * @return {Promise} */ +async function loadModules(arg) { + if (isStr(arg)) + arg = arg.split(';'); + if (!arg.length) + return true; + return import(/* webpackIgnore: true */ arg.shift()).then(() => loadModules(arg)); +} + +/** @summary Load script or CSS file into the browser + * @param {String} url - script or css file URL (or array, in this case they all loaded sequentially) + * @return {Promise} */ +async function loadScript(url) { + if (!url) + return true; + + if (isStr(url) && (url.indexOf(';') >= 0)) + url = url.split(';'); + + if (!isStr(url)) { + const scripts = url, loadNext = () => { + return !scripts.length ? true : loadScript(scripts.shift()).then(loadNext, loadNext); + }; + return loadNext(); + } + + if (url.indexOf('$$$') === 0) { + url = url.slice(3); + if ((url.indexOf('style/') === 0) && (url.indexOf('.css') < 0)) + url += '.css'; + url = exports.source_dir + url; + } + + const isstyle = url.indexOf('.css') > 0; + + if (nodejs) { + if (isstyle) + return null; + if ((url.indexOf('http:') === 0) || (url.indexOf('https:') === 0)) + return httpRequest(url, 'text').then(code => injectCode(code)); + + // local files, read and use it + if (url.indexOf('./') === 0) + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => injectCode(fs.readFileSync(url))); + + return import(/* webpackIgnore: true */ url); + } + + const match_url = src => { + if (src === url) + return true; + const indx = src.indexOf(url); + return (indx > 0) && (indx + url.length === src.length) && (src[indx - 1] === '/'); + }; + + if (isstyle) { + const styles = document.getElementsByTagName('link'); + for (let n = 0; n < styles.length; ++n) { + if (!styles[n].href || (styles[n].type !== 'text/css') || (styles[n].rel !== 'stylesheet')) + continue; + if (match_url(styles[n].href)) + return true; + } + } else { + const scripts = document.getElementsByTagName('script'); + for (let n = 0; n < scripts.length; ++n) { + if (match_url(scripts[n].src)) + return true; + } + } + + let element; + if (isstyle) { + element = document.createElement('link'); + element.setAttribute('rel', 'stylesheet'); + element.setAttribute('type', 'text/css'); + element.setAttribute('href', url); + } else { + element = document.createElement('script'); + element.setAttribute('type', 'text/javascript'); + element.setAttribute('src', url); + } + + return new Promise((resolveFunc, rejectFunc) => { + element.onload = () => resolveFunc(true); + element.onerror = () => { + element.remove(); + rejectFunc(Error(`Fail to load ${url}`)); + }; + document.head.appendChild(element); + }); +} + +exports._ensureJSROOT = async function() { + const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(exports.source_dir + 'scripts/JSRoot.core.js'); + + return pr.then(() => { + if (globalThis.JSROOT?._complete_loading) + return globalThis.JSROOT._complete_loading(); + }).then(() => globalThis.JSROOT); +}; + + const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = 'TString', clTObjString = 'TObjString', - clTKey = 'TKey', clTFile = 'TFile', + clTKey = 'TKey', clTFile = 'TFile', clTTree = 'TTree', clTList = 'TList', clTHashList = 'THashList', clTMap = 'TMap', clTObjArray = 'TObjArray', clTClonesArray = 'TClonesArray', clTAttLine = 'TAttLine', clTAttFill = 'TAttFill', clTAttMarker = 'TAttMarker', clTAttText = 'TAttText', clTHStack = 'THStack', clTGraph = 'TGraph', clTMultiGraph = 'TMultiGraph', clTCutG = 'TCutG', @@ -1046,16 +1281,17 @@ const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = clTPaveLabel = 'TPaveLabel', clTPaveClass = 'TPaveClass', clTDiamond = 'TDiamond', clTLegend = 'TLegend', clTLegendEntry = 'TLegendEntry', clTPaletteAxis = 'TPaletteAxis', clTImagePalette = 'TImagePalette', - clTText = 'TText', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', - clTColor = 'TColor', clTLine = 'TLine', clTBox = 'TBox', clTPolyLine = 'TPolyLine', + clTText = 'TText', clTLink = 'TLink', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', + clTColor = 'TColor', clTLine = 'TLine', clTMarker = 'TMarker', clTBox = 'TBox', clTPolyLine = 'TPolyLine', clTPolyLine3D = 'TPolyLine3D', clTPolyMarker3D = 'TPolyMarker3D', clTAttPad = 'TAttPad', clTPad = 'TPad', clTCanvas = 'TCanvas', clTFrame = 'TFrame', clTAttCanvas = 'TAttCanvas', clTGaxis = 'TGaxis', clTAttAxis = 'TAttAxis', clTAxis = 'TAxis', clTStyle = 'TStyle', - clTH1 = 'TH1', clTH1I = 'TH1I', clTH1D = 'TH1D', clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH3 = 'TH3', - clTF1 = 'TF1', clTF2 = 'TF2', clTF3 = 'TF3', clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', + clTH1 = 'TH1', clTH1I = 'TH1I', clTH1F = 'TH1F', clTH1D = 'TH1D', clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH2D = 'TH2D', clTH3 = 'TH3', + clTF1 = 'TF1', clTF12 = 'TF12', clTF2 = 'TF2', clTF3 = 'TF3', clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', clTGeoVolume = 'TGeoVolume', clTGeoNode = 'TGeoNode', clTGeoNodeMatrix = 'TGeoNodeMatrix', - nsREX = 'ROOT::Experimental::', nsSVG = 'http://www.w3.org/2000/svg', - kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title'; + nsROOT = 'ROOT::', nsREX = nsROOT + 'Experimental::', nsSVG = 'http://www.w3.org/2000/svg', + kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title', + urlClassPrefix = 'https://root.cern/doc/master/class'; /** @summary Create some ROOT classes @@ -1080,6 +1316,9 @@ function create$1(typename, target) { case clTHashList: extend$1(obj, { name: typename, arr: [], opt: [] }); break; + case clTObjArray: + extend$1(obj, { name: typename, arr: [] }); + break; case clTAttAxis: extend$1(obj, { fNdivisions: 510, fAxisColor: 1, fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, @@ -1105,6 +1344,11 @@ function create$1(typename, target) { create$1(clTAttLine, obj); extend$1(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); break; + case clTMarker: + create$1(clTObject, obj); + create$1(clTAttMarker, obj); + extend$1(obj, { fX: 0, fY: 0 }); + break; case clTBox: create$1(clTObject, obj); create$1(clTAttLine, obj); @@ -1113,7 +1357,7 @@ function create$1(typename, target) { break; case clTPave: create$1(clTBox, obj); - extend$1(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, + extend$1(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 0, fY2NDC: 0, fBorderSize: 0, fInit: 1, fShadowColor: 1, fCornerRadius: 0, fOption: 'brNDC', fName: '' }); break; @@ -1130,13 +1374,14 @@ function create$1(typename, target) { extend$1(obj, { fFillColor: gStyle.fStatColor, fFillStyle: gStyle.fStatStyle, fTextFont: gStyle.fStatFont, fTextSize: gStyle.fStatFontSize, fTextColor: gStyle.fStatTextColor, fBorderSize: gStyle.fStatBorderSize, - fOptFit: 0, fOptStat: 0, fFitFormat: '', fStatFormat: '', fParent: null }); + fOptFit: gStyle.fOptFit, fOptStat: gStyle.fOptStat, fFitFormat: gStyle.fFitFormat, fStatFormat: gStyle.fStatFormat, fParent: null }); break; case clTLegend: create$1(clTPave, obj); create$1(clTAttText, obj); extend$1(obj, { fColumnSeparation: 0, fEntrySeparation: 0.1, fMargin: 0.25, fNColumns: 1, fPrimitives: create$1(clTList), fName: clTPave, - fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, fFillColor: gStyle.fLegendFillColor }); + fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, + fFillColor: gStyle.fLegendFillColor, fFillStyle: gStyle.fLegendFillStyle }); break; case clTPaletteAxis: create$1(clTPave, obj); @@ -1153,12 +1398,12 @@ function create$1(typename, target) { case clTText: create$1(clTNamed, obj); create$1(clTAttText, obj); - extend$1(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); + extend$1(obj, { fX: 0, fY: 0 }); break; case clTLatex: create$1(clTText, obj); create$1(clTAttLine, obj); - extend$1(obj, { fX: 0, fY: 0 }); + extend$1(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); break; case clTObjString: create$1(clTObject, obj); @@ -1239,9 +1484,9 @@ function create$1(typename, target) { create$1(clTNamed, obj); create$1(clTAttText, obj); create$1(clTAttLine, obj); - extend$1(obj, { fRadian: true, fDegree: false, fGrad: false, fPolarLabelColor: 1, fRadialLabelColor: 1, + extend$1(obj, { fRadian: false, 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, + 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 clTPolyLine: @@ -1308,7 +1553,7 @@ function create$1(typename, target) { fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, fBorderSize: gStyle.fCanvasBorderSize, fBorderMode: gStyle.fCanvasBorderMode, - fCw: 500, fCh: 300, fCatt: create$1(clTAttCanvas), + fCw: settings.CanvasWidth, fCh: settings.CanvasHeight, fCatt: create$1(clTAttCanvas), kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true }); break; @@ -1345,7 +1590,7 @@ function create$1(typename, target) { } obj._typename = typename; - addMethods(obj, typename); + exports.addMethods(obj, typename); return obj; } @@ -1365,25 +1610,50 @@ function create$1(typename, target) { * h1.fXaxis.fLabelSize = 0.02; */ function createHistogram(typename, nbinsx, nbinsy, nbinsz) { const histo = create$1(typename); - if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) return null; - histo.fName = 'hist'; histo.fTitle = 'title'; - if (nbinsx) extend$1(histo.fXaxis, { fNbins: nbinsx, fXmin: 0, fXmax: nbinsx }); - if (nbinsy) extend$1(histo.fYaxis, { fNbins: nbinsy, fXmin: 0, fXmax: nbinsy }); - if (nbinsz) extend$1(histo.fZaxis, { fNbins: nbinsz, fXmin: 0, fXmax: nbinsz }); + if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) + return null; + histo.fName = 'hist'; + histo.fTitle = 'title'; + if (nbinsx) + extend$1(histo.fXaxis, { fNbins: nbinsx, fXmin: 0, fXmax: nbinsx }); + if (nbinsy) + extend$1(histo.fYaxis, { fNbins: nbinsy, fXmin: 0, fXmax: nbinsy }); + if (nbinsz) + extend$1(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; + 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 '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': - case 'D': histo.fArray = new Float64Array(histo.fNcells); break; - default: histo.fArray = new Array(histo.fNcells); + case 'D': + histo.fArray = new Float64Array(histo.fNcells); + break; + default: + histo.fArray = new Array(histo.fNcells); } histo.fArray.fill(0); } @@ -1394,15 +1664,19 @@ function createHistogram(typename, nbinsx, nbinsy, nbinsz) { * @desc Title may include axes titles, provided with ';' symbol like "Title;x;y;z" */ function setHistogramTitle(histo, title) { - if (!histo) return; + if (!histo || !isStr(title)) + return; if (title.indexOf(';') < 0) histo.fTitle = title; else { const arr = title.split(';'); histo.fTitle = arr[0]; - if (arr.length > 1) histo.fXaxis.fTitle = arr[1]; - if (arr.length > 2) histo.fYaxis.fTitle = arr[2]; - if (arr.length > 3) histo.fZaxis.fTitle = arr[3]; + if (arr.length > 1) + histo.fXaxis.fTitle = arr[1]; + if (arr.length > 2) + histo.fYaxis.fTitle = arr[2]; + if (arr.length > 3) + histo.fZaxis.fTitle = arr[3]; } } @@ -1439,8 +1713,8 @@ function createTGraph(npoints, xpts, ypts) { usey = isObject(ypts) && (ypts.length === npoints); for (let i = 0; i < npoints; ++i) { - graph.fX.push(usex ? xpts[i] : i/npoints); - graph.fY.push(usey ? ypts[i] : i/npoints); + graph.fX.push(usex ? xpts[i] : i / npoints); + graph.fY.push(usey ? ypts[i] : i / npoints); } } @@ -1456,10 +1730,10 @@ function createTGraph(npoints, xpts, ypts) { * let h2 = createHistogram('TH1F', nbinsx); * let h3 = createHistogram('TH1F', nbinsx); * let stack = createTHStack(h1, h2, h3); */ -function createTHStack() { +function createTHStack(...args) { const stack = create$1(clTHStack); - for (let i = 0; i < arguments.length; ++i) - stack.fHists.Add(arguments[i], ''); + for (let i = 0; i < args.length; ++i) + stack.fHists.Add(args[i], ''); return stack; } @@ -1471,10 +1745,10 @@ function createTHStack() { * let gr2 = createTGraph(100); * let gr3 = createTGraph(100); * let mgr = createTMultiGraph(gr1, gr2, gr3); */ -function createTMultiGraph() { +function createTMultiGraph(...args) { const mgraph = create$1(clTMultiGraph); - for (let i = 0; i < arguments.length; ++i) - mgraph.fGraphs.Add(arguments[i], ''); + for (let i = 0; i < args.length; ++i) + mgraph.fGraphs.Add(args[i], ''); return mgraph; } @@ -1487,30 +1761,33 @@ const methodsCache = {}; function getMethods(typename, obj) { let m = methodsCache[typename]; const has_methods = (m !== undefined); - if (!has_methods) m = {}; + 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 === clTObject) || (typename === clTNamed) || (obj?.fBits !== undefined)) { if (typeof m.TestBit === 'undefined') { - m.TestBit = function(f) { return (this.fBits & f) !== 0; }; - m.InvertBit = function(f) { this.fBits = this.fBits ^ (f & 0xffffff); }; + m.TestBit = function(f) { return Boolean(this.fBits & f); }; + m.InvertBit = function(f) { this.fBits ^= f & 0xffffff; }; + m.SetBit = function(f, on = true) { this.fBits = on ? this.fBits | f : this.fBits & ~f; }; } } - if (has_methods) return m; + if (has_methods) + return m; if ((typename === clTList) || (typename === clTHashList)) { m.Clear = function() { this.arr = []; this.opt = []; }; - m.Add = function(obj, opt) { - this.arr.push(obj); + m.Add = function(elem, opt) { + this.arr.push(elem); this.opt.push(isStr(opt) ? opt : ''); }; - m.AddFirst = function(obj, opt) { - this.arr.unshift(obj); + m.AddFirst = function(elem, opt) { + this.arr.unshift(elem); this.opt.unshift(isStr(opt) ? opt : ''); }; m.RemoveAt = function(indx) { @@ -1534,11 +1811,13 @@ function getMethods(typename, obj) { }; } - if ((typename.indexOf(clTF1) === 0) || (typename === clTF2)) { - m.addFormula = function(obj) { - if (!obj) return; - if (this.formulas === undefined) this.formulas = []; - this.formulas.push(obj); + if ((typename.indexOf(clTF1) === 0) || (typename === clTF12) || (typename === clTF2) || (typename === clTF3)) { + m.addFormula = function(formula) { + if (formula) { + if (this.formulas === undefined) + this.formulas = []; + this.formulas.push(formula); + } }; m.GetParName = function(n) { if (this.fParams?.fParNames) @@ -1552,10 +1831,11 @@ function getMethods(typename, obj) { return (this.fNames && this.fNames[n]) ? this.fNames[n] : `p${n}`; }; m.GetParValue = function(n) { - if (this.fParams?.fParameters) return this.fParams.fParameters[n]; - if (this.fFormula?.fClingParameters) return this.fFormula.fClingParameters[n]; - if (this.fParams) return this.fParams[n]; - return undefined; + if (this.fParams?.fParameters) + return this.fParams.fParameters[n]; + if (this.fFormula?.fClingParameters) + return this.fFormula.fClingParameters[n]; + return this.fParams ? this.fParams[n] : undefined; }; m.GetParError = function(n) { return this.fParErrors ? this.fParErrors[n] : undefined; @@ -1573,7 +1853,7 @@ function getMethods(typename, obj) { for (; 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) + if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) oddNodes = !oddNodes; } j = i; @@ -1589,8 +1869,10 @@ function getMethods(typename, obj) { // 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.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])); @@ -1616,61 +1898,64 @@ function getMethods(typename, obj) { } if (typename.indexOf(clTH2) === 0) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins+2) * y); }; + 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) { const a1 = this.fXaxis, a2 = this.fYaxis, bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2)*bin2] += weight ?? 1; + this.fArray[bin1 + (a1.fNbins + 2) * bin2] += weight ?? 1; this.fEntries++; }; } if (typename.indexOf(clTH3) === 0) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins+2) * (y + (this.fYaxis.fNbins+2) * z)); }; + 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) { const a1 = this.fXaxis, a2 = this.fYaxis, a3 = this.fZaxis, bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))), bin3 = Math.max(0, 1 + Math.min(a3.fNbins, Math.floor((z - a3.fXmin) / (a3.fXmax - a3.fXmin) * a3.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2)*bin3)] += weight ?? 1; + this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2) * bin3)] += weight ?? 1; this.fEntries++; }; } if (typename === clTPad || typename === clTCanvas) { - m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01) { + m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01, color = 0) { if (!ny) { const ndiv = nx; - if (ndiv < 2) return this; + if (ndiv < 2) + return this; nx = ny = Math.round(Math.sqrt(ndiv)); - if (nx * ny < ndiv) nx += 1; + if (nx * ny < ndiv) + nx += 1; } - if (nx*ny < 2) + if (nx * ny < 2) return 0; this.fPrimitives.Clear(); - const dy = 1/ny, dx = 1/nx; + const dy = 1 / ny, dx = 1 / nx; let n = 0; for (let iy = 0; iy < ny; iy++) { - const y2 = 1 - iy*dy - ymargin; - let y1 = y2 - dy + 2*ymargin; - if (y1 < 0) y1 = 0; - if (y1 > y2) continue; + const y2 = 1 - iy * dy - ymargin, + y1 = Math.max(0, y2 - dy + 2 * ymargin); + if (y1 > y2) + continue; for (let ix = 0; ix < nx; ix++) { - const x1 = ix*dx + xmargin, - x2 = x1 + dx -2*xmargin; - if (x1 > x2) continue; + const x1 = ix * dx + xmargin, + x2 = x1 + dx - 2 * xmargin; + if (x1 > x2) + continue; n++; const pad = create$1(clTPad); pad.fName = pad.fTitle = `${this.fName}_${n}`; pad.fNumber = n; if (this._typename !== clTCanvas) { - pad.fAbsWNDC = (x2-x1) * this.fAbsWNDC; - pad.fAbsHNDC = (y2-y1) * this.fAbsHNDC; + pad.fAbsWNDC = (x2 - x1) * this.fAbsWNDC; + pad.fAbsHNDC = (y2 - y1) * this.fAbsHNDC; pad.fAbsXlowNDC = this.fAbsXlowNDC + x1 * this.fAbsWNDC; - pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsWNDC; + pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsHNDC; } else { pad.fAbsWNDC = x2 - x1; pad.fAbsHNDC = y2 - y1; @@ -1678,6 +1963,8 @@ function getMethods(typename, obj) { pad.fAbsYlowNDC = y1; } + pad.fFillColor = color || this.fFillColor; + this.fPrimitives.Add(pad); } } @@ -1690,41 +1977,43 @@ function getMethods(typename, obj) { if (typename.indexOf(clTProfile) === 0) { if (typename === clTProfile3D) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins+2) * (y + (this.fYaxis.fNbins+2) * z)); }; + m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; m.getBinContent = function(x, y, z) { const bin = this.getBin(x, y, z); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) return 0; - return this.fArray ? this.fArray[bin]/this.fBinEntries[bin] : 0; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; }; m.getBinEntries = function(x, y, z) { const bin = this.getBin(x, y, z); return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; }; } else if (typename === clTProfile2D) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins+2) * y); }; + m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; m.getBinContent = function(x, y) { const 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]; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; }; m.getBinEntries = function(x, y) { const bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells) return 0; - return this.fBinEntries[bin]; + return (bin < 0 || bin >= this.fNcells) ? 0 : 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]; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; + }; + m.getBinEntries = function(bin) { + return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; }; } m.getBinEffectiveEntries = function(bin) { - if (bin < 0 || bin >= this.fNcells) return 0; + if (bin < 0 || bin >= this.fNcells) + return 0; const sumOfWeights = this.fBinEntries[bin]; if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) // this can happen when reading an old file @@ -1733,48 +2022,70 @@ function getMethods(typename, obj) { return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; }; m.getBinError = function(bin) { - if (bin < 0 || bin >= this.fNcells) return 0; + if (bin < 0 || bin >= this.fNcells) + return 0; const 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 - const EErrorType = { kERRORMEAN: 0, kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3 }; + if (sum < 1e-300) + return 0; // for empty bins + const EErrorType = { 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); + return 1.0 / Math.sqrt(sum); // compute variance in y (eprim2) and standard deviation in y (eprim) - const contsum = cont/sum, eprim = Math.sqrt(Math.abs(err2/sum - contsum**2)); + const contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum ** 2)); if (this.fErrorMode === EErrorType.kERRORSPREADI) { - if (eprim !== 0) return eprim/Math.sqrt(neff); + if (eprim) + 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); + 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; + if (this.fErrorMode === EErrorType.kERRORSPREAD) + return eprim; // default case : fErrorMode = kERRORMEAN // return standard error on the mean of y - return eprim/Math.sqrt(neff); + return eprim / Math.sqrt(neff); }; } if (typename === clTAxis) { 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; + if (this.fNbins <= 0) + return 0; + if (this.fXbins.length) { + if ((bin > 0) && (bin <= this.fNbins + 1)) + return this.fXbins[bin - 1]; + if (bin === 0) // underflow + return 2 * this.fXbins[0] - this.fXbins[1]; + if (bin === this.fNbins + 2) // right border of overflow bin + return 2 * this.fXbins[bin - 2] - this.fXbins[bin - 3]; + return 0; + } + 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 (this.fNbins <= 0) + return 0; + if (this.fXbins.length) { + if ((bin > 0) && (bin <= this.fNbins)) + return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; + if (bin === 0) // underflow + return 1.5 * this.fXbins[0] - 0.5 * this.fXbins[1]; + if (bin === this.fNbins + 1) // overflow + return 1.5 * this.fXbins[bin - 1] - 0.5 * this.fXbins[bin - 2]; + return 0; + } + return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; }; } - if (typename.indexOf('ROOT::Math::LorentzVector') === 0) { + if (typename.indexOf(nsROOT + 'Math::LorentzVector') === 0) { m.Px = m.X = function() { return this.fCoordinates.Px(); }; m.Py = m.Y = function() { return this.fCoordinates.Py(); }; m.Pz = m.Z = function() { return this.fCoordinates.Pz(); }; @@ -1785,28 +2096,32 @@ function getMethods(typename, obj) { m.P2 = function() { return this.P() * this.P(); }; m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; m.Phi = m.phi = function() { return Math.atan2(this.fCoordinates.Py(), this.fCoordinates.Px()); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz()/this.P()); }; + m.Eta = m.eta = function() { return Math.atanh(this.Pz() / this.P()); }; } - if (typename.indexOf('ROOT::Math::PxPyPzE4D') === 0) { + if (typename.indexOf(nsROOT + 'Math::PxPyPzE4D') === 0) { m.Px = m.X = function() { return this.fX; }; m.Py = m.Y = function() { return this.fY; }; m.Pz = m.Z = function() { return this.fZ; }; m.E = m.T = function() { return this.fT; }; - m.P2 = function() { return this.fX**2 + this.fY**2 + this.fZ**2; }; + m.P2 = function() { return this.fX ** 2 + this.fY ** 2 + this.fZ ** 2; }; m.R = m.P = function() { return Math.sqrt(this.P2()); }; - m.Mag2 = m.M2 = function() { return this.fT**2 - this.fX**2 - this.fY**2 - this.fZ**2; }; + m.Mag2 = m.M2 = function() { return this.fT ** 2 - this.fX ** 2 - this.fY ** 2 - this.fZ ** 2; }; m.Mag = m.M = function() { return (this.M2() >= 0) ? Math.sqrt(this.M2()) : -Math.sqrt(-this.M2()); }; - m.Perp2 = m.Pt2 = function() { return this.fX**2 + this.fY**2; }; + m.Perp2 = m.Pt2 = function() { return this.fX ** 2 + this.fY ** 2; }; m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; m.Phi = m.phi = function() { return Math.atan2(this.fY, this.fX); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz/this.P()); }; + m.Eta = m.eta = function() { return Math.atanh(this.Pz / this.P()); }; } methodsCache[typename] = m; return m; } +exports.addMethods = function(obj, typename) { + extend$1(obj, getMethods(typename || obj._typename, obj)); +}; + gStyle.fXaxis = create$1(clTAttAxis); gStyle.fYaxis = create$1(clTAttAxis); gStyle.fZaxis = create$1(clTAttAxis); @@ -1825,60 +2140,44 @@ function registerMethods(typename, m) { * @private */ function isRootCollection(lst, typename) { if (isObject(lst)) { - if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) return true; - if (!typename) typename = lst._typename; + if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) + return true; + if (!typename) + typename = lst._typename; } return (typename === clTList) || (typename === clTHashList) || (typename === clTMap) || (typename === clTObjArray) || (typename === clTClonesArray); } -/** @summary Check if argument is a not-null Object - * @private */ -function isObject(arg) { return arg && typeof arg === 'object'; } - -/** @summary Check if argument is a Function - * @private */ -function isFunc(arg) { return typeof arg === 'function'; } - -/** @summary Check if argument is a atring - * @private */ -function isStr(arg) { return typeof arg === 'string'; } - -/** @summary Check if object is a Promise - * @private */ -function isPromise(obj) { return isObject(obj) && isFunc(obj.then); } - -/** @summary Postpone func execution and return result in promise +/** @summary Return kind string for type + * @desc Used when internal objects without clear ROOT type are used + * For such objects abstract 'kind' is defined. Used in hpainter and draw handles * @private */ -function postponePromise(func, timeout) { - return new Promise(resolveFunc => { - setTimeout(() => { - const res = isFunc(func) ? func() : func; - resolveFunc(res); - }, timeout); - }); +function getKindForType(typ) { + return (!isStr(typ) || (typ.indexOf(nsROOT) !== 0)) ? prROOT + typ : typ; } -/** @summary Provide promise in any case +/** @summary Return type name from kind string * @private */ -function getPromise(obj) { return isPromise(obj) ? obj : Promise.resolve(obj); } +function getTypeForKind(kind) { + if (!isStr(kind)) + return null; + if (kind.indexOf(prROOT) === 0) + return kind.slice(prROOT.length); + if (kind.indexOf(nsROOT) === 0) + return kind; + return null; +} -/** @summary Ensure global JSROOT and v6 support methods +/** @summary Internal collection of functions potentially used by batch scripts * @private */ -async function _ensureJSROOT() { - const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(exports.source_dir + 'scripts/JSRoot.core.js'); - - return pr.then(() => { - if (globalThis.JSROOT?._complete_loading) - return globalThis.JSROOT._complete_loading(); - }).then(() => globalThis.JSROOT); -} +internals.jsroot = { version, source_dir: exports.source_dir, settings, gStyle, parse: parse$1, isBatchMode }; var core = /*#__PURE__*/Object.freeze({ __proto__: null, BIT: BIT, -_ensureJSROOT: _ensureJSROOT, -addMethods: addMethods, +get _ensureJSROOT () { return exports._ensureJSROOT; }, +get addMethods () { return exports.addMethods; }, atob_func: atob_func, browser: browser, btoa_func: btoa_func, @@ -1896,6 +2195,7 @@ clTColor: clTColor, clTCutG: clTCutG, clTDiamond: clTDiamond, clTF1: clTF1, +clTF12: clTF12, clTF2: clTF2, clTF3: clTF3, clTFile: clTFile, @@ -1912,8 +2212,10 @@ clTGraphPolargram: clTGraphPolargram, clTGraphTime: clTGraphTime, clTH1: clTH1, clTH1D: clTH1D, +clTH1F: clTH1F, clTH1I: clTH1I, clTH2: clTH2, +clTH2D: clTH2D, clTH2F: clTH2F, clTH2I: clTH2I, clTH3: clTH3, @@ -1925,8 +2227,10 @@ clTLatex: clTLatex, clTLegend: clTLegend, clTLegendEntry: clTLegendEntry, clTLine: clTLine, +clTLink: clTLink, clTList: clTList, clTMap: clTMap, +clTMarker: clTMarker, clTMathText: clTMathText, clTMultiGraph: clTMultiGraph, clTNamed: clTNamed, @@ -1950,6 +2254,7 @@ clTProfile3D: clTProfile3D, clTString: clTString, clTStyle: clTStyle, clTText: clTText, +clTTree: clTTree, clone: clone, constants: constants$1, create: create$1, @@ -1963,8 +2268,10 @@ decodeUrl: decodeUrl, findFunction: findFunction, gStyle: gStyle, getDocument: getDocument, +getKindForType: getKindForType, getMethods: getMethods, getPromise: getPromise, +getTypeForKind: getTypeForKind, httpRequest: httpRequest, injectCode: injectCode, internals: internals, @@ -1980,10 +2287,12 @@ kInspect: kInspect, kNoStats: kNoStats, kNoZoom: kNoZoom, kTitle: kTitle, +loadModules: loadModules, loadScript: loadScript, nsREX: nsREX, +nsROOT: nsROOT, nsSVG: nsSVG, -parse: parse, +parse: parse$1, parseMulti: parseMulti, postponePromise: postponePromise, prROOT: prROOT, @@ -1993,6 +2302,7 @@ setHistogramTitle: setHistogramTitle, settings: settings, get source_dir () { return exports.source_dir; }, toJSON: toJSON, +urlClassPrefix: urlClassPrefix, version: version, version_date: version_date, version_id: version_id @@ -2284,11 +2594,11 @@ define(Rgb, rgb, extend(Color$1, { })); function rgb_formatHex() { - return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}`; + return `#${hex$1(this.r)}${hex$1(this.g)}${hex$1(this.b)}`; } function rgb_formatHex8() { - return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}${hex((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`; + return `#${hex$1(this.r)}${hex$1(this.g)}${hex$1(this.b)}${hex$1((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`; } function rgb_formatRgb() { @@ -2304,7 +2614,7 @@ function clampi(value) { return Math.max(0, Math.min(255, Math.round(value) || 0)); } -function hex(value) { +function hex$1(value) { value = clampi(value); return (value < 16 ? "0" : "") + value.toString(16); } @@ -2410,7 +2720,7 @@ const radians = Math.PI / 180; const degrees$1 = 180 / Math.PI; // https://observablehq.com/@mbostock/lab-and-rgb -const K$1 = 18, +const K = 18, Xn = 0.96422, Yn = 1, Zn = 0.82521, @@ -2447,10 +2757,10 @@ function Lab(l, a, b, opacity) { define(Lab, lab, extend(Color$1, { brighter(k) { - return new Lab(this.l + K$1 * (k == null ? 1 : k), this.a, this.b, this.opacity); + return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); }, darker(k) { - return new Lab(this.l - K$1 * (k == null ? 1 : k), this.a, this.b, this.opacity); + return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); }, rgb() { var y = (this.l + 16) / 116, @@ -2511,10 +2821,10 @@ function hcl2lab(o) { define(Hcl, hcl, extend(Color$1, { brighter(k) { - return new Hcl(this.h, this.c, this.l + K$1 * (k == null ? 1 : k), this.opacity); + return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); }, darker(k) { - return new Hcl(this.h, this.c, this.l - K$1 * (k == null ? 1 : k), this.opacity); + return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); }, rgb() { return hcl2lab(this).rgb(); @@ -2522,10 +2832,10 @@ define(Hcl, hcl, extend(Color$1, { })); var A = -0.14861, - B = +1.78277, + B = 1.78277, C = -0.29227, D = -0.90649, - E = +1.97294, + E = 1.97294, ED = E * D, EB = E * B, BC_DA = B * C - D * A; @@ -2602,7 +2912,7 @@ function compareValue(compare) { } function chord() { - return chord$1(false, false); + return chord$1(false); } function chord$1(directed, transpose) { @@ -2619,9 +2929,7 @@ function chord$1(directed, transpose) { groups = new Array(n), k = 0, dx; - matrix = Float64Array.from({length: n * n}, transpose - ? (_, i) => matrix[i % n][i / n | 0] - : (_, i) => matrix[i / n | 0][i % n]); + matrix = Float64Array.from({length: n * n}, (_, i) => matrix[i / n | 0][i % n]); // Compute the scaling factor from value to angle in [0, 2pi]. for (let i = 0; i < n; ++i) { @@ -2638,20 +2946,7 @@ function chord$1(directed, transpose) { if (sortGroups) groupIndex.sort((a, b) => sortGroups(groupSums[a], groupSums[b])); for (const i of groupIndex) { const x0 = x; - if (directed) { - const subgroupIndex = range$1(~n + 1, n).filter(j => j < 0 ? matrix[~j * n + i] : matrix[i * n + j]); - if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(a < 0 ? -matrix[~a * n + i] : matrix[i * n + a], b < 0 ? -matrix[~b * n + i] : matrix[i * n + b])); - for (const j of subgroupIndex) { - if (j < 0) { - const chord = chords[~j * n + i] || (chords[~j * n + i] = {source: null, target: null}); - chord.target = {index: i, startAngle: x, endAngle: x += matrix[~j * n + i] * k, value: matrix[~j * n + i]}; - } else { - const chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); - chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - } - } - groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; - } else { + { const subgroupIndex = range$1(0, n).filter(j => matrix[i * n + j] || matrix[j * n + i]); if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(matrix[i * n + a], matrix[i * n + b])); for (const j of subgroupIndex) { @@ -2726,7 +3021,7 @@ function appendRound(digits) { }; } -let Path$1 = class Path { +let Path$2 = class Path { constructor(digits) { this._x0 = this._y0 = // start of current subpath this._x1 = this._y1 = null; // end of current subpath @@ -2848,11 +3143,11 @@ let Path$1 = class Path { }; function path() { - return new Path$1; + return new Path$2; } // Allow instanceof d3.path -path.prototype = Path$1.prototype; +path.prototype = Path$2.prototype; var slice = Array.prototype.slice; @@ -2921,12 +3216,7 @@ function ribbon(headRadius) { context.moveTo(sr * cos$1(sa0), sr * sin$1(sa0)); context.arc(0, 0, sr, sa0, sa1); if (sa0 !== ta0 || sa1 !== ta1) { - if (headRadius) { - var hr = +headRadius.apply(this, arguments), tr2 = tr - hr, ta2 = (ta0 + ta1) / 2; - context.quadraticCurveTo(0, 0, tr2 * cos$1(ta0), tr2 * sin$1(ta0)); - context.lineTo(tr * cos$1(ta2), tr * sin$1(ta2)); - context.lineTo(tr2 * cos$1(ta1), tr2 * sin$1(ta1)); - } else { + { context.quadraticCurveTo(0, 0, tr * cos$1(ta0), tr * sin$1(ta0)); context.arc(0, 0, tr, ta0, ta1); } @@ -2937,10 +3227,6 @@ function ribbon(headRadius) { if (buffer) return context = null, buffer + "" || null; } - if (headRadius) ribbon.headRadius = function(_) { - return arguments.length ? (headRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : headRadius; - }; - ribbon.radius = function(_) { return arguments.length ? (sourceRadius = targetRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : sourceRadius; }; @@ -3139,12 +3425,12 @@ function array(x) { return x == null ? [] : Array.isArray(x) ? x : Array.from(x); } -function empty() { +function empty$1() { return []; } function selectorAll(selector) { - return selector == null ? empty : function() { + return selector == null ? empty$1 : function() { return this.querySelectorAll(selector); }; } @@ -4333,7 +4619,7 @@ function bisector(f) { compare2 = (d, x) => ascending(f(d), x); delta = (d, x) => f(d) - x; } else { - compare1 = f === ascending || f === descending ? f : zero$1; + compare1 = f === ascending || f === descending ? f : zero$1$1; compare2 = f; delta = f; } @@ -4370,7 +4656,7 @@ function bisector(f) { return {left, center, right}; } -function zero$1() { +function zero$1$1() { return 0; } @@ -4381,7 +4667,6 @@ function number$2(x) { const ascendingBisect = bisector(ascending); const bisectRight = ascendingBisect.right; bisector(number$2).center; -var bisect = bisectRight; const e10 = Math.sqrt(50), e5 = Math.sqrt(10), @@ -4764,7 +5049,7 @@ function identity$2(x) { return x; } -function normalize$1(a, b) { +function normalize$2(a, b) { return (b -= (a = +a)) ? function(x) { return (x - a) / b; } : constants(isNaN(b) ? NaN : 0.5); @@ -4780,8 +5065,8 @@ function clamper(a, b) { // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. function bimap(domain, range, interpolate) { var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; - if (d1 < d0) d0 = normalize$1(d1, d0), r0 = interpolate(r1, r0); - else d0 = normalize$1(d0, d1), r0 = interpolate(r0, r1); + if (d1 < d0) d0 = normalize$2(d1, d0), r0 = interpolate(r1, r0); + else d0 = normalize$2(d0, d1), r0 = interpolate(r0, r1); return function(x) { return r0(d0(x)); }; } @@ -4798,12 +5083,12 @@ function polymap(domain, range, interpolate) { } while (++i < j) { - d[i] = normalize$1(domain[i], domain[i + 1]); + d[i] = normalize$2(domain[i], domain[i + 1]); r[i] = interpolate(range[i], range[i + 1]); } return function(x) { - var i = bisect(domain, x, 1, j) - 1; + var i = bisectRight(domain, x, 1, j) - 1; return r[i](d[i](x)); }; } @@ -5529,7 +5814,7 @@ function timeInterval(floori, offseti, count, field) { if (step < 0) while (++step <= 0) { while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty } else while (--step >= 0) { - while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty + while (offseti(date, 1), !test(date)) {} // eslint-disable-line no-empty } } }); @@ -6689,7 +6974,7 @@ function withPath(shape) { return shape; }; - return () => new Path$1(digits); + return () => new Path$2(digits); } function arcInnerRadius(d) { @@ -6969,7 +7254,7 @@ var frame = 0, // is an animation frame pending? clock = typeof performance === "object" && performance.now ? performance : Date, setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; -function now$1() { +function now() { return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); } @@ -6987,7 +7272,7 @@ Timer.prototype = timer.prototype = { constructor: Timer, restart: function(callback, delay, time) { if (typeof callback !== "function") throw new TypeError("callback is not a function"); - time = (time == null ? now$1() : +time) + (delay == null ? 0 : +delay); + time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); if (!this._next && taskTail !== this) { if (taskTail) taskTail._next = this; else taskHead = this; @@ -7013,7 +7298,7 @@ function timer(callback, delay, time) { } function timerFlush() { - now$1(); // Get the current time, if not already set. + now(); // Get the current time, if not already set. ++frame; // Pretend we’ve set an alarm, if we haven’t already. var t = taskHead, e; while (t) { @@ -7911,7 +8196,7 @@ var defaultTiming = { ease: cubicInOut }; -function inherit(node, id) { +function inherit$1(node, id) { var timing; while (!(timing = node.__transition) || !(timing = timing[id])) { if (!(node = node.parentNode)) { @@ -7928,13 +8213,13 @@ function selection_transition(name) { if (name instanceof Transition) { id = name._id, name = name._name; } else { - id = newId(), (timing = defaultTiming).time = now$1(), name = name == null ? null : name + ""; + id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; } for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { if (node = group[i]) { - schedule(node, name, id, i, group, timing || inherit(node, id)); + schedule(node, name, id, i, group, timing || inherit$1(node, id)); } } } @@ -7945,3067 +8230,3360 @@ function selection_transition(name) { selection.prototype.interrupt = selection_interrupt; selection.prototype.transition = selection_transition; -const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'Symbol', kWingdings = 'Wingdings', -// average width taken from symbols.html, counted only for letters and digits -root_fonts = [null, // index 0 not exists - { n: kTimes, s: 'italic', aw: 0.5314 }, - { n: kTimes, w: 'bold', aw: 0.5809 }, - { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, - { n: kArial, aw: 0.5778 }, - { n: kArial, s: 'oblique', aw: 0.5783 }, - { n: kArial, w: 'bold', aw: 0.6034 }, - { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, - { n: kCourier, aw: 0.6003 }, - { n: kCourier, s: 'oblique', aw: 0.6004 }, - { n: kCourier, w: 'bold', aw: 0.6003 }, - { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, - { n: kSymbol, aw: 0.5521 }, - { n: kTimes, aw: 0.5521 }, - { n: kWingdings, aw: 0.5664 }, - { n: kSymbol, s: 'italic', aw: 0.5314 }, - { n: kVerdana, aw: 0.5664 }, - { n: kVerdana, s: 'italic', aw: 0.5495 }, - { n: kVerdana, w: 'bold', aw: 0.5748 }, - { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }]; - -/** - * @summary Helper class for font handling - * @private - */ +const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', + kWhite = 0, kBlack = 1; -class FontHandler { +/** @summary Covert value between 0 and 1 into decimal string using scale factor, used for colors coding + * @private */ +function toDec(num, scale = 255) { + return Math.round(num * scale).toString(10); +} - /** @summary constructor */ - constructor(fontIndex, size, scale) { - if (scale && (size < 1)) { - size *= scale; - this.scaled = true; - } +/** @summary Convert alfa value from rgba to string + * @private */ +function toAlfa(a) { + const res = a.toFixed(2); + if ((res.length === 4) && (res[3] === '0')) + return res.slice(0, 3); + return res; +} - this.size = Math.round(size || 11); - this.scale = scale; +/** @summary Convert r,g,b,a values to string + * @private */ +function toColor(r, g, b, a = 1) { + return (a !== undefined) && (a !== 1) + ? `rgba(${toDec(r)}, ${toDec(g)}, ${toDec(b)}, ${toAlfa(a)})` + : `rgb(${toDec(r)}, ${toDec(g)}, ${toDec(b)})`; +} - this.func = this.setFont.bind(this); +/** @summary Convert color string to unify node.js and browser + * @private */ +function convertColor(col) { + return (isNodeJs() || (isBatchMode() && settings.ApproxTextSize)) && (col[0] === '#' || col[0] === 'r') ? color(col).formatRgb() : col; +} - const indx = (fontIndex && Number.isInteger(fontIndex)) ? Math.floor(fontIndex / 10) : 0, - cfg = root_fonts[indx]; +/** @summary list of global root colors + * @private */ +let gbl_colors_list = []; - if (cfg) - this.setNameStyleWeight(cfg.n, cfg.s, cfg.w, cfg.aw, cfg.format, cfg.base64); - else - this.setNameStyleWeight(kArial); +/** @summary Generates all root colors, used also in jstests to reset colors + * @private */ +function createRootColors() { + function conv(arg) { + const r = Number.parseInt(arg.slice(0, 2), 16), + g = Number.parseInt(arg.slice(2, 4), 16), + b = Number.parseInt(arg.slice(4, 6), 16); + return `rgb(${r}, ${g}, ${b})`; } - /** @summary Directly set name, style and weight for the font - * @private */ - setNameStyleWeight(name, style, weight, aver_width, format, base64) { - this.name = name; - this.style = style || null; - this.weight = weight || null; - this.aver_width = aver_width || (weight ? 0.58 : 0.55); - this.format = format; // format of custom font, ttf by default - this.base64 = base64; // indication of custom font - if ((this.name === kSymbol) || (this.name === kWingdings)) { - this.isSymbol = this.name; - this.name = kTimes; - } else - this.isSymbol = ''; - } + const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', conv('59d454'), conv('5954d9'), 'white']; + colorMap[110] = 'white'; - /** @summary Set painter for which font will be applied */ - setPainter(painter) { - this.painter = painter; - } + const moreCol = [ + { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff18006f2da8a52a2ab2beb55790fcf89c20e42536964a8b9c9ca17a21dd1845fbff5e02c91f16c849a9adad7d86c8dd578dff6563643f90daffa90ebd1f0194a4a2832db6a96b59e76300b9ac7071758192daddb2b2b2' }, + { n: 201, s: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' }, + { n: 390, s: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300' }, + { n: 406, s: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300' }, + { n: 422, s: 'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333' }, + { n: 590, s: 'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033' }, + { n: 606, s: 'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033' }, + { n: 622, s: 'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000' }, + { n: 791, s: 'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a' }, + { n: 920, s: 'cdcdcd9a9a9a666666333333' }]; - /** @summary Assigns font-related attributes */ - addCustomFontToSvg(svg) { - if (!this.base64 || !this.name) - return; - const clname = 'custom_font_' + this.name, fmt = 'ttf'; - let defs = svg.selectChild('.canvas_defs'); - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - const entry = defs.selectChild('.' + clname); - if (entry.empty()) { - console.log('Adding style entry for class', clname); - defs.append('style') - .attr('class', clname) - .property('$fonthandler', this) - .text(`@font-face { font-family: "${this.name}"; font-weight: normal; font-style: normal; src: url(data:application/font-${fmt};charset=utf-8;base64,${this.base64}); }`); + moreCol.forEach(entry => { + const s = entry.s; + for (let n = 0; n < s.length; n += 6) { + const num = entry.n + n / 6; + colorMap[num] = conv(s.slice(n, n + 6)); } - } + }); - /** @summary Assigns font-related attributes */ - setFont(selection) { - if (this.base64 && this.painter) - this.addCustomFontToSvg(this.painter.getCanvSvg()); + gbl_colors_list = colorMap; +} - selection.attr('font-family', this.name) - .attr('font-size', this.size) - .attr('xml:space', 'preserve') - .attr('font-weight', this.weight || null) - .attr('font-style', this.style || null); - } +/** @summary Get list of colors + * @private */ +function getRootColors() { + return gbl_colors_list; +} - /** @summary Set font size (optional) */ - setSize(size) { this.size = Math.round(size); } +/** @summary Produces rgb code for TColor object + * @private */ +function getRGBfromTColor(col) { + if (col?._typename !== clTColor) + return null; - /** @summary Set text color (optional) */ - setColor(color) { this.color = color; } + const rgb = toColor(col.fRed, col.fGreen, col.fBlue, col.fAlpha); - /** @summary Set text align (optional) */ - setAlign(align) { this.align = align; } + switch (rgb) { + case 'rgb(255, 255, 255)': return 'white'; + case 'rgb(0, 0, 0)': return 'black'; + case 'rgb(255, 0, 0)': return 'red'; + case 'rgb(0, 255, 0)': return 'green'; + case 'rgb(0, 0, 255)': return 'blue'; + case 'rgb(255, 255, 0)': return 'yellow'; + case 'rgb(255, 0, 255)': return 'magenta'; + case 'rgb(0, 255, 255)': return 'cyan'; + } + return rgb; +} - /** @summary Set text angle (optional) */ - setAngle(angle) { this.angle = angle; } +/** @summary Return list of grey colors for the original array + * @private */ +function getGrayColors(rgb_array) { + const gray_colors = []; - /** @summary Allign angle to step raster, add optional offset */ - roundAngle(step, offset) { - this.angle = parseInt(this.angle || 0); - if (!Number.isInteger(this.angle)) this.angle = 0; - this.angle = Math.round(this.angle/step) * step + (offset || 0); - if (this.angle < 0) - this.angle += 360; - else if (this.angle >= 360) - this.angle -= 360; - } + if (!rgb_array) + rgb_array = getRootColors(); - /** @summary Clears all font-related attributes */ - clearFont(selection) { - selection.attr('font-family', null) - .attr('font-size', null) - .attr('xml:space', null) - .attr('font-weight', null) - .attr('font-style', null); + for (let n = 0; n < rgb_array.length; ++n) { + if (!rgb_array[n]) + continue; + const rgb = color(rgb_array[n]), + gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; + rgb.r = rgb.g = rgb.b = gray; + gray_colors[n] = rgb.formatRgb(); } - /** @summary Returns true in case of monospace font - * @private */ - isMonospace() { - const n = this.name.toLowerCase(); - return (n.indexOf('courier') === 0) || (n === 'monospace') || (n === 'monaco'); + return gray_colors; +} + +/** @summary Add new colors from object array + * @private */ +function extendRootColors(jsarr, objarr, grayscale) { + if (!jsarr) { + jsarr = []; + for (let n = 0; n < gbl_colors_list.length; ++n) + jsarr[n] = gbl_colors_list[n]; } - /** @summary Return full font declaration which can be set as font property like '12pt Arial bold' - * @private */ - getFontHtml() { - let res = Math.round(this.size) + 'pt ' + this.name; - if (this.weight) res += ' ' + this.weight; - if (this.style) res += ' ' + this.style; - return res; + if (!objarr) + return jsarr; + + let rgb_array = objarr; + if (objarr._typename && objarr.arr) { + rgb_array = []; + for (let n = 0; n < objarr.arr.length; ++n) { + const col = objarr.arr[n]; + if ((col?._typename === clTLinearGradient) || (col?._typename === clTRadialGradient)) { + rgb_array[col.fNumber] = col; + col.toString = () => 'white'; + continue; + } + + if (col?._typename !== clTColor) + continue; + + if ((col.fNumber >= 0) && (col.fNumber <= 10000)) + rgb_array[col.fNumber] = getRGBfromTColor(col); + } } - /** @summary Returns font name */ - getFontName() { - return this.isSymbol || this.name || 'none'; + for (let n = 0; n < rgb_array.length; ++n) { + if (rgb_array[n] && (jsarr[n] !== rgb_array[n])) + jsarr[n] = rgb_array[n]; } -} // class FontHandler + return grayscale ? getGrayColors(jsarr) : jsarr; +} -/** @summary Register custom font +/** @summary Set global list of colors. + * @desc Either TObjArray of TColor instances or just plain array with rgb() code. + * List of colors typically stored together with TCanvas primitives * @private */ -function addCustomFont(index, name, format, base64) { - if (!Number.isInteger(index)) - console.error(`Wrong index ${index} for custom font`); - else - root_fonts[index] = { n: name, format, base64 }; +function adoptRootColors(objarr) { + extendRootColors(gbl_colors_list, objarr); } -/** @summary Return handle with custom font +/** @summary Return ROOT color by index + * @desc Color numbering corresponds typical ROOT colors + * @return {String} with RGB color code or existing color name like 'cyan' * @private */ -function getCustomFont(name) { - return root_fonts.find(h => (h?.n === name) && h?.base64); +function getColor(indx) { + return gbl_colors_list[indx]; } -/** @summary Try to detect and create font handler for SVG text node +/** @summary Search for specified color in the list of colors + * @return Color index or -1 if fails * @private */ -function detectFont(node) { - const sz = node.getAttribute('font-size'), - family = node.getAttribute('font-family'), - p = sz.indexOf('px'), - sz_pixels = p > 0 ? Number.parseInt(sz.slice(0, p)) : 12; - let style = node.getAttribute('font-style'), - weight = node.getAttribute('font-weight'), - fontIndx = null, name = ''; - if (weight === 'normal') - weight = ''; - else if (weight === 'bold') - name += 'b'; - if (style === 'normal') - style = ''; - else if (style === 'italic') - name += 'i'; - else if (style === 'oblique') - name += 'o'; - - if (family === 'arial') - name += 'Arial'; - else if (family === 'times') - name += 'Times New Roman'; - else if (family === 'verdana') - name += 'Verdana'; - - for (let n = 1; n < root_fonts.length; ++n) { - if (name === root_fonts[n]) { - fontIndx = n*10 + 2; - break; - } +function findColor(name) { + if (!name) + return -1; + for (let indx = 0; indx < gbl_colors_list.length; ++indx) { + if (gbl_colors_list[indx] === name) + return indx; } - - const handler = new FontHandler(fontIndx, sz_pixels); - if (!fontIndx) - handler.setNameStyleWeight(family, style, weight); - return handler; + return -1; } -const symbols_map = { - // greek letters from symbols.ttf - '#alpha': '\u03B1', - '#beta': '\u03B2', - '#chi': '\u03C7', - '#delta': '\u03B4', - '#varepsilon': '\u03B5', - '#phi': '\u03C6', - '#gamma': '\u03B3', - '#eta': '\u03B7', - '#iota': '\u03B9', - '#varphi': '\u03C6', - '#kappa': '\u03BA', - '#lambda': '\u03BB', - '#mu': '\u03BC', - '#nu': '\u03BD', - '#omicron': '\u03BF', - '#pi': '\u03C0', - '#theta': '\u03B8', - '#rho': '\u03C1', - '#sigma': '\u03C3', - '#tau': '\u03C4', - '#upsilon': '\u03C5', - '#varomega': '\u03D6', - '#omega': '\u03C9', - '#xi': '\u03BE', - '#psi': '\u03C8', - '#zeta': '\u03B6', - '#Alpha': '\u0391', - '#Beta': '\u0392', - '#Chi': '\u03A7', - '#Delta': '\u0394', - '#Epsilon': '\u0395', - '#Phi': '\u03A6', - '#Gamma': '\u0393', - '#Eta': '\u0397', - '#Iota': '\u0399', - '#vartheta': '\u03D1', - '#Kappa': '\u039A', - '#Lambda': '\u039B', - '#Mu': '\u039C', - '#Nu': '\u039D', - '#Omicron': '\u039F', - '#Pi': '\u03A0', - '#Theta': '\u0398', - '#Rho': '\u03A1', - '#Sigma': '\u03A3', - '#Tau': '\u03A4', - '#Upsilon': '\u03A5', - '#varsigma': '\u03C2', - '#Omega': '\u03A9', - '#Xi': '\u039E', - '#Psi': '\u03A8', - '#Zeta': '\u0396', - '#varUpsilon': '\u03D2', - '#epsilon': '\u03B5', - - // second set from symbols.ttf - '#leq': '\u2264', - '#/': '\u2044', - '#infty': '\u221E', - '#voidb': '\u0192', - '#club': '\u2663', - '#diamond': '\u2666', - '#heart': '\u2665', - '#spade': '\u2660', - '#leftrightarrow': '\u2194', - '#leftarrow': '\u2190', - '#uparrow': '\u2191', - '#rightarrow': '\u2192', - '#downarrow': '\u2193', - '#circ': '\u2E30', - '#pm': '\xB1', - '#doublequote': '\u2033', - '#geq': '\u2265', - '#times': '\xD7', - '#propto': '\u221D', - '#partial': '\u2202', - '#bullet': '\u2022', - '#divide': '\xF7', - '#neq': '\u2260', - '#equiv': '\u2261', - '#approx': '\u2248', // should be \u2245 ? - '#3dots': '\u2026', - '#cbar': '\x7C', - '#topbar': '\xAF', - '#downleftarrow': '\u21B5', - '#aleph': '\u2135', - '#Jgothic': '\u2111', - '#Rgothic': '\u211C', - '#voidn': '\u2118', - '#otimes': '\u2297', - '#oplus': '\u2295', - '#oslash': '\u2205', - '#cap': '\u2229', - '#cup': '\u222A', - '#supset': '\u2283', - '#supseteq': '\u2287', - '#notsubset': '\u2284', - '#subset': '\u2282', - '#subseteq': '\u2286', - '#in': '\u2208', - '#notin': '\u2209', - '#angle': '\u2220', - '#nabla': '\u2207', - '#oright': '\xAE', - '#ocopyright': '\xA9', - '#trademark': '\u2122', - '#prod': '\u220F', - '#surd': '\u221A', - '#upoint': '\u2027', - '#corner': '\xAC', - '#wedge': '\u2227', - '#vee': '\u2228', - '#Leftrightarrow': '\u21D4', - '#Leftarrow': '\u21D0', - '#Uparrow': '\u21D1', - '#Rightarrow': '\u21D2', - '#Downarrow': '\u21D3', - '#void2': '', // dummy, placeholder - '#LT': '\x3C', - '#void1': '\xAE', - '#copyright': '\xA9', - '#void3': '\u2122', // it is dummy placeholder, TM - '#sum': '\u2211', - '#arctop': '\u239B', - '#lbar': '\u23A2', - '#arcbottom': '\u239D', - '#void4': '', // dummy, placeholder - '#void8': '\u23A2', // same as lbar - '#bottombar': '\u230A', - '#arcbar': '\u23A7', - '#ltbar': '\u23A8', - '#AA': '\u212B', - '#aa': '\xE5', - '#void06': '', - '#GT': '\x3E', - '#int': '\u222B', - '#forall': '\u2200', - '#exists': '\u2203', - // here ends second set from symbols.ttf +/** @summary Add new color + * @param {string} rgb - color name or just string with rgb value + * @param {array} [lst] - optional colors list, to which add colors + * @param {array} [lst] - optional colors list, to which add colors + * @return {number} index of new color + * @private */ +function addColor(rgb, lst, indx) { + if (!lst) + lst = gbl_colors_list; - // more greek symbols - '#koppa': '\u03DF', - '#sampi': '\u03E1', - '#stigma': '\u03DB', - '#san': '\u03FB', - '#sho': '\u03F8', - '#varcoppa': '\u03D9', - '#digamma': '\u03DD', - '#Digamma': '\u03DC', - '#Koppa': '\u03DE', - '#varKoppa': '\u03D8', - '#Sampi': '\u03E0', - '#Stigma': '\u03DA', - '#San': '\u03FA', - '#Sho': '\u03F7', + if ((rgb[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) + rgb = color(rgb).formatRgb(); - '#vec': '', - '#dot': '\u22C5', - '#hat': '\xB7', - '#ddot': '', - '#acute': '', - '#grave': '', - '#check': '\u2713', - '#tilde': '\u02DC', - '#slash': '\u2044', - '#hbar': '\u0127', - '#box': '\u25FD', - '#Box': '\u2610', - '#parallel': '\u2225', - '#perp': '\u22A5', - '#odot': '\u2299', - '#left': '', - '#right': '', - '{}': '', + if (indx !== undefined) { + if (Number.isInteger(indx) && (indx > 0)) + lst[indx] = rgb; + return indx; + } - '#mp': '\u2213', + indx = lst.indexOf(rgb); + if (indx >= 0) + return indx; + lst.push(rgb); + return lst.length - 1; +} - '#P': '\u00B6', // paragraph +/** + * @summary Color palette handle + * + * @private + */ - // only required for MathJax to provide correct replacement - '#sqrt': '\u221A', - '#bar': '', - '#overline': '', - '#underline': '', - '#strike': '' -}, +class ColorPalette { + /** @summary constructor */ + constructor(arr, grayscale) { + this.palette = grayscale ? getGrayColors(arr) : arr; + } + /** @summary Returns color index which correspond to contour index of provided length */ + calcColorIndex(i, len) { + const plen = this.palette.length, theColor = Math.floor((i + 0.99) * plen / (len - 1)); + return (theColor > plen - 1) ? plen - 1 : theColor; + } -/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first - * @private */ -symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'), + /** @summary Returns color with provided index */ + getColor(indx) { return this.palette[indx]; } -/** @summary Simple replacement of latex letters - * @private */ -translateLaTeX = str => { - while ((str.length > 2) && (str[0] === '{') && (str[str.length - 1] === '}')) - str = str.slice(1, str.length - 1); - - return str.replace(symbolsRegexCache, ch => symbols_map[ch]).replace(/\{\}/g, ''); -}, - -// array with relative width of base symbols from range 32..126 -// eslint-disable-next-line -base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,431,570,468,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069], + /** @summary Returns number of colors in the palette */ + getLength() { return this.palette.length; } -// eslint-disable-next-line -extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; + /** @summary Calculate color for given i and len */ + calcColor(i, len) { return this.getColor(this.calcColorIndex(i, len)); } -/** @ummary Calculate approximate labels width - * @private */ -function approximateLabelWidth(label, font, fsize) { - const len = label.length, - symbol_width = (fsize || font.size) * font.aver_width; - if (font.isMonospace()) - return len * symbol_width; +} // class ColorPalette - let sum = 0; - for (let i = 0; i < len; ++i) { - const code = label.charCodeAt(i); - if ((code >= 32) && (code < 127)) - sum += base_symbols_width[code - 32]; - else - sum += extra_symbols_width[code] || 1000; +function createDefaultPalette(grayscale) { + const hue2rgb = (p, q, t) => { + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t < 1 / 6) + return p + (q - p) * 6 * t; + if (t < 1 / 2) + return q; + if (t < 2 / 3) + return p + (q - p) * (2 / 3 - t) * 6; + return p; + }, HLStoRGB = (h, l, s) => { + const q = l + s - l * s, + p = 2 * l - q, + r = hue2rgb(p, q, h + 1 / 3), + g = hue2rgb(p, q, h), + b = hue2rgb(p, q, h - 1 / 3); + return toColor(r, g, b); + }, minHue = 0, maxHue = 280, maxPretty = 50, palette = []; + for (let i = 0; i < maxPretty; ++i) { + const hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360; + palette.push(HLStoRGB(hue, 0.5, 1)); } + return new ColorPalette(palette, grayscale); +} - return sum/1000*symbol_width; +function createGrayPalette() { + const palette = []; + for (let i = 0; i < 50; ++i) { + const code = toDec((i + 2) / 60); + palette.push(`rgb(${code}, ${code}, ${code})`); + } + return new ColorPalette(palette); } -/** @summary array defines features supported by latex parser, used by both old and new parsers +/** @summary Create color palette * @private */ -const latex_features = [ - { name: '#it{' }, // italic - { name: '#bf{' }, // bold - { name: '#underline{', deco: 'underline' }, // underline - { name: '#overline{', deco: 'overline' }, // overline - { name: '#strike{', deco: 'line-through' }, // line through - { name: '#kern[', arg: 'float' }, // horizontal shift - { name: '#lower[', arg: 'float' }, // vertical shift - { name: '#scale[', arg: 'float' }, // font scale - { name: '#color[', arg: 'int' }, // font color - { name: '#font[', arg: 'int' }, // font face - { name: '_{', low_up: 'low' }, // subscript - { name: '^{', low_up: 'up' }, // superscript - { name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305' - { name: '#hat{', accent: '\u02C6', hasw: true }, // '\u0302' - { name: '#check{', accent: '\u02C7', hasw: true }, // '\u030C' - { name: '#acute{', accent: '\u02CA' }, // '\u0301' - { name: '#grave{', accent: '\u02CB' }, // '\u0300' - { name: '#dot{', accent: '\u02D9' }, // '\u0307' - { name: '#ddot{', accent: '\u02BA', hasw: true }, // '\u0308' - { name: '#tilde{', accent: '\u02DC', hasw: true }, // '\u0303' - { name: '#slash{', accent: '\u2215' }, // '\u0337' - { name: '#vec{', accent: '\u02ED', hasw: true }, // '\u0350' arrowhead - { name: '#frac{', twolines: 'line' }, - { name: '#splitline{', twolines: true }, - { name: '#sqrt[', arg: 'int', sqrt: true }, // root with arbitrary power - { name: '#sqrt{', sqrt: true }, // square root - { name: '#sum', special: '\u2211', w: 0.8, h: 0.9 }, - { name: '#int', special: '\u222B', w: 0.3, h: 1.0 }, - { name: '#left[', right: '#right]', braces: '[]' }, - { name: '#left(', right: '#right)', braces: '()' }, - { name: '#left{', right: '#right}', braces: '{}' }, - { name: '#left|', right: '#right|', braces: '||' }, - { name: '#[]{', braces: '[]' }, - { name: '#(){', braces: '()' }, - { name: '#{}{', braces: '{}' }, - { name: '#||{', braces: '||' } -], - -// taken from: https://sites.math.washington.edu/~marshall/cxseminar/symbol.htm, starts from 33 -// eslint-disable-next-line -symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993], - -// taken from http://www.alanwood.net/demos/wingdings.html, starts from 33 -// eslint-disable-next-line -wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505], +function getColorPalette(id, grayscale) { + id = id || settings.Palette; + if ((id > 0) && (id < 10)) + return createGrayPalette(); + if (id < 51) + return createDefaultPalette(grayscale); + if (id > 113) + id = 57; + const stops = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; + let rgb; + /* eslint-disable @stylistic/js/comma-spacing */ + switch (id) { + // Deep Sea + case 51: rgb = [[0,9,13,17,24,32,27,25,29],[0,0,0,2,37,74,113,160,221],[28,42,59,78,98,129,154,184,221]]; break; + // Grey Scale + case 52: rgb = [[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255]]; break; + // Dark Body Radiator + case 53: rgb = [[0,45,99,156,212,230,237,234,242],[0,0,0,45,101,168,238,238,243],[0,1,1,3,9,8,11,95,230]]; break; + // Two-color hue (dark blue through neutral gray to bright yellow) + case 54: rgb = [[0,22,44,68,93,124,160,192,237],[0,16,41,67,93,125,162,194,241],[97,100,99,99,93,68,44,26,74]]; break; + // Rain Bow + case 55: rgb = [[0,5,15,35,102,196,208,199,110],[0,48,124,192,206,226,97,16,0],[99,142,198,201,90,22,13,8,2]]; break; + // Inverted Dark Body Radiator + case 56: rgb = [[242,234,237,230,212,156,99,45,0],[243,238,238,168,101,45,0,0,0],[230,95,11,8,9,3,1,1,0]]; break; + // Bird (default, keep float for backward compatibility) + case 57: + rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982], + [42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716], + [134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; + break; + // Cubehelix + case 58: rgb = [[0,24,2,54,176,236,202,194,255],[0,29,92,129,117,120,176,236,255],[0,68,80,34,57,172,252,245,255]]; break; + // Green Red Violet + case 59: rgb = [[13,23,25,63,76,104,137,161,206],[95,67,37,21,0,12,35,52,79],[4,3,2,6,11,22,49,98,208]]; break; + // Blue Red Yellow + case 60: rgb = [[0,61,89,122,143,160,185,204,231],[0,0,0,0,14,37,72,132,235],[0,140,224,144,4,5,6,9,13]]; break; + // Ocean + case 61: rgb = [[14,7,2,0,5,11,55,131,229],[105,56,26,1,42,74,131,171,229],[2,21,35,60,92,113,160,185,229]]; break; + // Color Printable On Grey + case 62: rgb = [[0,0,0,70,148,231,235,237,244],[0,0,0,0,0,69,67,216,244],[0,102,228,231,177,124,137,20,244]]; break; + // Alpine + case 63: rgb = [[50,56,63,68,93,121,165,192,241],[66,81,91,96,111,128,155,189,241],[97,91,75,65,77,103,143,167,217]]; break; + // Aquamarine + case 64: rgb = [[145,166,167,156,131,114,101,112,132],[158,178,179,181,163,154,144,152,159],[190,199,201,192,176,169,160,166,190]]; break; + // Army + case 65: rgb = [[93,91,99,108,130,125,132,155,174],[126,124,128,129,131,121,119,153,173],[103,94,87,85,80,85,107,120,146]]; break; + // Atlantic + case 66: rgb = [[24,40,69,90,104,114,120,132,103],[29,52,94,127,150,162,159,151,101],[29,52,96,132,162,181,184,186,131]]; break; + // Aurora + case 67: rgb = [[46,38,61,92,113,121,132,150,191],[46,36,40,69,110,135,131,92,34],[46,80,74,70,81,105,165,211,225]]; break; + // Avocado + case 68: rgb = [[0,4,12,30,52,101,142,190,237],[0,40,86,121,140,172,187,213,240],[0,9,14,18,21,23,27,35,101]]; break; + // Beach + case 69: rgb = [[198,206,206,211,198,181,161,171,244],[103,133,150,172,178,174,163,175,244],[49,54,55,66,91,130,184,224,244]]; break; + // Black Body + case 70: rgb = [[243,243,240,240,241,239,186,151,129],[0,46,99,149,194,220,183,166,147],[6,8,36,91,169,235,246,240,233]]; break; + // Blue Green Yellow + case 71: rgb = [[22,19,19,25,35,53,88,139,210],[0,32,69,108,135,159,183,198,215],[77,96,110,116,110,100,90,78,70]]; break; + // Brown Cyan + case 72: rgb = [[68,116,165,182,189,180,145,111,71],[37,82,135,178,204,225,221,202,147],[16,55,105,147,196,226,232,224,178]]; break; + // CMYK + case 73: rgb = [[61,99,136,181,213,225,198,136,24],[149,140,96,83,132,178,190,135,22],[214,203,168,135,110,100,111,113,22]]; break; + // Candy + case 74: rgb = [[76,120,156,183,197,180,162,154,140],[34,35,42,69,102,137,164,188,197],[64,69,78,105,142,177,205,217,198]]; break; + // Cherry + case 75: rgb = [[37,102,157,188,196,214,223,235,251],[37,29,25,37,67,91,132,185,251],[37,32,33,45,66,98,137,187,251]]; break; + // Coffee + case 76: rgb = [[79,100,119,137,153,172,192,205,250],[63,79,93,103,115,135,167,196,250],[51,59,66,61,62,70,110,160,250]]; break; + // Dark Rain Bow + case 77: rgb = [[43,44,50,66,125,172,178,155,157],[63,63,85,101,138,163,122,51,39],[121,101,58,44,47,55,57,44,43]]; break; + // Dark Terrain + case 78: rgb = [[0,41,62,79,90,87,99,140,228],[0,57,81,93,85,70,71,125,228],[95,91,91,82,60,43,44,112,228]]; break; + // Fall + case 79: rgb = [[49,59,72,88,114,141,176,205,222],[78,72,66,57,59,75,106,142,173],[78,55,46,40,39,39,40,41,47]]; break; + // Fruit Punch + case 80: rgb = [[243,222,201,185,165,158,166,187,219],[94,108,132,135,125,96,68,51,61],[7,9,12,19,45,89,118,146,118]]; break; + // Fuchsia + case 81: rgb = [[19,44,74,105,137,166,194,206,220],[19,28,40,55,82,110,159,181,220],[19,42,68,96,129,157,188,203,220]]; break; + // Grey Yellow + case 82: rgb = [[33,44,70,99,140,165,199,211,216],[38,50,76,105,140,165,191,189,167],[55,67,97,124,140,166,163,129,52]]; break; + // Green Brown Terrain + case 83: rgb = [[0,33,73,124,136,152,159,171,223],[0,43,92,124,134,126,121,144,223],[0,43,68,76,73,64,72,114,223]]; break; + // Green Pink + case 84: rgb = [[5,18,45,124,193,223,205,128,49],[48,134,207,230,193,113,28,0,7],[6,15,41,121,193,226,208,130,49]]; break; + // Island + case 85: rgb = [[180,106,104,135,164,188,189,165,144],[72,126,154,184,198,207,205,190,179],[41,120,158,188,194,181,145,100,62]]; break; + // Lake + case 86: rgb = [[57,72,94,117,136,154,174,192,215],[0,33,68,109,140,171,192,196,209],[116,137,173,201,200,201,203,190,187]]; break; + // Light Temperature + case 87: rgb = [[31,71,123,160,210,222,214,199,183],[40,117,171,211,231,220,190,132,65],[234,214,228,222,210,160,105,60,34]]; break; + // Light Terrain + case 88: rgb = [[123,108,109,126,154,172,188,196,218],[184,138,130,133,154,175,188,196,218],[208,130,109,99,110,122,150,171,218]]; break; + // Mint + case 89: rgb = [[105,106,122,143,159,172,176,181,207],[252,197,194,187,174,162,153,136,125],[146,133,144,155,163,167,166,162,174]]; break; + // Neon + case 90: rgb = [[171,141,145,152,154,159,163,158,177],[236,143,100,63,53,55,44,31,6],[59,48,46,44,42,54,82,112,179]]; break; + // Pastel + case 91: rgb = [[180,190,209,223,204,228,205,152,91],[93,125,147,172,181,224,233,198,158],[236,218,160,133,114,132,162,220,218]]; break; + // Pearl + case 92: rgb = [[225,183,162,135,115,111,119,145,211],[205,177,166,135,124,117,117,132,172],[186,165,155,135,126,130,150,178,226]]; break; + // Pigeon + case 93: rgb = [[39,43,59,63,80,116,153,177,223],[39,43,59,74,91,114,139,165,223],[39,50,59,70,85,115,151,176,223]]; break; + // Plum + case 94: rgb = [[0,38,60,76,84,89,101,128,204],[0,10,15,23,35,57,83,123,199],[0,11,22,40,63,86,97,94,85]]; break; + // Red Blue + case 95: rgb = [[94,112,141,165,167,140,91,49,27],[27,46,88,135,166,161,135,97,58],[42,52,81,106,139,158,155,137,116]]; break; + // Rose + case 96: rgb = [[30,49,79,117,135,151,146,138,147],[63,60,72,90,94,94,68,46,16],[18,28,41,56,62,63,50,36,21]]; break; + // Rust + case 97: rgb = [[0,30,63,101,143,152,169,187,230],[0,14,28,42,58,61,67,74,91],[39,26,21,18,15,14,14,13,13]]; break; + // Sandy Terrain + case 98: rgb = [[149,140,164,179,182,181,131,87,61],[62,70,107,136,144,138,117,87,74],[40,38,45,49,49,49,38,32,34]]; break; + // Sienna + case 99: rgb = [[99,112,148,165,179,182,183,183,208],[39,40,57,79,104,127,148,161,198],[15,16,18,33,51,79,103,129,177]]; break; + // Solar + case 100: rgb = [[99,116,154,174,200,196,201,201,230],[0,0,8,32,58,83,119,136,173],[5,6,7,9,9,14,17,19,24]]; break; + // South West + case 101: rgb = [[82,106,126,141,155,163,142,107,66],[62,44,69,107,135,152,149,132,119],[39,25,31,60,73,68,49,72,188]]; break; + // Starry Night + case 102: rgb = [[18,29,44,72,116,158,184,208,221],[27,46,71,105,146,177,189,190,183],[39,55,80,108,130,133,124,100,76]]; break; + // Sunset + case 103: rgb = [[0,48,119,173,212,224,228,228,245],[0,13,30,47,79,127,167,205,245],[0,68,75,43,16,22,55,128,245]]; break; + // Temperature Map + case 104: rgb = [[34,70,129,187,225,226,216,193,179],[48,91,147,194,226,229,196,110,12],[234,212,216,224,206,110,53,40,29]]; break; + // Thermometer + case 105: rgb = [[30,55,103,147,174,203,188,151,105],[0,65,138,182,187,175,121,53,9],[191,202,212,208,171,140,97,57,30]]; break; + // Valentine + case 106: rgb = [[112,97,113,125,138,159,178,188,225],[16,17,24,37,56,81,110,136,189],[38,35,46,59,78,103,130,152,201]]; break; + // Visible Spectrum + case 107: rgb = [[18,72,5,23,29,201,200,98,29],[0,0,43,167,211,117,0,0,0],[51,203,177,26,10,9,8,3,0]]; break; + // Water Melon + case 108: rgb = [[19,42,64,88,118,147,175,187,205],[19,55,89,125,154,169,161,129,70],[19,32,47,70,100,128,145,130,75]]; break; + // Cool + case 109: rgb = [[33,31,42,68,86,111,141,172,227],[255,175,145,106,88,55,15,0,0],[255,205,202,203,208,205,203,206,231]]; break; + // Copper + case 110: rgb = [[0,25,50,79,110,145,181,201,254],[0,16,30,46,63,82,101,124,179],[0,12,21,29,39,49,61,74,103]]; break; + // Gist Earth + case 111: rgb = [[0,13,30,44,72,120,156,200,247],[0,36,84,117,141,153,151,158,247],[0,94,100,82,56,66,76,131,247]]; break; + // Viridis + case 112: rgb = [[26,51,43,33,28,35,74,144,246],[9,24,55,87,118,150,180,200,222],[30,96,112,114,112,101,72,35,0]]; break; + // Cividis + case 113: rgb = [[0,5,65,97,124,156,189,224,255],[32,54,77,100,123,148,175,203,234],[77,110,107,111,120,119,111,94,70]]; break; + default: return createDefaultPalette(); + } + /* eslint-enable @stylistic/js/comma-spacing */ -symbolsPdfMap = {}; + const NColors = 255, Red = rgb[0], Green = rgb[1], Blue = rgb[2], palette = []; -/** @summary Return code for symbols from symbols.ttf - * @desc Used in PDF generation - * @private */ -function remapSymbolTtfCode(code) { - if (!symbolsPdfMap[0x3B1]) { - let cnt = 0; - for (const key in symbols_map) { - const symbol = symbols_map[key]; - if (symbol.length === 1) { - let letter = 0; - if (cnt < 54) { - const opGreek = cnt; - // see code in TLatex.cxx, line 1302 - letter = 97 + opGreek; - if (opGreek > 25) letter -= 58; - if (opGreek === 52) letter = 0o241; // varUpsilon - if (opGreek === 53) letter = 0o316; // epsilon - } else { - // see code in TLatex.cxx, line 1323 - const opSpec = cnt - 54; - letter = 0o243 + opSpec; - switch (opSpec) { - case 75: letter = 0o305; break; // AA Angstroem - case 76: letter = 0o345; break; // aa Angstroem - case 80: letter = 0o42; break; // #forall - case 81: letter = 0o44; break; // #exists - } - } - const code = symbol.charCodeAt(0); - if (code > 0x80) - symbolsPdfMap[code] = letter; - } - if (++cnt > 54 + 82) break; + for (let g = 1; g < stops.length; g++) { + // create the colors... + const nColorsGradient = Math.round(Math.floor(NColors * stops[g]) - Math.floor(NColors * stops[g - 1])); + for (let c = 0; c < nColorsGradient; c++) { + const col = 'rgb(' + toDec(Red[g - 1] + c * (Red[g] - Red[g - 1]) / nColorsGradient, 1) + ', ' + + toDec(Green[g - 1] + c * (Green[g] - Green[g - 1]) / nColorsGradient, 1) + ', ' + + toDec(Blue[g - 1] + c * (Blue[g] - Blue[g - 1]) / nColorsGradient, 1) + ')'; + palette.push(col); } } - return symbolsPdfMap[code] ?? code; + + return new ColorPalette(palette, grayscale); } -/** @summary Reformat text node if it includes greek or special symbols - * @desc Used in PDF generation where greek symbols are not available - * @private */ -function replaceSymbolsInTextNode(node) { - if (node.childNodes.length !== 1) - return false; - const txt = node.textContent; - if (!txt) - return false; - let new_html = '', lasti = -1; - for (let i = 0; i < txt.length; i++) { - const code = txt.charCodeAt(i), - newcode = remapSymbolTtfCode(code); - if (code !== newcode) { - new_html += txt.slice(lasti+1, i) + ''+String.fromCharCode(newcode)+''; - lasti = i; +/** @summary Decode list of ROOT colors coded by TWebCanvas + * @private */ +function decodeWebCanvasColors(oper) { + const colors = [], arr = oper.split(';'), + convert_rgb = isNodeJs() || (isBatchMode() && settings.ApproxTextSize); + for (let n = 0; n < arr.length; ++n) { + const name = arr[n]; + let p = name.indexOf(':'); + if (p > 0) { + const col = `rgb(${name.slice(p + 1)})`; + colors[parseInt(name.slice(0, p))] = convert_rgb ? color(col).formatRgb() : col; + continue; } - } + p = name.indexOf('='); + if (p > 0) { + let col = `rgba(${name.slice(p + 1)})`; + if (convert_rgb) { + col = color(col); + col.opacity = (Math.round(col.opacity * 255) / 255).toFixed(2); + col = col.formatRgb(); + } + colors[parseInt(name.slice(0, p))] = col; + continue; + } + p = name.indexOf('#'); + if (p < 0) + continue; - if (lasti < 0) - return false; + const colindx = parseInt(name.slice(0, p)), + data = JSON.parse(name.slice(p + 1)), + grad = { _typename: data[0] === 10 ? clTLinearGradient : clTRadialGradient, fNumber: colindx, fType: data[0] }; - if (lasti < txt.length-1) - new_html += txt.slice(lasti+1, txt.length); + let cnt = 1; - node.$originalHTML = node.innerHTML; - node.innerHTML = new_html; - return true; -} + grad.fCoordinateMode = Math.round(data[cnt++]); + const nsteps = Math.round(data[cnt++]); + grad.fColorPositions = data.slice(cnt, cnt + nsteps); + cnt += nsteps; + grad.fColors = data.slice(cnt, cnt + 4 * nsteps); + cnt += 4 * nsteps; + grad.fStart = { fX: data[cnt++], fY: data[cnt++] }; + grad.fEnd = { fX: data[cnt++], fY: data[cnt++] }; + if (grad._typename === clTRadialGradient && cnt < data.length) { + grad.fR1 = data[cnt++]; + grad.fR2 = data[cnt]; + } -function replaceSymbols(s, kind) { - const m = (kind === 'Wingdings') ? wingdingsMap : symbolsMap; - let res = ''; - for (let k = 0; k < s.length; ++k) { - const code = s.charCodeAt(k), - new_code = (code > 32) ? m[code-33] : 0; - res += String.fromCodePoint(new_code || code); + colors[colindx] = grad; } - return res; -} -/** @summary Just add plain text to the SVG text elements - * @private */ -function producePlainText(painter, txt_node, arg) { - arg.plain = true; - if (arg.simple_latex) - arg.text = translateLaTeX(arg.text); // replace latex symbols - if (arg.font && arg.font.isSymbol) - txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol)); - else - txt_node.text(arg.text); + return colors; } -/** @summary Check if plain text - * @private */ -function isPlainText(txt) { - return !txt || ((txt.indexOf('#') < 0) && (txt.indexOf('{') < 0)); -} -/** @ummary translate TLatex and draw inside provided g element - * @desc use together with normal elements - * @private */ -function parseLatex(node, arg, label, curr) { - let nelements = 0; +createRootColors(); - const currG = () => { if (!curr.g) curr.g = node.append('svg:g'); return curr.g; }, +/** @summary Standard prefix for SVG file context as data url + * @private */ +const prSVG = 'data:image/svg+xml;charset=utf-8,', +/** @summary Standard prefix for JSON file context as data url + * @private */ + prJSON = 'data:application/json;charset=utf-8,'; - shiftX = dx => { curr.x += Math.round(dx); }, - extendPosition = (x1, y1, x2, y2) => { - if (!curr.rect) - curr.rect = { x1, y1, x2, y2 }; - else { - curr.rect.x1 = Math.min(curr.rect.x1, x1); - curr.rect.y1 = Math.min(curr.rect.y1, y1); - curr.rect.x2 = Math.max(curr.rect.x2, x2); - curr.rect.y2 = Math.max(curr.rect.y2, y2); - } +/** @summary Returns visible rect of element + * @param {object} elem - d3.select object with element + * @param {string} [kind] - which size method is used + * @desc kind = 'bbox' use getBBox, works only with SVG + * kind = 'full' - full size of element, using getBoundingClientRect function + * kind = 'nopadding' - excludes padding area + * With node.js can use 'width' and 'height' attributes when provided in element + * @private */ +function getElementRect(elem, sizearg) { + if (!elem || elem.empty()) + return { x: 0, y: 0, width: 0, height: 0 }; - curr.rect.last_y1 = y1; // upper position of last symbols + if ((isNodeJs() && (sizearg !== 'bbox')) || elem.property('_batch_mode')) + return { x: 0, y: 0, width: parseInt(elem.attr('width')), height: parseInt(elem.attr('height')) }; - curr.rect.width = curr.rect.x2 - curr.rect.x1; - curr.rect.height = curr.rect.y2 - curr.rect.y1; + const styleValue = name => { + let value = elem.style(name); + if (!value || !isStr(value)) + return 0; + value = parseFloat(value.replace('px', '')); + return !Number.isFinite(value) ? 0 : Math.round(value); + }; - if (!curr.parent) - arg.text_rect = curr.rect; - }, + let rect = elem.node().getBoundingClientRect(); + if ((sizearg === 'bbox') && (parseFloat(rect.width) > 0)) + rect = elem.node().getBBox(); - addSpaces = nspaces => { - extendPosition(curr.x, curr.y, curr.x + nspaces * curr.fsize * 0.4, curr.y); - shiftX(nspaces * curr.fsize * 0.4); - }, + const res = { x: 0, y: 0, width: parseInt(rect.width), height: parseInt(rect.height) }; + if (rect.left !== undefined) { + res.x = parseInt(rect.left); + res.y = parseInt(rect.top); + } else if (rect.x !== undefined) { + res.x = parseInt(rect.x); + res.y = parseInt(rect.y); + } - /** Position pos.g node which directly attached to curr.g and uses curr.g coordinates */ - positionGNode = (pos, x, y, inside_gg) => { - x = Math.round(x); - y = Math.round(y); + if ((sizearg === undefined) || (sizearg === 'nopadding')) { + // this is size exclude padding area + res.width -= styleValue('padding-left') + styleValue('padding-right'); + res.height -= styleValue('padding-top') + styleValue('padding-bottom'); + } - makeTranslate(pos.g, x, y); - pos.rect.x1 += x; - pos.rect.x2 += x; - pos.rect.y1 += y; - pos.rect.y2 += y; + return res; +} - if (inside_gg) - extendPosition(curr.x + pos.rect.x1, curr.y + pos.rect.y1, curr.x + pos.rect.x2, curr.y + pos.rect.y2); - else - extendPosition(pos.rect.x1, pos.rect.y1, pos.rect.x2, pos.rect.y2); - }, - /** Create special sub-container for elements like sqrt or braces */ - createGG = () => { - const gg = currG(); +/** @summary Calculate absolute position of provided element in canvas + * @private */ +function getAbsPosInCanvas(sel, pos) { + if (!pos) + return pos; - // this is indicator that gg element will be the only one, one can use directly main container - if ((nelements === 1) && !label && !curr.x && !curr.y) - return gg; + while (!sel.empty() && !sel.classed('root_canvas')) { + const cl = sel.attr('class'); + if (cl && ((cl.indexOf('root_frame') >= 0) || (cl.indexOf('__root_pad_') >= 0))) { + pos.x += sel.property('draw_x') || 0; + pos.y += sel.property('draw_y') || 0; + } + sel = select(sel.node().parentNode); + } + return pos; +} - return makeTranslate(gg.append('svg:g'), curr.x, curr.y); - }, - extractSubLabel = (check_first, lbrace, rbrace) => { - let pos = 0, n = 1, extra_braces = false; - if (!lbrace) lbrace = '{'; - if (!rbrace) rbrace = '}'; +/** @summary Converts numeric value to string according to specified format. + * @param {number} value - value to convert + * @param {string} [fmt='6.4g'] - format can be like 5.4g or 4.2e or 6.4f + * @param {boolean} [ret_fmt] - when true returns array with value and actual format like ['0.1','6.4f'] + * @return {string|Array} - converted value or array with value and actual format + * @private */ +function floatToString(value, fmt, ret_fmt) { + if (!fmt) + fmt = '6.4g'; + else if (fmt === 'g') + fmt = '7.5g'; - const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos+br.length) === br); + fmt = fmt.trim(); + const len = fmt.length; + if (len < 2) + return ret_fmt ? [value.toFixed(4), '6.4f'] : value.toFixed(4); - if (check_first) { - if (!match(lbrace)) { - console.log(`not starting with ${lbrace} in ${label}`); - return -1; - } else - label = label.slice(lbrace.length); - } + const kind = fmt[len - 1].toLowerCase(), + compact = (len > 1) && (fmt[len - 2] === 'c') ? 'c' : ''; + fmt = fmt.slice(0, len - (compact ? 2 : 1)); - while ((n !== 0) && (pos < label.length)) { - if (match(lbrace)) { - n++; - pos += lbrace.length; - } else if (match(rbrace)) { - n--; - pos += rbrace.length; - if ((n === 0) && (typeof check_first === 'string') && match(check_first + lbrace)) { - // handle special case like a^{b}^{2} should mean a^{b^{2}} - n++; - pos += lbrace.length + check_first.length; - check_first = true; - extra_braces = true; - } - } else pos++; - } - if (n !== 0) { - console.log(`mismatch with open ${lbrace} and closing ${rbrace} in ${label}`); - return -1; - } + if (kind === 'g') { + const se = floatToString(value, fmt + 'ce', true), + sg = floatToString(value, fmt + 'cf', true), + res = se[0].length < sg[0].length || ((sg[0] === '0') && value) ? se : sg; + return ret_fmt ? res : res[0]; + } - let sublabel = label.slice(0, pos - rbrace.length); + let isexp, prec = fmt.indexOf('.'); + prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec + 1)); + if (!Number.isInteger(prec) || (prec <= 0)) + prec = 4; - if (extra_braces) sublabel = lbrace + sublabel + rbrace; + switch (kind) { + case 'e': + isexp = true; + break; + case 'f': + isexp = false; + break; + default: + isexp = false; + prec = 4; + } - label = label.slice(pos); + if (isexp) { + let se = value.toExponential(prec); - return sublabel; - }, + if (compact) { + const pnt = se.indexOf('.'), + pe = se.toLowerCase().indexOf('e'); + if ((pnt > 0) && (pe > pnt)) { + let p = pe; + while ((p > pnt) && (se[p - 1] === '0')) + p--; + if (p === pnt + 1) + p--; + if (p !== pe) + se = se.slice(0, p) + se.slice(pe); + } + } - createPath = (gg, d, dofill) => { - return gg.append('svg:path') - .style('stroke', dofill ? 'none' : (curr.color || arg.color)) - .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize*(curr.font.weight ? 0.1 : 0.07)))) - .style('fill', dofill ? (curr.color || arg.color) : 'none') - .attr('d', d ?? null); - }, + return ret_fmt ? [se, `${prec + 2}.${prec}${compact}e`] : se; + } - createSubPos = fscale => { - return { lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize*(fscale || 1), color: curr.color, font: curr.font, parent: curr, painter: curr.painter }; - }; + let sg = value.toFixed(prec); - while (label) { - let best = label.length, found = null; + if (compact) { + let l = 0; + while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) + l++; - for (let n = 0; n < latex_features.length; ++n) { - const pos = label.indexOf(latex_features[n].name); - if ((pos >= 0) && (pos < best)) { best = pos; found = latex_features[n]; } + let diff = sg.length - l - prec; + if (sg.indexOf('.') > l) + diff--; + + if (diff) { + prec -= diff; + if (prec < 0) + prec = 0; + else if (prec > 20) + prec = 20; + sg = value.toFixed(prec); } - if (best > 0) { - const alone = (best === label.length) && (nelements === 0) && !found; + const pnt = sg.indexOf('.'); + if (pnt > 0) { + let p = sg.length; + while ((p > pnt) && (sg[p - 1] === '0')) + p--; + if (p === pnt + 1) + p--; + sg = sg.slice(0, p); + } - nelements++; + if (sg === '-0') + sg = '0'; + } - let s = translateLaTeX(label.slice(0, best)), - nbeginspaces = 0, nendspaces = 0; + return ret_fmt ? [sg, `${prec + 2}.${prec}${compact}f`] : sg; +} - while ((nbeginspaces < s.length) && (s[nbeginspaces] === ' ')) - nbeginspaces++; - if (nbeginspaces > 0) { - addSpaces(nbeginspaces); - s = s.slice(nbeginspaces); - } +/** @summary Draw options interpreter + * @private */ +class DrawOptions { - while ((nendspaces < s.length) && (s[s.length - 1 - nendspaces] === ' ')) - nendspaces++; + constructor(opt) { + if (isStr(opt)) { + this.origin = opt.trim(); + this.opt = this.origin.toUpperCase(); + } else + this.opt = this.origin = ''; + this.part = this.partO = ''; + } - if (nendspaces > 0) - s = s.slice(0, s.length - nendspaces); + /** @summary Returns true if remaining options are empty or contain only separators symbols. */ + empty() { return !this.opt ? true : !this.opt.replace(/[ ;_,]/g, ''); } - if (s || alone) { - // if single text element created, place it directly in the node - const g = curr.g || (alone ? node : currG()), - elem = g.append('svg:text'); + /** @summary Returns remaining part of the draw options. */ + remain() { return this.opt; } - if (alone && !curr.g) curr.g = elem; + /** @summary Remove [pos, pos2) part from the string */ + #cut(pos, pos2) { + this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); + this.origin = this.origin.slice(0, pos) + this.origin.slice(pos2); + } - // apply font attributes only once, inherited by all other elements - if (curr.ufont) { - curr.font.setPainter(arg.painter); - curr.font.setFont(curr.g); - } - - if (curr.bold !== undefined) - curr.g.attr('font-weight', curr.bold ? 'bold' : 'normal'); - - if (curr.italic !== undefined) - curr.g.attr('font-style', curr.italic ? 'italic' : 'normal'); - - // set fill color directly to element - elem.attr('fill', curr.color || arg.color || null); - - // set font size directly to element to avoid complex control - if (curr.fisze !== curr.font.size) - elem.attr('font-size', Math.round(curr.fsize)); - - if (curr.font && curr.font.isSymbol) - elem.text(replaceSymbols(s, curr.font.isSymbol)); - else - elem.text(s); - - const rect = !isNodeJs() && !settings.ApproxTextSize && !arg.fast - ? getElementRect(elem, 'nopadding') - : { height: curr.fsize * 1.2, width: approximateLabelWidth(s, curr.font, curr.fsize) }; + /** @summary Checks if given option exists */ + check(name, postpart) { + const pos = this.opt.indexOf(name); + if (pos < 0) + return false; + this.#cut(pos, pos + name.length); + this.part = ''; + if (!postpart) + return true; - if (curr.x) elem.attr('x', curr.x); - if (curr.y) elem.attr('y', curr.y); + let pos2 = pos; + const is_array = postpart === 'array'; + if (is_array) { + if (this.opt[pos2] !== '[') + return false; + while ((pos2 < this.opt.length) && (this.opt[pos2] !== ']')) + pos2++; + if (++pos2 > this.opt.length) + return false; + } else { + while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) + pos2++; + } + if (pos2 > pos) { + this.part = this.opt.slice(pos, pos2); + this.partO = this.origin.slice(pos, pos2); + this.#cut(pos, pos2); + } - // for single symbols like f,l.i one gets wrong estimation of total width, use it in sup/sub-scripts - const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1*curr.fsize : 0; + if (is_array) { + try { + this.array = JSON.parse(this.part); + } catch { + this.array = undefined; + } + return this.array?.length !== undefined; + } - extendPosition(curr.x, curr.y - rect.height*0.8, curr.x + rect.width, curr.y + rect.height*0.2); + if (postpart !== 'color') + return true; - if (!alone) { - shiftX(rect.width + xgap); - addSpaces(nendspaces); - curr.xgap = 0; - } else if (curr.deco) { - elem.attr('text-decoration', curr.deco); - delete curr.deco; // inform that decoration was applied - } else - curr.xgap = xgap; // may be used in accent or somewere else - } else - addSpaces(nendspaces); + if (((this.part.length === 6) || (this.part.length === 8)) && this.part.match(/^[a-fA-F0-9]+/)) { + this.color = addColor('#' + this.part); + return true; } - if (!found) return true; - - // remove preceeding block and tag itself - label = label.slice(best + found.name.length); + this.color = this.partAsInt(1) - 1; + if (this.color >= 0) + return true; + for (let col = 0; col < 8; ++col) { + if (getColor(col).toUpperCase() === this.part) { + this.color = col; + return true; + } + } + return false; + } - nelements++; + /** @summary Returns (original) part after found options. */ + getPart(origin) { return origin ? this.partO : this.part; } - if (found.accent) { - const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + /** @summary Returns remaining part of found option as integer. */ + partAsInt(offset, dflt) { + let mult = 1; + const last = this.part ? this.part.at(-1) : ''; + if (last === 'K') + mult = 1e3; + else if (last === 'M') + mult = 1e6; + else if (last === 'G') + mult = 1e9; + let val = this.part.replace(/^\D+/g, ''); + val = val ? parseInt(val, 10) : Number.NaN; + return !Number.isInteger(val) ? (dflt || 0) : mult * val + (offset || 0); + } - const gg = createGG(), - subpos = createSubPos(), - reduce = (sublabel.length !== 1) ? 1 : (((sublabel >= 'a') && (sublabel <= 'z') && ('tdbfhkli'.indexOf(sublabel) < 0)) ? 0.75 : 0.9); + /** @summary Returns remaining part of found option as float. */ + partAsFloat(offset, dflt) { + let val = this.part.replace(/^\D+/g, ''); + val = val ? parseFloat(val) : Number.NaN; + return !Number.isFinite(val) ? (dflt || 0) : val + (offset || 0); + } - parseLatex(gg, arg, sublabel, subpos); +} // class DrawOptions - const minw = curr.fsize * 0.6, - y1 = Math.round(subpos.rect.y1*reduce), - dy2 = Math.round(curr.fsize*0.1), dy = dy2*2, - dot = `a${dy2},${dy2},0,0,1,${dy},0a${dy2},${dy2},0,0,1,${-dy},0z`; - let xpos = 0, w = subpos.rect.width; - // shift symbol when it is too small - if (found.hasw && (w < minw)) { - w = minw; - xpos = (minw - subpos.rect.width) / 2; - } +/** @summary Simple random generator with controlled seed + * @private */ +class TRandom { - const w5 = Math.round(w*0.5), w3 = Math.round(w*0.3), w2 = w5-w3, w8 = w5+w3; - w = w5*2; + constructor(i) { + if (i !== undefined) + this.seed(i); + } - positionGNode(subpos, xpos, 0, true); + /** @summary Seed simple random generator */ + 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; + } - switch (found.name) { - case '#check{': createPath(gg, `M${w2},${y1-dy}L${w5},${y1}L${w8},${y1-dy}`); break; - case '#acute{': createPath(gg, `M${w5},${y1}l${dy},${-dy}`); break; - case '#grave{': createPath(gg, `M${w5},${y1}l${-dy},${-dy}`); break; - case '#dot{': createPath(gg, `M${w5-dy2},${y1}${dot}`, true); break; - case '#ddot{': createPath(gg, `M${w5-3*dy2},${y1}${dot} M${w5+dy2},${y1}${dot}`, true); break; - case '#tilde{': createPath(gg, `M${w2},${y1} a${w3},${dy},0,0,1,${w3},0 a${w3},${dy},0,0,0,${w3},0`); break; - case '#slash{': createPath(gg, `M${w},${y1}L0,${Math.round(subpos.rect.y2)}`); break; - case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8-dy},${y1-dy}l${dy},${dy}l${-dy},${dy}`); break; - default: createPath(gg, `M${w2},${y1}L${w5},${y1-dy}L${w8},${y1}`); // #hat{ - } + /** @summary Produce random value between 0 and 1 */ + 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; + let result = ((this.m_z << 16) + this.m_w) & 0xffffffff; + result /= 4294967296; + return result + 0.5; + } - shiftX(subpos.rect.width + (subpos.xgap ?? 0)); +} // class TRandom - continue; - } - if (found.twolines) { - curr.twolines = true; +/** @summary Build smooth SVG curve using Bezier + * @desc Reuse code from https://stackoverflow.com/questions/62855310 + * @private */ +function buildSvgCurve(p, args) { + if (!args) + args = {}; + if (!args.line) + args.calc = true; + else if (args.ndig === undefined) + args.ndig = 0; - const line1 = extractSubLabel(), line2 = extractSubLabel(true); - if ((line1 === -1) || (line2 === -1)) return false; + let npnts = p.length; + if (npnts < 3) + args.line = true; - const gg = createGG(), - fscale = (curr.parent && curr.parent.twolines) ? 0.7 : 1, - subpos1 = createSubPos(fscale); + args.t = args.t ?? 0.2; - parseLatex(gg, arg, line1, subpos1); + if ((args.ndig === undefined) || args.height) { + args.maxy = p[0].gry; + args.mindiff = 100; + for (let i = 1; i < npnts; i++) { + args.maxy = Math.max(args.maxy, p[i].gry); + args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i - 1].grx), Math.abs(p[i].gry - p[i - 1].gry)); + } + if (args.ndig === undefined) + args.ndig = args.mindiff > 20 ? 0 : (args.mindiff > 5 ? 1 : 2); + } - const path = (found.twolines === 'line') ? createPath(gg) : null, - subpos2 = createSubPos(fscale); + const end_point = (pnt1, pnt2, sign) => { + const len = Math.sqrt((pnt2.gry - pnt1.gry) ** 2 + (pnt2.grx - pnt1.grx) ** 2) * args.t, + a2 = Math.atan2(pnt2.dgry, pnt2.dgrx), + a1 = Math.atan2(sign * (pnt2.gry - pnt1.gry), sign * (pnt2.grx - pnt1.grx)); - parseLatex(gg, arg, line2, subpos2); + pnt1.dgrx = len * Math.cos(2 * a1 - a2); + pnt1.dgry = len * Math.sin(2 * a1 - a2); + }, conv = val => { + if (!args.ndig || (Math.round(val) === val)) + return val.toFixed(0); + let s = val.toFixed(args.ndig), p1 = s.length - 1; + while (s[p1] === '0') + p1--; + if (s[p1] === '.') + p1--; + s = s.slice(0, p1 + 1); + return (s === '-0') ? '0' : s; + }; - const w = Math.max(subpos1.rect.width, subpos2.rect.width), - dw = subpos1.rect.width - subpos2.rect.width, - dy = -curr.fsize*0.35; // approximate position of middle line + if (args.calc) { + for (let i = 1; i < npnts - 1; i++) { + p[i].dgrx = (p[i + 1].grx - p[i - 1].grx) * args.t; + p[i].dgry = (p[i + 1].gry - p[i - 1].gry) * args.t; + } - positionGNode(subpos1, (dw < 0 ? -dw/2 : 0), dy - subpos1.rect.y2, true); + if (npnts > 2) { + end_point(p[0], p[1], 1); + end_point(p[npnts - 1], p[npnts - 2], -1); + } else if (p.length === 2) { + p[0].dgrx = (p[1].grx - p[0].grx) * args.t; + p[0].dgry = (p[1].gry - p[0].gry) * args.t; + p[1].dgrx = -p[0].dgrx; + p[1].dgry = -p[0].dgry; + } + } - positionGNode(subpos2, (dw > 0 ? dw/2 : 0), dy - subpos2.rect.y1, true); + let path = `${args.cmd ?? 'M'}${conv(p[0].grx)},${conv(p[0].gry)}`; - if (path) path.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize*0.1)}`); + if (!args.line) { + let i0 = 1; + if (args.qubic) { + npnts--; i0++; + path += `Q${conv(p[1].grx - p[1].dgrx)},${conv(p[1].gry - p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; + } + path += `C${conv(p[i0 - 1].grx + p[i0 - 1].dgrx)},${conv(p[i0 - 1].gry + p[i0 - 1].dgry)},${conv(p[i0].grx - p[i0].dgrx)},${conv(p[i0].gry - p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; - shiftX(w); + // continue with simpler points + for (let i = i0 + 1; i < npnts; i++) + path += `S${conv(p[i].grx - p[i].dgrx)},${conv(p[i].gry - p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; - delete curr.twolines; + if (args.qubic) + path += `Q${conv(p[npnts].grx - p[npnts].dgrx)},${conv(p[npnts].gry - p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; + } else if (npnts < 10000) { + // build simple curve - continue; - } + let acc_x = 0, acc_y = 0, currx = Math.round(p[0].grx), curry = Math.round(p[0].gry); - const extractLowUp = name => { - const res = {}; - if (name) { - label = '{' + label; - res[name] = extractSubLabel(name === 'low' ? '_' : '^'); - if (res[name] === -1) return false; + const flush = () => { + if (acc_x) { + path += 'h' + acc_x; + acc_x = 0; } - - while (label) { - if (label[0] === '_') { - label = label.slice(1); - res.low = !res.low ? extractSubLabel('_') : -1; - if (res.low === -1) { - console.log(`error with ${found.name} low limit`); - return false; - } - } else if (label[0] === '^') { - label = label.slice(1); - res.up = !res.up ? extractSubLabel('^') : -1; - if (res.up === -1) { - console.log(`error with ${found.name} upper limit ${label}`); - return false; - } - } else break; + if (acc_y) { + path += 'v' + acc_y; + acc_y = 0; } - return res; }; - if (found.low_up) { - const subs = extractLowUp(found.low_up); - if (!subs) return false; - - const x = curr.x, dx = 0.03*curr.fsize, ylow = 0.25*curr.fsize; + for (let n = 1; n < npnts; ++n) { + const bin = p[n], + dx = Math.round(bin.grx) - currx, + dy = Math.round(bin.gry) - curry; + if (dx && dy) { + flush(); + path += `l${dx},${dy}`; + } else if (!dx && dy) { + if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) + flush(); + acc_y += dy; + } else if (dx && !dy) { + if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) + flush(); + acc_x += dx; + } + currx += dx; + curry += dy; + } - let pos_up, pos_low, w1 = 0, w2 = 0, yup = -curr.fsize; + flush(); + } else { + // build line with trying optimize many vertical moves + let currx = Math.round(p[0].grx), curry = Math.round(p[0].gry), + cminy = curry, cmaxy = curry, prevy = curry; - if (subs.up) { - pos_up = createSubPos(0.6); - parseLatex(currG(), arg, subs.up, pos_up); + for (let n = 1; n < npnts; ++n) { + const bin = p[n], + lastx = Math.round(bin.grx), + lasty = Math.round(bin.gry), + dx = lastx - currx; + if (dx === 0) { + // if X not change, just remember amplitude and + cminy = Math.min(cminy, lasty); + cmaxy = Math.max(cmaxy, lasty); + prevy = lasty; + continue; } - if (subs.low) { - pos_low = createSubPos(0.6); - parseLatex(currG(), arg, subs.low, pos_low); + if (cminy !== cmaxy) { + if (cminy !== curry) + path += `v${cminy - curry}`; + path += `v${cmaxy - cminy}`; + if (cmaxy !== prevy) + path += `v${prevy - cmaxy}`; + curry = prevy; } + const dy = lasty - curry; + if (dy) + path += `l${dx},${dy}`; + else + path += `h${dx}`; + currx = lastx; + curry = lasty; + prevy = cminy = cmaxy = lasty; + } - if (pos_up) { - if (!pos_low) yup = Math.min(yup, curr.rect.last_y1); - positionGNode(pos_up, x+dx, yup - pos_up.rect.y1 - curr.fsize*0.1); - w1 = pos_up.rect.width; - } + if (cminy !== cmaxy) { + if (cminy !== curry) + path += `v${cminy - curry}`; + path += `v${cmaxy - cminy}`; + if (cmaxy !== prevy) + path += `v${prevy - cmaxy}`; + } + } - if (pos_low) { - positionGNode(pos_low, x+dx, ylow - pos_low.rect.y2 + curr.fsize*0.1); - w2 = pos_low.rect.width; - } + if (args.height) + args.close = `L${conv(p.at(-1).grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; - shiftX(dx + Math.max(w1, w2)); + return path; +} - continue; - } +/** @summary Compress SVG code, produced from drawing + * @desc removes extra info or empty elements + * @private */ +function compressSVG(svg) { + svg = svg.replace(/url\("#(\w+)"\)/g, 'url(#$1)') // decode all URL + .replace(/ class="\w*"/g, '') // remove all classes + .replace(/ pad="\w*"/g, '') // remove all pad ids + .replace(/ title=""/g, '') // remove all empty titles + .replace(/ style=""/g, '') // remove all empty styles + .replace(/<\/g>/g, '') // remove all empty groups with transform + .replace(/<\/g>/g, '') // remove hidden title + .replace(/<\/g>/g, ''); // remove all empty groups - if (found.special) { - // this is sum and integral, now make fix height, later can adjust to right-content size + // remove all empty frame svg, typically appears in 3D drawings, maybe should be improved in frame painter itself + svg = svg.replace(/<\/svg>/g, ''); - const subs = extractLowUp() || {}, - gg = createGG(), path = createPath(gg), - h = Math.round(curr.fsize*1.7), w = Math.round(curr.fsize), r = Math.round(h*0.1); - let x_up, x_low; + return svg; +} - if (found.name === '#sum') { - x_up = x_low = w/2; - path.attr('d', `M${w},${Math.round(-0.75*h)}h${-w}l${Math.round(0.4*w)},${Math.round(0.3*h)}l${Math.round(-0.4*w)},${Math.round(0.7*h)}h${w}`); - } else { - x_up = 3*r; x_low = r; - path.attr('d', `M0,${Math.round(0.25*h-r)}a${r},${r},0,0,0,${2*r},0v${2*r-h}a${r},${r},0,1,1,${2*r},0`); - // path.attr('transform','skewX(-3)'); could use skewX for italic-like style - } - extendPosition(curr.x, curr.y - 0.6*h, curr.x + w, curr.y + 0.4*h); +/** + * @summary Base painter class + * + */ - if (subs.low) { - const subpos1 = createSubPos(0.6); - parseLatex(gg, arg, subs.low, subpos1); - positionGNode(subpos1, (x_low - subpos1.rect.width/2), 0.25*h - subpos1.rect.y1, true); - } +class BasePainter { - if (subs.up) { - const subpos2 = createSubPos(0.6); - parseLatex(gg, arg, subs.up, subpos2); - positionGNode(subpos2, (x_up - subpos2.rect.width/2), -0.75*h - subpos2.rect.y2, true); - } + #divid; // either id of DOM element or element itself + #selected_main; // d3.select for dom elements + #hitemname; // item name in the hpainter + #hdrawopt; // draw option in the hpainter + #hpainter; // assigned hpainter - shiftX(w); + /** @summary constructor + * @param {object|string} [dom] - dom element or id of dom element */ + constructor(dom) { + this.#divid = null; // either id of DOM element or element itself + if (dom) + this.setDom(dom); + } - continue; + /** @summary Assign painter to specified DOM element + * @param {string|object} elem - element ID or DOM Element + * @desc Normally DOM element should be already assigned in constructor + * @protected */ + setDom(elem) { + if (elem !== undefined) { + this.#divid = elem; + this.#selected_main = null; } + } - if (found.braces) { - const rbrace = found.right, - lbrace = rbrace ? found.name : '{', - sublabel = extractSubLabel(false, lbrace, rbrace), - gg = createGG(), - subpos = createSubPos(), - path1 = createPath(gg); + /** @summary Returns assigned dom element */ + getDom() { return this.#divid; } - parseLatex(gg, arg, sublabel, subpos); + /** @summary Returns argument for draw function */ + getDrawDom() { return this.#divid; } - const path2 = createPath(gg), - w = Math.max(2, Math.round(curr.fsize*0.2)), - r = subpos.rect, dy = Math.round(r.y2 - r.y1), - r_y1 = Math.round(r.y1), r_width = Math.round(r.width); + /** @summary Selects main HTML element assigned for drawing + * @desc if main element was layout, returns main element inside layout + * @param {string} [is_direct] - if 'origin' specified, returns original element even if actual drawing moved to some other place + * @return {object} d3.select object for main element for drawing */ + selectDom(is_direct) { + if (!this.#divid) + return select(null); - switch (found.braces) { - case '||': - path1.attr('d', `M${w},${r_y1}v${dy}`); - path2.attr('d', `M${3*w+r_width},${r_y1}v${dy}`); - break; - case '[]': - path1.attr('d', `M${2*w},${r_y1}h${-w}v${dy}h${w}`); - path2.attr('d', `M${2*w+r_width},${r_y1}h${w}v${dy}h${-w}`); - break; - case '{}': - path1.attr('d', `M${2*w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy/2-2*w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy/2-2*w}a${w},${w},0,0,0,${w},${w}`); - path2.attr('d', `M${2*w+r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy/2-2*w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy/2-2*w}a${w},${w},0,0,1,${-w},${w}`); - break; - default: // () - path1.attr('d', `M${w},${r_y1}a${4*dy},${4*dy},0,0,0,0,${dy}`); - path2.attr('d', `M${3*w+r_width},${r_y1}a${4*dy},${4*dy},0,0,1,0,${dy}`); - } + let res = this.#selected_main; + if (!res) { + if (isStr(this.#divid)) { + let id = this.#divid; + if (id[0] !== '#') + id = '#' + id; + res = select(id); + if (!res.empty()) + this.#divid = res.node(); + } else + res = select(this.#divid); + this.#selected_main = res; + } - positionGNode(subpos, 2*w, 0, true); + if (!res || res.empty() || (is_direct === 'origin')) + return res; - extendPosition(curr.x, curr.y + r.y1, curr.x + 4*w + r.width, curr.y + r.y2); + const use_enlarge = res.property('use_enlarge'), + layout = res.property('layout') || 'simple', + layout_selector = (layout === 'simple') ? '' : res.property('layout_selector'); - shiftX(4*w + r.width); + if (layout_selector) + res = res.select(layout_selector); - continue; - } + // one could redirect here + if (!is_direct && !res.empty() && use_enlarge) + res = select(getDocument().getElementById('jsroot_enlarge_div')); - if (found.deco) { - const sublabel = extractSubLabel(), - gg = createGG(), - subpos = createSubPos(); + return res; + } - subpos.deco = found.deco; + /** @summary Access/change top painter + * @private */ + #accessTopPainter(on) { + const chld = this.selectDom().node()?.firstChild; + if (!chld) + return null; + if (on === true) + chld.painter = this; + else if ((on === false) && (chld.painter === this)) + delete chld.painter; + return chld.painter; + } - parseLatex(gg, arg, sublabel, subpos); + /** @summary Set painter, stored in first child element + * @desc Only make sense after first drawing is completed and any child element add to configured DOM + * @protected */ + setTopPainter() { this.#accessTopPainter(true); } - const r = subpos.rect; - if (subpos.deco) { - const path = createPath(gg), r_width = Math.round(r.width); - switch (subpos.deco) { - case 'underline': path.attr('d', `M0,${Math.round(r.y2)}h${r_width}`); break; - case 'overline': path.attr('d', `M0,${Math.round(r.y1)}h${r_width}`); break; - case 'line-through': path.attr('d', `M0,${Math.round(0.45*r.y1+0.55*r.y2)}h${r_width}`); break; - } - } + /** @summary Return top painter set for the selected dom element + * @protected */ + getTopPainter() { return this.#accessTopPainter(); } - positionGNode(subpos, 0, 0, true); + /** @summary Clear reference on top painter + * @protected */ + clearTopPainter() { this.#accessTopPainter(false); } - shiftX(r.width); + /** @summary Generic method to cleanup painter + * @desc Removes all visible elements and all internal data */ + cleanup(keep_origin) { + this.clearTopPainter(); + const origin = this.selectDom('origin'); + if (!origin.empty() && !keep_origin) + origin.html(''); + this.#divid = null; + this.#selected_main = undefined; - continue; - } + if (isFunc(this.#hpainter?.removePainter)) + this.#hpainter.removePainter(this); - if (found.name === '#bf{' || found.name === '#it{') { - const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + this.#hitemname = undefined; + this.#hdrawopt = undefined; + this.#hpainter = undefined; + } - const subpos = createSubPos(); + /** @summary Checks if draw elements were resized and drawing should be updated + * @return {boolean} true if resize was detected + * @protected + * @abstract */ + checkResize(/* arg */) {} - if (found.name === '#bf{') - subpos.bold = !subpos.bold; - else - subpos.italic = !subpos.italic; + /** @summary Function checks if geometry of main div was changed. + * @desc take into account enlarge state, used only in PadPainter class + * @return size of area when main div is drawn + * @private */ + testMainResize(check_level, new_size, height_factor) { + const enlarge = this.enlargeMain('state'), + origin = this.selectDom('origin'), + main = this.selectDom(), + lmt = 5; // minimal size - parseLatex(currG(), arg, sublabel, subpos); + if ((enlarge !== 'on') && new_size?.width && new_size?.height) { + origin.style('width', new_size.width + 'px') + .style('height', new_size.height + 'px'); + } - positionGNode(subpos, curr.x, curr.y); + const rect_origin = getElementRect(origin, true), + can_resize = origin.attr('can_resize'); + let do_resize = false; - shiftX(subpos.rect.width); + if ((can_resize === 'height') && height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) + do_resize = true; - continue; + if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && can_resize && (can_resize !== 'false')) + do_resize = true; + + if (do_resize && (enlarge !== 'on')) { + // if zero size and can_resize attribute set, change container size + + if (rect_origin.width > lmt) { + height_factor = height_factor || 0.66; + origin.style('height', Math.round(rect_origin.width * height_factor) + 'px'); + } else if (can_resize !== 'height') + origin.style('width', '200px').style('height', '100px'); } - let foundarg = 0; + const rect = getElementRect(main), + old_h = main.property('_jsroot_height'), + old_w = main.property('_jsroot_width'); - if (found.arg) { - const pos = label.indexOf(']{'); - if (pos < 0) { console.log('missing argument for ', found.name); return false; } - foundarg = label.slice(0, pos); - if (found.arg === 'int') { - foundarg = parseInt(foundarg); - if (!Number.isInteger(foundarg)) { console.log('wrong int argument', label.slice(0, pos)); return false; } - } else if (found.arg === 'float') { - foundarg = parseFloat(foundarg); - if (!Number.isFinite(foundarg)) { console.log('wrong float argument', label.slice(0, pos)); return false; } + rect.changed = false; + + if (!rect.width && !rect.height && !main.empty() && main.attr('style')) { + const ws = main.style('width'), hs = main.style('height'); + if (isStr(ws) && isStr(hs) && ws.match(/^\d+px$/) && hs.match(/^\d+px$/)) { + rect.width = parseInt(ws.slice(0, ws.length - 2)); + rect.height = parseInt(hs.slice(0, hs.length - 2)); } - label = label.slice(pos + 2); } - if ((found.name === '#kern[') || (found.name === '#lower[')) { - const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (old_h && old_w && (old_h > 0) && (old_w > 0)) { + if ((old_h !== rect.height) || (old_w !== rect.width)) + rect.changed = (check_level > 1) || (rect.width / old_w < 0.99) || (rect.width / old_w > 1.01) || (rect.height / old_h < 0.99) || (rect.height / old_h > 1.01); + } else + rect.changed = true; - const subpos = createSubPos(); + if (rect.changed) + main.property('_jsroot_height', rect.height).property('_jsroot_width', rect.width); - parseLatex(currG(), arg, sublabel, subpos); + // after change enlarge state always mark main element as resized + if (origin.property('did_enlarge')) { + rect.changed = true; + origin.property('did_enlarge', false); + } - let shiftx = 0, shifty = 0; - if (found.name === 'kern[') shiftx = foundarg; else shifty = foundarg; + return rect; + } - positionGNode(subpos, curr.x + shiftx * subpos.rect.width, curr.y + shifty * subpos.rect.height); + /** @summary Try enlarge main drawing element to full HTML page. + * @param {string|boolean} action - defines that should be done + * @desc Possible values for action parameter: + * - true - try to enlarge + * - false - revert enlarge state + * - 'toggle' - toggle enlarge state + * - 'state' - only returns current enlarge state + * - 'verify' - check if element can be enlarged + * if action not specified, just return possibility to enlarge main div + * @protected */ + enlargeMain(action, skip_warning) { + const main = this.selectDom(true), + origin = this.selectDom('origin'), + doc = getDocument(); - shiftX(subpos.rect.width * (shiftx > 0 ? 1 + foundarg : 1)); + if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) + return false; - continue; - } + if ((action === undefined) || (action === 'verify')) + return true; - if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) { - const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + const state = origin.property('use_enlarge') ? 'on' : 'off'; - const subpos = createSubPos(); + if (action === 'state') + return state; - if (found.name === '#color[') - subpos.color = curr.painter.getColor(foundarg); - else if (found.name === '#font[') { - subpos.font = new FontHandler(foundarg); - subpos.ufont = true; // mark that custom font is applied - } else - subpos.fsize *= foundarg; + if (action === 'toggle') + action = (state === 'off'); - parseLatex(currG(), arg, sublabel, subpos); + let enlarge = select(doc.getElementById('jsroot_enlarge_div')); - positionGNode(subpos, curr.x, curr.y); + if ((action === true) && (state !== 'on')) { + if (!enlarge.empty()) + return false; - shiftX(subpos.rect.width); + enlarge = select(doc.body) + .append('div') + .attr('id', 'jsroot_enlarge_div') + .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; left: 1px; top: 1px; bottom: 1px; right: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); - continue; - } + const rect1 = getElementRect(main), + rect2 = getElementRect(enlarge); - if (found.sqrt) { - const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + // if new enlarge area not big enough, do not do it + if ((rect2.width <= rect1.width) || (rect2.height <= rect1.height)) { + if (rect2.width * rect2.height < rect1.width * rect1.height) { + if (!skip_warning) + console.log(`Enlarged area ${rect2.width} x ${rect2.height} smaller then original drawing ${rect1.width} x ${rect1.height}`); + enlarge.remove(); + return false; + } + } - const gg = createGG(), subpos = createSubPos(); - let subpos0; + while (main.node().childNodes.length) + enlarge.node().appendChild(main.node().firstChild); - if (found.arg) { - subpos0 = createSubPos(0.7); - parseLatex(gg, arg, foundarg.toString(), subpos0); - } + origin.property('use_enlarge', true); + origin.property('did_enlarge', true); + return true; + } + if ((action === false) && (state !== 'off')) { + while (enlarge.node()?.childNodes.length) + main.node().appendChild(enlarge.node().firstChild); - // placeholder for the sqrt sign - const path = createPath(gg); + enlarge.remove(); + origin.property('use_enlarge', false); + origin.property('did_enlarge', true); + return true; + } - parseLatex(gg, arg, sublabel, subpos); + return false; + } - const r = subpos.rect, - h = Math.round(r.height), - h1 = Math.round(r.height*0.1), - w = Math.round(r.width), midy = Math.round((r.y1 + r.y2)/2), - f2 = Math.round(curr.fsize*0.2), r_y2 = Math.round(r.y2); + /** @summary Set item name, associated with the painter + * @desc Used by {@link HierarchyPainter} + * @private */ + setItemName(name, opt, hpainter) { + this.#hitemname = isStr(name) ? name : undefined; + // only update draw option, never delete. + if (isStr(opt)) + this.#hdrawopt = opt; - if (subpos0) - positionGNode(subpos0, 0, midy - subpos0.fsize*0.3, true); + this.#hpainter = hpainter; + } - path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2-midy-f2}l${h1},${-h+f2}h${Math.round(h*0.2+w)}v${h1}`); + /** @summary Returns assigned histogram painter */ + getHPainter() { return this.#hpainter; } - positionGNode(subpos, h*0.4, 0, true); + /** @summary Returns assigned item name + * @desc Used with {@link HierarchyPainter} to identify drawn item name */ + getItemName() { return this.#hitemname ?? null; } - extendPosition(curr.x, curr.y + r.y1-curr.fsize*0.1, curr.x + w + h*0.6, curr.y + r.y2); + /** @summary Returns assigned item draw option + * @desc Used with {@link HierarchyPainter} to identify drawn item option */ + getItemDrawOpt() { return this.#hdrawopt ?? ''; } - shiftX(w + h*0.6); +} // class BasePainter - continue; - } - } +/** @summary Load and initialize JSDOM from nodes + * @return {Promise} with d3 selection for d3_body + * @private */ +async function _loadJSDOM() { + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => { + if (!internals.nodejs_window) { + internals.nodejs_window = (new handle.JSDOM('hello')).window; + internals.nodejs_document = internals.nodejs_window.document; // used with three.js + internals.nodejs_body = select(internals.nodejs_document).select('body'); // get d3 handle for body + } - return true; + return { JSDOM: handle.JSDOM, doc: internals.nodejs_document, body: internals.nodejs_body }; + }); } -/** @ummary translate TLatex and draw inside provided g element - * @desc use together with normal elements +/** @summary Return translate string for transform attribute of some svg element + * @return string or null if x and y are zeros * @private */ -function produceLatex(painter, node, arg) { - const pos = { lvl: 0, g: node, x: 0, y: 0, dx: 0, dy: -0.1, fsize: arg.font_size, font: arg.font, parent: null, painter }; - return parseLatex(node, arg, arg.text, pos); +function makeTranslate(g, x, y, scale = 1) { + if (!isObject(g)) { + scale = y; + y = x; + x = g; + g = null; + } + let res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); + if (scale && scale !== 1) { + if (res) + res += ' '; + else + res = ''; + res += `scale(${scale.toFixed(3)})`; + } + + return g ? g.attr('transform', res) : res; } -let _mj_loading; -/** @summary Load MathJax functionality, - * @desc one need not only to load script but wait for initialization +/** @summary Configure special style used for highlight or dragging elements * @private */ -async function loadMathjax() { - const loading = _mj_loading !== undefined; +function addHighlightStyle(elem, drag) { + if (drag) { + elem.style('stroke', 'steelblue') + .style('fill-opacity', '0.1'); + } else { + elem.style('stroke', '#4572A7') + .style('fill', '#4572A7') + .style('opacity', '0'); + } +} - if (!loading && (typeof globalThis.MathJax !== 'undefined')) - return globalThis.MathJax; +/** @summary Create image based on SVG + * @param {string} svg - svg code of the image + * @param {string} [image_format] - image format like 'png', 'jpeg' or 'webp' + * @param {Objects} [args] - optional arguments + * @param {boolean} [args.as_buffer] - return image as buffer + * @return {Promise} with produced image in base64 form or as Buffer (or canvas when no image_format specified) + * @private */ +async function svgToImage(svg, image_format, args) { + if ((args === true) || (args === false)) + args = { as_buffer: args }; - if (!loading) _mj_loading = []; + if (image_format === 'svg') + return svg; - const promise = new Promise(resolve => { _mj_loading ? _mj_loading.push(resolve) : resolve(globalThis.MathJax); }); + if (image_format === 'pdf') + return internals.makePDF ? internals.makePDF(svg, args) : null; - if (loading) return promise; + // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex + const doctype = ''; - const svg = { - scale: 1, // global scaling factor for all expressions - minScale: 0.5, // smallest scaling factor to use - mtextInheritFont: false, // true to make mtext elements use surrounding font - merrorInheritFont: true, // true to make merror text use surrounding font - mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules - skipAttributes: {}, // RFDa and other attributes NOT to copy to the output - exFactor: 0.5, // default size of ex in em units - displayAlign: 'center', // default for indentalign when set to 'auto' - displayIndent: '0', // default for indentshift when set to 'auto' - fontCache: 'local', // or 'global' or 'none' - localID: null, // ID to use for local font cache (for single equation processing) - internalSpeechTitles: true, // insert tags with speech content - titleID: 0 // initial id number to use for aria-labeledby titles - }; + if (isNodeJs()) { + svg = encodeURIComponent(doctype + svg); + svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { + const c = String.fromCharCode('0x' + p1); + return c === '%' ? '%25' : c; + }); - if (!isNodeJs()) { - window.MathJax = { - options: { - enableMenu: false - }, - loader: { - load: ['[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - startup: { - ready() { - // eslint-disable-next-line no-undef - MathJax.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(globalThis.MathJax)); - } - } - }; + const img_src = 'data:image/svg+xml;base64,' + btoa_func(decodeURIComponent(svg)); - let mj_dir = '../mathjax/3.2.0'; - if (browser.webwindow && exports.source_dir.indexOf('https://root.cern/js') < 0 && exports.source_dir.indexOf('https://jsroot.gsi.de') < 0) - mj_dir = 'mathjax'; + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(async handle => { + return handle.default.loadImage(img_src).then(img => { + const canvas = handle.default.createCanvas(img.width, img.height); - return loadScript(exports.source_dir + mj_dir + '/es5/tex-svg.js') - .catch(() => loadScript('https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js')) - .then(() => promise); - } + canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); - let JSDOM; + if (args?.as_buffer) + return canvas.toBuffer('image/' + image_format); - return _loadJSDOM().then(handle => { - JSDOM = handle.JSDOM; - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); - }).then(mj => { - // return Promise with mathjax loading - mj.init({ - loader: { - load: ['input/tex', 'output/svg', '[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - config: { - JSDOM - }, - startup: { - typeset: false, - ready() { - // eslint-disable-next-line no-undef - const mj = MathJax; - - mj.startup.registerConstructor('jsdomAdaptor', () => { - return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); - }); - mj.startup.useAdaptor('jsdomAdaptor', true); - mj.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(mj)); - } - } + return image_format ? canvas.toDataURL('image/' + image_format) : canvas; + }); }); + } - return promise; - }); -} + const img_src = URL.createObjectURL(new Blob([doctype + svg], { type: 'image/svg+xml;charset=utf-8' })); -const math_symbols_map = { - '#LT': '\\langle', - '#GT': '\\rangle', - '#club': '\\clubsuit', - '#spade': '\\spadesuit', - '#heart': '\\heartsuit', - '#diamond': '\\diamondsuit', - '#voidn': '\\wp', - '#voidb': 'f', - '#copyright': '(c)', - '#ocopyright': '(c)', - '#trademark': 'TM', - '#void3': 'TM', - '#oright': 'R', - '#void1': 'R', - '#3dots': '\\ldots', - '#lbar': '\\mid', - '#void8': '\\mid', - '#divide': '\\div', - '#Jgothic': '\\Im', - '#Rgothic': '\\Re', - '#doublequote': '"', - '#plus': '+', - '#minus': '-', - '#/': '/', - '#upoint': '.', - '#aa': '\\mathring{a}', - '#AA': '\\mathring{A}', - '#omicron': 'o', - '#Alpha': 'A', - '#Beta': 'B', - '#Epsilon': 'E', - '#Zeta': 'Z', - '#Eta': 'H', - '#Iota': 'I', - '#Kappa': 'K', - '#Mu': 'M', - '#Nu': 'N', - '#Omicron': 'O', - '#Rho': 'P', - '#Tau': 'T', - '#Chi': 'X', - '#varomega': '\\varpi', - '#corner': '?', - '#ltbar': '?', - '#bottombar': '?', - '#notsubset': '?', - '#arcbottom': '?', - '#cbar': '?', - '#arctop': '?', - '#topbar': '?', - '#arcbar': '?', - '#downleftarrow': '?', - '#splitline': '\\genfrac{}{}{0pt}{}', - '#it': '\\textit', - '#bf': '\\textbf', - '#frac': '\\frac', - '#left{': '\\lbrace', - '#right}': '\\rbrace', - '#left\\[': '\\lbrack', - '#right\\]': '\\rbrack', - '#\\[\\]{': '\\lbrack', - ' } ': '\\rbrack', - '#\\[': '\\lbrack', - '#\\]': '\\rbrack', - '#{': '\\lbrace', - '#}': '\\rbrace', - ' ': '\\;' -}, + return new Promise(resolveFunc => { + const image = document.createElement('img'); -mathjax_remap = { - upDelta: 'Updelta', - upGamma: 'Upgamma', - upLambda: 'Uplambda', - upOmega: 'Upomega', - upPhi: 'Upphi', - upPi: 'Uppi', - upPsi: 'Uppsi', - upSigma: 'Upsigma', - upTheta: 'Uptheta', - upUpsilon: 'Upupsilon', - upXi: 'Upxi', - notcong: 'ncong', - notgeq: 'ngeq', - notgr: 'ngtr', - notless: 'nless', - notleq: 'nleq', - notsucc: 'nsucc', - notprec: 'nprec', - notsubseteq: 'nsubseteq', - notsupseteq: 'nsupseteq', - openclubsuit: 'clubsuit', - openspadesuit: 'spadesuit', - dasharrow: 'dashrightarrow', - comp: 'circ', - iiintop: 'iiint', - iintop: 'iint', - ointop: 'oint' -}, + image.onload = function() { + URL.revokeObjectURL(img_src); -mathjax_unicode = { - Digamma: 0x3DC, - upDigamma: 0x3DC, - digamma: 0x3DD, - updigamma: 0x3DD, - Koppa: 0x3DE, - koppa: 0x3DF, - upkoppa: 0x3DF, - upKoppa: 0x3DE, - VarKoppa: 0x3D8, - upVarKoppa: 0x3D8, - varkoppa: 0x3D9, - upvarkoppa: 0x3D9, - varkappa: 0x3BA, // not found archaic kappa - use normal - upvarkappa: 0x3BA, - varbeta: 0x3D0, // not found archaic beta - use normal - upvarbeta: 0x3D0, - Sampi: 0x3E0, - upSampi: 0x3E0, - sampi: 0x3E1, - upsampi: 0x3E1, - Stigma: 0x3DA, - upStigma: 0x3DA, - stigma: 0x3DB, - upstigma: 0x3DB, - San: 0x3FA, - upSan: 0x3FA, - san: 0x3FB, - upsan: 0x3FB, - Sho: 0x3F7, - upSho: 0x3F7, - sho: 0x3F8, - upsho: 0x3F8, - P: 0xB6, - aa: 0xB0, - bulletdashcirc: 0x22B7, - circdashbullet: 0x22B6, - downuparrows: 0x21F5, - updownarrows: 0x21C5, - dashdownarrow: 0x21E3, - dashuparrow: 0x21E1, - complement: 0x2201, - dbar: 0x18C, - ddddot: 0x22EF, - dddot: 0x22EF, - ddots: 0x22F1, - defineequal: 0x225D, - defineeq: 0x225D, - downdownharpoons: 0x2965, - downupharpoons: 0x296F, - updownharpoons: 0x296E, - upupharpoons: 0x2963, - hateq: 0x2259, - ldbrack: 0x27E6, - rdbrack: 0x27E7, - leadsfrom: 0x219C, - leftsquigarrow: 0x21DC, - lightning: 0x2607, - napprox: 0x2249, - nasymp: 0x226D, - nequiv: 0x2262, - nsimeq: 0x2244, - nsubseteq: 0x2288, - nsubset: 0x2284, - notapprox: 0x2249, - notasymp: 0x226D, - notequiv: 0x2262, - notni: 0x220C, - notsimeq: 0x2244, - notsubseteq: 0x2288, - notsubset: 0x2284, - notsupseteq: 0x2289, - notsupset: 0x2285, - nsupset: 0x2285, - setdif: 0x2216, - simarrow: 0x2972, - t: 0x2040, - u: 0x2C7, - v: 0x2C7, - undercurvearrowright: 0x293B, - updbar: 0x18C, - wwbar: 0x2015, - awointop: 0x2232, - awoint: 0x2233, - barintop: 0x2A1C, - barint: 0x2A1B, - cwintop: 0x2231, // no opposite direction, use same - cwint: 0x2231, - cwointop: 0x2233, - cwoint: 0x2232, - oiiintop: 0x2230, - oiiint: 0x2230, - oiintop: 0x222F, - oiint: 0x222F, - slashintop: 0x2A0F, - slashint: 0x2A0F -}, + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; -mathjax_asis = ['"', '\'', '`', '=', '~']; + canvas.getContext('2d').drawImage(image, 0, 0); -/** @summary Function translates ROOT TLatex into MathJax format - * @private */ -function translateMath(str, kind, color, painter) { - if (kind !== 2) { - for (const x in math_symbols_map) - str = str.replace(new RegExp(x, 'g'), math_symbols_map[x]); + if (args?.as_buffer && image_format) + canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format); + else + resolveFunc(image_format ? canvas.toDataURL('image/' + image_format) : canvas); + }; + image.onerror = function(arg) { + URL.revokeObjectURL(img_src); + console.log(`IMAGE ERROR ${arg}`); + resolveFunc(null); + }; - for (const x in symbols_map) { - if (x.length > 2) - str = str.replace(new RegExp(x, 'g'), '\\' + x.slice(1)); - } + image.setAttribute('src', img_src); + }); +} - // replace all #color[]{} occurances - let clean = '', first = true; - while (str) { - let p = str.indexOf('#color['); - if ((p < 0) && first) { clean = str; break; } - first = false; - if (p !== 0) { - const norm = (p < 0) ? str : str.slice(0, p); - clean += norm; - if (p < 0) break; - } +/** @summary Convert ROOT TDatime object into Date + * @desc Always use UTC to avoid any variation between timezones */ +function getTDatime(dt) { + const y = (dt.fDatime >>> 26) + 1995, + m = ((dt.fDatime << 6) >>> 28) - 1, + d = (dt.fDatime << 10) >>> 27, + h = (dt.fDatime << 15) >>> 27, + min = (dt.fDatime << 20) >>> 26, + s = (dt.fDatime << 26) >>> 26; + return new Date(Date.UTC(y, m, d, h, min, s)); +} - str = str.slice(p + 7); - p = str.indexOf(']{'); - if (p <= 0) break; - const colindx = parseInt(str.slice(0, p)); - if (!Number.isInteger(colindx)) break; - const col = painter.getColor(colindx); - let cnt = 1; - str = str.slice(p + 2); - p = -1; - while (cnt && (++p < str.length)) { - if (str[p] === '{') - cnt++; - else if (str[p] === '}') - cnt--; - } - if (cnt !== 0) break; +/** @summary Convert Date object into string used configured time zone + * @desc Time zone stored in settings.TimeZone */ +function convertDate(dt) { + let res = ''; - const part = str.slice(0, p); - str = str.slice(p + 1); - if (part) - clean += `\\color{${col}}{${part}}`; + if (settings.TimeZone && isStr(settings.TimeZone)) { + try { + res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); + } catch { + res = ''; } - - str = clean; - } else { - if (str === '\\^') str = '\\unicode{0x5E}'; - if (str === '\\vec') str = '\\unicode{0x2192}'; - str = str.replace(/\\\./g, '\\unicode{0x2E}').replace(/\\\^/g, '\\hat'); - for (const x in mathjax_unicode) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\unicode{0x${mathjax_unicode[x].toString(16)}}`); - mathjax_asis.forEach(symbol => { - str = str.replace(new RegExp(`(\\\\${symbol})`, 'g'), `\\unicode{0x${symbol.charCodeAt(0).toString(16)}}`); - }); - for (const x in mathjax_remap) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\${mathjax_remap[x]}`); } - - if (!isStr(color)) return str; - - // MathJax SVG converter use colors in normal form - // if (color.indexOf('rgb(') >= 0) - // color = color.replace(/rgb/g, '[RGB]') - // .replace(/\(/g, '{') - // .replace(/\)/g, '}'); - return `\\color{${color}}{${str}}`; + return res || dt.toLocaleString('en-GB'); } -/** @summary Workaround to fix size attributes in MathJax SVG +/** @summary Box decorations * @private */ -function repairMathJaxSvgSize(painter, mj_node, svg, arg) { - const transform = value => { - if (!value || !isStr(value) || (value.length < 3)) return null; - const p = value.indexOf('ex'); - if ((p < 0) || (p !== value.length - 2)) return null; - value = parseFloat(value.slice(0, p)); - return Number.isFinite(value) ? value * arg.font.size * 0.5 : null; - }; - - let width = transform(svg.getAttribute('width')), - height = transform(svg.getAttribute('height')), - valign = svg.getAttribute('style'); - - if (valign && (valign.length > 18) && valign.indexOf('vertical-align:') === 0) { - const p = valign.indexOf('ex;'); - valign = ((p > 0) && (p === valign.length - 3)) ? transform(valign.slice(16, valign.length - 1)) : null; - } else - valign = null; - - width = (!width || (width <= 0.5)) ? 1 : Math.round(width); - height = (!height || (height <= 0.5)) ? 1 : Math.round(height); - - svg.setAttribute('width', width); - svg.setAttribute('height', height); - svg.removeAttribute('style'); +function getBoxDecorations(xx, yy, ww, hh, bmode, pww, phh) { + const side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2 * pww - ww}v${hh - 2 * phh}l${-pww},${phh}z`, + side2 = `M${xx + ww},${yy + hh}v${-hh}l${-pww},${phh}v${hh - 2 * phh}h${2 * pww - ww}l${-pww},${phh}z`; + return bmode > 0 ? [side1, side2] : [side2, side1]; +} + +const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'RootSymbol', kWingdings = 'Wingdings', + // average width taken from symbols.html, counted only for letters and digits + root_fonts = [null, // index 0 not exists + { n: kTimes, s: 'italic', aw: 0.5314 }, + { n: kTimes, w: 'bold', aw: 0.5809 }, + { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, + { n: kArial, aw: 0.5778 }, + { n: kArial, s: 'oblique', aw: 0.5783 }, + { n: kArial, w: 'bold', aw: 0.6034 }, + { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, + { n: kCourier, aw: 0.6003 }, + { n: kCourier, s: 'oblique', aw: 0.6004 }, + { n: kCourier, w: 'bold', aw: 0.6003 }, + { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, + { n: kSymbol, aw: 0.5521, file: 'symbol.ttf' }, + { n: kTimes, aw: 0.5521 }, + { n: kWingdings, aw: 0.5664, file: 'wingding.ttf' }, + { n: kSymbol, s: 'oblique', aw: 0.5314, file: 'symbol.ttf' }, + { n: kVerdana, aw: 0.5664 }, + { n: kVerdana, s: 'italic', aw: 0.5495 }, + { n: kVerdana, w: 'bold', aw: 0.5748 }, + { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }], + // list of loaded fonts including handling of multiple simultaneous requests + gFontFiles = {}; + +/** @summary Read font file from some pre-configured locations + * @return {Promise} with base64 code of the font + * @private */ +async function loadFontFile(fname) { + let entry = gFontFiles[fname]; + if (entry?.base64) + return entry?.base64; - if (!isNodeJs()) { - const box = getElementRect(mj_node, 'bbox'); - width = 1.05 * box.width; height = 1.05 * box.height; + if (entry?.promises !== undefined) { + return new Promise(resolveFunc => { + entry.promises.push(resolveFunc); + }); } - arg.valign = valign; - - if (arg.scale) - painter.scaleTextDrawing(Math.max(width / arg.width, height / arg.height), arg.draw_g); -} - -/** @summary Apply attributes to mathjax drawing - * @private */ -function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_factor) { - let mw = parseInt(svg.attr('width')), - mh = parseInt(svg.attr('height')); + entry = gFontFiles[fname] = { promises: [] }; - if (Number.isInteger(mh) && Number.isInteger(mw)) { - if (svg_factor > 0) { - mw = mw / svg_factor; - mh = mh / svg_factor; - svg.attr('width', Math.round(mw)).attr('height', Math.round(mh)); + const locations = []; + if (fname.indexOf('/') >= 0) + locations.push(''); // just use file name as is + else { + locations.push(exports.source_dir + 'fonts/'); + if (isNodeJs()) + locations.push('../../fonts/'); + else if (exports.source_dir.indexOf('jsrootsys/') >= 0) { + locations.unshift(exports.source_dir.replace(/jsrootsys/g, 'rootsys_fonts')); + locations.unshift(exports.source_dir.replace(/jsrootsys/g, 'rootsys/fonts')); } - } else { - const box = getElementRect(mj_node, 'bbox'); // sizes before rotation - mw = box.width || mw || 100; - mh = box.height || mh || 10; } - if ((svg_factor > 0) && arg.valign) arg.valign = arg.valign / svg_factor; - - if (arg.valign === null) arg.valign = (font_size - mh) / 2; - - const sign = { x: 1, y: 1 }; - let nx = 'x', ny = 'y'; - if (arg.rotate === 180) - sign.x = sign.y = -1; - else if ((arg.rotate === 270) || (arg.rotate === 90)) { - sign.x = (arg.rotate === 270) ? -1 : 1; - sign.y = -sign.x; - nx = 'y'; ny = 'x'; // replace names to which align applied + function completeReading(base64) { + entry.base64 = base64; + const arr = entry.promises; + delete entry.promises; + arr.forEach(func => func(base64)); + return base64; } - if (arg.align[0] === 'middle') - arg[nx] += sign.x * (arg.width - mw) / 2; - else if (arg.align[0] === 'end') - arg[nx] += sign.x * (arg.width - mw); - - if (arg.align[1] === 'middle') - arg[ny] += sign.y * (arg.height - mh) / 2; - else if (arg.align[1] === 'bottom') - arg[ny] += sign.y * (arg.height - mh); - else if (arg.align[1] === 'bottom-base') - arg[ny] += sign.y * (arg.height - mh - arg.valign); + async function tryNext() { + if (!locations.length) { + completeReading(null); + throw new Error(`Fail to load ${fname} font`); + } + let path = locations.shift() + fname; + console.log('loading font', path); + const pr = isNodeJs() ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { + const prefix = 'file://' + (process?.platform === 'win32' ? '/' : ''); + if (path.indexOf(prefix) === 0) + path = path.slice(prefix.length); + return fs.readFileSync(path).toString('base64'); + }) : httpRequest(path, 'bin').then(buf => btoa_func(buf)); - let trans = makeTranslate(arg.x, arg.y) || ''; - if (arg.rotate) { - if (trans) trans += ' '; - trans += `rotate(${arg.rotate})`; + return pr.then(res => { return res ? completeReading(res) : tryNext();}).catch(() => tryNext()); } - mj_node.attr('transform', trans || null).attr('visibility', null); + return tryNext(); } -/** @summary Produce text with MathJax - * @private */ -async function produceMathjax(painter, mj_node, arg) { - const mtext = translateMath(arg.text, arg.latex, arg.color, painter), - options = { em: arg.font.size, ex: arg.font.size/2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; - - return loadMathjax() - .then(mj => mj.tex2svgPromise(mtext, options)) - .then(elem => { - // when adding element to new node, it will be removed from original parent - const svg = elem.querySelector('svg'); - mj_node.append(() => svg); +/** + * @summary Helper class for font handling + * @private + */ - repairMathJaxSvgSize(painter, mj_node, svg, arg); +class FontHandler { - arg.applyAttributesToMathJax = applyAttributesToMathJax; - return true; - }); -} + /** @summary constructor */ + constructor(fontIndex, size, scale) { + if (scale && (size < 1)) { + size *= scale; + this.scaled = true; + } -/** @summary Just typeset HTML node with MathJax - * @private */ -async function typesetMathjax(node) { - return loadMathjax().then(mj => mj.typesetPromise(node ? [node] : undefined)); -} + this.size = Math.round(size); + this.scale = scale; + this.index = 0; -const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', - kWhite = 0, kBlack = 1; + this.func = this.setFont.bind(this); -/** @summary Covert value between 0 and 1 into hex, used for colors coding - * @private */ -function toHex(num, scale = 255) { - const s = Math.round(num * scale).toString(16); - return s.length === 1 ? '0'+s : s; -} + let cfg; -/** @summary list of global root colors - * @private */ -let gbl_colors_list = []; + if (fontIndex && isObject(fontIndex)) + cfg = fontIndex; + else { + if (fontIndex && Number.isInteger(fontIndex)) + this.index = Math.floor(fontIndex / 10); + cfg = root_fonts[this.index]; + } -/** @summary Generates all root colors, used also in jstests to reset colors - * @private */ -function createRootColors() { - const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', '#59d454', '#5954d9', 'white']; - colorMap[110] = 'white'; + if (cfg) { + this.cfg = cfg; + this.setNameStyleWeight(cfg.n, cfg.s, cfg.w, cfg.aw, cfg.format, cfg.base64); + } else + this.setNameStyleWeight(kArial); + } - const moreCol = [ - { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000' }, - { n: 201, s: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' }, - { n: 390, s: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300' }, - { n: 406, s: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300' }, - { n: 422, s: 'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333' }, - { n: 590, s: 'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033' }, - { n: 606, s: 'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033' }, - { n: 622, s: 'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000' }, - { n: 791, s: 'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a' }, - { n: 920, s: 'cdcdcd9a9a9a666666333333' }]; + /** @summary Should returns true if font has to be loaded before + * @private */ + needLoad() { return this.cfg?.file && !this.isSymbol && !this.base64; } - moreCol.forEach(entry => { - const s = entry.s; - for (let n = 0; n < s.length; n += 6) { - const num = entry.n + n / 6; - colorMap[num] = '#' + s.slice(n, n+6); - } - }); - - gbl_colors_list = colorMap; -} + /** @summary Async function to load font + * @private */ + async load() { + if (!this.needLoad()) + return true; + return loadFontFile(this.cfg.file).then(base64 => { + this.cfg.base64 = this.base64 = base64; + this.format = 'ttf'; + return Boolean(base64); + }); + } -/** @summary Get list of colors - * @private */ -function getRootColors() { - return gbl_colors_list; -} + /** @summary Directly set name, style and weight for the font + * @private */ + setNameStyleWeight(name, style, weight, aver_width, format, base64) { + this.name = name; + this.style = style || null; + this.weight = weight || null; + this.aver_width = aver_width || (weight ? 0.58 : 0.55); + this.format = format; // format of custom font, ttf by default + this.base64 = base64; // indication of custom font + if (!settings.LoadSymbolTtf && ((this.name === kSymbol) || (this.name === kWingdings))) { + this.isSymbol = this.name; + this.name = kTimes; + } else + this.isSymbol = ''; + } -/** @summary Produces rgb code for TColor object - * @private */ -function getRGBfromTColor(col) { - if (col?._typename !== clTColor) return null; + /** @summary Set painter for which font will be applied */ + setPainter(painter) { + this.painter = painter; + } - let rgb = '#' + toHex(col.fRed) + toHex(col.fGreen) + toHex(col.fBlue); - if ((col.fAlpha !== undefined) && (col.fAlpha !== 1)) - rgb += toHex(col.fAlpha); + /** @summary Force setting of style and weight, used in latex */ + setUseFullStyle(flag) { + this.full_style = flag; + } - switch (rgb) { - case '#ffffff': return 'white'; - case '#000000': return 'black'; - case '#ff0000': return 'red'; - case '#00ff00': return 'green'; - case '#0000ff': return 'blue'; - case '#ffff00': return 'yellow'; - case '#ff00ff': return 'magenta'; - case '#00ffff': return 'cyan'; + /** @summary Assigns font-related attributes */ + addCustomFontToSvg(svg) { + if (!this.base64 || !this.name) + return; + const clname = 'custom_font_' + this.name, fmt = 'ttf'; + let defs = svg.selectChild('.canvas_defs'); + if (defs.empty()) + defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); + const entry = defs.selectChild('.' + clname); + if (entry.empty()) { + defs.append('style') + .attr('class', clname) + .property('$fontcfg', this.cfg || null) + .text(`@font-face { font-family: "${this.name}"; font-weight: normal; font-style: normal; src: url(data:application/font-${fmt};charset=utf-8;base64,${this.base64}); }`); + } } - return rgb; -} -/** @ummary Return list of grey colors for the original array - * @private */ -function getGrayColors(rgb_array) { - const gray_colors = []; + /** @summary Assigns font-related attributes */ + setFont(selection) { + if (this.base64 && this.painter) + this.addCustomFontToSvg(this.painter.getCanvSvg()); - if (!rgb_array) rgb_array = getRootColors(); + selection.attr('font-family', this.name) + .attr('font-size', this.size) + .attr(':xml:space', 'preserve'); + this.setFontStyle(selection); + } - for (let n = 0; n < rgb_array.length; ++n) { - if (!rgb_array[n]) continue; - const rgb = color(rgb_array[n]), - gray = 0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b; - rgb.r = rgb.g = rgb.b = gray; - gray_colors[n] = rgb.hex(); + /** @summary Assigns only font style attributes */ + setFontStyle(selection) { + selection.attr('font-weight', this.weight || (this.full_style ? 'normal' : null)) + .attr('font-style', this.style || (this.full_style ? 'normal' : null)); } - return gray_colors; -} + /** @summary Set font size (optional) */ + setSize(size) { this.size = Math.round(size); } -/** @summary Add new colors from object array - * @private */ -function extendRootColors(jsarr, objarr, grayscale) { - if (!jsarr) { - jsarr = []; - for (let n = 0; n < gbl_colors_list.length; ++n) - jsarr[n] = gbl_colors_list[n]; - } + /** @summary Set text color (optional) */ + setColor(color) { this.color = color; } - if (!objarr) return jsarr; + /** @summary Set text align (optional) */ + setAlign(align) { this.align = align; } - let rgb_array = objarr; - if (objarr._typename && objarr.arr) { - rgb_array = []; - for (let n = 0; n < objarr.arr.length; ++n) { - const col = objarr.arr[n]; - if ((col?._typename === clTLinearGradient) || (col?._typename === clTRadialGradient)) { - rgb_array[col.fNumber] = col; - col.toString = () => 'white'; - continue; - } + /** @summary Set text angle (optional) */ + setAngle(angle) { this.angle = angle; } - if (col?._typename !== clTColor) - continue; + /** @summary Align angle to step raster, add optional offset */ + roundAngle(step, offset) { + this.angle = parseInt(this.angle || 0); + if (!Number.isInteger(this.angle)) + this.angle = 0; + this.angle = Math.round(this.angle / step) * step + (offset || 0); + if (this.angle < 0) + this.angle += 360; + else if (this.angle >= 360) + this.angle -= 360; + } - if ((col.fNumber >= 0) && (col.fNumber <= 10000)) - rgb_array[col.fNumber] = getRGBfromTColor(col); - } + /** @summary Clears all font-related attributes */ + clearFont(selection) { + selection.attr('font-family', null) + .attr('font-size', null) + .attr(':xml:space', null) + .attr('font-weight', null) + .attr('font-style', null); } - for (let n = 0; n < rgb_array.length; ++n) { - if (rgb_array[n] && (jsarr[n] !== rgb_array[n])) - jsarr[n] = rgb_array[n]; + /** @summary Returns true in case of monospace font + * @private */ + isMonospace() { + const n = this.name.toLowerCase(); + return (n.indexOf('courier') === 0) || (n === 'monospace') || (n === 'monaco'); } - return grayscale ? getGrayColors(jsarr) : jsarr; -} + /** @summary Return full font declaration which can be set as font property like '12pt Arial bold' + * @private */ + getFontHtml() { + let res = Math.round(this.size) + 'pt ' + this.name; + if (this.weight) + res += ' ' + this.weight; + if (this.style) + res += ' ' + this.style; + return res; + } -/** @ummary Set global list of colors. - * @desc Either TObjArray of TColor instances or just plain array with rgb() code. - * List of colors typically stored together with TCanvas primitives + /** @summary Returns font name */ + getFontName() { + return this.isSymbol || this.name || 'none'; + } + +} // class FontHandler + +/** @summary Register custom font * @private */ -function adoptRootColors(objarr) { - extendRootColors(gbl_colors_list, objarr); +function addCustomFont(index, name, format, base64) { + if (!Number.isInteger(index)) + console.error(`Wrong index ${index} for custom font`); + else + root_fonts[index] = { n: name, format, base64 }; } -/** @summary Return ROOT color by index - * @desc Color numbering corresponds typical ROOT colors - * @return {String} with RGB color code or existing color name like 'cyan' +/** @summary Try to detect and create font handler for SVG text node * @private */ -function getColor(indx) { - return gbl_colors_list[indx]; +function detectPdfFont(node) { + const sz = node.getAttribute('font-size'), + p = sz.indexOf('px'), + sz_pixels = p > 0 ? Number.parseInt(sz.slice(0, p)) : 12; + + let family = node.getAttribute('font-family'), + style = node.getAttribute('font-style'), + weight = node.getAttribute('font-weight'); + + if (family === 'times') + family = kTimes; + else if (family === 'symbol') + family = kSymbol; + else if (family === 'arial') + family = kArial; + else if (family === 'verdana') + family = kVerdana; + if (weight === 'normal') + weight = ''; + if (style === 'normal') + style = ''; + + const fcfg = root_fonts.find(elem => { + return (elem?.n === family) && + ((!weight && !elem.w) || (elem.w === weight)) && + ((!style && !elem.s) || (elem.s === style)); + }); + + return new FontHandler(fcfg || root_fonts[13], sz_pixels); } -/** @summary Search for specified color in the list of colors - * @return Color index or -1 if fails +/* eslint-disable one-var */ + +const symbols_map = { + // greek letters from symbols.ttf + '#alpha': '\u03B1', + '#beta': '\u03B2', + '#chi': '\u03C7', + '#delta': '\u03B4', + '#varepsilon': '\u03B5', + '#phi': '\u03C6', + '#gamma': '\u03B3', + '#eta': '\u03B7', + '#iota': '\u03B9', + '#varphi': '\u03C6', + '#kappa': '\u03BA', + '#lambda': '\u03BB', + '#mu': '\u03BC', + '#nu': '\u03BD', + '#omicron': '\u03BF', + '#pi': '\u03C0', + '#theta': '\u03B8', + '#rho': '\u03C1', + '#sigma': '\u03C3', + '#tau': '\u03C4', + '#upsilon': '\u03C5', + '#varomega': '\u03D6', + '#omega': '\u03C9', + '#xi': '\u03BE', + '#psi': '\u03C8', + '#zeta': '\u03B6', + '#Alpha': '\u0391', + '#Beta': '\u0392', + '#Chi': '\u03A7', + '#Delta': '\u0394', + '#Epsilon': '\u0395', + '#Phi': '\u03A6', + '#Gamma': '\u0393', + '#Eta': '\u0397', + '#Iota': '\u0399', + '#vartheta': '\u03D1', + '#Kappa': '\u039A', + '#Lambda': '\u039B', + '#Mu': '\u039C', + '#Nu': '\u039D', + '#Omicron': '\u039F', + '#Pi': '\u03A0', + '#Theta': '\u0398', + '#Rho': '\u03A1', + '#Sigma': '\u03A3', + '#Tau': '\u03A4', + '#Upsilon': '\u03A5', + '#varsigma': '\u03C2', + '#Omega': '\u03A9', + '#Xi': '\u039E', + '#Psi': '\u03A8', + '#Zeta': '\u0396', + '#varUpsilon': '\u03D2', + '#epsilon': '\u03B5', + + // second set from symbols.ttf + '#leq': '\u2264', + '#/': '\u2044', + '#infty': '\u221E', + '#voidb': '\u0192', + '#club': '\u2663', + '#diamond': '\u2666', + '#heart': '\u2665', + '#spade': '\u2660', + '#leftrightarrow': '\u2194', + '#leftarrow': '\u2190', + '#uparrow': '\u2191', + '#rightarrow': '\u2192', + '#downarrow': '\u2193', + '#circ': '\u2E30', + '#pm': '\xB1', + '#doublequote': '\u2033', + '#geq': '\u2265', + '#times': '\xD7', + '#propto': '\u221D', + '#partial': '\u2202', + '#bullet': '\u2022', + '#divide': '\xF7', + '#neq': '\u2260', + '#equiv': '\u2261', + '#approx': '\u2248', // should be \u2245 ? + '#3dots': '\u2026', + '#cbar': '\x7C', + '#topbar': '\xAF', + '#downleftarrow': '\u21B5', + '#aleph': '\u2135', + '#Jgothic': '\u2111', + '#Rgothic': '\u211C', + '#voidn': '\u2118', + '#otimes': '\u2297', + '#oplus': '\u2295', + '#oslash': '\u2205', + '#cap': '\u2229', + '#cup': '\u222A', + '#supset': '\u2283', + '#supseteq': '\u2287', + '#notsubset': '\u2284', + '#subset': '\u2282', + '#subseteq': '\u2286', + '#in': '\u2208', + '#notin': '\u2209', + '#angle': '\u2220', + '#nabla': '\u2207', + '#oright': '\xAE', + '#ocopyright': '\xA9', + '#trademark': '\u2122', + '#prod': '\u220F', + '#surd': '\u221A', + '#upoint': '\u2027', + '#corner': '\xAC', + '#wedge': '\u2227', + '#vee': '\u2228', + '#Leftrightarrow': '\u21D4', + '#Leftarrow': '\u21D0', + '#Uparrow': '\u21D1', + '#Rightarrow': '\u21D2', + '#Downarrow': '\u21D3', + '#void2': '', // dummy, placeholder + '#LT': '\x3C', + '#void1': '\xAE', + '#copyright': '\xA9', + '#void3': '\u2122', // it is dummy placeholder, TM + '#sum': '\u2211', + '#arctop': '\u239B', + '#lbar': '\u23A2', + '#arcbottom': '\u239D', + '#void4': '', // dummy, placeholder + '#void8': '\u23A2', // same as lbar + '#bottombar': '\u230A', + '#arcbar': '\u23A7', + '#ltbar': '\u23A8', + '#AA': '\u212B', + '#aa': '\xE5', + '#void06': '', + '#GT': '\x3E', + '#int': '\u222B', + '#forall': '\u2200', + '#exists': '\u2203', + // here ends second set from symbols.ttf + + // more greek symbols + '#koppa': '\u03DF', + '#sampi': '\u03E1', + '#stigma': '\u03DB', + '#san': '\u03FB', + '#sho': '\u03F8', + '#varcoppa': '\u03D9', + '#digamma': '\u03DD', + '#Digamma': '\u03DC', + '#Koppa': '\u03DE', + '#varKoppa': '\u03D8', + '#Sampi': '\u03E0', + '#Stigma': '\u03DA', + '#San': '\u03FA', + '#Sho': '\u03F7', + + '#vec': '', + '#dot': '\u22C5', + '#hat': '\xB7', + '#ddot': '', + '#acute': '', + '#grave': '', + '#check': '\u2713', + '#tilde': '\u02DC', + '#slash': '\u2044', + '#hbar': '\u0127', + '#box': '\u25FD', + '#Box': '\u2610', + '#parallel': '\u2225', + '#perp': '\u22A5', + '#odot': '\u2299', + '#left': '', + '#right': '', + '{}': '', + + '#mp': '\u2213', + + '#P': '\u00B6', // paragraph + + // only required for MathJax to provide correct replacement + '#sqrt': '\u221A', + '#bar': '', + '#overline': '', + '#underline': '', + '#strike': '' +}; + + +/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first * @private */ -function findColor(name) { - if (!name) return -1; - for (let indx = 0; indx < gbl_colors_list.length; ++indx) { - if (gbl_colors_list[indx] === name) - return indx; - } - return -1; -} +const symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'); -/** @summary Add new color - * @param {string} rgb - color name or just string with rgb value - * @param {array} [lst] - optional colors list, to which add colors - * @return {number} index of new color +/** @summary Simple replacement of latex letters * @private */ -function addColor(rgb, lst) { - if (!lst) lst = gbl_colors_list; - const indx = lst.indexOf(rgb); - if (indx >= 0) return indx; - lst.push(rgb); - return lst.length-1; +function translateLaTeX(str) { + while ((str.length > 2) && (str.at(0) === '{') && (str.at(-1) === '}')) + str = str.slice(1, str.length - 1); + + return str.replace(symbolsRegexCache, ch => symbols_map[ch]).replace(/\{\}/g, ''); } -/** - * @summary Color palette handle - * - * @private - */ +// array with relative width of base symbols from range 32..126 +// eslint-disable-next-line +const base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,200,570,200,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069]; -class ColorPalette { +// eslint-disable-next-line +const extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; - /** @summary constructor */ - constructor(arr, grayscale) { - this.palette = grayscale ? getGrayColors(arr) : arr; +/** @summary Calculate approximate labels width + * @private */ +function approximateLabelWidth(label, font, fsize) { + if (Number.isInteger(font)) + font = new FontHandler(font, fsize); + + const len = label.length, + symbol_width = (fsize || font.size) * font.aver_width; + if (font.isMonospace()) + return len * symbol_width; + + let sum = 0; + for (let i = 0; i < len; ++i) { + const code = label.charCodeAt(i); + if ((code >= 32) && (code < 127)) + sum += base_symbols_width[code - 32]; + else + sum += extra_symbols_width[code] || 1000; } - /** @summary Returns color index which correspond to contour index of provided length */ - calcColorIndex(i, len) { - const plen = this.palette.length, theColor = Math.floor((i + 0.99) * plen / (len - 1)); - return (theColor > plen - 1) ? plen - 1 : theColor; - } + return sum / 1000 * symbol_width; +} - /** @summary Returns color with provided index */ - getColor(indx) { return this.palette[indx]; } +/** @summary array defines features supported by latex parser, used by both old and new parsers + * @private */ +const latex_features = [ + { name: '#it{', bi: 'italic' }, // italic + { name: '#bf{', bi: 'bold' }, // bold + { name: '#underline{', deco: 'underline' }, // underline + { name: '#overline{', deco: 'overline' }, // overline + { name: '#strike{', deco: 'line-through' }, // line through + { name: '#kern[', arg: 'float', shift: 'x' }, // horizontal shift + { name: '#lower[', arg: 'float', shift: 'y' }, // vertical shift + { name: '#scale[', arg: 'float' }, // font scale + { name: '#color[', arg: 'int' }, // font color + { name: '#font[', arg: 'int' }, // font face + { name: '#url[', arg: 'string' }, // url link + { name: '_{', low_up: 'low' }, // subscript + { name: '^{', low_up: 'up' }, // superscript + { name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305' + { name: '#hat{', accent: '\u02C6', hasw: true }, // '\u0302' + { name: '#check{', accent: '\u02C7', hasw: true }, // '\u030C' + { name: '#acute{', accent: '\u02CA' }, // '\u0301' + { name: '#grave{', accent: '\u02CB' }, // '\u0300' + { name: '#dot{', accent: '\u02D9' }, // '\u0307' + { name: '#ddot{', accent: '\u02BA', hasw: true }, // '\u0308' + { name: '#tilde{', accent: '\u02DC', hasw: true }, // '\u0303' + { name: '#slash{', accent: '\u2215' }, // '\u0337' + { name: '#vec{', accent: '\u02ED', hasw: true }, // '\u0350' arrowhead + { name: '#frac{', twolines: 'line', middle: true }, + { name: '#splitmline{', twolines: true, middle: true }, + { name: '#splitline{', twolines: true }, + { name: '#sqrt[', arg: 'int', sqrt: true }, // root with arbitrary power + { name: '#sqrt{', sqrt: true }, // square root + { name: '#sum', special: '\u2211', w: 0.8, h: 0.9 }, + { name: '#int', special: '\u222B', w: 0.3, h: 1.0 }, + { name: '#left[', right: '#right]', braces: '[]' }, + { name: '#left(', right: '#right)', braces: '()' }, + { name: '#left{', right: '#right}', braces: '{}' }, + { name: '#left|', right: '#right|', braces: '||' }, + { name: '#[]{', braces: '[]' }, + { name: '#(){', braces: '()' }, + { name: '#{}{', braces: '{}' }, + { name: '#||{', braces: '||' } +]; - /** @summary Returns number of colors in the palette */ - getLength() { return this.palette.length; } +// taken from: https://sites.math.washington.edu/~marshall/cxseminar/symbol.htm, starts from 33 +// eslint-disable-next-line +const symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993]; - /** @summary Calculate color for given i and len */ - calcColor(i, len) { return this.getColor(this.calcColorIndex(i, len)); } +// taken from http://www.alanwood.net/demos/wingdings.html, starts from 33 +// eslint-disable-next-line +const wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505]; -} // class ColorPalette +const symbolsPdfMap = {}; -function createDefaultPalette(grayscale) { - const hue2rgb = (p, q, t) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2/3 - t) * 6; - return p; - }, HLStoRGB = (h, l, s) => { - const q = (l < 0.5) ? l * (1 + s) : l + s - l * s, - p = 2 * l - q, - r = hue2rgb(p, q, h + 1/3), - g = hue2rgb(p, q, h), - b = hue2rgb(p, q, h - 1/3); - return '#' + toHex(r) + toHex(g) + toHex(b); - }, minHue = 0, maxHue = 280, maxPretty = 50, palette = []; - for (let i = 0; i < maxPretty; ++i) { - const hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360; - palette.push(HLStoRGB(hue, 0.5, 1)); +/** @summary Return code for symbols from symbols.ttf + * @desc Used in PDF generation + * @private */ +function remapSymbolTtfCode(code) { + if (!symbolsPdfMap[0x3B1]) { + let cnt = 0; + for (const key in symbols_map) { + const symbol = symbols_map[key]; + if (symbol.length === 1) { + let letter; + if (cnt < 54) { + const opGreek = cnt; + // see code in TLatex.cxx, line 1302 + letter = 97 + opGreek; + if (opGreek > 25) + letter -= 58; + if (opGreek === 52) + letter = 0o241; // varUpsilon + if (opGreek === 53) + letter = 0o316; // epsilon + } else { + // see code in TLatex.cxx, line 1323 + const opSpec = cnt - 54; + letter = 0o243 + opSpec; + switch (opSpec) { + case 75: letter = 0o305; break; // AA Angstroem + case 76: letter = 0o345; break; // aa Angstroem + case 80: letter = 0o42; break; // #forall + case 81: letter = 0o44; break; // #exists + } + } + const scode = symbol.charCodeAt(0); + if (scode > 0x80) + symbolsPdfMap[scode] = letter; + } + if (++cnt > 54 + 82) + break; + } + for (let k = 0; k < symbolsMap.length; ++k) { + const scode2 = symbolsMap[k]; + if (scode2) + symbolsPdfMap[scode2] = k + 33; + } } - return new ColorPalette(palette, grayscale); + return symbolsPdfMap[code] ?? code; } -function createGrayPalette() { - const palette = []; - for (let i = 0; i < 50; ++i) { - const code = toHex((i+2)/60); - palette.push('#'+code+code+code); - } - return new ColorPalette(palette); -} -/* eslint-disable comma-spacing */ +/** @summary Reformat text node if it includes greek or special symbols + * @desc Used in PDF generation where greek symbols are not available + * @private */ +function replaceSymbolsInTextNode(node) { + if (node.$text && node.$font) { + node.$originalHTML = node.innerHTML; + node.$originalFont = node.getAttribute('font-family'); + node.innerHTML = node.$text; + if (settings.LoadSymbolTtf) + node.setAttribute('font-family', node.$font.isSymbol); + else + node.setAttribute('font-family', (node.$font.isSymbol === kWingdings) ? 'zapfdingbats' : 'symbol'); + return node.$font.isSymbol; + } -/** @summary Create color palette - * @private */ -function getColorPalette(id, grayscale) { - id = id || settings.Palette; - if ((id > 0) && (id < 10)) return createGrayPalette(); - if (id < 51) return createDefaultPalette(grayscale); - if (id > 113) id = 57; - const stops = [0,0.125,0.25,0.375,0.5,0.625,0.75,0.875,1]; - let rgb; - switch (id) { - // Deep Sea - case 51: rgb = [[0,9,13,17,24,32,27,25,29],[0,0,0,2,37,74,113,160,221],[28,42,59,78,98,129,154,184,221]]; break; - // Grey Scale - case 52: rgb = [[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255]]; break; - // Dark Body Radiator - case 53: rgb = [[0,45,99,156,212,230,237,234,242],[0,0,0,45,101,168,238,238,243],[0,1,1,3,9,8,11,95,230]]; break; - // Two-color hue (dark blue through neutral gray to bright yellow) - case 54: rgb = [[0,22,44,68,93,124,160,192,237],[0,16,41,67,93,125,162,194,241],[97,100,99,99,93,68,44,26,74]]; break; - // Rain Bow - case 55: rgb = [[0,5,15,35,102,196,208,199,110],[0,48,124,192,206,226,97,16,0],[99,142,198,201,90,22,13,8,2]]; break; - // Inverted Dark Body Radiator - case 56: rgb = [[242,234,237,230,212,156,99,45,0],[243,238,238,168,101,45,0,0,0],[230,95,11,8,9,3,1,1,0]]; break; - // Bird (default, keep float for backward compatibility) - case 57: rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982],[42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716],[134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; break; - // Cubehelix - case 58: rgb = [[0,24,2,54,176,236,202,194,255],[0,29,92,129,117,120,176,236,255],[0,68,80,34,57,172,252,245,255]]; break; - // Green Red Violet - case 59: rgb = [[13,23,25,63,76,104,137,161,206],[95,67,37,21,0,12,35,52,79],[4,3,2,6,11,22,49,98,208]]; break; - // Blue Red Yellow - case 60: rgb = [[0,61,89,122,143,160,185,204,231],[0,0,0,0,14,37,72,132,235],[0,140,224,144,4,5,6,9,13]]; break; - // Ocean - case 61: rgb = [[14,7,2,0,5,11,55,131,229],[105,56,26,1,42,74,131,171,229],[2,21,35,60,92,113,160,185,229]]; break; - // Color Printable On Grey - case 62: rgb = [[0,0,0,70,148,231,235,237,244],[0,0,0,0,0,69,67,216,244],[0,102,228,231,177,124,137,20,244]]; break; - // Alpine - case 63: rgb = [[50,56,63,68,93,121,165,192,241],[66,81,91,96,111,128,155,189,241],[97,91,75,65,77,103,143,167,217]]; break; - // Aquamarine - case 64: rgb = [[145,166,167,156,131,114,101,112,132],[158,178,179,181,163,154,144,152,159],[190,199,201,192,176,169,160,166,190]]; break; - // Army - case 65: rgb = [[93,91,99,108,130,125,132,155,174],[126,124,128,129,131,121,119,153,173],[103,94,87,85,80,85,107,120,146]]; break; - // Atlantic - case 66: rgb = [[24,40,69,90,104,114,120,132,103],[29,52,94,127,150,162,159,151,101],[29,52,96,132,162,181,184,186,131]]; break; - // Aurora - case 67: rgb = [[46,38,61,92,113,121,132,150,191],[46,36,40,69,110,135,131,92,34],[46,80,74,70,81,105,165,211,225]]; break; - // Avocado - case 68: rgb = [[0,4,12,30,52,101,142,190,237],[0,40,86,121,140,172,187,213,240],[0,9,14,18,21,23,27,35,101]]; break; - // Beach - case 69: rgb = [[198,206,206,211,198,181,161,171,244],[103,133,150,172,178,174,163,175,244],[49,54,55,66,91,130,184,224,244]]; break; - // Black Body - case 70: rgb = [[243,243,240,240,241,239,186,151,129],[0,46,99,149,194,220,183,166,147],[6,8,36,91,169,235,246,240,233]]; break; - // Blue Green Yellow - case 71: rgb = [[22,19,19,25,35,53,88,139,210],[0,32,69,108,135,159,183,198,215],[77,96,110,116,110,100,90,78,70]]; break; - // Brown Cyan - case 72: rgb = [[68,116,165,182,189,180,145,111,71],[37,82,135,178,204,225,221,202,147],[16,55,105,147,196,226,232,224,178]]; break; - // CMYK - case 73: rgb = [[61,99,136,181,213,225,198,136,24],[149,140,96,83,132,178,190,135,22],[214,203,168,135,110,100,111,113,22]]; break; - // Candy - case 74: rgb = [[76,120,156,183,197,180,162,154,140],[34,35,42,69,102,137,164,188,197],[64,69,78,105,142,177,205,217,198]]; break; - // Cherry - case 75: rgb = [[37,102,157,188,196,214,223,235,251],[37,29,25,37,67,91,132,185,251],[37,32,33,45,66,98,137,187,251]]; break; - // Coffee - case 76: rgb = [[79,100,119,137,153,172,192,205,250],[63,79,93,103,115,135,167,196,250],[51,59,66,61,62,70,110,160,250]]; break; - // Dark Rain Bow - case 77: rgb = [[43,44,50,66,125,172,178,155,157],[63,63,85,101,138,163,122,51,39],[121,101,58,44,47,55,57,44,43]]; break; - // Dark Terrain - case 78: rgb = [[0,41,62,79,90,87,99,140,228],[0,57,81,93,85,70,71,125,228],[95,91,91,82,60,43,44,112,228]]; break; - // Fall - case 79: rgb = [[49,59,72,88,114,141,176,205,222],[78,72,66,57,59,75,106,142,173],[78,55,46,40,39,39,40,41,47]]; break; - // Fruit Punch - case 80: rgb = [[243,222,201,185,165,158,166,187,219],[94,108,132,135,125,96,68,51,61],[7,9,12,19,45,89,118,146,118]]; break; - // Fuchsia - case 81: rgb = [[19,44,74,105,137,166,194,206,220],[19,28,40,55,82,110,159,181,220],[19,42,68,96,129,157,188,203,220]]; break; - // Grey Yellow - case 82: rgb = [[33,44,70,99,140,165,199,211,216],[38,50,76,105,140,165,191,189,167],[55,67,97,124,140,166,163,129,52]]; break; - // Green Brown Terrain - case 83: rgb = [[0,33,73,124,136,152,159,171,223],[0,43,92,124,134,126,121,144,223],[0,43,68,76,73,64,72,114,223]]; break; - // Green Pink - case 84: rgb = [[5,18,45,124,193,223,205,128,49],[48,134,207,230,193,113,28,0,7],[6,15,41,121,193,226,208,130,49]]; break; - // Island - case 85: rgb = [[180,106,104,135,164,188,189,165,144],[72,126,154,184,198,207,205,190,179],[41,120,158,188,194,181,145,100,62]]; break; - // Lake - case 86: rgb = [[57,72,94,117,136,154,174,192,215],[0,33,68,109,140,171,192,196,209],[116,137,173,201,200,201,203,190,187]]; break; - // Light Temperature - case 87: rgb = [[31,71,123,160,210,222,214,199,183],[40,117,171,211,231,220,190,132,65],[234,214,228,222,210,160,105,60,34]]; break; - // Light Terrain - case 88: rgb = [[123,108,109,126,154,172,188,196,218],[184,138,130,133,154,175,188,196,218],[208,130,109,99,110,122,150,171,218]]; break; - // Mint - case 89: rgb = [[105,106,122,143,159,172,176,181,207],[252,197,194,187,174,162,153,136,125],[146,133,144,155,163,167,166,162,174]]; break; - // Neon - case 90: rgb = [[171,141,145,152,154,159,163,158,177],[236,143,100,63,53,55,44,31,6],[59,48,46,44,42,54,82,112,179]]; break; - // Pastel - case 91: rgb = [[180,190,209,223,204,228,205,152,91],[93,125,147,172,181,224,233,198,158],[236,218,160,133,114,132,162,220,218]]; break; - // Pearl - case 92: rgb = [[225,183,162,135,115,111,119,145,211],[205,177,166,135,124,117,117,132,172],[186,165,155,135,126,130,150,178,226]]; break; - // Pigeon - case 93: rgb = [[39,43,59,63,80,116,153,177,223],[39,43,59,74,91,114,139,165,223],[39,50,59,70,85,115,151,176,223]]; break; - // Plum - case 94: rgb = [[0,38,60,76,84,89,101,128,204],[0,10,15,23,35,57,83,123,199],[0,11,22,40,63,86,97,94,85]]; break; - // Red Blue - case 95: rgb = [[94,112,141,165,167,140,91,49,27],[27,46,88,135,166,161,135,97,58],[42,52,81,106,139,158,155,137,116]]; break; - // Rose - case 96: rgb = [[30,49,79,117,135,151,146,138,147],[63,60,72,90,94,94,68,46,16],[18,28,41,56,62,63,50,36,21]]; break; - // Rust - case 97: rgb = [[0,30,63,101,143,152,169,187,230],[0,14,28,42,58,61,67,74,91],[39,26,21,18,15,14,14,13,13]]; break; - // Sandy Terrain - case 98: rgb = [[149,140,164,179,182,181,131,87,61],[62,70,107,136,144,138,117,87,74],[40,38,45,49,49,49,38,32,34]]; break; - // Sienna - case 99: rgb = [[99,112,148,165,179,182,183,183,208],[39,40,57,79,104,127,148,161,198],[15,16,18,33,51,79,103,129,177]]; break; - // Solar - case 100: rgb = [[99,116,154,174,200,196,201,201,230],[0,0,8,32,58,83,119,136,173],[5,6,7,9,9,14,17,19,24]]; break; - // South West - case 101: rgb = [[82,106,126,141,155,163,142,107,66],[62,44,69,107,135,152,149,132,119],[39,25,31,60,73,68,49,72,188]]; break; - // Starry Night - case 102: rgb = [[18,29,44,72,116,158,184,208,221],[27,46,71,105,146,177,189,190,183],[39,55,80,108,130,133,124,100,76]]; break; - // Sunset - case 103: rgb = [[0,48,119,173,212,224,228,228,245],[0,13,30,47,79,127,167,205,245],[0,68,75,43,16,22,55,128,245]]; break; - // Temperature Map - case 104: rgb = [[34,70,129,187,225,226,216,193,179],[48,91,147,194,226,229,196,110,12],[234,212,216,224,206,110,53,40,29]]; break; - // Thermometer - case 105: rgb = [[30,55,103,147,174,203,188,151,105],[0,65,138,182,187,175,121,53,9],[191,202,212,208,171,140,97,57,30]]; break; - // Valentine - case 106: rgb = [[112,97,113,125,138,159,178,188,225],[16,17,24,37,56,81,110,136,189],[38,35,46,59,78,103,130,152,201]]; break; - // Visible Spectrum - case 107: rgb = [[18,72,5,23,29,201,200,98,29],[0,0,43,167,211,117,0,0,0],[51,203,177,26,10,9,8,3,0]]; break; - // Water Melon - case 108: rgb = [[19,42,64,88,118,147,175,187,205],[19,55,89,125,154,169,161,129,70],[19,32,47,70,100,128,145,130,75]]; break; - // Cool - case 109: rgb = [[33,31,42,68,86,111,141,172,227],[255,175,145,106,88,55,15,0,0],[255,205,202,203,208,205,203,206,231]]; break; - // Copper - case 110: rgb = [[0,25,50,79,110,145,181,201,254],[0,16,30,46,63,82,101,124,179],[0,12,21,29,39,49,61,74,103]]; break; - // Gist Earth - case 111: rgb = [[0,13,30,44,72,120,156,200,247],[0,36,84,117,141,153,151,158,247],[0,94,100,82,56,66,76,131,247]]; break; - // Viridis - case 112: rgb = [[26,51,43,33,28,35,74,144,246],[9,24,55,87,118,150,180,200,222],[30,96,112,114,112,101,72,35,0]]; break; - // Cividis - case 113: rgb = [[0,5,65,97,124,156,189,224,255],[32,54,77,100,123,148,175,203,234],[77,110,107,111,120,119,111,94,70]]; break; - default: return createDefaultPalette(); + if (node.childNodes.length !== 1) + return false; + const txt = node.textContent; + if (!txt) + return false; + let new_html = '', lasti = -1; + for (let i = 0; i < txt.length; i++) { + const code = txt.charCodeAt(i), + newcode = remapSymbolTtfCode(code); + if (code !== newcode) { + new_html += txt.slice(lasti + 1, i) + `${String.fromCharCode(newcode)} `; + lasti = i; + } } - const NColors = 255, Red = rgb[0], Green = rgb[1], Blue = rgb[2], palette = []; + if (lasti < 0) + return false; - for (let g = 1; g < stops.length; g++) { - // create the colors... - const nColorsGradient = Math.round(Math.floor(NColors*stops[g]) - Math.floor(NColors*stops[g-1])); - for (let c = 0; c < nColorsGradient; c++) { - const col = '#' + toHex(Red[g-1] + c * (Red[g] - Red[g-1]) / nColorsGradient, 1) + - toHex(Green[g-1] + c * (Green[g] - Green[g-1]) / nColorsGradient, 1) + - toHex(Blue[g-1] + c * (Blue[g] - Blue[g-1]) / nColorsGradient, 1); - palette.push(col); - } - } + if (lasti < txt.length - 1) + new_html += txt.slice(lasti + 1, txt.length); - return new ColorPalette(palette, grayscale); + node.$originalHTML = node.innerHTML; + node.$originalFont = node.getAttribute('font-family'); + node.innerHTML = new_html; + return kSymbol; } -/** @summary Decode list of ROOT colors coded by TWebCanvas +/** @summary Replace codes from symbols.ttf into normal font - when symbols.ttf cannot be used * @private */ -function decodeWebCanvasColors(oper) { - const colors = [], arr = oper.split(';'); - for (let n = 0; n < arr.length; ++n) { - const name = arr[n]; - let p = name.indexOf(':'); - if (p > 0) { - colors[parseInt(name.slice(0, p))] = color(`rgb(${name.slice(p+1)})`).formatHex(); - continue; - } - p = name.indexOf('='); - if (p > 0) { - colors[parseInt(name.slice(0, p))] = color(`rgba(${name.slice(p+1)})`).formatHex8(); - continue; - } - p = name.indexOf('#'); - if (p < 0) continue; - - const colindx = parseInt(name.slice(0, p)), - data = JSON.parse(name.slice(p+1)), - grad = { _typename: data[0] === 10 ? clTLinearGradient : clTRadialGradient, fNumber: colindx, fType: data[0] }; - - let cnt = 1; - - grad.fCoordinateMode = Math.round(data[cnt++]); - const nsteps = Math.round(data[cnt++]); - grad.fColorPositions = data.slice(cnt, cnt + nsteps); cnt += nsteps; - grad.fColors = data.slice(cnt, cnt + 4*nsteps); cnt += 4*nsteps; - grad.fStart = { fX: data[cnt++], fY: data[cnt++] }; - grad.fEnd = { fX: data[cnt++], fY: data[cnt++] }; - if (grad._typename === clTRadialGradient && cnt < data.length) { - grad.fR1 = data[cnt++]; - grad.fR2 = data[cnt++]; - } - - colors[colindx] = grad; +function replaceSymbols(s, name) { + const m = name === kWingdings ? wingdingsMap : symbolsMap; + let res = ''; + for (let k = 0; k < s.length; ++k) { + const code = s.charCodeAt(k), + new_code = (code > 32) ? m[code - 33] : 0; + res += String.fromCodePoint(new_code || code); } - - return colors; + return res; } +/** @summary Just add plain text to the SVG text elements + * @private */ +function producePlainText(painter, txt_node, arg) { + arg.plain = true; + if (arg.simple_latex) + arg.text = translateLaTeX(arg.text); // replace latex symbols + if (arg.font?.isSymbol) { + txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol)); + txt_node.property('$text', arg.text); + txt_node.property('$font', arg.font); + } else + txt_node.text(arg.text); +} -createRootColors(); - -/** @summary Returns visible rect of element - * @param {object} elem - d3.select object with element - * @param {string} [kind] - which size method is used - * @desc kind = 'bbox' use getBBox, works only with SVG - * kind = 'full' - full size of element, using getBoundingClientRect function - * kind = 'nopadding' - excludes padding area - * With node.js can use 'width' and 'height' attributes when provided in element +/** @summary Check if plain text * @private */ -function getElementRect(elem, sizearg) { - if (!elem || elem.empty()) - return { x: 0, y: 0, width: 0, height: 0 }; +function isPlainText(txt) { + return !txt || ((txt.indexOf('#') < 0) && (txt.indexOf('{') < 0)); +} - if ((isNodeJs() && (sizearg !== 'bbox')) || elem.property('_batch_mode')) - return { x: 0, y: 0, width: parseInt(elem.attr('width')), height: parseInt(elem.attr('height')) }; +/** @summary translate TLatex and draw inside provided g element + * @desc use together with normal elements + * @private */ +function parseLatex(node, arg, label, curr) { + let nelements = 0; - const styleValue = name => { - let value = elem.style(name); - if (!value || !isStr(value)) return 0; - value = parseFloat(value.replace('px', '')); - return !Number.isFinite(value) ? 0 : Math.round(value); + const currG = () => { + if (!curr.g) + curr.g = node.append('svg:g'); + return curr.g; }; - let rect = elem.node().getBoundingClientRect(); - if ((sizearg === 'bbox') && (parseFloat(rect.width) > 0)) - rect = elem.node().getBBox(); - - const res = { x: 0, y: 0, width: parseInt(rect.width), height: parseInt(rect.height) }; - if (rect.left !== undefined) { - res.x = parseInt(rect.left); - res.y = parseInt(rect.top); - } else if (rect.x !== undefined) { - res.x = parseInt(rect.x); - res.y = parseInt(rect.y); - } + const shiftX = dx => { curr.x += Math.round(dx); }; - if ((sizearg === undefined) || (sizearg === 'nopadding')) { - // this is size exclude padding area - res.width -= styleValue('padding-left') + styleValue('padding-right'); - res.height -= styleValue('padding-top') + styleValue('padding-bottom'); - } + const extendPosition = (x1, y1, x2, y2) => { + if (!curr.rect) + curr.rect = { x1, y1, x2, y2 }; + else { + curr.rect.x1 = Math.min(curr.rect.x1, x1); + curr.rect.y1 = Math.min(curr.rect.y1, y1); + curr.rect.x2 = Math.max(curr.rect.x2, x2); + curr.rect.y2 = Math.max(curr.rect.y2, y2); + } - return res; -} + curr.rect.last_y1 = y1; // upper position of last symbols + curr.rect.width = curr.rect.x2 - curr.rect.x1; + curr.rect.height = curr.rect.y2 - curr.rect.y1; -/** @summary Calculate absolute position of provided element in canvas - * @private */ -function getAbsPosInCanvas(sel, pos) { - if (!pos) return pos; + if (!curr.parent) + arg.text_rect = curr.rect; + }; - while (!sel.empty() && !sel.classed('root_canvas')) { - const cl = sel.attr('class'); - if (cl && ((cl.indexOf('root_frame') >= 0) || (cl.indexOf('__root_pad_') >= 0))) { - pos.x += sel.property('draw_x') || 0; - pos.y += sel.property('draw_y') || 0; - } - sel = select(sel.node().parentNode); - } - return pos; -} + const addSpaces = nspaces => { + extendPosition(curr.x, curr.y, curr.x + nspaces * curr.fsize * 0.4, curr.y); + shiftX(nspaces * curr.fsize * 0.4); + }; + /** Position pos.g node which directly attached to curr.g and uses curr.g coordinates */ + const positionGNode = (pos, x, y, inside_gg) => { + x = Math.round(x); + y = Math.round(y); -/** @summary Converts numeric value to string according to specified format. - * @param {number} value - value to convert - * @param {string} [fmt='6.4g'] - format can be like 5.4g or 4.2e or 6.4f - * @param {boolean} [ret_fmt] - when true returns array with value and actual format like ['0.1','6.4f'] - * @return {string|Array} - converted value or array with value and actual format - * @private */ -function floatToString(value, fmt, ret_fmt) { - if (!fmt) fmt = '6.4g'; + makeTranslate(pos.g, x, y); + pos.rect.x1 += x; + pos.rect.x2 += x; + pos.rect.y1 += y; + pos.rect.y2 += y; - fmt = fmt.trim(); - const len = fmt.length; - if (len < 2) - return ret_fmt ? [value.toFixed(4), '6.4f'] : value.toFixed(4); - const last = fmt[len-1]; - fmt = fmt.slice(0, len-1); - let isexp, prec = fmt.indexOf('.'); - prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec+1)); - if (!Number.isInteger(prec) || (prec <= 0)) prec = 4; - - let 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')) { - const se = floatToString(value, fmt+'Q', true); - let sg = floatToString(value, fmt+'W', true); - if (se[0].length < sg[0].length) sg = se; - return ret_fmt ? sg : sg[0]; - } else { - isexp = false; - prec = 4; - } + if (inside_gg) + extendPosition(curr.x + pos.rect.x1, curr.y + pos.rect.y1, curr.x + pos.rect.x2, curr.y + pos.rect.y2); + else + extendPosition(pos.rect.x1, pos.rect.y1, pos.rect.x2, pos.rect.y2); + }; - if (isexp) { - // for exponential representation only one significant digit befor point - if (significance) prec--; - if (prec < 0) prec = 0; + /** Create special sub-container for elements like sqrt or braces */ + const createGG = is_a => { + const gg = currG(); - const se = value.toExponential(prec); - return ret_fmt ? [se, `5.${prec}e`] : se; - } + // this is indicator that gg element will be the only one, one can use directly main container + if ((nelements === 1) && !label && !curr.x && !curr.y && !is_a) + return gg; - let sg = value.toFixed(prec); + return makeTranslate(gg.append(is_a ? 'svg:a' : 'svg:g'), curr.x, curr.y); + }; - if (significance) { - // when using fixed representation, one could get 0 - if ((value !== 0) && (Number(sg) === 0) && (prec > 0)) { - prec = 20; sg = value.toFixed(prec); - } + const extractSubLabel = (check_first, lbrace, rbrace) => { + let pos = 0, n = 1, extra_braces = false; + if (!lbrace) + lbrace = '{'; + if (!rbrace) + rbrace = '}'; - let l = 0; - while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) l++; + const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos + br.length) === br); - let diff = sg.length - l - prec; - if (sg.indexOf('.') > l) diff--; + if (check_first) { + if (!match(lbrace)) { + console.log(`not starting with ${lbrace} in ${label}`); + return -1; + } + label = label.slice(lbrace.length); + } - if (diff !== 0) { - prec -= diff; - if (prec < 0) - prec = 0; - else if (prec > 20) - prec = 20; - sg = value.toFixed(prec); + while (n && (pos < label.length)) { + if (match(lbrace)) { + n++; + pos += lbrace.length; + } else if (match(rbrace)) { + n--; + pos += rbrace.length; + if ((n === 0) && (typeof check_first === 'string') && match(check_first + lbrace)) { + // handle special case like a^{b}^{2} should mean a^{b^{2}} + n++; + pos += lbrace.length + check_first.length; + check_first = true; + extra_braces = true; + } + } else pos++; + } + if (n) { + console.log(`mismatch with open ${lbrace} and closing ${rbrace} in ${label}`); + return -1; } - } - return ret_fmt ? [sg, '5.'+prec+'f'] : sg; -} + let sublabel = label.slice(0, pos - rbrace.length); + if (extra_braces) + sublabel = lbrace + sublabel + rbrace; -/** @summary Draw options interpreter - * @private */ -class DrawOptions { + label = label.slice(pos); - constructor(opt) { - this.opt = isStr(opt) ? opt.toUpperCase().trim() : ''; - this.part = ''; - } + return sublabel; + }; - /** @summary Returns true if remaining options are empty or contain only seperators symbols. */ - empty() { - if (this.opt.length === 0) return true; - return this.opt.replace(/[ ;_,]/g, '').length === 0; - } + const createPath = (gg, d, dofill) => { + return gg.append('svg:path') + .attr('d', d || 'M0,0') // provide dummy d value as placeholder, preserve order of attributes + .style('stroke', dofill ? 'none' : (curr.color || arg.color)) + .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize * (curr.font.weight ? 0.1 : 0.07)))) + .style('fill', dofill ? (curr.color || arg.color) : 'none'); + }; - /** @summary Returns remaining part of the draw options. */ - remain() { return this.opt; } + const createSubPos = fscale => { + return { + lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize * (fscale || 1), + color: curr.color, font: curr.font, parent: curr, + painter: curr.painter, italic: curr.italic, bold: curr.bold + }; + }; - /** @summary Checks if given option exists */ - check(name, postpart) { - const pos = this.opt.indexOf(name); - if (pos < 0) return false; - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos + name.length); - this.part = ''; - if (!postpart) return true; + while (label) { + let best = label.length, found = null; - let pos2 = pos; - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) pos2++; - if (pos2 > pos) { - this.part = this.opt.slice(pos, pos2); - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); + for (let n = 0; n < latex_features.length; ++n) { + const pos = label.indexOf(latex_features[n].name); + if ((pos >= 0) && (pos < best)) { + best = pos; + found = latex_features[n]; + } } - if (postpart !== 'color') - return true; + if (best > 0) { + const alone = (best === label.length) && (nelements === 0) && !found; - this.color = this.partAsInt(1) - 1; - if (this.color >= 0) return true; - for (let col = 0; col < 8; ++col) { - if (getColor(col).toUpperCase() === this.part) { - this.color = col; - return true; - } - } - return false; - } + nelements++; - /** @summary Returns remaining part of found option as integer. */ - partAsInt(offset, dflt) { - let mult = 1; - const last = this.part ? this.part[this.part.length - 1] : ''; - if (last === 'K') - mult = 1e3; - else if (last === 'M') - mult = 1e6; - else if (last === 'G') - mult = 1e9; - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseInt(val, 10) : Number.NaN; - return !Number.isInteger(val) ? (dflt || 0) : mult*val + (offset || 0); - } + let s = translateLaTeX(label.slice(0, best)), + nbeginspaces = 0, nendspaces = 0; - /** @summary Returns remaining part of found option as float. */ - partAsFloat(offset, dflt) { - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseFloat(val) : Number.NaN; - return !Number.isFinite(val) ? (dflt || 0) : val + (offset || 0); - } + while ((nbeginspaces < s.length) && (s[nbeginspaces] === ' ')) + nbeginspaces++; -} // class DrawOptions + if (nbeginspaces > 0) { + addSpaces(nbeginspaces); + s = s.slice(nbeginspaces); + } + while ((nendspaces < s.length) && (s[s.length - 1 - nendspaces] === ' ')) + nendspaces++; -/** @summary Simple random generator with controlled seed - * @private */ -class TRandom { + if (nendspaces > 0) + s = s.slice(0, s.length - nendspaces); - constructor(i) { - if (i !== undefined) this.seed(i); - } + if (s || alone) { + // if single text element created, place it directly in the node + const g = curr.g || (alone ? node : currG()), + elem = g.append('svg:text'); - /** @summary Seed simple random generator */ - 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; - } + if (alone && !curr.g) + curr.g = elem; - /** @summary Produce random value between 0 and 1 */ - 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; - let result = ((this.m_z << 16) + this.m_w) & 0xffffffff; - result /= 4294967296; - return result + 0.5; - } + // apply font attributes only once, inherited by all other elements + if (curr.ufont) { + curr.font.setPainter(arg.painter); + curr.font.setFont(curr.g); + } -} // class TRandom + if (curr.bold !== undefined) + curr.g.attr('font-weight', curr.bold ? 'bold' : 'normal'); + if (curr.italic !== undefined) + curr.g.attr('font-style', curr.italic ? 'italic' : 'normal'); -/** @summary Build smooth SVG curve uzing Bezier - * @desc Reuse code from https://stackoverflow.com/questions/62855310 - * @private */ -function buildSvgCurve(p, args) { - if (!args) - args = {}; - if (!args.line) - args.calc = true; - else if (args.ndig === undefined) - args.ndig = 0; + // set fill color directly to element + elem.attr('fill', curr.color || arg.color || null); - let npnts = p.length; - if (npnts < 3) args.line = true; + // set font size directly to element to avoid complex control + elem.attr('font-size', Math.max(1, Math.round(curr.fsize))); - args.t = args.t ?? 0.2; + if (curr.font?.isSymbol) { + elem.text(replaceSymbols(s, curr.font.isSymbol)); + elem.property('$text', s); + elem.property('$font', curr.font); + } else + elem.text(s); - if ((args.ndig === undefined) || args.height) { - args.maxy = p[0].gry; - args.mindiff = 100; - for (let i = 1; i < npnts; i++) { - args.maxy = Math.max(args.maxy, p[i].gry); - args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i-1].grx), Math.abs(p[i].gry - p[i-1].gry)); - } - if (args.ndig === undefined) - args.ndig = args.mindiff > 20 ? 0 : (args.mindiff > 5 ? 1 : 2); - } + const rect = !isNodeJs() && !settings.ApproxTextSize && !arg.fast + ? getElementRect(elem, 'nopadding') + : { height: curr.fsize * 1.2, width: approximateLabelWidth(s, curr.font, curr.fsize) }; - const end_point = (pnt1, pnt2, sign) => { - const len = Math.sqrt((pnt2.gry - pnt1.gry)**2 + (pnt2.grx - pnt1.grx)**2) * args.t, - a2 = Math.atan2(pnt2.dgry, pnt2.dgrx), - a1 = Math.atan2(sign*(pnt2.gry - pnt1.gry), sign*(pnt2.grx - pnt1.grx)); + if (curr.x) + elem.attr('x', curr.x); + if (curr.y) + elem.attr('y', curr.y); - pnt1.dgrx = len * Math.cos(2*a1 - a2); - pnt1.dgry = len * Math.sin(2*a1 - a2); - }, conv = val => { - if (!args.ndig || (Math.round(val) === val)) - return val.toFixed(0); - let s = val.toFixed(args.ndig), p = s.length-1; - while (s[p] === '0') p--; - if (s[p] === '.') p--; - s = s.slice(0, p+1); - return (s === '-0') ? '0' : s; - }; + // for single symbols like f,l,i,j one gets wrong estimation of total width, use it in sup/sub-scripts + const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1 * curr.fsize : 0; - if (args.calc) { - for (let i = 1; i < npnts - 1; i++) { - p[i].dgrx = (p[i+1].grx - p[i-1].grx) * args.t; - p[i].dgry = (p[i+1].gry - p[i-1].gry) * args.t; - } + extendPosition(curr.x, curr.y - rect.height * 0.8, curr.x + rect.width, curr.y + rect.height * 0.2); - if (npnts > 2) { - end_point(p[0], p[1], 1); - end_point(p[npnts - 1], p[npnts - 2], -1); - } else if (p.length === 2) { - p[0].dgrx = (p[1].grx - p[0].grx) * args.t; - p[0].dgry = (p[1].gry - p[0].gry) * args.t; - p[1].dgrx = -p[0].dgrx; - p[1].dgry = -p[0].dgry; + if (!alone) { + shiftX(rect.width + xgap); + addSpaces(nendspaces); + curr.xgap = 0; + } else if (curr.deco) { + elem.attr('text-decoration', curr.deco); + curr.deco = ''; // inform that decoration was applied + } else + curr.xgap = xgap; // may be used in accent or somewhere else + } else + addSpaces(nendspaces); } - } - let path = `${args.cmd ?? 'M'}${conv(p[0].grx)},${conv(p[0].gry)}`; + if (!found) + return true; - if (!args.line) { - let i0 = 1; - if (args.qubic) { - npnts--; i0++; - path += `Q${conv(p[1].grx-p[1].dgrx)},${conv(p[1].gry-p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; - } - path += `C${conv(p[i0-1].grx+p[i0-1].dgrx)},${conv(p[i0-1].gry+p[i0-1].dgry)},${conv(p[i0].grx-p[i0].dgrx)},${conv(p[i0].gry-p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; + // remove preceding block and tag itself + label = label.slice(best + found.name.length); - // continue with simpler points - for (let i = i0 + 1; i < npnts; i++) - path += `S${conv(p[i].grx-p[i].dgrx)},${conv(p[i].gry-p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; + nelements++; - if (args.qubic) - path += `Q${conv(p[npnts].grx-p[npnts].dgrx)},${conv(p[npnts].gry-p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; - } else if (npnts < 10000) { - // build simple curve + if (found.accent) { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; - let acc_x = 0, acc_y = 0, currx = Math.round(p[0].grx), curry = Math.round(p[0].gry); + const gg = createGG(), + subpos = createSubPos(), + reduce = (sublabel.length !== 1) ? 1 : (((sublabel >= 'a') && (sublabel <= 'z') && ('tdbfhkli'.indexOf(sublabel) < 0)) ? 0.75 : 0.9); - const flush = () => { - if (acc_x) { path += 'h' + acc_x; acc_x = 0; } - if (acc_y) { path += 'v' + acc_y; acc_y = 0; } - }; + parseLatex(gg, arg, sublabel, subpos); - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - dx = Math.round(bin.grx) - currx, - dy = Math.round(bin.gry) - curry; - if (dx && dy) { - flush(); - path += `l${dx},${dy}`; - } else if (!dx && dy) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) flush(); - acc_y += dy; - } else if (dx && !dy) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) flush(); - acc_x += dx; + const minw = curr.fsize * 0.6, + y1 = Math.round(subpos.rect.y1 * reduce), + dy2 = Math.round(curr.fsize * 0.1), dy = dy2 * 2, + dot = `a${dy2},${dy2},0,0,1,${dy},0a${dy2},${dy2},0,0,1,${-dy},0z`; + let xpos = 0, w = subpos.rect.width; + + // shift symbol when it is too small + if (found.hasw && (w < minw)) { + w = minw; + xpos = (minw - subpos.rect.width) / 2; } - currx += dx; curry += dy; - } - flush(); - } else { - // build line with trying optimize many vertical moves - let currx = Math.round(p[0].grx), curry = Math.round(p[0].gry), - cminy = curry, cmaxy = curry, prevy = curry; + const w5 = Math.round(w * 0.5), w3 = Math.round(w * 0.3), w2 = w5 - w3, w8 = w5 + w3; + w = w5 * 2; - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - lastx = Math.round(bin.grx), - lasty = Math.round(bin.gry), - dx = lastx - currx; - if (dx === 0) { - // if X not change, just remember amplitude and - cminy = Math.min(cminy, lasty); - cmaxy = Math.max(cmaxy, lasty); - prevy = lasty; - continue; - } + positionGNode(subpos, xpos, 0, true); - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy-curry}`; - path += `v${cmaxy-cminy}`; - if (cmaxy !== prevy) - path += `v${prevy-cmaxy}`; - curry = prevy; + switch (found.name) { + case '#check{': createPath(gg, `M${w2},${y1 - dy}L${w5},${y1}L${w8},${y1 - dy}`); break; + case '#acute{': createPath(gg, `M${w5},${y1}l${dy},${-dy}`); break; + case '#grave{': createPath(gg, `M${w5},${y1}l${-dy},${-dy}`); break; + case '#dot{': createPath(gg, `M${w5 - dy2},${y1}${dot}`, true); break; + case '#ddot{': createPath(gg, `M${w5 - 3 * dy2},${y1}${dot} M${w5 + dy2},${y1}${dot}`, true); break; + case '#tilde{': createPath(gg, `M${w2},${y1} a${w3},${dy},0,0,1,${w3},0 a${w3},${dy},0,0,0,${w3},0`); break; + case '#slash{': createPath(gg, `M${w},${y1}L0,${Math.round(subpos.rect.y2)}`); break; + case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8 - dy},${y1 - dy}l${dy},${dy}l${-dy},${dy}`); break; + default: createPath(gg, `M${w2},${y1}L${w5},${y1 - dy}L${w8},${y1}`); // #hat{ } - const dy = lasty - curry; - if (dy) - path += `l${dx},${dy}`; - else - path += `h${dx}`; - currx = lastx; curry = lasty; - prevy = cminy = cmaxy = lasty; - } - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy-curry}`; - path += `v${cmaxy-cminy}`; - if (cmaxy !== prevy) - path += `v${prevy-cmaxy}`; + shiftX(subpos.rect.width + (subpos.xgap ?? 0)); + + continue; } - } - if (args.height) - args.close = `L${conv(p[p.length-1].grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; + if (found.twolines) { + curr.twolines = true; - return path; -} + const line1 = extractSubLabel(), + line2 = extractSubLabel(true); + if ((line1 === -1) || (line2 === -1)) + return false; -/** @summary Compress SVG code, produced from drawing - * @desc removes extra info or empty elements - * @private */ -function compressSVG(svg) { - svg = svg.replace(/url\("#(\w+)"\)/g, 'url(#$1)') // decode all URL - .replace(/ class="\w*"/g, '') // remove all classes - .replace(/ pad="\w*"/g, '') // remove all pad ids - .replace(/ title=""/g, '') // remove all empty titles - .replace(/<\/g>/g, '') // remove all empty groups with transform - .replace(/<\/g>/g, ''); // remove all empty groups + const gg = createGG(), + fscale = curr.parent?.twolines ? 0.7 : 1, + subpos1 = createSubPos(fscale); - // remove all empty frame svgs, typically appears in 3D drawings, maybe should be improved in frame painter itself - svg = svg.replace(/<\/svg>/g, ''); + parseLatex(gg, arg, line1, subpos1); - return svg; -} + const path = found.twolines === 'line' ? createPath(gg) : null, + subpos2 = createSubPos(fscale); + parseLatex(gg, arg, line2, subpos2); -/** - * @summary Base painter class - * - */ + const w = Math.max(subpos1.rect.width, subpos2.rect.width), + dw = subpos1.rect.width - subpos2.rect.width, + dy = -curr.fsize * 0.35; // approximate position of middle line -class BasePainter { + positionGNode(subpos1, found.middle && (dw < 0) ? -dw / 2 : 0, dy - subpos1.rect.y2, true); - /** @summary constructor - * @param {object|string} [dom] - dom element or id of dom element */ - constructor(dom) { - this.divid = null; // either id of DOM element or element itself - if (dom) this.setDom(dom); - } + positionGNode(subpos2, found.middle && (dw > 0) ? dw / 2 : 0, dy - subpos2.rect.y1, true); - /** @summary Assign painter to specified DOM element - * @param {string|object} elem - element ID or DOM Element - * @desc Normally DOM element should be already assigned in constructor - * @protected */ - setDom(elem) { - if (elem !== undefined) { - this.divid = elem; - delete this._selected_main; - } - } + path?.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize * 0.1)}`); - /** @summary Returns assigned dom element */ - getDom() { - return this.divid; - } + shiftX(w); - /** @summary Selects main HTML element assigned for drawing - * @desc if main element was layouted, returns main element inside layout - * @param {string} [is_direct] - if 'origin' specified, returns original element even if actual drawing moved to some other place - * @return {object} d3.select object for main element for drawing */ - selectDom(is_direct) { - if (!this.divid) return select(null); + delete curr.twolines; - let res = this._selected_main; - if (!res) { - if (isStr(this.divid)) { - let id = this.divid; - if (id[0] !== '#') id = '#' + id; - res = select(id); - if (!res.empty()) this.divid = res.node(); - } else - res = select(this.divid); - this._selected_main = res; + continue; } - if (!res || res.empty() || (is_direct === 'origin')) return res; - - const use_enlarge = res.property('use_enlarge'), - layout = res.property('layout') || 'simple', - layout_selector = (layout === 'simple') ? '' : res.property('layout_selector'); + const extractLowUp = name => { + const res = {}; - if (layout_selector) - res = res.select(layout_selector); + if (name) { + label = '{' + label; + res[name] = extractSubLabel(name === 'low' ? '_' : '^'); + if (res[name] === -1) + return false; + } - // one could redirect here - if (!is_direct && !res.empty() && use_enlarge) - res = select(getDocument().getElementById('jsroot_enlarge_div')); + while (label) { + if ((label[0] === '_') && !res.low) { + label = label.slice(1); + res.low = extractSubLabel('_'); + if (res.low === -1) { + console.log(`error with ${found.name} low limit ${label}`); + return false; + } + } else if ((label[0] === '^') && !res.up) { + label = label.slice(1); + res.up = extractSubLabel('^'); + if (res.up === -1) { + console.log(`error with ${found.name} upper limit ${label}`); + return false; + } + } else break; + } + return res; + }; - return res; - } + if (found.low_up) { + const subs = extractLowUp(found.low_up); + if (!subs) + return false; - /** @summary Access/change top painter - * @private */ - _accessTopPainter(on) { - const chld = this.selectDom().node()?.firstChild; - if (!chld) return null; - if (on === true) - chld.painter = this; - else if (on === false) - delete chld.painter; - return chld.painter; - } + const x = curr.x, dx = 0.03 * curr.fsize, ylow = 0.25 * curr.fsize; + let pos_up, pos_low, w1 = 0, w2 = 0, yup = -curr.fsize; - /** @summary Set painter, stored in first child element - * @desc Only make sense after first drawing is completed and any child element add to configured DOM - * @protected */ - setTopPainter() { - this._accessTopPainter(true); - } + if (subs.up) { + pos_up = createSubPos(0.6); + parseLatex(currG(), arg, subs.up, pos_up); + } - /** @summary Return top painter set for the selected dom element - * @protected */ - getTopPainter() { - return this._accessTopPainter(); - } + if (subs.low) { + pos_low = createSubPos(0.6); + parseLatex(currG(), arg, subs.low, pos_low); + } - /** @summary Clear reference on top painter - * @protected */ - clearTopPainter() { - this._accessTopPainter(false); - } + if (pos_up) { + if (!pos_low && curr.rect) + yup = Math.min(yup, curr.rect.last_y1); + positionGNode(pos_up, x + dx, yup - pos_up.rect.y1 - curr.fsize * 0.1); + w1 = pos_up.rect.width; + } - /** @summary Generic method to cleanup painter - * @desc Removes all visible elements and all internal data */ - cleanup(keep_origin) { - this.clearTopPainter(); - const origin = this.selectDom('origin'); - if (!origin.empty() && !keep_origin) origin.html(''); - this.divid = null; - delete this._selected_main; + if (pos_low) { + positionGNode(pos_low, x + dx, ylow - pos_low.rect.y2 + curr.fsize * 0.1); + w2 = pos_low.rect.width; + } - if (isFunc(this._hpainter?.removePainter)) - this._hpainter.removePainter(this); + shiftX(dx + Math.max(w1, w2)); - delete this._hitemname; - delete this._hdrawopt; - delete this._hpainter; - } + continue; + } - /** @summary Checks if draw elements were resized and drawing should be updated - * @return {boolean} true if resize was detected - * @protected - * @abstract */ - checkResize(/* arg */) {} + if (found.special) { + // this is sum and integral, now make fix height, later can adjust to right-content size - /** @summary Function checks if geometry of main div was changed. - * @desc take into account enlarge state, used only in PadPainter class - * @return size of area when main div is drawn - * @private */ - testMainResize(check_level, new_size, height_factor) { - const enlarge = this.enlargeMain('state'), - origin = this.selectDom('origin'), - main = this.selectDom(), - lmt = 5; // minimal size + const subs = extractLowUp() || {}, + gg = createGG(), path = createPath(gg), + h = Math.round(curr.fsize * 1.7), w = Math.round(curr.fsize), r = Math.round(h * 0.1); + let x_up, x_low; - if ((enlarge !== 'on') && new_size?.width && new_size?.height) { - origin.style('width', new_size.width + 'px') - .style('height', new_size.height + 'px'); - } + if (found.name === '#sum') { + x_up = x_low = w / 2; + path.attr('d', `M${w},${Math.round(-0.75 * h)}h${-w}l${Math.round(0.4 * w)},${Math.round(0.3 * h)}l${Math.round(-0.4 * w)},${Math.round(0.7 * h)}h${w}`); + } else { + x_up = 3 * r; + x_low = r; + path.attr('d', `M0,${Math.round(0.25 * h - r)}a${r},${r},0,0,0,${2 * r},0v${2 * r - h}a${r},${r},0,1,1,${2 * r},0`); + // path.attr('transform','skewX(-3)'); could use skewX for italic-like style + } - const rect_origin = getElementRect(origin, true), - can_resize = origin.attr('can_resize'); - let do_resize = false; + extendPosition(curr.x, curr.y - 0.6 * h, curr.x + w, curr.y + 0.4 * h); - if (can_resize === 'height') - if (height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) do_resize = true; + if (subs.low) { + const subpos1 = createSubPos(0.6); + parseLatex(gg, arg, subs.low, subpos1); + positionGNode(subpos1, (x_low - subpos1.rect.width / 2), 0.25 * h - subpos1.rect.y1, true); + } - if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && - can_resize && can_resize !== 'false') do_resize = true; + if (subs.up) { + const subpos2 = createSubPos(0.6); + parseLatex(gg, arg, subs.up, subpos2); + positionGNode(subpos2, (x_up - subpos2.rect.width / 2), -0.75 * h - subpos2.rect.y2, true); + } - if (do_resize && (enlarge !== 'on')) { - // if zero size and can_resize attribute set, change container size + shiftX(w); - if (rect_origin.width > lmt) { - height_factor = height_factor || 0.66; - origin.style('height', Math.round(rect_origin.width * height_factor) + 'px'); - } else if (can_resize !== 'height') - origin.style('width', '200px').style('height', '100px'); + continue; } - const rect = getElementRect(main), - old_h = main.property('_jsroot_height'), - old_w = main.property('_jsroot_width'); + if (found.braces) { + const rbrace = found.right, + lbrace = rbrace ? found.name : '{', + sublabel = extractSubLabel(false, lbrace, rbrace), + gg = createGG(), + subpos = createSubPos(), + path1 = createPath(gg); - rect.changed = false; + parseLatex(gg, arg, sublabel, subpos); - if (old_h && old_w && (old_h > 0) && (old_w > 0)) { - if ((old_h !== rect.height) || (old_w !== rect.width)) - rect.changed = (check_level > 1) || (rect.width / old_w < 0.99) || (rect.width / old_w > 1.01) || (rect.height / old_h < 0.99) || (rect.height / old_h > 1.01); - } else - rect.changed = true; + const path2 = createPath(gg), + w = Math.max(2, Math.round(curr.fsize * 0.2)), + r = subpos.rect, dy = Math.round(r.y2 - r.y1), + r_y1 = Math.round(r.y1), r_width = Math.round(r.width); - if (rect.changed) - main.property('_jsroot_height', rect.height).property('_jsroot_width', rect.width); + switch (found.braces) { + case '||': + path1.attr('d', `M${w},${r_y1}v${dy}`); + path2.attr('d', `M${3 * w + r_width},${r_y1}v${dy}`); + break; + case '[]': + path1.attr('d', `M${2 * w},${r_y1}h${-w}v${dy}h${w}`); + path2.attr('d', `M${2 * w + r_width},${r_y1}h${w}v${dy}h${-w}`); + break; + case '{}': + path1.attr('d', `M${2 * w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}`); + path2.attr('d', `M${2 * w + r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}`); + break; + default: // () + path1.attr('d', `M${w},${r_y1}a${4 * dy},${4 * dy},0,0,0,0,${dy}`); + path2.attr('d', `M${3 * w + r_width},${r_y1}a${4 * dy},${4 * dy},0,0,1,0,${dy}`); + } - // after change enlarge state always mark main element as resized - if (origin.property('did_enlarge')) { - rect.changed = true; - origin.property('did_enlarge', false); - } + positionGNode(subpos, 2 * w, 0, true); - return rect; - } + extendPosition(curr.x, curr.y + r.y1, curr.x + 4 * w + r.width, curr.y + r.y2); - /** @summary Try enlarge main drawing element to full HTML page. - * @param {string|boolean} action - defines that should be done - * @desc Possible values for action parameter: - * - true - try to enlarge - * - false - revert enlarge state - * - 'toggle' - toggle enlarge state - * - 'state' - only returns current enlarge state - * - 'verify' - check if element can be enlarged - * if action not specified, just return possibility to enlarge main div - * @protected */ - enlargeMain(action, skip_warning) { - const main = this.selectDom(true), - origin = this.selectDom('origin'), - doc = getDocument(); + shiftX(4 * w + r.width); - if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) return false; + continue; + } - if ((action === undefined) || (action === 'verify')) return true; + if (found.deco) { + const sublabel = extractSubLabel(), + gg = createGG(), + subpos = createSubPos(); - const state = origin.property('use_enlarge') ? 'on' : 'off'; + subpos.deco = found.deco; - if (action === 'state') return state; + parseLatex(gg, arg, sublabel, subpos); - if (action === 'toggle') action = (state === 'off'); + const r = subpos.rect; + switch (subpos.deco) { + case 'underline': + createPath(gg, `M0,${Math.round(r.y2)}h${Math.round(r.width)}`); + break; + case 'overline': + createPath(gg, `M0,${Math.round(r.y1)}h${Math.round(r.width)}`); + break; + case 'line-through': + createPath(gg, `M0,${Math.round(0.45 * r.y1 + 0.55 * r.y2)}h${Math.round(r.width)}`); + break; + } - let enlarge = select(doc.getElementById('jsroot_enlarge_div')); + positionGNode(subpos, 0, 0, true); + shiftX(r.width); + continue; + } - if ((action === true) && (state !== 'on')) { - if (!enlarge.empty()) return false; + if (found.bi) { // bold or italic + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; - enlarge = select(doc.body) - .append('div') - .attr('id', 'jsroot_enlarge_div') - .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; left: 1px; top: 1px; bottom: 1px; right: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); + const subpos = createSubPos(); - const rect1 = getElementRect(main), - rect2 = getElementRect(enlarge); + subpos[found.bi] = !subpos[found.bi]; - // if new enlarge area not big enough, do not do it - if ((rect2.width <= rect1.width) || (rect2.height <= rect1.height)) { - if (rect2.width * rect2.height < rect1.width * rect1.height) { - if (!skip_warning) - console.log(`Enlarged area ${rect2.width} x ${rect2.height} smaller then original drawing ${rect1.width} x ${rect1.height}`); - enlarge.remove(); - return false; - } - } + parseLatex(currG(), arg, sublabel, subpos); - while (main.node().childNodes.length > 0) - enlarge.node().appendChild(main.node().firstChild); + positionGNode(subpos, curr.x, curr.y); - origin.property('use_enlarge', true); - origin.property('did_enlarge', true); - return true; + shiftX(subpos.rect.width); + + continue; } - if ((action === false) && (state !== 'off')) { - while (enlarge.node() && enlarge.node().childNodes.length > 0) - main.node().appendChild(enlarge.node().firstChild); - enlarge.remove(); - origin.property('use_enlarge', false); - origin.property('did_enlarge', true); - return true; + let foundarg = 0; + + if (found.arg) { + const pos = label.indexOf(']{'); + if (pos < 0) { + console.log('missing argument for ', found.name); + return false; + } + foundarg = label.slice(0, pos); + if (found.arg === 'int') { + foundarg = parseInt(foundarg); + if (!Number.isInteger(foundarg)) { + console.log('wrong int argument', label.slice(0, pos)); + return false; + } + } else if (found.arg === 'float') { + foundarg = parseFloat(foundarg); + if (!Number.isFinite(foundarg)) { + console.log('wrong float argument', label.slice(0, pos)); + return false; + } + } + label = label.slice(pos + 2); } - return false; - } + if (found.shift) { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; - /** @summary Set item name, associated with the painter - * @desc Used by {@link HierarchyPainter} - * @private */ - setItemName(name, opt, hpainter) { - if (isStr(name)) - this._hitemname = name; - else - delete this._hitemname; - // only upate draw option, never delete. - if (isStr(opt)) - this._hdrawopt = opt; + const subpos = createSubPos(); - this._hpainter = hpainter; - } + parseLatex(currG(), arg, sublabel, subpos); - /** @summary Returns assigned item name - * @desc Used with {@link HierarchyPainter} to identify drawn item name */ - getItemName() { return this._hitemname ?? null; } + let shiftx = 0, shifty = 0; + if (found.shift === 'x') + shiftx = foundarg * subpos.rect.width; + else + shifty = foundarg * subpos.rect.height; - /** @summary Returns assigned item draw option - * @desc Used with {@link HierarchyPainter} to identify drawn item option */ - getItemDrawOpt() { return this._hdrawopt ?? ''; } + positionGNode(subpos, curr.x + shiftx, curr.y + shifty); -} // class BasePainter + shiftX(subpos.rect.width * (shiftx > 0 ? 1 + foundarg : 1)); -/** @summary Load and initialize JSDOM from nodes - * @return {Promise} with d3 selection for d3_body - * @private */ -async function _loadJSDOM() { - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => { - if (!internals.nodejs_window) { - internals.nodejs_window = (new handle.JSDOM('hello')).window; - internals.nodejs_document = internals.nodejs_window.document; // used with three.js - internals.nodejs_body = select(internals.nodejs_document).select('body'); // get d3 handle for body + continue; } - return { JSDOM: handle.JSDOM, doc: internals.nodejs_document, body: internals.nodejs_body }; - }); -} + if (found.name === '#url[') { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; -/** @summary Return translate string for transform attribute of some svg element - * @return string or null if x and y are zeros - * @private */ -function makeTranslate(g, x, y) { - if (!isObject(g)) { - y = x; x = g; g = null; - } - const res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); - return g ? g.attr('transform', res) : res; -} + const gg = createGG(true), + subpos = createSubPos(); + gg.attr('href', foundarg); + if (!isBatchMode() && !curr.painter?.isBatchMode()) { + gg.on('mouseenter', () => gg.style('text-decoration', 'underline')) + .on('mouseleave', () => gg.style('text-decoration', null)) + .append('svg:title').text(`link on ${foundarg}`); + } -/** @summary Configure special style used for highlight or dragging elements - * @private */ -function addHighlightStyle(elem, drag) { - if (drag) { - elem.style('stroke', 'steelblue') - .style('fill-opacity', '0.1'); - } else { - elem.style('stroke', '#4572A7') - .style('fill', '#4572A7') - .style('opacity', '0'); - } -} + parseLatex(gg, arg, sublabel, subpos); -/** @summary Create pdf for existing SVG element - * @return {Promise} with produced PDF file as url string - * @private */ -async function svgToPDF(args, as_buffer) { - const nodejs = isNodeJs(); - let _jspdf, _svg2pdf, need_symbols = false; + positionGNode(subpos, 0, 0, true); + shiftX(subpos.rect.width); + continue; + } - const pr = nodejs - ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h1 => { _jspdf = h1; return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); }).then(h2 => { _svg2pdf = h2; }) - : loadScript(exports.source_dir + 'scripts/jspdf.umd.min.js').then(() => loadScript(exports.source_dir + 'scripts/svg2pdf.umd.min.js')).then(() => { _jspdf = globalThis.jspdf; _svg2pdf = globalThis.svg2pdf; }), - restore_fonts = [], restore_dominant = [], restore_text = [], - node_transform = args.node.getAttribute('transform'), custom_fonts = {}; + if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; - if (args.reset_tranform) - args.node.removeAttribute('transform'); + const subpos = createSubPos(); - return pr.then(() => { - select(args.node).selectAll('g').each(function() { - if (this.hasAttribute('font-family')) { - const name = this.getAttribute('font-family'); - if (name === 'Courier New') { - this.setAttribute('font-family', 'courier'); - if (!args.can_modify) restore_fonts.push(this); // keep to restore it + if (found.name === '#color[') + subpos.color = curr.painter.getColor(foundarg); + else if (found.name === '#font[') { + subpos.font = new FontHandler(foundarg, subpos.fsize); + // here symbols embedding not works, use replacement + if ((subpos.font.name === kSymbol) && !subpos.font.isSymbol) { + subpos.font.isSymbol = kSymbol; + subpos.font.name = kTimes; } - } - }); + subpos.font.setUseFullStyle(true); // while embedding - need to enforce full style + subpos.ufont = true; // mark that custom font is applied + } else + subpos.fsize *= foundarg; - select(args.node).selectAll('text').each(function() { - if (this.hasAttribute('dominant-baseline')) { - this.setAttribute('dy', '.2em'); // slightly different as in plain text - this.removeAttribute('dominant-baseline'); - if (!args.can_modify) restore_dominant.push(this); // keep to restore it - } else if (args.can_modify && nodejs && this.getAttribute('dy') === '.4em') - this.setAttribute('dy', '.2em'); // better allignment in PDF + parseLatex(currG(), arg, sublabel, subpos); - if (replaceSymbolsInTextNode(this)) { - need_symbols = true; - if (!args.can_modify) restore_text.push(this); // keep to restore it - } - }); + positionGNode(subpos, curr.x, curr.y); - if (nodejs) { - const doc = internals.nodejs_document; - doc.oldFunc = doc.createElementNS; - globalThis.document = doc; - globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet; - globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule; - doc.createElementNS = function(ns, kind) { - const res = doc.oldFunc(ns, kind); - res.getBBox = function() { - let width = 50, height = 10; - if (this.tagName === 'text') { - // TODO: use jsDOC fonts for label width estimation - const font = detectFont(this); - width = approximateLabelWidth(this.textContent, font); - height = font.size; - } + shiftX(subpos.rect.width); - return { x: 0, y: 0, width, height }; - }; - return res; - }; + continue; } - // eslint-disable-next-line new-cap - const doc = new _jspdf.jsPDF({ - orientation: 'landscape', - unit: 'px', - format: [args.width + 10, args.height + 10] - }); - - // add custom fonts to PDF document, only TTF format supported - select(args.node).selectAll('style').each(function() { - const fh = this.$fonthandler; - if (!fh || custom_fonts[fh.name] || (fh.format !== 'ttf')) return; - const filename = fh.name.toLowerCase().replace(/\s/g, '') + '.ttf'; - doc.addFileToVFS(filename, fh.base64); - doc.addFont(filename, fh.name, 'normal', 'normal', (fh.name === 'symbol') ? 'StandardEncoding' : 'Identity-H'); - custom_fonts[fh.name] = true; - }); + if (found.sqrt) { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; - let pr2 = Promise.resolve(true); + const gg = createGG(), subpos = createSubPos(); + let subpos0; - if (need_symbols && !custom_fonts.symbol) { - if (!getCustomFont('symbol')) { - pr2 = nodejs - ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { - const base64 = fs.readFileSync('../../fonts/symbol.ttf').toString('base64'); - console.log('reading symbol.ttf', base64.length); - addCustomFont(25, 'symbol', 'ttf', base64); - }) - : httpRequest(exports.source_dir+'fonts/symbol.ttf', 'bin').then(buf => { - const base64 = btoa_func(buf); - addCustomFont(25, 'symbol', 'ttf', base64); - }); + if (found.arg) { + subpos0 = createSubPos(0.7); + parseLatex(gg, arg, foundarg.toString(), subpos0); } - pr2 = pr2.then(() => { - const fh = getCustomFont('symbol'), - handler = new FontHandler(1242, 10); - handler.name = 'symbol'; - handler.base64 = fh.base64; - handler.addCustomFontToSvg(select(args.node)); - doc.addFileToVFS('symbol.ttf', fh.base64); - doc.addFont('symbol.ttf', 'symbol', 'normal', 'normal', 'StandardEncoding' /* 'WinAnsiEncoding' */); - }); - } - - return pr2.then(() => _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height })) - .then(() => { - if (args.reset_tranform && !args.can_modify && node_transform) - args.node.setAttribute('transform', node_transform); - - restore_fonts.forEach(node => node.setAttribute('font-family', 'Courier New')); - restore_dominant.forEach(node => { - node.setAttribute('dominant-baseline', 'middle'); - node.removeAttribute('dy'); - }); + // placeholder for the sqrt sign + const path = createPath(gg); - restore_text.forEach(node => { node.innerHTML = node.$originalHTML; }); + parseLatex(gg, arg, sublabel, subpos); - const res = as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); - if (nodejs) { - globalThis.document = undefined; - globalThis.CSSStyleSheet = undefined; - globalThis.CSSStyleRule = undefined; - internals.nodejs_document.createElementNS = internals.nodejs_document.oldFunc; - if (as_buffer) return Buffer.from(res); - } - return res; - }); - }); -} + const r = subpos.rect, + h = Math.round(r.height), + h1 = Math.round(r.height * 0.1), + w = Math.round(r.width), midy = Math.round((r.y1 + r.y2) / 2), + f2 = Math.round(curr.fsize * 0.2), r_y2 = Math.round(r.y2); + if (subpos0) + positionGNode(subpos0, 0, midy - subpos0.fsize * 0.3, true); -/** @summary Create image based on SVG - * @param {string} svg - svg code of the image - * @param {string} [image_format] - image format like 'png', 'jpeg' or 'webp' - * @param {boolean} [as_buffer] - return Buffer object for image - * @return {Promise} with produced image in base64 form or as Buffer (or canvas when no image_format specified) - * @private */ -async function svgToImage(svg, image_format, as_buffer) { - if (image_format === 'svg') - return svg; + path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2 - midy - f2}l${h1},${f2 - h}h${Math.round(h * 0.2 + w)}v${h1}`); - if (image_format === 'pdf') - return svgToPDF(svg, as_buffer); + positionGNode(subpos, h * 0.4, 0, true); - // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex - const doctype = ''; - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x'+p1); - return c === '%' ? '%25' : c; - }); - svg = decodeURIComponent(svg); + extendPosition(curr.x, curr.y + r.y1 - curr.fsize * 0.1, curr.x + w + h * 0.6, curr.y + r.y2); - const img_src = 'data:image/svg+xml;base64,' + btoa_func(svg); + shiftX(w + h * 0.6); - if (isNodeJs()) { - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(async handle => { - return handle.default.loadImage(img_src).then(img => { - const canvas = handle.default.createCanvas(img.width, img.height); + continue; + } + } - canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); + return true; +} - if (as_buffer) return canvas.toBuffer('image/' + image_format); +/** @summary translate TLatex and draw inside provided g element + * @desc use together with normal elements + * @private */ +function produceLatex(painter, node, arg) { + const pos = { lvl: 0, g: node, x: 0, y: 0, dx: 0, dy: -0.1, fsize: arg.font_size, font: arg.font, parent: null, painter }; + return parseLatex(node, arg, arg.text, pos); +} - return image_format ? canvas.toDataURL('image/' + image_format) : canvas; - }); - }); - } +let _mj_loading; - return new Promise(resolveFunc => { - const image = document.createElement('img'); +/** @summary Load MathJax functionality, + * @desc one need not only to load script but wait for initialization + * @private */ +async function loadMathjax() { + const loading = _mj_loading !== undefined; - image.onload = function() { - const canvas = document.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; + if (!loading && (typeof globalThis.MathJax !== 'undefined')) + return globalThis.MathJax; - canvas.getContext('2d').drawImage(image, 0, 0); + if (!loading) + _mj_loading = []; - if (as_buffer && image_format) - canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format); - else - resolveFunc(image_format ? canvas.toDataURL('image/' + image_format) : canvas); - }; - image.onerror = function(arg) { - console.log(`IMAGE ERROR ${arg}`); - resolveFunc(null); + const promise = new Promise(resolve => { + if (_mj_loading) + _mj_loading.push(resolve); + else + resolve(globalThis.MathJax); + }); + + if (loading) + return promise; + + const svg = { + scale: 1, // global scaling factor for all expressions + minScale: 0.5, // smallest scaling factor to use + mtextInheritFont: false, // true to make mtext elements use surrounding font + merrorInheritFont: true, // true to make merror text use surrounding font + mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules + skipAttributes: {}, // RFDa and other attributes NOT to copy to the output + exFactor: 0.5, // default size of ex in em units + displayAlign: 'center', // default for indentalign when set to 'auto' + displayIndent: '0', // default for indentshift when set to 'auto' + fontCache: 'local', // or 'global' or 'none' + localID: null, // ID to use for local font cache (for single equation processing) + internalSpeechTitles: true, // insert tags with speech content + titleID: 0 // initial id number to use for aria-labeledby titles + }; + + if (!isNodeJs()) { + window.MathJax = { + options: { + enableMenu: false + }, + loader: { + load: ['[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] + }, + tex: { + packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } + }, + svg, + startup: { + ready() { + MathJax.startup.defaultReady(); + const arr = _mj_loading; + _mj_loading = undefined; + arr.forEach(func => func(globalThis.MathJax)); + } + } }; - image.src = img_src; + let mj_dir = '../mathjax/3.2.0'; + if (browser.webwindow && exports.source_dir.indexOf('https://root.cern/js') < 0 && exports.source_dir.indexOf('https://jsroot.gsi.de') < 0) + mj_dir = 'mathjax'; + + return loadScript(exports.source_dir + mj_dir + '/es5/tex-svg.js') + .catch(() => loadScript('https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js')) + .then(() => promise); + } + + let JSDOM; + + return _loadJSDOM().then(handle => { + JSDOM = handle.JSDOM; + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); + }).then(mj0 => { + // return Promise with mathjax loading + mj0.init({ + loader: { + load: ['input/tex', 'output/svg', '[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] + }, + tex: { + packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } + }, + svg, + config: { + JSDOM + }, + startup: { + typeset: false, + ready() { + const mj = MathJax; + + mj.startup.registerConstructor('jsdomAdaptor', () => { + return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); + }); + mj.startup.useAdaptor('jsdomAdaptor', true); + mj.startup.defaultReady(); + const arr = _mj_loading; + _mj_loading = undefined; + arr.forEach(func => func(mj)); + } + } + }); + + return promise; }); } -/** @summary Convert ROOT TDatime object into Date - * @desc Always use UTC to avoid any variation between timezones */ -function getTDatime(dt) { - const y = (dt.fDatime >>> 26) + 1995, - m = ((dt.fDatime << 6) >>> 28) - 1, - d = (dt.fDatime << 10) >>> 27, - h = (dt.fDatime << 15) >>> 27, - min = (dt.fDatime << 20) >>> 26, - s = (dt.fDatime << 26) >>> 26; - return new Date(Date.UTC(y, m, d, h, min, s)); +const math_symbols_map = { + '#LT': '\\langle', + '#GT': '\\rangle', + '#club': '\\clubsuit', + '#spade': '\\spadesuit', + '#heart': '\\heartsuit', + '#diamond': '\\diamondsuit', + '#voidn': '\\wp', + '#voidb': 'f', + '#copyright': '(c)', + '#ocopyright': '(c)', + '#trademark': 'TM', + '#void3': 'TM', + '#oright': 'R', + '#void1': 'R', + '#3dots': '\\ldots', + '#lbar': '\\mid', + '#void8': '\\mid', + '#divide': '\\div', + '#Jgothic': '\\Im', + '#Rgothic': '\\Re', + '#doublequote': '"', + '#plus': '+', + '#minus': '-', + '#/': '/', + '#upoint': '.', + '#aa': '\\mathring{a}', + '#AA': '\\mathring{A}', + '#omicron': 'o', + '#Alpha': 'A', + '#Beta': 'B', + '#Epsilon': 'E', + '#Zeta': 'Z', + '#Eta': 'H', + '#Iota': 'I', + '#Kappa': 'K', + '#Mu': 'M', + '#Nu': 'N', + '#Omicron': 'O', + '#Rho': 'P', + '#Tau': 'T', + '#Chi': 'X', + '#varomega': '\\varpi', + '#corner': '?', + '#ltbar': '?', + '#bottombar': '?', + '#notsubset': '?', + '#arcbottom': '?', + '#cbar': '?', + '#arctop': '?', + '#topbar': '?', + '#arcbar': '?', + '#downleftarrow': '?', + '#splitline': '\\genfrac{}{}{0pt}{}', + '#it': '\\textit', + '#bf': '\\textbf', + '#frac': '\\frac', + '#left{': '\\lbrace', + '#right}': '\\rbrace', + '#left\\[': '\\lbrack', + '#right\\]': '\\rbrack', + '#\\[\\]{': '\\lbrack', + ' } ': '\\rbrack', + '#\\[': '\\lbrack', + '#\\]': '\\rbrack', + '#{': '\\lbrace', + '#}': '\\rbrace', + ' ': '\\;' +}; + +const mathjax_remap = { + upDelta: 'Updelta', + upGamma: 'Upgamma', + upLambda: 'Uplambda', + upOmega: 'Upomega', + upPhi: 'Upphi', + upPi: 'Uppi', + upPsi: 'Uppsi', + upSigma: 'Upsigma', + upTheta: 'Uptheta', + upUpsilon: 'Upupsilon', + upXi: 'Upxi', + notcong: 'ncong', + notgeq: 'ngeq', + notgr: 'ngtr', + notless: 'nless', + notleq: 'nleq', + notsucc: 'nsucc', + notprec: 'nprec', + notsubseteq: 'nsubseteq', + notsupseteq: 'nsupseteq', + openclubsuit: 'clubsuit', + openspadesuit: 'spadesuit', + dasharrow: 'dashrightarrow', + comp: 'circ', + iiintop: 'iiint', + iintop: 'iint', + ointop: 'oint' +}; + +const mathjax_unicode = { + Digamma: 0x3DC, + upDigamma: 0x3DC, + digamma: 0x3DD, + updigamma: 0x3DD, + Koppa: 0x3DE, + koppa: 0x3DF, + upkoppa: 0x3DF, + upKoppa: 0x3DE, + VarKoppa: 0x3D8, + upVarKoppa: 0x3D8, + varkoppa: 0x3D9, + upvarkoppa: 0x3D9, + varkappa: 0x3BA, // not found archaic kappa - use normal + upvarkappa: 0x3BA, + varbeta: 0x3D0, // not found archaic beta - use normal + upvarbeta: 0x3D0, + Sampi: 0x3E0, + upSampi: 0x3E0, + sampi: 0x3E1, + upsampi: 0x3E1, + Stigma: 0x3DA, + upStigma: 0x3DA, + stigma: 0x3DB, + upstigma: 0x3DB, + San: 0x3FA, + upSan: 0x3FA, + san: 0x3FB, + upsan: 0x3FB, + Sho: 0x3F7, + upSho: 0x3F7, + sho: 0x3F8, + upsho: 0x3F8, + P: 0xB6, + aa: 0xB0, + bulletdashcirc: 0x22B7, + circdashbullet: 0x22B6, + downuparrows: 0x21F5, + updownarrows: 0x21C5, + dashdownarrow: 0x21E3, + dashuparrow: 0x21E1, + complement: 0x2201, + dbar: 0x18C, + ddddot: 0x22EF, + dddot: 0x22EF, + ddots: 0x22F1, + defineequal: 0x225D, + defineeq: 0x225D, + downdownharpoons: 0x2965, + downupharpoons: 0x296F, + updownharpoons: 0x296E, + upupharpoons: 0x2963, + hateq: 0x2259, + ldbrack: 0x27E6, + rdbrack: 0x27E7, + leadsfrom: 0x219C, + leftsquigarrow: 0x21DC, + lightning: 0x2607, + napprox: 0x2249, + nasymp: 0x226D, + nequiv: 0x2262, + nsimeq: 0x2244, + nsubseteq: 0x2288, + nsubset: 0x2284, + notapprox: 0x2249, + notasymp: 0x226D, + notequiv: 0x2262, + notni: 0x220C, + notsimeq: 0x2244, + notsubseteq: 0x2288, + notsubset: 0x2284, + notsupseteq: 0x2289, + notsupset: 0x2285, + nsupset: 0x2285, + setdif: 0x2216, + simarrow: 0x2972, + t: 0x2040, + u: 0x2C7, + v: 0x2C7, + undercurvearrowright: 0x293B, + updbar: 0x18C, + wwbar: 0x2015, + awointop: 0x2232, + awoint: 0x2233, + barintop: 0x2A1C, + barint: 0x2A1B, + cwintop: 0x2231, // no opposite direction, use same + cwint: 0x2231, + cwointop: 0x2233, + cwoint: 0x2232, + oiiintop: 0x2230, + oiiint: 0x2230, + oiintop: 0x222F, + oiint: 0x222F, + slashintop: 0x2A0F, + slashint: 0x2A0F +}; + +const mathjax_asis = ['"', '\'', '`', '=', '~']; + +/** @summary Function translates ROOT TLatex into MathJax format + * @private */ +function translateMath(str, kind, color, painter) { + if (kind !== 2) { + for (const x in math_symbols_map) + str = str.replace(new RegExp(x, 'g'), math_symbols_map[x]); + + for (const x in symbols_map) { + if (x.length > 2) + str = str.replace(new RegExp(x, 'g'), '\\' + x.slice(1)); + } + + // replace all #color[]{} occurrences + let clean = '', first = true; + while (str) { + let p = str.indexOf('#color['); + if ((p < 0) && first) { + clean = str; + break; + } + first = false; + if (p) { + const norm = (p < 0) ? str : str.slice(0, p); + clean += norm; + if (p < 0) + break; + } + + str = str.slice(p + 7); + p = str.indexOf(']{'); + if (p <= 0) + break; + const colindx = parseInt(str.slice(0, p)); + if (!Number.isInteger(colindx)) + break; + const col = painter.getColor(colindx); + let cnt = 1; + str = str.slice(p + 2); + p = -1; + while (cnt && (++p < str.length)) { + if (str[p] === '{') + cnt++; + else if (str[p] === '}') + cnt--; + } + if (cnt) + break; + + const part = str.slice(0, p); + str = str.slice(p + 1); + if (part) + clean += `\\color{${col}}{${part}}`; + } + + str = clean; + } else { + if (str === '\\^') + str = '\\unicode{0x5E}'; + if (str === '\\vec') + str = '\\unicode{0x2192}'; + str = str.replace(/\\\./g, '\\unicode{0x2E}').replace(/\\\^/g, '\\hat'); + for (const x in mathjax_unicode) + str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\unicode{0x${mathjax_unicode[x].toString(16)}}`); + mathjax_asis.forEach(symbol => { + str = str.replace(new RegExp(`(\\\\${symbol})`, 'g'), `\\unicode{0x${symbol.charCodeAt(0).toString(16)}}`); + }); + for (const x in mathjax_remap) + str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\${mathjax_remap[x]}`); + } + + if (!isStr(color)) + return str; + + // MathJax SVG converter use colors in normal form + // if (color.indexOf('rgb(') >= 0) + // color = color.replace(/rgb/g, '[RGB]') + // .replace(/\(/g, '{') + // .replace(/\)/g, '}'); + return `\\color{${color}}{${str}}`; } -/** @summary Convert Date object into string used preconfigured time zone - * @desc Time zone stored in settings.TimeZone */ -function convertDate(dt) { - let res = ''; +/** @summary Workaround to fix size attributes in MathJax SVG + * @private */ +function repairMathJaxSvgSize(painter, mj_node, svg, arg) { + const transform = value => { + if (!value || !isStr(value) || (value.length < 3)) + return null; + const p = value.indexOf('ex'); + if ((p < 0) || (p !== value.length - 2)) + return null; + value = parseFloat(value.slice(0, p)); + return Number.isFinite(value) ? value * arg.font.size * 0.5 : null; + }; - if (settings.TimeZone && isStr(settings.TimeZone)) { - try { - res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); - } catch (err) { - res = ''; - } + let width = transform(svg.getAttribute('width')), + height = transform(svg.getAttribute('height')), + valign = svg.getAttribute('style'); + + if (valign && (valign.length > 18) && valign.indexOf('vertical-align:') === 0) { + const p = valign.indexOf('ex;'); + valign = ((p > 0) && (p === valign.length - 3)) ? transform(valign.slice(16, valign.length - 1)) : null; + } else + valign = null; + + width = (!width || (width <= 0.5)) ? 1 : Math.round(width); + height = (!height || (height <= 0.5)) ? 1 : Math.round(height); + + svg.setAttribute('width', width); + svg.setAttribute('height', height); + svg.removeAttribute('style'); + + if (!isNodeJs()) { + const box = getElementRect(mj_node, 'bbox'); + width = 1.05 * box.width; height = 1.05 * box.height; } - return res || dt.toLocaleString('en-GB'); + + arg.valign = valign; + + if (arg.scale) + painter.scaleTextDrawing(Math.max(width / arg.width, height / arg.height), arg.draw_g); +} + +/** @summary Apply attributes to mathjax drawing + * @private */ +function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_factor) { + let mw = parseInt(svg.attr('width')), + mh = parseInt(svg.attr('height')); + + if (isNodeJs()) { + // workaround for NaN in viewBox produced by MathJax + const vb = svg.attr('viewBox'); + if (isStr(vb) && vb.indexOf('NaN') > 0) + svg.attr('viewBox', vb.replaceAll('NaN', '600')); + // console.log('Problematic viewBox', vb, svg.select('text').node()?.innerHTML); + } + + if (Number.isInteger(mh) && Number.isInteger(mw)) { + if (svg_factor > 0) { + mw /= svg_factor; + mh /= svg_factor; + svg.attr('width', Math.round(mw)).attr('height', Math.round(mh)); + } + } else { + const box = getElementRect(mj_node, 'bbox'); // sizes before rotation + mw = box.width || mw || 100; + mh = box.height || mh || 10; + } + + if ((svg_factor > 0) && arg.valign) + arg.valign /= svg_factor; + + if (arg.valign === null) + arg.valign = (font_size - mh) / 2; + + const sign = { x: 1, y: 1 }; + let nx = 'x', ny = 'y'; + if (arg.rotate === 180) + sign.x = sign.y = -1; + else if ((arg.rotate === 270) || (arg.rotate === 90)) { + sign.x = (arg.rotate === 270) ? -1 : 1; + sign.y = -sign.x; + nx = 'y'; + ny = 'x'; // replace names to which align applied + } + + if (arg.align[0] === 'middle') + arg[nx] += sign.x * (arg.width - mw) / 2; + else if (arg.align[0] === 'end') + arg[nx] += sign.x * (arg.width - mw); + + if (arg.align[1] === 'middle') + arg[ny] += sign.y * (arg.height - mh) / 2; + else if (arg.align[1] === 'bottom') + arg[ny] += sign.y * (arg.height - mh); + else if (arg.align[1] === 'bottom-base') + arg[ny] += sign.y * (arg.height - mh - arg.valign); + + let trans = makeTranslate(arg.x, arg.y) || ''; + if (arg.rotate) + trans += `${trans ? ' ' : ''}rotate(${arg.rotate})`; + + mj_node.attr('transform', trans || null).attr('visibility', null); +} + +/** @summary Produce text with MathJax + * @private */ +async function produceMathjax(painter, mj_node, arg) { + const mtext = translateMath(arg.text, arg.latex, arg.color, painter), + options = { em: arg.font.size, ex: arg.font.size / 2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; + + return loadMathjax() + .then(mj => mj.tex2svgPromise(mtext, options)) + .then(elem => { + // when adding element to new node, it will be removed from original parent + const svg = elem.querySelector('svg'); + + mj_node.append(() => svg); + + repairMathJaxSvgSize(painter, mj_node, svg, arg); + + arg.mj_func = applyAttributesToMathJax; + return true; + }); +} + +/** @summary Just typeset HTML node with MathJax + * @private */ +async function typesetMathjax(node) { + return loadMathjax().then(mj => mj.typesetPromise(node ? [node] : undefined)); } -const root_markers = [ - 0, 1, 2, 3, 4, // 0..4 - 5, 106, 107, 104, 1, // 5..9 - 1, 1, 1, 1, 1, // 10..14 - 1, 1, 1, 1, 1, // 15..19 - 104, 125, 126, 132, 4, // 20..24 - 25, 26, 27, 28, 130, // 25..29 - 30, 3, 32, 127, 128, // 30..34 - 35, 36, 37, 38, 137, // 35..39 - 40, 140, 42, 142, 44, // 40..44 - 144, 46, 146, 148, 149]; // 45..49 +// list of marker types which can have line widths +const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46], + // internal recoding of root markers + root_markers = [ + 0, 1, 2, 3, 4, // 0..4 + 5, 106, 107, 104, 1, // 5..9 + 1, 1, 1, 1, 1, // 10..14 + 1, 1, 1, 1, 1, // 15..19 + 104, 125, 126, 132, 4, // 20..24 + 25, 26, 27, 28, 130, // 25..29 + 30, 3, 32, 127, 128, // 30..34 + 35, 36, 37, 38, 137, // 35..39 + 40, 140, 42, 142, 44, // 40..44 + 144, 46, 146, 148, 149]; // 45..49 /** @@ -11042,13 +11620,14 @@ class TAttMarkerHandler { * @param {number} args.size - marker size * @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */ setArgs(args) { - if (isObject(args) && (typeof args.fMarkerStyle === 'number')) args = { attr: args }; + if (isObject(args) && (typeof args.fMarkerStyle === 'number')) + args = { attr: args }; if (args.attr) { - if (args.color === undefined) - args.color = args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); - if (!args.style || (args.style < 0)) args.style = args.attr.fMarkerStyle; - if (!args.size) args.size = args.attr.fMarkerSize; + args.color ??= args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); + if (!args.style || (args.style < 0)) + args.style = args.attr.fMarkerStyle; + args.size ??= args.attr.fMarkerSize; } this.color = args.color; @@ -11056,7 +11635,7 @@ class TAttMarkerHandler { this.size = args.size; this.refsize = args.refsize; - this._configure(); + this.#configure(); } /** @summary Set usage flag of attribute */ @@ -11084,11 +11663,13 @@ class TAttMarkerHandler { if ((xx === this.lastx) && (yy === this.lasty)) mv = ''; // pathological case, but let exclude it else { - const m2 = `m${xx-this.lastx},${yy - this.lasty}`; - if (m2.length < mv.length) mv = m2; + const m2 = `m${xx - this.lastx},${yy - this.lasty}`; + if (m2.length < mv.length) + mv = m2; } } - this.lastx = xx + 1; this.lasty = yy; + this.lastx = xx + 1; + this.lasty = yy; return mv + 'h1'; } @@ -11105,16 +11686,19 @@ class TAttMarkerHandler { change(color, style, size) { this.changed = true; - if (color !== undefined) this.color = color; - if ((style !== undefined) && (style >= 0)) this.style = style; - if (size !== undefined) this.size = size; + if (color !== undefined) + this.color = color; + if ((style !== undefined) && (style >= 0)) + this.style = style; + if (size !== undefined) + this.size = size; - this._configure(); + this.#configure(); } /** @summary Prepare object to create marker * @private */ - _configure() { + #configure() { this.x0 = this.y0 = 0; if ((this.style === 1) || (this.style === 777)) { @@ -11127,8 +11711,15 @@ class TAttMarkerHandler { } this.optimized = false; + this.lwidth = 1; - const marker_kind = root_markers[this.style] ?? 104, + let style = this.style; + if (style >= 50) { + this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length); + style = root_50_67[(style - 50) % root_50_67.length]; + } + + const marker_kind = root_markers[style] ?? 104, shape = marker_kind % 100; this.fill = (marker_kind >= 100); @@ -11138,13 +11729,15 @@ class TAttMarkerHandler { const size = this.getFullSize(); this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2); - if (shape === 30) this.ndig++; // increase precision for star + if (shape === 30) + this.ndig++; // increase precision for star let s1 = size.toFixed(this.ndig); - const s2 = (size/2).toFixed(this.ndig), - s3 = (size/3).toFixed(this.ndig), - s4 = (size/4).toFixed(this.ndig), - s8 = (size/8).toFixed(this.ndig), - s38 = (size*3/8).toFixed(this.ndig); + const s2 = (size / 2).toFixed(this.ndig), + s3 = (size / 3).toFixed(this.ndig), + s4 = (size / 4).toFixed(this.ndig), + s8 = (size / 8).toFixed(this.ndig), + s38 = (size * 3 / 8).toFixed(this.ndig), + s34 = (size * 3 / 4).toFixed(this.ndig); switch (shape) { case 1: // dot @@ -11155,17 +11748,17 @@ class TAttMarkerHandler { this.marker = `v${s1}m-${s2},-${s2}h${s1}`; break; case 3: // asterisk - this.x0 = this.y0 = -size / 2; - this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}m0,-${s2}h${s1}m-${s2},-${s2}v${s1}`; + this.y0 = -size / 2; + this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`; break; case 4: // circle this.x0 = -parseFloat(s2); s1 = (parseFloat(s2) * 2).toFixed(this.ndig); this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`; break; - case 5: // mult - this.x0 = this.y0 = -size / 2; - this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}`; + case 5: // multiply + this.x0 = this.y0 = -3 / 8 * size; + this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`; break; case 6: // small dot this.x0 = -1; @@ -11183,7 +11776,7 @@ class TAttMarkerHandler { this.y0 = -size / 2; this.marker = `l-${s2},${s1}h${s1}z`; break; - case 27: // diamand + case 27: // diamond this.y0 = -size / 2; this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; break; @@ -11193,7 +11786,7 @@ class TAttMarkerHandler { break; case 30: { // star this.y0 = -size / 2; - const s56 = (size*5/6).toFixed(this.ndig), s58 = (size*5/8).toFixed(this.ndig); + const s56 = (size * 5 / 6).toFixed(this.ndig), s58 = (size * 5 / 8).toFixed(this.ndig); this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`; break; } @@ -11210,38 +11803,44 @@ class TAttMarkerHandler { this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`; break; case 37: - this.x0 = -size/2; + this.x0 = -size / 2; this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`; break; case 38: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`; break; case 40: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`; break; case 42: - this.y0 = -size/2; + this.y0 = -size / 2; this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`; break; case 44: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`; break; case 46: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`; break; case 48: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`; break; case 49: - this.x0 = -size/6; this.y0 = -size/2; + this.x0 = -size / 6; + this.y0 = -size / 2; this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`; break; - default: // diamand + default: // diamond this.y0 = -size / 2; this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; break; @@ -11263,6 +11862,7 @@ class TAttMarkerHandler { apply(selection) { this.used = true; selection.style('stroke', this.stroke ? this.color : 'none') + .style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null) .style('fill', this.fill ? this.color : 'none'); } @@ -11278,7 +11878,8 @@ class TAttMarkerHandler { * @param {number} height - height of sample SVG * @private */ createSample(svg, width, height, plain) { - if (plain) svg = select(svg); + if (plain) + svg = select(svg); this.resetPos(); svg.append('path') .attr('d', this.create(width / 2, height / 2)) @@ -11294,9 +11895,12 @@ class TAttMarkerHandler { class TAttFillHandler { + #disabled; // if fill disabled + /** @summary constructor * @param {object} args - arguments see {@link TAttFillHandler#setArgs} for more info - * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, 2 means all other objects where such combination is white-color filling */ + * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, + * 2 means all other objects where such combination is white-color filling */ constructor(args) { this.color = 'none'; this.colorindx = 0; @@ -11313,15 +11917,18 @@ class TAttFillHandler { * @param {object} args - different arguments to set fill attributes * @param {object} [args.attr] - TAttFill object * @param {number} [args.color] - color id - * @param {number} [args.pattern] - filll pattern id + * @param {number} [args.pattern] - fill pattern id * @param {object} [args.svg] - SVG element to store newly created patterns * @param {string} [args.color_as_svg] - color in SVG format */ setArgs(args) { if (isObject(args.attr)) { - if ((args.pattern === undefined) && (args.attr.fFillStyle !== undefined)) args.pattern = args.attr.fFillStyle; - if ((args.color === undefined) && (args.attr.fFillColor !== undefined)) args.color = args.attr.fFillColor; + args.pattern ??= args.attr.fFillStyle; + args.color ??= args.attr.fFillColor; } + if (args.enable !== undefined) + this.enable(args.enable); + const was_changed = this.changed; // preserve changed state this.change(args.color, args.pattern, args.svg, args.color_as_svg, args.painter); this.changed = was_changed; @@ -11329,6 +11936,11 @@ class TAttFillHandler { /** @summary Apply fill style to selection */ apply(selection) { + if (this.#disabled) { + selection.style('fill', 'none'); + return; + } + this.used = true; selection.style('fill', this.getFillColor()); @@ -11345,9 +11957,9 @@ class TAttFillHandler { /** @summary Returns fill color without pattern url. * @desc If empty, alternative color will be provided - * @param {string} [altern] - alternative color which returned when fill color not exists + * @param {string} [alt] - alternative color which returned when fill color not exists * @private */ - getFillColorAlt(altern) { return this.color && (this.color !== 'none') ? this.color : altern; } + getFillColorAlt(alt) { return this.color && (this.color !== 'none') ? this.color : alt; } /** @summary Returns true if color not specified or fill style not specified */ empty() { @@ -11355,6 +11967,11 @@ class TAttFillHandler { return !fill || (fill === 'none'); } + /** @summary Enable or disable fill usage - if disabled only 'fill: none' will be applied */ + enable(on) { + this.#disabled = ((on === undefined) || on) ? undefined : true; + } + /** @summary Set usage flag of attribute */ setUsed(flag) { this.used = flag; @@ -11381,7 +11998,8 @@ class TAttFillHandler { /** @summary Check if solid fill is used, also color can be checked * @param {string} [solid_color] - when specified, checks if fill color matches */ isSolid(solid_color) { - if ((this.pattern !== 1001) || this.gradient) return false; + if ((this.pattern !== 1001) || this.gradient) + return false; return !solid_color || (solid_color === this.color); } @@ -11393,7 +12011,7 @@ class TAttFillHandler { if (!Number.isInteger(this.pattern)) this.pattern = 0; - this.change(this.color, this.pattern, painter ? painter.getCanvSvg() : null, true, painter); + this.change(this.color, this.pattern, painter?.getCanvSvg(), true, painter); } /** @summary Method to change fill attributes. @@ -11438,7 +12056,8 @@ class TAttFillHandler { if (color_as_svg) { this.color = color$1; - if (color$1 !== 'none') indx = color(color$1).hex().slice(1); // fictional index produced from color code + if (color$1 !== 'none') + indx = color(color$1).hex().slice(1); // fictional index produced from color code } else this.color = painter ? painter.getColor(indx) : getColor(indx); @@ -11448,11 +12067,12 @@ class TAttFillHandler { this.color = 'none'; } - if (this.isSolid()) return true; + if (this.isSolid()) + return true; if (!this.gradient) { if ((this.pattern >= 4000) && (this.pattern <= 4100)) { - // special transparent colors (use for subpads) + // special transparent colors (use for sub-pads) this.opacity = (this.pattern - 4000) / 100; return true; } @@ -11460,9 +12080,10 @@ class TAttFillHandler { return false; } - if (!svg || svg.empty()) return false; + if (!svg || svg.empty()) + return false; - let id = '', lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; + let id, lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; if (this.gradient) id = `grad_${this.gradient.fNumber}`; @@ -11470,50 +12091,141 @@ class TAttFillHandler { id = `pat_${this.pattern}_${indx}`; switch (this.pattern) { - case 3001: w = h = 2; fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; break; - case 3002: w = 4; h = 2; fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; break; - case 3003: w = h = 4; fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; break; - case 3004: w = h = 8; lines = 'M8,0L0,8'; break; - case 3005: w = h = 8; lines = 'M0,0L8,8'; break; - case 3006: w = h = 4; lines = 'M1,0v4'; break; - case 3007: w = h = 4; lines = 'M0,1h4'; break; + case 3001: + w = h = 2; + fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; + break; + case 3002: + w = 4; + h = 2; + fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; + break; + case 3003: + w = h = 4; + fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; + break; + case 3004: + w = h = 8; + lines = 'M8,0L0,8'; + break; + case 3005: + w = h = 8; + lines = 'M0,0L8,8'; + break; + case 3006: + w = h = 4; + lines = 'M1,0v4'; + break; + case 3007: + w = h = 4; + lines = 'M0,1h4'; + break; case 3008: w = h = 10; fills = 'M0,3v-3h3ZM7,0h3v3ZM0,7v3h3ZM7,10h3v-3ZM5,2l3,3l-3,3l-3,-3Z'; lines = 'M0,3l5,5M3,10l5,-5M10,7l-5,-5M7,0l-5,5'; break; - case 3009: w = 12; h = 12; lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; lfill = 'none'; break; - case 3010: w = h = 10; lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; break; // bricks - case 3011: w = 9; h = 18; lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; lfill = 'none'; break; - case 3012: w = 10; h = 20; lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; lfill = 'none'; break; - case 3013: w = h = 7; lines = 'M0,0L7,7M7,0L0,7'; lfill = 'none'; break; - case 3014: w = h = 16; lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; lfill = 'none'; break; - case 3015: w = 6; h = 12; lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; lfill = 'none'; break; - case 3016: w = 12; h = 7; lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; lfill = 'none'; break; - case 3017: w = h = 4; lines = 'M3,1l-2,2'; break; - case 3018: w = h = 4; lines = 'M1,1l2,2'; break; + case 3009: + w = h = 12; + lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; + lfill = 'none'; + break; + case 3010: // bricks + w = h = 10; + lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; + break; + case 3011: + w = 9; + h = 18; + lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; + lfill = 'none'; + break; + case 3012: + w = 10; + h = 20; + lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; + lfill = 'none'; + break; + case 3013: + w = h = 7; + lines = 'M0,0L7,7M7,0L0,7'; + lfill = 'none'; + break; + case 3014: + w = h = 16; + lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; + lfill = 'none'; + break; + case 3015: + w = 6; + h = 12; + lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; + lfill = 'none'; + break; + case 3016: + w = 12; + h = 7; + lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; + lfill = 'none'; + break; + case 3017: + w = h = 4; + lines = 'M3,1l-2,2'; + break; + case 3018: + w = h = 4; + lines = 'M1,1l2,2'; + break; case 3019: w = h = 12; lines = 'M1,6A5,5,0,0,0,11,6A5,5,0,0,0,1,6h-1h1A5,5,0,0,1,6,11v1v-1A5,5,0,0,1,11,6h1h-1A5,5,0,0,1,6,1v-1v1A5,5,0,0,1,1,6'; lfill = 'none'; break; - case 3020: w = 7; h = 12; lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; lfill = 'none'; break; - case 3021: w = h = 8; lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; lfill = 'none'; break; // left stairs - case 3022: w = h = 8; lines = 'M0,2h2v4h4v2M6,0v2h2'; lfill = 'none'; break; // right stairs - case 3023: w = h = 8; fills = 'M4,0h4v4zM8,4v4h-4z'; fills2 = 'M4,0L0,4L4,8L8,4Z'; break; - case 3024: w = h = 16; fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; break; - case 3025: w = h = 18; fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; break; + case 3020: + w = 7; + h = 12; + lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; + lfill = 'none'; + break; + case 3021: // left stairs + w = h = 8; + lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; + lfill = 'none'; + break; + case 3022: // right stairs + w = h = 8; + lines = 'M0,2h2v4h4v2M6,0v2h2'; + lfill = 'none'; + break; + case 3023: + w = h = 8; + fills = 'M4,0h4v4zM8,4v4h-4z'; + fills2 = 'M4,0L0,4L4,8L8,4Z'; + break; + case 3024: + w = h = 16; + fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; + fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; + break; + case 3025: + w = h = 18; + fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; + break; default: { if ((this.pattern > 3025) && (this.pattern < 3100)) { // same as 3002, see TGX11.cxx, line 2234 - w = 4; h = 2; fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; break; + w = 4; + h = 2; + fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; + break; } const code = this.pattern % 1000, k = code % 10, j = ((code - k) % 100) / 10, i = (code - j * 10 - k) / 100; - if (!i) break; + if (!i) + break; // use flexible hatches only possible when single pattern is used, // otherwise it is not possible to adjust pattern dimension that both hatches match with each other @@ -11524,7 +12236,7 @@ class TAttFillHandler { hatches_spacing = Math.max(1, Math.round(spacing_original)) * 6, sz = i * hatches_spacing; // axis distance between lines - id += use_new ? `_hn${Math.round(spacing_original*100)}` : `_ho${hatches_spacing}`; + id += use_new ? `_hn${Math.round(spacing_original * 100)}` : `_ho${hatches_spacing}`; w = h = 6 * sz; // we use at least 6 steps @@ -11557,34 +12269,35 @@ class TAttFillHandler { pos.push(0, y1, w, y2); y1 += step; } - for (let k = 0; k < pos.length; k += 4) { + for (let b = 0; b < pos.length; b += 4) { if (swap) { - x1 = pos[k+1]; - y1 = pos[k]; - x2 = pos[k+3]; - y2 = pos[k+2]; + x1 = pos[b + 1]; + y1 = pos[b]; + x2 = pos[b + 3]; + y2 = pos[b + 2]; } else { - x1 = pos[k]; - y1 = pos[k+1]; - x2 = pos[k+2]; - y2 = pos[k+3]; + x1 = pos[b]; + y1 = pos[b + 1]; + x2 = pos[b + 2]; + y2 = pos[b + 3]; } lines += `M${x1},${y1}`; if (y2 === y1) - lines += `h${x2-x1}`; + lines += `h${x2 - x1}`; else if (x2 === x1) - lines += `v${y2-y1}`; + lines += `v${y2 - y1}`; else lines += `L${x2},${y2}`; } - }, + }; - produce_new = (_aa, _bb, angle, swapx) => { + /* eslint-disable-next-line one-var */ + const produce_new = (_aa, _bb, angle, swapx) => { if ((angle === 0) || (angle === 90)) { - const dy = i*spacing_original*3, + const dy = i * spacing_original * 3, nsteps = Math.round(h / dy), dyreal = h / nsteps; - let yy = dyreal/2; + let yy = dyreal / 2; while (yy < h) { if (angle === 0) @@ -11597,8 +12310,8 @@ class TAttFillHandler { return; } - const a = angle/180*Math.PI, - dy = i*spacing_original*3/Math.cos(a), + const a = angle / 180 * Math.PI, + dy = i * spacing_original * 3 / Math.cos(a), hside = Math.tan(a) * w, hside_steps = Math.round(hside / dy), dyreal = hside / hside_steps, @@ -11608,12 +12321,13 @@ class TAttFillHandler { let yy = nsteps * dyreal; - while (Math.abs(yy-h) < 0.1) yy -= dyreal; + while (Math.abs(yy - h) < 0.1) + yy -= dyreal; while (yy + hside > 0) { let x1 = 0, y1 = yy, x2 = w, y2 = yy + hside; - if (y1 < -0.00001) { + if (y1 < -1e-5) { // cut at the begin x1 = -y1 / hside * w; y1 = 0; @@ -11631,9 +12345,10 @@ class TAttFillHandler { lines += `M${Math.round(x1)},${Math.round(y1)}L${Math.round(x2)},${Math.round(y2)}`; yy -= dyreal; } - }, + }; - func = use_new ? produce_new : produce_old; + /* eslint-disable-next-line one-var */ + const func = use_new ? produce_new : produce_old; let horiz = false, vertical = false; @@ -11661,14 +12376,17 @@ class TAttFillHandler { case 9: vertical = true; break; } - if (horiz) func(0, false, 0); - if (vertical) func(0, true, 90); + if (horiz) + func(0, false, 0); + if (vertical) + func(0, true, 90); break; } } - if (!fills && !lines) return false; + if (!fills && !lines) + return false; } this.pattern_url = `url(#${id})`; @@ -11696,10 +12414,10 @@ class TAttFillHandler { } for (let n = 0; n < this.gradient.fColorPositions.length; ++n) { const pos = this.gradient.fColorPositions[n], - col = '#' + toHex(this.gradient.fColors[n*4]) + toHex(this.gradient.fColors[n*4+1]) + toHex(this.gradient.fColors[n*4+2]); - grad.append('svg:stop').attr('offset', `${Math.round(pos*100)}%`) + col = toColor(this.gradient.fColors[n * 4], this.gradient.fColors[n * 4 + 1], this.gradient.fColors[n * 4 + 2]); + grad.append('svg:stop').attr('offset', `${Math.round(pos * 100)}%`) .attr('stop-color', col) - .attr('stop-opacity', `${Math.round(this.gradient.fColors[n*4+3]*100)}%`); + .attr('stop-opacity', `${Math.round(this.gradient.fColors[n * 4 + 3] * 100)}%`); } } else { const patt = defs.append('svg:pattern') @@ -11708,11 +12426,15 @@ class TAttFillHandler { if (fills2) { const col = rgb(this.color); - col.r = Math.round((col.r + 255) / 2); col.g = Math.round((col.g + 255) / 2); col.b = Math.round((col.b + 255) / 2); + col.r = Math.round((col.r + 255) / 2); + col.g = Math.round((col.g + 255) / 2); + col.b = Math.round((col.b + 255) / 2); patt.append('svg:path').attr('d', fills2).style('fill', col); } - if (fills) patt.append('svg:path').attr('d', fills).style('fill', this.color); - if (lines) patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); + if (fills) + patt.append('svg:path').attr('d', fills).style('fill', this.color); + if (lines) + patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); } } @@ -11723,7 +12445,8 @@ class TAttFillHandler { * @private */ createSample(svg, width, height, plain) { // we need to create extra handle to change - if (plain) svg = select(svg); + if (plain) + svg = select(svg); const sample = new TAttFillHandler({ svg, pattern: this.pattern, color: this.color, color_as_svg: true }); @@ -11737,7 +12460,8 @@ class TAttFillHandler { saveToStyle(name_color, name_pattern) { if (name_color) { const indx = this.colorindx ?? findColor(this.color); - if (indx >= 0) gStyle[name_color] = indx; + if (indx >= 0) + gStyle[name_color] = indx; } if (name_pattern) gStyle[name_pattern] = this.pattern; @@ -11746,9 +12470,9 @@ class TAttFillHandler { } // class TAttFillHandler const root_line_styles = [ - '', '', '3,3', '1,2', - '3,4,1,4', '5,3,1,3', '5,3,1,3,1,3,1,3', '5,5', - '5,3,1,3,1,3', '20,5', '20,10,1,10', '1,3']; + '', '', '3, 3', '1, 2', + '3, 4, 1, 4', '5, 3, 1, 3', '5, 3, 1, 3, 1, 3, 1, 3', '5, 5', + '5, 3, 1, 3, 1, 3', '20, 5', '20, 10, 1, 10', '1, 3']; /** * @summary Handle for line attributes @@ -11762,7 +12486,8 @@ class TAttLineHandler { constructor(args) { this.func = this.apply.bind(this); this.used = true; - if (args._typename && (args.fLineStyle !== undefined)) args = { attr: args }; + if (args._typename && (args.fLineStyle !== undefined)) + args = { attr: args }; this.setArgs(args); } @@ -11776,10 +12501,11 @@ class TAttLineHandler { if (args.attr) { this.color_index = args.attr.fLineColor; args.color = args.color0 || (args.painter?.getColor(this.color_index) ?? getColor(this.color_index)); - if (args.width === undefined) args.width = args.attr.fLineWidth; - if (args.style === undefined) args.style = args.attr.fLineStyle; + args.width ??= args.attr.fLineWidth; + args.style ??= args.attr.fLineStyle; } else if (isStr(args.color)) { - if ((args.color !== 'none') && !args.width) args.width = 1; + if ((args.color !== 'none') && !args.width) + args.width = 1; } else if (typeof args.color === 'number') { this.color_index = args.color; args.color = args.painter?.getColor(args.color) ?? getColor(args.color); @@ -11788,7 +12514,8 @@ class TAttLineHandler { if (args.width === undefined) args.width = (args.color && args.color !== 'none') ? 1 : 0; - this.color = (args.width === 0) ? 'none' : args.color; + this.nocolor = args.nocolor; + this.color = (args.width === 0) || this.nocolor ? 'none' : args.color; this.width = args.width; this.style = args.style; this.pattern = args.pattern || root_line_styles[this.style] || null; @@ -11814,7 +12541,8 @@ class TAttLineHandler { this.excl_width = width; if (side !== undefined) { this.excl_side = side; - if ((this.excl_width === 0) && (this.excl_side !== 0)) this.excl_width = 20; + if ((this.excl_width === 0) && this.excl_side) + this.excl_width = 20; } this.changed = true; } @@ -11854,15 +12582,17 @@ class TAttLineHandler { applyBorder(selection) { this.used = true; if (this.empty()) { - selection.style('stroke', null) + selection.attr('rx', null) + .attr('ry', null) + .style('stroke', null) .style('stroke-width', null) - .style('stroke-dasharray', null) - .attr('rx', null).attr('ry', null); + .style('stroke-dasharray', null); } else { - selection.style('stroke', this.color) + selection.attr('rx', this.rx || null) + .attr('ry', this.ry || null) + .style('stroke', this.color) .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern) - .attr('rx', this.rx || null).attr('ry', this.ry || null); + .style('stroke-dasharray', this.pattern); } } @@ -11890,9 +12620,10 @@ class TAttLineHandler { /** @summary Create sample element inside primitive SVG - used in context menu */ createSample(svg, width, height, plain) { - if (plain) svg = select(svg); + if (plain) + svg = select(svg); svg.append('path') - .attr('d', `M0,${height/2}h${width}`) + .attr('d', `M0,${height / 2}h${width}`) .call(this.func); } @@ -11914,10 +12645,21 @@ class TAttLineHandler { /** @summary Get svg string for specified line style * @private */ function getSvgLineStyle(indx) { - if ((indx < 0) || (indx >= root_line_styles.length)) indx = 11; + if ((indx < 0) || (indx >= root_line_styles.length)) + indx = 11; return root_line_styles[indx]; } +function calcTextSize(sz, sz0, fact, pp) { + if (!sz) + sz = sz0 || 0; + + if (sz >= 1) + return Math.round(sz * (pp?.getPadScale() || 1)); + + return Math.round(sz * Math.min(pp?.getPadWidth() ?? 1000, pp?.getPadHeight() ?? 1000) * (fact || 1)); +} + /** * @summary Handle for text attributes * @private @@ -11929,7 +12671,8 @@ class TAttTextHandler { * @param {object} attr - attributes, see {@link TAttTextHandler#setArgs} */ constructor(args) { this.used = true; - if (args._typename && (args.fTextFont !== undefined)) args = { attr: args }; + if (args._typename && (args.fTextFont !== undefined)) + args = { attr: args }; this.setArgs(args); } @@ -11995,7 +12738,8 @@ class TAttTextHandler { /** @summary Create argument for drawText method */ createArg(arg) { - if (!arg) arg = {}; + if (!arg) + arg = {}; this.align_used = !arg.noalign && !arg.align; if (this.align_used) arg.align = this.align; @@ -12007,35 +12751,35 @@ class TAttTextHandler { } /** @summary Provides pixel size */ - getSize(w, h, fact, zero_size) { - if (this.size >= 1) - return Math.round(this.size); - if (!w) w = 1000; - if (!h) h = w; - if (!fact) fact = 1; - - return Math.round((this.size || zero_size || 0) * Math.min(w, h) * fact); - } + getSize(pp, fact, zero_size) { return calcTextSize(this.size, zero_size, fact, pp); } /** @summary Returns alternating size - which defined by sz1 variable */ - getAltSize(sz1, h) { - if (!sz1) sz1 = this.size; - return Math.round(sz1 >= 1 ? sz1 : sz1 * h); - } + getAltSize(sz1, pp) { return calcTextSize(sz1, this.size, 1, pp); } /** @summary Get font index - without precision */ - getGedFont() { return Math.floor(this.font/10); } + getGedFont() { return Math.floor(this.font / 10); } /** @summary Change text font from GED */ setGedFont(value) { const v = parseInt(value); if ((v > 0) && (v < 17)) - this.change(v*10 + (this.font % 10)); + this.change(v * 10 + (this.font % 10)); return this.font; } } // class TAttTextHandler +/** @summary returns true if pad painter @private */ +function isPadPainter(p) { + return isFunc(p?.getRootPad) && isFunc(p?.forEachPainterInPad); +} + +/** @summary returns canvas painter from DOM element @private */ +function getDomCanvasPainter(dom) { + const elem = dom?.select('.root_canvas'); + return !elem || elem.empty() ? null : elem.property('pad_painter'); +} + /** * @summary Painter class for ROOT objects * @@ -12043,39 +12787,67 @@ class TAttTextHandler { class ObjectPainter extends BasePainter { + #draw_object; // drawn object + #draw_g; // element for object drawing + #pad_painter_ref; // reference of pad painter + #main_painter; // WeakRef to main painter in the pad + #primary_ref; // reference of primary painter - if any + #snapid; // assigned online identifier + #is_primary; // if primary painter + #secondary_id; // id of this painter in relation to primary painter + #options; // current options object + #options_store; // stored draw options used to check changes + #user_tooltip_handler; // configured user tooltip handler + #user_tooltip_timeout; // timeout configured with tooltip handler + #user_toottip_handle; // timeout handle processing user tooltip + #user_context_menu; // function for user context menu + #special_draw_area; // current special draw area like projection + #root_colors; // custom colors list + #fillatt; // fill attribute + #lineatt; // line attribute + #markeratt; // marker attribute + #textatt; // text attribute + /** @summary constructor - * @param {object|string} dom - dom element or identifier + * @param {object|string} dom - dom element or identifier or pad painter * @param {object} obj - object to draw * @param {string} [opt] - object draw options */ constructor(dom, obj, opt) { - super(dom); - // this.draw_g = undefined; // container for all drawn objects - // this._main_painter = undefined; // main painter in the correspondent pad - this.pad_name = dom ? this.selectCurrentPad() : ''; // name of pad where object is drawn + const pp = isPadPainter(dom) ? dom : null; + + super(pp?.getDom() ?? dom); + + this.setPadPainter(pp); + + this.#draw_g = undefined; // container for all drawn objects this.assignObject(obj); if (isStr(opt)) - this.options = { original: opt }; + this.#options = { original: opt }; } /** @summary Assign object to the painter * @protected */ - assignObject(obj) { - if (isObject(obj)) - this.draw_object = obj; - else - delete this.draw_object; - } + assignObject(obj) { this.#draw_object = isObject(obj) ? obj : null; } - /** @summary Assigns pad name where element will be drawn - * @desc Should happend before first draw of element is performed, only for special use case - * @param {string} [pad_name] - on which subpad element should be draw, if not specified - use current - * @protected */ - setPadName(pad_name) { - this.pad_name = isStr(pad_name) ? pad_name : this.selectCurrentPad(); - } + /** @summary Returns drawn object */ + getObject() { return this.#draw_object; } + + /** @summary Assign new pad painter + * @protected */ + setPadPainter(pp) { this.#pad_painter_ref = pp ? new WeakRef(pp) : undefined; } - /** @summary Returns pad name where object is drawn */ - getPadName() { return this.pad_name || ''; } + /** @summary returns pad painter where object is drawn + * @protected */ + getPadPainter() { return this.#pad_painter_ref?.deref(); } + + /** @summary returns canvas painter + * @protected */ + getCanvPainter() { + let pp = this.getPadPainter(); + while (pp && !pp.isCanvas()) + pp = pp.getPadPainter(); + return pp; + } /** @summary Indicates that drawing runs in batch mode * @private */ @@ -12084,7 +12856,13 @@ class ObjectPainter extends BasePainter { /** @summary Assign snapid to the painter * @desc Identifier used to communicate with server side and identifies object on the server * @private */ - assignSnapId(id) { this.snapid = id; } + assignSnapId(id) { this.#snapid = id; } + + /** @summary Provides identifier on server for requested sub-element */ + getSnapId(subelem) { return !this.#snapid ? '' : (this.#snapid + (subelem ? '#' + subelem : '')); } + + /** @summary Returns true if snapid was assigned */ + hasSnapId() { return this.#snapid !== undefined; } /** @summary Generic method to cleanup painter. * @desc Remove object drawing and (in case of main painter) also main HTML components @@ -12096,24 +12874,24 @@ class ObjectPainter extends BasePainter { if (this.isMainPainter()) { const pp = this.getPadPainter(); - if (!pp || (pp.normal_canvas === false)) + if (!pp || pp.isCanvas('auto')) keep_origin = false; } // cleanup all existing references - delete this.pad_name; - delete this._main_painter; - this.draw_object = null; - delete this.snapid; + this.#pad_painter_ref = undefined; + this.#main_painter = null; + this.#draw_object = null; + this.#snapid = undefined; + this.#is_primary = undefined; + this.#primary_ref = undefined; + this.#secondary_id = undefined; // remove attributes objects (if any) - delete this.fillatt; - delete this.lineatt; - delete this.markeratt; - delete this.bins; - delete this.root_colors; - delete this.options; - delete this.options_store; + this.deleteAttr(); + this.#root_colors = undefined; + this.#options = undefined; + this.#options_store = undefined; // remove extra fields from v7 painters delete this.rstyle; @@ -12122,9 +12900,6 @@ class ObjectPainter extends BasePainter { super.cleanup(keep_origin); } - /** @summary Returns drawn object */ - getObject() { return this.draw_object; } - /** @summary Returns drawn object name */ getObjectName() { return this.getObject()?.fName ?? ''; } @@ -12136,59 +12911,101 @@ class ObjectPainter extends BasePainter { * @protected */ matchObjectType(arg) { const clname = this.getClassName(); - if (!arg || !clname) return false; - if (isStr(arg)) return arg === clname; - if (isStr(arg._typename)) return arg._typename === clname; - return clname.match(arg); + if (!arg || !clname) + return false; + if (isStr(arg)) + return arg === clname; + if (isStr(arg._typename)) + return arg._typename === clname; + return Boolean(clname.match(arg)); } /** @summary Change item name - * @desc When available, used for svg:title proprty + * @desc When available, used for svg:title property * @private */ setItemName(name, opt, hpainter) { super.setItemName(name, opt, hpainter); - if (this.no_default_title || !name) return; + if (this._no_default_title || !name) + return; const can = this.getCanvSvg(); - if (!can.empty()) can.select('title').text(name); - else this.selectDom().attr('title', name); + if (!can.empty()) + can.select('title').text(name); + else + this.selectDom().attr('title', name); const cp = this.getCanvPainter(); if (cp && ((cp === this) || (this.isMainPainter() && (cp === this.getPadPainter())))) cp.drawItemNameOnCanvas(name); } - /** @summary Store actual this.options together with original string + /** @summary Create options and copy new args + * @return options + * @private */ + setOptions(new_options, as_is) { + if (as_is) + this.#options = new_options; + else { + if (!this.#options) + this.#options = {}; + Object.assign(this.#options, new_options); + } + return this.#options; + } + + /** @summary Return actual options */ + getOptions(as_is) { + if (!as_is && !this.#options) + this.#options = {}; + return this.#options; + } + + /** @summary Emulate old options property */ + get options() { return this.getOptions(); } + + /** @summary Store actual options together with original string * @private */ storeDrawOpt(original) { - if (!this.options) return; - if (!original) original = ''; + if (!this.#options) + return; + if (!original) + original = ''; const pp = original.indexOf(';;'); - if (pp >= 0) original = original.slice(0, pp); - this.options.original = original; - this.options_store = Object.assign({}, this.options); + if (pp >= 0) + original = original.slice(0, pp); + this.#options.original = original; + this.#options_store = Object.assign({}, this.#options); } + /** @summary Return dom argument for object drawing + * @desc Can be used to draw other objects on same pad / same dom element + * @protected */ + getDrawDom() { return this.getPadPainter() || this.getDom(); } + /** @summary Return actual draw options as string * @param ignore_pad - do not include pad settings into histogram draw options * @desc if options are not modified - returns original string which was specified for object draw */ getDrawOpt(ignore_pad) { - if (!this.options) return ''; + if (!this.#options) + return ''; - if (isFunc(this.options.asString)) { + if (isFunc(this.#options.asString)) { let changed = false; const pp = this.getPadPainter(); - if (!this.options_store || pp?._interactively_changed) + if (!this.#options_store || pp?.options._interactively_changed) changed = true; else { - for (const k in this.options) { - if (this.options[k] !== this.options_store[k]) - changed = true; + for (const k in this.#options_store) { + if (this.#options[k] !== this.#options_store[k]) { + if ((k[0] !== '_') && (k[0] !== '$') && (k[0].toLowerCase() !== k[0])) + changed = true; } + } } - if (changed && isFunc(this.options.asString)) - return this.options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); + + if (changed && isFunc(this.#options.asString)) + return this.#options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); } - return this.options.original || ''; // nothing better, return original draw option + return this.#options.original || ''; // nothing better, return original draw option } /** @summary Returns array with supported draw options as configured in draw.mjs @@ -12200,7 +13017,7 @@ class ObjectPainter extends BasePainter { if (!cl || !isFunc(pp?.getObjectDrawSettings)) return []; - return pp.getObjectDrawSettings(prROOT + cl, 'nosame')?.opts; + return pp.getObjectDrawSettings(getKindForType(cl), 'nosame')?.opts; } /** @summary Central place to update objects drawing @@ -12213,13 +13030,17 @@ class ObjectPainter extends BasePainter { * only way to control how object can be update while requested from the server * @protected */ redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) return false; - const doc = getDocument(), - current = doc.body.style.cursor; - document.body.style.cursor = 'wait'; - const res = this.redrawPad(); - doc.body.style.cursor = current; - return res; + if (!this.updateObject(obj, opt)) + return false; + const doc = this.isBatchMode() ? null : getDocument(), + current = doc?.body.style.cursor; + if (doc) + doc.body.style.cursor = 'wait'; + return this.redrawPad().then(res => { + if (doc) + doc.body.style.cursor = current; + return res; + }); } /** @summary Generic method to update object content. @@ -12227,8 +13048,9 @@ class ObjectPainter extends BasePainter { * @param {object} obj - object with new data * @param {string} [opt] - option which will be used for redrawing * @protected */ - updateObject(obj /*, opt */) { - if (!this.matchObjectType(obj)) return false; + updateObject(obj /* , opt */) { + if (!this.matchObjectType(obj)) + return false; Object.assign(this.getObject(), obj); return true; } @@ -12236,7 +13058,7 @@ class ObjectPainter extends BasePainter { /** @summary Returns string with object hint * @desc It is either item name or object name or class name. * Such string typically used as object tooltip. - * If result string larger than 20 symbols, it will be cutted. */ + * If result string larger than 20 symbols, it will be shorten. */ getObjectHint() { const iname = this.getItemName(); if (iname) @@ -12244,29 +13066,36 @@ class ObjectPainter extends BasePainter { return this.getObjectName() || this.getClassName() || ''; } + /** @summary Set colors list + * @protected */ + setColors(lst) { this.#root_colors = lst; } + + /** @summary Return colors list + * @protected */ + getColors(force) { + if (!this.#root_colors && force) + this.setColors(this.getCanvPainter()?.getColors() || getRootColors()); + return this.#root_colors; + } + /** @summary returns color from current list of colors * @desc First checks canvas painter and then just access global list of colors * @param {number} indx - color index * @return {string} with SVG color name or rgb() * @protected */ - getColor(indx) { - if (!this.root_colors) - this.root_colors = this.getCanvPainter()?.root_colors || getRootColors(); - - return this.root_colors[indx]; - } + getColor(indx) { return this.getColors(true)[indx]; } /** @summary Add color to list of colors * @desc Returned color index can be used as color number in all other draw functions * @return {number} new color index * @protected */ addColor(color) { - if (!this.root_colors) - this.root_colors = this.getCanvPainter()?.root_colors || getRootColors(); - const indx = this.root_colors.indexOf(color); - if (indx >= 0) return indx; - this.root_colors.push(color); - return this.root_colors.length - 1; + const lst = this.getColors(true), + indx = lst.indexOf(color); + if (indx >= 0) + return indx; + lst.push(color); + return lst.length - 1; } /** @summary returns tooltip allowed flag @@ -12280,8 +13109,7 @@ class ObjectPainter extends BasePainter { /** @summary change tooltip allowed flag * @param {boolean|string} [on = true] set tooltip allowed state or 'toggle' * @private */ - setTooltipAllowed(on) { - if (on === undefined) on = true; + setTooltipAllowed(on = true) { const src = this.getCanvPainter() || this; src.tooltip_allowed = (on === 'toggle') ? !src.tooltip_allowed : on; } @@ -12297,44 +13125,66 @@ class ObjectPainter extends BasePainter { * @desc generic method to delete all graphical elements, associated with the painter * @protected */ removeG() { - this.draw_g?.remove(); - delete this.draw_g; + this.#draw_g?.remove(); + this.#draw_g = undefined; } /** @summary Returns created element used for object drawing * @desc Element should be created by {@link ObjectPainter#createG} * @protected */ - getG() { return this.draw_g; } + getG() { return this.#draw_g; } + + /** @summary introduce property for backward compatibility */ + get draw_g() { return this.#draw_g; } + + /** @summary Assign G element used for object drawing + * @protected */ + setG(g) { + this.#draw_g = g; + return g; + } + + /** @summary Append svg::path to G + * @protected */ + appendPath(d) { return this.#draw_g.append('svg:path').attr('d', d); } /** @summary (re)creates svg:g element for object drawings * @desc either one attach svg:g to pad primitives (default) * or svg:g element created in specified frame layer ('main_layer' will be used when true specified) * @param {boolean|string} [frame_layer] - when specified, element will be created inside frame layer, otherwise in the pad * @protected */ - createG(frame_layer) { + createG(frame_layer, use_a = false) { let layer; + const pp = this.getPadPainter(); + + if (frame_layer === 'frame2d') { + const fp = this.getFramePainter(); + frame_layer = fp && !fp.mode3d; + } + if (frame_layer) { - const frame = this.getFrameSvg(); + const frame = pp.getFrameSvg(); if (frame.empty()) { console.error('Not found frame to create g element inside'); return frame; } - if (!isStr(frame_layer)) frame_layer = 'main_layer'; + if (!isStr(frame_layer)) + frame_layer = 'main_layer'; layer = frame.selectChild('.' + frame_layer); } else - layer = this.getLayerSvg('primitives_layer'); + layer = pp.getLayerSvg('primitives_layer'); - if (this.draw_g && this.draw_g.node().parentNode !== layer.node()) { - console.log('g element changes its layer!!'); + if (this.#draw_g && this.#draw_g.node().parentNode !== layer.node()) { + console.log('g element changes its layer!'); this.removeG(); } - if (this.draw_g) { + if (this.#draw_g) { // clear all elements, keep g element on its place - this.draw_g.selectAll('*').remove(); + this.#draw_g.selectAll('*').remove(); } else { - this.draw_g = layer.append('svg:g'); + this.#draw_g = layer.append(use_a ? 'svg:a' : 'svg:g'); if (!frame_layer) layer.selectChildren('.most_upper_primitives').raise(); @@ -12343,129 +13193,75 @@ class ObjectPainter extends BasePainter { // set attributes for debugging, both should be there for opt out them later const clname = this.getClassName(), objname = this.getObjectName(); if (objname || clname) { - this.draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) - .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); + this.#draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) + .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); } - this.draw_g.property('in_frame', !!frame_layer); // indicates coordinate system + this.#draw_g.property('in_frame', Boolean(frame_layer)); // indicates coordinate system - return this.draw_g; + return this.#draw_g; } /** @summary Bring draw element to the front */ bringToFront(check_online) { - if (!this.draw_g) return; - const prnt = this.draw_g.node().parentNode; - prnt?.appendChild(this.draw_g.node()); - - if (!check_online || !this.snapid) return; - const pp = this.getPadPainter(); - if (!pp?.snapid) return; - - this.getCanvPainter()?.sendWebsocket('POPOBJ:'+JSON.stringify([pp.snapid.toString(), this.snapid.toString()])); - } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Pad svg element - * @param {string} [pad_name] - pad name to select, if not specified - pad where object is drawn - * @return {object} d3 selection with pad svg - * @protected */ - getPadSvg(pad_name) { - if (pad_name === undefined) - pad_name = this.pad_name; - - let c = this.getCanvSvg(); - if (!pad_name || c.empty()) return c; - - const cp = c.property('pad_painter'); - if (cp?.pads_cache && cp.pads_cache[pad_name]) - return select(cp.pads_cache[pad_name]); + if (!this.#draw_g) + return; + const prnt = this.#draw_g.node().parentNode; + prnt?.appendChild(this.#draw_g.node()); - c = c.select('.primitives_layer .__root_pad_' + pad_name); - if (cp) { - if (!cp.pads_cache) cp.pads_cache = {}; - cp.pads_cache[pad_name] = c.node(); + if (check_online && this.getSnapId()) { + const pp = this.getPadPainter(); + if (pp?.getSnapId()) + this.getCanvPainter()?.sendWebsocket('POPOBJ:' + JSON.stringify([pp.getSnapId(), this.getSnapId()])); } - return c; } - /** @summary Assign unique identifier for the painter + /** @summary Assign is_primary flag * @private */ - getUniqueId(only_read = false) { - if (!only_read && (this._unique_painter_id === undefined)) - this._unique_painter_id = internals.id_counter++; // assign unique identifier - return this._unique_painter_id; - } + setPrimary(flag = true) { this.#is_primary = flag; } + + /** @summary Return is_primary flag + * @private */ + isPrimary() { return this.#is_primary; } /** @summary Assign secondary id * @private */ - setSecondaryId(main, name) { - this._main_painter_id = main.getUniqueId(); - this._secondary_id = name; + setSecondaryId(primary, name) { + primary.setPrimary(true); // mark as primary, used later + this.#primary_ref = new WeakRef(primary); + this.#secondary_id = name; } + /** @summary Returns secondary id + * @private */ + getSecondaryId() { return this.#secondary_id; } + /** @summary Check if this is secondary painter - * @desc if main painter provided - check if this really main for this + * @desc if primary painter provided - check if this really main for this * @private */ - isSecondary(main) { - if (this._main_painter_id === undefined) + isSecondary(primary) { + if (!this.#primary_ref) return false; - return !isObject(main) ? true : this._main_painter_id === main.getUniqueId(true); + return !isObject(primary) ? true : this.#primary_ref.deref() === primary; } - /** @summary Provides identifier on server for requested sublement */ - getSnapId(subelem) { - if (!this.snapid) - return ''; - - return this.snapid.toString() + (subelem ? '#'+subelem : ''); - } + /** @summary Return primary object + * @private */ + getPrimary() { return this.#primary_ref?.deref(); } - /** @summary Method selects immediate layer under canvas/pad main element - * @param {string} name - layer name, exits 'primitives_layer', 'btns_layer', 'info_layer' - * @param {string} [pad_name] - pad name; current pad name used by default + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg * @protected */ - getLayerSvg(name, pad_name) { - let svg = this.getPadSvg(pad_name); - if (svg.empty()) return svg; - - if (name.indexOf('prim#') === 0) { - svg = svg.selectChild('.primitives_layer'); - name = name.slice(5); - } - - return svg.selectChild('.' + name); - } + getCanvSvg() { return this.selectDom().select('.root_canvas'); } /** @summary Method selects current pad name * @param {string} [new_name] - when specified, new current pad name will be configured * @return {string} previous selected pad or actual pad when new_name not specified - * @private */ - selectCurrentPad(new_name) { - const svg = this.getCanvSvg(); - if (svg.empty()) return ''; - const curr = svg.property('current_pad'); - if (new_name !== undefined) svg.property('current_pad', new_name); - return curr; - } - - /** @summary returns pad painter - * @param {string} [pad_name] pad name or use current pad by default - * @protected */ - getPadPainter(pad_name) { - const elem = this.getPadSvg(isStr(pad_name) ? pad_name : undefined); - return elem.empty() ? null : elem.property('pad_painter'); - } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter() { - const elem = this.getCanvSvg(); - return elem.empty() ? null : elem.property('pad_painter'); + * @private + * @deprecated to be removed in v8 */ + selectCurrentPad() { + console.warn('selectCurrentPad is deprecated, will be removed in v8'); + return ''; } /** @summary Return functor, which can convert x and y coordinates into pixels, used for drawing in the pad @@ -12477,22 +13273,22 @@ class ObjectPainter extends BasePainter { * @protected */ getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { const func = { isndc, nornd }, - use_frame = this.draw_g?.property('in_frame'); + use_frame = this.getG()?.property('in_frame'); if (use_frame || (use_frame_coordinates && !isndc)) - func.main = this.getFramePainter(); - if (func.main?.grx && func.main?.gry) { - func.x0 = (use_frame_coordinates && !isndc) ? func.main.getFrameX() : 0; - func.y0 = (use_frame_coordinates && !isndc) ? func.main.getFrameY() : 0; + func.fp = this.getFramePainter(); + if (func.fp?.grx && func.fp?.gry) { + func.x0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameX() : 0; + func.y0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameY() : 0; if (nornd) { - func.x = function(x) { return this.x0 + this.main.grx(x); }; - func.y = function(y) { return this.y0 + this.main.gry(y); }; + func.x = function(x) { return this.x0 + this.fp.grx(x); }; + func.y = function(y) { return this.y0 + this.fp.gry(y); }; } else { - func.x = function(x) { return this.x0 + Math.round(this.main.grx(x)); }; - func.y = function(y) { return this.y0 + Math.round(this.main.gry(y)); }; + func.x = function(x) { return this.x0 + Math.round(this.fp.grx(x)); }; + func.y = function(y) { return this.y0 + Math.round(this.fp.gry(y)); }; } } else if (!use_frame) { const pp = this.getPadPainter(); - if (!isndc) func.pad = pp?.getRootPad(true); // need for NDC conversion + func.pad = isndc ? null : pp?.getRootPad(true); // need for NDC conversion func.padw = pp?.getPadWidth() ?? 10; func.x = function(value) { if (this.pad) { @@ -12527,7 +13323,7 @@ class ObjectPainter extends BasePainter { * @param {number} value - axis value to convert. * @param {boolean} ndc - is value in NDC coordinates * @param {boolean} [noround] - skip rounding - * @return {number} value of requested coordiantes + * @return {number} value of requested coordinates * @protected */ axisToSvg(axis, value, ndc, noround) { const func = this.getAxisToSvgFunc(ndc, noround); @@ -12537,12 +13333,12 @@ class ObjectPainter extends BasePainter { /** @summary Converts pad SVG x or y coordinates into axis values. * @desc Reverse transformation for {@link ObjectPainter#axisToSvg} * @param {string} axis - name like 'x' or 'y' - * @param {number} coord - graphics coordiante. + * @param {number} coord - graphics coordinate. * @param {boolean} ndc - kind of return value - * @return {number} value of requested coordiantes + * @return {number} value of requested coordinates * @protected */ svgToAxis(axis, coord, ndc) { - const use_frame = this.draw_g?.property('in_frame'); + const use_frame = this.getG()?.property('in_frame'); if (use_frame) return this.getFramePainter()?.revertAxis(axis, coord) ?? 0; @@ -12554,30 +13350,18 @@ class ObjectPainter extends BasePainter { if (pad) { if (axis === 'y') { value = pad.fY1 + value * (pad.fY2 - pad.fY1); - if (pad.fLogy) value = Math.pow(10, value); + if (pad.fLogy) + value = Math.pow(10, value); } else { value = pad.fX1 + value * (pad.fX2 - pad.fX1); - if (pad.fLogx) value = Math.pow(10, value); + if (pad.fLogx) + value = Math.pow(10, value); } } return value; } - /** @summary Returns svg element for the frame in current pad - * @protected */ - getFrameSvg(pad_name) { - const layer = this.getLayerSvg('primitives_layer', pad_name); - if (layer.empty()) return layer; - let node = layer.node().firstChild; - while (node) { - const elem = select(node); - if (elem.classed('root_frame')) return elem; - node = node.nextSibling; - } - return select(null); - } - /** @summary Returns frame painter for current pad * @desc Pad has direct reference on frame if any * @protected */ @@ -12587,19 +13371,17 @@ class ObjectPainter extends BasePainter { /** @summary Returns painter for main object on the pad. * @desc Typically it is first histogram drawn on the pad and which draws frame axes - * But it also can be special usecase as TASImage or TGraphPolargram + * But it also can be special use-case as TASImage or TGraphPolargram * @param {boolean} [not_store] - if true, prevent temporary storage of main painter reference * @protected */ getMainPainter(not_store) { - let res = this._main_painter; + let res = this.#main_painter?.deref(); if (!res) { const pp = this.getPadPainter(); res = pp ? pp.getMainPainter() : this.getTopPainter(); - if (!res) res = null; - if (!not_store) - this._main_painter = res; + this.#main_painter = not_store || !res ? null : new WeakRef(res); } - return res; + return res || null; } /** @summary Returns true if this is main painter @@ -12619,34 +13401,28 @@ class ObjectPainter extends BasePainter { } /** @summary Add painter to pad list of painters - * @param {string} [pad_name] - optional pad name where painter should be add - * @desc Normally one should use {@link ensureTCanvas} to add painter to pad list of primitives + * @desc Normally called from {@link ensureTCanvas} function when new painter is created * @protected */ - addToPadPrimitives(pad_name) { - if (pad_name !== undefined) this.setPadName(pad_name); - const pp = this.getPadPainter(pad_name); // important - pad_name must be here, otherwise PadPainter class confuses itself - - if (!pp || (pp === this)) return false; - - if (pp.painters.indexOf(this) < 0) - pp.painters.push(this); + addToPadPrimitives(pad_painter) { + if (this.#pad_painter_ref) + pad_painter = this.#pad_painter_ref.deref(); + else { + if (!pad_painter) + pad_painter = getDomCanvasPainter(this.selectDom()); // try to detect in DOM + if (pad_painter) + this.#pad_painter_ref = new WeakRef(pad_painter); + } - if (!this.rstyle && pp.next_rstyle) - this.rstyle = pp.next_rstyle; + if (!pad_painter || (pad_painter === this)) + return null; - return true; + return pad_painter.addToPrimitives(this); } /** @summary Remove painter from pad list of painters + * @desc Can be used from external frameworks to add/remove painters * @protected */ - removeFromPadPrimitives() { - const pp = this.getPadPainter(); - if (!pp || (pp === this)) return false; - - const k = pp.painters.indexOf(this); - if (k >= 0) pp.painters.splice(k, 1); - return true; - } + removeFromPadPrimitives() { this.getPadPainter()?.removePrimitive(this); } /** @summary Creates marker attributes object * @desc Can be used to produce markers in painter. @@ -12656,22 +13432,27 @@ class ObjectPainter extends BasePainter { * @return {object} created handler * @protected */ createAttMarker(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fMarkerColor !== undefined && args.fMarkerStyle !== undefined && args.fMarkerSize !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.markeratt : null; + let handler = args.std ? this.#markeratt : null; if (!handler) handler = new TAttMarkerHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.markeratt = handler; + if (args.std) + this.#markeratt = handler; return handler; } @@ -12682,22 +13463,27 @@ class ObjectPainter extends BasePainter { * @param {object} args - either TAttLine or see constructor arguments of {@link TAttLineHandler} * @protected */ createAttLine(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fLineColor !== undefined && args.fLineStyle !== undefined && args.fLineWidth !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.lineatt : null; + let handler = args.std ? this.#lineatt : null; if (!handler) handler = new TAttLineHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.lineatt = handler; + if (args.std) + this.#lineatt = handler; return handler; } @@ -12705,22 +13491,27 @@ class ObjectPainter extends BasePainter { * @param {object} args - either TAttText or see constructor arguments of {@link TAttTextHandler} * @protected */ createAttText(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fTextFont !== undefined && args.fTextSize !== undefined && args.fTextColor !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.textatt : null; + let handler = args.std ? this.#textatt : null; if (!handler) handler = new TAttTextHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.textatt = handler; + if (args.std) + this.#textatt = handler; return handler; } @@ -12729,7 +13520,7 @@ class ObjectPainter extends BasePainter { * otherwise newly created patters will not be usable in the canvas * See {@link TAttFillHandler} for more info. * Instance assigned as this.fillatt data member, recognized by GED editors - * @param {object} args - for special cases one can specify TAttFill as args or number of parameters + * @param {object} [args] - for special cases one can specify TAttFill as args or number of parameters * @param {boolean} [args.std = true] - this is standard fill attribute for object and should be used as this.fillatt * @param {object} [args.attr = null] - object, derived from TAttFill * @param {number} [args.pattern = undefined] - integer index of fill pattern @@ -12739,39 +13530,53 @@ class ObjectPainter extends BasePainter { * @return created handle * @protected */ createAttFill(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args._typename && args.fFillColor !== undefined && args.fFillStyle !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.fillatt : null; + let handler = args.std ? this.#fillatt : null; - if (!args.svg) args.svg = this.getCanvSvg(); - if (args.painter === undefined) args.painter = this; + if (!args.svg) + args.svg = this.getCanvSvg(); if (!handler) handler = new TAttFillHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.fillatt = handler; + if (args.std) + this.#fillatt = handler; return handler; } + get fillatt() { return this.#fillatt; } + get lineatt() { return this.#lineatt; } + get markeratt() { return this.#markeratt; } + get textatt() { return this.#textatt; } + /** @summary call function for each painter in the pad * @desc Iterate over all known painters * @private */ forEachPainter(userfunc, kind) { // iterate over all painters from pad list - const pp = this.getPadPainter(); + let pp = this.getPadPainter(), top = null; + if (!pp) { + top = this.getTopPainter(); + if (isPadPainter(top)) + pp = top; + } if (pp) pp.forEachPainterInPad(userfunc, kind); - else { - const painter = this.getTopPainter(); - if (painter && (kind !== 'pads')) userfunc(painter); - } + else if (top && (kind !== 'pads')) + userfunc(top); } /** @summary indicate that redraw was invoked via interactive action (like context menu or zooming) @@ -12780,7 +13585,7 @@ class ObjectPainter extends BasePainter { * @private */ async interactiveRedraw(arg, info, subelem) { let reason, res; - if (isStr(info) && (info.indexOf('exec:') !== 0)) + if (isStr(info) && info.indexOf('exec:')) reason = info; if (arg === 'pad') @@ -12789,13 +13594,16 @@ class ObjectPainter extends BasePainter { res = this.redraw(reason); return getPromise(res).then(() => { + if (arg === 'attribute') + return this.getPadPainter()?.redrawLegend(); + }).then(() => { // inform GED that something changes const canp = this.getCanvPainter(); if (isFunc(canp?.producePadEvent)) canp.producePadEvent('redraw', this.getPadPainter(), this, null, subelem); - // inform server that drawopt changes + // inform server that draw options changes if (isFunc(canp?.processChanges)) canp.processChanges(info, this, subelem); @@ -12815,20 +13623,21 @@ class ObjectPainter extends BasePainter { * @private */ executeMenuCommand(method) { if (method.fName === 'Inspect') - // primitve inspector, keep it here + // primitive inspector, keep it here return this.showInspector(); return false; } /** @summary Invoke method for object via WebCanvas functionality - * @desc Requires that painter marked with object identifier (this.snapid) or identifier provided as second argument + * @desc Requires that painter marked with object identifier (this.#snapid) or identifier provided as second argument * Canvas painter should exists and in non-readonly mode * Execution string can look like 'Print()'. * Many methods call can be chained with 'Print();;Update();;Clear()' * @private */ submitCanvExec(exec, snapid) { - if (!exec || !isStr(exec)) return; + if (!exec || !isStr(exec)) + return; const canp = this.getCanvPainter(); if (isFunc(canp?.submitExec)) @@ -12837,10 +13646,15 @@ class ObjectPainter extends BasePainter { /** @summary remove all created draw attributes * @protected */ - deleteAttr() { - delete this.lineatt; - delete this.fillatt; - delete this.markeratt; + deleteAttr(name) { + if (!name || name === 'line') + this.#lineatt = undefined; + if (!name || name === 'fill') + this.#fillatt = undefined; + if (!name || name === 'marker') + this.#markeratt = undefined; + if (!name || name === 'text') + this.#textatt = undefined; } /** @summary Show object in inspector for provided object @@ -12852,13 +13666,14 @@ class ObjectPainter extends BasePainter { /** @summary Fill context menu for the object * @private */ fillContextMenu(menu) { - const name = this.getObjectName(); - let cl = this.getClassName(); - const p = cl.lastIndexOf('::'); - if (p > 0) cl = cl.slice(p+2); - const title = (cl && name) ? `${cl}:${name}` : (cl || name || 'object'); + const cl = this.getClassName(), + name = this.getObjectName(), + p = cl.lastIndexOf('::'), + cl0 = (p > 0) ? cl.slice(p + 2) : cl, + hdr = (cl0 && name) ? `${cl0}:${name}` : (cl0 || name || 'object'), + url = cl ? `${urlClassPrefix}${cl.replaceAll('::', '_1_1')}.html` : ''; - menu.add(`header:${title}`); + menu.header(hdr, url); const size0 = menu.size(); @@ -12874,21 +13689,25 @@ class ObjectPainter extends BasePainter { } /** @summary shows objects status - * @desc Either used canvas painter method or globaly assigned + * @desc Either used canvas painter method or globally assigned * When no parameters are specified, just basic object properties are shown * @private */ showObjectStatus(name, title, info, info2) { let cp = this.getCanvPainter(); - if (cp && !isFunc(cp.showCanvasStatus)) cp = null; + if (!isFunc(cp?.showCanvasStatus)) + cp = null; - if (!cp && !isFunc(internals.showStatus)) return false; + if (!cp && !isFunc(internals.showStatus)) + return false; - if (this.enlargeMain('state') === 'on') return false; + if (this.enlargeMain('state') === 'on') + return false; if ((name === undefined) && (title === undefined)) { const obj = this.getObject(); - if (!obj) return; + if (!obj) + return; name = this.getItemName() || obj.fName; title = obj.fTitle || obj._typename; info = obj._typename; @@ -12911,14 +13730,18 @@ class ObjectPainter extends BasePainter { * @desc required before any text can be drawn * @param {number} font_face - font id as used in ROOT font attributes * @param {number} font_size - font size as used in ROOT font attributes - * @param {object} [draw_g] - element where text drawm, by default using main object element + * @param {object} [draw_g] - element where text drawn, by default using main object element * @param {number} [max_font_size] - maximal font size, used when text can be scaled * @protected */ - startTextDrawing(font_face, font_size, draw_g, max_font_size) { - if (!draw_g) draw_g = this.draw_g; - if (!draw_g || draw_g.empty()) return; + startTextDrawing(font_face, font_size, draw_g, max_font_size, can_async) { + if (!draw_g) + draw_g = this.getG(); + if (!draw_g || draw_g.empty()) + return false; const font = (font_size === 'font') ? font_face : new FontHandler(font_face, font_size); + if (can_async && font.needLoad()) + return font; font.setPainter(this); // may be required when custom font is used @@ -12930,10 +13753,27 @@ class ObjectPainter extends BasePainter { .property('text_factor', 0) .property('max_text_width', 0) // keep maximal text width, use it later .property('max_font_size', max_font_size) - .property('_fast_drawing', this.getPadPainter()?._fast_drawing ?? false); + .property('_fast_drawing', this.getPadPainter()?.isFastDrawing() ?? false); if (draw_g.property('_fast_drawing')) draw_g.property('_font_too_small', (max_font_size && (max_font_size < 5)) || (font.size < 4)); + + return true; + } + + /** @summary Start async text drawing + * @return {Promise} for loading of font if necessary + * @private */ + async startTextDrawingAsync(font_face, font_size, draw_g, max_font_size) { + const font = this.startTextDrawing(font_face, font_size, draw_g, max_font_size, true); + if ((font === true) || (font === false)) + return font; + return font.load().then(res => { + if (!res) + return false; + + return this.startTextDrawing(font, 'font', draw_g, max_font_size); + }); } /** @summary Apply scaling factor to all drawn text in the element @@ -12942,22 +13782,24 @@ class ObjectPainter extends BasePainter { * @param {object} [draw_g] - drawing element for the text * @protected */ scaleTextDrawing(factor, draw_g) { - if (!draw_g) draw_g = this.draw_g; - if (!draw_g || draw_g.empty()) return; + if (!draw_g) + draw_g = this.getG(); + if (!draw_g || draw_g.empty()) + return; if (factor && (factor > draw_g.property('text_factor'))) draw_g.property('text_factor', factor); } /** @summary Analyze if all text draw operations are completed * @private */ - _checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { - let all_args = draw_g.property('all_args'), missing = 0; - if (!all_args) { - console.log('Text drawing is finished - why calling _checkAllTextDrawing?????'); - all_args = []; - } + #checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { + const all_args = draw_g.property('all_args') || []; + let missing = 0; - all_args.forEach(arg => { if (!arg.ready) missing++; }); + all_args.forEach(arg => { + if (!arg.ready) + missing++; + }); if (missing > 0) { if (isFunc(resolveFunc)) { @@ -12975,7 +13817,7 @@ class ObjectPainter extends BasePainter { max_sz = draw_g.property('max_font_size'); let font_size = font.size, any_text = false, only_text = true; - if ((f > 0) && ((f < 0.9) || (f > 1))) + if ((f > 0) && ((f < 0.95) || (f > 1.05))) font.size = Math.max(1, Math.floor(font.size / f)); if (max_sz && (font.size > max_sz)) @@ -12987,9 +13829,9 @@ class ObjectPainter extends BasePainter { } all_args.forEach(arg => { - if (arg.mj_node && arg.applyAttributesToMathJax) { + if (arg.mj_node && arg.mj_func) { const svg = arg.mj_node.select('svg'); // MathJax svg - arg.applyAttributesToMathJax(this, arg.mj_node, svg, arg, font_size, f); + arg.mj_func(this, arg.mj_node, svg, arg, font_size, f); delete arg.mj_node; // remove reference only_text = false; } else if (arg.txt_g) @@ -13012,7 +13854,8 @@ class ObjectPainter extends BasePainter { txt = arg.txt_node; delete arg.txt_node; is_txt = true; - if (optimize_arr !== null) optimize_arr.push(txt); + if (optimize_arr !== null) + optimize_arr.push(txt); } else if (arg.txt_g) { txt = arg.txt_g; delete arg.txt_g; @@ -13028,8 +13871,8 @@ class ObjectPainter extends BasePainter { // adjust x position when scale into specified rectangle if (arg.align[0] === 'middle') arg.x += arg.width / 2; - else if (arg.align[0] === 'end') - arg.x += arg.width; + else if (arg.align[0] === 'end') + arg.x += arg.width; } if (arg.height) { @@ -13045,7 +13888,10 @@ class ObjectPainter extends BasePainter { // handle simple text drawing if (isNodeJs()) { - if (arg.scale && (f > 0)) { arg.box.width *= 1/f; arg.box.height *= 1/f; } + if (arg.scale && (f > 0)) { + arg.box.width *= 1 / f; + arg.box.height *= 1 / f; + } } else if (!arg.plain && !arg.fast) { // exact box dimension only required when complex text was build arg.box = getElementRect(txt, 'bbox'); @@ -13056,8 +13902,8 @@ class ObjectPainter extends BasePainter { if (arg.align[1] === 'top') txt.attr('dy', '.8em'); else if (arg.align[1] === 'middle') { - if (isNodeJs()) txt.attr('dy', '.4em'); - else txt.attr('dominant-baseline', 'middle'); + // if (isNodeJs()) txt.attr('dy', '.4em'); else // old workaround for node.js + txt.attr('dominant-baseline', 'middle'); } } else { txt.attr('text-anchor', 'start'); @@ -13065,28 +13911,36 @@ class ObjectPainter extends BasePainter { dy = ((arg.align[1] === 'top') ? (arg.top_shift || 1) : (arg.align[1] === 'middle') ? (arg.mid_shift || 0.5) : 0) * arg.box.height; } } else if (arg.text_rect) { - // handle latext drawing + // handle latex drawing const box = arg.text_rect; - scale = (f > 0) && (Math.abs(1-f) > 0.01) ? 1/f : 1; + scale = (f > 0) && (Math.abs(1 - f) > 0.01) ? 1 / f : 1; dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * box.width * scale; if (arg.align[1] === 'top') - dy = -box.y1*scale; + dy = -box.y1 * scale; else if (arg.align[1] === 'bottom') - dy = -box.y2*scale; + dy = -box.y2 * scale; else if (arg.align[1] === 'middle') - dy = -0.5*(box.y1 + box.y2)*scale; + dy = -0.5 * (box.y1 + box.y2) * scale; } else console.error('text rect not calcualted - please check code'); - if (!arg.rotate) { arg.x += dx; arg.y += dy; dx = dy = 0; } + if (!arg.rotate) { + arg.x += dx; + arg.y += dy; + dx = dy = 0; + } // use translate and then rotate to avoid complex sign calculations let trans = makeTranslate(Math.round(arg.x), Math.round(arg.y)) || ''; const dtrans = makeTranslate(Math.round(dx), Math.round(dy)), - append = arg => { if (trans) trans += ' '; trans += arg; }; + append = aaa => { + if (trans) + trans += ' '; + trans += aaa; + }; if (arg.rotate) append(`rotate(${Math.round(arg.rotate)})`); @@ -13094,7 +13948,8 @@ class ObjectPainter extends BasePainter { append(`scale(${scale.toFixed(3)})`); if (dtrans) append(dtrans); - if (trans) txt.attr('transform', trans); + if (trans) + txt.attr('transform', trans); }); @@ -13107,7 +13962,8 @@ class ObjectPainter extends BasePainter { let first = optimize_arr[0].attr(name); optimize_arr.forEach(txt_node => { const value = txt_node.attr(name); - if (!value || (value !== first)) first = undefined; + if (!value || (value !== first)) + first = undefined; }); if (first) { draw_g.attr(name, first); @@ -13117,16 +13973,17 @@ class ObjectPainter extends BasePainter { } // if specified, call resolve function - if (resolveFunc) resolveFunc(this); // IMPORTANT - return painter, may use in draw methods + if (resolveFunc) + resolveFunc(this); // IMPORTANT - return painter, may use in draw methods } /** @summary Post-process plain text drawing * @private */ - _postprocessDrawText(arg, txt_node) { - // complete rectangle with very rougth size estimations + #postprocessDrawText(arg, txt_node) { + // complete rectangle with very rough size estimations arg.box = !isNodeJs() && !settings.ApproxTextSize && !arg.fast ? getElementRect(txt_node, 'bbox') - : (arg.text_rect || { height: arg.font_size * 1.2, width: arg.text.length * arg.font_size * arg.font.aver_width }); + : (arg.text_rect || { height: Math.round(1.15 * arg.font_size), width: approximateLabelWidth(arg.text, arg.font, arg.font_size) }); txt_node.attr('visibility', 'hidden'); // hide elements until text drawing is finished @@ -13157,7 +14014,7 @@ class ObjectPainter extends BasePainter { * @param {boolean} [arg.scale = true] - scale into draw box when width and height parameters are specified * @param {number} [arg.latex] - 0 - plain text, 1 - normal TLatex, 2 - math * @param {string} [arg.color=black] - text color - * @param {number} [arg.rotate] - rotaion angle + * @param {number} [arg.rotate] - rotation angle * @param {number} [arg.font_size] - fixed font size * @param {object} [arg.draw_g] - element where to place text, if not specified central draw_g container is used * @param {function} [arg.post_process] - optional function called when specified text is drawn @@ -13166,23 +14023,25 @@ class ObjectPainter extends BasePainter { if (!arg.text) arg.text = ''; - arg.draw_g = arg.draw_g || this.draw_g; - if (!arg.draw_g || arg.draw_g.empty()) return; + arg.draw_g = arg.draw_g || this.getG(); + if (!arg.draw_g || arg.draw_g.empty()) + return; const font = arg.draw_g.property('text_font'); arg.font = font; // use in latex conversion if (font) { - if (font.color && !arg.color) arg.color = font.color; - if (font.align && !arg.align) arg.align = font.align; - if (font.angle && !arg.rotate) arg.rotate = font.angle; + arg.color = arg.color || font.color; + arg.align = arg.align || font.align; + arg.rotate = arg.rotate || font.angle; } let align = ['start', 'middle']; if (isStr(arg.align)) { align = arg.align.split(';'); - if (align.length === 1) align.push('middle'); + if (align.length === 1) + align.push('middle'); } else if (typeof arg.align === 'number') { if ((arg.align / 10) >= 3) align[0] = 'end'; @@ -13197,7 +14056,8 @@ class ObjectPainter extends BasePainter { } else if (isObject(arg.align) && (arg.align.length === 2)) align = arg.align; - if (arg.latex === undefined) arg.latex = 1; // latex 0-text, 1-latex, 2-math + if (arg.latex === undefined) + arg.latex = 1; // 0: text, 1: latex, 2: math arg.align = align; arg.x = arg.x || 0; arg.y = arg.y || 0; @@ -13209,10 +14069,12 @@ class ObjectPainter extends BasePainter { if (arg.draw_g.property('_fast_drawing')) { if (arg.scale) { // area too small - ignore such drawing - if (arg.height < 4) return 0; + if (arg.height < 4) + return 0; } else if (arg.font_size) { // font size too small - if (arg.font_size < 4) return 0; + if (arg.font_size < 4) + return 0; } else if (arg.draw_g.property('_font_too_small')) { // configure font is too small - ignore drawing return 0; @@ -13235,30 +14097,33 @@ class ObjectPainter extends BasePainter { if (!use_mathjax || arg.nomathjax) { arg.txt_node = arg.draw_g.append('svg:text'); - if (arg.color) arg.txt_node.attr('fill', arg.color); + if (arg.color) + arg.txt_node.attr('fill', arg.color); - if (arg.font_size) arg.txt_node.attr('font-size', arg.font_size); - else arg.font_size = font.size; + if (arg.font_size) + arg.txt_node.attr('font-size', arg.font_size); + else + arg.font_size = font.size; arg.plain = !arg.latex || (settings.Latex === cl.Off) || (settings.Latex === cl.Symbols); arg.simple_latex = arg.latex && (settings.Latex === cl.Symbols); - if (!arg.plain || arg.simple_latex || (arg.font && arg.font.isSymbol)) { + if (!arg.plain || arg.simple_latex || arg.font?.isSymbol) { if (arg.simple_latex || isPlainText(arg.text) || arg.plain) { arg.simple_latex = true; producePlainText(this, arg.txt_node, arg); } else { - arg.txt_node.remove(); // just remove text node, + arg.txt_node.remove(); // just remove text node delete arg.txt_node; arg.txt_g = arg.draw_g.append('svg:g'); produceLatex(this, arg.txt_g, arg); } arg.ready = true; - this._postprocessDrawText(arg, arg.txt_g || arg.txt_node); + this.#postprocessDrawText(arg, arg.txt_g || arg.txt_node); if (arg.draw_g.property('draw_text_completed')) - this._checkAllTextDrawing(arg.draw_g); // check if all other elements are completed + this.#checkAllTextDrawing(arg.draw_g); // check if all other elements are completed return 0; } @@ -13266,7 +14131,7 @@ class ObjectPainter extends BasePainter { arg.txt_node.text(arg.text); arg.ready = true; - return this._postprocessDrawText(arg, arg.txt_node); + return this.#postprocessDrawText(arg, arg.txt_node); } arg.mj_node = arg.draw_g.append('svg:g').attr('visibility', 'hidden'); // hide text until drawing is finished @@ -13274,7 +14139,7 @@ class ObjectPainter extends BasePainter { produceMathjax(this, arg.mj_node, arg).then(() => { arg.ready = true; if (arg.draw_g.property('draw_text_completed')) - this._checkAllTextDrawing(arg.draw_g); + this.#checkAllTextDrawing(arg.draw_g); }); return 0; @@ -13282,51 +14147,50 @@ class ObjectPainter extends BasePainter { /** @summary Finish text drawing * @desc Should be called to complete all text drawing operations - * @param {function} [draw_g] - element for text drawing, this.draw_g used when not specified + * @param {function} [draw_g] - element for text drawing, default is getG() * @return {Promise} when text drawing completed * @protected */ async finishTextDrawing(draw_g, try_optimize) { - if (!draw_g) draw_g = this.draw_g; + if (!draw_g) + draw_g = this.getG(); if (!draw_g || draw_g.empty()) return false; draw_g.property('draw_text_completed', true); // mark that text drawing is completed return new Promise(resolveFunc => { - this._checkAllTextDrawing(draw_g, resolveFunc, try_optimize); + this.#checkAllTextDrawing(draw_g, resolveFunc, try_optimize); }); } /** @summary Configure user-defined context menu for the object - * @desc fillmenu_func will be called when context menu is actiavted + * @desc fillmenu_func will be called when context menu is activated * Arguments fillmenu_func are (menu,kind) - * First is menu object, second is object subelement like axis 'x' or 'y' + * First is menu object, second is object sub-element like axis 'x' or 'y' * Function should return promise with menu when items are filled - * @param {function} fillmenu_func - function to fill custom context menu for oabject */ + * @param {function} fillmenu_func - function to fill custom context menu for object */ configureUserContextMenu(fillmenu_func) { - if (!fillmenu_func || !isFunc(fillmenu_func)) - delete this._userContextMenuFunc; - else - this._userContextMenuFunc = fillmenu_func; + this.#user_context_menu = isFunc(fillmenu_func) ? fillmenu_func : undefined; } /** @summary Fill object menu in web canvas * @private */ async fillObjectExecMenu(menu, kind) { - if (isFunc(this._userContextMenuFunc)) - return this._userContextMenuFunc(menu, kind); + if (isFunc(this.#user_context_menu)) + return this.#user_context_menu(menu, kind); const canvp = this.getCanvPainter(); - if (!this.snapid || !canvp || canvp?._readonly || !canvp?._websocket) + if (!this.getSnapId() || !canvp || canvp?.isReadonly() || !canvp?.getWebsocket()) return menu; - function DoExecMenu(arg) { + function doExecMenu(arg) { const execp = menu.exec_painter || this, cp = execp.getCanvPainter(), item = menu.exec_items[parseInt(arg)]; - if (!item?.fName) return; + if (!item?.fName) + return; // this is special entry, produced by TWebMenuItem, which recognizes editor entries itself if (item.fExec === 'Show:Editor') { @@ -13335,29 +14199,29 @@ class ObjectPainter extends BasePainter { return; } - if (isFunc(cp?.executeObjectMethod)) - if (cp.executeObjectMethod(execp, item, item.$execid)) return; + if (isFunc(cp?.executeObjectMethod) && cp.executeObjectMethod(execp, item, item.$execid)) + return; item.fClassName = execp.getClassName(); if ((item.$execid.indexOf('#x') > 0) || (item.$execid.indexOf('#y') > 0) || (item.$execid.indexOf('#z') > 0)) item.fClassName = clTAxis; - if (execp.executeMenuCommand(item)) return; + if (execp.executeMenuCommand(item)) + return; - if (!item.$execid) return; + if (!item.$execid) + return; if (!item.fArgs) { - if (cp?.v7canvas) - return cp.submitExec(execp, item.fExec, kind); - else - return execp.submitCanvExec(item.fExec, item.$execid); + return cp?.v7canvas ? cp.submitExec(execp, item.fExec, kind) + : execp.submitCanvExec(item.fExec, item.$execid); } menu.showMethodArgsDialog(item).then(args => { - if (!args) return; - if (execp.executeMenuCommand(item, args)) return; + if (!args || execp.executeMenuCommand(item, args)) + return; - const exec = item.fExec.slice(0, item.fExec.length-1) + args + ')'; + const exec = item.fExec.slice(0, item.fExec.length - 1) + args + ')'; if (cp?.v7canvas) cp.submitExec(execp, exec, kind); else @@ -13365,9 +14229,10 @@ class ObjectPainter extends BasePainter { }); } - const DoFillMenu = (_menu, _reqid, _resolveFunc, reply) => { + const doFillMenu = (_menu, _reqid, _resolveFunc, reply) => { // avoid multiple call of the callback after timeout - if (menu._got_menu) return; + if (menu._got_menu) + return; menu._got_menu = true; if (reply && (_reqid !== reply.fId)) @@ -13377,7 +14242,7 @@ class ObjectPainter extends BasePainter { if (menu.exec_items?.length) { if (_menu.size() > 0) - _menu.add('separator'); + _menu.separator(); let lastclname; @@ -13387,29 +14252,30 @@ class ObjectPainter extends BasePainter { item.$menu = menu; if (item.fClassName && lastclname && (lastclname !== item.fClassName)) { - _menu.add('endsub:'); + _menu.endsub(); lastclname = ''; } if (lastclname !== item.fClassName) { lastclname = item.fClassName; const p = lastclname.lastIndexOf('::'), - shortname = (p > 0) ? lastclname.slice(p+2) : lastclname; + shortname = (p > 0) ? lastclname.slice(p + 2) : lastclname; - _menu.add('sub:' + shortname.replace(/[<>]/g, '_')); + _menu.sub(shortname.replace(/[<>]/g, '_')); } if ((item.fChecked === undefined) || (item.fChecked < 0)) - _menu.add(item.fName, n, DoExecMenu); + _menu.add(item.fName, n, doExecMenu); else - _menu.addchk(item.fChecked, item.fName, n, DoExecMenu); + _menu.addchk(item.fChecked, item.fName, n, doExecMenu); } - if (lastclname) _menu.add('endsub:'); + if (lastclname) + _menu.endsub(); } _resolveFunc(_menu); }, - reqid = this.getSnapId(kind); + reqid = this.getSnapId(kind); menu._got_menu = false; @@ -13421,36 +14287,37 @@ class ObjectPainter extends BasePainter { let did_resolve = false; function handleResolve(res) { - if (did_resolve) return; + if (did_resolve) + return; did_resolve = true; resolveFunc(res); } // set timeout to avoid menu hanging - setTimeout(() => DoFillMenu(menu, reqid, handleResolve), 2000); + setTimeout(() => doFillMenu(menu, reqid, handleResolve), 2000); - canvp.submitMenuRequest(this, kind, reqid).then(lst => DoFillMenu(menu, reqid, handleResolve, lst)); + canvp.submitMenuRequest(this, kind, reqid).then(lst => doFillMenu(menu, reqid, handleResolve, lst)); }); } /** @summary Configure user-defined tooltip handler * @desc Hook for the users to get tooltip information when mouse cursor moves over frame area - * Hanlder function will be called every time when new data is selected + * Handler function will be called every time when new data is selected * when mouse leave frame area, handler(null) will be called * @param {function} handler - function called when tooltip is produced * @param {number} [tmout = 100] - delay in ms before tooltip delivered */ - configureUserTooltipHandler(handler, tmout) { + configureUserTooltipHandler(handler, tmout = 100) { if (!handler || !isFunc(handler)) { - delete this._user_tooltip_handler; - delete this._user_tooltip_timeout; + this.#user_tooltip_handler = undefined; + this.#user_tooltip_timeout = undefined; } else { - this._user_tooltip_handler = handler; - this._user_tooltip_timeout = tmout || 100; + this.#user_tooltip_handler = handler; + this.#user_tooltip_timeout = tmout; } } - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed + /** @summary Configure user-defined click handler + * @desc Function will be called every time when frame click was performed * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of click will be disabled * @param {function} handler - function called when mouse click is done */ @@ -13474,42 +14341,44 @@ class ObjectPainter extends BasePainter { /** @summary Check if user-defined tooltip function was configured * @return {boolean} flag is user tooltip handler was configured */ hasUserTooltip() { - return isFunc(this._user_tooltip_handler); + return isFunc(this.#user_tooltip_handler); } /** @summary Provide tooltips data to user-defined function * @param {object} data - tooltip data * @private */ provideUserTooltip(data) { - if (!this.hasUserTooltip()) return; + if (!this.hasUserTooltip()) + return; - if (this._user_tooltip_timeout <= 0) - return this._user_tooltip_handler(data); + if (this.#user_tooltip_timeout <= 0) + return this.#user_tooltip_handler(data); - if (this._user_tooltip_handle) { - clearTimeout(this._user_tooltip_handle); - delete this._user_tooltip_handle; + if (this.#user_toottip_handle) { + clearTimeout(this.#user_toottip_handle); + this.#user_toottip_handle = undefined; } if (!data) - return this._user_tooltip_handler(data); + return this.#user_tooltip_handler(data); // only after timeout user function will be called - this._user_tooltip_handle = setTimeout(() => { - delete this._user_tooltip_handle; - if (this._user_tooltip_handler) this._user_tooltip_handler(data); - }, this._user_tooltip_timeout); + this.#user_toottip_handle = setTimeout(() => { + this.#user_toottip_handle = undefined; + if (this.#user_tooltip_handler) + this.#user_tooltip_handler(data); + }, this.#user_tooltip_timeout); } /** @summary Provide projection areas * @param kind - 'X', 'Y', 'XY' or '' * @private */ async provideSpecialDrawArea(kind) { - if (kind === this._special_draw_area) + if (kind === this.#special_draw_area) return true; return this.getCanvPainter().toggleProjection(kind).then(() => { - this._special_draw_area = kind; + this.#special_draw_area = kind; return true; }); } @@ -13521,22 +14390,25 @@ class ObjectPainter extends BasePainter { * @private */ async drawInSpecialArea(obj, opt, kind) { const canp = this.getCanvPainter(); - if (this._special_draw_area && isFunc(canp?.drawProjection)) - return canp.drawProjection(kind || this._special_draw_area, obj, opt); + if (this.#special_draw_area && isFunc(canp?.drawProjection)) + return canp.drawProjection(kind || this.#special_draw_area, obj, opt); return false; } /** @summary Get tooltip for painter and specified event position - * @param {Object} evnt - object wiith clientX and clientY positions + * @param {Object} evnt - object with clientX and clientY positions * @private */ getToolTip(evnt) { - if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) return null; + if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) + return null; - const frame = this.getFrameSvg(); - if (frame.empty()) return null; + const frame = this.getPadPainter()?.getFrameSvg(); + if (!frame || frame.empty()) + return null; const layer = frame.selectChild('.main_layer'); - if (layer.empty()) return null; + if (layer.empty()) + return null; const pos = pointer(evnt, layer.node()), pnt = { touch: false, x: pos[0], y: pos[1] }; @@ -13560,33 +14432,34 @@ function drawRawText(dom, txt /* , opt */) { const painter = new BasePainter(dom); painter.txt = txt; - painter.redrawObject = function(obj) { + painter.redrawObject = async function(obj) { this.txt = obj; - this.drawText(); - return true; + return this.drawText(); }; painter.drawText = async function() { - let txt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; - if (!isStr(txt)) txt = ''; - - const mathjax = this.txt.mathjax || (settings.Latex === constants$1.Latex.AlwaysMathJax); + let stxt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; + if (!isStr(stxt)) + stxt = ''; - if (!mathjax && !('as_is' in this.txt)) { - const arr = txt.split('\n'); txt = ''; - for (let i = 0; i < arr.length; ++i) - txt += `
${arr[i]}
`; - } - - const frame = this.selectDom(); + const mathjax = this.txt.mathjax || (settings.Latex === constants$1.Latex.AlwaysMathJax), + frame = this.selectDom(); let main = frame.select('div'); if (main.empty()) main = frame.append('div').attr('style', 'max-width:100%;max-height:100%;overflow:auto'); - main.html(txt); + else + main.html(''); // (re) set painter to first child element, base painter not requires canvas this.setTopPainter(); + if (!mathjax && !('as_is' in this.txt)) { + const arr = stxt.split('\n'); + for (let i = 0; i < arr.length; ++i) + main.append('pre').style('margin', '0').text(arr[i]); + } else + main.text(stxt); + if (mathjax) typesetMathjax(frame.node()); @@ -13596,18 +14469,25 @@ function drawRawText(dom, txt /* , opt */) { return painter.drawText(); } -/** @summary Returns canvas painter (if any) for specified HTML element - * @param {string|object} dom - id or DOM element +/** @summary Returns canvas painter (if any) for specified DOM element + * @param {string|object} dom - id or DOM element or pad painter * @private */ function getElementCanvPainter(dom) { - return new ObjectPainter(dom).getCanvPainter(); + return isPadPainter(dom) ? dom.getCanvPainter() : getDomCanvasPainter(new ObjectPainter(dom).selectDom()); +} + +/** @summary Returns pad painter (if any) for specified DOM element + * @param {string|object} dom - id or DOM element or pad painter + * @private */ +function getElementPadPainter(dom) { + return isPadPainter(dom) ? dom : new ObjectPainter(dom).getPadPainter(); } /** @summary Returns main painter (if any) for specified HTML element - typically histogram painter - * @param {string|object} dom - id or DOM element + * @param {string|object} dom - id or DOM element or pad painter * @private */ function getElementMainPainter(dom) { - return new ObjectPainter(dom).getMainPainter(true); + return isPadPainter(dom) ? dom.getMainPainter() : new ObjectPainter(dom).getMainPainter(true); } /** @summary Save object, drawn in specified element, as JSON. @@ -13676,7 +14556,10 @@ function resize(dom, arg) { * cleanup(document.querySelector('#drawing')); */ function cleanup(dom) { const dummy = new ObjectPainter(dom), lst = []; - dummy.forEachPainter(p => { if (lst.indexOf(p) < 0) lst.push(p); }); + dummy.forEachPainter(p => { + if (lst.indexOf(p) < 0) + lst.push(p); + }); lst.forEach(p => p.cleanup()); dummy.selectDom().html(''); return lst; @@ -13698,186 +14581,1315 @@ const EAxisBits = { kLabelsUp: BIT(21), kIsInteger: BIT(22), kMoreLogLabels: BIT(23), - kOppositeTitle: BIT(32) // atrificial bit, not possible to set in ROOT + kOppositeTitle: BIT(32) // artificial bit, not possible to set in ROOT }, kAxisLabels = 'labels', kAxisNormal = 'normal', kAxisFunc = 'func', kAxisTime = 'time'; +Object.assign(internals.jsroot, { ObjectPainter, cleanup, resize }); + /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -const REVISION = '162'; +const REVISION = '180'; + +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsMouse} + * @constant + */ +const MOUSE = { ROTATE: 0, DOLLY: 1, PAN: 2 }; -const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; +/** + * Represents touch interaction types in context of controls. + * + * @type {ConstantsTouch} + * @constant + */ const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; + +/** + * Disables face culling. + * + * @type {number} + * @constant + */ const CullFaceNone = 0; + +/** + * Culls back faces. + * + * @type {number} + * @constant + */ const CullFaceBack = 1; + +/** + * Culls front faces. + * + * @type {number} + * @constant + */ const CullFaceFront = 2; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm. + * + * @type {number} + * @constant + */ const PCFShadowMap = 1; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with + * better soft shadows especially when using low-resolution shadow maps. + * + * @type {number} + * @constant + */ const PCFSoftShadowMap = 2; + +/** + * Filters shadow maps using the Variance Shadow Map (VSM) algorithm. + * When using VSMShadowMap all shadow receivers will also cast shadows. + * + * @type {number} + * @constant + */ const VSMShadowMap = 3; + +/** + * Only front faces are rendered. + * + * @type {number} + * @constant + */ const FrontSide = 0; + +/** + * Only back faces are rendered. + * + * @type {number} + * @constant + */ const BackSide = 1; + +/** + * Both front and back faces are rendered. + * + * @type {number} + * @constant + */ const DoubleSide = 2; + +/** + * No blending is performed which effectively disables + * alpha transparency. + * + * @type {number} + * @constant + */ const NoBlending = 0; + +/** + * The default blending. + * + * @type {number} + * @constant + */ const NormalBlending = 1; + +/** + * Represents additive blending. + * + * @type {number} + * @constant + */ const AdditiveBlending = 2; + +/** + * Represents subtractive blending. + * + * @type {number} + * @constant + */ const SubtractiveBlending = 3; + +/** + * Represents multiply blending. + * + * @type {number} + * @constant + */ const MultiplyBlending = 4; + +/** + * Represents custom blending. + * + * @type {number} + * @constant + */ const CustomBlending = 5; + +/** + * A `source + destination` blending equation. + * + * @type {number} + * @constant + */ const AddEquation = 100; + +/** + * A `source - destination` blending equation. + * + * @type {number} + * @constant + */ const SubtractEquation = 101; + +/** + * A `destination - source` blending equation. + * + * @type {number} + * @constant + */ const ReverseSubtractEquation = 102; + +/** + * A blend equation that uses the minimum of source and destination. + * + * @type {number} + * @constant + */ const MinEquation = 103; + +/** + * A blend equation that uses the maximum of source and destination. + * + * @type {number} + * @constant + */ const MaxEquation = 104; + +/** + * Multiplies all colors by `0`. + * + * @type {number} + * @constant + */ const ZeroFactor = 200; + +/** + * Multiplies all colors by `1`. + * + * @type {number} + * @constant + */ const OneFactor = 201; + +/** + * Multiplies all colors by the source colors. + * + * @type {number} + * @constant + */ const SrcColorFactor = 202; + +/** + * Multiplies all colors by `1` minus each source color. + * + * @type {number} + * @constant + */ const OneMinusSrcColorFactor = 203; + +/** + * Multiplies all colors by the source alpha value. + * + * @type {number} + * @constant + */ const SrcAlphaFactor = 204; + +/** + * Multiplies all colors by 1 minus the source alpha value. + * + * @type {number} + * @constant + */ const OneMinusSrcAlphaFactor = 205; + +/** + * Multiplies all colors by the destination alpha value. + * + * @type {number} + * @constant + */ const DstAlphaFactor = 206; + +/** + * Multiplies all colors by `1` minus the destination alpha value. + * + * @type {number} + * @constant + */ const OneMinusDstAlphaFactor = 207; + +/** + * Multiplies all colors by the destination color. + * + * @type {number} + * @constant + */ const DstColorFactor = 208; + +/** + * Multiplies all colors by `1` minus each destination color. + * + * @type {number} + * @constant + */ const OneMinusDstColorFactor = 209; + +/** + * Multiplies the RGB colors by the smaller of either the source alpha + * value or the value of `1` minus the destination alpha value. The alpha + * value is multiplied by `1`. + * + * @type {number} + * @constant + */ const SrcAlphaSaturateFactor = 210; + +/** + * Multiplies all colors by a constant color. + * + * @type {number} + * @constant + */ const ConstantColorFactor = 211; + +/** + * Multiplies all colors by `1` minus a constant color. + * + * @type {number} + * @constant + */ const OneMinusConstantColorFactor = 212; + +/** + * Multiplies all colors by a constant alpha value. + * + * @type {number} + * @constant + */ const ConstantAlphaFactor = 213; + +/** + * Multiplies all colors by 1 minus a constant alpha value. + * + * @type {number} + * @constant + */ const OneMinusConstantAlphaFactor = 214; + +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverDepth = 0; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysDepth = 1; + +/** + * Pass if the incoming value is less than the depth buffer value. + * + * @type {number} + * @constant + */ const LessDepth = 2; + +/** + * Pass if the incoming value is less than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const LessEqualDepth = 3; + +/** + * Pass if the incoming value equals the depth buffer value. + * + * @type {number} + * @constant + */ const EqualDepth = 4; + +/** + * Pass if the incoming value is greater than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterEqualDepth = 5; + +/** + * Pass if the incoming value is greater than the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterDepth = 6; + +/** + * Pass if the incoming value is not equal to the depth buffer value. + * + * @type {number} + * @constant + */ const NotEqualDepth = 7; + +/** + * Multiplies the environment map color with the surface color. + * + * @type {number} + * @constant + */ const MultiplyOperation = 0; + +/** + * Uses reflectivity to blend between the two colors. + * + * @type {number} + * @constant + */ const MixOperation = 1; + +/** + * Adds the two colors. + * + * @type {number} + * @constant + */ const AddOperation = 2; + +/** + * No tone mapping is applied. + * + * @type {number} + * @constant + */ const NoToneMapping = 0; + +/** + * Linear tone mapping. + * + * @type {number} + * @constant + */ const LinearToneMapping = 1; + +/** + * Reinhard tone mapping. + * + * @type {number} + * @constant + */ const ReinhardToneMapping = 2; + +/** + * Cineon tone mapping. + * + * @type {number} + * @constant + */ const CineonToneMapping = 3; + +/** + * ACES Filmic tone mapping. + * + * @type {number} + * @constant + */ const ACESFilmicToneMapping = 4; + +/** + * Custom tone mapping. + * + * Expects a custom implementation by modifying shader code of the material's fragment shader. + * + * @type {number} + * @constant + */ const CustomToneMapping = 5; + +/** + * AgX tone mapping. + * + * @type {number} + * @constant + */ const AgXToneMapping = 6; + +/** + * Neutral tone mapping. + * + * Implementation based on the Khronos 3D Commerce Group standard tone mapping. + * + * @type {number} + * @constant + */ const NeutralToneMapping = 7; +/** + * Maps textures using the geometry's UV coordinates. + * + * @type {number} + * @constant + */ const UVMapping = 300; + +/** + * Reflection mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeReflectionMapping = 301; + +/** + * Refraction mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeRefractionMapping = 302; + +/** + * Reflection mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularReflectionMapping = 303; + +/** + * Refraction mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularRefractionMapping = 304; + +/** + * Reflection mapping for PMREM textures. + * + * @type {number} + * @constant + */ const CubeUVReflectionMapping = 306; + +/** + * The texture will simply repeat to infinity. + * + * @type {number} + * @constant + */ const RepeatWrapping = 1000; + +/** + * The last pixel of the texture stretches to the edge of the mesh. + * + * @type {number} + * @constant + */ const ClampToEdgeWrapping = 1001; + +/** + * The texture will repeats to infinity, mirroring on each repeat. + * + * @type {number} + * @constant + */ const MirroredRepeatWrapping = 1002; + +/** + * Returns the value of the texture element that is nearest (in Manhattan distance) + * to the specified texture coordinates. + * + * @type {number} + * @constant + */ const NearestFilter = 1003; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured + * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel) + * to produce a texture value. + * + * @type {number} + * @constant + */ const NearestMipmapNearestFilter = 1004; + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and + * uses the `NearestFilter` criterion to produce a texture value from each mipmap. + * The final texture value is a weighted average of those two values. + * + * @type {number} + * @constant + */ const NearestMipmapLinearFilter = 1005; + +/** + * Returns the weighted average of the four texture elements that are closest to the specified + * texture coordinates, and can include items wrapped or repeated from other parts of a texture, + * depending on the values of `wrapS` and `wrapT`, and on the exact mapping. + * + * @type {number} + * @constant + */ const LinearFilter = 1006; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured and uses + * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the + * center of the pixel) to produce a texture value. + * + * @type {number} + * @constant + */ const LinearMipmapNearestFilter = 1007; + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses + * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value + * is a weighted average of those two values. + * + * @type {number} + * @constant + */ const LinearMipmapLinearFilter = 1008; + +/** + * An unsigned byte data type for textures. + * + * @type {number} + * @constant + */ const UnsignedByteType = 1009; + +/** + * A byte data type for textures. + * + * @type {number} + * @constant + */ const ByteType = 1010; + +/** + * A short data type for textures. + * + * @type {number} + * @constant + */ const ShortType = 1011; + +/** + * An unsigned short data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShortType = 1012; + +/** + * An int data type for textures. + * + * @type {number} + * @constant + */ const IntType = 1013; + +/** + * An unsigned int data type for textures. + * + * @type {number} + * @constant + */ const UnsignedIntType = 1014; -const FloatType = 1015; -const HalfFloatType = 1016; -const UnsignedShort4444Type = 1017; + +/** + * A float data type for textures. + * + * @type {number} + * @constant + */ +const FloatType = 1015; + +/** + * A half float data type for textures. + * + * @type {number} + * @constant + */ +const HalfFloatType = 1016; + +/** + * An unsigned short 4_4_4_4 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedShort4444Type = 1017; + +/** + * An unsigned short 5_5_5_1 (packed) data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShort5551Type = 1018; + +/** + * An unsigned int 24_8 data type for textures. + * + * @type {number} + * @constant + */ const UnsignedInt248Type = 1020; + +/** + * An unsigned int 5_9_9_9 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt5999Type = 35902; + +/** + * An unsigned int 10_11_11 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt101111Type = 35899; + +/** + * Discards the red, green and blue components and reads just the alpha component. + * + * @type {number} + * @constant + */ const AlphaFormat = 1021; + +/** + * Discards the alpha component and reads the red, green and blue component. + * + * @type {number} + * @constant + */ +const RGBFormat = 1022; + +/** + * Reads the red, green, blue and alpha components. + * + * @type {number} + * @constant + */ const RGBAFormat = 1023; -const LuminanceFormat = 1024; -const LuminanceAlphaFormat = 1025; + +/** + * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`. + * + * @type {number} + * @constant + */ const DepthFormat = 1026; + +/** + * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as + * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format. + * + * @type {number} + * @constant + */ const DepthStencilFormat = 1027; + +/** + * Discards the green, blue and alpha components and reads just the red component. + * + * @type {number} + * @constant + */ const RedFormat = 1028; + +/** + * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RedIntegerFormat = 1029; + +/** + * Discards the alpha, and blue components and reads the red, and green components. + * + * @type {number} + * @constant + */ const RGFormat = 1030; + +/** + * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGIntegerFormat = 1031; + +/** + * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGBAIntegerFormat = 1033; +/** + * A DXT1-compressed image in an RGB image format. + * + * @type {number} + * @constant + */ const RGB_S3TC_DXT1_Format = 33776; + +/** + * A DXT1-compressed image in an RGB image format with a simple on/off alpha value. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT1_Format = 33777; + +/** + * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT3_Format = 33778; + +/** + * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3 + * compression in how the alpha compression is done. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT5_Format = 33779; + +/** + * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_4BPPV1_Format = 35840; + +/** + * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_2BPPV1_Format = 35841; + +/** + * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_4BPPV1_Format = 35842; + +/** + * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_2BPPV1_Format = 35843; + +/** + * ETC1 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC1_Format = 36196; + +/** + * ETC2 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC2_Format = 37492; + +/** + * ETC2 RGBA format. + * + * @type {number} + * @constant + */ const RGBA_ETC2_EAC_Format = 37496; + +/** + * ASTC RGBA 4x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_4x4_Format = 37808; + +/** + * ASTC RGBA 5x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x4_Format = 37809; + +/** + * ASTC RGBA 5x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x5_Format = 37810; + +/** + * ASTC RGBA 6x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x5_Format = 37811; + +/** + * ASTC RGBA 6x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x6_Format = 37812; + +/** + * ASTC RGBA 8x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x5_Format = 37813; + +/** + * ASTC RGBA 8x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x6_Format = 37814; + +/** + * ASTC RGBA 8x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x8_Format = 37815; + +/** + * ASTC RGBA 10x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x5_Format = 37816; + +/** + * ASTC RGBA 10x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x6_Format = 37817; + +/** + * ASTC RGBA 10x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x8_Format = 37818; + +/** + * ASTC RGBA 10x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x10_Format = 37819; + +/** + * ASTC RGBA 12x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x10_Format = 37820; + +/** + * ASTC RGBA 12x12 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x12_Format = 37821; + +/** + * BPTC RGBA format. + * + * @type {number} + * @constant + */ const RGBA_BPTC_Format = 36492; + +/** + * BPTC Signed RGB format. + * + * @type {number} + * @constant + */ const RGB_BPTC_SIGNED_Format = 36494; + +/** + * BPTC Unsigned RGB format. + * + * @type {number} + * @constant + */ const RGB_BPTC_UNSIGNED_Format = 36495; + +/** + * RGTC1 Red format. + * + * @type {number} + * @constant + */ const RED_RGTC1_Format = 36283; + +/** + * RGTC1 Signed Red format. + * + * @type {number} + * @constant + */ const SIGNED_RED_RGTC1_Format = 36284; + +/** + * RGTC2 Red Green format. + * + * @type {number} + * @constant + */ const RED_GREEN_RGTC2_Format = 36285; + +/** + * RGTC2 Signed Red Green format. + * + * @type {number} + * @constant + */ const SIGNED_RED_GREEN_RGTC2_Format = 36286; + +/** + * Basic depth packing. + * + * @type {number} + * @constant + */ const BasicDepthPacking = 3200; + +/** + * A depth value is packed into 32 bit RGBA. + * + * @type {number} + * @constant + */ const RGBADepthPacking = 3201; + +/** + * Normal information is relative to the underlying surface. + * + * @type {number} + * @constant + */ const TangentSpaceNormalMap = 0; + +/** + * Normal information is relative to the object orientation. + * + * @type {number} + * @constant + */ const ObjectSpaceNormalMap = 1; // Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. + +/** + * No color space. + * + * @type {string} + * @constant + */ const NoColorSpace = ''; + +/** + * sRGB color space. + * + * @type {string} + * @constant + */ const SRGBColorSpace = 'srgb'; + +/** + * sRGB-linear color space. + * + * @type {string} + * @constant + */ const LinearSRGBColorSpace = 'srgb-linear'; -const DisplayP3ColorSpace = 'display-p3'; -const LinearDisplayP3ColorSpace = 'display-p3-linear'; +/** + * Linear transfer function. + * + * @type {string} + * @constant + */ const LinearTransfer = 'linear'; + +/** + * sRGB transfer function. + * + * @type {string} + * @constant + */ const SRGBTransfer = 'srgb'; -const Rec709Primaries = 'rec709'; -const P3Primaries = 'p3'; +/** + * Keeps the current value. + * + * @type {number} + * @constant + */ const KeepStencilOp = 7680; + +/** + * Will always return true. + * + * @type {number} + * @constant + */ const AlwaysStencilFunc = 519; +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverCompare = 512; + +/** + * Pass if the incoming value is less than the texture value. + * + * @type {number} + * @constant + */ const LessCompare = 513; + +/** + * Pass if the incoming value equals the texture value. + * + * @type {number} + * @constant + */ const EqualCompare = 514; + +/** + * Pass if the incoming value is less than or equal to the texture value. + * + * @type {number} + * @constant + */ const LessEqualCompare = 515; + +/** + * Pass if the incoming value is greater than the texture value. + * + * @type {number} + * @constant + */ const GreaterCompare = 516; + +/** + * Pass if the incoming value is not equal to the texture value. + * + * @type {number} + * @constant + */ const NotEqualCompare = 517; + +/** + * Pass if the incoming value is greater than or equal to the texture value. + * + * @type {number} + * @constant + */ const GreaterEqualCompare = 518; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysCompare = 519; +/** + * The contents are intended to be specified once by the application, and used many + * times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const StaticDrawUsage = 35044; -const GLSL3 = '300 es'; -const _SRGBAFormat = 1035; // fallback for WebGL 1 +/** + * GLSL 3 shader code. + * + * @type {string} + * @constant + */ +const GLSL3 = '300 es'; +/** + * WebGL coordinate system. + * + * @type {number} + * @constant + */ const WebGLCoordinateSystem = 2000; + +/** + * WebGPU coordinate system. + * + * @type {number} + * @constant + */ const WebGPUCoordinateSystem = 2001; /** - * https://github.com/mrdoob/eventdispatcher.js/ + * This type represents mouse buttons and interaction types in context of controls. + * + * @typedef {Object} ConstantsMouse + * @property {number} MIDDLE - The left mouse button. + * @property {number} LEFT - The middle mouse button. + * @property {number} RIGHT - The right mouse button. + * @property {number} ROTATE - A rotate interaction. + * @property {number} DOLLY - A dolly interaction. + * @property {number} PAN - A pan interaction. + **/ + +/** + * This type represents touch interaction types in context of controls. + * + * @typedef {Object} ConstantsTouch + * @property {number} ROTATE - A rotate interaction. + * @property {number} PAN - A pan interaction. + * @property {number} DOLLY_PAN - The dolly-pan interaction. + * @property {number} DOLLY_ROTATE - A dolly-rotate interaction. + **/ + +/** + * This type represents the different timestamp query types. + * + * @typedef {Object} ConstantsTimestampQuery + * @property {string} COMPUTE - A `compute` timestamp query. + * @property {string} RENDER - A `render` timestamp query. + **/ + +/** + * Represents the different interpolation sampling types. + * + * @typedef {Object} ConstantsInterpolationSamplingType + * @property {string} PERSPECTIVE - Perspective-correct interpolation. + * @property {string} LINEAR - Linear interpolation. + * @property {string} FLAT - Flat interpolation. + */ + +/** + * Represents the different interpolation sampling modes. + * + * @typedef {Object} ConstantsInterpolationSamplingMode + * @property {string} NORMAL - Normal sampling mode. + * @property {string} CENTROID - Centroid sampling mode. + * @property {string} SAMPLE - Sample-specific sampling mode. + * @property {string} FIRST - Flat interpolation using the first vertex. + * @property {string} EITHER - Flat interpolation using either vertex. */ +/** + * This modules allows to dispatch event objects on custom JavaScript objects. + * + * Main repository: [eventdispatcher.js]{@link https://github.com/mrdoob/eventdispatcher.js/} + * + * Code Example: + * ```js + * class Car extends EventDispatcher { + * start() { + * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); + * } + *}; + * + * // Using events with the custom object + * const car = new Car(); + * car.addEventListener( 'start', function ( event ) { + * alert( event.message ); + * } ); + * + * car.start(); + * ``` + */ class EventDispatcher { + /** + * Adds the given event listener to the given event type. + * + * @param {string} type - The type of event to listen to. + * @param {Function} listener - The function that gets called when the event is fired. + */ addEventListener( type, listener ) { if ( this._listeners === undefined ) this._listeners = {}; @@ -13890,7 +15902,7 @@ class EventDispatcher { } - if ( listeners[ type ].indexOf( listener ) === - 1 ) { + if ( listeners[ type ].indexOf( listener ) === -1 ) { listeners[ type ].push( listener ); @@ -13898,28 +15910,42 @@ class EventDispatcher { } + /** + * Returns `true` if the given event listener has been added to the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to check. + * @return {boolean} Whether the given event listener has been added to the given event type. + */ hasEventListener( type, listener ) { - if ( this._listeners === undefined ) return false; - const listeners = this._listeners; - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; + if ( listeners === undefined ) return false; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1; } + /** + * Removes the given event listener from the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to remove. + */ removeEventListener( type, listener ) { - if ( this._listeners === undefined ) return; - const listeners = this._listeners; + + if ( listeners === undefined ) return; + const listenerArray = listeners[ type ]; if ( listenerArray !== undefined ) { const index = listenerArray.indexOf( listener ); - if ( index !== - 1 ) { + if ( index !== -1 ) { listenerArray.splice( index, 1 ); @@ -13929,11 +15955,17 @@ class EventDispatcher { } + /** + * Dispatches an event object. + * + * @param {Object} event - The event that gets fired. + */ dispatchEvent( event ) { - if ( this._listeners === undefined ) return; - const listeners = this._listeners; + + if ( listeners === undefined ) return; + const listenerArray = listeners[ event.type ]; if ( listenerArray !== undefined ) { @@ -13965,9 +15997,16 @@ let _seed = 1234567; const DEG2RAD = Math.PI / 180; const RAD2DEG = 180 / Math.PI; -// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 +/** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @return {string} The UUID. + */ function generateUUID() { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 + const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; @@ -13982,30 +16021,66 @@ function generateUUID() { } +/** + * Clamps the given value between min and max. + * + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ function clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } -// compute euclidean modulo of m % n -// https://en.wikipedia.org/wiki/Modulo_operation +/** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ function euclideanModulo( n, m ) { + // https://en.wikipedia.org/wiki/Modulo_operation + return ( ( n % m ) + m ) % m; } -// Linear mapping from range to range +/** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ function mapLinear( x, a1, a2, b1, b2 ) { return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); } -// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ +/** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ function inverseLerp( x, y, value ) { + // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ + if ( x !== y ) { return ( value - x ) / ( y - x ); @@ -14018,28 +16093,66 @@ function inverseLerp( x, y, value ) { } -// https://en.wikipedia.org/wiki/Linear_interpolation +/** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ function lerp( x, y, t ) { return ( 1 - t ) * x + t * y; } -// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ +/** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ function damp( x, y, lambda, dt ) { return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); } -// https://www.desmos.com/calculator/vcsjnyz7x4 +/** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ function pingpong( x, length = 1 ) { + // https://www.desmos.com/calculator/vcsjnyz7x4 + return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); } -// http://en.wikipedia.org/wiki/Smoothstep +/** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ function smoothstep( x, min, max ) { if ( x <= min ) return 0; @@ -14051,6 +16164,15 @@ function smoothstep( x, min, max ) { } +/** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ function smootherstep( x, min, max ) { if ( x <= min ) return 0; @@ -14062,28 +16184,50 @@ function smootherstep( x, min, max ) { } -// Random integer from interval +/** + * Returns a random integer from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ function randInt( low, high ) { return low + Math.floor( Math.random() * ( high - low + 1 ) ); } -// Random float from interval +/** + * Returns a random float from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ function randFloat( low, high ) { return low + Math.random() * ( high - low ); } -// Random float from <-range/2, range/2> interval +/** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ function randFloatSpread( range ) { return range * ( 0.5 - Math.random() ); } -// Deterministic pseudo-random float in the interval [ 0, 1 ] +/** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ function seededRandom( s ) { if ( s !== undefined ) _seed = s; @@ -14100,44 +16244,81 @@ function seededRandom( s ) { } +/** + * Converts degrees to radians. + * + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ function degToRad( degrees ) { return degrees * DEG2RAD; } +/** + * Converts radians to degrees. + * + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ function radToDeg( radians ) { return radians * RAD2DEG; } +/** + * Returns `true` if the given number is a power of two. + * + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ function isPowerOfTwo( value ) { return ( value & ( value - 1 ) ) === 0 && value !== 0; } +/** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ function ceilPowerOfTwo( value ) { return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); } +/** + * Returns the largest power of two that is less than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ function floorPowerOfTwo( value ) { return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); } +/** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ function setQuaternionFromProperEuler( q, a, b, c, order ) { - // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles - - // rotations are applied to the axes in the order specified by 'order' - // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' - // angles are in radians - const cos = Math.cos; const sin = Math.sin; @@ -14186,6 +16367,13 @@ function setQuaternionFromProperEuler( q, a, b, c, order ) { } +/** + * Denormalizes the given value according to the given typed array. + * + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ function denormalize( value, array ) { switch ( array.constructor ) { @@ -14208,15 +16396,15 @@ function denormalize( value, array ) { case Int32Array: - return Math.max( value / 2147483647.0, - 1.0 ); + return Math.max( value / 2147483647.0, -1 ); case Int16Array: - return Math.max( value / 32767.0, - 1.0 ); + return Math.max( value / 32767.0, -1 ); case Int8Array: - return Math.max( value / 127.0, - 1.0 ); + return Math.max( value / 127.0, -1 ); default: @@ -14226,7 +16414,14 @@ function denormalize( value, array ) { } -function normalize( value, array ) { +/** + * Normalizes the given value according to the given typed array. + * + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ +function normalize$1( value, array ) { switch ( array.constructor ) { @@ -14266,30 +16461,253 @@ function normalize( value, array ) { } +/** + * @class + * @classdesc A collection of math utility functions. + * @hideconstructor + */ const MathUtils = { DEG2RAD: DEG2RAD, RAD2DEG: RAD2DEG, + /** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @static + * @method + * @return {string} The UUID. + */ generateUUID: generateUUID, + /** + * Clamps the given value between min and max. + * + * @static + * @method + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ clamp: clamp, + /** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @static + * @method + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ euclideanModulo: euclideanModulo, + /** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @static + * @method + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ mapLinear: mapLinear, + /** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ inverseLerp: inverseLerp, + /** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ lerp: lerp, + /** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @static + * @method + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ damp: damp, + /** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @static + * @method + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ pingpong: pingpong, + /** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ smoothstep: smoothstep, + /** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ smootherstep: smootherstep, + /** + * Returns a random integer from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ randInt: randInt, + /** + * Returns a random float from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ randFloat: randFloat, + /** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @static + * @method + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ randFloatSpread: randFloatSpread, + /** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @static + * @method + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ seededRandom: seededRandom, + /** + * Converts degrees to radians. + * + * @static + * @method + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ degToRad: degToRad, + /** + * Converts radians to degrees. + * + * @static + * @method + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ radToDeg: radToDeg, + /** + * Returns `true` if the given number is a power of two. + * + * @static + * @method + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ isPowerOfTwo: isPowerOfTwo, + /** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ ceilPowerOfTwo: ceilPowerOfTwo, + /** + * Returns the largest power of two that is less than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ floorPowerOfTwo: floorPowerOfTwo, + /** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @static + * @method + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ setQuaternionFromProperEuler: setQuaternionFromProperEuler, - normalize: normalize, + /** + * Normalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ + normalize: normalize$1, + /** + * Denormalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ denormalize: denormalize }; @@ -14310,7 +16728,7 @@ var MathUtils$1 = /*#__PURE__*/Object.freeze({ isPowerOfTwo: isPowerOfTwo, lerp: lerp, mapLinear: mapLinear, - normalize: normalize, + normalize: normalize$1, pingpong: pingpong, radToDeg: radToDeg, randFloat: randFloat, @@ -14322,17 +16740,71 @@ var MathUtils$1 = /*#__PURE__*/Object.freeze({ smoothstep: smoothstep }); +/** + * Class representing a 2D vector. A 2D vector is an ordered pair of numbers + * (labeled x and y), which can be used to represent a number of things, such as: + * + * - A point in 2D space (i.e. a position on a plane). + * - A direction and length across a plane. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)` + * and the direction is also measured from `(0, 0)` towards `(x, y)`. + * - Any arbitrary ordered pair of numbers. + * + * There are other things a 2D vector can be used to represent, such as + * momentum vectors, complex numbers and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector2( 0, 1 ); + * + * //no arguments; will be initialised to (0, 0) + * const b = new THREE.Vector2( ); + * + * const d = a.distanceTo( b ); + * ``` + */ class Vector2 { + /** + * Constructs a new 2D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + */ constructor( x = 0, y = 0 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ Vector2.prototype.isVector2 = true; + /** + * The x value of this vector. + * + * @type {number} + */ this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ this.y = y; } + /** + * Alias for {@link Vector2#x}. + * + * @type {number} + */ get width() { return this.x; @@ -14345,6 +16817,11 @@ class Vector2 { } + /** + * Alias for {@link Vector2#y}. + * + * @type {number} + */ get height() { return this.y; @@ -14357,6 +16834,13 @@ class Vector2 { } + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @return {Vector2} A reference to this vector. + */ set( x, y ) { this.x = x; @@ -14366,6 +16850,12 @@ class Vector2 { } + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector2} A reference to this vector. + */ setScalar( scalar ) { this.x = scalar; @@ -14375,6 +16865,12 @@ class Vector2 { } + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector2} A reference to this vector. + */ setX( x ) { this.x = x; @@ -14383,6 +16879,12 @@ class Vector2 { } + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector2} A reference to this vector. + */ setY( y ) { this.y = y; @@ -14391,6 +16893,13 @@ class Vector2 { } + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @param {number} value - The value to set. + * @return {Vector2} A reference to this vector. + */ setComponent( index, value ) { switch ( index ) { @@ -14405,6 +16914,12 @@ class Vector2 { } + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @return {number} A vector component value. + */ getComponent( index ) { switch ( index ) { @@ -14417,12 +16932,23 @@ class Vector2 { } + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector2} A clone of this instance. + */ clone() { return new this.constructor( this.x, this.y ); } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector2} v - The vector to copy. + * @return {Vector2} A reference to this vector. + */ copy( v ) { this.x = v.x; @@ -14432,6 +16958,12 @@ class Vector2 { } + /** + * Adds the given vector to this instance. + * + * @param {Vector2} v - The vector to add. + * @return {Vector2} A reference to this vector. + */ add( v ) { this.x += v.x; @@ -14441,6 +16973,12 @@ class Vector2 { } + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector2} A reference to this vector. + */ addScalar( s ) { this.x += s; @@ -14450,6 +16988,13 @@ class Vector2 { } + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ addVectors( a, b ) { this.x = a.x + b.x; @@ -14459,6 +17004,13 @@ class Vector2 { } + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector2} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector2} A reference to this vector. + */ addScaledVector( v, s ) { this.x += v.x * s; @@ -14468,6 +17020,12 @@ class Vector2 { } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector2} v - The vector to subtract. + * @return {Vector2} A reference to this vector. + */ sub( v ) { this.x -= v.x; @@ -14477,6 +17035,12 @@ class Vector2 { } + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector2} A reference to this vector. + */ subScalar( s ) { this.x -= s; @@ -14486,6 +17050,13 @@ class Vector2 { } + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ subVectors( a, b ) { this.x = a.x - b.x; @@ -14495,6 +17066,12 @@ class Vector2 { } + /** + * Multiplies the given vector with this instance. + * + * @param {Vector2} v - The vector to multiply. + * @return {Vector2} A reference to this vector. + */ multiply( v ) { this.x *= v.x; @@ -14504,6 +17081,12 @@ class Vector2 { } + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector2} A reference to this vector. + */ multiplyScalar( scalar ) { this.x *= scalar; @@ -14513,6 +17096,12 @@ class Vector2 { } + /** + * Divides this instance by the given vector. + * + * @param {Vector2} v - The vector to divide. + * @return {Vector2} A reference to this vector. + */ divide( v ) { this.x /= v.x; @@ -14522,12 +17111,25 @@ class Vector2 { } + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector2} A reference to this vector. + */ divideScalar( scalar ) { return this.multiplyScalar( 1 / scalar ); } + /** + * Multiplies this vector (with an implicit 1 as the 3rd component) by + * the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to apply. + * @return {Vector2} A reference to this vector. + */ applyMatrix3( m ) { const x = this.x, y = this.y; @@ -14540,6 +17142,13 @@ class Vector2 { } + /** + * If this vector's x or y value is greater than the given vector's x or y + * value, replace that value with the corresponding min value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ min( v ) { this.x = Math.min( this.x, v.x ); @@ -14549,6 +17158,13 @@ class Vector2 { } + /** + * If this vector's x or y value is less than the given vector's x or y + * value, replace that value with the corresponding max value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ max( v ) { this.x = Math.max( this.x, v.x ); @@ -14558,34 +17174,69 @@ class Vector2 { } + /** + * If this vector's x or y value is greater than the max vector's x or y + * value, it is replaced by the corresponding value. + * If this vector's x or y value is less than the min vector's x or y value, + * it is replaced by the corresponding value. + * + * @param {Vector2} min - The minimum x and y values. + * @param {Vector2} max - The maximum x and y values in the desired range. + * @return {Vector2} A reference to this vector. + */ clamp( min, max ) { // assumes min < max, componentwise - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); return this; } + /** + * If this vector's x or y values are greater than the max value, they are + * replaced by the max value. + * If this vector's x or y values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector2} A reference to this vector. + */ clampScalar( minVal, maxVal ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); return this; } + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector2} A reference to this vector. + */ clampLength( min, max ) { const length = this.length(); - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); } + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ floor() { this.x = Math.floor( this.x ); @@ -14595,6 +17246,11 @@ class Vector2 { } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ ceil() { this.x = Math.ceil( this.x ); @@ -14604,6 +17260,11 @@ class Vector2 { } + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector2} A reference to this vector. + */ round() { this.x = Math.round( this.x ); @@ -14613,6 +17274,12 @@ class Vector2 { } + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector2} A reference to this vector. + */ roundToZero() { this.x = Math.trunc( this.x ); @@ -14622,6 +17289,11 @@ class Vector2 { } + /** + * Inverts this vector - i.e. sets x = -x and y = -y. + * + * @return {Vector2} A reference to this vector. + */ negate() { this.x = - this.x; @@ -14631,52 +17303,96 @@ class Vector2 { } + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ dot( v ) { return this.x * v.x + this.y * v.y; } + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the cross product with. + * @return {number} The result of the cross product. + */ cross( v ) { return this.x * v.y - this.y * v.x; } + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ lengthSq() { return this.x * this.x + this.y * this.y; } + /** + * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y). + * + * @return {number} The length of this vector. + */ length() { return Math.sqrt( this.x * this.x + this.y * this.y ); } + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ); } + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector2} A reference to this vector. + */ normalize() { return this.divideScalar( this.length() || 1 ); } + /** + * Computes the angle in radians of this vector with respect to the positive x-axis. + * + * @return {number} The angle in radians. + */ angle() { - // computes the angle in radians with respect to the positive x-axis - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; return angle; } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector2} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ angleTo( v ) { const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); @@ -14687,16 +17403,30 @@ class Vector2 { // clamp, to handle numerical problems - return Math.acos( clamp( theta, - 1, 1 ) ); + return Math.acos( clamp( theta, -1, 1 ) ); } + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the distance to. + * @return {number} The distance. + */ distanceTo( v ) { return Math.sqrt( this.distanceToSquared( v ) ); } + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector2} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ distanceToSquared( v ) { const dx = this.x - v.x, dy = this.y - v.y; @@ -14704,18 +17434,40 @@ class Vector2 { } + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ manhattanDistanceTo( v ) { return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); } + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector2} A reference to this vector. + */ setLength( length ) { return this.normalize().multiplyScalar( length ); } + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector2} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; @@ -14725,6 +17477,16 @@ class Vector2 { } + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector2} v1 - The first vector. + * @param {Vector2} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; @@ -14734,12 +17496,26 @@ class Vector2 { } + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector2} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) ); } + /** + * Sets this vector's x value to be `array[ offset ]` and y + * value to be `array[ offset + 1 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector2} A reference to this vector. + */ fromArray( array, offset = 0 ) { this.x = array[ offset ]; @@ -14749,6 +17525,14 @@ class Vector2 { } + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ toArray( array = [], offset = 0 ) { array[ offset ] = this.x; @@ -14758,6 +17542,13 @@ class Vector2 { } + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector2} A reference to this vector. + */ fromBufferAttribute( attribute, index ) { this.x = attribute.getX( index ); @@ -14767,6 +17558,13 @@ class Vector2 { } + /** + * Rotates this vector around the given center by the given angle. + * + * @param {Vector2} center - The point around which to rotate. + * @param {number} angle - The angle to rotate, in radians. + * @return {Vector2} A reference to this vector. + */ rotateAround( center, angle ) { const c = Math.cos( angle ), s = Math.sin( angle ); @@ -14781,6 +17579,12 @@ class Vector2 { } + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector2} A reference to this vector. + */ random() { this.x = Math.random(); @@ -14799,3248 +17603,4386 @@ class Vector2 { } -class Matrix3 { +/** + * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * + * Note that three.js expects Quaternions to be normalized. + * ```js + * const quaternion = new THREE.Quaternion(); + * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); + * + * const vector = new THREE.Vector3( 1, 0, 0 ); + * vector.applyQuaternion( quaternion ); + * ``` + */ +class Quaternion { - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + /** + * Constructs a new quaternion. + * + * @param {number} [x=0] - The x value of this quaternion. + * @param {number} [y=0] - The y value of this quaternion. + * @param {number} [z=0] - The z value of this quaternion. + * @param {number} [w=1] - The w value of this quaternion. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { - Matrix3.prototype.isMatrix3 = true; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuaternion = true; - this.elements = [ + this._x = x; + this._y = y; + this._z = z; + this._w = w; - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + } - ]; + /** + * Interpolates between two quaternions via SLERP. This implementation assumes the + * quaternion data are managed in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @param {number} t - The interpolation factor in the range `[0,1]`. + * @see {@link Quaternion#slerp} + */ + static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - if ( n11 !== undefined ) { + // fuzz-free, array-based Quaternion SLERP operation - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); + let x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ]; - } + const x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; - } + if ( t === 0 ) { - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + dst[ dstOffset + 0 ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + return; - const te = this.elements; + } - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; + if ( t === 1 ) { - return this; + dst[ dstOffset + 0 ] = x1; + dst[ dstOffset + 1 ] = y1; + dst[ dstOffset + 2 ] = z1; + dst[ dstOffset + 3 ] = w1; + return; - } + } - identity() { + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - this.set( + let s = 1 - t; + const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + dir = ( cos >= 0 ? 1 : -1 ), + sqrSin = 1 - cos * cos; - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { - ); + const sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); - return this; + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; - } + } - copy( m ) { + const tDir = t * dir; - const te = this.elements; - const me = m.elements; + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { - return this; + const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - } + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; - extractBasis( xAxis, yAxis, zAxis ) { + } - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); + } - return this; + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; } - setFromMatrix4( m ) { - - const me = m.elements; + /** + * Multiplies two quaternions. This implementation assumes the quaternion data are managed + * in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @return {Array} The destination array. + * @see {@link Quaternion#multiplyQuaternions}. + */ + static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - this.set( + const x0 = src0[ srcOffset0 ]; + const y0 = src0[ srcOffset0 + 1 ]; + const z0 = src0[ srcOffset0 + 2 ]; + const w0 = src0[ srcOffset0 + 3 ]; - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] + const x1 = src1[ srcOffset1 ]; + const y1 = src1[ srcOffset1 + 1 ]; + const z1 = src1[ srcOffset1 + 2 ]; + const w1 = src1[ srcOffset1 + 3 ]; - ); + dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; + dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; + dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; + dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - return this; + return dst; } - multiply( m ) { + /** + * The x value of this quaternion. + * + * @type {number} + * @default 0 + */ + get x() { - return this.multiplyMatrices( this, m ); + return this._x; } - premultiply( m ) { + set x( value ) { - return this.multiplyMatrices( m, this ); + this._x = value; + this._onChangeCallback(); } - multiplyMatrices( a, b ) { + /** + * The y value of this quaternion. + * + * @type {number} + * @default 0 + */ + get y() { - const ae = a.elements; - const be = b.elements; - const te = this.elements; + return this._y; - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; + } - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; + set y( value ) { - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; + this._y = value; + this._onChangeCallback(); - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; + } - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; + /** + * The z value of this quaternion. + * + * @type {number} + * @default 0 + */ + get z() { - return this; + return this._z; } - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + set z( value ) { - return this; + this._z = value; + this._onChangeCallback(); } - determinant() { - - const te = this.elements; - - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + /** + * The w value of this quaternion. + * + * @type {number} + * @default 1 + */ + get w() { - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + return this._w; } - invert() { - - const te = this.elements, + set w( value ) { - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], + this._w = value; + this._onChangeCallback(); - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, + } - det = n11 * t11 + n21 * t12 + n31 * t13; + /** + * Sets the quaternion components. + * + * @param {number} x - The x value of this quaternion. + * @param {number} y - The y value of this quaternion. + * @param {number} z - The z value of this quaternion. + * @param {number} w - The w value of this quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + set( x, y, z, w ) { - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + this._x = x; + this._y = y; + this._z = z; + this._w = w; - const detInv = 1 / det; + this._onChangeCallback(); - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + return this; - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + } - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; + /** + * Returns a new quaternion with copied values from this instance. + * + * @return {Quaternion} A clone of this instance. + */ + clone() { - return this; + return new this.constructor( this._x, this._y, this._z, this._w ); } - transpose() { + /** + * Copies the values of the given quaternion to this instance. + * + * @param {Quaternion} quaternion - The quaternion to copy. + * @return {Quaternion} A reference to this quaternion. + */ + copy( quaternion ) { - let tmp; - const m = this.elements; + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + this._onChangeCallback(); return this; } - getNormalMatrix( matrix4 ) { + /** + * Sets this quaternion from the rotation specified by the given + * Euler angles. + * + * @param {Euler} euler - The Euler angles. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Quaternion} A reference to this quaternion. + */ + setFromEuler( euler, update = true ) { - return this.setFromMatrix4( matrix4 ).invert().transpose(); + const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - } + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m - transposeIntoArray( r ) { + const cos = Math.cos; + const sin = Math.sin; - const m = this.elements; + const c1 = cos( x / 2 ); + const c2 = cos( y / 2 ); + const c3 = cos( z / 2 ); - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; + const s1 = sin( x / 2 ); + const s2 = sin( y / 2 ); + const s3 = sin( z / 2 ); - return this; + switch ( order ) { - } + case 'XYZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { + case 'YXZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); + case 'ZXY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); + case 'ZYX': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - return this; + case 'YZX': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - } + case 'XZY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - // + default: + console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - scale( sx, sy ) { + } - this.premultiply( _m3.makeScale( sx, sy ) ); + if ( update === true ) this._onChangeCallback(); return this; } - rotate( theta ) { - - this.premultiply( _m3.makeRotation( - theta ) ); + /** + * Sets this quaternion from the given axis and angle. + * + * @param {Vector3} axis - The normalized axis. + * @param {number} angle - The angle in radians. + * @return {Quaternion} A reference to this quaternion. + */ + setFromAxisAngle( axis, angle ) { - return this; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - } + const halfAngle = angle / 2, s = Math.sin( halfAngle ); - translate( tx, ty ) { + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); - this.premultiply( _m3.makeTranslation( tx, ty ) ); + this._onChangeCallback(); return this; } - // for 2D Transforms - - makeTranslation( x, y ) { + /** + * Sets this quaternion from the given rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @return {Quaternion} A reference to this quaternion. + */ + setFromRotationMatrix( m ) { - if ( x.isVector2 ) { + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - this.set( + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - 1, 0, x.x, - 0, 1, x.y, - 0, 0, 1 + const te = m.elements, - ); + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - } else { + trace = m11 + m22 + m33; - this.set( + if ( trace > 0 ) { - 1, 0, x, - 0, 1, y, - 0, 0, 1 + const s = 0.5 / Math.sqrt( trace + 1.0 ); - ); + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; - } + } else if ( m11 > m22 && m11 > m33 ) { - return this; + const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - } + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; - makeRotation( theta ) { + } else if ( m22 > m33 ) { - // counterclockwise + const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - const c = Math.cos( theta ); - const s = Math.sin( theta ); + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; - this.set( + } else { - c, - s, 0, - s, c, 0, - 0, 0, 1 + const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - ); + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this._onChangeCallback(); return this; } - makeScale( x, y ) { + /** + * Sets this quaternion to the rotation required to rotate the direction vector + * `vFrom` to the direction vector `vTo`. + * + * @param {Vector3} vFrom - The first (normalized) direction vector. + * @param {Vector3} vTo - The second (normalized) direction vector. + * @return {Quaternion} A reference to this quaternion. + */ + setFromUnitVectors( vFrom, vTo ) { - this.set( + // assumes direction vectors vFrom and vTo are normalized - x, 0, 0, - 0, y, 0, - 0, 0, 1 + let r = vFrom.dot( vTo ) + 1; - ); + if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286 - return this; + // vFrom and vTo point in opposite directions - } + r = 0; - // + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - equals( matrix ) { + this._x = - vFrom.y; + this._y = vFrom.x; + this._z = 0; + this._w = r; - const te = this.elements; - const me = matrix.elements; + } else { - for ( let i = 0; i < 9; i ++ ) { + this._x = 0; + this._y = - vFrom.z; + this._z = vFrom.y; + this._w = r; - if ( te[ i ] !== me[ i ] ) return false; + } - } + } else { - return true; + // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - } + this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; + this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; + this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; + this._w = r; - fromArray( array, offset = 0 ) { + } - for ( let i = 0; i < 9; i ++ ) { + return this.normalize(); - this.elements[ i ] = array[ i + offset ]; + } - } + /** + * Returns the angle between this quaternion and the given one in radians. + * + * @param {Quaternion} q - The quaternion to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( q ) { - return this; + return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) ); } - toArray( array = [], offset = 0 ) { + /** + * Rotates this quaternion by a given angular step to the given quaternion. + * The method ensures that the final quaternion will not overshoot `q`. + * + * @param {Quaternion} q - The target quaternion. + * @param {number} step - The angular step in radians. + * @return {Quaternion} A reference to this quaternion. + */ + rotateTowards( q, step ) { - const te = this.elements; + const angle = this.angleTo( q ); - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; + if ( angle === 0 ) return this; - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; + const t = Math.min( 1, step / angle ); - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; + this.slerp( q, t ); - return array; + return this; } - clone() { + /** + * Sets this quaternion to the identity quaternion; that is, to the + * quaternion that represents "no rotation". + * + * @return {Quaternion} A reference to this quaternion. + */ + identity() { - return new this.constructor().fromArray( this.elements ); + return this.set( 0, 0, 0, 1 ); } -} + /** + * Inverts this quaternion via {@link Quaternion#conjugate}. The + * quaternion is assumed to have unit length. + * + * @return {Quaternion} A reference to this quaternion. + */ + invert() { -const _m3 = /*@__PURE__*/ new Matrix3(); + return this.conjugate(); -function arrayNeedsUint32( array ) { + } - // assumes larger values usually on last + /** + * Returns the rotational conjugate of this quaternion. The conjugate of a + * quaternion represents the same rotation in the opposite direction about + * the rotational axis. + * + * @return {Quaternion} A reference to this quaternion. + */ + conjugate() { - for ( let i = array.length - 1; i >= 0; -- i ) { + this._x *= -1; + this._y *= -1; + this._z *= -1; - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 + this._onChangeCallback(); - } + return this; - return false; + } -} + /** + * Calculates the dot product of this quaternion and the given one. + * + * @param {Quaternion} v - The quaternion to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { -function createElementNS( name ) { + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); + } -} + /** + * Computes the squared Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. This can be useful if you are comparing the + * lengths of two quaternions, as this is a slightly more efficient calculation than + * {@link Quaternion#length}. + * + * @return {number} The squared Euclidean length. + */ + lengthSq() { -function createCanvasElement() { + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; + } -} + /** + * Computes the Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. + * + * @return {number} The Euclidean length. + */ + length() { -const _cache = {}; + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); -function warnOnce( message ) { + } - if ( message in _cache ) return; + /** + * Normalizes this quaternion - that is, calculated the quaternion that performs + * the same rotation as this one, but has a length equal to `1`. + * + * @return {Quaternion} A reference to this quaternion. + */ + normalize() { - _cache[ message ] = true; + let l = this.length(); - console.warn( message ); + if ( l === 0 ) { -} + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; -/** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ + } else { -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( - 0.8224621, 0.177538, 0.0, - 0.0331941, 0.9668058, 0.0, - 0.0170827, 0.0723974, 0.9105199, -); + l = 1 / l; -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().set( - 1.2249401, - 0.2249404, 0.0, - - 0.0420569, 1.0420571, 0.0, - - 0.0196376, - 0.0786361, 1.0982735 -); + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; -/** - * Defines supported color spaces by transfer function and primaries, - * and provides conversions to/from the Linear-sRGB reference space. - */ -const COLOR_SPACES = { - [ LinearSRGBColorSpace ]: { - transfer: LinearTransfer, - primaries: Rec709Primaries, - toReference: ( color ) => color, - fromReference: ( color ) => color, - }, - [ SRGBColorSpace ]: { - transfer: SRGBTransfer, - primaries: Rec709Primaries, - toReference: ( color ) => color.convertSRGBToLinear(), - fromReference: ( color ) => color.convertLinearToSRGB(), - }, - [ LinearDisplayP3ColorSpace ]: { - transfer: LinearTransfer, - primaries: P3Primaries, - toReference: ( color ) => color.applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ), - }, - [ DisplayP3ColorSpace ]: { - transfer: SRGBTransfer, - primaries: P3Primaries, - toReference: ( color ) => color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(), - }, -}; + } -const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] ); + this._onChangeCallback(); -const ColorManagement = { + return this; - enabled: true, + } - _workingColorSpace: LinearSRGBColorSpace, + /** + * Multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiply( q ) { - get workingColorSpace() { + return this.multiplyQuaternions( this, q ); - return this._workingColorSpace; + } - }, + /** + * Pre-multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + premultiply( q ) { - set workingColorSpace( colorSpace ) { + return this.multiplyQuaternions( q, this ); - if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) { + } - throw new Error( `Unsupported working color space, "${ colorSpace }".` ); + /** + * Multiplies the given quaternions and stores the result in this instance. + * + * @param {Quaternion} a - The first quaternion. + * @param {Quaternion} b - The second quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiplyQuaternions( a, b ) { - } + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - this._workingColorSpace = colorSpace; + const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - }, + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - convert: function ( color, sourceColorSpace, targetColorSpace ) { + this._onChangeCallback(); - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { + return this; - return color; + } - } + /** + * Performs a spherical linear interpolation between quaternions. + * + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerp( qb, t ) { - const sourceToReference = COLOR_SPACES[ sourceColorSpace ].toReference; - const targetFromReference = COLOR_SPACES[ targetColorSpace ].fromReference; + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); - return targetFromReference( sourceToReference( color ) ); + const x = this._x, y = this._y, z = this._z, w = this._w; - }, + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - fromWorkingColorSpace: function ( color, targetColorSpace ) { + let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - return this.convert( color, this._workingColorSpace, targetColorSpace ); + if ( cosHalfTheta < 0 ) { - }, + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; - toWorkingColorSpace: function ( color, sourceColorSpace ) { + cosHalfTheta = - cosHalfTheta; - return this.convert( color, sourceColorSpace, this._workingColorSpace ); + } else { - }, + this.copy( qb ); - getPrimaries: function ( colorSpace ) { + } - return COLOR_SPACES[ colorSpace ].primaries; + if ( cosHalfTheta >= 1.0 ) { - }, + this._w = w; + this._x = x; + this._y = y; + this._z = z; - getTransfer: function ( colorSpace ) { + return this; - if ( colorSpace === NoColorSpace ) return LinearTransfer; + } - return COLOR_SPACES[ colorSpace ].transfer; + const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - }, + if ( sqrSinHalfTheta <= Number.EPSILON ) { -}; + const s = 1 - t; + this._w = s * w + t * this._w; + this._x = s * x + t * this._x; + this._y = s * y + t * this._y; + this._z = s * z + t * this._z; + this.normalize(); // normalize calls _onChangeCallback() -function SRGBToLinear( c ) { + return this; - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + } -} + const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); + const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; -function LinearToSRGB( c ) { + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; + this._onChangeCallback(); -} + return this; -let _canvas; + } -class ImageUtils { + /** + * Performs a spherical linear interpolation between the given quaternions + * and stores the result in this quaternion. + * + * @param {Quaternion} qa - The source quaternion. + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerpQuaternions( qa, qb, t ) { - static getDataURL( image ) { + return this.copy( qa ).slerp( qb, t ); - if ( /^data:/i.test( image.src ) ) { + } - return image.src; + /** + * Sets this quaternion to a uniformly random, normalized quaternion. + * + * @return {Quaternion} A reference to this quaternion. + */ + random() { - } + // Ken Shoemake + // Uniform random rotations + // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. - if ( typeof HTMLCanvasElement === 'undefined' ) { + const theta1 = 2 * Math.PI * Math.random(); + const theta2 = 2 * Math.PI * Math.random(); - return image.src; + const x0 = Math.random(); + const r1 = Math.sqrt( 1 - x0 ); + const r2 = Math.sqrt( x0 ); - } + return this.set( + r1 * Math.sin( theta1 ), + r1 * Math.cos( theta1 ), + r2 * Math.sin( theta2 ), + r2 * Math.cos( theta2 ), + ); - let canvas; + } - if ( image instanceof HTMLCanvasElement ) { + /** + * Returns `true` if this quaternion is equal with the given one. + * + * @param {Quaternion} quaternion - The quaternion to test for equality. + * @return {boolean} Whether this quaternion is equal with the given one. + */ + equals( quaternion ) { - canvas = image; + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - } else { + } - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); + /** + * Sets this quaternion's components from the given array. + * + * @param {Array} array - An array holding the quaternion component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Quaternion} A reference to this quaternion. + */ + fromArray( array, offset = 0 ) { - _canvas.width = image.width; - _canvas.height = image.height; + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; - const context = _canvas.getContext( '2d' ); + this._onChangeCallback(); - if ( image instanceof ImageData ) { + return this; - context.putImageData( image, 0, 0 ); + } - } else { + /** + * Writes the components of this quaternion to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the quaternion components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The quaternion components. + */ + toArray( array = [], offset = 0 ) { - context.drawImage( image, 0, 0, image.width, image.height ); + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; - } + return array; - canvas = _canvas; + } - } + /** + * Sets the components of this quaternion from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data. + * @param {number} index - The index into the attribute. + * @return {Quaternion} A reference to this quaternion. + */ + fromBufferAttribute( attribute, index ) { - if ( canvas.width > 2048 || canvas.height > 2048 ) { + this._x = attribute.getX( index ); + this._y = attribute.getY( index ); + this._z = attribute.getZ( index ); + this._w = attribute.getW( index ); - console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); + this._onChangeCallback(); - return canvas.toDataURL( 'image/jpeg', 0.6 ); + return this; - } else { + } - return canvas.toDataURL( 'image/png' ); + /** + * This methods defines the serialization result of this class. Returns the + * numerical elements of this quaternion in an array of format `[x, y, z, w]`. + * + * @return {Array} The serialized quaternion. + */ + toJSON() { - } + return this.toArray(); } - static sRGBToLinear( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + _onChange( callback ) { - const canvas = createElementNS( 'canvas' ); + this._onChangeCallback = callback; - canvas.width = image.width; - canvas.height = image.height; + return this; - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); + } - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; + _onChangeCallback() {} - for ( let i = 0; i < data.length; i ++ ) { + *[ Symbol.iterator ]() { - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; + yield this._x; + yield this._y; + yield this._z; + yield this._w; - } + } - context.putImageData( imageData, 0, 0 ); +} - return canvas; +/** + * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers + * (labeled x, y and z), which can be used to represent a number of things, such as: + * + * - A point in 3D space. + * - A direction and length in 3D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)` + * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`. + * - Any arbitrary ordered triplet of numbers. + * + * There are other things a 3D vector can be used to represent, such as + * momentum vectors and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y, z)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector3( 0, 1, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0) + * const b = new THREE.Vector3( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector3 { - } else if ( image.data ) { + /** + * Constructs a new 3D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + */ + constructor( x = 0, y = 0, z = 0 ) { - const data = image.data.slice( 0 ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector3.prototype.isVector3 = true; - for ( let i = 0; i < data.length; i ++ ) { + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; - } else { + } - // assuming float + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @return {Vector3} A reference to this vector. + */ + set( x, y, z ) { - data[ i ] = SRGBToLinear( data[ i ] ); + if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - } + this.x = x; + this.y = y; + this.z = z; - } + return this; - return { - data: data, - width: image.width, - height: image.height - }; + } - } else { + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector3} A reference to this vector. + */ + setScalar( scalar ) { - console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; + this.x = scalar; + this.y = scalar; + this.z = scalar; - } + return this; } -} + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector3} A reference to this vector. + */ + setX( x ) { -let _sourceId = 0; - -class Source { - - constructor( data = null ) { + this.x = x; - this.isSource = true; + return this; - Object.defineProperty( this, 'id', { value: _sourceId ++ } ); + } - this.uuid = generateUUID(); + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector3} A reference to this vector. + */ + setY( y ) { - this.data = data; - this.dataReady = true; + this.y = y; - this.version = 0; + return this; } - set needsUpdate( value ) { + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector3} A reference to this vector. + */ + setZ( z ) { - if ( value === true ) this.version ++; + this.z = z; - } + return this; - toJSON( meta ) { + } - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @param {number} value - The value to set. + * @return {Vector3} A reference to this vector. + */ + setComponent( index, value ) { - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { + switch ( index ) { - return meta.images[ this.uuid ]; + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); } - const output = { - uuid: this.uuid, - url: '' - }; - - const data = this.data; - - if ( data !== null ) { - - let url; + return this; - if ( Array.isArray( data ) ) { + } - // cube texture + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @return {number} A vector component value. + */ + getComponent( index ) { - url = []; + switch ( index ) { - for ( let i = 0, l = data.length; i < l; i ++ ) { + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); - if ( data[ i ].isDataTexture ) { + } - url.push( serializeImage( data[ i ].image ) ); + } - } else { + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector3} A clone of this instance. + */ + clone() { - url.push( serializeImage( data[ i ] ) ); + return new this.constructor( this.x, this.y, this.z ); - } + } - } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3} v - The vector to copy. + * @return {Vector3} A reference to this vector. + */ + copy( v ) { - } else { + this.x = v.x; + this.y = v.y; + this.z = v.z; - // texture + return this; - url = serializeImage( data ); + } - } + /** + * Adds the given vector to this instance. + * + * @param {Vector3} v - The vector to add. + * @return {Vector3} A reference to this vector. + */ + add( v ) { - output.url = url; + this.x += v.x; + this.y += v.y; + this.z += v.z; - } + return this; - if ( ! isRootObject ) { + } - meta.images[ this.uuid ] = output; + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector3} A reference to this vector. + */ + addScalar( s ) { - } + this.x += s; + this.y += s; + this.z += s; - return output; + return this; } -} - -function serializeImage( image ) { + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + addVectors( a, b ) { - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; - // default images + return this; - return ImageUtils.getDataURL( image ); + } - } else { + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector3|Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector3} A reference to this vector. + */ + addScaledVector( v, s ) { - if ( image.data ) { + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; - // images of DataTexture + return this; - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; + } - } else { + /** + * Subtracts the given vector from this instance. + * + * @param {Vector3} v - The vector to subtract. + * @return {Vector3} A reference to this vector. + */ + sub( v ) { - console.warn( 'THREE.Texture: Unable to serialize Texture.' ); - return {}; + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; - } + return this; } -} + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector3} A reference to this vector. + */ + subScalar( s ) { -let _textureId = 0; + this.x -= s; + this.y -= s; + this.z -= s; -class Texture extends EventDispatcher { + return this; - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { + } - super(); + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + subVectors( a, b ) { - this.isTexture = true; + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; - Object.defineProperty( this, 'id', { value: _textureId ++ } ); + return this; - this.uuid = generateUUID(); + } - this.name = ''; + /** + * Multiplies the given vector with this instance. + * + * @param {Vector3} v - The vector to multiply. + * @return {Vector3} A reference to this vector. + */ + multiply( v ) { - this.source = new Source( image ); - this.mipmaps = []; + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; - this.mapping = mapping; - this.channel = 0; + return this; - this.wrapS = wrapS; - this.wrapT = wrapT; + } - this.magFilter = magFilter; - this.minFilter = minFilter; + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector3} A reference to this vector. + */ + multiplyScalar( scalar ) { - this.anisotropy = anisotropy; + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; - this.format = format; - this.internalFormat = null; - this.type = type; + return this; - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; + } - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); + /** + * Multiplies the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + multiplyVectors( a, b ) { - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; - this.colorSpace = colorSpace; + return this; - this.userData = {}; + } - this.version = 0; - this.onUpdate = null; + /** + * Applies the given Euler rotation to this vector. + * + * @param {Euler} euler - The Euler angles. + * @return {Vector3} A reference to this vector. + */ + applyEuler( euler ) { - this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not - this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) + return this.applyQuaternion( _quaternion$2.setFromEuler( euler ) ); } - get image() { + /** + * Applies a rotation specified by an axis and an angle to this vector. + * + * @param {Vector3} axis - A normalized vector representing the rotation axis. + * @param {number} angle - The angle in radians. + * @return {Vector3} A reference to this vector. + */ + applyAxisAngle( axis, angle ) { - return this.source.data; + return this.applyQuaternion( _quaternion$2.setFromAxisAngle( axis, angle ) ); } - set image( value = null ) { - - this.source.data = value; + /** + * Multiplies this vector with the given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Vector3} A reference to this vector. + */ + applyMatrix3( m ) { - } + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - updateMatrix() { + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); + return this; } - clone() { + /** + * Multiplies this vector by the given normal matrix and normalizes + * the result. + * + * @param {Matrix3} m - The normal matrix. + * @return {Vector3} A reference to this vector. + */ + applyNormalMatrix( m ) { - return new this.constructor().copy( this ); + return this.applyMatrix3( m ).normalize(); } - copy( source ) { - - this.name = source.name; - - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); + /** + * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and + * divides by perspective. + * + * @param {Matrix4} m - The matrix to apply. + * @return {Vector3} A reference to this vector. + */ + applyMatrix4( m ) { - this.mapping = source.mapping; - this.channel = source.channel; + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; + const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - this.anisotropy = source.anisotropy; + return this; - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; + } - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; + /** + * Applies the given Quaternion to this vector. + * + * @param {Quaternion} q - The Quaternion. + * @return {Vector3} A reference to this vector. + */ + applyQuaternion( q ) { - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); + // quaternion q is assumed to have unit length - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; + const vx = this.x, vy = this.y, vz = this.z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - this.userData = JSON.parse( JSON.stringify( source.userData ) ); + // t = 2 * cross( q.xyz, v ); + const tx = 2 * ( qy * vz - qz * vy ); + const ty = 2 * ( qz * vx - qx * vz ); + const tz = 2 * ( qx * vy - qy * vx ); - this.needsUpdate = true; + // v + q.w * t + cross( q.xyz, t ); + this.x = vx + qw * tx + qy * tz - qz * ty; + this.y = vy + qw * ty + qz * tx - qx * tz; + this.z = vz + qw * tz + qx * ty - qy * tx; return this; } - toJSON( meta ) { + /** + * Projects this vector from world space into the camera's normalized + * device coordinate (NDC) space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + project( camera ) { - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { + } - return meta.textures[ this.uuid ]; + /** + * Unprojects this vector from the camera's normalized device coordinate (NDC) + * space into world space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + unproject( camera ) { - } + return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - const output = { + } - metadata: { - version: 4.6, - type: 'Texture', - generator: 'Texture.toJSON' - }, + /** + * Transforms the direction of this vector by a matrix (the upper left 3 x 3 + * subset of the given 4x4 matrix and then normalizes the result. + * + * @param {Matrix4} m - The matrix. + * @return {Vector3} A reference to this vector. + */ + transformDirection( m ) { - uuid: this.uuid, - name: this.name, + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction - image: this.source.toJSON( meta ).uuid, + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - mapping: this.mapping, - channel: this.channel, + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, + return this.normalize(); - wrap: [ this.wrapS, this.wrapT ], + } - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - colorSpace: this.colorSpace, + /** + * Divides this instance by the given vector. + * + * @param {Vector3} v - The vector to divide. + * @return {Vector3} A reference to this vector. + */ + divide( v ) { - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; - flipY: this.flipY, + return this; - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment + } - }; + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector3} A reference to this vector. + */ + divideScalar( scalar ) { - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; + return this.multiplyScalar( 1 / scalar ); - if ( ! isRootObject ) { + } - meta.textures[ this.uuid ] = output; + /** + * If this vector's x, y or z value is greater than the given vector's x, y or z + * value, replace that value with the corresponding min value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + min( v ) { - } + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); - return output; + return this; } - dispose() { + /** + * If this vector's x, y or z value is less than the given vector's x, y or z + * value, replace that value with the corresponding max value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + max( v ) { - this.dispatchEvent( { type: 'dispose' } ); + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + + return this; } - transformUv( uv ) { + /** + * If this vector's x, y or z value is greater than the max vector's x, y or z + * value, it is replaced by the corresponding value. + * If this vector's x, y or z value is less than the min vector's x, y or z value, + * it is replaced by the corresponding value. + * + * @param {Vector3} min - The minimum x, y and z values. + * @param {Vector3} max - The maximum x, y and z values in the desired range. + * @return {Vector3} A reference to this vector. + */ + clamp( min, max ) { - if ( this.mapping !== UVMapping ) return uv; + // assumes min < max, componentwise - uv.applyMatrix3( this.matrix ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); - if ( uv.x < 0 || uv.x > 1 ) { + return this; - switch ( this.wrapS ) { + } - case RepeatWrapping: + /** + * If this vector's x, y or z values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y or z values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { - uv.x = uv.x - Math.floor( uv.x ); - break; + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); - case ClampToEdgeWrapping: + return this; - uv.x = uv.x < 0 ? 0 : 1; - break; + } - case MirroredRepeatWrapping: + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampLength( min, max ) { - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { + const length = this.length(); - uv.x = Math.ceil( uv.x ) - uv.x; + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - } else { + } - uv.x = uv.x - Math.floor( uv.x ); + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + floor() { - } + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); - break; + return this; - } + } - } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + ceil() { - if ( uv.y < 0 || uv.y > 1 ) { + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); - switch ( this.wrapT ) { + return this; - case RepeatWrapping: + } - uv.y = uv.y - Math.floor( uv.y ); - break; + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector3} A reference to this vector. + */ + round() { - case ClampToEdgeWrapping: + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); - uv.y = uv.y < 0 ? 0 : 1; - break; + return this; - case MirroredRepeatWrapping: + } - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector3} A reference to this vector. + */ + roundToZero() { - uv.y = Math.ceil( uv.y ) - uv.y; + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); - } else { + return this; - uv.y = uv.y - Math.floor( uv.y ); + } - } + /** + * Inverts this vector - i.e. sets x = -x, y = -y and z = -z. + * + * @return {Vector3} A reference to this vector. + */ + negate() { - break; + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; - } + return this; - } + } - if ( this.flipY ) { + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { - uv.y = 1 - uv.y; + return this.x * v.x + this.y * v.y + this.z * v.z; - } + } - return uv; + // TODO lengthSquared? - } + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { - set needsUpdate( value ) { + return this.x * this.x + this.y * this.y + this.z * this.z; - if ( value === true ) { + } - this.version ++; - this.source.needsUpdate = true; + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z). + * + * @return {number} The length of this vector. + */ + length() { - } + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); } -} - -Texture.DEFAULT_IMAGE = null; -Texture.DEFAULT_MAPPING = UVMapping; -Texture.DEFAULT_ANISOTROPY = 1; + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { -class Vector4 { + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - constructor( x = 0, y = 0, z = 0, w = 1 ) { + } - Vector4.prototype.isVector4 = true; + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector3} A reference to this vector. + */ + normalize() { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + return this.divideScalar( this.length() || 1 ); } - get width() { + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector3} A reference to this vector. + */ + setLength( length ) { - return this.z; + return this.normalize().multiplyScalar( length ); } - set width( value ) { + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector3} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerp( v, alpha ) { - this.z = value; + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + + return this; } - get height() { + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector3} v1 - The first vector. + * @param {Vector3} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { - return this.w; + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; + + return this; } - set height( value ) { + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the cross product with. + * @return {Vector3} The result of the cross product. + */ + cross( v ) { - this.w = value; + return this.crossVectors( this, v ); } - set( x, y, z, w ) { + /** + * Calculates the cross product of the given vectors and stores the result + * in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + crossVectors( a, b ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; return this; } - setScalar( scalar ) { + /** + * Projects this vector onto the given one. + * + * @param {Vector3} v - The vector to project to. + * @return {Vector3} A reference to this vector. + */ + projectOnVector( v ) { - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; + const denominator = v.lengthSq(); - return this; + if ( denominator === 0 ) return this.set( 0, 0, 0 ); + + const scalar = v.dot( this ) / denominator; + + return this.copy( v ).multiplyScalar( scalar ); } - setX( x ) { + /** + * Projects this vector onto a plane by subtracting this + * vector projected onto the plane's normal from this vector. + * + * @param {Vector3} planeNormal - The plane normal. + * @return {Vector3} A reference to this vector. + */ + projectOnPlane( planeNormal ) { - this.x = x; + _vector$8.copy( this ).projectOnVector( planeNormal ); - return this; + return this.sub( _vector$8 ); } - setY( y ) { - - this.y = y; + /** + * Reflects this vector off a plane orthogonal to the given normal vector. + * + * @param {Vector3} normal - The (normalized) normal vector. + * @return {Vector3} A reference to this vector. + */ + reflect( normal ) { - return this; + return this.sub( _vector$8.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector3} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { - setZ( z ) { + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - this.z = z; + if ( denominator === 0 ) return Math.PI / 2; - return this; + const theta = this.dot( v ) / denominator; + + // clamp, to handle numerical problems + + return Math.acos( clamp( theta, -1, 1 ) ); } - setW( w ) { + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { - this.w = w; + return Math.sqrt( this.distanceToSquared( v ) ); - return this; + } + + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector3} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { + + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + + return dx * dx + dy * dy + dz * dz; } - setComponent( index, value ) { + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { - switch ( index ) { + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); + } - } + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {Spherical} s - The spherical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromSpherical( s ) { - return this; + return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); } - getComponent( index ) { + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {number} radius - The radius. + * @param {number} phi - The phi angle in radians. + * @param {number} theta - The theta angle in radians. + * @return {Vector3} A reference to this vector. + */ + setFromSphericalCoords( radius, phi, theta ) { - switch ( index ) { + const sinPhiRadius = Math.sin( phi ) * radius; - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); + this.x = sinPhiRadius * Math.sin( theta ); + this.y = Math.cos( phi ) * radius; + this.z = sinPhiRadius * Math.cos( theta ); - } + return this; } - clone() { + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {Cylindrical} c - The cylindrical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromCylindrical( c ) { - return new this.constructor( this.x, this.y, this.z, this.w ); + return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); } - copy( v ) { + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle in radians. + * @param {number} y - The y value. + * @return {Vector3} A reference to this vector. + */ + setFromCylindricalCoords( radius, theta, y ) { - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; + this.x = radius * Math.sin( theta ); + this.y = y; + this.z = radius * Math.cos( theta ); return this; } - add( v ) { + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixPosition( m ) { - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; return this; } - addScalar( s ) { + /** + * Sets the vector components to the scale elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixScale( m ) { - this.x += s; - this.y += s; - this.z += s; - this.w += s; + const sx = this.setFromMatrixColumn( m, 0 ).length(); + const sy = this.setFromMatrixColumn( m, 1 ).length(); + const sz = this.setFromMatrixColumn( m, 2 ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; return this; } - addVectors( a, b ) { + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix4} m - The 4x4 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixColumn( m, index ) { - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; + return this.fromArray( m.elements, index * 4 ); - return this; + } + + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix3} m - The 3x3 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrix3Column( m, index ) { + + return this.fromArray( m.elements, index * 3 ); } - addScaledVector( v, s ) { + /** + * Sets the vector components from the given Euler angles. + * + * @param {Euler} e - The Euler angles to set. + * @return {Vector3} A reference to this vector. + */ + setFromEuler( e ) { - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; + this.x = e._x; + this.y = e._y; + this.z = e._z; return this; } - sub( v ) { + /** + * Sets the vector components from the RGB components of the + * given color. + * + * @param {Color} c - The color to set. + * @return {Vector3} A reference to this vector. + */ + setFromColor( c ) { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; + this.x = c.r; + this.y = c.g; + this.z = c.b; return this; } - subScalar( s ) { + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector3} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + } + + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]` + * and z value to be `array[ offset + 2 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector3} A reference to this vector. + */ + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; return this; } - subVectors( a, b ) { + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; - return this; + return array; } - multiply( v ) { + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector3} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); return this; } - multiplyScalar( scalar ) { + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector3} A reference to this vector. + */ + random() { - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); return this; } - applyMatrix4( m ) { + /** + * Sets this vector to a uniformly random point on a unit sphere. + * + * @return {Vector3} A reference to this vector. + */ + randomDirection() { - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; + // https://mathworld.wolfram.com/SpherePointPicking.html - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; + const theta = Math.random() * Math.PI * 2; + const u = Math.random() * 2 - 1; + const c = Math.sqrt( 1 - u * u ); + + this.x = c * Math.cos( theta ); + this.y = u; + this.z = c * Math.sin( theta ); return this; } - divideScalar( scalar ) { + *[ Symbol.iterator ]() { - return this.multiplyScalar( 1 / scalar ); + yield this.x; + yield this.y; + yield this.z; } - setAxisAngleFromQuaternion( q ) { +} - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm +const _vector$8 = /*@__PURE__*/ new Vector3(); +const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - // q is assumed to be normalized +/** + * Represents a 3x3 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix(); + * m.set( 11, 12, 13, + * 21, 22, 23, + * 31, 32, 33 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, + * 12, 22, 32, + * 13, 23, 33 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix3 { - this.w = 2 * Math.acos( q.w ); + /** + * Constructs a new 3x3 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + */ + constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - const s = Math.sqrt( 1 - q.w * q.w ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix3.prototype.isMatrix3 = true; - if ( s < 0.0001 ) { + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ - this.x = 1; - this.y = 0; - this.z = 0; + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - } else { + ]; - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; + if ( n11 !== undefined ) { - } + this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - return this; + } } - setAxisAngleFromRotationMatrix( m ) { + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @return {Matrix3} A reference to this matrix. + */ + set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + const te = this.elements; - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; + te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; + te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + return this; - te = m.elements, + } - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + /** + * Sets this matrix to the 3x3 identity matrix. + * + * @return {Matrix3} A reference to this matrix. + */ + identity() { - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { + this.set( - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + ); - // this singularity is identity matrix so angle = 0 + return this; - this.set( 1, 0, 0, 0 ); + } - return this; // zero angle, arbitrary axis + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix3} m - The matrix to copy. + * @return {Matrix3} A reference to this matrix. + */ + copy( m ) { - } + const te = this.elements; + const me = m.elements; - // otherwise this singularity is angle = 180 + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; + te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; + te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - angle = Math.PI; + return this; - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; + } - if ( ( xx > yy ) && ( xx > zz ) ) { + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix3} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { - // m11 is the largest diagonal term - - if ( xx < epsilon ) { + xAxis.setFromMatrix3Column( this, 0 ); + yAxis.setFromMatrix3Column( this, 1 ); + zAxis.setFromMatrix3Column( this, 2 ); - x = 0; - y = 0.707106781; - z = 0.707106781; + return this; - } else { + } - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; + /** + * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + setFromMatrix4( m ) { - } + const me = m.elements; - } else if ( yy > zz ) { + this.set( - // m22 is the largest diagonal term + me[ 0 ], me[ 4 ], me[ 8 ], + me[ 1 ], me[ 5 ], me[ 9 ], + me[ 2 ], me[ 6 ], me[ 10 ] - if ( yy < epsilon ) { + ); - x = 0.707106781; - y = 0; - z = 0.707106781; + return this; - } else { + } - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; + /** + * Post-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + multiply( m ) { - } + return this.multiplyMatrices( this, m ); - } else { + } - // m33 is the largest diagonal term so base result on this + /** + * Pre-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + premultiply( m ) { - if ( zz < epsilon ) { + return this.multiplyMatrices( m, this ); - x = 0.707106781; - y = 0.707106781; - z = 0; + } - } else { + /** + * Multiples the given 3x3 matrices and stores the result + * in this matrix. + * + * @param {Matrix3} a - The first matrix. + * @param {Matrix3} b - The second matrix. + * @return {Matrix3} A reference to this matrix. + */ + multiplyMatrices( a, b ) { - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; + const ae = a.elements; + const be = b.elements; + const te = this.elements; - } + const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; + const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; + const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - } + const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; + const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; + const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - this.set( x, y, z, angle ); + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; + te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; + te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - return this; // return 180 deg rotation + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; + te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; + te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - } + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; + te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; + te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - // as we have reached here there are no singularities so we can handle normally + return this; - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + } - if ( Math.abs( s ) < 0.001 ) s = 1; + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix3} A reference to this matrix. + */ + multiplyScalar( s ) { - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case + const te = this.elements; - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; return this; } - min( v ) { + /** + * Computes and returns the determinant of this matrix. + * + * @return {number} The determinant. + */ + determinant() { - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); + const te = this.elements; - return this; + const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; } - max( v ) { + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix3} A reference to this matrix. + */ + invert() { - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); + const te = this.elements, - return this; + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], + n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], + n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - } + t11 = n33 * n22 - n32 * n23, + t12 = n32 * n13 - n33 * n12, + t13 = n23 * n12 - n22 * n13, - clamp( min, max ) { + det = n11 * t11 + n21 * t12 + n31 * t13; - // assumes min < max, componentwise + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + + const detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; + te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); + te[ 3 ] = t12 * detInv; + te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; + te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + + te[ 6 ] = t13 * detInv; + te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; + te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; return this; } - clampScalar( minVal, maxVal ) { + /** + * Transposes this matrix in place. + * + * @return {Matrix3} A reference to this matrix. + */ + transpose() { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); + let tmp; + const m = this.elements; + + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; return this; } - clampLength( min, max ) { - - const length = this.length(); + /** + * Computes the normal matrix which is the inverse transpose of the upper + * left 3x3 portion of the given 4x4 matrix. + * + * @param {Matrix4} matrix4 - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + getNormalMatrix( matrix4 ) { - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.setFromMatrix4( matrix4 ).invert().transpose(); } - floor() { + /** + * Transposes this matrix into the supplied array, and returns itself unchanged. + * + * @param {Array} r - An array to store the transposed matrix elements. + * @return {Matrix3} A reference to this matrix. + */ + transposeIntoArray( r ) { - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); + const m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; return this; } - ceil() { + /** + * Sets the UV transform matrix from offset, repeat, rotation, and center. + * + * @param {number} tx - Offset x. + * @param {number} ty - Offset y. + * @param {number} sx - Repeat x. + * @param {number} sy - Repeat y. + * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise. + * @param {number} cx - Center x of rotation. + * @param {number} cy - Center y of rotation + * @return {Matrix3} A reference to this matrix. + */ + setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); + const c = Math.cos( rotation ); + const s = Math.sin( rotation ); + + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 1 + ); return this; } - round() { + /** + * Scales this matrix with the given scalar values. + * + * @param {number} sx - The amount to scale in the X axis. + * @param {number} sy - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + scale( sx, sy ) { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); + this.premultiply( _m3.makeScale( sx, sy ) ); return this; } - roundToZero() { + /** + * Rotates this matrix by the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + rotate( theta ) { - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - this.z = Math.trunc( this.z ); - this.w = Math.trunc( this.w ); + this.premultiply( _m3.makeRotation( - theta ) ); return this; } - negate() { + /** + * Translates this matrix by the given scalar values. + * + * @param {number} tx - The amount to translate in the X axis. + * @param {number} ty - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + translate( tx, ty ) { - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; + this.premultiply( _m3.makeTranslation( tx, ty ) ); return this; } - dot( v ) { + // for 2D Transforms - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + /** + * Sets this matrix as a 2D translation transform. + * + * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeTranslation( x, y ) { - } + if ( x.isVector2 ) { - lengthSq() { + this.set( - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + 1, 0, x.x, + 0, 1, x.y, + 0, 0, 1 - } + ); - length() { + } else { - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + this.set( - } + 1, 0, x, + 0, 1, y, + 0, 0, 1 - manhattanLength() { + ); - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + } + + return this; } - normalize() { + /** + * Sets this matrix as a 2D rotational transformation. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + makeRotation( theta ) { - return this.divideScalar( this.length() || 1 ); + // counterclockwise - } + const c = Math.cos( theta ); + const s = Math.sin( theta ); - setLength( length ) { + this.set( - return this.normalize().multiplyScalar( length ); + c, - s, 0, + s, c, 0, + 0, 0, 1 + + ); + + return this; } - lerp( v, alpha ) { + /** + * Sets this matrix as a 2D scale transform. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeScale( x, y ) { - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; + this.set( + + x, 0, 0, + 0, y, 0, + 0, 0, 1 + + ); return this; } - lerpVectors( v1, v2, alpha ) { + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix3} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; + const te = this.elements; + const me = matrix.elements; - return this; + for ( let i = 0; i < 9; i ++ ) { - } + if ( te[ i ] !== me[ i ] ) return false; - equals( v ) { + } - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + return true; } + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix3} A reference to this matrix. + */ fromArray( array, offset = 0 ) { - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; + for ( let i = 0; i < 9; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } return this; } + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ toArray( array = [], offset = 0 ) { - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; + const te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; return array; } - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix3} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().fromArray( this.elements ); } - random() { +} - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); +const _m3 = /*@__PURE__*/ new Matrix3(); - return this; +function arrayNeedsUint32( array ) { - } + // assumes larger values usually on last - *[ Symbol.iterator ]() { + for ( let i = array.length - 1; i >= 0; -- i ) { - yield this.x; - yield this.y; - yield this.z; - yield this.w; + if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 } + return false; + } -/* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers -*/ -class RenderTarget extends EventDispatcher { +function createElementNS( name ) { - constructor( width = 1, height = 1, options = {} ) { + return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - super(); +} - this.isRenderTarget = true; +function createCanvasElement() { - this.width = width; - this.height = height; - this.depth = 1; + const canvas = createElementNS( 'canvas' ); + canvas.style.display = 'block'; + return canvas; - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; +} - this.viewport = new Vector4( 0, 0, width, height ); +const _cache = {}; - const image = { width: width, height: height, depth: 1 }; +function warnOnce( message ) { - options = Object.assign( { - generateMipmaps: false, - internalFormat: null, - minFilter: LinearFilter, - depthBuffer: true, - stencilBuffer: false, - depthTexture: null, - samples: 0, - count: 1 - }, options ); + if ( message in _cache ) return; - const texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); + _cache[ message ] = true; - texture.flipY = false; - texture.generateMipmaps = options.generateMipmaps; - texture.internalFormat = options.internalFormat; + console.warn( message ); - this.textures = []; +} - const count = options.count; - for ( let i = 0; i < count; i ++ ) { +function probeAsync( gl, sync, interval ) { - this.textures[ i ] = texture.clone(); - this.textures[ i ].isRenderTargetTexture = true; + return new Promise( function ( resolve, reject ) { - } + function probe() { - this.depthBuffer = options.depthBuffer; - this.stencilBuffer = options.stencilBuffer; + switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) { - this.depthTexture = options.depthTexture; + case gl.WAIT_FAILED: + reject(); + break; - this.samples = options.samples; + case gl.TIMEOUT_EXPIRED: + setTimeout( probe, interval ); + break; - } + default: + resolve(); - get texture() { + } - return this.textures[ 0 ]; + } - } + setTimeout( probe, interval ); - set texture( value ) { + } ); - this.textures[ 0 ] = value; +} - } +const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4123908, 0.3575843, 0.1804808, + 0.2126390, 0.7151687, 0.0721923, + 0.0193308, 0.1191948, 0.9505322 +); - setSize( width, height, depth = 1 ) { +const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( + 3.2409699, -1.5373832, -0.4986108, + -0.9692436, 1.8759675, 0.0415551, + 0.0556301, -0.203977, 1.0569715 +); - if ( this.width !== width || this.height !== height || this.depth !== depth ) { +function createColorManagement() { - this.width = width; - this.height = height; - this.depth = depth; + const ColorManagement = { - for ( let i = 0, il = this.textures.length; i < il; i ++ ) { + enabled: true, - this.textures[ i ].image.width = width; - this.textures[ i ].image.height = height; - this.textures[ i ].image.depth = depth; + workingColorSpace: LinearSRGBColorSpace, - } + /** + * Implementations of supported color spaces. + * + * Required: + * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] + * - whitePoint: reference white [ x y ] + * - transfer: transfer function (pre-defined) + * - toXYZ: Matrix3 RGB to XYZ transform + * - fromXYZ: Matrix3 XYZ to RGB transform + * - luminanceCoefficients: RGB luminance coefficients + * + * Optional: + * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace, toneMappingMode: 'extended' | 'standard' } + * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } + * + * Reference: + * - https://www.russellcottrell.com/photo/matrixCalculator.htm + */ + spaces: {}, - this.dispose(); + convert: function ( color, sourceColorSpace, targetColorSpace ) { - } + if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); + return color; - } + } - clone() { + if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { - return new this.constructor().copy( this ); + color.r = SRGBToLinear( color.r ); + color.g = SRGBToLinear( color.g ); + color.b = SRGBToLinear( color.b ); - } + } - copy( source ) { + if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { - this.width = source.width; - this.height = source.height; - this.depth = source.depth; + color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); + color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; + } - this.viewport.copy( source.viewport ); + if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { - this.textures.length = 0; + color.r = LinearToSRGB( color.r ); + color.g = LinearToSRGB( color.g ); + color.b = LinearToSRGB( color.b ); - for ( let i = 0, il = source.textures.length; i < il; i ++ ) { + } - this.textures[ i ] = source.textures[ i ].clone(); - this.textures[ i ].isRenderTargetTexture = true; + return color; - } + }, - // ensure image object is not shared, see #20328 + workingToColorSpace: function ( color, targetColorSpace ) { - const image = Object.assign( {}, source.texture.image ); - this.texture.source = new Source( image ); + return this.convert( color, this.workingColorSpace, targetColorSpace ); - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; + }, - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + colorSpaceToWorking: function ( color, sourceColorSpace ) { - this.samples = source.samples; + return this.convert( color, sourceColorSpace, this.workingColorSpace ); - return this; + }, - } + getPrimaries: function ( colorSpace ) { - dispose() { + return this.spaces[ colorSpace ].primaries; - this.dispatchEvent( { type: 'dispose' } ); + }, - } + getTransfer: function ( colorSpace ) { -} + if ( colorSpace === NoColorSpace ) return LinearTransfer; -class WebGLRenderTarget extends RenderTarget { + return this.spaces[ colorSpace ].transfer; - constructor( width = 1, height = 1, options = {} ) { + }, - super( width, height, options ); + getToneMappingMode: function ( colorSpace ) { - this.isWebGLRenderTarget = true; + return this.spaces[ colorSpace ].outputColorSpaceConfig.toneMappingMode || 'standard'; - } + }, -} + getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { -const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; + return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); -const _hslA = { h: 0, s: 0, l: 0 }; -const _hslB = { h: 0, s: 0, l: 0 }; + }, -function hue2rgb( p, q, t ) { + define: function ( colorSpaces ) { - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; + Object.assign( this.spaces, colorSpaces ); -} + }, -class Color { + // Internal APIs - constructor( r, g, b ) { + _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { - this.isColor = true; + return targetMatrix + .copy( this.spaces[ sourceColorSpace ].toXYZ ) + .multiply( this.spaces[ targetColorSpace ].fromXYZ ); - this.r = 1; - this.g = 1; - this.b = 1; + }, - return this.set( r, g, b ); + _getDrawingBufferColorSpace: function ( colorSpace ) { - } + return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; - set( r, g, b ) { + }, - if ( g === undefined && b === undefined ) { + _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { - // r is THREE.Color, hex or string + return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; - const value = r; + }, - if ( value && value.isColor ) { + // Deprecated - this.copy( value ); + fromWorkingColorSpace: function ( color, targetColorSpace ) { - } else if ( typeof value === 'number' ) { + warnOnce( 'THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177 - this.setHex( value ); + return ColorManagement.workingToColorSpace( color, targetColorSpace ); - } else if ( typeof value === 'string' ) { + }, - this.setStyle( value ); + toWorkingColorSpace: function ( color, sourceColorSpace ) { - } + warnOnce( 'THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177 - } else { + return ColorManagement.colorSpaceToWorking( color, sourceColorSpace ); - this.setRGB( r, g, b ); + }, - } + }; - return this; + /****************************************************************************** + * sRGB definitions + */ - } + const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; + const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; + const D65 = [ 0.3127, 0.3290 ]; + + ColorManagement.define( { + + [ LinearSRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace }, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - setScalar( scalar ) { + [ SRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - this.r = scalar; - this.g = scalar; - this.b = scalar; + } ); - return this; + return ColorManagement; - } +} - setHex( hex, colorSpace = SRGBColorSpace ) { +const ColorManagement = /*@__PURE__*/ createColorManagement(); - hex = Math.floor( hex ); +function SRGBToLinear( c ) { - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - ColorManagement.toWorkingColorSpace( this, colorSpace ); +} - return this; +function LinearToSRGB( c ) { - } + return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { +} - this.r = r; - this.g = g; - this.b = b; +let _canvas; - ColorManagement.toWorkingColorSpace( this, colorSpace ); +/** + * A class containing utility functions for images. + * + * @hideconstructor + */ +class ImageUtils { - return this; + /** + * Returns a data URI containing a representation of the given image. + * + * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object. + * @param {string} [type='image/png'] - Indicates the image format. + * @return {string} The data URI. + */ + static getDataURL( image, type = 'image/png' ) { - } + if ( /^data:/i.test( image.src ) ) { - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { + return image.src; - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); + } - if ( s === 0 ) { + if ( typeof HTMLCanvasElement === 'undefined' ) { - this.r = this.g = this.b = l; + return image.src; - } else { + } - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; + let canvas; - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); + if ( image instanceof HTMLCanvasElement ) { - } + canvas = image; - ColorManagement.toWorkingColorSpace( this, colorSpace ); + } else { - return this; + if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - } + _canvas.width = image.width; + _canvas.height = image.height; - setStyle( style, colorSpace = SRGBColorSpace ) { + const context = _canvas.getContext( '2d' ); - function handleAlpha( string ) { + if ( image instanceof ImageData ) { - if ( string === undefined ) return; + context.putImageData( image, 0, 0 ); - if ( parseFloat( string ) < 1 ) { + } else { - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); + context.drawImage( image, 0, 0, image.width, image.height ); } + canvas = _canvas; + } + return canvas.toDataURL( type ); - let m; + } - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { + /** + * Converts the given sRGB image data to linear color space. + * + * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object. + * @return {HTMLCanvasElement|Object} The converted image. + */ + static sRGBToLinear( image ) { - // rgb / hsl + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; + const canvas = createElementNS( 'canvas' ); - switch ( name ) { + canvas.width = image.width; + canvas.height = image.height; - case 'rgb': - case 'rgba': + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height ); - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + const imageData = context.getImageData( 0, 0, image.width, image.height ); + const data = imageData.data; - // rgb(255,0,0) rgba(255,0,0,0.5) + for ( let i = 0; i < data.length; i ++ ) { - handleAlpha( color[ 4 ] ); + data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); + } - } + context.putImageData( imageData, 0, 0 ); - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + return canvas; - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) + } else if ( image.data ) { - handleAlpha( color[ 4 ] ); + const data = image.data.slice( 0 ); - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); + for ( let i = 0; i < data.length; i ++ ) { - } + if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - break; + data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - case 'hsl': - case 'hsla': + } else { - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + // assuming float - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) + data[ i ] = SRGBToLinear( data[ i ] ); - handleAlpha( color[ 4 ] ); + } - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); + } - } + return { + data: data, + width: image.width, + height: image.height + }; - break; + } else { - default: + console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); + return image; - console.warn( 'THREE.Color: Unknown color model ' + style ); + } - } + } - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { +} - // hex color +let _sourceId = 0; - const hex = m[ 1 ]; - const size = hex.length; +/** + * Represents the data source of a texture. + * + * The main purpose of this class is to decouple the data definition from the texture + * definition so the same data can be used with multiple texture instances. + */ +class Source { - if ( size === 3 ) { + /** + * Constructs a new video texture. + * + * @param {any} [data=null] - The data definition of a texture. + */ + constructor( data = null ) { - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSource = true; - } else if ( size === 6 ) { + /** + * The ID of the source. + * + * @name Source#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _sourceId ++ } ); - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); + /** + * The UUID of the source. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - } else { + /** + * The data definition of a texture. + * + * @type {any} + */ + this.data = data; - console.warn( 'THREE.Color: Invalid hex color ' + style ); + /** + * This property is only relevant when {@link Source#needsUpdate} is set to `true` and + * provides more control on how texture data should be processed. When `dataReady` is set + * to `false`, the engine performs the memory allocation (if necessary) but does not transfer + * the data into the GPU memory. + * + * @type {boolean} + * @default true + */ + this.dataReady = true; - } + /** + * This starts at `0` and counts how many times {@link Source#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - } else if ( style && style.length > 0 ) { + } - return this.setColorName( style, colorSpace ); + /** + * Returns the dimensions of the source into the given target vector. + * + * @param {(Vector2|Vector3)} target - The target object the result is written into. + * @return {(Vector2|Vector3)} The dimensions of the source. + */ + getSize( target ) { - } + const data = this.data; - return this; + if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( data instanceof HTMLVideoElement ) ) { - } + target.set( data.videoWidth, data.videoHeight, 0 ); - setColorName( style, colorSpace = SRGBColorSpace ) { + } else if ( data instanceof VideoFrame ) { - // color keywords - const hex = _colorKeywords[ style.toLowerCase() ]; + target.set( data.displayHeight, data.displayWidth, 0 ); - if ( hex !== undefined ) { + } else if ( data !== null ) { - // red - this.setHex( hex, colorSpace ); + target.set( data.width, data.height, data.depth || 0 ); } else { - // unknown color - console.warn( 'THREE.Color: Unknown color ' + style ); + target.set( 0, 0, 0 ); } - return this; + return target; } - clone() { + /** + * When the property is set to `true`, the engine allocates the memory + * for the texture (if necessary) and triggers the actual texture upload + * to the GPU next time the source is used. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - return new this.constructor( this.r, this.g, this.b ); + if ( value === true ) this.version ++; } - copy( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; + /** + * Serializes the source into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized source. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - return this; + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - } + if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - copySRGBToLinear( color ) { + return meta.images[ this.uuid ]; - this.r = SRGBToLinear( color.r ); - this.g = SRGBToLinear( color.g ); - this.b = SRGBToLinear( color.b ); + } - return this; + const output = { + uuid: this.uuid, + url: '' + }; - } + const data = this.data; - copyLinearToSRGB( color ) { + if ( data !== null ) { - this.r = LinearToSRGB( color.r ); - this.g = LinearToSRGB( color.g ); - this.b = LinearToSRGB( color.b ); + let url; - return this; + if ( Array.isArray( data ) ) { - } + // cube texture - convertSRGBToLinear() { + url = []; - this.copySRGBToLinear( this ); + for ( let i = 0, l = data.length; i < l; i ++ ) { - return this; + if ( data[ i ].isDataTexture ) { - } + url.push( serializeImage( data[ i ].image ) ); - convertLinearToSRGB() { + } else { - this.copyLinearToSRGB( this ); + url.push( serializeImage( data[ i ] ) ); - return this; + } - } + } - getHex( colorSpace = SRGBColorSpace ) { + } else { - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + // texture - return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); + url = serializeImage( data ); - } + } - getHexString( colorSpace = SRGBColorSpace ) { + output.url = url; - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); + } - } + if ( ! isRootObject ) { - getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { + meta.images[ this.uuid ] = output; - // h,s,l ranges are in 0.0 - 1.0 + } - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + return output; - const r = _color.r, g = _color.g, b = _color.b; + } - const max = Math.max( r, g, b ); - const min = Math.min( r, g, b ); +} - let hue, saturation; - const lightness = ( min + max ) / 2.0; +function serializeImage( image ) { - if ( min === max ) { + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - hue = 0; - saturation = 0; + // default images - } else { + return ImageUtils.getDataURL( image ); - const delta = max - min; + } else { - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + if ( image.data ) { - switch ( max ) { + // images of DataTexture - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; + return { + data: Array.from( image.data ), + width: image.width, + height: image.height, + type: image.data.constructor.name + }; - } + } else { - hue /= 6; + console.warn( 'THREE.Texture: Unable to serialize Texture.' ); + return {}; } - target.h = hue; - target.s = saturation; - target.l = lightness; - - return target; - } - getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { +} - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); +let _textureId = 0; - target.r = _color.r; - target.g = _color.g; - target.b = _color.b; +const _tempVec3 = /*@__PURE__*/ new Vector3(); - return target; +/** + * Base class for all textures. + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments EventDispatcher + */ +class Texture extends EventDispatcher { - } + /** + * Constructs a new texture. + * + * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - getStyle( colorSpace = SRGBColorSpace ) { + super(); - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture = true; - const r = _color.r, g = _color.g, b = _color.b; + /** + * The ID of the texture. + * + * @name Texture#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _textureId ++ } ); - if ( colorSpace !== SRGBColorSpace ) { + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). - return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; - } + /** + * The data definition of a texture. A reference to the data source can be + * shared across textures. This is often useful in context of spritesheets + * where multiple textures render the same data but with different texture + * transformations. + * + * @type {Source} + */ + this.source = new Source( image ); - return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; + /** + * An array holding user-defined mipmaps. + * + * @type {Array} + */ + this.mipmaps = []; - } + /** + * How the texture is applied to the object. The value `UVMapping` + * is the default, where texture or uv coordinates are used to apply the map. + * + * @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)} + * @default UVMapping + */ + this.mapping = mapping; - offsetHSL( h, s, l ) { + /** + * Lets you select the uv attribute to map the texture to. `0` for `uv`, + * `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. + * + * @type {number} + * @default 0 + */ + this.channel = 0; - this.getHSL( _hslA ); + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *U* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapS = wrapS; - return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *V* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapT = wrapT; - } + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearFilter + */ + this.magFilter = magFilter; - add( color ) { + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearMipmapLinearFilter + */ + this.minFilter = minFilter; - this.r += color.r; - this.g += color.g; - this.b += color.b; + /** + * The number of samples taken along the axis through the pixel that has the + * highest density of texels. By default, this value is `1`. A higher value + * gives a less blurry result than a basic mipmap, at the cost of more + * texture samples being used. + * + * @type {number} + * @default 0 + */ + this.anisotropy = anisotropy; - return this; + /** + * The format of the texture. + * + * @type {number} + * @default RGBAFormat + */ + this.format = format; - } + /** + * The default internal format is derived from {@link Texture#format} and {@link Texture#type} and + * defines how the texture data is going to be stored on the GPU. + * + * This property allows to overwrite the default format. + * + * @type {?string} + * @default null + */ + this.internalFormat = null; - addColors( color1, color2 ) { + /** + * The data type of the texture. + * + * @type {number} + * @default UnsignedByteType + */ + this.type = type; - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; + /** + * How much a single repetition of the texture is offset from the beginning, + * in each direction U and V. Typical range is `0.0` to `1.0`. + * + * @type {Vector2} + * @default (0,0) + */ + this.offset = new Vector2( 0, 0 ); - return this; + /** + * How many times the texture is repeated across the surface, in each + * direction U and V. If repeat is set greater than `1` in either direction, + * the corresponding wrap parameter should also be set to `RepeatWrapping` + * or `MirroredRepeatWrapping` to achieve the desired tiling effect. + * + * @type {Vector2} + * @default (1,1) + */ + this.repeat = new Vector2( 1, 1 ); - } + /** + * The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds + * to the center of the texture. Default is `(0, 0)`, the lower left. + * + * @type {Vector2} + * @default (0,0) + */ + this.center = new Vector2( 0, 0 ); - addScalar( s ) { + /** + * How much the texture is rotated around the center point, in radians. + * Positive values are counter-clockwise. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; - this.r += s; - this.g += s; - this.b += s; + /** + * Whether to update the texture's uv-transformation {@link Texture#matrix} + * from the properties {@link Texture#offset}, {@link Texture#repeat}, + * {@link Texture#rotation}, and {@link Texture#center}. + * + * Set this to `false` if you are specifying the uv-transform matrix directly. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = true; - return this; + /** + * The uv-transformation matrix of the texture. + * + * @type {Matrix3} + */ + this.matrix = new Matrix3(); - } + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Set this to `false` if you are creating mipmaps manually. + * + * @type {boolean} + * @default true + */ + this.generateMipmaps = true; - sub( color ) { + /** + * If set to `true`, the alpha channel, if present, is multiplied into the + * color channels when the texture is uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure premultiply alpha on bitmap creation instead. + * + * @type {boolean} + * @default false + */ + this.premultiplyAlpha = false; - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure the flip on bitmap creation instead. + * + * @type {boolean} + * @default true + */ + this.flipY = true; - return this; + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes), + * `4` (word-alignment), and `8` (rows start on double-word boundaries). + * + * @type {number} + * @default 4 + */ + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - } + /** + * Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`. + * + * @type {string} + * @default NoColorSpace + */ + this.colorSpace = colorSpace; - multiply( color ) { + /** + * An object that can be used to store custom data about the texture. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; + /** + * This can be used to only update a subregion or specific rows of the texture (for example, just the + * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; - return this; + /** + * This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - } + /** + * A callback function, called when the texture is updated (e.g., when + * {@link Texture#needsUpdate} has been set to true and then the texture is used). + * + * @type {?Function} + * @default null + */ + this.onUpdate = null; - multiplyScalar( s ) { + /** + * An optional back reference to the textures render target. + * + * @type {?(RenderTarget|WebGLRenderTarget)} + * @default null + */ + this.renderTarget = null; - this.r *= s; - this.g *= s; - this.b *= s; + /** + * Indicates whether a texture belongs to a render target or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isRenderTargetTexture = false; - return this; + /** + * Indicates if a texture should be handled like a texture array. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isArrayTexture = image && image.depth && image.depth > 1 ? true : false; - } + /** + * Indicates whether this texture should be processed by `PMREMGenerator` or not + * (only relevant for render target textures). + * + * @type {number} + * @readonly + * @default 0 + */ + this.pmremVersion = 0; - lerp( color, alpha ) { + } - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; + /** + * The width of the texture in pixels. + */ + get width() { - return this; + return this.source.getSize( _tempVec3 ).x; } - lerpColors( color1, color2, alpha ) { - - this.r = color1.r + ( color2.r - color1.r ) * alpha; - this.g = color1.g + ( color2.g - color1.g ) * alpha; - this.b = color1.b + ( color2.b - color1.b ) * alpha; + /** + * The height of the texture in pixels. + */ + get height() { - return this; + return this.source.getSize( _tempVec3 ).y; } - lerpHSL( color, alpha ) { + /** + * The depth of the texture in pixels. + */ + get depth() { - this.getHSL( _hslA ); - color.getHSL( _hslB ); + return this.source.getSize( _tempVec3 ).z; - const h = lerp( _hslA.h, _hslB.h, alpha ); - const s = lerp( _hslA.s, _hslB.s, alpha ); - const l = lerp( _hslA.l, _hslB.l, alpha ); + } - this.setHSL( h, s, l ); + /** + * The image object holding the texture data. + * + * @type {?Object} + */ + get image() { - return this; + return this.source.data; } - setFromVector3( v ) { - - this.r = v.x; - this.g = v.y; - this.b = v.z; + set image( value = null ) { - return this; + this.source.data = value; } - applyMatrix3( m ) { - - const r = this.r, g = this.g, b = this.b; - const e = m.elements; - - this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; - this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; - this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; + /** + * Updates the texture transformation matrix from the from the properties {@link Texture#offset}, + * {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}. + */ + updateMatrix() { - return this; + this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); } - equals( c ) { + /** + * Adds a range of data in the data texture to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + this.updateRanges.push( { start, count } ); } - fromArray( array, offset = 0 ) { - - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; + /** + * Clears the update ranges. + */ + clearUpdateRanges() { - return this; + this.updateRanges.length = 0; } - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; + /** + * Returns a new texture with copied values from this instance. + * + * @return {Texture} A clone of this instance. + */ + clone() { - return array; + return new this.constructor().copy( this ); } - fromBufferAttribute( attribute, index ) { + /** + * Copies the values of the given texture to this instance. + * + * @param {Texture} source - The texture to copy. + * @return {Texture} A reference to this instance. + */ + copy( source ) { - this.r = attribute.getX( index ); - this.g = attribute.getY( index ); - this.b = attribute.getZ( index ); + this.name = source.name; - return this; + this.source = source.source; + this.mipmaps = source.mipmaps.slice( 0 ); - } + this.mapping = source.mapping; + this.channel = source.channel; - toJSON() { + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; - return this.getHex(); + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; - } + this.anisotropy = source.anisotropy; - *[ Symbol.iterator ]() { + this.format = source.format; + this.internalFormat = source.internalFormat; + this.type = source.type; - yield this.r; - yield this.g; - yield this.b; + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); + this.center.copy( source.center ); + this.rotation = source.rotation; - } + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrix.copy( source.matrix ); -} + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; + this.colorSpace = source.colorSpace; -const _color = /*@__PURE__*/ new Color(); + this.renderTarget = source.renderTarget; + this.isRenderTargetTexture = source.isRenderTargetTexture; + this.isArrayTexture = source.isArrayTexture; -Color.NAMES = _colorKeywords; + this.userData = JSON.parse( JSON.stringify( source.userData ) ); -class Quaternion { + this.needsUpdate = true; - constructor( x = 0, y = 0, z = 0, w = 1 ) { + return this; - this.isQuaternion = true; + } - this._x = x; - this._y = y; - this._z = z; - this._w = w; + /** + * Sets this texture's properties based on `values`. + * @param {Object} values - A container with texture parameters. + */ + setValues( values ) { - } + for ( const key in values ) { - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { + const newValue = values[ key ]; - // fuzz-free, array-based Quaternion SLERP operation + if ( newValue === undefined ) { - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; + console.warn( `THREE.Texture.setValues(): parameter '${ key }' has value of undefined.` ); + continue; - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; + } - if ( t === 0 ) { + const currentValue = this[ key ]; - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; + if ( currentValue === undefined ) { - } + console.warn( `THREE.Texture.setValues(): property '${ key }' does not exist.` ); + continue; - if ( t === 1 ) { + } - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; + if ( ( currentValue && newValue ) && ( currentValue.isVector2 && newValue.isVector2 ) ) { - } + currentValue.copy( newValue ); - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + } else if ( ( currentValue && newValue ) && ( currentValue.isVector3 && newValue.isVector3 ) ) { - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; + currentValue.copy( newValue ); - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { + } else if ( ( currentValue && newValue ) && ( currentValue.isMatrix3 && newValue.isMatrix3 ) ) { - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); + currentValue.copy( newValue ); - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; + } else { + + this[ key ] = newValue; } - const tDir = t * dir; + } - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; + } - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { + /** + * Serializes the texture into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized texture. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; + if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - } + return meta.textures[ this.uuid ]; } - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - - } - - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { + const output = { - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; + metadata: { + version: 4.7, + type: 'Texture', + generator: 'Texture.toJSON' + }, - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; + uuid: this.uuid, + name: this.name, - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; + image: this.source.toJSON( meta ).uuid, - return dst; + mapping: this.mapping, + channel: this.channel, - } + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + center: [ this.center.x, this.center.y ], + rotation: this.rotation, - get x() { + wrap: [ this.wrapS, this.wrapT ], - return this._x; + format: this.format, + internalFormat: this.internalFormat, + type: this.type, + colorSpace: this.colorSpace, - } + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy, - set x( value ) { + flipY: this.flipY, - this._x = value; - this._onChangeCallback(); + generateMipmaps: this.generateMipmaps, + premultiplyAlpha: this.premultiplyAlpha, + unpackAlignment: this.unpackAlignment - } + }; - get y() { + if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; - return this._y; + if ( ! isRootObject ) { - } + meta.textures[ this.uuid ] = output; - set y( value ) { + } - this._y = value; - this._onChangeCallback(); + return output; } - get z() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { - return this._z; + /** + * Fires when the texture has been disposed of. + * + * @event Texture#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); } - set z( value ) { - - this._z = value; - this._onChangeCallback(); + /** + * Transforms the given uv vector with the textures uv transformation matrix. + * + * @param {Vector2} uv - The uv vector. + * @return {Vector2} The transformed uv vector. + */ + transformUv( uv ) { - } + if ( this.mapping !== UVMapping ) return uv; - get w() { + uv.applyMatrix3( this.matrix ); - return this._w; + if ( uv.x < 0 || uv.x > 1 ) { - } + switch ( this.wrapS ) { - set w( value ) { + case RepeatWrapping: - this._w = value; - this._onChangeCallback(); + uv.x = uv.x - Math.floor( uv.x ); + break; - } + case ClampToEdgeWrapping: - set( x, y, z, w ) { + uv.x = uv.x < 0 ? 0 : 1; + break; - this._x = x; - this._y = y; - this._z = z; - this._w = w; + case MirroredRepeatWrapping: - this._onChangeCallback(); + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - return this; + uv.x = Math.ceil( uv.x ) - uv.x; - } + } else { - clone() { + uv.x = uv.x - Math.floor( uv.x ); - return new this.constructor( this._x, this._y, this._z, this._w ); + } - } + break; - copy( quaternion ) { + } - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; + } - this._onChangeCallback(); + if ( uv.y < 0 || uv.y > 1 ) { - return this; + switch ( this.wrapT ) { - } + case RepeatWrapping: - setFromEuler( euler, update = true ) { + uv.y = uv.y - Math.floor( uv.y ); + break; - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; + case ClampToEdgeWrapping: - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m + uv.y = uv.y < 0 ? 0 : 1; + break; - const cos = Math.cos; - const sin = Math.sin; + case MirroredRepeatWrapping: - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); + uv.y = Math.ceil( uv.y ) - uv.y; - switch ( order ) { + } else { - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + uv.y = uv.y - Math.floor( uv.y ); - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + } - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + break; - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + } - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + } - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + if ( this.flipY ) { - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); + uv.y = 1 - uv.y; } - if ( update === true ) this._onChangeCallback(); - - return this; + return uv; } - setFromAxisAngle( axis, angle ) { + /** + * Setting this property to `true` indicates the engine the texture + * must be updated in the next render. This triggers a texture upload + * to the GPU and ensures correct texture parameter configuration. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - - // assumes axis is normalized - - const halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); - - this._onChangeCallback(); - - return this; - - } - - setFromRotationMatrix( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - - trace = m11 + m22 + m33; - - if ( trace > 0 ) { - - const s = 0.5 / Math.sqrt( trace + 1.0 ); - - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; - - } else if ( m11 > m22 && m11 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; - - } else if ( m22 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; - - } else { - - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; - - } - - this._onChangeCallback(); - - return this; - - } - - setFromUnitVectors( vFrom, vTo ) { - - // assumes direction vectors vFrom and vTo are normalized - - let r = vFrom.dot( vTo ) + 1; - - if ( r < Number.EPSILON ) { - - // vFrom and vTo point in opposite directions - - r = 0; - - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; - - } else { - - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; - - } - - } else { - - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; - - } - - return this.normalize(); - - } - - angleTo( q ) { - - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); - - } - - rotateTowards( q, step ) { - - const angle = this.angleTo( q ); - - if ( angle === 0 ) return this; - - const t = Math.min( 1, step / angle ); - - this.slerp( q, t ); - - return this; - - } - - identity() { - - return this.set( 0, 0, 0, 1 ); - - } - - invert() { - - // quaternion is assumed to have unit length - - return this.conjugate(); - - } - - conjugate() { - - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; - - this._onChangeCallback(); - - return this; - - } - - dot( v ) { - - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - - } - - lengthSq() { - - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - - } - - length() { - - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - - } - - normalize() { - - let l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; + if ( value === true ) { - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; + this.version ++; + this.source.needsUpdate = true; } - this._onChangeCallback(); - - return this; - - } - - multiply( q ) { - - return this.multiplyQuaternions( this, q ); - - } - - premultiply( q ) { - - return this.multiplyQuaternions( q, this ); - - } - - multiplyQuaternions( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - - this._onChangeCallback(); - - return this; - } - slerp( qb, t ) { - - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); - - const x = this._x, y = this._y, z = this._z, w = this._w; - - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - - if ( cosHalfTheta < 0 ) { - - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; - - cosHalfTheta = - cosHalfTheta; - - } else { - - this.copy( qb ); - - } - - if ( cosHalfTheta >= 1.0 ) { - - this._w = w; - this._x = x; - this._y = y; - this._z = z; - - return this; - - } - - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - - if ( sqrSinHalfTheta <= Number.EPSILON ) { - - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; + /** + * Setting this property to `true` indicates the engine the PMREM + * must be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsPMREMUpdate( value ) { - this.normalize(); // normalize calls _onChangeCallback() + if ( value === true ) { - return this; + this.pmremVersion ++; } - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); - - this._onChangeCallback(); - - return this; - - } - - slerpQuaternions( qa, qb, t ) { - - return this.copy( qa ).slerp( qb, t ); - - } - - random() { - - // sets this quaternion to a uniform random unit quaternnion - - // Ken Shoemake - // Uniform random rotations - // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. - - const theta1 = 2 * Math.PI * Math.random(); - const theta2 = 2 * Math.PI * Math.random(); - - const x0 = Math.random(); - const r1 = Math.sqrt( 1 - x0 ); - const r2 = Math.sqrt( x0 ); - - return this.set( - r1 * Math.sin( theta1 ), - r1 * Math.cos( theta1 ), - r2 * Math.sin( theta2 ), - r2 * Math.cos( theta2 ), - ); - - } - - equals( quaternion ) { - - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - } - fromArray( array, offset = 0 ) { - - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; - - this._onChangeCallback(); +} - return this; +/** + * The default image for all textures. + * + * @static + * @type {?Image} + * @default null + */ +Texture.DEFAULT_IMAGE = null; - } +/** + * The default mapping for all textures. + * + * @static + * @type {number} + * @default UVMapping + */ +Texture.DEFAULT_MAPPING = UVMapping; - toArray( array = [], offset = 0 ) { +/** + * The default anisotropy value for all textures. + * + * @static + * @type {number} + * @default 1 + */ +Texture.DEFAULT_ANISOTROPY = 1; - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; +/** + * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers + * (labeled x, y, z and w), which can be used to represent a number of things, such as: + * + * - A point in 4D space. + * - A direction and length in 4D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` + * and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. + * - Any arbitrary ordered quadruplet of numbers. + * + * There are other things a 4D vector can be used to represent, however these + * are the most common uses in *three.js*. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector4( 0, 1, 0, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0, 1) + * const b = new THREE.Vector4( ); + * + * const d = a.dot( b ); + * ``` + */ +class Vector4 { - return array; + /** + * Constructs a new 4D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + * @param {number} [w=1] - The w value of this vector. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector4.prototype.isVector4 = true; - fromBufferAttribute( attribute, index ) { + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; - this._onChangeCallback(); + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; - return this; + /** + * The w value of this vector. + * + * @type {number} + */ + this.w = w; } - toJSON() { + /** + * Alias for {@link Vector4#z}. + * + * @type {number} + */ + get width() { - return this.toArray(); + return this.z; } - _onChange( callback ) { - - this._onChangeCallback = callback; + set width( value ) { - return this; + this.z = value; } - _onChangeCallback() {} - - *[ Symbol.iterator ]() { + /** + * Alias for {@link Vector4#w}. + * + * @type {number} + */ + get height() { - yield this._x; - yield this._y; - yield this._z; - yield this._w; + return this.w; } -} - -class Vector3 { - - constructor( x = 0, y = 0, z = 0 ) { - - Vector3.prototype.isVector3 = true; + set height( value ) { - this.x = x; - this.y = y; - this.z = z; + this.w = value; } - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @param {number} w - The value of the w component. + * @return {Vector4} A reference to this vector. + */ + set( x, y, z, w ) { this.x = x; this.y = y; this.z = z; + this.w = w; return this; } + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector4} A reference to this vector. + */ setScalar( scalar ) { this.x = scalar; this.y = scalar; this.z = scalar; + this.w = scalar; return this; } + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector4} A reference to this vector. + */ setX( x ) { this.x = x; @@ -18049,6 +21991,12 @@ class Vector3 { } + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector4} A reference to this vector. + */ setY( y ) { this.y = y; @@ -18057,6 +22005,12 @@ class Vector3 { } + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector4} A reference to this vector. + */ setZ( z ) { this.z = z; @@ -18065,6 +22019,28 @@ class Vector3 { } + /** + * Sets the vector's w component to the given value + * + * @param {number} w - The value to set. + * @return {Vector4} A reference to this vector. + */ + setW( w ) { + + this.w = w; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @param {number} value - The value to set. + * @return {Vector4} A reference to this vector. + */ setComponent( index, value ) { switch ( index ) { @@ -18072,6 +22048,7 @@ class Vector3 { case 0: this.x = value; break; case 1: this.y = value; break; case 2: this.z = value; break; + case 3: this.w = value; break; default: throw new Error( 'index is out of range: ' + index ); } @@ -18080,6 +22057,13 @@ class Vector3 { } + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @return {number} A vector component value. + */ getComponent( index ) { switch ( index ) { @@ -18087,875 +22071,1632 @@ class Vector3 { case 0: return this.x; case 1: return this.y; case 2: return this.z; + case 3: return this.w; default: throw new Error( 'index is out of range: ' + index ); } } + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector4} A clone of this instance. + */ clone() { - return new this.constructor( this.x, this.y, this.z ); + return new this.constructor( this.x, this.y, this.z, this.w ); } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3|Vector4} v - The vector to copy. + * @return {Vector4} A reference to this vector. + */ copy( v ) { this.x = v.x; this.y = v.y; this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; return this; } + /** + * Adds the given vector to this instance. + * + * @param {Vector4} v - The vector to add. + * @return {Vector4} A reference to this vector. + */ add( v ) { this.x += v.x; this.y += v.y; this.z += v.z; + this.w += v.w; return this; } + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector4} A reference to this vector. + */ addScalar( s ) { this.x += s; this.y += s; this.z += s; + this.w += s; return this; } + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; + this.w = a.w + b.w; return this; } + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector4} A reference to this vector. + */ addScaledVector( v, s ) { this.x += v.x * s; this.y += v.y * s; this.z += v.z * s; + this.w += v.w * s; return this; } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector4} v - The vector to subtract. + * @return {Vector4} A reference to this vector. + */ sub( v ) { this.x -= v.x; this.y -= v.y; this.z -= v.z; + this.w -= v.w; return this; } + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector4} A reference to this vector. + */ subScalar( s ) { this.x -= s; this.y -= s; this.z -= s; + this.w -= s; return this; } + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; + this.w = a.w - b.w; return this; } - multiply( v ) { + /** + * Multiplies the given vector with this instance. + * + * @param {Vector4} v - The vector to multiply. + * @return {Vector4} A reference to this vector. + */ + multiply( v ) { this.x *= v.x; this.y *= v.y; this.z *= v.z; + this.w *= v.w; return this; } + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector4} A reference to this vector. + */ multiplyScalar( scalar ) { this.x *= scalar; this.y *= scalar; this.z *= scalar; + this.w *= scalar; return this; } - multiplyVectors( a, b ) { + /** + * Multiplies this vector with the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + applyMatrix4( m ) { - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; + const x = this.x, y = this.y, z = this.z, w = this.w; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; return this; } - applyEuler( euler ) { + /** + * Divides this instance by the given vector. + * + * @param {Vector4} v - The vector to divide. + * @return {Vector4} A reference to this vector. + */ + divide( v ) { - return this.applyQuaternion( _quaternion$2.setFromEuler( euler ) ); + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + this.w /= v.w; + + return this; } - applyAxisAngle( axis, angle ) { + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector4} A reference to this vector. + */ + divideScalar( scalar ) { - return this.applyQuaternion( _quaternion$2.setFromAxisAngle( axis, angle ) ); + return this.multiplyScalar( 1 / scalar ); } - applyMatrix3( m ) { + /** + * Sets the x, y and z components of this + * vector to the quaternion's axis and w to the angle. + * + * @param {Quaternion} q - The Quaternion to set. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromQuaternion( q ) { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + const s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } return this; } - applyNormalMatrix( m ) { + /** + * Sets the x, y and z components of this + * vector to the axis of rotation and w to the angle. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper left 3x3 matrix is a pure rotation matrix. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromRotationMatrix( m ) { - return this.applyMatrix3( m ).normalize(); + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - } + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - applyMatrix4( m ) { + let angle, x, y, z; // variables for result + const epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + te = m.elements, - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + if ( ( Math.abs( m12 - m21 ) < epsilon ) && + ( Math.abs( m13 - m31 ) < epsilon ) && + ( Math.abs( m23 - m32 ) < epsilon ) ) { - return this; + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms - } + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && + ( Math.abs( m13 + m31 ) < epsilon2 ) && + ( Math.abs( m23 + m32 ) < epsilon2 ) && + ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - applyQuaternion( q ) { + // this singularity is identity matrix so angle = 0 - // quaternion q is assumed to have unit length + this.set( 1, 0, 0, 0 ); - const vx = this.x, vy = this.y, vz = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + return this; // zero angle, arbitrary axis - // t = 2 * cross( q.xyz, v ); - const tx = 2 * ( qy * vz - qz * vy ); - const ty = 2 * ( qz * vx - qx * vz ); - const tz = 2 * ( qx * vy - qy * vx ); + } - // v + q.w * t + cross( q.xyz, t ); - this.x = vx + qw * tx + qy * tz - qz * ty; - this.y = vy + qw * ty + qz * tx - qx * tz; - this.z = vz + qw * tz + qx * ty - qy * tx; + // otherwise this singularity is angle = 180 - return this; + angle = Math.PI; - } + const xx = ( m11 + 1 ) / 2; + const yy = ( m22 + 1 ) / 2; + const zz = ( m33 + 1 ) / 2; + const xy = ( m12 + m21 ) / 4; + const xz = ( m13 + m31 ) / 4; + const yz = ( m23 + m32 ) / 4; - project( camera ) { + if ( ( xx > yy ) && ( xx > zz ) ) { - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); + // m11 is the largest diagonal term - } + if ( xx < epsilon ) { - unproject( camera ) { + x = 0; + y = 0.707106781; + z = 0.707106781; - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); + } else { - } + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; - transformDirection( m ) { + } - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction + } else if ( yy > zz ) { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + // m22 is the largest diagonal term - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + if ( yy < epsilon ) { - return this.normalize(); + x = 0.707106781; + y = 0; + z = 0.707106781; - } + } else { - divide( v ) { + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; + } + + } else { + + // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); return this; } - divideScalar( scalar ) { + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + setFromMatrixPosition( m ) { - return this.multiplyScalar( 1 / scalar ); + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + this.w = e[ 15 ]; + + return this; } + /** + * If this vector's x, y, z or w value is greater than the given vector's x, y, z or w + * value, replace that value with the corresponding min value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); this.z = Math.min( this.z, v.z ); + this.w = Math.min( this.w, v.w ); return this; } + /** + * If this vector's x, y, z or w value is less than the given vector's x, y, z or w + * value, replace that value with the corresponding max value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); this.z = Math.max( this.z, v.z ); + this.w = Math.max( this.w, v.w ); return this; } + /** + * If this vector's x, y, z or w value is greater than the max vector's x, y, z or w + * value, it is replaced by the corresponding value. + * If this vector's x, y, z or w value is less than the min vector's x, y, z or w value, + * it is replaced by the corresponding value. + * + * @param {Vector4} min - The minimum x, y and z values. + * @param {Vector4} max - The maximum x, y and z values in the desired range. + * @return {Vector4} A reference to this vector. + */ clamp( min, max ) { // assumes min < max, componentwise - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); + this.w = clamp( this.w, min.w, max.w ); return this; } + /** + * If this vector's x, y, z or w values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y, z or w values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector4} A reference to this vector. + */ clampScalar( minVal, maxVal ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); + this.w = clamp( this.w, minVal, maxVal ); return this; } + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector4} A reference to this vector. + */ clampLength( min, max ) { const length = this.length(); - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); } + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ floor() { this.x = Math.floor( this.x ); this.y = Math.floor( this.y ); this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); return this; } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ ceil() { this.x = Math.ceil( this.x ); this.y = Math.ceil( this.y ); this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); return this; } + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector4} A reference to this vector. + */ round() { this.x = Math.round( this.x ); this.y = Math.round( this.y ); this.z = Math.round( this.z ); + this.w = Math.round( this.w ); return this; } + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector4} A reference to this vector. + */ roundToZero() { this.x = Math.trunc( this.x ); this.y = Math.trunc( this.y ); this.z = Math.trunc( this.z ); + this.w = Math.trunc( this.w ); return this; } + /** + * Inverts this vector - i.e. sets x = -x, y = -y, z = -z, w = -w. + * + * @return {Vector4} A reference to this vector. + */ negate() { this.x = - this.x; this.y = - this.y; this.z = - this.z; + this.w = - this.w; return this; } + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector4} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ dot( v ) { - return this.x * v.x + this.y * v.y + this.z * v.z; + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; } - // TODO lengthSquared? - + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0, 0) to (x, y, z, w). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ lengthSq() { - return this.x * this.x + this.y * this.y + this.z * this.z; + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; } + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0, 0) to (x, y, z, w). + * + * @return {number} The length of this vector. + */ length() { - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); } + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ manhattanLength() { - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); } + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector4} A reference to this vector. + */ normalize() { return this.divideScalar( this.length() || 1 ); } + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector4} A reference to this vector. + */ setLength( length ) { return this.normalize().multiplyScalar( length ); } + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector4} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; this.y += ( v.y - this.y ) * alpha; this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; return this; } + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector4} v1 - The first vector. + * @param {Vector4} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; this.y = v1.y + ( v2.y - v1.y ) * alpha; this.z = v1.z + ( v2.z - v1.z ) * alpha; + this.w = v1.w + ( v2.w - v1.w ) * alpha; return this; } - cross( v ) { + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector4} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - return this.crossVectors( this, v ); + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); } - crossVectors( a, b ) { - - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`, + * z value to be `array[ offset + 2 ]`, w value to be `array[ offset + 3 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector4} A reference to this vector. + */ + fromArray( array, offset = 0 ) { - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; return this; } - projectOnVector( v ) { - - const denominator = v.lengthSq(); - - if ( denominator === 0 ) return this.set( 0, 0, 0 ); + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { - const scalar = v.dot( this ) / denominator; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; - return this.copy( v ).multiplyScalar( scalar ); + return array; } - projectOnPlane( planeNormal ) { + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector4} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - _vector$8.copy( this ).projectOnVector( planeNormal ); + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + this.w = attribute.getW( index ); - return this.sub( _vector$8 ); + return this; } - reflect( normal ) { + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector4} A reference to this vector. + */ + random() { - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); + this.w = Math.random(); - return this.sub( _vector$8.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + return this; } - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + *[ Symbol.iterator ]() { - if ( denominator === 0 ) return Math.PI / 2; + yield this.x; + yield this.y; + yield this.z; + yield this.w; - const theta = this.dot( v ) / denominator; + } - // clamp, to handle numerical problems +} - return Math.acos( clamp( theta, - 1, 1 ) ); +/** + * A render target is a buffer where the video card draws pixels for a scene + * that is being rendered in the background. It is used in different effects, + * such as applying postprocessing to a rendered image before displaying it + * on the screen. + * + * @augments EventDispatcher + */ +class RenderTarget extends EventDispatcher { - } + /** + * Render target options. + * + * @typedef {Object} RenderTarget~Options + * @property {boolean} [generateMipmaps=false] - Whether to generate mipmaps or not. + * @property {number} [magFilter=LinearFilter] - The mag filter. + * @property {number} [minFilter=LinearFilter] - The min filter. + * @property {number} [format=RGBAFormat] - The texture format. + * @property {number} [type=UnsignedByteType] - The texture type. + * @property {?string} [internalFormat=null] - The texture's internal format. + * @property {number} [wrapS=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [wrapT=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [anisotropy=1] - The texture's anisotropy value. + * @property {string} [colorSpace=NoColorSpace] - The texture's color space. + * @property {boolean} [depthBuffer=true] - Whether to allocate a depth buffer or not. + * @property {boolean} [stencilBuffer=false] - Whether to allocate a stencil buffer or not. + * @property {boolean} [resolveDepthBuffer=true] - Whether to resolve the depth buffer or not. + * @property {boolean} [resolveStencilBuffer=true] - Whether to resolve the stencil buffer or not. + * @property {?Texture} [depthTexture=null] - Reference to a depth texture. + * @property {number} [samples=0] - The MSAA samples count. + * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. + * @property {number} [depth=1] - The texture depth. + * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering. + */ - distanceTo( v ) { + /** + * Constructs a new render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { - return Math.sqrt( this.distanceToSquared( v ) ); + super(); - } + options = Object.assign( { + generateMipmaps: false, + internalFormat: null, + minFilter: LinearFilter, + depthBuffer: true, + stencilBuffer: false, + resolveDepthBuffer: true, + resolveStencilBuffer: true, + depthTexture: null, + samples: 0, + count: 1, + depth: 1, + multiview: false + }, options ); - distanceToSquared( v ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget = true; - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + /** + * The width of the render target. + * + * @type {number} + * @default 1 + */ + this.width = width; - return dx * dx + dy * dy + dz * dz; + /** + * The height of the render target. + * + * @type {number} + * @default 1 + */ + this.height = height; - } + /** + * The depth of the render target. + * + * @type {number} + * @default 1 + */ + this.depth = options.depth; - manhattanDistanceTo( v ) { + /** + * A rectangular area inside the render target's viewport. Fragments that are + * outside the area will be discarded. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.scissor = new Vector4( 0, 0, width, height ); - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + /** + * Indicates whether the scissor test should be enabled when rendering into + * this render target or not. + * + * @type {boolean} + * @default false + */ + this.scissorTest = false; - } + /** + * A rectangular area representing the render target's viewport. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.viewport = new Vector4( 0, 0, width, height ); - setFromSpherical( s ) { + const image = { width: width, height: height, depth: options.depth }; - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); + const texture = new Texture( image ); - } + /** + * An array of textures. Each color attachment is represented as a separate texture. + * Has at least a single entry for the default color attachment. + * + * @type {Array} + */ + this.textures = []; - setFromSphericalCoords( radius, phi, theta ) { + const count = options.count; + for ( let i = 0; i < count; i ++ ) { - const sinPhiRadius = Math.sin( phi ) * radius; + this.textures[ i ] = texture.clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); + } - return this; + this._setTextureOptions( options ); - } + /** + * Whether to allocate a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depthBuffer = options.depthBuffer; - setFromCylindrical( c ) { + /** + * Whether to allocate a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencilBuffer = options.stencilBuffer; - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); + /** + * Whether to resolve the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveDepthBuffer = options.resolveDepthBuffer; - } + /** + * Whether to resolve the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveStencilBuffer = options.resolveStencilBuffer; - setFromCylindricalCoords( radius, theta, y ) { + this._depthTexture = null; + this.depthTexture = options.depthTexture; - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); + /** + * The number of MSAA samples. + * + * A value of `0` disables MSAA. + * + * @type {number} + * @default 0 + */ + this.samples = options.samples; - return this; + /** + * Whether to this target is used in multiview rendering. + * + * @type {boolean} + * @default false + */ + this.multiview = options.multiview; } - setFromMatrixPosition( m ) { + _setTextureOptions( options = {} ) { - const e = m.elements; + const values = { + minFilter: LinearFilter, + generateMipmaps: false, + flipY: false, + internalFormat: null + }; - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; + if ( options.mapping !== undefined ) values.mapping = options.mapping; + if ( options.wrapS !== undefined ) values.wrapS = options.wrapS; + if ( options.wrapT !== undefined ) values.wrapT = options.wrapT; + if ( options.wrapR !== undefined ) values.wrapR = options.wrapR; + if ( options.magFilter !== undefined ) values.magFilter = options.magFilter; + if ( options.minFilter !== undefined ) values.minFilter = options.minFilter; + if ( options.format !== undefined ) values.format = options.format; + if ( options.type !== undefined ) values.type = options.type; + if ( options.anisotropy !== undefined ) values.anisotropy = options.anisotropy; + if ( options.colorSpace !== undefined ) values.colorSpace = options.colorSpace; + if ( options.flipY !== undefined ) values.flipY = options.flipY; + if ( options.generateMipmaps !== undefined ) values.generateMipmaps = options.generateMipmaps; + if ( options.internalFormat !== undefined ) values.internalFormat = options.internalFormat; - return this; + for ( let i = 0; i < this.textures.length; i ++ ) { - } + const texture = this.textures[ i ]; + texture.setValues( values ); - setFromMatrixScale( m ) { + } - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); + } - this.x = sx; - this.y = sy; - this.z = sz; + /** + * The texture representing the default color attachment. + * + * @type {Texture} + */ + get texture() { - return this; + return this.textures[ 0 ]; } - setFromMatrixColumn( m, index ) { + set texture( value ) { - return this.fromArray( m.elements, index * 4 ); + this.textures[ 0 ] = value; } - setFromMatrix3Column( m, index ) { + set depthTexture( current ) { - return this.fromArray( m.elements, index * 3 ); + if ( this._depthTexture !== null ) this._depthTexture.renderTarget = null; + if ( current !== null ) current.renderTarget = this; - } + this._depthTexture = current; - setFromEuler( e ) { + } - this.x = e._x; - this.y = e._y; - this.z = e._z; + /** + * Instead of saving the depth in a renderbuffer, a texture + * can be used instead which is useful for further processing + * e.g. in context of post-processing. + * + * @type {?DepthTexture} + * @default null + */ + get depthTexture() { - return this; + return this._depthTexture; } - setFromColor( c ) { - - this.x = c.r; - this.y = c.g; - this.z = c.b; + /** + * Sets the size of this render target. + * + * @param {number} width - The width. + * @param {number} height - The height. + * @param {number} [depth=1] - The depth. + */ + setSize( width, height, depth = 1 ) { - return this; + if ( this.width !== width || this.height !== height || this.depth !== depth ) { - } + this.width = width; + this.height = height; + this.depth = depth; - equals( v ) { + for ( let i = 0, il = this.textures.length; i < il; i ++ ) { - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + this.textures[ i ].image.width = width; + this.textures[ i ].image.height = height; + this.textures[ i ].image.depth = depth; + this.textures[ i ].isArrayTexture = this.textures[ i ].image.depth > 1; - } + } - fromArray( array, offset = 0 ) { + this.dispose(); - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; + } - return this; + this.viewport.set( 0, 0, width, height ); + this.scissor.set( 0, 0, width, height ); } - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; + /** + * Returns a new render target with copied values from this instance. + * + * @return {RenderTarget} A clone of this instance. + */ + clone() { - return array; + return new this.constructor().copy( this ); } - fromBufferAttribute( attribute, index ) { + /** + * Copies the settings of the given render target. This is a structural copy so + * no resources are shared between render targets after the copy. That includes + * all MRT textures and the depth texture. + * + * @param {RenderTarget} source - The render target to copy. + * @return {RenderTarget} A reference to this instance. + */ + copy( source ) { - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); + this.width = source.width; + this.height = source.height; + this.depth = source.depth; - return this; + this.scissor.copy( source.scissor ); + this.scissorTest = source.scissorTest; - } + this.viewport.copy( source.viewport ); - random() { + this.textures.length = 0; - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); + for ( let i = 0, il = source.textures.length; i < il; i ++ ) { - return this; + this.textures[ i ] = source.textures[ i ].clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; - } + // ensure image object is not shared, see #20328 - randomDirection() { + const image = Object.assign( {}, source.textures[ i ].image ); + this.textures[ i ].source = new Source( image ); - // https://mathworld.wolfram.com/SpherePointPicking.html + } - const theta = Math.random() * Math.PI * 2; - const u = Math.random() * 2 - 1; - const c = Math.sqrt( 1 - u * u ); + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; - this.x = c * Math.cos( theta ); - this.y = u; - this.z = c * Math.sin( theta ); + this.resolveDepthBuffer = source.resolveDepthBuffer; + this.resolveStencilBuffer = source.resolveStencilBuffer; + + if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + + this.samples = source.samples; return this; } - *[ Symbol.iterator ]() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires RenderTarget#dispose + */ + dispose() { - yield this.x; - yield this.y; - yield this.z; + this.dispatchEvent( { type: 'dispose' } ); } } -const _vector$8 = /*@__PURE__*/ new Vector3(); -const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - -class Box3 { +/** + * A render target used in context of {@link WebGLRenderer}. + * + * @augments RenderTarget + */ +class WebGLRenderTarget extends RenderTarget { - constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { - this.isBox3 = true; + super( width, height, options ); - this.min = min; - this.max = max; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderTarget = true; } - set( min, max ) { +} - this.min.copy( min ); - this.max.copy( max ); +const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, + 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, + 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, + 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, + 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, + 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, + 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, + 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, + 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, + 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, + 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, + 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, + 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, + 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, + 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, + 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, + 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, + 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, + 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, + 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, + 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, + 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, + 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, + 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - return this; +const _hslA = { h: 0, s: 0, l: 0 }; +const _hslB = { h: 0, s: 0, l: 0 }; - } +function hue2rgb( p, q, t ) { - setFromArray( array ) { + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; - this.makeEmpty(); +} - for ( let i = 0, il = array.length; i < il; i += 3 ) { +/** + * A Color instance is represented by RGB components in the linear working + * color space, which defaults to `LinearSRGBColorSpace`. Inputs + * conventionally using `SRGBColorSpace` (such as hexadecimals and CSS + * strings) are converted to the working color space automatically. + * + * ```js + * // converted automatically from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setHex( 0x112233 ); + * ``` + * Source color spaces may be specified explicitly, to ensure correct conversions. + * ```js + * // assumed already LinearSRGBColorSpace; no conversion + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); + * + * // converted explicitly from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); + * ``` + * If THREE.ColorManagement is disabled, no conversions occur. For details, + * see Color management. Iterating through a Color instance will yield + * its components (r, g, b) in the corresponding order. A Color can be initialised + * in any of the following ways: + * ```js + * //empty constructor - will default white + * const color1 = new THREE.Color(); + * + * //Hexadecimal color (recommended) + * const color2 = new THREE.Color( 0xff0000 ); + * + * //RGB string + * const color3 = new THREE.Color("rgb(255, 0, 0)"); + * const color4 = new THREE.Color("rgb(100%, 0%, 0%)"); + * + * //X11 color name - all 140 color names are supported. + * //Note the lack of CamelCase in the name + * const color5 = new THREE.Color( 'skyblue' ); + * //HSL string + * const color6 = new THREE.Color("hsl(0, 100%, 50%)"); + * + * //Separate RGB values between 0 and 1 + * const color7 = new THREE.Color( 1, 0, 0 ); + * ``` + */ +class Color { - this.expandByPoint( _vector$7.fromArray( array, i ) ); + /** + * Constructs a new color. + * + * Note that standard method of specifying color in three.js is with a hexadecimal triplet, + * and that method is used throughout the rest of the documentation. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + */ + constructor( r, g, b ) { - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColor = true; - return this; + /** + * The red component. + * + * @type {number} + * @default 1 + */ + this.r = 1; - } + /** + * The green component. + * + * @type {number} + * @default 1 + */ + this.g = 1; - setFromBufferAttribute( attribute ) { + /** + * The blue component. + * + * @type {number} + * @default 1 + */ + this.b = 1; - this.makeEmpty(); + return this.set( r, g, b ); - for ( let i = 0, il = attribute.count; i < il; i ++ ) { + } - this.expandByPoint( _vector$7.fromBufferAttribute( attribute, i ) ); + /** + * Sets the colors's components from the given values. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + * @return {Color} A reference to this color. + */ + set( r, g, b ) { - } + if ( g === undefined && b === undefined ) { - return this; + // r is THREE.Color, hex or string - } + const value = r; - setFromPoints( points ) { + if ( value && value.isColor ) { - this.makeEmpty(); + this.copy( value ); - for ( let i = 0, il = points.length; i < il; i ++ ) { + } else if ( typeof value === 'number' ) { - this.expandByPoint( points[ i ] ); + this.setHex( value ); - } + } else if ( typeof value === 'string' ) { - return this; + this.setStyle( value ); - } + } - setFromCenterAndSize( center, size ) { + } else { - const halfSize = _vector$7.copy( size ).multiplyScalar( 0.5 ); + this.setRGB( r, g, b ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); + } return this; } - setFromObject( object, precise = false ) { + /** + * Sets the colors's components to the given scalar value. + * + * @param {number} scalar - The scalar value. + * @return {Color} A reference to this color. + */ + setScalar( scalar ) { - this.makeEmpty(); + this.r = scalar; + this.g = scalar; + this.b = scalar; - return this.expandByObject( object, precise ); + return this; } - clone() { - - return new this.constructor().copy( this ); + /** + * Sets this color from a hexadecimal value. + * + * @param {number} hex - The hexadecimal value. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHex( hex, colorSpace = SRGBColorSpace ) { - } + hex = Math.floor( hex ); - copy( box ) { + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; - this.min.copy( box.min ); - this.max.copy( box.max ); + ColorManagement.colorSpaceToWorking( this, colorSpace ); return this; } - makeEmpty() { + /** + * Sets this color from RGB values. + * + * @param {number} r - Red channel value between `0.0` and `1.0`. + * @param {number} g - Green channel value between `0.0` and `1.0`. + * @param {number} b - Blue channel value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - this.min.x = this.min.y = this.min.z = + Infinity; - this.max.x = this.max.y = this.max.z = - Infinity; + this.r = r; + this.g = g; + this.b = b; + + ColorManagement.colorSpaceToWorking( this, colorSpace ); return this; } - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - - } + /** + * Sets this color from RGB values. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - getCenter( target ) { + // h,s,l ranges are in 0.0 - 1.0 + h = euclideanModulo( h, 1 ); + s = clamp( s, 0, 1 ); + l = clamp( l, 0, 1 ); - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + if ( s === 0 ) { - } + this.r = this.g = this.b = l; - getSize( target ) { + } else { - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); + const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + const q = ( 2 * l ) - p; - } + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); - expandByPoint( point ) { + } - this.min.min( point ); - this.max.max( point ); + ColorManagement.colorSpaceToWorking( this, colorSpace ); return this; } - expandByVector( vector ) { + /** + * Sets this color from a CSS-style string. For example, `rgb(250, 0,0)`, + * `rgb(100%, 0%, 0%)`, `hsl(0, 100%, 50%)`, `#ff0000`, `#f00`, or `red` ( or + * any [X11 color name]{@link https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart} - + * all 140 color names are supported). + * + * @param {string} style - Color as a CSS-style string. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setStyle( style, colorSpace = SRGBColorSpace ) { - this.min.sub( vector ); - this.max.add( vector ); + function handleAlpha( string ) { - return this; + if ( string === undefined ) return; - } + if ( parseFloat( string ) < 1 ) { - expandByScalar( scalar ) { + console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); + } - return this; + } - } - expandByObject( object, precise = false ) { + let m; - // Computes the world-axis-aligned bounding box of an object (including its children), - // accounting for both the object's, and children's, world transforms + if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - object.updateWorldMatrix( false, false ); + // rgb / hsl - const geometry = object.geometry; + let color; + const name = m[ 1 ]; + const components = m[ 2 ]; - if ( geometry !== undefined ) { + switch ( name ) { - const positionAttribute = geometry.getAttribute( 'position' ); + case 'rgb': + case 'rgba': - // precise AABB computation based on vertex data requires at least a position attribute. - // instancing isn't supported so far and uses the normal (conservative) code path. + if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { + // rgb(255,0,0) rgba(255,0,0,0.5) - for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { + handleAlpha( color[ 4 ] ); - if ( object.isMesh === true ) { + return this.setRGB( + Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, + colorSpace + ); - object.getVertexPosition( i, _vector$7 ); + } - } else { + if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - _vector$7.fromBufferAttribute( positionAttribute, i ); + // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - } + handleAlpha( color[ 4 ] ); - _vector$7.applyMatrix4( object.matrixWorld ); - this.expandByPoint( _vector$7 ); + return this.setRGB( + Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, + colorSpace + ); - } + } - } else { + break; - if ( object.boundingBox !== undefined ) { + case 'hsl': + case 'hsla': - // object-level bounding box + if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - if ( object.boundingBox === null ) { + // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - object.computeBoundingBox(); + handleAlpha( color[ 4 ] ); + + return this.setHSL( + parseFloat( color[ 1 ] ) / 360, + parseFloat( color[ 2 ] ) / 100, + parseFloat( color[ 3 ] ) / 100, + colorSpace + ); } - _box$3.copy( object.boundingBox ); + break; + default: - } else { + console.warn( 'THREE.Color: Unknown color model ' + style ); - // geometry-level bounding box + } - if ( geometry.boundingBox === null ) { + } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - geometry.computeBoundingBox(); + // hex color - } + const hex = m[ 1 ]; + const size = hex.length; - _box$3.copy( geometry.boundingBox ); + if ( size === 3 ) { - } + // #ff0 + return this.setRGB( + parseInt( hex.charAt( 0 ), 16 ) / 15, + parseInt( hex.charAt( 1 ), 16 ) / 15, + parseInt( hex.charAt( 2 ), 16 ) / 15, + colorSpace + ); - _box$3.applyMatrix4( object.matrixWorld ); + } else if ( size === 6 ) { - this.union( _box$3 ); + // #ff0000 + return this.setHex( parseInt( hex, 16 ), colorSpace ); - } + } else { - } + console.warn( 'THREE.Color: Invalid hex color ' + style ); - const children = object.children; + } - for ( let i = 0, l = children.length; i < l; i ++ ) { + } else if ( style && style.length > 0 ) { - this.expandByObject( children[ i ], precise ); + return this.setColorName( style, colorSpace ); } @@ -18963,487 +23704,672 @@ class Box3 { } - containsPoint( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; + /** + * Sets this color from a color name. Faster than {@link Color#setStyle} if + * you don't need the other CSS-style formats. + * + * For convenience, the list of names is exposed in `Color.NAMES` as a hash. + * ```js + * Color.NAMES.aliceblue // returns 0xF0F8FF + * ``` + * + * @param {string} style - The color name. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setColorName( style, colorSpace = SRGBColorSpace ) { - } + // color keywords + const hex = _colorKeywords[ style.toLowerCase() ]; - containsBox( box ) { + if ( hex !== undefined ) { - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y && - this.min.z <= box.min.z && box.max.z <= this.max.z; + // red + this.setHex( hex, colorSpace ); - } + } else { - getParameter( point, target ) { + // unknown color + console.warn( 'THREE.Color: Unknown color ' + style ); - // This can potentially have a divide by zero if the box - // has a size dimension of 0. + } - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ), - ( point.z - this.min.z ) / ( this.max.z - this.min.z ) - ); + return this; } - intersectsBox( box ) { + /** + * Returns a new color with copied values from this instance. + * + * @return {Color} A clone of this instance. + */ + clone() { - // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; + return new this.constructor( this.r, this.g, this.b ); } - intersectsSphere( sphere ) { + /** + * Copies the values of the given color to this instance. + * + * @param {Color} color - The color to copy. + * @return {Color} A reference to this color. + */ + copy( color ) { - // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, _vector$7 ); + this.r = color.r; + this.g = color.g; + this.b = color.b; - // If that point is inside the sphere, the AABB and sphere intersect. - return _vector$7.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); + return this; } - intersectsPlane( plane ) { + /** + * Copies the given color into this color, and then converts this color from + * `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copySRGBToLinear( color ) { - // We compute the minimum and maximum dot product values. If those values - // are on the same side (back or front) of the plane, then there is no intersection. + this.r = SRGBToLinear( color.r ); + this.g = SRGBToLinear( color.g ); + this.b = SRGBToLinear( color.b ); - let min, max; + return this; - if ( plane.normal.x > 0 ) { + } - min = plane.normal.x * this.min.x; - max = plane.normal.x * this.max.x; + /** + * Copies the given color into this color, and then converts this color from + * `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copyLinearToSRGB( color ) { - } else { + this.r = LinearToSRGB( color.r ); + this.g = LinearToSRGB( color.g ); + this.b = LinearToSRGB( color.b ); - min = plane.normal.x * this.max.x; - max = plane.normal.x * this.min.x; + return this; - } + } - if ( plane.normal.y > 0 ) { + /** + * Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertSRGBToLinear() { - min += plane.normal.y * this.min.y; - max += plane.normal.y * this.max.y; + this.copySRGBToLinear( this ); - } else { + return this; - min += plane.normal.y * this.max.y; - max += plane.normal.y * this.min.y; + } - } + /** + * Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertLinearToSRGB() { - if ( plane.normal.z > 0 ) { + this.copyLinearToSRGB( this ); - min += plane.normal.z * this.min.z; - max += plane.normal.z * this.max.z; + return this; - } else { + } - min += plane.normal.z * this.max.z; - max += plane.normal.z * this.min.z; + /** + * Returns the hexadecimal value of this color. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {number} The hexadecimal value. + */ + getHex( colorSpace = SRGBColorSpace ) { - } + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - return ( min <= - plane.constant && max >= - plane.constant ); + return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); } - intersectsTriangle( triangle ) { + /** + * Returns the hexadecimal value of this color as a string (for example, 'FFFFFF'). + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The hexadecimal value as a string. + */ + getHexString( colorSpace = SRGBColorSpace ) { - if ( this.isEmpty() ) { + return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( -6 ); - return false; + } - } + /** + * Converts the colors RGB values into the HSL format and stores them into the + * given target object. + * + * @param {{h:number,s:number,l:number}} target - The target object that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {{h:number,s:number,l:number}} The HSL representation of this color. + */ + getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { - // compute box center and extents - this.getCenter( _center ); - _extents.subVectors( this.max, _center ); + // h,s,l ranges are in 0.0 - 1.0 - // translate triangle to aabb origin - _v0$2.subVectors( triangle.a, _center ); - _v1$6.subVectors( triangle.b, _center ); - _v2$3.subVectors( triangle.c, _center ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - // compute edge vectors for triangle - _f0.subVectors( _v1$6, _v0$2 ); - _f1.subVectors( _v2$3, _v1$6 ); - _f2.subVectors( _v0$2, _v2$3 ); + const r = _color.r, g = _color.g, b = _color.b; - // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb - // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation - // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) - let axes = [ - 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, - _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 - ]; - if ( ! satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ) ) { + const max = Math.max( r, g, b ); + const min = Math.min( r, g, b ); - return false; + let hue, saturation; + const lightness = ( min + max ) / 2.0; - } + if ( min === max ) { - // test 3 face normals from the aabb - axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ) ) { + hue = 0; + saturation = 0; - return false; + } else { - } + const delta = max - min; - // finally testing the face normal of the triangle - // use already existing triangle edge vectors here - _triangleNormal.crossVectors( _f0, _f1 ); - axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - return satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ); + switch ( max ) { - } + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; - clampPoint( point, target ) { + } - return target.copy( point ).clamp( this.min, this.max ); + hue /= 6; - } + } - distanceToPoint( point ) { + target.h = hue; + target.s = saturation; + target.l = lightness; - return this.clampPoint( point, _vector$7 ).distanceTo( point ); + return target; } - getBoundingSphere( target ) { - - if ( this.isEmpty() ) { - - target.makeEmpty(); - - } else { - - this.getCenter( target.center ); + /** + * Returns the RGB values of this color and stores them into the given target object. + * + * @param {Color} target - The target color that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} The RGB representation of this color. + */ + getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - target.radius = this.getSize( _vector$7 ).length() * 0.5; + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - } + target.r = _color.r; + target.g = _color.g; + target.b = _color.b; return target; } - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); + /** + * Returns the value of this color as a CSS style string. Example: `rgb(255,0,0)`. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The CSS representation of this color. + */ + getStyle( colorSpace = SRGBColorSpace ) { - // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. - if ( this.isEmpty() ) this.makeEmpty(); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - return this; + const r = _color.r, g = _color.g, b = _color.b; - } + if ( colorSpace !== SRGBColorSpace ) { - union( box ) { + // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). + return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; - this.min.min( box.min ); - this.max.max( box.max ); + } - return this; + return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; } - applyMatrix4( matrix ) { - - // transform of empty box is an empty box. - if ( this.isEmpty() ) return this; - - // NOTE: I am using a binary pattern to specify all 2^3 combinations below - _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 - _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 - _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 - _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 - _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 - _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 - _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 - _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 + /** + * Adds the given HSL values to this color's values. + * Internally, this converts the color's RGB values to HSL, adds HSL + * and then converts the color back to RGB. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @return {Color} A reference to this color. + */ + offsetHSL( h, s, l ) { - this.setFromPoints( _points ); + this.getHSL( _hslA ); - return this; + return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); } - translate( offset ) { + /** + * Adds the RGB values of the given color to the RGB values of this color. + * + * @param {Color} color - The color to add. + * @return {Color} A reference to this color. + */ + add( color ) { - this.min.add( offset ); - this.max.add( offset ); + this.r += color.r; + this.g += color.g; + this.b += color.b; return this; } - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - -} - -const _points = [ - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3() -]; - -const _vector$7 = /*@__PURE__*/ new Vector3(); - -const _box$3 = /*@__PURE__*/ new Box3(); + /** + * Adds the RGB values of the given colors and stores the result in this instance. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @return {Color} A reference to this color. + */ + addColors( color1, color2 ) { -// triangle centered vertices + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; -const _v0$2 = /*@__PURE__*/ new Vector3(); -const _v1$6 = /*@__PURE__*/ new Vector3(); -const _v2$3 = /*@__PURE__*/ new Vector3(); + return this; -// triangle edge vectors + } -const _f0 = /*@__PURE__*/ new Vector3(); -const _f1 = /*@__PURE__*/ new Vector3(); -const _f2 = /*@__PURE__*/ new Vector3(); + /** + * Adds the given scalar value to the RGB values of this color. + * + * @param {number} s - The scalar to add. + * @return {Color} A reference to this color. + */ + addScalar( s ) { -const _center = /*@__PURE__*/ new Vector3(); -const _extents = /*@__PURE__*/ new Vector3(); -const _triangleNormal = /*@__PURE__*/ new Vector3(); -const _testAxis = /*@__PURE__*/ new Vector3(); + this.r += s; + this.g += s; + this.b += s; -function satForAxes( axes, v0, v1, v2, extents ) { + return this; - for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { + } - _testAxis.fromArray( axes, i ); - // project the aabb onto the separating axis - const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); - // project all 3 vertices of the triangle onto the separating axis - const p0 = v0.dot( _testAxis ); - const p1 = v1.dot( _testAxis ); - const p2 = v2.dot( _testAxis ); - // actual test, basically see if either of the most extreme of the triangle points intersects r - if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { + /** + * Subtracts the RGB values of the given color from the RGB values of this color. + * + * @param {Color} color - The color to subtract. + * @return {Color} A reference to this color. + */ + sub( color ) { - // points of the projected triangle are outside the projected half-length of the aabb - // the axis is separating and we can exit - return false; + this.r = Math.max( 0, this.r - color.r ); + this.g = Math.max( 0, this.g - color.g ); + this.b = Math.max( 0, this.b - color.b ); - } + return this; } - return true; + /** + * Multiplies the RGB values of the given color with the RGB values of this color. + * + * @param {Color} color - The color to multiply. + * @return {Color} A reference to this color. + */ + multiply( color ) { -} + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; -const _box$2 = /*@__PURE__*/ new Box3(); -const _v1$5 = /*@__PURE__*/ new Vector3(); -const _v2$2 = /*@__PURE__*/ new Vector3(); + return this; -class Sphere { + } - constructor( center = new Vector3(), radius = - 1 ) { + /** + * Multiplies the given scalar value with the RGB values of this color. + * + * @param {number} s - The scalar to multiply. + * @return {Color} A reference to this color. + */ + multiplyScalar( s ) { - this.isSphere = true; + this.r *= s; + this.g *= s; + this.b *= s; - this.center = center; - this.radius = radius; + return this; } - set( center, radius ) { + /** + * Linearly interpolates this color's RGB values toward the RGB values of the + * given color. The alpha argument can be thought of as the ratio between + * the two colors, where `0.0` is this color and `1.0` is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerp( color, alpha ) { - this.center.copy( center ); - this.radius = radius; + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; return this; } - setFromPoints( points, optionalCenter ) { + /** + * Linearly interpolates between the given colors and stores the result in this instance. + * The alpha argument can be thought of as the ratio between the two colors, where `0.0` + * is the first and `1.0` is the second color. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpColors( color1, color2, alpha ) { - const center = this.center; + this.r = color1.r + ( color2.r - color1.r ) * alpha; + this.g = color1.g + ( color2.g - color1.g ) * alpha; + this.b = color1.b + ( color2.b - color1.b ) * alpha; - if ( optionalCenter !== undefined ) { + return this; - center.copy( optionalCenter ); + } - } else { + /** + * Linearly interpolates this color's HSL values toward the HSL values of the + * given color. It differs from {@link Color#lerp} by not interpolating straight + * from one color to the other, but instead going through all the hues in between + * those two colors. The alpha argument can be thought of as the ratio between + * the two colors, where 0.0 is this color and 1.0 is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpHSL( color, alpha ) { - _box$2.setFromPoints( points ).getCenter( center ); + this.getHSL( _hslA ); + color.getHSL( _hslB ); - } + const h = lerp( _hslA.h, _hslB.h, alpha ); + const s = lerp( _hslA.s, _hslB.s, alpha ); + const l = lerp( _hslA.l, _hslB.l, alpha ); - let maxRadiusSq = 0; + this.setHSL( h, s, l ); - for ( let i = 0, il = points.length; i < il; i ++ ) { + return this; - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + } - } + /** + * Sets the color's RGB components from the given 3D vector. + * + * @param {Vector3} v - The vector to set. + * @return {Color} A reference to this color. + */ + setFromVector3( v ) { - this.radius = Math.sqrt( maxRadiusSq ); + this.r = v.x; + this.g = v.y; + this.b = v.z; return this; } - copy( sphere ) { + /** + * Transforms this color with the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix. + * @return {Color} A reference to this color. + */ + applyMatrix3( m ) { - this.center.copy( sphere.center ); - this.radius = sphere.radius; + const r = this.r, g = this.g, b = this.b; + const e = m.elements; + + this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; + this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; + this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; return this; } - isEmpty() { + /** + * Returns `true` if this color is equal with the given one. + * + * @param {Color} c - The color to test for equality. + * @return {boolean} Whether this bounding color is equal with the given one. + */ + equals( c ) { - return ( this.radius < 0 ); + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); } - makeEmpty() { + /** + * Sets this color's RGB components from the given array. + * + * @param {Array} array - An array holding the RGB values. + * @param {number} [offset=0] - The offset into the array. + * @return {Color} A reference to this color. + */ + fromArray( array, offset = 0 ) { - this.center.set( 0, 0, 0 ); - this.radius = - 1; + this.r = array[ offset ]; + this.g = array[ offset + 1 ]; + this.b = array[ offset + 2 ]; return this; } - containsPoint( point ) { - - return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - - } + /** + * Writes the RGB components of this color to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the color components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The color components. + */ + toArray( array = [], offset = 0 ) { - distanceToPoint( point ) { + array[ offset ] = this.r; + array[ offset + 1 ] = this.g; + array[ offset + 2 ] = this.b; - return ( point.distanceTo( this.center ) - this.radius ); + return array; } - intersectsSphere( sphere ) { + /** + * Sets the components of this color from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding color data. + * @param {number} index - The index into the attribute. + * @return {Color} A reference to this color. + */ + fromBufferAttribute( attribute, index ) { - const radiusSum = this.radius + sphere.radius; + this.r = attribute.getX( index ); + this.g = attribute.getY( index ); + this.b = attribute.getZ( index ); - return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); + return this; } - intersectsBox( box ) { + /** + * This methods defines the serialization result of this class. Returns the color + * as a hexadecimal value. + * + * @return {number} The hexadecimal value. + */ + toJSON() { - return box.intersectsSphere( this ); + return this.getHex(); } - intersectsPlane( plane ) { + *[ Symbol.iterator ]() { - return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; + yield this.r; + yield this.g; + yield this.b; } - clampPoint( point, target ) { - - const deltaLengthSq = this.center.distanceToSquared( point ); - - target.copy( point ); +} - if ( deltaLengthSq > ( this.radius * this.radius ) ) { +const _color = /*@__PURE__*/ new Color(); - target.sub( this.center ).normalize(); - target.multiplyScalar( this.radius ).add( this.center ); +/** + * A dictionary with X11 color names. + * + * Note that multiple words such as Dark Orange become the string 'darkorange'. + * + * @static + * @type {Object} + */ +Color.NAMES = _colorKeywords; - } +/** + * Represents an axis-aligned bounding box (AABB) in 3D space. + */ +class Box3 { - return target; + /** + * Constructs a new bounding box. + * + * @param {Vector3} [min=(Infinity,Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector3} [max=(-Infinity,-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ + constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBox3 = true; - getBoundingBox( target ) { + /** + * The lower boundary of the box. + * + * @type {Vector3} + */ + this.min = min; - if ( this.isEmpty() ) { + /** + * The upper boundary of the box. + * + * @type {Vector3} + */ + this.max = max; - // Empty sphere produces empty bounding box - target.makeEmpty(); - return target; + } - } + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector3} min - The lower boundary of the box. + * @param {Vector3} max - The upper boundary of the box. + * @return {Box3} A reference to this bounding box. + */ + set( min, max ) { - target.set( this.center, this.center ); - target.expandByScalar( this.radius ); + this.min.copy( min ); + this.max.copy( max ); - return target; + return this; } - applyMatrix4( matrix ) { - - this.center.applyMatrix4( matrix ); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} array - An array holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ + setFromArray( array ) { - return this; + this.makeEmpty(); - } + for ( let i = 0, il = array.length; i < il; i += 3 ) { - translate( offset ) { + this.expandByPoint( _vector$7.fromArray( array, i ) ); - this.center.add( offset ); + } return this; } - expandByPoint( point ) { - - if ( this.isEmpty() ) { + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given buffer attribute. + * + * @param {BufferAttribute} attribute - A buffer attribute holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ + setFromBufferAttribute( attribute ) { - this.center.copy( point ); + this.makeEmpty(); - this.radius = 0; + for ( let i = 0, il = attribute.count; i < il; i ++ ) { - return this; + this.expandByPoint( _vector$7.fromBufferAttribute( attribute, i ) ); } - _v1$5.subVectors( point, this.center ); - - const lengthSq = _v1$5.lengthSq(); - - if ( lengthSq > ( this.radius * this.radius ) ) { + return this; - // calculate the minimal sphere + } - const length = Math.sqrt( lengthSq ); + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 3D position data as instances of {@link Vector3}. + * @return {Box3} A reference to this bounding box. + */ + setFromPoints( points ) { - const delta = ( length - this.radius ) * 0.5; + this.makeEmpty(); - this.center.addScaledVector( _v1$5, delta / length ); + for ( let i = 0, il = points.length; i < il; i ++ ) { - this.radius += delta; + this.expandByPoint( points[ i ] ); } @@ -19451,1288 +24377,1726 @@ class Sphere { } - union( sphere ) { + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector3} center - The center of the box. + * @param {Vector3} size - The x, y and z dimensions of the box. + * @return {Box3} A reference to this bounding box. + */ + setFromCenterAndSize( center, size ) { - if ( sphere.isEmpty() ) { + const halfSize = _vector$7.copy( size ).multiplyScalar( 0.5 ); - return this; + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); - } + return this; - if ( this.isEmpty() ) { + } - this.copy( sphere ); + /** + * Computes the world-axis-aligned bounding box for the given 3D object + * (including its children), accounting for the object's, and children's, + * world transforms. The function may result in a larger box than strictly necessary. + * + * @param {Object3D} object - The 3D object to compute the bounding box for. + * @param {boolean} [precise=false] - If set to `true`, the method computes the smallest + * world-axis-aligned bounding box at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ + setFromObject( object, precise = false ) { - return this; + this.makeEmpty(); - } + return this.expandByObject( object, precise ); - if ( this.center.equals( sphere.center ) === true ) { - - this.radius = Math.max( this.radius, sphere.radius ); + } - } else { + /** + * Returns a new box with copied values from this instance. + * + * @return {Box3} A clone of this instance. + */ + clone() { - _v2$2.subVectors( sphere.center, this.center ).setLength( sphere.radius ); + return new this.constructor().copy( this ); - this.expandByPoint( _v1$5.copy( sphere.center ).add( _v2$2 ) ); + } - this.expandByPoint( _v1$5.copy( sphere.center ).sub( _v2$2 ) ); + /** + * Copies the values of the given box to this instance. + * + * @param {Box3} box - The box to copy. + * @return {Box3} A reference to this bounding box. + */ + copy( box ) { - } + this.min.copy( box.min ); + this.max.copy( box.max ); return this; } - equals( sphere ) { + /** + * Makes this box empty which means in encloses a zero space in 3D. + * + * @return {Box3} A reference to this bounding box. + */ + makeEmpty() { - return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + this.min.x = this.min.y = this.min.z = + Infinity; + this.max.x = this.max.y = this.max.z = - Infinity; - } + return this; - clone() { + } - return new this.constructor().copy( this ); + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ + isEmpty() { - } + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes -} + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); -const _vector1 = /*@__PURE__*/ new Vector3(); -const _vector2$1 = /*@__PURE__*/ new Vector3(); -const _normalMatrix = /*@__PURE__*/ new Matrix3(); + } -class Plane { + /** + * Returns the center point of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ + getCenter( target ) { - constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { + return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - this.isPlane = true; + } - // normal is assumed to be normalized + /** + * Returns the dimensions of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The size. + */ + getSize( target ) { - this.normal = normal; - this.constant = constant; + return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); } - set( normal, constant ) { + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector3} point - The point that should be included by the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByPoint( point ) { - this.normal.copy( normal ); - this.constant = constant; + this.min.min( point ); + this.max.max( point ); return this; } - setComponents( x, y, z, w ) { + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. The depth of this box will be + * expanded by the z component of the vector in both directions. + * + * @param {Vector3} vector - The vector that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByVector( vector ) { - this.normal.set( x, y, z ); - this.constant = w; + this.min.sub( vector ); + this.max.add( vector ); return this; } - setFromNormalAndCoplanarPoint( normal, point ) { + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByScalar( scalar ) { - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); return this; } - setFromCoplanarPoints( a, b, c ) { - - const normal = _vector1.subVectors( c, b ).cross( _vector2$1.subVectors( a, b ) ).normalize(); + /** + * Expands the boundaries of this box to include the given 3D object and + * its children, accounting for the object's, and children's, world + * transforms. The function may result in a larger box than strictly + * necessary (unless the precise parameter is set to true). + * + * @param {Object3D} object - The 3D object that should expand the bounding box. + * @param {boolean} precise - If set to `true`, the method expands the bounding box + * as little as necessary at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ + expandByObject( object, precise = false ) { - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and children's, world transforms - this.setFromNormalAndCoplanarPoint( normal, a ); + object.updateWorldMatrix( false, false ); - return this; + const geometry = object.geometry; - } + if ( geometry !== undefined ) { - copy( plane ) { + const positionAttribute = geometry.getAttribute( 'position' ); - this.normal.copy( plane.normal ); - this.constant = plane.constant; + // precise AABB computation based on vertex data requires at least a position attribute. + // instancing isn't supported so far and uses the normal (conservative) code path. - return this; + if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { - } + for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { - normalize() { + if ( object.isMesh === true ) { - // Note: will lead to a divide by zero if the plane is invalid. + object.getVertexPosition( i, _vector$7 ); - const inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; + } else { - return this; + _vector$7.fromBufferAttribute( positionAttribute, i ); - } + } - negate() { + _vector$7.applyMatrix4( object.matrixWorld ); + this.expandByPoint( _vector$7 ); - this.constant *= - 1; - this.normal.negate(); + } - return this; + } else { - } + if ( object.boundingBox !== undefined ) { - distanceToPoint( point ) { + // object-level bounding box - return this.normal.dot( point ) + this.constant; + if ( object.boundingBox === null ) { - } + object.computeBoundingBox(); - distanceToSphere( sphere ) { + } - return this.distanceToPoint( sphere.center ) - sphere.radius; + _box$3.copy( object.boundingBox ); - } - projectPoint( point, target ) { + } else { - return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); + // geometry-level bounding box - } + if ( geometry.boundingBox === null ) { - intersectLine( line, target ) { + geometry.computeBoundingBox(); - const direction = line.delta( _vector1 ); + } - const denominator = this.normal.dot( direction ); + _box$3.copy( geometry.boundingBox ); - if ( denominator === 0 ) { + } - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { + _box$3.applyMatrix4( object.matrixWorld ); - return target.copy( line.start ); + this.union( _box$3 ); } - // Unsure if this is the correct method to handle this case. - return null; - } - const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + const children = object.children; - if ( t < 0 || t > 1 ) { + for ( let i = 0, l = children.length; i < l; i ++ ) { - return null; + this.expandByObject( children[ i ], precise ); } - return target.copy( line.start ).addScaledVector( direction, t ); + return this; } - intersectsLine( line ) { - - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - - const startSign = this.distanceToPoint( line.start ); - const endSign = this.distanceToPoint( line.end ); + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ + containsPoint( point ) { - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y && + point.z >= this.min.z && point.z <= this.max.z; } - intersectsBox( box ) { + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ + containsBox( box ) { - return box.intersectsPlane( this ); + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y && + this.min.z <= box.min.z && box.max.z <= this.max.z; } - intersectsSphere( sphere ) { - - return sphere.intersectsPlane( this ); - - } + /** + * Returns a point as a proportion of this box's width, height and depth. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A point as a proportion of this box's width, height and depth. + */ + getParameter( point, target ) { - coplanarPoint( target ) { + // This can potentially have a divide by zero if the box + // has a size dimension of 0. - return target.copy( this.normal ).multiplyScalar( - this.constant ); + return target.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ), + ( point.z - this.min.z ) / ( this.max.z - this.min.z ) + ); } - applyMatrix4( matrix, optionalNormalMatrix ) { - - const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); - - const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); - - const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - - this.constant = - referencePoint.dot( normal ); + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ + intersectsBox( box ) { - return this; + // using 6 splitting planes to rule out intersections. + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y && + box.max.z >= this.min.z && box.min.z <= this.max.z; } - translate( offset ) { - - this.constant -= offset.dot( this.normal ); - - return this; - - } + /** + * Returns `true` if the given bounding sphere intersects with this bounding box. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with this bounding box. + */ + intersectsSphere( sphere ) { - equals( plane ) { + // Find the point on the AABB closest to the sphere center. + this.clampPoint( sphere.center, _vector$7 ); - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); + // If that point is inside the sphere, the AABB and sphere intersect. + return _vector$7.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); } - clone() { - - return new this.constructor().copy( this ); + /** + * Returns `true` if the given plane intersects with this bounding box. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether the given plane intersects with this bounding box. + */ + intersectsPlane( plane ) { - } + // We compute the minimum and maximum dot product values. If those values + // are on the same side (back or front) of the plane, then there is no intersection. -} + let min, max; -const _sphere$6 = /*@__PURE__*/ new Sphere(); -const _vector$6 = /*@__PURE__*/ new Vector3(); + if ( plane.normal.x > 0 ) { -class Frustum { + min = plane.normal.x * this.min.x; + max = plane.normal.x * this.max.x; - constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { + } else { - this.planes = [ p0, p1, p2, p3, p4, p5 ]; + min = plane.normal.x * this.max.x; + max = plane.normal.x * this.min.x; - } + } - set( p0, p1, p2, p3, p4, p5 ) { + if ( plane.normal.y > 0 ) { - const planes = this.planes; + min += plane.normal.y * this.min.y; + max += plane.normal.y * this.max.y; - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); + } else { - return this; + min += plane.normal.y * this.max.y; + max += plane.normal.y * this.min.y; - } + } - copy( frustum ) { + if ( plane.normal.z > 0 ) { - const planes = this.planes; + min += plane.normal.z * this.min.z; + max += plane.normal.z * this.max.z; - for ( let i = 0; i < 6; i ++ ) { + } else { - planes[ i ].copy( frustum.planes[ i ] ); + min += plane.normal.z * this.max.z; + max += plane.normal.z * this.min.z; } - return this; + return ( min <= - plane.constant && max >= - plane.constant ); } - setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem ) { + /** + * Returns `true` if the given triangle intersects with this bounding box. + * + * @param {Triangle} triangle - The triangle to test. + * @return {boolean} Whether the given triangle intersects with this bounding box. + */ + intersectsTriangle( triangle ) { - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + if ( this.isEmpty() ) { - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + return false; - if ( coordinateSystem === WebGLCoordinateSystem ) { + } - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + // compute box center and extents + this.getCenter( _center ); + _extents.subVectors( this.max, _center ); - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + // translate triangle to aabb origin + _v0$3.subVectors( triangle.a, _center ); + _v1$6.subVectors( triangle.b, _center ); + _v2$3.subVectors( triangle.c, _center ); - planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); + // compute edge vectors for triangle + _f0.subVectors( _v1$6, _v0$3 ); + _f1.subVectors( _v2$3, _v1$6 ); + _f2.subVectors( _v0$3, _v2$3 ); - } else { + // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb + // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation + // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) + let axes = [ + 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, + _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, + - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 + ]; + if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { - throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); + return false; } - return this; - - } + // test 3 face normals from the aabb + axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; + if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { - intersectsObject( object ) { + return false; - if ( object.boundingSphere !== undefined ) { + } - if ( object.boundingSphere === null ) object.computeBoundingSphere(); + // finally testing the face normal of the triangle + // use already existing triangle edge vectors here + _triangleNormal.crossVectors( _f0, _f1 ); + axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - _sphere$6.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); + return satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ); - } else { + } - const geometry = object.geometry; + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector3} point - The point to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ + clampPoint( point, target ) { - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + return target.copy( point ).clamp( this.min, this.max ); - _sphere$6.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + } - } + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ + distanceToPoint( point ) { - return this.intersectsSphere( _sphere$6 ); + return this.clampPoint( point, _vector$7 ).distanceTo( point ); } - intersectsSprite( sprite ) { + /** + * Returns a bounding sphere that encloses this bounding box. + * + * @param {Sphere} target - The target sphere that is used to store the method's result. + * @return {Sphere} The bounding sphere that encloses this bounding box. + */ + getBoundingSphere( target ) { - _sphere$6.center.set( 0, 0, 0 ); - _sphere$6.radius = 0.7071067811865476; - _sphere$6.applyMatrix4( sprite.matrixWorld ); + if ( this.isEmpty() ) { - return this.intersectsSphere( _sphere$6 ); + target.makeEmpty(); - } + } else { - intersectsSphere( sphere ) { + this.getCenter( target.center ); - const planes = this.planes; - const center = sphere.center; - const negRadius = - sphere.radius; + target.radius = this.getSize( _vector$7 ).length() * 0.5; - for ( let i = 0; i < 6; i ++ ) { + } - const distance = planes[ i ].distanceToPoint( center ); + return target; - if ( distance < negRadius ) { + } - return false; + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box3} box - The bounding box to intersect with. + * @return {Box3} A reference to this bounding box. + */ + intersect( box ) { - } + this.min.max( box.min ); + this.max.min( box.max ); - } + // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. + if ( this.isEmpty() ) this.makeEmpty(); - return true; + return this; } - intersectsBox( box ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box3} box - The bounding box that will be unioned with this instance. + * @return {Box3} A reference to this bounding box. + */ + union( box ) { - const plane = planes[ i ]; + this.min.min( box.min ); + this.max.max( box.max ); - // corner at max distance + return this; - _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; - _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; - _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; + } - if ( plane.distanceToPoint( _vector$6 ) < 0 ) { + /** + * Transforms this bounding box by the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Box3} A reference to this bounding box. + */ + applyMatrix4( matrix ) { - return false; + // transform of empty box is an empty box. + if ( this.isEmpty() ) return this; - } + // NOTE: I am using a binary pattern to specify all 2^3 combinations below + _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 + _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 + _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 + _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 + _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 + _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 + _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 + _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 - } + this.setFromPoints( _points ); - return true; + return this; } - containsPoint( point ) { + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 3D space. + * + * @param {Vector3} offset - The offset that should be used to translate the bounding box. + * @return {Box3} A reference to this bounding box. + */ + translate( offset ) { - const planes = this.planes; + this.min.add( offset ); + this.max.add( offset ); - for ( let i = 0; i < 6; i ++ ) { + return this; - if ( planes[ i ].distanceToPoint( point ) < 0 ) { + } - return false; + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box3} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ + equals( box ) { - } + return box.min.equals( this.min ) && box.max.equals( this.max ); - } + } - return true; + /** + * Returns a serialized structure of the bounding box. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + min: this.min.toArray(), + max: this.max.toArray() + }; } - clone() { + /** + * Returns a serialized structure of the bounding box. + * + * @param {Object} json - The serialized json to set the box from. + * @return {Box3} A reference to this bounding box. + */ + fromJSON( json ) { - return new this.constructor().copy( this ); + this.min.fromArray( json.min ); + this.max.fromArray( json.max ); + return this; } } -class Matrix4 { +const _points = [ + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3() +]; - constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { +const _vector$7 = /*@__PURE__*/ new Vector3(); - Matrix4.prototype.isMatrix4 = true; +const _box$3 = /*@__PURE__*/ new Box3(); - this.elements = [ +// triangle centered vertices - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 +const _v0$3 = /*@__PURE__*/ new Vector3(); +const _v1$6 = /*@__PURE__*/ new Vector3(); +const _v2$3 = /*@__PURE__*/ new Vector3(); - ]; +// triangle edge vectors - if ( n11 !== undefined ) { +const _f0 = /*@__PURE__*/ new Vector3(); +const _f1 = /*@__PURE__*/ new Vector3(); +const _f2 = /*@__PURE__*/ new Vector3(); - this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); - - } +const _center = /*@__PURE__*/ new Vector3(); +const _extents = /*@__PURE__*/ new Vector3(); +const _triangleNormal = /*@__PURE__*/ new Vector3(); +const _testAxis = /*@__PURE__*/ new Vector3(); - } +function satForAxes( axes, v0, v1, v2, extents ) { - set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { - const te = this.elements; + _testAxis.fromArray( axes, i ); + // project the aabb onto the separating axis + const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); + // project all 3 vertices of the triangle onto the separating axis + const p0 = v0.dot( _testAxis ); + const p1 = v1.dot( _testAxis ); + const p2 = v2.dot( _testAxis ); + // actual test, basically see if either of the most extreme of the triangle points intersects r + if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { - te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; - te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; - te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; - te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; + // points of the projected triangle are outside the projected half-length of the aabb + // the axis is separating and we can exit + return false; - return this; + } } - identity() { + return true; - this.set( +} - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 +const _box$2 = /*@__PURE__*/ new Box3(); +const _v1$5 = /*@__PURE__*/ new Vector3(); +const _v2$2 = /*@__PURE__*/ new Vector3(); - ); +/** + * An analytical 3D sphere defined by a center and radius. This class is mainly + * used as a Bounding Sphere for 3D objects. + */ +class Sphere { - return this; + /** + * Constructs a new sphere. + * + * @param {Vector3} [center=(0,0,0)] - The center of the sphere + * @param {number} [radius=-1] - The radius of the sphere. + */ + constructor( center = new Vector3(), radius = -1 ) { - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSphere = true; - clone() { + /** + * The center of the sphere + * + * @type {Vector3} + */ + this.center = center; - return new Matrix4().fromArray( this.elements ); + /** + * The radius of the sphere. + * + * @type {number} + */ + this.radius = radius; } - copy( m ) { - - const te = this.elements; - const me = m.elements; + /** + * Sets the sphere's components by copying the given values. + * + * @param {Vector3} center - The center. + * @param {number} radius - The radius. + * @return {Sphere} A reference to this sphere. + */ + set( center, radius ) { - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; - te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; - te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; - te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; + this.center.copy( center ); + this.radius = radius; return this; } - copyPosition( m ) { + /** + * Computes the minimum bounding sphere for list of points. + * If the optional center point is given, it is used as the sphere's + * center. Otherwise, the center of the axis-aligned bounding box + * encompassing the points is calculated. + * + * @param {Array} points - A list of points in 3D space. + * @param {Vector3} [optionalCenter] - The center of the sphere. + * @return {Sphere} A reference to this sphere. + */ + setFromPoints( points, optionalCenter ) { - const te = this.elements, me = m.elements; + const center = this.center; - te[ 12 ] = me[ 12 ]; - te[ 13 ] = me[ 13 ]; - te[ 14 ] = me[ 14 ]; + if ( optionalCenter !== undefined ) { - return this; + center.copy( optionalCenter ); - } + } else { - setFromMatrix3( m ) { + _box$2.setFromPoints( points ).getCenter( center ); - const me = m.elements; + } - this.set( + let maxRadiusSq = 0; - me[ 0 ], me[ 3 ], me[ 6 ], 0, - me[ 1 ], me[ 4 ], me[ 7 ], 0, - me[ 2 ], me[ 5 ], me[ 8 ], 0, - 0, 0, 0, 1 + for ( let i = 0, il = points.length; i < il; i ++ ) { - ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + + } + + this.radius = Math.sqrt( maxRadiusSq ); return this; } - extractBasis( xAxis, yAxis, zAxis ) { + /** + * Copies the values of the given sphere to this instance. + * + * @param {Sphere} sphere - The sphere to copy. + * @return {Sphere} A reference to this sphere. + */ + copy( sphere ) { - xAxis.setFromMatrixColumn( this, 0 ); - yAxis.setFromMatrixColumn( this, 1 ); - zAxis.setFromMatrixColumn( this, 2 ); + this.center.copy( sphere.center ); + this.radius = sphere.radius; return this; } - makeBasis( xAxis, yAxis, zAxis ) { + /** + * Returns `true` if the sphere is empty (the radius set to a negative number). + * + * Spheres with a radius of `0` contain only their center point and are not + * considered to be empty. + * + * @return {boolean} Whether this sphere is empty or not. + */ + isEmpty() { - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); + return ( this.radius < 0 ); + + } + + /** + * Makes this sphere empty which means in encloses a zero space in 3D. + * + * @return {Sphere} A reference to this sphere. + */ + makeEmpty() { + + this.center.set( 0, 0, 0 ); + this.radius = -1; return this; } - extractRotation( m ) { + /** + * Returns `true` if this sphere contains the given point inclusive of + * the surface of the sphere. + * + * @param {Vector3} point - The point to check. + * @return {boolean} Whether this sphere contains the given point or not. + */ + containsPoint( point ) { - // this method does not support reflection matrices + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - const te = this.elements; - const me = m.elements; + } - const scaleX = 1 / _v1$4.setFromMatrixColumn( m, 0 ).length(); - const scaleY = 1 / _v1$4.setFromMatrixColumn( m, 1 ).length(); - const scaleZ = 1 / _v1$4.setFromMatrixColumn( m, 2 ).length(); + /** + * Returns the closest distance from the boundary of the sphere to the + * given point. If the sphere contains the point, the distance will + * be negative. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The distance to the point. + */ + distanceToPoint( point ) { - te[ 0 ] = me[ 0 ] * scaleX; - te[ 1 ] = me[ 1 ] * scaleX; - te[ 2 ] = me[ 2 ] * scaleX; - te[ 3 ] = 0; + return ( point.distanceTo( this.center ) - this.radius ); - te[ 4 ] = me[ 4 ] * scaleY; - te[ 5 ] = me[ 5 ] * scaleY; - te[ 6 ] = me[ 6 ] * scaleY; - te[ 7 ] = 0; + } - te[ 8 ] = me[ 8 ] * scaleZ; - te[ 9 ] = me[ 9 ] * scaleZ; - te[ 10 ] = me[ 10 ] * scaleZ; - te[ 11 ] = 0; + /** + * Returns `true` if this sphere intersects with the given one. + * + * @param {Sphere} sphere - The sphere to test. + * @return {boolean} Whether this sphere intersects with the given one or not. + */ + intersectsSphere( sphere ) { - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; + const radiusSum = this.radius + sphere.radius; - return this; + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); } - makeRotationFromEuler( euler ) { + /** + * Returns `true` if this sphere intersects with the given box. + * + * @param {Box3} box - The box to test. + * @return {boolean} Whether this sphere intersects with the given box or not. + */ + intersectsBox( box ) { - const te = this.elements; + return box.intersectsSphere( this ); - const x = euler.x, y = euler.y, z = euler.z; - const a = Math.cos( x ), b = Math.sin( x ); - const c = Math.cos( y ), d = Math.sin( y ); - const e = Math.cos( z ), f = Math.sin( z ); + } - if ( euler.order === 'XYZ' ) { + /** + * Returns `true` if this sphere intersects with the given plane. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether this sphere intersects with the given plane or not. + */ + intersectsPlane( plane ) { - const ae = a * e, af = a * f, be = b * e, bf = b * f; + return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; - te[ 0 ] = c * e; - te[ 4 ] = - c * f; - te[ 8 ] = d; + } - te[ 1 ] = af + be * d; - te[ 5 ] = ae - bf * d; - te[ 9 ] = - b * c; + /** + * Clamps a point within the sphere. If the point is outside the sphere, it + * will clamp it to the closest point on the edge of the sphere. Points + * already inside the sphere will not be affected. + * + * @param {Vector3} point - The plane to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ + clampPoint( point, target ) { - te[ 2 ] = bf - ae * d; - te[ 6 ] = be + af * d; - te[ 10 ] = a * c; + const deltaLengthSq = this.center.distanceToSquared( point ); - } else if ( euler.order === 'YXZ' ) { + target.copy( point ); - const ce = c * e, cf = c * f, de = d * e, df = d * f; + if ( deltaLengthSq > ( this.radius * this.radius ) ) { - te[ 0 ] = ce + df * b; - te[ 4 ] = de * b - cf; - te[ 8 ] = a * d; + target.sub( this.center ).normalize(); + target.multiplyScalar( this.radius ).add( this.center ); - te[ 1 ] = a * f; - te[ 5 ] = a * e; - te[ 9 ] = - b; + } - te[ 2 ] = cf * b - de; - te[ 6 ] = df + ce * b; - te[ 10 ] = a * c; + return target; - } else if ( euler.order === 'ZXY' ) { + } - const ce = c * e, cf = c * f, de = d * e, df = d * f; + /** + * Returns a bounding box that encloses this sphere. + * + * @param {Box3} target - The target box that is used to store the method's result. + * @return {Box3} The bounding box that encloses this sphere. + */ + getBoundingBox( target ) { - te[ 0 ] = ce - df * b; - te[ 4 ] = - a * f; - te[ 8 ] = de + cf * b; + if ( this.isEmpty() ) { - te[ 1 ] = cf + de * b; - te[ 5 ] = a * e; - te[ 9 ] = df - ce * b; + // Empty sphere produces empty bounding box + target.makeEmpty(); + return target; - te[ 2 ] = - a * d; - te[ 6 ] = b; - te[ 10 ] = a * c; + } - } else if ( euler.order === 'ZYX' ) { + target.set( this.center, this.center ); + target.expandByScalar( this.radius ); - const ae = a * e, af = a * f, be = b * e, bf = b * f; + return target; - te[ 0 ] = c * e; - te[ 4 ] = be * d - af; - te[ 8 ] = ae * d + bf; + } - te[ 1 ] = c * f; - te[ 5 ] = bf * d + ae; - te[ 9 ] = af * d - be; + /** + * Transforms this sphere with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Sphere} A reference to this sphere. + */ + applyMatrix4( matrix ) { - te[ 2 ] = - d; - te[ 6 ] = b * c; - te[ 10 ] = a * c; + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); - } else if ( euler.order === 'YZX' ) { + return this; - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; + } - te[ 0 ] = c * e; - te[ 4 ] = bd - ac * f; - te[ 8 ] = bc * f + ad; + /** + * Translates the sphere's center by the given offset. + * + * @param {Vector3} offset - The offset. + * @return {Sphere} A reference to this sphere. + */ + translate( offset ) { - te[ 1 ] = f; - te[ 5 ] = a * e; - te[ 9 ] = - b * e; + this.center.add( offset ); - te[ 2 ] = - d * e; - te[ 6 ] = ad * f + bc; - te[ 10 ] = ac - bd * f; + return this; - } else if ( euler.order === 'XZY' ) { + } - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; + /** + * Expands the boundaries of this sphere to include the given point. + * + * @param {Vector3} point - The point to include. + * @return {Sphere} A reference to this sphere. + */ + expandByPoint( point ) { - te[ 0 ] = c * e; - te[ 4 ] = - f; - te[ 8 ] = d * e; + if ( this.isEmpty() ) { - te[ 1 ] = ac * f + bd; - te[ 5 ] = a * e; - te[ 9 ] = ad * f - bc; + this.center.copy( point ); - te[ 2 ] = bc * f - ad; - te[ 6 ] = b * e; - te[ 10 ] = bd * f + ac; + this.radius = 0; + + return this; } - // bottom row - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; + _v1$5.subVectors( point, this.center ); - // last column - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; + const lengthSq = _v1$5.lengthSq(); - return this; + if ( lengthSq > ( this.radius * this.radius ) ) { - } + // calculate the minimal sphere - makeRotationFromQuaternion( q ) { + const length = Math.sqrt( lengthSq ); - return this.compose( _zero, q, _one ); + const delta = ( length - this.radius ) * 0.5; - } + this.center.addScaledVector( _v1$5, delta / length ); - lookAt( eye, target, up ) { + this.radius += delta; - const te = this.elements; + } - _z.subVectors( eye, target ); + return this; - if ( _z.lengthSq() === 0 ) { + } - // eye and target are in the same position + /** + * Expands this sphere to enclose both the original sphere and the given sphere. + * + * @param {Sphere} sphere - The sphere to include. + * @return {Sphere} A reference to this sphere. + */ + union( sphere ) { - _z.z = 1; + if ( sphere.isEmpty() ) { + + return this; } - _z.normalize(); - _x.crossVectors( up, _z ); + if ( this.isEmpty() ) { - if ( _x.lengthSq() === 0 ) { + this.copy( sphere ); - // up and z are parallel + return this; - if ( Math.abs( up.z ) === 1 ) { + } - _z.x += 0.0001; + if ( this.center.equals( sphere.center ) === true ) { - } else { + this.radius = Math.max( this.radius, sphere.radius ); - _z.z += 0.0001; + } else { - } + _v2$2.subVectors( sphere.center, this.center ).setLength( sphere.radius ); - _z.normalize(); - _x.crossVectors( up, _z ); + this.expandByPoint( _v1$5.copy( sphere.center ).add( _v2$2 ) ); + + this.expandByPoint( _v1$5.copy( sphere.center ).sub( _v2$2 ) ); } - _x.normalize(); - _y.crossVectors( _z, _x ); + return this; - te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; - te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; - te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; + } - return this; + /** + * Returns `true` if this sphere is equal with the given one. + * + * @param {Sphere} sphere - The sphere to test for equality. + * @return {boolean} Whether this bounding sphere is equal with the given one. + */ + equals( sphere ) { + + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); } - multiply( m ) { + /** + * Returns a new sphere with copied values from this instance. + * + * @return {Sphere} A clone of this instance. + */ + clone() { - return this.multiplyMatrices( this, m ); + return new this.constructor().copy( this ); } - premultiply( m ) { + /** + * Returns a serialized structure of the bounding sphere. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { - return this.multiplyMatrices( m, this ); + return { + radius: this.radius, + center: this.center.toArray() + }; } - multiplyMatrices( a, b ) { + /** + * Returns a serialized structure of the bounding sphere. + * + * @param {Object} json - The serialized json to set the sphere from. + * @return {Box3} A reference to this bounding sphere. + */ + fromJSON( json ) { - const ae = a.elements; - const be = b.elements; - const te = this.elements; + this.radius = json.radius; + this.center.fromArray( json.center ); + return this; - const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; - const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; - const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; - const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; + } - const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; - const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; - const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; - const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; +} - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; - te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; - te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; - te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; +const _vector1 = /*@__PURE__*/ new Vector3(); +const _vector2$1 = /*@__PURE__*/ new Vector3(); +const _normalMatrix = /*@__PURE__*/ new Matrix3(); - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; - te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; - te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; - te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; +/** + * A two dimensional surface that extends infinitely in 3D space, represented + * in [Hessian normal form]{@link http://mathworld.wolfram.com/HessianNormalForm.html} + * by a unit length normal vector and a constant. + */ +class Plane { - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; - te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; - te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; - te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; + /** + * Constructs a new plane. + * + * @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane. + * @param {number} [constant=0] - The signed distance from the origin to the plane. + */ + constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { - te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; - te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; - te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; - te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPlane = true; + + /** + * A unit length vector defining the normal of the plane. + * + * @type {Vector3} + */ + this.normal = normal; + + /** + * The signed distance from the origin to the plane. + * + * @type {number} + * @default 0 + */ + this.constant = constant; + + } + + /** + * Sets the plane components by copying the given values. + * + * @param {Vector3} normal - The normal. + * @param {number} constant - The constant. + * @return {Plane} A reference to this plane. + */ + set( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; return this; } - multiplyScalar( s ) { + /** + * Sets the plane components by defining `x`, `y`, `z` as the + * plane normal and `w` as the constant. + * + * @param {number} x - The value for the normal's x component. + * @param {number} y - The value for the normal's y component. + * @param {number} z - The value for the normal's z component. + * @param {number} w - The constant value. + * @return {Plane} A reference to this plane. + */ + setComponents( x, y, z, w ) { - const te = this.elements; + this.normal.set( x, y, z ); + this.constant = w; - te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; - te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; - te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; - te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; + return this; + + } + + /** + * Sets the plane from the given normal and coplanar point (that is a point + * that lies onto the plane). + * + * @param {Vector3} normal - The normal. + * @param {Vector3} point - A coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromNormalAndCoplanarPoint( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); return this; } - determinant() { + /** + * Sets the plane from three coplanar points. The winding order is + * assumed to be counter-clockwise, and determines the direction of + * the plane normal. + * + * @param {Vector3} a - The first coplanar point. + * @param {Vector3} b - The second coplanar point. + * @param {Vector3} c - The third coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromCoplanarPoints( a, b, c ) { - const te = this.elements; + const normal = _vector1.subVectors( c, b ).cross( _vector2$1.subVectors( a, b ) ).normalize(); - const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; - const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; - const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; - const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) + this.setFromNormalAndCoplanarPoint( normal, a ); - return ( - n41 * ( - + n14 * n23 * n32 - - n13 * n24 * n32 - - n14 * n22 * n33 - + n12 * n24 * n33 - + n13 * n22 * n34 - - n12 * n23 * n34 - ) + - n42 * ( - + n11 * n23 * n34 - - n11 * n24 * n33 - + n14 * n21 * n33 - - n13 * n21 * n34 - + n13 * n24 * n31 - - n14 * n23 * n31 - ) + - n43 * ( - + n11 * n24 * n32 - - n11 * n22 * n34 - - n14 * n21 * n32 - + n12 * n21 * n34 - + n14 * n22 * n31 - - n12 * n24 * n31 - ) + - n44 * ( - - n13 * n22 * n31 - - n11 * n23 * n32 - + n11 * n22 * n33 - + n13 * n21 * n32 - - n12 * n21 * n33 - + n12 * n23 * n31 - ) - - ); + return this; } - transpose() { - - const te = this.elements; - let tmp; - - tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; - tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; - tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; + /** + * Copies the values of the given plane to this instance. + * + * @param {Plane} plane - The plane to copy. + * @return {Plane} A reference to this plane. + */ + copy( plane ) { - tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; - tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; - tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; + this.normal.copy( plane.normal ); + this.constant = plane.constant; return this; } - setPosition( x, y, z ) { + /** + * Normalizes the plane normal and adjusts the constant accordingly. + * + * @return {Plane} A reference to this plane. + */ + normalize() { - const te = this.elements; + // Note: will lead to a divide by zero if the plane is invalid. - if ( x.isVector3 ) { + const inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; - te[ 12 ] = x.x; - te[ 13 ] = x.y; - te[ 14 ] = x.z; + return this; - } else { + } - te[ 12 ] = x; - te[ 13 ] = y; - te[ 14 ] = z; + /** + * Negates both the plane normal and the constant. + * + * @return {Plane} A reference to this plane. + */ + negate() { - } + this.constant *= -1; + this.normal.negate(); return this; } - invert() { + /** + * Returns the signed distance from the given point to this plane. + * + * @param {Vector3} point - The point to compute the distance for. + * @return {number} The signed distance. + */ + distanceToPoint( point ) { - // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm - const te = this.elements, + return this.normal.dot( point ) + this.constant; - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], - n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], - n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], - n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], + } - t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, - t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, - t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, - t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; + /** + * Returns the signed distance from the given sphere to this plane. + * + * @param {Sphere} sphere - The sphere to compute the distance for. + * @return {number} The signed distance. + */ + distanceToSphere( sphere ) { - const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; + return this.distanceToPoint( sphere.center ) - sphere.radius; - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + } - const detInv = 1 / det; + /** + * Projects a the given point onto the plane. + * + * @param {Vector3} point - The point to project. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The projected point on the plane. + */ + projectPoint( point, target ) { - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; - te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; - te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; + return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); - te[ 4 ] = t12 * detInv; - te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; - te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; - te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; + } - te[ 8 ] = t13 * detInv; - te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; - te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; - te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; + /** + * Returns the intersection point of the passed line and the plane. Returns + * `null` if the line does not intersect. Returns the line's starting point if + * the line is coplanar with the plane. + * + * @param {Line3} line - The line to compute the intersection for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectLine( line, target ) { - te[ 12 ] = t14 * detInv; - te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; - te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; - te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; + const direction = line.delta( _vector1 ); - return this; + const denominator = this.normal.dot( direction ); - } + if ( denominator === 0 ) { - scale( v ) { + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) === 0 ) { - const te = this.elements; - const x = v.x, y = v.y, z = v.z; + return target.copy( line.start ); - te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; - te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; - te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; - te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; + } - return this; + // Unsure if this is the correct method to handle this case. + return null; - } + } - getMaxScaleOnAxis() { + const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - const te = this.elements; + if ( t < 0 || t > 1 ) { - const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; - const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; - const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; + return null; - return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); + } + + return target.copy( line.start ).addScaledVector( direction, t ); } - makeTranslation( x, y, z ) { + /** + * Returns `true` if the given line segment intersects with (passes through) the plane. + * + * @param {Line3} line - The line to test. + * @return {boolean} Whether the given line segment intersects with the plane or not. + */ + intersectsLine( line ) { - if ( x.isVector3 ) { + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - this.set( + const startSign = this.distanceToPoint( line.start ); + const endSign = this.distanceToPoint( line.end ); - 1, 0, 0, x.x, - 0, 1, 0, x.y, - 0, 0, 1, x.z, - 0, 0, 0, 1 + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - ); + } - } else { + /** + * Returns `true` if the given bounding box intersects with the plane. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with the plane or not. + */ + intersectsBox( box ) { - this.set( + return box.intersectsPlane( this ); - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 + } - ); + /** + * Returns `true` if the given bounding sphere intersects with the plane. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with the plane or not. + */ + intersectsSphere( sphere ) { - } + return sphere.intersectsPlane( this ); - return this; + } + + /** + * Returns a coplanar vector to the plane, by calculating the + * projection of the normal at the origin onto the plane. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The coplanar point. + */ + coplanarPoint( target ) { + + return target.copy( this.normal ).multiplyScalar( - this.constant ); } - makeRotationX( theta ) { + /** + * Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform. + * + * The optional normal matrix can be pre-computed like so: + * ```js + * const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + * ``` + * + * @param {Matrix4} matrix - The transformation matrix. + * @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix. + * @return {Plane} A reference to this plane. + */ + applyMatrix4( matrix, optionalNormalMatrix ) { - const c = Math.cos( theta ), s = Math.sin( theta ); + const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); - this.set( + const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); - 1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1 + const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - ); + this.constant = - referencePoint.dot( normal ); return this; } - makeRotationY( theta ) { + /** + * Translates the plane by the distance defined by the given offset vector. + * Note that this only affects the plane constant and will not affect the normal vector. + * + * @param {Vector3} offset - The offset vector. + * @return {Plane} A reference to this plane. + */ + translate( offset ) { - const c = Math.cos( theta ), s = Math.sin( theta ); + this.constant -= offset.dot( this.normal ); - this.set( + return this; - c, 0, s, 0, - 0, 1, 0, 0, - - s, 0, c, 0, - 0, 0, 0, 1 + } - ); + /** + * Returns `true` if this plane is equal with the given one. + * + * @param {Plane} plane - The plane to test for equality. + * @return {boolean} Whether this plane is equal with the given one. + */ + equals( plane ) { - return this; + return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); } - makeRotationZ( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); + /** + * Returns a new plane with copied values from this instance. + * + * @return {Plane} A clone of this instance. + */ + clone() { - this.set( + return new this.constructor().copy( this ); - c, - s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 + } - ); +} - return this; +const _sphere$6 = /*@__PURE__*/ new Sphere(); +const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 ); +const _vector$6 = /*@__PURE__*/ new Vector3(); - } +/** + * Frustums are used to determine what is inside the camera's field of view. + * They help speed up the rendering process - objects which lie outside a camera's + * frustum can safely be excluded from rendering. + * + * This class is mainly intended for use internally by a renderer. + */ +class Frustum { - makeRotationAxis( axis, angle ) { + /** + * Constructs a new frustum. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + */ + constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { - // Based on http://www.gamedev.net/reference/articles/article1199.asp + /** + * This array holds the planes that enclose the frustum. + * + * @type {Array} + */ + this.planes = [ p0, p1, p2, p3, p4, p5 ]; - const c = Math.cos( angle ); - const s = Math.sin( angle ); - const t = 1 - c; - const x = axis.x, y = axis.y, z = axis.z; - const tx = t * x, ty = t * y; + } - this.set( + /** + * Sets the frustum planes by copying the given planes. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + * @return {Frustum} A reference to this frustum. + */ + set( p0, p1, p2, p3, p4, p5 ) { - tx * x + c, tx * y - s * z, tx * z + s * y, 0, - tx * y + s * z, ty * y + c, ty * z - s * x, 0, - tx * z - s * y, ty * z + s * x, t * z * z + c, 0, - 0, 0, 0, 1 + const planes = this.planes; - ); + planes[ 0 ].copy( p0 ); + planes[ 1 ].copy( p1 ); + planes[ 2 ].copy( p2 ); + planes[ 3 ].copy( p3 ); + planes[ 4 ].copy( p4 ); + planes[ 5 ].copy( p5 ); return this; } - makeScale( x, y, z ) { + /** + * Copies the values of the given frustum to this instance. + * + * @param {Frustum} frustum - The frustum to copy. + * @return {Frustum} A reference to this frustum. + */ + copy( frustum ) { - this.set( + const planes = this.planes; - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 + for ( let i = 0; i < 6; i ++ ) { - ); + planes[ i ].copy( frustum.planes[ i ] ); + + } return this; } - makeShear( xy, xz, yx, yz, zx, zy ) { + /** + * Sets the frustum planes from the given projection matrix. + * + * @param {Matrix4} m - The projection matrix. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} coordinateSystem - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Frustum} A reference to this frustum. + */ + setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { - this.set( + const planes = this.planes; + const me = m.elements; + const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; + const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; + const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; + const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - 1, yx, zx, 0, - xy, 1, zy, 0, - xz, yz, 1, 0, - 0, 0, 0, 1 + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - ); + if ( reversedDepth ) { - return this; + planes[ 4 ].setComponents( me2, me6, me10, me14 ).normalize(); // far + planes[ 5 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // near - } + } else { - compose( position, quaternion, scale ) { + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // far - const te = this.elements; + if ( coordinateSystem === WebGLCoordinateSystem ) { - const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; - const x2 = x + x, y2 = y + y, z2 = z + z; - const xx = x * x2, xy = x * y2, xz = x * z2; - const yy = y * y2, yz = y * z2, zz = z * z2; - const wx = w * x2, wy = w * y2, wz = w * z2; + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); // near - const sx = scale.x, sy = scale.y, sz = scale.z; + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; - te[ 1 ] = ( xy + wz ) * sx; - te[ 2 ] = ( xz - wy ) * sx; - te[ 3 ] = 0; + planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); // near - te[ 4 ] = ( xy - wz ) * sy; - te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; - te[ 6 ] = ( yz + wx ) * sy; - te[ 7 ] = 0; + } else { - te[ 8 ] = ( xz + wy ) * sz; - te[ 9 ] = ( yz - wx ) * sz; - te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; - te[ 11 ] = 0; + throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); - te[ 12 ] = position.x; - te[ 13 ] = position.y; - te[ 14 ] = position.z; - te[ 15 ] = 1; + } + + } return this; } - decompose( position, quaternion, scale ) { + /** + * Returns `true` if the 3D object's bounding sphere is intersecting this frustum. + * + * Note that the 3D object must have a geometry so that the bounding sphere can be calculated. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object's bounding sphere is intersecting this frustum or not. + */ + intersectsObject( object ) { - const te = this.elements; + if ( object.boundingSphere !== undefined ) { - let sx = _v1$4.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); - const sy = _v1$4.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); - const sz = _v1$4.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); + if ( object.boundingSphere === null ) object.computeBoundingSphere(); - // if determine is negative, we need to invert one scale - const det = this.determinant(); - if ( det < 0 ) sx = - sx; + _sphere$6.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); - position.x = te[ 12 ]; - position.y = te[ 13 ]; - position.z = te[ 14 ]; + } else { - // scale the rotation part - _m1$4.copy( this ); + const geometry = object.geometry; - const invSX = 1 / sx; - const invSY = 1 / sy; - const invSZ = 1 / sz; + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _m1$4.elements[ 0 ] *= invSX; - _m1$4.elements[ 1 ] *= invSX; - _m1$4.elements[ 2 ] *= invSX; + _sphere$6.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); - _m1$4.elements[ 4 ] *= invSY; - _m1$4.elements[ 5 ] *= invSY; - _m1$4.elements[ 6 ] *= invSY; + } - _m1$4.elements[ 8 ] *= invSZ; - _m1$4.elements[ 9 ] *= invSZ; - _m1$4.elements[ 10 ] *= invSZ; + return this.intersectsSphere( _sphere$6 ); - quaternion.setFromRotationMatrix( _m1$4 ); + } - scale.x = sx; - scale.y = sy; - scale.z = sz; + /** + * Returns `true` if the given sprite is intersecting this frustum. + * + * @param {Sprite} sprite - The sprite to test. + * @return {boolean} Whether the sprite is intersecting this frustum or not. + */ + intersectsSprite( sprite ) { - return this; + _sphere$6.center.set( 0, 0, 0 ); - } + const offset = _defaultSpriteCenter.distanceTo( sprite.center ); - makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { + _sphere$6.radius = 0.7071067811865476 + offset; + _sphere$6.applyMatrix4( sprite.matrixWorld ); - const te = this.elements; - const x = 2 * near / ( right - left ); - const y = 2 * near / ( top - bottom ); + return this.intersectsSphere( _sphere$6 ); - const a = ( right + left ) / ( right - left ); - const b = ( top + bottom ) / ( top - bottom ); + } - let c, d; + /** + * Returns `true` if the given bounding sphere is intersecting this frustum. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the bounding sphere is intersecting this frustum or not. + */ + intersectsSphere( sphere ) { - if ( coordinateSystem === WebGLCoordinateSystem ) { + const planes = this.planes; + const center = sphere.center; + const negRadius = - sphere.radius; - c = - ( far + near ) / ( far - near ); - d = ( - 2 * far * near ) / ( far - near ); + for ( let i = 0; i < 6; i ++ ) { - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + const distance = planes[ i ].distanceToPoint( center ); - c = - far / ( far - near ); - d = ( - far * near ) / ( far - near ); + if ( distance < negRadius ) { - } else { + return false; - throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); + } } - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; - - return this; + return true; } - makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { - - const te = this.elements; - const w = 1.0 / ( right - left ); - const h = 1.0 / ( top - bottom ); - const p = 1.0 / ( far - near ); + /** + * Returns `true` if the given bounding box is intersecting this frustum. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box is intersecting this frustum or not. + */ + intersectsBox( box ) { - const x = ( right + left ) * w; - const y = ( top + bottom ) * h; + const planes = this.planes; - let z, zInv; + for ( let i = 0; i < 6; i ++ ) { - if ( coordinateSystem === WebGLCoordinateSystem ) { + const plane = planes[ i ]; - z = ( far + near ) * p; - zInv = - 2 * p; + // corner at max distance - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; + _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; + _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; - z = near * p; - zInv = - 1 * p; + if ( plane.distanceToPoint( _vector$6 ) < 0 ) { - } else { + return false; - throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); + } } - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = zInv; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; + return true; } - equals( matrix ) { + /** + * Returns `true` if the given point lies within the frustum. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the point lies within this frustum or not. + */ + containsPoint( point ) { - const te = this.elements; - const me = matrix.elements; + const planes = this.planes; - for ( let i = 0; i < 16; i ++ ) { + for ( let i = 0; i < 6; i ++ ) { - if ( te[ i ] !== me[ i ] ) return false; + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } } @@ -20740,510 +26104,845 @@ class Matrix4 { } - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 16; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } + /** + * Returns a new frustum with copied values from this instance. + * + * @return {Frustum} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().copy( this ); } - toArray( array = [], offset = 0 ) { +} - const te = this.elements; +/** + * Represents a 4x4 matrix. + * + * The most common use of a 4x4 matrix in 3D computer graphics is as a transformation matrix. + * For an introduction to transformation matrices as used in WebGL, check out [this tutorial]{@link https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices} + * + * This allows a 3D vector representing a point in 3D space to undergo + * transformations such as translation, rotation, shear, scale, reflection, + * orthogonal or perspective projection and so on, by being multiplied by the + * matrix. This is known as `applying` the matrix to the vector. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix4(); + * m.set( 11, 12, 13, 14, + * 21, 22, 23, 24, + * 31, 32, 33, 34, + * 41, 42, 43, 44 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, 41, + * 12, 22, 32, 42, + * 13, 23, 33, 43, + * 14, 24, 34, 44 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix4 { - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - array[ offset + 3 ] = te[ 3 ]; + /** + * Constructs a new 4x4 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + */ + constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix4.prototype.isMatrix4 = true; - array[ offset + 8 ] = te[ 8 ]; - array[ offset + 9 ] = te[ 9 ]; - array[ offset + 10 ] = te[ 10 ]; - array[ offset + 11 ] = te[ 11 ]; + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ - array[ offset + 12 ] = te[ 12 ]; - array[ offset + 13 ] = te[ 13 ]; - array[ offset + 14 ] = te[ 14 ]; - array[ offset + 15 ] = te[ 15 ]; + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - return array; + ]; - } + if ( n11 !== undefined ) { -} + this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); -const _v1$4 = /*@__PURE__*/ new Vector3(); -const _m1$4 = /*@__PURE__*/ new Matrix4(); -const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); -const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); -const _x = /*@__PURE__*/ new Vector3(); -const _y = /*@__PURE__*/ new Vector3(); -const _z = /*@__PURE__*/ new Vector3(); + } -function WebGLAnimation() { + } - let context = null; - let isAnimating = false; - let animationLoop = null; - let requestId = null; + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + * @return {Matrix4} A reference to this matrix. + */ + set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - function onAnimationFrame( time, frame ) { + const te = this.elements; - animationLoop( time, frame ); + te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; + te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; + te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; + te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; - requestId = context.requestAnimationFrame( onAnimationFrame ); + return this; } - return { - - start: function () { - - if ( isAnimating === true ) return; - if ( animationLoop === null ) return; - - requestId = context.requestAnimationFrame( onAnimationFrame ); + /** + * Sets this matrix to the 4x4 identity matrix. + * + * @return {Matrix4} A reference to this matrix. + */ + identity() { - isAnimating = true; + this.set( - }, + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - stop: function () { + ); - context.cancelAnimationFrame( requestId ); + return this; - isAnimating = false; + } - }, + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix4} A clone of this instance. + */ + clone() { - setAnimationLoop: function ( callback ) { + return new Matrix4().fromArray( this.elements ); - animationLoop = callback; + } - }, + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix4} m - The matrix to copy. + * @return {Matrix4} A reference to this matrix. + */ + copy( m ) { - setContext: function ( value ) { + const te = this.elements; + const me = m.elements; - context = value; + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; + te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; + te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; + te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; - } + return this; - }; + } -} + /** + * Copies the translation component of the given matrix + * into this matrix's translation component. + * + * @param {Matrix4} m - The matrix to copy the translation component. + * @return {Matrix4} A reference to this matrix. + */ + copyPosition( m ) { -function WebGLAttributes( gl, capabilities ) { + const te = this.elements, me = m.elements; - const isWebGL2 = capabilities.isWebGL2; + te[ 12 ] = me[ 12 ]; + te[ 13 ] = me[ 13 ]; + te[ 14 ] = me[ 14 ]; - const buffers = new WeakMap(); + return this; - function createBuffer( attribute, bufferType ) { + } - const array = attribute.array; - const usage = attribute.usage; - const size = array.byteLength; + /** + * Set the upper 3x3 elements of this matrix to the values of given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Matrix4} A reference to this matrix. + */ + setFromMatrix3( m ) { - const buffer = gl.createBuffer(); + const me = m.elements; - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); + this.set( - attribute.onUploadCallback(); + me[ 0 ], me[ 3 ], me[ 6 ], 0, + me[ 1 ], me[ 4 ], me[ 7 ], 0, + me[ 2 ], me[ 5 ], me[ 8 ], 0, + 0, 0, 0, 1 - let type; + ); - if ( array instanceof Float32Array ) { + return this; - type = gl.FLOAT; + } - } else if ( array instanceof Uint16Array ) { + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { - if ( attribute.isFloat16BufferAttribute ) { + xAxis.setFromMatrixColumn( this, 0 ); + yAxis.setFromMatrixColumn( this, 1 ); + zAxis.setFromMatrixColumn( this, 2 ); - if ( isWebGL2 ) { + return this; - type = gl.HALF_FLOAT; + } - } else { + /** + * Sets the given basis vectors to this matrix. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeBasis( xAxis, yAxis, zAxis ) { - throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); + this.set( + xAxis.x, yAxis.x, zAxis.x, 0, + xAxis.y, yAxis.y, zAxis.y, 0, + xAxis.z, yAxis.z, zAxis.z, 0, + 0, 0, 0, 1 + ); - } + return this; - } else { + } - type = gl.UNSIGNED_SHORT; + /** + * Extracts the rotation component of the given matrix + * into this matrix's rotation component. + * + * Note: This method does not support reflection matrices. + * + * @param {Matrix4} m - The matrix. + * @return {Matrix4} A reference to this matrix. + */ + extractRotation( m ) { - } + const te = this.elements; + const me = m.elements; - } else if ( array instanceof Int16Array ) { + const scaleX = 1 / _v1$4.setFromMatrixColumn( m, 0 ).length(); + const scaleY = 1 / _v1$4.setFromMatrixColumn( m, 1 ).length(); + const scaleZ = 1 / _v1$4.setFromMatrixColumn( m, 2 ).length(); - type = gl.SHORT; + te[ 0 ] = me[ 0 ] * scaleX; + te[ 1 ] = me[ 1 ] * scaleX; + te[ 2 ] = me[ 2 ] * scaleX; + te[ 3 ] = 0; - } else if ( array instanceof Uint32Array ) { + te[ 4 ] = me[ 4 ] * scaleY; + te[ 5 ] = me[ 5 ] * scaleY; + te[ 6 ] = me[ 6 ] * scaleY; + te[ 7 ] = 0; - type = gl.UNSIGNED_INT; + te[ 8 ] = me[ 8 ] * scaleZ; + te[ 9 ] = me[ 9 ] * scaleZ; + te[ 10 ] = me[ 10 ] * scaleZ; + te[ 11 ] = 0; - } else if ( array instanceof Int32Array ) { + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; - type = gl.INT; + return this; - } else if ( array instanceof Int8Array ) { + } - type = gl.BYTE; + /** + * Sets the rotation component (the upper left 3x3 matrix) of this matrix to + * the rotation specified by the given Euler angles. The rest of + * the matrix is set to the identity. Depending on the {@link Euler#order}, + * there are six possible outcomes. See [this page]{@link https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix} + * for a complete list. + * + * @param {Euler} euler - The Euler angles. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationFromEuler( euler ) { - } else if ( array instanceof Uint8Array ) { + const te = this.elements; - type = gl.UNSIGNED_BYTE; + const x = euler.x, y = euler.y, z = euler.z; + const a = Math.cos( x ), b = Math.sin( x ); + const c = Math.cos( y ), d = Math.sin( y ); + const e = Math.cos( z ), f = Math.sin( z ); - } else if ( array instanceof Uint8ClampedArray ) { + if ( euler.order === 'XYZ' ) { - type = gl.UNSIGNED_BYTE; + const ae = a * e, af = a * f, be = b * e, bf = b * f; - } else { + te[ 0 ] = c * e; + te[ 4 ] = - c * f; + te[ 8 ] = d; - throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); + te[ 1 ] = af + be * d; + te[ 5 ] = ae - bf * d; + te[ 9 ] = - b * c; - } + te[ 2 ] = bf - ae * d; + te[ 6 ] = be + af * d; + te[ 10 ] = a * c; - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version, - size: size - }; + } else if ( euler.order === 'YXZ' ) { - } + const ce = c * e, cf = c * f, de = d * e, df = d * f; - function updateBuffer( buffer, attribute, bufferType ) { + te[ 0 ] = ce + df * b; + te[ 4 ] = de * b - cf; + te[ 8 ] = a * d; - const array = attribute.array; - const updateRange = attribute._updateRange; // @deprecated, r159 - const updateRanges = attribute.updateRanges; + te[ 1 ] = a * f; + te[ 5 ] = a * e; + te[ 9 ] = - b; - gl.bindBuffer( bufferType, buffer ); + te[ 2 ] = cf * b - de; + te[ 6 ] = df + ce * b; + te[ 10 ] = a * c; - if ( updateRange.count === - 1 && updateRanges.length === 0 ) { + } else if ( euler.order === 'ZXY' ) { - // Not using update ranges - gl.bufferSubData( bufferType, 0, array ); + const ce = c * e, cf = c * f, de = d * e, df = d * f; - } + te[ 0 ] = ce - df * b; + te[ 4 ] = - a * f; + te[ 8 ] = de + cf * b; - if ( updateRanges.length !== 0 ) { + te[ 1 ] = cf + de * b; + te[ 5 ] = a * e; + te[ 9 ] = df - ce * b; - for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + te[ 2 ] = - a * d; + te[ 6 ] = b; + te[ 10 ] = a * c; - const range = updateRanges[ i ]; - if ( isWebGL2 ) { + } else if ( euler.order === 'ZYX' ) { - gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, - array, range.start, range.count ); + const ae = a * e, af = a * f, be = b * e, bf = b * f; - } else { + te[ 0 ] = c * e; + te[ 4 ] = be * d - af; + te[ 8 ] = ae * d + bf; - gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, - array.subarray( range.start, range.start + range.count ) ); + te[ 1 ] = c * f; + te[ 5 ] = bf * d + ae; + te[ 9 ] = af * d - be; - } + te[ 2 ] = - d; + te[ 6 ] = b * c; + te[ 10 ] = a * c; - } + } else if ( euler.order === 'YZX' ) { - attribute.clearUpdateRanges(); + const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - } + te[ 0 ] = c * e; + te[ 4 ] = bd - ac * f; + te[ 8 ] = bc * f + ad; - // @deprecated, r159 - if ( updateRange.count !== - 1 ) { + te[ 1 ] = f; + te[ 5 ] = a * e; + te[ 9 ] = - b * e; - if ( isWebGL2 ) { + te[ 2 ] = - d * e; + te[ 6 ] = ad * f + bc; + te[ 10 ] = ac - bd * f; - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array, updateRange.offset, updateRange.count ); + } else if ( euler.order === 'XZY' ) { - } else { + const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); + te[ 0 ] = c * e; + te[ 4 ] = - f; + te[ 8 ] = d * e; - } + te[ 1 ] = ac * f + bd; + te[ 5 ] = a * e; + te[ 9 ] = ad * f - bc; - updateRange.count = - 1; // reset range + te[ 2 ] = bc * f - ad; + te[ 6 ] = b * e; + te[ 10 ] = bd * f + ac; } - attribute.onUploadCallback(); - - } - - // - - function get( attribute ) { + // bottom row + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + // last column + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; - return buffers.get( attribute ); + return this; } - function remove( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data ) { - - gl.deleteBuffer( data.buffer ); - - buffers.delete( attribute ); + /** + * Sets the rotation component of this matrix to the rotation specified by + * the given Quaternion as outlined [here]{@link https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion} + * The rest of the matrix is set to the identity. + * + * @param {Quaternion} q - The Quaternion. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationFromQuaternion( q ) { - } + return this.compose( _zero, q, _one ); } - function update( attribute, bufferType ) { - - if ( attribute.isGLBufferAttribute ) { + /** + * Sets the rotation component of the transformation matrix, looking from `eye` towards + * `target`, and oriented by the up-direction. + * + * @param {Vector3} eye - The eye vector. + * @param {Vector3} target - The target vector. + * @param {Vector3} up - The up vector. + * @return {Matrix4} A reference to this matrix. + */ + lookAt( eye, target, up ) { - const cached = buffers.get( attribute ); + const te = this.elements; - if ( ! cached || cached.version < attribute.version ) { + _z.subVectors( eye, target ); - buffers.set( attribute, { - buffer: attribute.buffer, - type: attribute.type, - bytesPerElement: attribute.elementSize, - version: attribute.version - } ); + if ( _z.lengthSq() === 0 ) { - } + // eye and target are in the same position - return; + _z.z = 1; } - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + _z.normalize(); + _x.crossVectors( up, _z ); - const data = buffers.get( attribute ); + if ( _x.lengthSq() === 0 ) { - if ( data === undefined ) { + // up and z are parallel - buffers.set( attribute, createBuffer( attribute, bufferType ) ); + if ( Math.abs( up.z ) === 1 ) { - } else if ( data.version < attribute.version ) { + _z.x += 0.0001; - if ( data.size !== attribute.array.byteLength ) { + } else { - throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); + _z.z += 0.0001; } - updateBuffer( data.buffer, attribute, bufferType ); - - data.version = attribute.version; + _z.normalize(); + _x.crossVectors( up, _z ); } - } - - return { + _x.normalize(); + _y.crossVectors( _z, _x ); - get: get, - remove: remove, - update: update + te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; + te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; + te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; - }; + return this; -} + } -const _vector$5 = /*@__PURE__*/ new Vector3(); -const _vector2 = /*@__PURE__*/ new Vector2(); + /** + * Post-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ + multiply( m ) { -class BufferAttribute { + return this.multiplyMatrices( this, m ); - constructor( array, itemSize, normalized = false ) { + } - if ( Array.isArray( array ) ) { + /** + * Pre-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ + premultiply( m ) { - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + return this.multiplyMatrices( m, this ); - } + } - this.isBufferAttribute = true; + /** + * Multiples the given 4x4 matrices and stores the result + * in this matrix. + * + * @param {Matrix4} a - The first matrix. + * @param {Matrix4} b - The second matrix. + * @return {Matrix4} A reference to this matrix. + */ + multiplyMatrices( a, b ) { - this.name = ''; + const ae = a.elements; + const be = b.elements; + const te = this.elements; - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized; + const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; + const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; + const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; + const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; - this.usage = StaticDrawUsage; - this._updateRange = { offset: 0, count: - 1 }; - this.updateRanges = []; - this.gpuType = FloatType; + const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; + const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; + const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; + const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; - this.version = 0; + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; + te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; + te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; + te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; - } + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; + te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; + te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; + te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; - onUploadCallback() {} + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; + te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; + te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; + te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; - set needsUpdate( value ) { + te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; + te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; + te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; + te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; - if ( value === true ) this.version ++; + return this; } - get updateRange() { - - warnOnce( 'THREE.BufferAttribute: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead.' ); // @deprecated, r159 - return this._updateRange; - - } + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix4} A reference to this matrix. + */ + multiplyScalar( s ) { - setUsage( value ) { + const te = this.elements; - this.usage = value; + te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; + te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; + te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; + te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; return this; } - addUpdateRange( start, count ) { - - this.updateRanges.push( { start, count } ); - - } - - clearUpdateRanges() { + /** + * Computes and returns the determinant of this matrix. + * + * Based on the method outlined [here]{@link http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html}. + * + * @return {number} The determinant. + */ + determinant() { - this.updateRanges.length = 0; + const te = this.elements; - } + const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; + const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; + const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; + const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; - copy( source ) { + //TODO: make this more efficient - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; - - this.usage = source.usage; - this.gpuType = source.gpuType; + return ( + n41 * ( + + n14 * n23 * n32 + - n13 * n24 * n32 + - n14 * n22 * n33 + + n12 * n24 * n33 + + n13 * n22 * n34 + - n12 * n23 * n34 + ) + + n42 * ( + + n11 * n23 * n34 + - n11 * n24 * n33 + + n14 * n21 * n33 + - n13 * n21 * n34 + + n13 * n24 * n31 + - n14 * n23 * n31 + ) + + n43 * ( + + n11 * n24 * n32 + - n11 * n22 * n34 + - n14 * n21 * n32 + + n12 * n21 * n34 + + n14 * n22 * n31 + - n12 * n24 * n31 + ) + + n44 * ( + - n13 * n22 * n31 + - n11 * n23 * n32 + + n11 * n22 * n33 + + n13 * n21 * n32 + - n12 * n21 * n33 + + n12 * n23 * n31 + ) - return this; + ); } - copyAt( index1, attribute, index2 ) { - - index1 *= this.itemSize; - index2 *= attribute.itemSize; + /** + * Transposes this matrix in place. + * + * @return {Matrix4} A reference to this matrix. + */ + transpose() { - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { + const te = this.elements; + let tmp; - this.array[ index1 + i ] = attribute.array[ index2 + i ]; + tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; + tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; + tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; - } + tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; + tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; + tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; return this; } - copyArray( array ) { + /** + * Sets the position component for this matrix from the given vector, + * without affecting the rest of the matrix. + * + * @param {number|Vector3} x - The x component of the vector or alternatively the vector object. + * @param {number} y - The y component of the vector. + * @param {number} z - The z component of the vector. + * @return {Matrix4} A reference to this matrix. + */ + setPosition( x, y, z ) { - this.array.set( array ); + const te = this.elements; - return this; + if ( x.isVector3 ) { - } + te[ 12 ] = x.x; + te[ 13 ] = x.y; + te[ 14 ] = x.z; - applyMatrix3( m ) { + } else { - if ( this.itemSize === 2 ) { + te[ 12 ] = x; + te[ 13 ] = y; + te[ 14 ] = z; - for ( let i = 0, l = this.count; i < l; i ++ ) { + } - _vector2.fromBufferAttribute( this, i ); - _vector2.applyMatrix3( m ); + return this; - this.setXY( i, _vector2.x, _vector2.y ); + } - } + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix4} A reference to this matrix. + */ + invert() { - } else if ( this.itemSize === 3 ) { + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + const te = this.elements, - for ( let i = 0, l = this.count; i < l; i ++ ) { + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], + n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], + n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], + n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], - _vector$5.fromBufferAttribute( this, i ); - _vector$5.applyMatrix3( m ); + t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, + t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, + t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, + t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; - } + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - } + const detInv = 1 / det; - return this; + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; + te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; + te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; - } + te[ 4 ] = t12 * detInv; + te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; + te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; + te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; - applyMatrix4( m ) { + te[ 8 ] = t13 * detInv; + te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; + te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; + te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; - for ( let i = 0, l = this.count; i < l; i ++ ) { + te[ 12 ] = t14 * detInv; + te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; + te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; + te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; - _vector$5.fromBufferAttribute( this, i ); + return this; - _vector$5.applyMatrix4( m ); + } - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + /** + * Multiplies the columns of this matrix by the given vector. + * + * @param {Vector3} v - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + scale( v ) { - } + const te = this.elements; + const x = v.x, y = v.y, z = v.z; + + te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; + te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; + te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; + te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; return this; } - applyNormalMatrix( m ) { + /** + * Gets the maximum scale value of the three axes. + * + * @return {number} The maximum scale. + */ + getMaxScaleOnAxis() { - for ( let i = 0, l = this.count; i < l; i ++ ) { + const te = this.elements; - _vector$5.fromBufferAttribute( this, i ); + const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; + const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; + const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; - _vector$5.applyNormalMatrix( m ); + return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + } - } + /** + * Sets this matrix as a translation transform from the given vector. + * + * @param {number|Vector3} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @param {number} z - The amount to translate in the z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeTranslation( x, y, z ) { - return this; + if ( x.isVector3 ) { - } + this.set( - transformDirection( m ) { + 1, 0, 0, x.x, + 0, 1, 0, x.y, + 0, 0, 1, x.z, + 0, 0, 0, 1 - for ( let i = 0, l = this.count; i < l; i ++ ) { + ); - _vector$5.fromBufferAttribute( this, i ); + } else { - _vector$5.transformDirection( m ); + this.set( - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); } @@ -21251,1774 +26950,2806 @@ class BufferAttribute { } - set( value, offset = 0 ) { + /** + * Sets this matrix as a rotational transformation around the X axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationX( theta ) { - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); + const c = Math.cos( theta ), s = Math.sin( theta ); - return this; + this.set( - } + 1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1 - getComponent( index, component ) { + ); - let value = this.array[ index * this.itemSize + component ]; + return this; - if ( this.normalized ) value = denormalize( value, this.array ); + } - return value; + /** + * Sets this matrix as a rotational transformation around the Y axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationY( theta ) { - } + const c = Math.cos( theta ), s = Math.sin( theta ); - setComponent( index, component, value ) { + this.set( - if ( this.normalized ) value = normalize( value, this.array ); + c, 0, s, 0, + 0, 1, 0, 0, + - s, 0, c, 0, + 0, 0, 0, 1 - this.array[ index * this.itemSize + component ] = value; + ); return this; } - getX( index ) { - - let x = this.array[ index * this.itemSize ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; + /** + * Sets this matrix as a rotational transformation around the Z axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationZ( theta ) { - } + const c = Math.cos( theta ), s = Math.sin( theta ); - setX( index, x ) { + this.set( - if ( this.normalized ) x = normalize( x, this.array ); + c, - s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - this.array[ index * this.itemSize ] = x; + ); return this; } - getY( index ) { - - let y = this.array[ index * this.itemSize + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); + /** + * Sets this matrix as a rotational transformation around the given axis by + * the given angle. + * + * This is a somewhat controversial but mathematically sound alternative to + * rotating via Quaternions. See the discussion [here]{@link https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199}. + * + * @param {Vector3} axis - The normalized rotation axis. + * @param {number} angle - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationAxis( axis, angle ) { - return y; + // Based on http://www.gamedev.net/reference/articles/article1199.asp - } + const c = Math.cos( angle ); + const s = Math.sin( angle ); + const t = 1 - c; + const x = axis.x, y = axis.y, z = axis.z; + const tx = t * x, ty = t * y; - setY( index, y ) { + this.set( - if ( this.normalized ) y = normalize( y, this.array ); + tx * x + c, tx * y - s * z, tx * z + s * y, 0, + tx * y + s * z, ty * y + c, ty * z - s * x, 0, + tx * z - s * y, ty * z + s * x, t * z * z + c, 0, + 0, 0, 0, 1 - this.array[ index * this.itemSize + 1 ] = y; + ); return this; } - getZ( index ) { + /** + * Sets this matrix as a scale transformation. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @param {number} z - The amount to scale in the Z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeScale( x, y, z ) { - let z = this.array[ index * this.itemSize + 2 ]; + this.set( - if ( this.normalized ) z = denormalize( z, this.array ); + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 - return z; + ); + + return this; } - setZ( index, z ) { + /** + * Sets this matrix as a shear transformation. + * + * @param {number} xy - The amount to shear X by Y. + * @param {number} xz - The amount to shear X by Z. + * @param {number} yx - The amount to shear Y by X. + * @param {number} yz - The amount to shear Y by Z. + * @param {number} zx - The amount to shear Z by X. + * @param {number} zy - The amount to shear Z by Y. + * @return {Matrix4} A reference to this matrix. + */ + makeShear( xy, xz, yx, yz, zx, zy ) { - if ( this.normalized ) z = normalize( z, this.array ); + this.set( - this.array[ index * this.itemSize + 2 ] = z; + 1, yx, zx, 0, + xy, 1, zy, 0, + xz, yz, 1, 0, + 0, 0, 0, 1 + + ); return this; } - getW( index ) { + /** + * Sets this matrix to the transformation composed of the given position, + * rotation (Quaternion) and scale. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + compose( position, quaternion, scale ) { - let w = this.array[ index * this.itemSize + 3 ]; + const te = this.elements; - if ( this.normalized ) w = denormalize( w, this.array ); + const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; + const x2 = x + x, y2 = y + y, z2 = z + z; + const xx = x * x2, xy = x * y2, xz = x * z2; + const yy = y * y2, yz = y * z2, zz = z * z2; + const wx = w * x2, wy = w * y2, wz = w * z2; - return w; + const sx = scale.x, sy = scale.y, sz = scale.z; - } + te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; + te[ 1 ] = ( xy + wz ) * sx; + te[ 2 ] = ( xz - wy ) * sx; + te[ 3 ] = 0; - setW( index, w ) { + te[ 4 ] = ( xy - wz ) * sy; + te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; + te[ 6 ] = ( yz + wx ) * sy; + te[ 7 ] = 0; - if ( this.normalized ) w = normalize( w, this.array ); + te[ 8 ] = ( xz + wy ) * sz; + te[ 9 ] = ( yz - wx ) * sz; + te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; + te[ 11 ] = 0; - this.array[ index * this.itemSize + 3 ] = w; + te[ 12 ] = position.x; + te[ 13 ] = position.y; + te[ 14 ] = position.z; + te[ 15 ] = 1; return this; } - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { + /** + * Decomposes this matrix into its position, rotation and scale components + * and provides the result in the given objects. + * + * Note: Not all matrices are decomposable in this way. For example, if an + * object has a non-uniformly scaled parent, then the object's world matrix + * may not be decomposable, and this method may not be appropriate. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + decompose( position, quaternion, scale ) { - x = normalize( x, this.array ); - y = normalize( y, this.array ); + const te = this.elements; - } + let sx = _v1$4.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); + const sy = _v1$4.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); + const sz = _v1$4.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; + // if determine is negative, we need to invert one scale + const det = this.determinant(); + if ( det < 0 ) sx = - sx; - return this; + position.x = te[ 12 ]; + position.y = te[ 13 ]; + position.z = te[ 14 ]; - } + // scale the rotation part + _m1$4.copy( this ); - setXYZ( index, x, y, z ) { + const invSX = 1 / sx; + const invSY = 1 / sy; + const invSZ = 1 / sz; - index *= this.itemSize; + _m1$4.elements[ 0 ] *= invSX; + _m1$4.elements[ 1 ] *= invSX; + _m1$4.elements[ 2 ] *= invSX; - if ( this.normalized ) { + _m1$4.elements[ 4 ] *= invSY; + _m1$4.elements[ 5 ] *= invSY; + _m1$4.elements[ 6 ] *= invSY; - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); + _m1$4.elements[ 8 ] *= invSZ; + _m1$4.elements[ 9 ] *= invSZ; + _m1$4.elements[ 10 ] *= invSZ; - } + quaternion.setFromRotationMatrix( _m1$4 ); - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; + scale.x = sx; + scale.y = sy; + scale.z = sz; return this; } - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; + /** + * Creates a perspective projection matrix. This is used internally by + * {@link PerspectiveCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Matrix4} A reference to this matrix. + */ + makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { - if ( this.normalized ) { + const te = this.elements; - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); + const x = 2 * near / ( right - left ); + const y = 2 * near / ( top - bottom ); - } + const a = ( right + left ) / ( right - left ); + const b = ( top + bottom ) / ( top - bottom ); - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; + let c, d; - return this; + if ( reversedDepth ) { - } + c = near / ( far - near ); + d = ( far * near ) / ( far - near ); - onUpload( callback ) { + } else { - this.onUploadCallback = callback; + if ( coordinateSystem === WebGLCoordinateSystem ) { - return this; + c = - ( far + near ) / ( far - near ); + d = ( -2 * far * near ) / ( far - near ); - } + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - clone() { + c = - far / ( far - near ); + d = ( - far * near ) / ( far - near ); - return new this.constructor( this.array, this.itemSize ).copy( this ); + } else { - } + throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); - toJSON() { + } - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; + } - if ( this.name !== '' ) data.name = this.name; - if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = -1; te[ 15 ] = 0; - return data; + return this; } -} + /** + * Creates a orthographic projection matrix. This is used internally by + * {@link OrthographicCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Matrix4} A reference to this matrix. + */ + makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { -class Uint16BufferAttribute extends BufferAttribute { + const te = this.elements; - constructor( array, itemSize, normalized ) { + const x = 2 / ( right - left ); + const y = 2 / ( top - bottom ); - super( new Uint16Array( array ), itemSize, normalized ); + const a = - ( right + left ) / ( right - left ); + const b = - ( top + bottom ) / ( top - bottom ); - } + let c, d; -} + if ( reversedDepth ) { -class Uint32BufferAttribute extends BufferAttribute { + c = 1 / ( far - near ); + d = far / ( far - near ); - constructor( array, itemSize, normalized ) { + } else { - super( new Uint32Array( array ), itemSize, normalized ); + if ( coordinateSystem === WebGLCoordinateSystem ) { - } + c = -2 / ( far - near ); + d = - ( far + near ) / ( far - near ); -} + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + c = -1 / ( far - near ); + d = - near / ( far - near ); -class Float32BufferAttribute extends BufferAttribute { + } else { - constructor( array, itemSize, normalized ) { + throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); - super( new Float32Array( array ), itemSize, normalized ); + } - } + } -} + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = a; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = 0; te[ 13 ] = b; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; -const _matrix$2 = /*@__PURE__*/ new Matrix4(); -const _quaternion$1 = /*@__PURE__*/ new Quaternion(); + return this; -class Euler { + } - constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix4} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { - this.isEuler = true; + const te = this.elements; + const me = matrix.elements; - this._x = x; - this._y = y; - this._z = z; - this._order = order; + for ( let i = 0; i < 16; i ++ ) { - } + if ( te[ i ] !== me[ i ] ) return false; - get x() { + } - return this._x; + return true; } - set x( value ) { + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix4} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { - this._x = value; - this._onChangeCallback(); + for ( let i = 0; i < 16; i ++ ) { - } + this.elements[ i ] = array[ i + offset ]; - get y() { + } - return this._y; + return this; } - set y( value ) { + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ + toArray( array = [], offset = 0 ) { - this._y = value; - this._onChangeCallback(); + const te = this.elements; - } + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + array[ offset + 3 ] = te[ 3 ]; - get z() { + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; - return this._z; + array[ offset + 8 ] = te[ 8 ]; + array[ offset + 9 ] = te[ 9 ]; + array[ offset + 10 ] = te[ 10 ]; + array[ offset + 11 ] = te[ 11 ]; + + array[ offset + 12 ] = te[ 12 ]; + array[ offset + 13 ] = te[ 13 ]; + array[ offset + 14 ] = te[ 14 ]; + array[ offset + 15 ] = te[ 15 ]; + + return array; } - set z( value ) { +} - this._z = value; - this._onChangeCallback(); +const _v1$4 = /*@__PURE__*/ new Vector3(); +const _m1$4 = /*@__PURE__*/ new Matrix4(); +const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); +const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); +const _x = /*@__PURE__*/ new Vector3(); +const _y = /*@__PURE__*/ new Vector3(); +const _z = /*@__PURE__*/ new Vector3(); - } +function WebGLAnimation() { - get order() { + let context = null; + let isAnimating = false; + let animationLoop = null; + let requestId = null; - return this._order; + function onAnimationFrame( time, frame ) { + + animationLoop( time, frame ); + + requestId = context.requestAnimationFrame( onAnimationFrame ); } - set order( value ) { + return { - this._order = value; - this._onChangeCallback(); + start: function () { - } + if ( isAnimating === true ) return; + if ( animationLoop === null ) return; - set( x, y, z, order = this._order ) { + requestId = context.requestAnimationFrame( onAnimationFrame ); - this._x = x; - this._y = y; - this._z = z; - this._order = order; + isAnimating = true; - this._onChangeCallback(); + }, - return this; + stop: function () { - } + context.cancelAnimationFrame( requestId ); - clone() { + isAnimating = false; - return new this.constructor( this._x, this._y, this._z, this._order ); + }, - } + setAnimationLoop: function ( callback ) { - copy( euler ) { + animationLoop = callback; - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; + }, - this._onChangeCallback(); + setContext: function ( value ) { - return this; + context = value; - } + } - setFromRotationMatrix( m, order = this._order, update = true ) { + }; - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) +} - const te = m.elements; - const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; - const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; - const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; +function WebGLAttributes( gl ) { - switch ( order ) { + const buffers = new WeakMap(); - case 'XYZ': + function createBuffer( attribute, bufferType ) { - this._y = Math.asin( clamp( m13, - 1, 1 ) ); + const array = attribute.array; + const usage = attribute.usage; + const size = array.byteLength; - if ( Math.abs( m13 ) < 0.9999999 ) { + const buffer = gl.createBuffer(); - this._x = Math.atan2( - m23, m33 ); - this._z = Math.atan2( - m12, m11 ); + gl.bindBuffer( bufferType, buffer ); + gl.bufferData( bufferType, array, usage ); - } else { + attribute.onUploadCallback(); - this._x = Math.atan2( m32, m22 ); - this._z = 0; + let type; - } + if ( array instanceof Float32Array ) { - break; + type = gl.FLOAT; - case 'YXZ': + } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); + type = gl.HALF_FLOAT; - if ( Math.abs( m23 ) < 0.9999999 ) { + } else if ( array instanceof Uint16Array ) { - this._y = Math.atan2( m13, m33 ); - this._z = Math.atan2( m21, m22 ); + if ( attribute.isFloat16BufferAttribute ) { - } else { + type = gl.HALF_FLOAT; - this._y = Math.atan2( - m31, m11 ); - this._z = 0; + } else { - } + type = gl.UNSIGNED_SHORT; - break; + } - case 'ZXY': + } else if ( array instanceof Int16Array ) { - this._x = Math.asin( clamp( m32, - 1, 1 ) ); + type = gl.SHORT; - if ( Math.abs( m32 ) < 0.9999999 ) { + } else if ( array instanceof Uint32Array ) { - this._y = Math.atan2( - m31, m33 ); - this._z = Math.atan2( - m12, m22 ); + type = gl.UNSIGNED_INT; - } else { + } else if ( array instanceof Int32Array ) { - this._y = 0; - this._z = Math.atan2( m21, m11 ); + type = gl.INT; - } + } else if ( array instanceof Int8Array ) { - break; + type = gl.BYTE; - case 'ZYX': + } else if ( array instanceof Uint8Array ) { - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); + type = gl.UNSIGNED_BYTE; - if ( Math.abs( m31 ) < 0.9999999 ) { + } else if ( array instanceof Uint8ClampedArray ) { - this._x = Math.atan2( m32, m33 ); - this._z = Math.atan2( m21, m11 ); + type = gl.UNSIGNED_BYTE; - } else { + } else { - this._x = 0; - this._z = Math.atan2( - m12, m22 ); + throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); - } + } - break; + return { + buffer: buffer, + type: type, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version, + size: size + }; - case 'YZX': + } - this._z = Math.asin( clamp( m21, - 1, 1 ) ); + function updateBuffer( buffer, attribute, bufferType ) { - if ( Math.abs( m21 ) < 0.9999999 ) { + const array = attribute.array; + const updateRanges = attribute.updateRanges; - this._x = Math.atan2( - m23, m22 ); - this._y = Math.atan2( - m31, m11 ); + gl.bindBuffer( bufferType, buffer ); - } else { + if ( updateRanges.length === 0 ) { - this._x = 0; - this._y = Math.atan2( m13, m33 ); + // Not using update ranges + gl.bufferSubData( bufferType, 0, array ); - } + } else { - break; + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.bufferSubData`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. - case 'XZY': + updateRanges.sort( ( a, b ) => a.start - b.start ); - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; - if ( Math.abs( m12 ) < 0.9999999 ) { + for ( let i = 1; i < updateRanges.length; i ++ ) { - this._x = Math.atan2( m32, m22 ); - this._y = Math.atan2( m13, m11 ); + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; + + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( range.start <= previousRange.start + previousRange.count + 1 ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); } else { - this._x = Math.atan2( - m23, m33 ); - this._y = 0; + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; } - break; + } - default: + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - } + const range = updateRanges[ i ]; - this._order = order; + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); - if ( update === true ) this._onChangeCallback(); + } - return this; + attribute.clearUpdateRanges(); + + } + + attribute.onUploadCallback(); } - setFromQuaternion( q, order, update ) { + // - _matrix$2.makeRotationFromQuaternion( q ); + function get( attribute ) { - return this.setFromRotationMatrix( _matrix$2, order, update ); + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return buffers.get( attribute ); } - setFromVector3( v, order = this._order ) { + function remove( attribute ) { - return this.set( v.x, v.y, v.z, order ); + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - } + const data = buffers.get( attribute ); - reorder( newOrder ) { + if ( data ) { - // WARNING: this discards revolution information -bhouston + gl.deleteBuffer( data.buffer ); - _quaternion$1.setFromEuler( this ); + buffers.delete( attribute ); - return this.setFromQuaternion( _quaternion$1, newOrder ); + } } - equals( euler ) { + function update( attribute, bufferType ) { - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - } + if ( attribute.isGLBufferAttribute ) { - fromArray( array ) { + const cached = buffers.get( attribute ); - this._x = array[ 0 ]; - this._y = array[ 1 ]; - this._z = array[ 2 ]; - if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + if ( ! cached || cached.version < attribute.version ) { - this._onChangeCallback(); + buffers.set( attribute, { + buffer: attribute.buffer, + type: attribute.type, + bytesPerElement: attribute.elementSize, + version: attribute.version + } ); - return this; + } - } + return; - toArray( array = [], offset = 0 ) { + } - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._order; + const data = buffers.get( attribute ); - return array; + if ( data === undefined ) { - } + buffers.set( attribute, createBuffer( attribute, bufferType ) ); - _onChange( callback ) { + } else if ( data.version < attribute.version ) { - this._onChangeCallback = callback; + if ( data.size !== attribute.array.byteLength ) { - return this; + throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); - } + } - _onChangeCallback() {} + updateBuffer( data.buffer, attribute, bufferType ); - *[ Symbol.iterator ]() { + data.version = attribute.version; - yield this._x; - yield this._y; - yield this._z; - yield this._order; + } } -} + return { -Euler.DEFAULT_ORDER = 'XYZ'; + get: get, + remove: remove, + update: update -class Layers { + }; - constructor() { +} - this.mask = 1 | 0; +const _vector$5 = /*@__PURE__*/ new Vector3(); +const _vector2 = /*@__PURE__*/ new Vector2(); - } +let _id$2 = 0; - set( channel ) { +/** + * This class stores data for an attribute (such as vertex positions, face + * indices, normals, colors, UVs, and any custom attributes ) associated with + * a geometry, which allows for more efficient passing of data to the GPU. + * + * When working with vector-like data, the `fromBufferAttribute( attribute, index )` + * helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}. + */ +class BufferAttribute { - this.mask = ( 1 << channel | 0 ) >>> 0; + /** + * Constructs a new buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized = false ) { - } + if ( Array.isArray( array ) ) { - enable( channel ) { + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - this.mask |= 1 << channel | 0; + } - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferAttribute = true; - enableAll() { + /** + * The ID of the buffer attribute. + * + * @name BufferAttribute#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$2 ++ } ); - this.mask = 0xffffffff | 0; + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; - } + /** + * The array holding the attribute data. It should have `itemSize * numVertices` + * elements, where `numVertices` is the number of vertices in the associated geometry. + * + * @type {TypedArray} + */ + this.array = array; + + /** + * The number of values of the array that should be associated with a particular vertex. + * For instance, if this attribute is storing a 3-component vector (such as a position, + * normal, or color), then the value should be `3`. + * + * @type {number} + */ + this.itemSize = itemSize; + + /** + * Represents the number of items this buffer attribute stores. It is internally computed + * by dividing the `array` length by the `itemSize`. + * + * @type {number} + * @readonly + */ + this.count = array !== undefined ? array.length / itemSize : 0; - toggle( channel ) { + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`, + * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; - this.mask ^= 1 << channel | 0; + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; - } + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; - disable( channel ) { + /** + * Configures the bound GPU type for use in shaders. + * + * Note: this only has an effect for integer arrays and is not configurable for float arrays. + * For lower precision float types, use `Float16BufferAttribute`. + * + * @type {(FloatType|IntType)} + * @default FloatType + */ + this.gpuType = FloatType; - this.mask &= ~ ( 1 << channel | 0 ); + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; } - disableAll() { + /** + * A callback function that is executed after the renderer has transferred the attribute + * array data to the GPU. + */ + onUploadCallback() {} - this.mask = 0; + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; } - test( layers ) { + /** + * Sets the usage of this buffer attribute. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {BufferAttribute} A reference to this buffer attribute. + */ + setUsage( value ) { - return ( this.mask & layers.mask ) !== 0; + this.usage = value; + + return this; } - isEnabled( channel ) { + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { - return ( this.mask & ( 1 << channel | 0 ) ) !== 0; + this.updateRanges.push( { start, count } ); } -} + /** + * Clears the update ranges. + */ + clearUpdateRanges() { -let _object3DId = 0; + this.updateRanges.length = 0; -const _v1$3 = /*@__PURE__*/ new Vector3(); -const _q1 = /*@__PURE__*/ new Quaternion(); -const _m1$3 = /*@__PURE__*/ new Matrix4(); -const _target = /*@__PURE__*/ new Vector3(); + } -const _position$1 = /*@__PURE__*/ new Vector3(); -const _scale = /*@__PURE__*/ new Vector3(); -const _quaternion = /*@__PURE__*/ new Quaternion(); + /** + * Copies the values of the given buffer attribute to this instance. + * + * @param {BufferAttribute} source - The buffer attribute to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copy( source ) { -const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); -const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); -const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); + this.name = source.name; + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; + this.count = source.count; + this.normalized = source.normalized; -const _addedEvent = { type: 'added' }; -const _removedEvent = { type: 'removed' }; + this.usage = source.usage; + this.gpuType = source.gpuType; -const _childaddedEvent = { type: 'childadded', child: null }; -const _childremovedEvent = { type: 'childremoved', child: null }; + return this; -class Object3D extends EventDispatcher { + } - constructor() { + /** + * Copies a vector from the given buffer attribute to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this buffer attribute. + * @param {BufferAttribute} attribute - The buffer attribute to copy from. + * @param {number} index2 - The source index into the given buffer attribute. + * @return {BufferAttribute} A reference to this instance. + */ + copyAt( index1, attribute, index2 ) { - super(); + index1 *= this.itemSize; + index2 *= attribute.itemSize; - this.isObject3D = true; + for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - Object.defineProperty( this, 'id', { value: _object3DId ++ } ); + this.array[ index1 + i ] = attribute.array[ index2 + i ]; - this.uuid = generateUUID(); + } - this.name = ''; - this.type = 'Object3D'; + return this; - this.parent = null; - this.children = []; + } - this.up = Object3D.DEFAULT_UP.clone(); + /** + * Copies the given array data into this buffer attribute. + * + * @param {(TypedArray|Array)} array - The array to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copyArray( array ) { - const position = new Vector3(); - const rotation = new Euler(); - const quaternion = new Quaternion(); - const scale = new Vector3( 1, 1, 1 ); + this.array.set( array ); - function onRotationChange() { + return this; - quaternion.setFromEuler( rotation, false ); + } - } + /** + * Applies the given 3x3 matrix to the given attribute. Works with + * item size `2` and `3`. + * + * @param {Matrix3} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix3( m ) { - function onQuaternionChange() { + if ( this.itemSize === 2 ) { - rotation.setFromQuaternion( quaternion, undefined, false ); + for ( let i = 0, l = this.count; i < l; i ++ ) { - } + _vector2.fromBufferAttribute( this, i ); + _vector2.applyMatrix3( m ); - rotation._onChange( onRotationChange ); - quaternion._onChange( onQuaternionChange ); + this.setXY( i, _vector2.x, _vector2.y ); - Object.defineProperties( this, { - position: { - configurable: true, - enumerable: true, - value: position - }, - rotation: { - configurable: true, - enumerable: true, - value: rotation - }, - quaternion: { - configurable: true, - enumerable: true, - value: quaternion - }, - scale: { - configurable: true, - enumerable: true, - value: scale - }, - modelViewMatrix: { - value: new Matrix4() - }, - normalMatrix: { - value: new Matrix3() } - } ); - this.matrix = new Matrix4(); - this.matrixWorld = new Matrix4(); - - this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; + } else if ( this.itemSize === 3 ) { - this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer - this.matrixWorldNeedsUpdate = false; + for ( let i = 0, l = this.count; i < l; i ++ ) { - this.layers = new Layers(); - this.visible = true; + _vector$5.fromBufferAttribute( this, i ); + _vector$5.applyMatrix3( m ); - this.castShadow = false; - this.receiveShadow = false; + this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - this.frustumCulled = true; - this.renderOrder = 0; + } - this.animations = []; + } - this.userData = {}; + return this; } - onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix4( m ) { - onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + for ( let i = 0, l = this.count; i < l; i ++ ) { - onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} + _vector$5.fromBufferAttribute( this, i ); - onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - applyMatrix4( matrix ) { + _vector$5.applyMatrix4( m ); - if ( this.matrixAutoUpdate ) this.updateMatrix(); + this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - this.matrix.premultiply( matrix ); + } - this.matrix.decompose( this.position, this.quaternion, this.scale ); + return this; } - applyQuaternion( q ) { + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyNormalMatrix( m ) { - this.quaternion.premultiply( q ); + for ( let i = 0, l = this.count; i < l; i ++ ) { - return this; + _vector$5.fromBufferAttribute( this, i ); - } + _vector$5.applyNormalMatrix( m ); - setRotationFromAxisAngle( axis, angle ) { + this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - // assumes axis is normalized + } - this.quaternion.setFromAxisAngle( axis, angle ); + return this; } - setRotationFromEuler( euler ) { + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + transformDirection( m ) { - this.quaternion.setFromEuler( euler, true ); + for ( let i = 0, l = this.count; i < l; i ++ ) { - } + _vector$5.fromBufferAttribute( this, i ); - setRotationFromMatrix( m ) { + _vector$5.transformDirection( m ); - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - this.quaternion.setFromRotationMatrix( m ); + } + + return this; } - setRotationFromQuaternion( q ) { + /** + * Sets the given array data in the buffer attribute. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this buffer attribute's array. + * @return {BufferAttribute} A reference to this instance. + */ + set( value, offset = 0 ) { - // assumes q is normalized + // Matching BufferAttribute constructor, do not normalize the array. + this.array.set( value, offset ); - this.quaternion.copy( q ); + return this; } - rotateOnAxis( axis, angle ) { - - // rotate object on axis in object space - // axis is assumed to be normalized + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ + getComponent( index, component ) { - _q1.setFromAxisAngle( axis, angle ); + let value = this.array[ index * this.itemSize + component ]; - this.quaternion.multiply( _q1 ); + if ( this.normalized ) value = denormalize( value, this.array ); - return this; + return value; } - rotateOnWorldAxis( axis, angle ) { - - // rotate object on axis in world space - // axis is assumed to be normalized - // method assumes no rotated parent + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setComponent( index, component, value ) { - _q1.setFromAxisAngle( axis, angle ); + if ( this.normalized ) value = normalize$1( value, this.array ); - this.quaternion.premultiply( _q1 ); + this.array[ index * this.itemSize + component ] = value; return this; } - rotateX( angle ) { - - return this.rotateOnAxis( _xAxis, angle ); - - } - - rotateY( angle ) { - - return this.rotateOnAxis( _yAxis, angle ); + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ + getX( index ) { - } + let x = this.array[ index * this.itemSize ]; - rotateZ( angle ) { + if ( this.normalized ) x = denormalize( x, this.array ); - return this.rotateOnAxis( _zAxis, angle ); + return x; } - translateOnAxis( axis, distance ) { - - // translate object by distance along axis in object space - // axis is assumed to be normalized + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setX( index, x ) { - _v1$3.copy( axis ).applyQuaternion( this.quaternion ); + if ( this.normalized ) x = normalize$1( x, this.array ); - this.position.add( _v1$3.multiplyScalar( distance ) ); + this.array[ index * this.itemSize ] = x; return this; } - translateX( distance ) { - - return this.translateOnAxis( _xAxis, distance ); + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ + getY( index ) { - } + let y = this.array[ index * this.itemSize + 1 ]; - translateY( distance ) { + if ( this.normalized ) y = denormalize( y, this.array ); - return this.translateOnAxis( _yAxis, distance ); + return y; } - translateZ( distance ) { - - return this.translateOnAxis( _zAxis, distance ); - - } + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setY( index, y ) { - localToWorld( vector ) { + if ( this.normalized ) y = normalize$1( y, this.array ); - this.updateWorldMatrix( true, false ); + this.array[ index * this.itemSize + 1 ] = y; - return vector.applyMatrix4( this.matrixWorld ); + return this; } - worldToLocal( vector ) { + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ + getZ( index ) { - this.updateWorldMatrix( true, false ); + let z = this.array[ index * this.itemSize + 2 ]; - return vector.applyMatrix4( _m1$3.copy( this.matrixWorld ).invert() ); + if ( this.normalized ) z = denormalize( z, this.array ); + + return z; } - lookAt( x, y, z ) { + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setZ( index, z ) { - // This method does not support objects having non-uniformly-scaled parent(s) + if ( this.normalized ) z = normalize$1( z, this.array ); - if ( x.isVector3 ) { + this.array[ index * this.itemSize + 2 ] = z; - _target.copy( x ); + return this; - } else { + } - _target.set( x, y, z ); + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ + getW( index ) { - } + let w = this.array[ index * this.itemSize + 3 ]; - const parent = this.parent; + if ( this.normalized ) w = denormalize( w, this.array ); - this.updateWorldMatrix( true, false ); + return w; - _position$1.setFromMatrixPosition( this.matrixWorld ); + } - if ( this.isCamera || this.isLight ) { + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setW( index, w ) { - _m1$3.lookAt( _position$1, _target, this.up ); + if ( this.normalized ) w = normalize$1( w, this.array ); - } else { + this.array[ index * this.itemSize + 3 ] = w; - _m1$3.lookAt( _target, _position$1, this.up ); + return this; - } + } - this.quaternion.setFromRotationMatrix( _m1$3 ); + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXY( index, x, y ) { - if ( parent ) { + index *= this.itemSize; - _m1$3.extractRotation( parent.matrixWorld ); - _q1.setFromRotationMatrix( _m1$3 ); - this.quaternion.premultiply( _q1.invert() ); + if ( this.normalized ) { + + x = normalize$1( x, this.array ); + y = normalize$1( y, this.array ); } - } + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; - add( object ) { + return this; - if ( arguments.length > 1 ) { + } - for ( let i = 0; i < arguments.length; i ++ ) { + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZ( index, x, y, z ) { - this.add( arguments[ i ] ); + index *= this.itemSize; - } + if ( this.normalized ) { - return this; + x = normalize$1( x, this.array ); + y = normalize$1( y, this.array ); + z = normalize$1( z, this.array ); } - if ( object === this ) { + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; - console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); - return this; + return this; - } + } - if ( object && object.isObject3D ) { + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZW( index, x, y, z, w ) { - if ( object.parent !== null ) { + index *= this.itemSize; - object.parent.remove( object ); + if ( this.normalized ) { - } + x = normalize$1( x, this.array ); + y = normalize$1( y, this.array ); + z = normalize$1( z, this.array ); + w = normalize$1( w, this.array ); - object.parent = this; - this.children.push( object ); + } - object.dispatchEvent( _addedEvent ); + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; - _childaddedEvent.child = object; - this.dispatchEvent( _childaddedEvent ); - _childaddedEvent.child = null; + return this; - } else { + } - console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the attribute array data to the GPU. Can be used to perform clean-up operations after + * the upload when attribute data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {BufferAttribute} A reference to this instance. + */ + onUpload( callback ) { - } + this.onUploadCallback = callback; return this; } - remove( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { + /** + * Returns a new buffer attribute with copied values from this instance. + * + * @return {BufferAttribute} A clone of this instance. + */ + clone() { - this.remove( arguments[ i ] ); + return new this.constructor( this.array, this.itemSize ).copy( this ); - } + } - return this; + /** + * Serializes the buffer attribute into JSON. + * + * @return {Object} A JSON object representing the serialized buffer attribute. + */ + toJSON() { - } + const data = { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: Array.from( this.array ), + normalized: this.normalized + }; - const index = this.children.indexOf( object ); + if ( this.name !== '' ) data.name = this.name; + if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - if ( index !== - 1 ) { + return data; - object.parent = null; - this.children.splice( index, 1 ); + } - object.dispatchEvent( _removedEvent ); +} - _childremovedEvent.child = object; - this.dispatchEvent( _childremovedEvent ); - _childremovedEvent.child = null; +/** + * Convenient class that can be used when creating a `UInt16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint16BufferAttribute extends BufferAttribute { - } + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - return this; + super( new Uint16Array( array ), itemSize, normalized ); } - removeFromParent() { +} - const parent = this.parent; +/** + * Convenient class that can be used when creating a `UInt32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint32BufferAttribute extends BufferAttribute { - if ( parent !== null ) { + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - parent.remove( this ); + super( new Uint32Array( array ), itemSize, normalized ); - } + } - return this; +} - } +/** + * Convenient class that can be used when creating a `Float32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Float32BufferAttribute extends BufferAttribute { - clear() { + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Float32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - return this.remove( ... this.children ); + super( new Float32Array( array ), itemSize, normalized ); } - attach( object ) { - - // adds object as a child of this, while maintaining the object's world transform +} - // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) +const _matrix$2 = /*@__PURE__*/ new Matrix4(); +const _quaternion$1 = /*@__PURE__*/ new Quaternion(); - this.updateWorldMatrix( true, false ); +/** + * A class representing Euler angles. + * + * Euler angles describe a rotational transformation by rotating an object on + * its various axes in specified amounts per axis, and a specified axis + * order. + * + * Iterating through an instance will yield its components (x, y, z, + * order) in the corresponding order. + * + * ```js + * const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); + * const b = new THREE.Vector3( 1, 0, 1 ); + * b.applyEuler(a); + * ``` + */ +class Euler { - _m1$3.copy( this.matrixWorld ).invert(); + /** + * Constructs a new euler instance. + * + * @param {number} [x=0] - The angle of the x axis in radians. + * @param {number} [y=0] - The angle of the y axis in radians. + * @param {number} [z=0] - The angle of the z axis in radians. + * @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied. + */ + constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { - if ( object.parent !== null ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEuler = true; - object.parent.updateWorldMatrix( true, false ); + this._x = x; + this._y = y; + this._z = z; + this._order = order; - _m1$3.multiply( object.parent.matrixWorld ); + } - } + /** + * The angle of the x axis in radians. + * + * @type {number} + * @default 0 + */ + get x() { - object.applyMatrix4( _m1$3 ); + return this._x; - this.add( object ); + } - object.updateWorldMatrix( false, true ); + set x( value ) { - return this; + this._x = value; + this._onChangeCallback(); } - getObjectById( id ) { + /** + * The angle of the y axis in radians. + * + * @type {number} + * @default 0 + */ + get y() { - return this.getObjectByProperty( 'id', id ); + return this._y; } - getObjectByName( name ) { + set y( value ) { - return this.getObjectByProperty( 'name', name ); + this._y = value; + this._onChangeCallback(); } - getObjectByProperty( name, value ) { - - if ( this[ name ] === value ) return this; + /** + * The angle of the z axis in radians. + * + * @type {number} + * @default 0 + */ + get z() { - for ( let i = 0, l = this.children.length; i < l; i ++ ) { + return this._z; - const child = this.children[ i ]; - const object = child.getObjectByProperty( name, value ); + } - if ( object !== undefined ) { + set z( value ) { - return object; + this._z = value; + this._onChangeCallback(); - } + } - } + /** + * A string representing the order that the rotations are applied. + * + * @type {string} + * @default 'XYZ' + */ + get order() { - return undefined; + return this._order; } - getObjectsByProperty( name, value, result = [] ) { + set order( value ) { - if ( this[ name ] === value ) result.push( this ); + this._order = value; + this._onChangeCallback(); - const children = this.children; + } - for ( let i = 0, l = children.length; i < l; i ++ ) { + /** + * Sets the Euler components. + * + * @param {number} x - The angle of the x axis in radians. + * @param {number} y - The angle of the y axis in radians. + * @param {number} z - The angle of the z axis in radians. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + set( x, y, z, order = this._order ) { - children[ i ].getObjectsByProperty( name, value, result ); + this._x = x; + this._y = y; + this._z = z; + this._order = order; - } + this._onChangeCallback(); - return result; + return this; } - getWorldPosition( target ) { - - this.updateWorldMatrix( true, false ); + /** + * Returns a new Euler instance with copied values from this instance. + * + * @return {Euler} A clone of this instance. + */ + clone() { - return target.setFromMatrixPosition( this.matrixWorld ); + return new this.constructor( this._x, this._y, this._z, this._order ); } - getWorldQuaternion( target ) { + /** + * Copies the values of the given Euler instance to this instance. + * + * @param {Euler} euler - The Euler instance to copy. + * @return {Euler} A reference to this Euler instance. + */ + copy( euler ) { - this.updateWorldMatrix( true, false ); + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; - this.matrixWorld.decompose( _position$1, target, _scale ); + this._onChangeCallback(); - return target; + return this; } - getWorldScale( target ) { + /** + * Sets the angles of this Euler instance from a pure rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ + setFromRotationMatrix( m, order = this._order, update = true ) { - this.updateWorldMatrix( true, false ); + const te = m.elements; + const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; + const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; + const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - this.matrixWorld.decompose( _position$1, _quaternion, target ); + switch ( order ) { - return target; + case 'XYZ': - } + this._y = Math.asin( clamp( m13, -1, 1 ) ); - getWorldDirection( target ) { + if ( Math.abs( m13 ) < 0.9999999 ) { - this.updateWorldMatrix( true, false ); + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); - const e = this.matrixWorld.elements; + } else { - return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); + this._x = Math.atan2( m32, m22 ); + this._z = 0; - } + } - raycast( /* raycaster, intersects */ ) {} + break; - traverse( callback ) { + case 'YXZ': - callback( this ); + this._x = Math.asin( - clamp( m23, -1, 1 ) ); - const children = this.children; + if ( Math.abs( m23 ) < 0.9999999 ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); - children[ i ].traverse( callback ); + } else { - } + this._y = Math.atan2( - m31, m11 ); + this._z = 0; - } + } - traverseVisible( callback ) { + break; - if ( this.visible === false ) return; + case 'ZXY': - callback( this ); + this._x = Math.asin( clamp( m32, -1, 1 ) ); - const children = this.children; + if ( Math.abs( m32 ) < 0.9999999 ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); - children[ i ].traverseVisible( callback ); + } else { - } - - } - - traverseAncestors( callback ) { + this._y = 0; + this._z = Math.atan2( m21, m11 ); - const parent = this.parent; + } - if ( parent !== null ) { + break; - callback( parent ); + case 'ZYX': - parent.traverseAncestors( callback ); + this._y = Math.asin( - clamp( m31, -1, 1 ) ); - } + if ( Math.abs( m31 ) < 0.9999999 ) { - } + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); - updateMatrix() { + } else { - this.matrix.compose( this.position, this.quaternion, this.scale ); + this._x = 0; + this._z = Math.atan2( - m12, m22 ); - this.matrixWorldNeedsUpdate = true; + } - } + break; - updateMatrixWorld( force ) { + case 'YZX': - if ( this.matrixAutoUpdate ) this.updateMatrix(); + this._z = Math.asin( clamp( m21, -1, 1 ) ); - if ( this.matrixWorldNeedsUpdate || force ) { + if ( Math.abs( m21 ) < 0.9999999 ) { - if ( this.parent === null ) { + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); - this.matrixWorld.copy( this.matrix ); + } else { - } else { + this._x = 0; + this._y = Math.atan2( m13, m33 ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } - } + break; - this.matrixWorldNeedsUpdate = false; + case 'XZY': - force = true; + this._z = Math.asin( - clamp( m12, -1, 1 ) ); - } + if ( Math.abs( m12 ) < 0.9999999 ) { - // update children + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); - const children = this.children; + } else { - for ( let i = 0, l = children.length; i < l; i ++ ) { + this._x = Math.atan2( - m23, m33 ); + this._y = 0; - const child = children[ i ]; + } - if ( child.matrixWorldAutoUpdate === true || force === true ) { + break; - child.updateMatrixWorld( force ); + default: - } + console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); } - } + this._order = order; - updateWorldMatrix( updateParents, updateChildren ) { + if ( update === true ) this._onChangeCallback(); - const parent = this.parent; + return this; - if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { + } - parent.updateWorldMatrix( true, false ); + /** + * Sets the angles of this Euler instance from a normalized quaternion. + * + * @param {Quaternion} q - A normalized Quaternion. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ + setFromQuaternion( q, order, update ) { - } + _matrix$2.makeRotationFromQuaternion( q ); - if ( this.matrixAutoUpdate ) this.updateMatrix(); + return this.setFromRotationMatrix( _matrix$2, order, update ); - if ( this.parent === null ) { + } - this.matrixWorld.copy( this.matrix ); + /** + * Sets the angles of this Euler instance from the given vector. + * + * @param {Vector3} v - The vector. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + setFromVector3( v, order = this._order ) { - } else { + return this.set( v.x, v.y, v.z, order ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } - } + /** + * Resets the euler angle with a new order by creating a quaternion from this + * euler angle and then setting this euler angle with the quaternion and the + * new order. + * + * Warning: This discards revolution information. + * + * @param {string} [newOrder] - A string representing the new order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + reorder( newOrder ) { - // update children + _quaternion$1.setFromEuler( this ); - if ( updateChildren === true ) { + return this.setFromQuaternion( _quaternion$1, newOrder ); - const children = this.children; + } - for ( let i = 0, l = children.length; i < l; i ++ ) { + /** + * Returns `true` if this Euler instance is equal with the given one. + * + * @param {Euler} euler - The Euler instance to test for equality. + * @return {boolean} Whether this Euler instance is equal with the given one. + */ + equals( euler ) { - const child = children[ i ]; + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - if ( child.matrixWorldAutoUpdate === true ) { + } - child.updateWorldMatrix( false, true ); + /** + * Sets this Euler instance's components to values from the given array. The first three + * entries of the array are assign to the x,y and z components. An optional fourth entry + * defines the Euler order. + * + * @param {Array} array - An array holding the Euler component values. + * @return {Euler} A reference to this Euler instance. + */ + fromArray( array ) { - } + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - } + this._onChangeCallback(); - } + return this; } - toJSON( meta ) { + /** + * Writes the components of this Euler instance to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the Euler components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The Euler components. + */ + toArray( array = [], offset = 0 ) { - // meta is a string when called from JSON.stringify - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._order; - const output = {}; + return array; - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if ( isRootObject ) { + } - // initialize meta obj - meta = { - geometries: {}, - materials: {}, - textures: {}, - images: {}, - shapes: {}, - skeletons: {}, - animations: {}, - nodes: {} - }; + _onChange( callback ) { - output.metadata = { - version: 4.6, - type: 'Object', - generator: 'Object3D.toJSON' - }; + this._onChangeCallback = callback; - } + return this; - // standard Object3D serialization + } - const object = {}; + _onChangeCallback() {} - object.uuid = this.uuid; - object.type = this.type; + *[ Symbol.iterator ]() { - if ( this.name !== '' ) object.name = this.name; - if ( this.castShadow === true ) object.castShadow = true; - if ( this.receiveShadow === true ) object.receiveShadow = true; - if ( this.visible === false ) object.visible = false; - if ( this.frustumCulled === false ) object.frustumCulled = false; - if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; - if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; + yield this._x; + yield this._y; + yield this._z; + yield this._order; - object.layers = this.layers.mask; - object.matrix = this.matrix.toArray(); - object.up = this.up.toArray(); + } - if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; +} - // object specific properties +/** + * The default Euler angle order. + * + * @static + * @type {string} + * @default 'XYZ' + */ +Euler.DEFAULT_ORDER = 'XYZ'; - if ( this.isInstancedMesh ) { +/** + * A layers object assigns an 3D object to 1 or more of 32 + * layers numbered `0` to `31` - internally the layers are stored as a + * bit mask], and by default all 3D objects are a member of layer `0`. + * + * This can be used to control visibility - an object must share a layer with + * a camera to be visible when that camera's view is + * rendered. + * + * All classes that inherit from {@link Object3D} have an `layers` property which + * is an instance of this class. + */ +class Layers { - object.type = 'InstancedMesh'; - object.count = this.count; - object.instanceMatrix = this.instanceMatrix.toJSON(); - if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); + /** + * Constructs a new layers instance, with membership + * initially set to layer `0`. + */ + constructor() { - } + /** + * A bit mask storing which of the 32 layers this layers object is currently + * a member of. + * + * @type {number} + */ + this.mask = 1 | 0; - if ( this.isBatchedMesh ) { + } - object.type = 'BatchedMesh'; - object.perObjectFrustumCulled = this.perObjectFrustumCulled; - object.sortObjects = this.sortObjects; + /** + * Sets membership to the given layer, and remove membership all other layers. + * + * @param {number} layer - The layer to set. + */ + set( layer ) { - object.drawRanges = this._drawRanges; - object.reservedRanges = this._reservedRanges; + this.mask = ( 1 << layer | 0 ) >>> 0; - object.visibility = this._visibility; - object.active = this._active; - object.bounds = this._bounds.map( bound => ( { - boxInitialized: bound.boxInitialized, - boxMin: bound.box.min.toArray(), - boxMax: bound.box.max.toArray(), + } - sphereInitialized: bound.sphereInitialized, - sphereRadius: bound.sphere.radius, - sphereCenter: bound.sphere.center.toArray() - } ) ); + /** + * Adds membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + enable( layer ) { - object.maxGeometryCount = this._maxGeometryCount; - object.maxVertexCount = this._maxVertexCount; - object.maxIndexCount = this._maxIndexCount; + this.mask |= 1 << layer | 0; - object.geometryInitialized = this._geometryInitialized; - object.geometryCount = this._geometryCount; + } - object.matricesTexture = this._matricesTexture.toJSON( meta ); + /** + * Adds membership to all layers. + */ + enableAll() { - if ( this.boundingSphere !== null ) { + this.mask = 0xffffffff | 0; - object.boundingSphere = { - center: object.boundingSphere.center.toArray(), - radius: object.boundingSphere.radius - }; + } - } + /** + * Toggles the membership of the given layer. + * + * @param {number} layer - The layer to toggle. + */ + toggle( layer ) { - if ( this.boundingBox !== null ) { + this.mask ^= 1 << layer | 0; - object.boundingBox = { - min: object.boundingBox.min.toArray(), - max: object.boundingBox.max.toArray() - }; + } - } + /** + * Removes membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + disable( layer ) { - } + this.mask &= ~ ( 1 << layer | 0 ); - // + } - function serialize( library, element ) { + /** + * Removes the membership from all layers. + */ + disableAll() { - if ( library[ element.uuid ] === undefined ) { + this.mask = 0; - library[ element.uuid ] = element.toJSON( meta ); + } - } + /** + * Returns `true` if this and the given layers object have at least one + * layer in common. + * + * @param {Layers} layers - The layers to test. + * @return {boolean } Whether this and the given layers object have at least one layer in common or not. + */ + test( layers ) { - return element.uuid; + return ( this.mask & layers.mask ) !== 0; - } + } - if ( this.isScene ) { + /** + * Returns `true` if the given layer is enabled. + * + * @param {number} layer - The layer to test. + * @return {boolean } Whether the given layer is enabled or not. + */ + isEnabled( layer ) { - if ( this.background ) { + return ( this.mask & ( 1 << layer | 0 ) ) !== 0; - if ( this.background.isColor ) { + } - object.background = this.background.toJSON(); +} - } else if ( this.background.isTexture ) { +let _object3DId = 0; - object.background = this.background.toJSON( meta ).uuid; +const _v1$3 = /*@__PURE__*/ new Vector3(); +const _q1 = /*@__PURE__*/ new Quaternion(); +const _m1$3 = /*@__PURE__*/ new Matrix4(); +const _target = /*@__PURE__*/ new Vector3(); - } +const _position$1 = /*@__PURE__*/ new Vector3(); +const _scale = /*@__PURE__*/ new Vector3(); +const _quaternion = /*@__PURE__*/ new Quaternion(); - } +const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); +const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); +const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); - if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { +/** + * Fires when the object has been added to its parent object. + * + * @event Object3D#added + * @type {Object} + */ +const _addedEvent = { type: 'added' }; - object.environment = this.environment.toJSON( meta ).uuid; +/** + * Fires when the object has been removed from its parent object. + * + * @event Object3D#removed + * @type {Object} + */ +const _removedEvent = { type: 'removed' }; - } +/** + * Fires when a new child object has been added. + * + * @event Object3D#childadded + * @type {Object} + */ +const _childaddedEvent = { type: 'childadded', child: null }; - } else if ( this.isMesh || this.isLine || this.isPoints ) { +/** + * Fires when a child object has been removed. + * + * @event Object3D#childremoved + * @type {Object} + */ +const _childremovedEvent = { type: 'childremoved', child: null }; - object.geometry = serialize( meta.geometries, this.geometry ); +/** + * This is the base class for most objects in three.js and provides a set of + * properties and methods for manipulating objects in 3D space. + * + * @augments EventDispatcher + */ +class Object3D extends EventDispatcher { - const parameters = this.geometry.parameters; + /** + * Constructs a new 3D object. + */ + constructor() { - if ( parameters !== undefined && parameters.shapes !== undefined ) { + super(); - const shapes = parameters.shapes; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isObject3D = true; - if ( Array.isArray( shapes ) ) { + /** + * The ID of the 3D object. + * + * @name Object3D#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _object3DId ++ } ); - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - const shape = shapes[ i ]; + /** + * The name of the 3D object. + * + * @type {string} + */ + this.name = ''; - serialize( meta.shapes, shape ); + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Object3D'; - } + /** + * A reference to the parent object. + * + * @type {?Object3D} + * @default null + */ + this.parent = null; - } else { + /** + * An array holding the child 3D objects of this instance. + * + * @type {Array} + */ + this.children = []; - serialize( meta.shapes, shapes ); + /** + * Defines the `up` direction of the 3D object which influences + * the orientation via methods like {@link Object3D#lookAt}. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. + * + * @type {Vector3} + */ + this.up = Object3D.DEFAULT_UP.clone(); - } + const position = new Vector3(); + const rotation = new Euler(); + const quaternion = new Quaternion(); + const scale = new Vector3( 1, 1, 1 ); - } + function onRotationChange() { - } + quaternion.setFromEuler( rotation, false ); - if ( this.isSkinnedMesh ) { + } - object.bindMode = this.bindMode; - object.bindMatrix = this.bindMatrix.toArray(); + function onQuaternionChange() { - if ( this.skeleton !== undefined ) { + rotation.setFromQuaternion( quaternion, undefined, false ); - serialize( meta.skeletons, this.skeleton ); + } - object.skeleton = this.skeleton.uuid; + rotation._onChange( onRotationChange ); + quaternion._onChange( onQuaternionChange ); + Object.defineProperties( this, { + /** + * Represents the object's local position. + * + * @name Object3D#position + * @type {Vector3} + * @default (0,0,0) + */ + position: { + configurable: true, + enumerable: true, + value: position + }, + /** + * Represents the object's local rotation as Euler angles, in radians. + * + * @name Object3D#rotation + * @type {Euler} + * @default (0,0,0) + */ + rotation: { + configurable: true, + enumerable: true, + value: rotation + }, + /** + * Represents the object's local rotation as Quaternions. + * + * @name Object3D#quaternion + * @type {Quaternion} + */ + quaternion: { + configurable: true, + enumerable: true, + value: quaternion + }, + /** + * Represents the object's local scale. + * + * @name Object3D#scale + * @type {Vector3} + * @default (1,1,1) + */ + scale: { + configurable: true, + enumerable: true, + value: scale + }, + /** + * Represents the object's model-view matrix. + * + * @name Object3D#modelViewMatrix + * @type {Matrix4} + */ + modelViewMatrix: { + value: new Matrix4() + }, + /** + * Represents the object's normal matrix. + * + * @name Object3D#normalMatrix + * @type {Matrix3} + */ + normalMatrix: { + value: new Matrix3() } + } ); - } + /** + * Represents the object's transformation matrix in local space. + * + * @type {Matrix4} + */ + this.matrix = new Matrix4(); - if ( this.material !== undefined ) { + /** + * Represents the object's transformation matrix in world space. + * If the 3D object has no parent, then it's identical to the local transformation matrix + * + * @type {Matrix4} + */ + this.matrixWorld = new Matrix4(); - if ( Array.isArray( this.material ) ) { + /** + * When set to `true`, the engine automatically computes the local matrix from position, + * rotation and scale every frame. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; - const uuids = []; + /** + * When set to `true`, the engine automatically computes the world matrix from the current local + * matrix and the object's transformation hierarchy. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ + this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer - for ( let i = 0, l = this.material.length; i < l; i ++ ) { + /** + * When set to `true`, it calculates the world matrix in that frame and resets this property + * to `false`. + * + * @type {boolean} + * @default false + */ + this.matrixWorldNeedsUpdate = false; - uuids.push( serialize( meta.materials, this.material[ i ] ) ); + /** + * The layer membership of the 3D object. The 3D object is only visible if it has + * at least one layer in common with the camera in use. This property can also be + * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. + * + * @type {Layers} + */ + this.layers = new Layers(); - } + /** + * When set to `true`, the 3D object gets rendered. + * + * @type {boolean} + * @default true + */ + this.visible = true; - object.material = uuids; + /** + * When set to `true`, the 3D object gets rendered into shadow maps. + * + * @type {boolean} + * @default false + */ + this.castShadow = false; - } else { + /** + * When set to `true`, the 3D object is affected by shadows in the scene. + * + * @type {boolean} + * @default false + */ + this.receiveShadow = false; - object.material = serialize( meta.materials, this.material ); + /** + * When set to `true`, the 3D object is honored by view frustum culling. + * + * @type {boolean} + * @default true + */ + this.frustumCulled = true; - } + /** + * This value allows the default rendering order of scene graph objects to be + * overridden although opaque and transparent objects remain sorted independently. + * When this property is set for an instance of {@link Group},all descendants + * objects will be sorted and rendered together. Sorting is from lowest to highest + * render order. + * + * @type {number} + * @default 0 + */ + this.renderOrder = 0; - } + /** + * An array holding the animation clips of the 3D object. + * + * @type {Array} + */ + this.animations = []; - // + /** + * Custom depth material to be used when rendering to the depth map. Can only be used + * in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight}, + * if you are modifying vertex positions in the vertex shader you must specify a custom depth + * material for proper shadows. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDepthMaterial = undefined; - if ( this.children.length > 0 ) { + /** + * Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDistanceMaterial = undefined; - object.children = []; + /** + * An object that can be used to store custom data about the 3D object. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - for ( let i = 0; i < this.children.length; i ++ ) { + } - object.children.push( this.children[ i ].toJSON( meta ).object ); + /** + * A callback that is executed immediately before a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} - } + /** + * A callback that is executed immediately after a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} - } + /** + * A callback that is executed immediately before a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ + onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} - // + /** + * A callback that is executed immediately after a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ + onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} - if ( this.animations.length > 0 ) { + /** + * Applies the given transformation matrix to the object and updates the object's position, + * rotation and scale. + * + * @param {Matrix4} matrix - The transformation matrix. + */ + applyMatrix4( matrix ) { - object.animations = []; + if ( this.matrixAutoUpdate ) this.updateMatrix(); - for ( let i = 0; i < this.animations.length; i ++ ) { + this.matrix.premultiply( matrix ); - const animation = this.animations[ i ]; + this.matrix.decompose( this.position, this.quaternion, this.scale ); - object.animations.push( serialize( meta.animations, animation ) ); + } - } - - } - - if ( isRootObject ) { - - const geometries = extractFromCache( meta.geometries ); - const materials = extractFromCache( meta.materials ); - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const shapes = extractFromCache( meta.shapes ); - const skeletons = extractFromCache( meta.skeletons ); - const animations = extractFromCache( meta.animations ); - const nodes = extractFromCache( meta.nodes ); - - if ( geometries.length > 0 ) output.geometries = geometries; - if ( materials.length > 0 ) output.materials = materials; - if ( textures.length > 0 ) output.textures = textures; - if ( images.length > 0 ) output.images = images; - if ( shapes.length > 0 ) output.shapes = shapes; - if ( skeletons.length > 0 ) output.skeletons = skeletons; - if ( animations.length > 0 ) output.animations = animations; - if ( nodes.length > 0 ) output.nodes = nodes; - - } - - output.object = object; - - return output; + /** + * Applies a rotation represented by given the quaternion to the 3D object. + * + * @param {Quaternion} q - The quaternion. + * @return {Object3D} A reference to this instance. + */ + applyQuaternion( q ) { - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache( cache ) { + this.quaternion.premultiply( q ); - const values = []; - for ( const key in cache ) { + return this; - const data = cache[ key ]; - delete data.metadata; - values.push( data ); + } - } + /** + * Sets the given rotation represented as an axis/angle couple to the 3D object. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + */ + setRotationFromAxisAngle( axis, angle ) { - return values; + // assumes axis is normalized - } + this.quaternion.setFromAxisAngle( axis, angle ); } - clone( recursive ) { + /** + * Sets the given rotation represented as Euler angles to the 3D object. + * + * @param {Euler} euler - The Euler angles. + */ + setRotationFromEuler( euler ) { - return new this.constructor().copy( this, recursive ); + this.quaternion.setFromEuler( euler, true ); } - copy( source, recursive = true ) { - - this.name = source.name; - - this.up.copy( source.up ); - - this.position.copy( source.position ); - this.rotation.order = source.rotation.order; - this.quaternion.copy( source.quaternion ); - this.scale.copy( source.scale ); - - this.matrix.copy( source.matrix ); - this.matrixWorld.copy( source.matrixWorld ); - - this.matrixAutoUpdate = source.matrixAutoUpdate; + /** + * Sets the given rotation represented as rotation matrix to the 3D object. + * + * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be + * a pure rotation matrix (i.e, unscaled). + */ + setRotationFromMatrix( m ) { - this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - this.layers.mask = source.layers.mask; - this.visible = source.visible; + this.quaternion.setFromRotationMatrix( m ); - this.castShadow = source.castShadow; - this.receiveShadow = source.receiveShadow; + } - this.frustumCulled = source.frustumCulled; - this.renderOrder = source.renderOrder; + /** + * Sets the given rotation represented as a Quaternion to the 3D object. + * + * @param {Quaternion} q - The Quaternion + */ + setRotationFromQuaternion( q ) { - this.animations = source.animations.slice(); + // assumes q is normalized - this.userData = JSON.parse( JSON.stringify( source.userData ) ); + this.quaternion.copy( q ); - if ( recursive === true ) { + } - for ( let i = 0; i < source.children.length; i ++ ) { + /** + * Rotates the 3D object along an axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateOnAxis( axis, angle ) { - const child = source.children[ i ]; - this.add( child.clone() ); + // rotate object on axis in object space + // axis is assumed to be normalized - } + _q1.setFromAxisAngle( axis, angle ); - } + this.quaternion.multiply( _q1 ); return this; } -} + /** + * Rotates the 3D object along an axis in world space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateOnWorldAxis( axis, angle ) { -Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); -Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; -Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; + // rotate object on axis in world space + // axis is assumed to be normalized + // method assumes no rotated parent -let _id$1 = 0; + _q1.setFromAxisAngle( axis, angle ); -const _m1$2 = /*@__PURE__*/ new Matrix4(); -const _obj = /*@__PURE__*/ new Object3D(); -const _offset = /*@__PURE__*/ new Vector3(); -const _box$1 = /*@__PURE__*/ new Box3(); -const _boxMorphTargets = /*@__PURE__*/ new Box3(); -const _vector$4 = /*@__PURE__*/ new Vector3(); + this.quaternion.premultiply( _q1 ); -class BufferGeometry extends EventDispatcher { + return this; - constructor() { + } - super(); + /** + * Rotates the 3D object around its X axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateX( angle ) { - this.isBufferGeometry = true; + return this.rotateOnAxis( _xAxis, angle ); - Object.defineProperty( this, 'id', { value: _id$1 ++ } ); + } - this.uuid = generateUUID(); + /** + * Rotates the 3D object around its Y axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateY( angle ) { - this.name = ''; - this.type = 'BufferGeometry'; + return this.rotateOnAxis( _yAxis, angle ); - this.index = null; - this.attributes = {}; + } - this.morphAttributes = {}; - this.morphTargetsRelative = false; + /** + * Rotates the 3D object around its Z axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateZ( angle ) { - this.groups = []; + return this.rotateOnAxis( _zAxis, angle ); - this.boundingBox = null; - this.boundingSphere = null; + } - this.drawRange = { start: 0, count: Infinity }; + /** + * Translate the 3D object by a distance along the given axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateOnAxis( axis, distance ) { - this.userData = {}; + // translate object by distance along axis in object space + // axis is assumed to be normalized - } + _v1$3.copy( axis ).applyQuaternion( this.quaternion ); - getIndex() { + this.position.add( _v1$3.multiplyScalar( distance ) ); - return this.index; + return this; } - setIndex( index ) { - - if ( Array.isArray( index ) ) { - - this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); + /** + * Translate the 3D object by a distance along its X-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateX( distance ) { - } else { + return this.translateOnAxis( _xAxis, distance ); - this.index = index; + } - } + /** + * Translate the 3D object by a distance along its Y-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateY( distance ) { - return this; + return this.translateOnAxis( _yAxis, distance ); } - getAttribute( name ) { + /** + * Translate the 3D object by a distance along its Z-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateZ( distance ) { - return this.attributes[ name ]; + return this.translateOnAxis( _zAxis, distance ); } - setAttribute( name, attribute ) { + /** + * Converts the given vector from this 3D object's local space to world space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ + localToWorld( vector ) { - this.attributes[ name ] = attribute; + this.updateWorldMatrix( true, false ); - return this; + return vector.applyMatrix4( this.matrixWorld ); } - deleteAttribute( name ) { + /** + * Converts the given vector from this 3D object's word space to local space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ + worldToLocal( vector ) { - delete this.attributes[ name ]; + this.updateWorldMatrix( true, false ); - return this; + return vector.applyMatrix4( _m1$3.copy( this.matrixWorld ).invert() ); } - hasAttribute( name ) { - - return this.attributes[ name ] !== undefined; + /** + * Rotates the object to face a point in world space. + * + * This method does not support objects having non-uniformly-scaled parent(s). + * + * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space + * @param {number} [y] - The y coordinate in world space. + * @param {number} [z] - The z coordinate in world space. + */ + lookAt( x, y, z ) { - } + // This method does not support objects having non-uniformly-scaled parent(s) - addGroup( start, count, materialIndex = 0 ) { + if ( x.isVector3 ) { - this.groups.push( { + _target.copy( x ); - start: start, - count: count, - materialIndex: materialIndex + } else { - } ); + _target.set( x, y, z ); - } + } - clearGroups() { + const parent = this.parent; - this.groups = []; + this.updateWorldMatrix( true, false ); - } + _position$1.setFromMatrixPosition( this.matrixWorld ); - setDrawRange( start, count ) { + if ( this.isCamera || this.isLight ) { - this.drawRange.start = start; - this.drawRange.count = count; + _m1$3.lookAt( _position$1, _target, this.up ); - } + } else { - applyMatrix4( matrix ) { + _m1$3.lookAt( _target, _position$1, this.up ); - const position = this.attributes.position; + } - if ( position !== undefined ) { + this.quaternion.setFromRotationMatrix( _m1$3 ); - position.applyMatrix4( matrix ); + if ( parent ) { - position.needsUpdate = true; + _m1$3.extractRotation( parent.matrixWorld ); + _q1.setFromRotationMatrix( _m1$3 ); + this.quaternion.premultiply( _q1.invert() ); } - const normal = this.attributes.normal; + } - if ( normal !== undefined ) { + /** + * Adds the given 3D object as a child to this 3D object. An arbitrary number of + * objects may be added. Any current parent on an object passed in here will be + * removed, since an object can have at most one parent. + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to add. + * @return {Object3D} A reference to this instance. + */ + add( object ) { - const normalMatrix = new Matrix3().getNormalMatrix( matrix ); + if ( arguments.length > 1 ) { - normal.applyNormalMatrix( normalMatrix ); + for ( let i = 0; i < arguments.length; i ++ ) { - normal.needsUpdate = true; + this.add( arguments[ i ] ); - } + } - const tangent = this.attributes.tangent; + return this; - if ( tangent !== undefined ) { + } - tangent.transformDirection( matrix ); + if ( object === this ) { - tangent.needsUpdate = true; + console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); + return this; } - if ( this.boundingBox !== null ) { + if ( object && object.isObject3D ) { - this.computeBoundingBox(); + object.removeFromParent(); + object.parent = this; + this.children.push( object ); - } + object.dispatchEvent( _addedEvent ); - if ( this.boundingSphere !== null ) { + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; - this.computeBoundingSphere(); + } else { + + console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); } @@ -23026,17587 +29757,20096 @@ class BufferGeometry extends EventDispatcher { } - applyQuaternion( q ) { - - _m1$2.makeRotationFromQuaternion( q ); - - this.applyMatrix4( _m1$2 ); + /** + * Removes the given 3D object as child from this 3D object. + * An arbitrary number of objects may be removed. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @param {Object3D} object - The 3D object to remove. + * @return {Object3D} A reference to this instance. + */ + remove( object ) { - return this; + if ( arguments.length > 1 ) { - } + for ( let i = 0; i < arguments.length; i ++ ) { - rotateX( angle ) { + this.remove( arguments[ i ] ); - // rotate geometry around world x-axis + } - _m1$2.makeRotationX( angle ); + return this; - this.applyMatrix4( _m1$2 ); + } - return this; + const index = this.children.indexOf( object ); - } + if ( index !== -1 ) { - rotateY( angle ) { + object.parent = null; + this.children.splice( index, 1 ); - // rotate geometry around world y-axis + object.dispatchEvent( _removedEvent ); - _m1$2.makeRotationY( angle ); + _childremovedEvent.child = object; + this.dispatchEvent( _childremovedEvent ); + _childremovedEvent.child = null; - this.applyMatrix4( _m1$2 ); + } return this; } - rotateZ( angle ) { + /** + * Removes this 3D object from its current parent. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ + removeFromParent() { - // rotate geometry around world z-axis + const parent = this.parent; - _m1$2.makeRotationZ( angle ); + if ( parent !== null ) { - this.applyMatrix4( _m1$2 ); + parent.remove( this ); + + } return this; } - translate( x, y, z ) { + /** + * Removes all child objects. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ + clear() { - // translate geometry + return this.remove( ... this.children ); - _m1$2.makeTranslation( x, y, z ); + } - this.applyMatrix4( _m1$2 ); + /** + * Adds the given 3D object as a child of this 3D object, while maintaining the object's world + * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to attach. + * @return {Object3D} A reference to this instance. + */ + attach( object ) { - return this; + // adds object as a child of this, while maintaining the object's world transform - } + // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) - scale( x, y, z ) { + this.updateWorldMatrix( true, false ); - // scale geometry + _m1$3.copy( this.matrixWorld ).invert(); - _m1$2.makeScale( x, y, z ); + if ( object.parent !== null ) { - this.applyMatrix4( _m1$2 ); + object.parent.updateWorldMatrix( true, false ); - return this; + _m1$3.multiply( object.parent.matrixWorld ); - } + } - lookAt( vector ) { + object.applyMatrix4( _m1$3 ); - _obj.lookAt( vector ); + object.removeFromParent(); + object.parent = this; + this.children.push( object ); - _obj.updateMatrix(); + object.updateWorldMatrix( false, true ); - this.applyMatrix4( _obj.matrix ); + object.dispatchEvent( _addedEvent ); + + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; return this; } - center() { + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching ID. + * + * @param {number} id - The id. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectById( id ) { - this.computeBoundingBox(); + return this.getObjectByProperty( 'id', id ); - this.boundingBox.getCenter( _offset ).negate(); + } - this.translate( _offset.x, _offset.y, _offset.z ); + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching name. + * + * @param {string} name - The name. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectByName( name ) { - return this; + return this.getObjectByProperty( 'name', name ); } - setFromPoints( points ) { - - const position = []; - - for ( let i = 0, l = points.length; i < l; i ++ ) { + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectByProperty( name, value ) { - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); + if ( this[ name ] === value ) return this; - } + for ( let i = 0, l = this.children.length; i < l; i ++ ) { - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + const child = this.children[ i ]; + const object = child.getObjectByProperty( name, value ); - return this; + if ( object !== undefined ) { - } + return object; - computeBoundingBox() { + } - if ( this.boundingBox === null ) { + } - this.boundingBox = new Box3(); + return undefined; - } + } - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns all 3D objects with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @param {Array} result - The method stores the result in this array. + * @return {Array} The found 3D objects. + */ + getObjectsByProperty( name, value, result = [] ) { - if ( position && position.isGLBufferAttribute ) { + if ( this[ name ] === value ) result.push( this ); - console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); + const children = this.children; - this.boundingBox.set( - new Vector3( - Infinity, - Infinity, - Infinity ), - new Vector3( + Infinity, + Infinity, + Infinity ) - ); + for ( let i = 0, l = children.length; i < l; i ++ ) { - return; + children[ i ].getObjectsByProperty( name, value, result ); } - if ( position !== undefined ) { + return result; - this.boundingBox.setFromBufferAttribute( position ); + } - // process morph attributes if present + /** + * Returns a vector representing the position of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's position in world space. + */ + getWorldPosition( target ) { - if ( morphAttributesPosition ) { + this.updateWorldMatrix( true, false ); - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + return target.setFromMatrixPosition( this.matrixWorld ); - const morphAttribute = morphAttributesPosition[ i ]; - _box$1.setFromBufferAttribute( morphAttribute ); + } - if ( this.morphTargetsRelative ) { + /** + * Returns a Quaternion representing the position of the 3D object in world space. + * + * @param {Quaternion} target - The target Quaternion the result is stored to. + * @return {Quaternion} The 3D object's rotation in world space. + */ + getWorldQuaternion( target ) { - _vector$4.addVectors( this.boundingBox.min, _box$1.min ); - this.boundingBox.expandByPoint( _vector$4 ); + this.updateWorldMatrix( true, false ); - _vector$4.addVectors( this.boundingBox.max, _box$1.max ); - this.boundingBox.expandByPoint( _vector$4 ); + this.matrixWorld.decompose( _position$1, target, _scale ); - } else { + return target; - this.boundingBox.expandByPoint( _box$1.min ); - this.boundingBox.expandByPoint( _box$1.max ); + } - } + /** + * Returns a vector representing the scale of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's scale in world space. + */ + getWorldScale( target ) { - } + this.updateWorldMatrix( true, false ); - } + this.matrixWorld.decompose( _position$1, _quaternion, target ); - } else { + return target; - this.boundingBox.makeEmpty(); + } - } + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ + getWorldDirection( target ) { - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { + this.updateWorldMatrix( true, false ); - console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); + const e = this.matrixWorld.elements; - } + return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); } - computeBoundingSphere() { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } + /** + * Abstract method to get intersections between a casted ray and this + * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} + * implement this method in order to use raycasting. + * + * @abstract + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - An array holding the result of the method. + */ + raycast( /* raycaster, intersects */ ) {} - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; + /** + * Executes the callback on this 3D object and all descendants. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverse( callback ) { - if ( position && position.isGLBufferAttribute ) { + callback( this ); - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); + const children = this.children; - this.boundingSphere.set( new Vector3(), Infinity ); + for ( let i = 0, l = children.length; i < l; i ++ ) { - return; + children[ i ].traverse( callback ); } - if ( position ) { - - // first, find the center of the bounding sphere - - const center = this.boundingSphere.center; + } - _box$1.setFromBufferAttribute( position ); + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. + * Descendants of invisible 3D objects are not traversed. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverseVisible( callback ) { - // process morph attributes if present + if ( this.visible === false ) return; - if ( morphAttributesPosition ) { + callback( this ); - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + const children = this.children; - const morphAttribute = morphAttributesPosition[ i ]; - _boxMorphTargets.setFromBufferAttribute( morphAttribute ); + for ( let i = 0, l = children.length; i < l; i ++ ) { - if ( this.morphTargetsRelative ) { + children[ i ].traverseVisible( callback ); - _vector$4.addVectors( _box$1.min, _boxMorphTargets.min ); - _box$1.expandByPoint( _vector$4 ); + } - _vector$4.addVectors( _box$1.max, _boxMorphTargets.max ); - _box$1.expandByPoint( _vector$4 ); + } - } else { + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverseAncestors( callback ) { - _box$1.expandByPoint( _boxMorphTargets.min ); - _box$1.expandByPoint( _boxMorphTargets.max ); + const parent = this.parent; - } + if ( parent !== null ) { - } + callback( parent ); - } + parent.traverseAncestors( callback ); - _box$1.getCenter( center ); + } - // second, try to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + } - let maxRadiusSq = 0; + /** + * Updates the transformation matrix in local space by computing it from the current + * position, rotation and scale values. + */ + updateMatrix() { - for ( let i = 0, il = position.count; i < il; i ++ ) { + this.matrix.compose( this.position, this.quaternion, this.scale ); - _vector$4.fromBufferAttribute( position, i ); + this.matrixWorldNeedsUpdate = true; - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); + } - } + /** + * Updates the transformation matrix in world space of this 3D objects and its descendants. + * + * To ensure correct results, this method also recomputes the 3D object's transformation matrix in + * local space. The computation of the local and world matrix can be controlled with the + * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both + * `true` by default. Set these flags to `false` if you need more control over the update matrix process. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ + updateMatrixWorld( force ) { - // process morph attributes if present + if ( this.matrixAutoUpdate ) this.updateMatrix(); - if ( morphAttributesPosition ) { + if ( this.matrixWorldNeedsUpdate || force ) { - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + if ( this.matrixWorldAutoUpdate === true ) { - const morphAttribute = morphAttributesPosition[ i ]; - const morphTargetsRelative = this.morphTargetsRelative; + if ( this.parent === null ) { - for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { + this.matrixWorld.copy( this.matrix ); - _vector$4.fromBufferAttribute( morphAttribute, j ); + } else { - if ( morphTargetsRelative ) { + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - _offset.fromBufferAttribute( position, j ); - _vector$4.add( _offset ); + } - } + } - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); + this.matrixWorldNeedsUpdate = false; - } + force = true; - } + } - } + // make sure descendants are updated if required - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + const children = this.children; - if ( isNaN( this.boundingSphere.radius ) ) { + for ( let i = 0, l = children.length; i < l; i ++ ) { - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); + const child = children[ i ]; - } + child.updateMatrixWorld( force ); } } - computeTangents() { - - const index = this.index; - const attributes = this.attributes; + /** + * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the + * update of ancestor and descendant nodes. + * + * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. + * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. + */ + updateWorldMatrix( updateParents, updateChildren ) { - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) + const parent = this.parent; - if ( index === null || - attributes.position === undefined || - attributes.normal === undefined || - attributes.uv === undefined ) { + if ( updateParents === true && parent !== null ) { - console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); - return; + parent.updateWorldMatrix( true, false ); } - const positionAttribute = attributes.position; - const normalAttribute = attributes.normal; - const uvAttribute = attributes.uv; - - if ( this.hasAttribute( 'tangent' ) === false ) { + if ( this.matrixAutoUpdate ) this.updateMatrix(); - this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); + if ( this.matrixWorldAutoUpdate === true ) { - } + if ( this.parent === null ) { - const tangentAttribute = this.getAttribute( 'tangent' ); + this.matrixWorld.copy( this.matrix ); - const tan1 = [], tan2 = []; + } else { - for ( let i = 0; i < positionAttribute.count; i ++ ) { + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - tan1[ i ] = new Vector3(); - tan2[ i ] = new Vector3(); + } } - const vA = new Vector3(), - vB = new Vector3(), - vC = new Vector3(), + // make sure descendants are updated - uvA = new Vector2(), - uvB = new Vector2(), - uvC = new Vector2(), + if ( updateChildren === true ) { - sdir = new Vector3(), - tdir = new Vector3(); + const children = this.children; - function handleTriangle( a, b, c ) { + for ( let i = 0, l = children.length; i < l; i ++ ) { - vA.fromBufferAttribute( positionAttribute, a ); - vB.fromBufferAttribute( positionAttribute, b ); - vC.fromBufferAttribute( positionAttribute, c ); + const child = children[ i ]; - uvA.fromBufferAttribute( uvAttribute, a ); - uvB.fromBufferAttribute( uvAttribute, b ); - uvC.fromBufferAttribute( uvAttribute, c ); + child.updateWorldMatrix( false, true ); - vB.sub( vA ); - vC.sub( vA ); + } - uvB.sub( uvA ); - uvC.sub( uvA ); + } - const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); + } - // silently ignore degenerate uv triangles having coincident or colinear vertices + /** + * Serializes the 3D object into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized 3D object. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - if ( ! isFinite( r ) ) return; + // meta is a string when called from JSON.stringify + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); - tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); + const output = {}; - tan1[ a ].add( sdir ); - tan1[ b ].add( sdir ); - tan1[ c ].add( sdir ); + // meta is a hash used to collect geometries, materials. + // not providing it implies that this is the root object + // being serialized. + if ( isRootObject ) { - tan2[ a ].add( tdir ); - tan2[ b ].add( tdir ); - tan2[ c ].add( tdir ); + // initialize meta obj + meta = { + geometries: {}, + materials: {}, + textures: {}, + images: {}, + shapes: {}, + skeletons: {}, + animations: {}, + nodes: {} + }; - } + output.metadata = { + version: 4.7, + type: 'Object', + generator: 'Object3D.toJSON' + }; - let groups = this.groups; + } - if ( groups.length === 0 ) { + // standard Object3D serialization - groups = [ { - start: 0, - count: index.count - } ]; + const object = {}; - } + object.uuid = this.uuid; + object.type = this.type; - for ( let i = 0, il = groups.length; i < il; ++ i ) { + if ( this.name !== '' ) object.name = this.name; + if ( this.castShadow === true ) object.castShadow = true; + if ( this.receiveShadow === true ) object.receiveShadow = true; + if ( this.visible === false ) object.visible = false; + if ( this.frustumCulled === false ) object.frustumCulled = false; + if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; + if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; - const group = groups[ i ]; + object.layers = this.layers.mask; + object.matrix = this.matrix.toArray(); + object.up = this.up.toArray(); - const start = group.start; - const count = group.count; + if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; - for ( let j = start, jl = start + count; j < jl; j += 3 ) { + // object specific properties - handleTriangle( - index.getX( j + 0 ), - index.getX( j + 1 ), - index.getX( j + 2 ) - ); + if ( this.isInstancedMesh ) { - } + object.type = 'InstancedMesh'; + object.count = this.count; + object.instanceMatrix = this.instanceMatrix.toJSON(); + if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); } - const tmp = new Vector3(), tmp2 = new Vector3(); - const n = new Vector3(), n2 = new Vector3(); + if ( this.isBatchedMesh ) { - function handleVertex( v ) { + object.type = 'BatchedMesh'; + object.perObjectFrustumCulled = this.perObjectFrustumCulled; + object.sortObjects = this.sortObjects; - n.fromBufferAttribute( normalAttribute, v ); - n2.copy( n ); + object.drawRanges = this._drawRanges; + object.reservedRanges = this._reservedRanges; - const t = tan1[ v ]; + object.geometryInfo = this._geometryInfo.map( info => ( { + ...info, + boundingBox: info.boundingBox ? info.boundingBox.toJSON() : undefined, + boundingSphere: info.boundingSphere ? info.boundingSphere.toJSON() : undefined + } ) ); + object.instanceInfo = this._instanceInfo.map( info => ( { ...info } ) ); - // Gram-Schmidt orthogonalize + object.availableInstanceIds = this._availableInstanceIds.slice(); + object.availableGeometryIds = this._availableGeometryIds.slice(); - tmp.copy( t ); - tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + object.nextIndexStart = this._nextIndexStart; + object.nextVertexStart = this._nextVertexStart; + object.geometryCount = this._geometryCount; - // Calculate handedness + object.maxInstanceCount = this._maxInstanceCount; + object.maxVertexCount = this._maxVertexCount; + object.maxIndexCount = this._maxIndexCount; - tmp2.crossVectors( n2, t ); - const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? - 1.0 : 1.0; + object.geometryInitialized = this._geometryInitialized; - tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); + object.matricesTexture = this._matricesTexture.toJSON( meta ); - } + object.indirectTexture = this._indirectTexture.toJSON( meta ); - for ( let i = 0, il = groups.length; i < il; ++ i ) { + if ( this._colorsTexture !== null ) { - const group = groups[ i ]; + object.colorsTexture = this._colorsTexture.toJSON( meta ); - const start = group.start; - const count = group.count; + } - for ( let j = start, jl = start + count; j < jl; j += 3 ) { + if ( this.boundingSphere !== null ) { - handleVertex( index.getX( j + 0 ) ); - handleVertex( index.getX( j + 1 ) ); - handleVertex( index.getX( j + 2 ) ); + object.boundingSphere = this.boundingSphere.toJSON(); + + } + + if ( this.boundingBox !== null ) { + + object.boundingBox = this.boundingBox.toJSON(); } } - } + // - computeVertexNormals() { + function serialize( library, element ) { - const index = this.index; - const positionAttribute = this.getAttribute( 'position' ); + if ( library[ element.uuid ] === undefined ) { - if ( positionAttribute !== undefined ) { + library[ element.uuid ] = element.toJSON( meta ); - let normalAttribute = this.getAttribute( 'normal' ); + } - if ( normalAttribute === undefined ) { + return element.uuid; - normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); - this.setAttribute( 'normal', normalAttribute ); + } - } else { + if ( this.isScene ) { - // reset existing normals to zero + if ( this.background ) { - for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { + if ( this.background.isColor ) { - normalAttribute.setXYZ( i, 0, 0, 0 ); + object.background = this.background.toJSON(); - } + } else if ( this.background.isTexture ) { - } + object.background = this.background.toJSON( meta ).uuid; - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); + } - // indexed elements + } - if ( index ) { + if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { - for ( let i = 0, il = index.count; i < il; i += 3 ) { + object.environment = this.environment.toJSON( meta ).uuid; - const vA = index.getX( i + 0 ); - const vB = index.getX( i + 1 ); - const vC = index.getX( i + 2 ); + } - pA.fromBufferAttribute( positionAttribute, vA ); - pB.fromBufferAttribute( positionAttribute, vB ); - pC.fromBufferAttribute( positionAttribute, vC ); + } else if ( this.isMesh || this.isLine || this.isPoints ) { - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); + object.geometry = serialize( meta.geometries, this.geometry ); - nA.fromBufferAttribute( normalAttribute, vA ); - nB.fromBufferAttribute( normalAttribute, vB ); - nC.fromBufferAttribute( normalAttribute, vC ); + const parameters = this.geometry.parameters; - nA.add( cb ); - nB.add( cb ); - nC.add( cb ); + if ( parameters !== undefined && parameters.shapes !== undefined ) { - normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); - normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); - normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); + const shapes = parameters.shapes; - } + if ( Array.isArray( shapes ) ) { - } else { + for ( let i = 0, l = shapes.length; i < l; i ++ ) { - // non-indexed elements (unconnected triangle soup) + const shape = shapes[ i ]; - for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { + serialize( meta.shapes, shape ); - pA.fromBufferAttribute( positionAttribute, i + 0 ); - pB.fromBufferAttribute( positionAttribute, i + 1 ); - pC.fromBufferAttribute( positionAttribute, i + 2 ); + } - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); + } else { - normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); + serialize( meta.shapes, shapes ); } } - this.normalizeNormals(); - - normalAttribute.needsUpdate = true; - } - } - - normalizeNormals() { + if ( this.isSkinnedMesh ) { - const normals = this.attributes.normal; + object.bindMode = this.bindMode; + object.bindMatrix = this.bindMatrix.toArray(); - for ( let i = 0, il = normals.count; i < il; i ++ ) { + if ( this.skeleton !== undefined ) { - _vector$4.fromBufferAttribute( normals, i ); + serialize( meta.skeletons, this.skeleton ); - _vector$4.normalize(); + object.skeleton = this.skeleton.uuid; - normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z ); + } } - } + if ( this.material !== undefined ) { - toNonIndexed() { + if ( Array.isArray( this.material ) ) { - function convertBufferAttribute( attribute, indices ) { + const uuids = []; - const array = attribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; + for ( let i = 0, l = this.material.length; i < l; i ++ ) { - const array2 = new array.constructor( indices.length * itemSize ); + uuids.push( serialize( meta.materials, this.material[ i ] ) ); - let index = 0, index2 = 0; + } - for ( let i = 0, l = indices.length; i < l; i ++ ) { + object.material = uuids; - if ( attribute.isInterleavedBufferAttribute ) { + } else { - index = indices[ i ] * attribute.data.stride + attribute.offset; + object.material = serialize( meta.materials, this.material ); - } else { + } - index = indices[ i ] * itemSize; + } - } + // - for ( let j = 0; j < itemSize; j ++ ) { + if ( this.children.length > 0 ) { - array2[ index2 ++ ] = array[ index ++ ]; + object.children = []; - } + for ( let i = 0; i < this.children.length; i ++ ) { - } + object.children.push( this.children[ i ].toJSON( meta ).object ); - return new BufferAttribute( array2, itemSize, normalized ); + } } // - if ( this.index === null ) { + if ( this.animations.length > 0 ) { - console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); - return this; + object.animations = []; - } + for ( let i = 0; i < this.animations.length; i ++ ) { - const geometry2 = new BufferGeometry(); + const animation = this.animations[ i ]; - const indices = this.index.array; - const attributes = this.attributes; + object.animations.push( serialize( meta.animations, animation ) ); - // attributes + } - for ( const name in attributes ) { + } - const attribute = attributes[ name ]; + if ( isRootObject ) { - const newAttribute = convertBufferAttribute( attribute, indices ); + const geometries = extractFromCache( meta.geometries ); + const materials = extractFromCache( meta.materials ); + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const shapes = extractFromCache( meta.shapes ); + const skeletons = extractFromCache( meta.skeletons ); + const animations = extractFromCache( meta.animations ); + const nodes = extractFromCache( meta.nodes ); - geometry2.setAttribute( name, newAttribute ); + if ( geometries.length > 0 ) output.geometries = geometries; + if ( materials.length > 0 ) output.materials = materials; + if ( textures.length > 0 ) output.textures = textures; + if ( images.length > 0 ) output.images = images; + if ( shapes.length > 0 ) output.shapes = shapes; + if ( skeletons.length > 0 ) output.skeletons = skeletons; + if ( animations.length > 0 ) output.animations = animations; + if ( nodes.length > 0 ) output.nodes = nodes; } - // morph attributes - - const morphAttributes = this.morphAttributes; - - for ( const name in morphAttributes ) { - - const morphArray = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + output.object = object; - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { + return output; - const attribute = morphAttribute[ i ]; + // extract data from the cache hash + // remove metadata on each item + // and return as array + function extractFromCache( cache ) { - const newAttribute = convertBufferAttribute( attribute, indices ); + const values = []; + for ( const key in cache ) { - morphArray.push( newAttribute ); + const data = cache[ key ]; + delete data.metadata; + values.push( data ); } - geometry2.morphAttributes[ name ] = morphArray; + return values; } - geometry2.morphTargetsRelative = this.morphTargetsRelative; + } - // groups + /** + * Returns a new 3D object with copied values from this instance. + * + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. + * @return {Object3D} A clone of this instance. + */ + clone( recursive ) { - const groups = this.groups; + return new this.constructor().copy( this, recursive ); - for ( let i = 0, l = groups.length; i < l; i ++ ) { + } - const group = groups[ i ]; - geometry2.addGroup( group.start, group.count, group.materialIndex ); + /** + * Copies the values of the given 3D object to this instance. + * + * @param {Object3D} source - The 3D object to copy. + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. + * @return {Object3D} A reference to this instance. + */ + copy( source, recursive = true ) { - } + this.name = source.name; - return geometry2; + this.up.copy( source.up ); - } + this.position.copy( source.position ); + this.rotation.order = source.rotation.order; + this.quaternion.copy( source.quaternion ); + this.scale.copy( source.scale ); - toJSON() { + this.matrix.copy( source.matrix ); + this.matrixWorld.copy( source.matrixWorld ); - const data = { - metadata: { - version: 4.6, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; + this.matrixAutoUpdate = source.matrixAutoUpdate; - // standard BufferGeometry serialization + this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; + this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; + this.layers.mask = source.layers.mask; + this.visible = source.visible; - if ( this.parameters !== undefined ) { + this.castShadow = source.castShadow; + this.receiveShadow = source.receiveShadow; - const parameters = this.parameters; + this.frustumCulled = source.frustumCulled; + this.renderOrder = source.renderOrder; - for ( const key in parameters ) { + this.animations = source.animations.slice(); - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + this.userData = JSON.parse( JSON.stringify( source.userData ) ); - } + if ( recursive === true ) { - return data; + for ( let i = 0; i < source.children.length; i ++ ) { + + const child = source.children[ i ]; + this.add( child.clone() ); + + } } - // for simplicity the code assumes attributes are not shared across geometries, see #15811 + return this; - data.data = { attributes: {} }; + } - const index = this.index; +} - if ( index !== null ) { +/** + * The default up direction for objects, also used as the default + * position for {@link DirectionalLight} and {@link HemisphereLight}. + * + * @static + * @type {Vector3} + * @default (0,1,0) + */ +Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); - data.data.index = { - type: index.array.constructor.name, - array: Array.prototype.slice.call( index.array ) - }; +/** + * The default setting for {@link Object3D#matrixAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ +Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; - } +/** + * The default setting for {@link Object3D#matrixWorldAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ +Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; - const attributes = this.attributes; +let _id$1 = 0; - for ( const key in attributes ) { +const _m1$2 = /*@__PURE__*/ new Matrix4(); +const _obj = /*@__PURE__*/ new Object3D(); +const _offset = /*@__PURE__*/ new Vector3(); +const _box$1 = /*@__PURE__*/ new Box3(); +const _boxMorphTargets = /*@__PURE__*/ new Box3(); +const _vector$4 = /*@__PURE__*/ new Vector3(); - const attribute = attributes[ key ]; +/** + * A representation of mesh, line, or point geometry. Includes vertex + * positions, face indices, normals, colors, UVs, and custom attributes + * within buffers, reducing the cost of passing all this data to the GPU. + * + * ```js + * const geometry = new THREE.BufferGeometry(); + * // create a simple square shape. We duplicate the top left and bottom right + * // vertices because each vertex needs to appear once per triangle. + * const vertices = new Float32Array( [ + * -1.0, -1.0, 1.0, // v0 + * 1.0, -1.0, 1.0, // v1 + * 1.0, 1.0, 1.0, // v2 + * + * 1.0, 1.0, 1.0, // v3 + * -1.0, 1.0, 1.0, // v4 + * -1.0, -1.0, 1.0 // v5 + * ] ); + * // itemSize = 3 because there are 3 values (components) per vertex + * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * ``` + * + * @augments EventDispatcher + */ +class BufferGeometry extends EventDispatcher { - data.data.attributes[ key ] = attribute.toJSON( data.data ); + /** + * Constructs a new geometry. + */ + constructor() { - } + super(); - const morphAttributes = {}; - let hasMorphAttributes = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferGeometry = true; - for ( const key in this.morphAttributes ) { + /** + * The ID of the geometry. + * + * @name BufferGeometry#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$1 ++ } ); - const attributeArray = this.morphAttributes[ key ]; + /** + * The UUID of the geometry. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - const array = []; + /** + * The name of the geometry. + * + * @type {string} + */ + this.name = ''; + this.type = 'BufferGeometry'; - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + /** + * Allows for vertices to be re-used across multiple triangles; this is + * called using "indexed triangles". Each triangle is associated with the + * indices of three vertices. This attribute therefore stores the index of + * each vertex for each triangular face. If this attribute is not set, the + * renderer assumes that each three contiguous positions represent a single triangle. + * + * @type {?BufferAttribute} + * @default null + */ + this.index = null; - const attribute = attributeArray[ i ]; + /** + * A (storage) buffer attribute which was generated with a compute shader and + * now defines indirect draw calls. + * + * Can only be used with {@link WebGPURenderer} and a WebGPU backend. + * + * @type {?BufferAttribute} + * @default null + */ + this.indirect = null; - array.push( attribute.toJSON( data.data ) ); + /** + * This dictionary has as id the name of the attribute to be set and as value + * the buffer attribute to set it to. Rather than accessing this property directly, + * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. + * + * @type {Object} + */ + this.attributes = {}; - } + /** + * This dictionary holds the morph targets of the geometry. + * + * Note: Once the geometry has been rendered, the morph attribute data cannot + * be changed. You will have to call `dispose()?, and create a new geometry instance. + * + * @type {Object} + */ + this.morphAttributes = {}; - if ( array.length > 0 ) { + /** + * Used to control the morph target behavior; when set to `true`, the morph + * target data is treated as relative offsets, rather than as absolute + * positions/normals. + * + * @type {boolean} + * @default false + */ + this.morphTargetsRelative = false; - morphAttributes[ key ] = array; + /** + * Split the geometry into groups, each of which will be rendered in a + * separate draw call. This allows an array of materials to be used with the geometry. + * + * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. + * + * Every vertex and index must belong to exactly one group — groups must not share vertices or + * indices, and must not leave vertices or indices unused. + * + * @type {Array} + */ + this.groups = []; - hasMorphAttributes = true; + /** + * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; - } + /** + * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; - } + /** + * Determines the part of the geometry to render. This should not be set directly, + * instead use `setDrawRange()`. + * + * @type {{start:number,count:number}} + */ + this.drawRange = { start: 0, count: Infinity }; - if ( hasMorphAttributes ) { + /** + * An object that can be used to store custom data about the geometry. + * It should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - data.data.morphAttributes = morphAttributes; - data.data.morphTargetsRelative = this.morphTargetsRelative; + } - } + /** + * Returns the index of this geometry. + * + * @return {?BufferAttribute} The index. Returns `null` if no index is defined. + */ + getIndex() { - const groups = this.groups; + return this.index; - if ( groups.length > 0 ) { + } - data.data.groups = JSON.parse( JSON.stringify( groups ) ); + /** + * Sets the given index to this geometry. + * + * @param {Array|BufferAttribute} index - The index to set. + * @return {BufferGeometry} A reference to this instance. + */ + setIndex( index ) { - } + if ( Array.isArray( index ) ) { - const boundingSphere = this.boundingSphere; + this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - if ( boundingSphere !== null ) { + } else { - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; + this.index = index; } - return data; + return this; } - clone() { + /** + * Sets the given indirect attribute to this geometry. + * + * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. + * @return {BufferGeometry} A reference to this instance. + */ + setIndirect( indirect ) { - return new this.constructor().copy( this ); + this.indirect = indirect; + + return this; } - copy( source ) { + /** + * Returns the indirect attribute of this geometry. + * + * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. + */ + getIndirect() { - // reset + return this.indirect; - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; + } - // used for storing cloned, shared data + /** + * Returns the buffer attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. + * Returns `undefined` if not attribute has been found. + */ + getAttribute( name ) { - const data = {}; + return this.attributes[ name ]; - // name + } - this.name = source.name; + /** + * Sets the given attribute for the given name. + * + * @param {string} name - The attribute name. + * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. + * @return {BufferGeometry} A reference to this instance. + */ + setAttribute( name, attribute ) { - // index + this.attributes[ name ] = attribute; - const index = source.index; + return this; - if ( index !== null ) { + } - this.setIndex( index.clone( data ) ); + /** + * Deletes the attribute for the given name. + * + * @param {string} name - The attribute name to delete. + * @return {BufferGeometry} A reference to this instance. + */ + deleteAttribute( name ) { - } + delete this.attributes[ name ]; - // attributes + return this; - const attributes = source.attributes; + } - for ( const name in attributes ) { + /** + * Returns `true` if this geometry has an attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether this geometry has an attribute for the given name or not. + */ + hasAttribute( name ) { - const attribute = attributes[ name ]; - this.setAttribute( name, attribute.clone( data ) ); + return this.attributes[ name ] !== undefined; - } + } - // morph attributes + /** + * Adds a group to this geometry. + * + * @param {number} start - The first element in this draw call. That is the first + * vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - Specifies how many vertices (or indices) are part of this group. + * @param {number} [materialIndex=0] - The material array index to use. + */ + addGroup( start, count, materialIndex = 0 ) { - const morphAttributes = source.morphAttributes; + this.groups.push( { - for ( const name in morphAttributes ) { + start: start, + count: count, + materialIndex: materialIndex - const array = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + } ); - for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { + } - array.push( morphAttribute[ i ].clone( data ) ); + /** + * Clears all groups. + */ + clearGroups() { - } + this.groups = []; - this.morphAttributes[ name ] = array; + } - } + /** + * Sets the draw range for this geometry. + * + * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. + * For indexed BufferGeometry, `count` is the number of indices to render. + */ + setDrawRange( start, count ) { - this.morphTargetsRelative = source.morphTargetsRelative; + this.drawRange.start = start; + this.drawRange.count = count; - // groups + } - const groups = source.groups; + /** + * Applies the given 4x4 transformation matrix to the geometry. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyMatrix4( matrix ) { - for ( let i = 0, l = groups.length; i < l; i ++ ) { + const position = this.attributes.position; - const group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); + if ( position !== undefined ) { + + position.applyMatrix4( matrix ); + + position.needsUpdate = true; } - // bounding box + const normal = this.attributes.normal; - const boundingBox = source.boundingBox; + if ( normal !== undefined ) { - if ( boundingBox !== null ) { + const normalMatrix = new Matrix3().getNormalMatrix( matrix ); - this.boundingBox = boundingBox.clone(); + normal.applyNormalMatrix( normalMatrix ); + + normal.needsUpdate = true; } - // bounding sphere + const tangent = this.attributes.tangent; - const boundingSphere = source.boundingSphere; + if ( tangent !== undefined ) { - if ( boundingSphere !== null ) { + tangent.transformDirection( matrix ); - this.boundingSphere = boundingSphere.clone(); + tangent.needsUpdate = true; } - // draw range - - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; + if ( this.boundingBox !== null ) { - // user data + this.computeBoundingBox(); - this.userData = source.userData; + } - return this; + if ( this.boundingSphere !== null ) { - } + this.computeBoundingSphere(); - dispose() { + } - this.dispatchEvent( { type: 'dispose' } ); + return this; } -} + /** + * Applies the rotation represented by the Quaternion to the geometry. + * + * @param {Quaternion} q - The Quaternion to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyQuaternion( q ) { -class BoxGeometry extends BufferGeometry { + _m1$2.makeRotationFromQuaternion( q ); - constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { + this.applyMatrix4( _m1$2 ); - super(); + return this; - this.type = 'BoxGeometry'; + } - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; + /** + * Rotates the geometry about the X axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateX( angle ) { - const scope = this; + // rotate geometry around world x-axis - // segments + _m1$2.makeRotationX( angle ); - widthSegments = Math.floor( widthSegments ); - heightSegments = Math.floor( heightSegments ); - depthSegments = Math.floor( depthSegments ); + this.applyMatrix4( _m1$2 ); - // buffers + return this; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + } - // helper variables + /** + * Rotates the geometry about the Y axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateY( angle ) { - let numberOfVertices = 0; - let groupStart = 0; + // rotate geometry around world y-axis - // build each side of the box geometry + _m1$2.makeRotationY( angle ); - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz + this.applyMatrix4( _m1$2 ); - // build geometry + return this; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + } - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { + /** + * Rotates the geometry about the Z axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateZ( angle ) { - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; + // rotate geometry around world z-axis - const widthHalf = width / 2; - const heightHalf = height / 2; - const depthHalf = depth / 2; + _m1$2.makeRotationZ( angle ); - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; + this.applyMatrix4( _m1$2 ); - let vertexCounter = 0; - let groupCount = 0; + return this; - const vector = new Vector3(); + } - // generate vertices, normals and uvs + /** + * Translates the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#position} for typical + * real-time mesh rotation. + * + * @param {number} x - The x offset. + * @param {number} y - The y offset. + * @param {number} z - The z offset. + * @return {BufferGeometry} A reference to this instance. + */ + translate( x, y, z ) { - for ( let iy = 0; iy < gridY1; iy ++ ) { + // translate geometry - const y = iy * segmentHeight - heightHalf; + _m1$2.makeTranslation( x, y, z ); - for ( let ix = 0; ix < gridX1; ix ++ ) { + this.applyMatrix4( _m1$2 ); - const x = ix * segmentWidth - widthHalf; + return this; - // set values to correct vector component + } - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; + /** + * Scales the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#scale} for typical + * real-time mesh rotation. + * + * @param {number} x - The x scale. + * @param {number} y - The y scale. + * @param {number} z - The z scale. + * @return {BufferGeometry} A reference to this instance. + */ + scale( x, y, z ) { - // now apply vector to vertex buffer + // scale geometry - vertices.push( vector.x, vector.y, vector.z ); + _m1$2.makeScale( x, y, z ); - // set values to correct vector component + this.applyMatrix4( _m1$2 ); - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; + return this; - // now apply vector to normal buffer + } - normals.push( vector.x, vector.y, vector.z ); + /** + * Rotates the geometry to face a point in 3D space. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#lookAt} for typical + * real-time mesh rotation. + * + * @param {Vector3} vector - The target point. + * @return {BufferGeometry} A reference to this instance. + */ + lookAt( vector ) { - // uvs + _obj.lookAt( vector ); - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); + _obj.updateMatrix(); - // counters + this.applyMatrix4( _obj.matrix ); - vertexCounter += 1; + return this; - } + } - } + /** + * Center the geometry based on its bounding box. + * + * @return {BufferGeometry} A reference to this instance. + */ + center() { - // indices + this.computeBoundingBox(); - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment + this.boundingBox.getCenter( _offset ).negate(); - for ( let iy = 0; iy < gridY; iy ++ ) { + this.translate( _offset.x, _offset.y, _offset.z ); - for ( let ix = 0; ix < gridX; ix ++ ) { + return this; - const a = numberOfVertices + ix + gridX1 * iy; - const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; + } - // faces + /** + * Defines a geometry by creating a `position` attribute based on the given array of points. The array + * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is + * set to `0`. + * + * If the method is used with an existing `position` attribute, the vertex data are overwritten with the + * data from the array. The length of the array must match the vertex count. + * + * @param {Array|Array} points - The points. + * @return {BufferGeometry} A reference to this instance. + */ + setFromPoints( points ) { - indices.push( a, b, d ); - indices.push( b, c, d ); + const positionAttribute = this.getAttribute( 'position' ); - // increase counter + if ( positionAttribute === undefined ) { - groupCount += 6; + const position = []; - } + for ( let i = 0, l = points.length; i < l; i ++ ) { + + const point = points[ i ]; + position.push( point.x, point.y, point.z || 0 ); } - // add a group to the geometry. this will ensure multi material support + this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - scope.addGroup( groupStart, groupCount, materialIndex ); + } else { - // calculate new start value for groups + const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size - groupStart += groupCount; + for ( let i = 0; i < l; i ++ ) { - // update total number of vertices + const point = points[ i ]; + positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); - numberOfVertices += vertexCounter; + } - } + if ( points.length > positionAttribute.count ) { - } + console.warn( 'THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); - copy( source ) { + } - super.copy( source ); + positionAttribute.needsUpdate = true; - this.parameters = Object.assign( {}, source.parameters ); + } return this; } - static fromJSON( data ) { + /** + * Computes the bounding box of the geometry, and updates the `boundingBox` member. + * The bounding box is not computed by the engine; it must be computed by your app. + * You may need to recompute the bounding box if the geometry vertices are modified. + */ + computeBoundingBox() { - return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); + if ( this.boundingBox === null ) { - } + this.boundingBox = new Box3(); -} + } -class PlaneGeometry extends BufferGeometry { + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; - constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { + if ( position && position.isGLBufferAttribute ) { - super(); + console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); - this.type = 'PlaneGeometry'; + this.boundingBox.set( + new Vector3( - Infinity, - Infinity, - Infinity ), + new Vector3( + Infinity, + Infinity, + Infinity ) + ); - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; + return; - const width_half = width / 2; - const height_half = height / 2; + } - const gridX = Math.floor( widthSegments ); - const gridY = Math.floor( heightSegments ); + if ( position !== undefined ) { - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; + this.boundingBox.setFromBufferAttribute( position ); - const segment_width = width / gridX; - const segment_height = height / gridY; + // process morph attributes if present - // + if ( morphAttributesPosition ) { - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - for ( let iy = 0; iy < gridY1; iy ++ ) { + const morphAttribute = morphAttributesPosition[ i ]; + _box$1.setFromBufferAttribute( morphAttribute ); - const y = iy * segment_height - height_half; + if ( this.morphTargetsRelative ) { - for ( let ix = 0; ix < gridX1; ix ++ ) { + _vector$4.addVectors( this.boundingBox.min, _box$1.min ); + this.boundingBox.expandByPoint( _vector$4 ); - const x = ix * segment_width - width_half; + _vector$4.addVectors( this.boundingBox.max, _box$1.max ); + this.boundingBox.expandByPoint( _vector$4 ); - vertices.push( x, - y, 0 ); + } else { - normals.push( 0, 0, 1 ); + this.boundingBox.expandByPoint( _box$1.min ); + this.boundingBox.expandByPoint( _box$1.max ); - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); + } + + } } + } else { + + this.boundingBox.makeEmpty(); + } - for ( let iy = 0; iy < gridY; iy ++ ) { + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - for ( let ix = 0; ix < gridX; ix ++ ) { + console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - const a = ix + gridX1 * iy; - const b = ix + gridX1 * ( iy + 1 ); - const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = ( ix + 1 ) + gridX1 * iy; + } - indices.push( a, b, d ); - indices.push( b, c, d ); + } - } + /** + * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if the geometry vertices are modified. + */ + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); } - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; - } + if ( position && position.isGLBufferAttribute ) { - copy( source ) { + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); - super.copy( source ); + this.boundingSphere.set( new Vector3(), Infinity ); - this.parameters = Object.assign( {}, source.parameters ); + return; - return this; + } - } + if ( position ) { - static fromJSON( data ) { + // first, find the center of the bounding sphere - return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); + const center = this.boundingSphere.center; - } + _box$1.setFromBufferAttribute( position ); -} + // process morph attributes if present -let _materialId = 0; + if ( morphAttributesPosition ) { -class Material extends EventDispatcher { + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - constructor() { + const morphAttribute = morphAttributesPosition[ i ]; + _boxMorphTargets.setFromBufferAttribute( morphAttribute ); - super(); + if ( this.morphTargetsRelative ) { - this.isMaterial = true; + _vector$4.addVectors( _box$1.min, _boxMorphTargets.min ); + _box$1.expandByPoint( _vector$4 ); - Object.defineProperty( this, 'id', { value: _materialId ++ } ); + _vector$4.addVectors( _box$1.max, _boxMorphTargets.max ); + _box$1.expandByPoint( _vector$4 ); - this.uuid = generateUUID(); + } else { - this.name = ''; - this.type = 'Material'; + _box$1.expandByPoint( _boxMorphTargets.min ); + _box$1.expandByPoint( _boxMorphTargets.max ); - this.blending = NormalBlending; - this.side = FrontSide; - this.vertexColors = false; + } - this.opacity = 1; - this.transparent = false; - this.alphaHash = false; + } - this.blendSrc = SrcAlphaFactor; - this.blendDst = OneMinusSrcAlphaFactor; - this.blendEquation = AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; - this.blendColor = new Color( 0, 0, 0 ); - this.blendAlpha = 0; + } - this.depthFunc = LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; + _box$1.getCenter( center ); - this.stencilWriteMask = 0xff; - this.stencilFunc = AlwaysStencilFunc; - this.stencilRef = 0; - this.stencilFuncMask = 0xff; - this.stencilFail = KeepStencilOp; - this.stencilZFail = KeepStencilOp; - this.stencilZPass = KeepStencilOp; - this.stencilWrite = false; + // second, try to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case - this.clippingPlanes = null; - this.clipIntersection = false; - this.clipShadows = false; + let maxRadiusSq = 0; - this.shadowSide = null; + for ( let i = 0, il = position.count; i < il; i ++ ) { - this.colorWrite = true; + _vector$4.fromBufferAttribute( position, i ); - this.precision = null; // override the renderer's default precision for this material + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; + } - this.dithering = false; + // process morph attributes if present - this.alphaToCoverage = false; - this.premultipliedAlpha = false; - this.forceSinglePass = false; + if ( morphAttributesPosition ) { - this.visible = true; + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - this.toneMapped = true; + const morphAttribute = morphAttributesPosition[ i ]; + const morphTargetsRelative = this.morphTargetsRelative; - this.userData = {}; + for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { - this.version = 0; + _vector$4.fromBufferAttribute( morphAttribute, j ); - this._alphaTest = 0; + if ( morphTargetsRelative ) { - } + _offset.fromBufferAttribute( position, j ); + _vector$4.add( _offset ); - get alphaTest() { + } - return this._alphaTest; + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); - } + } - set alphaTest( value ) { + } - if ( this._alphaTest > 0 !== value > 0 ) { + } - this.version ++; + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - } + if ( isNaN( this.boundingSphere.radius ) ) { - this._alphaTest = value; + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - } + } - onBuild( /* shaderobject, renderer */ ) {} + } - onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} + } - onBeforeCompile( /* shaderobject, renderer */ ) {} + /** + * Calculates and adds a tangent attribute to this geometry. + * + * The computation is only supported for indexed geometries and if position, normal, and uv attributes + * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by + * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. + */ + computeTangents() { - customProgramCacheKey() { + const index = this.index; + const attributes = this.attributes; - return this.onBeforeCompile.toString(); + // based on http://www.terathon.com/code/tangent.html + // (per vertex tangents) - } + if ( index === null || + attributes.position === undefined || + attributes.normal === undefined || + attributes.uv === undefined ) { - setValues( values ) { + console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); + return; - if ( values === undefined ) return; + } - for ( const key in values ) { + const positionAttribute = attributes.position; + const normalAttribute = attributes.normal; + const uvAttribute = attributes.uv; - const newValue = values[ key ]; + if ( this.hasAttribute( 'tangent' ) === false ) { - if ( newValue === undefined ) { + this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); - console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); - continue; + } - } + const tangentAttribute = this.getAttribute( 'tangent' ); - const currentValue = this[ key ]; + const tan1 = [], tan2 = []; - if ( currentValue === undefined ) { + for ( let i = 0; i < positionAttribute.count; i ++ ) { - console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); - continue; + tan1[ i ] = new Vector3(); + tan2[ i ] = new Vector3(); - } + } - if ( currentValue && currentValue.isColor ) { + const vA = new Vector3(), + vB = new Vector3(), + vC = new Vector3(), - currentValue.set( newValue ); + uvA = new Vector2(), + uvB = new Vector2(), + uvC = new Vector2(), - } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { + sdir = new Vector3(), + tdir = new Vector3(); - currentValue.copy( newValue ); + function handleTriangle( a, b, c ) { - } else { + vA.fromBufferAttribute( positionAttribute, a ); + vB.fromBufferAttribute( positionAttribute, b ); + vC.fromBufferAttribute( positionAttribute, c ); - this[ key ] = newValue; + uvA.fromBufferAttribute( uvAttribute, a ); + uvB.fromBufferAttribute( uvAttribute, b ); + uvC.fromBufferAttribute( uvAttribute, c ); - } + vB.sub( vA ); + vC.sub( vA ); - } + uvB.sub( uvA ); + uvC.sub( uvA ); - } + const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); - toJSON( meta ) { + // silently ignore degenerate uv triangles having coincident or colinear vertices - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + if ( ! isFinite( r ) ) return; - if ( isRootObject ) { + sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); + tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); - meta = { - textures: {}, - images: {} - }; + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); } - const data = { - metadata: { - version: 4.6, - type: 'Material', - generator: 'Material.toJSON' - } - }; + let groups = this.groups; - // standard Material serialization - data.uuid = this.uuid; - data.type = this.type; + if ( groups.length === 0 ) { - if ( this.name !== '' ) data.name = this.name; + groups = [ { + start: 0, + count: index.count + } ]; - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); + } - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; + for ( let i = 0, il = groups.length; i < il; ++ i ) { - if ( this.sheen !== undefined ) data.sheen = this.sheen; - if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); - if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity !== undefined && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; + const group = groups[ i ]; - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; - if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; - if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; + const start = group.start; + const count = group.count; - if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { + for ( let j = start, jl = start + count; j < jl; j += 3 ) { - data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; + handleTriangle( + index.getX( j + 0 ), + index.getX( j + 1 ), + index.getX( j + 2 ) + ); + + } } - if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { + const tmp = new Vector3(), tmp2 = new Vector3(); + const n = new Vector3(), n2 = new Vector3(); - data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; + function handleVertex( v ) { - } + n.fromBufferAttribute( normalAttribute, v ); + n2.copy( n ); - if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { + const t = tan1[ v ]; - data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; - data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); + // Gram-Schmidt orthogonalize - } + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); - if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; - if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; - if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; + // Calculate handedness - if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { + tmp2.crossVectors( n2, t ); + const test = tmp2.dot( tan2[ v ] ); + const w = ( test < 0.0 ) ? -1 : 1.0; - data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; + tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); } - if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { + for ( let i = 0, il = groups.length; i < il; ++ i ) { - data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; + const group = groups[ i ]; - } + const start = group.start; + const count = group.count; - if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; - if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; + for ( let j = start, jl = start + count; j < jl; j += 3 ) { - if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { + handleVertex( index.getX( j + 0 ) ); + handleVertex( index.getX( j + 1 ) ); + handleVertex( index.getX( j + 2 ) ); - data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; + } } - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + } - if ( this.lightMap && this.lightMap.isTexture ) { + /** + * Computes vertex normals for the given vertex data. For indexed geometries, the method sets + * each vertex normal to be the average of the face normals of the faces that share that vertex. + * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal + * to be the same as the face normal. + */ + computeVertexNormals() { - data.lightMap = this.lightMap.toJSON( meta ).uuid; - data.lightMapIntensity = this.lightMapIntensity; + const index = this.index; + const positionAttribute = this.getAttribute( 'position' ); - } + if ( positionAttribute !== undefined ) { - if ( this.aoMap && this.aoMap.isTexture ) { + let normalAttribute = this.getAttribute( 'normal' ); - data.aoMap = this.aoMap.toJSON( meta ).uuid; - data.aoMapIntensity = this.aoMapIntensity; + if ( normalAttribute === undefined ) { - } + normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); + this.setAttribute( 'normal', normalAttribute ); - if ( this.bumpMap && this.bumpMap.isTexture ) { + } else { - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; + // reset existing normals to zero - } + for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { - if ( this.normalMap && this.normalMap.isTexture ) { + normalAttribute.setXYZ( i, 0, 0, 0 ); - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalMapType = this.normalMapType; - data.normalScale = this.normalScale.toArray(); + } - } + } - if ( this.displacementMap && this.displacementMap.isTexture ) { + const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); + const cb = new Vector3(), ab = new Vector3(); - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; + // indexed elements - } + if ( index ) { - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; + for ( let i = 0, il = index.count; i < il; i += 3 ) { - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; - if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; + const vA = index.getX( i + 0 ); + const vB = index.getX( i + 1 ); + const vC = index.getX( i + 2 ); - if ( this.envMap && this.envMap.isTexture ) { + pA.fromBufferAttribute( positionAttribute, vA ); + pB.fromBufferAttribute( positionAttribute, vB ); + pC.fromBufferAttribute( positionAttribute, vC ); - data.envMap = this.envMap.toJSON( meta ).uuid; + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - if ( this.combine !== undefined ) data.combine = this.combine; + nA.fromBufferAttribute( normalAttribute, vA ); + nB.fromBufferAttribute( normalAttribute, vB ); + nC.fromBufferAttribute( normalAttribute, vC ); - } + nA.add( cb ); + nB.add( cb ); + nC.add( cb ); - if ( this.envMapRotation !== undefined ) data.envMapRotation = this.envMapRotation.toArray(); - if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; - if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; - if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; + normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); + normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); + normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); - if ( this.gradientMap && this.gradientMap.isTexture ) { + } - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; + } else { - } + // non-indexed elements (unconnected triangle soup) - if ( this.transmission !== undefined ) data.transmission = this.transmission; - if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; - if ( this.thickness !== undefined ) data.thickness = this.thickness; - if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; - if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; - if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); + for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { - if ( this.size !== undefined ) data.size = this.size; - if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; + pA.fromBufferAttribute( positionAttribute, i + 0 ); + pB.fromBufferAttribute( positionAttribute, i + 1 ); + pC.fromBufferAttribute( positionAttribute, i + 2 ); - if ( this.blending !== NormalBlending ) data.blending = this.blending; - if ( this.side !== FrontSide ) data.side = this.side; - if ( this.vertexColors === true ) data.vertexColors = true; + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - if ( this.opacity < 1 ) data.opacity = this.opacity; - if ( this.transparent === true ) data.transparent = true; + normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); - if ( this.blendSrc !== SrcAlphaFactor ) data.blendSrc = this.blendSrc; - if ( this.blendDst !== OneMinusSrcAlphaFactor ) data.blendDst = this.blendDst; - if ( this.blendEquation !== AddEquation ) data.blendEquation = this.blendEquation; - if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha; - if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha; - if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha; - if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex(); - if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha; + } - if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc; - if ( this.depthTest === false ) data.depthTest = this.depthTest; - if ( this.depthWrite === false ) data.depthWrite = this.depthWrite; - if ( this.colorWrite === false ) data.colorWrite = this.colorWrite; - - if ( this.stencilWriteMask !== 0xff ) data.stencilWriteMask = this.stencilWriteMask; - if ( this.stencilFunc !== AlwaysStencilFunc ) data.stencilFunc = this.stencilFunc; - if ( this.stencilRef !== 0 ) data.stencilRef = this.stencilRef; - if ( this.stencilFuncMask !== 0xff ) data.stencilFuncMask = this.stencilFuncMask; - if ( this.stencilFail !== KeepStencilOp ) data.stencilFail = this.stencilFail; - if ( this.stencilZFail !== KeepStencilOp ) data.stencilZFail = this.stencilZFail; - if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; - if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; - - // rotation (SpriteMaterial) - if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; + } - if ( this.polygonOffset === true ) data.polygonOffset = true; - if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; - if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; + this.normalizeNormals(); - if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; + normalAttribute.needsUpdate = true; - if ( this.dithering === true ) data.dithering = true; + } - if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; - if ( this.alphaHash === true ) data.alphaHash = true; - if ( this.alphaToCoverage === true ) data.alphaToCoverage = true; - if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = true; - if ( this.forceSinglePass === true ) data.forceSinglePass = true; + } - if ( this.wireframe === true ) data.wireframe = true; - if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; + /** + * Ensures every normal vector in a geometry will have a magnitude of `1`. This will + * correct lighting on the geometry surfaces. + */ + normalizeNormals() { - if ( this.flatShading === true ) data.flatShading = true; + const normals = this.attributes.normal; - if ( this.visible === false ) data.visible = false; + for ( let i = 0, il = normals.count; i < il; i ++ ) { - if ( this.toneMapped === false ) data.toneMapped = false; + _vector$4.fromBufferAttribute( normals, i ); - if ( this.fog === false ) data.fog = false; + _vector$4.normalize(); - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; + normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z ); - // TODO: Copied from Object3D.toJSON + } - function extractFromCache( cache ) { + } - const values = []; + /** + * Return a new non-index version of this indexed geometry. If the geometry + * is already non-indexed, the method is a NOOP. + * + * @return {BufferGeometry} The non-indexed version of this indexed geometry. + */ + toNonIndexed() { - for ( const key in cache ) { + function convertBufferAttribute( attribute, indices ) { - const data = cache[ key ]; - delete data.metadata; - values.push( data ); + const array = attribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; - } + const array2 = new array.constructor( indices.length * itemSize ); - return values; + let index = 0, index2 = 0; - } + for ( let i = 0, l = indices.length; i < l; i ++ ) { - if ( isRootObject ) { + if ( attribute.isInterleavedBufferAttribute ) { - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); + index = indices[ i ] * attribute.data.stride + attribute.offset; - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; + } else { - } + index = indices[ i ] * itemSize; - return data; + } - } + for ( let j = 0; j < itemSize; j ++ ) { - clone() { + array2[ index2 ++ ] = array[ index ++ ]; - return new this.constructor().copy( this ); + } - } + } - copy( source ) { + return new BufferAttribute( array2, itemSize, normalized ); - this.name = source.name; + } - this.blending = source.blending; - this.side = source.side; - this.vertexColors = source.vertexColors; + // - this.opacity = source.opacity; - this.transparent = source.transparent; + if ( this.index === null ) { - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - this.blendColor.copy( source.blendColor ); - this.blendAlpha = source.blendAlpha; + console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); + return this; - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; + } - this.stencilWriteMask = source.stencilWriteMask; - this.stencilFunc = source.stencilFunc; - this.stencilRef = source.stencilRef; - this.stencilFuncMask = source.stencilFuncMask; - this.stencilFail = source.stencilFail; - this.stencilZFail = source.stencilZFail; - this.stencilZPass = source.stencilZPass; - this.stencilWrite = source.stencilWrite; + const geometry2 = new BufferGeometry(); - const srcPlanes = source.clippingPlanes; - let dstPlanes = null; + const indices = this.index.array; + const attributes = this.attributes; - if ( srcPlanes !== null ) { + // attributes - const n = srcPlanes.length; - dstPlanes = new Array( n ); + for ( const name in attributes ) { - for ( let i = 0; i !== n; ++ i ) { + const attribute = attributes[ name ]; - dstPlanes[ i ] = srcPlanes[ i ].clone(); + const newAttribute = convertBufferAttribute( attribute, indices ); - } + geometry2.setAttribute( name, newAttribute ); } - this.clippingPlanes = dstPlanes; - this.clipIntersection = source.clipIntersection; - this.clipShadows = source.clipShadows; + // morph attributes - this.shadowSide = source.shadowSide; + const morphAttributes = this.morphAttributes; - this.colorWrite = source.colorWrite; + for ( const name in morphAttributes ) { - this.precision = source.precision; + const morphArray = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; + for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - this.dithering = source.dithering; + const attribute = morphAttribute[ i ]; - this.alphaTest = source.alphaTest; - this.alphaHash = source.alphaHash; - this.alphaToCoverage = source.alphaToCoverage; - this.premultipliedAlpha = source.premultipliedAlpha; - this.forceSinglePass = source.forceSinglePass; + const newAttribute = convertBufferAttribute( attribute, indices ); - this.visible = source.visible; + morphArray.push( newAttribute ); - this.toneMapped = source.toneMapped; + } - this.userData = JSON.parse( JSON.stringify( source.userData ) ); + geometry2.morphAttributes[ name ] = morphArray; - return this; + } - } + geometry2.morphTargetsRelative = this.morphTargetsRelative; - dispose() { + // groups - this.dispatchEvent( { type: 'dispose' } ); + const groups = this.groups; - } + for ( let i = 0, l = groups.length; i < l; i ++ ) { - set needsUpdate( value ) { + const group = groups[ i ]; + geometry2.addGroup( group.start, group.count, group.materialIndex ); - if ( value === true ) this.version ++; + } + + return geometry2; } -} + /** + * Serializes the geometry into JSON. + * + * @return {Object} A JSON object representing the serialized geometry. + */ + toJSON() { -/** - * Uniform Utilities - */ + const data = { + metadata: { + version: 4.7, + type: 'BufferGeometry', + generator: 'BufferGeometry.toJSON' + } + }; -function cloneUniforms( src ) { + // standard BufferGeometry serialization - const dst = {}; + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - for ( const u in src ) { + if ( this.parameters !== undefined ) { - dst[ u ] = {}; + const parameters = this.parameters; - for ( const p in src[ u ] ) { + for ( const key in parameters ) { - const property = src[ u ][ p ]; + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - if ( property && ( property.isColor || - property.isMatrix3 || property.isMatrix4 || - property.isVector2 || property.isVector3 || property.isVector4 || - property.isTexture || property.isQuaternion ) ) { + } - if ( property.isRenderTargetTexture ) { + return data; - console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); - dst[ u ][ p ] = null; + } - } else { + // for simplicity the code assumes attributes are not shared across geometries, see #15811 - dst[ u ][ p ] = property.clone(); + data.data = { attributes: {} }; - } + const index = this.index; - } else if ( Array.isArray( property ) ) { + if ( index !== null ) { - dst[ u ][ p ] = property.slice(); + data.data.index = { + type: index.array.constructor.name, + array: Array.prototype.slice.call( index.array ) + }; - } else { + } - dst[ u ][ p ] = property; + const attributes = this.attributes; - } + for ( const key in attributes ) { - } + const attribute = attributes[ key ]; - } + data.data.attributes[ key ] = attribute.toJSON( data.data ); - return dst; + } -} + const morphAttributes = {}; + let hasMorphAttributes = false; -function mergeUniforms( uniforms ) { + for ( const key in this.morphAttributes ) { - const merged = {}; + const attributeArray = this.morphAttributes[ key ]; - for ( let u = 0; u < uniforms.length; u ++ ) { + const array = []; - const tmp = cloneUniforms( uniforms[ u ] ); + for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - for ( const p in tmp ) { + const attribute = attributeArray[ i ]; - merged[ p ] = tmp[ p ]; + array.push( attribute.toJSON( data.data ) ); - } + } - } + if ( array.length > 0 ) { - return merged; + morphAttributes[ key ] = array; -} + hasMorphAttributes = true; -function cloneUniformsGroups( src ) { + } - const dst = []; + } - for ( let u = 0; u < src.length; u ++ ) { + if ( hasMorphAttributes ) { - dst.push( src[ u ].clone() ); + data.data.morphAttributes = morphAttributes; + data.data.morphTargetsRelative = this.morphTargetsRelative; - } + } - return dst; + const groups = this.groups; -} + if ( groups.length > 0 ) { -function getUnlitUniformColorSpace( renderer ) { + data.data.groups = JSON.parse( JSON.stringify( groups ) ); - if ( renderer.getRenderTarget() === null ) { + } - // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 - return renderer.outputColorSpace; + const boundingSphere = this.boundingSphere; - } + if ( boundingSphere !== null ) { - return ColorManagement.workingColorSpace; + data.data.boundingSphere = boundingSphere.toJSON(); -} + } -// Legacy + return data; -const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; + } -var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; + /** + * Returns a new geometry with copied values from this instance. + * + * @return {BufferGeometry} A clone of this instance. + */ + clone() { -var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; + return new this.constructor().copy( this ); -class ShaderMaterial extends Material { + } - constructor( parameters ) { + /** + * Copies the values of the given geometry to this instance. + * + * @param {BufferGeometry} source - The geometry to copy. + * @return {BufferGeometry} A reference to this instance. + */ + copy( source ) { - super(); + // reset - this.isShaderMaterial = true; + this.index = null; + this.attributes = {}; + this.morphAttributes = {}; + this.groups = []; + this.boundingBox = null; + this.boundingSphere = null; - this.type = 'ShaderMaterial'; + // used for storing cloned, shared data - this.defines = {}; - this.uniforms = {}; - this.uniformsGroups = []; + const data = {}; - this.vertexShader = default_vertex; - this.fragmentShader = default_fragment; + // name - this.linewidth = 1; + this.name = source.name; - this.wireframe = false; - this.wireframeLinewidth = 1; + // index - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes + const index = source.index; - this.forceSinglePass = true; + if ( index !== null ) { - this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false, // set to use shader texture LOD - clipCullDistance: false, // set to use vertex shader clipping - multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID - }; + this.setIndex( index.clone() ); - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv1': [ 0, 0 ] - }; + } - this.index0AttributeName = undefined; - this.uniformsNeedUpdate = false; + // attributes - this.glslVersion = null; + const attributes = source.attributes; - if ( parameters !== undefined ) { + for ( const name in attributes ) { - this.setValues( parameters ); + const attribute = attributes[ name ]; + this.setAttribute( name, attribute.clone( data ) ); } - } - - copy( source ) { + // morph attributes - super.copy( source ); + const morphAttributes = source.morphAttributes; - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; + for ( const name in morphAttributes ) { - this.uniforms = cloneUniforms( source.uniforms ); - this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); + const array = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - this.defines = Object.assign( {}, source.defines ); + for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; + array.push( morphAttribute[ i ].clone( data ) ); - this.fog = source.fog; - this.lights = source.lights; - this.clipping = source.clipping; + } - this.extensions = Object.assign( {}, source.extensions ); + this.morphAttributes[ name ] = array; - this.glslVersion = source.glslVersion; + } - return this; + this.morphTargetsRelative = source.morphTargetsRelative; - } + // groups - toJSON( meta ) { + const groups = source.groups; - const data = super.toJSON( meta ); + for ( let i = 0, l = groups.length; i < l; i ++ ) { - data.glslVersion = this.glslVersion; - data.uniforms = {}; + const group = groups[ i ]; + this.addGroup( group.start, group.count, group.materialIndex ); - for ( const name in this.uniforms ) { + } - const uniform = this.uniforms[ name ]; - const value = uniform.value; + // bounding box - if ( value && value.isTexture ) { + const boundingBox = source.boundingBox; - data.uniforms[ name ] = { - type: 't', - value: value.toJSON( meta ).uuid - }; + if ( boundingBox !== null ) { - } else if ( value && value.isColor ) { + this.boundingBox = boundingBox.clone(); - data.uniforms[ name ] = { - type: 'c', - value: value.getHex() - }; + } - } else if ( value && value.isVector2 ) { + // bounding sphere - data.uniforms[ name ] = { - type: 'v2', - value: value.toArray() - }; + const boundingSphere = source.boundingSphere; - } else if ( value && value.isVector3 ) { + if ( boundingSphere !== null ) { - data.uniforms[ name ] = { - type: 'v3', - value: value.toArray() - }; + this.boundingSphere = boundingSphere.clone(); - } else if ( value && value.isVector4 ) { + } - data.uniforms[ name ] = { - type: 'v4', - value: value.toArray() - }; + // draw range - } else if ( value && value.isMatrix3 ) { + this.drawRange.start = source.drawRange.start; + this.drawRange.count = source.drawRange.count; - data.uniforms[ name ] = { - type: 'm3', - value: value.toArray() - }; + // user data - } else if ( value && value.isMatrix4 ) { + this.userData = source.userData; - data.uniforms[ name ] = { - type: 'm4', - value: value.toArray() - }; + return this; - } else { + } - data.uniforms[ name ] = { - value: value - }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires BufferGeometry#dispose + */ + dispose() { - // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far + this.dispatchEvent( { type: 'dispose' } ); - } + } - } +} - if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; +/** + * A geometry class for a rectangular cuboid with a given width, height, and depth. + * On creation, the cuboid is centred on the origin, with each edge parallel to one + * of the axes. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const cube = new THREE.Mesh( geometry, material ); + * scene.add( cube ); + * ``` + * + * @augments BufferGeometry + */ +class BoxGeometry extends BufferGeometry { - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; + /** + * Constructs a new box geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [widthSegments=1] - Number of segmented rectangular faces along the width of the sides. + * @param {number} [heightSegments=1] - Number of segmented rectangular faces along the height of the sides. + * @param {number} [depthSegments=1] - Number of segmented rectangular faces along the depth of the sides. + */ + constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { - data.lights = this.lights; - data.clipping = this.clipping; + super(); - const extensions = {}; + this.type = 'BoxGeometry'; - for ( const key in this.extensions ) { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; - if ( this.extensions[ key ] === true ) extensions[ key ] = true; + const scope = this; - } + // segments - if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; + widthSegments = Math.floor( widthSegments ); + heightSegments = Math.floor( heightSegments ); + depthSegments = Math.floor( depthSegments ); - return data; + // buffers - } + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; -} + // helper variables -const _vector$3 = /*@__PURE__*/ new Vector3(); -const _segCenter = /*@__PURE__*/ new Vector3(); -const _segDir = /*@__PURE__*/ new Vector3(); -const _diff = /*@__PURE__*/ new Vector3(); + let numberOfVertices = 0; + let groupStart = 0; -const _edge1 = /*@__PURE__*/ new Vector3(); -const _edge2 = /*@__PURE__*/ new Vector3(); -const _normal$1 = /*@__PURE__*/ new Vector3(); + // build each side of the box geometry -class Ray { + buildPlane( 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0 ); // px + buildPlane( 'z', 'y', 'x', 1, -1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx + buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py + buildPlane( 'x', 'z', 'y', 1, -1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny + buildPlane( 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4 ); // pz + buildPlane( 'x', 'y', 'z', -1, -1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { + // build geometry - this.origin = origin; - this.direction = direction; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - } + function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { - set( origin, direction ) { + const segmentWidth = width / gridX; + const segmentHeight = height / gridY; - this.origin.copy( origin ); - this.direction.copy( direction ); + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; - return this; + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; - } + let vertexCounter = 0; + let groupCount = 0; - copy( ray ) { + const vector = new Vector3(); - this.origin.copy( ray.origin ); - this.direction.copy( ray.direction ); + // generate vertices, normals and uvs - return this; + for ( let iy = 0; iy < gridY1; iy ++ ) { - } + const y = iy * segmentHeight - heightHalf; - at( t, target ) { + for ( let ix = 0; ix < gridX1; ix ++ ) { - return target.copy( this.origin ).addScaledVector( this.direction, t ); + const x = ix * segmentWidth - widthHalf; - } + // set values to correct vector component - lookAt( v ) { + vector[ u ] = x * udir; + vector[ v ] = y * vdir; + vector[ w ] = depthHalf; - this.direction.copy( v ).sub( this.origin ).normalize(); + // now apply vector to vertex buffer - return this; + vertices.push( vector.x, vector.y, vector.z ); - } + // set values to correct vector component - recast( t ) { + vector[ u ] = 0; + vector[ v ] = 0; + vector[ w ] = depth > 0 ? 1 : -1; - this.origin.copy( this.at( t, _vector$3 ) ); + // now apply vector to normal buffer - return this; + normals.push( vector.x, vector.y, vector.z ); - } + // uvs - closestPointToPoint( point, target ) { + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); - target.subVectors( point, this.origin ); + // counters - const directionDistance = target.dot( this.direction ); + vertexCounter += 1; - if ( directionDistance < 0 ) { + } - return target.copy( this.origin ); + } - } + // indices - return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); + // 1. you need three indices to draw a single face + // 2. a single segment consists of two faces + // 3. so we need to generate six (2*3) indices per segment - } + for ( let iy = 0; iy < gridY; iy ++ ) { - distanceToPoint( point ) { + for ( let ix = 0; ix < gridX; ix ++ ) { - return Math.sqrt( this.distanceSqToPoint( point ) ); + const a = numberOfVertices + ix + gridX1 * iy; + const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); + const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - } + // faces - distanceSqToPoint( point ) { + indices.push( a, b, d ); + indices.push( b, c, d ); - const directionDistance = _vector$3.subVectors( point, this.origin ).dot( this.direction ); + // increase counter - // point behind the ray + groupCount += 6; - if ( directionDistance < 0 ) { + } - return this.origin.distanceToSquared( point ); + } - } + // add a group to the geometry. this will ensure multi material support - _vector$3.copy( this.origin ).addScaledVector( this.direction, directionDistance ); + scope.addGroup( groupStart, groupCount, materialIndex ); - return _vector$3.distanceToSquared( point ); + // calculate new start value for groups - } + groupStart += groupCount; - distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { + // update total number of vertices - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h - // It returns the min distance between the ray and the segment - // defined by v0 and v1 - // It can also set two optional targets : - // - The closest point on the ray - // - The closest point on the segment + numberOfVertices += vertexCounter; - _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); - _segDir.copy( v1 ).sub( v0 ).normalize(); - _diff.copy( this.origin ).sub( _segCenter ); + } - const segExtent = v0.distanceTo( v1 ) * 0.5; - const a01 = - this.direction.dot( _segDir ); - const b0 = _diff.dot( this.direction ); - const b1 = - _diff.dot( _segDir ); - const c = _diff.lengthSq(); - const det = Math.abs( 1 - a01 * a01 ); - let s0, s1, sqrDist, extDet; + } - if ( det > 0 ) { + copy( source ) { - // The ray and segment are not parallel. + super.copy( source ); - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; + this.parameters = Object.assign( {}, source.parameters ); - if ( s0 >= 0 ) { + return this; - if ( s1 >= - extDet ) { + } - if ( s1 <= extDet ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {BoxGeometry} A new instance. + */ + static fromJSON( data ) { - // region 0 - // Minimum at interior points of ray and segment. + return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); - const invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + } - } else { +} - // region 1 +/** + * A geometry class for representing a plane. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const plane = new THREE.Mesh( geometry, material ); + * scene.add( plane ); + * ``` + * + * @augments BufferGeometry + */ +class PlaneGeometry extends BufferGeometry { - s1 = segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + /** + * Constructs a new plane geometry. + * + * @param {number} [width=1] - The width along the X axis. + * @param {number} [height=1] - The height along the Y axis + * @param {number} [widthSegments=1] - The number of segments along the X axis. + * @param {number} [heightSegments=1] - The number of segments along the Y axis. + */ + constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { - } + super(); - } else { + this.type = 'PlaneGeometry'; - // region 5 + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; - s1 = - segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + const width_half = width / 2; + const height_half = height / 2; - } + const gridX = Math.floor( widthSegments ); + const gridY = Math.floor( heightSegments ); - } else { + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; - if ( s1 <= - extDet ) { + const segment_width = width / gridX; + const segment_height = height / gridY; - // region 4 + // - s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - } else if ( s1 <= extDet ) { + for ( let iy = 0; iy < gridY1; iy ++ ) { - // region 3 + const y = iy * segment_height - height_half; - s0 = 0; - s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; + for ( let ix = 0; ix < gridX1; ix ++ ) { - } else { + const x = ix * segment_width - width_half; - // region 2 + vertices.push( x, - y, 0 ); - s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + normals.push( 0, 0, 1 ); - } + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); } - } else { - - // Ray and segment are parallel. - - s1 = ( a01 > 0 ) ? - segExtent : segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - } - if ( optionalPointOnRay ) { + for ( let iy = 0; iy < gridY; iy ++ ) { - optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); + for ( let ix = 0; ix < gridX; ix ++ ) { - } + const a = ix + gridX1 * iy; + const b = ix + gridX1 * ( iy + 1 ); + const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = ( ix + 1 ) + gridX1 * iy; - if ( optionalPointOnSegment ) { + indices.push( a, b, d ); + indices.push( b, c, d ); - optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); + } } - return sqrDist; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - intersectSphere( sphere, target ) { - - _vector$3.subVectors( sphere.center, this.origin ); - const tca = _vector$3.dot( this.direction ); - const d2 = _vector$3.dot( _vector$3 ) - tca * tca; - const radius2 = sphere.radius * sphere.radius; - - if ( d2 > radius2 ) return null; - - const thc = Math.sqrt( radius2 - d2 ); - - // t0 = first intersect point - entrance on front of sphere - const t0 = tca - thc; - - // t1 = second intersect point - exit point on back of sphere - const t1 = tca + thc; + copy( source ) { - // test to see if t1 is behind the ray - if so, return null - if ( t1 < 0 ) return null; + super.copy( source ); - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if ( t0 < 0 ) return this.at( t1, target ); + this.parameters = Object.assign( {}, source.parameters ); - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at( t0, target ); + return this; } - intersectsSphere( sphere ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PlaneGeometry} A new instance. + */ + static fromJSON( data ) { - return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); + return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); } - distanceToPlane( plane ) { - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator === 0 ) { +} - // line is coplanar, return origin - if ( plane.distanceToPoint( this.origin ) === 0 ) { +let _materialId = 0; - return 0; +/** + * Abstract base class for materials. + * + * Materials define the appearance of renderable 3D objects. + * + * @abstract + * @augments EventDispatcher + */ +class Material extends EventDispatcher { - } + /** + * Constructs a new material. + */ + constructor() { - // Null is preferable to undefined since undefined means.... it is undefined + super(); - return null; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMaterial = true; - } + /** + * The ID of the material. + * + * @name Material#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _materialId ++ } ); - const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - // Return if the ray never intersects the plane + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; - return t >= 0 ? t : null; + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Material'; - } + /** + * Defines the blending type of the material. + * + * It must be set to `CustomBlending` if custom blending properties like + * {@link Material#blendSrc}, {@link Material#blendDst} or {@link Material#blendEquation} + * should have any effect. + * + * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending)} + * @default NormalBlending + */ + this.blending = NormalBlending; - intersectPlane( plane, target ) { + /** + * Defines which side of faces will be rendered - front, back or both. + * + * @type {(FrontSide|BackSide|DoubleSide)} + * @default FrontSide + */ + this.side = FrontSide; - const t = this.distanceToPlane( plane ); + /** + * If set to `true`, vertex colors should be used. + * + * The engine supports RGB and RGBA vertex colors depending on whether a three (RGB) or + * four (RGBA) component color buffer attribute is used. + * + * @type {boolean} + * @default false + */ + this.vertexColors = false; - if ( t === null ) { + /** + * Defines how transparent the material is. + * A value of `0.0` indicates fully transparent, `1.0` is fully opaque. + * + * If the {@link Material#transparent} is not set to `true`, + * the material will remain fully opaque and this value will only affect its color. + * + * @type {number} + * @default 1 + */ + this.opacity = 1; - return null; + /** + * Defines whether this material is transparent. This has an effect on + * rendering as transparent objects need special treatment and are rendered + * after non-transparent objects. + * + * When set to true, the extent to which the material is transparent is + * controlled by {@link Material#opacity}. + * + * @type {boolean} + * @default false + */ + this.transparent = false; - } + /** + * Enables alpha hashed transparency, an alternative to {@link Material#transparent} or + * {@link Material#alphaTest}. The material will not be rendered if opacity is lower than + * a random threshold. Randomization introduces some grain or noise, but approximates alpha + * blending without the associated problems of sorting. Using TAA can reduce the resulting noise. + * + * @type {boolean} + * @default false + */ + this.alphaHash = false; - return this.at( t, target ); + /** + * Defines the blending source factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default SrcAlphaFactor + */ + this.blendSrc = SrcAlphaFactor; - } + /** + * Defines the blending destination factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default OneMinusSrcAlphaFactor + */ + this.blendDst = OneMinusSrcAlphaFactor; - intersectsPlane( plane ) { + /** + * Defines the blending equation. + * + * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default AddEquation + */ + this.blendEquation = AddEquation; - // check if the ray lies on the plane first + /** + * Defines the blending source alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendSrcAlpha = null; - const distToPoint = plane.distanceToPoint( this.origin ); + /** + * Defines the blending destination alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendDstAlpha = null; - if ( distToPoint === 0 ) { + /** + * Defines the blending equation of the alpha channel. + * + * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default null + */ + this.blendEquationAlpha = null; - return true; + /** + * Represents the RGB values of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantColor` or `OneMinusConstantColor`. + * + * @type {Color} + * @default (0,0,0) + */ + this.blendColor = new Color( 0, 0, 0 ); - } + /** + * Represents the alpha value of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantAlpha` or `OneMinusConstantAlpha`. + * + * @type {number} + * @default 0 + */ + this.blendAlpha = 0; - const denominator = plane.normal.dot( this.direction ); + /** + * Defines the depth function. + * + * @type {(NeverDepth|AlwaysDepth|LessDepth|LessEqualDepth|EqualDepth|GreaterEqualDepth|GreaterDepth|NotEqualDepth)} + * @default LessEqualDepth + */ + this.depthFunc = LessEqualDepth; - if ( denominator * distToPoint < 0 ) { + /** + * Whether to have depth test enabled when rendering this material. + * When the depth test is disabled, the depth write will also be implicitly disabled. + * + * @type {boolean} + * @default true + */ + this.depthTest = true; - return true; + /** + * Whether rendering this material has any effect on the depth buffer. + * + * When drawing 2D overlays it can be useful to disable the depth writing in + * order to layer several things together without creating z-index artifacts. + * + * @type {boolean} + * @default true + */ + this.depthWrite = true; - } + /** + * The bit mask to use when writing to the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilWriteMask = 0xff; - // ray origin is behind the plane (and is pointing behind it) + /** + * The stencil comparison function to use. + * + * @type {NeverStencilFunc|LessStencilFunc|EqualStencilFunc|LessEqualStencilFunc|GreaterStencilFunc|NotEqualStencilFunc|GreaterEqualStencilFunc|AlwaysStencilFunc} + * @default AlwaysStencilFunc + */ + this.stencilFunc = AlwaysStencilFunc; - return false; + /** + * The value to use when performing stencil comparisons or stencil operations. + * + * @type {number} + * @default 0 + */ + this.stencilRef = 0; - } + /** + * The bit mask to use when comparing against the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilFuncMask = 0xff; - intersectBox( box, target ) { + /** + * Which stencil operation to perform when the comparison function returns `false`. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilFail = KeepStencilOp; - let tmin, tmax, tymin, tymax, tzmin, tzmax; + /** + * Which stencil operation to perform when the comparison function returns + * `true` but the depth test fails. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZFail = KeepStencilOp; - const invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; + /** + * Which stencil operation to perform when the comparison function returns + * `true` and the depth test passes. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZPass = KeepStencilOp; - const origin = this.origin; + /** + * Whether stencil operations are performed against the stencil buffer. In + * order to perform writes or comparisons against the stencil buffer this + * value must be `true`. + * + * @type {boolean} + * @default false + */ + this.stencilWrite = false; - if ( invdirx >= 0 ) { + /** + * User-defined clipping planes specified as THREE.Plane objects in world + * space. These planes apply to the objects this material is attached to. + * Points in space whose signed distance to the plane is negative are clipped + * (not rendered). This requires {@link WebGLRenderer#localClippingEnabled} to + * be `true`. + * + * @type {?Array} + * @default null + */ + this.clippingPlanes = null; - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; + /** + * Changes the behavior of clipping planes so that only their intersection is + * clipped, rather than their union. + * + * @type {boolean} + * @default false + */ + this.clipIntersection = false; - } else { + /** + * Defines whether to clip shadows according to the clipping planes specified + * on this material. + * + * @type {boolean} + * @default false + */ + this.clipShadows = false; - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; + /** + * Defines which side of faces cast shadows. If `null`, the side casting shadows + * is determined as follows: + * + * - When {@link Material#side} is set to `FrontSide`, the back side cast shadows. + * - When {@link Material#side} is set to `BackSide`, the front side cast shadows. + * - When {@link Material#side} is set to `DoubleSide`, both sides cast shadows. + * + * @type {?(FrontSide|BackSide|DoubleSide)} + * @default null + */ + this.shadowSide = null; - } + /** + * Whether to render the material's color. + * + * This can be used in conjunction with {@link Object3D#renderOder} to create invisible + * objects that occlude other objects. + * + * @type {boolean} + * @default true + */ + this.colorWrite = true; - if ( invdiry >= 0 ) { + /** + * Override the renderer's default precision for this material. + * + * @type {?('highp'|'mediump'|'lowp')} + * @default null + */ + this.precision = null; - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; + /** + * Whether to use polygon offset or not. When enabled, each fragment's depth value will + * be offset after it is interpolated from the depth values of the appropriate vertices. + * The offset is added before the depth test is performed and before the value is written + * into the depth buffer. + * + * Can be useful for rendering hidden-line images, for applying decals to surfaces, and for + * rendering solids with highlighted edges. + * + * @type {boolean} + * @default false + */ + this.polygonOffset = false; - } else { + /** + * Specifies a scale factor that is used to create a variable depth offset for each polygon. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetFactor = 0; - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; + /** + * Is multiplied by an implementation-specific value to create a constant depth offset. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetUnits = 0; - } + /** + * Whether to apply dithering to the color to remove the appearance of banding. + * + * @type {boolean} + * @default false + */ + this.dithering = false; - if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; + /** + * Whether alpha to coverage should be enabled or not. Can only be used with MSAA-enabled contexts + * (meaning when the renderer was created with *antialias* parameter set to `true`). Enabling this + * will smooth aliasing on clip plane edges and alphaTest-clipped edges. + * + * @type {boolean} + * @default false + */ + this.alphaToCoverage = false; - if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; + /** + * Whether to premultiply the alpha (transparency) value. + * + * @type {boolean} + * @default false + */ + this.premultipliedAlpha = false; - if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; + /** + * Whether double-sided, transparent objects should be rendered with a single pass or not. + * + * The engine renders double-sided, transparent objects with two draw calls (back faces first, + * then front faces) to mitigate transparency artifacts. There are scenarios however where this + * approach produces no quality gains but still doubles draw calls e.g. when rendering flat + * vegetation like grass sprites. In these cases, set the `forceSinglePass` flag to `true` to + * disable the two pass rendering to avoid performance issues. + * + * @type {boolean} + * @default false + */ + this.forceSinglePass = false; - if ( invdirz >= 0 ) { + /** + * Whether it's possible to override the material with {@link Scene#overrideMaterial} or not. + * + * @type {boolean} + * @default true + */ + this.allowOverride = true; - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; + /** + * Defines whether 3D objects using this material are visible. + * + * @type {boolean} + * @default true + */ + this.visible = true; - } else { + /** + * Defines whether this material is tone mapped according to the renderer's tone mapping setting. + * + * It is ignored when rendering to a render target or using post processing or when using + * `WebGPURenderer`. In all these cases, all materials are honored by tone mapping. + * + * @type {boolean} + * @default true + */ + this.toneMapped = true; - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; + /** + * An object that can be used to store custom data about the Material. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - } + /** + * This starts at `0` and counts how many times {@link Material#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; + this._alphaTest = 0; - if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; + } - if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; + /** + * Sets the alpha value to be used when running an alpha test. The material + * will not be rendered if the opacity is lower than this value. + * + * @type {number} + * @readonly + * @default 0 + */ + get alphaTest() { - //return point closest to the ray (positive side) + return this._alphaTest; - if ( tmax < 0 ) return null; + } - return this.at( tmin >= 0 ? tmin : tmax, target ); + set alphaTest( value ) { - } + if ( this._alphaTest > 0 !== value > 0 ) { - intersectsBox( box ) { + this.version ++; - return this.intersectBox( box, _vector$3 ) !== null; + } + + this._alphaTest = value; } - intersectTriangle( a, b, c, backfaceCulling, target ) { + /** + * An optional callback that is executed immediately before the material is used to render a 3D object. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Object3D} object - The 3D object. + * @param {Object} group - The geometry group data. + */ + onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} - // Compute the offset origin, edges, and normal. + /** + * An optional callback that is executed immediately before the shader + * program is compiled. This function is called with the shader source code + * as a parameter. Useful for the modification of built-in materials. + * + * This method can only be used when rendering with {@link WebGLRenderer}. The + * recommended approach when customizing materials is to use `WebGPURenderer` with the new + * Node Material system and [TSL]{@link https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language}. + * + * @param {{vertexShader:string,fragmentShader:string,uniforms:Object}} shaderobject - The object holds the uniforms and the vertex and fragment shader source. + * @param {WebGLRenderer} renderer - A reference to the renderer. + */ + onBeforeCompile( /* shaderobject, renderer */ ) {} - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h + /** + * In case {@link Material#onBeforeCompile} is used, this callback can be used to identify + * values of settings used in `onBeforeCompile()`, so three.js can reuse a cached + * shader or recompile the shader for this material as needed. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @return {string} The custom program cache key. + */ + customProgramCacheKey() { - _edge1.subVectors( b, a ); - _edge2.subVectors( c, a ); - _normal$1.crossVectors( _edge1, _edge2 ); + return this.onBeforeCompile.toString(); - // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, - // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by - // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) - // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) - // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) - let DdN = this.direction.dot( _normal$1 ); - let sign; + } - if ( DdN > 0 ) { + /** + * This method can be used to set default values from parameter objects. + * It is a generic implementation so it can be used with different types + * of materials. + * + * @param {Object} [values] - The material values to set. + */ + setValues( values ) { - if ( backfaceCulling ) return null; - sign = 1; + if ( values === undefined ) return; - } else if ( DdN < 0 ) { + for ( const key in values ) { - sign = - 1; - DdN = - DdN; + const newValue = values[ key ]; - } else { + if ( newValue === undefined ) { - return null; + console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); + continue; - } + } - _diff.subVectors( this.origin, a ); - const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); + const currentValue = this[ key ]; - // b1 < 0, no intersection - if ( DdQxE2 < 0 ) { + if ( currentValue === undefined ) { - return null; + console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); + continue; - } + } - const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); + if ( currentValue && currentValue.isColor ) { - // b2 < 0, no intersection - if ( DdE1xQ < 0 ) { + currentValue.set( newValue ); - return null; + } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { - } + currentValue.copy( newValue ); - // b1+b2 > 1, no intersection - if ( DdQxE2 + DdE1xQ > DdN ) { + } else { - return null; + this[ key ] = newValue; + + } } - // Line intersects triangle, check if ray does. - const QdN = - sign * _diff.dot( _normal$1 ); + } - // t < 0, no intersection - if ( QdN < 0 ) { + /** + * Serializes the material into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized material. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - return null; + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - } + if ( isRootObject ) { - // Ray intersects triangle. - return this.at( QdN / DdN, target ); + meta = { + textures: {}, + images: {} + }; - } + } - applyMatrix4( matrix4 ) { + const data = { + metadata: { + version: 4.7, + type: 'Material', + generator: 'Material.toJSON' + } + }; - this.origin.applyMatrix4( matrix4 ); - this.direction.transformDirection( matrix4 ); + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; - return this; + if ( this.name !== '' ) data.name = this.name; - } + if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - equals( ray ) { + if ( this.roughness !== undefined ) data.roughness = this.roughness; + if ( this.metalness !== undefined ) data.metalness = this.metalness; - return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); + if ( this.sheen !== undefined ) data.sheen = this.sheen; + if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); + if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; + if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); + if ( this.emissiveIntensity !== undefined && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; - } + if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); + if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; + if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); + if ( this.shininess !== undefined ) data.shininess = this.shininess; + if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; + if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; - clone() { + if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { - return new this.constructor().copy( this ); + data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; - } + } -} + if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { -const _v0$1 = /*@__PURE__*/ new Vector3(); -const _v1$2 = /*@__PURE__*/ new Vector3(); -const _v2$1 = /*@__PURE__*/ new Vector3(); -const _v3$1 = /*@__PURE__*/ new Vector3(); + data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; -const _vab = /*@__PURE__*/ new Vector3(); -const _vac = /*@__PURE__*/ new Vector3(); -const _vbc = /*@__PURE__*/ new Vector3(); -const _vap = /*@__PURE__*/ new Vector3(); -const _vbp = /*@__PURE__*/ new Vector3(); -const _vcp = /*@__PURE__*/ new Vector3(); + } -class Triangle { + if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { - constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { + data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; + data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); - this.a = a; - this.b = b; - this.c = c; + } - } + if ( this.sheenColorMap && this.sheenColorMap.isTexture ) { - static getNormal( a, b, c, target ) { + data.sheenColorMap = this.sheenColorMap.toJSON( meta ).uuid; - target.subVectors( c, b ); - _v0$1.subVectors( a, b ); - target.cross( _v0$1 ); + } - const targetLengthSq = target.lengthSq(); - if ( targetLengthSq > 0 ) { + if ( this.sheenRoughnessMap && this.sheenRoughnessMap.isTexture ) { - return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); + data.sheenRoughnessMap = this.sheenRoughnessMap.toJSON( meta ).uuid; } - return target.set( 0, 0, 0 ); - - } + if ( this.dispersion !== undefined ) data.dispersion = this.dispersion; - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - static getBarycoord( point, a, b, c, target ) { + if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; + if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; + if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; - _v0$1.subVectors( c, a ); - _v1$2.subVectors( b, a ); - _v2$1.subVectors( point, a ); + if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { - const dot00 = _v0$1.dot( _v0$1 ); - const dot01 = _v0$1.dot( _v1$2 ); - const dot02 = _v0$1.dot( _v2$1 ); - const dot11 = _v1$2.dot( _v1$2 ); - const dot12 = _v1$2.dot( _v2$1 ); + data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; - const denom = ( dot00 * dot11 - dot01 * dot01 ); + } - // collinear or singular triangle - if ( denom === 0 ) { + if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { - target.set( 0, 0, 0 ); - return null; + data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; } - const invDenom = 1 / denom; - const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; + if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; - // barycentric coordinates must always sum to 1 - return target.set( 1 - u - v, v, u ); + if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { - } + data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; - static containsPoint( point, a, b, c ) { + } - // if the triangle is degenerate then we can't contain a point - if ( this.getBarycoord( point, a, b, c, _v3$1 ) === null ) { + if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; + if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; + if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - return false; + if ( this.lightMap && this.lightMap.isTexture ) { + + data.lightMap = this.lightMap.toJSON( meta ).uuid; + data.lightMapIntensity = this.lightMapIntensity; } - return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); + if ( this.aoMap && this.aoMap.isTexture ) { - } + data.aoMap = this.aoMap.toJSON( meta ).uuid; + data.aoMapIntensity = this.aoMapIntensity; - static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { + } - if ( this.getBarycoord( point, p1, p2, p3, _v3$1 ) === null ) { + if ( this.bumpMap && this.bumpMap.isTexture ) { - target.x = 0; - target.y = 0; - if ( 'z' in target ) target.z = 0; - if ( 'w' in target ) target.w = 0; - return null; + data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + data.bumpScale = this.bumpScale; } - target.setScalar( 0 ); - target.addScaledVector( v1, _v3$1.x ); - target.addScaledVector( v2, _v3$1.y ); - target.addScaledVector( v3, _v3$1.z ); + if ( this.normalMap && this.normalMap.isTexture ) { - return target; + data.normalMap = this.normalMap.toJSON( meta ).uuid; + data.normalMapType = this.normalMapType; + data.normalScale = this.normalScale.toArray(); - } + } - static isFrontFacing( a, b, c, direction ) { + if ( this.displacementMap && this.displacementMap.isTexture ) { - _v0$1.subVectors( c, b ); - _v1$2.subVectors( a, b ); + data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + data.displacementScale = this.displacementScale; + data.displacementBias = this.displacementBias; - // strictly front facing - return ( _v0$1.cross( _v1$2 ).dot( direction ) < 0 ) ? true : false; + } - } + if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; + if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - set( a, b, c ) { + if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; + if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; + if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - this.a.copy( a ); - this.b.copy( b ); - this.c.copy( c ); + if ( this.envMap && this.envMap.isTexture ) { - return this; + data.envMap = this.envMap.toJSON( meta ).uuid; - } + if ( this.combine !== undefined ) data.combine = this.combine; - setFromPointsAndIndices( points, i0, i1, i2 ) { + } - this.a.copy( points[ i0 ] ); - this.b.copy( points[ i1 ] ); - this.c.copy( points[ i2 ] ); + if ( this.envMapRotation !== undefined ) data.envMapRotation = this.envMapRotation.toArray(); + if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; + if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; + if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - return this; + if ( this.gradientMap && this.gradientMap.isTexture ) { - } + data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - setFromAttributeAndIndices( attribute, i0, i1, i2 ) { + } - this.a.fromBufferAttribute( attribute, i0 ); - this.b.fromBufferAttribute( attribute, i1 ); - this.c.fromBufferAttribute( attribute, i2 ); + if ( this.transmission !== undefined ) data.transmission = this.transmission; + if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; + if ( this.thickness !== undefined ) data.thickness = this.thickness; + if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; + if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; + if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - return this; + if ( this.size !== undefined ) data.size = this.size; + if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; + if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - } + if ( this.blending !== NormalBlending ) data.blending = this.blending; + if ( this.side !== FrontSide ) data.side = this.side; + if ( this.vertexColors === true ) data.vertexColors = true; - clone() { + if ( this.opacity < 1 ) data.opacity = this.opacity; + if ( this.transparent === true ) data.transparent = true; - return new this.constructor().copy( this ); + if ( this.blendSrc !== SrcAlphaFactor ) data.blendSrc = this.blendSrc; + if ( this.blendDst !== OneMinusSrcAlphaFactor ) data.blendDst = this.blendDst; + if ( this.blendEquation !== AddEquation ) data.blendEquation = this.blendEquation; + if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha; + if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha; + if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha; + if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex(); + if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha; - } + if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc; + if ( this.depthTest === false ) data.depthTest = this.depthTest; + if ( this.depthWrite === false ) data.depthWrite = this.depthWrite; + if ( this.colorWrite === false ) data.colorWrite = this.colorWrite; - copy( triangle ) { + if ( this.stencilWriteMask !== 0xff ) data.stencilWriteMask = this.stencilWriteMask; + if ( this.stencilFunc !== AlwaysStencilFunc ) data.stencilFunc = this.stencilFunc; + if ( this.stencilRef !== 0 ) data.stencilRef = this.stencilRef; + if ( this.stencilFuncMask !== 0xff ) data.stencilFuncMask = this.stencilFuncMask; + if ( this.stencilFail !== KeepStencilOp ) data.stencilFail = this.stencilFail; + if ( this.stencilZFail !== KeepStencilOp ) data.stencilZFail = this.stencilZFail; + if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; + if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; - this.a.copy( triangle.a ); - this.b.copy( triangle.b ); - this.c.copy( triangle.c ); + // rotation (SpriteMaterial) + if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; - return this; + if ( this.polygonOffset === true ) data.polygonOffset = true; + if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; + if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; - } + if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; + if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; + if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; + if ( this.scale !== undefined ) data.scale = this.scale; - getArea() { + if ( this.dithering === true ) data.dithering = true; - _v0$1.subVectors( this.c, this.b ); - _v1$2.subVectors( this.a, this.b ); + if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + if ( this.alphaHash === true ) data.alphaHash = true; + if ( this.alphaToCoverage === true ) data.alphaToCoverage = true; + if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = true; + if ( this.forceSinglePass === true ) data.forceSinglePass = true; - return _v0$1.cross( _v1$2 ).length() * 0.5; + if ( this.wireframe === true ) data.wireframe = true; + if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; + if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - } + if ( this.flatShading === true ) data.flatShading = true; - getMidpoint( target ) { + if ( this.visible === false ) data.visible = false; - return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); + if ( this.toneMapped === false ) data.toneMapped = false; - } + if ( this.fog === false ) data.fog = false; - getNormal( target ) { + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - return Triangle.getNormal( this.a, this.b, this.c, target ); + // TODO: Copied from Object3D.toJSON - } + function extractFromCache( cache ) { - getPlane( target ) { + const values = []; - return target.setFromCoplanarPoints( this.a, this.b, this.c ); + for ( const key in cache ) { - } + const data = cache[ key ]; + delete data.metadata; + values.push( data ); - getBarycoord( point, target ) { + } - return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); + return values; - } + } - getInterpolation( point, v1, v2, v3, target ) { + if ( isRootObject ) { - return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); - } + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; - containsPoint( point ) { + } - return Triangle.containsPoint( point, this.a, this.b, this.c ); + return data; } - isFrontFacing( direction ) { + /** + * Returns a new material with copied values from this instance. + * + * @return {Material} A clone of this instance. + */ + clone() { - return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); + return new this.constructor().copy( this ); } - intersectsBox( box ) { - - return box.intersectsTriangle( this ); + /** + * Copies the values of the given material to this instance. + * + * @param {Material} source - The material to copy. + * @return {Material} A reference to this instance. + */ + copy( source ) { - } + this.name = source.name; - closestPointToPoint( p, target ) { + this.blending = source.blending; + this.side = source.side; + this.vertexColors = source.vertexColors; - const a = this.a, b = this.b, c = this.c; - let v, w; + this.opacity = source.opacity; + this.transparent = source.transparent; - // algorithm thanks to Real-Time Collision Detection by Christer Ericson, - // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., - // under the accompanying license; see chapter 5.1.5 for detailed explanation. - // basically, we're distinguishing which of the voronoi regions of the triangle - // the point lies in with the minimum amount of redundant computation. + this.blendSrc = source.blendSrc; + this.blendDst = source.blendDst; + this.blendEquation = source.blendEquation; + this.blendSrcAlpha = source.blendSrcAlpha; + this.blendDstAlpha = source.blendDstAlpha; + this.blendEquationAlpha = source.blendEquationAlpha; + this.blendColor.copy( source.blendColor ); + this.blendAlpha = source.blendAlpha; - _vab.subVectors( b, a ); - _vac.subVectors( c, a ); - _vap.subVectors( p, a ); - const d1 = _vab.dot( _vap ); - const d2 = _vac.dot( _vap ); - if ( d1 <= 0 && d2 <= 0 ) { + this.depthFunc = source.depthFunc; + this.depthTest = source.depthTest; + this.depthWrite = source.depthWrite; - // vertex region of A; barycentric coords (1, 0, 0) - return target.copy( a ); + this.stencilWriteMask = source.stencilWriteMask; + this.stencilFunc = source.stencilFunc; + this.stencilRef = source.stencilRef; + this.stencilFuncMask = source.stencilFuncMask; + this.stencilFail = source.stencilFail; + this.stencilZFail = source.stencilZFail; + this.stencilZPass = source.stencilZPass; + this.stencilWrite = source.stencilWrite; - } + const srcPlanes = source.clippingPlanes; + let dstPlanes = null; - _vbp.subVectors( p, b ); - const d3 = _vab.dot( _vbp ); - const d4 = _vac.dot( _vbp ); - if ( d3 >= 0 && d4 <= d3 ) { + if ( srcPlanes !== null ) { - // vertex region of B; barycentric coords (0, 1, 0) - return target.copy( b ); + const n = srcPlanes.length; + dstPlanes = new Array( n ); - } + for ( let i = 0; i !== n; ++ i ) { - const vc = d1 * d4 - d3 * d2; - if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { + dstPlanes[ i ] = srcPlanes[ i ].clone(); - v = d1 / ( d1 - d3 ); - // edge region of AB; barycentric coords (1-v, v, 0) - return target.copy( a ).addScaledVector( _vab, v ); + } } - _vcp.subVectors( p, c ); - const d5 = _vab.dot( _vcp ); - const d6 = _vac.dot( _vcp ); - if ( d6 >= 0 && d5 <= d6 ) { + this.clippingPlanes = dstPlanes; + this.clipIntersection = source.clipIntersection; + this.clipShadows = source.clipShadows; - // vertex region of C; barycentric coords (0, 0, 1) - return target.copy( c ); + this.shadowSide = source.shadowSide; - } + this.colorWrite = source.colorWrite; - const vb = d5 * d2 - d1 * d6; - if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { + this.precision = source.precision; - w = d2 / ( d2 - d6 ); - // edge region of AC; barycentric coords (1-w, 0, w) - return target.copy( a ).addScaledVector( _vac, w ); + this.polygonOffset = source.polygonOffset; + this.polygonOffsetFactor = source.polygonOffsetFactor; + this.polygonOffsetUnits = source.polygonOffsetUnits; - } + this.dithering = source.dithering; - const va = d3 * d6 - d5 * d4; - if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { + this.alphaTest = source.alphaTest; + this.alphaHash = source.alphaHash; + this.alphaToCoverage = source.alphaToCoverage; + this.premultipliedAlpha = source.premultipliedAlpha; + this.forceSinglePass = source.forceSinglePass; - _vbc.subVectors( c, b ); - w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); - // edge region of BC; barycentric coords (0, 1-w, w) - return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC + this.visible = source.visible; - } + this.toneMapped = source.toneMapped; - // face region - const denom = 1 / ( va + vb + vc ); - // u = va * denom - v = vb * denom; - w = vc * denom; + this.userData = JSON.parse( JSON.stringify( source.userData ) ); - return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); + return this; } - equals( triangle ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Material#dispose + */ + dispose() { - return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); + /** + * Fires when the material has been disposed of. + * + * @event Material#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Setting this property to `true` indicates the engine the material + * needs to be recompiled. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; } } -class MeshBasicMaterial extends Material { +// Uniform Utilities - constructor( parameters ) { +function cloneUniforms( src ) { - super(); + const dst = {}; - this.isMeshBasicMaterial = true; + for ( const u in src ) { - this.type = 'MeshBasicMaterial'; + dst[ u ] = {}; - this.color = new Color( 0xffffff ); // emissive + for ( const p in src[ u ] ) { - this.map = null; + const property = src[ u ][ p ]; - this.lightMap = null; - this.lightMapIntensity = 1.0; + if ( property && ( property.isColor || + property.isMatrix3 || property.isMatrix4 || + property.isVector2 || property.isVector3 || property.isVector4 || + property.isTexture || property.isQuaternion ) ) { - this.aoMap = null; - this.aoMapIntensity = 1.0; + if ( property.isRenderTargetTexture ) { - this.specularMap = null; + console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); + dst[ u ][ p ] = null; - this.alphaMap = null; + } else { - this.envMap = null; - this.envMapRotation = new Euler(); - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + dst[ u ][ p ] = property.clone(); - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + } - this.fog = true; + } else if ( Array.isArray( property ) ) { - this.setValues( parameters ); + dst[ u ][ p ] = property.slice(); - } + } else { - copy( source ) { + dst[ u ][ p ] = property; - super.copy( source ); + } - this.color.copy( source.color ); + } - this.map = source.map; + } - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + return dst; - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; +} - this.specularMap = source.specularMap; +function mergeUniforms( uniforms ) { - this.alphaMap = source.alphaMap; + const merged = {}; - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + for ( let u = 0; u < uniforms.length; u ++ ) { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + const tmp = cloneUniforms( uniforms[ u ] ); - this.fog = source.fog; + for ( const p in tmp ) { - return this; + merged[ p ] = tmp[ p ]; - } + } -} + } -const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); -const _ray$3 = /*@__PURE__*/ new Ray(); -const _sphere$5 = /*@__PURE__*/ new Sphere(); -const _sphereHitAt = /*@__PURE__*/ new Vector3(); + return merged; -const _vA$1 = /*@__PURE__*/ new Vector3(); -const _vB$1 = /*@__PURE__*/ new Vector3(); -const _vC$1 = /*@__PURE__*/ new Vector3(); +} -const _tempA = /*@__PURE__*/ new Vector3(); -const _morphA = /*@__PURE__*/ new Vector3(); +function cloneUniformsGroups( src ) { -const _uvA$1 = /*@__PURE__*/ new Vector2(); -const _uvB$1 = /*@__PURE__*/ new Vector2(); -const _uvC$1 = /*@__PURE__*/ new Vector2(); + const dst = []; -const _normalA = /*@__PURE__*/ new Vector3(); -const _normalB = /*@__PURE__*/ new Vector3(); -const _normalC = /*@__PURE__*/ new Vector3(); + for ( let u = 0; u < src.length; u ++ ) { -const _intersectionPoint = /*@__PURE__*/ new Vector3(); -const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); + dst.push( src[ u ].clone() ); -class Mesh extends Object3D { + } - constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { + return dst; - super(); +} - this.isMesh = true; +function getUnlitUniformColorSpace( renderer ) { - this.type = 'Mesh'; + const currentRenderTarget = renderer.getRenderTarget(); - this.geometry = geometry; - this.material = material; + if ( currentRenderTarget === null ) { - this.updateMorphTargets(); + // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 + return renderer.outputColorSpace; } - copy( source, recursive ) { + // https://github.com/mrdoob/three.js/issues/27868 + if ( currentRenderTarget.isXRRenderTarget === true ) { - super.copy( source, recursive ); + return currentRenderTarget.texture.colorSpace; - if ( source.morphTargetInfluences !== undefined ) { + } - this.morphTargetInfluences = source.morphTargetInfluences.slice(); + return ColorManagement.workingColorSpace; - } +} - if ( source.morphTargetDictionary !== undefined ) { +// Legacy - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); +const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; - } +var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; +var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; - return this; +/** + * A material rendered with custom shaders. A shader is a small program written in GLSL. + * that runs on the GPU. You may want to use a custom shader if you need to implement an + * effect not included with any of the built-in materials. + * + * There are the following notes to bear in mind when using a `ShaderMaterial`: + * + * - `ShaderMaterial` can only be used with {@link WebGLRenderer}. + * - Built in attributes and uniforms are passed to the shaders along with your code. If + * you don't want that, use {@link RawShaderMaterial} instead. + * - You can use the directive `#pragma unroll_loop_start` and `#pragma unroll_loop_end` + * in order to unroll a `for` loop in GLSL by the shader preprocessor. The directive has + * to be placed right above the loop. The loop formatting has to correspond to a defined standard. + * - The loop has to be [normalized]{@link https://en.wikipedia.org/wiki/Normalized_loop}. + * - The loop variable has to be *i*. + * - The value `UNROLLED_LOOP_INDEX` will be replaced with the explicitly + * value of *i* for the given iteration and can be used in preprocessor + * statements. + * + * ```js + * const material = new THREE.ShaderMaterial( { + * uniforms: { + * time: { value: 1.0 }, + * resolution: { value: new THREE.Vector2() } + * }, + * vertexShader: document.getElementById( 'vertexShader' ).textContent, + * fragmentShader: document.getElementById( 'fragmentShader' ).textContent + * } ); + * ``` + * + * @augments Material + */ +class ShaderMaterial extends Material { - } + /** + * Constructs a new shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - updateMorphTargets() { + super(); - const geometry = this.geometry; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShaderMaterial = true; - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); + this.type = 'ShaderMaterial'; - if ( keys.length > 0 ) { + /** + * Defines custom constants using `#define` directives within the GLSL code + * for both the vertex shader and the fragment shader; each key/value pair + * yields another directive. + * ```js + * defines: { + * FOO: 15, + * BAR: true + * } + * ``` + * Yields the lines: + * ``` + * #define FOO 15 + * #define BAR true + * ``` + * + * @type {Object} + */ + this.defines = {}; - const morphAttribute = morphAttributes[ keys[ 0 ] ]; + /** + * An object of the form: + * ```js + * { + * "uniform1": { value: 1.0 }, + * "uniform2": { value: 2 } + * } + * ``` + * specifying the uniforms to be passed to the shader code; keys are uniform + * names, values are definitions of the form + * ``` + * { + * value: 1.0 + * } + * ``` + * where `value` is the value of the uniform. Names must match the name of + * the uniform, as defined in the GLSL code. Note that uniforms are refreshed + * on every frame, so updating the value of the uniform will immediately + * update the value available to the GLSL code. + * + * @type {Object} + */ + this.uniforms = {}; - if ( morphAttribute !== undefined ) { + /** + * An array holding uniforms groups for configuring UBOs. + * + * @type {Array} + */ + this.uniformsGroups = []; - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + /** + * Vertex shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.vertexShader = default_vertex; - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + /** + * Fragment shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.fragmentShader = default_fragment; - const name = morphAttribute[ m ].name || String( m ); + /** + * Controls line thickness or lines. + * + * WebGL and WebGPU ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - } + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - } + /** + * Define whether the material color is affected by global fog settings; `true` + * to pass fog uniforms to the shader. + * + * @type {boolean} + * @default false + */ + this.fog = false; - } + /** + * Defines whether this material uses lighting; `true` to pass uniform data + * related to lighting to this shader. + * + * @type {boolean} + * @default false + */ + this.lights = false; - } + /** + * Defines whether this material supports clipping; `true` to let the renderer + * pass the clippingPlanes uniform. + * + * @type {boolean} + * @default false + */ + this.clipping = false; - getVertexPosition( index, target ) { + /** + * Overwritten and set to `true` by default. + * + * @type {boolean} + * @default true + */ + this.forceSinglePass = true; - const geometry = this.geometry; - const position = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; + /** + * This object allows to enable certain WebGL 2 extensions. + * + * - clipCullDistance: set to `true` to use vertex shader clipping + * - multiDraw: set to `true` to use vertex shader multi_draw / enable gl_DrawID + * + * @type {{clipCullDistance:false,multiDraw:false}} + */ + this.extensions = { + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID + }; - target.fromBufferAttribute( position, index ); + /** + * When the rendered geometry doesn't include these attributes but the + * material does, these default values will be passed to the shaders. This + * avoids errors when buffer data is missing. + * + * - color: [ 1, 1, 1 ] + * - uv: [ 0, 0 ] + * - uv1: [ 0, 0 ] + * + * @type {Object} + */ + this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv1': [ 0, 0 ] + }; - const morphInfluences = this.morphTargetInfluences; + /** + * If set, this calls [gl.bindAttribLocation]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation} + * to bind a generic vertex index to an attribute variable. + * + * @type {string|undefined} + * @default undefined + */ + this.index0AttributeName = undefined; - if ( morphPosition && morphInfluences ) { + /** + * Can be used to force a uniform update while changing uniforms in + * {@link Object3D#onBeforeRender}. + * + * @type {boolean} + * @default false + */ + this.uniformsNeedUpdate = false; - _morphA.set( 0, 0, 0 ); + /** + * Defines the GLSL version of custom shader code. + * + * @type {?(GLSL1|GLSL3)} + * @default null + */ + this.glslVersion = null; - for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { + if ( parameters !== undefined ) { - const influence = morphInfluences[ i ]; - const morphAttribute = morphPosition[ i ]; + this.setValues( parameters ); - if ( influence === 0 ) continue; + } - _tempA.fromBufferAttribute( morphAttribute, index ); + } - if ( morphTargetsRelative ) { + copy( source ) { - _morphA.addScaledVector( _tempA, influence ); + super.copy( source ); - } else { + this.fragmentShader = source.fragmentShader; + this.vertexShader = source.vertexShader; - _morphA.addScaledVector( _tempA.sub( target ), influence ); + this.uniforms = cloneUniforms( source.uniforms ); + this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); - } + this.defines = Object.assign( {}, source.defines ); - } + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - target.add( _morphA ); + this.fog = source.fog; + this.lights = source.lights; + this.clipping = source.clipping; - } + this.extensions = Object.assign( {}, source.extensions ); - return target; + this.glslVersion = source.glslVersion; + + return this; } - raycast( raycaster, intersects ) { + toJSON( meta ) { - const geometry = this.geometry; - const material = this.material; - const matrixWorld = this.matrixWorld; + const data = super.toJSON( meta ); - if ( material === undefined ) return; + data.glslVersion = this.glslVersion; + data.uniforms = {}; - // test with bounding sphere in world space + for ( const name in this.uniforms ) { - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + const uniform = this.uniforms[ name ]; + const value = uniform.value; - _sphere$5.copy( geometry.boundingSphere ); - _sphere$5.applyMatrix4( matrixWorld ); + if ( value && value.isTexture ) { - // check distance from ray origin to bounding sphere + data.uniforms[ name ] = { + type: 't', + value: value.toJSON( meta ).uuid + }; - _ray$3.copy( raycaster.ray ).recast( raycaster.near ); + } else if ( value && value.isColor ) { - if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { + data.uniforms[ name ] = { + type: 'c', + value: value.getHex() + }; - if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; + } else if ( value && value.isVector2 ) { - if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; + data.uniforms[ name ] = { + type: 'v2', + value: value.toArray() + }; - } + } else if ( value && value.isVector3 ) { - // convert ray to local space of mesh + data.uniforms[ name ] = { + type: 'v3', + value: value.toArray() + }; - _inverseMatrix$3.copy( matrixWorld ).invert(); - _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); + } else if ( value && value.isVector4 ) { - // test with bounding box in local space + data.uniforms[ name ] = { + type: 'v4', + value: value.toArray() + }; - if ( geometry.boundingBox !== null ) { + } else if ( value && value.isMatrix3 ) { - if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; + data.uniforms[ name ] = { + type: 'm3', + value: value.toArray() + }; - } + } else if ( value && value.isMatrix4 ) { - // test for intersections with geometry + data.uniforms[ name ] = { + type: 'm4', + value: value.toArray() + }; - this._computeIntersections( raycaster, intersects, _ray$3 ); + } else { - } + data.uniforms[ name ] = { + value: value + }; - _computeIntersections( raycaster, intersects, rayLocalSpace ) { + // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far - let intersection; + } - const geometry = this.geometry; - const material = this.material; + } - const index = geometry.index; - const position = geometry.attributes.position; - const uv = geometry.attributes.uv; - const uv1 = geometry.attributes.uv1; - const normal = geometry.attributes.normal; - const groups = geometry.groups; - const drawRange = geometry.drawRange; + if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; - if ( index !== null ) { + data.vertexShader = this.vertexShader; + data.fragmentShader = this.fragmentShader; - // indexed buffer geometry + data.lights = this.lights; + data.clipping = this.clipping; - if ( Array.isArray( material ) ) { + const extensions = {}; - for ( let i = 0, il = groups.length; i < il; i ++ ) { + for ( const key in this.extensions ) { - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; + if ( this.extensions[ key ] === true ) extensions[ key ] = true; - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); + } - for ( let j = start, jl = end; j < jl; j += 3 ) { + if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; - const a = index.getX( j ); - const b = index.getX( j + 1 ); - const c = index.getX( j + 2 ); + return data; - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + } - if ( intersection ) { +} - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); +const _vector$3 = /*@__PURE__*/ new Vector3(); +const _segCenter = /*@__PURE__*/ new Vector3(); +const _segDir = /*@__PURE__*/ new Vector3(); +const _diff = /*@__PURE__*/ new Vector3(); - } +const _edge1 = /*@__PURE__*/ new Vector3(); +const _edge2 = /*@__PURE__*/ new Vector3(); +const _normal$1 = /*@__PURE__*/ new Vector3(); - } +/** + * A ray that emits from an origin in a certain direction. The class is used by + * {@link Raycaster} to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3D space the mouse is over) + * amongst other things. + */ +class Ray { - } + /** + * Constructs a new ray. + * + * @param {Vector3} [origin=(0,0,0)] - The origin of the ray. + * @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray. + */ + constructor( origin = new Vector3(), direction = new Vector3( 0, 0, -1 ) ) { - } else { + /** + * The origin of the ray. + * + * @type {Vector3} + */ + this.origin = origin; - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + /** + * The (normalized) direction of the ray. + * + * @type {Vector3} + */ + this.direction = direction; - for ( let i = start, il = end; i < il; i += 3 ) { + } - const a = index.getX( i ); - const b = index.getX( i + 1 ); - const c = index.getX( i + 2 ); + /** + * Sets the ray's components by copying the given values. + * + * @param {Vector3} origin - The origin. + * @param {Vector3} direction - The direction. + * @return {Ray} A reference to this ray. + */ + set( origin, direction ) { - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + this.origin.copy( origin ); + this.direction.copy( direction ); - if ( intersection ) { + return this; - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics - intersects.push( intersection ); + } - } + /** + * Copies the values of the given ray to this instance. + * + * @param {Ray} ray - The ray to copy. + * @return {Ray} A reference to this ray. + */ + copy( ray ) { - } + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); - } + return this; - } else if ( position !== undefined ) { + } - // non-indexed buffer geometry + /** + * Returns a vector that is located at a given distance along this ray. + * + * @param {number} t - The distance along the ray to retrieve a position for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A position on the ray. + */ + at( t, target ) { - if ( Array.isArray( material ) ) { + return target.copy( this.origin ).addScaledVector( this.direction, t ); - for ( let i = 0, il = groups.length; i < il; i ++ ) { + } - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = j; - const b = j + 1; - const c = j + 2; + /** + * Adjusts the direction of the ray to point at the given vector in world space. + * + * @param {Vector3} v - The target position. + * @return {Ray} A reference to this ray. + */ + lookAt( v ) { - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + this.direction.copy( v ).sub( this.origin ).normalize(); - if ( intersection ) { + return this; - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); + } - } + /** + * Shift the origin of this ray along its direction by the given distance. + * + * @param {number} t - The distance along the ray to interpolate. + * @return {Ray} A reference to this ray. + */ + recast( t ) { - } + this.origin.copy( this.at( t, _vector$3 ) ); - } + return this; - } else { + } - const start = Math.max( 0, drawRange.start ); - const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); + /** + * Returns the point along this ray that is closest to the given point. + * + * @param {Vector3} point - A point in 3D space to get the closet location on the ray for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on this ray. + */ + closestPointToPoint( point, target ) { - for ( let i = start, il = end; i < il; i += 3 ) { + target.subVectors( point, this.origin ); - const a = i; - const b = i + 1; - const c = i + 2; + const directionDistance = target.dot( this.direction ); - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + if ( directionDistance < 0 ) { - if ( intersection ) { + return target.copy( this.origin ); - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics - intersects.push( intersection ); + } - } + return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - } + } - } + /** + * Returns the distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The distance. + */ + distanceToPoint( point ) { - } + return Math.sqrt( this.distanceSqToPoint( point ) ); } -} + /** + * Returns the squared distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The squared distance. + */ + distanceSqToPoint( point ) { -function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { + const directionDistance = _vector$3.subVectors( point, this.origin ).dot( this.direction ); - let intersect; + // point behind the ray - if ( material.side === BackSide ) { + if ( directionDistance < 0 ) { - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); + return this.origin.distanceToSquared( point ); - } else { + } - intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); + _vector$3.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - } + return _vector$3.distanceToSquared( point ); - if ( intersect === null ) return null; + } - _intersectionPointWorld.copy( point ); - _intersectionPointWorld.applyMatrix4( object.matrixWorld ); + /** + * Returns the squared distance between this ray and the given line segment. + * + * @param {Vector3} v0 - The start point of the line segment. + * @param {Vector3} v1 - The end point of the line segment. + * @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment. + * @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray. + * @return {number} The squared distance. + */ + distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); + // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h + // It returns the min distance between the ray and the segment + // defined by v0 and v1 + // It can also set two optional targets : + // - The closest point on the ray + // - The closest point on the segment - if ( distance < raycaster.near || distance > raycaster.far ) return null; + _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); + _segDir.copy( v1 ).sub( v0 ).normalize(); + _diff.copy( this.origin ).sub( _segCenter ); - return { - distance: distance, - point: _intersectionPointWorld.clone(), - object: object - }; + const segExtent = v0.distanceTo( v1 ) * 0.5; + const a01 = - this.direction.dot( _segDir ); + const b0 = _diff.dot( this.direction ); + const b1 = - _diff.dot( _segDir ); + const c = _diff.lengthSq(); + const det = Math.abs( 1 - a01 * a01 ); + let s0, s1, sqrDist, extDet; -} + if ( det > 0 ) { -function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { + // The ray and segment are not parallel. - object.getVertexPosition( a, _vA$1 ); - object.getVertexPosition( b, _vB$1 ); - object.getVertexPosition( c, _vC$1 ); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; - const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); + if ( s0 >= 0 ) { - if ( intersection ) { + if ( s1 >= - extDet ) { - if ( uv ) { + if ( s1 <= extDet ) { - _uvA$1.fromBufferAttribute( uv, a ); - _uvB$1.fromBufferAttribute( uv, b ); - _uvC$1.fromBufferAttribute( uv, c ); + // region 0 + // Minimum at interior points of ray and segment. - intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); + const invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - } + } else { - if ( uv1 ) { + // region 1 - _uvA$1.fromBufferAttribute( uv1, a ); - _uvB$1.fromBufferAttribute( uv1, b ); - _uvC$1.fromBufferAttribute( uv1, c ); + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); + } - } + } else { - if ( normal ) { + // region 5 - _normalA.fromBufferAttribute( normal, a ); - _normalB.fromBufferAttribute( normal, b ); - _normalC.fromBufferAttribute( normal, c ); + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); + } - if ( intersection.normal.dot( ray.direction ) > 0 ) { + } else { - intersection.normal.multiplyScalar( - 1 ); + if ( s1 <= - extDet ) { - } + // region 4 - } + s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - const face = { - a: a, - b: b, - c: c, - normal: new Vector3(), - materialIndex: 0 - }; + } else if ( s1 <= extDet ) { - Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); + // region 3 - intersection.face = face; + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; - } + } else { - return intersection; + // region 2 -} + s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; -var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; + } -var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; + } -var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; + } else { -var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; + // Ray and segment are parallel. -var alphatest_fragment = "#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif"; + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; -var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; + } -var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; + if ( optionalPointOnRay ) { -var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; + optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); -var batching_pars_vertex = "#ifdef USE_BATCHING\n\tattribute float batchId;\n\tuniform highp sampler2D batchingTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; + } -var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( batchId );\n#endif"; + if ( optionalPointOnSegment ) { -var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; + optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); -var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; + } -var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; + return sqrDist; -var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; + } -var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; + /** + * Intersects this ray with the given sphere, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Sphere} sphere - The sphere to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectSphere( sphere, target ) { -var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif"; + _vector$3.subVectors( sphere.center, this.origin ); + const tca = _vector$3.dot( this.direction ); + const d2 = _vector$3.dot( _vector$3 ) - tca * tca; + const radius2 = sphere.radius * sphere.radius; -var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; + if ( d2 > radius2 ) return null; -var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; + const thc = Math.sqrt( radius2 - d2 ); -var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; + // t0 = first intersect point - entrance on front of sphere + const t0 = tca - thc; -var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; + // t1 = second intersect point - exit point on back of sphere + const t1 = tca + thc; -var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; + // test to see if t1 is behind the ray - if so, return null + if ( t1 < 0 ) return null; -var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; + // test to see if t0 is behind the ray: + // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, + // in order to always return an intersect point that is in front of the ray. + if ( t0 < 0 ) return this.at( t1, target ); -var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; + // else t0 is in front of the ray, so return the first collision point scaled by t0 + return this.at( t0, target ); -var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; + } -var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; + /** + * Returns `true` if this ray intersects with the given sphere. + * + * @param {Sphere} sphere - The sphere to intersect. + * @return {boolean} Whether this ray intersects with the given sphere or not. + */ + intersectsSphere( sphere ) { -var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; + if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187 -var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; + return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); -var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; + } -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; + /** + * Computes the distance from the ray's origin to the given plane. Returns `null` if the ray + * does not intersect with the plane. + * + * @param {Plane} plane - The plane to compute the distance to. + * @return {?number} Whether this ray intersects with the given sphere or not. + */ + distanceToPlane( plane ) { -var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; + const denominator = plane.normal.dot( this.direction ); -var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; + if ( denominator === 0 ) { -var colorspace_pars_fragment = "\nconst mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3(\n\tvec3( 0.8224621, 0.177538, 0.0 ),\n\tvec3( 0.0331941, 0.9668058, 0.0 ),\n\tvec3( 0.0170827, 0.0723974, 0.9105199 )\n);\nconst mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.2249401, - 0.2249404, 0.0 ),\n\tvec3( - 0.0420569, 1.0420571, 0.0 ),\n\tvec3( - 0.0196376, - 0.0786361, 1.0982735 )\n);\nvec4 LinearSRGBToLinearDisplayP3( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a );\n}\nvec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a );\n}\nvec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn sRGBTransferOETF( value );\n}"; + // line is coplanar, return origin + if ( plane.distanceToPoint( this.origin ) === 0 ) { -var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; + return 0; -var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; + } -var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; + // Null is preferable to undefined since undefined means.... it is undefined -var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; + return null; -var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; + } -var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; + const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; -var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; + // Return if the ray never intersects the plane -var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; + return t >= 0 ? t : null; -var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; + } -var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; + /** + * Intersects this ray with the given plane, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Plane} plane - The plane to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectPlane( plane, target ) { -var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; + const t = this.distanceToPlane( plane ); -var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; + if ( t === null ) { -var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; + return null; -var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; + } -var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; + return this.at( t, target ); -var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; + } -var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; + /** + * Returns `true` if this ray intersects with the given plane. + * + * @param {Plane} plane - The plane to intersect. + * @return {boolean} Whether this ray intersects with the given plane or not. + */ + intersectsPlane( plane ) { -var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; + // check if the ray lies on the plane first -var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; + const distToPoint = plane.distanceToPoint( this.origin ); -var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; + if ( distToPoint === 0 ) { -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; + return true; -var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; + } -var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; + const denominator = plane.normal.dot( this.direction ); -var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; + if ( denominator * distToPoint < 0 ) { -var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; + return true; -var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; + } -var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; + // ray origin is behind the plane (and is pointing behind it) -var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; + return false; -var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; + } -var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; + /** + * Intersects this ray with the given bounding box, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Box3} box - The box to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectBox( box, target ) { -var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; + let tmin, tmax, tymin, tymax, tzmin, tzmax; -var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; + const invdirx = 1 / this.direction.x, + invdiry = 1 / this.direction.y, + invdirz = 1 / this.direction.z; -var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; + const origin = this.origin; -var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; + if ( invdirx >= 0 ) { -var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; + tmin = ( box.min.x - origin.x ) * invdirx; + tmax = ( box.max.x - origin.x ) * invdirx; -var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[MORPHTARGETS_COUNT];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; + } else { -var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; + tmin = ( box.max.x - origin.x ) * invdirx; + tmax = ( box.min.x - origin.x ) * invdirx; -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; + } -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t#endif\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\t#ifndef USE_INSTANCING_MORPH\n\t\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\t#endif\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; + if ( invdiry >= 0 ) { -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; + tymin = ( box.min.y - origin.y ) * invdiry; + tymax = ( box.max.y - origin.y ) * invdiry; -var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; + } else { -var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; + tymin = ( box.max.y - origin.y ) * invdiry; + tymax = ( box.min.y - origin.y ) * invdiry; -var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; + } -var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; + if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; -var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; + if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; -var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; + if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; -var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; + if ( invdirz >= 0 ) { -var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; + tzmin = ( box.min.z - origin.z ) * invdirz; + tzmax = ( box.max.z - origin.z ) * invdirz; -var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; + } else { -var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; + tzmin = ( box.max.z - origin.z ) * invdirz; + tzmax = ( box.min.z - origin.z ) * invdirz; -var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; + } -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; + if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; -var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; + if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; -var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; + if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; -var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; + //return point closest to the ray (positive side) -var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; + if ( tmax < 0 ) return null; -var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; + return this.at( tmin >= 0 ? tmin : tmax, target ); -var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; + } -var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; + /** + * Returns `true` if this ray intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this ray intersects with the given box or not. + */ + intersectsBox( box ) { -var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; + return this.intersectBox( box, _vector$3 ) !== null; -var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; + } -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; + /** + * Intersects this ray with the given triangle, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Vector3} a - The first vertex of the triangle. + * @param {Vector3} b - The second vertex of the triangle. + * @param {Vector3} c - The third vertex of the triangle. + * @param {boolean} backfaceCulling - Whether to use backface culling or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectTriangle( a, b, c, backfaceCulling, target ) { -var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; + // Compute the offset origin, edges, and normal. -var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; + // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h -var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; + _edge1.subVectors( b, a ); + _edge2.subVectors( c, a ); + _normal$1.crossVectors( _edge1, _edge2 ); -var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + let DdN = this.direction.dot( _normal$1 ); + let sign; -var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; + if ( DdN > 0 ) { -var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; + if ( backfaceCulling ) return null; + sign = 1; -var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; + } else if ( DdN < 0 ) { -var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tfloat startCompression = 0.8 - 0.04;\n\tfloat desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min(color.r, min(color.g, color.b));\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max(color.r, max(color.g, color.b));\n\tif (peak < startCompression) return color;\n\tfloat d = 1. - startCompression;\n\tfloat newPeak = 1. - d * d / (peak + d - startCompression);\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);\n\treturn mix(color, vec3(1, 1, 1), g);\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; + sign = -1; + DdN = - DdN; -var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; + } else { -var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; + return null; -var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; + } -var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; + _diff.subVectors( this.origin, a ); + const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); -var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { -var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; + return null; -const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; + } -const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; + const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); -const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { -const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; + return null; -const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; + } -const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { -const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; + return null; -const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; + } -const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; + // Line intersects triangle, check if ray does. + const QdN = - sign * _diff.dot( _normal$1 ); -const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; + // t < 0, no intersection + if ( QdN < 0 ) { -const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; + return null; -const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; + } -const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + // Ray intersects triangle. + return this.at( QdN / DdN, target ); -const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + /** + * Transforms this ray with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix4 - The transformation matrix. + * @return {Ray} A reference to this ray. + */ + applyMatrix4( matrix4 ) { -const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + this.origin.applyMatrix4( matrix4 ); + this.direction.transformDirection( matrix4 ); -const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + return this; -const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; + /** + * Returns `true` if this ray is equal with the given one. + * + * @param {Ray} ray - The ray to test for equality. + * @return {boolean} Whether this ray is equal with the given one. + */ + equals( ray ) { -const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); -const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; + } -const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; + /** + * Returns a new ray with copied values from this instance. + * + * @return {Ray} A clone of this instance. + */ + clone() { -const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + return new this.constructor().copy( this ); -const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; +} -const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; +const _v0$2 = /*@__PURE__*/ new Vector3(); +const _v1$2 = /*@__PURE__*/ new Vector3(); +const _v2$1 = /*@__PURE__*/ new Vector3(); +const _v3$1 = /*@__PURE__*/ new Vector3(); -const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; +const _vab = /*@__PURE__*/ new Vector3(); +const _vac = /*@__PURE__*/ new Vector3(); +const _vbc = /*@__PURE__*/ new Vector3(); +const _vap = /*@__PURE__*/ new Vector3(); +const _vbp = /*@__PURE__*/ new Vector3(); +const _vcp = /*@__PURE__*/ new Vector3(); -const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; +const _v40 = /*@__PURE__*/ new Vector4(); +const _v41 = /*@__PURE__*/ new Vector4(); +const _v42 = /*@__PURE__*/ new Vector4(); -const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; +/** + * A geometric triangle as defined by three vectors representing its three corners. + */ +class Triangle { -const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + /** + * Constructs a new triangle. + * + * @param {Vector3} [a=(0,0,0)] - The first corner of the triangle. + * @param {Vector3} [b=(0,0,0)] - The second corner of the triangle. + * @param {Vector3} [c=(0,0,0)] - The third corner of the triangle. + */ + constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { -const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + /** + * The first corner of the triangle. + * + * @type {Vector3} + */ + this.a = a; -const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - -const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; + /** + * The second corner of the triangle. + * + * @type {Vector3} + */ + this.b = b; -const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + /** + * The third corner of the triangle. + * + * @type {Vector3} + */ + this.c = c; -const ShaderChunk = { - alphahash_fragment: alphahash_fragment, - alphahash_pars_fragment: alphahash_pars_fragment, - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - alphatest_pars_fragment: alphatest_pars_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - batching_pars_vertex: batching_pars_vertex, - batching_vertex: batching_vertex, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - iridescence_fragment: iridescence_fragment, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - colorspace_fragment: colorspace_fragment, - colorspace_pars_fragment: colorspace_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_common_pars_fragment: envmap_common_pars_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_physical_pars_fragment: envmap_physical_pars_fragment, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_fragment: lights_lambert_fragment, - lights_lambert_pars_fragment: lights_lambert_pars_fragment, - lights_pars_begin: lights_pars_begin, - lights_toon_fragment: lights_toon_fragment, - lights_toon_pars_fragment: lights_toon_pars_fragment, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_fragment_begin: lights_fragment_begin, - lights_fragment_maps: lights_fragment_maps, - lights_fragment_end: lights_fragment_end, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphinstance_vertex: morphinstance_vertex, - morphcolor_vertex: morphcolor_vertex, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment_begin: normal_fragment_begin, - normal_fragment_maps: normal_fragment_maps, - normal_pars_fragment: normal_pars_fragment, - normal_pars_vertex: normal_pars_vertex, - normal_vertex: normal_vertex, - normalmap_pars_fragment: normalmap_pars_fragment, - clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, - clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, - clearcoat_pars_fragment: clearcoat_pars_fragment, - iridescence_pars_fragment: iridescence_pars_fragment, - opaque_fragment: opaque_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - transmission_fragment: transmission_fragment, - transmission_pars_fragment: transmission_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - worldpos_vertex: worldpos_vertex, + } - background_vert: vertex$h, - background_frag: fragment$h, - backgroundCube_vert: vertex$g, - backgroundCube_frag: fragment$g, - cube_vert: vertex$f, - cube_frag: fragment$f, - depth_vert: vertex$e, - depth_frag: fragment$e, - distanceRGBA_vert: vertex$d, - distanceRGBA_frag: fragment$d, - equirect_vert: vertex$c, - equirect_frag: fragment$c, - linedashed_vert: vertex$b, - linedashed_frag: fragment$b, - meshbasic_vert: vertex$a, - meshbasic_frag: fragment$a, - meshlambert_vert: vertex$9, - meshlambert_frag: fragment$9, - meshmatcap_vert: vertex$8, - meshmatcap_frag: fragment$8, - meshnormal_vert: vertex$7, - meshnormal_frag: fragment$7, - meshphong_vert: vertex$6, - meshphong_frag: fragment$6, - meshphysical_vert: vertex$5, - meshphysical_frag: fragment$5, - meshtoon_vert: vertex$4, - meshtoon_frag: fragment$4, - points_vert: vertex$3, - points_frag: fragment$3, - shadow_vert: vertex$2, - shadow_frag: fragment$2, - sprite_vert: vertex$1, - sprite_frag: fragment$1 -}; + /** + * Computes the normal vector of a triangle. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ + static getNormal( a, b, c, target ) { -/** - * Uniforms library for shared webgl shaders - */ + target.subVectors( c, b ); + _v0$2.subVectors( a, b ); + target.cross( _v0$2 ); -const UniformsLib = { + const targetLengthSq = target.lengthSq(); + if ( targetLengthSq > 0 ) { - common: { + return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, + } - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, + return target.set( 0, 0, 0 ); - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + } - alphaTest: { value: 0 } + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ + static getBarycoord( point, a, b, c, target ) { - }, + // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - specularmap: { + _v0$2.subVectors( c, a ); + _v1$2.subVectors( b, a ); + _v2$1.subVectors( point, a ); - specularMap: { value: null }, - specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } + const dot00 = _v0$2.dot( _v0$2 ); + const dot01 = _v0$2.dot( _v1$2 ); + const dot02 = _v0$2.dot( _v2$1 ); + const dot11 = _v1$2.dot( _v1$2 ); + const dot12 = _v1$2.dot( _v2$1 ); - }, + const denom = ( dot00 * dot11 - dot01 * dot01 ); - envmap: { + // collinear or singular triangle + if ( denom === 0 ) { - envMap: { value: null }, - envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, - flipEnvMap: { value: - 1 }, - reflectivity: { value: 1.0 }, // basic, lambert, phong - ior: { value: 1.5 }, // physical - refractionRatio: { value: 0.98 }, // basic, lambert, phong + target.set( 0, 0, 0 ); + return null; - }, + } - aomap: { + const invDenom = 1 / denom; + const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; - aoMap: { value: null }, - aoMapIntensity: { value: 1 }, - aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } + // barycentric coordinates must always sum to 1 + return target.set( 1 - u - v, v, u ); - }, + } - lightmap: { + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ + static containsPoint( point, a, b, c ) { - lightMap: { value: null }, - lightMapIntensity: { value: 1 }, - lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } + // if the triangle is degenerate then we can't contain a point + if ( this.getBarycoord( point, a, b, c, _v3$1 ) === null ) { - }, + return false; - bumpmap: { + } - bumpMap: { value: null }, - bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - bumpScale: { value: 1 } + return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); - }, + } - normalmap: { + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} p1 - The first corner of the triangle. + * @param {Vector3} p2 - The second corner of the triangle. + * @param {Vector3} p3 - The third corner of the triangle. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ + static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { - normalMap: { value: null }, - normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } + if ( this.getBarycoord( point, p1, p2, p3, _v3$1 ) === null ) { - }, + target.x = 0; + target.y = 0; + if ( 'z' in target ) target.z = 0; + if ( 'w' in target ) target.w = 0; + return null; - displacementmap: { + } - displacementMap: { value: null }, - displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } + target.setScalar( 0 ); + target.addScaledVector( v1, _v3$1.x ); + target.addScaledVector( v2, _v3$1.y ); + target.addScaledVector( v3, _v3$1.z ); - }, + return target; - emissivemap: { + } - emissiveMap: { value: null }, - emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } + /** + * Computes the value barycentrically interpolated for the given attribute and indices. + * + * @param {BufferAttribute} attr - The attribute to interpolate. + * @param {number} i1 - Index of first vertex. + * @param {number} i2 - Index of second vertex. + * @param {number} i3 - Index of third vertex. + * @param {Vector3} barycoord - The barycoordinate value to use to interpolate. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The interpolated attribute value. + */ + static getInterpolatedAttribute( attr, i1, i2, i3, barycoord, target ) { - }, + _v40.setScalar( 0 ); + _v41.setScalar( 0 ); + _v42.setScalar( 0 ); - metalnessmap: { + _v40.fromBufferAttribute( attr, i1 ); + _v41.fromBufferAttribute( attr, i2 ); + _v42.fromBufferAttribute( attr, i3 ); - metalnessMap: { value: null }, - metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } + target.setScalar( 0 ); + target.addScaledVector( _v40, barycoord.x ); + target.addScaledVector( _v41, barycoord.y ); + target.addScaledVector( _v42, barycoord.z ); - }, + return target; - roughnessmap: { + } - roughnessMap: { value: null }, - roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ + static isFrontFacing( a, b, c, direction ) { - }, + _v0$2.subVectors( c, b ); + _v1$2.subVectors( a, b ); - gradientmap: { + // strictly front facing + return ( _v0$2.cross( _v1$2 ).dot( direction ) < 0 ) ? true : false; - gradientMap: { value: null } + } - }, + /** + * Sets the triangle's vertices by copying the given values. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + set( a, b, c ) { - fog: { + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } + return this; - }, + } - lights: { + /** + * Sets the triangle's vertices by copying the given array values. + * + * @param {Array} points - An array with 3D points. + * @param {number} i0 - The array index representing the first corner of the triangle. + * @param {number} i1 - The array index representing the second corner of the triangle. + * @param {number} i2 - The array index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + setFromPointsAndIndices( points, i0, i1, i2 ) { - ambientLightColor: { value: [] }, + this.a.copy( points[ i0 ] ); + this.b.copy( points[ i1 ] ); + this.c.copy( points[ i2 ] ); - lightProbe: { value: [] }, + return this; - directionalLights: { value: [], properties: { - direction: {}, - color: {} - } }, + } - directionalLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, + /** + * Sets the triangle's vertices by copying the given attribute values. + * + * @param {BufferAttribute} attribute - A buffer attribute with 3D points data. + * @param {number} i0 - The attribute index representing the first corner of the triangle. + * @param {number} i1 - The attribute index representing the second corner of the triangle. + * @param {number} i2 - The attribute index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + setFromAttributeAndIndices( attribute, i0, i1, i2 ) { - directionalShadowMap: { value: [] }, - directionalShadowMatrix: { value: [] }, + this.a.fromBufferAttribute( attribute, i0 ); + this.b.fromBufferAttribute( attribute, i1 ); + this.c.fromBufferAttribute( attribute, i2 ); - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {} - } }, + return this; - spotLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, + } - spotLightMap: { value: [] }, - spotShadowMap: { value: [] }, - spotLightMatrix: { value: [] }, + /** + * Returns a new triangle with copied values from this instance. + * + * @return {Triangle} A clone of this instance. + */ + clone() { - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {} - } }, + return new this.constructor().copy( this ); - pointLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, + } - pointShadowMap: { value: [] }, - pointShadowMatrix: { value: [] }, + /** + * Copies the values of the given triangle to this instance. + * + * @param {Triangle} triangle - The triangle to copy. + * @return {Triangle} A reference to this triangle. + */ + copy( triangle ) { - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } }, + return this; - ltc_1: { value: null }, - ltc_2: { value: null } + } - }, + /** + * Computes the area of the triangle. + * + * @return {number} The triangle's area. + */ + getArea() { - points: { + _v0$2.subVectors( this.c, this.b ); + _v1$2.subVectors( this.a, this.b ); - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaTest: { value: 0 }, - uvTransform: { value: /*@__PURE__*/ new Matrix3() } + return _v0$2.cross( _v1$2 ).length() * 0.5; - }, + } - sprite: { + /** + * Computes the midpoint of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's midpoint. + */ + getMidpoint( target ) { - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, - rotation: { value: 0.0 }, - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaTest: { value: 0 } + return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); } -}; - -const ShaderLib = { - - basic: { + /** + * Computes the normal of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ + getNormal( target ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), + return Triangle.getNormal( this.a, this.b, this.c, target ); - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag + } - }, + /** + * Computes a plane the triangle lies within. + * + * @param {Plane} target - The target vector that is used to store the method's result. + * @return {Plane} The plane the triangle lies within. + */ + getPlane( target ) { - lambert: { + return target.setFromCoplanarPoints( this.a, this.b, this.c ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), + } - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ + getBarycoord( point, target ) { - }, + return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); - phong: { + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, - shininess: { value: 30 } - } - ] ), + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ + getInterpolation( point, v1, v2, v3, target ) { - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag + return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); - }, + } - standard: { + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ + containsPoint( point ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - roughness: { value: 1.0 }, - metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } // temporary - } - ] ), + return Triangle.containsPoint( point, this.a, this.b, this.c ); - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag + } - }, + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ + isFrontFacing( direction ) { - toon: { + return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), + } - vertexShader: ShaderChunk.meshtoon_vert, - fragmentShader: ShaderChunk.meshtoon_frag + /** + * Returns `true` if this triangle intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this triangle intersects with the given box or not. + */ + intersectsBox( box ) { - }, + return box.intersectsTriangle( this ); - matcap: { + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - { - matcap: { value: null } - } - ] ), + /** + * Returns the closest point on the triangle to the given point. + * + * @param {Vector3} p - The point to compute the closest point for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the triangle. + */ + closestPointToPoint( p, target ) { - vertexShader: ShaderChunk.meshmatcap_vert, - fragmentShader: ShaderChunk.meshmatcap_frag + const a = this.a, b = this.b, c = this.c; + let v, w; - }, + // algorithm thanks to Real-Time Collision Detection by Christer Ericson, + // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., + // under the accompanying license; see chapter 5.1.5 for detailed explanation. + // basically, we're distinguishing which of the voronoi regions of the triangle + // the point lies in with the minimum amount of redundant computation. - points: { + _vab.subVectors( b, a ); + _vac.subVectors( c, a ); + _vap.subVectors( p, a ); + const d1 = _vab.dot( _vap ); + const d2 = _vac.dot( _vap ); + if ( d1 <= 0 && d2 <= 0 ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.points, - UniformsLib.fog - ] ), + // vertex region of A; barycentric coords (1, 0, 0) + return target.copy( a ); - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag + } - }, + _vbp.subVectors( p, b ); + const d3 = _vab.dot( _vbp ); + const d4 = _vac.dot( _vbp ); + if ( d3 >= 0 && d4 <= d3 ) { - dashed: { + // vertex region of B; barycentric coords (0, 1, 0) + return target.copy( b ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } - } - ] ), + } - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag + const vc = d1 * d4 - d3 * d2; + if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { - }, + v = d1 / ( d1 - d3 ); + // edge region of AB; barycentric coords (1-v, v, 0) + return target.copy( a ).addScaledVector( _vab, v ); - depth: { + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), + _vcp.subVectors( p, c ); + const d5 = _vab.dot( _vcp ); + const d6 = _vac.dot( _vcp ); + if ( d6 >= 0 && d5 <= d6 ) { - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag + // vertex region of C; barycentric coords (0, 0, 1) + return target.copy( c ); - }, + } - normal: { + const vb = d5 * d2 - d1 * d6; + if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } - } - ] ), + w = d2 / ( d2 - d6 ); + // edge region of AC; barycentric coords (1-w, 0, w) + return target.copy( a ).addScaledVector( _vac, w ); - vertexShader: ShaderChunk.meshnormal_vert, - fragmentShader: ShaderChunk.meshnormal_frag + } - }, + const va = d3 * d6 - d5 * d4; + if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { - sprite: { + _vbc.subVectors( c, b ); + w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); + // edge region of BC; barycentric coords (0, 1-w, w) + return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.sprite, - UniformsLib.fog - ] ), + } - vertexShader: ShaderChunk.sprite_vert, - fragmentShader: ShaderChunk.sprite_frag + // face region + const denom = 1 / ( va + vb + vc ); + // u = va * denom + v = vb * denom; + w = vc * denom; - }, + return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); - background: { + } - uniforms: { - uvTransform: { value: /*@__PURE__*/ new Matrix3() }, - t2D: { value: null }, - backgroundIntensity: { value: 1 } - }, + /** + * Returns `true` if this triangle is equal with the given one. + * + * @param {Triangle} triangle - The triangle to test for equality. + * @return {boolean} Whether this triangle is equal with the given one. + */ + equals( triangle ) { - vertexShader: ShaderChunk.background_vert, - fragmentShader: ShaderChunk.background_frag + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - }, + } - backgroundCube: { +} - uniforms: { - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - backgroundBlurriness: { value: 0 }, - backgroundIntensity: { value: 1 }, - backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } - }, +/** + * A material for drawing geometries in a simple shaded (flat or wireframe) way. + * + * This material is not affected by lights. + * + * @augments Material + */ +class MeshBasicMaterial extends Material { - vertexShader: ShaderChunk.backgroundCube_vert, - fragmentShader: ShaderChunk.backgroundCube_frag + /** + * Constructs a new mesh basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - }, + super(); - cube: { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshBasicMaterial = true; - uniforms: { - tCube: { value: null }, - tFlip: { value: - 1 }, - opacity: { value: 1.0 } - }, + this.type = 'MeshBasicMaterial'; - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - }, + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - equirect: { + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - uniforms: { - tEquirect: { value: null }, - }, + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - }, + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - distanceRGBA: { + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: /*@__PURE__*/ new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - vertexShader: ShaderChunk.distanceRGBA_vert, - fragmentShader: ShaderChunk.distanceRGBA_frag + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - }, + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - shadow: { + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; - } + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; -}; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; -ShaderLib.physical = { + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - uniforms: /*@__PURE__*/ mergeUniforms( [ - ShaderLib.standard.uniforms, - { - clearcoat: { value: 0 }, - clearcoatMap: { value: null }, - clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalMap: { value: null }, - clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, - clearcoatRoughness: { value: 0 }, - clearcoatRoughnessMap: { value: null }, - clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescence: { value: 0 }, - iridescenceMap: { value: null }, - iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescenceIOR: { value: 1.3 }, - iridescenceThicknessMinimum: { value: 100 }, - iridescenceThicknessMaximum: { value: 400 }, - iridescenceThicknessMap: { value: null }, - iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheen: { value: 0 }, - sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - sheenColorMap: { value: null }, - sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheenRoughness: { value: 1 }, - sheenRoughnessMap: { value: null }, - sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmission: { value: 0 }, - transmissionMap: { value: null }, - transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, - transmissionSamplerMap: { value: null }, - thickness: { value: 0 }, - thicknessMap: { value: null }, - thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - attenuationDistance: { value: 0 }, - attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, - specularColorMap: { value: null }, - specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - specularIntensity: { value: 1 }, - specularIntensityMap: { value: null }, - specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, - anisotropyMap: { value: null }, - anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - } - ] ), + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; -}; + this.setValues( parameters ); -const _rgb = { r: 0, b: 0, g: 0 }; -const _e1$1 = /*@__PURE__*/ new Euler(); -const _m1$1 = /*@__PURE__*/ new Matrix4(); + } -function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { + copy( source ) { - const clearColor = new Color( 0x000000 ); - let clearAlpha = alpha === true ? 0 : 1; + super.copy( source ); - let planeMesh; - let boxMesh; + this.color.copy( source.color ); - let currentBackground = null; - let currentBackgroundVersion = 0; - let currentTonemapping = null; + this.map = source.map; - function render( renderList, scene ) { + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - let forceClear = false; - let background = scene.isScene === true ? scene.background : null; + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - if ( background && background.isTexture ) { + this.specularMap = source.specularMap; - const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background - background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); + this.alphaMap = source.alphaMap; - } + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - if ( background === null ) { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - setClear( clearColor, clearAlpha ); + this.fog = source.fog; - } else if ( background && background.isColor ) { + return this; - setClear( background, 1 ); - forceClear = true; + } - } +} - const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); +const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); +const _ray$3 = /*@__PURE__*/ new Ray(); +const _sphere$5 = /*@__PURE__*/ new Sphere(); +const _sphereHitAt = /*@__PURE__*/ new Vector3(); - if ( environmentBlendMode === 'additive' ) { +const _vA$1 = /*@__PURE__*/ new Vector3(); +const _vB$1 = /*@__PURE__*/ new Vector3(); +const _vC$1 = /*@__PURE__*/ new Vector3(); - state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); +const _tempA = /*@__PURE__*/ new Vector3(); +const _morphA = /*@__PURE__*/ new Vector3(); - } else if ( environmentBlendMode === 'alpha-blend' ) { +const _intersectionPoint = /*@__PURE__*/ new Vector3(); +const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); - state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); +/** + * Class representing triangular polygon mesh based objects. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments Object3D + */ +class Mesh extends Object3D { - } + /** + * Constructs a new mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ + constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { - if ( renderer.autoClear || forceClear ) { + super(); - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMesh = true; - } + this.type = 'Mesh'; - if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { + /** + * The mesh geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - if ( boxMesh === undefined ) { + /** + * The mesh material. + * + * @type {Material|Array} + * @default MeshBasicMaterial + */ + this.material = material; - boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), - new ShaderMaterial( { - name: 'BackgroundCubeMaterial', - uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), - vertexShader: ShaderLib.backgroundCube.vertexShader, - fragmentShader: ShaderLib.backgroundCube.fragmentShader, - side: BackSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; - boxMesh.geometry.deleteAttribute( 'normal' ); - boxMesh.geometry.deleteAttribute( 'uv' ); + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { + /** + * The number of instances of this mesh. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; - this.matrixWorld.copyPosition( camera.matrixWorld ); + this.updateMorphTargets(); - }; + } - // add "envMap" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( boxMesh.material, 'envMap', { + copy( source, recursive ) { - get: function () { + super.copy( source, recursive ); - return this.uniforms.envMap.value; + if ( source.morphTargetInfluences !== undefined ) { - } + this.morphTargetInfluences = source.morphTargetInfluences.slice(); - } ); + } - objects.update( boxMesh ); + if ( source.morphTargetDictionary !== undefined ) { - } + this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - _e1$1.copy( scene.backgroundRotation ); + } - // accommodate left-handed frame - _e1$1.x *= - 1; _e1$1.y *= - 1; _e1$1.z *= - 1; + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; - if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { + return this; - // environment maps which are not cube render targets or PMREMs follow a different convention - _e1$1.y *= - 1; - _e1$1.z *= - 1; + } - } + /** + * Sets the values of {@link Mesh#morphTargetDictionary} and {@link Mesh#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { - boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; - boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; - boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( _e1$1 ) ); - boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; + const geometry = this.geometry; - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); - boxMesh.material.needsUpdate = true; + if ( keys.length > 0 ) { - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - } + if ( morphAttribute !== undefined ) { - boxMesh.layers.enableAll(); + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - // push to the pre-sorted opaque render list - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - } else if ( background && background.isTexture ) { + const name = morphAttribute[ m ].name || String( m ); - if ( planeMesh === undefined ) { + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; - planeMesh = new Mesh( - new PlaneGeometry( 2, 2 ), - new ShaderMaterial( { - name: 'BackgroundMaterial', - uniforms: cloneUniforms( ShaderLib.background.uniforms ), - vertexShader: ShaderLib.background.vertexShader, - fragmentShader: ShaderLib.background.fragmentShader, - side: FrontSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); + } - planeMesh.geometry.deleteAttribute( 'normal' ); + } - // add "map" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( planeMesh.material, 'map', { + } - get: function () { + } - return this.uniforms.t2D.value; + /** + * Returns the local-space position of the vertex at the given index, taking into + * account the current animation state of both morph targets and skinning. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * @return {Vector3} The vertex position in local space. + */ + getVertexPosition( index, target ) { - } + const geometry = this.geometry; + const position = geometry.attributes.position; + const morphPosition = geometry.morphAttributes.position; + const morphTargetsRelative = geometry.morphTargetsRelative; - } ); + target.fromBufferAttribute( position, index ); - objects.update( planeMesh ); + const morphInfluences = this.morphTargetInfluences; - } + if ( morphPosition && morphInfluences ) { - planeMesh.material.uniforms.t2D.value = background; - planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; + _morphA.set( 0, 0, 0 ); - if ( background.matrixAutoUpdate === true ) { + for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { - background.updateMatrix(); + const influence = morphInfluences[ i ]; + const morphAttribute = morphPosition[ i ]; - } + if ( influence === 0 ) continue; - planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); + _tempA.fromBufferAttribute( morphAttribute, index ); - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { + if ( morphTargetsRelative ) { - planeMesh.material.needsUpdate = true; + _morphA.addScaledVector( _tempA, influence ); - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; + } else { - } + _morphA.addScaledVector( _tempA.sub( target ), influence ); - planeMesh.layers.enableAll(); + } - // push to the pre-sorted opaque render list - renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); + } + + target.add( _morphA ); } + return target; + } - function setClear( color, alpha ) { + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { - color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); + const geometry = this.geometry; + const material = this.material; + const matrixWorld = this.matrixWorld; - state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); + if ( material === undefined ) return; - } + // test with bounding sphere in world space - return { + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - getClearColor: function () { + _sphere$5.copy( geometry.boundingSphere ); + _sphere$5.applyMatrix4( matrixWorld ); - return clearColor; + // check distance from ray origin to bounding sphere - }, - setClearColor: function ( color, alpha = 1 ) { + _ray$3.copy( raycaster.ray ).recast( raycaster.near ); - clearColor.set( color ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); + if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { - }, - getClearAlpha: function () { + if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; - return clearAlpha; + if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; - }, - setClearAlpha: function ( alpha ) { + } - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); + // convert ray to local space of mesh - }, - render: render + _inverseMatrix$3.copy( matrixWorld ).invert(); + _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); - }; + // test with bounding box in local space -} + if ( geometry.boundingBox !== null ) { -function WebGLBindingStates( gl, extensions, attributes, capabilities ) { + if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; - const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + } - const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); - const vaoAvailable = capabilities.isWebGL2 || extension !== null; + // test for intersections with geometry - const bindingStates = {}; + this._computeIntersections( raycaster, intersects, _ray$3 ); - const defaultState = createBindingState( null ); - let currentState = defaultState; - let forceUpdate = false; + } - function setup( object, material, program, geometry, index ) { + _computeIntersections( raycaster, intersects, rayLocalSpace ) { - let updateBuffers = false; + let intersection; - if ( vaoAvailable ) { + const geometry = this.geometry; + const material = this.material; - const state = getBindingState( geometry, program, material ); + const index = geometry.index; + const position = geometry.attributes.position; + const uv = geometry.attributes.uv; + const uv1 = geometry.attributes.uv1; + const normal = geometry.attributes.normal; + const groups = geometry.groups; + const drawRange = geometry.drawRange; - if ( currentState !== state ) { + if ( index !== null ) { - currentState = state; - bindVertexArrayObject( currentState.object ); + // indexed buffer geometry - } + if ( Array.isArray( material ) ) { - updateBuffers = needsUpdate( object, geometry, program, index ); + for ( let i = 0, il = groups.length; i < il; i ++ ) { - if ( updateBuffers ) saveCache( object, geometry, program, index ); + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - } else { + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - const wireframe = ( material.wireframe === true ); + for ( let j = start, jl = end; j < jl; j += 3 ) { - if ( currentState.geometry !== geometry.id || - currentState.program !== program.id || - currentState.wireframe !== wireframe ) { + const a = index.getX( j ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); - currentState.geometry = geometry.id; - currentState.program = program.id; - currentState.wireframe = wireframe; + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - updateBuffers = true; + if ( intersection ) { - } + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); - } + } - if ( index !== null ) { + } - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); + } - } + } else { - if ( updateBuffers || forceUpdate ) { + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - forceUpdate = false; + for ( let i = start, il = end; i < il; i += 3 ) { - setupVertexAttributes( object, material, program, geometry ); + const a = index.getX( i ); + const b = index.getX( i + 1 ); + const c = index.getX( i + 2 ); - if ( index !== null ) { + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); + if ( intersection ) { - } + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics + intersects.push( intersection ); - } + } - } + } - function createVertexArrayObject() { + } - if ( capabilities.isWebGL2 ) return gl.createVertexArray(); + } else if ( position !== undefined ) { - return extension.createVertexArrayOES(); + // non-indexed buffer geometry - } + if ( Array.isArray( material ) ) { - function bindVertexArrayObject( vao ) { + for ( let i = 0, il = groups.length; i < il; i ++ ) { - if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - return extension.bindVertexArrayOES( vao ); + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - } + for ( let j = start, jl = end; j < jl; j += 3 ) { - function deleteVertexArrayObject( vao ) { + const a = j; + const b = j + 1; + const c = j + 2; - if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - return extension.deleteVertexArrayOES( vao ); + if ( intersection ) { - } + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); - function getBindingState( geometry, program, material ) { + } - const wireframe = ( material.wireframe === true ); + } - let programMap = bindingStates[ geometry.id ]; + } - if ( programMap === undefined ) { + } else { - programMap = {}; - bindingStates[ geometry.id ] = programMap; + const start = Math.max( 0, drawRange.start ); + const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - } + for ( let i = start, il = end; i < il; i += 3 ) { - let stateMap = programMap[ program.id ]; + const a = i; + const b = i + 1; + const c = i + 2; - if ( stateMap === undefined ) { + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - stateMap = {}; - programMap[ program.id ] = stateMap; + if ( intersection ) { - } + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics + intersects.push( intersection ); - let state = stateMap[ wireframe ]; + } - if ( state === undefined ) { + } - state = createBindingState( createVertexArrayObject() ); - stateMap[ wireframe ] = state; + } } - return state; - } - function createBindingState( vao ) { +} - const newAttributes = []; - const enabledAttributes = []; - const attributeDivisors = []; +function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) { - for ( let i = 0; i < maxVertexAttributes; i ++ ) { + let intersect; - newAttributes[ i ] = 0; - enabledAttributes[ i ] = 0; - attributeDivisors[ i ] = 0; + if ( material.side === BackSide ) { - } + intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - return { + } else { - // for backward compatibility on non-VAO support browser - geometry: null, - program: null, - wireframe: false, + intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); - newAttributes: newAttributes, - enabledAttributes: enabledAttributes, - attributeDivisors: attributeDivisors, - object: vao, - attributes: {}, - index: null + } - }; + if ( intersect === null ) return null; - } + _intersectionPointWorld.copy( point ); + _intersectionPointWorld.applyMatrix4( object.matrixWorld ); - function needsUpdate( object, geometry, program, index ) { + const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); - const cachedAttributes = currentState.attributes; - const geometryAttributes = geometry.attributes; + if ( distance < raycaster.near || distance > raycaster.far ) return null; - let attributesNum = 0; + return { + distance: distance, + point: _intersectionPointWorld.clone(), + object: object + }; - const programAttributes = program.getAttributes(); +} - for ( const name in programAttributes ) { +function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { - const programAttribute = programAttributes[ name ]; + object.getVertexPosition( a, _vA$1 ); + object.getVertexPosition( b, _vB$1 ); + object.getVertexPosition( c, _vC$1 ); - if ( programAttribute.location >= 0 ) { + const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); - const cachedAttribute = cachedAttributes[ name ]; - let geometryAttribute = geometryAttributes[ name ]; + if ( intersection ) { - if ( geometryAttribute === undefined ) { + const barycoord = new Vector3(); + Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord ); - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; + if ( uv ) { - } + intersection.uv = Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new Vector2() ); - if ( cachedAttribute === undefined ) return true; + } - if ( cachedAttribute.attribute !== geometryAttribute ) return true; + if ( uv1 ) { - if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; + intersection.uv1 = Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new Vector2() ); - attributesNum ++; + } + + if ( normal ) { + + intersection.normal = Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new Vector3() ); + + if ( intersection.normal.dot( ray.direction ) > 0 ) { + + intersection.normal.multiplyScalar( -1 ); } } - if ( currentState.attributesNum !== attributesNum ) return true; + const face = { + a: a, + b: b, + c: c, + normal: new Vector3(), + materialIndex: 0 + }; - if ( currentState.index !== index ) return true; + Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); - return false; + intersection.face = face; + intersection.barycoord = barycoord; } - function saveCache( object, geometry, program, index ) { + return intersection; - const cache = {}; - const attributes = geometry.attributes; - let attributesNum = 0; +} - const programAttributes = program.getAttributes(); +var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; - for ( const name in programAttributes ) { +var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; - const programAttribute = programAttributes[ name ]; +var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - if ( programAttribute.location >= 0 ) { +var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - let attribute = attributes[ name ]; +var alphatest_fragment = "#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif"; - if ( attribute === undefined ) { +var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; - if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; +var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; - } +var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - const data = {}; - data.attribute = attribute; +var batching_pars_vertex = "#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec3 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 ).rgb;\n\t}\n#endif"; - if ( attribute && attribute.data ) { +var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif"; - data.data = attribute.data; +var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; - } +var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - cache[ name ] = data; +var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - attributesNum ++; +var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - } +var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - } +var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif"; - currentState.attributes = cache; - currentState.attributesNum = attributesNum; +var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - currentState.index = index; +var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - } +var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - function initAttributes() { +var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; - const newAttributes = currentState.newAttributes; +var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; - for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { +var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif"; - newAttributes[ i ] = 0; +var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif"; - } +var common$1 = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - } +var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - function enableAttribute( attribute ) { +var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; - enableAttributeAndDivisor( attribute, 0 ); +var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; - } +var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { +var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - const attributeDivisors = currentState.attributeDivisors; +var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - newAttributes[ attribute ] = 1; +var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - if ( enabledAttributes[ attribute ] === 0 ) { +var colorspace_pars_fragment = "vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; +var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; - } +var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { +var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); +var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; +var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - } +var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - } +var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - function disableUnusedAttributes() { +var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; +var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { +var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { +var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; +var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - } +var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - } +var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; - } +var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; - function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { +var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; - if ( integer === true ) { +var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - gl.vertexAttribIPointer( index, size, type, stride, offset ); +var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - } else { +var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); +var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; - } +var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - } +var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - function setupVertexAttributes( object, material, program, geometry ) { +var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { +var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; +var logdepthbuf_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - } +var logdepthbuf_pars_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - initAttributes(); +var logdepthbuf_pars_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - const geometryAttributes = geometry.attributes; +var logdepthbuf_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif"; - const programAttributes = program.getAttributes(); +var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; - const materialDefaultAttributeValues = material.defaultAttributeValues; +var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - for ( const name in programAttributes ) { +var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - const programAttribute = programAttributes[ name ]; +var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - if ( programAttribute.location >= 0 ) { +var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - let geometryAttribute = geometryAttributes[ name ]; +var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - if ( geometryAttribute === undefined ) { +var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; +var morphcolor_vertex = "#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - } +var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - if ( geometryAttribute !== undefined ) { +var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif"; - const normalized = geometryAttribute.normalized; - const size = geometryAttribute.itemSize; +var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - const attribute = attributes.get( geometryAttribute ); +var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; - // TODO Attribute may not be available on context restore +var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - if ( attribute === undefined ) continue; +var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - const buffer = attribute.buffer; - const type = attribute.type; - const bytesPerElement = attribute.bytesPerElement; +var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - // check for integer attributes (WebGL 2 only) +var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; - const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); +var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - if ( geometryAttribute.isInterleavedBufferAttribute ) { +var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; - const data = geometryAttribute.data; - const stride = data.stride; - const offset = geometryAttribute.offset; +var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - if ( data.isInstancedInterleavedBuffer ) { +var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); +var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - } +var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { +var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - geometry._maxInstanceCount = data.meshPerAttribute * data.count; +var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - } +var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - } else { +var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - enableAttribute( programAttribute.location + i ); +var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - } +var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\tfloat depth = unpackRGBAToDepth( texture2D( depths, uv ) );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\treturn step( depth, compare );\n\t\t#else\n\t\t\treturn step( compare, depth );\n\t\t#endif\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow( sampler2D shadow, vec2 uv, float compare ) {\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\tfloat hard_shadow = step( distribution.x, compare );\n\t\t#else\n\t\t\tfloat hard_shadow = step( compare, distribution.x );\n\t\t#endif\n\t\tif ( hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif"; - } +var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); +var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - stride * bytesPerElement, - ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, - integer - ); +var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - } +var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; - } else { +var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - if ( geometryAttribute.isInstancedBufferAttribute ) { +var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); +var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - } +var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { +var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; +var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - } +var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - } else { +var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - enableAttribute( programAttribute.location + i ); +var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - } +var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - } +const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); +const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - size * bytesPerElement, - ( size / programAttribute.locationSize ) * i * bytesPerElement, - integer - ); +const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - } +const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - } +const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - } else if ( materialDefaultAttributeValues !== undefined ) { +const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - const value = materialDefaultAttributeValues[ name ]; +const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\tfloat fragCoordZ = vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ];\n\t#else\n\t\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ] + 0.5;\n\t#endif\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}"; - if ( value !== undefined ) { +const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - switch ( value.length ) { +const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; - case 2: - gl.vertexAttrib2fv( programAttribute.location, value ); - break; +const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - case 3: - gl.vertexAttrib3fv( programAttribute.location, value ); - break; +const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - case 4: - gl.vertexAttrib4fv( programAttribute.location, value ); - break; +const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - default: - gl.vertexAttrib1fv( programAttribute.location, value ); +const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - disableUnusedAttributes(); +const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - function dispose() { +const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - reset(); +const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - for ( const geometryId in bindingStates ) { +const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - const programMap = bindingStates[ geometryId ]; +const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - for ( const programId in programMap ) { +const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - const stateMap = programMap[ programId ]; +const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - for ( const wireframe in stateMap ) { +const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - deleteVertexArrayObject( stateMap[ wireframe ].object ); +const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - delete stateMap[ wireframe ]; +const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - delete programMap[ programId ]; +const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - delete bindingStates[ geometryId ]; +const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const ShaderChunk = { + alphahash_fragment: alphahash_fragment, + alphahash_pars_fragment: alphahash_pars_fragment, + alphamap_fragment: alphamap_fragment, + alphamap_pars_fragment: alphamap_pars_fragment, + alphatest_fragment: alphatest_fragment, + alphatest_pars_fragment: alphatest_pars_fragment, + aomap_fragment: aomap_fragment, + aomap_pars_fragment: aomap_pars_fragment, + batching_pars_vertex: batching_pars_vertex, + batching_vertex: batching_vertex, + begin_vertex: begin_vertex, + beginnormal_vertex: beginnormal_vertex, + bsdfs: bsdfs, + iridescence_fragment: iridescence_fragment, + bumpmap_pars_fragment: bumpmap_pars_fragment, + clipping_planes_fragment: clipping_planes_fragment, + clipping_planes_pars_fragment: clipping_planes_pars_fragment, + clipping_planes_pars_vertex: clipping_planes_pars_vertex, + clipping_planes_vertex: clipping_planes_vertex, + color_fragment: color_fragment, + color_pars_fragment: color_pars_fragment, + color_pars_vertex: color_pars_vertex, + color_vertex: color_vertex, + common: common$1, + cube_uv_reflection_fragment: cube_uv_reflection_fragment, + defaultnormal_vertex: defaultnormal_vertex, + displacementmap_pars_vertex: displacementmap_pars_vertex, + displacementmap_vertex: displacementmap_vertex, + emissivemap_fragment: emissivemap_fragment, + emissivemap_pars_fragment: emissivemap_pars_fragment, + colorspace_fragment: colorspace_fragment, + colorspace_pars_fragment: colorspace_pars_fragment, + envmap_fragment: envmap_fragment, + envmap_common_pars_fragment: envmap_common_pars_fragment, + envmap_pars_fragment: envmap_pars_fragment, + envmap_pars_vertex: envmap_pars_vertex, + envmap_physical_pars_fragment: envmap_physical_pars_fragment, + envmap_vertex: envmap_vertex, + fog_vertex: fog_vertex, + fog_pars_vertex: fog_pars_vertex, + fog_fragment: fog_fragment, + fog_pars_fragment: fog_pars_fragment, + gradientmap_pars_fragment: gradientmap_pars_fragment, + lightmap_pars_fragment: lightmap_pars_fragment, + lights_lambert_fragment: lights_lambert_fragment, + lights_lambert_pars_fragment: lights_lambert_pars_fragment, + lights_pars_begin: lights_pars_begin, + lights_toon_fragment: lights_toon_fragment, + lights_toon_pars_fragment: lights_toon_pars_fragment, + lights_phong_fragment: lights_phong_fragment, + lights_phong_pars_fragment: lights_phong_pars_fragment, + lights_physical_fragment: lights_physical_fragment, + lights_physical_pars_fragment: lights_physical_pars_fragment, + lights_fragment_begin: lights_fragment_begin, + lights_fragment_maps: lights_fragment_maps, + lights_fragment_end: lights_fragment_end, + logdepthbuf_fragment: logdepthbuf_fragment, + logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, + logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, + logdepthbuf_vertex: logdepthbuf_vertex, + map_fragment: map_fragment, + map_pars_fragment: map_pars_fragment, + map_particle_fragment: map_particle_fragment, + map_particle_pars_fragment: map_particle_pars_fragment, + metalnessmap_fragment: metalnessmap_fragment, + metalnessmap_pars_fragment: metalnessmap_pars_fragment, + morphinstance_vertex: morphinstance_vertex, + morphcolor_vertex: morphcolor_vertex, + morphnormal_vertex: morphnormal_vertex, + morphtarget_pars_vertex: morphtarget_pars_vertex, + morphtarget_vertex: morphtarget_vertex, + normal_fragment_begin: normal_fragment_begin, + normal_fragment_maps: normal_fragment_maps, + normal_pars_fragment: normal_pars_fragment, + normal_pars_vertex: normal_pars_vertex, + normal_vertex: normal_vertex, + normalmap_pars_fragment: normalmap_pars_fragment, + clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, + clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, + clearcoat_pars_fragment: clearcoat_pars_fragment, + iridescence_pars_fragment: iridescence_pars_fragment, + opaque_fragment: opaque_fragment, + packing: packing, + premultiplied_alpha_fragment: premultiplied_alpha_fragment, + project_vertex: project_vertex, + dithering_fragment: dithering_fragment, + dithering_pars_fragment: dithering_pars_fragment, + roughnessmap_fragment: roughnessmap_fragment, + roughnessmap_pars_fragment: roughnessmap_pars_fragment, + shadowmap_pars_fragment: shadowmap_pars_fragment, + shadowmap_pars_vertex: shadowmap_pars_vertex, + shadowmap_vertex: shadowmap_vertex, + shadowmask_pars_fragment: shadowmask_pars_fragment, + skinbase_vertex: skinbase_vertex, + skinning_pars_vertex: skinning_pars_vertex, + skinning_vertex: skinning_vertex, + skinnormal_vertex: skinnormal_vertex, + specularmap_fragment: specularmap_fragment, + specularmap_pars_fragment: specularmap_pars_fragment, + tonemapping_fragment: tonemapping_fragment, + tonemapping_pars_fragment: tonemapping_pars_fragment, + transmission_fragment: transmission_fragment, + transmission_pars_fragment: transmission_pars_fragment, + uv_pars_fragment: uv_pars_fragment, + uv_pars_vertex: uv_pars_vertex, + uv_vertex: uv_vertex, + worldpos_vertex: worldpos_vertex, - } + background_vert: vertex$h, + background_frag: fragment$h, + backgroundCube_vert: vertex$g, + backgroundCube_frag: fragment$g, + cube_vert: vertex$f, + cube_frag: fragment$f, + depth_vert: vertex$e, + depth_frag: fragment$e, + distanceRGBA_vert: vertex$d, + distanceRGBA_frag: fragment$d, + equirect_vert: vertex$c, + equirect_frag: fragment$c, + linedashed_vert: vertex$b, + linedashed_frag: fragment$b, + meshbasic_vert: vertex$a, + meshbasic_frag: fragment$a, + meshlambert_vert: vertex$9, + meshlambert_frag: fragment$9, + meshmatcap_vert: vertex$8, + meshmatcap_frag: fragment$8, + meshnormal_vert: vertex$7, + meshnormal_frag: fragment$7, + meshphong_vert: vertex$6, + meshphong_frag: fragment$6, + meshphysical_vert: vertex$5, + meshphysical_frag: fragment$5, + meshtoon_vert: vertex$4, + meshtoon_frag: fragment$4, + points_vert: vertex$3, + points_frag: fragment$3, + shadow_vert: vertex$2, + shadow_frag: fragment$2, + sprite_vert: vertex$1, + sprite_frag: fragment$1 +}; - function releaseStatesOfGeometry( geometry ) { +// Uniforms library for shared webgl shaders +const UniformsLib = { - if ( bindingStates[ geometry.id ] === undefined ) return; + common: { - const programMap = bindingStates[ geometry.id ]; + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, - for ( const programId in programMap ) { + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - const stateMap = programMap[ programId ]; + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - for ( const wireframe in stateMap ) { + alphaTest: { value: 0 } - deleteVertexArrayObject( stateMap[ wireframe ].object ); + }, - delete stateMap[ wireframe ]; + specularmap: { - } + specularMap: { value: null }, + specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - delete programMap[ programId ]; + }, - } + envmap: { - delete bindingStates[ geometry.id ]; + envMap: { value: null }, + envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, + flipEnvMap: { value: -1 }, + reflectivity: { value: 1.0 }, // basic, lambert, phong + ior: { value: 1.5 }, // physical + refractionRatio: { value: 0.98 }, // basic, lambert, phong - } + }, - function releaseStatesOfProgram( program ) { + aomap: { - for ( const geometryId in bindingStates ) { + aoMap: { value: null }, + aoMapIntensity: { value: 1 }, + aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - const programMap = bindingStates[ geometryId ]; + }, - if ( programMap[ program.id ] === undefined ) continue; + lightmap: { - const stateMap = programMap[ program.id ]; + lightMap: { value: null }, + lightMapIntensity: { value: 1 }, + lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } - for ( const wireframe in stateMap ) { + }, - deleteVertexArrayObject( stateMap[ wireframe ].object ); + bumpmap: { - delete stateMap[ wireframe ]; + bumpMap: { value: null }, + bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + bumpScale: { value: 1 } - } + }, - delete programMap[ program.id ]; + normalmap: { - } + normalMap: { value: null }, + normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - } + }, - function reset() { + displacementmap: { - resetDefaultState(); - forceUpdate = true; + displacementMap: { value: null }, + displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + displacementScale: { value: 1 }, + displacementBias: { value: 0 } - if ( currentState === defaultState ) return; + }, - currentState = defaultState; - bindVertexArrayObject( currentState.object ); + emissivemap: { - } + emissiveMap: { value: null }, + emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - // for backward-compatibility + }, - function resetDefaultState() { + metalnessmap: { - defaultState.geometry = null; - defaultState.program = null; - defaultState.wireframe = false; + metalnessMap: { value: null }, + metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - } + }, - return { + roughnessmap: { - setup: setup, - reset: reset, - resetDefaultState: resetDefaultState, - dispose: dispose, - releaseStatesOfGeometry: releaseStatesOfGeometry, - releaseStatesOfProgram: releaseStatesOfProgram, + roughnessMap: { value: null }, + roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - initAttributes: initAttributes, - enableAttribute: enableAttribute, - disableUnusedAttributes: disableUnusedAttributes + }, - }; + gradientmap: { -} + gradientMap: { value: null } -function WebGLBufferRenderer( gl, extensions, info, capabilities ) { + }, - const isWebGL2 = capabilities.isWebGL2; + fog: { - let mode; + fogDensity: { value: 0.00025 }, + fogNear: { value: 1 }, + fogFar: { value: 2000 }, + fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - function setMode( value ) { + }, - mode = value; + lights: { - } + ambientLightColor: { value: [] }, - function render( start, count ) { + lightProbe: { value: [] }, - gl.drawArrays( mode, start, count ); + directionalLights: { value: [], properties: { + direction: {}, + color: {} + } }, - info.update( count, mode, 1 ); + directionalLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, - } + directionalShadowMap: { value: [] }, + directionalShadowMatrix: { value: [] }, - function renderInstances( start, count, primcount ) { + spotLights: { value: [], properties: { + color: {}, + position: {}, + direction: {}, + distance: {}, + coneCos: {}, + penumbraCos: {}, + decay: {} + } }, - if ( primcount === 0 ) return; + spotLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, + + spotLightMap: { value: [] }, + spotShadowMap: { value: [] }, + spotLightMatrix: { value: [] }, - let extension, methodName; + pointLights: { value: [], properties: { + color: {}, + position: {}, + decay: {}, + distance: {} + } }, - if ( isWebGL2 ) { + pointLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {}, + shadowCameraNear: {}, + shadowCameraFar: {} + } }, - extension = gl; - methodName = 'drawArraysInstanced'; + pointShadowMap: { value: [] }, + pointShadowMatrix: { value: [] }, - } else { + hemisphereLights: { value: [], properties: { + direction: {}, + skyColor: {}, + groundColor: {} + } }, - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawArraysInstancedANGLE'; + // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src + rectAreaLights: { value: [], properties: { + color: {}, + position: {}, + width: {}, + height: {} + } }, - if ( extension === null ) { + ltc_1: { value: null }, + ltc_2: { value: null } - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + }, - } + points: { - } + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + size: { value: 1.0 }, + scale: { value: 1.0 }, + map: { value: null }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 }, + uvTransform: { value: /*@__PURE__*/ new Matrix3() } + + }, - extension[ methodName ]( mode, start, count, primcount ); + sprite: { - info.update( count, mode, primcount ); + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, + rotation: { value: 0.0 }, + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 } } - function renderMultiDraw( starts, counts, drawCount ) { +}; - if ( drawCount === 0 ) return; +const ShaderLib = { - const extension = extensions.get( 'WEBGL_multi_draw' ); - if ( extension === null ) { - - for ( let i = 0; i < drawCount; i ++ ) { - - this.render( starts[ i ], counts[ i ] ); - - } + basic: { - } else { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.fog + ] ), - extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); + vertexShader: ShaderChunk.meshbasic_vert, + fragmentShader: ShaderChunk.meshbasic_frag - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { + }, - elementCount += counts[ i ]; + lambert: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } + ] ), - info.update( elementCount, mode, 1 ); + vertexShader: ShaderChunk.meshlambert_vert, + fragmentShader: ShaderChunk.meshlambert_frag - } + }, - } + phong: { - // + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, + shininess: { value: 30 } + } + ] ), - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; - this.renderMultiDraw = renderMultiDraw; + vertexShader: ShaderChunk.meshphong_vert, + fragmentShader: ShaderChunk.meshphong_frag -} + }, -function WebGLCapabilities( gl, extensions, parameters ) { + standard: { - let maxAnisotropy; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.roughnessmap, + UniformsLib.metalnessmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + roughness: { value: 1.0 }, + metalness: { value: 0.0 }, + envMapIntensity: { value: 1 } + } + ] ), - function getMaxAnisotropy() { + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag - if ( maxAnisotropy !== undefined ) return maxAnisotropy; + }, - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + toon: { - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.gradientmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } + } + ] ), - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); + vertexShader: ShaderChunk.meshtoon_vert, + fragmentShader: ShaderChunk.meshtoon_frag - } else { + }, - maxAnisotropy = 0; + matcap: { - } + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + { + matcap: { value: null } + } + ] ), - return maxAnisotropy; + vertexShader: ShaderChunk.meshmatcap_vert, + fragmentShader: ShaderChunk.meshmatcap_frag - } + }, - function getMaxPrecision( precision ) { + points: { - if ( precision === 'highp' ) { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.points, + UniformsLib.fog + ] ), - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { + vertexShader: ShaderChunk.points_vert, + fragmentShader: ShaderChunk.points_frag - return 'highp'; + }, + dashed: { + + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.fog, + { + scale: { value: 1 }, + dashSize: { value: 1 }, + totalSize: { value: 2 } } + ] ), - precision = 'mediump'; + vertexShader: ShaderChunk.linedashed_vert, + fragmentShader: ShaderChunk.linedashed_frag - } + }, - if ( precision === 'mediump' ) { + depth: { - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap + ] ), - return 'mediump'; + vertexShader: ShaderChunk.depth_vert, + fragmentShader: ShaderChunk.depth_frag - } + }, - } + normal: { - return 'lowp'; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + { + opacity: { value: 1.0 } + } + ] ), - } + vertexShader: ShaderChunk.meshnormal_vert, + fragmentShader: ShaderChunk.meshnormal_frag - const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; + }, - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - const maxPrecision = getMaxPrecision( precision ); + sprite: { - if ( maxPrecision !== precision ) { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.sprite, + UniformsLib.fog + ] ), - console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; + vertexShader: ShaderChunk.sprite_vert, + fragmentShader: ShaderChunk.sprite_frag - } + }, - const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); + background: { - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + uniforms: { + uvTransform: { value: /*@__PURE__*/ new Matrix3() }, + t2D: { value: null }, + backgroundIntensity: { value: 1 } + }, - const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); + vertexShader: ShaderChunk.background_vert, + fragmentShader: ShaderChunk.background_frag - const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); + }, - const vertexTextures = maxVertexTextures > 0; - const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); - const floatVertexTextures = vertexTextures && floatFragmentTextures; + backgroundCube: { - const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; + uniforms: { + envMap: { value: null }, + flipEnvMap: { value: -1 }, + backgroundBlurriness: { value: 0 }, + backgroundIntensity: { value: 1 }, + backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } + }, - return { + vertexShader: ShaderChunk.backgroundCube_vert, + fragmentShader: ShaderChunk.backgroundCube_frag - isWebGL2: isWebGL2, + }, - drawBuffers: drawBuffers, + cube: { - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, + uniforms: { + tCube: { value: null }, + tFlip: { value: -1 }, + opacity: { value: 1.0 } + }, - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, + vertexShader: ShaderChunk.cube_vert, + fragmentShader: ShaderChunk.cube_frag - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, + }, - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, + equirect: { - vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures, + uniforms: { + tEquirect: { value: null }, + }, - maxSamples: maxSamples + vertexShader: ShaderChunk.equirect_vert, + fragmentShader: ShaderChunk.equirect_frag - }; + }, -} + distanceRGBA: { -function WebGLClipping( properties ) { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap, + { + referencePosition: { value: /*@__PURE__*/ new Vector3() }, + nearDistance: { value: 1 }, + farDistance: { value: 1000 } + } + ] ), - const scope = this; + vertexShader: ShaderChunk.distanceRGBA_vert, + fragmentShader: ShaderChunk.distanceRGBA_frag - let globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false; + }, - const plane = new Plane(), - viewNormalMatrix = new Matrix3(), + shadow: { - uniform = { value: null, needsUpdate: false }; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.lights, + UniformsLib.fog, + { + color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, + opacity: { value: 1.0 } + }, + ] ), - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; + vertexShader: ShaderChunk.shadow_vert, + fragmentShader: ShaderChunk.shadow_frag - this.init = function ( planes, enableLocalClipping ) { + } - const enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; +}; - localClippingEnabled = enableLocalClipping; +ShaderLib.physical = { - numGlobalPlanes = planes.length; + uniforms: /*@__PURE__*/ mergeUniforms( [ + ShaderLib.standard.uniforms, + { + clearcoat: { value: 0 }, + clearcoatMap: { value: null }, + clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalMap: { value: null }, + clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, + clearcoatRoughness: { value: 0 }, + clearcoatRoughnessMap: { value: null }, + clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + dispersion: { value: 0 }, + iridescence: { value: 0 }, + iridescenceMap: { value: null }, + iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + iridescenceIOR: { value: 1.3 }, + iridescenceThicknessMinimum: { value: 100 }, + iridescenceThicknessMaximum: { value: 400 }, + iridescenceThicknessMap: { value: null }, + iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheen: { value: 0 }, + sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + sheenColorMap: { value: null }, + sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheenRoughness: { value: 1 }, + sheenRoughnessMap: { value: null }, + sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmission: { value: 0 }, + transmissionMap: { value: null }, + transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, + transmissionSamplerMap: { value: null }, + thickness: { value: 0 }, + thicknessMap: { value: null }, + thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + attenuationDistance: { value: 0 }, + attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, + specularColorMap: { value: null }, + specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + specularIntensity: { value: 1 }, + specularIntensityMap: { value: null }, + specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, + anisotropyMap: { value: null }, + anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + } + ] ), - return enabled; + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag - }; +}; - this.beginShadows = function () { +const _rgb = { r: 0, b: 0, g: 0 }; +const _e1$1 = /*@__PURE__*/ new Euler(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); - renderingShadows = true; - projectPlanes( null ); +function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { - }; + const clearColor = new Color( 0x000000 ); + let clearAlpha = alpha === true ? 0 : 1; - this.endShadows = function () { + let planeMesh; + let boxMesh; - renderingShadows = false; + let currentBackground = null; + let currentBackgroundVersion = 0; + let currentTonemapping = null; - }; + function getBackground( scene ) { - this.setGlobalState = function ( planes, camera ) { + let background = scene.isScene === true ? scene.background : null; - globalState = projectPlanes( planes, camera, 0 ); + if ( background && background.isTexture ) { - }; + const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background + background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); - this.setState = function ( material, camera, useCache ) { + } - const planes = material.clippingPlanes, - clipIntersection = material.clipIntersection, - clipShadows = material.clipShadows; + return background; - const materialProperties = properties.get( material ); + } - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { + function render( scene ) { - // there's no local clipping + let forceClear = false; + const background = getBackground( scene ); - if ( renderingShadows ) { + if ( background === null ) { - // there's no global clipping + setClear( clearColor, clearAlpha ); - projectPlanes( null ); + } else if ( background && background.isColor ) { - } else { + setClear( background, 1 ); + forceClear = true; - resetGlobalState(); + } - } + const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); - } else { + if ( environmentBlendMode === 'additive' ) { - const nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4; + state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - let dstArray = materialProperties.clippingState || null; + } else if ( environmentBlendMode === 'alpha-blend' ) { - uniform.value = dstArray; // ensure unique state + state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - dstArray = projectPlanes( planes, camera, lGlobal, useCache ); + } - for ( let i = 0; i !== lGlobal; ++ i ) { + if ( renderer.autoClear || forceClear ) { - dstArray[ i ] = globalState[ i ]; + // buffers might not be writable which is required to ensure a correct clear - } + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - materialProperties.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); } + } - }; - - function resetGlobalState() { + function addToRenderList( renderList, scene ) { - if ( uniform.value !== globalState ) { + const background = getBackground( scene ); - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; + if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - } + if ( boxMesh === undefined ) { - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; + boxMesh = new Mesh( + new BoxGeometry( 1, 1, 1 ), + new ShaderMaterial( { + name: 'BackgroundCubeMaterial', + uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), + vertexShader: ShaderLib.backgroundCube.vertexShader, + fragmentShader: ShaderLib.backgroundCube.fragmentShader, + side: BackSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - } + boxMesh.geometry.deleteAttribute( 'normal' ); + boxMesh.geometry.deleteAttribute( 'uv' ); - function projectPlanes( planes, camera, dstOffset, skipTransform ) { + boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - const nPlanes = planes !== null ? planes.length : 0; - let dstArray = null; + this.matrixWorld.copyPosition( camera.matrixWorld ); - if ( nPlanes !== 0 ) { + }; - dstArray = uniform.value; + // add "envMap" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( boxMesh.material, 'envMap', { - if ( skipTransform !== true || dstArray === null ) { + get: function () { - const flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; + return this.uniforms.envMap.value; - viewNormalMatrix.getNormalMatrix( viewMatrix ); + } - if ( dstArray === null || dstArray.length < flatSize ) { + } ); - dstArray = new Float32Array( flatSize ); + objects.update( boxMesh ); - } + } - for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { + _e1$1.copy( scene.backgroundRotation ); - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); + // accommodate left-handed frame + _e1$1.x *= -1; _e1$1.y *= -1; _e1$1.z *= -1; - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; + if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { - } + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1$1.y *= -1; + _e1$1.z *= -1; } - uniform.value = dstArray; - uniform.needsUpdate = true; - - } + boxMesh.material.uniforms.envMap.value = background; + boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? -1 : 1; + boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; + boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( _e1$1 ) ); + boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - scope.numPlanes = nPlanes; - scope.numIntersection = 0; - - return dstArray; + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - } + boxMesh.material.needsUpdate = true; -} + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; -class Camera extends Object3D { + } - constructor() { + boxMesh.layers.enableAll(); - super(); + // push to the pre-sorted opaque render list + renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - this.isCamera = true; + } else if ( background && background.isTexture ) { - this.type = 'Camera'; + if ( planeMesh === undefined ) { - this.matrixWorldInverse = new Matrix4(); + planeMesh = new Mesh( + new PlaneGeometry( 2, 2 ), + new ShaderMaterial( { + name: 'BackgroundMaterial', + uniforms: cloneUniforms( ShaderLib.background.uniforms ), + vertexShader: ShaderLib.background.vertexShader, + fragmentShader: ShaderLib.background.fragmentShader, + side: FrontSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - this.projectionMatrix = new Matrix4(); - this.projectionMatrixInverse = new Matrix4(); + planeMesh.geometry.deleteAttribute( 'normal' ); - this.coordinateSystem = WebGLCoordinateSystem; + // add "map" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( planeMesh.material, 'map', { - } + get: function () { - copy( source, recursive ) { + return this.uniforms.t2D.value; - super.copy( source, recursive ); + } - this.matrixWorldInverse.copy( source.matrixWorldInverse ); + } ); - this.projectionMatrix.copy( source.projectionMatrix ); - this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); + objects.update( planeMesh ); - this.coordinateSystem = source.coordinateSystem; + } - return this; + planeMesh.material.uniforms.t2D.value = background; + planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - } + if ( background.matrixAutoUpdate === true ) { - getWorldDirection( target ) { + background.updateMatrix(); - return super.getWorldDirection( target ).negate(); + } - } + planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - updateMatrixWorld( force ) { + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - super.updateMatrixWorld( force ); + planeMesh.material.needsUpdate = true; - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; - } + } - updateWorldMatrix( updateParents, updateChildren ) { + planeMesh.layers.enableAll(); - super.updateWorldMatrix( updateParents, updateChildren ); + // push to the pre-sorted opaque render list + renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + } } - clone() { + function setClear( color, alpha ) { - return new this.constructor().copy( this ); + color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); + + state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); } -} + function dispose() { -const _v3 = /*@__PURE__*/ new Vector3(); -const _minTarget = /*@__PURE__*/ new Vector2(); -const _maxTarget = /*@__PURE__*/ new Vector2(); + if ( boxMesh !== undefined ) { + boxMesh.geometry.dispose(); + boxMesh.material.dispose(); -class PerspectiveCamera extends Camera { + boxMesh = undefined; - constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { + } - super(); + if ( planeMesh !== undefined ) { - this.isPerspectiveCamera = true; + planeMesh.geometry.dispose(); + planeMesh.material.dispose(); - this.type = 'PerspectiveCamera'; + planeMesh = undefined; - this.fov = fov; - this.zoom = 1; + } - this.near = near; - this.far = far; - this.focus = 10; + } - this.aspect = aspect; - this.view = null; + return { - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) + getClearColor: function () { - this.updateProjectionMatrix(); + return clearColor; - } + }, + setClearColor: function ( color, alpha = 1 ) { - copy( source, recursive ) { + clearColor.set( color ); + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - super.copy( source, recursive ); + }, + getClearAlpha: function () { - this.fov = source.fov; - this.zoom = source.zoom; + return clearAlpha; - this.near = source.near; - this.far = source.far; - this.focus = source.focus; + }, + setClearAlpha: function ( alpha ) { - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; + }, + render: render, + addToRenderList: addToRenderList, + dispose: dispose - return this; + }; - } +} - /** - * Sets the FOV by focal length in respect to the current .filmGauge. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * Values for focal length and film gauge must have the same unit. - */ - setFocalLength( focalLength ) { +function WebGLBindingStates( gl, attributes ) { - /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ - const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; + const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); + const bindingStates = {}; - } + const defaultState = createBindingState( null ); + let currentState = defaultState; + let forceUpdate = false; - /** - * Calculates the focal length from the current .fov and .filmGauge. - */ - getFocalLength() { + function setup( object, material, program, geometry, index ) { - const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); + let updateBuffers = false; - return 0.5 * this.getFilmHeight() / vExtentSlope; + const state = getBindingState( geometry, program, material ); - } + if ( currentState !== state ) { - getEffectiveFOV() { + currentState = state; + bindVertexArrayObject( currentState.object ); - return RAD2DEG * 2 * Math.atan( - Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); + } - } + updateBuffers = needsUpdate( object, geometry, program, index ); - getFilmWidth() { + if ( updateBuffers ) saveCache( object, geometry, program, index ); - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); + if ( index !== null ) { - } + attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - getFilmHeight() { + } - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); + if ( updateBuffers || forceUpdate ) { - } + forceUpdate = false; - /** - * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. - * Sets minTarget and maxTarget to the coordinates of the lower-left and upper-right corners of the view rectangle. - */ - getViewBounds( distance, minTarget, maxTarget ) { + setupVertexAttributes( object, material, program, geometry ); - _v3.set( - 1, - 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); + if ( index !== null ) { - minTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); - _v3.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); + } - maxTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); + } } - /** - * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. - * Copies the result into the target Vector2, where x is width and y is height. - */ - getViewSize( distance, target ) { - - this.getViewBounds( distance, _minTarget, _maxTarget ); + function createVertexArrayObject() { - return target.subVectors( _maxTarget, _minTarget ); + return gl.createVertexArray(); } - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - * - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - * - * then for each monitor you would call it like this - * - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; - * - * --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * - * Note there is no reason monitors have to be the same size or in a grid. - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + function bindVertexArrayObject( vao ) { - this.aspect = fullWidth / fullHeight; + return gl.bindVertexArray( vao ); - if ( this.view === null ) { + } - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; + function deleteVertexArrayObject( vao ) { - } + return gl.deleteVertexArray( vao ); - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; + } - this.updateProjectionMatrix(); + function getBindingState( geometry, program, material ) { - } + const wireframe = ( material.wireframe === true ); - clearViewOffset() { + let programMap = bindingStates[ geometry.id ]; - if ( this.view !== null ) { + if ( programMap === undefined ) { - this.view.enabled = false; + programMap = {}; + bindingStates[ geometry.id ] = programMap; } - this.updateProjectionMatrix(); + let stateMap = programMap[ program.id ]; - } + if ( stateMap === undefined ) { - updateProjectionMatrix() { + stateMap = {}; + programMap[ program.id ] = stateMap; - const near = this.near; - let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; - let height = 2 * top; - let width = this.aspect * height; - let left = - 0.5 * width; - const view = this.view; + } - if ( this.view !== null && this.view.enabled ) { + let state = stateMap[ wireframe ]; - const fullWidth = view.fullWidth, - fullHeight = view.fullHeight; + if ( state === undefined ) { - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; + state = createBindingState( createVertexArrayObject() ); + stateMap[ wireframe ] = state; } - const skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + return state; } - toJSON( meta ) { + function createBindingState( vao ) { - const data = super.toJSON( meta ); + const newAttributes = []; + const enabledAttributes = []; + const attributeDivisors = []; - data.object.fov = this.fov; - data.object.zoom = this.zoom; + for ( let i = 0; i < maxVertexAttributes; i ++ ) { - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; + newAttributes[ i ] = 0; + enabledAttributes[ i ] = 0; + attributeDivisors[ i ] = 0; - data.object.aspect = this.aspect; + } - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + return { - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; + // for backward compatibility on non-VAO support browser + geometry: null, + program: null, + wireframe: false, - return data; + newAttributes: newAttributes, + enabledAttributes: enabledAttributes, + attributeDivisors: attributeDivisors, + object: vao, + attributes: {}, + index: null - } + }; -} + } -const fov = - 90; // negative fov is not an error -const aspect = 1; + function needsUpdate( object, geometry, program, index ) { -class CubeCamera extends Object3D { + const cachedAttributes = currentState.attributes; + const geometryAttributes = geometry.attributes; - constructor( near, far, renderTarget ) { + let attributesNum = 0; - super(); + const programAttributes = program.getAttributes(); - this.type = 'CubeCamera'; + for ( const name in programAttributes ) { - this.renderTarget = renderTarget; - this.coordinateSystem = null; - this.activeMipmapLevel = 0; + const programAttribute = programAttributes[ name ]; - const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.layers = this.layers; - this.add( cameraPX ); + if ( programAttribute.location >= 0 ) { - const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.layers = this.layers; - this.add( cameraNX ); + const cachedAttribute = cachedAttributes[ name ]; + let geometryAttribute = geometryAttributes[ name ]; - const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.layers = this.layers; - this.add( cameraPY ); + if ( geometryAttribute === undefined ) { - const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.layers = this.layers; - this.add( cameraNY ); + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.layers = this.layers; - this.add( cameraPZ ); + } - const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.layers = this.layers; - this.add( cameraNZ ); + if ( cachedAttribute === undefined ) return true; - } + if ( cachedAttribute.attribute !== geometryAttribute ) return true; - updateCoordinateSystem() { + if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - const coordinateSystem = this.coordinateSystem; + attributesNum ++; - const cameras = this.children.concat(); + } - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; + } - for ( const camera of cameras ) this.remove( camera ); + if ( currentState.attributesNum !== attributesNum ) return true; - if ( coordinateSystem === WebGLCoordinateSystem ) { + if ( currentState.index !== index ) return true; - cameraPX.up.set( 0, 1, 0 ); - cameraPX.lookAt( 1, 0, 0 ); + return false; - cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( - 1, 0, 0 ); + } - cameraPY.up.set( 0, 0, - 1 ); - cameraPY.lookAt( 0, 1, 0 ); + function saveCache( object, geometry, program, index ) { - cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, - 1, 0 ); + const cache = {}; + const attributes = geometry.attributes; + let attributesNum = 0; - cameraPZ.up.set( 0, 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); + const programAttributes = program.getAttributes(); - cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); + for ( const name in programAttributes ) { - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + const programAttribute = programAttributes[ name ]; - cameraPX.up.set( 0, - 1, 0 ); - cameraPX.lookAt( - 1, 0, 0 ); + if ( programAttribute.location >= 0 ) { - cameraNX.up.set( 0, - 1, 0 ); - cameraNX.lookAt( 1, 0, 0 ); + let attribute = attributes[ name ]; - cameraPY.up.set( 0, 0, 1 ); - cameraPY.lookAt( 0, 1, 0 ); + if ( attribute === undefined ) { - cameraNY.up.set( 0, 0, - 1 ); - cameraNY.lookAt( 0, - 1, 0 ); + if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; - cameraPZ.up.set( 0, - 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); + } - cameraNZ.up.set( 0, - 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); + const data = {}; + data.attribute = attribute; - } else { + if ( attribute && attribute.data ) { - throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); + data.data = attribute.data; - } + } - for ( const camera of cameras ) { + cache[ name ] = data; - this.add( camera ); + attributesNum ++; - camera.updateMatrixWorld(); + } } - } + currentState.attributes = cache; + currentState.attributesNum = attributesNum; - update( renderer, scene ) { + currentState.index = index; - if ( this.parent === null ) this.updateMatrixWorld(); + } - const { renderTarget, activeMipmapLevel } = this; + function initAttributes() { - if ( this.coordinateSystem !== renderer.coordinateSystem ) { + const newAttributes = currentState.newAttributes; - this.coordinateSystem = renderer.coordinateSystem; + for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - this.updateCoordinateSystem(); + newAttributes[ i ] = 0; } - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; + } - const currentRenderTarget = renderer.getRenderTarget(); - const currentActiveCubeFace = renderer.getActiveCubeFace(); - const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); + function enableAttribute( attribute ) { - const currentXrEnabled = renderer.xr.enabled; + enableAttributeAndDivisor( attribute, 0 ); - renderer.xr.enabled = false; + } - const generateMipmaps = renderTarget.texture.generateMipmaps; + function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - renderTarget.texture.generateMipmaps = false; + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; + const attributeDivisors = currentState.attributeDivisors; - renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); - renderer.render( scene, cameraPX ); + newAttributes[ attribute ] = 1; - renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); - renderer.render( scene, cameraNX ); + if ( enabledAttributes[ attribute ] === 0 ) { - renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); - renderer.render( scene, cameraPY ); + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; - renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); - renderer.render( scene, cameraNY ); + } - renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); - renderer.render( scene, cameraPZ ); + if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - // mipmaps are generated during the last call of render() - // at this point, all sides of the cube render target are defined + gl.vertexAttribDivisor( attribute, meshPerAttribute ); + attributeDivisors[ attribute ] = meshPerAttribute; - renderTarget.texture.generateMipmaps = generateMipmaps; + } - renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); - renderer.render( scene, cameraNZ ); + } - renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); + function disableUnusedAttributes() { - renderer.xr.enabled = currentXrEnabled; + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; - renderTarget.texture.needsPMREMUpdate = true; + for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - } + if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { -} + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; -class CubeTexture extends Texture { + } - constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { + } - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; + } - super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - this.isCubeTexture = true; + if ( integer === true ) { - this.flipY = false; + gl.vertexAttribIPointer( index, size, type, stride, offset ); - } + } else { - get images() { + gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); - return this.image; + } } - set images( value ) { + function setupVertexAttributes( object, material, program, geometry ) { - this.image = value; + initAttributes(); - } + const geometryAttributes = geometry.attributes; -} + const programAttributes = program.getAttributes(); -class WebGLCubeRenderTarget extends WebGLRenderTarget { + const materialDefaultAttributeValues = material.defaultAttributeValues; - constructor( size = 1, options = {} ) { + for ( const name in programAttributes ) { - super( size, size, options ); + const programAttribute = programAttributes[ name ]; - this.isWebGLCubeRenderTarget = true; + if ( programAttribute.location >= 0 ) { - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; + let geometryAttribute = geometryAttributes[ name ]; - this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); + if ( geometryAttribute === undefined ) { - // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) - // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, - // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped - // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture - // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). + } - this.texture.isRenderTargetTexture = true; + if ( geometryAttribute !== undefined ) { - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; + const normalized = geometryAttribute.normalized; + const size = geometryAttribute.itemSize; - } + const attribute = attributes.get( geometryAttribute ); - fromEquirectangularTexture( renderer, texture ) { + // TODO Attribute may not be available on context restore - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; + if ( attribute === undefined ) continue; - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; + const buffer = attribute.buffer; + const type = attribute.type; + const bytesPerElement = attribute.bytesPerElement; - const shader = { + // check for integer attributes - uniforms: { - tEquirect: { value: null }, - }, + const integer = ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ); - vertexShader: /* glsl */` + if ( geometryAttribute.isInterleavedBufferAttribute ) { - varying vec3 vWorldDirection; + const data = geometryAttribute.data; + const stride = data.stride; + const offset = geometryAttribute.offset; - vec3 transformDirection( in vec3 dir, in mat4 matrix ) { + if ( data.isInstancedInterleavedBuffer ) { - return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - } + enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); - void main() { + } - vWorldDirection = transformDirection( position, modelMatrix ); + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - #include - #include + geometry._maxInstanceCount = data.meshPerAttribute * data.count; - } - `, + } - fragmentShader: /* glsl */` + } else { - uniform sampler2D tEquirect; + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - varying vec3 vWorldDirection; + enableAttribute( programAttribute.location + i ); - #include + } - void main() { + } - vec3 direction = normalize( vWorldDirection ); + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - vec2 sampleUV = equirectUv( direction ); + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - gl_FragColor = texture2D( tEquirect, sampleUV ); + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + stride * bytesPerElement, + ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, + integer + ); - } - ` - }; + } - const geometry = new BoxGeometry( 5, 5, 5 ); + } else { - const material = new ShaderMaterial( { + if ( geometryAttribute.isInstancedBufferAttribute ) { - name: 'CubemapFromEquirect', + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - uniforms: cloneUniforms( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - blending: NoBlending + enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - } ); + } - material.uniforms.tEquirect.value = texture; + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - const mesh = new Mesh( geometry, material ); + geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - const currentMinFilter = texture.minFilter; + } - // Avoid blurred poles - if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; + } else { - const camera = new CubeCamera( 1, 10, this ); - camera.update( renderer, mesh ); + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - texture.minFilter = currentMinFilter; + enableAttribute( programAttribute.location + i ); - mesh.geometry.dispose(); - mesh.material.dispose(); + } - return this; + } - } + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - clear( renderer, color, depth, stencil ) { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - const currentRenderTarget = renderer.getRenderTarget(); + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + size * bytesPerElement, + ( size / programAttribute.locationSize ) * i * bytesPerElement, + integer + ); - for ( let i = 0; i < 6; i ++ ) { + } - renderer.setRenderTarget( this, i ); + } - renderer.clear( color, depth, stencil ); + } else if ( materialDefaultAttributeValues !== undefined ) { - } + const value = materialDefaultAttributeValues[ name ]; - renderer.setRenderTarget( currentRenderTarget ); + if ( value !== undefined ) { - } + switch ( value.length ) { -} + case 2: + gl.vertexAttrib2fv( programAttribute.location, value ); + break; -function WebGLCubeMaps( renderer ) { + case 3: + gl.vertexAttrib3fv( programAttribute.location, value ); + break; - let cubemaps = new WeakMap(); + case 4: + gl.vertexAttrib4fv( programAttribute.location, value ); + break; - function mapTextureMapping( texture, mapping ) { + default: + gl.vertexAttrib1fv( programAttribute.location, value ); - if ( mapping === EquirectangularReflectionMapping ) { + } - texture.mapping = CubeReflectionMapping; + } - } else if ( mapping === EquirectangularRefractionMapping ) { + } - texture.mapping = CubeRefractionMapping; + } } - return texture; + disableUnusedAttributes(); } - function get( texture ) { + function dispose() { - if ( texture && texture.isTexture ) { + reset(); - const mapping = texture.mapping; + for ( const geometryId in bindingStates ) { - if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { + const programMap = bindingStates[ geometryId ]; - if ( cubemaps.has( texture ) ) { + for ( const programId in programMap ) { - const cubemap = cubemaps.get( texture ).texture; - return mapTextureMapping( cubemap, texture.mapping ); + const stateMap = programMap[ programId ]; - } else { + for ( const wireframe in stateMap ) { - const image = texture.image; + deleteVertexArrayObject( stateMap[ wireframe ].object ); - if ( image && image.height > 0 ) { + delete stateMap[ wireframe ]; - const renderTarget = new WebGLCubeRenderTarget( image.height ); - renderTarget.fromEquirectangularTexture( renderer, texture ); - cubemaps.set( texture, renderTarget ); + } - texture.addEventListener( 'dispose', onTextureDispose ); + delete programMap[ programId ]; - return mapTextureMapping( renderTarget.texture, texture.mapping ); + } - } else { + delete bindingStates[ geometryId ]; - // image not yet ready. try the conversion next frame + } - return null; + } - } + function releaseStatesOfGeometry( geometry ) { - } + if ( bindingStates[ geometry.id ] === undefined ) return; + + const programMap = bindingStates[ geometry.id ]; + + for ( const programId in programMap ) { + + const stateMap = programMap[ programId ]; + + for ( const wireframe in stateMap ) { + + deleteVertexArrayObject( stateMap[ wireframe ].object ); + + delete stateMap[ wireframe ]; } + delete programMap[ programId ]; + } - return texture; + delete bindingStates[ geometry.id ]; } - function onTextureDispose( event ) { + function releaseStatesOfProgram( program ) { - const texture = event.target; + for ( const geometryId in bindingStates ) { - texture.removeEventListener( 'dispose', onTextureDispose ); + const programMap = bindingStates[ geometryId ]; - const cubemap = cubemaps.get( texture ); + if ( programMap[ program.id ] === undefined ) continue; - if ( cubemap !== undefined ) { + const stateMap = programMap[ program.id ]; - cubemaps.delete( texture ); - cubemap.dispose(); + for ( const wireframe in stateMap ) { + + deleteVertexArrayObject( stateMap[ wireframe ].object ); + + delete stateMap[ wireframe ]; + + } + + delete programMap[ program.id ]; } } - function dispose() { + function reset() { - cubemaps = new WeakMap(); + resetDefaultState(); + forceUpdate = true; + + if ( currentState === defaultState ) return; + + currentState = defaultState; + bindVertexArrayObject( currentState.object ); } - return { - get: get, - dispose: dispose - }; + // for backward-compatibility -} + function resetDefaultState() { -class OrthographicCamera extends Camera { + defaultState.geometry = null; + defaultState.program = null; + defaultState.wireframe = false; - constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { + } - super(); + return { - this.isOrthographicCamera = true; + setup: setup, + reset: reset, + resetDefaultState: resetDefaultState, + dispose: dispose, + releaseStatesOfGeometry: releaseStatesOfGeometry, + releaseStatesOfProgram: releaseStatesOfProgram, - this.type = 'OrthographicCamera'; + initAttributes: initAttributes, + enableAttribute: enableAttribute, + disableUnusedAttributes: disableUnusedAttributes - this.zoom = 1; - this.view = null; + }; - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; +} - this.near = near; - this.far = far; +function WebGLBufferRenderer( gl, extensions, info ) { - this.updateProjectionMatrix(); + let mode; - } + function setMode( value ) { - copy( source, recursive ) { + mode = value; - super.copy( source, recursive ); + } - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; + function render( start, count ) { - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); + gl.drawArrays( mode, start, count ); - return this; + info.update( count, mode, 1 ); } - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + function renderInstances( start, count, primcount ) { - if ( this.view === null ) { + if ( primcount === 0 ) return; - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; + gl.drawArraysInstanced( mode, start, count, primcount ); - } + info.update( count, mode, primcount ); - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; + } - this.updateProjectionMatrix(); + function renderMultiDraw( starts, counts, drawCount ) { - } + if ( drawCount === 0 ) return; - clearViewOffset() { + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - if ( this.view !== null ) { + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - this.view.enabled = false; + elementCount += counts[ i ]; } - this.updateProjectionMatrix(); + info.update( elementCount, mode, 1 ); } - updateProjectionMatrix() { - - const dx = ( this.right - this.left ) / ( 2 * this.zoom ); - const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - const cx = ( this.right + this.left ) / 2; - const cy = ( this.top + this.bottom ) / 2; + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { - let left = cx - dx; - let right = cx + dx; - let top = cy + dy; - let bottom = cy - dy; + if ( drawCount === 0 ) return; - if ( this.view !== null && this.view.enabled ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); - const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; - const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; + if ( extension === null ) { - left += scaleW * this.view.offsetX; - right = left + scaleW * this.view.width; - top -= scaleH * this.view.offsetY; - bottom = top - scaleH * this.view.height; + for ( let i = 0; i < starts.length; i ++ ) { - } + renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem ); + } - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + } else { - } + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); - toJSON( meta ) { + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - const data = super.toJSON( meta ); + elementCount += counts[ i ] * primcount[ i ]; - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; + } - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + info.update( elementCount, mode, 1 ); - return data; + } } -} + // -const LOD_MIN = 4; + this.setMode = setMode; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; -// The standard deviations (radians) associated with the extra mips. These are -// chosen to approximate a Trowbridge-Reitz distribution function times the -// geometric shadowing function. These sigma values squared must match the -// variance #defines in cube_uv_reflection_fragment.glsl.js. -const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; +} -// The maximum length of the blur for loop. Smaller sigmas will use fewer -// samples and exit early, but not recompile the shader. -const MAX_SAMPLES = 20; +function WebGLCapabilities( gl, extensions, parameters, utils ) { -const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); -const _clearColor = /*@__PURE__*/ new Color(); -let _oldTarget = null; -let _oldActiveCubeFace = 0; -let _oldActiveMipmapLevel = 0; + let maxAnisotropy; -// Golden Ratio -const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; -const INV_PHI = 1 / PHI; + function getMaxAnisotropy() { -// Vertices of a dodecahedron (except the opposites, which represent the -// same axis), used as axis directions evenly spread on a sphere. -const _axisDirections = [ - /*@__PURE__*/ new Vector3( 1, 1, 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, 1 ), - /*@__PURE__*/ new Vector3( 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), - /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), - /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), - /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; + if ( maxAnisotropy !== undefined ) return maxAnisotropy; -/** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view -*/ + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { -class PMREMGenerator { + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - constructor( renderer ) { + maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - this._renderer = renderer; - this._pingPongRenderTarget = null; + } else { - this._lodMax = 0; - this._cubeSize = 0; - this._lodPlanes = []; - this._sizeLods = []; - this._sigmas = []; + maxAnisotropy = 0; - this._blurMaterial = null; - this._cubemapMaterial = null; - this._equirectMaterial = null; + } - this._compileMaterial( this._blurMaterial ); + return maxAnisotropy; } - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). - */ - fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { - - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + function textureFormatReadable( textureFormat ) { - this._setSize( 256 ); + if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; + return false; - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); + } - if ( sigma > 0 ) { + return true; - this._blur( cubeUVRenderTarget, 0, 0, sigma ); + } - } + function textureTypeReadable( textureType ) { - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); + const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ); - return cubeUVRenderTarget; + if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) + textureType !== FloatType && ! halfFloatSupportedByExt ) { - } + return false; - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), - * as this matches best with the 256 x 256 cubemap output. - * The smallest supported equirectangular image size is 64 x 32. - */ - fromEquirectangular( equirectangular, renderTarget = null ) { + } - return this._fromTexture( equirectangular, renderTarget ); + return true; } - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, - * as this matches best with the 256 x 256 cubemap output. - * The smallest supported cube size is 16 x 16. - */ - fromCubemap( cubemap, renderTarget = null ) { + function getMaxPrecision( precision ) { - return this._fromTexture( cubemap, renderTarget ); + if ( precision === 'highp' ) { - } + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { + return 'highp'; - if ( this._cubemapMaterial === null ) { + } - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial( this._cubemapMaterial ); + precision = 'mediump'; } - } + if ( precision === 'mediump' ) { - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - if ( this._equirectMaterial === null ) { + return 'mediump'; - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial( this._equirectMaterial ); + } } - } - - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { - - this._dispose(); - - if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); - if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); + return 'lowp'; } - // private interface + let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; + const maxPrecision = getMaxPrecision( precision ); - _setSize( cubeSize ) { + if ( maxPrecision !== precision ) { - this._lodMax = Math.floor( Math.log2( cubeSize ) ); - this._cubeSize = Math.pow( 2, this._lodMax ); + console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); + precision = maxPrecision; } - _dispose() { + const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + const reversedDepthBuffer = parameters.reversedDepthBuffer === true && extensions.has( 'EXT_clip_control' ); - if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); + const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); + const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); + const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); + const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); + const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - for ( let i = 0; i < this._lodPlanes.length; i ++ ) { + const vertexTextures = maxVertexTextures > 0; - this._lodPlanes[ i ].dispose(); + const maxSamples = gl.getParameter( gl.MAX_SAMPLES ); - } + return { - } + isWebGL2: true, // keeping this for backwards compatibility - _cleanup( outputTarget ) { + getMaxAnisotropy: getMaxAnisotropy, + getMaxPrecision: getMaxPrecision, - this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); - outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); + textureFormatReadable: textureFormatReadable, + textureTypeReadable: textureTypeReadable, - } + precision: precision, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reversedDepthBuffer: reversedDepthBuffer, - _fromTexture( texture, renderTarget ) { + maxTextures: maxTextures, + maxVertexTextures: maxVertexTextures, + maxTextureSize: maxTextureSize, + maxCubemapSize: maxCubemapSize, - if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { + maxAttributes: maxAttributes, + maxVertexUniforms: maxVertexUniforms, + maxVaryings: maxVaryings, + maxFragmentUniforms: maxFragmentUniforms, - this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); + vertexTextures: vertexTextures, - } else { // Equirectangular + maxSamples: maxSamples - this._setSize( texture.image.width / 4 ); + }; - } +} - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); +function WebGLClipping( properties ) { - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV( texture, cubeUVRenderTarget ); - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); + const scope = this; - return cubeUVRenderTarget; + let globalState = null, + numGlobalPlanes = 0, + localClippingEnabled = false, + renderingShadows = false; - } + const plane = new Plane(), + viewNormalMatrix = new Matrix3(), - _allocateTargets() { + uniform = { value: null, needsUpdate: false }; - const width = 3 * Math.max( this._cubeSize, 16 * 7 ); - const height = 4 * this._cubeSize; + this.uniform = uniform; + this.numPlanes = 0; + this.numIntersection = 0; - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - depthBuffer: false - }; + this.init = function ( planes, enableLocalClipping ) { - const cubeUVRenderTarget = _createRenderTarget( width, height, params ); + const enabled = + planes.length !== 0 || + enableLocalClipping || + // enable state of previous frame - the clipping code has to + // run another frame in order to reset the state: + numGlobalPlanes !== 0 || + localClippingEnabled; - if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { + localClippingEnabled = enableLocalClipping; - if ( this._pingPongRenderTarget !== null ) { + numGlobalPlanes = planes.length; - this._dispose(); + return enabled; - } + }; - this._pingPongRenderTarget = _createRenderTarget( width, height, params ); + this.beginShadows = function () { - const { _lodMax } = this; - ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); + renderingShadows = true; + projectPlanes( null ); - this._blurMaterial = _getBlurShader( _lodMax, width, height ); + }; - } + this.endShadows = function () { - return cubeUVRenderTarget; + renderingShadows = false; - } + }; - _compileMaterial( material ) { + this.setGlobalState = function ( planes, camera ) { - const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); - this._renderer.compile( tmpMesh, _flatCamera ); + globalState = projectPlanes( planes, camera, 0 ); - } + }; - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { + this.setState = function ( material, camera, useCache ) { - const fov = 90; - const aspect = 1; - const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, - 1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; - const renderer = this._renderer; + const planes = material.clippingPlanes, + clipIntersection = material.clipIntersection, + clipShadows = material.clipShadows; - const originalAutoClear = renderer.autoClear; - const toneMapping = renderer.toneMapping; - renderer.getClearColor( _clearColor ); + const materialProperties = properties.get( material ); - renderer.toneMapping = NoToneMapping; - renderer.autoClear = false; + if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - const backgroundMaterial = new MeshBasicMaterial( { - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - } ); + // there's no local clipping - const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); + if ( renderingShadows ) { - let useSolidColor = false; - const background = scene.background; + // there's no global clipping - if ( background ) { + projectPlanes( null ); - if ( background.isColor ) { + } else { - backgroundMaterial.color.copy( background ); - scene.background = null; - useSolidColor = true; + resetGlobalState(); } } else { - backgroundMaterial.color.copy( _clearColor ); - useSolidColor = true; - - } - - for ( let i = 0; i < 6; i ++ ) { - - const col = i % 3; - - if ( col === 0 ) { + const nGlobal = renderingShadows ? 0 : numGlobalPlanes, + lGlobal = nGlobal * 4; - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); + let dstArray = materialProperties.clippingState || null; - } else if ( col === 1 ) { + uniform.value = dstArray; // ensure unique state - cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); + dstArray = projectPlanes( planes, camera, lGlobal, useCache ); - } else { + for ( let i = 0; i !== lGlobal; ++ i ) { - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); + dstArray[ i ] = globalState[ i ]; } - const size = this._cubeSize; + materialProperties.clippingState = dstArray; + this.numIntersection = clipIntersection ? this.numPlanes : 0; + this.numPlanes += nGlobal; - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); + } - renderer.setRenderTarget( cubeUVRenderTarget ); - if ( useSolidColor ) { + }; - renderer.render( backgroundBox, cubeCamera ); + function resetGlobalState() { - } + if ( uniform.value !== globalState ) { - renderer.render( scene, cubeCamera ); + uniform.value = globalState; + uniform.needsUpdate = numGlobalPlanes > 0; } - backgroundBox.geometry.dispose(); - backgroundBox.material.dispose(); - - renderer.toneMapping = toneMapping; - renderer.autoClear = originalAutoClear; - scene.background = background; + scope.numPlanes = numGlobalPlanes; + scope.numIntersection = 0; } - _textureToCubeUV( texture, cubeUVRenderTarget ) { + function projectPlanes( planes, camera, dstOffset, skipTransform ) { - const renderer = this._renderer; + const nPlanes = planes !== null ? planes.length : 0; + let dstArray = null; - const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); + if ( nPlanes !== 0 ) { - if ( isCubeTexture ) { + dstArray = uniform.value; - if ( this._cubemapMaterial === null ) { + if ( skipTransform !== true || dstArray === null ) { - this._cubemapMaterial = _getCubemapMaterial(); + const flatSize = dstOffset + nPlanes * 4, + viewMatrix = camera.matrixWorldInverse; - } + viewNormalMatrix.getNormalMatrix( viewMatrix ); - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; + if ( dstArray === null || dstArray.length < flatSize ) { - } else { + dstArray = new Float32Array( flatSize ); - if ( this._equirectMaterial === null ) { + } - this._equirectMaterial = _getEquirectMaterial(); + for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - } + plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - } + plane.normal.toArray( dstArray, i4 ); + dstArray[ i4 + 3 ] = plane.constant; - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - const mesh = new Mesh( this._lodPlanes[ 0 ], material ); + } - const uniforms = material.uniforms; + } - uniforms[ 'envMap' ].value = texture; + uniform.value = dstArray; + uniform.needsUpdate = true; - const size = this._cubeSize; + } - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); + scope.numPlanes = nPlanes; + scope.numIntersection = 0; - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( mesh, _flatCamera ); + return dstArray; } - _applyPMREM( cubeUVRenderTarget ) { +} - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; +/** + * Abstract base class for cameras. This class should always be inherited + * when you build a new camera. + * + * @abstract + * @augments Object3D + */ +class Camera extends Object3D { - for ( let i = 1; i < this._lodPlanes.length; i ++ ) { + /** + * Constructs a new camera. + */ + constructor() { - const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); + super(); - const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCamera = true; - this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); + this.type = 'Camera'; - } + /** + * The inverse of the camera's world matrix. + * + * @type {Matrix4} + */ + this.matrixWorldInverse = new Matrix4(); - renderer.autoClear = autoClear; + /** + * The camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrix = new Matrix4(); + + /** + * The inverse of the camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrixInverse = new Matrix4(); + + /** + * The coordinate system in which the camera is used. + * + * @type {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + */ + this.coordinateSystem = WebGLCoordinateSystem; + + this._reversedDepth = false; } /** - * This is a two-pass Gaussian blur for a cubemap. Normally this is done - * vertically and horizontally, but this breaks down on a cube. Here we apply - * the blur latitudinally (around the poles), and then longitudinally (towards - * the poles) to approximate the orthogonally-separable blur. It is least - * accurate at the poles, but still does a decent job. + * The flag that indicates whether the camera uses a reversed depth buffer. + * + * @type {boolean} + * @default false */ - _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { + get reversedDepth() { - const pingPongRenderTarget = this._pingPongRenderTarget; + return this._reversedDepth; - this._halfBlur( - cubeUVRenderTarget, - pingPongRenderTarget, - lodIn, - lodOut, - sigma, - 'latitudinal', - poleAxis ); + } - this._halfBlur( - pingPongRenderTarget, - cubeUVRenderTarget, - lodOut, - lodOut, - sigma, - 'longitudinal', - poleAxis ); + copy( source, recursive ) { - } + super.copy( source, recursive ); - _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { + this.matrixWorldInverse.copy( source.matrixWorldInverse ); - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; + this.projectionMatrix.copy( source.projectionMatrix ); + this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); - if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { + this.coordinateSystem = source.coordinateSystem; - console.error( - 'blur direction must be either latitudinal or longitudinal!' ); + return this; - } + } - // Number of standard deviations at which to cut off the discrete approximation. - const STANDARD_DEVIATIONS = 3; + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * This method is overwritten since cameras have a different forward vector compared to other + * 3D objects. A camera looks down its local, negative z-axis by default. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ + getWorldDirection( target ) { - const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); - const blurUniforms = blurMaterial.uniforms; + return super.getWorldDirection( target ).negate(); - const pixels = this._sizeLods[ lodIn ] - 1; - const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); - const sigmaPixels = sigmaRadians / radiansPerPixel; - const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; + } - if ( samples > MAX_SAMPLES ) { + updateMatrixWorld( force ) { - console.warn( `sigmaRadians, ${ - sigmaRadians}, is too large and will clip, as it requested ${ - samples} samples when the maximum is set to ${MAX_SAMPLES}` ); + super.updateMatrixWorld( force ); - } + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - const weights = []; - let sum = 0; + } - for ( let i = 0; i < MAX_SAMPLES; ++ i ) { + updateWorldMatrix( updateParents, updateChildren ) { - const x = i / sigmaPixels; - const weight = Math.exp( - x * x / 2 ); - weights.push( weight ); + super.updateWorldMatrix( updateParents, updateChildren ); - if ( i === 0 ) { + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - sum += weight; + } - } else if ( i < samples ) { + clone() { - sum += 2 * weight; + return new this.constructor().copy( this ); - } + } - } +} - for ( let i = 0; i < weights.length; i ++ ) { +const _v3 = /*@__PURE__*/ new Vector3(); +const _minTarget = /*@__PURE__*/ new Vector2(); +const _maxTarget = /*@__PURE__*/ new Vector2(); - weights[ i ] = weights[ i ] / sum; +/** + * Camera that uses [perspective projection]{@link https://en.wikipedia.org/wiki/Perspective_(graphical)}. + * + * This projection mode is designed to mimic the way the human eye sees. It + * is the most common projection mode used for rendering a 3D scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class PerspectiveCamera extends Camera { - } + /** + * Constructs a new perspective camera. + * + * @param {number} [fov=50] - The vertical field of view. + * @param {number} [aspect=1] - The aspect ratio. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { - blurUniforms[ 'envMap' ].value = targetIn.texture; - blurUniforms[ 'samples' ].value = samples; - blurUniforms[ 'weights' ].value = weights; - blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; + super(); - if ( poleAxis ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPerspectiveCamera = true; - blurUniforms[ 'poleAxis' ].value = poleAxis; + this.type = 'PerspectiveCamera'; - } + /** + * The vertical field of view, from bottom to top of view, + * in degrees. + * + * @type {number} + * @default 50 + */ + this.fov = fov; - const { _lodMax } = this; - blurUniforms[ 'dTheta' ].value = radiansPerPixel; - blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link PerspectiveCamera#far}. + * + * Note that, unlike for the {@link OrthographicCamera}, `0` is not a + * valid value for a perspective camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( targetOut ); - renderer.render( blurMesh, _flatCamera ); + /** + * The camera's far plane. Must be greater than the + * current value of {@link PerspectiveCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; - } + /** + * Object distance used for stereoscopy and depth-of-field effects. This + * parameter does not influence the projection matrix unless a + * {@link StereoCamera} is being used. + * + * @type {number} + * @default 10 + */ + this.focus = 10; -} + /** + * The aspect ratio, usually the canvas width / canvas height. + * + * @type {number} + * @default 1 + */ + this.aspect = aspect; + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; + /** + * Film size used for the larger axis. Default is `35` (millimeters). This + * parameter does not influence the projection matrix unless {@link PerspectiveCamera#filmOffset} + * is set to a nonzero value. + * + * @type {number} + * @default 35 + */ + this.filmGauge = 35; -function _createPlanes( lodMax ) { + /** + * Horizontal off-center offset in the same unit as {@link PerspectiveCamera#filmGauge}. + * + * @type {number} + * @default 0 + */ + this.filmOffset = 0; - const lodPlanes = []; - const sizeLods = []; - const sigmas = []; + this.updateProjectionMatrix(); - let lod = lodMax; + } - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + copy( source, recursive ) { - for ( let i = 0; i < totalLods; i ++ ) { + super.copy( source, recursive ); - const sizeLod = Math.pow( 2, lod ); - sizeLods.push( sizeLod ); - let sigma = 1.0 / sizeLod; + this.fov = source.fov; + this.zoom = source.zoom; - if ( i > lodMax - LOD_MIN ) { + this.near = source.near; + this.far = source.far; + this.focus = source.focus; - sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; + this.aspect = source.aspect; + this.view = source.view === null ? null : Object.assign( {}, source.view ); - } else if ( i === 0 ) { + this.filmGauge = source.filmGauge; + this.filmOffset = source.filmOffset; - sigma = 0; + return this; - } + } - sigmas.push( sigma ); + /** + * Sets the FOV by focal length in respect to the current {@link PerspectiveCamera#filmGauge}. + * + * The default film gauge is 35, so that the focal length can be specified for + * a 35mm (full frame) camera. + * + * @param {number} focalLength - Values for focal length and film gauge must have the same unit. + */ + setFocalLength( focalLength ) { - const texelSize = 1.0 / ( sizeLod - 2 ); - const min = - texelSize; - const max = 1 + texelSize; - const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; + /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ + const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - const uvSize = 2; - const faceIndexSize = 1; - - const position = new Float32Array( positionSize * vertices * cubeFaces ); - const uv = new Float32Array( uvSize * vertices * cubeFaces ); - const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); + this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); + this.updateProjectionMatrix(); - for ( let face = 0; face < cubeFaces; face ++ ) { + } - const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : - 1; - const coordinates = [ - x, y, 0, - x + 2 / 3, y, 0, - x + 2 / 3, y + 1, 0, - x, y, 0, - x + 2 / 3, y + 1, 0, - x, y + 1, 0 - ]; - position.set( coordinates, positionSize * vertices * face ); - uv.set( uv1, uvSize * vertices * face ); - const fill = [ face, face, face, face, face, face ]; - faceIndex.set( fill, faceIndexSize * vertices * face ); + /** + * Returns the focal length from the current {@link PerspectiveCamera#fov} and + * {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The computed focal length. + */ + getFocalLength() { - } + const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); - const planes = new BufferGeometry(); - planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); - planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); - planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); - lodPlanes.push( planes ); + return 0.5 * this.getFilmHeight() / vExtentSlope; - if ( lod > LOD_MIN ) { + } - lod --; + /** + * Returns the current vertical field of view angle in degrees considering {@link PerspectiveCamera#zoom}. + * + * @return {number} The effective FOV. + */ + getEffectiveFOV() { - } + return RAD2DEG * 2 * Math.atan( + Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); } - return { lodPlanes, sizeLods, sigmas }; - -} + /** + * Returns the width of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmWidth() { -function _createRenderTarget( width, height, params ) { + // film not completely covered in portrait format (aspect < 1) + return this.filmGauge * Math.min( this.aspect, 1 ); - const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; + } -} + /** + * Returns the height of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmHeight() { -function _setViewport( target, x, y, width, height ) { + // film not completely covered in landscape format (aspect > 1) + return this.filmGauge / Math.max( this.aspect, 1 ); - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); + } -} + /** + * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. + * Sets `minTarget` and `maxTarget` to the coordinates of the lower-left and upper-right corners of the view rectangle. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} minTarget - The lower-left corner of the view rectangle is written into this vector. + * @param {Vector2} maxTarget - The upper-right corner of the view rectangle is written into this vector. + */ + getViewBounds( distance, minTarget, maxTarget ) { -function _getBlurShader( lodMax, width, height ) { + _v3.set( -1, -1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); - const weights = new Float32Array( MAX_SAMPLES ); - const poleAxis = new Vector3( 0, 1, 0 ); - const shaderMaterial = new ShaderMaterial( { + minTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); - name: 'SphericalGaussianBlur', + _v3.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); - defines: { - 'n': MAX_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, + maxTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); - uniforms: { - 'envMap': { value: null }, - 'samples': { value: 1 }, - 'weights': { value: weights }, - 'latitudinal': { value: false }, - 'dTheta': { value: 0 }, - 'mipInt': { value: 0 }, - 'poleAxis': { value: poleAxis } - }, + } - vertexShader: _getCommonVertexShader(), + /** + * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} target - The target vector that is used to store result where x is width and y is height. + * @returns {Vector2} The view size. + */ + getViewSize( distance, target ) { - fragmentShader: /* glsl */` + this.getViewBounds( distance, _minTarget, _maxTarget ); - precision mediump float; - precision mediump int; + return target.subVectors( _maxTarget, _minTarget ); - varying vec3 vOutputDirection; + } - uniform sampler2D envMap; - uniform int samples; - uniform float weights[ n ]; - uniform bool latitudinal; - uniform float dTheta; - uniform float mipInt; - uniform vec3 poleAxis; + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + *``` + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + *``` + * then for each monitor you would call it like this: + *```js + * const w = 1920; + * const h = 1080; + * const fullWidth = w * 3; + * const fullHeight = h * 2; + * + * // --A-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * // --B-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * // --C-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * // --D-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * // --E-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * // --F-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * ``` + * + * Note there is no reason monitors have to be the same size or in a grid. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - #define ENVMAP_TYPE_CUBE_UV - #include + this.aspect = fullWidth / fullHeight; - vec3 getSample( float theta, vec3 axis ) { + if ( this.view === null ) { - float cosTheta = cos( theta ); - // Rodrigues' axis-angle rotation - vec3 sampleDirection = vOutputDirection * cosTheta - + cross( axis, vOutputDirection ) * sin( theta ) - + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; - return bilinearCubeUV( envMap, sampleDirection, mipInt ); + } - } + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; - void main() { + this.updateProjectionMatrix(); - vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); + } - if ( all( equal( axis, vec3( 0.0 ) ) ) ) { + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { - axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); + if ( this.view !== null ) { - } + this.view.enabled = false; - axis = normalize( axis ); + } - gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); + this.updateProjectionMatrix(); - for ( int i = 1; i < n; i++ ) { + } - if ( i >= samples ) { + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { - break; + const near = this.near; + let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; + let height = 2 * top; + let width = this.aspect * height; + let left = -0.5 * width; + const view = this.view; - } + if ( this.view !== null && this.view.enabled ) { - float theta = dTheta * float( i ); - gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); - gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); + const fullWidth = view.fullWidth, + fullHeight = view.fullHeight; - } + left += view.offsetX * width / fullWidth; + top -= view.offsetY * height / fullHeight; + width *= view.width / fullWidth; + height *= view.height / fullHeight; - } - `, + } - blending: NoBlending, - depthTest: false, - depthWrite: false + const skew = this.filmOffset; + if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - } ); + this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem, this.reversedDepth ); - return shaderMaterial; + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); -} + } -function _getEquirectMaterial() { + toJSON( meta ) { - return new ShaderMaterial( { + const data = super.toJSON( meta ); - name: 'EquirectangularToCubeUV', + data.object.fov = this.fov; + data.object.zoom = this.zoom; - uniforms: { - 'envMap': { value: null } - }, + data.object.near = this.near; + data.object.far = this.far; + data.object.focus = this.focus; - vertexShader: _getCommonVertexShader(), + data.object.aspect = this.aspect; - fragmentShader: /* glsl */` + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - precision mediump float; - precision mediump int; + data.object.filmGauge = this.filmGauge; + data.object.filmOffset = this.filmOffset; - varying vec3 vOutputDirection; + return data; - uniform sampler2D envMap; + } - #include +} - void main() { +const fov = -90; // negative fov is not an error +const aspect = 1; - vec3 outputDirection = normalize( vOutputDirection ); - vec2 uv = equirectUv( outputDirection ); +/** + * A special type of camera that is positioned in 3D space to render its surroundings into a + * cube render target. The render target can then be used as an environment map for rendering + * realtime reflections in your scene. + * + * ```js + * // Create cube render target + * const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); + * + * // Create cube camera + * const cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); + * scene.add( cubeCamera ); + * + * // Create car + * const chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); + * const car = new THREE.Mesh( carGeometry, chromeMaterial ); + * scene.add( car ); + * + * // Update the render target cube + * car.visible = false; + * cubeCamera.position.copy( car.position ); + * cubeCamera.update( renderer, scene ); + * + * // Render the scene + * car.visible = true; + * renderer.render( scene, camera ); + * ``` + * + * @augments Object3D + */ +class CubeCamera extends Object3D { - gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); + /** + * Constructs a new cube camera. + * + * @param {number} near - The camera's near plane. + * @param {number} far - The camera's far plane. + * @param {WebGLCubeRenderTarget} renderTarget - The cube render target. + */ + constructor( near, far, renderTarget ) { - } - `, + super(); - blending: NoBlending, - depthTest: false, - depthWrite: false + this.type = 'CubeCamera'; - } ); + /** + * A reference to the cube render target. + * + * @type {WebGLCubeRenderTarget} + */ + this.renderTarget = renderTarget; -} + /** + * The current active coordinate system. + * + * @type {?(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + * @default null + */ + this.coordinateSystem = null; -function _getCubemapMaterial() { + /** + * The current active mipmap level + * + * @type {number} + * @default 0 + */ + this.activeMipmapLevel = 0; - return new ShaderMaterial( { + const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); + cameraPX.layers = this.layers; + this.add( cameraPX ); - name: 'CubemapToCubeUV', + const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); + cameraNX.layers = this.layers; + this.add( cameraNX ); - uniforms: { - 'envMap': { value: null }, - 'flipEnvMap': { value: - 1 } - }, + const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); + cameraPY.layers = this.layers; + this.add( cameraPY ); - vertexShader: _getCommonVertexShader(), + const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); + cameraNY.layers = this.layers; + this.add( cameraNY ); - fragmentShader: /* glsl */` + const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.layers = this.layers; + this.add( cameraPZ ); - precision mediump float; - precision mediump int; + const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.layers = this.layers; + this.add( cameraNZ ); - uniform float flipEnvMap; + } - varying vec3 vOutputDirection; + /** + * Must be called when the coordinate system of the cube camera is changed. + */ + updateCoordinateSystem() { - uniform samplerCube envMap; + const coordinateSystem = this.coordinateSystem; - void main() { + const cameras = this.children.concat(); - gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; - } - `, + for ( const camera of cameras ) this.remove( camera ); - blending: NoBlending, - depthTest: false, - depthWrite: false + if ( coordinateSystem === WebGLCoordinateSystem ) { - } ); + cameraPX.up.set( 0, 1, 0 ); + cameraPX.lookAt( 1, 0, 0 ); -} + cameraNX.up.set( 0, 1, 0 ); + cameraNX.lookAt( -1, 0, 0 ); -function _getCommonVertexShader() { + cameraPY.up.set( 0, 0, -1 ); + cameraPY.lookAt( 0, 1, 0 ); - return /* glsl */` + cameraNY.up.set( 0, 0, 1 ); + cameraNY.lookAt( 0, -1, 0 ); - precision mediump float; - precision mediump int; + cameraPZ.up.set( 0, 1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); - attribute float faceIndex; + cameraNZ.up.set( 0, 1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); - varying vec3 vOutputDirection; + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - // RH coordinate system; PMREM face-indexing convention - vec3 getDirection( vec2 uv, float face ) { + cameraPX.up.set( 0, -1, 0 ); + cameraPX.lookAt( -1, 0, 0 ); - uv = 2.0 * uv - 1.0; + cameraNX.up.set( 0, -1, 0 ); + cameraNX.lookAt( 1, 0, 0 ); - vec3 direction = vec3( uv, 1.0 ); + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( 0, 1, 0 ); - if ( face == 0.0 ) { + cameraNY.up.set( 0, 0, -1 ); + cameraNY.lookAt( 0, -1, 0 ); - direction = direction.zyx; // ( 1, v, u ) pos x + cameraPZ.up.set( 0, -1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); - } else if ( face == 1.0 ) { + cameraNZ.up.set( 0, -1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); - direction = direction.xzy; - direction.xz *= -1.0; // ( -u, 1, -v ) pos y + } else { - } else if ( face == 2.0 ) { + throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); - direction.x *= -1.0; // ( -u, v, 1 ) pos z + } - } else if ( face == 3.0 ) { + for ( const camera of cameras ) { - direction = direction.zyx; - direction.xz *= -1.0; // ( -1, v, -u ) neg x + this.add( camera ); - } else if ( face == 4.0 ) { + camera.updateMatrixWorld(); - direction = direction.xzy; - direction.xy *= -1.0; // ( -u, -1, v ) neg y + } - } else if ( face == 5.0 ) { + } - direction.z *= -1.0; // ( u, v, -1 ) neg z + /** + * Calling this method will render the given scene with the given renderer + * into the cube render target of the camera. + * + * @param {(Renderer|WebGLRenderer)} renderer - The renderer. + * @param {Scene} scene - The scene to render. + */ + update( renderer, scene ) { - } + if ( this.parent === null ) this.updateMatrixWorld(); - return direction; + const { renderTarget, activeMipmapLevel } = this; - } + if ( this.coordinateSystem !== renderer.coordinateSystem ) { - void main() { + this.coordinateSystem = renderer.coordinateSystem; - vOutputDirection = getDirection( uv, faceIndex ); - gl_Position = vec4( position, 1.0 ); + this.updateCoordinateSystem(); } - `; - -} -function WebGLCubeUVMaps( renderer ) { + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; - let cubeUVmaps = new WeakMap(); + const currentRenderTarget = renderer.getRenderTarget(); + const currentActiveCubeFace = renderer.getActiveCubeFace(); + const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); - let pmremGenerator = null; + const currentXrEnabled = renderer.xr.enabled; - function get( texture ) { + renderer.xr.enabled = false; - if ( texture && texture.isTexture ) { + const generateMipmaps = renderTarget.texture.generateMipmaps; - const mapping = texture.mapping; + renderTarget.texture.generateMipmaps = false; - const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); - const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); + renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); + renderer.render( scene, cameraPX ); - // equirect/cube map to cubeUV conversion + renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); + renderer.render( scene, cameraNX ); - if ( isEquirectMap || isCubeMap ) { + renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); + renderer.render( scene, cameraPY ); - if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { + renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); + renderer.render( scene, cameraNY ); - texture.needsPMREMUpdate = false; + renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); + renderer.render( scene, cameraPZ ); - let renderTarget = cubeUVmaps.get( texture ); + // mipmaps are generated during the last call of render() + // at this point, all sides of the cube render target are defined - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); + renderTarget.texture.generateMipmaps = generateMipmaps; - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); - cubeUVmaps.set( texture, renderTarget ); + renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); + renderer.render( scene, cameraNZ ); - return renderTarget.texture; + renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - } else { + renderer.xr.enabled = currentXrEnabled; - if ( cubeUVmaps.has( texture ) ) { + renderTarget.texture.needsPMREMUpdate = true; - return cubeUVmaps.get( texture ).texture; + } - } else { +} - const image = texture.image; +/** + * Creates a cube texture made up of six images. + * + * ```js + * const loader = new THREE.CubeTextureLoader(); + * loader.setPath( 'textures/cube/pisa/' ); + * + * const textureCube = loader.load( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } ); + * ``` + * + * @augments Texture + */ +class CubeTexture extends Texture { - if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { + /** + * Constructs a new cube texture. + * + * @param {Array} [images=[]] - An array holding a image for each side of a cube. + * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space value. + */ + constructor( images = [], mapping = CubeReflectionMapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); + super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); - cubeUVmaps.set( texture, renderTarget ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTexture = true; - texture.addEventListener( 'dispose', onTextureDispose ); + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - return renderTarget.texture; + } - } else { + /** + * Alias for {@link CubeTexture#image}. + * + * @type {Array} + */ + get images() { - // image not yet ready. try the conversion next frame + return this.image; - return null; + } - } + set images( value ) { - } + this.image = value; - } + } - } +} - } +/** + * A cube render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGLCubeRenderTarget extends WebGLRenderTarget { - return texture; + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( size = 1, options = {} ) { - } + super( size, size, options ); - function isCubeTextureComplete( image ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLCubeRenderTarget = true; - let count = 0; - const length = 6; + const image = { width: size, height: size, depth: 1 }; + const images = [ image, image, image, image, image, image ]; - for ( let i = 0; i < length; i ++ ) { + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new CubeTexture( images ); + this._setTextureOptions( options ); - if ( image[ i ] !== undefined ) count ++; - - } - - return count === length; - - - } - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - const cubemapUV = cubeUVmaps.get( texture ); - - if ( cubemapUV !== undefined ) { + // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) + // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, + // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. - cubeUVmaps.delete( texture ); - cubemapUV.dispose(); + // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped + // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture + // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). - } + this.texture.isRenderTargetTexture = true; } - function dispose() { - - cubeUVmaps = new WeakMap(); + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {WebGLCubeRenderTarget} A reference to this cube render target. + */ + fromEquirectangularTexture( renderer, texture ) { - if ( pmremGenerator !== null ) { + this.texture.type = texture.type; + this.texture.colorSpace = texture.colorSpace; - pmremGenerator.dispose(); - pmremGenerator = null; + this.texture.generateMipmaps = texture.generateMipmaps; + this.texture.minFilter = texture.minFilter; + this.texture.magFilter = texture.magFilter; - } + const shader = { - } + uniforms: { + tEquirect: { value: null }, + }, - return { - get: get, - dispose: dispose - }; + vertexShader: /* glsl */` -} + varying vec3 vWorldDirection; -function WebGLExtensions( gl ) { + vec3 transformDirection( in vec3 dir, in mat4 matrix ) { - const extensions = {}; + return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); - function getExtension( name ) { + } - if ( extensions[ name ] !== undefined ) { + void main() { - return extensions[ name ]; + vWorldDirection = transformDirection( position, modelMatrix ); - } + #include + #include - let extension; + } + `, - switch ( name ) { + fragmentShader: /* glsl */` - case 'WEBGL_depth_texture': - extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); - break; + uniform sampler2D tEquirect; - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); - break; + varying vec3 vWorldDirection; - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); - break; + #include - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); - break; + void main() { - default: - extension = gl.getExtension( name ); + vec3 direction = normalize( vWorldDirection ); - } + vec2 sampleUV = equirectUv( direction ); - extensions[ name ] = extension; + gl_FragColor = texture2D( tEquirect, sampleUV ); - return extension; + } + ` + }; - } + const geometry = new BoxGeometry( 5, 5, 5 ); - return { + const material = new ShaderMaterial( { - has: function ( name ) { + name: 'CubemapFromEquirect', - return getExtension( name ) !== null; + uniforms: cloneUniforms( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: BackSide, + blending: NoBlending - }, + } ); - init: function ( capabilities ) { + material.uniforms.tEquirect.value = texture; - if ( capabilities.isWebGL2 ) { + const mesh = new Mesh( geometry, material ); - getExtension( 'EXT_color_buffer_float' ); - getExtension( 'WEBGL_clip_cull_distance' ); + const currentMinFilter = texture.minFilter; - } else { + // Avoid blurred poles + if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; - getExtension( 'WEBGL_depth_texture' ); - getExtension( 'OES_texture_float' ); - getExtension( 'OES_texture_half_float' ); - getExtension( 'OES_texture_half_float_linear' ); - getExtension( 'OES_standard_derivatives' ); - getExtension( 'OES_element_index_uint' ); - getExtension( 'OES_vertex_array_object' ); - getExtension( 'ANGLE_instanced_arrays' ); + const camera = new CubeCamera( 1, 10, this ); + camera.update( renderer, mesh ); - } + texture.minFilter = currentMinFilter; - getExtension( 'OES_texture_float_linear' ); - getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'WEBGL_multisampled_render_to_texture' ); + mesh.geometry.dispose(); + mesh.material.dispose(); - }, + return this; - get: function ( name ) { + } - const extension = getExtension( name ); + /** + * Clears this cube render target. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + clear( renderer, color = true, depth = true, stencil = true ) { - if ( extension === null ) { + const currentRenderTarget = renderer.getRenderTarget(); - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); + for ( let i = 0; i < 6; i ++ ) { - } + renderer.setRenderTarget( this, i ); - return extension; + renderer.clear( color, depth, stencil ); } - }; - -} + renderer.setRenderTarget( currentRenderTarget ); -function WebGLGeometries( gl, attributes, info, bindingStates ) { + } - const geometries = {}; - const wireframeAttributes = new WeakMap(); +} - function onGeometryDispose( event ) { +function WebGLCubeMaps( renderer ) { - const geometry = event.target; + let cubemaps = new WeakMap(); - if ( geometry.index !== null ) { + function mapTextureMapping( texture, mapping ) { - attributes.remove( geometry.index ); + if ( mapping === EquirectangularReflectionMapping ) { - } + texture.mapping = CubeReflectionMapping; - for ( const name in geometry.attributes ) { + } else if ( mapping === EquirectangularRefractionMapping ) { - attributes.remove( geometry.attributes[ name ] ); + texture.mapping = CubeRefractionMapping; } - for ( const name in geometry.morphAttributes ) { - - const array = geometry.morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.remove( array[ i ] ); + return texture; - } + } - } + function get( texture ) { - geometry.removeEventListener( 'dispose', onGeometryDispose ); + if ( texture && texture.isTexture ) { - delete geometries[ geometry.id ]; + const mapping = texture.mapping; - const attribute = wireframeAttributes.get( geometry ); + if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { - if ( attribute ) { + if ( cubemaps.has( texture ) ) { - attributes.remove( attribute ); - wireframeAttributes.delete( geometry ); + const cubemap = cubemaps.get( texture ).texture; + return mapTextureMapping( cubemap, texture.mapping ); - } + } else { - bindingStates.releaseStatesOfGeometry( geometry ); + const image = texture.image; - if ( geometry.isInstancedBufferGeometry === true ) { + if ( image && image.height > 0 ) { - delete geometry._maxInstanceCount; + const renderTarget = new WebGLCubeRenderTarget( image.height ); + renderTarget.fromEquirectangularTexture( renderer, texture ); + cubemaps.set( texture, renderTarget ); - } + texture.addEventListener( 'dispose', onTextureDispose ); - // + return mapTextureMapping( renderTarget.texture, texture.mapping ); - info.memory.geometries --; + } else { - } + // image not yet ready. try the conversion next frame - function get( object, geometry ) { + return null; - if ( geometries[ geometry.id ] === true ) return geometry; + } - geometry.addEventListener( 'dispose', onGeometryDispose ); + } - geometries[ geometry.id ] = true; + } - info.memory.geometries ++; + } - return geometry; + return texture; } - function update( geometry ) { + function onTextureDispose( event ) { - const geometryAttributes = geometry.attributes; + const texture = event.target; - // Updating index buffer in VAO now. See WebGLBindingStates. + texture.removeEventListener( 'dispose', onTextureDispose ); - for ( const name in geometryAttributes ) { + const cubemap = cubemaps.get( texture ); - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); + if ( cubemap !== undefined ) { - } + cubemaps.delete( texture ); + cubemap.dispose(); - // morph targets + } - const morphAttributes = geometry.morphAttributes; + } - for ( const name in morphAttributes ) { + function dispose() { - const array = morphAttributes[ name ]; + cubemaps = new WeakMap(); - for ( let i = 0, l = array.length; i < l; i ++ ) { + } - attributes.update( array[ i ], gl.ARRAY_BUFFER ); + return { + get: get, + dispose: dispose + }; - } +} - } +/** + * Camera that uses [orthographic projection]{@link https://en.wikipedia.org/wiki/Orthographic_projection}. + * + * In this projection mode, an object's size in the rendered image stays + * constant regardless of its distance from the camera. This can be useful + * for rendering 2D scenes and UI elements, amongst other things. + * + * ```js + * const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class OrthographicCamera extends Camera { - } + /** + * Constructs a new orthographic camera. + * + * @param {number} [left=-1] - The left plane of the camera's frustum. + * @param {number} [right=1] - The right plane of the camera's frustum. + * @param {number} [top=1] - The top plane of the camera's frustum. + * @param {number} [bottom=-1] - The bottom plane of the camera's frustum. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000 ) { - function updateWireframeAttribute( geometry ) { + super(); - const indices = []; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOrthographicCamera = true; - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - let version = 0; + this.type = 'OrthographicCamera'; - if ( geometryIndex !== null ) { + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; - const array = geometryIndex.array; - version = geometryIndex.version; + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; - for ( let i = 0, l = array.length; i < l; i += 3 ) { + /** + * The left plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.left = left; - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; + /** + * The right plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.right = right; - indices.push( a, b, b, c, c, a ); + /** + * The top plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.top = top; - } + /** + * The bottom plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.bottom = bottom; - } else if ( geometryPosition !== undefined ) { + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link OrthographicCamera#far}. + * + * Note that, unlike for the {@link PerspectiveCamera}, `0` is a + * valid value for an orthographic camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; - const array = geometryPosition.array; - version = geometryPosition.version; + /** + * The camera's far plane. Must be greater than the + * current value of {@link OrthographicCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { + this.updateProjectionMatrix(); - const a = i + 0; - const b = i + 1; - const c = i + 2; + } - indices.push( a, b, b, c, c, a ); + copy( source, recursive ) { - } + super.copy( source, recursive ); - } else { + this.left = source.left; + this.right = source.right; + this.top = source.top; + this.bottom = source.bottom; + this.near = source.near; + this.far = source.far; - return; + this.zoom = source.zoom; + this.view = source.view === null ? null : Object.assign( {}, source.view ); - } + return this; - const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = version; + } - // Updating index buffer in VAO now. See WebGLBindingStates + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + * @see {@link PerspectiveCamera#setViewOffset} + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - // + if ( this.view === null ) { - const previousAttribute = wireframeAttributes.get( geometry ); + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; - if ( previousAttribute ) attributes.remove( previousAttribute ); + } - // + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; - wireframeAttributes.set( geometry, attribute ); + this.updateProjectionMatrix(); } - function getWireframeAttribute( geometry ) { + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { - const currentAttribute = wireframeAttributes.get( geometry ); + if ( this.view !== null ) { - if ( currentAttribute ) { + this.view.enabled = false; - const geometryIndex = geometry.index; + } - if ( geometryIndex !== null ) { + this.updateProjectionMatrix(); - // if the attribute is obsolete, create a new one + } - if ( currentAttribute.version < geometryIndex.version ) { + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { - updateWireframeAttribute( geometry ); + const dx = ( this.right - this.left ) / ( 2 * this.zoom ); + const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); + const cx = ( this.right + this.left ) / 2; + const cy = ( this.top + this.bottom ) / 2; - } + let left = cx - dx; + let right = cx + dx; + let top = cy + dy; + let bottom = cy - dy; - } + if ( this.view !== null && this.view.enabled ) { - } else { + const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; + const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; - updateWireframeAttribute( geometry ); + left += scaleW * this.view.offsetX; + right = left + scaleW * this.view.width; + top -= scaleH * this.view.offsetY; + bottom = top - scaleH * this.view.height; } - return wireframeAttributes.get( geometry ); + this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem, this.reversedDepth ); + + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); } - return { + toJSON( meta ) { - get: get, - update: update, + const data = super.toJSON( meta ); - getWireframeAttribute: getWireframeAttribute + data.object.zoom = this.zoom; + data.object.left = this.left; + data.object.right = this.right; + data.object.top = this.top; + data.object.bottom = this.bottom; + data.object.near = this.near; + data.object.far = this.far; - }; + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + + return data; + + } } -function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { +const LOD_MIN = 4; - const isWebGL2 = capabilities.isWebGL2; +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; - let mode; +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; - function setMode( value ) { +const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); +const _clearColor = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; +let _oldXrEnabled = false; - mode = value; +// Golden Ratio +const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; +const INV_PHI = 1 / PHI; - } +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) ]; - let type, bytesPerElement; +const _origin = /*@__PURE__*/ new Vector3(); - function setIndex( value ) { +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} +*/ +class PMREMGenerator { - type = value.type; - bytesPerElement = value.bytesPerElement; + /** + * Constructs a new PMREM generator. + * + * @param {WebGLRenderer} renderer - The renderer. + */ + constructor( renderer ) { - } + this._renderer = renderer; + this._pingPongRenderTarget = null; - function render( start, count ) { + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; - gl.drawElements( mode, count, type, start * bytesPerElement ); + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; - info.update( count, mode, 1 ); + this._compileMaterial( this._blurMaterial ); } - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { - let extension, methodName; + const { + size = 256, + position = _origin, + } = options; - if ( isWebGL2 ) { + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - extension = gl; - methodName = 'drawElementsInstanced'; + this._renderer.xr.enabled = false; - } else { + this._setSize( size ); - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawElementsInstancedANGLE'; + const cubeUVRenderTarget = this._allocateTargets(); + cubeUVRenderTarget.depthBuffer = true; - if ( extension === null ) { + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + if ( sigma > 0 ) { - } + this._blur( cubeUVRenderTarget, 0, 0, sigma ); } - extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - info.update( count, mode, primcount ); + return cubeUVRenderTarget; } - function renderMultiDraw( starts, counts, drawCount ) { - - if ( drawCount === 0 ) return; - - const extension = extensions.get( 'WEBGL_multi_draw' ); - if ( extension === null ) { - - for ( let i = 0; i < drawCount; i ++ ) { + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromEquirectangular( equirectangular, renderTarget = null ) { - this.render( starts[ i ] / bytesPerElement, counts[ i ] ); + return this._fromTexture( equirectangular, renderTarget ); - } + } - } else { + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromCubemap( cubemap, renderTarget = null ) { - extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); + return this._fromTexture( cubemap, renderTarget ); - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { + } - elementCount += counts[ i ]; + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileCubemapShader() { - } + if ( this._cubemapMaterial === null ) { - info.update( elementCount, mode, 1 ); + this._cubemapMaterial = _getCubemapMaterial(); + this._compileMaterial( this._cubemapMaterial ); } } - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; - this.renderMultiDraw = renderMultiDraw; - -} - -function WebGLInfo( gl ) { - - const memory = { - geometries: 0, - textures: 0 - }; - - const render = { - frame: 0, - calls: 0, - triangles: 0, - points: 0, - lines: 0 - }; - - function update( count, mode, instanceCount ) { - - render.calls ++; - - switch ( mode ) { + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileEquirectangularShader() { - case gl.TRIANGLES: - render.triangles += instanceCount * ( count / 3 ); - break; + if ( this._equirectMaterial === null ) { - case gl.LINES: - render.lines += instanceCount * ( count / 2 ); - break; + this._equirectMaterial = _getEquirectMaterial(); + this._compileMaterial( this._equirectMaterial ); - case gl.LINE_STRIP: - render.lines += instanceCount * ( count - 1 ); - break; + } - case gl.LINE_LOOP: - render.lines += instanceCount * count; - break; + } - case gl.POINTS: - render.points += instanceCount * count; - break; + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { - default: - console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); - break; + this._dispose(); - } + if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); + if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); } - function reset() { + // private interface - render.calls = 0; - render.triangles = 0; - render.points = 0; - render.lines = 0; + _setSize( cubeSize ) { + + this._lodMax = Math.floor( Math.log2( cubeSize ) ); + this._cubeSize = Math.pow( 2, this._lodMax ); } - return { - memory: memory, - render: render, - programs: null, - autoReset: true, - reset: reset, - update: update - }; + _dispose() { -} + if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); -class DataArrayTexture extends Texture { + if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - constructor( data = null, width = 1, height = 1, depth = 1 ) { + for ( let i = 0; i < this._lodPlanes.length; i ++ ) { - super( null ); + this._lodPlanes[ i ].dispose(); - this.isDataArrayTexture = true; + } - this.image = { data, width, height, depth }; + } - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; + _cleanup( outputTarget ) { - this.wrapR = ClampToEdgeWrapping; + this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + this._renderer.xr.enabled = _oldXrEnabled; - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + outputTarget.scissorTest = false; + _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); } -} + _fromTexture( texture, renderTarget ) { -function numericalSort( a, b ) { + if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - return a[ 0 ] - b[ 0 ]; + this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); -} + } else { // Equirectangular -function absNumericalSort( a, b ) { + this._setSize( texture.image.width / 4 ); - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); + } -} + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; -function WebGLMorphtargets( gl, capabilities, textures ) { + this._renderer.xr.enabled = false; - const influencesList = {}; - const morphInfluences = new Float32Array( 8 ); - const morphTextures = new WeakMap(); - const morph = new Vector4(); + const cubeUVRenderTarget = renderTarget || this._allocateTargets(); + this._textureToCubeUV( texture, cubeUVRenderTarget ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - const workInfluences = []; + return cubeUVRenderTarget; - for ( let i = 0; i < 8; i ++ ) { + } - workInfluences[ i ] = [ i, 0 ]; + _allocateTargets() { - } + const width = 3 * Math.max( this._cubeSize, 16 * 7 ); + const height = 4 * this._cubeSize; - function update( object, geometry, program ) { + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + depthBuffer: false + }; - const objectInfluences = object.morphTargetInfluences; + const cubeUVRenderTarget = _createRenderTarget( width, height, params ); + + if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - if ( capabilities.isWebGL2 === true ) { + if ( this._pingPongRenderTarget !== null ) { - // instead of using attributes, the WebGL 2 code path encodes morph targets - // into an array of data textures. Each layer represents a single morph target. + this._dispose(); - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + } - let entry = morphTextures.get( geometry ); + this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - if ( entry === undefined || entry.count !== morphTargetsCount ) { + const { _lodMax } = this; + ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); - if ( entry !== undefined ) entry.texture.dispose(); + this._blurMaterial = _getBlurShader( _lodMax, width, height ); - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; + } - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; + return cubeUVRenderTarget; - let vertexDataCount = 0; + } - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; + _compileMaterial( material ) { - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; + const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); + this._renderer.compile( tmpMesh, _flatCamera ); - if ( width > capabilities.maxTextureSize ) { + } - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { - } + const fov = 90; + const aspect = 1; + const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); + const upSign = [ 1, -1, 1, 1, 1, 1 ]; + const forwardSign = [ 1, 1, 1, -1, -1, -1 ]; + const renderer = this._renderer; - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); + const originalAutoClear = renderer.autoClear; + const toneMapping = renderer.toneMapping; + renderer.getClearColor( _clearColor ); - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; + renderer.toneMapping = NoToneMapping; + renderer.autoClear = false; - // fill buffer + // https://github.com/mrdoob/three.js/issues/31413#issuecomment-3095966812 + const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); - const vertexDataStride = vertexDataCount * 4; + if ( reversedDepthBuffer ) { - for ( let i = 0; i < morphTargetsCount; i ++ ) { + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.clearDepth(); + renderer.setRenderTarget( null ); - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; + } - const offset = width * height * 4 * i; + const backgroundMaterial = new MeshBasicMaterial( { + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false, + } ); - for ( let j = 0; j < morphTarget.count; j ++ ) { + const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); - const stride = j * vertexDataStride; + let useSolidColor = false; + const background = scene.background; - if ( hasMorphPosition === true ) { + if ( background ) { - morph.fromBufferAttribute( morphTarget, j ); + if ( background.isColor ) { - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; + backgroundMaterial.color.copy( background ); + scene.background = null; + useSolidColor = true; - } + } - if ( hasMorphNormals === true ) { + } else { - morph.fromBufferAttribute( morphNormal, j ); + backgroundMaterial.color.copy( _clearColor ); + useSolidColor = true; - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; + } - } + for ( let i = 0; i < 6; i ++ ) { - if ( hasMorphColors === true ) { + const col = i % 3; - morph.fromBufferAttribute( morphColor, j ); + if ( col === 0 ) { - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); - } + } else if ( col === 1 ) { - } + cubeCamera.up.set( 0, 0, upSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); - } - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; + } else { - morphTextures.set( geometry, entry ); + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); - function disposeTexture() { + } - texture.dispose(); + const size = this._cubeSize; - morphTextures.delete( geometry ); + _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - geometry.removeEventListener( 'dispose', disposeTexture ); + renderer.setRenderTarget( cubeUVRenderTarget ); - } + if ( useSolidColor ) { - geometry.addEventListener( 'dispose', disposeTexture ); + renderer.render( backgroundBox, cubeCamera ); } - // - if ( object.isInstancedMesh === true && object.morphTexture !== null ) { + renderer.render( scene, cubeCamera ); - program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); + } - } else { + backgroundBox.geometry.dispose(); + backgroundBox.material.dispose(); + + renderer.toneMapping = toneMapping; + renderer.autoClear = originalAutoClear; + scene.background = background; - let morphInfluencesSum = 0; + } - for ( let i = 0; i < objectInfluences.length; i ++ ) { + _textureToCubeUV( texture, cubeUVRenderTarget ) { - morphInfluencesSum += objectInfluences[ i ]; + const renderer = this._renderer; - } + const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + if ( isCubeTexture ) { + if ( this._cubemapMaterial === null ) { - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); + this._cubemapMaterial = _getCubemapMaterial(); } - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); + this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? -1 : 1; } else { - // When object doesn't have morph target influences defined, we treat it as a 0-length array - // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences - - const length = objectInfluences === undefined ? 0 : objectInfluences.length; - - let influences = influencesList[ geometry.id ]; - - if ( influences === undefined || influences.length !== length ) { + if ( this._equirectMaterial === null ) { - // initialise list + this._equirectMaterial = _getEquirectMaterial(); - influences = []; + } - for ( let i = 0; i < length; i ++ ) { + } - influences[ i ] = [ i, 0 ]; + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + const mesh = new Mesh( this._lodPlanes[ 0 ], material ); - } + const uniforms = material.uniforms; - influencesList[ geometry.id ] = influences; + uniforms[ 'envMap' ].value = texture; - } + const size = this._cubeSize; - // Collect influences + _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - for ( let i = 0; i < length; i ++ ) { + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.render( mesh, _flatCamera ); - const influence = influences[ i ]; + } - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; + _applyPMREM( cubeUVRenderTarget ) { - } + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; - influences.sort( absNumericalSort ); + for ( let i = 1; i < n; i ++ ) { - for ( let i = 0; i < 8; i ++ ) { + const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - if ( i < length && influences[ i ][ 1 ] ) { + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; - workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; - workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; + this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); - } else { + } - workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; - workInfluences[ i ][ 1 ] = 0; + renderer.autoClear = autoClear; - } + } - } + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + * + * @private + * @param {WebGLRenderTarget} cubeUVRenderTarget + * @param {number} lodIn + * @param {number} lodOut + * @param {number} sigma + * @param {Vector3} [poleAxis] + */ + _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { - workInfluences.sort( numericalSort ); + const pingPongRenderTarget = this._pingPongRenderTarget; - const morphTargets = geometry.morphAttributes.position; - const morphNormals = geometry.morphAttributes.normal; + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis ); - let morphInfluencesSum = 0; + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis ); - for ( let i = 0; i < 8; i ++ ) { + } - const influence = workInfluences[ i ]; - const index = influence[ 0 ]; - const value = influence[ 1 ]; + _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - if ( index !== Number.MAX_SAFE_INTEGER && value ) { + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; - if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { + if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { - geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); + console.error( + 'blur direction must be either latitudinal or longitudinal!' ); - } + } - if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; - geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); + const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); + const blurUniforms = blurMaterial.uniforms; - } + const pixels = this._sizeLods[ lodIn ] - 1; + const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; - morphInfluences[ i ] = value; - morphInfluencesSum += value; + if ( samples > MAX_SAMPLES ) { - } else { + console.warn( `sigmaRadians, ${ + sigmaRadians}, is too large and will clip, as it requested ${ + samples} samples when the maximum is set to ${MAX_SAMPLES}` ); - if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { + } - geometry.deleteAttribute( 'morphTarget' + i ); + const weights = []; + let sum = 0; - } + for ( let i = 0; i < MAX_SAMPLES; ++ i ) { - if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { + const x = i / sigmaPixels; + const weight = Math.exp( - x * x / 2 ); + weights.push( weight ); - geometry.deleteAttribute( 'morphNormal' + i ); + if ( i === 0 ) { - } + sum += weight; - morphInfluences[ i ] = 0; + } else if ( i < samples ) { - } + sum += 2 * weight; } - // GLSL shader uses formula baseinfluence * base + sum(target * influence) - // This allows us to switch between absolute morphs and relative morphs without changing shader code - // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); - } - } - - return { - - update: update + for ( let i = 0; i < weights.length; i ++ ) { - }; + weights[ i ] = weights[ i ] / sum; -} + } -function WebGLObjects( gl, geometries, attributes, info ) { + blurUniforms[ 'envMap' ].value = targetIn.texture; + blurUniforms[ 'samples' ].value = samples; + blurUniforms[ 'weights' ].value = weights; + blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; - let updateMap = new WeakMap(); + if ( poleAxis ) { - function update( object ) { + blurUniforms[ 'poleAxis' ].value = poleAxis; - const frame = info.render.frame; + } - const geometry = object.geometry; - const buffergeometry = geometries.get( object, geometry ); + const { _lodMax } = this; + blurUniforms[ 'dTheta' ].value = radiansPerPixel; + blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; - // Update once per frame + const outputSize = this._sizeLods[ lodOut ]; + const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); + const y = 4 * ( this._cubeSize - outputSize ); - if ( updateMap.get( buffergeometry ) !== frame ) { + _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + renderer.setRenderTarget( targetOut ); + renderer.render( blurMesh, _flatCamera ); - geometries.update( buffergeometry ); + } - updateMap.set( buffergeometry, frame ); +} - } - if ( object.isInstancedMesh ) { - if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { +function _createPlanes( lodMax ) { - object.addEventListener( 'dispose', onInstancedMeshDispose ); + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; - } + let lod = lodMax; - if ( updateMap.get( object ) !== frame ) { + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); + for ( let i = 0; i < totalLods; i ++ ) { - if ( object.instanceColor !== null ) { + const sizeLod = Math.pow( 2, lod ); + sizeLods.push( sizeLod ); + let sigma = 1.0 / sizeLod; - attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); + if ( i > lodMax - LOD_MIN ) { - } + sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; - updateMap.set( object, frame ); + } else if ( i === 0 ) { - } + sigma = 0; } - if ( object.isSkinnedMesh ) { + sigmas.push( sigma ); - const skeleton = object.skeleton; + const texelSize = 1.0 / ( sizeLod - 2 ); + const min = - texelSize; + const max = 1 + texelSize; + const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - if ( updateMap.get( skeleton ) !== frame ) { + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; - skeleton.update(); + const position = new Float32Array( positionSize * vertices * cubeFaces ); + const uv = new Float32Array( uvSize * vertices * cubeFaces ); + const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); - updateMap.set( skeleton, frame ); + for ( let face = 0; face < cubeFaces; face ++ ) { - } + const x = ( face % 3 ) * 2 / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + position.set( coordinates, positionSize * vertices * face ); + uv.set( uv1, uvSize * vertices * face ); + const fill = [ face, face, face, face, face, face ]; + faceIndex.set( fill, faceIndexSize * vertices * face ); } - return buffergeometry; + const planes = new BufferGeometry(); + planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); + planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); + planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); + lodPlanes.push( planes ); - } + if ( lod > LOD_MIN ) { - function dispose() { + lod --; - updateMap = new WeakMap(); + } } - function onInstancedMeshDispose( event ) { - - const instancedMesh = event.target; - - instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); + return { lodPlanes, sizeLods, sigmas }; - attributes.remove( instancedMesh.instanceMatrix ); +} - if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); +function _createRenderTarget( width, height, params ) { - } + const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; - return { +} - update: update, - dispose: dispose +function _setViewport( target, x, y, width, height ) { - }; + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); } -class Data3DTexture extends Texture { +function _getBlurShader( lodMax, width, height ) { - constructor( data = null, width = 1, height = 1, depth = 1 ) { + const weights = new Float32Array( MAX_SAMPLES ); + const poleAxis = new Vector3( 0, 1, 0 ); + const shaderMaterial = new ShaderMaterial( { - // We're going to add .setXXX() methods for setting properties later. - // Users can still set in DataTexture3D directly. - // - // const texture = new THREE.DataTexture3D( data, width, height, depth ); - // texture.anisotropy = 16; - // - // See #14839 - - super( null ); - - this.isData3DTexture = true; - - this.image = { data, width, height, depth }; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; + name: 'SphericalGaussianBlur', - this.wrapR = ClampToEdgeWrapping; + defines: { + 'n': MAX_SAMPLES, + 'CUBEUV_TEXEL_WIDTH': 1.0 / width, + 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, + 'CUBEUV_MAX_MIP': `${lodMax}.0`, + }, - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + uniforms: { + 'envMap': { value: null }, + 'samples': { value: 1 }, + 'weights': { value: weights }, + 'latitudinal': { value: false }, + 'dTheta': { value: 0 }, + 'mipInt': { value: 0 }, + 'poleAxis': { value: poleAxis } + }, - } + vertexShader: _getCommonVertexShader(), -} + fragmentShader: /* glsl */` -class DepthTexture extends Texture { + precision mediump float; + precision mediump int; - constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { + varying vec3 vOutputDirection; - format = format !== undefined ? format : DepthFormat; + uniform sampler2D envMap; + uniform int samples; + uniform float weights[ n ]; + uniform bool latitudinal; + uniform float dTheta; + uniform float mipInt; + uniform vec3 poleAxis; - if ( format !== DepthFormat && format !== DepthStencilFormat ) { + #define ENVMAP_TYPE_CUBE_UV + #include - throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); + vec3 getSample( float theta, vec3 axis ) { - } + float cosTheta = cos( theta ); + // Rodrigues' axis-angle rotation + vec3 sampleDirection = vOutputDirection * cosTheta + + cross( axis, vOutputDirection ) * sin( theta ) + + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); - if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; + return bilinearCubeUV( envMap, sampleDirection, mipInt ); - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + } - this.isDepthTexture = true; + void main() { - this.image = { width: width, height: height }; + vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; + if ( all( equal( axis, vec3( 0.0 ) ) ) ) { - this.flipY = false; - this.generateMipmaps = false; + axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); - this.compareFunction = null; + } - } + axis = normalize( axis ); + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); - copy( source ) { + for ( int i = 1; i < n; i++ ) { - super.copy( source ); + if ( i >= samples ) { - this.compareFunction = source.compareFunction; + break; - return this; + } - } + float theta = dTheta * float( i ); + gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); + gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); - toJSON( meta ) { + } - const data = super.toJSON( meta ); + } + `, - if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; + blending: NoBlending, + depthTest: false, + depthWrite: false - return data; + } ); - } + return shaderMaterial; } -/** - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program )'. - * - * - * Properties of inner nodes including the top-level container: - * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [textures] ) - * - * uploads a uniform value(s) - * the 'textures' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (textures factorizations): - * - * .upload( gl, seq, values, textures ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (textures factorizations): - * - * .setValue( gl, name, value, textures ) - * - * sets uniform with name 'name' to 'value' - * - * .setOptional( gl, obj, prop ) - * - * like .set for an optional property of the object - * - */ +function _getEquirectMaterial() { + return new ShaderMaterial( { -const emptyTexture = /*@__PURE__*/ new Texture(); + name: 'EquirectangularToCubeUV', -const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); -emptyShadowTexture.compareFunction = LessEqualCompare; + uniforms: { + 'envMap': { value: null } + }, -const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); -const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); -const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); + vertexShader: _getCommonVertexShader(), -// --- Utilities --- + fragmentShader: /* glsl */` -// Array Caches (provide typed arrays for temporary by size) + precision mediump float; + precision mediump int; -const arrayCacheF32 = []; -const arrayCacheI32 = []; + varying vec3 vOutputDirection; -// Float32Array caches used for uploading Matrix uniforms + uniform sampler2D envMap; -const mat4array = new Float32Array( 16 ); -const mat3array = new Float32Array( 9 ); -const mat2array = new Float32Array( 4 ); + #include -// Flattening for arrays of vectors and matrices + void main() { -function flatten( array, nBlocks, blockSize ) { + vec3 outputDirection = normalize( vOutputDirection ); + vec2 uv = equirectUv( outputDirection ); - const firstElem = array[ 0 ]; + gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 + } + `, - const n = nBlocks * blockSize; - let r = arrayCacheF32[ n ]; + blending: NoBlending, + depthTest: false, + depthWrite: false - if ( r === undefined ) { + } ); - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; +} - } +function _getCubemapMaterial() { - if ( nBlocks !== 0 ) { + return new ShaderMaterial( { - firstElem.toArray( r, 0 ); + name: 'CubemapToCubeUV', - for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { + uniforms: { + 'envMap': { value: null }, + 'flipEnvMap': { value: -1 } + }, - offset += blockSize; - array[ i ].toArray( r, offset ); + vertexShader: _getCommonVertexShader(), - } + fragmentShader: /* glsl */` - } + precision mediump float; + precision mediump int; - return r; + uniform float flipEnvMap; -} + varying vec3 vOutputDirection; -function arraysEqual( a, b ) { + uniform samplerCube envMap; - if ( a.length !== b.length ) return false; + void main() { - for ( let i = 0, l = a.length; i < l; i ++ ) { + gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - if ( a[ i ] !== b[ i ] ) return false; + } + `, - } + blending: NoBlending, + depthTest: false, + depthWrite: false - return true; + } ); } -function copyArray( a, b ) { +function _getCommonVertexShader() { - for ( let i = 0, l = b.length; i < l; i ++ ) { + return /* glsl */` - a[ i ] = b[ i ]; + precision mediump float; + precision mediump int; - } + attribute float faceIndex; -} + varying vec3 vOutputDirection; -// Texture unit allocation + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { -function allocTexUnits( textures, n ) { + uv = 2.0 * uv - 1.0; - let r = arrayCacheI32[ n ]; + vec3 direction = vec3( uv, 1.0 ); - if ( r === undefined ) { + if ( face == 0.0 ) { - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; + direction = direction.zyx; // ( 1, v, u ) pos x - } + } else if ( face == 1.0 ) { - for ( let i = 0; i !== n; ++ i ) { + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y - r[ i ] = textures.allocateTextureUnit(); + } else if ( face == 2.0 ) { - } + direction.x *= -1.0; // ( -u, v, 1 ) pos z - return r; + } else if ( face == 3.0 ) { -} + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x -// --- Setters --- + } else if ( face == 4.0 ) { -// Note: Defining these methods externally, because they come in a bunch -// and this way their names minify. + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y -// Single scalar + } else if ( face == 5.0 ) { -function setValueV1f( gl, v ) { + direction.z *= -1.0; // ( u, v, -1 ) neg z - const cache = this.cache; + } - if ( cache[ 0 ] === v ) return; + return direction; - gl.uniform1f( this.addr, v ); + } - cache[ 0 ] = v; + void main() { + + vOutputDirection = getDirection( uv, faceIndex ); + gl_Position = vec4( position, 1.0 ); + + } + `; } -// Single float vector (from flat array or THREE.VectorN) +function WebGLCubeUVMaps( renderer ) { -function setValueV2f( gl, v ) { + let cubeUVmaps = new WeakMap(); - const cache = this.cache; + let pmremGenerator = null; - if ( v.x !== undefined ) { + function get( texture ) { - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + if ( texture && texture.isTexture ) { - gl.uniform2f( this.addr, v.x, v.y ); + const mapping = texture.mapping; - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); + const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - } + // equirect/cube map to cubeUV conversion - } else { + if ( isEquirectMap || isCubeMap ) { - if ( arraysEqual( cache, v ) ) return; + let renderTarget = cubeUVmaps.get( texture ); - gl.uniform2fv( this.addr, v ); + const currentPMREMVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : 0; - copyArray( cache, v ); + if ( texture.isRenderTargetTexture && texture.pmremVersion !== currentPMREMVersion ) { - } + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); -} + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); + renderTarget.texture.pmremVersion = texture.pmremVersion; -function setValueV3f( gl, v ) { + cubeUVmaps.set( texture, renderTarget ); - const cache = this.cache; + return renderTarget.texture; - if ( v.x !== undefined ) { + } else { - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { + if ( renderTarget !== undefined ) { - gl.uniform3f( this.addr, v.x, v.y, v.z ); + return renderTarget.texture; - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + } else { - } + const image = texture.image; - } else if ( v.r !== undefined ) { + if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - gl.uniform3f( this.addr, v.r, v.g, v.b ); + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); + renderTarget.texture.pmremVersion = texture.pmremVersion; - cache[ 0 ] = v.r; - cache[ 1 ] = v.g; - cache[ 2 ] = v.b; + cubeUVmaps.set( texture, renderTarget ); - } + texture.addEventListener( 'dispose', onTextureDispose ); - } else { + return renderTarget.texture; - if ( arraysEqual( cache, v ) ) return; + } else { - gl.uniform3fv( this.addr, v ); + // image not yet ready. try the conversion next frame - copyArray( cache, v ); + return null; - } + } -} + } -function setValueV4f( gl, v ) { + } - const cache = this.cache; + } - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + return texture; - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); + } - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + function isCubeTextureComplete( image ) { - } + let count = 0; + const length = 6; - } else { + for ( let i = 0; i < length; i ++ ) { - if ( arraysEqual( cache, v ) ) return; + if ( image[ i ] !== undefined ) count ++; - gl.uniform4fv( this.addr, v ); + } + + return count === length; - copyArray( cache, v ); } -} + function onTextureDispose( event ) { -// Single matrix (from flat array or THREE.MatrixN) + const texture = event.target; -function setValueM2( gl, v ) { + texture.removeEventListener( 'dispose', onTextureDispose ); - const cache = this.cache; - const elements = v.elements; + const cubemapUV = cubeUVmaps.get( texture ); - if ( elements === undefined ) { + if ( cubemapUV !== undefined ) { - if ( arraysEqual( cache, v ) ) return; + cubeUVmaps.delete( texture ); + cubemapUV.dispose(); - gl.uniformMatrix2fv( this.addr, false, v ); + } - copyArray( cache, v ); + } - } else { + function dispose() { - if ( arraysEqual( cache, elements ) ) return; + cubeUVmaps = new WeakMap(); - mat2array.set( elements ); + if ( pmremGenerator !== null ) { - gl.uniformMatrix2fv( this.addr, false, mat2array ); + pmremGenerator.dispose(); + pmremGenerator = null; - copyArray( cache, elements ); + } } + return { + get: get, + dispose: dispose + }; + } -function setValueM3( gl, v ) { +function WebGLExtensions( gl ) { - const cache = this.cache; - const elements = v.elements; + const extensions = {}; - if ( elements === undefined ) { + function getExtension( name ) { - if ( arraysEqual( cache, v ) ) return; + if ( extensions[ name ] !== undefined ) { - gl.uniformMatrix3fv( this.addr, false, v ); + return extensions[ name ]; - copyArray( cache, v ); + } - } else { + let extension; - if ( arraysEqual( cache, elements ) ) return; + switch ( name ) { - mat3array.set( elements ); + case 'WEBGL_depth_texture': + extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); + break; - gl.uniformMatrix3fv( this.addr, false, mat3array ); + case 'EXT_texture_filter_anisotropic': + extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + break; - copyArray( cache, elements ); + case 'WEBGL_compressed_texture_s3tc': + extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + break; - } + case 'WEBGL_compressed_texture_pvrtc': + extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); + break; -} + default: + extension = gl.getExtension( name ); -function setValueM4( gl, v ) { + } - const cache = this.cache; - const elements = v.elements; + extensions[ name ] = extension; - if ( elements === undefined ) { + return extension; - if ( arraysEqual( cache, v ) ) return; + } - gl.uniformMatrix4fv( this.addr, false, v ); + return { - copyArray( cache, v ); + has: function ( name ) { - } else { + return getExtension( name ) !== null; - if ( arraysEqual( cache, elements ) ) return; + }, - mat4array.set( elements ); + init: function () { - gl.uniformMatrix4fv( this.addr, false, mat4array ); + getExtension( 'EXT_color_buffer_float' ); + getExtension( 'WEBGL_clip_cull_distance' ); + getExtension( 'OES_texture_float_linear' ); + getExtension( 'EXT_color_buffer_half_float' ); + getExtension( 'WEBGL_multisampled_render_to_texture' ); + getExtension( 'WEBGL_render_shared_exponent' ); - copyArray( cache, elements ); + }, - } + get: function ( name ) { -} + const extension = getExtension( name ); -// Single integer / boolean + if ( extension === null ) { -function setValueV1i( gl, v ) { + warnOnce( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - const cache = this.cache; + } - if ( cache[ 0 ] === v ) return; + return extension; - gl.uniform1i( this.addr, v ); + } - cache[ 0 ] = v; + }; } -// Single integer / boolean vector (from flat array or THREE.VectorN) - -function setValueV2i( gl, v ) { +function WebGLGeometries( gl, attributes, info, bindingStates ) { - const cache = this.cache; + const geometries = {}; + const wireframeAttributes = new WeakMap(); - if ( v.x !== undefined ) { + function onGeometryDispose( event ) { - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + const geometry = event.target; - gl.uniform2i( this.addr, v.x, v.y ); + if ( geometry.index !== null ) { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + attributes.remove( geometry.index ); } - } else { + for ( const name in geometry.attributes ) { - if ( arraysEqual( cache, v ) ) return; + attributes.remove( geometry.attributes[ name ] ); - gl.uniform2iv( this.addr, v ); + } - copyArray( cache, v ); + geometry.removeEventListener( 'dispose', onGeometryDispose ); - } + delete geometries[ geometry.id ]; -} + const attribute = wireframeAttributes.get( geometry ); -function setValueV3i( gl, v ) { + if ( attribute ) { - const cache = this.cache; + attributes.remove( attribute ); + wireframeAttributes.delete( geometry ); - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { + bindingStates.releaseStatesOfGeometry( geometry ); - gl.uniform3i( this.addr, v.x, v.y, v.z ); + if ( geometry.isInstancedBufferGeometry === true ) { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + delete geometry._maxInstanceCount; } - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3iv( this.addr, v ); + // - copyArray( cache, v ); + info.memory.geometries --; } -} - -function setValueV4i( gl, v ) { - - const cache = this.cache; + function get( object, geometry ) { - if ( v.x !== undefined ) { + if ( geometries[ geometry.id ] === true ) return geometry; - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + geometry.addEventListener( 'dispose', onGeometryDispose ); - gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); + geometries[ geometry.id ] = true; - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + info.memory.geometries ++; - } + return geometry; - } else { + } - if ( arraysEqual( cache, v ) ) return; + function update( geometry ) { - gl.uniform4iv( this.addr, v ); + const geometryAttributes = geometry.attributes; - copyArray( cache, v ); + // Updating index buffer in VAO now. See WebGLBindingStates. - } + for ( const name in geometryAttributes ) { -} + attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); -// Single unsigned integer + } -function setValueV1ui( gl, v ) { + } - const cache = this.cache; + function updateWireframeAttribute( geometry ) { - if ( cache[ 0 ] === v ) return; + const indices = []; - gl.uniform1ui( this.addr, v ); + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + let version = 0; - cache[ 0 ] = v; + if ( geometryIndex !== null ) { -} + const array = geometryIndex.array; + version = geometryIndex.version; -// Single unsigned integer vector (from flat array or THREE.VectorN) + for ( let i = 0, l = array.length; i < l; i += 3 ) { -function setValueV2ui( gl, v ) { + const a = array[ i + 0 ]; + const b = array[ i + 1 ]; + const c = array[ i + 2 ]; - const cache = this.cache; + indices.push( a, b, b, c, c, a ); - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + } else if ( geometryPosition !== undefined ) { - gl.uniform2ui( this.addr, v.x, v.y ); + const array = geometryPosition.array; + version = geometryPosition.version; - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - } + const a = i + 0; + const b = i + 1; + const c = i + 2; - } else { + indices.push( a, b, b, c, c, a ); - if ( arraysEqual( cache, v ) ) return; + } - gl.uniform2uiv( this.addr, v ); + } else { - copyArray( cache, v ); + return; - } + } -} + const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + attribute.version = version; -function setValueV3ui( gl, v ) { + // Updating index buffer in VAO now. See WebGLBindingStates - const cache = this.cache; + // - if ( v.x !== undefined ) { + const previousAttribute = wireframeAttributes.get( geometry ); - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { + if ( previousAttribute ) attributes.remove( previousAttribute ); - gl.uniform3ui( this.addr, v.x, v.y, v.z ); + // - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + wireframeAttributes.set( geometry, attribute ); - } + } - } else { + function getWireframeAttribute( geometry ) { - if ( arraysEqual( cache, v ) ) return; + const currentAttribute = wireframeAttributes.get( geometry ); - gl.uniform3uiv( this.addr, v ); + if ( currentAttribute ) { - copyArray( cache, v ); + const geometryIndex = geometry.index; - } + if ( geometryIndex !== null ) { -} + // if the attribute is obsolete, create a new one -function setValueV4ui( gl, v ) { + if ( currentAttribute.version < geometryIndex.version ) { - const cache = this.cache; + updateWireframeAttribute( geometry ); - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + } - gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); + } else { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + updateWireframeAttribute( geometry ); } - } else { - - if ( arraysEqual( cache, v ) ) return; + return wireframeAttributes.get( geometry ); - gl.uniform4uiv( this.addr, v ); + } - copyArray( cache, v ); + return { - } + get: get, + update: update, -} + getWireframeAttribute: getWireframeAttribute + }; -// Single texture (2D / Cube) +} -function setValueT1( gl, v, textures ) { +function WebGLIndexedBufferRenderer( gl, extensions, info ) { - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + let mode; - if ( cache[ 0 ] !== unit ) { + function setMode( value ) { - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + mode = value; } - const emptyTexture2D = ( this.type === gl.SAMPLER_2D_SHADOW ) ? emptyShadowTexture : emptyTexture; + let type, bytesPerElement; - textures.setTexture2D( v || emptyTexture2D, unit ); + function setIndex( value ) { -} + type = value.type; + bytesPerElement = value.bytesPerElement; -function setValueT3D1( gl, v, textures ) { + } - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + function render( start, count ) { - if ( cache[ 0 ] !== unit ) { + gl.drawElements( mode, count, type, start * bytesPerElement ); - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + info.update( count, mode, 1 ); } - textures.setTexture3D( v || empty3dTexture, unit ); - -} - -function setValueT6( gl, v, textures ) { + function renderInstances( start, count, primcount ) { - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + if ( primcount === 0 ) return; - if ( cache[ 0 ] !== unit ) { + gl.drawElementsInstanced( mode, count, type, start * bytesPerElement, primcount ); - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + info.update( count, mode, primcount ); } - textures.setTextureCube( v || emptyCubeTexture, unit ); + function renderMultiDraw( starts, counts, drawCount ) { -} + if ( drawCount === 0 ) return; -function setValueT2DArray1( gl, v, textures ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - if ( cache[ 0 ] !== unit ) { + elementCount += counts[ i ]; - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + } - } + info.update( elementCount, mode, 1 ); - textures.setTexture2DArray( v || emptyArrayTexture, unit ); -} + } -// Helper to pick the right setter for the singular case + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { -function getSingularSetter( type ) { + if ( drawCount === 0 ) return; - switch ( type ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); - case 0x1406: return setValueV1f; // FLOAT - case 0x8b50: return setValueV2f; // _VEC2 - case 0x8b51: return setValueV3f; // _VEC3 - case 0x8b52: return setValueV4f; // _VEC4 + if ( extension === null ) { - case 0x8b5a: return setValueM2; // _MAT2 - case 0x8b5b: return setValueM3; // _MAT3 - case 0x8b5c: return setValueM4; // _MAT4 + for ( let i = 0; i < starts.length; i ++ ) { - case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 + renderInstances( starts[ i ] / bytesPerElement, counts[ i ], primcount[ i ] ); - case 0x1405: return setValueV1ui; // UINT - case 0x8dc6: return setValueV2ui; // _VEC2 - case 0x8dc7: return setValueV3ui; // _VEC3 - case 0x8dc8: return setValueV4ui; // _VEC4 + } - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1; + } else { - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3D1; + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, type, starts, 0, primcount, 0, drawCount ); - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArray1; + elementCount += counts[ i ] * primcount[ i ]; - } + } -} + info.update( elementCount, mode, 1 ); + } -// Array of scalars + } -function setValueV1fArray( gl, v ) { + // - gl.uniform1fv( this.addr, v ); + this.setMode = setMode; + this.setIndex = setIndex; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; } -// Array of vectors (from flat array or array of THREE.VectorN) +function WebGLInfo( gl ) { -function setValueV2fArray( gl, v ) { + const memory = { + geometries: 0, + textures: 0 + }; - const data = flatten( v, this.size, 2 ); + const render = { + frame: 0, + calls: 0, + triangles: 0, + points: 0, + lines: 0 + }; - gl.uniform2fv( this.addr, data ); + function update( count, mode, instanceCount ) { -} + render.calls ++; -function setValueV3fArray( gl, v ) { + switch ( mode ) { - const data = flatten( v, this.size, 3 ); + case gl.TRIANGLES: + render.triangles += instanceCount * ( count / 3 ); + break; - gl.uniform3fv( this.addr, data ); + case gl.LINES: + render.lines += instanceCount * ( count / 2 ); + break; -} + case gl.LINE_STRIP: + render.lines += instanceCount * ( count - 1 ); + break; -function setValueV4fArray( gl, v ) { + case gl.LINE_LOOP: + render.lines += instanceCount * count; + break; - const data = flatten( v, this.size, 4 ); + case gl.POINTS: + render.points += instanceCount * count; + break; - gl.uniform4fv( this.addr, data ); + default: + console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); + break; -} + } -// Array of matrices (from flat array or array of THREE.MatrixN) + } -function setValueM2Array( gl, v ) { + function reset() { - const data = flatten( v, this.size, 4 ); + render.calls = 0; + render.triangles = 0; + render.points = 0; + render.lines = 0; - gl.uniformMatrix2fv( this.addr, false, data ); + } + + return { + memory: memory, + render: render, + programs: null, + autoReset: true, + reset: reset, + update: update + }; } -function setValueM3Array( gl, v ) { +/** + * Creates an array of textures directly from raw buffer data. + * + * @augments Texture + */ +class DataArrayTexture extends Texture { - const data = flatten( v, this.size, 9 ); + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { - gl.uniformMatrix3fv( this.addr, false, data ); + super( null ); -} + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataArrayTexture = true; -function setValueM4Array( gl, v ) { + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; - const data = flatten( v, this.size, 16 ); + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; - gl.uniformMatrix4fv( this.addr, false, data ); + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; -} + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; -// Array of integer / boolean + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; -function setValueV1iArray( gl, v ) { + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - gl.uniform1iv( this.addr, v ); + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; -} + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set} + */ + this.layerUpdates = new Set(); -// Array of integer / boolean vectors (from flat array) + } -function setValueV2iArray( gl, v ) { + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire data texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { - gl.uniform2iv( this.addr, v ); + this.layerUpdates.add( layerIndex ); -} + } -function setValueV3iArray( gl, v ) { + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { - gl.uniform3iv( this.addr, v ); + this.layerUpdates.clear(); -} + } -function setValueV4iArray( gl, v ) { +} - gl.uniform4iv( this.addr, v ); +function WebGLMorphtargets( gl, capabilities, textures ) { -} + const morphTextures = new WeakMap(); + const morph = new Vector4(); -// Array of unsigned integer + function update( object, geometry, program ) { -function setValueV1uiArray( gl, v ) { + const objectInfluences = object.morphTargetInfluences; - gl.uniform1uiv( this.addr, v ); + // the following encodes morph targets into an array of data textures. Each layer represents a single morph target. -} + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; -// Array of unsigned integer vectors (from flat array) + let entry = morphTextures.get( geometry ); -function setValueV2uiArray( gl, v ) { + if ( entry === undefined || entry.count !== morphTargetsCount ) { - gl.uniform2uiv( this.addr, v ); + if ( entry !== undefined ) entry.texture.dispose(); -} + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; -function setValueV3uiArray( gl, v ) { + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; - gl.uniform3uiv( this.addr, v ); + let vertexDataCount = 0; -} + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; -function setValueV4uiArray( gl, v ) { + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; - gl.uniform4uiv( this.addr, v ); + if ( width > capabilities.maxTextureSize ) { -} + height = Math.ceil( width / capabilities.maxTextureSize ); + width = capabilities.maxTextureSize; + } -// Array of textures (2D / 3D / Cube / 2DArray) + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); -function setValueT1Array( gl, v, textures ) { + const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + texture.type = FloatType; + texture.needsUpdate = true; - const cache = this.cache; + // fill buffer - const n = v.length; + const vertexDataStride = vertexDataCount * 4; - const units = allocTexUnits( textures, n ); + for ( let i = 0; i < morphTargetsCount; i ++ ) { - if ( ! arraysEqual( cache, units ) ) { + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; - gl.uniform1iv( this.addr, units ); + const offset = width * height * 4 * i; - copyArray( cache, units ); + for ( let j = 0; j < morphTarget.count; j ++ ) { - } + const stride = j * vertexDataStride; - for ( let i = 0; i !== n; ++ i ) { + if ( hasMorphPosition === true ) { - textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); + morph.fromBufferAttribute( morphTarget, j ); - } + buffer[ offset + stride + 0 ] = morph.x; + buffer[ offset + stride + 1 ] = morph.y; + buffer[ offset + stride + 2 ] = morph.z; + buffer[ offset + stride + 3 ] = 0; -} + } -function setValueT3DArray( gl, v, textures ) { + if ( hasMorphNormals === true ) { - const cache = this.cache; + morph.fromBufferAttribute( morphNormal, j ); - const n = v.length; + buffer[ offset + stride + 4 ] = morph.x; + buffer[ offset + stride + 5 ] = morph.y; + buffer[ offset + stride + 6 ] = morph.z; + buffer[ offset + stride + 7 ] = 0; - const units = allocTexUnits( textures, n ); + } - if ( ! arraysEqual( cache, units ) ) { + if ( hasMorphColors === true ) { - gl.uniform1iv( this.addr, units ); + morph.fromBufferAttribute( morphColor, j ); - copyArray( cache, units ); + buffer[ offset + stride + 8 ] = morph.x; + buffer[ offset + stride + 9 ] = morph.y; + buffer[ offset + stride + 10 ] = morph.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - } + } - for ( let i = 0; i !== n; ++ i ) { + } - textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); + } - } + entry = { + count: morphTargetsCount, + texture: texture, + size: new Vector2( width, height ) + }; -} + morphTextures.set( geometry, entry ); -function setValueT6Array( gl, v, textures ) { + function disposeTexture() { - const cache = this.cache; + texture.dispose(); - const n = v.length; + morphTextures.delete( geometry ); - const units = allocTexUnits( textures, n ); + geometry.removeEventListener( 'dispose', disposeTexture ); - if ( ! arraysEqual( cache, units ) ) { + } - gl.uniform1iv( this.addr, units ); + geometry.addEventListener( 'dispose', disposeTexture ); - copyArray( cache, units ); + } - } + // + if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - for ( let i = 0; i !== n; ++ i ) { + program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); - textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); + } else { - } + let morphInfluencesSum = 0; -} + for ( let i = 0; i < objectInfluences.length; i ++ ) { -function setValueT2DArrayArray( gl, v, textures ) { + morphInfluencesSum += objectInfluences[ i ]; - const cache = this.cache; + } - const n = v.length; + const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - const units = allocTexUnits( textures, n ); - if ( ! arraysEqual( cache, units ) ) { + program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); + program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - gl.uniform1iv( this.addr, units ); + } - copyArray( cache, units ); + program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); + program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); } - for ( let i = 0; i !== n; ++ i ) { + return { - textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); + update: update - } + }; } +function WebGLObjects( gl, geometries, attributes, info ) { -// Helper to pick the right setter for a pure (bottom-level) array + let updateMap = new WeakMap(); -function getPureArraySetter( type ) { + function update( object ) { - switch ( type ) { - - case 0x1406: return setValueV1fArray; // FLOAT - case 0x8b50: return setValueV2fArray; // _VEC2 - case 0x8b51: return setValueV3fArray; // _VEC3 - case 0x8b52: return setValueV4fArray; // _VEC4 + const frame = info.render.frame; - case 0x8b5a: return setValueM2Array; // _MAT2 - case 0x8b5b: return setValueM3Array; // _MAT3 - case 0x8b5c: return setValueM4Array; // _MAT4 + const geometry = object.geometry; + const buffergeometry = geometries.get( object, geometry ); - case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 + // Update once per frame - case 0x1405: return setValueV1uiArray; // UINT - case 0x8dc6: return setValueV2uiArray; // _VEC2 - case 0x8dc7: return setValueV3uiArray; // _VEC3 - case 0x8dc8: return setValueV4uiArray; // _VEC4 + if ( updateMap.get( buffergeometry ) !== frame ) { - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1Array; + geometries.update( buffergeometry ); - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3DArray; + updateMap.set( buffergeometry, frame ); - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6Array; + } - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArrayArray; + if ( object.isInstancedMesh ) { - } + if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { -} + object.addEventListener( 'dispose', onInstancedMeshDispose ); -// --- Uniform Classes --- + } -class SingleUniform { + if ( updateMap.get( object ) !== frame ) { - constructor( id, activeInfo, addr ) { + attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - this.id = id; - this.addr = addr; - this.cache = []; - this.type = activeInfo.type; - this.setValue = getSingularSetter( activeInfo.type ); + if ( object.instanceColor !== null ) { - // this.path = activeInfo.name; // DEBUG + attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - } + } -} + updateMap.set( object, frame ); -class PureArrayUniform { + } - constructor( id, activeInfo, addr ) { + } - this.id = id; - this.addr = addr; - this.cache = []; - this.type = activeInfo.type; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); + if ( object.isSkinnedMesh ) { - // this.path = activeInfo.name; // DEBUG + const skeleton = object.skeleton; - } + if ( updateMap.get( skeleton ) !== frame ) { -} + skeleton.update(); -class StructuredUniform { + updateMap.set( skeleton, frame ); - constructor( id ) { + } - this.id = id; + } - this.seq = []; - this.map = {}; + return buffergeometry; } - setValue( gl, value, textures ) { - - const seq = this.seq; + function dispose() { - for ( let i = 0, n = seq.length; i !== n; ++ i ) { + updateMap = new WeakMap(); - const u = seq[ i ]; - u.setValue( gl, value[ u.id ], textures ); + } - } + function onInstancedMeshDispose( event ) { - } + const instancedMesh = event.target; -} + instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); -// --- Top-level --- + attributes.remove( instancedMesh.instanceMatrix ); -// Parser - builds up the property tree from the path strings + if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); -const RePathPart = /(\w+)(\])?(\[|\.)?/g; + } -// extracts -// - the identifier (member name or array index) -// - followed by an optional right bracket (found when array index) -// - followed by an optional left bracket or dot (type of subscript) -// -// Note: These portions can be read in a non-overlapping fashion and -// allow straightforward parsing of the hierarchy that WebGL encodes -// in the uniform names. + return { -function addUniform( container, uniformObject ) { + update: update, + dispose: dispose - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; + }; } -function parseUniform( activeInfo, addr, container ) { +/** + * Creates a three-dimensional texture from raw data, with parameters to + * divide it into width, height, and depth. + * + * @augments Texture + */ +class Data3DTexture extends Texture { - const path = activeInfo.name, - pathLength = path.length; + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; + // We're going to add .setXXX() methods for setting properties later. + // Users can still set in Data3DTexture directly. + // + // const texture = new THREE.Data3DTexture( data, width, height, depth ); + // texture.anisotropy = 16; + // + // See #14839 - while ( true ) { + super( null ); - const match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isData3DTexture = true; - let id = match[ 1 ]; - const idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; - if ( idIsIndex ) id = id | 0; // convert to integer + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; - // bare name or "pure" bottom-level array "[0]" suffix + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - break; + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - } else { + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; - // step into inner node / create it in case it doesn't exist + } - const map = container.map; - let next = map[ id ]; +} - if ( next === undefined ) { +/** + * This class can be used to automatically save the depth information of a + * rendering into a texture. + * + * @augments Texture + */ +class DepthTexture extends Texture { - next = new StructuredUniform( id ); - addUniform( container, next ); + /** + * Constructs a new depth texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [type=UnsignedIntType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {number} [format=DepthFormat] - The texture format. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) { - } + if ( format !== DepthFormat && format !== DepthStencilFormat ) { - container = next; + throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); } - } + const image = { width: width, height: height, depth: depth }; -} + super( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); -// Root Container + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDepthTexture = true; -class WebGLUniforms { + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - constructor( gl, program ) { + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - this.seq = []; - this.map = {}; + /** + * Code corresponding to the depth compare function. + * + * @type {?(NeverCompare|LessCompare|EqualCompare|LessEqualCompare|GreaterCompare|NotEqualCompare|GreaterEqualCompare|AlwaysCompare)} + * @default null + */ + this.compareFunction = null; - const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); + } - for ( let i = 0; i < n; ++ i ) { - const info = gl.getActiveUniform( program, i ), - addr = gl.getUniformLocation( program, info.name ); + copy( source ) { - parseUniform( info, addr, this ); + super.copy( source ); - } + this.source = new Source( Object.assign( {}, source.image ) ); // see #30540 + this.compareFunction = source.compareFunction; + + return this; } - setValue( gl, name, value, textures ) { + toJSON( meta ) { - const u = this.map[ name ]; + const data = super.toJSON( meta ); - if ( u !== undefined ) u.setValue( gl, value, textures ); + if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; + + return data; } - setOptional( gl, object, name ) { +} - const v = object[ name ]; +/** + * Uniforms of a program. + * Those form a tree structure with a special top-level container for the root, + * which you get by calling 'new WebGLUniforms( gl, program )'. + * + * + * Properties of inner nodes including the top-level container: + * + * .seq - array of nested uniforms + * .map - nested uniforms by name + * + * + * Methods of all nodes except the top-level container: + * + * .setValue( gl, value, [textures] ) + * + * uploads a uniform value(s) + * the 'textures' parameter is needed for sampler uniforms + * + * + * Static methods of the top-level container (textures factorizations): + * + * .upload( gl, seq, values, textures ) + * + * sets uniforms in 'seq' to 'values[id].value' + * + * .seqWithValue( seq, values ) : filteredSeq + * + * filters 'seq' entries with corresponding entry in values + * + * + * Methods of the top-level container (textures factorizations): + * + * .setValue( gl, name, value, textures ) + * + * sets uniform with name 'name' to 'value' + * + * .setOptional( gl, obj, prop ) + * + * like .set for an optional property of the object + * + */ - if ( v !== undefined ) this.setValue( gl, name, v ); - } +const emptyTexture = /*@__PURE__*/ new Texture(); - static upload( gl, seq, values, textures ) { +const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); - for ( let i = 0, n = seq.length; i !== n; ++ i ) { +const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); +const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); +const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - const u = seq[ i ], - v = values[ u.id ]; +// --- Utilities --- - if ( v.needsUpdate !== false ) { +// Array Caches (provide typed arrays for temporary by size) - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, textures ); +const arrayCacheF32 = []; +const arrayCacheI32 = []; - } +// Float32Array caches used for uploading Matrix uniforms - } +const mat4array = new Float32Array( 16 ); +const mat3array = new Float32Array( 9 ); +const mat2array = new Float32Array( 4 ); - } +// Flattening for arrays of vectors and matrices - static seqWithValue( seq, values ) { +function flatten( array, nBlocks, blockSize ) { - const r = []; + const firstElem = array[ 0 ]; - for ( let i = 0, n = seq.length; i !== n; ++ i ) { + if ( firstElem <= 0 || firstElem > 0 ) return array; + // unoptimized: ! isNaN( firstElem ) + // see http://jacksondunstan.com/articles/983 - const u = seq[ i ]; - if ( u.id in values ) r.push( u ); + const n = nBlocks * blockSize; + let r = arrayCacheF32[ n ]; - } + if ( r === undefined ) { - return r; + r = new Float32Array( n ); + arrayCacheF32[ n ] = r; } -} - -function WebGLShader( gl, type, string ) { + if ( nBlocks !== 0 ) { - const shader = gl.createShader( type ); + firstElem.toArray( r, 0 ); - gl.shaderSource( shader, string ); - gl.compileShader( shader ); + for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - return shader; + offset += blockSize; + array[ i ].toArray( r, offset ); -} + } -// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ -const COMPLETION_STATUS_KHR = 0x91B1; + } -let programIdCount = 0; + return r; -function handleSource( string, errorLine ) { +} - const lines = string.split( '\n' ); - const lines2 = []; +function arraysEqual( a, b ) { - const from = Math.max( errorLine - 6, 0 ); - const to = Math.min( errorLine + 6, lines.length ); + if ( a.length !== b.length ) return false; - for ( let i = from; i < to; i ++ ) { + for ( let i = 0, l = a.length; i < l; i ++ ) { - const line = i + 1; - lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); + if ( a[ i ] !== b[ i ] ) return false; } - return lines2.join( '\n' ); + return true; } -function getEncodingComponents( colorSpace ) { - - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const encodingPrimaries = ColorManagement.getPrimaries( colorSpace ); +function copyArray( a, b ) { - let gamutMapping; + for ( let i = 0, l = b.length; i < l; i ++ ) { - if ( workingPrimaries === encodingPrimaries ) { + a[ i ] = b[ i ]; - gamutMapping = ''; + } - } else if ( workingPrimaries === P3Primaries && encodingPrimaries === Rec709Primaries ) { +} - gamutMapping = 'LinearDisplayP3ToLinearSRGB'; +// Texture unit allocation - } else if ( workingPrimaries === Rec709Primaries && encodingPrimaries === P3Primaries ) { +function allocTexUnits( textures, n ) { - gamutMapping = 'LinearSRGBToLinearDisplayP3'; + let r = arrayCacheI32[ n ]; - } + if ( r === undefined ) { - switch ( colorSpace ) { + r = new Int32Array( n ); + arrayCacheI32[ n ] = r; - case LinearSRGBColorSpace: - case LinearDisplayP3ColorSpace: - return [ gamutMapping, 'LinearTransferOETF' ]; + } - case SRGBColorSpace: - case DisplayP3ColorSpace: - return [ gamutMapping, 'sRGBTransferOETF' ]; + for ( let i = 0; i !== n; ++ i ) { - default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ gamutMapping, 'LinearTransferOETF' ]; + r[ i ] = textures.allocateTextureUnit(); } -} + return r; -function getShaderErrors( gl, shader, type ) { +} - const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - const errors = gl.getShaderInfoLog( shader ).trim(); +// --- Setters --- - if ( status && errors === '' ) return ''; +// Note: Defining these methods externally, because they come in a bunch +// and this way their names minify. - const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); - if ( errorMatches ) { +// Single scalar - // --enable-privileged-webgl-extension - // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); +function setValueV1f( gl, v ) { - const errorLine = parseInt( errorMatches[ 1 ] ); - return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); + const cache = this.cache; - } else { + if ( cache[ 0 ] === v ) return; - return errors; + gl.uniform1f( this.addr, v ); - } + cache[ 0 ] = v; } -function getTexelEncodingFunction( functionName, colorSpace ) { - - const components = getEncodingComponents( colorSpace ); - return `vec4 ${functionName}( vec4 value ) { return ${components[ 0 ]}( ${components[ 1 ]}( value ) ); }`; - -} +// Single float vector (from flat array or THREE.VectorN) -function getToneMappingFunction( functionName, toneMapping ) { +function setValueV2f( gl, v ) { - let toneMappingName; + const cache = this.cache; - switch ( toneMapping ) { + if ( v.x !== undefined ) { - case LinearToneMapping: - toneMappingName = 'Linear'; - break; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - case ReinhardToneMapping: - toneMappingName = 'Reinhard'; - break; + gl.uniform2f( this.addr, v.x, v.y ); - case CineonToneMapping: - toneMappingName = 'OptimizedCineon'; - break; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - case ACESFilmicToneMapping: - toneMappingName = 'ACESFilmic'; - break; + } - case AgXToneMapping: - toneMappingName = 'AgX'; - break; + } else { - case NeutralToneMapping: - toneMappingName = 'Neutral'; - break; + if ( arraysEqual( cache, v ) ) return; - case CustomToneMapping: - toneMappingName = 'Custom'; - break; + gl.uniform2fv( this.addr, v ); - default: - console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); - toneMappingName = 'Linear'; + copyArray( cache, v ); } - return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - } -function generateExtensions( parameters ) { +function setValueV3f( gl, v ) { - const chunks = [ - ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.alphaToCoverage || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', - ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; + const cache = this.cache; - return chunks.filter( filterEmptyLine ).join( '\n' ); + if ( v.x !== undefined ) { -} + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { -function generateVertexExtensions( parameters ) { + gl.uniform3f( this.addr, v.x, v.y, v.z ); - const chunks = [ - parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '', - parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '', - ]; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - return chunks.filter( filterEmptyLine ).join( '\n' ); + } -} + } else if ( v.r !== undefined ) { -function generateDefines( defines ) { + if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - const chunks = []; + gl.uniform3f( this.addr, v.r, v.g, v.b ); - for ( const name in defines ) { + cache[ 0 ] = v.r; + cache[ 1 ] = v.g; + cache[ 2 ] = v.b; - const value = defines[ name ]; + } - if ( value === false ) continue; + } else { - chunks.push( '#define ' + name + ' ' + value ); + if ( arraysEqual( cache, v ) ) return; - } + gl.uniform3fv( this.addr, v ); - return chunks.join( '\n' ); + copyArray( cache, v ); + + } } -function fetchAttributeLocations( gl, program ) { +function setValueV4f( gl, v ) { - const attributes = {}; + const cache = this.cache; - const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); + if ( v.x !== undefined ) { - for ( let i = 0; i < n; i ++ ) { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - const info = gl.getActiveAttrib( program, i ); - const name = info.name; - - let locationSize = 1; - if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; - if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; - if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; + gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - attributes[ name ] = { - type: info.type, - location: gl.getAttribLocation( program, name ), - locationSize: locationSize - }; + } - } + } else { - return attributes; + if ( arraysEqual( cache, v ) ) return; -} + gl.uniform4fv( this.addr, v ); -function filterEmptyLine( string ) { + copyArray( cache, v ); - return string !== ''; + } } -function replaceLightNums( string, parameters ) { +// Single matrix (from flat array or THREE.MatrixN) - const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; +function setValueM2( gl, v ) { - return string - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) - .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) - .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) - .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) - .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) - .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); + const cache = this.cache; + const elements = v.elements; -} + if ( elements === undefined ) { -function replaceClippingPlaneNums( string, parameters ) { + if ( arraysEqual( cache, v ) ) return; - return string - .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) - .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); + gl.uniformMatrix2fv( this.addr, false, v ); -} + copyArray( cache, v ); -// Resolve Includes + } else { -const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; + if ( arraysEqual( cache, elements ) ) return; -function resolveIncludes( string ) { + mat2array.set( elements ); - return string.replace( includePattern, includeReplacer ); + gl.uniformMatrix2fv( this.addr, false, mat2array ); + + copyArray( cache, elements ); + + } } -const shaderChunkMap = new Map( [ - [ 'encodings_fragment', 'colorspace_fragment' ], // @deprecated, r154 - [ 'encodings_pars_fragment', 'colorspace_pars_fragment' ], // @deprecated, r154 - [ 'output_fragment', 'opaque_fragment' ], // @deprecated, r154 -] ); +function setValueM3( gl, v ) { -function includeReplacer( match, include ) { + const cache = this.cache; + const elements = v.elements; - let string = ShaderChunk[ include ]; + if ( elements === undefined ) { - if ( string === undefined ) { + if ( arraysEqual( cache, v ) ) return; - const newInclude = shaderChunkMap.get( include ); + gl.uniformMatrix3fv( this.addr, false, v ); - if ( newInclude !== undefined ) { + copyArray( cache, v ); - string = ShaderChunk[ newInclude ]; - console.warn( 'THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); + } else { - } else { + if ( arraysEqual( cache, elements ) ) return; - throw new Error( 'Can not resolve #include <' + include + '>' ); + mat3array.set( elements ); - } + gl.uniformMatrix3fv( this.addr, false, mat3array ); - } + copyArray( cache, elements ); - return resolveIncludes( string ); + } } -// Unroll Loops +function setValueM4( gl, v ) { -const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; + const cache = this.cache; + const elements = v.elements; -function unrollLoops( string ) { + if ( elements === undefined ) { - return string.replace( unrollLoopPattern, loopReplacer ); + if ( arraysEqual( cache, v ) ) return; -} + gl.uniformMatrix4fv( this.addr, false, v ); -function loopReplacer( match, start, end, snippet ) { + copyArray( cache, v ); - let string = ''; + } else { - for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { + if ( arraysEqual( cache, elements ) ) return; - string += snippet - .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) - .replace( /UNROLLED_LOOP_INDEX/g, i ); + mat4array.set( elements ); - } + gl.uniformMatrix4fv( this.addr, false, mat4array ); - return string; + copyArray( cache, elements ); -} + } -// +} -function generatePrecision( parameters ) { +// Single integer / boolean - let precisionstring = `precision ${parameters.precision} float; - precision ${parameters.precision} int; - precision ${parameters.precision} sampler2D; - precision ${parameters.precision} samplerCube; - `; +function setValueV1i( gl, v ) { - if ( parameters.isWebGL2 ) { + const cache = this.cache; - precisionstring += `precision ${parameters.precision} sampler3D; - precision ${parameters.precision} sampler2DArray; - precision ${parameters.precision} sampler2DShadow; - precision ${parameters.precision} samplerCubeShadow; - precision ${parameters.precision} sampler2DArrayShadow; - precision ${parameters.precision} isampler2D; - precision ${parameters.precision} isampler3D; - precision ${parameters.precision} isamplerCube; - precision ${parameters.precision} isampler2DArray; - precision ${parameters.precision} usampler2D; - precision ${parameters.precision} usampler3D; - precision ${parameters.precision} usamplerCube; - precision ${parameters.precision} usampler2DArray; - `; + if ( cache[ 0 ] === v ) return; - } + gl.uniform1i( this.addr, v ); - if ( parameters.precision === 'highp' ) { + cache[ 0 ] = v; - precisionstring += '\n#define HIGH_PRECISION'; +} - } else if ( parameters.precision === 'mediump' ) { +// Single integer / boolean vector (from flat array or THREE.VectorN) - precisionstring += '\n#define MEDIUM_PRECISION'; +function setValueV2i( gl, v ) { - } else if ( parameters.precision === 'lowp' ) { + const cache = this.cache; - precisionstring += '\n#define LOW_PRECISION'; + if ( v.x !== undefined ) { - } + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - return precisionstring; + gl.uniform2i( this.addr, v.x, v.y ); -} + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; -function generateShadowMapTypeDefine( parameters ) { + } - let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; + } else { - if ( parameters.shadowMapType === PCFShadowMap ) { + if ( arraysEqual( cache, v ) ) return; - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; + gl.uniform2iv( this.addr, v ); - } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { + copyArray( cache, v ); - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; + } - } else if ( parameters.shadowMapType === VSMShadowMap ) { +} - shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; +function setValueV3i( gl, v ) { - } + const cache = this.cache; - return shadowMapTypeDefine; + if ( v.x !== undefined ) { -} + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { -function generateEnvMapTypeDefine( parameters ) { + gl.uniform3i( this.addr, v.x, v.y, v.z ); - let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - if ( parameters.envMap ) { + } - switch ( parameters.envMapMode ) { + } else { - case CubeReflectionMapping: - case CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; + if ( arraysEqual( cache, v ) ) return; - case CubeUVReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; - break; + gl.uniform3iv( this.addr, v ); - } + copyArray( cache, v ); } - return envMapTypeDefine; - } -function generateEnvMapModeDefine( parameters ) { +function setValueV4i( gl, v ) { - let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; + const cache = this.cache; - if ( parameters.envMap ) { + if ( v.x !== undefined ) { - switch ( parameters.envMapMode ) { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - case CubeRefractionMapping: + gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; } - } - - return envMapModeDefine; + } else { -} + if ( arraysEqual( cache, v ) ) return; -function generateEnvMapBlendingDefine( parameters ) { + gl.uniform4iv( this.addr, v ); - let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; + copyArray( cache, v ); - if ( parameters.envMap ) { + } - switch ( parameters.combine ) { +} - case MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; +// Single unsigned integer - case MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; +function setValueV1ui( gl, v ) { - case AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; + const cache = this.cache; - } + if ( cache[ 0 ] === v ) return; - } + gl.uniform1ui( this.addr, v ); - return envMapBlendingDefine; + cache[ 0 ] = v; } -function generateCubeUVSize( parameters ) { - - const imageHeight = parameters.envMapCubeUVHeight; +// Single unsigned integer vector (from flat array or THREE.VectorN) - if ( imageHeight === null ) return null; +function setValueV2ui( gl, v ) { - const maxMip = Math.log2( imageHeight ) - 2; + const cache = this.cache; - const texelHeight = 1.0 / imageHeight; + if ( v.x !== undefined ) { - const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - return { texelWidth, texelHeight, maxMip }; + gl.uniform2ui( this.addr, v.x, v.y ); -} + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; -function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { + } - // TODO Send this event to Three.js DevTools - // console.log( 'WebGLProgram', cacheKey ); + } else { - const gl = renderer.getContext(); + if ( arraysEqual( cache, v ) ) return; - const defines = parameters.defines; + gl.uniform2uiv( this.addr, v ); - let vertexShader = parameters.vertexShader; - let fragmentShader = parameters.fragmentShader; + copyArray( cache, v ); - const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); - const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); - const envMapModeDefine = generateEnvMapModeDefine( parameters ); - const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); - const envMapCubeUVSize = generateCubeUVSize( parameters ); + } - const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); +} - const customVertexExtensions = generateVertexExtensions( parameters ); +function setValueV3ui( gl, v ) { - const customDefines = generateDefines( defines ); + const cache = this.cache; - const program = gl.createProgram(); + if ( v.x !== undefined ) { - let prefixVertex, prefixFragment; - let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - if ( parameters.isRawShaderMaterial ) { + gl.uniform3ui( this.addr, v.x, v.y, v.z ); - prefixVertex = [ + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, + } - customDefines + } else { - ].filter( filterEmptyLine ).join( '\n' ); + if ( arraysEqual( cache, v ) ) return; - if ( prefixVertex.length > 0 ) { + gl.uniform3uiv( this.addr, v ); - prefixVertex += '\n'; + copyArray( cache, v ); - } + } - prefixFragment = [ +} - customExtensions, +function setValueV4ui( gl, v ) { - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, + const cache = this.cache; - customDefines + if ( v.x !== undefined ) { - ].filter( filterEmptyLine ).join( '\n' ); + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - if ( prefixFragment.length > 0 ) { + gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - prefixFragment += '\n'; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; } } else { - prefixVertex = [ - - generatePrecision( parameters ), - - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, + if ( arraysEqual( cache, v ) ) return; - customDefines, + gl.uniform4uiv( this.addr, v ); - parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', - parameters.batching ? '#define USE_BATCHING' : '', - parameters.instancing ? '#define USE_INSTANCING' : '', - parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', - parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', + copyArray( cache, v ); - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', + } - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', +} - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', +// Single texture (2D / Cube) - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', +function setValueT1( gl, v, textures ) { - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaHash ? '#define USE_ALPHAHASH' : '', + if ( cache[ 0 ] !== unit ) { - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', + } - // + let emptyTexture2D; - parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', - parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', - parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', - parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', - parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', - parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', - parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', - parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', + if ( this.type === gl.SAMPLER_2D_SHADOW ) { - parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', - parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', + emptyShadowTexture.compareFunction = LessEqualCompare; // #28670 + emptyTexture2D = emptyShadowTexture; - parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', + } else { - parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', - parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', - parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', + emptyTexture2D = emptyTexture; - parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', - parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', + } - parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', - parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', + textures.setTexture2D( v || emptyTexture2D, unit ); - parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', - parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', - parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', +} - parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', - parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', +function setValueT3D1( gl, v, textures ) { - // + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', + if ( cache[ 0 ] !== unit ) { - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - parameters.flatShading ? '#define FLAT_SHADED' : '', + } - parameters.skinning ? '#define USE_SKINNING' : '', + textures.setTexture3D( v || empty3dTexture, unit ); - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', +} - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', +function setValueT6( gl, v, textures ) { - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', + if ( cache[ 0 ] !== unit ) { - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + } - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', + textures.setTextureCube( v || emptyCubeTexture, unit ); - '#ifdef USE_INSTANCING', +} - ' attribute mat4 instanceMatrix;', +function setValueT2DArray1( gl, v, textures ) { - '#endif', + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - '#ifdef USE_INSTANCING_COLOR', + if ( cache[ 0 ] !== unit ) { - ' attribute vec3 instanceColor;', + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - '#endif', + } - '#ifdef USE_INSTANCING_MORPH', + textures.setTexture2DArray( v || emptyArrayTexture, unit ); - ' uniform sampler2D morphTexture;', +} - '#endif', +// Helper to pick the right setter for the singular case - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', +function getSingularSetter( type ) { - '#ifdef USE_UV1', + switch ( type ) { - ' attribute vec2 uv1;', + case 0x1406: return setValueV1f; // FLOAT + case 0x8b50: return setValueV2f; // _VEC2 + case 0x8b51: return setValueV3f; // _VEC3 + case 0x8b52: return setValueV4f; // _VEC4 - '#endif', + case 0x8b5a: return setValueM2; // _MAT2 + case 0x8b5b: return setValueM3; // _MAT3 + case 0x8b5c: return setValueM4; // _MAT4 - '#ifdef USE_UV2', + case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 - ' attribute vec2 uv2;', + case 0x1405: return setValueV1ui; // UINT + case 0x8dc6: return setValueV2ui; // _VEC2 + case 0x8dc7: return setValueV3ui; // _VEC3 + case 0x8dc8: return setValueV4ui; // _VEC4 - '#endif', + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1; - '#ifdef USE_UV3', + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3D1; - ' attribute vec2 uv3;', + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6; - '#endif', + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArray1; - '#ifdef USE_TANGENT', + } - ' attribute vec4 tangent;', +} - '#endif', - '#if defined( USE_COLOR_ALPHA )', +// Array of scalars - ' attribute vec4 color;', +function setValueV1fArray( gl, v ) { - '#elif defined( USE_COLOR )', + gl.uniform1fv( this.addr, v ); - ' attribute vec3 color;', +} - '#endif', +// Array of vectors (from flat array or array of THREE.VectorN) - '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', +function setValueV2fArray( gl, v ) { - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', + const data = flatten( v, this.size, 2 ); - ' #ifdef USE_MORPHNORMALS', + gl.uniform2fv( this.addr, data ); - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', +} - ' #else', +function setValueV3fArray( gl, v ) { - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', + const data = flatten( v, this.size, 3 ); - ' #endif', + gl.uniform3fv( this.addr, data ); - '#endif', +} - '#ifdef USE_SKINNING', +function setValueV4fArray( gl, v ) { - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', + const data = flatten( v, this.size, 4 ); - '#endif', + gl.uniform4fv( this.addr, data ); - '\n' +} - ].filter( filterEmptyLine ).join( '\n' ); +// Array of matrices (from flat array or array of THREE.MatrixN) - prefixFragment = [ +function setValueM2Array( gl, v ) { - customExtensions, + const data = flatten( v, this.size, 4 ); - generatePrecision( parameters ), + gl.uniformMatrix2fv( this.addr, false, data ); - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, +} - customDefines, +function setValueM3Array( gl, v ) { - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', + const data = flatten( v, this.size, 9 ); - parameters.alphaToCoverage ? '#define ALPHA_TO_COVERAGE' : '', - parameters.map ? '#define USE_MAP' : '', - parameters.matcap ? '#define USE_MATCAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', - envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + gl.uniformMatrix3fv( this.addr, false, data ); - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', +} - parameters.clearcoat ? '#define USE_CLEARCOAT' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', +function setValueM4Array( gl, v ) { - parameters.iridescence ? '#define USE_IRIDESCENCE' : '', - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', + const data = flatten( v, this.size, 16 ); - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', + gl.uniformMatrix4fv( this.addr, false, data ); - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', +} - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaTest ? '#define USE_ALPHATEST' : '', - parameters.alphaHash ? '#define USE_ALPHAHASH' : '', +// Array of integer / boolean - parameters.sheen ? '#define USE_SHEEN' : '', - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', +function setValueV1iArray( gl, v ) { - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', + gl.uniform1iv( this.addr, v ); - parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', +} - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', +// Array of integer / boolean vectors (from flat array) - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', +function setValueV2iArray( gl, v ) { - parameters.flatShading ? '#define FLAT_SHADED' : '', + gl.uniform2iv( this.addr, v ); - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', +} - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', +function setValueV3iArray( gl, v ) { - parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', + gl.uniform3iv( this.addr, v ); - parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', +} - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', +function setValueV4iArray( gl, v ) { - parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', + gl.uniform4iv( this.addr, v ); - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', +} - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', +// Array of unsigned integer - ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', +function setValueV1uiArray( gl, v ) { - parameters.dithering ? '#define DITHERING' : '', - parameters.opaque ? '#define OPAQUE' : '', + gl.uniform1uiv( this.addr, v ); - ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below - getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), +} - parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', +// Array of unsigned integer vectors (from flat array) - '\n' +function setValueV2uiArray( gl, v ) { - ].filter( filterEmptyLine ).join( '\n' ); + gl.uniform2uiv( this.addr, v ); - } +} - vertexShader = resolveIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); +function setValueV3uiArray( gl, v ) { - fragmentShader = resolveIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); + gl.uniform3uiv( this.addr, v ); - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); +} - if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { +function setValueV4uiArray( gl, v ) { - // GLSL 3.0 conversion for built-in materials and ShaderMaterial + gl.uniform4uiv( this.addr, v ); - versionString = '#version 300 es\n'; +} - prefixVertex = [ - customVertexExtensions, - 'precision mediump sampler2DArray;', - '#define attribute in', - '#define varying out', - '#define texture2D texture' - ].join( '\n' ) + '\n' + prefixVertex; - prefixFragment = [ - 'precision mediump sampler2DArray;', - '#define varying in', - ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', - ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', - '#define gl_FragDepthEXT gl_FragDepth', - '#define texture2D texture', - '#define textureCube texture', - '#define texture2DProj textureProj', - '#define texture2DLodEXT textureLod', - '#define texture2DProjLodEXT textureProjLod', - '#define textureCubeLodEXT textureLod', - '#define texture2DGradEXT textureGrad', - '#define texture2DProjGradEXT textureProjGrad', - '#define textureCubeGradEXT textureGrad' - ].join( '\n' ) + '\n' + prefixFragment; +// Array of textures (2D / 3D / Cube / 2DArray) - } +function setValueT1Array( gl, v, textures ) { - const vertexGlsl = versionString + prefixVertex + vertexShader; - const fragmentGlsl = versionString + prefixFragment + fragmentShader; + const cache = this.cache; - // console.log( '*VERTEX*', vertexGlsl ); - // console.log( '*FRAGMENT*', fragmentGlsl ); + const n = v.length; - const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); + const units = allocTexUnits( textures, n ); - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); + if ( ! arraysEqual( cache, units ) ) { - // Force a particular attribute to index 0. + gl.uniform1iv( this.addr, units ); - if ( parameters.index0AttributeName !== undefined ) { + copyArray( cache, units ); - gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); + } - } else if ( parameters.morphTargets === true ) { + for ( let i = 0; i !== n; ++ i ) { - // programs with morphTargets displace position out of attribute 0 - gl.bindAttribLocation( program, 0, 'position' ); + textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); } - gl.linkProgram( program ); - - function onFirstUse( self ) { +} - // check for link errors - if ( renderer.debug.checkShaderErrors ) { +function setValueT3DArray( gl, v, textures ) { - const programLog = gl.getProgramInfoLog( program ).trim(); - const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); - const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); + const cache = this.cache; - let runnable = true; - let haveDiagnostics = true; + const n = v.length; - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { + const units = allocTexUnits( textures, n ); - runnable = false; + if ( ! arraysEqual( cache, units ) ) { - if ( typeof renderer.debug.onShaderError === 'function' ) { + gl.uniform1iv( this.addr, units ); - renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); + copyArray( cache, units ); - } else { + } - // default error reporting + for ( let i = 0; i !== n; ++ i ) { - const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); - const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); + textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); - console.error( - 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + - 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + - 'Material Name: ' + self.name + '\n' + - 'Material Type: ' + self.type + '\n\n' + - 'Program Info Log: ' + programLog + '\n' + - vertexErrors + '\n' + - fragmentErrors - ); + } - } +} - } else if ( programLog !== '' ) { +function setValueT6Array( gl, v, textures ) { - console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); + const cache = this.cache; - } else if ( vertexLog === '' || fragmentLog === '' ) { + const n = v.length; - haveDiagnostics = false; + const units = allocTexUnits( textures, n ); - } + if ( ! arraysEqual( cache, units ) ) { - if ( haveDiagnostics ) { + gl.uniform1iv( this.addr, units ); - self.diagnostics = { + copyArray( cache, units ); - runnable: runnable, + } - programLog: programLog, + for ( let i = 0; i !== n; ++ i ) { - vertexShader: { + textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - log: vertexLog, - prefix: prefixVertex + } - }, +} - fragmentShader: { +function setValueT2DArrayArray( gl, v, textures ) { - log: fragmentLog, - prefix: prefixFragment + const cache = this.cache; - } + const n = v.length; - }; + const units = allocTexUnits( textures, n ); - } + if ( ! arraysEqual( cache, units ) ) { - } + gl.uniform1iv( this.addr, units ); - // Clean up + copyArray( cache, units ); - // Crashes in iOS9 and iOS10. #18402 - // gl.detachShader( program, glVertexShader ); - // gl.detachShader( program, glFragmentShader ); + } - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); + for ( let i = 0; i !== n; ++ i ) { - cachedUniforms = new WebGLUniforms( gl, program ); - cachedAttributes = fetchAttributeLocations( gl, program ); + textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); } - // set up caching for uniform locations +} - let cachedUniforms; - this.getUniforms = function () { +// Helper to pick the right setter for a pure (bottom-level) array - if ( cachedUniforms === undefined ) { +function getPureArraySetter( type ) { - // Populates cachedUniforms and cachedAttributes - onFirstUse( this ); + switch ( type ) { - } + case 0x1406: return setValueV1fArray; // FLOAT + case 0x8b50: return setValueV2fArray; // _VEC2 + case 0x8b51: return setValueV3fArray; // _VEC3 + case 0x8b52: return setValueV4fArray; // _VEC4 - return cachedUniforms; + case 0x8b5a: return setValueM2Array; // _MAT2 + case 0x8b5b: return setValueM3Array; // _MAT3 + case 0x8b5c: return setValueM4Array; // _MAT4 - }; + case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - // set up caching for attribute locations + case 0x1405: return setValueV1uiArray; // UINT + case 0x8dc6: return setValueV2uiArray; // _VEC2 + case 0x8dc7: return setValueV3uiArray; // _VEC3 + case 0x8dc8: return setValueV4uiArray; // _VEC4 - let cachedAttributes; + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1Array; - this.getAttributes = function () { + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3DArray; - if ( cachedAttributes === undefined ) { + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6Array; - // Populates cachedAttributes and cachedUniforms - onFirstUse( this ); + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArrayArray; - } + } - return cachedAttributes; +} - }; +// --- Uniform Classes --- - // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, - // flag the program as ready immediately. It may cause a stall when it's first used. +class SingleUniform { - let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); + constructor( id, activeInfo, addr ) { - this.isReady = function () { + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.setValue = getSingularSetter( activeInfo.type ); - if ( programReady === false ) { + // this.path = activeInfo.name; // DEBUG - programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); + } - } +} - return programReady; +class PureArrayUniform { - }; + constructor( id, activeInfo, addr ) { - // free resource + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.size = activeInfo.size; + this.setValue = getPureArraySetter( activeInfo.type ); - this.destroy = function () { + // this.path = activeInfo.name; // DEBUG - bindingStates.releaseStatesOfProgram( this ); + } - gl.deleteProgram( program ); - this.program = undefined; +} - }; +class StructuredUniform { - // + constructor( id ) { - this.type = parameters.shaderType; - this.name = parameters.shaderName; - this.id = programIdCount ++; - this.cacheKey = cacheKey; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; + this.id = id; - return this; + this.seq = []; + this.map = {}; -} + } -let _id = 0; + setValue( gl, value, textures ) { -class WebGLShaderCache { + const seq = this.seq; - constructor() { + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - this.shaderCache = new Map(); - this.materialCache = new Map(); + const u = seq[ i ]; + u.setValue( gl, value[ u.id ], textures ); + + } } - update( material ) { +} - const vertexShader = material.vertexShader; - const fragmentShader = material.fragmentShader; +// --- Top-level --- - const vertexShaderStage = this._getShaderStage( vertexShader ); - const fragmentShaderStage = this._getShaderStage( fragmentShader ); +// Parser - builds up the property tree from the path strings - const materialShaders = this._getShaderCacheForMaterial( material ); +const RePathPart = /(\w+)(\])?(\[|\.)?/g; - if ( materialShaders.has( vertexShaderStage ) === false ) { +// extracts +// - the identifier (member name or array index) +// - followed by an optional right bracket (found when array index) +// - followed by an optional left bracket or dot (type of subscript) +// +// Note: These portions can be read in a non-overlapping fashion and +// allow straightforward parsing of the hierarchy that WebGL encodes +// in the uniform names. - materialShaders.add( vertexShaderStage ); - vertexShaderStage.usedTimes ++; +function addUniform( container, uniformObject ) { - } + container.seq.push( uniformObject ); + container.map[ uniformObject.id ] = uniformObject; - if ( materialShaders.has( fragmentShaderStage ) === false ) { +} - materialShaders.add( fragmentShaderStage ); - fragmentShaderStage.usedTimes ++; +function parseUniform( activeInfo, addr, container ) { - } + const path = activeInfo.name, + pathLength = path.length; - return this; + // reset RegExp object, because of the early exit of a previous run + RePathPart.lastIndex = 0; - } + while ( true ) { - remove( material ) { + const match = RePathPart.exec( path ), + matchEnd = RePathPart.lastIndex; - const materialShaders = this.materialCache.get( material ); + let id = match[ 1 ]; + const idIsIndex = match[ 2 ] === ']', + subscript = match[ 3 ]; - for ( const shaderStage of materialShaders ) { + if ( idIsIndex ) id = id | 0; // convert to integer - shaderStage.usedTimes --; + if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); + // bare name or "pure" bottom-level array "[0]" suffix - } + addUniform( container, subscript === undefined ? + new SingleUniform( id, activeInfo, addr ) : + new PureArrayUniform( id, activeInfo, addr ) ); - this.materialCache.delete( material ); + break; - return this; + } else { - } + // step into inner node / create it in case it doesn't exist - getVertexShaderID( material ) { + const map = container.map; + let next = map[ id ]; - return this._getShaderStage( material.vertexShader ).id; + if ( next === undefined ) { - } + next = new StructuredUniform( id ); + addUniform( container, next ); - getFragmentShaderID( material ) { + } - return this._getShaderStage( material.fragmentShader ).id; + container = next; + + } } - dispose() { +} - this.shaderCache.clear(); - this.materialCache.clear(); +// Root Container - } +class WebGLUniforms { - _getShaderCacheForMaterial( material ) { + constructor( gl, program ) { - const cache = this.materialCache; - let set = cache.get( material ); + this.seq = []; + this.map = {}; - if ( set === undefined ) { + const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - set = new Set(); - cache.set( material, set ); + for ( let i = 0; i < n; ++ i ) { + + const info = gl.getActiveUniform( program, i ), + addr = gl.getUniformLocation( program, info.name ); + + parseUniform( info, addr, this ); } - return set; + } + + setValue( gl, name, value, textures ) { + + const u = this.map[ name ]; + + if ( u !== undefined ) u.setValue( gl, value, textures ); } - _getShaderStage( code ) { + setOptional( gl, object, name ) { - const cache = this.shaderCache; - let stage = cache.get( code ); + const v = object[ name ]; - if ( stage === undefined ) { + if ( v !== undefined ) this.setValue( gl, name, v ); - stage = new WebGLShaderStage( code ); - cache.set( code, stage ); + } - } + static upload( gl, seq, values, textures ) { - return stage; + for ( let i = 0, n = seq.length; i !== n; ++ i ) { + + const u = seq[ i ], + v = values[ u.id ]; + + if ( v.needsUpdate !== false ) { + + // note: always updating when .needsUpdate is undefined + u.setValue( gl, v.value, textures ); + + } + + } } -} + static seqWithValue( seq, values ) { -class WebGLShaderStage { + const r = []; - constructor( code ) { + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - this.id = _id ++; + const u = seq[ i ]; + if ( u.id in values ) r.push( u ); - this.code = code; - this.usedTimes = 0; + } + + return r; } } -function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { +function WebGLShader( gl, type, string ) { - const _programLayers = new Layers(); - const _customShaders = new WebGLShaderCache(); - const _activeChannels = new Set(); - const programs = []; + const shader = gl.createShader( type ); - const IS_WEBGL2 = capabilities.isWebGL2; - const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; - const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; + gl.shaderSource( shader, string ); + gl.compileShader( shader ); - let precision = capabilities.precision; + return shader; - const shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distanceRGBA', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'toon', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - MeshMatcapMaterial: 'matcap', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow', - SpriteMaterial: 'sprite' - }; +} - function getChannel( value ) { +// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ +const COMPLETION_STATUS_KHR = 0x91B1; - _activeChannels.add( value ); +let programIdCount = 0; - if ( value === 0 ) return 'uv'; +function handleSource( string, errorLine ) { - return `uv${ value }`; + const lines = string.split( '\n' ); + const lines2 = []; - } + const from = Math.max( errorLine - 6, 0 ); + const to = Math.min( errorLine + 6, lines.length ); - function getParameters( material, lights, shadows, scene, object ) { + for ( let i = from; i < to; i ++ ) { - const fog = scene.fog; - const geometry = object.geometry; - const environment = material.isMeshStandardMaterial ? scene.environment : null; + const line = i + 1; + lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; + } - const shaderID = shaderIDs[ material.type ]; + return lines2.join( '\n' ); - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) +} - if ( material.precision !== null ) { +const _m0 = /*@__PURE__*/ new Matrix3(); - precision = capabilities.getMaxPrecision( material.precision ); +function getEncodingComponents( colorSpace ) { - if ( precision !== material.precision ) { + ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); + const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - } + switch ( ColorManagement.getTransfer( colorSpace ) ) { - } + case LinearTransfer: + return [ encodingMatrix, 'LinearTransferOETF' ]; - // + case SRGBTransfer: + return [ encodingMatrix, 'sRGBTransferOETF' ]; - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + default: + console.warn( 'THREE.WebGLProgram: Unsupported color space: ', colorSpace ); + return [ encodingMatrix, 'LinearTransferOETF' ]; - let morphTextureStride = 0; + } - if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; - if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; - if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; +} - // +function getShaderErrors( gl, shader, type ) { - let vertexShader, fragmentShader; - let customVertexShaderID, customFragmentShaderID; + const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - if ( shaderID ) { + const shaderInfoLog = gl.getShaderInfoLog( shader ) || ''; + const errors = shaderInfoLog.trim(); - const shader = ShaderLib[ shaderID ]; + if ( status && errors === '' ) return ''; - vertexShader = shader.vertexShader; - fragmentShader = shader.fragmentShader; + const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); + if ( errorMatches ) { - } else { + // --enable-privileged-webgl-extension + // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - vertexShader = material.vertexShader; - fragmentShader = material.fragmentShader; + const errorLine = parseInt( errorMatches[ 1 ] ); + return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - _customShaders.update( material ); + } else { - customVertexShaderID = _customShaders.getVertexShaderID( material ); - customFragmentShaderID = _customShaders.getFragmentShaderID( material ); + return errors; - } + } - const currentRenderTarget = renderer.getRenderTarget(); +} - const IS_INSTANCEDMESH = object.isInstancedMesh === true; - const IS_BATCHEDMESH = object.isBatchedMesh === true; +function getTexelEncodingFunction( functionName, colorSpace ) { - const HAS_MAP = !! material.map; - const HAS_MATCAP = !! material.matcap; - const HAS_ENVMAP = !! envMap; - const HAS_AOMAP = !! material.aoMap; - const HAS_LIGHTMAP = !! material.lightMap; - const HAS_BUMPMAP = !! material.bumpMap; - const HAS_NORMALMAP = !! material.normalMap; - const HAS_DISPLACEMENTMAP = !! material.displacementMap; - const HAS_EMISSIVEMAP = !! material.emissiveMap; + const components = getEncodingComponents( colorSpace ); - const HAS_METALNESSMAP = !! material.metalnessMap; - const HAS_ROUGHNESSMAP = !! material.roughnessMap; + return [ - const HAS_ANISOTROPY = material.anisotropy > 0; - const HAS_CLEARCOAT = material.clearcoat > 0; - const HAS_IRIDESCENCE = material.iridescence > 0; - const HAS_SHEEN = material.sheen > 0; - const HAS_TRANSMISSION = material.transmission > 0; + `vec4 ${functionName}( vec4 value ) {`, - const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; + ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, - const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; - const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; - const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; + '}', - const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; - const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; + ].join( '\n' ); - const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; - const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; +} - const HAS_SPECULARMAP = !! material.specularMap; - const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; - const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; +function getToneMappingFunction( functionName, toneMapping ) { - const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; - const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; + let toneMappingName; - const HAS_GRADIENTMAP = !! material.gradientMap; + switch ( toneMapping ) { - const HAS_ALPHAMAP = !! material.alphaMap; + case LinearToneMapping: + toneMappingName = 'Linear'; + break; - const HAS_ALPHATEST = material.alphaTest > 0; + case ReinhardToneMapping: + toneMappingName = 'Reinhard'; + break; - const HAS_ALPHAHASH = !! material.alphaHash; + case CineonToneMapping: + toneMappingName = 'Cineon'; + break; - const HAS_EXTENSIONS = !! material.extensions; + case ACESFilmicToneMapping: + toneMappingName = 'ACESFilmic'; + break; - let toneMapping = NoToneMapping; + case AgXToneMapping: + toneMappingName = 'AgX'; + break; - if ( material.toneMapped ) { + case NeutralToneMapping: + toneMappingName = 'Neutral'; + break; - if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { + case CustomToneMapping: + toneMappingName = 'Custom'; + break; - toneMapping = renderer.toneMapping; + default: + console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); + toneMappingName = 'Linear'; - } + } - } + return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - const parameters = { +} - isWebGL2: IS_WEBGL2, +const _v0$1 = /*@__PURE__*/ new Vector3(); - shaderID: shaderID, - shaderType: material.type, - shaderName: material.name, +function getLuminanceFunction() { - vertexShader: vertexShader, - fragmentShader: fragmentShader, - defines: material.defines, + ColorManagement.getLuminanceCoefficients( _v0$1 ); - customVertexShaderID: customVertexShaderID, - customFragmentShaderID: customFragmentShaderID, + const r = _v0$1.x.toFixed( 4 ); + const g = _v0$1.y.toFixed( 4 ); + const b = _v0$1.z.toFixed( 4 ); - isRawShaderMaterial: material.isRawShaderMaterial === true, - glslVersion: material.glslVersion, + return [ - precision: precision, + 'float luminance( const in vec3 rgb ) {', - batching: IS_BATCHEDMESH, - instancing: IS_INSTANCEDMESH, - instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, - instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, + ` const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`, - supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, - outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), - alphaToCoverage: !! material.alphaToCoverage, + ' return dot( weights, rgb );', - map: HAS_MAP, - matcap: HAS_MATCAP, - envMap: HAS_ENVMAP, - envMapMode: HAS_ENVMAP && envMap.mapping, - envMapCubeUVHeight: envMapCubeUVHeight, - aoMap: HAS_AOMAP, - lightMap: HAS_LIGHTMAP, - bumpMap: HAS_BUMPMAP, - normalMap: HAS_NORMALMAP, - displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, - emissiveMap: HAS_EMISSIVEMAP, + '}' - normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, - normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, + ].join( '\n' ); - metalnessMap: HAS_METALNESSMAP, - roughnessMap: HAS_ROUGHNESSMAP, +} - anisotropy: HAS_ANISOTROPY, - anisotropyMap: HAS_ANISOTROPYMAP, +function generateVertexExtensions( parameters ) { - clearcoat: HAS_CLEARCOAT, - clearcoatMap: HAS_CLEARCOATMAP, - clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, - clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, + const chunks = [ + parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '', + parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '', + ]; - iridescence: HAS_IRIDESCENCE, - iridescenceMap: HAS_IRIDESCENCEMAP, - iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, + return chunks.filter( filterEmptyLine ).join( '\n' ); - sheen: HAS_SHEEN, - sheenColorMap: HAS_SHEEN_COLORMAP, - sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, +} - specularMap: HAS_SPECULARMAP, - specularColorMap: HAS_SPECULAR_COLORMAP, - specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, +function generateDefines( defines ) { - transmission: HAS_TRANSMISSION, - transmissionMap: HAS_TRANSMISSIONMAP, - thicknessMap: HAS_THICKNESSMAP, + const chunks = []; - gradientMap: HAS_GRADIENTMAP, + for ( const name in defines ) { - opaque: material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false, + const value = defines[ name ]; - alphaMap: HAS_ALPHAMAP, - alphaTest: HAS_ALPHATEST, - alphaHash: HAS_ALPHAHASH, + if ( value === false ) continue; - combine: material.combine, + chunks.push( '#define ' + name + ' ' + value ); - // + } - mapUv: HAS_MAP && getChannel( material.map.channel ), - aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), - lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), - bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), - normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), - displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), - emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), + return chunks.join( '\n' ); - metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), - roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), +} - anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), +function fetchAttributeLocations( gl, program ) { - clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), - clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), - clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), + const attributes = {}; - iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), - iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), + const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), - sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), + for ( let i = 0; i < n; i ++ ) { - specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), - specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), - specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), + const info = gl.getActiveAttrib( program, i ); + const name = info.name; - transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), - thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), + let locationSize = 1; + if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; + if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; + if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), + // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - // + attributes[ name ] = { + type: info.type, + location: gl.getAttribLocation( program, name ), + locationSize: locationSize + }; - vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), - vertexColors: material.vertexColors, - vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, + } - pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), + return attributes; - fog: !! fog, - useFog: material.fog === true, - fogExp2: ( !! fog && fog.isFogExp2 ), +} - flatShading: material.flatShading === true, +function filterEmptyLine( string ) { - sizeAttenuation: material.sizeAttenuation === true, - logarithmicDepthBuffer: logarithmicDepthBuffer, + return string !== ''; - skinning: object.isSkinnedMesh === true, +} - morphTargets: geometry.morphAttributes.position !== undefined, - morphNormals: geometry.morphAttributes.normal !== undefined, - morphColors: geometry.morphAttributes.color !== undefined, - morphTargetsCount: morphTargetsCount, - morphTextureStride: morphTextureStride, +function replaceLightNums( string, parameters ) { - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numSpotLightMaps: lights.spotLightMap.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, + const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - numDirLightShadows: lights.directionalShadowMap.length, - numPointLightShadows: lights.pointShadowMap.length, - numSpotLightShadows: lights.spotShadowMap.length, - numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, + return string + .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) + .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) + .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) + .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) + .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) + .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) + .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) + .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) + .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) + .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) + .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - numLightProbes: lights.numLightProbes, +} - numClippingPlanes: clipping.numPlanes, - numClipIntersection: clipping.numIntersection, +function replaceClippingPlaneNums( string, parameters ) { - dithering: material.dithering, + return string + .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) + .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, +} - toneMapping: toneMapping, - useLegacyLights: renderer._useLegacyLights, +// Resolve Includes - decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), +const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - premultipliedAlpha: material.premultipliedAlpha, +function resolveIncludes( string ) { - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, + return string.replace( includePattern, includeReplacer ); - useDepthPacking: material.depthPacking >= 0, - depthPacking: material.depthPacking || 0, +} - index0AttributeName: material.index0AttributeName, +const shaderChunkMap = new Map(); - extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, - extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, - extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, - extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, - extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), - extensionMultiDraw: HAS_EXTENSIONS && material.extensions.multiDraw === true && extensions.has( 'WEBGL_multi_draw' ), +function includeReplacer( match, include ) { - rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), - rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), - rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), - rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), + let string = ShaderChunk[ include ]; - customProgramCacheKey: material.customProgramCacheKey() + if ( string === undefined ) { - }; + const newInclude = shaderChunkMap.get( include ); - // the usage of getChannel() determines the active texture channels for this shader + if ( newInclude !== undefined ) { - parameters.vertexUv1s = _activeChannels.has( 1 ); - parameters.vertexUv2s = _activeChannels.has( 2 ); - parameters.vertexUv3s = _activeChannels.has( 3 ); + string = ShaderChunk[ newInclude ]; + console.warn( 'THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); - _activeChannels.clear(); + } else { - return parameters; + throw new Error( 'Can not resolve #include <' + include + '>' ); + + } } - function getProgramCacheKey( parameters ) { + return resolveIncludes( string ); - const array = []; +} - if ( parameters.shaderID ) { +// Unroll Loops - array.push( parameters.shaderID ); +const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - } else { +function unrollLoops( string ) { - array.push( parameters.customVertexShaderID ); - array.push( parameters.customFragmentShaderID ); + return string.replace( unrollLoopPattern, loopReplacer ); - } +} - if ( parameters.defines !== undefined ) { +function loopReplacer( match, start, end, snippet ) { - for ( const name in parameters.defines ) { + let string = ''; - array.push( name ); - array.push( parameters.defines[ name ] ); + for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - } + string += snippet + .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) + .replace( /UNROLLED_LOOP_INDEX/g, i ); - } + } - if ( parameters.isRawShaderMaterial === false ) { + return string; - getProgramCacheKeyParameters( array, parameters ); - getProgramCacheKeyBooleans( array, parameters ); - array.push( renderer.outputColorSpace ); +} - } +// - array.push( parameters.customProgramCacheKey ); +function generatePrecision( parameters ) { - return array.join(); + let precisionstring = `precision ${parameters.precision} float; + precision ${parameters.precision} int; + precision ${parameters.precision} sampler2D; + precision ${parameters.precision} samplerCube; + precision ${parameters.precision} sampler3D; + precision ${parameters.precision} sampler2DArray; + precision ${parameters.precision} sampler2DShadow; + precision ${parameters.precision} samplerCubeShadow; + precision ${parameters.precision} sampler2DArrayShadow; + precision ${parameters.precision} isampler2D; + precision ${parameters.precision} isampler3D; + precision ${parameters.precision} isamplerCube; + precision ${parameters.precision} isampler2DArray; + precision ${parameters.precision} usampler2D; + precision ${parameters.precision} usampler3D; + precision ${parameters.precision} usamplerCube; + precision ${parameters.precision} usampler2DArray; + `; + + if ( parameters.precision === 'highp' ) { + + precisionstring += '\n#define HIGH_PRECISION'; + + } else if ( parameters.precision === 'mediump' ) { + + precisionstring += '\n#define MEDIUM_PRECISION'; + + } else if ( parameters.precision === 'lowp' ) { + + precisionstring += '\n#define LOW_PRECISION'; } - function getProgramCacheKeyParameters( array, parameters ) { + return precisionstring; - array.push( parameters.precision ); - array.push( parameters.outputColorSpace ); - array.push( parameters.envMapMode ); - array.push( parameters.envMapCubeUVHeight ); - array.push( parameters.mapUv ); - array.push( parameters.alphaMapUv ); - array.push( parameters.lightMapUv ); - array.push( parameters.aoMapUv ); - array.push( parameters.bumpMapUv ); - array.push( parameters.normalMapUv ); - array.push( parameters.displacementMapUv ); - array.push( parameters.emissiveMapUv ); - array.push( parameters.metalnessMapUv ); - array.push( parameters.roughnessMapUv ); - array.push( parameters.anisotropyMapUv ); - array.push( parameters.clearcoatMapUv ); - array.push( parameters.clearcoatNormalMapUv ); - array.push( parameters.clearcoatRoughnessMapUv ); - array.push( parameters.iridescenceMapUv ); - array.push( parameters.iridescenceThicknessMapUv ); - array.push( parameters.sheenColorMapUv ); - array.push( parameters.sheenRoughnessMapUv ); - array.push( parameters.specularMapUv ); - array.push( parameters.specularColorMapUv ); - array.push( parameters.specularIntensityMapUv ); - array.push( parameters.transmissionMapUv ); - array.push( parameters.thicknessMapUv ); - array.push( parameters.combine ); - array.push( parameters.fogExp2 ); - array.push( parameters.sizeAttenuation ); - array.push( parameters.morphTargetsCount ); - array.push( parameters.morphAttributeCount ); - array.push( parameters.numDirLights ); - array.push( parameters.numPointLights ); - array.push( parameters.numSpotLights ); - array.push( parameters.numSpotLightMaps ); - array.push( parameters.numHemiLights ); - array.push( parameters.numRectAreaLights ); - array.push( parameters.numDirLightShadows ); - array.push( parameters.numPointLightShadows ); - array.push( parameters.numSpotLightShadows ); - array.push( parameters.numSpotLightShadowsWithMaps ); - array.push( parameters.numLightProbes ); - array.push( parameters.shadowMapType ); - array.push( parameters.toneMapping ); - array.push( parameters.numClippingPlanes ); - array.push( parameters.numClipIntersection ); - array.push( parameters.depthPacking ); +} - } +function generateShadowMapTypeDefine( parameters ) { - function getProgramCacheKeyBooleans( array, parameters ) { + let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - _programLayers.disableAll(); + if ( parameters.shadowMapType === PCFShadowMap ) { - if ( parameters.isWebGL2 ) - _programLayers.enable( 0 ); - if ( parameters.supportsVertexTextures ) - _programLayers.enable( 1 ); - if ( parameters.instancing ) - _programLayers.enable( 2 ); - if ( parameters.instancingColor ) - _programLayers.enable( 3 ); - if ( parameters.instancingMorph ) - _programLayers.enable( 4 ); - if ( parameters.matcap ) - _programLayers.enable( 5 ); - if ( parameters.envMap ) - _programLayers.enable( 6 ); - if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 7 ); - if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 8 ); - if ( parameters.clearcoat ) - _programLayers.enable( 9 ); - if ( parameters.iridescence ) - _programLayers.enable( 10 ); - if ( parameters.alphaTest ) - _programLayers.enable( 11 ); - if ( parameters.vertexColors ) - _programLayers.enable( 12 ); - if ( parameters.vertexAlphas ) - _programLayers.enable( 13 ); - if ( parameters.vertexUv1s ) - _programLayers.enable( 14 ); - if ( parameters.vertexUv2s ) - _programLayers.enable( 15 ); - if ( parameters.vertexUv3s ) - _programLayers.enable( 16 ); - if ( parameters.vertexTangents ) - _programLayers.enable( 17 ); - if ( parameters.anisotropy ) - _programLayers.enable( 18 ); - if ( parameters.alphaHash ) - _programLayers.enable( 19 ); - if ( parameters.batching ) - _programLayers.enable( 20 ); + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - array.push( _programLayers.mask ); - _programLayers.disableAll(); + } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - if ( parameters.fog ) - _programLayers.enable( 0 ); - if ( parameters.useFog ) - _programLayers.enable( 1 ); - if ( parameters.flatShading ) - _programLayers.enable( 2 ); - if ( parameters.logarithmicDepthBuffer ) - _programLayers.enable( 3 ); - if ( parameters.skinning ) - _programLayers.enable( 4 ); - if ( parameters.morphTargets ) - _programLayers.enable( 5 ); - if ( parameters.morphNormals ) - _programLayers.enable( 6 ); - if ( parameters.morphColors ) - _programLayers.enable( 7 ); - if ( parameters.premultipliedAlpha ) - _programLayers.enable( 8 ); - if ( parameters.shadowMapEnabled ) - _programLayers.enable( 9 ); - if ( parameters.useLegacyLights ) - _programLayers.enable( 10 ); - if ( parameters.doubleSided ) - _programLayers.enable( 11 ); - if ( parameters.flipSided ) - _programLayers.enable( 12 ); - if ( parameters.useDepthPacking ) - _programLayers.enable( 13 ); - if ( parameters.dithering ) - _programLayers.enable( 14 ); - if ( parameters.transmission ) - _programLayers.enable( 15 ); - if ( parameters.sheen ) - _programLayers.enable( 16 ); - if ( parameters.opaque ) - _programLayers.enable( 17 ); - if ( parameters.pointsUvs ) - _programLayers.enable( 18 ); - if ( parameters.decodeVideoTexture ) - _programLayers.enable( 19 ); - if ( parameters.alphaToCoverage ) - _programLayers.enable( 20 ); + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - array.push( _programLayers.mask ); + } else if ( parameters.shadowMapType === VSMShadowMap ) { + + shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; } - function getUniforms( material ) { + return shadowMapTypeDefine; - const shaderID = shaderIDs[ material.type ]; - let uniforms; +} - if ( shaderID ) { +function generateEnvMapTypeDefine( parameters ) { - const shader = ShaderLib[ shaderID ]; - uniforms = UniformsUtils.clone( shader.uniforms ); + let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - } else { + if ( parameters.envMap ) { - uniforms = material.uniforms; + switch ( parameters.envMapMode ) { - } + case CubeReflectionMapping: + case CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; - return uniforms; + case CubeUVReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; + break; + + } } - function acquireProgram( parameters, cacheKey ) { + return envMapTypeDefine; - let program; +} - // Check if code has been already compiled - for ( let p = 0, pl = programs.length; p < pl; p ++ ) { +function generateEnvMapModeDefine( parameters ) { - const preexistingProgram = programs[ p ]; + let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - if ( preexistingProgram.cacheKey === cacheKey ) { + if ( parameters.envMap ) { - program = preexistingProgram; - ++ program.usedTimes; + switch ( parameters.envMapMode ) { - break; + case CubeRefractionMapping: - } + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; } - if ( program === undefined ) { + } - program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); - programs.push( program ); + return envMapModeDefine; - } +} - return program; +function generateEnvMapBlendingDefine( parameters ) { - } + let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; - function releaseProgram( program ) { + if ( parameters.envMap ) { - if ( -- program.usedTimes === 0 ) { + switch ( parameters.combine ) { - // Remove from unordered set - const i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); + case MultiplyOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; - // Free WebGL resources - program.destroy(); + case MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; + + case AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; } } - function releaseShaderCache( material ) { - - _customShaders.remove( material ); - - } + return envMapBlendingDefine; - function dispose() { +} - _customShaders.dispose(); +function generateCubeUVSize( parameters ) { - } + const imageHeight = parameters.envMapCubeUVHeight; - return { - getParameters: getParameters, - getProgramCacheKey: getProgramCacheKey, - getUniforms: getUniforms, - acquireProgram: acquireProgram, - releaseProgram: releaseProgram, - releaseShaderCache: releaseShaderCache, - // Exposed for resource monitoring & error feedback via renderer.info: - programs: programs, - dispose: dispose - }; + if ( imageHeight === null ) return null; -} + const maxMip = Math.log2( imageHeight ) - 2; -function WebGLProperties() { + const texelHeight = 1.0 / imageHeight; - let properties = new WeakMap(); + const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - function get( object ) { + return { texelWidth, texelHeight, maxMip }; - let map = properties.get( object ); +} - if ( map === undefined ) { +function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - map = {}; - properties.set( object, map ); + // TODO Send this event to Three.js DevTools + // console.log( 'WebGLProgram', cacheKey ); - } + const gl = renderer.getContext(); - return map; + const defines = parameters.defines; - } + let vertexShader = parameters.vertexShader; + let fragmentShader = parameters.fragmentShader; - function remove( object ) { + const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); + const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); + const envMapModeDefine = generateEnvMapModeDefine( parameters ); + const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); + const envMapCubeUVSize = generateCubeUVSize( parameters ); - properties.delete( object ); + const customVertexExtensions = generateVertexExtensions( parameters ); - } + const customDefines = generateDefines( defines ); - function update( object, key, value ) { + const program = gl.createProgram(); - properties.get( object )[ key ] = value; + let prefixVertex, prefixFragment; + let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - } + if ( parameters.isRawShaderMaterial ) { - function dispose() { + prefixVertex = [ - properties = new WeakMap(); + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - } + customDefines - return { - get: get, - remove: remove, - update: update, - dispose: dispose - }; + ].filter( filterEmptyLine ).join( '\n' ); -} + if ( prefixVertex.length > 0 ) { -function painterSortStable( a, b ) { + prefixVertex += '\n'; - if ( a.groupOrder !== b.groupOrder ) { + } - return a.groupOrder - b.groupOrder; + prefixFragment = [ - } else if ( a.renderOrder !== b.renderOrder ) { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - return a.renderOrder - b.renderOrder; + customDefines - } else if ( a.material.id !== b.material.id ) { + ].filter( filterEmptyLine ).join( '\n' ); - return a.material.id - b.material.id; + if ( prefixFragment.length > 0 ) { - } else if ( a.z !== b.z ) { + prefixFragment += '\n'; - return a.z - b.z; + } } else { - return a.id - b.id; + prefixVertex = [ - } + generatePrecision( parameters ), -} + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, -function reversePainterSortStable( a, b ) { + customDefines, - if ( a.groupOrder !== b.groupOrder ) { + parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', + parameters.batching ? '#define USE_BATCHING' : '', + parameters.batchingColor ? '#define USE_BATCHING_COLOR' : '', + parameters.instancing ? '#define USE_INSTANCING' : '', + parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', + parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', - return a.groupOrder - b.groupOrder; + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - } else if ( a.renderOrder !== b.renderOrder ) { + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - return a.renderOrder - b.renderOrder; + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - } else if ( a.z !== b.z ) { + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - return b.z - a.z; + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - } else { + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - return a.id - b.id; + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - } + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', -} + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', + // -function WebGLRenderList() { + parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', + parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', + parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', + parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', + parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', + parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', + parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', + parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', - const renderItems = []; - let renderItemsIndex = 0; + parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', + parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - const opaque = []; - const transmissive = []; - const transparent = []; + parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - function init() { + parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', + parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', + parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - renderItemsIndex = 0; + parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', + parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - opaque.length = 0; - transmissive.length = 0; - transparent.length = 0; + parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', + parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - } + parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', + parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', + parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { + parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', + parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - let renderItem = renderItems[ renderItemsIndex ]; + // - if ( renderItem === undefined ) { + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - renderItems[ renderItemsIndex ] = renderItem; + parameters.flatShading ? '#define FLAT_SHADED' : '', - } else { + parameters.skinning ? '#define USE_SKINNING' : '', - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; + parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', + parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', + ( parameters.morphColors ) ? '#define USE_MORPHCOLORS' : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - } + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - renderItemsIndex ++; + parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - return renderItem; + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - } + parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', + parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', - function push( object, geometry, material, groupOrder, z, group ) { + 'uniform mat4 modelMatrix;', + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); + '#ifdef USE_INSTANCING', - if ( material.transmission > 0.0 ) { + ' attribute mat4 instanceMatrix;', - transmissive.push( renderItem ); + '#endif', - } else if ( material.transparent === true ) { + '#ifdef USE_INSTANCING_COLOR', - transparent.push( renderItem ); + ' attribute vec3 instanceColor;', - } else { + '#endif', - opaque.push( renderItem ); + '#ifdef USE_INSTANCING_MORPH', - } + ' uniform sampler2D morphTexture;', - } + '#endif', - function unshift( object, geometry, material, groupOrder, z, group ) { + 'attribute vec3 position;', + 'attribute vec3 normal;', + 'attribute vec2 uv;', - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); + '#ifdef USE_UV1', - if ( material.transmission > 0.0 ) { + ' attribute vec2 uv1;', - transmissive.unshift( renderItem ); + '#endif', - } else if ( material.transparent === true ) { + '#ifdef USE_UV2', - transparent.unshift( renderItem ); + ' attribute vec2 uv2;', - } else { + '#endif', - opaque.unshift( renderItem ); + '#ifdef USE_UV3', - } + ' attribute vec2 uv3;', - } + '#endif', - function sort( customOpaqueSort, customTransparentSort ) { + '#ifdef USE_TANGENT', - if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); - if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); - if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); + ' attribute vec4 tangent;', - } + '#endif', - function finish() { + '#if defined( USE_COLOR_ALPHA )', - // Clear references from inactive renderItems in the list + ' attribute vec4 color;', - for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { + '#elif defined( USE_COLOR )', - const renderItem = renderItems[ i ]; + ' attribute vec3 color;', - if ( renderItem.id === null ) break; + '#endif', - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.group = null; + '#ifdef USE_SKINNING', - } + ' attribute vec4 skinIndex;', + ' attribute vec4 skinWeight;', - } + '#endif', - return { + '\n' - opaque: opaque, - transmissive: transmissive, - transparent: transparent, + ].filter( filterEmptyLine ).join( '\n' ); - init: init, - push: push, - unshift: unshift, - finish: finish, + prefixFragment = [ - sort: sort - }; + generatePrecision( parameters ), -} + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, -function WebGLRenderLists() { + customDefines, - let lists = new WeakMap(); + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - function get( scene, renderCallDepth ) { + parameters.alphaToCoverage ? '#define ALPHA_TO_COVERAGE' : '', + parameters.map ? '#define USE_MAP' : '', + parameters.matcap ? '#define USE_MATCAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapTypeDefine : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.envMap ? '#define ' + envMapBlendingDefine : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', + envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - const listArray = lists.get( scene ); - let list; + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - if ( listArray === undefined ) { + parameters.clearcoat ? '#define USE_CLEARCOAT' : '', + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - list = new WebGLRenderList(); - lists.set( scene, [ list ] ); + parameters.dispersion ? '#define USE_DISPERSION' : '', - } else { + parameters.iridescence ? '#define USE_IRIDESCENCE' : '', + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - if ( renderCallDepth >= listArray.length ) { + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - list = new WebGLRenderList(); - listArray.push( list ); + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - } else { + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaTest ? '#define USE_ALPHATEST' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - list = listArray[ renderCallDepth ]; + parameters.sheen ? '#define USE_SHEEN' : '', + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - } + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - } + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors || parameters.instancingColor || parameters.batchingColor ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', - return list; + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - } + parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - function dispose() { + parameters.flatShading ? '#define FLAT_SHADED' : '', - lists = new WeakMap(); + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - } + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - return { - get: get, - dispose: dispose - }; + parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', -} + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', -function UniformsCache() { + parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', + parameters.decodeVideoTextureEmissive ? '#define DECODE_VIDEO_TEXTURE_EMISSIVE' : '', - const lights = {}; + parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', + parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', - return { + 'uniform mat4 viewMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - get: function ( light ) { + ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', + ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below + ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - if ( lights[ light.id ] !== undefined ) { + parameters.dithering ? '#define DITHERING' : '', + parameters.opaque ? '#define OPAQUE' : '', - return lights[ light.id ]; + ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below + getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), + getLuminanceFunction(), - } + parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - let uniforms; + '\n' - switch ( light.type ) { + ].filter( filterEmptyLine ).join( '\n' ); - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color() - }; - break; + } - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0 - }; - break; + vertexShader = resolveIncludes( vertexShader ); + vertexShader = replaceLightNums( vertexShader, parameters ); + vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0 - }; - break; + fragmentShader = resolveIncludes( fragmentShader ); + fragmentShader = replaceLightNums( fragmentShader, parameters ); + fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; + vertexShader = unrollLoops( vertexShader ); + fragmentShader = unrollLoops( fragmentShader ); - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - }; - break; + if ( parameters.isRawShaderMaterial !== true ) { - } + // GLSL 3.0 conversion for built-in materials and ShaderMaterial - lights[ light.id ] = uniforms; + versionString = '#version 300 es\n'; - return uniforms; + prefixVertex = [ + customVertexExtensions, + '#define attribute in', + '#define varying out', + '#define texture2D texture' + ].join( '\n' ) + '\n' + prefixVertex; - } + prefixFragment = [ + '#define varying in', + ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', + ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', + '#define gl_FragDepthEXT gl_FragDepth', + '#define texture2D texture', + '#define textureCube texture', + '#define texture2DProj textureProj', + '#define texture2DLodEXT textureLod', + '#define texture2DProjLodEXT textureProjLod', + '#define textureCubeLodEXT textureLod', + '#define texture2DGradEXT textureGrad', + '#define texture2DProjGradEXT textureProjGrad', + '#define textureCubeGradEXT textureGrad' + ].join( '\n' ) + '\n' + prefixFragment; - }; + } -} + const vertexGlsl = versionString + prefixVertex + vertexShader; + const fragmentGlsl = versionString + prefixFragment + fragmentShader; -function ShadowUniformsCache() { + // console.log( '*VERTEX*', vertexGlsl ); + // console.log( '*FRAGMENT*', fragmentGlsl ); - const lights = {}; + const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); + const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - return { + gl.attachShader( program, glVertexShader ); + gl.attachShader( program, glFragmentShader ); - get: function ( light ) { + // Force a particular attribute to index 0. - if ( lights[ light.id ] !== undefined ) { + if ( parameters.index0AttributeName !== undefined ) { - return lights[ light.id ]; + gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - } + } else if ( parameters.morphTargets === true ) { - let uniforms; + // programs with morphTargets displace position out of attribute 0 + gl.bindAttribLocation( program, 0, 'position' ); - switch ( light.type ) { + } - case 'DirectionalLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; + gl.linkProgram( program ); - case 'SpotLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; + function onFirstUse( self ) { - case 'PointLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; + // check for link errors + if ( renderer.debug.checkShaderErrors ) { - // TODO (abelnation): set RectAreaLight shadow uniforms + const programInfoLog = gl.getProgramInfoLog( program ) || ''; + const vertexShaderInfoLog = gl.getShaderInfoLog( glVertexShader ) || ''; + const fragmentShaderInfoLog = gl.getShaderInfoLog( glFragmentShader ) || ''; - } + const programLog = programInfoLog.trim(); + const vertexLog = vertexShaderInfoLog.trim(); + const fragmentLog = fragmentShaderInfoLog.trim(); - lights[ light.id ] = uniforms; + let runnable = true; + let haveDiagnostics = true; - return uniforms; + if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - } + runnable = false; - }; + if ( typeof renderer.debug.onShaderError === 'function' ) { -} + renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); + } else { + // default error reporting -let nextVersion = 0; + const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); + const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); -function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { + console.error( + 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + + 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + + 'Material Name: ' + self.name + '\n' + + 'Material Type: ' + self.type + '\n\n' + + 'Program Info Log: ' + programLog + '\n' + + vertexErrors + '\n' + + fragmentErrors + ); - return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); + } -} + } else if ( programLog !== '' ) { -function WebGLLights( extensions, capabilities ) { + console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); - const cache = new UniformsCache(); + } else if ( vertexLog === '' || fragmentLog === '' ) { - const shadowCache = ShadowUniformsCache(); + haveDiagnostics = false; - const state = { + } - version: 0, + if ( haveDiagnostics ) { - hash: { - directionalLength: - 1, - pointLength: - 1, - spotLength: - 1, - rectAreaLength: - 1, - hemiLength: - 1, - - numDirectionalShadows: - 1, - numPointShadows: - 1, - numSpotShadows: - 1, - numSpotMaps: - 1, - - numLightProbes: - 1 - }, + self.diagnostics = { - ambient: [ 0, 0, 0 ], - probe: [], - directional: [], - directionalShadow: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotLightMap: [], - spotShadow: [], - spotShadowMap: [], - spotLightMatrix: [], - rectArea: [], - rectAreaLTC1: null, - rectAreaLTC2: null, - point: [], - pointShadow: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [], - numSpotLightShadowsWithMaps: 0, - numLightProbes: 0 + runnable: runnable, - }; + programLog: programLog, - for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); + vertexShader: { - const vector3 = new Vector3(); - const matrix4 = new Matrix4(); - const matrix42 = new Matrix4(); + log: vertexLog, + prefix: prefixVertex - function setup( lights, useLegacyLights ) { + }, - let r = 0, g = 0, b = 0; + fragmentShader: { - for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); + log: fragmentLog, + prefix: prefixFragment - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; + } - let numDirectionalShadows = 0; - let numPointShadows = 0; - let numSpotShadows = 0; - let numSpotMaps = 0; - let numSpotShadowsWithMaps = 0; + }; - let numLightProbes = 0; + } - // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] - lights.sort( shadowCastingAndTexturingLightsFirst ); + } - // artist-friendly light intensity scaling factor - const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; + // Clean up - for ( let i = 0, l = lights.length; i < l; i ++ ) { + // Crashes in iOS9 and iOS10. #18402 + // gl.detachShader( program, glVertexShader ); + // gl.detachShader( program, glFragmentShader ); - const light = lights[ i ]; + gl.deleteShader( glVertexShader ); + gl.deleteShader( glFragmentShader ); - const color = light.color; - const intensity = light.intensity; - const distance = light.distance; + cachedUniforms = new WebGLUniforms( gl, program ); + cachedAttributes = fetchAttributeLocations( gl, program ); - const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; + } - if ( light.isAmbientLight ) { + // set up caching for uniform locations - r += color.r * intensity * scaleFactor; - g += color.g * intensity * scaleFactor; - b += color.b * intensity * scaleFactor; + let cachedUniforms; - } else if ( light.isLightProbe ) { + this.getUniforms = function () { - for ( let j = 0; j < 9; j ++ ) { + if ( cachedUniforms === undefined ) { - state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); + // Populates cachedUniforms and cachedAttributes + onFirstUse( this ); - } + } - numLightProbes ++; + return cachedUniforms; - } else if ( light.isDirectionalLight ) { + }; - const uniforms = cache.get( light ); + // set up caching for attribute locations - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); + let cachedAttributes; - if ( light.castShadow ) { + this.getAttributes = function () { - const shadow = light.shadow; + if ( cachedAttributes === undefined ) { - const shadowUniforms = shadowCache.get( light ); + // Populates cachedAttributes and cachedUniforms + onFirstUse( this ); - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; + } - state.directionalShadow[ directionalLength ] = shadowUniforms; - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; + return cachedAttributes; - numDirectionalShadows ++; + }; - } + // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, + // flag the program as ready immediately. It may cause a stall when it's first used. - state.directional[ directionalLength ] = uniforms; + let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); - directionalLength ++; + this.isReady = function () { - } else if ( light.isSpotLight ) { + if ( programReady === false ) { - const uniforms = cache.get( light ); + programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); - uniforms.position.setFromMatrixPosition( light.matrixWorld ); + } - uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); - uniforms.distance = distance; + return programReady; - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = light.decay; + }; - state.spot[ spotLength ] = uniforms; + // free resource - const shadow = light.shadow; + this.destroy = function () { - if ( light.map ) { + bindingStates.releaseStatesOfProgram( this ); - state.spotLightMap[ numSpotMaps ] = light.map; - numSpotMaps ++; + gl.deleteProgram( program ); + this.program = undefined; - // make sure the lightMatrix is up to date - // TODO : do it if required only - shadow.updateMatrices( light ); + }; - if ( light.castShadow ) numSpotShadowsWithMaps ++; + // - } + this.type = parameters.shaderType; + this.name = parameters.shaderName; + this.id = programIdCount ++; + this.cacheKey = cacheKey; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; - state.spotLightMatrix[ spotLength ] = shadow.matrix; + return this; - if ( light.castShadow ) { +} - const shadowUniforms = shadowCache.get( light ); +let _id = 0; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; +class WebGLShaderCache { - state.spotShadow[ spotLength ] = shadowUniforms; - state.spotShadowMap[ spotLength ] = shadowMap; + constructor() { - numSpotShadows ++; + this.shaderCache = new Map(); + this.materialCache = new Map(); - } + } - spotLength ++; + update( material ) { - } else if ( light.isRectAreaLight ) { + const vertexShader = material.vertexShader; + const fragmentShader = material.fragmentShader; - const uniforms = cache.get( light ); + const vertexShaderStage = this._getShaderStage( vertexShader ); + const fragmentShaderStage = this._getShaderStage( fragmentShader ); - uniforms.color.copy( color ).multiplyScalar( intensity ); + const materialShaders = this._getShaderCacheForMaterial( material ); - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); + if ( materialShaders.has( vertexShaderStage ) === false ) { - state.rectArea[ rectAreaLength ] = uniforms; + materialShaders.add( vertexShaderStage ); + vertexShaderStage.usedTimes ++; - rectAreaLength ++; + } - } else if ( light.isPointLight ) { + if ( materialShaders.has( fragmentShaderStage ) === false ) { - const uniforms = cache.get( light ); + materialShaders.add( fragmentShaderStage ); + fragmentShaderStage.usedTimes ++; - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - uniforms.distance = light.distance; - uniforms.decay = light.decay; - - if ( light.castShadow ) { + } - const shadow = light.shadow; + return this; - const shadowUniforms = shadowCache.get( light ); + } - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - shadowUniforms.shadowCameraNear = shadow.camera.near; - shadowUniforms.shadowCameraFar = shadow.camera.far; + remove( material ) { - state.pointShadow[ pointLength ] = shadowUniforms; - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; + const materialShaders = this.materialCache.get( material ); - numPointShadows ++; + for ( const shaderStage of materialShaders ) { - } + shaderStage.usedTimes --; - state.point[ pointLength ] = uniforms; + if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); - pointLength ++; + } - } else if ( light.isHemisphereLight ) { + this.materialCache.delete( material ); - const uniforms = cache.get( light ); + return this; - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); + } - state.hemi[ hemiLength ] = uniforms; + getVertexShaderID( material ) { - hemiLength ++; + return this._getShaderStage( material.vertexShader ).id; - } + } - } + getFragmentShaderID( material ) { - if ( rectAreaLength > 0 ) { + return this._getShaderStage( material.fragmentShader ).id; - if ( capabilities.isWebGL2 ) { + } - // WebGL 2 + dispose() { - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { + this.shaderCache.clear(); + this.materialCache.clear(); - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; + } - } else { + _getShaderCacheForMaterial( material ) { - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; + const cache = this.materialCache; + let set = cache.get( material ); - } + if ( set === undefined ) { - } else { + set = new Set(); + cache.set( material, set ); - // WebGL 1 + } - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { + return set; - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; + } - } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { + _getShaderStage( code ) { - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; + const cache = this.shaderCache; + let stage = cache.get( code ); - } else { + if ( stage === undefined ) { - console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); + stage = new WebGLShaderStage( code ); + cache.set( code, stage ); - } + } - } + return stage; - } + } - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; +} - const hash = state.hash; +class WebGLShaderStage { - if ( hash.directionalLength !== directionalLength || - hash.pointLength !== pointLength || - hash.spotLength !== spotLength || - hash.rectAreaLength !== rectAreaLength || - hash.hemiLength !== hemiLength || - hash.numDirectionalShadows !== numDirectionalShadows || - hash.numPointShadows !== numPointShadows || - hash.numSpotShadows !== numSpotShadows || - hash.numSpotMaps !== numSpotMaps || - hash.numLightProbes !== numLightProbes ) { + constructor( code ) { - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; + this.id = _id ++; - state.directionalShadow.length = numDirectionalShadows; - state.directionalShadowMap.length = numDirectionalShadows; - state.pointShadow.length = numPointShadows; - state.pointShadowMap.length = numPointShadows; - state.spotShadow.length = numSpotShadows; - state.spotShadowMap.length = numSpotShadows; - state.directionalShadowMatrix.length = numDirectionalShadows; - state.pointShadowMatrix.length = numPointShadows; - state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; - state.spotLightMap.length = numSpotMaps; - state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; - state.numLightProbes = numLightProbes; + this.code = code; + this.usedTimes = 0; - hash.directionalLength = directionalLength; - hash.pointLength = pointLength; - hash.spotLength = spotLength; - hash.rectAreaLength = rectAreaLength; - hash.hemiLength = hemiLength; + } - hash.numDirectionalShadows = numDirectionalShadows; - hash.numPointShadows = numPointShadows; - hash.numSpotShadows = numSpotShadows; - hash.numSpotMaps = numSpotMaps; +} - hash.numLightProbes = numLightProbes; +function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { - state.version = nextVersion ++; + const _programLayers = new Layers(); + const _customShaders = new WebGLShaderCache(); + const _activeChannels = new Set(); + const programs = []; - } + const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; + const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; - } + let precision = capabilities.precision; - function setupView( lights, camera ) { + const shaderIDs = { + MeshDepthMaterial: 'depth', + MeshDistanceMaterial: 'distanceRGBA', + MeshNormalMaterial: 'normal', + MeshBasicMaterial: 'basic', + MeshLambertMaterial: 'lambert', + MeshPhongMaterial: 'phong', + MeshToonMaterial: 'toon', + MeshStandardMaterial: 'physical', + MeshPhysicalMaterial: 'physical', + MeshMatcapMaterial: 'matcap', + LineBasicMaterial: 'basic', + LineDashedMaterial: 'dashed', + PointsMaterial: 'points', + ShadowMaterial: 'shadow', + SpriteMaterial: 'sprite' + }; - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; + function getChannel( value ) { - const viewMatrix = camera.matrixWorldInverse; + _activeChannels.add( value ); - for ( let i = 0, l = lights.length; i < l; i ++ ) { + if ( value === 0 ) return 'uv'; - const light = lights[ i ]; + return `uv${ value }`; - if ( light.isDirectionalLight ) { + } - const uniforms = state.directional[ directionalLength ]; + function getParameters( material, lights, shadows, scene, object ) { - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); + const fog = scene.fog; + const geometry = object.geometry; + const environment = material.isMeshStandardMaterial ? scene.environment : null; - directionalLength ++; + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - } else if ( light.isSpotLight ) { + const shaderID = shaderIDs[ material.type ]; - const uniforms = state.spot[ spotLength ]; + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + if ( material.precision !== null ) { - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); + precision = capabilities.getMaxPrecision( material.precision ); - spotLength ++; + if ( precision !== material.precision ) { - } else if ( light.isRectAreaLight ) { + console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - const uniforms = state.rectArea[ rectAreaLength ]; + } - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + } - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); + // - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); + let morphTextureStride = 0; - rectAreaLength ++; + if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; + if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; + if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - } else if ( light.isPointLight ) { + // - const uniforms = state.point[ pointLength ]; + let vertexShader, fragmentShader; + let customVertexShaderID, customFragmentShaderID; - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + if ( shaderID ) { - pointLength ++; + const shader = ShaderLib[ shaderID ]; - } else if ( light.isHemisphereLight ) { + vertexShader = shader.vertexShader; + fragmentShader = shader.fragmentShader; - const uniforms = state.hemi[ hemiLength ]; + } else { - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); + vertexShader = material.vertexShader; + fragmentShader = material.fragmentShader; - hemiLength ++; + _customShaders.update( material ); - } + customVertexShaderID = _customShaders.getVertexShaderID( material ); + customFragmentShaderID = _customShaders.getFragmentShaderID( material ); } - } - - return { - setup: setup, - setupView: setupView, - state: state - }; + const currentRenderTarget = renderer.getRenderTarget(); + const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); -} + const IS_INSTANCEDMESH = object.isInstancedMesh === true; + const IS_BATCHEDMESH = object.isBatchedMesh === true; -function WebGLRenderState( extensions, capabilities ) { + const HAS_MAP = !! material.map; + const HAS_MATCAP = !! material.matcap; + const HAS_ENVMAP = !! envMap; + const HAS_AOMAP = !! material.aoMap; + const HAS_LIGHTMAP = !! material.lightMap; + const HAS_BUMPMAP = !! material.bumpMap; + const HAS_NORMALMAP = !! material.normalMap; + const HAS_DISPLACEMENTMAP = !! material.displacementMap; + const HAS_EMISSIVEMAP = !! material.emissiveMap; - const lights = new WebGLLights( extensions, capabilities ); + const HAS_METALNESSMAP = !! material.metalnessMap; + const HAS_ROUGHNESSMAP = !! material.roughnessMap; - const lightsArray = []; - const shadowsArray = []; + const HAS_ANISOTROPY = material.anisotropy > 0; + const HAS_CLEARCOAT = material.clearcoat > 0; + const HAS_DISPERSION = material.dispersion > 0; + const HAS_IRIDESCENCE = material.iridescence > 0; + const HAS_SHEEN = material.sheen > 0; + const HAS_TRANSMISSION = material.transmission > 0; - function init() { + const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - lightsArray.length = 0; - shadowsArray.length = 0; + const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; + const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; + const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - } + const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; + const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; - function pushLight( light ) { + const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; + const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; - lightsArray.push( light ); + const HAS_SPECULARMAP = !! material.specularMap; + const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; + const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - } + const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; + const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - function pushShadow( shadowLight ) { + const HAS_GRADIENTMAP = !! material.gradientMap; - shadowsArray.push( shadowLight ); + const HAS_ALPHAMAP = !! material.alphaMap; - } + const HAS_ALPHATEST = material.alphaTest > 0; - function setupLights( useLegacyLights ) { + const HAS_ALPHAHASH = !! material.alphaHash; - lights.setup( lightsArray, useLegacyLights ); + const HAS_EXTENSIONS = !! material.extensions; - } + let toneMapping = NoToneMapping; - function setupLightsView( camera ) { + if ( material.toneMapped ) { - lights.setupView( lightsArray, camera ); + if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { - } + toneMapping = renderer.toneMapping; - const state = { - lightsArray: lightsArray, - shadowsArray: shadowsArray, + } - lights: lights - }; + } - return { - init: init, - state: state, - setupLights: setupLights, - setupLightsView: setupLightsView, + const parameters = { - pushLight: pushLight, - pushShadow: pushShadow - }; + shaderID: shaderID, + shaderType: material.type, + shaderName: material.name, -} + vertexShader: vertexShader, + fragmentShader: fragmentShader, + defines: material.defines, -function WebGLRenderStates( extensions, capabilities ) { + customVertexShaderID: customVertexShaderID, + customFragmentShaderID: customFragmentShaderID, - let renderStates = new WeakMap(); + isRawShaderMaterial: material.isRawShaderMaterial === true, + glslVersion: material.glslVersion, - function get( scene, renderCallDepth = 0 ) { + precision: precision, - const renderStateArray = renderStates.get( scene ); - let renderState; + batching: IS_BATCHEDMESH, + batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null, + instancing: IS_INSTANCEDMESH, + instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, + instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, - if ( renderStateArray === undefined ) { + supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, + outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), + alphaToCoverage: !! material.alphaToCoverage, - renderState = new WebGLRenderState( extensions, capabilities ); - renderStates.set( scene, [ renderState ] ); + map: HAS_MAP, + matcap: HAS_MATCAP, + envMap: HAS_ENVMAP, + envMapMode: HAS_ENVMAP && envMap.mapping, + envMapCubeUVHeight: envMapCubeUVHeight, + aoMap: HAS_AOMAP, + lightMap: HAS_LIGHTMAP, + bumpMap: HAS_BUMPMAP, + normalMap: HAS_NORMALMAP, + displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, + emissiveMap: HAS_EMISSIVEMAP, - } else { + normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, + normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - if ( renderCallDepth >= renderStateArray.length ) { + metalnessMap: HAS_METALNESSMAP, + roughnessMap: HAS_ROUGHNESSMAP, - renderState = new WebGLRenderState( extensions, capabilities ); - renderStateArray.push( renderState ); + anisotropy: HAS_ANISOTROPY, + anisotropyMap: HAS_ANISOTROPYMAP, - } else { + clearcoat: HAS_CLEARCOAT, + clearcoatMap: HAS_CLEARCOATMAP, + clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, + clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - renderState = renderStateArray[ renderCallDepth ]; + dispersion: HAS_DISPERSION, - } + iridescence: HAS_IRIDESCENCE, + iridescenceMap: HAS_IRIDESCENCEMAP, + iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - } + sheen: HAS_SHEEN, + sheenColorMap: HAS_SHEEN_COLORMAP, + sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - return renderState; + specularMap: HAS_SPECULARMAP, + specularColorMap: HAS_SPECULAR_COLORMAP, + specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - } + transmission: HAS_TRANSMISSION, + transmissionMap: HAS_TRANSMISSIONMAP, + thicknessMap: HAS_THICKNESSMAP, - function dispose() { + gradientMap: HAS_GRADIENTMAP, - renderStates = new WeakMap(); + opaque: material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false, - } + alphaMap: HAS_ALPHAMAP, + alphaTest: HAS_ALPHATEST, + alphaHash: HAS_ALPHAHASH, - return { - get: get, - dispose: dispose - }; + combine: material.combine, -} + // -class MeshDepthMaterial extends Material { + mapUv: HAS_MAP && getChannel( material.map.channel ), + aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), + lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), + bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), + normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), + displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), + emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - constructor( parameters ) { + metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), + roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - super(); + anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - this.isMeshDepthMaterial = true; + clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), + clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), + clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - this.type = 'MeshDepthMaterial'; + iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), + iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - this.depthPacking = BasicDepthPacking; + sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), + sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - this.map = null; + specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), + specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), + specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - this.alphaMap = null; + transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), + thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - this.wireframe = false; - this.wireframeLinewidth = 1; + // - this.setValues( parameters ); + vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), + vertexColors: material.vertexColors, + vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - } + pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), - copy( source ) { + fog: !! fog, + useFog: material.fog === true, + fogExp2: ( !! fog && fog.isFogExp2 ), - super.copy( source ); + flatShading: ( material.flatShading === true && material.wireframe === false ), - this.depthPacking = source.depthPacking; + sizeAttenuation: material.sizeAttenuation === true, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reversedDepthBuffer: reversedDepthBuffer, - this.map = source.map; + skinning: object.isSkinnedMesh === true, - this.alphaMap = source.alphaMap; + morphTargets: geometry.morphAttributes.position !== undefined, + morphNormals: geometry.morphAttributes.normal !== undefined, + morphColors: geometry.morphAttributes.color !== undefined, + morphTargetsCount: morphTargetsCount, + morphTextureStride: morphTextureStride, - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + numDirLights: lights.directional.length, + numPointLights: lights.point.length, + numSpotLights: lights.spot.length, + numSpotLightMaps: lights.spotLightMap.length, + numRectAreaLights: lights.rectArea.length, + numHemiLights: lights.hemi.length, - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; + numDirLightShadows: lights.directionalShadowMap.length, + numPointLightShadows: lights.pointShadowMap.length, + numSpotLightShadows: lights.spotShadowMap.length, + numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - return this; + numLightProbes: lights.numLightProbes, - } + numClippingPlanes: clipping.numPlanes, + numClipIntersection: clipping.numIntersection, -} + dithering: material.dithering, -class MeshDistanceMaterial extends Material { + shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, + shadowMapType: renderer.shadowMap.type, - constructor( parameters ) { + toneMapping: toneMapping, - super(); + decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), + decodeVideoTextureEmissive: HAS_EMISSIVEMAP && ( material.emissiveMap.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.emissiveMap.colorSpace ) === SRGBTransfer ), - this.isMeshDistanceMaterial = true; + premultipliedAlpha: material.premultipliedAlpha, - this.type = 'MeshDistanceMaterial'; + doubleSided: material.side === DoubleSide, + flipSided: material.side === BackSide, - this.map = null; + useDepthPacking: material.depthPacking >= 0, + depthPacking: material.depthPacking || 0, - this.alphaMap = null; + index0AttributeName: material.index0AttributeName, - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), + extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), - this.setValues( parameters ); + rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), - } + customProgramCacheKey: material.customProgramCacheKey() - copy( source ) { + }; - super.copy( source ); + // the usage of getChannel() determines the active texture channels for this shader - this.map = source.map; - - this.alphaMap = source.alphaMap; + parameters.vertexUv1s = _activeChannels.has( 1 ); + parameters.vertexUv2s = _activeChannels.has( 2 ); + parameters.vertexUv3s = _activeChannels.has( 3 ); - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + _activeChannels.clear(); - return this; + return parameters; } -} - -const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - -const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - -function WebGLShadowMap( _renderer, _objects, _capabilities ) { + function getProgramCacheKey( parameters ) { - let _frustum = new Frustum(); + const array = []; - const _shadowMapSize = new Vector2(), - _viewportSize = new Vector2(), + if ( parameters.shaderID ) { - _viewport = new Vector4(), + array.push( parameters.shaderID ); - _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), - _distanceMaterial = new MeshDistanceMaterial(), + } else { - _materialCache = {}, + array.push( parameters.customVertexShaderID ); + array.push( parameters.customFragmentShaderID ); - _maxTextureSize = _capabilities.maxTextureSize; + } - const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; + if ( parameters.defines !== undefined ) { - const shadowMaterialVertical = new ShaderMaterial( { - defines: { - VSM_SAMPLES: 8 - }, - uniforms: { - shadow_pass: { value: null }, - resolution: { value: new Vector2() }, - radius: { value: 4.0 } - }, + for ( const name in parameters.defines ) { - vertexShader: vertex, - fragmentShader: fragment + array.push( name ); + array.push( parameters.defines[ name ] ); - } ); + } - const shadowMaterialHorizontal = shadowMaterialVertical.clone(); - shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; + } - const fullScreenTri = new BufferGeometry(); - fullScreenTri.setAttribute( - 'position', - new BufferAttribute( - new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), - 3 - ) - ); + if ( parameters.isRawShaderMaterial === false ) { - const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); + getProgramCacheKeyParameters( array, parameters ); + getProgramCacheKeyBooleans( array, parameters ); + array.push( renderer.outputColorSpace ); - const scope = this; + } - this.enabled = false; + array.push( parameters.customProgramCacheKey ); - this.autoUpdate = true; - this.needsUpdate = false; + return array.join(); - this.type = PCFShadowMap; - let _previousType = this.type; + } - this.render = function ( lights, scene, camera ) { + function getProgramCacheKeyParameters( array, parameters ) { - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; + array.push( parameters.precision ); + array.push( parameters.outputColorSpace ); + array.push( parameters.envMapMode ); + array.push( parameters.envMapCubeUVHeight ); + array.push( parameters.mapUv ); + array.push( parameters.alphaMapUv ); + array.push( parameters.lightMapUv ); + array.push( parameters.aoMapUv ); + array.push( parameters.bumpMapUv ); + array.push( parameters.normalMapUv ); + array.push( parameters.displacementMapUv ); + array.push( parameters.emissiveMapUv ); + array.push( parameters.metalnessMapUv ); + array.push( parameters.roughnessMapUv ); + array.push( parameters.anisotropyMapUv ); + array.push( parameters.clearcoatMapUv ); + array.push( parameters.clearcoatNormalMapUv ); + array.push( parameters.clearcoatRoughnessMapUv ); + array.push( parameters.iridescenceMapUv ); + array.push( parameters.iridescenceThicknessMapUv ); + array.push( parameters.sheenColorMapUv ); + array.push( parameters.sheenRoughnessMapUv ); + array.push( parameters.specularMapUv ); + array.push( parameters.specularColorMapUv ); + array.push( parameters.specularIntensityMapUv ); + array.push( parameters.transmissionMapUv ); + array.push( parameters.thicknessMapUv ); + array.push( parameters.combine ); + array.push( parameters.fogExp2 ); + array.push( parameters.sizeAttenuation ); + array.push( parameters.morphTargetsCount ); + array.push( parameters.morphAttributeCount ); + array.push( parameters.numDirLights ); + array.push( parameters.numPointLights ); + array.push( parameters.numSpotLights ); + array.push( parameters.numSpotLightMaps ); + array.push( parameters.numHemiLights ); + array.push( parameters.numRectAreaLights ); + array.push( parameters.numDirLightShadows ); + array.push( parameters.numPointLightShadows ); + array.push( parameters.numSpotLightShadows ); + array.push( parameters.numSpotLightShadowsWithMaps ); + array.push( parameters.numLightProbes ); + array.push( parameters.shadowMapType ); + array.push( parameters.toneMapping ); + array.push( parameters.numClippingPlanes ); + array.push( parameters.numClipIntersection ); + array.push( parameters.depthPacking ); - if ( lights.length === 0 ) return; + } - const currentRenderTarget = _renderer.getRenderTarget(); - const activeCubeFace = _renderer.getActiveCubeFace(); - const activeMipmapLevel = _renderer.getActiveMipmapLevel(); + function getProgramCacheKeyBooleans( array, parameters ) { - const _state = _renderer.state; + _programLayers.disableAll(); - // Set GL state for depth map. - _state.setBlending( NoBlending ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); + if ( parameters.supportsVertexTextures ) + _programLayers.enable( 0 ); + if ( parameters.instancing ) + _programLayers.enable( 1 ); + if ( parameters.instancingColor ) + _programLayers.enable( 2 ); + if ( parameters.instancingMorph ) + _programLayers.enable( 3 ); + if ( parameters.matcap ) + _programLayers.enable( 4 ); + if ( parameters.envMap ) + _programLayers.enable( 5 ); + if ( parameters.normalMapObjectSpace ) + _programLayers.enable( 6 ); + if ( parameters.normalMapTangentSpace ) + _programLayers.enable( 7 ); + if ( parameters.clearcoat ) + _programLayers.enable( 8 ); + if ( parameters.iridescence ) + _programLayers.enable( 9 ); + if ( parameters.alphaTest ) + _programLayers.enable( 10 ); + if ( parameters.vertexColors ) + _programLayers.enable( 11 ); + if ( parameters.vertexAlphas ) + _programLayers.enable( 12 ); + if ( parameters.vertexUv1s ) + _programLayers.enable( 13 ); + if ( parameters.vertexUv2s ) + _programLayers.enable( 14 ); + if ( parameters.vertexUv3s ) + _programLayers.enable( 15 ); + if ( parameters.vertexTangents ) + _programLayers.enable( 16 ); + if ( parameters.anisotropy ) + _programLayers.enable( 17 ); + if ( parameters.alphaHash ) + _programLayers.enable( 18 ); + if ( parameters.batching ) + _programLayers.enable( 19 ); + if ( parameters.dispersion ) + _programLayers.enable( 20 ); + if ( parameters.batchingColor ) + _programLayers.enable( 21 ); + if ( parameters.gradientMap ) + _programLayers.enable( 22 ); - // check for shadow map type changes + array.push( _programLayers.mask ); + _programLayers.disableAll(); - const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); - const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); + if ( parameters.fog ) + _programLayers.enable( 0 ); + if ( parameters.useFog ) + _programLayers.enable( 1 ); + if ( parameters.flatShading ) + _programLayers.enable( 2 ); + if ( parameters.logarithmicDepthBuffer ) + _programLayers.enable( 3 ); + if ( parameters.reversedDepthBuffer ) + _programLayers.enable( 4 ); + if ( parameters.skinning ) + _programLayers.enable( 5 ); + if ( parameters.morphTargets ) + _programLayers.enable( 6 ); + if ( parameters.morphNormals ) + _programLayers.enable( 7 ); + if ( parameters.morphColors ) + _programLayers.enable( 8 ); + if ( parameters.premultipliedAlpha ) + _programLayers.enable( 9 ); + if ( parameters.shadowMapEnabled ) + _programLayers.enable( 10 ); + if ( parameters.doubleSided ) + _programLayers.enable( 11 ); + if ( parameters.flipSided ) + _programLayers.enable( 12 ); + if ( parameters.useDepthPacking ) + _programLayers.enable( 13 ); + if ( parameters.dithering ) + _programLayers.enable( 14 ); + if ( parameters.transmission ) + _programLayers.enable( 15 ); + if ( parameters.sheen ) + _programLayers.enable( 16 ); + if ( parameters.opaque ) + _programLayers.enable( 17 ); + if ( parameters.pointsUvs ) + _programLayers.enable( 18 ); + if ( parameters.decodeVideoTexture ) + _programLayers.enable( 19 ); + if ( parameters.decodeVideoTextureEmissive ) + _programLayers.enable( 20 ); + if ( parameters.alphaToCoverage ) + _programLayers.enable( 21 ); - // render depth map + array.push( _programLayers.mask ); - for ( let i = 0, il = lights.length; i < il; i ++ ) { + } - const light = lights[ i ]; - const shadow = light.shadow; + function getUniforms( material ) { - if ( shadow === undefined ) { + const shaderID = shaderIDs[ material.type ]; + let uniforms; - console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); - continue; + if ( shaderID ) { - } + const shader = ShaderLib[ shaderID ]; + uniforms = UniformsUtils.clone( shader.uniforms ); - if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; + } else { - _shadowMapSize.copy( shadow.mapSize ); + uniforms = material.uniforms; - const shadowFrameExtents = shadow.getFrameExtents(); + } - _shadowMapSize.multiply( shadowFrameExtents ); + return uniforms; - _viewportSize.copy( shadow.mapSize ); + } - if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { + function acquireProgram( parameters, cacheKey ) { - if ( _shadowMapSize.x > _maxTextureSize ) { + let program; - _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); - _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; - shadow.mapSize.x = _viewportSize.x; + // Check if code has been already compiled + for ( let p = 0, pl = programs.length; p < pl; p ++ ) { - } + const preexistingProgram = programs[ p ]; - if ( _shadowMapSize.y > _maxTextureSize ) { + if ( preexistingProgram.cacheKey === cacheKey ) { - _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); - _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; - shadow.mapSize.y = _viewportSize.y; + program = preexistingProgram; + ++ program.usedTimes; - } + break; } - if ( shadow.map === null || toVSM === true || fromVSM === true ) { - - const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - - if ( shadow.map !== null ) { - - shadow.map.dispose(); - - } + } - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); - shadow.map.texture.name = light.name + '.shadowMap'; + if ( program === undefined ) { - shadow.camera.updateProjectionMatrix(); + program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); + programs.push( program ); - } + } - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); + return program; - const viewportCount = shadow.getViewportCount(); + } - for ( let vp = 0; vp < viewportCount; vp ++ ) { + function releaseProgram( program ) { - const viewport = shadow.getViewport( vp ); + if ( -- program.usedTimes === 0 ) { - _viewport.set( - _viewportSize.x * viewport.x, - _viewportSize.y * viewport.y, - _viewportSize.x * viewport.z, - _viewportSize.y * viewport.w - ); + // Remove from unordered set + const i = programs.indexOf( program ); + programs[ i ] = programs[ programs.length - 1 ]; + programs.pop(); - _state.viewport( _viewport ); + // Free WebGL resources + program.destroy(); - shadow.updateMatrices( light, vp ); + } - _frustum = shadow.getFrustum(); + } - renderObject( scene, camera, shadow.camera, light, this.type ); + function releaseShaderCache( material ) { - } + _customShaders.remove( material ); - // do blur pass for VSM + } - if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { + function dispose() { - VSMPass( shadow, camera ); + _customShaders.dispose(); - } + } - shadow.needsUpdate = false; + return { + getParameters: getParameters, + getProgramCacheKey: getProgramCacheKey, + getUniforms: getUniforms, + acquireProgram: acquireProgram, + releaseProgram: releaseProgram, + releaseShaderCache: releaseShaderCache, + // Exposed for resource monitoring & error feedback via renderer.info: + programs: programs, + dispose: dispose + }; - } +} - _previousType = this.type; +function WebGLProperties() { - scope.needsUpdate = false; + let properties = new WeakMap(); - _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); + function has( object ) { - }; + return properties.has( object ); - function VSMPass( shadow, camera ) { + } - const geometry = _objects.update( fullScreenMesh ); + function get( object ) { - if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { + let map = properties.get( object ); - shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; - shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; + if ( map === undefined ) { - shadowMaterialVertical.needsUpdate = true; - shadowMaterialHorizontal.needsUpdate = true; + map = {}; + properties.set( object, map ); } - if ( shadow.mapPass === null ) { + return map; - shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); + } - } + function remove( object ) { - // vertical pass + properties.delete( object ); - shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; - shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; - shadowMaterialVertical.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.mapPass ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); + } - // horizontal pass + function update( object, key, value ) { - shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; - shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; - shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); + properties.get( object )[ key ] = value; } - function getDepthMaterial( object, material, light, type ) { + function dispose() { - let result = null; + properties = new WeakMap(); - const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; + } - if ( customMaterial !== undefined ) { + return { + has: has, + get: get, + remove: remove, + update: update, + dispose: dispose + }; - result = customMaterial; +} - } else { +function painterSortStable( a, b ) { - result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; + if ( a.groupOrder !== b.groupOrder ) { - if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || - ( material.displacementMap && material.displacementScale !== 0 ) || - ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) ) { + return a.groupOrder - b.groupOrder; - // in this case we need a unique material instance reflecting the - // appropriate state + } else if ( a.renderOrder !== b.renderOrder ) { - const keyA = result.uuid, keyB = material.uuid; + return a.renderOrder - b.renderOrder; - let materialsForVariant = _materialCache[ keyA ]; + } else if ( a.material.id !== b.material.id ) { - if ( materialsForVariant === undefined ) { + return a.material.id - b.material.id; - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; + } else if ( a.z !== b.z ) { - } + return a.z - b.z; - let cachedMaterial = materialsForVariant[ keyB ]; + } else { - if ( cachedMaterial === undefined ) { + return a.id - b.id; - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; - material.addEventListener( 'dispose', onMaterialDispose ); + } - } +} - result = cachedMaterial; +function reversePainterSortStable( a, b ) { - } + if ( a.groupOrder !== b.groupOrder ) { - } + return a.groupOrder - b.groupOrder; - result.visible = material.visible; - result.wireframe = material.wireframe; + } else if ( a.renderOrder !== b.renderOrder ) { - if ( type === VSMShadowMap ) { + return a.renderOrder - b.renderOrder; - result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; + } else if ( a.z !== b.z ) { - } else { + return b.z - a.z; - result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; + } else { - } + return a.id - b.id; - result.alphaMap = material.alphaMap; - result.alphaTest = material.alphaTest; - result.map = material.map; + } - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; +} - result.displacementMap = material.displacementMap; - result.displacementScale = material.displacementScale; - result.displacementBias = material.displacementBias; - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; +function WebGLRenderList() { - if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { + const renderItems = []; + let renderItemsIndex = 0; - const materialProperties = _renderer.properties.get( result ); - materialProperties.light = light; + const opaque = []; + const transmissive = []; + const transparent = []; - } + function init() { - return result; + renderItemsIndex = 0; + + opaque.length = 0; + transmissive.length = 0; + transparent.length = 0; } - function renderObject( object, camera, shadowCamera, light, type ) { + function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - if ( object.visible === false ) return; + let renderItem = renderItems[ renderItemsIndex ]; - const visible = object.layers.test( camera.layers ); + if ( renderItem === undefined ) { - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group + }; - if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { + renderItems[ renderItemsIndex ] = renderItem; - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + } else { - const geometry = _objects.update( object ); - const material = object.material; + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; - if ( Array.isArray( material ) ) { + } - const groups = geometry.groups; + renderItemsIndex ++; - for ( let k = 0, kl = groups.length; k < kl; k ++ ) { + return renderItem; - const group = groups[ k ]; - const groupMaterial = material[ group.materialIndex ]; + } - if ( groupMaterial && groupMaterial.visible ) { + function push( object, geometry, material, groupOrder, z, group ) { - const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); + if ( material.transmission > 0.0 ) { - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); + transmissive.push( renderItem ); - object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); + } else if ( material.transparent === true ) { - } + transparent.push( renderItem ); - } + } else { - } else if ( material.visible ) { + opaque.push( renderItem ); - const depthMaterial = getDepthMaterial( object, material, light, type ); + } - object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); + } - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); + function unshift( object, geometry, material, groupOrder, z, group ) { - object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - } + if ( material.transmission > 0.0 ) { - } + transmissive.unshift( renderItem ); - } + } else if ( material.transparent === true ) { - const children = object.children; + transparent.unshift( renderItem ); - for ( let i = 0, l = children.length; i < l; i ++ ) { + } else { - renderObject( children[ i ], camera, shadowCamera, light, type ); + opaque.unshift( renderItem ); } } - function onMaterialDispose( event ) { - - const material = event.target; + function sort( customOpaqueSort, customTransparentSort ) { - material.removeEventListener( 'dispose', onMaterialDispose ); + if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); + if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); + if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - // make sure to remove the unique distance/depth materials used for shadow map rendering + } - for ( const id in _materialCache ) { + function finish() { - const cache = _materialCache[ id ]; + // Clear references from inactive renderItems in the list - const uuid = event.target.uuid; + for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { - if ( uuid in cache ) { + const renderItem = renderItems[ i ]; - const shadowMaterial = cache[ uuid ]; - shadowMaterial.dispose(); - delete cache[ uuid ]; + if ( renderItem.id === null ) break; - } + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.group = null; } } -} + return { -function WebGLState( gl, extensions, capabilities ) { + opaque: opaque, + transmissive: transmissive, + transparent: transparent, - const isWebGL2 = capabilities.isWebGL2; + init: init, + push: push, + unshift: unshift, + finish: finish, - function ColorBuffer() { + sort: sort + }; - let locked = false; +} - const color = new Vector4(); - let currentColorMask = null; - const currentColorClear = new Vector4( 0, 0, 0, 0 ); +function WebGLRenderLists() { - return { + let lists = new WeakMap(); - setMask: function ( colorMask ) { + function get( scene, renderCallDepth ) { - if ( currentColorMask !== colorMask && ! locked ) { + const listArray = lists.get( scene ); + let list; - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; + if ( listArray === undefined ) { - } + list = new WebGLRenderList(); + lists.set( scene, [ list ] ); - }, + } else { - setLocked: function ( lock ) { + if ( renderCallDepth >= listArray.length ) { - locked = lock; + list = new WebGLRenderList(); + listArray.push( list ); - }, + } else { - setClear: function ( r, g, b, a, premultipliedAlpha ) { + list = listArray[ renderCallDepth ]; - if ( premultipliedAlpha === true ) { + } - r *= a; g *= a; b *= a; + } - } + return list; - color.set( r, g, b, a ); + } - if ( currentColorClear.equals( color ) === false ) { + function dispose() { - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); + lists = new WeakMap(); - } + } - }, + return { + get: get, + dispose: dispose + }; - reset: function () { +} - locked = false; +function UniformsCache() { - currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state + const lights = {}; - } + return { - }; + get: function ( light ) { - } + if ( lights[ light.id ] !== undefined ) { - function DepthBuffer() { + return lights[ light.id ]; - let locked = false; + } - let currentDepthMask = null; - let currentDepthFunc = null; - let currentDepthClear = null; + let uniforms; - return { + switch ( light.type ) { - setTest: function ( depthTest ) { + case 'DirectionalLight': + uniforms = { + direction: new Vector3(), + color: new Color() + }; + break; - if ( depthTest ) { + case 'SpotLight': + uniforms = { + position: new Vector3(), + direction: new Vector3(), + color: new Color(), + distance: 0, + coneCos: 0, + penumbraCos: 0, + decay: 0 + }; + break; - enable( gl.DEPTH_TEST ); + case 'PointLight': + uniforms = { + position: new Vector3(), + color: new Color(), + distance: 0, + decay: 0 + }; + break; - } else { + case 'HemisphereLight': + uniforms = { + direction: new Vector3(), + skyColor: new Color(), + groundColor: new Color() + }; + break; - disable( gl.DEPTH_TEST ); + case 'RectAreaLight': + uniforms = { + color: new Color(), + position: new Vector3(), + halfWidth: new Vector3(), + halfHeight: new Vector3() + }; + break; - } + } - }, + lights[ light.id ] = uniforms; - setMask: function ( depthMask ) { + return uniforms; - if ( currentDepthMask !== depthMask && ! locked ) { + } - gl.depthMask( depthMask ); - currentDepthMask = depthMask; + }; - } +} - }, +function ShadowUniformsCache() { - setFunc: function ( depthFunc ) { + const lights = {}; - if ( currentDepthFunc !== depthFunc ) { + return { - switch ( depthFunc ) { + get: function ( light ) { - case NeverDepth: + if ( lights[ light.id ] !== undefined ) { - gl.depthFunc( gl.NEVER ); - break; + return lights[ light.id ]; - case AlwaysDepth: + } - gl.depthFunc( gl.ALWAYS ); - break; + let uniforms; - case LessDepth: + switch ( light.type ) { - gl.depthFunc( gl.LESS ); - break; + case 'DirectionalLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; - case LessEqualDepth: + case 'SpotLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; - gl.depthFunc( gl.LEQUAL ); - break; + case 'PointLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2(), + shadowCameraNear: 1, + shadowCameraFar: 1000 + }; + break; - case EqualDepth: + // TODO (abelnation): set RectAreaLight shadow uniforms - gl.depthFunc( gl.EQUAL ); - break; + } - case GreaterEqualDepth: + lights[ light.id ] = uniforms; - gl.depthFunc( gl.GEQUAL ); - break; + return uniforms; - case GreaterDepth: + } - gl.depthFunc( gl.GREATER ); - break; + }; - case NotEqualDepth: +} - gl.depthFunc( gl.NOTEQUAL ); - break; - default: - gl.depthFunc( gl.LEQUAL ); +let nextVersion = 0; - } +function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - currentDepthFunc = depthFunc; + return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - } +} - }, +function WebGLLights( extensions ) { - setLocked: function ( lock ) { + const cache = new UniformsCache(); - locked = lock; + const shadowCache = ShadowUniformsCache(); - }, + const state = { - setClear: function ( depth ) { + version: 0, - if ( currentDepthClear !== depth ) { + hash: { + directionalLength: -1, + pointLength: -1, + spotLength: -1, + rectAreaLength: -1, + hemiLength: -1, + + numDirectionalShadows: -1, + numPointShadows: -1, + numSpotShadows: -1, + numSpotMaps: -1, + + numLightProbes: -1 + }, - gl.clearDepth( depth ); - currentDepthClear = depth; + ambient: [ 0, 0, 0 ], + probe: [], + directional: [], + directionalShadow: [], + directionalShadowMap: [], + directionalShadowMatrix: [], + spot: [], + spotLightMap: [], + spotShadow: [], + spotShadowMap: [], + spotLightMatrix: [], + rectArea: [], + rectAreaLTC1: null, + rectAreaLTC2: null, + point: [], + pointShadow: [], + pointShadowMap: [], + pointShadowMatrix: [], + hemi: [], + numSpotLightShadowsWithMaps: 0, + numLightProbes: 0 - } + }; - }, + for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - reset: function () { + const vector3 = new Vector3(); + const matrix4 = new Matrix4(); + const matrix42 = new Matrix4(); - locked = false; + function setup( lights ) { - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; + let r = 0, g = 0, b = 0; - } + for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - }; + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - } + let numDirectionalShadows = 0; + let numPointShadows = 0; + let numSpotShadows = 0; + let numSpotMaps = 0; + let numSpotShadowsWithMaps = 0; - function StencilBuffer() { + let numLightProbes = 0; - let locked = false; + // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] + lights.sort( shadowCastingAndTexturingLightsFirst ); - let currentStencilMask = null; - let currentStencilFunc = null; - let currentStencilRef = null; - let currentStencilFuncMask = null; - let currentStencilFail = null; - let currentStencilZFail = null; - let currentStencilZPass = null; - let currentStencilClear = null; + for ( let i = 0, l = lights.length; i < l; i ++ ) { - return { + const light = lights[ i ]; - setTest: function ( stencilTest ) { + const color = light.color; + const intensity = light.intensity; + const distance = light.distance; - if ( ! locked ) { + const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - if ( stencilTest ) { + if ( light.isAmbientLight ) { - enable( gl.STENCIL_TEST ); + r += color.r * intensity; + g += color.g * intensity; + b += color.b * intensity; - } else { + } else if ( light.isLightProbe ) { - disable( gl.STENCIL_TEST ); + for ( let j = 0; j < 9; j ++ ) { - } + state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); } - }, + numLightProbes ++; - setMask: function ( stencilMask ) { + } else if ( light.isDirectionalLight ) { - if ( currentStencilMask !== stencilMask && ! locked ) { + const uniforms = cache.get( light ); - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - } + if ( light.castShadow ) { - }, + const shadow = light.shadow; - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { + const shadowUniforms = shadowCache.get( light ); - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); + state.directionalShadow[ directionalLength ] = shadowUniforms; + state.directionalShadowMap[ directionalLength ] = shadowMap; + state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; + numDirectionalShadows ++; } - }, + state.directional[ directionalLength ] = uniforms; - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { + directionalLength ++; - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { + } else if ( light.isSpotLight ) { - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); + const uniforms = cache.get( light ); - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; + uniforms.position.setFromMatrixPosition( light.matrixWorld ); - } + uniforms.color.copy( color ).multiplyScalar( intensity ); + uniforms.distance = distance; - }, + uniforms.coneCos = Math.cos( light.angle ); + uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); + uniforms.decay = light.decay; - setLocked: function ( lock ) { + state.spot[ spotLength ] = uniforms; - locked = lock; + const shadow = light.shadow; - }, + if ( light.map ) { - setClear: function ( stencil ) { + state.spotLightMap[ numSpotMaps ] = light.map; + numSpotMaps ++; - if ( currentStencilClear !== stencil ) { + // make sure the lightMatrix is up to date + // TODO : do it if required only + shadow.updateMatrices( light ); - gl.clearStencil( stencil ); - currentStencilClear = stencil; + if ( light.castShadow ) numSpotShadowsWithMaps ++; } - }, + state.spotLightMatrix[ spotLength ] = shadow.matrix; - reset: function () { + if ( light.castShadow ) { - locked = false; + const shadowUniforms = shadowCache.get( light ); - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - } + state.spotShadow[ spotLength ] = shadowUniforms; + state.spotShadowMap[ spotLength ] = shadowMap; - }; + numSpotShadows ++; - } + } - // + spotLength ++; - const colorBuffer = new ColorBuffer(); - const depthBuffer = new DepthBuffer(); - const stencilBuffer = new StencilBuffer(); + } else if ( light.isRectAreaLight ) { - const uboBindings = new WeakMap(); - const uboProgramMap = new WeakMap(); + const uniforms = cache.get( light ); - let enabledCapabilities = {}; + uniforms.color.copy( color ).multiplyScalar( intensity ); - let currentBoundFramebuffers = {}; - let currentDrawbuffers = new WeakMap(); - let defaultDrawbuffers = []; + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - let currentProgram = null; + state.rectArea[ rectAreaLength ] = uniforms; - let currentBlendingEnabled = false; - let currentBlending = null; - let currentBlendEquation = null; - let currentBlendSrc = null; - let currentBlendDst = null; - let currentBlendEquationAlpha = null; - let currentBlendSrcAlpha = null; - let currentBlendDstAlpha = null; - let currentBlendColor = new Color( 0, 0, 0 ); - let currentBlendAlpha = 0; - let currentPremultipledAlpha = false; + rectAreaLength ++; - let currentFlipSided = null; - let currentCullFace = null; + } else if ( light.isPointLight ) { - let currentLineWidth = null; + const uniforms = cache.get( light ); - let currentPolygonOffsetFactor = null; - let currentPolygonOffsetUnits = null; + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); + uniforms.distance = light.distance; + uniforms.decay = light.decay; - const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); + if ( light.castShadow ) { - let lineWidthAvailable = false; - let version = 0; - const glVersion = gl.getParameter( gl.VERSION ); + const shadow = light.shadow; - if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { + const shadowUniforms = shadowCache.get( light ); - version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 1.0 ); + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; + shadowUniforms.shadowCameraNear = shadow.camera.near; + shadowUniforms.shadowCameraFar = shadow.camera.far; - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { + state.pointShadow[ pointLength ] = shadowUniforms; + state.pointShadowMap[ pointLength ] = shadowMap; + state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 2.0 ); - - } - - let currentTextureSlot = null; - let currentBoundTextures = {}; - - const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); - const viewportParam = gl.getParameter( gl.VIEWPORT ); - - const currentScissor = new Vector4().fromArray( scissorParam ); - const currentViewport = new Vector4().fromArray( viewportParam ); + numPointShadows ++; - function createTexture( type, target, count, dimensions ) { + } - const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - const texture = gl.createTexture(); + state.point[ pointLength ] = uniforms; - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + pointLength ++; - for ( let i = 0; i < count; i ++ ) { + } else if ( light.isHemisphereLight ) { - if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { + const uniforms = cache.get( light ); - gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); + uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); - } else { + state.hemi[ hemiLength ] = uniforms; - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + hemiLength ++; } } - return texture; - - } - - const emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - - if ( isWebGL2 ) { - - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); + if ( rectAreaLength > 0 ) { - } + if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - // init + state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; + state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); + } else { - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); + state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; + state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); + } - setBlending( NoBlending ); + } - // + state.ambient[ 0 ] = r; + state.ambient[ 1 ] = g; + state.ambient[ 2 ] = b; - function enable( id ) { + const hash = state.hash; - if ( enabledCapabilities[ id ] !== true ) { + if ( hash.directionalLength !== directionalLength || + hash.pointLength !== pointLength || + hash.spotLength !== spotLength || + hash.rectAreaLength !== rectAreaLength || + hash.hemiLength !== hemiLength || + hash.numDirectionalShadows !== numDirectionalShadows || + hash.numPointShadows !== numPointShadows || + hash.numSpotShadows !== numSpotShadows || + hash.numSpotMaps !== numSpotMaps || + hash.numLightProbes !== numLightProbes ) { - gl.enable( id ); - enabledCapabilities[ id ] = true; + state.directional.length = directionalLength; + state.spot.length = spotLength; + state.rectArea.length = rectAreaLength; + state.point.length = pointLength; + state.hemi.length = hemiLength; - } + state.directionalShadow.length = numDirectionalShadows; + state.directionalShadowMap.length = numDirectionalShadows; + state.pointShadow.length = numPointShadows; + state.pointShadowMap.length = numPointShadows; + state.spotShadow.length = numSpotShadows; + state.spotShadowMap.length = numSpotShadows; + state.directionalShadowMatrix.length = numDirectionalShadows; + state.pointShadowMatrix.length = numPointShadows; + state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; + state.spotLightMap.length = numSpotMaps; + state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; + state.numLightProbes = numLightProbes; - } + hash.directionalLength = directionalLength; + hash.pointLength = pointLength; + hash.spotLength = spotLength; + hash.rectAreaLength = rectAreaLength; + hash.hemiLength = hemiLength; - function disable( id ) { + hash.numDirectionalShadows = numDirectionalShadows; + hash.numPointShadows = numPointShadows; + hash.numSpotShadows = numSpotShadows; + hash.numSpotMaps = numSpotMaps; - if ( enabledCapabilities[ id ] !== false ) { + hash.numLightProbes = numLightProbes; - gl.disable( id ); - enabledCapabilities[ id ] = false; + state.version = nextVersion ++; } } - function bindFramebuffer( target, framebuffer ) { - - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - - gl.bindFramebuffer( target, framebuffer ); - - currentBoundFramebuffers[ target ] = framebuffer; + function setupView( lights, camera ) { - if ( isWebGL2 ) { + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER + const viewMatrix = camera.matrixWorldInverse; - if ( target === gl.DRAW_FRAMEBUFFER ) { + for ( let i = 0, l = lights.length; i < l; i ++ ) { - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; + const light = lights[ i ]; - } + if ( light.isDirectionalLight ) { - if ( target === gl.FRAMEBUFFER ) { + const uniforms = state.directional[ directionalLength ]; - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - } + directionalLength ++; - } + } else if ( light.isSpotLight ) { - return true; + const uniforms = state.spot[ spotLength ]; - } + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - return false; + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - } + spotLength ++; - function drawBuffers( renderTarget, framebuffer ) { + } else if ( light.isRectAreaLight ) { - let drawBuffers = defaultDrawbuffers; + const uniforms = state.rectArea[ rectAreaLength ]; - let needsUpdate = false; + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - if ( renderTarget ) { + // extract local rotation of light to derive width/height half vectors + matrix42.identity(); + matrix4.copy( light.matrixWorld ); + matrix4.premultiply( viewMatrix ); + matrix42.extractRotation( matrix4 ); - drawBuffers = currentDrawbuffers.get( framebuffer ); + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - if ( drawBuffers === undefined ) { + uniforms.halfWidth.applyMatrix4( matrix42 ); + uniforms.halfHeight.applyMatrix4( matrix42 ); - drawBuffers = []; - currentDrawbuffers.set( framebuffer, drawBuffers ); + rectAreaLength ++; - } + } else if ( light.isPointLight ) { - const textures = renderTarget.textures; + const uniforms = state.point[ pointLength ]; - if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - for ( let i = 0, il = textures.length; i < il; i ++ ) { + pointLength ++; - drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; + } else if ( light.isHemisphereLight ) { - } + const uniforms = state.hemi[ hemiLength ]; - drawBuffers.length = textures.length; + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + uniforms.direction.transformDirection( viewMatrix ); - needsUpdate = true; + hemiLength ++; } - } else { + } - if ( drawBuffers[ 0 ] !== gl.BACK ) { + } - drawBuffers[ 0 ] = gl.BACK; + return { + setup: setup, + setupView: setupView, + state: state + }; - needsUpdate = true; +} - } +function WebGLRenderState( extensions ) { - } + const lights = new WebGLLights( extensions ); - if ( needsUpdate ) { + const lightsArray = []; + const shadowsArray = []; - if ( capabilities.isWebGL2 ) { + function init( camera ) { - gl.drawBuffers( drawBuffers ); + state.camera = camera; - } else if ( extensions.has( 'WEBGL_draw_buffers' ) === true ) { + lightsArray.length = 0; + shadowsArray.length = 0; - extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); + } - } else { + function pushLight( light ) { - throw new Error( 'THREE.WebGLState: Usage of gl.drawBuffers() require WebGL2 or WEBGL_draw_buffers extension' ); + lightsArray.push( light ); - } + } - } + function pushShadow( shadowLight ) { + shadowsArray.push( shadowLight ); } - function useProgram( program ) { - - if ( currentProgram !== program ) { - - gl.useProgram( program ); + function setupLights() { - currentProgram = program; + lights.setup( lightsArray ); - return true; + } - } + function setupLightsView( camera ) { - return false; + lights.setupView( lightsArray, camera ); } - const equationToGL = { - [ AddEquation ]: gl.FUNC_ADD, - [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT - }; + const state = { + lightsArray: lightsArray, + shadowsArray: shadowsArray, - if ( isWebGL2 ) { + camera: null, - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; + lights: lights, - } else { + transmissionRenderTarget: {} + }; - const extension = extensions.get( 'EXT_blend_minmax' ); + return { + init: init, + state: state, + setupLights: setupLights, + setupLightsView: setupLightsView, - if ( extension !== null ) { + pushLight: pushLight, + pushShadow: pushShadow + }; - equationToGL[ MinEquation ] = extension.MIN_EXT; - equationToGL[ MaxEquation ] = extension.MAX_EXT; +} - } +function WebGLRenderStates( extensions ) { - } + let renderStates = new WeakMap(); - const factorToGL = { - [ ZeroFactor ]: gl.ZERO, - [ OneFactor ]: gl.ONE, - [ SrcColorFactor ]: gl.SRC_COLOR, - [ SrcAlphaFactor ]: gl.SRC_ALPHA, - [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, - [ DstColorFactor ]: gl.DST_COLOR, - [ DstAlphaFactor ]: gl.DST_ALPHA, - [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, - [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, - [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, - [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, - [ ConstantColorFactor ]: gl.CONSTANT_COLOR, - [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, - [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, - [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA - }; + function get( scene, renderCallDepth = 0 ) { - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { + const renderStateArray = renderStates.get( scene ); + let renderState; - if ( blending === NoBlending ) { + if ( renderStateArray === undefined ) { - if ( currentBlendingEnabled === true ) { + renderState = new WebGLRenderState( extensions ); + renderStates.set( scene, [ renderState ] ); - disable( gl.BLEND ); - currentBlendingEnabled = false; + } else { - } + if ( renderCallDepth >= renderStateArray.length ) { - return; + renderState = new WebGLRenderState( extensions ); + renderStateArray.push( renderState ); - } + } else { - if ( currentBlendingEnabled === false ) { + renderState = renderStateArray[ renderCallDepth ]; - enable( gl.BLEND ); - currentBlendingEnabled = true; + } } - if ( blending !== CustomBlending ) { + return renderState; - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { + } - if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { + function dispose() { - gl.blendEquation( gl.FUNC_ADD ); + renderStates = new WeakMap(); - currentBlendEquation = AddEquation; - currentBlendEquationAlpha = AddEquation; + } - } + return { + get: get, + dispose: dispose + }; - if ( premultipliedAlpha ) { +} - switch ( blending ) { +/** + * A material for drawing geometry by depth. Depth is based off of the camera + * near and far plane. White is nearest, black is farthest. + * + * @augments Material + */ +class MeshDepthMaterial extends Material { - case NormalBlending: - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; + /** + * Constructs a new mesh depth material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - case AdditiveBlending: - gl.blendFunc( gl.ONE, gl.ONE ); - break; + super(); - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDepthMaterial = true; - case MultiplyBlending: - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); - break; + this.type = 'MeshDepthMaterial'; - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; + /** + * Type for depth packing. + * + * @type {(BasicDepthPacking|RGBADepthPacking|RGBDepthPacking|RGDepthPacking)} + * @default BasicDepthPacking + */ + this.depthPacking = BasicDepthPacking; - } + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; - } else { + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - switch ( blending ) { + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - case NormalBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - case AdditiveBlending: - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - break; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - case MultiplyBlending: - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - break; + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; + this.setValues( parameters ); - } + } - } + copy( source ) { - currentBlendSrc = null; - currentBlendDst = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentBlendColor.set( 0, 0, 0 ); - currentBlendAlpha = 0; + super.copy( source ); - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; + this.depthPacking = source.depthPacking; - } + this.map = source.map; - return; + this.alphaMap = source.alphaMap; - } + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - // custom blending + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; + return this; - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { + } - gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); +} - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; +/** + * A material used internally for implementing shadow mapping with + * point lights. + * + * Can also be used to customize the shadow casting of an object by assigning + * an instance of `MeshDistanceMaterial` to {@link Object3D#customDistanceMaterial}. + * The following examples demonstrates this approach in order to ensure + * transparent parts of objects do not cast shadows. + * + * @augments Material + */ +class MeshDistanceMaterial extends Material { - } + /** + * Constructs a new mesh distance material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { + super(); - gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDistanceMaterial = true; - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; + this.type = 'MeshDistanceMaterial'; - } + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; - if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - currentBlendColor.copy( blendColor ); - currentBlendAlpha = blendAlpha; + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - } + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - currentBlending = blending; - currentPremultipledAlpha = false; + this.setValues( parameters ); } - function setMaterial( material, frontFaceCW ) { - - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); + copy( source ) { - let flipSided = ( material.side === BackSide ); - if ( frontFaceCW ) flipSided = ! flipSided; + super.copy( source ); - setFlipSided( flipSided ); + this.map = source.map; - ( material.blending === NormalBlending && material.transparent === false ) - ? setBlending( NoBlending ) - : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); + this.alphaMap = source.alphaMap; - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - const stencilWrite = material.stencilWrite; - stencilBuffer.setTest( stencilWrite ); - if ( stencilWrite ) { + return this; - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + } - } +} - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); +const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - material.alphaToCoverage === true - ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) - : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); +const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - } +function WebGLShadowMap( renderer, objects, capabilities ) { - // + let _frustum = new Frustum(); - function setFlipSided( flipSided ) { + const _shadowMapSize = new Vector2(), + _viewportSize = new Vector2(), - if ( currentFlipSided !== flipSided ) { + _viewport = new Vector4(), - if ( flipSided ) { + _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), + _distanceMaterial = new MeshDistanceMaterial(), - gl.frontFace( gl.CW ); + _materialCache = {}, - } else { + _maxTextureSize = capabilities.maxTextureSize; - gl.frontFace( gl.CCW ); + const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - } + const shadowMaterialVertical = new ShaderMaterial( { + defines: { + VSM_SAMPLES: 8 + }, + uniforms: { + shadow_pass: { value: null }, + resolution: { value: new Vector2() }, + radius: { value: 4.0 } + }, - currentFlipSided = flipSided; + vertexShader: vertex, + fragmentShader: fragment - } + } ); - } + const shadowMaterialHorizontal = shadowMaterialVertical.clone(); + shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - function setCullFace( cullFace ) { + const fullScreenTri = new BufferGeometry(); + fullScreenTri.setAttribute( + 'position', + new BufferAttribute( + new Float32Array( [ -1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5 ] ), + 3 + ) + ); - if ( cullFace !== CullFaceNone ) { + const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - enable( gl.CULL_FACE ); + const scope = this; - if ( cullFace !== currentCullFace ) { + this.enabled = false; - if ( cullFace === CullFaceBack ) { + this.autoUpdate = true; + this.needsUpdate = false; - gl.cullFace( gl.BACK ); + this.type = PCFShadowMap; + let _previousType = this.type; - } else if ( cullFace === CullFaceFront ) { + this.render = function ( lights, scene, camera ) { - gl.cullFace( gl.FRONT ); + if ( scope.enabled === false ) return; + if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - } else { + if ( lights.length === 0 ) return; - gl.cullFace( gl.FRONT_AND_BACK ); + const currentRenderTarget = renderer.getRenderTarget(); + const activeCubeFace = renderer.getActiveCubeFace(); + const activeMipmapLevel = renderer.getActiveMipmapLevel(); - } + const _state = renderer.state; - } + // Set GL state for depth map. + _state.setBlending( NoBlending ); + + if ( _state.buffers.depth.getReversed() === true ) { + + _state.buffers.color.setClear( 0, 0, 0, 0 ); } else { - disable( gl.CULL_FACE ); + _state.buffers.color.setClear( 1, 1, 1, 1 ); } - currentCullFace = cullFace; + _state.buffers.depth.setTest( true ); + _state.setScissorTest( false ); - } + // check for shadow map type changes - function setLineWidth( width ) { + const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); + const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); - if ( width !== currentLineWidth ) { + // render depth map - if ( lineWidthAvailable ) gl.lineWidth( width ); + for ( let i = 0, il = lights.length; i < il; i ++ ) { - currentLineWidth = width; + const light = lights[ i ]; + const shadow = light.shadow; - } + if ( shadow === undefined ) { - } + console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); + continue; - function setPolygonOffset( polygonOffset, factor, units ) { + } - if ( polygonOffset ) { + if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - enable( gl.POLYGON_OFFSET_FILL ); + _shadowMapSize.copy( shadow.mapSize ); - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { + const shadowFrameExtents = shadow.getFrameExtents(); - gl.polygonOffset( factor, units ); + _shadowMapSize.multiply( shadowFrameExtents ); - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; + _viewportSize.copy( shadow.mapSize ); - } + if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - } else { + if ( _shadowMapSize.x > _maxTextureSize ) { - disable( gl.POLYGON_OFFSET_FILL ); + _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); + _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; + shadow.mapSize.x = _viewportSize.x; - } + } - } + if ( _shadowMapSize.y > _maxTextureSize ) { - function setScissorTest( scissorTest ) { + _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); + _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; + shadow.mapSize.y = _viewportSize.y; - if ( scissorTest ) { + } - enable( gl.SCISSOR_TEST ); + } - } else { + if ( shadow.map === null || toVSM === true || fromVSM === true ) { - disable( gl.SCISSOR_TEST ); + const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - } + if ( shadow.map !== null ) { - } + shadow.map.dispose(); - // texture + } - function activeTexture( webglSlot ) { + shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); + shadow.map.texture.name = light.name + '.shadowMap'; - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; + shadow.camera.updateProjectionMatrix(); - if ( currentTextureSlot !== webglSlot ) { + } - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; + renderer.setRenderTarget( shadow.map ); + renderer.clear(); - } + const viewportCount = shadow.getViewportCount(); - } + for ( let vp = 0; vp < viewportCount; vp ++ ) { - function bindTexture( webglType, webglTexture, webglSlot ) { + const viewport = shadow.getViewport( vp ); - if ( webglSlot === undefined ) { + _viewport.set( + _viewportSize.x * viewport.x, + _viewportSize.y * viewport.y, + _viewportSize.x * viewport.z, + _viewportSize.y * viewport.w + ); - if ( currentTextureSlot === null ) { + _state.viewport( _viewport ); - webglSlot = gl.TEXTURE0 + maxTextures - 1; + shadow.updateMatrices( light, vp ); - } else { + _frustum = shadow.getFrustum(); - webglSlot = currentTextureSlot; + renderObject( scene, camera, shadow.camera, light, this.type ); } - } + // do blur pass for VSM - let boundTexture = currentBoundTextures[ webglSlot ]; + if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - if ( boundTexture === undefined ) { + VSMPass( shadow, camera ); - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ webglSlot ] = boundTexture; + } + + shadow.needsUpdate = false; } - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { + _previousType = this.type; - if ( currentTextureSlot !== webglSlot ) { + scope.needsUpdate = false; - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; + renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); - } + }; - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); + function VSMPass( shadow, camera ) { - boundTexture.type = webglType; - boundTexture.texture = webglTexture; + const geometry = objects.update( fullScreenMesh ); + + if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { + + shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; + shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; + + shadowMaterialVertical.needsUpdate = true; + shadowMaterialHorizontal.needsUpdate = true; } - } + if ( shadow.mapPass === null ) { - function unbindTexture() { + shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - const boundTexture = currentBoundTextures[ currentTextureSlot ]; + } - if ( boundTexture !== undefined && boundTexture.type !== undefined ) { + // vertical pass - gl.bindTexture( boundTexture.type, null ); + shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; + shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; + shadowMaterialVertical.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.mapPass ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - boundTexture.type = undefined; - boundTexture.texture = undefined; + // horizontal pass - } + shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; + shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; + shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.map ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); } - function compressedTexImage2D() { + function getDepthMaterial( object, material, light, type ) { - try { + let result = null; - gl.compressedTexImage2D.apply( gl, arguments ); + const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - } catch ( error ) { + if ( customMaterial !== undefined ) { - console.error( 'THREE.WebGLState:', error ); + result = customMaterial; - } + } else { - } + result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - function compressedTexImage3D() { + if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || + ( material.displacementMap && material.displacementScale !== 0 ) || + ( material.alphaMap && material.alphaTest > 0 ) || + ( material.map && material.alphaTest > 0 ) || + ( material.alphaToCoverage === true ) ) { - try { + // in this case we need a unique material instance reflecting the + // appropriate state - gl.compressedTexImage3D.apply( gl, arguments ); + const keyA = result.uuid, keyB = material.uuid; - } catch ( error ) { + let materialsForVariant = _materialCache[ keyA ]; - console.error( 'THREE.WebGLState:', error ); + if ( materialsForVariant === undefined ) { - } + materialsForVariant = {}; + _materialCache[ keyA ] = materialsForVariant; - } + } - function texSubImage2D() { + let cachedMaterial = materialsForVariant[ keyB ]; - try { + if ( cachedMaterial === undefined ) { - gl.texSubImage2D.apply( gl, arguments ); + cachedMaterial = result.clone(); + materialsForVariant[ keyB ] = cachedMaterial; + material.addEventListener( 'dispose', onMaterialDispose ); - } catch ( error ) { + } - console.error( 'THREE.WebGLState:', error ); + result = cachedMaterial; - } + } - } + } - function texSubImage3D() { + result.visible = material.visible; + result.wireframe = material.wireframe; - try { + if ( type === VSMShadowMap ) { - gl.texSubImage3D.apply( gl, arguments ); + result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - } catch ( error ) { + } else { - console.error( 'THREE.WebGLState:', error ); + result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; } - } + result.alphaMap = material.alphaMap; + result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value + result.map = material.map; - function compressedTexSubImage2D() { + result.clipShadows = material.clipShadows; + result.clippingPlanes = material.clippingPlanes; + result.clipIntersection = material.clipIntersection; - try { + result.displacementMap = material.displacementMap; + result.displacementScale = material.displacementScale; + result.displacementBias = material.displacementBias; - gl.compressedTexSubImage2D.apply( gl, arguments ); + result.wireframeLinewidth = material.wireframeLinewidth; + result.linewidth = material.linewidth; - } catch ( error ) { + if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - console.error( 'THREE.WebGLState:', error ); + const materialProperties = renderer.properties.get( result ); + materialProperties.light = light; } + return result; + } - function compressedTexSubImage3D() { + function renderObject( object, camera, shadowCamera, light, type ) { - try { + if ( object.visible === false ) return; - gl.compressedTexSubImage3D.apply( gl, arguments ); + const visible = object.layers.test( camera.layers ); - } catch ( error ) { + if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - console.error( 'THREE.WebGLState:', error ); + if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - } + object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - } + const geometry = objects.update( object ); + const material = object.material; - function texStorage2D() { + if ( Array.isArray( material ) ) { - try { + const groups = geometry.groups; - gl.texStorage2D.apply( gl, arguments ); + for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - } catch ( error ) { + const group = groups[ k ]; + const groupMaterial = material[ group.materialIndex ]; - console.error( 'THREE.WebGLState:', error ); + if ( groupMaterial && groupMaterial.visible ) { - } + const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - } + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - function texStorage3D() { + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - try { + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - gl.texStorage3D.apply( gl, arguments ); + } - } catch ( error ) { + } - console.error( 'THREE.WebGLState:', error ); + } else if ( material.visible ) { - } + const depthMaterial = getDepthMaterial( object, material, light, type ); - } + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - function texImage2D() { + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - try { + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); + + } - gl.texImage2D.apply( gl, arguments ); + } - } catch ( error ) { + } - console.error( 'THREE.WebGLState:', error ); + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + renderObject( children[ i ], camera, shadowCamera, light, type ); } } - function texImage3D() { - - try { + function onMaterialDispose( event ) { - gl.texImage3D.apply( gl, arguments ); + const material = event.target; - } catch ( error ) { + material.removeEventListener( 'dispose', onMaterialDispose ); - console.error( 'THREE.WebGLState:', error ); + // make sure to remove the unique distance/depth materials used for shadow map rendering - } + for ( const id in _materialCache ) { - } + const cache = _materialCache[ id ]; - // + const uuid = event.target.uuid; - function scissor( scissor ) { + if ( uuid in cache ) { - if ( currentScissor.equals( scissor ) === false ) { + const shadowMaterial = cache[ uuid ]; + shadowMaterial.dispose(); + delete cache[ uuid ]; - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); + } } } - function viewport( viewport ) { +} - if ( currentViewport.equals( viewport ) === false ) { +const reversedFuncs = { + [ NeverDepth ]: AlwaysDepth, + [ LessDepth ]: GreaterDepth, + [ EqualDepth ]: NotEqualDepth, + [ LessEqualDepth ]: GreaterEqualDepth, - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); + [ AlwaysDepth ]: NeverDepth, + [ GreaterDepth ]: LessDepth, + [ NotEqualDepth ]: EqualDepth, + [ GreaterEqualDepth ]: LessEqualDepth, +}; - } +function WebGLState( gl, extensions ) { - } + function ColorBuffer() { - function updateUBOMapping( uniformsGroup, program ) { + let locked = false; - let mapping = uboProgramMap.get( program ); + const color = new Vector4(); + let currentColorMask = null; + const currentColorClear = new Vector4( 0, 0, 0, 0 ); - if ( mapping === undefined ) { + return { - mapping = new WeakMap(); + setMask: function ( colorMask ) { - uboProgramMap.set( program, mapping ); + if ( currentColorMask !== colorMask && ! locked ) { - } + gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + currentColorMask = colorMask; - let blockIndex = mapping.get( uniformsGroup ); + } - if ( blockIndex === undefined ) { + }, - blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); + setLocked: function ( lock ) { - mapping.set( uniformsGroup, blockIndex ); + locked = lock; - } + }, - } + setClear: function ( r, g, b, a, premultipliedAlpha ) { - function uniformBlockBinding( uniformsGroup, program ) { + if ( premultipliedAlpha === true ) { - const mapping = uboProgramMap.get( program ); - const blockIndex = mapping.get( uniformsGroup ); + r *= a; g *= a; b *= a; - if ( uboBindings.get( program ) !== blockIndex ) { + } - // bind shader specific block index to global block point - gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); + color.set( r, g, b, a ); - uboBindings.set( program, blockIndex ); + if ( currentColorClear.equals( color ) === false ) { - } + gl.clearColor( r, g, b, a ); + currentColorClear.copy( color ); - } + } - // + }, - function reset() { + reset: function () { - // reset state + locked = false; - gl.disable( gl.BLEND ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.POLYGON_OFFSET_FILL ); - gl.disable( gl.SCISSOR_TEST ); - gl.disable( gl.STENCIL_TEST ); - gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + currentColorMask = null; + currentColorClear.set( -1, 0, 0, 0 ); // set to invalid state - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ONE, gl.ZERO ); - gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); - gl.blendColor( 0, 0, 0, 0 ); + } - gl.colorMask( true, true, true, true ); - gl.clearColor( 0, 0, 0, 0 ); + }; - gl.depthMask( true ); - gl.depthFunc( gl.LESS ); - gl.clearDepth( 1 ); + } - gl.stencilMask( 0xffffffff ); - gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); - gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); - gl.clearStencil( 0 ); + function DepthBuffer() { - gl.cullFace( gl.BACK ); - gl.frontFace( gl.CCW ); + let locked = false; - gl.polygonOffset( 0, 0 ); + let currentReversed = false; + let currentDepthMask = null; + let currentDepthFunc = null; + let currentDepthClear = null; - gl.activeTexture( gl.TEXTURE0 ); + return { - gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + setReversed: function ( reversed ) { - if ( isWebGL2 === true ) { + if ( currentReversed !== reversed ) { - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); + const ext = extensions.get( 'EXT_clip_control' ); - } + if ( reversed ) { - gl.useProgram( null ); + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT ); - gl.lineWidth( 1 ); + } else { - gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); - gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT ); - // reset internals + } - enabledCapabilities = {}; + currentReversed = reversed; - currentTextureSlot = null; - currentBoundTextures = {}; + const oldDepth = currentDepthClear; + currentDepthClear = null; + this.setClear( oldDepth ); - currentBoundFramebuffers = {}; - currentDrawbuffers = new WeakMap(); - defaultDrawbuffers = []; + } - currentProgram = null; + }, - currentBlendingEnabled = false; - currentBlending = null; - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentBlendColor = new Color( 0, 0, 0 ); - currentBlendAlpha = 0; - currentPremultipledAlpha = false; + getReversed: function () { - currentFlipSided = null; - currentCullFace = null; + return currentReversed; - currentLineWidth = null; + }, - currentPolygonOffsetFactor = null; - currentPolygonOffsetUnits = null; + setTest: function ( depthTest ) { - currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); - currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); + if ( depthTest ) { - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); + enable( gl.DEPTH_TEST ); - } + } else { - return { + disable( gl.DEPTH_TEST ); - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, + } - enable: enable, - disable: disable, + }, - bindFramebuffer: bindFramebuffer, - drawBuffers: drawBuffers, + setMask: function ( depthMask ) { - useProgram: useProgram, + if ( currentDepthMask !== depthMask && ! locked ) { - setBlending: setBlending, - setMaterial: setMaterial, + gl.depthMask( depthMask ); + currentDepthMask = depthMask; - setFlipSided: setFlipSided, - setCullFace: setCullFace, + } - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, + }, - setScissorTest: setScissorTest, + setFunc: function ( depthFunc ) { - activeTexture: activeTexture, - bindTexture: bindTexture, - unbindTexture: unbindTexture, - compressedTexImage2D: compressedTexImage2D, - compressedTexImage3D: compressedTexImage3D, - texImage2D: texImage2D, - texImage3D: texImage3D, + if ( currentReversed ) depthFunc = reversedFuncs[ depthFunc ]; - updateUBOMapping: updateUBOMapping, - uniformBlockBinding: uniformBlockBinding, + if ( currentDepthFunc !== depthFunc ) { - texStorage2D: texStorage2D, - texStorage3D: texStorage3D, - texSubImage2D: texSubImage2D, - texSubImage3D: texSubImage3D, - compressedTexSubImage2D: compressedTexSubImage2D, - compressedTexSubImage3D: compressedTexSubImage3D, + switch ( depthFunc ) { - scissor: scissor, - viewport: viewport, + case NeverDepth: - reset: reset + gl.depthFunc( gl.NEVER ); + break; - }; + case AlwaysDepth: -} + gl.depthFunc( gl.ALWAYS ); + break; -function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { + case LessDepth: - const isWebGL2 = capabilities.isWebGL2; - const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; - const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); + gl.depthFunc( gl.LESS ); + break; - const _imageDimensions = new Vector2(); - const _videoTextures = new WeakMap(); - let _canvas; + case LessEqualDepth: - const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source + gl.depthFunc( gl.LEQUAL ); + break; - // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, - // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! - // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). + case EqualDepth: - let useOffscreenCanvas = false; + gl.depthFunc( gl.EQUAL ); + break; - try { + case GreaterEqualDepth: - useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' - // eslint-disable-next-line compat/compat - && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; + gl.depthFunc( gl.GEQUAL ); + break; - } catch ( err ) { + case GreaterDepth: - // Ignore any errors + gl.depthFunc( gl.GREATER ); + break; - } + case NotEqualDepth: - function createCanvas( width, height ) { + gl.depthFunc( gl.NOTEQUAL ); + break; - // Use OffscreenCanvas when available. Specially needed in web workers + default: - return useOffscreenCanvas ? - // eslint-disable-next-line compat/compat - new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); + gl.depthFunc( gl.LEQUAL ); - } + } - function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { + currentDepthFunc = depthFunc; - let scale = 1; + } - const dimensions = getDimensions( image ); + }, - // handle case if texture exceeds max size + setLocked: function ( lock ) { - if ( dimensions.width > maxSize || dimensions.height > maxSize ) { + locked = lock; - scale = maxSize / Math.max( dimensions.width, dimensions.height ); + }, - } + setClear: function ( depth ) { - // only perform resize if necessary + if ( currentDepthClear !== depth ) { - if ( scale < 1 || needsPowerOfTwo === true ) { + if ( currentReversed ) { - // only perform resize for certain image types + depth = 1 - depth; - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || - ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { + } - const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; + gl.clearDepth( depth ); + currentDepthClear = depth; - const width = floor( scale * dimensions.width ); - const height = floor( scale * dimensions.height ); + } - if ( _canvas === undefined ) _canvas = createCanvas( width, height ); + }, - // cube textures can't reuse the same canvas + reset: function () { - const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; + locked = false; - canvas.width = width; - canvas.height = height; + currentDepthMask = null; + currentDepthFunc = null; + currentDepthClear = null; + currentReversed = false; - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, width, height ); + } - console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + dimensions.width + 'x' + dimensions.height + ') to (' + width + 'x' + height + ').' ); + }; - return canvas; + } - } else { + function StencilBuffer() { - if ( 'data' in image ) { + let locked = false; - console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + dimensions.width + 'x' + dimensions.height + ').' ); + let currentStencilMask = null; + let currentStencilFunc = null; + let currentStencilRef = null; + let currentStencilFuncMask = null; + let currentStencilFail = null; + let currentStencilZFail = null; + let currentStencilZPass = null; + let currentStencilClear = null; - } + return { - return image; + setTest: function ( stencilTest ) { - } + if ( ! locked ) { - } + if ( stencilTest ) { - return image; + enable( gl.STENCIL_TEST ); - } + } else { - function isPowerOfTwo$1( image ) { + disable( gl.STENCIL_TEST ); - const dimensions = getDimensions( image ); + } - return isPowerOfTwo( dimensions.width ) && isPowerOfTwo( dimensions.height ); + } - } + }, - function textureNeedsPowerOfTwo( texture ) { + setMask: function ( stencilMask ) { - if ( isWebGL2 ) return false; + if ( currentStencilMask !== stencilMask && ! locked ) { - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); + gl.stencilMask( stencilMask ); + currentStencilMask = stencilMask; - } + } - function textureNeedsGenerateMipmaps( texture, supportsMips ) { + }, - return texture.generateMipmaps && supportsMips && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; + setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - } + if ( currentStencilFunc !== stencilFunc || + currentStencilRef !== stencilRef || + currentStencilFuncMask !== stencilMask ) { - function generateMipmap( target ) { + gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - _gl.generateMipmap( target ); + currentStencilFunc = stencilFunc; + currentStencilRef = stencilRef; + currentStencilFuncMask = stencilMask; - } + } - function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + }, - if ( isWebGL2 === false ) return glFormat; + setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - if ( internalFormatName !== null ) { + if ( currentStencilFail !== stencilFail || + currentStencilZFail !== stencilZFail || + currentStencilZPass !== stencilZPass ) { - if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; + gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); + currentStencilFail = stencilFail; + currentStencilZFail = stencilZFail; + currentStencilZPass = stencilZPass; - } + } - let internalFormat = glFormat; + }, - if ( glFormat === _gl.RED ) { + setLocked: function ( lock ) { - if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; + locked = lock; - } + }, - if ( glFormat === _gl.RED_INTEGER ) { + setClear: function ( stencil ) { - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; - if ( glType === _gl.INT ) internalFormat = _gl.R32I; + if ( currentStencilClear !== stencil ) { - } + gl.clearStencil( stencil ); + currentStencilClear = stencil; - if ( glFormat === _gl.RG ) { + } - if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; + }, - } + reset: function () { - if ( glFormat === _gl.RG_INTEGER ) { + locked = false; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RG16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RG32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.RG8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.RG16I; - if ( glType === _gl.INT ) internalFormat = _gl.RG32I; + currentStencilMask = null; + currentStencilFunc = null; + currentStencilRef = null; + currentStencilFuncMask = null; + currentStencilFail = null; + currentStencilZFail = null; + currentStencilZPass = null; + currentStencilClear = null; - } + } - if ( glFormat === _gl.RGBA ) { + }; - const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + } - if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; - if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; - if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; + // - } + const colorBuffer = new ColorBuffer(); + const depthBuffer = new DepthBuffer(); + const stencilBuffer = new StencilBuffer(); - if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || - internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || - internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { + const uboBindings = new WeakMap(); + const uboProgramMap = new WeakMap(); - extensions.get( 'EXT_color_buffer_float' ); + let enabledCapabilities = {}; - } + let currentBoundFramebuffers = {}; + let currentDrawbuffers = new WeakMap(); + let defaultDrawbuffers = []; - return internalFormat; + let currentProgram = null; - } + let currentBlendingEnabled = false; + let currentBlending = null; + let currentBlendEquation = null; + let currentBlendSrc = null; + let currentBlendDst = null; + let currentBlendEquationAlpha = null; + let currentBlendSrcAlpha = null; + let currentBlendDstAlpha = null; + let currentBlendColor = new Color( 0, 0, 0 ); + let currentBlendAlpha = 0; + let currentPremultipledAlpha = false; - function getMipLevels( texture, image, supportsMips ) { + let currentFlipSided = null; + let currentCullFace = null; - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { + let currentLineWidth = null; - return Math.log2( Math.max( image.width, image.height ) ) + 1; + let currentPolygonOffsetFactor = null; + let currentPolygonOffsetUnits = null; - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { + const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - // user-defined mipmaps + let lineWidthAvailable = false; + let version = 0; + const glVersion = gl.getParameter( gl.VERSION ); - return texture.mipmaps.length; + if ( glVersion.indexOf( 'WebGL' ) !== -1 ) { - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { + version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 1.0 ); - return image.mipmaps.length; + } else if ( glVersion.indexOf( 'OpenGL ES' ) !== -1 ) { - } else { + version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 2.0 ); - // texture without mipmaps (only base level) + } - return 1; + let currentTextureSlot = null; + let currentBoundTextures = {}; - } + const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); + const viewportParam = gl.getParameter( gl.VIEWPORT ); - } + const currentScissor = new Vector4().fromArray( scissorParam ); + const currentViewport = new Vector4().fromArray( viewportParam ); - // Fallback filters for non-power-of-2 textures + function createTexture( type, target, count, dimensions ) { - function filterFallback( f ) { + const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. + const texture = gl.createTexture(); - if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { + gl.bindTexture( type, texture ); + gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - return _gl.NEAREST; + for ( let i = 0; i < count; i ++ ) { - } + if ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) { - return _gl.LINEAR; + gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - } + } else { - // + gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - function onTextureDispose( event ) { + } - const texture = event.target; + } - texture.removeEventListener( 'dispose', onTextureDispose ); + return texture; - deallocateTexture( texture ); + } - if ( texture.isVideoTexture ) { + const emptyTextures = {}; + emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); + emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); + emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); + emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - _videoTextures.delete( texture ); + // init - } + colorBuffer.setClear( 0, 0, 0, 1 ); + depthBuffer.setClear( 1 ); + stencilBuffer.setClear( 0 ); - } + enable( gl.DEPTH_TEST ); + depthBuffer.setFunc( LessEqualDepth ); - function onRenderTargetDispose( event ) { + setFlipSided( false ); + setCullFace( CullFaceBack ); + enable( gl.CULL_FACE ); - const renderTarget = event.target; + setBlending( NoBlending ); - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); + // - deallocateRenderTarget( renderTarget ); + function enable( id ) { - } + if ( enabledCapabilities[ id ] !== true ) { - // + gl.enable( id ); + enabledCapabilities[ id ] = true; - function deallocateTexture( texture ) { + } - const textureProperties = properties.get( texture ); + } - if ( textureProperties.__webglInit === undefined ) return; + function disable( id ) { - // check if it's necessary to remove the WebGLTexture object + if ( enabledCapabilities[ id ] !== false ) { - const source = texture.source; - const webglTextures = _sources.get( source ); + gl.disable( id ); + enabledCapabilities[ id ] = false; - if ( webglTextures ) { + } - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - webglTexture.usedTimes --; + } - // the WebGLTexture object is not used anymore, remove it + function bindFramebuffer( target, framebuffer ) { - if ( webglTexture.usedTimes === 0 ) { + if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - deleteTexture( texture ); + gl.bindFramebuffer( target, framebuffer ); - } + currentBoundFramebuffers[ target ] = framebuffer; - // remove the weak map entry if no WebGLTexture uses the source anymore + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - if ( Object.keys( webglTextures ).length === 0 ) { + if ( target === gl.DRAW_FRAMEBUFFER ) { - _sources.delete( source ); + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; } - } - - properties.remove( texture ); + if ( target === gl.FRAMEBUFFER ) { - } + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; - function deleteTexture( texture ) { + } - const textureProperties = properties.get( texture ); - _gl.deleteTexture( textureProperties.__webglTexture ); + return true; - const source = texture.source; - const webglTextures = _sources.get( source ); - delete webglTextures[ textureProperties.__cacheKey ]; + } - info.memory.textures --; + return false; } - function deallocateRenderTarget( renderTarget ) { + function drawBuffers( renderTarget, framebuffer ) { - const renderTargetProperties = properties.get( renderTarget ); + let drawBuffers = defaultDrawbuffers; - if ( renderTarget.depthTexture ) { + let needsUpdate = false; - renderTarget.depthTexture.dispose(); + if ( renderTarget ) { - } + drawBuffers = currentDrawbuffers.get( framebuffer ); - if ( renderTarget.isWebGLCubeRenderTarget ) { + if ( drawBuffers === undefined ) { - for ( let i = 0; i < 6; i ++ ) { + drawBuffers = []; + currentDrawbuffers.set( framebuffer, drawBuffers ); - if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { + } - for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); + const textures = renderTarget.textures; - } else { + if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); + for ( let i = 0, il = textures.length; i < il; i ++ ) { + + drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; } - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); + drawBuffers.length = textures.length; + + needsUpdate = true; } } else { - if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { - - for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); + if ( drawBuffers[ 0 ] !== gl.BACK ) { - } else { + drawBuffers[ 0 ] = gl.BACK; - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); + needsUpdate = true; } - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - - if ( renderTargetProperties.__webglColorRenderbuffer ) { - - for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - - if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } + } - } + if ( needsUpdate ) { - if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); + gl.drawBuffers( drawBuffers ); } - const textures = renderTarget.textures; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); + } - if ( attachmentProperties.__webglTexture ) { + function useProgram( program ) { - _gl.deleteTexture( attachmentProperties.__webglTexture ); + if ( currentProgram !== program ) { - info.memory.textures --; + gl.useProgram( program ); - } + currentProgram = program; - properties.remove( textures[ i ] ); + return true; } - properties.remove( renderTarget ); + return false; } - // + const equationToGL = { + [ AddEquation ]: gl.FUNC_ADD, + [ SubtractEquation ]: gl.FUNC_SUBTRACT, + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + }; - let textureUnits = 0; + equationToGL[ MinEquation ] = gl.MIN; + equationToGL[ MaxEquation ] = gl.MAX; - function resetTextureUnits() { + const factorToGL = { + [ ZeroFactor ]: gl.ZERO, + [ OneFactor ]: gl.ONE, + [ SrcColorFactor ]: gl.SRC_COLOR, + [ SrcAlphaFactor ]: gl.SRC_ALPHA, + [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, + [ DstColorFactor ]: gl.DST_COLOR, + [ DstAlphaFactor ]: gl.DST_ALPHA, + [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, + [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, + [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, + [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, + [ ConstantColorFactor ]: gl.CONSTANT_COLOR, + [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, + [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, + [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA + }; - textureUnits = 0; + function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { - } + if ( blending === NoBlending ) { - function allocateTextureUnit() { + if ( currentBlendingEnabled === true ) { - const textureUnit = textureUnits; + disable( gl.BLEND ); + currentBlendingEnabled = false; - if ( textureUnit >= capabilities.maxTextures ) { + } - console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); + return; } - textureUnits += 1; + if ( currentBlendingEnabled === false ) { - return textureUnit; + enable( gl.BLEND ); + currentBlendingEnabled = true; - } + } - function getTextureCacheKey( texture ) { + if ( blending !== CustomBlending ) { - const array = []; + if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR || 0 ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - array.push( texture.internalFormat ); - array.push( texture.format ); - array.push( texture.type ); - array.push( texture.generateMipmaps ); - array.push( texture.premultiplyAlpha ); - array.push( texture.flipY ); - array.push( texture.unpackAlignment ); - array.push( texture.colorSpace ); + if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - return array.join(); + gl.blendEquation( gl.FUNC_ADD ); - } + currentBlendEquation = AddEquation; + currentBlendEquationAlpha = AddEquation; - // + } - function setTexture2D( texture, slot ) { + if ( premultipliedAlpha ) { - const textureProperties = properties.get( texture ); + switch ( blending ) { - if ( texture.isVideoTexture ) updateVideoTexture( texture ); + case NormalBlending: + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { + case AdditiveBlending: + gl.blendFunc( gl.ONE, gl.ONE ); + break; - const image = texture.image; + case SubtractiveBlending: + gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + break; - if ( image === null ) { + case MultiplyBlending: + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); + break; - console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - } else if ( image.complete === false ) { + } - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); + } else { - } else { + switch ( blending ) { - uploadTexture( textureProperties, texture, slot ); - return; + case NormalBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - } + case AdditiveBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); + break; - } + case SubtractiveBlending: + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + case MultiplyBlending: + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; - } + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - function setTexture2DArray( texture, slot ) { + } - const textureProperties = properties.get( texture ); + } - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + currentBlendSrc = null; + currentBlendDst = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor.set( 0, 0, 0 ); + currentBlendAlpha = 0; + + currentBlending = blending; + currentPremultipledAlpha = premultipliedAlpha; + + } - uploadTexture( textureProperties, texture, slot ); return; } - state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } + // custom blending - function setTexture3D( texture, slot ) { + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; - const textureProperties = properties.get( texture ); + if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - uploadTexture( textureProperties, texture, slot ); - return; + currentBlendEquation = blendEquation; + currentBlendEquationAlpha = blendEquationAlpha; } - state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - } + gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - function setTextureCube( texture, slot ) { + currentBlendSrc = blendSrc; + currentBlendDst = blendDst; + currentBlendSrcAlpha = blendSrcAlpha; + currentBlendDstAlpha = blendDstAlpha; - const textureProperties = properties.get( texture ); + } - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { - uploadCubeTexture( textureProperties, texture, slot ); - return; + gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); + + currentBlendColor.copy( blendColor ); + currentBlendAlpha = blendAlpha; } - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + currentBlending = blending; + currentPremultipledAlpha = false; } - const wrappingToGL = { - [ RepeatWrapping ]: _gl.REPEAT, - [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, - [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT - }; + function setMaterial( material, frontFaceCW ) { - const filterToGL = { - [ NearestFilter ]: _gl.NEAREST, - [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, - [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, + material.side === DoubleSide + ? disable( gl.CULL_FACE ) + : enable( gl.CULL_FACE ); - [ LinearFilter ]: _gl.LINEAR, - [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, - [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR - }; + let flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; - const compareToGL = { - [ NeverCompare ]: _gl.NEVER, - [ AlwaysCompare ]: _gl.ALWAYS, - [ LessCompare ]: _gl.LESS, - [ LessEqualCompare ]: _gl.LEQUAL, - [ EqualCompare ]: _gl.EQUAL, - [ GreaterEqualCompare ]: _gl.GEQUAL, - [ GreaterCompare ]: _gl.GREATER, - [ NotEqualCompare ]: _gl.NOTEQUAL - }; + setFlipSided( flipSided ); - function setTextureParameters( textureType, texture, supportsMips ) { + ( material.blending === NormalBlending && material.transparent === false ) + ? setBlending( NoBlending ) + : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && - ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || - texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ) ) { + depthBuffer.setFunc( material.depthFunc ); + depthBuffer.setTest( material.depthTest ); + depthBuffer.setMask( material.depthWrite ); + colorBuffer.setMask( material.colorWrite ); - console.warn( 'THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device.' ); + const stencilWrite = material.stencilWrite; + stencilBuffer.setTest( stencilWrite ); + if ( stencilWrite ) { + + stencilBuffer.setMask( material.stencilWriteMask ); + stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); } - if ( supportsMips ) { + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); + material.alphaToCoverage === true + ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) + : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { + } - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); + // - } + function setFlipSided( flipSided ) { - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); + if ( currentFlipSided !== flipSided ) { - } else { + if ( flipSided ) { - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + gl.frontFace( gl.CW ); - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { + } else { - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); + gl.frontFace( gl.CCW ); } - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); + currentFlipSided = flipSided; - } + } - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); + } - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { + function setCullFace( cullFace ) { - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); + if ( cullFace !== CullFaceNone ) { - } + enable( gl.CULL_FACE ); - } + if ( cullFace !== currentCullFace ) { - if ( texture.compareFunction ) { + if ( cullFace === CullFaceBack ) { - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); + gl.cullFace( gl.BACK ); - } + } else if ( cullFace === CullFaceFront ) { - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + gl.cullFace( gl.FRONT ); - if ( texture.magFilter === NearestFilter ) return; - if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 - if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only + } else { - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { + gl.cullFace( gl.FRONT_AND_BACK ); - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; + } } - } - - } + } else { - function initTexture( textureProperties, texture ) { + disable( gl.CULL_FACE ); - let forceUpload = false; + } - if ( textureProperties.__webglInit === undefined ) { + currentCullFace = cullFace; - textureProperties.__webglInit = true; + } - texture.addEventListener( 'dispose', onTextureDispose ); + function setLineWidth( width ) { - } + if ( width !== currentLineWidth ) { - // create Source <-> WebGLTextures mapping if necessary + if ( lineWidthAvailable ) gl.lineWidth( width ); - const source = texture.source; - let webglTextures = _sources.get( source ); + currentLineWidth = width; - if ( webglTextures === undefined ) { + } - webglTextures = {}; - _sources.set( source, webglTextures ); + } - } + function setPolygonOffset( polygonOffset, factor, units ) { - // check if there is already a WebGLTexture object for the given texture parameters + if ( polygonOffset ) { - const textureCacheKey = getTextureCacheKey( texture ); + enable( gl.POLYGON_OFFSET_FILL ); - if ( textureCacheKey !== textureProperties.__cacheKey ) { + if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - // if not, create a new instance of WebGLTexture + gl.polygonOffset( factor, units ); - if ( webglTextures[ textureCacheKey ] === undefined ) { + currentPolygonOffsetFactor = factor; + currentPolygonOffsetUnits = units; - // create new entry + } - webglTextures[ textureCacheKey ] = { - texture: _gl.createTexture(), - usedTimes: 0 - }; + } else { - info.memory.textures ++; + disable( gl.POLYGON_OFFSET_FILL ); - // when a new instance of WebGLTexture was created, a texture upload is required - // even if the image contents are identical + } - forceUpload = true; + } - } + function setScissorTest( scissorTest ) { - webglTextures[ textureCacheKey ].usedTimes ++; + if ( scissorTest ) { - // every time the texture cache key changes, it's necessary to check if an instance of - // WebGLTexture can be deleted in order to avoid a memory leak. + enable( gl.SCISSOR_TEST ); - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; + } else { - if ( webglTexture !== undefined ) { + disable( gl.SCISSOR_TEST ); - webglTextures[ textureProperties.__cacheKey ].usedTimes --; + } - if ( webglTexture.usedTimes === 0 ) { + } - deleteTexture( texture ); + // texture - } + function activeTexture( webglSlot ) { - } + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - // store references to cache key and WebGLTexture object + if ( currentTextureSlot !== webglSlot ) { - textureProperties.__cacheKey = textureCacheKey; - textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; } - return forceUpload; - } - function uploadTexture( textureProperties, texture, slot ) { + function bindTexture( webglType, webglTexture, webglSlot ) { - let textureType = _gl.TEXTURE_2D; + if ( webglSlot === undefined ) { - if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; - if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; + if ( currentTextureSlot === null ) { - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; + webglSlot = gl.TEXTURE0 + maxTextures - 1; - state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + } else { - const sourceProperties = properties.get( source ); + webglSlot = currentTextureSlot; - if ( source.version !== sourceProperties.__version || forceUpload === true ) { + } - state.activeTexture( _gl.TEXTURE0 + slot ); + } - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); - const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; + let boundTexture = currentBoundTextures[ webglSlot ]; - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); + if ( boundTexture === undefined ) { - const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; - let image = resizeImage( texture.image, needsPowerOfTwo, false, capabilities.maxTextureSize ); - image = verifyColorSpace( texture, image ); + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ webglSlot ] = boundTexture; - const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ); + } - let glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - setTextureParameters( textureType, texture, supportsMips ); + if ( currentTextureSlot !== webglSlot ) { - let mipmap; - const mipmaps = texture.mipmaps; + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true && glInternalFormat !== RGB_ETC1_Format ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const dataReady = source.dataReady; - const levels = getMipLevels( texture, image, supportsMips ); + } - if ( texture.isDepthTexture ) { + gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - // populate depth texture with dummy data + boundTexture.type = webglType; + boundTexture.texture = webglTexture; - glInternalFormat = _gl.DEPTH_COMPONENT; + } - if ( isWebGL2 ) { + } - if ( texture.type === FloatType ) { + function unbindTexture() { - glInternalFormat = _gl.DEPTH_COMPONENT32F; + const boundTexture = currentBoundTextures[ currentTextureSlot ]; - } else if ( texture.type === UnsignedIntType ) { + if ( boundTexture !== undefined && boundTexture.type !== undefined ) { - glInternalFormat = _gl.DEPTH_COMPONENT24; + gl.bindTexture( boundTexture.type, null ); - } else if ( texture.type === UnsignedInt248Type ) { + boundTexture.type = undefined; + boundTexture.texture = undefined; - glInternalFormat = _gl.DEPTH24_STENCIL8; + } - } else { + } - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D + function compressedTexImage2D() { - } + try { - } else { + gl.compressedTexImage2D( ...arguments ); - if ( texture.type === FloatType ) { + } catch ( error ) { - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); + console.error( 'THREE.WebGLState:', error ); - } + } - } + } - // validation checks for WebGL 1 + function compressedTexImage3D() { - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { + try { - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { + gl.compressedTexImage3D( ...arguments ); - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); + } catch ( error ) { - texture.type = UnsignedIntType; - glType = utils.convert( texture.type ); + console.error( 'THREE.WebGLState:', error ); - } + } - } + } - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { + function texSubImage2D() { - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; + try { - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { + gl.texSubImage2D( ...arguments ); - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); + } catch ( error ) { - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); + console.error( 'THREE.WebGLState:', error ); - } + } - } + } - // + function texSubImage3D() { - if ( allocateMemory ) { + try { - if ( useTexStorage ) { + gl.texSubImage3D( ...arguments ); - state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); + } catch ( error ) { - } else { + console.error( 'THREE.WebGLState:', error ); - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); + } - } + } - } + function compressedTexSubImage2D() { - } else if ( texture.isDataTexture ) { + try { - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels + gl.compressedTexSubImage2D( ...arguments ); - if ( mipmaps.length > 0 && supportsMips ) { + } catch ( error ) { - if ( useTexStorage && allocateMemory ) { + console.error( 'THREE.WebGLState:', error ); - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); + } - } + } - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + function compressedTexSubImage3D() { - mipmap = mipmaps[ i ]; + try { - if ( useTexStorage ) { + gl.compressedTexSubImage3D( ...arguments ); - if ( dataReady ) { + } catch ( error ) { - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); + console.error( 'THREE.WebGLState:', error ); - } + } - } else { + } - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + function texStorage2D() { - } + try { - } + gl.texStorage2D( ...arguments ); - texture.generateMipmaps = false; + } catch ( error ) { - } else { + console.error( 'THREE.WebGLState:', error ); - if ( useTexStorage ) { + } - if ( allocateMemory ) { + } - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); + function texStorage3D() { - } + try { - if ( dataReady ) { + gl.texStorage3D( ...arguments ); - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); + } catch ( error ) { - } + console.error( 'THREE.WebGLState:', error ); - } else { + } - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); + } - } + function texImage2D() { - } + try { - } else if ( texture.isCompressedTexture ) { + gl.texImage2D( ...arguments ); - if ( texture.isCompressedArrayTexture ) { + } catch ( error ) { - if ( useTexStorage && allocateMemory ) { + console.error( 'THREE.WebGLState:', error ); - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); + } - } + } - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + function texImage3D() { - mipmap = mipmaps[ i ]; + try { - if ( texture.format !== RGBAFormat ) { + gl.texImage3D( ...arguments ); - if ( glFormat !== null ) { + } catch ( error ) { - if ( useTexStorage ) { + console.error( 'THREE.WebGLState:', error ); - if ( dataReady ) { + } - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); + } - } + // - } else { + function scissor( scissor ) { - state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); + if ( currentScissor.equals( scissor ) === false ) { - } + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + currentScissor.copy( scissor ); - } else { + } - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + } - } + function viewport( viewport ) { - } else { + if ( currentViewport.equals( viewport ) === false ) { - if ( useTexStorage ) { + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + currentViewport.copy( viewport ); - if ( dataReady ) { + } - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); + } - } + function updateUBOMapping( uniformsGroup, program ) { - } else { + let mapping = uboProgramMap.get( program ); - state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); + if ( mapping === undefined ) { - } + mapping = new WeakMap(); - } + uboProgramMap.set( program, mapping ); - } + } - } else { + let blockIndex = mapping.get( uniformsGroup ); - if ( useTexStorage && allocateMemory ) { + if ( blockIndex === undefined ) { - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); + blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - } + mapping.set( uniformsGroup, blockIndex ); - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + } - mipmap = mipmaps[ i ]; + } - if ( texture.format !== RGBAFormat ) { + function uniformBlockBinding( uniformsGroup, program ) { - if ( glFormat !== null ) { + const mapping = uboProgramMap.get( program ); + const blockIndex = mapping.get( uniformsGroup ); - if ( useTexStorage ) { + if ( uboBindings.get( program ) !== blockIndex ) { - if ( dataReady ) { + // bind shader specific block index to global block point + gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); + uboBindings.set( program, blockIndex ); - } + } - } else { + } - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + // - } + function reset() { - } else { + // reset state - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + gl.disable( gl.BLEND ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.POLYGON_OFFSET_FILL ); + gl.disable( gl.SCISSOR_TEST ); + gl.disable( gl.STENCIL_TEST ); + gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - } + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ONE, gl.ZERO ); + gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); + gl.blendColor( 0, 0, 0, 0 ); - } else { + gl.colorMask( true, true, true, true ); + gl.clearColor( 0, 0, 0, 0 ); - if ( useTexStorage ) { + gl.depthMask( true ); + gl.depthFunc( gl.LESS ); - if ( dataReady ) { + depthBuffer.setReversed( false ); - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); + gl.clearDepth( 1 ); - } + gl.stencilMask( 0xffffffff ); + gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); + gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); + gl.clearStencil( 0 ); - } else { + gl.cullFace( gl.BACK ); + gl.frontFace( gl.CCW ); - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + gl.polygonOffset( 0, 0 ); - } + gl.activeTexture( gl.TEXTURE0 ); - } + gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - } + gl.useProgram( null ); - } + gl.lineWidth( 1 ); - } else if ( texture.isDataArrayTexture ) { + gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); + gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); - if ( useTexStorage ) { + // reset internals - if ( allocateMemory ) { + enabledCapabilities = {}; - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); + currentTextureSlot = null; + currentBoundTextures = {}; - } + currentBoundFramebuffers = {}; + currentDrawbuffers = new WeakMap(); + defaultDrawbuffers = []; - if ( dataReady ) { + currentProgram = null; - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + currentBlendingEnabled = false; + currentBlending = null; + currentBlendEquation = null; + currentBlendSrc = null; + currentBlendDst = null; + currentBlendEquationAlpha = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor = new Color( 0, 0, 0 ); + currentBlendAlpha = 0; + currentPremultipledAlpha = false; - } + currentFlipSided = null; + currentCullFace = null; - } else { + currentLineWidth = null; - state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); + currentPolygonOffsetFactor = null; + currentPolygonOffsetUnits = null; - } + currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); + currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - } else if ( texture.isData3DTexture ) { + colorBuffer.reset(); + depthBuffer.reset(); + stencilBuffer.reset(); - if ( useTexStorage ) { + } - if ( allocateMemory ) { + return { - state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); + buffers: { + color: colorBuffer, + depth: depthBuffer, + stencil: stencilBuffer + }, - } + enable: enable, + disable: disable, - if ( dataReady ) { + bindFramebuffer: bindFramebuffer, + drawBuffers: drawBuffers, - state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + useProgram: useProgram, - } + setBlending: setBlending, + setMaterial: setMaterial, - } else { + setFlipSided: setFlipSided, + setCullFace: setCullFace, - state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); + setLineWidth: setLineWidth, + setPolygonOffset: setPolygonOffset, - } + setScissorTest: setScissorTest, - } else if ( texture.isFramebufferTexture ) { + activeTexture: activeTexture, + bindTexture: bindTexture, + unbindTexture: unbindTexture, + compressedTexImage2D: compressedTexImage2D, + compressedTexImage3D: compressedTexImage3D, + texImage2D: texImage2D, + texImage3D: texImage3D, - if ( allocateMemory ) { + updateUBOMapping: updateUBOMapping, + uniformBlockBinding: uniformBlockBinding, - if ( useTexStorage ) { + texStorage2D: texStorage2D, + texStorage3D: texStorage3D, + texSubImage2D: texSubImage2D, + texSubImage3D: texSubImage3D, + compressedTexSubImage2D: compressedTexSubImage2D, + compressedTexSubImage3D: compressedTexSubImage3D, - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); + scissor: scissor, + viewport: viewport, - } else { + reset: reset - let width = image.width, height = image.height; + }; - for ( let i = 0; i < levels; i ++ ) { +} - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); +/** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ +function getByteLength( width, height, format, type ) { + + const typeByteLength = getTextureTypeByteLength( type ); + + switch ( format ) { + + // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml + case AlphaFormat: + return width * height; + case RedFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RedIntegerFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGIntegerFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBFormat: + return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAIntegerFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ + case RGB_S3TC_DXT1_Format: + case RGBA_S3TC_DXT1_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_S3TC_DXT3_Format: + case RGBA_S3TC_DXT5_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ + case RGB_PVRTC_2BPPV1_Format: + case RGBA_PVRTC_2BPPV1_Format: + return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; + case RGB_PVRTC_4BPPV1_Format: + case RGBA_PVRTC_4BPPV1_Format: + return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ + case RGB_ETC1_Format: + case RGB_ETC2_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_ETC2_EAC_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ + case RGBA_ASTC_4x4_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x4_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x5_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x5_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x6_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x5_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_8x6_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x8_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x5_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_10x6_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_10x8_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x10_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x10_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x12_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ + case RGBA_BPTC_Format: + case RGB_BPTC_SIGNED_Format: + case RGB_BPTC_UNSIGNED_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ + case RED_RGTC1_Format: + case SIGNED_RED_RGTC1_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; + case RED_GREEN_RGTC2_Format: + case SIGNED_RED_GREEN_RGTC2_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + } + + throw new Error( + `Unable to determine texture byte length for ${format} format.`, + ); - width >>= 1; - height >>= 1; +} - } +function getTextureTypeByteLength( type ) { - } + switch ( type ) { - } + case UnsignedByteType: + case ByteType: + return { byteLength: 1, components: 1 }; + case UnsignedShortType: + case ShortType: + case HalfFloatType: + return { byteLength: 2, components: 1 }; + case UnsignedShort4444Type: + case UnsignedShort5551Type: + return { byteLength: 2, components: 4 }; + case UnsignedIntType: + case IntType: + case FloatType: + return { byteLength: 4, components: 1 }; + case UnsignedInt5999Type: + case UnsignedInt101111Type: + return { byteLength: 4, components: 3 }; - } else { + } - // regular Texture (image, video, canvas) + throw new Error( `Unknown texture type ${type}.` ); - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels +} - if ( mipmaps.length > 0 && supportsMips ) { +function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - if ( useTexStorage && allocateMemory ) { + const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; + const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - const dimensions = getDimensions( mipmaps[ 0 ] ); + const _imageDimensions = new Vector2(); + const _videoTextures = new WeakMap(); + let _canvas; - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); + const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source - } + // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, + // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! + // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + let useOffscreenCanvas = false; - mipmap = mipmaps[ i ]; + try { - if ( useTexStorage ) { + useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' + // eslint-disable-next-line compat/compat + && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - if ( dataReady ) { + } catch ( err ) { - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); + // Ignore any errors - } + } - } else { + function createCanvas( width, height ) { - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); + // Use OffscreenCanvas when available. Specially needed in web workers - } + return useOffscreenCanvas ? + // eslint-disable-next-line compat/compat + new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - } + } - texture.generateMipmaps = false; + function resizeImage( image, needsNewCanvas, maxSize ) { - } else { + let scale = 1; - if ( useTexStorage ) { + const dimensions = getDimensions( image ); - if ( allocateMemory ) { + // handle case if texture exceeds max size - const dimensions = getDimensions( image ); + if ( dimensions.width > maxSize || dimensions.height > maxSize ) { - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); + scale = maxSize / Math.max( dimensions.width, dimensions.height ); - } + } - if ( dataReady ) { + // only perform resize if necessary - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); + if ( scale < 1 ) { - } + // only perform resize for certain image types - } else { + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || + ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); + const width = Math.floor( scale * dimensions.width ); + const height = Math.floor( scale * dimensions.height ); - } + if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - } + // cube textures can't reuse the same canvas - } + const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + canvas.width = width; + canvas.height = height; - generateMipmap( textureType ); + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, width, height ); - } + console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + dimensions.width + 'x' + dimensions.height + ') to (' + width + 'x' + height + ').' ); - sourceProperties.__version = source.version; + return canvas; - if ( texture.onUpdate ) texture.onUpdate( texture ); + } else { + + if ( 'data' in image ) { + + console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + dimensions.width + 'x' + dimensions.height + ').' ); + + } + + return image; + + } } - textureProperties.__version = texture.version; + return image; } - function uploadCubeTexture( textureProperties, texture, slot ) { - - if ( texture.image.length !== 6 ) return; + function textureNeedsGenerateMipmaps( texture ) { - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; + return texture.generateMipmaps; - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + } - const sourceProperties = properties.get( source ); + function generateMipmap( target ) { - if ( source.version !== sourceProperties.__version || forceUpload === true ) { + _gl.generateMipmap( target ); - state.activeTexture( _gl.TEXTURE0 + slot ); + } - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); - const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; + function getTargetType( texture ) { - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); + if ( texture.isWebGLCubeRenderTarget ) return _gl.TEXTURE_CUBE_MAP; + if ( texture.isWebGL3DRenderTarget ) return _gl.TEXTURE_3D; + if ( texture.isWebGLArrayRenderTarget || texture.isCompressedArrayTexture ) return _gl.TEXTURE_2D_ARRAY; + return _gl.TEXTURE_2D; - const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); - const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); + } - const cubeImage = []; + function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - for ( let i = 0; i < 6; i ++ ) { + if ( internalFormatName !== null ) { - if ( ! isCompressed && ! isDataTexture ) { + if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, capabilities.maxCubemapSize ); + console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - } else { + } - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; + let internalFormat = glFormat; - } + if ( glFormat === _gl.RED ) { - cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); + if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; - } + } - const image = cubeImage[ 0 ], - supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ), - glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + if ( glFormat === _gl.RED_INTEGER ) { - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const dataReady = source.dataReady; - let levels = getMipLevels( texture, image, supportsMips ); + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; + if ( glType === _gl.INT ) internalFormat = _gl.R32I; - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + } - let mipmaps; + if ( glFormat === _gl.RG ) { - if ( isCompressed ) { + if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - if ( useTexStorage && allocateMemory ) { + } - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); + if ( glFormat === _gl.RG_INTEGER ) { - } + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RG16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RG32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RG8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RG16I; + if ( glType === _gl.INT ) internalFormat = _gl.RG32I; - for ( let i = 0; i < 6; i ++ ) { + } - mipmaps = cubeImage[ i ].mipmaps; + if ( glFormat === _gl.RGB_INTEGER ) { - for ( let j = 0; j < mipmaps.length; j ++ ) { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGB8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGB16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGB32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGB8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGB16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGB32I; - const mipmap = mipmaps[ j ]; + } - if ( texture.format !== RGBAFormat ) { + if ( glFormat === _gl.RGBA_INTEGER ) { - if ( glFormat !== null ) { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGBA8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGBA16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGBA32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGBA8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGBA16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGBA32I; - if ( useTexStorage ) { + } - if ( dataReady ) { + if ( glFormat === _gl.RGB ) { - state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); + if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; + if ( glType === _gl.UNSIGNED_INT_10F_11F_11F_REV ) internalFormat = _gl.R11F_G11F_B10F; - } + } - } else { + if ( glFormat === _gl.RGBA ) { - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); - } + if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; + if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; + if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - } else { + } - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); + if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || + internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || + internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - } + extensions.get( 'EXT_color_buffer_float' ); - } else { + } - if ( useTexStorage ) { + return internalFormat; - if ( dataReady ) { + } - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); + function getInternalDepthFormat( useStencil, depthType ) { - } + let glInternalFormat; + if ( useStencil ) { - } else { + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + glInternalFormat = _gl.DEPTH24_STENCIL8; - } + } else if ( depthType === FloatType ) { - } + glInternalFormat = _gl.DEPTH32F_STENCIL8; - } + } else if ( depthType === UnsignedShortType ) { - } + glInternalFormat = _gl.DEPTH24_STENCIL8; + console.warn( 'DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.' ); - } else { + } - mipmaps = texture.mipmaps; + } else { - if ( useTexStorage && allocateMemory ) { + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - // TODO: Uniformly handle mipmap definitions - // Normal textures and compressed cube textures define base level + mips with their mipmap array - // Uncompressed cube textures use their mipmap array only for mips (no base level) + glInternalFormat = _gl.DEPTH_COMPONENT24; - if ( mipmaps.length > 0 ) levels ++; + } else if ( depthType === FloatType ) { - const dimensions = getDimensions( cubeImage[ 0 ] ); + glInternalFormat = _gl.DEPTH_COMPONENT32F; - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, dimensions.width, dimensions.height ); + } else if ( depthType === UnsignedShortType ) { - } + glInternalFormat = _gl.DEPTH_COMPONENT16; - for ( let i = 0; i < 6; i ++ ) { + } - if ( isDataTexture ) { + } - if ( useTexStorage ) { + return glInternalFormat; - if ( dataReady ) { + } - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); + function getMipLevels( texture, image ) { - } + if ( textureNeedsGenerateMipmaps( texture ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - } else { + return Math.log2( Math.max( image.width, image.height ) ) + 1; - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); + } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - } + // user-defined mipmaps - for ( let j = 0; j < mipmaps.length; j ++ ) { + return texture.mipmaps.length; - const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ i ].image; + } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - if ( useTexStorage ) { + return image.mipmaps.length; - if ( dataReady ) { + } else { - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); + // texture without mipmaps (only base level) - } + return 1; - } else { + } - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); + } - } + // - } + function onTextureDispose( event ) { - } else { + const texture = event.target; - if ( useTexStorage ) { + texture.removeEventListener( 'dispose', onTextureDispose ); - if ( dataReady ) { + deallocateTexture( texture ); - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); + if ( texture.isVideoTexture ) { - } + _videoTextures.delete( texture ); - } else { + } - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); + } - } + function onRenderTargetDispose( event ) { - for ( let j = 0; j < mipmaps.length; j ++ ) { + const renderTarget = event.target; - const mipmap = mipmaps[ j ]; + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - if ( useTexStorage ) { + deallocateRenderTarget( renderTarget ); - if ( dataReady ) { + } - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); + // - } + function deallocateTexture( texture ) { - } else { + const textureProperties = properties.get( texture ); - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); + if ( textureProperties.__webglInit === undefined ) return; - } + // check if it's necessary to remove the WebGLTexture object - } + const source = texture.source; + const webglTextures = _sources.get( source ); - } + if ( webglTextures ) { - } + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; + webglTexture.usedTimes --; - } + // the WebGLTexture object is not used anymore, remove it - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( webglTexture.usedTimes === 0 ) { - // We assume images for cube map have the same size. - generateMipmap( _gl.TEXTURE_CUBE_MAP ); + deleteTexture( texture ); } - sourceProperties.__version = source.version; + // remove the weak map entry if no WebGLTexture uses the source anymore - if ( texture.onUpdate ) texture.onUpdate( texture ); + if ( Object.keys( webglTextures ).length === 0 ) { + + _sources.delete( source ); + + } } - textureProperties.__version = texture.version; + properties.remove( texture ); } - // Render targets + function deleteTexture( texture ) { - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { + const textureProperties = properties.get( texture ); + _gl.deleteTexture( textureProperties.__webglTexture ); - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const renderTargetProperties = properties.get( renderTarget ); + const source = texture.source; + const webglTextures = _sources.get( source ); + delete webglTextures[ textureProperties.__cacheKey ]; - if ( ! renderTargetProperties.__hasExternalTextures ) { + info.memory.textures --; - const width = Math.max( 1, renderTarget.width >> level ); - const height = Math.max( 1, renderTarget.height >> level ); + } - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { + function deallocateRenderTarget( renderTarget ) { - state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); + const renderTargetProperties = properties.get( renderTarget ); - } else { + if ( renderTarget.depthTexture ) { - state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); + renderTarget.depthTexture.dispose(); - } + properties.remove( renderTarget.depthTexture ); } - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + if ( renderTarget.isWebGLCubeRenderTarget ) { - } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 + for ( let i = 0; i < 6; i ++ ) { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level ); + if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { - } + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + } else { - } + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); + } - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + } - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + } else { - let glInternalFormat = ( isWebGL2 === true ) ? _gl.DEPTH_COMPONENT24 : _gl.DEPTH_COMPONENT16; + if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { - if ( isMultisample || useMultisampledRTT( renderTarget ) ) { + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); - const depthTexture = renderTarget.depthTexture; + } else { - if ( depthTexture && depthTexture.isDepthTexture ) { + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( depthTexture.type === FloatType ) { + } - glInternalFormat = _gl.DEPTH_COMPONENT32F; + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); + if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - } else if ( depthTexture.type === UnsignedIntType ) { + if ( renderTargetProperties.__webglColorRenderbuffer ) { - glInternalFormat = _gl.DEPTH_COMPONENT24; + for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - } + if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); } - const samples = getRenderTargetSamples( renderTarget ); - - if ( useMultisampledRTT( renderTarget ) ) { + } - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - } else { + } - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + const textures = renderTarget.textures; - } + for ( let i = 0, il = textures.length; i < il; i ++ ) { - } else { + const attachmentProperties = properties.get( textures[ i ] ); - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); + if ( attachmentProperties.__webglTexture ) { - } + _gl.deleteTexture( attachmentProperties.__webglTexture ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + info.memory.textures --; - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + } - const samples = getRenderTargetSamples( renderTarget ); + properties.remove( textures[ i ] ); - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { + } - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + properties.remove( renderTarget ); - } else if ( useMultisampledRTT( renderTarget ) ) { + } - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + // - } else { + let textureUnits = 0; - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + function resetTextureUnits() { - } + textureUnits = 0; + } - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + function allocateTextureUnit() { - } else { + const textureUnit = textureUnits; - const textures = renderTarget.textures; + if ( textureUnit >= capabilities.maxTextures ) { - for ( let i = 0; i < textures.length; i ++ ) { + console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - const texture = textures[ i ]; + } - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const samples = getRenderTargetSamples( renderTarget ); + textureUnits += 1; - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { + return textureUnit; - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + } - } else if ( useMultisampledRTT( renderTarget ) ) { + function getTextureCacheKey( texture ) { - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + const array = []; - } else { + array.push( texture.wrapS ); + array.push( texture.wrapT ); + array.push( texture.wrapR || 0 ); + array.push( texture.magFilter ); + array.push( texture.minFilter ); + array.push( texture.anisotropy ); + array.push( texture.internalFormat ); + array.push( texture.format ); + array.push( texture.type ); + array.push( texture.generateMipmaps ); + array.push( texture.premultiplyAlpha ); + array.push( texture.flipY ); + array.push( texture.unpackAlignment ); + array.push( texture.colorSpace ); - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); + return array.join(); - } + } - } + // - } + function setTexture2D( texture, slot ) { - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + const textureProperties = properties.get( texture ); - } + if ( texture.isVideoTexture ) updateVideoTexture( texture ); - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget ) { + if ( texture.isRenderTargetTexture === false && texture.isExternalTexture !== true && texture.version > 0 && textureProperties.__version !== texture.version ) { - const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); - if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); + const image = texture.image; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + if ( image === null ) { - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { + console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); - throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); + } else if ( image.complete === false ) { - } + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); - // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { + } else { - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; + uploadTexture( textureProperties, texture, slot ); + return; - } + } - setTexture2D( renderTarget.depthTexture, 0 ); + } else if ( texture.isExternalTexture ) { - const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; - const samples = getRenderTargetSamples( renderTarget ); + textureProperties.__webglTexture = texture.sourceTexture ? texture.sourceTexture : null; - if ( renderTarget.depthTexture.format === DepthFormat ) { + } - if ( useMultisampledRTT( renderTarget ) ) { + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + } - } else { + function setTexture2DArray( texture, slot ) { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + const textureProperties = properties.get( texture ); - } + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { + uploadTexture( textureProperties, texture, slot ); + return; - if ( useMultisampledRTT( renderTarget ) ) { + } - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - } else { + } - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + function setTexture3D( texture, slot ) { - } + const textureProperties = properties.get( texture ); - } else { + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - throw new Error( 'Unknown depthTexture format' ); + uploadTexture( textureProperties, texture, slot ); + return; } + state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + } - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { + function setTextureCube( texture, slot ) { - const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + const textureProperties = properties.get( texture ); - if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); + uploadCubeTexture( textureProperties, texture, slot ); + return; - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); + } - } else { + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - if ( isCube ) { + } - renderTargetProperties.__webglDepthbuffer = []; + const wrappingToGL = { + [ RepeatWrapping ]: _gl.REPEAT, + [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, + [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT + }; - for ( let i = 0; i < 6; i ++ ) { + const filterToGL = { + [ NearestFilter ]: _gl.NEAREST, + [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, + [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); + [ LinearFilter ]: _gl.LINEAR, + [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, + [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR + }; - } + const compareToGL = { + [ NeverCompare ]: _gl.NEVER, + [ AlwaysCompare ]: _gl.ALWAYS, + [ LessCompare ]: _gl.LESS, + [ LessEqualCompare ]: _gl.LEQUAL, + [ EqualCompare ]: _gl.EQUAL, + [ GreaterEqualCompare ]: _gl.GEQUAL, + [ GreaterCompare ]: _gl.GREATER, + [ NotEqualCompare ]: _gl.NOTEQUAL + }; - } else { + function setTextureParameters( textureType, texture ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && + ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ) ) { - } + console.warn( 'THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device.' ); } - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // rebind framebuffer with external textures - function rebindTextures( renderTarget, colorTexture, depthTexture ) { + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - const renderTargetProperties = properties.get( renderTarget ); - - if ( colorTexture !== undefined ) { + if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); } - if ( depthTexture !== undefined ) { + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - setupDepthRenderbuffer( renderTarget ); + if ( texture.compareFunction ) { + + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); } - } + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { + if ( texture.magFilter === NearestFilter ) return; + if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension - const texture = renderTarget.texture; + if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); + properties.get( texture ).__currentAnisotropy = texture.anisotropy; - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); + } - const textures = renderTarget.textures; + } - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - const isMultipleRenderTargets = ( textures.length > 1 ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; + } - if ( ! isMultipleRenderTargets ) { + function initTexture( textureProperties, texture ) { - if ( textureProperties.__webglTexture === undefined ) { + let forceUpload = false; - textureProperties.__webglTexture = _gl.createTexture(); + if ( textureProperties.__webglInit === undefined ) { - } + textureProperties.__webglInit = true; - textureProperties.__version = texture.version; - info.memory.textures ++; + texture.addEventListener( 'dispose', onTextureDispose ); } - // Setup framebuffer + // create Source <-> WebGLTextures mapping if necessary - if ( isCube ) { + const source = texture.source; + let webglTextures = _sources.get( source ); - renderTargetProperties.__webglFramebuffer = []; + if ( webglTextures === undefined ) { - for ( let i = 0; i < 6; i ++ ) { + webglTextures = {}; + _sources.set( source, webglTextures ); - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + } - renderTargetProperties.__webglFramebuffer[ i ] = []; + // check if there is already a WebGLTexture object for the given texture parameters - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { + const textureCacheKey = getTextureCacheKey( texture ); - renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); + if ( textureCacheKey !== textureProperties.__cacheKey ) { - } + // if not, create a new instance of WebGLTexture - } else { + if ( webglTextures[ textureCacheKey ] === undefined ) { - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + // create new entry - } + webglTextures[ textureCacheKey ] = { + texture: _gl.createTexture(), + usedTimes: 0 + }; - } + info.memory.textures ++; - } else { + // when a new instance of WebGLTexture was created, a texture upload is required + // even if the image contents are identical - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + forceUpload = true; - renderTargetProperties.__webglFramebuffer = []; + } - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { + webglTextures[ textureCacheKey ].usedTimes ++; - renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); + // every time the texture cache key changes, it's necessary to check if an instance of + // WebGLTexture can be deleted in order to avoid a memory leak. - } + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - } else { + if ( webglTexture !== undefined ) { - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); + webglTextures[ textureProperties.__cacheKey ].usedTimes --; - } + if ( webglTexture.usedTimes === 0 ) { - if ( isMultipleRenderTargets ) { + deleteTexture( texture ); - if ( capabilities.drawBuffers ) { + } - for ( let i = 0, il = textures.length; i < il; i ++ ) { + } - const attachmentProperties = properties.get( textures[ i ] ); + // store references to cache key and WebGLTexture object - if ( attachmentProperties.__webglTexture === undefined ) { + textureProperties.__cacheKey = textureCacheKey; + textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; - attachmentProperties.__webglTexture = _gl.createTexture(); + } - info.memory.textures ++; + return forceUpload; - } + } - } + function getRow( index, rowLength, componentStride ) { - } else { + return Math.floor( Math.floor( index / componentStride ) / rowLength ); - console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); + } - } + function updateTexture( texture, image, glFormat, glType ) { - } + const componentStride = 4; // only RGBA supported - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { + const updateRanges = texture.updateRanges; - renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); - renderTargetProperties.__webglColorRenderbuffer = []; + if ( updateRanges.length === 0 ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); - for ( let i = 0; i < textures.length; i ++ ) { + } else { - const texture = textures[ i ]; - renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + updateRanges.sort( ( a, b ) => a.start - b.start ); - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); - const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + for ( let i = 1; i < updateRanges.length; i ++ ) { - } + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + // Only merge if in the same row and overlapping/adjacent + const previousEnd = previousRange.start + previousRange.count; + const currentRow = getRow( range.start, image.width, componentStride ); + const previousRow = getRow( previousRange.start, image.width, componentStride ); + + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( + range.start <= previousEnd + 1 && + currentRow === previousRow && + getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill + ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); - if ( renderTarget.depthBuffer ) { + } else { - renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; } - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - } - - // Setup color buffer - - if ( isCube ) { + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - for ( let i = 0; i < 6; i ++ ) { + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { + const range = updateRanges[ i ]; - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); + const pixelStart = Math.floor( range.start / componentStride ); + const pixelCount = Math.ceil( range.count / componentStride ); - } + const x = pixelStart % image.width; + const y = Math.floor( pixelStart / image.width ); - } else { + // Assumes update ranges refer to contiguous memory + const width = pixelCount; + const height = 1; - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y ); - } + state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data ); } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + texture.clearUpdateRanges(); - generateMipmap( _gl.TEXTURE_CUBE_MAP ); + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); - } + } - state.unbindTexture(); + } - } else if ( isMultipleRenderTargets ) { + function uploadTexture( textureProperties, texture, slot ) { - for ( let i = 0, il = textures.length; i < il; i ++ ) { + let textureType = _gl.TEXTURE_2D; - const attachment = textures[ i ]; - const attachmentProperties = properties.get( attachment ); + if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; + if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 ); + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { + state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - generateMipmap( _gl.TEXTURE_2D ); + const sourceProperties = properties.get( source ); - } + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - } + state.activeTexture( _gl.TEXTURE0 + slot ); - state.unbindTexture(); + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - } else { + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - let glTextureType = _gl.TEXTURE_2D; + let image = resizeImage( texture.image, false, capabilities.maxTextureSize ); + image = verifyColorSpace( texture, image ); - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { + const glFormat = utils.convert( texture.format, texture.colorSpace ); - if ( isWebGL2 ) { + const glType = utils.convert( texture.type ); + let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; + setTextureParameters( textureType, texture ); - } else { + let mipmap; + const mipmaps = texture.mipmaps; - console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + const levels = getMipLevels( texture, image ); - } + if ( texture.isDepthTexture ) { - } + glInternalFormat = getInternalDepthFormat( texture.format === DepthStencilFormat, texture.type ); - state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture, supportsMips ); + // - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + if ( allocateMemory ) { - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { + if ( useTexStorage ) { - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); + state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - } + } else { - } else { + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); + } - } + } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + } else if ( texture.isDataTexture ) { - generateMipmap( glTextureType ); + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels - } + if ( mipmaps.length > 0 ) { - state.unbindTexture(); + if ( useTexStorage && allocateMemory ) { - } + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - // Setup depth and stencil buffers + } - if ( renderTarget.depthBuffer ) { + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - setupDepthRenderbuffer( renderTarget ); + mipmap = mipmaps[ i ]; - } + if ( useTexStorage ) { - } + if ( dataReady ) { - function updateRenderTargetMipmap( renderTarget ) { + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; + } - const textures = renderTarget.textures; + } else { - for ( let i = 0, il = textures.length; i < il; i ++ ) { + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - const texture = textures[ i ]; + } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + } - const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; - const webglTexture = properties.get( texture ).__webglTexture; + texture.generateMipmaps = false; - state.bindTexture( target, webglTexture ); - generateMipmap( target ); - state.unbindTexture(); + } else { - } + if ( useTexStorage ) { - } + if ( allocateMemory ) { - } + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - function updateMultisampleRenderTarget( renderTarget ) { + } - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { + if ( dataReady ) { - const textures = renderTarget.textures; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const invalidationArray = []; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( textures.length > 1 ); + updateTexture( texture, image, glFormat, glType ); - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { + } - for ( let i = 0; i < textures.length; i ++ ) { + } else { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); + } } - } + } else if ( texture.isCompressedTexture ) { - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + if ( texture.isCompressedArrayTexture ) { - for ( let i = 0; i < textures.length; i ++ ) { + if ( useTexStorage && allocateMemory ) { - invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - if ( renderTarget.depthBuffer ) { + } - invalidationArray.push( depthStyle ); + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; - const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; + if ( texture.format !== RGBAFormat ) { - if ( ignoreDepthValues === false ) { + if ( glFormat !== null ) { - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; + if ( useTexStorage ) { - } + if ( dataReady ) { - if ( isMultipleRenderTargets ) { + if ( texture.layerUpdates.size > 0 ) { - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + const layerByteLength = getByteLength( mipmap.width, mipmap.height, texture.format, texture.type ); - } + for ( const layerIndex of texture.layerUpdates ) { - if ( ignoreDepthValues === true ) { + const layerData = mipmap.data.subarray( + layerIndex * layerByteLength / mipmap.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / mipmap.data.BYTES_PER_ELEMENT + ); + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, layerIndex, mipmap.width, mipmap.height, 1, glFormat, layerData ); - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); + } - } + texture.clearLayerUpdates(); - if ( isMultipleRenderTargets ) { + } else { - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); - } - - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); + } - if ( supportsInvalidateFramebuffer ) { - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); + } - } + } else { + state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - } + } - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); + } else { - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - for ( let i = 0; i < textures.length; i ++ ) { + } - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + } else { - const webglTexture = properties.get( textures[ i ] ).__webglTexture; + if ( useTexStorage ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); + if ( dataReady ) { - } + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); - } + } - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + } else { - } + state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - } + } - function getRenderTargetSamples( renderTarget ) { + } - return Math.min( capabilities.maxSamples, renderTarget.samples ); + } - } + } else { - function useMultisampledRTT( renderTarget ) { + if ( useTexStorage && allocateMemory ) { - const renderTargetProperties = properties.get( renderTarget ); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; + } - } + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - function updateVideoTexture( texture ) { + mipmap = mipmaps[ i ]; - const frame = info.render.frame; + if ( texture.format !== RGBAFormat ) { - // Check the last frame we updated the VideoTexture + if ( glFormat !== null ) { - if ( _videoTextures.get( texture ) !== frame ) { + if ( useTexStorage ) { - _videoTextures.set( texture, frame ); - texture.update(); + if ( dataReady ) { - } + state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - } + } - function verifyColorSpace( texture, image ) { + } else { - const colorSpace = texture.colorSpace; - const format = texture.format; - const type = texture.type; + state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - if ( texture.isCompressedTexture === true || texture.isVideoTexture === true || texture.format === _SRGBAFormat ) return image; + } - if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { + } else { - // sRGB + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { + } - if ( isWebGL2 === false ) { + } else { - // in WebGL 1, try to use EXT_sRGB extension and unsized formats + if ( useTexStorage ) { - if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { + if ( dataReady ) { - texture.format = _SRGBAFormat; + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - // it's not possible to generate mips in WebGL 1 with this extension + } - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; + } else { - } else { + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - // slow fallback (CPU decode) + } - image = ImageUtils.sRGBToLinear( image ); + } } - } else { + } + + } else if ( texture.isDataArrayTexture ) { - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format + if ( useTexStorage ) { - if ( format !== RGBAFormat || type !== UnsignedByteType ) { + if ( allocateMemory ) { - console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); } - } + if ( dataReady ) { - } else { + if ( texture.layerUpdates.size > 0 ) { - console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); + const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type ); - } + for ( const layerIndex of texture.layerUpdates ) { - } + const layerData = image.data.subarray( + layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT + ); + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData ); - return image; + } - } + texture.clearLayerUpdates(); - function getDimensions( image ) { + } else { - if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) { + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - // if intrinsic data are not available, fallback to width/height + } - _imageDimensions.width = image.naturalWidth || image.width; - _imageDimensions.height = image.naturalHeight || image.height; + } - } else if ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) { + } else { - _imageDimensions.width = image.displayWidth; - _imageDimensions.height = image.displayHeight; + state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - } else { + } - _imageDimensions.width = image.width; - _imageDimensions.height = image.height; + } else if ( texture.isData3DTexture ) { - } + if ( useTexStorage ) { - return _imageDimensions; + if ( allocateMemory ) { - } + state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - // + } - this.allocateTextureUnit = allocateTextureUnit; - this.resetTextureUnits = resetTextureUnits; + if ( dataReady ) { - this.setTexture2D = setTexture2D; - this.setTexture2DArray = setTexture2DArray; - this.setTexture3D = setTexture3D; - this.setTextureCube = setTextureCube; - this.rebindTextures = rebindTextures; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; - this.setupDepthRenderbuffer = setupDepthRenderbuffer; - this.setupFrameBufferTexture = setupFrameBufferTexture; - this.useMultisampledRTT = useMultisampledRTT; + state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); -} + } -function WebGLUtils( gl, extensions, capabilities ) { + } else { - const isWebGL2 = capabilities.isWebGL2; + state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - function convert( p, colorSpace = NoColorSpace ) { + } - let extension; + } else if ( texture.isFramebufferTexture ) { - const transfer = ColorManagement.getTransfer( colorSpace ); + if ( allocateMemory ) { - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( useTexStorage ) { - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - if ( p === HalfFloatType ) { + } else { - if ( isWebGL2 ) return gl.HALF_FLOAT; + let width = image.width, height = image.height; - extension = extensions.get( 'OES_texture_half_float' ); + for ( let i = 0; i < levels; i ++ ) { - if ( extension !== null ) { + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); - return extension.HALF_FLOAT_OES; + width >>= 1; + height >>= 1; - } else { + } - return null; + } - } + } - } + } else { - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; + // regular Texture (image, video, canvas) - // WebGL 1 sRGB fallback + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels - if ( p === _SRGBAFormat ) { + if ( mipmaps.length > 0 ) { - extension = extensions.get( 'EXT_sRGB' ); + if ( useTexStorage && allocateMemory ) { - if ( extension !== null ) { + const dimensions = getDimensions( mipmaps[ 0 ] ); - return extension.SRGB_ALPHA_EXT; + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - } else { + } - return null; + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; - } + if ( useTexStorage ) { - // WebGL2 formats. + if ( dataReady ) { - if ( p === RedFormat ) return gl.RED; - if ( p === RedIntegerFormat ) return gl.RED_INTEGER; - if ( p === RGFormat ) return gl.RG; - if ( p === RGIntegerFormat ) return gl.RG_INTEGER; - if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - // S3TC + } - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { + } else { - if ( transfer === SRGBTransfer ) { + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); + } - if ( extension !== null ) { + } - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + texture.generateMipmaps = false; } else { - return null; + if ( useTexStorage ) { - } + if ( allocateMemory ) { - } else { + const dimensions = getDimensions( image ); - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - if ( extension !== null ) { + } - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + if ( dataReady ) { - } else { + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - return null; + } + + } else { + + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); + + } } } - } + if ( textureNeedsGenerateMipmaps( texture ) ) { - // PVRTC + generateMipmap( textureType ); - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { + } - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + sourceProperties.__version = source.version; - if ( extension !== null ) { + if ( texture.onUpdate ) texture.onUpdate( texture ); - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + } - } else { + textureProperties.__version = texture.version; - return null; + } - } + function uploadCubeTexture( textureProperties, texture, slot ) { - } + if ( texture.image.length !== 6 ) return; - // ETC1 + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - if ( p === RGB_ETC1_Format ) { + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); + const sourceProperties = properties.get( source ); - if ( extension !== null ) { + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - return extension.COMPRESSED_RGB_ETC1_WEBGL; + state.activeTexture( _gl.TEXTURE0 + slot ); - } else { + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - return null; + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - } + const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); + const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - } + const cubeImage = []; - // ETC2 + for ( let i = 0; i < 6; i ++ ) { - if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { + if ( ! isCompressed && ! isDataTexture ) { - extension = extensions.get( 'WEBGL_compressed_texture_etc' ); + cubeImage[ i ] = resizeImage( texture.image[ i ], true, capabilities.maxCubemapSize ); - if ( extension !== null ) { + } else { - if ( p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - } else { + } - return null; + cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); } - } - - // ASTC - - if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || - p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || - p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || - p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || - p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + const image = cubeImage[ 0 ], + glFormat = utils.convert( texture.format, texture.colorSpace ), + glType = utils.convert( texture.type ), + glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - extension = extensions.get( 'WEBGL_compressed_texture_astc' ); + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + let levels = getMipLevels( texture, image ); - if ( extension !== null ) { + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; + let mipmaps; - } else { + if ( isCompressed ) { - return null; + if ( useTexStorage && allocateMemory ) { - } + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - } + } - // BPTC + for ( let i = 0; i < 6; i ++ ) { - if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { + mipmaps = cubeImage[ i ].mipmaps; - extension = extensions.get( 'EXT_texture_compression_bptc' ); + for ( let j = 0; j < mipmaps.length; j ++ ) { - if ( extension !== null ) { + const mipmap = mipmaps[ j ]; - if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; - if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; - if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; + if ( texture.format !== RGBAFormat ) { - } else { + if ( glFormat !== null ) { - return null; + if ( useTexStorage ) { - } + if ( dataReady ) { - } + state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - // RGTC + } - if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { + } else { - extension = extensions.get( 'EXT_texture_compression_rgtc' ); + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - if ( extension !== null ) { + } - if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; - if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; - if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; - if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; + } else { - } else { + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - return null; + } - } + } else { - } + if ( useTexStorage ) { - // + if ( dataReady ) { - if ( p === UnsignedInt248Type ) { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; + } - extension = extensions.get( 'WEBGL_depth_texture' ); + } else { - if ( extension !== null ) { + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - return extension.UNSIGNED_INT_24_8_WEBGL; + } - } else { + } - return null; + } - } + } - } + } else { - // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) + mipmaps = texture.mipmaps; - return ( gl[ p ] !== undefined ) ? gl[ p ] : null; + if ( useTexStorage && allocateMemory ) { - } + // TODO: Uniformly handle mipmap definitions + // Normal textures and compressed cube textures define base level + mips with their mipmap array + // Uncompressed cube textures use their mipmap array only for mips (no base level) - return { convert: convert }; + if ( mipmaps.length > 0 ) levels ++; -} + const dimensions = getDimensions( cubeImage[ 0 ] ); -class ArrayCamera extends PerspectiveCamera { + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, dimensions.width, dimensions.height ); - constructor( array = [] ) { + } - super(); + for ( let i = 0; i < 6; i ++ ) { - this.isArrayCamera = true; + if ( isDataTexture ) { - this.cameras = array; + if ( useTexStorage ) { - } + if ( dataReady ) { -} + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); -class Group extends Object3D { + } - constructor() { + } else { - super(); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - this.isGroup = true; + } - this.type = 'Group'; + for ( let j = 0; j < mipmaps.length; j ++ ) { - } + const mipmap = mipmaps[ j ]; + const mipmapImage = mipmap.image[ i ].image; -} + if ( useTexStorage ) { -const _moveEvent = { type: 'move' }; + if ( dataReady ) { -class WebXRController { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - constructor() { + } - this._targetRay = null; - this._grip = null; - this._hand = null; + } else { - } + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - getHandSpace() { + } - if ( this._hand === null ) { + } - this._hand = new Group(); - this._hand.matrixAutoUpdate = false; - this._hand.visible = false; + } else { - this._hand.joints = {}; - this._hand.inputState = { pinching: false }; + if ( useTexStorage ) { - } + if ( dataReady ) { - return this._hand; + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - } + } - getTargetRaySpace() { + } else { - if ( this._targetRay === null ) { + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); - this._targetRay = new Group(); - this._targetRay.matrixAutoUpdate = false; - this._targetRay.visible = false; - this._targetRay.hasLinearVelocity = false; - this._targetRay.linearVelocity = new Vector3(); - this._targetRay.hasAngularVelocity = false; - this._targetRay.angularVelocity = new Vector3(); + } - } + for ( let j = 0; j < mipmaps.length; j ++ ) { - return this._targetRay; + const mipmap = mipmaps[ j ]; - } + if ( useTexStorage ) { - getGripSpace() { + if ( dataReady ) { - if ( this._grip === null ) { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - this._grip = new Group(); - this._grip.matrixAutoUpdate = false; - this._grip.visible = false; - this._grip.hasLinearVelocity = false; - this._grip.linearVelocity = new Vector3(); - this._grip.hasAngularVelocity = false; - this._grip.angularVelocity = new Vector3(); + } - } + } else { - return this._grip; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - } + } - dispatchEvent( event ) { + } - if ( this._targetRay !== null ) { + } - this._targetRay.dispatchEvent( event ); + } - } + } - if ( this._grip !== null ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { - this._grip.dispatchEvent( event ); + // We assume images for cube map have the same size. + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - } + } - if ( this._hand !== null ) { + sourceProperties.__version = source.version; - this._hand.dispatchEvent( event ); + if ( texture.onUpdate ) texture.onUpdate( texture ); } - return this; + textureProperties.__version = texture.version; } - connect( inputSource ) { - - if ( inputSource && inputSource.hand ) { - - const hand = this._hand; - - if ( hand ) { - - for ( const inputjoint of inputSource.hand.values() ) { - - // Initialize hand with joints when connected - this._getHandJoint( hand, inputjoint ); + // Render targets - } + // Setup storage for target texture and bind it to correct framebuffer + function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { - } + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - } + textureProperties.__renderTarget = renderTarget; - this.dispatchEvent( { type: 'connected', data: inputSource } ); + if ( ! renderTargetProperties.__hasExternalTextures ) { - return this; + const width = Math.max( 1, renderTarget.width >> level ); + const height = Math.max( 1, renderTarget.height >> level ); - } + if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - disconnect( inputSource ) { + state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); - this.dispatchEvent( { type: 'disconnected', data: inputSource } ); + } else { - if ( this._targetRay !== null ) { + state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); - this._targetRay.visible = false; + } } - if ( this._grip !== null ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - this._grip.visible = false; + if ( useMultisampledRTT( renderTarget ) ) { - } + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - if ( this._hand !== null ) { + } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - this._hand.visible = false; + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, level ); } - return this; + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - update( inputSource, frame, referenceSpace ) { + // Setup storage for internal depth/stencil buffers and bind to correct framebuffer + function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { - let inputPose = null; - let gripPose = null; - let handPose = null; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - const targetRay = this._targetRay; - const grip = this._grip; - const hand = this._hand; + if ( renderTarget.depthBuffer ) { - if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { + // retrieve the depth attachment types + const depthTexture = renderTarget.depthTexture; + const depthType = depthTexture && depthTexture.isDepthTexture ? depthTexture.type : null; + const glInternalFormat = getInternalDepthFormat( renderTarget.stencilBuffer, depthType ); + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - if ( hand && inputSource.hand ) { + // set up the attachment + const samples = getRenderTargetSamples( renderTarget ); + const isUseMultisampledRTT = useMultisampledRTT( renderTarget ); + if ( isUseMultisampledRTT ) { - handPose = true; + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - for ( const inputjoint of inputSource.hand.values() ) { + } else if ( isMultisample ) { - // Update the joints groups with the XRJoint poses - const jointPose = frame.getJointPose( inputjoint, referenceSpace ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - // The transform of this joint will be updated with the joint pose on each frame - const joint = this._getHandJoint( hand, inputjoint ); + } else { - if ( jointPose !== null ) { + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - joint.matrix.fromArray( jointPose.transform.matrix ); - joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); - joint.matrixWorldNeedsUpdate = true; - joint.jointRadius = jointPose.radius; + } - } + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - joint.visible = jointPose !== null; + } else { - } + const textures = renderTarget.textures; - // Custom events + for ( let i = 0; i < textures.length; i ++ ) { - // Check pinchz - const indexTip = hand.joints[ 'index-finger-tip' ]; - const thumbTip = hand.joints[ 'thumb-tip' ]; - const distance = indexTip.position.distanceTo( thumbTip.position ); + const texture = textures[ i ]; - const distanceToPinch = 0.02; - const threshold = 0.005; + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const samples = getRenderTargetSamples( renderTarget ); - if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { + if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - hand.inputState.pinching = false; - this.dispatchEvent( { - type: 'pinchend', - handedness: inputSource.handedness, - target: this - } ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { + } else if ( useMultisampledRTT( renderTarget ) ) { - hand.inputState.pinching = true; - this.dispatchEvent( { - type: 'pinchstart', - handedness: inputSource.handedness, - target: this - } ); + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + + } else { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); } - } else { + } - if ( grip !== null && inputSource.gripSpace ) { + } - gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - if ( gripPose !== null ) { + } - grip.matrix.fromArray( gripPose.transform.matrix ); - grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); - grip.matrixWorldNeedsUpdate = true; + // Setup resources for a Depth Texture for a FBO (needs an extension) + function setupDepthTexture( framebuffer, renderTarget ) { - if ( gripPose.linearVelocity ) { + const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); + if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - grip.hasLinearVelocity = true; - grip.linearVelocity.copy( gripPose.linearVelocity ); + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - } else { + if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - grip.hasLinearVelocity = false; + throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); - } + } - if ( gripPose.angularVelocity ) { + const textureProperties = properties.get( renderTarget.depthTexture ); + textureProperties.__renderTarget = renderTarget; - grip.hasAngularVelocity = true; - grip.angularVelocity.copy( gripPose.angularVelocity ); + // upload an empty depth texture with framebuffer size + if ( ! textureProperties.__webglTexture || + renderTarget.depthTexture.image.width !== renderTarget.width || + renderTarget.depthTexture.image.height !== renderTarget.height ) { - } else { + renderTarget.depthTexture.image.width = renderTarget.width; + renderTarget.depthTexture.image.height = renderTarget.height; + renderTarget.depthTexture.needsUpdate = true; - grip.hasAngularVelocity = false; + } - } + setTexture2D( renderTarget.depthTexture, 0 ); - } + const webglDepthTexture = textureProperties.__webglTexture; + const samples = getRenderTargetSamples( renderTarget ); - } + if ( renderTarget.depthTexture.format === DepthFormat ) { - } + if ( useMultisampledRTT( renderTarget ) ) { - if ( targetRay !== null ) { + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); + } else { - // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it - if ( inputPose === null && gripPose !== null ) { + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - inputPose = gripPose; + } - } + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - if ( inputPose !== null ) { + if ( useMultisampledRTT( renderTarget ) ) { - targetRay.matrix.fromArray( inputPose.transform.matrix ); - targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); - targetRay.matrixWorldNeedsUpdate = true; + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - if ( inputPose.linearVelocity ) { + } else { - targetRay.hasLinearVelocity = true; - targetRay.linearVelocity.copy( inputPose.linearVelocity ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - } else { + } - targetRay.hasLinearVelocity = false; + } else { - } + throw new Error( 'Unknown depthTexture format' ); - if ( inputPose.angularVelocity ) { + } - targetRay.hasAngularVelocity = true; - targetRay.angularVelocity.copy( inputPose.angularVelocity ); + } - } else { + // Setup GL resources for a non-texture depth buffer + function setupDepthRenderbuffer( renderTarget ) { - targetRay.hasAngularVelocity = false; + const renderTargetProperties = properties.get( renderTarget ); + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - } + // if the bound depth texture has changed + if ( renderTargetProperties.__boundDepthTexture !== renderTarget.depthTexture ) { - this.dispatchEvent( _moveEvent ); + // fire the dispose event to get rid of stored state associated with the previously bound depth buffer + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__depthDisposeCallback ) { - } + renderTargetProperties.__depthDisposeCallback(); } + // set up dispose listeners to track when the currently attached buffer is implicitly unbound + if ( depthTexture ) { - } + const disposeEvent = () => { - if ( targetRay !== null ) { + delete renderTargetProperties.__boundDepthTexture; + delete renderTargetProperties.__depthDisposeCallback; + depthTexture.removeEventListener( 'dispose', disposeEvent ); - targetRay.visible = ( inputPose !== null ); + }; - } + depthTexture.addEventListener( 'dispose', disposeEvent ); + renderTargetProperties.__depthDisposeCallback = disposeEvent; - if ( grip !== null ) { + } - grip.visible = ( gripPose !== null ); + renderTargetProperties.__boundDepthTexture = depthTexture; } - if ( hand !== null ) { + if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - hand.visible = ( handPose !== null ); + if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - } + const mipmaps = renderTarget.texture.mipmaps; - return this; + if ( mipmaps && mipmaps.length > 0 ) { - } + setupDepthTexture( renderTargetProperties.__webglFramebuffer[ 0 ], renderTarget ); - // private method + } else { - _getHandJoint( hand, inputjoint ) { + setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - if ( hand.joints[ inputjoint.jointName ] === undefined ) { + } - const joint = new Group(); - joint.matrixAutoUpdate = false; - joint.visible = false; - hand.joints[ inputjoint.jointName ] = joint; + } else { - hand.add( joint ); + if ( isCube ) { - } + renderTargetProperties.__webglDepthbuffer = []; - return hand.joints[ inputjoint.jointName ]; + for ( let i = 0; i < 6; i ++ ) { - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); -} + if ( renderTargetProperties.__webglDepthbuffer[ i ] === undefined ) { -const _occlusion_vertex = ` -void main() { + renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - gl_Position = vec4( position, 1.0 ); + } else { -}`; + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer[ i ]; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); -const _occlusion_fragment = ` -uniform sampler2DArray depthColor; -uniform float depthWidth; -uniform float depthHeight; + } -void main() { + } - vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); + } else { - if ( coord.x >= 1.0 ) { + const mipmaps = renderTarget.texture.mipmaps; - gl_FragDepthEXT = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; + if ( mipmaps && mipmaps.length > 0 ) { - } else { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - gl_FragDepthEXT = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; + } else { - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); -}`; + } -class WebXRDepthSensing { + if ( renderTargetProperties.__webglDepthbuffer === undefined ) { - constructor() { + renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - this.texture = null; - this.mesh = null; + } else { - this.depthNear = 0; - this.depthFar = 0; + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - } + } - init( renderer, depthData, renderState ) { + } - if ( this.texture === null ) { + } - const texture = new Texture(); + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - const texProps = renderer.properties.get( texture ); - texProps.__webglTexture = depthData.texture; + } - if ( ( depthData.depthNear != renderState.depthNear ) || ( depthData.depthFar != renderState.depthFar ) ) { + // rebind framebuffer with external textures + function rebindTextures( renderTarget, colorTexture, depthTexture ) { - this.depthNear = depthData.depthNear; - this.depthFar = depthData.depthFar; + const renderTargetProperties = properties.get( renderTarget ); - } + if ( colorTexture !== undefined ) { - this.texture = texture; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); } - } + if ( depthTexture !== undefined ) { - render( renderer, cameraXR ) { + setupDepthRenderbuffer( renderTarget ); - if ( this.texture !== null ) { + } - if ( this.mesh === null ) { + } - const viewport = cameraXR.cameras[ 0 ].viewport; - const material = new ShaderMaterial( { - extensions: { fragDepth: true }, - vertexShader: _occlusion_vertex, - fragmentShader: _occlusion_fragment, - uniforms: { - depthColor: { value: this.texture }, - depthWidth: { value: viewport.z }, - depthHeight: { value: viewport.w } - } - } ); + // Set up GL resources for the render target + function setupRenderTarget( renderTarget ) { - this.mesh = new Mesh( new PlaneGeometry( 20, 20 ), material ); + const texture = renderTarget.texture; - } + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - renderer.render( this.mesh, cameraXR ); + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - } + const textures = renderTarget.textures; - } + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + const isMultipleRenderTargets = ( textures.length > 1 ); - reset() { + if ( ! isMultipleRenderTargets ) { - this.texture = null; - this.mesh = null; + if ( textureProperties.__webglTexture === undefined ) { - } + textureProperties.__webglTexture = _gl.createTexture(); -} + } -class WebXRManager extends EventDispatcher { + textureProperties.__version = texture.version; + info.memory.textures ++; - constructor( renderer, gl ) { + } - super(); + // Setup framebuffer - const scope = this; + if ( isCube ) { - let session = null; + renderTargetProperties.__webglFramebuffer = []; - let framebufferScaleFactor = 1.0; + for ( let i = 0; i < 6; i ++ ) { - let referenceSpace = null; - let referenceSpaceType = 'local-floor'; - // Set default foveation to maximum. - let foveation = 1.0; - let customReferenceSpace = null; + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - let pose = null; - let glBinding = null; - let glProjLayer = null; - let glBaseLayer = null; - let xrFrame = null; + renderTargetProperties.__webglFramebuffer[ i ] = []; - const depthSensing = new WebXRDepthSensing(); - const attributes = gl.getContextAttributes(); + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - let initialRenderTarget = null; - let newRenderTarget = null; + renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); - const controllers = []; - const controllerInputSources = []; + } - const currentSize = new Vector2(); - let currentPixelRatio = null; + } else { - // + renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - const cameraL = new PerspectiveCamera(); - cameraL.layers.enable( 1 ); - cameraL.viewport = new Vector4(); + } - const cameraR = new PerspectiveCamera(); - cameraR.layers.enable( 2 ); - cameraR.viewport = new Vector4(); + } - const cameras = [ cameraL, cameraR ]; + } else { - const cameraXR = new ArrayCamera(); - cameraXR.layers.enable( 1 ); - cameraXR.layers.enable( 2 ); + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - let _currentDepthNear = null; - let _currentDepthFar = null; + renderTargetProperties.__webglFramebuffer = []; - // + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - this.cameraAutoUpdate = true; - this.enabled = false; + renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); - this.isPresenting = false; + } - this.getController = function ( index ) { + } else { - let controller = controllers[ index ]; + renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - if ( controller === undefined ) { + } - controller = new WebXRController(); - controllers[ index ] = controller; + if ( isMultipleRenderTargets ) { - } + for ( let i = 0, il = textures.length; i < il; i ++ ) { - return controller.getTargetRaySpace(); + const attachmentProperties = properties.get( textures[ i ] ); - }; + if ( attachmentProperties.__webglTexture === undefined ) { - this.getControllerGrip = function ( index ) { + attachmentProperties.__webglTexture = _gl.createTexture(); - let controller = controllers[ index ]; + info.memory.textures ++; - if ( controller === undefined ) { + } - controller = new WebXRController(); - controllers[ index ] = controller; + } } - return controller.getGripSpace(); + if ( ( renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - }; + renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); + renderTargetProperties.__webglColorRenderbuffer = []; - this.getHand = function ( index ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - let controller = controllers[ index ]; + for ( let i = 0; i < textures.length; i ++ ) { - if ( controller === undefined ) { + const texture = textures[ i ]; + renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - controller = new WebXRController(); - controllers[ index ] = controller; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - } + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); + const samples = getRenderTargetSamples( renderTarget ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - return controller.getHandSpace(); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - }; + } - // + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - function onSessionEvent( event ) { + if ( renderTarget.depthBuffer ) { - const controllerIndex = controllerInputSources.indexOf( event.inputSource ); + renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - if ( controllerIndex === - 1 ) { + } - return; + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - const controller = controllers[ controllerIndex ]; + } - if ( controller !== undefined ) { + // Setup color buffer - controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); - controller.dispatchEvent( { type: event.type, data: event.inputSource } ); + if ( isCube ) { - } + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - } + for ( let i = 0; i < 6; i ++ ) { - function onSessionEnd() { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - session.removeEventListener( 'select', onSessionEvent ); - session.removeEventListener( 'selectstart', onSessionEvent ); - session.removeEventListener( 'selectend', onSessionEvent ); - session.removeEventListener( 'squeeze', onSessionEvent ); - session.removeEventListener( 'squeezestart', onSessionEvent ); - session.removeEventListener( 'squeezeend', onSessionEvent ); - session.removeEventListener( 'end', onSessionEnd ); - session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - for ( let i = 0; i < controllers.length; i ++ ) { + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); - const inputSource = controllerInputSources[ i ]; + } - if ( inputSource === null ) continue; + } else { - controllerInputSources[ i ] = null; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); - controllers[ i ].disconnect( inputSource ); + } } - _currentDepthNear = null; - _currentDepthFar = null; + if ( textureNeedsGenerateMipmaps( texture ) ) { - depthSensing.reset(); + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - // restore framebuffer/rendering state + } - renderer.setRenderTarget( initialRenderTarget ); + state.unbindTexture(); - glBaseLayer = null; - glProjLayer = null; - glBinding = null; - session = null; - newRenderTarget = null; + } else if ( isMultipleRenderTargets ) { - // + for ( let i = 0, il = textures.length; i < il; i ++ ) { - animation.stop(); + const attachment = textures[ i ]; + const attachmentProperties = properties.get( attachment ); - scope.isPresenting = false; + let glTextureType = _gl.TEXTURE_2D; - renderer.setPixelRatio( currentPixelRatio ); - renderer.setSize( currentSize.width, currentSize.height, false ); + if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - scope.dispatchEvent( { type: 'sessionend' } ); + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - } + } - this.setFramebufferScaleFactor = function ( value ) { + state.bindTexture( glTextureType, attachmentProperties.__webglTexture ); + setTextureParameters( glTextureType, attachment ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, glTextureType, 0 ); - framebufferScaleFactor = value; + if ( textureNeedsGenerateMipmaps( attachment ) ) { - if ( scope.isPresenting === true ) { + generateMipmap( glTextureType ); - console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); + } } - }; + state.unbindTexture(); - this.setReferenceSpaceType = function ( value ) { + } else { - referenceSpaceType = value; + let glTextureType = _gl.TEXTURE_2D; - if ( scope.isPresenting === true ) { + if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; } - }; + state.bindTexture( glTextureType, textureProperties.__webglTexture ); + setTextureParameters( glTextureType, texture ); - this.getReferenceSpace = function () { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - return customReferenceSpace || referenceSpace; + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - }; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); - this.setReferenceSpace = function ( space ) { + } - customReferenceSpace = space; + } else { - }; - - this.getBaseLayer = function () { + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); - return glProjLayer !== null ? glProjLayer : glBaseLayer; + } - }; + if ( textureNeedsGenerateMipmaps( texture ) ) { - this.getBinding = function () { + generateMipmap( glTextureType ); - return glBinding; + } - }; + state.unbindTexture(); - this.getFrame = function () { + } - return xrFrame; + // Setup depth and stencil buffers - }; + if ( renderTarget.depthBuffer ) { - this.getSession = function () { + setupDepthRenderbuffer( renderTarget ); - return session; + } - }; + } - this.setSession = async function ( value ) { + function updateRenderTargetMipmap( renderTarget ) { - session = value; + const textures = renderTarget.textures; - if ( session !== null ) { + for ( let i = 0, il = textures.length; i < il; i ++ ) { - initialRenderTarget = renderer.getRenderTarget(); + const texture = textures[ i ]; - session.addEventListener( 'select', onSessionEvent ); - session.addEventListener( 'selectstart', onSessionEvent ); - session.addEventListener( 'selectend', onSessionEvent ); - session.addEventListener( 'squeeze', onSessionEvent ); - session.addEventListener( 'squeezestart', onSessionEvent ); - session.addEventListener( 'squeezeend', onSessionEvent ); - session.addEventListener( 'end', onSessionEnd ); - session.addEventListener( 'inputsourceschange', onInputSourcesChange ); + if ( textureNeedsGenerateMipmaps( texture ) ) { - if ( attributes.xrCompatible !== true ) { + const targetType = getTargetType( renderTarget ); + const webglTexture = properties.get( texture ).__webglTexture; - await gl.makeXRCompatible(); + state.bindTexture( targetType, webglTexture ); + generateMipmap( targetType ); + state.unbindTexture(); - } + } - currentPixelRatio = renderer.getPixelRatio(); - renderer.getSize( currentSize ); + } - if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { + } - const layerInit = { - antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, - alpha: true, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; + const invalidationArrayRead = []; + const invalidationArrayDraw = []; - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); + function updateMultisampleRenderTarget( renderTarget ) { - session.updateRenderState( { baseLayer: glBaseLayer } ); + if ( renderTarget.samples > 0 ) { - renderer.setPixelRatio( 1 ); - renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); + if ( useMultisampledRTT( renderTarget ) === false ) { - newRenderTarget = new WebGLRenderTarget( - glBaseLayer.framebufferWidth, - glBaseLayer.framebufferHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil - } - ); + const textures = renderTarget.textures; + const width = renderTarget.width; + const height = renderTarget.height; + let mask = _gl.COLOR_BUFFER_BIT; + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderTargetProperties = properties.get( renderTarget ); + const isMultipleRenderTargets = ( textures.length > 1 ); - } else { + // If MRT we need to remove FBO attachments + if ( isMultipleRenderTargets ) { - let depthFormat = null; - let depthType = null; - let glDepthFormat = null; + for ( let i = 0; i < textures.length; i ++ ) { - if ( attributes.depth ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; - depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; - depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); } - const projectionlayerInit = { - colorFormat: gl.RGBA8, - depthFormat: glDepthFormat, - scaleFactor: framebufferScaleFactor - }; + } - glBinding = new XRWebGLBinding( session, gl ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); + const mipmaps = renderTarget.texture.mipmaps; - session.updateRenderState( { layers: [ glProjLayer ] } ); + if ( mipmaps && mipmaps.length > 0 ) { - renderer.setPixelRatio( 1 ); - renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 - } ); + } else { - const renderTargetProperties = renderer.properties.get( newRenderTarget ); - renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); } - newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - - this.setFoveation( foveation ); - - customReferenceSpace = null; - referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - - animation.setContext( session ); - animation.start(); - - scope.isPresenting = true; - - scope.dispatchEvent( { type: 'sessionstart' } ); - - } + for ( let i = 0; i < textures.length; i ++ ) { - }; + if ( renderTarget.resolveDepthBuffer ) { - this.getEnvironmentBlendMode = function () { + if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( session !== null ) { + // resolving stencil is slow with a D3D backend. disable it for all transmission render targets (see #27799) - return session.environmentBlendMode; + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - } + } - }; + if ( isMultipleRenderTargets ) { - function onInputSourcesChange( event ) { + _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - // Notify disconnected + const webglTexture = properties.get( textures[ i ] ).__webglTexture; + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - for ( let i = 0; i < event.removed.length; i ++ ) { + } - const inputSource = event.removed[ i ]; - const index = controllerInputSources.indexOf( inputSource ); + _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - if ( index >= 0 ) { + if ( supportsInvalidateFramebuffer === true ) { - controllerInputSources[ index ] = null; - controllers[ index ].disconnect( inputSource ); + invalidationArrayRead.length = 0; + invalidationArrayDraw.length = 0; - } + invalidationArrayRead.push( _gl.COLOR_ATTACHMENT0 + i ); - } + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false ) { - // Notify connected + invalidationArrayRead.push( depthStyle ); + invalidationArrayDraw.push( depthStyle ); - for ( let i = 0; i < event.added.length; i ++ ) { + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, invalidationArrayDraw ); - const inputSource = event.added[ i ]; + } - let controllerIndex = controllerInputSources.indexOf( inputSource ); + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArrayRead ); - if ( controllerIndex === - 1 ) { + } - // Assign input source a controller that currently has no input source + } - for ( let i = 0; i < controllers.length; i ++ ) { + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - if ( i >= controllerInputSources.length ) { + // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments + if ( isMultipleRenderTargets ) { - controllerInputSources.push( inputSource ); - controllerIndex = i; - break; + for ( let i = 0; i < textures.length; i ++ ) { - } else if ( controllerInputSources[ i ] === null ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - controllerInputSources[ i ] = inputSource; - controllerIndex = i; - break; + const webglTexture = properties.get( textures[ i ] ).__webglTexture; - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); } - // If all controllers do currently receive input we ignore new ones + } - if ( controllerIndex === - 1 ) break; + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - } + } else { - const controller = controllers[ controllerIndex ]; + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false && supportsInvalidateFramebuffer ) { - if ( controller ) { + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - controller.connect( inputSource ); + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); } @@ -40614,1226 +49854,1629 @@ class WebXRManager extends EventDispatcher { } - // + } - const cameraLPos = new Vector3(); - const cameraRPos = new Vector3(); + function getRenderTargetSamples( renderTarget ) { - /** - * Assumes 2 cameras that are parallel and share an X-axis, and that - * the cameras' projection and world matrices have already been set. - * And that near and far planes are identical for both cameras. - * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 - */ - function setProjectionFromUnion( camera, cameraL, cameraR ) { + return Math.min( capabilities.maxSamples, renderTarget.samples ); - cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); - cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); + } - const ipd = cameraLPos.distanceTo( cameraRPos ); + function useMultisampledRTT( renderTarget ) { - const projL = cameraL.projectionMatrix.elements; - const projR = cameraR.projectionMatrix.elements; + const renderTargetProperties = properties.get( renderTarget ); - // VR systems will have identical far and near planes, and - // most likely identical top and bottom frustum extents. - // Use the left camera for these values. - const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); - const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); - const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; - const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; + return renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; - const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; - const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; - const left = near * leftFov; - const right = near * rightFov; + } - // Calculate the new camera's position offset from the - // left camera. xOffset should be roughly half `ipd`. - const zOffset = ipd / ( - leftFov + rightFov ); - const xOffset = zOffset * - leftFov; + function updateVideoTexture( texture ) { - // TODO: Better way to apply this offset? - cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); - camera.translateX( xOffset ); - camera.translateZ( zOffset ); - camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + const frame = info.render.frame; + + // Check the last frame we updated the VideoTexture - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; + if ( _videoTextures.get( texture ) !== frame ) { - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + _videoTextures.set( texture, frame ); + texture.update(); } - function updateCamera( camera, parent ) { + } - if ( parent === null ) { + function verifyColorSpace( texture, image ) { - camera.matrixWorld.copy( camera.matrix ); + const colorSpace = texture.colorSpace; + const format = texture.format; + const type = texture.type; - } else { + if ( texture.isCompressedTexture === true || texture.isVideoTexture === true ) return image; - camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); + if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - } + // sRGB - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { - } + // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - this.updateCamera = function ( camera ) { + if ( format !== RGBAFormat || type !== UnsignedByteType ) { - if ( session === null ) return; + console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - if ( depthSensing.texture !== null ) { + } - camera.near = depthSensing.depthNear; - camera.far = depthSensing.depthFar; + } else { + + console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); } - cameraXR.near = cameraR.near = cameraL.near = camera.near; - cameraXR.far = cameraR.far = cameraL.far = camera.far; + } - if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { + return image; - // Note that the new renderState won't apply until the next frame. See #18320 + } - session.updateRenderState( { - depthNear: cameraXR.near, - depthFar: cameraXR.far - } ); + function getDimensions( image ) { - _currentDepthNear = cameraXR.near; - _currentDepthFar = cameraXR.far; + if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) { - cameraL.near = _currentDepthNear; - cameraL.far = _currentDepthFar; - cameraR.near = _currentDepthNear; - cameraR.far = _currentDepthFar; + // if intrinsic data are not available, fallback to width/height - cameraL.updateProjectionMatrix(); - cameraR.updateProjectionMatrix(); - camera.updateProjectionMatrix(); + _imageDimensions.width = image.naturalWidth || image.width; + _imageDimensions.height = image.naturalHeight || image.height; - } + } else if ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) { - const parent = camera.parent; - const cameras = cameraXR.cameras; + _imageDimensions.width = image.displayWidth; + _imageDimensions.height = image.displayHeight; - updateCamera( cameraXR, parent ); + } else { - for ( let i = 0; i < cameras.length; i ++ ) { + _imageDimensions.width = image.width; + _imageDimensions.height = image.height; - updateCamera( cameras[ i ], parent ); + } - } + return _imageDimensions; - // update projection matrix for proper view frustum culling + } - if ( cameras.length === 2 ) { + // - setProjectionFromUnion( cameraXR, cameraL, cameraR ); + this.allocateTextureUnit = allocateTextureUnit; + this.resetTextureUnits = resetTextureUnits; - } else { + this.setTexture2D = setTexture2D; + this.setTexture2DArray = setTexture2DArray; + this.setTexture3D = setTexture3D; + this.setTextureCube = setTextureCube; + this.rebindTextures = rebindTextures; + this.setupRenderTarget = setupRenderTarget; + this.updateRenderTargetMipmap = updateRenderTargetMipmap; + this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; + this.setupDepthRenderbuffer = setupDepthRenderbuffer; + this.setupFrameBufferTexture = setupFrameBufferTexture; + this.useMultisampledRTT = useMultisampledRTT; - // assume single camera setup (AR) +} - cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); +function WebGLUtils( gl, extensions ) { - } + function convert( p, colorSpace = NoColorSpace ) { - // update user camera and its children + let extension; - updateUserCamera( camera, cameraXR, parent ); + const transfer = ColorManagement.getTransfer( colorSpace ); - }; + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; + if ( p === UnsignedInt101111Type ) return gl.UNSIGNED_INT_10F_11F_11F_REV; - function updateUserCamera( camera, cameraXR, parent ) { + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + if ( p === HalfFloatType ) return gl.HALF_FLOAT; - if ( parent === null ) { + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - camera.matrix.copy( cameraXR.matrixWorld ); + // WebGL2 formats. - } else { + if ( p === RedFormat ) return gl.RED; + if ( p === RedIntegerFormat ) return gl.RED_INTEGER; + if ( p === RGFormat ) return gl.RG; + if ( p === RGIntegerFormat ) return gl.RG_INTEGER; + if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); + // S3TC - } + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); + if ( transfer === SRGBTransfer ) { - camera.projectionMatrix.copy( cameraXR.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); + extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - if ( camera.isPerspectiveCamera ) { + if ( extension !== null ) { - camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); - camera.zoom = 1; + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - } + } else { - } + return null; - this.getCamera = function () { + } - return cameraXR; + } else { - }; + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - this.getFoveation = function () { + if ( extension !== null ) { - if ( glProjLayer === null && glBaseLayer === null ) { + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - return undefined; + } else { - } + return null; - return foveation; + } - }; + } - this.setFoveation = function ( value ) { + } - // 0 = no foveation = full resolution - // 1 = maximum foveation = the edges render at lower resolution + // PVRTC - foveation = value; + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - if ( glProjLayer !== null ) { + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - glProjLayer.fixedFoveation = value; + if ( extension !== null ) { - } + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { + } else { - glBaseLayer.fixedFoveation = value; + return null; } - }; - - this.hasDepthSensing = function () { + } - return depthSensing.texture !== null; + // ETC - }; + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { - // Animation Loop + extension = extensions.get( 'WEBGL_compressed_texture_etc' ); - let onAnimationFrameCallback = null; + if ( extension !== null ) { - function onAnimationFrame( time, frame ) { + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); - xrFrame = frame; + } else { - if ( pose !== null ) { + return null; - const views = pose.views; + } - if ( glBaseLayer !== null ) { + } - renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); - renderer.setRenderTarget( newRenderTarget ); + // ASTC - } + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { - let cameraXRNeedsUpdate = false; + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - // check if it's necessary to rebuild cameraXR's camera list + if ( extension !== null ) { - if ( views.length !== cameraXR.cameras.length ) { + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - cameraXR.cameras.length = 0; - cameraXRNeedsUpdate = true; + } else { - } + return null; - for ( let i = 0; i < views.length; i ++ ) { + } - const view = views[ i ]; + } - let viewport = null; + // BPTC - if ( glBaseLayer !== null ) { + if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { - viewport = glBaseLayer.getViewport( view ); + extension = extensions.get( 'EXT_texture_compression_bptc' ); - } else { + if ( extension !== null ) { - const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); - viewport = glSubImage.viewport; + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; + if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; - // For side-by-side projection, we only produce a single texture for both eyes. - if ( i === 0 ) { + } else { - renderer.setRenderTargetTextures( - newRenderTarget, - glSubImage.colorTexture, - glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); + return null; - renderer.setRenderTarget( newRenderTarget ); + } - } + } - } + // RGTC - let camera = cameras[ i ]; + if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - if ( camera === undefined ) { + extension = extensions.get( 'EXT_texture_compression_rgtc' ); - camera = new PerspectiveCamera(); - camera.layers.enable( i ); - camera.viewport = new Vector4(); - cameras[ i ] = camera; + if ( extension !== null ) { - } + if ( p === RED_RGTC1_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; + if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; + if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - camera.matrix.fromArray( view.transform.matrix ); - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.projectionMatrix.fromArray( view.projectionMatrix ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); + } else { - if ( i === 0 ) { + return null; - cameraXR.matrix.copy( camera.matrix ); - cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); + } - } + } - if ( cameraXRNeedsUpdate === true ) { + // - cameraXR.cameras.push( camera ); + if ( p === UnsignedInt248Type ) return gl.UNSIGNED_INT_24_8; - } + // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - } + return ( gl[ p ] !== undefined ) ? gl[ p ] : null; - // + } - const enabledFeatures = session.enabledFeatures; + return { convert: convert }; - if ( enabledFeatures && enabledFeatures.includes( 'depth-sensing' ) ) { +} - const depthData = glBinding.getDepthInformation( views[ 0 ] ); +/** + * This type of camera can be used in order to efficiently render a scene with a + * predefined set of cameras. This is an important performance aspect for + * rendering VR scenes. + * + * An instance of `ArrayCamera` always has an array of sub cameras. It's mandatory + * to define for each sub camera the `viewport` property which determines the + * part of the viewport that is rendered with this camera. + * + * @augments PerspectiveCamera + */ +class ArrayCamera extends PerspectiveCamera { - if ( depthData && depthData.isValid && depthData.texture ) { - - depthSensing.init( renderer, depthData, session.renderState ); + /** + * Constructs a new array camera. + * + * @param {Array} [array=[]] - An array of perspective sub cameras. + */ + constructor( array = [] ) { - } + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayCamera = true; - } + /** + * Whether this camera is used with multiview rendering or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isMultiViewCamera = false; - // + /** + * An array of perspective sub cameras. + * + * @type {Array} + */ + this.cameras = array; - for ( let i = 0; i < controllers.length; i ++ ) { + } - const inputSource = controllerInputSources[ i ]; - const controller = controllers[ i ]; +} - if ( inputSource !== null && controller !== undefined ) { +/** + * This is almost identical to an {@link Object3D}. Its purpose is to + * make working with groups of objects syntactically clearer. + * + * ```js + * // Create a group and add the two cubes. + * // These cubes can now be rotated / scaled etc as a group. + * const group = new THREE.Group(); + * + * group.add( meshA ); + * group.add( meshB ); + * + * scene.add( group ); + * ``` + * + * @augments Object3D + */ +let Group$1 = class Group extends Object3D { - controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); + constructor() { - } + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isGroup = true; - depthSensing.render( renderer, cameraXR ); + this.type = 'Group'; - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); + } - if ( frame.detectedPlanes ) { +}; - scope.dispatchEvent( { type: 'planesdetected', data: frame } ); +const _moveEvent = { type: 'move' }; - } +/** + * Class for representing a XR controller with its + * different coordinate systems. + * + * @private + */ +class WebXRController { - xrFrame = null; + /** + * Constructs a new XR controller. + */ + constructor() { - } + /** + * A group representing the target ray space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._targetRay = null; - const animation = new WebGLAnimation(); + /** + * A group representing the grip space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._grip = null; - animation.setAnimationLoop( onAnimationFrame ); + /** + * A group representing the hand space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._hand = null; - this.setAnimationLoop = function ( callback ) { + } - onAnimationFrameCallback = callback; + /** + * Returns a group representing the hand space of the XR controller. + * + * @return {Group} A group representing the hand space of the XR controller. + */ + getHandSpace() { - }; + if ( this._hand === null ) { - this.dispose = function () {}; + this._hand = new Group$1(); + this._hand.matrixAutoUpdate = false; + this._hand.visible = false; - } + this._hand.joints = {}; + this._hand.inputState = { pinching: false }; -} + } -const _e1 = /*@__PURE__*/ new Euler(); -const _m1 = /*@__PURE__*/ new Matrix4(); + return this._hand; -function WebGLMaterials( renderer, properties ) { + } - function refreshTransformUniform( map, uniform ) { + /** + * Returns a group representing the target ray space of the XR controller. + * + * @return {Group} A group representing the target ray space of the XR controller. + */ + getTargetRaySpace() { - if ( map.matrixAutoUpdate === true ) { + if ( this._targetRay === null ) { - map.updateMatrix(); + this._targetRay = new Group$1(); + this._targetRay.matrixAutoUpdate = false; + this._targetRay.visible = false; + this._targetRay.hasLinearVelocity = false; + this._targetRay.linearVelocity = new Vector3(); + this._targetRay.hasAngularVelocity = false; + this._targetRay.angularVelocity = new Vector3(); } - uniform.value.copy( map.matrix ); + return this._targetRay; } - function refreshFogUniforms( uniforms, fog ) { - - fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - - if ( fog.isFog ) { - - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; + /** + * Returns a group representing the grip space of the XR controller. + * + * @return {Group} A group representing the grip space of the XR controller. + */ + getGripSpace() { - } else if ( fog.isFogExp2 ) { + if ( this._grip === null ) { - uniforms.fogDensity.value = fog.density; + this._grip = new Group$1(); + this._grip.matrixAutoUpdate = false; + this._grip.visible = false; + this._grip.hasLinearVelocity = false; + this._grip.linearVelocity = new Vector3(); + this._grip.hasAngularVelocity = false; + this._grip.angularVelocity = new Vector3(); } - } + return this._grip; - function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { + } - if ( material.isMeshBasicMaterial ) { + /** + * Dispatches the given event to the groups representing + * the different coordinate spaces of the XR controller. + * + * @param {Object} event - The event to dispatch. + * @return {WebXRController} A reference to this instance. + */ + dispatchEvent( event ) { - refreshUniformsCommon( uniforms, material ); + if ( this._targetRay !== null ) { - } else if ( material.isMeshLambertMaterial ) { + this._targetRay.dispatchEvent( event ); - refreshUniformsCommon( uniforms, material ); + } - } else if ( material.isMeshToonMaterial ) { + if ( this._grip !== null ) { - refreshUniformsCommon( uniforms, material ); - refreshUniformsToon( uniforms, material ); + this._grip.dispatchEvent( event ); - } else if ( material.isMeshPhongMaterial ) { + } - refreshUniformsCommon( uniforms, material ); - refreshUniformsPhong( uniforms, material ); + if ( this._hand !== null ) { - } else if ( material.isMeshStandardMaterial ) { + this._hand.dispatchEvent( event ); - refreshUniformsCommon( uniforms, material ); - refreshUniformsStandard( uniforms, material ); + } - if ( material.isMeshPhysicalMaterial ) { + return this; - refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); + } - } + /** + * Connects the controller with the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + connect( inputSource ) { - } else if ( material.isMeshMatcapMaterial ) { + if ( inputSource && inputSource.hand ) { - refreshUniformsCommon( uniforms, material ); - refreshUniformsMatcap( uniforms, material ); + const hand = this._hand; - } else if ( material.isMeshDepthMaterial ) { + if ( hand ) { - refreshUniformsCommon( uniforms, material ); + for ( const inputjoint of inputSource.hand.values() ) { - } else if ( material.isMeshDistanceMaterial ) { + // Initialize hand with joints when connected + this._getHandJoint( hand, inputjoint ); - refreshUniformsCommon( uniforms, material ); - refreshUniformsDistance( uniforms, material ); + } - } else if ( material.isMeshNormalMaterial ) { + } - refreshUniformsCommon( uniforms, material ); + } - } else if ( material.isLineBasicMaterial ) { + this.dispatchEvent( { type: 'connected', data: inputSource } ); - refreshUniformsLine( uniforms, material ); + return this; - if ( material.isLineDashedMaterial ) { + } - refreshUniformsDash( uniforms, material ); + /** + * Disconnects the controller from the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + disconnect( inputSource ) { - } + this.dispatchEvent( { type: 'disconnected', data: inputSource } ); - } else if ( material.isPointsMaterial ) { + if ( this._targetRay !== null ) { - refreshUniformsPoints( uniforms, material, pixelRatio, height ); + this._targetRay.visible = false; - } else if ( material.isSpriteMaterial ) { + } - refreshUniformsSprites( uniforms, material ); + if ( this._grip !== null ) { - } else if ( material.isShadowMaterial ) { + this._grip.visible = false; - uniforms.color.value.copy( material.color ); - uniforms.opacity.value = material.opacity; + } - } else if ( material.isShaderMaterial ) { + if ( this._hand !== null ) { - material.uniformsNeedUpdate = false; // #15581 + this._hand.visible = false; } + return this; + } - function refreshUniformsCommon( uniforms, material ) { + /** + * Updates the controller with the given input source, XR frame and reference space. + * This updates the transformations of the groups that represent the different + * coordinate systems of the controller. + * + * @param {XRInputSource} inputSource - The input source. + * @param {XRFrame} frame - The XR frame. + * @param {XRReferenceSpace} referenceSpace - The reference space. + * @return {WebXRController} A reference to this instance. + */ + update( inputSource, frame, referenceSpace ) { - uniforms.opacity.value = material.opacity; + let inputPose = null; + let gripPose = null; + let handPose = null; - if ( material.color ) { + const targetRay = this._targetRay; + const grip = this._grip; + const hand = this._hand; - uniforms.diffuse.value.copy( material.color ); + if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { - } + if ( hand && inputSource.hand ) { - if ( material.emissive ) { + handPose = true; - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + for ( const inputjoint of inputSource.hand.values() ) { - } + // Update the joints groups with the XRJoint poses + const jointPose = frame.getJointPose( inputjoint, referenceSpace ); - if ( material.map ) { + // The transform of this joint will be updated with the joint pose on each frame + const joint = this._getHandJoint( hand, inputjoint ); - uniforms.map.value = material.map; + if ( jointPose !== null ) { - refreshTransformUniform( material.map, uniforms.mapTransform ); + joint.matrix.fromArray( jointPose.transform.matrix ); + joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); + joint.matrixWorldNeedsUpdate = true; + joint.jointRadius = jointPose.radius; - } + } - if ( material.alphaMap ) { + joint.visible = jointPose !== null; - uniforms.alphaMap.value = material.alphaMap; + } - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); + // Custom events - } + // Check pinchz + const indexTip = hand.joints[ 'index-finger-tip' ]; + const thumbTip = hand.joints[ 'thumb-tip' ]; + const distance = indexTip.position.distanceTo( thumbTip.position ); - if ( material.bumpMap ) { + const distanceToPinch = 0.02; + const threshold = 0.005; - uniforms.bumpMap.value = material.bumpMap; + if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { - refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); + hand.inputState.pinching = false; + this.dispatchEvent( { + type: 'pinchend', + handedness: inputSource.handedness, + target: this + } ); - uniforms.bumpScale.value = material.bumpScale; + } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { - if ( material.side === BackSide ) { + hand.inputState.pinching = true; + this.dispatchEvent( { + type: 'pinchstart', + handedness: inputSource.handedness, + target: this + } ); - uniforms.bumpScale.value *= - 1; + } - } + } else { - } + if ( grip !== null && inputSource.gripSpace ) { - if ( material.normalMap ) { + gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); - uniforms.normalMap.value = material.normalMap; + if ( gripPose !== null ) { - refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); + grip.matrix.fromArray( gripPose.transform.matrix ); + grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); + grip.matrixWorldNeedsUpdate = true; - uniforms.normalScale.value.copy( material.normalScale ); + if ( gripPose.linearVelocity ) { - if ( material.side === BackSide ) { + grip.hasLinearVelocity = true; + grip.linearVelocity.copy( gripPose.linearVelocity ); - uniforms.normalScale.value.negate(); + } else { - } + grip.hasLinearVelocity = false; - } + } - if ( material.displacementMap ) { + if ( gripPose.angularVelocity ) { - uniforms.displacementMap.value = material.displacementMap; + grip.hasAngularVelocity = true; + grip.angularVelocity.copy( gripPose.angularVelocity ); - refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); + } else { - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; + grip.hasAngularVelocity = false; - } + } - if ( material.emissiveMap ) { + } - uniforms.emissiveMap.value = material.emissiveMap; + } - refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); + } - } + if ( targetRay !== null ) { - if ( material.specularMap ) { + inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); - uniforms.specularMap.value = material.specularMap; + // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it + if ( inputPose === null && gripPose !== null ) { - refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); + inputPose = gripPose; - } + } - if ( material.alphaTest > 0 ) { + if ( inputPose !== null ) { - uniforms.alphaTest.value = material.alphaTest; + targetRay.matrix.fromArray( inputPose.transform.matrix ); + targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); + targetRay.matrixWorldNeedsUpdate = true; - } + if ( inputPose.linearVelocity ) { - const materialProperties = properties.get( material ); + targetRay.hasLinearVelocity = true; + targetRay.linearVelocity.copy( inputPose.linearVelocity ); - const envMap = materialProperties.envMap; - const envMapRotation = materialProperties.envMapRotation; + } else { - if ( envMap ) { + targetRay.hasLinearVelocity = false; - uniforms.envMap.value = envMap; + } - _e1.copy( envMapRotation ); + if ( inputPose.angularVelocity ) { - // accommodate left-handed frame - _e1.x *= - 1; _e1.y *= - 1; _e1.z *= - 1; + targetRay.hasAngularVelocity = true; + targetRay.angularVelocity.copy( inputPose.angularVelocity ); - if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { + } else { - // environment maps which are not cube render targets or PMREMs follow a different convention - _e1.y *= - 1; - _e1.z *= - 1; + targetRay.hasAngularVelocity = false; - } + } - uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( _e1 ) ); + this.dispatchEvent( _moveEvent ); - uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + } + + } - uniforms.reflectivity.value = material.reflectivity; - uniforms.ior.value = material.ior; - uniforms.refractionRatio.value = material.refractionRatio; } - if ( material.lightMap ) { + if ( targetRay !== null ) { - uniforms.lightMap.value = material.lightMap; + targetRay.visible = ( inputPose !== null ); - // artist-friendly light intensity scaling factor - const scaleFactor = ( renderer._useLegacyLights === true ) ? Math.PI : 1; + } - uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; + if ( grip !== null ) { - refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); + grip.visible = ( gripPose !== null ); } - if ( material.aoMap ) { - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; + if ( hand !== null ) { - refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); + hand.visible = ( handPose !== null ); } - } + return this; - function refreshUniformsLine( uniforms, material ) { + } - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; + /** + * Returns a group representing the hand joint for the given input joint. + * + * @private + * @param {Group} hand - The group representing the hand space. + * @param {XRJointSpace} inputjoint - The hand joint data. + * @return {Group} A group representing the hand joint for the given input joint. + */ + _getHandJoint( hand, inputjoint ) { - if ( material.map ) { + if ( hand.joints[ inputjoint.jointName ] === undefined ) { - uniforms.map.value = material.map; + const joint = new Group$1(); + joint.matrixAutoUpdate = false; + joint.visible = false; + hand.joints[ inputjoint.jointName ] = joint; - refreshTransformUniform( material.map, uniforms.mapTransform ); + hand.add( joint ); } - } - - function refreshUniformsDash( uniforms, material ) { - - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; + return hand.joints[ inputjoint.jointName ]; } - function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * pixelRatio; - uniforms.scale.value = height * 0.5; - - if ( material.map ) { +} - uniforms.map.value = material.map; +/** + * Represents a texture created externally with the same renderer context. + * + * This may be a texture from a protected media stream, device camera feed, + * or other data feeds like a depth sensor. + * + * Note that this class is only supported in {@link WebGLRenderer}, and in + * the {@link WebGPURenderer} WebGPU backend. + * + * @augments Texture + */ +class ExternalTexture extends Texture { - refreshTransformUniform( material.map, uniforms.uvTransform ); + /** + * Creates a new raw texture. + * + * @param {?(WebGLTexture|GPUTexture)} [sourceTexture=null] - The external texture. + */ + constructor( sourceTexture = null ) { - } + super(); - if ( material.alphaMap ) { + /** + * The external source texture. + * + * @type {?(WebGLTexture|GPUTexture)} + * @default null + */ + this.sourceTexture = sourceTexture; - uniforms.alphaMap.value = material.alphaMap; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isExternalTexture = true; - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); + } - } + copy( source ) { - if ( material.alphaTest > 0 ) { + super.copy( source ); - uniforms.alphaTest.value = material.alphaTest; + this.sourceTexture = source.sourceTexture; - } + return this; } - function refreshUniformsSprites( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.rotation.value = material.rotation; - - if ( material.map ) { +} - uniforms.map.value = material.map; +const _occlusion_vertex = ` +void main() { - refreshTransformUniform( material.map, uniforms.mapTransform ); + gl_Position = vec4( position, 1.0 ); - } +}`; - if ( material.alphaMap ) { +const _occlusion_fragment = ` +uniform sampler2DArray depthColor; +uniform float depthWidth; +uniform float depthHeight; - uniforms.alphaMap.value = material.alphaMap; +void main() { - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); + vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); - } + if ( coord.x >= 1.0 ) { - if ( material.alphaTest > 0 ) { + gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; - uniforms.alphaTest.value = material.alphaTest; + } else { - } + gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; } - function refreshUniformsPhong( uniforms, material ) { +}`; - uniforms.specular.value.copy( material.specular ); - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) +/** + * A XR module that manages the access to the Depth Sensing API. + */ +class WebXRDepthSensing { - } + /** + * Constructs a new depth sensing module. + */ + constructor() { - function refreshUniformsToon( uniforms, material ) { + /** + * An opaque texture representing the depth of the user's environment. + * + * @type {?ExternalTexture} + */ + this.texture = null; - if ( material.gradientMap ) { + /** + * A plane mesh for visualizing the depth texture. + * + * @type {?Mesh} + */ + this.mesh = null; - uniforms.gradientMap.value = material.gradientMap; + /** + * The depth near value. + * + * @type {number} + */ + this.depthNear = 0; - } + /** + * The depth near far. + * + * @type {number} + */ + this.depthFar = 0; } - function refreshUniformsStandard( uniforms, material ) { + /** + * Inits the depth sensing module + * + * @param {XRWebGLDepthInformation} depthData - The XR depth data. + * @param {XRRenderState} renderState - The XR render state. + */ + init( depthData, renderState ) { - uniforms.metalness.value = material.metalness; + if ( this.texture === null ) { - if ( material.metalnessMap ) { + const texture = new ExternalTexture( depthData.texture ); - uniforms.metalnessMap.value = material.metalnessMap; + if ( ( depthData.depthNear !== renderState.depthNear ) || ( depthData.depthFar !== renderState.depthFar ) ) { - refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); + this.depthNear = depthData.depthNear; + this.depthFar = depthData.depthFar; - } + } - uniforms.roughness.value = material.roughness; + this.texture = texture; - if ( material.roughnessMap ) { + } - uniforms.roughnessMap.value = material.roughnessMap; + } - refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); + /** + * Returns a plane mesh that visualizes the depth texture. + * + * @param {ArrayCamera} cameraXR - The XR camera. + * @return {?Mesh} The plane mesh. + */ + getMesh( cameraXR ) { - } + if ( this.texture !== null ) { - const envMap = properties.get( material ).envMap; + if ( this.mesh === null ) { - if ( envMap ) { + const viewport = cameraXR.cameras[ 0 ].viewport; + const material = new ShaderMaterial( { + vertexShader: _occlusion_vertex, + fragmentShader: _occlusion_fragment, + uniforms: { + depthColor: { value: this.texture }, + depthWidth: { value: viewport.z }, + depthHeight: { value: viewport.w } + } + } ); - //uniforms.envMap.value = material.envMap; // part of uniforms common - uniforms.envMapIntensity.value = material.envMapIntensity; + this.mesh = new Mesh( new PlaneGeometry( 20, 20 ), material ); + + } } - } + return this.mesh; - function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { + } - uniforms.ior.value = material.ior; // also part of uniforms common + /** + * Resets the module + */ + reset() { - if ( material.sheen > 0 ) { + this.texture = null; + this.mesh = null; - uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); + } - uniforms.sheenRoughness.value = material.sheenRoughness; + /** + * Returns a texture representing the depth of the user's environment. + * + * @return {?ExternalTexture} The depth texture. + */ + getDepthTexture() { - if ( material.sheenColorMap ) { + return this.texture; - uniforms.sheenColorMap.value = material.sheenColorMap; + } - refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); +} - } +/** + * This class represents an abstraction of the WebXR Device API and is + * internally used by {@link WebGLRenderer}. `WebXRManager` also provides a public + * interface that allows users to enable/disable XR and perform XR related + * tasks like for instance retrieving controllers. + * + * @augments EventDispatcher + * @hideconstructor + */ +class WebXRManager extends EventDispatcher { - if ( material.sheenRoughnessMap ) { + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGL2RenderingContext} gl - The rendering context. + */ + constructor( renderer, gl ) { - uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; + super(); - refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); + const scope = this; - } + let session = null; - } + let framebufferScaleFactor = 1.0; - if ( material.clearcoat > 0 ) { + let referenceSpace = null; + let referenceSpaceType = 'local-floor'; + // Set default foveation to maximum. + let foveation = 1.0; + let customReferenceSpace = null; - uniforms.clearcoat.value = material.clearcoat; - uniforms.clearcoatRoughness.value = material.clearcoatRoughness; + let pose = null; + let glBinding = null; + let glProjLayer = null; + let glBaseLayer = null; + let xrFrame = null; - if ( material.clearcoatMap ) { + const supportsGlBinding = typeof XRWebGLBinding !== 'undefined'; - uniforms.clearcoatMap.value = material.clearcoatMap; + const depthSensing = new WebXRDepthSensing(); + const cameraAccessTextures = {}; + const attributes = gl.getContextAttributes(); - refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); + let initialRenderTarget = null; + let newRenderTarget = null; - } + const controllers = []; + const controllerInputSources = []; - if ( material.clearcoatRoughnessMap ) { + const currentSize = new Vector2(); + let currentPixelRatio = null; - uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; + // - refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); + const cameraL = new PerspectiveCamera(); + cameraL.viewport = new Vector4(); - } + const cameraR = new PerspectiveCamera(); + cameraR.viewport = new Vector4(); - if ( material.clearcoatNormalMap ) { + const cameras = [ cameraL, cameraR ]; - uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; + const cameraXR = new ArrayCamera(); - refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); + let _currentDepthNear = null; + let _currentDepthFar = null; - uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); + // - if ( material.side === BackSide ) { + /** + * Whether the manager's XR camera should be automatically updated or not. + * + * @type {boolean} + * @default true + */ + this.cameraAutoUpdate = true; - uniforms.clearcoatNormalScale.value.negate(); + /** + * This flag notifies the renderer to be ready for XR rendering. Set it to `true` + * if you are going to use XR in your app. + * + * @type {boolean} + * @default false + */ + this.enabled = false; - } + /** + * Whether XR presentation is active or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isPresenting = false; - } + /** + * Returns a group representing the `target ray` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `target ray` space. + */ + this.getController = function ( index ) { - } + let controller = controllers[ index ]; - if ( material.iridescence > 0 ) { + if ( controller === undefined ) { - uniforms.iridescence.value = material.iridescence; - uniforms.iridescenceIOR.value = material.iridescenceIOR; - uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; - uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; + controller = new WebXRController(); + controllers[ index ] = controller; - if ( material.iridescenceMap ) { + } - uniforms.iridescenceMap.value = material.iridescenceMap; + return controller.getTargetRaySpace(); - refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); + }; - } + /** + * Returns a group representing the `grip` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * Note: If you want to show something in the user's hand AND offer a + * pointing ray at the same time, you'll want to attached the handheld object + * to the group returned by `getControllerGrip()` and the ray to the + * group returned by `getController()`. The idea is to have two + * different groups in two different coordinate spaces for the same WebXR + * controller. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `grip` space. + */ + this.getControllerGrip = function ( index ) { - if ( material.iridescenceThicknessMap ) { + let controller = controllers[ index ]; - uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; + if ( controller === undefined ) { - refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); + controller = new WebXRController(); + controllers[ index ] = controller; } - } + return controller.getGripSpace(); - if ( material.transmission > 0 ) { + }; - uniforms.transmission.value = material.transmission; - uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; - uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); + /** + * Returns a group representing the `hand` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `hand` space. + */ + this.getHand = function ( index ) { - if ( material.transmissionMap ) { + let controller = controllers[ index ]; - uniforms.transmissionMap.value = material.transmissionMap; + if ( controller === undefined ) { - refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); + controller = new WebXRController(); + controllers[ index ] = controller; } - uniforms.thickness.value = material.thickness; - - if ( material.thicknessMap ) { + return controller.getHandSpace(); - uniforms.thicknessMap.value = material.thicknessMap; + }; - refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); + // - } + function onSessionEvent( event ) { - uniforms.attenuationDistance.value = material.attenuationDistance; - uniforms.attenuationColor.value.copy( material.attenuationColor ); + const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - } + if ( controllerIndex === -1 ) { - if ( material.anisotropy > 0 ) { + return; - uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); + } - if ( material.anisotropyMap ) { + const controller = controllers[ controllerIndex ]; - uniforms.anisotropyMap.value = material.anisotropyMap; + if ( controller !== undefined ) { - refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); + controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); + controller.dispatchEvent( { type: event.type, data: event.inputSource } ); } } - uniforms.specularIntensity.value = material.specularIntensity; - uniforms.specularColor.value.copy( material.specularColor ); - - if ( material.specularColorMap ) { + function onSessionEnd() { - uniforms.specularColorMap.value = material.specularColorMap; + session.removeEventListener( 'select', onSessionEvent ); + session.removeEventListener( 'selectstart', onSessionEvent ); + session.removeEventListener( 'selectend', onSessionEvent ); + session.removeEventListener( 'squeeze', onSessionEvent ); + session.removeEventListener( 'squeezestart', onSessionEvent ); + session.removeEventListener( 'squeezeend', onSessionEvent ); + session.removeEventListener( 'end', onSessionEnd ); + session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); + for ( let i = 0; i < controllers.length; i ++ ) { - } + const inputSource = controllerInputSources[ i ]; - if ( material.specularIntensityMap ) { + if ( inputSource === null ) continue; - uniforms.specularIntensityMap.value = material.specularIntensityMap; + controllerInputSources[ i ] = null; - refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); + controllers[ i ].disconnect( inputSource ); - } + } - } + _currentDepthNear = null; + _currentDepthFar = null; - function refreshUniformsMatcap( uniforms, material ) { + depthSensing.reset(); + for ( const key in cameraAccessTextures ) { - if ( material.matcap ) { + delete cameraAccessTextures[ key ]; - uniforms.matcap.value = material.matcap; + } - } + // restore framebuffer/rendering state - } + renderer.setRenderTarget( initialRenderTarget ); - function refreshUniformsDistance( uniforms, material ) { + glBaseLayer = null; + glProjLayer = null; + glBinding = null; + session = null; + newRenderTarget = null; - const light = properties.get( material ).light; + // - uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); - uniforms.nearDistance.value = light.shadow.camera.near; - uniforms.farDistance.value = light.shadow.camera.far; + animation.stop(); - } + scope.isPresenting = false; - return { - refreshFogUniforms: refreshFogUniforms, - refreshMaterialUniforms: refreshMaterialUniforms - }; + renderer.setPixelRatio( currentPixelRatio ); + renderer.setSize( currentSize.width, currentSize.height, false ); -} + scope.dispatchEvent( { type: 'sessionend' } ); -function WebGLUniformsGroups( gl, info, capabilities, state ) { + } - let buffers = {}; - let updateList = {}; - let allocatedBindingPoints = []; + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} value - The framebuffer scale factor. + */ + this.setFramebufferScaleFactor = function ( value ) { - const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program + framebufferScaleFactor = value; - function bind( uniformsGroup, program ) { + if ( scope.isPresenting === true ) { - const webglProgram = program.program; - state.uniformBlockBinding( uniformsGroup, webglProgram ); + console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); - } + } - function update( uniformsGroup, program ) { + }; - let buffer = buffers[ uniformsGroup.id ]; + /** + * Sets the reference space type. Can be used to configure a spatial relationship with the user's physical + * environment. Depending on how the user moves in 3D space, setting an appropriate reference space can + * improve tracking. Default is `local-floor`. Valid values can be found here + * https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace#reference_space_types. + * + * This method can not be used during a XR session. + * + * @param {string} value - The reference space type. + */ + this.setReferenceSpaceType = function ( value ) { - if ( buffer === undefined ) { + referenceSpaceType = value; - prepareUniformsGroup( uniformsGroup ); + if ( scope.isPresenting === true ) { - buffer = createBuffer( uniformsGroup ); - buffers[ uniformsGroup.id ] = buffer; + console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); - uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); + } - } + }; - // ensure to update the binding points/block indices mapping for this program + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ + this.getReferenceSpace = function () { - const webglProgram = program.program; - state.updateUBOMapping( uniformsGroup, webglProgram ); + return customReferenceSpace || referenceSpace; - // update UBO once per frame + }; - const frame = info.render.frame; + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ + this.setReferenceSpace = function ( space ) { - if ( updateList[ uniformsGroup.id ] !== frame ) { + customReferenceSpace = space; - updateBufferData( uniformsGroup ); + }; - updateList[ uniformsGroup.id ] = frame; + /** + * Returns the current base layer. + * + * This is an `XRProjectionLayer` when the targeted XR device supports the + * WebXR Layers API, or an `XRWebGLLayer` otherwise. + * + * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. + */ + this.getBaseLayer = function () { - } + return glProjLayer !== null ? glProjLayer : glBaseLayer; - } + }; - function createBuffer( uniformsGroup ) { + /** + * Returns the current XR binding. + * + * Creates a new binding if needed and the browser is + * capable of doing so. + * + * @return {?XRWebGLBinding} The XR binding. Returns `null` if one cannot be created. + */ + this.getBinding = function () { - // the setup of an UBO is independent of a particular shader program but global + if ( glBinding === null && supportsGlBinding ) { - const bindingPointIndex = allocateBindingPointIndex(); - uniformsGroup.__bindingPointIndex = bindingPointIndex; + glBinding = new XRWebGLBinding( session, gl ); - const buffer = gl.createBuffer(); - const size = uniformsGroup.__size; - const usage = uniformsGroup.usage; + } - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); + return glBinding; - return buffer; + }; - } + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ + this.getFrame = function () { - function allocateBindingPointIndex() { + return xrFrame; - for ( let i = 0; i < maxBindingPoints; i ++ ) { + }; - if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ + this.getSession = function () { - allocatedBindingPoints.push( i ); - return i; + return session; - } + }; - } + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} value - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ + this.setSession = async function ( value ) { - console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); + session = value; - return 0; + if ( session !== null ) { - } + initialRenderTarget = renderer.getRenderTarget(); - function updateBufferData( uniformsGroup ) { + session.addEventListener( 'select', onSessionEvent ); + session.addEventListener( 'selectstart', onSessionEvent ); + session.addEventListener( 'selectend', onSessionEvent ); + session.addEventListener( 'squeeze', onSessionEvent ); + session.addEventListener( 'squeezestart', onSessionEvent ); + session.addEventListener( 'squeezeend', onSessionEvent ); + session.addEventListener( 'end', onSessionEnd ); + session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - const buffer = buffers[ uniformsGroup.id ]; - const uniforms = uniformsGroup.uniforms; - const cache = uniformsGroup.__cache; + if ( attributes.xrCompatible !== true ) { - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + await gl.makeXRCompatible(); - for ( let i = 0, il = uniforms.length; i < il; i ++ ) { + } - const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; + currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( currentSize ); - for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - const uniform = uniformArray[ j ]; + // Check that the browser implements the necessary APIs to use an + // XRProjectionLayer rather than an XRWebGLLayer + const supportsLayers = supportsGlBinding && 'createProjectionLayer' in XRWebGLBinding.prototype; - if ( hasUniformChanged( uniform, i, j, cache ) === true ) { + if ( ! supportsLayers ) { - const offset = uniform.__offset; + const layerInit = { + antialias: attributes.antialias, + alpha: true, + depth: attributes.depth, + stencil: attributes.stencil, + framebufferScaleFactor: framebufferScaleFactor + }; - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; + glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - let arrayOffset = 0; + session.updateRenderState( { baseLayer: glBaseLayer } ); - for ( let k = 0; k < values.length; k ++ ) { + renderer.setPixelRatio( 1 ); + renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); - const value = values[ k ]; + newRenderTarget = new WebGLRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + stencilBuffer: attributes.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ) - const info = getUniformSize( value ); + } + ); - // TODO add integer and struct support - if ( typeof value === 'number' || typeof value === 'boolean' ) { + } else { - uniform.__data[ 0 ] = value; - gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); + let depthFormat = null; + let depthType = null; + let glDepthFormat = null; - } else if ( value.isMatrix3 ) { + if ( attributes.depth ) { - // manually converting 3x3 to 3x4 + glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; + depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - uniform.__data[ 0 ] = value.elements[ 0 ]; - uniform.__data[ 1 ] = value.elements[ 1 ]; - uniform.__data[ 2 ] = value.elements[ 2 ]; - uniform.__data[ 3 ] = 0; - uniform.__data[ 4 ] = value.elements[ 3 ]; - uniform.__data[ 5 ] = value.elements[ 4 ]; - uniform.__data[ 6 ] = value.elements[ 5 ]; - uniform.__data[ 7 ] = 0; - uniform.__data[ 8 ] = value.elements[ 6 ]; - uniform.__data[ 9 ] = value.elements[ 7 ]; - uniform.__data[ 10 ] = value.elements[ 8 ]; - uniform.__data[ 11 ] = 0; + } - } else { + const projectionlayerInit = { + colorFormat: gl.RGBA8, + depthFormat: glDepthFormat, + scaleFactor: framebufferScaleFactor + }; - value.toArray( uniform.__data, arrayOffset ); + glBinding = this.getBinding(); - arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; + glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - } + session.updateRenderState( { layers: [ glProjLayer ] } ); - } + renderer.setPixelRatio( 1 ); + renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); - gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); + newRenderTarget = new WebGLRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + colorSpace: renderer.outputColorSpace, + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ) + } ); } - } - - } + newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + this.setFoveation( foveation ); - } + customReferenceSpace = null; + referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - function hasUniformChanged( uniform, index, indexArray, cache ) { + animation.setContext( session ); + animation.start(); - const value = uniform.value; - const indexString = index + '_' + indexArray; + scope.isPresenting = true; - if ( cache[ indexString ] === undefined ) { + scope.dispatchEvent( { type: 'sessionstart' } ); - // cache entry does not exist so far + } - if ( typeof value === 'number' || typeof value === 'boolean' ) { + }; - cache[ indexString ] = value; + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ + this.getEnvironmentBlendMode = function () { - } else { + if ( session !== null ) { - cache[ indexString ] = value.clone(); + return session.environmentBlendMode; } - return true; + }; - } else { + /** + * Returns the current depth texture computed via depth sensing. + * + * See {@link WebXRDepthSensing#getDepthTexture}. + * + * @return {?Texture} The depth texture. + */ + this.getDepthTexture = function () { - const cachedObject = cache[ indexString ]; + return depthSensing.getDepthTexture(); - // compare current value with cached entry + }; - if ( typeof value === 'number' || typeof value === 'boolean' ) { - - if ( cachedObject !== value ) { + function onInputSourcesChange( event ) { - cache[ indexString ] = value; - return true; + // Notify disconnected - } + for ( let i = 0; i < event.removed.length; i ++ ) { - } else { + const inputSource = event.removed[ i ]; + const index = controllerInputSources.indexOf( inputSource ); - if ( cachedObject.equals( value ) === false ) { + if ( index >= 0 ) { - cachedObject.copy( value ); - return true; + controllerInputSources[ index ] = null; + controllers[ index ].disconnect( inputSource ); } } - } - - return false; - - } - - function prepareUniformsGroup( uniformsGroup ) { - - // determine total buffer size according to the STD140 layout - // Hint: STD140 is the only supported layout in WebGL 2 - - const uniforms = uniformsGroup.uniforms; - - let offset = 0; // global buffer offset in bytes - const chunkSize = 16; // size of a chunk in bytes + // Notify connected - for ( let i = 0, l = uniforms.length; i < l; i ++ ) { + for ( let i = 0; i < event.added.length; i ++ ) { - const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; + const inputSource = event.added[ i ]; - for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { + let controllerIndex = controllerInputSources.indexOf( inputSource ); - const uniform = uniformArray[ j ]; + if ( controllerIndex === -1 ) { - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; + // Assign input source a controller that currently has no input source - for ( let k = 0, kl = values.length; k < kl; k ++ ) { + for ( let i = 0; i < controllers.length; i ++ ) { - const value = values[ k ]; + if ( i >= controllerInputSources.length ) { - const info = getUniformSize( value ); + controllerInputSources.push( inputSource ); + controllerIndex = i; + break; - // Calculate the chunk offset - const chunkOffsetUniform = offset % chunkSize; + } else if ( controllerInputSources[ i ] === null ) { - // Check for chunk overflow - if ( chunkOffsetUniform !== 0 && ( chunkSize - chunkOffsetUniform ) < info.boundary ) { + controllerInputSources[ i ] = inputSource; + controllerIndex = i; + break; - // Add padding and adjust offset - offset += ( chunkSize - chunkOffsetUniform ); + } } - // the following two properties will be used for partial buffer updates + // If all controllers do currently receive input we ignore new ones - uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); - uniform.__offset = offset; + if ( controllerIndex === -1 ) break; + } - // Update the global offset - offset += info.storage; + const controller = controllers[ controllerIndex ]; + + if ( controller ) { + controller.connect( inputSource ); } @@ -41841,4532 +51484,5115 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { } - // ensure correct final padding - - const chunkOffset = offset % chunkSize; - - if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - // - uniformsGroup.__size = offset; - uniformsGroup.__cache = {}; + const cameraLPos = new Vector3(); + const cameraRPos = new Vector3(); - return this; + /** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. + */ + function setProjectionFromUnion( camera, cameraL, cameraR ) { - } + cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - function getUniformSize( value ) { + const ipd = cameraLPos.distanceTo( cameraRPos ); - const info = { - boundary: 0, // bytes - storage: 0 // bytes - }; + const projL = cameraL.projectionMatrix.elements; + const projR = cameraR.projectionMatrix.elements; - // determine sizes according to STD140 + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - if ( typeof value === 'number' || typeof value === 'boolean' ) { + const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + const left = near * leftFov; + const right = near * rightFov; - // float/int/bool + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + const zOffset = ipd / ( - leftFov + rightFov ); + const xOffset = zOffset * - leftFov; - info.boundary = 4; - info.storage = 4; + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - } else if ( value.isVector2 ) { + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { - // vec2 + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); - info.boundary = 8; - info.storage = 8; + } else { - } else if ( value.isVector3 || value.isColor ) { + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; - // vec3 + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - info.boundary = 16; - info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes + } - } else if ( value.isVector4 ) { + } - // vec4 + function updateCamera( camera, parent ) { - info.boundary = 16; - info.storage = 16; + if ( parent === null ) { - } else if ( value.isMatrix3 ) { + camera.matrixWorld.copy( camera.matrix ); - // mat3 (in STD140 a 3x3 matrix is represented as 3x4) + } else { - info.boundary = 48; - info.storage = 48; + camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); - } else if ( value.isMatrix4 ) { + } - // mat4 + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - info.boundary = 64; - info.storage = 64; + } - } else if ( value.isTexture ) { + /** + * Updates the state of the XR camera. Use this method on app level if you + * set `cameraAutoUpdate` to `false`. The method requires the non-XR + * camera of the scene as a parameter. The passed in camera's transformation + * is automatically adjusted to the position of the XR camera when calling + * this method. + * + * @param {Camera} camera - The camera. + */ + this.updateCamera = function ( camera ) { - console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); + if ( session === null ) return; - } else { + let depthNear = camera.near; + let depthFar = camera.far; - console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); + if ( depthSensing.texture !== null ) { - } + if ( depthSensing.depthNear > 0 ) depthNear = depthSensing.depthNear; + if ( depthSensing.depthFar > 0 ) depthFar = depthSensing.depthFar; - return info; + } - } + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; - function onUniformsGroupsDispose( event ) { + if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - const uniformsGroup = event.target; + // Note that the new renderState won't apply until the next frame. See #18320 - uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); + session.updateRenderState( { + depthNear: cameraXR.near, + depthFar: cameraXR.far + } ); - const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); - allocatedBindingPoints.splice( index, 1 ); + _currentDepthNear = cameraXR.near; + _currentDepthFar = cameraXR.far; - gl.deleteBuffer( buffers[ uniformsGroup.id ] ); + } - delete buffers[ uniformsGroup.id ]; - delete updateList[ uniformsGroup.id ]; + // inherit camera layers and enable eye layers (1 = left, 2 = right) + cameraXR.layers.mask = camera.layers.mask | 0b110; + cameraL.layers.mask = cameraXR.layers.mask & 0b011; + cameraR.layers.mask = cameraXR.layers.mask & 0b101; - } + const parent = camera.parent; + const cameras = cameraXR.cameras; - function dispose() { + updateCamera( cameraXR, parent ); - for ( const id in buffers ) { + for ( let i = 0; i < cameras.length; i ++ ) { - gl.deleteBuffer( buffers[ id ] ); + updateCamera( cameras[ i ], parent ); - } + } - allocatedBindingPoints = []; - buffers = {}; - updateList = {}; + // update projection matrix for proper view frustum culling - } + if ( cameras.length === 2 ) { - return { + setProjectionFromUnion( cameraXR, cameraL, cameraR ); - bind: bind, - update: update, + } else { - dispose: dispose + // assume single camera setup (AR) - }; + cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); -} + } -class WebGLRenderer { + // update user camera and its children - constructor( parameters = {} ) { + updateUserCamera( camera, cameraXR, parent ); - const { - canvas = createCanvasElement(), - context = null, - depth = true, - stencil = true, - alpha = false, - antialias = false, - premultipliedAlpha = true, - preserveDrawingBuffer = false, - powerPreference = 'default', - failIfMajorPerformanceCaveat = false, - } = parameters; + }; - this.isWebGLRenderer = true; + function updateUserCamera( camera, cameraXR, parent ) { - let _alpha; + if ( parent === null ) { - if ( context !== null ) { + camera.matrix.copy( cameraXR.matrixWorld ); - _alpha = context.getContextAttributes().alpha; + } else { - } else { + camera.matrix.copy( parent.matrixWorld ); + camera.matrix.invert(); + camera.matrix.multiply( cameraXR.matrixWorld ); - _alpha = alpha; + } - } + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.updateMatrixWorld( true ); - const uintClearColor = new Uint32Array( 4 ); - const intClearColor = new Int32Array( 4 ); + camera.projectionMatrix.copy( cameraXR.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - let currentRenderList = null; - let currentRenderState = null; + if ( camera.isPerspectiveCamera ) { - // render() can be called from within a callback triggered by another render. - // We track this so that the nested render call gets its list and state isolated from the parent render call. + camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); + camera.zoom = 1; - const renderListStack = []; - const renderStateStack = []; + } - // public properties + } - this.domElement = canvas; + /** + * Returns an instance of {@link ArrayCamera} which represents the XR camera + * of the active XR session. For each view it holds a separate camera object. + * + * The camera's `fov` is currently not used and does not reflect the fov of + * the XR camera. If you need the fov on app level, you have to compute in + * manually from the XR camera's projection matrices. + * + * @return {ArrayCamera} The XR camera. + */ + this.getCamera = function () { - // Debug configuration container - this.debug = { + return cameraXR; - /** - * Enables error checking and reporting when shader programs are being compiled - * @type {boolean} - */ - checkShaderErrors: true, - /** - * Callback for custom error reporting. - * @type {?Function} - */ - onShaderError: null }; - // clearing + /** + * Returns the amount of foveation used by the XR compositor for the projection layer. + * + * @return {number|undefined} The amount of foveation. + */ + this.getFoveation = function () { - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; + if ( glProjLayer === null && glBaseLayer === null ) { - // scene graph + return undefined; - this.sortObjects = true; + } - // user-defined clipping + return foveation; - this.clippingPlanes = []; - this.localClippingEnabled = false; + }; - // physically based shading + /** + * Sets the foveation value. + * + * @param {number} value - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ + this.setFoveation = function ( value ) { - this._outputColorSpace = SRGBColorSpace; + // 0 = no foveation = full resolution + // 1 = maximum foveation = the edges render at lower resolution - // physical lights + foveation = value; - this._useLegacyLights = false; + if ( glProjLayer !== null ) { - // tone mapping + glProjLayer.fixedFoveation = value; - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; + } - // internal properties + if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - const _this = this; + glBaseLayer.fixedFoveation = value; - let _isContextLost = false; + } - // internal state cache + }; - let _currentActiveCubeFace = 0; - let _currentActiveMipmapLevel = 0; - let _currentRenderTarget = null; - let _currentMaterialId = - 1; + /** + * Returns `true` if depth sensing is supported. + * + * @return {boolean} Whether depth sensing is supported or not. + */ + this.hasDepthSensing = function () { - let _currentCamera = null; + return depthSensing.texture !== null; - const _currentViewport = new Vector4(); - const _currentScissor = new Vector4(); - let _currentScissorTest = null; + }; - const _currentClearColor = new Color( 0x000000 ); - let _currentClearAlpha = 0; + /** + * Returns the depth sensing mesh. + * + * See {@link WebXRDepthSensing#getMesh}. + * + * @return {Mesh} The depth sensing mesh. + */ + this.getDepthSensingMesh = function () { - // + return depthSensing.getMesh( cameraXR ); - let _width = canvas.width; - let _height = canvas.height; + }; - let _pixelRatio = 1; - let _opaqueSort = null; - let _transparentSort = null; + /** + * Retrieves an opaque texture from the view-aligned {@link XRCamera}. + * Only available during the current animation loop. + * + * @param {XRCamera} xrCamera - The camera to query. + * @return {?Texture} An opaque texture representing the current raw camera frame. + */ + this.getCameraTexture = function ( xrCamera ) { - const _viewport = new Vector4( 0, 0, _width, _height ); - const _scissor = new Vector4( 0, 0, _width, _height ); - let _scissorTest = false; + return cameraAccessTextures[ xrCamera ]; - // frustum + }; - const _frustum = new Frustum(); + // Animation Loop - // clipping + let onAnimationFrameCallback = null; - let _clippingEnabled = false; - let _localClippingEnabled = false; + function onAnimationFrame( time, frame ) { - // transmission + pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); + xrFrame = frame; - let _transmissionRenderTarget = null; + if ( pose !== null ) { - // camera matrices cache + const views = pose.views; - const _projScreenMatrix = new Matrix4(); + if ( glBaseLayer !== null ) { - const _vector2 = new Vector2(); - const _vector3 = new Vector3(); + renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); + renderer.setRenderTarget( newRenderTarget ); - const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; + } - function getTargetPixelRatio() { + let cameraXRNeedsUpdate = false; - return _currentRenderTarget === null ? _pixelRatio : 1; + // check if it's necessary to rebuild cameraXR's camera list - } + if ( views.length !== cameraXR.cameras.length ) { - // initialize + cameraXR.cameras.length = 0; + cameraXRNeedsUpdate = true; - let _gl = context; + } - function getContext( contextNames, contextAttributes ) { + for ( let i = 0; i < views.length; i ++ ) { - for ( let i = 0; i < contextNames.length; i ++ ) { + const view = views[ i ]; - const contextName = contextNames[ i ]; - const context = canvas.getContext( contextName, contextAttributes ); - if ( context !== null ) return context; + let viewport = null; - } + if ( glBaseLayer !== null ) { - return null; + viewport = glBaseLayer.getViewport( view ); - } + } else { - try { + const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); + viewport = glSubImage.viewport; - const contextAttributes = { - alpha: true, - depth, - stencil, - antialias, - premultipliedAlpha, - preserveDrawingBuffer, - powerPreference, - failIfMajorPerformanceCaveat, - }; + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { - // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); + renderer.setRenderTargetTextures( + newRenderTarget, + glSubImage.colorTexture, + glSubImage.depthStencilTexture ); - // event listeners must be registered before WebGL context is created, see #12753 - canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); + renderer.setRenderTarget( newRenderTarget ); - if ( _gl === null ) { + } + + } - const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; + let camera = cameras[ i ]; - if ( _this.isWebGL1Renderer === true ) { + if ( camera === undefined ) { - contextNames.shift(); + camera = new PerspectiveCamera(); + camera.layers.enable( i ); + camera.viewport = new Vector4(); + cameras[ i ] = camera; - } + } - _gl = getContext( contextNames, contextAttributes ); + camera.matrix.fromArray( view.transform.matrix ); + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - if ( _gl === null ) { + if ( i === 0 ) { - if ( getContext( contextNames ) ) { + cameraXR.matrix.copy( camera.matrix ); + cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - throw new Error( 'Error creating WebGL context with your selected attributes.' ); + } - } else { + if ( cameraXRNeedsUpdate === true ) { - throw new Error( 'Error creating WebGL context.' ); + cameraXR.cameras.push( camera ); } } - } + // - if ( typeof WebGLRenderingContext !== 'undefined' && _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 + const enabledFeatures = session.enabledFeatures; + const gpuDepthSensingEnabled = enabledFeatures && + enabledFeatures.includes( 'depth-sensing' ) && + session.depthUsage == 'gpu-optimized'; - console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); + if ( gpuDepthSensingEnabled && supportsGlBinding ) { - } + glBinding = scope.getBinding(); - // Some experimental-webgl implementations do not have getShaderPrecisionFormat + const depthData = glBinding.getDepthInformation( views[ 0 ] ); - if ( _gl.getShaderPrecisionFormat === undefined ) { + if ( depthData && depthData.isValid && depthData.texture ) { - _gl.getShaderPrecisionFormat = function () { + depthSensing.init( depthData, session.renderState ); - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; + } - }; + } - } + const cameraAccessEnabled = enabledFeatures && + enabledFeatures.includes( 'camera-access' ); - } catch ( error ) { + if ( cameraAccessEnabled && supportsGlBinding ) { - console.error( 'THREE.WebGLRenderer: ' + error.message ); - throw error; + renderer.state.unbindTexture(); - } + glBinding = scope.getBinding(); - let extensions, capabilities, state, info; - let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; - let programCache, materials, renderLists, renderStates, clipping, shadowMap; + for ( let i = 0; i < views.length; i ++ ) { - let background, morphtargets, bufferRenderer, indexedBufferRenderer; + const camera = views[ i ].camera; - let utils, bindingStates, uniformsGroups; + if ( camera ) { - function initGLContext() { + let cameraTex = cameraAccessTextures[ camera ]; - extensions = new WebGLExtensions( _gl ); + if ( ! cameraTex ) { - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); + cameraTex = new ExternalTexture(); + cameraAccessTextures[ camera ] = cameraTex; - extensions.init( capabilities ); + } - utils = new WebGLUtils( _gl, extensions, capabilities ); + const glTexture = glBinding.getCameraImage( camera ); + cameraTex.sourceTexture = glTexture; - state = new WebGLState( _gl, extensions, capabilities ); + } - info = new WebGLInfo( _gl ); - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); - cubemaps = new WebGLCubeMaps( _this ); - cubeuvmaps = new WebGLCubeUVMaps( _this ); - attributes = new WebGLAttributes( _gl, capabilities ); - bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); - geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); - objects = new WebGLObjects( _gl, geometries, attributes, info ); - morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); - clipping = new WebGLClipping( properties ); - programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); - materials = new WebGLMaterials( _this, properties ); - renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions, capabilities ); - background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); - shadowMap = new WebGLShadowMap( _this, objects, capabilities ); - uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); + } - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); + } - info.programs = programCache.programs; + } - _this.capabilities = capabilities; - _this.extensions = extensions; - _this.properties = properties; - _this.renderLists = renderLists; - _this.shadowMap = shadowMap; - _this.state = state; - _this.info = info; + // - } + for ( let i = 0; i < controllers.length; i ++ ) { - initGLContext(); + const inputSource = controllerInputSources[ i ]; + const controller = controllers[ i ]; - // xr + if ( inputSource !== null && controller !== undefined ) { - const xr = new WebXRManager( _this, _gl ); + controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - this.xr = xr; + } - // API + } - this.getContext = function () { + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - return _gl; + if ( frame.detectedPlanes ) { - }; + scope.dispatchEvent( { type: 'planesdetected', data: frame } ); - this.getContextAttributes = function () { + } - return _gl.getContextAttributes(); + xrFrame = null; - }; + } - this.forceContextLoss = function () { + const animation = new WebGLAnimation(); - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); + animation.setAnimationLoop( onAnimationFrame ); + + this.setAnimationLoop = function ( callback ) { + + onAnimationFrameCallback = callback; }; - this.forceContextRestore = function () { + this.dispose = function () {}; - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); + } - }; +} - this.getPixelRatio = function () { +const _e1 = /*@__PURE__*/ new Euler(); +const _m1 = /*@__PURE__*/ new Matrix4(); - return _pixelRatio; +function WebGLMaterials( renderer, properties ) { - }; + function refreshTransformUniform( map, uniform ) { - this.setPixelRatio = function ( value ) { + if ( map.matrixAutoUpdate === true ) { - if ( value === undefined ) return; + map.updateMatrix(); - _pixelRatio = value; + } - this.setSize( _width, _height, false ); + uniform.value.copy( map.matrix ); - }; + } - this.getSize = function ( target ) { + function refreshFogUniforms( uniforms, fog ) { - return target.set( _width, _height ); + fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - }; + if ( fog.isFog ) { - this.setSize = function ( width, height, updateStyle = true ) { + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; - if ( xr.isPresenting ) { + } else if ( fog.isFogExp2 ) { - console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; + uniforms.fogDensity.value = fog.density; - } + } - _width = width; - _height = height; + } - canvas.width = Math.floor( width * _pixelRatio ); - canvas.height = Math.floor( height * _pixelRatio ); + function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - if ( updateStyle === true ) { + if ( material.isMeshBasicMaterial ) { - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; + refreshUniformsCommon( uniforms, material ); - } + } else if ( material.isMeshLambertMaterial ) { - this.setViewport( 0, 0, width, height ); + refreshUniformsCommon( uniforms, material ); - }; + } else if ( material.isMeshToonMaterial ) { - this.getDrawingBufferSize = function ( target ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsToon( uniforms, material ); - return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); + } else if ( material.isMeshPhongMaterial ) { - }; + refreshUniformsCommon( uniforms, material ); + refreshUniformsPhong( uniforms, material ); - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { + } else if ( material.isMeshStandardMaterial ) { - _width = width; - _height = height; + refreshUniformsCommon( uniforms, material ); + refreshUniformsStandard( uniforms, material ); - _pixelRatio = pixelRatio; + if ( material.isMeshPhysicalMaterial ) { - canvas.width = Math.floor( width * pixelRatio ); - canvas.height = Math.floor( height * pixelRatio ); + refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - this.setViewport( 0, 0, width, height ); + } - }; + } else if ( material.isMeshMatcapMaterial ) { - this.getCurrentViewport = function ( target ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsMatcap( uniforms, material ); - return target.copy( _currentViewport ); + } else if ( material.isMeshDepthMaterial ) { - }; + refreshUniformsCommon( uniforms, material ); - this.getViewport = function ( target ) { + } else if ( material.isMeshDistanceMaterial ) { - return target.copy( _viewport ); + refreshUniformsCommon( uniforms, material ); + refreshUniformsDistance( uniforms, material ); - }; + } else if ( material.isMeshNormalMaterial ) { - this.setViewport = function ( x, y, width, height ) { + refreshUniformsCommon( uniforms, material ); - if ( x.isVector4 ) { + } else if ( material.isLineBasicMaterial ) { - _viewport.set( x.x, x.y, x.z, x.w ); + refreshUniformsLine( uniforms, material ); - } else { + if ( material.isLineDashedMaterial ) { - _viewport.set( x, y, width, height ); + refreshUniformsDash( uniforms, material ); } - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); - - }; + } else if ( material.isPointsMaterial ) { - this.getScissor = function ( target ) { + refreshUniformsPoints( uniforms, material, pixelRatio, height ); - return target.copy( _scissor ); + } else if ( material.isSpriteMaterial ) { - }; + refreshUniformsSprites( uniforms, material ); - this.setScissor = function ( x, y, width, height ) { + } else if ( material.isShadowMaterial ) { - if ( x.isVector4 ) { + uniforms.color.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - _scissor.set( x.x, x.y, x.z, x.w ); + } else if ( material.isShaderMaterial ) { - } else { + material.uniformsNeedUpdate = false; // #15581 - _scissor.set( x, y, width, height ); + } - } + } - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); + function refreshUniformsCommon( uniforms, material ) { - }; + uniforms.opacity.value = material.opacity; - this.getScissorTest = function () { + if ( material.color ) { - return _scissorTest; + uniforms.diffuse.value.copy( material.color ); - }; + } - this.setScissorTest = function ( boolean ) { + if ( material.emissive ) { - state.setScissorTest( _scissorTest = boolean ); + uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - }; + } - this.setOpaqueSort = function ( method ) { + if ( material.map ) { - _opaqueSort = method; + uniforms.map.value = material.map; - }; + refreshTransformUniform( material.map, uniforms.mapTransform ); - this.setTransparentSort = function ( method ) { + } - _transparentSort = method; + if ( material.alphaMap ) { - }; + uniforms.alphaMap.value = material.alphaMap; - // Clearing + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - this.getClearColor = function ( target ) { + } - return target.copy( background.getClearColor() ); + if ( material.bumpMap ) { - }; + uniforms.bumpMap.value = material.bumpMap; - this.setClearColor = function () { + refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - background.setClearColor.apply( background, arguments ); + uniforms.bumpScale.value = material.bumpScale; - }; + if ( material.side === BackSide ) { - this.getClearAlpha = function () { + uniforms.bumpScale.value *= -1; - return background.getClearAlpha(); + } - }; + } - this.setClearAlpha = function () { + if ( material.normalMap ) { - background.setClearAlpha.apply( background, arguments ); + uniforms.normalMap.value = material.normalMap; - }; + refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - this.clear = function ( color = true, depth = true, stencil = true ) { + uniforms.normalScale.value.copy( material.normalScale ); - let bits = 0; + if ( material.side === BackSide ) { - if ( color ) { + uniforms.normalScale.value.negate(); - // check if we're trying to clear an integer target - let isIntegerFormat = false; - if ( _currentRenderTarget !== null ) { + } - const targetFormat = _currentRenderTarget.texture.format; - isIntegerFormat = targetFormat === RGBAIntegerFormat || - targetFormat === RGIntegerFormat || - targetFormat === RedIntegerFormat; + } - } + if ( material.displacementMap ) { - // use the appropriate clear functions to clear the target if it's a signed - // or unsigned integer target - if ( isIntegerFormat ) { + uniforms.displacementMap.value = material.displacementMap; - const targetType = _currentRenderTarget.texture.type; - const isUnsignedType = targetType === UnsignedByteType || - targetType === UnsignedIntType || - targetType === UnsignedShortType || - targetType === UnsignedInt248Type || - targetType === UnsignedShort4444Type || - targetType === UnsignedShort5551Type; + refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); - const clearColor = background.getClearColor(); - const a = background.getClearAlpha(); - const r = clearColor.r; - const g = clearColor.g; - const b = clearColor.b; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; - if ( isUnsignedType ) { + } - uintClearColor[ 0 ] = r; - uintClearColor[ 1 ] = g; - uintClearColor[ 2 ] = b; - uintClearColor[ 3 ] = a; - _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); + if ( material.emissiveMap ) { - } else { + uniforms.emissiveMap.value = material.emissiveMap; - intClearColor[ 0 ] = r; - intClearColor[ 1 ] = g; - intClearColor[ 2 ] = b; - intClearColor[ 3 ] = a; - _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); + refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - } + } - } else { + if ( material.specularMap ) { - bits |= _gl.COLOR_BUFFER_BIT; + uniforms.specularMap.value = material.specularMap; - } + refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); - } + } - if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; - if ( stencil ) { + if ( material.alphaTest > 0 ) { - bits |= _gl.STENCIL_BUFFER_BIT; - this.state.buffers.stencil.setMask( 0xffffffff ); + uniforms.alphaTest.value = material.alphaTest; - } + } - _gl.clear( bits ); + const materialProperties = properties.get( material ); - }; + const envMap = materialProperties.envMap; + const envMapRotation = materialProperties.envMapRotation; - this.clearColor = function () { + if ( envMap ) { - this.clear( true, false, false ); + uniforms.envMap.value = envMap; - }; + _e1.copy( envMapRotation ); - this.clearDepth = function () { + // accommodate left-handed frame + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; - this.clear( false, true, false ); + if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { - }; + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1.y *= -1; + _e1.z *= -1; - this.clearStencil = function () { + } - this.clear( false, false, true ); + uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( _e1 ) ); - }; + uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - // + uniforms.reflectivity.value = material.reflectivity; + uniforms.ior.value = material.ior; + uniforms.refractionRatio.value = material.refractionRatio; - this.dispose = function () { + } - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); + if ( material.lightMap ) { - renderLists.dispose(); - renderStates.dispose(); - properties.dispose(); - cubemaps.dispose(); - cubeuvmaps.dispose(); - objects.dispose(); - bindingStates.dispose(); - uniformsGroups.dispose(); - programCache.dispose(); + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; - xr.dispose(); + refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); - xr.removeEventListener( 'sessionstart', onXRSessionStart ); - xr.removeEventListener( 'sessionend', onXRSessionEnd ); + } - if ( _transmissionRenderTarget ) { + if ( material.aoMap ) { - _transmissionRenderTarget.dispose(); - _transmissionRenderTarget = null; + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; - } + refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); - animation.stop(); + } - }; + } - // Events + function refreshUniformsLine( uniforms, material ) { - function onContextLost( event ) { + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - event.preventDefault(); + if ( material.map ) { - console.log( 'THREE.WebGLRenderer: Context Lost.' ); + uniforms.map.value = material.map; - _isContextLost = true; + refreshTransformUniform( material.map, uniforms.mapTransform ); } - function onContextRestore( /* event */ ) { + } - console.log( 'THREE.WebGLRenderer: Context Restored.' ); + function refreshUniformsDash( uniforms, material ) { - _isContextLost = false; + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; - const infoAutoReset = info.autoReset; - const shadowMapEnabled = shadowMap.enabled; - const shadowMapAutoUpdate = shadowMap.autoUpdate; - const shadowMapNeedsUpdate = shadowMap.needsUpdate; - const shadowMapType = shadowMap.type; + } - initGLContext(); + function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - info.autoReset = infoAutoReset; - shadowMap.enabled = shadowMapEnabled; - shadowMap.autoUpdate = shadowMapAutoUpdate; - shadowMap.needsUpdate = shadowMapNeedsUpdate; - shadowMap.type = shadowMapType; + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size * pixelRatio; + uniforms.scale.value = height * 0.5; - } + if ( material.map ) { - function onContextCreationError( event ) { + uniforms.map.value = material.map; - console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); + refreshTransformUniform( material.map, uniforms.uvTransform ); } - function onMaterialDispose( event ) { - - const material = event.target; + if ( material.alphaMap ) { - material.removeEventListener( 'dispose', onMaterialDispose ); + uniforms.alphaMap.value = material.alphaMap; - deallocateMaterial( material ); + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); } - // Buffer deallocation + if ( material.alphaTest > 0 ) { - function deallocateMaterial( material ) { + uniforms.alphaTest.value = material.alphaTest; - releaseMaterialProgramReferences( material ); + } - properties.remove( material ); + } - } + function refreshUniformsSprites( uniforms, material ) { + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.rotation.value = material.rotation; - function releaseMaterialProgramReferences( material ) { + if ( material.map ) { - const programs = properties.get( material ).programs; + uniforms.map.value = material.map; - if ( programs !== undefined ) { + refreshTransformUniform( material.map, uniforms.mapTransform ); - programs.forEach( function ( program ) { + } - programCache.releaseProgram( program ); + if ( material.alphaMap ) { - } ); + uniforms.alphaMap.value = material.alphaMap; - if ( material.isShaderMaterial ) { + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - programCache.releaseShaderCache( material ); + } - } + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; } - // Buffer rendering + } - this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { + function refreshUniformsPhong( uniforms, material ) { - if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) + uniforms.specular.value.copy( material.specular ); + uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + } - const program = setProgram( camera, scene, geometry, material, object ); + function refreshUniformsToon( uniforms, material ) { - state.setMaterial( material, frontFaceCW ); + if ( material.gradientMap ) { - // + uniforms.gradientMap.value = material.gradientMap; - let index = geometry.index; - let rangeFactor = 1; + } - if ( material.wireframe === true ) { + } - index = geometries.getWireframeAttribute( geometry ); + function refreshUniformsStandard( uniforms, material ) { - if ( index === undefined ) return; + uniforms.metalness.value = material.metalness; - rangeFactor = 2; + if ( material.metalnessMap ) { - } + uniforms.metalnessMap.value = material.metalnessMap; - // + refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - const drawRange = geometry.drawRange; - const position = geometry.attributes.position; + } - let drawStart = drawRange.start * rangeFactor; - let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; + uniforms.roughness.value = material.roughness; - if ( group !== null ) { + if ( material.roughnessMap ) { - drawStart = Math.max( drawStart, group.start * rangeFactor ); - drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); + uniforms.roughnessMap.value = material.roughnessMap; - } + refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); - if ( index !== null ) { + } - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, index.count ); + if ( material.envMap ) { - } else if ( position !== undefined && position !== null ) { + //uniforms.envMap.value = material.envMap; // part of uniforms common - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, position.count ); + uniforms.envMapIntensity.value = material.envMapIntensity; - } + } - const drawCount = drawEnd - drawStart; + } - if ( drawCount < 0 || drawCount === Infinity ) return; + function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - // + uniforms.ior.value = material.ior; // also part of uniforms common - bindingStates.setup( object, material, program, geometry, index ); + if ( material.sheen > 0 ) { - let attribute; - let renderer = bufferRenderer; + uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - if ( index !== null ) { + uniforms.sheenRoughness.value = material.sheenRoughness; - attribute = attributes.get( index ); + if ( material.sheenColorMap ) { - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); + uniforms.sheenColorMap.value = material.sheenColorMap; + + refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); } - // + if ( material.sheenRoughnessMap ) { - if ( object.isMesh ) { + uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - if ( material.wireframe === true ) { + refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); + } - } else { + } - renderer.setMode( _gl.TRIANGLES ); + if ( material.clearcoat > 0 ) { - } + uniforms.clearcoat.value = material.clearcoat; + uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - } else if ( object.isLine ) { + if ( material.clearcoatMap ) { - let lineWidth = material.linewidth; + uniforms.clearcoatMap.value = material.clearcoatMap; - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material + refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); - state.setLineWidth( lineWidth * getTargetPixelRatio() ); + } - if ( object.isLineSegments ) { + if ( material.clearcoatRoughnessMap ) { - renderer.setMode( _gl.LINES ); + uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; - } else if ( object.isLineLoop ) { + refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); - renderer.setMode( _gl.LINE_LOOP ); + } - } else { + if ( material.clearcoatNormalMap ) { - renderer.setMode( _gl.LINE_STRIP ); + uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - } + refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - } else if ( object.isPoints ) { + uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - renderer.setMode( _gl.POINTS ); + if ( material.side === BackSide ) { - } else if ( object.isSprite ) { + uniforms.clearcoatNormalScale.value.negate(); - renderer.setMode( _gl.TRIANGLES ); + } } - if ( object.isBatchedMesh ) { + } - renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + if ( material.dispersion > 0 ) { - } else if ( object.isInstancedMesh ) { + uniforms.dispersion.value = material.dispersion; - renderer.renderInstances( drawStart, drawCount, object.count ); + } - } else if ( geometry.isInstancedBufferGeometry ) { + if ( material.iridescence > 0 ) { - const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; - const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); + uniforms.iridescence.value = material.iridescence; + uniforms.iridescenceIOR.value = material.iridescenceIOR; + uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; + uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - renderer.renderInstances( drawStart, drawCount, instanceCount ); + if ( material.iridescenceMap ) { - } else { + uniforms.iridescenceMap.value = material.iridescenceMap; - renderer.render( drawStart, drawCount ); + refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); } - }; - - // Compile - - function prepareMaterial( material, scene, object ) { + if ( material.iridescenceThicknessMap ) { - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - material.side = BackSide; - material.needsUpdate = true; - getProgram( material, scene, object ); + refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); - material.side = FrontSide; - material.needsUpdate = true; - getProgram( material, scene, object ); + } - material.side = DoubleSide; + } - } else { + if ( material.transmission > 0 ) { - getProgram( material, scene, object ); + uniforms.transmission.value = material.transmission; + uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; + uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - } + if ( material.transmissionMap ) { - } + uniforms.transmissionMap.value = material.transmissionMap; - this.compile = function ( scene, camera, targetScene = null ) { + refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - if ( targetScene === null ) targetScene = scene; + } - currentRenderState = renderStates.get( targetScene ); - currentRenderState.init(); + uniforms.thickness.value = material.thickness; - renderStateStack.push( currentRenderState ); + if ( material.thicknessMap ) { - // gather lights from both the target scene and the new object that will be added to the scene. + uniforms.thicknessMap.value = material.thicknessMap; - targetScene.traverseVisible( function ( object ) { + refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); - if ( object.isLight && object.layers.test( camera.layers ) ) { + } - currentRenderState.pushLight( object ); + uniforms.attenuationDistance.value = material.attenuationDistance; + uniforms.attenuationColor.value.copy( material.attenuationColor ); - if ( object.castShadow ) { + } - currentRenderState.pushShadow( object ); + if ( material.anisotropy > 0 ) { - } + uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - } + if ( material.anisotropyMap ) { - } ); + uniforms.anisotropyMap.value = material.anisotropyMap; - if ( scene !== targetScene ) { + refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); - scene.traverseVisible( function ( object ) { + } - if ( object.isLight && object.layers.test( camera.layers ) ) { + } - currentRenderState.pushLight( object ); + uniforms.specularIntensity.value = material.specularIntensity; + uniforms.specularColor.value.copy( material.specularColor ); - if ( object.castShadow ) { + if ( material.specularColorMap ) { - currentRenderState.pushShadow( object ); + uniforms.specularColorMap.value = material.specularColorMap; - } + refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); - } + } - } ); + if ( material.specularIntensityMap ) { - } + uniforms.specularIntensityMap.value = material.specularIntensityMap; - currentRenderState.setupLights( _this._useLegacyLights ); + refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); - // Only initialize materials in the new scene, not the targetScene. + } - const materials = new Set(); + } - scene.traverse( function ( object ) { + function refreshUniformsMatcap( uniforms, material ) { - const material = object.material; + if ( material.matcap ) { - if ( material ) { + uniforms.matcap.value = material.matcap; - if ( Array.isArray( material ) ) { + } - for ( let i = 0; i < material.length; i ++ ) { + } - const material2 = material[ i ]; + function refreshUniformsDistance( uniforms, material ) { - prepareMaterial( material2, targetScene, object ); - materials.add( material2 ); + const light = properties.get( material ).light; - } + uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); + uniforms.nearDistance.value = light.shadow.camera.near; + uniforms.farDistance.value = light.shadow.camera.far; - } else { + } - prepareMaterial( material, targetScene, object ); - materials.add( material ); + return { + refreshFogUniforms: refreshFogUniforms, + refreshMaterialUniforms: refreshMaterialUniforms + }; - } +} - } +function WebGLUniformsGroups( gl, info, capabilities, state ) { - } ); + let buffers = {}; + let updateList = {}; + let allocatedBindingPoints = []; - renderStateStack.pop(); - currentRenderState = null; + const maxBindingPoints = gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ); // binding points are global whereas block indices are per shader program - return materials; + function bind( uniformsGroup, program ) { - }; + const webglProgram = program.program; + state.uniformBlockBinding( uniformsGroup, webglProgram ); - // compileAsync + } - this.compileAsync = function ( scene, camera, targetScene = null ) { + function update( uniformsGroup, program ) { - const materials = this.compile( scene, camera, targetScene ); + let buffer = buffers[ uniformsGroup.id ]; - // Wait for all the materials in the new object to indicate that they're - // ready to be used before resolving the promise. + if ( buffer === undefined ) { - return new Promise( ( resolve ) => { + prepareUniformsGroup( uniformsGroup ); - function checkMaterialsReady() { + buffer = createBuffer( uniformsGroup ); + buffers[ uniformsGroup.id ] = buffer; - materials.forEach( function ( material ) { + uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); - const materialProperties = properties.get( material ); - const program = materialProperties.currentProgram; + } - if ( program.isReady() ) { + // ensure to update the binding points/block indices mapping for this program - // remove any programs that report they're ready to use from the list - materials.delete( material ); + const webglProgram = program.program; + state.updateUBOMapping( uniformsGroup, webglProgram ); - } + // update UBO once per frame - } ); + const frame = info.render.frame; - // once the list of compiling materials is empty, call the callback + if ( updateList[ uniformsGroup.id ] !== frame ) { - if ( materials.size === 0 ) { + updateBufferData( uniformsGroup ); - resolve( scene ); - return; + updateList[ uniformsGroup.id ] = frame; - } + } - // if some materials are still not ready, wait a bit and check again + } - setTimeout( checkMaterialsReady, 10 ); + function createBuffer( uniformsGroup ) { - } + // the setup of an UBO is independent of a particular shader program but global - if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { + const bindingPointIndex = allocateBindingPointIndex(); + uniformsGroup.__bindingPointIndex = bindingPointIndex; - // If we can check the compilation status of the materials without - // blocking then do so right away. + const buffer = gl.createBuffer(); + const size = uniformsGroup.__size; + const usage = uniformsGroup.usage; - checkMaterialsReady(); + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - } else { + return buffer; - // Otherwise start by waiting a bit to give the materials we just - // initialized a chance to finish. + } - setTimeout( checkMaterialsReady, 10 ); + function allocateBindingPointIndex() { - } + for ( let i = 0; i < maxBindingPoints; i ++ ) { - } ); + if ( allocatedBindingPoints.indexOf( i ) === -1 ) { - }; + allocatedBindingPoints.push( i ); + return i; - // Animation Loop + } - let onAnimationFrameCallback = null; + } - function onAnimationFrame( time ) { + console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); + return 0; - } + } - function onXRSessionStart() { + function updateBufferData( uniformsGroup ) { - animation.stop(); + const buffer = buffers[ uniformsGroup.id ]; + const uniforms = uniformsGroup.uniforms; + const cache = uniformsGroup.__cache; - } + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - function onXRSessionEnd() { + for ( let i = 0, il = uniforms.length; i < il; i ++ ) { - animation.start(); + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - } + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - const animation = new WebGLAnimation(); - animation.setAnimationLoop( onAnimationFrame ); + const uniform = uniformArray[ j ]; - if ( typeof self !== 'undefined' ) animation.setContext( self ); + if ( hasUniformChanged( uniform, i, j, cache ) === true ) { - this.setAnimationLoop = function ( callback ) { + const offset = uniform.__offset; - onAnimationFrameCallback = callback; - xr.setAnimationLoop( callback ); + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - ( callback === null ) ? animation.stop() : animation.start(); + let arrayOffset = 0; - }; + for ( let k = 0; k < values.length; k ++ ) { - xr.addEventListener( 'sessionstart', onXRSessionStart ); - xr.addEventListener( 'sessionend', onXRSessionEnd ); + const value = values[ k ]; - // Rendering + const info = getUniformSize( value ); - this.render = function ( scene, camera ) { + // TODO add integer and struct support + if ( typeof value === 'number' || typeof value === 'boolean' ) { - if ( camera !== undefined && camera.isCamera !== true ) { + uniform.__data[ 0 ] = value; + gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); - console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; + } else if ( value.isMatrix3 ) { - } + // manually converting 3x3 to 3x4 - if ( _isContextLost === true ) return; + uniform.__data[ 0 ] = value.elements[ 0 ]; + uniform.__data[ 1 ] = value.elements[ 1 ]; + uniform.__data[ 2 ] = value.elements[ 2 ]; + uniform.__data[ 3 ] = 0; + uniform.__data[ 4 ] = value.elements[ 3 ]; + uniform.__data[ 5 ] = value.elements[ 4 ]; + uniform.__data[ 6 ] = value.elements[ 5 ]; + uniform.__data[ 7 ] = 0; + uniform.__data[ 8 ] = value.elements[ 6 ]; + uniform.__data[ 9 ] = value.elements[ 7 ]; + uniform.__data[ 10 ] = value.elements[ 8 ]; + uniform.__data[ 11 ] = 0; - // update scene graph + } else { - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + value.toArray( uniform.__data, arrayOffset ); - // update camera matrices and frustum + arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + } - if ( xr.enabled === true && xr.isPresenting === true ) { + } - if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); + gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); - camera = xr.getCamera(); // use XR camera for rendering + } } - // - if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); + } - currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init(); + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - renderStateStack.push( currentRenderState ); + } - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); + function hasUniformChanged( uniform, index, indexArray, cache ) { - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); + const value = uniform.value; + const indexString = index + '_' + indexArray; - currentRenderList = renderLists.get( scene, renderListStack.length ); - currentRenderList.init(); + if ( cache[ indexString ] === undefined ) { - renderListStack.push( currentRenderList ); + // cache entry does not exist so far - projectObject( scene, camera, 0, _this.sortObjects ); + if ( typeof value === 'number' || typeof value === 'boolean' ) { - currentRenderList.finish(); + cache[ indexString ] = value; - if ( _this.sortObjects === true ) { + } else { - currentRenderList.sort( _opaqueSort, _transparentSort ); + cache[ indexString ] = value.clone(); } - // + return true; - this.info.render.frame ++; + } else { - if ( _clippingEnabled === true ) clipping.beginShadows(); + const cachedObject = cache[ indexString ]; - const shadowsArray = currentRenderState.state.shadowsArray; + // compare current value with cached entry - shadowMap.render( shadowsArray, scene, camera ); + if ( typeof value === 'number' || typeof value === 'boolean' ) { - if ( _clippingEnabled === true ) clipping.endShadows(); + if ( cachedObject !== value ) { - // + cache[ indexString ] = value; + return true; - if ( this.info.autoReset === true ) this.info.reset(); + } + } else { - // + if ( cachedObject.equals( value ) === false ) { - if ( xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false ) { + cachedObject.copy( value ); + return true; - background.render( currentRenderList, scene ); + } } - // render scene - - currentRenderState.setupLights( _this._useLegacyLights ); + } - if ( camera.isArrayCamera ) { + return false; - const cameras = camera.cameras; + } - for ( let i = 0, l = cameras.length; i < l; i ++ ) { + function prepareUniformsGroup( uniformsGroup ) { - const camera2 = cameras[ i ]; + // determine total buffer size according to the STD140 layout + // Hint: STD140 is the only supported layout in WebGL 2 - renderScene( currentRenderList, scene, camera2, camera2.viewport ); + const uniforms = uniformsGroup.uniforms; - } + let offset = 0; // global buffer offset in bytes + const chunkSize = 16; // size of a chunk in bytes - } else { + for ( let i = 0, l = uniforms.length; i < l; i ++ ) { - renderScene( currentRenderList, scene, camera ); + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - } + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - // + const uniform = uniformArray[ j ]; - if ( _currentRenderTarget !== null ) { + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - // resolve multisample renderbuffers to a single-sample texture if necessary + for ( let k = 0, kl = values.length; k < kl; k ++ ) { - textures.updateMultisampleRenderTarget( _currentRenderTarget ); + const value = values[ k ]; - // Generate mipmap if we're using any kind of mipmap filtering + const info = getUniformSize( value ); - textures.updateRenderTargetMipmap( _currentRenderTarget ); + const chunkOffset = offset % chunkSize; // offset in the current chunk + const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data - } + offset += chunkPadding; - // + // Check for chunk overflow + if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) { - if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); + // Add padding and adjust offset + offset += ( chunkSize - chunkStart ); - // _gl.finish(); + } - bindingStates.resetDefaultState(); - _currentMaterialId = - 1; - _currentCamera = null; + // the following two properties will be used for partial buffer updates + uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); + uniform.__offset = offset; - renderStateStack.pop(); + // Update the global offset + offset += info.storage; - if ( renderStateStack.length > 0 ) { + } - currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; + } - } else { + } - currentRenderState = null; + // ensure correct final padding - } + const chunkOffset = offset % chunkSize; - renderListStack.pop(); + if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - if ( renderListStack.length > 0 ) { + // - currentRenderList = renderListStack[ renderListStack.length - 1 ]; + uniformsGroup.__size = offset; + uniformsGroup.__cache = {}; - } else { + return this; - currentRenderList = null; + } - } + function getUniformSize( value ) { + const info = { + boundary: 0, // bytes + storage: 0 // bytes }; - function projectObject( object, camera, groupOrder, sortObjects ) { - - if ( object.visible === false ) return; + // determine sizes according to STD140 - const visible = object.layers.test( camera.layers ); + if ( typeof value === 'number' || typeof value === 'boolean' ) { - if ( visible ) { + // float/int/bool - if ( object.isGroup ) { + info.boundary = 4; + info.storage = 4; - groupOrder = object.renderOrder; + } else if ( value.isVector2 ) { - } else if ( object.isLOD ) { + // vec2 - if ( object.autoUpdate === true ) object.update( camera ); + info.boundary = 8; + info.storage = 8; - } else if ( object.isLight ) { + } else if ( value.isVector3 || value.isColor ) { - currentRenderState.pushLight( object ); + // vec3 - if ( object.castShadow ) { + info.boundary = 16; + info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - currentRenderState.pushShadow( object ); + } else if ( value.isVector4 ) { - } + // vec4 - } else if ( object.isSprite ) { + info.boundary = 16; + info.storage = 16; - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { + } else if ( value.isMatrix3 ) { - if ( sortObjects ) { + // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); + info.boundary = 48; + info.storage = 48; - } + } else if ( value.isMatrix4 ) { - const geometry = objects.update( object ); - const material = object.material; + // mat4 - if ( material.visible ) { + info.boundary = 64; + info.storage = 64; - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + } else if ( value.isTexture ) { - } + console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - } + } else { - } else if ( object.isMesh || object.isLine || object.isPoints ) { + console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { + } - const geometry = objects.update( object ); - const material = object.material; + return info; - if ( sortObjects ) { + } - if ( object.boundingSphere !== undefined ) { + function onUniformsGroupsDispose( event ) { - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector3.copy( object.boundingSphere.center ); + const uniformsGroup = event.target; - } else { + uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector3.copy( geometry.boundingSphere.center ); + const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); + allocatedBindingPoints.splice( index, 1 ); - } + gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - _vector3 - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); + delete buffers[ uniformsGroup.id ]; + delete updateList[ uniformsGroup.id ]; - } + } - if ( Array.isArray( material ) ) { + function dispose() { - const groups = geometry.groups; + for ( const id in buffers ) { - for ( let i = 0, l = groups.length; i < l; i ++ ) { + gl.deleteBuffer( buffers[ id ] ); - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; + } - if ( groupMaterial && groupMaterial.visible ) { + allocatedBindingPoints = []; + buffers = {}; + updateList = {}; - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); + } - } + return { - } - - } else if ( material.visible ) { + bind: bind, + update: update, - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + dispose: dispose - } + }; - } +} - } +/** + * This renderer uses WebGL 2 to display scenes. + * + * WebGL 1 is not supported since `r163`. + */ +class WebGLRenderer { - } + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { - const children = object.children; + const { + canvas = createCanvasElement(), + context = null, + depth = true, + stencil = false, + alpha = false, + antialias = false, + premultipliedAlpha = true, + preserveDrawingBuffer = false, + powerPreference = 'default', + failIfMajorPerformanceCaveat = false, + reversedDepthBuffer = false, + } = parameters; - for ( let i = 0, l = children.length; i < l; i ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderer = true; - projectObject( children[ i ], camera, groupOrder, sortObjects ); + let _alpha; - } + if ( context !== null ) { - } + if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { - function renderScene( currentRenderList, scene, camera, viewport ) { + throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); - const opaqueObjects = currentRenderList.opaque; - const transmissiveObjects = currentRenderList.transmissive; - const transparentObjects = currentRenderList.transparent; + } - currentRenderState.setupLightsView( camera ); + _alpha = context.getContextAttributes().alpha; - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); + } else { - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); + _alpha = alpha; - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); + } - if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); - if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); - if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); + const uintClearColor = new Uint32Array( 4 ); + const intClearColor = new Int32Array( 4 ); - // Ensure depth buffer writing is enabled so it can be cleared on next render + let currentRenderList = null; + let currentRenderState = null; - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); + // render() can be called from within a callback triggered by another render. + // We track this so that the nested render call gets its list and state isolated from the parent render call. - state.setPolygonOffset( false ); + const renderListStack = []; + const renderStateStack = []; - } + // public properties - function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { + /** + * A canvas where the renderer draws its output.This is automatically created by the renderer + * in the constructor (if not provided already); you just need to add it to your page like so: + * ```js + * document.body.appendChild( renderer.domElement ); + * ``` + * + * @type {DOMElement} + */ + this.domElement = canvas; - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; + /** + * A object with debug configuration settings. + * + * - `checkShaderErrors`: If it is `true`, defines whether material shader programs are + * checked for errors during compilation and linkage process. It may be useful to disable + * this check in production for performance gain. It is strongly recommended to keep these + * checks enabled during development. If the shader does not compile and link - it will not + * work and associated material will not render. + * - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that + * can be used for custom error reporting. The callback receives the WebGL context, an instance + * of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader. + * Assigning a custom function disables the default error reporting. + * + * @type {Object} + */ + this.debug = { - if ( overrideMaterial !== null ) { + /** + * Enables error checking and reporting when shader programs are being compiled. + * @type {boolean} + */ + checkShaderErrors: true, + /** + * Callback for custom error reporting. + * @type {?Function} + */ + onShaderError: null + }; - return; + // clearing - } + /** + * Whether the renderer should automatically clear its output before rendering a frame or not. + * + * @type {boolean} + * @default true + */ + this.autoClear = true; - const isWebGL2 = capabilities.isWebGL2; + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the color buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearColor = true; - if ( _transmissionRenderTarget === null ) { + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearDepth = true; - _transmissionRenderTarget = new WebGLRenderTarget( 1, 1, { - generateMipmaps: true, - type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, - minFilter: LinearMipmapLinearFilter, - samples: ( isWebGL2 ) ? 4 : 0 - } ); + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearStencil = true; - // debug + // scene graph - /* - const geometry = new PlaneGeometry(); - const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); + /** + * Whether the renderer should sort objects or not. + * + * Note: Sorting is used to attempt to properly render objects that have some + * degree of transparency. By definition, sorting objects may not work in all + * cases. Depending on the needs of application, it may be necessary to turn + * off sorting and use other methods to deal with transparency rendering e.g. + * manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; - const mesh = new Mesh( geometry, material ); - scene.add( mesh ); - */ + // user-defined clipping - } + /** + * User-defined clipping planes specified in world space. These planes apply globally. + * Points in space whose dot product with the plane is negative are cut away. + * + * @type {Array} + */ + this.clippingPlanes = []; - _this.getDrawingBufferSize( _vector2 ); + /** + * Whether the renderer respects object-level clipping planes or not. + * + * @type {boolean} + * @default false + */ + this.localClippingEnabled = false; - if ( isWebGL2 ) { + // tone mapping - _transmissionRenderTarget.setSize( _vector2.x, _vector2.y ); + /** + * The tone mapping technique of the renderer. + * + * @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)} + * @default NoToneMapping + */ + this.toneMapping = NoToneMapping; - } else { + /** + * Exposure level of tone mapping. + * + * @type {number} + * @default 1 + */ + this.toneMappingExposure = 1.0; - _transmissionRenderTarget.setSize( floorPowerOfTwo( _vector2.x ), floorPowerOfTwo( _vector2.y ) ); + // transmission - } + /** + * The normalized resolution scale for the transmission render target, measured in percentage + * of viewport dimensions. Lowering this value can result in significant performance improvements + * when using {@link MeshPhysicalMaterial#transmission}. + * + * @type {number} + * @default 1 + */ + this.transmissionResolutionScale = 1.0; - // + // internal properties - const currentRenderTarget = _this.getRenderTarget(); - _this.setRenderTarget( _transmissionRenderTarget ); + const _this = this; - _this.getClearColor( _currentClearColor ); - _currentClearAlpha = _this.getClearAlpha(); - if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); + let _isContextLost = false; - _this.clear(); + // internal state cache - // Turn off the features which can affect the frag color for opaque objects pass. - // Otherwise they are applied twice in opaque objects pass and transmission objects pass. - const currentToneMapping = _this.toneMapping; - _this.toneMapping = NoToneMapping; + this._outputColorSpace = SRGBColorSpace; - renderObjects( opaqueObjects, scene, camera ); + let _currentActiveCubeFace = 0; + let _currentActiveMipmapLevel = 0; + let _currentRenderTarget = null; + let _currentMaterialId = -1; - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + let _currentCamera = null; - let renderTargetNeedsUpdate = false; + const _currentViewport = new Vector4(); + const _currentScissor = new Vector4(); + let _currentScissorTest = null; - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { + const _currentClearColor = new Color( 0x000000 ); + let _currentClearAlpha = 0; - const renderItem = transmissiveObjects[ i ]; + // - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = renderItem.material; - const group = renderItem.group; + let _width = canvas.width; + let _height = canvas.height; - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { + let _pixelRatio = 1; + let _opaqueSort = null; + let _transparentSort = null; - const currentSide = material.side; + const _viewport = new Vector4( 0, 0, _width, _height ); + const _scissor = new Vector4( 0, 0, _width, _height ); + let _scissorTest = false; - material.side = BackSide; - material.needsUpdate = true; + // frustum - renderObject( object, scene, camera, geometry, material, group ); + const _frustum = new Frustum(); - material.side = currentSide; - material.needsUpdate = true; + // clipping - renderTargetNeedsUpdate = true; + let _clippingEnabled = false; + let _localClippingEnabled = false; - } + // camera matrices cache - } + const _projScreenMatrix = new Matrix4(); - if ( renderTargetNeedsUpdate === true ) { + const _vector3 = new Vector3(); - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + const _vector4 = new Vector4(); - } + const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - _this.setRenderTarget( currentRenderTarget ); + let _renderBackground = false; - _this.setClearColor( _currentClearColor, _currentClearAlpha ); + function getTargetPixelRatio() { - _this.toneMapping = currentToneMapping; + return _currentRenderTarget === null ? _pixelRatio : 1; } - function renderObjects( renderList, scene, camera ) { - - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; + // initialize - for ( let i = 0, l = renderList.length; i < l; i ++ ) { + let _gl = context; - const renderItem = renderList[ i ]; + function getContext( contextName, contextAttributes ) { - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = overrideMaterial === null ? renderItem.material : overrideMaterial; - const group = renderItem.group; + return canvas.getContext( contextName, contextAttributes ); - if ( object.layers.test( camera.layers ) ) { + } - renderObject( object, scene, camera, geometry, material, group ); + try { - } + const contextAttributes = { + alpha: true, + depth, + stencil, + antialias, + premultipliedAlpha, + preserveDrawingBuffer, + powerPreference, + failIfMajorPerformanceCaveat, + }; - } + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - } + // event listeners must be registered before WebGL context is created, see #12753 + canvas.addEventListener( 'webglcontextlost', onContextLost, false ); + canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - function renderObject( object, scene, camera, geometry, material, group ) { + if ( _gl === null ) { - object.onBeforeRender( _this, scene, camera, geometry, material, group ); + const contextName = 'webgl2'; - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + _gl = getContext( contextName, contextAttributes ); - material.onBeforeRender( _this, scene, camera, geometry, object, group ); + if ( _gl === null ) { - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + if ( getContext( contextName ) ) { - material.side = BackSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + throw new Error( 'Error creating WebGL context with your selected attributes.' ); - material.side = FrontSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + } else { - material.side = DoubleSide; + throw new Error( 'Error creating WebGL context.' ); - } else { + } - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + } } - object.onAfterRender( _this, scene, camera, geometry, material, group ); - - } - - function getProgram( material, scene, object ) { + } catch ( error ) { - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... + console.error( 'THREE.WebGLRenderer: ' + error.message ); + throw error; - const materialProperties = properties.get( material ); + } - const lights = currentRenderState.state.lights; - const shadowsArray = currentRenderState.state.shadowsArray; + let extensions, capabilities, state, info; + let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; + let programCache, materials, renderLists, renderStates, clipping, shadowMap; - const lightsStateVersion = lights.state.version; + let background, morphtargets, bufferRenderer, indexedBufferRenderer; - const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); - const programCacheKey = programCache.getProgramCacheKey( parameters ); + let utils, bindingStates, uniformsGroups; - let programs = materialProperties.programs; + function initGLContext() { - // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change + extensions = new WebGLExtensions( _gl ); + extensions.init(); - materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; - materialProperties.fog = scene.fog; - materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); - materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; + utils = new WebGLUtils( _gl, extensions ); - if ( programs === undefined ) { + capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); - // new material + state = new WebGLState( _gl, extensions ); - material.addEventListener( 'dispose', onMaterialDispose ); + if ( capabilities.reversedDepthBuffer && reversedDepthBuffer ) { - programs = new Map(); - materialProperties.programs = programs; + state.buffers.depth.setReversed( true ); } - let program = programs.get( programCacheKey ); + info = new WebGLInfo( _gl ); + properties = new WebGLProperties(); + textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); + cubemaps = new WebGLCubeMaps( _this ); + cubeuvmaps = new WebGLCubeUVMaps( _this ); + attributes = new WebGLAttributes( _gl ); + bindingStates = new WebGLBindingStates( _gl, attributes ); + geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); + objects = new WebGLObjects( _gl, geometries, attributes, info ); + morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); + clipping = new WebGLClipping( properties ); + programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); + materials = new WebGLMaterials( _this, properties ); + renderLists = new WebGLRenderLists(); + renderStates = new WebGLRenderStates( extensions ); + background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); + shadowMap = new WebGLShadowMap( _this, objects, capabilities ); + uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - if ( program !== undefined ) { + bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); + indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); - // early out if program and light state is identical + info.programs = programCache.programs; - if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { + /** + * Holds details about the capabilities of the current rendering context. + * + * @name WebGLRenderer#capabilities + * @type {WebGLRenderer~Capabilities} + */ + _this.capabilities = capabilities; - updateCommonMaterialProperties( material, parameters ); + /** + * Provides methods for retrieving and testing WebGL extensions. + * + * - `get(extensionName:string)`: Used to check whether a WebGL extension is supported + * and return the extension object if available. + * - `has(extensionName:string)`: returns `true` if the extension is supported. + * + * @name WebGLRenderer#extensions + * @type {Object} + */ + _this.extensions = extensions; - return program; + /** + * Used to track properties of other objects like native WebGL objects. + * + * @name WebGLRenderer#properties + * @type {Object} + */ + _this.properties = properties; - } + /** + * Manages the render lists of the renderer. + * + * @name WebGLRenderer#renderLists + * @type {Object} + */ + _this.renderLists = renderLists; - } else { - parameters.uniforms = programCache.getUniforms( material ); - material.onBuild( object, parameters, _this ); + /** + * Interface for managing shadows. + * + * @name WebGLRenderer#shadowMap + * @type {WebGLRenderer~ShadowMap} + */ + _this.shadowMap = shadowMap; - material.onBeforeCompile( parameters, _this ); + /** + * Interface for managing the WebGL state. + * + * @name WebGLRenderer#state + * @type {Object} + */ + _this.state = state; - program = programCache.acquireProgram( parameters, programCacheKey ); - programs.set( programCacheKey, program ); + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * By default these data are reset at each render call but when having + * multiple render passes per frame (e.g. when using post processing) it can + * be preferred to reset with a custom pattern. First, set `autoReset` to + * `false`. + * ```js + * renderer.info.autoReset = false; + * ``` + * Call `reset()` whenever you have finished to render a single frame. + * ```js + * renderer.info.reset(); + * ``` + * + * @name WebGLRenderer#info + * @type {WebGLRenderer~Info} + */ + _this.info = info; - materialProperties.uniforms = parameters.uniforms; + } - } + initGLContext(); - const uniforms = materialProperties.uniforms; + // xr - if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { + const xr = new WebXRManager( _this, _gl ); - uniforms.clippingPlanes = clipping.uniform; + /** + * A reference to the XR manager. + * + * @type {WebXRManager} + */ + this.xr = xr; - } + /** + * Returns the rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ + this.getContext = function () { - updateCommonMaterialProperties( material, parameters ); + return _gl; - // store the light setup it was created for + }; - materialProperties.needsLights = materialNeedsLights( material ); - materialProperties.lightsStateVersion = lightsStateVersion; + /** + * Returns the rendering context attributes. + * + * @return {WebGLContextAttributes} The rendering context attributes. + */ + this.getContextAttributes = function () { - if ( materialProperties.needsLights ) { + return _gl.getContextAttributes(); - // wire up the material to this renderer's lighting state + }; - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.lightProbe.value = lights.state.probe; - uniforms.directionalLights.value = lights.state.directional; - uniforms.directionalLightShadows.value = lights.state.directionalShadow; - uniforms.spotLights.value = lights.state.spot; - uniforms.spotLightShadows.value = lights.state.spotShadow; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.ltc_1.value = lights.state.rectAreaLTC1; - uniforms.ltc_2.value = lights.state.rectAreaLTC2; - uniforms.pointLights.value = lights.state.point; - uniforms.pointLightShadows.value = lights.state.pointShadow; - uniforms.hemisphereLights.value = lights.state.hemi; + /** + * Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextLoss = function () { - uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotShadowMap.value = lights.state.spotShadowMap; - uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; - uniforms.spotLightMap.value = lights.state.spotLightMap; - uniforms.pointShadowMap.value = lights.state.pointShadowMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); - } + }; - materialProperties.currentProgram = program; - materialProperties.uniformsList = null; + /** + * Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextRestore = function () { - return program; + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.restoreContext(); - } + }; - function getUniformList( materialProperties ) { + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ + this.getPixelRatio = function () { - if ( materialProperties.uniformsList === null ) { + return _pixelRatio; - const progUniforms = materialProperties.currentProgram.getUniforms(); - materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); + }; - } + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} value - The pixel ratio. + */ + this.setPixelRatio = function ( value ) { - return materialProperties.uniformsList; + if ( value === undefined ) return; - } + _pixelRatio = value; - function updateCommonMaterialProperties( material, parameters ) { + this.setSize( _width, _height, false ); - const materialProperties = properties.get( material ); + }; - materialProperties.outputColorSpace = parameters.outputColorSpace; - materialProperties.batching = parameters.batching; - materialProperties.instancing = parameters.instancing; - materialProperties.instancingColor = parameters.instancingColor; - materialProperties.instancingMorph = parameters.instancingMorph; - materialProperties.skinning = parameters.skinning; - materialProperties.morphTargets = parameters.morphTargets; - materialProperties.morphNormals = parameters.morphNormals; - materialProperties.morphColors = parameters.morphColors; - materialProperties.morphTargetsCount = parameters.morphTargetsCount; - materialProperties.numClippingPlanes = parameters.numClippingPlanes; - materialProperties.numIntersection = parameters.numClipIntersection; - materialProperties.vertexAlphas = parameters.vertexAlphas; - materialProperties.vertexTangents = parameters.vertexTangents; - materialProperties.toneMapping = parameters.toneMapping; + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ + this.getSize = function ( target ) { - } + return target.set( _width, _height ); - function setProgram( camera, scene, geometry, material, object ) { + }; - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... + /** + * Resizes the output canvas to (width, height) with device pixel ratio taken + * into account, and also sets the viewport to fit that size, starting in (0, + * 0). Setting `updateStyle` to false prevents any style changes to the output canvas. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ + this.setSize = function ( width, height, updateStyle = true ) { - textures.resetTextureUnits(); + if ( xr.isPresenting ) { - const fog = scene.fog; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; - const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); - const morphTargets = !! geometry.morphAttributes.position; - const morphNormals = !! geometry.morphAttributes.normal; - const morphColors = !! geometry.morphAttributes.color; + console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); + return; - let toneMapping = NoToneMapping; + } - if ( material.toneMapped ) { + _width = width; + _height = height; - if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { + canvas.width = Math.floor( width * _pixelRatio ); + canvas.height = Math.floor( height * _pixelRatio ); - toneMapping = _this.toneMapping; + if ( updateStyle === true ) { - } + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; } - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + this.setViewport( 0, 0, width, height ); - const materialProperties = properties.get( material ); - const lights = currentRenderState.state.lights; + }; - if ( _clippingEnabled === true ) { + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ + this.getDrawingBufferSize = function ( target ) { - if ( _localClippingEnabled === true || camera !== _currentCamera ) { + return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - const useCache = - camera === _currentCamera && - material.id === _currentMaterialId; + }; - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - clipping.setState( material, camera, useCache ); + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ + this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - } + _width = width; + _height = height; - } + _pixelRatio = pixelRatio; - // + canvas.width = Math.floor( width * pixelRatio ); + canvas.height = Math.floor( height * pixelRatio ); - let needsProgramChange = false; + this.setViewport( 0, 0, width, height ); - if ( material.version === materialProperties.__version ) { + }; - if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.outputColorSpace !== colorSpace ) { + /** + * Returns the current viewport definition. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The current viewport definition. + */ + this.getCurrentViewport = function ( target ) { - needsProgramChange = true; + return target.copy( _currentViewport ); - } else if ( object.isBatchedMesh && materialProperties.batching === false ) { + }; - needsProgramChange = true; + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ + this.getViewport = function ( target ) { - } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { + return target.copy( _viewport ); - needsProgramChange = true; + }; - } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { + /** + * Sets the viewport to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the viewport. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + */ + this.setViewport = function ( x, y, width, height ) { - needsProgramChange = true; + if ( x.isVector4 ) { - } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { + _viewport.set( x.x, x.y, x.z, x.w ); - needsProgramChange = true; + } else { - } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { + _viewport.set( x, y, width, height ); - needsProgramChange = true; + } - } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { + state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); - needsProgramChange = true; + }; - } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { + /** + * Returns the scissor region. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor region. + */ + this.getScissor = function ( target ) { - needsProgramChange = true; + return target.copy( _scissor ); - } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { + }; - needsProgramChange = true; + /** + * Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the scissor region. + * @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * @param {number} width - The width of the scissor region in logical pixel unit. + * @param {number} height - The height of the scissor region in logical pixel unit. + */ + this.setScissor = function ( x, y, width, height ) { - } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { + if ( x.isVector4 ) { - needsProgramChange = true; + _scissor.set( x.x, x.y, x.z, x.w ); - } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { + } else { - needsProgramChange = true; + _scissor.set( x, y, width, height ); - } else if ( materialProperties.envMap !== envMap ) { + } - needsProgramChange = true; + state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); - } else if ( material.fog === true && materialProperties.fog !== fog ) { + }; - needsProgramChange = true; + /** + * Returns `true` if the scissor test is enabled. + * + * @return {boolean} Whether the scissor test is enabled or not. + */ + this.getScissorTest = function () { - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== clipping.numPlanes || - materialProperties.numIntersection !== clipping.numIntersection ) ) { + return _scissorTest; - needsProgramChange = true; + }; - } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { + /** + * Enable or disable the scissor test. When this is enabled, only the pixels + * within the defined scissor area will be affected by further renderer + * actions. + * + * @param {boolean} boolean - Whether the scissor test is enabled or not. + */ + this.setScissorTest = function ( boolean ) { - needsProgramChange = true; + state.setScissorTest( _scissorTest = boolean ); - } else if ( materialProperties.vertexTangents !== vertexTangents ) { + }; - needsProgramChange = true; + /** + * Sets a custom opaque sort function for the render lists. Pass `null` + * to use the default `painterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setOpaqueSort = function ( method ) { - } else if ( materialProperties.morphTargets !== morphTargets ) { + _opaqueSort = method; - needsProgramChange = true; + }; - } else if ( materialProperties.morphNormals !== morphNormals ) { + /** + * Sets a custom transparent sort function for the render lists. Pass `null` + * to use the default `reversePainterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setTransparentSort = function ( method ) { - needsProgramChange = true; + _transparentSort = method; - } else if ( materialProperties.morphColors !== morphColors ) { + }; - needsProgramChange = true; + // Clearing - } else if ( materialProperties.toneMapping !== toneMapping ) { + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ + this.getClearColor = function ( target ) { - needsProgramChange = true; + return target.copy( background.getClearColor() ); - } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { + }; - needsProgramChange = true; + /** + * Sets the clear color and alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ + this.setClearColor = function () { - } + background.setClearColor( ...arguments ); - } else { + }; - needsProgramChange = true; - materialProperties.__version = material.version; + /** + * Returns the clear alpha. Ranges within `[0,1]`. + * + * @return {number} The clear alpha. + */ + this.getClearAlpha = function () { - } + return background.getClearAlpha(); - // + }; - let program = materialProperties.currentProgram; + /** + * Sets the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ + this.setClearAlpha = function () { - if ( needsProgramChange === true ) { + background.setClearAlpha( ...arguments ); - program = getProgram( material, scene, object ); + }; - } + /** + * Tells the renderer to clear its color, depth or stencil drawing buffer(s). + * This method initializes the buffers to the current clear color values. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + this.clear = function ( color = true, depth = true, stencil = true ) { - let refreshProgram = false; - let refreshMaterial = false; - let refreshLights = false; + let bits = 0; - const p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.uniforms; + if ( color ) { - if ( state.useProgram( program.program ) ) { + // check if we're trying to clear an integer target + let isIntegerFormat = false; + if ( _currentRenderTarget !== null ) { - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; + const targetFormat = _currentRenderTarget.texture.format; + isIntegerFormat = targetFormat === RGBAIntegerFormat || + targetFormat === RGIntegerFormat || + targetFormat === RedIntegerFormat; - } + } - if ( material.id !== _currentMaterialId ) { + // use the appropriate clear functions to clear the target if it's a signed + // or unsigned integer target + if ( isIntegerFormat ) { - _currentMaterialId = material.id; + const targetType = _currentRenderTarget.texture.type; + const isUnsignedType = targetType === UnsignedByteType || + targetType === UnsignedIntType || + targetType === UnsignedShortType || + targetType === UnsignedInt248Type || + targetType === UnsignedShort4444Type || + targetType === UnsignedShort5551Type; - refreshMaterial = true; + const clearColor = background.getClearColor(); + const a = background.getClearAlpha(); + const r = clearColor.r; + const g = clearColor.g; + const b = clearColor.b; - } + if ( isUnsignedType ) { - if ( refreshProgram || _currentCamera !== camera ) { + uintClearColor[ 0 ] = r; + uintClearColor[ 1 ] = g; + uintClearColor[ 2 ] = b; + uintClearColor[ 3 ] = a; + _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); - // common camera uniforms + } else { - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + intClearColor[ 0 ] = r; + intClearColor[ 1 ] = g; + intClearColor[ 2 ] = b; + intClearColor[ 3 ] = a; + _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); - const uCamPos = p_uniforms.map.cameraPosition; + } - if ( uCamPos !== undefined ) { + } else { - uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); + bits |= _gl.COLOR_BUFFER_BIT; } - if ( capabilities.logarithmicDepthBuffer ) { + } - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); + if ( depth ) { - } + bits |= _gl.DEPTH_BUFFER_BIT; - // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 + } - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial ) { + if ( stencil ) { - p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); + bits |= _gl.STENCIL_BUFFER_BIT; + this.state.buffers.stencil.setMask( 0xffffffff ); - } + } - if ( _currentCamera !== camera ) { + _gl.clear( bits ); - _currentCamera = camera; + }; - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: + /** + * Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`. + */ + this.clearColor = function () { - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done + this.clear( true, false, false ); - } + }; - } + /** + * Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`. + */ + this.clearDepth = function () { - // skinning and morph target uniforms must be set even if material didn't change - // auto-setting of texture unit for bone and morph texture must go before other textures - // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures + this.clear( false, true, false ); - if ( object.isSkinnedMesh ) { + }; - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); + /** + * Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`. + */ + this.clearStencil = function () { - const skeleton = object.skeleton; + this.clear( false, false, true ); - if ( skeleton ) { + }; - if ( capabilities.floatVertexTextures ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + this.dispose = function () { + + canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); + canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + background.dispose(); + renderLists.dispose(); + renderStates.dispose(); + properties.dispose(); + cubemaps.dispose(); + cubeuvmaps.dispose(); + objects.dispose(); + bindingStates.dispose(); + uniformsGroups.dispose(); + programCache.dispose(); - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); + xr.dispose(); - } else { + xr.removeEventListener( 'sessionstart', onXRSessionStart ); + xr.removeEventListener( 'sessionend', onXRSessionEnd ); - console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); + animation.stop(); - } + }; - } + // Events - } + function onContextLost( event ) { - if ( object.isBatchedMesh ) { + event.preventDefault(); - p_uniforms.setOptional( _gl, object, 'batchingTexture' ); - p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); + console.log( 'THREE.WebGLRenderer: Context Lost.' ); - } + _isContextLost = true; - const morphAttributes = geometry.morphAttributes; + } - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { + function onContextRestore( /* event */ ) { - morphtargets.update( object, geometry, program ); + console.log( 'THREE.WebGLRenderer: Context Restored.' ); - } + _isContextLost = false; - if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { + const infoAutoReset = info.autoReset; + const shadowMapEnabled = shadowMap.enabled; + const shadowMapAutoUpdate = shadowMap.autoUpdate; + const shadowMapNeedsUpdate = shadowMap.needsUpdate; + const shadowMapType = shadowMap.type; - materialProperties.receiveShadow = object.receiveShadow; - p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); + initGLContext(); - } + info.autoReset = infoAutoReset; + shadowMap.enabled = shadowMapEnabled; + shadowMap.autoUpdate = shadowMapAutoUpdate; + shadowMap.needsUpdate = shadowMapNeedsUpdate; + shadowMap.type = shadowMapType; - // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 + } - if ( material.isMeshGouraudMaterial && material.envMap !== null ) { + function onContextCreationError( event ) { - m_uniforms.envMap.value = envMap; + console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); - m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + } - } + function onMaterialDispose( event ) { - if ( refreshMaterial ) { + const material = event.target; - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); + material.removeEventListener( 'dispose', onMaterialDispose ); - if ( materialProperties.needsLights ) { + deallocateMaterial( material ); - // the current material requires lighting info + } - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required + // Buffer deallocation - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); + function deallocateMaterial( material ) { - } + releaseMaterialProgramReferences( material ); - // refresh uniforms common to several materials + properties.remove( material ); - if ( fog && material.fog === true ) { + } - materials.refreshFogUniforms( m_uniforms, fog ); - } + function releaseMaterialProgramReferences( material ) { - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); + const programs = properties.get( material ).programs; - WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); + if ( programs !== undefined ) { - } + programs.forEach( function ( program ) { - if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { + programCache.releaseProgram( program ); - WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - material.uniformsNeedUpdate = false; + } ); - } + if ( material.isShaderMaterial ) { - if ( material.isSpriteMaterial ) { + programCache.releaseShaderCache( material ); - p_uniforms.setValue( _gl, 'center', object.center ); + } } - // common matrices + } - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); + // Buffer rendering - // UBOs + this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - if ( material.isShaderMaterial || material.isRawShaderMaterial ) { + if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - const groups = material.uniformsGroups; + const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - for ( let i = 0, l = groups.length; i < l; i ++ ) { + const program = setProgram( camera, scene, geometry, material, object ); - if ( capabilities.isWebGL2 ) { + state.setMaterial( material, frontFaceCW ); - const group = groups[ i ]; + // - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); + let index = geometry.index; + let rangeFactor = 1; - } else { + if ( material.wireframe === true ) { - console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); + index = geometries.getWireframeAttribute( geometry ); - } + if ( index === undefined ) return; - } + rangeFactor = 2; } - return program; + // - } + const drawRange = geometry.drawRange; + const position = geometry.attributes.position; - // If uniforms are marked as clean, they don't need to be loaded to the GPU. + let drawStart = drawRange.start * rangeFactor; + let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - function markUniformsLightsNeedsUpdate( uniforms, value ) { + if ( group !== null ) { - uniforms.ambientLightColor.needsUpdate = value; - uniforms.lightProbe.needsUpdate = value; + drawStart = Math.max( drawStart, group.start * rangeFactor ); + drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - uniforms.directionalLights.needsUpdate = value; - uniforms.directionalLightShadows.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.pointLightShadows.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.spotLightShadows.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; + } - } + if ( index !== null ) { - function materialNeedsLights( material ) { + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, index.count ); - return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || - material.isMeshStandardMaterial || material.isShadowMaterial || - ( material.isShaderMaterial && material.lights === true ); + } else if ( position !== undefined && position !== null ) { - } + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, position.count ); - this.getActiveCubeFace = function () { + } - return _currentActiveCubeFace; + const drawCount = drawEnd - drawStart; - }; + if ( drawCount < 0 || drawCount === Infinity ) return; - this.getActiveMipmapLevel = function () { + // - return _currentActiveMipmapLevel; + bindingStates.setup( object, material, program, geometry, index ); - }; + let attribute; + let renderer = bufferRenderer; - this.getRenderTarget = function () { + if ( index !== null ) { - return _currentRenderTarget; + attribute = attributes.get( index ); - }; + renderer = indexedBufferRenderer; + renderer.setIndex( attribute ); - this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { + } - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; + // - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__hasExternalTextures = true; + if ( object.isMesh ) { - renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; + if ( material.wireframe === true ) { - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { + state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); + renderer.setMode( _gl.LINES ); - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { + } else { - console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); - renderTargetProperties.__useRenderToTexture = false; + renderer.setMode( _gl.TRIANGLES ); } - } - - }; + } else if ( object.isLine ) { - this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { + let lineWidth = material.linewidth; - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; - renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; + if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - }; + state.setLineWidth( lineWidth * getTargetPixelRatio() ); - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + if ( object.isLineSegments ) { - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; + renderer.setMode( _gl.LINES ); - let useDefaultFramebuffer = true; - let framebuffer = null; - let isCube = false; - let isRenderTarget3D = false; + } else if ( object.isLineLoop ) { - if ( renderTarget ) { + renderer.setMode( _gl.LINE_LOOP ); - const renderTargetProperties = properties.get( renderTarget ); + } else { - if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { + renderer.setMode( _gl.LINE_STRIP ); - // We need to make sure to rebind the framebuffer. - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - useDefaultFramebuffer = false; + } - } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { + } else if ( object.isPoints ) { - textures.setupRenderTarget( renderTarget ); + renderer.setMode( _gl.POINTS ); - } else if ( renderTargetProperties.__hasExternalTextures ) { + } else if ( object.isSprite ) { - // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); + renderer.setMode( _gl.TRIANGLES ); - } + } - const texture = renderTarget.texture; + if ( object.isBatchedMesh ) { - if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + if ( object._multiDrawInstances !== null ) { - isRenderTarget3D = true; + // @deprecated, r174 + warnOnce( 'THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); - } + } else { - const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; + if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { - if ( renderTarget.isWebGLCubeRenderTarget ) { + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; + const uniforms = properties.get( material ).currentProgram.getUniforms(); + for ( let i = 0; i < drawCount; i ++ ) { - if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { + uniforms.setValue( _gl, '_gl_DrawID', i ); + renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); - framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; + } } else { - framebuffer = __webglFramebuffer[ activeCubeFace ]; + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); } - isCube = true; + } - } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { + } else if ( object.isInstancedMesh ) { - framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; + renderer.renderInstances( drawStart, drawCount, object.count ); - } else { + } else if ( geometry.isInstancedBufferGeometry ) { - if ( Array.isArray( __webglFramebuffer ) ) { - - framebuffer = __webglFramebuffer[ activeMipmapLevel ]; - - } else { - - framebuffer = __webglFramebuffer; - - } - - } + const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; + const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; + renderer.renderInstances( drawStart, drawCount, instanceCount ); } else { - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); - _currentScissorTest = _scissorTest; + renderer.render( drawStart, drawCount ); } - const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + }; - if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { + // Compile - state.drawBuffers( renderTarget, framebuffer ); + function prepareMaterial( material, scene, object ) { - } + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); + material.side = BackSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - if ( isCube ) { + material.side = FrontSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); + material.side = DoubleSide; - } else if ( isRenderTarget3D ) { + } else { - const textureProperties = properties.get( renderTarget.texture ); - const layer = activeCubeFace || 0; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); + getProgram( material, scene, object ); } - _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings - - }; - - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; - - } + } - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + /** + * Compiles all materials in the scene with the camera. This is useful to precompile shaders + * before the first rendering. If you want to add a 3D object to an existing scene, use the third + * optional parameter for applying the target scene. + * + * Note that the (target) scene's lighting and environment must be configured before calling this method. + * + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Set} The precompiled materials. + */ + this.compile = function ( scene, camera, targetScene = null ) { - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { + if ( targetScene === null ) targetScene = scene; - framebuffer = framebuffer[ activeCubeFaceIndex ]; + currentRenderState = renderStates.get( targetScene ); + currentRenderState.init( camera ); - } + renderStateStack.push( currentRenderState ); - if ( framebuffer ) { + // gather lights from both the target scene and the new object that will be added to the scene. - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + targetScene.traverseVisible( function ( object ) { - try { + if ( object.isLight && object.layers.test( camera.layers ) ) { - const texture = renderTarget.texture; - const textureFormat = texture.format; - const textureType = texture.type; + currentRenderState.pushLight( object ); - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { + if ( object.castShadow ) { - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; + currentRenderState.pushShadow( object ); } - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); - - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! halfFloatSupportedByExt ) { + } - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; + } ); - } + if ( scene !== targetScene ) { - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + scene.traverseVisible( function ( object ) { - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + if ( object.isLight && object.layers.test( camera.layers ) ) { - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); + currentRenderState.pushLight( object ); - } + if ( object.castShadow ) { - } finally { + currentRenderState.pushShadow( object ); - // restore framebuffer of current render target if necessary + } - const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + } - } + } ); } - }; - - this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { + currentRenderState.setupLights(); - const levelScale = Math.pow( 2, - level ); - const width = Math.floor( texture.image.width * levelScale ); - const height = Math.floor( texture.image.height * levelScale ); + // Only initialize materials in the new scene, not the targetScene. - textures.setTexture2D( texture, 0 ); + const materials = new Set(); - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); + scene.traverse( function ( object ) { - state.unbindTexture(); + if ( ! ( object.isMesh || object.isPoints || object.isLine || object.isSprite ) ) { - }; + return; - this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { + } - const width = srcTexture.image.width; - const height = srcTexture.image.height; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); + const material = object.material; - textures.setTexture2D( dstTexture, 0 ); + if ( material ) { - // As another texture upload may have changed pixelStorei - // parameters, make sure they are correct for the dstTexture - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + if ( Array.isArray( material ) ) { - if ( srcTexture.isDataTexture ) { + for ( let i = 0; i < material.length; i ++ ) { - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); + const material2 = material[ i ]; - } else { + prepareMaterial( material2, targetScene, object ); + materials.add( material2 ); - if ( srcTexture.isCompressedTexture ) { + } - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); + } else { - } else { + prepareMaterial( material, targetScene, object ); + materials.add( material ); - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); + } } - } + } ); - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); + currentRenderState = renderStateStack.pop(); - state.unbindTexture(); + return materials; }; - this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { - - if ( _this.isWebGL1Renderer ) { + // compileAsync - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); - return; + /** + * Asynchronous version of {@link WebGLRenderer#compile}. + * + * This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence, + * it is recommended to use this version of `compile()` whenever possible. + * + * @async + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation. + */ + this.compileAsync = function ( scene, camera, targetScene = null ) { - } + const materials = this.compile( scene, camera, targetScene ); - const width = Math.round( sourceBox.max.x - sourceBox.min.x ); - const height = Math.round( sourceBox.max.y - sourceBox.min.y ); - const depth = sourceBox.max.z - sourceBox.min.z + 1; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - let glTarget; + // Wait for all the materials in the new object to indicate that they're + // ready to be used before resolving the promise. - if ( dstTexture.isData3DTexture ) { + return new Promise( ( resolve ) => { - textures.setTexture3D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_3D; + function checkMaterialsReady() { - } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { + materials.forEach( function ( material ) { - textures.setTexture2DArray( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D_ARRAY; + const materialProperties = properties.get( material ); + const program = materialProperties.currentProgram; - } else { + if ( program.isReady() ) { - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); - return; + // remove any programs that report they're ready to use from the list + materials.delete( material ); - } + } - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + } ); - const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); - const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); + // once the list of compiling materials is empty, call the callback - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ level ] : srcTexture.image; + if ( materials.size === 0 ) { - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); + resolve( scene ); + return; - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + } - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); + // if some materials are still not ready, wait a bit and check again - } else { + setTimeout( checkMaterialsReady, 10 ); - if ( dstTexture.isCompressedArrayTexture ) { + } - _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); + if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { - } else { + // If we can check the compilation status of the materials without + // blocking then do so right away. - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); + checkMaterialsReady(); - } + } else { - } + // Otherwise start by waiting a bit to give the materials we just + // initialized a chance to finish. - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); + setTimeout( checkMaterialsReady, 10 ); - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); + } - state.unbindTexture(); + } ); }; - this.initTexture = function ( texture ) { + // Animation Loop - if ( texture.isCubeTexture ) { + let onAnimationFrameCallback = null; - textures.setTextureCube( texture, 0 ); + function onAnimationFrame( time ) { - } else if ( texture.isData3DTexture ) { + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - textures.setTexture3D( texture, 0 ); + } - } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + function onXRSessionStart() { - textures.setTexture2DArray( texture, 0 ); + animation.stop(); - } else { + } - textures.setTexture2D( texture, 0 ); + function onXRSessionEnd() { - } + animation.start(); - state.unbindTexture(); + } - }; + const animation = new WebGLAnimation(); + animation.setAnimationLoop( onAnimationFrame ); - this.resetState = function () { + if ( typeof self !== 'undefined' ) animation.setContext( self ); - _currentActiveCubeFace = 0; - _currentActiveMipmapLevel = 0; - _currentRenderTarget = null; + this.setAnimationLoop = function ( callback ) { - state.reset(); - bindingStates.reset(); + onAnimationFrameCallback = callback; + xr.setAnimationLoop( callback ); + + ( callback === null ) ? animation.stop() : animation.start(); }; - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + xr.addEventListener( 'sessionstart', onXRSessionStart ); + xr.addEventListener( 'sessionend', onXRSessionEnd ); - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + // Rendering - } + /** + * Renders the given scene (or other type of 3D object) using the given camera. + * + * The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget} + * or to the canvas as usual. + * + * By default render buffers are cleared before rendering but you can prevent + * this by setting the property `autoClear` to `false`. If you want to prevent + * only certain buffers being cleared you can `autoClearColor`, `autoClearDepth` + * or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ + this.render = function ( scene, camera ) { - } + if ( camera !== undefined && camera.isCamera !== true ) { - get coordinateSystem() { + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; - return WebGLCoordinateSystem; + } - } + if ( _isContextLost === true ) return; - get outputColorSpace() { + // update scene graph - return this._outputColorSpace; + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - } + // update camera matrices and frustum - set outputColorSpace( colorSpace ) { + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - this._outputColorSpace = colorSpace; + if ( xr.enabled === true && xr.isPresenting === true ) { - const gl = this.getContext(); - gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; - gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; + if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); - } + camera = xr.getCamera(); // use XR camera for rendering - get useLegacyLights() { // @deprecated, r155 + } - console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); - return this._useLegacyLights; + // + if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - } + currentRenderState = renderStates.get( scene, renderStateStack.length ); + currentRenderState.init( camera ); - set useLegacyLights( value ) { // @deprecated, r155 + renderStateStack.push( currentRenderState ); - console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); - this._useLegacyLights = value; + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromProjectionMatrix( _projScreenMatrix, WebGLCoordinateSystem, camera.reversedDepth ); - } + _localClippingEnabled = this.localClippingEnabled; + _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); -} + currentRenderList = renderLists.get( scene, renderListStack.length ); + currentRenderList.init(); -class Fog { + renderListStack.push( currentRenderList ); - constructor( color, near = 1, far = 1000 ) { + if ( xr.enabled === true && xr.isPresenting === true ) { - this.isFog = true; + const depthSensingMesh = _this.xr.getDepthSensingMesh(); - this.name = ''; + if ( depthSensingMesh !== null ) { - this.color = new Color( color ); + projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); - this.near = near; - this.far = far; + } - } + } - clone() { + projectObject( scene, camera, 0, _this.sortObjects ); - return new Fog( this.color, this.near, this.far ); + currentRenderList.finish(); - } + if ( _this.sortObjects === true ) { - toJSON( /* meta */ ) { + currentRenderList.sort( _opaqueSort, _transparentSort ); - return { - type: 'Fog', - name: this.name, - color: this.color.getHex(), - near: this.near, - far: this.far - }; + } - } + _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; + if ( _renderBackground ) { -} + background.addToRenderList( currentRenderList, scene ); -class Scene extends Object3D { + } - constructor() { + // - super(); + this.info.render.frame ++; - this.isScene = true; + if ( _clippingEnabled === true ) clipping.beginShadows(); - this.type = 'Scene'; + const shadowsArray = currentRenderState.state.shadowsArray; - this.background = null; - this.environment = null; - this.fog = null; + shadowMap.render( shadowsArray, scene, camera ); - this.backgroundBlurriness = 0; - this.backgroundIntensity = 1; - this.backgroundRotation = new Euler(); - this.environmentRotation = new Euler(); + if ( _clippingEnabled === true ) clipping.endShadows(); - this.overrideMaterial = null; + // - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + if ( this.info.autoReset === true ) this.info.reset(); - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + // render scene - } + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; - } + currentRenderState.setupLights(); - copy( source, recursive ) { + if ( camera.isArrayCamera ) { - super.copy( source, recursive ); + const cameras = camera.cameras; - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); + if ( transmissiveObjects.length > 0 ) { - this.backgroundBlurriness = source.backgroundBlurriness; - this.backgroundIntensity = source.backgroundIntensity; - this.backgroundRotation.copy( source.backgroundRotation ); - this.environmentRotation.copy( source.environmentRotation ); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + const camera2 = cameras[ i ]; - this.matrixAutoUpdate = source.matrixAutoUpdate; + renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); - return this; + } - } + } - toJSON( meta ) { + if ( _renderBackground ) background.render( scene ); - const data = super.toJSON( meta ); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; - if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; + const camera2 = cameras[ i ]; - data.object.backgroundRotation = this.backgroundRotation.toArray(); - data.object.environmentRotation = this.environmentRotation.toArray(); + renderScene( currentRenderList, scene, camera2, camera2.viewport ); - return data; + } - } + } else { -} + if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); -class InstancedBufferAttribute extends BufferAttribute { + if ( _renderBackground ) background.render( scene ); - constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { + renderScene( currentRenderList, scene, camera ); - super( array, itemSize, normalized ); + } - this.isInstancedBufferAttribute = true; + // - this.meshPerAttribute = meshPerAttribute; + if ( _currentRenderTarget !== null && _currentActiveMipmapLevel === 0 ) { - } + // resolve multisample renderbuffers to a single-sample texture if necessary - copy( source ) { + textures.updateMultisampleRenderTarget( _currentRenderTarget ); - super.copy( source ); + // Generate mipmap if we're using any kind of mipmap filtering - this.meshPerAttribute = source.meshPerAttribute; + textures.updateRenderTargetMipmap( _currentRenderTarget ); - return this; + } - } + // - toJSON() { + if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - const data = super.toJSON(); + // _gl.finish(); - data.meshPerAttribute = this.meshPerAttribute; + bindingStates.resetDefaultState(); + _currentMaterialId = -1; + _currentCamera = null; - data.isInstancedBufferAttribute = true; + renderStateStack.pop(); - return data; + if ( renderStateStack.length > 0 ) { - } + currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; -} + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); -class DataTexture extends Texture { + } else { - constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { + currentRenderState = null; - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + } - this.isDataTexture = true; + renderListStack.pop(); - this.image = { data: data, width: width, height: height }; + if ( renderListStack.length > 0 ) { - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + currentRenderList = renderListStack[ renderListStack.length - 1 ]; - } + } else { -} + currentRenderList = null; -const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); -const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); + } -const _instanceIntersects = []; + }; -const _box3 = /*@__PURE__*/ new Box3(); -const _identity = /*@__PURE__*/ new Matrix4(); -const _mesh$1 = /*@__PURE__*/ new Mesh(); -const _sphere$4 = /*@__PURE__*/ new Sphere(); + function projectObject( object, camera, groupOrder, sortObjects ) { -class InstancedMesh extends Mesh { + if ( object.visible === false ) return; - constructor( geometry, material, count ) { + const visible = object.layers.test( camera.layers ); - super( geometry, material ); + if ( visible ) { - this.isInstancedMesh = true; + if ( object.isGroup ) { - this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - this.instanceColor = null; - this.morphTexture = null; + groupOrder = object.renderOrder; - this.count = count; + } else if ( object.isLOD ) { - this.boundingBox = null; - this.boundingSphere = null; + if ( object.autoUpdate === true ) object.update( camera ); - for ( let i = 0; i < count; i ++ ) { + } else if ( object.isLight ) { - this.setMatrixAt( i, _identity ); + currentRenderState.pushLight( object ); - } + if ( object.castShadow ) { - } + currentRenderState.pushShadow( object ); - computeBoundingBox() { + } - const geometry = this.geometry; - const count = this.count; + } else if ( object.isSprite ) { - if ( this.boundingBox === null ) { + if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - this.boundingBox = new Box3(); + if ( sortObjects ) { - } + _vector4.setFromMatrixPosition( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); - if ( geometry.boundingBox === null ) { + } - geometry.computeBoundingBox(); + const geometry = objects.update( object ); + const material = object.material; - } + if ( material.visible ) { - this.boundingBox.makeEmpty(); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - for ( let i = 0; i < count; i ++ ) { + } - this.getMatrixAt( i, _instanceLocalMatrix ); + } - _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); + } else if ( object.isMesh || object.isLine || object.isPoints ) { - this.boundingBox.union( _box3 ); + if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - } + const geometry = objects.update( object ); + const material = object.material; - } + if ( sortObjects ) { - computeBoundingSphere() { + if ( object.boundingSphere !== undefined ) { - const geometry = this.geometry; - const count = this.count; + if ( object.boundingSphere === null ) object.computeBoundingSphere(); + _vector4.copy( object.boundingSphere.center ); - if ( this.boundingSphere === null ) { + } else { - this.boundingSphere = new Sphere(); + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + _vector4.copy( geometry.boundingSphere.center ); - } + } - if ( geometry.boundingSphere === null ) { + _vector4 + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); - geometry.computeBoundingSphere(); + } - } + if ( Array.isArray( material ) ) { - this.boundingSphere.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { + const groups = geometry.groups; - this.getMatrixAt( i, _instanceLocalMatrix ); + for ( let i = 0, l = groups.length; i < l; i ++ ) { - _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - this.boundingSphere.union( _sphere$4 ); + if ( groupMaterial && groupMaterial.visible ) { - } + currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group ); - } + } - copy( source, recursive ) { + } - super.copy( source, recursive ); + } else if ( material.visible ) { - this.instanceMatrix.copy( source.instanceMatrix ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); + } - this.count = source.count; + } - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + } - return this; + } - } + const children = object.children; - getColorAt( index, color ) { + for ( let i = 0, l = children.length; i < l; i ++ ) { - color.fromArray( this.instanceColor.array, index * 3 ); + projectObject( children[ i ], camera, groupOrder, sortObjects ); - } + } - getMatrixAt( index, matrix ) { + } - matrix.fromArray( this.instanceMatrix.array, index * 16 ); + function renderScene( currentRenderList, scene, camera, viewport ) { - } + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; + const transparentObjects = currentRenderList.transparent; - getMorphAt( index, object ) { + currentRenderState.setupLightsView( camera ); - const objectInfluences = object.morphTargetInfluences; + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - const array = this.morphTexture.source.data.data; + if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - const len = objectInfluences.length + 1; // All influences + the baseInfluenceSum + if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); + if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); + if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - const dataIndex = index * len + 1; // Skip the baseInfluenceSum at the beginning + // Ensure depth buffer writing is enabled so it can be cleared on next render - for ( let i = 0; i < objectInfluences.length; i ++ ) { + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - objectInfluences[ i ] = array[ dataIndex + i ]; + state.setPolygonOffset( false ); } - } + function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - raycast( raycaster, intersects ) { + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - const matrixWorld = this.matrixWorld; - const raycastTimes = this.count; + if ( overrideMaterial !== null ) { - _mesh$1.geometry = this.geometry; - _mesh$1.material = this.material; + return; - if ( _mesh$1.material === undefined ) return; + } - // test with bounding sphere first + if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { - if ( this.boundingSphere === null ) this.computeBoundingSphere(); + currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { + generateMipmaps: true, + type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType, + minFilter: LinearMipmapLinearFilter, + samples: 4, + stencilBuffer: stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false, + colorSpace: ColorManagement.workingColorSpace, + } ); - _sphere$4.copy( this.boundingSphere ); - _sphere$4.applyMatrix4( matrixWorld ); + // debug - if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; + /* + const geometry = new PlaneGeometry(); + const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - // now test each instance + const mesh = new Mesh( geometry, material ); + scene.add( mesh ); + */ - for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { + } - // calculate the world matrix for each instance + const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; - this.getMatrixAt( instanceId, _instanceLocalMatrix ); + const activeViewport = camera.viewport || _currentViewport; + transmissionRenderTarget.setSize( activeViewport.z * _this.transmissionResolutionScale, activeViewport.w * _this.transmissionResolutionScale ); - _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); + // - // the mesh represents this single instance + const currentRenderTarget = _this.getRenderTarget(); + const currentActiveCubeFace = _this.getActiveCubeFace(); + const currentActiveMipmapLevel = _this.getActiveMipmapLevel(); - _mesh$1.matrixWorld = _instanceWorldMatrix; + _this.setRenderTarget( transmissionRenderTarget ); - _mesh$1.raycast( raycaster, _instanceIntersects ); + _this.getClearColor( _currentClearColor ); + _currentClearAlpha = _this.getClearAlpha(); + if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - // process the result of raycast + _this.clear(); - for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { + if ( _renderBackground ) background.render( scene ); - const intersect = _instanceIntersects[ i ]; - intersect.instanceId = instanceId; - intersect.object = this; - intersects.push( intersect ); + // Turn off the features which can affect the frag color for opaque objects pass. + // Otherwise they are applied twice in opaque objects pass and transmission objects pass. + const currentToneMapping = _this.toneMapping; + _this.toneMapping = NoToneMapping; - } + // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). + // Transmission render pass requires viewport to match the transmissionRenderTarget. + const currentCameraViewport = camera.viewport; + if ( camera.viewport !== undefined ) camera.viewport = undefined; - _instanceIntersects.length = 0; + currentRenderState.setupLightsView( camera ); - } + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - } + renderObjects( opaqueObjects, scene, camera ); - setColorAt( index, color ) { + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - if ( this.instanceColor === null ) { + if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); + let renderTargetNeedsUpdate = false; - } + for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - color.toArray( this.instanceColor.array, index * 3 ); + const renderItem = transmissiveObjects[ i ]; - } + const object = renderItem.object; + const geometry = renderItem.geometry; + const material = renderItem.material; + const group = renderItem.group; - setMatrixAt( index, matrix ) { + if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - matrix.toArray( this.instanceMatrix.array, index * 16 ); + const currentSide = material.side; - } + material.side = BackSide; + material.needsUpdate = true; - setMorphAt( index, object ) { + renderObject( object, scene, camera, geometry, material, group ); - const objectInfluences = object.morphTargetInfluences; + material.side = currentSide; + material.needsUpdate = true; - const len = objectInfluences.length + 1; // morphBaseInfluence + all influences + renderTargetNeedsUpdate = true; - if ( this.morphTexture === null ) { + } - this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType ); + } - } + if ( renderTargetNeedsUpdate === true ) { - const array = this.morphTexture.source.data.data; + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - let morphInfluencesSum = 0; + } - for ( let i = 0; i < objectInfluences.length; i ++ ) { + } - morphInfluencesSum += objectInfluences[ i ]; + _this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - } + _this.setClearColor( _currentClearColor, _currentClearAlpha ); - const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; - const dataIndex = len * index; + _this.toneMapping = currentToneMapping; - array[ dataIndex ] = morphBaseInfluence; + } - array.set( objectInfluences, dataIndex + 1 ); + function renderObjects( renderList, scene, camera ) { - } + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - updateMorphTargets() { + for ( let i = 0, l = renderList.length; i < l; i ++ ) { - } + const renderItem = renderList[ i ]; - dispose() { + const object = renderItem.object; + const geometry = renderItem.geometry; + const group = renderItem.group; + let material = renderItem.material; - this.dispatchEvent( { type: 'dispose' } ); + if ( material.allowOverride === true && overrideMaterial !== null ) { - } + material = overrideMaterial; -} + } -class LineBasicMaterial extends Material { + if ( object.layers.test( camera.layers ) ) { - constructor( parameters ) { + renderObject( object, scene, camera, geometry, material, group ); - super(); + } - this.isLineBasicMaterial = true; + } - this.type = 'LineBasicMaterial'; + } - this.color = new Color( 0xffffff ); + function renderObject( object, scene, camera, geometry, material, group ) { - this.map = null; + object.onBeforeRender( _this, scene, camera, geometry, material, group ); - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - this.fog = true; + material.onBeforeRender( _this, scene, camera, geometry, object, group ); - this.setValues( parameters ); + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - } + material.side = BackSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + material.side = FrontSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - copy( source ) { + material.side = DoubleSide; - super.copy( source ); + } else { - this.color.copy( source.color ); + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - this.map = source.map; + } - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; + object.onAfterRender( _this, scene, camera, geometry, material, group ); - this.fog = source.fog; + } - return this; + function getProgram( material, scene, object ) { - } + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... -} + const materialProperties = properties.get( material ); -const _start$1 = /*@__PURE__*/ new Vector3(); -const _end$1 = /*@__PURE__*/ new Vector3(); -const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); -const _ray$2 = /*@__PURE__*/ new Ray(); -const _sphere$3 = /*@__PURE__*/ new Sphere(); + const lights = currentRenderState.state.lights; + const shadowsArray = currentRenderState.state.shadowsArray; -class Line extends Object3D { + const lightsStateVersion = lights.state.version; - constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { + const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); + const programCacheKey = programCache.getProgramCacheKey( parameters ); - super(); + let programs = materialProperties.programs; - this.isLine = true; + // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - this.type = 'Line'; + materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; + materialProperties.fog = scene.fog; + materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); + materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; - this.geometry = geometry; - this.material = material; + if ( programs === undefined ) { - this.updateMorphTargets(); + // new material - } + material.addEventListener( 'dispose', onMaterialDispose ); - copy( source, recursive ) { + programs = new Map(); + materialProperties.programs = programs; - super.copy( source, recursive ); + } - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; + let program = programs.get( programCacheKey ); - return this; + if ( program !== undefined ) { - } + // early out if program and light state is identical - computeLineDistances() { + if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - const geometry = this.geometry; + updateCommonMaterialProperties( material, parameters ); - // we assume non-indexed geometry + return program; - if ( geometry.index === null ) { + } - const positionAttribute = geometry.attributes.position; - const lineDistances = [ 0 ]; + } else { - for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { + parameters.uniforms = programCache.getUniforms( material ); - _start$1.fromBufferAttribute( positionAttribute, i - 1 ); - _end$1.fromBufferAttribute( positionAttribute, i ); + material.onBeforeCompile( parameters, _this ); - lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _start$1.distanceTo( _end$1 ); + program = programCache.acquireProgram( parameters, programCacheKey ); + programs.set( programCacheKey, program ); - } + materialProperties.uniforms = parameters.uniforms; - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + } - } else { + const uniforms = materialProperties.uniforms; - console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - } + uniforms.clippingPlanes = clipping.uniform; - return this; + } - } + updateCommonMaterialProperties( material, parameters ); - raycast( raycaster, intersects ) { + // store the light setup it was created for - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Line.threshold; - const drawRange = geometry.drawRange; + materialProperties.needsLights = materialNeedsLights( material ); + materialProperties.lightsStateVersion = lightsStateVersion; - // Checking boundingSphere distance to ray + if ( materialProperties.needsLights ) { - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + // wire up the material to this renderer's lighting state - _sphere$3.copy( geometry.boundingSphere ); - _sphere$3.applyMatrix4( matrixWorld ); - _sphere$3.radius += threshold; + uniforms.ambientLightColor.value = lights.state.ambient; + uniforms.lightProbe.value = lights.state.probe; + uniforms.directionalLights.value = lights.state.directional; + uniforms.directionalLightShadows.value = lights.state.directionalShadow; + uniforms.spotLights.value = lights.state.spot; + uniforms.spotLightShadows.value = lights.state.spotShadow; + uniforms.rectAreaLights.value = lights.state.rectArea; + uniforms.ltc_1.value = lights.state.rectAreaLTC1; + uniforms.ltc_2.value = lights.state.rectAreaLTC2; + uniforms.pointLights.value = lights.state.point; + uniforms.pointLightShadows.value = lights.state.pointShadow; + uniforms.hemisphereLights.value = lights.state.hemi; - if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; + uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; + uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; + uniforms.spotShadowMap.value = lights.state.spotShadowMap; + uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; + uniforms.spotLightMap.value = lights.state.spotLightMap; + uniforms.pointShadowMap.value = lights.state.pointShadowMap; + uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; + // TODO (abelnation): add area lights shadow info to uniforms - // + } - _inverseMatrix$2.copy( matrixWorld ).invert(); - _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); + materialProperties.currentProgram = program; + materialProperties.uniformsList = null; - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; + return program; - const vStart = new Vector3(); - const vEnd = new Vector3(); - const interSegment = new Vector3(); - const interRay = new Vector3(); - const step = this.isLineSegments ? 2 : 1; + } - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; + function getUniformList( materialProperties ) { - if ( index !== null ) { + if ( materialProperties.uniformsList === null ) { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + const progUniforms = materialProperties.currentProgram.getUniforms(); + materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); - for ( let i = start, l = end - 1; i < l; i += step ) { + } - const a = index.getX( i ); - const b = index.getX( i + 1 ); + return materialProperties.uniformsList; - vStart.fromBufferAttribute( positionAttribute, a ); - vEnd.fromBufferAttribute( positionAttribute, b ); + } - const distSq = _ray$2.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + function updateCommonMaterialProperties( material, parameters ) { - if ( distSq > localThresholdSq ) continue; + const materialProperties = properties.get( material ); - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + materialProperties.outputColorSpace = parameters.outputColorSpace; + materialProperties.batching = parameters.batching; + materialProperties.batchingColor = parameters.batchingColor; + materialProperties.instancing = parameters.instancing; + materialProperties.instancingColor = parameters.instancingColor; + materialProperties.instancingMorph = parameters.instancingMorph; + materialProperties.skinning = parameters.skinning; + materialProperties.morphTargets = parameters.morphTargets; + materialProperties.morphNormals = parameters.morphNormals; + materialProperties.morphColors = parameters.morphColors; + materialProperties.morphTargetsCount = parameters.morphTargetsCount; + materialProperties.numClippingPlanes = parameters.numClippingPlanes; + materialProperties.numIntersection = parameters.numClipIntersection; + materialProperties.vertexAlphas = parameters.vertexAlphas; + materialProperties.vertexTangents = parameters.vertexTangents; + materialProperties.toneMapping = parameters.toneMapping; - const distance = raycaster.ray.origin.distanceTo( interRay ); + } - if ( distance < raycaster.near || distance > raycaster.far ) continue; + function setProgram( camera, scene, geometry, material, object ) { - intersects.push( { + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + textures.resetTextureUnits(); - } ); + const fog = scene.fog; + const environment = material.isMeshStandardMaterial ? scene.environment : null; + const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; + const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); + const morphTargets = !! geometry.morphAttributes.position; + const morphNormals = !! geometry.morphAttributes.normal; + const morphColors = !! geometry.morphAttributes.color; - } + let toneMapping = NoToneMapping; - } else { + if ( material.toneMapped ) { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); + if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { - for ( let i = start, l = end - 1; i < l; i += step ) { + toneMapping = _this.toneMapping; - vStart.fromBufferAttribute( positionAttribute, i ); - vEnd.fromBufferAttribute( positionAttribute, i + 1 ); + } - const distSq = _ray$2.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + } - if ( distSq > localThresholdSq ) continue; + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + const materialProperties = properties.get( material ); + const lights = currentRenderState.state.lights; - const distance = raycaster.ray.origin.distanceTo( interRay ); + if ( _clippingEnabled === true ) { - if ( distance < raycaster.near || distance > raycaster.far ) continue; + if ( _localClippingEnabled === true || camera !== _currentCamera ) { - intersects.push( { + const useCache = + camera === _currentCamera && + material.id === _currentMaterialId; - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + // we might want to call this function with some ClippingGroup + // object instead of the material, once it becomes feasible + // (#8465, #8379) + clipping.setState( material, camera, useCache ); - } ); + } } - } + // - } + let needsProgramChange = false; - updateMorphTargets() { + if ( material.version === materialProperties.__version ) { - const geometry = this.geometry; + if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); + needsProgramChange = true; - if ( keys.length > 0 ) { + } else if ( materialProperties.outputColorSpace !== colorSpace ) { - const morphAttribute = morphAttributes[ keys[ 0 ] ]; + needsProgramChange = true; - if ( morphAttribute !== undefined ) { + } else if ( object.isBatchedMesh && materialProperties.batching === false ) { - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + needsProgramChange = true; - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { - const name = morphAttribute[ m ].name || String( m ); + needsProgramChange = true; - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { - } + needsProgramChange = true; - } + } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { - } + needsProgramChange = true; - } + } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { -} + needsProgramChange = true; -const _start = /*@__PURE__*/ new Vector3(); -const _end = /*@__PURE__*/ new Vector3(); + } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { -class LineSegments extends Line { + needsProgramChange = true; - constructor( geometry, material ) { + } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - super( geometry, material ); + needsProgramChange = true; - this.isLineSegments = true; + } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - this.type = 'LineSegments'; + needsProgramChange = true; - } + } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { - computeLineDistances() { + needsProgramChange = true; - const geometry = this.geometry; + } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { - // we assume non-indexed geometry + needsProgramChange = true; - if ( geometry.index === null ) { + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { - const positionAttribute = geometry.attributes.position; - const lineDistances = []; + needsProgramChange = true; - for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { - _start.fromBufferAttribute( positionAttribute, i ); - _end.fromBufferAttribute( positionAttribute, i + 1 ); + needsProgramChange = true; - lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; - lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); + } else if ( materialProperties.envMap !== envMap ) { - } + needsProgramChange = true; - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + } else if ( material.fog === true && materialProperties.fog !== fog ) { - } else { + needsProgramChange = true; - console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + } else if ( materialProperties.numClippingPlanes !== undefined && + ( materialProperties.numClippingPlanes !== clipping.numPlanes || + materialProperties.numIntersection !== clipping.numIntersection ) ) { - } + needsProgramChange = true; - return this; + } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - } + needsProgramChange = true; -} + } else if ( materialProperties.vertexTangents !== vertexTangents ) { -class PointsMaterial extends Material { + needsProgramChange = true; - constructor( parameters ) { + } else if ( materialProperties.morphTargets !== morphTargets ) { - super(); + needsProgramChange = true; - this.isPointsMaterial = true; + } else if ( materialProperties.morphNormals !== morphNormals ) { - this.type = 'PointsMaterial'; + needsProgramChange = true; - this.color = new Color( 0xffffff ); + } else if ( materialProperties.morphColors !== morphColors ) { - this.map = null; + needsProgramChange = true; - this.alphaMap = null; + } else if ( materialProperties.toneMapping !== toneMapping ) { - this.size = 1; - this.sizeAttenuation = true; + needsProgramChange = true; - this.fog = true; + } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { - this.setValues( parameters ); + needsProgramChange = true; - } + } - copy( source ) { + } else { - super.copy( source ); + needsProgramChange = true; + materialProperties.__version = material.version; - this.color.copy( source.color ); + } - this.map = source.map; + // - this.alphaMap = source.alphaMap; + let program = materialProperties.currentProgram; - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; + if ( needsProgramChange === true ) { - this.fog = source.fog; + program = getProgram( material, scene, object ); - return this; + } - } + let refreshProgram = false; + let refreshMaterial = false; + let refreshLights = false; -} + const p_uniforms = program.getUniforms(), + m_uniforms = materialProperties.uniforms; -const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _ray$1 = /*@__PURE__*/ new Ray(); -const _sphere$2 = /*@__PURE__*/ new Sphere(); -const _position = /*@__PURE__*/ new Vector3(); + if ( state.useProgram( program.program ) ) { -class Points extends Object3D { + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; - constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { + } - super(); + if ( material.id !== _currentMaterialId ) { - this.isPoints = true; + _currentMaterialId = material.id; - this.type = 'Points'; + refreshMaterial = true; - this.geometry = geometry; - this.material = material; + } - this.updateMorphTargets(); + if ( refreshProgram || _currentCamera !== camera ) { - } + // common camera uniforms - copy( source, recursive ) { + const reversedDepthBuffer = state.buffers.depth.getReversed(); - super.copy( source, recursive ); + if ( reversedDepthBuffer && camera.reversedDepth !== true ) { - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; + camera._reversedDepth = true; + camera.updateProjectionMatrix(); - return this; + } - } + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - raycast( raycaster, intersects ) { + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Points.threshold; - const drawRange = geometry.drawRange; + const uCamPos = p_uniforms.map.cameraPosition; - // Checking boundingSphere distance to ray + if ( uCamPos !== undefined ) { - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - _sphere$2.copy( geometry.boundingSphere ); - _sphere$2.applyMatrix4( matrixWorld ); - _sphere$2.radius += threshold; + } - if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; + if ( capabilities.logarithmicDepthBuffer ) { - // + p_uniforms.setValue( _gl, 'logDepthBufFC', + 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - _inverseMatrix$1.copy( matrixWorld ).invert(); - _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); + } - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; + // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; + if ( material.isMeshPhongMaterial || + material.isMeshToonMaterial || + material.isMeshLambertMaterial || + material.isMeshBasicMaterial || + material.isMeshStandardMaterial || + material.isShaderMaterial ) { - if ( index !== null ) { + p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + } - for ( let i = start, il = end; i < il; i ++ ) { + if ( _currentCamera !== camera ) { - const a = index.getX( i ); + _currentCamera = camera; - _position.fromBufferAttribute( positionAttribute, a ); + // lighting uniforms depend on the camera so enforce an update + // now, in case this material supports lights - or later, when + // the next material that does gets activated: - testPoint( _position, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); + refreshMaterial = true; // set to true on material change + refreshLights = true; // remains set until update done + + } } - } else { + // skinning and morph target uniforms must be set even if material didn't change + // auto-setting of texture unit for bone and morph texture must go before other textures + // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); + if ( object.isSkinnedMesh ) { - for ( let i = start, l = end; i < l; i ++ ) { + p_uniforms.setOptional( _gl, object, 'bindMatrix' ); + p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - _position.fromBufferAttribute( positionAttribute, i ); + const skeleton = object.skeleton; - testPoint( _position, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); + if ( skeleton ) { - } + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - } + p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - } + } - updateMorphTargets() { + } - const geometry = this.geometry; + if ( object.isBatchedMesh ) { - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); + p_uniforms.setOptional( _gl, object, 'batchingTexture' ); + p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); - if ( keys.length > 0 ) { + p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); + p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); - const morphAttribute = morphAttributes[ keys[ 0 ] ]; + p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); + if ( object._colorsTexture !== null ) { - if ( morphAttribute !== undefined ) { + p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + } - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + } - const name = morphAttribute[ m ].name || String( m ); + const morphAttributes = geometry.morphAttributes; - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { - } + morphtargets.update( object, geometry, program ); } - } + if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - } + materialProperties.receiveShadow = object.receiveShadow; + p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); -} + } -function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { + // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 - const rayPointDistanceSq = _ray$1.distanceSqToPoint( point ); + if ( material.isMeshGouraudMaterial && material.envMap !== null ) { - if ( rayPointDistanceSq < localThresholdSq ) { + m_uniforms.envMap.value = envMap; - const intersectPoint = new Vector3(); + m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - _ray$1.closestPointToPoint( point, intersectPoint ); - intersectPoint.applyMatrix4( matrixWorld ); + } - const distance = raycaster.ray.origin.distanceTo( intersectPoint ); + if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) { - if ( distance < raycaster.near || distance > raycaster.far ) return; + m_uniforms.envMapIntensity.value = scene.environmentIntensity; - intersects.push( { + } - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint, - index: index, - face: null, - object: object + if ( refreshMaterial ) { - } ); + p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - } + if ( materialProperties.needsLights ) { -} + // the current material requires lighting info -class CanvasTexture extends Texture { + // note: all lighting uniforms are always set correctly + // they simply reference the renderer's state for their + // values + // + // use the current material's .needsUpdate flags to set + // the GL state when required - constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + } - this.isCanvasTexture = true; + // refresh uniforms common to several materials - this.needsUpdate = true; + if ( fog && material.fog === true ) { - } + materials.refreshFogUniforms( m_uniforms, fog ); -} + } -/** - * Extensible curve object. - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: - * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve - * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ + materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); -class Curve { + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - constructor() { + } - this.type = 'Curve'; + if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - this.arcLengthDivisions = 200; + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); + material.uniformsNeedUpdate = false; - } + } + + if ( material.isSpriteMaterial ) { - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] + p_uniforms.setValue( _gl, 'center', object.center ); - getPoint( /* t, optionalTarget */ ) { + } - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; + // common matrices - } + p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); + p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - // Get point at relative position in curve according to arc length - // - u [0 .. 1] + // UBOs - getPointAt( u, optionalTarget ) { + if ( material.isShaderMaterial || material.isRawShaderMaterial ) { - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); + const groups = material.uniformsGroups; - } + for ( let i = 0, l = groups.length; i < l; i ++ ) { - // Get sequence of points using getPoint( t ) + const group = groups[ i ]; - getPoints( divisions = 5 ) { + uniformsGroups.update( group, program ); + uniformsGroups.bind( group, program ); - const points = []; + } - for ( let d = 0; d <= divisions; d ++ ) { + } - points.push( this.getPoint( d / divisions ) ); + return program; } - return points; + // If uniforms are marked as clean, they don't need to be loaded to the GPU. - } + function markUniformsLightsNeedsUpdate( uniforms, value ) { - // Get sequence of points using getPointAt( u ) + uniforms.ambientLightColor.needsUpdate = value; + uniforms.lightProbe.needsUpdate = value; - getSpacedPoints( divisions = 5 ) { + uniforms.directionalLights.needsUpdate = value; + uniforms.directionalLightShadows.needsUpdate = value; + uniforms.pointLights.needsUpdate = value; + uniforms.pointLightShadows.needsUpdate = value; + uniforms.spotLights.needsUpdate = value; + uniforms.spotLightShadows.needsUpdate = value; + uniforms.rectAreaLights.needsUpdate = value; + uniforms.hemisphereLights.needsUpdate = value; - const points = []; + } - for ( let d = 0; d <= divisions; d ++ ) { + function materialNeedsLights( material ) { - points.push( this.getPointAt( d / divisions ) ); + return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || + material.isMeshStandardMaterial || material.isShadowMaterial || + ( material.isShaderMaterial && material.lights === true ); } - return points; + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ + this.getActiveCubeFace = function () { - } + return _currentActiveCubeFace; - // Get total curve arc length + }; - getLength() { + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ + this.getActiveMipmapLevel = function () { - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; + return _currentActiveMipmapLevel; - } + }; - // Get list of cumulative segment lengths + /** + * Returns the active render target. + * + * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target + * is currently set. + */ + this.getRenderTarget = function () { - getLengths( divisions = this.arcLengthDivisions ) { + return _currentRenderTarget; - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { + }; - return this.cacheArcLengths; + this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - } + const renderTargetProperties = properties.get( renderTarget ); - this.needsUpdate = false; + renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false; + if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) { - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth buffer. Disable use of the extension. + renderTargetProperties.__useRenderToTexture = false; - cache.push( 0 ); + } - for ( let p = 1; p <= divisions; p ++ ) { + properties.get( renderTarget.texture ).__webglTexture = colorTexture; + properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture; - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; + renderTargetProperties.__hasExternalTextures = true; - } + }; - this.cacheArcLengths = cache; + this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - return cache; // { sums: cache, sum: sum }; Sum is in the last element. + const renderTargetProperties = properties.get( renderTarget ); + renderTargetProperties.__webglFramebuffer = defaultFramebuffer; + renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - } + }; - updateArcLengths() { + const _scratchFrameBuffer = _gl.createFramebuffer(); - this.needsUpdate = true; - this.getLengths(); + /** + * Sets the active rendertarget. + * + * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given, + * the canvas is set as the active render target instead. + * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target. + * Indicates the z layer to render in to when using 3D or array render targets. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - } + _currentRenderTarget = renderTarget; + _currentActiveCubeFace = activeCubeFace; + _currentActiveMipmapLevel = activeMipmapLevel; - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant + let useDefaultFramebuffer = true; + let framebuffer = null; + let isCube = false; + let isRenderTarget3D = false; - getUtoTmapping( u, distance ) { + if ( renderTarget ) { - const arcLengths = this.getLengths(); + const renderTargetProperties = properties.get( renderTarget ); - let i = 0; - const il = arcLengths.length; + if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - let targetArcLength; // The targeted u distance value to get + // We need to make sure to rebind the framebuffer. + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + useDefaultFramebuffer = false; - if ( distance ) { + } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - targetArcLength = distance; + textures.setupRenderTarget( renderTarget ); - } else { + } else if ( renderTargetProperties.__hasExternalTextures ) { - targetArcLength = u * arcLengths[ il - 1 ]; + // Color and depth texture must be rebound in order for the swapchain to update. + textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - } + } else if ( renderTarget.depthBuffer ) { - // binary search for the index with largest value smaller than target u distance + // check if the depth texture is already bound to the frame buffer and that it's been initialized + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__boundDepthTexture !== depthTexture ) { - let low = 0, high = il - 1, comparison; + // check if the depth texture is compatible + if ( + depthTexture !== null && + properties.has( depthTexture ) && + ( renderTarget.width !== depthTexture.image.width || renderTarget.height !== depthTexture.image.height ) + ) { - while ( low <= high ) { + throw new Error( 'WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.' ); - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + } - comparison = arcLengths[ i ] - targetArcLength; + // Swap the depth buffer to the currently attached one + textures.setupDepthRenderbuffer( renderTarget ); - if ( comparison < 0 ) { + } - low = i + 1; + } - } else if ( comparison > 0 ) { + const texture = renderTarget.texture; - high = i - 1; + if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - } else { + isRenderTarget3D = true; - high = i; - break; + } - // DONE + const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - } + if ( renderTarget.isWebGLCubeRenderTarget ) { - } + if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { - i = high; + framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; - if ( arcLengths[ i ] === targetArcLength ) { + } else { - return i / ( il - 1 ); + framebuffer = __webglFramebuffer[ activeCubeFace ]; - } + } - // we could get finer grain at lengths, or use simple interpolation between two points + isCube = true; - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; + } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - const segmentLength = lengthAfter - lengthBefore; + framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - // determine where we are between the 'before' and 'after' points + } else { - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + if ( Array.isArray( __webglFramebuffer ) ) { - // add that fractional amount to t + framebuffer = __webglFramebuffer[ activeMipmapLevel ]; - const t = ( i + segmentFraction ) / ( il - 1 ); + } else { - return t; + framebuffer = __webglFramebuffer; - } + } - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation + } - getTangent( t, optionalTarget ) { + _currentViewport.copy( renderTarget.viewport ); + _currentScissor.copy( renderTarget.scissor ); + _currentScissorTest = renderTarget.scissorTest; - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; + } else { - // Capping in case of danger + _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); + _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); + _currentScissorTest = _scissorTest; - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; + } - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); + // Use a scratch frame buffer if rendering to a mip level to avoid depth buffers + // being bound that are different sizes. + if ( activeMipmapLevel !== 0 ) { - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); + framebuffer = _scratchFrameBuffer; - tangent.copy( pt2 ).sub( pt1 ).normalize(); + } - return tangent; + const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - } + if ( framebufferBound && useDefaultFramebuffer ) { - getTangentAt( u, optionalTarget ) { + state.drawBuffers( renderTarget, framebuffer ); - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); + } - } + state.viewport( _currentViewport ); + state.scissor( _currentScissor ); + state.setScissorTest( _currentScissorTest ); - computeFrenetFrames( segments, closed ) { + if ( isCube ) { - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - const normal = new Vector3(); + } else if ( isRenderTarget3D ) { - const tangents = []; - const normals = []; - const binormals = []; + const layer = activeCubeFace; - const vec = new Vector3(); - const mat = new Matrix4(); + for ( let i = 0; i < renderTarget.textures.length; i ++ ) { - // compute the tangent vectors for each segment on the curve + const textureProperties = properties.get( renderTarget.textures[ i ] ); - for ( let i = 0; i <= segments; i ++ ) { + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, textureProperties.__webglTexture, activeMipmapLevel, layer ); - const u = i / segments; + } - tangents[ i ] = this.getTangentAt( u, new Vector3() ); + } else if ( renderTarget !== null && activeMipmapLevel !== 0 ) { - } + // Only bind the frame buffer if we are using a scratch frame buffer to render to a mipmap. + // If we rebind the texture when using a multi sample buffer then an error about inconsistent samples will be thrown. + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, textureProperties.__webglTexture, activeMipmapLevel ); - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component + } - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); + _currentMaterialId = -1; // reset current material to ensure correct uniform bindings - if ( tx <= min ) { + }; - min = tx; - normal.set( 1, 0, 0 ); + /** + * Reads the pixel data from the given render target into the given buffer. + * + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + */ + this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - } + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - if ( ty <= min ) { + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + return; - min = ty; - normal.set( 0, 1, 0 ); + } - } + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - if ( tz <= min ) { + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - normal.set( 0, 0, 1 ); + framebuffer = framebuffer[ activeCubeFaceIndex ]; - } + } - vec.crossVectors( tangents[ 0 ], normal ).normalize(); + if ( framebuffer ) { - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + try { - // compute the slowly-varying normal and binormal vectors for each segment on the curve + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - for ( let i = 1; i <= segments; i ++ ) { + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - normals[ i ] = normals[ i - 1 ].clone(); + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); + return; - binormals[ i ] = binormals[ i - 1 ].clone(); + } - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); + if ( ! capabilities.textureTypeReadable( textureType ) ) { - if ( vec.length() > Number.EPSILON ) { + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); + return; - vec.normalize(); + } - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - } + // when using MRT, select the correct color buffer for the subsequent read command - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - } + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + } - if ( closed === true ) { + } finally { - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; + // restore framebuffer of current render target if necessary - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { + const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - theta = - theta; + } } - for ( let i = 1; i <= segments; i ++ ) { + }; - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + /** + * Asynchronous, non-blocking version of {@link WebGLRenderer#readRenderTargetPixels}. + * + * It is recommended to use this version of `readRenderTargetPixels()` whenever possible. + * + * @async + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { + + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { + + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); } - } + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; + framebuffer = framebuffer[ activeCubeFaceIndex ]; - } + } - clone() { + if ( framebuffer ) { - return new this.constructor().copy( this ); + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - } + // set the active frame buffer to the one we want to read + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - copy( source ) { + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - this.arcLengthDivisions = source.arcLengthDivisions; + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - return this; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); - } + } - toJSON() { + if ( ! capabilities.textureTypeReadable( textureType ) ) { - const data = { - metadata: { - version: 4.6, - type: 'Curve', - generator: 'Curve.toJSON' - } - }; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; + } - return data; + const glBuffer = _gl.createBuffer(); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); - } + // when using MRT, select the correct color buffer for the subsequent read command - fromJSON( json ) { + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - this.arcLengthDivisions = json.arcLengthDivisions; + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); - return this; + // reset the frame buffer to the currently set buffer before waiting + const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, currFramebuffer ); - } + // check if the commands have finished every 8 ms + const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); -} + _gl.flush(); -class EllipseCurve extends Curve { + await probeAsync( _gl, sync, 4 ); - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { + // read the data and delete the buffer + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); + _gl.deleteBuffer( glBuffer ); + _gl.deleteSync( sync ); - super(); + return buffer; - this.isEllipseCurve = true; + } else { - this.type = 'EllipseCurve'; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' ); - this.aX = aX; - this.aY = aY; + } - this.xRadius = xRadius; - this.yRadius = yRadius; + } - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; + }; - this.aClockwise = aClockwise; + /** + * Copies pixels from the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} texture - The texture. + * @param {?Vector2} [position=null] - The start position of the copy operation. + * @param {number} [level=0] - The mip level. The default represents the base mip. + */ + this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { - this.aRotation = aRotation; + const levelScale = Math.pow( 2, - level ); + const width = Math.floor( texture.image.width * levelScale ); + const height = Math.floor( texture.image.height * levelScale ); - } + const x = position !== null ? position.x : 0; + const y = position !== null ? position.y : 0; - getPoint( t, optionalTarget = new Vector2() ) { + textures.setTexture2D( texture, 0 ); - const point = optionalTarget; + _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; + state.unbindTexture(); - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; + }; - if ( deltaAngle < Number.EPSILON ) { + const _srcFramebuffer = _gl.createFramebuffer(); + const _dstFramebuffer = _gl.createFramebuffer(); - if ( samePoints ) { + /** + * Copies data of the given source texture into a destination texture. + * + * When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized + * {@link WebGLRenderer#initRenderTarget}. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box2|Box3)} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {?(Vector2|Vector3)} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mipmap level to copy. + * @param {?number} [dstLevel=null] - The destination mipmap level. + */ + this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = null ) { - deltaAngle = 0; + // support the previous signature with just a single dst mipmap level + if ( dstLevel === null ) { - } else { + if ( srcLevel !== 0 ) { - deltaAngle = twoPi; + // @deprecated, r171 + warnOnce( 'WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels.' ); + dstLevel = srcLevel; + srcLevel = 0; - } + } else { - } + dstLevel = 0; - if ( this.aClockwise === true && ! samePoints ) { + } - if ( deltaAngle === twoPi ) { + } - deltaAngle = - twoPi; + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + if ( srcRegion !== null ) { + + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; } else { - deltaAngle = deltaAngle - twoPi; + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + if ( srcTexture.isDataArrayTexture ) { - } + depth = image.depth; - } + } else if ( srcTexture.isData3DTexture ) { - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); + depth = Math.floor( image.depth * levelScale ); - if ( this.aRotation !== 0 ) { + } else { - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); + depth = 1; - const tx = x - this.aX; - const ty = y - this.aY; + } - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; + minX = 0; + minY = 0; + minZ = 0; - } + } - return point.set( x, y ); + if ( dstPosition !== null ) { - } + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; - copy( source ) { + } else { - super.copy( source ); + dstX = 0; + dstY = 0; + dstZ = 0; - this.aX = source.aX; - this.aY = source.aY; + } - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; + // Set up the destination target + const glFormat = utils.convert( dstTexture.format ); + const glType = utils.convert( dstTexture.type ); + let glTarget; - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; + if ( dstTexture.isData3DTexture ) { - this.aClockwise = source.aClockwise; + textures.setTexture3D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_3D; - this.aRotation = source.aRotation; + } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { - return this; + textures.setTexture2DArray( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D_ARRAY; - } + } else { - toJSON() { + textures.setTexture2D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D; - const data = super.toJSON(); + } - data.aX = this.aX; - data.aY = this.aY; + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; + // used for copying data from cpu + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); - data.aClockwise = this.aClockwise; + // set up the src texture + const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture; + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; + if ( srcTexture.isDepthTexture ) { - data.aRotation = this.aRotation; + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); + const srcRenderTargetProperties = properties.get( srcTextureProperties.__renderTarget ); + const dstRenderTargetProperties = properties.get( dstTextureProperties.__renderTarget ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, srcRenderTargetProperties.__webglFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, dstRenderTargetProperties.__webglFramebuffer ); - return data; + for ( let i = 0; i < depth; i ++ ) { - } + // if the source or destination are a 3d target then a layer needs to be bound + if ( isSrc3D ) { - fromJSON( json ) { + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( srcTexture ).__webglTexture, srcLevel, minZ + i ); + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( dstTexture ).__webglTexture, dstLevel, dstZ + i ); - super.fromJSON( json ); + } - this.aX = json.aX; - this.aY = json.aY; + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.DEPTH_BUFFER_BIT, _gl.NEAREST ); - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; + } - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - this.aClockwise = json.aClockwise; + } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || properties.has( srcTexture ) ) { - this.aRotation = json.aRotation; + // get the appropriate frame buffers + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); - return this; + // bind the frame buffer targets + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, _srcFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, _dstFramebuffer ); - } + for ( let i = 0; i < depth; i ++ ) { -} + // assign the correct layers and mip maps to the frame buffers + if ( isSrc3D ) { -class ArcCurve extends EllipseCurve { + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, srcTextureProperties.__webglTexture, srcLevel, minZ + i ); - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + } else { - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + _gl.framebufferTexture2D( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, srcTextureProperties.__webglTexture, srcLevel ); - this.isArcCurve = true; + } - this.type = 'ArcCurve'; + if ( isDst3D ) { - } + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, dstTextureProperties.__webglTexture, dstLevel, dstZ + i ); -} + } else { -/** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 - */ + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, dstTextureProperties.__webglTexture, dstLevel ); + } -/* -Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM + // copy the data using the fastest function that can achieve the copy + if ( srcLevel !== 0 ) { -This CubicPoly class could be used for reusing some variables and calculations, -but for three.js curve use, it could be possible inlined and flatten into a single function call -which can be placed in CurveUtils. -*/ + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.COLOR_BUFFER_BIT, _gl.NEAREST ); -function CubicPoly() { + } else if ( isDst3D ) { - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; + _gl.copyTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height ); - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { + } else { - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; + _gl.copyTexSubImage2D( glTarget, dstLevel, dstX, dstY, minX, minY, width, height ); - } + } - return { + } - initCatmullRom: function ( x0, x1, x2, x3, tension ) { + // unbind read, draw buffers + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); + } else { - }, + if ( isDst3D ) { - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; + } else if ( dstTexture.isCompressedArrayTexture ) { - init( x1, x2, t1, t2 ); + _gl.compressedTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); - }, + } else { - calc: function ( t ) { + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; + } - } + } else { - }; + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { -} + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); -// + } else if ( srcTexture.isCompressedTexture ) { -const tmp = /*@__PURE__*/ new Vector3(); -const px = /*@__PURE__*/ new CubicPoly(); -const py = /*@__PURE__*/ new CubicPoly(); -const pz = /*@__PURE__*/ new CubicPoly(); + _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); -class CatmullRomCurve3 extends Curve { + } else { - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); - super(); + } - this.isCatmullRomCurve3 = true; + } - this.type = 'CatmullRomCurve3'; + } - this.points = points; - this.closed = closed; - this.curveType = curveType; - this.tension = tension; + // reset values + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); - } + // Generate mipmaps only when copying level 0 + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - getPoint( t, optionalTarget = new Vector3() ) { + _gl.generateMipmap( glTarget ); - const point = optionalTarget; + } - const points = this.points; - const l = points.length; + state.unbindTexture(); - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; + }; - if ( this.closed ) { + /** + * Initializes the given WebGLRenderTarget memory. Useful for initializing a render target so data + * can be copied into it using {@link WebGLRenderer#copyTextureToTexture} before it has been + * rendered to. + * + * @param {WebGLRenderTarget} target - The render target. + */ + this.initRenderTarget = function ( target ) { - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; + if ( properties.get( target ).__webglFramebuffer === undefined ) { - } else if ( weight === 0 && intPoint === l - 1 ) { + textures.setupRenderTarget( target ); - intPoint = l - 2; - weight = 1; + } - } + }; - let p0, p3; // 4 points (p1 & p2 defined below) + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first + * render (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @param {Texture} texture - The texture. + */ + this.initTexture = function ( texture ) { - if ( this.closed || intPoint > 0 ) { + if ( texture.isCubeTexture ) { - p0 = points[ ( intPoint - 1 ) % l ]; + textures.setTextureCube( texture, 0 ); - } else { + } else if ( texture.isData3DTexture ) { - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; + textures.setTexture3D( texture, 0 ); - } + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; + textures.setTexture2DArray( texture, 0 ); - if ( this.closed || intPoint + 2 < l ) { + } else { - p3 = points[ ( intPoint + 2 ) % l ]; + textures.setTexture2D( texture, 0 ); - } else { + } - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; + state.unbindTexture(); - } + }; - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { + /** + * Can be used to reset the internal WebGL state. This method is mostly + * relevant for applications which share a single WebGL context across + * multiple WebGL libraries. + */ + this.resetState = function () { - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); + _currentActiveCubeFace = 0; + _currentActiveMipmapLevel = 0; + _currentRenderTarget = null; - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; + state.reset(); + bindingStates.reset(); - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); + }; - } else if ( this.curveType === 'catmullrom' ) { + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); } - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); - - return point; - } - copy( source ) { + /** + * Defines the coordinate system of the renderer. + * + * In `WebGLRenderer`, the value is always `WebGLCoordinateSystem`. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + * @readonly + */ + get coordinateSystem() { - super.copy( source ); + return WebGLCoordinateSystem; - this.points = []; + } - for ( let i = 0, l = source.points.length; i < l; i ++ ) { + /** + * Defines the output color space of the renderer. + * + * @type {SRGBColorSpace|LinearSRGBColorSpace} + * @default SRGBColorSpace + */ + get outputColorSpace() { - const point = source.points[ i ]; + return this._outputColorSpace; - this.points.push( point.clone() ); + } - } + set outputColorSpace( colorSpace ) { - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; + this._outputColorSpace = colorSpace; - return this; + const gl = this.getContext(); + gl.drawingBufferColorSpace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); + gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); } - toJSON() { +} - const data = super.toJSON(); +/** + * This class can be used to define a linear fog that grows linearly denser + * with the distance. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + * ``` + */ +class Fog { - data.points = []; + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [near=1] - The minimum distance to start applying fog. + * @param {number} [far=1000] - The maximum distance at which fog stops being calculated and applied. + */ + constructor( color, near = 1, far = 1000 ) { - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFog = true; - const point = this.points[ i ]; - data.points.push( point.toArray() ); + /** + * The name of the fog. + * + * @type {string} + */ + this.name = ''; - } + /** + * The fog's color. + * + * @type {Color} + */ + this.color = new Color( color ); - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; + /** + * The minimum distance to start applying fog. Objects that are less than + * `near` units from the active camera won't be affected by fog. + * + * @type {number} + * @default 1 + */ + this.near = near; - return data; + /** + * The maximum distance at which fog stops being calculated and applied. + * Objects that are more than `far` units away from the active camera won't + * be affected by fog. + * + * @type {number} + * @default 1000 + */ + this.far = far; } - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { + /** + * Returns a new fog with copied values from this instance. + * + * @return {Fog} A clone of this instance. + */ + clone() { - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); + return new Fog( this.color, this.near, this.far ); - } + } - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ + toJSON( /* meta */ ) { - return this; + return { + type: 'Fog', + name: this.name, + color: this.color.getHex(), + near: this.near, + far: this.far + }; } } /** - * Bezier Curves formulas obtained from - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve + * Scenes allow you to set up what is to be rendered and where by three.js. + * This is where you place 3D objects like meshes, lines or lights. + * + * @augments Object3D */ +class Scene extends Object3D { -function CatmullRom( t, p0, p1, p2, p3 ) { - - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + /** + * Constructs a new scene. + */ + constructor() { -} + super(); -// + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScene = true; -function QuadraticBezierP0( t, p ) { + this.type = 'Scene'; - const k = 1 - t; - return k * k * p; + /** + * Defines the background of the scene. Valid inputs are: + * + * - A color for defining a uniform colored background. + * - A texture for defining a (flat) textured background. + * - Cube textures or equirectangular textures for defining a skybox. + * + * @type {?(Color|Texture)} + * @default null + */ + this.background = null; -} + /** + * Sets the environment map for all physical materials in the scene. However, + * it's not possible to overwrite an existing texture assigned to the `envMap` + * material property. + * + * @type {?Texture} + * @default null + */ + this.environment = null; -function QuadraticBezierP1( t, p ) { + /** + * A fog instance defining the type of fog that affects everything + * rendered in the scene. + * + * @type {?(Fog|FogExp2)} + * @default null + */ + this.fog = null; - return 2 * ( 1 - t ) * t * p; + /** + * Sets the blurriness of the background. Only influences environment maps + * assigned to {@link Scene#background}. Valid input is a float between `0` + * and `1`. + * + * @type {number} + * @default 0 + */ + this.backgroundBlurriness = 0; -} + /** + * Attenuates the color of the background. Only applies to background textures. + * + * @type {number} + * @default 1 + */ + this.backgroundIntensity = 1; -function QuadraticBezierP2( t, p ) { + /** + * The rotation of the background in radians. Only influences environment maps + * assigned to {@link Scene#background}. + * + * @type {Euler} + * @default (0,0,0) + */ + this.backgroundRotation = new Euler(); - return t * t * p; + /** + * Attenuates the color of the environment. Only influences environment maps + * assigned to {@link Scene#environment}. + * + * @type {number} + * @default 1 + */ + this.environmentIntensity = 1; -} + /** + * The rotation of the environment map in radians. Only influences physical materials + * in the scene when {@link Scene#environment} is used. + * + * @type {Euler} + * @default (0,0,0) + */ + this.environmentRotation = new Euler(); -function QuadraticBezier( t, p0, p1, p2 ) { - - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); - -} - -// - -function CubicBezierP0( t, p ) { - - const k = 1 - t; - return k * k * k * p; + /** + * Forces everything in the scene to be rendered with the defined material. It is possible + * to exclude materials from override by setting {@link Material#allowOverride} to `false`. + * + * @type {?Material} + * @default null + */ + this.overrideMaterial = null; -} + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { -function CubicBezierP1( t, p ) { + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - const k = 1 - t; - return 3 * k * k * t * p; + } -} + } -function CubicBezierP2( t, p ) { + copy( source, recursive ) { - return 3 * ( 1 - t ) * t * t * p; + super.copy( source, recursive ); -} + if ( source.background !== null ) this.background = source.background.clone(); + if ( source.environment !== null ) this.environment = source.environment.clone(); + if ( source.fog !== null ) this.fog = source.fog.clone(); -function CubicBezierP3( t, p ) { + this.backgroundBlurriness = source.backgroundBlurriness; + this.backgroundIntensity = source.backgroundIntensity; + this.backgroundRotation.copy( source.backgroundRotation ); - return t * t * t * p; + this.environmentIntensity = source.environmentIntensity; + this.environmentRotation.copy( source.environmentRotation ); -} + if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); -function CubicBezier( t, p0, p1, p2, p3 ) { + this.matrixAutoUpdate = source.matrixAutoUpdate; - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); + return this; -} + } -class CubicBezierCurve extends Curve { + toJSON( meta ) { - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { + const data = super.toJSON( meta ); - super(); + if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - this.isCubicBezierCurve = true; + if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; + if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; + data.object.backgroundRotation = this.backgroundRotation.toArray(); - this.type = 'CubicBezierCurve'; + if ( this.environmentIntensity !== 1 ) data.object.environmentIntensity = this.environmentIntensity; + data.object.environmentRotation = this.environmentRotation.toArray(); - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + return data; } - getPoint( t, optionalTarget = new Vector2() ) { +} - const point = optionalTarget; +/** + * An instanced version of a buffer attribute. + * + * @augments BufferAttribute + */ +class InstancedBufferAttribute extends BufferAttribute { - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + /** + * Constructs a new instanced buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + * @param {number} [meshPerAttribute=1] - How often a value of this buffer attribute should be repeated. + */ + constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); + super( array, itemSize, normalized ); - return point; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedBufferAttribute = true; + + /** + * Defines how often a value of this buffer attribute should be repeated. A + * value of one means that each value of the instanced attribute is used for + * a single instance. A value of two means that each value is used for two + * consecutive instances (and so on). + * + * @type {number} + * @default 1 + */ + this.meshPerAttribute = meshPerAttribute; } @@ -46374,10 +56600,7 @@ class CubicBezierCurve extends Curve { super.copy( source ); - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + this.meshPerAttribute = source.meshPerAttribute; return this; @@ -46387,378 +56610,580 @@ class CubicBezierCurve extends Curve { const data = super.toJSON(); - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); + data.meshPerAttribute = this.meshPerAttribute; + + data.isInstancedBufferAttribute = true; return data; } - fromJSON( json ) { - - super.fromJSON( json ); +} - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); +/** + * Creates a texture directly from raw buffer data. + * + * The interpretation of the data depends on type and format: If the type is + * `UnsignedByteType`, a `Uint8Array` will be useful for addressing the + * texel data. If the format is `RGBAFormat`, data needs four values for + * one texel; Red, Green, Blue and Alpha (typically the opacity). + * + * @augments Texture + */ +class DataTexture extends Texture { - return this; + /** + * Constructs a new data texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=NearestFilter] - The mag filter value. + * @param {number} [minFilter=NearestFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { - } + super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); -} + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataTexture = true; -class CubicBezierCurve3 extends Curve { + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number}} + */ + this.image = { data: data, width: width, height: height }; - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - super(); + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - this.isCubicBezierCurve3 = true; + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; - this.type = 'CubicBezierCurve3'; + } - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; +} - } +const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); +const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); - getPoint( t, optionalTarget = new Vector3() ) { +const _instanceIntersects = []; - const point = optionalTarget; +const _box3 = /*@__PURE__*/ new Box3(); +const _identity = /*@__PURE__*/ new Matrix4(); +const _mesh$1 = /*@__PURE__*/ new Mesh(); +const _sphere$4 = /*@__PURE__*/ new Sphere(); - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; +/** + * A special version of a mesh with instanced rendering support. Use + * this class if you have to render a large number of objects with the same + * geometry and material(s) but with different world transformations. The usage + * of 'InstancedMesh' will help you to reduce the number of draw calls and thus + * improve the overall rendering performance in your application. + * + * @augments Mesh + */ +class InstancedMesh extends Mesh { - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); + /** + * Constructs a new instanced mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + * @param {number} count - The number of instances. + */ + constructor( geometry, material, count ) { - return point; + super( geometry, material ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedMesh = true; - copy( source ) { + /** + * Represents the local transformation of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMatrixAt}. + * + * @type {InstancedBufferAttribute} + */ + this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - super.copy( source ); + /** + * Represents the color of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setColorAt}. + * + * @type {?InstancedBufferAttribute} + * @default null + */ + this.instanceColor = null; - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + /** + * Represents the morph target weights of all instances. You have to set its + * {@link Texture#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMorphAt}. + * + * @type {?DataTexture} + * @default null + */ + this.morphTexture = null; - return this; + /** + * The number of instances. + * + * @type {number} + */ + this.count = count; - } + /** + * The bounding box of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; - toJSON() { + /** + * The bounding sphere of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; - const data = super.toJSON(); + for ( let i = 0; i < count; i ++ ) { - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); + this.setMatrixAt( i, _identity ); - return data; + } } - fromJSON( json ) { + /** + * Computes the bounding box of the instanced mesh, and updates {@link InstancedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * You may need to recompute the bounding box if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingBox() { - super.fromJSON( json ); + const geometry = this.geometry; + const count = this.count; - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); + if ( this.boundingBox === null ) { - return this; + this.boundingBox = new Box3(); - } + } -} + if ( geometry.boundingBox === null ) { -class LineCurve extends Curve { + geometry.computeBoundingBox(); - constructor( v1 = new Vector2(), v2 = new Vector2() ) { + } - super(); + this.boundingBox.makeEmpty(); - this.isLineCurve = true; + for ( let i = 0; i < count; i ++ ) { - this.type = 'LineCurve'; + this.getMatrixAt( i, _instanceLocalMatrix ); - this.v1 = v1; - this.v2 = v2; + _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); - } + this.boundingBox.union( _box3 ); - getPoint( t, optionalTarget = new Vector2() ) { + } - const point = optionalTarget; + } - if ( t === 1 ) { + /** + * Computes the bounding sphere of the instanced mesh, and updates {@link InstancedMesh#boundingSphere} + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingSphere() { - point.copy( this.v2 ); + const geometry = this.geometry; + const count = this.count; - } else { + if ( this.boundingSphere === null ) { - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); + this.boundingSphere = new Sphere(); } - return point; + if ( geometry.boundingSphere === null ) { - } + geometry.computeBoundingSphere(); - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { + } - return this.getPoint( u, optionalTarget ); + this.boundingSphere.makeEmpty(); - } + for ( let i = 0; i < count; i ++ ) { - getTangent( t, optionalTarget = new Vector2() ) { + this.getMatrixAt( i, _instanceLocalMatrix ); - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); + + this.boundingSphere.union( _sphere$4 ); + + } } - getTangentAt( u, optionalTarget ) { + copy( source, recursive ) { - return this.getTangent( u, optionalTarget ); + super.copy( source, recursive ); - } + this.instanceMatrix.copy( source.instanceMatrix ); - copy( source ) { + if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); + if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); - super.copy( source ); + this.count = source.count; - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); + if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); return this; } - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + /** + * Gets the color of the defined instance. + * + * @param {number} index - The instance index. + * @param {Color} color - The target object that is used to store the method's result. + */ + getColorAt( index, color ) { - return data; + color.fromArray( this.instanceColor.array, index * 3 ); } - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + /** + * Gets the local transformation matrix of the defined instance. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + */ + getMatrixAt( index, matrix ) { - return this; + matrix.fromArray( this.instanceMatrix.array, index * 16 ); } -} + /** + * Gets the morph target weights of the defined instance. + * + * @param {number} index - The instance index. + * @param {Mesh} object - The target object that is used to store the method's result. + */ + getMorphAt( index, object ) { -class LineCurve3 extends Curve { + const objectInfluences = object.morphTargetInfluences; - constructor( v1 = new Vector3(), v2 = new Vector3() ) { + const array = this.morphTexture.source.data.data; - super(); + const len = objectInfluences.length + 1; // All influences + the baseInfluenceSum - this.isLineCurve3 = true; + const dataIndex = index * len + 1; // Skip the baseInfluenceSum at the beginning - this.type = 'LineCurve3'; + for ( let i = 0; i < objectInfluences.length; i ++ ) { - this.v1 = v1; - this.v2 = v2; + objectInfluences[ i ] = array[ dataIndex + i ]; + + } } - getPoint( t, optionalTarget = new Vector3() ) { + raycast( raycaster, intersects ) { - const point = optionalTarget; + const matrixWorld = this.matrixWorld; + const raycastTimes = this.count; - if ( t === 1 ) { + _mesh$1.geometry = this.geometry; + _mesh$1.material = this.material; - point.copy( this.v2 ); + if ( _mesh$1.material === undefined ) return; - } else { + // test with bounding sphere first - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); + if ( this.boundingSphere === null ) this.computeBoundingSphere(); - } + _sphere$4.copy( this.boundingSphere ); + _sphere$4.applyMatrix4( matrixWorld ); - return point; + if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; - } + // now test each instance - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { + for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { - return this.getPoint( u, optionalTarget ); + // calculate the world matrix for each instance - } + this.getMatrixAt( instanceId, _instanceLocalMatrix ); - getTangent( t, optionalTarget = new Vector3() ) { + _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + // the mesh represents this single instance - } + _mesh$1.matrixWorld = _instanceWorldMatrix; - getTangentAt( u, optionalTarget ) { + _mesh$1.raycast( raycaster, _instanceIntersects ); - return this.getTangent( u, optionalTarget ); + // process the result of raycast - } + for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { - copy( source ) { + const intersect = _instanceIntersects[ i ]; + intersect.instanceId = instanceId; + intersect.object = this; + intersects.push( intersect ); - super.copy( source ); + } - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + _instanceIntersects.length = 0; - return this; + } } - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } + /** + * Sets the given color to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceColor} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Color} color - The instance color. + */ + setColorAt( index, color ) { - fromJSON( json ) { + if ( this.instanceColor === null ) { - super.fromJSON( json ); + this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ).fill( 1 ), 3 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + } - return this; + color.toArray( this.instanceColor.array, index * 3 ); } -} + /** + * Sets the given local transformation matrix to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceMatrix} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The local transformation. + */ + setMatrixAt( index, matrix ) { -class QuadraticBezierCurve extends Curve { + matrix.toArray( this.instanceMatrix.array, index * 16 ); - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { + } - super(); + /** + * Sets the morph target weights to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#morphTexture} to `true` after updating all the influences. + * + * @param {number} index - The instance index. + * @param {Mesh} object - A mesh which `morphTargetInfluences` property containing the morph target weights + * of a single instance. + */ + setMorphAt( index, object ) { - this.isQuadraticBezierCurve = true; + const objectInfluences = object.morphTargetInfluences; - this.type = 'QuadraticBezierCurve'; + const len = objectInfluences.length + 1; // morphBaseInfluence + all influences - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + if ( this.morphTexture === null ) { - } + this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType ); - getPoint( t, optionalTarget = new Vector2() ) { + } - const point = optionalTarget; + const array = this.morphTexture.source.data.data; - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + let morphInfluencesSum = 0; - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); + for ( let i = 0; i < objectInfluences.length; i ++ ) { - return point; + morphInfluencesSum += objectInfluences[ i ]; - } + } - copy( source ) { + const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - super.copy( source ); + const dataIndex = len * index; - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + array[ dataIndex ] = morphBaseInfluence; - return this; + array.set( objectInfluences, dataIndex + 1 ); } - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; + updateMorphTargets() { } - fromJSON( json ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - super.fromJSON( json ); + this.dispatchEvent( { type: 'dispose' } ); - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + if ( this.morphTexture !== null ) { - return this; + this.morphTexture.dispose(); + this.morphTexture = null; + + } } } -class QuadraticBezierCurve3 extends Curve { +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * ``` + * + * @augments Material + */ +class LineBasicMaterial extends Material { - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { + /** + * Constructs a new line basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { super(); - this.isQuadraticBezierCurve3 = true; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineBasicMaterial = true; - this.type = 'QuadraticBezierCurve3'; + this.type = 'LineBasicMaterial'; - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); - } + /** + * Sets the color of the lines using data from a texture. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - getPoint( t, optionalTarget = new Vector3() ) { + /** + * Controls line thickness or lines. + * + * Can only be used with {@link SVGRenderer}. WebGL and WebGPU + * ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; - const point = optionalTarget; + /** + * Defines appearance of line ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('butt'|'round'|'square')} + * @default 'round' + */ + this.linecap = 'round'; - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + /** + * Defines appearance of line joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.linejoin = 'round'; - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - return point; + this.setValues( parameters ); } @@ -46766,33 +57191,15 @@ class QuadraticBezierCurve3 extends Curve { super.copy( source ); - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } + this.color.copy( source.color ); - fromJSON( json ) { + this.map = source.map; - super.fromJSON( json ); + this.linewidth = source.linewidth; + this.linecap = source.linecap; + this.linejoin = source.linejoin; - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + this.fog = source.fog; return this; @@ -46800,89 +57207,142 @@ class QuadraticBezierCurve3 extends Curve { } -class SplineCurve extends Curve { - - constructor( points = [] ) { +const _vStart = /*@__PURE__*/ new Vector3(); +const _vEnd = /*@__PURE__*/ new Vector3(); - super(); +const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _ray$2 = /*@__PURE__*/ new Ray(); +const _sphere$3 = /*@__PURE__*/ new Sphere(); - this.isSplineCurve = true; +const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); +const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); - this.type = 'SplineCurve'; +/** + * A continuous line. The line are rendered by connecting consecutive + * vertices with straight lines. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); + * + * const points = []; + * points.push( new THREE.Vector3( - 10, 0, 0 ) ); + * points.push( new THREE.Vector3( 0, 10, 0 ) ); + * points.push( new THREE.Vector3( 10, 0, 0 ) ); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments Object3D + */ +let Line$1 = class Line extends Object3D { - this.points = points; + /** + * Constructs a new line. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { - } + super(); - getPoint( t, optionalTarget = new Vector2() ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine = true; - const point = optionalTarget; + this.type = 'Line'; - const points = this.points; - const p = ( points.length - 1 ) * t; + /** + * The line geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - const intPoint = Math.floor( p ); - const weight = p - intPoint; + /** + * The line material. + * + * @type {Material|Array} + * @default LineBasicMaterial + */ + this.material = material; - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; - return point; + this.updateMorphTargets(); } - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; + copy( source, recursive ) { - this.points.push( point.clone() ); + super.copy( source, recursive ); - } + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; return this; } - toJSON() { - - const data = super.toJSON(); + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Line} A reference to this line. + */ + computeLineDistances() { - data.points = []; + const geometry = this.geometry; - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + // we assume non-indexed geometry - const point = this.points[ i ]; - data.points.push( point.toArray() ); + if ( geometry.index === null ) { - } + const positionAttribute = geometry.attributes.position; + const lineDistances = [ 0 ]; - return data; + for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - } + _vStart.fromBufferAttribute( positionAttribute, i - 1 ); + _vEnd.fromBufferAttribute( positionAttribute, i ); - fromJSON( json ) { + lineDistances[ i ] = lineDistances[ i - 1 ]; + lineDistances[ i ] += _vStart.distanceTo( _vEnd ); - super.fromJSON( json ); + } - this.points = []; + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - for ( let i = 0, l = json.points.length; i < l; i ++ ) { + } else { - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); + console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); } @@ -46890,466 +57350,686 @@ class SplineCurve extends Curve { } -} + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { -var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve -}); + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Line.threshold; + const drawRange = geometry.drawRange; -/************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ + // Checking boundingSphere distance to ray -class CurvePath extends Curve { + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - constructor() { + _sphere$3.copy( geometry.boundingSphere ); + _sphere$3.applyMatrix4( matrixWorld ); + _sphere$3.radius += threshold; - super(); + if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; - this.type = 'CurvePath'; + // - this.curves = []; - this.autoClose = false; // Automatically closes the path + _inverseMatrix$2.copy( matrixWorld ).invert(); + _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); - } + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; - add( curve ) { + const step = this.isLineSegments ? 2 : 1; - this.curves.push( curve ); + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; - } + if ( index !== null ) { - closePath() { + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); + for ( let i = start, l = end - 1; i < l; i += step ) { - if ( ! startPoint.equals( endPoint ) ) { + const a = index.getX( i ); + const b = index.getX( i + 1 ); - const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; - this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, i ); - } + if ( intersect ) { - return this; + intersects.push( intersect ); - } + } - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: + } - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') + if ( this.isLineLoop ) { - getPoint( t, optionalTarget ) { + const a = index.getX( end - 1 ); + const b = index.getX( start ); - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, end - 1 ); - // To think about boundaries points. + if ( intersect ) { - while ( i < curveLengths.length ) { + intersects.push( intersect ); - if ( curveLengths[ i ] >= d ) { + } - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; + } - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + } else { - return curve.getPointAt( u, optionalTarget ); + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - } + for ( let i = start, l = end - 1; i < l; i += step ) { - i ++; + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, i, i + 1, i ); - } + if ( intersect ) { - return null; + intersects.push( intersect ); - // loop where sum != 0, sum > d , sum+1 0 ) { - return this.cacheLengths; + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - } + if ( morphAttribute !== undefined ) { - // Get length of sub-curve - // Push sums into cached array + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - const lengths = []; - let sums = 0; + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + const name = morphAttribute[ m ].name || String( m ); - sums += this.curves[ i ].getLength(); - lengths.push( sums ); + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; - } + } - this.cacheLengths = lengths; + } - return lengths; + } } - getSpacedPoints( divisions = 40 ) { +}; - const points = []; +function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) { - for ( let i = 0; i <= divisions; i ++ ) { + const positionAttribute = object.geometry.attributes.position; - points.push( this.getPoint( i / divisions ) ); + _vStart.fromBufferAttribute( positionAttribute, a ); + _vEnd.fromBufferAttribute( positionAttribute, b ); - } + const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment ); - if ( this.autoClose ) { + if ( distSq > thresholdSq ) return; - points.push( points[ 0 ] ); + _intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation - } + const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); - return points; + if ( distance < raycaster.near || distance > raycaster.far ) return; - } + return { - getPoints( divisions = 12 ) { + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ), + index: i, + face: null, + faceIndex: null, + barycoord: null, + object: object - const points = []; - let last; + }; - for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { +} - const curve = curves[ i ]; - const resolution = curve.isEllipseCurve ? divisions * 2 - : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 - : curve.isSplineCurve ? divisions * curve.points.length - : divisions; +const _start = /*@__PURE__*/ new Vector3(); +const _end = /*@__PURE__*/ new Vector3(); - const pts = curve.getPoints( resolution ); +/** + * A series of lines drawn between pairs of vertices. + * + * @augments Line + */ +class LineSegments extends Line$1 { - for ( let j = 0; j < pts.length; j ++ ) { + /** + * Constructs a new line segments. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry, material ) { - const point = pts[ j ]; + super( geometry, material ); - if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineSegments = true; - points.push( point ); - last = point; + this.type = 'LineSegments'; - } + } - } + computeLineDistances() { - if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { + const geometry = this.geometry; - points.push( points[ 0 ] ); + // we assume non-indexed geometry - } + if ( geometry.index === null ) { - return points; + const positionAttribute = geometry.attributes.position; + const lineDistances = []; - } + for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { - copy( source ) { + _start.fromBufferAttribute( positionAttribute, i ); + _end.fromBufferAttribute( positionAttribute, i + 1 ); - super.copy( source ); + lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; + lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); - this.curves = []; + } - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - const curve = source.curves[ i ]; + } else { - this.curves.push( curve.clone() ); + console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); } - this.autoClose = source.autoClose; - return this; } - toJSON() { - - const data = super.toJSON(); - - data.autoClose = this.autoClose; - data.curves = []; +} - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { +/** + * A material for rendering point primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const vertices = []; + * + * for ( let i = 0; i < 10000; i ++ ) { + * const x = THREE.MathUtils.randFloatSpread( 2000 ); + * const y = THREE.MathUtils.randFloatSpread( 2000 ); + * const z = THREE.MathUtils.randFloatSpread( 2000 ); + * + * vertices.push( x, y, z ); + * } + * + * const geometry = new THREE.BufferGeometry(); + * geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + * const material = new THREE.PointsMaterial( { color: 0x888888 } ); + * const points = new THREE.Points( geometry, material ); + * scene.add( points ); + * ``` + * + * @augments Material + */ +class PointsMaterial extends Material { - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); + /** + * Constructs a new points material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - return data; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointsMaterial = true; - } + this.type = 'PointsMaterial'; - fromJSON( json ) { + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); - super.fromJSON( json ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - this.autoClose = json.autoClose; - this.curves = []; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { + /** + * Defines the size of the points in pixels. + * + * Might be capped if the value exceeds hardware dependent parameters like [gl.ALIASED_POINT_SIZE_RANGE]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParamete}. + * + * @type {number} + * @default 1 + */ + this.size = 1; - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); + /** + * Specifies whether size of individual points is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ + this.sizeAttenuation = true; - } + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - return this; + this.setValues( parameters ); } -} - -class Path extends CurvePath { + copy( source ) { - constructor( points ) { + super.copy( source ); - super(); + this.color.copy( source.color ); - this.type = 'Path'; + this.map = source.map; - this.currentPoint = new Vector2(); + this.alphaMap = source.alphaMap; - if ( points ) { + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; - this.setFromPoints( points ); + this.fog = source.fog; - } + return this; } - setFromPoints( points ) { +} - this.moveTo( points[ 0 ].x, points[ 0 ].y ); +const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _ray$1 = /*@__PURE__*/ new Ray(); +const _sphere$2 = /*@__PURE__*/ new Sphere(); +const _position = /*@__PURE__*/ new Vector3(); - for ( let i = 1, l = points.length; i < l; i ++ ) { +/** + * A class for displaying points or point clouds. + * + * @augments Object3D + */ +class Points extends Object3D { - this.lineTo( points[ i ].x, points[ i ].y ); + /** + * Constructs a new point cloud. + * + * @param {BufferGeometry} [geometry] - The points geometry. + * @param {Material|Array} [material] - The points material. + */ + constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { - } + super(); - return this; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPoints = true; - } + this.type = 'Points'; - moveTo( x, y ) { + /** + * The points geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? + /** + * The line material. + * + * @type {Material|Array} + * @default PointsMaterial + */ + this.material = material; - return this; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + this.updateMorphTargets(); } - lineTo( x, y ) { + copy( source, recursive ) { - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); + super.copy( source, recursive ); - this.currentPoint.set( x, y ); + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; return this; } - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + /** + * Computes intersection points between a casted ray and this point cloud. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Points.threshold; + const drawRange = geometry.drawRange; - return this; + // Checking boundingSphere distance to ray - } + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + _sphere$2.copy( geometry.boundingSphere ); + _sphere$2.applyMatrix4( matrixWorld ); + _sphere$2.radius += threshold; - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); + if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; - this.curves.push( curve ); + // - this.currentPoint.set( aX, aY ); + _inverseMatrix$1.copy( matrixWorld ).invert(); + _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); - return this; + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; - } + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; - splineThru( pts /*Array of Vector*/ ) { + if ( index !== null ) { - const npts = [ this.currentPoint.clone() ].concat( pts ); + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - const curve = new SplineCurve( npts ); - this.curves.push( curve ); + for ( let i = start, il = end; i < il; i ++ ) { - this.currentPoint.copy( pts[ pts.length - 1 ] ); + const a = index.getX( i ); - return this; + _position.fromBufferAttribute( positionAttribute, a ); - } + testPoint( _position, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + } - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; + } else { - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - return this; + for ( let i = start, l = end; i < l; i ++ ) { - } + _position.fromBufferAttribute( positionAttribute, i ); - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + testPoint( _position, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + } - return this; + } } - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + /** + * Sets the values of {@link Points#morphTargetDictionary} and {@link Points#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; + const geometry = this.geometry; - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); - return this; + if ( keys.length > 0 ) { - } + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + if ( morphAttribute !== undefined ) { - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - if ( this.curves.length > 0 ) { + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); + const name = morphAttribute[ m ].name || String( m ); - if ( ! firstPoint.equals( this.currentPoint ) ) { + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; - this.lineTo( firstPoint.x, firstPoint.y ); + } } } - this.curves.push( curve ); - - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); + } - return this; +} - } +function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { - copy( source ) { + const rayPointDistanceSq = _ray$1.distanceSqToPoint( point ); - super.copy( source ); + if ( rayPointDistanceSq < localThresholdSq ) { - this.currentPoint.copy( source.currentPoint ); + const intersectPoint = new Vector3(); - return this; + _ray$1.closestPointToPoint( point, intersectPoint ); + intersectPoint.applyMatrix4( matrixWorld ); - } + const distance = raycaster.ray.origin.distanceTo( intersectPoint ); - toJSON() { + if ( distance < raycaster.near || distance > raycaster.far ) return; - const data = super.toJSON(); + intersects.push( { - data.currentPoint = this.currentPoint.toArray(); + distance: distance, + distanceToRay: Math.sqrt( rayPointDistanceSq ), + point: intersectPoint, + index: index, + face: null, + faceIndex: null, + barycoord: null, + object: object - return data; + } ); } - fromJSON( json ) { +} - super.fromJSON( json ); +/** + * Creates a texture from a canvas element. + * + * This is almost the same as the base texture class, except that it sets {@link Texture#needsUpdate} + * to `true` immediately since a canvas can directly be used for rendering. + * + * @augments Texture + */ +class CanvasTexture extends Texture { - this.currentPoint.fromArray( json.currentPoint ); + /** + * Constructs a new texture. + * + * @param {HTMLCanvasElement} [canvas] - The HTML canvas element. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - return this; + super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCanvasTexture = true; + + this.needsUpdate = true; } } +/** + * A simple shape of Euclidean geometry. It is constructed from a + * number of triangular segments that are oriented around a central point and + * extend as far out as a given radius. It is built counter-clockwise from a + * start angle and a given central angle. It can also be used to create + * regular polygons, where the number of segments determines the number of + * sides. + * + * ```js + * const geometry = new THREE.CircleGeometry( 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const circle = new THREE.Mesh( geometry, material ); + * scene.add( circle ) + * ``` + * + * @augments BufferGeometry + */ class CircleGeometry extends BufferGeometry { + /** + * Constructs a new circle geometry. + * + * @param {number} [radius=1] - Radius of the circle. + * @param {number} [segments=32] - Number of segments (triangles), minimum = `3`. + * @param {number} [thetaStart=0] - Start angle for first segment in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, + * of the circular sector in radians. The default value results in a complete circle. + */ constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'CircleGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, segments: segments, @@ -47428,6 +58108,13 @@ class CircleGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CircleGeometry} A new instance. + */ static fromJSON( data ) { return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); @@ -47436,42235 +58123,55952 @@ class CircleGeometry extends BufferGeometry { } -class Shape extends Path { +/** + * An abstract base class for creating an analytic curve object that contains methods + * for interpolation. + * + * @abstract + */ +class Curve { - constructor( points ) { + /** + * Constructs a new curve. + */ + constructor() { - super( points ); + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Curve'; - this.uuid = generateUUID(); + /** + * This value determines the amount of divisions when calculating the + * cumulative segment lengths of a curve via {@link Curve#getLengths}. To ensure + * precision when using methods like {@link Curve#getSpacedPoints}, it is + * recommended to increase the value of this property if the curve is very large. + * + * @type {number} + * @default 200 + */ + this.arcLengthDivisions = 200; - this.type = 'Shape'; + /** + * Must be set to `true` if the curve parameters have changed. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; - this.holes = []; + /** + * An internal cache that holds precomputed curve length values. + * + * @private + * @type {?Array} + * @default null + */ + this.cacheArcLengths = null; } - getPointsHoles( divisions ) { - - const holesPts = []; - - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); - - } + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. + * + * @abstract + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( /* t, optionalTarget */ ) { - return holesPts; + console.warn( 'THREE.Curve: .getPoint() not implemented.' ); } - // get points of shape and holes (keypoints based on segments parameter) - - extractPoints( divisions ) { - - return { - - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. Unlike {@link Curve#getPoint}, this method honors the length + * of the curve which equidistant samples. + * + * @param {number} u - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPointAt( u, optionalTarget ) { - }; + const t = this.getUtoTmapping( u ); + return this.getPoint( t, optionalTarget ); } - copy( source ) { - - super.copy( source ); - - this.holes = []; + /** + * This method samples the curve via {@link Curve#getPoint} and returns an array of points representing + * the curve shape. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getPoints( divisions = 5 ) { - for ( let i = 0, l = source.holes.length; i < l; i ++ ) { + const points = []; - const hole = source.holes[ i ]; + for ( let d = 0; d <= divisions; d ++ ) { - this.holes.push( hole.clone() ); + points.push( this.getPoint( d / divisions ) ); } - return this; + return points; } - toJSON() { + // Get sequence of points using getPointAt( u ) - const data = super.toJSON(); + /** + * This method samples the curve via {@link Curve#getPointAt} and returns an array of points representing + * the curve shape. Unlike {@link Curve#getPoints}, this method returns equi-spaced points across the entire + * curve. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getSpacedPoints( divisions = 5 ) { - data.uuid = this.uuid; - data.holes = []; + const points = []; - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + for ( let d = 0; d <= divisions; d ++ ) { - const hole = this.holes[ i ]; - data.holes.push( hole.toJSON() ); + points.push( this.getPointAt( d / divisions ) ); } - return data; + return points; } - fromJSON( json ) { - - super.fromJSON( json ); - - this.uuid = json.uuid; - this.holes = []; - - for ( let i = 0, l = json.holes.length; i < l; i ++ ) { - - const hole = json.holes[ i ]; - this.holes.push( new Path().fromJSON( hole ) ); - - } + /** + * Returns the total arc length of the curve. + * + * @return {number} The length of the curve. + */ + getLength() { - return this; + const lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; } -} - -/** - * Port from https://github.com/mapbox/earcut (v2.2.4) - */ - -const Earcut = { - - triangulate: function ( data, holeIndices, dim = 2 ) { - - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; - let outerNode = linkedList( data, 0, outerLen, dim, true ); - const triangles = []; - - if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; - - let minX, minY, maxX, maxY, x, y, invSize; - - if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if ( data.length > 80 * dim ) { - - minX = maxX = data[ 0 ]; - minY = maxY = data[ 1 ]; - - for ( let i = dim; i < outerLen; i += dim ) { - - x = data[ i ]; - y = data[ i + 1 ]; - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; + /** + * Returns an array of cumulative segment lengths of the curve. + * + * @param {number} [divisions=this.arcLengthDivisions] - The number of divisions. + * @return {Array} An array holding the cumulative segment lengths. + */ + getLengths( divisions = this.arcLengthDivisions ) { - } + if ( this.cacheArcLengths && + ( this.cacheArcLengths.length === divisions + 1 ) && + ! this.needsUpdate ) { - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max( maxX - minX, maxY - minY ); - invSize = invSize !== 0 ? 32767 / invSize : 0; + return this.cacheArcLengths; } - earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); - - return triangles; - - } + this.needsUpdate = false; -}; + const cache = []; + let current, last = this.getPoint( 0 ); + let sum = 0; -// create a circular doubly linked list from polygon points in the specified winding order -function linkedList( data, start, end, dim, clockwise ) { + cache.push( 0 ); - let i, last; + for ( let p = 1; p <= divisions; p ++ ) { - if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; - for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + } - } else { + this.cacheArcLengths = cache; - for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + return cache; // { sums: cache, sum: sum }; Sum is in the last element. } - if ( last && equals( last, last.next ) ) { + /** + * Update the cumulative segment distance cache. The method must be called + * every time curve parameters are changed. If an updated curve is part of a + * composed curve like {@link CurvePath}, this method must be called on the + * composed curve, too. + */ + updateArcLengths() { - removeNode( last ); - last = last.next; + this.needsUpdate = true; + this.getLengths(); } - return last; - -} - -// eliminate colinear or duplicate points -function filterPoints( start, end ) { + /** + * Given an interpolation factor in the range `[0,1]`, this method returns an updated + * interpolation factor in the same range that can be ued to sample equidistant points + * from a curve. + * + * @param {number} u - The interpolation factor. + * @param {?number} distance - An optional distance on the curve. + * @return {number} The updated interpolation factor. + */ + getUtoTmapping( u, distance = null ) { - if ( ! start ) return start; - if ( ! end ) end = start; + const arcLengths = this.getLengths(); - let p = start, - again; - do { + let i = 0; + const il = arcLengths.length; - again = false; + let targetArcLength; // The targeted u distance value to get - if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { + if ( distance ) { - removeNode( p ); - p = end = p.prev; - if ( p === p.next ) break; - again = true; + targetArcLength = distance; } else { - p = p.next; + targetArcLength = u * arcLengths[ il - 1 ]; } - } while ( again || p !== end ); - - return end; + // binary search for the index with largest value smaller than target u distance -} + let low = 0, high = il - 1, comparison; -// main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { + while ( low <= high ) { - if ( ! ear ) return; + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - // interlink polygon nodes in z-order - if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); + comparison = arcLengths[ i ] - targetArcLength; - let stop = ear, - prev, next; + if ( comparison < 0 ) { - // iterate through ears, slicing them one by one - while ( ear.prev !== ear.next ) { + low = i + 1; - prev = ear.prev; - next = ear.next; + } else if ( comparison > 0 ) { - if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + high = i - 1; - // cut off the triangle - triangles.push( prev.i / dim | 0 ); - triangles.push( ear.i / dim | 0 ); - triangles.push( next.i / dim | 0 ); + } else { - removeNode( ear ); + high = i; + break; - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; + // DONE - continue; + } } - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if ( ear === stop ) { + i = high; - // try filtering points and slicing again - if ( ! pass ) { + if ( arcLengths[ i ] === targetArcLength ) { - earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); + return i / ( il - 1 ); - // if this didn't work, try curing all small self-intersections locally + } - } else if ( pass === 1 ) { + // we could get finer grain at lengths, or use simple interpolation between two points - ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); - earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); + const lengthBefore = arcLengths[ i ]; + const lengthAfter = arcLengths[ i + 1 ]; - // as a last resort, try splitting the remaining polygon into two + const segmentLength = lengthAfter - lengthBefore; - } else if ( pass === 2 ) { + // determine where we are between the 'before' and 'after' points - splitEarcut( ear, triangles, dim, minX, minY, invSize ); + const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - } + // add that fractional amount to t - break; + const t = ( i + segmentFraction ) / ( il - 1 ); - } + return t; } -} + /** + * Returns a unit vector tangent for the given interpolation factor. + * If the derived curve does not implement its tangent derivation, + * two points a small delta apart will be used to find its gradient + * which seems to give a reasonable approximation. + * + * @param {number} t - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + */ + getTangent( t, optionalTarget ) { -// check whether a polygon node forms a valid ear with adjacent nodes -function isEar( ear ) { + const delta = 0.0001; + let t1 = t - delta; + let t2 = t + delta; - const a = ear.prev, - b = ear, - c = ear.next; + // Capping in case of danger - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + const pt1 = this.getPoint( t1 ); + const pt2 = this.getPoint( t2 ); - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - let p = c.next; - while ( p !== a ) { + tangent.copy( pt2 ).sub( pt1 ).normalize(); - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && - area( p.prev, p, p.next ) >= 0 ) return false; - p = p.next; + return tangent; } - return true; + /** + * Same as {@link Curve#getTangent} but with equidistant samples. + * + * @param {number} u - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + * @see {@link Curve#getPointAt} + */ + getTangentAt( u, optionalTarget ) { -} + const t = this.getUtoTmapping( u ); + return this.getTangent( t, optionalTarget ); -function isEarHashed( ear, minX, minY, invSize ) { + } - const a = ear.prev, - b = ear, - c = ear.next; + /** + * Generates the Frenet Frames. Requires a curve definition in 3D space. Used + * in geometries like {@link TubeGeometry} or {@link ExtrudeGeometry}. + * + * @param {number} segments - The number of segments. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @return {{tangents: Array, normals: Array, binormals: Array}} The Frenet Frames. + */ + computeFrenetFrames( segments, closed = false ) { - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + const normal = new Vector3(); - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + const tangents = []; + const normals = []; + const binormals = []; - // z-order range for the current triangle bbox; - const minZ = zOrder( x0, y0, minX, minY, invSize ), - maxZ = zOrder( x1, y1, minX, minY, invSize ); + const vec = new Vector3(); + const mat = new Matrix4(); - let p = ear.prevZ, - n = ear.nextZ; + // compute the tangent vectors for each segment on the curve - // look for points inside the triangle in both directions - while ( p && p.z >= minZ && n && n.z <= maxZ ) { + for ( let i = 0; i <= segments; i ++ ) { - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + const u = i / segments; - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; + tangents[ i ] = this.getTangentAt( u, new Vector3() ); - } + } - // look for remaining points in decreasing z-order - while ( p && p.z >= minZ ) { + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the minimum tangent xyz component - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + normals[ 0 ] = new Vector3(); + binormals[ 0 ] = new Vector3(); + let min = Number.MAX_VALUE; + const tx = Math.abs( tangents[ 0 ].x ); + const ty = Math.abs( tangents[ 0 ].y ); + const tz = Math.abs( tangents[ 0 ].z ); - } + if ( tx <= min ) { - // look for remaining points in increasing z-order - while ( n && n.z <= maxZ ) { + min = tx; + normal.set( 1, 0, 0 ); - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; + } - } + if ( ty <= min ) { - return true; + min = ty; + normal.set( 0, 1, 0 ); -} + } -// go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections( start, triangles, dim ) { + if ( tz <= min ) { - let p = start; - do { + normal.set( 0, 0, 1 ); - const a = p.prev, - b = p.next.next; + } - if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + vec.crossVectors( tangents[ 0 ], normal ).normalize(); - triangles.push( a.i / dim | 0 ); - triangles.push( p.i / dim | 0 ); - triangles.push( b.i / dim | 0 ); + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - // remove two nodes involved - removeNode( p ); - removeNode( p.next ); - p = start = b; + // compute the slowly-varying normal and binormal vectors for each segment on the curve - } + for ( let i = 1; i <= segments; i ++ ) { - p = p.next; + normals[ i ] = normals[ i - 1 ].clone(); - } while ( p !== start ); + binormals[ i ] = binormals[ i - 1 ].clone(); - return filterPoints( p ); + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); -} + if ( vec.length() > Number.EPSILON ) { -// try splitting polygon into two and triangulate them independently -function splitEarcut( start, triangles, dim, minX, minY, invSize ) { + vec.normalize(); - // look for a valid diagonal that divides the polygon into two - let a = start; - do { + const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors - let b = a.next.next; - while ( b !== a.prev ) { + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - if ( a.i !== b.i && isValidDiagonal( a, b ) ) { + } - // split the polygon in two by the diagonal - let c = splitPolygon( a, b ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - // filter colinear points around the cuts - a = filterPoints( a, a.next ); - c = filterPoints( c, c.next ); + } - // run earcut on each half - earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); - earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); - return; + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - } + if ( closed === true ) { - b = b.next; + let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), -1, 1 ) ); + theta /= segments; - } + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - a = a.next; + theta = - theta; - } while ( a !== start ); + } -} + for ( let i = 1; i <= segments; i ++ ) { -// link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles( data, holeIndices, outerNode, dim ) { + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - const queue = []; - let i, len, start, end, list; + } - for ( i = 0, len = holeIndices.length; i < len; i ++ ) { + } - start = holeIndices[ i ] * dim; - end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; - list = linkedList( data, start, end, dim, false ); - if ( list === list.next ) list.steiner = true; - queue.push( getLeftmost( list ) ); + return { + tangents: tangents, + normals: normals, + binormals: binormals + }; } - queue.sort( compareX ); - - // process holes from left to right - for ( i = 0; i < queue.length; i ++ ) { + /** + * Returns a new curve with copied values from this instance. + * + * @return {Curve} A clone of this instance. + */ + clone() { - outerNode = eliminateHole( queue[ i ], outerNode ); + return new this.constructor().copy( this ); } - return outerNode; + /** + * Copies the values of the given curve to this instance. + * + * @param {Curve} source - The curve to copy. + * @return {Curve} A reference to this curve. + */ + copy( source ) { -} + this.arcLengthDivisions = source.arcLengthDivisions; -function compareX( a, b ) { + return this; - return a.x - b.x; + } -} + /** + * Serializes the curve into JSON. + * + * @return {Object} A JSON object representing the serialized curve. + * @see {@link ObjectLoader#parse} + */ + toJSON() { -// find a bridge between vertices that connects hole with an outer ring and link it -function eliminateHole( hole, outerNode ) { + const data = { + metadata: { + version: 4.7, + type: 'Curve', + generator: 'Curve.toJSON' + } + }; - const bridge = findHoleBridge( hole, outerNode ); - if ( ! bridge ) { + data.arcLengthDivisions = this.arcLengthDivisions; + data.type = this.type; - return outerNode; + return data; } - const bridgeReverse = splitPolygon( bridge, hole ); + /** + * Deserializes the curve from the given JSON. + * + * @param {Object} json - The JSON holding the serialized curve. + * @return {Curve} A reference to this curve. + */ + fromJSON( json ) { - // filter collinear points around the cuts - filterPoints( bridgeReverse, bridgeReverse.next ); - return filterPoints( bridge, bridge.next ); + this.arcLengthDivisions = json.arcLengthDivisions; -} + return this; -// David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge( hole, outerNode ) { + } + +} - let p = outerNode, - qx = - Infinity, - m; +/** + * A curve representing an ellipse. + * + * ```js + * const curve = new THREE.EllipseCurve( + * 0, 0, + * 10, 10, + * 0, 2 * Math.PI, + * false, + * 0 + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const ellipse = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class EllipseCurve extends Curve { - const hx = hole.x, hy = hole.y; + /** + * Constructs a new ellipse curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [yRadius=1] - The radius of the ellipse in the y direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + */ + constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { + super(); - if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEllipseCurve = true; - const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); - if ( x <= hx && x > qx ) { + this.type = 'EllipseCurve'; - qx = x; - m = p.x < p.next.x ? p : p.next; - if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint + /** + * The X center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aX = aX; - } + /** + * The Y center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aY = aY; - } + /** + * The radius of the ellipse in the x direction. + * Setting the this value equal to the {@link EllipseCurve#yRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.xRadius = xRadius; - p = p.next; + /** + * The radius of the ellipse in the y direction. + * Setting the this value equal to the {@link EllipseCurve#xRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.yRadius = yRadius; + + /** + * The start angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aStartAngle = aStartAngle; + + /** + * The end angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default Math.PI*2 + */ + this.aEndAngle = aEndAngle; + + /** + * Whether the ellipse is drawn clockwise or not. + * + * @type {boolean} + * @default false + */ + this.aClockwise = aClockwise; - } while ( p !== outerNode ); + /** + * The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aRotation = aRotation; + + } - if ( ! m ) return null; + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point + const point = optionalTarget; - const stop = m, - mx = m.x, - my = m.y; - let tanMin = Infinity, tan; + const twoPi = Math.PI * 2; + let deltaAngle = this.aEndAngle - this.aStartAngle; + const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - p = m; + // ensures that deltaAngle is 0 .. 2 PI + while ( deltaAngle < 0 ) deltaAngle += twoPi; + while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - do { + if ( deltaAngle < Number.EPSILON ) { - if ( hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { + if ( samePoints ) { - tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential + deltaAngle = 0; - if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { + } else { - m = p; - tanMin = tan; + deltaAngle = twoPi; } } - p = p.next; - - } while ( p !== stop ); + if ( this.aClockwise === true && ! samePoints ) { - return m; + if ( deltaAngle === twoPi ) { -} + deltaAngle = - twoPi; -// whether sector in vertex m contains sector in vertex p in the same coordinates -function sectorContainsSector( m, p ) { + } else { - return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; + deltaAngle = deltaAngle - twoPi; -} + } -// interlink polygon nodes in z-order -function indexCurve( start, minX, minY, invSize ) { + } - let p = start; - do { + const angle = this.aStartAngle + t * deltaAngle; + let x = this.aX + this.xRadius * Math.cos( angle ); + let y = this.aY + this.yRadius * Math.sin( angle ); - if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; + if ( this.aRotation !== 0 ) { - } while ( p !== start ); + const cos = Math.cos( this.aRotation ); + const sin = Math.sin( this.aRotation ); - p.prevZ.nextZ = null; - p.prevZ = null; + const tx = x - this.aX; + const ty = y - this.aY; - sortLinked( p ); + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + this.aX; + y = tx * sin + ty * cos + this.aY; -} + } -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked( list ) { + return point.set( x, y ); - let i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; + } - do { + copy( source ) { - p = list; - list = null; - tail = null; - numMerges = 0; + super.copy( source ); - while ( p ) { + this.aX = source.aX; + this.aY = source.aY; - numMerges ++; - q = p; - pSize = 0; - for ( i = 0; i < inSize; i ++ ) { + this.xRadius = source.xRadius; + this.yRadius = source.yRadius; - pSize ++; - q = q.nextZ; - if ( ! q ) break; + this.aStartAngle = source.aStartAngle; + this.aEndAngle = source.aEndAngle; - } + this.aClockwise = source.aClockwise; - qSize = inSize; + this.aRotation = source.aRotation; - while ( pSize > 0 || ( qSize > 0 && q ) ) { + return this; - if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { + } - e = p; - p = p.nextZ; - pSize --; + toJSON() { - } else { + const data = super.toJSON(); - e = q; - q = q.nextZ; - qSize --; + data.aX = this.aX; + data.aY = this.aY; - } + data.xRadius = this.xRadius; + data.yRadius = this.yRadius; - if ( tail ) tail.nextZ = e; - else list = e; + data.aStartAngle = this.aStartAngle; + data.aEndAngle = this.aEndAngle; - e.prevZ = tail; - tail = e; + data.aClockwise = this.aClockwise; - } + data.aRotation = this.aRotation; - p = q; + return data; - } + } - tail.nextZ = null; - inSize *= 2; + fromJSON( json ) { - } while ( numMerges > 1 ); + super.fromJSON( json ); - return list; + this.aX = json.aX; + this.aY = json.aY; -} + this.xRadius = json.xRadius; + this.yRadius = json.yRadius; -// z-order of a point given coords and inverse of the longer side of data bbox -function zOrder( x, y, minX, minY, invSize ) { + this.aStartAngle = json.aStartAngle; + this.aEndAngle = json.aEndAngle; - // coords are transformed into non-negative 15-bit integer range - x = ( x - minX ) * invSize | 0; - y = ( y - minY ) * invSize | 0; + this.aClockwise = json.aClockwise; - x = ( x | ( x << 8 ) ) & 0x00FF00FF; - x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; - x = ( x | ( x << 2 ) ) & 0x33333333; - x = ( x | ( x << 1 ) ) & 0x55555555; + this.aRotation = json.aRotation; - y = ( y | ( y << 8 ) ) & 0x00FF00FF; - y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; - y = ( y | ( y << 2 ) ) & 0x33333333; - y = ( y | ( y << 1 ) ) & 0x55555555; + return this; - return x | ( y << 1 ); + } } -// find the leftmost node of a polygon ring -function getLeftmost( start ) { +/** + * A curve representing an arc. + * + * @augments EllipseCurve + */ +class ArcCurve extends EllipseCurve { + + /** + * Constructs a new arc curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [aRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + */ + constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - let p = start, - leftmost = start; - do { + super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; - p = p.next; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArcCurve = true; - } while ( p !== start ); + this.type = 'ArcCurve'; - return leftmost; + } } -// check if a point lies within a convex triangle -function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { +function CubicPoly() { - return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && - ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && - ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); + /** + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ -} + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal( a, b ) { + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ - return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges - ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible - ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors - equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case + let c0 = 0, c1 = 0, c2 = 0, c3 = 0; -} + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + function init( x0, x1, t0, t1 ) { -// signed area of a triangle -function area( p, q, r ) { + c0 = x0; + c1 = t0; + c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1; + c3 = 2 * x0 - 2 * x1 + t0 + t1; - return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + } -} + return { -// check if two points are equal -function equals( p1, p2 ) { + initCatmullRom: function ( x0, x1, x2, x3, tension ) { - return p1.x === p2.x && p1.y === p2.y; + init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); -} + }, -// check if two segments intersect -function intersects( p1, q1, p2, q2 ) { + initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { + + // compute tangents when parameterized in [t1,t2] + let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - const o1 = sign( area( p1, q1, p2 ) ); - const o2 = sign( area( p1, q1, q2 ) ); - const o3 = sign( area( p2, q2, p1 ) ); - const o4 = sign( area( p2, q2, q1 ) ); + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; - if ( o1 !== o2 && o3 !== o4 ) return true; // general case + init( x1, x2, t1, t2 ); - if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + }, - return false; + calc: function ( t ) { -} + const t2 = t * t; + const t3 = t2 * t; + return c0 + c1 * t + c2 * t2 + c3 * t3; -// for collinear points p, q, r, check if point q lies on segment pr -function onSegment( p, q, r ) { + } - return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); + }; } -function sign( num ) { - - return num > 0 ? 1 : num < 0 ? - 1 : 0; +// -} +const tmp = /*@__PURE__*/ new Vector3(); +const px = /*@__PURE__*/ new CubicPoly(); +const py = /*@__PURE__*/ new CubicPoly(); +const pz = /*@__PURE__*/ new CubicPoly(); -// check if a polygon diagonal intersects any polygon segments -function intersectsPolygon( a, b ) { +/** + * A curve representing a Catmull-Rom spline. + * + * ```js + * //Create a closed wavey loop + * const curve = new THREE.CatmullRomCurve3( [ + * new THREE.Vector3( -10, 0, 10 ), + * new THREE.Vector3( -5, 5, 5 ), + * new THREE.Vector3( 0, 0, 0 ), + * new THREE.Vector3( 5, -5, 5 ), + * new THREE.Vector3( 10, 0, 10 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CatmullRomCurve3 extends Curve { - let p = a; - do { + /** + * Constructs a new Catmull-Rom curve. + * + * @param {Array} [points] - An array of 3D points defining the curve. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @param {('centripetal'|'chordal'|'catmullrom')} [curveType='centripetal'] - The curve type. + * @param {number} [tension=0.5] - Tension of the curve. + */ + constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { - if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects( p, p.next, a, b ) ) return true; - p = p.next; + super(); - } while ( p !== a ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCatmullRomCurve3 = true; - return false; + this.type = 'CatmullRomCurve3'; -} + /** + * An array of 3D points defining the curve. + * + * @type {Array} + */ + this.points = points; -// check if a polygon diagonal is locally inside the polygon -function locallyInside( a, b ) { + /** + * Whether the curve is closed or not. + * + * @type {boolean} + * @default false + */ + this.closed = closed; - return area( a.prev, a, a.next ) < 0 ? - area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : - area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; + /** + * The curve type. + * + * @type {('centripetal'|'chordal'|'catmullrom')} + * @default 'centripetal' + */ + this.curveType = curveType; -} + /** + * Tension of the curve. + * + * @type {number} + * @default 0.5 + */ + this.tension = tension; -// check if the middle point of a polygon diagonal is inside the polygon -function middleInside( a, b ) { + } - let p = a, - inside = false; - const px = ( a.x + b.x ) / 2, - py = ( a.y + b.y ) / 2; - do { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && - ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) - inside = ! inside; - p = p.next; + const point = optionalTarget; - } while ( p !== a ); + const points = this.points; + const l = points.length; - return inside; + const p = ( l - ( this.closed ? 0 : 1 ) ) * t; + let intPoint = Math.floor( p ); + let weight = p - intPoint; -} + if ( this.closed ) { -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; -// if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon( a, b ) { + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - const a2 = new Node$1( a.i, a.x, a.y ), - b2 = new Node$1( b.i, b.x, b.y ), - an = a.next, - bp = b.prev; + } else if ( weight === 0 && intPoint === l - 1 ) { - a.next = b; - b.prev = a; + intPoint = l - 2; + weight = 1; - a2.next = an; - an.prev = a2; + } - b2.next = a2; - a2.prev = b2; + let p0, p3; // 4 points (p1 & p2 defined below) - bp.next = b2; - b2.prev = bp; + if ( this.closed || intPoint > 0 ) { - return b2; + p0 = points[ ( intPoint - 1 ) % l ]; -} + } else { -// create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode( i, x, y, last ) { + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; - const p = new Node$1( i, x, y ); + } - if ( ! last ) { + const p1 = points[ intPoint % l ]; + const p2 = points[ ( intPoint + 1 ) % l ]; - p.prev = p; - p.next = p; + if ( this.closed || intPoint + 2 < l ) { - } else { + p3 = points[ ( intPoint + 2 ) % l ]; - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; + } else { - } + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); + p3 = tmp; - return p; + } -} + if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { -function removeNode( p ) { + // init Centripetal / Chordal Catmull-Rom + const pow = this.curveType === 'chordal' ? 0.5 : 0.25; + let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - p.next.prev = p.prev; - p.prev.next = p.next; + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; - if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; - if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); -} + } else if ( this.curveType === 'catmullrom' ) { -function Node$1( i, x, y ) { + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - // vertex index in coordinates array - this.i = i; + } - // vertex coordinates - this.x = x; - this.y = y; + point.set( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; + return point; - // z-order curve value - this.z = 0; + } - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; + copy( source ) { - // indicates whether this is a steiner point - this.steiner = false; + super.copy( source ); -} + this.points = []; -function signedArea( data, start, end, dim ) { + for ( let i = 0, l = source.points.length; i < l; i ++ ) { - let sum = 0; - for ( let i = start, j = end - dim; i < end; i += dim ) { + const point = source.points[ i ]; - sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); - j = i; + this.points.push( point.clone() ); - } + } - return sum; + this.closed = source.closed; + this.curveType = source.curveType; + this.tension = source.tension; -} + return this; -class ShapeUtils { + } - // calculate area of the contour polygon + toJSON() { - static area( contour ) { + const data = super.toJSON(); - const n = contour.length; - let a = 0.0; + data.points = []; - for ( let p = n - 1, q = 0; q < n; p = q ++ ) { + for ( let i = 0, l = this.points.length; i < l; i ++ ) { - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + const point = this.points[ i ]; + data.points.push( point.toArray() ); } - return a * 0.5; + data.closed = this.closed; + data.curveType = this.curveType; + data.tension = this.tension; + + return data; } - static isClockWise( pts ) { + fromJSON( json ) { - return ShapeUtils.area( pts ) < 0; + super.fromJSON( json ); + + this.points = []; + + for ( let i = 0, l = json.points.length; i < l; i ++ ) { + + const point = json.points[ i ]; + this.points.push( new Vector3().fromArray( point ) ); + + } + + this.closed = json.closed; + this.curveType = json.curveType; + this.tension = json.tension; + + return this; } - static triangulateShape( contour, holes ) { +} - const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] - const holeIndices = []; // array of hole indices - const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] +// Bezier Curves formulas obtained from: https://en.wikipedia.org/wiki/B%C3%A9zier_curve - removeDupEndPts( contour ); - addContour( vertices, contour ); +/** + * Computes a point on a Catmull-Rom spline. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Catmull-Rom spline. + */ +function CatmullRom( t, p0, p1, p2, p3 ) { - // + const v0 = ( p2 - p0 ) * 0.5; + const v1 = ( p3 - p1 ) * 0.5; + const t2 = t * t; + const t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( -3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - let holeIndex = contour.length; +} - holes.forEach( removeDupEndPts ); +// - for ( let i = 0; i < holes.length; i ++ ) { +function QuadraticBezierP0( t, p ) { - holeIndices.push( holeIndex ); - holeIndex += holes[ i ].length; - addContour( vertices, holes[ i ] ); + const k = 1 - t; + return k * k * p; - } +} - // +function QuadraticBezierP1( t, p ) { - const triangles = Earcut.triangulate( vertices, holeIndices ); + return 2 * ( 1 - t ) * t * p; - // +} - for ( let i = 0; i < triangles.length; i += 3 ) { +function QuadraticBezierP2( t, p ) { - faces.push( triangles.slice( i, i + 3 ) ); + return t * t * p; - } +} - return faces; +/** + * Computes a point on a Quadratic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @return {number} The calculated point on a Quadratic Bezier curve. + */ +function QuadraticBezier( t, p0, p1, p2 ) { - } + return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + + QuadraticBezierP2( t, p2 ); } -function removeDupEndPts( points ) { +// - const l = points.length; +function CubicBezierP0( t, p ) { - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { + const k = 1 - t; + return k * k * k * p; - points.pop(); +} - } +function CubicBezierP1( t, p ) { + + const k = 1 - t; + return 3 * k * k * t * p; } -function addContour( vertices, contour ) { +function CubicBezierP2( t, p ) { - for ( let i = 0; i < contour.length; i ++ ) { + return 3 * ( 1 - t ) * t * t * p; - vertices.push( contour[ i ].x ); - vertices.push( contour[ i ].y ); +} - } +function CubicBezierP3( t, p ) { + + return t * t * t * p; } /** - * Creates extruded geometry from a path shape. + * Computes a point on a Cubic Bezier curve. * - * parameters = { + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Cubic Bezier curve. + */ +function CubicBezier( t, p0, p1, p2, p3 ) { + + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); + +} + +/** + * A curve representing a 2D Cubic Bezier curve. * - * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * depth: , // Depth to extrude the shape + * ```js + * const curve = new THREE.CubicBezierCurve( + * new THREE.Vector2( - 0, 0 ), + * new THREE.Vector2( - 5, 15 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ); * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into the original shape bevel goes - * bevelSize: , // how far from shape outline (including bevelOffset) is bevel - * bevelOffset: , // how far from shape outline does bevel start - * bevelSegments: , // number of bevel layers + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); * - * extrudePath: // curve to extrude shape along + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); * - * UVGenerator: // object that provides UV generator functions + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` * - * } + * @augments Curve */ +class CubicBezierCurve extends Curve { - -class ExtrudeGeometry extends BufferGeometry { - - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The first control point. + * @param {Vector2} [v2] - The second control point. + * @param {Vector2} [v3] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { super(); - this.type = 'ExtrudeGeometry'; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve = true; - this.parameters = { - shapes: shapes, - options: options - }; + this.type = 'CubicBezierCurve'; - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - const scope = this; + /** + * The first control point. + * + * @type {Vector2} + */ + this.v1 = v1; - const verticesArray = []; - const uvArray = []; + /** + * The second control point. + * + * @type {Vector2} + */ + this.v2 = v2; - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + /** + * The end point. + * + * @type {Vector2} + */ + this.v3 = v3; - const shape = shapes[ i ]; - addShape( shape ); + } - } + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - // build geometry + const point = optionalTarget; - this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - this.computeVertexNormals(); + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) + ); - // functions + return point; - function addShape( shape ) { - - const placeholder = []; + } - // options + copy( source ) { - const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - const steps = options.steps !== undefined ? options.steps : 1; - const depth = options.depth !== undefined ? options.depth : 1; + super.copy( source ); - let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; - let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; - let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; - let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; - let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); - const extrudePath = options.extrudePath; + return this; - const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; + } - // + toJSON() { - let extrudePts, extrudeByPath = false; - let splineTube, binormal, normal, position2; + const data = super.toJSON(); - if ( extrudePath ) { + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - extrudePts = extrudePath.getSpacedPoints( steps ); + return data; - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion + } - // SETUP TNB variables + fromJSON( json ) { - // TODO1 - have a .isClosed in spline? + super.fromJSON( json ); - splineTube = extrudePath.computeFrenetFrames( steps, false ); + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); - // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + return this; - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); + } - } +} - // Safeguards if bevels are not enabled +/** + * A curve representing a 3D Cubic Bezier curve. + * + * @augments Curve + */ +class CubicBezierCurve3 extends Curve { - if ( ! bevelEnabled ) { + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The first control point. + * @param {Vector3} [v2] - The second control point. + * @param {Vector3} [v3] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - bevelOffset = 0; + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve3 = true; - // Variables initialization + this.type = 'CubicBezierCurve3'; - const shapePoints = shape.extractPoints( curveSegments ); + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - let vertices = shapePoints.shape; - const holes = shapePoints.holes; + /** + * The first control point. + * + * @type {Vector3} + */ + this.v1 = v1; - const reverse = ! ShapeUtils.isClockWise( vertices ); + /** + * The second control point. + * + * @type {Vector3} + */ + this.v2 = v2; - if ( reverse ) { + /** + * The end point. + * + * @type {Vector3} + */ + this.v3 = v3; - vertices = vertices.reverse(); + } - // Maybe we should also check if holes are in the opposite direction, just to be safe ... + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const point = optionalTarget; - const ahole = holes[ h ]; + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - if ( ShapeUtils.isClockWise( ahole ) ) { + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), + CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) + ); - holes[ h ] = ahole.reverse(); + return point; - } + } - } + copy( source ) { - } + super.copy( source ); + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); - const faces = ShapeUtils.triangulateShape( vertices, holes ); + return this; - /* Vertices */ + } - const contour = vertices; // vertices has all points but contour has only points of circumference + toJSON() { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const data = super.toJSON(); - const ahole = holes[ h ]; + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - vertices = vertices.concat( ahole ); + return data; - } + } + fromJSON( json ) { - function scalePt2( pt, vec, size ) { + super.fromJSON( json ); - if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); - return pt.clone().addScaledVector( vec, size ); + return this; - } + } - const vlen = vertices.length, flen = faces.length; +} +/** + * A curve representing a 2D line segment. + * + * @augments Curve + */ +class LineCurve extends Curve { - // Find directions for point movement + /** + * Constructs a new line curve. + * + * @param {Vector2} [v1] - The start point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v1 = new Vector2(), v2 = new Vector2() ) { + super(); - function getBevelVec( inPt, inPrev, inNext ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve = true; - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. + this.type = 'LineCurve'; - let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt + /** + * The start point. + * + * @type {Vector2} + */ + this.v1 = v1; - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - const v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - const v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; + } - const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the line. + */ + getPoint( t, optionalTarget = new Vector2() ) { - // check for collinear edges - const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + const point = optionalTarget; - if ( Math.abs( collinear0 ) > Number.EPSILON ) { + if ( t === 1 ) { - // not collinear + point.copy( this.v2 ); - // length of vectors for normalizing + } else { - const v_prev_len = Math.sqrt( v_prev_lensq ); - const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - // shift adjacent points by unit vectors to the left + } - const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + return point; - const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + } - // scaling factor for v_prev to intersection point + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + return this.getPoint( u, optionalTarget ); - // vector from inPt to intersection point + } - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + getTangent( t, optionalTarget = new Vector2() ) { - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - return new Vector2( v_trans_x, v_trans_y ); + } - } else { + getTangentAt( u, optionalTarget ) { - shrink_by = Math.sqrt( v_trans_lensq / 2 ); + return this.getTangent( u, optionalTarget ); - } + } - } else { + copy( source ) { - // handle special case of collinear edges + super.copy( source ); - let direction_eq = false; // assumes: opposite + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - if ( v_prev_x > Number.EPSILON ) { + return this; - if ( v_next_x > Number.EPSILON ) { + } - direction_eq = true; + toJSON() { - } + const data = super.toJSON(); - } else { + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - if ( v_prev_x < - Number.EPSILON ) { + return data; - if ( v_next_x < - Number.EPSILON ) { + } - direction_eq = true; + fromJSON( json ) { - } + super.fromJSON( json ); - } else { + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { + return this; - direction_eq = true; + } - } +} - } +/** + * A curve representing a 3D line segment. + * + * @augments Curve + */ +class LineCurve3 extends Curve { - } + /** + * Constructs a new line curve. + * + * @param {Vector3} [v1] - The start point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v1 = new Vector3(), v2 = new Vector3() ) { - if ( direction_eq ) { + super(); - // console.log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve3 = true; - } else { + this.type = 'LineCurve3'; - // console.log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); + /** + * The start point. + * + * @type {Vector3} + */ + this.v1 = v1; - } + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - } + } - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the line. + */ + getPoint( t, optionalTarget = new Vector3() ) { - } + const point = optionalTarget; + if ( t === 1 ) { - const contourMovements = []; + point.copy( this.v2 ); - for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + } else { - if ( j === il ) j = 0; - if ( k === il ) k = 0; + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - // (j)---(i)---(k) - // console.log('i,j,k', i, j , k) + } - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + return point; - } + } - const holesMovements = []; - let oneHoleMovements, verticesMovements = contourMovements.concat(); + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + return this.getPoint( u, optionalTarget ); - const ahole = holes[ h ]; + } - oneHoleMovements = []; + getTangent( t, optionalTarget = new Vector3() ) { - for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - if ( j === il ) j = 0; - if ( k === il ) k = 0; + } - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + getTangentAt( u, optionalTarget ) { - } + return this.getTangent( u, optionalTarget ); - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); + } - } + copy( source ) { + super.copy( source ); - // Loop bevelSegments, 1 for the front, 1 for the back + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - for ( let b = 0; b < bevelSegments; b ++ ) { + return this; - //for ( b = bevelSegments; b > 0; b -- ) { + } - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + toJSON() { - // contract shape + const data = super.toJSON(); - for ( let i = 0, il = contour.length; i < il; i ++ ) { + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + return data; - v( vert.x, vert.y, - z ); + } - } + fromJSON( json ) { - // expand holes + super.fromJSON( json ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + return this; - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + } - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); +} - v( vert.x, vert.y, - z ); +/** + * A curve representing a 2D Quadratic Bezier curve. + * + * ```js + * const curve = new THREE.QuadraticBezierCurve( + * new THREE.Vector2( - 10, 0 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ) + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class QuadraticBezierCurve extends Curve { - } + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The control point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - } + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve = true; - const bs = bevelSize + bevelOffset; + this.type = 'QuadraticBezierCurve'; - // Back facing vertices + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - for ( let i = 0; i < vlen; i ++ ) { + /** + * The control point. + * + * @type {Vector2} + */ + this.v1 = v1; - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - if ( ! extrudeByPath ) { + } - v( vert.x, vert.y, 0 ); + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - } else { + const point = optionalTarget; - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ) + ); - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); + return point; - v( position2.x, position2.y, position2.z ); + } - } + copy( source ) { - } + super.copy( source ); - // Add stepped vertices... - // Including front facing vertices + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - for ( let s = 1; s <= steps; s ++ ) { + return this; - for ( let i = 0; i < vlen; i ++ ) { + } - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + toJSON() { - if ( ! extrudeByPath ) { + const data = super.toJSON(); - v( vert.x, vert.y, depth / steps * s ); + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - } else { + return data; - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + } - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); + fromJSON( json ) { - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); + super.fromJSON( json ); - v( position2.x, position2.y, position2.z ); + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - } + return this; - } + } - } +} +/** + * A curve representing a 3D Quadratic Bezier curve. + * + * @augments Curve + */ +class QuadraticBezierCurve3 extends Curve { - // Add bevel segments planes + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The control point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( let b = bevelSegments - 1; b >= 0; b -- ) { + super(); - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve3 = true; - // contract shape + this.type = 'QuadraticBezierCurve3'; - for ( let i = 0, il = contour.length; i < il; i ++ ) { + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, depth + z ); + /** + * The control point. + * + * @type {Vector3} + */ + this.v1 = v1; - } + /** + * The end point. + * + * @type {Vector3} + */ + this.v2 = v2; - // expand holes + } - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + const point = optionalTarget; - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ), + QuadraticBezier( t, v0.z, v1.z, v2.z ) + ); - if ( ! extrudeByPath ) { + return point; - v( vert.x, vert.y, depth + z ); + } - } else { + copy( source ) { - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + super.copy( source ); - } + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - } + return this; - } + } - } + toJSON() { - /* Faces */ + const data = super.toJSON(); - // Top and bottom faces + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - buildLidFaces(); + return data; - // Sides faces + } - buildSideFaces(); + fromJSON( json ) { + super.fromJSON( json ); - ///// Internal functions + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - function buildLidFaces() { + return this; - const start = verticesArray.length / 3; + } - if ( bevelEnabled ) { +} - let layer = 0; // steps + 1 - let offset = vlen * layer; +/** + * A curve representing a 2D spline curve. + * + * ```js + * // Create a sine-like wave + * const curve = new THREE.SplineCurve( [ + * new THREE.Vector2( -10, 0 ), + * new THREE.Vector2( -5, 5 ), + * new THREE.Vector2( 0, 0 ), + * new THREE.Vector2( 5, -5 ), + * new THREE.Vector2( 10, 0 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const splineObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class SplineCurve extends Curve { - // Bottom faces + /** + * Constructs a new 2D spline curve. + * + * @param {Array} [points] - An array of 2D points defining the curve. + */ + constructor( points = [] ) { - for ( let i = 0; i < flen; i ++ ) { + super(); - const face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplineCurve = true; - } + this.type = 'SplineCurve'; - layer = steps + bevelSegments * 2; - offset = vlen * layer; + /** + * An array of 2D points defining the curve. + * + * @type {Array} + */ + this.points = points; - // Top faces + } - for ( let i = 0; i < flen; i ++ ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - const face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); + const point = optionalTarget; - } + const points = this.points; + const p = ( points.length - 1 ) * t; - } else { + const intPoint = Math.floor( p ); + const weight = p - intPoint; - // Bottom faces + const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + const p1 = points[ intPoint ]; + const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - for ( let i = 0; i < flen; i ++ ) { + point.set( + CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), + CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) + ); - const face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); + return point; - } + } - // Top faces + copy( source ) { - for ( let i = 0; i < flen; i ++ ) { + super.copy( source ); - const face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); + this.points = []; - } + for ( let i = 0, l = source.points.length; i < l; i ++ ) { - } + const point = source.points[ i ]; - scope.addGroup( start, verticesArray.length / 3 - start, 0 ); + this.points.push( point.clone() ); - } + } - // Create faces for the z-sides of the shape + return this; - function buildSideFaces() { + } - const start = verticesArray.length / 3; - let layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; + toJSON() { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const data = super.toJSON(); - const ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); + data.points = []; - //, true - layeroffset += ahole.length; + for ( let i = 0, l = this.points.length; i < l; i ++ ) { - } + const point = this.points[ i ]; + data.points.push( point.toArray() ); + } - scope.addGroup( start, verticesArray.length / 3 - start, 1 ); + return data; + } - } + fromJSON( json ) { - function sidewalls( contour, layeroffset ) { + super.fromJSON( json ); - let i = contour.length; + this.points = []; - while ( -- i >= 0 ) { + for ( let i = 0, l = json.points.length; i < l; i ++ ) { - const j = i; - let k = i - 1; - if ( k < 0 ) k = contour.length - 1; + const point = json.points[ i ]; + this.points.push( new Vector2().fromArray( point ) ); - //console.log('b', i,j, i-1, k,vertices.length); + } - for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { + return this; - const slen1 = vlen * s; - const slen2 = vlen * ( s + 1 ); + } - const a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; +} - f4( a, b, c, d ); +var Curves = /*#__PURE__*/Object.freeze({ + __proto__: null, + ArcCurve: ArcCurve, + CatmullRomCurve3: CatmullRomCurve3, + CubicBezierCurve: CubicBezierCurve, + CubicBezierCurve3: CubicBezierCurve3, + EllipseCurve: EllipseCurve, + LineCurve: LineCurve, + LineCurve3: LineCurve3, + QuadraticBezierCurve: QuadraticBezierCurve, + QuadraticBezierCurve3: QuadraticBezierCurve3, + SplineCurve: SplineCurve +}); - } +/** + * A base class extending {@link Curve}. `CurvePath` is simply an + * array of connected curves, but retains the API of a curve. + * + * @augments Curve + */ +class CurvePath extends Curve { - } + /** + * Constructs a new curve path. + */ + constructor() { - } + super(); - function v( x, y, z ) { + this.type = 'CurvePath'; - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); + /** + * An array of curves defining the + * path. + * + * @type {Array} + */ + this.curves = []; - } + /** + * Whether the path should automatically be closed + * by a line curve. + * + * @type {boolean} + * @default false + */ + this.autoClose = false; + } - function f3( a, b, c ) { + /** + * Adds a curve to this curve path. + * + * @param {Curve} curve - The curve to add. + */ + add( curve ) { - addVertex( a ); - addVertex( b ); - addVertex( c ); + this.curves.push( curve ); - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + } - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); + /** + * Adds a line curve to close the path. + * + * @return {CurvePath} A reference to this curve path. + */ + closePath() { - } + // Add a line curve if start and end of lines are not connected + const startPoint = this.curves[ 0 ].getPoint( 0 ); + const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - function f4( a, b, c, d ) { + if ( ! startPoint.equals( endPoint ) ) { - addVertex( a ); - addVertex( b ); - addVertex( d ); + const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; + this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); - addVertex( b ); - addVertex( c ); - addVertex( d ); + } + return this; - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + } - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); + /** + * This method returns a vector in 2D or 3D space (depending on the curve definitions) + * for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( t, optionalTarget ) { - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); + // To get accurate point with reference to + // entire path distance at time t, + // following has to be done: - } + // 1. Length of each sub path have to be known + // 2. Locate and identify type of curve + // 3. Get t for the curve + // 4. Return curve.getPointAt(t') - function addVertex( index ) { + const d = t * this.getLength(); + const curveLengths = this.getCurveLengths(); + let i = 0; - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); + // To think about boundaries points. - } + while ( i < curveLengths.length ) { + if ( curveLengths[ i ] >= d ) { - function addUV( vector2 ) { + const diff = curveLengths[ i ] - d; + const curve = this.curves[ i ]; - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); + const segmentLength = curve.getLength(); + const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + + return curve.getPointAt( u, optionalTarget ); } + i ++; + } - } + return null; - copy( source ) { + // loop where sum != 0, sum > d , sum+1 } The curve lengths. + */ + getCurveLengths() { - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + // Compute lengths and cache them + // We cannot overwrite getLengths() because UtoT mapping uses it. + // We use cache values if curves and cache array are same length - const shape = shapes[ data.shapes[ j ] ]; + if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { - geometryShapes.push( shape ); + return this.cacheLengths; } - const extrudePath = data.options.extrudePath; + // Get length of sub-curve + // Push sums into cached array - if ( extrudePath !== undefined ) { + const lengths = []; + let sums = 0; - data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + + sums += this.curves[ i ].getLength(); + lengths.push( sums ); } - return new ExtrudeGeometry( geometryShapes, data.options ); + this.cacheLengths = lengths; - } + return lengths; -} + } -const WorldUVGenerator = { + getSpacedPoints( divisions = 40 ) { - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { + const points = []; - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; + for ( let i = 0; i <= divisions; i ++ ) { - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; + points.push( this.getPoint( i / divisions ) ); - }, + } - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { + if ( this.autoClose ) { - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const a_z = vertices[ indexA * 3 + 2 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const b_z = vertices[ indexB * 3 + 2 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - const c_z = vertices[ indexC * 3 + 2 ]; - const d_x = vertices[ indexD * 3 ]; - const d_y = vertices[ indexD * 3 + 1 ]; - const d_z = vertices[ indexD * 3 + 2 ]; + points.push( points[ 0 ] ); - if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { + } - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; + return points; - } else { + } - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; + getPoints( divisions = 12 ) { - } + const points = []; + let last; - } + for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { -}; + const curve = curves[ i ]; + const resolution = curve.isEllipseCurve ? divisions * 2 + : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 + : curve.isSplineCurve ? divisions * curve.points.length + : divisions; -function toJSON$1( shapes, options, data ) { + const pts = curve.getPoints( resolution ); - data.shapes = []; + for ( let j = 0; j < pts.length; j ++ ) { - if ( Array.isArray( shapes ) ) { + const point = pts[ j ]; - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates - const shape = shapes[ i ]; + points.push( point ); + last = point; - data.shapes.push( shape.uuid ); + } } - } else { - - data.shapes.push( shapes.uuid ); - - } + if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - data.options = Object.assign( {}, options ); + points.push( points[ 0 ] ); - if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); + } - return data; + return points; -} + } -class SphereGeometry extends BufferGeometry { + copy( source ) { - constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { + super.copy( source ); - super(); + this.curves = []; - this.type = 'SphereGeometry'; + for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + const curve = source.curves[ i ]; - widthSegments = Math.max( 3, Math.floor( widthSegments ) ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) ); + this.curves.push( curve.clone() ); - const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); + } - let index = 0; - const grid = []; + this.autoClose = source.autoClose; - const vertex = new Vector3(); - const normal = new Vector3(); + return this; - // buffers + } - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + toJSON() { - // generate vertices, normals and uvs + const data = super.toJSON(); - for ( let iy = 0; iy <= heightSegments; iy ++ ) { + data.autoClose = this.autoClose; + data.curves = []; - const verticesRow = []; + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - const v = iy / heightSegments; + const curve = this.curves[ i ]; + data.curves.push( curve.toJSON() ); - // special case for the poles + } - let uOffset = 0; + return data; - if ( iy === 0 && thetaStart === 0 ) { + } - uOffset = 0.5 / widthSegments; + fromJSON( json ) { - } else if ( iy === heightSegments && thetaEnd === Math.PI ) { + super.fromJSON( json ); - uOffset = - 0.5 / widthSegments; + this.autoClose = json.autoClose; + this.curves = []; - } + for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - for ( let ix = 0; ix <= widthSegments; ix ++ ) { + const curve = json.curves[ i ]; + this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); - const u = ix / widthSegments; + } - // vertex + return this; - vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); - vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + } - vertices.push( vertex.x, vertex.y, vertex.z ); +} - // normal +/** + * A 2D path representation. The class provides methods for creating paths + * and contours of 2D shapes similar to the 2D Canvas API. + * + * ```js + * const path = new THREE.Path(); + * + * path.lineTo( 0, 0.8 ); + * path.quadraticCurveTo( 0, 1, 0.2, 1 ); + * path.lineTo( 1, 1 ); + * + * const points = path.getPoints(); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments CurvePath + */ +let Path$1 = class Path extends CurvePath { - normal.copy( vertex ).normalize(); - normals.push( normal.x, normal.y, normal.z ); + /** + * Constructs a new path. + * + * @param {Array} [points] - An array of 2D points defining the path. + */ + constructor( points ) { - // uv + super(); - uvs.push( u + uOffset, 1 - v ); + this.type = 'Path'; - verticesRow.push( index ++ ); + /** + * The current offset of the path. Any new curve added will start here. + * + * @type {Vector2} + */ + this.currentPoint = new Vector2(); - } + if ( points ) { - grid.push( verticesRow ); + this.setFromPoints( points ); } - // indices - - for ( let iy = 0; iy < heightSegments; iy ++ ) { + } - for ( let ix = 0; ix < widthSegments; ix ++ ) { + /** + * Creates a path from the given list of points. The points are added + * to the path as instances of {@link LineCurve}. + * + * @param {Array} points - An array of 2D points. + * @return {Path} A reference to this path. + */ + setFromPoints( points ) { - const a = grid[ iy ][ ix + 1 ]; - const b = grid[ iy ][ ix ]; - const c = grid[ iy + 1 ][ ix ]; - const d = grid[ iy + 1 ][ ix + 1 ]; + this.moveTo( points[ 0 ].x, points[ 0 ].y ); - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); + for ( let i = 1, l = points.length; i < l; i ++ ) { - } + this.lineTo( points[ i ].x, points[ i ].y ); } - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + return this; } - copy( source ) { - - super.copy( source ); + /** + * Moves {@link Path#currentPoint} to the given point. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {Path} A reference to this path. + */ + moveTo( x, y ) { - this.parameters = Object.assign( {}, source.parameters ); + this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? return this; } - static fromJSON( data ) { + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + lineTo( x, y ) { - return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); + const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); + this.curves.push( curve ); - } + this.currentPoint.set( x, y ); -} + return this; -class MeshStandardMaterial extends Material { + } - constructor( parameters ) { + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { - super(); + const curve = new QuadraticBezierCurve( + this.currentPoint.clone(), + new Vector2( aCPx, aCPy ), + new Vector2( aX, aY ) + ); - this.isMeshStandardMaterial = true; + this.curves.push( curve ); - this.defines = { 'STANDARD': '' }; + this.currentPoint.set( aX, aY ); - this.type = 'MeshStandardMaterial'; + return this; - this.color = new Color( 0xffffff ); // diffuse - this.roughness = 1.0; - this.metalness = 0.0; + } - this.map = null; + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - this.lightMap = null; - this.lightMapIntensity = 1.0; + const curve = new CubicBezierCurve( + this.currentPoint.clone(), + new Vector2( aCP1x, aCP1y ), + new Vector2( aCP2x, aCP2y ), + new Vector2( aX, aY ) + ); - this.aoMap = null; - this.aoMapIntensity = 1.0; + this.curves.push( curve ); - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + this.currentPoint.set( aX, aY ); - this.bumpMap = null; - this.bumpScale = 1; + return this; - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {Path} A reference to this path. + */ + splineThru( pts ) { - this.roughnessMap = null; + const npts = [ this.currentPoint.clone() ].concat( pts ); - this.metalnessMap = null; + const curve = new SplineCurve( npts ); + this.curves.push( curve ); - this.alphaMap = null; + this.currentPoint.copy( pts[ pts.length - 1 ] ); - this.envMap = null; - this.envMapRotation = new Euler(); - this.envMapIntensity = 1.0; + return this; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + } - this.flatShading = false; + /** + * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - this.fog = true; + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; - this.setValues( parameters ); + this.absarc( aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); + + return this; } - copy( source ) { + /** + * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc. + * @param {number} [aY=0] - The y coordinate of the center of the arc. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - super.copy( source ); + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - this.defines = { 'STANDARD': '' }; + return this; - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; + } - this.map = source.map; + /** + * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point + * + * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + return this; - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + } - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + /** + * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse. + * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - this.roughnessMap = source.roughnessMap; + if ( this.curves.length > 0 ) { - this.metalnessMap = source.metalnessMap; + // if a previous curve is present, attempt to join + const firstPoint = curve.getPoint( 0 ); - this.alphaMap = source.alphaMap; + if ( ! firstPoint.equals( this.currentPoint ) ) { - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.envMapIntensity = source.envMapIntensity; + this.lineTo( firstPoint.x, firstPoint.y ); - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + } - this.flatShading = source.flatShading; + } - this.fog = source.fog; + this.curves.push( curve ); + + const lastPoint = curve.getPoint( 1 ); + this.currentPoint.copy( lastPoint ); return this; } -} + copy( source ) { -class MeshPhysicalMaterial extends MeshStandardMaterial { + super.copy( source ); - constructor( parameters ) { + this.currentPoint.copy( source.currentPoint ); - super(); + return this; - this.isMeshPhysicalMaterial = true; + } - this.defines = { + toJSON() { - 'STANDARD': '', - 'PHYSICAL': '' + const data = super.toJSON(); - }; + data.currentPoint = this.currentPoint.toArray(); - this.type = 'MeshPhysicalMaterial'; + return data; - this.anisotropyRotation = 0; - this.anisotropyMap = null; + } - this.clearcoatMap = null; - this.clearcoatRoughness = 0.0; - this.clearcoatRoughnessMap = null; - this.clearcoatNormalScale = new Vector2( 1, 1 ); - this.clearcoatNormalMap = null; + fromJSON( json ) { - this.ior = 1.5; + super.fromJSON( json ); - Object.defineProperty( this, 'reflectivity', { - get: function () { + this.currentPoint.fromArray( json.currentPoint ); - return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); + return this; - }, - set: function ( reflectivity ) { + } - this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); +}; - } - } ); +/** + * Defines an arbitrary 2d shape plane using paths with optional holes. It + * can be used with {@link ExtrudeGeometry}, {@link ShapeGeometry}, to get + * points, or to get triangulated faces. + * + * ```js + * const heartShape = new THREE.Shape(); + * + * heartShape.moveTo( 25, 25 ); + * heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); + * heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); + * heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); + * heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); + * heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); + * heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); + * + * const extrudeSettings = { + * depth: 8, + * bevelEnabled: true, + * bevelSegments: 2, + * steps: 2, + * bevelSize: 1, + * bevelThickness: 1 + * }; + * + * const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); + * const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() ); + * ``` + * + * @augments Path + */ +class Shape extends Path$1 { - this.iridescenceMap = null; - this.iridescenceIOR = 1.3; - this.iridescenceThicknessRange = [ 100, 400 ]; - this.iridescenceThicknessMap = null; + /** + * Constructs a new shape. + * + * @param {Array} [points] - An array of 2D points defining the shape. + */ + constructor( points ) { - this.sheenColor = new Color( 0x000000 ); - this.sheenColorMap = null; - this.sheenRoughness = 1.0; - this.sheenRoughnessMap = null; + super( points ); - this.transmissionMap = null; + /** + * The UUID of the shape. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - this.thickness = 0; - this.thicknessMap = null; - this.attenuationDistance = Infinity; - this.attenuationColor = new Color( 1, 1, 1 ); + this.type = 'Shape'; - this.specularIntensity = 1.0; - this.specularIntensityMap = null; - this.specularColor = new Color( 1, 1, 1 ); - this.specularColorMap = null; + /** + * Defines the holes in the shape. Hole definitions must use the + * opposite winding order (CW/CCW) than the outer shape. + * + * @type {Array} + * @readonly + */ + this.holes = []; - this._anisotropy = 0; - this._clearcoat = 0; - this._iridescence = 0; - this._sheen = 0.0; - this._transmission = 0; + } - this.setValues( parameters ); + /** + * Returns an array representing each contour of the holes + * as a list of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {Array>} The holes as a series of 2D points. + */ + getPointsHoles( divisions ) { - } + const holesPts = []; - get anisotropy() { + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - return this._anisotropy; + holesPts[ i ] = this.holes[ i ].getPoints( divisions ); + + } + + return holesPts; } - set anisotropy( value ) { + // get points of shape and holes (keypoints based on segments parameter) - if ( this._anisotropy > 0 !== value > 0 ) { + /** + * Returns an object that holds contour data for the shape and its holes as + * arrays of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {{shape:Array,holes:Array>}} An object with contour data. + */ + extractPoints( divisions ) { - this.version ++; + return { - } + shape: this.getPoints( divisions ), + holes: this.getPointsHoles( divisions ) - this._anisotropy = value; + }; } - get clearcoat() { + copy( source ) { - return this._clearcoat; + super.copy( source ); - } + this.holes = []; - set clearcoat( value ) { + for ( let i = 0, l = source.holes.length; i < l; i ++ ) { - if ( this._clearcoat > 0 !== value > 0 ) { + const hole = source.holes[ i ]; - this.version ++; + this.holes.push( hole.clone() ); } - this._clearcoat = value; + return this; } - get iridescence() { - - return this._iridescence; + toJSON() { - } + const data = super.toJSON(); - set iridescence( value ) { + data.uuid = this.uuid; + data.holes = []; - if ( this._iridescence > 0 !== value > 0 ) { + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - this.version ++; + const hole = this.holes[ i ]; + data.holes.push( hole.toJSON() ); } - this._iridescence = value; + return data; } - get sheen() { - - return this._sheen; + fromJSON( json ) { - } + super.fromJSON( json ); - set sheen( value ) { + this.uuid = json.uuid; + this.holes = []; - if ( this._sheen > 0 !== value > 0 ) { + for ( let i = 0, l = json.holes.length; i < l; i ++ ) { - this.version ++; + const hole = json.holes[ i ]; + this.holes.push( new Path$1().fromJSON( hole ) ); } - this._sheen = value; + return this; } - get transmission() { +} - return this._transmission; +/* eslint-disable */ +// copy of mapbox/earcut version 3.0.1 +// https://github.com/mapbox/earcut/tree/v3.0.1 - } +function earcut(data, holeIndices, dim = 2) { - set transmission( value ) { + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; - if ( this._transmission > 0 !== value > 0 ) { + if (!outerNode || outerNode.next === outerNode.prev) return triangles; - this.version ++; + let minX, minY, invSize; - } + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); - this._transmission = value; + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = Infinity; + minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; - } + for (let i = dim; i < outerLen; i += dim) { + const x = data[i]; + const y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } - copy( source ) { + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } - super.copy( source ); + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); - this.defines = { + return triangles; +} - 'STANDARD': '', - 'PHYSICAL': '' +// create a circular doubly linked list from polygon points in the specified winding order +function linkedList(data, start, end, dim, clockwise) { + let last; - }; + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } else { + for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } - this.anisotropy = source.anisotropy; - this.anisotropyRotation = source.anisotropyRotation; - this.anisotropyMap = source.anisotropyMap; + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } - this.clearcoat = source.clearcoat; - this.clearcoatMap = source.clearcoatMap; - this.clearcoatRoughness = source.clearcoatRoughness; - this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; - this.clearcoatNormalMap = source.clearcoatNormalMap; - this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); + return last; +} - this.ior = source.ior; +// eliminate colinear or duplicate points +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; - this.iridescence = source.iridescence; - this.iridescenceMap = source.iridescenceMap; - this.iridescenceIOR = source.iridescenceIOR; - this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; - this.iridescenceThicknessMap = source.iridescenceThicknessMap; + let p = start, + again; + do { + again = false; - this.sheen = source.sheen; - this.sheenColor.copy( source.sheenColor ); - this.sheenColorMap = source.sheenColorMap; - this.sheenRoughness = source.sheenRoughness; - this.sheenRoughnessMap = source.sheenRoughnessMap; + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; - this.transmission = source.transmission; - this.transmissionMap = source.transmissionMap; + } else { + p = p.next; + } + } while (again || p !== end); - this.thickness = source.thickness; - this.thicknessMap = source.thicknessMap; - this.attenuationDistance = source.attenuationDistance; - this.attenuationColor.copy( source.attenuationColor ); + return end; +} - this.specularIntensity = source.specularIntensity; - this.specularIntensityMap = source.specularIntensityMap; - this.specularColor.copy( source.specularColor ); - this.specularColorMap = source.specularColorMap; +// main ear slicing loop which triangulates a polygon (given as a linked list) +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; - return this; + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); - } + let stop = ear; -} + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + const prev = ear.prev; + const next = ear.next; -class MeshPhongMaterial extends Material { + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + triangles.push(prev.i, ear.i, next.i); // cut off the triangle - constructor( parameters ) { + removeNode(ear); - super(); + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; - this.isMeshPhongMaterial = true; + continue; + } - this.type = 'MeshPhongMaterial'; + ear = next; - this.color = new Color( 0xffffff ); // diffuse - this.specular = new Color( 0x111111 ); - this.shininess = 30; + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); - this.map = null; + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); - this.lightMap = null; - this.lightMapIntensity = 1.0; + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } - this.aoMap = null; - this.aoMapIntensity = 1.0; + break; + } + } +} - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; +// check whether a polygon node forms a valid ear with adjacent nodes +function isEar(ear) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + let p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } - this.bumpMap = null; - this.bumpScale = 1; + return true; +} - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); +function isEarHashed(ear, minX, minY, invSize) { + const a = ear.prev, + b = ear, + c = ear.next; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - this.specularMap = null; + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - this.alphaMap = null; + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); - this.envMap = null; - this.envMapRotation = new Euler(); - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + let p = ear.prevZ, + n = ear.nextZ; - this.flatShading = false; + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; - this.fog = true; + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } - this.setValues( parameters ); + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } - } + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } - copy( source ) { + return true; +} - super.copy( source ); +// go through all polygon nodes and cure small local self-intersections +function cureLocalIntersections(start, triangles) { + let p = start; + do { + const a = p.prev, + b = p.next.next; - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { - this.map = source.map; + triangles.push(a.i, p.i, b.i); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + // remove two nodes involved + removeNode(p); + removeNode(p.next); - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + p = start = b; + } + p = p.next; + } while (p !== start); - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + return filterPoints(p); +} - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + let b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); +} - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +// link every hole into the outer loop, producing a single-ring polygon without holes +function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; + + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + const list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + queue.sort(compareXYSlope); - this.specularMap = source.specularMap; + // process holes from left to right + for (let i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } - this.alphaMap = source.alphaMap; + return outerNode; +} - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; +function compareXYSlope(a, b) { + let result = a.x - b.x; + // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find + // the bridge to the outer shell is always the point that they meet at. + if (result === 0) { + result = a.y - b.y; + if (result === 0) { + const aSlope = (a.next.y - a.y) / (a.next.x - a.x); + const bSlope = (b.next.y - b.y) / (b.next.x - b.x); + result = aSlope - bSlope; + } + } + return result; +} - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } - this.flatShading = source.flatShading; + const bridgeReverse = splitPolygon(bridge, hole); - this.fog = source.fog; + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); +} - return this; +// David Eberly's algorithm for finding a bridge between hole and outer polygon +function findHoleBridge(hole, outerNode) { + let p = outerNode; + const hx = hole.x; + const hy = hole.y; + let qx = -Infinity; + let m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + // unless they intersect at a vertex, then choose the vertex + if (equals(hole, p)) return p; + do { + if (equals(hole, p.next)) return p.next; + else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); - } + if (!m) return null; -} + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point -class MeshToonMaterial extends Material { + const stop = m; + const mx = m.x; + const my = m.y; + let tanMin = Infinity; - constructor( parameters ) { + p = m; - super(); + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { - this.isMeshToonMaterial = true; + const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential - this.defines = { 'TOON': '' }; + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } - this.type = 'MeshToonMaterial'; + p = p.next; + } while (p !== stop); - this.color = new Color( 0xffffff ); + return m; +} - this.map = null; - this.gradientMap = null; +// whether sector in vertex m contains sector in vertex p in the same coordinates +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; +} - this.lightMap = null; - this.lightMapIntensity = 1.0; +// interlink polygon nodes in z-order +function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); - this.aoMap = null; - this.aoMapIntensity = 1.0; + p.prevZ.nextZ = null; + p.prevZ = null; - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + sortLinked(p); +} - this.bumpMap = null; - this.bumpScale = 1; +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +function sortLinked(list) { + let numMerges; + let inSize = 1; + + do { + let p = list; + let e; + list = null; + let tail = null; + numMerges = 0; + + while (p) { + numMerges++; + let q = p; + let pSize = 0; + for (let i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + let qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + if (tail) tail.nextZ = e; + else list = e; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + e.prevZ = tail; + tail = e; + } - this.alphaMap = null; + p = q; + } - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + tail.nextZ = null; + inSize *= 2; - this.fog = true; + } while (numMerges > 1); - this.setValues( parameters ); + return list; +} - } +// z-order of a point given coords and inverse of the longer side of data bbox +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; - copy( source ) { + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; - super.copy( source ); + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; - this.color.copy( source.color ); + return x | (y << 1); +} - this.map = source.map; - this.gradientMap = source.gradientMap; +// find the leftmost node of a polygon ring +function getLeftmost(start) { + let p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + return leftmost; +} - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; +// check if a point lies within a convex triangle +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; +// check if a point lies within a convex triangle but false if its equal to the first point of the triangle +function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { + return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py); +} - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case +} - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +// signed area of a triangle +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); +} - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; +// check if two points are equal +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} - this.alphaMap = source.alphaMap; +// check if two segments intersect +function intersects(p1, q1, p2, q2) { + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + if (o1 !== o2 && o3 !== o4) return true; // general case - this.fog = source.fog; + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - return this; + return false; +} - } +// for collinear points p, q, r, check if point q lies on segment pr +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); +} +function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; } -class MeshNormalMaterial extends Material { +// check if a polygon diagonal intersects any polygon segments +function intersectsPolygon(a, b) { + let p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); - constructor( parameters ) { + return false; +} - super(); +// check if a polygon diagonal is locally inside the polygon +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} - this.isMeshNormalMaterial = true; +// check if the middle point of a polygon diagonal is inside the polygon +function middleInside(a, b) { + let p = a; + let inside = false; + const px = (a.x + b.x) / 2; + const py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); - this.type = 'MeshNormalMaterial'; + return inside; +} - this.bumpMap = null; - this.bumpScale = 1; +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring +function splitPolygon(a, b) { + const a2 = createNode(a.i, a.x, a.y), + b2 = createNode(b.i, b.x, b.y), + an = a.next, + bp = b.prev; - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + a.next = b; + b.prev = a; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + a2.next = an; + an.prev = a2; - this.wireframe = false; - this.wireframeLinewidth = 1; + b2.next = a2; + a2.prev = b2; - this.flatShading = false; + bp.next = b2; + b2.prev = bp; - this.setValues( parameters ); + return b2; +} - } +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + const p = createNode(i, x, y); - copy( source ) { + if (!last) { + p.prev = p; + p.next = p; - super.copy( source ); + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; +} - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; +function createNode(i, x, y) { + return { + i, // vertex index in coordinates array + x, y, // vertex coordinates + prev: null, // previous and next vertex nodes in a polygon ring + next: null, + z: 0, // z-order curve value + prevZ: null, // previous and next nodes in z-order + nextZ: null, + steiner: false // indicates whether this is a steiner point + }; +} - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; +function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} - this.flatShading = source.flatShading; +class Earcut { - return this; + /** + * Triangulates the given shape definition by returning an array of triangles. + * + * @param {Array} data - An array with 2D points. + * @param {Array} holeIndices - An array with indices defining holes. + * @param {number} [dim=2] - The number of coordinates per vertex in the input array. + * @return {Array} An array representing the triangulated faces. Each face is defined by three consecutive numbers + * representing vertex indices. + */ + static triangulate( data, holeIndices, dim = 2 ) { + + return earcut( data, holeIndices, dim ); } } -class MeshLambertMaterial extends Material { +/** + * A class containing utility functions for shapes. + * + * @hideconstructor + */ +class ShapeUtils { - constructor( parameters ) { + /** + * Calculate area of a ( 2D ) contour polygon. + * + * @param {Array} contour - An array of 2D points. + * @return {number} The area. + */ + static area( contour ) { - super(); + const n = contour.length; + let a = 0.0; - this.isMeshLambertMaterial = true; + for ( let p = n - 1, q = 0; q < n; p = q ++ ) { - this.type = 'MeshLambertMaterial'; + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - this.color = new Color( 0xffffff ); // diffuse + } - this.map = null; + return a * 0.5; - this.lightMap = null; - this.lightMapIntensity = 1.0; + } - this.aoMap = null; - this.aoMapIntensity = 1.0; + /** + * Returns `true` if the given contour uses a clockwise winding order. + * + * @param {Array} pts - An array of 2D points defining a polygon. + * @return {boolean} Whether the given contour uses a clockwise winding order or not. + */ + static isClockWise( pts ) { - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + return ShapeUtils.area( pts ) < 0; - this.bumpMap = null; - this.bumpScale = 1; + } - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + /** + * Triangulates the given shape definition. + * + * @param {Array} contour - An array of 2D points defining the contour. + * @param {Array>} holes - An array that holds arrays of 2D points defining the holes. + * @return {Array>} An array that holds for each face definition an array with three indices. + */ + static triangulateShape( contour, holes ) { - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] + const holeIndices = []; // array of hole indices + const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] - this.specularMap = null; + removeDupEndPts( contour ); + addContour( vertices, contour ); - this.alphaMap = null; + // - this.envMap = null; - this.envMapRotation = new Euler(); - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + let holeIndex = contour.length; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + holes.forEach( removeDupEndPts ); - this.flatShading = false; + for ( let i = 0; i < holes.length; i ++ ) { - this.fog = true; + holeIndices.push( holeIndex ); + holeIndex += holes[ i ].length; + addContour( vertices, holes[ i ] ); - this.setValues( parameters ); + } - } + // - copy( source ) { + const triangles = Earcut.triangulate( vertices, holeIndices ); - super.copy( source ); + // - this.color.copy( source.color ); + for ( let i = 0; i < triangles.length; i += 3 ) { - this.map = source.map; + faces.push( triangles.slice( i, i + 3 ) ); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + } - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + return faces; - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + } - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +} - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +function removeDupEndPts( points ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + const l = points.length; - this.specularMap = source.specularMap; + if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { - this.alphaMap = source.alphaMap; + points.pop(); - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + } - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; +} - this.flatShading = source.flatShading; +function addContour( vertices, contour ) { - this.fog = source.fog; + for ( let i = 0; i < contour.length; i ++ ) { - return this; + vertices.push( contour[ i ].x ); + vertices.push( contour[ i ].y ); } } -class MeshMatcapMaterial extends Material { +/** + * Creates extruded geometry from a path shape. + * + * ```js + * const length = 12, width = 8; + * + * const shape = new THREE.Shape(); + * shape.moveTo( 0,0 ); + * shape.lineTo( 0, width ); + * shape.lineTo( length, width ); + * shape.lineTo( length, 0 ); + * shape.lineTo( 0, 0 ); + * + * const geometry = new THREE.ExtrudeGeometry( shape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class ExtrudeGeometry extends BufferGeometry { - constructor( parameters ) { + /** + * Constructs a new extrude geometry. + * + * @param {Shape|Array} [shapes] - A shape or an array of shapes. + * @param {ExtrudeGeometry~Options} [options] - The extrude settings. + */ + constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( -0.5, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), options = {} ) { super(); - this.isMeshMatcapMaterial = true; + this.type = 'ExtrudeGeometry'; - this.defines = { 'MATCAP': '' }; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + shapes: shapes, + options: options + }; - this.type = 'MeshMatcapMaterial'; + shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; - this.color = new Color( 0xffffff ); // diffuse + const scope = this; - this.matcap = null; + const verticesArray = []; + const uvArray = []; - this.map = null; + for ( let i = 0, l = shapes.length; i < l; i ++ ) { - this.bumpMap = null; - this.bumpScale = 1; + const shape = shapes[ i ]; + addShape( shape ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + // build geometry - this.alphaMap = null; + this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); - this.flatShading = false; + this.computeVertexNormals(); - this.fog = true; + // functions - this.setValues( parameters ); + function addShape( shape ) { - } + const placeholder = []; + // options - copy( source ) { + const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + const steps = options.steps !== undefined ? options.steps : 1; + const depth = options.depth !== undefined ? options.depth : 1; - super.copy( source ); + let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; + let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; + let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; + let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; + let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; - this.defines = { 'MATCAP': '' }; + const extrudePath = options.extrudePath; - this.color.copy( source.color ); + const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; - this.matcap = source.matcap; + // - this.map = source.map; + let extrudePts, extrudeByPath = false; + let splineTube, binormal, normal, position2; - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + if ( extrudePath ) { - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + extrudePts = extrudePath.getSpacedPoints( steps ); - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion - this.alphaMap = source.alphaMap; + // SETUP TNB variables - this.flatShading = source.flatShading; + // TODO1 - have a .isClosed in spline? - this.fog = source.fog; + splineTube = extrudePath.computeFrenetFrames( steps, false ); - return this; + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); - } + binormal = new Vector3(); + normal = new Vector3(); + position2 = new Vector3(); -} + } -class LineDashedMaterial extends LineBasicMaterial { + // Safeguards if bevels are not enabled - constructor( parameters ) { + if ( ! bevelEnabled ) { - super(); + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + bevelOffset = 0; - this.isLineDashedMaterial = true; + } - this.type = 'LineDashedMaterial'; + // Variables initialization - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; + const shapePoints = shape.extractPoints( curveSegments ); - this.setValues( parameters ); + let vertices = shapePoints.shape; + const holes = shapePoints.holes; - } + const reverse = ! ShapeUtils.isClockWise( vertices ); - copy( source ) { + if ( reverse ) { - super.copy( source ); + vertices = vertices.reverse(); - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; + // Maybe we should also check if holes are in the opposite direction, just to be safe ... - return this; + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - } + const ahole = holes[ h ]; -} + if ( ShapeUtils.isClockWise( ahole ) ) { -const Cache = { + holes[ h ] = ahole.reverse(); - enabled: false, + } - files: {}, + } - add: function ( key, file ) { + } - if ( this.enabled === false ) return; + /**Merges index-adjacent points that are within a threshold distance of each other. Array is modified in-place. Threshold distance is empirical, and scaled based on the magnitude of point coordinates. + * @param {Array} points + */ + function mergeOverlappingPoints( points ) { - // console.log( 'THREE.Cache', 'Adding key:', key ); + const THRESHOLD = 1e-10; + const THRESHOLD_SQ = THRESHOLD * THRESHOLD; + let prevPos = points[ 0 ]; + for ( let i = 1; i <= points.length; i ++ ) { - this.files[ key ] = file; + const currentIndex = i % points.length; + const currentPos = points[ currentIndex ]; + const dx = currentPos.x - prevPos.x; + const dy = currentPos.y - prevPos.y; + const distSq = dx * dx + dy * dy; - }, + const scalingFactorSqrt = Math.max( + Math.abs( currentPos.x ), + Math.abs( currentPos.y ), + Math.abs( prevPos.x ), + Math.abs( prevPos.y ) + ); + const thresholdSqScaled = THRESHOLD_SQ * scalingFactorSqrt * scalingFactorSqrt; + if ( distSq <= thresholdSqScaled ) { - get: function ( key ) { + points.splice( currentIndex, 1 ); + i --; + continue; - if ( this.enabled === false ) return; + } - // console.log( 'THREE.Cache', 'Checking key:', key ); + prevPos = currentPos; - return this.files[ key ]; + } - }, + } - remove: function ( key ) { + mergeOverlappingPoints( vertices ); + holes.forEach( mergeOverlappingPoints ); - delete this.files[ key ]; + const numHoles = holes.length; - }, + /* Vertices */ - clear: function () { + const contour = vertices; // vertices has all points but contour has only points of circumference - this.files = {}; + for ( let h = 0; h < numHoles; h ++ ) { - } + const ahole = holes[ h ]; -}; + vertices = vertices.concat( ahole ); -class LoadingManager { + } - constructor( onLoad, onProgress, onError ) { - const scope = this; + function scalePt2( pt, vec, size ) { - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; + if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor + return pt.clone().addScaledVector( vec, size ); - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; + } - this.itemStart = function ( url ) { + const vlen = vertices.length; - itemsTotal ++; - if ( isLoading === false ) { + // Find directions for point movement - if ( scope.onStart !== undefined ) { - scope.onStart( url, itemsLoaded, itemsTotal ); + function getBevelVec( inPt, inPrev, inNext ) { - } + // computes for inPt the corresponding point inPt' on a new contour + // shifted by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. - } + let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt - isLoading = true; + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html - }; + const v_prev_x = inPt.x - inPrev.x, + v_prev_y = inPt.y - inPrev.y; + const v_next_x = inNext.x - inPt.x, + v_next_y = inNext.y - inPt.y; - this.itemEnd = function ( url ) { + const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); - itemsLoaded ++; + // check for collinear edges + const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - if ( scope.onProgress !== undefined ) { + if ( Math.abs( collinear0 ) > Number.EPSILON ) { - scope.onProgress( url, itemsLoaded, itemsTotal ); + // not collinear - } + // length of vectors for normalizing - if ( itemsLoaded === itemsTotal ) { + const v_prev_len = Math.sqrt( v_prev_lensq ); + const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); - isLoading = false; + // shift adjacent points by unit vectors to the left - if ( scope.onLoad !== undefined ) { + const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - scope.onLoad(); + const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - } + // scaling factor for v_prev to intersection point - } + const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - }; + // vector from inPt to intersection point - this.itemError = function ( url ) { + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); - if ( scope.onError !== undefined ) { + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); + if ( v_trans_lensq <= 2 ) { - scope.onError( url ); + return new Vector2( v_trans_x, v_trans_y ); - } + } else { - }; + shrink_by = Math.sqrt( v_trans_lensq / 2 ); - this.resolveURL = function ( url ) { + } - if ( urlModifier ) { + } else { - return urlModifier( url ); + // handle special case of collinear edges - } + let direction_eq = false; // assumes: opposite - return url; + if ( v_prev_x > Number.EPSILON ) { - }; + if ( v_next_x > Number.EPSILON ) { - this.setURLModifier = function ( transform ) { + direction_eq = true; - urlModifier = transform; + } - return this; + } else { - }; + if ( v_prev_x < - Number.EPSILON ) { - this.addHandler = function ( regex, loader ) { + if ( v_next_x < - Number.EPSILON ) { - handlers.push( regex, loader ); + direction_eq = true; - return this; + } - }; + } else { - this.removeHandler = function ( regex ) { + if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - const index = handlers.indexOf( regex ); + direction_eq = true; - if ( index !== - 1 ) { + } - handlers.splice( index, 2 ); + } - } + } - return this; + if ( direction_eq ) { - }; + // console.log("Warning: lines are a straight sequence"); + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); - this.getHandler = function ( file ) { + } else { - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; + } - if ( regex.global ) regex.lastIndex = 0; // see #17920 + } - if ( regex.test( file ) ) { + return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - return loader; + } - } - } + const contourMovements = []; - return null; + for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - }; + if ( j === il ) j = 0; + if ( k === il ) k = 0; - } + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) -} + contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); -const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + } -class Loader { + const holesMovements = []; + let oneHoleMovements, verticesMovements = contourMovements.concat(); - constructor( manager ) { + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + const ahole = holes[ h ]; - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; + oneHoleMovements = []; - } + for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - load( /* url, onLoad, onProgress, onError */ ) {} + if ( j === il ) j = 0; + if ( k === il ) k = 0; - loadAsync( url, onProgress ) { + // (j)---(i)---(k) + oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - const scope = this; + } - return new Promise( function ( resolve, reject ) { + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); - scope.load( url, resolve, onProgress, reject ); + } - } ); + let faces; - } + if ( bevelSegments === 0 ) { - parse( /* data */ ) {} + faces = ShapeUtils.triangulateShape( contour, holes ); - setCrossOrigin( crossOrigin ) { + } else { - this.crossOrigin = crossOrigin; - return this; + const contractedContourVertices = []; + const expandedHoleVertices = []; - } + // Loop bevelSegments, 1 for the front, 1 for the back - setWithCredentials( value ) { + for ( let b = 0; b < bevelSegments; b ++ ) { - this.withCredentials = value; - return this; + //for ( b = bevelSegments; b > 0; b -- ) { - } + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - setPath( path ) { + // contract shape - this.path = path; - return this; + for ( let i = 0, il = contour.length; i < il; i ++ ) { - } + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - setResourcePath( resourcePath ) { + v( vert.x, vert.y, - z ); + if ( t === 0 ) contractedContourVertices.push( vert ); - this.resourcePath = resourcePath; - return this; + } - } + // expand holes - setRequestHeader( requestHeader ) { + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { - this.requestHeader = requestHeader; - return this; + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + const oneHoleVertices = []; + for ( let i = 0, il = ahole.length; i < il; i ++ ) { - } + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); -} + v( vert.x, vert.y, - z ); + if ( t === 0 ) oneHoleVertices.push( vert ); -Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; + } -class ImageLoader extends Loader { + if ( t === 0 ) expandedHoleVertices.push( oneHoleVertices ); - constructor( manager ) { + } - super( manager ); + } - } + faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices ); - load( url, onLoad, onProgress, onError ) { + } - if ( this.path !== undefined ) url = this.path + url; + const flen = faces.length; - url = this.manager.resolveURL( url ); + const bs = bevelSize + bevelOffset; - const scope = this; + // Back facing vertices - const cached = Cache.get( url ); + for ( let i = 0; i < vlen; i ++ ) { - if ( cached !== undefined ) { + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - scope.manager.itemStart( url ); + if ( ! extrudeByPath ) { - setTimeout( function () { + v( vert.x, vert.y, 0 ); - if ( onLoad ) onLoad( cached ); + } else { - scope.manager.itemEnd( url ); + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - }, 0 ); + normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); - return cached; + position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); - } + v( position2.x, position2.y, position2.z ); - const image = createElementNS( 'img' ); + } - function onImageLoad() { + } - removeEventListeners(); + // Add stepped vertices... + // Including front facing vertices - Cache.add( url, this ); + for ( let s = 1; s <= steps; s ++ ) { - if ( onLoad ) onLoad( this ); + for ( let i = 0; i < vlen; i ++ ) { - scope.manager.itemEnd( url ); + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - } + if ( ! extrudeByPath ) { - function onImageError( event ) { + v( vert.x, vert.y, depth / steps * s ); - removeEventListeners(); + } else { - if ( onError ) onError( event ); + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); - } + position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); - function removeEventListeners() { + v( position2.x, position2.y, position2.z ); - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); + } - } + } - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); + } - if ( url.slice( 0, 5 ) !== 'data:' ) { - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + // Add bevel segments planes - } + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( let b = bevelSegments - 1; b >= 0; b -- ) { - scope.manager.itemStart( url ); + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - image.src = url; + // contract shape - return image; + for ( let i = 0, il = contour.length; i < il; i ++ ) { - } + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, depth + z ); -} + } -class TextureLoader extends Loader { + // expand holes - constructor( manager ) { + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - super( manager ); + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; - } + for ( let i = 0, il = ahole.length; i < il; i ++ ) { - load( url, onLoad, onProgress, onError ) { + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - const texture = new Texture(); + if ( ! extrudeByPath ) { - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); + v( vert.x, vert.y, depth + z ); - loader.load( url, function ( image ) { + } else { - texture.image = image; - texture.needsUpdate = true; + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); - if ( onLoad !== undefined ) { + } - onLoad( texture ); + } + + } } - }, onProgress, onError ); + /* Faces */ - return texture; + // Top and bottom faces - } + buildLidFaces(); -} + // Sides faces -class Light extends Object3D { + buildSideFaces(); - constructor( color, intensity = 1 ) { - super(); + ///// Internal functions - this.isLight = true; + function buildLidFaces() { - this.type = 'Light'; + const start = verticesArray.length / 3; - this.color = new Color( color ); - this.intensity = intensity; + if ( bevelEnabled ) { - } + let layer = 0; // steps + 1 + let offset = vlen * layer; - dispose() { + // Bottom faces - // Empty here in base class; some subclasses override. + for ( let i = 0; i < flen; i ++ ) { - } + const face = faces[ i ]; + f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - copy( source, recursive ) { + } - super.copy( source, recursive ); + layer = steps + bevelSegments * 2; + offset = vlen * layer; - this.color.copy( source.color ); - this.intensity = source.intensity; + // Top faces - return this; + for ( let i = 0; i < flen; i ++ ) { - } + const face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - toJSON( meta ) { + } - const data = super.toJSON( meta ); + } else { - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; + // Bottom faces - if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); + for ( let i = 0; i < flen; i ++ ) { - if ( this.distance !== undefined ) data.object.distance = this.distance; - if ( this.angle !== undefined ) data.object.angle = this.angle; - if ( this.decay !== undefined ) data.object.decay = this.decay; - if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; + const face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + } - return data; + // Top faces - } + for ( let i = 0; i < flen; i ++ ) { -} + const face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); -class HemisphereLight extends Light { + } - constructor( skyColor, groundColor, intensity ) { + } - super( skyColor, intensity ); + scope.addGroup( start, verticesArray.length / 3 - start, 0 ); - this.isHemisphereLight = true; + } - this.type = 'HemisphereLight'; + // Create faces for the z-sides of the shape - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + function buildSideFaces() { - this.groundColor = new Color( groundColor ); + const start = verticesArray.length / 3; + let layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; - } + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - copy( source, recursive ) { + const ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); - super.copy( source, recursive ); + //, true + layeroffset += ahole.length; - this.groundColor.copy( source.groundColor ); + } - return this; - } + scope.addGroup( start, verticesArray.length / 3 - start, 1 ); -} -const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); -const _lookTarget$1 = /*@__PURE__*/ new Vector3(); + } -class LightShadow { + function sidewalls( contour, layeroffset ) { - constructor( camera ) { + let i = contour.length; - this.camera = camera; + while ( -- i >= 0 ) { - this.bias = 0; - this.normalBias = 0; - this.radius = 1; - this.blurSamples = 8; + const j = i; + let k = i - 1; + if ( k < 0 ) k = contour.length - 1; - this.mapSize = new Vector2( 512, 512 ); + //console.log('b', i,j, i-1, k,vertices.length); - this.map = null; - this.mapPass = null; - this.matrix = new Matrix4(); + for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { - this.autoUpdate = true; - this.needsUpdate = false; + const slen1 = vlen * s; + const slen2 = vlen * ( s + 1 ); - this._frustum = new Frustum(); - this._frameExtents = new Vector2( 1, 1 ); + const a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; - this._viewportCount = 1; + f4( a, b, c, d ); - this._viewports = [ + } - new Vector4( 0, 0, 1, 1 ) + } - ]; + } - } + function v( x, y, z ) { - getViewportCount() { + placeholder.push( x ); + placeholder.push( y ); + placeholder.push( z ); - return this._viewportCount; + } - } - getFrustum() { + function f3( a, b, c ) { - return this._frustum; + addVertex( a ); + addVertex( b ); + addVertex( c ); - } + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - updateMatrices( light ) { + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); - const shadowCamera = this.camera; - const shadowMatrix = this.matrix; + } - _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld$1 ); + function f4( a, b, c, d ) { - _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget$1 ); - shadowCamera.updateMatrixWorld(); + addVertex( a ); + addVertex( b ); + addVertex( d ); - _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); + addVertex( b ); + addVertex( c ); + addVertex( d ); - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - shadowMatrix.multiply( _projScreenMatrix$1 ); + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - } + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 3 ] ); - getViewport( viewportIndex ) { + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + addUV( uvs[ 3 ] ); - return this._viewports[ viewportIndex ]; + } - } + function addVertex( index ) { - getFrameExtents() { + verticesArray.push( placeholder[ index * 3 + 0 ] ); + verticesArray.push( placeholder[ index * 3 + 1 ] ); + verticesArray.push( placeholder[ index * 3 + 2 ] ); - return this._frameExtents; + } - } - dispose() { + function addUV( vector2 ) { - if ( this.map ) { + uvArray.push( vector2.x ); + uvArray.push( vector2.y ); - this.map.dispose(); + } } - if ( this.mapPass ) { - - this.mapPass.dispose(); - - } - - } + } copy( source ) { - this.camera = source.camera.clone(); - - this.bias = source.bias; - this.radius = source.radius; + super.copy( source ); - this.mapSize.copy( source.mapSize ); + this.parameters = Object.assign( {}, source.parameters ); return this; } - clone() { + toJSON() { - return new this.constructor().copy( this ); + const data = super.toJSON(); + + const shapes = this.parameters.shapes; + const options = this.parameters.options; + + return toJSON$1( shapes, options, data ); } - toJSON() { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array} shapes - An array of shapes. + * @return {ExtrudeGeometry} A new instance. + */ + static fromJSON( data, shapes ) { - const object = {}; + const geometryShapes = []; - if ( this.bias !== 0 ) object.bias = this.bias; - if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; - if ( this.radius !== 1 ) object.radius = this.radius; - if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); + for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; + const shape = shapes[ data.shapes[ j ] ]; - return object; + geometryShapes.push( shape ); - } + } -} + const extrudePath = data.options.extrudePath; -class DirectionalLightShadow extends LightShadow { + if ( extrudePath !== undefined ) { - constructor() { + data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); - super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); + } - this.isDirectionalLightShadow = true; + return new ExtrudeGeometry( geometryShapes, data.options ); } } -class DirectionalLight extends Light { +const WorldUVGenerator = { - constructor( color, intensity ) { + generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { - super( color, intensity ); + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; - this.isDirectionalLight = true; + return [ + new Vector2( a_x, a_y ), + new Vector2( b_x, b_y ), + new Vector2( c_x, c_y ) + ]; - this.type = 'DirectionalLight'; + }, - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - this.target = new Object3D(); + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const a_z = vertices[ indexA * 3 + 2 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const b_z = vertices[ indexB * 3 + 2 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + const c_z = vertices[ indexC * 3 + 2 ]; + const d_x = vertices[ indexD * 3 ]; + const d_y = vertices[ indexD * 3 + 1 ]; + const d_z = vertices[ indexD * 3 + 2 ]; - this.shadow = new DirectionalLightShadow(); + if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { - } + return [ + new Vector2( a_x, 1 - a_z ), + new Vector2( b_x, 1 - b_z ), + new Vector2( c_x, 1 - c_z ), + new Vector2( d_x, 1 - d_z ) + ]; - dispose() { + } else { - this.shadow.dispose(); + return [ + new Vector2( a_y, 1 - a_z ), + new Vector2( b_y, 1 - b_z ), + new Vector2( c_y, 1 - c_z ), + new Vector2( d_y, 1 - d_z ) + ]; - } + } - copy( source ) { + } - super.copy( source ); +}; - this.target = source.target.clone(); - this.shadow = source.shadow.clone(); +function toJSON$1( shapes, options, data ) { - return this; + data.shapes = []; - } + if ( Array.isArray( shapes ) ) { -} + for ( let i = 0, l = shapes.length; i < l; i ++ ) { -class AmbientLight extends Light { + const shape = shapes[ i ]; - constructor( color, intensity ) { + data.shapes.push( shape.uuid ); - super( color, intensity ); + } - this.isAmbientLight = true; + } else { - this.type = 'AmbientLight'; + data.shapes.push( shapes.uuid ); } -} + data.options = Object.assign( {}, options ); -const _matrix = /*@__PURE__*/ new Matrix4(); + if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); -class Raycaster { + return data; - constructor( origin, direction, near = 0, far = Infinity ) { +} - this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) +/** + * A class for generating a sphere geometry. + * + * ```js + * const geometry = new THREE.SphereGeometry( 15, 32, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const sphere = new THREE.Mesh( geometry, material ); + * scene.add( sphere ); + * ``` + * + * @augments BufferGeometry + */ +class SphereGeometry extends BufferGeometry { - this.near = near; - this.far = far; - this.camera = null; - this.layers = new Layers(); + /** + * Constructs a new sphere geometry. + * + * @param {number} [radius=1] - The sphere radius. + * @param {number} [widthSegments=32] - The number of horizontal segments. Minimum value is `3`. + * @param {number} [heightSegments=16] - The number of vertical segments. Minimum value is `2`. + * @param {number} [phiStart=0] - The horizontal starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The horizontal sweep angle size. + * @param {number} [thetaStart=0] - The vertical starting angle in radians. + * @param {number} [thetaLength=Math.PI] - The vertical sweep angle size. + */ + constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { - this.params = { - Mesh: {}, - Line: { threshold: 1 }, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} + super(); + + this.type = 'SphereGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength }; - } + widthSegments = Math.max( 3, Math.floor( widthSegments ) ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) ); - set( origin, direction ) { + const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); - // direction is assumed to be normalized (for accurate distance calculations) + let index = 0; + const grid = []; - this.ray.set( origin, direction ); + const vertex = new Vector3(); + const normal = new Vector3(); - } + // buffers - setFromCamera( coords, camera ) { + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - if ( camera.isPerspectiveCamera ) { + // generate vertices, normals and uvs - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - this.camera = camera; + for ( let iy = 0; iy <= heightSegments; iy ++ ) { - } else if ( camera.isOrthographicCamera ) { + const verticesRow = []; - this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - this.camera = camera; + const v = iy / heightSegments; - } else { + // special case for the poles - console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); + let uOffset = 0; - } + if ( iy === 0 && thetaStart === 0 ) { - } + uOffset = 0.5 / widthSegments; - setFromXRController( controller ) { + } else if ( iy === heightSegments && thetaEnd === Math.PI ) { - _matrix.identity().extractRotation( controller.matrixWorld ); + uOffset = -0.5 / widthSegments; - this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); - this.ray.direction.set( 0, 0, - 1 ).applyMatrix4( _matrix ); + } - return this; + for ( let ix = 0; ix <= widthSegments; ix ++ ) { - } + const u = ix / widthSegments; - intersectObject( object, recursive = true, intersects = [] ) { + // vertex - intersect( object, this, intersects, recursive ); + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - intersects.sort( ascSort ); + vertices.push( vertex.x, vertex.y, vertex.z ); - return intersects; + // normal - } + normal.copy( vertex ).normalize(); + normals.push( normal.x, normal.y, normal.z ); - intersectObjects( objects, recursive = true, intersects = [] ) { + // uv - for ( let i = 0, l = objects.length; i < l; i ++ ) { + uvs.push( u + uOffset, 1 - v ); - intersect( objects[ i ], this, intersects, recursive ); + verticesRow.push( index ++ ); - } + } - intersects.sort( ascSort ); + grid.push( verticesRow ); - return intersects; + } - } + // indices -} + for ( let iy = 0; iy < heightSegments; iy ++ ) { -function ascSort( a, b ) { + for ( let ix = 0; ix < widthSegments; ix ++ ) { - return a.distance - b.distance; + const a = grid[ iy ][ ix + 1 ]; + const b = grid[ iy ][ ix ]; + const c = grid[ iy + 1 ][ ix ]; + const d = grid[ iy + 1 ][ ix + 1 ]; -} + if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); + if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); -function intersect( object, raycaster, intersects, recursive ) { + } - if ( object.layers.test( raycaster.layers ) ) { + } - object.raycast( raycaster, intersects ); + // build geometry - } + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - if ( recursive === true ) { + } - const children = object.children; + copy( source ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + super.copy( source ); - intersect( children[ i ], raycaster, intersects, true ); + this.parameters = Object.assign( {}, source.parameters ); - } + return this; } -} - -class Clock { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {SphereGeometry} A new instance. + */ + static fromJSON( data ) { - constructor( autoStart = true ) { + return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); - this.autoStart = autoStart; + } - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; +} - this.running = false; +/** + * A standard physically based material, using Metallic-Roughness workflow. + * + * Physically based rendering (PBR) has recently become the standard in many + * 3D applications, such as [Unity]{@link https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/}, + * [Unreal]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/} and + * [3D Studio Max]{@link http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017}. + * + * This approach differs from older approaches in that instead of using + * approximations for the way in which light interacts with a surface, a + * physically correct model is used. The idea is that, instead of tweaking + * materials to look good under specific lighting, a material can be created + * that will react 'correctly' under all lighting scenarios. + * + * In practice this gives a more accurate and realistic looking result than + * the {@link MeshLambertMaterial} or {@link MeshPhongMaterial}, at the cost of + * being somewhat more computationally expensive. `MeshStandardMaterial` uses per-fragment + * shading. + * + * Note that for best results you should always specify an environment map when using this material. + * + * For a non-technical introduction to the concept of PBR and how to set up a + * PBR material, check out these articles by the people at [marmoset]{@link https://www.marmoset.co}: + * + * - [Basic Theory of Physically Based Rendering]{@link https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/} + * - [Physically Based Rendering and You Can Too]{@link https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/} + * + * Technical details of the approach used in three.js (and most other PBR systems) can be found is this + * [paper from Disney]{@link https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf} + * (pdf), by Brent Burley. + * + * @augments Material + */ +class MeshStandardMaterial extends Material { - } + /** + * Constructs a new mesh standard material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - start() { + super(); - this.startTime = now(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshStandardMaterial = true; - this.oldTime = this.startTime; - this.elapsedTime = 0; - this.running = true; + this.type = 'MeshStandardMaterial'; - } + this.defines = { 'STANDARD': '' }; - stop() { + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - this.getElapsedTime(); - this.running = false; - this.autoStart = false; + /** + * How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` + * means fully diffuse. If `roughnessMap` is also provided, + * both values are multiplied. + * + * @type {number} + * @default 1 + */ + this.roughness = 1.0; - } + /** + * How much the material is like a metal. Non-metallic materials such as wood + * or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. + * A value between `0.0` and `1.0` could be used for a rusty metal look. + * If `metalnessMap` is also provided, both values are multiplied. + * + * @type {number} + * @default 0 + */ + this.metalness = 0.0; - getElapsedTime() { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - this.getDelta(); - return this.elapsedTime; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - getDelta() { + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - let diff = 0; + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - if ( this.autoStart && ! this.running ) { + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - this.start(); - return 0; + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - } + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - if ( this.running ) { + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - const newTime = now(); + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - diff = ( newTime - this.oldTime ) / 1000; - this.oldTime = newTime; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - this.elapsedTime += diff; + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - } + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - return diff; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; -} + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; -function now() { + /** + * The green channel of this texture is used to alter the roughness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.roughnessMap = null; - return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 + /** + * The blue channel of this texture is used to alter the metalness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.metalnessMap = null; -} + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; -/** - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. - * The azimuthal angle (theta) is measured from the positive z-axis. - */ + /** + * The environment map. To ensure a physically correct rendering, environment maps + * are internally pre-processed with {@link PMREMGenerator}. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); -class Spherical { + /** + * Scales the effect of the environment map by multiplying its color. + * + * @type {number} + * @default 1 + */ + this.envMapIntensity = 1.0; - constructor( radius = 1, phi = 0, theta = 0 ) { + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - this.radius = radius; - this.phi = phi; // polar angle - this.theta = theta; // azimuthal angle + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - return this; + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - } + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - set( radius, phi, theta ) { + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - this.radius = radius; - this.phi = phi; - this.theta = theta; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - return this; + this.setValues( parameters ); } - copy( other ) { + copy( source ) { - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; + super.copy( source ); - return this; + this.defines = { 'STANDARD': '' }; - } + this.color.copy( source.color ); + this.roughness = source.roughness; + this.metalness = source.metalness; - // restrict phi to be between EPS and PI-EPS - makeSafe() { + this.map = source.map; - const EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - return this; + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - } + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - setFromVector3( v ) { + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - return this.setFromCartesianCoords( v.x, v.y, v.z ); + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - } + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - setFromCartesianCoords( x, y, z ) { + this.roughnessMap = source.roughnessMap; - this.radius = Math.sqrt( x * x + y * y + z * z ); + this.metalnessMap = source.metalnessMap; - if ( this.radius === 0 ) { + this.alphaMap = source.alphaMap; - this.theta = 0; - this.phi = 0; + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.envMapIntensity = source.envMapIntensity; - } else { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); + this.flatShading = source.flatShading; - } + this.fog = source.fog; return this; } - clone() { - - return new this.constructor().copy( this ); +} - } +/** + * An extension of the {@link MeshStandardMaterial}, providing more advanced + * physically-based rendering properties: + * + * - Anisotropy: Ability to represent the anisotropic property of materials + * as observable with brushed metals. + * - Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require + * a clear, reflective layer on top of another layer that may be irregular or rough. + * Clearcoat approximates this effect, without the need for a separate transparent surface. + * - Iridescence: Allows to render the effect where hue varies depending on the viewing + * angle and illumination angle. This can be seen on soap bubbles, oil films, or on the + * wings of many insects. + * - Physically-based transparency: One limitation of {@link Material#opacity} is that highly + * transparent materials are less reflective. Physically-based transmission provides a more + * realistic option for thin, transparent surfaces like glass. + * - Advanced reflectivity: More flexible reflectivity for non-metallic materials. + * - Sheen: Can be used for representing cloth and fabric materials. + * + * As a result of these complex shading features, `MeshPhysicalMaterial` has a + * higher performance cost, per pixel, than other three.js materials. Most + * effects are disabled by default, and add cost as they are enabled. For + * best results, always specify an environment map when using this material. + * + * @augments MeshStandardMaterial + */ +class MeshPhysicalMaterial extends MeshStandardMaterial { -} + /** + * Constructs a new mesh physical material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { -const _vector = /*@__PURE__*/ new Vector2(); + super(); -class Box2 { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhysicalMaterial = true; - constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { + this.defines = { - this.isBox2 = true; + 'STANDARD': '', + 'PHYSICAL': '' - this.min = min; - this.max = max; + }; - } + this.type = 'MeshPhysicalMaterial'; - set( min, max ) { + /** + * The rotation of the anisotropy in tangent, bitangent space, measured in radians + * counter-clockwise from the tangent. When `anisotropyMap` is present, this + * property provides additional rotation to the vectors in the texture. + * + * @type {number} + * @default 1 + */ + this.anisotropyRotation = 0; - this.min.copy( min ); - this.max.copy( max ); + /** + * Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, + * bitangent space, to be rotated by `anisotropyRotation`. The blue channel + * contains strength as `[0, 1]` to be multiplied by `anisotropy`. + * + * @type {?Texture} + * @default null + */ + this.anisotropyMap = null; - return this; + /** + * The red channel of this texture is multiplied against `clearcoat`, + * for per-pixel control over a coating's intensity. + * + * @type {?Texture} + * @default null + */ + this.clearcoatMap = null; - } + /** + * Roughness of the clear coat layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + this.clearcoatRoughness = 0.0; - setFromPoints( points ) { + /** + * The green channel of this texture is multiplied against + * `clearcoatRoughness`, for per-pixel control over a coating's roughness. + * + * @type {?Texture} + * @default null + */ + this.clearcoatRoughnessMap = null; - this.makeEmpty(); + /** + * How much `clearcoatNormalMap` affects the clear coat layer, from + * `(0,0)` to `(1,1)`. + * + * @type {Vector2} + * @default (1,1) + */ + this.clearcoatNormalScale = new Vector2( 1, 1 ); - for ( let i = 0, il = points.length; i < il; i ++ ) { + /** + * Can be used to enable independent normals for the clear coat layer. + * + * @type {?Texture} + * @default null + */ + this.clearcoatNormalMap = null; - this.expandByPoint( points[ i ] ); + /** + * Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. + * + * @type {number} + * @default 1.5 + */ + this.ior = 1.5; - } + /** + * Degree of reflectivity, from `0.0` to `1.0`. Default is `0.5`, which + * corresponds to an index-of-refraction of `1.5`. + * + * This models the reflectivity of non-metallic materials. It has no effect + * when `metalness` is `1.0` + * + * @name MeshPhysicalMaterial#reflectivity + * @type {number} + * @default 0.5 + */ + Object.defineProperty( this, 'reflectivity', { + get: function () { - return this; + return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); - } + }, + set: function ( reflectivity ) { - setFromCenterAndSize( center, size ) { + this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); - const halfSize = _vector.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); + } + } ); - return this; + /** + * The red channel of this texture is multiplied against `iridescence`, for per-pixel + * control over iridescence. + * + * @type {?Texture} + * @default null + */ + this.iridescenceMap = null; - } + /** + * Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. + * Between `1.0` to `2.333`. + * + * @type {number} + * @default 1.3 + */ + this.iridescenceIOR = 1.3; - clone() { + /** + *Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. + Thickness of iridescence layer has an equivalent effect of the one `thickness` has on `ior`. + * + * @type {Array} + * @default [100,400] + */ + this.iridescenceThicknessRange = [ 100, 400 ]; - return new this.constructor().copy( this ); + /** + * A texture that defines the thickness of the iridescence layer, stored in the green channel. + * Minimum and maximum values of thickness are defined by `iridescenceThicknessRange` array: + * - `0.0` in the green channel will result in thickness equal to first element of the array. + * - `1.0` in the green channel will result in thickness equal to second element of the array. + * - Values in-between will linearly interpolate between the elements of the array. + * + * @type {?Texture} + * @default null + */ + this.iridescenceThicknessMap = null; - } + /** + * The sheen tint. + * + * @type {Color} + * @default (0,0,0) + */ + this.sheenColor = new Color( 0x000000 ); - copy( box ) { + /** + * The RGB channels of this texture are multiplied against `sheenColor`, for per-pixel control + * over sheen tint. + * + * @type {?Texture} + * @default null + */ + this.sheenColorMap = null; - this.min.copy( box.min ); - this.max.copy( box.max ); + /** + * Roughness of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.sheenRoughness = 1.0; - return this; + /** + * The alpha channel of this texture is multiplied against `sheenRoughness`, for per-pixel control + * over sheen roughness. + * + * @type {?Texture} + * @default null + */ + this.sheenRoughnessMap = null; - } + /** + * The red channel of this texture is multiplied against `transmission`, for per-pixel control over + * optical transparency. + * + * @type {?Texture} + * @default null + */ + this.transmissionMap = null; - makeEmpty() { + /** + * The thickness of the volume beneath the surface. The value is given in the + * coordinate space of the mesh. If the value is `0` the material is + * thin-walled. Otherwise the material is a volume boundary. + * + * @type {number} + * @default 0 + */ + this.thickness = 0; - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; + /** + * A texture that defines the thickness, stored in the green channel. This will + * be multiplied by `thickness`. + * + * @type {?Texture} + * @default null + */ + this.thicknessMap = null; - return this; + /** + * Density of the medium given as the average distance that light travels in + * the medium before interacting with a particle. The value is given in world + * space units, and must be greater than zero. + * + * @type {number} + * @default Infinity + */ + this.attenuationDistance = Infinity; - } + /** + * The color that white light turns into due to absorption when reaching the + * attenuation distance. + * + * @type {Color} + * @default (1,1,1) + */ + this.attenuationColor = new Color( 1, 1, 1 ); - isEmpty() { + /** + * A float that scales the amount of specular reflection for non-metals only. + * When set to zero, the model is effectively Lambertian. From `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.specularIntensity = 1.0; - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + /** + * The alpha channel of this texture is multiplied against `specularIntensity`, + * for per-pixel control over specular intensity. + * + * @type {?Texture} + * @default null + */ + this.specularIntensityMap = null; - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + /** + * Tints the specular reflection at normal incidence for non-metals only. + * + * @type {Color} + * @default (1,1,1) + */ + this.specularColor = new Color( 1, 1, 1 ); - } + /** + * The RGB channels of this texture are multiplied against `specularColor`, + * for per-pixel control over specular color. + * + * @type {?Texture} + * @default null + */ + this.specularColorMap = null; - getCenter( target ) { + this._anisotropy = 0; + this._clearcoat = 0; + this._dispersion = 0; + this._iridescence = 0; + this._sheen = 0.0; + this._transmission = 0; - return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + this.setValues( parameters ); } - getSize( target ) { + /** + * The anisotropy strength. + * + * @type {number} + * @default 0 + */ + get anisotropy() { - return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); + return this._anisotropy; } - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; + set anisotropy( value ) { - } + if ( this._anisotropy > 0 !== value > 0 ) { - expandByVector( vector ) { + this.version ++; - this.min.sub( vector ); - this.max.add( vector ); + } - return this; + this._anisotropy = value; } - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); + /** + * Represents the intensity of the clear coat layer, from `0.0` to `1.0`. Use + * clear coat related properties to enable multilayer materials that have a + * thin translucent layer over the base layer. + * + * @type {number} + * @default 0 + */ + get clearcoat() { - return this; + return this._clearcoat; } - containsPoint( point ) { + set clearcoat( value ) { - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; + if ( this._clearcoat > 0 !== value > 0 ) { - } + this.version ++; - containsBox( box ) { + } - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; + this._clearcoat = value; } + /** + * The intensity of the iridescence layer, simulating RGB color shift based on the angle between + * the surface and the viewer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get iridescence() { - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); + return this._iridescence; } - intersectsBox( box ) { - - // using 4 splitting planes to rule out intersections + set iridescence( value ) { - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; + if ( this._iridescence > 0 !== value > 0 ) { - } + this.version ++; - clampPoint( point, target ) { + } - return target.copy( point ).clamp( this.min, this.max ); + this._iridescence = value; } - distanceToPoint( point ) { + /** + * Defines the strength of the angular separation of colors (chromatic aberration) transmitting + * through a relatively clear volume. Any value zero or larger is valid, the typical range of + * realistic values is `[0, 1]`. This property can be only be used with transmissive objects. + * + * @type {number} + * @default 0 + */ + get dispersion() { - return this.clampPoint( point, _vector ).distanceTo( point ); + return this._dispersion; } - intersect( box ) { + set dispersion( value ) { - this.min.max( box.min ); - this.max.min( box.max ); + if ( this._dispersion > 0 !== value > 0 ) { - if ( this.isEmpty() ) this.makeEmpty(); + this.version ++; - return this; + } - } + this._dispersion = value; - union( box ) { + } - this.min.min( box.min ); - this.max.max( box.max ); + /** + * The intensity of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get sheen() { - return this; + return this._sheen; } - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); + set sheen( value ) { - return this; + if ( this._sheen > 0 !== value > 0 ) { - } + this.version ++; - equals( box ) { + } - return box.min.equals( this.min ) && box.max.equals( this.max ); + this._sheen = value; } -} - -const _startP = /*@__PURE__*/ new Vector3(); -const _startEnd = /*@__PURE__*/ new Vector3(); + /** + * Degree of transmission (or optical transparency), from `0.0` to `1.0`. + * + * Thin, transparent or semitransparent, plastic or glass materials remain + * largely reflective even if they are fully transmissive. The transmission + * property can be used to model these materials. + * + * When transmission is non-zero, `opacity` should be set to `1`. + * + * @type {number} + * @default 0 + */ + get transmission() { -class Line3 { + return this._transmission; - constructor( start = new Vector3(), end = new Vector3() ) { + } - this.start = start; - this.end = end; + set transmission( value ) { - } + if ( this._transmission > 0 !== value > 0 ) { - set( start, end ) { + this.version ++; - this.start.copy( start ); - this.end.copy( end ); + } - return this; + this._transmission = value; } - copy( line ) { - - this.start.copy( line.start ); - this.end.copy( line.end ); + copy( source ) { - return this; + super.copy( source ); - } + this.defines = { - getCenter( target ) { + 'STANDARD': '', + 'PHYSICAL': '' - return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + }; - } + this.anisotropy = source.anisotropy; + this.anisotropyRotation = source.anisotropyRotation; + this.anisotropyMap = source.anisotropyMap; - delta( target ) { + this.clearcoat = source.clearcoat; + this.clearcoatMap = source.clearcoatMap; + this.clearcoatRoughness = source.clearcoatRoughness; + this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; + this.clearcoatNormalMap = source.clearcoatNormalMap; + this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); - return target.subVectors( this.end, this.start ); + this.dispersion = source.dispersion; + this.ior = source.ior; - } + this.iridescence = source.iridescence; + this.iridescenceMap = source.iridescenceMap; + this.iridescenceIOR = source.iridescenceIOR; + this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; + this.iridescenceThicknessMap = source.iridescenceThicknessMap; - distanceSq() { + this.sheen = source.sheen; + this.sheenColor.copy( source.sheenColor ); + this.sheenColorMap = source.sheenColorMap; + this.sheenRoughness = source.sheenRoughness; + this.sheenRoughnessMap = source.sheenRoughnessMap; - return this.start.distanceToSquared( this.end ); + this.transmission = source.transmission; + this.transmissionMap = source.transmissionMap; - } + this.thickness = source.thickness; + this.thicknessMap = source.thicknessMap; + this.attenuationDistance = source.attenuationDistance; + this.attenuationColor.copy( source.attenuationColor ); - distance() { + this.specularIntensity = source.specularIntensity; + this.specularIntensityMap = source.specularIntensityMap; + this.specularColor.copy( source.specularColor ); + this.specularColorMap = source.specularColorMap; - return this.start.distanceTo( this.end ); + return this; } - at( t, target ) { +} - return this.delta( target ).multiplyScalar( t ).add( this.start ); +/** + * A material for shiny surfaces with specular highlights. + * + * The material uses a non-physically based [Blinn-Phong]{@link https://en.wikipedia.org/wiki/Blinn-Phong_shading_model} + * model for calculating reflectance. Unlike the Lambertian model used in the + * {@link MeshLambertMaterial} this can simulate shiny surfaces with specular + * highlights (such as varnished wood). `MeshPhongMaterial` uses per-fragment shading. + * + * Performance will generally be greater when using this material over the + * {@link MeshStandardMaterial} or {@link MeshPhysicalMaterial}, at the cost of + * some graphical accuracy. + * + * @augments Material + */ +class MeshPhongMaterial extends Material { - } + /** + * Constructs a new mesh phong material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - closestPointToPointParameter( point, clampToLine ) { + super(); - _startP.subVectors( point, this.start ); - _startEnd.subVectors( this.end, this.start ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhongMaterial = true; - const startEnd2 = _startEnd.dot( _startEnd ); - const startEnd_startP = _startEnd.dot( _startP ); + this.type = 'MeshPhongMaterial'; - let t = startEnd_startP / startEnd2; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - if ( clampToLine ) { + /** + * Specular color of the material. The default color is set to `0x111111` (very dark grey) + * + * This defines how shiny the material is and the color of its shine. + * + * @type {Color} + */ + this.specular = new Color( 0x111111 ); - t = clamp( t, 0, 1 ); + /** + * How shiny the specular highlight is; a higher value gives a sharper highlight. + * + * @type {number} + * @default 30 + */ + this.shininess = 30; - } + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - return t; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - closestPointToPoint( point, clampToLine, target ) { + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - const t = this.closestPointToPointParameter( point, clampToLine ); + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - return this.delta( target ).multiplyScalar( t ).add( this.start ); + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - } + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - applyMatrix4( matrix ) { + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - return this; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - } + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - equals( line ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - return line.start.equals( this.start ) && line.end.equals( this.end ); + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - } + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - clone() { + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - return new this.constructor().copy( this ); + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } + /** + * The specular map value affects both how much the specular surface + * highlight contributes and how much of the environment map affects the + * surface. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; -} + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; -class PlaneHelper extends Line { + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - constructor( plane, size = 1, hex = 0xffff00 ) { + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - const color = hex; + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - this.type = 'PlaneHelper'; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - this.plane = plane; + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - this.size = size; + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - const geometry2 = new BufferGeometry(); - geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); + this.setValues( parameters ); } - updateMatrixWorld( force ) { + copy( source ) { - this.position.set( 0, 0, 0 ); + super.copy( source ); - this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); + this.color.copy( source.color ); + this.specular.copy( source.specular ); + this.shininess = source.shininess; - this.lookAt( this.plane.normal ); + this.map = source.map; - this.translateZ( - this.plane.constant ); - - super.updateMatrixWorld( force ); - - } - - dispose() { + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - this.geometry.dispose(); - this.material.dispose(); - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - } + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; -} + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; -class ShapePath { + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - constructor() { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - this.type = 'ShapePath'; + this.specularMap = source.specularMap; - this.color = new Color(); + this.alphaMap = source.alphaMap; - this.subPaths = []; - this.currentPath = null; + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - } + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - moveTo( x, y ) { + this.flatShading = source.flatShading; - this.currentPath = new Path(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); + this.fog = source.fog; return this; } - lineTo( x, y ) { +} - this.currentPath.lineTo( x, y ); +/** + * A material implementing toon shading. + * + * @augments Material + */ +class MeshToonMaterial extends Material { - return this; + /** + * Constructs a new mesh toon material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshToonMaterial = true; - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + this.defines = { 'TOON': '' }; - return this; + this.type = 'MeshToonMaterial'; - } + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + /** + * Gradient map for toon shading. It's required to set + * {@link Texture#minFilter} and {@link Texture#magFilter} to {@linkNearestFilter} + * when using this type of texture. + * + * @type {?Texture} + * @default null + */ + this.gradientMap = null; - return this; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - splineThru( pts ) { + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - this.currentPath.splineThru( pts ); + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - return this; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - } + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - toShapes( isCCW ) { + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - function toShapesNoHoles( inSubpaths ) { + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - const shapes = []; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - const tmpPath = inSubpaths[ i ]; + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - const tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - shapes.push( tmpShape ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - return shapes; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - function isPointInsidePolygon( inPt, inPolygon ) { + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - const polyLen = inPolygon.length; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - // inPt on polygon contour => immediate success or - // toggling of inside/outside at every single! intersection point of an edge - // with the horizontal line through inPt, left of inPt - // not counting lowerY endpoints of edges and whole edges on that line - let inside = false; - for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - let edgeLowPt = inPolygon[ p ]; - let edgeHighPt = inPolygon[ q ]; + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - let edgeDx = edgeHighPt.x - edgeLowPt.x; - let edgeDy = edgeHighPt.y - edgeLowPt.y; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - if ( Math.abs( edgeDy ) > Number.EPSILON ) { + this.setValues( parameters ); - // not parallel - if ( edgeDy < 0 ) { + } - edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; - edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + copy( source ) { - } + super.copy( source ); - if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + this.color.copy( source.color ); - if ( inPt.y === edgeLowPt.y ) { + this.map = source.map; + this.gradientMap = source.gradientMap; - if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? - // continue; // no intersection or edgeLowPt => doesn't count !!! + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - } else { + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); - if ( perpEdge === 0 ) return true; // inPt is on contour ? - if ( perpEdge < 0 ) continue; - inside = ! inside; // true intersection left of inPt + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - } + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - } else { + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - // parallel or collinear - if ( inPt.y !== edgeLowPt.y ) continue; // parallel - // edge lies on the same horizontal line as inPt - if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || - ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! - // continue; + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - } + this.alphaMap = source.alphaMap; - } + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - return inside; + this.fog = source.fog; - } + return this; - const isClockWise = ShapeUtils.isClockWise; + } - const subPaths = this.subPaths; - if ( subPaths.length === 0 ) return []; +} - let solid, tmpPath, tmpShape; - const shapes = []; +/** + * A material that maps the normal vectors to RGB colors. + * + * @augments Material + */ +class MeshNormalMaterial extends Material { - if ( subPaths.length === 1 ) { + /** + * Constructs a new mesh normal material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - tmpPath = subPaths[ 0 ]; - tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - shapes.push( tmpShape ); - return shapes; + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshNormalMaterial = true; - let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); - holesFirst = isCCW ? ! holesFirst : holesFirst; + this.type = 'MeshNormalMaterial'; - // console.log("Holes first", holesFirst); + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - const betterShapeHoles = []; - const newShapes = []; - let newShapeHoles = []; - let mainIdx = 0; - let tmpPoints; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - newShapes[ mainIdx ] = undefined; - newShapeHoles[ mainIdx ] = []; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - for ( let i = 0, l = subPaths.length; i < l; i ++ ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - tmpPath = subPaths[ i ]; - tmpPoints = tmpPath.getPoints(); - solid = isClockWise( tmpPoints ); - solid = isCCW ? ! solid : solid; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - if ( solid ) { + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; - newShapes[ mainIdx ].s.curves = tmpPath.curves; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - if ( holesFirst ) mainIdx ++; - newShapeHoles[ mainIdx ] = []; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - //console.log('cw', i); + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - } else { + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); + this.setValues( parameters ); - //console.log('ccw', i); + } - } + copy( source ) { - } + super.copy( source ); - // only Holes? -> probably all Shapes with wrong orientation - if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - if ( newShapes.length > 1 ) { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - let ambiguous = false; - let toChange = 0; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + this.flatShading = source.flatShading; - betterShapeHoles[ sIdx ] = []; + return this; - } + } - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { +} - const sho = newShapeHoles[ sIdx ]; +/** + * A material for non-shiny surfaces, without specular highlights. + * + * The material uses a non-physically based [Lambertian]{@link https://en.wikipedia.org/wiki/Lambertian_reflectance} + * model for calculating reflectance. This can simulate some surfaces (such + * as untreated wood or stone) well, but cannot simulate shiny surfaces with + * specular highlights (such as varnished wood). `MeshLambertMaterial` uses per-fragment + * shading. + * + * Due to the simplicity of the reflectance and illumination models, + * performance will be greater when using this material over the + * {@link MeshPhongMaterial}, {@link MeshStandardMaterial} or + * {@link MeshPhysicalMaterial}, at the cost of some graphical accuracy. + * + * @augments Material + */ +class MeshLambertMaterial extends Material { - for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { + /** + * Constructs a new mesh lambert material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - const ho = sho[ hIdx ]; - let hole_unassigned = true; + super(); - for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshLambertMaterial = true; - if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { + this.type = 'MeshLambertMaterial'; - if ( sIdx !== s2Idx ) toChange ++; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - if ( hole_unassigned ) { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - hole_unassigned = false; - betterShapeHoles[ s2Idx ].push( ho ); + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } else { + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - ambiguous = true; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - } + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - } + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - } + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - if ( hole_unassigned ) { + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - betterShapeHoles[ sIdx ].push( ho ); + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - } + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - } + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - } + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - if ( toChange > 0 && ambiguous === false ) { + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - newShapeHoles = betterShapeHoles; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - } + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - let tmpHoles; + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; - for ( let i = 0, il = newShapes.length; i < il; i ++ ) { + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - tmpShape = newShapes[ i ].s; - shapes.push( tmpShape ); - tmpHoles = newShapeHoles[ i ]; + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - tmpShape.holes.push( tmpHoles[ j ].h ); + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - } + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - } + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; - //console.log("shape", shapes); + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - return shapes; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - } + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; -} + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; -// export * from './Three.Legacy.js'; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; -if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - /* eslint-disable no-undef */ - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { - revision: REVISION, - } } ) ); - /* eslint-enable no-undef */ + this.setValues( parameters ); -} + } -if ( typeof window !== 'undefined' ) { + copy( source ) { - if ( window.__THREE__ ) { + super.copy( source ); - console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); + this.color.copy( source.color ); - } else { + this.map = source.map; - window.__THREE__ = REVISION; + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - } + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; -} + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; -/** - * @license - * Copyright 2010-2023 Three.js Authors - * SPDX-License-Identifier: MIT - */ + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; -/** - * Text = 3D Text - * - * parameters = { - * font: , // font - * - * size: , // size of the text - * height: , // thickness to extrude text - * curveSegments: , // number of points on the curves - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into text bevel goes - * bevelSize: , // how far from text outline (including bevelOffset) is bevel - * bevelOffset: // how far from text outline does bevel start - * } - */ + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; -class TextGeometry extends ExtrudeGeometry { + this.specularMap = source.specularMap; - constructor( text, parameters = {} ) { + this.alphaMap = source.alphaMap; - const font = parameters.font; + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - if ( font === undefined ) { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - super(); // generate default extrude geometry + this.flatShading = source.flatShading; - } else { + this.fog = source.fog; - const shapes = font.generateShapes( text, parameters.size ); + return this; - // translate parameters to ExtrudeGeometry API + } - parameters.depth = parameters.height !== undefined ? parameters.height : 50; +} - // defaults +/** + * This material is defined by a MatCap (or Lit Sphere) texture, which encodes the + * material color and shading. + * + * `MeshMatcapMaterial` does not respond to lights since the matcap image file encodes + * baked lighting. It will cast a shadow onto an object that receives shadows + * (and shadow clipping works), but it will not self-shadow or receive + * shadows. + * + * @augments Material + */ +class MeshMatcapMaterial extends Material { - if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; - if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; - if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + /** + * Constructs a new mesh matcap material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - super( shapes, parameters ); + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshMatcapMaterial = true; - this.type = 'TextGeometry'; + this.defines = { 'MATCAP': '' }; - } + this.type = 'MeshMatcapMaterial'; -} + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse -// + /** + * The matcap map. + * + * @type {?Texture} + * @default null + */ + this.matcap = null; -class Font { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - constructor( data ) { + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - this.isFont = true; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - this.type = 'Font'; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - this.data = data; + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - } + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - generateShapes( text, size = 100 ) { + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - const shapes = []; - const paths = createPaths( text, size, this.data ); + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - for ( let p = 0, pl = paths.length; p < pl; p ++ ) { + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - shapes.push( ...paths[ p ].toShapes() ); + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - } + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - return shapes; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); } -} -function createPaths( text, size, data ) { + copy( source ) { - const chars = Array.from( text ); - const scale = size / data.resolution; - const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; + super.copy( source ); - const paths = []; + this.defines = { 'MATCAP': '' }; - let offsetX = 0, offsetY = 0; + this.color.copy( source.color ); - for ( let i = 0; i < chars.length; i ++ ) { + this.matcap = source.matcap; - const char = chars[ i ]; + this.map = source.map; - if ( char === '\n' ) { + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - offsetX = 0; - offsetY -= line_height; + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - } else { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - const ret = createPath( char, scale, offsetX, offsetY, data ); - offsetX += ret.offsetX; - paths.push( ret.path ); + this.alphaMap = source.alphaMap; - } + this.flatShading = source.flatShading; - } + this.fog = source.fog; - return paths; + return this; + + } } -function createPath( char, scale, offsetX, offsetY, data ) { +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineDashedMaterial( { + * color: 0xffffff, + * scale: 1, + * dashSize: 3, + * gapSize: 1, + * } ); + * ``` + * + * @augments LineBasicMaterial + */ +class LineDashedMaterial extends LineBasicMaterial { - const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; + /** + * Constructs a new line dashed material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - if ( ! glyph ) { + super(); - console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineDashedMaterial = true; + this.type = 'LineDashedMaterial'; - return; + /** + * The scale of the dashed part of a line. + * + * @type {number} + * @default 1 + */ + this.scale = 1; - } + /** + * The size of the dash. This is both the gap with the stroke. + * + * @type {number} + * @default 3 + */ + this.dashSize = 3; - const path = new ShapePath(); + /** + * The size of the gap. + * + * @type {number} + * @default 1 + */ + this.gapSize = 1; - let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; + this.setValues( parameters ); - if ( glyph.o ) { + } - const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + copy( source ) { - for ( let i = 0, l = outline.length; i < l; ) { + super.copy( source ); - const action = outline[ i ++ ]; + this.scale = source.scale; + this.dashSize = source.dashSize; + this.gapSize = source.gapSize; - switch ( action ) { + return this; - case 'm': // moveTo + } - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; +} - path.moveTo( x, y ); +/** + * @class + * @classdesc A simple caching system, used internally by {@link FileLoader}. + * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app. + * @hideconstructor + */ +const Cache = { - break; + /** + * Whether caching is enabled or not. + * + * @static + * @type {boolean} + * @default false + */ + enabled: false, - case 'l': // lineTo + /** + * A dictionary that holds cached files. + * + * @static + * @type {Object} + */ + files: {}, - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; + /** + * Adds a cache entry with a key to reference the file. If this key already + * holds a file, it is overwritten. + * + * @static + * @param {string} key - The key to reference the cached file. + * @param {Object} file - The file to be cached. + */ + add: function ( key, file ) { - path.lineTo( x, y ); + if ( this.enabled === false ) return; - break; + // console.log( 'THREE.Cache', 'Adding key:', key ); - case 'q': // quadraticCurveTo + this.files[ key ] = file; - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; + }, - path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + /** + * Gets the cached value for the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned. + */ + get: function ( key ) { - break; + if ( this.enabled === false ) return; - case 'b': // bezierCurveTo + // console.log( 'THREE.Cache', 'Checking key:', key ); - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; - cpx2 = outline[ i ++ ] * scale + offsetX; - cpy2 = outline[ i ++ ] * scale + offsetY; + return this.files[ key ]; - path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + }, - break; + /** + * Removes the cached file associated with the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + */ + remove: function ( key ) { - } + delete this.files[ key ]; - } + }, - } + /** + * Remove all values from the cache. + * + * @static + */ + clear: function () { - return { offsetX: glyph.ha * scale, path: path }; + this.files = {}; -} + } -// OrbitControls performs orbiting, dollying (zooming), and panning. -// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). -// -// Orbit - left mouse / touch: one-finger move -// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish -// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move +}; -const _changeEvent = { type: 'change' }; -const _startEvent = { type: 'start' }; -const _endEvent = { type: 'end' }; -const _ray = new Ray(); -const _plane = new Plane(); -const TILT_LIMIT = Math.cos( 70 * MathUtils$1.DEG2RAD ); +/** + * Handles and keeps track of loaded and pending data. A default global + * instance of this class is created and used by loaders if not supplied + * manually. + * + * In general that should be sufficient, however there are times when it can + * be useful to have separate loaders - for example if you want to show + * separate loading bars for objects and textures. + * + * ```js + * const manager = new THREE.LoadingManager(); + * manager.onLoad = () => console.log( 'Loading complete!' ); + * + * const loader1 = new OBJLoader( manager ); + * const loader2 = new ColladaLoader( manager ); + * ``` + */ +class LoadingManager { -class OrbitControls extends EventDispatcher { + /** + * Constructs a new loading manager. + * + * @param {Function} [onLoad] - Executes when all items have been loaded. + * @param {Function} [onProgress] - Executes when single items have been loaded. + * @param {Function} [onError] - Executes when an error occurs. + */ + constructor( onLoad, onProgress, onError ) { - constructor( object, domElement ) { + const scope = this; - super(); + let isLoading = false; + let itemsLoaded = 0; + let itemsTotal = 0; + let urlModifier = undefined; + const handlers = []; - this.object = object; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll + // Refer to #5689 for the reason why we don't set .onStart + // in the constructor - // Set to false to disable this control - this.enabled = true; + /** + * Executes when an item starts loading. + * + * @type {Function|undefined} + * @default undefined + */ + this.onStart = undefined; - // "target" sets the location of focus, where the object orbits around - this.target = new Vector3(); + /** + * Executes when all items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onLoad = onLoad; - // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect - this.cursor = new Vector3(); + /** + * Executes when single items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onProgress = onProgress; - // How far you can dolly in and out ( PerspectiveCamera only ) - this.minDistance = 0; - this.maxDistance = Infinity; + /** + * Executes when an error occurs. + * + * @type {Function|undefined} + * @default undefined + */ + this.onError = onError; - // How far you can zoom in and out ( OrthographicCamera only ) - this.minZoom = 0; - this.maxZoom = Infinity; + /** + * Used for aborting ongoing requests in loaders using this manager. + * + * @type {AbortController} + */ + this.abortController = new AbortController(); - // Limit camera target within a spherical area around the cursor - this.minTargetRadius = 0; - this.maxTargetRadius = Infinity; + /** + * This should be called by any loader using the manager when the loader + * starts loading an item. + * + * @param {string} url - The URL to load. + */ + this.itemStart = function ( url ) { - // How far you can orbit vertically, upper and lower limits. - // Range is 0 to Math.PI radians. - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians + itemsTotal ++; - // How far you can orbit horizontally, upper and lower limits. - // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians + if ( isLoading === false ) { - // Set to true to enable damping (inertia) - // If damping is enabled, you must call controls.update() in your animation loop - this.enableDamping = false; - this.dampingFactor = 0.05; + if ( scope.onStart !== undefined ) { - // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. - // Set to false to disable zooming - this.enableZoom = true; - this.zoomSpeed = 1.0; + scope.onStart( url, itemsLoaded, itemsTotal ); - // Set to false to disable rotating - this.enableRotate = true; - this.rotateSpeed = 1.0; + } - // Set to false to disable panning - this.enablePan = true; - this.panSpeed = 1.0; - this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up - this.keyPanSpeed = 7.0; // pixels moved per arrow key push - this.zoomToCursor = false; + } - // Set to true to automatically rotate around the target - // If auto-rotate is enabled, you must call controls.update() in your animation loop - this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + isLoading = true; - // The four arrow keys - this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; + }; - // Mouse buttons - this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + /** + * This should be called by any loader using the manager when the loader + * ended loading an item. + * + * @param {string} url - The URL of the loaded item. + */ + this.itemEnd = function ( url ) { - // Touch fingers - this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + itemsLoaded ++; - // for reset - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.zoom0 = this.object.zoom; + if ( scope.onProgress !== undefined ) { - // the target DOM element for key events - this._domElementKeyEvents = null; + scope.onProgress( url, itemsLoaded, itemsTotal ); - // - // public methods - // + } + + if ( itemsLoaded === itemsTotal ) { - this.getPolarAngle = function () { + isLoading = false; - return spherical.phi; + if ( scope.onLoad !== undefined ) { - }; + scope.onLoad(); - this.getAzimuthalAngle = function () { + } - return spherical.theta; + } }; - this.getDistance = function () { - - return this.object.position.distanceTo( this.target ); + /** + * This should be called by any loader using the manager when the loader + * encounters an error when loading an item. + * + * @param {string} url - The URL of the item that produces an error. + */ + this.itemError = function ( url ) { - }; + if ( scope.onError !== undefined ) { - this.listenToKeyEvents = function ( domElement ) { + scope.onError( url ); - domElement.addEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = domElement; + } }; - this.stopListenToKeyEvents = function () { + /** + * Given a URL, uses the URL modifier callback (if any) and returns a + * resolved URL. If no URL modifier is set, returns the original URL. + * + * @param {string} url - The URL to load. + * @return {string} The resolved URL. + */ + this.resolveURL = function ( url ) { - this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = null; + if ( urlModifier ) { - }; + return urlModifier( url ); - this.saveState = function () { + } - scope.target0.copy( scope.target ); - scope.position0.copy( scope.object.position ); - scope.zoom0 = scope.object.zoom; + return url; }; - this.reset = function () { - - scope.target.copy( scope.target0 ); - scope.object.position.copy( scope.position0 ); - scope.object.zoom = scope.zoom0; - - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( _changeEvent ); + /** + * If provided, the callback will be passed each resource URL before a + * request is sent. The callback may return the original URL, or a new URL to + * override loading behavior. This behavior can be used to load assets from + * .ZIP files, drag-and-drop APIs, and Data URIs. + * + * ```js + * const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + * + * const manager = new THREE.LoadingManager(); + * + * // Initialize loading manager with URL callback. + * const objectURLs = []; + * manager.setURLModifier( ( url ) => { + * + * url = URL.createObjectURL( blobs[ url ] ); + * objectURLs.push( url ); + * return url; + * + * } ); + * + * // Load as usual, then revoke the blob URLs. + * const loader = new GLTFLoader( manager ); + * loader.load( 'fish.gltf', (gltf) => { + * + * scene.add( gltf.scene ); + * objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + * + * } ); + * ``` + * + * @param {function(string):string} transform - URL modifier callback. Called with an URL and must return a resolved URL. + * @return {LoadingManager} A reference to this loading manager. + */ + this.setURLModifier = function ( transform ) { - scope.update(); + urlModifier = transform; - state = STATE.NONE; + return this; }; - this.resetOrthoPanZoom = function () { - panOffset.set(0,0,0); - scope.object.zoom = 1; - scope.object.updateProjectionMatrix(); - }; - - // this method is exposed, but perhaps it would be better if we can make it private... - this.update = function () { + /** + * Registers a loader with the given regular expression. Can be used to + * define what loader should be used in order to load specific files. A + * typical use case is to overwrite the default loader for textures. + * + * ```js + * // add handler for TGA textures + * manager.addHandler( /\.tga$/i, new TGALoader() ); + * ``` + * + * @param {string} regex - A regular expression. + * @param {Loader} loader - A loader that should handle matched cases. + * @return {LoadingManager} A reference to this loading manager. + */ + this.addHandler = function ( regex, loader ) { - const offset = new Vector3(); + handlers.push( regex, loader ); - // so camera.up is the orbit axis - const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - const quatInverse = quat.clone().invert(); + return this; - const lastPosition = new Vector3(); - const lastQuaternion = new Quaternion(); - const lastTargetPosition = new Vector3(); + }; - const twoPI = 2 * Math.PI; + /** + * Removes the loader for the given regular expression. + * + * @param {string} regex - A regular expression. + * @return {LoadingManager} A reference to this loading manager. + */ + this.removeHandler = function ( regex ) { - return function update( deltaTime = null ) { + const index = handlers.indexOf( regex ); - const position = scope.object.position; + if ( index !== -1 ) { - offset.copy( position ).sub( scope.target ); + handlers.splice( index, 2 ); - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); + } - // angle from z-axis around y-axis - spherical.setFromVector3( offset ); + return this; - if ( scope.autoRotate && state === STATE.NONE ) { + }; - rotateLeft( getAutoRotationAngle( deltaTime ) ); + /** + * Can be used to retrieve the registered loader for the given file path. + * + * @param {string} file - The file path. + * @return {?Loader} The registered loader. Returns `null` if no loader was found. + */ + this.getHandler = function ( file ) { - } + for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - if ( scope.enableDamping ) { + const regex = handlers[ i ]; + const loader = handlers[ i + 1 ]; - spherical.theta += sphericalDelta.theta * scope.dampingFactor; - spherical.phi += sphericalDelta.phi * scope.dampingFactor; + if ( regex.global ) regex.lastIndex = 0; // see #17920 - } else { + if ( regex.test( file ) ) { - spherical.theta += sphericalDelta.theta; - spherical.phi += sphericalDelta.phi; + return loader; } - // restrict theta to be between desired limits - - let min = scope.minAzimuthAngle; - let max = scope.maxAzimuthAngle; + } - if ( isFinite( min ) && isFinite( max ) ) { + return null; - if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + }; - if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + /** + * Can be used to abort ongoing loading requests in loaders using this manager. + * The abort only works if the loaders implement {@link Loader#abort} and `AbortSignal.any()` + * is supported in the browser. + * + * @return {LoadingManager} A reference to this loading manager. + */ + this.abort = function () { - if ( min <= max ) { + this.abortController.abort(); + this.abortController = new AbortController(); - spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + return this; - } else { + }; - spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, spherical.theta ) : - Math.min( max, spherical.theta ); + } - } +} - } +/** + * The global default loading manager. + * + * @constant + * @type {LoadingManager} + */ +const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - // restrict phi to be between desired limits - spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); +/** + * Abstract base class for loaders. + * + * @abstract + */ +class Loader { - spherical.makeSafe(); + /** + * Constructs a new loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + /** + * The loading manager. + * + * @type {LoadingManager} + * @default DefaultLoadingManager + */ + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - // move target to panned location + /** + * The crossOrigin string to implement CORS for loading the url from a + * different domain that allows CORS. + * + * @type {string} + * @default 'anonymous' + */ + this.crossOrigin = 'anonymous'; - if ( scope.enableDamping === true ) { + /** + * Whether the XMLHttpRequest uses credentials. + * + * @type {boolean} + * @default false + */ + this.withCredentials = false; - scope.target.addScaledVector( panOffset, scope.dampingFactor ); + /** + * The base path from which the asset will be loaded. + * + * @type {string} + */ + this.path = ''; - } else { + /** + * The base path from which additional resources like textures will be loaded. + * + * @type {string} + */ + this.resourcePath = ''; - scope.target.add( panOffset ); + /** + * The [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * used in HTTP request. + * + * @type {Object} + */ + this.requestHeader = {}; - } + } - // Limit the target distance from the cursor to create a sphere around the center of interest - scope.target.sub( scope.cursor ); - scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius ); - scope.target.add( scope.cursor ); + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for loading assets from the backend. + * + * @abstract + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + */ + load( /* url, onLoad, onProgress, onError */ ) {} - let zoomChanged = false; - // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera - // we adjust zoom later in these cases - if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) { + /** + * A async version of {@link Loader#load}. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves when the asset has been loaded. + */ + loadAsync( url, onProgress ) { - spherical.radius = clampDistance( spherical.radius ); + const scope = this; - } else { + return new Promise( function ( resolve, reject ) { - const prevRadius = spherical.radius; - spherical.radius = clampDistance( spherical.radius * scale ); - zoomChanged = prevRadius != spherical.radius; + scope.load( url, resolve, onProgress, reject ); - } + } ); - offset.setFromSpherical( spherical ); + } - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); - - position.copy( scope.target ).add( offset ); - - scope.object.lookAt( scope.target ); - - if ( scope.enableDamping === true ) { - - sphericalDelta.theta *= ( 1 - scope.dampingFactor ); - sphericalDelta.phi *= ( 1 - scope.dampingFactor ); - - panOffset.multiplyScalar( 1 - scope.dampingFactor ); + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for parsing the asset into three.js entities. + * + * @abstract + * @param {any} data - The data to parse. + */ + parse( /* data */ ) {} - } else { + /** + * Sets the `crossOrigin` String to implement CORS for loading the URL + * from a different domain that allows CORS. + * + * @param {string} crossOrigin - The `crossOrigin` value. + * @return {Loader} A reference to this instance. + */ + setCrossOrigin( crossOrigin ) { - sphericalDelta.set( 0, 0, 0 ); + this.crossOrigin = crossOrigin; + return this; - panOffset.set( 0, 0, 0 ); + } - } + /** + * Whether the XMLHttpRequest uses credentials such as cookies, authorization + * headers or TLS client certificates, see [XMLHttpRequest.withCredentials]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials}. + * + * Note: This setting has no effect if you are loading files locally or from the same domain. + * + * @param {boolean} value - The `withCredentials` value. + * @return {Loader} A reference to this instance. + */ + setWithCredentials( value ) { - // adjust camera position - if ( scope.zoomToCursor && performCursorZoom ) { + this.withCredentials = value; + return this; - let newRadius = null; - if ( scope.object.isPerspectiveCamera ) { + } - // move the camera down the pointer ray - // this method avoids floating point error - const prevRadius = offset.length(); - newRadius = clampDistance( prevRadius * scale ); + /** + * Sets the base path for the asset. + * + * @param {string} path - The base path. + * @return {Loader} A reference to this instance. + */ + setPath( path ) { - const radiusDelta = prevRadius - newRadius; - scope.object.position.addScaledVector( dollyDirection, radiusDelta ); - scope.object.updateMatrixWorld(); + this.path = path; + return this; - zoomChanged = !! radiusDelta; + } - } else if ( scope.object.isOrthographicCamera ) { + /** + * Sets the base path for dependent resources like textures. + * + * @param {string} resourcePath - The resource path. + * @return {Loader} A reference to this instance. + */ + setResourcePath( resourcePath ) { - // adjust the ortho camera position based on zoom changes - const mouseBefore = new Vector3( mouse.x, mouse.y, 0 ); - mouseBefore.unproject( scope.object ); + this.resourcePath = resourcePath; + return this; - const prevZoom = scope.object.zoom; - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); - scope.object.updateProjectionMatrix(); + } - zoomChanged = prevZoom !== scope.object.zoom; + /** + * Sets the given request header. + * + * @param {Object} requestHeader - A [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * for configuring the HTTP request. + * @return {Loader} A reference to this instance. + */ + setRequestHeader( requestHeader ) { - const mouseAfter = new Vector3( mouse.x, mouse.y, 0 ); - mouseAfter.unproject( scope.object ); + this.requestHeader = requestHeader; + return this; - scope.object.position.sub( mouseAfter ).add( mouseBefore ); - scope.object.updateMatrixWorld(); + } - newRadius = offset.length(); + /** + * This method can be implemented in loaders for aborting ongoing requests. + * + * @abstract + * @return {Loader} A reference to this instance. + */ + abort() { - } else { + return this; - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); - scope.zoomToCursor = false; + } - } +} - // handle the placement of the target - if ( newRadius !== null ) { +/** + * Callback for onProgress in loaders. + * + * @callback onProgressCallback + * @param {ProgressEvent} event - An instance of `ProgressEvent` that represents the current loading status. + */ - if ( this.screenSpacePanning ) { +/** + * Callback for onError in loaders. + * + * @callback onErrorCallback + * @param {Error} error - The error which occurred during the loading process. + */ - // position the orbit target in front of the new camera position - scope.target.set( 0, 0, - 1 ) - .transformDirection( scope.object.matrix ) - .multiplyScalar( newRadius ) - .add( scope.object.position ); +/** + * The default material name that is used by loaders + * when creating materials for loaded 3D objects. + * + * Note: Not all loaders might honor this setting. + * + * @static + * @type {string} + * @default '__DEFAULT' + */ +Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; - } else { +const _loading = new WeakMap(); - // get the ray and translation plane to compute target - _ray.origin.copy( scope.object.position ); - _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix ); +/** + * A loader for loading images. The class loads images with the HTML `Image` API. + * + * ```js + * const loader = new THREE.ImageLoader(); + * const image = await loader.loadAsync( 'image.png' ); + * ``` + * Please note that `ImageLoader` has dropped support for progress + * events in `r84`. For an `ImageLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639}. + * + * @augments Loader + */ +class ImageLoader extends Loader { - // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid - // extremely large values - if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) { + /** + * Constructs a new image loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - object.lookAt( scope.target ); + super( manager ); - } else { + } - _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target ); - _ray.intersectPlane( _plane, scope.target ); + /** + * Starts loading from the given URL and passes the loaded image + * to the `onLoad()` callback. The method also returns a new `Image` object which can + * directly be used for texture creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Image)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Image} The image. + */ + load( url, onLoad, onProgress, onError ) { - } + if ( this.path !== undefined ) url = this.path + url; - } + url = this.manager.resolveURL( url ); - } + const scope = this; - } else if ( scope.object.isOrthographicCamera ) { + const cached = Cache.get( `image:${url}` ); - const prevZoom = scope.object.zoom; - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); + if ( cached !== undefined ) { - if ( prevZoom !== scope.object.zoom ) { + if ( cached.complete === true ) { - scope.object.updateProjectionMatrix(); - zoomChanged = true; + scope.manager.itemStart( url ); - } + setTimeout( function () { - } + if ( onLoad ) onLoad( cached ); - scale = 1; - performCursorZoom = false; + scope.manager.itemEnd( url ); - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + }, 0 ); - if ( zoomChanged || - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS || - lastTargetPosition.distanceToSquared( scope.target ) > EPS ) { + } else { - scope.dispatchEvent( _changeEvent ); + let arr = _loading.get( cached ); - lastPosition.copy( scope.object.position ); - lastQuaternion.copy( scope.object.quaternion ); - lastTargetPosition.copy( scope.target ); + if ( arr === undefined ) { - return true; + arr = []; + _loading.set( cached, arr ); } - return false; - - }; - - }(); - - this.dispose = function () { - - scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); - - scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); - scope.domElement.removeEventListener( 'wheel', onMouseWheel ); - - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); - - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + arr.push( { onLoad, onError } ); - document.removeEventListener( 'keydown', interceptControlDown, { capture: true } ); - - if ( scope._domElementKeyEvents !== null ) { + } - scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - scope._domElementKeyEvents = null; + return cached; - } + } - //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + const image = createElementNS( 'img' ); - }; + function onImageLoad() { - // - // internals - // + removeEventListeners(); - const scope = this; + if ( onLoad ) onLoad( this ); - const STATE = { - NONE: - 1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 - }; + // - let state = STATE.NONE; + const callbacks = _loading.get( this ) || []; - const EPS = 0.000001; + for ( let i = 0; i < callbacks.length; i ++ ) { - // current position in spherical coordinates - const spherical = new Spherical(); - const sphericalDelta = new Spherical(); + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( this ); - let scale = 1; - const panOffset = new Vector3(); + } - const rotateStart = new Vector2(); - const rotateEnd = new Vector2(); - const rotateDelta = new Vector2(); + _loading.delete( this ); - const panStart = new Vector2(); - const panEnd = new Vector2(); - const panDelta = new Vector2(); + scope.manager.itemEnd( url ); - const dollyStart = new Vector2(); - const dollyEnd = new Vector2(); - const dollyDelta = new Vector2(); + } - const dollyDirection = new Vector3(); - const mouse = new Vector2(); - let performCursorZoom = false; + function onImageError( event ) { - const pointers = []; - const pointerPositions = {}; + removeEventListeners(); - let controlActive = false; + if ( onError ) onError( event ); - function getAutoRotationAngle( deltaTime ) { + Cache.remove( `image:${url}` ); - if ( deltaTime !== null ) { + // - return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime; + const callbacks = _loading.get( this ) || []; - } else { + for ( let i = 0; i < callbacks.length; i ++ ) { - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); } - } + _loading.delete( this ); - function getZoomScale( delta ) { - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); } - function rotateLeft( angle ) { + function removeEventListeners() { - sphericalDelta.theta -= angle; + image.removeEventListener( 'load', onImageLoad, false ); + image.removeEventListener( 'error', onImageError, false ); } - function rotateUp( angle ) { + image.addEventListener( 'load', onImageLoad, false ); + image.addEventListener( 'error', onImageError, false ); + + if ( url.slice( 0, 5 ) !== 'data:' ) { - sphericalDelta.phi -= angle; + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; } - const panLeft = function () { + Cache.add( `image:${url}`, image ); + scope.manager.itemStart( url ); - const v = new Vector3(); + image.src = url; - return function panLeft( distance, objectMatrix ) { + return image; - v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - v.multiplyScalar( - distance ); + } - panOffset.add( v ); +} - }; +/** + * Class for loading textures. Images are internally + * loaded via {@link ImageLoader}. + * + * ```js + * const loader = new THREE.TextureLoader(); + * const texture = await loader.loadAsync( 'textures/land_ocean_ice_cloud_2048.jpg' ); + * + * const material = new THREE.MeshBasicMaterial( { map:texture } ); + * ``` + * Please note that `TextureLoader` has dropped support for progress + * events in `r84`. For a `TextureLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145}. + * + * @augments Loader + */ +class TextureLoader extends Loader { - }(); + /** + * Constructs a new texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - const panUp = function () { + super( manager ); - const v = new Vector3(); + } - return function panUp( distance, objectMatrix ) { + /** + * Starts loading from the given URL and pass the fully loaded texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Texture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Texture} The texture. + */ + load( url, onLoad, onProgress, onError ) { - if ( scope.screenSpacePanning === true ) { + const texture = new Texture(); - v.setFromMatrixColumn( objectMatrix, 1 ); + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); - } else { + loader.load( url, function ( image ) { - v.setFromMatrixColumn( objectMatrix, 0 ); - v.crossVectors( scope.object.up, v ); + texture.image = image; + texture.needsUpdate = true; - } + if ( onLoad !== undefined ) { - v.multiplyScalar( distance ); + onLoad( texture ); - panOffset.add( v ); + } - }; + }, onProgress, onError ); - }(); + return texture; - // deltaX and deltaY are in pixels; right and down are positive - const pan = function () { + } - const offset = new Vector3(); +} - return function pan( deltaX, deltaY ) { +/** + * Abstract base class for lights - all other light types inherit the + * properties and methods described here. + * + * @abstract + * @augments Object3D + */ +class Light extends Object3D { - const element = scope.domElement; + /** + * Constructs a new light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity = 1 ) { - if ( scope.object.isPerspectiveCamera ) { + super(); - // perspective - const position = scope.object.position; - offset.copy( position ).sub( scope.target ); - let targetDistance = offset.length(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLight = true; - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + this.type = 'Light'; - // we use only clientHeight here so aspect ratio does not distort speed - panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); - panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + /** + * The light's color. + * + * @type {Color} + */ + this.color = new Color( color ); - } else if ( scope.object.isOrthographicCamera ) { + /** + * The light's intensity. + * + * @type {number} + * @default 1 + */ + this.intensity = intensity; - // orthographic - panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); - panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + } - } else { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - scope.enablePan = false; + // Empty here in base class; some subclasses override. - } + } - }; + copy( source, recursive ) { - }(); + super.copy( source, recursive ); - function dollyOut( dollyScale ) { + this.color.copy( source.color ); + this.intensity = source.intensity; - if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { + return this; - scale /= dollyScale; + } - } else { + toJSON( meta ) { - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; + const data = super.toJSON( meta ); - } + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; - } + if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); - function dollyIn( dollyScale ) { + if ( this.distance !== undefined ) data.object.distance = this.distance; + if ( this.angle !== undefined ) data.object.angle = this.angle; + if ( this.decay !== undefined ) data.object.decay = this.decay; + if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; - if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { + if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + if ( this.target !== undefined ) data.object.target = this.target.uuid; - scale *= dollyScale; + return data; - } else { + } - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; +} - } +/** + * A light source positioned directly above the scene, with color fading from + * the sky color to the ground color. + * + * This light cannot be used to cast shadows. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ +class HemisphereLight extends Light { - } + /** + * Constructs a new hemisphere light. + * + * @param {(number|Color|string)} [skyColor=0xffffff] - The light's sky color. + * @param {(number|Color|string)} [groundColor=0xffffff] - The light's ground color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( skyColor, groundColor, intensity ) { - function updateZoomParameters( x, y ) { + super( skyColor, intensity ); - if ( ! scope.zoomToCursor ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isHemisphereLight = true; - return; + this.type = 'HemisphereLight'; - } + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); - performCursorZoom = true; + /** + * The light's ground color. + * + * @type {Color} + */ + this.groundColor = new Color( groundColor ); - const rect = scope.domElement.getBoundingClientRect(); - const dx = x - rect.left; - const dy = y - rect.top; - const w = rect.width; - const h = rect.height; + } - mouse.x = ( dx / w ) * 2 - 1; - mouse.y = - ( dy / h ) * 2 + 1; + copy( source, recursive ) { - dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize(); + super.copy( source, recursive ); - } + this.groundColor.copy( source.groundColor ); - function clampDistance( dist ) { + return this; - return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) ); + } - } +} - // - // event callbacks - update the object state - // +const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); +const _lookTarget$1 = /*@__PURE__*/ new Vector3(); + +/** + * Abstract base class for light shadow classes. These classes + * represent the shadow configuration for different light types. + * + * @abstract + */ +class LightShadow { - function handleMouseDownRotate( event ) { + /** + * Constructs a new light shadow. + * + * @param {Camera} camera - The light's view of the world. + */ + constructor( camera ) { - rotateStart.set( event.clientX, event.clientY ); + /** + * The light's view of the world. + * + * @type {Camera} + */ + this.camera = camera; - } + /** + * The intensity of the shadow. The default is `1`. + * Valid values are in the range `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.intensity = 1; - function handleMouseDownDolly( event ) { + /** + * Shadow map bias, how much to add or subtract from the normalized depth + * when deciding whether a surface is in shadow. + * + * The default is `0`. Very tiny adjustments here (in the order of `0.0001`) + * may help reduce artifacts in shadows. + * + * @type {number} + * @default 0 + */ + this.bias = 0; - updateZoomParameters( event.clientX, event.clientX ); - dollyStart.set( event.clientX, event.clientY ); + /** + * Defines how much the position used to query the shadow map is offset along + * the object normal. The default is `0`. Increasing this value can be used to + * reduce shadow acne especially in large scenes where light shines onto + * geometry at a shallow angle. The cost is that shadows may appear distorted. + * + * @type {number} + * @default 0 + */ + this.normalBias = 0; - } + /** + * Setting this to values greater than 1 will blur the edges of the shadow. + * High values will cause unwanted banding effects in the shadows - a greater + * map size will allow for a higher value to be used here before these effects + * become visible. + * + * The property has no effect when the shadow map type is `PCFSoftShadowMap` and + * and it is recommended to increase softness by decreasing the shadow map size instead. + * + * The property has no effect when the shadow map type is `BasicShadowMap`. + * + * @type {number} + * @default 1 + */ + this.radius = 1; - function handleMouseDownPan( event ) { + /** + * The amount of samples to use when blurring a VSM shadow map. + * + * @type {number} + * @default 8 + */ + this.blurSamples = 8; - panStart.set( event.clientX, event.clientY ); + /** + * Defines the width and height of the shadow map. Higher values give better quality + * shadows at the cost of computation time. Values must be powers of two. + * + * @type {Vector2} + * @default (512,512) + */ + this.mapSize = new Vector2( 512, 512 ); - } + /** + * The type of shadow texture. The default is `UnsignedByteType`. + * + * @type {number} + * @default UnsignedByteType + */ + this.mapType = UnsignedByteType; - function handleMouseMoveRotate( event ) { + /** + * The depth map generated using the internal camera; a location beyond a + * pixel's depth is in shadow. Computed internally during rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.map = null; - rotateEnd.set( event.clientX, event.clientY ); + /** + * The distribution map generated using the internal camera; an occlusion is + * calculated based on the distribution of depths. Computed internally during + * rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.mapPass = null; - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + /** + * Model to shadow camera space, to compute location and depth in shadow map. + * This is computed internally during rendering. + * + * @type {Matrix4} + */ + this.matrix = new Matrix4(); - const element = scope.domElement; + /** + * Enables automatic updates of the light's shadow. If you do not require dynamic + * lighting / shadows, you may set this to `false`. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + /** + * When set to `true`, shadow maps will be updated in the next `render` call. + * If you have set {@link LightShadow#autoUpdate} to `false`, you will need to + * set this property to `true` and then make a render call to update the light's shadow. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + this._frustum = new Frustum(); + this._frameExtents = new Vector2( 1, 1 ); - rotateStart.copy( rotateEnd ); + this._viewportCount = 1; - scope.update(); + this._viewports = [ - } + new Vector4( 0, 0, 1, 1 ) - function handleMouseMoveDolly( event ) { + ]; - dollyEnd.set( event.clientX, event.clientY ); + } - dollyDelta.subVectors( dollyEnd, dollyStart ); + /** + * Used internally by the renderer to get the number of viewports that need + * to be rendered for this shadow. + * + * @return {number} The viewport count. + */ + getViewportCount() { - if ( dollyDelta.y > 0 ) { + return this._viewportCount; - dollyOut( getZoomScale( dollyDelta.y ) ); + } - } else if ( dollyDelta.y < 0 ) { + /** + * Gets the shadow cameras frustum. Used internally by the renderer to cull objects. + * + * @return {Frustum} The shadow camera frustum. + */ + getFrustum() { - dollyIn( getZoomScale( dollyDelta.y ) ); + return this._frustum; - } + } - dollyStart.copy( dollyEnd ); + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + */ + updateMatrices( light ) { - scope.update(); + const shadowCamera = this.camera; + const shadowMatrix = this.matrix; - } + _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); + shadowCamera.position.copy( _lightPositionWorld$1 ); - function handleMouseMovePan( event ) { + _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _lookTarget$1 ); + shadowCamera.updateMatrixWorld(); - panEnd.set( event.clientX, event.clientY ); + _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix$1, shadowCamera.coordinateSystem, shadowCamera.reversedDepth ); - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + if ( shadowCamera.reversedDepth ) { - pan( panDelta.x, panDelta.y ); + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); - panStart.copy( panEnd ); + } else { - scope.update(); + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); } - function handleMouseWheel( event ) { + shadowMatrix.multiply( _projScreenMatrix$1 ); - updateZoomParameters( event.clientX, event.clientY ); + } - if ( event.deltaY < 0 ) { + /** + * Returns a viewport definition for the given viewport index. + * + * @param {number} viewportIndex - The viewport index. + * @return {Vector4} The viewport. + */ + getViewport( viewportIndex ) { - dollyIn( getZoomScale( event.deltaY ) ); + return this._viewports[ viewportIndex ]; - } else if ( event.deltaY > 0 ) { + } - dollyOut( getZoomScale( event.deltaY ) ); + /** + * Returns the frame extends. + * + * @return {Vector2} The frame extends. + */ + getFrameExtents() { - } + return this._frameExtents; - scope.update(); + } - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - function handleKeyDown( event ) { + if ( this.map ) { - let needsUpdate = false; + this.map.dispose(); - switch ( event.code ) { + } - case scope.keys.UP: + if ( this.mapPass ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this.mapPass.dispose(); - rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + } - } else { + } - pan( 0, scope.keyPanSpeed ); + /** + * Copies the values of the given light shadow instance to this instance. + * + * @param {LightShadow} source - The light shadow to copy. + * @return {LightShadow} A reference to this light shadow instance. + */ + copy( source ) { - } + this.camera = source.camera.clone(); - needsUpdate = true; - break; + this.intensity = source.intensity; - case scope.keys.BOTTOM: + this.bias = source.bias; + this.radius = source.radius; - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this.autoUpdate = source.autoUpdate; + this.needsUpdate = source.needsUpdate; + this.normalBias = source.normalBias; + this.blurSamples = source.blurSamples; - rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + this.mapSize.copy( source.mapSize ); - } else { + return this; - pan( 0, - scope.keyPanSpeed ); + } - } + /** + * Returns a new light shadow instance with copied values from this instance. + * + * @return {LightShadow} A clone of this instance. + */ + clone() { - needsUpdate = true; - break; + return new this.constructor().copy( this ); - case scope.keys.LEFT: + } - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + /** + * Serializes the light shadow into JSON. + * + * @return {Object} A JSON object representing the serialized light shadow. + * @see {@link ObjectLoader#parse} + */ + toJSON() { - rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + const object = {}; - } else { + if ( this.intensity !== 1 ) object.intensity = this.intensity; + if ( this.bias !== 0 ) object.bias = this.bias; + if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; + if ( this.radius !== 1 ) object.radius = this.radius; + if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); - pan( scope.keyPanSpeed, 0 ); + object.camera = this.camera.toJSON( false ).object; + delete object.camera.matrix; - } + return object; - needsUpdate = true; - break; + } - case scope.keys.RIGHT: +} - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ +class DirectionalLightShadow extends LightShadow { - rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + /** + * Constructs a new directional light shadow. + */ + constructor() { - } else { + super( new OrthographicCamera( -5, 5, 5, -5, 0.5, 500 ) ); - pan( - scope.keyPanSpeed, 0 ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLightShadow = true; - } + } - needsUpdate = true; - break; +} - } +/** + * A light that gets emitted in a specific direction. This light will behave + * as though it is infinitely far away and the rays produced from it are all + * parallel. The common use case for this is to simulate daylight; the sun is + * far enough away that its position can be considered to be infinite, and + * all light rays coming from it are parallel. + * + * A common point of confusion for directional lights is that setting the + * rotation has no effect. This is because three.js's DirectionalLight is the + * equivalent to what is often called a 'Target Direct Light' in other + * applications. + * + * This means that its direction is calculated as pointing from the light's + * {@link Object3D#position} to the {@link DirectionalLight#target} position + * (as opposed to a 'Free Direct Light' that just has a rotation + * component). + * + * This light can cast shadows - see the {@link DirectionalLightShadow} for details. + * + * ```js + * // White directional light at half intensity shining from the top. + * const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + * scene.add( directionalLight ); + * ``` + * + * @augments Light + */ +class DirectionalLight extends Light { - if ( needsUpdate ) { + /** + * Constructs a new directional light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { - // prevent the browser from scrolling on cursor keys - event.preventDefault(); + super( color, intensity ); - scope.update(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLight = true; - } + this.type = 'DirectionalLight'; + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); - } + /** + * The directional light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ + this.target = new Object3D(); - function handleTouchStartRotate( event ) { + /** + * This property holds the light's shadow configuration. + * + * @type {DirectionalLightShadow} + */ + this.shadow = new DirectionalLightShadow(); - if ( pointers.length === 1 ) { + } - rotateStart.set( event.pageX, event.pageY ); + dispose() { - } else { + this.shadow.dispose(); - const position = getSecondPointerPosition( event ); + } - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + copy( source ) { - rotateStart.set( x, y ); + super.copy( source ); - } + this.target = source.target.clone(); + this.shadow = source.shadow.clone(); - } + return this; - function handleTouchStartPan( event ) { + } - if ( pointers.length === 1 ) { +} - panStart.set( event.pageX, event.pageY ); +/** + * This light globally illuminates all objects in the scene equally. + * + * It cannot be used to cast shadows as it does not have a direction. + * + * ```js + * const light = new THREE.AmbientLight( 0x404040 ); // soft white light + * scene.add( light ); + * ``` + * + * @augments Light + */ +class AmbientLight extends Light { - } else { + /** + * Constructs a new ambient light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { - const position = getSecondPointerPosition( event ); + super( color, intensity ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAmbientLight = true; - panStart.set( x, y ); + this.type = 'AmbientLight'; - } + } - } +} - function handleTouchStartDolly( event ) { +const _matrix = /*@__PURE__*/ new Matrix4(); - const position = getSecondPointerPosition( event ); +/** + * This class is designed to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3d space the mouse is over) + * amongst other things. + */ +class Raycaster { - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); + /** + * Constructs a new raycaster. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + * @param {number} [near=0] - All results returned are further away than near. Near can't be negative. + * @param {number} [far=Infinity] - All results returned are closer than far. Far can't be lower than near. + */ + constructor( origin, direction, near = 0, far = Infinity ) { - dollyStart.set( 0, distance ); + /** + * The ray used for raycasting. + * + * @type {Ray} + */ + this.ray = new Ray( origin, direction ); - } + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default 0 + */ + this.near = near; - function handleTouchStartDollyPan( event ) { + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default Infinity + */ + this.far = far; - if ( scope.enableZoom ) handleTouchStartDolly( event ); + /** + * The camera to use when raycasting against view-dependent objects such as + * billboarded objects like sprites. This field can be set manually or + * is set when calling `setFromCamera()`. + * + * @type {?Camera} + * @default null + */ + this.camera = null; - if ( scope.enablePan ) handleTouchStartPan( event ); + /** + * Allows to selectively ignore 3D objects when performing intersection tests. + * The following code example ensures that only 3D objects on layer `1` will be + * honored by raycaster. + * ```js + * raycaster.layers.set( 1 ); + * object.layers.enable( 1 ); + * ``` + * + * @type {Layers} + */ + this.layers = new Layers(); - } - function handleTouchStartDollyRotate( event ) { + /** + * A parameter object that configures the raycasting. It has the structure: + * + * ``` + * { + * Mesh: {}, + * Line: { threshold: 1 }, + * LOD: {}, + * Points: { threshold: 1 }, + * Sprite: {} + * } + * ``` + * Where `threshold` is the precision of the raycaster when intersecting objects, in world units. + * + * @type {Object} + */ + this.params = { + Mesh: {}, + Line: { threshold: 1 }, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + }; - if ( scope.enableZoom ) handleTouchStartDolly( event ); + } - if ( scope.enableRotate ) handleTouchStartRotate( event ); + /** + * Updates the ray with a new origin and direction by copying the values from the arguments. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + */ + set( origin, direction ) { - } + // direction is assumed to be normalized (for accurate distance calculations) - function handleTouchMoveRotate( event ) { + this.ray.set( origin, direction ); - if ( pointers.length == 1 ) { + } - rotateEnd.set( event.pageX, event.pageY ); + /** + * Uses the given coordinates and camera to compute a new origin and direction for the internal ray. + * + * @param {Vector2} coords - 2D coordinates of the mouse, in normalized device coordinates (NDC). + * X and Y components should be between `-1` and `1`. + * @param {Camera} camera - The camera from which the ray should originate. + */ + setFromCamera( coords, camera ) { - } else { + if ( camera.isPerspectiveCamera ) { - const position = getSecondPointerPosition( event ); + this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); + this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); + this.camera = camera; - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + } else if ( camera.isOrthographicCamera ) { - rotateEnd.set( x, y ); + this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera + this.ray.direction.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ); + this.camera = camera; - } + } else { - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); - const element = scope.domElement; + } - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + } - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + /** + * Uses the given WebXR controller to compute a new origin and direction for the internal ray. + * + * @param {WebXRController} controller - The controller to copy the position and direction from. + * @return {Raycaster} A reference to this raycaster. + */ + setFromXRController( controller ) { - rotateStart.copy( rotateEnd ); + _matrix.identity().extractRotation( controller.matrixWorld ); - } + this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); + this.ray.direction.set( 0, 0, -1 ).applyMatrix4( _matrix ); - function handleTouchMovePan( event ) { + return this; - if ( pointers.length === 1 ) { + } - panEnd.set( event.pageX, event.pageY ); + /** + * The intersection point of a raycaster intersection test. + * @typedef {Object} Raycaster~Intersection + * @property {number} distance - The distance from the ray's origin to the intersection point. + * @property {number} distanceToRay - Some 3D objects e.g. {@link Points} provide the distance of the + * intersection to the nearest point on the ray. For other objects it will be `undefined`. + * @property {Vector3} point - The intersection point, in world coordinates. + * @property {Object} face - The face that has been intersected. + * @property {number} faceIndex - The face index. + * @property {Object3D} object - The 3D object that has been intersected. + * @property {Vector2} uv - U,V coordinates at point of intersection. + * @property {Vector2} uv1 - Second set of U,V coordinates at point of intersection. + * @property {Vector3} uv1 - Interpolated normal vector at point of intersection. + * @property {number} instanceId - The index number of the instance where the ray + * intersects the {@link InstancedMesh}. + */ - } else { + /** + * Checks all intersection between the ray and the object with or without the + * descendants. Intersections are returned sorted by distance, closest first. + * + * `Raycaster` delegates to the `raycast()` method of the passed 3D object, when + * evaluating whether the ray intersects the object or not. This allows meshes to respond + * differently to ray casting than lines or points. + * + * Note that for meshes, faces must be pointed towards the origin of the ray in order + * to be detected; intersections of the ray passing through the back of a face will not + * be detected. To raycast against both faces of an object, you'll want to set {@link Material#side} + * to `THREE.DoubleSide`. + * + * @param {Object3D} object - The 3D object to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObject( object, recursive = true, intersects = [] ) { - const position = getSecondPointerPosition( event ); + intersect( object, this, intersects, recursive ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + intersects.sort( ascSort ); - panEnd.set( x, y ); + return intersects; - } + } - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + /** + * Checks all intersection between the ray and the objects with or without + * the descendants. Intersections are returned sorted by distance, closest first. + * + * @param {Array} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObjects( objects, recursive = true, intersects = [] ) { - pan( panDelta.x, panDelta.y ); + for ( let i = 0, l = objects.length; i < l; i ++ ) { - panStart.copy( panEnd ); + intersect( objects[ i ], this, intersects, recursive ); } - function handleTouchMoveDolly( event ) { - - const position = getSecondPointerPosition( event ); + intersects.sort( ascSort ); - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; + return intersects; - const distance = Math.sqrt( dx * dx + dy * dy ); + } - dollyEnd.set( 0, distance ); +} - dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); +function ascSort( a, b ) { - dollyOut( dollyDelta.y ); + return a.distance - b.distance; - dollyStart.copy( dollyEnd ); +} - const centerX = ( event.pageX + position.x ) * 0.5; - const centerY = ( event.pageY + position.y ) * 0.5; +function intersect( object, raycaster, intersects, recursive ) { - updateZoomParameters( centerX, centerY ); + let propagate = true; - } + if ( object.layers.test( raycaster.layers ) ) { - function handleTouchMoveDollyPan( event ) { + const result = object.raycast( raycaster, intersects ); - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + if ( result === false ) propagate = false; - if ( scope.enablePan ) handleTouchMovePan( event ); + } - } + if ( propagate === true && recursive === true ) { - function handleTouchMoveDollyRotate( event ) { + const children = object.children; - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + for ( let i = 0, l = children.length; i < l; i ++ ) { - if ( scope.enableRotate ) handleTouchMoveRotate( event ); + intersect( children[ i ], raycaster, intersects, true ); } - // - // event handlers - FSM: listen for events and reset state - // + } - function onPointerDown( event ) { +} - if ( scope.enabled === false ) return; +/** + * Class for keeping track of time. + */ +class Clock { - if ( pointers.length === 0 ) { + /** + * Constructs a new clock. + * + * @param {boolean} [autoStart=true] - Whether to automatically start the clock when + * `getDelta()` is called for the first time. + */ + constructor( autoStart = true ) { - scope.domElement.setPointerCapture( event.pointerId ); + /** + * If set to `true`, the clock starts automatically when `getDelta()` is called + * for the first time. + * + * @type {boolean} + * @default true + */ + this.autoStart = autoStart; - scope.domElement.addEventListener( 'pointermove', onPointerMove ); - scope.domElement.addEventListener( 'pointerup', onPointerUp ); + /** + * Holds the time at which the clock's `start()` method was last called. + * + * @type {number} + * @default 0 + */ + this.startTime = 0; - } + /** + * Holds the time at which the clock's `start()`, `getElapsedTime()` or + * `getDelta()` methods were last called. + * + * @type {number} + * @default 0 + */ + this.oldTime = 0; - // + /** + * Keeps track of the total time that the clock has been running. + * + * @type {number} + * @default 0 + */ + this.elapsedTime = 0; - if ( isTrackingPointer( event ) ) return; + /** + * Whether the clock is running or not. + * + * @type {boolean} + * @default true + */ + this.running = false; - // + } - addPointer( event ); + /** + * Starts the clock. When `autoStart` is set to `true`, the method is automatically + * called by the class. + */ + start() { - if ( event.pointerType === 'touch' ) { + this.startTime = performance.now(); - onTouchStart( event ); + this.oldTime = this.startTime; + this.elapsedTime = 0; + this.running = true; - } else { + } - onMouseDown( event ); + /** + * Stops the clock. + */ + stop() { - } + this.getElapsedTime(); + this.running = false; + this.autoStart = false; - } + } - function onPointerMove( event ) { + /** + * Returns the elapsed time in seconds. + * + * @return {number} The elapsed time. + */ + getElapsedTime() { - if ( scope.enabled === false ) return; + this.getDelta(); + return this.elapsedTime; - if ( event.pointerType === 'touch' ) { + } - onTouchMove( event ); + /** + * Returns the delta time in seconds. + * + * @return {number} The delta time. + */ + getDelta() { - } else { + let diff = 0; - onMouseMove( event ); + if ( this.autoStart && ! this.running ) { - } + this.start(); + return 0; } - function onPointerUp( event ) { + if ( this.running ) { - removePointer( event ); + const newTime = performance.now(); - switch ( pointers.length ) { + diff = ( newTime - this.oldTime ) / 1000; + this.oldTime = newTime; - case 0: + this.elapsedTime += diff; - scope.domElement.releasePointerCapture( event.pointerId ); + } - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + return diff; - scope.dispatchEvent( _endEvent ); + } - state = STATE.NONE; +} - break; +/** + * This class can be used to represent points in 3D space as + * [Spherical coordinates]{@link https://en.wikipedia.org/wiki/Spherical_coordinate_system}. + */ +class Spherical { - case 1: + /** + * Constructs a new spherical. + * + * @param {number} [radius=1] - The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * @param {number} [phi=0] - The polar angle in radians from the y (up) axis. + * @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis. + */ + constructor( radius = 1, phi = 0, theta = 0 ) { - const pointerId = pointers[ 0 ]; - const position = pointerPositions[ pointerId ]; + /** + * The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * + * @type {number} + * @default 1 + */ + this.radius = radius; - // minimal placeholder event - allows state correction on pointer-up - onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); + /** + * The polar angle in radians from the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.phi = phi; - break; + /** + * The equator/azimuthal angle in radians around the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; - } + } - } + /** + * Sets the spherical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} phi - The polar angle. + * @param {number} theta - The azimuthal angle. + * @return {Spherical} A reference to this spherical. + */ + set( radius, phi, theta ) { - function onMouseDown( event ) { + this.radius = radius; + this.phi = phi; + this.theta = theta; - let mouseAction; + return this; - switch ( event.button ) { + } - case 0: + /** + * Copies the values of the given spherical to this instance. + * + * @param {Spherical} other - The spherical to copy. + * @return {Spherical} A reference to this spherical. + */ + copy( other ) { - mouseAction = scope.mouseButtons.LEFT; - break; + this.radius = other.radius; + this.phi = other.phi; + this.theta = other.theta; - case 1: + return this; - mouseAction = scope.mouseButtons.MIDDLE; - break; + } - case 2: + /** + * Restricts the polar angle [page:.phi phi] to be between `0.000001` and pi - + * `0.000001`. + * + * @return {Spherical} A reference to this spherical. + */ + makeSafe() { - mouseAction = scope.mouseButtons.RIGHT; - break; + const EPS = 0.000001; + this.phi = clamp( this.phi, EPS, Math.PI - EPS ); - default: + return this; - mouseAction = - 1; + } - } + /** + * Sets the spherical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Spherical} A reference to this spherical. + */ + setFromVector3( v ) { - switch ( mouseAction ) { + return this.setFromCartesianCoords( v.x, v.y, v.z ); - case MOUSE.DOLLY: + } - if ( scope.enableZoom === false ) return; + /** + * Sets the spherical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The y value. + * @param {number} z - The z value. + * @return {Spherical} A reference to this spherical. + */ + setFromCartesianCoords( x, y, z ) { - handleMouseDownDolly( event ); + this.radius = Math.sqrt( x * x + y * y + z * z ); - state = STATE.DOLLY; + if ( this.radius === 0 ) { - break; + this.theta = 0; + this.phi = 0; - case MOUSE.ROTATE: + } else { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this.theta = Math.atan2( x, z ); + this.phi = Math.acos( clamp( y / this.radius, -1, 1 ) ); - if ( scope.enablePan === false ) return; + } - handleMouseDownPan( event ); + return this; - state = STATE.PAN; + } - } else { + /** + * Returns a new spherical with copied values from this instance. + * + * @return {Spherical} A clone of this instance. + */ + clone() { - if ( scope.enableRotate === false ) return; + return new this.constructor().copy( this ); - handleMouseDownRotate( event ); + } - state = STATE.ROTATE; +} - } +const _vector = /*@__PURE__*/ new Vector2(); - break; +/** + * Represents an axis-aligned bounding box (AABB) in 2D space. + */ +class Box2 { - case MOUSE.PAN: + /** + * Constructs a new bounding box. + * + * @param {Vector2} [min=(Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector2} [max=(-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ + constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBox2 = true; - if ( scope.enableRotate === false ) return; + /** + * The lower boundary of the box. + * + * @type {Vector2} + */ + this.min = min; - handleMouseDownRotate( event ); + /** + * The upper boundary of the box. + * + * @type {Vector2} + */ + this.max = max; - state = STATE.ROTATE; + } - } else { + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector2} min - The lower boundary of the box. + * @param {Vector2} max - The upper boundary of the box. + * @return {Box2} A reference to this bounding box. + */ + set( min, max ) { - if ( scope.enablePan === false ) return; + this.min.copy( min ); + this.max.copy( max ); - handleMouseDownPan( event ); + return this; - state = STATE.PAN; + } - } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 2D position data as instances of {@link Vector2}. + * @return {Box2} A reference to this bounding box. + */ + setFromPoints( points ) { - break; + this.makeEmpty(); - default: + for ( let i = 0, il = points.length; i < il; i ++ ) { - state = STATE.NONE; + this.expandByPoint( points[ i ] ); - } + } - if ( state !== STATE.NONE ) { + return this; - scope.dispatchEvent( _startEvent ); + } - } + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector2} center - The center of the box. + * @param {Vector2} size - The x and y dimensions of the box. + * @return {Box2} A reference to this bounding box. + */ + setFromCenterAndSize( center, size ) { - } + const halfSize = _vector.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); - function onMouseMove( event ) { + return this; - switch ( state ) { + } - case STATE.ROTATE: + /** + * Returns a new box with copied values from this instance. + * + * @return {Box2} A clone of this instance. + */ + clone() { - if ( scope.enableRotate === false ) return; + return new this.constructor().copy( this ); - handleMouseMoveRotate( event ); + } - break; + /** + * Copies the values of the given box to this instance. + * + * @param {Box2} box - The box to copy. + * @return {Box2} A reference to this bounding box. + */ + copy( box ) { - case STATE.DOLLY: + this.min.copy( box.min ); + this.max.copy( box.max ); - if ( scope.enableZoom === false ) return; + return this; - handleMouseMoveDolly( event ); + } - break; + /** + * Makes this box empty which means in encloses a zero space in 2D. + * + * @return {Box2} A reference to this bounding box. + */ + makeEmpty() { - case STATE.PAN: + this.min.x = this.min.y = + Infinity; + this.max.x = this.max.y = - Infinity; - if ( scope.enablePan === false ) return; + return this; - handleMouseMovePan( event ); + } - break; + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ + isEmpty() { - } + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - } + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - function onMouseWheel( event ) { + } - if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + /** + * Returns the center point of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The center point. + */ + getCenter( target ) { - event.preventDefault(); + return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - scope.dispatchEvent( _startEvent ); + } - handleMouseWheel( customWheelEvent( event ) ); + /** + * Returns the dimensions of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The size. + */ + getSize( target ) { - scope.dispatchEvent( _endEvent ); + return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); - } + } - function customWheelEvent( event ) { + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector2} point - The point that should be included by the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByPoint( point ) { - const mode = event.deltaMode; + this.min.min( point ); + this.max.max( point ); - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; + return this; - switch ( mode ) { + } - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. + * + * @param {Vector2} vector - The vector that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByVector( vector ) { - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; + this.min.sub( vector ); + this.max.add( vector ); - } + return this; - // detect if event was triggered by pinching - if ( event.ctrlKey && ! controlActive ) { + } - newEvent.deltaY *= 10; + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByScalar( scalar ) { - } + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); - return newEvent; + return this; - } + } - function interceptControlDown( event ) { + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector2} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ + containsPoint( point ) { - if ( event.key === 'Control' ) { + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y; - controlActive = true; + } + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ + containsBox( box ) { - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y; - document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); + } - } + /** + * Returns a point as a proportion of this box's width and height. + * + * @param {Vector2} point - A point in 2D space. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} A point as a proportion of this box's width and height. + */ + getParameter( point, target ) { - } + // This can potentially have a divide by zero if the box + // has a size dimension of 0. - function interceptControlUp( event ) { + return target.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); - if ( event.key === 'Control' ) { + } - controlActive = false; + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ + intersectsBox( box ) { + // using 4 splitting planes to rule out intersections - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y; - document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); + } - } + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector2} point - The point to clamp. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The clamped point. + */ + clampPoint( point, target ) { - } + return target.copy( point ).clamp( this.min, this.max ); - function onKeyDown( event ) { + } - if ( scope.enabled === false || scope.enablePan === false ) return; + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector2} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ + distanceToPoint( point ) { - handleKeyDown( event ); + return this.clampPoint( point, _vector ).distanceTo( point ); - } + } - function onTouchStart( event ) { + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box2} box - The bounding box to intersect with. + * @return {Box2} A reference to this bounding box. + */ + intersect( box ) { - trackPointer( event ); + this.min.max( box.min ); + this.max.min( box.max ); - switch ( pointers.length ) { + if ( this.isEmpty() ) this.makeEmpty(); - case 1: + return this; - switch ( scope.touches.ONE ) { + } - case TOUCH.ROTATE: + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box2} box - The bounding box that will be unioned with this instance. + * @return {Box2} A reference to this bounding box. + */ + union( box ) { - if ( scope.enableRotate === false ) return; + this.min.min( box.min ); + this.max.max( box.max ); - handleTouchStartRotate( event ); + return this; - state = STATE.TOUCH_ROTATE; + } - break; + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 2D space. + * + * @param {Vector2} offset - The offset that should be used to translate the bounding box. + * @return {Box2} A reference to this bounding box. + */ + translate( offset ) { - case TOUCH.PAN: + this.min.add( offset ); + this.max.add( offset ); - if ( scope.enablePan === false ) return; + return this; - handleTouchStartPan( event ); + } - state = STATE.TOUCH_PAN; + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box2} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ + equals( box ) { - break; + return box.min.equals( this.min ) && box.max.equals( this.max ); - default: + } - state = STATE.NONE; +} - } +const _startP = /*@__PURE__*/ new Vector3(); +const _startEnd = /*@__PURE__*/ new Vector3(); - break; +const _d1 = /*@__PURE__*/ new Vector3(); +const _d2 = /*@__PURE__*/ new Vector3(); +const _r = /*@__PURE__*/ new Vector3(); +const _c1 = /*@__PURE__*/ new Vector3(); +const _c2 = /*@__PURE__*/ new Vector3(); - case 2: +/** + * An analytical line segment in 3D space represented by a start and end point. + */ +class Line3 { - switch ( scope.touches.TWO ) { + /** + * Constructs a new line segment. + * + * @param {Vector3} [start=(0,0,0)] - Start of the line segment. + * @param {Vector3} [end=(0,0,0)] - End of the line segment. + */ + constructor( start = new Vector3(), end = new Vector3() ) { - case TOUCH.DOLLY_PAN: + /** + * Start of the line segment. + * + * @type {Vector3} + */ + this.start = start; - if ( scope.enableZoom === false && scope.enablePan === false ) return; + /** + * End of the line segment. + * + * @type {Vector3} + */ + this.end = end; - handleTouchStartDollyPan( event ); + } - state = STATE.TOUCH_DOLLY_PAN; + /** + * Sets the start and end values by copying the given vectors. + * + * @param {Vector3} start - The start point. + * @param {Vector3} end - The end point. + * @return {Line3} A reference to this line segment. + */ + set( start, end ) { - break; + this.start.copy( start ); + this.end.copy( end ); - case TOUCH.DOLLY_ROTATE: + return this; - if ( scope.enableZoom === false && scope.enableRotate === false ) return; + } - handleTouchStartDollyRotate( event ); + /** + * Copies the values of the given line segment to this instance. + * + * @param {Line3} line - The line segment to copy. + * @return {Line3} A reference to this line segment. + */ + copy( line ) { - state = STATE.TOUCH_DOLLY_ROTATE; + this.start.copy( line.start ); + this.end.copy( line.end ); - break; + return this; - default: + } - state = STATE.NONE; + /** + * Returns the center of the line segment. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ + getCenter( target ) { - } + return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - break; + } - default: + /** + * Returns the delta vector of the line segment's start and end point. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + delta( target ) { - state = STATE.NONE; + return target.subVectors( this.end, this.start ); - } + } - if ( state !== STATE.NONE ) { + /** + * Returns the squared Euclidean distance between the line' start and end point. + * + * @return {number} The squared Euclidean distance. + */ + distanceSq() { - scope.dispatchEvent( _startEvent ); + return this.start.distanceToSquared( this.end ); - } + } - } + /** + * Returns the Euclidean distance between the line' start and end point. + * + * @return {number} The Euclidean distance. + */ + distance() { + + return this.start.distanceTo( this.end ); - function onTouchMove( event ) { + } - trackPointer( event ); + /** + * Returns a vector at a certain position along the line segment. + * + * @param {number} t - A value between `[0,1]` to represent a position along the line segment. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + at( t, target ) { - switch ( state ) { + return this.delta( target ).multiplyScalar( t ).add( this.start ); - case STATE.TOUCH_ROTATE: + } - if ( scope.enableRotate === false ) return; + /** + * Returns a point parameter based on the closest point as projected on the line segment. + * + * @param {Vector3} point - The point for which to return a point parameter. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @return {number} The point parameter. + */ + closestPointToPointParameter( point, clampToLine ) { - handleTouchMoveRotate( event ); + _startP.subVectors( point, this.start ); + _startEnd.subVectors( this.end, this.start ); - scope.update(); + const startEnd2 = _startEnd.dot( _startEnd ); + const startEnd_startP = _startEnd.dot( _startP ); - break; + let t = startEnd_startP / startEnd2; - case STATE.TOUCH_PAN: + if ( clampToLine ) { - if ( scope.enablePan === false ) return; + t = clamp( t, 0, 1 ); - handleTouchMovePan( event ); + } - scope.update(); + return t; - break; + } - case STATE.TOUCH_DOLLY_PAN: + /** + * Returns the closest point on the line for a given point. + * + * @param {Vector3} point - The point to compute the closest point on the line for. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the line. + */ + closestPointToPoint( point, clampToLine, target ) { - if ( scope.enableZoom === false && scope.enablePan === false ) return; + const t = this.closestPointToPointParameter( point, clampToLine ); - handleTouchMoveDollyPan( event ); + return this.delta( target ).multiplyScalar( t ).add( this.start ); - scope.update(); + } - break; + /** + * Returns the closest squared distance between this line segment and the given one. + * + * @param {Line3} line - The line segment to compute the closest squared distance to. + * @param {Vector3} [c1] - The closest point on this line segment. + * @param {Vector3} [c2] - The closest point on the given line segment. + * @return {number} The squared distance between this line segment and the given one. + */ + distanceSqToLine3( line, c1 = _c1, c2 = _c2 ) { - case STATE.TOUCH_DOLLY_ROTATE: + // from Real-Time Collision Detection by Christer Ericson, chapter 5.1.9 - if ( scope.enableZoom === false && scope.enableRotate === false ) return; + // Computes closest points C1 and C2 of S1(s)=P1+s*(Q1-P1) and + // S2(t)=P2+t*(Q2-P2), returning s and t. Function result is squared + // distance between between S1(s) and S2(t) - handleTouchMoveDollyRotate( event ); + const EPSILON = 1e-8 * 1e-8; // must be squared since we compare squared length + let s, t; - scope.update(); + const p1 = this.start; + const p2 = line.start; + const q1 = this.end; + const q2 = line.end; - break; + _d1.subVectors( q1, p1 ); // Direction vector of segment S1 + _d2.subVectors( q2, p2 ); // Direction vector of segment S2 + _r.subVectors( p1, p2 ); - default: + const a = _d1.dot( _d1 ); // Squared length of segment S1, always nonnegative + const e = _d2.dot( _d2 ); // Squared length of segment S2, always nonnegative + const f = _d2.dot( _r ); - state = STATE.NONE; + // Check if either or both segments degenerate into points - } + if ( a <= EPSILON && e <= EPSILON ) { - } + // Both segments degenerate into points - function onContextMenu( event ) { + c1.copy( p1 ); + c2.copy( p2 ); - if ( scope.enabled === false ) return; + c1.sub( c2 ); - event.preventDefault(); + return c1.dot( c1 ); } - function addPointer( event ) { + if ( a <= EPSILON ) { - pointers.push( event.pointerId ); + // First segment degenerates into a point - } + s = 0; + t = f / e; // s = 0 => t = (b*s + f) / e = f / e + t = clamp( t, 0, 1 ); - function removePointer( event ) { - delete pointerPositions[ event.pointerId ]; + } else { - for ( let i = 0; i < pointers.length; i ++ ) { + const c = _d1.dot( _r ); - if ( pointers[ i ] == event.pointerId ) { + if ( e <= EPSILON ) { - pointers.splice( i, 1 ); - return; + // Second segment degenerates into a point - } + t = 0; + s = clamp( - c / a, 0, 1 ); // t = 0 => s = (b*t - c) / a = -c / a - } + } else { - } + // The general nondegenerate case starts here - function isTrackingPointer( event ) { + const b = _d1.dot( _d2 ); + const denom = a * e - b * b; // Always nonnegative - for ( let i = 0; i < pointers.length; i ++ ) { + // If segments not parallel, compute closest point on L1 to L2 and + // clamp to segment S1. Else pick arbitrary s (here 0) - if ( pointers[ i ] == event.pointerId ) return true; + if ( denom !== 0 ) { - } + s = clamp( ( b * f - c * e ) / denom, 0, 1 ); - return false; + } else { - } + s = 0; - function trackPointer( event ) { + } - let position = pointerPositions[ event.pointerId ]; + // Compute point on L2 closest to S1(s) using + // t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e - if ( position === undefined ) { + t = ( b * s + f ) / e; - position = new Vector2(); - pointerPositions[ event.pointerId ] = position; + // If t in [0,1] done. Else clamp t, recompute s for the new value + // of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a + // and clamp s to [0, 1] - } + if ( t < 0 ) { - position.set( event.pageX, event.pageY ); + t = 0.; + s = clamp( - c / a, 0, 1 ); - } + } else if ( t > 1 ) { - function getSecondPointerPosition( event ) { + t = 1; + s = clamp( ( b - c ) / a, 0, 1 ); - const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ]; + } - return pointerPositions[ pointerId ]; + } } - // + c1.copy( p1 ).add( _d1.multiplyScalar( s ) ); + c2.copy( p2 ).add( _d2.multiplyScalar( t ) ); - scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + c1.sub( c2 ); - scope.domElement.addEventListener( 'pointerdown', onPointerDown ); - scope.domElement.addEventListener( 'pointercancel', onPointerUp ); - scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + return c1.dot( c1 ); - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + } - document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); + /** + * Applies a 4x4 transformation matrix to this line segment. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Line3} A reference to this line segment. + */ + applyMatrix4( matrix ) { - // force an update at start + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); - this.update(); + return this; } -} - -/** - * Full-screen textured quad shader - */ - -const CopyShader = { + /** + * Returns `true` if this line segment is equal with the given one. + * + * @param {Line3} line - The line segment to test for equality. + * @return {boolean} Whether this line segment is equal with the given one. + */ + equals( line ) { - name: 'CopyShader', + return line.start.equals( this.start ) && line.end.equals( this.end ); - uniforms: { + } - 'tDiffuse': { value: null }, - 'opacity': { value: 1.0 } + /** + * Returns a new line segment with copied values from this instance. + * + * @return {Line3} A clone of this instance. + */ + clone() { - }, + return new this.constructor().copy( this ); - vertexShader: /* glsl */` + } - varying vec2 vUv; +} - void main() { +/** + * A helper object to visualize an instance of {@link Plane}. + * + * ```js + * const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); + * const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); + * scene.add( helper ); + * ``` + * + * @augments Line + */ +class PlaneHelper extends Line$1 { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + /** + * Constructs a new plane helper. + * + * @param {Plane} plane - The plane to be visualized. + * @param {number} [size=1] - The side length of plane helper. + * @param {number|Color|string} [hex=0xffff00] - The helper's color. + */ + constructor( plane, size = 1, hex = 0xffff00 ) { - }`, + const color = hex; - fragmentShader: /* glsl */` + const positions = [ 1, -1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0 ]; - uniform float opacity; + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); - uniform sampler2D tDiffuse; + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - varying vec2 vUv; + this.type = 'PlaneHelper'; - void main() { + /** + * The plane being visualized. + * + * @type {Plane} + */ + this.plane = plane; - vec4 texel = texture2D( tDiffuse, vUv ); - gl_FragColor = opacity * texel; + /** + * The side length of plane helper. + * + * @type {number} + * @default 1 + */ + this.size = size; + const positions2 = [ 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0 ]; - }` + const geometry2 = new BufferGeometry(); + geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); -}; + this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); -class Pass { + } - constructor() { + updateMatrixWorld( force ) { - this.isPass = true; + this.position.set( 0, 0, 0 ); - // if set to true, the pass is processed by the composer - this.enabled = true; + this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); - // if set to true, the pass indicates to swap read and write buffer after rendering - this.needsSwap = true; + this.lookAt( this.plane.normal ); - // if set to true, the pass clears its buffer before rendering - this.clear = false; + this.translateZ( - this.plane.constant ); - // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. - this.renderToScreen = false; + super.updateMatrixWorld( force ); } - setSize( /* width, height */ ) {} - - render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + dispose() { - console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); + this.geometry.dispose(); + this.material.dispose(); + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); } - dispose() {} - } -// Helper for passes that need to fill the viewport with a single quad. - -const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - -// https://github.com/mrdoob/three.js/pull/21358 - -class FullscreenTriangleGeometry extends BufferGeometry { +/** + * This class is used to convert a series of paths to an array of + * shapes. It is specifically used in context of fonts and SVG. + */ +class ShapePath { + /** + * Constructs a new shape path. + */ constructor() { - super(); + this.type = 'ShapePath'; - this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); + /** + * The color of the shape. + * + * @type {Color} + */ + this.color = new Color(); - } + /** + * The paths that have been generated for this shape. + * + * @type {Array} + * @default null + */ + this.subPaths = []; -} + /** + * The current path that is being generated. + * + * @type {?Path} + * @default null + */ + this.currentPath = null; -const _geometry = new FullscreenTriangleGeometry(); + } -class FullScreenQuad { + /** + * Creates a new path and moves it current point to the given one. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {ShapePath} A reference to this shape path. + */ + moveTo( x, y ) { - constructor( material ) { + this.currentPath = new Path$1(); + this.subPaths.push( this.currentPath ); + this.currentPath.moveTo( x, y ); - this._mesh = new Mesh( _geometry, material ); + return this; } - dispose() { + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + lineTo( x, y ) { - this._mesh.geometry.dispose(); + this.currentPath.lineTo( x, y ); + + return this; } - render( renderer ) { + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { - renderer.render( this._mesh, _camera ); + this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + + return this; } - get material() { + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - return this._mesh.material; + this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + + return this; } - set material( value ) { + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {ShapePath} A reference to this shape path. + */ + splineThru( pts ) { - this._mesh.material = value; + this.currentPath.splineThru( pts ); + + return this; } -} + /** + * Converts the paths into an array of shapes. + * + * @param {boolean} isCCW - By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). + * If this flag is set to `true`, then those are flipped. + * @return {Array} An array of shapes. + */ + toShapes( isCCW ) { -class ShaderPass extends Pass { + function toShapesNoHoles( inSubpaths ) { - constructor( shader, textureID ) { + const shapes = []; - super(); + for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { - this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; + const tmpPath = inSubpaths[ i ]; - if ( shader instanceof ShaderMaterial ) { + const tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; - this.uniforms = shader.uniforms; + shapes.push( tmpShape ); - this.material = shader; + } - } else if ( shader ) { + return shapes; - this.uniforms = UniformsUtils.clone( shader.uniforms ); + } - this.material = new ShaderMaterial( { + function isPointInsidePolygon( inPt, inPolygon ) { - name: ( shader.name !== undefined ) ? shader.name : 'unspecified', - defines: Object.assign( {}, shader.defines ), - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + const polyLen = inPolygon.length; - } ); + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + let inside = false; + for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { - } + let edgeLowPt = inPolygon[ p ]; + let edgeHighPt = inPolygon[ q ]; - this.fsQuad = new FullScreenQuad( this.material ); + let edgeDx = edgeHighPt.x - edgeLowPt.x; + let edgeDy = edgeHighPt.y - edgeLowPt.y; - } + if ( Math.abs( edgeDy ) > Number.EPSILON ) { - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + // not parallel + if ( edgeDy < 0 ) { - if ( this.uniforms[ this.textureID ] ) { + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; - this.uniforms[ this.textureID ].value = readBuffer.texture; + } - } + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; - this.fsQuad.material = this.material; + if ( inPt.y === edgeLowPt.y ) { - if ( this.renderToScreen ) { + if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + } else { - } else { + const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); + if ( perpEdge === 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = ! inside; // true intersection left of inPt - renderer.setRenderTarget( writeBuffer ); - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.fsQuad.render( renderer ); + } - } + } else { - } + // parallel or collinear + if ( inPt.y !== edgeLowPt.y ) continue; // parallel + // edge lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; - dispose() { + } - this.material.dispose(); + } - this.fsQuad.dispose(); + return inside; - } + } -} + const isClockWise = ShapeUtils.isClockWise; -class MaskPass extends Pass { + const subPaths = this.subPaths; + if ( subPaths.length === 0 ) return []; - constructor( scene, camera ) { + let solid, tmpPath, tmpShape; + const shapes = []; - super(); + if ( subPaths.length === 1 ) { - this.scene = scene; - this.camera = camera; + tmpPath = subPaths[ 0 ]; + tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; - this.clear = true; - this.needsSwap = false; + } - this.inverse = false; + let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; - } + // console.log("Holes first", holesFirst); - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + const betterShapeHoles = []; + const newShapes = []; + let newShapeHoles = []; + let mainIdx = 0; + let tmpPoints; - const context = renderer.getContext(); - const state = renderer.state; + newShapes[ mainIdx ] = undefined; + newShapeHoles[ mainIdx ] = []; - // don't update color or depth + for ( let i = 0, l = subPaths.length; i < l; i ++ ) { - state.buffers.color.setMask( false ); - state.buffers.depth.setMask( false ); + tmpPath = subPaths[ i ]; + tmpPoints = tmpPath.getPoints(); + solid = isClockWise( tmpPoints ); + solid = isCCW ? ! solid : solid; - // lock buffers + if ( solid ) { - state.buffers.color.setLocked( true ); - state.buffers.depth.setLocked( true ); + if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; - // set up stencil + newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; + newShapes[ mainIdx ].s.curves = tmpPath.curves; - let writeValue, clearValue; + if ( holesFirst ) mainIdx ++; + newShapeHoles[ mainIdx ] = []; - if ( this.inverse ) { + //console.log('cw', i); - writeValue = 0; - clearValue = 1; + } else { - } else { + newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); - writeValue = 1; - clearValue = 0; + //console.log('ccw', i); + + } } - state.buffers.stencil.setTest( true ); - state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE ); - state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff ); - state.buffers.stencil.setClear( clearValue ); - state.buffers.stencil.setLocked( true ); + // only Holes? -> probably all Shapes with wrong orientation + if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); - // draw into the stencil buffer - renderer.setRenderTarget( readBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); + if ( newShapes.length > 1 ) { - renderer.setRenderTarget( writeBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); + let ambiguous = false; + let toChange = 0; - // unlock color and depth buffer and make them writable for subsequent rendering/clearing + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - state.buffers.color.setLocked( false ); - state.buffers.depth.setLocked( false ); + betterShapeHoles[ sIdx ] = []; - state.buffers.color.setMask( true ); - state.buffers.depth.setMask( true ); + } - // only render where stencil is set to 1 + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - state.buffers.stencil.setLocked( false ); - state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 - state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP ); - state.buffers.stencil.setLocked( true ); + const sho = newShapeHoles[ sIdx ]; - } + for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { -} + const ho = sho[ hIdx ]; + let hole_unassigned = true; -class ClearMaskPass extends Pass { + for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { - constructor() { + if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { - super(); + if ( sIdx !== s2Idx ) toChange ++; - this.needsSwap = false; + if ( hole_unassigned ) { - } + hole_unassigned = false; + betterShapeHoles[ s2Idx ].push( ho ); - render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { + } else { - renderer.state.buffers.stencil.setLocked( false ); - renderer.state.buffers.stencil.setTest( false ); + ambiguous = true; - } + } -} + } -class EffectComposer { + } - constructor( renderer, renderTarget ) { + if ( hole_unassigned ) { - this.renderer = renderer; + betterShapeHoles[ sIdx ].push( ho ); - this._pixelRatio = renderer.getPixelRatio(); + } - if ( renderTarget === undefined ) { + } - const size = renderer.getSize( new Vector2() ); - this._width = size.width; - this._height = size.height; + } - renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); - renderTarget.texture.name = 'EffectComposer.rt1'; + if ( toChange > 0 && ambiguous === false ) { - } else { + newShapeHoles = betterShapeHoles; - this._width = renderTarget.width; - this._height = renderTarget.height; + } } - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); - this.renderTarget2.texture.name = 'EffectComposer.rt2'; + let tmpHoles; - this.writeBuffer = this.renderTarget1; - this.readBuffer = this.renderTarget2; + for ( let i = 0, il = newShapes.length; i < il; i ++ ) { - this.renderToScreen = true; + tmpShape = newShapes[ i ].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[ i ]; - this.passes = []; + for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { - this.copyPass = new ShaderPass( CopyShader ); - this.copyPass.material.blending = NoBlending; + tmpShape.holes.push( tmpHoles[ j ].h ); - this.clock = new Clock(); + } - } + } - swapBuffers() { + //console.log("shape", shapes); - const tmp = this.readBuffer; - this.readBuffer = this.writeBuffer; - this.writeBuffer = tmp; + return shapes; } - addPass( pass ) { +} - this.passes.push( pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); +/** + * Abstract base class for controls. + * + * @abstract + * @augments EventDispatcher + */ +class Controls extends EventDispatcher { - } + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { - insertPass( pass, index ) { + super(); - this.passes.splice( index, 0, pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + /** + * The object that is managed by the controls. + * + * @type {Object3D} + */ + this.object = object; - } + /** + * The HTML element used for event listeners. + * + * @type {?HTMLDOMElement} + * @default null + */ + this.domElement = domElement; - removePass( pass ) { + /** + * Whether the controls responds to user input or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; - const index = this.passes.indexOf( pass ); + /** + * The internal state of the controls. + * + * @type {number} + * @default -1 + */ + this.state = -1; - if ( index !== - 1 ) { + /** + * This object defines the keyboard input of the controls. + * + * @type {Object} + */ + this.keys = {}; - this.passes.splice( index, 1 ); + /** + * This object defines what type of actions are assigned to the available mouse buttons. + * It depends on the control implementation what kind of mouse buttons and actions are supported. + * + * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}} + */ + this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null }; - } + /** + * This object defines what type of actions are assigned to what kind of touch interaction. + * It depends on the control implementation what kind of touch interaction and actions are supported. + * + * @type {{ONE: ?number, TWO: ?number}} + */ + this.touches = { ONE: null, TWO: null }; } - isLastEnabledPass( passIndex ) { - - for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { - - if ( this.passes[ i ].enabled ) { + /** + * Connects the controls to the DOM. This method has so called "side effects" since + * it adds the module's event listeners to the DOM. + * + * @param {HTMLDOMElement} element - The DOM element to connect to. + */ + connect( element ) { - return false; + if ( element === undefined ) { - } + console.warn( 'THREE.Controls: connect() now requires an element.' ); // @deprecated, the warning can be removed with r185 + return; } - return true; + if ( this.domElement !== null ) this.disconnect(); - } + this.domElement = element; - render( deltaTime ) { + } - // deltaTime value is in seconds + /** + * Disconnects the controls from the DOM. + */ + disconnect() {} - if ( deltaTime === undefined ) { + /** + * Call this method if you no longer want use to the controls. It frees all internal + * resources and removes all event listeners. + */ + dispose() {} - deltaTime = this.clock.getDelta(); + /** + * Controls should implement this method if they have to update their internal state + * per simulation step. + * + * @param {number} [delta] - The time delta in seconds. + */ + update( /* delta */ ) {} - } +} - const currentRenderTarget = this.renderer.getRenderTarget(); +// export * from './Three.Legacy.js'; - let maskActive = false; +if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - for ( let i = 0, il = this.passes.length; i < il; i ++ ) { + /* eslint-disable no-undef */ + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { + revision: REVISION, + } } ) ); + /* eslint-enable no-undef */ - const pass = this.passes[ i ]; +} - if ( pass.enabled === false ) continue; +if ( typeof window !== 'undefined' ) { - pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); - pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); + if ( window.__THREE__ ) { - if ( pass.needsSwap ) { + console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); - if ( maskActive ) { + } else { - const context = this.renderer.getContext(); - const stencil = this.renderer.state.buffers.stencil; + window.__THREE__ = REVISION; - //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); - stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); + } - this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); +} - //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); - stencil.setFunc( context.EQUAL, 1, 0xffffffff ); +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ - } +/** + * A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of + * parameters consisting of a loaded font and extrude settings. + * + * See the {@link FontLoader} page for additional details. + * + * `TextGeometry` uses [typeface.json]{@link http://gero3.github.io/facetype.js/} generated fonts. + * Some existing fonts can be found located in `/examples/fonts`. + * + * ```js + * const loader = new FontLoader(); + * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' ); + * const geometry = new TextGeometry( 'Hello three.js!', { + * font: font, + * size: 80, + * depth: 5, + * curveSegments: 12 + * } ); + * ``` + * + * @augments ExtrudeGeometry + * @three_import import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; + */ +class TextGeometry extends ExtrudeGeometry { - this.swapBuffers(); + /** + * Constructs a new text geometry. + * + * @param {string} text - The text that should be transformed into a geometry. + * @param {TextGeometry~Options} [parameters] - The text settings. + */ + constructor( text, parameters = {} ) { - } + const font = parameters.font; - if ( MaskPass !== undefined ) { + if ( font === undefined ) { - if ( pass instanceof MaskPass ) { + super(); // generate default extrude geometry - maskActive = true; + } else { - } else if ( pass instanceof ClearMaskPass ) { + const shapes = font.generateShapes( text, parameters.size ); - maskActive = false; + // defaults - } + if ( parameters.depth === undefined ) parameters.depth = 50; + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; - } + super( shapes, parameters ); } - this.renderer.setRenderTarget( currentRenderTarget ); + this.type = 'TextGeometry'; } - reset( renderTarget ) { - - if ( renderTarget === undefined ) { +} - const size = this.renderer.getSize( new Vector2() ); - this._pixelRatio = this.renderer.getPixelRatio(); - this._width = size.width; - this._height = size.height; +/** + * Class representing a font. + */ +class Font { - renderTarget = this.renderTarget1.clone(); - renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + /** + * Constructs a new font. + * + * @param {Object} data - The font data as JSON. + */ + constructor( data ) { - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFont = true; - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); + this.type = 'Font'; - this.writeBuffer = this.renderTarget1; - this.readBuffer = this.renderTarget2; + /** + * The font data as JSON. + * + * @type {Object} + */ + this.data = data; } - setSize( width, height ) { - - this._width = width; - this._height = height; - - const effectiveWidth = this._width * this._pixelRatio; - const effectiveHeight = this._height * this._pixelRatio; + /** + * Generates geometry shapes from the given text and size. The result of this method + * should be used with {@link ShapeGeometry} to generate the actual geometry data. + * + * @param {string} text - The text. + * @param {number} [size=100] - The text size. + * @return {Array} An array of shapes representing the text. + */ + generateShapes( text, size = 100 ) { - this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); - this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); + const shapes = []; + const paths = createPaths( text, size, this.data ); - for ( let i = 0; i < this.passes.length; i ++ ) { + for ( let p = 0, pl = paths.length; p < pl; p ++ ) { - this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); + shapes.push( ...paths[ p ].toShapes() ); } - } - - setPixelRatio( pixelRatio ) { - - this._pixelRatio = pixelRatio; - - this.setSize( this._width, this._height ); + return shapes; } - dispose() { +} - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); +function createPaths( text, size, data ) { - this.copyPass.dispose(); + const chars = Array.from( text ); + const scale = size / data.resolution; + const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; - } + const paths = []; -} + let offsetX = 0, offsetY = 0; -class RenderPass extends Pass { + for ( let i = 0; i < chars.length; i ++ ) { - constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { + const char = chars[ i ]; - super(); + if ( char === '\n' ) { - this.scene = scene; - this.camera = camera; + offsetX = 0; + offsetY -= line_height; - this.overrideMaterial = overrideMaterial; + } else { - this.clearColor = clearColor; - this.clearAlpha = clearAlpha; + const ret = createPath( char, scale, offsetX, offsetY, data ); + offsetX += ret.offsetX; + paths.push( ret.path ); - this.clear = true; - this.clearDepth = false; - this.needsSwap = false; - this._oldClearColor = new Color(); + } } - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + return paths; - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; +} - let oldClearAlpha, oldOverrideMaterial; +function createPath( char, scale, offsetX, offsetY, data ) { - if ( this.overrideMaterial !== null ) { + const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; - oldOverrideMaterial = this.scene.overrideMaterial; + if ( ! glyph ) { - this.scene.overrideMaterial = this.overrideMaterial; + console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); - } + return; - if ( this.clearColor !== null ) { + } - renderer.getClearColor( this._oldClearColor ); - renderer.setClearColor( this.clearColor ); + const path = new ShapePath(); - } + let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; - if ( this.clearAlpha !== null ) { + if ( glyph.o ) { - oldClearAlpha = renderer.getClearAlpha(); - renderer.setClearAlpha( this.clearAlpha ); + const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); - } + for ( let i = 0, l = outline.length; i < l; ) { - if ( this.clearDepth == true ) { + const action = outline[ i ++ ]; - renderer.clearDepth(); + switch ( action ) { - } + case 'm': // moveTo - renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; - if ( this.clear === true ) { + path.moveTo( x, y ); - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + break; - } + case 'l': // lineTo - renderer.render( this.scene, this.camera ); + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; - // restore + path.lineTo( x, y ); - if ( this.clearColor !== null ) { + break; - renderer.setClearColor( this._oldClearColor ); + case 'q': // quadraticCurveTo - } + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; - if ( this.clearAlpha !== null ) { + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); - renderer.setClearAlpha( oldClearAlpha ); + break; - } + case 'b': // bezierCurveTo - if ( this.overrideMaterial !== null ) { + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + cpx2 = outline[ i ++ ] * scale + offsetX; + cpy2 = outline[ i ++ ] * scale + offsetY; - this.scene.overrideMaterial = oldOverrideMaterial; + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); - } + break; - renderer.autoClear = oldAutoClear; + } + + } } + return { offsetX: glyph.ha * scale, path: path }; + } /** - * Luminosity - * http://en.wikipedia.org/wiki/Luminosity + * Fires when the camera has been transformed by the controls. + * + * @event OrbitControls#change + * @type {Object} */ +const _changeEvent = { type: 'change' }; -const LuminosityHighPassShader = { +/** + * Fires when an interaction was initiated. + * + * @event OrbitControls#start + * @type {Object} + */ +const _startEvent = { type: 'start' }; - name: 'LuminosityHighPassShader', +/** + * Fires when an interaction has finished. + * + * @event OrbitControls#end + * @type {Object} + */ +const _endEvent = { type: 'end' }; - shaderID: 'luminosityHighPass', +const _ray = new Ray(); +const _plane = new Plane(); +const _TILT_LIMIT = Math.cos( 70 * MathUtils$1.DEG2RAD ); + +const _v = new Vector3(); +const _twoPI = 2 * Math.PI; + +const _STATE = { + NONE: -1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 +}; +const _EPS = 0.000001; - uniforms: { - 'tDiffuse': { value: null }, - 'luminosityThreshold': { value: 1.0 }, - 'smoothWidth': { value: 1.0 }, - 'defaultColor': { value: new Color( 0x000000 ) }, - 'defaultOpacity': { value: 0.0 } +/** + * Orbit controls allow the camera to orbit around a target. + * + * OrbitControls performs orbiting, dollying (zooming), and panning. Unlike {@link TrackballControls}, + * it maintains the "up" direction `object.up` (+Y by default). + * + * - Orbit: Left mouse / touch: one-finger move. + * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. + * - Pan: Right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move. + * + * ```js + * const controls = new OrbitControls( camera, renderer.domElement ); + * + * // controls.update() must be called after any manual changes to the camera's transform + * camera.position.set( 0, 20, 100 ); + * controls.update(); + * + * function animate() { + * + * // required if controls.enableDamping or controls.autoRotate are set to true + * controls.update(); + * + * renderer.render( scene, camera ); + * + * } + * ``` + * + * @augments Controls + * @three_import import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + */ +class OrbitControls extends Controls { - }, + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { - vertexShader: /* glsl */` + super( object, domElement ); - varying vec2 vUv; + this.state = _STATE.NONE; - void main() { + /** + * The focus point of the controls, the `object` orbits around this. + * It can be updated manually at any point to change the focus of the controls. + * + * @type {Vector3} + */ + this.target = new Vector3(); - vUv = uv; + /** + * The focus point of the `minTargetRadius` and `maxTargetRadius` limits. + * It can be updated manually at any point to change the center of interest + * for the `target`. + * + * @type {Vector3} + */ + this.cursor = new Vector3(); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + /** + * How far you can dolly in (perspective camera only). + * + * @type {number} + * @default 0 + */ + this.minDistance = 0; - }`, + /** + * How far you can dolly out (perspective camera only). + * + * @type {number} + * @default Infinity + */ + this.maxDistance = Infinity; - fragmentShader: /* glsl */` + /** + * How far you can zoom in (orthographic camera only). + * + * @type {number} + * @default 0 + */ + this.minZoom = 0; - uniform sampler2D tDiffuse; - uniform vec3 defaultColor; - uniform float defaultOpacity; - uniform float luminosityThreshold; - uniform float smoothWidth; + /** + * How far you can zoom out (orthographic camera only). + * + * @type {number} + * @default Infinity + */ + this.maxZoom = Infinity; - varying vec2 vUv; + /** + * How close you can get the target to the 3D `cursor`. + * + * @type {number} + * @default 0 + */ + this.minTargetRadius = 0; - void main() { + /** + * How far you can move the target from the 3D `cursor`. + * + * @type {number} + * @default Infinity + */ + this.maxTargetRadius = Infinity; - vec4 texel = texture2D( tDiffuse, vUv ); + /** + * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default 0 + */ + this.minPolarAngle = 0; - vec3 luma = vec3( 0.299, 0.587, 0.114 ); + /** + * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default Math.PI + */ + this.maxPolarAngle = Math.PI; - float v = dot( texel.xyz, luma ); + /** + * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.minAzimuthAngle = - Infinity; - vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity ); + /** + * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.maxAzimuthAngle = Infinity; - float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v ); + /** + * Set to `true` to enable damping (inertia), which can be used to give a sense of weight + * to the controls. Note that if this is enabled, you must call `update()` in your animation + * loop. + * + * @type {boolean} + * @default false + */ + this.enableDamping = false; - gl_FragColor = mix( outputColor, texel, alpha ); + /** + * The damping inertia used if `enableDamping` is set to `true`. + * + * Note that for this to work, you must call `update()` in your animation loop. + * + * @type {number} + * @default 0.05 + */ + this.dampingFactor = 0.05; - }` + /** + * Enable or disable zooming (dollying) of the camera. + * + * @type {boolean} + * @default true + */ + this.enableZoom = true; -}; + /** + * Speed of zooming / dollying. + * + * @type {number} + * @default 1 + */ + this.zoomSpeed = 1.0; -/** - * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a - * mip map chain of bloom textures and blurs them with different radii. Because - * of the weighted combination of mips, and because larger blurs are done on - * higher mips, this effect provides good quality and performance. - * - * Reference: - * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ - */ -class UnrealBloomPass extends Pass { + /** + * Enable or disable horizontal and vertical rotation of the camera. + * + * Note that it is possible to disable a single axis by setting the min and max of the + * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical + * or horizontal rotation to be fixed at that value. + * + * @type {boolean} + * @default true + */ + this.enableRotate = true; - constructor( resolution, strength, radius, threshold ) { + /** + * Speed of rotation. + * + * @type {number} + * @default 1 + */ + this.rotateSpeed = 1.0; - super(); + /** + * How fast to rotate the camera when the keyboard is used. + * + * @type {number} + * @default 1 + */ + this.keyRotateSpeed = 1.0; - this.strength = ( strength !== undefined ) ? strength : 1; - this.radius = radius; - this.threshold = threshold; - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); + /** + * Enable or disable camera panning. + * + * @type {boolean} + * @default true + */ + this.enablePan = true; - // create color only once here, reuse it later inside the render function - this.clearColor = new Color( 0, 0, 0 ); + /** + * Speed of panning. + * + * @type {number} + * @default 1 + */ + this.panSpeed = 1.0; - // render targets - this.renderTargetsHorizontal = []; - this.renderTargetsVertical = []; - this.nMips = 5; - let resx = Math.round( this.resolution.x / 2 ); - let resy = Math.round( this.resolution.y / 2 ); + /** + * Defines how the camera's position is translated when panning. If `true`, the camera pans + * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up + * direction. + * + * @type {boolean} + * @default true + */ + this.screenSpacePanning = true; - this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; - this.renderTargetBright.texture.generateMipmaps = false; + /** + * How fast to pan the camera when the keyboard is used in + * pixels per keypress. + * + * @type {number} + * @default 7 + */ + this.keyPanSpeed = 7.0; - for ( let i = 0; i < this.nMips; i ++ ) { + /** + * Setting this property to `true` allows to zoom to the cursor's position. + * + * @type {boolean} + * @default false + */ + this.zoomToCursor = false; - const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + /** + * Set to true to automatically rotate around the target + * + * Note that if this is enabled, you must call `update()` in your animation loop. + * If you want the auto-rotate speed to be independent of the frame rate (the refresh + * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`. + * + * @type {boolean} + * @default false + */ + this.autoRotate = false; - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizonal.texture.generateMipmaps = false; + /** + * How fast to rotate around the target if `autoRotate` is `true`. The default equates to 30 seconds + * per orbit at 60fps. + * + * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop. + * + * @type {number} + * @default 2 + */ + this.autoRotateSpeed = 2.0; - this.renderTargetsHorizontal.push( renderTargetHorizonal ); + /** + * This object contains references to the keycodes for controlling camera panning. + * + * ```js + * controls.keys = { + * LEFT: 'ArrowLeft', //left arrow + * UP: 'ArrowUp', // up arrow + * RIGHT: 'ArrowRight', // right arrow + * BOTTOM: 'ArrowDown' // down arrow + * } + * ``` + * @type {Object} + */ + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + /** + * This object contains references to the mouse actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * LEFT: THREE.MOUSE.ROTATE, + * MIDDLE: THREE.MOUSE.DOLLY, + * RIGHT: THREE.MOUSE.PAN + * } + * ``` + * @type {Object} + */ + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; - renderTargetVertical.texture.generateMipmaps = false; + /** + * This object contains references to the touch actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * ONE: THREE.TOUCH.ROTATE, + * TWO: THREE.TOUCH.DOLLY_PAN + * } + * ``` + * @type {Object} + */ + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - this.renderTargetsVertical.push( renderTargetVertical ); + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ + this.target0 = this.target.clone(); - resx = Math.round( resx / 2 ); + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ + this.position0 = this.object.position.clone(); - resy = Math.round( resy / 2 ); + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {number} + */ + this.zoom0 = this.object.zoom; - } + // the target DOM element for key events + this._domElementKeyEvents = null; - // luminosity high pass material + // internals - const highPassShader = LuminosityHighPassShader; - this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); + this._lastPosition = new Vector3(); + this._lastQuaternion = new Quaternion(); + this._lastTargetPosition = new Vector3(); - this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; - this.highPassUniforms[ 'smoothWidth' ].value = 0.01; + // so camera.up is the orbit axis + this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + this._quatInverse = this._quat.clone().invert(); - this.materialHighPassFilter = new ShaderMaterial( { - uniforms: this.highPassUniforms, - vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader - } ); + // current position in spherical coordinates + this._spherical = new Spherical(); + this._sphericalDelta = new Spherical(); - // gaussian blur materials + this._scale = 1; + this._panOffset = new Vector3(); - this.separableBlurMaterials = []; - const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; - resx = Math.round( this.resolution.x / 2 ); - resy = Math.round( this.resolution.y / 2 ); + this._rotateStart = new Vector2(); + this._rotateEnd = new Vector2(); + this._rotateDelta = new Vector2(); - for ( let i = 0; i < this.nMips; i ++ ) { + this._panStart = new Vector2(); + this._panEnd = new Vector2(); + this._panDelta = new Vector2(); - this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); + this._dollyStart = new Vector2(); + this._dollyEnd = new Vector2(); + this._dollyDelta = new Vector2(); - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + this._dollyDirection = new Vector3(); + this._mouse = new Vector2(); + this._performCursorZoom = false; - resx = Math.round( resx / 2 ); + this._pointers = []; + this._pointerPositions = {}; - resy = Math.round( resy / 2 ); + this._controlActive = false; - } + // event listeners - // composite material + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); + this._onMouseWheel = onMouseWheel.bind( this ); + this._onKeyDown = onKeyDown.bind( this ); - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); - this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; + this._onTouchStart = onTouchStart.bind( this ); + this._onTouchMove = onTouchMove.bind( this ); - const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; - this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; - this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + this._onMouseDown = onMouseDown.bind( this ); + this._onMouseMove = onMouseMove.bind( this ); - // blend material + this._interceptControlDown = interceptControlDown.bind( this ); + this._interceptControlUp = interceptControlUp.bind( this ); - const copyShader = CopyShader; + // - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + if ( this.domElement !== null ) { - this.blendMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); + this.connect( this.domElement ); - this.enabled = true; - this.needsSwap = false; + } - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this.update(); + + } + + connect( element ) { + + super.connect( element ); + + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this._onPointerUp ); + + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); + this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); - this.basic = new MeshBasicMaterial(); + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } ); - this.fsQuad = new FullScreenQuad( null ); + this.domElement.style.touchAction = 'none'; // disable touch scroll } - dispose() { + disconnect() { - for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerUp ); - this.renderTargetsHorizontal[ i ].dispose(); + this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - } + this.stopListenToKeyEvents(); - for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } ); - this.renderTargetsVertical[ i ].dispose(); + this.domElement.style.touchAction = 'auto'; - } + } - this.renderTargetBright.dispose(); + dispose() { - // + this.disconnect(); - for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { + } - this.separableBlurMaterials[ i ].dispose(); + /** + * Get the current vertical rotation, in radians. + * + * @return {number} The current vertical rotation, in radians. + */ + getPolarAngle() { - } + return this._spherical.phi; - this.compositeMaterial.dispose(); - this.blendMaterial.dispose(); - this.basic.dispose(); + } - // + /** + * Get the current horizontal rotation, in radians. + * + * @return {number} The current horizontal rotation, in radians. + */ + getAzimuthalAngle() { - this.fsQuad.dispose(); + return this._spherical.theta; } - setSize( width, height ) { + /** + * Returns the distance from the camera to the target. + * + * @return {number} The distance from the camera to the target. + */ + getDistance() { - let resx = Math.round( width / 2 ); - let resy = Math.round( height / 2 ); + return this.object.position.distanceTo( this.target ); - this.renderTargetBright.setSize( resx, resy ); + } - for ( let i = 0; i < this.nMips; i ++ ) { + /** + * Adds key event listeners to the given DOM element. + * `window` is a recommended argument for using this method. + * + * @param {HTMLDOMElement} domElement - The DOM element + */ + listenToKeyEvents( domElement ) { - this.renderTargetsHorizontal[ i ].setSize( resx, resy ); - this.renderTargetsVertical[ i ].setSize( resx, resy ); + domElement.addEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = domElement; - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); + } - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); + /** + * Removes the key event listener previously defined with `listenToKeyEvents()`. + */ + stopListenToKeyEvents() { + + if ( this._domElementKeyEvents !== null ) { + + this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = null; } } - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { + /** + * Save the current state of the controls. This can later be recovered with `reset()`. + */ + saveState() { - renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; + this.target0.copy( this.target ); + this.position0.copy( this.object.position ); + this.zoom0 = this.object.zoom; - renderer.setClearColor( this.clearColor, 0 ); + } - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); + /** + * Reset the controls to their state from either the last time the `saveState()` + * was called, or the initial state. + */ + reset() { - // Render input to screen + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + this.object.zoom = this.zoom0; - if ( this.renderToScreen ) { + this.object.updateProjectionMatrix(); + this.dispatchEvent( _changeEvent ); - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + this.update(); - renderer.setRenderTarget( null ); - renderer.clear(); - this.fsQuad.render( renderer ); + this.state = _STATE.NONE; - } + } - // 1. Extract Bright Areas + update( deltaTime = null ) { - this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + const position = this.object.position; - renderer.setRenderTarget( this.renderTargetBright ); - renderer.clear(); - this.fsQuad.render( renderer ); + _v.copy( position ).sub( this.target ); - // 2. Blur All the mips progressively + // rotate offset to "y-axis-is-up" space + _v.applyQuaternion( this._quat ); - let inputRenderTarget = this.renderTargetBright; + // angle from z-axis around y-axis + this._spherical.setFromVector3( _v ); - for ( let i = 0; i < this.nMips; i ++ ) { + if ( this.autoRotate && this.state === _STATE.NONE ) { - this.fsQuad.material = this.separableBlurMaterials[ i ]; + this._rotateLeft( this._getAutoRotationAngle( deltaTime ) ); - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + } - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + if ( this.enableDamping ) { - inputRenderTarget = this.renderTargetsVertical[ i ]; + this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor; + this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor; + + } else { + + this._spherical.theta += this._sphericalDelta.theta; + this._spherical.phi += this._sphericalDelta.phi; } - // Composite All the mips + // restrict theta to be between desired limits - this.fsQuad.material = this.compositeMaterial; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; + let min = this.minAzimuthAngle; + let max = this.maxAzimuthAngle; - renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); - renderer.clear(); - this.fsQuad.render( renderer ); + if ( isFinite( min ) && isFinite( max ) ) { - // Blend it additively over the input texture + if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI; - this.fsQuad.material = this.blendMaterial; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; + if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); + if ( min <= max ) { - if ( this.renderToScreen ) { + this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + } else { - } else { + this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, this._spherical.theta ) : + Math.min( max, this._spherical.theta ); - renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + } } - // Restore renderer settings + // restrict phi to be between desired limits + this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); - renderer.autoClear = oldAutoClear; + this._spherical.makeSafe(); - } - getSeperableBlurMaterial( kernelRadius ) { + // move target to panned location - const coefficients = []; + if ( this.enableDamping === true ) { - for ( let i = 0; i < kernelRadius; i ++ ) { + this.target.addScaledVector( this._panOffset, this.dampingFactor ); + + } else { - coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + this.target.add( this._panOffset ); } - return new ShaderMaterial( { + // Limit the target distance from the cursor to create a sphere around the center of interest + this.target.sub( this.cursor ); + this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); + this.target.add( this.cursor ); - defines: { - 'KERNEL_RADIUS': kernelRadius - }, + let zoomChanged = false; + // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera + // we adjust zoom later in these cases + if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { - uniforms: { - 'colorTexture': { value: null }, - 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients - }, + this._spherical.radius = this._clampDistance( this._spherical.radius ); - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + } else { - fragmentShader: - `#include - varying vec2 vUv; - uniform sampler2D colorTexture; - uniform vec2 invSize; - uniform vec2 direction; - uniform float gaussianCoefficients[KERNEL_RADIUS]; + const prevRadius = this._spherical.radius; + this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); + zoomChanged = prevRadius != this._spherical.radius; - void main() { - float weightSum = gaussianCoefficients[0]; - vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; - for( int i = 1; i < KERNEL_RADIUS; i ++ ) { - float x = float(i); - float w = gaussianCoefficients[i]; - vec2 uvOffset = direction * invSize * x; - vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; - vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; - diffuseSum += (sample1 + sample2) * w; - weightSum += 2.0 * w; - } - gl_FragColor = vec4(diffuseSum/weightSum, 1.0); - }` - } ); + } - } + _v.setFromSpherical( this._spherical ); - getCompositeMaterial( nMips ) { + // rotate offset back to "camera-up-vector-is-up" space + _v.applyQuaternion( this._quatInverse ); - return new ShaderMaterial( { + position.copy( this.target ).add( _v ); - defines: { - 'NUM_MIPS': nMips - }, + this.object.lookAt( this.target ); - uniforms: { - 'blurTexture1': { value: null }, - 'blurTexture2': { value: null }, - 'blurTexture3': { value: null }, - 'blurTexture4': { value: null }, - 'blurTexture5': { value: null }, - 'bloomStrength': { value: 1.0 }, - 'bloomFactors': { value: null }, - 'bloomTintColors': { value: null }, - 'bloomRadius': { value: 0.0 } - }, + if ( this.enableDamping === true ) { - vertexShader: - `varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, + this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); + this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); - fragmentShader: - `varying vec2 vUv; - uniform sampler2D blurTexture1; - uniform sampler2D blurTexture2; - uniform sampler2D blurTexture3; - uniform sampler2D blurTexture4; - uniform sampler2D blurTexture5; - uniform float bloomStrength; - uniform float bloomRadius; - uniform float bloomFactors[NUM_MIPS]; - uniform vec3 bloomTintColors[NUM_MIPS]; + this._panOffset.multiplyScalar( 1 - this.dampingFactor ); - float lerpBloomFactor(const in float factor) { - float mirrorFactor = 1.2 - factor; - return mix(factor, mirrorFactor, bloomRadius); - } + } else { - void main() { - gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + - lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + - lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + - lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + - lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); - }` - } ); + this._sphericalDelta.set( 0, 0, 0 ); - } + this._panOffset.set( 0, 0, 0 ); -} + } -UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 ); -UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 ); + // adjust camera position + if ( this.zoomToCursor && this._performCursorZoom ) { -class RenderableObject { + let newRadius = null; + if ( this.object.isPerspectiveCamera ) { - constructor() { + // move the camera down the pointer ray + // this method avoids floating point error + const prevRadius = _v.length(); + newRadius = this._clampDistance( prevRadius * this._scale ); - this.id = 0; + const radiusDelta = prevRadius - newRadius; + this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); + this.object.updateMatrixWorld(); - this.object = null; - this.z = 0; - this.renderOrder = 0; + zoomChanged = !! radiusDelta; - } + } else if ( this.object.isOrthographicCamera ) { -} + // adjust the ortho camera position based on zoom changes + const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseBefore.unproject( this.object ); -// + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); + this.object.updateProjectionMatrix(); -class RenderableFace { + zoomChanged = prevZoom !== this.object.zoom; - constructor() { + const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseAfter.unproject( this.object ); - this.id = 0; + this.object.position.sub( mouseAfter ).add( mouseBefore ); + this.object.updateMatrixWorld(); - this.v1 = new RenderableVertex(); - this.v2 = new RenderableVertex(); - this.v3 = new RenderableVertex(); + newRadius = _v.length(); - this.normalModel = new Vector3(); + } else { - this.vertexNormalsModel = [ new Vector3(), new Vector3(), new Vector3() ]; - this.vertexNormalsLength = 0; + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); + this.zoomToCursor = false; - this.color = new Color(); - this.material = null; - this.uvs = [ new Vector2(), new Vector2(), new Vector2() ]; + } - this.z = 0; - this.renderOrder = 0; + // handle the placement of the target + if ( newRadius !== null ) { - } + if ( this.screenSpacePanning ) { -} + // position the orbit target in front of the new camera position + this.target.set( 0, 0, -1 ) + .transformDirection( this.object.matrix ) + .multiplyScalar( newRadius ) + .add( this.object.position ); -// + } else { -class RenderableVertex { + // get the ray and translation plane to compute target + _ray.origin.copy( this.object.position ); + _ray.direction.set( 0, 0, -1 ).transformDirection( this.object.matrix ); - constructor() { + // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid + // extremely large values + if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { - this.position = new Vector3(); - this.positionWorld = new Vector3(); - this.positionScreen = new Vector4(); + this.object.lookAt( this.target ); - this.visible = true; + } else { - } + _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); + _ray.intersectPlane( _plane, this.target ); - copy( vertex ) { + } - this.positionWorld.copy( vertex.positionWorld ); - this.positionScreen.copy( vertex.positionScreen ); + } - } + } -} + } else if ( this.object.isOrthographicCamera ) { -// + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); -class RenderableLine { + if ( prevZoom !== this.object.zoom ) { - constructor() { + this.object.updateProjectionMatrix(); + zoomChanged = true; - this.id = 0; + } - this.v1 = new RenderableVertex(); - this.v2 = new RenderableVertex(); + } - this.vertexColors = [ new Color(), new Color() ]; - this.material = null; + this._scale = 1; + this._performCursorZoom = false; - this.z = 0; - this.renderOrder = 0; + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - } + if ( zoomChanged || + this._lastPosition.distanceToSquared( this.object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || + this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { -} + this.dispatchEvent( _changeEvent ); -// + this._lastPosition.copy( this.object.position ); + this._lastQuaternion.copy( this.object.quaternion ); + this._lastTargetPosition.copy( this.target ); -class RenderableSprite { + return true; - constructor() { + } - this.id = 0; + return false; - this.object = null; + } - this.x = 0; - this.y = 0; - this.z = 0; + _getAutoRotationAngle( deltaTime ) { - this.rotation = 0; - this.scale = new Vector2(); + if ( deltaTime !== null ) { - this.material = null; - this.renderOrder = 0; + return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; + + } else { + + return _twoPI / 60 / 60 * this.autoRotateSpeed; + + } } -} + _getZoomScale( delta ) { -// + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); -class Projector { + } - constructor() { + _rotateLeft( angle ) { - let _object, _objectCount, _objectPoolLength = 0, - _vertex, _vertexCount, _vertexPoolLength = 0, - _face, _faceCount, _facePoolLength = 0, - _line, _lineCount, _linePoolLength = 0, - _sprite, _spriteCount, _spritePoolLength = 0, - _modelMatrix; + this._sphericalDelta.theta -= angle; - const + } - _renderData = { objects: [], lights: [], elements: [] }, + _rotateUp( angle ) { - _vector3 = new Vector3(), - _vector4 = new Vector4(), + this._sphericalDelta.phi -= angle; - _clipBox = new Box3( new Vector3( - 1, - 1, - 1 ), new Vector3( 1, 1, 1 ) ), - _boundingBox = new Box3(), - _points3 = new Array( 3 ), + } - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), + _panLeft( distance, objectMatrix ) { - _modelViewProjectionMatrix = new Matrix4(), + _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + _v.multiplyScalar( - distance ); - _frustum = new Frustum(), + this._panOffset.add( _v ); - _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = []; + } - // + _panUp( distance, objectMatrix ) { - function RenderList() { + if ( this.screenSpacePanning === true ) { - const normals = []; - const colors = []; - const uvs = []; + _v.setFromMatrixColumn( objectMatrix, 1 ); - let object = null; + } else { - const normalMatrix = new Matrix3(); + _v.setFromMatrixColumn( objectMatrix, 0 ); + _v.crossVectors( this.object.up, _v ); - function setObject( value ) { + } - object = value; + _v.multiplyScalar( distance ); - normalMatrix.getNormalMatrix( object.matrixWorld ); + this._panOffset.add( _v ); - normals.length = 0; - colors.length = 0; - uvs.length = 0; + } - } + // deltaX and deltaY are in pixels; right and down are positive + _pan( deltaX, deltaY ) { - function projectVertex( vertex ) { + const element = this.domElement; - const position = vertex.position; - const positionWorld = vertex.positionWorld; - const positionScreen = vertex.positionScreen; + if ( this.object.isPerspectiveCamera ) { - positionWorld.copy( position ).applyMatrix4( _modelMatrix ); - positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); + // perspective + const position = this.object.position; + _v.copy( position ).sub( this.target ); + let targetDistance = _v.length(); - const invW = 1 / positionScreen.w; + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); - positionScreen.x *= invW; - positionScreen.y *= invW; - positionScreen.z *= invW; + // we use only clientHeight here so aspect ratio does not distort speed + this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); + this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); - vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 && - positionScreen.y >= - 1 && positionScreen.y <= 1 && - positionScreen.z >= - 1 && positionScreen.z <= 1; + } else if ( this.object.isOrthographicCamera ) { - } + // orthographic + this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); + this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); - function pushVertex( x, y, z ) { + } else { - _vertex = getNextVertexInPool(); - _vertex.position.set( x, y, z ); + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + this.enablePan = false; - projectVertex( _vertex ); + } - } + } - function pushNormal( x, y, z ) { + _dollyOut( dollyScale ) { - normals.push( x, y, z ); + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - } + this._scale /= dollyScale; - function pushColor( r, g, b ) { + } else { - colors.push( r, g, b ); + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; - } + } - function pushUv( x, y ) { + } - uvs.push( x, y ); + _dollyIn( dollyScale ) { - } + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - function checkTriangleVisibility( v1, v2, v3 ) { + this._scale *= dollyScale; - if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true; + } else { - _points3[ 0 ] = v1.positionScreen; - _points3[ 1 ] = v2.positionScreen; - _points3[ 2 ] = v3.positionScreen; + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; - return _clipBox.intersectsBox( _boundingBox.setFromPoints( _points3 ) ); + } - } + } - function checkBackfaceCulling( v1, v2, v3 ) { + _updateZoomParameters( x, y ) { - return ( ( v3.positionScreen.x - v1.positionScreen.x ) * - ( v2.positionScreen.y - v1.positionScreen.y ) - - ( v3.positionScreen.y - v1.positionScreen.y ) * - ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; + if ( ! this.zoomToCursor ) { - } + return; - function pushLine( a, b ) { + } - const v1 = _vertexPool[ a ]; - const v2 = _vertexPool[ b ]; + this._performCursorZoom = true; - // Clip + const rect = this.domElement.getBoundingClientRect(); + const dx = x - rect.left; + const dy = y - rect.top; + const w = rect.width; + const h = rect.height; - v1.positionScreen.copy( v1.position ).applyMatrix4( _modelViewProjectionMatrix ); - v2.positionScreen.copy( v2.position ).applyMatrix4( _modelViewProjectionMatrix ); + this._mouse.x = ( dx / w ) * 2 - 1; + this._mouse.y = - ( dy / h ) * 2 + 1; - if ( clipLine( v1.positionScreen, v2.positionScreen ) === true ) { + this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); - // Perform the perspective divide - v1.positionScreen.multiplyScalar( 1 / v1.positionScreen.w ); - v2.positionScreen.multiplyScalar( 1 / v2.positionScreen.w ); + } - _line = getNextLineInPool(); - _line.id = object.id; - _line.v1.copy( v1 ); - _line.v2.copy( v2 ); - _line.z = Math.max( v1.positionScreen.z, v2.positionScreen.z ); - _line.renderOrder = object.renderOrder; + _clampDistance( dist ) { - _line.material = object.material; + return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); - if ( object.material.vertexColors ) { + } - _line.vertexColors[ 0 ].fromArray( colors, a * 3 ); - _line.vertexColors[ 1 ].fromArray( colors, b * 3 ); + // + // event callbacks - update the object state + // - } + _handleMouseDownRotate( event ) { - _renderData.elements.push( _line ); + this._rotateStart.set( event.clientX, event.clientY ); - } + } - } + _handleMouseDownDolly( event ) { - function pushTriangle( a, b, c, material ) { + this._updateZoomParameters( event.clientX, event.clientX ); + this._dollyStart.set( event.clientX, event.clientY ); - const v1 = _vertexPool[ a ]; - const v2 = _vertexPool[ b ]; - const v3 = _vertexPool[ c ]; + } - if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return; + _handleMouseDownPan( event ) { - if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) { + this._panStart.set( event.clientX, event.clientY ); - _face = getNextFaceInPool(); + } - _face.id = object.id; - _face.v1.copy( v1 ); - _face.v2.copy( v2 ); - _face.v3.copy( v3 ); - _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; - _face.renderOrder = object.renderOrder; + _handleMouseMoveRotate( event ) { - // face normal - _vector3.subVectors( v3.position, v2.position ); - _vector4.subVectors( v1.position, v2.position ); - _vector3.cross( _vector4 ); - _face.normalModel.copy( _vector3 ); - _face.normalModel.applyMatrix3( normalMatrix ).normalize(); + this._rotateEnd.set( event.clientX, event.clientY ); - for ( let i = 0; i < 3; i ++ ) { + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - const normal = _face.vertexNormalsModel[ i ]; - normal.fromArray( normals, arguments[ i ] * 3 ); - normal.applyMatrix3( normalMatrix ).normalize(); + const element = this.domElement; - const uv = _face.uvs[ i ]; - uv.fromArray( uvs, arguments[ i ] * 2 ); + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - } + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - _face.vertexNormalsLength = 3; + this._rotateStart.copy( this._rotateEnd ); - _face.material = material; + this.update(); - if ( material.vertexColors ) { + } - _face.color.fromArray( colors, a * 3 ); + _handleMouseMoveDolly( event ) { - } + this._dollyEnd.set( event.clientX, event.clientY ); - _renderData.elements.push( _face ); + this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); - } + if ( this._dollyDelta.y > 0 ) { - } + this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); - return { - setObject: setObject, - projectVertex: projectVertex, - checkTriangleVisibility: checkTriangleVisibility, - checkBackfaceCulling: checkBackfaceCulling, - pushVertex: pushVertex, - pushNormal: pushNormal, - pushColor: pushColor, - pushUv: pushUv, - pushLine: pushLine, - pushTriangle: pushTriangle - }; + } else if ( this._dollyDelta.y < 0 ) { + + this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); } - const renderList = new RenderList(); + this._dollyStart.copy( this._dollyEnd ); - function projectObject( object ) { + this.update(); - if ( object.visible === false ) return; + } - if ( object.isLight ) { + _handleMouseMovePan( event ) { - _renderData.lights.push( object ); + this._panEnd.set( event.clientX, event.clientY ); - } else if ( object.isMesh || object.isLine || object.isPoints ) { + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - if ( object.material.visible === false ) return; - if ( object.frustumCulled === true && _frustum.intersectsObject( object ) === false ) return; + this._pan( this._panDelta.x, this._panDelta.y ); - addObject( object ); + this._panStart.copy( this._panEnd ); - } else if ( object.isSprite ) { + this.update(); - if ( object.material.visible === false ) return; - if ( object.frustumCulled === true && _frustum.intersectsSprite( object ) === false ) return; + } - addObject( object ); + _handleMouseWheel( event ) { - } + this._updateZoomParameters( event.clientX, event.clientY ); - const children = object.children; + if ( event.deltaY < 0 ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + this._dollyIn( this._getZoomScale( event.deltaY ) ); - projectObject( children[ i ] ); + } else if ( event.deltaY > 0 ) { - } + this._dollyOut( this._getZoomScale( event.deltaY ) ); } - function addObject( object ) { + this.update(); - _object = getNextObjectInPool(); - _object.id = object.id; - _object.object = object; + } - _vector3.setFromMatrixPosition( object.matrixWorld ); - _vector3.applyMatrix4( _viewProjectionMatrix ); - _object.z = _vector3.z; - _object.renderOrder = object.renderOrder; + _handleKeyDown( event ) { - _renderData.objects.push( _object ); + let needsUpdate = false; - } + switch ( event.code ) { - this.projectScene = function ( scene, camera, sortObjects, sortElements ) { + case this.keys.UP: - _faceCount = 0; - _lineCount = 0; - _spriteCount = 0; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - _renderData.elements.length = 0; + if ( this.enableRotate ) { - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - _viewMatrix.copy( camera.matrixWorldInverse ); - _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); + } - _frustum.setFromProjectionMatrix( _viewProjectionMatrix ); + } else { - // + if ( this.enablePan ) { - _objectCount = 0; + this._pan( 0, this.keyPanSpeed ); - _renderData.objects.length = 0; - _renderData.lights.length = 0; + } - projectObject( scene ); + } - if ( sortObjects === true ) { + needsUpdate = true; + break; - _renderData.objects.sort( painterSort ); + case this.keys.BOTTOM: - } + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - // + if ( this.enableRotate ) { - const objects = _renderData.objects; + this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - for ( let o = 0, ol = objects.length; o < ol; o ++ ) { + } - const object = objects[ o ].object; - const geometry = object.geometry; + } else { - renderList.setObject( object ); + if ( this.enablePan ) { - _modelMatrix = object.matrixWorld; + this._pan( 0, - this.keyPanSpeed ); - _vertexCount = 0; + } - if ( object.isMesh ) { + } - let material = object.material; + needsUpdate = true; + break; - const isMultiMaterial = Array.isArray( material ); + case this.keys.LEFT: - const attributes = geometry.attributes; - const groups = geometry.groups; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - if ( attributes.position === undefined ) continue; + if ( this.enableRotate ) { - const positions = attributes.position.array; + this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - for ( let i = 0, l = positions.length; i < l; i += 3 ) { + } - let x = positions[ i ]; - let y = positions[ i + 1 ]; - let z = positions[ i + 2 ]; + } else { - const morphTargets = geometry.morphAttributes.position; + if ( this.enablePan ) { - if ( morphTargets !== undefined ) { + this._pan( this.keyPanSpeed, 0 ); - const morphTargetsRelative = geometry.morphTargetsRelative; - const morphInfluences = object.morphTargetInfluences; + } - for ( let t = 0, tl = morphTargets.length; t < tl; t ++ ) { + } - const influence = morphInfluences[ t ]; + needsUpdate = true; + break; - if ( influence === 0 ) continue; + case this.keys.RIGHT: - const target = morphTargets[ t ]; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - if ( morphTargetsRelative ) { + if ( this.enableRotate ) { - x += target.getX( i / 3 ) * influence; - y += target.getY( i / 3 ) * influence; - z += target.getZ( i / 3 ) * influence; + this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - } else { + } - x += ( target.getX( i / 3 ) - positions[ i ] ) * influence; - y += ( target.getY( i / 3 ) - positions[ i + 1 ] ) * influence; - z += ( target.getZ( i / 3 ) - positions[ i + 2 ] ) * influence; + } else { - } + if ( this.enablePan ) { - } + this._pan( - this.keyPanSpeed, 0 ); - } + } - renderList.pushVertex( x, y, z ); + } - } + needsUpdate = true; + break; - if ( attributes.normal !== undefined ) { + } - const normals = attributes.normal.array; + if ( needsUpdate ) { - for ( let i = 0, l = normals.length; i < l; i += 3 ) { + // prevent the browser from scrolling on cursor keys + event.preventDefault(); - renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); + this.update(); - } + } - } - if ( attributes.color !== undefined ) { + } - const colors = attributes.color.array; + _handleTouchStartRotate( event ) { - for ( let i = 0, l = colors.length; i < l; i += 3 ) { + if ( this._pointers.length === 1 ) { - renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); + this._rotateStart.set( event.pageX, event.pageY ); - } + } else { - } + const position = this._getSecondPointerPosition( event ); - if ( attributes.uv !== undefined ) { + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - const uvs = attributes.uv.array; + this._rotateStart.set( x, y ); - for ( let i = 0, l = uvs.length; i < l; i += 2 ) { + } - renderList.pushUv( uvs[ i ], uvs[ i + 1 ] ); + } - } + _handleTouchStartPan( event ) { - } + if ( this._pointers.length === 1 ) { - if ( geometry.index !== null ) { + this._panStart.set( event.pageX, event.pageY ); - const indices = geometry.index.array; + } else { - if ( groups.length > 0 ) { + const position = this._getSecondPointerPosition( event ); - for ( let g = 0; g < groups.length; g ++ ) { + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - const group = groups[ g ]; + this._panStart.set( x, y ); - material = isMultiMaterial === true - ? object.material[ group.materialIndex ] - : object.material; + } - if ( material === undefined ) continue; + } - for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { + _handleTouchStartDolly( event ) { - renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); + const position = this._getSecondPointerPosition( event ); - } + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - } + const distance = Math.sqrt( dx * dx + dy * dy ); - } else { + this._dollyStart.set( 0, distance ); - for ( let i = 0, l = indices.length; i < l; i += 3 ) { + } - renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); + _handleTouchStartDollyPan( event ) { - } + if ( this.enableZoom ) this._handleTouchStartDolly( event ); - } + if ( this.enablePan ) this._handleTouchStartPan( event ); - } else { + } - if ( groups.length > 0 ) { + _handleTouchStartDollyRotate( event ) { - for ( let g = 0; g < groups.length; g ++ ) { + if ( this.enableZoom ) this._handleTouchStartDolly( event ); - const group = groups[ g ]; + if ( this.enableRotate ) this._handleTouchStartRotate( event ); - material = isMultiMaterial === true - ? object.material[ group.materialIndex ] - : object.material; + } - if ( material === undefined ) continue; + _handleTouchMoveRotate( event ) { - for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { + if ( this._pointers.length == 1 ) { - renderList.pushTriangle( i, i + 1, i + 2, material ); + this._rotateEnd.set( event.pageX, event.pageY ); - } + } else { - } + const position = this._getSecondPointerPosition( event ); - } else { + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - for ( let i = 0, l = positions.length / 3; i < l; i += 3 ) { + this._rotateEnd.set( x, y ); - renderList.pushTriangle( i, i + 1, i + 2, material ); + } - } + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - } + const element = this.domElement; - } + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - } else if ( object.isLine ) { + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); + this._rotateStart.copy( this._rotateEnd ); - const attributes = geometry.attributes; + } - if ( attributes.position !== undefined ) { + _handleTouchMovePan( event ) { - const positions = attributes.position.array; + if ( this._pointers.length === 1 ) { - for ( let i = 0, l = positions.length; i < l; i += 3 ) { + this._panEnd.set( event.pageX, event.pageY ); - renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + } else { - } + const position = this._getSecondPointerPosition( event ); - if ( attributes.color !== undefined ) { + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - const colors = attributes.color.array; + this._panEnd.set( x, y ); - for ( let i = 0, l = colors.length; i < l; i += 3 ) { + } - renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - } + this._pan( this._panDelta.x, this._panDelta.y ); - } + this._panStart.copy( this._panEnd ); - if ( geometry.index !== null ) { + } - const indices = geometry.index.array; + _handleTouchMoveDolly( event ) { - for ( let i = 0, l = indices.length; i < l; i += 2 ) { + const position = this._getSecondPointerPosition( event ); - renderList.pushLine( indices[ i ], indices[ i + 1 ] ); + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - } + const distance = Math.sqrt( dx * dx + dy * dy ); - } else { + this._dollyEnd.set( 0, distance ); - const step = object.isLineSegments ? 2 : 1; + this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); - for ( let i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) { + this._dollyOut( this._dollyDelta.y ); - renderList.pushLine( i, i + 1 ); + this._dollyStart.copy( this._dollyEnd ); - } + const centerX = ( event.pageX + position.x ) * 0.5; + const centerY = ( event.pageY + position.y ) * 0.5; - } + this._updateZoomParameters( centerX, centerY ); - } + } - } else if ( object.isPoints ) { + _handleTouchMoveDollyPan( event ) { - _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - const attributes = geometry.attributes; + if ( this.enablePan ) this._handleTouchMovePan( event ); - if ( attributes.position !== undefined ) { + } - const positions = attributes.position.array; + _handleTouchMoveDollyRotate( event ) { - for ( let i = 0, l = positions.length; i < l; i += 3 ) { + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - _vector4.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ], 1 ); - _vector4.applyMatrix4( _modelViewProjectionMatrix ); + if ( this.enableRotate ) this._handleTouchMoveRotate( event ); - pushPoint( _vector4, object, camera ); + } - } + // pointers - } + _addPointer( event ) { - } else if ( object.isSprite ) { + this._pointers.push( event.pointerId ); - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - _vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); - _vector4.applyMatrix4( _viewProjectionMatrix ); + } - pushPoint( _vector4, object, camera ); + _removePointer( event ) { - } + delete this._pointerPositions[ event.pointerId ]; - } + for ( let i = 0; i < this._pointers.length; i ++ ) { - if ( sortElements === true ) { + if ( this._pointers[ i ] == event.pointerId ) { - _renderData.elements.sort( painterSort ); + this._pointers.splice( i, 1 ); + return; } - return _renderData; + } - }; + } - function pushPoint( _vector4, object, camera ) { + _isTrackingPointer( event ) { - const invW = 1 / _vector4.w; + for ( let i = 0; i < this._pointers.length; i ++ ) { - _vector4.z *= invW; + if ( this._pointers[ i ] == event.pointerId ) return true; - if ( _vector4.z >= - 1 && _vector4.z <= 1 ) { + } - _sprite = getNextSpriteInPool(); - _sprite.id = object.id; - _sprite.x = _vector4.x * invW; - _sprite.y = _vector4.y * invW; - _sprite.z = _vector4.z; - _sprite.renderOrder = object.renderOrder; - _sprite.object = object; + return false; - _sprite.rotation = object.rotation; + } - _sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) ); - _sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) ); + _trackPointer( event ) { - _sprite.material = object.material; + let position = this._pointerPositions[ event.pointerId ]; - _renderData.elements.push( _sprite ); + if ( position === undefined ) { - } + position = new Vector2(); + this._pointerPositions[ event.pointerId ] = position; } - // Pools + position.set( event.pageX, event.pageY ); - function getNextObjectInPool() { + } - if ( _objectCount === _objectPoolLength ) { + _getSecondPointerPosition( event ) { - const object = new RenderableObject(); - _objectPool.push( object ); - _objectPoolLength ++; - _objectCount ++; - return object; + const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - } + return this._pointerPositions[ pointerId ]; - return _objectPool[ _objectCount ++ ]; + } - } + // - function getNextVertexInPool() { + _customWheelEvent( event ) { - if ( _vertexCount === _vertexPoolLength ) { + const mode = event.deltaMode; - const vertex = new RenderableVertex(); - _vertexPool.push( vertex ); - _vertexPoolLength ++; - _vertexCount ++; - return vertex; + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; - } + switch ( mode ) { - return _vertexPool[ _vertexCount ++ ]; + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; } - function getNextFaceInPool() { + // detect if event was triggered by pinching + if ( event.ctrlKey && ! this._controlActive ) { - if ( _faceCount === _facePoolLength ) { + newEvent.deltaY *= 10; - const face = new RenderableFace(); - _facePool.push( face ); - _facePoolLength ++; - _faceCount ++; - return face; + } - } + return newEvent; - return _facePool[ _faceCount ++ ]; + } +} - } +function onPointerDown( event ) { - function getNextLineInPool() { + if ( this.enabled === false ) return; - if ( _lineCount === _linePoolLength ) { + if ( this._pointers.length === 0 ) { - const line = new RenderableLine(); - _linePool.push( line ); - _linePoolLength ++; - _lineCount ++; - return line; + this.domElement.setPointerCapture( event.pointerId ); - } + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - return _linePool[ _lineCount ++ ]; + } - } + // - function getNextSpriteInPool() { + if ( this._isTrackingPointer( event ) ) return; - if ( _spriteCount === _spritePoolLength ) { + // - const sprite = new RenderableSprite(); - _spritePool.push( sprite ); - _spritePoolLength ++; - _spriteCount ++; - return sprite; + this._addPointer( event ); - } + if ( event.pointerType === 'touch' ) { - return _spritePool[ _spriteCount ++ ]; + this._onTouchStart( event ); - } + } else { - // + this._onMouseDown( event ); - function painterSort( a, b ) { + } - if ( a.renderOrder !== b.renderOrder ) { +} - return a.renderOrder - b.renderOrder; +function onPointerMove( event ) { - } else if ( a.z !== b.z ) { + if ( this.enabled === false ) return; - return b.z - a.z; + if ( event.pointerType === 'touch' ) { - } else if ( a.id !== b.id ) { + this._onTouchMove( event ); - return a.id - b.id; + } else { - } else { + this._onMouseMove( event ); - return 0; + } - } +} - } +function onPointerUp( event ) { - function clipLine( s1, s2 ) { + this._removePointer( event ); - let alpha1 = 0, alpha2 = 1; + switch ( this._pointers.length ) { - // Calculate the boundary coordinate of each vertex for the near and far clip planes, - // Z = -1 and Z = +1, respectively. + case 0: - const bc1near = s1.z + s1.w, - bc2near = s2.z + s2.w, - bc1far = - s1.z + s1.w, - bc2far = - s2.z + s2.w; + this.domElement.releasePointerCapture( event.pointerId ); - if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - // Both vertices lie entirely within all clip planes. - return true; + this.dispatchEvent( _endEvent ); - } else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) { + this.state = _STATE.NONE; - // Both vertices lie entirely outside one of the clip planes. - return false; + break; - } else { + case 1: - // The line segment spans at least one clip plane. + const pointerId = this._pointers[ 0 ]; + const position = this._pointerPositions[ pointerId ]; - if ( bc1near < 0 ) { + // minimal placeholder event - allows state correction on pointer-up + this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); - // v1 lies outside the near plane, v2 inside - alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); + break; - } else if ( bc2near < 0 ) { + } - // v2 lies outside the near plane, v1 inside - alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); +} - } +function onMouseDown( event ) { - if ( bc1far < 0 ) { + let mouseAction; - // v1 lies outside the far plane, v2 inside - alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); + switch ( event.button ) { - } else if ( bc2far < 0 ) { + case 0: - // v2 lies outside the far plane, v2 inside - alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); + mouseAction = this.mouseButtons.LEFT; + break; - } + case 1: - if ( alpha2 < alpha1 ) { + mouseAction = this.mouseButtons.MIDDLE; + break; - // The line segment spans two boundaries, but is outside both of them. - // (This can't happen when we're only clipping against just near/far but good - // to leave the check here for future usage if other clip planes are added.) - return false; + case 2: - } else { + mouseAction = this.mouseButtons.RIGHT; + break; - // Update the s1 and s2 vertices to match the clipped line segment. - s1.lerp( s2, alpha1 ); - s2.lerp( s1, 1 - alpha2 ); + default: - return true; + mouseAction = -1; - } + } - } + switch ( mouseAction ) { - } + case MOUSE.DOLLY: - } + if ( this.enableZoom === false ) return; -} + this._handleMouseDownDolly( event ); -class SVGRenderer { + this.state = _STATE.DOLLY; - constructor() { + break; - let _renderData, _elements, _lights, - _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, + case MOUSE.ROTATE: - _v1, _v2, _v3, + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - _svgNode, - _pathCount = 0, + if ( this.enablePan === false ) return; - _precision = null, - _quality = 1, + this._handleMouseDownPan( event ); - _currentPath, _currentStyle; + this.state = _STATE.PAN; - const _this = this, - _clipBox = new Box2(), - _elemBox = new Box2(), + } else { - _color = new Color(), - _diffuseColor = new Color(), - _ambientLight = new Color(), - _directionalLights = new Color(), - _pointLights = new Color(), - _clearColor = new Color(), + if ( this.enableRotate === false ) return; - _vector3 = new Vector3(), // Needed for PointLight - _centroid = new Vector3(), - _normal = new Vector3(), - _normalViewMatrix = new Matrix3(), + this._handleMouseDownRotate( event ); - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), + this.state = _STATE.ROTATE; - _svgPathPool = [], + } - _projector = new Projector(), - _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + break; - this.domElement = _svg; + case MOUSE.PAN: - this.autoClear = true; - this.sortObjects = true; - this.sortElements = true; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - this.overdraw = 0.5; + if ( this.enableRotate === false ) return; - this.outputColorSpace = SRGBColorSpace; + this._handleMouseDownRotate( event ); - this.info = { + this.state = _STATE.ROTATE; - render: { + } else { - vertices: 0, - faces: 0 + if ( this.enablePan === false ) return; + + this._handleMouseDownPan( event ); + + this.state = _STATE.PAN; } - }; + break; - this.setQuality = function ( quality ) { + default: - switch ( quality ) { + this.state = _STATE.NONE; - case 'high': _quality = 1; break; - case 'low': _quality = 0; break; + } - } + if ( this.state !== _STATE.NONE ) { - }; + this.dispatchEvent( _startEvent ); - this.setClearColor = function ( color ) { + } - _clearColor.set( color ); +} - }; +function onMouseMove( event ) { - this.setPixelRatio = function () {}; + switch ( this.state ) { - this.setSize = function ( width, height ) { + case _STATE.ROTATE: - _svgWidth = width; _svgHeight = height; - _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2; + if ( this.enableRotate === false ) return; - _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight ); - _svg.setAttribute( 'width', _svgWidth ); - _svg.setAttribute( 'height', _svgHeight ); + this._handleMouseMoveRotate( event ); - _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf ); - _clipBox.max.set( _svgWidthHalf, _svgHeightHalf ); + break; - }; + case _STATE.DOLLY: - this.getSize = function () { + if ( this.enableZoom === false ) return; - return { - width: _svgWidth, - height: _svgHeight - }; + this._handleMouseMoveDolly( event ); - }; + break; - this.setPrecision = function ( precision ) { + case _STATE.PAN: - _precision = precision; + if ( this.enablePan === false ) return; - }; + this._handleMouseMovePan( event ); - function removeChildNodes() { + break; - _pathCount = 0; + } - while ( _svg.childNodes.length > 0 ) { +} - _svg.removeChild( _svg.childNodes[ 0 ] ); +function onMouseWheel( event ) { - } + if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; - } + event.preventDefault(); - function convert( c ) { + this.dispatchEvent( _startEvent ); - return _precision !== null ? c.toFixed( _precision ) : c; + this._handleMouseWheel( this._customWheelEvent( event ) ); - } + this.dispatchEvent( _endEvent ); - this.clear = function () { +} - removeChildNodes(); - _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace ); +function onKeyDown( event ) { - }; + if ( this.enabled === false ) return; - this.render = function ( scene, camera ) { + this._handleKeyDown( event ); - if ( camera instanceof Camera === false ) { +} - console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' ); - return; +function onTouchStart( event ) { - } + this._trackPointer( event ); - const background = scene.background; + switch ( this._pointers.length ) { - if ( background && background.isColor ) { + case 1: - removeChildNodes(); - _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace ); + switch ( this.touches.ONE ) { - } else if ( this.autoClear === true ) { + case TOUCH.ROTATE: - this.clear(); + if ( this.enableRotate === false ) return; - } + this._handleTouchStartRotate( event ); - _this.info.render.vertices = 0; - _this.info.render.faces = 0; + this.state = _STATE.TOUCH_ROTATE; - _viewMatrix.copy( camera.matrixWorldInverse ); - _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); + break; - _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); - _elements = _renderData.elements; - _lights = _renderData.lights; + case TOUCH.PAN: - _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); + if ( this.enablePan === false ) return; - calculateLights( _lights ); + this._handleTouchStartPan( event ); - // reset accumulated path + this.state = _STATE.TOUCH_PAN; - _currentPath = ''; - _currentStyle = ''; + break; - for ( let e = 0, el = _elements.length; e < el; e ++ ) { + default: - const element = _elements[ e ]; - const material = element.material; + this.state = _STATE.NONE; - if ( material === undefined || material.opacity === 0 ) continue; + } - _elemBox.makeEmpty(); + break; - if ( element instanceof RenderableSprite ) { + case 2: - _v1 = element; - _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf; + switch ( this.touches.TWO ) { - renderSprite( _v1, element, material ); + case TOUCH.DOLLY_PAN: - } else if ( element instanceof RenderableLine ) { - - _v1 = element.v1; _v2 = element.v2; + if ( this.enableZoom === false && this.enablePan === false ) return; - _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; - _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; + this._handleTouchStartDollyPan( event ); - _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] ); + this.state = _STATE.TOUCH_DOLLY_PAN; - if ( _clipBox.intersectsBox( _elemBox ) === true ) { + break; - renderLine( _v1, _v2, material ); + case TOUCH.DOLLY_ROTATE: - } + if ( this.enableZoom === false && this.enableRotate === false ) return; - } else if ( element instanceof RenderableFace ) { + this._handleTouchStartDollyRotate( event ); - _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; + this.state = _STATE.TOUCH_DOLLY_ROTATE; - if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue; - if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue; - if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue; + break; - _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; - _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; - _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf; + default: - if ( this.overdraw > 0 ) { + this.state = _STATE.NONE; - expand( _v1.positionScreen, _v2.positionScreen, this.overdraw ); - expand( _v2.positionScreen, _v3.positionScreen, this.overdraw ); - expand( _v3.positionScreen, _v1.positionScreen, this.overdraw ); + } - } + break; - _elemBox.setFromPoints( [ - _v1.positionScreen, - _v2.positionScreen, - _v3.positionScreen - ] ); + default: - if ( _clipBox.intersectsBox( _elemBox ) === true ) { + this.state = _STATE.NONE; - renderFace3( _v1, _v2, _v3, element, material ); + } - } + if ( this.state !== _STATE.NONE ) { - } + this.dispatchEvent( _startEvent ); - } + } - flushPath(); // just to flush last svg:path +} - scene.traverseVisible( function ( object ) { +function onTouchMove( event ) { - if ( object.isSVGObject ) { + this._trackPointer( event ); - _vector3.setFromMatrixPosition( object.matrixWorld ); - _vector3.applyMatrix4( _viewProjectionMatrix ); + switch ( this.state ) { - if ( _vector3.z < - 1 || _vector3.z > 1 ) return; + case _STATE.TOUCH_ROTATE: - const x = _vector3.x * _svgWidthHalf; - const y = - _vector3.y * _svgHeightHalf; + if ( this.enableRotate === false ) return; - const node = object.node; - node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' ); + this._handleTouchMoveRotate( event ); - _svg.appendChild( node ); + this.update(); - } + break; - } ); + case _STATE.TOUCH_PAN: - }; + if ( this.enablePan === false ) return; - function calculateLights( lights ) { + this._handleTouchMovePan( event ); - _ambientLight.setRGB( 0, 0, 0 ); - _directionalLights.setRGB( 0, 0, 0 ); - _pointLights.setRGB( 0, 0, 0 ); + this.update(); - for ( let l = 0, ll = lights.length; l < ll; l ++ ) { + break; - const light = lights[ l ]; - const lightColor = light.color; + case _STATE.TOUCH_DOLLY_PAN: - if ( light.isAmbientLight ) { + if ( this.enableZoom === false && this.enablePan === false ) return; - _ambientLight.r += lightColor.r; - _ambientLight.g += lightColor.g; - _ambientLight.b += lightColor.b; + this._handleTouchMoveDollyPan( event ); - } else if ( light.isDirectionalLight ) { + this.update(); - _directionalLights.r += lightColor.r; - _directionalLights.g += lightColor.g; - _directionalLights.b += lightColor.b; + break; - } else if ( light.isPointLight ) { + case _STATE.TOUCH_DOLLY_ROTATE: - _pointLights.r += lightColor.r; - _pointLights.g += lightColor.g; - _pointLights.b += lightColor.b; + if ( this.enableZoom === false && this.enableRotate === false ) return; - } + this._handleTouchMoveDollyRotate( event ); - } + this.update(); - } + break; - function calculateLight( lights, position, normal, color ) { + default: - for ( let l = 0, ll = lights.length; l < ll; l ++ ) { + this.state = _STATE.NONE; - const light = lights[ l ]; - const lightColor = light.color; + } - if ( light.isDirectionalLight ) { +} - const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); +function onContextMenu( event ) { - let amount = normal.dot( lightPosition ); + if ( this.enabled === false ) return; - if ( amount <= 0 ) continue; + event.preventDefault(); - amount *= light.intensity; +} - color.r += lightColor.r * amount; - color.g += lightColor.g * amount; - color.b += lightColor.b * amount; +function interceptControlDown( event ) { - } else if ( light.isPointLight ) { + if ( event.key === 'Control' ) { - const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); + this._controlActive = true; - let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); + const document = this.domElement.getRootNode(); // offscreen canvas compatibility - if ( amount <= 0 ) continue; + document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); + } - if ( amount == 0 ) continue; +} - amount *= light.intensity; +function interceptControlUp( event ) { - color.r += lightColor.r * amount; - color.g += lightColor.g * amount; - color.b += lightColor.b * amount; + if ( event.key === 'Control' ) { - } + this._controlActive = false; - } + const document = this.domElement.getRootNode(); // offscreen canvas compatibility - } + document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - function renderSprite( v1, element, material ) { + } - let scaleX = element.scale.x * _svgWidthHalf; - let scaleY = element.scale.y * _svgHeightHalf; +} - if ( material.isPointsMaterial ) { +/** + * @module CopyShader + * @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js'; + */ - scaleX *= material.size; - scaleY *= material.size; +/** + * Full-screen copy shader pass. + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const CopyShader = { - } + name: 'CopyShader', - const path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z'; - let style = ''; + uniforms: { - if ( material.isSpriteMaterial || material.isPointsMaterial ) { + 'tDiffuse': { value: null }, + 'opacity': { value: 1.0 } - style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; + }, - } + vertexShader: /* glsl */` - addPath( style, path ); + varying vec2 vUv; - } + void main() { - function renderLine( v1, v2, material ) { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ); + }`, - if ( material.isLineBasicMaterial ) { + fragmentShader: /* glsl */` - let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap; + uniform float opacity; - if ( material.isLineDashedMaterial ) { + uniform sampler2D tDiffuse; - style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize; + varying vec2 vUv; - } + void main() { - addPath( style, path ); + vec4 texel = texture2D( tDiffuse, vUv ); + gl_FragColor = opacity * texel; - } - } + }` - function renderFace3( v1, v2, v3, element, material ) { +}; - _this.info.render.vertices += 3; - _this.info.render.faces ++; +/** + * Abstract base class for all post processing passes. + * + * This module is only relevant for post processing with {@link WebGLRenderer}. + * + * @abstract + * @three_import import { Pass } from 'three/addons/postprocessing/Pass.js'; + */ +class Pass { - const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z'; - let style = ''; + /** + * Constructs a new pass. + */ + constructor() { - if ( material.isMeshBasicMaterial ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPass = true; - _color.copy( material.color ); + /** + * If set to `true`, the pass is processed by the composer. + * + * @type {boolean} + * @default true + */ + this.enabled = true; - if ( material.vertexColors ) { + /** + * If set to `true`, the pass indicates to swap read and write buffer after rendering. + * + * @type {boolean} + * @default true + */ + this.needsSwap = true; - _color.multiply( element.color ); + /** + * If set to `true`, the pass clears its buffer before rendering + * + * @type {boolean} + * @default false + */ + this.clear = false; - } + /** + * If set to `true`, the result of the pass is rendered to screen. The last pass in the composers + * pass chain gets automatically rendered to screen, no matter how this property is configured. + * + * @type {boolean} + * @default false + */ + this.renderToScreen = false; - } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) { + } - _diffuseColor.copy( material.color ); + /** + * Sets the size of the pass. + * + * @abstract + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( /* width, height */ ) {} - if ( material.vertexColors ) { + /** + * This method holds the render logic of a pass. It must be implemented in all derived classes. + * + * @abstract + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - _diffuseColor.multiply( element.color ); + console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); - } + } - _color.copy( _ambientLight ); + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + * + * @abstract + */ + dispose() {} - _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 ); +} - calculateLight( _lights, _centroid, element.normalModel, _color ); +// Helper for passes that need to fill the viewport with a single quad. - _color.multiply( _diffuseColor ).add( material.emissive ); +const _camera = new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); - } else if ( material.isMeshNormalMaterial ) { +// https://github.com/mrdoob/three.js/pull/21358 - _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize(); +class FullscreenTriangleGeometry extends BufferGeometry { - _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + constructor() { - } + super(); - if ( material.wireframe ) { + this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); - style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin; + } - } else { +} - style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; +const _geometry = new FullscreenTriangleGeometry(); - } - addPath( style, path ); +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single full screen quad for rendering + * subsequent passes by just reassigning the `material` reference. + * + * This module can only be used with {@link WebGLRenderer}. + * + * @augments Mesh + * @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; + */ +class FullScreenQuad { - } + /** + * Constructs a new full screen quad. + * + * @param {?Material} material - The material to render te full screen quad with. + */ + constructor( material ) { - // Hide anti-alias gaps + this._mesh = new Mesh( _geometry, material ); - function expand( v1, v2, pixels ) { + } - let x = v2.x - v1.x, y = v2.y - v1.y; - const det = x * x + y * y; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the instance is no longer used in your app. + */ + dispose() { - if ( det === 0 ) return; + this._mesh.geometry.dispose(); - const idet = pixels / Math.sqrt( det ); + } - x *= idet; y *= idet; + /** + * Renders the full screen quad. + * + * @param {WebGLRenderer} renderer - The renderer. + */ + render( renderer ) { - v2.x += x; v2.y += y; - v1.x -= x; v1.y -= y; + renderer.render( this._mesh, _camera ); - } + } - function addPath( style, path ) { + /** + * The quad's material. + * + * @type {?Material} + */ + get material() { - if ( _currentStyle === style ) { + return this._mesh.material; - _currentPath += path; + } - } else { + set material( value ) { - flushPath(); + this._mesh.material = value; - _currentStyle = style; - _currentPath = path; + } - } +} - } +/** + * This pass can be used to create a post processing effect + * with a raw GLSL shader object. Useful for implementing custom + * effects. + * + * ```js + * const fxaaPass = new ShaderPass( FXAAShader ); + * composer.addPass( fxaaPass ); + * ``` + * + * @augments Pass + * @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; + */ +class ShaderPass extends Pass { - function flushPath() { + /** + * Constructs a new shader pass. + * + * @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as + * defines and uniforms. It's also valid to pass a custom shader material. + * @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample + * the read buffer. + */ + constructor( shader, textureID = 'tDiffuse' ) { - if ( _currentPath ) { + super(); - _svgNode = getPathNode( _pathCount ++ ); - _svgNode.setAttribute( 'd', _currentPath ); - _svgNode.setAttribute( 'style', _currentStyle ); - _svg.appendChild( _svgNode ); + /** + * The name of the texture uniform that should sample the read buffer. + * + * @type {string} + * @default 'tDiffuse' + */ + this.textureID = textureID; - } + /** + * The pass uniforms. + * + * @type {?Object} + */ + this.uniforms = null; - _currentPath = ''; - _currentStyle = ''; + /** + * The pass material. + * + * @type {?ShaderMaterial} + */ + this.material = null; - } + if ( shader instanceof ShaderMaterial ) { - function getPathNode( id ) { + this.uniforms = shader.uniforms; - if ( _svgPathPool[ id ] == null ) { + this.material = shader; - _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); + } else if ( shader ) { - if ( _quality == 0 ) { + this.uniforms = UniformsUtils.clone( shader.uniforms ); - _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed + this.material = new ShaderMaterial( { - } + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', + defines: Object.assign( {}, shader.defines ), + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader - return _svgPathPool[ id ]; + } ); - } + } - return _svgPathPool[ id ]; + // internals - } + this._fsQuad = new FullScreenQuad( this.material ); } -} + /** + * Performs the shader pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { -/** @ummary Create three.js Font with Helvetica - * @private */ -function createHelveticaFont() { -// eslint-disable-next-line -const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; -// eslint-disable-next-line -const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; + if ( this.uniforms[ this.textureID ] ) { - return new Font({ - ascender, boundingBox, cssFontStyle, cssFontWeight, default: helvetiker_regular_typeface, descender, familyName, glyphs, - lineHeight, original_font_information, resolution, underlinePosition, underlineThickness - }); -} + this.uniforms[ this.textureID ].value = readBuffer.texture; -// eslint-disable-next-line -const HelveticerRegularFont = new createHelveticaFont(); + } -/** @ummary Create three.js Color instance, handles optional opacity - * @private */ -function getMaterialArgs(color$1, args) { - if (!args || !isObject(args)) args = {}; + this._fsQuad.material = this.material; - if (isStr(color$1) && (((color$1[0] === '#') && (color$1.length === 9)) || (color$1.indexOf('rgba') >= 0))) { - const col = color(color$1); - args.color = new Color(col.r, col.g, col.b); - args.opacity = col.opacity ?? 1; - args.transparent = args.opacity < 1; - } else - args.color = new Color(color$1); - return args; -} + if ( this.renderToScreen ) { -function createSVGRenderer(as_is, precision, doc) { - if (as_is) { - if (doc !== undefined) - globalThis.docuemnt = doc; - const rndr = new SVGRenderer(); - rndr.setPrecision(precision); - return rndr; - } + renderer.setRenderTarget( null ); + this._fsQuad.render( renderer ); - const excl_style1 = ';stroke-opacity:1;stroke-width:1;stroke-linecap:round', - excl_style2 = ';fill-opacity:1', - doc_wrapper = { - svg_attr: {}, - svg_style: {}, - path_attr: {}, - accPath: '', - createElementNS(ns, kind) { - if (kind === 'path') { - return { - _wrapper: this, - setAttribute(name, value) { - // cut useless fill-opacity:1 at the end of many SVG attributes - if ((name === 'style') && value) { - const pos1 = value.indexOf(excl_style1); - if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) - value = value.slice(0, value.length - excl_style1.length); - const pos2 = value.indexOf(excl_style2); - if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) - value = value.slice(0, value.length - excl_style2.length); - } - this._wrapper.path_attr[name] = value; - } - }; - } + } else { - if (kind !== 'svg') { - console.error(`not supported element for SVGRenderer ${kind}`); - return null; - } + renderer.setRenderTarget( writeBuffer ); + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + this._fsQuad.render( renderer ); - return { - _wrapper: this, - childNodes: [], // may be accessed - make dummy - style: this.svg_style, // for background color - setAttribute(name, value) { - this._wrapper.svg_attr[name] = value; - }, - appendChild(_node) { - this._wrapper.accPath += ``; - this._wrapper.path_attr = {}; - }, - removeChild(_node) { - this.childNodes = []; - } - }; - } - }; + } - let originalDocument; + } - if (isNodeJs()) { - originalDocument = globalThis.document; - globalThis.document = doc_wrapper; - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { - const rndr = new SVGRenderer(); + this.material.dispose(); - if (isNodeJs()) - globalThis.document = originalDocument; + this._fsQuad.dispose(); - rndr.doc_wrapper = doc_wrapper; // use it to get final SVG code + } - rndr.originalRender = rndr.render; +} - rndr.render = function(scene, camera) { - const originalDocument = globalThis.document; - if (isNodeJs()) - globalThis.document = this.doc_wrapper; +/** + * This pass can be used to define a mask during post processing. + * Meaning only areas of subsequent post processing are affected + * which lie in the masking area of this pass. Internally, the masking + * is implemented with the stencil buffer. + * + * ```js + * const maskPass = new MaskPass( scene, camera ); + * composer.addPass( maskPass ); + * ``` + * + * @augments Pass + * @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js'; + */ +class MaskPass extends Pass { - this.originalRender(scene, camera); + /** + * Constructs a new mask pass. + * + * @param {Scene} scene - The 3D objects in this scene will define the mask. + * @param {Camera} camera - The camera. + */ + constructor( scene, camera ) { - if (isNodeJs()) - globalThis.document = originalDocument; - }; + super(); - rndr.clearHTML = function() { - this.doc_wrapper.accPath = ''; - }; + /** + * The scene that defines the mask. + * + * @type {Scene} + */ + this.scene = scene; - rndr.makeOuterHTML = function() { - const wrap = this.doc_wrapper, - _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, - _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; + /** + * The camera. + * + * @type {Camera} + */ + this.camera = camera; - return `${wrap.accPath}`; - }; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ + this.clear = true; - rndr.fillTargetSVG = function(svg) { - if (isNodeJs()) { - const wrap = this.doc_wrapper; + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; - svg.setAttribute('viewBox', wrap.svg_attr.viewBox); - svg.setAttribute('width', wrap.svg_attr.width); - svg.setAttribute('height', wrap.svg_attr.height); - svg.style.background = wrap.svg_style.backgroundColor || ''; + /** + * Whether to inverse the mask or not. + * + * @type {boolean} + * @default false + */ + this.inverse = false; - svg.innerHTML = wrap.accPath; - } else { - const src = this.domElement; + } - svg.setAttribute('viewBox', src.getAttribute('viewBox')); - svg.setAttribute('width', src.getAttribute('width')); - svg.setAttribute('height', src.getAttribute('height')); - svg.style.background = src.style.backgroundColor; + /** + * Performs a mask pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - while (src.firstChild) { - const elem = src.firstChild; - src.removeChild(elem); - svg.appendChild(elem); - } - } - }; + const context = renderer.getContext(); + const state = renderer.state; - rndr.setPrecision(precision); + // don't update color or depth - return rndr; -} + state.buffers.color.setMask( false ); + state.buffers.depth.setMask( false ); + // lock buffers -/** @ummary Define rendering kind which will be used for rendering of 3D elements - * @param {value} [render3d] - preconfigured value, will be used if applicable - * @param {value} [is_batch] - is batch mode is configured - * @return {value} - rendering kind, see constants.Render3D - * @private */ -function getRender3DKind(render3d, is_batch) { - if (is_batch === undefined) - is_batch = isBatchMode(); + state.buffers.color.setLocked( true ); + state.buffers.depth.setLocked( true ); - if (!render3d) render3d = is_batch ? settings.Render3DBatch : settings.Render3D; - const rc = constants$1.Render3D; + // set up stencil - if (render3d === rc.Default) render3d = is_batch ? rc.WebGLImage : rc.WebGL; - if (is_batch && (render3d === rc.WebGL)) render3d = rc.WebGLImage; + let writeValue, clearValue; - return render3d; -} + if ( this.inverse ) { -const Handling3DDrawings = { + writeValue = 0; + clearValue = 1; - /** @summary Access current 3d mode - * @param {string} [new_value] - when specified, set new 3d mode - * @return current value - * @private */ - access3dKind(new_value) { - const svg = this.getPadSvg(); - if (svg.empty()) return -1; + } else { - // returns kind of currently created 3d canvas - const kind = svg.property('can3d'); - if (new_value !== undefined) svg.property('can3d', new_value); - return ((kind === null) || (kind === undefined)) ? -1 : kind; - }, + writeValue = 1; + clearValue = 0; - /** @summary Returns size which availble for 3D drawing. - * @desc One uses frame sizes for the 3D drawing - like TH2/TH3 objects - * @private */ - getSizeFor3d(can3d /*, render3d */) { - if (can3d === undefined) { - // analyze which render/embed mode can be used - can3d = getRender3DKind(); - // all non-webgl elements can be embedded into SVG as is - if (can3d !== constants$1.Render3D.WebGL) - can3d = constants$1.Embed3D.EmbedSVG; - else if (settings.Embed3D !== constants$1.Embed3D.Default) - can3d = settings.Embed3D; - else if (browser.isFirefox) - can3d = constants$1.Embed3D.Embed; - else if (browser.chromeVersion > 95) - // version 96 works partially, 97 works fine - can3d = constants$1.Embed3D.Embed; - else - can3d = constants$1.Embed3D.Overlay; - } + } - const pad = this.getPadSvg(), - clname = 'draw3d_' + (this.getPadName() || 'canvas'); + state.buffers.stencil.setTest( true ); + state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE ); + state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff ); + state.buffers.stencil.setClear( clearValue ); + state.buffers.stencil.setLocked( true ); - if (pad.empty()) { - // this is a case when object drawn without canvas + // draw into the stencil buffer - const rect = getElementRect(this.selectDom()); - if ((rect.height < 10) && (rect.width > 10)) { - rect.height = Math.round(0.66 * rect.width); - this.selectDom().style('height', rect.height + 'px'); - } - rect.x = 0; rect.y = 0; rect.clname = clname; rect.can3d = -1; - return rect; - } + renderer.setRenderTarget( readBuffer ); + if ( this.clear ) renderer.clear(); + renderer.render( this.scene, this.camera ); - const fp = this.getFramePainter(), pp = this.getPadPainter(); - let size; + renderer.setRenderTarget( writeBuffer ); + if ( this.clear ) renderer.clear(); + renderer.render( this.scene, this.camera ); - if (fp?.mode3d && (can3d > 0)) - size = fp.getFrameRect(); - else { - let elem = (can3d > 0) ? pad : this.getCanvSvg(); - size = { x: 0, y: 0, width: elem.property('draw_width'), height: elem.property('draw_height') }; - if (Number.isNaN(size.width) || Number.isNaN(size.height)) { - size.width = pp.getPadWidth(); - size.height = pp.getPadHeight(); - } else if (fp && !fp.mode3d) { - elem = this.getFrameSvg(); - size.x = elem.property('draw_x'); - size.y = elem.property('draw_y'); - } - } + // unlock color and depth buffer and make them writable for subsequent rendering/clearing - size.clname = clname; - size.can3d = can3d; + state.buffers.color.setLocked( false ); + state.buffers.depth.setLocked( false ); - const rect = pp?.getPadRect(); - if (rect) { - // while 3D canvas uses area also for the axis labels, extend area relative to normal frame - const dx = Math.round(size.width*0.07), dy = Math.round(size.height*0.05); + state.buffers.color.setMask( true ); + state.buffers.depth.setMask( true ); - size.x = Math.max(0, size.x-dx); - size.y = Math.max(0, size.y-dy); - size.width = Math.min(size.width + 2*dx, rect.width - size.x); - size.height = Math.min(size.height + 2*dy, rect.height - size.y); - } + // only render where stencil is set to 1 - if (can3d === 1) - size = getAbsPosInCanvas(this.getPadSvg(), size); + state.buffers.stencil.setLocked( false ); + state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 + state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP ); + state.buffers.stencil.setLocked( true ); - return size; - }, + } - /** @summary Clear all 3D drawings - * @return can3d value - how webgl canvas was placed - * @private */ - clear3dCanvas() { - const can3d = this.access3dKind(null); - if (can3d < 0) { - // remove first child from main element - if it is canvas - const main = this.selectDom().node(); - let chld = main?.firstChild; +} - if (chld && !chld.$jsroot) - chld = chld.nextSibling; +/** + * This pass can be used to clear a mask previously defined with {@link MaskPass}. + * + * ```js + * const clearPass = new ClearMaskPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + */ +class ClearMaskPass extends Pass { - if (chld?.$jsroot) { - delete chld.painter; - main.removeChild(chld); - } - return can3d; - } + /** + * Constructs a new clear mask pass. + */ + constructor() { - const size = this.getSizeFor3d(can3d); - if (size.can3d === 0) { - select(this.getCanvSvg().node().nextSibling).remove(); // remove html5 canvas - this.getCanvSvg().style('display', null); // show SVG canvas - } else { - if (this.getPadSvg().empty()) return; + super(); - this.apply3dSize(size).remove(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; - this.getFrameSvg().style('display', null); // clear display property - } - return can3d; - }, + } - /** @summary Add 3D canvas - * @private */ - add3dCanvas(size, canv, webgl) { - if (!canv || (size.can3d < -1)) return; + /** + * Performs the clear of the currently defined mask. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - if (size.can3d === -1) { - // case when 3D object drawn without canvas + renderer.state.buffers.stencil.setLocked( false ); + renderer.state.buffers.stencil.setTest( false ); - const main = this.selectDom().node(); - if (main !== null) { - main.appendChild(canv); - canv.painter = this; - canv.$jsroot = true; // mark canvas as added by jsroot - } + } - return; - } +} - if ((size.can3d > 0) && !webgl) - size.can3d = constants$1.Embed3D.EmbedSVG; +/** + * Used to implement post-processing effects in three.js. + * The class manages a chain of post-processing passes to produce the final visual result. + * Post-processing passes are executed in order of their addition/insertion. + * The last pass is automatically rendered to screen. + * + * This module can only be used with {@link WebGLRenderer}. + * + * ```js + * const composer = new EffectComposer( renderer ); + * + * // adding some passes + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * + * const outputPass = new OutputPass() + * composer.addPass( outputPass ); + * + * function animate() { + * + * composer.render(); // instead of renderer.render() + * + * } + * ``` + * + * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; + */ +class EffectComposer { - this.access3dKind(size.can3d); + /** + * Constructs a new effect composer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will + * be used as the internal read and write buffers. If not given, the composer creates + * the buffers automatically. + */ + constructor( renderer, renderTarget ) { - if (size.can3d === 0) { - this.getCanvSvg().style('display', 'none'); // hide SVG canvas + /** + * The renderer. + * + * @type {WebGLRenderer} + */ + this.renderer = renderer; - this.getCanvSvg().node().parentNode.appendChild(canv); // add directly - } else { - if (this.getPadSvg().empty()) return; + this._pixelRatio = renderer.getPixelRatio(); - // first hide normal frame - this.getFrameSvg().style('display', 'none'); + if ( renderTarget === undefined ) { - const elem = this.apply3dSize(size); - elem.attr('title', '').node().appendChild(canv); - } - }, + const size = renderer.getSize( new Vector2() ); + this._width = size.width; + this._height = size.height; - /** @summary Apply size to 3D elements - * @private */ - apply3dSize(size, onlyget) { - if (size.can3d < 0) - return select(null); + renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); + renderTarget.texture.name = 'EffectComposer.rt1'; - let elem; + } else { - if (size.can3d > 1) { - elem = this.getLayerSvg(size.clname); - if (onlyget) - return elem; + this._width = renderTarget.width; + this._height = renderTarget.height; - const svg = this.getPadSvg(); + } - if (size.can3d === constants$1.Embed3D.EmbedSVG) { - // this is SVG mode or image mode - just create group to hold element + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); + this.renderTarget2.texture.name = 'EffectComposer.rt2'; - if (elem.empty()) - elem = svg.insert('g', '.primitives_layer').attr('class', size.clname); + /** + * A reference to the internal write buffer. Passes usually write + * their result into this buffer. + * + * @type {WebGLRenderTarget} + */ + this.writeBuffer = this.renderTarget1; - makeTranslate(elem, size.x, size.y); - } else { - if (elem.empty()) - elem = svg.insert('foreignObject', '.primitives_layer').attr('class', size.clname); + /** + * A reference to the internal read buffer. Passes usually read + * the previous render result from this buffer. + * + * @type {WebGLRenderTarget} + */ + this.readBuffer = this.renderTarget2; - elem.attr('x', size.x) - .attr('y', size.y) - .attr('width', size.width) - .attr('height', size.height) - .attr('viewBox', `0 0 ${size.width} ${size.height}`) - .attr('preserveAspectRatio', 'xMidYMid'); - } - } else { - let prnt = this.getCanvSvg().node().parentNode; + /** + * Whether the final pass is rendered to the screen (default framebuffer) or not. + * + * @type {boolean} + * @default true + */ + this.renderToScreen = true; - elem = select(prnt).select('.' + size.clname); - if (onlyget) - return elem; + /** + * An array representing the (ordered) chain of post-processing passes. + * + * @type {Array} + */ + this.passes = []; - // force redraw by resize - this.getCanvSvg().property('redraw_by_resize', true); + /** + * A copy pass used for internal swap operations. + * + * @private + * @type {ShaderPass} + */ + this.copyPass = new ShaderPass( CopyShader ); + this.copyPass.material.blending = NoBlending; - if (elem.empty()) { - elem = select(prnt).append('div').attr('class', size.clname) - .style('user-select', 'none'); - } + /** + * The internal clock for managing time data. + * + * @private + * @type {Clock} + */ + this.clock = new Clock(); - // our position inside canvas, but to set 'absolute' position we should use - // canvas element offset relative to first parent with non-static position - // now try to use getBoundingClientRect - it should be more precise + } - const pos0 = prnt.getBoundingClientRect(), doc = getDocument(); + /** + * Swaps the internal read/write buffers. + */ + swapBuffers() { - while (prnt) { - if (prnt === doc) { prnt = null; break; } - try { - if (getComputedStyle(prnt).position !== 'static') break; - } catch (err) { - break; - } - prnt = prnt.parentNode; - } + const tmp = this.readBuffer; + this.readBuffer = this.writeBuffer; + this.writeBuffer = tmp; - const pos1 = prnt?.getBoundingClientRect() ?? { top: 0, left: 0 }, - offx = Math.round(pos0.left - pos1.left), - offy = Math.round(pos0.top - pos1.top); + } - elem.style('position', 'absolute').style('left', (size.x + offx) + 'px').style('top', (size.y + offy) + 'px').style('width', size.width + 'px').style('height', size.height + 'px'); - } + /** + * Adds the given pass to the pass chain. + * + * @param {Pass} pass - The pass to add. + */ + addPass( pass ) { - return elem; - } + this.passes.push( pass ); + pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); -}; // Handling3DDrawings + } + /** + * Inserts the given pass at a given index. + * + * @param {Pass} pass - The pass to insert. + * @param {number} index - The index into the pass chain. + */ + insertPass( pass, index ) { -/** @summary Assigns method to handle 3D drawings inside SVG - * @private */ -function assign3DHandler(painter) { - Object.assign(painter, Handling3DDrawings); -} + this.passes.splice( index, 0, pass ); + pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + } -/** @summary Creates renderer for the 3D drawings - * @param {value} width - rendering width - * @param {value} height - rendering height - * @param {value} render3d - render type, see {@link constants.Render3D} - * @param {object} args - different arguments for creating 3D renderer - * @return {Promise} with renderer object - * @private */ -async function createRender3D(width, height, render3d, args) { - const rc = constants$1.Render3D, doc = getDocument(); + /** + * Removes the given pass from the pass chain. + * + * @param {Pass} pass - The pass to remove. + */ + removePass( pass ) { - render3d = getRender3DKind(render3d); + const index = this.passes.indexOf( pass ); - if (!args) args = { antialias: true, alpha: true }; + if ( index !== -1 ) { - let promise; + this.passes.splice( index, 1 ); - if (render3d === rc.SVG) { - // SVG rendering - const r = createSVGRenderer(false, 0, doc); - r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); - promise = Promise.resolve(r); - } else if (isNodeJs()) { - // try to use WebGL inside node.js - need to create headless context - promise = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(node_canvas => { - args.canvas = node_canvas.default.createCanvas(width, height); - args.canvas.addEventListener = () => {}; // dummy - args.canvas.removeEventListener = () => {}; // dummy - args.canvas.style = {}; - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); - }).then(node_gl => { - const gl = node_gl.default(width, height, { preserveDrawingBuffer: true }); - if (!gl) throw Error('Fail to create headless-gl'); - args.context = gl; - gl.canvas = args.canvas; + } - const r = new WebGLRenderer(args); - r.jsroot_output = new WebGLRenderTarget(width, height); - r.setRenderTarget(r.jsroot_output); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - return r; - }); - } else if (render3d === rc.WebGL) { - // interactive WebGL Rendering - promise = Promise.resolve(new WebGLRenderer(args)); - } else { - // rendering with WebGL directly into svg image - const r = new WebGLRenderer(args); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - promise = Promise.resolve(r); - } + } - return promise.then(renderer => { - if (!renderer.jsroot_dom) - renderer.jsroot_dom = renderer.domElement; - else - renderer.jsroot_custom_dom = true; + /** + * Returns `true` if the pass for the given index is the last enabled pass in the pass chain. + * + * @param {number} passIndex - The pass index. + * @return {boolean} Whether the pass for the given index is the last pass in the pass chain. + */ + isLastEnabledPass( passIndex ) { - // res.renderer.setClearColor('#000000', 1); - // res.renderer.setClearColor(0x0, 0); - renderer.jsroot_render3d = render3d; + for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { - // which format used to convert into images - renderer.jsroot_image_format = 'png'; + if ( this.passes[ i ].enabled ) { - renderer.originalSetSize = renderer.setSize; + return false; - // apply size to dom element - renderer.setSize = function(width, height, updateStyle) { - if (this.jsroot_custom_dom) { - this.jsroot_dom.setAttribute('width', width); - this.jsroot_dom.setAttribute('height', height); - } + } - this.originalSetSize(width, height, updateStyle); - }; + } - renderer.setSize(width, height); + return true; - return renderer; - }); -} + } + /** + * Executes all enabled post-processing passes in order to produce the final frame. + * + * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes + * its own time delta value. + */ + render( deltaTime ) { -/** @summary Cleanup created renderer object - * @private */ -function cleanupRender3D(renderer) { - if (!renderer) return; + // deltaTime value is in seconds - if (isNodeJs()) { - const ctxt = isFunc(renderer.getContext) ? renderer.getContext() : null, - ext = ctxt?.getExtension('STACKGL_destroy_context'); - if (isFunc(ext?.destroy)) - ext.destroy(); - } else { - // suppress warnings in Chrome about lost webgl context, not required in firefox - if (browser.isChrome && isFunc(renderer.forceContextLoss)) - renderer.forceContextLoss(); + if ( deltaTime === undefined ) { - if (isFunc(renderer.dispose)) - renderer.dispose(); - } -} + deltaTime = this.clock.getDelta(); -/** @summary Cleanup previous renderings before doing next one - * @desc used together with SVG - * @private */ -function beforeRender3D(renderer) { - if (isFunc(renderer.clearHTML)) - renderer.clearHTML(); -} + } -/** @summary Post-process result of rendering - * @desc used together with SVG or node.js image rendering - * @private */ -function afterRender3D(renderer) { - const rc = constants$1.Render3D; + const currentRenderTarget = this.renderer.getRenderTarget(); - if (renderer.jsroot_render3d === rc.WebGL) - return; + let maskActive = false; - if (renderer.jsroot_render3d === rc.SVG) { - // case of SVGRenderer - renderer.fillTargetSVG(renderer.jsroot_dom); - } else if (isNodeJs()) { - // this is WebGL rendering in node.js - const canvas = renderer.domElement, - context = canvas.getContext('2d'), - pixels = new Uint8Array(4 * canvas.width * canvas.height); + for ( let i = 0, il = this.passes.length; i < il; i ++ ) { - renderer.readRenderTargetPixels(renderer.jsroot_output, 0, 0, canvas.width, canvas.height, pixels); + const pass = this.passes[ i ]; - // small code to flip Y scale - let indx1 = 0, indx2 = (canvas.height - 1) * 4 * canvas.width, k, d; - while (indx1 < indx2) { - for (k = 0; k < 4 * canvas.width; ++k) { - d = pixels[indx1 + k]; pixels[indx1 + k] = pixels[indx2 + k]; pixels[indx2 + k] = d; - } - indx1 += 4 * canvas.width; - indx2 -= 4 * canvas.width; - } + if ( pass.enabled === false ) continue; - const imageData = context.createImageData(canvas.width, canvas.height); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); + pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); + pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); - const format = 'image/' + renderer.jsroot_image_format, - dataUrl = canvas.toDataURL(format); + if ( pass.needsSwap ) { - renderer.jsroot_dom.setAttribute('href', dataUrl); - } else { - const dataUrl = renderer.domElement.toDataURL('image/' + renderer.jsroot_image_format); - renderer.jsroot_dom.setAttribute('href', dataUrl); - } -} + if ( maskActive ) { -// ======================================================================================================== + const context = this.renderer.getContext(); + const stencil = this.renderer.state.buffers.stencil; -/** - * @summary Tooltip handler for 3D drawings - * - * @private - */ + //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); + stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); -class TooltipFor3D { + this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); - /** @summary constructor - * @param {object} dom - DOM element - * @param {object} canvas - canvas for 3D rendering */ - constructor(prnt, canvas) { - this.tt = null; - this.cont = null; - this.lastlbl = ''; - this.parent = prnt || getDocument().body; - this.canvas = canvas; // we need canvas to recalculate mouse events - this.abspos = !prnt; - } + //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); + stencil.setFunc( context.EQUAL, 1, 0xffffffff ); - /** @summary check parent */ - checkParent(prnt) { - if (prnt && (this.parent !== prnt)) { - this.hide(); - this.parent = prnt; - } - } + } - /** @summary extract position from event - * @desc can be used to process it later when event is gone */ - extract_pos(e) { - if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) return e; - const res = { u: 0, l: 0 }; - if (this.abspos) { - res.l = e.pageX; - res.u = e.pageY; - } else { - res.l = e.offsetX; - res.u = e.offsetY; - } - return res; - } + this.swapBuffers(); - /** @summary Method used to define position of next tooltip - * @desc event is delivered from canvas, - * but position should be calculated relative to the element where tooltip is placed */ - pos(e) { - if (!this.tt) return; + } - const pos = this.extract_pos(e); - if (!this.abspos) { - const rect1 = this.parent.getBoundingClientRect(), - rect2 = this.canvas.getBoundingClientRect(); + if ( MaskPass !== undefined ) { - if ((rect1.left !== undefined) && (rect2.left!== undefined)) - pos.l += (rect2.left-rect1.left); + if ( pass instanceof MaskPass ) { - if ((rect1.top !== undefined) && (rect2.top!== undefined)) - pos.u += rect2.top-rect1.top; + maskActive = true; - if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth) - pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3; + } else if ( pass instanceof ClearMaskPass ) { - if (pos.u + this.tt.offsetHeight + 15 >= this.parent.offsetHeight) - pos.u = this.parent.offsetHeight - this.tt.offsetHeight - 15; + maskActive = false; - // one should find parent with non-static position, - // all absolute coordinates calculated relative to such node - let abs_parent = this.parent; - while (abs_parent) { - const style = getComputedStyle(abs_parent); - if (!style || (style.position !== 'static')) break; - if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) break; - abs_parent = abs_parent.parentNode; - } + } - if (abs_parent && (abs_parent !== this.parent)) { - const rect0 = abs_parent.getBoundingClientRect(); - pos.l += (rect1.left - rect0.left); - pos.u += (rect1.top - rect0.top); - } - } + } - this.tt.style.top = `${pos.u+15}px`; - this.tt.style.left = `${pos.l+3}px`; - } + } - /** @summary Show tooltip */ - show(v /* , mouse_pos, status_func */) { - if (!v) return this.hide(); + this.renderer.setRenderTarget( currentRenderTarget ); - if (isObject(v) && (v.lines || v.line)) { - if (v.only_status) return this.hide(); + } - if (v.line) - v = v.line; - else { - let res = v.lines[0]; - for (let n = 1; n < v.lines.length; ++n) - res += '
' + v.lines[n]; - v = res; - } - } + /** + * Resets the internal state of the EffectComposer. + * + * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like + * the one from the constructor. If set, it is used to setup the read and write buffers. + */ + reset( renderTarget ) { - if (this.tt === null) { - const doc = getDocument(); - this.tt = doc.createElement('div'); - this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; overflow: hidden; z-index: 101;'); - this.cont = doc.createElement('div'); - this.cont.setAttribute('style', 'display: block; padding: 2px 12px 3px 7px; margin-left: 5px; font-size: 11px; background: #777; color: #fff;'); - this.tt.appendChild(this.cont); - this.parent.appendChild(this.tt); - } - - if (this.lastlbl !== v) { - this.cont.innerHTML = v; - this.lastlbl = v; - this.tt.style.width = 'auto'; // let it be automatically resizing... - } - } - - /** @summary Hide tooltip */ - hide() { - if (this.tt !== null) - this.parent.removeChild(this.tt); + if ( renderTarget === undefined ) { - this.tt = null; - this.lastlbl = ''; - } + const size = this.renderer.getSize( new Vector2() ); + this._pixelRatio = this.renderer.getPixelRatio(); + this._width = size.width; + this._height = size.height; -} // class TooltipFor3D + renderTarget = this.renderTarget1.clone(); + renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); -/** @summary Create OrbitControls for painter - * @private */ -function createOrbitControl(painter, camera, scene, renderer, lookat) { - const enable_zoom = settings.Zooming && settings.ZoomMouse, - enable_select = isFunc(painter.processMouseClick); + } - let control = null; + this.renderTarget1.dispose(); + this.renderTarget2.dispose(); + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); - function control_mousedown(evnt) { - if (!control) return; + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; - // function used to hide some events from orbit control and redirect them to zooming rect - if (control.mouse_zoom_mesh) { - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } + } - // only left-button is considered - if ((evnt.button!==undefined) && (evnt.button !== 0)) return; - if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return; + /** + * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize}, + * this method honors the current pixel ration. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + */ + setSize( width, height ) { - if (control.enable_zoom) { - control.mouse_zoom_mesh = control.detectZoomMesh(evnt); - if (control.mouse_zoom_mesh) { - // just block orbit control - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } - } + this._width = width; + this._height = height; - if (control.enable_select) - control.mouse_select_pnt = control.getMousePos(evnt, {}); - } + const effectiveWidth = this._width * this._pixelRatio; + const effectiveHeight = this._height * this._pixelRatio; - function control_mouseup(evnt) { - if (!control) return; + this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); + this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); - if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.get3dZoomCoord) { - let kind = control.mouse_zoom_mesh.object.zoom, - pos1 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point, kind), - pos2 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point2, kind); + for ( let i = 0; i < this.passes.length; i ++ ) { - if (pos1 > pos2) - [pos1, pos2] = [pos2, pos1]; + this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); - if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) kind = 'y'; + } - // try to zoom - if ((pos1 < pos2) && control.painter.zoom(kind, pos1, pos2)) - control.mouse_zoom_mesh = null; - } + } - // if selection was drawn, it should be removed and picture rendered again - if (control.enable_zoom) - control.removeZoomMesh(); + /** + * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output. + * Setting the pixel ratio will automatically resize the composer. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ + setPixelRatio( pixelRatio ) { - // only left-button is considered - // if ((evnt.button!==undefined) && (evnt.button !== 0)) return; - // if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return; + this._pixelRatio = pixelRatio; - if (control.enable_select && control.mouse_select_pnt) { - const pnt = control.getMousePos(evnt, {}), - same_pnt = (pnt.x === control.mouse_select_pnt.x) && (pnt.y === control.mouse_select_pnt.y); - delete control.mouse_select_pnt; + this.setSize( this._width, this._height ); - if (same_pnt) { - const intersects = control.getMouseIntersects(pnt); - control.painter.processMouseClick(pnt, intersects, evnt); - } - } - } + } - function render3DFired(painter) { - if (!painter || painter.renderer === undefined) return false; - return painter.render_tmout !== undefined; // when timeout configured, object is prepared for rendering - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the composer is no longer used in your app. + */ + dispose() { - function control_mousewheel(evnt) { - if (!control) return; + this.renderTarget1.dispose(); + this.renderTarget2.dispose(); - // try to handle zoom extra - if (render3DFired(control.painter) || control.mouse_zoom_mesh) { - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); - return; // already fired redraw, do not react on the mouse wheel - } + this.copyPass.dispose(); - const intersect = control.detectZoomMesh(evnt); - if (!intersect) return; + } - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); +} - if (isFunc(control.painter?.analyzeMouseWheelEvent)) { - let kind = intersect.object.zoom, - position = intersect.point[kind]; - const item = { name: kind, ignore: false }; +/** + * This class represents a render pass. It takes a camera and a scene and produces + * a beauty pass for subsequent post processing effects. + * + * ```js + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * ``` + * + * @augments Pass + * @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; + */ +class RenderPass extends Pass { - // z changes from 0..2*size_z3d, others -size_x3d..+size_x3d - switch (kind) { - case 'x': position = (position + control.painter.size_x3d)/2/control.painter.size_x3d; break; - case 'y': position = (position + control.painter.size_y3d)/2/control.painter.size_y3d; break; - case 'z': position = position/2/control.painter.size_z3d; break; - } + /** + * Constructs a new render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used + * for all objects in the scene. + * @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass. + * @param {?number} [clearAlpha=null] - The clear alpha of the render pass. + */ + constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { - control.painter.analyzeMouseWheelEvent(evnt, item, position, false); + super(); - if ((kind === 'z') && intersect.object.use_y_for_z) kind = 'y'; + /** + * The scene to render. + * + * @type {Scene} + */ + this.scene = scene; - control.painter.zoom(kind, item.min, item.max); - } - } + /** + * The camera. + * + * @type {Camera} + */ + this.camera = camera; - // assign own handler before creating OrbitControl + /** + * The override material. If set, this material is used + * for all objects in the scene. + * + * @type {?Material} + * @default null + */ + this.overrideMaterial = overrideMaterial; - if (settings.Zooming && settings.ZoomWheel) - renderer.domElement.addEventListener('wheel', control_mousewheel); + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default null + */ + this.clearColor = clearColor; - if (enable_zoom || enable_select) { - renderer.domElement.addEventListener('pointerdown', control_mousedown); - renderer.domElement.addEventListener('pointerup', control_mouseup); - } + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default null + */ + this.clearAlpha = clearAlpha; - control = new OrbitControls(camera, renderer.domElement); + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ + this.clear = true; - control.enableDamping = false; - control.dampingFactor = 1.0; - control.enableZoom = true; - control.enableKeys = settings.HandleKeys; + /** + * If set to `true`, only the depth can be cleared when `clear` is to `false`. + * + * @type {boolean} + * @default false + */ + this.clearDepth = false; - if (lookat) { - control.target.copy(lookat); - control.target0.copy(lookat); - control.update(); - } + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + this._oldClearColor = new Color(); - control.tooltip = new TooltipFor3D(painter.selectDom().node(), renderer.domElement); + } - control.painter = painter; - control.camera = camera; - control.scene = scene; - control.renderer = renderer; - control.raycaster = new Raycaster(); - control.raycaster.params.Line.threshold = 10; - control.raycaster.params.Points.threshold = 5; - control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming - control.block_ctxt = false; // require to block context menu command appearing after control ends, required in chrome which inject contextmenu when key released - control.block_mousemove = false; // when true, tooltip or cursor will not react on mouse move - control.cursor_changed = false; - control.control_changed = false; - control.control_active = false; - control.mouse_ctxt = { x: 0, y: 0, on: false }; - control.enable_zoom = enable_zoom; - control.enable_select = enable_select; + /** + * Performs a beauty pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - control.cleanup = function() { - if (settings.Zooming && settings.ZoomWheel) - this.domElement.removeEventListener('wheel', control_mousewheel); - if (this.enable_zoom || this.enable_select) { - this.domElement.removeEventListener('pointerdown', control_mousedown); - this.domElement.removeEventListener('pointerup', control_mouseup); - } + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; - this.domElement.removeEventListener('click', this.lstn_click); - this.domElement.removeEventListener('dblclick', this.lstn_dblclick); - this.domElement.removeEventListener('contextmenu', this.lstn_contextmenu); - this.domElement.removeEventListener('mousemove', this.lstn_mousemove); - this.domElement.removeEventListener('mouseleave', this.lstn_mouseleave); + let oldClearAlpha, oldOverrideMaterial; - this.dispose(); // this is from OrbitControl itself + if ( this.overrideMaterial !== null ) { - this.tooltip.hide(); - delete this.tooltip; - delete this.painter; - delete this.camera; - delete this.scene; - delete this.renderer; - delete this.raycaster; - delete this.mouse_zoom_mesh; - }; + oldOverrideMaterial = this.scene.overrideMaterial; - control.HideTooltip = function() { - this.tooltip.hide(); - }; + this.scene.overrideMaterial = this.overrideMaterial; - control.getMousePos = function(evnt, mouse) { - mouse.x = ('offsetX' in evnt) ? evnt.offsetX : evnt.layerX; - mouse.y = ('offsetY' in evnt) ? evnt.offsetY : evnt.layerY; - mouse.clientX = evnt.clientX; - mouse.clientY = evnt.clientY; - return mouse; - }; + } - control.getOriginDirectionIntersects = function(origin, direction) { - this.raycaster.set(origin, direction); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); - return intersects; - }; + if ( this.clearColor !== null ) { - control.getMouseIntersects = function(mouse) { - // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer - if (!this.renderer) return []; + renderer.getClearColor( this._oldClearColor ); + renderer.setClearColor( this.clearColor, renderer.getClearAlpha() ); - const sz = (this.renderer instanceof SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new Vector2()), - pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 }; + } - this.camera.updateMatrix(); - this.camera.updateMatrixWorld(); - this.raycaster.setFromCamera(pnt, this.camera); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); + if ( this.clearAlpha !== null ) { - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); + oldClearAlpha = renderer.getClearAlpha(); + renderer.setClearAlpha( this.clearAlpha ); - return intersects; - }; + } - control.detectZoomMesh = function(evnt) { - const mouse = this.getMousePos(evnt, {}), - intersects = this.getMouseIntersects(mouse); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - return intersects[n]; - } - } + if ( this.clearDepth == true ) { - return null; - }; + renderer.clearDepth(); - control.getInfoAtMousePosition = function(mouse_pos) { - const intersects = this.getMouseIntersects(mouse_pos); - let tip = null, painter = null; + } - for (let i = 0; i < intersects.length; ++i) { - if (intersects[i].object.tooltip) { - tip = intersects[i].object.tooltip(intersects[i]); - painter = intersects[i].object.painter; - break; - } - } + renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); - if (tip && painter) { - return { obj: painter.getObject(), name: painter.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1+tip.x2)/2, gry: (tip.y1+tip.y2)/2, grz: (tip.z1+tip.z2)/2 }; - } - }; + if ( this.clear === true ) { - control.processDblClick = function(evnt) { - // first check if zoom mesh clicked - const zoom_intersect = this.detectZoomMesh(evnt); - if (zoom_intersect && this.painter) { - this.painter.unzoom(zoom_intersect.object.use_y_for_z ? 'y' : zoom_intersect.object.zoom); - return; - } + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - // then check if double-click handler assigned - const fp = this.painter?.getFramePainter(); - if (isFunc(fp?._dblclick_handler)) { - const info = this.getInfoAtMousePosition(this.getMousePos(evnt, {})); - if (info) { - fp._dblclick_handler(info); - return; - } - } + } - this.reset(); - }; + renderer.render( this.scene, this.camera ); - control.changeEvent = function() { - this.mouse_ctxt.on = false; // disable context menu if any changes where done by orbit control - this.painter.render3D(0); - this.control_changed = true; - }; + // restore - control.startEvent = function() { - this.control_active = true; - this.block_ctxt = false; - this.mouse_ctxt.on = false; + if ( this.clearColor !== null ) { - this.tooltip.hide(); + renderer.setClearColor( this._oldClearColor ); - // do not reset here, problem of events sequence in orbitcontrol - // it issue change/start/stop event when do zooming - // control.control_changed = false; - }; + } - control.endEvent = function() { - this.control_active = false; - if (this.mouse_ctxt.on) { - this.mouse_ctxt.on = false; - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - } /* else if (this.control_changed) { - // react on camera change when required - } */ - this.control_changed = false; - }; + if ( this.clearAlpha !== null ) { - control.mainProcessContextMenu = function(evnt) { - evnt.preventDefault(); - this.getMousePos(evnt, this.mouse_ctxt); - if (this.control_active) - this.mouse_ctxt.on = true; - else if (this.block_ctxt) - this.block_ctxt = false; - else - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - }; + renderer.setClearAlpha( oldClearAlpha ); - control.contextMenu = function(/* pos, intersects */) { - // do nothing, function called when context menu want to be activated - }; + } - control.setTooltipEnabled = function(on) { - this.block_mousemove = !on; - if (on === false) { - this.tooltip.hide(); - this.removeZoomMesh(); - } - }; + if ( this.overrideMaterial !== null ) { - control.removeZoomMesh = function() { - if (this.mouse_zoom_mesh?.object.showSelection()) - this.painter.render3D(); - this.mouse_zoom_mesh = null; // in any case clear mesh, enable orbit control again - }; + this.scene.overrideMaterial = oldOverrideMaterial; - control.mainProcessMouseMove = function(evnt) { - if (!this.painter) return; // protect when cleanup + } - if (this.control_active && evnt.buttons && (evnt.buttons & 2)) - this.block_ctxt = true; // if right button in control was active, block next context menu + renderer.autoClear = oldAutoClear; - if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) return; + } - if (this.mouse_zoom_mesh) { - // when working with zoom mesh, need special handling +} - const zoom2 = this.detectZoomMesh(evnt), - pnt2 = (zoom2?.object === this.mouse_zoom_mesh.object) ? zoom2.point : this.mouse_zoom_mesh.object.globalIntersect(this.raycaster); +/** + * @module LuminosityHighPassShader + * @three_import import { LuminosityHighPassShader } from 'three/addons/shaders/LuminosityHighPassShader.js'; + */ - if (pnt2) this.mouse_zoom_mesh.point2 = pnt2; +/** + * Luminosity high pass shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const LuminosityHighPassShader = { - if (pnt2 && this.painter.enable_highlight) { - if (this.mouse_zoom_mesh.object.showSelection(this.mouse_zoom_mesh.point, pnt2)) - this.painter.render3D(0); - } + uniforms: { - this.tooltip.hide(); - return; - } + 'tDiffuse': { value: null }, + 'luminosityThreshold': { value: 1.0 }, + 'smoothWidth': { value: 1.0 }, + 'defaultColor': { value: new Color( 0x000000 ) }, + 'defaultOpacity': { value: 0.0 } - evnt.preventDefault(); + }, - // extract mouse position - this.tmout_mouse = this.getMousePos(evnt, {}); - this.tmout_ttpos = this.tooltip?.extract_pos(evnt); + vertexShader: /* glsl */` - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } + varying vec2 vUv; - if (!this.mouse_tmout) - this.delayedProcessMouseMove(); - else - this.tmout_handle = setTimeout(() => this.delayedProcessMouseMove(), this.mouse_tmout); - }; + void main() { - control.delayedProcessMouseMove = function() { - // remove handle - allow to trigger new timeout - delete this.tmout_handle; - if (!this.painter) return; // protect when cleanup + vUv = uv; - const mouse = this.tmout_mouse, - intersects = this.getMouseIntersects(mouse), - tip = this.processMouseMove(intersects); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - if (tip) { - let name = '', title = '', coord = '', info = ''; - if (mouse) coord = mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0); - if (isStr(tip)) - info = tip; - else { - name = tip.name; title = tip.title; - if (tip.line) info = tip.line; else - if (tip.lines) { info = tip.lines.slice(1).join(' '); name = tip.lines[0]; } - } - this.painter.showObjectStatus(name, title, info, coord); - } + }`, - this.cursor_changed = false; - if (tip && this.painter?.isTooltipAllowed()) { - this.tooltip.checkParent(this.painter.selectDom().node()); + fragmentShader: /* glsl */` - this.tooltip.show(tip, mouse); - this.tooltip.pos(this.tmout_ttpos); - } else { - this.tooltip.hide(); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - this.cursor_changed = true; - } - } - } + uniform sampler2D tDiffuse; + uniform vec3 defaultColor; + uniform float defaultOpacity; + uniform float luminosityThreshold; + uniform float smoothWidth; - getDocument().body.style.cursor = this.cursor_changed ? 'pointer' : 'auto'; - }; + varying vec2 vUv; - control.mainProcessMouseLeave = function() { - if (!this.painter) return; // protect when cleanup + void main() { - // do not enter main event at all - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } - this.tooltip.hide(); - if (isFunc(this.processMouseLeave)) - this.processMouseLeave(); - if (this.cursor_changed) { - getDocument().body.style.cursor = 'auto'; - this.cursor_changed = false; - } - }; + vec4 texel = texture2D( tDiffuse, vUv ); - control.mainProcessDblClick = function(evnt) { - // suppress simple click handler if double click detected - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } - this.processDblClick(evnt); - }; + float v = luminance( texel.xyz ); - control.processClick = function(mouse_pos, kind) { - delete this.single_click_tm; + vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity ); - if (kind === 1) { - const fp = this.painter?.getFramePainter(); - if (isFunc(fp?._click_handler)) { - const info = this.getInfoAtMousePosition(mouse_pos); - if (info) { - fp._click_handler(info); - return; - } - } - } + float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v ); - // method assigned in the Eve7 and used for object selection - if ((kind === 2) && isFunc(this.processSingleClick)) { - const intersects = this.getMouseIntersects(mouse_pos); - this.processSingleClick(intersects); - } - }; + gl_FragColor = mix( outputColor, texel, alpha ); - control.lstn_click = function(evnt) { - // ignore right-mouse click - if (evnt.detail === 2) return; + }` - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } +}; - let kind = 0; - if (isFunc(this.painter?.getFramePainter()?._click_handler)) - kind = 1; // user click handler - else if (this.processSingleClick && this.painter?.options?.mouse_click) - kind = 2; // eve7 click handler +/** + * This pass is inspired by the bloom pass of Unreal Engine. It creates a + * mip map chain of bloom textures and blurs them with different radii. Because + * of the weighted combination of mips, and because larger blurs are done on + * higher mips, this effect provides good quality and performance. + * + * When using this pass, tone mapping must be enabled in the renderer settings. + * + * Reference: + * - [Bloom in Unreal Engine]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/} + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ); + * composer.addPass( bloomPass ); + * ``` + * + * @augments Pass + * @three_import import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; + */ +class UnrealBloomPass extends Pass { - // if normal event, set longer timeout waiting if double click not detected - if (kind) - this.single_click_tm = setTimeout(this.processClick.bind(this, this.getMousePos(evnt, {}), kind), 300); - }.bind(control); + /** + * Constructs a new Unreal Bloom pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {number} [strength=1] - The Bloom strength. + * @param {number} radius - The Bloom radius. + * @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect. + */ + constructor( resolution, strength = 1, radius, threshold ) { - control.addEventListener('change', () => control.changeEvent()); - control.addEventListener('start', () => control.startEvent()); - control.addEventListener('end', () => control.endEvent()); + super(); - control.lstn_contextmenu = evnt => control.mainProcessContextMenu(evnt); - control.lstn_dblclick = evnt => control.mainProcessDblClick(evnt); - control.lstn_mousemove = evnt => control.mainProcessMouseMove(evnt); - control.lstn_mouseleave = () => control.mainProcessMouseLeave(); + /** + * The Bloom strength. + * + * @type {number} + * @default 1 + */ + this.strength = strength; - renderer.domElement.addEventListener('click', control.lstn_click); - renderer.domElement.addEventListener('dblclick', control.lstn_dblclick); - renderer.domElement.addEventListener('contextmenu', control.lstn_contextmenu); - renderer.domElement.addEventListener('mousemove', control.lstn_mousemove); - renderer.domElement.addEventListener('mouseleave', control.lstn_mouseleave); + /** + * The Bloom radius. + * + * @type {number} + */ + this.radius = radius; - return control; -} + /** + * The luminance threshold limits which bright areas contribute to the Bloom effect. + * + * @type {number} + */ + this.threshold = threshold; -/** @summary Method cleanup three.js object as much as possible. - * @desc Simplify JS engine to remove it from memory - * @private */ -function disposeThreejsObject(obj, only_childs) { - if (!obj) return; + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ + this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - if (obj.children) { - for (let i = 0; i < obj.children.length; i++) - disposeThreejsObject(obj.children[i]); - } + /** + * The effect's clear color + * + * @type {Color} + * @default (0,0,0) + */ + this.clearColor = new Color( 0, 0, 0 ); - if (only_childs) { - obj.children = []; - return; - } + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; - obj.children = undefined; + // internals - if (obj.geometry) { - obj.geometry.dispose(); - obj.geometry = undefined; - } - if (obj.material) { - if (obj.material.map) { - obj.material.map.dispose(); - obj.material.map = undefined; - } - obj.material.dispose(); - obj.material = undefined; - } + // render targets + this.renderTargetsHorizontal = []; + this.renderTargetsVertical = []; + this.nMips = 5; + let resx = Math.round( this.resolution.x / 2 ); + let resy = Math.round( this.resolution.y / 2 ); - // cleanup jsroot fields to simplify browser cleanup job - delete obj.painter; - delete obj.bins_index; - delete obj.tooltip; - delete obj.stack; // used in geom painter - delete obj.drawn_highlight; // special highlight object + this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; + this.renderTargetBright.texture.generateMipmaps = false; - obj = undefined; -} + for ( let i = 0; i < this.nMips; i ++ ) { + const renderTargetHorizontal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); -/** @summary Create LineSegments mesh (or only geometry) - * @desc If required, calculates lineDistance attribute for dashed geometries - * @private */ -function createLineSegments(arr, material, index = undefined, only_geometry = false) { - const geom = new BufferGeometry(); + renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizontal.texture.generateMipmaps = false; - geom.setAttribute('position', arr instanceof Float32Array ? new BufferAttribute(arr, 3) : new Float32BufferAttribute(arr, 3)); - if (index) geom.setIndex(new BufferAttribute(index, 1)); + this.renderTargetsHorizontal.push( renderTargetHorizontal ); - if (material.isLineDashedMaterial) { - const v1 = new Vector3(), - v2 = new Vector3(); - let d = 0, distances = null; + const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - if (index) { - distances = new Float32Array(index.length); - for (let n = 0; n < index.length; n += 2) { - const i1 = index[n], i2 = index[n+1]; - v1.set(arr[i1], arr[i1+1], arr[i1+2]); - v2.set(arr[i2], arr[i2+1], arr[i2+2]); - distances[n] = d; - d += v2.distanceTo(v1); - distances[n+1] = d; - } - } else { - distances = new Float32Array(arr.length/3); - for (let n = 0; n < arr.length; n += 6) { - v1.set(arr[n], arr[n+1], arr[n+2]); - v2.set(arr[n+3], arr[n+4], arr[n+5]); - distances[n/3] = d; - d += v2.distanceTo(v1); - distances[n/3+1] = d; - } - } - geom.setAttribute('lineDistance', new BufferAttribute(distances, 1)); - } + renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; + renderTargetVertical.texture.generateMipmaps = false; - return only_geometry ? geom : new LineSegments(geom, material); -} + this.renderTargetsVertical.push( renderTargetVertical ); -/** @summary Help structures for calculating Box mesh - * @private */ -const Box3D = { - Vertices: [new Vector3(1, 1, 1), new Vector3(1, 1, 0), - new Vector3(1, 0, 1), new Vector3(1, 0, 0), - new Vector3(0, 1, 0), new Vector3(0, 1, 1), - new Vector3(0, 0, 0), new Vector3(0, 0, 1)], - Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, - 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], - Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], - Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices - MeshSegments: undefined -}; + resx = Math.round( resx / 2 ); -// these segments address vertices from the mesh, we can use positions from box mesh -Box3D.MeshSegments = (function() { - const arr = new Int32Array(Box3D.Segments.length); + resy = Math.round( resy / 2 ); - for (let n = 0; n < arr.length; ++n) { - for (let k = 0; k < Box3D.Indexes.length; ++k) { - if (Box3D.Segments[n] === Box3D.Indexes[k]) { - arr[n] = k; - break; - } - } - } - return arr; -})(); + } + // luminosity high pass material -/** - * @summary Abstract interactive control interface for 3D objects - * - * @abstract - * @private - */ + const highPassShader = LuminosityHighPassShader; + this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); -class InteractiveControl { + this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; + this.highPassUniforms[ 'smoothWidth' ].value = 0.01; - cleanup() {} - extractIndex(/* intersect */) {} - setSelected(/* col, indx */) {} - setHighlight(/* col, indx */) {} - checkHighlightIndex(/* indx */) {} + this.materialHighPassFilter = new ShaderMaterial( { + uniforms: this.highPassUniforms, + vertexShader: highPassShader.vertexShader, + fragmentShader: highPassShader.fragmentShader + } ); -} // class InteractiveControl + // gaussian blur materials + this.separableBlurMaterials = []; + const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; + resx = Math.round( this.resolution.x / 2 ); + resy = Math.round( this.resolution.y / 2 ); -/** - * @summary Class for creation of 3D points - * - * @private - */ + for ( let i = 0; i < this.nMips; i ++ ) { -class PointsCreator { + this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); - /** @summary constructor - * @param {number} number - number of points - * @param {boolean} [iswebgl] - if WebGL is used - * @param {number} [scale] - scale factor */ - constructor(number, iswebgl = true, scale = 1) { - this.webgl = iswebgl; - this.scale = scale || 1; + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - this.pos = new Float32Array(number*3); - this.geom = new BufferGeometry(); - this.geom.setAttribute('position', new BufferAttribute(this.pos, 3)); - this.indx = 0; - } + resx = Math.round( resx / 2 ); - /** @summary Add point */ - addPoint(x, y, z) { - this.pos[this.indx] = x; - this.pos[this.indx+1] = y; - this.pos[this.indx+2] = z; - this.indx += 3; - } + resy = Math.round( resy / 2 ); - /** @summary Create points */ - createPoints(args) { - if (!isObject(args)) - args = { color: args }; - if (!args.color) - args.color = 'black'; + } - let k = 1; + // composite material - // special dots - if (!args.style) k = 1.1; else - if (args.style === 1) k = 0.3; else - if (args.style === 6) k = 0.5; else - if (args.style === 7) k = 0.7; + this.compositeMaterial = this._getCompositeMaterial( this.nMips ); + this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; + this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; - const makePoints = texture => { - const material_args = { size: 3*this.scale*k }; - if (texture) { - material_args.map = texture; - material_args.transparent = true; - } else - material_args.color = args.color || 'black'; + const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; + this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; + this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - const pnts = new Points(this.geom, new PointsMaterial(material_args)); - pnts.nvertex = 1; - return pnts; - }; + // blend material - // this is plain creation of points, no need for texture loading + this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); - if (k !== 1) { - const res = makePoints(); - return this.noPromise ? res : Promise.resolve(res); - } + this.blendMaterial = new ShaderMaterial( { + uniforms: this.copyUniforms, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true + } ); - const handler = new TAttMarkerHandler({ style: args.style, color: args.color, size: 7 }), - w = handler.fill ? 1 : 7, - imgdata = '' + - ``+ - '', - dataUrl = 'data:image/svg+xml;charset=utf8,' + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); - let promise; + this._oldClearColor = new Color(); + this._oldClearAlpha = 1; - if (isNodeJs()) { - promise = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => handle.default.loadImage(dataUrl).then(img => { - const canvas = handle.default.createCanvas(64, 64), - ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, 64, 64); - return new CanvasTexture(canvas); - })); - } else if (this.noPromise) { - // only for v6 support - return makePoints(new TextureLoader().load(dataUrl)); - } else { - promise = new Promise((resolveFunc, rejectFunc) => { - const loader = new TextureLoader(); - // eslint-disable-next-line prefer-promise-reject-errors - loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc()); - }); - } + this._basic = new MeshBasicMaterial(); - return promise.then(makePoints); - } + this._fsQuad = new FullScreenQuad( null ); -} // class PointsCreator + } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { -/** @summary Create material for 3D line - * @desc Takes into account dashed properties - * @private */ -function create3DLineMaterial(painter, arg, is_v7 = false) { - if (!painter || !arg) return null; + for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { - let color, lstyle, lwidth; - if (isStr(arg) || is_v7) { - color = painter.v7EvalColor(arg+'color', 'black'); - lstyle = parseInt(painter.v7EvalAttr(arg+'style', 0)); - lwidth = parseInt(painter.v7EvalAttr(arg+'width', 1)); - } else { - color = painter.getColor(arg.fLineColor); - lstyle = arg.fLineStyle; - lwidth = arg.fLineWidth; - } + this.renderTargetsHorizontal[ i ].dispose(); - const style = lstyle ? getSvgLineStyle(lstyle) : '', - dash = style ? style.split(',') : [], - material = (dash && dash.length >= 2) - ? new LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) - : new LineBasicMaterial({ color }); + } - if (lwidth && (lwidth > 1)) material.linewidth = lwidth; + for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { - return material; -} + this.renderTargetsVertical[ i ].dispose(); -/** - * A math namespace - all functions can be exported from base/math.mjs. - * Also all these functions can be used with TFormula calcualtions - * @namespace Math - */ + } -/* eslint-disable no-loss-of-precision */ -/* eslint-disable space-in-parens */ -/* eslint-disable curly */ -/* eslint-disable operator-linebreak */ -/* eslint-disable no-floating-decimal */ -/* eslint-disable brace-style */ -/* eslint-disable comma-spacing */ + this.renderTargetBright.dispose(); -// this can be improved later + // -/* eslint-disable no-unreachable-loop */ -/* eslint-disable eqeqeq */ + for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { -const kMACHEP = 1.11022302462515654042363166809e-16, - kMINLOG = -708.396418532264078748994506896, - kMAXLOG = 709.782712893383973096206318587, - kMAXSTIR = 108.116855767857671821730036754, - kBig = 4.503599627370496e15, - kBiginv = 2.22044604925031308085e-16, - kSqrt2 = 1.41421356237309515, - M_PI = 3.14159265358979323846264338328; + this.separableBlurMaterials[ i ].dispose(); -/** @summary Polynomialeval function - * @desc calculates a value of a polynomial of the form: - * a[0]x^N+a[1]x^(N-1) + ... + a[N] - * @memberof Math */ -function Polynomialeval(x, a, N) { - if (!N) return a[0]; + } - let pom = a[0]; - for (let i = 1; i <= N; ++i) - pom = pom *x + a[i]; - return pom; -} + this.compositeMaterial.dispose(); + this.blendMaterial.dispose(); + this._basic.dispose(); -/** @summary Polynomial1eval function - * @desc calculates a value of a polynomial of the form: - * x^N+a[0]x^(N-1) + ... + a[N-1] - * @memberof Math */ -function Polynomial1eval(x, a, N) { - if (!N) return a[0]; + // - let pom = x + a[0]; - for (let i = 1; i < N; ++i) - pom = pom *x + a[i]; - return pom; -} + this._fsQuad.dispose(); -/** @summary lgam function, logarithm from gamma - * @memberof Math */ -function lgam(x) { - let p, q, u, w, z; - const kMAXLGM = 2.556348e305, - LS2PI = 0.91893853320467274178, - A = [ - 8.11614167470508450300E-4, - -5.95061904284301438324E-4, - 7.93650340457716943945E-4, - -2.77777777730099687205E-3, - 8.33333333333331927722E-2 - ], B = [ - -1.37825152569120859100E3, - -3.88016315134637840924E4, - -3.31612992738871184744E5, - -1.16237097492762307383E6, - -1.72173700820839662146E6, - -8.53555664245765465627E5 - ], C = [ - /* 1.00000000000000000000E0, */ - -3.51815701436523470549E2, - -1.70642106651881159223E4, - -2.20528590553854454839E5, - -1.13933444367982507207E6, - -2.53252307177582951285E6, - -2.01889141433532773231E6 - ]; + } - if ((x >= Number.MAX_VALUE) || (x == Number.POSITIVE_INFINITY)) - return Number.POSITIVE_INFINITY; + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { - if ( x < -34.0 ) { - q = -x; - w = lgam(q); - p = Math.floor(q); - if ( p === q ) // _unur_FP_same(p,q) - return Number.POSITIVE_INFINITY; - z = q - p; - if ( z > 0.5 ) { - p += 1.0; - z = p - q; - } - z = q * Math.sin( Math.PI * z ); - if ( z < 1e-300 ) - return Number.POSITIVE_INFINITY; - z = Math.log(Math.PI) - Math.log( z ) - w; - return z; - } - if ( x < 13.0 ) { - z = 1.0; - p = 0.0; - u = x; - while ( u >= 3.0 ) { - p -= 1.0; - u = x + p; - z *= u; - } - while ( u < 2.0 ) { - if ( u < 1e-300 ) - return Number.POSITIVE_INFINITY; - z /= u; - p += 1.0; - u = x + p; - } - if ( z < 0.0 ) { - z = -z; - } - if ( u === 2.0 ) - return Math.log(z); - p -= 2.0; - x = x + p; - p = x * Polynomialeval(x, B, 5 ) / Polynomial1eval( x, C, 6); - return Math.log(z) + p; - } - if ( x > kMAXLGM ) - return Number.POSITIVE_INFINITY; + let resx = Math.round( width / 2 ); + let resy = Math.round( height / 2 ); - q = ( x - 0.5 ) * Math.log(x) - x + LS2PI; - if ( x > 1.0e8 ) - return q; + this.renderTargetBright.setSize( resx, resy ); - p = 1.0/(x*x); - if ( x >= 1000.0 ) - q += ((7.9365079365079365079365e-4 * p - - 2.7777777777777777777778e-3) *p - + 0.0833333333333333333333) / x; - else - q += Polynomialeval( p, A, 4 ) / x; - return q; -} + for ( let i = 0; i < this.nMips; i ++ ) { -/** @summary Stirling formula for the gamma function - * @memberof Math */ -function stirf(x) { - let y, w, v; + this.renderTargetsHorizontal[ i ].setSize( resx, resy ); + this.renderTargetsVertical[ i ].setSize( resx, resy ); - const STIR = [ - 7.87311395793093628397E-4, - -2.29549961613378126380E-4, - -2.68132617805781232825E-3, - 3.47222221605458667310E-3, - 8.33333333333482257126E-2 - ], SQTPI = Math.sqrt(2*Math.PI); + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - w = 1.0/x; - w = 1.0 + w * Polynomialeval( w, STIR, 4 ); - y = Math.exp(x); + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); -/* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ + } - if ( x > kMAXSTIR ) - { /* Avoid overflow in pow() */ - v = Math.pow( x, 0.5 * x - 0.25 ); - y = v * (v / y); - } - else - { - y = Math.pow( x, x - 0.5 ) / y; - } - y = SQTPI * y * w; - return y; -} + } -/** @summary complementary error function - * @memberof Math */ -function erfc(a) { - const erfP = [ - 2.46196981473530512524E-10, - 5.64189564831068821977E-1, - 7.46321056442269912687E0, - 4.86371970985681366614E1, - 1.96520832956077098242E2, - 5.26445194995477358631E2, - 9.34528527171957607540E2, - 1.02755188689515710272E3, - 5.57535335369399327526E2 - ], erfQ = [ - 1.32281951154744992508E1, - 8.67072140885989742329E1, - 3.54937778887819891062E2, - 9.75708501743205489753E2, - 1.82390916687909736289E3, - 2.24633760818710981792E3, - 1.65666309194161350182E3, - 5.57535340817727675546E2 - ], erfR = [ - 5.64189583547755073984E-1, - 1.27536670759978104416E0, - 5.01905042251180477414E0, - 6.16021097993053585195E0, - 7.40974269950448939160E0, - 2.97886665372100240670E0 - ], erfS = [ - 2.26052863220117276590E0, - 9.39603524938001434673E0, - 1.20489539808096656605E1, - 1.70814450747565897222E1, - 9.60896809063285878198E0, - 3.36907645100081516050E0 - ]; + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - let p,q,x,y,z; + renderer.getClearColor( this._oldClearColor ); + this._oldClearAlpha = renderer.getClearAlpha(); + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; - if ( a < 0.0 ) - x = -a; - else - x = a; + renderer.setClearColor( this.clearColor, 0 ); - if ( x < 1.0 ) - return 1.0 - erf(a); + if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - z = -a * a; + // Render input to screen - if (z < -kMAXLOG) - return (a < 0) ? 2.0 : 0.0; + if ( this.renderToScreen ) { - z = Math.exp(z); + this._fsQuad.material = this._basic; + this._basic.map = readBuffer.texture; - if ( x < 8.0 ) { - p = Polynomialeval( x, erfP, 8 ); - q = Polynomial1eval( x, erfQ, 8 ); - } else { - p = Polynomialeval( x, erfR, 5 ); - q = Polynomial1eval( x, erfS, 6 ); - } - y = (z * p)/q; + renderer.setRenderTarget( null ); + renderer.clear(); + this._fsQuad.render( renderer ); - if (a < 0) - y = 2.0 - y; + } - if (y == 0) - return (a < 0) ? 2.0 : 0.0; + // 1. Extract Bright Areas - return y; -} + this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; + this._fsQuad.material = this.materialHighPassFilter; -/** @summary error function - * @memberof Math */ -function erf(x) { - if (Math.abs(x) > 1.0) - return 1.0 - erfc(x); + renderer.setRenderTarget( this.renderTargetBright ); + renderer.clear(); + this._fsQuad.render( renderer ); - const erfT = [ - 9.60497373987051638749E0, - 9.00260197203842689217E1, - 2.23200534594684319226E3, - 7.00332514112805075473E3, - 5.55923013010394962768E4 - ], erfU = [ - 3.35617141647503099647E1, - 5.21357949780152679795E2, - 4.59432382970980127987E3, - 2.26290000613890934246E4, - 4.92673942608635921086E4 - ], + // 2. Blur All the mips progressively - z = x * x; + let inputRenderTarget = this.renderTargetBright; - return x * Polynomialeval(z, erfT, 4) / Polynomial1eval(z, erfU, 5); -} + for ( let i = 0; i < this.nMips; i ++ ) { -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf_c(x, m, s, x0) { - if (x0 === undefined) x0 = 0; - const z = (Math.log((x-x0))-m)/(s*kSqrt2); - if (z > 1.) return 0.5*erfc(z); - else return 0.5*(1.0 - erf(z)); -} + this._fsQuad.material = this.separableBlurMaterials[ i ]; -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf(x, m, s, x0 = 0) { - const z = (Math.log((x-x0))-m)/(s*kSqrt2); - if (z < -1.) return 0.5*erfc(-z); - else return 0.5*(1.0 + erf(z)); -} + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; + renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); + renderer.clear(); + this._fsQuad.render( renderer ); -/** @summary normal_cdf_c function - * @memberof Math */ -function normal_cdf_c(x, sigma, x0 = 0) { - const z = (x-x0)/(sigma*kSqrt2); - if (z > 1.) return 0.5*erfc(z); - else return 0.5*(1.-erf(z)); -} + this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; + this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; + renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); + renderer.clear(); + this._fsQuad.render( renderer ); -/** @summary normal_cdf function - * @memberof Math */ -function normal_cdf(x, sigma, x0 = 0) { - const z = (x-x0)/(sigma*kSqrt2); - if (z < -1.) return 0.5*erfc(-z); - else return 0.5*(1.0 + erf(z)); -} + inputRenderTarget = this.renderTargetsVertical[ i ]; -/** @summary log normal pdf - * @memberof Math */ -function lognormal_pdf(x, m, s, x0 = 0) { - if ((x-x0) <= 0) - return 0.0; - const tmp = (Math.log((x-x0)) - m)/s; - return 1.0 / ((x-x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) /2); -} + } -/** @summary normal pdf - * @memberof Math */ -function normal_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x-x0)/sigma; - return (1.0/(Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp*tmp/2); -} + // Composite All the mips -/** @summary gamma calculation - * @memberof Math */ -function gamma(x) { - let p, q, z, i, sgngam = 1; + this._fsQuad.material = this.compositeMaterial; + this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; + this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; + this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - if (x >= Number.MAX_VALUE) - return x; + renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); + renderer.clear(); + this._fsQuad.render( renderer ); - q = Math.abs(x); + // Blend it additively over the input texture - if ( q > 33.0 ) - { - if ( x < 0.0 ) - { - p = Math.floor(q); - if ( p == q ) - return Number.POSITIVE_INFINITY; - i = Math.round(p); - if ( (i & 1) == 0 ) - sgngam = -1; - z = q - p; - if ( z > 0.5 ) - { - p += 1.0; - z = q - p; - } - z = q * Math.sin(Math.PI * z); - if ( z == 0 ) - { - return sgngam > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - } - z = Math.abs(z); - z = Math.PI / (z * stirf(q)); - } - else - { - z = stirf(x); - } - return sgngam * z; - } + this._fsQuad.material = this.blendMaterial; + this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; - z = 1.0; - while ( x >= 3.0 ) { - x -= 1.0; - z *= x; - } + if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - let small = false; + if ( this.renderToScreen ) { - while (( x < 0.0 ) && !small) { - if ( x > -1.E-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } + renderer.setRenderTarget( null ); + this._fsQuad.render( renderer ); - while (( x < 2.0 ) && !small) { - if ( x < 1.e-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } + } else { - if (small) { - if ( x == 0 ) - return Number.POSITIVE_INFINITY; - else - return z/((1.0 + 0.5772156649015329 * x) * x); - } + renderer.setRenderTarget( readBuffer ); + this._fsQuad.render( renderer ); - if ( x == 2.0 ) - return z; + } - const P = [ - 1.60119522476751861407E-4, - 1.19135147006586384913E-3, - 1.04213797561761569935E-2, - 4.76367800457137231464E-2, - 2.07448227648435975150E-1, - 4.94214826801497100753E-1, - 9.99999999999999996796E-1 - ], Q = [ - -2.31581873324120129819E-5, - 5.39605580493303397842E-4, - -4.45641913851797240494E-3, - 1.18139785222060435552E-2, - 3.58236398605498653373E-2, - -2.34591795718243348568E-1, - 7.14304917030273074085E-2, - 1.00000000000000000320E0]; + // Restore renderer settings - x -= 2.0; - p = Polynomialeval( x, P, 6 ); - q = Polynomialeval( x, Q, 7 ); - return z * p / q; -} + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); + renderer.autoClear = oldAutoClear; -/** @summary ndtri function - * @memberof Math */ -function ndtri(y0) { - if ( y0 <= 0.0 ) - return Number.NEGATIVE_INFINITY; - if ( y0 >= 1.0 ) - return Number.POSITIVE_INFINITY; + } - const P0 = [ - -5.99633501014107895267E1, - 9.80010754185999661536E1, - -5.66762857469070293439E1, - 1.39312609387279679503E1, - -1.23916583867381258016E0 - ], Q0 = [ - 1.95448858338141759834E0, - 4.67627912898881538453E0, - 8.63602421390890590575E1, - -2.25462687854119370527E2, - 2.00260212380060660359E2, - -8.20372256168333339912E1, - 1.59056225126211695515E1, - -1.18331621121330003142E0 - ], P1 = [ - 4.05544892305962419923E0, - 3.15251094599893866154E1, - 5.71628192246421288162E1, - 4.40805073893200834700E1, - 1.46849561928858024014E1, - 2.18663306850790267539E0, - -1.40256079171354495875E-1, - -3.50424626827848203418E-2, - -8.57456785154685413611E-4 - ], Q1 = [ - 1.57799883256466749731E1, - 4.53907635128879210584E1, - 4.13172038254672030440E1, - 1.50425385692907503408E1, - 2.50464946208309415979E0, - -1.42182922854787788574E-1, - -3.80806407691578277194E-2, - -9.33259480895457427372E-4 - ], P2 = [ - 3.23774891776946035970E0, - 6.91522889068984211695E0, - 3.93881025292474443415E0, - 1.33303460815807542389E0, - 2.01485389549179081538E-1, - 1.23716634817820021358E-2, - 3.01581553508235416007E-4, - 2.65806974686737550832E-6, - 6.23974539184983293730E-9 - ], Q2 = [ - 6.02427039364742014255E0, - 3.67983563856160859403E0, - 1.37702099489081330271E0, - 2.16236993594496635890E-1, - 1.34204006088543189037E-2, - 3.28014464682127739104E-4, - 2.89247864745380683936E-6, - 6.79019408009981274425E-9 - ], s2pi = 2.50662827463100050242e0, dd = 0.13533528323661269189; + // internals - let code = 1, y = y0, x, y2, x1; + _getSeparableBlurMaterial( kernelRadius ) { - if (y > (1.0 - dd)) { - y = 1.0 - y; - code = 0; - } - if ( y > dd ) { - y = y - 0.5; - y2 = y * y; - x = y + y * (y2 * Polynomialeval( y2, P0, 4)/ Polynomial1eval( y2, Q0, 8 )); - x = x * s2pi; - return x; - } - x = Math.sqrt(-2.0 * Math.log(y)); - const x0 = x - Math.log(x)/x, - z = 1.0/x; - if ( x < 8.0 ) - x1 = z * Polynomialeval( z, P1, 8 )/ Polynomial1eval( z, Q1, 8 ); - else - x1 = z * Polynomialeval( z, P2, 8 )/ Polynomial1eval( z, Q2, 8 ); - x = x0 - x1; - if ( code != 0 ) - x = -x; - return x; -} + const coefficients = []; -/** @summary normal_quantile function - * @memberof Math */ -function normal_quantile(z, sigma) { - return sigma * ndtri(z); -} + for ( let i = 0; i < kernelRadius; i ++ ) { -/** @summary normal_quantile_c function - * @memberof Math */ -function normal_quantile_c(z, sigma) { - return -sigma * ndtri(z); -} + coefficients.push( 0.39894 * Math.exp( -0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); -/** @summary igamc function - * @memberof Math */ -function igamc(a,x) { - // LM: for negative values returns 0.0 - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) return 0.0; + } - if (x <= 0) return 1.0; + return new ShaderMaterial( { - if ((x < 1.0) || (x < a)) - return (1.0 - igam(a,x)); + defines: { + 'KERNEL_RADIUS': kernelRadius + }, - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; + uniforms: { + 'colorTexture': { value: null }, + 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients + }, - ax = Math.exp(ax); + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - /* continued fraction */ - let y = 1.0 - a, - z = x + y + 1.0, - c = 0.0, - pkm2 = 1.0, - qkm2 = x, - pkm1 = x + 1.0, - qkm1 = z * x, - ans = pkm1/qkm1, - yc, r, t, pk, qk; + fragmentShader: + `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 invSize; + uniform vec2 direction; + uniform float gaussianCoefficients[KERNEL_RADIUS]; - do { - c += 1.0; - y += 1.0; - z += 2.0; - yc = y * c; - pk = pkm1 * z - pkm2 * yc; - qk = qkm1 * z - qkm2 * yc; - if (qk) - { - r = pk/qk; - t = Math.abs( (ans - r)/r ); - ans = r; - } - else - t = 1.0; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if ( Math.abs(pk) > kBig ) - { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - } while ( t > kMACHEP ); + void main() { + float weightSum = gaussianCoefficients[0]; + vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; + for( int i = 1; i < KERNEL_RADIUS; i ++ ) { + float x = float(i); + float w = gaussianCoefficients[i]; + vec2 uvOffset = direction * invSize * x; + vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; + vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; + diffuseSum += (sample1 + sample2) * w; + weightSum += 2.0 * w; + } + gl_FragColor = vec4(diffuseSum/weightSum, 1.0); + }` + } ); - return ans * ax; -} + } -/** @summary igam function - * @memberof Math */ -function igam(a, x) { - // LM: for negative values returns 1.0 instead of zero - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) return 1.0; + _getCompositeMaterial( nMips ) { - if (x <= 0) return 0.0; + return new ShaderMaterial( { - if ((x > 1.0) && (x > a)) - return 1.0 - igamc(a,x); + defines: { + 'NUM_MIPS': nMips + }, - /* Compute x**a * exp(-x) / gamma(a) */ - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; + uniforms: { + 'blurTexture1': { value: null }, + 'blurTexture2': { value: null }, + 'blurTexture3': { value: null }, + 'blurTexture4': { value: null }, + 'blurTexture5': { value: null }, + 'bloomStrength': { value: 1.0 }, + 'bloomFactors': { value: null }, + 'bloomTintColors': { value: null }, + 'bloomRadius': { value: 0.0 } + }, - ax = Math.exp(ax); + vertexShader: + `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, - /* power series */ - let r = a, c = 1.0, ans = 1.0; + fragmentShader: + `varying vec2 vUv; + uniform sampler2D blurTexture1; + uniform sampler2D blurTexture2; + uniform sampler2D blurTexture3; + uniform sampler2D blurTexture4; + uniform sampler2D blurTexture5; + uniform float bloomStrength; + uniform float bloomRadius; + uniform float bloomFactors[NUM_MIPS]; + uniform vec3 bloomTintColors[NUM_MIPS]; - do { - r += 1.0; - c *= x/r; - ans += c; - } while ( c/ans > kMACHEP ); + float lerpBloomFactor(const in float factor) { + float mirrorFactor = 1.2 - factor; + return mix(factor, mirrorFactor, bloomRadius); + } - return ans * ax/a; -} + void main() { + gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + + lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + + lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + + lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + + lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); + }` + } ); + } -/** @summary igami function - * @memberof Math */ -function igami(a, y0) { - // check the domain - if (a <= 0) { - console.error(`igami : Wrong domain for parameter a = ${a} (must be > 0)`); - return 0; - } - if (y0 <= 0) { - return Number.POSITIVE_INFINITY; - } - if (y0 >= 1) { - return 0; - } - const kMAXNUM = Number.MAX_VALUE, dithresh = 5.0 * kMACHEP; - let x0 = kMAXNUM, x1 = 0, x, yl = 0, yh = 1, y, d, lgm, i, dir; +} - /* approximation to inverse function */ - d = 1.0/(9.0*a); - y = 1.0 - d - ndtri(y0) * Math.sqrt(d); - x = a * y * y * y; +UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 ); +UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 ); - lgm = lgam(a); +class RenderableObject { - for ( i=0; i<10; ++i ) { - if ( x > x0 || x < x1 ) - break; - y = igamc(a,x); - if ( y < yl || y > yh ) - break; - if ( y < y0 ) { - x0 = x; - yl = y; - } - else { - x1 = x; - yh = y; - } - /* compute the derivative of the function at this point */ - d = (a - 1.0) * Math.log(x) - x - lgm; - if ( d < -kMAXLOG ) - break; - d = -Math.exp(d); - /* compute the step to the next approximation of x */ - d = (y - y0)/d; - if ( Math.abs(d/x) < kMACHEP ) - return x; - x = x - d; - } - /* Resort to interval halving if Newton iteration did not converge. */ - d = 0.0625; - if ( x0 == kMAXNUM ) { - if ( x <= 0.0 ) - x = 1.0; - while ( x0 == kMAXNUM ) { - x = (1.0 + d) * x; - y = igamc( a, x ); - if ( y < y0 ) { - x0 = x; - yl = y; - break; - } - d = d + d; - } - } - d = 0.5; - dir = 0; + constructor() { - for ( i=0; i<400; ++i ) { - x = x1 + d * (x0 - x1); - y = igamc( a, x ); - lgm = (x0 - x1)/(x1 + x0); - if ( Math.abs(lgm) < dithresh ) - break; - lgm = (y - y0)/y0; - if ( Math.abs(lgm) < dithresh ) - break; - if ( x <= 0.0 ) - break; - if ( y >= y0 ) { - x1 = x; - yh = y; - if ( dir < 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir > 1 ) - d = 0.5 * d + 0.5; - else - d = (y0 - yl)/(yh - yl); - dir += 1; - } - else { - x0 = x; - yl = y; - if ( dir > 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir < -1 ) - d = 0.5 * d; - else - d = (y0 - yl)/(yh - yl); - dir -= 1; - } - } - return x; -} + this.id = 0; -/** @summary landau_pdf function - * @desc LANDAU pdf : algorithm from CERNLIB G110 denlan - * same algorithm is used in GSL - * @memberof Math */ -function landau_pdf(x, xi, x0 = 0) { - if (xi <= 0) return 0; - const v = (x - x0)/xi; - let u, ue, us, denlan; - const p1 = [0.4259894875,-0.1249762550, 0.03984243700, -0.006298287635, 0.001511162253], - q1 = [1.0 ,-0.3388260629, 0.09594393323, -0.01608042283, 0.003778942063], - p2 = [0.1788541609, 0.1173957403, 0.01488850518, -0.001394989411, 0.0001283617211], - q2 = [1.0 , 0.7428795082, 0.3153932961, 0.06694219548, 0.008790609714], - p3 = [0.1788544503, 0.09359161662,0.006325387654, 0.00006611667319,-0.000002031049101], - q3 = [1.0 , 0.6097809921, 0.2560616665, 0.04746722384, 0.006957301675], - p4 = [0.9874054407, 118.6723273, 849.2794360, -743.7792444, 427.0262186], - q4 = [1.0 , 106.8615961, 337.6496214, 2016.712389, 1597.063511], - p5 = [1.003675074, 167.5702434, 4789.711289, 21217.86767, -22324.94910], - q5 = [1.0 , 156.9424537, 3745.310488, 9834.698876, 66924.28357], - p6 = [1.000827619, 664.9143136, 62972.92665, 475554.6998, -5743609.109], - q6 = [1.0 , 651.4101098, 56974.73333, 165917.4725, -2815759.939], - a1 = [0.04166666667,-0.01996527778, 0.02709538966], - a2 = [-1.845568670,-4.284640743]; + this.object = null; + this.z = 0; + this.renderOrder = 0; - if (v < -5.5) { - u = Math.exp(v+1.0); - if (u < 1e-10) return 0.0; - ue = Math.exp(-1/u); - us = Math.sqrt(u); - denlan = 0.3989422803*(ue/us)*(1+(a1[0]+(a1[1]+a1[2]*u)*u)*u); - } else if (v < -1) { - u = Math.exp(-v-1); - denlan = Math.exp(-u)*Math.sqrt(u)* - (p1[0]+(p1[1]+(p1[2]+(p1[3]+p1[4]*v)*v)*v)*v)/ - (q1[0]+(q1[1]+(q1[2]+(q1[3]+q1[4]*v)*v)*v)*v); - } else if (v < 1) { - denlan = (p2[0]+(p2[1]+(p2[2]+(p2[3]+p2[4]*v)*v)*v)*v)/ - (q2[0]+(q2[1]+(q2[2]+(q2[3]+q2[4]*v)*v)*v)*v); - } else if (v < 5) { - denlan = (p3[0]+(p3[1]+(p3[2]+(p3[3]+p3[4]*v)*v)*v)*v)/ - (q3[0]+(q3[1]+(q3[2]+(q3[3]+q3[4]*v)*v)*v)*v); - } else if (v < 12) { - u = 1/v; - denlan = u*u*(p4[0]+(p4[1]+(p4[2]+(p4[3]+p4[4]*u)*u)*u)*u)/ - (q4[0]+(q4[1]+(q4[2]+(q4[3]+q4[4]*u)*u)*u)*u); - } else if (v < 50) { - u = 1/v; - denlan = u*u*(p5[0]+(p5[1]+(p5[2]+(p5[3]+p5[4]*u)*u)*u)*u)/ - (q5[0]+(q5[1]+(q5[2]+(q5[3]+q5[4]*u)*u)*u)*u); - } else if (v < 300) { - u = 1/v; - denlan = u*u*(p6[0]+(p6[1]+(p6[2]+(p6[3]+p6[4]*u)*u)*u)*u)/ - (q6[0]+(q6[1]+(q6[2]+(q6[3]+q6[4]*u)*u)*u)*u); - } else { - u = 1/(v-v*Math.log(v)/(v+1)); - denlan = u*u*(1+(a2[0]+a2[1]*u)*u); - } - return denlan/xi; -} + } -/** @summary Landau function - * @memberof Math */ -function Landau(x, mpv, sigma, norm) { - if (sigma <= 0) return 0; - const den = landau_pdf((x - mpv) / sigma, 1, 0); - if (!norm) return den; - return den/sigma; } -/** @summary inc_gamma_c - * @memberof Math */ -function inc_gamma_c(a,x) { - return igamc(a,x); -} +// -/** @summary inc_gamma - * @memberof Math */ -function inc_gamma(a,x) { - return igam(a,x); -} +class RenderableFace { -/** @summary lgamma - * @memberof Math */ -function lgamma(z) { - return lgam(z); -} + constructor() { -/** @summary Probability density function of the beta distribution. - * @memberof Math */ -function beta_pdf(x, a, b) { - if (x < 0 || x > 1.0) return 0; - if (x == 0 ) { - if (a < 1) return Number.POSITIVE_INFINITY; - else if (a > 1) return 0; - else if ( a == 1) return b; // to avoid a nan from log(0)*0 - } - if (x == 1 ) { - if (b < 1) return Number.POSITIVE_INFINITY; - else if (b > 1) return 0; - else if ( b == 1) return a; // to avoid a nan from log(0)*0 - } - return Math.exp(lgamma(a + b) - lgamma(a) - lgamma(b) + - Math.log(x) * (a -1.) + Math.log1p(-x) * (b - 1.)); -} + this.id = 0; -/** @summary beta - * @memberof Math */ -function beta(x,y) { - return Math.exp(lgamma(x)+lgamma(y)-lgamma(x+y)); -} + this.v1 = new RenderableVertex(); + this.v2 = new RenderableVertex(); + this.v3 = new RenderableVertex(); -/** @summary chisquared_cdf_c - * @memberof Math */ -function chisquared_cdf_c(x,r,x0 = 0) { - return inc_gamma_c(0.5 * r, 0.5*(x-x0)); -} + this.normalModel = new Vector3(); -/** @summary Continued fraction expansion #1 for incomplete beta integral - * @memberof Math */ -function incbcf(a,b,x) { - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; - const thresh = 3.0 * kMACHEP; + this.vertexNormalsModel = [ new Vector3(), new Vector3(), new Vector3() ]; + this.vertexNormalsLength = 0; - k1 = a; - k2 = a + b; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = b - 1.0; - k7 = k4; - k8 = a + 2.0; + this.color = new Color(); + this.material = null; + this.uvs = [ new Vector2(), new Vector2(), new Vector2() ]; - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; + this.z = 0; + this.renderOrder = 0; - do { - xk = -( x * k1 * k2 )/( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; + } - xk = ( x * k5 * k6 )/( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; +} - if ( qk !=0 ) - r = pk/qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r)/r ); - ans = r; - } - else - t = 1.0; +// - if ( t < thresh ) - break; // goto cdone; +class RenderableVertex { - k1 += 1.0; - k2 += 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 -= 1.0; - k7 += 2.0; - k8 += 2.0; + constructor() { - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); + this.position = new Vector3(); + this.positionWorld = new Vector3(); + this.positionScreen = new Vector4(); + + this.visible = true; + + } + + copy( vertex ) { + + this.positionWorld.copy( vertex.positionWorld ); + this.positionScreen.copy( vertex.positionScreen ); + + } -// cdone: - return ans; } -/** @summary Continued fraction expansion #2 for incomplete beta integral - * @memberof Math */ -function incbd(a,b,x) { - const z = x / (1.0-x), - thresh = 3.0 * kMACHEP; +// - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; +class RenderableLine { - k1 = a; - k2 = b - 1.0; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = a + b; - k7 = a + 1.0; - k8 = a + 2.0; + constructor() { - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - do { - xk = -( z * k1 * k2 )/( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; + this.id = 0; - xk = ( z * k5 * k6 )/( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; + this.v1 = new RenderableVertex(); + this.v2 = new RenderableVertex(); - if ( qk != 0 ) - r = pk/qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r)/r ); - ans = r; - } - else - t = 1.0; + this.vertexColors = [ new Color(), new Color() ]; + this.material = null; - if ( t < thresh ) - break; // goto cdone; + this.z = 0; + this.renderOrder = 0; - k1 += 1.0; - k2 -= 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 += 1.0; - k7 += 2.0; - k8 += 2.0; + } - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); -// cdone: - return ans; } -/** @summary ROOT::Math::Cephes::pseries - * @memberof Math */ -function pseries(a,b,x) { - let s, t, u, v, n; +// - const ai = 1.0 / a; - u = (1.0 - b) * x; - v = u / (a + 1.0); - const t1 = v; - t = u; - n = 2.0; - s = 0.0; - const z = kMACHEP * ai; - while ( Math.abs(v) > z ) { - u = (n - b) * x / n; - t *= u; - v = t / (a + n); - s += v; - n += 1.0; - } - s += t1; - s += ai; +class RenderableSprite { + + constructor() { + + this.id = 0; + + this.object = null; + + this.x = 0; + this.y = 0; + this.z = 0; + + this.rotation = 0; + this.scale = new Vector2(); + + this.material = null; + this.renderOrder = 0; + + } - u = a * Math.log(x); - if ( (a+b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) - { - t = gamma(a+b) / (gamma(a)*gamma(b)); - s = s * t * Math.pow(x,a); - } - else - { - t = lgam(a+b) - lgam(a) - lgam(b) + u + Math.log(s); - if ( t < kMINLOG ) - s = 0.0; - else - s = Math.exp(t); - } - return s; } -/** @summary ROOT::Math::Cephes::incbet - * @memberof Math */ -function incbet(aa,bb,xx) { - let a, b, t, x, xc, w, y, flag; +/** + * This class can project a given scene in 3D space into a 2D representation + * used for rendering with a 2D API. `Projector` is currently used by {@link SVGRenderer} + * and was previously used by the legacy `CanvasRenderer`. + * + * @three_import import { Projector } from 'three/addons/renderers/Projector.js'; + */ +class Projector { - if ( aa <= 0.0 || bb <= 0.0 ) - return 0.0; + /** + * Constructs a new projector. + */ + constructor() { - // LM: changed: for X > 1 return 1. - if (xx <= 0.0) return 0.0; - if ( xx >= 1.0) return 1.0; + let _object, _objectCount, _objectPoolLength = 0, + _vertex, _vertexCount, _vertexPoolLength = 0, + _face, _faceCount, _facePoolLength = 0, + _line, _lineCount, _linePoolLength = 0, + _sprite, _spriteCount, _spritePoolLength = 0, + _modelMatrix; - flag = 0; + const -/* - to test if that way is better for large b/ (comment out from Cephes version) - if ( (bb * xx) <= 1.0 && xx <= 0.95) - { - t = pseries(aa, bb, xx); - goto done; - } + _renderData = { objects: [], lights: [], elements: [] }, -**/ - w = 1.0 - xx; + _vector3 = new Vector3(), + _vector4 = new Vector4(), -/* Reverse a and b if x is greater than the mean. */ -/* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ - if (xx > (aa/(aa+bb))) - { - flag = 1; - a = bb; - b = aa; - xc = xx; - x = w; - } - else - { - a = aa; - b = bb; - xc = w; - x = xx; - } + _clipBox = new Box3( new Vector3( -1, -1, -1 ), new Vector3( 1, 1, 1 ) ), + _boundingBox = new Box3(), + _points3 = new Array( 3 ), - if ( flag == 1 && (b * x) <= 1.0 && x <= 0.95) { - t = pseries(a, b, x); - // goto done; - } else { - /* Choose expansion for better convergence. */ - y = x * (a+b-2.0) - (a-1.0); - if ( y < 0.0 ) - w = incbcf( a, b, x ); - else - w = incbd( a, b, x ) / xc; + _viewMatrix = new Matrix4(), + _viewProjectionMatrix = new Matrix4(), - /* Multiply w by the factor - a b _ _ _ - x (1-x) | (a+b) / (a | (a) | (b)) . */ + _modelViewProjectionMatrix = new Matrix4(), - y = a * Math.log(x); - t = b * Math.log(xc); - if ( (a+b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) - { - t = Math.pow(xc,b); - t *= Math.pow(x,a); - t /= a; - t *= w; - t *= gamma(a+b) / (gamma(a) * gamma(b)); - // goto done; - } else { - /* Resort to logarithms. */ - y += t + lgam(a+b) - lgam(a) - lgam(b); - y += Math.log(w/a); - if ( y < kMINLOG ) - t = 0.0; - else - t = Math.exp(y); - } - } + _frustum = new Frustum(), -// done: + _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = []; - if (flag == 1) { - if ( t <= kMACHEP ) - t = 1.0 - kMACHEP; - else - t = 1.0 - t; - } - return t; -} + // -/** @summary copy of ROOT::Math::Cephes::incbi - * @memberof Math */ -function incbi(aa,bb,yy0) { - let a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt, - i, rflg, dir, nflg, ihalve = true; + function RenderList() { - // check the domain - if (aa <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter a (must be > 0)'); - return 0; - } - if (bb <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter b (must be > 0)'); - return 0; - } + const normals = []; + const colors = []; + const uvs = []; - const process_done = () => { - if ( rflg ) { - if ( x <= kMACHEP ) - x = 1.0 - kMACHEP; - else - x = 1.0 - x; - } - return x; - }; + let object = null; - i = 0; - if ( yy0 <= 0 ) - return 0.0; - if ( yy0 >= 1.0 ) - return 1.0; - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - nflg = 0; + const normalMatrix = new Matrix3(); - if ( aa <= 1.0 || bb <= 1.0 ) - { - dithresh = 1.0e-6; - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - x = a/(a+b); - y = incbet( a, b, x ); - // goto ihalve; // will start - } - else - { - dithresh = 1.0e-4; -/* approximation to inverse function */ + function setObject( value ) { - yp = -ndtri(yy0); + object = value; - if ( yy0 > 0.5 ) - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - yp = -yp; - } - else - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } + normalMatrix.getNormalMatrix( object.matrixWorld ); - lgm = (yp * yp - 3.0)/6.0; - x = 2.0/(1.0/(2.0*a-1.0) + 1.0/(2.0*b-1.0)); - d = yp * Math.sqrt( x + lgm ) / x - - (1.0/(2.0*b-1.0) - 1.0/(2.0*a-1.0)) - * (lgm + 5.0/6.0 - 2.0/(3.0*x)); - d = 2.0 * d; - if ( d < kMINLOG ) { - // x = 1.0; - // goto under; - x = 0.0; - return process_done(); - } - x = a/(a + b * Math.exp(d)); - y = incbet( a, b, x ); - yp = (y - y0)/y0; - if ( Math.abs(yp) < 0.2 ) - ihalve = false; // instead goto newt; exclude ihalve for the first time - } + normals.length = 0; + colors.length = 0; + uvs.length = 0; - let mainloop = 1000; + } - // endless loop until coverage - while (mainloop-- > 0) { - /* Resort to interval halving if not close enough. */ - // ihalve: - if (ihalve) { - dir = 0; - di = 0.5; - for ( i=0; i<100; i++ ) - { - if ( i != 0 ) - { - x = x0 + di * (x1 - x0); - if ( x == 1.0 ) - x = 1.0 - kMACHEP; - if ( x == 0.0 ) - { - di = 0.5; - x = x0 + di * (x1 - x0); - if ( x == 0.0 ) - return process_done(); // goto under; - } - y = incbet( a, b, x ); - yp = (x1 - x0)/(x1 + x0); - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - yp = (y-y0)/y0; - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - } - if ( y < y0 ) - { - x0 = x; - yl = y; - if ( dir < 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir > 3 ) - di = 1.0 - (1.0 - di) * (1.0 - di); - else if ( dir > 1 ) - di = 0.5 * di + 0.5; - else - di = (y0 - y)/(yh - yl); - dir += 1; - if ( x0 > 0.75 ) - { - if ( rflg == 1 ) - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - else - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - } - x = 1.0 - x; - y = incbet( a, b, x ); - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - continue; // goto ihalve; - } - } - else - { - x1 = x; - if ( rflg == 1 && x1 < kMACHEP ) - { - x = 0.0; - return process_done(); // goto done; - } - yh = y; - if ( dir > 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir < -3 ) - di = di * di; - else if ( dir < -1 ) - di = 0.5 * di; - else - di = (y - y0)/(yh - yl); - dir -= 1; - } - } - // math_error( 'incbi', PLOSS ); - if ( x0 >= 1.0 ) { - x = 1.0 - kMACHEP; - return process_done(); // goto done; - } - if ( x <= 0.0 ) { - // math_error( 'incbi', UNDERFLOW ); - x = 0.0; - return process_done(); // goto done; - } - break; // if here, break ihalve - } // end of ihalve + function projectVertex( vertex ) { - ihalve = true; // enter loop next time + const position = vertex.position; + const positionWorld = vertex.positionWorld; + const positionScreen = vertex.positionScreen; - // newt: + positionWorld.copy( position ).applyMatrix4( _modelMatrix ); + positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); - if ( nflg ) - return process_done(); // goto done; - nflg = 1; - lgm = lgam(a+b) - lgam(a) - lgam(b); + const invW = 1 / positionScreen.w; - for ( i=0; i<8; i++ ) - { - /* Compute the function at this point. */ - if ( i != 0 ) - y = incbet(a,b,x); - if ( y < yl ) - { - x = x0; - y = yl; - } - else if ( y > yh ) - { - x = x1; - y = yh; - } - else if ( y < y0 ) - { - x0 = x; - yl = y; - } - else - { - x1 = x; - yh = y; - } - if ( x == 1.0 || x == 0.0 ) - break; - /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0-x) + lgm; - if ( d < kMINLOG ) - return process_done(); // goto done; - if ( d > kMAXLOG ) - break; - d = Math.exp(d); - /* Compute the step to the next approximation of x. */ - d = (y - y0)/d; - xt = x - d; - if ( xt <= x0 ) - { - y = (x - x0) / (x1 - x0); - xt = x0 + 0.5 * y * (x - x0); - if ( xt <= 0.0 ) - break; - } - if ( xt >= x1 ) - { - y = (x1 - x) / (x1 - x0); - xt = x1 - 0.5 * y * (x1 - x); - if ( xt >= 1.0 ) - break; - } - x = xt; - if ( Math.abs(d/x) < 128.0 * kMACHEP ) - return process_done(); // goto done; - } - /* Did not converge. */ - dithresh = 256.0 * kMACHEP; - } // endless loop instead of // goto ihalve; + positionScreen.x *= invW; + positionScreen.y *= invW; + positionScreen.z *= invW; -// done: + vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 && + positionScreen.y >= -1 && positionScreen.y <= 1 && + positionScreen.z >= -1 && positionScreen.z <= 1; - return process_done(); -} + } -/** @summary Calculates the normalized (regularized) incomplete beta function. - * @memberof Math */ -function inc_beta(x,a,b) { - return incbet(a,b,x); -} + function pushVertex( x, y, z ) { -const BetaIncomplete = inc_beta; + _vertex = getNextVertexInPool(); + _vertex.position.set( x, y, z ); -/** @summary ROOT::Math::beta_quantile - * @memberof Math */ -function beta_quantile(z,a,b) { - return incbi(a,b,z); -} + projectVertex( _vertex ); -/** @summary Complement of the cumulative distribution function of the beta distribution. - * @memberof Math */ -function beta_cdf_c(x,a,b) { - return inc_beta(1-x, b, a); -} + } -/** @summary chisquared_cdf - * @memberof Math */ -function chisquared_cdf(x,r,x0=0) { - return inc_gamma(0.5 * r, 0.5*(x-x0)); -} + function pushNormal( x, y, z ) { -/** @summary gamma_quantile_c function - * @memberof Math */ -function gamma_quantile_c(z, alpha, theta) { - return theta * igami( alpha, z); -} + normals.push( x, y, z ); -/** @summary gamma_quantile function - * @memberof Math */ -function gamma_quantile(z, alpha, theta) { - return theta * igami( alpha, 1.- z); -} + } -/** @summary breitwigner_cdf_c function - * @memberof Math */ -function breitwigner_cdf_c(x,gamma, x0 = 0) { - return 0.5 - Math.atan(2.0 * (x-x0) / gamma) / M_PI; -} + function pushColor( r, g, b ) { -/** @summary breitwigner_cdf function - * @memberof Math */ -function breitwigner_cdf(x, gamma, x0 = 0) { - return 0.5 + Math.atan(2.0 * (x-x0) / gamma) / M_PI; -} + colors.push( r, g, b ); -/** @summary cauchy_cdf_c function - * @memberof Math */ -function cauchy_cdf_c(x, b, x0 = 0) { - return 0.5 - Math.atan( (x-x0) / b) / M_PI; -} + } -/** @summary cauchy_cdf function - * @memberof Math */ -function cauchy_cdf(x, b, x0 = 0) { - return 0.5 + Math.atan( (x-x0) / b) / M_PI; -} + function pushUv( x, y ) { -/** @summary cauchy_pdf function - * @memberof Math */ -function cauchy_pdf(x, b = 1, x0 = 0) { - return b/(M_PI * ((x-x0)*(x-x0) + b*b)); -} + uvs.push( x, y ); -/** @summary gaussian_pdf function - * @memberof Math */ -function gaussian_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x-x0)/sigma; - return (1.0/(Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp*tmp/2); -} + } -/** @summary gamma_pdf function - * @memberof Math */ -function gamma_pdf(x, alpha, theta, x0 = 0) { - if ((x - x0) < 0) { - return 0.0; - } else if ((x - x0) == 0) { - return (alpha == 1) ? 1.0 / theta : 0; - } else if (alpha == 1) { - return Math.exp(-(x - x0) / theta) / theta; - } - return Math.exp((alpha - 1) * Math.log((x - x0) / theta) - (x - x0) / theta - lgamma(alpha)) / theta; -} + function checkTriangleVisibility( v1, v2, v3 ) { -/** @summary tdistribution_cdf_c function - * @memberof Math */ -function tdistribution_cdf_c(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 - .5*inc_beta(p*p/(r + p*p), .5, .5*r)*sign; -} + if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true; -/** @summary tdistribution_cdf function - * @memberof Math */ -function tdistribution_cdf(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 + .5*inc_beta(p*p/(r + p*p), .5, .5*r)*sign; -} + _points3[ 0 ] = v1.positionScreen; + _points3[ 1 ] = v2.positionScreen; + _points3[ 2 ] = v3.positionScreen; -/** @summary tdistribution_pdf function - * @memberof Math */ -function tdistribution_pdf(x, r, x0 = 0) { - return (Math.exp(lgamma((r + 1.0)/2.0) - lgamma(r/2.0)) / Math.sqrt(M_PI * r)) - * Math.pow((1.0 + (x-x0)*(x-x0)/r), -(r + 1.0)/2.0); -} + return _clipBox.intersectsBox( _boundingBox.setFromPoints( _points3 ) ); -/** @summary exponential_cdf_c function - * @memberof Math */ -function exponential_cdf_c(x, lambda, x0 = 0) { - return ((x-x0) < 0) ? 1.0 : Math.exp(-lambda * (x-x0)); -} + } -/** @summary exponential_cdf function - * @memberof Math */ -function exponential_cdf(x, lambda, x0 = 0) { - return ((x-x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x-x0)); -} + function checkBackfaceCulling( v1, v2, v3 ) { -/** @summary chisquared_pdf - * @memberof Math */ -function chisquared_pdf(x, r, x0 = 0) { - if ((x-x0) < 0) return 0.0; - const a = r/2 -1.; - // let return inf for case x = x0 and treat special case of r = 2 otherwise will return nan - if (x == x0 && a == 0) return 0.5; + return ( ( v3.positionScreen.x - v1.positionScreen.x ) * + ( v2.positionScreen.y - v1.positionScreen.y ) - + ( v3.positionScreen.y - v1.positionScreen.y ) * + ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; - return Math.exp((r/2 - 1) * Math.log((x-x0)/2) - (x-x0)/2 - lgamma(r/2))/2; -} + } -/** @summary Probability density function of the F-distribution. - * @memberof Math */ -function fdistribution_pdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - if ((x-x0) < 0) - return 0.0; + function pushLine( a, b ) { - return Math.exp((n/2) * Math.log(n) + (m/2) * Math.log(m) + lgamma((n+m)/2) - lgamma(n/2) - lgamma(m/2) - + (n/2 -1) * Math.log(x-x0) - ((n+m)/2) * Math.log(m + n*(x-x0))); -} + const v1 = _vertexPool[ a ]; + const v2 = _vertexPool[ b ]; -/** @summary fdistribution_cdf_c function - * @memberof Math */ -function fdistribution_cdf_c(x, n, m, x0 = 0) { - if (n < 0 || m < 0) return Number.NaN; + // Clip - const z = m / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) return 1. - fdistribution_cdf(x, n, m, x0); + v1.positionScreen.copy( v1.position ).applyMatrix4( _modelViewProjectionMatrix ); + v2.positionScreen.copy( v2.position ).applyMatrix4( _modelViewProjectionMatrix ); - // for the complement use the fact that IB(x,a,b) = 1. - IB(1-x,b,a) - return inc_beta(m / (m + n * (x - x0)), .5 * m, .5 * n); -} + if ( clipLine( v1.positionScreen, v2.positionScreen ) === true ) { -/** @summary fdistribution_cdf function - * @memberof Math */ -function fdistribution_cdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) return Number.NaN; + // Perform the perspective divide + v1.positionScreen.multiplyScalar( 1 / v1.positionScreen.w ); + v2.positionScreen.multiplyScalar( 1 / v2.positionScreen.w ); - const z = n * (x - x0) / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) - return 1. - fdistribution_cdf_c(x, n, m, x0); + _line = getNextLineInPool(); + _line.id = object.id; + _line.v1.copy( v1 ); + _line.v2.copy( v2 ); + _line.z = Math.max( v1.positionScreen.z, v2.positionScreen.z ); + _line.renderOrder = object.renderOrder; - return inc_beta(z, .5 * n, .5 * m); -} + _line.material = object.material; -/** @summary Prob function - * @memberof Math */ -function Prob(chi2, ndf) { - if (ndf <= 0) return 0; // Set CL to zero in case ndf <= 0 + if ( object.material.vertexColors ) { - if (chi2 <= 0) { - if (chi2 < 0) return 0; - else return 1; - } + _line.vertexColors[ 0 ].fromArray( colors, a * 3 ); + _line.vertexColors[ 1 ].fromArray( colors, b * 3 ); - return chisquared_cdf_c(chi2,ndf,0); -} + } -/** @summary Gaus function - * @memberof Math */ -function Gaus(x, mean, sigma, norm) { - if (!sigma) return 1e30; - const arg = (x - mean) / sigma; - if (arg < -39 || arg > 39) return 0; - const res = Math.exp(-0.5*arg*arg); - return norm ? res/(2.50662827463100024*sigma) : res; // sqrt(2*Pi)=2.50662827463100024 -} + _renderData.elements.push( _line ); -/** @summary BreitWigner function - * @memberof Math */ -function BreitWigner(x, mean, gamma) { - return gamma/((x-mean)*(x-mean) + gamma*gamma/4) / 2 / Math.PI; -} + } -/** @summary Calculates Beta-function Gamma(p)*Gamma(q)/Gamma(p+q). - * @memberof Math */ -function Beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x+y)); -} + } -/** @summary GammaDist function - * @memberof Math */ -function GammaDist(x, gamma, mu = 0, beta = 1) { - if ((x < mu) || (gamma <= 0) || (beta <= 0)) return 0; - return gamma_pdf(x, gamma, beta, mu); -} + function pushTriangle( a, b, c, material ) { -/** @summary probability density function of Laplace distribution - * @memberof Math */ -function LaplaceDist(x, alpha = 0, beta = 1) { - return Math.exp(-Math.abs((x-alpha)/beta)) / (2.*beta); -} + const v1 = _vertexPool[ a ]; + const v2 = _vertexPool[ b ]; + const v3 = _vertexPool[ c ]; -/** @summary distribution function of Laplace distribution - * @memberof Math */ -function LaplaceDistI(x, alpha = 0, beta = 1) { - return (x <= alpha) ? 0.5*Math.exp(-Math.abs((x-alpha)/beta)) : 1 - 0.5*Math.exp(-Math.abs((x-alpha)/beta)); -} + if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return; -/** @summary density function for Student's t- distribution - * @memberof Math */ -function Student(T, ndf) { - if (ndf < 1) return 0; + if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) { - const r = ndf, - rh = 0.5*r, - rh1 = rh + 0.5, - denom = Math.sqrt(r*Math.PI)*gamma(rh)*Math.pow(1+T*T/r, rh1); - return gamma(rh1)/denom; -} + _face = getNextFaceInPool(); -/** @summary cumulative distribution function of Student's - * @memberof Math */ -function StudentI(T, ndf) { - const r = ndf; + _face.id = object.id; + _face.v1.copy( v1 ); + _face.v2.copy( v2 ); + _face.v3.copy( v3 ); + _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; + _face.renderOrder = object.renderOrder; - return (T > 0) - ? (1 - 0.5*BetaIncomplete((r/(r + T*T)), r*0.5, 0.5)) - : 0.5*BetaIncomplete((r/(r + T*T)), r*0.5, 0.5); -} + // face normal + _vector3.subVectors( v3.position, v2.position ); + _vector4.subVectors( v1.position, v2.position ); + _vector3.cross( _vector4 ); + _face.normalModel.copy( _vector3 ); + _face.normalModel.applyMatrix3( normalMatrix ).normalize(); -/** @summary LogNormal function - * @memberof Math */ -function LogNormal(x, sigma, theta = 0, m = 1) { - if ((x < theta) || (sigma <= 0) || (m <= 0)) return 0; - return lognormal_pdf(x, Math.log(m), sigma, theta); -} + for ( let i = 0; i < 3; i ++ ) { -/** @summary Computes the probability density function of the Beta distribution - * @memberof Math */ -function BetaDist(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; - const beta = Beta(p, q); - return Math.pow(x, p-1) * Math.pow(1-x, q-1) / beta; -} + const normal = _face.vertexNormalsModel[ i ]; + normal.fromArray( normals, arguments[ i ] * 3 ); + normal.applyMatrix3( normalMatrix ).normalize(); -/** @summary Computes the distribution function of the Beta distribution. - * @memberof Math */ -function BetaDistI(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) return 0; - return BetaIncomplete(x, p, q); -} + const uv = _face.uvs[ i ]; + uv.fromArray( uvs, arguments[ i ] * 2 ); -/** @summary gaus function for TFormula - * @memberof Math */ -function gaus(f, x, i) { - return f.GetParValue(i+0) * Math.exp(-0.5 * Math.pow((x-f.GetParValue(i+1)) / f.GetParValue(i+2), 2)); -} + } -/** @summary gausn function for TFormula - * @memberof Math */ -function gausn(f, x, i) { - return gaus(f, x, i)/(Math.sqrt(2 * Math.PI) * f.GetParValue(i+2)); -} + _face.vertexNormalsLength = 3; -/** @summary gausxy function for TFormula - * @memberof Math */ -function gausxy(f, x, y, i) { - return f.GetParValue(i+0) * Math.exp(-0.5 * Math.pow((x-f.GetParValue(i+1)) / f.GetParValue(i+2), 2)) - * Math.exp(-0.5 * Math.pow((y-f.GetParValue(i+3)) / f.GetParValue(i+4), 2)); -} + _face.material = material; -/** @summary expo function for TFormula - * @memberof Math */ -function expo(f, x, i) { - return Math.exp(f.GetParValue(i+0) + f.GetParValue(i+1) * x); -} + if ( material.vertexColors ) { -/** @summary landau function for TFormula - * @memberof Math */ -function landau(f, x, i) { - return Landau(x, f.GetParValue(i+1),f.GetParValue(i+2), false); -} + _face.color.fromArray( colors, a * 3 ); -/** @summary landaun function for TFormula - * @memberof Math */ -function landaun(f, x, i) { - return Landau(x, f.GetParValue(i+1),f.GetParValue(i+2), true); -} + } -/** @summary Crystal ball function - * @memberof Math */ -function crystalball_function(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) return 0.; - let z = (x - mean)/sigma; - if (alpha < 0) z = -z; - const abs_alpha = Math.abs(alpha); - if (z > -abs_alpha) - return Math.exp(-0.5 * z * z); - const nDivAlpha = n/abs_alpha, - AA = Math.exp(-0.5*abs_alpha*abs_alpha), - B = nDivAlpha - abs_alpha, - arg = nDivAlpha/(B-z); - return AA * Math.pow(arg,n); -} + _renderData.elements.push( _face ); -/** @summary pdf definition of the crystal_ball which is defined only for n > 1 otherwise integral is diverging - * @memberof Math */ -function crystalball_pdf(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) return 0.; - if (n <= 1) return Number.NaN; // pdf is not normalized for n <=1 - const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1.+erf(abs_alpha/Math.sqrt(2.))), - N = 1./(sigma*(C+D)); - return N * crystalball_function(x,alpha,n,sigma,mean); -} + } -/** @summary compute the integral of the crystal ball function - * @memberof Math */ -function crystalball_integral(x, alpha, n, sigma, mean = 0) { - if (sigma == 0) return 0; - if (alpha == 0) return 0.; - const useLog = (n == 1.0), - abs_alpha = Math.abs(alpha); + } - let z = (x-mean)/sigma, intgaus = 0., intpow = 0.; - if (alpha < 0 ) z = -z; + return { + setObject: setObject, + projectVertex: projectVertex, + checkTriangleVisibility: checkTriangleVisibility, + checkBackfaceCulling: checkBackfaceCulling, + pushVertex: pushVertex, + pushNormal: pushNormal, + pushColor: pushColor, + pushUv: pushUv, + pushLine: pushLine, + pushTriangle: pushTriangle + }; - const sqrtpiover2 = Math.sqrt(M_PI/2.), - sqrt2pi = Math.sqrt( 2.*M_PI), - oneoversqrt2 = 1./Math.sqrt(2.); - if (z <= -abs_alpha) { - const A = Math.pow(n/abs_alpha,n) * Math.exp(-0.5 * alpha*alpha), - B = n/abs_alpha - abs_alpha; + } - if (!useLog) { - const C = (n/abs_alpha) * (1./(n-1)) * Math.exp(-alpha*alpha/2.); - intpow = C - A /(n-1.) * Math.pow(B-z,-n+1); - } - else { - // for n=1 the primitive of 1/x is log(x) - intpow = -A * Math.log( n / abs_alpha ) + A * Math.log(B - z); - } - intgaus = sqrtpiover2*(1. + erf(abs_alpha*oneoversqrt2)); - } else { - intgaus = normal_cdf_c(z, 1); - intgaus *= sqrt2pi; - intpow = 0; - } - return sigma * (intgaus + intpow); -} + const renderList = new RenderList(); -/** @summary crystalball_cdf function - * @memberof Math */ -function crystalball_cdf(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; + function projectObject( object ) { - const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1. + erf(abs_alpha/Math.sqrt(2.))), - totIntegral = sigma*(C+D), - integral = crystalball_integral(x,alpha,n,sigma,mean); + if ( object.visible === false ) return; - return (alpha > 0) ? 1. - integral/totIntegral : integral/totIntegral; -} + if ( object.isLight ) { -/** @summary crystalball_cdf_c function - * @memberof Math */ -function crystalball_cdf_c(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; + _renderData.lights.push( object ); - const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1. + erf(abs_alpha/Math.sqrt(2.))), - totIntegral = sigma*(C+D), - integral = crystalball_integral(x,alpha,n,sigma,mean); + } else if ( object.isMesh || object.isLine || object.isPoints ) { - return (alpha > 0) ? integral/totIntegral : 1. - (integral/totIntegral); -} + if ( object.material.visible === false ) return; + if ( object.frustumCulled === true && _frustum.intersectsObject( object ) === false ) return; -/** @summary ChebyshevN function - * @memberof Math */ -function ChebyshevN(n, x, c) { - let d1 = 0.0, d2 = 0.0; - const y2 = 2.0 * x; + addObject( object ); - for (let i = n; i >= 1; i--) { - const temp = d1; - d1 = y2 * d1 - d2 + c[i]; - d2 = temp; - } + } else if ( object.isSprite ) { - return x * d1 - d2 + c[0]; -} + if ( object.material.visible === false ) return; + if ( object.frustumCulled === true && _frustum.intersectsSprite( object ) === false ) return; -/** @summary Chebyshev0 function - * @memberof Math */ -function Chebyshev0(_x, c0) { - return c0; -} + addObject( object ); -/** @summary Chebyshev1 function - * @memberof Math */ -function Chebyshev1(x, c0, c1) { - return c0 + c1*x; -} + } -/** @summary Chebyshev2 function - * @memberof Math */ -function Chebyshev2(x, c0, c1, c2) { - return c0 + c1*x + c2*(2.0*x*x - 1.0); -} + const children = object.children; -/** @summary Chebyshev3 function - * @memberof Math */ -function Chebyshev3(x, ...args) { - return ChebyshevN(3, x, args); -} + for ( let i = 0, l = children.length; i < l; i ++ ) { -/** @summary Chebyshev4 function - * @memberof Math */ -function Chebyshev4(x, ...args) { - return ChebyshevN(4, x, args); -} + projectObject( children[ i ] ); -/** @summary Chebyshev5 function - * @memberof Math */ -function Chebyshev5(x, ...args) { - return ChebyshevN(5, x, args); -} + } -/** @summary Chebyshev6 function - * @memberof Math */ -function Chebyshev6(x, ...args) { - return ChebyshevN(6, x, args); -} + } -/** @summary Chebyshev7 function - * @memberof Math */ -function Chebyshev7(x, ...args) { - return ChebyshevN(7, x, args); -} + function addObject( object ) { -/** @summary Chebyshev8 function - * @memberof Math */ -function Chebyshev8(x, ...args) { - return ChebyshevN(8, x, args); -} + _object = getNextObjectInPool(); + _object.id = object.id; + _object.object = object; -/** @summary Chebyshev9 function - * @memberof Math */ -function Chebyshev9(x, ...args) { - return ChebyshevN(9, x, args); -} + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyMatrix4( _viewProjectionMatrix ); + _object.z = _vector3.z; + _object.renderOrder = object.renderOrder; -/** @summary Chebyshev10 function - * @memberof Math */ -function Chebyshev10(x, ...args) { - return ChebyshevN(10, x, args); -} + _renderData.objects.push( _object ); -// ========================================================================= + } -/** @summary Caluclate ClopperPearson - * @memberof Math */ -function eff_ClopperPearson(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2; - if (bUpper) - return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total-passed)); + /** + * Projects the given scene in 3D space into a 2D representation. The result + * is an object with renderable items. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + * @param {boolean} sortObjects - Whether to sort objects or not. + * @param {boolean} sortElements - Whether to sort elements (faces, lines and sprites) or not. + * @return {{objects:Array,lights:Array,elements:Array}} The projected scene as renderable objects. + */ + this.projectScene = function ( scene, camera, sortObjects, sortElements ) { - return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total-passed+1.0)); -} + _faceCount = 0; + _lineCount = 0; + _spriteCount = 0; -/** @summary Caluclate normal - * @memberof Math */ -function eff_Normal(total,passed,level,bUpper) { - if (total == 0) return bUpper ? 1 : 0; + _renderData.elements.length = 0; - const alpha = (1.0 - level)/2, - average = passed / total, - sigma = Math.sqrt(average * (1 - average) / total), - delta = normal_quantile(1 - alpha, sigma); + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - if (bUpper) - return ((average + delta) > 1) ? 1.0 : (average + delta); + _viewMatrix.copy( camera.matrixWorldInverse ); + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); - return ((average - delta) < 0) ? 0.0 : (average - delta); -} + _frustum.setFromProjectionMatrix( _viewProjectionMatrix ); -/** @summary Calculates the boundaries for the frequentist Wilson interval - * @memberof Math */ -function eff_Wilson(total,passed,level,bUpper) { - const alpha = (1.0 - level)/2; - if (total == 0) return bUpper ? 1 : 0; - const average = passed / total, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa / (total + kappa*kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); + // - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); + _objectCount = 0; - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} + _renderData.objects.length = 0; + _renderData.lights.length = 0; -/** @summary Calculates the boundaries for the frequentist Agresti-Coull interval - * @memberof Math */ -function eff_AgrestiCoull(total,passed,level,bUpper) { - const alpha = (1.0 - level)/2, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa * Math.sqrt(mode * (1 - mode) / (total + kappa * kappa)); + projectObject( scene ); - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); + if ( sortObjects === true ) { - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} + _renderData.objects.sort( painterSort ); -/** @summary Calculates the boundaries using the mid-P binomial - * @memberof Math */ -function eff_MidPInterval(total,passed,level,bUpper) { - const alpha = 1. - level, alpha_min = alpha/2 , tol = 1e-9; // tolerance - let pmin = 0, pmax = 1, p = 0; + } - // treat special case for 0 0 && passed < 1) { - const p0 = eff_MidPInterval(total, 0.0, level, bUpper), - p1 = eff_MidPInterval(total, 1.0, level, bUpper); - p = (p1 - p0) * passed + p0; - return p; - } + // - while (Math.abs(pmax - pmin) > tol) { - p = (pmin + pmax)/2; - // double v = 0.5 * ROOT::Math::binomial_pdf(int(passed), p, int(total)); - // make it work for non integer using the binomial - beta relationship - let v = 0.5 * beta_pdf(p, passed+1., total-passed+1)/(total+1); - // if (passed > 0) v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); - // compute the binomial cdf at passed -1 - if ( (passed-1) >= 0) v += beta_cdf_c(p, passed, total-passed+1); + const objects = _renderData.objects; - const vmin = bUpper ? alpha_min : 1.- alpha_min; - if (v > vmin) - pmin = p; - else - pmax = p; - } + for ( let o = 0, ol = objects.length; o < ol; o ++ ) { - return p; -} + const object = objects[ o ].object; + const geometry = object.geometry; -/** @summary for a central confidence interval for a Beta distribution - * @memberof Math */ -function eff_Bayesian(total,passed,level,bUpper,alpha,beta) { - const a = passed + alpha, - b = total - passed + beta; - if (bUpper) { - if ((a > 0) && (b > 0)) - return beta_quantile((1+level)/2,a,b); - else - return 1; - } else { - if ((a > 0) && (b > 0)) - return beta_quantile((1-level)/2,a,b); - else - return 0; - } -} + renderList.setObject( object ); -/** @summary Return function to calculate boundary of TEfficiency - * @memberof Math */ -function getTEfficiencyBoundaryFunc(option, isbayessian) { - const kFCP = 0, // Clopper-Pearson interval (recommended by PDG) - kFNormal = 1, // Normal approximation - kFWilson = 2, // Wilson interval - kFAC = 3, // Agresti-Coull interval - kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript - // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) - // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) - // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) - kMidP = 8; // Mid-P Lancaster interval + _modelMatrix = object.matrixWorld; - if (isbayessian) - return eff_Bayesian; + _vertexCount = 0; - switch (option) { - case kFCP: return eff_ClopperPearson; - case kFNormal: return eff_Normal; - case kFWilson: return eff_Wilson; - case kFAC: return eff_AgrestiCoull; - case kFFC: console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); return eff_ClopperPearson; - case kMidP: return eff_MidPInterval; - // case kBJeffrey: - // case kBUniform: - // case kBBayesian: return eff_ClopperPearson; - } - console.log(`Not recognized stat option ${option}, using kFCP`); - return eff_ClopperPearson; -} + if ( object.isMesh ) { -/** @summary Square function - * @memberof Math */ -function Sq(x) { - return x * x; -} + let material = object.material; -/** @summary Pi function - * @memberof Math */ -function Pi() { - return Math.PI; -} + const isMultiMaterial = Array.isArray( material ); -/** @summary TwoPi function - * @memberof Math */ -function TwoPi() { - return 2 * Math.PI; -} + const attributes = geometry.attributes; + const groups = geometry.groups; -/** @summary PiOver2 function - * @memberof Math */ -function PiOver2() -{ - return Math.PI / 2; -} + if ( attributes.position === undefined ) continue; -/** @summary PiOver4 function - * @memberof Math */ -function PiOver4() -{ - return Math.PI / 4; -} + const positions = attributes.position.array; -/** @summary InvPi function - * @memberof Math */ -function InvPi() -{ - return 1 / Math.PI; -} + for ( let i = 0, l = positions.length; i < l; i += 3 ) { -var jsroot_math = /*#__PURE__*/Object.freeze({ -__proto__: null, -Beta: Beta, -BetaDist: BetaDist, -BetaDistI: BetaDistI, -BetaIncomplete: BetaIncomplete, -BreitWigner: BreitWigner, -Chebyshev0: Chebyshev0, -Chebyshev1: Chebyshev1, -Chebyshev10: Chebyshev10, -Chebyshev2: Chebyshev2, -Chebyshev3: Chebyshev3, -Chebyshev4: Chebyshev4, -Chebyshev5: Chebyshev5, -Chebyshev6: Chebyshev6, -Chebyshev7: Chebyshev7, -Chebyshev8: Chebyshev8, -Chebyshev9: Chebyshev9, -ChebyshevN: ChebyshevN, -FDist: fdistribution_pdf, -FDistI: fdistribution_cdf, -Gamma: gamma, -GammaDist: GammaDist, -Gaus: Gaus, -InvPi: InvPi, -Landau: Landau, -LaplaceDist: LaplaceDist, -LaplaceDistI: LaplaceDistI, -LogNormal: LogNormal, -Pi: Pi, -PiOver2: PiOver2, -PiOver4: PiOver4, -Polynomial1eval: Polynomial1eval, -Polynomialeval: Polynomialeval, -Prob: Prob, -Sq: Sq, -Student: Student, -StudentI: StudentI, -TwoPi: TwoPi, -beta: beta, -beta_cdf_c: beta_cdf_c, -beta_pdf: beta_pdf, -beta_quantile: beta_quantile, -breitwigner_cdf: breitwigner_cdf, -breitwigner_cdf_c: breitwigner_cdf_c, -cauchy_cdf: cauchy_cdf, -cauchy_cdf_c: cauchy_cdf_c, -cauchy_pdf: cauchy_pdf, -chisquared_cdf: chisquared_cdf, -chisquared_cdf_c: chisquared_cdf_c, -chisquared_pdf: chisquared_pdf, -crystalball_cdf: crystalball_cdf, -crystalball_cdf_c: crystalball_cdf_c, -crystalball_function: crystalball_function, -crystalball_pdf: crystalball_pdf, -erf: erf, -erfc: erfc, -expo: expo, -exponential_cdf: exponential_cdf, -exponential_cdf_c: exponential_cdf_c, -fdistribution_cdf: fdistribution_cdf, -fdistribution_cdf_c: fdistribution_cdf_c, -fdistribution_pdf: fdistribution_pdf, -gamma: gamma, -gamma_pdf: gamma_pdf, -gamma_quantile: gamma_quantile, -gamma_quantile_c: gamma_quantile_c, -gaus: gaus, -gausn: gausn, -gaussian_cdf: normal_cdf, -gaussian_cdf_c: normal_cdf_c, -gaussian_pdf: gaussian_pdf, -gausxy: gausxy, -getTEfficiencyBoundaryFunc: getTEfficiencyBoundaryFunc, -igam: igam, -igamc: igamc, -igami: igami, -inc_beta: inc_beta, -inc_gamma: inc_gamma, -inc_gamma_c: inc_gamma_c, -incbet: incbet, -incbi: incbi, -landau: landau, -landau_pdf: landau_pdf, -landaun: landaun, -lgam: lgam, -lgamma: lgamma, -lognormal_cdf: lognormal_cdf, -lognormal_cdf_c: lognormal_cdf_c, -lognormal_pdf: lognormal_pdf, -ndtri: ndtri, -normal_cdf: normal_cdf, -normal_cdf_c: normal_cdf_c, -normal_pdf: normal_pdf, -normal_quantile: normal_quantile, -normal_quantile_c: normal_quantile_c, -pseries: pseries, -stirf: stirf, -tdistribution_cdf: tdistribution_cdf, -tdistribution_cdf_c: tdistribution_cdf_c, -tdistribution_pdf: tdistribution_pdf, -tgamma: gamma -}); + let x = positions[ i ]; + let y = positions[ i + 1 ]; + let z = positions[ i + 2 ]; -/** @summary Display progress message in the left bottom corner. - * @desc Previous message will be overwritten - * if no argument specified, any shown messages will be removed - * @param {string} msg - message to display - * @param {number} [tmout] - optional timeout in milliseconds, after message will disappear - * @param {function} [click_handle] - optional handle to process click events - * @private */ -function showProgress(msg, tmout, click_handle) { - if (isBatchMode() || (typeof document === 'undefined')) - return; + const morphTargets = geometry.morphAttributes.position; - const id = 'jsroot_progressbox', modal = (settings.ProgressBox === 'modal') && isFunc(internals._modalProgress) ? internals._modalProgress : null; - let box = select('#' + id); + if ( morphTargets !== undefined ) { - if (!settings.ProgressBox) { - if (modal) modal(); - return box.remove(); - } + const morphTargetsRelative = geometry.morphTargetsRelative; + const morphInfluences = object.morphTargetInfluences; - if ((arguments.length === 0) || !msg) { - if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) box.remove(); - if (modal) modal(); - return; - } + for ( let t = 0, tl = morphTargets.length; t < tl; t ++ ) { - if (modal) { - box.remove(); - modal(msg, click_handle); - } else { - if (box.empty()) { - box = select(document.body) - .append('div').attr('id', id) - .attr('style', 'position: fixed; min-width: 100px; height: auto; overflow: visible; z-index: 101; border: 1px solid #999; background: #F8F8F8; left: 10px; bottom: 10px;'); - box.append('p'); - } + const influence = morphInfluences[ t ]; - box.property('with_timeout', false); + if ( influence === 0 ) continue; - const p = box.select('p'); + const target = morphTargets[ t ]; - if (isStr(msg)) { - p.html(msg) - .on('click', isFunc(click_handle) ? click_handle : null) - .attr('title', isFunc(click_handle) ? 'Click element to abort current operation' : ''); - } + if ( morphTargetsRelative ) { - p.attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); - } + x += target.getX( i / 3 ) * influence; + y += target.getY( i / 3 ) * influence; + z += target.getZ( i / 3 ) * influence; - if (Number.isFinite(tmout) && (tmout > 0)) { - if (!box.empty()) - box.property('with_timeout', true); - setTimeout(() => showProgress('', -1), tmout); - } -} + } else { -/** @summary Tries to close current browser tab - * @desc Many browsers do not allow simple window.close() call, - * therefore try several workarounds - * @private */ -function closeCurrentWindow() { - if (typeof window === 'undefined') return; - window.close(); - window.open('', '_self').close(); -} + x += ( target.getX( i / 3 ) - positions[ i ] ) * influence; + y += ( target.getY( i / 3 ) - positions[ i + 1 ] ) * influence; + z += ( target.getZ( i / 3 ) - positions[ i + 2 ] ) * influence; -/** @summary Tries to open ui5 - * @private */ -function tryOpenOpenUI(sources, args) { - if (!sources || (sources.length === 0)) { - if (isFunc(args.rejectFunc)) { - args.rejectFunc(Error('openui5 was not possible to load')); - args.rejectFunc = null; - } - return; - } + } - // where to take openui5 sources - let src = sources.shift(); + } - if ((src.indexOf('roothandler') === 0) && (src.indexOf('://') < 0)) - src = src.replace(/:\//g, '://'); + } - const element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.setAttribute('id', 'sap-ui-bootstrap'); - // use nojQuery while we are already load jquery and jquery-ui, later one can use directly sap-ui-core.js + renderList.pushVertex( x, y, z ); - // this is location of openui5 scripts when working with THttpServer or when scripts are installed inside JSROOT - element.setAttribute('src', src + (args.ui5dbg ? 'resources/sap-ui-core-dbg.js' : 'resources/sap-ui-core.js')); // latest openui5 version + } - element.setAttribute('data-sap-ui-libs', args.openui5libs ?? 'sap.m, sap.ui.layout, sap.ui.unified, sap.ui.commons'); + if ( attributes.normal !== undefined ) { - element.setAttribute('data-sap-ui-theme', args.openui5theme || 'sap_belize'); - element.setAttribute('data-sap-ui-compatVersion', 'edge'); - element.setAttribute('data-sap-ui-async', 'true'); - // element.setAttribute('data-sap-ui-bindingSyntax', 'complex'); + const normals = attributes.normal.array; - element.setAttribute('data-sap-ui-preload', 'async'); // '' to disable Component-preload.js + for ( let i = 0, l = normals.length; i < l; i += 3 ) { - element.setAttribute('data-sap-ui-evt-oninit', 'completeUI5Loading()'); + renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); - element.onerror = function() { - // remove failed element - element.parentNode.removeChild(element); - // and try next - tryOpenOpenUI(sources, args); - }; + } - element.onload = function() { - console.log(`Load openui5 from ${src}`); - }; + } - document.head.appendChild(element); -} + if ( attributes.color !== undefined ) { + const colors = attributes.color.array; -/** @summary load openui5 - * @return {Promise} for loading ready - * @private */ -async function loadOpenui5(args) { - // very simple - openui5 was loaded before and will be used as is - if (typeof globalThis.sap === 'object') - return globalThis.sap; + for ( let i = 0, l = colors.length; i < l; i += 3 ) { - if (!args) args = {}; + renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); - let rootui5sys = exports.source_dir.replace(/jsrootsys/g, 'rootui5sys'); + } - if (rootui5sys === exports.source_dir) { - // if jsrootsys location not detected, try to guess it - if (window.location.port && (window.location.pathname.indexOf('/win') >= 0) && (!args.openui5src || args.openui5src === 'nojsroot' || args.openui5src === 'jsroot')) - rootui5sys = window.location.origin + window.location.pathname + '../rootui5sys/'; - else - rootui5sys = undefined; - } + } - const openui5_sources = []; - let openui5_dflt = 'https://openui5.hana.ondemand.com/1.98.0/', - openui5_root = rootui5sys ? rootui5sys + 'distribution/' : ''; + if ( attributes.uv !== undefined ) { - if (isStr(args.openui5src)) { - switch (args.openui5src) { - case 'nodefault': openui5_dflt = ''; break; - case 'default': openui5_sources.push(openui5_dflt); openui5_dflt = ''; break; - case 'nojsroot': /* openui5_root = ''; */ break; - case 'jsroot': openui5_sources.push(openui5_root); openui5_root = ''; break; - default: openui5_sources.push(args.openui5src); break; - } - } else if (args.ui5dbg) - openui5_root = ''; // exclude ROOT version in debug mode + const uvs = attributes.uv.array; - if (openui5_root && (openui5_sources.indexOf(openui5_root) < 0)) - openui5_sources.push(openui5_root); - if (openui5_dflt && (openui5_sources.indexOf(openui5_dflt) < 0)) - openui5_sources.push(openui5_dflt); + for ( let i = 0, l = uvs.length; i < l; i += 2 ) { - return new Promise((resolve, reject) => { - args.resolveFunc = resolve; - args.rejectFunc = reject; + renderList.pushUv( uvs[ i ], uvs[ i + 1 ] ); - globalThis.completeUI5Loading = function() { - globalThis.sap.ui.loader.config({ - paths: { - jsroot: exports.source_dir, - rootui5: rootui5sys - } - }); + } - if (args.resolveFunc) { - args.resolveFunc(globalThis.sap); - args.resolveFunc = null; - } - }; + } - tryOpenOpenUI(openui5_sources, args); - }); -} + if ( geometry.index !== null ) { -/* eslint-disable key-spacing */ -/* eslint-disable comma-spacing */ -/* eslint-disable object-curly-spacing */ + const indices = geometry.index.array; -// some icons taken from http://uxrepo.com/ -const ToolbarIcons = { - camera: { path: 'M 152.00,304.00c0.00,57.438, 46.562,104.00, 104.00,104.00s 104.00-46.562, 104.00-104.00s-46.562-104.00-104.00-104.00S 152.00,246.562, 152.00,304.00z M 480.00,128.00L 368.00,128.00 c-8.00-32.00-16.00-64.00-48.00-64.00L 192.00,64.00 c-32.00,0.00-40.00,32.00-48.00,64.00L 32.00,128.00 c-17.60,0.00-32.00,14.40-32.00,32.00l0.00,288.00 c0.00,17.60, 14.40,32.00, 32.00,32.00l 448.00,0.00 c 17.60,0.00, 32.00-14.40, 32.00-32.00L 512.00,160.00 C 512.00,142.40, 497.60,128.00, 480.00,128.00z M 256.00,446.00c-78.425,0.00-142.00-63.574-142.00-142.00c0.00-78.425, 63.575-142.00, 142.00-142.00c 78.426,0.00, 142.00,63.575, 142.00,142.00 C 398.00,382.426, 334.427,446.00, 256.00,446.00z M 480.00,224.00l-64.00,0.00 l0.00-32.00 l 64.00,0.00 L 480.00,224.00 z' }, - disk: { path: 'M384,0H128H32C14.336,0,0,14.336,0,32v448c0,17.656,14.336,32,32,32h448c17.656,0,32-14.344,32-32V96L416,0H384z M352,160 V32h32v128c0,17.664-14.344,32-32,32H160c-17.664,0-32-14.336-32-32V32h128v128H352z M96,288c0-17.656,14.336-32,32-32h256 c17.656,0,32,14.344,32,32v192H96V288z' }, - question: { path: 'M256,512c141.375,0,256-114.625,256-256S397.375,0,256,0S0,114.625,0,256S114.625,512,256,512z M256,64 c63.719,0,128,36.484,128,118.016c0,47.453-23.531,84.516-69.891,110.016C300.672,299.422,288,314.047,288,320 c0,17.656-14.344,32-32,32c-17.664,0-32-14.344-32-32c0-40.609,37.25-71.938,59.266-84.031 C315.625,218.109,320,198.656,320,182.016C320,135.008,279.906,128,256,128c-30.812,0-64,20.227-64,64.672 c0,17.664-14.336,32-32,32s-32-14.336-32-32C128,109.086,193.953,64,256,64z M256,449.406c-18.211,0-32.961-14.75-32.961-32.969 c0-18.188,14.75-32.953,32.961-32.953c18.219,0,32.969,14.766,32.969,32.953C288.969,434.656,274.219,449.406,256,449.406z' }, - undo: { path: 'M450.159,48.042c8.791,9.032,16.983,18.898,24.59,29.604c7.594,10.706,14.146,22.207,19.668,34.489 c5.509,12.296,9.82,25.269,12.92,38.938c3.113,13.669,4.663,27.834,4.663,42.499c0,14.256-1.511,28.863-4.532,43.822 c-3.009,14.952-7.997,30.217-14.953,45.795c-6.955,15.577-16.202,31.52-27.755,47.826s-25.88,32.9-42.942,49.807 c-5.51,5.444-11.787,11.67-18.834,18.651c-7.033,6.98-14.496,14.366-22.39,22.168c-7.88,7.802-15.955,15.825-24.187,24.069 c-8.258,8.231-16.333,16.203-24.252,23.888c-18.3,18.13-37.354,37.016-57.191,56.65l-56.84-57.445 c19.596-19.472,38.54-38.279,56.84-56.41c7.75-7.685,15.772-15.604,24.108-23.757s16.438-16.163,24.33-24.057 c7.894-7.893,15.356-15.33,22.402-22.312c7.034-6.98,13.312-13.193,18.821-18.651c22.351-22.402,39.165-44.648,50.471-66.738 c11.279-22.09,16.932-43.567,16.932-64.446c0-15.785-3.217-31.005-9.638-45.671c-6.422-14.665-16.229-28.504-29.437-41.529 c-3.282-3.282-7.358-6.395-12.217-9.325c-4.871-2.938-10.381-5.503-16.516-7.697c-6.121-2.201-12.815-3.992-20.058-5.373 c-7.242-1.374-14.9-2.064-23.002-2.064c-8.218,0-16.802,0.834-25.788,2.507c-8.961,1.674-18.053,4.429-27.222,8.271 c-9.189,3.842-18.456,8.869-27.808,15.089c-9.358,6.219-18.521,13.819-27.502,22.793l-59.92,60.271l93.797,94.058H0V40.91 l93.27,91.597l60.181-60.532c13.376-15.018,27.222-27.248,41.536-36.697c14.308-9.443,28.608-16.776,42.89-21.992 c14.288-5.223,28.505-8.74,42.623-10.557C294.645,0.905,308.189,0,321.162,0c13.429,0,26.389,1.185,38.84,3.562 c12.478,2.377,24.2,5.718,35.192,10.029c11.006,4.311,21.126,9.404,30.374,15.265C434.79,34.724,442.995,41.119,450.159,48.042z' }, - arrow_right: { path: 'M30.796,226.318h377.533L294.938,339.682c-11.899,11.906-11.899,31.184,0,43.084c11.887,11.899,31.19,11.893,43.077,0 l165.393-165.386c5.725-5.712,8.924-13.453,8.924-21.539c0-8.092-3.213-15.84-8.924-21.551L338.016,8.925 C332.065,2.975,324.278,0,316.478,0c-7.802,0-15.603,2.968-21.539,8.918c-11.899,11.906-11.899,31.184,0,43.084l113.391,113.384 H30.796c-16.822,0-30.463,13.645-30.463,30.463C0.333,212.674,13.974,226.318,30.796,226.318z' }, - arrow_up: { path: 'M295.505,629.446V135.957l148.193,148.206c15.555,15.559,40.753,15.559,56.308,0c15.555-15.538,15.546-40.767,0-56.304 L283.83,11.662C276.372,4.204,266.236,0,255.68,0c-10.568,0-20.705,4.204-28.172,11.662L11.333,227.859 c-7.777,7.777-11.666,17.965-11.666,28.158c0,10.192,3.88,20.385,11.657,28.158c15.563,15.555,40.762,15.555,56.317,0 l148.201-148.219v493.489c0,21.993,17.837,39.82,39.82,39.82C277.669,669.267,295.505,651.439,295.505,629.446z' }, - arrow_diag: { path: 'M279.875,511.994c-1.292,0-2.607-0.102-3.924-0.312c-10.944-1.771-19.333-10.676-20.457-21.71L233.97,278.348 L22.345,256.823c-11.029-1.119-19.928-9.51-21.698-20.461c-1.776-10.944,4.031-21.716,14.145-26.262L477.792,2.149 c9.282-4.163,20.167-2.165,27.355,5.024c7.201,7.189,9.199,18.086,5.024,27.356L302.22,497.527 C298.224,506.426,289.397,511.994,279.875,511.994z M118.277,217.332l140.534,14.294c11.567,1.178,20.718,10.335,21.878,21.896 l14.294,140.519l144.09-320.792L118.277,217.332z' }, - auto_zoom: { path: 'M505.441,242.47l-78.303-78.291c-9.18-9.177-24.048-9.171-33.216,0c-9.169,9.172-9.169,24.045,0.006,33.217l38.193,38.188 H280.088V80.194l38.188,38.199c4.587,4.584,10.596,6.881,16.605,6.881c6.003,0,12.018-2.297,16.605-6.875 c9.174-9.172,9.174-24.039,0.011-33.217L273.219,6.881C268.803,2.471,262.834,0,256.596,0c-6.229,0-12.202,2.471-16.605,6.881 l-78.296,78.302c-9.178,9.172-9.178,24.045,0,33.217c9.177,9.171,24.051,9.171,33.21,0l38.205-38.205v155.4H80.521l38.2-38.188 c9.177-9.171,9.177-24.039,0.005-33.216c-9.171-9.172-24.039-9.178-33.216,0L7.208,242.464c-4.404,4.403-6.881,10.381-6.881,16.611 c0,6.227,2.477,12.207,6.881,16.61l78.302,78.291c4.587,4.581,10.599,6.875,16.605,6.875c6.006,0,12.023-2.294,16.61-6.881 c9.172-9.174,9.172-24.036-0.005-33.211l-38.205-38.199h152.593v152.063l-38.199-38.211c-9.171-9.18-24.039-9.18-33.216-0.022 c-9.178,9.18-9.178,24.059-0.006,33.222l78.284,78.302c4.41,4.404,10.382,6.881,16.611,6.881c6.233,0,12.208-2.477,16.611-6.881 l78.302-78.296c9.181-9.18,9.181-24.048,0-33.205c-9.174-9.174-24.054-9.174-33.21,0l-38.199,38.188v-152.04h152.051l-38.205,38.199 c-9.18,9.175-9.18,24.037-0.005,33.211c4.587,4.587,10.596,6.881,16.604,6.881c6.01,0,12.024-2.294,16.605-6.875l78.303-78.285 c4.403-4.403,6.887-10.378,6.887-16.611C512.328,252.851,509.845,246.873,505.441,242.47z' }, - statbox: { - path: 'M28.782,56.902H483.88c15.707,0,28.451-12.74,28.451-28.451C512.331,12.741,499.599,0,483.885,0H28.782 C13.074,0,0.331,12.741,0.331,28.451C0.331,44.162,13.074,56.902,28.782,56.902z' + - 'M483.885,136.845H28.782c-15.708,0-28.451,12.741-28.451,28.451c0,15.711,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.74,28.451-28.451C512.331,149.586,499.599,136.845,483.885,136.845z' + - 'M483.885,273.275H28.782c-15.708,0-28.451,12.731-28.451,28.452c0,15.707,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.744,28.451-28.451C512.337,286.007,499.599,273.275,483.885,273.275z' + - 'M256.065,409.704H30.492c-15.708,0-28.451,12.731-28.451,28.451c0,15.707,12.744,28.451,28.451,28.451h225.585 c15.707,0,28.451-12.744,28.451-28.451C284.516,422.436,271.785,409.704,256.065,409.704z' - }, - circle: { path: 'M256,256 m-150,0 a150,150 0 1,0 300,0 a150,150 0 1,0 -300,0' }, - three_circles: { path: 'M256,85 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,255 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,425 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 ' }, - diamand: { path: 'M256,0L384,256L256,511L128,256z' }, - rect: { path: 'M90,90h352v352h-352z' }, - cross: { path: 'M80,40l176,176l176,-176l40,40l-176,176l176,176l-40,40l-176,-176l-176,176l-40,-40l176,-176l-176,-176z' }, - vrgoggles: { size: '245.82 141.73', path: 'M175.56,111.37c-22.52,0-40.77-18.84-40.77-42.07S153,27.24,175.56,27.24s40.77,18.84,40.77,42.07S198.08,111.37,175.56,111.37ZM26.84,69.31c0-23.23,18.25-42.07,40.77-42.07s40.77,18.84,40.77,42.07-18.26,42.07-40.77,42.07S26.84,92.54,26.84,69.31ZM27.27,0C11.54,0,0,12.34,0,28.58V110.9c0,16.24,11.54,30.83,27.27,30.83H99.57c2.17,0,4.19-1.83,5.4-3.7L116.47,118a8,8,0,0,1,12.52-.18l11.51,20.34c1.2,1.86,3.22,3.61,5.39,3.61h72.29c15.74,0,27.63-14.6,27.63-30.83V28.58C245.82,12.34,233.93,0,218.19,0H27.27Z' }, - th2colorz: { recs: [{ x: 128, y: 486, w: 256, h: 26, f: 'rgb(38,62,168)' }, { y: 461, f: 'rgb(22,82,205)' }, { y: 435, f: 'rgb(16,100,220)' }, { y: 410, f: 'rgb(18,114,217)' }, { y: 384, f: 'rgb(20,129,214)' }, { y: 358, f: 'rgb(14,143,209)' }, { y: 333, f: 'rgb(9,157,204)' }, { y: 307, f: 'rgb(13,167,195)' }, { y: 282, f: 'rgb(30,175,179)' }, { y: 256, f: 'rgb(46,183,164)' }, { y: 230, f: 'rgb(82,186,146)' }, { y: 205, f: 'rgb(116,189,129)' }, { y: 179, f: 'rgb(149,190,113)' }, { y: 154, f: 'rgb(179,189,101)' }, { y: 128, f: 'rgb(209,187,89)' }, { y: 102, f: 'rgb(226,192,75)' }, { y: 77, f: 'rgb(244,198,59)' }, { y: 51, f: 'rgb(253,210,43)' }, { y: 26, f: 'rgb(251,230,29)' }, { y: 0, f: 'rgb(249,249,15)' }] }, - th2color: { recs: [{x:0,y:256,w:13,h:39,f:'rgb(38,62,168)'},{x:13,y:371,w:39,h:39},{y:294,h:39},{y:256,h:39},{y:218,h:39},{x:51,y:410,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:90,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294},{y:256,h:39,f:'rgb(16,100,220)'},{y:218,h:39},{y:179,h:39,f:'rgb(22,82,205)'},{y:141,h:39},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:0,h:27},{x:128,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294,f:'rgb(20,129,214)'},{y:256,h:39,f:'rgb(9,157,204)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(20,129,214)'},{y:141,h:39,f:'rgb(16,100,220)'},{y:102,h:39,f:'rgb(22,82,205)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{y:0,h:27},{x:166,y:486,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(82,186,146)'},{y:256,h:39,f:'rgb(179,189,101)'},{y:218,h:39,f:'rgb(116,189,129)'},{y:179,h:39,f:'rgb(82,186,146)'},{y:141,h:39,f:'rgb(14,143,209)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:205,y:486,w:39,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(16,100,220)'},{y:333,h:39,f:'rgb(9,157,204)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(244,198,59)'},{y:218,h:39},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(13,167,195)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(22,82,205)'},{y:26,h:39,f:'rgb(38,62,168)'},{x:243,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(30,175,179)'},{y:294,f:'rgb(209,187,89)'},{y:256,h:39,f:'rgb(251,230,29)'},{y:218,h:39,f:'rgb(249,249,15)'},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(30,175,179)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:282,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(14,143,209)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(226,192,75)'},{y:218,h:39,f:'rgb(244,198,59)'},{y:179,h:39,f:'rgb(149,190,113)'},{y:141,h:39,f:'rgb(9,157,204)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:320,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(46,183,164)'},{y:256,h:39},{y:218,h:39,f:'rgb(82,186,146)'},{y:179,h:39,f:'rgb(9,157,204)'},{y:141,h:39,f:'rgb(20,129,214)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:358,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39},{y:294,f:'rgb(16,100,220)'},{y:256,h:39,f:'rgb(20,129,214)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(18,114,217)'},{y:141,h:39,f:'rgb(22,82,205)'},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:26,h:39},{x:397,y:448,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294,f:'rgb(22,82,205)'},{y:256,h:39},{y:218,h:39},{y:179,h:39,f:'rgb(38,62,168)'},{y:141,h:39},{y:102,h:39},{y:64},{y:26,h:39},{x:435,y:410,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:474,y:256,h:39},{y:179,h:39}] }, - th2draw3d: { - path: 'M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z '+ - 'M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z '+ - 'M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z '+ - 'M460.293,256.149H339.237c-28.521,0-51.721,23.199-51.721,51.726v89.915c0,28.504,23.2,51.715,51.721,51.715h121.045 c28.521,0,51.721-23.199,51.721-51.715v-89.915C512.002,279.354,488.802,256.149,460.293,256.149z M465.03,397.784 c0,2.615-2.122,4.736-4.748,4.736H339.237c-2.614,0-4.747-2.121-4.747-4.736v-89.909c0-2.626,2.121-4.753,4.747-4.753h121.045 c2.615,0,4.748,2.116,4.748,4.753V397.784z' - }, + if ( groups.length > 0 ) { - createSVG(group, btn, size, title, arg) { - const use_dark = (arg === true) || (arg === false) ? arg : settings.DarkMode, - opacity0 = (arg === 'browser') ? (browser.touches ? 0.2 : 0) : (use_dark ? 0.8 : 0.2), - svg = group.append('svg:svg') - .attr('width', size + 'px') - .attr('height', size + 'px') - .attr('viewBox', '0 0 512 512') - .style('overflow', 'hidden') - .style('cursor', 'pointer') - .style('fill', use_dark ? 'rgba(255, 224, 160)' : 'steelblue') - .style('opacity', opacity0) - .property('opacity0', opacity0) - .property('opacity1', use_dark ? 1 : 0.8) - .on('mouseenter', function() { - const elem = select(this); - elem.style('opacity', elem.property('opacity1')); - const func = elem.node()._mouseenter; - if (isFunc(func)) func(); - }) - .on('mouseleave', function() { - const elem = select(this); - elem.style('opacity', elem.property('opacity0')); - const func = elem.node()._mouseleave; - if (isFunc(func)) func(); - }); + for ( let g = 0; g < groups.length; g ++ ) { - if ('recs' in btn) { - const rec = {}; - for (let n = 0; n < btn.recs.length; ++n) { - Object.assign(rec, btn.recs[n]); - svg.append('rect').attr('x', rec.x).attr('y', rec.y) - .attr('width', rec.w).attr('height', rec.h) - .style('fill', rec.f); - } - } else - svg.append('svg:path').attr('d', btn.path); + const group = groups[ g ]; + material = isMultiMaterial === true + ? object.material[ group.materialIndex ] + : object.material; - // special rect to correctly get mouse events for whole button area - svg.append('svg:rect').attr('x', 0).attr('y', 0).attr('width', 512).attr('height', 512) - .style('opacity', 0).style('fill', 'none').style('pointer-events', 'visibleFill') - .append('svg:title').text(title); + if ( material === undefined ) continue; - return svg; - } + for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { -}; // ToolbarIcons + renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); + } -/** @summary Register handle to react on window resize - * @desc function used to react on browser window resize event - * While many resize events could come in short time, - * resize will be handled with delay after last resize event - * @param {object|string} handle can be function or object with checkResize function or dom where painting was done - * @param {number} [delay] - one could specify delay after which resize event will be handled - * @protected */ -function registerForResize(handle, delay) { - if (!handle || isBatchMode() || (typeof window === 'undefined') || (typeof document === 'undefined')) return; + } - let myInterval = null, myDelay = delay || 300; - if (myDelay < 20) myDelay = 20; + } else { - function ResizeTimer() { - myInterval = null; + for ( let i = 0, l = indices.length; i < l; i += 3 ) { - document.body.style.cursor = 'wait'; - if (isFunc(handle)) - handle(); - else if (isFunc(handle?.checkResize)) - handle.checkResize(); - else { - const node = new BasePainter(handle).selectDom(); - if (!node.empty()) { - const mdi = node.property('mdi'); - if (isFunc(mdi?.checkMDIResize)) - mdi.checkMDIResize(); - else - resize(node.node()); - } - } - document.body.style.cursor = 'auto'; - } - - window.addEventListener('resize', () => { - if (myInterval !== null) clearTimeout(myInterval); - myInterval = setTimeout(ResizeTimer, myDelay); - }); -} - -/** @summary Detect mouse right button - * @private */ -function detectRightButton(event) { - return (event?.buttons === 2) || (event?.button === 2); -} + renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); -/** @summary Add move handlers for drawn element - * @private */ -function addMoveHandler(painter, enabled = true) { - if (!settings.MoveResize || painter.isBatchMode() || !painter.draw_g) return; + } - if (painter.getPadPainter()?.isEditable() === false) - enabled = false; + } - if (!enabled) { - if (painter.draw_g.property('assigned_move')) { - const drag_move = drag().subject(Object); - drag_move.on('start', null).on('drag', null).on('end', null); - painter.draw_g - .style('cursor', null) - .property('assigned_move', null) - .call(drag_move); - } - return; - } + } else { - if (painter.draw_g.property('assigned_move')) return; + if ( groups.length > 0 ) { - const drag_move = drag().subject(Object); - let not_changed = true, move_disabled = false; + for ( let g = 0; g < groups.length; g ++ ) { - drag_move - .on('start', function(evnt) { - move_disabled = this.moveEnabled ? !this.moveEnabled() : false; - if (move_disabled) return; - if (detectRightButton(evnt.sourceEvent)) return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - const pos = pointer(evnt, this.draw_g.node()); - not_changed = true; - if (this.moveStart) - this.moveStart(pos[0], pos[1]); - }.bind(painter)).on('drag', function(evnt) { - if (move_disabled) return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - not_changed = false; - if (this.moveDrag) - this.moveDrag(evnt.dx, evnt.dy); - }.bind(painter)).on('end', function(evnt) { - if (move_disabled) return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - if (this.moveEnd) - this.moveEnd(not_changed); + const group = groups[ g ]; - let arg = null; - if (not_changed) { - // if not changed - provide click position - const pos = pointer(evnt, this.draw_g.node()); - arg = { x: pos[0], y: pos[1], dbl: false }; - } - this.getPadPainter()?.selectObjectPainter(this, arg); - }.bind(painter)); + material = isMultiMaterial === true + ? object.material[ group.materialIndex ] + : object.material; - painter.draw_g - .style('cursor', 'move') - .property('assigned_move', true) - .call(drag_move); -} + if ( material === undefined ) continue; -/** @summary Inject style - * @param {String} code - css string - * @private */ -function injectStyle(code, node, tag) { - if (isBatchMode() || !code || (typeof document === 'undefined')) - return true; + for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { - const styles = (node || document).getElementsByTagName('style'); - for (let n = 0; n < styles.length; ++n) { - if (tag && styles[n].getAttribute('tag') === tag) { - styles[n].innerHTML = code; - return true; - } + renderList.pushTriangle( i, i + 1, i + 2, material ); - if (styles[n].innerHTML === code) - return true; - } + } - const element = document.createElement('style'); - if (tag) element.setAttribute('tag', tag); - element.innerHTML = code; - (node || document.head).appendChild(element); - return true; -} + } -/** @summary Select predefined style - * @private */ -function selectgStyle(name) { - gStyle.fName = name; - switch (name) { - case 'Modern': Object.assign(gStyle, { fFrameBorderMode: 0, fFrameFillColor: 0, - fCanvasBorderMode: 0, fCanvasColor: 0, fPadBorderMode: 0, fPadColor: 0, fStatColor: 0, - fTitleAlign: 23, fTitleX: 0.5, fTitleBorderSize: 0, fTitleColor: 0, fTitleStyle: 0, - fOptStat: 1111, fStatY: 0.935, - fLegendBorderSize: 1, fLegendFont: 42, fLegendTextSize: 0, fLegendFillColor: 0 }); - break; - case 'Plain': Object.assign(gStyle, { fFrameBorderMode: 0, - fCanvasBorderMode: 0, fPadBorderMode: 0, fPadColor: 0, fCanvasColor: 0, - fTitleColor: 0, fTitleBorderSize: 0, fStatColor: 0, fStatBorderSize: 1, fLegendBorderSize: 1 }); - break; - case 'Bold': Object.assign(gStyle, { fCanvasColor: 10, fCanvasBorderMode: 0, - fFrameLineWidth: 3, fFrameFillColor: 10, - fPadColor: 10, fPadTickX: 1, fPadTickY: 1, fPadBottomMargin: 0.15, fPadLeftMargin: 0.15, - fTitleColor: 10, fTitleTextColor: 600, fStatColor: 10 }); - break; - } -} + } else { -let _storage_prefix = 'jsroot_'; + for ( let i = 0, l = positions.length / 3; i < l; i += 3 ) { -/** @summary Set custom prefix for the local storage - * @private */ -function setStoragePrefix(prefix) { - _storage_prefix = prefix || 'jsroot_'; -} + renderList.pushTriangle( i, i + 1, i + 2, material ); -/** @summary Save object in local storage - * @private */ -function saveLocalStorage(obj, expires, name) { - if (typeof localStorage === 'undefined') - return; - if (Number.isFinite(expires) && (expires < 0)) - localStorage.removeItem(_storage_prefix + name); - else - localStorage.setItem(_storage_prefix + name, btoa_func(JSON.stringify(obj))); -} + } -/** @summary Read object from storage with specified name - * @private */ -function readLocalStorage(name) { - if (typeof localStorage === 'undefined') - return null; - const v = localStorage.getItem(_storage_prefix + name), - s = v ? JSON.parse(atob_func(v)) : null; - return isObject(s) ? s : null; -} + } -/** @summary Save JSROOT settings in local storage - * @param {Number} [expires] - delete settings when negative - * @param {String} [name] - storage name, 'settings' by default - * @private */ -function saveSettings(expires = 365, name = 'settings') { - saveLocalStorage(settings, expires, name); -} + } -/** @summary Read JSROOT settings from specified cookie parameter - * @param {Boolean} only_check - when true just checks if settings were stored before with provided name - * @param {String} [name] - storage name, 'settings' by default - * @private */ -function readSettings(only_check = false, name = 'settings') { - const s = readLocalStorage(name); - if (!s) return false; - if (!only_check) - Object.assign(settings, s); - return true; -} + } else if ( object.isLine ) { -/** @summary Save JSROOT gStyle object in local storage - * @param {Number} [expires] - delete style when negative - * @param {String} [name] - storage name, 'style' by default - * @private */ -function saveStyle(expires = 365, name = 'style') { - saveLocalStorage(gStyle, expires, name); -} + _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); -/** @summary Read JSROOT gStyle object from local storage - * @param {Boolean} [only_check] - when true just checks if settings were stored before with provided name - * @param {String} [name] - storage name, 'style' by default - * @private */ -function readStyle(only_check = false, name = 'style') { - const s = readLocalStorage(name); - if (!s) return false; - if (!only_check) - Object.assign(gStyle, s); - return true; -} + const attributes = geometry.attributes; -let _saveFileFunc = null; + if ( attributes.position !== undefined ) { -/** @summary Returns image file content as it should be stored on the disc - * @desc Replaces all kind of base64 coding - * @private */ + const positions = attributes.position.array; -function getBinFileContent(content) { - const svg_prefix = 'data:image/svg+xml;charset=utf-8,'; + for ( let i = 0, l = positions.length; i < l; i += 3 ) { - if (content.indexOf(svg_prefix) === 0) - return decodeURIComponent(content.slice(svg_prefix.length)); + renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); - if (content.indexOf('data:image/') === 0) { - const p = content.indexOf('base64,'); - if (p > 0) { - const base64 = content.slice(p + 7); - return atob_func(base64); - } - } + } - return content; -} + if ( attributes.color !== undefined ) { -/** @summary Function store content as file with filename - * @private */ -async function saveFile(filename, content) { - if (isFunc(_saveFileFunc)) - return _saveFileFunc(filename, getBinFileContent(content)); - if (isNodeJs()) { - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { - fs.writeFileSync(filename, getBinFileContent(content)); - return true; - }); - } else if (typeof document !== 'undefined') { - const a = document.createElement('a'); - a.download = filename; - a.href = content; - document.body.appendChild(a); + const colors = attributes.color.array; - return new Promise(resolve => { - a.addEventListener('click', () => { a.parentNode.removeChild(a); resolve(true); }); - a.click(); - }); - } - return false; -} + for ( let i = 0, l = colors.length; i < l; i += 3 ) { -/** @summary Function store content as file with filename - * @private */ -function setSaveFile(func) { - _saveFileFunc = func; -} + renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); -/** @summary Returns color id for the color - * @private */ -function getColorId(col) { - const arr = getRootColors(); - let id = -1; - if (isStr(col)) { - if (!col || (col === 'none')) - id = 0; - else { - for (let k = 1; k < arr.length; ++k) - if (arr[k] === col) { id = k; break; } - } - if ((id < 0) && (col.indexOf('rgb') === 0)) - id = 9999; - } else if (Number.isInteger(col) && arr[col]) { - id = col; - col = arr[id]; - } + } - return { id, col }; -} + } -/** @summary Produce exec string for WebCanas to set color value - * @desc Color can be id or string, but should belong to list of known colors - * For higher color numbers TColor::GetColor(r,g,b) will be invoked to ensure color is exists - * @private */ -function getColorExec(col, method) { - const d = getColorId(col); + if ( geometry.index !== null ) { - if (d.id < 0) - return ''; + const indices = geometry.index.array; - // for higher color numbers ensure that such color exists - if (d.id >= 50) { - const c = color(d.col); - d.id = `TColor::GetColor(${c.r},${c.g},${c.b})`; - } + for ( let i = 0, l = indices.length; i < l; i += 2 ) { - return `exec:${method}(${d.id})`; -} + renderList.pushLine( indices[ i ], indices[ i + 1 ] ); -/** @summary Change object member in the painter - * @desc Used when interactively change in the menu - * Special handling for color is provided - * @private */ -function changeObjectMember(painter, member, val, is_color) { - if (is_color) { - const d = getColorId(val); - if ((d.id < 0) || (d.id === 9999)) - return; - val = d.id; - } + } - const obj = painter?.getObject(); - if (obj && (obj[member] !== undefined)) - obj[member] = val; -} + } else { -const kToFront = '__front__'; + const step = object.isLineSegments ? 2 : 1; -/** - * @summary Abstract class for creating context menu - * - * @desc Use {@link createMenu} to create instance of the menu - * @private - */ + for ( let i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) { -class JSRootMenu { + renderList.pushLine( i, i + 1 ); - constructor(painter, menuname, show_event) { - this.painter = painter; - this.menuname = menuname; - if (isObject(show_event) && (show_event.clientX !== undefined) && (show_event.clientY !== undefined)) - this.show_evnt = { clientX: show_event.clientX, clientY: show_event.clientY, skip_close: show_event.skip_close }; + } - this.remove_handler = () => this.remove(); - this.element = null; - this.cnt = 0; - } + } - native() { return false; } + } - async load() { return this; } + } else if ( object.isPoints ) { - /** @summary Returns object with mouse event position when context menu was actiavted - * @desc Return object will have members 'clientX' and 'clientY' */ - getEventPosition() { return this.show_evnt; } + _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); - add(/* name, arg, func, title */) { - throw Error('add() method has to be implemented in the menu'); - } + const attributes = geometry.attributes; - /** @summary Returns menu size */ - size() { return this.cnt; } + if ( attributes.position !== undefined ) { - /** @summary Close and remove menu */ - remove() { - if (!this.element) - return; + const positions = attributes.position.array; - if (this.show_evnt?.skip_close) { - this.show_evnt.skip_close = 0; - return; - } + for ( let i = 0, l = positions.length; i < l; i += 3 ) { - this.element.remove(); - this.element = null; - if (isFunc(this.resolveFunc)) { - const func = this.resolveFunc; - delete this.resolveFunc; - func(); - } - document.body.removeEventListener('click', this.remove_handler); - } + _vector4.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ], 1 ); + _vector4.applyMatrix4( _modelViewProjectionMatrix ); - show(/* event */) { - throw Error('show() method has to be implemented in the menu class'); - } + pushPoint( _vector4, object, camera ); - /** @summary Add checked menu item - * @param {boolean} flag - flag - * @param {string} name - item name - * @param {function} func - func called when item is selected */ - addchk(flag, name, arg, func, title) { - let handler = func; - if (isFunc(arg)) { - title = func; - func = arg; - handler = res => func(res === '1'); - arg = flag ? '0' : '1'; - } - this.add((flag ? 'chk:' : 'unk:') + name, arg, handler, title); - } + } - /** @summary Add draw sub-menu with draw options - * @protected */ - addDrawMenu(top_name, opts, call_back, title) { - if (!opts || !opts.length) - return; + } - let without_sub = false; - if (top_name.indexOf('nosub:') === 0) { - without_sub = true; - top_name = top_name.slice(6); - } + } else if ( object.isSprite ) { - if (opts.length === 1) { - if (opts[0] === kInspect) - top_name = top_name.replace('Draw', 'Inspect'); - this.add(top_name, opts[0], call_back); - return; - } + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + _vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); + _vector4.applyMatrix4( _viewProjectionMatrix ); - if (!without_sub) - this.add('sub:' + top_name, opts[0], call_back, title); + pushPoint( _vector4, object, camera ); - for (let i = 1; i < opts.length; ++i) { - let name = opts[i] || (this._use_plain_text ? '' : '<dflt>'), - group = i+1; - if (opts.length > 5) { - // check if there are similar options, which can be grouped once again - while ((group < opts.length) && (opts[group].indexOf(name) === 0)) group++; - } + } - if (without_sub) - name = top_name + ' ' + name; + } - if (group >= i+2) { - this.add('sub:' + name, opts[i], call_back); - for (let k = i+1; k < group; ++k) - this.add(opts[k], opts[k], call_back); - this.add('endsub:'); - i = group - 1; - } else if (name === kInspect) { - this.add('sub:' + name, opts[i], call_back, 'Inspect object content'); - for (let k = 0; k < 10; ++k) - this.add(k.toString(), kInspect + k, call_back, `Inspect object and expand to level ${k}`); - this.add('endsub:'); - } else - this.add(name, opts[i], call_back); - } - if (!without_sub) { - this.add('', () => { - const opt = isFunc(this.painter?.getDrawOpt) ? this.painter.getDrawOpt() : opts[0]; - this.input('Provide draw option', opt, 'text').then(call_back); - }, 'Enter draw option in dialog'); - this.add('endsub:'); - } - } + if ( sortElements === true ) { - /** @summary Add color selection menu entries - * @protected */ - addColorMenu(name, value, set_func, fill_kind) { - if (value === undefined) return; - const useid = !isStr(value); - this.add('sub:' + name, () => { - this.input('Enter color ' + (useid ? '(only id number)' : '(name or id)'), value, useid ? 'int' : 'text', useid ? 0 : undefined, useid ? 9999 : undefined).then(col => { - const id = parseInt(col); - if (Number.isInteger(id) && getColor(id)) - col = getColor(id); - else - if (useid) return; + _renderData.elements.sort( painterSort ); - set_func(useid ? id : col); - }); - }); + } - for (let ncolumn = 0; ncolumn < 5; ++ncolumn) { - this.add('column:'); + return _renderData; - for (let nrow = 0; nrow < 10; nrow++) { - let n = ncolumn*10 + nrow; - if (!useid) --n; // use -1 as none color + }; - let col = (n < 0) ? 'none' : getColor(n); - if ((n === 0) && (fill_kind === 1)) col = 'none'; - const lbl = (n <= 0) || (col[0] !== '#') ? col : `col ${n}`, - fill = (n === 1) ? 'white' : 'black', - stroke = (n === 1) ? 'red' : 'black', - rect = (value === (useid ? n : col)) ? `` : '', - svg = `${rect}${lbl}`; + function pushPoint( _vector4, object, camera ) { - this.add(svg, (useid ? n : col), res => set_func(useid ? parseInt(res) : res), 'Select color ' + col); - } + const invW = 1 / _vector4.w; - this.add('endcolumn:'); - if (!this.native()) break; - } + _vector4.z *= invW; - this.add('endsub:'); - } + if ( _vector4.z >= -1 && _vector4.z <= 1 ) { - /** @summary Add size selection menu entries - * @protected */ - addSizeMenu(name, min, max, step, size_value, set_func, title) { - if (size_value === undefined) return; + _sprite = getNextSpriteInPool(); + _sprite.id = object.id; + _sprite.x = _vector4.x * invW; + _sprite.y = _vector4.y * invW; + _sprite.z = _vector4.z; + _sprite.renderOrder = object.renderOrder; + _sprite.object = object; - let values = [], miss_current = false; - if (isObject(step)) { - values = step; step = 1; - } else { - for (let sz = min; sz <= max; sz += step) - values.push(sz); - } + _sprite.rotation = object.rotation; - const match = v => Math.abs(v-size_value) < (max - min)*1e-5, - conv = (v, more) => { - if ((v === size_value) && miss_current) more = true; - if (step >= 1) return v.toFixed(0); - if (step >= 0.1) return v.toFixed(more ? 2 : 1); - return v.toFixed(more ? 4 : 2); - }; + _sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) ); + _sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) ); - if (values.findIndex(match) < 0) { - miss_current = true; - values.push(size_value); - values = values.sort((a, b) => a > b); - } + _sprite.material = object.material; - this.add('sub:' + name, () => this.input('Enter value of ' + name, conv(size_value, true), (step >= 1) ? 'int' : 'float').then(set_func), title); - values.forEach(v => this.addchk(match(v), conv(v), v, res => set_func((step >= 1) ? Number.parseInt(res) : Number.parseFloat(res)))); - this.add('endsub:'); - } + _renderData.elements.push( _sprite ); - /** @summary Add palette menu entries - * @protected */ - addPaletteMenu(curr, set_func) { - const add = (id, name, title, more) => { - if (!name) - name = `pal ${id}`; - else if (!title) - title = name; - if (title) title += `, code ${id}`; - this.addchk((id === curr) || more, '' + name + '', id, set_func, title || name); - }; + } - this.add('sub:Palette', () => this.input('Enter palette code [1..113]', curr, 'int', 1, 113).then(set_func)); + } - this.add('column:'); + // Pools - add(57, 'Bird', 'Default color palette', (curr > 113)); - add(55, 'Rainbow'); - add(51, 'Deep Sea'); - add(52, 'Grayscale', 'New gray scale'); - add(1, '', 'Old gray scale', (curr > 0) && (curr < 10)); - add(50, 'ROOT 5', 'Default color palette in ROOT 5', (curr >= 10) && (curr < 51)); - add(53, '', 'Dark body radiator'); - add(54, '', 'Two-color hue'); - add(56, '', 'Inverted dark body radiator'); - add(58, 'Cubehelix'); - add(59, '', 'Green Red Violet'); - add(60, '', 'Blue Red Yellow'); - add(61, 'Ocean'); + function getNextObjectInPool() { - this.add('endcolumn:'); + if ( _objectCount === _objectPoolLength ) { - if (!this.native()) - return this.add('endsub:'); + const object = new RenderableObject(); + _objectPool.push( object ); + _objectPoolLength ++; + _objectCount ++; + return object; - this.add('column:'); + } - add(62, '', 'Color Printable On Grey'); - add(63, 'Alpine'); - add(64, 'Aquamarine'); - add(65, 'Army'); - add(66, 'Atlantic'); - add(67, 'Aurora'); - add(68, 'Avocado'); - add(69, 'Beach'); - add(70, 'Black Body'); - add(71, '', 'Blue Green Yellow'); - add(72, 'Brown Cyan'); - add(73, 'CMYK'); - add(74, 'Candy'); + return _objectPool[ _objectCount ++ ]; - this.add('endcolumn:'); - this.add('column:'); + } - add(75, 'Cherry'); - add(76, 'Coffee'); - add(77, '', 'Dark Rain Bow'); - add(78, '', 'Dark Terrain'); - add(79, 'Fall'); - add(80, 'Fruit Punch'); - add(81, 'Fuchsia'); - add(82, 'Grey Yellow'); - add(83, '', 'Green Brown Terrain'); - add(84, 'Green Pink'); - add(85, 'Island'); - add(86, 'Lake'); - add(87, '', 'Light Temperature'); + function getNextVertexInPool() { - this.add('endcolumn:'); - this.add('column:'); + if ( _vertexCount === _vertexPoolLength ) { - add(88, '', 'Light Terrain'); - add(89, 'Mint'); - add(90, 'Neon'); - add(91, 'Pastel'); - add(92, 'Pearl'); - add(93, 'Pigeon'); - add(94, 'Plum'); - add(95, 'Red Blue'); - add(96, 'Rose'); - add(97, 'Rust'); - add(98, '', 'Sandy Terrain'); - add(99, 'Sienna'); - add(100, 'Solar'); + const vertex = new RenderableVertex(); + _vertexPool.push( vertex ); + _vertexPoolLength ++; + _vertexCount ++; + return vertex; - this.add('endcolumn:'); - this.add('column:'); + } - add(101, '', 'South West'); - add(102, '', 'Starry Night'); - add(103, '', 'Sunset'); - add(104, '', 'Temperature Map'); - add(105, '', 'Thermometer'); - add(106, 'Valentine'); - add(107, '', 'Visible Spectrum'); - add(108, '', 'Water Melon'); - add(109, 'Cool'); - add(110, 'Copper'); - add(111, '', 'Gist Earth'); - add(112, 'Viridis'); - add(113, 'Cividis'); + return _vertexPool[ _vertexCount ++ ]; - this.add('endcolumn:'); + } - this.add('endsub:'); - } + function getNextFaceInPool() { - /** @summary Add rebin menu entries - * @protected */ - addRebinMenu(rebin_func) { - this.add('sub:Rebin', () => this.input('Enter rebin value', 2, 'int', 2).then(rebin_func)); - for (let sz = 2; sz <= 7; sz++) - this.add(sz.toString(), sz, res => rebin_func(parseInt(res))); - this.add('endsub:'); - } + if ( _faceCount === _facePoolLength ) { - /** @summary Add selection menu entries - * @param {String} name - name of submenu - * @param {Array} values - array of string entries used as list for selection - * @param {String|Number} value - currently elected value, either name or index - * @param {Function} set_func - function called when item selected, either name or index depending from value parameter - * @protected */ - addSelectMenu(name, values, value, set_func) { - const use_number = (typeof value === 'number'); - this.add('sub:' + name); - for (let n = 0; n < values.length; ++n) - this.addchk(use_number ? (n === value) : (values[n] === value), values[n], use_number ? n : values[n], res => set_func(use_number ? Number.parseInt(res) : res)); - this.add('endsub:'); - } + const face = new RenderableFace(); + _facePool.push( face ); + _facePoolLength ++; + _faceCount ++; + return face; - /** @summary Add RColor selection menu entries - * @protected */ - addRColorMenu(name, value, set_func) { - // if (value === undefined) return; - const colors = ['default', 'black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; + } - this.add('sub:' + name, () => { - this.input('Enter color name - empty string will reset color', value).then(set_func); - }); - let fillcol = 'black'; - for (let n = 0; n < colors.length; ++n) { - const coltxt = colors[n]; - let match = false, bkgr = ''; - if (n > 0) { - bkgr = 'background-color:' + coltxt; - fillcol = (coltxt === 'white') ? 'black' : 'white'; + return _facePool[ _faceCount ++ ]; - if (isStr(value) && value && (value !== 'auto') && (value[0] !== '[')) - match = (rgb(value).toString() === rgb(coltxt).toString()); - } else - match = !value; - const svg = `${coltxt}`; - this.addchk(match, svg, coltxt, res => set_func(res === 'default' ? null : res)); - } - this.add('endsub:'); - } + } - /** @summary Add items to change RAttrText - * @protected */ - addRAttrTextItems(fontHandler, opts, set_func) { - if (!opts) opts = {}; - this.addRColorMenu('color', fontHandler.color, value => set_func({ name: 'color', value })); - if (fontHandler.scaled) - this.addSizeMenu('size', 0.01, 0.10, 0.01, fontHandler.size /fontHandler.scale, value => set_func({ name: 'size', value })); - else - this.addSizeMenu('size', 6, 20, 2, fontHandler.size, value => set_func({ name: 'size', value })); + function getNextLineInPool() { - this.addSelectMenu('family', ['Arial', 'Times New Roman', 'Courier New', 'Symbol'], fontHandler.name, value => set_func({ name: 'font_family', value })); + if ( _lineCount === _linePoolLength ) { - this.addSelectMenu('style', ['normal', 'italic', 'oblique'], fontHandler.style || 'normal', res => set_func({ name: 'font_style', value: res === 'normal' ? null : res })); + const line = new RenderableLine(); + _linePool.push( line ); + _linePoolLength ++; + _lineCount ++; + return line; - this.addSelectMenu('weight', ['normal', 'lighter', 'bold', 'bolder'], fontHandler.weight || 'normal', res => set_func({ name: 'font_weight', value: res === 'normal' ? null : res })); + } - if (!opts.noalign) - this.add('align'); - if (!opts.noangle) - this.add('angle'); - } + return _linePool[ _lineCount ++ ]; - /** @summary Add line style menu - * @private */ - addLineStyleMenu(name, value, set_func) { - this.add('sub:'+name, () => this.input('Enter line style id (1-solid)', value, 'int', 1, 11).then(val => { - if (getSvgLineStyle(val)) set_func(val); - })); - for (let n = 1; n < 11; ++n) { - const dash = getSvgLineStyle(n), - svg = `${n}`; + } - this.addchk((value === n), svg, n, arg => set_func(parseInt(arg))); - } - this.add('endsub:'); - } + function getNextSpriteInPool() { - /** @summary Add fill style menu - * @private */ - addFillStyleMenu(name, value, color_index, painter, set_func) { - this.add('sub:' + name, () => { - this.input('Enter fill style id (1001-solid, 3000..3010)', value, 'int', 0, 4000).then(id => { - if ((id >= 0) && (id <= 4000)) set_func(id); - }); - }); + if ( _spriteCount === _spritePoolLength ) { - const supported = [1, 1001, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3010, 3021, 3022]; + const sprite = new RenderableSprite(); + _spritePool.push( sprite ); + _spritePoolLength ++; + _spriteCount ++; + return sprite; - for (let n = 0; n < supported.length; ++n) { - let svg = supported[n]; - if (painter) { - const sample = painter.createAttFill({ std: false, pattern: supported[n], color: color_index || 1 }); - svg = `${supported[n].toString()}`; - } - this.addchk(value === supported[n], svg, supported[n], arg => set_func(parseInt(arg))); - } - this.add('endsub:'); - } + } - /** @summary Add font selection menu - * @private */ - addFontMenu(name, value, set_func) { - const prec = value && Number.isInteger(value) ? value % 10 : 2; + return _spritePool[ _spriteCount ++ ]; - this.add('sub:' + name, () => { - this.input('Enter font id from [0..20]', Math.floor(value/10), 'int', 0, 20).then(id => { - if ((id >= 0) && (id <= 20)) set_func(id*10 + prec); - }); - }); + } - this.add('column:'); + // - const doc = getDocument(); + function painterSort( a, b ) { - for (let n = 1; n < 20; ++n) { - const id = n*10 + prec, - handler = new FontHandler(id, 14), - txt = select(doc.createElementNS(nsSVG, 'text')); - let fullname = handler.getFontName(), qual = ''; - if (handler.weight) { qual += 'b'; fullname += ' ' + handler.weight; } - if (handler.style) { qual += handler.style[0]; fullname += ' ' + handler.style; } - if (qual) qual = ' ' + qual; - txt.attr('x', 1).attr('y', 15).text(fullname.split(' ')[0] + qual); - handler.setFont(txt); + if ( a.renderOrder !== b.renderOrder ) { - const rect = (value !== id) ? '' : '', - svg = `${txt.node().outerHTML}${rect}`; - this.add(svg, id, arg => set_func(parseInt(arg)), `${id}: ${fullname}`); + return a.renderOrder - b.renderOrder; - if (n === 10) { - this.add('endcolumn:'); - this.add('column:'); - } - } + } else if ( a.z !== b.z ) { - this.add('endcolumn:'); - this.add('endsub:'); - } + return b.z - a.z; - /** @summary Add align selection menu - * @private */ - addAlignMenu(name, value, set_func) { - this.add(`sub:${name}`, () => { - this.input('Enter align like 12 or 31', value).then(arg => { - const id = parseInt(arg); - if ((id < 11) || (id > 33)) return; - const h = Math.floor(id/10), v = id % 10; - if ((h > 0) && (h < 4) && (v > 0) && (v < 4)) set_func(id); - }); - }); + } else if ( a.id !== b.id ) { - const hnames = ['left', 'middle', 'right'], vnames = ['bottom', 'centered', 'top']; - for (let h = 1; h < 4; ++h) { - for (let v = 1; v < 4; ++v) - this.addchk(h*10+v === value, `${h*10+v}: ${hnames[h-1]} ${vnames[v-1]}`, h*10+v, arg => set_func(parseInt(arg))); - } + return a.id - b.id; - this.add('endsub:'); - } + } else { - /** @summary Fill context menu for graphical attributes in painter - * @desc this method used to fill entries for different attributes of the object - * like TAttFill, TAttLine, TAttText - * There is special handling for the frame where attributes handled by the pad - * @private */ - addAttributesMenu(painter, preffix) { - const is_frame = painter === painter.getFramePainter(), - pp = is_frame ? painter.getPadPainter() : null; - if (!preffix) preffix = ''; + return 0; - if (painter.lineatt?.used) { - this.add(`sub:${preffix}Line att`); - this.addSizeMenu('width', 1, 10, 1, painter.lineatt.width, arg => { - painter.lineatt.change(undefined, arg); - changeObjectMember(painter, 'fLineWidth', arg); - if (pp) changeObjectMember(pp, 'fFrameLineWidth', arg); - painter.interactiveRedraw(true, `exec:SetLineWidth(${arg})`); - }); - this.addColorMenu('color', painter.lineatt.color, arg => { - painter.lineatt.change(arg); - changeObjectMember(painter, 'fLineColor', arg, true); - if (pp) changeObjectMember(pp, 'fFrameLineColor', arg, true); - painter.interactiveRedraw(true, getColorExec(arg, 'SetLineColor')); - }); - this.addLineStyleMenu('style', painter.lineatt.style, id => { - painter.lineatt.change(undefined, undefined, id); - changeObjectMember(painter, 'fLineStyle', id); - if (pp) changeObjectMember(pp, 'fFrameLineStyle', id); - painter.interactiveRedraw(true, `exec:SetLineStyle(${id})`); - }); - this.add('endsub:'); + } - if (!is_frame && painter.lineatt?.excl_side) { - this.add('sub:Exclusion'); - this.add('sub:side'); - for (let side = -1; side <= 1; ++side) { - this.addchk((painter.lineatt.excl_side === side), side, side, - arg => { painter.lineatt.changeExcl(parseInt(arg)); painter.interactiveRedraw(); }); - } - this.add('endsub:'); + } - this.addSizeMenu('width', 10, 100, 10, painter.lineatt.excl_width, - arg => { painter.lineatt.changeExcl(undefined, arg); painter.interactiveRedraw(); }); + function clipLine( s1, s2 ) { - this.add('endsub:'); - } - } + let alpha1 = 0, alpha2 = 1; - if (painter.fillatt?.used) { - this.add(`sub:${preffix}Fill att`); - this.addColorMenu('color', painter.fillatt.colorindx, arg => { - painter.fillatt.change(arg, undefined, painter.getCanvSvg()); - changeObjectMember(painter, 'fFillColor', arg, true); - if (pp) changeObjectMember(pp, 'fFrameFillColor', arg, true); - painter.interactiveRedraw(true, getColorExec(arg, 'SetFillColor')); - }, painter.fillatt.kind); - this.addFillStyleMenu('style', painter.fillatt.pattern, painter.fillatt.colorindx, painter, id => { - painter.fillatt.change(undefined, id, painter.getCanvSvg()); - changeObjectMember(painter, 'fFillStyle', id); - if (pp) changeObjectMember(pp, 'fFrameFillStyle', id); - painter.interactiveRedraw(true, `exec:SetFillStyle(${id})`); - }); - this.add('endsub:'); - } + // Calculate the boundary coordinate of each vertex for the near and far clip planes, + // Z = -1 and Z = +1, respectively. - if (painter.markeratt?.used) { - this.add(`sub:${preffix}Marker att`); - this.addColorMenu('color', painter.markeratt.color, arg => { - changeObjectMember(painter, 'fMarkerColor', arg, true); - painter.markeratt.change(arg); - painter.interactiveRedraw(true, getColorExec(arg, 'SetMarkerColor')); - }); - this.addSizeMenu('size', 0.5, 6, 0.5, painter.markeratt.size, arg => { - changeObjectMember(painter, 'fMarkerSize', arg); - painter.markeratt.change(undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetMarkerSize(${arg})`); - }); + const bc1near = s1.z + s1.w, + bc2near = s2.z + s2.w, + bc1far = - s1.z + s1.w, + bc2far = - s2.z + s2.w; - this.add('sub:style'); - const supported = [1, 2, 3, 4, 5, 6, 7, 8, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]; + if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { - for (let n = 0; n < supported.length; ++n) { - const clone = new TAttMarkerHandler({ style: supported[n], color: painter.markeratt.color, size: 1.7 }), - svg = `${supported[n].toString()}`; + // Both vertices lie entirely within all clip planes. + return true; - this.addchk(painter.markeratt.style === supported[n], svg, supported[n], - arg => { painter.markeratt.change(undefined, parseInt(arg)); painter.interactiveRedraw(true, `exec:SetMarkerStyle(${arg})`); }); - } - this.add('endsub:'); - this.add('endsub:'); - } + } else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) { - if (painter.textatt?.used) { - this.add(`sub:${preffix}Text att`); + // Both vertices lie entirely outside one of the clip planes. + return false; - this.addFontMenu('font', painter.textatt.font, arg => { - changeObjectMember(painter, 'fTextFont', arg); - painter.textatt.change(arg); - painter.interactiveRedraw(true, `exec:SetTextFont(${arg})`); - }); + } else { - const rel = painter.textatt.size < 1.0; + // The line segment spans at least one clip plane. - this.addSizeMenu('size', rel ? 0.03 : 6, rel ? 0.20 : 26, rel ? 0.01 : 2, painter.textatt.size, arg => { - changeObjectMember(painter, 'fTextSize', arg); - painter.textatt.change(undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextSize(${arg})`); - }); + if ( bc1near < 0 ) { - this.addColorMenu('color', painter.textatt.color, arg => { - changeObjectMember(painter, 'fTextColor', arg, true); - painter.textatt.change(undefined, undefined, arg); - painter.interactiveRedraw(true, getColorExec(arg, 'SetTextColor')); - }); + // v1 lies outside the near plane, v2 inside + alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); - this.addAlignMenu('align', painter.textatt.align, arg => { - changeObjectMember(painter, 'fTextAlign', arg); - painter.textatt.change(undefined, undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextAlign(${arg})`); - }); + } else if ( bc2near < 0 ) { - if (painter.textatt.can_rotate) { - this.addSizeMenu('angle', -180, 180, 45, painter.textatt.angle, arg => { - changeObjectMember(painter, 'fTextAngle', arg); - painter.textatt.change(undefined, undefined, undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextAngle(${arg})`); - }); - } + // v2 lies outside the near plane, v1 inside + alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); - this.add('endsub:'); - } - } + } - /** @summary Fill context menu for axis - * @private */ - addTAxisMenu(EAxisBits, painter, faxis, kind) { - const is_gaxis = faxis._typename === clTGaxis; + if ( bc1far < 0 ) { - this.add('Divisions', () => this.input('Set Ndivisions', faxis.fNdivisions, 'int', 0).then(val => { - faxis.fNdivisions = val; painter.interactiveRedraw('pad', `exec:SetNdivisions(${val})`, kind); - })); + // v1 lies outside the far plane, v2 inside + alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); - this.add('sub:Labels'); - this.addchk(faxis.TestBit(EAxisBits.kCenterLabels), 'Center', - arg => { faxis.InvertBit(EAxisBits.kCenterLabels); painter.interactiveRedraw('pad', `exec:CenterLabels(${arg})`, kind); }); - this.addchk(faxis.TestBit(EAxisBits.kLabelsVert), 'Rotate', - arg => { faxis.InvertBit(EAxisBits.kLabelsVert); painter.interactiveRedraw('pad', `exec:SetBit(TAxis::kLabelsVert,${arg})`, kind); }); - this.addColorMenu('Color', faxis.fLabelColor, - arg => { faxis.fLabelColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLabelColor'), kind); }); - this.addSizeMenu('Offset', -0.02, 0.1, 0.01, faxis.fLabelOffset, - arg => { faxis.fLabelOffset = arg; painter.interactiveRedraw('pad', `exec:SetLabelOffset(${arg})`, kind); }); - let a = faxis.fLabelSize >= 1; - this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fLabelSize, - arg => { faxis.fLabelSize = arg; painter.interactiveRedraw('pad', `exec:SetLabelSize(${arg})`, kind); }); - this.add('endsub:'); - this.add('sub:Title'); - this.add('SetTitle', () => { - this.input('Enter axis title', faxis.fTitle).then(t => { - faxis.fTitle = t; - painter.interactiveRedraw('pad', `exec:SetTitle("${t}")`, kind); - }); - }); - this.addchk(faxis.TestBit(EAxisBits.kCenterTitle), 'Center', - arg => { faxis.InvertBit(EAxisBits.kCenterTitle); painter.interactiveRedraw('pad', `exec:CenterTitle(${arg})`, kind); }); - this.addchk(faxis.TestBit(EAxisBits.kOppositeTitle), 'Opposite', - () => { faxis.InvertBit(EAxisBits.kOppositeTitle); painter.redrawPad(); }); - this.addchk(faxis.TestBit(EAxisBits.kRotateTitle), 'Rotate', - arg => { faxis.InvertBit(EAxisBits.kRotateTitle); painter.interactiveRedraw('pad', is_gaxis ? `exec:SetBit(TAxis::kRotateTitle, ${arg})` : `exec:RotateTitle(${arg})`, kind); }); - if (is_gaxis) { - this.addColorMenu('Color', faxis.fTextColor, - arg => { faxis.fTextColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); }); - } else { - this.addColorMenu('Color', faxis.fTitleColor, - arg => { faxis.fTitleColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); }); - } - this.addSizeMenu('Offset', 0, 3, 0.2, faxis.fTitleOffset, - arg => { faxis.fTitleOffset = arg; painter.interactiveRedraw('pad', `exec:SetTitleOffset(${arg})`, kind); }); - a = faxis.fTitleSize >= 1; - this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fTitleSize, - arg => { faxis.fTitleSize = arg; painter.interactiveRedraw('pad', `exec:SetTitleSize(${arg})`, kind); }); - this.add('endsub:'); - this.add('sub:Ticks'); - if (is_gaxis) { - this.addColorMenu('Color', faxis.fLineColor, - arg => { faxis.fLineColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLineColor'), kind); }); - this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickSize, - arg => { faxis.fTickSize = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); - } else { - this.addColorMenu('Color', faxis.fAxisColor, - arg => { faxis.fAxisColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetAxisColor'), kind); }); - this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickLength, - arg => { faxis.fTickLength = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); - } - this.add('endsub:'); + } else if ( bc2far < 0 ) { - if (is_gaxis) { - this.add('Options', () => this.input('Enter TGaxis options like +L or -G', faxis.fChopt, 'string').then(arg => { - faxis.fChopt = arg; painter.interactiveRedraw('pad', `exec:SetOption("${arg}")`, kind); - })); - } - } + // v2 lies outside the far plane, v2 inside + alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); - /** @summary Fill menu to edit settings properties - * @private */ - addSettingsMenu(with_hierarchy, alone, handle_func) { - if (alone) - this.add('header:Settings'); - else - this.add('sub:Settings'); + } - this.add('sub:Files'); + if ( alpha2 < alpha1 ) { - if (with_hierarchy) { - this.addchk(settings.OnlyLastCycle, 'Last cycle', flag => { - settings.OnlyLastCycle = flag; - if (handle_func) handle_func('refresh'); - }); + // The line segment spans two boundaries, but is outside both of them. + // (This can't happen when we're only clipping against just near/far but good + // to leave the check here for future usage if other clip planes are added.) + return false; - this.addchk(!settings.SkipStreamerInfos, 'Streamer infos', flag => { - settings.SkipStreamerInfos = !flag; - if (handle_func) handle_func('refresh'); - }); - } + } else { - this.addchk(settings.UseStamp, 'Use stamp arg', flag => { settings.UseStamp = flag; }); - this.addSizeMenu('Max ranges', 1, 1000, [1, 10, 20, 50, 200, 1000], settings.MaxRanges, value => { settings.MaxRanges = value; }, 'Maximal number of ranges in single http request'); + // Update the s1 and s2 vertices to match the clipped line segment. + s1.lerp( s2, alpha1 ); + s2.lerp( s1, 1 - alpha2 ); - this.addchk(settings.HandleWrongHttpResponse, 'Handle wrong http response', flag => { settings.HandleWrongHttpResponse = flag; }); - this.addchk(settings.WithCredentials, 'With credentials', flag => { settings.WithCredentials = flag; }, 'Submit http request with user credentials'); + return true; - this.add('endsub:'); + } - this.add('sub:Toolbar'); - this.addchk(settings.ToolBar === false, 'Off', flag => { settings.ToolBar = !flag; }); - this.addchk(settings.ToolBar === true, 'On', flag => { settings.ToolBar = flag; }); - this.addchk(settings.ToolBar === 'popup', 'Popup', flag => { settings.ToolBar = flag ? 'popup' : false; }); - this.add('separator'); - this.addchk(settings.ToolBarSide === 'left', 'Left side', flag => { settings.ToolBarSide = flag ? 'left' : 'right'; }); - this.addchk(settings.ToolBarVert, 'Vertical', flag => { settings.ToolBarVert = flag; }); - this.add('endsub:'); + } - this.add('sub:Interactive'); - this.addchk(settings.Tooltip, 'Tooltip', flag => { settings.Tooltip = flag; }); - this.addchk(settings.ContextMenu, 'Context menus', flag => { settings.ContextMenu = flag; }); - this.add('sub:Zooming'); - this.addchk(settings.Zooming, 'Global', flag => { settings.Zooming = flag; }); - this.addchk(settings.ZoomMouse, 'Mouse', flag => { settings.ZoomMouse = flag; }); - this.addchk(settings.ZoomWheel, 'Wheel', flag => { settings.ZoomWheel = flag; }); - this.addchk(settings.ZoomTouch, 'Touch', flag => { settings.ZoomTouch = flag; }); - this.add('endsub:'); - this.addchk(settings.HandleKeys, 'Keypress handling', flag => { settings.HandleKeys = flag; }); - this.addchk(settings.MoveResize, 'Move and resize', flag => { settings.MoveResize = flag; }); - this.addchk(settings.DragAndDrop, 'Drag and drop', flag => { settings.DragAndDrop = flag; }); - this.addchk(settings.DragGraphs, 'Drag graph points', flag => { settings.DragGraphs = flag; }); - this.addSelectMenu('Progress box', ['off', 'on', 'modal'], isStr(settings.ProgressBox) ? settings.ProgressBox : (settings.ProgressBox ? 'on' : 'off'), value => { - settings.ProgressBox = (value === 'off') ? false : (value === ' on' ? true : value); - }); - this.add('endsub:'); + } - this.add('sub:Drawing'); - this.addSelectMenu('Optimize', ['None', 'Smart', 'Always'], settings.OptimizeDraw, value => { settings.OptimizeDraw = value; }); - this.addPaletteMenu(settings.Palette, pal => { settings.Palette = pal; }); - this.addchk(settings.AutoStat, 'Auto stat box', flag => { settings.AutoStat = flag; }); - this.addSelectMenu('Latex', ['Off', 'Symbols', 'Normal', 'MathJax', 'Force MathJax'], settings.Latex, value => { settings.Latex = value; }); - this.addSelectMenu('3D rendering', ['Default', 'WebGL', 'Image'], settings.Render3D, value => { settings.Render3D = value; }); - this.addSelectMenu('WebGL embeding', ['Default', 'Overlay', 'Embed'], settings.Embed3D, value => { settings.Embed3D = value; }); + } - this.add('endsub:'); +} - this.add('sub:Geometry'); - this.add('Grad per segment: ' + settings.GeoGradPerSegm, () => this.input('Grad per segment in geometry', settings.GeoGradPerSegm, 'int', 1, 60).then(val => { settings.GeoGradPerSegm = val; })); - this.addchk(settings.GeoCompressComp, 'Compress composites', flag => { settings.GeoCompressComp = flag; }); - this.add('endsub:'); +/** + * This renderer an be used to render geometric data using SVG. The produced vector + * graphics are particular useful in the following use cases: + * + * - Animated logos or icons. + * - Interactive 2D/3D diagrams or graphs. + * - Interactive maps. + * - Complex or animated user interfaces. + * + * `SVGRenderer` has various advantages. It produces crystal-clear and sharp output which + * is independent of the actual viewport resolution.SVG elements can be styled via CSS. + * And they have good accessibility since it's possible to add metadata like title or description + * (useful for search engines or screen readers). + * + * There are, however, some important limitations: + * - No advanced shading. + * - No texture support. + * - No shadow support. + * + * @three_import import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js'; + */ +class SVGRenderer { - if (with_hierarchy) { - this.add('sub:Browser'); - this.add('Hierarchy limit: ' + settings.HierarchyLimit, () => this.input('Max number of items in hierarchy', settings.HierarchyLimit, 'int', 10, 100000).then(val => { - settings.HierarchyLimit = val; - if (handle_func) handle_func('refresh'); - })); - this.add('Browser width: ' + settings.BrowserWidth, () => this.input('Browser width in px', settings.BrowserWidth, 'int', 50, 2000).then(val => { - settings.BrowserWidth = val; - if (handle_func) handle_func('width'); - })); - this.add('endsub:'); - } + /** + * Constructs a new SVG renderer. + */ + constructor() { - this.add('Dark mode: ' + (settings.DarkMode ? 'On' : 'Off'), () => { - settings.DarkMode = !settings.DarkMode; - if (handle_func) handle_func('dark'); - }); + let _renderData, _elements, _lights, + _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, - const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, - addStyleIntField = (name, field, arr) => { - this.add('sub:' + name); - const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; - for (let v = 0; v < arr.length; ++v) - this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); - this.add('endsub:'); - }; + _v1, _v2, _v3, - this.add('sub:gStyle'); + _svgNode, + _pathCount = 0, - this.add('sub:Canvas'); - this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); - addStyleIntField('Draw date', 'fOptDate', ['Off', 'Current time', 'File create time', 'File modify time']); - this.add(`Time zone: ${settings.TimeZone}`, () => this.input('Input time zone like UTC. empty string - local timezone', settings.TimeZone, 'string').then(val => { settings.TimeZone = val; })); - addStyleIntField('Draw file', 'fOptFile', ['Off', 'File name', 'Full file URL', 'Item name']); - this.addSizeMenu('Date X', 0.01, 0.1, 0.01, gStyle.fDateX, x => { gStyle.fDateX = x; }, 'configure gStyle.fDateX for date/item name drawings'); - this.addSizeMenu('Date Y', 0.01, 0.1, 0.01, gStyle.fDateY, y => { gStyle.fDateY = y; }, 'configure gStyle.fDateY for date/item name drawings'); - this.add('endsub:'); + _precision = null, + _quality = 1, - this.add('sub:Pad'); - this.addColorMenu('Color', gStyle.fPadColor, col => { gStyle.fPadColor = col; }); - this.add('sub:Grid'); - this.addchk(gStyle.fPadGridX, 'X', flag => { gStyle.fPadGridX = flag; }); - this.addchk(gStyle.fPadGridY, 'Y', flag => { gStyle.fPadGridY = flag; }); - this.addColorMenu('Color', gStyle.fGridColor, col => { gStyle.fGridColor = col; }); - this.addSizeMenu('Width', 1, 10, 1, gStyle.fGridWidth, w => { gStyle.fGridWidth = w; }); - this.addLineStyleMenu('Style', gStyle.fGridStyle, st => { gStyle.fGridStyle = st; }); - this.add('endsub:'); - addStyleIntField('Ticks X', 'fPadTickX', ['normal', 'ticks on both sides', 'labels on both sides']); - addStyleIntField('Ticks Y', 'fPadTickY', ['normal', 'ticks on both sides', 'labels on both sides']); - addStyleIntField('Log X', 'fOptLogx', ['off', 'on', 'log 2']); - addStyleIntField('Log Y', 'fOptLogy', ['off', 'on', 'log 2']); - addStyleIntField('Log Z', 'fOptLogz', ['off', 'on', 'log 2']); - this.add('endsub:'); + _currentPath, _currentStyle; - this.add('sub:Frame'); - this.addColorMenu('Fill color', gStyle.fFrameFillColor, col => { gStyle.fFrameFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fFrameFillStyle, gStyle.fFrameFillColor, null, id => { gStyle.fFrameFillStyle = id; }); - this.addColorMenu('Line color', gStyle.fFrameLineColor, col => { gStyle.fFrameLineColor = col; }); - this.addSizeMenu('Line width', 1, 10, 1, gStyle.fFrameLineWidth, w => { gStyle.fFrameLineWidth = w; }); - this.addLineStyleMenu('Line style', gStyle.fFrameLineStyle, st => { gStyle.fFrameLineStyle = st; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fFrameBorderSize, sz => { gStyle.fFrameBorderSize = sz; }); - // fFrameBorderMode: 0, - this.add('sub:Margins'); - this.addSizeMenu('Bottom', 0, 0.5, 0.05, gStyle.fPadBottomMargin, v => { gStyle.fPadBottomMargin = v; }); - this.addSizeMenu('Top', 0, 0.5, 0.05, gStyle.fPadTopMargin, v => { gStyle.fPadTopMargin = v; }); - this.addSizeMenu('Left', 0, 0.5, 0.05, gStyle.fPadLeftMargin, v => { gStyle.fPadLeftMargin = v; }); - this.addSizeMenu('Right', 0, 0.5, 0.05, gStyle.fPadRightMargin, v => { gStyle.fPadRightMargin = v; }); - this.add('endsub:'); - this.add('endsub:'); + const _this = this, + _clipBox = new Box2(), + _elemBox = new Box2(), - this.add('sub:Title'); - this.addColorMenu('Fill color', gStyle.fTitleColor, col => { gStyle.fTitleColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fTitleStyle, gStyle.fTitleColor, null, id => { gStyle.fTitleStyle = id; }); - this.addColorMenu('Text color', gStyle.fTitleTextColor, col => { gStyle.fTitleTextColor = col; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fTitleBorderSize, sz => { gStyle.fTitleBorderSize = sz; }); - this.addSizeMenu('Font size', 0.01, 0.1, 0.01, gStyle.fTitleFontSize, sz => { gStyle.fTitleFontSize = sz; }); - this.addFontMenu('Font', gStyle.fTitleFont, fnt => { gStyle.fTitleFont = fnt; }); - this.addSizeMenu('X: ' + gStyle.fTitleX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleX, v => { gStyle.fTitleX = v; }); - this.addSizeMenu('Y: ' + gStyle.fTitleY.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleY, v => { gStyle.fTitleY = v; }); - this.addSizeMenu('W: ' + gStyle.fTitleW.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleW, v => { gStyle.fTitleW = v; }); - this.addSizeMenu('H: ' + gStyle.fTitleH.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleH, v => { gStyle.fTitleH = v; }); - this.add('endsub:'); + _color = new Color(), + _diffuseColor = new Color(), + _ambientLight = new Color(), + _directionalLights = new Color(), + _pointLights = new Color(), + _clearColor = new Color(), - this.add('sub:Stat box'); - this.addColorMenu('Fill color', gStyle.fStatColor, col => { gStyle.fStatColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fStatStyle, gStyle.fStatColor, null, id => { gStyle.fStatStyle = id; }); - this.addColorMenu('Text color', gStyle.fStatTextColor, col => { gStyle.fStatTextColor = col; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fStatBorderSize, sz => { gStyle.fStatBorderSize = sz; }); - this.addSizeMenu('Font size', 0, 30, 5, gStyle.fStatFontSize, sz => { gStyle.fStatFontSize = sz; }); - this.addFontMenu('Font', gStyle.fStatFont, fnt => { gStyle.fStatFont = fnt; }); - this.add('Stat format', () => this.input('Stat format', gStyle.fStatFormat).then(fmt => { gStyle.fStatFormat = fmt; })); - this.addSizeMenu('X: ' + gStyle.fStatX.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatX, v => { gStyle.fStatX = v; }); - this.addSizeMenu('Y: ' + gStyle.fStatY.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatY, v => { gStyle.fStatY = v; }); - this.addSizeMenu('Width: ' + gStyle.fStatW.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatW, v => { gStyle.fStatW = v; }); - this.addSizeMenu('Height: ' + gStyle.fStatH.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatH, v => { gStyle.fStatH = v; }); - this.add('endsub:'); + _vector3 = new Vector3(), // Needed for PointLight + _centroid = new Vector3(), + _normal = new Vector3(), + _normalViewMatrix = new Matrix3(), - this.add('sub:Legend'); - this.addColorMenu('Fill color', gStyle.fLegendFillColor, col => { gStyle.fLegendFillColor = col; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fLegendBorderSize, sz => { gStyle.fLegendBorderSize = sz; }); - this.addFontMenu('Font', gStyle.fLegendFont, fnt => { gStyle.fLegendFont = fnt; }); - this.addSizeMenu('Text size', 0, 0.1, 0.01, gStyle.fLegendTextSize, v => { gStyle.fLegendTextSize = v; }, 'legend text size, when 0 - auto adjustment is used'); - this.add('endsub:'); + _viewMatrix = new Matrix4(), + _viewProjectionMatrix = new Matrix4(), - this.add('sub:Histogram'); - this.addchk(gStyle.fOptTitle === 1, 'Hist title', flag => { gStyle.fOptTitle = flag ? 1 : 0; }); - this.addchk(gStyle.fOrthoCamera, 'Orthographic camera', flag => { gStyle.fOrthoCamera = flag; }); - this.addchk(gStyle.fHistMinimumZero, 'Base0', flag => { gStyle.fHistMinimumZero = flag; }, 'when true, BAR and LEGO drawing using base = 0'); - this.add('Text format', () => this.input('Paint text format', gStyle.fPaintTextFormat).then(fmt => { gStyle.fPaintTextFormat = fmt; })); - this.add('Time offset', () => this.input('Time offset in seconds, default is 788918400 for 1/1/1995', gStyle.fTimeOffset, 'int').then(ofset => { gStyle.fTimeOffset = ofset; })); - this.addSizeMenu('ErrorX: ' + gStyle.fErrorX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fErrorX, v => { gStyle.fErrorX = v; }); - this.addSizeMenu('End error', 0, 12, 1, gStyle.fEndErrorSize, v => { gStyle.fEndErrorSize = v; }, 'size in pixels of end error for E1 draw options, gStyle.fEndErrorSize'); - this.addSizeMenu('Top margin', 0.0, 0.5, 0.05, gStyle.fHistTopMargin, v => { gStyle.fHistTopMargin = v; }, 'Margin between histogram top and frame top'); - this.addColorMenu('Fill color', gStyle.fHistFillColor, col => { gStyle.fHistFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fHistFillStyle, gStyle.fHistFillColor, null, id => { gStyle.fHistFillStyle = id; }); - this.addColorMenu('Line color', gStyle.fHistLineColor, col => { gStyle.fHistLineColor = col; }); - this.addSizeMenu('Line width', 1, 10, 1, gStyle.fHistLineWidth, w => { gStyle.fHistLineWidth = w; }); - this.addLineStyleMenu('Line style', gStyle.fHistLineStyle, st => { gStyle.fHistLineStyle = st; }); - this.add('endsub:'); + _svgPathPool = [], - this.add('separator'); - this.add('sub:Predefined'); - ['Modern', 'Plain', 'Bold'].forEach(name => this.addchk((gStyle.fName === name), name, name, selectgStyle)); - this.add('endsub:'); + _projector = new Projector(), + _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); - this.add('endsub:'); // gStyle + /** + * The DOM where the renderer appends its child-elements. + * + * @type {DOMElement} + */ + this.domElement = _svg; - this.add('separator'); + /** + * Whether to automatically perform a clear before a render call or not. + * + * @type {boolean} + * @default true + */ + this.autoClear = true; - this.add('Save settings', () => { - const promise = readSettings(true) ? Promise.resolve(true) : this.confirm('Save settings', 'Pressing OK one agreess that JSROOT will store settings in browser local storage'); - promise.then(res => { if (res) { saveSettings(); saveStyle(); } }); - }, 'Store settings and gStyle in browser local storage'); - this.add('Delete settings', () => { saveSettings(-1); saveStyle(-1); }, 'Delete settings and gStyle from browser local storage'); + /** + * Whether to sort 3D objects or not. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; - if (!alone) this.add('endsub:'); - } + /** + * Whether to sort elements or not. + * + * @type {boolean} + * @default true + */ + this.sortElements = true; - /** @summary Run modal dialog - * @return {Promise} with html element inside dialg - * @private */ - async runModal() { - throw Error('runModal() must be reimplemented'); - } + /** + * Number of fractional pixels to enlarge polygons in order to + * prevent anti-aliasing gaps. Range is `[0,1]`. + * + * @type {number} + * @default 0.5 + */ + this.overdraw = 0.5; - /** @summary Show modal info dialog - * @param {String} title - title - * @param {String} message - message - * @protected */ - info(title, message) { - return this.runModal(title, `

${message}

`, { height: 120, width: 400, resizable: true }); - } + /** + * The output color space. + * + * @type {(SRGBColorSpace|LinearSRGBColorSpace)} + * @default SRGBColorSpace + */ + this.outputColorSpace = SRGBColorSpace; - /** @summary Show confirm dialog - * @param {String} title - title - * @param {String} message - message - * @return {Promise} with true when 'Ok' pressed or false when 'Cancel' pressed - * @protected */ - async confirm(title, message) { - return this.runModal(title, message, { btns: true, height: 120, width: 400 }).then(elem => { return !!elem; }); - } + /** + * Provides information about the number of + * rendered vertices and faces. + * + * @type {Object} + */ + this.info = { - /** @summary Input value - * @return {Promise} with input value - * @param {string} title - input dialog title - * @param value - initial value - * @param {string} [kind] - use 'text' (default), 'number', 'float' or 'int' - * @protected */ - async input(title, value, kind, min, max) { - if (!kind) kind = 'text'; - const inp_type = (kind === 'int') ? 'number' : 'text'; - let ranges = ''; - if ((value === undefined) || (value === null)) value = ''; - if (kind === 'int') { - if (min !== undefined) ranges += ` min="${min}"`; - if (max !== undefined) ranges += ` max="${max}"`; - } + render: { - const main_content = - '
'+ - ``+ - '
'; + vertices: 0, + faces: 0 - return new Promise(resolveFunc => { - this.runModal(title, main_content, { btns: true, height: 150, width: 400 }).then(element => { - if (!element) return; - let val = element.querySelector('.jsroot_dlginp').value; - if (kind === 'float') { - val = Number.parseFloat(val); - if (Number.isFinite(val)) - resolveFunc(val); - } else if (kind === 'int') { - val = parseInt(val); - if (Number.isInteger(val)) - resolveFunc(val); - } else - resolveFunc(val); - }); - }); - } + } - /** @summary Let input arguments from the method - * @return {Promise} with method argument */ - async showMethodArgsDialog(method) { - const dlg_id = this.menuname + '_dialog'; - let main_content = '
'; + }; - for (let n = 0; n < method.fArgs.length; ++n) { - const arg = method.fArgs[n]; - arg.fValue = arg.fDefault; - if (arg.fValue === '""') arg.fValue = ''; - main_content += ` - `; - } + /** + * Sets the render quality. Setting to `high` means This value indicates that the browser + * tries to improve the SVG quality over rendering speed and geometric precision. + * + * @param {('low'|'high')} quality - The quality. + */ + this.setQuality = function ( quality ) { - main_content += '
'; + switch ( quality ) { - return new Promise(resolveFunc => { - this.runModal(method.fClassName + '::' + method.fName, main_content, { btns: true, height: 100 + method.fArgs.length*60, width: 400, resizable: true }).then(element => { - if (!element) return; - let args = ''; + case 'high': _quality = 1; break; + case 'low': _quality = 0; break; - for (let k = 0; k < method.fArgs.length; ++k) { - const arg = method.fArgs[k]; - let value = element.querySelector(`#${dlg_id}_inp${k}`).value; - if (value === '') value = arg.fDefault; - if ((arg.fTitle === 'Option_t*') || (arg.fTitle === 'const char*')) { - // check quotes, - // TODO: need to make more precise checking of escape characters - if (!value) value = '""'; - if (value[0] !== '"') value = '"' + value; - if (value[value.length-1] !== '"') value += '"'; - } + } - args += (k > 0 ? ',' : '') + value; - } + }; - resolveFunc(args); - }); - }); - } + /** + * Sets the clear color. + * + * @param {(number|Color|string)} color - The clear color to set. + */ + this.setClearColor = function ( color ) { - /** @summary Let input arguments from the Command - * @return {Promise} with command argument */ - async showCommandArgsDialog(cmdname, args) { - const dlg_id = this.menuname + '_dialog'; - let main_content = '
'; + _clearColor.set( color ); - for (let n = 0; n < args.length; ++n) { - main_content += ``+ - ``; - } + }; - main_content += '
'; + this.setPixelRatio = function () {}; - return new Promise(resolveFunc => { - this.runModal('Arguments for command ' + cmdname, main_content, { btns: true, height: 110 + args.length*60, width: 400, resizable: true }).then(element => { - if (!element) - return resolveFunc(null); + /** + * Resizes the renderer to the given width and height. + * + * @param {number} width - The width of the renderer. + * @param {number} height - The height of the renderer. + */ + this.setSize = function ( width, height ) { - const resargs = []; - for (let k = 0; k < args.length; ++k) - resargs.push(element.querySelector(`#${dlg_id}_inp${k}`).value); - resolveFunc(resargs); - }); - }); - } + _svgWidth = width; _svgHeight = height; + _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2; -} // class JSRootMenu + _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight ); + _svg.setAttribute( 'width', _svgWidth ); + _svg.setAttribute( 'height', _svgHeight ); -/** - * @summary Context menu class using plain HTML/JavaScript - * - * @desc Use {@link createMenu} to create instance of the menu - * based on {@link https://github.com/L1quidH2O/ContextMenu.js} - * @private - */ + _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf ); + _clipBox.max.set( _svgWidthHalf, _svgHeightHalf ); -class StandaloneMenu extends JSRootMenu { + }; - constructor(painter, menuname, show_event) { - super(painter, menuname, show_event); + /** + * Returns an object containing the width and height of the renderer. + * + * @return {{width:number,height:number}} The size of the renderer. + */ + this.getSize = function () { - this.code = []; - this._use_plain_text = true; - this.stack = [this.code]; - } + return { + width: _svgWidth, + height: _svgHeight + }; - native() { return true; } + }; - /** @summary Load required modules, noop for that menu class */ - async load() { return this; } + /** + * Sets the precision of the data used to create a paths. + * + * @param {number} precision - The precision to set. + */ + this.setPrecision = function ( precision ) { - /** @summary Add menu item - * @param {string} name - item name - * @param {function} func - func called when item is selected */ - add(name, arg, func, title) { - let curr = this.stack[this.stack.length-1]; + _precision = precision; - if (name === 'separator') - return curr.push({ divider: true }); + }; - if (name.indexOf('header:') === 0) - return curr.push({ text: name.slice(7), header: true }); + function removeChildNodes() { - if (name === 'endsub:') { - this.stack.pop(); - curr = this.stack[this.stack.length-1]; - if (curr[curr.length-1].sub.length === 0) - curr[curr.length-1].sub = undefined; - return; - } + _pathCount = 0; - if (name === 'endcolumn:') - return this.stack.pop(); + while ( _svg.childNodes.length > 0 ) { + _svg.removeChild( _svg.childNodes[ 0 ] ); - if (isFunc(arg)) { title = func; func = arg; arg = name; } + } - const elem = {}; - curr.push(elem); + } - if (name === 'column:') { - elem.column = true; - elem.sub = []; - this.stack.push(elem.sub); - return; - } + function convert( c ) { - if (name.indexOf('sub:') === 0) { - name = name.slice(4); - elem.sub = []; - this.stack.push(elem.sub); - } + return _precision !== null ? c.toFixed( _precision ) : c; - if (name.indexOf('chk:') === 0) { - elem.checked = true; - name = name.slice(4); - } else if (name.indexOf('unk:') === 0) { - elem.checked = false; - name = name.slice(4); - } + } - elem.text = name; - elem.title = title; - elem.arg = arg; - elem.func = func; - } + /** + * Performs a manual clear with the defined clear color. + */ + this.clear = function () { - /** @summary Returns size of main menu */ - size() { return this.code.length; } + removeChildNodes(); + _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace ); - /** @summary Build HTML elements of the menu - * @private */ - _buildContextmenu(menu, left, top, loc) { - const doc = getDocument(), - outer = doc.createElement('div'), - container_style = - 'position: absolute; top: 0; user-select: none; z-index: 100000; background-color: rgb(250, 250, 250); margin: 0; padding: 0px; width: auto;'+ - 'min-width: 100px; box-shadow: 0px 0px 10px rgb(0, 0, 0, 0.2); border: 3px solid rgb(215, 215, 215); font-family: Arial, helvetica, sans-serif, serif;'+ - 'font-size: 13px; color: rgb(0, 0, 0, 0.8); line-height: 15px;'; + }; - // if loc !== doc.body then its a submenu, so it needs to have position: relative; - if (loc === doc.body) { - // delete all elements with className jsroot_ctxt_container - const deleteElems = doc.getElementsByClassName('jsroot_ctxt_container'); - while (deleteElems.length > 0) - deleteElems[0].parentNode.removeChild(deleteElems[0]); + /** + * Renders the given scene using the given camera. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + */ + this.render = function ( scene, camera ) { - outer.className = 'jsroot_ctxt_container'; - outer.style = container_style; - outer.style.position = 'fixed'; - outer.style.left = left + 'px'; - outer.style.top = top + 'px'; - } else if ((left < 0) && (top === left)) { - // column - outer.className = 'jsroot_ctxt_column'; - outer.style.float = 'left'; - outer.style.width = (100/-left).toFixed(1) + '%'; - } else { - outer.className = 'jsroot_ctxt_container'; - outer.style = container_style; - outer.style.left = -loc.offsetLeft + loc.offsetWidth + 'px'; - } + if ( camera instanceof Camera === false ) { - let need_check_area = false, ncols = 0; - menu.forEach(d => { - if (d.checked) need_check_area = true; - if (d.column) ncols++; - }); + console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' ); + return; - menu.forEach(d => { - if (ncols > 0) { - outer.style.display = 'flex'; - if (d.column) this._buildContextmenu(d.sub, -ncols, -ncols, outer); - return; - } + } - if (d.divider) { - const hr = doc.createElement('hr'); - hr.style = 'width: 85%; margin: 3px auto; border: 1px solid rgb(0, 0, 0, 0.15)'; - outer.appendChild(hr); - return; - } + const background = scene.background; - const item = doc.createElement('div'); - item.style.position = 'relative'; - outer.appendChild(item); + if ( background && background.isColor ) { - if (d.header) { - item.style = 'background-color: lightblue; padding: 3px 7px; font-weight: bold; border-bottom: 1px;'; - item.innerHTML = d.text; - return; - } + removeChildNodes(); + _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace ); - const hovArea = doc.createElement('div'); - hovArea.style.width = '100%'; - hovArea.style.height = '100%'; - hovArea.style.display = 'flex'; - hovArea.style.justifyContent = 'space-between'; - hovArea.style.cursor = 'pointer'; - if (d.title) hovArea.setAttribute('title', d.title); + } else if ( this.autoClear === true ) { - item.appendChild(hovArea); - if (!d.text) d.text = 'item'; + this.clear(); - const text = doc.createElement('div'); - text.style = 'margin: 0; padding: 3px 7px; pointer-events: none; white-space: nowrap'; + } - if (d.text.indexOf('= 0) { - if (need_check_area) { - text.style.display = 'flex'; + _this.info.render.vertices = 0; + _this.info.render.faces = 0; - const chk = doc.createElement('span'); - chk.innerHTML = d.checked ? '\u2713' : ''; - chk.style.display = 'inline-block'; - chk.style.width = '1em'; - text.appendChild(chk); + _viewMatrix.copy( camera.matrixWorldInverse ); + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); - const sub = doc.createElement('div'); - sub.innerHTML = d.text; - text.appendChild(sub); - } else - text.innerHTML = d.text; - } else { - if (need_check_area) { - const chk = doc.createElement('span'); - chk.innerHTML = d.checked ? '\u2713' : ''; - chk.style.display = 'inline-block'; - chk.style.width = '1em'; - text.appendChild(chk); - } + _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); + _elements = _renderData.elements; + _lights = _renderData.lights; - const sub = doc.createElement('span'); - if (d.text.indexOf('') === 0) - sub.textContent = d.text.slice(6, d.text.length-7); - else - sub.textContent = d.text; - text.appendChild(sub); - } + _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); - hovArea.appendChild(text); + calculateLights( _lights ); - function changeFocus(item, on) { - if (on) { - item.classList.add('jsroot_ctxt_focus'); - item.style['background-color'] = 'rgb(220, 220, 220)'; - } else if (item.classList.contains('jsroot_ctxt_focus')) { - item.style['background-color'] = null; - item.classList.remove('jsroot_ctxt_focus'); - item.querySelector('.jsroot_ctxt_container')?.remove(); - } - } + // reset accumulated path - if (d.extraText || d.sub) { - const extraText = doc.createElement('span'); - extraText.className = 'jsroot_ctxt_extraText'; - extraText.style = 'margin: 0; padding: 3px 7px; color: rgb(0, 0, 0, 0.6);'; - extraText.textContent = d.sub ? '\u25B6' : d.extraText; - hovArea.appendChild(extraText); + _currentPath = ''; + _currentStyle = ''; - if (d.sub && browser.touches) { - extraText.addEventListener('click', evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); - const was_active = item.parentNode.querySelector('.jsroot_ctxt_focus'); + for ( let e = 0, el = _elements.length; e < el; e ++ ) { - if (was_active) - changeFocus(was_active, false); + const element = _elements[ e ]; + const material = element.material; - if (item !== was_active) { - changeFocus(item, true); - this._buildContextmenu(d.sub, 0, 0, item); - } - }); - } - } + if ( material === undefined || material.opacity === 0 ) continue; - if (!browser.touches) { - hovArea.addEventListener('mouseenter', () => { - if (this.prevHovArea) - this.prevHovArea.style['background-color'] = null; - hovArea.style['background-color'] = 'rgb(235, 235, 235)'; - this.prevHovArea = hovArea; + _elemBox.makeEmpty(); - outer.childNodes.forEach(chld => changeFocus(chld, false)); + if ( element instanceof RenderableSprite ) { - if (d.sub) { - changeFocus(item, true); - this._buildContextmenu(d.sub, 0, 0, item); - } - }); - } + _v1 = element; + _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf; - if (d.func) { - item.addEventListener('click', evnt => { - const func = this.painter ? d.func.bind(this.painter) : d.func; - func(d.arg); - evnt.stopPropagation(); - this.remove(); - }); - } - }); + renderSprite( _v1, element, material ); - loc.appendChild(outer); + } else if ( element instanceof RenderableLine ) { - const docWidth = doc.documentElement.clientWidth, docHeight = doc.documentElement.clientHeight; + _v1 = element.v1; _v2 = element.v2; - // Now determine where the contextmenu will be - if (loc === doc.body) { - if (left + outer.offsetWidth > docWidth) { - // Does sub-contextmenu overflow window width? - outer.style.left = (docWidth - outer.offsetWidth) + 'px'; - } - if (outer.offsetHeight > docHeight) { - // is the contextmenu height larger than the window height? - outer.style.top = 0; - outer.style.overflowY = 'scroll'; - outer.style.overflowX = 'hidden'; - outer.style.height = docHeight + 'px'; - } else if (top + outer.offsetHeight > docHeight) { - // Does contextmenu overflow window height? - outer.style.top = (docHeight - outer.offsetHeight) + 'px'; - } - } else if (outer.className !== 'jsroot_ctxt_column') { - // if its sub-contextmenu - const dimensionsLoc = loc.getBoundingClientRect(), dimensionsOuter = outer.getBoundingClientRect(); + _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; + _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; - // Does sub-contextmenu overflow window width? - if (dimensionsOuter.left + dimensionsOuter.width > docWidth) - outer.style.left = (-loc.offsetLeft - dimensionsOuter.width) + 'px'; + _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] ); + if ( _clipBox.intersectsBox( _elemBox ) === true ) { - if (dimensionsOuter.height > docHeight) { - // is the sub-contextmenu height larger than the window height? - outer.style.top = -dimensionsOuter.top + 'px'; - outer.style.overflowY = 'scroll'; - outer.style.overflowX = 'hidden'; - outer.style.height = docHeight + 'px'; - } else if (dimensionsOuter.height < docHeight && dimensionsOuter.height > docHeight / 2) { - // is the sub-contextmenu height smaller than the window height AND larger than half of window height? - if (dimensionsOuter.top - docHeight / 2 >= 0) { // If sub-contextmenu is closer to bottom of the screen - outer.style.top = (-dimensionsOuter.top - dimensionsOuter.height + docHeight) + 'px'; - } else { // If sub-contextmenu is closer to top of the screen - outer.style.top = (-dimensionsOuter.top) + 'px'; - } - } else if (dimensionsOuter.top + dimensionsOuter.height > docHeight) { - // Does sub-contextmenu overflow window height? - outer.style.top = (-dimensionsOuter.height + dimensionsLoc.height) + 'px'; - } - } - return outer; - } + renderLine( _v1, _v2, material ); - /** @summary Show standalone menu */ - async show(event) { - this.remove(); + } - if (!event && this.show_evnt) event = this.show_evnt; + } else if ( element instanceof RenderableFace ) { - const doc = getDocument(), - woffset = typeof window === 'undefined' ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY }; + _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; - doc.body.addEventListener('click', this.remove_handler); + if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue; + if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue; + if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue; - const oldmenu = doc.getElementById(this.menuname); - if (oldmenu) oldmenu.remove(); + _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; + _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; + _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf; - this.element = this._buildContextmenu(this.code, (event?.clientX || 0) + woffset.x, (event?.clientY || 0) + woffset.y, doc.body); + if ( this.overdraw > 0 ) { - this.element.setAttribute('id', this.menuname); + expand( _v1.positionScreen, _v2.positionScreen, this.overdraw ); + expand( _v2.positionScreen, _v3.positionScreen, this.overdraw ); + expand( _v3.positionScreen, _v1.positionScreen, this.overdraw ); - return this; - } + } - /** @summary Run modal elements with standalone code */ - createModal(title, main_content, args) { - if (!args) args = {}; + _elemBox.setFromPoints( [ + _v1.positionScreen, + _v2.positionScreen, + _v3.positionScreen + ] ); - if (!args.Ok) args.Ok = 'Ok'; + if ( _clipBox.intersectsBox( _elemBox ) === true ) { - const modal = { args }, dlg_id = (this?.menuname ?? 'root_modal') + '_dialog'; - select(`#${dlg_id}`).remove(); - select(`#${dlg_id}_block`).remove(); + renderFace3( _v1, _v2, _v3, element, material ); - const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)); - modal.block = select('body').append('div') - .attr('id', `${dlg_id}_block`) - .attr('class', 'jsroot_dialog_block') - .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); - modal.element = select('body') - .append('div') - .attr('id', dlg_id) - .attr('class', 'jsroot_dialog') - .style('position', 'absolute') - .style('width', `${w}px`) - .style('left', '50%') - .style('top', '50%') - .style('z-index', 100001) - .attr('tabindex', '0') - .html( - '
'+ - `
${title}
`+ - `
${main_content}
`+ - '
'+ - ``+ - (args.btns ? '' : '') + - '
'); + } - modal.done = function(res) { - if (this._done) return; - this._done = true; - if (isFunc(this.call_back)) - this.call_back(res); - this.element.remove(); - this.block.remove(); - }; + } - modal.setContent = function(content, btn_text) { - if (!this._done) { - this.element.select('.jsroot_dialog_content').html(content); - if (btn_text) { - this.args.Ok = btn_text; - this.element.select('.jsroot_dialog_button').text(btn_text); - } - } - }; + } - modal.element.on('keyup', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - modal.done(evnt.code === 'Enter' ? modal.element.node() : null); - } - }); - modal.element.on('keydown', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - } - }); - modal.element.selectAll('.jsroot_dialog_button').on('click', evnt => { - modal.done(args.btns && (select(evnt.target).text() === args.Ok) ? modal.element.node() : null); - }); + flushPath(); // just to flush last svg:path - let f = modal.element.select('.jsroot_dialog_content').select('input'); - if (f.empty()) f = modal.element.select('.jsroot_dialog_footer').select('button'); - if (!f.empty()) f.node().focus(); - return modal; - } + scene.traverseVisible( function ( object ) { - /** @summary Run modal elements with standalone code */ - async runModal(title, main_content, args) { - const modal = this.createModal(title, main_content, args); - return new Promise(resolveFunc => { - modal.call_back = resolveFunc; - }); - } + if ( object.isSVGObject ) { + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyMatrix4( _viewProjectionMatrix ); -} // class StandaloneMenu + if ( _vector3.z < -1 || _vector3.z > 1 ) return; -/** @summary Create JSROOT menu - * @desc See {@link JSRootMenu} class for detailed list of methods - * @param {object} [evnt] - event object like mouse context menu event - * @param {object} [handler] - object with handling function, in this case one not need to bind function - * @param {string} [menuname] - optional menu name - * @example - * import { createMenu } from 'https://root.cern/js/latest/modules/gui/menu.mjs'; - * let menu = await createMenu()); - * menu.add('First', () => console.log('Click first')); - * let flag = true; - * menu.addchk(flag, 'Checked', arg => console.log(`Now flag is ${arg}`)); - * menu.show(); */ -function createMenu(evnt, handler, menuname) { - const menu = new StandaloneMenu(handler, menuname || 'root_ctx_menu', evnt); - return menu.load(); -} + const x = _vector3.x * _svgWidthHalf; + const y = - _vector3.y * _svgHeightHalf; -/** @summary Close previousely created and shown JSROOT menu - * @param {string} [menuname] - optional menu name */ -function closeMenu(menuname) { - const element = getDocument().getElementById(menuname || 'root_ctx_menu'); - element?.remove(); - return !!element; -} + const node = object.node; + node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' ); -/** @summary Fill and show context menu for painter object - * @private */ -function showPainterMenu(evnt, painter, kind) { - if (isFunc(evnt.stopPropagation)) { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - } + _svg.appendChild( node ); - createMenu(evnt, painter).then(menu => { - painter.fillContextMenu(menu); - if ((kind === kToFront) && isFunc(painter.bringToFront)) { - menu.add('Bring to front', () => painter.bringToFront(true)); - kind = undefined; - } - return painter.fillObjectExecMenu(menu, kind); - }).then(menu => menu.show()); -} + } -/** @summary Internal method to implement modal progress - * @private */ -internals._modalProgress = function(msg, click_handle) { - if (!msg || !isStr(msg)) { - internals.modal?.done(); - delete internals.modal; - return; - } + } ); - if (!internals.modal) - internals.modal = StandaloneMenu.prototype.createModal('Progress', msg); + }; - internals.modal.setContent(msg, click_handle ? 'Abort' : 'Ok'); + function calculateLights( lights ) { - internals.modal.call_back = click_handle; -}; + _ambientLight.setRGB( 0, 0, 0 ); + _directionalLights.setRGB( 0, 0, 0 ); + _pointLights.setRGB( 0, 0, 0 ); -/** @summary Assign handler for context menu for painter draw element - * @private */ -function assignContextMenu(painter, kind) { - if (!painter?.isBatchMode() && painter?.draw_g) - painter.draw_g.on('contextmenu', settings.ContextMenu ? evnt => showPainterMenu(evnt, painter, kind) : null); -} + for ( let l = 0, ll = lights.length; l < ll; l ++ ) { -/** @summary Return time offset value for given TAxis object - * @private */ -function getTimeOffset(axis) { - const dflt_time_offset = 788918400000; + const light = lights[ l ]; + const lightColor = light.color; - if (!axis) return dflt_time_offset; - const idF = axis.fTimeFormat.indexOf('%F'); - if (idF < 0) return gStyle.fTimeOffset * 1000; - let sof = axis.fTimeFormat.slice(idF + 2); - // default string in axis offset - if (sof.indexOf('1995-01-01 00:00:00s0') === 0) - return dflt_time_offset; - // another default string with unix time - if (sof.indexOf('1970-01-01 00:00:00s0') === 0) - return 0; - // special case, used from DABC painters - if ((sof === '0') || (sof === '')) return 0; + if ( light.isAmbientLight ) { - // decode time from ROOT string - const next = (separ, min, max) => { - const pos = sof.indexOf(separ); - if (pos < 0) return min; - const val = parseInt(sof.slice(0, pos)); - sof = sof.slice(pos + 1); - if (!Number.isInteger(val) || (val < min) || (val > max)) return min; - return val; - }, year = next('-', 1900, 2900), - month = next('-', 1, 12) - 1, - day = next(' ', 1, 31), - hour = next(':', 0, 23), - min = next(':', 0, 59), - sec = next('s', 0, 59), - msec = next(' ', 0, 999); + _ambientLight.r += lightColor.r; + _ambientLight.g += lightColor.g; + _ambientLight.b += lightColor.b; - let offset = Date.UTC(year, month, day, hour, min, sec, msec); + } else if ( light.isDirectionalLight ) { - // now also handle suffix like GMT or GMT -0600 - sof = sof.toUpperCase(); + _directionalLights.r += lightColor.r; + _directionalLights.g += lightColor.g; + _directionalLights.b += lightColor.b; - if (sof.indexOf('GMT') === 0) { - sof = sof.slice(4).trim(); - if (sof.length > 3) { - let p = 0, sign = 1000; - if (sof[0] === '-') { p = 1; sign = -1000; } - offset -= sign * (parseInt(sof.slice(p, p + 2)) * 3600 + parseInt(sof.slice(p + 2, p + 4)) * 60); - } - } + } else if ( light.isPointLight ) { - return offset; -} + _pointLights.r += lightColor.r; + _pointLights.g += lightColor.g; + _pointLights.b += lightColor.b; -/** @summary Return true when GMT option configured in time format - * @private */ -function getTimeGMT(axis) { - const fmt = axis?.fTimeFormat ?? ''; - return (fmt.indexOf('gmt') > 0) || (fmt.indexOf('GMT') > 0); -} + } -/** @summary Tries to choose time format for provided time interval - * @private */ -function chooseTimeFormat(awidth, ticks) { - if (awidth < 0.5) return ticks ? '%S.%L' : '%H:%M:%S.%L'; - if (awidth < 30) return ticks ? '%Mm%S' : '%H:%M:%S'; - awidth /= 60; if (awidth < 30) return ticks ? '%Hh%M' : '%d/%m %H:%M'; - awidth /= 60; if (awidth < 12) return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; - awidth /= 24; if (awidth < 15.218425) return ticks ? '%d/%m' : '%d/%m/%y'; - awidth /= 30.43685; if (awidth < 6) return '%d/%m/%y'; - awidth /= 12; if (awidth < 2) return ticks ? '%m/%y' : '%d/%m/%y'; - return '%Y'; -} + } -/** - * @summary Base axis painter methods - * - * @private - */ + } -const AxisPainterMethods = { + function calculateLight( lights, position, normal, color ) { - initAxisPainter() { - this.name = 'yaxis'; - this.kind = kAxisNormal; - this.func = null; - this.order = 0; // scaling order for axis labels + for ( let l = 0, ll = lights.length; l < ll; l ++ ) { - this.full_min = 0; - this.full_max = 1; - this.scale_min = 0; - this.scale_max = 1; - this.ticks = []; // list of major ticks - }, + const light = lights[ l ]; + const lightColor = light.color; - /** @summary Cleanup axis painter */ - cleanupAxisPainter() { - this.ticks = []; - delete this.format; - delete this.func; - delete this.tfunc1; - delete this.tfunc2; - delete this.gr; - }, + if ( light.isDirectionalLight ) { - /** @summary Assign often used members of frame painter */ - assignFrameMembers(fp, axis) { - fp[`gr${axis}`] = this.gr; // fp.grx - fp[`log${axis}`] = this.log; // fp.logx - fp[`scale_${axis}min`] = this.scale_min; // fp.scale_xmin - fp[`scale_${axis}max`] = this.scale_max; // fp.scale_xmax - }, + const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); - /** @summary Convert axis value into the Date object */ - convertDate(v) { - const dt = new Date(this.timeoffset + v*1000); - let res = dt; - if (!this.timegmt && settings.TimeZone) { - try { - const ms = dt.getMilliseconds(); - res = new Date(dt.toLocaleString('en-US', { timeZone: settings.TimeZone })); - res.setMilliseconds(ms); - } catch (err) { - res = dt; - } - } - return res; - }, + let amount = normal.dot( lightPosition ); - /** @summary Convert graphical point back into axis value */ - revertPoint(pnt) { - const value = this.func.invert(pnt); - return this.kind === kAxisTime ? (value - this.timeoffset) / 1000 : value; - }, + if ( amount <= 0 ) continue; - /** @summary Provide label for time axis */ - formatTime(dt, asticks) { - return asticks ? this.tfunc1(dt) : this.tfunc2(dt); - }, + amount *= light.intensity; - /** @summary Provide label for log axis */ - formatLog(d, asticks, fmt) { - const val = parseFloat(d), rnd = Math.round(val); - if (!asticks) - return ((rnd === val) && (Math.abs(rnd) < 1e9)) ? rnd.toString() : floatToString(val, fmt || gStyle.fStatFormat); - if (val <= 0) return null; - let vlog = Math.log10(val); - const base = this.logbase; - if (base !== 10) vlog = vlog / Math.log10(base); - if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog)) < 0.001)) { - if (!this.noexp && (asticks !== 2)) - return this.formatExp(base, Math.floor(vlog + 0.01), val); - if (Math.abs(base - Math.E) < 0.001) - return floatToString(val, fmt || gStyle.fStatFormat); - return (vlog < 0) ? val.toFixed(Math.round(-vlog + 0.5)) : val.toFixed(0); - } - return null; - }, + color.r += lightColor.r * amount; + color.g += lightColor.g * amount; + color.b += lightColor.b * amount; - /** @summary Provide label for normal axis */ - formatNormal(d, asticks, fmt) { - let val = parseFloat(d); - if (asticks && this.order) - val = val / Math.pow(10, this.order); + } else if ( light.isPointLight ) { - if (gStyle.fStripDecimals && (val === Math.round(val))) - return Math.abs(val) < 1e9 ? val.toFixed(0) : val.toExponential(4); + const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); - if (asticks) { - if (this.ndig > 10) - return val.toExponential(this.ndig - 11); - let res = val.toFixed(this.ndig); - const p = res.indexOf('.'); - if ((p > 0) && settings.StripAxisLabels) { - while ((res.length >= p) && ((res[res.length-1] === '0') || (res[res.length-1] === '.'))) - res = res.slice(0, res.length - 1); - } - return res; - } + let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); - return floatToString(val, fmt || gStyle.fStatFormat); - }, + if ( amount <= 0 ) continue; - /** @summary Provide label for exponential form */ - formatExp(base, order, value) { - let res = ''; - if (value) { - value = Math.round(value/Math.pow(base, order)); - if ((value !== 0) && (value !== 1)) res = value.toString() + (settings.Latex ? '#times' : 'x'); - } - if (Math.abs(base - Math.E) < 0.001) - res += 'e'; - else - res += base.toString(); - if (settings.StripAxisLabels) { - if (order === 0) - return '1'; - else if (order === 1) - return res; - } - if (settings.Latex > constants$1.Latex.Symbols) - return res + `^{${order}}`; - const superscript_symbols = { - 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', - 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' - }, str = order.toString(); - for (let n = 0; n < str.length; ++n) - res += superscript_symbols[str[n]]; - return res; - }, + amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); - /** @summary Convert 'raw' axis value into text */ - axisAsText(value, fmt) { - if (this.kind === kAxisTime) - value = this.convertDate(value); - if (this.format) - return this.format(value, false, fmt); - return value.toPrecision(4); - }, + if ( amount == 0 ) continue; - /** @summary Produce ticks for d3.scaleLog - * @desc Fixing following problem, described [here]{@link https://stackoverflow.com/questions/64649793} */ - poduceLogTicks(func, number) { - const linearArray = arr => { - let sum1 = 0, sum2 = 0; - for (let k = 1; k < arr.length; ++k) { - const diff = (arr[k] - arr[k-1]); - sum1 += diff; - sum2 += diff**2; - } - const mean = sum1/(arr.length-1), - dev = sum2/(arr.length-1) - mean**2; + amount *= light.intensity; - if (dev <= 0) return true; - if (Math.abs(mean) < 1e-100) return false; - return Math.sqrt(dev)/mean < 1e-6; - }; + color.r += lightColor.r * amount; + color.g += lightColor.g * amount; + color.b += lightColor.b * amount; - let arr = func.ticks(number); + } - while ((number > 4) && linearArray(arr)) { - number = Math.round(number*0.8); - arr = func.ticks(number); - } + } - // if still linear array, try to sort out 'bad' ticks - if ((number < 5) && linearArray(arr) && this.logbase && (this.logbase !== 10)) { - const arr2 = []; - arr.forEach(val => { - const pow = Math.log10(val) / Math.log10(this.logbase); - if (Math.abs(Math.round(pow) - pow) < 0.01) arr2.push(val); - }); - if (arr2.length > 0) arr = arr2; - } + } - return arr; - }, + function renderSprite( v1, element, material ) { - /** @summary Produce axis ticks */ - produceTicks(ndiv, ndiv2) { - if (!this.noticksopt) { - const total = ndiv * (ndiv2 || 1); + let scaleX = element.scale.x * _svgWidthHalf; + let scaleY = element.scale.y * _svgHeightHalf; - if (this.log) return this.poduceLogTicks(this.func, total); + if ( material.isPointsMaterial ) { - const dom = this.func.domain(), - check = ticks => { - if (ticks.length <= total) return true; - if (ticks.length > total + 1) return false; - return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range - }, res1 = this.func.ticks(total); - if (ndiv2 || check(res1)) return res1; + scaleX *= material.size; + scaleY *= material.size; - const res2 = this.func.ticks(Math.round(total * 0.7)); - return (res2.length > 2) && check(res2) ? res2 : res1; - } + } - const dom = this.func.domain(), ticks = []; - if (ndiv2) ndiv = (ndiv-1) * ndiv2; - for (let n = 0; n <= ndiv; ++n) - ticks.push((dom[0]*(ndiv-n) + dom[1]*n)/ndiv); - return ticks; - }, + const path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z'; + let style = ''; - /** @summary Method analyze mouse wheel event and returns item with suggested zooming range */ - analyzeWheelEvent(evnt, dmin, item, test_ignore) { - if (!item) item = {}; + if ( material.isSpriteMaterial || material.isPointsMaterial ) { - let delta = 0, delta_left = 1, delta_right = 1; + style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; - if ('dleft' in item) { delta_left = item.dleft; delta = 1; } - if ('dright' in item) { delta_right = item.dright; delta = 1; } + } - if (item.delta) - delta = item.delta; - else if (evnt) - delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); + addPath( style, path ); - if (!delta || (test_ignore && item.ignore)) return; + } - delta = (delta < 0) ? -0.2 : 0.2; - delta_left *= delta; - delta_right *= delta; + function renderLine( v1, v2, material ) { - const lmin = item.min = this.scale_min, - lmax = item.max = this.scale_max, - gmin = this.full_min, - gmax = this.full_max; + const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ); - if ((item.min === item.max) && (delta < 0)) { - item.min = gmin; - item.max = gmax; - } + if ( material.isLineBasicMaterial ) { - if (item.min >= item.max) return; + let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap; - if (item.reverse) dmin = 1 - dmin; + if ( material.isLineDashedMaterial ) { - if ((dmin > 0) && (dmin < 1)) { - if (this.log) { - let factor = (item.min > 0) ? Math.log10(item.max/item.min) : 2; - if (factor > 10) factor = 10; else if (factor < 0.01) factor = 0.01; - item.min = item.min / Math.pow(10, factor*delta_left*dmin); - item.max = item.max * Math.pow(10, factor*delta_right*(1-dmin)); - } else if ((delta_left === -delta_right) && !item.reverse) { - // shift left/right, try to keep range constant - let delta = (item.max - item.min) * delta_right * dmin; + style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize; - if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta) > 1)) delta = Math.round(delta); + } - if (item.min + delta < gmin) - delta = gmin - item.min; - else if (item.max + delta > gmax) - delta = gmax - item.max; + addPath( style, path ); - if (delta !== 0) { - item.min += delta; - item.max += delta; - } else { - delete item.min; - delete item.max; - } - } else { - let rx_left = (item.max - item.min), rx_right = rx_left; - if (delta_left > 0) rx_left = 1.001 * rx_left / (1-delta_left); - item.min += -delta_left*dmin*rx_left; - if (delta_right > 0) rx_right = 1.001 * rx_right / (1-delta_right); - item.max -= -delta_right*(1-dmin)*rx_right; - } - if (item.min >= item.max) - item.min = item.max = undefined; - else if (delta_left !== delta_right) { - // extra check case when moving left or right - if (((item.min < gmin) && (lmin === gmin)) || - ((item.max > gmax) && (lmax === gmax))) - item.min = item.max = undefined; - } else { - if (item.min < gmin) item.min = gmin; - if (item.max > gmax) item.max = gmax; - } - } else - item.min = item.max = undefined; + } + } - item.changed = ((item.min !== undefined) && (item.max !== undefined)); + function renderFace3( v1, v2, v3, element, material ) { - return item; - } + _this.info.render.vertices += 3; + _this.info.render.faces ++; -}; // AxisPainterMethods + const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z'; + let style = ''; + if ( material.isMeshBasicMaterial ) { -/** - * @summary Painter for TAxis object - * - * @private - */ + _color.copy( material.color ); -class TAxisPainter extends ObjectPainter { + if ( material.vertexColors ) { - /** @summary constructor - * @param {object|string} dom - identifier or dom element - * @param {object} axis - object to draw - * @param {boolean} embedded - if true, painter used in other objects painters */ - constructor(dom, axis, embedded) { - super(dom, axis); + _color.multiply( element.color ); - this.is_gaxis = axis?._typename === clTGaxis; + } - Object.assign(this, AxisPainterMethods); - this.initAxisPainter(); + } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) { - this.embedded = embedded; // indicate that painter embedded into the histo painter - this.invert_side = false; - this.lbls_both_sides = false; // draw labels on both sides - } + _diffuseColor.copy( material.color ); - /** @summary cleanup painter */ - cleanup() { - this.cleanupAxisPainter(); - super.cleanup(); - } + if ( material.vertexColors ) { - /** @summary Use in GED to identify kind of axis */ - getAxisType() { return clTAxis; } + _diffuseColor.multiply( element.color ); - /** @summary Configure axis painter - * @desc Axis can be drawn inside frame group with offset to 0 point for the frame - * Therefore one should distinguish when caclulated coordinates used for axis drawing itself or for calculation of frame coordinates - * @private */ - configureAxis(name, min, max, smin, smax, vertical, range, opts) { - this.name = name; - this.full_min = min; - this.full_max = max; - this.kind = kAxisNormal; - this.vertical = vertical; - this.log = opts.log || 0; - this.noexp_changed = opts.noexp_changed; - this.symlog = opts.symlog || false; - this.reverse = opts.reverse || false; - this.swap_side = opts.swap_side || false; - this.fixed_ticks = opts.fixed_ticks || null; - this.maxTickSize = opts.maxTickSize || 0; + } - const axis = this.getObject(); + _color.copy( _ambientLight ); - if (opts.time_scale || axis.fTimeDisplay) { - this.kind = kAxisTime; - this.timeoffset = getTimeOffset(axis); - this.timegmt = getTimeGMT(axis); - } else if (opts.axis_func) - this.kind = kAxisFunc; - else - this.kind = !axis.fLabels ? kAxisNormal : kAxisLabels; + _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 ); + calculateLight( _lights, _centroid, element.normalModel, _color ); - if (this.kind === kAxisTime) - this.func = time().domain([this.convertDate(smin), this.convertDate(smax)]); - else if (this.log) { - if ((this.log === 1) || (this.log === 10)) - this.logbase = 10; - else if (this.log === 3) - this.logbase = Math.E; - else - this.logbase = Math.round(this.log); + _color.multiply( _diffuseColor ).add( material.emissive ); - if (smax <= 0) smax = 1; + } else if ( material.isMeshNormalMaterial ) { - if ((smin <= 0) && axis && !opts.logcheckmin) { - for (let i = 0; i < axis.fNbins; ++i) { - smin = Math.max(smin, axis.GetBinLowEdge(i+1)); - if (smin > 0) break; - } - } + _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize(); - if ((smin <= 0) && opts.log_min_nz) - smin = this.log_min_nz = opts.log_min_nz; + _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); - if ((smin <= 0) || (smin >= smax)) - smin = smax * (opts.logminfactor || 1e-4); + } - if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, this.logbase, smin, smax); - else - this.func = log().base(this.logbase).domain([smin, smax]); - } else if (this.symlog) { - let v = Math.max(Math.abs(smin), Math.abs(smax)); - if (Number.isInteger(this.symlog) && (this.symlog > 0)) - v *= Math.pow(10, -1*this.symlog); - else - v *= 0.01; - this.func = symlog().constant(v).domain([smin, smax]); - } else if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, 0, smin, smax); - else - this.func = linear().domain([smin, smax]); + if ( material.wireframe ) { + style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin; - if (this.vertical ^ this.reverse) { - const d = range[0]; range[0] = range[1]; range[1] = d; - } + } else { - this.func.range(range); + style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; - this.scale_min = smin; - this.scale_max = smax; + } - if (this.kind === kAxisTime) - this.gr = val => this.func(this.convertDate(val)); - else if (this.log) - this.gr = val => (val < this.scale_min) ? (this.vertical ? this.func.range()[0]+5 : -5) : this.func(val); - else - this.gr = this.func; + addPath( style, path ); - delete this.format;// remove formatting func + } - let ndiv = 508; - if (this.is_gaxis) - ndiv = axis.fNdiv; - else if (axis) { - if (!axis.fNdivisions) - ndiv = 0; - else - ndiv = Math.max(axis.fNdivisions, 4); - } + // Hide anti-alias gaps - this.nticks = ndiv % 100; - this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv/10000); + function expand( v1, v2, pixels ) { - if (axis && !this.is_gaxis && (this.nticks > 20)) this.nticks = 20; + let x = v2.x - v1.x, y = v2.y - v1.y; + const det = x * x + y * y; - let gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - if (gr_range <= 0) gr_range = 100; + if ( det === 0 ) return; - if (this.kind === kAxisTime) { - if (this.nticks > 8) this.nticks = 8; + const idet = pixels / Math.sqrt( det ); - const scale_range = this.scale_max - this.scale_min, - idF = axis.fTimeFormat.indexOf('%F'), - tf2 = chooseTimeFormat(scale_range / gr_range, false); - let tf1 = (idF >= 0) ? axis.fTimeFormat.slice(0, idF) : axis.fTimeFormat; + x *= idet; y *= idet; - if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) - tf1 = chooseTimeFormat(scale_range / this.nticks, true); + v2.x += x; v2.y += y; + v1.x -= x; v1.y -= y; - this.tfunc1 = this.tfunc2 = this.timegmt ? utcFormat(tf1) : timeFormat(tf1); - if (tf2 !== tf1) - this.tfunc2 = this.timegmt ? utcFormat(tf2) : timeFormat(tf2); + } - this.format = this.formatTime; - } else if (this.log) { - if (this.nticks2 > 1) { - this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally - this.nticks2 = 1; - } - this.noexp = axis?.TestBit(EAxisBits.kNoExponent); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed) this.noexp = true; - this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels); - this.format = this.formatLog; - } else if (this.kind === kAxisLabels) { - this.nticks = 50; // for text output allow max 50 names - const scale_range = this.scale_max - this.scale_min; - if (this.nticks > scale_range) - this.nticks = Math.round(scale_range); + function addPath( style, path ) { - this.regular_labels = true; + if ( _currentStyle === style ) { - if (axis && axis.fNbins && axis.fLabels) { - if ((axis.fNbins !== Math.round(axis.fXmax - axis.fXmin)) || - (axis.fXmin !== 0) || (axis.fXmax !== axis.fNbins)) - this.regular_labels = false; - } + _currentPath += path; - this.nticks2 = 1; + } else { - this.format = this.formatLabels; - } else { - this.order = 0; - this.ndig = 0; - this.format = this.formatNormal; - } - } + flushPath(); - /** @summary Return scale min */ - getScaleMin() { - return this.func?.domain()[0] ?? 0; - } + _currentStyle = style; + _currentPath = path; - /** @summary Return scale max */ - getScaleMax() { - return this.func?.domain()[1] ?? 0; - } + } - /** @summary Provide label for axis value */ - formatLabels(d) { - const a = this.getObject(); - let indx = parseFloat(d); - if (!this.regular_labels) - indx = Math.round((indx - a.fXmin)/(a.fXmax - a.fXmin) * a.fNbins); - else - indx = Math.floor(indx); - if ((indx < 0) || (indx >= a.fNbins)) return null; - for (let i = 0; i < a.fLabels.arr.length; ++i) { - const tstr = a.fLabels.arr[i]; - if (tstr.fUniqueID === indx+1) return tstr.fString; - } - return null; - } + } - /** @summary Creates array with minor/middle/major ticks */ - createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) - this.noticksopt = true; + function flushPath() { - const handle = { painter: this, nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: [], middle: [], major: [] }; - let ticks; + if ( _currentPath ) { - if (this.fixed_ticks) { - ticks = []; - this.fixed_ticks.forEach(v => { - if ((v >= this.scale_min) && (v <= this.scale_max)) ticks.push(v); - }); - } else if ((this.kind === kAxisLabels) && !this.regular_labels) { - ticks = []; - handle.lbl_pos = []; - const axis = this.getObject(); - for (let n = 0; n < axis.fNbins; ++n) { - const x = axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); - if ((x >= this.scale_min) && (x < this.scale_max)) { - handle.lbl_pos.push(x); - if (x > this.scale_min) ticks.push(x); - } - } - } else - ticks = this.produceTicks(this.nticks); + _svgNode = getPathNode( _pathCount ++ ); + _svgNode.setAttribute( 'd', _currentPath ); + _svgNode.setAttribute( 'style', _currentStyle ); + _svg.appendChild( _svgNode ); - handle.minor = handle.middle = handle.major = ticks; + } - if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min)*1e-5; - if (res[0] > this.scale_min + delta) res.unshift(this.scale_min); - if (res[res.length-1] < this.scale_max - delta) res.push(this.scale_max); - return res; - } + _currentPath = ''; + _currentStyle = ''; - if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10)) && !this.fixed_ticks) { - handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); + } - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); + function getPathNode( id ) { - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) - handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { - handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) - handle.minor = handle.middle; - } - } + if ( _svgPathPool[ id ] == null ) { - handle.reset = function() { - this.nminor = this.nmiddle = this.nmajor = 0; - }; + _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); - handle.next = function(doround) { - if (this.nminor >= this.minor.length) return false; + if ( _quality == 0 ) { - this.tick = this.minor[this.nminor++]; - this.grpos = this.func(this.tick); - if (doround) this.grpos = Math.round(this.grpos); - this.kind = 3; + _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed - if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { - this.nmiddle++; - this.kind = 2; - } + } - if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { - this.nmajor++; - this.kind = 1; - } - return true; - }; + return _svgPathPool[ id ]; - handle.last_major = function() { - return (this.kind !== 1) ? false : this.nmajor === this.major.length; - }; + } - handle.next_major_grpos = function() { - if (this.nmajor >= this.major.length) return null; - return this.func(this.major[this.nmajor]); - }; + return _svgPathPool[ id ]; - handle.get_modifier = function() { - return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor-1, this.major); - }; + } - this.order = 0; - this.ndig = 0; + } - // at the moment when drawing labels, we can try to find most optimal text representation for them +} - if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && (handle.major.length > 0)) { - let maxorder = 0, minorder = 0, exclorder3 = false; +const originalTHREE = { + REVISION, DoubleSide, FrontSide, Object3D, Color, Vector2, Vector3, Matrix4, Line3, Raycaster, + WebGLRenderer, WebGLRenderTarget, + BufferGeometry, BufferAttribute, Float32BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, + LineSegments, LineDashedMaterial, LineBasicMaterial, Points, PointsMaterial, + Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, + Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, + MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, + Group: Group$1, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, + AmbientLight, HemisphereLight, DirectionalLight, + CanvasTexture, TextureLoader, - if (!optionNoexp) { - const maxtick = Math.max(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - mintick = Math.min(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick)/3)*3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick)/3)*3 : 0; + Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass +}, THREE = Object.assign({}, originalTHREE); - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } - } +/** @summary Import proper three.js version + * @desc in node.js only r162 supports WebGL1 which can be emulated with "gl" package. + * Therefore only this version can be used for working in node.js + * @private */ +async function importThreeJs(original) { - // now try to find best combination of order and ndig for labels + if (!isNodeJs() || (THREE.REVISION <= 162)) + return THREE; + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h1 => { + Object.assign(THREE, h1); + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); + }).then(h2 => { + Object.assign(THREE, h2); + return THREE; + }); +} - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; +let _hfont; - for (let order = minorder; order <= maxorder; order+=3) { - if (exclorder3 && (order === 3)) continue; - this.order = order; - this.ndig = 0; - let lbls = [], indx = 0, totallen = 0; - while (indx < handle.major.length) { - const lbl = this.format(handle.major[indx], true); - if (lbls.indexOf(lbl) < 0) { - lbls.push(lbl); - const p = lbl.indexOf('.'); - if (!order && !optionNoexp && ((p > gStyle.fAxisMaxDigits) || ((p < 0) && (lbl.length > gStyle.fAxisMaxDigits)))) { - totallen += 1e10; // do not use order = 0 when too many digits are there - exclorder3 = false; - } - totallen += lbl.length; - indx++; - continue; - } - if (++this.ndig > 15) break; // not too many digits, anyway it will be exponential - lbls = []; indx = 0; totallen = 0; - } +/** @summary Create three.js Helvetica Regular Font instance + * @private */ +function getHelveticaFont() { + if (_hfont && _hfont instanceof THREE.Font) + return _hfont; - // for order === 0 we should virtually remove '0.' and extra label on top - if (!order && (this.ndig < 4)) - totallen -= handle.major.length * 2 + 3; + // eslint-disable-next-line + const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; + // eslint-disable-next-line + const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; - if (totallen < bestlen) { - bestlen = totallen; - bestorder = this.order; - bestndig = this.ndig; - } - } + // segment in clockwise - holes, otherwise - solid + // provide infinity symbol + glyphs['\u221E'] = { x_min: 100, x_max: 700, ha: 800, o: 'm 150 200 l 350 300 l 150 400 l 150 200 m 450 300 l 650 200 l 650 400 l 450 300 m 100 100 l 100 500 l 400 350 l 700 500 l 700 100 l 400 250 l 100 100 ' }; - this.order = bestorder; - this.ndig = bestndig; + _hfont = new THREE.Font({ + ascender, boundingBox, cssFontStyle, cssFontWeight, default: helvetiker_regular_typeface, descender, familyName, glyphs, + lineHeight, original_font_information, resolution, underlinePosition, underlineThickness + }); - if (optionInt) { - if (this.order) console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); - this.ndig = 0; - this.order = 0; - } - } + return _hfont; +} - return handle; - } +/** @summary Create three.js Color instance, handles optional opacity + * @private */ +function getMaterialArgs(color$1, args) { + if (!args || !isObject(args)) + args = {}; - /** @summary Is labels should be centered */ - isCenteredLabels() { - if (this.kind === kAxisLabels) return true; - if (this.log) return false; - return this.getObject()?.TestBit(EAxisBits.kCenterLabels); - } + if (isStr(color$1) && (((color$1[0] === '#') && (color$1.length === 9)) || (color$1.indexOf('rgba') >= 0))) { + const col = color(color$1); + args.color = new THREE.Color(col.r, col.g, col.b); + args.opacity = col.opacity ?? 1; + args.transparent = args.opacity < 1; + } else + args.color = new THREE.Color(color$1); + return args; +} - /** @summary Add interactive elements to draw axes title */ - addTitleDrag(title_g, vertical, offset_k, reverse, axis_length) { - if (!settings.MoveResize || this.isBatchMode()) return; +function createSVGRenderer(as_is, precision, doc) { - let drag_rect = null, - acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx; - const drag_move = drag().subject(Object); + const excl_style1 = ';stroke-opacity:1;stroke-width:1;stroke-linecap:round', + excl_style2 = ';fill-opacity:1'; + /* eslint-disable-next-line one-var */ + const doc_wrapper = { + svg_attr: {}, + svg_style: {}, + path_attr: {}, + accPath: '', + createElementNS(ns, kind) { + if (kind === 'path') { + return { + _wrapper: this, + setAttribute(name, value) { + // cut useless fill-opacity:1 at the end of many SVG attributes + if ((name === 'style') && value) { + const pos1 = value.indexOf(excl_style1); + if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) + value = value.slice(0, value.length - excl_style1.length); + const pos2 = value.indexOf(excl_style2); + if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) + value = value.slice(0, value.length - excl_style2.length); + } + this._wrapper.path_attr[name] = value; + } + }; + } - drag_move.on('start', evnt => { - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + if (kind !== 'svg') { + console.error(`not supported element for SVGRenderer ${kind}`); + return null; + } - const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = vertical ? box.height : box.width; + return { + _wrapper: this, + childNodes: [], // may be accessed - make dummy + style: this.svg_style, // for background color + setAttribute(name, value) { + this._wrapper.svg_attr[name] = value; + }, + appendChild(/* node */) { + this._wrapper.accPath += ``; + this._wrapper.path_attr = {}; + }, + removeChild(/* node */) { + this.childNodes = []; + } + }; + } + }; - new_x = acc_x = title_g.property('shift_x'); - new_y = acc_y = title_g.property('shift_y'); + let originalDocument; - sign_0 = vertical ? (acc_x > 0) : (acc_y > 0); // sign should remain + if (isNodeJs()) { + originalDocument = globalThis.document; + globalThis.document = doc_wrapper; + } - alt_pos = vertical ? [axis_length, axis_length/2, 0] : [0, axis_length/2, axis_length]; // possible positions - const off = vertical ? -title_length/2 : title_length/2; - if (this.title_align === 'middle') { - alt_pos[0] += off; - alt_pos[2] -= off; - } else if (this.title_align === 'begin') { - alt_pos[1] -= off; - alt_pos[2] -= 2*off; - } else { // end - alt_pos[0] += 2*off; - alt_pos[1] += off; - } + const rndr = new THREE.SVGRenderer(); - if (this.titleCenter) - curr_indx = 1; - else if (reverse ^ this.titleOpposite) - curr_indx = 0; - else - curr_indx = 2; + if (isNodeJs()) + globalThis.document = originalDocument; - alt_pos[curr_indx] = vertical ? acc_y : acc_x; + rndr.doc_wrapper = doc_wrapper; // use it to get final SVG code - drag_rect = title_g.append('rect') - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) return; + rndr.originalRender = rndr.render; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + rndr.render = function(scene, camera) { + const _doc = globalThis.document; + if (isNodeJs()) + globalThis.document = this.doc_wrapper; - acc_x += evnt.dx; - acc_y += evnt.dy; + this.originalRender(scene, camera); - let set_x, set_y, besti = 0; - const p = vertical ? acc_y : acc_x; + if (isNodeJs()) + globalThis.document = _doc; + }; - for (let i = 1; i < 3; ++i) - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) besti = i; + rndr.clearHTML = function() { + this.doc_wrapper.accPath = ''; + }; - if (vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_y = acc_y; - set_x = alt_pos[besti]; - } + rndr.makeOuterHTML = function() { + const wrap = this.doc_wrapper, + _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, + _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; - if (sign_0 === (vertical ? (set_x > 0) : (set_y > 0))) { - new_x = set_x; new_y = set_y; curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - } - }).on('end', evnt => { - if (!drag_rect) return; + return `${wrap.accPath}`; + }; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + rndr.fillTargetSVG = function(svg) { + if (isNodeJs()) { + const wrap = this.doc_wrapper; - title_g.property('shift_x', new_x) - .property('shift_y', new_y); + svg.setAttribute('viewBox', wrap.svg_attr.viewBox); + svg.setAttribute('width', wrap.svg_attr.width); + svg.setAttribute('height', wrap.svg_attr.height); + svg.style.background = wrap.svg_style.backgroundColor || ''; - const axis = this.getObject(), axis2 = this.source_axis, - setBit = (bit, on) => { - if (axis && axis.TestBit(bit) !== on) axis.InvertBit(bit); - if (axis2 && axis2.TestBit(bit) !== on) axis2.InvertBit(bit); - }; + svg.innerHTML = wrap.accPath; + } else { + const src = this.domElement; - this.titleOffset = (vertical ? new_x : new_y) / offset_k; - const offset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis) axis.fTitleOffset = offset; - if (axis2) axis2.fTitleOffset = offset; + svg.setAttribute('viewBox', src.getAttribute('viewBox')); + svg.setAttribute('width', src.getAttribute('width')); + svg.setAttribute('height', src.getAttribute('height')); + svg.style.background = src.style.backgroundColor; - if (curr_indx === 1) { - setBit(EAxisBits.kCenterTitle, true); this.titleCenter = true; - setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; - } else if (curr_indx === 0) { - setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, true); this.titleOpposite = true; - } else { - setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; + while (src.firstChild) { + const elem = src.firstChild; + src.removeChild(elem); + svg.appendChild(elem); } + } + }; - this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter?1:0})`); - - drag_rect.remove(); - drag_rect = null; - }); + rndr.setPrecision(precision); - title_g.style('cursor', 'move').call(drag_move); - } + return rndr; +} - /** @summary Configure hist painter which creates axis - to be able submit execs - * @private */ - setHistPainter(hist_painter, axis_name) { - this.hist_painter = hist_painter; - this.hist_axis = axis_name; - } - /** @summary Submit exec for the axis - if possible - * @private */ - submitAxisExec(exec, only_gaxis) { - const snapid = this.hist_painter?.snapid; - if (snapid && this.hist_axis && !only_gaxis) - this.submitCanvExec(exec, `${snapid}#${this.hist_axis}`); - else if (this.is_gaxis) - this.submitCanvExec(exec); - } +/** @summary Define rendering kind which will be used for rendering of 3D elements + * @param {value} [render3d] - pre-configured value, will be used if applicable + * @param {value} [is_batch] - is batch mode is configured + * @return {value} - rendering kind, see constants.Render3D + * @private */ +function getRender3DKind(render3d, is_batch) { + if (is_batch === undefined) + is_batch = isBatchMode(); - /** @summary Produce svg path for axis ticks */ - produceTicksPath(handle, side, tickSize, ticksPlusMinus, secondShift, real_draw) { - let path1 = '', path2 = ''; - this.ticks = []; + if (!render3d) + render3d = is_batch ? settings.Render3DBatch : settings.Render3D; + const rc = constants$1.Render3D; - while (handle.next(true)) { - let h1 = Math.round(tickSize/4), h2 = 0; + if (render3d === rc.Default) + render3d = is_batch ? rc.WebGLImage : rc.WebGL; + if (is_batch && (render3d === rc.WebGL)) + render3d = rc.WebGLImage; - if (handle.kind < 3) - h1 = Math.round(tickSize/2); + return render3d; +} - if (handle.kind === 1) { - // if not showing labels, not show large tick - // FIXME: for labels last tick is smaller, - if (/* (this.kind === kAxisLabels) || */ (this.format(handle.tick, true) !== null)) h1 = tickSize; - this.ticks.push(handle.grpos); // keep graphical positions of major ticks - } +const Handling3DDrawings = { - if (ticksPlusMinus > 0) - h2 = -h1; - else if (side < 0) { - h2 = -h1; h1 = 0; - } + /** @summary Access current 3d mode + * @param {string} [new_value] - when specified, set new 3d mode + * @return current value + * @private */ + access3dKind(new_value) { + const svg = this.getPadPainter()?.getPadSvg(); + if (!svg || svg.empty()) + return -1; - path1 += this.vertical ? `M${h1},${handle.grpos}H${h2}` : `M${handle.grpos},${-h1}V${-h2}`; + // returns kind of currently created 3d canvas + const kind = svg.property('can3d'); + if (new_value !== undefined) + svg.property('can3d', new_value); + return ((kind === null) || (kind === undefined)) ? -1 : kind; + }, - if (secondShift) - path2 += this.vertical ? `M${secondShift-h1},${handle.grpos}H${secondShift-h2}` : `M${handle.grpos},${secondShift+h1}V${secondShift+h2}`; + /** @summary Returns size which available for 3D drawing. + * @desc One uses frame sizes for the 3D drawing - like TH2/TH3 objects + * @private */ + getSizeFor3d(can3d /* , render3d */) { + if (can3d === undefined) { + // analyze which render/embed mode can be used + can3d = getRender3DKind(); + // all non-webgl elements can be embedded into SVG as is + if (can3d !== constants$1.Render3D.WebGL) + can3d = constants$1.Embed3D.EmbedSVG; + else if (settings.Embed3D !== constants$1.Embed3D.Default) + can3d = settings.Embed3D; + else if (browser.isFirefox) + can3d = constants$1.Embed3D.Embed; + else if (browser.chromeVersion > 95) + // version 96 works partially, 97 works fine + can3d = constants$1.Embed3D.Embed; + else + can3d = constants$1.Embed3D.Overlay; } - return real_draw ? path1 + path2 : ''; - } + const pp = this.getPadPainter(), + pad = pp?.getPadSvg(), + clname = 'draw3d_' + (pp?.getPadName() || 'canvas'); - /** @summary Returns modifier for axis label */ - findLabelModifier(axis, nlabel, positions) { - if (!axis.fModLabs) return null; - for (let n = 0; n < axis.fModLabs.arr.length; ++n) { - const mod = axis.fModLabs.arr[n]; + if (!pad || pad.empty()) { + // this is a case when object drawn without canvas - if ((mod.fLabValue !== undefined) && (mod.fLabNum === 0)) { - const eps = this.log ? positions[nlabel]*1e-6 : (this.scale_max - this.scale_min)*1e-6; - if (Math.abs(mod.fLabValue - positions[nlabel]) < eps) - return mod; + const rect = getElementRect(this.selectDom()); + if ((rect.height < 10) && (rect.width > 10)) { + rect.height = Math.round(0.66 * rect.width); + this.selectDom().style('height', rect.height + 'px'); } - - if ((mod.fLabNum === nlabel + 1) || - ((mod.fLabNum < 0) && (nlabel === positions.length + mod.fLabNum))) - return mod; + rect.x = rect.y = 0; + rect.clname = clname; + rect.can3d = -1; + return rect; } - return null; - } - /** @summary Draw axis labels - * @return {Promise} with array label size and max width */ - async drawLabels(axis_g, axis, w, h, handle, side, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { - const center_lbls = this.isCenteredLabels(), - rotate_lbls = axis.TestBit(EAxisBits.kLabelsVert), - label_g = [axis_g.append('svg:g').attr('class', 'axis_labels')], - lbl_pos = handle.lbl_pos || handle.major, - tilt_angle = gStyle.AxisTiltAngle ?? 25; - let textscale = 1, maxtextlen = 0, applied_scale = 0, - lbl_tilt = false, any_modified = false, max_textwidth = 0, max_tiltsize = 0; + const fp = this.getFramePainter(); + let size; - if (this.lbls_both_sides) - label_g.push(axis_g.append('svg:g').attr('class', 'axis_labels').attr('transform', this.vertical ? `translate(${w})` : `translate(0,${-h})`)); + if (fp?.mode3d && (can3d > 0)) + size = fp.getFrameRect(); + else { + let elem = (can3d > 0) ? pad : this.getCanvSvg(); + size = { x: 0, y: 0, width: elem.property('draw_width'), height: elem.property('draw_height') }; + if (Number.isNaN(size.width) || Number.isNaN(size.height)) { + size.width = pp.getPadWidth(); + size.height = pp.getPadHeight(); + } else if (fp && !fp.mode3d) { + elem = pp.getFrameSvg(); + size.x = elem.property('draw_x'); + size.y = elem.property('draw_y'); + } + } - if (frame_ygap > 0) - max_tiltsize = frame_ygap / Math.sin(tilt_angle/180*Math.PI) - Math.tan(tilt_angle/180*Math.PI); + size.clname = clname; + size.can3d = can3d; - // function called when text is drawn to analyze width, required to correctly scale all labels - // must be function to correctly handle 'this' argument - function process_drawtext_ready(painter) { - const textwidth = this.result_width; - max_textwidth = Math.max(max_textwidth, textwidth); + const rect = pp?.getPadRect(); + if (rect) { + // while 3D canvas uses area also for the axis labels, extend area relative to normal frame + const dx = Math.round(size.width * 0.07), dy = Math.round(size.height * 0.05); - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { - let maxwidth = this.gap_before*0.45 + this.gap_after*0.45; - if (!this.gap_before) - maxwidth = 0.9*this.gap_after; - else if (!this.gap_after) - maxwidth = 0.9*this.gap_before; - textscale = Math.min(textscale, maxwidth / textwidth); - } else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) - textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth); + size.x = Math.max(0, size.x - dx); + size.y = Math.max(0, size.y - dy); + size.width = Math.min(size.width + 2 * dx, rect.width - size.x); + size.height = Math.min(size.height + 2 * dy, rect.height - size.y); + } - if ((textscale > 0.0001) && (textscale < 0.7) && !any_modified && - !painter.vertical && !rotate_lbls && (maxtextlen > 5) && (label_g.length === 1) && (lbl_tilt === false)) - lbl_tilt = true; + if (can3d === constants$1.Embed3D.Overlay) { + size = getAbsPosInCanvas(pad, size); + const scale = this.getCanvPainter().getPadScale(); + if (scale && scale !== 1) { + size.x /= scale; + size.y /= scale; + size.width /= scale; + size.height /= scale; + } + } - let scale = textscale; + return size; + }, - if (lbl_tilt) { - if (max_tiltsize && max_textwidth) { - scale = Math.min(1, 0.8*max_tiltsize/max_textwidth); - if (scale < textscale) { - // if due to tilt scale is even smaller - ignore tilting - lbl_tilt = 0; - scale = textscale; - } - } else - scale *= 3; - } + /** @summary Clear all 3D drawings + * @return can3d value - how webgl canvas was placed + * @private */ + clear3dCanvas() { + const can3d = this.access3dKind(null); + if (can3d < 0) { + // remove first child from main element - if it is canvas + const main = this.selectDom().node(); + let chld = main?.firstChild; - if (((scale > 0.0001) && (scale < 1)) || (lbl_tilt !== false)) { - applied_scale = 1/scale; - painter.scaleTextDrawing(applied_scale, label_g[0]); + while (chld && !chld.$jsroot) + chld = chld.nextSibling; + + if (chld?.$jsroot === '3d') { + delete chld.painter; + main.removeChild(chld); } + return can3d; } - for (let lcnt = 0; lcnt < label_g.length; ++lcnt) { - if (lcnt > 0) side = -side; + const size = this.getSizeFor3d(can3d); + if (size.can3d === 0) { + select(this.getCanvSvg().node().nextSibling).remove(); // remove html5 canvas + this.getCanvSvg().style('display', null); // show SVG canvas + } else { + const pp = this.getPadPainter(); + if (!pp || pp.getPadSvg().empty()) + return; - let lastpos = 0; - const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; + this.apply3dSize(size).remove(); - this.startTextDrawing(labelsFont, 'font', label_g[lcnt]); + pp.getFrameSvg().style('display', null); // clear display property + } + return can3d; + }, - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - let text = this.format(lbl_pos[nmajor], true); - if (text === null) continue; + /** @summary Add 3D canvas + * @private */ + add3dCanvas(size, canv, webgl) { + if (!canv || (size.can3d < -1)) + return; - const mod = this.findLabelModifier(axis, nmajor, lbl_pos); - if (mod?.fTextSize === 0) continue; + if (size.can3d === -1) { + // case when 3D object drawn without canvas - if (mod) any_modified = true; - if (mod?.fLabText) text = mod.fLabText; + const main = this.selectDom().node(); + if (main) { + main.appendChild(canv); + canv.painter = this; + canv.$jsroot = '3d'; // mark canvas as added by jsroot + } - const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; - let pos = Math.round(this.func(lbl_pos[nmajor])); + return; + } - if (mod?.fTextColor > 0) arg.color = this.getColor(mod.fTextColor); + if ((size.can3d > 0) && !webgl) + size.can3d = constants$1.Embed3D.EmbedSVG; - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; + this.access3dKind(size.can3d); - arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; + if (size.can3d === 0) { + this.getCanvSvg().style('display', 'none'); // hide SVG canvas - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); - if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) continue; - } + this.getCanvSvg().node().parentNode.appendChild(canv); // add directly + } else { + const pp = this.getPadPainter(); + if (!pp || pp.getPadSvg().empty()) + return; - maxtextlen = Math.max(maxtextlen, text.length); + // first hide normal frame + pp.getFrameSvg().style('display', 'none'); - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += labelsFont.size; - } else if (arg.align % 10 === 3) - arg.y -= labelsFont.size*0.1; // font takes 10% more by top align - } + const elem = this.apply3dSize(size); + elem.attr('title', '').node().appendChild(canv); + } + }, - if (rotate_lbls) - arg.rotate = 270; - else if (mod && mod.fTextAngle !== -1) - arg.rotate = -mod.fTextAngle; + /** @summary Apply size to 3D elements + * @private */ + apply3dSize(size, onlyget) { + if (size.can3d < 0) + return select(null); - // only for major text drawing scale factor need to be checked - if (lcnt === 0) arg.post_process = process_drawtext_ready; + let elem; - this.drawText(arg); + if (size.can3d > 1) { + const pp = this.getPadPainter(); - // workaround for symlog where labels can be compressed to close - if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos - lastpos); - textscale = Math.min(textscale, 1.1*axis_step/labelsFont.size); - } + elem = pp.getLayerSvg(size.clname); + if (onlyget) + return elem; - lastpos = pos; - } + const svg = pp.getPadSvg(); - if (this.order) { - let xoff = 0, yoff = 0; - if (this.name === 'xaxis') { - xoff = gStyle.fXAxisExpXOffset || 0; - yoff = gStyle.fXAxisExpYOffset || 0; - } else if (this.name === 'yaxis') { - xoff = gStyle.fYAxisExpXOffset || 0; - yoff = gStyle.fYAxisExpYOffset || 0; - } + if (size.can3d === constants$1.Embed3D.EmbedSVG) { + // this is SVG mode or image mode - just create group to hold element - if (xoff) xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); - if (yoff) yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); + if (elem.empty()) + elem = svg.insert('g', '.primitives_layer').attr('class', size.clname); - this.drawText({ color: labelsFont.color, - x: xoff + (this.vertical ? side*5 : w+5), - y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3*side)), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g[lcnt] }); - } - } + makeTranslate(elem, size.x, size.y); + } else { + if (elem.empty()) + elem = svg.insert('foreignObject', '.primitives_layer').attr('class', size.clname); - // first complete major labels drawing - return this.finishTextDrawing(label_g[0], true).then(() => { - if (label_g.length > 1) { - // now complete drawing of second half with scaling if necessary - if (applied_scale) - this.scaleTextDrawing(applied_scale, label_g[1]); - return this.finishTextDrawing(label_g[1], true); - } - }).then(() => { - if (lbl_tilt) { - label_g[0].selectAll('text').each(function() { - const txt = select(this), tr = txt.attr('transform'); - txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); - }); + elem.attr('x', size.x) + .attr('y', size.y) + .attr('width', size.width) + .attr('height', size.height) + .attr('viewBox', `0 0 ${size.width} ${size.height}`) + .attr('preserveAspectRatio', 'xMidYMid'); } - - return max_textwidth; - }); - } - - /** @summary Extract major draw attributes, which are also used in interactive operations - * @private */ - extractDrawAttributes(scalingSize, w, h) { - const axis = this.getObject(); - let pp = this.getPadPainter(); - if (axis.$use_top_pad) - pp = pp?.getPadPainter(); // workaround for ratio plot - const pad_w = pp?.getPadWidth() || scalingSize || w/0.8, // use factor 0.8 as ratio between frame and pad size - pad_h = pp?.getPadHeight() || scalingSize || h/0.8, - // if no external scaling size use scaling as in TGaxis.cxx:1448 - NDC axis length is in the scaling factor - tickScalingSize = scalingSize || (this.vertical ? h/pad_h*pad_w : w/pad_w*pad_h); - - let tickSize = 0, titleColor, titleFontId, offset; - - this.scalingSize = scalingSize || Math.max(Math.min(pad_w, pad_h), 10); - - if (this.is_gaxis) { - const optionSize = axis.fChopt.indexOf('S') >= 0; - this.optionUnlab = axis.fChopt.indexOf('U') >= 0; - this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || axis.TestBit(EAxisBits.kTickMinus); - this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || axis.TestBit(EAxisBits.kTickPlus); - this.optionNoopt = (axis.fChopt.indexOf('N') >= 0); // no ticks position optimization - this.optionInt = (axis.fChopt.indexOf('I') >= 0); // integer labels - this.optionText = (axis.fChopt.indexOf('T') >= 0); // text scaling? - this.createAttLine({ attr: axis }); - tickSize = optionSize ? axis.fTickSize : 0.03; - titleColor = this.getColor(axis.fTextColor); - titleFontId = axis.fTextFont; - offset = axis.fLabelOffset; - if ((this.vertical && axis.fY1 > axis.fY2 && !this.optionMinus) || (!this.vertical && axis.fX1 > axis.fX2)) - offset = -offset; } else { - this.optionUnlab = false; - this.optionMinus = this.vertical ^ this.invert_side; - this.optionPlus = !this.optionMinus; - this.optionNoopt = false; // no ticks position optimization - this.optionInt = false; // integer labels - this.optionText = false; - this.createAttLine({ color: axis.fAxisColor, width: 1, style: 1 }); - tickSize = axis.fTickLength; - titleColor = this.getColor(axis.fTitleColor); - titleFontId = axis.fTitleFont; - offset = axis.fLabelOffset; - } + let prnt = this.getCanvSvg().node().parentNode; - offset += (this.vertical ? 0.002 : 0.005); + elem = select(prnt).select('.' + size.clname); + if (onlyget) + return elem; - if (this.kind === kAxisLabels) - this.optionText = true; + // force redraw by resize + this.getCanvSvg().property('redraw_by_resize', true); - this.optionNoexp = axis.TestBit(EAxisBits.kNoExponent); + if (elem.empty()) { + elem = select(prnt).append('div').attr('class', size.clname) + .style('user-select', 'none'); + } - this.ticksSize = Math.round(tickSize * tickScalingSize); - if (scalingSize && (this.ticksSize < 0)) - this.ticksSize = -this.ticksSize; + // our position inside canvas, but to set 'absolute' position we should use + // canvas element offset relative to first parent with non-static position + // now try to use getBoundingClientRect - it should be more precise - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) this.ticksSize = this.maxTickSize; + const pos0 = prnt.getBoundingClientRect(), doc = getDocument(); - // now used only in 3D drawing - this.ticksColor = this.lineatt.color; - this.ticksWidth = this.lineatt.width; + while (prnt) { + if (prnt === doc) { + prnt = null; + break; + } + try { + if (getComputedStyle(prnt).position !== 'static') + break; + } catch { + break; + } + prnt = prnt.parentNode; + } - const k = this.optionText ? 0.66666 : 1; // set TGaxis.cxx, line 1504 - this.labelSize = Math.round((axis.fLabelSize < 1) ? k * axis.fLabelSize * this.scalingSize : k * axis.fLabelSize); - this.labelsOffset = Math.round(offset * this.scalingSize); - this.labelsFont = new FontHandler(axis.fLabelFont, this.labelSize, scalingSize); - if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) this.optionUnlab = true; // disable labels when size not specified - this.labelsFont.setColor(this.getColor(axis.fLabelColor)); + const pos1 = prnt?.getBoundingClientRect() ?? { top: 0, left: 0 }, + offx = Math.round(pos0.left - pos1.left), + offy = Math.round(pos0.top - pos1.top); - this.fTitle = axis.fTitle; - if (this.fTitle) { - this.titleSize = (axis.fTitleSize >= 1) ? axis.fTitleSize : Math.round(axis.fTitleSize * this.scalingSize); - this.titleFont = new FontHandler(titleFontId, this.titleSize, scalingSize); - this.titleFont.setColor(titleColor); - this.offsetScaling = (axis.fTitleSize >= 1) ? 1 : (this.vertical ? pad_w : pad_h) / this.scalingSize; - this.titleOffset = axis.fTitleOffset; - if (!this.titleOffset && this.name[0] === 'x') - this.titleOffset = gStyle.fXaxis.fTitleOffset; - this.titleOffset *= this.titleSize * this.offsetScaling; - this.titleCenter = axis.TestBit(EAxisBits.kCenterTitle); - this.titleOpposite = axis.TestBit(EAxisBits.kOppositeTitle); - } else { - delete this.titleSize; - delete this.titleFont; - delete this.offsetScaling; - delete this.titleOffset; - delete this.titleCenter; - delete this.titleOpposite; + elem.style('position', 'absolute').style('left', (size.x + offx) + 'px').style('top', (size.y + offy) + 'px').style('width', size.width + 'px').style('height', size.height + 'px'); } - } - - /** @summary function draws TAxis or TGaxis object - * @return {Promise} for drawing ready */ - async drawAxis(layer, w, h, transform, secondShift, disable_axis_drawing, max_text_width, calculate_position, frame_ygap) { - const axis = this.getObject(), - swap_side = this.swap_side || false; - let axis_g = layer, draw_lines = true; - // shift for second ticks set (if any) - if (!secondShift) - secondShift = 0; - else if (this.invert_side) - secondShift = -secondShift; + return elem; + } - this.extractDrawAttributes(undefined, w, h); +}; // Handling3DDrawings - if (this.is_gaxis) - draw_lines = axis.fLineColor !== 0; - // indicate that attributes created not for TAttLine, therefore cannot be updated as TAttLine in GED - this.lineatt.not_standard = true; +/** @summary Assigns method to handle 3D drawings inside SVG + * @private */ +function assign3DHandler(painter) { + Object.assign(painter, Handling3DDrawings); +} - if (!this.is_gaxis || (this.name === 'zaxis')) { - axis_g = layer.selectChild(`.${this.name}_container`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); - else - axis_g.selectAll('*').remove(); - } - let axis_lines = ''; - if (draw_lines) { - axis_lines = 'M0,0' + (this.vertical ? `v${h}` : `h${w}`); - if (secondShift) - axis_lines += this.vertical ? `M${secondShift},0v${h}` : `M0,${secondShift}h${w}`; - } +/** @summary Creates renderer for the 3D drawings + * @param {value} width - rendering width + * @param {value} height - rendering height + * @param {value} render3d - render type, see {@link constants.Render3D} + * @param {object} args - different arguments for creating 3D renderer + * @return {Promise} with renderer object + * @private */ +async function createRender3D(width, height, render3d, args) { + const rc = constants$1.Render3D, doc = getDocument(); - axis_g.attr('transform', transform); + render3d = getRender3DKind(render3d); - let side = 1, ticksPlusMinus = 0; + if (!args) + args = { antialias: true, alpha: true }; - if (this.optionPlus && this.optionMinus) { - side = 1; ticksPlusMinus = 1; - } else if (this.optionMinus) - side = (swap_side ^ this.vertical) ? 1 : -1; - else if (this.optionPlus) - side = (swap_side ^ this.vertical) ? -1 : 1; + let promise; - // first draw ticks + if (render3d === rc.None) + promise = Promise.resolve(null); + else if (render3d === rc.SVG) { + // SVG rendering + const r = createSVGRenderer(false, 0); + r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); + promise = Promise.resolve(r); + } else if (isNodeJs()) { + // try to use WebGL inside node.js - need to create headless context + promise = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(node_canvas => { + args.canvas = node_canvas.default.createCanvas(width, height); + args.canvas.addEventListener = () => {}; // dummy + args.canvas.removeEventListener = () => {}; // dummy + args.canvas.style = {}; + return internals._node_gl || Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); + }).then(node_gl => { + internals._node_gl = node_gl; + const gl = node_gl?.default(width, height, { preserveDrawingBuffer: true }); + if (!gl) + throw Error('Fail to create headless-gl'); + args.context = gl; + gl.canvas = args.canvas; - const handle = this.createTicks(false, this.optionNoexp, this.optionNoopt, this.optionInt); + const r = new THREE.WebGLRenderer(args); + r.jsroot_output = new THREE.WebGLRenderTarget(width, height); + r.setRenderTarget(r.jsroot_output); + r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); + return r; + }).catch(() => { + console.log('gl module is not available - fallback to SVGRenderer'); + render3d = rc.SVG; + const r = createSVGRenderer(false, 0); + r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); + return r; + }); + } else if (render3d === rc.WebGL) { + // interactive WebGL Rendering + promise = Promise.resolve(new THREE.WebGLRenderer(args)); + } else { + // rendering with WebGL directly into svg image + const r = new THREE.WebGLRenderer(args); + r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); + promise = Promise.resolve(r); + } - axis_lines += this.produceTicksPath(handle, side, this.ticksSize, ticksPlusMinus, secondShift, draw_lines && !disable_axis_drawing && !this.disable_ticks); + return promise.then(renderer => { + if (!renderer) + return renderer; - if (!disable_axis_drawing && axis_lines && !this.lineatt.empty()) { - axis_g.append('svg:path') - .attr('d', axis_lines) - .call(this.lineatt.func); - } + if (!renderer.jsroot_dom) + renderer.jsroot_dom = renderer.domElement; + else + renderer.jsroot_custom_dom = true; - let title_shift_x = 0, title_shift_y = 0, title_g = null, labelsMaxWidth = 0; - // draw labels (sometime on both sides) - const pr = (disable_axis_drawing || this.optionUnlab) - ? Promise.resolve(0) - : this.drawLabels(axis_g, axis, w, h, handle, side, this.labelsFont, this.labelsOffset, this.ticksSize, ticksPlusMinus, max_text_width, frame_ygap); + // res.renderer.setClearColor('#000000', 1); + // res.renderer.setClearColor(0x0, 0); + renderer.jsroot_render3d = render3d; - return pr.then(maxw => { - labelsMaxWidth = maxw; + // which format used to convert into images + renderer.jsroot_image_format = 'png'; - if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const labelSize = Math.max(this.labelsFont.size, 5), - r = axis_g.append('svg:rect') - .attr('class', 'axis_zoom') - .style('opacity', '0') - .style('cursor', 'crosshair'); + renderer.originalSetSize = renderer.setSize; - if (this.vertical) { - const rw = (labelsMaxWidth || 2*labelSize) + 3; - r.attr('x', (side > 0) ? -rw : 0).attr('y', 0) - .attr('width', rw).attr('height', h); - } else { - r.attr('x', 0).attr('y', (side > 0) ? 0 : -labelSize - 3) - .attr('width', w).attr('height', labelSize + 3); - } + // apply size to dom element + renderer.setSize = function(w, h, updateStyle) { + if (this.jsroot_custom_dom) { + this.jsroot_dom.setAttribute('width', w); + this.jsroot_dom.setAttribute('height', h); } - this.position = 0; - - if (calculate_position) { - const node1 = axis_g.node(), node2 = this.getPadSvg().node(); - if (isFunc(node1?.getBoundingClientRect) && isFunc(node2?.getBoundingClientRect)) { - const rect1 = node1.getBoundingClientRect(), - rect2 = node2.getBoundingClientRect(); - this.position = rect1.left - rect2.left; // use to control left position of Y scale - } - if (node1 && !node2) - console.warn('Why PAD element missing when search for position'); - } + this.originalSetSize(w, h, updateStyle); + }; - if (!this.fTitle || disable_axis_drawing) return true; + renderer.setSize(width, height); - title_g = axis_g.append('svg:g').attr('class', 'axis_title'); + return renderer; + }); +} - let title_offest_k = side; - const rotate = axis.TestBit(EAxisBits.kRotateTitle) ? -1 : 1; - this.startTextDrawing(this.titleFont, 'font', title_g); +/** @summary Cleanup created renderer object + * @private */ +function cleanupRender3D(renderer) { + if (!renderer) + return; - const xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; + if (isNodeJs()) { + const ctxt = isFunc(renderer.getContext) ? renderer.getContext() : null, + ext = ctxt?.getExtension('STACKGL_destroy_context'); + if (isFunc(ext?.destroy)) + ext.destroy(); + } else { + // suppress warnings in Chrome about lost webgl context, not required in firefox + if (browser.isChrome && isFunc(renderer.forceContextLoss)) + renderer.forceContextLoss(); - this.title_align = this.titleCenter ? 'middle' : (myxor ? 'begin' : 'end'); + if (isFunc(renderer.dispose)) + renderer.dispose(); + } +} - if (this.vertical) { - title_offest_k *= -1.6; +/** @summary Cleanup previous renderings before doing next one + * @desc used together with SVG + * @private */ +function beforeRender3D(renderer) { + if (isFunc(renderer.clearHTML)) + renderer.clearHTML(); +} - title_shift_x = Math.round(title_offest_k * this.titleOffset); +/** @summary Post-process result of rendering + * @desc used together with SVG or node.js image rendering + * @private */ +function afterRender3D(renderer) { + const rc = constants$1.Render3D; - // if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { - // // special handling for color palette labels - draw them always on right side - // const rect = axis_g.node().getBoundingClientRect(); - // if (title_shift_x < rect.width - this.ticksSize) - // title_shift_x = Math.round(rect.width - this.ticksSize); - // } + if (renderer.jsroot_render3d === rc.WebGL) + return; - title_shift_y = Math.round(this.titleCenter ? h/2 : (xor_reverse ? h : 0)); + if (renderer.jsroot_render3d === rc.SVG) { + // case of SVGRenderer + renderer.fillTargetSVG(renderer.jsroot_dom); + } else if (isNodeJs()) { + // this is WebGL rendering in node.js + const canvas = renderer.domElement, + context = canvas.getContext('2d'), + pixels = new Uint8Array(4 * canvas.width * canvas.height); - this.drawText({ align: this.title_align+';middle', - rotate: (rotate < 0) ? 90 : 270, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g }); - } else { - title_offest_k *= 1.6; + renderer.readRenderTargetPixels(renderer.jsroot_output, 0, 0, canvas.width, canvas.height, pixels); - title_shift_x = Math.round(this.titleCenter ? w/2 : (xor_reverse ? 0 : w)); - title_shift_y = Math.round(title_offest_k * this.titleOffset); - this.drawText({ align: this.title_align+';middle', - rotate: (rotate < 0) ? 180 : 0, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g }); + // small code to flip Y scale + let indx1 = 0, indx2 = (canvas.height - 1) * 4 * canvas.width, k, d; + while (indx1 < indx2) { + for (k = 0; k < 4 * canvas.width; ++k) { + d = pixels[indx1 + k]; + pixels[indx1 + k] = pixels[indx2 + k]; + pixels[indx2 + k] = d; } + indx1 += 4 * canvas.width; + indx2 -= 4 * canvas.width; + } - this.addTitleDrag(title_g, this.vertical, title_offest_k, swap_side, this.vertical ? h : w); + const imageData = context.createImageData(canvas.width, canvas.height); + imageData.data.set(pixels); + context.putImageData(imageData, 0, 0); - return this.finishTextDrawing(title_g); - }).then(() => { - if (title_g) { - if (!this.titleOffset && this.vertical && labelsMaxWidth) - title_shift_x = Math.round(-side * (labelsMaxWidth + 0.7*this.offsetScaling*this.titleSize)); - makeTranslate(title_g, title_shift_x, title_shift_y); - title_g.property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); - } + const format = 'image/' + renderer.jsroot_image_format, + dataUrl = canvas.toDataURL(format); - return this; - }); + renderer.jsroot_dom.setAttribute('href', dataUrl); + } else { + const dataUrl = renderer.domElement.toDataURL('image/' + renderer.jsroot_image_format); + renderer.jsroot_dom.setAttribute('href', dataUrl); } +} -} // class TAxisPainter +// ======================================================================================================== -const logminfactorX = 0.0001, logminfactorY = 3e-4; +/** + * @summary Tooltip handler for 3D drawings + * + * @private + */ -/** @summary Configure tooltip enable flag for painter - * @private */ -function setPainterTooltipEnabled(painter, on) { - if (!painter) return; +class TooltipFor3D { - const fp = painter.getFramePainter(); - if (isFunc(fp?.setTooltipEnabled)) { - fp.setTooltipEnabled(on); - fp.processFrameTooltipEvent(null); + /** @summary constructor + * @param {object} dom - DOM element + * @param {object} canvas - canvas for 3D rendering */ + constructor(prnt, canvas) { + this.tt = null; + this.cont = null; + this.lastlbl = ''; + this.parent = prnt || getDocument().body; + this.canvas = canvas; // we need canvas to recalculate mouse events + this.abspos = !prnt; + this.scale = 1; } - // this is 3D control object - if (isFunc(painter.control?.setTooltipEnabled)) - painter.control.setTooltipEnabled(on); -} -/** @summary Return pointers on touch event - * @private */ -function get_touch_pointers(event, node) { - return event.$touch_arr ?? pointers(event, node); -} + /** @summary check parent */ + checkParent(prnt) { + if (prnt && (this.parent !== prnt)) { + this.hide(); + this.parent = prnt; + } + } -/** @summary Returns coordinates transformation func - * @private */ -function getEarthProjectionFunc(id) { - switch (id) { - // Aitoff2xy - case 1: return (l, b) => { - const DegToRad = Math.PI/180, - alpha2 = (l/2)*DegToRad, - delta = b*DegToRad, - r2 = Math.sqrt(2), - f = 2*r2/Math.PI, - cdec = Math.cos(delta), - denom = Math.sqrt(1.0 + cdec*Math.cos(alpha2)); - return { - x: cdec*Math.sin(alpha2)*2.0*r2/denom/f/DegToRad, - y: Math.sin(delta)*r2/denom/f/DegToRad - }; - }; - // mercator - case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI/2 + b/180*Math.PI)/2)) }; }; - // sinusoidal - case 3: return (l, b) => { return { x: l*Math.cos(b/180*Math.PI), y: b }; }; - // parabolic - case 4: return (l, b) => { return { x: l*(2.0*Math.cos(2*b/180*Math.PI/3) - 1), y: 180*Math.sin(b/180*Math.PI/3) }; }; - // Mollweide projection - case 5: return (l, b) => { - const theta0 = b * Math.PI/180; - let theta = theta0, num, den; - for (let i = 0; i < 100; i++) { - num = 2 * theta + Math.sin(2 * theta) - Math.PI * Math.sin(theta0); - den = 4 * (Math.cos(theta)**2); - if (den < 1e-20) { - theta = theta0; - break; - } - theta -= num / den; - if (Math.abs(num / den) < 1e-4) break; - } - return { - x: l * Math.cos(theta), - y: 90 * Math.sin(theta) - }; - }; + /** @summary set scaling factor */ + setScale(v) { + this.scale = v; } -} -/** @summary Unzoom preselected range for main histogram painter - * @desc Used with TGraph where Y zooming selected with fMinimum/fMaximum but histogram - * axis range can be wider. Or for normal histogram drawing when preselected range smaller than histogram range - * @private */ -function unzoomHistogramYRange(main) { - if (!isFunc(main?.getDimension) || main.getDimension() !== 1) return; + /** @summary extract position from event + * @desc can be used to process it later when event is gone */ + extract_pos(e) { + if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) + return e; + const res = { u: 0, l: 0 }; + if (this.abspos) { + res.l = e.pageX; + res.u = e.pageY; + } else { + res.l = e.offsetX; + res.u = e.offsetY; + } + res.l /= this.scale; + res.u /= this.scale; + return res; + } - const ymin = main.draw_content ? main.hmin : main.ymin, - ymax = main.draw_content ? main.hmax : main.ymax; + /** @summary Method used to define position of next tooltip + * @desc event is delivered from canvas, + * but position should be calculated relative to the element where tooltip is placed */ + pos(e) { + if (!this.tt) + return; - if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && - (ymin <= main.zoom_ymin) && (main.zoom_ymax <= ymax)) - main.zoom_ymin = main.zoom_ymax = 0; -} + const pos = this.extract_pos(e); + if (!this.abspos) { + const rect1 = this.parent.getBoundingClientRect(), + rect2 = this.canvas.getBoundingClientRect(); -// global, allow single drag at once -let drag_rect = null, drag_kind = '', drag_painter = null; + if ((rect1.left !== undefined) && (rect2.left !== undefined)) + pos.l += (rect2.left - rect1.left); -/** @summary Check if dragging performed currently - * @private */ -function is_dragging(painter, kind) { - return drag_rect && (drag_painter === painter) && (drag_kind === kind); -} + if ((rect1.top !== undefined) && (rect2.top !== undefined)) + pos.u += rect2.top - rect1.top; -/** @summary Add drag for interactive rectangular elements for painter - * @private */ -function addDragHandler(_painter, arg) { - if (!settings.MoveResize) return; + if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth) + pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3; - const painter = _painter, pp = painter.getPadPainter(); - if (pp?._fast_drawing || pp?.isBatchMode()) return; - // cleanup all drag elements when canvas is not ediatable - if (pp?.isEditable() === false) - arg.cleanup = true; + if (pos.u + this.tt.offsetHeight + 15 >= this.parent.offsetHeight) + pos.u = this.parent.offsetHeight - this.tt.offsetHeight - 15; - if (!isFunc(arg.getDrawG)) - arg.getDrawG = () => painter?.draw_g; + // one should find parent with non-static position, + // all absolute coordinates calculated relative to such node + let abs_parent = this.parent; + while (abs_parent) { + const style = getComputedStyle(abs_parent); + if (!style || (style.position !== 'static')) + break; + if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) + break; + abs_parent = abs_parent.parentNode; + } - function makeResizeElements(group, handler) { - function addElement(cursor, d) { - const clname = 'js_' + cursor.replace(/[-]/g, '_'); - let elem = group.selectChild('.' + clname); - if (arg.cleanup) return elem.remove(); - if (elem.empty()) elem = group.append('path').classed(clname, true); - elem.style('opacity', 0).style('cursor', cursor).attr('d', d); - if (handler) elem.call(handler); + if (abs_parent && (abs_parent !== this.parent)) { + const rect0 = abs_parent.getBoundingClientRect(); + pos.l += (rect1.left - rect0.left); + pos.u += (rect1.top - rect0.top); + } } - addElement('nw-resize', 'M2,2h15v-5h-20v20h5Z'); - addElement('ne-resize', `M${arg.width-2},2h-15v-5h20v20h-5 Z`); - addElement('sw-resize', `M2,${arg.height-2}h15v5h-20v-20h5Z`); - addElement('se-resize', `M${arg.width-2},${arg.height-2}h-15v5h20v-20h-5Z`); - - if (!arg.no_change_x) { - addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height-2*18)}h-5Z`); - addElement('e-resize', `M${arg.width+3},18h-5v${Math.max(0, arg.height-2*18)}h5Z`); - } - if (!arg.no_change_y) { - addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width-2*18)}v-5Z`); - addElement('s-resize', `M18,${arg.height+3}v-5h${Math.max(0, arg.width-2*18)}v5Z`); - } + this.tt.style.top = `${pos.u + 15}px`; + this.tt.style.left = `${pos.l + 3}px`; } - const complete_drag = (newx, newy, newwidth, newheight) => { - drag_painter = null; - drag_kind = ''; - if (drag_rect) { - drag_rect.remove(); - drag_rect = null; - } + /** @summary Show tooltip */ + show(v /* , mouse_pos, status_func */) { + let lines; + if (v && isObject(v) && (v.lines || v.line)) { + if (!v.only_status) + lines = v.line ? [v.line] : v.lines; + } else if (isStr(v)) + lines = [v]; - const draw_g = arg.getDrawG(); + const doc = this.parent.ownerDocument; - if (!draw_g) - return false; + if (!lines || !doc) + return this.hide(); - const oldx = arg.x, oldy = arg.y; + if (!this.tt) { + this.tt = doc.createElement('div'); + this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; width: auto; overflow: hidden; z-index: 101;'); + this.cont = doc.createElement('div'); + this.cont.setAttribute('style', 'display: block; padding: 5px; margin-left: 5px; font-size: 11px; line-height: 18px; background: #777; color: #fff;'); + this.tt.appendChild(this.cont); + this.parent.appendChild(this.tt); + } - if (arg.minwidth && newwidth < arg.minwidth) newwidth = arg.minwidth; - if (arg.minheight && newheight < arg.minheight) newheight = arg.minheight; + this.cont.innerText = ''; + lines.forEach(lbl => { + const p = doc.createElement('p'); + p.innerText = lbl; + p.setAttribute('style', 'padding: 0px; margin: 1px;'); + this.cont.appendChild(p); + }); + } - const change_size = (newwidth !== arg.width) || (newheight !== arg.height), - change_pos = (newx !== oldx) || (newy !== oldy); + /** @summary Hide tooltip */ + hide() { + if (this.tt) + this.parent.removeChild(this.tt); - arg.x = newx; arg.y = newy; arg.width = newwidth; arg.height = newheight; + this.tt = this.cont = null; + } - if (!arg.no_transform) - makeTranslate(draw_g, newx, newy); +} // class TooltipFor3D - setPainterTooltipEnabled(painter, true); +/** @summary Create OrbitControls for painter + * @private */ +function createOrbitControl(painter, camera, scene, renderer, lookat) { + const enable_zoom = settings.Zooming && settings.ZoomMouse, + enable_select = isFunc(painter.processMouseClick); - makeResizeElements(draw_g); + let control = null; - if (change_size || change_pos) { - if (change_size && isFunc(arg.resize)) - arg.resize(newwidth, newheight); + function control_mousedown(evnt) { + if (!control) + return; - if (change_pos && isFunc(arg.move)) - arg.move(newx, newy, newx - oldx, newy - oldy); + // function used to hide some events from orbit control and redirect them to zooming rect + if (control.mouse_zoom_mesh) { + evnt.stopImmediatePropagation(); + evnt.stopPropagation(); + return; + } - if (change_size || change_pos) { - if (arg.obj) { - const rect = arg.pad_rect ?? pp.getPadRect(); - arg.obj.fX1NDC = newx / rect.width; - arg.obj.fX2NDC = (newx + newwidth) / rect.width; - arg.obj.fY1NDC = 1 - (newy + newheight) / rect.height; - arg.obj.fY2NDC = 1 - newy / rect.height; - arg.obj.modified_NDC = true; // indicate that NDC was interactively changed, block in updated - } else if (isFunc(arg.move_resize)) - arg.move_resize(newx, newy, newwidth, newheight); + // only left-button is considered + if ((evnt.button !== undefined) && (evnt.button !== 0)) + return; + if ((evnt.buttons !== undefined) && (evnt.buttons !== 1)) + return; - if (isFunc(arg.redraw)) - arg.redraw(arg); + if (control.enable_zoom) { + control.mouse_zoom_mesh = control.detectZoomMesh(evnt); + if (control.mouse_zoom_mesh) { + // just block orbit control + evnt.stopImmediatePropagation(); + evnt.stopPropagation(); + return; } } - return change_size || change_pos; - }, - drag_move = drag().subject(Object), - drag_move_off = drag().subject(Object); + if (control.enable_select) + control.mouse_select_pnt = control.getMousePos(evnt, {}); + } - drag_move_off.on('start', null).on('drag', null).on('end', null); + function control_mouseup(evnt) { + if (!control) + return; - drag_move - .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) return; - if (isFunc(arg.is_disabled) && arg.is_disabled('move')) return; + if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.get3dZoomCoord) { + let kind = control.mouse_zoom_mesh.object.zoom, + pos1 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point, kind), + pos2 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point2, kind); - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip + if (pos1 > pos2) + [pos1, pos2] = [pos2, pos1]; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) + kind = 'y'; - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - pad_w: pad_rect.width - arg.width, - pad_h: pad_rect.height - arg.height, - drag_tm: new Date(), - path: `v${arg.height}h${arg.width}v${-arg.height}z`, - evnt_x: evnt.x, evnt_y: evnt.y - }; + // try to zoom + if ((pos1 < pos2) && control.painter.zoom(kind, pos1, pos2)) + control.mouse_zoom_mesh = null; + } - drag_painter = painter; - drag_kind = 'move'; - drag_rect = select(arg.getDrawG().node().parentNode).append('path') - .attr('d', `M${handle.acc_x1},${handle.acc_y1}${handle.path}`) - .style('cursor', 'move') - .style('pointer-events', 'none') // let forward double click to underlying elements - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', function(evnt) { - if (!is_dragging(painter, 'move')) return; + // if selection was drawn, it should be removed and picture rendered again + if (control.enable_zoom) + control.removeZoomMesh(); - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + if (control.enable_select && control.mouse_select_pnt) { + const pnt = control.getMousePos(evnt, {}), + same_pnt = (pnt.x === control.mouse_select_pnt.x) && (pnt.y === control.mouse_select_pnt.y); + delete control.mouse_select_pnt; - const handle = drag_rect.property('drag_handle'); + if (same_pnt) { + const intersects = control.getMouseIntersects(pnt); + control.painter.processMouseClick(pnt, intersects, evnt); + } + } + } - if (!arg.no_change_x) - handle.acc_x1 += evnt.dx; - if (!arg.no_change_y) - handle.acc_y1 += evnt.dy; + function render3DFired(_painter) { + if (_painter?.renderer === undefined) + return false; + // when timeout configured, object is prepared for rendering + return _painter.render_tmout !== undefined; + } - handle.x = Math.min(Math.max(handle.acc_x1, 0), handle.pad_w); - handle.y = Math.min(Math.max(handle.acc_y1, 0), handle.pad_h); + function control_mousewheel(evnt) { + if (!control) + return; - drag_rect.attr('d', `M${handle.x},${handle.y}${handle.path}`); - }).on('end', function(evnt) { - if (!is_dragging(painter, 'move')) return; + // try to handle zoom extra + if (render3DFired(control.painter) || control.mouse_zoom_mesh) { + evnt.preventDefault(); + evnt.stopPropagation(); + evnt.stopImmediatePropagation(); + return; // already fired redraw, do not react on the mouse wheel + } - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); + const intersect = control.detectZoomMesh(evnt); + if (!intersect) + return; - const handle = drag_rect.property('drag_handle'); + evnt.preventDefault(); + evnt.stopPropagation(); + evnt.stopImmediatePropagation(); - if (complete_drag(handle.x, handle.y, arg.width, arg.height) === false) { - const spent = (new Date()).getTime() - handle.drag_tm.getTime(); + if (isFunc(control.painter?.analyzeMouseWheelEvent)) { + let kind = intersect.object.zoom, + position = intersect.point[kind]; + const item = { name: kind, ignore: false }; - if (arg.ctxmenu && (spent > 600)) - showPainterMenu({ clientX: handle.evnt_x, clientY: handle.evnt_y, skip_close: 1 }, painter); - else if (arg.canselect && (spent <= 600)) - painter.getPadPainter()?.selectObjectPainter(painter); + // z changes from 0..2*size_z3d, others -size_x3d..+size_x3d + switch (kind) { + case 'x': + position = (position + control.painter.size_x3d) / 2 / control.painter.size_x3d; + break; + case 'y': + position = (position + control.painter.size_y3d) / 2 / control.painter.size_y3d; + break; + case 'z': + position = position / 2 / control.painter.size_z3d; + break; } - }); - const drag_resize = drag().subject(Object); + control.painter.analyzeMouseWheelEvent(evnt, item, position, false); - drag_resize - .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) return; - if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) return; + if ((kind === 'z') && intersect.object.use_y_for_z) + kind = 'y'; - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip + control.painter.zoom(kind, item.min, item.max); + } + } - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); + // assign own handler before creating OrbitControl - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - acc_x2: arg.x + arg.width, acc_y2: arg.y + arg.height, - pad_w: pad_rect.width, pad_h: pad_rect.height - }; + if (settings.Zooming && settings.ZoomWheel) + renderer.domElement.addEventListener('wheel', control_mousewheel); - drag_painter = painter; - drag_kind = 'resize'; - drag_rect = select(arg.getDrawG().node().parentNode) - .append('rect') - .style('cursor', select(this).style('cursor')) - .attr('x', handle.acc_x1) - .attr('y', handle.acc_y1) - .attr('width', handle.acc_x2 - handle.acc_x1) - .attr('height', handle.acc_y2 - handle.acc_y1) - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', function(evnt) { - if (!is_dragging(painter, 'resize')) return; + if (enable_zoom || enable_select) { + renderer.domElement.addEventListener('pointerdown', control_mousedown); + renderer.domElement.addEventListener('pointerup', control_mouseup); + } - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + control = new THREE.OrbitControls(camera, renderer.domElement); - const handle = drag_rect.property('drag_handle'), - elem = select(this); - let dx = evnt.dx, dy = evnt.dy; + control.enableDamping = false; + control.dampingFactor = 1.0; + control.enableZoom = true; + control.enableKeys = settings.HandleKeys; - if (arg.no_change_x) dx = 0; - if (arg.no_change_y) dy = 0; + if (lookat) { + control.target.copy(lookat); + control.target0.copy(lookat); + control.update(); + } - if (elem.classed('js_nw_resize')) { - handle.acc_x1 += dx; handle.acc_y1 += dy; - } else if (elem.classed('js_ne_resize')) { - handle.acc_x2 += dx; handle.acc_y1 += dy; - } else if (elem.classed('js_sw_resize')) { - handle.acc_x1 += dx; handle.acc_y2 += dy; - } else if (elem.classed('js_se_resize')) { - handle.acc_x2 += dx; handle.acc_y2 += dy; - } else if (elem.classed('js_w_resize')) - handle.acc_x1 += dx; - else if (elem.classed('js_n_resize')) - handle.acc_y1 += dy; - else if (elem.classed('js_e_resize')) - handle.acc_x2 += dx; - else if (elem.classed('js_s_resize')) - handle.acc_y2 += dy; + control.tooltip = new TooltipFor3D(painter.selectDom().node(), renderer.domElement); - const x1 = Math.max(0, handle.acc_x1), x2 = Math.min(handle.acc_x2, handle.pad_w), - y1 = Math.max(0, handle.acc_y1), y2 = Math.min(handle.acc_y2, handle.pad_h); + control.painter = painter; + control.camera = camera; + control.scene = scene; + control.renderer = renderer; + control.raycaster = new THREE.Raycaster(); + control.raycaster.params.Line.threshold = 10; + control.raycaster.params.Points.threshold = 5; + control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming + control.block_ctxt = false; // require to block context menu command appearing after control ends, required in chrome which inject contextmenu when key released + control.block_mousemove = false; // when true, tooltip or cursor will not react on mouse move + control.cursor_changed = false; + control.control_changed = false; + control.control_active = false; + control.mouse_ctxt = { x: 0, y: 0, on: false }; + control.enable_zoom = enable_zoom; + control.enable_select = enable_select; - handle.x = Math.min(x1, x2); - handle.y = Math.min(y1, y2); - handle.width = Math.abs(x2 - x1); - handle.height = Math.abs(y2 - y1); + control.cleanup = function() { + if (settings.Zooming && settings.ZoomWheel) + this.domElement.removeEventListener('wheel', control_mousewheel); + if (this.enable_zoom || this.enable_select) { + this.domElement.removeEventListener('pointerdown', control_mousedown); + this.domElement.removeEventListener('pointerup', control_mouseup); + } - drag_rect.attr('x', handle.x).attr('y', handle.y).attr('width', handle.width).attr('height', handle.height); - }).on('end', function(evnt) { - if (!is_dragging(painter, 'resize')) return; + this.domElement.removeEventListener('click', this.lstn_click); + this.domElement.removeEventListener('dblclick', this.lstn_dblclick); + this.domElement.removeEventListener('contextmenu', this.lstn_contextmenu); + this.domElement.removeEventListener('mousemove', this.lstn_mousemove); + this.domElement.removeEventListener('mouseleave', this.lstn_mouseleave); - evnt.sourceEvent.preventDefault(); + this.dispose(); // this is from OrbitControl itself - const handle = drag_rect.property('drag_handle'); + this.tooltip.hide(); + delete this.tooltip; + delete this.painter; + delete this.camera; + delete this.scene; + delete this.renderer; + delete this.raycaster; + delete this.mouse_zoom_mesh; + }; - complete_drag(handle.x, handle.y, handle.width, handle.height); - }); + control.hideTooltip = function() { + this.tooltip.hide(); + }; - if (!arg.only_resize) - arg.getDrawG().style('cursor', arg.cleanup ? null : 'move').call(arg.cleanup ? drag_move_off : drag_move); + control.getMousePos = function(evnt, mouse) { + mouse.x = ('offsetX' in evnt) ? evnt.offsetX : evnt.layerX; + mouse.y = ('offsetY' in evnt) ? evnt.offsetY : evnt.layerY; + mouse.clientX = evnt.clientX; + mouse.clientY = evnt.clientY; + return mouse; + }; - if (!arg.only_move) - makeResizeElements(arg.getDrawG(), drag_resize); -} + control.getOriginDirectionIntersects = function(origin, direction) { + this.raycaster.set(origin, direction); + let intersects = this.raycaster.intersectObjects(this.scene.children, true); + // painter may want to filter intersects + if (isFunc(this.painter.filterIntersects)) + intersects = this.painter.filterIntersects(intersects); + return intersects; + }; -const TooltipHandler = { + control.getMouseIntersects = function(mouse) { + // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer + if (!this.renderer) + return []; - /** @desc only canvas info_layer can be used while other pads can overlay - * @return layer where frame tooltips are shown */ - hints_layer() { - return this.getCanvPainter()?.getLayerSvg('info_layer') ?? select(null); - }, + const sz = (this.renderer instanceof THREE.SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new THREE.Vector2()), + pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 }; - /** @return true if tooltip is shown, use to prevent some other action */ - isTooltipShown() { - if (!this.tooltip_enabled || !this.isTooltipAllowed()) - return false; - const hintsg = this.hints_layer().selectChild('.objects_hints'); - return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadName(); - }, + this.camera.updateMatrix(); + this.camera.updateMatrixWorld(); + this.raycaster.setFromCamera(pnt, this.camera); + let intersects = this.raycaster.intersectObjects(this.scene.children, true); - /** @summary set tooltips enabled on/off */ - setTooltipEnabled(enabled) { - if (enabled !== undefined) - this.tooltip_enabled = enabled; - }, + // painter may want to filter intersects + if (isFunc(this.painter.filterIntersects)) + intersects = this.painter.filterIntersects(intersects); - /** @summary central function which let show selected hints for the object */ - processFrameTooltipEvent(pnt, evnt) { - if (pnt?.handler) { - // special use of interactive handler in the frame painter - const rect = this.draw_g?.selectChild('.main_layer'); - if (!rect || rect.empty()) - pnt = null; // disable - else if (pnt.touch && evnt) { - const pos = get_touch_pointers(evnt, rect.node()); - pnt = (pos && pos.length === 1) ? { touch: true, x: pos[0][0], y: pos[0][1] } : null; - } else if (evnt) { - const pos = pointer(evnt, rect.node()); - pnt = { touch: false, x: pos[0], y: pos[1] }; + return intersects; + }; + + control.detectZoomMesh = function(evnt) { + const mouse = this.getMousePos(evnt, {}), + intersects = this.getMouseIntersects(mouse); + if (intersects) { + for (let n = 0; n < intersects.length; ++n) { + if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) + return intersects[n]; } } - let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false, textheight = 11; - const hmargin = 3, wmargin = 3, hstep = 1.2, - frame_rect = this.getFrameRect(), - pp = this.getPadPainter(), - pad_width = pp?.getPadWidth(), - font = new FontHandler(160, textheight), - disable_tootlips = !this.isTooltipAllowed() || !this.tooltip_enabled; + return null; + }; - if (pnt) { - pnt.disabled = disable_tootlips; // indicate that highlighting is not required - pnt.painters = true; // get also painter + control.getInfoAtMousePosition = function(mouse_pos) { + const intersects = this.getMouseIntersects(mouse_pos); + let tip = null, p = null; + + for (let i = 0; i < intersects.length; ++i) { + const obj3d = intersects[i].object; + if (isFunc(obj3d?.tooltip)) { + tip = obj3d.tooltip(intersects[i]); + p = obj3d.tip_painter || obj3d.painter || tip?.$painter; + break; + } } - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp?.processPadTooltipEvent(pnt) ?? []; + if (tip && p) { + return { + obj: p.getObject(), + name: p.getObject().fName, + bin: tip.bin, cont: tip.value, + binx: tip.ix, biny: tip.iy, binz: tip.iz, + grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 + }; + } + }; - if (pp?._deliver_webcanvas_events && pp?.is_active_pad && pnt && isFunc(pp?.deliverWebCanvasEvent)) - pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints); + control.processDblClick = function(evnt) { + // first check if zoom mesh clicked + const zoom_intersect = this.detectZoomMesh(evnt); + if (zoom_intersect && this.painter) { + this.painter.unzoom(zoom_intersect.object.use_y_for_z ? 'y' : zoom_intersect.object.zoom); + return; + } - if (pnt?.touch) textheight = 15; + // then check if double-click handler assigned + const handler = this.painter?.getFramePainter()?.getDblclickHandler(); - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n]; - if (!hint) continue; + if (isFunc(handler)) { + const info = this.getInfoAtMousePosition(this.getMousePos(evnt, {})); + if (info) { + handler(info); + return; + } + } - if (hint.user_info !== undefined) - hint.painter?.provideUserTooltip(hint.user_info); + this.reset(); + }; - if (!hint.lines || (hint.lines.length === 0)) { - hints[n] = null; - continue; - } + control.changeEvent = function() { + this.mouse_ctxt.on = false; // disable context menu if any changes where done by orbit control + this.painter.render3D(0); + this.control_changed = true; + }; - // check if fully duplicated hint already exists - for (let k = 0; k < n; ++k) { - const hprev = hints[k]; - let diff = false; - if (!hprev || (hprev.lines.length !== hint.lines.length)) continue; - for (let l = 0; l < hint.lines.length && !diff; ++l) - if (hprev.lines[l] !== hint.lines[l]) diff = true; - if (!diff) { hints[n] = null; break; } - } - if (!hints[n]) continue; + control.startEvent = function() { + this.control_active = true; + this.block_ctxt = false; + this.mouse_ctxt.on = false; - nhints++; + this.tooltip.hide(); - if (hint.exact) nexact++; + // do not reset here, problem of events sequence in orbitcontrol + // it issue change/start/stop event when do zooming + // control.control_changed = false; + }; - hint.lines.forEach(line => { maxlen = Math.max(maxlen, line.length); }); + control.endEvent = function() { + this.control_active = false; + if (this.mouse_ctxt.on) { + this.mouse_ctxt.on = false; + this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); + } /* else if (this.control_changed) { + // react on camera change when required + } */ + this.control_changed = false; + }; - hint.height = Math.round(hint.lines.length * textheight * hstep + 2 * hmargin - textheight * (hstep - 1)); + control.mainProcessContextMenu = function(evnt) { + evnt.preventDefault(); + this.getMousePos(evnt, this.mouse_ctxt); + if (this.control_active) + this.mouse_ctxt.on = true; + else if (this.block_ctxt) + this.block_ctxt = false; + else + this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); + }; - if ((hint.color1 !== undefined) && (hint.color1 !== 'none')) { - if ((lastcolor1 !== 0) && (lastcolor1 !== hint.color1)) usecolor1 = true; - lastcolor1 = hint.color1; - } - } + control.contextMenu = function(/* pos, intersects */) { + // do nothing, function called when context menu want to be activated + }; - let path_name = null, same_path = hints.length > 1; - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n], p = hint?.lines ? hint.lines[0]?.lastIndexOf('/') : -1; - if (p > 0) { - const path = hint.lines[0].slice(0, p + 1); - if (path_name === null) - path_name = path; - else if (path_name !== path) - same_path = false; - } else - same_path = false; + control.setTooltipEnabled = function(on) { + this.block_mousemove = !on; + if (on === false) { + this.tooltip.hide(); + this.removeZoomMesh(); } + }; - const layer = this.hints_layer(), - show_only_best = nhints > 15, - coordinates = pnt ? Math.round(pnt.x) + ',' + Math.round(pnt.y) : ''; - let hintsg = layer.selectChild('.objects_hints'), // group with all tooltips - title = '', name = '', info = '', - hint = null, best_dist2 = 1e10, best_hint = null; + control.removeZoomMesh = function() { + if (this.mouse_zoom_mesh?.object.showSelection()) + this.painter.render3D(); + this.mouse_zoom_mesh = null; // in any case clear mesh, enable orbit control again + }; - // try to select hint with exact match of the position when several hints available - for (let k = 0; k < hints.length; ++k) { - if (!hints[k]) continue; - if (!hint) hint = hints[k]; + control.mainProcessMouseMove = function(evnt) { + if (!this.painter) + return; // protect when cleanup - // select exact hint if this is the only one - if (hints[k].exact && (nexact < 2) && (!hint || !hint.exact)) { hint = hints[k]; break; } + if (this.control_active && evnt.buttons && (evnt.buttons & 2)) + this.block_ctxt = true; // if right button in control was active, block next context menu - if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) continue; + if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) + return; - const dist2 = (pnt.x - hints[k].x) ** 2 + (pnt.y - hints[k].y) ** 2; - if (dist2 < best_dist2) { best_dist2 = dist2; best_hint = hints[k]; } - } + if (this.mouse_zoom_mesh) { + // when working with zoom mesh, need special handling - if ((!hint || !hint.exact) && (best_dist2 < 400)) hint = best_hint; + const zoom2 = this.detectZoomMesh(evnt), + pnt2 = (zoom2?.object === this.mouse_zoom_mesh.object) ? zoom2.point : this.mouse_zoom_mesh.object.globalIntersect(this.raycaster); - if (hint) { - name = (hint.lines && hint.lines.length > 1) ? hint.lines[0] : hint.name; - title = hint.title || ''; - info = hint.line; - if (!info && hint.lines) info = hint.lines.slice(1).join(' '); - } + if (pnt2) + this.mouse_zoom_mesh.point2 = pnt2; - this.showObjectStatus(name, title, info, coordinates); + if (pnt2 && this.painter.enable_highlight) { + if (this.mouse_zoom_mesh.object.showSelection(this.mouse_zoom_mesh.point, pnt2)) + this.painter.render3D(0); + } - // end of closing tooltips - if (!pnt || disable_tootlips || (hints.length === 0) || (maxlen === 0) || (show_only_best && !best_hint)) { - hintsg.remove(); + this.tooltip.hide(); return; } - // we need to set pointer-events=none for all elements while hints - // placed in front of so-called interactive rect in frame, used to catch mouse events + evnt.preventDefault(); - if (hintsg.empty()) { - hintsg = layer.append('svg:g') - .attr('class', 'objects_hints') - .style('pointer-events', 'none'); - } + // extract mouse position + this.tmout_mouse = this.getMousePos(evnt, {}); + this.tmout_ttpos = this.tooltip?.extract_pos(evnt); - let frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || ''; - if (!pp.iscan) { - frame_shift = getAbsPosInCanvas(this.getPadSvg(), frame_shift); - trans = `translate(${frame_shift.x},${frame_shift.y}) ${trans}`; + if (this.tmout_handle) { + clearTimeout(this.tmout_handle); + delete this.tmout_handle; } - // copy transform attributes from frame itself - hintsg.attr('transform', trans) - .property('last_point', pnt) - .property('hints_pad', this.getPadName()); + if (!this.mouse_tmout) + this.delayedProcessMouseMove(); + else + this.tmout_handle = setTimeout(() => this.delayedProcessMouseMove(), this.mouse_tmout); + }; - let viewmode = hintsg.property('viewmode') || '', - actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; + control.delayedProcessMouseMove = function() { + // remove handle - allow to trigger new timeout + delete this.tmout_handle; + if (!this.painter) + return; // protect when cleanup - if (show_only_best || (nhints === 1)) { - viewmode = 'single'; - posx += 15; + const mouse = this.tmout_mouse, + intersects = this.getMouseIntersects(mouse), + tip = this.processMouseMove(intersects); + + if (tip) { + let name = '', title = '', info = ''; + const coord = mouse ? mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0) : ''; + if (isStr(tip)) + info = tip; + else { + name = tip.name; + title = tip.title; + if (tip.line) + info = tip.line; + else if (tip.lines) { + info = tip.lines.slice(1).join(' '); + name = tip.lines[0]; + } + } + this.painter.showObjectStatus(name, title, info, coord); + } + + this.cursor_changed = false; + if (tip && this.painter?.isTooltipAllowed()) { + this.tooltip.checkParent(this.painter.selectDom().node()); + this.tooltip.show(tip, mouse); + this.tooltip.pos(this.tmout_ttpos); } else { - // if there are many hints, place them left or right + this.tooltip.hide(); + if (intersects) { + for (let n = 0; n < intersects.length; ++n) { + if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) + this.cursor_changed = true; + } + } + } - let bleft = 0.5, bright = 0.5; + getDocument().body.style.cursor = this.cursor_changed ? 'pointer' : 'auto'; + }; - if (viewmode === 'left') - bright = 0.7; - else if (viewmode === 'right') - bleft = 0.3; + control.mainProcessMouseLeave = function() { + if (!this.painter) + return; // protect when cleanup - if (posx <= bleft * frame_rect.width) { - viewmode = 'left'; - posx = 20; - } else if (posx >= bright * frame_rect.width) { - viewmode = 'right'; - posx = frame_rect.width - 60; - } else - posx = hintsg.property('startx'); + // do not enter main event at all + if (this.tmout_handle) { + clearTimeout(this.tmout_handle); + delete this.tmout_handle; } + this.tooltip.hide(); + if (isFunc(this.processMouseLeave)) + this.processMouseLeave(); + if (this.cursor_changed) { + getDocument().body.style.cursor = 'auto'; + this.cursor_changed = false; + } + }; - if (viewmode !== hintsg.property('viewmode')) { - hintsg.property('viewmode', viewmode); - hintsg.selectAll('*').remove(); + control.mainProcessDblClick = function(evnt) { + // suppress simple click handler if double click detected + if (this.single_click_tm) { + clearTimeout(this.single_click_tm); + delete this.single_click_tm; } + this.processDblClick(evnt); + }; - let curry = 10, // normal y coordinate - gapy = 10, // y coordinate, taking into account all gaps - gapminx = -1111, gapmaxx = -1111; - const minhinty = -frame_shift.y, - cp = this.getCanvPainter(), - maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y; + control.processClick = function(mouse_pos, kind) { + delete this.single_click_tm; - for (let n = 0; n < hints.length; ++n) { - let hint = hints[n], - group = hintsg.selectChild(`.painter_hint_${n}`); + if (kind === 1) { + const handler = this.painter?.getFramePainter()?.getClickHandler(); + if (isFunc(handler)) { + const info = this.getInfoAtMousePosition(mouse_pos); + if (info) { + handler(info); + return; + } + } + } - if (show_only_best && (hint !== best_hint)) - hint = null; + // method assigned in the Eve7 and used for object selection + if ((kind === 2) && isFunc(this.processSingleClick)) { + const intersects = this.getMouseIntersects(mouse_pos); + this.processSingleClick(intersects); + } - if (hint === null) { - group.remove(); - continue; + if (kind === 3) { + const intersects = this.getMouseIntersects(mouse_pos); + let objpainter = null; + for (let i = 0; !objpainter && (i < intersects.length); ++i) { + const obj3d = intersects[i].object; + objpainter = obj3d.painter || obj3d.parent?.painter; // check one top level + } + if (objpainter) { + // while axis painter not directly appears in the list of primitives, pad and canvas take from frame + const padp = this.painter?.getPadPainter(), + canvp = this.painter?.getCanvPainter(); + canvp?.producePadEvent('select', padp, objpainter); } + } + }; - const was_empty = group.empty(); + control.lstn_click = function(evnt) { + // ignore right-mouse click + if (evnt.detail === 2) + return; - if (was_empty) { - group = hintsg.append('svg:svg') - .attr('class', `painter_hint_${n}`) - .attr('opacity', 0) // use attribute, not style to make animation with d3.transition() - .style('overflow', 'hidden') - .style('pointer-events', 'none'); - } + if (this.single_click_tm) { + clearTimeout(this.single_click_tm); + delete this.single_click_tm; + } - if (viewmode === 'single') - curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y; - else { - for (let n = 0; (n < hints.length) && (gapy < maxhinty); ++n) { - const hint = hints[n]; - if (!hint) continue; - if ((hint.y >= gapy - 5) && (hint.y <= gapy + hint.height + 5)) { - gapy = hint.y + 10; - n = -1; - } - } - if ((gapminx === -1111) && (gapmaxx === -1111)) gapminx = gapmaxx = hint.x; - gapminx = Math.min(gapminx, hint.x); - gapmaxx = Math.min(gapmaxx, hint.x); - } + let kind = 0; + if (this.painter?.getFramePainter()?.getClickHandler()) + kind = 1; // user click handler + else if (this.processSingleClick && this.painter?.options?.mouse_click) + kind = 2; // eve7 click handler + else if (this.painter?.getCanvPainter()) + kind = 3; // select event for GED - group.attr('x', posx) - .attr('y', curry) - .property('curry', curry) - .property('gapy', gapy); + // if normal event, set longer timeout waiting if double click not detected + if (kind) + this.single_click_tm = setTimeout(this.processClick.bind(this, this.getMousePos(evnt, {}), kind), 300); + }.bind(control); - curry += hint.height + 5; - gapy += hint.height + 5; + control.addEventListener('change', () => control.changeEvent()); + control.addEventListener('start', () => control.startEvent()); + control.addEventListener('end', () => control.endEvent()); - if (!was_empty) - group.selectAll('*').remove(); + control.lstn_contextmenu = evnt => control.mainProcessContextMenu(evnt); + control.lstn_dblclick = evnt => control.mainProcessDblClick(evnt); + control.lstn_mousemove = evnt => control.mainProcessMouseMove(evnt); + control.lstn_mouseleave = () => control.mainProcessMouseLeave(); - group.attr('width', 60) - .attr('height', hint.height); + renderer.domElement.addEventListener('click', control.lstn_click); + renderer.domElement.addEventListener('dblclick', control.lstn_dblclick); + renderer.domElement.addEventListener('contextmenu', control.lstn_contextmenu); + renderer.domElement.addEventListener('mousemove', control.lstn_mousemove); + renderer.domElement.addEventListener('mouseleave', control.lstn_mouseleave); - const r = group.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', 60) - .attr('height', hint.height) - .style('fill', 'lightgrey') - .style('pointer-events', 'none'); + return control; +} - if (nhints > 1) { - const col = usecolor1 ? hint.color1 : hint.color2; - if (col && (col !== 'none')) - r.style('stroke', col); - } - r.attr('stroke-width', hint.exact ? 3 : 1); +/** @summary Method cleanup three.js object as much as possible. + * @desc Simplify JS engine to remove it from memory + * @private */ +function disposeThreejsObject(obj, only_childs) { + if (!obj) + return; - for (let l = 0; l < (hint.lines?.length ?? 0); l++) { - let line = hint.lines[l]; - if (l === 0 && path_name && same_path) - line = line.slice(path_name.length); - if (line) { - const txt = group.append('svg:text') - .attr('text-anchor', 'start') - .attr('x', wmargin) - .attr('y', hmargin + l * textheight * hstep) - .attr('dy', '.8em') - .style('fill', 'black') - .style('pointer-events', 'none') - .call(font.func) - .text(line), - box = getElementRect(txt, 'bbox'); + if (obj.children) { + for (let i = 0; i < obj.children.length; i++) + disposeThreejsObject(obj.children[i]); + } - actualw = Math.max(actualw, box.width); - } - } + if (only_childs) { + obj.children = []; + return; + } - function translateFn() { - // We only use 'd', but list d,i,a as params just to show can have them as params. - // Code only really uses d and t. - return function(/* d, i, a */) { - return function(t) { - return t < 0.8 ? '0' : (t - 0.8) * 5; - }; - }; - } + obj.children = undefined; - if (was_empty) { - if (settings.TooltipAnimation > 0) - group.transition().duration(settings.TooltipAnimation).attrTween('opacity', translateFn()); - else - group.attr('opacity', 1); - } + if (obj.geometry) { + obj.geometry.dispose(); + obj.geometry = undefined; + } + if (obj.material) { + if (obj.material.map) { + obj.material.map.dispose(); + obj.material.map = undefined; } + obj.material.dispose(); + obj.material = undefined; + } - actualw += 2 * wmargin; + // cleanup jsroot fields to simplify browser cleanup job + delete obj.painter; + delete obj.bins_index; + delete obj.tooltip; + delete obj.stack; // used in geom painter + delete obj.drawn_highlight; // special highlight object + // used in lego tooltips + delete obj.face_to_bins_index; + delete obj.tip_painter; + delete obj.handle; +} - const svgs = hintsg.selectAll('svg'); - if ((viewmode === 'right') && (posx + actualw > frame_rect.width - 20)) { - posx = frame_rect.width - actualw - 20; - svgs.attr('x', posx); - } +/** @summary Create LineSegments mesh (or only geometry) + * @desc If required, calculates lineDistance attribute for dashed geometries + * @private */ +function createLineSegments(arr, material, index = undefined, only_geometry = false) { + const geom = new THREE.BufferGeometry(); - if ((viewmode === 'single') && (posx + actualw > pad_width - frame_rect.x) && (posx > actualw + 20)) { - posx -= (actualw + 20); - svgs.attr('x', posx); - } + geom.setAttribute('position', arr instanceof Float32Array ? new THREE.BufferAttribute(arr, 3) : new THREE.Float32BufferAttribute(arr, 3)); + if (index) + geom.setIndex(new THREE.BufferAttribute(index, 1)); - // if gap not very big, apply gapy coordinate to open view on the histogram - if ((viewmode !== 'single') && (gapy < maxhinty) && (gapy !== curry)) { - if ((gapminx <= posx + actualw + 5) && (gapmaxx >= posx - 5)) - svgs.attr('y', function() { return select(this).property('gapy'); }); - } else if ((viewmode !== 'single') && (curry > maxhinty)) { - const shift = Math.max((maxhinty - curry - 10), minhinty); - if (shift < 0) - svgs.attr('y', function() { return select(this).property('curry') + shift; }); + if (material.isLineDashedMaterial) { + const v1 = new THREE.Vector3(), + v2 = new THREE.Vector3(); + let d = 0, distances; + + if (index) { + distances = new Float32Array(index.length); + for (let n = 0; n < index.length; n += 2) { + const i1 = index[n], i2 = index[n + 1]; + v1.set(arr[i1], arr[i1 + 1], arr[i1 + 2]); + v2.set(arr[i2], arr[i2 + 1], arr[i2 + 2]); + distances[n] = d; + d += v2.distanceTo(v1); + distances[n + 1] = d; + } + } else { + distances = new Float32Array(arr.length / 3); + for (let n = 0; n < arr.length; n += 6) { + v1.set(arr[n], arr[n + 1], arr[n + 2]); + v2.set(arr[n + 3], arr[n + 4], arr[n + 5]); + distances[n / 3] = d; + d += v2.distanceTo(v1); + distances[n / 3 + 1] = d; + } } + geom.setAttribute('lineDistance', new THREE.BufferAttribute(distances, 1)); + } - if (actualw > 10) - svgs.attr('width', actualw).select('rect').attr('width', actualw); + return only_geometry ? geom : new THREE.LineSegments(geom, material); +} - hintsg.property('startx', posx); +/** @summary Help structures for calculating Box mesh + * @private */ +const Box3D = { + Vertices: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 0), + new THREE.Vector3(1, 0, 1), new THREE.Vector3(1, 0, 0), + new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 1, 1), + new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)], + Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, + 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], + Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], + Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices + Crosses: [0, 7, 2, 5, 0, 3, 1, 2, 7, 3, 2, 6, 5, 6, 4, 7, 5, 1, 0, 4, 3, 4, 1, 6], // addresses Vertices + MeshSegments: undefined +}; - if (cp._highlight_connect && isFunc(cp.processHighlightConnect)) - cp.processHighlightConnect(hints); - }, +// these segments address vertices from the mesh, we can use positions from box mesh +Box3D.MeshSegments = (function() { + const arr = new Int32Array(Box3D.Segments.length); - /** @summary Assigns tooltip methods */ - assign(painter) { - Object.assign(painter, this, { tooltip_enabled: true }); + for (let n = 0; n < arr.length; ++n) { + for (let k = 0; k < Box3D.Indexes.length; ++k) { + if (Box3D.Segments[n] === Box3D.Indexes[k]) { + arr[n] = k; + break; + } + } } + return arr; +})(); -}, // TooltipHandler +/** + * @summary Abstract interactive control interface for 3D objects + * + * @abstract + * @private + */ -/** @summary Set of frame interactivity methods - * @private */ +class InteractiveControl { - FrameInteractive = { + cleanup() {} + extractIndex(/* intersect */) {} + setSelected(/* col, indx */) {} + setHighlight(/* col, indx */) {} + checkHighlightIndex(/* indx */) {} - /** @summary Adding basic interactivity */ - addBasicInteractivity() { - TooltipHandler.assign(this); +} // class InteractiveControl - if (!this._frame_rotate && !this._frame_fixpos) { - addDragHandler(this, { obj: this, x: this._frame_x, y: this._frame_y, width: this.getFrameWidth(), height: this.getFrameHeight(), - is_disabled: kind => { return (kind === 'move') && this.mode3d; }, - only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() }); - } - const top_rect = this.draw_g.selectChild('path'), - main_svg = this.draw_g.selectChild('.main_layer'); +/** + * @summary Class for creation of 3D points + * + * @private + */ - top_rect.style('pointer-events', 'visibleFill') // let process mouse events inside frame - .style('cursor', 'default'); // show normal cursor +class PointsCreator { - main_svg.style('pointer-events', 'visibleFill') - .style('cursor', 'default') - .property('handlers_set', 0); + /** @summary constructor + * @param {number} number - number of points + * @param {boolean} [iswebgl] - if WebGL is used + * @param {number} [scale] - scale factor */ + constructor(number, iswebgl = true, scale = 1) { + this.webgl = iswebgl; + this.scale = scale || 1; - const pp = this.getPadPainter(), - handlers_set = pp?._fast_drawing ? 0 : 1; + this.pos = new Float32Array(number * 3); + this.geom = new THREE.BufferGeometry(); + this.geom.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); + this.indx = 0; + } - if (main_svg.property('handlers_set') !== handlers_set) { - const close_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, null) : null, - mouse_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, { handler: true, touch: false }) : null; + /** @summary Add point */ + addPoint(x, y, z) { + this.pos[this.indx] = x; + this.pos[this.indx + 1] = y; + this.pos[this.indx + 2] = z; + this.indx += 3; + } - main_svg.property('handlers_set', handlers_set) - .on('mouseenter', mouse_handler) - .on('mousemove', mouse_handler) - .on('mouseleave', close_handler); + /** @summary Create points */ + createPoints(args) { + if (!isObject(args)) + args = { color: args }; + if (!args.color) + args.color = 'black'; - if (browser.touches) { - const touch_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, { handler: true, touch: true }) : null; + let k = 1; - main_svg.on('touchstart', touch_handler) - .on('touchmove', touch_handler) - .on('touchend', close_handler) - .on('touchcancel', close_handler); - } - } + // special dots + if (!args.style) + k = 1.1; + else if (args.style === 1) + k = 0.3; + else if (args.style === 6) + k = 0.5; + else if (args.style === 7) + k = 0.7; - main_svg.attr('x', 0) - .attr('y', 0) - .attr('width', this.getFrameWidth()) - .attr('height', this.getFrameHeight()); + const makePoints = texture => { + const material_args = { size: 3 * this.scale * k }; + if (texture) { + material_args.map = texture; + material_args.transparent = true; + } else + material_args.color = args.color || 'black'; - const hintsg = this.hints_layer().selectChild('.objects_hints'); - // if tooltips were visible before, try to reconstruct them after short timeout - if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadName())) - setTimeout(this.processFrameTooltipEvent.bind(this, hintsg.property('last_point'), null), 10); - }, + const pnts = new THREE.Points(this.geom, new THREE.PointsMaterial(material_args)); + pnts.nvertex = 1; + return pnts; + }; - /** @summary Add interactive handlers */ - async addFrameInteractivity(for_second_axes) { - const pp = this.getPadPainter(), - svg = this.getFrameSvg(); - if (pp?._fast_drawing || svg.empty()) - return this; + // this is plain creation of points, no need for texture loading - if (for_second_axes) { - // add extra handlers for second axes - const svg_x2 = svg.selectAll('.x2axis_container'), - svg_y2 = svg.selectAll('.y2axis_container'); - if (settings.ContextMenu) { - svg_x2.on('contextmenu', evnt => this.showContextMenu('x2', evnt)); - svg_y2.on('contextmenu', evnt => this.showContextMenu('y2', evnt)); - } - svg_x2.on('mousemove', evnt => this.showAxisStatus('x2', evnt)); - svg_y2.on('mousemove', evnt => this.showAxisStatus('y2', evnt)); - return this; + if (k !== 1) { + const res = makePoints(); + return this.noPromise ? res : Promise.resolve(res); } - const svg_x = svg.selectAll('.xaxis_container'), - svg_y = svg.selectAll('.yaxis_container'); - - this.can_zoom_x = this.can_zoom_y = settings.Zooming; + const handler = new TAttMarkerHandler({ style: args.style, color: args.color, size: 7 }), + w = handler.fill ? 1 : 7, + imgdata = `` + + `` + + '', + dataUrl = prSVG + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); + let promise; - if (pp?.options) { - if (pp.options.NoZoomX) this.can_zoom_x = false; - if (pp.options.NoZoomY) this.can_zoom_y = false; + if (isNodeJs()) { + promise = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => handle.default.loadImage(dataUrl).then(img => { + const canvas = handle.default.createCanvas(64, 64), + ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, 64, 64); + return new THREE.CanvasTexture(canvas); + })); + } else if (this.noPromise) { + // only for v6 support + return makePoints(new THREE.TextureLoader().load(dataUrl)); + } else { + promise = new Promise((resolveFunc, rejectFunc) => { + const loader = new THREE.TextureLoader(); + loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc(Error(`Fail to load ${dataUrl}`))); + }); } - if (!svg.property('interactive_set')) { - this.addFrameKeysHandler(); + return promise.then(makePoints); + } - this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches) - this.zoom_rect = null; - this.zoom_origin = null; // original point where zooming started - this.zoom_curr = null; // current point for zooming - } +} // class PointsCreator - if (settings.Zooming) { - if (settings.ZoomMouse) { - svg.on('mousedown', evnt => this.startRectSel(evnt)); - svg.on('dblclick', evnt => this.mouseDoubleClick(evnt)); - } - if (settings.ZoomWheel) - svg.on('wheel', evnt => this.mouseWheel(evnt)); - } - if (browser.touches && ((settings.Zooming && settings.ZoomTouch) || settings.ContextMenu)) - svg.on('touchstart', evnt => this.startTouchZoom(evnt)); +/** @summary Create material for 3D line + * @desc Takes into account dashed properties + * @private */ +function create3DLineMaterial(painter, arg, is_v7 = false) { + if (!painter || !arg) + return null; - if (settings.ContextMenu) { - if (browser.touches) { - svg_x.on('touchstart', evnt => this.startSingleTouchHandling('x', evnt)); - svg_y.on('touchstart', evnt => this.startSingleTouchHandling('y', evnt)); - } - svg.on('contextmenu', evnt => this.showContextMenu('', evnt)); - svg_x.on('contextmenu', evnt => this.showContextMenu('x', evnt)); - svg_y.on('contextmenu', evnt => this.showContextMenu('y', evnt)); - } + let color, lstyle, lwidth; + if (isStr(arg) || is_v7) { + color = painter.v7EvalColor(arg + 'color', 'black'); + lstyle = parseInt(painter.v7EvalAttr(arg + 'style', 0)); + lwidth = parseInt(painter.v7EvalAttr(arg + 'width', 1)); + } else { + color = painter.getColor(arg.fLineColor); + lstyle = arg.fLineStyle; + lwidth = arg.fLineWidth; + } - svg_x.on('mousemove', evnt => this.showAxisStatus('x', evnt)); - svg_y.on('mousemove', evnt => this.showAxisStatus('y', evnt)); + const style = lstyle ? getSvgLineStyle(lstyle) : '', + dash = style ? style.split(',') : [], + material = (dash && dash.length >= 2) + ? new THREE.LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) + : new THREE.LineBasicMaterial({ color }); - svg.property('interactive_set', true); + if (lwidth && (lwidth > 1)) + material.linewidth = lwidth; - return this; - }, + return material; +} + +/** @summary Create plain text geometry + * @private */ +function createTextGeometry(lbl, size) { + const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }; + if (THREE.REVISION > 162) + geom_args.depth = 0; + else + geom_args.height = 0; - /** @summary Add keys handler */ - addFrameKeysHandler() { - if (this.keys_handler || (typeof window === 'undefined')) return; + return new THREE.TextGeometry(lbl, geom_args); +} - this.keys_handler = evnt => this.processKeyPress(evnt); +class TextParseWrapper { - window.addEventListener('keydown', this.keys_handler, false); - }, + constructor(kind, parent, font_size) { + this.kind = kind ?? 'g'; + this.childs = []; + this.x = 0; + this.y = 0; + this.font_size = parent?.font_size ?? font_size; + this.stroke_width = parent?.stroke_width ?? 5; + parent?.childs.push(this); + } - /** @summary Handle key press */ - processKeyPress(evnt) { - const allowed = ['PageUp', 'PageDown', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'PrintScreen', 'Escape', '*'], - main = this.selectDom(), - pp = this.getPadPainter(); - let key = evnt.key; + append(kind) { + if (kind === 'svg:g') + return new TextParseWrapper('g', this); + if (kind === 'svg:text') + return new TextParseWrapper('text', this); + if (kind === 'svg:path') + return new TextParseWrapper('path', this); + console.warn('missing handle for svg', kind); + } - if (!settings.HandleKeys || main.empty() || (this.enabledKeys === false) || - (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) return false; + style(name, value) { + if ((name === 'stroke-width') && value) + this.stroke_width = Number.parseInt(value); + return this; + } - if (evnt.shiftKey) key = `Shift ${key}`; - if (evnt.altKey) key = `Alt ${key}`; - if (evnt.ctrlKey) key = `Ctrl ${key}`; + property(name, value) { + if (value === undefined) + return this[name]; + this[name] = value; + return this; + } - const zoom = { name: 'x', dleft: 0, dright: 0 }; + attr(name, value) { + const get = () => { + if (!value) + return ''; + const res = value[0]; + value = value.slice(1); + return res; + }, getN = skip => { + let p = 0; + while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) + p++; + const res = Number.parseInt(value.slice(0, p)); + value = value.slice(p); + if (skip) + get(); + return res; + }; - switch (key) { - case 'ArrowLeft': zoom.dleft = -1; zoom.dright = 1; break; - case 'ArrowRight': zoom.dleft = 1; zoom.dright = -1; break; - case 'Ctrl ArrowLeft': zoom.dleft = zoom.dright = -1; break; - case 'Ctrl ArrowRight': zoom.dleft = zoom.dright = 1; break; - case 'ArrowUp': zoom.name = 'y'; zoom.dleft = 1; zoom.dright = -1; break; - case 'ArrowDown': zoom.name = 'y'; zoom.dleft = -1; zoom.dright = 1; break; - case 'Ctrl ArrowUp': zoom.name = 'y'; zoom.dleft = zoom.dright = 1; break; - case 'Ctrl ArrowDown': zoom.name = 'y'; zoom.dleft = zoom.dright = -1; break; - case 'Escape': pp?.enlargePad(null, false, true); return true; - } + if ((name === 'font-size') && value) + this.font_size = Number.parseInt(value); + else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { + const arr = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')')).split(','); + this.x += arr[0] ? Number.parseInt(arr[0]) * 0.01 : 0; + this.y -= arr[1] ? Number.parseInt(arr[1]) * 0.01 : 0; + } else if ((name === 'x') && (this.kind === 'text')) + this.x += Number.parseInt(value) * 0.01; + else if ((name === 'y') && (this.kind === 'text')) + this.y -= Number.parseInt(value) * 0.01; + else if ((name === 'fill') && (this.kind === 'text')) + this.fill = value; + else if ((name === 'd') && (this.kind === 'path') && (value !== 'M0,0')) { + if (get() !== 'M') + return console.error('Not starts with M'); + let x1 = getN(true), y1 = getN(), next; + const pnts = [], add_line = (x2, y2) => { + const angle = Math.atan2(y2 - y1, x2 - x1), + dx = 0.5 * this.stroke_width * Math.sin(angle), + dy = -0.5 * this.stroke_width * Math.cos(angle); + // front side + pnts.push(x1 - dx, y1 - dy, 0, x2 - dx, y2 - dy, 0, x2 + dx, y2 + dy, 0, x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x1 + dx, y1 + dy, 0); + // back side + pnts.push(x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x2 - dx, y2 - dy, 0, x1 - dx, y1 - dy, 0, x1 + dx, y1 + dy, 0, x2 + dx, y2 + dy, 0); + x1 = x2; + y1 = y2; + }; - if (zoom.dleft || zoom.dright) { - if (!settings.Zooming) return false; - // in 3dmode with orbit control ignore simple arrows - if (this.mode3d && (key.indexOf('Ctrl') !== 0)) return false; - this.analyzeMouseWheelEvent(null, zoom, 0.5); - if (zoom.changed) { - this.zoom(zoom.name, zoom.min, zoom.max); - this.zoomChangedInteractive(zoom.name, true); + while ((next = get())) { + switch (next) { + case 'L': + add_line(getN(true), getN()); + continue; + case 'l': + add_line(x1 + getN(true), y1 + getN()); + continue; + case 'H': + add_line(getN(), y1); + continue; + case 'h': + add_line(x1 + getN(), y1); + continue; + case 'V': + add_line(x1, getN()); + continue; + case 'v': + add_line(x1, y1 + getN()); + continue; + case 'a': { + const rx = getN(true), ry = getN(true), + angle = getN(true) / 180 * Math.PI, flag1 = getN(true); + getN(true); // skip unused flag2 + const x2 = x1 + getN(true), + y2 = y1 + getN(), + x0 = x1 + rx * Math.cos(angle), + y0 = y1 + ry * Math.sin(angle); + let angle2 = Math.atan2(y0 - y2, x0 - x2); + if (flag1 && (angle2 < angle)) + angle2 += 2 * Math.PI; + else if (!flag1 && (angle2 > angle)) + angle2 -= 2 * Math.PI; + + for (let cnt = 0; cnt < 10; ++cnt) { + const a = angle + (angle2 - angle) / 10 * (cnt + 1); + add_line(x0 - rx * Math.cos(a), y0 - ry * Math.sin(a)); + } + continue; + } + default: + console.log('not supported path operator', next); + } } - evnt.stopPropagation(); - evnt.preventDefault(); - } else { - const func = pp?.findPadButton(key); - if (func) { - pp.clickPadButton(func); - evnt.stopPropagation(); - evnt.preventDefault(); + + if (pnts.length) { + const pos = new Float32Array(pnts); + this.geom = new THREE.BufferGeometry(); + this.geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + this.geom.scale(0.01, -0.01, 0.01); + this.geom.computeVertexNormals(); } } + return this; + } - return true; // just process any key press - }, - - /** @summary Function called when frame is clicked and object selection can be performed - * @desc such event can be used to select */ - processFrameClick(pnt, dblckick) { - const pp = this.getPadPainter(); - if (!pp) return; - - pnt.painters = true; // provide painters reference in the hints - pnt.disabled = true; // do not invoke graphics + text(v) { + if (this.kind === 'text') + this._text = v; + } - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp.processPadTooltipEvent(pnt); - let exact = null, res; - for (let k = 0; (k < hints.length) && !exact; ++k) { - if (hints[k] && hints[k].exact) - exact = hints[k]; + collect(geoms, geom_args, as_array) { + if (this._text) { + geom_args.size = Math.round(0.01 * this.font_size); + const geom = new THREE.TextGeometry(this._text, geom_args); + if (as_array) { + // this is latex parsing + // while three.js uses full height, make it more like normal fonts + geom.scale(1, 0.9, 1); + geom.translate(0, 0.0005 * this.font_size, 0); + } + geom.translate(this.x, this.y, 0); + geom._fill = this.fill; + geoms.push(geom); } - - if (exact) { - const handler = dblckick ? this._dblclick_handler : this._click_handler; - if (handler) res = handler(exact.user_info, pnt); + if (this.geom) { + this.geom.translate(this.x, this.y, 0); + this.geom._fill = this.fill; + geoms.push(this.geom); } - if (!dblckick) { - pp.selectObjectPainter(exact ? exact.painter : this, - { x: pnt.x + (this._frame_x || 0), y: pnt.y + (this._frame_y || 0) }); - } + this.childs.forEach(chld => { + chld.x += this.x; + chld.y += this.y; + chld.collect(geoms, geom_args, as_array); + }); + } - return res; - }, +} // class TextParseWrapper - /** @summary Check mouse moving */ - shiftMoveHanlder(evnt, pos0) { - if (evnt.buttons === this._shifting_buttons) { - const frame = this.getFrameSvg(), - pos = pointer(evnt, frame.node()), - main_svg = this.draw_g.selectChild('.main_layer'), - dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); - this._shifting_dx = dx; - this._shifting_dy = dy; +function createLatexGeometry(painter, lbl, size, as_array, use_latex = true) { + const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }, + font_size = size * 100, + node = new TextParseWrapper('g', null, font_size), + arg = { font_size, text: lbl, fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }, + geoms = []; - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); + if (THREE.REVISION > 162) + geom_args.depth = 0; + else + geom_args.height = 0; - evnt.preventDefault(); - evnt.stopPropagation(); - } - }, + if (!isPlainText(lbl)) { + produceLatex(painter, node, arg); + node.collect(geoms, geom_args, as_array); + } - /** @summary mouse up handler for shifting */ - shiftUpHanlder(evnt) { - evnt.preventDefault(); - - select(window).on('mousemove.shiftHandler', null) - .on('mouseup.shiftHandler', null); - - if ((this._shifting_dx !== undefined) && (this._shifting_dy !== undefined)) - this.performScalesShift(); - }, - - /** @summary Shift scales on defined positions */ - performScalesShift() { - const w = this.getFrameWidth(), h = this.getFrameHeight(), - main_svg = this.draw_g.selectChild('.main_layer'), - gr = this.getGrFuncs(), - xmin = gr.revertAxis('x', this._shifting_dx), - xmax = gr.revertAxis('x', this._shifting_dx + w), - ymin = gr.revertAxis('y', this._shifting_dy + h), - ymax = gr.revertAxis('y', this._shifting_dy); - - main_svg.attr('viewBox', `0 0 ${w} ${h}`); - - delete this._shifting_dx; - delete this._shifting_dy; - - setPainterTooltipEnabled(this, true); - - if (this.scales_ndim === 1) - this.zoomSingle('x', xmin, xmax); - else - this.zoom(xmin, xmax, ymin, ymax); - }, + if (!geoms.length) { + geom_args.size = size; + const res = new THREE.TextGeometry(translateLaTeX(lbl), geom_args); + return as_array ? [res] : res; + } - /** @summary Start mouse rect zooming */ - startRectSel(evnt) { - // ignore when touch selection is activated - if (this.zoom_kind > 100) return; + if (as_array) + return geoms; - const frame = this.getFrameSvg(), - pos = pointer(evnt, frame.node()); + if (geoms.length === 1) + return geoms[0]; - if ((evnt.buttons === 3) || (evnt.button === 1)) { - this.clearInteractiveElements(); - this._shifting_buttons = evnt.buttons; + let total_size = 0; + geoms.forEach(geom => { total_size += geom.getAttribute('position').array.length; }); - if (!evnt.$emul) { - select(window).on('mousemove.shiftHandler', evnt => this.shiftMoveHanlder(evnt, pos)) - .on('mouseup.shiftHandler', evnt => this.shiftUpHanlder(evnt), true); - } + const pos = new Float32Array(total_size), + norm = new Float32Array(total_size); + let indx = 0; - setPainterTooltipEnabled(this, false); - evnt.preventDefault(); - evnt.stopPropagation(); - return; + geoms.forEach(geom => { + const p1 = geom.getAttribute('position').array, + n1 = geom.getAttribute('normal').array; + for (let i = 0; i < p1.length; ++i, ++indx) { + pos[indx] = p1[i]; + norm[indx] = n1[i]; } + }); - // ignore all events from non-left button - if (evnt.button !== 0) return; + const fullgeom = new THREE.BufferGeometry(); + fullgeom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + fullgeom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); + return fullgeom; +} - evnt.preventDefault(); - this.clearInteractiveElements(); +/** @summary Build three.js object for the TLatex + * @private */ +function build3dlatex(obj, opt, painter, fp) { + if (!painter) + painter = new ObjectPainter(null, obj, opt); + const handle = painter.createAttText({ attr: obj }), + valign = handle.align % 10, + halign = (handle.align - valign) / 10, + text_size = handle.size > 1 ? handle.size : 2 * handle.size * (fp?.size_z3d || 100), + arr3d = createLatexGeometry(painter, obj.fTitle, text_size || 10, true, fp || (obj._typename === clTLatex)), + bb = new THREE.Box3().makeEmpty(); + + arr3d.forEach(geom => { + geom.computeBoundingBox(); + bb.expandByPoint(geom.boundingBox.max); + bb.expandByPoint(geom.boundingBox.min); + }); - const w = this.getFrameWidth(), h = this.getFrameHeight(); + let dx = 0, dy = 0; + if (halign === 2) + dx = 0.5 * (bb.max.x + bb.min.x); + else if (halign === 3) + dx = bb.max.x; + + if (valign === 2) + dy = 0.5 * (bb.max.y + bb.min.y); + else if (valign === 3) + dy = bb.max.y; + + const obj3d = new THREE.Object3D(), + materials = [], + getMaterial = color => { + if (!color) + color = 'black'; + if (!materials[color]) + materials[color] = new THREE.MeshBasicMaterial(getMaterialArgs(color, { vertexColors: false })); + return materials[color]; + }; - this.zoom_lastpos = pos; - this.zoom_curr = [Math.max(0, Math.min(w, pos[0])), Math.max(0, Math.min(h, pos[1]))]; + arr3d.forEach(geom => { + geom.translate(-dx, -dy, 0); + obj3d.add(new THREE.Mesh(geom, getMaterial(geom._fill || handle.color))); + }); - this.zoom_origin = [0, 0]; - this.zoom_second = false; + return arr3d.length === 1 ? obj3d.children[0] : obj3d; +} - if ((pos[0] < 0) || (pos[0] > w)) { - this.zoom_second = (pos[0] > w) && this.y2_handle; - this.zoom_kind = 3; // only y - this.zoom_origin[1] = this.zoom_curr[1]; - this.zoom_curr[0] = w; - this.zoom_curr[1] += 1; - } else if ((pos[1] < 0) || (pos[1] > h)) { - this.zoom_second = (pos[1] < 0) && this.x2_handle; - this.zoom_kind = 2; // only x - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_curr[0] += 1; - this.zoom_curr[1] = h; - } else { - this.zoom_kind = 1; // x and y - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_origin[1] = this.zoom_curr[1]; - } +var latex3d = /*#__PURE__*/Object.freeze({ +__proto__: null, +build3dlatex: build3dlatex, +createLatexGeometry: createLatexGeometry +}); - if (!evnt.$emul) { - select(window).on('mousemove.zoomRect', evnt => this.moveRectSel(evnt)) - .on('mouseup.zoomRect', evnt => this.endRectSel(evnt), true); - } +/** + * A math namespace - all functions can be exported from base/math.mjs. + * Also all these functions can be used with TFormula calculations + * @namespace Math + */ - this.zoom_rect = null; +/* eslint-disable curly */ +/* eslint-disable no-loss-of-precision */ +/* eslint-disable no-useless-assignment */ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-else-return */ +/* eslint-disable no-shadow */ +/* eslint-disable operator-assignment */ +/* eslint-disable @stylistic/js/comma-spacing */ +/* eslint-disable @stylistic/js/no-floating-decimal */ +/* eslint-disable @stylistic/js/space-in-parens */ - // disable tooltips in frame painter - setPainterTooltipEnabled(this, false); +// this can be improved later - evnt.stopPropagation(); +/* eslint-disable eqeqeq */ - if (this.zoom_kind !== 1) - return postponePromise(() => this.startLabelsMove(), 500); - }, +const kMACHEP = 1.11022302462515654042363166809e-16, + kMINLOG = -708.3964185322641, + kMAXLOG = 709.782712893383973096206318587, + kMAXSTIR = 108.116855767857671821730036754, + kBig = 4.503599627370496e15, + kBiginv = 2.22044604925031308085e-16, + kSqrt2 = 1.41421356237309515, + M_PI = 3.14159265358979323846264338328; - /** @summary Starts labels move */ - startLabelsMove() { - if (this.zoom_rect) return; +/** @summary Polynomialeval function + * @desc calculates a value of a polynomial of the form: + * a[0]x^N+a[1]x^(N-1) + ... + a[N] + * @memberof Math */ +function Polynomialeval(x, a, N) { + if (!N) + return a[0]; - const handle = (this.zoom_kind === 2) ? this.x_handle : this.y_handle; + let pom = a[0]; + for (let i = 1; i <= N; ++i) + pom = pom * x + a[i]; + return pom; +} - if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) return; +/** @summary Polynomial1eval function + * @desc calculates a value of a polynomial of the form: + * x^N+a[0]x^(N-1) + ... + a[N-1] + * @memberof Math */ +function Polynomial1eval(x, a, N) { + if (!N) + return a[0]; - if (handle.processLabelsMove('start', this.zoom_lastpos)) - this.zoom_labels = handle; - }, + let pom = x + a[0]; + for (let i = 1; i < N; ++i) + pom = pom * x + a[i]; + return pom; +} - /** @summary Process mouse rect zooming */ - moveRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) return; +/** @summary lgam function, logarithm from gamma + * @memberof Math */ +function lgam(x) { + let p, q, u, w, z; + const kMAXLGM = 2.556348e305, + LS2PI = 0.91893853320467274178, + A = [ + 8.11614167470508450300E-4, + -5950619042843014e-19, + 7.93650340457716943945E-4, + -0.002777777777300997, + 8.33333333333331927722E-2 + ], B = [ + -1378.2515256912086, + -38801.631513463784, + -331612.9927388712, + -1162370.974927623, + -1721737.0082083966, + -853555.6642457654 + ], C = [ + /* 1.00000000000000000000E0, */ + -351.81570143652345, + -17064.210665188115, + -220528.59055385445, + -1139334.4436798252, + -2532523.0717758294, + -2018891.4143353277 + ]; - evnt.preventDefault(); - const m = pointer(evnt, this.getFrameSvg().node()); + if ((x >= Number.MAX_VALUE) || (x == Number.POSITIVE_INFINITY)) + return Number.POSITIVE_INFINITY; - if (this.zoom_labels) - return this.zoom_labels.processLabelsMove('move', m); + if ( x < -34 ) { + q = -x; + w = lgam(q); + p = Math.floor(q); + if ( p === q ) // _unur_FP_same(p,q) + return Number.POSITIVE_INFINITY; + z = q - p; + if ( z > 0.5 ) { + p += 1.0; + z = p - q; + } + z = q * Math.sin( Math.PI * z ); + if ( z < 1e-300 ) + return Number.POSITIVE_INFINITY; + z = Math.log(Math.PI) - Math.log( z ) - w; + return z; + } + if ( x < 13.0 ) { + z = 1.0; + p = 0.0; + u = x; + while ( u >= 3.0 ) { + p -= 1.0; + u = x + p; + z *= u; + } + while ( u < 2.0 ) { + if ( u < 1e-300 ) + return Number.POSITIVE_INFINITY; + z /= u; + p += 1.0; + u = x + p; + } + if ( z < 0.0 ) { + z = -z; + } + if ( u === 2.0 ) + return Math.log(z); + p -= 2.0; + x = x + p; + p = x * Polynomialeval(x, B, 5 ) / Polynomial1eval( x, C, 6); + return Math.log(z) + p; + } + if ( x > kMAXLGM ) + return Number.POSITIVE_INFINITY; - this.zoom_lastpos[0] = m[0]; - this.zoom_lastpos[1] = m[1]; + q = ( x - 0.5 ) * Math.log(x) - x + LS2PI; + if ( x > 1.0e8 ) + return q; - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); + p = 1.0 / (x * x); + if ( x >= 1000.0 ) + q += ((7.9365079365079365079365e-4 * p + - 2.7777777777777777777778e-3) * p + + 0.0833333333333333333333) / x; + else + q += Polynomialeval( p, A, 4 ) / x; + return q; +} - switch (this.zoom_kind) { - case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break; - case 2: this.zoom_curr[0] = m[0]; break; - case 3: this.zoom_curr[1] = m[1]; break; - } +/** @summary Stirling formula for the gamma function + * @memberof Math */ +function stirf(x) { + let y, w, v; - const x = Math.min(this.zoom_origin[0], this.zoom_curr[0]), - y = Math.min(this.zoom_origin[1], this.zoom_curr[1]), - w = Math.abs(this.zoom_curr[0] - this.zoom_origin[0]), - h = Math.abs(this.zoom_curr[1] - this.zoom_origin[1]); + const STIR = [ + 7.87311395793093628397E-4, + -22954996161337813e-20, + -0.0026813261780578124, + 3.47222221605458667310E-3, + 8.33333333333482257126E-2 + ], SQTPI = Math.sqrt(2 * Math.PI); - if (!this.zoom_rect) { - // ignore small changes, can be switching to labels move - if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) return; + w = 1.0 / x; + w = 1.0 + w * Polynomialeval( w, STIR, 4 ); + y = Math.exp(x); - this.zoom_rect = this.getFrameSvg() - .append('rect') - .style('pointer-events', 'none') - .call(addHighlightStyle, true); - } + /* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ - this.zoom_rect.attr('x', x).attr('y', y).attr('width', w).attr('height', h); - }, + if ( x > kMAXSTIR ) + { /* Avoid overflow in pow() */ + v = Math.pow( x, 0.5 * x - 0.25 ); + y = v * (v / y); + } + else + { + y = Math.pow( x, x - 0.5 ) / y; + } + y = SQTPI * y * w; + return y; +} - /** @summary Finish mouse rect zooming */ - endRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) return; +/** @summary complementary error function + * @memberof Math */ +function erfc(a) { + const erfP = [ + 2.46196981473530512524E-10, + 5.64189564831068821977E-1, + 7.46321056442269912687E0, + 4.86371970985681366614E1, + 1.96520832956077098242E2, + 5.26445194995477358631E2, + 9.34528527171957607540E2, + 1.02755188689515710272E3, + 5.57535335369399327526E2 + ], erfQ = [ + 1.32281951154744992508E1, + 8.67072140885989742329E1, + 3.54937778887819891062E2, + 9.75708501743205489753E2, + 1.82390916687909736289E3, + 2.24633760818710981792E3, + 1.65666309194161350182E3, + 5.57535340817727675546E2 + ], erfR = [ + 5.64189583547755073984E-1, + 1.27536670759978104416E0, + 5.01905042251180477414E0, + 6.16021097993053585195E0, + 7.40974269950448939160E0, + 2.97886665372100240670E0 + ], erfS = [ + 2.26052863220117276590E0, + 9.39603524938001434673E0, + 1.20489539808096656605E1, + 1.70814450747565897222E1, + 9.60896809063285878198E0, + 3.36907645100081516050E0 + ]; - evnt.preventDefault(); + let p,q,x,y,z; - if (!evnt.$emul) { - select(window).on('mousemove.zoomRect', null) - .on('mouseup.zoomRect', null); - } + if ( a < 0.0 ) + x = -a; + else + x = a; - const m = pointer(evnt, this.getFrameSvg().node()); - let kind = this.zoom_kind, pr; + if ( x < 1.0 ) + return 1.0 - erf(a); - if (this.zoom_labels) - this.zoom_labels.processLabelsMove('stop', m); - else { - const changed = [this.can_zoom_x, this.can_zoom_y]; - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); + z = -a * a; - switch (this.zoom_kind) { - case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break; - case 2: this.zoom_curr[0] = m[0]; changed[1] = false; break; // only X - case 3: this.zoom_curr[1] = m[1]; changed[0] = false; break; // only Y - } + if (z < -kMAXLOG) + return (a < 0) ? 2.0 : 0.0; - const idx = this.swap_xy ? 1 : 0, idy = 1 - idx; - let xmin, xmax, ymin, ymax, isany = false, - namex = 'x', namey = 'y'; + z = Math.exp(z); - if (changed[idx] && (Math.abs(this.zoom_curr[idx] - this.zoom_origin[idx]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 2)) namex = 'x2'; - xmin = Math.min(this.revertAxis(namex, this.zoom_origin[idx]), this.revertAxis(namex, this.zoom_curr[idx])); - xmax = Math.max(this.revertAxis(namex, this.zoom_origin[idx]), this.revertAxis(namex, this.zoom_curr[idx])); - isany = true; - } + if ( x < 8.0 ) { + p = Polynomialeval( x, erfP, 8 ); + q = Polynomial1eval( x, erfQ, 8 ); + } else { + p = Polynomialeval( x, erfR, 5 ); + q = Polynomial1eval( x, erfS, 6 ); + } + y = (z * p) / q; - if (changed[idy] && (Math.abs(this.zoom_curr[idy] - this.zoom_origin[idy]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 3)) namey = 'y2'; - ymin = Math.min(this.revertAxis(namey, this.zoom_origin[idy]), this.revertAxis(namey, this.zoom_curr[idy])); - ymax = Math.max(this.revertAxis(namey, this.zoom_origin[idy]), this.revertAxis(namey, this.zoom_curr[idy])); - isany = true; - } + if (a < 0) + y = 2.0 - y; - if (namex === 'x2') { - this.zoomChangedInteractive(namex, true); - pr = this.zoomSingle(namex, xmin, xmax); - kind = 0; - } else if (namey === 'y2') { - this.zoomChangedInteractive(namey, true); - pr = this.zoomSingle(namey, ymin, ymax); - kind = 0; - } else if (isany) { - this.zoomChangedInteractive('x', true); - this.zoomChangedInteractive('y', true); - pr = this.zoom(xmin, xmax, ymin, ymax); - kind = 0; - } - } + if (y == 0) + return (a < 0) ? 2.0 : 0.0; - const pnt = (kind === 1) ? { x: this.zoom_origin[0], y: this.zoom_origin[1] } : null; + return y; +} - this.clearInteractiveElements(); +/** @summary error function + * @memberof Math */ +function erf(x) { + if (Math.abs(x) > 1.0) + return 1.0 - erfc(x); - // if no zooming was done, select active object instead - switch (kind) { - case 1: - this.processFrameClick(pnt); - break; - case 2: - this.getPadPainter()?.selectObjectPainter(this, null, 'xaxis'); - break; - case 3: - this.getPadPainter()?.selectObjectPainter(this, null, 'yaxis'); - break; - } + const erfT = [ + 9.60497373987051638749E0, + 9.00260197203842689217E1, + 2.23200534594684319226E3, + 7.00332514112805075473E3, + 5.55923013010394962768E4 + ], erfU = [ + 3.35617141647503099647E1, + 5.21357949780152679795E2, + 4.59432382970980127987E3, + 2.26290000613890934246E4, + 4.92673942608635921086E4 + ], z = x * x; - // return promise - if any - return pr; - }, + return x * Polynomialeval(z, erfT, 4) / Polynomial1eval(z, erfU, 5); +} - /** @summary Handle mouse double click on frame */ - mouseDoubleClick(evnt) { - evnt.preventDefault(); - const m = pointer(evnt, this.getFrameSvg().node()), - fw = this.getFrameWidth(), fh = this.getFrameHeight(); - this.clearInteractiveElements(); +/** @summary lognormal_cdf_c function + * @memberof Math */ +function lognormal_cdf_c(x, m, s, x0) { + if (x0 === undefined) + x0 = 0; + const z = (Math.log((x - x0)) - m) / (s * kSqrt2); + if (z > 1.) + return 0.5 * erfc(z); + else + return 0.5 * (1.0 - erf(z)); +} - const valid_x = (m[0] >= 0) && (m[0] <= fw), - valid_y = (m[1] >= 0) && (m[1] <= fh); +/** @summary lognormal_cdf_c function + * @memberof Math */ +function lognormal_cdf(x, m, s, x0 = 0) { + const z = (Math.log((x - x0)) - m) / (s * kSqrt2); + if (z < -1) + return 0.5 * erfc(-z); + else + return 0.5 * (1.0 + erf(z)); +} - if (valid_x && valid_y && this._dblclick_handler) - if (this.processFrameClick({ x: m[0], y: m[1] }, true)) return; +/** @summary normal_cdf_c function + * @memberof Math */ +function normal_cdf_c(x, sigma, x0 = 0) { + const z = (x - x0) / (sigma * kSqrt2); + if (z > 1.) + return 0.5 * erfc(z); + else + return 0.5 * (1. - erf(z)); +} - let kind = (this.can_zoom_x ? 'x' : '') + (this.can_zoom_y ? 'y' : '') + 'z'; - if (!valid_x) { - if (!this.can_zoom_y) return; - kind = this.swap_xy ? 'x' : 'y'; - if ((m[0] > fw) && this[kind+'2_handle']) kind += '2'; // let unzoom second axis - } else if (!valid_y) { - if (!this.can_zoom_x) return; - kind = this.swap_xy ? 'y' : 'x'; - if ((m[1] < 0) && this[kind+'2_handle']) kind += '2'; // let unzoom second axis - } - return this.unzoom(kind).then(changed => { - if (changed) return; - const pp = this.getPadPainter(), rect = this.getFrameRect(); - return pp?.selectObjectPainter(pp, { x: m[0] + rect.x, y: m[1] + rect.y, dbl: true }); - }); - }, +/** @summary normal_cdf function + * @memberof Math */ +function normal_cdf(x, sigma, x0 = 0) { + const z = (x - x0) / (sigma * kSqrt2); + if (z < -1) + return 0.5 * erfc(-z); + else + return 0.5 * (1.0 + erf(z)); +} - /** @summary Start touch zoom */ - startTouchZoom(evnt) { - evnt.preventDefault(); - evnt.stopPropagation(); +/** @summary log normal pdf + * @memberof Math */ +function lognormal_pdf(x, m, s, x0 = 0) { + if ((x - x0) <= 0) + return 0.0; + const tmp = (Math.log((x - x0)) - m) / s; + return 1.0 / ((x - x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) / 2); +} - // in case when zooming was started, block any other kind of events - // also prevent zooming together with active dragging - if ((this.zoom_kind !== 0) || drag_kind) - return; +/** @summary normal pdf + * @memberof Math */ +function normal_pdf(x, sigma = 1, x0 = 0) { + const tmp = (x - x0) / sigma; + return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); +} - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); +/** @summary gamma calculation + * @memberof Math */ +function gamma(x) { + let p, q, z, i, sgngam = 1; - // normally double-touch will be handled - // touch with single click used for context menu - if (arr.length === 1) { - // this is touch with single element + if (x >= Number.MAX_VALUE) + return x; - const now = new Date().getTime(); - let tmdiff = 1e10, dx = 100, dy = 100; + q = Math.abs(x); - if (this.last_touch_time && this.last_touch_pos) { - tmdiff = now - this.last_touch_time; - dx = Math.abs(arr[0][0] - this.last_touch_pos[0]); - dy = Math.abs(arr[0][1] - this.last_touch_pos[1]); + if ( q > 33.0 ) + { + if ( x < 0.0 ) + { + p = Math.floor(q); + if ( p == q ) + return Number.POSITIVE_INFINITY; + i = Math.round(p); + if ( (i & 1) == 0 ) + sgngam = -1; + z = q - p; + if ( z > 0.5 ) + { + p += 1.0; + z = q - p; + } + z = q * Math.sin(Math.PI * z); + if ( z == 0 ) + { + return sgngam > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; } + z = Math.abs(z); + z = Math.PI / (z * stirf(q)); + } + else + { + z = stirf(x); + } + return sgngam * z; + } - this.last_touch_time = now; - this.last_touch_pos = arr[0]; + z = 1.0; + while ( x >= 3.0 ) { + x -= 1.0; + z *= x; + } - if ((tmdiff < 500) && (dx < 20) && (dy < 20)) { - this.clearInteractiveElements(); - this.unzoom('xyz'); + let small = false; - delete this.last_touch_time; - } else if (settings.ContextMenu) - this.startSingleTouchHandling('', evnt); + while (( x < 0.0 ) && !small) { + if ( x > -1e-9 ) + small = true; + else { + z /= x; + x += 1.0; } + } - if ((arr.length !== 2) || !settings.Zooming || !settings.ZoomTouch) - return; - - this.clearInteractiveElements(); + while (( x < 2.0 ) && !small) { + if ( x < 1.e-9 ) + small = true; + else { + z /= x; + x += 1.0; + } + } - // clear single touch handler - this.endSingleTouchHandling(null); + if (small) { + if ( x == 0 ) + return Number.POSITIVE_INFINITY; + else + return z / ((1.0 + 0.5772156649015329 * x) * x); + } - const pnt1 = arr[0], pnt2 = arr[1], w = this.getFrameWidth(), h = this.getFrameHeight(); + if ( x == 2.0 ) + return z; - this.zoom_curr = [Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1])]; - this.zoom_origin = [Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1])]; - this.zoom_second = false; + const P = [ + 1.60119522476751861407E-4, + 1.19135147006586384913E-3, + 1.04213797561761569935E-2, + 4.76367800457137231464E-2, + 2.07448227648435975150E-1, + 4.94214826801497100753E-1, + 9.99999999999999996796E-1 + ], Q = [ + -23158187332412014e-21, + 5.39605580493303397842E-4, + -0.004456419138517973, + 1.18139785222060435552E-2, + 3.58236398605498653373E-2, + -0.23459179571824335, + 7.14304917030273074085E-2, + 1.00000000000000000320E0]; - if ((this.zoom_curr[0] < 0) || (this.zoom_curr[0] > w)) { - this.zoom_second = (this.zoom_curr[0] > w) && this.y2_handle; - this.zoom_kind = 103; // only y - this.zoom_curr[0] = 0; - this.zoom_origin[0] = w; - } else if ((this.zoom_origin[1] > h) || (this.zoom_origin[1] < 0)) { - this.zoom_second = (this.zoom_origin[1] < 0) && this.x2_handle; - this.zoom_kind = 102; // only x - this.zoom_curr[1] = 0; - this.zoom_origin[1] = h; - } else - this.zoom_kind = 101; // x and y + x -= 2.0; + p = Polynomialeval( x, P, 6 ); + q = Polynomialeval( x, Q, 7 ); + return z * p / q; +} - drag_kind = 'zoom'; // block other possible dragging +/** @summary ndtri function + * @memberof Math */ +function ndtri(y0) { + if ( y0 <= 0.0 ) + return Number.NEGATIVE_INFINITY; + if ( y0 >= 1.0 ) + return Number.POSITIVE_INFINITY; - setPainterTooltipEnabled(this, false); + const P0 = [ + -59.96335010141079, + 9.80010754185999661536E1, + -56.67628574690703, + 1.39312609387279679503E1, + -1.2391658386738125 + ], Q0 = [ + 1.95448858338141759834E0, + 4.67627912898881538453E0, + 8.63602421390890590575E1, + -225.46268785411937, + 2.00260212380060660359E2, + -82.03722561683334, + 1.59056225126211695515E1, + -1.1833162112133 + ], P1 = [ + 4.05544892305962419923E0, + 3.15251094599893866154E1, + 5.71628192246421288162E1, + 4.40805073893200834700E1, + 1.46849561928858024014E1, + 2.18663306850790267539E0, + -0.1402560791713545, + -0.03504246268278482, + -8574567851546854e-19 + ], Q1 = [ + 1.57799883256466749731E1, + 4.53907635128879210584E1, + 4.13172038254672030440E1, + 1.50425385692907503408E1, + 2.50464946208309415979E0, + -0.14218292285478779, + -0.03808064076915783, + -9332594808954574e-19 + ], P2 = [ + 3.23774891776946035970E0, + 6.91522889068984211695E0, + 3.93881025292474443415E0, + 1.33303460815807542389E0, + 2.01485389549179081538E-1, + 1.23716634817820021358E-2, + 3.01581553508235416007E-4, + 2.65806974686737550832E-6, + 6.23974539184983293730E-9 + ], Q2 = [ + 6.02427039364742014255E0, + 3.67983563856160859403E0, + 1.37702099489081330271E0, + 2.16236993594496635890E-1, + 1.34204006088543189037E-2, + 3.28014464682127739104E-4, + 2.89247864745380683936E-6, + 6.79019408009981274425E-9 + ], s2pi = 2.50662827463100050242e0, dd = 0.13533528323661269189; - this.zoom_rect = this.getFrameSvg().append('rect') - .attr('id', 'zoomRect') - .attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]) - .call(addHighlightStyle, true); + let code = 1, y = y0, x, y2, x1; - if (!evnt.$emul) { - select(window).on('touchmove.zoomRect', evnt => this.moveTouchZoom(evnt)) - .on('touchcancel.zoomRect', evnt => this.endTouchZoom(evnt)) - .on('touchend.zoomRect', evnt => this.endTouchZoom(evnt)); - } - }, + if (y > (1.0 - dd)) { + y = 1.0 - y; + code = 0; + } + if ( y > dd ) { + y = y - 0.5; + y2 = y * y; + x = y + y * (y2 * Polynomialeval( y2, P0, 4) / Polynomial1eval( y2, Q0, 8 )); + x = x * s2pi; + return x; + } + x = Math.sqrt(-2 * Math.log(y)); + const x0 = x - Math.log(x) / x, + z = 1.0 / x; + if ( x < 8.0 ) + x1 = z * Polynomialeval( z, P1, 8 ) / Polynomial1eval( z, Q1, 8 ); + else + x1 = z * Polynomialeval( z, P2, 8 ) / Polynomial1eval( z, Q2, 8 ); + x = x0 - x1; + if ( code != 0 ) + x = -x; + return x; +} - /** @summary Move touch zooming */ - moveTouchZoom(evnt) { - if (this.zoom_kind < 100) return; +/** @summary normal_quantile function + * @memberof Math */ +function normal_quantile(z, sigma) { + return sigma * ndtri(z); +} - evnt.preventDefault(); +/** @summary normal_quantile_c function + * @memberof Math */ +function normal_quantile_c(z, sigma) { + return -sigma * ndtri(z); +} - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); +/** @summary igamc function + * @memberof Math */ +function igamc(a,x) { + // LM: for negative values returns 0.0 + // This is correct if a is a negative integer since Gamma(-n) = +/- inf + if (a <= 0) + return 0.0; - if (arr.length !== 2) - return this.clearInteractiveElements(); + if (x <= 0) + return 1.0; - const pnt1 = arr[0], pnt2 = arr[1]; + if ((x < 1.0) || (x < a)) + return (1.0 - igam(a,x)); - if (this.zoom_kind !== 103) { - this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]); - this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]); + let ax = a * Math.log(x) - x - lgam(a); + if ( ax < -kMAXLOG ) + return 0.0; + + ax = Math.exp(ax); + + /* continued fraction */ + let y = 1.0 - a, + z = x + y + 1.0, + c = 0.0, + pkm2 = 1.0, + qkm2 = x, + pkm1 = x + 1.0, + qkm1 = z * x, + ans = pkm1 / qkm1, + yc, r, t, pk, qk; + + do { + c += 1.0; + y += 1.0; + z += 2.0; + yc = y * c; + pk = pkm1 * z - pkm2 * yc; + qk = qkm1 * z - qkm2 * yc; + if (qk) + { + r = pk / qk; + t = Math.abs( (ans - r) / r ); + ans = r; } - if (this.zoom_kind !== 102) { - this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]); - this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]); + else + t = 1.0; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + if ( Math.abs(pk) > kBig ) + { + pkm2 *= kBiginv; + pkm1 *= kBiginv; + qkm2 *= kBiginv; + qkm1 *= kBiginv; } + } while ( t > kMACHEP ); - this.zoom_rect.attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]); + return ans * ax; +} - if ((this.zoom_origin[0] - this.zoom_curr[0] > 10) || (this.zoom_origin[1] - this.zoom_curr[1] > 10)) - setPainterTooltipEnabled(this, false); +/** @summary igam function + * @memberof Math */ +function igam(a, x) { + // LM: for negative values returns 1.0 instead of zero + // This is correct if a is a negative integer since Gamma(-n) = +/- inf + if (a <= 0) + return 1.0; - evnt.stopPropagation(); - }, + if (x <= 0) + return 0.0; - /** @summary End touch zooming handler */ - endTouchZoom(evnt) { - if (this.zoom_kind < 100) return; + if ((x > 1.0) && (x > a)) + return 1.0 - igamc(a,x); - drag_kind = ''; // reset global flag + /* Compute x**a * exp(-x) / gamma(a) */ + let ax = a * Math.log(x) - x - lgam(a); + if ( ax < -kMAXLOG ) + return 0.0; - evnt.preventDefault(); - if (!evnt.$emul) { - select(window).on('touchmove.zoomRect', null) - .on('touchend.zoomRect', null) - .on('touchcancel.zoomRect', null); - } + ax = Math.exp(ax); - let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; - const xid = this.swap_xy ? 1 : 0, yid = 1 - xid, changed = [true, true]; + /* power series */ + let r = a, c = 1.0, ans = 1.0; - if (this.zoom_kind === 102) changed[1] = false; - if (this.zoom_kind === 103) changed[0] = false; + do { + r += 1.0; + c *= x / r; + ans += c; + } while ( c / ans > kMACHEP ); - if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 102)) namex = 'x2'; - xmin = Math.min(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - xmax = Math.max(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - isany = true; - } + return ans * ax / a; +} - if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 103)) namey = 'y2'; - ymin = Math.min(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - ymax = Math.max(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - isany = true; - } - this.clearInteractiveElements(); - delete this.last_touch_time; +/** @summary igami function + * @memberof Math */ +function igami(a, y0) { + // check the domain + if (a <= 0) { + console.error(`igami : Wrong domain for parameter a = ${a} (must be > 0)`); + return 0; + } + if (y0 <= 0) { + return Number.POSITIVE_INFINITY; + } + if (y0 >= 1) { + return 0; + } + const kMAXNUM = Number.MAX_VALUE, dithresh = 5.0 * kMACHEP; + let x0 = kMAXNUM, x1 = 0, x, yl = 0, yh = 1, y, d, lgm, i, dir; - if (namex === 'x2') { - this.zoomChangedInteractive(namex, true); - this.zoomSingle(namex, xmin, xmax); - } else if (namey === 'y2') { - this.zoomChangedInteractive(namey, true); - this.zoomSingle(namey, ymin, ymax); - } else if (isany) { - this.zoomChangedInteractive('x', true); - this.zoomChangedInteractive('y', true); - this.zoom(xmin, xmax, ymin, ymax); - } + /* approximation to inverse function */ + d = 1.0 / (9.0 * a); + y = 1.0 - d - ndtri(y0) * Math.sqrt(d); + x = a * y * y * y; - evnt.stopPropagation(); - }, + lgm = lgam(a); - /** @summary Analyze zooming with mouse wheel */ - analyzeMouseWheelEvent(event, item, dmin, test_ignore, second_side) { - // if there is second handle, use it - const handle2 = second_side ? this[item.name + '2_handle'] : null; - if (handle2) { - item.second = Object.assign({}, item); - return handle2.analyzeWheelEvent(event, dmin, item.second, test_ignore); + for ( i = 0; i < 10; ++i ) { + if ( x > x0 || x < x1 ) + break; + y = igamc(a,x); + if ( y < yl || y > yh ) + break; + if ( y < y0 ) { + x0 = x; + yl = y; } - const handle = this[item.name + '_handle']; - return handle?.analyzeWheelEvent(event, dmin, item, test_ignore); - }, - - /** @summary return true if default Y zooming should be enabled - * @desc it is typically for 2-Dim histograms or - * when histogram not draw, defined by other painters */ - isAllowedDefaultYZooming() { - if (this.self_drawaxes) return true; + else { + x1 = x; + yh = y; + } + /* compute the derivative of the function at this point */ + d = (a - 1.0) * Math.log(x) - x - lgm; + if ( d < -kMAXLOG ) + break; + d = -Math.exp(d); + /* compute the step to the next approximation of x */ + d = (y - y0) / d; + if ( Math.abs(d / x) < kMACHEP ) + return x; + x = x - d; + } + /* Resort to interval halving if Newton iteration did not converge. */ + d = 0.0625; + if ( x0 == kMAXNUM ) { + if ( x <= 0.0 ) + x = 1.0; + while ( x0 == kMAXNUM ) { + x = (1.0 + d) * x; + y = igamc( a, x ); + if ( y < y0 ) { + x0 = x; + yl = y; + break; + } + d = d + d; + } + } + d = 0.5; + dir = 0; - const pad_painter = this.getPadPainter(); - if (pad_painter?.painters) { - for (let k = 0; k < pad_painter.painters.length; ++k) { - const subpainter = pad_painter.painters[k]; - if (subpainter?.wheel_zoomy !== undefined) - return subpainter.wheel_zoomy; + for ( i = 0; i < 400; ++i ) { + x = x1 + d * (x0 - x1); + y = igamc( a, x ); + lgm = (x0 - x1) / (x1 + x0); + if ( Math.abs(lgm) < dithresh ) + break; + lgm = (y - y0) / y0; + if ( Math.abs(lgm) < dithresh ) + break; + if ( x <= 0.0 ) + break; + if ( y >= y0 ) { + x1 = x; + yh = y; + if ( dir < 0 ) { + dir = 0; + d = 0.5; + } + else if ( dir > 1 ) + d = 0.5 * d + 0.5; + else + d = (y0 - yl) / (yh - yl); + dir += 1; + } + else { + x0 = x; + yl = y; + if ( dir > 0 ) { + dir = 0; + d = 0.5; } + else if ( dir < -1 ) + d = 0.5 * d; + else + d = (y0 - yl) / (yh - yl); + dir -= 1; } + } + return x; +} - return false; - }, +/** @summary landau_pdf function + * @desc LANDAU pdf : algorithm from CERNLIB G110 denlan + * same algorithm is used in GSL + * @memberof Math */ +function landau_pdf(x, xi, x0 = 0) { + if (xi <= 0) + return 0; + const v = (x - x0) / xi; + let u, ue, us, denlan; + const p1 = [0.4259894875,-0.124976255, 0.03984243700, -0.006298287635, 0.001511162253], + q1 = [1.0 ,-0.3388260629, 0.09594393323, -0.01608042283, 0.003778942063], + p2 = [0.1788541609, 0.1173957403, 0.01488850518, -0.001394989411, 0.0001283617211], + q2 = [1.0 , 0.7428795082, 0.3153932961, 0.06694219548, 0.008790609714], + p3 = [0.1788544503, 0.09359161662,0.006325387654, 0.00006611667319,-2031049101e-15], + q3 = [1.0 , 0.6097809921, 0.2560616665, 0.04746722384, 0.006957301675], + p4 = [0.9874054407, 118.6723273, 849.2794360, -743.7792444, 427.0262186], + q4 = [1.0 , 106.8615961, 337.6496214, 2016.712389, 1597.063511], + p5 = [1.003675074, 167.5702434, 4789.711289, 21217.86767, -22324.9491], + q5 = [1.0 , 156.9424537, 3745.310488, 9834.698876, 66924.28357], + p6 = [1.000827619, 664.9143136, 62972.92665, 475554.6998, -5743609.109], + q6 = [1.0 , 651.4101098, 56974.73333, 165917.4725, -2815759.939], + a1 = [0.04166666667,-0.01996527778, 0.02709538966], + a2 = [-1.84556867,-4.284640743]; - /** @summary Handles mouse wheel event */ - mouseWheel(evnt) { - evnt.stopPropagation(); - evnt.preventDefault(); - this.clearInteractiveElements(); + if (v < -5.5) { + u = Math.exp(v + 1.0); + if (u < 1e-10) + return 0.0; + ue = Math.exp(-1 / u); + us = Math.sqrt(u); + denlan = 0.3989422803 * (ue / us) * (1 + (a1[0] + (a1[1] + a1[2] * u) * u) * u); + } else if (v < -1) { + u = Math.exp(-v - 1); + denlan = Math.exp(-u) * Math.sqrt(u) * + (p1[0] + (p1[1] + (p1[2] + (p1[3] + p1[4] * v) * v) * v) * v) / + (q1[0] + (q1[1] + (q1[2] + (q1[3] + q1[4] * v) * v) * v) * v); + } else if (v < 1) { + denlan = (p2[0] + (p2[1] + (p2[2] + (p2[3] + p2[4] * v) * v) * v) * v) / + (q2[0] + (q2[1] + (q2[2] + (q2[3] + q2[4] * v) * v) * v) * v); + } else if (v < 5) { + denlan = (p3[0] + (p3[1] + (p3[2] + (p3[3] + p3[4] * v) * v) * v) * v) / + (q3[0] + (q3[1] + (q3[2] + (q3[3] + q3[4] * v) * v) * v) * v); + } else if (v < 12) { + u = 1 / v; + denlan = u * u * (p4[0] + (p4[1] + (p4[2] + (p4[3] + p4[4] * u) * u) * u) * u) / + (q4[0] + (q4[1] + (q4[2] + (q4[3] + q4[4] * u) * u) * u) * u); + } else if (v < 50) { + u = 1 / v; + denlan = u * u * (p5[0] + (p5[1] + (p5[2] + (p5[3] + p5[4] * u) * u) * u) * u) / + (q5[0] + (q5[1] + (q5[2] + (q5[3] + q5[4] * u) * u) * u) * u); + } else if (v < 300) { + u = 1 / v; + denlan = u * u * (p6[0] + (p6[1] + (p6[2] + (p6[3] + p6[4] * u) * u) * u) * u) / + (q6[0] + (q6[1] + (q6[2] + (q6[3] + q6[4] * u) * u) * u) * u); + } else { + u = 1 / (v - v * Math.log(v) / (v + 1)); + denlan = u * u * (1 + (a2[0] + a2[1] * u) * u); + } + return denlan / xi; +} - const itemx = { name: 'x', reverse: this.reverse_x }, - itemy = { name: 'y', reverse: this.reverse_y, ignore: !this.isAllowedDefaultYZooming() }, - cur = pointer(evnt, this.getFrameSvg().node()), - w = this.getFrameWidth(), h = this.getFrameHeight(); +/** @summary Landau function + * @memberof Math */ +function Landau(x, mpv, sigma, norm) { + if (sigma <= 0) + return 0; + const den = landau_pdf((x - mpv) / sigma, 1, 0); + if (!norm) + return den; + return den / sigma; +} - if (this.can_zoom_x) - this.analyzeMouseWheelEvent(evnt, this.swap_xy ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); +/** @summary inc_gamma_c + * @memberof Math */ +function inc_gamma_c(a,x) { + return igamc(a,x); +} - if (this.can_zoom_y) - this.analyzeMouseWheelEvent(evnt, this.swap_xy ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); +/** @summary inc_gamma + * @memberof Math */ +function inc_gamma(a,x) { + return igam(a,x); +} - let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max); +/** @summary lgamma + * @memberof Math */ +function lgamma(z) { + return lgam(z); +} - if (itemx.changed) this.zoomChangedInteractive('x', true); - if (itemy.changed) this.zoomChangedInteractive('y', true); +/** @summary Probability density function of the beta distribution. + * @memberof Math */ +function beta_pdf(x, a, b) { + if (x < 0 || x > 1.0) + return 0; + if (x == 0 ) { + if (a < 1) + return Number.POSITIVE_INFINITY; + else if (a > 1) + return 0; + else if ( a == 1) + return b; // to avoid a nan from log(0)*0 + } + if (x == 1 ) { + if (b < 1) + return Number.POSITIVE_INFINITY; + else if (b > 1) + return 0; + else if ( b == 1) + return a; // to avoid a nan from log(0)*0 + } + return Math.exp(lgamma(a + b) - lgamma(a) - lgamma(b) + + Math.log(x) * (a - 1.) + Math.log1p(-x) * (b - 1.)); +} - if (itemx.second) { - pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max)); - if (itemx.second.changed) this.zoomChangedInteractive('x2', true); - } - if (itemy.second) { - pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max)); - if (itemy.second.changed) this.zoomChangedInteractive('y2', true); - } +/** @summary beta + * @memberof Math */ +function beta(x,y) { + return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); +} - return pr; - }, +/** @summary chisquared_cdf_c + * @memberof Math */ +function chisquared_cdf_c(x,r,x0 = 0) { + return inc_gamma_c(0.5 * r, 0.5 * (x - x0)); +} - /** @summary Show frame context menu */ - showContextMenu(kind, evnt, obj) { - // disable context menu left/right buttons clicked - if (evnt?.buttons === 3) - return evnt.preventDefault(); +/** @summary Continued fraction expansion #1 for incomplete beta integral + * @memberof Math */ +function incbcf(a,b,x) { + let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, + k1, k2, k3, k4, k5, k6, k7, k8, + r, t, ans, n; + const thresh = 3.0 * kMACHEP; - // ignore context menu when touches zooming is ongoing or - if (('zoom_kind' in this) && (this.zoom_kind > 100)) return; + k1 = a; + k2 = a + b; + k3 = a; + k4 = a + 1.0; + k5 = 1.0; + k6 = b - 1.0; + k7 = k4; + k8 = a + 2.0; - let pnt, menu_painter = this, exec_painter = null, - frame_corner = false, fp = null; // object used to show context menu - const svg_node = this.getFrameSvg().node(); + pkm2 = 0.0; + qkm2 = 1.0; + pkm1 = 1.0; + qkm1 = 1.0; + ans = 1.0; + r = 1.0; + n = 0; - if (isFunc(evnt?.stopPropagation)) { - evnt.preventDefault(); - evnt.stopPropagation(); // disable main context menu - const ms = pointer(evnt, svg_node), - tch = get_touch_pointers(evnt, svg_node); - if (tch.length === 1) - pnt = { x: tch[0][0], y: tch[0][1], touch: true }; - else if (ms.length === 2) - pnt = { x: ms[0], y: ms[1], touch: false }; - } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { - pnt = evnt; - const rect = svg_node.getBoundingClientRect(); - evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; - } - - if ((kind === 'painter') && obj) { - menu_painter = obj; - kind = ''; - } else if (kind === 'main') { - menu_painter = this.getMainPainter(true); - kind = ''; - } else if (!kind) { - const pp = this.getPadPainter(); - let sel = null; + do { + xk = -( x * k1 * k2 ) / ( k3 * k4 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; - fp = this; - if (pnt && pp) { - pnt.painters = true; // assign painter for every tooltip - const hints = pp.processPadTooltipEvent(pnt); - let bestdist = 1000; - for (let n = 0; n < hints.length; ++n) { - if (hints[n]?.menu) { - const dist = hints[n].menu_dist ?? 7; - if (dist < bestdist) { sel = hints[n].painter; bestdist = dist; } - } - } - } + xk = ( x * k5 * k6 ) / ( k7 * k8 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; - if (sel) menu_painter = sel; - else kind = 'frame'; + if ( qk != 0 ) + r = pk / qk; + if ( r != 0 ) + { + t = Math.abs( (ans - r) / r ); + ans = r; + } + else + t = 1.0; - if (pnt) frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); + if ( t < thresh ) + break; // goto cdone; - fp.setLastEventPos(pnt); - } else if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'pal')) { - exec_painter = this.getMainPainter(true); // histogram painter delivers items for axis menu + k1 += 1.0; + k2 += 1.0; + k3 += 2.0; + k4 += 2.0; + k5 += 1.0; + k6 -= 1.0; + k7 += 2.0; + k8 += 2.0; - if (this.v7_frame && isFunc(exec_painter?.v7EvalAttr)) - exec_painter = null; + if ((Math.abs(qk) + Math.abs(pk)) > kBig) { + pkm2 *= kBiginv; + pkm1 *= kBiginv; + qkm2 *= kBiginv; + qkm1 *= kBiginv; + } + if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { + pkm2 *= kBig; + pkm1 *= kBig; + qkm2 *= kBig; + qkm1 *= kBig; } + } + while ( ++n < 300 ); - if (!exec_painter) exec_painter = menu_painter; + // cdone: + return ans; +} - if (!isFunc(menu_painter?.fillContextMenu)) return; +/** @summary Continued fraction expansion #2 for incomplete beta integral + * @memberof Math */ +function incbd(a,b,x) { + const z = x / (1.0 - x), + thresh = 3.0 * kMACHEP; - this.clearInteractiveElements(); + let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, + k1, k2, k3, k4, k5, k6, k7, k8, + r, t, ans, n; - return createMenu(evnt, menu_painter).then(menu => { - let domenu = menu.painter.fillContextMenu(menu, kind, obj); + k1 = a; + k2 = b - 1.0; + k3 = a; + k4 = a + 1.0; + k5 = 1.0; + k6 = a + b; + k7 = a + 1.0; + k8 = a + 2.0; - // fill frame menu by default - or append frame elements when activated in the frame corner - if (fp && (!domenu || (frame_corner && (kind !== 'frame')))) - domenu = fp.fillContextMenu(menu); - - if (domenu) { - return exec_painter.fillObjectExecMenu(menu, kind).then(menu => { - // suppress any running zooming - setPainterTooltipEnabled(menu.painter, false); - return menu.show().then(() => setPainterTooltipEnabled(menu.painter, true)); - }); - } - }); - }, - - /** @summary Activate touch handling on frame - * @private */ - startSingleTouchHandling(kind, evnt) { - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - if (arr.length !== 1) return; - - evnt.preventDefault(); - evnt.stopPropagation(); - closeMenu(); - - const tm = new Date().getTime(); - - this._shifting_dx = 0; - this._shifting_dy = 0; - - setPainterTooltipEnabled(this, false); - - select(window).on('touchmove.singleTouch', kind ? null : evnt => this.moveTouchHandling(evnt, kind, arr[0])) - .on('touchcancel.singleTouch', evnt => this.endSingleTouchHandling(evnt, kind, arr[0], tm)) - .on('touchend.singleTouch', evnt => this.endSingleTouchHandling(evnt, kind, arr[0], tm)); - }, + pkm2 = 0.0; + qkm2 = 1.0; + pkm1 = 1.0; + qkm1 = 1.0; + ans = 1.0; + r = 1.0; + n = 0; + do { + xk = -( z * k1 * k2 ) / ( k3 * k4 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; - /** @summary Moving of touch pointer - * @private */ - moveTouchHandling(evnt, kind, pos0) { - const frame = this.getFrameSvg(), - main_svg = this.draw_g.selectChild('.main_layer'); - let pos; + xk = ( z * k5 * k6 ) / ( k7 * k8 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; - try { - pos = get_touch_pointers(evnt, frame.node())[0]; - } catch (err) { - pos = [0, 0]; - if (evnt?.changedTouches) - pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; + if ( qk != 0 ) + r = pk / qk; + if ( r != 0 ) + { + t = Math.abs( (ans - r) / r ); + ans = r; } + else + t = 1.0; - const dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); + if ( t < thresh ) + break; // goto cdone; - this._shifting_dx = dx; - this._shifting_dy = dy; + k1 += 1.0; + k2 -= 1.0; + k3 += 2.0; + k4 += 2.0; + k5 += 1.0; + k6 += 1.0; + k7 += 2.0; + k8 += 2.0; - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - }, + if ((Math.abs(qk) + Math.abs(pk)) > kBig) { + pkm2 *= kBiginv; + pkm1 *= kBiginv; + qkm2 *= kBiginv; + qkm1 *= kBiginv; + } + if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { + pkm2 *= kBig; + pkm1 *= kBig; + qkm2 *= kBig; + qkm1 *= kBig; + } + } + while ( ++n < 300 ); + // cdone: + return ans; +} - /** @summary Process end-touch event, which can cause content menu to appear - * @private */ - endSingleTouchHandling(evnt, kind, pos, tm) { - evnt?.preventDefault(); - evnt?.stopPropagation(); +/** @summary ROOT::Math::Cephes::pseries + * @memberof Math */ +function pseries(a,b,x) { + let s, t, u, v, n; - setPainterTooltipEnabled(this, true); + const ai = 1.0 / a; + u = (1.0 - b) * x; + v = u / (a + 1.0); + const t1 = v; + t = u; + n = 2.0; + s = 0.0; + const z = kMACHEP * ai; + while ( Math.abs(v) > z ) { + u = (n - b) * x / n; + t *= u; + v = t / (a + n); + s += v; + n += 1.0; + } + s += t1; + s += ai; - select(window).on('touchmove.singleTouch', null) - .on('touchcancel.singleTouch', null) - .on('touchend.singleTouch', null); + u = a * Math.log(x); + if ( (a + b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) + { + t = gamma(a + b) / (gamma(a) * gamma(b)); + s = s * t * Math.pow(x,a); + } + else + { + t = lgam(a + b) - lgam(a) - lgam(b) + u + Math.log(s); + if ( t < kMINLOG ) + s = 0.0; + else + s = Math.exp(t); + } + return s; +} - if (evnt === null) return; +/** @summary ROOT::Math::Cephes::incbet + * @memberof Math */ +function incbet(aa,bb,xx) { + let a, b, t, x, xc, w, y, flag; - if (Math.abs(this._shifting_dx) > 2 || Math.abs(this._shifting_dy) > 2) - this.performScalesShift(); - else if (new Date().getTime() - tm > 700) - this.showContextMenu(kind, { x: pos[0], y: pos[1] }); - }, + if ( aa <= 0.0 || bb <= 0.0 ) + return 0.0; - /** @summary Clear frame interactive elements */ - clearInteractiveElements() { - closeMenu(); - this.zoom_kind = 0; - this.zoom_rect?.remove(); - delete this.zoom_rect; - delete this.zoom_curr; - delete this.zoom_origin; - delete this.zoom_lastpos; - delete this.zoom_labels; + // LM: changed: for X > 1 return 1. + if (xx <= 0.0) + return 0.0; + if (xx >= 1.0) + return 1.0; - // enable tooltip in frame painter - setPainterTooltipEnabled(this, true); - }, + flag = 0; - /** @summary Assign frame interactive methods */ - assign(painter) { - Object.assign(painter, this); + /* - to test if that way is better for large b/ (comment out from Cephes version) + if ( (bb * xx) <= 1.0 && xx <= 0.95) + { + t = pseries(aa, bb, xx); + goto done; } -}; // FrameInterative +**/ + w = 1.0 - xx; + /* Reverse a and b if x is greater than the mean. */ + /* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ + if (xx > (aa / (aa + bb))) + { + flag = 1; + a = bb; + b = aa; + xc = xx; + x = w; + } + else + { + a = aa; + b = bb; + xc = w; + x = xx; + } -/** - * @summary Painter class for TFrame, main handler for interactivity - * @private - */ + if ( flag == 1 && (b * x) <= 1.0 && x <= 0.95) { + t = pseries(a, b, x); + // goto done; + } else { + /* Choose expansion for better convergence. */ + y = x * (a + b - 2.0) - (a - 1.0); + if ( y < 0.0 ) + w = incbcf( a, b, x ); + else + w = incbd( a, b, x ) / xc; -class TFramePainter extends ObjectPainter { + /* Multiply w by the factor + a b _ _ _ + x (1-x) | (a+b) / (a | (a) | (b)) . */ - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} tframe - TFrame object */ - constructor(dom, tframe) { - super(dom, (tframe && tframe.$dummy) ? null : tframe); - this.zoom_kind = 0; - this.mode3d = false; - this.shrink_frame_left = 0.0; - this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing - this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.ranges_set = false; - this.axes_drawn = false; - this.keys_handler = null; - this.projection = 0; // different projections + y = a * Math.log(x); + t = b * Math.log(xc); + if ( (a + b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) + { + t = Math.pow(xc,b); + t *= Math.pow(x,a); + t /= a; + t *= w; + t *= gamma(a + b) / (gamma(a) * gamma(b)); + // goto done; + } else { + /* Resort to logarithms. */ + y += t + lgam(a + b) - lgam(a) - lgam(b); + y += Math.log(w / a); + if ( y < kMINLOG ) + t = 0.0; + else + t = Math.exp(y); + } } - /** @summary Returns frame painter - object itself */ - getFramePainter() { return this; } - - /** @summary Returns true if it is ROOT6 frame - * @private */ - is_root6() { return true; } + // done: - /** @summary Returns frame or sub-objects, used in GED editor */ - getObject(place) { - if (place === 'xaxis') return this.xaxis; - if (place === 'yaxis') return this.yaxis; - return super.getObject(); + if (flag == 1) { + if ( t <= kMACHEP ) + t = 1.0 - kMACHEP; + else + t = 1.0 - t; } + return t; +} - /** @summary Set active flag for frame - can block some events - * @private */ - setFrameActive(on) { - this.enabledKeys = on && settings.HandleKeys; - // used only in 3D mode where control is used - if (this.control) - this.control.enableKeys = this.enabledKeys; - } +/** @summary copy of ROOT::Math::Cephes::incbi + * @memberof Math */ +function incbi(aa,bb,yy0) { + let a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt, + i, rflg, dir, nflg, ihalve = true; - /** @summary Shrink frame size - * @private */ - shrinkFrame(shrink_left, shrink_right) { - this.fX1NDC += shrink_left; - this.fX2NDC -= shrink_right; + // check the domain + if (aa <= 0) { + // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter a (must be > 0)'); + return 0; } - - /** @summary Set position of last context menu event */ - setLastEventPos(pnt) { - this.fLastEventPnt = pnt; + if (bb <= 0) { + // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter b (must be > 0)'); + return 0; } - /** @summary Return position of last event - * @private */ - getLastEventPos() { return this.fLastEventPnt; } - - /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.projection); } + const process_done = () => { + if ( rflg ) { + if ( x <= kMACHEP ) + x = 1.0 - kMACHEP; + else + x = 1.0 - x; + } + return x; + }; - /** @summary Rcalculate frame ranges using specified projection functions */ - recalculateRange(Proj, change_x, change_y) { - this.projection = Proj || 0; + i = 0; + if ( yy0 <= 0 ) + return 0.0; + if ( yy0 >= 1.0 ) + return 1.0; + x0 = 0.0; + yl = 0.0; + x1 = 1.0; + yh = 1.0; + nflg = 0; - if ((this.projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { - console.warn(`Mercator Projection: Latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.projection = 0; - } + if ( aa <= 1.0 || bb <= 1.0 ) + { + dithresh = 1.0e-6; + rflg = 0; + a = aa; + b = bb; + y0 = yy0; + x = a / (a + b); + y = incbet( a, b, x ); + // goto ihalve; // will start + } + else + { + dithresh = 1.0e-4; + /* approximation to inverse function */ - const func = this.getProjectionFunc(); - if (!func) return; + yp = -ndtri(yy0); - const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; - if (this.scale_xmin < 0 && this.scale_xmax > 0) { - pnts.push(func(0, this.scale_ymin)); - pnts.push(func(0, this.scale_ymax)); + if ( yy0 > 0.5 ) + { + rflg = 1; + a = bb; + b = aa; + y0 = 1.0 - yy0; + yp = -yp; } - if (this.scale_ymin < 0 && this.scale_ymax > 0) { - pnts.push(func(this.scale_xmin, 0)); - pnts.push(func(this.scale_xmax, 0)); + else + { + rflg = 0; + a = aa; + b = bb; + y0 = yy0; } - this.original_xmin = this.scale_xmin; - this.original_xmax = this.scale_xmax; - this.original_ymin = this.scale_ymin; - this.original_ymax = this.scale_ymax; - - if (change_x) - this.scale_xmin = this.scale_xmax = pnts[0].x; - if (change_y) - this.scale_ymin = this.scale_ymax = pnts[0].y; - - for (let n = 1; n < pnts.length; ++n) { - if (change_x) { - this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); - this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); - } - if (change_y) { - this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); - this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); - } + lgm = (yp * yp - 3.0) / 6.0; + x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); + d = yp * Math.sqrt( x + lgm ) / x + - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) + * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); + d = 2.0 * d; + if ( d < kMINLOG ) { + // x = 1.0; + // goto under; + x = 0.0; + return process_done(); } + x = a / (a + b * Math.exp(d)); + y = incbet( a, b, x ); + yp = (y - y0) / y0; + if ( Math.abs(yp) < 0.2 ) + ihalve = false; // instead goto newt; exclude ihalve for the first time } - /** @summary Configure frame axes ranges */ - setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax, hpainter) { - this.ranges_set = true; - - this.xaxis = xaxis; - this.xmin = xmin; - this.xmax = xmax; - - this.yaxis = yaxis; - this.ymin = ymin; - this.ymax = ymax; - - this.zaxis = zaxis; - this.zmin = zmin; - this.zmax = zmax; - - if (hpainter?.check_pad_range) { - delete hpainter.check_pad_range; - const ndim = hpainter.getDimension(); - this.applyAxisZoom('x'); - if (ndim > 1) - this.applyAxisZoom('y'); - if (ndim > 2) - this.applyAxisZoom('z'); - } - - if (hpainter && !hpainter._checked_zooming) { - hpainter._checked_zooming = true; + let mainloop = 1000; - if (hpainter.options.minimum !== kNoZoom) { - this.zoom_zmin = hpainter.options.minimum; - this.zoom_zmax = this.zmax; + // endless loop until coverage + while (mainloop-- > 0) { + /* Resort to interval halving if not close enough. */ + // ihalve: + if (ihalve) { + dir = 0; + di = 0.5; + for ( i = 0; i < 100; i++ ) + { + if ( i != 0 ) + { + x = x0 + di * (x1 - x0); + if ( x == 1.0 ) + x = 1.0 - kMACHEP; + if ( x == 0.0 ) + { + di = 0.5; + x = x0 + di * (x1 - x0); + if ( x == 0.0 ) + return process_done(); // goto under; + } + y = incbet( a, b, x ); + yp = (x1 - x0) / (x1 + x0); + if ( Math.abs(yp) < dithresh ) + break; // goto newt; + yp = (y - y0) / y0; + if ( Math.abs(yp) < dithresh ) + break; // goto newt; + } + if ( y < y0 ) + { + x0 = x; + yl = y; + if ( dir < 0 ) + { + dir = 0; + di = 0.5; + } + else if ( dir > 3 ) + di = 1.0 - (1.0 - di) * (1.0 - di); + else if ( dir > 1 ) + di = 0.5 * di + 0.5; + else + di = (y0 - y) / (yh - yl); + dir += 1; + if ( x0 > 0.75 ) + { + if ( rflg == 1 ) + { + rflg = 0; + a = aa; + b = bb; + y0 = yy0; + } + else + { + rflg = 1; + a = bb; + b = aa; + y0 = 1.0 - yy0; + } + x = 1.0 - x; + y = incbet( a, b, x ); + x0 = 0.0; + yl = 0.0; + x1 = 1.0; + yh = 1.0; + continue; // goto ihalve; + } + } + else + { + x1 = x; + if ( rflg == 1 && x1 < kMACHEP ) + { + x = 0.0; + return process_done(); // goto done; + } + yh = y; + if ( dir > 0 ) + { + dir = 0; + di = 0.5; + } + else if ( dir < -3 ) + di = di * di; + else if ( dir < -1 ) + di = 0.5 * di; + else + di = (y - y0) / (yh - yl); + dir -= 1; + } } - if (hpainter.options.maximum !== kNoZoom) { - this.zoom_zmax = hpainter.options.maximum; - if (this.zoom_zmin === undefined) this.zoom_zmin = this.zmin; + // math_error( 'incbi', PLOSS ); + if ( x0 >= 1.0 ) { + x = 1.0 - kMACHEP; + return process_done(); // goto done; } - } - } + if ( x <= 0.0 ) { + // math_error( 'incbi', UNDERFLOW ); + x = 0.0; + return process_done(); // goto done; + } + break; // if here, break ihalve + } // end of ihalve - /** @summary Configure secondary frame axes ranges */ - setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { - if (second_x) { - this.x2axis = xaxis; - this.x2min = xmin; - this.x2max = xmax; - } - if (second_y) { - this.y2axis = yaxis; - this.y2min = ymin; - this.y2max = ymax; - } - } + ihalve = true; // enter loop next time - /** @summary Retuns associated axis object */ - getAxis(name) { - switch (name) { - case 'x': return this.xaxis; - case 'y': return this.yaxis; - case 'z': return this.zaxis; - case 'x2': return this.x2axis; - case 'y2': return this.y2axis; - } - return null; - } + // newt: - /** @summary Apply axis zooming from pad user range - * @private */ - applyPadUserRange(pad, name) { - if (!pad) return; + if ( nflg ) + return process_done(); // goto done; + nflg = 1; + lgm = lgam(a + b) - lgam(a) - lgam(b); - // seems to be, not allways user range calculated - let umin = pad[`fU${name}min`], - umax = pad[`fU${name}max`], - eps = 1e-7; - - if (name === 'x') { - if ((Math.abs(pad.fX1) > eps) || (Math.abs(pad.fX2 - 1) > eps)) { - const dx = pad.fX2 - pad.fX1; - umin = pad.fX1 + dx*pad.fLeftMargin; - umax = pad.fX2 - dx*pad.fRightMargin; + for ( i = 0; i < 8; i++ ) + { + /* Compute the function at this point. */ + if ( i != 0 ) + y = incbet(a,b,x); + if ( y < yl ) + { + x = x0; + y = yl; } - } else { - if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { - const dy = pad.fY2 - pad.fY1; - umin = pad.fY1 + dy*pad.fBottomMargin; - umax = pad.fY2 - dy*pad.fTopMargin; + else if ( y > yh ) + { + x = x1; + y = yh; + } + else if ( y < y0 ) + { + x0 = x; + yl = y; + } + else + { + x1 = x; + yh = y; + } + if ( x == 1.0 || x == 0.0 ) + break; + /* Compute the derivative of the function at this point. */ + d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0 - x) + lgm; + if ( d < kMINLOG ) + return process_done(); // goto done; + if ( d > kMAXLOG ) + break; + d = Math.exp(d); + /* Compute the step to the next approximation of x. */ + d = (y - y0) / d; + xt = x - d; + if ( xt <= x0 ) + { + y = (x - x0) / (x1 - x0); + xt = x0 + 0.5 * y * (x - x0); + if ( xt <= 0.0 ) + break; + } + if ( xt >= x1 ) + { + y = (x1 - x) / (x1 - x0); + xt = x1 - 0.5 * y * (x1 - x); + if ( xt >= 1.0 ) + break; } + x = xt; + if ( Math.abs(d / x) < 128.0 * kMACHEP ) + return process_done(); // goto done; } + /* Did not converge. */ + dithresh = 256.0 * kMACHEP; + } // endless loop instead of // goto ihalve; - if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax-1) < eps)) return; + // done: - if (pad[`fLog${name}`] > 0) { - umin = Math.exp(umin * Math.log(10)); - umax = Math.exp(umax * Math.log(10)); - } + return process_done(); +} - let aname = name; - if (this.swap_xy) aname = (name === 'x') ? 'y' : 'x'; - const smin = this[`scale_${aname}min`], - smax = this[`scale_${aname}max`]; +/** @summary Calculates the normalized (regularized) incomplete beta function. + * @memberof Math */ +function inc_beta(x,a,b) { + return incbet(a,b,x); +} - eps = (smax - smin) * 1e-7; +const BetaIncomplete = inc_beta; - if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { - this[`zoom_${aname}min`] = umin; - this[`zoom_${aname}max`] = umax; - } - } +/** @summary ROOT::Math::beta_quantile + * @memberof Math */ +function beta_quantile(z,a,b) { + return incbi(a,b,z); +} - /** @summary Apply zooming from TAxis attributes */ - applyAxisZoom(name) { - if (this.zoomChangedInteractive(name)) return; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; +/** @summary Complement of the cumulative distribution function of the beta distribution. + * @memberof Math */ +function beta_cdf_c(x,a,b) { + return inc_beta(1 - x, b, a); +} - const axis = this.getAxis(name); +/** @summary chisquared_cdf + * @memberof Math */ +function chisquared_cdf(x,r,x0 = 0) { + return inc_gamma(0.5 * r, 0.5 * (x - x0)); +} - if (axis?.TestBit(EAxisBits.kAxisRange)) { - if ((axis.fFirst !== axis.fLast) && ((axis.fFirst > 1) || (axis.fLast < axis.fNbins))) { - this[`zoom_${name}min`] = axis.fFirst > 1 ? axis.GetBinLowEdge(axis.fFirst) : axis.fXmin; - this[`zoom_${name}max`] = axis.fLast < axis.fNbins ? axis.GetBinLowEdge(axis.fLast + 1) : axis.fXmax; - // reset user range for main painter - axis.InvertBit(EAxisBits.kAxisRange); - axis.fFirst = 1; axis.fLast = axis.fNbins; - } - } - } +/** @summary gamma_quantile_c function + * @memberof Math */ +function gamma_quantile_c(z, alpha, theta) { + return theta * igami( alpha, z); +} - /** @summary Create x,y objects which maps user coordinates into pixels - * @desc While only first painter really need such object, all others just reuse it - * following functions are introduced - * this.GetBin[X/Y] return bin coordinate - * this.[x,y] these are d3.scale objects - * this.gr[x,y] converts root scale into graphical value - * @private */ - createXY(opts) { - this.cleanXY(); // remove all previous configurations +/** @summary gamma_quantile function + * @memberof Math */ +function gamma_quantile(z, alpha, theta) { + return theta * igami( alpha, 1. - z); +} - if (!opts) opts = { ndim: 1 }; +/** @summary breitwigner_cdf_c function + * @memberof Math */ +function breitwigner_cdf_c(x,gamma, x0 = 0) { + return 0.5 - Math.atan(2.0 * (x - x0) / gamma) / M_PI; +} - this.swap_xy = opts.swap_xy || false; - this.reverse_x = opts.reverse_x || false; - this.reverse_y = opts.reverse_y || false; +/** @summary breitwigner_cdf function + * @memberof Math */ +function breitwigner_cdf(x, gamma, x0 = 0) { + return 0.5 + Math.atan(2.0 * (x - x0) / gamma) / M_PI; +} - this.logx = this.logy = 0; +/** @summary cauchy_cdf_c function + * @memberof Math */ +function cauchy_cdf_c(x, b, x0 = 0) { + return 0.5 - Math.atan( (x - x0) / b) / M_PI; +} - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), pad = pp.getRootPad(), - pad_logx = pad.fLogx, - pad_logy = (opts.ndim === 1 ? pad.fLogv : undefined) ?? pad.fLogy; +/** @summary cauchy_cdf function + * @memberof Math */ +function cauchy_cdf(x, b, x0 = 0) { + return 0.5 + Math.atan( (x - x0) / b) / M_PI; +} - this.scales_ndim = opts.ndim; +/** @summary cauchy_pdf function + * @memberof Math */ +function cauchy_pdf(x, b = 1, x0 = 0) { + return b / (M_PI * ((x - x0) * (x - x0) + b * b)); +} - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; +/** @summary gaussian_pdf function + * @memberof Math */ +function gaussian_pdf(x, sigma = 1, x0 = 0) { + const tmp = (x - x0) / sigma; + return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); +} - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; +/** @summary gamma_pdf function + * @memberof Math */ +function gamma_pdf(x, alpha, theta, x0 = 0) { + if ((x - x0) < 0) { + return 0.0; + } else if ((x - x0) == 0) { + return (alpha == 1) ? 1.0 / theta : 0; + } else if (alpha == 1) { + return Math.exp(-(x - x0) / theta) / theta; + } + return Math.exp((alpha - 1) * Math.log((x - x0) / theta) - (x - x0) / theta - lgamma(alpha)) / theta; +} - if (opts.extra_y_space) { - const log_scale = this.swap_xy ? pad_logx : pad_logy; - if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax)*1.1); - else - this.scale_ymax += (this.scale_ymax - this.scale_ymin)*0.1; - } +/** @summary tdistribution_cdf_c function + * @memberof Math */ +function tdistribution_cdf_c(x, r, x0 = 0) { + const p = x - x0, + sign = (p > 0) ? 1. : -1; + return .5 - .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; +} - if (opts.check_pad_range) { - // take zooming out of pad or axis attributes - this.applyAxisZoom('x'); - if (opts.ndim > 1) this.applyAxisZoom('y'); - if (opts.ndim > 2) this.applyAxisZoom('z'); +/** @summary tdistribution_cdf function + * @memberof Math */ +function tdistribution_cdf(x, r, x0 = 0) { + const p = x - x0, + sign = (p > 0) ? 1. : -1; + return .5 + .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; +} - // Use configured pad range - only when main histogram drawn with SAME draw option - if (opts.check_pad_range === 'pad_range') { - this.applyPadUserRange(pad, 'x'); - this.applyPadUserRange(pad, 'y'); - } - } +/** @summary tdistribution_pdf function + * @memberof Math */ +function tdistribution_pdf(x, r, x0 = 0) { + return (Math.exp(lgamma((r + 1.0) / 2.0) - lgamma(r / 2.0)) / Math.sqrt(M_PI * r)) + * Math.pow((1.0 + (x - x0) * (x - x0) / r), -(r + 1.0) / 2.0); +} - if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { - this.zoom_xmin = opts.zoom_xmin; - this.zoom_xmax = opts.zoom_xmax; - } +/** @summary exponential_cdf_c function + * @memberof Math */ +function exponential_cdf_c(x, lambda, x0 = 0) { + return ((x - x0) < 0) ? 1.0 : Math.exp(-lambda * (x - x0)); +} - if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { - this.zoom_ymin = opts.zoom_ymin; - this.zoom_ymax = opts.zoom_ymax; - } +/** @summary exponential_cdf function + * @memberof Math */ +function exponential_cdf(x, lambda, x0 = 0) { + return ((x - x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x - x0)); +} - let orig_x = true, orig_y = true; +/** @summary chisquared_pdf + * @memberof Math */ +function chisquared_pdf(x, r, x0 = 0) { + if ((x - x0) < 0) + return 0.0; + const a = r / 2 - 1.; + // let return inf for case x = x0 and treat special case of r = 2 otherwise will return nan + if (x == x0 && a == 0) + return 0.5; - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - orig_x = false; - } + return Math.exp((r / 2 - 1) * Math.log((x - x0) / 2) - (x - x0) / 2 - lgamma(r / 2)) / 2; +} - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - orig_y = false; - } +/** @summary Probability density function of the F-distribution. + * @memberof Math */ +function fdistribution_pdf(x, n, m, x0 = 0) { + if (n < 0 || m < 0) + return Number.NaN; + if ((x - x0) < 0) + return 0.0; - // projection should be assigned - this.recalculateRange(opts.Proj, orig_x, orig_y); + return Math.exp((n / 2) * Math.log(n) + (m / 2) * Math.log(m) + lgamma((n + m) / 2) - lgamma(n / 2) - lgamma(m / 2) + + (n / 2 - 1) * Math.log(x - x0) - ((n + m) / 2) * Math.log(m + n * (x - x0))); +} - this.x_handle = new TAxisPainter(this.getDom(), this.xaxis, true); - this.x_handle.setPadName(this.getPadName()); - this.x_handle.setHistPainter(opts.hist_painter, 'x'); +/** @summary fdistribution_cdf_c function + * @memberof Math */ +function fdistribution_cdf_c(x, n, m, x0 = 0) { + if (n < 0 || m < 0) + return Number.NaN; - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x, - log: this.swap_xy ? pad_logy : pad_logx, - noexp_changed: this.x_noexp_changed, - symlog: this.swap_xy ? opts.symlog_y : opts.symlog_x, - logcheckmin: this.swap_xy, - logminfactor: logminfactorX }); + const z = m / (m + n * (x - x0)); + // fox z->1 and large a and b IB looses precision use complement function + if (z > 0.9 && n > 1 && m > 1) + return 1. - fdistribution_cdf(x, n, m, x0); - this.x_handle.assignFrameMembers(this, 'x'); + // for the complement use the fact that IB(x,a,b) = 1. - IB(1-x,b,a) + return inc_beta(m / (m + n * (x - x0)), .5 * m, .5 * n); +} - this.y_handle = new TAxisPainter(this.getDom(), this.yaxis, true); - this.y_handle.setPadName(this.getPadName()); - this.y_handle.setHistPainter(opts.hist_painter, 'y'); +/** @summary fdistribution_cdf function + * @memberof Math */ +function fdistribution_cdf(x, n, m, x0 = 0) { + if (n < 0 || m < 0) + return Number.NaN; + + const z = n * (x - x0) / (m + n * (x - x0)); + // fox z->1 and large a and b IB looses precision use complement function + if (z > 0.9 && n > 1 && m > 1) + return 1. - fdistribution_cdf_c(x, n, m, x0); - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y, - log: this.swap_xy ? pad_logx : pad_logy, - noexp_changed: this.y_noexp_changed, - symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5*opts.ymin_nz : 0, - logminfactor: logminfactorY }); + return inc_beta(z, .5 * n, .5 * m); +} - this.y_handle.assignFrameMembers(this, 'y'); +/** @summary Prob function + * @memberof Math */ +function Prob(chi2, ndf) { + if (ndf <= 0) + return 0; // Set CL to zero in case ndf <= 0 - this.setRootPadRange(pad); + if (chi2 <= 0) { + if (chi2 < 0) + return 0; + else + return 1; } - /** @summary Create x,y objects for drawing of second axes - * @private */ - createXY2(opts) { - if (!opts) opts = { ndim: this.scales_ndim ?? 1 }; + return chisquared_cdf_c(chi2,ndf,0); +} - this.reverse_x2 = opts.reverse_x || false; - this.reverse_y2 = opts.reverse_y || false; +/** @summary Gaus function + * @memberof Math */ +function Gaus(x, mean, sigma, norm) { + if (!sigma) + return 1e30; + const arg = (x - mean) / sigma; + if (arg < -39 || arg > 39) + return 0; + const res = Math.exp(-0.5 * arg * arg); + return norm ? res / (2.50662827463100024 * sigma) : res; // sqrt(2*Pi)=2.50662827463100024 +} - this.logx2 = this.logy2 = 0; +/** @summary BreitWigner function + * @memberof Math */ +function BreitWigner(x, mean, gamma) { + return gamma / ((x - mean) * (x - mean) + gamma * gamma / 4) / 2 / Math.PI; +} - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(); +/** @summary Calculates Beta-function Gamma(p)*Gamma(q)/Gamma(p+q). + * @memberof Math */ +function Beta(x,y) { + return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); +} - if (opts.second_x) { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; - } +/** @summary GammaDist function + * @memberof Math */ +function GammaDist(x, gamma, mu = 0, beta = 1) { + if ((x < mu) || (gamma <= 0) || (beta <= 0)) + return 0; + return gamma_pdf(x, gamma, beta, mu); +} - if (opts.second_y) { - this.scale_y2min = this.y2min; - this.scale_y2max = this.y2max; - } +/** @summary probability density function of Laplace distribution + * @memberof Math */ +function LaplaceDist(x, alpha = 0, beta = 1) { + return Math.exp(-Math.abs((x - alpha) / beta)) / (2. * beta); +} - if (opts.extra_y_space && opts.second_y) { - const log_scale = this.swap_xy ? pad.fLogx : pad.fLogy; - if (log_scale && (this.scale_y2max > 0)) - this.scale_y2max = Math.exp(Math.log(this.scale_y2max)*1.1); - else - this.scale_y2max += (this.scale_y2max - this.scale_y2min)*0.1; - } +/** @summary distribution function of Laplace distribution + * @memberof Math */ +function LaplaceDistI(x, alpha = 0, beta = 1) { + return (x <= alpha) ? 0.5 * Math.exp(-Math.abs((x - alpha) / beta)) : 1 - 0.5 * Math.exp(-Math.abs((x - alpha) / beta)); +} - if ((this.zoom_x2min !== this.zoom_x2max) && opts.second_x) { - this.scale_x2min = this.zoom_x2min; - this.scale_x2max = this.zoom_x2max; - } +/** @summary density function for Student's t- distribution + * @memberof Math */ +function Student(T, ndf) { + if (ndf < 1) + return 0; - if ((this.zoom_y2min !== this.zoom_y2max) && opts.second_y) { - this.scale_y2min = this.zoom_y2min; - this.scale_y2max = this.zoom_y2max; - } + const r = ndf, + rh = 0.5 * r, + rh1 = rh + 0.5, + denom = Math.sqrt(r * Math.PI) * gamma(rh) * Math.pow(1 + T * T / r, rh1); + return gamma(rh1) / denom; +} - if (opts.second_x) { - this.x2_handle = new TAxisPainter(this.getDom(), this.x2axis, true); - this.x2_handle.setPadName(this.getPadName()); - this.x2_handle.setHistPainter(opts.hist_painter, 'x'); +/** @summary cumulative distribution function of Student's + * @memberof Math */ +function StudentI(T, ndf) { + const r = ndf; - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x2, - log: this.swap_xy ? pad.fLogy : pad.fLogx, - noexp_changed: this.x2_noexp_changed, - logcheckmin: this.swap_xy, - logminfactor: logminfactorX }); + return (T > 0) + ? (1 - 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5)) + : 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5); +} - this.x2_handle.assignFrameMembers(this, 'x2'); - } +/** @summary LogNormal function + * @memberof Math */ +function LogNormal(x, sigma, theta = 0, m = 1) { + if ((x < theta) || (sigma <= 0) || (m <= 0)) + return 0; + return lognormal_pdf(x, Math.log(m), sigma, theta); +} - if (opts.second_y) { - this.y2_handle = new TAxisPainter(this.getDom(), this.y2axis, true); - this.y2_handle.setPadName(this.getPadName()); - this.y2_handle.setHistPainter(opts.hist_painter, 'y'); +/** @summary Computes the probability density function of the Beta distribution + * @memberof Math */ +function BetaDist(x, p, q) { + if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) + return 0; + const beta = Beta(p, q); + return Math.pow(x, p - 1) * Math.pow(1 - x, q - 1) / beta; +} - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y2, - log: this.swap_xy ? pad.fLogx : pad.fLogy, - noexp_changed: this.y2_noexp_changed, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, - logminfactor: logminfactorY }); +/** @summary Computes the distribution function of the Beta distribution. + * @memberof Math */ +function BetaDistI(x, p, q) { + if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) + return 0; + return BetaIncomplete(x, p, q); +} - this.y2_handle.assignFrameMembers(this, 'y2'); - } - } +/** @summary gaus function for TFormula + * @memberof Math */ +function gaus(f, x, i) { + return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)); +} - /** @summary Return functions to create x/y points based on coordinates - * @desc In default case returns frame painter itself - * @private */ - getGrFuncs(second_x, second_y) { - const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) return this; +/** @summary gausn function for TFormula + * @memberof Math */ +function gausn(f, x, i) { + return gaus(f, x, i) / (Math.sqrt(2 * Math.PI) * f.GetParValue(i + 2)); +} - return { - use_x2, - grx: use_x2 ? this.grx2 : this.grx, - logx: this.logx, - x_handle: use_x2 ? this.x2_handle : this.x_handle, - scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, - scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, - use_y2, - gry: use_y2 ? this.gry2 : this.gry, - logy: this.logy, - y_handle: use_y2 ? this.y2_handle : this.y_handle, - scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, - scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - swap_xy: this.swap_xy, - fp: this, - revertAxis(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.revertAxis(name, v); - }, - axisAsText(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.axisAsText(name, v); - } - }; - } +/** @summary gausxy function for TFormula + * @memberof Math */ +function gausxy(f, x, y, i) { + return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)) + * Math.exp(-0.5 * Math.pow((y - f.GetParValue(i + 3)) / f.GetParValue(i + 4), 2)); +} - /** @summary Set selected range back to TPad object - * @private */ - setRootPadRange(pad, is3d) { - if (!pad || !this.ranges_set) return; +/** @summary expo function for TFormula + * @memberof Math */ +function expo(f, x, i) { + return Math.exp(f.GetParValue(i + 0) + f.GetParValue(i + 1) * x); +} - if (is3d) { - // this is fake values, algorithm should be copied from TView3D class of ROOT - // pad.fLogx = pad.fLogy = 0; - pad.fUxmin = pad.fUymin = -0.9; - pad.fUxmax = pad.fUymax = 0.9; - } else { - pad.fLogx = this.swap_xy ? this.logy : this.logx; - pad.fUxmin = pad.fLogx ? Math.log10(this.scale_xmin) : this.scale_xmin; - pad.fUxmax = pad.fLogx ? Math.log10(this.scale_xmax) : this.scale_xmax; - pad.fLogy = this.swap_xy ? this.logx : this.logy; - pad.fUymin = pad.fLogy ? Math.log10(this.scale_ymin) : this.scale_ymin; - pad.fUymax = pad.fLogy ? Math.log10(this.scale_ymax) : this.scale_ymax; - } +/** @summary landau function for TFormula + * @memberof Math */ +function landau(f, x, i) { + return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), false); +} - const rx = pad.fUxmax - pad.fUxmin, - ry = pad.fUymax - pad.fUymin; - let mx = 1 - pad.fLeftMargin - pad.fRightMargin, - my = 1 - pad.fBottomMargin - pad.fTopMargin; +/** @summary landaun function for TFormula + * @memberof Math */ +function landaun(f, x, i) { + return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), true); +} - if (mx <= 0) mx = 0.01; // to prevent overflow - if (my <= 0) my = 0.01; +/** @summary Crystal ball function + * @memberof Math */ +function crystalball_function(x, alpha, n, sigma, mean = 0) { + if (sigma < 0.) + return 0.; + let z = (x - mean) / sigma; + if (alpha < 0) + z = -z; + const abs_alpha = Math.abs(alpha); + if (z > -abs_alpha) + return Math.exp(-0.5 * z * z); + const nDivAlpha = n / abs_alpha, + AA = Math.exp(-0.5 * abs_alpha * abs_alpha), + B = nDivAlpha - abs_alpha, + arg = nDivAlpha / (B - z); + return AA * Math.pow(arg,n); +} - pad.fX1 = pad.fUxmin - rx/mx*pad.fLeftMargin; - pad.fX2 = pad.fUxmax + rx/mx*pad.fRightMargin; - pad.fY1 = pad.fUymin - ry/my*pad.fBottomMargin; - pad.fY2 = pad.fUymax + ry/my*pad.fTopMargin; - } +/** @summary pdf definition of the crystal_ball which is defined only for n > 1 otherwise integral is diverging + * @memberof Math */ +function crystalball_pdf(x, alpha, n, sigma, mean = 0) { + if (sigma < 0.) + return 0.; + if (n <= 1) + return Number.NaN; // pdf is not normalized for n <=1 + const abs_alpha = Math.abs(alpha), + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + N = 1. / (sigma * (C + D)); + return N * crystalball_function(x,alpha,n,sigma,mean); +} +/** @summary compute the integral of the crystal ball function + * @memberof Math */ +function crystalball_integral(x, alpha, n, sigma, mean = 0) { + if (sigma == 0) + return 0; + if (alpha == 0) + return 0.; + const useLog = (n == 1.0), + abs_alpha = Math.abs(alpha); - /** @summary Draw axes grids - * @desc Called immediately after axes drawing */ - drawGrids(draw_grids) { - const layer = this.getFrameSvg().selectChild('.axis_layer'); + let z = (x - mean) / sigma, intgaus = 0., intpow = 0.; + if (alpha < 0 ) + z = -z; - layer.selectAll('.xgrid').remove(); - layer.selectAll('.ygrid').remove(); + const sqrtpiover2 = Math.sqrt(M_PI / 2.), + sqrt2pi = Math.sqrt( 2. * M_PI), + oneoversqrt2 = 1. / Math.sqrt(2.); + if (z <= -abs_alpha) { + const A = Math.pow(n / abs_alpha,n) * Math.exp(-0.5 * alpha * alpha), + B = n / abs_alpha - abs_alpha; - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - h = this.getFrameHeight(), - w = this.getFrameWidth(), - grid_style = gStyle.fGridStyle; + if (!useLog) { + const C = (n / abs_alpha) * (1. / (n - 1)) * Math.exp(-alpha * alpha / 2.); + intpow = C - A / (n - 1.) * Math.pow(B - z,-n + 1); + } + else { + // for n=1 the primitive of 1/x is log(x) + intpow = -A * Math.log( n / abs_alpha ) + A * Math.log(B - z); + } + intgaus = sqrtpiover2 * (1. + erf(abs_alpha * oneoversqrt2)); + } else { + intgaus = normal_cdf_c(z, 1); + intgaus *= sqrt2pi; + intpow = 0; + } + return sigma * (intgaus + intpow); +} - // add a grid on x axis, if the option is set - if (pad?.fGridx && draw_grids && this.x_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('x')?.fAxisColor ?? 1); - let gridx = ''; +/** @summary crystalball_cdf function + * @memberof Math */ +function crystalball_cdf(x, alpha, n, sigma, mean = 0) { + if (n <= 1.) + return Number.NaN; - this.x_handle.ticks.forEach(pos => { - gridx += this.swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; - }); + const abs_alpha = Math.abs(alpha), + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + totIntegral = sigma * (C + D), + integral = crystalball_integral(x,alpha,n,sigma,mean); - layer.append('svg:path') - .attr('class', 'xgrid') - .attr('d', gridx) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } + return (alpha > 0) ? 1. - integral / totIntegral : integral / totIntegral; +} - // add a grid on y axis, if the option is set - if (pad?.fGridy && draw_grids && this.y_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('y')?.fAxisColor ?? 1); - let gridy = ''; +/** @summary crystalball_cdf_c function + * @memberof Math */ +function crystalball_cdf_c(x, alpha, n, sigma, mean = 0) { + if (n <= 1.) + return Number.NaN; - this.y_handle.ticks.forEach(pos => { - gridy += this.swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; - }); + const abs_alpha = Math.abs(alpha), + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + totIntegral = sigma * (C + D), + integral = crystalball_integral(x,alpha,n,sigma,mean); - layer.append('svg:path') - .attr('class', 'ygrid') - .attr('d', gridy) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } - } - - /** @summary Converts 'raw' axis value into text */ - axisAsText(axis, value) { - const handle = this[`${axis}_handle`]; - - if (handle) - return handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']); + return (alpha > 0) ? integral / totIntegral : 1. - (integral / totIntegral); +} - return value.toPrecision(4); - } +/** @summary ChebyshevN function + * @memberof Math */ +function ChebyshevN(n, x, c) { + let d1 = 0.0, d2 = 0.0; + const y2 = 2.0 * x; - /** @summary Identify if requested axes are drawn - * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { - return !second_x && !second_y ? this.axes_drawn : false; + for (let i = n; i >= 1; i--) { + const temp = d1; + d1 = y2 * d1 - d2 + c[i]; + d2 = temp; } - /** @summary draw axes, - * @return {Promise} which ready when drawing is completed */ - async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, - AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { - this.cleanAxesDrawings(); - - if ((this.xmin === this.xmax) || (this.ymin === this.ymax)) - return false; + return x * d1 - d2 + c[0]; +} - if (AxisPos === undefined) AxisPos = 0; +/** @summary Chebyshev0 function + * @memberof Math */ +function Chebyshev0(_x, c0) { + return c0; +} - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true), - draw_grids = enable_grids && (pad?.fGridx || pad?.fGridy); +/** @summary Chebyshev1 function + * @memberof Math */ +function Chebyshev1(x, c0, c1) { + return c0 + c1 * x; +} - this.x_handle.invert_side = (AxisPos >= 10); - this.x_handle.lbls_both_sides = !this.x_handle.invert_side && (pad?.fTickx > 1); // labels on both sides - this.x_handle.has_obstacle = has_x_obstacle; +/** @summary Chebyshev2 function + * @memberof Math */ +function Chebyshev2(x, c0, c1, c2) { + return c0 + c1 * x + c2 * (2.0 * x * x - 1.0); +} - this.y_handle.invert_side = ((AxisPos % 10) === 1); - this.y_handle.lbls_both_sides = !this.y_handle.invert_side && (pad?.fTicky > 1); // labels on both sides - this.y_handle.has_obstacle = has_y_obstacle; +/** @summary Chebyshev3 function + * @memberof Math */ +function Chebyshev3(x, ...args) { + return ChebyshevN(3, x, args); +} - const draw_horiz = this.swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.swap_xy ? this.x_handle : this.y_handle; +/** @summary Chebyshev4 function + * @memberof Math */ +function Chebyshev4(x, ...args) { + return ChebyshevN(4, x, args); +} - if ((!disable_x_draw || !disable_y_draw) && pp._fast_drawing) - disable_x_draw = disable_y_draw = true; +/** @summary Chebyshev5 function + * @memberof Math */ +function Chebyshev5(x, ...args) { + return ChebyshevN(5, x, args); +} - let pr = Promise.resolve(true); +/** @summary Chebyshev6 function + * @memberof Math */ +function Chebyshev6(x, ...args) { + return ChebyshevN(6, x, args); +} - if (!disable_x_draw || !disable_y_draw || draw_grids) { - const can_adjust_frame = !shrink_forbidden && settings.CanAdjustFrame, +/** @summary Chebyshev7 function + * @memberof Math */ +function Chebyshev7(x, ...args) { + return ChebyshevN(7, x, args); +} - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, disable_x_draw, - undefined, false, pp.getPadHeight() - h - this.getFrameY()), +/** @summary Chebyshev8 function + * @memberof Math */ +function Chebyshev8(x, ...args) { + return ChebyshevN(8, x, args); +} - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this._frame_x, can_adjust_frame); +/** @summary Chebyshev9 function + * @memberof Math */ +function Chebyshev9(x, ...args) { + return ChebyshevN(9, x, args); +} - pr = Promise.all([pr1, pr2]).then(() => { - this.drawGrids(draw_grids); +/** @summary Chebyshev10 function + * @memberof Math */ +function Chebyshev10(x, ...args) { + return ChebyshevN(10, x, args); +} - if (!can_adjust_frame) return; +// ========================================================================= - let shrink = 0.0; - const ypos = draw_vertical.position; +/** @summary Calculate ClopperPearson + * @memberof Math */ +function eff_ClopperPearson(total,passed,level,bUpper) { + const alpha = (1.0 - level) / 2; + if (bUpper) + return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total - passed)); - if ((-0.2 * w < ypos) && (ypos < 0)) { - shrink = -ypos / w + 0.001; - this.shrink_frame_left += shrink; - } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.shrink_frame_left > 0) && (ypos / w > this.shrink_frame_left)) { - shrink = -this.shrink_frame_left; - this.shrink_frame_left = 0.0; - } + return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total - passed + 1.0)); +} - if (!shrink) return; +/** @summary Calculate normal + * @memberof Math */ +function eff_Normal(total,passed,level,bUpper) { + if (total == 0) + return bUpper ? 1 : 0; - this.shrinkFrame(shrink, 0); - return this.redraw().then(() => this.drawAxes(true)); - }); - } + const alpha = (1.0 - level) / 2, + average = passed / total, + sigma = Math.sqrt(average * (1 - average) / total), + delta = normal_quantile(1 - alpha, sigma); - return pr.then(() => { - if (!shrink_forbidden) - this.axes_drawn = true; - return true; - }); - } + if (bUpper) + return ((average + delta) > 1) ? 1.0 : (average + delta); - /** @summary draw second axes (if any) */ - drawAxes2(second_x, second_y) { - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true); + return ((average - delta) < 0) ? 0.0 : (average - delta); +} - if (second_x) { - this.x2_handle.invert_side = true; - this.x2_handle.lbls_both_sides = false; - this.x2_handle.has_obstacle = false; - } +/** @summary Calculates the boundaries for the frequentist Wilson interval + * @memberof Math */ +function eff_Wilson(total,passed,level,bUpper) { + const alpha = (1.0 - level) / 2; + if (total == 0) + return bUpper ? 1 : 0; + const average = passed / total, + kappa = normal_quantile(1 - alpha,1), + mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), + delta = kappa / (total + kappa * kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); - if (second_y) { - this.y2_handle.invert_side = true; - this.y2_handle.lbls_both_sides = false; - } + if (bUpper) + return ((mode + delta) > 1) ? 1.0 : (mode + delta); - let draw_horiz = this.swap_xy ? this.y2_handle : this.x2_handle, - draw_vertical = this.swap_xy ? this.x2_handle : this.y2_handle; + return ((mode - delta) < 0) ? 0.0 : (mode - delta); +} - if ((draw_horiz || draw_vertical) && pp._fast_drawing) - draw_horiz = draw_vertical = null; +/** @summary Calculates the boundaries for the frequentist Agresti-Coull interval + * @memberof Math */ +function eff_AgrestiCoull(total,passed,level,bUpper) { + const alpha = (1.0 - level) / 2, + kappa = normal_quantile(1 - alpha,1), + mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), + delta = kappa * Math.sqrt(mode * (1 - mode) / (total + kappa * kappa)); - let pr1, pr2; + if (bUpper) + return ((mode + delta) > 1) ? 1.0 : (mode + delta); - if (draw_horiz) { - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, false, - undefined, false); - } + return ((mode - delta) < 0) ? 0.0 : (mode - delta); +} - if (draw_vertical) { - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, false, - draw_vertical.invert_side ? 0 : this._frame_x, false); - } +/** @summary Calculates the boundaries using the mid-P binomial + * @memberof Math */ +function eff_MidPInterval(total,passed,level,bUpper) { + const alpha = 1. - level, alpha_min = alpha / 2 , tol = 1e-9; // tolerance + let pmin = 0, pmax = 1, p = 0; - return Promise.all([pr1, pr2]); + // treat special case for 0 0 && passed < 1) { + const p0 = eff_MidPInterval(total, 0.0, level, bUpper), + p1 = eff_MidPInterval(total, 1.0, level, bUpper); + p = (p1 - p0) * passed + p0; + return p; } + while (Math.abs(pmax - pmin) > tol) { + p = (pmin + pmax) / 2; + // double v = 0.5 * ROOT::Math::binomial_pdf(int(passed), p, int(total)); + // make it work for non integer using the binomial - beta relationship + let v = 0.5 * beta_pdf(p, passed + 1., total - passed + 1) / (total + 1); + // if (passed > 0) + // v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); + // compute the binomial cdf at passed -1 + if ((passed - 1) >= 0) + v += beta_cdf_c(p, passed, total - passed + 1); - /** @summary Update frame attributes - * @private */ - updateAttributes(force) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - tframe = this.getObject(); - - if ((this.fX1NDC === undefined) || (force && !this.modified_NDC)) { - if (!pad) { - this.fX1NDC = gStyle.fPadLeftMargin; - this.fX2NDC = 1 - gStyle.fPadRightMargin; - this.fY1NDC = gStyle.fPadBottomMargin; - this.fY2NDC = 1 - gStyle.fPadTopMargin; - } else { - this.fX1NDC = pad.fLeftMargin; - this.fX2NDC = 1 - pad.fRightMargin; - this.fY1NDC = pad.fBottomMargin; - this.fY2NDC = 1 - pad.fTopMargin; - } - } - - if (this.fillatt === undefined) { - if (tframe) - this.createAttFill({ attr: tframe }); - else if (pad?.fFrameFillColor) - this.createAttFill({ pattern: pad.fFrameFillStyle, color: pad.fFrameFillColor }); - else if (pad) - this.createAttFill({ attr: pad }); - else - this.createAttFill({ pattern: 1001, color: 0 }); - - // force white color for the canvas frame - if (!tframe && this.fillatt.empty() && pp?.iscan) - this.fillatt.setSolidColor('white'); - else if ((pad?.fFillStyle === 4000) && !this.fillatt.empty()) // special case of transpad.C macro, which set transparent pad - this.fillatt.setOpacity(0); - } - - if (!tframe && (pad?.fFrameLineColor !== undefined)) - this.createAttLine({ color: pad.fFrameLineColor, width: pad.fFrameLineWidth, style: pad.fFrameLineStyle }); + const vmin = bUpper ? alpha_min : 1. - alpha_min; + if (v > vmin) + pmin = p; else - this.createAttLine({ attr: tframe, color: 'black' }); + pmax = p; } - /** @summary Function called at the end of resize of frame - * @desc One should apply changes to the pad - * @private */ - sizeChanged() { - const pad = this.getPadPainter()?.getRootPad(true); - - if (pad) { - pad.fLeftMargin = this.fX1NDC; - pad.fRightMargin = 1 - this.fX2NDC; - pad.fBottomMargin = this.fY1NDC; - pad.fTopMargin = 1 - this.fY2NDC; - this.setRootPadRange(pad); - } - - this.interactiveRedraw('pad', 'frame'); - } + return p; +} - /** @summary Remove all kinds of X/Y function for axes transformation */ - cleanXY() { - delete this.grx; - delete this.gry; - delete this.grz; - delete this.grx2; - delete this.gry2; +/** @summary for a central confidence interval for a Beta distribution + * @memberof Math */ +function eff_Bayesian(total,passed,level,bUpper,alpha,beta) { + const a = passed + alpha, + b = total - passed + beta; + if (bUpper) { + if ((a > 0) && (b > 0)) + return beta_quantile((1 + level) / 2,a,b); + else + return 1; + } else if ((a > 0) && (b > 0)) + return beta_quantile((1 - level) / 2,a,b); - this.x_handle?.cleanup(); - this.y_handle?.cleanup(); - this.z_handle?.cleanup(); - this.x2_handle?.cleanup(); - this.y2_handle?.cleanup(); + return 0; +} - delete this.x_handle; - delete this.y_handle; - delete this.z_handle; - delete this.x2_handle; - delete this.y2_handle; - } +/** @summary Return function to calculate boundary of TEfficiency + * @memberof Math */ +function getTEfficiencyBoundaryFunc(option, isbayessian) { + const kFCP = 0, // Clopper-Pearson interval (recommended by PDG) + kFNormal = 1, // Normal approximation + kFWilson = 2, // Wilson interval + kFAC = 3, // Agresti-Coull interval + kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript + // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) + // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) + // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) + kMidP = 8; // Mid-P Lancaster interval - /** @summary remove all axes drawings */ - cleanAxesDrawings() { - this.x_handle?.removeG(); - this.y_handle?.removeG(); - this.z_handle?.removeG(); - this.x2_handle?.removeG(); - this.y2_handle?.removeG(); + if (isbayessian) + return eff_Bayesian; - this.draw_g?.selectChild('.axis_layer').selectAll('*').remove(); - this.axes_drawn = false; + switch (option) { + case kFCP: + return eff_ClopperPearson; + case kFNormal: + return eff_Normal; + case kFWilson: + return eff_Wilson; + case kFAC: + return eff_AgrestiCoull; + case kFFC: + console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); + return eff_ClopperPearson; + case kMidP: + return eff_MidPInterval; + // case kBJeffrey: + // case kBUniform: + // case kBBayesian: return eff_ClopperPearson; } + console.log(`Not recognized stat option ${option}, using kFCP`); + return eff_ClopperPearson; +} - /** @summary Returns frame rectangle plus extra info for hint display */ - cleanFrameDrawings() { - // cleanup all 3D drawings if any - if (isFunc(this.create3DScene)) - this.create3DScene(-1); - - this.cleanAxesDrawings(); - this.cleanXY(); +/** @summary Square function + * @memberof Math */ +function Sq(x) { + return x * x; +} - this.ranges_set = false; +/** @summary Pi function + * @memberof Math */ +function Pi() { + return Math.PI; +} - this.xmin = this.xmax = 0; - this.ymin = this.ymax = 0; - this.zmin = this.zmax = 0; +/** @summary TwoPi function + * @memberof Math */ +function TwoPi() { + return 2 * Math.PI; +} - this.zoom_xmin = this.zoom_xmax = 0; - this.zoom_ymin = this.zoom_ymax = 0; - this.zoom_zmin = this.zoom_zmax = 0; +/** @summary PiOver2 function + * @memberof Math */ +function PiOver2() +{ + return Math.PI / 2; +} - this.scale_xmin = this.scale_xmax = 0; - this.scale_ymin = this.scale_ymax = 0; - this.scale_zmin = this.scale_zmax = 0; +/** @summary PiOver4 function + * @memberof Math */ +function PiOver4() +{ + return Math.PI / 4; +} - this.draw_g?.selectChild('.main_layer').selectAll('*').remove(); - this.draw_g?.selectChild('.upper_layer').selectAll('*').remove(); +/** @summary InvPi function + * @memberof Math */ +function InvPi() +{ + return 1 / Math.PI; +} - this.xaxis = null; - this.yaxis = null; - this.zaxis = null; +var jsroot_math = /*#__PURE__*/Object.freeze({ +__proto__: null, +Beta: Beta, +BetaDist: BetaDist, +BetaDistI: BetaDistI, +BetaIncomplete: BetaIncomplete, +BreitWigner: BreitWigner, +Chebyshev0: Chebyshev0, +Chebyshev1: Chebyshev1, +Chebyshev10: Chebyshev10, +Chebyshev2: Chebyshev2, +Chebyshev3: Chebyshev3, +Chebyshev4: Chebyshev4, +Chebyshev5: Chebyshev5, +Chebyshev6: Chebyshev6, +Chebyshev7: Chebyshev7, +Chebyshev8: Chebyshev8, +Chebyshev9: Chebyshev9, +ChebyshevN: ChebyshevN, +FDist: fdistribution_pdf, +FDistI: fdistribution_cdf, +Gamma: gamma, +GammaDist: GammaDist, +Gaus: Gaus, +InvPi: InvPi, +Landau: Landau, +LaplaceDist: LaplaceDist, +LaplaceDistI: LaplaceDistI, +LogNormal: LogNormal, +Pi: Pi, +PiOver2: PiOver2, +PiOver4: PiOver4, +Polynomial1eval: Polynomial1eval, +Polynomialeval: Polynomialeval, +Prob: Prob, +Sq: Sq, +Student: Student, +StudentI: StudentI, +TwoPi: TwoPi, +beta: beta, +beta_cdf_c: beta_cdf_c, +beta_pdf: beta_pdf, +beta_quantile: beta_quantile, +breitwigner_cdf: breitwigner_cdf, +breitwigner_cdf_c: breitwigner_cdf_c, +cauchy_cdf: cauchy_cdf, +cauchy_cdf_c: cauchy_cdf_c, +cauchy_pdf: cauchy_pdf, +chisquared_cdf: chisquared_cdf, +chisquared_cdf_c: chisquared_cdf_c, +chisquared_pdf: chisquared_pdf, +crystalball_cdf: crystalball_cdf, +crystalball_cdf_c: crystalball_cdf_c, +crystalball_function: crystalball_function, +crystalball_pdf: crystalball_pdf, +erf: erf, +erfc: erfc, +expo: expo, +exponential_cdf: exponential_cdf, +exponential_cdf_c: exponential_cdf_c, +fdistribution_cdf: fdistribution_cdf, +fdistribution_cdf_c: fdistribution_cdf_c, +fdistribution_pdf: fdistribution_pdf, +gamma: gamma, +gamma_pdf: gamma_pdf, +gamma_quantile: gamma_quantile, +gamma_quantile_c: gamma_quantile_c, +gaus: gaus, +gausn: gausn, +gaussian_cdf: normal_cdf, +gaussian_cdf_c: normal_cdf_c, +gaussian_pdf: gaussian_pdf, +gausxy: gausxy, +getTEfficiencyBoundaryFunc: getTEfficiencyBoundaryFunc, +igam: igam, +igamc: igamc, +igami: igami, +inc_beta: inc_beta, +inc_gamma: inc_gamma, +inc_gamma_c: inc_gamma_c, +incbet: incbet, +incbi: incbi, +landau: landau, +landau_pdf: landau_pdf, +landaun: landaun, +lgam: lgam, +lgamma: lgamma, +lognormal_cdf: lognormal_cdf, +lognormal_cdf_c: lognormal_cdf_c, +lognormal_pdf: lognormal_pdf, +ndtri: ndtri, +normal_cdf: normal_cdf, +normal_cdf_c: normal_cdf_c, +normal_pdf: normal_pdf, +normal_quantile: normal_quantile, +normal_quantile_c: normal_quantile_c, +pseries: pseries, +stirf: stirf, +tdistribution_cdf: tdistribution_cdf, +tdistribution_cdf_c: tdistribution_cdf_c, +tdistribution_pdf: tdistribution_pdf, +tgamma: gamma +}); - if (this.draw_g) { - this.draw_g.selectAll('*').remove(); - this.draw_g.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null); - this.draw_g.remove(); - } +/** @summary Display progress message in the left bottom corner. + * @desc Previous message will be overwritten + * if no argument specified, any shown messages will be removed + * @param {string} msg - message to display + * @param {number} [tmout] - optional timeout in milliseconds, after message will disappear + * @param {function} [click_handle] - optional handle to process click events + * @private */ +function showProgress(msg, tmout, click_handle) { + if (isBatchMode() || (typeof document === 'undefined')) + return; - delete this.draw_g; // frame element managet by the pad + const id = 'jsroot_progressbox', modal = (settings.ProgressBox === 'modal') && isFunc(internals._modalProgress) ? internals._modalProgress : null; + let box = select('#' + id); - if (this.keys_handler) { - window.removeEventListener('keydown', this.keys_handler, false); - this.keys_handler = null; - } + if (!settings.ProgressBox) { + if (modal) + modal(); + return box.remove(); } - /** @summary Cleanup frame */ - cleanup() { - this.cleanFrameDrawings(); - delete this._click_handler; - delete this._dblclick_handler; - delete this.enabledKeys; - - const pp = this.getPadPainter(); - if (pp?.frame_painter_ref === this) - delete pp.frame_painter_ref; - - super.cleanup(); + if (!arguments.length || !msg) { + if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) + box.remove(); + if (modal) + modal(); + return; } - /** @summary Redraw TFrame */ - redraw(/* reason */) { - const pp = this.getPadPainter(); - if (pp) pp.frame_painter_ref = this; // keep direct reference to the frame painter + if (modal) { + box.remove(); + modal(msg, click_handle); + } else { + if (box.empty()) { + box = select(document.body) + .append('div').attr('id', id) + .attr('style', 'position: fixed; min-width: 100px; height: auto; overflow: visible; z-index: 101; border: 1px solid #999; background: #F8F8F8; left: 10px; bottom: 10px;'); + box.append('p'); + } - // first update all attributes from objects - this.updateAttributes(); + box.property('with_timeout', false); - const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, - lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)); - let w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)), - rotate = false, fixpos = false, trans; + const p = box.select('p'); - if (pp?.options) { - if (pp.options.RotateFrame) rotate = true; - if (pp.options.FixFrame) fixpos = true; + if (isStr(msg)) { + p.html(msg) + .on('click', isFunc(click_handle) ? click_handle : null) + .attr('title', isFunc(click_handle) ? 'Click element to abort current operation' : ''); } - if (rotate) { - trans = `rotate(-90,${lm},${tm}) translate(${lm-h},${tm})`; - [w, h] = [h, w]; - } else - trans = makeTranslate(lm, tm); - - this._frame_x = lm; - this._frame_y = tm; - this._frame_width = w; - this._frame_height = h; - this._frame_rotate = rotate; - this._frame_fixpos = fixpos; + p.attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); + } - if (this.mode3d) return this; // no need to create any elements in 3d mode + if (Number.isFinite(tmout) && (tmout > 0)) { + if (!box.empty()) + box.property('with_timeout', true); + setTimeout(() => showProgress('', -1), tmout); + } +} - // this is svg:g object - container for every other items belonging to frame - this.draw_g = this.getFrameSvg(); - - let top_rect, main_svg; - - if (this.draw_g.empty()) { - this.draw_g = this.getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame'); - - // empty title on the frame required to suppress title of the canvas - if (!this.isBatchMode()) - this.draw_g.append('svg:title').text(''); - - top_rect = this.draw_g.append('svg:path'); - - main_svg = this.draw_g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); +/** @summary Tries to close current browser tab + * @desc Many browsers do not allow simple window.close() call, + * therefore try several workarounds + * @private */ +function closeCurrentWindow() { + if (typeof window !== 'undefined') { + window.close(); + window.open('', '_self').close(); + } +} - this.draw_g.append('svg:g').attr('class', 'axis_layer'); - this.draw_g.append('svg:g').attr('class', 'upper_layer'); - } else { - top_rect = this.draw_g.selectChild('path'); - main_svg = this.draw_g.selectChild('.main_layer'); +/** @summary Tries to open ui5 + * @private */ +function tryOpenOpenUI(sources, args) { + if (!sources?.length) { + if (isFunc(args.rejectFunc)) { + args.rejectFunc(Error('openui5 was not possible to load')); + args.rejectFunc = null; } - - this.axes_drawn = false; - - this.draw_g.attr('transform', trans); - - top_rect.attr('d', `M0,0H${w}V${h}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - - main_svg.attr('width', w) - .attr('height', h) - .attr('viewBox', `0 0 ${w} ${h}`); - - return this; + return; } - /** @summary Change log state of specified axis - * @param {number} value - 0 (linear), 1 (log) or 2 (log2) */ - changeAxisLog(axis, value) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true); - if (!pad) return; - - pp._interactively_changed = true; + // where to take openui5 sources + let src = sources.shift(); - const name = `fLog${axis}`; + if ((src.indexOf('roothandler') === 0) && (src.indexOf('://') < 0)) + src = src.replace(/:\//g, '://'); - // do not allow log scale for labels - if (!pad[name]) { - if (this.swap_xy && axis === 'x') - axis = 'y'; - else if (this.swap_xy && axis === 'y') - axis = 'x'; - const handle = this[`${axis}_handle`]; - if (handle?.kind === kAxisLabels) return; - } + if (settings.Debug) + console.log('Try openui5 from ' + src); - if ((value === 'toggle') || (value === undefined)) - value = pad[name] ? 0 : 1; + const element = document.createElement('script'); + element.setAttribute('type', 'text/javascript'); + element.setAttribute('id', 'sap-ui-bootstrap'); + // use nojQuery while we are already load jquery and jquery-ui, later one can use directly sap-ui-core.js - // directly change attribute in the pad - pad[name] = value; + // this is location of openui5 scripts when working with THttpServer or when scripts are installed inside JSROOT + element.setAttribute('src', src + (args.ui5dbg ? 'resources/sap-ui-core-dbg.js' : 'resources/sap-ui-core.js')); // latest openui5 version - return this.interactiveRedraw('pad', `log${axis}`); - } + element.setAttribute('data-sap-ui-libs', args.openui5libs ?? 'sap.m, sap.ui.layout, sap.ui.unified, sap.ui.commons'); + // element.setAttribute('data-sap-ui-language', args.openui5language ?? 'en'); + element.setAttribute('data-sap-ui-theme', args.openui5theme || (settings.DarkMode ? 'sap_fiori_3_dark' : 'sap_fiori_3')); + element.setAttribute('data-sap-ui-compatVersion', 'edge'); + element.setAttribute('data-sap-ui-async', 'true'); + // element.setAttribute('data-sap-ui-bindingSyntax', 'complex'); - /** @summary Toggle log state on the specified axis */ - toggleAxisLog(axis) { - return this.changeAxisLog(axis, 'toggle'); - } + element.setAttribute('data-sap-ui-preload', 'async'); // '' to disable Component-preload.js - /** @summary Fill context menu for the frame - * @desc It could be appended to the histogram menus */ - fillContextMenu(menu, kind, obj) { - const main = this.getMainPainter(true), - pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - is_pal = kind === 'pal'; + element.setAttribute('data-sap-ui-evt-oninit', 'completeUI5Loading()'); - if (is_pal) kind = 'z'; + element.onerror = function() { + // remove failed element + element.parentNode.removeChild(element); + // and try next + tryOpenOpenUI(sources, args); + }; - if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'x2') || (kind === 'y2')) { - const faxis = obj || this[kind+'axis'], - handle = this[`${kind}_handle`]; - if (!isFunc(faxis?.TestBit)) - return false; + element.onload = function() { + args.load_src = src; + }; - menu.add(`header: ${kind.toUpperCase()} axis`); - menu.add('Unzoom', () => this.unzoom(kind)); - if (pad) { - const member = 'fLog'+kind[0]; - menu.add('sub:SetLog '+kind[0], () => { - menu.input('Enter log kind: 0 - off, 1 - log10, 2 - log2, 3 - ln, ...', pad[member], 'int', 0, 10000).then(v => { - this.changeAxisLog(kind[0], v); - }); - }); - menu.addchk(pad[member] === 0, 'linear', () => this.changeAxisLog(kind[0], 0)); - menu.addchk(pad[member] === 1, 'log10', () => this.changeAxisLog(kind[0], 1)); - menu.addchk(pad[member] === 2, 'log2', () => this.changeAxisLog(kind[0], 2)); - menu.addchk(pad[member] === 3, 'ln', () => this.changeAxisLog(kind[0], 3)); - menu.addchk(pad[member] === 4, 'log4', () => this.changeAxisLog(kind[0], 4)); - menu.addchk(pad[member] === 8, 'log8', () => this.changeAxisLog(kind[0], 8)); - menu.add('endsub:'); - } - menu.addchk(faxis.TestBit(EAxisBits.kMoreLogLabels), 'More log', flag => { - faxis.InvertBit(EAxisBits.kMoreLogLabels); - if (main?.snapid && (kind.length === 1)) - main.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); - menu.addchk(handle?.noexp ?? faxis.TestBit(EAxisBits.kNoExponent), 'No exponent', flag => { - if (flag !== faxis.TestBit(EAxisBits.kNoExponent)) - faxis.InvertBit(EAxisBits.kNoExponent); - if (handle) handle.noexp_changed = true; - this[`${kind}_noexp_changed`] = true; - if (main?.snapid && (kind.length === 1)) - main.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); + document.head.appendChild(element); +} - if ((kind === 'z') && isFunc(main?.fillPaletteMenu)) - main.fillPaletteMenu(menu, !is_pal); - if ((handle?.kind === kAxisLabels) && (faxis.fNbins > 20)) { - menu.add('Find label', () => menu.input('Label id').then(id => { - if (!id) return; - for (let bin = 0; bin < faxis.fNbins; ++bin) { - const lbl = handle.formatLabels(bin); - if (lbl === id) - return this.zoom(kind, Math.max(0, bin - 4), Math.min(faxis.fNbins, bin+5)); - } - })); - } +/** @summary load openui5 + * @return {Promise} for loading ready + * @private */ +async function loadOpenui5(args) { + // very simple - openui5 was loaded before and will be used as is + if (typeof globalThis.sap === 'object') + return globalThis.sap; - menu.addTAxisMenu(EAxisBits, main || this, faxis, kind); - return true; - } + if (!args) + args = {}; - const alone = menu.size() === 0; + let rootui5sys = exports.source_dir.replace(/jsrootsys/g, 'rootui5sys'); - if (alone) - menu.add('header:Frame'); + if (rootui5sys === exports.source_dir) { + // if jsrootsys location not detected, try to guess it + if (window.location.port && (window.location.pathname.indexOf('/win') >= 0) && (!args.openui5src || args.openui5src === 'nojsroot' || args.openui5src === 'jsroot')) + rootui5sys = window.location.origin + window.location.pathname + '../rootui5sys/'; else - menu.add('separator'); - - if (this.zoom_xmin !== this.zoom_xmax) - menu.add('Unzoom X', () => this.unzoom('x')); - if (this.zoom_ymin !== this.zoom_ymax) - menu.add('Unzoom Y', () => this.unzoom('y')); - if (this.zoom_zmin !== this.zoom_zmax) - menu.add('Unzoom Z', () => this.unzoom('z')); - if (this.zoom_x2min !== this.zoom_x2max) - menu.add('Unzoom X2', () => this.unzoom('x2')); - if (this.zoom_y2min !== this.zoom_y2max) - menu.add('Unzoom Y2', () => this.unzoom('y2')); - menu.add('Unzoom all', () => this.unzoom('all')); + rootui5sys = undefined; + } - if (pad) { - menu.addchk(pad.fLogx, 'SetLogx', () => this.toggleAxisLog('x')); - menu.addchk(pad.fLogy, 'SetLogy', () => this.toggleAxisLog('y')); + const openui5_sources = []; + let openui5_dflt = 'https://openui5.hana.ondemand.com/1.135.0/', + openui5_root = rootui5sys ? rootui5sys + 'distribution/' : ''; - if (isFunc(main?.getDimension) && (main.getDimension() > 1)) - menu.addchk(pad.fLogz, 'SetLogz', () => this.toggleAxisLog('z')); - menu.add('separator'); + if (isStr(args.openui5src)) { + switch (args.openui5src) { + case 'nodefault': + openui5_dflt = ''; + break; + case 'default': + openui5_sources.push(openui5_dflt); + openui5_dflt = ''; + break; + case 'nojsroot': + /* openui5_root = ''; */ + break; + case 'jsroot': + openui5_sources.push(openui5_root); + openui5_root = ''; + break; + default: + openui5_sources.push(args.openui5src); + break; } + } else if (args.ui5dbg) + openui5_root = ''; // exclude ROOT version in debug mode - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - menu.addAttributesMenu(this, alone ? '' : 'Frame '); - menu.add('Save to gStyle', () => { - gStyle.fPadBottomMargin = this.fY1NDC; - gStyle.fPadTopMargin = 1 - this.fY2NDC; - gStyle.fPadLeftMargin = this.fX1NDC; - gStyle.fPadRightMargin = 1 - this.fX2NDC; - this.fillatt?.saveToStyle('fFrameFillColor', 'fFrameFillStyle'); - this.lineatt?.saveToStyle('fFrameLineColor', 'fFrameLineWidth', 'fFrameLineStyle'); - }, 'Store frame position and graphical attributes to gStyle'); - - menu.add('separator'); - - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.add('endsub:'); - - return true; - } - - /** @summary Fill option object used in TWebCanvas - * @private */ - fillWebObjectOptions(res) { - res.fcust = 'frame'; - res.fopt = [this.scale_xmin || 0, this.scale_ymin || 0, this.scale_xmax || 0, this.scale_ymax || 0]; - } + if (openui5_root && (openui5_sources.indexOf(openui5_root) < 0)) + openui5_sources.push(openui5_root); + if (openui5_dflt && (openui5_sources.indexOf(openui5_dflt) < 0)) + openui5_sources.push(openui5_dflt); - /** @summary Returns frame X position */ - getFrameX() { return this._frame_x || 0; } + return new Promise((resolve, reject) => { + args.resolveFunc = resolve; + args.rejectFunc = reject; - /** @summary Returns frame Y position */ - getFrameY() { return this._frame_y || 0; } + globalThis.completeUI5Loading = function() { + console.log(`Load openui5 version ${globalThis.sap.ui.version} from ${args.load_src}`); - /** @summary Returns frame width */ - getFrameWidth() { return this._frame_width || 0; } + globalThis.sap.ui.loader.config({ + paths: { + jsroot: exports.source_dir, + rootui5: rootui5sys + } + }); - /** @summary Returns frame height */ - getFrameHeight() { return this._frame_height || 0; } + if (args.resolveFunc) { + args.resolveFunc(globalThis.sap); + args.resolveFunc = null; + } - /** @summary Returns frame rectangle plus extra info for hint display */ - getFrameRect() { - return { - x: this._frame_x || 0, - y: this._frame_y || 0, - width: this.getFrameWidth(), - height: this.getFrameHeight(), - transform: this.draw_g?.attr('transform') || '', - hint_delta_x: 0, - hint_delta_y: 0 + delete globalThis.completeUI5Loading; }; - } - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled */ - configureUserClickHandler(handler) { - this._click_handler = isFunc(handler) ? handler : null; - } + tryOpenOpenUI(openui5_sources, args); + }); +} - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ - configureUserDblclickHandler(handler) { - this._dblclick_handler = isFunc(handler) ? handler : null; - } +/* eslint-disable @stylistic/js/key-spacing */ +/* eslint-disable @stylistic/js/comma-spacing */ +/* eslint-disable @stylistic/js/object-curly-spacing */ - /** @summary Function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed - * @param {number} xmin - * @param {number} xmax - * @param {number} [ymin] - * @param {number} [ymax] - * @param {number} [zmin] - * @param {number} [zmax] - * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax) { - if (xmin === 'x') { xmin = xmax; xmax = ymin; ymin = undefined; } else - if (xmin === 'y') { ymax = ymin; ymin = xmax; xmin = xmax = undefined; } else - if (xmin === 'z') { zmin = xmax; zmax = ymin; xmin = xmax = ymin = undefined; } +// some icons taken from http://uxrepo.com/ +const ToolbarIcons = { + camera: { path: 'M 152.00,304.00c0.00,57.438, 46.562,104.00, 104.00,104.00s 104.00-46.562, 104.00-104.00s-46.562-104.00-104.00-104.00S 152.00,246.562, 152.00,304.00z M 480.00,128.00L 368.00,128.00 c-8.00-32.00-16.00-64.00-48.00-64.00L 192.00,64.00 c-32.00,0.00-40.00,32.00-48.00,64.00L 32.00,128.00 c-17.60,0.00-32.00,14.40-32.00,32.00l0.00,288.00 c0.00,17.60, 14.40,32.00, 32.00,32.00l 448.00,0.00 c 17.60,0.00, 32.00-14.40, 32.00-32.00L 512.00,160.00 C 512.00,142.40, 497.60,128.00, 480.00,128.00z M 256.00,446.00c-78.425,0.00-142.00-63.574-142.00-142.00c0.00-78.425, 63.575-142.00, 142.00-142.00c 78.426,0.00, 142.00,63.575, 142.00,142.00 C 398.00,382.426, 334.427,446.00, 256.00,446.00z M 480.00,224.00l-64.00,0.00 l0.00-32.00 l 64.00,0.00 L 480.00,224.00 z' }, + disk: { path: 'M384,0H128H32C14.336,0,0,14.336,0,32v448c0,17.656,14.336,32,32,32h448c17.656,0,32-14.344,32-32V96L416,0H384z M352,160 V32h32v128c0,17.664-14.344,32-32,32H160c-17.664,0-32-14.336-32-32V32h128v128H352z M96,288c0-17.656,14.336-32,32-32h256 c17.656,0,32,14.344,32,32v192H96V288z' }, + question: { path: 'M256,512c141.375,0,256-114.625,256-256S397.375,0,256,0S0,114.625,0,256S114.625,512,256,512z M256,64 c63.719,0,128,36.484,128,118.016c0,47.453-23.531,84.516-69.891,110.016C300.672,299.422,288,314.047,288,320 c0,17.656-14.344,32-32,32c-17.664,0-32-14.344-32-32c0-40.609,37.25-71.938,59.266-84.031 C315.625,218.109,320,198.656,320,182.016C320,135.008,279.906,128,256,128c-30.812,0-64,20.227-64,64.672 c0,17.664-14.336,32-32,32s-32-14.336-32-32C128,109.086,193.953,64,256,64z M256,449.406c-18.211,0-32.961-14.75-32.961-32.969 c0-18.188,14.75-32.953,32.961-32.953c18.219,0,32.969,14.766,32.969,32.953C288.969,434.656,274.219,449.406,256,449.406z' }, + undo: { path: 'M450.159,48.042c8.791,9.032,16.983,18.898,24.59,29.604c7.594,10.706,14.146,22.207,19.668,34.489 c5.509,12.296,9.82,25.269,12.92,38.938c3.113,13.669,4.663,27.834,4.663,42.499c0,14.256-1.511,28.863-4.532,43.822 c-3.009,14.952-7.997,30.217-14.953,45.795c-6.955,15.577-16.202,31.52-27.755,47.826s-25.88,32.9-42.942,49.807 c-5.51,5.444-11.787,11.67-18.834,18.651c-7.033,6.98-14.496,14.366-22.39,22.168c-7.88,7.802-15.955,15.825-24.187,24.069 c-8.258,8.231-16.333,16.203-24.252,23.888c-18.3,18.13-37.354,37.016-57.191,56.65l-56.84-57.445 c19.596-19.472,38.54-38.279,56.84-56.41c7.75-7.685,15.772-15.604,24.108-23.757s16.438-16.163,24.33-24.057 c7.894-7.893,15.356-15.33,22.402-22.312c7.034-6.98,13.312-13.193,18.821-18.651c22.351-22.402,39.165-44.648,50.471-66.738 c11.279-22.09,16.932-43.567,16.932-64.446c0-15.785-3.217-31.005-9.638-45.671c-6.422-14.665-16.229-28.504-29.437-41.529 c-3.282-3.282-7.358-6.395-12.217-9.325c-4.871-2.938-10.381-5.503-16.516-7.697c-6.121-2.201-12.815-3.992-20.058-5.373 c-7.242-1.374-14.9-2.064-23.002-2.064c-8.218,0-16.802,0.834-25.788,2.507c-8.961,1.674-18.053,4.429-27.222,8.271 c-9.189,3.842-18.456,8.869-27.808,15.089c-9.358,6.219-18.521,13.819-27.502,22.793l-59.92,60.271l93.797,94.058H0V40.91 l93.27,91.597l60.181-60.532c13.376-15.018,27.222-27.248,41.536-36.697c14.308-9.443,28.608-16.776,42.89-21.992 c14.288-5.223,28.505-8.74,42.623-10.557C294.645,0.905,308.189,0,321.162,0c13.429,0,26.389,1.185,38.84,3.562 c12.478,2.377,24.2,5.718,35.192,10.029c11.006,4.311,21.126,9.404,30.374,15.265C434.79,34.724,442.995,41.119,450.159,48.042z' }, + arrow_right: { path: 'M30.796,226.318h377.533L294.938,339.682c-11.899,11.906-11.899,31.184,0,43.084c11.887,11.899,31.19,11.893,43.077,0 l165.393-165.386c5.725-5.712,8.924-13.453,8.924-21.539c0-8.092-3.213-15.84-8.924-21.551L338.016,8.925 C332.065,2.975,324.278,0,316.478,0c-7.802,0-15.603,2.968-21.539,8.918c-11.899,11.906-11.899,31.184,0,43.084l113.391,113.384 H30.796c-16.822,0-30.463,13.645-30.463,30.463C0.333,212.674,13.974,226.318,30.796,226.318z' }, + arrow_up: { path: 'M295.505,629.446V135.957l148.193,148.206c15.555,15.559,40.753,15.559,56.308,0c15.555-15.538,15.546-40.767,0-56.304 L283.83,11.662C276.372,4.204,266.236,0,255.68,0c-10.568,0-20.705,4.204-28.172,11.662L11.333,227.859 c-7.777,7.777-11.666,17.965-11.666,28.158c0,10.192,3.88,20.385,11.657,28.158c15.563,15.555,40.762,15.555,56.317,0 l148.201-148.219v493.489c0,21.993,17.837,39.82,39.82,39.82C277.669,669.267,295.505,651.439,295.505,629.446z' }, + arrow_diag: { path: 'M279.875,511.994c-1.292,0-2.607-0.102-3.924-0.312c-10.944-1.771-19.333-10.676-20.457-21.71L233.97,278.348 L22.345,256.823c-11.029-1.119-19.928-9.51-21.698-20.461c-1.776-10.944,4.031-21.716,14.145-26.262L477.792,2.149 c9.282-4.163,20.167-2.165,27.355,5.024c7.201,7.189,9.199,18.086,5.024,27.356L302.22,497.527 C298.224,506.426,289.397,511.994,279.875,511.994z M118.277,217.332l140.534,14.294c11.567,1.178,20.718,10.335,21.878,21.896 l14.294,140.519l144.09-320.792L118.277,217.332z' }, + auto_zoom: { path: 'M505.441,242.47l-78.303-78.291c-9.18-9.177-24.048-9.171-33.216,0c-9.169,9.172-9.169,24.045,0.006,33.217l38.193,38.188 H280.088V80.194l38.188,38.199c4.587,4.584,10.596,6.881,16.605,6.881c6.003,0,12.018-2.297,16.605-6.875 c9.174-9.172,9.174-24.039,0.011-33.217L273.219,6.881C268.803,2.471,262.834,0,256.596,0c-6.229,0-12.202,2.471-16.605,6.881 l-78.296,78.302c-9.178,9.172-9.178,24.045,0,33.217c9.177,9.171,24.051,9.171,33.21,0l38.205-38.205v155.4H80.521l38.2-38.188 c9.177-9.171,9.177-24.039,0.005-33.216c-9.171-9.172-24.039-9.178-33.216,0L7.208,242.464c-4.404,4.403-6.881,10.381-6.881,16.611 c0,6.227,2.477,12.207,6.881,16.61l78.302,78.291c4.587,4.581,10.599,6.875,16.605,6.875c6.006,0,12.023-2.294,16.61-6.881 c9.172-9.174,9.172-24.036-0.005-33.211l-38.205-38.199h152.593v152.063l-38.199-38.211c-9.171-9.18-24.039-9.18-33.216-0.022 c-9.178,9.18-9.178,24.059-0.006,33.222l78.284,78.302c4.41,4.404,10.382,6.881,16.611,6.881c6.233,0,12.208-2.477,16.611-6.881 l78.302-78.296c9.181-9.18,9.181-24.048,0-33.205c-9.174-9.174-24.054-9.174-33.21,0l-38.199,38.188v-152.04h152.051l-38.205,38.199 c-9.18,9.175-9.18,24.037-0.005,33.211c4.587,4.587,10.596,6.881,16.604,6.881c6.01,0,12.024-2.294,16.605-6.875l78.303-78.285 c4.403-4.403,6.887-10.378,6.887-16.611C512.328,252.851,509.845,246.873,505.441,242.47z' }, + statbox: { + path: 'M28.782,56.902H483.88c15.707,0,28.451-12.74,28.451-28.451C512.331,12.741,499.599,0,483.885,0H28.782 C13.074,0,0.331,12.741,0.331,28.451C0.331,44.162,13.074,56.902,28.782,56.902z' + + 'M483.885,136.845H28.782c-15.708,0-28.451,12.741-28.451,28.451c0,15.711,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.74,28.451-28.451C512.331,149.586,499.599,136.845,483.885,136.845z' + + 'M483.885,273.275H28.782c-15.708,0-28.451,12.731-28.451,28.452c0,15.707,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.744,28.451-28.451C512.337,286.007,499.599,273.275,483.885,273.275z' + + 'M256.065,409.704H30.492c-15.708,0-28.451,12.731-28.451,28.451c0,15.707,12.744,28.451,28.451,28.451h225.585 c15.707,0,28.451-12.744,28.451-28.451C284.516,422.436,271.785,409.704,256.065,409.704z' + }, + circle: { path: 'M256,256 m-150,0 a150,150 0 1,0 300,0 a150,150 0 1,0 -300,0' }, + three_circles: { path: 'M256,85 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,255 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,425 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 ' }, + diamand: { path: 'M256,0L384,256L256,511L128,256z' }, + rect: { path: 'M90,90h352v352h-352z' }, + cross: { path: 'M80,40l176,176l176,-176l40,40l-176,176l176,176l-40,40l-176,-176l-176,176l-40,-40l176,-176l-176,-176z' }, + vrgoggles: { size: '245.82 141.73', path: 'M175.56,111.37c-22.52,0-40.77-18.84-40.77-42.07S153,27.24,175.56,27.24s40.77,18.84,40.77,42.07S198.08,111.37,175.56,111.37ZM26.84,69.31c0-23.23,18.25-42.07,40.77-42.07s40.77,18.84,40.77,42.07-18.26,42.07-40.77,42.07S26.84,92.54,26.84,69.31ZM27.27,0C11.54,0,0,12.34,0,28.58V110.9c0,16.24,11.54,30.83,27.27,30.83H99.57c2.17,0,4.19-1.83,5.4-3.7L116.47,118a8,8,0,0,1,12.52-.18l11.51,20.34c1.2,1.86,3.22,3.61,5.39,3.61h72.29c15.74,0,27.63-14.6,27.63-30.83V28.58C245.82,12.34,233.93,0,218.19,0H27.27Z' }, + th2colorz: { recs: [{ x: 128, y: 486, w: 256, h: 26, f: 'rgb(38,62,168)' }, { y: 461, f: 'rgb(22,82,205)' }, { y: 435, f: 'rgb(16,100,220)' }, { y: 410, f: 'rgb(18,114,217)' }, { y: 384, f: 'rgb(20,129,214)' }, { y: 358, f: 'rgb(14,143,209)' }, { y: 333, f: 'rgb(9,157,204)' }, { y: 307, f: 'rgb(13,167,195)' }, { y: 282, f: 'rgb(30,175,179)' }, { y: 256, f: 'rgb(46,183,164)' }, { y: 230, f: 'rgb(82,186,146)' }, { y: 205, f: 'rgb(116,189,129)' }, { y: 179, f: 'rgb(149,190,113)' }, { y: 154, f: 'rgb(179,189,101)' }, { y: 128, f: 'rgb(209,187,89)' }, { y: 102, f: 'rgb(226,192,75)' }, { y: 77, f: 'rgb(244,198,59)' }, { y: 51, f: 'rgb(253,210,43)' }, { y: 26, f: 'rgb(251,230,29)' }, { y: 0, f: 'rgb(249,249,15)' }] }, + th2color: { recs: [{x:0,y:256,w:13,h:39,f:'rgb(38,62,168)'},{x:13,y:371,w:39,h:39},{y:294,h:39},{y:256,h:39},{y:218,h:39},{x:51,y:410,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:90,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294},{y:256,h:39,f:'rgb(16,100,220)'},{y:218,h:39},{y:179,h:39,f:'rgb(22,82,205)'},{y:141,h:39},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:0,h:27},{x:128,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294,f:'rgb(20,129,214)'},{y:256,h:39,f:'rgb(9,157,204)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(20,129,214)'},{y:141,h:39,f:'rgb(16,100,220)'},{y:102,h:39,f:'rgb(22,82,205)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{y:0,h:27},{x:166,y:486,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(82,186,146)'},{y:256,h:39,f:'rgb(179,189,101)'},{y:218,h:39,f:'rgb(116,189,129)'},{y:179,h:39,f:'rgb(82,186,146)'},{y:141,h:39,f:'rgb(14,143,209)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:205,y:486,w:39,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(16,100,220)'},{y:333,h:39,f:'rgb(9,157,204)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(244,198,59)'},{y:218,h:39},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(13,167,195)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(22,82,205)'},{y:26,h:39,f:'rgb(38,62,168)'},{x:243,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(30,175,179)'},{y:294,f:'rgb(209,187,89)'},{y:256,h:39,f:'rgb(251,230,29)'},{y:218,h:39,f:'rgb(249,249,15)'},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(30,175,179)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:282,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(14,143,209)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(226,192,75)'},{y:218,h:39,f:'rgb(244,198,59)'},{y:179,h:39,f:'rgb(149,190,113)'},{y:141,h:39,f:'rgb(9,157,204)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:320,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(46,183,164)'},{y:256,h:39},{y:218,h:39,f:'rgb(82,186,146)'},{y:179,h:39,f:'rgb(9,157,204)'},{y:141,h:39,f:'rgb(20,129,214)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:358,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39},{y:294,f:'rgb(16,100,220)'},{y:256,h:39,f:'rgb(20,129,214)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(18,114,217)'},{y:141,h:39,f:'rgb(22,82,205)'},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:26,h:39},{x:397,y:448,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294,f:'rgb(22,82,205)'},{y:256,h:39},{y:218,h:39},{y:179,h:39,f:'rgb(38,62,168)'},{y:141,h:39},{y:102,h:39},{y:64},{y:26,h:39},{x:435,y:410,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:474,y:256,h:39},{y:179,h:39}] }, + th2draw3d: { + path: 'M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z ' + + 'M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z ' + + 'M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z ' + + 'M460.293,256.149H339.237c-28.521,0-51.721,23.199-51.721,51.726v89.915c0,28.504,23.2,51.715,51.721,51.715h121.045 c28.521,0,51.721-23.199,51.721-51.715v-89.915C512.002,279.354,488.802,256.149,460.293,256.149z M465.03,397.784 c0,2.615-2.122,4.736-4.748,4.736H339.237c-2.614,0-4.747-2.121-4.747-4.736v-89.909c0-2.626,2.121-4.753,4.747-4.753h121.045 c2.615,0,4.748,2.116,4.748,4.753V397.784z' + }, - let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), - unzoom_x = false, unzoom_y = false, unzoom_z = false; + /* eslint-enable @stylistic/js/key-spacing */ + /* eslint-enable @stylistic/js/comma-spacing */ + /* eslint-enable @stylistic/js/object-curly-spacing */ - if (zoom_x) { - let cnt = 0; - if (xmin <= this.xmin) { xmin = this.xmin; cnt++; } - if (xmax >= this.xmax) { xmax = this.xmax; cnt++; } - if (cnt === 2) { zoom_x = false; unzoom_x = true; } - } else - unzoom_x = (xmin === xmax) && (xmin === 0); + createSVG(group, btn, size, title, arg) { + const use_dark = (arg === true) || (arg === false) ? arg : settings.DarkMode, + opacity0 = (arg === 'browser') ? (browser.touches ? 0.2 : 0) : (use_dark ? 0.8 : 0.2), + svg = group.append('svg:svg') + .attr('width', size + 'px') + .attr('height', size + 'px') + .attr('viewBox', '0 0 512 512') + .style('overflow', 'hidden') + .style('cursor', 'pointer') + .style('fill', use_dark ? 'rgba(255, 224, 160)' : 'steelblue') + .style('opacity', opacity0) + .property('opacity0', opacity0) + .property('opacity1', use_dark ? 1 : 0.8) + .on('mouseenter', function() { + const elem = select(this); + elem.style('opacity', elem.property('opacity1')); + const func = elem.node()._mouseenter; + if (isFunc(func)) + func(); + }) + .on('mouseleave', function() { + const elem = select(this); + elem.style('opacity', elem.property('opacity0')); + const func = elem.node()._mouseleave; + if (isFunc(func)) + func(); + }); - if (zoom_y) { - let cnt = 0; - if ((ymin <= this.ymin) || (!this.ymin && this.logy && - ((!this.y_handle?.log_min_nz && ymin < logminfactorY*this.ymax) || (ymin < this.y_handle?.log_min_nz)))) { - ymin = this.ymin; - cnt++; - } - if (ymax >= this.ymax) { ymax = this.ymax; cnt++; } - if ((cnt === 2) && (this.scales_ndim !== 1)) { - zoom_y = false; - unzoom_y = true; + if ('recs' in btn) { + const rec = {}; + for (let n = 0; n < btn.recs.length; ++n) { + Object.assign(rec, btn.recs[n]); + svg.append('rect').attr('x', rec.x).attr('y', rec.y) + .attr('width', rec.w).attr('height', rec.h) + .style('fill', rec.f); } } else - unzoom_y = (ymin === ymax) && (ymin === 0); - - if (zoom_z) { - let cnt = 0; - if (zmin <= this.zmin) { zmin = this.zmin; cnt++; } - if (zmax >= this.zmax) { zmax = this.zmax; cnt++; } - if ((cnt === 2) && (this.scales_ndim > 2)) { zoom_z = false; unzoom_z = true; } - } else - unzoom_z = (zmin === zmax) && (zmin === 0); - - - let changed = false; - - // first process zooming (if any) - if (zoom_x || zoom_y || zoom_z) { - this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) return; - if (zoom_x && obj.canZoomInside('x', xmin, xmax)) { - this.zoom_xmin = xmin; - this.zoom_xmax = xmax; - changed = true; - zoom_x = false; - } - if (zoom_y && obj.canZoomInside('y', ymin, ymax)) { - this.zoom_ymin = ymin; - this.zoom_ymax = ymax; - changed = true; - zoom_y = false; - } - if (zoom_z && obj.canZoomInside('z', zmin, zmax)) { - this.zoom_zmin = zmin; - this.zoom_zmax = zmax; - changed = true; - zoom_z = false; - } - }); - } + svg.append('svg:path').attr('d', btn.path); - // and process unzoom, if any - if (unzoom_x || unzoom_y || unzoom_z) { - if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) changed = true; - this.zoom_xmin = this.zoom_xmax = 0; - } - if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { - changed = true; - unzoomHistogramYRange(this.getMainPainter()); - } - this.zoom_ymin = this.zoom_ymax = 0; - } - if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) changed = true; - this.zoom_zmin = this.zoom_zmax = 0; - } - // than try to unzoom all overlapped objects - if (!changed) { - this.getPadPainter()?.painters?.forEach(painter => { - if (isFunc(painter?.unzoomUserRange)) { - if (painter.unzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) - changed = true; - } - }); - } - } + // special rect to correctly get mouse events for whole button area + svg.append('svg:rect').attr('x', 0).attr('y', 0).attr('width', 512).attr('height', 512) + .style('opacity', 0).style('fill', 'none').style('pointer-events', 'visibleFill') + .append('svg:title').text(title); - return changed ? this.interactiveRedraw('pad', 'zoom').then(() => true) : false; + return svg; } - /** @summary Provide zooming of single axis - * @desc One can specify names like x/y/z but also second axis x2 or y2 - * @private */ - async zoomSingle(name, vmin, vmax) { - if (!this[name+'_handle']) - return false; - - let zoom_v = (vmin !== vmax), unzoom_v = false; +}; // ToolbarIcons - if (zoom_v) { - let cnt = 0; - if (vmin <= this[name+'min']) { vmin = this[name+'min']; cnt++; } - if (vmax >= this[name+'max']) { vmax = this[name+'max']; cnt++; } - if (cnt === 2) { zoom_v = false; unzoom_v = true; } - } else - unzoom_v = (vmin === vmax) && (vmin === 0); +/** @summary Register handle to react on window resize + * @desc function used to react on browser window resize event + * While many resize events could come in short time, + * resize will be handled with delay after last resize event + * @param {object|string} handle can be function or object with checkResize function or dom where painting was done + * @param {number} [delay] - one could specify delay after which resize event will be handled + * @protected */ +function registerForResize(handle, delay) { + if (!handle || isBatchMode() || (typeof window === 'undefined') || (typeof document === 'undefined')) + return; - let changed = false; + let myInterval = null; - // first process zooming - if (zoom_v) { - this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) return; - if (zoom_v && obj.canZoomInside(name[0], vmin, vmax)) { - this[`zoom_${name}min`] = vmin; - this[`zoom_${name}max`] = vmax; - changed = true; - zoom_v = false; - } - }); - } + function ResizeTimer() { + myInterval = null; - // and process unzoom, if any - if (unzoom_v) { - if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) { - changed = true; - if (name === 'y') unzoomHistogramYRange(this.getMainPainter()); + document.body.style.cursor = 'wait'; + if (isFunc(handle)) + handle(); + else if (isFunc(handle?.checkResize)) + handle.checkResize(); + else { + const node = new BasePainter(handle).selectDom(); + if (!node.empty()) { + const mdi = node.property('mdi'); + if (isFunc(mdi?.checkMDIResize)) + mdi.checkMDIResize(); + else + resize(node.node()); } - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; } + document.body.style.cursor = 'auto'; + } - if (!changed) return false; + window.addEventListener('resize', () => { + if (myInterval) + clearTimeout(myInterval); + myInterval = setTimeout(ResizeTimer, Math.max(20, delay || 300)); + }); +} - return this.interactiveRedraw('pad', 'zoom').then(() => true); - } +/** @summary Detect mouse right button + * @private */ +function detectRightButton(event) { + return (event?.buttons === 2) || (event?.button === 2); +} - /** @summary Checks if specified axis zoomed */ - isAxisZoomed(axis) { - return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; - } +/** @summary Add move handlers for drawn element + * @private */ +function addMoveHandler(painter, enabled = true, hover_handler = false) { + if (!settings.MoveResize || painter.isBatchMode() || !painter.getG()) + return; - /** @summary Unzoom speicified axes - * @return {Promise} with boolean flag if zooming changed */ - async unzoom(dox, doy, doz) { - if (dox === 'all') - return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); + if (painter.getPadPainter()?.isEditable() === false) + enabled = false; - if ((dox === 'x2') || (dox === 'y2')) { - return this.zoomSingle(dox, 0, 0).then(changed => { - if (changed) this.zoomChangedInteractive(dox, 'unzoom'); - return changed; - }); + if (!enabled) { + if (painter.getG().property('assigned_move')) { + const drag_move = drag().subject(Object); + drag_move.on('start', null).on('drag', null).on('end', null); + painter.getG() + .style('cursor', null) + .property('assigned_move', null) + .call(drag_move); } + return; + } - if (typeof dox === 'undefined') dox = doy = doz = true; else - if (isStr(dox)) { doz = dox.indexOf('z') >= 0; doy = dox.indexOf('y') >= 0; dox = dox.indexOf('x') >= 0; } + if (painter.getG().property('assigned_move')) + return; - return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, - doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined).then(changed => { - if (changed && dox) this.zoomChangedInteractive('x', 'unzoom'); - if (changed && doy) this.zoomChangedInteractive('y', 'unzoom'); - if (changed && doz) this.zoomChangedInteractive('z', 'unzoom'); + const drag_move = drag().subject(Object); + let not_changed = true, move_disabled = false; - return changed; - }); - } + drag_move + .on('start', function(evnt) { + move_disabled = this.moveEnabled ? !this.moveEnabled() : false; + if (move_disabled || detectRightButton(evnt.sourceEvent)) + return; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + const pos = pointer(evnt, this.getG().node()); + not_changed = true; + if (this.moveStart) + this.moveStart(pos[0], pos[1], evnt.sourceEvent); + }.bind(painter)).on('drag', function(evnt) { + if (move_disabled) + return; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + not_changed = false; + if (this.moveDrag) + this.moveDrag(evnt.dx, evnt.dy, evnt.sourceEvent); + }.bind(painter)).on('end', function(evnt) { + if (move_disabled) + return; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + if (this.moveEnd) + this.moveEnd(not_changed, evnt.sourceEvent); - /** @summary Mark/check if zoom for specific axis was changed interactively - * @private */ - zoomChangedInteractive(axis, value) { - if (axis === 'reset') { - this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; - return; - } - if (!axis || axis === 'any') - return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; + let arg = null; + if (not_changed) { + // if not changed - provide click position + const pos = pointer(evnt, this.getG().node()); + arg = { x: pos[0], y: pos[1], dbl: false }; + } + this.getPadPainter()?.selectObjectPainter(this, arg); + }.bind(painter)); - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) return; + painter.getG() + .style('cursor', hover_handler ? 'pointer' : 'move') + .property('assigned_move', true) + .call(drag_move); - const fld = 'zoom_changed_' + axis; - if (value === undefined) - return this[fld]; + if (hover_handler) { + painter.getG().on('mouseenter', () => painter.getG().style('text-decoration', 'underline')) + .on('mouseleave', () => painter.getG().style('text-decoration', null)); + } +} - if (value === 'unzoom') { - // special handling of unzoom, only if was never changed before flag set to true - this[fld] = (this[fld] === undefined); - return; +/** @summary Inject style + * @param {String} code - css string + * @private */ +function injectStyle(code, node, tag) { + if (isBatchMode() || !code || (typeof document === 'undefined')) + return true; + + const styles = (node || document).getElementsByTagName('style'); + for (let n = 0; n < styles.length; ++n) { + if (tag && styles[n].getAttribute('tag') === tag) { + styles[n].innerText = code; + return true; } - if (value) - this[fld] = true; + if (styles[n].innerText === code) + return true; } - /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; } - - /** @summary Show axis status message - * @desc method called normally when mouse enter main object element - * @private */ - showAxisStatus(axis_name, evnt) { - const taxis = this.getAxis(axis_name), - m = pointer(evnt, this.getFrameSvg().node()); - let hint_name = axis_name, - hint_title = clTAxis, - id = (axis_name === 'x') ? 0 : 1; - - if (taxis) { - hint_name = taxis.fName; - hint_title = taxis.fTitle || `TAxis object for ${axis_name}`; - } - if (this.swap_xy) id = 1 - id; - - const axis_value = this.revertAxis(axis_name, m[id]); + const element = document.createElement('style'); + if (tag) + element.setAttribute('tag', tag); + element.innerText = code; + (node || document.head).appendChild(element); + return true; +} - this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${m[0]},${m[1]}`); +/** @summary Select predefined style + * @private */ +function selectgStyle(name) { + gStyle.fName = name; + switch (name) { + case 'Modern': Object.assign(gStyle, { fFrameBorderMode: 0, fFrameFillColor: 0, + fCanvasBorderMode: 0, fCanvasColor: 0, fPadBorderMode: 0, fPadColor: 0, fStatColor: 0, + fTitleAlign: 23, fTitleX: 0.5, fTitleBorderSize: 0, fTitleColor: 0, fTitleStyle: 0, + fOptStat: 1111, fStatY: 0.935, + fLegendBorderSize: 1, fLegendFont: 42, fLegendTextSize: 0, fLegendFillColor: 0 }); + break; + case 'Plain': Object.assign(gStyle, { fFrameBorderMode: 0, + fCanvasBorderMode: 0, fPadBorderMode: 0, fPadColor: 0, fCanvasColor: 0, + fTitleColor: 0, fTitleBorderSize: 0, fStatColor: 0, fStatBorderSize: 1, fLegendBorderSize: 1 }); + break; + case 'Bold': Object.assign(gStyle, { fCanvasColor: 10, fCanvasBorderMode: 0, + fFrameLineWidth: 3, fFrameFillColor: 10, + fPadColor: 10, fPadTickX: 1, fPadTickY: 1, fPadBottomMargin: 0.15, fPadLeftMargin: 0.15, + fTitleColor: 10, fTitleTextColor: 600, fStatColor: 10 }); + break; } +} - /** @summary Add interactive keys handlers - * @private */ - addKeysHandler() { - if (this.isBatchMode()) return; - FrameInteractive.assign(this); - this.addFrameKeysHandler(); - } +let _storage_prefix = 'jsroot_'; - /** @summary Add interactive functionality to the frame - * @private */ - addInteractivity(for_second_axes) { - if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) - return false; +/** @summary Set custom prefix for the local storage + * @private */ +function setStoragePrefix(prefix) { + _storage_prefix = prefix || 'jsroot_'; +} - FrameInteractive.assign(this); - if (!for_second_axes) - this.addBasicInteractivity(); +/** @summary Save object in local storage + * @private */ +function saveLocalStorage(obj, expires, name) { + if (typeof localStorage === 'undefined') + return; + if (Number.isFinite(expires) && (expires < 0)) + localStorage.removeItem(_storage_prefix + name); + else + localStorage.setItem(_storage_prefix + name, btoa_func(JSON.stringify(obj))); +} - return this.addFrameInteractivity(for_second_axes); - } +/** @summary Read object from storage with specified name + * @private */ +function readLocalStorage(name) { + if (typeof localStorage === 'undefined') + return null; + const v = localStorage.getItem(_storage_prefix + name), + s = v ? JSON.parse(atob_func(v)) : null; + return isObject(s) ? s : null; +} -} // class TFramePainter +/** @summary Save JSROOT settings in local storage + * @param {Number} [expires] - delete settings when negative + * @param {String} [name] - storage name, 'settings' by default + * @private */ +function saveSettings(expires = 365, name = 'settings') { + saveLocalStorage(settings, expires, name); +} -/** @summary Current hierarchy painter - * @desc Instance of {@link HierarchyPainter} object +/** @summary Read JSROOT settings from specified cookie parameter + * @param {Boolean} only_check - when true just checks if settings were stored before with provided name + * @param {String} [name] - storage name, 'settings' by default * @private */ -let first_hpainter = null; +function readSettings(only_check = false, name = 'settings') { + const s = readLocalStorage(name); + if (!s) + return false; + if (!only_check) + Object.assign(settings, s); + return true; +} -/** @summary Returns current hierarchy painter object +/** @summary Save JSROOT gStyle object in local storage + * @param {Number} [expires] - delete style when negative + * @param {String} [name] - storage name, 'style' by default * @private */ -function getHPainter() { return first_hpainter; } +function saveStyle(expires = 365, name = 'style') { + saveLocalStorage(gStyle, expires, name); +} -/** @summary Set hierarchy painter object +/** @summary Read JSROOT gStyle object from local storage + * @param {Boolean} [only_check] - when true just checks if settings were stored before with provided name + * @param {String} [name] - storage name, 'style' by default * @private */ -function setHPainter(hp) { first_hpainter = hp; } +function readStyle(only_check = false, name = 'style') { + const s = readLocalStorage(name); + if (!s) + return false; + if (!only_check) + Object.assign(gStyle, s); + return true; +} -/** - * @summary Base class to manage multiple document interface for drawings - * - * @private - */ +let _saveFileFunc = null; -class MDIDisplay extends BasePainter { +/** @summary Returns image file content as it should be stored on the disc + * @desc Replaces all kind of base64 coding + * @private */ +function getBinFileContent(content) { + if (content.indexOf(prSVG) === 0) + return decodeURIComponent(content.slice(prSVG.length)); - /** @summary constructor */ - constructor(frameid) { - super(); - this.frameid = frameid; - if (frameid !== '$batch$') { - this.setDom(frameid); - this.selectDom().property('mdi', this); - } - this.cleanupFrame = cleanup; // use standard cleanup function by default - this.active_frame_title = ''; // keep title of active frame - } + if (content.indexOf(prJSON) === 0) + return decodeURIComponent(content.slice(prJSON.length)); - /** @summary Assign func which called for each newly created frame */ - setInitFrame(func) { - this.initFrame = func; - this.forEachFrame(frame => func(frame)); + if ((content.indexOf('data:image/') === 0) || (content.indexOf('data:application/pdf') === 0)) { + const p = content.indexOf('base64,'); + if (p > 0) + return atob_func(content.slice(p + 7)); } - /** @summary method called before new frame is created */ - beforeCreateFrame(title) { this.active_frame_title = title; } + return content; +} - /** @summary method called after new frame is created - * @private */ - afterCreateFrame(frame) { - if (isFunc(this.initFrame)) - this.initFrame(frame); - return frame; - } +/** @summary Returns type of file content + * @private */ +function getContentType(content) { + if (content.indexOf('data:')) + return ''; - /** @summary method dedicated to iterate over existing panels - * @param {function} userfunc is called with arguments (frame) - * @param {boolean} only_visible let select only visible frames */ - forEachFrame(userfunc, only_visible) { - console.warn(`forEachFrame not implemented in MDIDisplay ${typeof userfunc} ${only_visible}`); - } + const p = content.indexOf(';'); + return (p > 0) ? content.slice(5, p) : ''; +} - /** @summary method dedicated to iterate over existing panles - * @param {function} userfunc is called with arguments (painter, frame) - * @param {boolean} only_visible let select only visible frames */ - forEachPainter(userfunc, only_visible) { - this.forEachFrame(frame => { - new ObjectPainter(frame).forEachPainter(painter => userfunc(painter, frame)); - }, only_visible); - } +/** @summary Function store content as file with filename + * @private */ +async function saveFile(filename, content) { + if (isFunc(_saveFileFunc)) + return _saveFileFunc(filename, getBinFileContent(content)); + if (isNodeJs()) { + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { + fs.writeFileSync(filename, getBinFileContent(content)); + return true; + }); + } else if (typeof document === 'undefined') + return false; - /** @summary Returns total number of drawings */ - numDraw() { - let cnt = 0; - this.forEachFrame(() => ++cnt); - return cnt; - } + const a = document.createElement('a'); + a.download = filename; + a.style.display = 'none'; + let fileURL = ''; + const contentType = getContentType(content); + + if ((content.length > 1e6) && (contentType === 'application/pdf')) { + // large PDF files do not work in the browser with plain base64 coding + const bindata = getBinFileContent(content), + len = bindata.length, + buffer = new ArrayBuffer(len), + view = new DataView(buffer, 0, len); + for (let i = 0; i < len; ++i) + view.setUint8(i, bindata.charCodeAt(i)); + const blob = new Blob([buffer], { type: contentType }); + fileURL = URL.createObjectURL(blob); + a.href = fileURL; + } else + a.href = content; - /** @summary Serach for the frame using item name */ - findFrame(searchtitle, force) { - let found_frame = null; + document.body.appendChild(a); - this.forEachFrame(frame => { - if (select(frame).attr('frame_title') === searchtitle) - found_frame = frame; + return new Promise(resolve => { + a.addEventListener('click', () => { + if (fileURL) { + setTimeout(() => { + a.parentNode.removeChild(a); + URL.revokeObjectURL(fileURL); + }, 3000); + } else + a.parentNode.removeChild(a); + resolve(true); }); + a.click(); + }); +} - if (!found_frame && force) - found_frame = this.createFrame(searchtitle); +/** @summary Function store content as file with filename + * @private */ +function setSaveFile(func) { + _saveFileFunc = func; +} - return found_frame; +/** @summary Returns color id for the color + * @private */ +function getColorId(col) { + const arr = getRootColors(); + let id = -1; + if (isStr(col)) { + if (!col || (col === 'none')) + id = 0; + else { + for (let k = 1; k < arr.length; ++k) { + if (arr[k] === col) { + id = k; + break; + } + } + } + if ((id < 0) && (col.indexOf('rgb') === 0)) + id = 9999; + } else if (Number.isInteger(col) && arr[col]) { + id = col; + col = arr[id]; } - /** @summary Activate frame */ - activateFrame(frame) { this.active_frame_title = frame ? select(frame).attr('frame_title') : ''; } + return { id, col }; +} - /** @summary Return active frame */ - getActiveFrame() { return this.findFrame(this.active_frame_title); } +/** @summary Produce exec string for WebCanvas to set color value + * @desc Color can be id or string, but should belong to list of known colors + * For higher color numbers TColor::GetColor(r,g,b) will be invoked to ensure color is exists + * @private */ +function getColorExec(col, method, extra_arg) { + const d = getColorId(col); - /** @summary perform resize for each frame - * @protected */ - checkMDIResize(only_frame_id, size) { - let resized_frame = null; + if (d.id < 0) + return ''; - this.forEachPainter((painter, frame) => { - if (only_frame_id && (select(frame).attr('id') !== only_frame_id)) return; + if (!extra_arg) + extra_arg = ''; + else + extra_arg += ','; - if ((painter.getItemName() !== null) && isFunc(painter.checkResize)) { - // do not call resize for many painters on the same frame - if (resized_frame === frame) return; - painter.checkResize(size); - resized_frame = frame; - } - }); + // for higher color numbers ensure that such color exists + if (d.id >= 50) { + const c = color(d.col); + d.id = `TColor::GetColor(${c.r},${c.g},${c.b})`; } - /** @summary Cleanup all drawings */ - cleanup() { - this.active_frame_title = ''; - - this.forEachFrame(this.cleanupFrame); + return `exec:${method}(${extra_arg}${d.id})`; +} - this.selectDom().html('').property('mdi', null); +/** @summary Change object member in the painter + * @desc Used when interactively change in the menu + * Special handling for color is provided + * @private */ +function changeObjectMember(painter, member, val, is_color) { + if (is_color) { + const d = getColorId(val); + if ((d.id < 0) || (d.id === 9999)) + return; + val = d.id; } -} // class MDIDisplay + const obj = painter?.getObject(); + if (obj && (obj[member] !== undefined)) + obj[member] = val; +} +Object.assign(internals.jsroot, { addMoveHandler, registerForResize }); + +const kToFront = '__front__', kNoReorder = '__no_reorder', + sDfltName = 'root_ctx_menu', sDfltDlg = '_dialog', + sSub = 'sub:', sEndsub = 'endsub:', + sColumn = 'column:', sEndcolumn = 'endcolumn:', + sSeparator = 'separator', sHeader = 'header:'; /** - * @summary Custom MDI display + * @summary Abstract class for creating context menu * - * @desc All HTML frames should be created before and add via {@link CustomDisplay#addFrame} calls + * @desc Use {@link createMenu} to create instance of the menu * @private */ -class CustomDisplay extends MDIDisplay { +class JSRootMenu { - constructor() { - super('dummy'); - this.frames = {}; // array of configured frames + constructor(painter, menuname, show_event) { + this.painter = painter; + this.menuname = menuname; + if (isObject(show_event) && (show_event.clientX !== undefined) && (show_event.clientY !== undefined)) + this.show_evnt = { clientX: show_event.clientX, clientY: show_event.clientY, skip_close: show_event.skip_close }; + + this.remove_handler = () => this.remove(); + this.element = null; + this.cnt = 0; } - addFrame(divid, itemname) { - const prev = this.frames[divid] || ''; - this.frames[divid] = prev + (itemname + ';'); + native() { return false; } + + async load() { return this; } + + /** @summary Returns object with mouse event position when context menu was activated + * @desc Return object will have members 'clientX' and 'clientY' */ + getEventPosition() { return this.show_evnt; } + + add(/* name, arg, func, title */) { + throw Error('add() method has to be implemented in the menu'); } - forEachFrame(userfunc) { - const ks = Object.keys(this.frames); - for (let k = 0; k < ks.length; ++k) { - const node = select('#'+ks[k]); - if (!node.empty()) - userfunc(node.node()); + /** @summary Returns menu size */ + size() { return this.cnt; } + + /** @summary Close and remove menu */ + remove() { + if (!this.element) + return; + + if (this.show_evnt?.skip_close) { + this.show_evnt.skip_close = 0; + return; + } + + this.element.remove(); + this.element = null; + if (isFunc(this.resolveFunc)) { + const func = this.resolveFunc; + delete this.resolveFunc; + func(); } + document.body.removeEventListener('click', this.remove_handler); } - createFrame(title) { - this.beforeCreateFrame(title); + show(/* event */) { + throw Error('show() method has to be implemented in the menu class'); + } - const ks = Object.keys(this.frames); - for (let k = 0; k < ks.length; ++k) { - const items = this.frames[ks[k]]; - if (items.indexOf(title+';') >= 0) - return select('#'+ks[k]).node(); + /** @summary Add checked menu item + * @param {boolean} flag - flag + * @param {string} name - item name + * @param {function} func - func called when item is selected + * @param {string} [title] - optional title */ + addchk(flag, name, arg, func, title) { + let handler = func; + if (isFunc(arg)) { + title = func; + func = arg; + handler = res => func(res === '1'); + arg = flag ? '0' : '1'; } - return null; + this.add((flag ? 'chk:' : 'unk:') + name, arg, handler, title); } - cleanup() { - super.cleanup(); - this.forEachFrame(frame => select(frame).html('')); + /** @summary Add sub-menu */ + sub(name, arg, func, title) { + this.add(sSub + name, arg, func, title); } -} // class CustomDisplay + /** @summary Mark end of submenu */ + endsub() { this.add(sEndsub); } -/** - * @summary Generic grid MDI display - * - * @private - */ + /** @summary Start column with items */ + column() { this.add(sColumn); } -class GridDisplay extends MDIDisplay { + /** @summary End column with items */ + endcolumn() { this.add(sEndcolumn); } - /** @summary Create GridDisplay instance - * @param {string} frameid - where grid display is created - * @param {string} kind - kind of grid - * @desc following kinds are supported - * - vertical or horizontal - only first letter matters, defines basic orientation - * - 'x' in the name disable interactive separators - * - v4 or h4 - 4 equal elements in specified direction - * - v231 - created 3 vertical elements, first divided on 2, second on 3 and third on 1 part - * - v23_52 - create two vertical elements with 2 and 3 subitems, size ratio 5:2 - * - gridNxM - normal grid layout without interactive separators - * - gridiNxM - grid layout with interactive separators - * - simple - no layout, full frame used for object drawings */ - constructor(frameid, kind, kind2) { - super(frameid); + /** @summary Add separator */ + separator() { this.add(sSeparator); } - this.framecnt = 0; - this.getcnt = 0; - this.groups = []; - this.vertical = kind && (kind[0] === 'v'); - this.use_separarators = !kind || (kind.indexOf('x') < 0); - this.simple_layout = false; + /** @summary Add menu header - must be first entry */ + header(name, title) { + this.add(sHeader + name, undefined, undefined, title); + } - const dom = this.selectDom(); - dom.style('overflow', 'hidden'); + /** @summary Add draw sub-menu with draw options + * @protected */ + addDrawMenu(top_name, opts, call_back, title) { + if (!opts || !opts.length) + return; - if (kind === 'simple') { - this.simple_layout = true; - this.use_separarators = false; - this.framecnt = 1; + let without_sub = false; + if (top_name.indexOf('nosub:') === 0) { + without_sub = true; + top_name = top_name.slice(6); + } + + if (opts.length === 1) { + if (opts[0] === kInspect) + top_name = top_name.replace('Draw', 'Inspect'); + this.add(top_name, opts[0], call_back); return; } - let num = 2, arr, sizes, chld_sizes; + const used = {}; - if (kind === 'projxy') { - this.vertical = false; - this.use_separarators = true; - arr = [2, 2]; - sizes = [1, 3]; - chld_sizes = [[3, 1], [3, 1]]; - kind = ''; - this.match_sizes = true; - } else if ((kind.indexOf('grid') === 0) || kind2) { - if (kind2) kind = kind + 'x' + kind2; - else kind = kind.slice(4).trim(); - this.use_separarators = false; - if (kind[0] === 'i') { - this.use_separarators = true; - kind = kind.slice(1); - } + if (!without_sub) + this.sub(top_name, opts[0], call_back, title); - const separ = kind.indexOf('x'); - let sizex, sizey; + if ((opts.indexOf('') >= 0) && (!without_sub || opts[0])) + this.add(this._use_plain_text ? '' : '<dflt>', '', call_back); - if (separ > 0) { - sizey = parseInt(kind.slice(separ + 1)); - sizex = parseInt(kind.slice(0, separ)); - } else - sizex = sizey = parseInt(kind); + for (let i = 0; i < opts.length; ++i) { + let name = opts[i]; + if (!name || used[name]) + continue; + used[name] = true; + const group = []; + if (opts.length > 5) { + // check if there are similar options, which can be grouped again + for (let i2 = i + 1; i2 < opts.length; ++i2) { + if (opts[i2] && !used[opts[i2]] && (opts[i2].indexOf(name) === 0)) + group.push(opts[i2]); + else if (name.length < 4) + break; + } + } - if (!Number.isInteger(sizex)) sizex = 3; - if (!Number.isInteger(sizey)) sizey = 3; + if (without_sub) + name = top_name + ' ' + name; - if (sizey > 1) { - this.vertical = true; - num = sizey; - if (sizex > 1) - arr = new Array(num).fill(sizex); - } else if (sizex > 1) { - this.vertical = false; - num = sizex; - } else { - this.simple_layout = true; - this.use_separarators = false; - this.framecnt = 1; - return; - } - kind = ''; + if (group.length) { + this.sub(name, opts[i], call_back); + group.forEach(sub => { + this.add(sub, sub, call_back); + used[sub] = true; + }); + this.endsub(); + } else if (name === kInspect) { + this.sub(name, opts[i], call_back, 'Inspect object content'); + for (let k = 0; k < 10; ++k) + this.add(k.toString(), kInspect + k, call_back, `Inspect object and expand to level ${k}`); + this.endsub(); + } else + this.add(name, opts[i], call_back); } - - if (kind && kind.indexOf('_') > 0) { - let arg = parseInt(kind.slice(kind.indexOf('_')+1), 10); - if (Number.isInteger(arg) && (arg > 10)) { - kind = kind.slice(0, kind.indexOf('_')); - sizes = []; - while (arg > 0) { - sizes.unshift(Math.max(arg % 10, 1)); - arg = Math.round((arg-sizes[0])/10); - if (sizes[0] === 0) sizes[0] = 1; - } - } + if (!without_sub) { + this.add('', () => { + const opt = isFunc(this.painter?.getDrawOpt) ? this.painter.getDrawOpt() : opts[0]; + this.input('Provide draw option', opt, 'text').then(call_back); + }, 'Enter draw option in dialog'); + this.endsub(); } + } - kind = kind ? parseInt(kind.replace(/^\D+/g, ''), 10) : 0; - if (Number.isInteger(kind) && (kind > 1)) { - if (kind < 10) - num = kind; - else { - arr = []; - while (kind > 0) { - arr.unshift(kind % 10); - kind = Math.round((kind-arr[0])/10); - if (arr[0] === 0) arr[0] = 1; - } - num = arr.length; - } - } + /** @summary Add redraw menu for the painter + * @protected */ + addRedrawMenu(painter) { + if (!painter || !isFunc(painter.redrawWith) || !isFunc(painter.getSupportedDrawOptions)) + return false; - if (sizes?.length !== num) - sizes = undefined; - if (chld_sizes?.length !== num) - chld_sizes = undefined; + const opts = painter.getSupportedDrawOptions(); - if (!this.simple_layout) - this.createGroup(this, dom, num, arr, sizes, chld_sizes); - } + this.addDrawMenu(`Draw ${painter.getClassName()} with`, opts, arg => { + if ((arg.indexOf(kInspect) === 0) && isFunc(painter.showInspector)) + return painter.showInspector(arg); - /** @summary Create frames group - * @private */ - createGroup(handle, main, num, childs, sizes, childs_sizes) { - if (!sizes) sizes = new Array(num); - let sum1 = 0, sum2 = 0; - for (let n = 0; n < num; ++n) - sum1 += (sizes[n] || 1); - for (let n = 0; n < num; ++n) { - sizes[n] = Math.round(100 * (sizes[n] || 1) / sum1); - sum2 += sizes[n]; - if (n === num-1) sizes[n] += (100-sum2); // make 100% - } + painter.redrawWith(arg); + }); + return true; + } - for (let cnt = 0; cnt < num; ++cnt) { - const group = { id: cnt, drawid: -1, position: 0, size: sizes[cnt], parent: handle }; - if (cnt > 0) group.position = handle.groups[cnt-1].position + handle.groups[cnt-1].size; - group.position0 = group.position; + /** @summary Add color selection menu entries + * @protected */ + addColorMenu(name, value, set_func, fill_kind) { + if (value === undefined) + return; + const useid = !isStr(value); + this.sub(name, () => { + this.input('Enter color ' + (useid ? '(only id number)' : '(name or id)'), value, useid ? 'int' : 'text', useid ? 0 : undefined, useid ? 9999 : undefined).then(col => { + const id = parseInt(col); + if (Number.isInteger(id) && getColor(id)) + col = getColor(id); + else if (useid) + return; - if (!childs || !childs[cnt] || childs[cnt] < 2) - group.drawid = this.framecnt++; + set_func(useid ? id : col); + }); + }); - handle.groups.push(group); + for (let ncolumn = 0; ncolumn < 5; ++ncolumn) { + this.column(); - const elem = main.append('div').attr('groupid', group.id); + for (let nrow = 0; nrow < 10; nrow++) { + let n = ncolumn * 10 + nrow; + if (!useid) + --n; // use -1 as none color - // remember HTML node only when need to match sizes of different groups - if (handle.match_sizes) - group.node = elem.node(); + const col = (n < 0) || ((n === 0) && (fill_kind === 1)) ? 'none' : getColor(n), + lbl = (n <= 0) || ((col[0] !== '#') && (col.indexOf('rgb') < 0)) ? col : `col ${n}`, + fill = (n === 1) ? 'white' : 'black', + stroke = (n === 1) ? 'red' : 'black', + rect = (value === (useid ? n : col)) ? `` : '', + svg = `${rect}${lbl}`; - if (handle.vertical) - elem.style('float', 'bottom').style('height', group.size.toFixed(2)+'%').style('width', '100%'); - else - elem.style('float', 'left').style('width', group.size.toFixed(2)+'%').style('height', '100%'); + this.add(svg, (useid ? n : col), res => set_func(useid ? parseInt(res) : res), 'Select color ' + col); + } - if (group.drawid >= 0) { - elem.classed('jsroot_newgrid', true); - if (isStr(this.frameid)) - elem.attr('id', `${this.frameid}_${group.drawid}`); - } else - elem.style('display', 'flex').style('flex-direction', handle.vertical ? 'row' : 'column'); + this.endcolumn(); + if (!this.native()) + break; + } + this.endsub(); + } - if (childs && (childs[cnt] > 1)) { - group.vertical = !handle.vertical; - group.groups = []; - elem.style('overflow', 'hidden'); - this.createGroup(group, elem, childs[cnt], null, childs_sizes ? childs_sizes[cnt] : null); - } + /** @summary Add size selection menu entries + * @protected */ + addSizeMenu(name, min, max, step, size_value, set_func, title) { + if (size_value === undefined) + return; + + let values = [], miss_current = false; + if (isObject(step)) { + values = step; + step = 1; + } else { + for (let sz = min; sz <= max; sz += step) + values.push(sz); } - if (this.use_separarators && isFunc(this.createSeparator)) { - for (let cnt = 1; cnt < num; ++cnt) - this.createSeparator(handle, main, handle.groups[cnt]); + const match = v => Math.abs(v - size_value) < (max - min) * 1e-5, + conv = (v, more) => { + if ((v === size_value) && miss_current) + more = true; + if (step >= 1) + return v.toFixed(0); + if (step >= 0.1) + return v.toFixed(more ? 2 : 1); + return v.toFixed(more ? 4 : 2); + }; + + if (values.findIndex(match) < 0) { + miss_current = true; + values.push(size_value); + values = values.sort((a, b) => a > b); } + + this.sub(name, () => this.input('Enter value of ' + name, conv(size_value, true), (step >= 1) ? 'int' : 'float').then(set_func), title); + values.forEach(v => this.addchk(match(v), conv(v), v, res => set_func((step >= 1) ? Number.parseInt(res) : Number.parseFloat(res)))); + this.endsub(); } - /** @summary Handle interactive sepearator movement - * @private */ - handleSeparator(elem, action) { - const findGroup = (node, grid) => { - let chld = node?.firstChild; - while (chld) { - if (chld.getAttribute('groupid') === grid) - return select(chld); - chld = chld.nextSibling; - } - // should never happen, but keep it here like - return select(node).select(`[groupid='${grid}']`); - }, setGroupSize = (h, node, grid) => { - const name = h.vertical ? 'height' : 'width', - size = h.groups[grid].size.toFixed(2)+'%'; - findGroup(node, grid).style(name, size) - .selectAll('.jsroot_separator').style(name, size); - }, resizeGroup = (node, grid) => { - let sel = findGroup(node, grid); - if (!sel.classed('jsroot_newgrid')) - sel = sel.select('.jsroot_newgrid'); - sel.each(function() { resize(this); }); - }, posSepar = (h, group, separ) => { - separ.style(h.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`); - }, separ = select(elem), - parent = elem.parentNode, - handle = separ.property('handle'), - id = separ.property('separator_id'), - group = handle.groups[id]; - let needResize = false, needSetSize = false; + /** @summary Add palette menu entries + * @protected */ + addPaletteMenu(curr, set_func) { + const add = (id, name, title, more) => { + if (!name) + name = `pal ${id}`; + else if (!title) + title = name; + if (title) + title += `, code ${id}`; + this.addchk((id === curr) || more, name, id, set_func, title || name); + }; - if (action === 'start') { - group.startpos = group.position; - group.acc_drag = 0; - return; - } + this.sub('Palette', () => this.input('Enter palette code [1..113]', curr, 'int', 1, 113).then(set_func)); - if (action === 'end') { - if (Math.abs(group.startpos - group.position) < 0.5) - return; - needResize = true; - } else { - let pos; - if (action === 'restore') - pos = group.position0; - else if (handle.vertical) { - group.acc_drag += action.dy; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientHeight) * 100; - } else { - group.acc_drag += action.dx; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientWidth) * 100; - } + this.column(); + add(57, 'Bird', 'Default color palette', (curr > 113)); + add(55, 'Rainbow'); + add(51, 'Deep Sea'); + add(52, 'Grayscale', 'New gray scale'); + add(1, '', 'Old gray scale', (curr > 0) && (curr < 10)); + add(50, 'ROOT 5', 'Default color palette in ROOT 5', (curr >= 10) && (curr < 51)); + add(53, '', 'Dark body radiator'); + add(54, '', 'Two-color hue'); + add(56, '', 'Inverted dark body radiator'); + add(58, 'Cubehelix'); + add(59, '', 'Green Red Violet'); + add(60, '', 'Blue Red Yellow'); + add(61, 'Ocean'); + this.endcolumn(); - const diff = group.position - pos; + if (!this.native()) + return this.endsub(); - if (Math.abs(diff) < 0.3) return; // if no significant change, do nothing + this.column(); + add(62, '', 'Color Printable On Grey'); + add(63, 'Alpine'); + add(64, 'Aquamarine'); + add(65, 'Army'); + add(66, 'Atlantic'); + add(67, 'Aurora'); + add(68, 'Avocado'); + add(69, 'Beach'); + add(70, 'Black Body'); + add(71, '', 'Blue Green Yellow'); + add(72, 'Brown Cyan'); + add(73, 'CMYK'); + add(74, 'Candy'); + this.endcolumn(); - // do not change if size too small - if (Math.min(handle.groups[id-1].size - diff, group.size+diff) < 3) return; + this.column(); + add(75, 'Cherry'); + add(76, 'Coffee'); + add(77, '', 'Dark Rain Bow'); + add(78, '', 'Dark Terrain'); + add(79, 'Fall'); + add(80, 'Fruit Punch'); + add(81, 'Fuchsia'); + add(82, 'Grey Yellow'); + add(83, '', 'Green Brown Terrain'); + add(84, 'Green Pink'); + add(85, 'Island'); + add(86, 'Lake'); + add(87, '', 'Light Temperature'); + this.endcolumn(); - handle.groups[id-1].size -= diff; - group.size += diff; - group.position = pos; + this.column(); + add(88, '', 'Light Terrain'); + add(89, 'Mint'); + add(90, 'Neon'); + add(91, 'Pastel'); + add(92, 'Pearl'); + add(93, 'Pigeon'); + add(94, 'Plum'); + add(95, 'Red Blue'); + add(96, 'Rose'); + add(97, 'Rust'); + add(98, '', 'Sandy Terrain'); + add(99, 'Sienna'); + add(100, 'Solar'); + this.endcolumn(); - posSepar(handle, group, separ); + this.column(); + add(101, '', 'South West'); + add(102, '', 'Starry Night'); + add(103, '', 'Sunset'); + add(104, '', 'Temperature Map'); + add(105, '', 'Thermometer'); + add(106, 'Valentine'); + add(107, '', 'Visible Spectrum'); + add(108, '', 'Water Melon'); + add(109, 'Cool'); + add(110, 'Copper'); + add(111, '', 'Gist Earth'); + add(112, 'Viridis'); + add(113, 'Cividis'); + this.endcolumn(); - needSetSize = true; - needResize = (action === 'restore'); - } + this.endsub(); + } - if (needSetSize) { - setGroupSize(handle, parent, id-1); - setGroupSize(handle, parent, id); - } + /** @summary Add rebin menu entries + * @protected */ + addRebinMenu(rebin_func) { + this.sub('Rebin', () => this.input('Enter rebin value', 2, 'int', 2).then(rebin_func)); + for (let sz = 2; sz <= 7; sz++) + this.add(sz.toString(), sz, res => rebin_func(parseInt(res))); + this.endsub(); + } - if (needResize) { - resizeGroup(parent, id-1); - resizeGroup(parent, id); - } + /** @summary Add selection menu entries + * @param {String} name - name of submenu + * @param {Array} values - array of string entries used as list for selection + * @param {String|Number} value - currently selected value, either name or index + * @param {Function} set_func - function called when item selected, either name or index depending from value parameter + * @param {String} [title] - optional title for menu items + * @protected */ + addSelectMenu(name, values, value, set_func, title) { + const use_number = (typeof value === 'number'); + this.sub(name, undefined, undefined, title); + for (let n = 0; n < values.length; ++n) + this.addchk(use_number ? (n === value) : (values[n] === value), values[n], use_number ? n : values[n], res => set_func(use_number ? Number.parseInt(res) : res)); + this.endsub(); + } - // now handling match of the sizes - if (!handle.parent?.match_sizes) - return; + /** @summary Add RColor selection menu entries + * @protected */ + addRColorMenu(name, value, set_func) { + const colors = ['default', 'black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; - for (let k = 0; k < handle.parent.groups.length; ++k) { - const hh = handle.parent.groups[k]; - if ((hh === handle) || !hh.node) continue; - hh.groups[id].size = handle.groups[id].size; - hh.groups[id].position = handle.groups[id].position; - hh.groups[id-1].size = handle.groups[id-1].size; - hh.groups[id-1].position = handle.groups[id-1].position; - if (needSetSize) { - select(hh.node).selectAll('.jsroot_separator').each(function() { - const s = select(this); - if (s.property('separator_id') === id) - posSepar(hh, hh.groups[id], s); - }); - setGroupSize(hh, hh.node, id-1); - setGroupSize(hh, hh.node, id); - } - if (needResize) { - resizeGroup(hh.node, id-1); - resizeGroup(hh.node, id); - } + this.sub(name, () => { + this.input('Enter color name - empty string will reset color', value).then(set_func); + }); + let fillcol = 'black'; + for (let n = 0; n < colors.length; ++n) { + const coltxt = colors[n]; + let match = false, bkgr = ''; + if (n > 0) { + bkgr = 'background-color:' + coltxt; + fillcol = (coltxt === 'white') ? 'black' : 'white'; + + if (isStr(value) && value && (value !== 'auto') && (value[0] !== '[')) + match = (rgb(value).toString() === rgb(coltxt).toString()); + } else + match = !value; + + const svg = `${coltxt}`; + this.addchk(match, svg, coltxt, res => set_func(res === 'default' ? null : res)); } + this.endsub(); } - /** @summary Create group separator - * @private */ - createSeparator(handle, main, group) { - const separ = main.append('div'); + /** @summary Add items to change RAttrText + * @protected */ + addRAttrTextItems(fontHandler, opts, set_func) { + if (!opts) + opts = {}; + this.addRColorMenu('color', fontHandler.color, value => set_func({ name: 'color', value })); + if (fontHandler.scaled) + this.addSizeMenu('size', 0.01, 0.10, 0.01, fontHandler.size / fontHandler.scale, value => set_func({ name: 'size', value })); + else + this.addSizeMenu('size', 6, 20, 2, fontHandler.size, value => set_func({ name: 'size', value })); - separ.classed('jsroot_separator', true) - .property('handle', handle) - .property('separator_id', group.id) - .attr('style', 'pointer-events: all; border: 0; margin: 0; padding: 0; position: absolute;') - .style(handle.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`) - .style(handle.vertical ? 'width' : 'height', (handle.size?.toFixed(2) || 100)+'%') - .style(handle.vertical ? 'height' : 'width', '5px') - .style('cursor', handle.vertical ? 'ns-resize' : 'ew-resize') - .append('div').attr('style', 'position: absolute;' + (handle.vertical - ? 'left: 0; right: 0; top: 50%; height: 3px; border-top: 1px dotted #ff0000' - : 'top: 0; bottom: 0; left: 50%; width: 3px; border-left: 1px dotted #ff0000')); + this.addSelectMenu('family', [kArial, 'Times New Roman', 'Courier New', 'Symbol'], fontHandler.name, value => set_func({ name: 'font_family', value })); - const pthis = this, drag_move = - drag().on('start', function() { pthis.handleSeparator(this, 'start'); }) - .on('drag', function(evnt) { pthis.handleSeparator(this, evnt); }) - .on('end', function() { pthis.handleSeparator(this, 'end'); }); + this.addSelectMenu('style', ['normal', 'italic', 'oblique'], fontHandler.style || 'normal', res => set_func({ name: 'font_style', value: res === 'normal' ? null : res })); - separ.call(drag_move).on('dblclick', function() { pthis.handleSeparator(this, 'restore'); }); + this.addSelectMenu('weight', ['normal', 'lighter', 'bold', 'bolder'], fontHandler.weight || 'normal', res => set_func({ name: 'font_weight', value: res === 'normal' ? null : res })); - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); + if (!opts.noalign) + this.add('align'); + if (!opts.noangle) + this.add('angle'); } + /** @summary Add line style menu + * @private */ + addLineStyleMenu(name, value, set_func) { + this.sub(name, () => this.input('Enter line style id (1-solid)', value, 'int', 1, 11).then(val => { + if (getSvgLineStyle(val)) + set_func(val); + })); + for (let n = 1; n < 11; ++n) { + const dash = getSvgLineStyle(n), + svg = `${n}`; - /** @summary Call function for each frame */ - forEachFrame(userfunc) { - if (this.simple_layout) - userfunc(this.getGridFrame()); - else { - this.selectDom().selectAll('.jsroot_newgrid').each(function() { - userfunc(this); - }); + this.addchk((value === n), svg, n, arg => set_func(parseInt(arg))); } + this.endsub(); } - /** @summary Returns active frame */ - getActiveFrame() { - if (this.simple_layout) - return this.getGridFrame(); - - let found = super.getActiveFrame(); - if (found) return found; + /** @summary Add fill style menu + * @private */ + addFillStyleMenu(name, value, color_index, set_func) { + this.sub(name, () => { + this.input('Enter fill style id (1001-solid, 3100..4000)', value, 'int', 0, 4000).then(id => { + if ((id >= 0) && (id <= 4000)) + set_func(id); + }); + }); - this.forEachFrame(frame => { if (!found) found = frame; }); + const supported = [1, 1001]; + for (let k = 3001; k < 3025; ++k) + supported.push(k); + supported.push(3144, 3244, 3344, 3305, 3315, 3325, 3490, 3481, 3472); - return found; + for (let n = 0; n < supported.length; ++n) { + if (n % 7 === 0) + this.column(); + + const selected = (value === supported[n]); + + if (typeof document !== 'undefined') { + const svgelement = select(document.createElement('svg')), + handler = new TAttFillHandler({ color: color_index || 1, pattern: supported[n], svg: svgelement }); + svgelement.attr('width', 60).attr('height', 24); + if (selected) + svgelement.append('rect').attr('x', 0).attr('y', 0).attr('width', 60).attr('height', 24).style('stroke', 'red').style('fill', 'none').style('stroke-width', '3px'); + svgelement.append('rect').attr('x', 3).attr('y', 3).attr('width', 54).attr('height', 18).style('stroke', 'none').call(handler.func); + this.add(svgelement.node().outerHTML, supported[n], arg => set_func(parseInt(arg)), `Pattern : ${supported[n]}` + (selected ? ' Active' : '')); + } else + this.addchk(selected, supported[n].toString(), supported[n], arg => set_func(parseInt(arg))); + if (n % 7 === 6) + this.endcolumn(); + } + this.endsub(); } - /** @summary Returns number of frames in grid layout */ - numGridFrames() { return this.framecnt; } + /** @summary Add font selection menu + * @private */ + addFontMenu(name, value, set_func) { + const prec = value && Number.isInteger(value) ? value % 10 : 2; - /** @summary Return grid frame by its id */ - getGridFrame(id) { - if (this.simple_layout) - return this.selectDom('origin').node(); - let res = null; - this.selectDom().selectAll('.jsroot_newgrid').each(function() { - if (id-- === 0) res = this; + this.sub(name, () => { + this.input('Enter font id from [0..20]', Math.floor(value / 10), 'int', 0, 20).then(id => { + if ((id >= 0) && (id <= 20)) + set_func(id * 10 + prec); + }); }); - return res; - } - /** @summary Create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); + this.column(); - let frame = null, maxloop = this.framecnt || 2; + const doc = getDocument(); - while (!frame && maxloop--) { - frame = this.getGridFrame(this.getcnt); - if (!this.simple_layout && this.framecnt) - this.getcnt = (this.getcnt+1) % this.framecnt; + for (let n = 1; n < 20; ++n) { + const id = n * 10 + prec, + handler = new FontHandler(id, 14), + txt = select(doc.createElementNS(nsSVG, 'text')); + let fullname = handler.getFontName(), qual = ''; + if (handler.weight) { + qual += 'b'; + fullname += ' ' + handler.weight; + } + if (handler.style) { + qual += handler.style[0]; + fullname += ' ' + handler.style; + } + if (qual) + qual = ' ' + qual; + txt.attr('x', 1).attr('y', 15).text(fullname.split(' ')[0] + qual); + handler.setFont(txt); - if (select(frame).classed('jsroot_fixed_frame')) frame = null; - } + const rect = (value !== id) ? '' : '', + svg = `${txt.node().outerHTML}${rect}`; + this.add(svg, id, arg => set_func(parseInt(arg)), `${id}: ${fullname}`); - if (frame) { - this.cleanupFrame(frame); - select(frame).attr('frame_title', title); + if (n === 10) { + this.endcolumn(); + this.column(); + } } - return this.afterCreateFrame(frame); + this.endcolumn(); + this.endsub(); } -} // class GridDisplay + /** @summary Add align selection menu + * @private */ + addAlignMenu(name, value, set_func) { + this.sub(name, () => { + this.input('Enter align like 12 or 31', value).then(arg => { + const id = parseInt(arg); + if ((id < 11) || (id > 33)) + return; + const h = Math.floor(id / 10), v = id % 10; + if ((h > 0) && (h < 4) && (v > 0) && (v < 4)) + set_func(id); + }); + }); + const hnames = ['left', 'middle', 'right'], vnames = ['bottom', 'centered', 'top']; + for (let h = 1; h < 4; ++h) { + for (let v = 1; v < 4; ++v) + this.addchk(h * 10 + v === value, `${h * 10 + v}: ${hnames[h - 1]} ${vnames[v - 1]}`, h * 10 + v, arg => set_func(parseInt(arg))); + } -// ================================================ + this.endsub(); + } -/** - * @summary Tabs-based display - * - * @private - */ + /** @summary Fill context menu for graphical attributes in painter + * @desc this method used to fill entries for different attributes of the object + * like TAttFill, TAttLine, TAttText + * There is special handling for the frame where attributes handled by the pad + * @private */ + addAttributesMenu(painter, preffix) { + const is_frame = painter === painter.getFramePainter(), + pp = is_frame ? painter.getPadPainter() : null, + redraw_arg = !preffix && !is_frame ? 'attribute' : true; + if (!preffix) + preffix = ''; -class TabsDisplay extends MDIDisplay { + if (painter.lineatt?.used) { + this.sub(`${preffix}Line att`); + this.addSizeMenu('width', 1, 10, 1, painter.lineatt.width, arg => { + painter.lineatt.change(undefined, arg); + changeObjectMember(painter, 'fLineWidth', arg); + changeObjectMember(pp, 'fFrameLineWidth', arg); + painter.interactiveRedraw(redraw_arg, `exec:SetLineWidth(${arg})`); + }); + if (!painter.lineatt.nocolor) { + this.addColorMenu('color', painter.lineatt.color, arg => { + painter.lineatt.change(arg); + changeObjectMember(painter, 'fLineColor', arg, true); + changeObjectMember(pp, 'fFrameLineColor', arg, true); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetLineColor')); + }); + } + this.addLineStyleMenu('style', painter.lineatt.style, id => { + painter.lineatt.change(undefined, undefined, id); + changeObjectMember(painter, 'fLineStyle', id); + changeObjectMember(pp, 'fFrameLineStyle', id); + painter.interactiveRedraw(redraw_arg, `exec:SetLineStyle(${id})`); + }); + this.endsub(); - constructor(frameid) { - super(frameid); - this.cnt = 0; // use to count newly created frames - this.selectDom().style('overflow', 'hidden'); - } + if (!is_frame && painter.lineatt?.excl_side) { + this.sub('Exclusion'); + this.sub('side'); + for (let side = -1; side <= 1; ++side) { + this.addchk((painter.lineatt.excl_side === side), side, side, + arg => { painter.lineatt.changeExcl(parseInt(arg)); painter.interactiveRedraw(); }); + } + this.endsub(); - /** @summary Cleanup all drawings */ - cleanup() { - this.selectDom().style('overflow', null); - this.cnt = 0; - super.cleanup(); - } + this.addSizeMenu('width', 10, 100, 10, painter.lineatt.excl_width, + arg => { painter.lineatt.changeExcl(undefined, arg); painter.interactiveRedraw(); }); - /** @summary call function for each frame */ - forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) return; + this.endsub(); + } + } - if (only_visible) { - const active = this.getActiveFrame(); - if (active) userfunc(active); - return; + if (painter.fillatt?.used) { + this.sub(`${preffix}Fill att`); + this.addColorMenu('color', painter.fillatt.colorindx, arg => { + painter.fillatt.change(arg, undefined, painter.getCanvSvg()); + changeObjectMember(painter, 'fFillColor', arg, true); + changeObjectMember(pp, 'fFrameFillColor', arg, true); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetFillColor')); + }, painter.fillatt.kind); + this.addFillStyleMenu('style', painter.fillatt.pattern, painter.fillatt.colorindx, id => { + painter.fillatt.change(undefined, id, painter.getCanvSvg()); + changeObjectMember(painter, 'fFillStyle', id); + changeObjectMember(pp, 'fFrameFillStyle', id); + painter.interactiveRedraw(redraw_arg, `exec:SetFillStyle(${id})`); + }); + this.endsub(); } - const main = this.selectDom().select('.jsroot_tabs_main'); + if (painter.markeratt?.used) { + this.sub(`${preffix}Marker att`); + this.addColorMenu('color', painter.markeratt.color, arg => { + changeObjectMember(painter, 'fMarkerColor', arg, true); + painter.markeratt.change(arg); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetMarkerColor')); + }); + this.addSizeMenu('size', 0.5, 6, 0.5, painter.markeratt.size, arg => { + changeObjectMember(painter, 'fMarkerSize', arg); + painter.markeratt.change(undefined, undefined, arg); + painter.interactiveRedraw(redraw_arg, `exec:SetMarkerSize(${arg})`); + }); - main.selectAll('.jsroot_tabs_draw').each(function() { - userfunc(this); - }); - } + this.sub('style'); + const supported = [1, 2, 3, 4, 5, 6, 7, 8, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]; - /** @summary modify tab state by id */ - modifyTabsFrame(frame_id, action) { - const top = this.selectDom().select('.jsroot_tabs'), - labels = top.select('.jsroot_tabs_labels'), - main = top.select('.jsroot_tabs_main'); + for (let n = 0; n < supported.length; ++n) { + const clone = new TAttMarkerHandler({ style: supported[n], color: painter.markeratt.color, size: 1.7 }), + svg = `${supported[n].toString()}`; - labels.selectAll('.jsroot_tabs_label').each(function() { - const id = select(this).property('frame_id'), - is_same = (id === frame_id), - active_color = settings.DarkMode ? '#333' : 'white'; + this.addchk(painter.markeratt.style === supported[n], svg, supported[n], + arg => { painter.markeratt.change(undefined, parseInt(arg)); painter.interactiveRedraw(redraw_arg, `exec:SetMarkerStyle(${arg})`); }); + } + this.endsub(); + this.endsub(); + } - if (action === 'activate') { - select(this).style('background', is_same ? active_color : (settings.DarkMode ? 'black' : '#ddd')) - .style('color', settings.DarkMode ? '#ddd' : 'inherit') - .style('border-color', active_color); - } else if ((action === 'close') && is_same) - this.parentNode.remove(); - }); + if (painter.textatt?.used) { + this.sub(`${preffix}Text att`); - let selected_frame, other_frame; + this.addFontMenu('font', painter.textatt.font, arg => { + changeObjectMember(painter, 'fTextFont', arg); + painter.textatt.change(arg); + painter.interactiveRedraw(true, `exec:SetTextFont(${arg})`); + }); - main.selectAll('.jsroot_tabs_draw').each(function() { - const match = select(this).property('frame_id') === frame_id; - if (match) - selected_frame = this; - else - other_frame = this; - if (action === 'activate') - select(this).style('background', settings.DarkMode ? 'black' : 'white'); - }); + const rel = painter.textatt.size < 1.0; - if (!selected_frame) return; + this.addSizeMenu('size', rel ? 0.03 : 6, rel ? 0.20 : 26, rel ? 0.01 : 2, painter.textatt.size, arg => { + changeObjectMember(painter, 'fTextSize', arg); + painter.textatt.change(undefined, arg); + painter.interactiveRedraw(true, `exec:SetTextSize(${arg})`); + }); - if (action === 'activate') - selected_frame.parentNode.appendChild(selected_frame); - // super.activateFrame(selected_frame); - else if (action === 'close') { - const was_active = (selected_frame === this.getActiveFrame()); - cleanup(selected_frame); - selected_frame.remove(); + this.addColorMenu('color', painter.textatt.color, arg => { + changeObjectMember(painter, 'fTextColor', arg, true); + painter.textatt.change(undefined, undefined, arg); + painter.interactiveRedraw(true, getColorExec(arg, 'SetTextColor')); + }); - if (was_active) - this.activateFrame(other_frame); + this.addAlignMenu('align', painter.textatt.align, arg => { + changeObjectMember(painter, 'fTextAlign', arg); + painter.textatt.change(undefined, undefined, undefined, arg); + painter.interactiveRedraw(true, `exec:SetTextAlign(${arg})`); + }); + + if (painter.textatt.can_rotate) { + this.addSizeMenu('angle', -180, 180, 45, painter.textatt.angle, arg => { + changeObjectMember(painter, 'fTextAngle', arg); + painter.textatt.change(undefined, undefined, undefined, undefined, arg); + painter.interactiveRedraw(true, `exec:SetTextAngle(${arg})`); + }); + } + + this.endsub(); } } - /** @summary actiavte frame */ - activateFrame(frame) { - if (frame) - this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); - super.activateFrame(frame); - } + /** @summary Fill context menu for axis + * @private */ + addTAxisMenu(EAxisBits, painter, faxis, kind, axis_painter, frame_painter) { + const is_gaxis = faxis._typename === clTGaxis; - /** @summary create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); + this.add('Divisions', () => this.input('Set Ndivisions', faxis.fNdivisions, 'int', 0).then(val => { + faxis.fNdivisions = val; painter.interactiveRedraw('pad', `exec:SetNdivisions(${val})`, kind); + })); - const dom = this.selectDom(); - let top = dom.select('.jsroot_tabs'), labels, main; + this.sub('Labels'); + this.addchk(faxis.TestBit(EAxisBits.kCenterLabels), 'Center', + arg => { faxis.SetBit(EAxisBits.kCenterLabels, arg); painter.interactiveRedraw('pad', `exec:CenterLabels(${arg})`, kind); }); + this.addchk(faxis.TestBit(EAxisBits.kLabelsVert), 'Rotate', + arg => { faxis.SetBit(EAxisBits.kLabelsVert, arg); painter.interactiveRedraw('pad', `exec:SetBit(TAxis::kLabelsVert,${arg})`, kind); }); + this.addColorMenu('Color', faxis.fLabelColor, + arg => { faxis.fLabelColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLabelColor'), kind); }); + this.addSizeMenu('Offset', -0.02, 0.1, 0.01, faxis.fLabelOffset, + arg => { faxis.fLabelOffset = arg; painter.interactiveRedraw('pad', `exec:SetLabelOffset(${arg})`, kind); }); + let a = faxis.fLabelSize >= 1; + this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fLabelSize, + arg => { faxis.fLabelSize = arg; painter.interactiveRedraw('pad', `exec:SetLabelSize(${arg})`, kind); }); - if (top.empty()) { - top = dom.append('div').attr('class', 'jsroot_tabs') - .attr('style', 'display: flex; flex-direction: column; position: absolute; overflow: hidden; left: 0px; top: 0px; bottom: 0px; right: 0px;'); - labels = top.append('div').attr('class', 'jsroot_tabs_labels') - .attr('style', 'white-space: nowrap; position: relative; overflow-x: auto'); - main = top.append('div').attr('class', 'jsroot_tabs_main') - .attr('style', 'margin: 0; flex: 1 1 0%; position: relative'); - } else { - labels = top.select('.jsroot_tabs_labels'); - main = top.select('.jsroot_tabs_main'); + if (frame_painter && (axis_painter?.kind === kAxisLabels) && (faxis.fNbins > 20)) { + this.add('Find label', () => this.input('Label id').then(id => { + if (!id) + return; + for (let bin = 0; bin < faxis.fNbins; ++bin) { + const lbl = axis_painter.formatLabels(bin); + if (lbl === id) + return frame_painter.zoomSingle(kind, Math.max(0, bin - 4), Math.min(faxis.fNbins, bin + 5)); + } + }), 'Zoom into region around specific label'); } + if (frame_painter && faxis.fLabels) { + const ignore = `${kind}_ignore_labels`; + this.addchk(!frame_painter[ignore], 'Custom', flag => { + frame_painter[ignore] = !flag; + painter.interactiveRedraw('pad'); + }, `Use of custom labels in axis ${kind}`); + } + this.endsub(); - const frame_id = this.cnt++, mdi = this; - let lbl = title; + this.sub('Title'); + this.add('SetTitle', () => { + this.input('Enter axis title', faxis.fTitle).then(t => { + faxis.fTitle = t; + painter.interactiveRedraw('pad', `exec:SetTitle("${t}")`, kind); + }); + }); + this.addchk(faxis.TestBit(EAxisBits.kCenterTitle), 'Center', + arg => { faxis.SetBit(EAxisBits.kCenterTitle, arg); painter.interactiveRedraw('pad', `exec:CenterTitle(${arg})`, kind); }); + if (!painter?.hasSnapId()) { + this.addchk(faxis.TestBit(EAxisBits.kOppositeTitle), 'Opposite', + arg => { faxis.SetBit(EAxisBits.kOppositeTitle, arg); painter.redrawPad(); }); + } + this.addchk(faxis.TestBit(EAxisBits.kRotateTitle), 'Rotate', + arg => { faxis.SetBit(EAxisBits.kRotateTitle, arg); painter.interactiveRedraw('pad', is_gaxis ? `exec:SetBit(TAxis::kRotateTitle, ${arg})` : `exec:RotateTitle(${arg})`, kind); }); + this.addColorMenu('Color', is_gaxis ? faxis.fTextColor : faxis.fTitleColor, arg => { + if (is_gaxis) + faxis.fTextColor = arg; + else + faxis.fTitleColor = arg; - if (!lbl || !isStr(lbl)) lbl = `frame_${frame_id}`; + painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); + }); + this.addSizeMenu('Offset', 0, 3, 0.2, faxis.fTitleOffset, + arg => { faxis.fTitleOffset = arg; painter.interactiveRedraw('pad', `exec:SetTitleOffset(${arg})`, kind); }); + a = faxis.fTitleSize >= 1; + this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fTitleSize, + arg => { faxis.fTitleSize = arg; painter.interactiveRedraw('pad', `exec:SetTitleSize(${arg})`, kind); }); + this.endsub(); - if (lbl.length > 15) { - let p = lbl.lastIndexOf('/'); - if (p === lbl.length-1) p = lbl.lastIndexOf('/', p-1); - if ((p > 0) && (lbl.length - p < 20) && (lbl.length - p > 1)) - lbl = lbl.slice(p+1); - else - lbl = '...' + lbl.slice(lbl.length-17); + this.sub('Ticks'); + if (is_gaxis) { + this.addColorMenu('Color', faxis.fLineColor, + arg => { faxis.fLineColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLineColor'), kind); }); + this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickSize, + arg => { faxis.fTickSize = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); + } else { + this.addColorMenu('Color', faxis.fAxisColor, + arg => { faxis.fAxisColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetAxisColor'), kind); }); + this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickLength, + arg => { faxis.fTickLength = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); } + this.endsub(); - labels.append('span') - .attr('tabindex', 0) - .append('label') - .attr('class', 'jsroot_tabs_label') - .attr('style', 'border: 1px solid; display: inline-block; font-size: 1rem; left: 1px;'+ - 'margin-left: 3px; padding: 0px 5px 1px 5px; position: relative; vertical-align: bottom;') - .property('frame_id', frame_id) - .text(lbl) - .attr('title', title) - .on('click', function(evnt) { - evnt.preventDefault(); // prevent handling in close button - mdi.modifyTabsFrame(select(this).property('frame_id'), 'activate'); - }).append('button') - .attr('title', 'close') - .attr('style', 'margin-left: .5em; padding: 0; font-size: 0.5em; width: 1.8em; height: 1.8em; vertical-align: center;') - .html('✕') - .on('click', function() { - mdi.modifyTabsFrame(select(this.parentNode).property('frame_id'), 'close'); + if (is_gaxis) { + this.add('Options', () => this.input('Enter TGaxis options like +L or -G', faxis.fChopt, 'string').then(arg => { + faxis.fChopt = arg; + painter.interactiveRedraw('pad', `exec:SetOption("${arg}")`, kind); + })); + } + } + + /** @summary Fill menu to edit settings properties + * @private */ + addSettingsMenu(with_hierarchy, alone, handle_func) { + if (!isFunc(handle_func)) + handle_func = () => {}; + if (alone) + this.header('Settings'); + else + this.sub('Settings'); + + this.sub('Files'); + + if (with_hierarchy) { + this.addchk(settings.OnlyLastCycle, 'Last cycle', flag => { + settings.OnlyLastCycle = flag; + handle_func('refresh'); }); - const draw_frame = main.append('div') - .attr('frame_title', title) - .attr('class', 'jsroot_tabs_draw') - .attr('style', 'overflow: hidden; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px;') - .property('frame_id', frame_id); + this.addchk(!settings.SkipStreamerInfos, 'Streamer infos', flag => { + settings.SkipStreamerInfos = !flag; + handle_func('refresh'); + }); + } - this.modifyTabsFrame(frame_id, 'activate'); + this.addchk(settings.UseStamp, 'Use stamp arg', flag => { settings.UseStamp = flag; }); + this.addSizeMenu('Max ranges', 1, 1000, [1, 10, 20, 50, 200, 1000], settings.MaxRanges, value => { settings.MaxRanges = value; }, 'Maximal number of ranges in single http request'); - return this.afterCreateFrame(draw_frame.node()); - } + this.addchk(settings.HandleWrongHttpResponse, 'Handle wrong http response', flag => { settings.HandleWrongHttpResponse = flag; }, 'Let detect and solve problem when server returns wrong Content-Length header, see https://github.com/root-project/jsroot/issues/189'); + this.addchk(settings.WithCredentials, 'With credentials', flag => { settings.WithCredentials = flag; }, 'Submit http request with user credentials'); - /** @summary Handle changes in dark mode */ - changeDarkMode() { - const frame = this.getActiveFrame(); - this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); - } + this.endsub(); -} // class TabsDisplay + this.sub('Toolbar'); + this.addchk(settings.ToolBar === false, 'Off', flag => { settings.ToolBar = !flag; }); + this.addchk(settings.ToolBar === true, 'On', flag => { settings.ToolBar = flag; }); + this.addchk(settings.ToolBar === 'popup', 'Popup', flag => { settings.ToolBar = flag ? 'popup' : false; }); + this.separator(); + this.addchk(settings.ToolBarSide === 'left', 'Left side', flag => { settings.ToolBarSide = flag ? 'left' : 'right'; }); + this.addchk(settings.ToolBarVert, 'Vertical', flag => { settings.ToolBarVert = flag; }); + this.endsub(); + this.sub('Interactive'); + this.addchk(settings.Tooltip, 'Tooltip', flag => { settings.Tooltip = flag; }); + this.addchk(settings.ContextMenu, 'Context menus', flag => { settings.ContextMenu = flag; }); + this.sub('Zooming'); + this.addchk(settings.Zooming, 'Global', flag => { settings.Zooming = flag; }); + this.addchk(settings.ZoomMouse, 'Mouse', flag => { settings.ZoomMouse = flag; }); + this.addchk(settings.ZoomWheel, 'Wheel', flag => { settings.ZoomWheel = flag; }); + this.addchk(settings.ZoomTouch, 'Touch', flag => { settings.ZoomTouch = flag; }); + this.endsub(); + this.addchk(settings.HandleKeys, 'Keypress handling', flag => { settings.HandleKeys = flag; }); + this.addchk(!settings.UserSelect, 'User select', flag => { settings.UserSelect = flag ? '' : 'none'; }, 'Set "user-select: none" for drawings to avoid text selection '); + this.addchk(settings.MoveResize, 'Move and resize', flag => { settings.MoveResize = flag; }); + this.addchk(settings.DragAndDrop, 'Drag and drop', flag => { settings.DragAndDrop = flag; }); + this.addchk(settings.DragGraphs, 'Drag graph points', flag => { settings.DragGraphs = flag; }); + this.addSelectMenu('Progress box', ['off', 'on', 'modal'], isStr(settings.ProgressBox) ? settings.ProgressBox : (settings.ProgressBox ? 'on' : 'off'), value => { + settings.ProgressBox = (value === 'off') ? false : (value === ' on' ? true : value); + }); + this.endsub(); + + this.sub('Drawing'); + this.addSelectMenu('Optimize', ['None', 'Smart', 'Always'], settings.OptimizeDraw, value => { settings.OptimizeDraw = value; }, 'Histogram drawing optimization'); + this.sub('SmallPad', undefined, undefined, 'Minimal pad size drawn normally'); + this.add(`width ${settings.SmallPad?.width ?? 0}px`, () => this.input('Small pad width', settings.SmallPad?.width, 'int', 1, 1000).then(val => { settings.SmallPad.width = val; })); + this.add(`height ${settings.SmallPad?.height ?? 0}px`, () => this.input('Small pad height', settings.SmallPad?.height, 'int', 1, 800).then(val => { settings.SmallPad.height = val; })); + this.add('disable', () => { settings.SmallPad = { width: 0, height: 0 }; }, 'disable small pad drawing optimization'); + this.add('default', () => { settings.SmallPad = { width: 150, height: 100 }; }, 'Set to default 150x100 dimension'); + this.endsub(); + this.addPaletteMenu(settings.Palette, pal => { settings.Palette = pal; }); + this.addchk(settings.AutoStat, 'Auto stat box', flag => { settings.AutoStat = flag; }); + this.addchk(settings.LoadSymbolTtf, 'Load symbol.ttf', flag => { settings.LoadSymbolTtf = flag; }, 'Use symbol.ttf font file to render greek symbols, also used in PDF'); + + this.sub('Axis'); + this.addchk(settings.StripAxisLabels, 'Strip labels', flag => { settings.StripAxisLabels = flag; }, 'Provide shorter labels like 10^0 -> 1'); + this.addchk(settings.CutAxisLabels, 'Cut labels', flag => { settings.CutAxisLabels = flag; }, 'Remove labels which may exceed graphical range'); + this.add(`Tilt angle ${settings.AxisTiltAngle}`, () => this.input('Axis tilt angle', settings.AxisTiltAngle, 'int', 0, 180).then(val => { settings.AxisTiltAngle = val; })); + this.add(`X format ${settings.XValuesFormat ?? gStyle.fStatFormat}`, () => this.input('X axis format', settings.XValuesFormat).then(val => { settings.XValuesFormat = val; })); + this.add(`Y format ${settings.YValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Y axis format', settings.YValuesFormat).then(val => { settings.YValuesFormat = val; })); + this.add(`Z format ${settings.ZValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Z axis format', settings.ZValuesFormat).then(val => { settings.ZValuesFormat = val; })); + this.endsub(); + this.addSelectMenu('Latex', ['Off', 'Symbols', 'Normal', 'MathJax', 'Force MathJax'], settings.Latex, value => { settings.Latex = value; }); + this.addSelectMenu('3D rendering', ['Default', 'WebGL', 'Image'], settings.Render3D, value => { settings.Render3D = value; }); + this.addSelectMenu('WebGL embeding', ['Default', 'Overlay', 'Embed'], settings.Embed3D, value => { settings.Embed3D = value; }); + if (internals.setDefaultDrawOpt) { + this.add('Default options', () => this.input('List of options like TH2:lego2;TH3:glbox2', settings._dflt_drawopt || '').then(v => { + settings._dflt_drawopt = v; + internals.setDefaultDrawOpt(v); + }), 'Configure custom default draw options for some classes'); + } + this.endsub(); -/** - * @summary Generic flexible MDI display - * - * @private - */ + this.sub('Geometry'); + this.add('Grad per segment: ' + settings.GeoGradPerSegm, () => this.input('Grad per segment in geometry', settings.GeoGradPerSegm, 'int', 1, 60).then(val => { settings.GeoGradPerSegm = val; })); + this.addchk(settings.GeoCompressComp, 'Compress composites', flag => { settings.GeoCompressComp = flag; }); + this.endsub(); -class FlexibleDisplay extends MDIDisplay { + if (with_hierarchy) { + this.sub('Browser'); + this.add('Hierarchy limit: ' + settings.HierarchyLimit, () => this.input('Max number of items in hierarchy', settings.HierarchyLimit, 'int', 10, 100000).then(val => { + settings.HierarchyLimit = val; + handle_func('refresh'); + })); + this.add('Browser width: ' + settings.BrowserWidth, () => this.input('Browser width in px', settings.BrowserWidth, 'int', 50, 2000).then(val => { + settings.BrowserWidth = val; + handle_func('width'); + })); + this.endsub(); + } - constructor(frameid) { - super(frameid); - this.cnt = 0; // use to count newly created frames - this.selectDom().on('contextmenu', evnt => this.showContextMenu(evnt)) - .style('overflow', 'auto'); - } + this.add('Dark mode: ' + (settings.DarkMode ? 'On' : 'Off'), () => { + settings.DarkMode = !settings.DarkMode; + handle_func('dark'); + }); - /** @summary Cleanup all drawings */ - cleanup() { - this.selectDom().style('overflow', null) - .on('contextmenu', null); - this.cnt = 0; - super.cleanup(); - } + const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, + addStyleIntField = (name, field, arr) => { + this.sub(name); + const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; + for (let v = 0; v < arr.length; ++v) + this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); + this.endsub(); + }; - /** @summary call function for each frame */ - forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) return; + this.sub('gStyle'); - const mdi = this, top = this.selectDom().select('.jsroot_flex_top'); + this.sub('Canvas'); + this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); + addStyleIntField('Draw date', 'fOptDate', ['Off', 'Current time', 'File create time', 'File modify time']); + this.add(`Time zone: ${settings.TimeZone}`, () => this.input('Input time zone like UTC. empty string - local timezone', settings.TimeZone, 'string').then(val => { settings.TimeZone = val; })); + addStyleIntField('Draw file', 'fOptFile', ['Off', 'File name', 'Full file URL', 'Item name']); + this.addSizeMenu('Date X', 0.01, 0.1, 0.01, gStyle.fDateX, x => { gStyle.fDateX = x; }, 'configure gStyle.fDateX for date/item name drawings'); + this.addSizeMenu('Date Y', 0.01, 0.1, 0.01, gStyle.fDateY, y => { gStyle.fDateY = y; }, 'configure gStyle.fDateY for date/item name drawings'); + this.endsub(); - top.selectAll('.jsroot_flex_draw').each(function() { - // check if only visible specified - if (only_visible && (mdi.getFrameState(this) === 'min')) return; + this.sub('Pad'); + this.addColorMenu('Color', gStyle.fPadColor, col => { gStyle.fPadColor = col; }); + this.sub('Grid'); + this.addchk(gStyle.fPadGridX, 'X', flag => { gStyle.fPadGridX = flag; }); + this.addchk(gStyle.fPadGridY, 'Y', flag => { gStyle.fPadGridY = flag; }); + this.addColorMenu('Color', gStyle.fGridColor, col => { gStyle.fGridColor = col; }); + this.addSizeMenu('Width', 1, 10, 1, gStyle.fGridWidth, w => { gStyle.fGridWidth = w; }); + this.addLineStyleMenu('Style', gStyle.fGridStyle, st => { gStyle.fGridStyle = st; }); + this.endsub(); + addStyleIntField('Ticks X', 'fPadTickX', ['normal', 'ticks on both sides', 'labels on both sides']); + addStyleIntField('Ticks Y', 'fPadTickY', ['normal', 'ticks on both sides', 'labels on both sides']); + addStyleIntField('Log X', 'fOptLogx', ['off', 'on', 'log 2']); + addStyleIntField('Log Y', 'fOptLogy', ['off', 'on', 'log 2']); + addStyleIntField('Log Z', 'fOptLogz', ['off', 'on', 'log 2']); + this.endsub(); - userfunc(this); - }); - } + this.sub('Frame'); + this.addColorMenu('Fill color', gStyle.fFrameFillColor, col => { gStyle.fFrameFillColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fFrameFillStyle, gStyle.fFrameFillColor, id => { gStyle.fFrameFillStyle = id; }); + this.addColorMenu('Line color', gStyle.fFrameLineColor, col => { gStyle.fFrameLineColor = col; }); + this.addSizeMenu('Line width', 1, 10, 1, gStyle.fFrameLineWidth, w => { gStyle.fFrameLineWidth = w; }); + this.addLineStyleMenu('Line style', gStyle.fFrameLineStyle, st => { gStyle.fFrameLineStyle = st; }); + this.addSizeMenu('Border size', 0, 10, 1, gStyle.fFrameBorderSize, sz => { gStyle.fFrameBorderSize = sz; }); + this.addSelectMenu('Border mode', ['Down', 'Off', 'Up'], gStyle.fFrameBorderMode + 1, v => { gStyle.fFrameBorderMode = v - 1; }); - /** @summary return active frame */ - getActiveFrame() { - let found = super.getActiveFrame(); - if (found && select(found.parentNode).property('state') !== 'min') return found; + // fFrameBorderMode: 0, + this.sub('Margins'); + this.addSizeMenu('Bottom', 0, 0.5, 0.05, gStyle.fPadBottomMargin, v => { gStyle.fPadBottomMargin = v; }); + this.addSizeMenu('Top', 0, 0.5, 0.05, gStyle.fPadTopMargin, v => { gStyle.fPadTopMargin = v; }); + this.addSizeMenu('Left', 0, 0.5, 0.05, gStyle.fPadLeftMargin, v => { gStyle.fPadLeftMargin = v; }); + this.addSizeMenu('Right', 0, 0.5, 0.05, gStyle.fPadRightMargin, v => { gStyle.fPadRightMargin = v; }); + this.endsub(); + this.endsub(); - found = null; - this.forEachFrame(frame => { found = frame; }, true); - return found; - } + this.sub('Title'); + this.addColorMenu('Fill color', gStyle.fTitleColor, col => { gStyle.fTitleColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fTitleStyle, gStyle.fTitleColor, id => { gStyle.fTitleStyle = id; }); + this.addColorMenu('Text color', gStyle.fTitleTextColor, col => { gStyle.fTitleTextColor = col; }); + this.addSizeMenu('Border size', 0, 10, 1, gStyle.fTitleBorderSize, sz => { gStyle.fTitleBorderSize = sz; }); + this.addSizeMenu('Font size', 0.01, 0.1, 0.01, gStyle.fTitleFontSize, sz => { gStyle.fTitleFontSize = sz; }); + this.addFontMenu('Font', gStyle.fTitleFont, fnt => { gStyle.fTitleFont = fnt; }); + this.addSizeMenu('X: ' + gStyle.fTitleX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleX, v => { gStyle.fTitleX = v; }); + this.addSizeMenu('Y: ' + gStyle.fTitleY.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleY, v => { gStyle.fTitleY = v; }); + this.addSizeMenu('W: ' + gStyle.fTitleW.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleW, v => { gStyle.fTitleW = v; }); + this.addSizeMenu('H: ' + gStyle.fTitleH.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleH, v => { gStyle.fTitleH = v; }); + this.endsub(); - /** @summary actiavte frame */ - activateFrame(frame) { - if ((frame === 'first') || (frame === 'last')) { - let res = null; - this.forEachFrame(f => { if (frame === 'last' || !res) res = f; }, true); - frame = res; - } - if (!frame) return; - if (frame.getAttribute('class') !== 'jsroot_flex_draw') return; + this.sub('Stat box'); + this.addColorMenu('Fill color', gStyle.fStatColor, col => { gStyle.fStatColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fStatStyle, gStyle.fStatColor, id => { gStyle.fStatStyle = id; }); + this.addColorMenu('Text color', gStyle.fStatTextColor, col => { gStyle.fStatTextColor = col; }); + this.addSizeMenu('Border size', 0, 10, 1, gStyle.fStatBorderSize, sz => { gStyle.fStatBorderSize = sz; }); + this.addSizeMenu('Font size', 0, 30, 5, gStyle.fStatFontSize, sz => { gStyle.fStatFontSize = sz; }); + this.addFontMenu('Font', gStyle.fStatFont, fnt => { gStyle.fStatFont = fnt; }); + this.add('Stat format', () => this.input('Stat format', gStyle.fStatFormat).then(fmt => { gStyle.fStatFormat = fmt; })); + this.addSizeMenu('X: ' + gStyle.fStatX.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatX, v => { gStyle.fStatX = v; }); + this.addSizeMenu('Y: ' + gStyle.fStatY.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatY, v => { gStyle.fStatY = v; }); + this.addSizeMenu('Width: ' + gStyle.fStatW.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatW, v => { gStyle.fStatW = v; }); + this.addSizeMenu('Height: ' + gStyle.fStatH.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatH, v => { gStyle.fStatH = v; }); + this.endsub(); - if (this.getActiveFrame() === frame) return; + this.sub('Legend'); + this.addColorMenu('Fill color', gStyle.fLegendFillColor, col => { gStyle.fLegendFillColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fLegendFillStyle, gStyle.fLegendFillColor, id => { gStyle.fLegendFillStyle = id; }); + this.addSizeMenu('Border size', 0, 10, 1, gStyle.fLegendBorderSize, sz => { gStyle.fLegendBorderSize = sz; }); + this.addFontMenu('Font', gStyle.fLegendFont, fnt => { gStyle.fLegendFont = fnt; }); + this.addSizeMenu('Text size', 0, 0.1, 0.01, gStyle.fLegendTextSize, v => { gStyle.fLegendTextSize = v; }, 'legend text size, when 0 - auto adjustment is used'); + this.endsub(); - super.activateFrame(frame); + this.sub('Histogram'); + this.addchk(gStyle.fOptTitle === 1, 'Hist title', flag => { gStyle.fOptTitle = flag ? 1 : 0; }); + this.addchk(gStyle.fOrthoCamera, 'Orthographic camera', flag => { gStyle.fOrthoCamera = flag; }); + this.addchk(gStyle.fHistMinimumZero, 'Base0', flag => { gStyle.fHistMinimumZero = flag; }, 'when true, BAR and LEGO drawing using base = 0'); + this.add('Text format', () => this.input('Paint text format', gStyle.fPaintTextFormat).then(fmt => { gStyle.fPaintTextFormat = fmt; })); + this.add('Time offset', () => this.input('Time offset in seconds, default is 788918400 for 1/1/1995', gStyle.fTimeOffset, 'int').then(ofset => { gStyle.fTimeOffset = ofset; })); + this.addSizeMenu('ErrorX: ' + gStyle.fErrorX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fErrorX, v => { gStyle.fErrorX = v; }); + this.addSizeMenu('End error', 0, 12, 1, gStyle.fEndErrorSize, v => { gStyle.fEndErrorSize = v; }, 'size in pixels of end error for E1 draw options, gStyle.fEndErrorSize'); + this.addSizeMenu('Top margin', 0.0, 0.5, 0.05, gStyle.fHistTopMargin, v => { gStyle.fHistTopMargin = v; }, 'Margin between histogram top and frame top'); + this.addColorMenu('Fill color', gStyle.fHistFillColor, col => { gStyle.fHistFillColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fHistFillStyle, gStyle.fHistFillColor, id => { gStyle.fHistFillStyle = id; }); + this.addColorMenu('Line color', gStyle.fHistLineColor, col => { gStyle.fHistLineColor = col; }); + this.addSizeMenu('Line width', 1, 10, 1, gStyle.fHistLineWidth, w => { gStyle.fHistLineWidth = w; }); + this.addLineStyleMenu('Line style', gStyle.fHistLineStyle, st => { gStyle.fHistLineStyle = st; }); + this.endsub(); - const main = frame.parentNode; - main.parentNode.append(main); + this.separator(); + this.sub('Predefined'); + ['Modern', 'Plain', 'Bold'].forEach(name => this.addchk((gStyle.fName === name), name, name, selectgStyle)); + this.endsub(); - if (this.getFrameState(frame) !== 'min') { - selectActivePad({ pp: getElementCanvPainter(frame), active: true }); - resize(frame); - } - } + this.endsub(); // gStyle - /** @summary get frame state */ - getFrameState(frame) { - const main = select(frame.parentNode); - return main.property('state'); + this.separator(); + + this.add('Save settings', () => { + const promise = readSettings(true) ? Promise.resolve(true) : this.confirm('Save settings', 'Pressing OK one agreess that JSROOT will store settings in browser local storage'); + promise.then(res => { + if (res) { + saveSettings(); + saveStyle(); + } + }); + }, 'Store settings and gStyle in browser local storage'); + this.add('Delete settings', () => { + saveSettings(-1); + saveStyle(-1); + }, 'Delete settings and gStyle from browser local storage'); + + if (!alone) + this.endsub(); } - /** @summary returns frame rect */ - getFrameRect(frame) { - if (this.getFrameState(frame) === 'max') { - const top = this.selectDom().select('.jsroot_flex_top'); - return { x: 0, y: 0, w: top.node().clientWidth, h: top.node().clientHeight }; - } + /** @summary Run modal dialog + * @return {Promise} with html element inside dialog + * @private */ + async runModal() { + throw Error('runModal() must be reimplemented'); + } - const main = select(frame.parentNode), left = main.style('left'), top = main.style('top'); + /** @summary Show modal info dialog + * @param {String} title - title + * @param {String} message - message + * @protected */ + info(title, message) { + return this.runModal(title, `

${message}

`, { height: 120, width: 400, resizable: true }); + } - return { x: parseInt(left.slice(0, left.length-2)), y: parseInt(top.slice(0, top.length-2)), - w: main.node().clientWidth, h: main.node().clientHeight }; + /** @summary Show confirm dialog + * @param {String} title - title + * @param {String} message - message + * @return {Promise} with true when 'Ok' pressed or false when 'Cancel' pressed + * @protected */ + async confirm(title, message) { + return this.runModal(title, message, { btns: true, height: 120, width: 400 }).then(elem => Boolean(elem)); } - /** @summary change frame state */ - changeFrameState(frame, newstate, no_redraw) { - const main = select(frame.parentNode), - state = main.property('state'), - top = this.selectDom().select('.jsroot_flex_top'); + /** @summary Input value + * @return {Promise} with input value + * @param {string} title - input dialog title + * @param value - initial value + * @param {string} [kind] - use 'text' (default), 'number', 'float' or 'int' + * @protected */ + async input(title, value, kind, min, max) { + let onchange = null; + if (isFunc(kind)) { + onchange = kind; + kind = ''; + } + if (!kind) + kind = 'text'; + const inp_type = (kind === 'int') ? 'number' : 'text', value0 = value; + let ranges = ''; + if ((value === undefined) || (value === null)) + value = ''; + if (kind === 'int') { + if (min !== undefined) + ranges += ` min="${min}"`; + if (max !== undefined) + ranges += ` max="${max}"`; + } - if (state === newstate) - return false; + const main_content = + '
' + + `` + + '
', oninit = !onchange ? null : elem => { + const inp = elem.querySelector('.jsroot_dlginp'); + if (inp) + inp.oninput = () => onchange(inp.value); + }; - if (state === 'normal') - main.property('original_style', main.attr('style')); + return new Promise(resolveFunc => { + this.runModal(title, main_content, { btns: true, height: 150, width: 400, oninit }).then(element => { + if (!element) { + if (onchange) + onchange(value0); + return; + } + let val = element.querySelector('.jsroot_dlginp').value, isok = true; + if (kind === 'float') { + val = Number.parseFloat(val); + isok = Number.isFinite(val); + } else if (kind === 'int') { + val = parseInt(val); + isok = Number.isInteger(val); + } + if (isok) { + if (onchange) + onchange(val); + resolveFunc(val); + } + }); + }); + } - // clear any previous settings - top.style('overflow', null); + /** @summary Let input arguments from the method + * @return {Promise} with method argument */ + async showMethodArgsDialog(method) { + const dlg_id = this.menuname + sDfltDlg; + let main_content = '
'; - switch (newstate) { - case 'min': - main.style('height', 'auto').style('width', 'auto'); - main.select('.jsroot_flex_draw').style('display', 'none'); - break; - case 'max': - main.style('height', '100%').style('width', '100%').style('left', '').style('top', ''); - main.select('.jsroot_flex_draw').style('display', null); - top.style('overflow', 'hidden'); - break; - default: - main.select('.jsroot_flex_draw').style('display', null); - main.attr('style', main.property('original_style')); + for (let n = 0; n < method.fArgs.length; ++n) { + const arg = method.fArgs[n]; + arg.fValue = arg.fDefault; + if (arg.fValue === '""') + arg.fValue = ''; + main_content += ` + `; } - main.select('.jsroot_flex_header').selectAll('button').each(function(d) { - const btn = select(this); - if (((d.t === 'minimize') && (newstate === 'min')) || - ((d.t === 'maximize') && (newstate === 'max'))) - btn.html('▞').attr('title', 'restore'); - else - btn.html(d.n).attr('title', d.t); - }); + main_content += '
'; - main.property('state', newstate); - main.select('.jsroot_flex_resize').style('display', (newstate === 'normal') ? null : 'none'); + return new Promise(resolveFunc => { + this.runModal(method.fClassName + '::' + method.fName, main_content, { btns: true, height: 100 + method.fArgs.length * 60, width: 400, resizable: true }).then(element => { + if (!element) + return; + let args = ''; - // adjust position of new minified rect - if (newstate === 'min') { - const rect = this.getFrameRect(frame), - top = this.selectDom().select('.jsroot_flex_top'), - ww = top.node().clientWidth, - hh = top.node().clientHeight, - arr = [], step = 4, - crossX = (r1, r2) => ((r1.x <= r2.x) && (r1.x + r1.w >= r2.x)) || ((r2.x <= r1.x) && (r2.x + r2.w >= r1.x)), - crossY = (r1, r2) => ((r1.y <= r2.y) && (r1.y + r1.h >= r2.y)) || ((r2.y <= r1.y) && (r2.y + r2.h >= r1.y)); + for (let k = 0; k < method.fArgs.length; ++k) { + const arg = method.fArgs[k]; + let value = element.querySelector(`#${dlg_id}_inp${k}`).value; + if (value === '') + value = arg.fDefault; + if ((arg.fTitle === 'Option_t*') || (arg.fTitle === 'const char*')) { + // check quotes, + // TODO: need to make more precise checking of escape characters + if (!value) + value = '""'; + if (value[0] !== '"') + value = '"' + value; + if (value.at(-1) !== '"') + value += '"'; + } - this.forEachFrame(f => { if ((f!==frame) && (this.getFrameState(f) === 'min')) arr.push(this.getFrameRect(f)); }); + args += (k > 0 ? ',' : '') + value; + } - rect.y = hh; - do { - rect.x = step; - rect.y -= rect.h + step; - let maxx = step, iscrossed = false; - arr.forEach(r => { - if (crossY(r, rect)) { - maxx = Math.max(maxx, r.x + r.w + step); - if (crossX(r, rect)) iscrossed = true; - } - }); - if (iscrossed) rect.x = maxx; - } while ((rect.x + rect.w > ww - step) && (rect.y > 0)); - if (rect.y < 0) { rect.x = step; rect.y = hh - rect.h - step; } + resolveFunc(args); + }); + }); + } - main.style('left', rect.x + 'px').style('top', rect.y + 'px'); - } else if (!no_redraw) - resize(frame); + /** @summary Let input arguments from the Command + * @return {Promise} with command argument */ + async showCommandArgsDialog(cmdname, args) { + const dlg_id = this.menuname + sDfltDlg; + let main_content = '
'; + + for (let n = 0; n < args.length; ++n) { + main_content += `` + + ``; + } + main_content += '
'; - return true; + return new Promise(resolveFunc => { + this.runModal('Arguments for command ' + cmdname, main_content, { btns: true, height: 110 + args.length * 60, width: 400, resizable: true }).then(element => { + if (!element) + return resolveFunc(null); + + const resargs = []; + for (let k = 0; k < args.length; ++k) + resargs.push(element.querySelector(`#${dlg_id}_inp${k}`).value); + resolveFunc(resargs); + }); + }); } - /** @summary handle button click - * @private */ - _clickButton(btn) { - const kind = select(btn).datum(), - main = select(btn.parentNode.parentNode), - frame = main.select('.jsroot_flex_draw').node(); +} // class JSRootMenu - if (kind.t === 'close') { - this.cleanupFrame(frame); - main.remove(); - this.activateFrame('last'); // set active as last non-minfied window - return; - } +/** + * @summary Context menu class using plain HTML/JavaScript + * + * @desc Use {@link createMenu} to create instance of the menu + * based on {@link https://github.com/L1quidH2O/ContextMenu.js} + * @private + */ - const state = main.property('state'); - let newstate; - if (kind.t === 'maximize') - newstate = (state === 'max') ? 'normal' : 'max'; - else - newstate = (state === 'min') ? 'normal' : 'min'; +class StandaloneMenu extends JSRootMenu { - if (this.changeFrameState(frame, newstate)) - this.activateFrame(newstate !== 'min' ? frame : 'last'); + constructor(painter, menuname, show_event) { + super(painter, menuname, show_event); + + this.code = []; + this._use_plain_text = true; + this.stack = [this.code]; } - /** @summary create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); + native() { return true; } - const mdi = this, - dom = this.selectDom(); - let top = dom.select('.jsroot_flex_top'); + /** @summary Load required modules, noop for that menu class */ + async load() { return this; } - if (top.empty()) { - top = dom.append('div') - .attr('class', 'jsroot_flex_top') - .attr('style', 'overflow: auto; position: relative; height: 100%; width: 100%'); - } + /** @summary Add menu item + * @param {string} name - item name + * @param {function} func - func called when item is selected */ + add(name, arg, func, title) { + let curr = this.stack.at(-1); - const w = top.node().clientWidth, - h = top.node().clientHeight, - main = top.append('div'); + if (name === sSeparator) + return curr.push({ divider: true }); - main.html('
' + - `

${title}

`+ - `
`+ - '
'); + if (name.indexOf(sHeader) === 0) + return curr.push({ text: name.slice(sHeader.length), header: true, title }); - main.attr('class', 'jsroot_flex_frame') - .style('position', 'absolute') - .style('left', Math.round(w * (this.cnt % 5)/10) + 'px') - .style('top', Math.round(h * (this.cnt % 5)/10) + 'px') - .style('width', Math.round(w * 0.58) + 'px') - .style('height', Math.round(h * 0.58) + 'px') - .style('border', '1px solid black') - .style('box-shadow', '1px 1px 2px 2px #aaa') - .property('state', 'normal') - .select('.jsroot_flex_header') - .on('click', function() { mdi.activateFrame(select(this.parentNode).select('.jsroot_flex_draw').node()); }) - .selectAll('button') - .data([{ n: '✕', t: 'close' }, { n: '▔', t: 'maximize' }, { n: '▁', t: 'minimize' }]) - .enter() - .append('button') - .attr('type', 'button') - .attr('style', 'float: right; padding: 0; width: 1.4em; text-align: center; font-size: 10px; margin-top: 2px; margin-right: 4px') - .attr('title', d => d.t) - .html(d => d.n) - .on('click', function() { mdi._clickButton(this); }); + if (name === sEndsub) { + this.stack.pop(); + curr = this.stack.at(-1); + if (!curr.at(-1).sub.length) + curr.at(-1).sub = undefined; + return; + } - let moving_frame = null, moving_div = null, doing_move = false, current = []; - const drag_object = drag().subject(Object); - drag_object.on('start', function(evnt) { - if (evnt.sourceEvent.target.type === 'button') - return mdi._clickButton(evnt.sourceEvent.target); + if (name === sEndcolumn) + return this.stack.pop(); - if (detectRightButton(evnt.sourceEvent)) return; + if (isFunc(arg)) { + title = func; + func = arg; + arg = name; + } - const main = select(this.parentNode); - if (!main.classed('jsroot_flex_frame') || (main.property('state') === 'max')) return; + const elem = {}; + curr.push(elem); - doing_move = !select(this).classed('jsroot_flex_resize'); - if (!doing_move && (main.property('state') === 'min')) return; + if (name === sColumn) { + elem.column = true; + elem.sub = []; + this.stack.push(elem.sub); + return; + } - mdi.activateFrame(main.select('.jsroot_flex_draw').node()); + if (name.indexOf(sSub) === 0) { + name = name.slice(4); + elem.sub = []; + this.stack.push(elem.sub); + } - moving_div = top.append('div').attr('style', main.attr('style')).style('border', '2px dotted #00F'); + if (name.indexOf('chk:') === 0) { + elem.checked = true; + name = name.slice(4); + } else if (name.indexOf('unk:') === 0) { + elem.checked = false; + name = name.slice(4); + } - if (main.property('state') === 'min') { - moving_div.style('width', main.node().clientWidth + 'px') - .style('height', main.node().clientHeight + 'px'); - } + elem.text = name; + elem.title = title; + elem.arg = arg; + elem.func = func; + } - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + /** @summary Returns size of main menu */ + size() { return this.code.length; } - moving_frame = main; - current = []; - }).on('drag', function(evnt) { - if (!moving_div) return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - const changeProp = (i, name, dd) => { - if (i >= current.length) { - const v = moving_div.style(name); - current[i] = parseInt(v.slice(0, v.length-2)); - } - current[i] += dd; - moving_div.style(name, Math.max(0, current[i])+'px'); - }; - if (doing_move) { - changeProp(0, 'left', evnt.dx); - changeProp(1, 'top', evnt.dy); - } else { - changeProp(0, 'width', evnt.dx); - changeProp(1, 'height', evnt.dy); - } - }).on('end', function(evnt) { - if (!moving_div) return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - if (doing_move) { - moving_frame.style('left', moving_div.style('left')); - moving_frame.style('top', moving_div.style('top')); - } else { - moving_frame.style('width', moving_div.style('width')); - moving_frame.style('height', moving_div.style('height')); - } - moving_div.remove(); - moving_div = null; - if (!doing_move) - resize(moving_frame.select('.jsroot_flex_draw').node()); - }); - - main.select('.jsroot_flex_header').call(drag_object); - main.select('.jsroot_flex_resize').call(drag_object); - - const draw_frame = main.select('.jsroot_flex_draw') - .attr('frame_title', title) - .property('frame_cnt', this.cnt++) - .node(); + /** @summary Build HTML elements of the menu + * @private */ + _buildContextmenu(menu, left, top, loc) { + const doc = getDocument(), + outer = doc.createElement('div'), + clname = 'jsroot_ctxt_container', + clfocus = 'jsroot_ctxt_focus', + clcolumn = 'jsroot_ctxt_column', + container_style = + 'position: absolute; top: 0; user-select: none; z-index: 100000; background-color: rgb(250, 250, 250); margin: 0; padding: 0px; width: auto;' + + 'min-width: 100px; box-shadow: 0px 0px 10px rgb(0, 0, 0, 0.2); border: 3px solid rgb(215, 215, 215); font-family: Arial, helvetica, sans-serif, serif;' + + 'font-size: 13px; color: rgb(0, 0, 0, 0.8); line-height: 15px;'; - return this.afterCreateFrame(draw_frame); - } + // if loc !== doc.body then its a submenu, so it needs to have position: relative; + if (loc === doc.body) { + // delete all elements with menu className + const deleteElems = doc.getElementsByClassName(clname); + for (let k = deleteElems.length - 1; k >= 0; --k) + deleteElems[k].parentNode.removeChild(deleteElems[k]); - /** @summary minimize all frames */ - minimizeAll() { - this.forEachFrame(frame => this.changeFrameState(frame, 'min')); - } + outer.className = clname; + outer.style = container_style; + outer.style.position = 'fixed'; + outer.style.left = left + 'px'; + outer.style.top = top + 'px'; + } else if ((left < 0) && (top === left)) { + // column + outer.className = clcolumn; + outer.style.float = 'left'; + outer.style.width = (100 / -left).toFixed(1) + '%'; + } else { + outer.className = clname; + outer.style = container_style; + outer.style.left = -loc.offsetLeft + loc.offsetWidth + 'px'; + } - /** @summary show all frames which are minimized */ - showAll() { - this.forEachFrame(frame => { - if (this.getFrameState(frame) === 'min') - this.changeFrameState(frame, 'normal'); + let need_check_area = false, ncols = 0; + menu.forEach(d => { + if (d.checked) + need_check_area = true; + if (d.column) + ncols++; }); - } - /** @summary close all frames */ - closeAllFrames() { - const arr = []; - this.forEachFrame(frame => arr.push(frame)); - arr.forEach(frame => { - this.cleanupFrame(frame); - select(frame.parentNode).remove(); - }); - } + menu.forEach(d => { + if (ncols > 0) { + outer.style.display = 'flex'; + if (d.column) + this._buildContextmenu(d.sub, -ncols, -ncols, outer); + return; + } - /** @summary cascade frames */ - sortFrames(kind) { - const arr = []; - this.forEachFrame(frame => { - const state = this.getFrameState(frame); - if (state === 'min') return; - if (state === 'max') this.changeFrameState(frame, 'normal', true); - arr.push(frame); - }); + if (d.divider) { + const hr = doc.createElement('hr'); + hr.style = 'width: 85%; margin: 3px auto; border: 1px solid rgb(0, 0, 0, 0.15)'; + outer.appendChild(hr); + return; + } - if (arr.length === 0) return; + const item = doc.createElement('div'); + item.style.position = 'relative'; + outer.appendChild(item); - const top = this.selectDom(), - w = top.node().clientWidth, - h = top.node().clientHeight, - dx = Math.min(40, Math.round(w*0.4/arr.length)), - dy = Math.min(40, Math.round(h*0.4/arr.length)); - let nx = Math.ceil(Math.sqrt(arr.length)), ny = nx; + if (d.header) { + item.style = 'background-color: lightblue; padding: 3px 7px; font-weight: bold; border-bottom: 1px;'; - // calculate number of divisions for 'tile' sorting - if ((nx > 1) && (nx*(nx-1) >= arr.length)) - if (w > h) ny--; else nx--; + let url = '', title = ''; + if (d.title) { + const p = d.title.indexOf('https://'); + if (p >= 0) { + url = d.title.slice(p); + title = d.title.slice(0, p); + } else + title = d.title; + } + if (!url) + item.innerText = d.text; + else { + item.style.display = 'flex'; + item.style['justify-content'] = 'space-between'; + + const txt = doc.createElement('span'); + txt.innerText = d.text; + txt.style = 'display: inline-block; margin: 0;'; + item.appendChild(txt); + + const anchor = doc.createElement('span'); + anchor.style = 'margin: 0; color: blue; opacity: 0.1; margin-left: 7px; right: 3px; display: inline-block; cursor: pointer;'; + anchor.textContent = '?'; + anchor.title = url; + anchor.addEventListener('click', () => { + const cp = this.painter?.getCanvPainter(); + if (cp?.canSendWebsocket()) + cp.sendWebsocket(`SHOWURL:${url}`); + else + window.open(url); + }); + anchor.addEventListener('mouseenter', () => { anchor.style.opacity = 1; }); + anchor.addEventListener('mouseleave', () => { anchor.style.opacity = 0.1; }); + item.appendChild(anchor); + } + if (title) + item.setAttribute('title', title); - arr.forEach((frame, i) => { - const main = select(frame.parentNode); - if (kind === 'cascade') { - main.style('left', (i*dx) + 'px') - .style('top', (i*dy) + 'px') - .style('width', Math.round(w * 0.58) + 'px') - .style('height', Math.round(h * 0.58) + 'px'); - } else { - main.style('left', Math.round(w/nx*(i%nx)) + 'px') - .style('top', Math.round(h/ny*((i-i%nx)/nx)) + 'px') - .style('width', Math.round(w/nx - 4) + 'px') - .style('height', Math.round(h/ny - 4) + 'px'); + return; } - resize(frame); - }); - } - /** @summary context menu */ - showContextMenu(evnt) { - // handle context menu only for MDI area - if ((evnt.target.getAttribute('class') !== 'jsroot_flex_top') || (this.numDraw() === 0)) return; + const hovArea = doc.createElement('div'); + hovArea.style = 'width: 100%; height: 100%; display: flex; justify-content: space-between; cursor: pointer;'; + if (d.title) + hovArea.setAttribute('title', d.title); - evnt.preventDefault(); + item.appendChild(hovArea); + if (!d.text) + d.text = 'item'; - const arr = []; - let nummin = 0; - this.forEachFrame(f => { - arr.push(f); - if (this.getFrameState(f) === 'min') nummin++; - }); - const active = this.getActiveFrame(); + const text = doc.createElement('div'); + text.style = 'margin: 0; padding: 3px 7px; pointer-events: none; white-space: nowrap'; - arr.sort((f1, f2) => (select(f1).property('frame_cnt') < select(f2).property('frame_cnt') ? -1 : 1)); + if (d.text.indexOf('= 0) { + if (need_check_area) { + text.style.display = 'flex'; - createMenu(evnt, this).then(menu => { - menu.add('header:Flex'); - menu.add('Cascade', () => this.sortFrames('cascade'), 'Cascade frames'); - menu.add('Tile', () => this.sortFrames('tile'), 'Tile all frames'); - if (nummin < arr.length) - menu.add('Minimize all', () => this.minimizeAll(), 'Minimize all frames'); - if (nummin > 0) - menu.add('Show all', () => this.showAll(), 'Restore minimized frames'); - menu.add('Close all', () => this.closeAllFrames()); - menu.add('separator'); + const chk = doc.createElement('span'); + chk.innerText = d.checked ? '\u2713' : ''; + chk.style.display = 'inline-block'; + chk.style.width = '1em'; + text.appendChild(chk); - arr.forEach((f, i) => menu.addchk((f===active), ((this.getFrameState(f) === 'min') ? '[min] ' : '') + select(f).attr('frame_title'), i, - arg => { - const frame = arr[arg]; - if (this.getFrameState(frame) === 'min') - this.changeFrameState(frame, 'normal'); - this.activateFrame(frame); - })); + const sub = doc.createElement('div'); + sub.innerHTML = d.text; + text.appendChild(sub); + } else + text.innerHTML = d.text; + } else { + if (need_check_area) { + const chk = doc.createElement('span'); + chk.innerText = d.checked ? '\u2713' : ''; + chk.style.display = 'inline-block'; + chk.style.width = '1em'; + text.appendChild(chk); + } - menu.show(); - }); - } + const sub = doc.createElement('span'); + sub.textContent = d.text; + text.appendChild(sub); + } -} // class FlexibleDisplay + hovArea.appendChild(text); + function changeFocus(fitem, on) { + if (on) { + fitem.classList.add(clfocus); + fitem.style['background-color'] = 'rgb(220, 220, 220)'; + } else if (fitem.classList.contains(clfocus)) { + fitem.style['background-color'] = null; + fitem.classList.remove(clfocus); + fitem.querySelector(`.${clname}`)?.remove(); + } + } -/** - * @summary Batch MDI display - * - * @desc Can be used together with hierarchy painter in node.js - * @private - */ + if (d.extraText || d.sub) { + const extraText = doc.createElement('span'); + extraText.className = 'jsroot_ctxt_extraText'; + extraText.style = 'margin: 0; padding: 3px 7px; color: rgba(0, 0, 0, 0.6);'; + extraText.textContent = d.sub ? '\u25B6' : d.extraText; + hovArea.appendChild(extraText); -class BatchDisplay extends MDIDisplay { + if (d.sub && browser.touches) { + extraText.addEventListener('click', evnt => { + evnt.preventDefault(); + evnt.stopPropagation(); + const was_active = item.parentNode.querySelector(`.${clfocus}`); - constructor(width, height, jsdom_body) { - super('$batch$'); - this.frames = []; // array of configured frames - this.width = width || 1200; - this.height = height || 800; - this.jsdom_body = jsdom_body || select('body'); // d3 body handle - } + if (was_active) + changeFocus(was_active, false); - /** @summary Call function for each frame */ - forEachFrame(userfunc) { - this.frames.forEach(userfunc); - } + if (item !== was_active) { + changeFocus(item, true); + this._buildContextmenu(d.sub, 0, 0, item); + } + }); + } + } - /** @summary Create batch frame */ - createFrame(title) { - this.beforeCreateFrame(title); + if (!browser.touches) { + hovArea.addEventListener('mouseenter', () => { + if (this.prevHovArea) + this.prevHovArea.style['background-color'] = null; + hovArea.style['background-color'] = 'rgb(235, 235, 235)'; + this.prevHovArea = hovArea; - const frame = - this.jsdom_body.append('div') - .style('visible', 'hidden') - .attr('width', this.width).attr('height', this.height) - .style('width', this.width + 'px').style('height', this.height + 'px') - .attr('id', 'jsroot_batch_' + this.frames.length) - .attr('frame_title', title); + outer.childNodes.forEach(chld => changeFocus(chld, false)); - this.frames.push(frame.node()); + if (d.sub) { + changeFocus(item, true); + this._buildContextmenu(d.sub, 0, 0, item); + } + }); + } - return this.afterCreateFrame(frame.node()); - } + if (d.func) { + item.addEventListener('click', evnt => { + const func = this.painter ? d.func.bind(this.painter) : d.func; + func(d.arg); + evnt.stopPropagation(); + this.remove(); + }); + } + }); - /** @summary Returns number of created frames */ - numFrames() { return this.frames.length; } + loc.appendChild(outer); - /** @summary returns JSON representation if any - * @desc Now works only for inspector, can be called once */ - makeJSON(id, spacing) { - const frame = this.frames[id]; - if (!frame) return; - const obj = select(frame).property('_json_object_'); - if (obj) { - select(frame).property('_json_object_', null); - cleanup(frame); - select(frame).remove(); - return toJSON(obj, spacing); - } - } + const docWidth = doc.documentElement.clientWidth, docHeight = doc.documentElement.clientHeight; - /** @summary Create SVG for specified frame id */ - makeSVG(id) { - const frame = this.frames[id]; - if (!frame) return; - const main = select(frame); - main.select('svg') - .attr('xmlns', nsSVG) - .attr('width', this.width) - .attr('height', this.height) - .attr('title', null).attr('style', null).attr('class', null).attr('x', null).attr('y', null); + // Now determine where the contextmenu will be + if (loc === doc.body) { + if (left + outer.offsetWidth > docWidth) { + // Does sub-contextmenu overflow window width? + outer.style.left = (docWidth - outer.offsetWidth) + 'px'; + } + if (outer.offsetHeight > docHeight) { + // is the contextmenu height larger than the window height? + outer.style.top = 0; + outer.style.overflowY = 'scroll'; + outer.style.overflowX = 'hidden'; + outer.style.height = docHeight + 'px'; + } else if (top + outer.offsetHeight > docHeight) { + // Does contextmenu overflow window height? + outer.style.top = (docHeight - outer.offsetHeight) + 'px'; + } + } else if (outer.className !== clcolumn) { + // if its sub-contextmenu + const dimensionsLoc = loc.getBoundingClientRect(), dimensionsOuter = outer.getBoundingClientRect(); - function clear_element() { - const elem = select(this); - if (elem.style('display') === 'none') elem.remove(); + // Does sub-contextmenu overflow window width? + if (dimensionsOuter.left + dimensionsOuter.width > docWidth) + outer.style.left = (-loc.offsetLeft - dimensionsOuter.width) + 'px'; + + + if (dimensionsOuter.height > docHeight) { + // is the sub-contextmenu height larger than the window height? + outer.style.top = -dimensionsOuter.top + 'px'; + outer.style.overflowY = 'scroll'; + outer.style.overflowX = 'hidden'; + outer.style.height = docHeight + 'px'; + } else if (dimensionsOuter.height < docHeight && dimensionsOuter.height > docHeight / 2) { + // is the sub-contextmenu height smaller than the window height AND larger than half of window height? + if (dimensionsOuter.top - docHeight / 2 >= 0) { // If sub-contextmenu is closer to bottom of the screen + outer.style.top = (-dimensionsOuter.top - dimensionsOuter.height + docHeight) + 'px'; + } else { // If sub-contextmenu is closer to top of the screen + outer.style.top = (-dimensionsOuter.top) + 'px'; + } + } else if (dimensionsOuter.top + dimensionsOuter.height > docHeight) { + // Does sub-contextmenu overflow window height? + outer.style.top = (-dimensionsOuter.height + dimensionsLoc.height) + 'px'; + } } + return outer; + } - main.selectAll('g.root_frame').each(clear_element); - main.selectAll('svg').each(clear_element); + /** @summary Show standalone menu */ + async show(event) { + this.remove(); - const svg = compressSVG(main.html()); + if (!event && this.show_evnt) + event = this.show_evnt; - cleanup(frame); - main.remove(); - return svg; - } + const doc = getDocument(), + woffset = typeof window === 'undefined' ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY }; -} // class BatchDisplay + doc.body.addEventListener('click', this.remove_handler); + doc.getElementById(this.menuname)?.remove(); -/** - * @summary Special browser layout - * - * @desc Contains three different areas for browser (left), status line (bottom) and central drawing - * Main application is normal browser, but also used in other applications like ROOT6 canvas - * @private - */ + this.element = this._buildContextmenu(this.code, (event?.clientX || 0) + woffset.x, (event?.clientY || 0) + woffset.y, doc.body); -class BrowserLayout { + this.element.setAttribute('id', this.menuname); - /** @summary Constructor */ - constructor(id, hpainter, objpainter) { - this.gui_div = id; - this.hpainter = hpainter; // painter for brwoser area (if any) - this.objpainter = objpainter; // painter for object area (if any) - this.browser_kind = null; // should be 'float' or 'fix' + return this; } - /** @summary Selects main element */ - main() { return select('#' + this.gui_div); } + /** @summary Run modal elements with standalone code */ + createModal(title, main_content, args) { + if (!args) + args = {}; - /** @summary Selects browser div */ - browser() { return this.main().select('.jsroot_browser'); } + if (!args.Ok) + args.Ok = 'Ok'; - /** @summary Selects drawing div */ - drawing() { return select(`#${this.gui_div}_drawing`); } + const modal = { args }, dlg_id = (this?.menuname ?? sDfltName) + sDfltDlg; + select(`#${dlg_id}`).remove(); + select(`#${dlg_id}_block`).remove(); - /** @summary Selects drawing div */ - status() { return select(`#${this.gui_div}_status`); } + const w = Math.min(args.width || 450, Math.round(0.9 * browser.screenWidth)), + b = select('body'); + modal.block = b.append('div') + .attr('id', `${dlg_id}_block`) + .attr('class', 'jsroot_dialog_block') + .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); + modal.element = b.append('div') + .attr('id', dlg_id) + .attr('class', 'jsroot_dialog') + .style('position', 'absolute') + .style('width', `${w}px`) + .style('left', '50%') + .style('top', '50%') + .style('z-index', 100001) + .attr('tabindex', '0'); + + modal.element.html( + '
' + + `
${title}
` + + `
${main_content}
` + + '
' + + `` + + (args.btns ? '' : '') + + '
' + ); - /** @summary Returns drawing divid */ - drawing_divid() { return this.gui_div + '_drawing'; } + const drag_move = drag().on('start', () => { modal.y0 = 0; }).on('drag', evnt => { + if (!modal.y0) + modal.y0 = pointer(evnt, modal.element.node())[1]; + let p0 = Math.max(0, pointer(evnt, b.node())[1] - modal.y0); + if (b.node().clientHeight) + p0 = Math.min(p0, 0.8 * b.node().clientHeight); + modal.element.style('top', `${p0}px`); + }); + modal.element.select('.jsroot_dialog_title').call(drag_move); - /** @summary Check resize action */ - checkResize() { - if (isFunc(this.hpainter?.checkResize)) - this.hpainter.checkResize(); - else if (isFunc(this.objpainter?.checkResize)) - this.objpainter.checkResize(true); - } + modal.done = function(res) { + if (this._done) + return; + this._done = true; + if (isFunc(this.call_back)) + this.call_back(res); + this.element.remove(); + this.block.remove(); + }; - /** @summary Create or update CSS style */ - createStyle() { - const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', - title_color = settings.DarkMode ? '#ccc' : 'inherit', - text_color = settings.DarkMode ? '#ddd' : 'inherit', - input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; + modal.setContent = function(content, btn_text) { + if (!this._done) { + this.element.select('.jsroot_dialog_content').html(content); + if (btn_text) { + this.args.Ok = btn_text; + this.element.select('.jsroot_dialog_button').text(btn_text); + } + } + }; - injectStyle( - '.jsroot_browser { pointer-events: none; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; margin: 0px; border: 0px; overflow: hidden; }'+ - `.jsroot_draw_area { background-color: ${bkgr_color}; overflow: hidden; margin: 0px; border: 0px; }`+ - `.jsroot_browser_area { color: ${text_color}; background-color: ${bkgr_color}; font-size: 12px; font-family: Verdana; pointer-events: all; box-sizing: initial; }`+ - `.jsroot_browser_area input { ${input_style} }`+ - `.jsroot_browser_area select { ${input_style} }`+ - `.jsroot_browser_title { font-family: Verdana; font-size: 20px; color: ${title_color}; }`+ - '.jsroot_browser_btns { pointer-events: all; display: flex; flex-direction: column; }'+ - '.jsroot_browser_area p { margin-top: 5px; margin-bottom: 5px; white-space: nowrap; }'+ - '.jsroot_browser_hierarchy { flex: 1; margin-top: 2px; }'+ - `.jsroot_status_area { background-color: ${bkgr_color}; overflow: hidden; font-size: 12px; font-family: Verdana; pointer-events: all; }`+ - '.jsroot_browser_resize { position: absolute; right: 3px; bottom: 3px; margin-bottom: 0px; margin-right: 0px; opacity: 0.5; cursor: se-resize; z-index: 1; }', - this.main().node(), 'browser_layout_style'); + modal.element.on('keyup', evnt => { + if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { + evnt.preventDefault(); + evnt.stopPropagation(); + modal.done(evnt.code === 'Enter' ? modal.element.node() : null); + } + }); + modal.element.on('keydown', evnt => { + if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { + evnt.preventDefault(); + evnt.stopPropagation(); + } + }); + modal.element.selectAll('.jsroot_dialog_button').on('click', evnt => { + modal.done(args.btns && (select(evnt.target).text() === args.Ok) ? modal.element.node() : null); + }); + + let f = modal.element.select('.jsroot_dialog_content').select('input'); + if (f.empty()) + f = modal.element.select('.jsroot_dialog_footer').select('button'); + if (!f.empty()) + f.node().focus(); + if (isFunc(args.oninit)) + args.oninit(modal.element.node()); + return modal; } - /** @summary method used to create basic elements - * @desc should be called only once */ - create(with_browser) { - const main = this.main(); + /** @summary Run modal elements with standalone code */ + async runModal(title, main_content, args) { + const modal = this.createModal(title, main_content, args); + return new Promise(resolveFunc => { + modal.call_back = resolveFunc; + }); + } - main.append('div').attr('id', this.drawing_divid()) - .classed('jsroot_draw_area', true) - .style('position', 'absolute') - .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); +} // class StandaloneMenu - if (with_browser) - main.append('div').classed('jsroot_browser', true); - this.createStyle(); - } +/** @summary Create JSROOT menu + * @desc See {@link JSRootMenu} class for detailed list of methods + * @param {object} [evnt] - event object like mouse context menu event + * @param {object} [handler] - object with handling function, in this case one not need to bind function + * @param {string} [menuname] - optional menu name + * @example + * import { createMenu } from 'https://root.cern/js/latest/modules/gui/menu.mjs'; + * let menu = await createMenu()); + * menu.add('First', () => console.log('Click first')); + * let flag = true; + * menu.addchk(flag, 'Checked', arg => console.log(`Now flag is ${arg}`)); + * menu.show(); */ +function createMenu(evnt, handler, menuname) { + const menu = new StandaloneMenu(handler, menuname || sDfltName, evnt); + return menu.load(); +} - /** @summary Create buttons in the layout */ - createBrowserBtns() { - const br = this.browser(); - if (br.empty()) return; - let btns = br.select('.jsroot_browser_btns'); - if (btns.empty()) { - btns = br.append('div') - .attr('class', 'jsroot jsroot_browser_btns') - .attr('style', 'position: absolute; left: 7px; top: 7px'); - } else - btns.html(''); - return btns; - } +/** @summary Close previously created and shown JSROOT menu + * @param {string} [menuname] - optional menu name */ +function closeMenu(menuname) { + const element = getDocument().getElementById(menuname || sDfltName); + element?.remove(); + return Boolean(element); +} - /** @summary Remove browser buttons */ - removeBrowserBtns() { - this.browser().select('.jsroot_browser_btns').remove(); +/** @summary Returns true if menu or modal dialog present + * @private */ +function hasMenu(menuname) { + const doc = getDocument(); + if (doc.getElementById(menuname || sDfltName)) + return true; + if (doc.getElementById((menuname || sDfltName) + sDfltDlg)) + return true; + return false; +} + +/** @summary Fill and show context menu for painter object + * @private */ +function showPainterMenu(evnt, painter, kind) { + if (isFunc(evnt.stopPropagation)) { + evnt.stopPropagation(); // disable main context menu + evnt.preventDefault(); // disable browser context menu } - /** @summary Set browser content */ - setBrowserContent(guiCode) { - const main = this.browser(); - if (main.empty()) return; + createMenu(evnt, painter).then(menu => { + painter.fillContextMenu(menu); + if (kind === kNoReorder) + kind = undefined; + else if (isFunc(painter.bringToFront)) + menu.add('Bring to front', () => painter.bringToFront(true)); + if (kind === kToFront) + kind = undefined; + return painter.fillObjectExecMenu(menu, kind); + }).then(menu => menu.show()); +} - main.insert('div', '.jsroot_browser_btns').classed('jsroot_browser_area', true) - .style('position', 'absolute').style('left', '0px').style('top', '0px').style('bottom', '0px').style('width', '250px') - .style('overflow', 'hidden') - .style('padding-left', '5px') - .style('display', 'flex').style('flex-direction', 'column') /* use the flex model */ - .html(`

title

${guiCode}`); +/** @summary Internal method to implement modal progress + * @private */ +internals._modalProgress = function(msg, click_handle) { + if (!msg || !isStr(msg)) { + internals.modal?.done(); + delete internals.modal; + return; } - /** @summary Check if there is browser content */ - hasContent() { - const main = this.browser(); - return main.empty() ? false : !main.select('.jsroot_browser_area').empty(); - } + if (!internals.modal) + internals.modal = StandaloneMenu.prototype.createModal('Progress', msg); - /** @summary Delete content */ - deleteContent(keep_status) { - const main = this.browser(); - if (main.empty()) return; + internals.modal.setContent(msg, click_handle ? 'Abort' : 'Ok'); - if (!keep_status) - this.createStatusLine(0, 'delete'); + internals.modal.call_back = click_handle; +}; - this.toggleBrowserVisisbility(true); +/** @summary Assign handler for context menu for painter draw element + * @private */ +function assignContextMenu(painter, kind) { + if (!painter?.isBatchMode()) + painter?.getG()?.on('contextmenu', settings.ContextMenu ? evnt => showPainterMenu(evnt, painter, kind) : null); +} - if (keep_status) { - // try to delete only content, not status - main.select('.jsroot_browser_area').remove(); - main.select('.jsroot_browser_btns').remove(); - main.select('.jsroot_v_separator').remove(); - } else - main.selectAll('*').remove(); +Object.assign(internals.jsroot, { createMenu, closeMenu, assignContextMenu, kToFront, kNoReorder }); - delete this.browser_visible; - delete this.browser_kind; +/** @summary Return time offset value for given TAxis object + * @private */ +function getTimeOffset(axis) { + const dflt_time_offset = 788918400000; - this.checkResize(); - } + if (!axis) + return dflt_time_offset; + const idF = axis.fTimeFormat.indexOf('%F'); + if (idF < 0) + return gStyle.fTimeOffset * 1000; + let sof = axis.fTimeFormat.slice(idF + 2); + // default string in axis offset + if (sof.indexOf('1995-01-01 00:00:00s0') === 0) + return dflt_time_offset; + // another default string with unix time + if (sof.indexOf('1970-01-01 00:00:00s0') === 0) + return 0; + // special case, used from DABC painters + if ((sof === '0') || (sof === '')) + return 0; - /** @summary Returns true when status line exists */ - hasStatus() { - const main = this.browser(); - return main.empty() ? false : !this.status().empty(); - } + // decode time from ROOT string + const next = (separ, min, max) => { + const pos = sof.indexOf(separ); + if (pos < 0) + return min; + const val = parseInt(sof.slice(0, pos)); + sof = sof.slice(pos + 1); + if (!Number.isInteger(val) || (val < min) || (val > max)) + return min; + return val; + }; + // eslint-disable-next-line one-var + const year = next('-', 1900, 2900), + month = next('-', 1, 12) - 1, + day = next(' ', 1, 31), + hour = next(':', 0, 23), + min = next(':', 0, 59), + sec = next('s', 0, 59), + msec = next(' ', 0, 999); - /** @summary Set browser title text - * @desc Title also used for dragging of the float browser */ - setBrowserTitle(title) { - const main = this.browser(), - elem = !main.empty() ? main.select('.jsroot_browser_title') : null; - if (elem) elem.text(title).style('cursor', this.browser_kind === 'flex' ? 'move' : null); - return elem; - } + let offset = Date.UTC(year, month, day, hour, min, sec, msec); - /** @summary Toggle browser kind - * @desc used together with browser buttons */ - toggleKind(browser_kind) { - if (this.browser_visible !== 'changing') { - if (browser_kind === this.browser_kind) this.toggleBrowserVisisbility(); - else this.toggleBrowserKind(browser_kind); + // now also handle suffix like GMT or GMT -0600 + sof = sof.toUpperCase(); + + if (sof.indexOf('GMT') === 0) { + sof = sof.slice(4).trim(); + if (sof.length > 3) { + let p = 0, sign = 1000; + if (sof[0] === '-') { + p = 1; + sign = -1e3; + } + offset -= sign * (parseInt(sof.slice(p, p + 2)) * 3600 + parseInt(sof.slice(p + 2, p + 4)) * 60); } } - /** @summary Creates status line */ - async createStatusLine(height, mode) { - const main = this.browser(); - if (main.empty()) - return ''; - - const id = this.gui_div + '_status', - line = select('#'+id), - is_visible = !line.empty(); + return offset; +} - if (mode === 'toggle') - mode = !is_visible; - else if (mode === 'delete') { - mode = false; height = 0; delete this.status_layout; - } else if (mode === undefined) { - mode = true; this.status_layout = 'app'; - } +/** @summary Return true when GMT option configured in time format + * @private */ +function getTimeGMT(axis) { + const fmt = axis?.fTimeFormat ?? ''; + return (fmt.indexOf('gmt') > 0) || (fmt.indexOf('GMT') > 0); +} - if (is_visible) { - if (mode === true) - return id; +/** @summary Tries to choose time format for provided time interval + * @private */ +function chooseTimeFormat(awidth, ticks) { + if (awidth < 0.5) + return ticks ? '%S.%L' : '%H:%M:%S.%L'; + if (awidth < 30) + return ticks ? '%Mm%S' : '%H:%M:%S'; + awidth /= 60; + if (awidth < 30) + return ticks ? '%Hh%M' : '%d/%m %H:%M'; + awidth /= 60; + if (awidth < 12) + return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; + awidth /= 24; + if (awidth < 15.218425) + return ticks ? '%d/%m' : '%d/%m/%y'; + awidth /= 30.43685; + if (awidth < 6) + return '%d/%m/%y'; + awidth /= 12; + if (awidth < 2) + return ticks ? '%m/%y' : '%d/%m/%y'; + return '%Y'; +} - const hsepar = main.select('.jsroot_h_separator'); +/** + * @summary Base axis painter methods + * + * @private + */ - hsepar.remove(); - line.remove(); +const AxisPainterMethods = { - if (this.status_layout !== 'app') - delete this.status_layout; + initAxisPainter() { + this.name = 'yaxis'; + this.kind = kAxisNormal; + this.func = null; + this.order = 0; // scaling order for axis labels - if (this.status_handler && (internals.showStatus === this.status_handler)) { - delete internals.showStatus; - delete this.status_handler; - } + this.full_min = 0; + this.full_max = 1; + this.scale_min = 0; + this.scale_max = 1; + this.ticks = []; // list of major ticks + }, - this.adjustSeparators(null, 0, true); - return ''; - } + /** @summary Cleanup axis painter */ + cleanupAxisPainter() { + this.ticks = []; + delete this.format; + delete this.func; + delete this.tfunc1; + delete this.tfunc2; + delete this.gr; + }, - if (mode === false) - return ''; + /** @summary Assign often used members of frame painter */ + assignFrameMembers(fp, axis) { + fp[`gr${axis}`] = this.gr; // fp.grx + fp[`log${axis}`] = this.log; // fp.logx + fp[`scale_${axis}min`] = this.scale_min; // fp.scale_xmin + fp[`scale_${axis}max`] = this.scale_max; // fp.scale_xmax + }, - const left_pos = this.drawing().style('left'); + /** @summary Convert axis value into the Date object */ + convertDate(v) { + const dt = new Date(this.timeoffset + v * 1000); + let res = dt; + if (!this.timegmt && settings.TimeZone) { + try { + const ms = dt.getMilliseconds(); + res = new Date(dt.toLocaleString('en-US', { timeZone: settings.TimeZone })); + res.setMilliseconds(ms); + } catch { + res = dt; + } + } + return res; + }, - main.insert('div', '.jsroot_browser_area') - .attr('id', id) - .classed('jsroot_status_area', true) - .style('position', 'absolute').style('left', left_pos).style('height', '20px').style('bottom', '0px').style('right', '0px') - .style('margin', 0).style('border', 0); + /** @summary Convert graphical point back into axis value */ + revertPoint(pnt) { + const value = this.func.invert(pnt); + return this.kind === kAxisTime ? (value - this.timeoffset) / 1000 : value; + }, - const separ_color = settings.DarkMode ? 'grey' : 'azure', - hsepar = main.insert('div', '.jsroot_browser_area') - .classed('jsroot_h_separator', true) - .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; left: ${left_pos}; right: 0; bottom: 20px; height: 5px; cursor: ns-resize;`), + /** @summary Provide label for time axis */ + formatTime(dt, asticks) { + return asticks ? this.tfunc1(dt) : this.tfunc2(dt); + }, - drag_move = drag().on('start', () => { - this._hsepar_move = this._hsepar_position; - hsepar.style('background-color', 'grey'); - }).on('drag', evnt => { - this._hsepar_move -= evnt.dy; // hsepar is position from bottom - this.adjustSeparators(null, Math.max(5, Math.round(this._hsepar_move))); - }).on('end', () => { - delete this._hsepar_move; - hsepar.style('background-color', null); - this.checkResize(); - }); + /** @summary Provide label for log axis */ + formatLog(d, asticks, fmt) { + const val = parseFloat(d), rnd = Math.round(val); + if (!asticks) + return ((rnd === val) && (Math.abs(rnd) < 1e9)) ? rnd.toString() : floatToString(val, fmt || gStyle.fStatFormat); + if (val <= 0) + return null; + let vlog = Math.log10(val); + const base = this.logbase; + if (base !== 10) + vlog /= Math.log10(base); + if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog)) < 0.001)) { + if (!this.noexp && (asticks !== 2)) + return this.formatExp(base, Math.floor(vlog + 0.01), val); + if (Math.abs(base - Math.E) < 0.001) + return floatToString(val, fmt || gStyle.fStatFormat); + return (vlog < 0) ? val.toFixed(Math.round(-vlog + 0.5)) : val.toFixed(0); + } + return null; + }, - hsepar.call(drag_move); + /** @summary Detect if tick is extra-ticks */ + isExtraLogTick(val) { + if (!this.log) + return false; + let vlog = Math.log10(val); + if (this.logbase !== 10) + vlog /= Math.log10(this.logbase); - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); + return Math.abs(vlog - Math.round(vlog)) >= 0.001; + }, - if (!height || isStr(height)) height = this.last_hsepar_height || 20; + /** @summary Provide label for normal axis */ + formatNormal(d, asticks, fmt) { + let val = parseFloat(d); + if (asticks && this.order) + val /= Math.pow(10, this.order); - this.adjustSeparators(null, height, true); + if (gStyle.fStripDecimals && (val === Math.round(val))) + return Math.abs(val) < 1e9 ? val.toFixed(0) : val.toExponential(4); - if (this.status_layout === 'app') - return id; + if (asticks) { + if (this.ndig > 10) + return val.toExponential(this.ndig - 11); + let res = val.toFixed(this.ndig); + const p = res.indexOf('.'); + if ((p > 0) && settings.StripAxisLabels) { + while ((res.length >= p) && ((res.at(-1) === '0') || (res.at(-1) === '.'))) + res = res.slice(0, res.length - 1); + } + return res; + } - this.status_layout = new GridDisplay(id, 'horizx4_1213'); + return floatToString(val, fmt || '8.6g'); + }, - const frame_titles = ['object name', 'object title', 'mouse coordinates', 'object info']; - for (let k = 0; k < 4; ++k) { - select(this.status_layout.getGridFrame(k)) - .attr('title', frame_titles[k]).style('overflow', 'hidden') - .append('label').attr('style', 'margin: 3px; margin-left: 5px; font-size: 14px; vertical-align: middle; white-space: nowrap;'); + /** @summary Provide label for exponential form */ + formatExp(base, order, value) { + let res = ''; + const sbase = Math.abs(base - Math.E) < 0.001 ? 'e' : base.toString(); + if (value) { + value = Math.round(value / Math.pow(base, order)); + if (settings.StripAxisLabels) { + if (order === 0) + return value.toString(); + else if ((order === 1) && (value === 1)) + return sbase; + } + if (value !== 1) + res = value.toString() + (settings.Latex ? '#times' : 'x'); } + res += sbase; + if (settings.Latex > constants$1.Latex.Symbols) + return res + `^{${order}}`; + const superscript_symbols = { + 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', + 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' + }, str = order.toString(); + for (let n = 0; n < str.length; ++n) + res += superscript_symbols[str[n]]; + return res; + }, - internals.showStatus = this.status_handler = this.showStatus.bind(this); + /** @summary Convert 'raw' axis value into text */ + axisAsText(value, fmt) { + if (this.kind === kAxisTime) + value = this.convertDate(value); + if (this.format) + return this.format(value, false, fmt); + return value.toPrecision(4); + }, - return id; - } + /** @summary Produce ticks for d3.scaleLog + * @desc Fixing following problem, described [here]{@link https://stackoverflow.com/questions/64649793} */ + poduceLogTicks(func, number) { + const linearArray = arr => { + if (arr.length < 2) + return false; + let sum1 = 0, sum2 = 0; + for (let k = 1; k < arr.length; ++k) { + const diff = (arr[k] - arr[k - 1]); + sum1 += diff; + sum2 += diff ** 2; + } + const mean = sum1 / (arr.length - 1), + dev = sum2 / (arr.length - 1) - mean ** 2; - /** @summary Adjust separator positions */ - adjustSeparators(vsepar, hsepar, redraw, first_time) { - if (!this.gui_div) return; + if (dev <= 0) + return true; + if (Math.abs(mean) < 1e-100) + return false; + return Math.sqrt(dev) / mean < 1e-6; + }; - const main = this.browser(), w = 5; + let arr = func.ticks(number); - if ((hsepar === null) && first_time && !main.select('.jsroot_h_separator').empty()) { - // if separator set for the first time, check if status line present - hsepar = main.select('.jsroot_h_separator').style('bottom'); - if (isStr(hsepar) && (hsepar.length > 2) && (hsepar.indexOf('px') === hsepar.length-2)) - hsepar = hsepar.slice(0, hsepar.length-2); - else - hsepar = null; + while ((number > 4) && linearArray(arr)) { + number = Math.round(number * 0.8); + arr = func.ticks(number); } - if (hsepar !== null) { - hsepar = parseInt(hsepar); - const elem = main.select('.jsroot_h_separator'); - let hlimit = 0; + // if still linear array, try to sort out 'bad' ticks + if ((number < 5) && linearArray(arr) && this.logbase && (this.logbase !== 10)) { + const arr2 = []; + arr.forEach(val => { + const pow = Math.log10(val) / Math.log10(this.logbase); + if (Math.abs(Math.round(pow) - pow) < 0.01) + arr2.push(val); + }); + if (arr2.length) + arr = arr2; + } - if (!elem.empty()) { - if (hsepar < 5) hsepar = 5; + return arr; + }, - const maxh = main.node().clientHeight - w; - if (maxh > 0) { - if (hsepar < 0) hsepar += maxh; - if (hsepar > maxh) hsepar = maxh; - } + /** @summary Produce axis ticks */ + produceTicks(ndiv, ndiv2) { + if (!this.noticksopt) { + const total = ndiv * (ndiv2 || 1); - this.last_hsepar_height = hsepar; - elem.style('bottom', hsepar+'px').style('height', w+'px'); - this.status().style('height', hsepar+'px'); - hlimit = hsepar + w; - } + if (this.log) + return this.poduceLogTicks(this.func, total); - this._hsepar_position = hsepar; + const dom = this.func.domain(), + check = ticks => { + if (ticks.length <= total) + return true; + if (ticks.length > total + 1) + return false; + return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range + }, res1 = this.func.ticks(total); + if (ndiv2 || check(res1)) + return res1; - this.drawing().style('bottom', `${hlimit}px`); + const res2 = this.func.ticks(Math.round(total * 0.7)); + return (res2.length > 2) && check(res2) ? res2 : res1; } - if (vsepar !== null) { - vsepar = Math.max(50, Number.parseInt(vsepar)); - this._vsepar_position = vsepar; - main.select('.jsroot_browser_area').style('width', (vsepar-5)+'px'); - this.drawing().style('left', (vsepar+w)+'px'); - main.select('.jsroot_h_separator').style('left', (vsepar+w)+'px'); - this.status().style('left', (vsepar+w)+'px'); - main.select('.jsroot_v_separator').style('left', vsepar+'px').style('width', w+'px'); - } + const dom = this.func.domain(), ticks = []; + if (ndiv2) + ndiv = (ndiv - 1) * ndiv2; + for (let n = 0; n <= ndiv; ++n) + ticks.push((dom[0] * (ndiv - n) + dom[1] * n) / ndiv); + return ticks; + }, - if (redraw) this.checkResize(); - } + /** @summary Method analyze mouse wheel event and returns item with suggested zooming range */ + analyzeWheelEvent(evnt, dmin, item, test_ignore) { + if (!item) + item = {}; - /** @summary Show status information inside special fields of browser layout */ - showStatus(...msgs) { - if (!isObject(this.status_layout) || !isFunc(this.status_layout.getGridFrame)) return; + let delta = 0, delta_left = 1, delta_right = 1; - let maxh = 0; - for (let n = 0; n < 4; ++n) { - const lbl = this.status_layout.getGridFrame(n).querySelector('label'); - maxh = Math.max(maxh, lbl.clientHeight); - lbl.innerHTML = msgs[n] || ''; + if ('dleft' in item) { + delta_left = item.dleft; + delta = 1; } - - if (!this.status_layout.first_check) { - this.status_layout.first_check = true; - if ((maxh > 5) && ((maxh > this.last_hsepar_height) || (maxh < this.last_hsepar_height+5))) - this.adjustSeparators(null, maxh, true); + if ('dright' in item) { + delta_right = item.dright; + delta = 1; } - } - - /** @summary Toggle browser visibility */ - toggleBrowserVisisbility(fast_close) { - if (!this.gui_div || isStr(this.browser_visible)) return; - - const main = this.browser(), area = main.select('.jsroot_browser_area'); - if (area.empty()) return; - - const vsepar = main.select('.jsroot_v_separator'), - drawing = select(`#${this.gui_div}_drawing`); - let tgt = area.property('last_left'), - tgt_separ = area.property('last_vsepar'), - tgt_drawing = area.property('last_drawing'); + if (item.delta) + delta = item.delta; + else if (evnt) + delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - if (!this.browser_visible) { - if (fast_close) return; - area.property('last_left', null).property('last_vsepar', null).property('last_drawing', null); - } else { - area.property('last_left', area.style('left')); - if (!vsepar.empty()) { - area.property('last_vsepar', vsepar.style('left')); - area.property('last_drawing', drawing.style('left')); - } + if (!delta || (test_ignore && item.ignore)) + return; - tgt = (-area.node().clientWidth - 10) + 'px'; - const mainw = main.node().clientWidth; + delta = (delta < 0) ? -0.2 : 0.2; + delta_left *= delta; + delta_right *= delta; - if (vsepar.empty() && (area.node().offsetLeft > mainw/2)) - tgt = (mainw+10) + 'px'; + const lmin = item.min = this.scale_min, + lmax = item.max = this.scale_max, + gmin = this.full_min, + gmax = this.full_max; - tgt_separ = '-10px'; - tgt_drawing = '0px'; + if ((item.min === item.max) && (delta < 0)) { + item.min = gmin; + item.max = gmax; } - const visible_at_the_end = !this.browser_visible, _duration = fast_close ? 0 : 700; + if (item.min >= item.max) + return; - this.browser_visible = 'changing'; + if (item.reverse) + dmin = 1 - dmin; - area.transition().style('left', tgt).duration(_duration).on('end', () => { - if (fast_close) return; - this.browser_visible = visible_at_the_end; - if (visible_at_the_end) this.setButtonsPosition(); - }); + if ((dmin > 0) && (dmin < 1)) { + if (this.log) { + let factor = (item.min > 0) ? Math.log10(item.max / item.min) : 2; + if (factor > 10) + factor = 10; + else if (factor < 0.01) + factor = 0.01; + item.min /= Math.pow(10, factor * delta_left * dmin); + item.max *= Math.pow(10, factor * delta_right * (1 - dmin)); + // special handling for Z scale - limit zooming of color scale + if (this.minposbin && this.name === 'zaxis') + item.min = Math.max(item.min, 0.3 * this.minposbin); + } else if ((delta_left === -delta_right) && !item.reverse) { + // shift left/right, try to keep range constant + let delta_shift = (item.max - item.min) * delta_right * dmin; - if (!visible_at_the_end) - main.select('.jsroot_browser_btns').transition().style('left', '7px').style('top', '7px').duration(_duration); + if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta_shift) > 1)) + delta_shift = Math.round(delta_shift); - if (!vsepar.empty()) { - vsepar.transition().style('left', tgt_separ).duration(_duration); - drawing.transition().style('left', tgt_drawing).duration(_duration).on('end', this.checkResize.bind(this)); - } + if (item.min + delta_shift < gmin) + delta_shift = gmin - item.min; + else if (item.max + delta_shift > gmax) + delta_shift = gmax - item.max; - if (this.status_layout && (this.browser_kind === 'fix')) { - main.select('.jsroot_h_separator').transition().style('left', tgt_drawing).duration(_duration); - main.select('.jsroot_status_area').transition().style('left', tgt_drawing).duration(_duration); - } - } + if (delta_shift) { + item.min += delta_shift; + item.max += delta_shift; + } else { + delete item.min; + delete item.max; + } + } else { + let rx_left = (item.max - item.min), rx_right = rx_left; + if (delta_left > 0) + rx_left = 1.001 * rx_left / (1 - delta_left); + item.min += -delta_left * dmin * rx_left; + if (delta_right > 0) + rx_right = 1.001 * rx_right / (1 - delta_right); + item.max -= -delta_right * (1 - dmin) * rx_right; + } + if (item.min >= item.max) + item.min = item.max = undefined; + else if (delta_left !== delta_right) { + // extra check case when moving left or right + if (((item.min < gmin) && (lmin === gmin)) || + ((item.max > gmax) && (lmax === gmax))) + item.min = item.max = undefined; + } else { + item.min = Math.max(item.min, gmin); + item.max = Math.min(item.max, gmax); + } + } else + item.min = item.max = undefined; - /** @summary Adjust browser size */ - adjustBrowserSize(onlycheckmax) { - if (!this.gui_div || (this.browser_kind !== 'float')) return; + item.changed = (item.min !== undefined) && (item.max !== undefined); - const main = this.browser(); - if (main.empty()) return; + return item; + } - const area = main.select('.jsroot_browser_area'), - cont = main.select('.jsroot_browser_hierarchy'), - chld = select(cont.node().firstChild); +}; // AxisPainterMethods - if (onlycheckmax) { - if (area.node().parentNode.clientHeight - 10 < area.node().clientHeight) - area.style('bottom', '0px').style('top', '0px'); - return; - } - if (chld.empty()) return; - const h1 = cont.node().clientHeight, - h2 = chld.node().clientHeight; +/** + * @summary Painter for TAxis object + * + * @private + */ - if ((h2 !== undefined) && (h2 < h1*0.7)) area.style('bottom', ''); - } +class TAxisPainter extends ObjectPainter { - /** @summary Set buttons position */ - setButtonsPosition() { - if (!this.gui_div) return; + /** @summary constructor + * @param {object|string} dom - identifier or dom element + * @param {object} axis - object to draw + * @param {boolean} embedded - if true, painter used in other objects painters */ + constructor(dom, axis, embedded) { + super(dom, axis); - const main = this.browser(), - btns = main.select('.jsroot_browser_btns'); - if (btns.empty()) return; + this.is_gaxis = axis?._typename === clTGaxis; - let top = 7, left = 7; - if (this.browser_visible) { - const area = main.select('.jsroot_browser_area'); - top = area.node().offsetTop + 7; - left = area.node().offsetLeft - main.node().offsetLeft + area.node().clientWidth - 27; - } + Object.assign(this, AxisPainterMethods); + this.initAxisPainter(); - btns.style('left', `${left}px`).style('top', `${top}px`); + this.embedded = embedded; // indicate that painter embedded into the histogram painter + this.invert_side = false; + this.lbls_both_sides = false; // draw labels on both sides } - /** @summary Toggle browser kind */ - async toggleBrowserKind(kind) { - if (!this.gui_div) - return null; - - if (!kind) { - if (!this.browser_kind) - return null; - kind = (this.browser_kind === 'float') ? 'fix' : 'float'; - } + /** @summary cleanup painter */ + cleanup() { + this.cleanupAxisPainter(); + delete this.hist_painter; + delete this.hist_axis; + delete this.is_gaxis; + super.cleanup(); + } - const main = this.browser(), - area = main.select('.jsroot_browser_area'); + /** @summary Use in GED to identify kind of axis */ + getAxisType() { return clTAxis; } - if (this.browser_kind === 'float') { - area.style('bottom', '0px') - .style('top', '0px') - .style('width', '') - .style('height', '') - .classed('jsroot_float_browser', false) - .style('border', null); - } else if (this.browser_kind === 'fix') { - main.select('.jsroot_v_separator').remove(); - area.style('left', '0px'); - this.drawing().style('left', '0px'); // reset size - main.select('.jsroot_h_separator').style('left', '0px'); - this.status().style('left', '0px'); // reset left - this.checkResize(); - } + /** @summary Configure axis painter + * @desc Axis can be drawn inside frame group with offset to 0 point for the frame + * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates + * @private */ + configureAxis(name, min, max, smin, smax, vertical, range, opts) { + const axis = this.getObject(); - this.browser_kind = kind; - this.browser_visible = true; + this.name = name; + this.full_min = min; + this.full_max = max; + this.kind = kAxisNormal; + this.vertical = vertical; + this.log = opts.log || 0; + this.minposbin = opts.minposbin; + this.ignore_labels = opts.ignore_labels; + this.noexp_changed = opts.noexp_changed; + this.symlog = opts.symlog || false; + this.reverse = opts.reverse || false; + // special flag to change align of labels on vertical axis + // it is workaround shown in TGaxis docu + this.reverseAlign = this.vertical && this.reverse && this.is_gaxis && (axis.fX1 !== axis.fX2); + this.swap_side = opts.swap_side || false; + this.fixed_ticks = opts.fixed_ticks || null; + this.maxTickSize = opts.maxTickSize || 0; + this.value_axis = opts.value_axis ?? false; // use fMinimum/fMaximum from source object - main.select('.jsroot_browser_resize').style('display', (kind === 'float') ? null : 'none'); - main.select('.jsroot_browser_title').style('cursor', (kind === 'float') ? 'move' : null); + if (opts.time_scale || axis.fTimeDisplay) { + this.kind = kAxisTime; + this.timeoffset = getTimeOffset(axis); + this.timegmt = getTimeGMT(axis); + } else if (opts.axis_func) + this.kind = kAxisFunc; + else + this.kind = !axis.fLabels || this.ignore_labels ? kAxisNormal : kAxisLabels; - if (kind === 'float') { - area.style('bottom', '40px') - .classed('jsroot_float_browser', true) - .style('border', 'solid 3px white'); + if (this.kind === kAxisTime) + this.func = time().domain([this.convertDate(smin), this.convertDate(smax)]); + else if (this.log) { + if ((this.log === 1) || (this.log === 10)) + this.logbase = 10; + else if (this.log === 3) + this.logbase = Math.E; + else + this.logbase = Math.round(this.log); - const drag_move = drag().on('start', () => { - const sl = area.style('left'), st = area.style('top'); - this._float_left = parseInt(sl.slice(0, sl.length-2)); - this._float_top = parseInt(st.slice(0, st.length-2)); - this._max_left = Math.max(0, main.node().clientWidth - area.node().offsetWidth - 1); - this._max_top = Math.max(0, main.node().clientHeight - area.node().offsetHeight - 1); - }).filter(evnt => { - return main.select('.jsroot_browser_title').node() === evnt.target; - }).on('drag', evnt => { - this._float_left += evnt.dx; - this._float_top += evnt.dy; - area.style('left', Math.min(Math.max(0, this._float_left), this._max_left) + 'px') - .style('top', Math.min(Math.max(0, this._float_top), this._max_top) + 'px'); - this.setButtonsPosition(); - }), + if (smax <= 0) + smax = 1; - drag_resize = drag().on('start', () => { - const sw = area.style('width'); - this._float_width = parseInt(sw.slice(0, sw.length-2)); - this._float_height = area.node().clientHeight; - this._max_width = main.node().clientWidth - area.node().offsetLeft - 1; - this._max_height = main.node().clientHeight - area.node().offsetTop - 1; - }).on('drag', evnt => { - this._float_width += evnt.dx; - this._float_height += evnt.dy; + if (opts.log_min_nz) + this.log_min_nz = opts.log_min_nz; + else if (axis && opts.logcheckmin) { + let v = 0; + for (let i = 0; i < axis.fNbins; ++i) { + v = axis.GetBinLowEdge(i + 1); + if (v > 0) + break; + v = axis.GetBinCenter(i + 1); + if (v > 0) + break; + } + if (v > 0) + this.log_min_nz = v; + } - area.style('width', Math.min(Math.max(100, this._float_width), this._max_width) + 'px') - .style('height', Math.min(Math.max(100, this._float_height), this._max_height) + 'px'); + if ((smin <= 0) && this.log_min_nz) + smin = this.log_min_nz; - this.setButtonsPosition(); - }); + if ((smin <= 0) || (smin >= smax)) + smin = smax * (opts.logminfactor || 1e-4); - main.call(drag_move); - main.select('.jsroot_browser_resize').call(drag_resize); - - this.adjustBrowserSize(); - } else { - area.style('left', '0px').style('top', '0px').style('bottom', '0px').style('height', null); - - const separ_color = settings.DarkMode ? 'grey' : 'azure', - vsepar = main.append('div').classed('jsroot_v_separator', true) - .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; top: 0; bottom: 0; cursor: ew-resize;`), - - drag_move = drag().on('start', () => { - this._vsepar_move = this._vsepar_position; - vsepar.style('background-color', 'grey'); - }).on('drag', evnt => { - this._vsepar_move += evnt.dx; - this.setButtonsPosition(); - settings.BrowserWidth = Math.max(50, Math.round(this._vsepar_move)); - this.adjustSeparators(settings.BrowserWidth, null); - }).on('end', () => { - delete this._vsepar_move; - vsepar.style('background-color', null); - this.checkResize(); - }); + if (this.kind === kAxisFunc) + this.func = this.createFuncHandle(opts.axis_func, this.logbase, smin, smax); + else + this.func = log().base(this.logbase).domain([smin, smax]); + } else if (this.symlog) { + let v = Math.max(Math.abs(smin), Math.abs(smax)); + if (Number.isInteger(this.symlog) && (this.symlog > 0)) + v *= Math.pow(10, -1 * this.symlog); + else + v *= 0.01; + this.func = symlog().constant(v).domain([smin, smax]); + } else if (this.kind === kAxisFunc) + this.func = this.createFuncHandle(opts.axis_func, 0, smin, smax); + else + this.func = linear().domain([smin, smax]); - vsepar.call(drag_move); + if (this.vertical ^ this.reverse) + [range[0], range[1]] = [range[1], range[0]]; - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); + this.func.range(range); - this.adjustSeparators(settings.BrowserWidth, null, true, true); - } + this.scale_min = smin; + this.scale_max = smax; - this.setButtonsPosition(); + if (this.kind === kAxisTime) + this.gr = val => this.func(this.convertDate(val)); + else if (this.log) + this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; + else + this.gr = this.func; - return this; - } + delete this.format;// remove formatting func -} // class BrowserLayout + let ndiv = 508; + if (this.is_gaxis) + ndiv = axis.fNdiv; + else if (axis) + ndiv = axis.fNdivisions ? Math.max(axis.fNdivisions, 4) : 0; -const clTButton = 'TButton', kIsGrayscale = BIT(22); + this.nticks = ndiv % 100; + this.nticks2 = (ndiv % 10000 - this.nticks) / 100; + this.nticks3 = Math.floor(ndiv / 10000); -function getButtonSize(handler, fact) { - return Math.round((fact || 1) * (handler.iscan || !handler.has_canvas ? 16 : 12)); -} + if (axis && !this.is_gaxis) + this.nticks = Math.min(this.nticks, 20); -function toggleButtonsVisibility(handler, action, evnt) { - evnt?.preventDefault(); - evnt?.stopPropagation(); + let gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); + if (gr_range <= 0) + gr_range = 100; - const group = handler.getLayerSvg('btns_layer', handler.this_pad_name), - btn = group.select('[name=\'Toggle\']'); + if (this.kind === kAxisTime) { + this.nticks = Math.min(this.nticks, 8); - if (btn.empty()) return; + const scale_range = this.scale_max - this.scale_min, + idF = axis.fTimeFormat.indexOf('%F'), + tf2 = chooseTimeFormat(scale_range / gr_range, false); + let tf1 = (idF >= 0) ? axis.fTimeFormat.slice(0, idF) : axis.fTimeFormat; - let state = btn.property('buttons_state'); + if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) + tf1 = chooseTimeFormat(scale_range / this.nticks, true); - if (btn.property('timout_handler')) { - if (action !== 'timeout') clearTimeout(btn.property('timout_handler')); - btn.property('timout_handler', null); - } + this.tfunc1 = this.tfunc2 = this.timegmt ? utcFormat(tf1) : timeFormat(tf1); + if (tf2 !== tf1) + this.tfunc2 = this.timegmt ? utcFormat(tf2) : timeFormat(tf2); - let is_visible = false; - switch (action) { - case 'enable': - is_visible = true; - handler.btns_active_flag = true; - break; - case 'enterbtn': - handler.btns_active_flag = true; - return; // do nothing, just cleanup timeout - case 'timeout': is_visible = false; break; - case 'toggle': - state = !state; - btn.property('buttons_state', state); - is_visible = state; - break; - case 'disable': - case 'leavebtn': - handler.btns_active_flag = false; - if (!state) - btn.property('timout_handler', setTimeout(() => toggleButtonsVisibility(handler, 'timeout'), 1200)); - return; - } + this.format = this.formatTime; + } else if (this.log) { + if (this.nticks2 > 1) { + this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally + this.nticks2 = 1; + } + this.noexp = axis?.TestBit(EAxisBits.kNoExponent); + if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed && (this.log === 1)) + this.noexp = true; + this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels); + this.format = this.formatLog; + } else if (this.kind === kAxisLabels) { + this.nticks = 50; // for text output allow max 50 names + const scale_range = this.scale_max - this.scale_min; + if (this.nticks > scale_range) + this.nticks = Math.round(scale_range); - group.selectAll('svg').each(function() { - if (this !== btn.node()) - select(this).style('display', is_visible ? '' : 'none'); - }); -} + this.regular_labels = true; -const PadButtonsHandler = { + if (axis?.fNbins && axis?.fLabels) { + if ((axis.fNbins !== Math.round(axis.fXmax - axis.fXmin)) || + axis.fXmin || (axis.fXmax !== axis.fNbins)) + this.regular_labels = false; + } - alignButtons(btns, width, height) { - const sz0 = getButtonSize(this, 1.25), nextx = (btns.property('nextx') || 0) + sz0; - let btns_x, btns_y; + this.nticks2 = 1; - if (btns.property('vertical')) { - btns_x = btns.property('leftside') ? 2 : (width - sz0); - btns_y = height - nextx; + this.format = this.formatLabels; } else { - btns_x = btns.property('leftside') ? 2 : (width - nextx); - btns_y = height - sz0; - } - - makeTranslate(btns, btns_x, btns_y); - }, - - findPadButton(keyname) { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); - let found_func = ''; - if (!group.empty()) { - group.selectAll('svg').each(function() { - if (select(this).attr('key') === keyname) - found_func = select(this).attr('name'); - }); - } - return found_func; - }, - - removePadButtons() { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); - if (!group.empty()) { - group.selectAll('*').remove(); - group.property('nextx', null); + this.order = 0; + this.ndig = 0; + this.format = this.formatNormal; } - }, + } - showPadButtons() { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); - if (group.empty()) return; + /** @summary Check zooming value for log scale + * @private */ + checkZoomMin(value) { + return this.log && this.log_min_nz ? Math.max(value, this.log_min_nz) : value; + } - // clean all previous buttons - group.selectAll('*').remove(); - if (!this._buttons) return; + /** @summary Return scale min */ + getScaleMin() { + return this.func?.domain()[0] ?? 0; + } - const iscan = this.iscan || !this.has_canvas, y = 0; - let ctrl, x = group.property('leftside') ? getButtonSize(this, 1.25) : 0; + /** @summary Return scale max */ + getScaleMax() { + return this.func?.domain()[1] ?? 0; + } - if (this._fast_drawing) { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, getButtonSize(this), 'enlargePad', false) - .attr('name', 'Enlarge').attr('x', 0).attr('y', 0) - .on('click', evnt => this.clickPadButton('enlargePad', evnt)); - } else { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.rect, getButtonSize(this), 'Toggle tool buttons', false) - .attr('name', 'Toggle').attr('x', 0).attr('y', 0) - .property('buttons_state', (settings.ToolBar !== 'popup') || browser.touches) - .on('click', evnt => toggleButtonsVisibility(this, 'toggle', evnt)); - ctrl.node()._mouseenter = () => toggleButtonsVisibility(this, 'enable'); - ctrl.node()._mouseleave = () => toggleButtonsVisibility(this, 'disable'); + /** @summary Return true if labels may be removed while they are not fit to graphical range */ + cutLabels() { + if (!settings.CutAxisLabels) + return false; + if (isStr(settings.CutAxisLabels)) + return settings.CutAxisLabels.indexOf(this.name) >= 0; + return this.vertical; // cut vertical axis by default + } - for (let k = 0; k < this._buttons.length; ++k) { - const item = this._buttons[k]; - let btn = item.btn; + /** @summary Provide label for axis value */ + formatLabels(d) { + const a = this.getObject(); + let indx = parseFloat(d); + if (!this.regular_labels) + indx = Math.round((indx - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins); + else + indx = Math.floor(indx); + if ((indx < 0) || (indx >= a.fNbins)) + return null; + const arr = a.fLabels.arr; + for (let i = 0; i < arr.length; ++i) { + if (arr[i].fUniqueID === indx + 1) + return arr[i].fString; + } + return null; + } - if (isStr(btn)) - btn = ToolbarIcons[btn]; - if (!btn) - btn = ToolbarIcons.circle; + /** @summary Creates array with minor/middle/major ticks */ + createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { + if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) + this.noticksopt = true; - const svg = ToolbarIcons.createSVG(group, btn, getButtonSize(this), - item.tooltip + (iscan ? '' : (` on pad ${this.this_pad_name}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); + const handle = { painter: this, nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: [], middle: [], major: [] }; + let ticks = []; - if (group.property('vertical')) - svg.attr('x', y).attr('y', x); - else - svg.attr('x', x).attr('y', y); + if (this.fixed_ticks) { + this.fixed_ticks.forEach(v => { + if ((v >= this.scale_min) && (v <= this.scale_max)) + ticks.push(v); + }); + } else if (this.kind === kAxisLabels) { + handle.lbl_pos = []; + const axis = this.getObject(); + for (let n = 0; n <= axis.fNbins; ++n) { + const x = this.regular_labels ? n : axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); + if ((x >= this.scale_min) && (x <= this.scale_max)) { + handle.lbl_pos.push(x); + ticks.push(x); + } + } + } else + ticks = this.produceTicks(this.nticks); - svg.attr('name', item.funcname) - .style('display', ctrl.property('buttons_state') ? '' : 'none') - .attr('key', item.keyname || null) - .on('click', evnt => this.clickPadButton(item.funcname, evnt)); + handle.minor = handle.middle = handle.major = ticks; - svg.node()._mouseenter = () => toggleButtonsVisibility(this, 'enterbtn'); - svg.node()._mouseleave = () => toggleButtonsVisibility(this, 'leavebtn'); + if (only_major_as_array) { + const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; + if (res.at(0) > this.scale_min + delta) + res.unshift(this.scale_min); + if (res.at(-1) < this.scale_max - delta) + res.push(this.scale_max); + return res; + } - x += getButtonSize(this, 1.25); + if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10)) && !this.fixed_ticks) { + handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); + const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); + // avoid black filling by middle-size + if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) + handle.minor = handle.middle = handle.major; + else if ((this.nticks3 > 1) && !this.log) { + handle.minor = this.produceTicks(handle.middle.length, this.nticks3); + if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) + handle.minor = handle.middle; } } - group.property('nextx', x); + handle.reset = function() { + this.nminor = this.nmiddle = this.nmajor = 0; + }; - this.alignButtons(group, this.getPadWidth(), this.getPadHeight()); + handle.next = function(doround) { + if (this.nminor >= this.minor.length) + return false; - if (group.property('vertical')) - ctrl.attr('y', x); - else if (!group.property('leftside')) - ctrl.attr('x', x); - }, + this.tick = this.minor[this.nminor++]; + this.grpos = this.func(this.tick); + if (doround) + this.grpos = Math.round(this.grpos); + this.kind = 3; - assign(painter) { - Object.assign(painter, this); - } + if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { + this.nmiddle++; + this.kind = 2; + } -}, // PadButtonsHandler + if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { + this.nmajor++; + this.kind = 1; + } + return true; + }; -// identifier used in TWebCanvas painter -webSnapIds = { kNone: 0, kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; + handle.last_major = function() { + return (this.kind !== 1) ? false : this.nmajor === this.major.length; + }; + handle.next_major_grpos = function() { + return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); + }; -/** @summary Fill TWebObjectOptions for painter - * @private */ -function createWebObjectOptions(painter) { - if (!painter?.snapid) - return null; + handle.get_modifier = function() { + return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor - 1, this.major); + }; - const obj = { _typename: 'TWebObjectOptions', snapid: painter.snapid.toString(), opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; - if (isFunc(painter.fillWebObjectOptions)) - painter.fillWebObjectOptions(obj); - return obj; -} + this.order = 0; + this.ndig = 0; + // at the moment when drawing labels, we can try to find most optimal text representation for them + if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && handle.major.length) { + let maxorder = 0, minorder = 0, exclorder3 = false; -/** - * @summary Painter for TPad object - * @private - */ + if (!optionNoexp && !this.cutLabels()) { + const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, + ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; -class TPadPainter extends ObjectPainter { + exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} pad - TPad object to draw - * @param {boolean} [iscan] - if TCanvas object */ - constructor(dom, pad, iscan) { - super(dom, pad); - this.pad = pad; - this.iscan = iscan; // indicate if working with canvas - this.this_pad_name = ''; - if (!this.iscan && pad?.fName) { - this.this_pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name - const regexp = /^[A-Za-z][A-Za-z0-9_]*$/; - if (!regexp.test(this.this_pad_name) || ((this.this_pad_name === 'button') && (pad._typename === clTButton))) - this.this_pad_name = 'jsroot_pad_' + internals.id_counter++; - } - this.painters = []; // complete list of all painters in the pad - this.has_canvas = true; - this.forEachPainter = this.forEachPainterInPad; - const d = this.selectDom(); - if (!d.empty() && d.property('_batch_mode')) - this.batch_mode = true; - } + if (maxtick || mintick) { + maxorder = Math.max(ord1, ord2) + 3; + minorder = Math.min(ord1, ord2) - 3; + } + } - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { - if (this.batch_mode !== undefined) - return this.batch_mode; + // now try to find best combination of order and ndig for labels + let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - if (isBatchMode()) - return true; + for (let order = minorder; order <= maxorder; order += 3) { + if (exclorder3 && (order === 3)) + continue; + this.order = order; + this.ndig = 0; + let lbls = [], indx = 0, totallen = 0; + while (indx < handle.major.length) { + const v0 = handle.major[indx], + lbl = this.format(v0, true); + + let bad_value = lbls.indexOf(lbl) >= 0; + if (!bad_value) { + try { + const v1 = parseFloat(lbl) * Math.pow(10, order); + bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); + } catch { + console.warn('Failure by parsing of', lbl); + bad_value = true; + } + } + if (bad_value) { + if (++this.ndig > 15) { + totallen += 1e10; + break; // not too many digits, anyway it will be exponential + } + lbls = []; + indx = totallen = 0; + } else { + lbls.push(lbl); + const p = lbl.indexOf('.'); + if (!order && !optionNoexp && ((p > gStyle.fAxisMaxDigits) || ((p < 0) && (lbl.length > gStyle.fAxisMaxDigits)))) { + totallen += 1e10; // do not use order = 0 when too many digits are there + exclorder3 = false; + } + totallen += lbl.length; + indx++; + } + } - if (!this.iscan && this.has_canvas) - return this.getCanvPainter()?.isBatchMode(); + // for order === 0 we should virtually remove '0.' and extra label on top + if (!order && (this.ndig < 4)) + totallen -= handle.major.length * 2 + 3; - return false; - } + if (totallen < bestlen) { + bestlen = totallen; + bestorder = this.order; + bestndig = this.ndig; + } + } - /** @summary Indicates that is is Root6 pad painter - * @private */ - isRoot6() { return true; } + this.order = bestorder; + this.ndig = bestndig; - /** @summary Returns true if pad is editable */ - isEditable() { - return this.pad?.fEditable ?? true; - } + if (optionInt) { + if (this.order) + console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); + if (this.ndig) + console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); + this.ndig = 0; + this.order = 0; + } + } - /** @summary Returns SVG element for the pad itself - * @private */ - svg_this_pad() { - return this.getPadSvg(this.this_pad_name); + return handle; } - /** @summary Returns main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { - return this.main_painter_ref || null; + /** @summary Is labels should be centered */ + isCenteredLabels() { + if (this.kind === kAxisLabels) + return true; + if (this.log) + return false; + return this.getObject()?.TestBit(EAxisBits.kCenterLabels); } - /** @summary Assign main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - setMainPainter(painter, force) { - if (!this.main_painter_ref || force) - this.main_painter_ref = painter; + /** @summary Is labels should be rotated */ + isRotateLabels() { + return this.getObject()?.TestBit(EAxisBits.kLabelsVert); } - /** @summary cleanup pad and all primitives inside */ - cleanup() { - if (this._doing_draw) - console.error('pad drawing is not completed when cleanup is called'); - - this.painters.forEach(p => p.cleanup()); - - const svg_p = this.svg_this_pad(); - if (!svg_p.empty()) { - svg_p.property('pad_painter', null); - if (!this.iscan) svg_p.remove(); - } - - delete this.main_painter_ref; - delete this.frame_painter_ref; - delete this.pads_cache; - delete this.custom_palette; - delete this._pad_x; - delete this._pad_y; - delete this._pad_width; - delete this._pad_height; - delete this._doing_draw; - delete this._interactively_changed; - delete this._snap_primitives; - delete this._last_grayscale; - delete this._custom_colors; - delete this._custom_palette_indexes; - delete this._custom_palette_colors; - delete this.root_colors; - - this.painters = []; - this.pad = null; - this.this_pad_name = undefined; - this.has_canvas = false; - - selectActivePad({ pp: this, active: false }); - - super.cleanup(); + /** @summary Is title should be rotated */ + isRotateTitle() { + return this.getObject()?.TestBit(EAxisBits.kRotateTitle); } - /** @summary Returns frame painter inside the pad - * @private */ - getFramePainter() { return this.frame_painter_ref; } + /** @summary Add interactive elements to draw axes title */ + addTitleDrag(title_g, vertical, offset_k, reverse, axis_length) { + if (!settings.MoveResize || this.isBatchMode()) + return; - /** @summary get pad width */ - getPadWidth() { return this._pad_width || 0; } + let drag_rect = null, x_0, y_0, i_0, + acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx, can_indx0 = true; + const drag_move = drag().subject(Object); - /** @summary get pad height */ - getPadHeight() { return this._pad_height || 0; } + drag_move.on('start', evnt => { + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - /** @summary get pad rect */ - getPadRect() { - return { - x: this._pad_x || 0, - y: this._pad_y || 0, - width: this.getPadWidth(), - height: this.getPadHeight() - }; - } + const box = title_g.node().getBBox(), // check that elements visible, request precise value + title_length = vertical ? box.height : box.width; - /** @summary return pad log state x or y are allowed */ - getPadLog(name) { - const pad = this.getRootPad(); - if (name === 'x') - return pad?.fLogx; - if (name === 'y') - return pad?.fLogv ?? pad?.fLogy; - return false; - } + x_0 = new_x = acc_x = title_g.property('shift_x'); + y_0 = new_y = acc_y = title_g.property('shift_y'); - /** @summary Returns frame coordiantes - also when frame is not drawn */ - getFrameRect() { - const fp = this.getFramePainter(); - if (fp) return fp.getFrameRect(); + sign_0 = vertical ? (acc_x > 0) : (acc_y > 0); // sign should remain + can_indx0 = !this.hist_painter?.getSnapId(); // online canvas does not allow alternate position - const w = this.getPadWidth(), - h = this.getPadHeight(), - rect = {}; + alt_pos = vertical ? [axis_length, axis_length / 2, 0] : [0, axis_length / 2, axis_length]; // possible positions + const off = vertical ? -title_length / 2 : title_length / 2; + if (this.title_align === 'middle') { + alt_pos[0] += off; + alt_pos[2] -= off; + } else if (this.title_align === 'begin') { + alt_pos[1] -= off; + alt_pos[2] -= 2 * off; + } else { // end + alt_pos[0] += 2 * off; + alt_pos[1] += off; + } - if (this.pad) { - rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.pad.fLeftMargin, this.pad.fRightMargin))*w); - rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.pad.fBottomMargin, this.pad.fTopMargin))*h); - } else { - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); - } + if (this.titleCenter) + curr_indx = 1; + else if ((reverse ^ this.titleOpposite) && can_indx0) + curr_indx = 0; + else + curr_indx = 2; - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - rect.transform = makeTranslate(rect.x, rect.y) || ''; + alt_pos[curr_indx] = vertical ? acc_y : acc_x; + i_0 = curr_indx; - return rect; - } + drag_rect = title_g.append('rect') + .attr('x', box.x) + .attr('y', box.y) + .attr('width', box.width) + .attr('height', box.height) + .style('cursor', 'move') + .call(addHighlightStyle, true); + // .style('pointer-events','none'); // let forward double click to underlying elements + }).on('drag', evnt => { + if (!drag_rect) + return; - /** @summary return RPad object */ - getRootPad(is_root6) { - return (is_root6 === undefined) || is_root6 ? this.pad : null; - } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - /** @summary Cleanup primitives from pad - selector lets define which painters to remove */ - cleanPrimitives(selector) { - if (!isFunc(selector)) return; + acc_x += evnt.dx; + acc_y += evnt.dy; - for (let k = this.painters.length-1; k >= 0; --k) { - if (selector(this.painters[k])) { - this.painters[k].cleanup(); - this.painters.splice(k, 1); + let set_x, set_y, besti = can_indx0 ? 0 : 1; + const p = vertical ? acc_y : acc_x; + + for (let i = 1; i < 3; ++i) { + if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) + besti = i; } - } - } - /** @summary Removes and cleanup specified primitive - * @desc also secondary primitives will be removed - * @return new index to continue loop or -111 if main painter removed - * @private */ - removePrimitive(indx) { - const prim = this.painters[indx], arr = []; - let resindx = indx; - for (let k = this.painters.length-1; k >= 0; --k) { - if ((k === indx) || this.painters[k].isSecondary(prim)) { - arr.push(this.painters[k]); - this.painters.splice(k, 1); - if (k <= indx) resindx--; + if (vertical) { + set_x = acc_x; + set_y = alt_pos[besti]; + } else { + set_y = acc_y; + set_x = alt_pos[besti]; } - } - arr.forEach(painter => { - painter.cleanup(); - if (this.main_painter_ref === painter) { - delete this.main_painter_ref; - resindx = -111; + if (sign_0 === (vertical ? (set_x > 0) : (set_y > 0))) { + new_x = set_x; + new_y = set_y; + curr_indx = besti; + makeTranslate(title_g, new_x, new_y); } - }); + }).on('end', evnt => { + if (!drag_rect) + return; - return resindx; - } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - /** @summary returns custom palette associated with pad or top canvas - * @private */ - getCustomPalette() { - return this.custom_palette || this.getCanvPainter()?.custom_palette; - } + title_g.property('shift_x', new_x) + .property('shift_y', new_y); - /** @summary Returns number of painters - * @private */ - getNumPainters() { return this.painters.length; } + const axis = this.getObject(), axis2 = this.source_axis, + setBit = (bit, on) => { + axis?.SetBit(bit, on); + axis2?.SetBit(bit, on); + }; - /** @summary Provides automatic color - * @desc Uses ROOT colors palette if possible - * @private */ - getAutoColor(numprimitives) { - if (!numprimitives) - numprimitives = this._num_primitives || 5; - if (numprimitives < 2) numprimitives = 2; + this.titleOffset = (vertical ? new_x : new_y) / offset_k; + const offset = this.titleOffset / this.offsetScaling / this.titleSize; + if (axis) + axis.fTitleOffset = offset; + if (axis2) + axis2.fTitleOffset = offset; - let indx = this._auto_color ?? 0; - this._auto_color = (indx + 1) % numprimitives; - if (indx >= numprimitives) indx = numprimitives - 1; + if (curr_indx === 1) { + setBit(EAxisBits.kCenterTitle, true); + this.titleCenter = true; + setBit(EAxisBits.kOppositeTitle, false); + this.titleOpposite = false; + } else if (curr_indx === 0) { + setBit(EAxisBits.kCenterTitle, false); + this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, true); + this.titleOpposite = true; + } else { + setBit(EAxisBits.kCenterTitle, false); + this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, false); + this.titleOpposite = false; + } - const indexes = this._custom_palette_indexes || this.getCanvPainter()?._custom_palette_indexes; + drag_rect.remove(); + drag_rect = null; - if (indexes?.length) { - const p = Math.round(indx * (indexes.length - 3) / (numprimitives - 1)); - return indexes[p]; - } + if ((x_0 !== new_x) || (y_0 !== new_y) || (i_0 !== curr_indx)) + this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter ? 1 : 0})`); - if (!this._auto_palette) - this._auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); - const palindx = Math.round(indx * (this._auto_palette.getLength()-3) / (numprimitives-1)), - colvalue = this._auto_palette.getColor(palindx); + if (this.hist_painter && this.hist_axis) + this.hist_painter.getCanvPainter()?.producePadEvent('select', this.hist_painter.getPadPainter(), this); + }); - return this.addColor(colvalue); + title_g.style('cursor', 'move').call(drag_move); } - /** @summary Call function for each painter in pad - * @param {function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and subpads, 'objects' only for object in current pad + /** @summary Configure hist painter which creates axis - to be able submit execs * @private */ - forEachPainterInPad(userfunc, kind) { - if (!kind) kind = 'all'; - if (kind !== 'objects') userfunc(this); - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') - userfunc(sub); - } - } - - /** @summary register for pad events receiver - * @desc in pad painter, while pad may be drawn without canvas */ - registerForPadEvents(receiver) { - this.pad_events_receiver = receiver; + setHistPainter(hist_painter, axis_name) { + this.hist_painter = hist_painter; + this.hist_axis = axis_name; } - /** @summary Generate pad events, normally handled by GED - * @desc in pad painter, while pad may be drawn without canvas + /** @summary Submit exec for the axis - if possible * @private */ - producePadEvent(what, padpainter, painter, position, place) { - if ((what === 'select') && isFunc(this.selectActivePad)) - this.selectActivePad(padpainter, painter, position); - - if (isFunc(this.pad_events_receiver)) - this.pad_events_receiver({ what, padpainter, painter, position, place }); + submitAxisExec(exec, only_gaxis) { + const snapid = this.hist_painter?.getSnapId(); + if (snapid && this.hist_axis && !only_gaxis) + this.submitCanvExec(exec, `${snapid}#${this.hist_axis}`); + else if (this.is_gaxis) + this.submitCanvExec(exec); } - /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos, place) { - const istoppad = this.iscan || !this.has_canvas, - canp = istoppad ? this : this.getCanvPainter(); - - if (painter === undefined) painter = this; + /** @summary Produce svg path for axis ticks */ + produceTicksPath(handle, side, tickSize, ticksPlusMinus, secondShift, real_draw) { + let path1 = '', path2 = ''; + this.ticks = []; - if (pos && !istoppad) - pos = getAbsPosInCanvas(this.svg_this_pad(), pos); + while (handle.next(true)) { + let h1 = Math.round(tickSize / 4), h2 = 0; - selectActivePad({ pp: this, active: true }); + if (handle.kind < 3) + h1 = Math.round(tickSize / 2); - canp?.producePadEvent('select', this, painter, pos, place); - } + if (handle.kind === 1) { + // if not showing labels, not show large tick + // FIXME: for labels last tick is smaller + if (!this.isExtraLogTick(handle.tick) && (this.format(handle.tick, true) !== null)) + h1 = tickSize; + this.ticks.push(handle.grpos); // keep graphical positions of major ticks + } - /** @summary Draw pad active border - * @private */ - drawActiveBorder(svg_rect, is_active) { - if (is_active !== undefined) { - if (this.is_active_pad === is_active) return; - this.is_active_pad = is_active; - } + if (ticksPlusMinus > 0) + h2 = -h1; + else if (side < 0) { + h2 = -h1; + h1 = 0; + } - if (this.is_active_pad === undefined) return; + path1 += this.vertical ? `M${h1},${handle.grpos}H${h2}` : `M${handle.grpos},${-h1}V${-h2}`; - if (!svg_rect) - svg_rect = this.iscan ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.svg_this_pad().selectChild('.root_pad_border'); + if (secondShift) + path2 += this.vertical ? `M${secondShift - h1},${handle.grpos}H${secondShift - h2}` : `M${handle.grpos},${secondShift + h1}V${secondShift + h2}`; + } - const cp = this.getCanvPainter(); + return real_draw ? path1 + path2 : ''; + } - let lineatt = this.is_active_pad && cp?.highlight_gpad ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; + /** @summary Returns modifier for axis label */ + findLabelModifier(axis, nlabel, positions) { + if (!axis.fModLabs) + return null; + for (let n = 0; n < axis.fModLabs.arr.length; ++n) { + const mod = axis.fModLabs.arr[n]; - if (!lineatt) lineatt = new TAttLineHandler({ color: 'none' }); + if ((mod.fLabValue !== undefined) && (mod.fLabNum === 0)) { + const eps = this.log ? positions[nlabel] * 1e-6 : (this.scale_max - this.scale_min) * 1e-6; + if (Math.abs(mod.fLabValue - positions[nlabel]) < eps) + return mod; + } - svg_rect.call(lineatt.func); + if ((mod.fLabNum === nlabel + 1) || + ((mod.fLabNum < 0) && (nlabel === positions.length + mod.fLabNum))) + return mod; + } + return null; } - /** @summary Set fast drawing property depending on the size - * @private */ - setFastDrawing(w, h) { - const was_fast = this._fast_drawing; - this._fast_drawing = settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this._fast_drawing) - this.showPadButtons(); - } + /** @summary Draw axis labels + * @return {Promise} with array label size and max width */ + async drawLabels(axis_g, axis, w, h, handle, labelsside, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { + const center_lbls = this.isCenteredLabels(), + label_g = [axis_g.append('svg:g').attr('class', 'axis_labels')], + lbl_pos = handle.lbl_pos || handle.major, + tilt_angle = gStyle.AxisTiltAngle ?? 25; + let rotate_lbls = this.isRotateLabels(), + textscale = 1, flipscale = 1, maxtextlen = 0, applied_scale = 0, + lbl_tilt = false, any_modified = false, max_textwidth = 0, max_tiltsize = 0; - /** @summary Returns true if canvas configured with grayscale - * @private */ - isGrayscale() { - if (!this.iscan) return false; - return this.pad?.TestBit(kIsGrayscale) ?? false; - } + if (this.lbls_both_sides) + label_g.push(axis_g.append('svg:g').attr('class', 'axis_labels').attr('transform', this.vertical ? `translate(${w})` : `translate(0,${-h})`)); - /** @summary Set grayscale mode for the canvas - * @private */ - setGrayscale(flag) { - if (!this.iscan) return; + if (frame_ygap > 0) + max_tiltsize = frame_ygap / Math.sin(tilt_angle / 180 * Math.PI) - Math.tan(tilt_angle / 180 * Math.PI); - let changed = false; + // function called when text is drawn to analyze width, required to correctly scale all labels + // must be function to correctly handle 'this' argument + function process_drawtext_ready(painter) { + const textwidth = this.result_width; + max_textwidth = Math.max(max_textwidth, textwidth); - if (flag === undefined) { - flag = this.pad?.TestBit(kIsGrayscale) ?? false; - changed = (this._last_grayscale !== undefined) && (this._last_grayscale !== flag); - } else if (flag !== this.pad?.TestBit(kIsGrayscale)) { - this.pad?.InvertBit(kIsGrayscale); - changed = true; + const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); + + if (!painter.vertical && !rotate_lbls && this.result_height && maxwidth) + flipscale = Math.min(flipscale, maxwidth / this.result_height); + + if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) + textscale = Math.min(textscale, maxwidth / textwidth); + else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) + textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth); + + if ((textscale > 0.0001) && (textscale < 0.7) && !any_modified && + !painter.vertical && !rotate_lbls && (label_g.length === 1) && (lbl_tilt === false)) { + if (maxtextlen > 5) + lbl_tilt = true; + } + + let scale = textscale; + + if (lbl_tilt) { + if (max_tiltsize && max_textwidth) { + scale = Math.min(1, 0.8 * max_tiltsize / max_textwidth); + if (scale < textscale) { + // if due to tilt scale is even smaller - ignore tilting + lbl_tilt = 0; + scale = textscale; + } + } else + scale *= 3; + } + + if (((scale > 0.0001) && (scale < 1)) || (lbl_tilt !== false)) { + applied_scale = 1 / scale; + painter.scaleTextDrawing(applied_scale, label_g[0]); + } } - if (changed) - this.forEachPainter(p => { delete p._color_palette; }); + // check if short labels can be rotated + if (!this.vertical && this.regular_labels && !rotate_lbls) { + let tlen = 0; + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + const text = this.format(lbl_pos[nmajor], true); + if (text) + tlen = Math.max(tlen, text.length); + } - this.root_colors = flag ? getGrayColors(this._custom_colors) : this._custom_colors; + if ((tlen > 2) && (tlen <= 5) && (lbl_pos.length * labelsFont.size > w / 2)) { + rotate_lbls = true; + lbl_tilt = 0; + } + } - this._last_grayscale = flag; + const draw_labels = (lcnt, side) => { + return this.startTextDrawingAsync(labelsFont, 'font', label_g[lcnt]).then(() => { + let lastpos = 0; + const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; - this.custom_palette = this._custom_palette_colors ? new ColorPalette(this._custom_palette_colors, flag) : null; - } + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + let text = this.format(lbl_pos[nmajor], true); + if (text === null) + continue; - /** @summary Create SVG element for canvas */ - createCanvasSvg(check_resize, new_size) { - const is_batch = this.isBatchMode(), lmt = 5; - let factor = null, svg = null, rect = null, btns, info, frect; + const mod = this.findLabelModifier(axis, nmajor, lbl_pos); + if (mod?.fTextSize === 0) + continue; - if (check_resize > 0) { - if (this._fixed_size) - return check_resize > 1; // flag used to force re-drawing of all subpads + if (mod) + any_modified = true; + if (mod?.fLabText) + text = mod.fLabText; - svg = this.getCanvSvg(); - if (svg.empty()) - return false; + const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; + let pos = Math.round(this.func(lbl_pos[nmajor])); - factor = svg.property('height_factor'); + // exclude labels for extra log ticks + if (lastpos && this.vertical && (Math.abs(pos - lastpos) < labelsFont.size * 1.1) && this.isExtraLogTick(lbl_pos[nmajor])) { + lastpos = pos; + continue; + } - rect = this.testMainResize(check_resize, null, factor); + if (mod?.fTextColor > 0) + arg.color = this.getColor(mod.fTextColor); - if (!rect.changed && (check_resize === 1)) - return false; + arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; - if (!is_batch) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - info = this.getLayerSvg('info_layer', this.this_pad_name); - frect = svg.selectChild('.canvas_fillrect'); - } else { - const render_to = this.selectDom(); + if (center_lbls) { + const gap = arg.gap_after || arg.gap_before; + pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); + if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) + continue; + } - if (render_to.style('position') === 'static') - render_to.style('position', 'relative'); + maxtextlen = Math.max(maxtextlen, text.length); - svg = render_to.append('svg') - .attr('class', 'jsroot root_canvas') - .property('pad_painter', this) // this is custom property - .property('current_pad', '') // this is custom property - .property('redraw_by_resize', false); // could be enabled to force redraw by each resize + if (this.vertical) { + arg.x = fix_coord; + arg.y = pos; + const flag = this.optionLeft || this.reverseAlign || (side < 0); + arg.align = rotate_lbls ? (flag ? 23 : 21) : (flag ? 12 : 32); + if (this.cutLabels()) { + const gap = labelsFont.size * (rotate_lbls ? 1.5 : 0.6); + if ((pos < gap) || (pos > h - gap)) + continue; + } + } else { + arg.x = pos; + arg.y = fix_coord; + arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 21 : 23); + if (this.log && !this.noexp && !this.vertical && arg.align === 23) { + arg.align = 21; + arg.y += labelsFont.size; + } else if (arg.align % 10 === 3) + arg.y -= labelsFont.size * 0.1; // font takes 10% more by top align + + if (this.cutLabels()) { + const gap = labelsFont.size * (rotate_lbls ? 0.4 : 1.5); + if ((pos < gap) || (pos > w - gap)) + continue; + } + } - this.setTopPainter(); // assign canvas as top painter of that element + if (rotate_lbls) + arg.rotate = 270; + else if (mod && mod.fTextAngle !== -1) + arg.rotate = -mod.fTextAngle; - if (is_batch) - svg.attr('xmlns', nsSVG); - else if (!this.online_canvas) - svg.append('svg:title').text('ROOT canvas'); + // only for major text drawing scale factor need to be checked + // for modified labels ignore scaling + if ((lcnt === 0) && !mod?.fLabText) + arg.post_process = process_drawtext_ready; - if (!is_batch || (this.pad.fFillStyle > 0)) - frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); + this.drawText(arg); - if (!is_batch) { - frect.style('pointer-events', 'visibleFill') - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } + // workaround for symlog where labels can be compressed to close + if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + const axis_step = Math.abs(pos - lastpos); + textscale = Math.min(textscale, 1.1 * axis_step / labelsFont.size); + } - svg.append('svg:g').attr('class', 'primitives_layer'); - info = svg.append('svg:g').attr('class', 'info_layer'); - if (!is_batch) { - btns = svg.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide === 'left') - .property('vertical', settings.ToolBarVert); - } + lastpos = pos; + } - factor = 0.66; - if (this.pad?.fCw && this.pad?.fCh && (this.pad?.fCw > 0)) { - factor = this.pad.fCh / this.pad.fCw; - if ((factor < 0.1) || (factor > 10)) factor = 0.66; - } + if (this.order) { + let xoff = 0, yoff = 0; + if (this.name === 'xaxis') { + xoff = gStyle.fXAxisExpXOffset || 0; + yoff = gStyle.fXAxisExpYOffset || 0; + } else if (this.name === 'yaxis') { + xoff = gStyle.fYAxisExpXOffset || 0; + yoff = gStyle.fYAxisExpYOffset || 0; + } - if (this._fixed_size) { - render_to.style('overflow', 'auto'); - rect = { width: this.pad.fCw, height: this.pad.fCh }; - if (!rect.width || !rect.height) - rect = getElementRect(render_to); - } else - rect = this.testMainResize(2, new_size, factor); - } + if (xoff) + xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); + if (yoff) + yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); + + this.drawText({ + color: labelsFont.color, + x: xoff + (this.vertical ? side * 5 : w + 5), + y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3 * side)), + align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), + latex: 1, + text: '#times' + this.formatExp(10, this.order), + draw_g: label_g[lcnt] + }); + } - this.setGrayscale(); + if ((lcnt > 0) && applied_scale) + this.scaleTextDrawing(applied_scale, label_g[lcnt]); - this.createAttFill({ attr: this.pad }); + return this.finishTextDrawing(label_g[lcnt], true); + }); + }; - if ((rect.width <= lmt) || (rect.height <= lmt)) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - if (this._pad_width && this._pad_height) { - // use last valid dimensions - rect.width = this._pad_width; - rect.height = this._pad_height; - } else { - // just to complete drawing. - rect.width = 800; - rect.height = 600; - } - } else - svg.style('display', null); + return draw_labels(0, labelsside).then(() => { + return label_g.length < 2 ? true : draw_labels(1, -labelsside); + }).then(() => { + this._maxlbllen = maxtextlen; // for internal use in palette painter - svg.attr('x', 0).attr('y', 0).style('position', 'absolute'); + if (lbl_tilt) { + label_g[0].selectAll('text').each(function() { + const txt = select(this), tr = txt.attr('transform'); + if (lbl_tilt) + txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); + }); + } - if (this._fixed_size) - svg.attr('width', rect.width).attr('height', rect.height); - else - svg.style('width', '100%').style('height', '100%').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); + return max_textwidth; + }); + } - svg.style('filter', settings.DarkMode || this.pad?.$dark ? 'invert(100%)' : null); + /** @summary Extract major draw attributes, which are also used in interactive operations + * @private */ + extractDrawAttributes(scalingSize, w, h) { + const axis = this.getObject(); + let pp = this.getPadPainter(); + if (axis.$use_top_pad) + pp = pp?.getPadPainter(); // workaround for ratio plot + const pad_w = pp?.getPadWidth() || scalingSize || w / 0.8, // use factor 0.8 as ratio between frame and pad size + pad_h = pp?.getPadHeight() || scalingSize || h / 0.8, + // if no external scaling size use scaling as in TGaxis.cxx:1448 - NDC axis length is in the scaling factor + tickScalingSize = scalingSize || (this.vertical ? h / pad_h * pad_w : w / pad_w * pad_h), + bit_plus = axis.TestBit(EAxisBits.kTickPlus), bit_minus = axis.TestBit(EAxisBits.kTickMinus); - svg.attr('viewBox', `0 0 ${rect.width} ${rect.height}`) - .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio - .property('height_factor', factor) - .property('draw_x', 0) - .property('draw_y', 0) - .property('draw_width', rect.width) - .property('draw_height', rect.height); + let tickSize, titleColor, titleFontId, offset; - this._pad_x = 0; - this._pad_y = 0; - this._pad_width = rect.width; - this._pad_height = rect.height; + this.scalingSize = scalingSize || Math.max(Math.min(pad_w, pad_h), 10); - if (frect) { - frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) - .call(this.fillatt.func); - this.drawActiveBorder(frect); + if (this.is_gaxis) { + const optionSize = axis.fChopt.indexOf('S') >= 0; + this.optionUnlab = axis.fChopt.indexOf('U') >= 0; + this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || bit_minus; + this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || bit_plus; + this.optionNoopt = (axis.fChopt.indexOf('N') >= 0); // no ticks position optimization + this.optionInt = (axis.fChopt.indexOf('I') >= 0); // integer labels + this.optionText = (axis.fChopt.indexOf('T') >= 0); // text scaling? + this.optionLeft = (axis.fChopt.indexOf('L') >= 0); // left text align + this.optionRight = (axis.fChopt.indexOf('R') >= 0); // right text align + this.optionCenter = (axis.fChopt.indexOf('C') >= 0); // center text align + this.createAttLine({ attr: axis }); + tickSize = optionSize ? axis.fTickSize : 0.03; + titleColor = this.getColor(axis.fTextColor); + titleFontId = axis.fTextFont; + offset = axis.fLabelOffset; + // workaround for old reverse axes where offset is not properly working + if (this.reverse && (!this.vertical || (!this.optionMinus && (axis.fX1 !== axis.fX2)))) + offset = -offset; + } else { + this.optionUnlab = false; + if (!bit_plus && !bit_minus) { + this.optionMinus = this.vertical ^ this.invert_side; + this.optionPlus = !this.optionMinus; + } else { + this.optionPlus = bit_plus; + this.optionMinus = bit_minus; + } + this.optionNoopt = false; // no ticks position optimization + this.optionInt = false; // integer labels + this.optionText = false; + this.createAttLine({ color: axis.fAxisColor, width: 1, style: 1 }); + tickSize = axis.fTickLength; + titleColor = this.getColor(axis.fTitleColor); + titleFontId = axis.fTitleFont; + offset = axis.fLabelOffset; } - this.setFastDrawing(rect.width * (1 - this.pad.fLeftMargin - this.pad.fRightMargin), rect.height * (1 - this.pad.fBottomMargin - this.pad.fTopMargin)); + offset += (this.vertical ? 0.002 : 0.005); - if (this.alignButtons && btns) - this.alignButtons(btns, rect.width, rect.height); + if (this.kind === kAxisLabels) + this.optionText = true; - let dt = info.selectChild('.canvas_date'); - if (!gStyle.fOptDate) - dt.remove(); - else { - if (dt.empty()) - dt = info.append('text').attr('class', 'canvas_date'); - const posy = Math.round(rect.height * (1 - gStyle.fDateY)), - date = new Date(); - let posx = Math.round(rect.width * gStyle.fDateX); - if (!is_batch && (posx < 25)) - posx = 25; - if (gStyle.fOptDate > 3) - date.setTime(gStyle.fOptDate*1000); + this.optionNoexp = axis.TestBit(EAxisBits.kNoExponent); - makeTranslate(dt, posx, posy) - .style('text-anchor', 'start') - .text(convertDate(date)); - } + this.ticksSize = Math.round(tickSize * tickScalingSize); + if (scalingSize && (this.ticksSize < 0)) + this.ticksSize = -this.ticksSize; - const iname = this.getItemName(); - if (iname) - this.drawItemNameOnCanvas(iname); - else if (!gStyle.fOptFile) - info.selectChild('.canvas_item').remove(); + if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) + this.ticksSize = this.maxTickSize; - return true; - } + // now used only in 3D drawing + this.ticksColor = this.lineatt.color; + this.ticksWidth = this.lineatt.width; - /** @summary Draw item name on canvas if gStyle.fOptFile is configured - * @private */ - drawItemNameOnCanvas(item_name) { - const info = this.getLayerSvg('info_layer', this.this_pad_name); - let df = info.selectChild('.canvas_item'); - const fitem = getHPainter().findRootFileForItem(item_name), - fname = (gStyle.fOptFile === 3) ? item_name : ((gStyle.fOptFile === 2) ? fitem?._fullurl : fitem?._name); + const k = this.optionText ? 0.66666 : 1; // set TGaxis.cxx, line 1504 + this.labelSize = Math.round((axis.fLabelSize < 1) ? k * axis.fLabelSize * this.scalingSize : k * axis.fLabelSize); + this.labelsOffset = Math.round(offset * this.scalingSize); + this.labelsFont = new FontHandler(axis.fLabelFont, this.labelSize, scalingSize); + if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) + this.optionUnlab = true; // disable labels when size not specified + this.labelsFont.setColor(this.getColor(axis.fLabelColor)); - if (!gStyle.fOptFile || !fname) - df.remove(); - else { - if (df.empty()) - df = info.append('text').attr('class', 'canvas_item'); - const rect = this.getPadRect(); - makeTranslate(df, Math.round(rect.width * (1 - gStyle.fDateX)), Math.round(rect.height * (1 - gStyle.fDateY))) - .style('text-anchor', 'end') - .text(fname); - } - if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { - info.selectChild('.canvas_date') - .text(convertDate(getTDatime(gStyle.fOptDate === 2 ? fitem._file.fDatimeC : fitem._file.fDatimeM))); + this.fTitle = axis.fTitle; + if (this.fTitle) { + this.titleSize = (axis.fTitleSize >= 1) ? axis.fTitleSize : Math.round(axis.fTitleSize * this.scalingSize); + this.titleFont = new FontHandler(titleFontId, this.titleSize, scalingSize); + this.titleFont.setColor(titleColor); + this.offsetScaling = (axis.fTitleSize >= 1) ? 1 : (this.vertical ? pad_w : pad_h) / this.scalingSize; + this.titleOffset = axis.fTitleOffset; + if (!this.titleOffset && this.name[0] === 'x') + this.titleOffset = gStyle.fXaxis.fTitleOffset; + this.titleOffset *= this.titleSize * this.offsetScaling; + this.titleCenter = axis.TestBit(EAxisBits.kCenterTitle); + this.titleOpposite = axis.TestBit(EAxisBits.kOppositeTitle); + } else { + delete this.titleSize; + delete this.titleFont; + delete this.offsetScaling; + delete this.titleOffset; + delete this.titleCenter; + delete this.titleOpposite; } } - /** @summary Return true if this pad enlarged */ - isPadEnlarged() { - if (this.iscan || !this.has_canvas) - return this.enlargeMain('state') === 'on'; - return this.getCanvSvg().property('pad_enlarged') === this.pad; - } + /** @summary function draws TAxis or TGaxis object + * @return {Promise} for drawing ready */ + async drawAxis(layer, w, h, transform, secondShift, disable_axis_drawing, max_text_width, calculate_position, frame_ygap) { + const axis = this.getObject(), + swap_side = this.swap_side || false; + let axis_g = layer, draw_lines = true; - /** @summary Enlarge pad draw element when possible */ - enlargePad(evnt, is_dblclick, is_escape) { - evnt?.preventDefault(); - evnt?.stopPropagation(); + // shift for second ticks set (if any) + if (!secondShift) + secondShift = 0; + else if (this.invert_side) + secondShift = -secondShift; - // ignore double click on canvas itself for enlarge - if (is_dblclick && this._websocket && (this.enlargeMain('state') === 'off')) - return; + this.extractDrawAttributes(undefined, w, h); - const svg_can = this.getCanvSvg(), - pad_enlarged = svg_can.property('pad_enlarged'); + if (this.is_gaxis) + draw_lines = Boolean(axis.fLineColor); - if (this.iscan || !this.has_canvas || (!pad_enlarged && !this.hasObjectsToDraw() && !this.painters)) { - if (this._fixed_size) return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) return; - if (this.enlargeMain('state') === 'off') - svg_can.property('pad_enlarged', null); + if (!this.is_gaxis || (this.name === 'zaxis')) { + axis_g = layer.selectChild(`.${this.name}_container`); + if (axis_g.empty()) + axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); else - selectActivePad({ pp: this, active: true }); - } else if (!pad_enlarged && !is_escape) { - this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.pad); - selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.pad) { - this.enlargeMain(false); - svg_can.property('pad_enlarged', null); - } else if (!is_escape && is_dblclick) - console.error('missmatch with pad double click events'); - - return this.checkResize(true); - } + axis_g.selectAll('*').remove(); + } - /** @summary Create main SVG element for pad - * @return true when pad is displayed and all its items should be redrawn */ - createPadSvg(only_resize) { - if (!this.has_canvas) { - this.createCanvasSvg(only_resize ? 2 : 0); - return true; + let axis_lines = ''; + if (draw_lines) { + axis_lines = 'M0,0' + (this.vertical ? `v${h}` : `h${w}`); + if (secondShift) + axis_lines += this.vertical ? `M${secondShift},0v${h}` : `M0,${secondShift}h${w}`; } - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'), - pad_enlarged = svg_can.property('pad_enlarged'), - pad_visible = !this.pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.pad)), - is_batch = this.isBatchMode(); - let w = Math.round(this.pad.fAbsWNDC * width), - h = Math.round(this.pad.fAbsHNDC * height), - x = Math.round(this.pad.fAbsXlowNDC * width), - y = Math.round(height * (1 - this.pad.fAbsYlowNDC)) - h, - svg_pad, svg_border, btns; + axis_g.attr('transform', transform); - if (pad_enlarged === this.pad) { w = width; h = height; x = y = 0; } + let side = 1, ticksPlusMinus = 0; - if (only_resize) { - svg_pad = this.svg_this_pad(); - svg_border = svg_pad.selectChild('.root_pad_border'); - if (!is_batch) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); - this.addPadInteractive(true); - } else { - svg_pad = svg_can.selectChild('.primitives_layer') - .append('svg:svg') // svg used to blend all drawings outside - .classed('__root_pad_' + this.this_pad_name, true) - .attr('pad', this.this_pad_name) // set extra attribute to mark pad name - .property('pad_painter', this); // this is custom property + if (this.optionPlus && this.optionMinus) + side = ticksPlusMinus = 1; + else if (this.optionMinus) + side = (swap_side ^ this.vertical) ? 1 : -1; + else if (this.optionPlus) + side = (swap_side ^ this.vertical) ? -1 : 1; - if (!is_batch) - svg_pad.append('svg:title').text('subpad ' + this.this_pad_name); + // first draw ticks - // need to check attributes directly while attributes objects will be created later - if (!is_batch || (this.pad.fFillStyle > 0) || ((this.pad.fLineStyle > 0) && (this.pad.fLineColor > 0))) - svg_border = svg_pad.append('svg:path').attr('class', 'root_pad_border'); + const handle = this.createTicks(false, this.optionNoexp, this.optionNoopt, this.optionInt); - if (!is_batch) { - svg_border.style('pointer-events', 'visibleFill') // get events also for not visible rect - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } + axis_lines += this.produceTicksPath(handle, side, this.ticksSize, ticksPlusMinus, secondShift, draw_lines && !disable_axis_drawing && !this.disable_ticks); - svg_pad.append('svg:g').attr('class', 'primitives_layer'); - if (!is_batch) { - btns = svg_pad.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide !== 'left') - .property('vertical', settings.ToolBarVert); - } + if (!disable_axis_drawing && axis_lines && !this.lineatt.empty()) { + axis_g.append('svg:path') + .attr('d', axis_lines) + .call(this.lineatt.func); } - this.createAttFill({ attr: this.pad }); - this.createAttLine({ attr: this.pad, color0: !this.pad.fBorderMode ? 'none' : '' }); + let title_shift_x = 0, title_shift_y = 0, title_g, labelsMaxWidth = 0; + // draw labels (sometime on both sides) + const labelSize = Math.max(this.labelsFont.size, 5), + pr = (disable_axis_drawing || this.optionUnlab) + ? Promise.resolve(0) + : this.drawLabels(axis_g, axis, w, h, handle, side, this.labelsFont, this.labelsOffset, this.ticksSize, ticksPlusMinus, max_text_width, frame_ygap); - svg_pad.style('display', pad_visible ? null : 'none') - .attr('viewBox', `0 0 ${w} ${h}`) // due to svg - .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio - .attr('x', x) // due to svg - .attr('y', y) // due to svg - .attr('width', w) // due to svg - .attr('height', h) // due to svg - .property('draw_x', x) // this is to make similar with canvas - .property('draw_y', y) - .property('draw_width', w) - .property('draw_height', h); + return pr.then(maxw => { + labelsMaxWidth = maxw; - this._pad_x = x; - this._pad_y = y; - this._pad_width = w; - this._pad_height = h; + if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { + const r = axis_g.append('svg:rect') + .attr('class', 'axis_zoom') + .style('opacity', '0') + .style('cursor', 'crosshair'); - if (svg_border) { - svg_border.attr('d', `M0,0H${w}V${h}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - this.drawActiveBorder(svg_border); - - let svg_border1 = svg_pad.selectChild('.root_pad_border1'), - svg_border2 = svg_pad.selectChild('.root_pad_border2'); - - if (this.pad.fBorderMode && this.pad.fBorderSize) { - const pw = this.pad.fBorderSize, ph = this.pad.fBorderSize, - side1 = `M0,0h${w}l${-pw},${ph}h${2*pw-w}v${h-2*ph}l${-pw},${ph}z`, - side2 = `M${w},${h}v${-h}l${-pw},${ph}v${h-2*ph}h${2*pw-w}l${-pw},${ph}z`; - - if (svg_border2.empty()) - svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); - if (svg_border1.empty()) - svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); - - svg_border1.attr('d', this.pad.fBorderMode > 0 ? side1 : side2) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); - svg_border2.attr('d', this.pad.fBorderMode > 0 ? side2 : side1) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatHex()); - } else { - svg_border1.remove(); - svg_border2.remove(); + if (this.vertical) { + const rw = Math.max(labelsMaxWidth, 2 * labelSize) + 3; + r.attr('x', (side > 0) ? -rw : 0).attr('y', 0) + .attr('width', rw).attr('height', h); + } else { + r.attr('x', 0).attr('y', (side > 0) ? 0 : -labelSize - 3) + .attr('width', w).attr('height', labelSize + 3); + } } - } - this.setFastDrawing(w * (1 - this.pad.fLeftMargin-this.pad.fRightMargin), h * (1 - this.pad.fBottomMargin - this.pad.fTopMargin)); + this.position = 0; - // special case of 3D canvas overlay - if (svg_pad.property('can3d') === constants$1.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.this_pad_name) - .style('display', pad_visible ? '' : 'none'); - } + if (calculate_position) { + const node1 = axis_g.node(), + node2 = this.getPadPainter()?.getPadSvg().node(); + if (isFunc(node1?.getBoundingClientRect) && isFunc(node2?.getBoundingClientRect)) { + const rect1 = node1.getBoundingClientRect(), + rect2 = node2.getBoundingClientRect(); + this.position = rect1.left - rect2.left; // use to control left position of Y scale + } + if (node1 && !node2) + console.warn('Why PAD element missing when search for position'); + } - if (this.alignButtons && btns) - this.alignButtons(btns, w, h); + if (!this.fTitle || disable_axis_drawing) + return; - return pad_visible; - } + title_g = axis_g.append('svg:g').attr('class', 'axis_title'); - /** @summary Add pad interactive features like dragging and resize - * @private */ - addPadInteractive(cleanup = false) { - if (isFunc(this.$userInteractive)) { - this.$userInteractive(); - delete this.$userInteractive; - } + return this.startTextDrawingAsync(this.titleFont, 'font', title_g); + }).then(() => { + if (!title_g) + return; - if (this.isBatchMode() || this.iscan) - return; + const rotate = this.isRotateTitle() ? -1 : 1, + xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'); + let title_offest_k = side; - addDragHandler(this, { - cleanup, // do cleanup to let assign new handlers later on - x: this._pad_x, y: this._pad_y, width: this._pad_width, height: this._pad_height, no_transform: true, - only_resize: true, // !cleanup && (this._disable_dragging || this.getFramePainter()?.mode3d), - is_disabled: kind => svg_can.property('pad_enlarged') || this.btns_active_flag || - (kind === 'move' && (this._disable_dragging || this.getFramePainter()?.mode3d)), - getDrawG: () => this.svg_this_pad(), - pad_rect: { width, height }, - minwidth: 20, minheight: 20, - move_resize: (_x, _y, _w, _h) => { - const x0 = this.pad.fAbsXlowNDC, - y0 = this.pad.fAbsYlowNDC, - scale_w = _w / width / this.pad.fAbsWNDC, - scale_h = _h / height / this.pad.fAbsHNDC, - shift_x = _x / width - x0, - shift_y = 1 - (_y + _h) / height - y0; - this.forEachPainterInPad(p => { - p.pad.fAbsXlowNDC += (p.pad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; - p.pad.fAbsYlowNDC += (p.pad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; - p.pad.fAbsWNDC *= scale_w; - p.pad.fAbsHNDC *= scale_h; - }, 'pads'); - }, - redraw: () => this.interactiveRedraw('pad', 'padpos') - }); - } + this.title_align = this.titleCenter ? 'middle' : (myxor ? 'begin' : 'end'); - /** @summary Disable pad drawing - * @desc Complete SVG element will be hidden */ - disablePadDrawing() { - if (!this.pad_draw_disabled && this.has_canvas && !this.iscan) { - this.pad_draw_disabled = true; - this.createPadSvg(true); - } - } + if (this.vertical) { + title_offest_k *= -1.6; - /** @summary Check if it is special object, which should be handled separately - * @desc It can be TStyle or list of colors or palette object - * @return {boolean} true if any */ - checkSpecial(obj) { - if (!obj) return false; + title_shift_x = Math.round(title_offest_k * this.titleOffset); - if (obj._typename === clTStyle) { - Object.assign(gStyle, obj); - return true; - } + title_shift_y = Math.round(this.titleCenter ? h / 2 : (xor_reverse ? h : 0)); - if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { - if (this.options?.CreatePalette) { - let arr = []; - for (let n = obj.arr.length - this.options.CreatePalette; n < obj.arr.length; ++n) { - const col = getRGBfromTColor(obj.arr[n]); - if (!col) { console.log('Fail to create color for palette'); arr = null; break; } - arr.push(col); - } - if (arr) this.custom_palette = new ColorPalette(arr); + this.drawText({ + align: this.title_align + ';middle', + rotate: (rotate < 0) ? 90 : 270, + text: this.fTitle, color: this.titleFont.color, draw_g: title_g + }); + } else { + title_offest_k *= 1.6; + + title_shift_x = Math.round(this.titleCenter ? w / 2 : (xor_reverse ? 0 : w)); + title_shift_y = Math.round(title_offest_k * this.titleOffset); + this.drawText({ + align: this.title_align + ';middle', + rotate: (rotate < 0) ? 180 : 0, + text: this.fTitle, color: this.titleFont.color, draw_g: title_g + }); } - if (!this.options || this.options.GlobalColors) // set global list of colors - adoptRootColors(obj); + this.addTitleDrag(title_g, this.vertical, title_offest_k, swap_side, this.vertical ? h : w); - // copy existing colors and extend with new values - this._custom_colors = this.options?.LocalColors ? extendRootColors(null, obj) : null; - return true; - } + return this.finishTextDrawing(title_g); + }).then(() => { + if (title_g) { + if (!this.titleOffset && this.vertical) + title_shift_x = Math.round(-side * ((labelsMaxWidth || labelSize) + 0.7 * this.offsetScaling * this.titleSize)); + makeTranslate(title_g, title_shift_x, title_shift_y); + title_g.property('shift_x', title_shift_x) + .property('shift_y', title_shift_y); + } - if ((obj._typename === clTObjArray) && (obj.name === 'CurrentColorPalette')) { - const arr = [], indx = []; - let missing = false; - for (let n = 0; n < obj.arr.length; ++n) { - const col = obj.arr[n]; - if (col?._typename === clTColor) { - indx[n] = col.fNumber; - arr[n] = getRGBfromTColor(col); - } else { - console.log(`Missing color with index ${n}`); - missing = true; - } - } + return this; + }); + } - const apply = (!this.options || (!missing && !this.options.IgnorePalette)); - this._custom_palette_indexes = apply ? indx : null; - this._custom_palette_colors = apply ? arr : null; +} // class TAxisPainter - return true; - } +const logminfactorX = 0.0001, logminfactorY = 3e-4; - return false; - } +/** @summary Configure tooltip enable flag for painter + * @private */ +function setPainterTooltipEnabled(painter, on) { + if (!painter) + return; - /** @summary Check if special objects appears in primitives - * @desc it could be list of colors or palette */ - checkSpecialsInPrimitives(can) { - const lst = can?.fPrimitives; - if (!lst) return; - for (let i = 0; i < lst.arr?.length; ++i) { - if (this.checkSpecial(lst.arr[i])) { - lst.arr.splice(i, 1); - lst.opt.splice(i, 1); - i--; - } - } + const fp = painter.getFramePainter(); + if (isFunc(fp?.setTooltipEnabled)) { + fp.setTooltipEnabled(on); + fp.processFrameTooltipEvent(null); } + // this is 3D control object + if (isFunc(painter.control?.setTooltipEnabled)) + painter.control.setTooltipEnabled(on); +} - /** @summary try to find object by name in list of pad primitives - * @desc used to find title drawing - * @private */ - findInPrimitives(objname, objtype) { - const match = obj => obj && (obj?.fName === objname) && (objtype ? (obj?._typename === objtype) : true), - snap = this._snap_primitives?.find(snap => match((snap.fKind === webSnapIds.kObject) ? snap.fSnapshot : null)); - if (snap) return snap.fSnapshot; +/** @summary Return pointers on touch event + * @private */ +function get_touch_pointers(event, node) { + return event.$touch_arr ?? pointers(event, node); +} - return this.pad?.fPrimitives?.arr.find(match); +/** @summary Returns coordinates transformation func + * @private */ +function getEarthProjectionFunc(id) { + switch (id) { + // Aitoff2xy + case 1: return (l, b) => { + const DegToRad = Math.PI / 180, + alpha2 = (l / 2) * DegToRad, + delta = b * DegToRad, + r2 = Math.sqrt(2), + f = 2 * r2 / Math.PI, + cdec = Math.cos(delta), + denom = Math.sqrt(1.0 + cdec * Math.cos(alpha2)); + return { + x: cdec * Math.sin(alpha2) * 2.0 * r2 / denom / f / DegToRad, + y: Math.sin(delta) * r2 / denom / f / DegToRad + }; + }; + // mercator + case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI / 2 + b / 180 * Math.PI) / 2)) }; }; + // sinusoidal + case 3: return (l, b) => { return { x: l * Math.cos(b / 180 * Math.PI), y: b }; }; + // parabolic + case 4: return (l, b) => { return { x: l * (2.0 * Math.cos(2 * b / 180 * Math.PI / 3) - 1), y: 180 * Math.sin(b / 180 * Math.PI / 3) }; }; + // Mollweide projection + case 5: return (l, b) => { + const theta0 = b * Math.PI / 180; + let theta = theta0, num, den; + for (let i = 0; i < 100; i++) { + num = 2 * theta + Math.sin(2 * theta) - Math.PI * Math.sin(theta0); + den = 4 * (Math.cos(theta) ** 2); + if (den < 1e-20) { + theta = theta0; + break; + } + theta -= num / den; + if (Math.abs(num / den) < 1e-4) + break; + } + return { + x: l * Math.cos(theta), + y: 90 * Math.sin(theta) + }; + }; } +} - /** @summary Try to find painter for specified object - * @desc can be used to find painter for some special objects, registered as - * histogram functions - * @param {object} selobj - object to which painter should be search, set null to ignore parameter - * @param {string} [selname] - object name, set to null to ignore - * @param {string} [seltype] - object type, set to null to ignore - * @return {object} - painter for specified object (if any) - * @private */ - findPainterFor(selobj, selname, seltype) { - return this.painters.find(p => { - const pobj = p.getObject(); - if (!pobj) return false; - - if (selobj && (pobj === selobj)) return true; - if (!selname && !seltype) return false; - if (selname && (pobj.fName !== selname)) return false; - if (seltype && (pobj._typename !== seltype)) return false; - return true; - }); - } +/** @summary Unzoom preselected range for main histogram painter + * @desc Used with TGraph where Y zooming selected with fMinimum/fMaximum but histogram + * axis range can be wider. Or for normal histogram drawing when preselected range smaller than histogram range + * @private */ +function unzoomHistogramYRange(main) { + if (!isFunc(main?.getDimension) || main.getDimension() !== 1) + return; - /** @summary Return true if any objects beside sub-pads exists in the pad */ - hasObjectsToDraw() { - return this.pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); - } + const ymin = main.draw_content ? main.hmin : main.ymin, + ymax = main.draw_content ? main.hmax : main.ymax; - /** @summary sync drawing/redrawing/resize of the pad - * @param {string} kind - kind of draw operation, if true - always queued - * @return {Promise} when pad is ready for draw operation or false if operation already queued - * @private */ - syncDraw(kind) { - const entry = { kind: kind || 'redraw' }; - if (this._doing_draw === undefined) { - this._doing_draw = [entry]; - return Promise.resolve(true); - } - // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this._doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) - return false; - this._doing_draw.push(entry); - return new Promise(resolveFunc => { - entry.func = resolveFunc; - }); - } + if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && + (ymin <= main.zoom_ymin) && (main.zoom_ymax <= ymax)) + main.zoom_ymin = main.zoom_ymax = 0; +} - /** @summary indicates if painter performing objects draw - * @private */ - doingDraw() { - return this._doing_draw !== undefined; - } +// global, allow single drag at once +let drag_rect = null, drag_kind = '', drag_painter = null; - /** @summary confirms that drawing is completed, may trigger next drawing immediately - * @private */ - confirmDraw() { - if (this._doing_draw === undefined) - return console.warn('failure, should not happen'); - this._doing_draw.shift(); - if (this._doing_draw.length === 0) - delete this._doing_draw; - else { - const entry = this._doing_draw[0]; - if (entry.func) { entry.func(); delete entry.func; } - } - } +/** @summary Check if dragging performed currently + * @private */ +function is_dragging(painter, kind) { + return drag_rect && (drag_painter === painter) && (drag_kind === kind); +} - /** @summary Draw single primitive */ - async drawObject(/* dom, obj, opt */) { - console.log('Not possible to draw object without loading of draw.mjs'); - return null; - } +/** @summary Add drag for interactive rectangular elements for painter + * @private */ +function addDragHandler(_painter, arg) { + if (!settings.MoveResize) + return; - /** @summary Draw pad primitives - * @return {Promise} when drawing completed - * @private */ - async drawPrimitives(indx) { - if (indx === undefined) { - if (this.iscan) - this._start_tm = new Date().getTime(); + const painter = _painter, pp = painter.getPadPainter(); + if (pp?.isFastDrawing() || pp?.isBatchMode()) + return; + // cleanup all drag elements when canvas is not editable + if (pp?.isEditable() === false) + arg.cleanup = true; - // set number of primitves - this._num_primitives = this.pad?.fPrimitives?.arr?.length || 0; + if (!isFunc(arg.getDrawG)) + arg.getDrawG = () => painter?.getG(); - // sync to prevent immediate pad redraw during normal drawing sequence - return this.syncDraw(true).then(() => this.drawPrimitives(0)); + function makeResizeElements(group, handler) { + function addElement(cursor, d) { + const clname = 'js_' + cursor.replace(/[-]/g, '_'); + let elem = group.selectChild('.' + clname); + if (arg.cleanup) + return elem.remove(); + if (elem.empty()) + elem = group.append('path').classed(clname, true); + elem.style('opacity', 0).style('cursor', cursor).attr('d', d); + if (handler) + elem.call(handler); } - if (!this.pad || (indx >= this._num_primitives)) { - if (this._start_tm) { - const spenttm = new Date().getTime() - this._start_tm; - if (spenttm > 1000) console.log(`Canvas ${this.pad?.fName || '---'} drawing took ${(spenttm*1e-3).toFixed(2)}s`); - delete this._start_tm; - } + addElement('nw-resize', 'M2,2h15v-5h-20v20h5Z'); + addElement('ne-resize', `M${arg.width - 2},2h-15v-5h20v20h-5 Z`); + addElement('sw-resize', `M2,${arg.height - 2}h15v5h-20v-20h5Z`); + addElement('se-resize', `M${arg.width - 2},${arg.height - 2}h-15v5h20v-20h-5Z`); - this.confirmDraw(); - return; + if (!arg.no_change_x) { + addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height - 2 * 18)}h-5Z`); + addElement('e-resize', `M${arg.width + 3},18h-5v${Math.max(0, arg.height - 2 * 18)}h5Z`); } + if (!arg.no_change_y) { + addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width - 2 * 18)}v-5Z`); + addElement('s-resize', `M18,${arg.height + 3}v-5h${Math.max(0, arg.width - 2 * 18)}v5Z`); + } + } - const obj = this.pad.fPrimitives.arr[indx]; - - if (!obj || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) - return this.drawPrimitives(indx+1); - - // use of Promise should avoid large call-stack depth when many primitives are drawn - return this.drawObject(this.getDom(), obj, this.pad.fPrimitives.opt[indx]).then(op => { - if (isObject(op)) - op._primitive = true; // mark painter as belonging to primitives + const complete_drag = (newx, newy, newwidth, newheight) => { + drag_painter = null; + drag_kind = ''; + if (drag_rect) { + drag_rect.remove(); + drag_rect = null; + } - return this.drawPrimitives(indx+1); - }); - } + const draw_g = arg.getDrawG(); - /** @summary Divide pad on subpads - * @return {Promise} when finished - * @private */ - async divide(nx, ny) { - if (!this.pad.Divide(nx, ny)) - return this; + if (!draw_g) + return false; - const drawNext = indx => { - if (indx >= this.pad.fPrimitives.arr.length) - return this; - return this.drawObject(this.getDom(), this.pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); - }; + const oldx = arg.x, oldy = arg.y; - return drawNext(0); - } + if (arg.minwidth && newwidth < arg.minwidth) + newwidth = arg.minwidth; + if (arg.minheight && newheight < arg.minheight) + newheight = arg.minheight; - /** @summary Return sub-pads painter, only direct childs are checked - * @private */ - getSubPadPainter(n) { - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if (sub.pad && isFunc(sub.forEachPainterInPad) && (sub.pad.fNumber === n)) return sub; - } - return null; - } + const change_size = (newwidth !== arg.width) || (newheight !== arg.height), + change_pos = (newx !== oldx) || (newy !== oldy); + arg.x = newx; + arg.y = newy; + arg.width = newwidth; + arg.height = newheight; - /** @summary Process tooltip event in the pad - * @private */ - processPadTooltipEvent(pnt) { - const painters = [], hints = []; + if (!arg.no_transform) + makeTranslate(draw_g, newx, newy); - // first count - how many processors are there - this.painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) - painters.push(obj); - }); + setPainterTooltipEnabled(painter, true); - if (pnt) pnt.nproc = painters.length; + makeResizeElements(draw_g); - painters.forEach(obj => { - const hint = obj.processTooltipEvent(pnt) || { user_info: null }; - hints.push(hint); - if (pnt?.painters) hint.painter = obj; - }); + if (change_size || change_pos) { + if (change_size && isFunc(arg.resize)) + arg.resize(newwidth, newheight); - return hints; - } + if (change_pos && isFunc(arg.move)) + arg.move(newx, newy, newx - oldx, newy - oldy); - /** @summary Changes canvas dark mode - * @private */ - changeDarkMode(mode) { - this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); - } + if (change_size || change_pos) { + if (arg.obj) { + const rect = arg.pad_rect ?? pp.getPadRect(); + arg.obj.fX1NDC = newx / rect.width; + arg.obj.fX2NDC = (newx + newwidth) / rect.width; + arg.obj.fY1NDC = 1 - (newy + newheight) / rect.height; + arg.obj.fY2NDC = 1 - newy / rect.height; + arg.obj.$modifiedNDC = true; // indicate that NDC was interactively changed, block in updated + } else if (isFunc(arg.move_resize)) + arg.move_resize(newx, newy, newwidth, newheight); - /** @summary Fill pad context menu - * @private */ - fillContextMenu(menu) { - if (this.pad) - menu.add(`header:${this.pad._typename}::${this.pad.fName}`); - else - menu.add('header:Canvas'); + if (isFunc(arg.redraw)) + arg.redraw(arg); + } + } - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + return change_size || change_pos; + }, drag_move = drag().subject(Object), drag_move_off = drag().subject(Object); - if (!this._websocket) { - function SetPadField(arg) { - this.pad[arg.slice(1)] = parseInt(arg[0]); - this.interactiveRedraw('pad', arg.slice(1)); - } - - menu.addchk(this.pad?.fGridx, 'Grid x', (this.pad?.fGridx ? '0' : '1') + 'fGridx', SetPadField); - menu.addchk(this.pad?.fGridy, 'Grid y', (this.pad?.fGridy ? '0' : '1') + 'fGridy', SetPadField); - menu.add('sub:Ticks x'); - menu.addchk(this.pad?.fTickx === 0, 'normal', '0fTickx', SetPadField); - menu.addchk(this.pad?.fTickx === 1, 'ticks on both sides', '1fTickx', SetPadField); - menu.addchk(this.pad?.fTickx === 2, 'labels on both sides', '2fTickx', SetPadField); - menu.add('endsub:'); - menu.add('sub:Ticks y'); - menu.addchk(this.pad?.fTicky === 0, 'normal', '0fTicky', SetPadField); - menu.addchk(this.pad?.fTicky === 1, 'ticks on both sides', '1fTicky', SetPadField); - menu.addchk(this.pad?.fTicky === 2, 'labels on both sides', '2fTicky', SetPadField); - menu.add('endsub:'); - menu.addchk(this.pad?.fEditable, 'Editable', flag => { this.pad.fEditable = flag; this.interactiveRedraw('pad'); }); - if (this.iscan) - menu.addchk(this.pad?.TestBit(kIsGrayscale), 'Gray scale', flag => { this.setGrayscale(flag); this.interactiveRedraw('pad'); }); + drag_move_off.on('start', null).on('drag', null).on('end', null); - if (isFunc(this.drawObject)) - menu.add('Build legend', () => this.buildLegend()); + drag_move + .on('start', evnt => { + if (detectRightButton(evnt.sourceEvent) || drag_kind) + return; + if (isFunc(arg.is_disabled) && arg.is_disabled('move')) + return; - menu.addAttributesMenu(this); - menu.add('Save to gStyle', () => { - if (!this.pad) return; - this.fillatt?.saveToStyle(this.iscan ? 'fCanvasColor' : 'fPadColor'); - gStyle.fPadGridX = this.pad.fGridx; - gStyle.fPadGridY = this.pad.fGridy; - gStyle.fPadTickX = this.pad.fTickx; - gStyle.fPadTickY = this.pad.fTicky; - gStyle.fOptLogx = this.pad.fLogx; - gStyle.fOptLogy = this.pad.fLogy; - gStyle.fOptLogz = this.pad.fLogz; - }, 'Store pad fill attributes, grid, tick and log scale settings to gStyle'); + closeMenu(); // close menu + setPainterTooltipEnabled(painter, false); // disable tooltip - if (this.iscan) { - menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') this.changeDarkMode(); - }); - } - } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - menu.add('separator'); + const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { + x: arg.x, y: arg.y, width: arg.width, height: arg.height, + acc_x1: arg.x, acc_y1: arg.y, + pad_w: pad_rect.width - arg.width, + pad_h: pad_rect.height - arg.height, + drag_tm: new Date(), + path: `v${arg.height}h${arg.width}v${-arg.height}z`, + evnt_x: evnt.x, evnt_y: evnt.y + }; - if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) - menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); + drag_painter = painter; + drag_kind = 'move'; + drag_rect = select(arg.getDrawG().node().parentNode).append('path') + .attr('d', `M${handle.acc_x1},${handle.acc_y1}${handle.path}`) + .style('cursor', 'move') + .style('pointer-events', 'none') // let forward double click to underlying elements + .property('drag_handle', handle) + .call(addHighlightStyle, true); + }).on('drag', evnt => { + if (!is_dragging(painter, 'move')) + return; - if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { - if (this.canStatusBar()) - menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); - } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - if (this.enlargeMain() || (this.has_canvas && this.hasObjectsToDraw())) - menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.iscan ? 'canvas' : 'pad'), () => this.enlargePad()); + const handle = drag_rect.property('drag_handle'); - const fname = this.this_pad_name || (this.iscan ? 'canvas' : 'pad'); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.iscan, `${fname}.${fmt}`))); - menu.add('endsub:'); + if (!arg.no_change_x) + handle.acc_x1 += evnt.dx; + if (!arg.no_change_y) + handle.acc_y1 += evnt.dy; - return true; - } + handle.x = Math.min(Math.max(handle.acc_x1, 0), handle.pad_w); + handle.y = Math.min(Math.max(handle.acc_y1, 0), handle.pad_h); - /** @summary Show pad context menu - * @private */ - async padContextMenu(evnt) { - if (evnt.stopPropagation) { - // this is normal event processing and not emulated jsroot event - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - this.getFramePainter()?.setLastEventPos(); - } + drag_rect.attr('d', `M${handle.x},${handle.y}${handle.path}`); + }).on('end', evnt => { + if (!is_dragging(painter, 'move')) + return; - return createMenu(evnt, this).then(menu => { - this.fillContextMenu(menu); - return this.fillObjectExecMenu(menu, ''); - }).then(menu => menu.show()); - } + evnt.sourceEvent.stopPropagation(); + evnt.sourceEvent.preventDefault(); - /** @summary Redraw pad means redraw ourself - * @return {Promise} when redrawing ready */ - async redrawPad(reason) { - const sync_promise = this.syncDraw(reason); - if (sync_promise === false) { - console.log(`Prevent redrawing of ${this.pad.fName}`); - return false; - } + const handle = drag_rect.property('drag_handle'); - let showsubitems = true; - const redrawNext = indx => { - while (indx < this.painters.length) { - const sub = this.painters[indx++]; - let res = 0; - if (showsubitems || sub.this_pad_name) - res = sub.redraw(reason); + if (complete_drag(handle.x, handle.y, arg.width, arg.height) === false) { + const spent = (new Date()).getTime() - handle.drag_tm.getTime(); - if (isPromise(res)) - return res.then(() => redrawNext(indx)); + if (arg.ctxmenu && (spent > 600)) + showPainterMenu({ clientX: handle.evnt_x, clientY: handle.evnt_y, skip_close: 1 }, painter); + else if (arg.canselect && (spent <= 600)) + painter.getPadPainter()?.selectObjectPainter(painter); } - return true; - }; - - return sync_promise.then(() => { - if (this.iscan) - this.createCanvasSvg(2); - else - showsubitems = this.createPadSvg(true); - return redrawNext(0); - }).then(() => { - this.addPadInteractive(); - this.confirmDraw(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - return true; }); - } - /** @summary redraw pad */ - redraw(reason) { - // intentially do not return Promise to let re-draw sub-pads in parallel - this.redrawPad(reason); - } + const drag_resize = drag().subject(Object); - /** @summary Checks if pad should be redrawn by resize - * @private */ - needRedrawByResize() { - const elem = this.svg_this_pad(); - if (!elem.empty() && elem.property('can3d') === constants$1.Embed3D.Overlay) return true; + drag_resize + .on('start', function(evnt) { + if (detectRightButton(evnt.sourceEvent) || drag_kind) + return; + if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) + return; - return this.painters.findIndex(objp => { - return isFunc(objp.needRedrawByResize) ? objp.needRedrawByResize() : false; - }) >= 0; - } + closeMenu(); // close menu + setPainterTooltipEnabled(painter, false); // disable tooltip - /** @summary Check resize of canvas - * @return {Promise} with result or false */ - checkCanvasResize(size, force) { - if (this._ignore_resize) - return false; + evnt.sourceEvent.stopPropagation(); + evnt.sourceEvent.preventDefault(); - if (!this.iscan && this.has_canvas) return false; + const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { + x: arg.x, y: arg.y, width: arg.width, height: arg.height, + acc_x1: arg.x, acc_y1: arg.y, + acc_x2: arg.x + arg.width, acc_y2: arg.y + arg.height, + pad_w: pad_rect.width, pad_h: pad_rect.height + }; - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) return false; + drag_painter = painter; + drag_kind = 'resize'; + drag_rect = select(arg.getDrawG().node().parentNode) + .append('rect') + .style('cursor', select(this).style('cursor')) + .attr('x', handle.acc_x1) + .attr('y', handle.acc_y1) + .attr('width', handle.acc_x2 - handle.acc_x1) + .attr('height', handle.acc_y2 - handle.acc_y1) + .property('drag_handle', handle) + .call(addHighlightStyle, true); + }).on('drag', function(evnt) { + if (!is_dragging(painter, 'resize')) + return; - if ((size === true) || (size === false)) { force = size; size = null; } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - if (isObject(size) && size.force) force = true; + const handle = drag_rect.property('drag_handle'), + elem = select(this), + dx = arg.no_change_x ? 0 : evnt.dx, + dy = arg.no_change_y ? 0 : evnt.dy; - if (!force) force = this.needRedrawByResize(); + if (elem.classed('js_nw_resize')) { + handle.acc_x1 += dx; + handle.acc_y1 += dy; + } else if (elem.classed('js_ne_resize')) { + handle.acc_x2 += dx; + handle.acc_y1 += dy; + } else if (elem.classed('js_sw_resize')) { + handle.acc_x1 += dx; + handle.acc_y2 += dy; + } else if (elem.classed('js_se_resize')) { + handle.acc_x2 += dx; + handle.acc_y2 += dy; + } else if (elem.classed('js_w_resize')) + handle.acc_x1 += dx; + else if (elem.classed('js_n_resize')) + handle.acc_y1 += dy; + else if (elem.classed('js_e_resize')) + handle.acc_x2 += dx; + else if (elem.classed('js_s_resize')) + handle.acc_y2 += dy; - let changed = false; - const redrawNext = indx => { - if (!changed || (indx >= this.painters.length)) { - this.confirmDraw(); - return changed; - } + const x1 = Math.max(0, handle.acc_x1), x2 = Math.min(handle.acc_x2, handle.pad_w), + y1 = Math.max(0, handle.acc_y1), y2 = Math.min(handle.acc_y2, handle.pad_h); - return getPromise(this.painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx+1)); - }; + handle.x = Math.min(x1, x2); + handle.y = Math.min(y1, y2); + handle.width = Math.abs(x2 - x1); + handle.height = Math.abs(y2 - y1); - // return sync_promise.then(() => this.ensureBrowserSize(this.pad?.fCw, this.pad?.fCh)).then(() => { + drag_rect.attr('x', handle.x).attr('y', handle.y).attr('width', handle.width).attr('height', handle.height); + }).on('end', evnt => { + if (!is_dragging(painter, 'resize')) + return; - return sync_promise.then(() => { - changed = this.createCanvasSvg(force ? 2 : 1, size); + evnt.sourceEvent.preventDefault(); - if (changed && this.iscan && this.pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this._resize_tmout) - clearTimeout(this._resize_tmout); - this._resize_tmout = setTimeout(() => { - delete this._resize_tmout; - if (isFunc(this.sendResized)) - this.sendResized(); - }, 1000); // long enough delay to prevent multiple occurence - } + const handle = drag_rect.property('drag_handle'); - // if canvas changed, redraw all its subitems. - // If redrawing was forced for canvas, same applied for sub-elements - return redrawNext(0); + complete_drag(handle.x, handle.y, handle.width, handle.height); }); - } - /** @summary Update TPad object */ - updateObject(obj) { - if (!obj) return false; - - this.pad.fBits = obj.fBits; - this.pad.fTitle = obj.fTitle; - - this.pad.fGridx = obj.fGridx; - this.pad.fGridy = obj.fGridy; - this.pad.fTickx = obj.fTickx; - this.pad.fTicky = obj.fTicky; - this.pad.fLogx = obj.fLogx; - this.pad.fLogy = obj.fLogy; - this.pad.fLogz = obj.fLogz; - - this.pad.fUxmin = obj.fUxmin; - this.pad.fUxmax = obj.fUxmax; - this.pad.fUymin = obj.fUymin; - this.pad.fUymax = obj.fUymax; - - this.pad.fX1 = obj.fX1; - this.pad.fX2 = obj.fX2; - this.pad.fY1 = obj.fY1; - this.pad.fY2 = obj.fY2; - - this.pad.fLeftMargin = obj.fLeftMargin; - this.pad.fRightMargin = obj.fRightMargin; - this.pad.fBottomMargin = obj.fBottomMargin; - this.pad.fTopMargin = obj.fTopMargin; - - this.pad.fFillColor = obj.fFillColor; - this.pad.fFillStyle = obj.fFillStyle; - this.pad.fLineColor = obj.fLineColor; - this.pad.fLineStyle = obj.fLineStyle; - this.pad.fLineWidth = obj.fLineWidth; - - this.pad.fPhi = obj.fPhi; - this.pad.fTheta = obj.fTheta; - this.pad.fEditable = obj.fEditable; - - if (this.iscan) - this.checkSpecialsInPrimitives(obj); + if (!arg.only_resize) + arg.getDrawG().style('cursor', arg.cleanup ? null : 'move').call(arg.cleanup ? drag_move_off : drag_move); - const fp = this.getFramePainter(); - if (fp) fp.updateAttributes(!fp.modified_NDC); + if (!arg.only_move) + makeResizeElements(arg.getDrawG(), drag_resize); +} - if (!obj.fPrimitives) return false; +/** @summary Tooltip handler class + * @private */ +class TooltipHandler extends ObjectPainter { - let isany = false, p = 0; - for (let n = 0; n < obj.fPrimitives.arr?.length; ++n) { - while (p < this.painters.length) { - const op = this.painters[p++]; - if (!op._primitive) continue; - if (op.updateObject(obj.fPrimitives.arr[n], obj.fPrimitives.opt[n])) - isany = true; - break; - } - } + // cannot use private members because of RFramePainter - return isany; + /** @desc only canvas info_layer can be used while other pads can overlay + * @return layer where frame tooltips are shown */ + hints_layer() { + return this.getCanvPainter()?.getLayerSvg('info_layer') ?? select(null); } - /** @summary add legend object to the pad and redraw it - * @private */ - async buildLegend(x1, y1, x2, y2, title, opt) { - const lp = this.findPainterFor(null, '', clTLegend); + /** @return true if tooltip is shown, use to prevent some other action */ + isTooltipShown() { + if (!this.tooltip_enabled || !this.isTooltipAllowed()) + return false; + const hintsg = this.hints_layer().selectChild('.objects_hints'); + return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadPainter()?.getPadName(); + } - if (!lp && !isFunc(this.drawObject)) - return Promise.reject(Error('Not possible to build legend while module draw.mjs was not load')); + /** @summary set tooltips enabled on/off */ + setTooltipEnabled(enabled) { + if (enabled !== undefined) + this.tooltip_enabled = enabled; + } - const leg = lp?.getObject() ?? create$1(clTLegend), - pad = this.getRootPad(true); + /** @summary central function which let show selected hints for the object */ + processFrameTooltipEvent(pnt, evnt) { + if (pnt?.handler) { + // special use of interactive handler in the frame painter + const rect = this.getG()?.selectChild('.main_layer'); + if (!rect || rect.empty()) + pnt = null; // disable + else if (pnt.touch && evnt) { + const pos = get_touch_pointers(evnt, rect.node()); + pnt = (pos && pos.length === 1) ? { touch: true, x: pos[0][0], y: pos[0][1] } : null; + } else if (evnt) { + const pos = pointer(evnt, rect.node()); + pnt = { touch: false, x: pos[0], y: pos[1] }; + } + } - leg.fPrimitives.Clear(); + let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false; + const hmargin = 3, wmargin = 3, hstep = 1.2, + frame_rect = this.getFrameRect(), + pp = this.getPadPainter(), + pad_width = pp?.getPadWidth(), + scale = pp?.getPadScale() ?? 1, + textheight = (pnt?.touch ? 15 : 11) * scale, + font = new FontHandler(160, textheight), + disable_tootlips = !this.isTooltipAllowed() || !this.tooltip_enabled; - for (let k = 0; k < this.painters.length; ++k) { - const painter = this.painters[k], - obj = painter.getObject(); - if (!obj || obj.fName === kTitle || obj.fName === 'stats' || painter.draw_content === false || - obj._typename === clTLegend || obj._typename === clTHStack || obj._typename === clTMultiGraph) - continue; + if (pnt) { + pnt.disabled = disable_tootlips; // indicate that highlighting is not required + pnt.painters = true; // get also painter + } - const entry = create$1(clTLegendEntry); - entry.fObject = obj; - entry.fLabel = painter.getItemName(); - if ((opt === 'all') || !entry.fLabel) - entry.fLabel = obj.fName; - entry.fOption = ''; - if (!entry.fLabel) continue; + // collect tooltips from pad painter - it has list of all drawn objects + const hints = pp?.processPadTooltipEvent(pnt) ?? []; - if (painter.lineatt?.used) - entry.fOption += 'l'; - if (painter.fillatt?.used) - entry.fOption += 'f'; - if (painter.markeratt?.used) - entry.fOption += 'p'; - if (!entry.fOption) - entry.fOption = 'l'; + if (pnt && frame_rect) + pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints ? hints[0]?.painter?.getSnapId() : ''); - leg.fPrimitives.Add(entry); - } + for (let n = 0; n < hints.length; ++n) { + const hint = hints[n]; + if (!hint) + continue; - if (lp) - return lp.redraw(); + if (hint.user_info !== undefined) + hint.painter?.provideUserTooltip(hint.user_info); - const szx = 0.4; - let szy = leg.fPrimitives.arr.length; - // no entries - no need to draw legend - if (!szy) return null; - if (szy > 8) szy = 8; - szy *= 0.1; + if (!hint.lines?.length) { + hints[n] = null; + continue; + } - if ((x1 === x2) || (y1 === y2)) { - leg.fX1NDC = szx * pad.fLeftMargin + (1 - szx) * (1 - pad.fRightMargin); - leg.fY1NDC = (1 - szy) * (1 - pad.fTopMargin) + szy * pad.fBottomMargin; - leg.fX2NDC = 0.99 - pad.fRightMargin; - leg.fY2NDC = 0.99 - pad.fTopMargin; - if (opt === undefined) opt = 'autoplace'; - } else { - leg.fX1NDC = x1; - leg.fY1NDC = y1; - leg.fX2NDC = x2; - leg.fY2NDC = y2; - } - leg.fFillStyle = 1001; - leg.fTitle = title ?? ''; + // check if fully duplicated hint already exists + for (let k = 0; k < n; ++k) { + const hprev = hints[k]; + let diff = false; + if (!hprev || (hprev.lines.length !== hint.lines.length)) + continue; + for (let l = 0; l < hint.lines.length && !diff; ++l) { + if (hprev.lines[l] !== hint.lines[l]) + diff = true; + } + if (!diff) { + hints[n] = null; + break; + } + } + if (!hints[n]) + continue; - const prev_name = this.has_canvas ? this.selectCurrentPad(this.this_pad_name) : undefined; + nhints++; - return this.drawObject(this.getDom(), leg, opt).then(p => { - this.selectCurrentPad(prev_name); - return p; - }); - } + if (hint.exact) + nexact++; - /** @summary Add object painter to list of primitives - * @private */ - addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && (objpainter.snapid === undefined)) { - // keep snap id in painter, will be used for the - if (this.painters.indexOf(objpainter) < 0) - this.painters.push(objpainter); + hint.lines.forEach(line => { maxlen = Math.max(maxlen, line.length); }); - objpainter.snapid = lst[indx].fObjectID; - const setSubSnaps = p => { - if (!p._unique_painter_id) return; - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if ((sub._main_painter_id === p._unique_painter_id) && sub._secondary_id) { - sub.snapid = p.snapid + '#' + sub._secondary_id; - setSubSnaps(sub); - } - } - }; + hint.height = Math.round(hint.lines.length * textheight * hstep + 2 * hmargin - textheight * (hstep - 1)); - setSubSnaps(objpainter); + if ((hint.color1 !== undefined) && (hint.color1 !== 'none')) { + if (lastcolor1 && (lastcolor1 !== hint.color1)) + usecolor1 = true; + lastcolor1 = hint.color1; + } } - } - /** @summary Process snap with style - * @private */ - processSnapStyle(snap) { - Object.assign(gStyle, snap.fSnapshot); - } + let path_name = null, same_path = hints.length > 1; + for (let n = 0; n < hints.length; ++n) { + const hint = hints[n], p = hint?.lines ? hint.lines[0]?.lastIndexOf('/') : -1; + if (p > 0) { + const path = hint.lines[0].slice(0, p + 1); + if (path_name === null) + path_name = path; + else if (path_name !== path) + same_path = false; + } else + same_path = false; + } - /** @summary Process snap with colors - * @private */ - processSnapColors(snap) { - const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper); + const layer = this.hints_layer(), + show_only_best = nhints > 15, + coordinates = pnt ? Math.round(pnt.x) + ',' + Math.round(pnt.y) : ''; + let hintsg = layer.selectChild('.objects_hints'), // group with all tooltips + title = '', name = '', info = '', + hint0 = null, best_dist2 = 1e10, best_hint = null; - // set global list of colors - if (!this.options || this.options.GlobalColors) - adoptRootColors(ListOfColors); + // try to select hint with exact match of the position when several hints available + for (let k = 0; k < hints.length; ++k) { + if (!hints[k]) + continue; + if (!hint0) + hint0 = hints[k]; - const greyscale = this.pad?.TestBit(kIsGrayscale) ?? false, - colors = extendRootColors(null, ListOfColors, greyscale); + // select exact hint if this is the only one + if (hints[k].exact && (nexact < 2) && (!hint0 || !hint0.exact)) { + hint0 = hints[k]; + break; + } - // copy existing colors and extend with new values - this._custom_colors = this.options?.LocalColors ? colors : null; + if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) + continue; - // set palette - if (snap.fSnapshot.fBuf && (!this.options || !this.options.IgnorePalette)) { - const indexes = [], palette = []; - for (let n = 0; n < snap.fSnapshot.fBuf.length; ++n) { - indexes[n] = Math.round(snap.fSnapshot.fBuf[n]); - palette[n] = colors[indexes[n]]; + const dist2 = (pnt.x - hints[k].x) ** 2 + (pnt.y - hints[k].y) ** 2; + if (dist2 < best_dist2) { + best_dist2 = dist2; + best_hint = hints[k]; } - this._custom_palette_indexes = indexes; - this._custom_palette_colors = palette; - this.custom_palette = new ColorPalette(palette, greyscale); - } else { - delete this._custom_palette_indexes; - delete this._custom_palette_colors; - delete this.custom_palette; } - } - - /** @summary Process snap with custom font - * @private */ - processSnapFont(snap) { - const arr = snap.fSnapshot.fOper.split(':'); - addCustomFont(Number.parseInt(arr[0]), arr[1], arr[2], arr[3]); - } - - /** @summary Process special snaps like colors or style objects - * @return {Promise} index where processing should start - * @private */ - processSpecialSnaps(lst) { - while (lst?.length) { - const snap = lst[0]; - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - lst.shift(); - this.processSnapStyle(snap); - } else if (snap.fKind === webSnapIds.kColors) { - lst.shift(); - this.processSnapColors(snap); - } else if (snap.fKind === webSnapIds.kFont) { - lst.shift(); - this.processSnapFont(snap); - } else - break; - } - } + if ((!hint0 || !hint0.exact) && (best_dist2 < 400)) + hint0 = best_hint; - /** @summary Function called when drawing next snapshot from the list - * @return {Promise} for drawing of the snap - * @private */ - async drawNextSnap(lst, indx) { - if (indx === undefined) { - indx = -1; - this._snaps_map = {}; // to control how much snaps are drawn - this._num_primitives = lst ? lst.length : 0; + if (hint0) { + name = (hint0.lines && hint0.lines.length > 1) ? hint0.lines[0] : hint0.name; + title = hint0.title || ''; + info = hint0.line; + if (!info && hint0.lines) + info = hint0.lines.slice(1).join(' '); } - ++indx; // change to the next snap + this.showObjectStatus(name, title, info, coordinates); - if (!lst || (indx >= lst.length)) { - delete this._snaps_map; - return this; + // end of closing tooltips + if (!pnt || disable_tootlips || !hints.length || (maxlen === 0) || (show_only_best && !best_hint)) { + hintsg.remove(); + return; } - const snap = lst[indx]; + // we need to set pointer-events=none for all elements while hints + // placed in front of so-called interactive rect in frame, used to catch mouse events - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - this.processSnapStyle(snap); - return this.drawNextSnap(lst, indx); // call next + if (hintsg.empty()) { + hintsg = layer.append('svg:g') + .attr('class', 'objects_hints') + .style('pointer-events', 'none'); } - // list of colors - if (snap.fKind === webSnapIds.kColors) { - this.processSnapColors(snap); - return this.drawNextSnap(lst, indx); // call next + let frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || ''; + if (!pp?.isCanvas()) { + frame_shift = getAbsPosInCanvas(pp.getPadSvg(), frame_shift); + trans = `translate(${frame_shift.x},${frame_shift.y}) ${trans}`; } - const snapid = snap.fObjectID; - let cnt = (this._snaps_map[snapid] || 0) + 1, - objpainter = null; + // copy transform attributes from frame itself + hintsg.attr('transform', trans) + .property('last_point', pnt) + .property('hints_pad', pp.getPadName()); - this._snaps_map[snapid] = cnt; // check how many objects with same snapid drawn, use them again + let viewmode = hintsg.property('viewmode') || '', + actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; - // first appropriate painter for the object - // if same object drawn twice, two painters will exists - for (let k = 0; k < this.painters.length; ++k) { - const subp = this.painters[k]; - if (subp.snapid === snapid) - if (--cnt === 0) { objpainter = subp; break; } - } + if (show_only_best || (nhints === 1)) { + viewmode = 'single'; + posx += 15; + } else { + // if there are many hints, place them left or right - if (objpainter) { - if (snap.fKind === webSnapIds.kSubPad) // subpad - return objpainter.redrawPadSnap(snap).then(() => this.drawNextSnap(lst, indx)); + let bleft = 0.5, bright = 0.5; - let promise; + if (viewmode === 'left') + bright = 0.7; + else if (viewmode === 'right') + bleft = 0.3; - if (snap.fKind === webSnapIds.kObject) { // object itself - if (objpainter.updateObject(snap.fSnapshot, snap.fOption, true)) - promise = objpainter.redraw(); - } else if (snap.fKind === webSnapIds.kSVG) { // update SVG - if (objpainter.updateObject(snap.fSnapshot)) - promise = objpainter.redraw(); - } + if (posx <= bleft * frame_rect.width) { + viewmode = 'left'; + posx = 20; + } else if (posx >= bright * frame_rect.width) { + viewmode = 'right'; + posx = frame_rect.width - 60; + } else + posx = hintsg.property('startx'); + } - return getPromise(promise).then(() => this.drawNextSnap(lst, indx)); // call next + if (viewmode !== hintsg.property('viewmode')) { + hintsg.property('viewmode', viewmode); + hintsg.selectAll('*').remove(); } - if (snap.fKind === webSnapIds.kSubPad) { // subpad - const subpad = snap.fSnapshot; + let curry = 10, // normal y coordinate + gapy = 10, // y coordinate, taking into account all gaps + gapminx = -1111, gapmaxx = -1111; + const minhinty = -frame_shift.y, + cp = this.getCanvPainter(), + maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y; - subpad.fPrimitives = null; // clear primitives, they just because of I/O + for (let n = 0; n < hints.length; ++n) { + let hint = hints[n], + group = hintsg.selectChild(`.painter_hint_${n}`); - const padpainter = new TPadPainter(this.getDom(), subpad, false); - padpainter.decodeOptions(snap.fOption); - padpainter.addToPadPrimitives(this.this_pad_name); - padpainter.snapid = snap.fObjectID; - padpainter.is_active_pad = !!snap.fActive; // enforce boolean flag - padpainter._readonly = snap.fReadOnly ?? false; // readonly flag - padpainter._snap_primitives = snap.fPrimitives; // keep list to be able find primitive - padpainter._has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features + if (show_only_best && (hint !== best_hint)) + hint = null; - if (subpad.$disable_drawing) - padpainter.pad_draw_disabled = true; + if (hint === null) { + group.remove(); + continue; + } - padpainter.processSpecialSnaps(snap.fPrimitives); // need to process style and colors before creating graph elements + const was_empty = group.empty(); - padpainter.createPadSvg(); + if (was_empty) { + group = hintsg.append('svg:svg') + .attr('class', `painter_hint_${n}`) + .attr('opacity', 0) // use attribute, not style to make animation with d3.transition() + .style('overflow', 'hidden') + .style('pointer-events', 'none'); + } - if (padpainter.matchObjectType(clTPad) && (snap.fPrimitives.length > 0)) - padpainter.addPadButtons(true); + if (viewmode === 'single') + curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y; + else { + for (let n2 = 0; (n2 < hints.length) && (gapy < maxhinty); ++n2) { + const hint2 = hints[n2]; + if (!hint2) + continue; + if ((hint2.y >= gapy - 5) && (hint2.y <= gapy + hint2.height + 5)) { + gapy = hint2.y + 10; + n2 = -1; + } + } + if ((gapminx === -1111) && (gapmaxx === -1111)) + gapminx = gapmaxx = hint.x; + gapminx = Math.min(gapminx, hint.x); + gapmaxx = Math.min(gapmaxx, hint.x); + } - // we select current pad, where all drawing is performed - const prev_name = padpainter.selectCurrentPad(padpainter.this_pad_name); - return padpainter.drawNextSnap(snap.fPrimitives).then(() => { - padpainter.addPadInteractive(); - padpainter.selectCurrentPad(prev_name); - return this.drawNextSnap(lst, indx); // call next - }); - } + group.attr('x', posx) + .attr('y', curry) + .property('curry', curry) + .property('gapy', gapy); - // here the case of normal drawing, will be handled in promise - if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { - return this.drawObject(this.getDom(), snap.fSnapshot, snap.fOption).then(objpainter => { - this.addObjectPainter(objpainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); - } + curry += hint.height + 5; + gapy += hint.height + 5; - return this.drawNextSnap(lst, indx); - } + if (!was_empty) + group.selectAll('*').remove(); - /** @summary Return painter with specified id - * @private */ - findSnap(snapid) { - if (this.snapid === snapid) - return this; + group.attr('width', 60) + .attr('height', hint.height); - if (!this.painters) - return null; + const r = group.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', 60) + .attr('height', hint.height) + .style('fill', 'lightgrey') + .style('pointer-events', 'none'); - for (let k = 0; k < this.painters.length; ++k) { - let sub = this.painters[k]; + if (nhints > 1) { + const col = usecolor1 ? hint.color1 : hint.color2; + if (col && (col !== 'none')) + r.style('stroke', col); + } + r.attr('stroke-width', hint.exact ? 3 : 1); - if (isFunc(sub.findSnap)) - sub = sub.findSnap(snapid); - else if (sub.snapid !== snapid) - sub = null; + for (let l = 0; l < (hint.lines?.length ?? 0); l++) { + let line = hint.lines[l]; + if (l === 0 && path_name && same_path) + line = line.slice(path_name.length); + if (line) { + const txt = group.append('svg:text') + .attr('text-anchor', 'start') + .attr('x', wmargin) + .attr('y', hmargin + l * textheight * hstep) + .attr('dy', '.8em') + .style('fill', 'black') + .style('pointer-events', 'none') + .call(font.func) + .text(line), + box = getElementRect(txt, 'bbox'); + + actualw = Math.max(actualw, box.width); + } + } + + function translateFn() { + // We only use 'd', but list d,i,a as params just to show can have them as params. + // Code only really uses d and t. + return function(/* d, i, a */) { + return function(t) { + return t < 0.8 ? '0' : (t - 0.8) * 5; + }; + }; + } - if (sub) return sub; + if (was_empty) { + if (settings.TooltipAnimation > 0) + group.transition().duration(settings.TooltipAnimation).attrTween('opacity', translateFn()); + else + group.attr('opacity', 1); + } } - return null; - } + actualw += 2 * wmargin; - /** @summary Redraw pad snap - * @desc Online version of drawing pad primitives - * for the canvas snapshot contains list of objects - * as first entry, graphical properties of canvas itself is provided - * in ROOT6 it also includes primitives, but we ignore them - * @return {Promise} with pad painter when drawing completed - * @private */ - async redrawPadSnap(snap) { - if (!snap?.fPrimitives) - return this; + const svgs = hintsg.selectAll('svg'); - this.is_active_pad = !!snap.fActive; // enforce boolean flag - this._readonly = snap.fReadOnly ?? false; // readonly flag - this._snap_primitives = snap.fPrimitives; // keep list to be able find primitive - this._has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features + if ((viewmode === 'right') && (posx + actualw > frame_rect.width - 20)) { + posx = frame_rect.width - actualw - 20; + svgs.attr('x', posx); + } - const first = snap.fSnapshot; - first.fPrimitives = null; // primitives are not interesting, they are disabled in IO + if ((viewmode === 'single') && (posx + actualw > pad_width - frame_rect.x) && (posx > actualw + 20)) { + posx -= (actualw + 20); + svgs.attr('x', posx); + } - // if there are execs in the pad, deliver events to the server - this._deliver_webcanvas_events = first.fExecs?.arr?.length > 0; + // if gap not very big, apply gapy coordinate to open view on the histogram + if ((viewmode !== 'single') && (gapy < maxhinty) && (gapy !== curry)) { + if ((gapminx <= posx + actualw + 5) && (gapmaxx >= posx - 5)) + svgs.attr('y', function() { return select(this).property('gapy'); }); + } else if ((viewmode !== 'single') && (curry > maxhinty)) { + const shift = Math.max((maxhinty - curry - 10), minhinty); + if (shift < 0) + svgs.attr('y', function() { return select(this).property('curry') + shift; }); + } - if (this.snapid === undefined) { - // first time getting snap, create all gui elements first + if (actualw > 10) + svgs.attr('width', actualw).select('rect').attr('width', actualw); - this.snapid = snap.fObjectID; + hintsg.property('startx', posx); - this.draw_object = this.pad = first; // first object is pad + if (cp._highlight_connect && isFunc(cp.processHighlightConnect)) + cp.processHighlightConnect(hints); + } - // this._fixed_size = true; +} // class TooltipHandler - // if canvas size not specified in batch mode, temporary use 900x700 size - if (this.isBatchMode() && (!first.fCw || !first.fCh)) { first.fCw = 900; first.fCh = 700; } - // case of ROOT7 with always dummy TPad as first entry - if (!first.fCw || !first.fCh) this._fixed_size = false; +/** @summary Frame interactivity class + * @private */ +class FrameInteractive extends TooltipHandler { - const mainid = this.selectDom().attr('id'); + // cannot use private members because of RFramePainter - if (!this.isBatchMode() && !this.use_openui && !this.brlayout && mainid && isStr(mainid)) { - this.brlayout = new BrowserLayout(mainid, null, this); - this.brlayout.create(mainid, true); - // this.brlayout.toggleBrowserKind('float'); - this.setDom(this.brlayout.drawing_divid()); // need to create canvas - registerForResize(this.brlayout); - } + /** @summary Adding basic interactivity */ + addBasicInteractivity() { + this.setTooltipEnabled(true); - this.processSpecialSnaps(snap.fPrimitives); + if (this.$can_drag) { + addDragHandler(this, { + obj: this, x: this.getFrameX(), y: this.getFrameY(), width: this.getFrameWidth(), height: this.getFrameHeight(), + is_disabled: kind => { return (kind === 'move') && this.mode3d; }, + only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() + }); + } - this.createCanvasSvg(0); + const top_rect = this.getG().selectChild('path'), + main_svg = this.getG().selectChild('.main_layer'); - if (!this.isBatchMode()) - this.addPadButtons(true); + top_rect.style('pointer-events', 'visibleFill') // let process mouse events inside frame + .style('cursor', 'default'); // show normal cursor - if (typeof snap.fHighlightConnect !== 'undefined') - this._highlight_connect = snap.fHighlightConnect; + main_svg.style('pointer-events', 'visibleFill') + .style('cursor', 'default') + .property('handlers_set', 0); - let pr = Promise.resolve(true); + const handlers_set = this.getPadPainter()?.isFastDrawing() ? 0 : 1; - if (isStr(snap.fScripts) && snap.fScripts) { - let src = ''; + if (main_svg.property('handlers_set') !== handlers_set) { + const close_handler = handlers_set ? evnt => this.processFrameTooltipEvent(null, evnt) : null, + mouse_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: false }, evnt) : null; - if (snap.fScripts.indexOf('load:') === 0) - src = snap.fScripts.slice(5).split(';'); - else if (snap.fScripts.indexOf('assert:') === 0) - src = snap.fScripts.slice(7); + main_svg.property('handlers_set', handlers_set) + .on('mouseenter', mouse_handler) + .on('mousemove', mouse_handler) + .on('mouseleave', close_handler); - pr = src ? loadScript(src) : injectCode(snap.fScripts); - } + if (browser.touches) { + const touch_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: true }, evnt) : null; - return pr.then(() => this.drawNextSnap(snap.fPrimitives)); + main_svg.on('touchstart', touch_handler) + .on('touchmove', touch_handler) + .on('touchend', close_handler) + .on('touchcancel', close_handler); + } } - this.updateObject(first); // update only object attributes + main_svg.attr('x', 0) + .attr('y', 0) + .attr('width', this.getFrameWidth()) + .attr('height', this.getFrameHeight()); - // apply all changes in the object (pad or canvas) - if (this.iscan) - this.createCanvasSvg(2); - else - this.createPadSvg(true); + const hintsg = this.hints_layer().selectChild('.objects_hints'); + // if tooltips were visible before, try to reconstruct them after short timeout + if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadPainter()?.getPadName())) + setTimeout(() => this.processFrameTooltipEvent(hintsg.property('last_point'), null), 10); + } - const matchPrimitive = (painters, primitives, class_name, obj_name) => { - const painter = painters.find(p => { - if (p.snapid === undefined) return false; - if (!p.matchObjectType(class_name)) return false; - if (obj_name && (!p.getObject() || (p.getObject().fName !== obj_name))) return false; - return true; - }); - if (!painter) return; - const primitive = primitives.find(pr => { - if ((pr.fKind !== 1) || !pr.fSnapshot || (pr.fSnapshot._typename !== class_name)) return false; - if (obj_name && (pr.fSnapshot.fName !== obj_name)) return false; - return true; - }); - if (!primitive) return; + getFrameSvg() { return this.getPadPainter().getFrameSvg(); } - // force painter to use new object id - if (painter.snapid !== primitive.fObjectID) - painter.snapid = primitive.fObjectID; - }; + /** @summary Add interactive handlers */ + async addFrameInteractivity(for_second_axes) { + const pp = this.getPadPainter(), + svg = this.getFrameSvg(); + if (pp?.isFastDrawing() || svg.empty()) + return this; - // check if frame or title was recreated, we could reassign handlers for them directly - // while this is temporary objects, which can be recreated very often, try to catch such situation ourselfs - if (!snap.fWithoutPrimitives) { - matchPrimitive(this.painters, snap.fPrimitives, clTFrame); - matchPrimitive(this.painters, snap.fPrimitives, clTPaveText, kTitle); + if (for_second_axes) { + // add extra handlers for second axes + const svg_x2 = svg.selectAll('.x2axis_container'), + svg_y2 = svg.selectAll('.y2axis_container'); + if (settings.ContextMenu) { + svg_x2.on('contextmenu', evnt => this.showContextMenu('x2', evnt)); + svg_y2.on('contextmenu', evnt => this.showContextMenu('y2', evnt)); + } + svg_x2.on('mousemove', evnt => this.showAxisStatus('x2', evnt)); + svg_y2.on('mousemove', evnt => this.showAxisStatus('y2', evnt)); + return this; } - let isanyfound = false, isanyremove = false; + const svg_x = svg.selectAll('.xaxis_container'), + svg_y = svg.selectAll('.yaxis_container'); - // find and remove painters which no longer exists in the list - if (!snap.fWithoutPrimitives) { - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; + this.can_zoom_x = this.can_zoom_y = settings.Zooming; - // skip secondary painters or painters without snapid - if (!isStr(sub.snapid) || sub.isSecondary()) continue; // look only for painters with snapid + if (pp?.options) { + if (pp.options.NoZoomX) + this.can_zoom_x = false; + if (pp.options.NoZoomY) + this.can_zoom_y = false; + } - const prim = snap.fPrimitives.find(prim => (prim.fObjectID === sub.snapid && !prim.$checked)); - if (prim) { - isanyfound = true; - prim.$checked = true; - } else { - // remove painter which does not found in the list of snaps - k = this.removePrimitive(k); // index modified - isanyremove = true; - if (k === -111) { - // main painter is removed - do full cleanup and redraw - isanyfound = false; - break; - } - } + if (!svg.property('interactive_set')) { + this.addKeysHandler(); + + this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches) + this.zoom_rect = null; + this.zoom_origin = null; // original point where zooming started + this.zoom_curr = null; // current point for zooming + } + + if (settings.Zooming) { + if (settings.ZoomMouse) { + svg.on('mousedown', evnt => this.startRectSel(evnt)); + svg.on('dblclick', evnt => this.mouseDoubleClick(evnt)); } + if (settings.ZoomWheel) + svg.on('wheel', evnt => this.mouseWheel(evnt)); } - if (isanyremove) - delete this.pads_cache; + if (browser.touches && ((settings.Zooming && settings.ZoomTouch) || settings.ContextMenu)) + svg.on('touchstart', evnt => this.startTouchZoom(evnt)); - if (!isanyfound && !snap.fWithoutPrimitives) { - // TODO: maybe just remove frame painter? - const fp = this.getFramePainter(), - old_painters = this.painters; - this.painters = []; - old_painters.forEach(objp => { - if (fp !== objp) objp.cleanup(); - }); - delete this.main_painter_ref; - if (fp) { - this.painters.push(fp); - fp.cleanFrameDrawings(); - fp.redraw(); + if (settings.ContextMenu) { + if (browser.touches) { + svg_x.on('touchstart', evnt => this.startSingleTouchHandling('x', evnt)); + svg_y.on('touchstart', evnt => this.startSingleTouchHandling('y', evnt)); } - if (isFunc(this.removePadButtons)) this.removePadButtons(); - this.addPadButtons(true); + svg.on('contextmenu', evnt => this.showContextMenu('', evnt)); + svg_x.on('contextmenu', evnt => this.showContextMenu('x', evnt)); + svg_y.on('contextmenu', evnt => this.showContextMenu('y', evnt)); } - const prev_name = this.selectCurrentPad(this.this_pad_name); + svg_x.on('mousemove', evnt => this.showAxisStatus('x', evnt)); + svg_y.on('mousemove', evnt => this.showAxisStatus('y', evnt)); - return this.drawNextSnap(snap.fPrimitives).then(() => { - this.addPadInteractive(); - this.selectCurrentPad(prev_name); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - return this; - }); - } + svg.property('interactive_set', true); - /** @summary Deliver mouse move or click event to the web canvas - * @private */ - deliverWebCanvasEvent(kind, x, y, hints) { - if (!this._deliver_webcanvas_events || !this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) return; - const cp = this.getCanvPainter(); - if (!cp || !cp._websocket || !cp._websocket.canSend(2) || cp._readonly) return; + return this; + } - let selobj_snapid = ''; - if (hints && hints[0] && hints[0].painter?.snapid) - selobj_snapid = hints[0].painter.snapid.toString(); + /** @summary Handle key press */ + processKeyPress(evnt) { + // no custom keys handling when menu is present + if (hasMenu()) + return true; - const msg = JSON.stringify([this.snapid, kind, x.toString(), y.toString(), selobj_snapid]); + const allowed = ['PageUp', 'PageDown', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'PrintScreen', 'Escape', '*'], + main = this.selectDom(), + pp = this.getPadPainter(); + let key = evnt.key; - cp.sendWebsocket(`EVENT:${msg}`); - } + if (!settings.HandleKeys || main.empty() || (this.isEnabledKeys() === false) || + (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) + return false; - /** @summary Create image for the pad - * @desc Used with web-based canvas to create images for server side - * @return {Promise} with image data, coded with btoa() function - * @private */ - async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'pdf')) { - return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg')) return res; - const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ+7) : ''; - }); - } + if (evnt.shiftKey) + key = `Shift ${key}`; + if (evnt.altKey) + key = `Alt ${key}`; + if (evnt.ctrlKey) + key = `Ctrl ${key}`; - return ''; - } + const zoom = { name: 'x', dleft: 0, dright: 0 }; - /** @summary Collects pad information for TWebCanvas - * @desc need to update different states - * @private */ - getWebPadOptions(arg, cp) { - let is_top = (arg === undefined), elem = null, scan_subpads = true; - // no any options need to be collected in readonly mode - if (is_top && this._readonly) - return ''; - if (arg === 'only_this') { - is_top = true; - scan_subpads = false; - } else if (arg === 'with_subpads') { - is_top = true; - scan_subpads = true; - } - if (is_top) arg = []; - if (!cp) cp = this.iscan ? this : this.getCanvPainter(); - - if (this.snapid) { - elem = { _typename: 'TWebPadOptions', snapid: this.snapid.toString(), - active: !!this.is_active_pad, - cw: 0, ch: 0, w: [], - bits: 0, primitives: [], - logx: this.pad.fLogx, logy: this.pad.fLogy, logz: this.pad.fLogz, - gridx: this.pad.fGridx, gridy: this.pad.fGridy, - tickx: this.pad.fTickx, ticky: this.pad.fTicky, - mleft: this.pad.fLeftMargin, mright: this.pad.fRightMargin, - mtop: this.pad.fTopMargin, mbottom: this.pad.fBottomMargin, - xlow: 0, ylow: 0, xup: 1, yup: 1, - zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0 }; - - if (this.iscan) { - elem.bits = this.getStatusBits(); - elem.cw = this.getPadWidth(); - elem.ch = this.getPadHeight(); - elem.w = [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]; - } else if (cp) { - const cw = cp.getPadWidth(), ch = cp.getPadHeight(), rect = this.getPadRect(); - elem.cw = cw; - elem.ch = ch; - elem.xlow = rect.x / cw; - elem.ylow = 1 - (rect.y + rect.height) / ch; - elem.xup = elem.xlow + rect.width / cw; - elem.yup = elem.ylow + rect.height / ch; - } - - if (this.getPadRanges(elem)) - arg.push(elem); - else - console.log(`fail to get ranges for pad ${this.pad.fName}`); + switch (key) { + case 'ArrowLeft': + zoom.dleft = -1; + zoom.dright = 1; + break; + case 'ArrowRight': + zoom.dleft = 1; + zoom.dright = -1; + break; + case 'Ctrl ArrowLeft': + zoom.dleft = zoom.dright = -1; + break; + case 'Ctrl ArrowRight': + zoom.dleft = zoom.dright = 1; + break; + case 'ArrowUp': + zoom.name = 'y'; + zoom.dleft = 1; + zoom.dright = -1; + break; + case 'ArrowDown': + zoom.name = 'y'; + zoom.dleft = -1; + zoom.dright = 1; + break; + case 'Ctrl ArrowUp': + zoom.name = 'y'; + zoom.dleft = zoom.dright = 1; + break; + case 'Ctrl ArrowDown': + zoom.name = 'y'; + zoom.dleft = zoom.dright = -1; + break; + case 'Escape': + pp?.enlargePad(null, false, true); + return true; } - this.painters.forEach(sub => { - if (isFunc(sub.getWebPadOptions)) { - if (scan_subpads) sub.getWebPadOptions(arg, cp); - } else { - const opt = createWebObjectOptions(sub); - if (opt) - elem.primitives.push(opt); + if (zoom.dleft || zoom.dright) { + if (!settings.Zooming) + return false; + // in 3d mode with orbit control ignore simple arrows + if (this.mode3d && key.indexOf('Ctrl')) + return false; + this.analyzeMouseWheelEvent(null, zoom, 0.5); + if (zoom.changed) + this.zoomSingle(zoom.name, zoom.min, zoom.max, true); + evnt.stopPropagation(); + evnt.preventDefault(); + } else { + const func = pp?.findPadButton(key); + if (func) { + pp.clickPadButton(func); + evnt.stopPropagation(); + evnt.preventDefault(); } - }); + } - if (is_top) return toJSON(arg); + return true; // just process any key press } - /** @summary returns actual ranges in the pad, which can be applied to the server - * @private */ - getPadRanges(r) { - if (!r) return false; - - const main = this.getFramePainter(), - p = this.svg_this_pad(); - - r.ranges = main?.ranges_set ?? false; // indicate that ranges are assigned - - r.ux1 = r.px1 = r.ranges ? main.scale_xmin : 0; // need to initialize for JSON reader - r.uy1 = r.py1 = r.ranges ? main.scale_ymin : 0; - r.ux2 = r.px2 = r.ranges ? main.scale_xmax : 0; - r.uy2 = r.py2 = r.ranges ? main.scale_ymax : 0; - r.uz1 = r.ranges ? (main.scale_zmin ?? 0) : 0; - r.uz2 = r.ranges ? (main.scale_zmax ?? 0) : 0; - - if (main) { - if (main.zoom_xmin !== main.zoom_xmax) { - r.zx1 = main.zoom_xmin; r.zx2 = main.zoom_xmax; - } + /** @summary Function called when frame is clicked and object selection can be performed + * @desc such event can be used to select */ + processFrameClick(pnt, dblckick) { + const pp = this.getPadPainter(); + if (!pp) + return; - if (main.zoom_ymin !== main.zoom_ymax) { - r.zy1 = main.zoom_ymin; r.zy2 = main.zoom_ymax; - } + pnt.painters = true; // provide painters reference in the hints + pnt.disabled = true; // do not invoke graphics - if (main.zoom_zmin !== main.zoom_zmax) { - r.zz1 = main.zoom_zmin; r.zz2 = main.zoom_zmax; - } + // collect tooltips from pad painter - it has list of all drawn objects + const hints = pp.processPadTooltipEvent(pnt); + let exact = null, res; + for (let k = 0; (k < hints.length) && !exact; ++k) { + if (hints[k] && hints[k].exact) + exact = hints[k]; } - if (!r.ranges || p.empty()) return true; + if (exact) { + const handler = dblckick ? this.getDblclickHandler() : this.getClickHandler(); + if (isFunc(handler)) + res = handler(exact.user_info, pnt); + } - // calculate user range for full pad - const func = (log, value, err) => { - if (!log) return value; - if (value <= 0) return err; - value = Math.log10(value); - if (log > 1) value = value/Math.log10(log); - return value; - }, frect = main.getFrameRect(); + if (!dblckick) { + pp.selectObjectPainter(exact ? exact.painter : this, + { x: pnt.x + this.getFrameX(), y: pnt.y + this.getFrameY() }); + } - r.ux1 = func(main.logx, r.ux1, 0); - r.ux2 = func(main.logx, r.ux2, 1); + return res; + } - let k = (r.ux2 - r.ux1)/(frect.width || 10); - r.px1 = r.ux1 - k*frect.x; - r.px2 = r.px1 + k*this.getPadWidth(); + /** @summary Check mouse moving */ + shiftMoveHanlder(evnt, pos0) { + if (evnt.buttons === this._shifting_buttons) { + const frame = this.getFrameSvg(), + pos = pointer(evnt, frame.node()), + main_svg = this.getG().selectChild('.main_layer'), + dx = pos0[0] - pos[0], + dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], + w = this.getFrameWidth(), h = this.getFrameHeight(); - r.uy1 = func(main.logy, r.uy1, 0); - r.uy2 = func(main.logy, r.uy2, 1); + this._shifting_dx = dx; + this._shifting_dy = dy; - k = (r.uy2 - r.uy1)/(frect.height || 10); - r.py1 = r.uy1 - k*frect.y; - r.py2 = r.py1 + k*this.getPadHeight(); + main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - return true; + evnt.preventDefault(); + evnt.stopPropagation(); + } } - /** @summary Show context menu for specified item - * @private */ - itemContextMenu(name) { - const rrr = this.svg_this_pad().node().getBoundingClientRect(), - evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getFramePainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) - selp = this.painters[indx]; - } - } + /** @summary mouse up handler for shifting */ + shiftUpHanlder(evnt) { + evnt.preventDefault(); - if (!isFunc(selp?.fillContextMenu)) return; + select(window).on('mousemove.shiftHandler', null) + .on('mouseup.shiftHandler', null); - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.snapid) - return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); + if ((this._shifting_dx !== undefined) && (this._shifting_dy !== undefined)) + this.performScalesShift(); } - /** @summary Save pad as image - * @param {string} kind - format of saved image like 'png', 'svg' or 'jpeg' - * @param {boolean} full_canvas - does complete canvas (true) or only frame area (false) should be saved - * @param {string} [filename] - name of the file which should be stored - * @desc Normally used from context menu - * @example - * import { getElementCanvPainter } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * let canvas_painter = getElementCanvPainter('drawing_div_id'); - * canvas_painter.saveAs('png', true, 'canvas.png'); */ - saveAs(kind, full_canvas, filename) { - if (!filename) - filename = (this.this_pad_name || (this.iscan ? 'canvas' : 'pad')) + '.' + kind; - - this.produceImage(full_canvas, kind).then(imgdata => { - if (!imgdata) - return console.error(`Fail to produce image ${filename}`); - - saveFile(filename, (kind !== 'svg') ? imgdata : 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(imgdata)); - }); - } + /** @summary Shift scales on defined positions */ + performScalesShift() { + const w = this.getFrameWidth(), h = this.getFrameHeight(), + main_svg = this.getG().selectChild('.main_layer'), + gr = this.getGrFuncs(), + xmin = gr.revertAxis('x', this._shifting_dx), + xmax = gr.revertAxis('x', this._shifting_dx + w), + ymin = gr.revertAxis('y', this._shifting_dy + h), + ymax = gr.revertAxis('y', this._shifting_dy); - /** @summary Search active pad - * @return {Object} pad painter for active pad */ - findActivePad() { - let active_pp; - this.forEachPainterInPad(pp => { - if (pp.is_active_pad && !active_pp) - active_pp = pp; - }, 'pads'); - return active_pp; - } + main_svg.attr('viewBox', `0 0 ${w} ${h}`); - /** @summary Prodce image for the pad - * @return {Promise} with created image */ - async produceImage(full_canvas, file_format) { - const use_frame = (full_canvas === 'frame'), - elem = use_frame ? this.getFrameSvg(this.this_pad_name) : (full_canvas ? this.getCanvSvg() : this.svg_this_pad()), - painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, - items = []; // keep list of replaced elements, which should be moved back at the end + this._shifting_dx = this._shifting_dy = undefined; - if (elem.empty()) - return ''; + setPainterTooltipEnabled(this, true); - if (use_frame || !full_canvas) { - const defs = this.getCanvSvg().selectChild('.canvas_defs'); - if (!defs.empty()) { - items.push({ prnt: this.getCanvSvg(), defs }); - elem.node().insertBefore(defs.node(), elem.node().firstChild); - } - } + if (this.scales_ndim === 1) + this.zoomSingle('x', xmin, xmax); + else + this.zoom(xmin, xmax, ymin, ymax); + } - let active_pp = null; - painter.forEachPainterInPad(pp => { - if (pp.is_active_pad && !active_pp) { - active_pp = pp; - active_pp.drawActiveBorder(null, false); - } + /** @summary Start mouse rect zooming */ + startRectSel(evnt) { + // ignore when touch selection is activated + if (this.zoom_kind > 100) + return; - if (use_frame) return; // do not make transformations for the frame + const frame = this.getFrameSvg(), + pos = pointer(evnt, frame.node()); - const item = { prnt: pp.svg_this_pad() }; - items.push(item); + if ((evnt.buttons === 3) || (evnt.button === 1)) { + this.clearInteractiveElements(); + this._shifting_buttons = evnt.buttons; - // remove buttons from each subpad - const btns = pp.getLayerSvg('btns_layer', pp.this_pad_name); - item.btns_node = btns.node(); - if (item.btns_node) { - item.btns_prnt = item.btns_node.parentNode; - item.btns_next = item.btns_node.nextSibling; - btns.remove(); + if (!evnt.$emul) { + select(window).on('mousemove.shiftHandler', evnt2 => this.shiftMoveHanlder(evnt2, pos)) + .on('mouseup.shiftHandler', evnt2 => this.shiftUpHanlder(evnt2), true); } - const main = pp.getFramePainter(); - if (!isFunc(main?.render3D) || !isFunc(main?.access3dKind)) return; + setPainterTooltipEnabled(this, false); + evnt.preventDefault(); + evnt.stopPropagation(); + return; + } - const can3d = main.access3dKind(); - if ((can3d !== constants$1.Embed3D.Overlay) && (can3d !== constants$1.Embed3D.Embed)) return; + // ignore all events from non-left button + if (evnt.button) + return; - const sz2 = main.getSizeFor3d(constants$1.Embed3D.Embed), // get size and position of DOM element as it will be embed + evnt.preventDefault(); - canvas = main.renderer.domElement; - main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately - const dataUrl = canvas.toDataURL('image/png'); + this.clearInteractiveElements(); - // remove 3D drawings - if (can3d === constants$1.Embed3D.Embed) { - item.foreign = item.prnt.select('.' + sz2.clname); - item.foreign.remove(); - } + const w = this.getFrameWidth(), h = this.getFrameHeight(); - const svg_frame = main.getFrameSvg(); - item.frame_node = svg_frame.node(); - if (item.frame_node) { - item.frame_next = item.frame_node.nextSibling; - svg_frame.remove(); - } + this.zoom_lastpos = pos; + this.zoom_curr = [Math.max(0, Math.min(w, pos[0])), Math.max(0, Math.min(h, pos[1]))]; - // add svg image - item.img = item.prnt.insert('image', '.primitives_layer') // create image object - .attr('x', sz2.x) - .attr('y', sz2.y) - .attr('width', canvas.width) - .attr('height', canvas.height) - .attr('href', dataUrl); - }, 'pads'); + this.zoom_origin = [0, 0]; + this.zoom_second = false; - let width = elem.property('draw_width'), height = elem.property('draw_height'); - if (use_frame) { - const fp = this.getFramePainter(); - width = fp.getFrameWidth(); - height = fp.getFrameHeight(); + if ((pos[0] < 0) || (pos[0] > w)) { + this.zoom_second = (pos[0] > w) && this.y2_handle; + this.zoom_kind = 3; // only y + this.zoom_origin[1] = this.zoom_curr[1]; + this.zoom_curr[0] = w; + this.zoom_curr[1] += 1; + } else if ((pos[1] < 0) || (pos[1] > h)) { + this.zoom_second = (pos[1] < 0) && this.x2_handle; + this.zoom_kind = 2; // only x + this.zoom_origin[0] = this.zoom_curr[0]; + this.zoom_curr[0] += 1; + this.zoom_curr[1] = h; + } else { + this.zoom_kind = 1; // x and y + this.zoom_origin[0] = this.zoom_curr[0]; + this.zoom_origin[1] = this.zoom_curr[1]; } - const arg = (file_format === 'pdf') - ? { node: elem.node(), width, height, reset_tranform: use_frame } - : compressSVG(`${elem.node().innerHTML}`); + if (!evnt.$emul) { + select(window).on('mousemove.zoomRect', evnt2 => this.moveRectSel(evnt2)) + .on('mouseup.zoomRect', evnt2 => this.endRectSel(evnt2), true); + } - return svgToImage(arg, file_format).then(res => { - // reactivate border - active_pp?.drawActiveBorder(null, true); + this.zoom_rect = null; - for (let k = 0; k < items.length; ++k) { - const item = items[k]; + // disable tooltips in frame painter + setPainterTooltipEnabled(this, false); - item.img?.remove(); // delete embed image + evnt.stopPropagation(); - const prim = item.prnt.selectChild('.primitives_layer'); + if (this.zoom_kind !== 1) + return postponePromise(() => this.startLabelsMove(), 500); + } - if (item.foreign) // reinsert foreign object - item.prnt.node().insertBefore(item.foreign.node(), prim.node()); + /** @summary Starts labels move */ + startLabelsMove() { + if (this.zoom_rect) + return; - if (item.frame_node) // reinsert frame as first in list of primitives - prim.node().insertBefore(item.frame_node, item.frame_next); + const handle = (this.zoom_kind === 2) ? this.x_handle : this.y_handle; - if (item.btns_node) // reinsert buttons - item.btns_prnt.insertBefore(item.btns_node, item.btns_next); + if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) + return; - if (item.defs) // reinsert defs - item.prnt.node().insertBefore(item.defs.node(), item.prnt.node().firstChild); - } - return res; - }); + if (handle.processLabelsMove('start', this.zoom_lastpos)) + this.zoom_labels = handle; } - /** @summary Process pad button click */ - clickPadButton(funcname, evnt) { - if (funcname === 'CanvasSnapShot') - return this.saveAs('png', true); + /** @summary Process mouse rect zooming */ + moveRectSel(evnt) { + if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) + return; - if (funcname === 'enlargePad') - return this.enlargePad(); + evnt.preventDefault(); + const m = pointer(evnt, this.getFrameSvg().node()); - if (funcname === 'PadSnapShot') - return this.saveAs('png', false); + if (this.zoom_labels) + return this.zoom_labels.processLabelsMove('move', m); - if (funcname === 'PadContextMenus') { - evnt?.preventDefault(); - evnt?.stopPropagation(); - if (closeMenu()) return; + this.zoom_lastpos[0] = m[0]; + this.zoom_lastpos[1] = m[1]; - return createMenu(evnt, this).then(menu => { - menu.add('header:Menus'); + m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); + m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - if (this.iscan) - menu.add('Canvas', 'pad', this.itemContextMenu); - else - menu.add('Pad', 'pad', this.itemContextMenu); + switch (this.zoom_kind) { + case 1: + this.zoom_curr[0] = m[0]; + this.zoom_curr[1] = m[1]; + break; + case 2: + this.zoom_curr[0] = m[0]; + break; + case 3: + this.zoom_curr[1] = m[1]; + break; + } - if (this.getFramePainter()) - menu.add('Frame', 'frame', this.itemContextMenu); + const x = Math.min(this.zoom_origin[0], this.zoom_curr[0]), + y = Math.min(this.zoom_origin[1], this.zoom_curr[1]), + w = Math.abs(this.zoom_curr[0] - this.zoom_origin[0]), + h = Math.abs(this.zoom_curr[1] - this.zoom_origin[1]); - const main = this.getMainPainter(); // here pad painter method + if (!this.zoom_rect) { + // ignore small changes, can be switching to labels move + if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) + return; - if (main) { - menu.add('X axis', 'xaxis', this.itemContextMenu); - menu.add('Y axis', 'yaxis', this.itemContextMenu); - if (isFunc(main.getDimension) && (main.getDimension() > 1)) - menu.add('Z axis', 'zaxis', this.itemContextMenu); - } + this.zoom_rect = this.getFrameSvg() + .append('rect') + .style('pointer-events', 'none') + .call(addHighlightStyle, true); + } - if (this.painters?.length) { - menu.add('separator'); - const shown = []; - this.painters.forEach((pp, indx) => { - const obj = pp?.getObject(); - if (!obj || (shown.indexOf(obj) >= 0)) return; - let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); - if (name) name += '::'; - name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); - menu.add(name, indx, this.itemContextMenu); - shown.push(obj); - }); - } + this.zoom_rect.attr('x', x).attr('y', y).attr('width', w).attr('height', h); + } - menu.show(); - }); - } + /** @summary Finish mouse rect zooming */ + endRectSel(evnt) { + if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) + return; - // click automatically goes to all sub-pads - // if any painter indicates that processing completed, it returns true - let done = false; - const prs = []; + evnt.preventDefault(); + + if (!evnt.$emul) { + select(window).on('mousemove.zoomRect', null) + .on('mouseup.zoomRect', null); + } - for (let i = 0; i < this.painters.length; ++i) { - const pp = this.painters[i]; + const m = pointer(evnt, this.getFrameSvg().node()); + let kind = this.zoom_kind, pr; - if (isFunc(pp.clickPadButton)) - prs.push(pp.clickPadButton(funcname, evnt)); + if (this.zoom_labels) + this.zoom_labels.processLabelsMove('stop', m); + else { + const changed = [this.can_zoom_x, this.can_zoom_y]; + m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); + m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - if (!done && isFunc(pp.clickButton)) { - done = pp.clickButton(funcname); - if (isPromise(done)) prs.push(done); + switch (this.zoom_kind) { + case 1: + this.zoom_curr[0] = m[0]; + this.zoom_curr[1] = m[1]; + break; + case 2: // only X + this.zoom_curr[0] = m[0]; + changed[1] = false; + break; + case 3: // only Y + this.zoom_curr[1] = m[1]; + changed[0] = false; + break; } - } - return Promise.all(prs); - } + let xmin, xmax, ymin, ymax, isany = false, + namex = 'x', namey = 'y'; - /** @summary Add button to the pad - * @private */ - addPadButton(btn, tooltip, funcname, keyname) { - if (!settings.ToolBar || this.isBatchMode()) return; + if (changed[0] && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 5)) { + if (this.zoom_second && (this.zoom_kind === 2)) + namex = 'x2'; + const v1 = this.revertAxis(namex, this.zoom_origin[0]), + v2 = this.revertAxis(namex, this.zoom_curr[0]); + xmin = Math.min(v1, v2); + xmax = Math.max(v1, v2); + isany = true; + } - if (!this._buttons) this._buttons = []; - // check if there are duplications + if (changed[1] && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 5)) { + if (this.zoom_second && (this.zoom_kind === 3)) + namey = 'y2'; - for (let k = 0; k < this._buttons.length; ++k) - if (this._buttons[k].funcname === funcname) return; + const v1 = this.revertAxis(namey, this.zoom_origin[1]), + v2 = this.revertAxis(namey, this.zoom_curr[1]); + ymin = Math.min(v1, v2); + ymax = Math.max(v1, v2); + isany = true; + } - this._buttons.push({ btn, tooltip, funcname, keyname }); + if (this.swap_xy() && !this.zoom_second) + [xmin, xmax, ymin, ymax] = [ymin, ymax, xmin, xmax]; - const iscan = this.iscan || !this.has_canvas; - if (!iscan && (funcname.indexOf('Pad') !== 0) && (funcname !== 'enlargePad')) { - const cp = this.getCanvPainter(); - if (cp && (cp !== this)) cp.addPadButton(btn, tooltip, funcname); + if (namex === 'x2') { + pr = this.zoomSingle(namex, xmin, xmax, true); + kind = 0; + } else if (namey === 'y2') { + pr = this.zoomSingle(namey, ymin, ymax, true); + kind = 0; + } else if (isany) { + pr = this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); + kind = 0; + } } - } - /** @summary Show pad buttons - * @private */ - showPadButtons() { - if (!this._buttons) return; + const pnt = (kind === 1) ? { x: this.zoom_origin[0], y: this.zoom_origin[1] } : null; - PadButtonsHandler.assign(this); - this.showPadButtons(); - } + this.clearInteractiveElements(); - /** @summary Add buttons for pad or canvas - * @private */ - addPadButtons(is_online) { - this.addPadButton('camera', 'Create PNG', this.iscan ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); + // if no zooming was done, select active object instead + switch (kind) { + case 1: + this.processFrameClick(pnt); + break; + case 2: + this.getPadPainter()?.selectObjectPainter(this.x_handle); + break; + case 3: + this.getPadPainter()?.selectObjectPainter(this.y_handle); + break; + } - if (settings.ContextMenu) - this.addPadButton('question', 'Access context menus', 'PadContextMenus'); + // return promise - if any + return pr; + } - const add_enlarge = !this.iscan && this.has_canvas && this.hasObjectsToDraw(); + /** @summary Handle mouse double click on frame */ + mouseDoubleClick(evnt) { + evnt.preventDefault(); + const m = pointer(evnt, this.getFrameSvg().node()), + fw = this.getFrameWidth(), fh = this.getFrameHeight(); + this.clearInteractiveElements(); - if (add_enlarge || this.enlargeMain('verify')) - this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); + const valid_x = (m[0] >= 0) && (m[0] <= fw), + valid_y = (m[1] >= 0) && (m[1] <= fh); - if (is_online && this.brlayout) { - this.addPadButton('diamand', 'Toggle Ged', 'ToggleGed'); - this.addPadButton('three_circles', 'Toggle Status', 'ToggleStatus'); + if (valid_x && valid_y && this.getDblclickHandler()) { + if (this.processFrameClick({ x: m[0], y: m[1] }, true)) + return; } - } - - /** @summary Decode pad draw options - * @private */ - decodeOptions(opt) { - const pad = this.getObject(); - if (!pad) return; - const d = new DrawOptions(opt); + let kind = (this.can_zoom_x ? 'x' : '') + (this.can_zoom_y ? 'y' : '') + 'z'; + if (!valid_x) { + if (!this.can_zoom_y) + return; + kind = this.swap_xy() ? 'x' : 'y'; + if ((m[0] > fw) && this[kind + '2_handle']) + kind += '2'; // let unzoom second axis + } else if (!valid_y) { + if (!this.can_zoom_x) + return; + kind = this.swap_xy() ? 'y' : 'x'; + if ((m[1] < 0) && this[kind + '2_handle']) + kind += '2'; // let unzoom second axis + } + return this.unzoom(kind).then(changed => { + if (changed) + return; + const pp = this.getPadPainter(), rect = this.getFrameRect(); + return pp?.selectObjectPainter(pp, { x: m[0] + rect.x, y: m[1] + rect.y, dbl: true }); + }); + } - if (!this.options) this.options = {}; + /** @summary Start touch zoom */ + startTouchZoom(evnt) { + evnt.preventDefault(); + evnt.stopPropagation(); - Object.assign(this.options, { GlobalColors: true, LocalColors: false, CreatePalette: 0, IgnorePalette: false, RotateFrame: false, FixFrame: false }); + // in case when zooming was started, block any other kind of events + // also prevent zooming together with active dragging + if (this.zoom_kind || drag_kind) + return; - if (d.check('NOCOLORS') || d.check('NOCOL')) this.options.GlobalColors = this.options.LocalColors = false; - if (d.check('LCOLORS') || d.check('LCOL')) { this.options.GlobalColors = false; this.options.LocalColors = true; } - if (d.check('NOPALETTE') || d.check('NOPAL')) this.options.IgnorePalette = true; - if (d.check('ROTATE')) this.options.RotateFrame = true; - if (d.check('FIXFRAME')) this.options.FixFrame = true; - if (d.check('FIXSIZE') && this.iscan) this._fixed_size = true; + const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - if (d.check('CP', true)) this.options.CreatePalette = d.partAsInt(0, 0); + // normally double-touch will be handled + // touch with single click used for context menu + if (arr.length === 1) { + // this is touch with single element - if (d.check('NOZOOMX')) this.options.NoZoomX = true; - if (d.check('NOZOOMY')) this.options.NoZoomY = true; - if (d.check('GRAYSCALE') && !pad.TestBit(kIsGrayscale)) - pad.InvertBit(kIsGrayscale); + const now = new Date().getTime(); + let tmdiff = 1e10, dx = 100, dy = 100; - function forEach(func, p) { - if (!p) p = pad; - func(p); - const arr = p.fPrimitives?.arr || []; - for (let i = 0; i < arr.length; ++i) { - if (arr[i]._typename === clTPad) - forEach(func, arr[i]); + if (this.last_touch_time && this.last_touch_pos) { + tmdiff = now - this.last_touch_time; + dx = Math.abs(arr[0][0] - this.last_touch_pos[0]); + dy = Math.abs(arr[0][1] - this.last_touch_pos[1]); } - } - if (d.check('NOMARGINS')) forEach(p => { p.fLeftMargin = p.fRightMargin = p.fBottomMargin = p.fTopMargin = 0; }); - if (d.check('WHITE')) forEach(p => { p.fFillColor = 0; }); - if (d.check('LOG2X')) forEach(p => { p.fLogx = 2; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LOGX')) forEach(p => { p.fLogx = 1; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LOG2Y')) forEach(p => { p.fLogy = 2; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LOGY')) forEach(p => { p.fLogy = 1; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LOG2Z')) forEach(p => { p.fLogz = 2; }); - if (d.check('LOGZ')) forEach(p => { p.fLogz = 1; }); - if (d.check('LOGV')) forEach(p => { p.fLogv = 1; }); - if (d.check('LOG2')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 2; }); - if (d.check('LOG')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 1; }); - if (d.check('LNX')) forEach(p => { p.fLogx = 3; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LNY')) forEach(p => { p.fLogy = 3; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LN')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 3; }); - if (d.check('GRIDX')) forEach(p => { p.fGridx = 1; }); - if (d.check('GRIDY')) forEach(p => { p.fGridy = 1; }); - if (d.check('GRID')) forEach(p => { p.fGridx = p.fGridy = 1; }); - if (d.check('TICKX')) forEach(p => { p.fTickx = 1; }); - if (d.check('TICKY')) forEach(p => { p.fTicky = 1; }); - if (d.check('TICKZ')) forEach(p => { p.fTickz = 1; }); - if (d.check('TICK')) forEach(p => { p.fTickx = p.fTicky = 1; }); - if (d.check('OTX')) forEach(p => { p.$OTX = true; }); - if (d.check('OTY')) forEach(p => { p.$OTY = true; }); - if (d.check('CTX')) forEach(p => { p.$CTX = true; }); - if (d.check('CTY')) forEach(p => { p.$CTY = true; }); - if (d.check('RX')) forEach(p => { p.$RX = true; }); - if (d.check('RY')) forEach(p => { p.$RY = true; }); - - this.storeDrawOpt(opt); - } + this.last_touch_time = now; + this.last_touch_pos = arr[0]; - /** @summary draw TPad object */ - static async draw(dom, pad, opt) { - const painter = new TPadPainter(dom, pad, false); - painter.decodeOptions(opt); + if ((tmdiff < 500) && (dx < 20) && (dy < 20)) { + this.clearInteractiveElements(); + this.unzoom('xyz'); - if (painter.getCanvSvg().empty()) { - // one can draw pad without canvas - painter.has_canvas = false; - painter.this_pad_name = ''; - painter.setTopPainter(); - } else { - // pad painter will be registered in the canvas painters list - painter.addToPadPrimitives(painter.pad_name); + delete this.last_touch_time; + } else if (settings.ContextMenu) + this.startSingleTouchHandling('', evnt); } - if (pad?.$disable_drawing) - painter.pad_draw_disabled = true; + if ((arr.length !== 2) || !settings.Zooming || !settings.ZoomTouch) + return; - painter.createPadSvg(); + this.clearInteractiveElements(); - if (painter.matchObjectType(clTPad) && (!painter.has_canvas || painter.hasObjectsToDraw())) - painter.addPadButtons(); + // clear single touch handler + this.endSingleTouchHandling(null); - // we select current pad, where all drawing is performed - const prev_name = painter.has_canvas ? painter.selectCurrentPad(painter.this_pad_name) : undefined; + const pnt1 = arr[0], pnt2 = arr[1], w = this.getFrameWidth(), h = this.getFrameHeight(); - // set active pad - selectActivePad({ pp: painter, active: true }); + this.zoom_curr = [Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1])]; + this.zoom_origin = [Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1])]; + this.zoom_second = false; - // flag used to prevent immediate pad redraw during first draw - return painter.drawPrimitives().then(() => { - painter.showPadButtons(); - painter.addPadInteractive(); - // we restore previous pad name - painter.selectCurrentPad(prev_name); - return painter; - }); - } + if ((this.zoom_curr[0] < 0) || (this.zoom_curr[0] > w)) { + this.zoom_second = (this.zoom_curr[0] > w) && this.y2_handle; + this.zoom_kind = 103; // only y + this.zoom_curr[0] = 0; + this.zoom_origin[0] = w; + } else if ((this.zoom_origin[1] > h) || (this.zoom_origin[1] < 0)) { + this.zoom_second = (this.zoom_origin[1] < 0) && this.x2_handle; + this.zoom_kind = 102; // only x + this.zoom_curr[1] = 0; + this.zoom_origin[1] = h; + } else + this.zoom_kind = 101; // x and y -} // class TPadPainter + drag_kind = 'zoom'; // block other possible dragging -const kShowEventStatus = BIT(15), - // kAutoExec = BIT(16), - kMenuBar = BIT(17), - kShowToolBar = BIT(18), - kShowEditor = BIT(19), - // kMoveOpaque = BIT(20), - // kResizeOpaque = BIT(21), - // kIsGrayscale = BIT(22), - kShowToolTips = BIT(23); + setPainterTooltipEnabled(this, false); -/** @summary direct draw of TFrame object, - * @desc pad or canvas should already exist - * @private */ -function directDrawTFrame(dom, obj, opt) { - const fp = new TFramePainter(dom, obj); - fp.addToPadPrimitives(); - if (opt === '3d') fp.mode3d = true; - return fp.redraw(); -} + this.zoom_rect = this.getFrameSvg().append('rect') + .attr('id', 'zoomRect') + .attr('x', this.zoom_curr[0]) + .attr('y', this.zoom_curr[1]) + .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) + .attr('height', this.zoom_origin[1] - this.zoom_curr[1]) + .call(addHighlightStyle, true); -/** - * @summary Painter for TCanvas object - * - * @private - */ + if (!evnt.$emul) { + select(window).on('touchmove.zoomRect', evnt2 => this.moveTouchZoom(evnt2)) + .on('touchcancel.zoomRect', evnt2 => this.endTouchZoom(evnt2)) + .on('touchend.zoomRect', evnt2 => this.endTouchZoom(evnt2)); + } + } -class TCanvasPainter extends TPadPainter { + /** @summary Move touch zooming */ + moveTouchZoom(evnt) { + if (this.zoom_kind < 100) + return; - /** @summary Constructor */ - constructor(dom, canvas) { - super(dom, canvas, true); - this._websocket = null; - this.tooltip_allowed = settings.Tooltip; - if ((dom === null) && (canvas === null)) { - // for web canvas details are important - settings.SmallPad.width = 20; - settings.SmallPad.height = 10; + evnt.preventDefault(); + + const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); + + if (arr.length !== 2) + return this.clearInteractiveElements(); + + const pnt1 = arr[0], pnt2 = arr[1]; + + if (this.zoom_kind !== 103) { + this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]); + this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]); + } + if (this.zoom_kind !== 102) { + this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]); + this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]); } - } - /** @summary Cleanup canvas painter */ - cleanup() { - if (this._changed_layout) - this.setLayoutKind('simple'); - delete this._changed_layout; - super.cleanup(); + this.zoom_rect.attr('x', this.zoom_curr[0]) + .attr('y', this.zoom_curr[1]) + .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) + .attr('height', this.zoom_origin[1] - this.zoom_curr[1]); + + if ((this.zoom_origin[0] - this.zoom_curr[0] > 10) || (this.zoom_origin[1] - this.zoom_curr[1] > 10)) + setPainterTooltipEnabled(this, false); + + evnt.stopPropagation(); } - /** @summary Returns layout kind */ - getLayoutKind() { - const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); + /** @summary End touch zooming handler */ + endTouchZoom(evnt) { + if (this.zoom_kind < 100) + return; - return layout || 'simple'; + drag_kind = ''; // reset global flag + + evnt.preventDefault(); + if (!evnt.$emul) { + select(window).on('touchmove.zoomRect', null) + .on('touchend.zoomRect', null) + .on('touchcancel.zoomRect', null); + } + + let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; + const xid = this.swap_xy() ? 1 : 0, yid = 1 - xid, changed = [true, true]; + + if (this.zoom_kind === 102) + changed[1] = false; + if (this.zoom_kind === 103) + changed[0] = false; + + if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) { + if (this.zoom_second && (this.zoom_kind === 102)) + namex = 'x2'; + xmin = Math.min(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); + xmax = Math.max(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); + isany = true; + } + + if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) { + if (this.zoom_second && (this.zoom_kind === 103)) + namey = 'y2'; + ymin = Math.min(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); + ymax = Math.max(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); + isany = true; + } + + this.clearInteractiveElements(); + delete this.last_touch_time; + + if (namex === 'x2') + this.zoomSingle(namex, xmin, xmax, true); + else if (namey === 'y2') + this.zoomSingle(namey, ymin, ymax, true); + else if (isany) + this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); + + evnt.stopPropagation(); } - /** @summary Set canvas layout kind */ - setLayoutKind(kind, main_selector) { - const origin = this.selectDom('origin'); - if (!origin.empty()) { - if (!kind) kind = 'simple'; - origin.property('layout', kind); - origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this._changed_layout = (kind !== 'simple'); // use in cleanup + /** @summary Analyze zooming with mouse wheel */ + analyzeMouseWheelEvent(event, item, dmin, test_ignore, second_side) { + // if there is second handle, use it + const handle2 = second_side ? this[item.name + '2_handle'] : null; + if (handle2) { + item.second = Object.assign({}, item); + return handle2.analyzeWheelEvent(event, dmin, item.second, test_ignore); } + const handle = this[item.name + '_handle']; + return handle?.analyzeWheelEvent(event, dmin, item, test_ignore); } - /** @summary Changes layout - * @return {Promise} indicating when finished */ - async changeLayout(layout_kind, mainid) { - const current = this.getLayoutKind(); - if (current === layout_kind) + /** @summary return true if default Y zooming should be enabled + * @desc it is typically for 2-Dim histograms or + * when histogram not draw, defined by other painters */ + isAllowedDefaultYZooming() { + if (this.self_drawaxes) return true; - const origin = this.selectDom('origin'), - sidebar2 = origin.select('.side_panel2'), - lst = []; - let sidebar = origin.select('.side_panel'), - main = this.selectDom(), force; + let res; + this.forEachPainter(objp => { + res = res ?? objp._wheel_zoomy; + }, 'objects'); + return res; + } - while (main.node().firstChild) - lst.push(main.node().removeChild(main.node().firstChild)); + /** @summary Handles mouse wheel event */ + mouseWheel(evnt) { + evnt.stopPropagation(); + evnt.preventDefault(); + this.clearInteractiveElements(); - if (!sidebar.empty()) - cleanup(sidebar.node()); - if (!sidebar2.empty()) - cleanup(sidebar2.node()); + const itemx = { name: 'x', reverse: this.reverse_x() }, + itemy = { name: 'y', reverse: this.reverse_y(), ignore: !this.isAllowedDefaultYZooming() }, + cur = pointer(evnt, this.getFrameSvg().node()), + w = this.getFrameWidth(), h = this.getFrameHeight(); - this.setLayoutKind('simple'); // restore defaults - origin.html(''); // cleanup origin + if (this.can_zoom_x) + this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); - if (layout_kind === 'simple') { - main = origin; - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - this.setLayoutKind(layout_kind); - force = true; - } else { - const grid = new GridDisplay(origin.node(), layout_kind); + if (this.can_zoom_y) + this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); - if (mainid === undefined) - mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; + let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max, undefined, undefined, itemx.changed || itemy.changed); - main = select(grid.getGridFrame(mainid)); - main.classed('central_panel', true).style('position', 'relative'); + if (itemx.second) + pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max, itemx.second.changed)); - if (mainid === 2) { - // left panel for Y - sidebar = select(grid.getGridFrame(0)); - sidebar.classed('side_panel2', true).style('position', 'relative'); - // bottom panel for X - sidebar = select(grid.getGridFrame(3)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } else { - sidebar = select(grid.getGridFrame(1 - mainid)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } + if (itemy.second) + pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max, itemy.second.changed)); - // now append all childs to the new main - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); + return pr; + } - this.setLayoutKind(layout_kind, '.central_panel'); + /** @summary Show frame context menu */ + showContextMenu(kind, evnt, obj) { + // disable context menu left/right buttons clicked + if (evnt?.buttons === 3) + return evnt.preventDefault(); - // remove reference to MDIDisplay, solves resize problem - origin.property('mdi', null); + // ignore context menu when touches zooming is ongoing or + if (this.zoom_kind && (this.zoom_kind > 100)) + return; + + let pnt, menu_painter = this, exec_painter = null, + frame_corner = false, fp = null; // object used to show context menu + const svg_node = this.getFrameSvg().node(); + + if (isFunc(evnt?.stopPropagation)) { + evnt.preventDefault(); + evnt.stopPropagation(); // disable main context menu + const ms = pointer(evnt, svg_node), + tch = get_touch_pointers(evnt, svg_node); + if (tch.length === 1) + pnt = { x: tch[0][0], y: tch[0][1], touch: true }; + else if (ms.length === 2) + pnt = { x: ms[0], y: ms[1], touch: false }; + } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { + pnt = evnt; + const rect = svg_node.getBoundingClientRect(); + evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; } - // resize main drawing and let draw extras - resize(main.node(), force); - return true; - } + if ((kind === 'painter') && obj) { + menu_painter = obj; + kind = ''; + } else if (kind === 'main') { + menu_painter = this.getMainPainter(true); + kind = ''; + } else if (!kind) { + const pp = this.getPadPainter(); + let sel = null; - /** @summary Toggle projection - * @return {Promise} indicating when ready - * @private */ - async toggleProjection(kind) { - delete this.proj_painter; + fp = this; + if (pnt && pp) { + pnt.painters = true; // assign painter for every tooltip + const hints = pp.processPadTooltipEvent(pnt); + let bestdist = 1000; + for (let n = 0; n < hints.length; ++n) { + if (hints[n]?.menu) { + const dist = hints[n].menu_dist ?? 7; + if (dist < bestdist) { + sel = hints[n].painter; + bestdist = dist; + } + } + } + } - if (kind) this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed + if (sel) + menu_painter = sel; + else + kind = 'frame'; - if (isFunc(this.showUI5ProjectionArea)) - return this.showUI5ProjectionArea(kind); + if (pnt) + frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); - let layout = 'simple', mainid; + fp.setLastEventPos(pnt); + } else if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'pal')) { + exec_painter = this.getMainPainter(true); // histogram painter delivers items for axis menu - switch (kind) { - case 'XY': layout = 'projxy'; mainid = 2; break; - case 'X': - case 'bottom': layout = 'vert2_31'; mainid = 0; break; - case 'Y': - case 'left': layout = 'horiz2_13'; mainid = 1; break; - case 'top': layout = 'vert2_13'; mainid = 1; break; - case 'right': layout = 'horiz2_31'; mainid = 0; break; + if (this.v7_frame && isFunc(exec_painter?.v7EvalAttr)) + exec_painter = null; } - return this.changeLayout(layout, mainid); - } + if (!exec_painter) + exec_painter = menu_painter; - /** @summary Draw projection for specified histogram - * @private */ - async drawProjection(kind, hist, hopt) { - if (!this.proj_painter) - return false; // ignore drawing if projection not configured + if (!isFunc(menu_painter?.fillContextMenu)) + return; - if (hopt === undefined) - hopt = 'hist'; - if (!kind) kind = 'X'; + this.clearInteractiveElements(); - if (!this.proj_painter[kind]) { - this.proj_painter[kind] = 'init'; + return createMenu(evnt, menu_painter).then(menu => { + let domenu = menu.painter.fillContextMenu(menu, kind, obj); - const canv = create$1(clTCanvas), - pad = this.pad, - main = this.getFramePainter(); - let drawopt; + // fill frame menu by default - or append frame elements when activated in the frame corner + if (fp && (!domenu || (frame_corner && (kind !== 'frame')))) + domenu = fp.fillContextMenu(menu); - if (kind === 'X') { - canv.fLeftMargin = pad.fLeftMargin; - canv.fRightMargin = pad.fRightMargin; - canv.fLogx = main.logx; - canv.fUxmin = main.logx ? Math.log10(main.scale_xmin) : main.scale_xmin; - canv.fUxmax = main.logx ? Math.log10(main.scale_xmax) : main.scale_xmax; - drawopt = 'fixframe'; - } else if (kind === 'Y') { - canv.fBottomMargin = pad.fBottomMargin; - canv.fTopMargin = pad.fTopMargin; - canv.fLogx = main.logy; - canv.fUxmin = main.logy ? Math.log10(main.scale_ymin) : main.scale_ymin; - canv.fUxmax = main.logy ? Math.log10(main.scale_ymax) : main.scale_ymax; - drawopt = 'rotate'; + if (domenu) { + return exec_painter.fillObjectExecMenu(menu, kind).then(menu2 => { + // suppress any running zooming + setPainterTooltipEnabled(menu2.painter, false); + return menu2.show().then(() => setPainterTooltipEnabled(menu2.painter, true)); + }); } + }); + } - canv.fPrimitives.Add(hist, hopt); + /** @summary Activate touch handling on frame + * @private */ + startSingleTouchHandling(kind, evnt) { + const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); + if (arr.length !== 1) + return; - const promise = isFunc(this.drawInUI5ProjectionArea) - ? this.drawInUI5ProjectionArea(canv, drawopt, kind) - : this.drawInSidePanel(canv, drawopt, kind); + evnt.preventDefault(); + evnt.stopPropagation(); + closeMenu(); - return promise.then(painter => { this.proj_painter[kind] = painter; return painter; }); - } else if (isStr(this.proj_painter[kind])) { - console.log('Not ready with first painting', kind); - return true; + const tm = new Date().getTime(); + + this._shifting_dx = 0; + this._shifting_dy = 0; + + setPainterTooltipEnabled(this, false); + + select(window).on('touchmove.singleTouch', kind ? null : evnt2 => this.moveTouchHandling(evnt2, kind, arr[0])) + .on('touchcancel.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)) + .on('touchend.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)); + } + + /** @summary Moving of touch pointer + * @private */ + moveTouchHandling(evnt, kind, pos0) { + const frame = this.getFrameSvg(), + main_svg = this.getG().selectChild('.main_layer'); + let pos; + + try { + pos = get_touch_pointers(evnt, frame.node())[0]; + } catch { + pos = [0, 0]; + if (evnt?.changedTouches) + pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; } - this.proj_painter[kind].getMainPainter()?.updateObject(hist, hopt); - return this.proj_painter[kind].redrawPad(); + const dx = pos0[0] - pos[0], + dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], + w = this.getFrameWidth(), h = this.getFrameHeight(); + + this._shifting_dx = dx; + this._shifting_dy = dy; + + main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); } - /** @summary Checks if canvas shown inside ui5 widget - * @desc Function should be used only from the func which supposed to be replaced by ui5 - * @private */ - testUI5() { - if (!this.use_openui) return false; - console.warn('full ui5 should be used - not loaded yet? Please check!!'); - return true; + /** @summary Process end-touch event, which can cause content menu to appear + * @private */ + endSingleTouchHandling(evnt, kind, pos, tm) { + evnt?.preventDefault(); + evnt?.stopPropagation(); + + setPainterTooltipEnabled(this, true); + + select(window).on('touchmove.singleTouch', null) + .on('touchcancel.singleTouch', null) + .on('touchend.singleTouch', null); + + if (evnt === null) + return; + + if (Math.abs(this._shifting_dx) > 2 || Math.abs(this._shifting_dy) > 2) + this.performScalesShift(); + else if (new Date().getTime() - tm > 700) + this.showContextMenu(kind, { x: pos[0], y: pos[1] }); } - /** @summary Draw in side panel - * @private */ - async drawInSidePanel(canv, opt, kind) { - const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', - side = this.selectDom('origin').select(sel); - return side.empty() ? null : this.drawObject(side.node(), canv, opt); + /** @summary Clear frame interactive elements */ + clearInteractiveElements() { + closeMenu(); + this.zoom_kind = 0; + this.zoom_rect?.remove(); + delete this.zoom_rect; + delete this.zoom_curr; + delete this.zoom_origin; + delete this.zoom_lastpos; + delete this.zoom_labels; + + // enable tooltip in frame painter + setPainterTooltipEnabled(this, true); } - /** @summary Show message - * @desc Used normally with web-based canvas and handled in ui5 - * @private */ - showMessage(msg) { - if (!this.testUI5()) - showProgress(msg, 7000); + /** @summary Assign interactive methods to frame painter */ + static assign(fpainter) { + const src = FrameInteractive.prototype, + src2 = TooltipHandler.prototype; + Object.getOwnPropertyNames(src).forEach(name => { + if (name !== 'constructor') + fpainter[name] = src[name]; + }); + Object.getOwnPropertyNames(src2).forEach(name => { + if (name !== 'constructor') + fpainter[name] = src2[name]; + }); } - /** @summary Function called when canvas menu item Save is called */ - saveCanvasAsFile(fname) { - const pnt = fname.indexOf('.'); - this.createImage(fname.slice(pnt+1)) - .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); +} // class FrameInteractive + +/** + * @summary Painter class for TFrame, main handler for interactivity + * @private + */ + +class TFramePainter extends FrameInteractive { + + #frame_x; // frame X coordinate + #frame_y; // frame Y coordinate + #frame_width; // frame width + #frame_height; // frame height + #frame_trans; // transform of frame element + #swap_xy; // swap X/Y axis on the frame + #reverse_x; // reverse X axis + #reverse_y; // reverse Y axis + #reverse_x2; // reverse X2 axis + #reverse_y2; // reverse Y2 axis + #border_mode; // frame border mode + #border_size; // frame border size + #axes_drawn; // when axes are drawn + #axes2_drawn; // when axes are drawn + #shrink_frame_left; // shrink frame on left side + #projection; // id of projection function + #click_handler; // handle for click events + #dblclick_handler; // handle for double click events + #keys_handler; // assigned handler for keyboard events + #enabled_keys; // when keyboard press handling enabled + #last_event_pos; // position of last event + + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} frame - TFrame object */ + constructor(dom, frame) { + super(dom, frame?.$dummy ? null : frame); + this.zoom_kind = 0; + this.mode3d = false; + this.#shrink_frame_left = 0.0; + this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing + this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing + this.ranges_set = false; + this.#axes_drawn = false; + this.#axes2_drawn = false; + this.#border_mode = gStyle.fFrameBorderMode; + this.#border_size = gStyle.fFrameBorderSize; + this.#projection = 0; // different projections } - /** @summary Send command to server to save canvas with specified name - * @desc Should be only used in web-based canvas + /** @summary Returns frame painter - object itself */ + getFramePainter() { return this; } + + /** @summary Returns true if it is ROOT6 frame * @private */ - sendSaveCommand(fname) { - this.sendWebsocket('PRODUCE:' + fname); + is_root6() { return true; } + + /** @summary Returns frame or sub-objects, used in GED editor */ + getObject(place) { + if (place === 'xaxis') + return this.xaxis; + if (place === 'yaxis') + return this.yaxis; + return super.getObject(); } - /** @summary Submit menu request + /** @summary Set active flag for frame - can block some events * @private */ - async submitMenuRequest(_painter, _kind, reqid) { - // only single request can be handled, no limit better in RCanvas - return new Promise(resolveFunc => { - this._getmenu_callback = resolveFunc; - this.sendWebsocket('GETMENU:' + reqid); // request menu items for given painter - }); + setFrameActive(on) { + this.#enabled_keys = on && settings.HandleKeys; + // used only in 3D mode where control is used + if (this.control) + this.control.enableKeys = this.#enabled_keys; } - /** @summary Submit object exec request + /** @summary Returns true if keys handling enabled * @private */ - submitExec(painter, exec, snapid) { - if (this._readonly || !painter) return; + isEnabledKeys() { return this.#enabled_keys; } - if (!snapid) snapid = painter.snapid; - if (snapid && isStr(snapid) && exec) - return this.sendWebsocket(`OBJEXEC:${snapid}:${exec}`); - } + /** @summary Returns true if X/Y axis swapped */ + swap_xy() { return this.#swap_xy; } - /** @summary Send text message with web socket - * @desc used for communication with server-side of web canvas - * @private */ - sendWebsocket(msg) { - if (this._websocket?.canSend()) { - this._websocket.send(msg); - return true; - } - console.warn(`DROP SEND: ${msg}`); - return false; - } + /** @summary Is reverse x */ + reverse_x() { return this.#reverse_x; } - /** @summary Close websocket connection to canvas + /** @summary Is reverse x */ + reverse_y() { return this.#reverse_y; } + + /** @summary Shrink frame size * @private */ - closeWebsocket(force) { - if (this._websocket) { - this._websocket.close(force); - this._websocket.cleanup(); - delete this._websocket; - } + shrinkFrame(shrink_left, shrink_right) { + this.fX1NDC += shrink_left; + this.fX2NDC -= shrink_right; } - /** @summary Use provided connection for the web canvas + /** @summary Set position of last context menu event + * @private */ + setLastEventPos(pnt) { this.#last_event_pos = pnt; } + + /** @summary Return position of last event * @private */ - useWebsocket(handle) { - this.closeWebsocket(); + getLastEventPos() { return this.#last_event_pos; } - this._websocket = handle; - this._websocket.setReceiver(this); - this._websocket.connect(); - } + /** @summary Returns coordinates transformation func */ + getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - /** @summary set, test or reset timeout of specified name - * @desc Used to prevent overloading of websocket for specific function */ - websocketTimeout(name, tm) { - if (!this._websocket) + /** @summary Recalculate frame ranges using specified projection functions */ + recalculateRange(Proj, change_x, change_y) { + this.#projection = Proj || 0; + + if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { + console.warn(`Mercator Projection: Latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); + this.#projection = 0; + } + + const func = this.getProjectionFunc(); + if (!func) return; - if (!this._websocket._tmouts) - this._websocket._tmouts = {}; - const handle = this._websocket._tmouts[name]; - if (tm === undefined) - return handle !== undefined; + const pnts = [func(this.scale_xmin, this.scale_ymin), + func(this.scale_xmin, this.scale_ymax), + func(this.scale_xmax, this.scale_ymax), + func(this.scale_xmax, this.scale_ymin)]; + if (this.scale_xmin < 0 && this.scale_xmax > 0) { + pnts.push(func(0, this.scale_ymin)); + pnts.push(func(0, this.scale_ymax)); + } + if (this.scale_ymin < 0 && this.scale_ymax > 0) { + pnts.push(func(this.scale_xmin, 0)); + pnts.push(func(this.scale_xmax, 0)); + } - if (tm === 'reset') { - if (handle) { clearTimeout(handle); delete this._websocket._tmouts[name]; } - } else if (!handle && Number.isInteger(tm)) - this._websocket._tmouts[name] = setTimeout(() => { delete this._websocket._tmouts[name]; }, tm); - } + this.original_xmin = this.scale_xmin; + this.original_xmax = this.scale_xmax; + this.original_ymin = this.scale_ymin; + this.original_ymax = this.scale_ymax; - /** @summary Hanler for websocket open event - * @private */ - onWebsocketOpened(/* handle */) { - // indicate that we are ready to recieve any following commands - } + if (change_x) + this.scale_xmin = this.scale_xmax = pnts[0].x; + if (change_y) + this.scale_ymin = this.scale_ymax = pnts[0].y; - /** @summary Hanler for websocket close event - * @private */ - onWebsocketClosed(/* handle */) { - if (!this.embed_canvas) - closeCurrentWindow(); + for (let n = 1; n < pnts.length; ++n) { + if (change_x) { + this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); + this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); + } + if (change_y) { + this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); + this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); + } + } } - /** @summary Handle websocket messages - * @private */ - onWebsocketMsg(handle, msg) { - // console.log(`GET MSG len:${msg.length} ${msg.slice(0,60)}`); + /** @summary Configure frame axes ranges */ + setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax, hpainter) { + this.ranges_set = true; - if (msg === 'CLOSE') { - this.onWebsocketClosed(); - this.closeWebsocket(true); - } else if (msg.slice(0, 6) === 'SNAP6:') { - // This is snapshot, produced with TWebCanvas - const p1 = msg.indexOf(':', 6), - version = msg.slice(6, p1), - snap = parse(msg.slice(p1+1)); + this.xaxis = xaxis; + this.xmin = xmin; + this.xmax = xmax; - this.syncDraw(true) - .then(() => { - if (!this.snapid) - this.resizeBrowser(snap.fSnapshot.fWindowWidth, snap.fSnapshot.fWindowHeight); - if (!this.snapid && isFunc(this.setFixedCanvasSize)) - this._online_fixed_size = this.setFixedCanvasSize(snap.fSnapshot.fCw, snap.fSnapshot.fCh, snap.fFixedSize); - }) - .then(() => this.redrawPadSnap(snap)) - .then(() => { - this.completeCanvasSnapDrawing(); - let ranges = this.getWebPadOptions(); // all data, including subpads - if (ranges) ranges = ':' + ranges; - handle.send(`READY6:${version}${ranges}`); // send ready message back when drawing completed - this.confirmDraw(); - }); - } else if (msg.slice(0, 5) === 'MENU:') { - // this is menu with exact identifier for object - const lst = parse(msg.slice(5)); - if (isFunc(this._getmenu_callback)) { - this._getmenu_callback(lst); - delete this._getmenu_callback; - } - } else if (msg.slice(0, 4) === 'CMD:') { - msg = msg.slice(4); - const p1 = msg.indexOf(':'), - cmdid = msg.slice(0, p1), - cmd = msg.slice(p1+1), - reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG')) { - this.createImage(cmd.toLowerCase()) - .then(res => handle.send(reply + res)); - } else { - console.log(`Unrecognized command ${cmd}`); - handle.send(reply); - } - } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { - const kind = msg[1], - hist = parse(msg.slice(7)); - this.websocketTimeout(`proj${kind}`, 'reset'); - this.drawProjection(kind, hist); - } else if (msg.slice(0, 5) === 'CTRL:') { - const ctrl = parse(msg.slice(5)) || {}; - let resized = false; - if ((ctrl.title !== undefined) && (typeof document !== 'undefined')) - document.title = ctrl.title; - if (ctrl.x && ctrl.y && typeof window !== 'undefined') { - window.moveTo(ctrl.x, ctrl.y); - resized = true; - } - if (ctrl.w && ctrl.h) { - this.resizeBrowser(Number.parseInt(ctrl.w), Number.parseInt(ctrl.h)); - resized = true; - } - if (ctrl.cw && ctrl.ch && isFunc(this.setFixedCanvasSize)) { - this._online_fixed_size = this.setFixedCanvasSize(Number.parseInt(ctrl.cw), Number.parseInt(ctrl.ch), true); - resized = true; - } - const kinds = ['Menu', 'StatusBar', 'Editor', 'ToolBar', 'ToolTips']; - kinds.forEach(kind => { - if (ctrl[kind] !== undefined) - this.showSection(kind, ctrl[kind] === '1'); - }); + this.yaxis = yaxis; + this.ymin = ymin; + this.ymax = ymax; - if (ctrl.edit) { - const obj_painter = this.findSnap(ctrl.edit); - if (obj_painter) { - this.showSection('Editor', true) - .then(() => this.producePadEvent('select', obj_painter.getPadPainter(), obj_painter)); - } - } + this.zaxis = zaxis; + this.zmin = zmin; + this.zmax = zmax; - if (ctrl.winstate && typeof window !== 'undefined') { - if (ctrl.winstate === 'iconify') - window.blur(); - else - window.focus(); - } + if (hpainter?.check_pad_range) { + delete hpainter.check_pad_range; + const ndim = hpainter.getDimension(); + this.applyAxisZoom('x'); + if (ndim > 1) + this.applyAxisZoom('y'); + if (ndim > 2) + this.applyAxisZoom('z'); + } - if (resized) - this.sendResized(true); - } else - console.log(`unrecognized msg ${msg}`); - } + if (hpainter && !hpainter._checked_zooming) { + hpainter._checked_zooming = true; - /** @summary Send RESIZED message to client to inform about changes in canvas/window geometry - * @private */ - sendResized(force) { - if (!this.pad || (typeof window === 'undefined')) - return; - const cw = this.getPadWidth(), ch = this.getPadHeight(), - wx = window.screenLeft, wy = window.screenTop, - ww = window.outerWidth, wh = window.outerHeight, - fixed = this._online_fixed_size ? 1 : 0; - if (!force) { - force = (cw > 0) && (ch > 0) && ((this.pad.fCw !== cw) || (this.pad.fCh !== ch)); - if (force) { - this.pad.fCw = cw; - this.pad.fCh = ch; + if (hpainter.options.minimum !== kNoZoom) { + this.zoom_zmin = hpainter.options.minimum; + this.zoom_zmax = this.zmax; + } + if (hpainter.options.maximum !== kNoZoom) { + this.zoom_zmax = hpainter.options.maximum; + this.zoom_zmin ??= this.zmin; } } - if (force) - this.sendWebsocket(`RESIZED:${JSON.stringify([wx, wy, ww, wh, cw, ch, fixed])}`); } - /** @summary Handle pad button click event */ - clickPadButton(funcname, evnt) { - if (funcname === 'ToggleGed') - return this.activateGed(this, null, 'toggle'); - if (funcname === 'ToggleStatus') - return this.activateStatusBar('toggle'); - return super.clickPadButton(funcname, evnt); + /** @summary Configure secondary frame axes ranges */ + setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { + if (second_x) { + this.x2axis = xaxis; + this.x2min = xmin; + this.x2max = xmax; + } + if (second_y) { + this.y2axis = yaxis; + this.y2min = ymin; + this.y2max = ymax; + } } - /** @summary Returns true if event status shown in the canvas */ - hasEventStatus() { - if (this.testUI5()) - return false; - if (this.brlayout) - return this.brlayout.hasStatus(); - return getHPainter()?.hasStatusLine() ?? false; + /** @summary Returns associated axis object */ + getAxis(name) { + switch (name) { + case 'x': return this.xaxis; + case 'y': return this.yaxis; + case 'z': return this.zaxis; + case 'x2': return this.x2axis; + case 'y2': return this.y2axis; + } + return null; } - /** @summary Check if status bar can be toggled + /** @summary Apply axis zooming from pad user range * @private */ - canStatusBar() { - return this.testUI5() || this.brlayout || getHPainter(); - } + applyPadUserRange(pad, name) { + if (!pad) + return; - /** @summary Show/toggle event status bar - * @private */ - activateStatusBar(state) { - if (this.testUI5()) + // seems to be, not always user range calculated + let umin = pad[`fU${name}min`], + umax = pad[`fU${name}max`], + eps = 1e-7; + + if (name === 'x') { + if ((Math.abs(pad.fX1) > eps) || (Math.abs(pad.fX2 - 1) > eps)) { + const dx = pad.fX2 - pad.fX1; + umin = pad.fX1 + dx * pad.fLeftMargin; + umax = pad.fX2 - dx * pad.fRightMargin; + } + } else if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { + const dy = pad.fY2 - pad.fY1; + umin = pad.fY1 + dy * pad.fBottomMargin; + umax = pad.fY2 - dy * pad.fTopMargin; + } + + if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax - 1) < eps)) return; - if (this.brlayout) - this.brlayout.createStatusLine(23, state); - else - getHPainter()?.createStatusLine(23, state); - this.processChanges('sbits', this); - } - /** @summary Show online canvas status - * @private */ - showCanvasStatus(...msgs) { - if (this.testUI5()) return; + if (pad[`fLog${name}`] > 0) { + umin = Math.exp(umin * Math.log(10)); + umax = Math.exp(umax * Math.log(10)); + } - const br = this.brlayout || getHPainter()?.brlayout; + const aname = !this.#swap_xy ? name : (name === 'x' ? 'y' : 'x'), + smin = this[`scale_${aname}min`], + smax = this[`scale_${aname}max`]; - br?.showStatus(...msgs); - } + eps = (smax - smin) * 1e-7; - /** @summary Returns true if GED is present on the canvas */ - hasGed() { - if (this.testUI5()) return false; - return this.brlayout?.hasContent() ?? false; + if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { + this[`zoom_${aname}min`] = umin; + this[`zoom_${aname}max`] = umax; + } } - /** @summary Function used to de-activate GED - * @private */ - removeGed() { - if (this.testUI5()) return; + /** @summary Apply zooming from TAxis attributes */ + applyAxisZoom(name) { + if (this.zoomChangedInteractive(name)) + return; + this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - this.registerForPadEvents(null); + const axis = this.getAxis(name); - if (this.ged_view) { - this.ged_view.getController().cleanupGed(); - this.ged_view.destroy(); - delete this.ged_view; + if (axis?.TestBit(EAxisBits.kAxisRange)) { + if ((axis.fFirst !== axis.fLast) && ((axis.fFirst > 1) || (axis.fLast < axis.fNbins))) { + this[`zoom_${name}min`] = axis.fFirst > 1 ? axis.GetBinLowEdge(axis.fFirst) : axis.fXmin; + this[`zoom_${name}max`] = axis.fLast < axis.fNbins ? axis.GetBinLowEdge(axis.fLast + 1) : axis.fXmax; + // reset user range for main painter + axis.SetBit(EAxisBits.kAxisRange, false); + axis.fFirst = 1; + axis.fLast = axis.fNbins; + } } - this.brlayout?.deleteContent(true); - this.processChanges('sbits', this); } - /** @summary Get view data for ui5 panel + /** @summary Create x,y objects which maps user coordinates into pixels + * @desc While only first painter really need such object, all others just reuse it + * following functions are introduced + * this.GetBin[X/Y] return bin coordinate + * this.[x,y] these are d3.scale objects + * this.gr[x,y] converts root scale into graphical value * @private */ - getUi5PanelData(/* panel_name */) { - return { jsroot: { settings, create: create$1, parse, toJSON, loadScript, EAxisBits, getColorExec } }; - } + createXY(opts) { + this.cleanXY(); // remove all previous configurations - /** @summary Function used to activate GED - * @return {Promise} when GED is there - * @private */ - async activateGed(objpainter, kind, mode) { - if (this.testUI5() || !this.brlayout) - return false; + if (!opts) + opts = { ndim: 1 }; - if (this.brlayout.hasContent()) { - if ((mode === 'toggle') || (mode === false)) - this.removeGed(); - else - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); + this.#swap_xy = opts.swap_xy || false; + this.#reverse_x = opts.reverse_x || false; + this.#reverse_y = opts.reverse_y || false; - return true; - } + this.logx = this.logy = 0; - if (mode === false) - return false; + const w = this.getFrameWidth(), h = this.getFrameHeight(), + pp = this.getPadPainter(), pad = pp.getRootPad(), + pad_logx = pad.fLogx, + pad_logy = (opts.ndim === 1 ? pad.fLogv : undefined) ?? pad.fLogy; - const btns = this.brlayout.createBrowserBtns(); + this.scales_ndim = opts.ndim; - ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); + this.scale_xmin = this.xmin; + this.scale_xmax = this.xmax; - ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); + this.scale_ymin = this.ymin; + this.scale_ymax = this.ymax; - ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') - .style('margin', '3px').on('click', () => this.removeGed()); + if (opts.extra_y_space) { + const log_scale = this.#swap_xy ? pad_logx : pad_logy; + if (log_scale && (this.scale_ymax > 0)) + this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); + else + this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; + } - // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area - this.brlayout.setBrowserContent('
Loading GED ...
'); - this.brlayout.setBrowserTitle('GED'); - this.brlayout.toggleBrowserKind(kind || 'float'); + if (opts.check_pad_range) { + // take zooming out of pad or axis attributes + this.applyAxisZoom('x'); + if (opts.ndim > 1) + this.applyAxisZoom('y'); + if (opts.ndim > 2) + this.applyAxisZoom('z'); - return new Promise(resolveFunc => { - loadOpenui5().then(sap => { - select('#ged_placeholder').text(''); + // Use configured pad range - only when main histogram drawn with SAME draw option + if (opts.check_pad_range === 'pad_range') { + this.applyPadUserRange(pad, 'x'); + this.applyPadUserRange(pad, 'y'); + } + } - sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { - const oModel = new JSONModel({ handle: null }); + if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { + this.zoom_xmin = opts.zoom_xmin; + this.zoom_xmax = opts.zoom_xmax; + } - XMLView.create({ - viewName: 'rootui5.canv.view.Ged', - viewData: this.getUi5PanelData('Ged') - }).then(oGed => { - oGed.setModel(oModel); + if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { + this.zoom_ymin = opts.zoom_ymin; + this.zoom_ymax = opts.zoom_ymax; + } - oGed.placeAt('ged_placeholder'); + let orig_x = true, orig_y = true; - this.ged_view = oGed; + if (this.zoom_xmin !== this.zoom_xmax) { + this.scale_xmin = this.zoom_xmin; + this.scale_xmax = this.zoom_xmax; + orig_x = false; + } - // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself - this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); + if (this.zoom_ymin !== this.zoom_ymax) { + this.scale_ymin = this.zoom_ymin; + this.scale_ymax = this.zoom_ymax; + orig_y = false; + } - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); + // projection should be assigned + this.recalculateRange(opts.Proj, orig_x, orig_y); - console.log('activate GED'); - this.processChanges('sbits', this); + this.x_handle = new TAxisPainter(pp, this.xaxis, true); + this.x_handle.setHistPainter(opts.hist_painter, 'x'); - resolveFunc(true); - }); - }); - }); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x, + log: this.#swap_xy ? pad_logy : pad_logx, + ignore_labels: this.x_ignore_labels, + noexp_changed: this.x_noexp_changed, + fixed_ticks: opts.xticks, + symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, + log_min_nz: opts.xmin_nz && (opts.xmin_nz <= this.xmax) ? 0.9 * opts.xmin_nz : 0, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: logminfactorX }); - } - - /** @summary Show section of canvas like menu or editor */ - async showSection(that, on) { - if (this.testUI5()) - return false; - switch (that) { - case 'Menu': break; - case 'StatusBar': this.activateStatusBar(on); break; - case 'Editor': return this.activateGed(this, null, !!on); - case 'ToolBar': break; - case 'ToolTips': this.setTooltipAllowed(on); break; - } - return true; - } + this.x_handle.assignFrameMembers(this, 'x'); - /** @summary Complete handling of online canvas drawing - * @private */ - completeCanvasSnapDrawing() { - if (!this.pad) return; + this.y_handle = new TAxisPainter(pp, this.yaxis, true); + this.y_handle.setHistPainter(opts.hist_painter, 'y'); - this.addPadInteractive(); + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + value_axis: opts.ndim === 1, + reverse: this.#reverse_y, + log: this.#swap_xy ? pad_logx : pad_logy, + ignore_labels: this.y_ignore_labels, + noexp_changed: this.y_noexp_changed, + fixed_ticks: opts.yticks, + symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, + log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5 * opts.ymin_nz : 0, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + logminfactor: logminfactorY + }); - if ((typeof document !== 'undefined') && !this.embed_canvas && this._websocket) - document.title = this.pad.fTitle; + this.y_handle.assignFrameMembers(this, 'y'); - if (this._all_sections_showed) return; - this._all_sections_showed = true; - this.showSection('Menu', this.pad.TestBit(kMenuBar)); - this.showSection('StatusBar', this.pad.TestBit(kShowEventStatus)); - this.showSection('ToolBar', this.pad.TestBit(kShowToolBar)); - this.showSection('Editor', this.pad.TestBit(kShowEditor)); - this.showSection('ToolTips', this.pad.TestBit(kShowToolTips) || this._highlight_connect); + this.setRootPadRange(pad); } - /** @summary Handle highlight in canvas - deliver information to server + /** @summary Create x,y objects for drawing of second axes * @private */ - processHighlightConnect(hints) { - if (!hints || hints.length === 0 || !this._highlight_connect || - !this._websocket || this.doingDraw() || !this._websocket.canSend(2)) return; - - const hint = hints[0] || hints[1]; - if (!hint || !hint.painter || !hint.painter.snapid || !hint.user_info) return; - const pp = hint.painter.getPadPainter() || this; - if (!pp.snapid) return; - - const arr = [pp.snapid, hint.painter.snapid, '0', '0']; + createXY2(opts) { + if (!opts) + opts = { ndim: this.scales_ndim ?? 1 }; - if ((hint.user_info.binx !== undefined) && (hint.user_info.biny !== undefined)) { - arr[2] = hint.user_info.binx.toString(); - arr[3] = hint.user_info.biny.toString(); - } else if (hint.user_info.bin !== undefined) - arr[2] = hint.user_info.bin.toString(); + this.#reverse_x2 = opts.reverse_x || false; + this.#reverse_y2 = opts.reverse_y || false; + this.logx2 = this.logy2 = 0; - const msg = JSON.stringify(arr); + const w = this.getFrameWidth(), h = this.getFrameHeight(), + pp = this.getPadPainter(), pad = pp.getRootPad(); - if (this._last_highlight_msg !== msg) { - this._last_highlight_msg = msg; - this.sendWebsocket(`HIGHLIGHT:${msg}`); + if (opts.second_x) { + this.scale_x2min = this.x2min; + this.scale_x2max = this.x2max; } - } - - /** @summary Method informs that something was changed in the canvas - * @desc used to update information on the server (when used with web6gui) - * @private */ - processChanges(kind, painter, subelem) { - // check if we could send at least one message more - for some meaningful actions - if (!this._websocket || this._readonly || !this._websocket.canSend(2) || !isStr(kind)) return; - - let msg = ''; - if (!painter) painter = this; - switch (kind) { - case 'sbits': - msg = 'STATUSBITS:' + this.getStatusBits(); - break; - case 'frame': // when changing frame - case 'zoom': // when changing zoom inside frame - if (!isFunc(painter.getWebPadOptions)) - painter = painter.getPadPainter(); - if (isFunc(painter.getWebPadOptions)) - msg = 'OPTIONS6:' + painter.getWebPadOptions('only_this'); - break; - case 'padpos': // when changing pad position - msg = 'OPTIONS6:' + painter.getWebPadOptions('with_subpads'); - break; - case 'drawopt': - if (painter.snapid) - msg = 'DRAWOPT:' + JSON.stringify([painter.snapid.toString(), painter.getDrawOpt() || '']); - break; - case 'pave_moved': { - const info = createWebObjectOptions(painter); - if (info) msg = 'PRIMIT6:' + toJSON(info); - break; - } - case 'logx': - case 'logy': - case 'logz': { - const pp = painter.getPadPainter(); - if (pp?.snapid && pp?.pad) { - const name = 'SetLog' + kind[3], value = pp.pad['fLog' + kind[3]]; - painter = pp; - kind = `exec:${name}(${value})`; - } - break; - } + if (opts.second_y) { + this.scale_y2min = this.y2min; + this.scale_y2max = this.y2max; } - if (!msg && isFunc(painter?.getSnapId) && (kind.slice(0, 5) === 'exec:')) { - const snapid = painter.getSnapId(subelem); - if (snapid) { - msg = 'PRIMIT6:' + toJSON({ _typename: 'TWebObjectOptions', - snapid, opt: kind.slice(5), fcust: 'exec', fopt: [] }); - } + if (opts.extra_y_space && opts.second_y) { + const log_scale = this.#swap_xy ? pad.fLogx : pad.fLogy; + if (log_scale && (this.scale_y2max > 0)) + this.scale_y2max = Math.exp(Math.log(this.scale_y2max) * 1.1); + else + this.scale_y2max += (this.scale_y2max - this.scale_y2min) * 0.1; } - if (msg) { - // console.log(`Sending ${msg.length} ${msg.slice(0,40)}`); - this._websocket.send(msg); - } else - console.log(`Unprocessed changes ${kind} for painter of ${painter?.getObject()?._typename} subelem ${subelem}`); - } + if ((this.zoom_x2min !== this.zoom_x2max) && opts.second_x) { + this.scale_x2min = this.zoom_x2min; + this.scale_x2max = this.zoom_x2max; + } - /** @summary Select active pad on the canvas */ - selectActivePad(pad_painter, obj_painter, click_pos) { - if (!this.snapid || !pad_painter) return; // only interactive canvas + if ((this.zoom_y2min !== this.zoom_y2max) && opts.second_y) { + this.scale_y2min = this.zoom_y2min; + this.scale_y2max = this.zoom_y2max; + } - let arg = null, ischanged = false; - const is_button = pad_painter.matchObjectType(clTButton); + if (opts.second_x) { + this.x2_handle = new TAxisPainter(pp, this.x2axis, true); + this.x2_handle.setHistPainter(opts.hist_painter, 'x'); - if (pad_painter.snapid && this._websocket) - arg = { _typename: 'TWebPadClick', padid: pad_painter.snapid.toString(), objid: '', x: -1, y: -1, dbl: false }; + this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x2, + log: this.#swap_xy ? pad.fLogy : pad.fLogx, + ignore_labels: this.x2_ignore_labels, + noexp_changed: this.x2_noexp_changed, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: logminfactorX + }); - if (!pad_painter.is_active_pad && !is_button) { - ischanged = true; - this.forEachPainterInPad(pp => pp.drawActiveBorder(null, pp === pad_painter), 'pads'); + this.x2_handle.assignFrameMembers(this, 'x2'); } - if ((obj_painter?.snapid !== undefined) && arg) { - ischanged = true; - arg.objid = obj_painter.snapid.toString(); - } + if (opts.second_y) { + this.y2_handle = new TAxisPainter(pp, this.y2axis, true); + this.y2_handle.setHistPainter(opts.hist_painter, 'y'); - if (click_pos && arg) { - ischanged = true; - arg.x = Math.round(click_pos.x || 0); - arg.y = Math.round(click_pos.y || 0); - if (click_pos.dbl) arg.dbl = true; - } + this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + reverse: this.#reverse_y2, + log: this.#swap_xy ? pad.fLogx : pad.fLogy, + ignore_labels: this.y2_ignore_labels, + noexp_changed: this.y2_noexp_changed, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, + logminfactor: logminfactorY + }); - if (arg && (ischanged || is_button)) - this.sendWebsocket('PADCLICKED:' + toJSON(arg)); + this.y2_handle.assignFrameMembers(this, 'y2'); + } } - /** @summary Return actual TCanvas status bits */ - getStatusBits() { - let bits = 0; - if (this.hasEventStatus()) bits |= kShowEventStatus; - if (this.hasGed()) bits |= kShowEditor; - if (this.isTooltipAllowed()) bits |= kShowToolTips; - if (this.use_openui) bits |= kMenuBar; - return bits; + /** @summary Return functions to create x/y points based on coordinates + * @desc In default case returns frame painter itself + * @private */ + getGrFuncs(second_x, second_y) { + const use_x2 = second_x && this.grx2, + use_y2 = second_y && this.gry2; + if (!use_x2 && !use_y2) + return this; + + return { + use_x2, + grx: use_x2 ? this.grx2 : this.grx, + logx: this.logx, + x_handle: use_x2 ? this.x2_handle : this.x_handle, + scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, + scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, + use_y2, + gry: use_y2 ? this.gry2 : this.gry, + logy: this.logy, + y_handle: use_y2 ? this.y2_handle : this.y_handle, + scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, + scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, + fp: this, + _remap(name) { + if ((name === 'x') && this.use_x2) + return 'x2'; + if ((name === 'y') && this.use_y2) + return 'y2'; + return name; + }, + swap_xy() { return this.fp.swap_xy(); }, + isAxisZoomed(name) { return this.fp.isAxisZoomed(this._remap(name)); }, + revertAxis(name, v) { return this.fp.revertAxis(this._remap(name), v); }, + axisAsText(name, v) { return this.fp.axisAsText(this._remap(name), v); }, + getFrameWidth() { return this.fp.getFrameWidth(); }, + getFrameHeight() { return this.fp.getFrameHeight(); } + }; } - /** @summary produce JSON for TCanvas, which can be used to display canvas once again */ - produceJSON() { - const canv = this.getObject(), - fill0 = (canv.fFillStyle === 0), - axes = []; + /** @summary Set selected range back to TPad object + * @private */ + setRootPadRange(pad, is3d) { + if (!pad || !this.ranges_set) + return; - if (fill0) canv.fFillStyle = 1001; + if (is3d) { + // this is fake values, algorithm should be copied from TView3D class of ROOT + pad.fUxmin = pad.fUymin = -0.9; + pad.fUxmax = pad.fUymax = 0.9; + } else { + pad.fLogx = this.#swap_xy ? this.logy : this.logx; + pad.fUxmin = pad.fLogx ? Math.log10(this.scale_xmin) : this.scale_xmin; + pad.fUxmax = pad.fLogx ? Math.log10(this.scale_xmax) : this.scale_xmax; + pad.fLogy = this.#swap_xy ? this.logx : this.logy; + pad.fUymin = pad.fLogy ? Math.log10(this.scale_ymin) : this.scale_ymin; + pad.fUymax = pad.fLogy ? Math.log10(this.scale_ymax) : this.scale_ymax; + } - // write selected range into TAxis properties - this.forEachPainterInPad(pp => { - const main = pp.getMainPainter(), - fp = pp.getFramePainter(); - if (!isFunc(main?.getHisto) || !isFunc(main?.getDimension)) return; + const rx = pad.fUxmax - pad.fUxmin, + ry = pad.fUymax - pad.fUymin, + mx = Math.max(0.001, 1 - pad.fLeftMargin - pad.fRightMargin), + my = Math.max(0.001, 1 - pad.fBottomMargin - pad.fTopMargin); - const hist = main.getHisto(), - ndim = main.getDimension(); - if (!hist?.fXaxis) return; + pad.fX1 = pad.fUxmin - rx / mx * pad.fLeftMargin; + pad.fX2 = pad.fUxmax + rx / mx * pad.fRightMargin; + pad.fY1 = pad.fUymin - ry / my * pad.fBottomMargin; + pad.fY2 = pad.fUymax + ry / my * pad.fTopMargin; + } - const setAxisRange = (name, axis) => { - if (fp?.zoomChangedInteractive(name)) { - axes.push({ axis, f: axis.fFirst, l: axis.fLast, b: axis.fBits }); - axis.fFirst = main.getSelectIndex(name, 'left'); - axis.fLast = main.getSelectIndex(name, 'right'); - const has_range = (axis.fFirst > 0) || (axis.fLast < axis.fNbins); - if (has_range !== axis.TestBit(EAxisBits.kAxisRange)) - axis.InvertBit(EAxisBits.kAxisRange); - } - }; + /** @summary Draw axes grids + * @desc Called immediately after axes drawing */ + drawGrids(draw_grids) { + const layer = this.getFrameSvg().selectChild('.axis_layer'); - setAxisRange('x', hist.fXaxis); - if (ndim > 1) setAxisRange('y', hist.fYaxis); - if (ndim > 2) setAxisRange('z', hist.fZaxis); - }, 'pads'); + layer.selectAll('.xgrid').remove(); + layer.selectAll('.ygrid').remove(); - if (!this.normal_canvas) { - // fill list of primitives from painters - this.forEachPainterInPad(p => { - // ignore all secondary painters - if (p.isSecondary()) - return; - const subobj = p.getObject(); - if (subobj?._typename) - canv.fPrimitives.Add(subobj, p.getDrawOpt()); - }, 'objects'); - } + const pp = this.getPadPainter(), + pad = pp?.getRootPad(true), + h = this.getFrameHeight(), + w = this.getFrameWidth(), + grid_style = gStyle.fGridStyle; - // const fp = this.getFramePainter(); - // fp?.setRootPadRange(this.getRootPad()); + // add a grid on x axis, if the option is set + if (pad?.fGridx && draw_grids && this.x_handle?.ticks) { + const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('x')?.fAxisColor ?? 1); + let gridx = ''; + this.x_handle.ticks.forEach(pos => { + gridx += this.#swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; + }); - const res = toJSON(canv); + layer.append('svg:path') + .attr('class', 'xgrid') + .attr('d', gridx) + .style('stroke', this.getColor(colid) || 'black') + .style('stroke-width', gStyle.fGridWidth) + .style('stroke-dasharray', getSvgLineStyle(grid_style)); + } - if (fill0) canv.fFillStyle = 0; - - axes.forEach(e => { - e.axis.fFirst = e.f; - e.axis.fLast = e.l; - e.axis.fBits = e.b; - }); - - if (!this.normal_canvas) - canv.fPrimitives.Clear(); - - return res; - } - - /** @summary resize browser window */ - resizeBrowser(fullW, fullH) { - if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) - return; + // add a grid on y axis, if the option is set + if (pad?.fGridy && draw_grids && this.y_handle?.ticks) { + const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('y')?.fAxisColor ?? 1); + let gridy = ''; + this.y_handle.ticks.forEach(pos => { + gridy += this.#swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; + }); - // workaround for qt5-based display where inner window size is used - if (browser.qt5 && fullW > 100 && fullH > 60) { - fullW -= 3; - fullH -= 30; + layer.append('svg:path') + .attr('class', 'ygrid') + .attr('d', gridy) + .style('stroke', this.getColor(colid) || 'black') + .style('stroke-width', gStyle.fGridWidth) + .style('stroke-dasharray', getSvgLineStyle(grid_style)); } - - this._websocket?.resizeWindow(fullW, fullH); } - /** @summary draw TCanvas */ - static async draw(dom, can, opt) { - const nocanvas = !can; - if (nocanvas) can = create$1(clTCanvas); - - const painter = new TCanvasPainter(dom, can); - painter.checkSpecialsInPrimitives(can); - - if (!nocanvas && can.fCw && can.fCh && !painter.isBatchMode()) { - const rect0 = painter.selectDom().node().getBoundingClientRect(); - if (!rect0.height && (rect0.width > 0.1*can.fCw)) { - painter.selectDom().style('width', can.fCw+'px').style('height', can.fCh+'px'); - painter._fixed_size = true; - } - } - - painter.decodeOptions(opt); - painter.normal_canvas = !nocanvas; - painter.createCanvasSvg(0); - - painter.addPadButtons(); - - if (nocanvas && opt.indexOf('noframe') < 0) - directDrawTFrame(dom, null); + /** @summary Converts 'raw' axis value into text */ + axisAsText(axis, value) { + const handle = this[`${axis}_handle`]; - // select global reference - required for keys handling - selectActivePad({ pp: painter, active: true }); + if (handle) + return handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']); - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.showPadButtons(); - return painter; - }); + return value.toPrecision(4); } -} // class TCanvasPainter - + /** @summary Identify if requested axes are drawn + * @desc Checks if x/y axes are drawn. Also if second side is already there */ + hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : this.#axes2_drawn; } -/** @summary Ensure TCanvas and TFrame for the painter object - * @param {Object} painter - painter object to process - * @param {string|boolean} frame_kind - false for no frame or '3d' for special 3D mode - * @desc Assign dom, creates TCanvas if necessary, add to list of pad painters */ -async function ensureTCanvas(painter, frame_kind) { - if (!painter) - return Promise.reject(Error('Painter not provided in ensureTCanvas')); + /** @summary draw axes, + * @return {Promise} which ready when drawing is completed */ + async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { + this.cleanAxesDrawings(); - // simple check - if canvas there, can use painter - const noframe = (frame_kind === false) || (frame_kind === '3d') ? 'noframe' : '', - promise = painter.getCanvSvg().empty() - ? TCanvasPainter.draw(painter.getDom(), null, noframe) - : Promise.resolve(true); + if ((this.xmin === this.xmax) || (this.ymin === this.ymax)) + return false; - return promise.then(() => { - if ((frame_kind !== false) && painter.getFrameSvg().selectChild('.main_layer').empty() && !painter.getFramePainter()) - directDrawTFrame(painter.getDom(), null, frame_kind); + if (AxisPos === undefined) + AxisPos = 0; - painter.addToPadPrimitives(); - return painter; - }); -} + const layer = this.getFrameSvg().selectChild('.axis_layer'), + w = this.getFrameWidth(), + h = this.getFrameHeight(), + pp = this.getPadPainter(), + pad = pp.getRootPad(true), + draw_grids = enable_grids && (pad?.fGridx || pad?.fGridy); -/** @summary draw TPad snapshot from TWebCanvas - * @private */ -async function drawTPadSnapshot(dom, snap /*, opt */) { - const can = create$1(clTCanvas), - painter = new TCanvasPainter(dom, can); - painter.normal_canvas = false; - painter.addPadButtons(); + this.x_handle.invert_side = (AxisPos >= 10); + this.x_handle.lbls_both_sides = !this.x_handle.invert_side && (pad?.fTickx > 1); // labels on both sides + this.x_handle.has_obstacle = has_x_obstacle; - return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { - painter.confirmDraw(); - painter.showPadButtons(); - return painter; - }); -} + this.y_handle.invert_side = ((AxisPos % 10) === 1); + this.y_handle.lbls_both_sides = !this.y_handle.invert_side && (pad?.fTicky > 1); // labels on both sides + this.y_handle.has_obstacle = has_y_obstacle; -/** @summary draw TFrame object - * @private */ -async function drawTFrame(dom, obj, opt) { - const fp = new TFramePainter(dom, obj); - fp.mode3d = opt === '3d'; - return ensureTCanvas(fp, false).then(() => fp.redraw()); -} + const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, + draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; -var TCanvasPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TCanvasPainter: TCanvasPainter, -TPadPainter: TPadPainter, -drawTFrame: drawTFrame, -drawTPadSnapshot: drawTPadSnapshot, -ensureTCanvas: ensureTCanvas -}); + if ((!disable_x_draw || !disable_y_draw) && pp.isFastDrawing()) + disable_x_draw = disable_y_draw = true; -const kTakeStyle = BIT(17); + let pr = Promise.resolve(true); -/** @summary Returns true if stat box on default place and can be adjusted - * @private */ -function isDefaultStatPosition(pt) { - const test = (v1, v2) => (Math.abs(v1-v2) < 1e-3); - return test(pt.fX1NDC, gStyle.fStatX - gStyle.fStatW) && - test(pt.fY1NDC, gStyle.fStatY - gStyle.fStatH) && - test(pt.fX2NDC, gStyle.fStatX) && - test(pt.fY2NDC, gStyle.fStatY); -} + if (!disable_x_draw || !disable_y_draw || draw_grids) { + draw_vertical.optionLeft = draw_vertical.invert_side; // text align -/** - * @summary painter for TPave-derived classes - * - * @private - */ + const can_adjust_frame = !shrink_forbidden && settings.CanAdjustFrame, + pr1 = draw_horiz.drawAxis(layer, w, h, + draw_horiz.invert_side ? null : `translate(0,${h})`, + pad?.fTickx ? -h : 0, disable_x_draw, + undefined, false, pp.getPadHeight() - h - this.getFrameY()), -class TPavePainter extends ObjectPainter { + pr2 = draw_vertical.drawAxis(layer, w, h, + draw_vertical.invert_side ? `translate(${w})` : null, + pad?.fTicky ? w : 0, disable_y_draw, + draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} pave - TPave-based object */ - constructor(dom, pave) { - super(dom, pave); - this.Enabled = true; - this.UseContextMenu = true; - } + pr = Promise.all([pr1, pr2]).then(() => { + this.drawGrids(draw_grids); - /** @summary Autoplace legend on the frame - * @return {Promise} with boolean flag if position was changed */ - async autoPlaceLegend(pt, pad, keep_origin) { - const main_svg = this.getFrameSvg().selectChild('.main_layer'); + if (!can_adjust_frame) + return; - let svg_code = main_svg.node().outerHTML; + let shrink = 0.0; + const ypos = draw_vertical.position; - svg_code = compressSVG(svg_code); + if ((-0.2 * w < ypos) && (ypos < 0)) { + shrink = -ypos / w + 0.001; + this.#shrink_frame_left += shrink; + } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.#shrink_frame_left > 0) && (ypos / w > this.#shrink_frame_left)) { + shrink = -this.#shrink_frame_left; + this.#shrink_frame_left = 0.0; + } - svg_code = ' this.drawAxes(true)); + } + }); + } - const lm = pad?.fLeftMargin ?? gStyle.fPadLeftMargin, - rm = pad?.fRightMargin ?? gStyle.fPadRightMargin, - tm = pad?.fTopMargin ?? gStyle.fPadTopMargin, - bm = pad?.fBottomMargin ?? gStyle.fPadBottomMargin; + return pr.then(() => { + if (!shrink_forbidden) + this.#axes_drawn = true; + return true; + }); + } - return svgToImage(svg_code).then(canvas => { - if (!canvas) return false; + /** @summary draw second axes (if any) */ + async drawAxes2(second_x, second_y) { + const layer = this.getFrameSvg().selectChild('.axis_layer'), + w = this.getFrameWidth(), + h = this.getFrameHeight(), + pp = this.getPadPainter(), + pad = pp.getRootPad(true); - let nX = 100, nY = 100; - const context = canvas.getContext('2d'), - arr = context.getImageData(0, 0, canvas.width, canvas.height).data, - boxW = Math.floor(canvas.width / nX), boxH = Math.floor(canvas.height / nY), - raster = new Array(nX*nY); + if (second_x) { + this.x2_handle.invert_side = true; + this.x2_handle.lbls_both_sides = false; + this.x2_handle.has_obstacle = false; + } - if (arr.length !== canvas.width * canvas.height * 4) { - console.log(`Image size missmatch in TLegend autoplace ${arr.length} expected ${canvas.width*canvas.height * 4}`); - nX = nY = 0; - } + if (second_y) { + this.y2_handle.invert_side = true; + this.y2_handle.lbls_both_sides = false; + } - for (let ix = 0; ix < nX; ++ix) { - const px1 = ix * boxW, px2 = px1 + boxW; - for (let iy = 0; iy < nY; ++iy) { - const py1 = iy * boxH, py2 = py1 + boxH; - let filled = 0; + let draw_horiz = this.#swap_xy ? this.y2_handle : this.x2_handle, + draw_vertical = this.#swap_xy ? this.x2_handle : this.y2_handle; - for (let x = px1; (x < px2) && !filled; ++x) { - for (let y = py1; y < py2; ++y) { - const indx = (y * canvas.width + x) * 4; - if (arr[indx] || arr[indx+1] || arr[indx+2] || arr[indx+3]) { - filled = 1; - break; - } - } - } - raster[iy * nX + ix] = filled; - } - } + if ((draw_horiz || draw_vertical) && pp.isFastDrawing()) + draw_horiz = draw_vertical = null; - const legWidth = 0.3 / Math.max(0.2, (1 - lm - rm)), - legHeight = Math.min(0.5, Math.max(0.1, pt.fPrimitives.arr.length*0.05)) / Math.max(0.2, (1 - tm - bm)), - needW = Math.round(legWidth * nX), needH = Math.round(legHeight * nY), + let pr1, pr2; - test = (x, y) => { - for (let ix = x; ix < x + needW; ++ix) { - for (let iy = y; iy < y + needH; ++iy) - if (raster[iy * nX + ix]) return false; - } - return true; - }; + if (draw_horiz) { + pr1 = draw_horiz.drawAxis(layer, w, h, + draw_horiz.invert_side ? null : `translate(0,${h})`, + pad?.fTickx ? -h : 0, false, + undefined, false); + } - for (let ix = 0; ix < (nX - needW); ++ix) { - for (let iy = nY-needH - 1; iy >= 0; --iy) { - if (test(ix, iy)) { - pt.fX1NDC = lm + ix / nX * (1 - lm - rm); - pt.fX2NDC = pt.fX1NDC + legWidth * (1 - lm - rm); - pt.fY2NDC = 1 - tm - iy/nY * (1 - bm - tm); - pt.fY1NDC = pt.fY2NDC - legHeight * (1 - bm - tm); - return true; - } - } - } - }).then(res => { - if (res || keep_origin) - return res; + if (draw_vertical) { + draw_vertical.optionLeft = draw_vertical.invert_side; + pr2 = draw_vertical.drawAxis(layer, w, h, + draw_vertical.invert_side ? `translate(${w})` : null, + pad?.fTicky ? w : 0, false, + draw_vertical.invert_side ? 0 : this.#frame_x, false); + } - pt.fX1NDC = Math.max(lm ?? 0, pt.fX2NDC - 0.3); - pt.fX2NDC = Math.min(pt.fX1NDC + 0.3, 1 - rm); - const h0 = Math.max(pt.fPrimitives ? pt.fPrimitives.arr.length*0.05 : 0, 0.2); - pt.fY2NDC = Math.min(1 - tm, pt.fY1NDC + h0); - pt.fY1NDC = Math.max(pt.fY2NDC - h0, bm); + return Promise.all([pr1, pr2]).then(() => { + this.#axes2_drawn = true; return true; }); } - /** @summary Draw pave and content - * @return {Promise} */ - async drawPave(arg) { - if (!this.Enabled) { - this.removeG(); - return this; - } - - const pt = this.getObject(), opt = pt.fOption.toUpperCase(), - fp = this.getFramePainter(), pp = this.getPadPainter(), - pad = pp.getRootPad(true); - let interactive_element, width, height; - if (pt.fInit === 0) { - this.stored = Object.assign({}, pt); // store coordinates to use them when updating - pt.fInit = 1; + /** @summary Update frame attributes + * @private */ + updateAttributes(force) { + const pp = this.getPadPainter(), + pad = pp?.getRootPad(true), + tframe = this.getObject(); - if ((pt._typename === clTPaletteAxis) && !pt.fX1 && !pt.fX2 && !pt.fY1 && !pt.fY2) { - if (fp) { - pt.fX1NDC = fp.fX2NDC + 0.01; - pt.fX2NDC = Math.min(0.96, fp.fX2NDC + 0.06); - pt.fY1NDC = fp.fY1NDC; - pt.fY2NDC = fp.fY2NDC; - } else { - pt.fX2NDC = 0.8; - pt.fX1NDC = 0.9; - pt.fY1NDC = 0.1; - pt.fY2NDC = 0.9; - } - } else if (opt.indexOf('NDC') >= 0) { - pt.fX1NDC = pt.fX1; pt.fX2NDC = pt.fX2; - pt.fY1NDC = pt.fY1; pt.fY2NDC = pt.fY2; - } else if (pad && (pad.fX1 === 0) && (pad.fX2 === 1) && (pad.fY1 === 0) && (pad.fY2 === 1) && isStr(arg) && (arg.indexOf('postpone') >= 0)) { - // special case when pad not yet initialized - pt.fInit = 0; // do not init until axes drawn - pt.fX1NDC = pt.fY1NDC = 0.99; - pt.fX2NDC = pt.fY2NDC = 1; - } else if (pad) { - if (pad.fLogx) { - if (pt.fX1 > 0) pt.fX1 = Math.log10(pt.fX1); - if (pt.fX2 > 0) pt.fX2 = Math.log10(pt.fX2); - } - if (pad.fLogy) { - if (pt.fY1 > 0) pt.fY1 = Math.log10(pt.fY1); - if (pt.fY2 > 0) pt.fY2 = Math.log10(pt.fY2); - } - pt.fX1NDC = (pt.fX1 - pad.fX1) / (pad.fX2 - pad.fX1); - pt.fY1NDC = (pt.fY1 - pad.fY1) / (pad.fY2 - pad.fY1); - pt.fX2NDC = (pt.fX2 - pad.fX1) / (pad.fX2 - pad.fX1); - pt.fY2NDC = (pt.fY2 - pad.fY1) / (pad.fY2 - pad.fY1); + if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { + if (!pad) { + this.fX1NDC = gStyle.fPadLeftMargin; + this.fX2NDC = 1 - gStyle.fPadRightMargin; + this.fY1NDC = gStyle.fPadBottomMargin; + this.fY2NDC = 1 - gStyle.fPadTopMargin; } else { - pt.fX1NDC = pt.fY1NDC = 0.1; - pt.fX2NDC = pt.fY2NDC = 0.9; + this.fX1NDC = pad.fLeftMargin; + this.fX2NDC = 1 - pad.fRightMargin; + this.fY1NDC = pad.fBottomMargin; + this.fY2NDC = 1 - pad.fTopMargin; } } - let promise = Promise.resolve(true); - - if ((pt._typename === clTLegend) && (this.AutoPlace || ((pt.fX1NDC === pt.fX2NDC) && (pt.fY1NDC === pt.fY2NDC)))) { - promise = this.autoPlaceLegend(pt, pad).then(res => { - delete this.AutoPlace; - if (!res) { - pt.fX1NDC = fp.fX2NDC - 0.2; pt.fX2NDC = fp.fX2NDC; - pt.fY1NDC = fp.fY2NDC - 0.1; pt.fY2NDC = fp.fY2NDC; - } - return res; - }); - } - - return promise.then(() => { - // fill stats before drawing to have coordinates early - if (this.isStats() && !this.NoFillStats && !pp._fast_drawing) { - const main = pt.$main_painter || this.getMainPainter(); + if (tframe) { + this.createAttFill({ attr: tframe }); + this.#border_mode = tframe.fBorderMode; + this.#border_size = tframe.fBorderSize; + } else if (!this.fillatt) { + if (pad?.fFrameFillColor) + this.createAttFill({ pattern: pad.fFrameFillStyle, color: pad.fFrameFillColor }); + else if (pad) + this.createAttFill({ attr: pad }); + else + this.createAttFill({ pattern: gStyle.fFrameFillStyle, color: gStyle.fFrameFillColor }); - if (isFunc(main?.fillStatistic)) { - let dostat = parseInt(pt.fOptStat), dofit = parseInt(pt.fOptFit); - if (!Number.isInteger(dostat) || pt.TestBit(kTakeStyle)) dostat = gStyle.fOptStat; - if (!Number.isInteger(dofit)|| pt.TestBit(kTakeStyle)) dofit = gStyle.fOptFit; + // force white color for the canvas frame + if (!tframe && this.fillatt.empty() && pp?.isCanvas()) + this.fillatt.setSolidColor('white'); + else if ((pad?.fFillStyle === 4000) && !this.fillatt.empty()) // special case of transpad.C macro, which set transparent pad + this.fillatt.setOpacity(0); - // we take statistic from main painter - if (main.fillStatistic(this, dostat, dofit)) { - // adjust the size of the stats box with the number of lines - let nlines = pt.fLines?.arr.length || 0; - if ((nlines > 0) && !this.moved_interactive && isDefaultStatPosition(pt)) { - // in ROOT TH2 and TH3 always add full statsh for fit parameters - const extrah = this._has_fit && (this._fit_dim > 1) ? gStyle.fStatH : 0; - // but fit parameters not used in full size calculations - if (extrah) nlines -= this._fit_cnt; - let stath = gStyle.fStatH, statw = gStyle.fStatW; - if (this._has_fit) - statw = 1.8 * gStyle.fStatW; - if ((gStyle.fStatFontSize <= 0) || (gStyle.fStatFont % 10 === 3)) - stath = nlines * 0.25 * gStyle.fStatH; - else if (gStyle.fStatFontSize < 1) - stath = nlines * gStyle.fStatFontSize; - pt.fX1NDC = Math.max(0.02, pt.fX2NDC - statw); - pt.fY1NDC = Math.max(0.02, pt.fY2NDC - stath - extrah); - } - } - } + if (pad && (pad.fFrameBorderMode || (pad.fFrameBorderSize !== 1))) { + this.#border_mode = pad.fFrameBorderMode; + this.#border_size = pad.fFrameBorderSize; } + } - const pad_rect = pp.getPadRect(), - brd = pt.fBorderSize, - noborder = opt.indexOf('NB') >= 0, - dx = (opt.indexOf('L') >= 0) ? -1 : ((opt.indexOf('R') >= 0) ? 1 : 0), - dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0); + if (!tframe && (pad?.fFrameLineColor !== undefined)) + this.createAttLine({ color: pad.fFrameLineColor, width: pad.fFrameLineWidth, style: pad.fFrameLineStyle }); + else if (tframe) + this.createAttLine({ attr: tframe, color: 'black' }); + else + this.createAttLine({ color: gStyle.fFrameLineColor, width: gStyle.fFrameLineWidth, style: gStyle.fFrameLineStyle }); + } - // container used to recalculate coordinates - this.createG(); + /** @summary Function called at the end of resize of frame + * @desc One should apply changes to the pad + * @private */ + sizeChanged() { + const pad = this.getPadPainter()?.getRootPad(true); - this._pave_x = Math.round(pt.fX1NDC * pad_rect.width); - this._pave_y = Math.round((1.0 - pt.fY2NDC) * pad_rect.height); - width = Math.round((pt.fX2NDC - pt.fX1NDC) * pad_rect.width); - height = Math.round((pt.fY2NDC - pt.fY1NDC) * pad_rect.height); + if (pad) { + pad.fLeftMargin = this.fX1NDC; + pad.fRightMargin = 1 - this.fX2NDC; + pad.fBottomMargin = this.fY1NDC; + pad.fTopMargin = 1 - this.fY2NDC; + this.setRootPadRange(pad); + } - makeTranslate(this.draw_g, this._pave_x, this._pave_y); + this.interactiveRedraw('pad', 'frame'); + } - this.createAttLine({ attr: pt, width: (brd > 0) ? pt.fLineWidth : 0 }); + /** @summary Remove all kinds of X/Y function for axes transformation */ + cleanXY() { + delete this.grx; + delete this.gry; + delete this.grz; + delete this.grx2; + delete this.gry2; - this.createAttFill({ attr: pt }); + this.x_handle?.cleanup(); + this.y_handle?.cleanup(); + this.z_handle?.cleanup(); + this.x2_handle?.cleanup(); + this.y2_handle?.cleanup(); - if (pt._typename === clTDiamond) { - const h2 = Math.round(height/2), w2 = Math.round(width/2), - dpath = `l${w2},${-h2}l${w2},${h2}l${-w2},${h2}z`; + delete this.x_handle; + delete this.y_handle; + delete this.z_handle; + delete this.x2_handle; + delete this.y2_handle; + } - if ((brd > 1) && (pt.fShadowColor > 0) && (dx || dy) && !this.fillatt.empty() && !noborder) { - this.draw_g.append('svg:path') - .attr('d', 'M0,'+(h2+brd) + dpath) - .style('fill', this.getColor(pt.fShadowColor)) - .style('stroke', this.getColor(pt.fShadowColor)) - .style('stroke-width', '1px'); - } + /** @summary remove all axes drawings */ + cleanAxesDrawings() { + this.x_handle?.removeG(); + this.y_handle?.removeG(); + this.z_handle?.removeG(); + this.x2_handle?.removeG(); + this.y2_handle?.removeG(); - interactive_element = this.draw_g.append('svg:path') - .attr('d', 'M0,'+h2 +dpath) - .call(this.fillatt.func) - .call(this.lineatt.func); + this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); + this.#axes_drawn = this.#axes2_drawn = false; + } - const text_g = this.draw_g.append('svg:g'); - makeTranslate(text_g, Math.round(width/4), Math.round(height/4)); + /** @summary Returns frame rectangle plus extra info for hint display */ + cleanFrameDrawings() { + // cleanup all 3D drawings if any + if (isFunc(this.create3DScene)) + this.create3DScene(-1); - return this.drawPaveText(w2, h2, arg, text_g); - } else { - // add shadow decoration before main rect - if ((brd > 1) && (pt.fShadowColor > 0) && !pt.fNpaves && (dx || dy) && !noborder) { - const scol = this.getColor(pt.fShadowColor); - let spath = ''; - - if ((dx < 0) && (dy < 0)) - spath = `M0,0v${height-brd}h${-brd}v${-height}h${width}v${brd}z`; - else if ((dx < 0) && (dy > 0)) - spath = `M0,${height}v${brd-height}h${-brd}v${height}h${width}v${-brd}z`; - else if ((dx > 0) && (dy < 0)) - spath = `M${brd},0v${-brd}h${width}v${height}h${-brd}v${brd-height}z`; - else - spath = `M${width},${brd}h${brd}v${height}h${-width}v${-brd}h${width-brd}z`; + this.cleanAxesDrawings(); + this.cleanXY(); - this.draw_g.append('svg:path') - .attr('d', spath) - .style('fill', scol) - .style('stroke', scol) - .style('stroke-width', '1px'); - } + this.ranges_set = false; - if (pt.fNpaves) { - for (let n = pt.fNpaves-1; n > 0; --n) { - this.draw_g.append('svg:path') - .attr('d', `M${dx*4*n},${dy*4*n}h${width}v${height}h${-width}z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - } - } + this.xmin = this.xmax = 0; + this.ymin = this.ymax = 0; + this.zmin = this.zmax = 0; - if (!this.isBatchMode() || !this.fillatt.empty() || (!this.lineatt.empty() && !noborder)) { - interactive_element = this.draw_g.append('svg:path') - .attr('d', `M0,0H${width}V${height}H0Z`) - .call(this.fillatt.func); - if (!noborder) - interactive_element.call(this.lineatt.func); - } + this.zoom_xmin = this.zoom_xmax = 0; + this.zoom_ymin = this.zoom_ymax = 0; + this.zoom_zmin = this.zoom_zmax = 0; - return isFunc(this.paveDrawFunc) ? this.paveDrawFunc(width, height, arg) : true; - } - }).then(() => { - if (this.isBatchMode() || (pt._typename === clTPave)) - return this; + this.scale_xmin = this.scale_xmax = 0; + this.scale_ymin = this.scale_ymax = 0; + this.scale_zmin = this.scale_zmax = 0; - // here all kind of interactive settings - if (interactive_element) { - interactive_element.style('pointer-events', 'visibleFill') - .on('mouseenter', () => this.showObjectStatus()); - } + this.getG()?.selectChild('.main_layer').selectAll('*').remove(); + this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); - addDragHandler(this, { obj: pt, x: this._pave_x, y: this._pave_y, width, height, - minwidth: 10, minheight: 20, canselect: true, - redraw: () => { this.moved_interactive = true; this.interactiveRedraw(false, 'pave_moved'); this.drawPave(); }, - ctxmenu: browser.touches && settings.ContextMenu && this.UseContextMenu }); + this.xaxis = null; + this.yaxis = null; + this.zaxis = null; - if (this.UseContextMenu && settings.ContextMenu) - this.draw_g.on('contextmenu', evnt => this.paveContextMenu(evnt)); + this.getG()?.selectAll('*').remove(); + this.getG()?.on('mousedown', null) + .on('dblclick', null) + .on('wheel', null) + .on('contextmenu', null) + .property('interactive_set', null) + .remove(); - if (pt._typename === clTPaletteAxis) - this.interactivePaletteAxis(width, height); + this.setG(undefined); // frame element managed by the pad - return this; - }); + if (this.#keys_handler) { + window.removeEventListener('keydown', this.#keys_handler, false); + this.#keys_handler = undefined; + } } - /** @summary Fill option object used in TWebCanvas */ - fillWebObjectOptions(res) { - const pave = this.getObject(); - - if (pave?.fInit) { - res.fcust = 'pave'; - res.fopt = [pave.fX1NDC, pave.fY1NDC, pave.fX2NDC, pave.fY2NDC]; + /** @summary Cleanup frame */ + cleanup() { + this.cleanFrameDrawings(); + this.#click_handler = undefined; + this.#dblclick_handler = undefined; + this.#enabled_keys = undefined; - if ((pave.fName === 'stats') && this.isStats()) { - pave.fLines.arr.forEach(entry => { - if ((entry._typename === clTText) || (entry._typename === clTLatex)) - res.fcust += `;;${entry.fTitle}`; - }); - } - } + this.getPadPainter()?.setFramePainter(this, false); - return res; + super.cleanup(); } - /** @summary draw TPaveLabel object */ - async drawPaveLabel(width, height) { - const pave = this.getObject(); - if (!pave.fLabel || !pave.fLabel.trim()) - return this; + /** @summary Redraw TFrame */ + redraw(/* reason */) { + const pp = this.getPadPainter(); + pp?.setFramePainter(this, true); - this.createAttText({ attr: pave, can_rotate: false }); + // first update all attributes from objects + this.updateAttributes(); - this.startTextDrawing(this.textatt.font, height/1.2); + const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, + lm = Math.round(rect.width * this.fX1NDC), + tm = Math.round(rect.height * (1 - this.fY2NDC)), + rotate = pp?.options?.RotateFrame, + w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), + h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); - this.drawText(this.textatt.createArg({ width, height, text: pave.fLabel, norotate: true })); + this.#frame_x = lm; + this.#frame_y = tm; + this.#frame_width = rotate ? h : w; + this.#frame_height = rotate ? w : h; + this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); + this.$can_drag = !rotate && !pp?.options?.FixFrame; - return this.finishTextDrawing(); + return this.mode3d ? this : this.createFrameG(); } - /** @summary draw TPaveStats object */ - drawPaveStats(width, height) { - const pt = this.getObject(), lines = [], colors = []; - let first_stat = 0, num_cols = 0, maxlen = 0; + /** @summary Create frame element and update all attributes + * @private */ + createFrameG() { + // this is svg:g object - container for every other items belonging to frame + let g = this.setG(this.getFrameSvg()), + top_rect, main_svg; - // extract only text - for (let j = 0; j < pt.fLines.arr.length; ++j) { - const entry = pt.fLines.arr[j]; - if ((entry._typename === clTText) || (entry._typename === clTLatex)) { - lines.push(entry.fTitle); - colors.push(entry.fTextColor); - } - } + if (g.empty()) { + g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); - const nlines = lines.length; + // empty title on the frame required to suppress title of the canvas + if (!this.isBatchMode()) + g.append('svg:title').text(''); - // adjust font size - for (let j = 0; j < nlines; ++j) { - const line = lines[j]; - if (j > 0) maxlen = Math.max(maxlen, line.length); - if ((j === 0) || (line.indexOf('|') < 0)) continue; - if (first_stat === 0) first_stat = j; - const parts = line.split('|'); - if (parts.length > num_cols) - num_cols = parts.length; - } + top_rect = g.append('svg:path'); - // for characters like 'p' or 'y' several more pixels required to stay in the box when drawn in last line - const stepy = height / nlines, margin_x = pt.fMargin * width; - let has_head = false; + main_svg = g.append('svg:svg') + .attr('class', 'main_layer') + .attr('x', 0) + .attr('y', 0) + .attr('overflow', 'hidden'); - this.createAttText({ attr: pt, can_rotate: false }); + g.append('svg:g').attr('class', 'axis_layer'); + g.append('svg:g').attr('class', 'upper_layer'); + } else { + top_rect = g.selectChild('path'); + main_svg = g.selectChild('.main_layer'); + } - this.startTextDrawing(this.textatt.font, height/(nlines * 1.2)); + this.#axes_drawn = this.#axes2_drawn = false; - if (nlines === 1) - this.drawText(this.textatt.createArg({ width, height, text: lines[0], latex: 1, norotate: true })); - else { - for (let j = 0; j < nlines; ++j) { - const y = j*stepy, - color = (colors[j] > 1) ? this.getColor(colors[j]) : this.textatt.color; + g.attr('transform', this.#frame_trans); - if (first_stat && (j >= first_stat)) { - const parts = lines[j].split('|'); - for (let n = 0; n < parts.length; ++n) { - this.drawText({ align: 'middle', x: width * n / num_cols, y, latex: 0, - width: width/num_cols, height: stepy, text: parts[n], color }); - } - } else if (lines[j].indexOf('=') < 0) { - if (j === 0) { - has_head = true; - const max_hlen = Math.max(maxlen, Math.round((width-2*margin_x)/stepy/0.65)); - if (lines[j].length > max_hlen + 5) - lines[j] = lines[j].slice(0, max_hlen+2) + '...'; - } - this.drawText({ align: (j === 0) ? 'middle' : 'start', x: margin_x, y, - width: width-2*margin_x, height: stepy, text: lines[j], color }); - } else { - const parts = lines[j].split('='), args = []; - - for (let n = 0; n < 2; ++n) { - const arg = { - align: (n === 0) ? 'start' : 'end', x: margin_x, y, - width: width - 2*margin_x, height: stepy, text: parts[n], color, - _expected_width: width-2*margin_x, _args: args, - post_process(painter) { - if (this._args[0].ready && this._args[1].ready) - painter.scaleTextDrawing(1.05*(this._args[0].result_width+this._args[1].result_width)/this._expected_width, painter.draw_g); - } - }; - args.push(arg); - } + top_rect.attr('d', `M0,0H${this.#frame_width}V${this.#frame_height}H0Z`) + .call(this.fillatt.func) + .call(this.lineatt.func); - for (let n = 0; n < 2; ++n) - this.drawText(args[n]); - } - } + main_svg.attr('width', this.#frame_width) + .attr('height', this.#frame_height) + .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); + + g.selectAll('.frame_deco').remove(); + if (this.#border_mode && this.fillatt.hasColor()) { + const paths = getBoxDecorations(0, 0, this.#frame_width, this.#frame_height, this.#border_mode, this.#border_size || 2, this.#border_size || 2); + g.insert('svg:path', '.main_layer') + .attr('class', 'frame_deco') + .attr('d', paths[0]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); + g.insert('svg:path', '.main_layer') + .attr('class', 'frame_deco') + .attr('d', paths[1]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); } - let lpath = ''; + return this; + } + + /** @summary Change log state of specified axis + * @param {string} axis - name of axis like 'x' or 'y' + * @param {number} value - 0 (linear), 1 (log) or 2 (log2) */ + changeAxisLog(axis, value) { + const pp = this.getPadPainter(), + pad = pp?.getRootPad(true); + if (!pad) + return; + + pp.options._interactively_changed = true; - if ((pt.fBorderSize > 0) && has_head) - lpath += `M0,${Math.round(stepy)}h${width}`; + const name = `fLog${axis}`; - if ((first_stat > 0) && (num_cols > 1)) { - for (let nrow = first_stat; nrow < nlines; ++nrow) - lpath += `M0,${Math.round(nrow * stepy)}h${width}`; - for (let ncol = 0; ncol < num_cols - 1; ++ncol) - lpath += `M${Math.round(width / num_cols * (ncol + 1))},${Math.round(first_stat * stepy)}V${height}`; + // do not allow log scale for labels + if (!pad[name]) { + if (this.#swap_xy && axis === 'x') + axis = 'y'; + else if (this.#swap_xy && axis === 'y') + axis = 'x'; + const handle = this[`${axis}_handle`]; + if (handle?.kind === kAxisLabels) + return; } - if (lpath) this.draw_g.append('svg:path').attr('d', lpath).call(this.lineatt.func); + if ((value === 'toggle') || (value === undefined)) + value = pad[name] ? 0 : 1; - // this.draw_g.classed('most_upper_primitives', true); // this primitive will remain on top of list + // directly change attribute in the pad + pad[name] = value; - return this.finishTextDrawing(undefined, (nlines > 1)); + return this.interactiveRedraw('pad', `log${axis}`); } - /** @summary draw TPaveText object */ - drawPaveText(width, height, _dummy_arg, text_g) { - const pt = this.getObject(), - arr = pt.fLines?.arr || [], - nlines = arr.length, + /** @summary Toggle log state on the specified axis */ + toggleAxisLog(axis) { + return this.changeAxisLog(axis, 'toggle'); + } + + /** @summary Fill context menu for the frame + * @desc It could be appended to the histogram menus */ + fillContextMenu(menu, kind, obj) { + const main = this.getMainPainter(true), + wrk = main?.$stack_hist ? main.getPrimary() : main, pp = this.getPadPainter(), - pad_height = pp.getPadHeight(), - draw_header = (pt.fLabel.length > 0), - promises = [], - margin_x = pt.fMargin * width, - stepy = height / (nlines || 1); - let max_font_size = 0; + pad = pp?.getRootPad(true), + is_pal = kind === 'pal'; - this.createAttText({ attr: pt, can_rotate: false }); + if (is_pal) + kind = 'z'; - // for single line (typically title) limit font size - if ((nlines === 1) && (this.textatt.size > 0)) - max_font_size = Math.max(3, this.textatt.getSize(pad_height)); + if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'x2') || (kind === 'y2')) { + const faxis = obj || this[kind + 'axis'], + handle = this[`${kind}_handle`]; + if (!isFunc(faxis?.TestBit)) + return false; + const hist_painter = handle?.hist_painter || main; - if (!text_g) text_g = this.draw_g; + menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}${clTAxis}.html`); - const fast = (nlines === 1) && pp._fast_drawing; - let num_default = 0; + menu.sub('Range'); + menu.add('Zoom', () => { + let min = this[`zoom_${kind}min`] ?? this[`${kind}min`], + max = this[`zoom_${kind}max`] ?? this[`${kind}max`]; + if (min === max) { + min = this[`${kind}min`]; + max = this[`${kind}max`]; + } + menu.input('Enter zoom range like: [min, max]', `[${min}, ${max}]`).then(v => { + const arr = JSON.parse(v); + if (arr && Array.isArray(arr) && (arr.length === 2)) { + let flag = false; + if (arr[0] < faxis.fXmin) { + faxis.fFirst = 0; + flag = true; + } else + faxis.fFirst = 1; + if (arr[1] > faxis.fXmax) { + faxis.fLast = faxis.fNbins + 1; + flag = true; + } else + faxis.fLast = faxis.fNbins; + faxis.SetBit(EAxisBits.kAxisRange, flag); + hist_painter?.scanContent(); + this.zoomSingle(kind, arr[0], arr[1], true).then(res => { + if (!res && flag) + this.interactiveRedraw('pad'); + }); + } + }); + }); + menu.add('Unzoom', () => { + this.unzoomSingle(kind).then(res => { + if (!res && (faxis.fFirst !== faxis.fLast)) { + faxis.fFirst = faxis.fLast = 0; + hist_painter?.scanContent(); + this.interactiveRedraw('pad'); + } + }); + }); + if (handle?.value_axis && isFunc(wrk?.accessMM)) { + menu.add('Minimum', () => { + menu.input(`Enter minimum value or ${kNoZoom} as default`, wrk.accessMM(true), 'float').then(v => { + this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; + wrk.accessMM(true, v); + }); + }); + menu.add('Maximum', () => { + menu.input(`Enter maximum value or ${kNoZoom} as default`, wrk.accessMM(false), 'float').then(v => { + this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; + wrk.accessMM(false, v); + }); + }); + } + menu.endsub(); - for (let nline = 0; nline < nlines; ++nline) { - const entry = arr[nline], texty = nline*stepy; + if (pad) { + const member = 'fLog' + kind[0]; + menu.sub('SetLog ' + kind[0], () => { + menu.input('Enter log kind: 0 - off, 1 - log10, 2 - log2, 3 - ln, ...', pad[member], 'int', 0, 10000).then(v => { + this.changeAxisLog(kind[0], v); + }); + }); + menu.addchk(pad[member] === 0, 'linear', () => this.changeAxisLog(kind[0], 0)); + menu.addchk(pad[member] === 1, 'log10', () => this.changeAxisLog(kind[0], 1)); + menu.addchk(pad[member] === 2, 'log2', () => this.changeAxisLog(kind[0], 2)); + menu.addchk(pad[member] === 3, 'ln', () => this.changeAxisLog(kind[0], 3)); + menu.addchk(pad[member] === 4, 'log4', () => this.changeAxisLog(kind[0], 4)); + menu.addchk(pad[member] === 8, 'log8', () => this.changeAxisLog(kind[0], 8)); + menu.endsub(); + } + menu.addchk(faxis.TestBit(EAxisBits.kMoreLogLabels), 'More log', flag => { + faxis.SetBit(EAxisBits.kMoreLogLabels, flag); + if (hist_painter?.getSnapId() && (kind.length === 1)) + hist_painter.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); + else + this.interactiveRedraw('pad'); + }); + menu.addchk(handle?.noexp ?? faxis.TestBit(EAxisBits.kNoExponent), 'No exponent', flag => { + faxis.SetBit(EAxisBits.kNoExponent, flag); + if (handle) + handle.noexp_changed = true; + this[`${kind}_noexp_changed`] = true; + if (hist_painter?.getSnapId() && (kind.length === 1)) + hist_painter.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); + else + this.interactiveRedraw('pad'); + }); - switch (entry._typename) { - case clTText: - case clTLatex: { - if (!entry.fTitle || !entry.fTitle.trim()) continue; + if ((kind === 'z') && isFunc(hist_painter?.fillPaletteMenu)) + hist_painter.fillPaletteMenu(menu, !is_pal); - let color = entry.fTextColor ? this.getColor(entry.fTextColor) : ''; - if (!color) color = this.textatt.color; + menu.addTAxisMenu(EAxisBits, hist_painter || this, faxis, kind, handle, this); + return true; + } - if (entry.fX || entry.fY || entry.fTextSize) { - // individual positioning - const align = entry.fTextAlign || this.textatt.align, - halign = Math.floor(align/10), - valign = align % 10, - x = entry.fX ? entry.fX*width : (halign === 1 ? margin_x : (halign === 2 ? width / 2 : width - margin_x)), - y = entry.fY ? (1 - entry.fY)*height : (texty + (valign === 2 ? stepy / 2 : (valign === 3 ? stepy : 0))), - sub_g = text_g.append('svg:g'); + const alone = menu.size() === 0; - this.startTextDrawing(this.textatt.font, this.textatt.getAltSize(entry.fTextSize, pad_height), sub_g); + if (alone) + menu.header('Frame', `${urlClassPrefix}${clTFrame}.html`); + else + menu.separator(); - this.drawText({ align, x, y, text: entry.fTitle, color, - latex: (entry._typename === clTText) ? 0 : 1, draw_g: sub_g, fast }); + if (this.zoom_xmin !== this.zoom_xmax) + menu.add('Unzoom X', () => this.unzoom('x')); + if (this.zoom_ymin !== this.zoom_ymax) + menu.add('Unzoom Y', () => this.unzoom('y')); + if (this.zoom_zmin !== this.zoom_zmax) + menu.add('Unzoom Z', () => this.unzoom('z')); + if (this.zoom_x2min !== this.zoom_x2max) + menu.add('Unzoom X2', () => this.unzoom('x2')); + if (this.zoom_y2min !== this.zoom_y2max) + menu.add('Unzoom Y2', () => this.unzoom('y2')); + menu.add('Unzoom all', () => this.unzoom('all')); - promises.push(this.finishTextDrawing(sub_g)); - } else { - // default position - if (num_default++ === 0) - this.startTextDrawing(this.textatt.font, 0.85*height/nlines, text_g, max_font_size); - - this.drawText({ x: margin_x, y: texty, width: width - 2*margin_x, height: stepy, - align: entry.fTextAlign || this.textatt.align, - draw_g: text_g, latex: (entry._typename === clTText) ? 0 : 1, - text: entry.fTitle, color, fast }); - } - break; - } + if (pad) { + menu.addchk(pad.fLogx, 'SetLogx', () => this.toggleAxisLog('x')); + menu.addchk(pad.fLogy, 'SetLogy', () => this.toggleAxisLog('y')); - case clTLine: { - const lx1 = entry.fX1 ? Math.round(entry.fX1*width) : 0, - lx2 = entry.fX2 ? Math.round(entry.fX2*width) : width, - ly1 = entry.fY1 ? Math.round((1 - entry.fY1)*height) : Math.round(texty + stepy*0.5), - ly2 = entry.fY2 ? Math.round((1 - entry.fY2)*height) : Math.round(texty + stepy*0.5), - lineatt = this.createAttLine(entry); - text_g.append('svg:path') - .attr('d', `M${lx1},${ly1}L${lx2},${ly2}`) - .call(lineatt.func); - break; - } - case clTBox: { - const bx1 = entry.fX1 ? Math.round(entry.fX1*width) : 0, - bx2 = entry.fX2 ? Math.round(entry.fX2*width) : width, - by1 = entry.fY1 ? Math.round((1 - entry.fY1)*height) : Math.round(texty), - by2 = entry.fY2 ? Math.round((1 - entry.fY2)*height) : Math.round(texty + stepy), - fillatt = this.createAttFill(entry); - text_g.append('svg:path') - .attr('d', `M${bx1},${by1}H${bx2}V${by2}H${bx1}Z`) - .call(fillatt.func); - break; - } - } + if (isFunc(main?.getDimension) && (main.getDimension() > 1)) + menu.addchk(pad.fLogz, 'SetLogz', () => this.toggleAxisLog('z')); + menu.separator(); } - if (num_default > 0) - promises.push(this.finishTextDrawing(text_g, num_default > 1)); + menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + menu.addAttributesMenu(this, alone ? '' : 'Frame '); - if (draw_header) { - const x = Math.round(width*0.25), - y = Math.round(-height*0.02), - w = Math.round(width*0.5), - h = Math.round(height*0.04), - lbl_g = text_g.append('svg:g'); + menu.sub('Border'); + menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], this.#border_mode + 1, v => { + this.#border_mode = v - 1; + this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); + }, 'Frame border mode'); + menu.addSizeMenu('Size', 0, 20, 2, this.#border_size, v => { + this.#border_size = v; + this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); + }, 'Frame border size'); + menu.endsub(); - lbl_g.append('svg:path') - .attr('d', `M${x},${y}h${w}v${h}h${-w}z`) - .call(this.fillatt.func) - .call(this.lineatt.func); + menu.add('Save to gStyle', () => { + gStyle.fPadBottomMargin = this.fY1NDC; + gStyle.fPadTopMargin = 1 - this.fY2NDC; + gStyle.fPadLeftMargin = this.fX1NDC; + gStyle.fPadRightMargin = 1 - this.fX2NDC; + this.fillatt?.saveToStyle('fFrameFillColor', 'fFrameFillStyle'); + this.lineatt?.saveToStyle('fFrameLineColor', 'fFrameLineWidth', 'fFrameLineStyle'); + gStyle.fFrameBorderMode = this.#border_mode; + gStyle.fFrameBorderSize = this.#border_size; + }, 'Store frame position and graphical attributes to gStyle'); - this.startTextDrawing(this.textatt.font, h/1.5, lbl_g); + menu.separator(); - this.drawText({ align: 22, x, y, width: w, height: h, text: pt.fLabel, color: this.textatt.color, draw_g: lbl_g }); + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); + menu.endsub(); - promises.push(this.finishTextDrawing(lbl_g)); - } + return true; + } - return Promise.all(promises).then(() => this); + /** @summary Fill option object used in TWebCanvas + * @private */ + fillWebObjectOptions(res) { + res.fcust = 'frame'; + res.fopt = [this.scale_xmin || 0, this.scale_ymin || 0, this.scale_xmax || 0, this.scale_ymax || 0]; } - /** @summary Method used to convert value to string according specified format - * @desc format can be like 5.4g or 4.2e or 6.4f or 'stat' or 'fit' or 'entries' */ - format(value, fmt) { - if (!fmt) fmt = 'stat'; + /** @summary Returns frame X position */ + getFrameX() { return this.#frame_x || 0; } - const pave = this.getObject(); + /** @summary Returns frame Y position */ + getFrameY() { return this.#frame_y || 0; } - switch (fmt) { - case 'stat' : fmt = pave.fStatFormat || gStyle.fStatFormat; break; - case 'fit': fmt = pave.fFitFormat || gStyle.fFitFormat; break; - case 'entries': - if ((Math.abs(value) < 1e9) && (Math.round(value) === value)) - return value.toFixed(0); - fmt = '14.7g'; - break; - case 'last': fmt = this.lastformat; break; - } + /** @summary Returns frame width */ + getFrameWidth() { return this.#frame_width || 0; } - const res = floatToString(value, fmt || '6.4g', true); + /** @summary Returns frame height */ + getFrameHeight() { return this.#frame_height || 0; } - this.lastformat = res[1]; + /** @summary Returns frame rectangle plus extra info for hint display */ + getFrameRect() { + return { + x: this.#frame_x || 0, + y: this.#frame_y || 0, + width: this.getFrameWidth(), + height: this.getFrameHeight(), + transform: this.getG()?.attr('transform') || '', + hint_delta_x: 0, + hint_delta_y: 0 + }; + } - return res[0]; + /** @summary Configure user-defined click handler + * @desc Function will be called every time when frame click was performed + * As argument, tooltip object with selected bins will be provided + * If handler function returns true, default handling of click will be disabled */ + configureUserClickHandler(handler) { + this.#click_handler = isFunc(handler) ? handler : null; } - /** @summary Draw TLegend object */ - drawLegend(w, h) { - const legend = this.getObject(), - nlines = legend.fPrimitives.arr.length; - let ncols = legend.fNColumns, - nrows = nlines, - any_text = false, - custom_textg = false; // each text entry has own attributes + /** @summary Returns actual click handler */ + getClickHandler() { return this.#click_handler; } - if (ncols < 2) - ncols = 1; - else - while ((nrows-1)*ncols >= nlines) nrows--; + /** @summary Configure user-defined dblclick handler + * @desc Function will be called every time when double click was called + * As argument, tooltip object with selected bins will be provided + * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ + configureUserDblclickHandler(handler) { + this.#dblclick_handler = isFunc(handler) ? handler : null; + } - const isEmpty = entry => !entry.fObject && !entry.fOption && (!entry.fLabel || (entry.fLabel === ' ')); + /** @summary Returns actual double-click handler */ + getDblclickHandler() { return this.#dblclick_handler; } - for (let ii = 0; ii < nlines; ++ii) { - const entry = legend.fPrimitives.arr[ii]; - if (isEmpty(entry)) { - if (ncols === 1) - nrows--; - } else if (entry.fLabel) { - any_text = true; - if ((entry.fTextFont && (entry.fTextFont !== legend.fTextFont)) || - (entry.fTextSize && (entry.fTextSize !== legend.fTextSize))) - custom_textg = true; - } + /** @summary Function can be used for zooming into specified range + * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be un-zoomed + * @param {number} xmin + * @param {number} xmax + * @param {number} [ymin] + * @param {number} [ymax] + * @param {number} [zmin] + * @param {number} [zmax] + * @param [interactive] - if changes was performed interactively + * @return {Promise} with boolean flag if zoom operation was performed */ + async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { + if (xmin === 'x') { + xmin = xmax; + xmax = ymin; + interactive = ymax; + ymin = ymax = undefined; + } else if (xmin === 'y') { + interactive = ymax; + ymax = ymin; + ymin = xmax; + xmin = xmax = undefined; + } else if (xmin === 'z') { + interactive = ymax; + zmin = xmax; + zmax = ymin; + xmin = xmax = ymin = ymax = undefined; } - if (nrows < 1) nrows = 1; - - // calculate positions of columns by weight - means more letters, more weight - const column_pos = new Array(ncols + 1).fill(0); - if (ncols > 1) { - const column_weight = new Array(ncols).fill(1); + let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), + unzoom_x = false, unzoom_y = false, unzoom_z = false; - for (let ii = 0; ii < nlines; ++ii) { - const entry = legend.fPrimitives.arr[ii]; - if (isEmpty(entry)) continue; // let discard empty entry - const icol = ii % ncols; - column_weight[icol] = Math.max(column_weight[icol], entry.fLabel.length); + if (zoom_x) { + let cnt = 0; + xmin = this.x_handle?.checkZoomMin(xmin) ?? xmin; + if (xmin <= this.xmin) { + xmin = this.xmin; + cnt++; } + if (xmax >= this.xmax) { + xmax = this.xmax; + cnt++; + } + if (cnt === 2) { + zoom_x = false; + unzoom_x = true; + } + } else + unzoom_x = (xmin === xmax) && (xmin === 0); - let sum_weight = 0; - for (let icol = 0; icol < ncols; ++icol) - sum_weight += column_weight[icol]; - for (let icol = 0; icol < ncols-1; ++icol) - column_pos[icol+1] = column_pos[icol] + legend.fMargin*w/ncols + column_weight[icol] * (1-legend.fMargin) * w / sum_weight; - } - column_pos[ncols] = w; - - const padding_x = Math.round(0.03*w/ncols), - padding_y = Math.round(0.03*h), - step_y = (h - 2*padding_y)/nrows, - text_promises = [], - pp = this.getPadPainter(); - let font_size = 0.9*step_y, - max_font_size = 0, // not limited in the beggining - any_opt = false; - - this.createAttText({ attr: legend, can_rotate: false }); - - const tsz = this.textatt.getSize(pp.getPadHeight()); - if (tsz && (tsz < font_size)) - font_size = max_font_size = tsz; - - if (any_text && !custom_textg) - this.startTextDrawing(this.textatt.font, font_size, this.draw_g, max_font_size); - - for (let ii = 0, i = -1; ii < nlines; ++ii) { - const entry = legend.fPrimitives.arr[ii]; - if (isEmpty(entry)) continue; // let discard empty entry - - if (ncols === 1) ++i; else i = ii; - - const lopt = entry.fOption.toLowerCase(), - icol = i % ncols, irow = (i - icol) / ncols, - x0 = Math.round(column_pos[icol]), - column_width = Math.round(column_pos[icol + 1] - column_pos[icol]), - tpos_x = x0 + Math.round(legend.fMargin*w/ncols), - mid_x = Math.round((x0 + tpos_x)/2), - pos_y = Math.round(irow*step_y + padding_y), // top corner - mid_y = Math.round((irow+0.5)*step_y + padding_y), // center line - mo = entry.fObject, - draw_fill = lopt.indexOf('f') !== -1, - draw_line = lopt.indexOf('l') !== -1, - draw_error = lopt.indexOf('e') !== -1, - draw_marker = lopt.indexOf('p') !== -1; - - let o_fill = entry, o_marker = entry, o_line = entry, - painter = null, isany = false; - - if (isObject(mo)) { - if ('fLineColor' in mo) o_line = mo; - if ('fFillColor' in mo) o_fill = mo; - if ('fMarkerColor' in mo) o_marker = mo; + if (zoom_y) { + let cnt = 0; + ymin = this.y_handle?.checkZoomMin(ymin) ?? ymin; + if (ymin <= this.ymin) { + ymin = this.ymin; + cnt++; + } + if (ymax >= this.ymax) { + ymax = this.ymax; + cnt++; + } + if ((cnt === 2) && (this.scales_ndim !== 1)) { + zoom_y = false; + unzoom_y = true; + } + } else + unzoom_y = (ymin === ymax) && (ymin === 0); - painter = pp.findPainterFor(mo); + if (zoom_z) { + let cnt = 0; + zmin = this.z_handle?.checkZoomMin(zmin) ?? zmin; + if (zmin <= this.zmin) { + zmin = this.zmin; + cnt++; + } + if (zmax >= this.zmax) { + zmax = this.zmax; + cnt++; + } + if ((cnt === 2) && (this.scales_ndim > 2)) { + zoom_z = false; + unzoom_z = true; } + } else + unzoom_z = (zmin === zmax) && (zmin === 0); + let changed = false; - // Draw fill pattern (in a box) - if (draw_fill) { - const fillatt = painter?.fillatt?.used ? painter.fillatt : this.createAttFill(o_fill); - let lineatt; - if (!draw_line && !draw_error && !draw_marker) { - lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); - if (lineatt.empty()) lineatt = null; + // first process zooming (if any) + if (zoom_x || zoom_y || zoom_z) { + this.forEachPainter(obj => { + if (!isFunc(obj.canZoomInside)) + return; + if (zoom_x && obj.canZoomInside('x', xmin, xmax)) { + this.zoom_xmin = xmin; + this.zoom_xmax = xmax; + changed = true; + zoom_x = false; + if (interactive) + this.zoomChangedInteractive('x', interactive); + } + if (zoom_y && obj.canZoomInside('y', ymin, ymax)) { + this.zoom_ymin = ymin; + this.zoom_ymax = ymax; + changed = true; + zoom_y = false; + if (interactive) + this.zoomChangedInteractive('y', interactive); + } + if (zoom_z && obj.canZoomInside('z', zmin, zmax)) { + this.zoom_zmin = zmin; + this.zoom_zmax = zmax; + changed = true; + zoom_z = false; + if (interactive) + this.zoomChangedInteractive('z', interactive); } + }); + } - if (!fillatt.empty() || lineatt) { - isany = true; - // box total height is yspace*0.7 - // define x,y as the center of the symbol for this entry - const rect = this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${Math.round(pos_y+step_y*0.1)}v${Math.round(step_y*0.8)}h${tpos_x-2*padding_x-x0}v${-Math.round(step_y*0.8)}z`); - if (!fillatt.empty()) - rect.call(fillatt.func); - if (lineatt) - rect.call(lineatt.func); + // and process unzoom, if any + if (unzoom_x || unzoom_y || unzoom_z) { + if (unzoom_x) { + if (this.zoom_xmin !== this.zoom_xmax) + changed = true; + this.zoom_xmin = this.zoom_xmax = 0; + if (interactive) + this.zoomChangedInteractive('x', interactive); + } + if (unzoom_y) { + if (this.zoom_ymin !== this.zoom_ymax) { + changed = true; + unzoomHistogramYRange(this.getMainPainter()); } + this.zoom_ymin = this.zoom_ymax = 0; + if (interactive) + this.zoomChangedInteractive('y', interactive); + } + if (unzoom_z) { + if (this.zoom_zmin !== this.zoom_zmax) + changed = true; + this.zoom_zmin = this.zoom_zmax = 0; + if (interactive) + this.zoomChangedInteractive('z', interactive); } - // Draw line and/or error (when specified) - if (draw_line || draw_error) { - const lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); - if (!lineatt.empty()) { - isany = true; - if (draw_line) { - this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${mid_y}H${tpos_x - padding_x}`) - .call(lineatt.func); - } - if (draw_error) { - let endcaps = 0, edx = step_y*0.05; - if (isFunc(painter?.getHisto) && painter.options?.ErrorKind === 1) - endcaps = 1; // draw bars for e1 option in histogram - else if (isFunc(painter?.getGraph) && mo?.fLineWidth !== undefined && mo?.fMarkerSize !== undefined) { - endcaps = painter.options?.Ends ?? 1; // deafult is 1 - edx = mo.fLineWidth + gStyle.fEndErrorSize; - if (endcaps > 1) edx = Math.max(edx, mo.fMarkerSize*8*0.66); - } - - const eoff = (endcaps === 3) ? 0.03 : 0, - ey1 = Math.round(pos_y+step_y*(0.1 + eoff)), - ey2 = Math.round(pos_y+step_y*(0.9 - eoff)), - edy = Math.round(edx * 0.66); - edx = Math.round(edx); - let path = `M${mid_x},${ey1}V${ey2}`; - switch (endcaps) { - case 1: path += `M${mid_x-edx},${ey1}h${2*edx}M${mid_x-edx},${ey2}h${2*edx}`; break; // bars - case 2: path += `M${mid_x-edx},${ey1+edy}v${-edy}h${2*edx}v${edy}M${mid_x-edx},${ey2-edy}v${edy}h${2*edx}v${-edy}`; break; // ] - case 3: path += `M${mid_x-edx},${ey1}h${2*edx}l${-edx},${-edy}zM${mid_x-edx},${ey2}h${2*edx}l${-edx},${edy}z`; break; // triangle - case 4: path += `M${mid_x-edx},${ey1+edy}l${edx},${-edy}l${edx},${edy}M${mid_x-edx},${ey2-edy}l${edx},${edy}l${edx},${-edy}`; break; // arrow - } - this.draw_g.append('svg:path') - .attr('d', path) - .call(lineatt.func) - .style('fill', endcaps > 1 ? 'none' : null); + // than try to unzoom all overlapped objects + if (!changed) { + this.forEachPainter(painter => { + if (isFunc(painter?.unzoomUserRange)) { + if (painter.unzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) + changed = true; } - } + }, 'objects'); } + } + if (!changed) + return false; - // Draw Polymarker - if (draw_marker) { - const marker = painter?.markeratt?.used ? painter.markeratt : this.createAttMarker(o_marker); - if (!marker.empty()) { - isany = true; - this.draw_g - .append('svg:path') - .attr('d', marker.create((x0 + tpos_x)/2, mid_y)) - .call(marker.func); - } - } + return this.interactiveRedraw('pad', 'zoom').then(() => true); + } + + /** @summary Zooming of single axis + * @param {String} name - axis name like x/y/z but also second axis x2 or y2 + * @param {Number} vmin - axis minimal value, 0 for unzoom + * @param {Number} vmax - axis maximal value, 0 for unzoom + * @param {Boolean} [interactive] - if change was performed interactively + * @protected */ + async zoomSingle(name, vmin, vmax, interactive) { + const handle = this[`${name}_handle`], name_min = `zoom_${name}min`, name_max = `zoom_${name}max`; + if (!handle && (name !== 'z')) + return false; + + let zoom_v = (vmin !== vmax), unzoom_v = false; - // special case - nothing draw, try to show rect with line attributes - if (!isany && painter?.lineatt && !painter.lineatt.empty()) { - this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${Math.round(pos_y+step_y*0.1)}v${Math.round(step_y*0.8)}h${tpos_x-2*padding_x-x0}v${-Math.round(step_y*0.8)}z`) - .style('fill', 'none') - .call(painter.lineatt.func); + if (zoom_v) { + let cnt = 0; + vmin = handle?.checkZoomMin(vmin) ?? vmin; + if (vmin <= this[name + 'min']) { + vmin = this[name + 'min']; + cnt++; + } + if (vmax >= this[name + 'max']) { + vmax = this[name + 'max']; + cnt++; + } + if (cnt === 2) { + zoom_v = false; + unzoom_v = true; } + } else + unzoom_v = (vmin === vmax) && (vmin === 0); - let pos_x = tpos_x; - if (isStr(lopt) && (lopt.toLowerCase() !== 'h')) - any_opt = true; - else if (!any_opt) - pos_x = x0 + padding_x; + let changed = false; - if (entry.fLabel) { - let lbl_g = this.draw_g; - const textatt = this.createAttText({ attr: entry, std: false, attr_alt: legend }); - if (custom_textg) { - lbl_g = this.draw_g.append('svg:g'); - const entry_font_size = textatt.getSize(pp.getPadHeight()); - this.startTextDrawing(textatt.font, entry_font_size, lbl_g, max_font_size); + // first process zooming + if (zoom_v) { + this.forEachPainter(obj => { + if (zoom_v && isFunc(obj.canZoomInside) && obj.canZoomInside(name[0], vmin, vmax)) { + this[name_min] = vmin; + this[name_max] = vmax; + changed = true; + zoom_v = false; } + }); + } - this.drawText({ draw_g: lbl_g, align: textatt.align, x: pos_x, y: pos_y, - scale: (custom_textg && !entry.fTextSize) || !legend.fTextSize, - width: x0+column_width-pos_x-padding_x, height: step_y, - text: entry.fLabel, color: textatt.color }); - - if (custom_textg) - text_promises.push(this.finishTextDrawing(lbl_g)); + // and process unzoom, if any + if (unzoom_v) { + if (this[name_min] !== this[name_max]) { + changed = true; + if (name === 'y') + unzoomHistogramYRange(this.getMainPainter()); } + this[name_min] = this[name_max] = 0; } - if (any_text && !custom_textg) - text_promises.push(this.finishTextDrawing()); + if (!changed) + return false; + + if (interactive) + this.zoomChangedInteractive(name, interactive); - // rescale after all entries are shown - return Promise.all(text_promises); + return this.interactiveRedraw('pad', 'zoom').then(() => true); } - /** @summary draw color palette with axis */ - drawPaletteAxis(s_width, s_height, arg) { - const palette = this.getObject(), - axis = palette.fAxis, - can_move = isStr(arg) && (arg.indexOf('can_move') >= 0), - postpone_draw = isStr(arg) && (arg.indexOf('postpone') >= 0), - cjust = isStr(arg) && (arg.indexOf('cjust') >= 0), - pp = this.getPadPainter(), - width = pp.getPadWidth(), - height = pp.getPadHeight(), - pad = pp.getRootPad(true), - main = palette.$main_painter || this.getMainPainter(), - framep = this.getFramePainter(), - contour = main.fContour, - levels = contour?.getLevels(), - is_th3 = isFunc(main.getDimension) && (main.getDimension() === 3), - log = (is_th3 ? pad?.fLogv : pad?.fLogz) ?? 0, - draw_palette = main._color_palette, - zaxis = main.getObject()?.fZaxis, - sizek = pad?.fTickz ? 0.35 : 0.7; + /** @summary Unzoom single axis */ + async unzoomSingle(name, interactive) { + return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); + } - let zmin = 0, zmax = 100, gzmin, gzmax, axis_transform = '', axis_second = 0; + /** @summary Checks if specified axis zoomed */ + isAxisZoomed(axis) { + return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; + } - this._palette_vertical = (palette.fX2NDC - palette.fX1NDC) < (palette.fY2NDC - palette.fY1NDC); + /** @summary Unzoom specified axes + * @return {Promise} with boolean flag if zooming changed */ + async unzoom(dox, doy, doz) { + if (dox === 'all') + return this.unzoomSingle('x2').then(() => this.unzoomSingle('y2')).then(() => this.unzoom('xyz')); - axis.fTickSize = 0.6 * s_width / width; // adjust axis ticks size - if ((typeof zaxis?.fLabelOffset !== 'undefined') && !is_th3) { - axis.fTitle = zaxis.fTitle; - axis.fTitleSize = zaxis.fTitleSize; - axis.fTitleOffset = zaxis.fTitleOffset; - axis.fTextColor = zaxis.fTitleColor; - axis.fTextFont = zaxis.fTitleFont; - axis.fLineColor = zaxis.fAxisColor; - axis.fLabelSize = zaxis.fLabelSize; - axis.fLabelColor = zaxis.fLabelColor; - axis.fLabelFont = zaxis.fLabelFont; - axis.fLabelOffset = zaxis.fLabelOffset; - this.z_handle.setHistPainter(main, 'z'); - this.z_handle.source_axis = zaxis; - } + if ((dox === 'x2') || (dox === 'y2')) + return this.unzoomSingle(dox); - if (contour && framep && !is_th3) { - if ((framep.zmin !== undefined) && (framep.zmax !== undefined) && (framep.zmin !== framep.zmax)) { - gzmin = framep.zmin; - gzmax = framep.zmax; - zmin = framep.zoom_zmin; - zmax = framep.zoom_zmax; - if (zmin === zmax) { zmin = gzmin; zmax = gzmax; } - } else { - zmin = levels[0]; - zmax = levels[levels.length-1]; - } - // zmin = Math.min(levels[0], framep.zmin); - // zmax = Math.max(levels[levels.length-1], framep.zmax); - } else if ((main.gmaxbin !== undefined) && (main.gminbin !== undefined)) { - // this is case of TH2 (needs only for size adjustment) - zmin = main.gminbin; zmax = main.gmaxbin; - } else if ((main.hmin !== undefined) && (main.hmax !== undefined)) { - // this is case of TH1 - zmin = main.hmin; zmax = main.hmax; + if (typeof dox === 'undefined') + dox = doy = doz = true; + else if (isStr(dox)) { + doz = dox.indexOf('z') >= 0; + doy = dox.indexOf('y') >= 0; + dox = dox.indexOf('x') >= 0; } - this.draw_g.selectAll('rect').style('fill', 'white'); + return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, + doy ? 0 : undefined, doy ? 0 : undefined, + doz ? 0 : undefined, doz ? 0 : undefined, + 'unzoom'); + } - if ((gzmin === undefined) || (gzmax === undefined) || (gzmin === gzmax)) { - gzmin = zmin; gzmax = zmax; - } + /** @summary Reset all zoom attributes + * @private */ + resetZoom() { + ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { + this[`zoom_${n}min`] = undefined; + this[`zoom_${n}max`] = undefined; + this[`zoom_changed_${n}`] = undefined; + }); + } - if (this._palette_vertical) { - this._swap_side = palette.fX2NDC < 0.5; - this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, true, [0, s_height], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_width*sizek), swap_side: this._swap_side }); - axis_transform = this._swap_side ? null : `translate(${s_width})`; - if (pad?.fTickz) axis_second = this._swap_side ? s_width : -s_width; - } else { - this._swap_side = palette.fY1NDC > 0.5; - this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, false, [0, s_width], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_height*sizek), swap_side: this._swap_side }); - axis_transform = this._swap_side ? null : `translate(0,${s_height})`; - if (pad?.fTickz) axis_second = this._swap_side ? s_height : -s_height; + /** @summary Mark/check if zoom for specific axis was changed interactively + * @private */ + zoomChangedInteractive(axis, value) { + if (axis === 'reset') { + this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; + return; } + if (!axis || axis === 'any') + return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - if (!contour || !draw_palette || postpone_draw) { - // we need such rect to correctly calculate size - this.draw_g.append('svg:path') - .attr('d', `M0,0H${s_width}V${s_height}H0Z`) - .style('fill', 'white'); - } else { - for (let i = 0; i < levels.length-1; ++i) { - let z0 = Math.round(this.z_handle.gr(levels[i])), - z1 = Math.round(this.z_handle.gr(levels[i+1])), - lvl = (levels[i]+levels[i+1])/2, d; - - if (this._palette_vertical) { - if ((z1 >= s_height) || (z0 < 0)) continue; - z0 += 1; // ensure correct gap filling between colors - - if (z0 > s_height) { - z0 = s_height; - lvl = levels[i]*0.001+levels[i+1]*0.999; - } else if (z1 < 0) { - z1 = 0; - lvl = levels[i]*0.999+levels[i+1]*0.001; - } - d = `M0,${z1}H${s_width}V${z0}H0Z`; - } else { - if ((z0 >= s_width) || (z1 < 0)) continue; - z1 += 1; // ensure correct gap filling between colors + if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) + return; - if (z1 > s_width) { - z1 = s_width; - lvl = levels[i]*0.999+levels[i+1]*0.001; - } else if (z0 < 0) { - z0 = 0; - lvl = levels[i]*0.001+levels[i+1]*0.999; - } - d = `M${z0},0V${s_height}H${z1}V0Z`; - } + const fld = 'zoom_changed_' + axis; + if (value === undefined) + return this[fld]; - const col = contour.getPaletteColor(draw_palette, lvl); - if (!col) continue; + // special handling of unzoom, only if was never changed before flag set to true + if (value === 'unzoom') + this[fld] = (this[fld] === undefined); + else if (value) + this[fld] = true; + } - const r = this.draw_g.append('svg:path') - .attr('d', d) - .style('fill', col) - .property('fill0', col) - .property('fill1', rgb(col).darker(0.5).formatHex()); + /** @summary Convert graphical coordinate into axis value */ + revertAxis(axis, pnt) { + if (this.#swap_xy) + axis = (axis[0] === 'x') ? 'y' : 'x'; + return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; + } - if (this.isTooltipAllowed()) { - r.on('mouseover', function() { - select(this).transition().duration(100).style('fill', select(this).property('fill1')); - }).on('mouseout', function() { - select(this).transition().duration(100).style('fill', select(this).property('fill0')); - }).append('svg:title').text(levels[i].toFixed(2) + ' - ' + levels[i+1].toFixed(2)); - } + /** @summary Show axis status message + * @desc method called normally when mouse enter main object element + * @private */ + showAxisStatus(axis_name, evnt) { + const taxis = this.getAxis(axis_name), + m = pointer(evnt, this.getFrameSvg().node()); + let hint_name = axis_name, + hint_title = clTAxis, + id = (axis_name === 'x') ? 0 : 1; - if (settings.Zooming) - r.on('dblclick', () => this.getFramePainter().unzoom('z')); - } + if (taxis) { + hint_name = taxis.fName; + hint_title = taxis.fTitle || `TAxis object for ${axis_name}`; } + if (this.#swap_xy) + id = 1 - id; - return this.z_handle.drawAxis(this.draw_g, s_width, s_height, axis_transform, axis_second).then(() => { - if (can_move && ('getBoundingClientRect' in this.draw_g.node())) { - const rect = this.draw_g.node().getBoundingClientRect(); - - if (this._palette_vertical) { - const shift = (this._pave_x + parseInt(rect.width)) - Math.round(0.995*width) + 3; - - if (shift > 0) { - this._pave_x -= shift; - makeTranslate(this.draw_g, this._pave_x, this._pave_y); - palette.fX1NDC -= shift/width; - palette.fX2NDC -= shift/width; - } - } else { - const shift = Math.round((1.05 - gStyle.fTitleY)*height) - rect.y; - if (shift > 0) { - this._pave_y += shift; - makeTranslate(this.draw_g, this._pave_x, this._pave_y); - palette.fY1NDC -= shift/height; - palette.fY2NDC -= shift/height; - } - } - } + const axis_value = this.revertAxis(axis_name, m[id]); - return this; - }); + this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${m[0]},${m[1]}`); } - /** @summary Add interactive methods for palette drawing */ - interactivePaletteAxis(s_width, s_height) { - let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect = null; + /** @summary Add interactive keys handlers + * @private */ + addKeysHandler() { + if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) + return; - const moveRectSel = evnt => { - if (!doing_zoom) return; - evnt.preventDefault(); + this.#keys_handler = evnt => this.processKeyPress(evnt); - const m = pointer(evnt, this.draw_g.node()); - if (this._palette_vertical) { - sel2 = Math.min(Math.max(m[1], 0), s_height); - zoom_rect.attr('y', Math.min(sel1, sel2)) - .attr('height', Math.abs(sel2-sel1)); - } else { - sel2 = Math.min(Math.max(m[0], 0), s_width); - zoom_rect.attr('x', Math.min(sel1, sel2)) - .attr('width', Math.abs(sel2-sel1)); - } - }, endRectSel = evnt => { - if (!doing_zoom) return; + window.addEventListener('keydown', this.#keys_handler, false); + } - evnt.preventDefault(); - select(window).on('mousemove.colzoomRect', null) - .on('mouseup.colzoomRect', null); - zoom_rect.remove(); - zoom_rect = null; - doing_zoom = false; + /** @summary Add interactive functionality to the frame + * @private */ + addInteractivity(for_second_axes) { + if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) + return false; - const z = this.z_handle.gr, z1 = z.invert(sel1), z2 = z.invert(sel2); + if (!for_second_axes) + this.addBasicInteractivity(); - this.getFramePainter().zoom('z', Math.min(z1, z2), Math.max(z1, z2)); - }, startRectSel = evnt => { - // ignore when touch selection is activated - if (doing_zoom) return; - doing_zoom = true; + return this.addFrameInteractivity(for_second_axes); + } - evnt.preventDefault(); - evnt.stopPropagation(); +} // class TFramePainter - const origin = pointer(evnt, this.draw_g.node()); +/** @summary Current hierarchy painter + * @desc Instance of {@link HierarchyPainter} object + * @private */ +let _first_hpainter = null; - zoom_rect = this.draw_g.append('svg:rect').attr('id', 'colzoomRect').call(addHighlightStyle, true); +/** @summary Returns current hierarchy painter object + * @private */ +function getHPainter() { return _first_hpainter; } - if (this._palette_vertical) { - sel1 = sel2 = origin[1]; - zoom_rect.attr('x', '0') - .attr('width', s_width) - .attr('y', sel1) - .attr('height', 1); - } else { - sel1 = sel2 = origin[0]; - zoom_rect.attr('x', sel1) - .attr('width', 1) - .attr('y', 0) - .attr('height', s_height); - } +/** @summary Set hierarchy painter object + * @private */ +function setHPainter(hp) { _first_hpainter = hp; } - select(window).on('mousemove.colzoomRect', moveRectSel) - .on('mouseup.colzoomRect', endRectSel, true); - }; +/** + * @summary Base class to manage multiple document interface for drawings + * + * @private + */ - if (settings.Zooming) { - this.draw_g.selectAll('.axis_zoom') - .on('mousedown', startRectSel) - .on('dblclick', () => this.getFramePainter().unzoom('z')); - } +class MDIDisplay extends BasePainter { - if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { - const pos = pointer(evnt, this.draw_g.node()), - coord = this._palette_vertical ? (1 - pos[1] / s_height) : pos[0] / s_width, - item = this.z_handle.analyzeWheelEvent(evnt, coord); - if (item?.changed) - this.getFramePainter().zoom('z', item.min, item.max); - }); - } + /** @summary constructor */ + constructor(frameid) { + super(); + this.frameid = frameid; + if (frameid !== '$batch$') { + this.setDom(frameid); + this.selectDom().property('mdi', this); + } + this.cleanupFrame = cleanup; // use standard cleanup function by default + this.active_frame_title = ''; // keep title of active frame } - /** @summary Fill context menu items for the TPave object */ - fillContextMenuItems(menu) { - const pave = this.getObject(); - - if (this.isStats()) { - menu.add('Default position', () => { - pave.fX2NDC = gStyle.fStatX; - pave.fX1NDC = pave.fX2NDC - gStyle.fStatW; - pave.fY2NDC = gStyle.fStatY; - pave.fY1NDC = pave.fY2NDC - gStyle.fStatH; - pave.fInit = 1; - this.interactiveRedraw(true, 'pave_moved'); - }); - - menu.add('Save to gStyle', () => { - gStyle.fStatX = pave.fX2NDC; - gStyle.fStatW = pave.fX2NDC - pave.fX1NDC; - gStyle.fStatY = pave.fY2NDC; - gStyle.fStatH = pave.fY2NDC - pave.fY1NDC; - this.fillatt?.saveToStyle('fStatColor', 'fStatStyle'); - gStyle.fStatTextColor = pave.fTextColor; - gStyle.fStatFontSize = pave.fTextSize; - gStyle.fStatFont = pave.fTextFont; - }, 'Store stats position and graphical attributes to gStyle'); - - menu.add('SetStatFormat', () => { - menu.input('Enter StatFormat', pave.fStatFormat).then(fmt => { - if (!fmt) return; - pave.fStatFormat = fmt; - this.interactiveRedraw(true, `exec:SetStatFormat("${fmt}")`); - }); - }); - menu.add('SetFitFormat', () => { - menu.input('Enter FitFormat', pave.fFitFormat).then(fmt => { - if (!fmt) return; - pave.fFitFormat = fmt; - this.interactiveRedraw(true, `exec:SetFitFormat("${fmt}")`); - }); - }); - menu.add('separator'); - menu.add('sub:SetOptStat', () => { - menu.input('Enter OptStat', pave.fOptStat, 'int').then(fmt => { - pave.fOptStat = fmt; - this.interactiveRedraw(true, `exec:SetOptStat(${fmt})`); - }); - }); - const addStatOpt = (pos, name) => { - let opt = (pos < 10) ? pave.fOptStat : pave.fOptFit; - opt = parseInt(parseInt(opt) / parseInt(Math.pow(10, pos % 10))) % 10; - menu.addchk(opt, name, opt * 100 + pos, arg => { - const oldopt = parseInt(arg / 100); - let newopt = (arg % 100 < 10) ? pave.fOptStat : pave.fOptFit; - newopt -= (oldopt > 0 ? oldopt : -1) * parseInt(Math.pow(10, arg % 10)); - if (arg % 100 < 10) { - pave.fOptStat = newopt; - this.interactiveRedraw(true, `exec:SetOptStat(${newopt})`); - } else { - pave.fOptFit = newopt; - this.interactiveRedraw(true, `exec:SetOptFit(${newopt})`); - } - }); - }; - - addStatOpt(0, 'Histogram name'); - addStatOpt(1, 'Entries'); - addStatOpt(2, 'Mean'); - addStatOpt(3, 'Std Dev'); - addStatOpt(4, 'Underflow'); - addStatOpt(5, 'Overflow'); - addStatOpt(6, 'Integral'); - addStatOpt(7, 'Skewness'); - addStatOpt(8, 'Kurtosis'); - menu.add('endsub:'); - - menu.add('sub:SetOptFit', () => { - menu.input('Enter OptStat', pave.fOptFit, 'int').then(fmt => { - pave.fOptFit = fmt; - this.interactiveRedraw(true, `exec:SetOptFit(${fmt})`); - }); - }); - addStatOpt(10, 'Fit parameters'); - addStatOpt(11, 'Par errors'); - addStatOpt(12, 'Chi square / NDF'); - addStatOpt(13, 'Probability'); - menu.add('endsub:'); - - menu.add('separator'); - } else if (pave._typename === clTLegend) { - menu.add('Autoplace', () => { - this.autoPlaceLegend(pave, this.getPadPainter()?.getRootPad(true), true).then(res => { - if (res) this.interactiveRedraw(true, 'pave_moved'); - }); - }); - } else if (pave.fName === kTitle) { - menu.add('Default position', () => { - pave.fX1NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX - gStyle.fTitleW/2 : gStyle.fPadLeftMargin; - pave.fY1NDC = gStyle.fTitleY - Math.min(gStyle.fTitleFontSize*1.1, 0.06); - pave.fX2NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX + gStyle.fTitleW/2 : 1 - gStyle.fPadRightMargin; - pave.fY2NDC = gStyle.fTitleY; - pave.fInit = 1; - this.interactiveRedraw(true, 'pave_moved'); - }); - - menu.add('Save to gStyle', () => { - gStyle.fTitleX = (pave.fX2NDC + pave.fX1NDC)/2; - gStyle.fTitleY = pave.fY2NDC; - this.fillatt?.saveToStyle('fTitleColor', 'fTitleStyle'); - gStyle.fTitleTextColor = pave.fTextColor; - gStyle.fTitleFontSize = pave.fTextSize; - gStyle.fTitleFont = pave.fTextFont; - }, 'Store title position and graphical attributes to gStyle'); - } - - menu.add('Bring to front', () => this.bringToFront(!this.isStats() && !this.z_handle)); + /** @summary Assign func which called for each newly created frame */ + setInitFrame(func) { + this.initFrame = func; + this.forEachFrame(frame => func(frame)); } - /** @summary Show pave context menu */ - paveContextMenu(evnt) { - if (this.z_handle) { - const fp = this.getFramePainter(); - if (isFunc(fp?.showContextMenu)) - fp.showContextMenu('pal', evnt); - } else - showPainterMenu(evnt, this, this.isTitle() ? kTitle : undefined); - } + /** @summary method called before new frame is created */ + beforeCreateFrame(title) { this.active_frame_title = title; } - /** @summary Returns true when stat box is drawn */ - isStats() { - return this.matchObjectType(clTPaveStats); + /** @summary method called after new frame is created + * @private */ + afterCreateFrame(frame) { + if (isFunc(this.initFrame)) + this.initFrame(frame); + return frame; } - /** @summary Returns true when title is drawn */ - isTitle() { - return this.matchObjectType(clTPaveText) && (this.getObject()?.fName === kTitle); + /** @summary method dedicated to iterate over existing panels + * @param {function} userfunc is called with arguments (frame) + * @param {boolean} only_visible let select only visible frames */ + forEachFrame(userfunc, only_visible) { + console.warn(`forEachFrame not implemented in MDIDisplay ${typeof userfunc} ${only_visible}`); } - /** @summary Clear text in the pave */ - clearPave() { - this.getObject().Clear(); + /** @summary method dedicated to iterate over existing panels + * @param {function} userfunc is called with arguments (painter, frame) + * @param {boolean} only_visible let select only visible frames */ + forEachPainter(userfunc, only_visible) { + this.forEachFrame(frame => { + new ObjectPainter(frame).forEachPainter(painter => userfunc(painter, frame)); + }, only_visible); } - /** @summary Add text to pave */ - addText(txt) { - this.getObject().AddText(txt); + /** @summary Returns total number of drawings */ + numDraw() { + let cnt = 0; + this.forEachFrame(() => ++cnt); + return cnt; } - /** @summary Fill function parameters */ - fillFunctionStat(f1, dofit, ndim = 1) { - this._has_fit = false; + /** @summary Search for the frame using item name */ + findFrame(searchtitle, force) { + let found_frame = null; - if (!dofit || !f1) return false; + this.forEachFrame(frame => { + if (select(frame).attr('frame_title') === searchtitle) + found_frame = frame; + }); - this._has_fit = true; - this._fit_dim = ndim; - this._fit_cnt = 0; + if (!found_frame && force) + found_frame = this.createFrame(searchtitle); - const print_fval = (ndim === 1) ? dofit % 10 : 1, - print_ferrors = (ndim === 1) ? Math.floor(dofit/10) % 10 : 1, - print_fchi2 = (ndim === 1) ? Math.floor(dofit/100) % 10 : 1, - print_fprob = (ndim === 1) ? Math.floor(dofit/1000) % 10 : 0; + return found_frame; + } - if (print_fchi2) { - this.addText('#chi^{2} / ndf = ' + this.format(f1.fChisquare, 'fit') + ' / ' + f1.fNDF); - this._fit_cnt++; - } - if (print_fprob) { - this.addText('Prob = ' + this.format(Prob(f1.fChisquare, f1.fNDF))); - this._fit_cnt++; - } - if (print_fval) { - for (let n = 0; n < f1.GetNumPars(); ++n) { - const parname = f1.GetParName(n); - let parvalue = f1.GetParValue(n), parerr = f1.GetParError(n); + /** @summary Activate frame */ + activateFrame(frame) { this.active_frame_title = frame ? select(frame).attr('frame_title') : ''; } - parvalue = (parvalue === undefined) ? '' : this.format(Number(parvalue), 'fit'); - if (parerr !== undefined) { - parerr = this.format(parerr, 'last'); - if ((Number(parerr) === 0) && (f1.GetParError(n) !== 0)) - parerr = this.format(f1.GetParError(n), '4.2g'); - } + /** @summary Return active frame */ + getActiveFrame() { return this.findFrame(this.active_frame_title); } - if (print_ferrors && parerr) - this.addText(`${parname} = ${parvalue} #pm ${parerr}`); - else - this.addText(`${parname} = ${parvalue}`); - this._fit_cnt++; - } - } + /** @summary perform resize for each frame + * @protected */ + checkMDIResize(only_frame_id, size) { + let resized_frame = null; + this.forEachPainter((painter, frame) => { + if (only_frame_id && (select(frame).attr('id') !== only_frame_id)) + return; - return true; + if ((painter.getItemName() !== null) && isFunc(painter.checkResize)) { + // do not call resize for many painters on the same frame + if (resized_frame === frame) + return; + painter.checkResize(size); + resized_frame = frame; + } + }); } - /** @summary Is dummy pos of the pave painter */ - isDummyPos(p) { - if (!p) return true; + /** @summary Cleanup all drawings */ + cleanup() { + this.active_frame_title = ''; - return !p.fInit && !p.fX1 && !p.fX2 && !p.fY1 && !p.fY2 && !p.fX1NDC && !p.fX2NDC && !p.fY1NDC && !p.fY2NDC; + this.forEachFrame(this.cleanupFrame); + + this.selectDom().html('').property('mdi', null); } - /** @summary Update TPave object */ - updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; +} // class MDIDisplay - const pave = this.getObject(); - if (!pave.modified_NDC && !this.isDummyPos(obj)) { - // if position was not modified interactively, update from source object +/** + * @summary Custom MDI display + * + * @desc All HTML frames should be created before and add via {@link CustomDisplay#addFrame} calls + * @private + */ - if (this.stored && !obj.fInit && (this.stored.fX1 === obj.fX1) && - (this.stored.fX2 === obj.fX2) && (this.stored.fY1 === obj.fY1) && (this.stored.fY2 === obj.fY2)) { - // case when source object not initialized and original coordinates are not changed - // take over only modified NDC coordinate, used in tutorials/graphics/canvas.C - if (this.stored.fX1NDC !== obj.fX1NDC) pave.fX1NDC = obj.fX1NDC; - if (this.stored.fX2NDC !== obj.fX2NDC) pave.fX2NDC = obj.fX2NDC; - if (this.stored.fY1NDC !== obj.fY1NDC) pave.fY1NDC = obj.fY1NDC; - if (this.stored.fY2NDC !== obj.fY2NDC) pave.fY2NDC = obj.fY2NDC; - } else { - pave.fInit = obj.fInit; - pave.fX1 = obj.fX1; pave.fX2 = obj.fX2; - pave.fY1 = obj.fY1; pave.fY2 = obj.fY2; - pave.fX1NDC = obj.fX1NDC; pave.fX2NDC = obj.fX2NDC; - pave.fY1NDC = obj.fY1NDC; pave.fY2NDC = obj.fY2NDC; - } +class CustomDisplay extends MDIDisplay { - this.stored = Object.assign({}, obj); // store latest coordinates - } + constructor() { + super('dummy'); + this.frames = {}; // array of configured frames + } - pave.fOption = obj.fOption; - pave.fBorderSize = obj.fBorderSize; - if (pave.fTextColor !== undefined && obj.fTextColor !== undefined) { - pave.fTextAngle = obj.fTextAngle; - pave.fTextSize = obj.fTextSize; - pave.fTextAlign = obj.fTextAlign; - pave.fTextColor = obj.fTextColor; - pave.fTextFont = obj.fTextFont; - } + addFrame(divid, itemname) { + const prev = this.frames[divid] || ''; + this.frames[divid] = prev + (itemname + ';'); + } - switch (obj._typename) { - case clTDiamond: - case clTPaveText: - pave.fLines = clone(obj.fLines); - return true; - case clTPavesText: - pave.fLines = clone(obj.fLines); - pave.fNpaves = obj.fNpaves; - return true; - case clTPaveLabel: - case clTPaveClass: - pave.fLabel = obj.fLabel; - return true; - case clTPaveStats: - pave.fOptStat = obj.fOptStat; - pave.fOptFit = obj.fOptFit; - return true; - case clTLegend: { - const oldprim = pave.fPrimitives; - pave.fPrimitives = obj.fPrimitives; - pave.fNColumns = obj.fNColumns; - this.AutoPlace = opt === 'autoplace'; - if (oldprim?.arr?.length && (oldprim?.arr?.length === pave.fPrimitives?.arr?.length)) { - // try to sync object reference, new object does not displayed automatically - // in ideal case one should use snapids in the entries - for (let k = 0; k < oldprim.arr.length; ++k) { - const oldobj = oldprim.arr[k].fObject, newobj = pave.fPrimitives.arr[k].fObject; - if (oldobj && newobj && oldobj._typename === newobj._typename && oldobj.fName === newobj.fName) - pave.fPrimitives.arr[k].fObject = oldobj; - } - } - return true; - } - case clTPaletteAxis: - pave.fBorderSize = 1; - pave.fShadowColor = 0; - return true; + forEachFrame(userfunc) { + const ks = Object.keys(this.frames); + for (let k = 0; k < ks.length; ++k) { + const node = select('#' + ks[k]); + if (!node.empty()) + userfunc(node.node()); } - - return false; } - /** @summary redraw pave object */ - async redraw() { - return this.drawPave(); - } + createFrame(title) { + this.beforeCreateFrame(title); - /** @summary cleanup pave painter */ - cleanup() { - if (this.z_handle) { - this.z_handle.cleanup(); - delete this.z_handle; + const ks = Object.keys(this.frames); + for (let k = 0; k < ks.length; ++k) { + const items = this.frames[ks[k]]; + if (items.indexOf(title + ';') >= 0) + return select('#' + ks[k]).node(); } + return null; + } + cleanup() { super.cleanup(); + this.forEachFrame(frame => select(frame).html('')); } - /** @summary Returns true if object is supported */ - static canDraw(obj) { - const typ = obj?._typename; - return typ === clTPave || typ === clTPaveLabel || typ === clTPaveClass || typ === clTPaveStats || typ === clTPaveText || - typ === clTPavesText || typ === clTDiamond || typ === clTLegend || typ === clTPaletteAxis; - } +} // class CustomDisplay - /** @summary Draw TPave */ - static async draw(dom, pave, opt) { - const painter = new TPavePainter(dom, pave); +/** + * @summary Generic grid MDI display + * + * @private + */ - return ensureTCanvas(painter, false).then(() => { - if ((pave.fName === kTitle) && (pave._typename === clTPaveText)) { - const tpainter = painter.getPadPainter().findPainterFor(null, kTitle, clTPaveText); - if (tpainter && (tpainter !== painter)) { - tpainter.removeFromPadPrimitives(); - tpainter.cleanup(); - } else if ((opt === 'postitle') || painter.isDummyPos(pave)) { - const st = gStyle, fp = painter.getFramePainter(); - if (st && fp) { - const midx = st.fTitleX, y2 = st.fTitleY; - let w = st.fTitleW, h = st.fTitleH; - - if (!h) h = (y2 - fp.fY2NDC) * 0.7; - if (!w) w = fp.fX2NDC - fp.fX1NDC; - if (!Number.isFinite(h) || (h <= 0)) h = 0.06; - if (!Number.isFinite(w) || (w <= 0)) w = 0.44; - - pave.fX1NDC = midx - w/2; - pave.fY1NDC = y2 - h; - pave.fX2NDC = midx + w/2; - pave.fY2NDC = y2; - pave.fInit = 1; - } - } - } else if (pave._typename === clTPaletteAxis) { - pave.fBorderSize = 1; - pave.fShadowColor = 0; +class GridDisplay extends MDIDisplay { - // check some default values of TGaxis object, otherwise axis will not be drawn - if (pave.fAxis) { - if (!pave.fAxis.fChopt) pave.fAxis.fChopt = '+'; - if (!pave.fAxis.fNdiv) pave.fAxis.fNdiv = 12; - if (!pave.fAxis.fLabelOffset) pave.fAxis.fLabelOffset = 0.005; - } + /** @summary Create GridDisplay instance + * @param {string} frameid - where grid display is created + * @param {string} kind - kind of grid + * @desc following kinds are supported + * - vertical or horizontal - only first letter matters, defines basic orientation + * - 'x' in the name disable interactive separators + * - v4 or h4 - 4 equal elements in specified direction + * - v231 - created 3 vertical elements, first divided on 2, second on 3 and third on 1 part + * - v23_52 - create two vertical elements with 2 and 3 subitems, size ratio 5:2 + * - gridNxM - normal grid layout without interactive separators + * - gridiNxM - grid layout with interactive separators + * - simple - no layout, full frame used for object drawings */ + constructor(frameid, kind, kind2) { + super(frameid); - painter.z_handle = new TAxisPainter(dom, pave.fAxis, true); - painter.z_handle.setPadName(painter.getPadName()); + this.framecnt = 0; + this.getcnt = 0; + this.groups = []; + this.vertical = kind && (kind[0] === 'v'); + this.use_separarators = !kind || (kind.indexOf('x') < 0); + this.simple_layout = false; - painter.UseContextMenu = true; - } + const dom = this.selectDom(); + dom.style('overflow', 'hidden'); + + if (kind === 'simple') { + this.simple_layout = true; + this.use_separarators = false; + this.framecnt = 1; + return; + } - painter.NoFillStats = (opt === 'nofillstats') || (pave.fName !== 'stats'); + let num = 2, arr, sizes, chld_sizes; - switch (pave._typename) { - case clTPaveLabel: - case clTPaveClass: - painter.paveDrawFunc = painter.drawPaveLabel; - break; - case clTPaveStats: - painter.paveDrawFunc = painter.drawPaveStats; - break; - case clTPaveText: - case clTPavesText: - case clTDiamond: - painter.paveDrawFunc = painter.drawPaveText; - break; - case clTLegend: - painter.AutoPlace = (opt === 'autoplace'); - painter.paveDrawFunc = painter.drawLegend; - break; - case clTPaletteAxis: - painter.paveDrawFunc = painter.drawPaletteAxis; - break; + if (kind === 'projxy') { + this.vertical = false; + this.use_separarators = true; + arr = [2, 2]; + sizes = [1, 3]; + chld_sizes = [[3, 1], [3, 1]]; + kind = ''; + this.match_sizes = true; + } else if ((kind.indexOf('grid') === 0) || kind2) { + if (kind2) + kind = kind + 'x' + kind2; + else + kind = kind.slice(4).trim(); + this.use_separarators = false; + if (kind[0] === 'i') { + this.use_separarators = true; + kind = kind.slice(1); } - return painter.drawPave(opt); - }); - } + const separ = kind.indexOf('x'); + let sizex, sizey; -} // class TPavePainter + if (separ > 0) { + sizey = parseInt(kind.slice(separ + 1)); + sizex = parseInt(kind.slice(0, separ)); + } else + sizex = sizey = parseInt(kind); -var TPavePainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TPavePainter: TPavePainter -}); + if (!Number.isInteger(sizex)) + sizex = 3; + if (!Number.isInteger(sizey)) + sizey = 3; -const kCARTESIAN = 1, kPOLAR = 2, kCYLINDRICAL = 3, kSPHERICAL = 4, kRAPIDITY = 5; + if (sizey > 1) { + this.vertical = true; + num = sizey; + if (sizex > 1) + arr = new Array(num).fill(sizex); + } else if (sizex > 1) { + this.vertical = false; + num = sizex; + } else { + this.simple_layout = true; + this.use_separarators = false; + this.framecnt = 1; + return; + } + kind = ''; + } -/** - * @summary Class to decode histograms draw options - * - * @private - */ + if (kind && kind.indexOf('_') > 0) { + let arg = parseInt(kind.slice(kind.indexOf('_') + 1), 10); + if (Number.isInteger(arg) && (arg > 10)) { + kind = kind.slice(0, kind.indexOf('_')); + sizes = []; + while (arg > 0) { + sizes.unshift(Math.max(arg % 10, 1)); + arg = Math.round((arg - sizes[0]) / 10); + if (sizes[0] === 0) + sizes[0] = 1; + } + } + } -class THistDrawOptions { + kind = kind ? parseInt(kind.replace(/^\D+/g, ''), 10) : 0; + if (Number.isInteger(kind) && (kind > 1)) { + if (kind < 10) + num = kind; + else { + arr = []; + while (kind > 0) { + arr.unshift(kind % 10); + kind = Math.round((kind - arr[0]) / 10); + if (arr[0] === 0) + arr[0] = 1; + } + num = arr.length; + } + } - constructor() { this.reset(); } + if (sizes?.length !== num) + sizes = undefined; + if (chld_sizes?.length !== num) + chld_sizes = undefined; - /** @summary Reset hist draw options */ - reset() { - Object.assign(this, - { Axis: 0, RevX: false, RevY: false, SymlogX: 0, SymlogY: 0, - Bar: false, BarStyle: 0, Curve: false, - Hist: 1, Line: false, Fill: false, - Error: 0, ErrorKind: -1, errorX: gStyle.fErrorX, - Mark: false, Same: false, Scat: false, ScatCoef: 1.0, Func: true, AllFunc: false, - Arrow: false, Box: false, BoxStyle: 0, - Text: false, TextAngle: 0, TextKind: '', Char: 0, Color: false, Contour: 0, Cjust: false, - Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0, Ortho: gStyle.fOrthoCamera, - Spec: false, Pie: false, List: false, Zscale: false, Zvert: true, PadPalette: false, - Candle: '', Violin: '', Scaled: null, Circular: 0, - GLBox: 0, GLColor: false, Project: '', System: kCARTESIAN, - AutoColor: false, NoStat: false, ForceStat: false, PadStats: false, PadTitle: false, AutoZoom: false, - HighRes: 0, Zero: 1, Palette: 0, BaseLine: false, - Optimize: settings.OptimizeDraw, adjustFrame: false, - Mode3D: false, x3dscale: 1, y3dscale: 1, - Render3D: constants$1.Render3D.Default, - FrontBox: true, BackBox: true, - need_fillcol: false, - minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null, IgnoreMainScale: false }); + if (!this.simple_layout) + this.createGroup(this, dom, num, arr, sizes, chld_sizes); } - isCartesian() { return this.System === kCARTESIAN; } - - is3d() { return this.Lego || this.Surf; } - - /** @summary Base on sumw2 values (re)set some bacis draw options, only for 1dim hist */ - decodeSumw2(histo, force) { - const len = histo.fSumw2?.length ?? 0; - let isany = false; - for (let n = 0; n < len; ++n) - if (histo.fSumw2[n] > 0) { isany = true; break; } - - if (Number.isInteger(this.Error) || force) - this.Error = isany ? 1 : 0; + /** @summary Create frames group + * @private */ + createGroup(handle, main, num, childs, sizes, childs_sizes) { + if (!sizes) + sizes = new Array(num); + let sum1 = 0, sum2 = 0; + for (let n = 0; n < num; ++n) + sum1 += (sizes[n] || 1); + for (let n = 0; n < num; ++n) { + sizes[n] = Math.round(100 * (sizes[n] || 1) / sum1); + sum2 += sizes[n]; + if (n === num - 1) + sizes[n] += (100 - sum2); // make 100% + } - if (Number.isInteger(this.Hist) || force) - this.Hist = isany ? 0 : 1; + for (let cnt = 0; cnt < num; ++cnt) { + const group = { id: cnt, drawid: -1, position: 0, size: sizes[cnt], parent: handle }; + if (cnt > 0) + group.position = handle.groups[cnt - 1].position + handle.groups[cnt - 1].size; + group.position0 = group.position; - if (Number.isInteger(this.Zero) || force) - this.Zero = isany ? 0 : 1; - } + if (!childs || !childs[cnt] || childs[cnt] < 2) + group.drawid = this.framecnt++; - /** @summary Is palette can be used with current draw options */ - canHavePalette() { - if (this.ndim !== 2) - return false; + handle.groups.push(group); - if (this.Mode3D) - return this.Lego === 12 || this.Lego === 14 || this.Surf === 11 || this.Surf === 12; + const elem = main.append('div').attr('groupid', group.id); - if (this.Color || this.Contour || this.Hist || this.Axis) - return true; + // remember HTML node only when need to match sizes of different groups + if (handle.match_sizes) + group.node = elem.node(); - return !this.Scat && !this.Box && !this.Arrow && !this.Proj && !this.Candle && !this.Violin && !this.Text; - } + if (handle.vertical) + elem.style('float', 'bottom').style('height', group.size.toFixed(2) + '%').style('width', '100%'); + else + elem.style('float', 'left').style('width', group.size.toFixed(2) + '%').style('height', '100%'); - /** @summary Decode histogram draw options */ - decode(opt, hdim, histo, pp, pad, painter) { - this.orginal = opt; // will be overwritten by storeDrawOpt call + if (group.drawid >= 0) { + elem.classed('jsroot_newgrid', true); + if (isStr(this.frameid)) + elem.attr('id', `${this.frameid}_${group.drawid}`); + } else + elem.style('display', 'flex').style('flex-direction', handle.vertical ? 'row' : 'column'); - this.cutg_name = ''; - if (isStr(opt) && (hdim === 2)) { - const p1 = opt.lastIndexOf('['), p2 = opt.lastIndexOf(']'); - if ((p1 >= 0) && (p2 > p1+1)) { - this.cutg_name = opt.slice(p1+1, p2); - opt = opt.slice(0, p1) + opt.slice(p2+1); - this.cutg = pp?.findInPrimitives(this.cutg_name, clTCutG); - if (this.cutg) this.cutg.$redraw_pad = true; + if (childs && (childs[cnt] > 1)) { + group.vertical = !handle.vertical; + group.groups = []; + elem.style('overflow', 'hidden'); + this.createGroup(group, elem, childs[cnt], null, childs_sizes ? childs_sizes[cnt] : null); } } - const d = new DrawOptions(opt); - - if (hdim === 1) this.decodeSumw2(histo, true); - - this.ndim = hdim || 1; // keep dimensions, used for now in GED - - // for old web canvas json - // TODO: remove in version 8 - d.check('USE_PAD_TITLE'); - d.check('USE_PAD_PALETTE'); - d.check('USE_PAD_STATS'); + if (this.use_separarators && isFunc(this.createSeparator)) { + for (let cnt = 1; cnt < num; ++cnt) + this.createSeparator(handle, main, handle.groups[cnt]); + } + } - if (d.check('PAL', true)) - this.Palette = d.partAsInt(); - // this is zooming of histo content - if (d.check('MINIMUM:', true)) { - this.ominimum = true; - this.minimum = parseFloat(d.part); - } else { - this.ominimum = false; - this.minimum = histo.fMinimum; - } - if (d.check('MAXIMUM:', true)) { - this.omaximum = true; - this.maximum = parseFloat(d.part); - } else { - this.omaximum = false; - this.maximum = histo.fMaximum; - } - if (d.check('HMIN:', true)) { - this.ohmin = true; - this.hmin = parseFloat(d.part); - } else { - this.ohmin = false; - delete this.hmin; - } - if (d.check('HMAX:', true)) { - this.ohmax = true; - this.hmax = parseFloat(d.part); - } else { - this.ohmax = false; - delete this.hmax; - } + /** @summary Handle interactive separator movement + * @private */ + handleSeparator(elem, action) { + const findGroup = (node, grid) => { + let chld = node?.firstChild; + while (chld) { + if (chld.getAttribute('groupid') === grid) + return select(chld); + chld = chld.nextSibling; + } + // should never happen, but keep it here like + return select(node).select(`[groupid='${grid}']`); + }, setGroupSize = (h, node, grid) => { + const name = h.vertical ? 'height' : 'width', + size = h.groups[grid].size.toFixed(2) + '%'; + findGroup(node, grid).style(name, size) + .selectAll('.jsroot_separator').style(name, size); + }, resizeGroup = (node, grid) => { + let sel = findGroup(node, grid); + if (!sel.classed('jsroot_newgrid')) + sel = sel.select('.jsroot_newgrid'); + sel.each(function() { resize(this); }); + }, posSepar = (h, group, separ) => { + separ.style(h.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`); + }; + // eslint-disable-next-line one-var + const separ = select(elem), + parent = elem.parentNode, + handle = separ.property('handle'), + id = separ.property('separator_id'), + group = handle.groups[id]; - // let configure histogram titles - only for debug purposes - if (d.check('HTITLE:', true)) histo.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('XTITLE:', true)) histo.fXaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('YTITLE:', true)) histo.fYaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('ZTITLE:', true)) histo.fZaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); + if (action === 'start') { + group.startpos = group.position; + group.acc_drag = 0; + return; + } - if (d.check('_ADJUST_FRAME_')) this.adjustFrame = true; + let needResize, needSetSize = false; - if (d.check('NOOPTIMIZE')) this.Optimize = 0; - if (d.check('OPTIMIZE')) this.Optimize = 2; + if (action === 'end') { + if (Math.abs(group.startpos - group.position) < 0.5) + return; + needResize = true; + } else { + let pos; + if (action === 'restore') + pos = group.position0; + else if (handle.vertical) { + group.acc_drag += action.dy; + pos = group.startpos + ((group.acc_drag + 2) / parent.clientHeight) * 100; + } else { + group.acc_drag += action.dx; + pos = group.startpos + ((group.acc_drag + 2) / parent.clientWidth) * 100; + } - if (d.check('AUTOCOL')) this.AutoColor = true; - if (d.check('AUTOZOOM')) this.AutoZoom = true; + const diff = group.position - pos; + if ((Math.abs(diff) < 0.3) || (Math.min(handle.groups[id - 1].size - diff, group.size + diff) < 3)) + return; // if no significant change, do nothing - if (d.check('OPTSTAT', true)) this.optstat = d.partAsInt(); - if (d.check('OPTFIT', true)) this.optfit = d.partAsInt(); + handle.groups[id - 1].size -= diff; + group.size += diff; + group.position = pos; - if ((this.optstat || this.optstat) && histo?.TestBit(kNoStats)) - histo?.InvertBit(kNoStats); + posSepar(handle, group, separ); - if (d.check('NOSTAT')) this.NoStat = true; - if (d.check('STAT')) this.ForceStat = true; + needSetSize = true; + needResize = (action === 'restore'); + } - if (d.check('NOTOOLTIP') && painter) painter.setTooltipAllowed(false); - if (d.check('TOOLTIP') && painter) painter.setTooltipAllowed(true); + if (needSetSize) { + setGroupSize(handle, parent, id - 1); + setGroupSize(handle, parent, id); + } - if (d.check('SYMLOGX', true)) this.SymlogX = d.partAsInt(0, 3); - if (d.check('SYMLOGY', true)) this.SymlogY = d.partAsInt(0, 3); + if (needResize) { + resizeGroup(parent, id - 1); + resizeGroup(parent, id); + } - if (d.check('X3DSC', true)) this.x3dscale = d.partAsInt(0, 100) / 100; - if (d.check('Y3DSC', true)) this.y3dscale = d.partAsInt(0, 100) / 100; + // now handling match of the sizes + if (!handle.parent?.match_sizes) + return; - if (d.check('PERSPECTIVE') || d.check('PERSP')) this.Ortho = false; - if (d.check('ORTHO')) this.Ortho = true; + for (let k = 0; k < handle.parent.groups.length; ++k) { + const hh = handle.parent.groups[k]; + if ((hh === handle) || !hh.node) + continue; + hh.groups[id].size = handle.groups[id].size; + hh.groups[id].position = handle.groups[id].position; + hh.groups[id - 1].size = handle.groups[id - 1].size; + hh.groups[id - 1].position = handle.groups[id - 1].position; + if (needSetSize) { + select(hh.node).selectAll('.jsroot_separator').each(function() { + const s = select(this); + if (s.property('separator_id') === id) + posSepar(hh, hh.groups[id], s); + }); + setGroupSize(hh, hh.node, id - 1); + setGroupSize(hh, hh.node, id); + } + if (needResize) { + resizeGroup(hh.node, id - 1); + resizeGroup(hh.node, id); + } + } + } - let lx = 0, ly = 0, check3dbox = ''; - if (d.check('LOG2XY')) lx = ly = 2; - if (d.check('LOGXY')) lx = ly = 1; - if (d.check('LOG2X')) lx = 2; - if (d.check('LOGX')) lx = 1; - if (d.check('LOG2Y')) ly = 2; - if (d.check('LOGY')) ly = 1; - if (lx && pad) { pad.fLogx = lx; pad.fUxmin = 0; pad.fUxmax = 1; pad.fX1 = 0; pad.fX2 = 1; } - if (ly && pad) { pad.fLogy = ly; pad.fUymin = 0; pad.fUymax = 1; pad.fY1 = 0; pad.fY2 = 1; } - if (d.check('LOG2Z') && pad) pad.fLogz = 2; - if (d.check('LOGZ') && pad) pad.fLogz = 1; - if (d.check('LOGV') && pad) pad.fLogv = 1; // ficitional member, can be introduced in ROOT - if (d.check('GRIDXY') && pad) pad.fGridx = pad.fGridy = 1; - if (d.check('GRIDX') && pad) pad.fGridx = 1; - if (d.check('GRIDY') && pad) pad.fGridy = 1; - if (d.check('TICKXY') && pad) pad.fTickx = pad.fTicky = 1; - if (d.check('TICKX') && pad) pad.fTickx = 1; - if (d.check('TICKY') && pad) pad.fTicky = 1; - if (d.check('TICKZ') && pad) pad.fTickz = 1; - if (d.check('GRAYSCALE')) - pp?.setGrayscale(true); + /** @summary Create group separator + * @private */ + createSeparator(handle, main, group) { + const separ = main.append('div'); - if (d.check('FILL_', 'color')) { - this.histoFillColor = d.color; - this.histoFillPattern = 1001; - } + separ.classed('jsroot_separator', true) + .property('handle', handle) + .property('separator_id', group.id) + .attr('style', 'pointer-events: all; border: 0; margin: 0; padding: 0; position: absolute;') + .style(handle.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`) + .style(handle.vertical ? 'width' : 'height', (handle.size?.toFixed(2) || 100) + '%') + .style(handle.vertical ? 'height' : 'width', '5px') + .style('cursor', handle.vertical ? 'ns-resize' : 'ew-resize') + .append('div').attr('style', 'position: absolute;' + (handle.vertical + ? 'left: 0; right: 0; top: 50%; height: 3px; border-top: 1px dotted #ff0000' + : 'top: 0; bottom: 0; left: 50%; width: 3px; border-left: 1px dotted #ff0000')); - if (d.check('LINE_', 'color')) - this.histoLineColor = getColor(d.color); + const pthis = this, drag_move = + drag().on('start', function() { pthis.handleSeparator(this, 'start'); }) + .on('drag', function(evnt) { pthis.handleSeparator(this, evnt); }) + .on('end', function() { pthis.handleSeparator(this, 'end'); }); - if (d.check('XAXIS_', 'color')) - histo.fXaxis.fAxisColor = histo.fXaxis.fLabelColor = histo.fXaxis.fTitleColor = d.color; + separ.call(drag_move).on('dblclick', function() { pthis.handleSeparator(this, 'restore'); }); - if (d.check('YAXIS_', 'color')) - histo.fYaxis.fAxisColor = histo.fYaxis.fLabelColor = histo.fYaxis.fTitleColor = d.color; + // need to get touches events handling in drag + if (browser.touches && !main.on('touchmove')) + main.on('touchmove', () => {}); + } - const has_main = painter ? !!painter.getMainPainter() : false; + /** @summary Call function for each frame */ + forEachFrame(userfunc) { + if (this.simple_layout) + userfunc(this.getGridFrame()); + else + this.selectDom().selectAll('.jsroot_newgrid').each(function() { userfunc(this); }); + } - if (d.check('X+')) { this.AxisPos = 10; this.second_x = has_main; } - if (d.check('Y+')) { this.AxisPos += 1; this.second_y = has_main; } + /** @summary Returns active frame */ + getActiveFrame() { + if (this.simple_layout) + return this.getGridFrame(); - if (d.check('SAME0')) { this.Same = true; this.IgnoreMainScale = true; } - if (d.check('SAMES')) { this.Same = true; this.ForceStat = true; } - if (d.check('SAME')) { this.Same = true; this.Func = true; } + let found = super.getActiveFrame(); + this.forEachFrame(frame => { + if (!found) + found = frame; + }); - if (d.check('SPEC')) this.Spec = true; // not used + return found; + } - if (d.check('BASE0') || d.check('MIN0')) - this.BaseLine = 0; - else if (gStyle.fHistMinimumZero) - this.BaseLine = 0; + /** @summary Returns number of frames in grid layout */ + numGridFrames() { return this.framecnt; } - if (d.check('PIE')) this.Pie = true; // not used + /** @summary Return grid frame by its id */ + getGridFrame(id) { + if (this.simple_layout) + return this.selectDom('origin').node(); + let res = null; + this.selectDom().selectAll('.jsroot_newgrid').each(function() { + if (id-- === 0) + res = this; + }); + return res; + } - if (d.check('CANDLE', true)) this.Candle = d.part || '1'; - if (d.check('VIOLIN', true)) { this.Violin = d.part || '1'; delete this.Candle; } - if (d.check('NOSCALED')) this.Scaled = false; - if (d.check('SCALED')) this.Scaled = true; + /** @summary Create new frame */ + createFrame(title) { + this.beforeCreateFrame(title); - if (d.check('GLBOX', true)) this.GLBox = 10 + d.partAsInt(); - if (d.check('GLCOL')) this.GLColor = true; + let frame = null, maxloop = this.framecnt || 2; - d.check('GL'); // suppress GL + while (!frame && maxloop--) { + frame = this.getGridFrame(this.getcnt); + if (!this.simple_layout && this.framecnt) + this.getcnt = (this.getcnt + 1) % this.framecnt; - if (d.check('CIRCULAR', true) || d.check('CIRC', true)) { - this.Circular = 11; - if (d.part.indexOf('0') >= 0) this.Circular = 10; // black and white - if (d.part.indexOf('1') >= 0) this.Circular = 11; // color - if (d.part.indexOf('2') >= 0) this.Circular = 12; // color and width + if (select(frame).classed('jsroot_fixed_frame')) + frame = null; } - this.Chord = d.check('CHORD'); - - if (d.check('LEGO', true)) { - this.Lego = 1; - if (d.part.indexOf('0') >= 0) this.Zero = false; - if (d.part.indexOf('1') >= 0) this.Lego = 11; - if (d.part.indexOf('2') >= 0) this.Lego = 12; - if (d.part.indexOf('3') >= 0) this.Lego = 13; - if (d.part.indexOf('4') >= 0) this.Lego = 14; - check3dbox = d.part; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; + if (frame) { + this.cleanupFrame(frame); + select(frame).attr('frame_title', title); } - if (d.check('R3D_', true)) - this.Render3D = constants$1.Render3D.fromString(d.part.toLowerCase()); + return this.afterCreateFrame(frame); + } - if (d.check('POL')) this.System = kPOLAR; - if (d.check('CYL')) this.System = kCYLINDRICAL; - if (d.check('SPH')) this.System = kSPHERICAL; - if (d.check('PSR')) this.System = kRAPIDITY; +} // class GridDisplay - if (d.check('SURF', true)) { - this.Surf = d.partAsInt(10, 1); - check3dbox = d.part; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; - } - if (d.check('TF3', true)) check3dbox = d.part; +// ================================================ - if (d.check('ISO', true)) check3dbox = d.part; +/** + * @summary Tabs-based display + * + * @private + */ - if (d.check('LIST')) this.List = true; // not used +class TabsDisplay extends MDIDisplay { - if (d.check('CONT', true) && (hdim > 1)) { - this.Contour = 1; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; - if (d.part.indexOf('1') >= 0) this.Contour = 11; else - if (d.part.indexOf('2') >= 0) this.Contour = 12; else - if (d.part.indexOf('3') >= 0) this.Contour = 13; else - if (d.part.indexOf('4') >= 0) this.Contour = 14; - } + constructor(frameid) { + super(frameid); + this.cnt = 0; // use to count newly created frames + this.selectDom().style('overflow', 'hidden'); + } - // decode bar/hbar option - if (d.check('HBAR', true)) - this.BarStyle = 20; - else if (d.check('BAR', true)) - this.BarStyle = 10; - if (this.BarStyle > 0) { - this.Hist = false; - this.need_fillcol = true; - this.BarStyle += d.partAsInt(); - } + /** @summary Cleanup all drawings */ + cleanup() { + this.selectDom().style('overflow', null); + this.cnt = 0; + super.cleanup(); + } - if (d.check('ARR')) - this.Arrow = true; + /** @summary call function for each frame */ + forEachFrame(userfunc, only_visible) { + if (!isFunc(userfunc)) + return; - if (d.check('BOX', true)) { - this.BoxStyle = 10; - if (d.part.indexOf('1') >= 0) this.BoxStyle = 11; else - if (d.part.indexOf('2') >= 0) this.BoxStyle = 12; else - if (d.part.indexOf('3') >= 0) this.BoxStyle = 13; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; + if (only_visible) { + const active = this.getActiveFrame(); + if (active) + userfunc(active); + return; } - this.Box = this.BoxStyle > 0; + const main = this.selectDom().select('.jsroot_tabs_main'); - if (d.check('CJUST')) this.Cjust = true; - if (d.check('COL')) this.Color = true; - if (d.check('CHAR')) this.Char = 1; - if (d.check('ALLFUNC')) this.AllFunc = true; - if (d.check('FUNC')) { this.Func = true; this.Hist = false; } - if (d.check('AXIS')) this.Axis = 1; - if (d.check('AXIG')) this.Axis = 2; + main.selectAll('.jsroot_tabs_draw').each(function() { + userfunc(this); + }); + } - if (d.check('TEXT', true)) { - this.Text = true; - this.Hist = false; - this.TextAngle = Math.min(d.partAsInt(), 90); - if (d.part.indexOf('N') >= 0) this.TextKind = 'N'; - if (d.part.indexOf('E0') >= 0) this.TextLine = true; - if (d.part.indexOf('E') >= 0) this.TextKind = 'E'; - } + /** @summary modify tab state by id */ + modifyTabsFrame(frame_id, action) { + const top = this.selectDom().select('.jsroot_tabs'), + labels = top.select('.jsroot_tabs_labels'), + main = top.select('.jsroot_tabs_main'); - if (d.check('SCAT=', true)) { - this.Scat = true; - this.ScatCoef = parseFloat(d.part); - if (!Number.isFinite(this.ScatCoef) || (this.ScatCoef <= 0)) this.ScatCoef = 1.0; - } + labels.selectAll('.jsroot_tabs_label').each(function() { + const id = select(this).property('frame_id'), + is_same = (id === frame_id), + active_color = settings.DarkMode ? '#333' : 'white'; - if (d.check('SCAT')) this.Scat = true; + if (action === 'activate') { + select(this).style('background', is_same ? active_color : (settings.DarkMode ? 'black' : '#ddd')) + .style('color', settings.DarkMode ? '#ddd' : 'inherit') + .style('border-color', active_color); + } else if ((action === 'close') && is_same) + this.parentNode.remove(); + }); - if (d.check('TRI', true)) { - this.Color = false; - this.Tri = 1; - check3dbox = d.part; - if (d.part.indexOf('ERR') >= 0) this.Error = true; - } + let selected_frame, other_frame; - if (d.check('AITOFF')) this.Proj = 1; - if (d.check('MERCATOR')) this.Proj = 2; - if (d.check('SINUSOIDAL')) this.Proj = 3; - if (d.check('PARABOLIC')) this.Proj = 4; - if (d.check('MOLLWEIDE')) this.Proj = 5; - if (this.Proj > 0) this.Contour = 14; + main.selectAll('.jsroot_tabs_draw').each(function() { + const match = select(this).property('frame_id') === frame_id; + if (match) + selected_frame = this; + else + other_frame = this; + if (action === 'activate') + select(this).style('background', settings.DarkMode ? 'black' : 'white'); + }); - if (d.check('PROJXY', true)) this.Project = 'XY' + d.partAsInt(0, 1); - if (d.check('PROJX', true)) this.Project = 'X' + d.part; - if (d.check('PROJY', true)) this.Project = 'Y' + d.part; - if (d.check('PROJ')) this.Project = 'Y1'; + if (!selected_frame) + return; - if (check3dbox) { - if (check3dbox.indexOf('FB') >= 0) this.FrontBox = false; - if (check3dbox.indexOf('BB') >= 0) this.BackBox = false; + if (action === 'activate') + selected_frame.parentNode.appendChild(selected_frame); + // super.activateFrame(selected_frame); + else if (action === 'close') { + const was_active = (selected_frame === this.getActiveFrame()); + cleanup(selected_frame); + selected_frame.remove(); + + if (was_active) + this.activateFrame(other_frame); } + } - if ((hdim === 3) && d.check('FB')) this.FrontBox = false; - if ((hdim === 3) && d.check('BB')) this.BackBox = false; + /** @summary activate frame */ + activateFrame(frame) { + if (frame) + this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); + super.activateFrame(frame); + } - if (d.check('PFC') && !this._pfc) - this._pfc = 2; - if ((d.check('PLC') || this.AutoColor) && !this._plc) - this._plc = 2; - if (d.check('PMC') && !this._pmc) - this._pmc = 2; + /** @summary create new frame */ + createFrame(title) { + this.beforeCreateFrame(title); - const check_axis_bit = (opt, axis, bit) => { - // ignore Z scale options for 2D plots - if ((axis === 'fZaxis') && (hdim < 3) && !this.Lego && !this.Surf) - return; - let flag = d.check(opt); - if (pad && pad['$'+opt]) { flag = true; pad['$'+opt] = undefined; } - if (flag && histo) { - if (!histo[axis].TestBit(bit)) - histo[axis].InvertBit(bit); - } - }; + const dom = this.selectDom(); + let top = dom.select('.jsroot_tabs'), labels, main; - check_axis_bit('OTX', 'fXaxis', EAxisBits.kOppositeTitle); - check_axis_bit('OTY', 'fYaxis', EAxisBits.kOppositeTitle); - check_axis_bit('OTZ', 'fZaxis', EAxisBits.kOppositeTitle); - check_axis_bit('CTX', 'fXaxis', EAxisBits.kCenterTitle); - check_axis_bit('CTY', 'fYaxis', EAxisBits.kCenterTitle); - check_axis_bit('CTZ', 'fZaxis', EAxisBits.kCenterTitle); - check_axis_bit('MLX', 'fXaxis', EAxisBits.kMoreLogLabels); - check_axis_bit('MLY', 'fYaxis', EAxisBits.kMoreLogLabels); - check_axis_bit('MLZ', 'fZaxis', EAxisBits.kMoreLogLabels); + if (top.empty()) { + top = dom.append('div').attr('class', 'jsroot_tabs') + .attr('style', 'display: flex; flex-direction: column; position: absolute; overflow: hidden; left: 0px; top: 0px; bottom: 0px; right: 0px;'); + labels = top.append('div').attr('class', 'jsroot_tabs_labels') + .attr('style', 'white-space: nowrap; position: relative; overflow-x: auto'); + main = top.append('div').attr('class', 'jsroot_tabs_main') + .attr('style', 'margin: 0; flex: 1 1 0%; position: relative'); + } else { + labels = top.select('.jsroot_tabs_labels'); + main = top.select('.jsroot_tabs_main'); + } - if (d.check('RX') || pad?.$RX) this.RevX = true; - if (d.check('RY') || pad?.$RY) this.RevY = true; + const frame_id = this.cnt++, mdi = this; + let lbl = title; - if (d.check('L')) { this.Line = true; this.Hist = false; } - if (d.check('F')) { this.Fill = true; this.need_fillcol = true; } + if (!lbl || !isStr(lbl)) + lbl = `frame_${frame_id}`; - if (d.check('A')) this.Axis = -1; - if (pad?.$ratio_pad === 'up') { - if (!this.Same) this.Axis = 0; // draw both axes - histo.fXaxis.fLabelSize = 0; - histo.fXaxis.fTitle = ''; - histo.fYaxis.$use_top_pad = true; - } else if (pad?.$ratio_pad === 'low') { - if (!this.Same) this.Axis = 0; // draw both axes - histo.fXaxis.$use_top_pad = true; - histo.fYaxis.$use_top_pad = true; - histo.fXaxis.fTitle = 'x'; - const fp = painter?.getCanvPainter().findPainterFor(null, 'upper_pad', clTPad)?.getFramePainter(); - if (fp) { - painter.zoom_xmin = fp.scale_xmin; - painter.zoom_xmax = fp.scale_xmax; - } + if (lbl.length > 15) { + let p = lbl.lastIndexOf('/'); + if (p === lbl.length - 1) + p = lbl.lastIndexOf('/', p - 1); + if ((p > 0) && (lbl.length - p < 20) && (lbl.length - p > 1)) + lbl = lbl.slice(p + 1); + else + lbl = '...' + lbl.slice(lbl.length - 17); } - if (d.check('B1')) { this.BarStyle = 1; this.BaseLine = 0; this.Hist = false; this.need_fillcol = true; } - if (d.check('B')) { this.BarStyle = 1; this.Hist = false; this.need_fillcol = true; } - if (d.check('C')) { this.Curve = true; this.Hist = false; } - if (d.check('][')) { this.Off = 1; this.Hist = true; } + labels.append('span') + .attr('tabindex', 0) + .append('label') + .attr('class', 'jsroot_tabs_label') + .attr('style', 'border: 1px solid; display: inline-block; font-size: 1rem; left: 1px;' + + 'margin-left: 3px; padding: 0px 5px 1px 5px; position: relative; vertical-align: bottom;') + .property('frame_id', frame_id) + .text(lbl) + .attr('title', title) + .on('click', function(evnt) { + evnt.preventDefault(); // prevent handling in close button + mdi.modifyTabsFrame(select(this).property('frame_id'), 'activate'); + }).append('button') + .attr('title', 'close') + .attr('style', 'margin-left: .5em; padding: 0; font-size: 0.5em; width: 1.8em; height: 1.8em; vertical-align: center;') + .text('\u2715') + .on('click', function() { + mdi.modifyTabsFrame(select(this.parentNode).property('frame_id'), 'close'); + }); - if (d.check('HIST')) { this.Hist = true; this.Func = true; this.Error = false; } + const draw_frame = main.append('div') + .attr('frame_title', title) + .attr('class', 'jsroot_tabs_draw') + .attr('style', 'overflow: hidden; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px;') + .property('frame_id', frame_id); - this.Bar = (this.BarStyle > 0); + this.modifyTabsFrame(frame_id, 'activate'); - delete this.MarkStyle; // remove mark style if any + return this.afterCreateFrame(draw_frame.node()); + } - if (d.check('P0')) { this.Mark = true; this.Hist = false; this.Zero = true; } - if (d.check('P')) { this.Mark = true; this.Hist = false; this.Zero = false; } - if (d.check('HZ')) { this.Zscale = true; this.Zvert = false; } - if (d.check('Z')) this.Zscale = true; - if (d.check('*')) { this.Mark = true; this.MarkStyle = 3; this.Hist = false; } - if (d.check('H')) this.Hist = true; + /** @summary Handle changes in dark mode */ + changeDarkMode() { + const frame = this.getActiveFrame(); + this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); + } - if (d.check('E', true)) { - this.Error = true; - if (hdim === 1) { - this.Zero = false; // do not draw empty bins with errors - if (this.Hist === 1) this.Hist = false; - if (Number.isInteger(parseInt(d.part[0]))) this.ErrorKind = parseInt(d.part[0]); - if ((this.ErrorKind === 3) || (this.ErrorKind === 4)) this.need_fillcol = true; - if (this.ErrorKind === 0) this.Zero = true; // enable drawing of empty bins - if (d.part.indexOf('X0') >= 0) this.errorX = 0; - } - } - if (d.check('9')) this.HighRes = 1; - if (d.check('0')) this.Zero = false; - if (this.Color && d.check('1')) this.Zero = false; +} // class TabsDisplay - // flag identifies 3D drawing mode for histogram - if ((this.Lego > 0) || (hdim === 3) || - (((this.Surf > 0) || this.Error) && (hdim === 2))) this.Mode3D = true; - // default draw options for TF1 is line and fill - if (painter?.isTF1() && (hdim === 1) && (this.Hist === 1) && !this.Line && !this.Fill && !this.Curve && !this.Mark) { - this.Hist = false; - this.Curve = settings.FuncAsCurve; - this.Line = !this.Curve; - this.Fill = true; - } +/** + * @summary Generic flexible MDI display + * + * @private + */ - if ((this.Surf === 15) && (this.System === kPOLAR || this.System === kCARTESIAN)) - this.Surf = 13; +class FlexibleDisplay extends MDIDisplay { + + constructor(frameid) { + super(frameid); + this.cnt = 0; // use to count newly created frames + this.selectDom().on('contextmenu', evnt => this.showContextMenu(evnt)) + .style('overflow', 'auto'); } - /** @summary Tries to reconstruct string with hist draw options */ - asString(is_main_hist, pad) { - let res = '', zopt = ''; - if (this.Zscale) - zopt = this.Zvert ? 'Z' : 'HZ'; - if (this.Mode3D) { - if (this.Lego) { - res = 'LEGO'; - if (!this.Zero) res += '0'; - if (this.Lego > 10) res += (this.Lego-10); - res += zopt; - } else if (this.Surf) { - res = 'SURF' + (this.Surf-10); - res += zopt; - } - if (!this.FrontBox) res += 'FB'; - if (!this.BackBox) res += 'BB'; + /** @summary Cleanup all drawings */ + cleanup() { + this.selectDom().style('overflow', null) + .on('contextmenu', null); + this.cnt = 0; + super.cleanup(); + } - if (this.x3dscale !== 1) res += `_X3DSC${Math.round(this.x3dscale * 100)}`; - if (this.y3dscale !== 1) res += `_Y3DSC${Math.round(this.y3dscale * 100)}`; - } else { - if (this.Candle) - res = 'CANDLE' + this.Candle; - else if (this.Violin) - res = 'VIOLIN' + this.Violin; - else if (this.Scat) - res = 'SCAT'; - else if (this.Color) { - res = 'COL'; - if (!this.Zero) res += '0'; - res += zopt; - if (this.Axis < 0) res += 'A'; - } else if (this.Contour) { - res = 'CONT'; - if (this.Contour > 10) res += (this.Contour-10); - res += zopt; - } else if (this.Bar) - res = (this.BaseLine === false) ? 'B' : 'B1'; - else if (this.Mark) - res = this.Zero ? 'P0' : 'P'; // here invert logic with 0 - else if (this.Error) { - res = 'E'; - if (this.ErrorKind >= 0) res += this.ErrorKind; - } else if (this.Line) { - res += 'L'; - if (this.Fill) res += 'F'; - } else if (this.Off) - res = ']['; + /** @summary call function for each frame */ + forEachFrame(userfunc, only_visible) { + if (!isFunc(userfunc)) + return; - if (this.Cjust) res += ' CJUST'; + const mdi = this, top = this.selectDom().select('.jsroot_flex_top'); - if (this.Text) { - res += 'TEXT'; - if (this.TextAngle) res += this.TextAngle; - res += this.TextKind; - } - } + top.selectAll('.jsroot_flex_draw').each(function() { + // check if only visible specified + if (only_visible && (mdi.getFrameState(this) === 'min')) + return; - if (this.Palette && this.canHavePalette()) - res += `_PAL${this.Palette}`; + userfunc(this); + }); + } - if (this.is3d() && this.Ortho && is_main_hist) - res += '_ORTHO'; + /** @summary return active frame */ + getActiveFrame() { + let found = super.getActiveFrame(); + if (found && select(found.parentNode).property('state') !== 'min') + return found; - if (this.Same) - res += this.ForceStat ? 'SAMES' : 'SAME'; - else if (is_main_hist && res) { - if (this.ForceStat || (this.StatEnabled === true)) - res += '_STAT'; - else if (this.NoStat || (this.StatEnabled === false)) - res += '_NOSTAT'; - } + found = null; + this.forEachFrame(frame => { found = frame; }, true); + return found; + } - if (is_main_hist && pad && res) { - if (pad.fLogx === 2) - res += '_LOG2X'; - else if (pad.fLogx) - res += '_LOGX'; - if (pad.fLogy === 2) - res += '_LOG2Y'; - else if (pad.fLogy) - res += '_LOGY'; - if (pad.fLogz === 2) - res += '_LOG2Z'; - else if (pad.fLogz) - res += '_LOGZ'; - if (pad.fGridx) res += '_GRIDX'; - if (pad.fGridy) res += '_GRIDY'; - if (pad.fTickx) res += '_TICKX'; - if (pad.fTicky) res += '_TICKY'; - if (pad.fTickz) res += '_TICKZ'; + /** @summary activate frame */ + activateFrame(frame) { + if ((frame === 'first') || (frame === 'last')) { + let res = null; + this.forEachFrame(f => { + if (frame === 'last' || !res) + res = f; + }, true); + frame = res; } + if ((frame?.getAttribute('class') !== 'jsroot_flex_draw') || (this.getActiveFrame() === frame)) + return; - if (this.cutg_name) - res += ` [${this.cutg_name}]`; + super.activateFrame(frame); - return res; + const main = frame.parentNode; + main.parentNode.append(main); + + if (this.getFrameState(frame) !== 'min') { + selectActivePad({ pp: getElementCanvPainter(frame), active: true }); + resize(frame); + } } -} // class THistDrawOptions + /** @summary get frame state */ + getFrameState(frame) { + const main = select(frame.parentNode); + return main.property('state'); + } + /** @summary returns frame rect */ + getFrameRect(frame) { + if (this.getFrameState(frame) === 'max') { + const top = this.selectDom().select('.jsroot_flex_top'); + return { x: 0, y: 0, w: top.node().clientWidth, h: top.node().clientHeight }; + } -/** - * @summary Handle for histogram contour - * - * @private - */ + const main = select(frame.parentNode), left = main.style('left'), top = main.style('top'); + return { + x: parseInt(left.slice(0, left.length - 2)), y: parseInt(top.slice(0, top.length - 2)), + w: main.node().clientWidth, h: main.node().clientHeight + }; + } -class HistContour { + /** @summary change frame state */ + changeFrameState(frame, newstate, no_redraw) { + const main = select(frame.parentNode), + state = main.property('state'), + top = this.selectDom().select('.jsroot_flex_top'); - constructor(zmin, zmax) { - this.arr = []; - this.colzmin = zmin; - this.colzmax = zmax; - this.below_min_indx = -1; - this.exact_min_indx = 0; - } + if (state === newstate) + return false; - /** @summary Returns contour levels */ - getLevels() { return this.arr; } + if (state === 'normal') + main.property('original_style', main.attr('style')); - /** @summary Create normal contour levels */ - createNormal(nlevels, log_scale, zminpositive) { - if (log_scale) { - if (this.colzmax <= 0) - this.colzmax = 1.0; - if (this.colzmin <= 0) { - if ((zminpositive === undefined) || (zminpositive <= 0)) - this.colzmin = 0.0001*this.colzmax; - else - this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3*zminpositive : 1; - } - if (this.colzmin >= this.colzmax) - this.colzmin = 0.0001*this.colzmax; + // clear any previous settings + top.style('overflow', null); - const logmin = Math.log(this.colzmin)/Math.log(10), - logmax = Math.log(this.colzmax)/Math.log(10), - dz = (logmax-logmin)/nlevels; - this.arr.push(this.colzmin); - for (let level = 1; level < nlevels; level++) - this.arr.push(Math.exp((logmin + dz*level)*Math.log(10))); - this.arr.push(this.colzmax); - this.custom = true; - } else { - if ((this.colzmin === this.colzmax) && (this.colzmin !== 0)) { - this.colzmax += 0.01*Math.abs(this.colzmax); - this.colzmin -= 0.01*Math.abs(this.colzmin); - } - const dz = (this.colzmax-this.colzmin)/nlevels; - for (let level = 0; level <= nlevels; level++) - this.arr.push(this.colzmin + dz*level); + switch (newstate) { + case 'min': + main.style('height', 'auto').style('width', 'auto'); + main.select('.jsroot_flex_draw').style('display', 'none'); + break; + case 'max': + main.style('height', '100%').style('width', '100%').style('left', '').style('top', ''); + main.select('.jsroot_flex_draw').style('display', null); + top.style('overflow', 'hidden'); + break; + default: + main.select('.jsroot_flex_draw').style('display', null); + main.attr('style', main.property('original_style')); } - } - /** @summary Create custom contour levels */ - createCustom(levels) { - this.custom = true; - for (let n = 0; n < levels.length; ++n) - this.arr.push(levels[n]); + main.select('.jsroot_flex_header').selectAll('button').each(function(d) { + const btn = select(this); + if (((d.t === 'minimize') && (newstate === 'min')) || + ((d.t === 'maximize') && (newstate === 'max'))) + btn.text('\u259E').attr('title', 'restore'); + else + btn.text(d.n).attr('title', d.t); + }); - if (this.colzmax > this.arr[this.arr.length-1]) - this.arr.push(this.colzmax); - } + main.property('state', newstate); + main.select('.jsroot_flex_resize').style('display', (newstate === 'normal') ? null : 'none'); - /** @summary Configure indicies */ - configIndicies(below_min, exact_min) { - this.below_min_indx = below_min; - this.exact_min_indx = exact_min; - } + // adjust position of new minified rect + if (newstate === 'min') { + const rect = this.getFrameRect(frame), + ww = top.node().clientWidth, + hh = top.node().clientHeight, + arr = [], step = 4, + crossX = (r1, r2) => ((r1.x <= r2.x) && (r1.x + r1.w >= r2.x)) || ((r2.x <= r1.x) && (r2.x + r2.w >= r1.x)), + crossY = (r1, r2) => ((r1.y <= r2.y) && (r1.y + r1.h >= r2.y)) || ((r2.y <= r1.y) && (r2.y + r2.h >= r1.y)); - /** @summary Get index based on z value */ - getContourIndex(zc) { - // bins less than zmin not drawn - if (zc < this.colzmin) return this.below_min_indx; + this.forEachFrame(f => { + if ((f !== frame) && (this.getFrameState(f) === 'min')) + arr.push(this.getFrameRect(f)); + }); - // if bin content exactly zmin, draw it when col0 specified or when content is positive - if (zc === this.colzmin) return this.exact_min_indx; + rect.y = hh; + do { + rect.x = step; + rect.y -= rect.h + step; + let maxx = step, iscrossed = false; + arr.forEach(r => { + if (crossY(r, rect)) { + maxx = Math.max(maxx, r.x + r.w + step); + if (crossX(r, rect)) + iscrossed = true; + } + }); + if (iscrossed) + rect.x = maxx; + } while ((rect.x + rect.w > ww - step) && (rect.y > 0)); + if (rect.y < 0) { + rect.x = step; + rect.y = hh - rect.h - step; + } - if (!this.custom) - return Math.floor(0.01+(zc-this.colzmin)*(this.arr.length-1)/(this.colzmax-this.colzmin)); + main.style('left', rect.x + 'px').style('top', rect.y + 'px'); + } else if (!no_redraw) + resize(frame); - let l = 0, r = this.arr.length-1; - if (zc < this.arr[0]) return -1; - if (zc >= this.arr[r]) return r; - while (l < r-1) { - const mid = Math.round((l+r)/2); - if (this.arr[mid] > zc) r = mid; else l = mid; - } - return l; + return true; } - /** @summary Get palette color */ - getPaletteColor(palette, zc) { - const zindx = this.getContourIndex(zc); - if (zindx < 0) return null; - const pindx = palette.calcColorIndex(zindx, this.arr.length); - return palette.getColor(pindx); - } + /** @summary handle button click + * @private */ + _clickButton(btn) { + const kind = select(btn).datum(), + main = select(btn.parentNode.parentNode), + frame = main.select('.jsroot_flex_draw').node(); - /** @summary Get palette index */ - getPaletteIndex(palette, zc) { - const zindx = this.getContourIndex(zc); - return (zindx < 0) ? null : palette.calcColorIndex(zindx, this.arr.length); + if (kind.t === 'close') { + this.cleanupFrame(frame); + main.remove(); + this.activateFrame('last'); // set active as last non-minified window + return; + } + + const state = main.property('state'); + let newstate; + if (kind.t === 'maximize') + newstate = (state === 'max') ? 'normal' : 'max'; + else + newstate = (state === 'min') ? 'normal' : 'min'; + + if (this.changeFrameState(frame, newstate)) + this.activateFrame(newstate !== 'min' ? frame : 'last'); } -} // class HistContour + /** @summary create new frame */ + createFrame(title) { + this.beforeCreateFrame(title); -/** - * @summary Handle for updateing of secondary functions - * - * @private - */ + const mdi = this, + dom = this.selectDom(); + let top = dom.select('.jsroot_flex_top'); -class FunctionsHandler { + if (top.empty()) { + top = dom.append('div') + .attr('class', 'jsroot_flex_top') + .attr('style', 'overflow: auto; position: relative; height: 100%; width: 100%'); + } - constructor(painter, pp, funcs, statpainter) { - this.painter = painter; - this.pp = pp; + const w = top.node().clientWidth, + h = top.node().clientHeight, + main = top.append('div'); - const painters = [], update_painters = [], - only_draw = (statpainter === true); + main.html('
' + + '

' + + `
` + + '
'); - this.newfuncs = []; - this.newopts = []; + main.select('.jsroot_flex_header p').text(title); - // find painters associated with histogram/graph/... - if (!only_draw) { - pp?.forEachPainterInPad(objp => { - if (objp.isSecondary(painter) && objp._secondary_id?.match(/^func_|^indx_/)) - painters.push(objp); - }, 'objects'); - } + main.attr('class', 'jsroot_flex_frame') + .style('position', 'absolute') + .style('left', Math.round(w * (this.cnt % 5) / 10) + 'px') + .style('top', Math.round(h * (this.cnt % 5) / 10) + 'px') + .style('width', Math.round(w * 0.58) + 'px') + .style('height', Math.round(h * 0.58) + 'px') + .style('border', '1px solid black') + .style('box-shadow', '1px 1px 2px 2px #aaa') + .property('state', 'normal') + .select('.jsroot_flex_header') + .on('contextmenu', evnt => mdi.showContextMenu(evnt, true)) + .on('click', function() { mdi.activateFrame(select(this.parentNode).select('.jsroot_flex_draw').node()); }) + .selectAll('button') + .data([{ n: '\u2715', t: 'close' }, { n: '\u2594', t: 'maximize' }, { n: '\u2581', t: 'minimize' }]) + .enter() + .append('button') + .attr('type', 'button') + .attr('style', 'float: right; padding: 0; width: 1.4em; text-align: center; font-size: 10px; margin-top: 2px; margin-right: 4px') + .attr('title', d => d.t) + .text(d => d.n) + .on('click', function() { mdi._clickButton(this); }); - for (let n = 0; n < funcs?.arr.length; ++n) { - const func = funcs.arr[n], fopt = funcs.opt[n]; - if (!func?._typename) continue; - if (isFunc(painter.needDrawFunc) && !painter.needDrawFunc(painter.getObject(), func)) continue; + let moving_frame = null, moving_div = null, doing_move = false, current = []; + const drag_object = drag().subject(Object); + drag_object.on('start', function(evnt) { + if (evnt.sourceEvent.target.type === 'button') + return mdi._clickButton(evnt.sourceEvent.target); - let funcpainter = null, func_indx = -1; + if (detectRightButton(evnt.sourceEvent)) + return; - if (!only_draw) { - // try to find matching object in associated list of painters - for (let i = 0; i < painters.length; ++i) { - if (painters[i].matchObjectType(func._typename) && (painters[i].getObjectName() === func.fName)) { - funcpainter = painters[i]; - func_indx = i; - break; - } - } - // or just in generic list of painted objects - if (!funcpainter && func.fName) - funcpainter = pp?.findPainterFor(null, func.fName, func._typename); + const mframe = select(this.parentNode); + if (!mframe.classed('jsroot_flex_frame') || (mframe.property('state') === 'max')) + return; + + doing_move = !select(this).classed('jsroot_flex_resize'); + if (!doing_move && (mframe.property('state') === 'min')) + return; + + mdi.activateFrame(mframe.select('.jsroot_flex_draw').node()); + + moving_div = top.append('div').attr('style', mframe.attr('style')).style('border', '2px dotted #00F'); + + if (mframe.property('state') === 'min') { + moving_div.style('width', mframe.node().clientWidth + 'px') + .style('height', mframe.node().clientHeight + 'px'); } - if (funcpainter) { - funcpainter.updateObject(func, fopt); - if (func_indx >= 0) { - painters.splice(func_indx, 1); - update_painters.push(funcpainter); - } + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + + moving_frame = mframe; + current = []; + }).on('drag', evnt => { + if (!moving_div) + return; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + const changeProp = (i, name, dd) => { + if (i >= current.length) { + const v = moving_div.style(name); + current[i] = parseInt(v.slice(0, v.length - 2)); + } + current[i] += dd; + moving_div.style(name, Math.max(0, current[i]) + 'px'); + }; + if (doing_move) { + changeProp(0, 'left', evnt.dx); + changeProp(1, 'top', evnt.dy); } else { - // use arrays index while index is important - this.newfuncs[n] = func; - this.newopts[n] = fopt; + changeProp(0, 'width', evnt.dx); + changeProp(1, 'height', evnt.dy); } - } + }).on('end', evnt => { + if (!moving_div) + return; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); + if (doing_move) { + moving_frame.style('left', moving_div.style('left')); + moving_frame.style('top', moving_div.style('top')); + } else { + moving_frame.style('width', moving_div.style('width')); + moving_frame.style('height', moving_div.style('height')); + } + moving_div.remove(); + moving_div = null; + if (!doing_move) + resize(moving_frame.select('.jsroot_flex_draw').node()); + }); - // stat painter has to be kept even when no object exists in the list - if (isObject(statpainter)) { - const indx = painters.indexOf(statpainter); - if (indx >= 0) painters.splice(indx, 1); - } + main.select('.jsroot_flex_header').call(drag_object); + main.select('.jsroot_flex_resize').call(drag_object); - // remove all function which are not found in new list of functions - if (painters.length > 0) - pp?.cleanPrimitives(p => painters.indexOf(p) >= 0); + const draw_frame = main.select('.jsroot_flex_draw') + .attr('frame_title', title) + .property('frame_cnt', this.cnt++) + .node(); - if (update_painters.length > 0) - this._extraPainters = update_painters; + return this.afterCreateFrame(draw_frame); } - /** @summary Draw/update functions selected before */ - drawNext(indx) { - if (this._extraPainters) { - const p = this._extraPainters.shift(); - if (this._extraPainters.length === 0) - delete this._extraPainters; - return getPromise(p.redraw()).then(() => this.drawNext(0)); - } + /** @summary minimize all frames */ + minimizeAll() { + this.forEachFrame(frame => this.changeFrameState(frame, 'min')); + } + + /** @summary show all frames which are minimized */ + showAll() { + this.forEachFrame(frame => { + if (this.getFrameState(frame) === 'min') + this.changeFrameState(frame, 'normal'); + }); + } + + /** @summary close all frames */ + closeAllFrames() { + const arr = []; + this.forEachFrame(frame => arr.push(frame)); + arr.forEach(frame => { + this.cleanupFrame(frame); + select(frame.parentNode).remove(); + }); + } + + /** @summary cascade frames */ + sortFrames(kind) { + const arr = []; + this.forEachFrame(frame => { + const state = this.getFrameState(frame); + if (state === 'min') + return; + if (state === 'max') + this.changeFrameState(frame, 'normal', true); + arr.push(frame); + }); + + if (!arr.length) + return; + + const top = this.selectDom(), + w = top.node().clientWidth, + h = top.node().clientHeight, + dx = Math.min(40, Math.round(w * 0.4 / arr.length)), + dy = Math.min(40, Math.round(h * 0.4 / arr.length)); + let nx = Math.ceil(Math.sqrt(arr.length)), ny = nx; - if (!this.newfuncs || (indx >= this.newfuncs.length)) { - delete this.newfuncs; - delete this.newopts; - return Promise.resolve(this.painter); // simplify drawing + // calculate number of divisions for 'tile' sorting + if ((nx > 1) && (nx * (nx - 1) >= arr.length)) { + if (w > h) + ny--; + else + nx--; } - const func = this.newfuncs[indx], fopt = this.newopts[indx]; + arr.forEach((frame, i) => { + const main = select(frame.parentNode); + if (kind === 'cascade') { + main.style('left', (i * dx) + 'px') + .style('top', (i * dy) + 'px') + .style('width', Math.round(w * 0.58) + 'px') + .style('height', Math.round(h * 0.58) + 'px'); + } else { + main.style('left', Math.round(w / nx * (i % nx)) + 'px') + .style('top', Math.round(h / ny * ((i - i % nx) / nx)) + 'px') + .style('width', Math.round(w / nx - 4) + 'px') + .style('height', Math.round(h / ny - 4) + 'px'); + } + resize(frame); + }); + } - if (!func || this.pp?.findPainterFor(func)) - return this.drawNext(indx+1); + /** @summary context menu */ + showContextMenu(evnt, is_header) { + // no context menu for no windows + if (this.numDraw() === 0) + return; + // handle context menu only for MDI area or for window header + if (!is_header && evnt.target.getAttribute('class') !== 'jsroot_flex_top') + return; - const func_secondary_id = func?.fName ? `func_${func.fName}` : `indx_${indx}`; + evnt.preventDefault(); - // Required to correctly draw multiple stats boxes - // TODO: set reference via weak pointer - func.$main_painter = this.painter; + const arr = []; + let nummin = 0; + this.forEachFrame(f => { + arr.push(f); + if (this.getFrameState(f) === 'min') + nummin++; + }); + const active = this.getActiveFrame(); - const promise = TPavePainter.canDraw(func) - ? TPavePainter.draw(this.painter.getDom(), func, fopt) - : this.pp.drawObject(this.painter.getDom(), func, fopt); + arr.sort((f1, f2) => (select(f1).property('frame_cnt') < select(f2).property('frame_cnt') ? -1 : 1)); - return promise.then(fpainter => { - fpainter.setSecondaryId(this.painter, func_secondary_id); + createMenu(evnt, this).then(menu => { + menu.header('Flex'); + menu.add('Cascade', () => this.sortFrames('cascade'), 'Cascade frames'); + menu.add('Tile', () => this.sortFrames('tile'), 'Tile all frames'); + if (nummin < arr.length) + menu.add('Minimize all', () => this.minimizeAll(), 'Minimize all frames'); + if (nummin > 0) + menu.add('Show all', () => this.showAll(), 'Restore minimized frames'); + menu.add('Close all', () => this.closeAllFrames()); + menu.separator(); + + arr.forEach((f, i) => { + menu.addchk((f === active), ((this.getFrameState(f) === 'min') ? '[min] ' : '') + select(f).attr('frame_title'), i, arg => { + const frame = arr[arg]; + if (this.getFrameState(frame) === 'min') + this.changeFrameState(frame, 'normal'); + this.activateFrame(frame); + }); + }); - return this.drawNext(indx+1); + menu.show(); }); } -} // class FunctionsHandler - +} // class FlexibleDisplay -// TH1 bits -// kNoStats = BIT(9), don't draw stats box -const 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) /** - * @summary Basic painter for histogram classes + * @summary Batch MDI display + * + * @desc Can be used together with hierarchy painter in node.js * @private */ -class THistPainter extends ObjectPainter { +class BatchDisplay extends MDIDisplay { - /** @summary Constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} histo - TH1 derived histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.draw_content = true; - this.nbinsx = this.nbinsy = 0; - this.accept_drops = true; // indicate that one can drop other objects like doing Draw('same') - this.mode3d = false; + constructor(width, height, jsdom_body) { + super('$batch$'); + this.frames = []; // array of configured frames + this.width = width || settings.CanvasWidth; + this.height = height || settings.CanvasHeight; + this.jsdom_body = jsdom_body || select('body'); // d3 body handle } - /** @summary Returns histogram object */ - getHisto() { - return this.getObject(); + /** @summary Call function for each frame */ + forEachFrame(userfunc) { + this.frames.forEach(userfunc); } - /** @summary Returns histogram axis */ - getAxis(name) { - const histo = this.getObject(); - switch (name) { - case 'x': return histo?.fXaxis; - case 'y': return histo?.fYaxis; - case 'z': return histo?.fZaxis; - } - return null; - } + /** @summary Create batch frame */ + createFrame(title) { + this.beforeCreateFrame(title); - /** @summary Returns true if TProfile */ - isTProfile() { - return this.matchObjectType(clTProfile); - } + const frame = + this.jsdom_body.append('div') + .style('visible', 'hidden') + .attr('width', this.width).attr('height', this.height) + .style('width', this.width + 'px').style('height', this.height + 'px') + .attr('id', 'jsroot_batch_' + this.frames.length) + .attr('frame_title', title); - /** @summary Returns true if histogram drawn instead of TF1/TF2 object */ - isTF1() { return false; } + this.frames.push(frame.node()); - /** @summary Returns true if TH1K */ - isTH1K() { - return this.matchObjectType('TH1K'); + return this.afterCreateFrame(frame.node()); } - /** @summary Returns true if TH2Poly */ - isTH2Poly() { - return this.matchObjectType(/^TH2Poly/) || this.matchObjectType(/^TProfile2Poly/); - } + /** @summary Create final frame */ + createFinalBatchFrame() { + const cnt = this.numFrames(), prs = []; - /** @summary Clear 3d drawings - if any */ - clear3DScene() { - const fp = this.getFramePainter(); - if (isFunc(fp?.create3DScene)) - fp.create3DScene(-1); - this.mode3d = false; - } + for (let n = 0; n < cnt; ++n) { + const json = this.makeJSON(n, 1, true); + if (json) + select(this.frames[n]).text('json:' + btoa_func(json)); + else + prs.push(this.makeSVG(n, true)); + } - /** @summary Cleanup histogram painter */ - cleanup() { - this.clear3DScene(); + return Promise.all(prs).then(() => { + this.jsdom_body.append('div') + .attr('id', 'jsroot_batch_final') + .text(`${cnt}`); + }); + } - delete this._color_palette; - delete this.fContour; - delete this.options; + /** @summary Returns number of created frames */ + numFrames() { return this.frames.length; } - super.cleanup(); + /** @summary returns JSON representation if any + * @desc Now works only for inspector, can be called once */ + makeJSON(id, spacing, keep_frame) { + const frame = this.frames[id]; + if (!frame) + return; + const obj = select(frame).property('_json_object_'); + if (obj) { + select(frame).property('_json_object_', null); + cleanup(frame); + if (!keep_frame) + select(frame).remove(); + return toJSON(obj, spacing); + } } - /** @summary Returns number of histogram dimensions */ - getDimension() { - const histo = this.getHisto(); - if (!histo) return 0; - if (histo._typename.match(/^TH2/)) return 2; - if (histo._typename === clTProfile2D) return 2; - if (histo._typename.match(/^TH3/)) return 3; - if (histo._typename === clTProfile3D) return 3; - if (this.isTH2Poly()) return 2; - return 1; - } + /** @summary Create SVG for specified frame id - used in testing */ + makeSVG(id, keep_frame) { + const frame = this.frames[id]; + if (!frame) + return; + const main = select(frame), + mainsvg = main.select('svg'); + if (mainsvg.empty()) + return; - /** @summary Decode options string opt and fill the option structure */ - decodeOptions(opt) { - const histo = this.getHisto(), - hdim = this.getDimension(), - pp = this.getPadPainter(), - pad = pp?.getRootPad(true); + const style_filter = mainsvg.style('filter'); - if (!this.options) - this.options = new THistDrawOptions(); - else - this.options.reset(); + mainsvg.attr('xmlns', nsSVG) + .attr('title', null).attr('style', null).attr('class', null).attr('x', null).attr('y', null); - // when changing draw option, reset attributes usage - this.lineatt?.setUsed(false); - this.fillatt?.setUsed(false); - this.markeratt?.setUsed(false); + if (!mainsvg.attr('width') && !mainsvg.attr('height')) + mainsvg.attr('width', this.width).attr('height', this.height); - this.options.decode(opt || histo.fOption, hdim, histo, pp, pad, this); + if (style_filter) + mainsvg.style('filter', style_filter); - this.storeDrawOpt(opt); // opt will be return as default draw option, used in webcanvas - } + function clear_element() { + const elem = select(this); + if (elem.style('display') === 'none') + elem.remove(); + } - /** @summary Copy draw options from other painter */ - copyOptionsFrom(src) { - if (src === this) return; - const o = this.options, o0 = src.options; + main.selectAll('g.root_frame').each(clear_element); + main.selectAll('svg').each(clear_element); - o.Mode3D = o0.Mode3D; - o.Zero = o0.Zero; - if (o0.Mode3D) { - o.Lego = o0.Lego; - o.Surf = o0.Surf; - } else { - o.Color = o0.Color; - o.Contour = o0.Contour; + if (internals.batch_png) { + return svgToImage(compressSVG(main.html()), 'png').then(href => { + select(this.frames[id]).text('png:' + href); + }); } - } - /** @summary copy draw options to all other histograms in the pad */ - copyOptionsToOthers() { - this.forEachPainter(painter => { - if ((painter !== this) && isFunc(painter.copyOptionsFrom)) - painter.copyOptionsFrom(this); - }, 'objects'); - } + if (keep_frame) + return true; - /** @summary Scan histogram content - * @abstract */ - scanContent(/* when_axis_changed */) { - // function will be called once new histogram or - // new histogram content is assigned - // one should find min,max,nbins, maxcontent values - // if when_axis_changed === true specified, content will be scanned after axis zoom changed - } + const svg = compressSVG(main.html()); - /** @summary Check pad ranges when drawing of frame axes will be performed - * @desc Only if histogram is main painter and drawn with SAME option, pad range can be used - * In all other cases configured range must be derived from histogram itself */ - checkPadRange() { - if (this.isMainPainter()) - this.check_pad_range = this.options.Same ? 'pad_range' : true; + cleanup(frame); + main.remove(); + return svg; } - /** @summary Create necessary histogram draw attributes */ - createHistDrawAttributes(only_check_auto) { - const histo = this.getHisto(), o = this.options; +} // class BatchDisplay - if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { - const pp = this.getPadPainter(); - if (isFunc(pp?.getAutoColor)) { - const icolor = pp.getAutoColor(histo.$num_histos); - this._auto_exec = ''; // can be reused when sending option back to server - if (o._pfc > 1) { o._pfc = 1; histo.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (o._plc > 1) { o._plc = 1; histo.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (o._pmc > 1) { o._pmc = 1; histo.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } - } - } - if (only_check_auto) - this.deleteAttr(); - else { - this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); - this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); - } +/** + * @summary Special browser layout + * + * @desc Contains three different areas for browser (left), status line (bottom) and central drawing + * Main application is normal browser, but also used in other applications like ROOT6 canvas + * @private + */ + +class BrowserLayout { + + #float_left; + #float_top; + #max_left; + #max_top; + #float_width; + #float_height; + #max_width; + #max_height; + #hsepar_position; + #vsepar_position; + #hsepar_move; + #vsepar_move; + + /** @summary Constructor */ + constructor(id, hpainter, objpainter) { + this.gui_div = id; + this.hpainter = hpainter; // painter for browser area (if any) + this.objpainter = objpainter; // painter for object area (if any) + this.browser_kind = null; // should be 'float' or 'fix' } - /** @summary Update axes attributes in target histogram - * @private */ - updateAxes(tgt_histo, src_histo, fp) { - const copyTAxisMembers = (tgt, src, copy_zoom) => { - tgt.fTitle = src.fTitle; - tgt.fLabels = src.fLabels; - tgt.fXmin = src.fXmin; - tgt.fXmax = src.fXmax; - tgt.fTimeDisplay = src.fTimeDisplay; - tgt.fTimeFormat = src.fTimeFormat; - tgt.fAxisColor = src.fAxisColor; - tgt.fLabelColor = src.fLabelColor; - tgt.fLabelFont = src.fLabelFont; - tgt.fLabelOffset = src.fLabelOffset; - tgt.fLabelSize = src.fLabelSize; - tgt.fNdivisions = src.fNdivisions; - tgt.fTickLength = src.fTickLength; - tgt.fTitleColor = src.fTitleColor; - tgt.fTitleFont = src.fTitleFont; - tgt.fTitleOffset = src.fTitleOffset; - tgt.fTitleSize = src.fTitleSize; - if (copy_zoom) { - tgt.fFirst = src.fFirst; - tgt.fLast = src.fLast; - tgt.fBits = src.fBits; - } - }; - - copyTAxisMembers(tgt_histo.fXaxis, src_histo.fXaxis, this.snapid && !fp?.zoomChangedInteractive('x')); - copyTAxisMembers(tgt_histo.fYaxis, src_histo.fYaxis, this.snapid && !fp?.zoomChangedInteractive('y')); - copyTAxisMembers(tgt_histo.fZaxis, src_histo.fZaxis, this.snapid && !fp?.zoomChangedInteractive('z')); - } - - /** @summary Update histogram object - * @param obj - new histogram instance - * @param opt - new drawing option (optional) - * @return {Boolean} - true if histogram was successfully updated */ - updateObject(obj, opt) { - const histo = this.getHisto(), - fp = this.getFramePainter(), - pp = this.getPadPainter(), - o = this.options; + /** @summary Selects main element */ + main() { return select('#' + this.gui_div); } - if (obj !== histo) { - if (!this.matchObjectType(obj)) return false; + /** @summary Selects browser div */ + browser() { return this.main().select('.jsroot_browser'); } - // simple replace of object does not help - one can have different - // complex relations between histo and stat box, histo and colz axis, - // one could have THStack or TMultiGraph object - // The only that could be done is update of content + /** @summary Selects drawing div */ + drawing() { return select(`#${this.gui_div}_drawing`); } - // check only stats bit, later other settings can be monitored - const statpainter = pp?.findPainterFor(this.findStat()); - if (histo.TestBit(kNoStats) !== obj.TestBit(kNoStats)) { - histo.fBits = obj.fBits; - if (statpainter) statpainter.Enabled = !histo.TestBit(kNoStats); - } + /** @summary Selects drawing div */ + status() { return select(`#${this.gui_div}_status`); } - // special treatment for webcanvas - also name can be changed - if (this.snapid !== undefined) { - histo.fName = obj.fName; - o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas - } + /** @summary Returns drawing divid */ + drawing_divid() { return this.gui_div + '_drawing'; } - if (!o._pfc) - histo.fFillColor = obj.fFillColor; - histo.fFillStyle = obj.fFillStyle; - if (!o._plc) - histo.fLineColor = obj.fLineColor; - histo.fLineStyle = obj.fLineStyle; - histo.fLineWidth = obj.fLineWidth; - if (!o._pmc) - histo.fMarkerColor = obj.fMarkerColor; - histo.fMarkerSize = obj.fMarkerSize; - histo.fMarkerStyle = obj.fMarkerStyle; + /** @summary Check resize action */ + checkResize() { + if (isFunc(this.hpainter?.checkResize)) + this.hpainter.checkResize(); + else if (isFunc(this.objpainter?.checkResize)) + this.objpainter.checkResize(true); + } - histo.fEntries = obj.fEntries; - histo.fTsumw = obj.fTsumw; - histo.fTsumwx = obj.fTsumwx; - histo.fTsumwx2 = obj.fTsumwx2; - histo.fXaxis.fNbins = obj.fXaxis.fNbins; - if (this.getDimension() > 1) { - histo.fTsumwy = obj.fTsumwy; - histo.fTsumwy2 = obj.fTsumwy2; - histo.fTsumwxy = obj.fTsumwxy; - histo.fYaxis.fNbins = obj.fYaxis.fNbins; - if (this.getDimension() > 2) { - histo.fTsumwz = obj.fTsumwz; - histo.fTsumwz2 = obj.fTsumwz2; - histo.fTsumwxz = obj.fTsumwxz; - histo.fTsumwyz = obj.fTsumwyz; - histo.fZaxis.fNbins = obj.fZaxis.fNbins; - } - } + /** @summary Create or update CSS style */ + createStyle() { + const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', + title_color = settings.DarkMode ? '#ccc' : 'inherit', + text_color = settings.DarkMode ? '#ddd' : 'inherit', + input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; - this.updateAxes(histo, obj, fp); + injectStyle( + '.jsroot_browser { pointer-events: none; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; margin: 0px; border: 0px; overflow: hidden; }' + + `.jsroot_draw_area { background-color: ${bkgr_color}; overflow: hidden; margin: 0px; border: 0px; }` + + `.jsroot_browser_area { color: ${text_color}; background-color: ${bkgr_color}; font-size: 12px; font-family: Verdana; pointer-events: all; box-sizing: initial; }` + + `.jsroot_browser_area input { ${input_style} }` + + `.jsroot_browser_area select { ${input_style} }` + + `.jsroot_browser_title { font-family: Verdana; font-size: 20px; color: ${title_color}; }` + + '.jsroot_browser_btns { pointer-events: all; display: flex; flex-direction: column; }' + + '.jsroot_browser_area p { margin-top: 5px; margin-bottom: 5px; white-space: nowrap; }' + + '.jsroot_browser_hierarchy { flex: 1; margin-top: 2px; }' + + `.jsroot_status_area { background-color: ${bkgr_color}; overflow: hidden; font-size: 12px; font-family: Verdana; pointer-events: all; }` + + '.jsroot_browser_resize { position: absolute; right: 3px; bottom: 3px; margin-bottom: 0px; margin-right: 0px; opacity: 0.5; cursor: se-resize; z-index: 1; }', + this.main().node(), 'browser_layout_style'); + } - histo.fArray = obj.fArray; - histo.fNcells = obj.fNcells; - histo.fTitle = obj.fTitle; - histo.fMinimum = obj.fMinimum; - histo.fMaximum = obj.fMaximum; - histo.fSumw2 = obj.fSumw2; + /** @summary method used to create basic elements + * @desc should be called only once */ + create(with_browser) { + const main = this.main(); - if (this.getDimension() === 1) - o.decodeSumw2(histo); + main.append('div').attr('id', this.drawing_divid()) + .classed('jsroot_draw_area', true) + .style('position', 'absolute') + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - if (this.isTProfile()) - histo.fBinEntries = obj.fBinEntries; - else if (this.isTH1K()) { - histo.fNIn = obj.fNIn; - histo.fReady = 0; - } else if (this.isTH2Poly()) - histo.fBins = obj.fBins; + if (with_browser) + main.append('div').classed('jsroot_browser', true); - // remove old functions, update existing, prepare to draw new one - this._funcHandler = new FunctionsHandler(this, pp, obj.fFunctions, statpainter); + this.createStyle(); + } - const changed_opt = (histo.fOption !== obj.fOption); - histo.fOption = obj.fOption; + /** @summary Create buttons in the layout */ + createBrowserBtns() { + const br = this.browser(); + if (br.empty()) + return; + let btns = br.select('.jsroot_browser_btns'); + if (btns.empty()) { + btns = br.append('div') + .attr('class', 'jsroot jsroot_browser_btns') + .attr('style', 'position: absolute; left: 7px; top: 7px'); + } else + btns.html(''); + return btns; + } - if (((opt !== undefined) && (o.original !== opt)) || changed_opt) - this.decodeOptions(opt || histo.fOption); - } + /** @summary Remove browser buttons */ + removeBrowserBtns() { + this.browser().select('.jsroot_browser_btns').remove(); + } - if (!o.ominimum) - o.minimum = histo.fMinimum; - if (!o.omaximum) - o.maximum = histo.fMaximum; + /** @summary Set browser content */ + setBrowserContent(guiCode) { + const main = this.browser(); + if (main.empty()) + return; - if (!fp || !fp.zoomChangedInteractive()) - this.checkPadRange(); + main.insert('div', '.jsroot_browser_btns').classed('jsroot_browser_area', true) + .style('position', 'absolute').style('left', '0px').style('top', '0px').style('bottom', '0px').style('width', '250px') + .style('overflow', 'hidden') + .style('padding-left', '5px') + .style('display', 'flex').style('flex-direction', 'column') /* use the flex model */ + .html(`

title

${guiCode}`); + } - this.scanContent(); + /** @summary Check if there is browser content */ + hasContent() { + const main = this.browser(); + return main.empty() ? false : !main.select('.jsroot_browser_area').empty(); + } - this.histogram_updated = true; // indicate that object updated + /** @summary Delete content */ + deleteContent(keep_status) { + const main = this.browser(); + if (main.empty()) + return; - return true; - } + if (!keep_status) + this.createStatusLine(0, 'delete'); - /** @summary Extract axes bins and ranges - * @desc here functions are defined to convert index to axis value and back - * was introduced to support non-equidistant bins */ - extractAxesProperties(ndim) { - const assignTAxisFuncs = axis => { - if (axis.fXbins.length >= axis.fNbins) { - axis.GetBinCoord = function(bin) { - const indx = Math.round(bin); - if (indx <= 0) return this.fXmin; - if (indx > this.fNbins) return this.fXmax; - if (indx === bin) return this.fXbins[indx]; - const indx2 = (bin < indx) ? indx - 1 : indx + 1; - return this.fXbins[indx] * Math.abs(bin-indx2) + this.fXbins[indx2] * Math.abs(bin-indx); - }; - axis.FindBin = function(x, add) { - for (let k = 1; k < this.fXbins.length; ++k) - if (x < this.fXbins[k]) return Math.floor(k-1+add); - return this.fNbins; - }; - } else { - axis.$binwidth = (axis.fXmax - axis.fXmin) / (axis.fNbins || 1); - axis.GetBinCoord = function(bin) { return this.fXmin + bin*this.$binwidth; }; - axis.FindBin = function(x, add) { return Math.floor((x - this.fXmin) / this.$binwidth + add); }; - } - }; + this.toggleBrowserVisisbility(true); - this.nbinsx = this.nbinsy = this.nbinsz = 0; + if (keep_status) { + // try to delete only content, not status + main.select('.jsroot_browser_area').remove(); + main.select('.jsroot_browser_btns').remove(); + main.select('.jsroot_v_separator').remove(); + } else + main.selectAll('*').remove(); - const histo = this.getHisto(); + delete this.browser_visible; + delete this.browser_kind; - this.nbinsx = histo.fXaxis.fNbins; - this.xmin = histo.fXaxis.fXmin; - this.xmax = histo.fXaxis.fXmax; - assignTAxisFuncs(histo.fXaxis); + this.checkResize(); + } - this.ymin = histo.fYaxis.fXmin; - this.ymax = histo.fYaxis.fXmax; + /** @summary Returns true when status line exists */ + hasStatus() { + const main = this.browser(); + return main.empty() ? false : !this.status().empty(); + } - this._exact_y_range = (ndim === 1) && this.options.ohmin && this.options.ohmax; + /** @summary Set browser title text + * @desc Title also used for dragging of the float browser */ + setBrowserTitle(title) { + const main = this.browser(), + elem = !main.empty() ? main.select('.jsroot_browser_title') : null; + if (elem) + elem.text(title).style('cursor', this.browser_kind === 'flex' ? 'move' : null); + return elem; + } - if (this._exact_y_range) { - this.ymin = this.options.hmin; - this.ymax = this.options.hmax; + /** @summary Toggle browser kind + * @desc used together with browser buttons */ + toggleKind(browser_kind) { + if (this.browser_visible !== 'changing') { + if (browser_kind === this.browser_kind) + this.toggleBrowserVisisbility(); + else + this.toggleBrowserKind(browser_kind); } + } - if (ndim > 1) { - this.nbinsy = histo.fYaxis.fNbins; - assignTAxisFuncs(histo.fYaxis); + /** @summary Creates status line */ + async createStatusLine(height, mode) { + const main = this.browser(); + if (main.empty()) + return ''; - this.zmin = histo.fZaxis.fXmin; - this.zmax = histo.fZaxis.fXmax; + const id = this.gui_div + '_status', + line = select('#' + id), + is_visible = !line.empty(); - if ((ndim === 2) && this.options.ohmin && this.options.ohmax) { - this.zmin = this.options.hmin; - this.zmax = this.options.hmax; - } + if (mode === 'toggle') + mode = !is_visible; + else if (mode === 'delete') { + mode = false; + height = 0; + delete this.status_layout; + } else if (mode === undefined) { + mode = true; + this.status_layout = 'app'; } - if (ndim > 2) { - this.nbinsz = histo.fZaxis.fNbins; - assignTAxisFuncs(histo.fZaxis); - } - } + if (is_visible) { + if (mode === true) + return id; - /** @summary Draw axes for histogram - * @desc axes can be drawn only for main histogram */ - async drawAxes() { - const fp = this.getFramePainter(); - if (!fp) return false; + const hsepar = main.select('.jsroot_h_separator'); - const histo = this.getHisto(); + hsepar.remove(); + line.remove(); - // artificially add y range to display axes - if (this.ymin === this.ymax) this.ymax += 1; + if (this.status_layout !== 'app') + delete this.status_layout; - if (!this.isMainPainter()) { - const opts = { - second_x: (this.options.AxisPos >= 10), - second_y: (this.options.AxisPos % 10) === 1, - hist_painter: this - }; + if (this.status_handler && (internals.showStatus === this.status_handler)) { + delete internals.showStatus; + delete this.status_handler; + } - if ((!opts.second_x && !opts.second_y) || fp.hasDrawnAxes(opts.second_x, opts.second_y)) - return false; + this.adjustSeparators(null, 0, true); + return ''; + } - fp.setAxes2Ranges(opts.second_x, histo.fXaxis, this.xmin, this.xmax, opts.second_y, histo.fYaxis, this.ymin, this.ymax); + if (mode === false) + return ''; - fp.createXY2(opts); + const left_pos = this.drawing().style('left'); - return fp.drawAxes2(opts.second_x, opts.second_y); - } + main.insert('div', '.jsroot_browser_area') + .attr('id', id) + .classed('jsroot_status_area', true) + .style('position', 'absolute').style('left', left_pos).style('height', '20px').style('bottom', '0px').style('right', '0px') + .style('margin', 0).style('border', 0); - if (this.options.adjustFrame) { - const pad = this.getPadPainter().getRootPad(); - if (pad) { - if (pad.fUxmin < pad.fUxmax) { - fp.fX1NDC = (this.xmin - pad.fUxmin) / (pad.fUxmax - pad.fUxmin); - fp.fX2NDC = (this.xmax - pad.fUxmin) / (pad.fUxmax - pad.fUxmin); - } - if (pad.fUymin < pad.fUymax) { - fp.fY1NDC = (this.ymin - pad.fUymin) / (pad.fUymax - pad.fUymin); - fp.fY2NDC = (this.ymax - pad.fUymin) / (pad.fUymax - pad.fUymin); - } + const separ_color = settings.DarkMode ? 'grey' : 'azure', + hsepar = main.insert('div', '.jsroot_browser_area') + .classed('jsroot_h_separator', true) + .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; left: ${left_pos}; right: 0; bottom: 20px; height: 5px; cursor: ns-resize;`), - pad.fLeftMargin = fp.fX1NDC; - pad.fRightMargin = 1 - fp.fX2NDC; - pad.fBottomMargin = fp.fY1NDC; - pad.fTopMargin = 1 - fp.fY2NDC; - pad.fFrameLineColor = 0; - pad.fFrameLineWidth = 0; - fp.setRootPadRange(pad); + drag_move = drag().on('start', () => { + this.#hsepar_move = this.#hsepar_position; + hsepar.style('background-color', 'grey'); + }).on('drag', evnt => { + this.#hsepar_move -= evnt.dy; // hsepar is position from bottom + this.adjustSeparators(null, Math.max(5, Math.round(this.#hsepar_move))); + }).on('end', () => { + this.#hsepar_move = undefined; + hsepar.style('background-color', null); + this.checkResize(); + }); - fp.fillatt.setSolidColor('none'); + hsepar.call(drag_move); - fp.redraw(); - } + // need to get touches events handling in drag + if (browser.touches && !main.on('touchmove')) + main.on('touchmove', () => {}); - this.options.adjustFrame = false; - } + if (!height || isStr(height)) + height = this.last_hsepar_height || 20; - fp.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0); + this.adjustSeparators(null, height, true); - fp.createXY({ ndim: this.getDimension(), - check_pad_range: this.check_pad_range, - zoom_xmin: this.zoom_xmin, - zoom_xmax: this.zoom_xmax, - zoom_ymin: this.zoom_ymin, - zoom_ymax: this.zoom_ymax, - ymin_nz: this.ymin_nz, - swap_xy: (this.options.BarStyle >= 20), - reverse_x: this.options.RevX, - reverse_y: this.options.RevY, - symlog_x: this.options.SymlogX, - symlog_y: this.options.SymlogY, - Proj: this.options.Proj, - extra_y_space: this.options.Text && (this.options.BarStyle > 0), - hist_painter: this }); + if (this.status_layout === 'app') + return id; - delete this.check_pad_range; - delete this.zoom_xmin; - delete this.zoom_xmax; - delete this.zoom_ymin; - delete this.zoom_ymax; + this.status_layout = new GridDisplay(id, 'horizx4_1213'); - if (this.options.Same) - return false; + const frame_titles = ['object name', 'object title', 'mouse coordinates', 'object info']; + for (let k = 0; k < 4; ++k) { + select(this.status_layout.getGridFrame(k)) + .attr('title', frame_titles[k]).style('overflow', 'hidden').style('display', 'flex').style('align-items', 'center') + .append('label').attr('style', 'margin: 5px 5px 5px 3px; font-size: 14px; white-space: nowrap;'); + } - const disable_axis_draw = (this.options.Axis < 0) || (this.options.Axis === 2); + internals.showStatus = this.status_handler = this.showStatus.bind(this); - return fp.drawAxes(false, disable_axis_draw, disable_axis_draw, - this.options.AxisPos, this.options.Zscale && this.options.Zvert, - this.options.Zscale && !this.options.Zvert, this.options.Axis !== 1); + return id; } - /** @summary Inform web canvas that something changed in the histogram */ - processOnlineChange(kind) { - const cp = this.getCanvPainter(); - if (isFunc(cp?.processChanges)) - cp.processChanges(kind, this); - } + /** @summary Adjust separator positions */ + adjustSeparators(vsepar, hsepar, redraw, first_time) { + if (!this.gui_div) + return; - /** @summary Fill option object used in TWebCanvas */ - fillWebObjectOptions(res) { - if (this._auto_exec && res) { - res.fcust = 'auto_exec:' + this._auto_exec; - delete this._auto_exec; + const main = this.browser(), w = 5; + + if ((hsepar === null) && first_time && !main.select('.jsroot_h_separator').empty()) { + // if separator set for the first time, check if status line present + hsepar = main.select('.jsroot_h_separator').style('bottom'); + if (isStr(hsepar) && (hsepar.length > 2) && (hsepar.indexOf('px') === hsepar.length - 2)) + hsepar = hsepar.slice(0, hsepar.length - 2); + else + hsepar = null; } - } - /** @summary Toggle histogram title drawing */ - toggleTitle(arg) { - const histo = this.getHisto(); - if (!this.isMainPainter() || !histo) - return false; - if (arg === 'only-check') - return !histo.TestBit(kNoTitle); - histo.InvertBit(kNoTitle); - this.updateHistTitle().then(() => this.processOnlineChange(`exec:SetBit(TH1::kNoTitle,${histo.TestBit(kNoTitle)?1:0})`)); - } + if (hsepar !== null) { + hsepar = parseInt(hsepar); + const elem = main.select('.jsroot_h_separator'); + let hlimit = 0; - /** @summary Only redraw histogram title - * @return {Promise} with painter */ - async updateHistTitle() { - // case when histogram drawn over other histogram (same option) - if (!this.isMainPainter() || this.options.Same || (this.options.Axis > 0)) - return this; + if (!elem.empty()) { + if (hsepar < 5) + hsepar = 5; - const tpainter = this.getPadPainter()?.findPainterFor(null, kTitle, clTPaveText), - pt = tpainter?.getObject(); + const maxh = main.node().clientHeight - w; + if (maxh > 0) { + if (hsepar < 0) + hsepar += maxh; + if (hsepar > maxh) + hsepar = maxh; + } - if (!tpainter || !pt) - return this; + this.last_hsepar_height = hsepar; + elem.style('bottom', hsepar + 'px').style('height', w + 'px'); + this.status().style('height', hsepar + 'px'); + hlimit = hsepar + w; + } - const histo = this.getHisto(), st = gStyle, - draw_title = !histo.TestBit(kNoTitle) && (st.fOptTitle > 0); + this.#hsepar_position = hsepar; - pt.Clear(); - if (draw_title) pt.AddText(histo.fTitle); - return tpainter.redraw().then(() => this); - } + this.drawing().style('bottom', `${hlimit}px`); + } - /** @summary Draw histogram title - * @return {Promise} with painter */ - async drawHistTitle() { - // case when histogram drawn over other histogram (same option) - if (!this.isMainPainter() || this.options.Same || (this.options.Axis > 0)) - return this; + if (vsepar !== null) { + vsepar = Math.max(50, Number.parseInt(vsepar)); + this.#vsepar_position = vsepar; + main.select('.jsroot_browser_area').style('width', (vsepar - 5) + 'px'); + this.drawing().style('left', (vsepar + w) + 'px'); + main.select('.jsroot_h_separator').style('left', (vsepar + w) + 'px'); + this.status().style('left', (vsepar + w) + 'px'); + main.select('.jsroot_v_separator').style('left', vsepar + 'px').style('width', w + 'px'); + } - const histo = this.getHisto(), st = gStyle, - draw_title = !histo.TestBit(kNoTitle) && (st.fOptTitle > 0); + if (redraw) + this.checkResize(); + } - let pt = this.getPadPainter()?.findInPrimitives(kTitle, clTPaveText); + /** @summary Show status information inside special fields of browser layout */ + showStatus(...msgs) { + if (!isObject(this.status_layout) || !isFunc(this.status_layout.getGridFrame)) + return; - if (pt) { - pt.Clear(); - if (draw_title) pt.AddText(histo.fTitle); - return this; + let maxh = 0; + for (let n = 0; n < 4; ++n) { + const lbl = this.status_layout.getGridFrame(n).querySelector('label'); + maxh = Math.max(maxh, lbl.clientHeight); + lbl.innerText = msgs[n] || ''; } - pt = create$1(clTPaveText); - Object.assign(pt, { fName: kTitle, fFillColor: st.fTitleColor, fFillStyle: st.fTitleStyle, fBorderSize: st.fTitleBorderSize, - fTextFont: st.fTitleFont, fTextSize: st.fTitleFontSize, fTextColor: st.fTitleTextColor, fTextAlign: st.fTitleAlign }); - if (draw_title) pt.AddText(histo.fTitle); - return TPavePainter.draw(this.getDom(), pt, 'postitle'); + if (!this.status_layout.first_check) { + this.status_layout.first_check = true; + if ((maxh > 5) && ((maxh > this.last_hsepar_height) || (maxh < this.last_hsepar_height + 5))) + this.adjustSeparators(null, maxh, true); + } } - /** @summary Live change and update of title drawing - * @desc Used from the GED */ - processTitleChange(arg) { - const histo = this.getHisto(), - tpainter = this.getPadPainter()?.findPainterFor(null, kTitle); - - if (!histo || !tpainter) return null; - - if (arg === 'check') - return (!this.isMainPainter() || this.options.Same) ? null : histo; + /** @summary Toggle browser visibility */ + toggleBrowserVisisbility(fast_close) { + if (!this.gui_div || isStr(this.browser_visible)) + return; - tpainter.clearPave(); - tpainter.addText(histo.fTitle); + const main = this.browser(), + area = main.select('.jsroot_browser_area'); + if (area.empty()) + return; - tpainter.redraw(); + const vsepar = main.select('.jsroot_v_separator'), + drawing = select(`#${this.gui_div}_drawing`); + let tgt = area.property('last_left'), + tgt_separ = area.property('last_vsepar'), + tgt_drawing = area.property('last_drawing'); - this.submitCanvExec(`SetTitle("${histo.fTitle}")`); - } + if (!this.browser_visible) { + if (fast_close) + return; + area.property('last_left', null).property('last_vsepar', null).property('last_drawing', null); + } else { + area.property('last_left', area.style('left')); + if (!vsepar.empty()) { + area.property('last_vsepar', vsepar.style('left')); + area.property('last_drawing', drawing.style('left')); + } - /** @summary Update statistics when web canvas is drawn */ - updateStatWebCanvas() { - if (!this.snapid) return; + tgt = (-area.node().clientWidth - 10) + 'px'; + const mainw = main.node().clientWidth; - const stat = this.findStat(), - statpainter = this.getPadPainter()?.findPainterFor(stat); + if (vsepar.empty() && (area.node().offsetLeft > mainw / 2)) + tgt = (mainw + 10) + 'px'; - if (statpainter && !statpainter.snapid) statpainter.redraw(); - } + tgt_separ = '-10px'; + tgt_drawing = '0px'; + } - /** @summary Find stats box in list of functions */ - findStat() { - return this.findFunction(clTPaveStats, 'stats'); - } + const visible_at_the_end = !this.browser_visible, _duration = fast_close ? 0 : 700; - /** @summary Toggle statbox drawing - * @private */ - toggleStat(arg) { - let stat = this.findStat(), statpainter; + this.browser_visible = 'changing'; - if (!arg) arg = ''; + area.transition().style('left', tgt).duration(_duration).on('end', () => { + if (fast_close) + return; + this.browser_visible = visible_at_the_end; + if (visible_at_the_end) + this.setButtonsPosition(); + }); - if (!stat) { - if (arg.indexOf('-check') > 0) return false; - // when statbox created first time, one need to draw it - stat = this.createStat(true); - } else - statpainter = this.getPadPainter()?.findPainterFor(stat); + if (!visible_at_the_end) + main.select('.jsroot_browser_btns').transition().style('left', '7px').style('top', '7px').duration(_duration); + if (!vsepar.empty()) { + vsepar.transition().style('left', tgt_separ).duration(_duration); + drawing.transition().style('left', tgt_drawing).duration(_duration).on('end', this.checkResize.bind(this)); + } - if (arg === 'only-check') - return statpainter?.Enabled || false; + if (this.status_layout && (this.browser_kind === 'fix')) { + main.select('.jsroot_h_separator').transition().style('left', tgt_drawing).duration(_duration); + main.select('.jsroot_status_area').transition().style('left', tgt_drawing).duration(_duration); + } + } - if (arg === 'fitpar-check') - return stat?.fOptFit || false; + /** @summary Adjust browser size */ + adjustBrowserSize(onlycheckmax) { + if (!this.gui_div || (this.browser_kind !== 'float')) + return; - if (arg === 'fitpar-toggle') { - if (!stat) return false; - stat.fOptFit = stat.fOptFit ? 0 : 1111; // for websocket command should be send to server - statpainter?.redraw(); - return true; - } + const main = this.browser(); + if (main.empty()) + return; - let has_stats; + const area = main.select('.jsroot_browser_area'), + cont = main.select('.jsroot_browser_hierarchy'), + chld = select(cont.node().firstChild); - if (statpainter) { - statpainter.Enabled = !statpainter.Enabled; - this.options.StatEnabled = statpainter.Enabled; // used only for interactive - // when stat box is drawn, it always can be drawn individually while it - // should be last for colz redrawPad is used - statpainter.redraw(); - has_stats = statpainter.Enabled; - } else { - const prev_name = this.selectCurrentPad(this.getPadName()); - // return promise which will be used to process - has_stats = TPavePainter.draw(this.getDom(), stat).then(() => this.selectCurrentPad(prev_name)); + if (onlycheckmax) { + if (area.node().parentNode.clientHeight - 10 < area.node().clientHeight) + area.style('bottom', '0px').style('top', '0px'); + return; } - this.processOnlineChange(`exec:SetBit(TH1::kNoStats,${has_stats ? 0 : 1})`, this); + if (chld.empty()) + return; - return has_stats; + const h1 = cont.node().clientHeight, + h2 = chld.node().clientHeight; + if ((h2 !== undefined) && (h2 < h1 * 0.7)) + area.style('bottom', ''); } - /** @summary Returns true if stats box fill can be ingored */ - isIgnoreStatsFill() { - return !this.getObject() || (!this.draw_content && !this.create_stats && !this.snapid); // || (this.options.Axis > 0); - } + /** @summary Set buttons position */ + setButtonsPosition() { + if (!this.gui_div) + return; - /** @summary Create stat box for histogram if required */ - createStat(force) { - const histo = this.getHisto(); - if (!histo) return null; + const main = this.browser(), + btns = main.select('.jsroot_browser_btns'); + if (btns.empty()) + return; - if (!force && !this.options.ForceStat) { - if (this.options.NoStat || histo.TestBit(kNoStats) || !settings.AutoStat) return null; - if (!this.isMainPainter()) return null; + let top = 7, left = 7; + if (this.browser_visible) { + const area = main.select('.jsroot_browser_area'); + top = area.node().offsetTop + 7; + left = area.node().offsetLeft - main.node().offsetLeft + area.node().clientWidth - 27; } - const st = gStyle; - let stats = this.findStat(), - optstat = this.options.optstat, - optfit = this.options.optfit; + btns.style('left', `${left}px`).style('top', `${top}px`); + } - if (optstat !== undefined) { - if (stats) stats.fOptStat = optstat; - delete this.options.optstat; - } else - optstat = histo.$custom_stat || st.fOptStat; + /** @summary Toggle browser kind */ + async toggleBrowserKind(kind) { + if (!this.gui_div) + return null; - if (optfit !== undefined) { - if (stats) stats.fOptFit = optfit; - delete this.options.optfit; - } else - optfit = st.fOptFit; + if (!kind) { + if (!this.browser_kind) + return null; + kind = (this.browser_kind === 'float') ? 'fix' : 'float'; + } - if (!stats && !optstat && !optfit) return null; + const main = this.browser(), + area = main.select('.jsroot_browser_area'); - this.create_stats = true; + if (this.browser_kind === 'float') { + area.style('bottom', '0px') + .style('top', '0px') + .style('width', '') + .style('height', '') + .classed('jsroot_float_browser', false) + .style('border', null); + } else if (this.browser_kind === 'fix') { + main.select('.jsroot_v_separator').remove(); + area.style('left', '0px'); + this.drawing().style('left', '0px'); // reset size + main.select('.jsroot_h_separator').style('left', '0px'); + this.status().style('left', '0px'); // reset left + this.checkResize(); + } - if (stats) - return stats; + this.browser_kind = kind; + this.browser_visible = true; - stats = create$1(clTPaveStats); - Object.assign(stats, { - fName: 'stats', fOptStat: optstat, fOptFit: optfit, - fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, - fTextAlign: 12 - }); + main.select('.jsroot_browser_resize').style('display', (kind === 'float') ? null : 'none'); + main.select('.jsroot_browser_title').style('cursor', (kind === 'float') ? 'move' : null); - stats.AddText(histo.fName); + if (kind === 'float') { + area.style('bottom', '40px') + .classed('jsroot_float_browser', true) + .style('border', 'solid 3px white'); - this.addFunction(stats); + const drag_move = drag().on('start', () => { + const sl = area.style('left'), st = area.style('top'); + this.#float_left = parseInt(sl.slice(0, sl.length - 2)); + this.#float_top = parseInt(st.slice(0, st.length - 2)); + this.#max_left = Math.max(0, main.node().clientWidth - area.node().offsetWidth - 1); + this.#max_top = Math.max(0, main.node().clientHeight - area.node().offsetHeight - 1); + }).filter(evnt => { + return main.select('.jsroot_browser_title').node() === evnt.target; + }).on('drag', evnt => { + this.#float_left += evnt.dx; + this.#float_top += evnt.dy; + area.style('left', Math.min(Math.max(0, this.#float_left), this.#max_left) + 'px') + .style('top', Math.min(Math.max(0, this.#float_top), this.#max_top) + 'px'); + this.setButtonsPosition(); + }); - return stats; - } + // eslint-disable-next-line one-var + const drag_resize = drag().on('start', () => { + const sw = area.style('width'); + this.#float_width = parseInt(sw.slice(0, sw.length - 2)); + this.#float_height = area.node().clientHeight; + this.#max_width = main.node().clientWidth - area.node().offsetLeft - 1; + this.#max_height = main.node().clientHeight - area.node().offsetTop - 1; + }).on('drag', evnt => { + this.#float_width += evnt.dx; + this.#float_height += evnt.dy; - /** @summary Find function in histogram list of functions */ - findFunction(type_name, obj_name) { - const funcs = this.getHisto()?.fFunctions?.arr; - if (!funcs) return null; + area.style('width', Math.min(Math.max(100, this.#float_width), this.#max_width) + 'px') + .style('height', Math.min(Math.max(100, this.#float_height), this.#max_height) + 'px'); - for (let i = 0; i < funcs.length; ++i) { - const f = funcs[i]; - if (obj_name && (f.fName !== obj_name)) continue; - if (f._typename === type_name) return f; - } + this.setButtonsPosition(); + }); - return null; - } + main.call(drag_move); + main.select('.jsroot_browser_resize').call(drag_resize); - /** @summary Add function to histogram list of functions */ - addFunction(obj, asfirst) { - const histo = this.getHisto(); - if (!histo || !obj) return; + this.adjustBrowserSize(); + } else { + area.style('left', '0px').style('top', '0px').style('bottom', '0px').style('height', null); - if (!histo.fFunctions) - histo.fFunctions = create$1(clTList); + const separ_color = settings.DarkMode ? 'grey' : 'azure', + vsepar = main.append('div').classed('jsroot_v_separator', true) + .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; top: 0; bottom: 0; cursor: ew-resize;`); - if (asfirst) - histo.fFunctions.AddFirst(obj); - else - histo.fFunctions.Add(obj); - } + // eslint-disable-next-line one-var + const drag_move = drag().on('start', () => { + this.#vsepar_move = this.#vsepar_position; + vsepar.style('background-color', 'grey'); + }).on('drag', evnt => { + this.#vsepar_move += evnt.dx; + this.setButtonsPosition(); + settings.BrowserWidth = Math.max(50, Math.round(this.#vsepar_move)); + this.adjustSeparators(settings.BrowserWidth, null); + }).on('end', () => { + this.#vsepar_move = undefined; + vsepar.style('background-color', null); + this.checkResize(); + }); - /** @summary Check if such function should be drawn directly */ - needDrawFunc(histo, func) { - if (func._typename === clTPaveStats) - return (func.fName !== 'stats') || (!histo.TestBit(kNoStats) && !this.options.NoStat); + vsepar.call(drag_move); - if ((func._typename === clTF1) || (func._typename === clTF2)) - return this.options.AllFunc || !func.TestBit(BIT(9)); // TF1::kNotDraw + // need to get touches events handling in drag + if (browser.touches && !main.on('touchmove')) + main.on('touchmove', () => {}); - if ((func._typename === 'TGraphDelaunay') || (func._typename === 'TGraphDelaunay2D')) - return false; // do not try to draw delaunay classes + this.adjustSeparators(settings.BrowserWidth, null, true, true); + } - return func._typename !== clTPaletteAxis; - } + this.setButtonsPosition(); - /** @summary Method draws functions from the histogram list of functions - * @return {Promise} fulfilled when drawing is ready */ - async drawFunctions() { - const handler = new FunctionsHandler(this, this.getPadPainter(), this.getHisto().fFunctions, true); - return handler.drawNext(0); // returns this painter + return this; } - /** @summary Method used to update functions which are prepared before - * @return {Promise} fulfilled when drawing is ready */ - async updateFunctions() { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; - return res; - } +} // class BrowserLayout - /** @summary Returns selected index for specified axis - * @desc be aware - here indexes starts from 0 */ - getSelectIndex(axis, side, add) { - let indx = 0, taxis = this.getAxis(axis); - const nbin = this[`nbins${axis}`] ?? 0; +const clTButton = 'TButton', kIsGrayscale = BIT(22), + // identifier used in TWebCanvas painter + webSnapIds = { kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; - if (this.options.second_x && axis === 'x') axis = 'x2'; - if (this.options.second_y && axis === 'y') axis = 'y2'; - const main = this.getFramePainter(), - min = main ? main[`zoom_${axis}min`] : 0, - max = main ? main[`zoom_${axis}max`] : 0; +// eslint-disable-next-line one-var +const PadButtonsHandler = { - if ((min !== max) && taxis) { - if (side === 'left') - indx = taxis.FindBin(min, add || 0); - else - indx = taxis.FindBin(max, (add || 0) + 0.5); - if (indx < 0) - indx = 0; else - if (indx > nbin) - indx = nbin; - } else - indx = (side === 'left') ? 0 : nbin; + getButtonSize(fact) { + const cp = this.getCanvPainter(); + return Math.round((fact || 1) * (cp?.getPadScale() || 1) * (cp === this ? 16 : 12)); + }, + toggleButtonsVisibility(action, evnt) { + evnt?.preventDefault(); + evnt?.stopPropagation(); - // TAxis object of histogram, where user range can be stored - if (taxis) { - if ((taxis.fFirst === taxis.fLast) || !taxis.TestBit(EAxisBits.kAxisRange) || - ((taxis.fFirst <= 1) && (taxis.fLast >= nbin))) taxis = undefined; - } + const group = this.getLayerSvg('btns_layer'), + btn = group.select('[name=\'Toggle\']'); + if (btn.empty()) + return; - if (side === 'left') { - if (indx < 0) indx = 0; - if (taxis && (taxis.fFirst > 1) && (indx < taxis.fFirst)) indx = taxis.fFirst - 1; - } else { - if (indx > nbin) indx = nbin; - if (taxis && (taxis.fLast <= nbin) && (indx>taxis.fLast)) indx = taxis.fLast; + let state = btn.property('buttons_state'); + + if (btn.property('timout_handler')) { + if (action !== 'timeout') + clearTimeout(btn.property('timout_handler')); + btn.property('timout_handler', null); } - return indx; - } + let is_visible = false; + switch (action) { + case 'enable': + is_visible = true; + this.btns_active_flag = true; + break; + case 'enterbtn': + this.btns_active_flag = true; + return; // do nothing, just cleanup timeout + case 'timeout': + break; + case 'toggle': + state = !state; + btn.property('buttons_state', state); + is_visible = state; + break; + case 'disable': + case 'leavebtn': + this.btns_active_flag = false; + if (!state) + btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout'), 1200)); + return; + } - /** @summary Unzoom user range if any */ - unzoomUserRange(dox, doy, doz) { - const histo = this.getHisto(); - if (!histo) return false; + group.selectAll('svg').each(function() { + if (this !== btn.node()) + select(this).style('display', is_visible ? '' : 'none'); + }); + }, - let res = false; - const unzoomTAxis = obj => { - if (!obj || !obj.TestBit(EAxisBits.kAxisRange)) return false; - if (obj.fFirst === obj.fLast) return false; - if ((obj.fFirst <= 1) && (obj.fLast >= obj.fNbins)) return false; - obj.InvertBit(EAxisBits.kAxisRange); - return true; - }, + alignButtons(btns, width, height) { + const sz0 = this.getButtonSize(1.25), nextx = (btns.property('nextx') || 0) + sz0; + let btns_x, btns_y; - uzoomMinMax = ndim => { - if (this.getDimension() !== ndim) return false; - if ((this.options.minimum === kNoZoom) && (this.options.maximum === kNoZoom)) return false; - if (!this.draw_content) return false; // if not drawing content, not change min/max - this.options.minimum = this.options.maximum = kNoZoom; - this.scanContent(true); // to reset ymin/ymax - return true; - }; + if (btns.property('vertical')) { + btns_x = btns.property('leftside') ? 2 : (width - sz0); + btns_y = height - nextx; + } else { + btns_x = btns.property('leftside') ? 2 : (width - nextx); + btns_y = height - sz0; + } - if (dox && unzoomTAxis(histo.fXaxis)) res = true; - if (doy && (unzoomTAxis(histo.fYaxis) || uzoomMinMax(1))) res = true; - if (doz && (unzoomTAxis(histo.fZaxis) || uzoomMinMax(2))) res = true; + makeTranslate(btns, btns_x, btns_y); + }, - return res; - } + findPadButton(keyname) { + const group = this.getLayerSvg('btns_layer'); + let found_func = ''; + if (!group.empty()) { + group.selectAll('svg').each(function() { + if (select(this).attr('key') === keyname) + found_func = select(this).attr('name'); + }); + } + return found_func; + }, - /** @summary Add different interactive handlers - * @desc only first (main) painter in list allowed to add interactive functionality - * Most of interactivity now handled by frame - * @return {Promise} for ready */ - async addInteractivity() { - const ismain = this.isMainPainter(), - second_axis = (this.options.AxisPos > 0), - fp = (ismain || second_axis) ? this.getFramePainter() : null; - return fp?.addInteractivity(!ismain && second_axis) ?? false; - } + removePadButtons() { + const group = this.getLayerSvg('btns_layer'); + if (!group.empty()) { + group.selectAll('*').remove(); + group.property('nextx', null); + } + }, - /** @summary Invoke dialog to enter and modify user range */ - changeUserRange(menu, arg) { - const histo = this.getHisto(), - taxis = histo ? histo[`f${arg}axis`] : null; - if (!taxis) return; + showPadButtons() { + const group = this.getLayerSvg('btns_layer'); + if (group.empty()) + return; - let curr = `[1,${taxis.fNbins}]`; - if (taxis.TestBit(EAxisBits.kAxisRange)) - curr = `[${taxis.fFirst},${taxis.fLast}]`; + // clean all previous buttons + group.selectAll('*').remove(); + if (!this._buttons) + return; - menu.input(`Enter user range for axis ${arg} like [1,${taxis.fNbins}]`, curr).then(res => { - if (!res) return; - res = JSON.parse(res); - if (!res || (res.length !== 2)) return; - const first = parseInt(res[0]), last = parseInt(res[1]); - if (!Number.isInteger(first) || !Number.isInteger(last)) return; - taxis.fFirst = first; - taxis.fLast = last; + const istop = this.isTopPad(), y = 0; + let ctrl, x = group.property('leftside') ? this.getButtonSize(1.25) : 0; - const newflag = (taxis.fFirst < taxis.fLast) && (taxis.fFirst >= 1) && (taxis.fLast <= taxis.fNbins); - if (newflag !== taxis.TestBit(EAxisBits.kAxisRange)) - taxis.InvertBit(EAxisBits.kAxisRange); + if (this.isFastDrawing()) { + ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, this.getButtonSize(), 'enlargePad', false) + .attr('name', 'Enlarge').attr('x', 0).attr('y', 0) + .on('click', evnt => this.clickPadButton('enlargePad', evnt)); + } else { + ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.rect, this.getButtonSize(), 'Toggle tool buttons', false) + .attr('name', 'Toggle').attr('x', 0).attr('y', 0) + .property('buttons_state', (settings.ToolBar !== 'popup') || browser.touches) + .on('click', evnt => this.toggleButtonsVisibility('toggle', evnt)); + ctrl.node()._mouseenter = () => this.toggleButtonsVisibility('enable'); + ctrl.node()._mouseleave = () => this.toggleButtonsVisibility('disable'); - this.interactiveRedraw(); - }); - } + for (let k = 0; k < this._buttons.length; ++k) { + const item = this._buttons[k]; + let btn = item.btn; - /** @summary Start dialog to modify range of axis where histogram values are displayed */ - changeValuesRange(menu) { - let curr; - if ((this.options.minimum !== kNoZoom) && (this.options.maximum !== kNoZoom)) - curr = `[${this.options.minimum},${this.options.maximum}]`; - else - curr = `[${this.gminbin},${this.gmaxbin}]`; + if (isStr(btn)) + btn = ToolbarIcons[btn]; + if (!btn) + btn = ToolbarIcons.circle; - menu.input('Enter min/max hist values or empty string to reset', curr).then(res => { - res = res ? JSON.parse(res) : []; + const svg = ToolbarIcons.createSVG(group, btn, this.getButtonSize(), + item.tooltip + (istop ? '' : (` on pad ${this.getPadName()}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); - if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) - this.options.minimum = this.options.maximum = kNoZoom; - else { - this.options.minimum = res[0]; - this.options.maximum = res[1]; - } + if (group.property('vertical')) + svg.attr('x', y).attr('y', x); + else + svg.attr('x', x).attr('y', y); - this.interactiveRedraw(); - }); - } + svg.attr('name', item.funcname) + .style('display', ctrl.property('buttons_state') ? '' : 'none') + .attr('key', item.keyname || null) + .on('click', evnt => this.clickPadButton(item.funcname, evnt)); - /** @summary Execute histogram menu command - * @desc Used to catch standard menu items and provide local implementation */ - executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) - return true; + svg.node()._mouseenter = () => this.toggleButtonsVisibility('enterbtn'); + svg.node()._mouseleave = () => this.toggleButtonsVisibility('leavebtn'); - if (method.fClassName === clTAxis) { - const p = isStr(method.$execid) ? method.$execid.indexOf('#') : -1, - kind = p > 0 ? method.$execid.slice(p+1) : 'x', - fp = this.getFramePainter(); - if (method.fName === 'UnZoom') { - fp?.unzoom(kind); - return true; - } else if (method.fName === 'SetRange') { - const axis = fp?.getAxis(kind), bins = JSON.parse(`[${args}]`); - if (axis && bins?.length === 2) - fp?.zoom(kind, axis.GetBinLowEdge(bins[0]), axis.GetBinLowEdge(bins[1]+1)); - // let execute command on server - } else if (method.fName === 'SetRangeUser') { - const values = JSON.parse(`[${args}]`); - if (values?.length === 2) - fp?.zoom(kind, values[0], values[1]); - // let execute command on server + x += this.getButtonSize(1.25); } } - return false; - } + group.property('nextx', x); - /** @summary Fill histogram context menu */ - fillContextMenuItems(menu) { - const histo = this.getHisto(), - fp = this.getFramePainter(); - if (!histo) return; + this.alignButtons(group, this.getPadWidth(), this.getPadHeight()); - if ((this.options.Axis <= 0) && !this.isTF1()) - menu.addchk(this.toggleStat('only-check'), 'Show statbox', () => this.toggleStat()); + if (group.property('vertical')) + ctrl.attr('y', x); + else if (!group.property('leftside')) + ctrl.attr('x', x); + }, - if (histo.fTitle && this.isMainPainter()) - menu.addchk(this.toggleTitle('only-check'), 'Show title', () => this.toggleTitle()); + assign(painter) { + Object.assign(painter, this); + } - if (this.draw_content) { - if (this.getDimension() === 1) - menu.add('User range X', () => this.changeUserRange(menu, 'X')); - else { - menu.add('sub:User ranges'); - menu.add('X', () => this.changeUserRange(menu, 'X')); - menu.add('Y', () => this.changeUserRange(menu, 'Y')); - if (this.getDimension() > 2) - menu.add('Z', () => this.changeUserRange(menu, 'Z')); - else - menu.add('Values', () => this.changeValuesRange(menu)); - menu.add('endsub:'); - } +}; // PadButtonsHandler - if (isFunc(this.fillHistContextMenu)) - this.fillHistContextMenu(menu); - } - if (this.options.Mode3D) { - // menu for 3D drawings +/** @summary Fill TWebObjectOptions for painter + * @private */ +function createWebObjectOptions(painter) { + if (!painter?.getSnapId()) + return null; - if (menu.size() > 0) - menu.add('separator'); + const obj = { _typename: 'TWebObjectOptions', snapid: painter.getSnapId(), opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; + if (isFunc(painter.fillWebObjectOptions)) + painter.fillWebObjectOptions(obj); + return obj; +} - const main = this.getMainPainter() || this; - menu.addchk(main.isTooltipAllowed(), 'Show tooltips', () => main.setTooltipAllowed('toggle')); +/** + * @summary Painter for TPad object + * @private + */ - menu.addchk(fp?.enable_highlight, 'Highlight bins', () => { - fp.enable_highlight = !fp.enable_highlight; - if (!fp.enable_highlight && fp.mode3d && isFunc(fp.highlightBin3D)) - fp.highlightBin3D(null); - }); +class TPadPainter extends ObjectPainter { - if (isFunc(fp?.render3D)) { - menu.addchk(main.options.FrontBox, 'Front box', () => { - main.options.FrontBox = !main.options.FrontBox; - fp.render3D(); - }); - menu.addchk(main.options.BackBox, 'Back box', () => { - main.options.BackBox = !main.options.BackBox; - fp.render3D(); - }); - menu.addchk(fp.camera?.isOrthographicCamera, 'Orthographic camera', flag => { - main.options.Ortho = flag; - fp.change3DCamera(flag); - }); - } + #iscan; // is canvas flag + #pad_name; // name of the pad + #pad; // TPad object + #painters; // painters in the pad + #pad_scale; // scale factor of the pad + #pad_x; // pad x coordinate + #pad_y; // pad y coordinate + #pad_width; // pad width + #pad_height; // pad height + #doing_draw; // drawing handles + #pad_draw_disabled; // disable drawing of the pad + #last_grayscale; // grayscale change flag + #custom_palette; // custom palette + #custom_colors; // custom colors + #custom_palette_indexes; // custom palette indexes + #custom_palette_colors; // custom palette colors + #frame_painter_ref; // frame painter + #main_painter_ref; // main painter on the pad + #snap_primitives; // stored snap primitives from web canvas + #has_execs; // indicate is pad has TExec objects assigned + #deliver_move_events; // deliver move events to server + #readonly; // if changes on pad is not allowed + #num_primitives; // number of primitives + #num_specials; // number of special objects - if counted + #auto_color_cnt; // counter used in assigning auto colors + #auto_palette; // palette for creating of automatic colors + #fixed_size; // fixed size flag + #has_canvas; // indicate if top canvas painter exists + #fast_drawing; // fast drawing flag + #resize_tmout; // timeout handle for resize + #start_draw_tm; // time when start drawing primitives - if (this.draw_content) { - menu.addchk(!this.options.Zero, 'Suppress zeros', () => { - this.options.Zero = !this.options.Zero; - this.interactiveRedraw('pad'); - }); + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} pad - TPad object to draw + * @param {String} [opt] - draw option + * @param {boolean} [iscan] - if TCanvas object + * @param [add_to_primitives] - add pad painter to canvas + * */ + constructor(dom, pad, opt, iscan, add_to_primitives) { + super(dom, pad); + this.#pad = pad; + this.#iscan = iscan; // indicate if working with canvas + this.#pad_name = ''; + if (!iscan && pad?.fName) { + this.#pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name + const regexp = /^[A-Za-z][A-Za-z0-9_]*$/; + if (!regexp.test(this.#pad_name) || ((this.#pad_name === 'button') && (pad._typename === clTButton))) + this.#pad_name = 'jsroot_pad_' + internals.id_counter++; + } + this.#painters = []; // complete list of all painters in the pad + this.#has_canvas = true; + this.forEachPainter = this.forEachPainterInPad; + const d = this.selectDom(); + if (!d.empty() && d.property('_batch_mode')) + this.batch_mode = true; - if ((this.options.Lego === 12) || (this.options.Lego === 14)) { - menu.addchk(this.options.Zscale, 'Z scale', () => this.toggleColz()); - this.fillPaletteMenu(menu, true); - } - } + if (opt !== undefined) + this.decodeOptions(opt); - if (isFunc(main.control?.reset)) - menu.add('Reset camera', () => main.control.reset()); + if (add_to_primitives) { + if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { + // one can draw pad without canvas + this.#has_canvas = false; + this.#pad_name = ''; + this.setTopPainter(); + } else { + // pad painter will be registered in the parent pad + this.addToPadPrimitives(); + } } - if (this.histogram_updated && fp.zoomChangedInteractive()) - menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); + if (pad?.$disable_drawing) + this.#pad_draw_disabled = true; } - /** @summary Returns snap id for object or subelement - * @private */ - getSnapId(subelem) { - if (!this.snapid) - return ''; - let res = this.snapid.toString(); - if (subelem) { - res += '#'; - if (this.isTF1() && (subelem === 'x' || subelem === 'y' || subelem === 'z')) - res += 'hist#'; - res += subelem; - } - return res; - } + /** @summary returns pad painter + * @protected */ + getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } - /** @summary Auto zoom into histogram non-empty range - * @abstract */ - autoZoom() {} + /** @summary returns canvas painter + * @protected */ + getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const fp = this.getFramePainter(); - if (!this.isMainPainter() || !fp) return false; + /** @summary Returns pad name + * @protected */ + getPadName() { return this.#pad_name; } - switch (funcname) { - case 'ToggleZoom': - if ((fp.zoom_xmin !== fp.zoom_xmax) || (fp.zoom_ymin !== fp.zoom_ymax) || (fp.zoom_zmin !== fp.zoom_zmax)) { - const pr = fp.unzoom(); - fp.zoomChangedInteractive('reset'); - return pr; - } - if (this.draw_content) - return this.autoZoom(); - break; - case 'ToggleLogX': return fp.toggleAxisLog('x'); - case 'ToggleLogY': return fp.toggleAxisLog('y'); - case 'ToggleLogZ': return fp.toggleAxisLog('z'); - case 'ToggleStatBox': return getPromise(this.toggleStat()); - } - return false; - } + /** @summary Indicates that drawing runs in batch mode + * @private */ + isBatchMode() { + if (this.batch_mode !== undefined) + return this.batch_mode; - /** @summary Fill pad toolbar with histogram-related functions */ - fillToolbar(not_shown) { - const pp = this.getPadPainter(); - if (!pp) return; + if (isBatchMode()) + return true; - pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); - pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); - pp.addPadButton('arrow_up', 'Toggle log y', 'ToggleLogY', 'PageUp'); - if (this.getDimension() > 1) - pp.addPadButton('arrow_diag', 'Toggle log z', 'ToggleLogZ'); - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - if (!not_shown) - pp.showPadButtons(); + return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); } - /** @summary Returns tooltip information for 3D drawings */ - get3DToolTip(indx) { - const histo = this.getHisto(), - tip = { bin: indx, name: histo.fName, title: histo.fTitle }; - switch (this.getDimension()) { - case 1: - tip.ix = indx; tip.iy = 1; - tip.value = histo.getBinContent(tip.ix); - tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(indx-1); - break; - case 2: - tip.ix = indx % (this.nbinsx + 2); - tip.iy = (indx - tip.ix) / (this.nbinsx + 2); - tip.value = histo.getBinContent(tip.ix, tip.iy); - tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1); - break; - case 3: - tip.ix = indx % (this.nbinsx+2); - tip.iy = ((indx - tip.ix) / (this.nbinsx+2)) % (this.nbinsy+2); - tip.iz = (indx - tip.ix - tip.iy * (this.nbinsx+2)) / (this.nbinsx+2) / (this.nbinsy+2); - tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); - tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1, tip.iz-1); - break; - } + /** @summary Indicates that is is Root6 pad painter + * @private */ + isRoot6() { return true; } - return tip; - } + /** @summary Returns true if pad is editable */ + isEditable() { return this.#pad?.fEditable ?? true; } - /** @summary Create contour object for histogram */ - createContour(nlevels, zmin, zmax, zminpositive, custom_levels) { - const cntr = new HistContour(zmin, zmax), - ndim = this.getDimension(); + /** @summary Returns true if button */ + isButton() { return this.matchObjectType(clTButton); } - if (custom_levels) - cntr.createCustom(custom_levels); - else { - if (nlevels < 2) nlevels = gStyle.fNumberContours; - const pad = this.getPadPainter().getRootPad(true), - logv = pad?.fLogv ?? ((ndim === 2) && pad?.fLogz); + /** @summary Returns true if read-only mode is enabled */ + isReadonly() { return this.#readonly; } - cntr.createNormal(nlevels, logv ?? 0, zminpositive); - } + /** @summary Returns true if it is canvas + * @param {Boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ + isCanvas(is_online = false) { + if (!this.#iscan) + return false; + if (is_online === true) + return isFunc(this.getWebsocket) && this.getWebsocket(); + return isStr(is_online) ? this.#iscan === is_online : true; + } - cntr.configIndicies(this.options.Zero ? -1 : 0, (cntr.colzmin !== 0) || !this.options.Zero || this.isTH2Poly() ? 0 : -1); + /** @summary Returns true if it is canvas or top pad without canvas */ + isTopPad() { return this.isCanvas() || !this.#has_canvas; } - const fp = this.getFramePainter(); - if (fp && (ndim < 3) && !fp.mode3d) { - fp.zmin = cntr.colzmin; - fp.zmax = cntr.colzmax; - } + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg + * @protected */ + getCanvSvg() { return this.selectDom().select('.root_canvas'); } - this.fContour = cntr; - return cntr; - } + /** @summary Pad svg element + * @return {object} d3 selection with pad svg + * @protected */ + getPadSvg() { + const c = this.getCanvSvg(); + if (!this.#pad_name || c.empty()) + return c; - /** @summary Return contour object */ - getContour(force_recreate) { - if (this.fContour && !force_recreate) - return this.fContour; + return c.select('.primitives_layer .__root_pad_' + this.#pad_name); + } - const main = this.getMainPainter(), - fp = this.getFramePainter(); + /** @summary Method selects immediate layer under canvas/pad main element + * @param {string} name - layer name lik 'primitives_layer', 'btns_layer', 'info_layer' + * @protected */ + getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } - if (main?.fContour && (main !== this) && !this.options.IgnoreMainScale) { - this.fContour = main.fContour; - return this.fContour; + /** @summary Returns svg element for the frame in current pad + * @protected */ + getFrameSvg() { + const layer = this.getLayerSvg('primitives_layer'); + if (layer.empty()) + return layer; + let node = layer.node().firstChild; + while (node) { + const elem = select(node); + if (elem.classed('root_frame')) + return elem; + node = node.nextSibling; } + return select(null); + } - // if not initialized, first create contour array - // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz - // when no same0 draw option specified, use main painter for creating contour, also ignore scatter drawing for main painer - const histo = this.getObject(), - src = (this !== main) && (main?.minbin !== undefined) && !this.options.IgnoreMainScale && !main?.tt_handle?.ScatterPlot ? main : this; - let nlevels = 0, apply_min, - zmin = src.minbin, zmax = src.maxbin, zminpos = src.minposbin, - custom_levels; - if (zmin === zmax) { zmin = src.gminbin; zmax = src.gmaxbin; zminpos = src.gminposbin; } + /** @summary Returns main painter on the pad + * @desc Typically main painter is TH1/TH2 object which is drawing axes + * @private */ + getMainPainter() { return this.#main_painter_ref || null; } - let gzmin = zmin, gzmax = zmax; - if (this.options.minimum !== kNoZoom) { zmin = this.options.minimum; gzmin = Math.min(gzmin, zmin); apply_min = true; } - if (this.options.maximum !== kNoZoom) { zmax = this.options.maximum; gzmax = Math.max(gzmax, zmax); apply_min = false; } - if (zmin >= zmax) { - if (apply_min) - zmax = zmin + 1; - else - zmin = zmax - 1; - } + /** @summary Assign main painter on the pad + * @desc Typically main painter is TH1/TH2 object which is drawing axes + * @private */ + setMainPainter(painter, force) { + if (!this.#main_painter_ref || force) + this.#main_painter_ref = painter; + } - if (fp && (fp.zoom_zmin !== fp.zoom_zmax)) { - zmin = fp.zoom_zmin; - zmax = fp.zoom_zmax; - } + /** @summary cleanup pad and all primitives inside */ + cleanup() { + if (this.#doing_draw) + console.error('pad drawing is not completed when cleanup is called'); - if (histo.fContour?.length > 1) { - if (histo.TestBit(kUserContour)) - custom_levels = histo.fContour; - else - nlevels = histo.fContour.length; + this.#painters.forEach(p => p.cleanup()); + + const svg_p = this.getPadSvg(); + if (!svg_p.empty()) { + svg_p.property('pad_painter', null); + if (!this.isCanvas()) + svg_p.remove(); } - const cntr = this.createContour(nlevels, zmin, zmax, zminpos, custom_levels); + this.#main_painter_ref = undefined; + this.#frame_painter_ref = undefined; + this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; + this.#doing_draw = undefined; + this.#snap_primitives = undefined; + this.#last_grayscale = undefined; + this.#custom_palette = this.#custom_colors = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; - if ((this.getDimension() < 3) && fp) { - fp.zmin = gzmin; - fp.zmax = gzmax; + this.#painters = []; + this.#pad = undefined; + this.#pad_name = undefined; + this.#has_canvas = false; - if ((gzmin !== cntr.colzmin) || (gzmax !== cntr.colzmax)) { - fp.zoom_zmin = cntr.colzmin; - fp.zoom_zmax = cntr.colzmax; - } else - fp.zoom_zmin = fp.zoom_zmax = undefined; - } + selectActivePad({ pp: this, active: false }); - return cntr; + super.cleanup(); } - /** @summary Return levels from contour object */ - getContourLevels(force_recreate) { - return this.getContour(force_recreate).getLevels(); - } + /** @summary Returns frame painter inside the pad + * @private */ + getFramePainter() { return this.#frame_painter_ref; } - /** @summary Returns color palette associated with histogram - * @desc Create if required, checks pad and canvas for custom palette */ - getHistPalette(force) { - if (force) this._color_palette = null; - const pp = this.getPadPainter(); - if (!this._color_palette && !this.options.Palette) { - if (isFunc(pp?.getCustomPalette)) - this._color_palette = pp.getCustomPalette(); - } - if (!this._color_palette) - this._color_palette = getColorPalette(this.options.Palette, pp?.isGrayscale()); - return this._color_palette; + /** @summary Assign actual frame painter + * @private */ + setFramePainter(fp, on) { + if (on) + this.#frame_painter_ref = fp; + else if (this.#frame_painter_ref === fp) + this.#frame_painter_ref = undefined; } - /** @summary Fill menu entries for palette */ - fillPaletteMenu(menu, only_palette) { - menu.addPaletteMenu(this.options.Palette || settings.Palette, arg => { - this.options.Palette = parseInt(arg); - this.getHistPalette(true); - this.redraw(); // redraw histogram - }); - if (!only_palette) { - menu.add('Default position', () => { - this.drawColorPalette(this.options.Zscale, false, true) - .then(() => this.processOnlineChange('drawopt')); - }, 'Set default position for palette'); + /** @summary get pad width */ + getPadWidth() { return this.#pad_width || 0; } - const pal = this.findFunction(clTPaletteAxis), - is_vert = !pal ? true : pal.fX2NDC - pal.fX1NDC < pal.fY2NDC - pal.fY1NDC; - menu.addchk(is_vert, 'Vertical', flag => { - this.options.Zvert = flag; - this.drawColorPalette(this.options.Zscale, false, 'toggle') - .then(() => this.processOnlineChange('drawopt')); - }, 'Toggle palette vertical/horizontal flag'); + /** @summary get pad height */ + getPadHeight() { return this.#pad_height || 0; } - menu.add('Bring to front', () => this.getPadPainter()?.findPainterFor(pal)?.bringToFront()); - } - } + /** @summary get pad height */ + getPadScale() { return this.#pad_scale || 1; } - /** @summary draw color palette - * @return {Promise} when done */ - async drawColorPalette(enabled, postpone_draw, can_move) { - // only when create new palette, one could change frame size - const mp = this.getMainPainter(), - pp = this.getPadPainter(); - if (mp !== this) { - if (mp && (mp.draw_content !== false) && mp.options.Zscale) - return null; - } + /** @summary get pad rect */ + getPadRect() { + return { + x: this.#pad_x || 0, + y: this.#pad_y || 0, + width: this.getPadWidth(), + height: this.getPadHeight() + }; + } - let pal = this.findFunction(clTPaletteAxis), - pal_painter = pp?.findPainterFor(pal); + /** @summary return pad log state x or y are allowed */ + getPadLog(name) { + const pad = this.getRootPad(); + if (name === 'x') + return pad?.fLogx; + if (name === 'y') + return pad?.fLogv ?? pad?.fLogy; + return false; + } - const found_in_func = !!pal; + /** @summary Returns frame coordinates - also when frame is not drawn */ + getFrameRect() { + const fp = this.getFramePainter(); + if (fp) + return fp.getFrameRect(); - if (this._can_move_colz) { - delete this._can_move_colz; - if (!can_move) can_move = true; - } + const w = this.getPadWidth(), + h = this.getPadHeight(), + rect = {}; - if (!pal_painter && !pal && !this.options.Axis) { - pal_painter = pp?.findPainterFor(undefined, undefined, clTPaletteAxis); - if (pal_painter) { - pal = pal_painter.getObject(); - // add to list of functions - this.addFunction(pal, true); - } + if (this.#pad) { + rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fLeftMargin, this.#pad.fRightMargin)) * w); + rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fBottomMargin, this.#pad.fTopMargin)) * h); + } else { + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); } - if (!enabled) { - if (pal_painter) { - this.options.Zvert = pal_painter._palette_vertical; - pal_painter.Enabled = false; - pal_painter.removeG(); // completely remove drawing without need to redraw complete pad - } + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); + rect.hint_delta_x = rect.szx; + rect.hint_delta_y = rect.szy; + rect.transform = makeTranslate(rect.x, rect.y) || ''; - return null; - } + return rect; + } - if (!pal) { - pal = create$1(clTPaletteAxis); + /** @summary return RPad object */ + getRootPad(is_root6) { + return (is_root6 === undefined) || is_root6 ? this.#pad : null; + } - pal.fInit = 1; - pal.$can_move = true; - pal.$generated = true; + /** @summary Cleanup primitives from pad - selector lets define which painters to remove + * @return true if any painter was removed */ + cleanPrimitives(selector) { + // remove all primitives + if (selector === true) + selector = () => true; - if (!this.options.Zvert) - Object.assign(pal, { fX1NDC: gStyle.fPadLeftMargin, fX2NDC: 1 - gStyle.fPadRightMargin, fY1NDC: 1.005 - gStyle.fPadTopMargin, fY2NDC: 1.045 - gStyle.fPadTopMargin }); - else - Object.assign(pal, { fX1NDC: 1.005 - gStyle.fPadRightMargin, fX2NDC: 1.045 - gStyle.fPadRightMargin, fY1NDC: gStyle.fPadBottomMargin, fY2NDC: 1 - gStyle.fPadTopMargin }); + if (!isFunc(selector)) + return false; - Object.assign(pal.fAxis, { fChopt: '+', fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11 }); + let is_any = false; - if (this.getDimension() === 2) { - const zaxis = this.getHisto().fZaxis; - Object.assign(pal.fAxis, { fTitle: zaxis.fTitle, fTitleSize: zaxis.fTitleSize, - fTitleOffset: zaxis.fTitleOffset, fTitleColor: zaxis.fTitleColor, - fLineColor: zaxis.fAxisColor, fTextSize: zaxis.fLabelSize, - fTextColor: zaxis.fLabelColor, fTextFont: zaxis.fLabelFont, - fLabelOffset: zaxis.fLabelOffset }); + for (let k = this.#painters.length - 1; k >= 0; --k) { + const subp = this.#painters[k]; + if (!subp || selector(subp)) { + subp?.cleanup(); + this.#painters.splice(k, 1); + is_any = true; } + } - // place colz in the beginning, that stat box is always drawn on the top - this.addFunction(pal, true); - - can_move = true; - } else if (pp?._palette_vertical !== undefined) - this.options.Zvert = pp._palette_vertical; - - const fp = this.getFramePainter(); + return is_any; + } - // keep palette width - if (can_move && fp && pal.$can_move) { - if (this.options.Zvert) { - if (can_move === 'toggle') { - const d = pal.fY2NDC - pal.fY1NDC; - pal.fX1NDC = fp.fX2NDC + 0.005; - pal.fX2NDC = pal.fX1NDC + d; - } - if (pal.fX1NDC > (fp.fX1NDC + fp.fX2NDC)*0.5) { - pal.fX2NDC = fp.fX2NDC + 0.005 + (pal.fX2NDC - pal.fX1NDC); - pal.fX1NDC = fp.fX2NDC + 0.005; - } else { - pal.fX1NDC = fp.fX1NDC - 0.03 - (pal.fX2NDC - pal.fX1NDC); - pal.fX2NDC = fp.fX1NDC - 0.03; - } - pal.fY1NDC = fp.fY1NDC; - pal.fY2NDC = fp.fY2NDC; - } else { - if (can_move === 'toggle') { - const d = pal.fX2NDC - pal.fX1NDC; - pal.fY1NDC = fp.fY2NDC + 0.005; - pal.fY2NDC = pal.fY1NDC + d; - } + /** @summary Removes and cleanup specified primitive + * @desc also secondary primitives will be removed + * @return new index to continue loop or -111 if main painter removed + * @private */ + removePrimitive(arg, clean_only_secondary) { + let indx, prim; + if (Number.isInteger(arg)) { + indx = arg; + prim = this.#painters[indx]; + } else { + indx = this.#painters.indexOf(arg); + prim = arg; + } + if (indx < 0) + return indx; - pal.fX1NDC = fp.fX1NDC; - pal.fX2NDC = fp.fX2NDC; - if (pal.fY2NDC > (fp.fY1NDC + fp.fY2NDC)*0.5) { - pal.fY2NDC = fp.fY2NDC + 0.005 + (pal.fY2NDC - pal.fY1NDC); - pal.fY1NDC = fp.fY2NDC + 0.005; - } else { - pal.fY1NDC = fp.fY1NDC - 0.05 - (pal.fY2NDC - pal.fY1NDC); - pal.fY2NDC = fp.fY1NDC - 0.05; + const arr = [], get_main = clean_only_secondary ? this.getMainPainter() : null; + let resindx = indx - 1; // object removed itself + arr.push(prim); + this.#painters.splice(indx, 1); + + // loop to extract all dependent painters + let len0 = 0; + while (len0 < arr.length) { + for (let k = this.#painters.length - 1; k >= 0; --k) { + if (this.#painters[k].isSecondary(arr[len0])) { + arr.push(this.#painters[k]); + this.#painters.splice(k, 1); + if (k < indx) + resindx--; } } + len0++; } - // required for z scale setting - // TODO: use weak reference (via pad list of painters and any kind of string) - pal.$main_painter = this; + arr.forEach(painter => { + if ((painter !== prim) || !clean_only_secondary) + painter.cleanup(); + if (this.getMainPainter() === painter) { + this.setMainPainter(undefined, true); + resindx = -111; + } + }); - let arg = '', pr; - if (postpone_draw) arg += ';postpone'; - if (can_move && !this.do_redraw_palette) arg += ';can_move'; - if (this.options.Cjust) arg += ';cjust'; + // when main painter disappears because of special cleanup - also reset zooming + if (clean_only_secondary && get_main && !this.getMainPainter()) + this.getFramePainter()?.resetZoom(); - if (!pal_painter) { - // when histogram drawn on sub pad, let draw new axis object on the same pad - const prev = this.selectCurrentPad(this.getPadName()); - pr = TPavePainter.draw(this.getDom(), pal, arg).then(_palp => { - pal_painter = _palp; - this.selectCurrentPad(prev); - pal_painter.setSecondaryId(this, found_in_func && !pal.$generated ? `func_${pal.fName}` : undefined); - }); - } else { - pal_painter.Enabled = true; - // real drawing will be perform at the end - if (postpone_draw) return pal_painter; - pr = pal_painter.drawPave(arg); - } + return resindx; + } - return pr.then(() => { - // mark painter as secondary - not in list of TCanvas primitives - this.options.Zvert = pal_painter._palette_vertical; + /** @summary returns custom palette associated with pad or top canvas + * @private */ + getCustomPalette(no_recursion) { + return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); + } - // make dummy redraw, palette will be updated only from histogram painter - pal_painter.redraw = () => {}; + _getCustomPaletteIndexes() { return this.#custom_palette_indexes; } - let need_redraw = false; + /** @summary Provides automatic color + * @desc Uses ROOT colors palette if possible + * @private */ + getAutoColor(numprimitives) { + numprimitives = Math.max(numprimitives || (this.#num_primitives || 5) - (this.#num_specials || 0), 2); - // special code to adjust frame position to actual position of palette - if (can_move && fp && !this.do_redraw_palette) { - const pad = pp?.getRootPad(true); + let indx = this.#auto_color_cnt ?? 0; + this.#auto_color_cnt = (indx + 1) % numprimitives; + if (indx >= numprimitives) + indx = numprimitives - 1; - if (this.options.Zvert) { - if ((pal.fX1NDC > 0.5) && (fp.fX2NDC > pal.fX1NDC)) { - need_redraw = true; - fp.fX2NDC = pal.fX1NDC - 0.01; + let indexes = this._getCustomPaletteIndexes(); + if (!indexes) { + const cp = this.getCanvPainter(); + if ((cp !== this) && isFunc(cp?._getCustomPaletteIndexes)) + indexes = cp._getCustomPaletteIndexes(); + } - if (fp.fX1NDC > fp.fX2NDC - 0.1) fp.fX1NDC = Math.max(0, fp.fX2NDC - 0.1); - } else if ((pal.fX2NDC < 0.5) && (fp.fX1NDC < pal.fX2NDC)) { - need_redraw = true; - fp.fX1NDC = pal.fX2NDC + 0.05; - if (fp.fX2NDC < fp.fX1NDC + 0.1) fp.fX2NDC = Math.min(1, fp.fX1NDC + 0.1); - } - if (need_redraw && pad) { - pad.fLeftMargin = fp.fX1NDC; - pad.fRightMargin = 1 - fp.fX2NDC; - } - } else { - if ((pal.fY1NDC > 0.5) && (fp.fY2NDC > pal.fY1NDC)) { - need_redraw = true; - fp.fY2NDC = pal.fY1NDC - 0.01; - if (fp.fY1NDC > fp.fY2NDC - 0.1) fp.fY1NDC = Math.max(0, fp.fXYNDC - 0.1); - } else if ((pal.fY2NDC < 0.5) && (fp.fY1NDC < pal.fY2NDC)) { - need_redraw = true; - fp.fY1NDC = pal.fY2NDC + 0.05; - if (fp.fXYNDC < fp.fY1NDC + 0.1) fp.fY2NDC = Math.min(1, fp.fY1NDC + 0.1); - } - if (need_redraw && pad) { - pad.fTopMargin = fp.fY1NDC; - pad.fBottomMargin = 1 - fp.fY2NDC; - } - } - } + if (indexes?.length) { + const p = Math.round(indx * (indexes.length - 3) / (numprimitives - 1)); + return indexes[p]; + } - if (!need_redraw) - return pal_painter; + if (!this.#auto_palette) + this.#auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); + const palindx = Math.round(indx * (this.#auto_palette.getLength() - 3) / (numprimitives - 1)), + colvalue = this.#auto_palette.getColor(palindx); - this.do_redraw_palette = true; + return this.addColor(colvalue); + } - fp.redraw(); + /** @summary Returns number of painters + * @protected */ + getNumPainters() { return this.#painters.length; } - const pr = !postpone_draw ? this.redraw() : Promise.resolve(true); - return pr.then(() => { - delete this.do_redraw_palette; - return pal_painter; - }); - }); + /** @summary Add painter to pad list of painters + * @protected */ + addToPrimitives(painter) { + if (this.#painters.indexOf(painter) < 0) + this.#painters.push(painter); + return this; } - /** @summary Toggle color z palette drawing */ - toggleColz() { - if (this.options.canHavePalette()) { - this.options.Zscale = !this.options.Zscale; - return this.drawColorPalette(this.options.Zscale, false, true) - .then(() => this.processOnlineChange('drawopt')); + /** @summary Call function for each painter in pad + * @param {function} userfunc - function to call + * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad + * @private */ + forEachPainterInPad(userfunc, kind) { + if (!kind) + kind = 'all'; + if (kind !== 'objects') + userfunc(this); + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (isFunc(sub.forEachPainterInPad)) { + if (kind !== 'objects') + sub.forEachPainterInPad(userfunc, kind); + } else if (kind !== 'pads') + userfunc(sub); } } - /** @summary Toggle 3D drawing mode */ - toggleMode3D() { - this.options.Mode3D = !this.options.Mode3D; - - if (this.options.Mode3D) { - if (!this.options.Surf && !this.options.Lego && !this.options.Error) { - if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) - this.options.Lego = this.options.Scat ? 13 : 14; - else - this.options.Lego = this.options.Scat ? 1 : 12; + /** @summary register for pad events receiver + * @desc in pad painter, while pad may be drawn without canvas */ + registerForPadEvents(receiver) { + this.pad_events_receiver = receiver; + } - this.options.Zero = false; // do not show zeros by default - } - } + /** @summary Generate pad events, normally handled by GED + * @desc in pad painter, while pad may be drawn without canvas + * @private */ + producePadEvent(what, padpainter, painter, position) { + if ((what === 'select') && isFunc(this.selectActivePad)) + this.selectActivePad(padpainter, painter, position); - this.copyOptionsToOthers(); - return this.interactiveRedraw('pad', 'drawopt'); + if (isFunc(this.pad_events_receiver)) + this.pad_events_receiver({ what, padpainter, painter, position }); } - /** @summary Prepare handle for color draw */ - prepareDraw(args) { - if (!args) args = { rounding: true, extra: 0, middle: 0 }; + /** @summary method redirect call to pad events receiver */ + selectObjectPainter(painter, pos) { + const canp = this.isTopPad() ? this : this.getCanvPainter(); - if (args.extra === undefined) args.extra = 0; - if (args.middle === undefined) args.middle = 0; + if (painter === undefined) + painter = this; - const histo = this.getHisto(), - xaxis = histo.fXaxis, yaxis = histo.fYaxis, - pmain = this.getFramePainter(), - hdim = this.getDimension(), - res = { - i1: args.nozoom ? 0 : this.getSelectIndex('x', 'left', 0 - args.extra), - i2: args.nozoom ? this.nbinsx : this.getSelectIndex('x', 'right', 1 + args.extra), - j1: (hdim === 1) ? 0 : (args.nozoom ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra)), - j2: (hdim === 1) ? 1 : (args.nozoom ? this.nbinsy : this.getSelectIndex('y', 'right', 1 + args.extra)), - min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1 - }; + if (pos && !this.isTopPad()) + pos = getAbsPosInCanvas(this.getPadSvg(), pos); - if (args.cutg) { - // if using cutg - define rectengular region - let i1 = res.i2, i2 = res.i1, j1 = res.j2, j2 = res.j1; - for (let ii = res.i1; ii < res.i2; ++ii) { - for (let jj = res.j1; jj < res.j2; ++jj) { - if (args.cutg.IsInside(xaxis.GetBinCoord(ii + args.middle), yaxis.GetBinCoord(jj + args.middle))) { - i1 = Math.min(i1, ii); - i2 = Math.max(i2, ii+1); - j1 = Math.min(j1, jj); - j2 = Math.max(j2, jj+1); - } - } - } + selectActivePad({ pp: this, active: true }); + + canp?.producePadEvent('select', this, painter, pos); + } - res.i1 = i1; res.i2 = i2; res.j1 = j1; res.j2 = j2; + /** @summary Draw pad active border + * @private */ + drawActiveBorder(svg_rect, is_active) { + if (is_active !== undefined) { + if (this.is_active_pad === is_active) + return; + this.is_active_pad = is_active; } - let i, j, x, y, binz, binarea; + if (this.is_active_pad === undefined) + return; - res.grx = new Float32Array(res.i2+1); - res.gry = new Float32Array(res.j2+1); + if (!svg_rect) + svg_rect = this.isCanvas() ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.getPadSvg().selectChild('.root_pad_border'); - if ((typeof histo.fBarOffset === 'number') && (typeof histo.fBarWidth === 'number') && - (histo.fBarOffset || histo.fBarWidth !== 1000)) { - if (histo.fBarOffset <= 1000) - res.xbar1 = res.ybar1 = 0.001*histo.fBarOffset; - else if (histo.fBarOffset <= 3000) - res.xbar1 = 0.001*(histo.fBarOffset-2000); - else if (histo.fBarOffset <= 5000) - res.ybar1 = 0.001*(histo.fBarOffset-4000); + const cp = this.getCanvPainter(); - if (histo.fBarWidth <= 1000) { - res.xbar2 = Math.min(1, res.xbar1 + 0.001*histo.fBarWidth); - res.ybar2 = Math.min(1, res.ybar1 + 0.001*histo.fBarWidth); - } else if (histo.fBarWidth <= 3000) - res.xbar2 = Math.min(1, res.xbar1 + 0.001*(histo.fBarWidth-2000)); - else if (histo.fBarWidth <= 5000) - res.ybar2 = Math.min(1, res.ybar1 + 0.001*(histo.fBarWidth-4000)); - } + let lineatt = this.is_active_pad && cp?.highlight_gpad ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; + if (!lineatt) + lineatt = new TAttLineHandler({ color: 'none' }); - if (args.original) { - res.original = true; - res.origx = new Float32Array(res.i2+1); - res.origy = new Float32Array(res.j2+1); - } + svg_rect.call(lineatt.func); + } - if (args.pixel_density) args.rounding = true; + /** @summary Set fast drawing property depending on the size + * @private */ + setFastDrawing(w, h) { + const was_fast = this.#fast_drawing; + this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); + if (was_fast !== this.#fast_drawing) + this.showPadButtons(); + } - if (!pmain) { - console.warn('cannot draw histogram without frame'); - return res; - } + /** @summary Return fast drawing flag + * @private */ + isFastDrawing() { return this.#fast_drawing; } - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); + /** @summary Returns true if canvas configured with grayscale + * @private */ + isGrayscale() { + if (!this.isCanvas()) + return false; + return this.#pad?.TestBit(kIsGrayscale) ?? false; + } - // calculate graphical coordinates in advance - for (i = res.i1; i <= res.i2; ++i) { - x = xaxis.GetBinCoord(i + args.middle); - if (funcs.logx && (x <= 0)) { res.i1 = i+1; continue; } - if (res.origx) res.origx[i] = x; - res.grx[i] = funcs.grx(x); - if (args.rounding) res.grx[i] = Math.round(res.grx[i]); + /** @summary Returns true if default pad range is configured + * @private */ + isDefaultPadRange() { + if (!this.#pad) + return true; + return (this.#pad.fX1 === 0) && (this.#pad.fX2 === 1) && (this.#pad.fY1 === 0) && (this.#pad.fY2 === 1); + } - if (args.use3d) { - if (res.grx[i] < -pmain.size_x3d) { - res.grx[i] = -pmain.size_x3d; - if (this.options.RevX) res.i2 = i; - else res.i1 = i; - } - if (res.grx[i] > pmain.size_x3d) { - res.grx[i] = pmain.size_x3d; - if (this.options.RevX) res.i1 = i; - else res.i2 = i; - } - } - } + /** @summary Set grayscale mode for the canvas + * @private */ + setGrayscale(flag) { + if (!this.isTopPad()) + return; - if (hdim === 1) { - res.gry[0] = funcs.gry(0); - res.gry[1] = funcs.gry(1); - } else { - for (j = res.j1; j <= res.j2; ++j) { - y = yaxis.GetBinCoord(j + args.middle); - if (funcs.logy && (y <= 0)) { res.j1 = j+1; continue; } - if (res.origy) res.origy[j] = y; - res.gry[j] = funcs.gry(y); - if (args.rounding) res.gry[j] = Math.round(res.gry[j]); + let changed = false; - if (args.use3d) { - if (res.gry[j] < -pmain.size_y3d) { - res.gry[j] = -pmain.size_y3d; - if (this.options.RevY) res.j2 = j; - else res.j1 = j; - } - if (res.gry[j] > pmain.size_y3d) { - res.gry[j] = pmain.size_y3d; - if (this.options.RevY) res.j1 = j; - else res.j2 = j; - } - } - } + if (flag === undefined) { + flag = this.#pad?.TestBit(kIsGrayscale) ?? false; + changed = (this.#last_grayscale !== undefined) && (this.#last_grayscale !== flag); + } else if (flag !== this.#pad?.TestBit(kIsGrayscale)) { + this.#pad?.InvertBit(kIsGrayscale); + changed = true; } - // find min/max values in selected range - - this.maxbin = this.minbin = this.minposbin = null; - - for (i = res.i1; i < res.i2; ++i) { - for (j = res.j1; j < res.j2; ++j) { - binz = histo.getBinContent(i + 1, j + 1); - res.sumz += binz; - if (args.pixel_density) { - binarea = (res.grx[i+1]-res.grx[i])*(res.gry[j]-res.gry[j+1]); - if (binarea <= 0) continue; - res.max = Math.max(res.max, binz); - if ((binz > 0) && ((binz 0) - if ((this.minposbin === null) || (binz < this.minposbin)) this.minposbin = binz; - } + if (changed) { + this.forEachPainter(p => { + if (isFunc(p.clearHistPalette)) + p.clearHistPalette(); + }); } - // force recalculation of z levels - this.fContour = null; + this.setColors(flag ? getGrayColors(this.#custom_colors) : this.#custom_colors); - return res; + this.#last_grayscale = flag; + + this.#custom_palette = this.#custom_palette_colors ? new ColorPalette(this.#custom_palette_colors, flag) : null; } - /** @summary Get tip text for axis bin */ - getAxisBinTip(name, axis, bin) { - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - handle = funcs[`${name}_handle`], - x1 = axis.GetBinLowEdge(bin+1); + /** @summary Set fixed-size canvas + * @private */ + _setFixedSize(on) { this.#fixed_size = on; } - if (handle.kind === kAxisLabels) - return funcs.axisAsText(name, x1); + /** @summary Create SVG element for canvas */ + createCanvasSvg(check_resize, new_size) { + const is_batch = this.isBatchMode(), lmt = 5; + let factor, svg, rect, btns, info, frect; - const x2 = axis.GetBinLowEdge(bin+2); + if (check_resize > 0) { + if (this.#fixed_size) + return check_resize > 1; // flag used to force re-drawing of all sub-pads - if ((handle.kind === kAxisTime) || this.isTF1()) - return funcs.axisAsText(name, (x1+x2)/2); + svg = this.getCanvSvg(); + if (svg.empty()) + return false; - return `[${funcs.axisAsText(name, x1)}, ${funcs.axisAsText(name, x2)})`; - } + factor = svg.property('height_factor'); - /** @summary generic draw function for histograms - * @private */ - static async _drawHist(painter, opt) { - return ensureTCanvas(painter).then(() => { - painter.setAsMainPainter(); - painter.decodeOptions(opt); + rect = this.testMainResize(check_resize, null, factor); - if (painter.isTH2Poly()) { - if (painter.options.Mode3D) - painter.options.Lego = 12; // lego always 12 - } + if (!rect.changed && (check_resize === 1)) + return false; - painter.checkPadRange(); + if (!is_batch) + btns = this.getLayerSvg('btns_layer'); - painter.scanContent(); + info = this.getLayerSvg('info_layer'); + frect = svg.selectChild('.canvas_fillrect'); + } else { + const render_to = this.selectDom(); - painter.createStat(); // only when required + if (render_to.style('position') === 'static') + render_to.style('position', 'relative'); - return painter.callDrawFunc(); - }).then(() => { - return painter.drawFunctions(); - }).then(() => { - return painter.drawHistTitle(); - }).then(() => { - if (!painter.Mode3D && painter.options.AutoZoom) - return painter.autoZoom(); - }).then(() => { - if (painter.options.Project && !painter.mode3d && isFunc(painter.toggleProjection)) - return painter.toggleProjection(painter.options.Project); - }).then(() => { - painter.fillToolbar(); - return painter; - }); - } + svg = render_to.append('svg') + .attr('class', 'jsroot root_canvas') + .property('pad_painter', this) // this is custom property + .property('redraw_by_resize', false); // could be enabled to force redraw by each resize -} // class THistPainter + this.setTopPainter(); // assign canvas as top painter of that element -/** @summary Build histogram contour lines - * @private */ -function buildHist2dContour(histo, handle, levels, palette, contour_func) { - const kMAXCONTOUR = 2004, - kMAXCOUNT = 2000, - // arguments used in the PaintContourLine - xarr = new Float32Array(2*kMAXCONTOUR), - yarr = new Float32Array(2*kMAXCONTOUR), - itarr = new Int32Array(2*kMAXCONTOUR), - nlevels = levels.length, - first_level = levels[0], last_level = levels[nlevels - 1], - polys = [], - x = [0, 0, 0, 0], y = [0, 0, 0, 0], zc = [0, 0, 0, 0], ir = [0, 0, 0, 0], - arrx = handle.grx, - arry = handle.gry; + if (is_batch) + svg.attr('xmlns', nsSVG); + else if (!this.online_canvas) + svg.append('svg:title').text('ROOT canvas'); - let lj = 0, ipoly, poly, np, npmax = 0, - i, j, k, n, m, ljfill, count, - xsave, ysave, itars, ix, jx; + if (!is_batch) + svg.style('user-select', settings.UserSelect || null); - const LinearSearch = zc => { - if (zc >= last_level) - return nlevels-1; + if (!is_batch || (this.#pad.fFillStyle > 0)) + frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); - for (let kk = 0; kk < nlevels; ++kk) { - if (zc < levels[kk]) - return kk-1; - } - return nlevels-1; - }, BinarySearch = zc => { - if (zc < first_level) - return -1; - if (zc >= last_level) - return nlevels - 1; + if (!is_batch) { + frect.style('pointer-events', 'visibleFill') + .on('dblclick', evnt => this.enlargePad(evnt, true)) + .on('click', () => this.selectObjectPainter()) + .on('mouseenter', () => this.showObjectStatus()) + .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); + } - let l = 0, r = nlevels - 1, m; - while (r - l > 1) { - m = Math.round((r + l) / 2); - if (zc < levels[m]) - r = m; - else - l = m; - } - return l; - }, - LevelSearch = nlevels < 10 ? LinearSearch : BinarySearch, - PaintContourLine = (elev1, icont1, x1, y1, elev2, icont2, x2, y2) => { - /* Double_t *xarr, Double_t *yarr, Int_t *itarr, Double_t *levels */ - const vert = (x1 === x2), - tlen = vert ? (y2 - y1) : (x2 - x1), - tdif = elev2 - elev1; - let n = icont1 + 1, ii = lj-1, icount = 0, - xlen, pdif, diff, elev; - const maxii = ii + kMAXCONTOUR/2 -3; + svg.append('svg:g').attr('class', 'primitives_layer'); + info = svg.append('svg:g').attr('class', 'info_layer'); + if (!is_batch) { + btns = svg.append('svg:g') + .attr('class', 'btns_layer') + .property('leftside', settings.ToolBarSide === 'left') + .property('vertical', settings.ToolBarVert); + } - while (n <= icont2 && ii <= maxii) { - // elev = fH->GetContourLevel(n); - elev = levels[n]; - diff = elev - elev1; - pdif = diff/tdif; - xlen = tlen*pdif; - if (vert) { - xarr[ii] = x1; - yarr[ii] = y1 + xlen; - } else { - xarr[ii] = x1 + xlen; - yarr[ii] = y1; + factor = 0.66; + if (this.#pad?.fCw && this.#pad?.fCh && (this.#pad?.fCw > 0)) { + factor = this.#pad.fCh / this.#pad.fCw; + if ((factor < 0.1) || (factor > 10)) + factor = 0.66; } - itarr[ii] = n; - icount++; - ii += 2; - n++; + + if (this.#fixed_size) { + render_to.style('overflow', 'auto'); + rect = { width: this.#pad.fCw, height: this.#pad.fCh }; + if (!rect.width || !rect.height) + rect = getElementRect(render_to); + } else + rect = this.testMainResize(2, new_size, factor); } - return icount; - }; - for (j = handle.j1; j < handle.j2-1; ++j) { - y[1] = y[0] = (arry[j] + arry[j+1])/2; - y[3] = y[2] = (arry[j+1] + arry[j+2])/2; + this.setGrayscale(); - for (i = handle.i1; i < handle.i2-1; ++i) { - zc[0] = histo.getBinContent(i+1, j+1); - zc[1] = histo.getBinContent(i+2, j+1); - zc[2] = histo.getBinContent(i+2, j+2); - zc[3] = histo.getBinContent(i+1, j+2); + this.createAttFill({ attr: this.#pad }); - for (k = 0; k < 4; k++) - ir[k] = LevelSearch(zc[k]); + if ((rect.width <= lmt) || (rect.height <= lmt)) { + if (!this.hasSnapId()) { + svg.style('display', 'none'); + console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); + } + if (this.#pad_width && this.#pad_height) { + // use last valid dimensions + rect.width = this.#pad_width; + rect.height = this.#pad_height; + } else { + // just to complete drawing. + rect.width = 800; + rect.height = 600; + } + } else + svg.style('display', null); - if ((ir[0] !== ir[1]) || (ir[1] !== ir[2]) || (ir[2] !== ir[3]) || (ir[3] !== ir[0])) { // deepscan-disable-line - x[3] = x[0] = (arrx[i] + arrx[i+1])/2; - x[2] = x[1] = (arrx[i+1] + arrx[i+2])/2; + svg.attr('x', 0).attr('y', 0).style('position', 'absolute'); - if (zc[0] <= zc[1]) n = 0; else n = 1; - if (zc[2] <= zc[3]) m = 2; else m = 3; - if (zc[n] > zc[m]) n = m; - n++; - lj=1; - for (ix=1; ix<=4; ix++) { - m = n%4 + 1; - ljfill = PaintContourLine(zc[n-1], ir[n-1], x[n-1], y[n-1], zc[m-1], ir[m-1], x[m-1], y[m-1]); - lj += 2*ljfill; - n = m; - } + if (this.#fixed_size) + svg.attr('width', rect.width).attr('height', rect.height); + else + svg.style('width', '100%').style('height', '100%').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - if (zc[0] <= zc[1]) n = 0; else n = 1; - if (zc[2] <= zc[3]) m = 2; else m = 3; - if (zc[n] > zc[m]) n = m; - n++; - lj=2; - for (ix=1; ix<=4; ix++) { - m = (n === 1) ? 4 : n-1; - ljfill = PaintContourLine(zc[n-1], ir[n-1], x[n-1], y[n-1], zc[m-1], ir[m-1], x[m-1], y[m-1]); - lj += 2*ljfill; - n = m; - } - // Re-order endpoints + svg.style('filter', settings.DarkMode || this.#pad?.$dark ? 'invert(100%)' : null); - count = 0; - for (ix = 1; ix <= lj - 5; ix += 2) { - // count = 0; - while (itarr[ix-1] !== itarr[ix]) { - xsave = xarr[ix]; - ysave = yarr[ix]; - itars = itarr[ix]; - for (jx=ix; jx<=lj-5; jx +=2) { - xarr[jx] = xarr[jx+2]; - yarr[jx] = yarr[jx+2]; - itarr[jx] = itarr[jx+2]; - } - xarr[lj-3] = xsave; - yarr[lj-3] = ysave; - itarr[lj-3] = itars; - if (count > kMAXCOUNT) break; - count++; - } - } + this.#pad_scale = settings.CanvasScale || 1; + this.#pad_x = 0; + this.#pad_y = 0; + this.#pad_width = rect.width * this.#pad_scale; + this.#pad_height = rect.height * this.#pad_scale; - if (count > 100) continue; + svg.attr('viewBox', `0 0 ${this.#pad_width} ${this.#pad_height}`) + .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio + .property('height_factor', factor) + .property('draw_x', this.#pad_x) + .property('draw_y', this.#pad_y) + .property('draw_width', this.#pad_width) + .property('draw_height', this.#pad_height); - for (ix = 1; ix <= lj - 2; ix += 2) { - ipoly = itarr[ix-1]; + this.addPadBorder(svg, frect); - if ((ipoly >= 0) && (ipoly < levels.length)) { - poly = polys[ipoly]; - if (!poly) - poly = polys[ipoly] = createTPolyLine(kMAXCONTOUR*4, true); - - np = poly.fLastPoint; - if (np < poly.fN-2) { - poly.fX[np+1] = Math.round(xarr[ix-1]); - poly.fY[np+1] = Math.round(yarr[ix-1]); - poly.fX[np+2] = Math.round(xarr[ix]); - poly.fY[np+2] = Math.round(yarr[ix]); - poly.fLastPoint = np+2; - npmax = Math.max(npmax, poly.fLastPoint+1); - } - } - } - } // end of if (ir[0] - } // end of j - } // end of i - - const polysort = new Int32Array(levels.length); - let first = 0; - // find first positive contour - for (ipoly = 0; ipoly < levels.length; ipoly++) - if (levels[ipoly] >= 0) { first = ipoly; break; } - - // store negative contours from 0 to minimum, then all positive contours - k = 0; - for (ipoly = first-1; ipoly >= 0; ipoly--) { polysort[k] = ipoly; k++; } - for (ipoly = first; ipoly < levels.length; ipoly++) { polysort[k] = ipoly; k++; } + this.setFastDrawing(this.#pad_width * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), this.#pad_height * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); - const xp = new Float32Array(2*npmax), - yp = new Float32Array(2*npmax), - has_func = isFunc(palette.calcColorIndex); // rcanvas for v7 + if (this.alignButtons && btns) + this.alignButtons(btns, this.#pad_width, this.#pad_height); - for (k = 0; k < levels.length; ++k) { - ipoly = polysort[k]; - poly = polys[ipoly]; - if (!poly) continue; + let dt = info.selectChild('.canvas_date'); + if (!gStyle.fOptDate) + dt.remove(); + else { + if (dt.empty()) + dt = info.append('text').attr('class', 'canvas_date'); + const posy = Math.round(this.#pad_height * (1 - gStyle.fDateY)), + date = new Date(); + let posx = Math.round(this.#pad_width * gStyle.fDateX); + if (!is_batch && (posx < 25)) + posx = 25; + if (gStyle.fOptDate > 3) + date.setTime(gStyle.fOptDate * 1000); - const colindx = has_func ? palette.calcColorIndex(ipoly, levels.length) : ipoly, - xx = poly.fX, yy = poly.fY, np = poly.fLastPoint+1, - xmin = 0, ymin = 0; - let istart = 0, iminus, iplus, nadd; + makeTranslate(dt, posx, posy) + .style('text-anchor', 'start') + .text(convertDate(date)); + } - while (true) { - iminus = npmax; - iplus = iminus+1; - xp[iminus]= xx[istart]; yp[iminus] = yy[istart]; - xp[iplus] = xx[istart+1]; yp[iplus] = yy[istart+1]; - xx[istart] = xx[istart+1] = xmin; - yy[istart] = yy[istart+1] = ymin; - while (true) { - nadd = 0; - for (i = 2; i < np; i += 2) { - if ((iplus < 2*npmax-1) && (xx[i] === xp[iplus]) && (yy[i] === yp[iplus])) { - iplus++; - xp[iplus] = xx[i+1]; yp[iplus] = yy[i+1]; - xx[i] = xx[i+1] = xmin; - yy[i] = yy[i+1] = ymin; - nadd++; - } - if ((iminus > 0) && (xx[i+1] === xp[iminus]) && (yy[i+1] === yp[iminus])) { - iminus--; - xp[iminus] = xx[i]; yp[iminus] = yy[i]; - xx[i] = xx[i+1] = xmin; - yy[i] = yy[i+1] = ymin; - nadd++; - } - } - if (nadd === 0) break; - } + const iname = this.getItemName(); + if (iname) + this.drawItemNameOnCanvas(iname); + else if (!gStyle.fOptFile) + info.selectChild('.canvas_item').remove(); - if ((iminus+1 < iplus) && (iminus >= 0)) - contour_func(colindx, xp, yp, iminus, iplus, ipoly); + return true; + } - istart = 0; - for (i = 2; i < np; i += 2) { - if (xx[i] !== xmin && yy[i] !== ymin) { - istart = i; - break; - } - } + /** @summary Draw item name on canvas if gStyle.fOptFile is configured + * @private */ + drawItemNameOnCanvas(item_name) { + const info = this.getLayerSvg('info_layer'); + let df = info.selectChild('.canvas_item'); + const fitem = getHPainter().findRootFileForItem(item_name), + fname = (gStyle.fOptFile === 3) ? item_name : ((gStyle.fOptFile === 2) ? fitem?._fullurl : fitem?._name); - if (istart === 0) break; + if (!gStyle.fOptFile || !fname) + df.remove(); + else { + if (df.empty()) + df = info.append('text').attr('class', 'canvas_item'); + const rect = this.getPadRect(); + makeTranslate(df, Math.round(rect.width * (1 - gStyle.fDateX)), Math.round(rect.height * (1 - gStyle.fDateY))) + .style('text-anchor', 'end') + .text(fname); + } + if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { + info.selectChild('.canvas_date') + .text(convertDate(getTDatime(gStyle.fOptDate === 2 ? fitem._file.fDatimeC : fitem._file.fDatimeM))); } } -} - -/** @summary Handle 3D triangles with color levels */ -class Triangles3DHandler { + /** @summary Return true if this pad enlarged */ + isPadEnlarged() { + if (this.isTopPad()) + return this.enlargeMain('state') === 'on'; + return this.getCanvSvg().property('pad_enlarged') === this.#pad; + } - constructor(ilevels, grz, grz_min, grz_max, dolines, donormals, dogrid) { - let levels = [grz_min, grz_max]; // just cut top/bottom parts + /** @summary Enlarge pad draw element when possible */ + enlargePad(evnt, is_dblclick, is_escape) { + evnt?.preventDefault(); + evnt?.stopPropagation(); - if (ilevels) { - // recalculate levels into graphical coordinates - levels = new Float32Array(ilevels.length); - for (let ll = 0; ll < ilevels.length; ++ll) - levels[ll] = grz(ilevels[ll]); - } + // ignore double click on online canvas itself for enlarge + if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) + return; - Object.assign(this, { grz_min, grz_max, dolines, donormals, dogrid }); + const svg_can = this.getCanvSvg(), + pad_enlarged = svg_can.property('pad_enlarged'); - this.loop = 0; + if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { + if (this.#fixed_size) + return; // canvas cannot be enlarged in such mode + if (!this.enlargeMain(is_escape ? false : 'toggle')) + return; + if (this.enlargeMain('state') === 'off') + svg_can.property('pad_enlarged', null); + else + selectActivePad({ pp: this, active: true }); + } else if (!pad_enlarged && !is_escape) { + this.enlargeMain(true, true); + svg_can.property('pad_enlarged', this.#pad); + selectActivePad({ pp: this, active: true }); + } else if (pad_enlarged === this.#pad) { + this.enlargeMain(false); + svg_can.property('pad_enlarged', null); + } else if (!is_escape && is_dblclick) + console.error('missmatch with pad double click events'); - const nfaces = [], posbuf = [], posbufindx = [], // buffers for faces - pntbuf = new Float32Array(6*3), // maximal 6 points - gridpnts = new Float32Array(2*3), - levels_eps = (levels[levels.length-1] - levels[0]) / levels.length / 1e2; - let nsegments = 0, lpos = null, lindx = 0, // buffer for lines - ngridsegments = 0, grid = null, gindx = 0, // buffer for grid lines segments - normindx = [], // buffer to remember place of vertex for each bin - pntindx = 0, lastpart = 0, gridcnt = 0; + return this.checkResize(true); + } - function checkSide(z, level1, level2, eps) { - return (z < level1 - eps) ? -1 : (z > level2 + eps ? 1 : 0); + /** @summary Create main SVG element for pad + * @return true when pad is displayed and all its items should be redrawn */ + createPadSvg(only_resize) { + if (this.isTopPad()) { + this.createCanvasSvg(only_resize ? 2 : 0); + return true; } - this.createNormIndex = function(handle) { - // for each bin maximal 8 points reserved - if (handle.donormals) - normindx = new Int32Array((handle.i2-handle.i1)*(handle.j2-handle.j1)*8).fill(-1); - }; - - this.createBuffers = function() { - if (!this.loop) return; - - for (let lvl = 1; lvl < levels.length; ++lvl) { - if (nfaces[lvl]) { - posbuf[lvl] = new Float32Array(nfaces[lvl] * 9); - posbufindx[lvl] = 0; - } - } - if (this.dolines && (nsegments > 0)) - lpos = new Float32Array(nsegments * 6); - if (this.dogrid && (ngridsegments > 0)) - grid = new Float32Array(ngridsegments * 6); - }; + const svg_can = this.getCanvSvg(), + width = svg_can.property('draw_width'), + height = svg_can.property('draw_height'), + pad_enlarged = svg_can.property('pad_enlarged'), + pad_visible = !this.#pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.#pad)), + is_batch = this.isBatchMode(); + let w = Math.round(this.#pad.fAbsWNDC * width), + h = Math.round(this.#pad.fAbsHNDC * height), + x = Math.round(this.#pad.fAbsXlowNDC * width), + y = Math.round(height * (1 - this.#pad.fAbsYlowNDC)) - h, + svg_pad, svg_border, btns; - this.addLineSegment = function(x1, y1, z1, x2, y2, z2) { - if (!this.dolines) return; - const side1 = checkSide(z1, this.grz_min, this.grz_max, 0), - side2 = checkSide(z2, this.grz_min, this.grz_max, 0); - if ((side1 === side2) && (side1 !== 0)) - return; - if (!this.loop) - return ++nsegments; + if (pad_enlarged === this.#pad) { + w = width; + h = height; + x = y = 0; + } - if (side1 !== 0) { - const diff = z2 - z1; - z1 = (side1 < 0) ? this.grz_min : this.grz_max; - x1 = x2 - (x2 - x1) / diff * (z2 - z1); - y1 = y2 - (y2 - y1) / diff * (z2 - z1); - } - if (side2 !== 0) { - const diff = z1 - z2; - z2 = (side2 < 0) ? this.grz_min : this.grz_max; - x2 = x1 - (x1 - x2) / diff * (z1 - z2); - y2 = y1 - (y1 - y2) / diff * (z1 - z2); - } + if (only_resize) { + svg_pad = this.getPadSvg(); + svg_border = svg_pad.selectChild('.root_pad_border'); + if (!is_batch) + btns = this.getLayerSvg('btns_layer'); + this.addPadInteractive(true); + } else { + svg_pad = svg_can.selectChild('.primitives_layer') + .append('svg:svg') // svg used to blend all drawings outside + .classed('__root_pad_' + this.#pad_name, true) + .attr('pad', this.#pad_name) // set extra attribute to mark pad name + .property('pad_painter', this); // this is custom property - lpos[lindx] = x1; lpos[lindx+1] = y1; lpos[lindx+2] = z1; lindx+=3; - lpos[lindx] = x2; lpos[lindx+1] = y2; lpos[lindx+2] = z2; lindx+=3; - }; + if (!is_batch) + svg_pad.append('svg:title').text('subpad ' + this.#pad_name); - function addCrossingPoint(xx1, yy1, zz1, xx2, yy2, zz2, crossz, with_grid) { - if (pntindx >= pntbuf.length) - console.log('more than 6 points???'); + // need to check attributes directly while attributes objects will be created later + if (!is_batch || (this.#pad.fFillStyle > 0) || ((this.#pad.fLineStyle > 0) && (this.#pad.fLineColor > 0))) + svg_border = svg_pad.append('svg:path').attr('class', 'root_pad_border'); - const part = (crossz - zz1) / (zz2 - zz1); - let shift = 3; - if ((lastpart !== 0) && (Math.abs(part) < Math.abs(lastpart))) { - // while second crossing point closer than first to original, move it in memory - pntbuf[pntindx] = pntbuf[pntindx-3]; - pntbuf[pntindx+1] = pntbuf[pntindx-2]; - pntbuf[pntindx+2] = pntbuf[pntindx-1]; - pntindx-=3; shift = 6; + if (!is_batch) { + svg_border.style('pointer-events', 'visibleFill') // get events also for not visible rect + .on('dblclick', evnt => this.enlargePad(evnt, true)) + .on('click', () => this.selectObjectPainter()) + .on('mouseenter', () => this.showObjectStatus()) + .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); } - pntbuf[pntindx] = xx1 + part*(xx2-xx1); - pntbuf[pntindx+1] = yy1 + part*(yy2-yy1); - pntbuf[pntindx+2] = crossz; - - if (with_grid && grid) { - gridpnts[gridcnt] = pntbuf[pntindx]; - gridpnts[gridcnt+1] = pntbuf[pntindx+1]; - gridpnts[gridcnt+2] = pntbuf[pntindx+2]; - gridcnt += 3; + svg_pad.append('svg:g').attr('class', 'primitives_layer'); + if (!is_batch) { + btns = svg_pad.append('svg:g') + .attr('class', 'btns_layer') + .property('leftside', settings.ToolBarSide !== 'left') + .property('vertical', settings.ToolBarVert); } - - pntindx += shift; - lastpart = part; } - function rememberVertex(indx, handle, ii, jj) { - const bin = ((ii-handle.i1) * (handle.j2-handle.j1) + (jj-handle.j1))*8; + this.createAttFill({ attr: this.#pad }); + this.createAttLine({ attr: this.#pad, color0: !this.#pad.fBorderMode ? 'none' : '' }); - if (normindx[bin] >= 0) - return console.error('More than 8 vertexes for the bin'); + svg_pad.style('display', pad_visible ? null : 'none') + .attr('viewBox', `0 0 ${w} ${h}`) // due to svg + .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio + .attr('x', x) // due to svg + .attr('y', y) // due to svg + .attr('width', w) // due to svg + .attr('height', h) // due to svg + .property('draw_x', x) // this is to make similar with canvas + .property('draw_y', y) + .property('draw_width', w) + .property('draw_height', h); - const pos = bin + 8 + normindx[bin]; // position where write index - normindx[bin]--; - normindx[pos] = indx; // at this moment index can be overwritten, means all 8 position are there - } + this.#pad_scale = this.getCanvPainter().getPadScale(); + this.#pad_x = x; + this.#pad_y = y; + this.#pad_width = w; + this.#pad_height = h; - this.addMainTriangle = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, is_first, handle, i, j) { - for (let lvl = 1; lvl < levels.length; ++lvl) { - let side1 = checkSide(z1, levels[lvl-1], levels[lvl], levels_eps), - side2 = checkSide(z2, levels[lvl-1], levels[lvl], levels_eps), - side3 = checkSide(z3, levels[lvl-1], levels[lvl], levels_eps), - side_sum = side1 + side2 + side3; + this.addPadBorder(svg_pad, svg_border, true); - // always show top segments - if ((lvl > 1) && (lvl === levels.length - 1) && (side_sum === 3) && (z1 <= this.grz_max)) - side1 = side2 = side3 = side_sum = 0; + this.setFastDrawing(w * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), h * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); + // special case of 3D canvas overlay + if (svg_pad.property('can3d') === constants$1.Embed3D.Overlay) { + this.selectDom().select('.draw3d_' + this.#pad_name) + .style('display', pad_visible ? '' : 'none'); + } - if (side_sum === 3) continue; - if (side_sum === -3) return; + if (this.alignButtons && btns) + this.alignButtons(btns, this.#pad_width, this.#pad_height); - if (!this.loop) { - let npnts = Math.abs(side2-side1) + Math.abs(side3-side2) + Math.abs(side1-side3); - if (side1 === 0) ++npnts; - if (side2 === 0) ++npnts; - if (side3 === 0) ++npnts; + return pad_visible; + } - if ((npnts === 1) || (npnts === 2)) console.error(`FOUND npnts = ${npnts}`); + /** @summary Add border decorations + * @private */ + addPadBorder(svg_pad, svg_border, draw_line) { + if (!svg_border) + return; - if (npnts > 2) { - if (nfaces[lvl] === undefined) - nfaces[lvl] = 0; - nfaces[lvl] += npnts-2; - } + svg_border.attr('d', `M0,0H${this.#pad_width}V${this.#pad_height}H0Z`) + .call(this.fillatt.func); + if (draw_line) + svg_border.call(this.lineatt.func); - // check if any(contours for given level exists - if (((side1 > 0) || (side2 > 0) || (side3 > 0)) && - ((side1 !== side2) || (side2 !== side3) || (side3 !== side1))) // deepscan-disable-line - ++ngridsegments; + this.drawActiveBorder(svg_border); - continue; - } + let svg_border1 = svg_pad.selectChild('.root_pad_border1'), + svg_border2 = svg_pad.selectChild('.root_pad_border2'); - gridcnt = 0; + if (this.#pad.fBorderMode && this.#pad.fBorderSize) { + const arr = getBoxDecorations(0, 0, this.#pad_width, this.#pad_height, this.#pad.fBorderMode, this.#pad.fBorderSize, this.#pad.fBorderSize); - pntindx = 0; - if (side1 === 0) { pntbuf[pntindx] = x1; pntbuf[pntindx+1] = y1; pntbuf[pntindx+2] = z1; pntindx += 3; } + if (svg_border2.empty()) + svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); + if (svg_border1.empty()) + svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); - if (side1 !== side2) { - // order is important, should move from 1->2 point, checked via lastpart - lastpart = 0; - if ((side1 < 0) || (side2 < 0)) addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl-1]); - if ((side1 > 0) || (side2 > 0)) addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl], true); - } + svg_border1.attr('d', arr[0]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); + svg_border2.attr('d', arr[1]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); + } else { + svg_border1.remove(); + svg_border2.remove(); + } + } - if (side2 === 0) { pntbuf[pntindx] = x2; pntbuf[pntindx+1] = y2; pntbuf[pntindx+2] = z2; pntindx += 3; } + /** @summary Add pad interactive features like dragging and resize + * @private */ + addPadInteractive(cleanup = false) { + if (isFunc(this.$userInteractive)) { + this.$userInteractive(); + delete this.$userInteractive; + } - if (side2 !== side3) { - // order is important, should move from 2->3 point, checked via lastpart - lastpart = 0; - if ((side2 < 0) || (side3 < 0)) addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl-1]); - if ((side2 > 0) || (side3 > 0)) addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl], true); - } + if (this.isBatchMode() || this.isCanvas() || !this.isEditable()) + return; - if (side3 === 0) { pntbuf[pntindx] = x3; pntbuf[pntindx+1] = y3; pntbuf[pntindx+2] = z3; pntindx += 3; } + const svg_can = this.getCanvSvg(), + width = svg_can.property('draw_width'), + height = svg_can.property('draw_height'); - if (side3 !== side1) { - // order is important, should move from 3->1 point, checked via lastpart - lastpart = 0; - if ((side3 < 0) || (side1 < 0)) addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl-1]); - if ((side3 > 0) || (side1 > 0)) addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl], true); - } + addDragHandler(this, { + cleanup, // do cleanup to let assign new handlers later on + x: this.#pad_x, y: this.#pad_y, width: this.#pad_width, height: this.#pad_height, no_transform: true, + only_resize: true, + is_disabled: kind => svg_can.property('pad_enlarged') || this.btns_active_flag || + (kind === 'move' && (this.options._disable_dragging || this.getFramePainter()?.mode3d)), + getDrawG: () => this.getPadSvg(), + pad_rect: { width, height }, + minwidth: 20, minheight: 20, + move_resize: (_x, _y, _w, _h) => { + const x0 = this.#pad.fAbsXlowNDC, + y0 = this.#pad.fAbsYlowNDC, + scale_w = _w / width / this.#pad.fAbsWNDC, + scale_h = _h / height / this.#pad.fAbsHNDC, + shift_x = _x / width - x0, + shift_y = 1 - (_y + _h) / height - y0; + this.forEachPainterInPad(p => { + const subpad = p.getRootPad(); + subpad.fAbsXlowNDC += (subpad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; + subpad.fAbsYlowNDC += (subpad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; + subpad.fAbsWNDC *= scale_w; + subpad.fAbsHNDC *= scale_h; + }, 'pads'); + }, + redraw: () => this.interactiveRedraw('pad', 'padpos') + }); + } - if (pntindx === 0) continue; - if (pntindx < 9) { console.log(`found ${pntindx/3} points, must be at least 3`); continue; } + /** @summary Disable pad drawing + * @desc Complete SVG element will be hidden */ + disablePadDrawing() { + if (!this.#pad_draw_disabled && !this.isTopPad()) { + this.#pad_draw_disabled = true; + this.createPadSvg(true); + } + } - if (grid && (gridcnt === 6)) { - for (let jj = 0; jj < 6; ++jj) - grid[gindx+jj] = gridpnts[jj]; - gindx += 6; - } + /** @summary Check if it is special object, which should be handled separately + * @desc It can be TStyle or list of colors or palette object + * @return {boolean} true if any */ + checkSpecial(obj) { + if (!obj) + return false; - // if three points and surf === 14, remember vertex for each point + if (obj._typename === clTStyle) { + Object.assign(gStyle, obj); + return true; + } - const buf = posbuf[lvl]; - let s = posbufindx[lvl]; - if (this.donormals && (pntindx === 9)) { - rememberVertex(s, handle, i, j); - rememberVertex(s+3, handle, i+1, is_first ? j+1 : j); - rememberVertex(s+6, handle, is_first ? i : i+1, j+1); - } + const o = this.getOptions(true); - for (let k1 = 3; k1 < pntindx - 3; k1 += 3) { - buf[s] = pntbuf[0]; buf[s+1] = pntbuf[1]; buf[s+2] = pntbuf[2]; s+=3; - buf[s] = pntbuf[k1]; buf[s+1] = pntbuf[k1+1]; buf[s+2] = pntbuf[k1+2]; s+=3; - buf[s] = pntbuf[k1+3]; buf[s+1] = pntbuf[k1+4]; buf[s+2] = pntbuf[k1+5]; s+=3; + if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { + if (o?.CreatePalette) { + let arr = []; + for (let n = obj.arr.length - o.CreatePalette; n < obj.arr.length; ++n) { + const col = getRGBfromTColor(obj.arr[n]); + if (!col) { + console.log('Fail to create color for palette'); + arr = null; + break; + } + arr.push(col); } - posbufindx[lvl] = s; + if (arr.length) + this.#custom_palette = new ColorPalette(arr); } - }; - this.callFuncs = function(meshFunc, linesFunc) { - for (let lvl = 1; lvl < levels.length; ++lvl) { - if (posbuf[lvl] && meshFunc) - meshFunc(lvl, posbuf[lvl], normindx); - } + if (!o || o.GlobalColors) // set global list of colors + adoptRootColors(obj); - if (lpos && linesFunc) { - if (nsegments*6 !== lindx) - console.error(`SURF lines mismmatch nsegm=${nsegments} lindx=${lindx} diff=${nsegments*6 - lindx}`); - linesFunc(false, lpos); - } + // copy existing colors and extend with new values + this.#custom_colors = o?.LocalColors ? extendRootColors(null, obj) : null; + return true; + } - if (grid && linesFunc) { - if (ngridsegments*6 !== gindx) - console.error(`SURF grid draw mismatch ngridsegm=${ngridsegments} gindx=${gindx} diff=${ngridsegments*6 - gindx}`); - linesFunc(true, grid); + if ((obj._typename === clTObjArray) && (obj.name === 'CurrentColorPalette')) { + const arr = [], indx = []; + let missing = false; + for (let n = 0; n < obj.arr.length; ++n) { + const col = obj.arr[n]; + if (col?._typename === clTColor) { + indx[n] = col.fNumber; + arr[n] = getRGBfromTColor(col); + } else { + console.log(`Missing color with index ${n}`); + missing = true; + } } - }; - } - -} - - -/** @summary Build 3d surface - * @desc Make it indepependent from three.js to be able reuse it for 2d case - * @private */ -function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { - const main_grz = handle.grz, - arrx = handle.original ? handle.origx : handle.grx, - arry = handle.original ? handle.origy : handle.gry, - triangles = new Triangles3DHandler(ilevels, handle.grz, handle.grz_min, handle.grz_max, handle.dolines, handle.donormals, handle.dogrid); - let i, j, x1, x2, y1, y2, z11, z12, z21, z22; - - triangles.createNormIndex(handle); - - for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { - triangles.createBuffers(); - - for (i = handle.i1; i < handle.i2-1; ++i) { - x1 = handle.original ? 0.5 * (arrx[i] + arrx[i+1]) : arrx[i]; - x2 = handle.original ? 0.5 * (arrx[i+1] + arrx[i+2]) : arrx[i+1]; - for (j = handle.j1; j < handle.j2-1; ++j) { - y1 = handle.original ? 0.5 * (arry[j] + arry[j+1]) : arry[j]; - y2 = handle.original ? 0.5 * (arry[j+1] + arry[j+2]) : arry[j+1]; - z11 = main_grz(histo.getBinContent(i+1, j+1)); - z12 = main_grz(histo.getBinContent(i+1, j+2)); - z21 = main_grz(histo.getBinContent(i+2, j+1)); - z22 = main_grz(histo.getBinContent(i+2, j+2)); + const apply = (!o || (!missing && !o.IgnorePalette)); + this.#custom_palette_indexes = apply ? indx : null; + this.#custom_palette_colors = apply ? arr : null; - triangles.addMainTriangle(x1, y1, z11, x2, y2, z22, x1, y2, z12, true, handle, i, j); - - triangles.addMainTriangle(x1, y1, z11, x2, y1, z21, x2, y2, z22, false, handle, i, j); + return true; + } - triangles.addLineSegment(x1, y2, z12, x1, y1, z11); - triangles.addLineSegment(x1, y1, z11, x2, y1, z21); + return false; + } - if (i === handle.i2 - 2) triangles.addLineSegment(x2, y1, z21, x2, y2, z22); - if (j === handle.j2 - 2) triangles.addLineSegment(x1, y2, z12, x2, y2, z22); + /** @summary Check if special objects appears in primitives + * @desc it could be list of colors or palette */ + checkSpecialsInPrimitives(can, count_specials) { + const lst = can?.fPrimitives; + if (count_specials) + this.#num_specials = 0; + if (!lst) + return; + for (let i = 0; i < lst.arr?.length; ++i) { + if (this.checkSpecial(lst.arr[i])) { + lst.arr[i].$special = true; // mark object as special one, do not use in drawing + if (count_specials) + this.#num_specials++; } } } - triangles.callFuncs(meshFunc, linesFunc); -} - + /** @summary try to find object by name in list of pad primitives + * @desc used to find title drawing + * @private */ + findInPrimitives(objname, objtype) { + const match = obj => obj && (obj?.fName === objname) && (objtype ? (obj?._typename === objtype) : true), + snap = this.#snap_primitives?.find(s => match((s.fKind === webSnapIds.kObject) ? s.fSnapshot : null)); -/** - * @summary Painter for TH2 classes - * @private - */ + return snap ? snap.fSnapshot : this.#pad?.fPrimitives?.arr.find(match); + } -let TH2Painter$2 = class TH2Painter extends THistPainter { + /** @summary Try to find painter for specified object + * @desc can be used to find painter for some special objects, registered as + * histogram functions + * @param {object} selobj - object to which painter should be search, set null to ignore parameter + * @param {string} [selname] - object name, set to null to ignore + * @param {string} [seltype] - object type, set to null to ignore + * @return {object} - painter for specified object (if any) + * @private */ + findPainterFor(selobj, selname, seltype) { + return this.#painters.find(p => { + const pobj = p.getObject(); + if (!pobj) + return false; - /** @summary constructor - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = true; - this._show_empty_bins = false; + if (selobj && (pobj === selobj)) + return true; + if (!selname && !seltype) + return false; + if (selname && (pobj.fName !== selname)) + return false; + if (seltype && (pobj._typename !== seltype)) + return false; + return true; + }); } - /** @summary cleanup painter */ - cleanup() { - delete this.tt_handle; - - super.cleanup(); + /** @summary Return true if any objects beside sub-pads exists in the pad */ + hasObjectsToDraw() { + return this.#pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); } - /** @summary Toggle projection */ - toggleProjection(kind, width) { - if ((kind === 'Projections') || (kind === 'Off')) - kind = ''; + /** @summary sync drawing/redrawing/resize of the pad + * @param {string} kind - kind of draw operation, if true - always queued + * @return {Promise} when pad is ready for draw operation or false if operation already queued + * @private */ + syncDraw(kind) { + const entry = { kind: kind || 'redraw' }; + if (this.#doing_draw === undefined) { + this.#doing_draw = [entry]; + return Promise.resolve(true); + } + // if queued operation registered, ignore next calls, indx === 0 is running operation + if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) + return false; + this.#doing_draw.push(entry); + return new Promise(resolveFunc => { + entry.func = resolveFunc; + }); + } - let widthX = width, widthY = width; + /** @summary indicates if painter performing objects draw + * @private */ + doingDraw() { return this.#doing_draw !== undefined; } - if (isStr(kind) && (kind.indexOf('XY') === 0)) { - const ws = (kind.length > 2) ? kind.slice(2) : ''; - kind = 'XY'; - widthX = widthY = parseInt(ws) || 1; - } else if (isStr(kind) && (kind.length > 1)) { - const ps = kind.indexOf('_'); - if ((ps > 0) && (kind[0] === 'X') && (kind[ps+1] === 'Y')) { - widthX = parseInt(kind.slice(1, ps)) || 1; - widthY = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps+1] === 'X')) { - widthY = parseInt(kind.slice(1, ps)) || 1; - widthX = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else { - widthX = widthY = parseInt(kind.slice(1)) || 1; - kind = kind[0]; + /** @summary confirms that drawing is completed, may trigger next drawing immediately + * @private */ + confirmDraw() { + if (this.#doing_draw === undefined) + return console.warn('failure, should not happen'); + this.#doing_draw.shift(); + if (!this.#doing_draw.length) + this.#doing_draw = undefined; + else { + const entry = this.#doing_draw[0]; + if (entry.func) { + entry.func(); + delete entry.func; } } + } - if (!widthX && !widthY) - widthX = widthY = 1; + /** @summary Draw single primitive */ + async drawObject(/* dom, obj, opt */) { + console.log('Not possible to draw object without loading of draw.mjs'); + return null; + } - if (kind && (this.is_projection === kind)) { - if ((this.projection_widthX === widthX) && (this.projection_widthY === widthY)) - kind = ''; - else { - this.projection_widthX = widthX; - this.projection_widthY = widthY; - return; - } + /** @summary Draw pad primitives + * @return {Promise} when drawing completed + * @private */ + async drawPrimitives(indx) { + if (indx === undefined) { + if (this.isCanvas()) + this.#start_draw_tm = new Date().getTime(); + + // set number of primitives + this.#num_primitives = this.#pad?.fPrimitives?.arr?.length || 0; + + // sync to prevent immediate pad redraw during normal drawing sequence + return this.syncDraw(true).then(() => this.drawPrimitives(0)); } - delete this.proj_hist; + if (!this.#pad || (indx >= this.#num_primitives)) { + if (this.#start_draw_tm) { + const spenttm = new Date().getTime() - this.#start_draw_tm; + if (spenttm > 1000) + console.log(`Canvas ${this.#pad?.fName || '---'} drawing took ${(spenttm * 1e-3).toFixed(2)}s`); + this.#start_draw_tm = undefined; + } - const new_proj = (this.is_projection === kind) ? '' : kind; - this.projection_widthX = widthX; - this.projection_widthY = widthY; - this.is_projection = ''; // avoid projection handling until area is created + this.confirmDraw(); + return; + } - this.provideSpecialDrawArea(new_proj).then(() => { this.is_projection = new_proj; return this.redrawProjection(); }); - } + const obj = this.#pad.fPrimitives.arr[indx]; - /** @summary Redraw projection */ - async redrawProjection(ii1, ii2, jj1, jj2) { - if (!this.is_projection) - return false; + if (!obj || obj.$special || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) + return this.drawPrimitives(indx + 1); - if (jj2 === undefined) { - if (!this.tt_handle) return; - ii1 = Math.round((this.tt_handle.i1 + this.tt_handle.i2)/2); ii2 = ii1+1; - jj1 = Math.round((this.tt_handle.j1 + this.tt_handle.j2)/2); jj2 = jj1+1; - } + // use of Promise should avoid large call-stack depth when many primitives are drawn + return this.drawObject(this, obj, this.#pad.fPrimitives.opt[indx]).then(op => { + if (isObject(op)) + op._primitive = true; // mark painter as belonging to primitives - const canp = this.getCanvPainter(); + return this.drawPrimitives(indx + 1); + }); + } - if (canp && !canp._readonly && (this.snapid !== undefined)) { - // this is when projection should be created on the server side - if (((this.is_projection === 'X') || (this.is_projection === 'XY')) && !canp.websocketTimeout('projX')) { - if (canp.sendWebsocket(`EXECANDSEND:DXPROJ:${this.snapid}:ProjectionX("_projx",${jj1+1},${jj2},"")`)) - canp.websocketTimeout('projX', 1000); - } - if (((this.is_projection === 'Y') || (this.is_projection === 'XY')) && !canp.websocketTimeout('projY')) { - if (canp.sendWebsocket(`EXECANDSEND:DYPROJ:${this.snapid}:ProjectionY("_projy",${ii1+1},${ii2},"")`)) - canp.websocketTimeout('projY', 1000); + /** @summary Divide pad on sub-pads + * @return {Promise} when finished + * @private */ + async divide(nx, ny, use_existing) { + let color = this.#pad.fFillColor; + if (!use_existing) { + if (color < 15) + color = 19; + else if (color < 20) + color--; + } + + if (nx && !ny && use_existing) { + for (let k = 0; k < nx; ++k) { + if (!this.getSubPadPainter(k + 1)) { + use_existing = false; + break; + } } - return true; + if (use_existing) + return this; } - if (this.doing_projection) - return false; + this.cleanPrimitives(isPadPainter); + if (!this.#pad.fPrimitives) + this.#pad.fPrimitives = create$1(clTList); + this.#pad.fPrimitives.Clear(); - this.doing_projection = true; + if ((!nx && !ny) || !this.#pad.Divide(nx, ny, 0.01, 0.01, color)) + return this; - const histo = this.getHisto(), - createXProject = () => { - const p = createHistogram(clTH1D, this.nbinsx); - Object.assign(p.fXaxis, histo.fXaxis); - p.fName = 'xproj'; - p.fTitle = 'X projection'; - return p; - }, - createYProject = () => { - const p = createHistogram(clTH1D, this.nbinsy); - Object.assign(p.fXaxis, histo.fYaxis); - p.fName = 'yproj'; - p.fTitle = 'Y projection'; - return p; - }, - fillProjectHist = (kind, p) => { - let first = 0, last = -1; - if (kind === 'X') { - for (let i = 0; i < this.nbinsx; ++i) { - let sum = 0; - for (let j = jj1; j < jj2; ++j) - sum += histo.getBinContent(i+1, j+1); - p.setBinContent(i+1, sum); - } - p.fTitle = 'X projection ' + (jj1+1 === jj2 ? `bin ${jj2}` : `bins [${jj1+1} .. ${jj2}]`); - if (this.tt_handle) { first = this.tt_handle.i1+1; last = this.tt_handle.i2; } - } else { - for (let j = 0; j < this.nbinsy; ++j) { - let sum = 0; - for (let i = ii1; i < ii2; ++i) - sum += histo.getBinContent(i+1, j+1); - p.setBinContent(j+1, sum); - } - p.fTitle = 'Y projection ' + (ii1+1 === ii2 ? `bin ${ii2}` : `bins [${ii1+1} .. ${ii2}]`); - if (this.tt_handle) { first = this.tt_handle.j1+1; last = this.tt_handle.j2; } - } + const drawNext = indx => { + if (indx >= this.#pad.fPrimitives.arr.length) + return this; + return this.drawObject(this, this.#pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); + }; - if (first < last) { - const axis = p.fXaxis; - axis.fFirst = first; - axis.fLast = last; + return drawNext(0); + } - if (((axis.fFirst === 1) && (axis.fLast === axis.fNbins)) === axis.TestBit(EAxisBits.kAxisRange)) - axis.InvertBit(EAxisBits.kAxisRange); - } + /** @summary Return sub-pads painter, only direct childs are checked + * @private */ + getSubPadPainter(n) { + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (isPadPainter(sub) && (sub.getRootPad()?.fNumber === n)) + return sub; + } + return null; + } - // reset statistic before display - p.fEntries = 0; - p.fTsumw = 0; - }; + /** @summary Process tooltip event in the pad + * @private */ + processPadTooltipEvent(pnt) { + const painters = [], hints = []; - if (!this.proj_hist) { - switch (this.is_projection) { - case 'X': - this.proj_hist = createXProject(); - break; - case 'XY': - this.proj_hist = createXProject(); - this.proj_hist2 = createYProject(); - break; - default: - this.proj_hist = createYProject(); - } - } + // first count - how many processors are there + this.#painters?.forEach(obj => { + if (isFunc(obj.processTooltipEvent)) + painters.push(obj); + }); - if (this.is_projection === 'XY') { - fillProjectHist('X', this.proj_hist); - fillProjectHist('Y', this.proj_hist2); - return this.drawInSpecialArea(this.proj_hist, '', 'X') - .then(() => this.drawInSpecialArea(this.proj_hist2, '', 'Y')) - .then(res => { delete this.doing_projection; return res; }); - } + if (pnt) + pnt.nproc = painters.length; - fillProjectHist(this.is_projection, this.proj_hist); + painters.forEach(obj => { + const hint = obj.processTooltipEvent(pnt) || { user_info: null }; + hints.push(hint); + if (pnt?.painters) + hint.painter = obj; + }); - return this.drawInSpecialArea(this.proj_hist).then(res => { delete this.doing_projection; return res; }); + return hints; } - /** @summary Execute TH2 menu command - * @desc Used to catch standard menu items and provide local implementation */ - executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) - return true; + /** @summary Changes canvas dark mode + * @private */ + changeDarkMode(mode) { + this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); + } - if ((method.fName === 'SetShowProjectionX') || (method.fName === 'SetShowProjectionY')) { - this.toggleProjection(method.fName[17], args && parseInt(args) ? parseInt(args) : 1); - return true; - } + /** @summary Fill pad context menu + * @private */ + fillContextMenu(menu) { + const pad = this.getRootPad(true); + if (!pad) + return false; - if (method.fName === 'SetShowProjectionXY') { - this.toggleProjection('X' + args.replaceAll(',', '_Y')); - return true; - } + menu.header(`${pad._typename}::${pad.fName}`, `${urlClassPrefix}${pad._typename}.html`); - return false; - } + menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - if (!this.isTH2Poly() && this.getPadPainter()?.iscan) { - let kind = this.is_projection || ''; - if (kind) kind += this.projection_widthX; - if ((this.projection_widthX !== this.projection_widthY) && (this.is_projection === 'XY')) - kind = `X${this.projection_widthX}_Y${this.projection_widthY}`; + menu.addchk(pad.fGridx, 'Grid x', flag => { + pad.fGridx = flag ? 1 : 0; + this.interactiveRedraw('pad', `exec:SetGridx(${flag ? 1 : 0})`); + }); + menu.addchk(pad.fGridy, 'Grid y', flag => { + pad.fGridy = flag ? 1 : 0; + this.interactiveRedraw('pad', `exec:SetGridy(${flag ? 1 : 0})`); + }); + menu.sub('Ticks x'); + menu.addchk(pad.fTickx === 0, 'normal', () => { + pad.fTickx = 0; + this.interactiveRedraw('pad', 'exec:SetTickx(0)'); + }); + menu.addchk(pad.fTickx === 1, 'ticks on both sides', () => { + pad.fTickx = 1; + this.interactiveRedraw('pad', 'exec:SetTickx(1)'); + }); + menu.addchk(pad.fTickx === 2, 'labels on both sides', () => { + pad.fTickx = 2; + this.interactiveRedraw('pad', 'exec:SetTickx(2)'); + }); + menu.endsub(); + menu.sub('Ticks y'); + menu.addchk(pad.fTicky === 0, 'normal', () => { + pad.fTicky = 0; + this.interactiveRedraw('pad', 'exec:SetTicky(0)'); + }); + menu.addchk(pad.fTicky === 1, 'ticks on both sides', () => { + pad.fTicky = 1; + this.interactiveRedraw('pad', 'exec:SetTicky(1)'); + }); + menu.addchk(pad.fTicky === 2, 'labels on both sides', () => { + pad.fTicky = 2; + this.interactiveRedraw('pad', 'exec:SetTicky(2)'); + }); + menu.endsub(); - const kinds = ['X1', 'X2', 'X3', 'X5', 'X10', 'Y1', 'Y2', 'Y3', 'Y5', 'Y10', 'XY1', 'XY2', 'XY3', 'XY5', 'XY10']; - if (kind) kinds.unshift('Off'); + menu.addchk(pad.fEditable, 'Editable', flag => { + pad.fEditable = flag; + this.interactiveRedraw('pad', `exec:SetEditable(${flag})`); + }); - menu.add('sub:Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); - for (let k = 0; k < kinds.length; ++k) - menu.addchk(kind === kinds[k], kinds[k], kinds[k], arg => this.toggleProjection(arg)); - menu.add('endsub:'); + if (this.isCanvas()) { + menu.addchk(pad.TestBit(kIsGrayscale), 'Gray scale', flag => { + this.setGrayscale(flag); + this.interactiveRedraw('pad', `exec:SetGrayscale(${flag})`); + }); } - if (!this.isTH2Poly()) - menu.add('Auto zoom-in', () => this.autoZoom()); + menu.sub('Border'); + menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], pad.fBorderMode + 1, v => { + pad.fBorderMode = v - 1; + this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); + }, 'Pad border mode'); + menu.addSizeMenu('Size', 0, 20, 2, pad.fBorderSize, v => { + pad.fBorderSize = v; + this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); + }, 'Pad border size'); + menu.endsub(); - const opts = this.getSupportedDrawOptions(); + menu.addAttributesMenu(this); - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); - this.decodeOptions(arg); - this.interactiveRedraw('pad', 'drawopt'); - }); + if (!this.isCanvas(true)) { + // if not online canvas - + const do_divide = arg => { + if (!arg || !isStr(arg)) + return; + // delete auto_canvas flag to prevent deletion + if (this.#iscan === 'auto') + this.#iscan = true; + this.cleanPrimitives(true); + if (arg === 'reset') + return; + const arr = arg.split('x'); + if (arr.length === 1) + this.divide(Number.parseInt(arr[0])); + else if (arr.length === 2) + this.divide(Number.parseInt(arr[0]), Number.parseInt(arr[1])); + }; - if (this.options.Color || this.options.Contour || this.options.Hist || this.options.Surf || this.options.Lego === 12 || this.options.Lego === 14) - this.fillPaletteMenu(menu, true); - } + if (isFunc(this.drawObject)) + menu.add('Build legend', () => this.buildLegend()); - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const res = super.clickButton(funcname); - if (res) return res; + menu.sub('Divide', () => menu.input('Input divide arg', '2x2').then(do_divide), 'Divide on sub-pads'); + ['1x2', '2x1', '2x2', '2x3', '3x2', '3x3', '4x4', 'reset'].forEach(item => menu.add(item, item, do_divide)); + menu.endsub(); - if (this.isMainPainter()) { - switch (funcname) { - case 'ToggleColor': return this.toggleColor(); - case 'ToggleColorZ': return this.toggleColz(); - case 'Toggle3D': return this.toggleMode3D(); + menu.add('Save to gStyle', () => { + this.fillatt?.saveToStyle(this.isCanvas() ? 'fCanvasColor' : 'fPadColor'); + gStyle.fPadGridX = pad.fGridx; + gStyle.fPadGridY = pad.fGridy; + gStyle.fPadTickX = pad.fTickx; + gStyle.fPadTickY = pad.fTicky; + gStyle.fOptLogx = pad.fLogx; + gStyle.fOptLogy = pad.fLogy; + gStyle.fOptLogz = pad.fLogz; + }, 'Store pad fill attributes, grid, tick and log scale settings to gStyle'); + + if (this.isCanvas()) { + menu.addSettingsMenu(false, false, arg => { + if (arg === 'dark') + this.changeDarkMode(); + }); } } - // all methods here should not be processed further - return false; + menu.separator(); + + if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) + menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); + + if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { + if (this.canStatusBar()) + menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); + } + + if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) + menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); + + const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`))); + if (this.isCanvas()) { + menu.separator(); + menu.add(`${fname}.json`, () => this.saveAs('json', true, `${fname}.json`), 'Produce JSON with line spacing'); + menu.add(`${fname}0.json`, () => this.saveAs('json', false, `${fname}0.json`), 'Produce JSON without line spacing'); + } + menu.endsub(); + + return true; } - /** @summary Fill pad toolbar with histogram-related functions */ - fillToolbar() { - super.fillToolbar(true); + /** @summary Show pad context menu + * @private */ + async padContextMenu(evnt) { + if (evnt.stopPropagation) { + // this is normal event processing and not emulated jsroot event + evnt.stopPropagation(); // disable main context menu + evnt.preventDefault(); // disable browser context menu + this.getFramePainter()?.setLastEventPos(); + } - const pp = this.getPadPainter(); - if (!pp) return; + return createMenu(evnt, this).then(menu => { + this.fillContextMenu(menu); + return this.fillObjectExecMenu(menu, ''); + }).then(menu => menu.show()); + } - if (!this.isTH2Poly() && !this.options.Axis) - pp.addPadButton('th2color', 'Toggle color', 'ToggleColor'); - if (!this.options.Axis) - pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); - pp.addPadButton('th2draw3d', 'Toggle 3D mode', 'Toggle3D'); - pp.showPadButtons(); + /** @summary Redraw TLegend object + * @desc Used when object attributes are changed to ensure that legend is up to date + * @private */ + async redrawLegend() { + return this.findPainterFor(null, '', clTLegend)?.redraw(); } - /** @summary Toggle color drawing mode */ - toggleColor() { - if (this.options.Mode3D) { - this.options.Mode3D = false; - this.options.Color = true; - } else { - this.options.Color = !this.options.Color; - this.options.Scat = !this.options.Color; + /** @summary Redraw pad means redraw ourself + * @return {Promise} when redrawing ready */ + async redrawPad(reason) { + const sync_promise = this.syncDraw(reason); + if (sync_promise === false) { + console.log(`Prevent redrawing of ${this.#pad.fName}`); + return false; } - this._can_move_colz = true; // indicate that next redraw can move Z scale + let showsubitems = true; + const redrawNext = indx => { + while (indx < this.#painters.length) { + const sub = this.#painters[indx++]; + let res = 0; + if (showsubitems || isPadPainter(sub)) + res = sub.redraw(reason); - this.copyOptionsToOthers(); + if (isPromise(res)) + return res.then(() => redrawNext(indx)); + } + return true; + }; - return this.interactiveRedraw('pad', 'drawopt'); + return sync_promise.then(() => { + if (this.isCanvas()) + this.createCanvasSvg(2); + else + showsubitems = this.createPadSvg(true); + return redrawNext(0); + }).then(() => { + this.addPadInteractive(); + this.confirmDraw(); + if (getActivePad() === this) + this.getCanvPainter()?.producePadEvent('padredraw', this); + return true; + }); } - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - if (this.isTH2Poly()) return; // not implemented + /** @summary redraw pad */ + redraw(reason) { + // intentionally do not return Promise to let re-draw sub-pads in parallel + this.redrawPad(reason); + } - const i1 = this.getSelectIndex('x', 'left', -1), - i2 = this.getSelectIndex('x', 'right', 1), - j1 = this.getSelectIndex('y', 'left', -1), - j2 = this.getSelectIndex('y', 'right', 1), - histo = this.getObject(); + /** @summary Checks if pad should be redrawn by resize + * @private */ + needRedrawByResize() { + const elem = this.getPadSvg(); + if (!elem.empty() && elem.property('can3d') === constants$1.Embed3D.Overlay) + return true; - if ((i1 === i2) || (j1 === j2)) return; + return this.#painters.findIndex(objp => { + return isFunc(objp.needRedrawByResize) ? objp.needRedrawByResize() : false; + }) >= 0; + } - // first find minimum - let min = histo.getBinContent(i1 + 1, j1 + 1); - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) - min = Math.min(min, histo.getBinContent(i + 1, j + 1)); - } - if (min > 0) return; // if all points positive, no chance for autoscale + /** @summary Check resize of canvas + * @return {Promise} with result or false */ + checkCanvasResize(size, force) { + if (this._ignore_resize || !this.isTopPad()) + return false; - let ileft = i2, iright = i1, jleft = j2, jright = j1; + const sync_promise = this.syncDraw('canvas_resize'); + if (sync_promise === false) + return false; - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) { - if (histo.getBinContent(i + 1, j + 1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - } - } + if ((size === true) || (size === false)) { + force = size; + size = null; } - let xmin, xmax, ymin, ymax, isany = false; + if (isObject(size) && size.force) + force = true; - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } + if (!force) + force = this.needRedrawByResize(); - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = histo.fXaxis.GetBinLowEdge(ileft+1); - xmax = histo.fXaxis.GetBinLowEdge(iright+1); - isany = true; - } + let changed = false; + const redrawNext = indx => { + if (!changed || (indx >= this.#painters.length)) { + this.confirmDraw(); + return changed; + } - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = histo.fYaxis.GetBinLowEdge(jleft+1); - ymax = histo.fYaxis.GetBinLowEdge(jright+1); - isany = true; - } + return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); + }; - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax); - } + // return sync_promise.then(() => this.ensureBrowserSize(this.#pad?.fCw, this.#pad?.fCh)).then(() => { - /** @summary Scan TH2 histogram content */ - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy) return; + return sync_promise.then(() => { + changed = this.createCanvasSvg(force ? 2 : 1, size); - const histo = this.getObject(); - let i, j; + if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { + if (this.#resize_tmout) + clearTimeout(this.#resize_tmout); + this.#resize_tmout = setTimeout(() => { + this.#resize_tmout = undefined; + if (isFunc(this.sendResized)) + this.sendResized(); + }, 1000); // long enough delay to prevent multiple occurrence + } - this.extractAxesProperties(2); + // if canvas changed, redraw all its subitems. + // If redrawing was forced for canvas, same applied for sub-elements + return redrawNext(0); + }); + } - if (this.isTH2Poly()) { - this.gminposbin = null; - this.gminbin = this.gmaxbin = 0; + /** @summary Update TPad object */ + updateObject(obj) { + const pad = this.getRootPad(); + if (!obj || !pad) + return false; - for (let n = 0, len = histo.fBins.arr.length; n < len; ++n) { - const bin_content = histo.fBins.arr[n].fContent; - if (n === 0) this.gminbin = this.gmaxbin = bin_content; + pad.fBits = obj.fBits; + pad.fTitle = obj.fTitle; + + pad.fGridx = obj.fGridx; + pad.fGridy = obj.fGridy; + pad.fTickx = obj.fTickx; + pad.fTicky = obj.fTicky; + pad.fLogx = obj.fLogx; + pad.fLogy = obj.fLogy; + pad.fLogz = obj.fLogz; + + pad.fUxmin = obj.fUxmin; + pad.fUxmax = obj.fUxmax; + pad.fUymin = obj.fUymin; + pad.fUymax = obj.fUymax; + + pad.fX1 = obj.fX1; + pad.fX2 = obj.fX2; + pad.fY1 = obj.fY1; + pad.fY2 = obj.fY2; + + // this is main coordinates for sub-pad relative to canvas + pad.fAbsWNDC = obj.fAbsWNDC; + pad.fAbsHNDC = obj.fAbsHNDC; + pad.fAbsXlowNDC = obj.fAbsXlowNDC; + pad.fAbsYlowNDC = obj.fAbsYlowNDC; + + pad.fLeftMargin = obj.fLeftMargin; + pad.fRightMargin = obj.fRightMargin; + pad.fBottomMargin = obj.fBottomMargin; + pad.fTopMargin = obj.fTopMargin; + + pad.fFillColor = obj.fFillColor; + pad.fFillStyle = obj.fFillStyle; + pad.fLineColor = obj.fLineColor; + pad.fLineStyle = obj.fLineStyle; + pad.fLineWidth = obj.fLineWidth; + + pad.fPhi = obj.fPhi; + pad.fTheta = obj.fTheta; + pad.fEditable = obj.fEditable; + + if (this.isCanvas()) + this.checkSpecialsInPrimitives(obj); - if (bin_content < this.gminbin) - this.gminbin = bin_content; - else if (bin_content > this.gmaxbin) - this.gmaxbin = bin_content; + const fp = this.getFramePainter(); + fp?.updateAttributes(!fp.$modifiedNDC); - if ((bin_content > 0) && ((this.gminposbin === null) || (this.gminposbin > bin_content))) - this.gminposbin = bin_content; - } - } else { - // global min/max, used at the moment in 3D drawing - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1); - this.gminposbin = null; - for (i = 0; i < this.nbinsx; ++i) { - for (j = 0; j < this.nbinsy; ++j) { - const bin_content = histo.getBinContent(i+1, j+1); - if (bin_content < this.gminbin) - this.gminbin = bin_content; - else if (bin_content > this.gmaxbin) - this.gmaxbin = bin_content; - if (bin_content > 0) { - if ((this.gminposbin === null) || (this.gminposbin > bin_content)) - this.gminposbin = bin_content; - } - } + if (!obj.fPrimitives) + return false; + + let isany = false, p = 0; + for (let n = 0; n < obj.fPrimitives.arr?.length; ++n) { + if (obj.fPrimitives.arr[n].$special) + continue; + while (p < this.#painters.length) { + const op = this.#painters[p++]; + if (!op._primitive) + continue; + if (op.updateObject(obj.fPrimitives.arr[n], obj.fPrimitives.opt[n])) + isany = true; + break; } } - // this value used for logz scale drawing - if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; + return isany; + } - const is_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); + /** @summary add legend object to the pad and redraw it + * @private */ + async buildLegend(x1, y1, x2, y2, title, opt) { + const lp = this.findPainterFor(null, '', clTLegend); - if (this.options.Axis > 0) { - // Paint histogram axis only - this.draw_content = false; - } else if (this.isTH2Poly()) { - this.draw_content = is_content || this.options.Line || this.options.Fill || this.options.Mark; - if (!this.draw_content && this.options.Zero) { - this.draw_content = true; - this.options.Line = 1; - } - } else - this.draw_content = is_content; - } + if (!lp && !isFunc(this.drawObject)) + return Promise.reject(Error('Not possible to build legend while module draw.mjs was not load')); - /** @summary Count TH2 histogram statistic - * @desc Optionally one could provide condition function to select special range */ - countStat(cond, count_skew) { - if (!isFunc(cond)) - cond = this.options.cutg ? (x, y) => this.options.cutg.IsInside(x, y) : null; + const leg = lp?.getObject() ?? create$1(clTLegend), + pad = this.getRootPad(true); - const histo = this.getHisto(), xaxis = histo.fXaxis, yaxis = histo.fYaxis, - fp = this.getFramePainter(), - funcs = fp.getGrFuncs(this.options.second_x, this.options.second_y), - res = { name: histo.fName, entries: 0, eff_entries: 0, integral: 0, - meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], - xmax: 0, ymax: 0, wmax: null, skewx: 0, skewy: 0, skewd: 0, kurtx: 0, kurty: 0, kurtd: 0 }, - has_counted_stat = !fp.isAxisZoomed('x') && !fp.isAxisZoomed('y') && (Math.abs(histo.fTsumw) > 1e-300) && !cond; - let stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumx2 = 0, stat_sumy2 = 0, - xside, yside, xx, yy, zz, xleft, xright, yleft, yright; + leg.fPrimitives.Clear(); - if (this.isTH2Poly()) { - const len = histo.fBins.arr.length; - let i, bin, n, gr, ngr, numgraphs, numpoints; + for (let k = 0; k < this.#painters.length; ++k) { + const painter = this.#painters[k], + obj = painter.getObject(); + if (!obj || obj.fName === kTitle || obj.fName === 'stats' || painter.draw_content === false || + obj._typename === clTLegend || obj._typename === clTHStack || obj._typename === clTMultiGraph) + continue; - for (i = 0; i < len; ++i) { - bin = histo.fBins.arr[i]; + const entry = create$1(clTLegendEntry); + entry.fObject = obj; + entry.fLabel = painter.getItemName(); + if ((opt === 'all') || !entry.fLabel) + entry.fLabel = obj.fName; + entry.fOption = ''; + if (!entry.fLabel) + continue; - xside = (bin.fXmin > funcs.scale_xmax) ? 2 : (bin.fXmax < funcs.scale_xmin ? 0 : 1); - yside = (bin.fYmin > funcs.scale_ymax) ? 2 : (bin.fYmax < funcs.scale_ymin ? 0 : 1); + if (painter.lineatt?.used) + entry.fOption += 'l'; + if (painter.fillatt?.used) + entry.fOption += 'f'; + if (painter.markeratt?.used) + entry.fOption += 'p'; + if (!entry.fOption) + entry.fOption = 'l'; - xx = yy = numpoints = 0; - gr = bin.fPoly; numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } + leg.fPrimitives.Add(entry); + } - for (ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if (lp) + return lp.redraw(); - for (n = 0; n < gr.fNpoints; ++n) { - ++numpoints; - xx += gr.fX[n]; - yy += gr.fY[n]; + const szx = 0.4; + let szy = leg.fPrimitives.arr.length; + // no entries - no need to draw legend + if (!szy) + return null; + if (szy > 8) + szy = 8; + szy *= 0.1; + + if ((x1 === x2) || (y1 === y2)) { + leg.fX1NDC = szx * pad.fLeftMargin + (1 - szx) * (1 - pad.fRightMargin); + leg.fY1NDC = (1 - szy) * (1 - pad.fTopMargin) + szy * pad.fBottomMargin; + leg.fX2NDC = 0.99 - pad.fRightMargin; + leg.fY2NDC = 0.99 - pad.fTopMargin; + if (opt === undefined) + opt = 'autoplace'; + } else { + leg.fX1NDC = x1; + leg.fY1NDC = y1; + leg.fX2NDC = x2; + leg.fY2NDC = y2; + } + leg.fFillStyle = 1001; + leg.fTitle = title ?? ''; + + return this.drawObject(this, leg, opt); + } + + /** @summary Add object painter to list of primitives + * @private */ + addObjectPainter(objpainter, lst, indx) { + if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { + // keep snap id in painter, will be used for the + if (this.#painters.indexOf(objpainter) < 0) + this.#painters.push(objpainter); + + objpainter.assignSnapId(lst[indx].fObjectID); + const setSubSnaps = p => { + if (!p.isPrimary()) + return; + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (sub.isSecondary(p) && sub.getSecondaryId()) { + sub.assignSnapId(p.getSnapId() + '#' + sub.getSecondaryId()); + setSubSnaps(sub); } } + }; - if (numpoints > 1) { - xx = xx / numpoints; - yy = yy / numpoints; - } + setSubSnaps(objpainter); + } + } - zz = bin.fContent; + /** @summary Process snap with style + * @private */ + processSnapStyle(snap) { + Object.assign(gStyle, snap.fSnapshot); + } - res.entries += zz; + /** @summary Process snap with colors + * @private */ + processSnapColors(snap) { + const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper), + o = this.getOptions(true); - res.matrix[yside * 3 + xside] += zz; + // set global list of colors + if (!o || o.GlobalColors) + adoptRootColors(ListOfColors); - if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) continue; + const greyscale = this.#pad?.TestBit(kIsGrayscale) ?? false, + colors = extendRootColors(null, ListOfColors, greyscale); - if ((res.wmax === null) || (zz > res.wmax)) { - res.wmax = zz; - res.xmax = xx; - res.ymax = yy; - } + // copy existing colors and extend with new values + this.#custom_colors = o?.LocalColors ? colors : null; - if (!has_counted_stat) { - stat_sum0 += zz; - stat_sumw2 += zz * zz; - stat_sumx1 += xx * zz; - stat_sumy1 += yy * zz; - stat_sumx2 += xx * xx * zz; - stat_sumy2 += yy * yy * zz; - } + // set palette + if (snap.fSnapshot.fBuf && (!o || !o.IgnorePalette)) { + const indexes = [], palette = []; + for (let n = 0; n < snap.fSnapshot.fBuf.length; ++n) { + indexes[n] = Math.round(snap.fSnapshot.fBuf[n]); + palette[n] = colors[indexes[n]]; } - } else { - xleft = this.getSelectIndex('x', 'left'); - xright = this.getSelectIndex('x', 'right'); - yleft = this.getSelectIndex('y', 'left'); - yright = this.getSelectIndex('y', 'right'); + this.#custom_palette_indexes = indexes; + this.#custom_palette_colors = palette; + this.#custom_palette = new ColorPalette(palette, greyscale); + } else + this.#custom_palette = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; + } - for (let xi = 0; xi <= this.nbinsx + 1; ++xi) { - xside = (xi <= xleft) ? 0 : (xi > xright ? 2 : 1); - xx = xaxis.GetBinCoord(xi - 0.5); + /** @summary Process snap with custom font + * @private */ + processSnapFont(snap) { + const arr = snap.fSnapshot.fOper.split(':'); + addCustomFont(Number.parseInt(arr[0]), arr[1], arr[2], arr[3]); + } - for (let yi = 0; yi <= this.nbinsy + 1; ++yi) { - yside = (yi <= yleft) ? 0 : (yi > yright ? 2 : 1); - yy = yaxis.GetBinCoord(yi - 0.5); + /** @summary Process special snaps like colors or style objects + * @return {Promise} index where processing should start + * @private */ + processSpecialSnaps(lst) { + while (lst?.length) { + const snap = lst[0]; - zz = histo.getBinContent(xi, yi); + // gStyle object + if (snap.fKind === webSnapIds.kStyle) { + lst.shift(); + this.processSnapStyle(snap); + } else if (snap.fKind === webSnapIds.kColors) { + lst.shift(); + this.processSnapColors(snap); + } else if (snap.fKind === webSnapIds.kFont) { + lst.shift(); + this.processSnapFont(snap); + } else + break; + } + } - res.entries += zz; + /** @summary Function called when drawing next snapshot from the list + * @return {Promise} for drawing of the snap + * @private */ + async drawNextSnap(lst, pindx, indx) { + if (indx === undefined) { + indx = -1; + this.#num_primitives = lst?.length ?? 0; + } - res.matrix[yside * 3 + xside] += zz; + ++indx; // change to the next snap - if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) continue; + if (!lst || (indx >= lst.length)) + return this; - if ((res.wmax === null) || (zz > res.wmax)) { - res.wmax = zz; - res.xmax = xx; - res.ymax = yy; - } + const snap = lst[indx], is_subpad = (snap.fKind === webSnapIds.kSubPad); - if (!has_counted_stat) { - stat_sum0 += zz; - stat_sumw2 += zz * zz; - stat_sumx1 += xx * zz; - stat_sumy1 += yy * zz; - stat_sumx2 += xx**2 * zz; - stat_sumy2 += yy**2 * zz; - // stat_sumxy += xx * yy * zz; - } - } - } + // gStyle object + if (snap.fKind === webSnapIds.kStyle) { + this.processSnapStyle(snap); + return this.drawNextSnap(lst, pindx, indx); // call next } - if (has_counted_stat) { - stat_sum0 = histo.fTsumw; - stat_sumw2 = histo.fTsumw2; - stat_sumx1 = histo.fTsumwx; - stat_sumx2 = histo.fTsumwx2; - stat_sumy1 = histo.fTsumwy; - stat_sumy2 = histo.fTsumwy2; - // stat_sumxy = histo.fTsumwxy; + // list of colors + if (snap.fKind === webSnapIds.kColors) { + this.processSnapColors(snap); + return this.drawNextSnap(lst, pindx, indx); // call next } - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); + // try to locate existing object painter, only allowed when redrawing pad snap + let objpainter, promise; + + while ((pindx !== undefined) && (pindx < this.#painters.length)) { + const subp = this.#painters[pindx++]; + if (subp.getSnapId() === snap.fObjectID) { + objpainter = subp; + break; + } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { + console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fSnapshot?._typename}`); + break; + } } - if (res.wmax === null) - res.wmax = 0; - res.integral = stat_sum0; + if (objpainter) { + // painter exists - try to update drawing + if (is_subpad) + promise = objpainter.redrawPadSnap(snap); + else if (snap.fKind === webSnapIds.kObject) { // object itself + if (objpainter.updateObject(snap.fSnapshot, snap.fOption, true)) + promise = objpainter.redraw(); + } else if (snap.fKind === webSnapIds.kSVG) { // update SVG + if (objpainter.updateObject(snap.fSnapshot)) + promise = objpainter.redraw(); + } + } else if (is_subpad) { + const subpad = snap.fSnapshot; - if (histo.fEntries > 1) - res.entries = histo.fEntries; + subpad.fPrimitives = null; // clear primitives, they just because of I/O - res.eff_entries = stat_sumw2 ? stat_sum0*stat_sum0/stat_sumw2 : Math.abs(stat_sum0); + const padpainter = new TPadPainter(this, subpad, snap.fOption, false, 'webpad'); + padpainter.assignSnapId(snap.fObjectID); + padpainter.is_active_pad = Boolean(snap.fActive); // enforce boolean flag + padpainter.#readonly = snap.fReadOnly ?? false; // readonly flag + padpainter.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive + padpainter.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - if (count_skew && !this.isTH2Poly()) { - let sumx3 = 0, sumy3 = 0, sumx4 = 0, sumy4 = 0, np = 0, w = 0; - for (let xi = xleft; xi < xright; ++xi) { - xx = xaxis.GetBinCoord(xi + 0.5); - for (let yi = yleft; yi < yright; ++yi) { - yy = yaxis.GetBinCoord(yi + 0.5); - if (cond && !cond(xx, yy)) continue; - w = histo.getBinContent(xi + 1, yi + 1); - np += w; - sumx3 += w * Math.pow(xx - res.meanx, 3); - sumy3 += w * Math.pow(yy - res.meany, 3); - sumx4 += w * Math.pow(xx - res.meanx, 4); - sumy4 += w * Math.pow(yy - res.meany, 4); - } - } + padpainter.processSpecialSnaps(snap.fPrimitives); // need to process style and colors before creating graph elements - const stddev3x = Math.pow(res.rmsx, 3), - stddev3y = Math.pow(res.rmsy, 3), - stddev4x = Math.pow(res.rmsx, 4), - stddev4y = Math.pow(res.rmsy, 4); - if (np * stddev3x !== 0) - res.skewx = sumx3 / (np * stddev3x); - if (np * stddev3y !== 0) - res.skewy = sumy3 / (np * stddev3y); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; - if (np * stddev4x !== 0) - res.kurtx = sumx4 / (np * stddev4x) - 3; - if (np * stddev4y !== 0) - res.kurty = sumy4 / (np * stddev4y) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; + padpainter.createPadSvg(); + + if (padpainter.matchObjectType(clTPad) && snap.fPrimitives.length) + padpainter.addPadButtons(true); + pindx++; // new painter will be add + promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); + } else if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { + // here the case of normal drawing + pindx++; // new painter will be add + promise = this.drawObject(this, snap.fSnapshot, snap.fOption).then(objp => this.addObjectPainter(objp, lst, indx)); } - return res; + return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next } - /** @summary Fill TH2 statistic in stat box */ - fillStatistic(stat, dostat, dofit) { - // no need to refill statistic if histogram is dummy - if (this.isIgnoreStatsFill()) return false; - - if (dostat === 1) dostat = 1111; - - const print_name = Math.floor(dostat % 10), - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10, - data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); + /** @summary Return painter with specified id + * @private */ + findSnap(snapid) { + if (this.getSnapId() === snapid) + return this; - stat.clearPave(); + if (!this.#painters) + return null; - if (print_name > 0) - stat.addText(data.name); + for (let k = 0; k < this.#painters.length; ++k) { + let sub = this.#painters[k]; - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (isFunc(sub.findSnap)) + sub = sub.findSnap(snapid); + else if (sub.getSnapId() !== snapid) + sub = null; - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); + if (sub) + return sub; } - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - } + return null; + } - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.matrix[4], 'entries')); + /** @summary Redraw pad snap + * @desc Online version of drawing pad primitives + * for the canvas snapshot contains list of objects + * as first entry, graphical properties of canvas itself is provided + * in ROOT6 it also includes primitives, but we ignore them + * @return {Promise} with pad painter when drawing completed + * @private */ + async redrawPadSnap(snap) { + if (!snap?.fPrimitives) + return this; - if (print_skew === 2) { - stat.addText(`Skewness x = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); - stat.addText(`Skewness y = ${stat.format(data.skewy)} #pm ${stat.format(data.skewd)}`); - } else if (print_skew > 0) { - stat.addText(`Skewness x = ${stat.format(data.skewx)}`); - stat.addText(`Skewness y = ${stat.format(data.skewy)}`); - } + this.is_active_pad = Boolean(snap.fActive); // enforce boolean flag + this.#readonly = snap.fReadOnly ?? false; // readonly flag + this.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive + this.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - if (print_kurt === 2) { - stat.addText(`Kurtosis x = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); - stat.addText(`Kurtosis y = ${stat.format(data.kurty)} #pm ${stat.format(data.kurtd)}`); - } else if (print_kurt > 0) { - stat.addText(`Kurtosis x = ${stat.format(data.kurtx)}`); - stat.addText(`Kurtosis y = ${stat.format(data.kurty)}`); - } + const first = snap.fSnapshot; + first.fPrimitives = null; // primitives are not interesting, they are disabled in IO - if ((print_under > 0) || (print_over > 0)) { - const get = i => data.matrix[i].toFixed(0); + // if there are execs in the pad, deliver events to the server + this.#deliver_move_events = this.#has_execs || first.fExecs?.arr?.length; - stat.addText(`${get(6)} | ${get(7)} | ${get(7)}`); - stat.addText(`${get(3)} | ${get(4)} | ${get(5)}`); - stat.addText(`${get(0)} | ${get(1)} | ${get(2)}`); - } + if (!this.hasSnapId()) { + // first time getting snap, create all gui elements first - if (dofit) stat.fillFunctionStat(this.findFunction(clTF2), dofit, 2); + this.assignSnapId(snap.fObjectID); - return true; - } + this.assignObject(first); + this.#pad = first; // first object is pad - /** @summary Draw TH2 bins as colors */ - drawBinsColor() { - const histo = this.getHisto(), - handle = this.prepareDraw(), - cntr = this.getContour(), - palette = this.getHistPalette(), - entries = [], - show_empty = this._show_empty_bins, - can_merge_x = (handle.xbar2 === 1) && (handle.xbar1 === 0), - can_merge_y = (handle.ybar2 === 1) && (handle.ybar1 === 0); + // this._setFixedSize(true); - let dx, dy, x1, y2, binz, is_zero, colindx, last_entry = null, - skip_zero = !this.options.Zero; + // if canvas size not specified in batch mode, temporary use 900x700 size + if (this.isBatchMode() && (!first.fCw || !first.fCh)) { + first.fCw = 900; + first.fCh = 700; + } - const test_cutg = this.options.cutg, - flush_last_entry = () => { - last_entry.path += `h${dx}v${last_entry.y1-last_entry.y2}h${-dx}z`; - last_entry = null; - }; + // case of ROOT7 with always dummy TPad as first entry + if (!first.fCw || !first.fCh) + this._setFixedSize(false); - // check in the beginning if zero can be skipped - if (!skip_zero && !show_empty && (cntr.getPaletteIndex(palette, 0) === null)) skip_zero = true; + const mainid = this.selectDom().attr('id'); - // now start build - for (let i = handle.i1; i < handle.i2; ++i) { - dx = (handle.grx[i+1] - handle.grx[i]) || 1; - if (can_merge_x) - x1 = handle.grx[i]; - else { - x1 = Math.round(handle.grx[i] + dx*handle.xbar1); - dx = Math.round(dx*(handle.xbar2 - handle.xbar1)) || 1; + if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { + this.brlayout = new BrowserLayout(mainid, null, this); + this.brlayout.create(mainid, true); + this.setDom(this.brlayout.drawing_divid()); // need to create canvas + registerForResize(this.brlayout); } - for (let j = handle.j2 - 1; j >= handle.j1; --j) { - binz = histo.getBinContent(i + 1, j + 1); - is_zero = (binz === 0); + this.processSpecialSnaps(snap.fPrimitives); - if ((is_zero && skip_zero) || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) { - if (last_entry) flush_last_entry(); - continue; - } + this.createCanvasSvg(0); - colindx = cntr.getPaletteIndex(palette, binz); - if (colindx === null) { - if (is_zero && show_empty) - colindx = 0; - else { - if (last_entry) flush_last_entry(); - continue; - } - } + if (!this.isBatchMode()) + this.addPadButtons(true); - dy = (handle.gry[j] - handle.gry[j+1]) || 1; - if (can_merge_y) - y2 = handle.gry[j+1]; - else { - y2 = Math.round(handle.gry[j] - dy*handle.ybar2); - dy = Math.round(dy*(handle.ybar2 - handle.ybar1)) || 1; - } + if (typeof snap.fHighlightConnect !== 'undefined') + this._highlight_connect = snap.fHighlightConnect; - const cmd1 = `M${x1},${y2}`; - let entry = entries[colindx]; - if (!entry) - entry = entries[colindx] = { path: cmd1 }; - else if (can_merge_y && (entry === last_entry)) { - entry.y1 = y2 + dy; - continue; - } else { - const ddx = x1 - entry.x1, ddy = y2 - entry.y2; - if (ddx || ddy) { - const cmd2 = `m${ddx},${ddy}`; - entry.path += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - } - } - if (last_entry) flush_last_entry(); + let pr = Promise.resolve(true); - entry.x1 = x1; - entry.y2 = y2; + if (isStr(snap.fScripts) && snap.fScripts) { + let src = '', m = null; - if (can_merge_y) { - entry.y1 = y2 + dy; - last_entry = entry; - } else - entry.path += `h${dx}v${dy}h${-dx}z`; + if (snap.fScripts.indexOf('modules:') === 0) + m = snap.fScripts.slice(8).split(';'); + else if (snap.fScripts.indexOf('load:') === 0) + src = snap.fScripts.slice(5).split(';'); + else if (snap.fScripts.indexOf('assert:') === 0) + src = snap.fScripts.slice(7); + + pr = (m !== null) ? loadModules(m) : (src ? loadScript(src) : injectCode(snap.fScripts)); } - if (last_entry) flush_last_entry(); + + return pr.then(() => this.drawNextSnap(snap.fPrimitives)).then(() => { + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); + return this; + }); } - entries.forEach((entry, colindx) => { - if (entry) { - this.draw_g - .append('svg:path') - .attr('fill', palette.getColor(colindx)) - .attr('d', entry.path); - } - }); + this.updateObject(first); // update only object attributes - return handle; - } + // apply all changes in the object (pad or canvas) + if (this.isCanvas()) + this.createCanvasSvg(2); + else + this.createPadSvg(true); - /** @summary Draw histogram bins with projection function */ - drawBinsProjected() { - const handle = this.prepareDraw({ rounding: false, nozoom: true, extra: 100, original: true }), - main = this.getFramePainter(), - funcs = main.getGrFuncs(this.options.second_x, this.options.second_y), - ilevels = this.getContourLevels(), - palette = this.getHistPalette(), - func = main.getProjectionFunc(); + let missmatch = false; - handle.grz = z => z; - handle.grz_min = ilevels[0]; - handle.grz_max = ilevels[ilevels.length - 1]; + // match painters with new list of primitives + if (!snap.fWithoutPrimitives) { + let i = 0, k = 0; + while (k < this.#painters.length) { + const sub = this.#painters[k]; + + // skip check secondary painters or painters without snapid + if (!sub.hasSnapId() || sub.isSecondary()) { + k++; + continue; // look only for painters with snapid + } - buildSurf3D(this.getHisto(), handle, ilevels, (lvl, pos) => { - let dd = '', lastx, lasty; + if (i >= snap.fPrimitives.length) + break; - for (let i = 0; i < pos.length; i += 3) { - const pnt = func(pos[i], pos[i + 1]), - x = Math.round(funcs.grx(pnt.x)), - y = Math.round(funcs.gry(pnt.y)); + const prim = snap.fPrimitives[i]; - if (i === 0) - dd = `M${x},${y}`; - else { - if ((x === lastx) && (y === lasty)) - continue; - if (i % 9 === 0) - dd += `m${x-lastx},${y-lasty}`; - else if (y === lasty) - dd += `h${x-lastx}`; - else if (x === lastx) - dd += `v${y-lasty}`; - else - dd += `l${x-lastx},${y-lasty}`; + // only real objects drawing checked for existing painters + if ((prim.fKind !== webSnapIds.kSubPad) && (prim.fKind !== webSnapIds.kObject) && (prim.fKind !== webSnapIds.kSVG)) { + i++; + continue; // look only for primitives of real objects } - lastx = x; lasty = y; + if (prim.fObjectID === sub.getSnapId()) { + i++; + k++; + } else { + missmatch = true; + break; + } } - this.draw_g - .append('svg:path') - .attr('d', dd) - .style('fill', palette.calcColor(lvl, ilevels.length)); + let cnt = 1000; + // remove painters without primitives, limit number of checks + while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { + if (this.removePrimitive(k) === -111) + missmatch = true; + } + if (cnt < 0) + missmatch = true; + } + + if (missmatch) { + const old_painters = this.#painters; + this.#painters = []; + old_painters.forEach(objp => objp.cleanup()); + this.setMainPainter(undefined, true); + if (isFunc(this.removePadButtons)) + this.removePadButtons(); + this.addPadButtons(true); + } + + return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { + this.addPadInteractive(); + if (getActivePad() === this) + this.getCanvPainter()?.producePadEvent('padredraw', this); + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); + return this; }); + } - return handle; + /** @summary Deliver mouse move or click event to the web canvas + * @private */ + deliverWebCanvasEvent(kind, x, y, snapid) { + if (!this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) + return; + if ((kind === 'move') && !this.#deliver_move_events) + return; + const cp = this.getCanvPainter(); + if (!cp || !cp.canSendWebsocket(2) || cp.isReadonly()) + return; + + const msg = JSON.stringify([this.getSnapId(), kind, x.toString(), y.toString(), snapid || '']); + cp.sendWebsocket(`EVENT:${msg}`); } - /** @summary Draw histogram bins as contour */ - drawBinsContour() { - const handle = this.prepareDraw({ rounding: false, extra: 100 }), - main = this.getFramePainter(), - frame_w = main.getFrameWidth(), - frame_h = main.getFrameHeight(), - levels = this.getContourLevels(), - palette = this.getHistPalette(), + /** @summary Create image for the pad + * @desc Used with web-based canvas to create images for server side + * @return {Promise} with image data, coded with btoa() function + * @private */ + async createImage(format) { + if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { + return this.produceImage(true, format).then(res => { + if (!res || (format === 'svg')) + return res; + const separ = res.indexOf('base64,'); + return (separ > 0) ? res.slice(separ + 7) : ''; + }); + } + return ''; + } - get_segm_intersection = (segm1, segm2) => { - const s10_x = segm1.x2 - segm1.x1, - s10_y = segm1.y2 - segm1.y1, - s32_x = segm2.x2 - segm2.x1, - s32_y = segm2.y2 - segm2.y1, - denom = s10_x * s32_y - s32_x * s10_y; - - if (denom === 0) - return 0; // Collinear - const denomPositive = denom > 0, - s02_x = segm1.x1 - segm2.x1, - s02_y = segm1.y1 - segm2.y1, - s_numer = s10_x * s02_y - s10_y * s02_x; - if ((s_numer < 0) === denomPositive) - return null; // No collision - - const t_numer = s32_x * s02_y - s32_y * s02_x; - if ((t_numer < 0) === denomPositive) - return null; // No collision - - if (((s_numer > denom) === denomPositive) || ((t_numer > denom) === denomPositive)) - return null; // No collision - // Collision detected - const t = t_numer / denom; - return { x: Math.round(segm1.x1 + (t * s10_x)), y: Math.round(segm1.y1 + (t * s10_y)) }; - }, buildPath = (xp, yp, iminus, iplus, do_close, check_rapair) => { - let cmd = '', lastx, lasty, x0, y0, isany = false, matched, x, y; - for (let i = iminus; i <= iplus; ++i) { - x = Math.round(xp[i]); - y = Math.round(yp[i]); - if (!cmd) { - cmd = `M${x},${y}`; x0 = x; y0 = y; - } else if ((i === iplus) && (iminus !== iplus) && (x === x0) && (y === y0)) { - if (!isany) return ''; // all same points - cmd += 'z'; do_close = false; matched = true; - } else { - const dx = x - lastx, dy = y - lasty; - if (dx) { - isany = true; - cmd += dy ? `l${dx},${dy}` : `h${dx}`; - } else if (dy) { - isany = true; - cmd += `v${dy}`; - } - } + /** @summary Collects pad information for TWebCanvas + * @desc need to update different states + * @private */ + getWebPadOptions(arg, cp) { + let is_top = (arg === undefined), elem = null, scan_subpads = true; + // no any options need to be collected in readonly mode + if (is_top && this.isReadonly()) + return ''; + if (arg === 'only_this') { + is_top = true; + scan_subpads = false; + } else if (arg === 'with_subpads') { + is_top = true; + scan_subpads = true; + } + if (is_top) + arg = []; + if (!cp) + cp = this.isCanvas() ? this : this.getCanvPainter(); + + if (this.getSnapId()) { + elem = { + _typename: 'TWebPadOptions', snapid: this.getSnapId(), + active: Boolean(this.is_active_pad), + cw: 0, ch: 0, w: [], + bits: 0, primitives: [], + logx: this.#pad.fLogx, logy: this.#pad.fLogy, logz: this.#pad.fLogz, + gridx: this.#pad.fGridx, gridy: this.#pad.fGridy, + tickx: this.#pad.fTickx, ticky: this.#pad.fTicky, + mleft: this.#pad.fLeftMargin, mright: this.#pad.fRightMargin, + mtop: this.#pad.fTopMargin, mbottom: this.#pad.fBottomMargin, + xlow: 0, ylow: 0, xup: 1, yup: 1, + zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0, phi: 0, theta: 0 + }; - lastx = x; lasty = y; + if (this.isCanvas()) { + elem.bits = this.getStatusBits(); + elem.cw = this.getPadWidth(); + elem.ch = this.getPadHeight(); + elem.w = [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]; + } else if (cp) { + const cw = cp.getPadWidth(), ch = cp.getPadHeight(), rect = this.getPadRect(); + elem.cw = cw; + elem.ch = ch; + elem.xlow = rect.x / cw; + elem.ylow = 1 - (rect.y + rect.height) / ch; + elem.xup = elem.xlow + rect.width / cw; + elem.yup = elem.ylow + rect.height / ch; } - if (!do_close || matched || !check_rapair) - return do_close ? cmd + 'z' : cmd; + if ((this.#pad.fTheta !== 30) || (this.#pad.fPhi !== 30)) { + elem.phi = this.#pad.fPhi; + elem.theta = this.#pad.fTheta; + } - // try to build path which fills area to outside borders + if (this.getPadRanges(elem)) + arg.push(elem); + else + console.log(`fail to get ranges for pad ${this.#pad.fName}`); + } - const points = [{ x: 0, y: 0 }, { x: frame_w, y: 0 }, { x: frame_w, y: frame_h }, { x: 0, y: frame_h }], + this.#painters.forEach(sub => { + if (isFunc(sub.getWebPadOptions)) { + if (scan_subpads) + sub.getWebPadOptions(arg, cp); + } else { + const opt = createWebObjectOptions(sub); + if (opt) + elem.primitives.push(opt); + } + }); - get_intersect = (i, di) => { - const segm = { x1: xp[i], y1: yp[i], x2: 2*xp[i] - xp[i+di], y2: 2*yp[i] - yp[i+di] }; - for (let i = 0; i < 4; ++i) { - const res = get_segm_intersection(segm, { x1: points[i].x, y1: points[i].y, x2: points[(i+1)%4].x, y2: points[(i+1)%4].y }); - if (res) { - res.indx = i + 0.5; - return res; - } - } - return null; - }; + if (is_top) + return toJSON(arg); + } - let pnt1, pnt2; - iminus--; - while ((iminus < iplus - 1) && !pnt1) - pnt1 = get_intersect(++iminus, 1); - if (!pnt1) return ''; - iplus++; - while ((iminus < iplus - 1) && !pnt2) - pnt2 = get_intersect(--iplus, -1); - if (!pnt2) return ''; + /** @summary returns actual ranges in the pad, which can be applied to the server + * @private */ + getPadRanges(r) { + if (!r) + return false; - // TODO: now side is always same direction, could be that side should be checked more precise + const fp = this.getFramePainter(), + p = this.getPadSvg(); - let dd = buildPath(xp, yp, iminus, iplus), - indx = pnt2.indx; - const side = 1, step = side*0.5; + r.ranges = fp?.ranges_set ?? false; // indicate that ranges are assigned - dd += `L${pnt2.x},${pnt2.y}`; + r.ux1 = r.px1 = r.ranges ? fp.scale_xmin : 0; // need to initialize for JSON reader + r.uy1 = r.py1 = r.ranges ? fp.scale_ymin : 0; + r.ux2 = r.px2 = r.ranges ? fp.scale_xmax : 0; + r.uy2 = r.py2 = r.ranges ? fp.scale_ymax : 0; + r.uz1 = r.ranges ? (fp.scale_zmin ?? 0) : 0; + r.uz2 = r.ranges ? (fp.scale_zmax ?? 0) : 0; - while (Math.abs(indx - pnt1.indx) > 0.1) { - indx = Math.round(indx + step) % 4; - dd += `L${points[indx].x},${points[indx].y}`; - indx += step; + if (fp) { + if (fp.zoom_xmin !== fp.zoom_xmax) { + r.zx1 = fp.zoom_xmin; + r.zx2 = fp.zoom_xmax; } - return dd + `L${pnt1.x},${pnt1.y}z`; - }; - - if (this.options.Contour === 14) { - this.draw_g - .append('svg:path') - .attr('d', `M0,0h${frame_w}v${frame_h}h${-frame_w}z`) - .style('fill', palette.calcColor(0, levels.length)); - } - - buildHist2dContour(this.getHisto(), handle, levels, palette, (colindx, xp, yp, iminus, iplus, ipoly) => { - const icol = palette.getColor(colindx); - let fillcolor = icol, lineatt; - switch (this.options.Contour) { - case 1: break; - case 11: fillcolor = 'none'; lineatt = this.createAttLine({ color: icol, std: false }); break; - case 12: fillcolor = 'none'; lineatt = this.createAttLine({ color: 1, style: (ipoly%5 + 1), width: 1, std: false }); break; - case 13: fillcolor = 'none'; lineatt = this.lineatt; break; + if (fp.zoom_ymin !== fp.zoom_ymax) { + r.zy1 = fp.zoom_ymin; + r.zy2 = fp.zoom_ymax; } - const dd = buildPath(xp, yp, iminus, iplus, fillcolor !== 'none', true); - if (!dd) return; - - const elem = this.draw_g - .append('svg:path') - .attr('d', dd) - .style('fill', fillcolor); + if (fp.zoom_zmin !== fp.zoom_zmax) { + r.zz1 = fp.zoom_zmin; + r.zz2 = fp.zoom_zmax; + } + } - if (lineatt) - elem.call(lineatt.func); - }); + if (!r.ranges || p.empty()) + return true; - handle.hide_only_zeros = true; // text drawing suppress only zeros + // calculate user range for full pad + const func = (log, value, err) => { + if (!log) + return value; + if (value <= 0) + return err; + value = Math.log10(value); + if (log > 1) + value /= Math.log10(log); + return value; + }, frect = fp.getFrameRect(); - return handle; - } + r.ux1 = func(fp.logx, r.ux1, 0); + r.ux2 = func(fp.logx, r.ux2, 1); - getGrNPoints(gr) { - const x = gr.fX, y = gr.fY; - let npnts = gr.fNpoints; - if ((npnts > 2) && (x[0] === x[npnts-1]) && (y[0] === y[npnts-1])) - npnts--; - return npnts; - } + let k = (r.ux2 - r.ux1) / (frect.width || 10); + r.px1 = r.ux1 - k * frect.x; + r.px2 = r.px1 + k * this.getPadWidth(); - /** @summary Create single graph path from TH2PolyBin */ - createPolyGr(funcs, gr, textbin) { - let grcmd = '', acc_x = 0, acc_y = 0; + r.uy1 = func(fp.logy, r.uy1, 0); + r.uy2 = func(fp.logy, r.uy2, 1); - const x = gr.fX, y = gr.fY, - flush = () => { - if (acc_x) { grcmd += 'h' + acc_x; acc_x = 0; } - if (acc_y) { grcmd += 'v' + acc_y; acc_y = 0; } - }, addPoint = (x1, y1, x2, y2) => { - const len = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2); - textbin.sumx += (x1 + x2) * len / 2; - textbin.sumy += (y1 + y2) * len / 2; - textbin.sum += len; - }, npnts = this.getGrNPoints(gr); + k = (r.uy2 - r.uy1) / (frect.height || 10); + r.py1 = r.uy1 - k * frect.y; + r.py2 = r.py1 + k * this.getPadHeight(); - if (npnts < 2) - return ''; + return true; + } - const grx0 = Math.round(funcs.grx(x[0])), - gry0 = Math.round(funcs.gry(y[0])); - let grx = grx0, gry = gry0; + /** @summary Show context menu for specified item + * @private */ + itemContextMenu(name) { + const rrr = this.getPadSvg().node().getBoundingClientRect(), + evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - for (let n = 1; n < npnts; ++n) { - const nextx = Math.round(funcs.grx(x[n])), - nexty = Math.round(funcs.gry(y[n])), - dx = nextx - grx, - dy = nexty - gry; + // use timeout to avoid conflict with mouse click and automatic menu close + if (name === 'pad') + return postponePromise(() => this.padContextMenu(evnt), 50); - if (textbin) addPoint(grx, gry, nextx, nexty); - if (dx || dy) { - if (dx === 0) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) flush(); - acc_y += dy; - } else if (dy === 0) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) flush(); - acc_x += dx; - } else { - flush(); - grcmd += `l${dx},${dy}`; - } + let selp = null, selkind; - grx = nextx; gry = nexty; + switch (name) { + case 'xaxis': + case 'yaxis': + case 'zaxis': + selp = this.getFramePainter(); + selkind = name[0]; + break; + case 'frame': + selp = this.getFramePainter(); + break; + default: { + const indx = parseInt(name); + if (Number.isInteger(indx)) + selp = this.#painters[indx]; } } - if (textbin) addPoint(grx, gry, grx0, gry0); - flush(); + if (!isFunc(selp?.fillContextMenu)) + return; - return grcmd ? `M${grx0},${gry0}` + grcmd + 'z' : ''; + return createMenu(evnt, selp).then(menu => { + const offline_menu = selp.fillContextMenu(menu, selkind); + if (offline_menu || selp.getSnapId()) + return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); + }); } - /** @summary Create path for complete TH2PolyBin */ - createPolyBin(funcs, bin) { - const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; - let cmd = ''; - for (let k = 0; k < arr.length; ++k) - cmd += this.createPolyGr(funcs, arr[k]); - return cmd; + /** @summary Save pad as image + * @param {string} kind - format of saved image like 'png', 'svg' or 'jpeg' + * @param {boolean} full_canvas - does complete canvas (true) or only frame area (false) should be saved + * @param {string} [filename] - name of the file which should be stored + * @desc Normally used from context menu + * @example + * import { getElementCanvPainter } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; + * let canvas_painter = getElementCanvPainter('drawing_div_id'); + * canvas_painter.saveAs('png', true, 'canvas.png'); */ + saveAs(kind, full_canvas, filename) { + if (!filename) + filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; + + this.produceImage(full_canvas, kind).then(imgdata => { + if (!imgdata) + return console.error(`Fail to produce image ${filename}`); + + if ((browser.qt6 || browser.cef3) && this.getSnapId()) { + console.warn(`sending file ${filename} to server`); + let res = imgdata; + if (kind !== 'svg') { + const separ = res.indexOf('base64,'); + res = (separ > 0) ? res.slice(separ + 7) : ''; + } + if (res) + this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); + } else { + const prefix = (kind === 'svg') ? prSVG : (kind === 'json' ? prJSON : ''); + saveFile(filename, prefix ? prefix + encodeURIComponent(imgdata) : imgdata); + } + }); } - /** @summary draw TH2Poly bins */ - async drawPolyBins() { - const histo = this.getObject(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - draw_colors = this.options.Color || (!this.options.Line && !this.options.Fill && !this.options.Text && !this.options.Mark), - draw_lines = this.options.Line || (this.options.Text && !draw_colors), - draw_fill = this.options.Fill && !draw_colors, - draw_mark = this.options.Mark, - h = pmain.getFrameHeight(), - textbins = [], - len = histo.fBins.arr.length; - let colindx, cmd, - full_cmd = '', allmarkers_cmd = '', - bin, item, i, gr0 = null, - lineatt_match = draw_lines, - fillatt_match = draw_fill, - markatt_match = draw_mark; + /** @summary Search active pad + * @return {Object} pad painter for active pad */ + findActivePad() { + let active_pp; + this.forEachPainterInPad(pp => { + if (pp.is_active_pad && !active_pp) + active_pp = pp; + }, 'pads'); + return active_pp; + } - // force recalculations of contours - // use global coordinates - this.maxbin = this.gmaxbin; - this.minbin = this.gminbin; - this.minposbin = this.gminposbin; + /** @summary Produce image for the pad + * @return {Promise} with created image */ + async produceImage(full_canvas, file_format, args) { + if (file_format === 'json') + return isFunc(this.produceJSON) ? this.produceJSON(full_canvas ? 2 : 0) : ''; - const cntr = draw_colors ? this.getContour(true) : null, - palette = cntr ? this.getHistPalette() : null, - rejectBin = bin => { - // check if bin outside visible range - return ((bin.fXmin > funcs.scale_xmax) || (bin.fXmax < funcs.scale_xmin) || - (bin.fYmin > funcs.scale_ymax) || (bin.fYmax < funcs.scale_ymin)); - }; + const use_frame = (full_canvas === 'frame'), + elem = use_frame ? this.getFrameSvg() : (full_canvas ? this.getCanvSvg() : this.getPadSvg()), + painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, + items = []; // keep list of replaced elements, which should be moved back at the end - // check if similar fill attributes - for (i = 0; i < len; ++i) { - bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + if (elem.empty()) + return ''; - const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; - for (let k = 0; k < arr.length; ++k) { - const gr = arr[k]; - if (!gr0) { gr0 = gr; continue; } - if (lineatt_match && ((gr0.fLineColor !== gr.fLineColor) || (gr0.fLineWidth !== gr.fLineWidth) || (gr0.fLineStyle !== gr.fLineStyle))) - lineatt_match = false; - if (fillatt_match && ((gr0.fFillColor !== gr.fFillColor) || (gr0.fFillStyle !== gr.fFillStyle))) - fillatt_match = false; - if (markatt_match && ((gr0.fMarkerColor !== gr.fMarkerColor) || (gr0.fMarkerStyle !== gr.fMarkerStyle) || (gr0.fMarkerSize !== gr.fMarkerSize))) - markatt_match = false; + if (use_frame || !full_canvas) { + const defs = this.getCanvSvg().selectChild('.canvas_defs'); + if (!defs.empty()) { + items.push({ prnt: this.getCanvSvg(), defs }); + elem.node().insertBefore(defs.node(), elem.node().firstChild); } - if (!lineatt_match && !fillatt_match && !markatt_match) - break; } - // do not try color draw optimization as with plain th2 while - // bins are not rectangular and drawings artefacts are nasty - // therefore draw each bin separately when doing color draw - const lineatt0 = lineatt_match && gr0 ? this.createAttLine(gr0) : null, - fillatt0 = fillatt_match && gr0 ? this.createAttFill(gr0) : null, - markeratt0 = markatt_match && gr0 ? this.createAttMarker({ attr: gr0, style: this.options.MarkStyle, std: false }) : null, - optimize_draw = !draw_colors && (draw_lines ? lineatt_match : true) && (draw_fill ? fillatt_match : true); + let active_pp = null; + painter.forEachPainterInPad(pp => { + if (pp.is_active_pad && !active_pp) { + active_pp = pp; + active_pp.drawActiveBorder(null, false); + } + if (use_frame) + return; // do not make transformations for the frame - // draw bins - for (i = 0; i < len; ++i) { - bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + const item = { prnt: pp.getPadSvg() }; + items.push(item); - const draw_bin = bin.fContent || this.options.Zero, - arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; + // remove buttons from each sub-pad + const btns = pp.getLayerSvg('btns_layer'); + item.btns_node = btns.node(); + if (item.btns_node) { + item.btns_prnt = item.btns_node.parentNode; + item.btns_next = item.btns_node.nextSibling; + btns.remove(); + } - colindx = draw_colors && draw_bin ? cntr.getPaletteIndex(palette, bin.fContent) : null; + const fp = pp.getFramePainter(); + if (!isFunc(fp?.access3dKind)) + return; - const textbin = this.options.Text && draw_bin ? { bin, sumx: 0, sumy: 0, sum: 0 } : null; + const can3d = fp.access3dKind(); + if ((can3d !== constants$1.Embed3D.Overlay) && (can3d !== constants$1.Embed3D.Embed)) + return; - for (let k = 0; k < arr.length; ++k) { - const gr = arr[k]; - if (markeratt0) { - const npnts = this.getGrNPoints(gr); - for (let n = 0; n < npnts; ++n) - allmarkers_cmd += markeratt0.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); - } + const main = isFunc(fp.getRenderer) ? fp : fp.getMainPainter(), + canvas = isFunc(main.getRenderer) ? main.getRenderer()?.domElement : null; + if (!isFunc(main?.render3D) || !isObject(canvas)) + return; - cmd = this.createPolyGr(funcs, gr, textbin); - if (!cmd) continue; + const sz2 = fp.getSizeFor3d(constants$1.Embed3D.Embed); // get size and position of DOM element as it will be embed + main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately + const dataUrl = canvas.toDataURL('image/png'); - if (optimize_draw) - full_cmd += cmd; - else if ((colindx !== null) || draw_fill || draw_lines) { - item = this.draw_g.append('svg:path').attr('d', cmd); - if (draw_colors && (colindx !== null)) - item.style('fill', this._color_palette.getColor(colindx)); - else if (draw_fill) - item.call('fill', this.createAttFill(gr).func); - else - item.style('fill', 'none'); - if (draw_lines) - item.call(this.createAttLine(gr).func); - } - } // loop over graphs + // remove 3D drawings + if (can3d === constants$1.Embed3D.Embed) { + item.foreign = item.prnt.select('.' + sz2.clname); + item.foreign.remove(); + } - if (textbin?.sum) - textbins.push(textbin); - } // loop over bins + const svg_frame = fp.getFrameSvg(); + item.frame_node = svg_frame.node(); + if (item.frame_node) { + item.frame_next = item.frame_node.nextSibling; + svg_frame.remove(); + } - if (optimize_draw) { - item = this.draw_g.append('svg:path').attr('d', full_cmd); - if (draw_fill && fillatt0) - item.call(fillatt0.func); - else - item.style('fill', 'none'); - if (draw_lines && lineatt0) - item.call(lineatt0.func); + // add svg image + item.img = item.prnt.insert('image', '.primitives_layer') // create image object + .attr('x', sz2.x) + .attr('y', sz2.y) + .attr('width', canvas.width) + .attr('height', canvas.height) + .attr('href', dataUrl); + }, 'pads'); + + let width = elem.property('draw_width'), + height = elem.property('draw_height'), + viewBox = ''; + if (use_frame) { + const fp = this.getFramePainter(); + width = fp.getFrameWidth(); + height = fp.getFrameHeight(); } + const scale = this.getPadScale(); + if (scale !== 1) { + viewBox = ` viewBox="0 0 ${width} ${height}"`; + width = Math.round(width / scale); + height = Math.round(height / scale); + } + if (settings.DarkMode || this.#pad?.$dark) + viewBox += ' style="filter: invert(100%)"'; - if (markeratt0 && !markeratt0.empty() && allmarkers_cmd) { - this.draw_g.append('svg:path') - .attr('d', allmarkers_cmd) - .call(markeratt0.func); - } else if (draw_mark) { - for (i = 0; i < len; ++i) { - bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + const arg = (file_format === 'pdf') + ? { node: elem.node(), width, height, scale, reset_tranform: use_frame } + : compressSVG(`${elem.node().innerHTML}`); - const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; + return svgToImage(arg, file_format, args).then(res => { + // reactivate border + active_pp?.drawActiveBorder(null, true); - for (let k = 0; k < arr.length; ++k) { - const gr = arr[k], npnts = this.getGrNPoints(gr), - markeratt = this.createAttMarker({ attr: gr, style: this.options.MarkStyle, std: false }); - if (!npnts || markeratt.empty()) - continue; - let cmd = ''; + for (let k = 0; k < items.length; ++k) { + const item = items[k]; - for (let n = 0; n < npnts; ++n) - cmd += markeratt.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); + item.img?.remove(); // delete embed image - this.draw_g.append('svg:path') - .attr('d', cmd) - .call(markeratt.func); - } // loop over graphs - } // loop over bins - } + const prim = item.prnt.selectChild('.primitives_layer'); - let pr = Promise.resolve(true); + if (item.foreign) // reinsert foreign object + item.prnt.node().insertBefore(item.foreign.node(), prim.node()); - if (textbins.length > 0) { - const color = this.getColor(histo.fMarkerColor), - rotate = -1*this.options.TextAngle, - text_g = this.draw_g.append('svg:g').attr('class', 'th2poly_text'), - text_size = ((histo.fMarkerSize !== 1) && rotate) ? Math.round(0.02*h*histo.fMarkerSize) : 12; + if (item.frame_node) // reinsert frame as first in list of primitives + prim.node().insertBefore(item.frame_node, item.frame_next); - this.startTextDrawing(42, text_size, text_g, text_size); + if (item.btns_node) // reinsert buttons + item.btns_prnt.insertBefore(item.btns_node, item.btns_next); + + if (item.defs) // reinsert defs + item.prnt.node().insertBefore(item.defs.node(), item.prnt.node().firstChild); + } + return res; + }); + } - for (i = 0; i < textbins.length; ++i) { - const textbin = textbins[i]; + /** @summary Process pad button click */ + clickPadButton(funcname, evnt) { + if (funcname === 'CanvasSnapShot') + return this.saveAs('png', true); - bin = textbin.bin; + if (funcname === 'enlargePad') + return this.enlargePad(); - if (textbin.sum > 0) { - textbin.midx = Math.round(textbin.sumx / textbin.sum); - textbin.midy = Math.round(textbin.sumy / textbin.sum); - } else { - textbin.midx = Math.round(funcs.grx((bin.fXmin + bin.fXmax)/2)); - textbin.midy = Math.round(funcs.gry((bin.fYmin + bin.fYmax)/2)); - } + if (funcname === 'PadSnapShot') + return this.saveAs('png', false); - let text; + if (funcname === 'PadContextMenus') { + evnt?.preventDefault(); + evnt?.stopPropagation(); + if (closeMenu()) + return; - if (!this.options.TextKind) - text = (Math.round(bin.fContent) === bin.fContent) ? bin.fContent.toString() : floatToString(bin.fContent, gStyle.fPaintTextFormat); - else { - text = bin.fPoly?.fName; - if (!text || (text === 'Graph')) - text = bin.fNumber.toString(); - } + return createMenu(evnt, this).then(menu => { + menu.header('Menus'); - this.drawText({ align: 22, x: textbin.midx, y: textbin.midy, rotate, text, color, latex: 0, draw_g: text_g }); - } + menu.add(this.isCanvas() ? 'Canvas' : 'Pad', 'pad', this.itemContextMenu); - pr = this.finishTextDrawing(text_g, true); - } + if (this.getFramePainter()) + menu.add('Frame', 'frame', this.itemContextMenu); - return pr.then(() => { return { poly: true }; }); - } + const main = this.getMainPainter(); // here pad painter method - /** @summary Draw TH2 bins as text */ - async drawBinsText(handle) { - const histo = this.getObject(), - test_cutg = this.options.cutg, - color = this.getColor(histo.fMarkerColor), - rotate = -1*this.options.TextAngle, - draw_g = this.draw_g.append('svg:g').attr('class', 'th2_text'), - profile2d = this.matchObjectType(clTProfile2D) && isFunc(histo.getBinEntries), - show_err = (this.options.TextKind === 'E'), - latex = (show_err && !this.options.TextLine) ? 1 : 0; - let x, y, width, height, - text_size = 20, text_offset = 0; + if (main) { + menu.add('X axis', 'xaxis', this.itemContextMenu); + menu.add('Y axis', 'yaxis', this.itemContextMenu); + if (isFunc(main.getDimension) && (main.getDimension() > 1)) + menu.add('Z axis', 'zaxis', this.itemContextMenu); + } - if (!handle) handle = this.prepareDraw({ rounding: false }); + if (this.#painters?.length) { + menu.separator(); + const shown = []; + this.#painters.forEach((pp, indx) => { + const obj = pp?.getObject(); + if (!obj || (shown.indexOf(obj) >= 0)) + return; + let name; + if (isFunc(pp.getMenuHeader)) + name = pp.getMenuHeader(); + else { + name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); + if (name) + name += '::'; + name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); + } + menu.add(name, indx, this.itemContextMenu); + shown.push(obj); + }); + } - if ((histo.fMarkerSize !== 1) && rotate) - text_size = Math.round(0.02*histo.fMarkerSize*this.getFramePainter().getFrameHeight()); + menu.show(); + }); + } - if (histo.fBarOffset !== 0) text_offset = histo.fBarOffset*1e-3; + // click automatically goes to all sub-pads + // if any painter indicates that processing completed, it returns true + let done = false; + const prs = []; - this.startTextDrawing(42, text_size, draw_g, text_size); + for (let i = 0; i < this.#painters.length; ++i) { + const pp = this.#painters[i]; - for (let i = handle.i1; i < handle.i2; ++i) { - const binw = handle.grx[i+1] - handle.grx[i]; - for (let j = handle.j1; j < handle.j2; ++j) { - let binz = histo.getBinContent(i+1, j+1); - if ((binz === 0) && !this._show_empty_bins) continue; + if (isFunc(pp.clickPadButton)) + prs.push(pp.clickPadButton(funcname, evnt)); - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + if (!done && isFunc(pp.clickButton)) { + done = pp.clickButton(funcname); + if (isPromise(done)) + prs.push(done); + } + } - const binh = handle.gry[j] - handle.gry[j+1]; + return Promise.all(prs); + } - if (profile2d) - binz = histo.getBinEntries(i+1, j+1); + /** @summary Add button to the pad + * @private */ + addPadButton(btn, tooltip, funcname, keyname) { + if (!settings.ToolBar || this.isBatchMode()) + return; - let text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); + if (!this._buttons) + this._buttons = []; + // check if there are duplications - if (show_err) { - const errz = histo.getBinError(histo.getBin(i+1, j+1)), - lble = (errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat); - if (this.options.TextLine) - text += '\xB1' + lble; - else - text = `#splitline{${text}}{#pm${lble}}`; - } + for (let k = 0; k < this._buttons.length; ++k) { + if (this._buttons[k].funcname === funcname) + return; + } - if (rotate /* || (histo.fMarkerSize !== 1) */) { - x = Math.round(handle.grx[i] + binw*0.5); - y = Math.round(handle.gry[j+1] + binh*(0.5 + text_offset)); - width = height = 0; - } else { - x = Math.round(handle.grx[i] + binw*0.1); - y = Math.round(handle.gry[j+1] + binh*(0.1 + text_offset)); - width = Math.round(binw*0.8); - height = Math.round(binh*0.8); - } + this._buttons.push({ btn, tooltip, funcname, keyname }); - this.drawText({ align: 22, x, y, width, height, rotate, text, color, latex, draw_g }); - } + if (!this.isTopPad() && funcname.indexOf('Pad') && (funcname !== 'enlargePad')) { + const cp = this.getCanvPainter(); + if (cp && (cp !== this)) + cp.addPadButton(btn, tooltip, funcname); } + } - handle.hide_only_zeros = true; // text drawing suppress only zeros + /** @summary Show pad buttons + * @private */ + showPadButtons() { + if (!this._buttons) + return; - return this.finishTextDrawing(draw_g, true).then(() => handle); + PadButtonsHandler.assign(this); + this.showPadButtons(); } - /** @summary Draw TH2 bins as arrows */ - drawBinsArrow() { - const histo = this.getObject(), - test_cutg = this.options.cutg, - handle = this.prepareDraw({ rounding: false }), - scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1])/(handle.i2 - handle.i1 + 1)/2, - scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1])/(handle.j2 - handle.j1 + 1)/2, - makeLine = (dx, dy) => dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); - let i, j, dn = 1e-30, dx, dy, xc, yc, cmd = '', - dxn, dyn, x1, x2, y1, y2, anr, si, co; + /** @summary Add buttons for pad or canvas + * @private */ + addPadButtons(is_online) { + this.addPadButton('camera', 'Create PNG', this.isCanvas() ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); - for (let loop = 0; loop < 2; ++loop) { - for (i = handle.i1; i < handle.i2; ++i) { - for (j = handle.j1; j < handle.j2; ++j) { - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + if (settings.ContextMenu) + this.addPadButton('question', 'Access context menus', 'PadContextMenus'); - if (i === handle.i1) - dx = histo.getBinContent(i+2, j+1) - histo.getBinContent(i+1, j+1); - else if (i === handle.i2-1) - dx = histo.getBinContent(i+1, j+1) - histo.getBinContent(i, j+1); - else - dx = 0.5*(histo.getBinContent(i+2, j+1) - histo.getBinContent(i, j+1)); + const add_enlarge = !this.isTopPad() && this.hasObjectsToDraw(); - if (j === handle.j1) - dy = histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j+1); - else if (j === handle.j2-1) - dy = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1, j); - else - dy = 0.5*(histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j)); + if (add_enlarge || this.enlargeMain('verify')) + this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); - if (loop === 0) - dn = Math.max(dn, Math.abs(dx), Math.abs(dy)); - else { - xc = (handle.grx[i] + handle.grx[i+1])/2; - yc = (handle.gry[j] + handle.gry[j+1])/2; - dxn = scale_x*dx/dn; - dyn = scale_y*dy/dn; - x1 = xc - dxn; - x2 = xc + dxn; - y1 = yc - dyn; - y2 = yc + dyn; - dx = Math.round(x2-x1); - dy = Math.round(y2-y1); + if (is_online && this.brlayout) { + this.addPadButton('diamand', 'Toggle Ged', 'ToggleGed'); + this.addPadButton('three_circles', 'Toggle Status', 'ToggleStatus'); + } + } - if (dx || dy) { - cmd += `M${Math.round(x1)},${Math.round(y1)}${makeLine(dx, dy)}`; + /** @summary Decode pad draw options + * @private */ + decodeOptions(opt) { + const pad = this.getObject(); + if (!pad) + return; - if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { - anr = Math.sqrt(9/(dx**2 + dy**2)); - si = Math.round(anr*(dx + dy)); - co = Math.round(anr*(dx - dy)); - if (si || co) - cmd += `m${-si},${co}${makeLine(si, -co)}${makeLine(-co, -si)}`; - } - } - } - } + const d = new DrawOptions(opt), + o = this.setOptions({ GlobalColors: true, LocalColors: false, CreatePalette: 0, IgnorePalette: false, RotateFrame: false, FixFrame: false }, opt); + + if (d.check('NOCOLORS') || d.check('NOCOL')) + o.GlobalColors = o.LocalColors = false; + if (d.check('LCOLORS') || d.check('LCOL')) { + o.GlobalColors = false; + o.LocalColors = true; + } + if (d.check('NOPALETTE') || d.check('NOPAL')) + o.IgnorePalette = true; + if (d.check('ROTATE')) + o.RotateFrame = true; + if (d.check('FIXFRAME')) + o.FixFrame = true; + if (d.check('FIXSIZE') && this.isCanvas()) + this._setFixedSize(true); + if (d.check('CP', true)) + o.CreatePalette = d.partAsInt(0, 0); + if (d.check('NOZOOMX')) + o.NoZoomX = true; + if (d.check('NOZOOMY')) + o.NoZoomY = true; + if (d.check('GRAYSCALE')) + pad.SetBit(kIsGrayscale, true); + + function forEach(func, p) { + func(p); + const arr = p.fPrimitives?.arr || []; + for (let i = 0; i < arr.length; ++i) { + if (arr[i]._typename === clTPad) + forEach(func, arr[i]); } } - this.draw_g - .append('svg:path') - .attr('d', cmd) - .style('fill', 'none') - .call(this.lineatt.func); + function padOpt(name, func) { + if (d.check(name)) + forEach(func, pad); + } + + /* eslint-disable @stylistic/js/max-statements-per-line */ + padOpt('NOMARGINS', p => { p.fLeftMargin = p.fRightMargin = p.fBottomMargin = p.fTopMargin = 0; }); + padOpt('WHITE', p => { p.fFillColor = 0; }); + padOpt('LOG2X', p => { p.fLogx = 2; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LOGX', p => { p.fLogx = 1; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LOG2Y', p => { p.fLogy = 2; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LOGY', p => { p.fLogy = 1; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LOG2Z', p => { p.fLogz = 2; }); + padOpt('LOGZ', p => { p.fLogz = 1; }); + padOpt('LOGV', p => { p.fLogv = 1; }); + padOpt('LOG2', p => { p.fLogx = p.fLogy = p.fLogz = 2; }); + padOpt('LOG', p => { p.fLogx = p.fLogy = p.fLogz = 1; }); + padOpt('LNX', p => { p.fLogx = 3; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LNY', p => { p.fLogy = 3; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LN', p => { p.fLogx = p.fLogy = p.fLogz = 3; }); + padOpt('GRIDX', p => { p.fGridx = 1; }); + padOpt('GRIDY', p => { p.fGridy = 1; }); + padOpt('GRID', p => { p.fGridx = p.fGridy = 1; }); + padOpt('TICKX2', p => { p.fTickx = 2; }); + padOpt('TICKY2', p => { p.fTicky = 2; }); + padOpt('TICKZ2', p => { p.fTickz = 2; }); + padOpt('TICK2', p => { p.fTickx = p.fTicky = 2; }); + padOpt('TICKX', p => { p.fTickx = 1; }); + padOpt('TICKY', p => { p.fTicky = 1; }); + padOpt('TICKZ', p => { p.fTickz = 1; }); + padOpt('TICK', p => { p.fTickx = p.fTicky = 1; }); + ['OTX', 'OTY', 'CTX', 'CTY', 'NOEX', 'NOEY', 'RX', 'RY'] + .forEach(name => padOpt(name, p => { p['$' + name] = true; })); + + if (!d.empty() && pad?.fPrimitives) { + for (let n = 0; n < pad.fPrimitives.arr.length; ++n) { + if (d.check(`SUB${n}_`, true)) + pad.fPrimitives.opt[n] = d.part; + } + } - return handle; + this.storeDrawOpt(opt); } - /** @summary Draw TH2 bins as boxes */ - drawBinsBox() { - const histo = this.getObject(), - handle = this.prepareDraw({ rounding: false }), - main = this.getMainPainter(); + /** @summary draw TPad object */ + static async draw(dom, pad, opt) { + const painter = new TPadPainter(dom, pad, opt, false, true); - if (main === this) { - if (main.maxbin === main.minbin) { - main.maxbin = main.gmaxbin; - main.minbin = main.gminbin; - main.minposbin = main.gminposbin; - } - if (main.maxbin === main.minbin) - main.minbin = Math.min(0, main.maxbin-1); - } + painter.createPadSvg(); - const absmax = Math.max(Math.abs(main.maxbin), Math.abs(main.minbin)), - absmin = Math.max(0, main.minbin), - pad = this.getPadPainter().getRootPad(true), - test_cutg = this.options.cutg; - let i, j, binz, absz, res = '', cross = '', btn1 = '', btn2 = '', - zdiff, dgrx, dgry, xx, yy, ww, hh, xyfactor, - uselogz = false, logmin = 0; + if (painter.matchObjectType(clTPad) && (painter.isTopPad() || painter.hasObjectsToDraw())) + painter.addPadButtons(); - if ((pad?.fLogv ?? pad?.fLogz) && (absmax > 0)) { - uselogz = true; - const logmax = Math.log(absmax); - if (absmin > 0) - logmin = Math.log(absmin); - else if ((main.minposbin>=1) && (main.minposbin<100)) - logmin = Math.log(0.7); - else - logmin = (main.minposbin > 0) ? Math.log(0.7*main.minposbin) : logmax - 10; - if (logmin >= logmax) logmin = logmax - 10; - xyfactor = 1.0 / (logmax - logmin); - } else - xyfactor = 1.0 / (absmax - absmin); + // set active pad + selectActivePad({ pp: painter, active: true }); + // flag used to prevent immediate pad redraw during first draw + return painter.drawPrimitives().then(() => { + painter.showPadButtons(); + painter.addPadInteractive(); + return painter; + }); + } - // now start build - for (i = handle.i1; i < handle.i2; ++i) { - for (j = handle.j1; j < handle.j2; ++j) { - binz = histo.getBinContent(i + 1, j + 1); - absz = Math.abs(binz); - if ((absz === 0) || (absz < absmin)) continue; +} // class TPadPainter - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; +const kShowEventStatus = BIT(15), + // kAutoExec = BIT(16), + kMenuBar = BIT(17), + kShowToolBar = BIT(18), + kShowEditor = BIT(19), + // kMoveOpaque = BIT(20), + // kResizeOpaque = BIT(21), + // kIsGrayscale = BIT(22), + kShowToolTips = BIT(23); - zdiff = uselogz ? ((absz > 0) ? Math.log(absz) - logmin : 0) : (absz - absmin); - // area of the box should be proportional to absolute bin content - zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor))); - // avoid oversized bins - if (zdiff < 0) zdiff = 0; +/** @summary direct draw of TFrame object, + * @desc pad or canvas should already exist + * @private */ +function directDrawTFrame(dom, obj, opt) { + const fp = new TFramePainter(dom, obj); + fp.addToPadPrimitives(); + if (opt === '3d') + fp.mode3d = true; + return fp.redraw(); +} - ww = handle.grx[i+1] - handle.grx[i]; - hh = handle.gry[j] - handle.gry[j+1]; +/** + * @summary Painter for TCanvas object + * + * @private + */ - dgrx = zdiff * ww; - dgry = zdiff * hh; +class TCanvasPainter extends TPadPainter { - xx = Math.round(handle.grx[i] + dgrx); - yy = Math.round(handle.gry[j+1] + dgry); + #websocket; // WebWindow handle used for communication with server + #changed_layout; // modified layout + #getmenu_callback; // function called when menu items get from server + #online_fixed_size; // when size fixed for online canvas + #all_sections_showed; // set once after online canvas drawn + #last_highlight_msg; // last highligh msg send to server - ww = Math.max(Math.round(ww - 2*dgrx), 1); - hh = Math.max(Math.round(hh - 2*dgry), 1); + /** @summary Constructor */ + constructor(dom, canvas, opt, kind = true) { + super(dom, canvas, opt, kind); + this.#websocket = null; + this.tooltip_allowed = settings.Tooltip; + } - res += `M${xx},${yy}v${hh}h${ww}v${-hh}z`; + /** @summary Cleanup canvas painter */ + cleanup() { + if (this.#changed_layout) + this.setLayoutKind('simple'); + this.#changed_layout = undefined; + super.cleanup(); + } - if ((binz < 0) && (this.options.BoxStyle === 10)) - cross += `M${xx},${yy}l${ww},${hh}m0,${-hh}l${-ww},${hh}`; + /** @summary Returns canvas name */ + getCanvasName() { return this.getObjectName(); } - if ((this.options.BoxStyle === 11) && (ww > 5) && (hh > 5)) { - const pww = Math.round(ww*0.1), - phh = Math.round(hh*0.1), - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; - btn1 += (binz < 0) ? side2 : side1; - btn2 += (binz < 0) ? side1 : side2; - } - } - } + /** @summary Returns layout kind */ + getLayoutKind() { + const origin = this.selectDom('origin'), + layout = origin.empty() ? '' : origin.property('layout'); - if (res) { - const elem = this.draw_g.append('svg:path') - .attr('d', res) - .call(this.fillatt.func); - if ((this.options.BoxStyle !== 11) && this.fillatt.empty()) - elem.call(this.lineatt.func); - } + return layout || 'simple'; + } - if (btn1 && this.fillatt.hasColor()) { - this.draw_g.append('svg:path') - .attr('d', btn1) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); + /** @summary Set canvas layout kind */ + setLayoutKind(kind, main_selector) { + const origin = this.selectDom('origin'); + if (!origin.empty()) { + if (!kind) + kind = 'simple'; + origin.property('layout', kind); + origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); + this.#changed_layout = (kind !== 'simple'); // use in cleanup } + } - if (btn2) { - this.draw_g.append('svg:path') - .attr('d', btn2) - .call(this.fillatt.func) - .style('fill', !this.fillatt.hasColor() ? 'red' : rgb(this.fillatt.color).darker(0.5).formatHex()); - } + /** @summary Changes layout + * @return {Promise} indicating when finished */ + async changeLayout(layout_kind, mainid) { + const current = this.getLayoutKind(); + if (current === layout_kind) + return true; - if (cross) { - const elem = this.draw_g.append('svg:path') - .attr('d', cross) - .style('fill', 'none'); - if (!this.lineatt.empty()) - elem.call(this.lineatt.func); - else - elem.style('stroke', 'black'); - } + const origin = this.selectDom('origin'), + sidebar2 = origin.select('.side_panel2'), + lst = []; + let sidebar = origin.select('.side_panel'), + main = this.selectDom(), force; - return handle; - } + while (main.node().firstChild) + lst.push(main.node().removeChild(main.node().firstChild)); - /** @summary Draw histogram bins as candle plot */ - drawBinsCandle() { - const kNoOption = 0, - kBox = 1, - kMedianLine = 10, - kMedianNotched = 20, - kMedianCircle = 30, - kMeanLine = 100, - kMeanCircle = 300, - kWhiskerAll = 1000, - kWhisker15 = 2000, - kAnchor = 10000, - kPointsOutliers = 100000, - kPointsAll = 200000, - kPointsAllScat = 300000, - kHistoLeft = 1000000, - kHistoRight = 2000000, - kHistoViolin = 3000000, - kHistoZeroIndicator = 10000000, - kHorizontal = 100000000, - fallbackCandle = kBox + kMedianLine + kMeanCircle + kWhiskerAll + kAnchor, - fallbackViolin = kMeanCircle + kWhiskerAll + kHistoViolin + kHistoZeroIndicator; + if (!sidebar.empty()) + cleanup(sidebar.node()); + if (!sidebar2.empty()) + cleanup(sidebar2.node()); - let fOption = kNoOption; + this.setLayoutKind('simple'); // restore defaults + origin.html(''); // cleanup origin - const isOption = opt => { - let mult = 1; - while (opt >= mult) mult *= 10; - mult /= 10; - return Math.floor(fOption/mult) % 10 === Math.floor(opt/mult); - }, parseOption = (opt, is_candle) => { - let direction = '', preset = '', res = kNoOption; - const c0 = opt[0], c1 = opt[1]; + if (layout_kind === 'simple') { + main = origin; + for (let k = 0; k < lst.length; ++k) + main.node().appendChild(lst[k]); + this.setLayoutKind(layout_kind); + force = true; + } else { + const grid = new GridDisplay(origin.node(), layout_kind); - if (c0 >= 'A' && c0 <= 'Z') direction = c0; - if (c0 >= '1' && c0 <= '9') preset = c0; - if (c1 >= 'A' && c1 <= 'Z' && preset) direction = c1; - if (c1 >= '1' && c1 <= '9' && direction) preset = c1; + if (mainid === undefined) + mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; - if (is_candle) { - switch (preset) { - case '1': res += fallbackCandle; break; - case '2': res += kBox + kMeanLine + kMedianLine + kWhisker15 + kAnchor + kPointsOutliers; break; - case '3': res += kBox + kMeanCircle + kMedianLine + kWhisker15 + kAnchor + kPointsOutliers; break; - case '4': res += kBox + kMeanCircle + kMedianNotched + kWhisker15 + kAnchor + kPointsOutliers; break; - case '5': res += kBox + kMeanLine + kMedianLine + kWhisker15 + kAnchor + kPointsAll; break; - case '6': res += kBox + kMeanCircle + kMedianLine + kWhisker15 + kAnchor + kPointsAllScat; break; - default: res += fallbackCandle; - } + main = select(grid.getGridFrame(mainid)); + main.classed('central_panel', true).style('position', 'relative'); + + if (mainid === 2) { + // left panel for Y + sidebar = select(grid.getGridFrame(0)); + sidebar.classed('side_panel2', true).style('position', 'relative'); + // bottom panel for X + sidebar = select(grid.getGridFrame(3)); + sidebar.classed('side_panel', true).style('position', 'relative'); } else { - switch (preset) { - case '1': res += fallbackViolin; break; - case '2': res += kMeanCircle + kWhisker15 + kHistoViolin + kHistoZeroIndicator + kPointsOutliers; break; - default: res += fallbackViolin; - } + sidebar = select(grid.getGridFrame(1 - mainid)); + sidebar.classed('side_panel', true).style('position', 'relative'); } - const l = opt.indexOf('('), r = opt.lastIndexOf(')'); - if ((l >= 0) && (r > l+1)) - res = parseInt(opt.slice(l+1, r)); + // now append all childs to the new main + for (let k = 0; k < lst.length; ++k) + main.node().appendChild(lst[k]); - fOption = res; + this.setLayoutKind(layout_kind, '.central_panel'); - if ((direction === 'Y' || direction === 'H') && !isOption(kHorizontal)) - fOption += kHorizontal; - }, extractQuantiles = (xx, proj, prob) => { - let integral = 0, cnt = 0, sum1 = 0; - const res = { max: 0, first: -1, last: -1, entries: 0 }; + // remove reference to MDIDisplay, solves resize problem + origin.property('mdi', null); + } - for (let j = 0; j < proj.length; ++j) { - if (proj[j] > 0) { - res.max = Math.max(res.max, proj[j]); - if (res.first < 0) res.first = j; - res.last = j; - } - integral += proj[j]; - sum1 += proj[j]*(xx[j]+xx[j+1])/2; - } - if (integral <= 0) return null; + // resize main drawing and let draw extras + resize(main.node(), force); + return true; + } - res.entries = integral; - res.mean = sum1/integral; - res.quantiles = new Array(prob.length); - res.indx = new Array(prob.length); + /** @summary Toggle projection + * @return {Promise} indicating when ready + * @private */ + async toggleProjection(kind) { + delete this.proj_painter; - for (let j = 0, sum = 0, nextv = 0; j < proj.length; ++j) { - const v = nextv; - let x = xx[j]; + if (kind) + this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed - // special case - flat integral with const value - if ((v === prob[cnt]) && (proj[j] === 0) && (v < 0.99)) { - while ((proj[j] === 0) && (j < proj.length)) j++; - x = (xx[j] + x) / 2; // this will be mid value - } + if (isFunc(this.showUI5ProjectionArea)) + return this.showUI5ProjectionArea(kind); - sum += proj[j]; - nextv = sum / integral; - while ((prob[cnt] >= v) && (prob[cnt] < nextv)) { - res.indx[cnt] = j; - res.quantiles[cnt] = x + ((prob[cnt] - v) / (nextv - v)) * (xx[j + 1] - x); - if (cnt++ === prob.length) return res; - x = xx[j]; - } - } + let layout = 'simple', mainid; - while (cnt < prob.length) { - res.indx[cnt] = proj.length-1; - res.quantiles[cnt++] = xx[xx.length-1]; - } + switch (kind) { + case 'XY': + layout = 'projxy'; + mainid = 2; + break; + case 'X': + case 'bottom': + layout = 'vert2_31'; + mainid = 0; + break; + case 'Y': + case 'left': + layout = 'horiz2_13'; + mainid = 1; + break; + case 'top': + layout = 'vert2_13'; + mainid = 1; + break; + case 'right': + layout = 'horiz2_31'; + mainid = 0; + break; + } - return res; - }; + return this.changeLayout(layout, mainid); + } - if (this.options.Candle) - parseOption(this.options.Candle, true); - else if (this.options.Violin) - parseOption(this.options.Violin, false); + /** @summary Draw projection for specified histogram + * @private */ + async drawProjection(kind, hist, hopt) { + if (!this.proj_painter) + return false; // ignore drawing if projection not configured - const histo = this.getHisto(), - handle = this.prepareDraw(), - pmain = this.getFramePainter(), // used for axis values conversions - cp = this.getCanvPainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - swapXY = isOption(kHorizontal); - let bars = '', lines = '', dashed_lines = '', - hists = '', hlines = '', - markers = '', cmarkers = '', attrcmarkers = null, - xx, proj, - scaledViolin = gStyle.fViolinScaled, - scaledCandle = gStyle.fCandleScaled, - maxContent = 0, maxIntegral = 0; + if (hopt === undefined) + hopt = 'hist'; + if (!kind) + kind = 'X'; - if (this.options.Scaled !== null) - scaledViolin = scaledCandle = this.options.Scaled; - else if (cp?.online_canvas) ; else if (histo.fTitle.indexOf('unscaled') >= 0) - scaledViolin = scaledCandle = false; - else if (histo.fTitle.indexOf('scaled') >= 0) - scaledViolin = scaledCandle = true; + if (!this.proj_painter[kind]) { + this.proj_painter[kind] = 'init'; - if (scaledViolin && (isOption(kHistoRight) || isOption(kHistoLeft) || isOption(kHistoViolin))) { - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) - maxContent = Math.max(maxContent, histo.getBinContent(i + 1, j + 1)); - } - } - - const make_path = (...a) => { - if (a[1] === 'array') a = a[0]; - const l = a.length; - let i = 2, xx = a[0], yy = a[1], - res = swapXY ? `M${yy},${xx}` : `M${xx},${yy}`; - while (i < l) { - switch (a[i]) { - case 'Z': return res + 'z'; - case 'V': if (yy !== a[i+1]) { res += (swapXY ? 'h' : 'v') + (a[i+1] - yy); yy = a[i+1]; } break; - case 'H': if (xx !== a[i+1]) { res += (swapXY ? 'v' : 'h') + (a[i+1] - xx); xx = a[i+1]; } break; - default: res += swapXY ? `l${a[i+1]-yy},${a[i]-xx}` : `l${a[i]-xx},${a[i+1]-yy}`; xx = a[i]; yy = a[i+1]; - } - i += 2; - } - return res; - }, make_marker = (x, y) => { - if (!markers) { - this.createAttMarker({ attr: histo, style: isOption(kPointsAllScat) ? 0 : 5 }); - this.markeratt.resetPos(); - } - markers += swapXY ? this.markeratt.create(y, x) : this.markeratt.create(x, y); - }, make_cmarker = (x, y) => { - if (!attrcmarkers) { - attrcmarkers = this.createAttMarker({ attr: histo, style: 24, std: false }); - attrcmarkers.resetPos(); - } - cmarkers += swapXY ? attrcmarkers.create(y, x) : attrcmarkers.create(x, y); - }; - - // if ((histo.fFillStyle === 0) && (histo.fFillColor > 0) && (!this.fillatt || this.fillatt.empty())) - // this.createAttFill({ color: this.getColor(histo.fFillColor), pattern: 1001 }); - - if (histo.fMarkerColor === 1) histo.fMarkerColor = histo.fLineColor; - - handle.candle = []; // array of drawn points - - // Determining the quantiles - const wRange = gStyle.fCandleWhiskerRange, bRange = gStyle.fCandleBoxRange, - prob = [(wRange >= 1) ? 1e-15 : 0.5 - wRange/2.0, - (bRange >= 1) ? 1E-14 : 0.5 - bRange/2.0, - 0.5, - (bRange >= 1) ? 1-1E-14 : 0.5 + bRange/2.0, - (wRange >= 1) ? 1-1e-15 : 0.5 + wRange/2.0], - - produceCandlePoint = (bin_indx, grx_left, grx_right, xindx1, xindx2) => { - const res = extractQuantiles(xx, proj, prob); - if (!res) return; - - const pnt = { bin: bin_indx, swapXY, fBoxDown: res.quantiles[1], fMedian: res.quantiles[2], fBoxUp: res.quantiles[3] }, - iqr = pnt.fBoxUp - pnt.fBoxDown; - let fWhiskerDown = res.quantiles[0], fWhiskerUp = res.quantiles[4]; + const canv = create$1(clTCanvas), + pad = this.getRootPad(), + fp = this.getFramePainter(); + let drawopt; - if (isOption(kWhisker15)) { // Improved whisker definition, with 1.5*iqr - let pos = pnt.fBoxDown-1.5*iqr, indx = res.indx[1]; - while ((xx[indx] > pos) && (indx > 0)) indx--; - while (!proj[indx]) indx++; - fWhiskerDown = xx[indx]; // use lower edge here - pos = pnt.fBoxUp+1.5*iqr; indx = res.indx[3]; - while ((xx[indx] < pos) && (indx < proj.length)) indx++; - while (!proj[indx]) indx--; - fWhiskerUp = xx[indx+1]; // use upper index edge here + if (kind === 'X') { + canv.fLeftMargin = pad.fLeftMargin; + canv.fRightMargin = pad.fRightMargin; + canv.fLogx = fp.logx; + canv.fUxmin = fp.logx ? Math.log10(fp.scale_xmin) : fp.scale_xmin; + canv.fUxmax = fp.logx ? Math.log10(fp.scale_xmax) : fp.scale_xmax; + drawopt = 'fixframe'; + } else if (kind === 'Y') { + canv.fBottomMargin = pad.fBottomMargin; + canv.fTopMargin = pad.fTopMargin; + canv.fLogx = fp.logy; + canv.fUxmin = fp.logy ? Math.log10(fp.scale_ymin) : fp.scale_ymin; + canv.fUxmax = fp.logy ? Math.log10(fp.scale_ymax) : fp.scale_ymax; + drawopt = 'rotate'; } - const fMean = res.mean, - fMedianErr = 1.57*iqr/Math.sqrt(res.entries); + canv.fPrimitives.Add(hist, hopt); - // estimate quantiles... simple function... not so nice as GetQuantiles + const promise = isFunc(this.drawInUI5ProjectionArea) + ? this.drawInUI5ProjectionArea(canv, drawopt, kind) + : this.drawInSidePanel(canv, drawopt, kind); - // exclude points with negative y when log scale is specified - if (fWhiskerDown <= 0) - if ((swapXY && funcs.logx) || (!swapXY && funcs.logy)) return; + return promise.then(painter => { + this.proj_painter[kind] = painter; + return painter; + }); + } else if (isStr(this.proj_painter[kind])) { + console.log('Not ready with first painting', kind); + return true; + } - const w = (grx_right - grx_left); - let candleWidth, histoWidth, - center = (grx_left + grx_right) / 2 + histo.fBarOffset/1000*w; - if ((histo.fBarWidth > 0) && (histo.fBarWidth !== 1000)) - candleWidth = histoWidth = w * histo.fBarWidth / 1000; - else { - candleWidth = w*0.66; - histoWidth = w*0.8; - } + this.proj_painter[kind].getMainPainter()?.updateObject(hist, hopt); + return this.proj_painter[kind].redrawPad(); + } - if (scaledViolin && (maxContent > 0)) - histoWidth *= res.max/maxContent; - if (scaledCandle && (maxIntegral > 0)) - candleWidth *= res.entries/maxIntegral; + /** @summary Checks if canvas shown inside ui5 widget + * @desc Function should be used only from the func which supposed to be replaced by ui5 + * @private */ + testUI5() { + return this.use_openui ?? false; + } - pnt.x1 = Math.round(center - candleWidth/2); - pnt.x2 = Math.round(center + candleWidth/2); - center = Math.round(center); + /** @summary Draw in side panel + * @private */ + async drawInSidePanel(canv, opt, kind) { + const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', + side = this.selectDom('origin').select(sel); + return side.empty() ? null : this.drawObject(side.node(), canv, opt); + } - const x1d = Math.round(center - candleWidth/3), - x2d = Math.round(center + candleWidth/3), - ff = swapXY ? funcs.grx : funcs.gry; + /** @summary Show message + * @desc Used normally with web-based canvas and handled in ui5 + * @private */ + showMessage(msg) { + if (!this.testUI5()) + showProgress(msg, 7000); + } - pnt.yy1 = Math.round(ff(fWhiskerUp)); - pnt.y1 = Math.round(ff(pnt.fBoxUp)); - pnt.y0 = Math.round(ff(pnt.fMedian)); - pnt.y2 = Math.round(ff(pnt.fBoxDown)); - pnt.yy2 = Math.round(ff(fWhiskerDown)); + /** @summary Function called when canvas menu item Save is called */ + saveCanvasAsFile(fname) { + const pnt = fname.indexOf('.'); + this.createImage(fname.slice(pnt + 1)) + .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); + } - const y0m = Math.round(ff(fMean)), - y01 = Math.round(ff(pnt.fMedian + fMedianErr)), - y02 = Math.round(ff(pnt.fMedian - fMedianErr)); + /** @summary Send command to server to save canvas with specified name + * @desc Should be only used in web-based canvas + * @private */ + sendSaveCommand(fname) { + this.sendWebsocket('PRODUCE:' + fname); + } - if (isOption(kHistoZeroIndicator)) - hlines += make_path(center, Math.round(ff(xx[xindx1])), 'V', Math.round(ff(xx[xindx2]))); + /** @summary Submit menu request + * @private */ + async submitMenuRequest(_painter, _kind, reqid) { + // only single request can be handled, no limit better in RCanvas + return new Promise(resolveFunc => { + this.#getmenu_callback = resolveFunc; + this.sendWebsocket('GETMENU:' + reqid); // request menu items for given painter + }); + } - if (isOption(kMedianLine)) - lines += make_path(pnt.x1, pnt.y0, 'H', pnt.x2); - else if (isOption(kMedianNotched)) - lines += make_path(x1d, pnt.y0, 'H', x2d); - else if (isOption(kMedianCircle)) - make_cmarker(center, pnt.y0); + /** @summary Submit object exec request + * @private */ + submitExec(painter, exec, snapid) { + if (this.isReadonly() || !painter) + return; - if (isOption(kMeanCircle)) - make_cmarker(center, y0m); - else if (isOption(kMeanLine)) - dashed_lines += make_path(pnt.x1, y0m, 'H', pnt.x2); + if (!snapid) + snapid = painter.getSnapId(); + if (snapid && isStr(snapid) && exec) + return this.sendWebsocket(`OBJEXEC:${snapid}:${exec}`); + } - if (isOption(kBox)) { - if (isOption(kMedianNotched)) - bars += make_path(pnt.x1, pnt.y1, 'V', y01, x1d, pnt.y0, pnt.x1, y02, 'V', pnt.y2, 'H', pnt.x2, 'V', y02, x2d, pnt.y0, pnt.x2, y01, 'V', pnt.y1, 'Z'); - else - bars += make_path(pnt.x1, pnt.y1, 'V', pnt.y2, 'H', pnt.x2, 'V', pnt.y1, 'Z'); - } + /** @summary Return assigned web socket + * @private */ + getWebsocket() { return this.#websocket; } - if (isOption(kAnchor)) // Draw the anchor line - lines += make_path(pnt.x1, pnt.yy1, 'H', pnt.x2) + make_path(pnt.x1, pnt.yy2, 'H', pnt.x2); + /** @summary Return true if message can be send via web socket + * @private */ + canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } - if (isOption(kWhiskerAll) && !isOption(kHistoZeroIndicator)) { // Whiskers are dashed - dashed_lines += make_path(center, pnt.y1, 'V', pnt.yy1) + make_path(center, pnt.y2, 'V', pnt.yy2); - } else if ((isOption(kWhiskerAll) && isOption(kHistoZeroIndicator)) || isOption(kWhisker15)) - lines += make_path(center, pnt.y1, 'V', pnt.yy1) + make_path(center, pnt.y2, 'V', pnt.yy2); + /** @summary Send text message with web socket + * @desc used for communication with server-side of web canvas + * @private */ + sendWebsocket(msg) { + if (this.#websocket?.canSend()) { + this.#websocket.send(msg); + return true; + } + console.warn(`DROP SEND: ${msg}`); + return false; + } + /** @summary Close websocket connection to canvas + * @private */ + closeWebsocket(force) { + if (this.#websocket) { + this.#websocket.close(force); + this.#websocket.cleanup(); + this.#websocket = undefined; + } + } - if (isOption(kPointsOutliers) || isOption(kPointsAll) || isOption(kPointsAllScat)) { - // reset seed for each projection to have always same pixels - const rnd = new TRandom(bin_indx*7521 + Math.round(res.integral)), - show_all = !isOption(kPointsOutliers), - show_scat = isOption(kPointsAllScat); - for (let ii = 0; ii < proj.length; ++ii) { - const bin_content = proj[ii], binx = (xx[ii] + xx[ii+1])/2; - let marker_x = center, marker_y = 0; + /** @summary Use provided connection for the web canvas + * @private */ + useWebsocket(handle) { + this.closeWebsocket(); - if (!bin_content) continue; - if (!show_all && (binx >= fWhiskerDown) && (binx <= fWhiskerUp)) continue; + this.#websocket = handle; + this.#websocket.setReceiver(this); + this.#websocket.connect(); + } - for (let k = 0; k < bin_content; k++) { - if (show_scat) - marker_x = center + Math.round(((rnd.random() - 0.5) * candleWidth)); + /** @summary set, test or reset timeout of specified name + * @desc Used to prevent overloading of websocket for specific function */ + websocketTimeout(name, tm) { + if (!this.#websocket) + return; + if (!this.#websocket._tmouts) + this.#websocket._tmouts = {}; - if ((bin_content === 1) && !show_scat) - marker_y = Math.round(ff(binx)); - else - marker_y = Math.round(ff(xx[ii] + rnd.random()*(xx[ii+1]-xx[ii]))); + const handle = this.#websocket._tmouts[name]; + if (tm === undefined) + return handle !== undefined; - make_marker(marker_x, marker_y); - } - } + if (tm === 'reset') { + if (handle) { + clearTimeout(handle); + delete this.#websocket._tmouts[name]; } + } else if (!handle && Number.isInteger(tm)) + this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); + } - if ((isOption(kHistoRight) || isOption(kHistoLeft) || isOption(kHistoViolin)) && (res.max > 0) && (res.first >= 0)) { - const arr = [], scale = (swapXY ? -0.5 : 0.5) *histoWidth/res.max; - - xindx1 = Math.max(xindx1, res.first); - xindx2 = Math.min(xindx2-1, res.last); - - if (isOption(kHistoRight) || isOption(kHistoViolin)) { - let prev_x = center, prev_y = Math.round(ff(xx[xindx1])); - arr.push(prev_x, prev_y); - for (let ii = xindx1; ii <= xindx2; ii++) { - const curr_x = Math.round(center + scale*proj[ii]), - curr_y = Math.round(ff(xx[ii+1])); - if (curr_x !== prev_x) { - if (ii !== xindx1) arr.push('V', prev_y); - arr.push('H', curr_x); - } - prev_x = curr_x; - prev_y = curr_y; - } - arr.push('V', prev_y); - } + /** @summary Handler for websocket open event + * @private */ + onWebsocketOpened(/* handle */) { + // indicate that we are ready to receive any following commands + } - if (isOption(kHistoLeft) || isOption(kHistoViolin)) { - let prev_x = center, prev_y = Math.round(ff(xx[xindx2+1])); - if (arr.length === 0) - arr.push(prev_x, prev_y); - for (let ii = xindx2; ii >= xindx1; ii--) { - const curr_x = Math.round(center - scale*proj[ii]), - curr_y = Math.round(ff(xx[ii])); - if (curr_x !== prev_x) { - if (ii !== xindx2) arr.push('V', prev_y); - arr.push('H', curr_x); - } - prev_x = curr_x; - prev_y = curr_y; - } - arr.push('V', prev_y); - } + /** @summary Handler for websocket close event + * @private */ + onWebsocketClosed(/* handle */) { + if (!this.embed_canvas) + closeCurrentWindow(); + } - arr.push('H', center); // complete histogram + /** @summary Handle websocket messages + * @private */ + onWebsocketMsg(handle, msg) { + // console.log(`GET len:${msg.length} msg:${msg.slice(0,60)}`); - hists += make_path(arr, 'array'); + if (msg === 'CLOSE') { + this.onWebsocketClosed(); + this.closeWebsocket(true); + } else if (msg.slice(0, 6) === 'SNAP6:') { + // This is snapshot, produced with TWebCanvas + const p1 = msg.indexOf(':', 6), + version = msg.slice(6, p1), + snap = parse$1(msg.slice(p1 + 1)); - if (!this.fillatt.empty()) hists += 'Z'; + this.syncDraw(true) + .then(() => { + if (!this.getSnapId()) + this.resizeBrowser(snap.fSnapshot.fWindowWidth, snap.fSnapshot.fWindowHeight); + if (!this.getSnapId() && isFunc(this.setFixedCanvasSize)) + this.#online_fixed_size = this.setFixedCanvasSize(snap.fSnapshot.fCw, snap.fSnapshot.fCh, snap.fFixedSize); + }) + .then(() => this.redrawPadSnap(snap)) + .then(() => { + this.completeCanvasSnapDrawing(); + let ranges = this.getWebPadOptions(); // all data, including sub-pads + if (ranges) + ranges = ':' + ranges; + handle.send(`READY6:${version}${ranges}`); // send ready message back when drawing completed + this.confirmDraw(); + }).catch(err => { + if (isFunc(this.showConsoleError)) + this.showConsoleError(err); + else + console.log(err); + }); + } else if (msg.slice(0, 5) === 'MENU:') { + // this is menu with exact identifier for object + const lst = parse$1(msg.slice(5)); + if (isFunc(this.#getmenu_callback)) { + this.#getmenu_callback(lst); + this.#getmenu_callback = undefined; } + } else if (msg.slice(0, 4) === 'CMD:') { + msg = msg.slice(4); + const p1 = msg.indexOf(':'), + cmdid = msg.slice(0, p1), + cmd = msg.slice(p1 + 1), + reply = `REPLY:${cmdid}:`; + if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { + this.createImage(cmd.toLowerCase()) + .then(res => handle.send(reply + res)); + } else { + console.log(`Unrecognized command ${cmd}`); + handle.send(reply); + } + } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { + const kind = msg[1], + hist = parse$1(msg.slice(7)); + this.websocketTimeout(`proj${kind}`, 'reset'); + this.drawProjection(kind, hist); + } else if (msg.slice(0, 5) === 'CTRL:') { + const ctrl = parse$1(msg.slice(5)) || {}; + let resized = false; + if ((ctrl.title !== undefined) && (typeof document !== 'undefined')) + document.title = ctrl.title; + if (ctrl.x && ctrl.y && typeof window !== 'undefined') { + window.moveTo(ctrl.x, ctrl.y); + resized = true; + } + if (ctrl.w && ctrl.h) { + this.resizeBrowser(Number.parseInt(ctrl.w), Number.parseInt(ctrl.h)); + resized = true; + } + if (ctrl.cw && ctrl.ch && isFunc(this.setFixedCanvasSize)) { + this.#online_fixed_size = this.setFixedCanvasSize(Number.parseInt(ctrl.cw), Number.parseInt(ctrl.ch), true); + resized = true; + } + const kinds = ['Menu', 'StatusBar', 'Editor', 'ToolBar', 'ToolTips']; + kinds.forEach(kind => { + if (ctrl[kind] !== undefined) + this.showSection(kind, ctrl[kind] === '1'); + }); - handle.candle.push(pnt); // keep point for the tooltip - }; - - if (swapXY) { - xx = new Array(this.nbinsx+1); - proj = new Array(this.nbinsx); - for (let i = 0; i < this.nbinsx+1; ++i) - xx[i] = histo.fXaxis.GetBinLowEdge(i+1); - - if (scaledCandle) { - for (let j = 0; j < this.nbinsy; ++j) { - let sum = 0; - for (let i = 0; i < this.nbinsx; ++i) - sum += histo.getBinContent(i+1, j+1); - maxIntegral = Math.max(maxIntegral, sum); + if (ctrl.edit) { + const obj_painter = this.findSnap(ctrl.edit); + if (obj_painter) { + this.showSection('Editor', true) + .then(() => this.producePadEvent('select', obj_painter.getPadPainter(), obj_painter)); } } - for (let j = handle.j1; j < handle.j2; ++j) { - for (let i = 0; i < this.nbinsx; ++i) - proj[i] = histo.getBinContent(i+1, j+1); - - produceCandlePoint(j, handle.gry[j+1], handle.gry[j], handle.i1, handle.i2); + if (ctrl.winstate && typeof window !== 'undefined') { + if (ctrl.winstate === 'iconify') + window.blur(); + else + window.focus(); } - } else { - xx = new Array(this.nbinsy+1); - proj = new Array(this.nbinsy); - for (let j = 0; j < this.nbinsy+1; ++j) - xx[j] = histo.fYaxis.GetBinLowEdge(j+1); + if (resized) + this.sendResized(true); + } else + console.log(`unrecognized msg ${msg}`); + } - if (scaledCandle) { - for (let i = 0; i < this.nbinsx; ++i) { - let sum = 0; - for (let j = 0; j < this.nbinsy; ++j) - sum += histo.getBinContent(i+1, j+1); - maxIntegral = Math.max(maxIntegral, sum); - } + /** @summary Send RESIZED message to client to inform about changes in canvas/window geometry + * @private */ + sendResized(force) { + const pad = this.getRootPad(); + if (!pad || (typeof window === 'undefined')) + return; + const cw = this.getPadWidth(), ch = this.getPadHeight(), + wx = window.screenLeft, wy = window.screenTop, + ww = window.outerWidth, wh = window.outerHeight, + fixed = this.#online_fixed_size ? 1 : 0; + if (!force) { + force = (cw > 0) && (ch > 0) && ((pad.fCw !== cw) || (pad.fCh !== ch)); + if (force) { + pad.fCw = cw; + pad.fCh = ch; } + } + if (force) + this.sendWebsocket(`RESIZED:${JSON.stringify([wx, wy, ww, wh, cw, ch, fixed])}`); + } - // loop over visible x-bins - for (let i = handle.i1; i < handle.i2; ++i) { - for (let j = 0; j < this.nbinsy; ++j) - proj[j] = histo.getBinContent(i+1, j+1); + /** @summary Handle pad button click event */ + clickPadButton(funcname, evnt) { + if (funcname === 'ToggleGed') + return this.activateGed(this, null, 'toggle'); + if (funcname === 'ToggleStatus') + return this.activateStatusBar('toggle'); + return super.clickPadButton(funcname, evnt); + } - produceCandlePoint(i, handle.grx[i], handle.grx[i+1], handle.j1, handle.j2); - } - } + /** @summary Returns true if event status shown in the canvas */ + hasEventStatus() { + if (this.testUI5()) + return false; + if (this.brlayout) + return this.brlayout.hasStatus(); + return getHPainter()?.hasStatusLine() ?? false; + } - if (hlines && (histo.fFillColor > 0)) { - this.draw_g.append('svg:path') - .attr('d', hlines) - .style('stroke', this.getColor(histo.fFillColor)); - } + /** @summary Check if status bar can be toggled + * @private */ + canStatusBar() { + return this.testUI5() || this.brlayout || getHPainter(); + } - const hline_color = (isOption(kHistoZeroIndicator) && (histo.fFillStyle !== 0)) ? this.fillatt.color : this.lineatt.color; - if (hists && (!this.fillatt.empty() || (hline_color !== 'none'))) { - this.draw_g.append('svg:path') - .attr('d', hists) - .style('stroke', (hline_color !== 'none') ? hline_color : null) - .style('pointer-events', this.isBatchMode() ? null : 'visibleFill') - .call(this.fillatt.func); - } + /** @summary Show/toggle event status bar + * @private */ + activateStatusBar(state) { + if (this.testUI5()) + return; + if (this.brlayout) + this.brlayout.createStatusLine(23, state); + else + getHPainter()?.createStatusLine(23, state); + this.processChanges('sbits', this); + } - if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) - .call(this.lineatt.func) - .call(this.fillatt.func); - } + /** @summary Show online canvas status + * @private */ + showCanvasStatus(...msgs) { + if (this.testUI5()) + return; - if (lines) { - this.draw_g.append('svg:path') - .attr('d', lines) - .call(this.lineatt.func) - .style('fill', 'none'); - } + const br = this.brlayout || getHPainter()?.brlayout; + br?.showStatus(...msgs); + } - if (dashed_lines) { - const dashed = this.createAttLine({ attr: histo, style: 2, std: false, color: kBlack }); - this.draw_g.append('svg:path') - .attr('d', dashed_lines) - .call(dashed.func) - .style('fill', 'none'); - } + /** @summary Returns true if GED is present on the canvas */ + hasGed() { + if (this.testUI5()) + return false; + return this.brlayout?.hasContent() ?? false; + } - if (cmarkers) { - this.draw_g.append('svg:path') - .attr('d', cmarkers) - .call(attrcmarkers.func); - } + /** @summary Function used to de-activate GED + * @private */ + removeGed() { + if (this.testUI5()) + return; - if (markers) { - this.draw_g.append('svg:path') - .attr('d', markers) - .call(this.markeratt.func); + this.registerForPadEvents(null); + + if (this.ged_view) { + this.ged_view.getController().cleanupGed(); + this.ged_view.destroy(); + delete this.ged_view; } + this.brlayout?.deleteContent(true); + this.processChanges('sbits', this); + } - return handle; + /** @summary Get view data for ui5 panel + * @private */ + getUi5PanelData(/* panel_name */) { + return { jsroot: { settings, create: create$1, parse: parse$1, toJSON, loadScript, EAxisBits, getColorExec } }; } - /** @summary Draw TH2 bins as scatter plot */ - drawBinsScatter() { - const histo = this.getObject(), - handle = this.prepareDraw({ rounding: true, pixel_density: true }), - test_cutg = this.options.cutg, - colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [], - scale = this.options.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), - rnd = new TRandom(handle.sumz); - let colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1.0; + /** @summary Function used to activate GED + * @return {Promise} when GED is there + * @private */ + async activateGed(objpainter, kind, mode) { + if (this.testUI5() || !this.brlayout) + return false; - handle.ScatterPlot = true; + if (this.brlayout.hasContent()) { + if ((mode === 'toggle') || (mode === false)) + this.removeGed(); + else + objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - if (scale*handle.sumz < 1e5) { - // one can use direct drawing of scatter plot without any patterns + return true; + } - this.createAttMarker({ attr: histo }); + if (mode === false) + return false; - this.markeratt.resetPos(); + const btns = this.brlayout.createBrowserBtns(); - let path = ''; - for (i = handle.i1; i < handle.i2; ++i) { - cw = handle.grx[i+1] - handle.grx[i]; - for (j = handle.j1; j < handle.j2; ++j) { - ch = handle.gry[j] - handle.gry[j+1]; - binz = histo.getBinContent(i + 1, j + 1); + ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') + .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); - const npix = Math.round(scale*binz); - if (npix <= 0) continue; + ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') + .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') + .style('margin', '3px').on('click', () => this.removeGed()); - for (let k = 0; k < npix; ++k) { - path += this.markeratt.create( - Math.round(handle.grx[i] + cw * rnd.random()), - Math.round(handle.gry[j+1] + ch * rnd.random())); - } - } - } + // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area + this.brlayout.setBrowserContent('
Loading GED ...
'); + this.brlayout.setBrowserTitle('GED'); + this.brlayout.toggleBrowserKind(kind || 'float'); - this.draw_g - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + return new Promise(resolveFunc => { + loadOpenui5().then(sap => { + select('#ged_placeholder').text(''); - return handle; - } + sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { + const oModel = new JSONModel({ handle: null }); - // limit filling factor, do not try to produce as many points as filled area; - if (this.maxbin > 0.7) factor = 0.7/this.maxbin; + XMLView.create({ + viewName: 'rootui5.canv.view.Ged', + viewData: this.getUi5PanelData('Ged') + }).then(oGed => { + oGed.setModel(oModel); - const nlevels = Math.round(handle.max - handle.min), - cntr = this.createContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin); + oGed.placeAt('ged_placeholder'); - // now start build - for (i = handle.i1; i < handle.i2; ++i) { - for (j = handle.j1; j < handle.j2; ++j) { - binz = histo.getBinContent(i + 1, j + 1); - if ((binz <= 0) || (binz < this.minbin)) continue; + this.ged_view = oGed; - cw = handle.grx[i+1] - handle.grx[i]; - ch = handle.gry[j] - handle.gry[j+1]; - if (cw*ch <= 0) continue; + // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself + this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); - colindx = cntr.getContourIndex(binz/cw/ch); - if (colindx < 0) continue; + objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + this.processChanges('sbits', this); - cmd1 = `M${handle.grx[i]},${handle.gry[j+1]}`; - if (colPaths[colindx] === undefined) { - colPaths[colindx] = cmd1; - cell_w[colindx] = cw; - cell_h[colindx] = ch; - } else { - cmd2 = `m${handle.grx[i]-currx[colindx]},${handle.gry[j+1]-curry[colindx]}`; - colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - cell_w[colindx] = Math.max(cell_w[colindx], cw); - cell_h[colindx] = Math.max(cell_h[colindx], ch); - } + resolveFunc(true); + }); + }); + }); + }); + } - currx[colindx] = handle.grx[i]; - curry[colindx] = handle.gry[j+1]; + /** @summary Show section of canvas like menu or editor */ + async showSection(that, on) { + if (this.testUI5()) + return false; - colPaths[colindx] += `v${ch}h${cw}v${-ch}z`; - } + switch (that) { + case 'Menu': break; + case 'StatusBar': this.activateStatusBar(on); break; + case 'Editor': return this.activateGed(this, null, on); + case 'ToolBar': break; + case 'ToolTips': this.setTooltipAllowed(on); break; } + return true; + } - const layer = this.getFrameSvg().selectChild('.main_layer'); - let defs = layer.selectChild('defs'); - if (defs.empty() && (colPaths.length > 0)) - defs = layer.insert('svg:defs', ':first-child'); + /** @summary Send command to start fit panel code on the server + * @private */ + startFitPanel(standalone) { + if (!this.getWebsocket()) + return false; - this.createAttMarker({ attr: histo }); + const new_conn = standalone ? null : this.getWebsocket().createChannel(); - for (colindx = 0; colindx < colPaths.length; ++colindx) { - if ((colPaths[colindx] !== undefined) && (colindx < cntr.arr.length)) { - const pattern_id = (this.pad_name || 'canv') + `_scatter_${colindx}`; - let pattern = defs.selectChild(`#${pattern_id}`); - if (pattern.empty()) { - pattern = defs.append('svg:pattern') - .attr('id', pattern_id) - .attr('patternUnits', 'userSpaceOnUse'); - } else - pattern.selectAll('*').remove(); - - let npix = Math.round(factor*cntr.arr[colindx]*cell_w[colindx]*cell_h[colindx]); - if (npix < 1) npix = 1; - - const arrx = new Float32Array(npix), arry = new Float32Array(npix); - - if (npix === 1) - arrx[0] = arry[0] = 0.5; - else { - for (let n = 0; n < npix; ++n) { - arrx[n] = rnd.random(); - arry[n] = rnd.random(); - } - } + this.sendWebsocket('FITPANEL:' + (standalone ? 'standalone' : new_conn.getChannelId())); + + return new_conn; + } - this.markeratt.resetPos(); + /** @summary Complete handling of online canvas drawing + * @private */ + completeCanvasSnapDrawing() { + const pad = this.getRootPad(); + if (!pad) + return; - let path = ''; + this.addPadInteractive(); - for (let n = 0; n < npix; ++n) - path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]); + if ((typeof document !== 'undefined') && !this.embed_canvas && this.getWebsocket()) + document.title = pad.fTitle; - pattern.attr('width', cell_w[colindx]) - .attr('height', cell_h[colindx]) - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + if (this.#all_sections_showed) + return; + this.#all_sections_showed = true; - this.draw_g - .append('svg:path') - .attr('scatter-index', colindx) - .style('fill', `url(#${pattern_id})`) - .attr('d', colPaths[colindx]); - } - } + // used in Canvas.controller.js to avoid browser resize because of initial sections show/hide + this._ignore_section_resize = true; - return handle; + this.showSection('Menu', pad.TestBit(kMenuBar)); + this.showSection('StatusBar', pad.TestBit(kShowEventStatus)); + this.showSection('ToolBar', pad.TestBit(kShowToolBar)); + this.showSection('Editor', pad.TestBit(kShowEditor)); + this.showSection('ToolTips', pad.TestBit(kShowToolTips) || this._highlight_connect); + + this._ignore_section_resize = false; } - /** @summary Draw TH2 bins in 2D mode */ - draw2DBins() { - if (this._hide_frame && this.isMainPainter()) { - this.getFrameSvg().style('display', null); - delete this._hide_frame; - } + /** @summary Handle highlight in canvas - deliver information to server + * @private */ + processHighlightConnect(hints) { + if (!hints?.length || !this._highlight_connect || + this.doingDraw() || !this.canSendWebsocket(2)) + return; - if (!this.draw_content) - return this.removeG(); + const hint = hints[0] || hints[1]; + if (!hint || !hint.painter?.getSnapId() || !hint.user_info) + return; + const pp = hint.painter.getPadPainter() || this; + if (!pp.getSnapId()) + return; - this.createHistDrawAttributes(); + const arr = [pp.getSnapId(), hint.painter.getSnapId(), '0', '0']; - this.createG(true); + if ((hint.user_info.binx !== undefined) && (hint.user_info.biny !== undefined)) { + arr[2] = hint.user_info.binx.toString(); + arr[3] = hint.user_info.biny.toString(); + } else if (hint.user_info.bin !== undefined) + arr[2] = hint.user_info.bin.toString(); - let handle, pr; + const msg = JSON.stringify(arr); - if (this.isTH2Poly()) - pr = this.drawPolyBins(); - else { - if (this.options.Scat) - handle = this.drawBinsScatter(); + if (this.#last_highlight_msg !== msg) { + this.#last_highlight_msg = msg; + this.sendWebsocket(`HIGHLIGHT:${msg}`); + } + } - if (this.options.Color) - handle = this.drawBinsColor(); - else if (this.options.Box) - handle = this.drawBinsBox(); - else if (this.options.Arrow) - handle = this.drawBinsArrow(); - else if (this.options.Proj) - handle = this.drawBinsProjected(); - else if (this.options.Contour) - handle = this.drawBinsContour(); - else if (this.options.Candle || this.options.Violin) - handle = this.drawBinsCandle(); + /** @summary Method informs that something was changed in the canvas + * @desc used to update information on the server (when used with web6gui) + * @private */ + processChanges(kind, painter, subelem) { + // check if we could send at least one message more - for some meaningful actions + if (this.isReadonly() || !this.canSendWebsocket(2) || !isStr(kind)) + return; - if (this.options.Text) - pr = this.drawBinsText(handle); + let msg = ''; + if (!painter) + painter = this; + switch (kind) { + case 'sbits': + msg = 'STATUSBITS:' + this.getStatusBits(); + break; + case 'frame': // when changing frame + case 'zoom': // when changing zoom inside frame + if (!isFunc(painter.getWebPadOptions)) + painter = painter.getPadPainter(); + if (isFunc(painter.getWebPadOptions)) + msg = 'OPTIONS6:' + painter.getWebPadOptions('only_this'); + break; + case 'padpos': // when changing pad position + msg = 'OPTIONS6:' + painter.getWebPadOptions('with_subpads'); + break; + case 'drawopt': + if (painter.getSnapId()) + msg = 'DRAWOPT:' + JSON.stringify([painter.getSnapId(), painter.getDrawOpt() || '']); + break; + case 'pave_moved': { + const info = createWebObjectOptions(painter); + if (info) + msg = 'PRIMIT6:' + toJSON(info); + break; + } + case 'logx': + case 'logy': + case 'logz': { + const pp = painter.getPadPainter(), + pad = pp?.getRootPad(); + if (pp?.getSnapId() && pad) { + const name = 'SetLog' + kind[3], value = pad['fLog' + kind[3]]; + painter = pp; + kind = `exec:${name}(${value})`; + } + break; + } + } - if (!handle && !pr) - handle = this.drawBinsColor(); + if (!msg && isFunc(painter?.getSnapId) && (kind.slice(0, 5) === 'exec:')) { + const snapid = painter.getSnapId(subelem); + if (snapid) { + msg = 'PRIMIT6:' + toJSON({ + _typename: 'TWebObjectOptions', + snapid, opt: kind.slice(5), fcust: 'exec', fopt: [] + }); + } } - if (handle) - this.tt_handle = handle; - else if (pr) - return pr.then(tt => { this.tt_handle = tt; }); + if (msg) { + // console.log(`Sending ${msg.length} ${msg.slice(0,40)}`); + this.sendWebsocket(msg); + } else + console.log(`Unprocessed changes ${kind} for painter of ${painter?.getObject()?._typename} subelem ${subelem}`); } - /** @summary Draw TH2 in circular mode */ - drawBinsCircular() { - this.getFrameSvg().style('display', 'none'); - this._hide_frame = true; - - const rect = this.getPadPainter().getFrameRect(), - hist = this.getHisto(), - palette = this.options.Circular > 10 ? this.getHistPalette() : null, - text_size = 20, - circle_size = 16, - axis = hist.fXaxis, - getBinLabel = indx => { - if (axis.fLabels) { - for (let i = 0; i < axis.fLabels.arr.length; ++i) { - const tstr = axis.fLabels.arr[i]; - if (tstr.fUniqueID === indx+1) return tstr.fString; - } - } - return indx.toString(); - }; + /** @summary Select active pad on the canvas */ + selectActivePad(pad_painter, obj_painter, click_pos) { + if (!this.getSnapId() || !pad_painter) + return; // only interactive canvas - this.createG(); + let arg = null, ischanged = false; + const is_button = pad_painter.matchObjectType(clTButton); - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); + if (pad_painter.getSnapId() && this.getWebsocket()) + arg = { _typename: 'TWebPadClick', padid: pad_painter.getSnapId(), objid: '', x: -1, y: -1, dbl: false }; - const nbins = Math.min(this.nbinsx, this.nbinsy); + if (!pad_painter.is_active_pad && !is_button) { + ischanged = true; + this.forEachPainterInPad(pp => pp.drawActiveBorder(null, pp === pad_painter), 'pads'); + } - this.startTextDrawing(42, text_size, this.draw_g); + if (obj_painter?.hasSnapId() && arg) { + ischanged = true; + arg.objid = obj_painter.getSnapId(); + } - const pnts = []; + if (click_pos && arg) { + ischanged = true; + arg.x = Math.round(click_pos.x || 0); + arg.y = Math.round(click_pos.y || 0); + if (click_pos.dbl) + arg.dbl = true; + } - for (let n = 0; n < nbins; n++) { - const a = (0.5 - n/nbins)*Math.PI*2, - cx = Math.round((0.9*rect.width/2 - 2*circle_size) * Math.cos(a)), - cy = Math.round((0.9*rect.height/2 - 2*circle_size) * Math.sin(a)), - x = Math.round(0.9*rect.width/2 * Math.cos(a)), - y = Math.round(0.9*rect.height/2 * Math.sin(a)), - color = palette?.calcColor(n, nbins) ?? 'black'; - let rotate = Math.round(a/Math.PI*180), align = 12; + if (arg && (ischanged || is_button)) + this.sendWebsocket('PADCLICKED:' + toJSON(arg)); + } - pnts.push({ x: cx, y: cy, a, color }); // remember points coordinates + /** @summary Return actual TCanvas status bits */ + getStatusBits() { + let bits = 0; + if (this.hasEventStatus()) + bits |= kShowEventStatus; + if (this.hasGed()) + bits |= kShowEditor; + if (this.isTooltipAllowed()) + bits |= kShowToolTips; + if (this.use_openui) + bits |= kMenuBar; + return bits; + } - if ((rotate < -90) || (rotate > 90)) { rotate += 180; align = 32; } + /** @summary produce JSON for TCanvas, which can be used to display canvas once again */ + produceJSON(spacing) { + const canv = this.getObject(); - const s2 = Math.round(text_size/2), s1 = 2*s2; + if (canv._typename !== clTCanvas) + return; - this.draw_g.append('path') - .attr('d', `M${cx-s2},${cy} a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,${-s1},0z`) - .style('stroke', color) - .style('fill', 'none'); + const fill0 = (canv.fFillStyle === 0), + axes = [], hists = [], prims = []; - this.drawText({ align, rotate, x, y, text: getBinLabel(n) }); - } + if (fill0) + canv.fFillStyle = 1001; - const max_width = circle_size/2; - let max_value = 0, min_value = 0; - if (this.options.Circular > 11) { - for (let i = 0; i < nbins - 1; ++i) { - for (let j = i+1; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); - if (cont > 0) { - max_value = Math.max(max_value, cont); - if (!min_value || (cont < min_value)) min_value = cont; - } - } + this.forEachPainterInPad(pp => { + const pad = pp.getRootPad(true); + if (pp.getNumPainters() && pad?.fPrimitives && !pad.fPrimitives.arr.length) { + // create list of primitives when missing + prims.push(pad.fPrimitives); + pp.forEachPainterInPad(p => { + // ignore all secondary painters + if (p.isSecondary()) + return; + const subobj = p.getObject(); + if (subobj?._typename) + pad.fPrimitives.Add(subobj, p.getDrawOpt()); + }, 'objects'); } - } - - for (let i = 0; i < nbins-1; ++i) { - const pi = pnts[i]; - let path = ''; - for (let j = i+1; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); - if (cont <= 0) continue; - - const pj = pnts[j], - a = (pi.a + pj.a)/2, - qr = 0.5*(1-Math.abs(pi.a - pj.a)/Math.PI), // how far Q point will be away from center - qx = Math.round(qr*rect.width/2 * Math.cos(a)), - qy = Math.round(qr*rect.height/2 * Math.sin(a)); + const main = pp.getMainPainter(), + fp = pp.getFramePainter(); + if (!isFunc(main?.getHisto) || !isFunc(main?.getDimension)) + return; - path += `M${pi.x},${pi.y}Q${qx},${qy},${pj.x},${pj.y}`; + // write selected range into TAxis properties + const hist = main.getHisto(), + ndim = main.getDimension(); + if (!hist?.fXaxis) + return; - if ((this.options.Circular > 11) && (max_value > min_value)) { - const width = Math.round((cont - min_value) / (max_value - min_value) * (max_width - 1) + 1); - this.draw_g.append('path').attr('d', path).style('stroke', pi.color).style('stroke-width', width).style('fill', 'none'); - path = ''; + const setAxisRange = (name, axis) => { + if (fp?.zoomChangedInteractive(name)) { + axes.push({ axis, f: axis.fFirst, l: axis.fLast, b: axis.fBits }); + axis.fFirst = main.getSelectIndex(name, 'left', 1); + axis.fLast = main.getSelectIndex(name, 'right'); + axis.SetBit(EAxisBits.kAxisRange, (axis.fFirst > 0) || (axis.fLast < axis.fNbins)); } + }; + + setAxisRange('x', hist.fXaxis); + if (ndim > 1) + setAxisRange('y', hist.fYaxis); + if (ndim > 2) + setAxisRange('z', hist.fZaxis); + if ((ndim === 2) && fp?.zoomChangedInteractive('z')) { + hists.push({ hist, min: hist.fMinimum, max: hist.fMaximum }); + hist.fMinimum = fp.zoom_zmin ?? fp.zmin; + hist.fMaximum = fp.zoom_zmax ?? fp.zmax; } - if (path) - this.draw_g.append('path').attr('d', path).style('stroke', pi.color).style('fill', 'none'); - } + }, 'pads'); - return this.finishTextDrawing(); - } + const res = toJSON(canv, spacing); - /** @summary Draw histogram bins as chord diagram */ - async drawBinsChord() { - this.getFrameSvg().style('display', 'none'); - this._hide_frame = true; + if (fill0) + canv.fFillStyle = 0; - const used = [], - nbins = Math.min(this.nbinsx, this.nbinsy), - hist = this.getHisto(); - let fullsum = 0, isint = true; - for (let i = 0; i < nbins; ++i) { - let sum = 0; - for (let j = 0; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); - if (cont > 0) { - sum += cont; - if (isint && (Math.round(cont) !== cont)) isint = false; - } - } - if (sum > 0) used.push(i); - fullsum += sum; - } + axes.forEach(e => { + e.axis.fFirst = e.f; + e.axis.fLast = e.l; + e.axis.fBits = e.b; + }); - // do not show less than 2 elements - if (used.length < 2) return true; + hists.forEach(e => { + e.hist.fMinimum = e.min; + e.hist.fMaximum = e.max; + }); - let ndig = 0, tickStep = 1; - const rect = this.getPadPainter().getFrameRect(), - palette = this.getHistPalette(), - outerRadius = Math.max(10, Math.min(rect.width, rect.height) * 0.5 - 60), - innerRadius = Math.max(2, outerRadius - 10), - data = [], labels = [], - getColor = indx => palette.calcColor(indx, used.length), - formatValue = v => v.toString(), - formatTicks = v => ndig > 3 ? v.toExponential(0) : v.toFixed(ndig), - d3_descending = (a, b) => { return b < a ? -1 : b > a ? 1 : b >= a ? 0 : Number.NaN; }; + prims.forEach(lst => lst.Clear()); - if (!isint && fullsum < 10) { - const lstep = Math.round(Math.log10(fullsum) - 2.3); - ndig = -lstep; - tickStep = Math.pow(10, lstep); - } else if (fullsum > 200) { - const lstep = Math.round(Math.log10(fullsum) - 2.3); - tickStep = Math.pow(10, lstep); - } + return res; + } - if (tickStep * 250 < fullsum) - tickStep *= 5; - else if (tickStep * 100 < fullsum) - tickStep *= 2; + /** @summary resize browser window */ + resizeBrowser(fullW, fullH) { + if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) + return; - for (let i = 0; i < used.length; ++i) { - data[i] = []; - for (let j = 0; j < used.length; ++j) - data[i].push(hist.getBinContent(used[i]+1, used[j]+1)); - const axis = hist.fXaxis; - let lbl = 'indx_' + used[i].toString(); - if (axis.fLabels) { - for (let k = 0; k < axis.fLabels.arr.length; ++k) { - const tstr = axis.fLabels.arr[k]; - if (tstr.fUniqueID === used[i]+1) { lbl = tstr.fString; break; } - } - } - labels.push(lbl); + // workaround for qt-based display where inner window size is used + if (browser.qt6 && fullW > 100 && fullH > 60) { + fullW -= 3; + fullH -= 30; } - this.createG(); - - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); + this.getWebsocket()?.resizeWindow(fullW, fullH); + } - const chord$1 = chord() - .padAngle(10 / innerRadius) - .sortSubgroups(d3_descending) - .sortChords(d3_descending), + /** @summary create three.js object for TCanvas */ + static async build3d(can, opt, get_painter) { + const painter = new TCanvasPainter(null, can, opt, true); + painter.checkSpecialsInPrimitives(can, true); - chords = chord$1(data), + const fp = new TFramePainter(null, null); + // return dummy frame painter as result + painter.getFramePainter = () => fp; - group = this.draw_g.append('g') - .attr('font-size', 10) - .attr('font-family', 'sans-serif') - .selectAll('g') - .data(chords.groups) - .join('g'), + return painter.drawPrimitives().then(() => { + return get_painter ? painter : fp.create3DScene(-1, true); + }); + } - arc$1 = arc().innerRadius(innerRadius).outerRadius(outerRadius), + /** @summary draw TCanvas */ + static async draw(dom, can, opt) { + const nocanvas = !can; + if (nocanvas) + can = create$1(clTCanvas); - ribbon = ribbon$1().radius(innerRadius - 1).padAngle(1 / innerRadius); + const painter = new TCanvasPainter(dom, can, opt, nocanvas ? 'auto' : true); + painter.checkSpecialsInPrimitives(can, true); - function ticks({ startAngle, endAngle, value }) { - const k = (endAngle - startAngle) / value, - arr = []; - for (let z = 0; z <= value; z += tickStep) - arr.push({ value: z, angle: z * k + startAngle }); - return arr; + if (!nocanvas && can.fCw && can.fCh) { + const d = painter.selectDom(); + let apply_size; + if (!painter.isBatchMode()) { + const rect0 = d.node().getBoundingClientRect(); + apply_size = !rect0.height && (rect0.width > 0.1 * can.fCw); + } else { + const arg = d.property('_batch_use_canvsize'); + apply_size = arg || (arg === undefined); + } + if (apply_size) { + d.style('width', can.fCw + 'px').style('height', can.fCh + 'px') + .attr('width', can.fCw).attr('height', can.fCh); + painter._setFixedSize(true); + } } - group.append('path') - .attr('fill', d => getColor(d.index)) - .attr('d', arc$1); + painter.createCanvasSvg(0); - group.append('title').text(d => `${labels[d.index]} ${formatValue(d.value)}`); + painter.addPadButtons(); - const groupTick = group.append('g') - .selectAll('g') - .data(ticks) - .join('g') - .attr('transform', d => `rotate(${Math.round(d.angle*180/Math.PI-90)}) translate(${outerRadius})`); - groupTick.append('line') - .attr('stroke', 'currentColor') - .attr('x2', 6); + if (nocanvas && opt.indexOf('noframe') < 0) + directDrawTFrame(painter, null); - groupTick.append('text') - .attr('x', 8) - .attr('dy', '0.35em') - .attr('transform', d => d.angle > Math.PI ? 'rotate(180) translate(-16)' : null) - .attr('text-anchor', d => d.angle > Math.PI ? 'end' : null) - .text(d => formatTicks(d.value)); + // select global reference - required for keys handling + selectActivePad({ pp: painter, active: true }); - group.select('text') - .attr('font-weight', 'bold') - .text(function(d) { - return this.getAttribute('text-anchor') === 'end' ? `↑ ${labels[d.index]}` : `${labels[d.index]} ↓`; - }); + return painter.drawPrimitives().then(() => { + painter.addPadInteractive(); + painter.showPadButtons(); + return painter; + }); + } - this.draw_g.append('g') - .attr('fill-opacity', 0.8) - .selectAll('path') - .data(chords) - .join('path') - .style('mix-blend-mode', 'multiply') - .attr('fill', d => getColor(d.source.index)) - .attr('d', ribbon) - .append('title') - .text(d => `${formatValue(d.source.value)} ${labels[d.target.index]} → ${labels[d.source.index]}${d.source.index === d.target.index ? '' : `\n${formatValue(d.target.value)} ${labels[d.source.index]} → ${labels[d.target.index]}`}`); +} // class TCanvasPainter - return true; - } - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(i, j) { - const histo = this.getHisto(); - let binz = histo.getBinContent(i+1, j+1); +/** @summary Ensure TCanvas and TFrame for the painter object + * @param {Object} painter - painter object to process + * @param {string|boolean} frame_kind - false for no frame or '3d' for special 3D mode + * @desc Assign dom, creates TCanvas if necessary, add to list of pad painters */ +async function ensureTCanvas(painter, frame_kind) { + if (!painter) + return Promise.reject(Error('Painter not provided in ensureTCanvas')); - if (histo.$baseh) - binz -= histo.$baseh.getBinContent(i+1, j+1); + // simple check - if canvas there, can use painter + const noframe = (frame_kind === false) || (frame_kind === '3d') ? 'noframe' : '', + createCanv = () => { + if ((noframe !== 'noframe') || !isFunc(painter.getUserRanges)) + return null; + const ranges = painter.getUserRanges(); + if (!ranges) + return null; + const canv = create$1(clTCanvas), + dx = (ranges.maxx - ranges.minx) || 1, + dy = (ranges.maxy - ranges.miny) || 1; + canv.fX1 = ranges.minx - dx * gStyle.fPadLeftMargin; + canv.fX2 = ranges.maxx + dx * gStyle.fPadRightMargin; + canv.fY1 = ranges.miny - dy * gStyle.fPadBottomMargin; + canv.fY2 = ranges.maxy + dy * gStyle.fPadTopMargin; + return canv; + }, + pad_painter = painter.getPadPainter() || getDomCanvasPainter(painter.selectDom()), + promise = pad_painter ? Promise.resolve(pad_painter) : + TCanvasPainter.draw(painter.getDom(), createCanv(), noframe); - const lines = [this.getObjectHint(), - 'x = ' + this.getAxisBinTip('x', histo.fXaxis, i), - 'y = ' + this.getAxisBinTip('y', histo.fYaxis, j), - `bin = ${histo.getBin(i+1, j+1)} x: ${i+1} y: ${j+1}`, - 'entries = ' + ((binz === Math.round(binz)) ? binz : floatToString(binz, gStyle.fStatFormat))]; + return promise.then(pp => { + if ((frame_kind !== false) && pp.getFrameSvg().selectChild('.main_layer').empty() && !pp.getFramePainter()) + directDrawTFrame(pp, null, frame_kind); - if ((this.options.TextKind === 'E') || this.matchObjectType(clTProfile2D)) { - const errz = histo.getBinError(histo.getBin(i+1, j+1)); - lines.push('error = ' + ((errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat))); - } + painter.addToPadPrimitives(pp); + return painter; + }); +} - return lines; - } +/** @summary draw TPad snapshot from TWebCanvas + * @private */ +async function drawTPadSnapshot(dom, snap, opt) { + const can = create$1(clTCanvas), + painter = new TCanvasPainter(dom, can, opt); + painter.addPadButtons(); - /** @summary Provide text information (tooltips) for candle bin */ - getCandleTooltips(p) { - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - histo = this.getHisto(); + return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { + painter.confirmDraw(); + painter.showPadButtons(); + return painter; + }); +} - return [this.getObjectHint(), - p.swapXY - ? 'y = ' + funcs.axisAsText('y', histo.fYaxis.GetBinLowEdge(p.bin+1)) - : 'x = ' + funcs.axisAsText('x', histo.fXaxis.GetBinLowEdge(p.bin+1)), - 'm-25% = ' + floatToString(p.fBoxDown, gStyle.fStatFormat), - 'median = ' + floatToString(p.fMedian, gStyle.fStatFormat), - 'm+25% = ' + floatToString(p.fBoxUp, gStyle.fStatFormat)]; - } +/** @summary draw TFrame object + * @private */ +async function drawTFrame(dom, obj, opt) { + const fp = new TFramePainter(dom, obj); + fp.mode3d = opt === '3d'; + return ensureTCanvas(fp, false).then(() => fp.redraw()); +} - /** @summary Provide text information (tooltips) for poly bin */ - getPolyBinTooltips(binindx, realx, realy) { - const histo = this.getHisto(), - bin = histo.fBins.arr[binindx], - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - lines = []; - let binname = bin.fPoly.fName, numpoints = 0; +Object.assign(internals.jsroot, { ensureTCanvas, TPadPainter, TCanvasPainter }); - if (binname === 'Graph') binname = ''; - if (binname.length === 0) binname = bin.fNumber; +var TCanvasPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TCanvasPainter: TCanvasPainter, +TPadPainter: TPadPainter, +drawTFrame: drawTFrame, +drawTPadSnapshot: drawTPadSnapshot, +ensureTCanvas: ensureTCanvas +}); - if ((realx === undefined) && (realy === undefined)) { - realx = realy = 0; - let gr = bin.fPoly, numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } +const kTakeStyle = BIT(17), kPosTitle = 'postitle', kAutoPlace = 'autoplace', kDefaultDrawOpt = 'brNDC'; - for (let ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; +/** @summary Returns true if stat box on default place and can be adjusted + * @private */ +function isDefaultStatPosition(pt) { + const test = (v1, v2) => (Math.abs(v1 - v2) < 1e-3); + return test(pt.fX1NDC, gStyle.fStatX - gStyle.fStatW) && + test(pt.fY1NDC, gStyle.fStatY - gStyle.fStatH) && + test(pt.fX2NDC, gStyle.fStatX) && + test(pt.fY2NDC, gStyle.fStatY); +} - for (let n = 0; n < gr.fNpoints; ++n) { - ++numpoints; - realx += gr.fX[n]; - realy += gr.fY[n]; - } - } +/** + * @summary painter for TPave-derived classes + * + * @private + */ - if (numpoints > 1) { - realx = realx / numpoints; - realy = realy / numpoints; - } - } +class TPavePainter extends ObjectPainter { - lines.push(this.getObjectHint(), - 'x = ' + funcs.axisAsText('x', realx), - 'y = ' + funcs.axisAsText('y', realy)); - if (numpoints > 0) - lines.push('npnts = ' + numpoints); - lines.push(`bin = ${binname}`); - if (bin.fContent === Math.round(bin.fContent)) - lines.push('content = ' + bin.fContent); - else - lines.push('content = ' + floatToString(bin.fContent, gStyle.fStatFormat)); - return lines; + #pave_x; // x position of pave + #pave_y; // y position of pave + #palette_vertical; // when palette drawing vertical + #swap_side; // swap palette side + #has_fit; // has fit info + #fit_dim; // dimension of fit function + #fit_cnt; // lines number in fit info + + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} pave - TPave-based object */ + constructor(dom, pave, opt) { + super(dom, pave, opt); + this.Enabled = true; + this.UseContextMenu = true; } - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const histo = this.getHisto(), - h = this.tt_handle; - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + /** @summary Auto place legend on the frame + * @return {Promise} with boolean flag if position was changed */ + async autoPlaceLegend(pt, pad, keep_origin) { + const main_svg = this.getPadPainter().getFrameSvg().selectChild('.main_layer'); - if (!pnt || !this.draw_content || !this.draw_g || !h || this.options.Proj) { - ttrect?.remove(); - return null; - } + let svg_code = main_svg.node().outerHTML; - if (h.poly) { - // process tooltips from TH2Poly + svg_code = compressSVG(svg_code); - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - realx = funcs.revertAxis('x', pnt.x), - realy = funcs.revertAxis('y', pnt.y); - let foundindx = -1, bin; + svg_code = ` { + if (!canvas) + return false; - // found potential bins candidate - if ((realx < bin.fXmin) || (realx > bin.fXmax) || - (realy < bin.fYmin) || (realy > bin.fYmax)) continue; + let nX = 100, nY = 100; + const context = canvas.getContext('2d'), + arr = context.getImageData(0, 0, canvas.width, canvas.height).data, + boxW = Math.floor(canvas.width / nX), boxH = Math.floor(canvas.height / nY), + raster = new Array(nX * nY); - // ignore empty bins with col0 option - if (!bin.fContent && !this.options.Zero) continue; + if (arr.length !== canvas.width * canvas.height * 4) { + console.log(`Image size missmatch in TLegend autoplace ${arr.length} expected ${canvas.width * canvas.height * 4}`); + nX = nY = 0; + } - let gr = bin.fPoly, numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } + for (let ix = 0; ix < nX; ++ix) { + const px1 = ix * boxW, px2 = px1 + boxW; + for (let iy = 0; iy < nY; ++iy) { + const py1 = iy * boxH, py2 = py1 + boxH; + let filled = 0; - for (let ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; - if (gr.IsInside(realx, realy)) { - foundindx = i; - break; + for (let x = px1; (x < px2) && !filled; ++x) { + for (let y = py1; y < py2; ++y) { + const indx = (y * canvas.width + x) * 4; + if (arr[indx] || arr[indx + 1] || arr[indx + 2] || arr[indx + 3]) { + filled = 1; + break; + } } } + raster[iy * nX + ix] = filled; } } - if (foundindx < 0) { - ttrect.remove(); - return null; - } - - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - exact: true, menu: true, - lines: this.getPolyBinTooltips(foundindx, realx, realy) }; - - if (pnt.disabled) { - ttrect.remove(); - res.changed = true; - } else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } + const legWidth = 0.3 / Math.max(0.2, (1 - lm - rm)), + legHeight = Math.min(0.5, Math.max(0.1, pt.fPrimitives.arr.length * 0.05)) / Math.max(0.2, (1 - tm - bm)), + needW = Math.round(legWidth * nX), needH = Math.round(legHeight * nY), - res.changed = ttrect.property('current_bin') !== foundindx; + test = (x, y) => { + for (let ix = x; ix < x + needW; ++ix) { + for (let iy = y; iy < y + needH; ++iy) { + if (raster[iy * nX + ix]) + return false; + } + } + return true; + }; - if (res.changed) { - ttrect.attr('d', this.createPolyBin(funcs, bin)) - .style('opacity', '0.7') - .property('current_bin', foundindx); + for (let ix = 0; ix < (nX - needW); ++ix) { + for (let iy = nY - needH - 1; iy >= 0; --iy) { + if (test(ix, iy)) { + pt.fX1NDC = lm + ix / nX * (1 - lm - rm); + pt.fX2NDC = pt.fX1NDC + legWidth * (1 - lm - rm); + pt.fY2NDC = 1 - tm - iy / nY * (1 - bm - tm); + pt.fY1NDC = pt.fY2NDC - legHeight * (1 - bm - tm); + return true; + } } } + }).then(res => { + if (res || keep_origin) + return res; - if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: foundindx, - cont: bin.fContent, - grx: pnt.x, gry: pnt.y }; - } - return res; - } else if (h.candle) { - // process tooltips for candle + pt.fX1NDC = Math.max(lm ?? 0, pt.fX2NDC - 0.3); + pt.fX2NDC = Math.min(pt.fX1NDC + 0.3, 1 - rm); + const h0 = Math.max(pt.fPrimitives ? pt.fPrimitives.arr.length * 0.05 : 0, 0.2); + pt.fY2NDC = Math.min(1 - tm, pt.fY1NDC + h0); + pt.fY1NDC = Math.max(pt.fY2NDC - h0, bm); + return true; + }); + } - let i, p, match; + /** @summary Get draw option for the pave + * @desc only stats using fOption directly, all other classes - stored in the pad */ + getPaveDrawOption() { + let opt = this.getDrawOpt(); + if (this.isStats() || !opt) + opt = this.getObject()?.fOption; + return opt || kDefaultDrawOpt; + } - for (i = 0; i < h.candle.length; ++i) { - p = h.candle[i]; - match = p.swapXY - ? ((p.x1 <= pnt.y) && (pnt.y <= p.x2) && (p.yy1 >= pnt.x) && (pnt.x >= p.yy2)) - : ((p.x1 <= pnt.x) && (pnt.x <= p.x2) && (p.yy1 <= pnt.y) && (pnt.y <= p.yy2)); - if (match) break; - } + /** @summary Change pave draw option */ + setPaveDrawOption(opt) { + if (this.isStats()) + this.getObject().fOption = opt; + else + this.storeDrawOpt(opt); + } - if (!match) { - ttrect.remove(); - return null; - } + /** @summary Draw pave and content + * @return {Promise} */ + async drawPave(arg) { + if (!this.Enabled) { + this.removeG(); + return this; + } + + const pt = this.getObject(), + opt = this.getPaveDrawOption().toUpperCase(), + fp = this.getFramePainter(), pp = this.getPadPainter(), + pad = pp.getRootPad(true); - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getCandleTooltips(p), exact: true, menu: true }; + // special handling of dummy frame painter + if (fp?.getDrawDom() === null) + return this; - if (pnt.disabled) { - ttrect.remove(); - res.changed = true; - } else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle) - .style('opacity', '0.7'); - } + let interactive_element, width, height; - res.changed = ttrect.property('current_bin') !== i; + if (pt.fInit === 0) { + this.stored = Object.assign({}, pt); // store coordinates to use them when updating + pt.fInit = 1; - if (res.changed) { - ttrect.attr('d', p.swapXY ? `M${p.yy1},${p.x1}H${p.yy2}V${p.x2}H${p.yy1}Z` : `M${p.x1},${p.yy1}H${p.x2}V${p.yy2}H${p.x1}Z`) - .property('current_bin', i); + if ((pt._typename === clTPaletteAxis) && !pt.fX1 && !pt.fX2 && !pt.fY1 && !pt.fY2) { + if (fp) { + pt.fX1NDC = fp.fX2NDC + 0.01; + pt.fX2NDC = Math.min(0.96, fp.fX2NDC + 0.06); + pt.fY1NDC = fp.fY1NDC; + pt.fY2NDC = fp.fY2NDC; + } else { + pt.fX2NDC = 0.8; + pt.fX1NDC = 0.9; + pt.fY1NDC = 0.1; + pt.fY2NDC = 0.9; + } + } else if (pt.fOption.indexOf('NDC') >= 0) { + // check if NDC was modified but fInit was not set + // wired - ROOT checks fOption even when absolutely different draw option may be specified, + // happens in stressGraphics.cxx, sg30 where stats box not initialized when call C->Update() in batch mode + if (pt.fX1NDC < 1e-20 && pt.fX2NDC < 1e-20) { + pt.fX1NDC = pt.fX1; + pt.fX2NDC = pt.fX2; + } + if (pt.fY1NDC < 1e-20 && pt.fY2NDC < 1e-20) { + pt.fY1NDC = pt.fY1; + pt.fY2NDC = pt.fY2; + } + } else if (pad && (pad.fX1 === 0) && (pad.fX2 === 1) && (pad.fY1 === 0) && (pad.fY2 === 1) && isStr(arg) && (arg.indexOf('postpone') >= 0)) { + // special case when pad not yet initialized + pt.fInit = 0; // do not init until axes drawn + pt.fX1NDC = pt.fY1NDC = 0.99; + pt.fX2NDC = pt.fY2NDC = 1; + } else if (pad) { + if (pad.fLogx) { + if (pt.fX1 > 0) + pt.fX1 = Math.log10(pt.fX1); + if (pt.fX2 > 0) + pt.fX2 = Math.log10(pt.fX2); + } + if (pad.fLogy) { + if (pt.fY1 > 0) + pt.fY1 = Math.log10(pt.fY1); + if (pt.fY2 > 0) + pt.fY2 = Math.log10(pt.fY2); } + pt.fX1NDC = (pt.fX1 - pad.fX1) / (pad.fX2 - pad.fX1); + pt.fY1NDC = (pt.fY1 - pad.fY1) / (pad.fY2 - pad.fY1); + pt.fX2NDC = (pt.fX2 - pad.fX1) / (pad.fX2 - pad.fX1); + pt.fY2NDC = (pt.fY2 - pad.fY1) / (pad.fY2 - pad.fY1); + } else { + pt.fX1NDC = pt.fY1NDC = 0.1; + pt.fX2NDC = pt.fY2NDC = 0.9; } + } - if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: i+1, cont: p.fMedian, binx: i+1, biny: 1, - grx: pnt.x, gry: pnt.y }; - } + let promise = Promise.resolve(true); - return res; + if ((pt._typename === clTLegend) && (this.AutoPlace || ((pt.fX1NDC === pt.fX2NDC) && (pt.fY1NDC === pt.fY2NDC)))) { + promise = this.autoPlaceLegend(pt, pad).then(res => { + delete this.AutoPlace; + if (!res) { + pt.fX1NDC = fp.fX2NDC - 0.2; + pt.fX2NDC = fp.fX2NDC; + pt.fY1NDC = fp.fY2NDC - 0.1; + pt.fY2NDC = fp.fY2NDC; + } + return res; + }); } - const pmain = this.getFramePainter(); - let i, j, binz = 0, colindx = null, - i1, i2, j1, j2, x1, x2, y1, y2; + return promise.then(() => { + // fill stats before drawing to have coordinates early + if (this.isStats() && !this.NoFillStats && !pp.isFastDrawing()) { + const main = pt.$main_painter || this.getMainPainter(); - // search bins position - if (pmain.reverse_x) { - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x <= h.grx[i]) && (pnt.x >= h.grx[i+1])) break; - } else { - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x >= h.grx[i]) && (pnt.x <= h.grx[i+1])) break; - } + if (isFunc(main?.fillStatistic)) { + let dostat = pt.fOptStat, dofit = pt.fOptFit; + if (pt.TestBit(kTakeStyle) || !Number.isInteger(dostat)) + dostat = gStyle.fOptStat; + if (pt.TestBit(kTakeStyle) || !Number.isInteger(dofit)) + dofit = gStyle.fOptFit; - if (pmain.reverse_y) { - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y <= h.gry[j+1]) && (pnt.y >= h.gry[j])) break; - } else { - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y >= h.gry[j+1]) && (pnt.y <= h.gry[j])) break; - } + // we take statistic from main painter + if (main.fillStatistic(this, dostat, dofit)) { + // adjust the size of the stats box with the number of lines + let nlines = pt.fLines?.arr.length || 0; + const set_default = (nlines > 0) && !this.moved_interactive && isDefaultStatPosition(pt), + // in ROOT TH2 and TH3 always add full stats for fit parameters + extrah = this.#has_fit && (this.#fit_dim > 1) ? gStyle.fStatH : 0; + if (extrah) + nlines -= this.#fit_cnt; + let stath = gStyle.fStatH, statw = gStyle.fStatW; + if (this.#has_fit) + statw = 1.8 * gStyle.fStatW; + if ((gStyle.fStatFontSize <= 0) || (gStyle.fStatFont % 10 === 3)) + stath = nlines * 0.25 * gStyle.fStatH; + else if (gStyle.fStatFontSize < 1) + stath = nlines * gStyle.fStatFontSize; + + if (set_default) { + // but fit parameters not used in full size calculations + pt.fX1NDC = Math.max(0.005, pt.fX2NDC - statw); + pt.fY1NDC = Math.max(0.005, pt.fY2NDC - stath - extrah); + } else { + // when some NDC values are set directly and not match with each other + if (pt.fY1NDC > pt.fY2NDC) + pt.fY2NDC = Math.min(0.995, pt.fY1NDC + stath + extrah); + if (pt.fX1NDC > pt.fX2NDC) + pt.fY2NDC = Math.min(0.995, pt.fX1NDC + statw); + } + } + } + } - if ((i < h.i2) && (j < h.j2)) { - i1 = i; i2 = i+1; j1 = j; j2 = j+1; - x1 = h.grx[i1]; x2 = h.grx[i2]; - y1 = h.gry[j2]; y2 = h.gry[j1]; + const pad_rect = pp.getPadRect(), + brd = pt.fBorderSize, + noborder = opt.indexOf('NB') >= 0, + dx = (opt.indexOf('L') >= 0) ? -1 : ((opt.indexOf('R') >= 0) ? 1 : 0), + dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0), + g = this.createG(); // container used to recalculate coordinates - let match = true; + this.#pave_x = Math.round(pt.fX1NDC * pad_rect.width); + this.#pave_y = Math.round((1.0 - pt.fY2NDC) * pad_rect.height); + width = Math.round((pt.fX2NDC - pt.fX1NDC) * pad_rect.width); + height = Math.round((pt.fY2NDC - pt.fY1NDC) * pad_rect.height); - if (this.options.Color) { - // take into account bar settings - const dx = x2 - x1, dy = y2 - y1; - x2 = Math.round(x1 + dx*h.xbar2); - x1 = Math.round(x1 + dx*h.xbar1); - y2 = Math.round(y1 + dy*h.ybar2); - y1 = Math.round(y1 + dy*h.ybar1); - if (pmain.reverse_x) { - if ((pnt.x > x1) || (pnt.x <= x2)) match = false; - } else - if ((pnt.x < x1) || (pnt.x >= x2)) match = false; + const arc_radius = opt.indexOf('ARC') >= 0 && (pt.fCornerRadius > 0) ? Math.round(Math.min(width, height) * pt.fCornerRadius) : 0; - if (pmain.reverse_y) { - if ((pnt.y > y1) || (pnt.y <= y2)) match = false; - } else - if ((pnt.y < y1) || (pnt.y >= y2)) match = false; - } + makeTranslate(g, this.#pave_x, this.#pave_y); - binz = histo.getBinContent(i+1, j+1); - if (this.is_projection) - colindx = 0; // just to avoid hide - else if (!match) - colindx = null; - else if (h.hide_only_zeros) - colindx = (binz === 0) && !this._show_empty_bins ? null : 0; - else { - colindx = this.getContour().getPaletteIndex(this.getHistPalette(), binz); - if ((colindx === null) && (binz === 0) && this._show_empty_bins) colindx = 0; - } - } + this.createAttLine({ attr: pt, width: (brd > 0) ? pt.fLineWidth : 0 }); - if (colindx === null) { - ttrect.remove(); - return null; - } + this.createAttFill({ attr: pt }); + // need to fill pave while + if (this.fillatt.empty() && arc_radius) + this.fillatt.setSolidColor(this.getColor(pt.fFillColor) || 'white'); - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(i, j), exact: true, menu: true }; + if (pt._typename === clTDiamond) { + const h2 = Math.round(height / 2), w2 = Math.round(width / 2), + dpath = `l${w2},${-h2}l${w2},${h2}l${-w2},${h2}z`; - if (this.options.Color) res.color2 = this.getHistPalette().getColor(colindx); + if (!this.fillatt.empty()) + this.drawBorder(g, width, height, 0, dpath); - if (pnt.disabled && !this.is_projection) { - ttrect.remove(); - res.changed = true; - } else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } + interactive_element = g.append('svg:path') + .attr('d', 'M0,' + h2 + dpath) + .call(this.fillatt.func) + .call(this.lineatt.func); - let binid = i*10000 + j, path; + const text_g = g.append('svg:g'); + makeTranslate(text_g, Math.round(width / 4), Math.round(height / 4)); - if (this.is_projection) { - const pwx = this.projection_widthX || 1, ddx = (pwx - 1) / 2; - if ((this.is_projection.indexOf('X')) >= 0 && (pwx > 1)) { - if (j2+ddx >= h.j2) { - j2 = Math.min(Math.round(j2+ddx), h.j2); - j1 = Math.max(j2-pwx, h.j1); - } else { - j1 = Math.max(Math.round(j1-ddx), h.j1); - j2 = Math.min(j1+pwx, h.j2); - } + return this.drawPaveText(w2, h2, arg, text_g); + } + + if (pt.fNpaves) { + for (let n = pt.fNpaves - 1; n > 0; --n) { + g.append('svg:path') + .attr('d', `M${dx * 4 * n},${dy * 4 * n}h${width}v${height}h${-width}z`) + .call(this.fillatt.func) + .call(this.lineatt.func); } - const pwy = this.projection_widthY || 1, ddy = (pwy - 1) / 2; - if ((this.is_projection.indexOf('Y')) >= 0 && (pwy > 1)) { - if (i2+ddy >= h.i2) { - i2 = Math.min(Math.round(i2+ddy), h.i2); - i1 = Math.max(i2-pwy, h.i1); - } else { - i1 = Math.max(Math.round(i1-ddy), h.i1); - i2 = Math.min(i1+pwy, h.i2); - } + } else + this.drawBorder(g, width, height, arc_radius); + + if (!this.isBatchMode() || !this.fillatt.empty() || (!this.lineatt.empty() && !noborder)) { + if (arc_radius) { + interactive_element = g.append('svg:rect') + .attr('width', width) + .attr('height', height) + .attr('rx', arc_radius); + } else { + interactive_element = g.append('svg:path') + .attr('d', `M0,0H${width}V${height}H0Z`); } + interactive_element.call(this.fillatt.func); + if (!noborder) + interactive_element.call(this.lineatt.func); } - if (this.is_projection === 'X') { - x1 = 0; x2 = pmain.getFrameWidth(); - y1 = h.gry[j2]; y2 = h.gry[j1]; - binid = j1*777 + j2*333; - } else if (this.is_projection === 'Y') { - y1 = 0; y2 = pmain.getFrameHeight(); - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*777 + i2*333; - } else if (this.is_projection === 'XY') { - y1 = h.gry[j2]; y2 = h.gry[j1]; - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*789 + i2*653 + j1*12345 + j2*654321; - path = `M${x1},0H${x2}V${y1}H${pmain.getFrameWidth()}V${y2}H${x2}V${pmain.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; + switch (pt._typename) { + case clTPaveLabel: + case clTPaveClass: + return this.drawPaveLabel(width, height, arg); + case clTPaveStats: + return this.drawPaveStats(width, height, arg); + case clTPaveText: + case clTPavesText: + case clTDiamond: + return this.drawPaveText(width, height, arg); + case clTLegend: + return this.drawLegend(width, height, arg); + case clTPaletteAxis: + return this.drawPaletteAxis(width, height, arg); } + }).then(() => { + if (this.isBatchMode() || (pt._typename === clTPave)) + return this; - res.changed = ttrect.property('current_bin') !== binid; - - if (res.changed) { - ttrect.attr('d', path || `M${x1},${y1}H${x2}V${y2}H${x1}Z`) - .style('opacity', '0.7') - .property('current_bin', binid); - } + // here all kind of interactive settings + interactive_element?.style('pointer-events', 'visibleFill') + .on('mouseenter', () => this.showObjectStatus()); + + addDragHandler(this, { + obj: pt, x: this.#pave_x, y: this.#pave_y, width, height, + minwidth: 10, minheight: 20, canselect: true, + ctxmenu: browser.touches && settings.ContextMenu && this.UseContextMenu, + redraw: () => { + this.moved_interactive = true; + this.interactiveRedraw(false, 'pave_moved').then(() => this.drawPave()); + } + }); - if (this.is_projection && res.changed) - this.redrawProjection(i1, i2, j1, j2); - } + if (this.UseContextMenu && settings.ContextMenu) + this.getG().on('contextmenu', evnt => this.paveContextMenu(evnt)); - if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1, - grx: pnt.x, gry: pnt.y }; - } + if (this.isPalette()) + this.interactivePaletteAxis(width, height); - return res; + return this; + }); } - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - if ((axis === 'z') || this.options.Proj) - return true; - - let obj = this.getHisto(); - if (obj) obj = (axis === 'y') ? obj.fYaxis : obj.fXaxis; + drawBorder(draw_g, width, height, arc_radius, diamond) { + const pt = this.getObject(), + opt = this.getPaveDrawOption().toUpperCase().replaceAll('ARC', '').replaceAll('NDC', ''), + noborder = this.isPalette() || (opt.indexOf('NB') >= 0), + dx = (opt.indexOf('L') >= 0) ? -1 : ((opt.indexOf('R') >= 0) ? 1 : 0), + dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0); - return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); - } + if ((pt.fBorderSize < 2) || (pt.fShadowColor === 0) || (!dx && !dy) || noborder) + return; - /** @summary Complete paletted drawing */ - completePalette(pp) { - if (!pp) return true; + const scol = this.getColor(pt.fShadowColor), + brd = pt.fBorderSize, + brd_width = !this.lineatt.empty() && (this.lineatt.width > 2) ? `${this.lineatt.width - 1}px` : '1px'; - pp.$main_painter = this; - this.options.Zvert = pp._palette_vertical; + if (diamond) { + draw_g.append('svg:path') + .attr('d', `M0,${Math.round(height / 2) + brd}${diamond}`) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } else if (arc_radius) { + draw_g.append('svg:rect') + .attr('width', width) + .attr('height', height) + .attr('rx', arc_radius) + .attr('x', dx * brd) + .attr('y', dy * brd) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } else { + let spath; + + if ((dx < 0) && (dy < 0)) + spath = `M0,0v${height - brd - 1}h${1 - brd}v${2 - height}h${width - 2}v${brd - 1}z`; + else if ((dx < 0) && (dy > 0)) + spath = `M0,${height}v${brd + 1 - height}h${1 - brd}v${height - 2}h${width - 2}v${1 - brd}z`; + else if ((dx > 0) && (dy < 0)) + spath = `M${brd + 1},0v${1 - brd}h${width - 2}v${height - 2}h${1 - brd}v${brd + 1 - height}z`; + else + spath = `M${width},${brd + 1}h${brd - 1}v${height - 2}h${2 - width}v${1 - brd}h${width - brd - 2}z`; - // redraw palette till the end when contours are available - return pp.drawPave(this.options.Cjust ? 'cjust' : ''); + draw_g.append('svg:path') + .attr('d', spath) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } } - /** @summary Performs 2D drawing of histogram - * @return {Promise} when ready */ - async draw2D(/* reason */) { - this.clear3DScene(); - - const need_palette = this.options.Zscale && this.options.canHavePalette(); + /** @summary Fill option object used in TWebCanvas */ + fillWebObjectOptions(res) { + const pave = this.getObject(); - // draw new palette, resize frame if required - return this.drawColorPalette(need_palette, true).then(async pp => { - let pr; - if (this.options.Circular && this.isMainPainter()) - pr = this.drawBinsCircular(); - else if (this.options.Chord && this.isMainPainter()) - pr = this.drawBinsChord(); - else - pr = this.drawAxes().then(() => this.draw2DBins()); + if (pave?.fInit) { + res.fcust = 'pave'; + res.fopt = [pave.fX1NDC, pave.fY1NDC, pave.fX2NDC, pave.fY2NDC]; - return pr.then(() => this.completePalette(pp)); - }).then(() => this.updateFunctions()) - .then(() => this.updateHistTitle()) - .then(() => { - this.updateStatWebCanvas(); - return this.addInteractivity(); - }); - } + if ((pave.fName === 'stats') && this.isStats()) { + pave.fLines.arr.forEach(entry => { + if ((entry._typename === clTText) || (entry._typename === clTLatex)) + res.fcust += `;;${entry.fTitle}`; + }); + } + } - /** @summary Should performs 3D drawing of histogram - * @desc Disabled in 2D case. just draw default draw options - * @return {Promise} when ready */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/TH2Painter.mjs'); - return this.draw2D(reason); + return res; } - /** @summary Call drawing function depending from 3D mode */ - async callDrawFunc(reason) { - const main = this.getMainPainter(), - fp = this.getFramePainter(); + /** @summary draw TPaveLabel object */ + async drawPaveLabel(width, height) { + const pave = this.getObject(); + if (!pave.fLabel || !pave.fLabel.trim()) + return this; - if ((main !== this) && fp && (fp.mode3d !== this.options.Mode3D)) - this.copyOptionsFrom(main); + this.createAttText({ attr: pave, can_rotate: false }); - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); + return this.startTextDrawingAsync(this.textatt.font, height / 1.2) + .then(() => this.drawText(this.textatt.createArg({ width, height, text: pave.fLabel, norotate: true }))) + .then(() => this.finishTextDrawing()); } - /** @summary Redraw histogram */ - async redraw(reason) { - return this.callDrawFunc(reason); - } + /** @summary draw TPaveStats object */ + drawPaveStats(width, height) { + const pt = this.getObject(), lines = [], colors = []; + let first_stat = 0, num_cols = 0, maxlen = 0; - /** @summary draw TH2 object */ - static async draw(dom, histo, opt) { - return THistPainter._drawHist(new TH2Painter(dom, histo), opt); - } + // extract only text + for (let j = 0; j < pt.fLines.arr.length; ++j) { + const entry = pt.fLines.arr[j]; + if ((entry._typename === clTText) || (entry._typename === clTLatex)) { + lines.push(entry.fTitle); + colors.push(entry.fTextColor); + } + } -}; // class TH2Painter + const nlines = lines.length; -function createTextGeometry(painter, lbl, size) { - if (isPlainText(lbl)) - return new TextGeometry(translateLaTeX(lbl), { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); + // adjust font size + for (let j = 0; j < nlines; ++j) { + const line = lines[j]; + if (j > 0) + maxlen = Math.max(maxlen, line.length); + if ((j === 0) || (line.indexOf('|') < 0)) + continue; + if (first_stat === 0) + first_stat = j; + const parts = line.split('|'); + if (parts.length > num_cols) + num_cols = parts.length; + } - const font_size = size * 100, geoms = []; - let stroke_width = 5; + // for characters like 'p' or 'y' several more pixels required to stay in the box when drawn in last line + const stepy = height / nlines, margin_x = pt.fMargin * width; + let has_head = false; - class TextParseWrapper { + this.createAttText({ attr: pt, can_rotate: false }); - constructor(kind, parent) { - this.kind = kind ?? 'g'; - this.childs = []; - this.x = 0; - this.y = 0; - this.font_size = parent?.font_size ?? font_size; - parent?.childs.push(this); - } + return this.startTextDrawingAsync(this.textatt.font, height / (nlines * 1.2)).then(() => { + if (nlines === 1) + this.drawText(this.textatt.createArg({ width, height, text: lines[0], latex: 1, norotate: true })); + else { + for (let j = 0; j < nlines; ++j) { + const y = j * stepy, + color = (colors[j] > 1) ? this.getColor(colors[j]) : this.textatt.color; + + if (first_stat && (j >= first_stat)) { + const parts = lines[j].split('|'); + for (let n = 0; n < parts.length; ++n) { + this.drawText({ + align: 'middle', x: width * n / num_cols, y, latex: 0, + width: width / num_cols, height: stepy, text: parts[n], color + }); + } + } else if (lines[j].indexOf('=') < 0) { + if (j === 0) { + has_head = true; + const max_hlen = Math.max(maxlen, Math.round((width - 2 * margin_x) / stepy / 0.65)); + if (lines[j].length > max_hlen + 5) + lines[j] = lines[j].slice(0, max_hlen + 2) + '...'; + } + this.drawText({ + align: (j === 0) ? 'middle' : 'start', x: margin_x, y, + width: width - 2 * margin_x, height: stepy, text: lines[j], color + }); + } else { + const parts = lines[j].split('='), args = []; + + for (let n = 0; n < 2; ++n) { + const arg = { + align: (n === 0) ? 'start' : 'end', x: margin_x, y, + width: width - 2 * margin_x, height: stepy, text: n > 0 ? parts[n].trimStart() : parts[n].trimEnd(), color, + _expected_width: width - 2 * margin_x, _args: args, + post_process(painter) { + if (this._args[0].ready && this._args[1].ready) + painter.scaleTextDrawing(1.05 * (this._args[0].result_width + this._args[1].result_width) / this._expected_width, painter.getG()); + } + }; + args.push(arg); + } - append(kind) { - if (kind === 'svg:g') - return new TextParseWrapper('g', this); - if (kind === 'svg:text') - return new TextParseWrapper('text', this); - if (kind === 'svg:path') - return new TextParseWrapper('path', this); - console.log('should create', kind); - } + for (let n = 0; n < 2; ++n) + this.drawText(args[n]); + } + } + } - style(name, value) { - // console.log(`style ${name} = ${value}`); - if ((name === 'stroke-width') && value) - stroke_width = Number.parseInt(value); - return this; - } + let lpath = ''; - translate() { - if (this.geom) { - // special workaround for path elements, while 3d font is exact height, keep some space on the top - // let dy = this.kind === 'path' ? this.font_size*0.002 : 0; - this.geom.translate(this.x, this.y, 0); + if ((pt.fBorderSize > 0) && has_head) + lpath += `M0,${Math.round(stepy)}h${width}`; + + if ((first_stat > 0) && (num_cols > 1)) { + for (let nrow = first_stat; nrow < nlines; ++nrow) + lpath += `M0,${Math.round(nrow * stepy)}h${width}`; + for (let ncol = 0; ncol < num_cols - 1; ++ncol) + lpath += `M${Math.round(width / num_cols * (ncol + 1))},${Math.round(first_stat * stepy)}V${height}`; } - this.childs.forEach(chld => { - chld.x += this.x; - chld.y += this.y; - chld.translate(); - }); - } - attr(name, value) { - // console.log(`attr ${name} = ${value}`); + if (lpath) + this.appendPath(lpath).call(this.lineatt.func); - const get = () => { - if (!value) return ''; - const res = value[0]; - value = value.slice(1); - return res; - }, getN = (skip) => { - let p = 0; - while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) p++; - const res = Number.parseInt(value.slice(0, p)); - value = value.slice(p); - if (skip) get(); - return res; - }; + // this.getG().classed('most_upper_primitives', true); // this primitive will remain on top of list - if ((name === 'font-size') && value) - this.font_size = Number.parseInt(value); - else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { - const arr = value.slice(value.indexOf('(')+1, value.lastIndexOf(')')).split(','); - this.x += arr[0] ? Number.parseInt(arr[0])*0.01 : 0; - this.y -= arr[1] ? Number.parseInt(arr[1])*0.01 : 0; - } else if ((name === 'x') && (this.kind === 'text')) - this.x += Number.parseInt(value)*0.01; - else if ((name === 'y') && (this.kind === 'text')) - this.y -= Number.parseInt(value)*0.01; - else if ((name === 'd') && (this.kind === 'path')) { - if (get() !== 'M') return console.error('Not starts with M'); - const pnts = []; - let x1 = getN(true), y1 = getN(), next; - - while ((next = get())) { - let x2 = x1, y2 = y1; - switch (next) { - case 'L': x2 = getN(true); y2 = getN(); break; - case 'l': x2 += getN(true); y2 += getN(); break; - case 'H': x2 = getN(); break; - case 'h': x2 += getN(); break; - case 'V': y2 = getN(); break; - case 'v': y2 += getN(); break; - default: console.log('not supported operator', next); - } + return this.finishTextDrawing(undefined, (nlines > 1)); + }); + } - const angle = Math.atan2(y2-y1, x2-x1), - dx = 0.5 * stroke_width * Math.sin(angle), - dy = -0.5 * stroke_width * Math.cos(angle); + /** @summary draw TPaveText object */ + async drawPaveText(width, height, _dummy_arg, text_g) { + const pt = this.getObject(), + arr = pt.fLines?.arr || [], + nlines = arr.length, + pp = this.getPadPainter(), + pad_height = pp.getPadHeight(), + draw_header = pt.fLabel.length, + promises = [], + margin_x = pt.fMargin * width, + stepy = height / (nlines || 1), + dflt_font_size = 0.85 * stepy; + let max_font_size = 0; - pnts.push(x1-dx, y1-dy, 0, x2-dx, y2-dy, 0, x2+dx, y2+dy, 0, x1-dx, y1-dy, 0, x2+dx, y2+dy, 0, x1+dx, y1+dy, 0); + this.createAttText({ attr: pt, can_rotate: false }); - x1 = x2; y1 = y2; - } + // for single line (typically title) limit font size + if ((nlines === 1) && (this.textatt.size > 0)) + max_font_size = Math.max(3, this.textatt.getSize(pp)); - const pos = new Float32Array(pnts); + if (!text_g) + text_g = this.getG(); - this.geom = new BufferGeometry(); - this.geom.setAttribute('position', new BufferAttribute(pos, 3)); - this.geom.scale(0.01, -0.01, 0.01); - this.geom.computeVertexNormals(); + const fast = (nlines === 1) && pp.isFastDrawing(); + let num_txt = 0, num_custom = 0, longest_line = 0, alt_text_size = 0; - geoms.push(this.geom); - } - return this; - } + arr.forEach(entry => { + if (((entry._typename !== clTText) && (entry._typename !== clTLatex)) || !entry.fTitle?.trim()) + return; + num_txt++; + if (entry.fX || entry.fY || entry.fTextSize) + num_custom++; - text(v) { - if (this.kind === 'text') { - this.geom = new TextGeometry(v, { font: HelveticerRegularFont, size: Math.round(0.01*this.font_size), height: 0, curveSegments: 5 }); - geoms.push(this.geom); - } + if (!entry.fTextSize && !this.textatt.size) + longest_line = Math.max(longest_line, approximateLabelWidth(entry.fTitle, this.textatt.font, dflt_font_size)); + }); + + if (longest_line) { + alt_text_size = dflt_font_size; + if (longest_line > 0.92 * width) + alt_text_size *= (0.92 * width / longest_line); + alt_text_size = Math.round(alt_text_size); } -} + const pr = (num_txt > num_custom) ? this.startTextDrawingAsync(this.textatt.font, this.$postitle ? this.textatt.getSize(pp, 1, 0.05) : dflt_font_size, text_g, max_font_size) : Promise.resolve(); - const node = new TextParseWrapper(), - arg = { font_size, latex: 1, x: 0, y: 0, text: lbl, align: ['start', 'top'], fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }; + return pr.then(() => { + for (let nline = 0; nline < nlines; ++nline) { + const entry = arr[nline], texty = nline * stepy; + + switch (entry._typename) { + case clTText: + case clTLatex: { + if (!entry.fTitle || !entry.fTitle.trim()) + continue; + + let color = entry.fTextColor ? this.getColor(entry.fTextColor) : ''; + if (!color) + color = this.textatt.color; + const align = entry.fTextAlign || this.textatt.align, + valign = align % 10, + halign = (align - valign) / 10; + + if (entry.fX || entry.fY || entry.fTextSize) { + // individual positioning + const x = entry.fX ? entry.fX * width : (halign === 1 ? margin_x : (halign === 2 ? width / 2 : width - margin_x)), + y = entry.fY ? (1 - entry.fY) * height : (texty + (valign === 2 ? stepy / 2 : (valign === 3 ? stepy : 0))), + draw_g = text_g.append('svg:g'); + + promises.push(this.startTextDrawingAsync(this.textatt.font, this.textatt.getAltSize(entry.fTextSize, pp) || alt_text_size, draw_g) + .then(() => this.drawText({ + align, x, y, text: entry.fTitle, color, + latex: (entry._typename === clTText) ? 0 : 1, draw_g, fast + })) + .then(() => this.finishTextDrawing(draw_g))); + } else { + const arg = { + x: 0, y: texty, draw_g: text_g, + latex: (entry._typename === clTText) ? 0 : 1, + text: entry.fTitle, color, fast + }; + + if (this.$postitle) { + // remember box produced by title text + arg.post_process = function(painter) { + painter.$titlebox = this.box; + }; + } else { + arg.align = align; + arg.x = (halign === 1) ? margin_x : 0; + arg.width = (halign === 2) ? width : width - margin_x; + arg.y = texty + 0.05 * stepy; + arg.height = 0.9 * stepy; + // prevent expand of normal title on full width + // if (this.isTitle() && (halign === 2) && (arg.width > 0.1*pad_width) && (arg.width < 0.7*pad_width)) { + // arg.width -= 0.02*pad_width; + // arg.x = 0.01*pad_width; + // } + } - produceLatex(painter, node, arg); + this.drawText(arg); + } + break; + } - if (!geoms.length) - return new TextGeometry(translateLaTeX(lbl), { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); + case clTLine: { + const lx1 = entry.fX1 ? Math.round(entry.fX1 * width) : 0, + lx2 = entry.fX2 ? Math.round(entry.fX2 * width) : width, + ly1 = entry.fY1 ? Math.round((1 - entry.fY1) * height) : Math.round(texty + stepy * 0.5), + ly2 = entry.fY2 ? Math.round((1 - entry.fY2) * height) : Math.round(texty + stepy * 0.5), + lineatt = this.createAttLine(entry); + text_g.append('svg:path') + .attr('d', `M${lx1},${ly1}L${lx2},${ly2}`) + .call(lineatt.func); + break; + } + case clTBox: { + const bx1 = entry.fX1 ? Math.round(entry.fX1 * width) : 0, + bx2 = entry.fX2 ? Math.round(entry.fX2 * width) : width, + by1 = entry.fY1 ? Math.round((1 - entry.fY1) * height) : Math.round(texty), + by2 = entry.fY2 ? Math.round((1 - entry.fY2) * height) : Math.round(texty + stepy), + fillatt = this.createAttFill(entry); + text_g.append('svg:path') + .attr('d', `M${bx1},${by1}H${bx2}V${by2}H${bx1}Z`) + .call(fillatt.func); + break; + } + } + } - node.translate(); // apply translate attributes + if (num_txt > num_custom) + promises.push(this.finishTextDrawing(text_g, num_txt > num_custom + 1)); - if (geoms.length === 1) - return geoms[0]; + if (this.isTitle()) + this.getG().style('display', !num_txt ? 'none' : null); - let total_size = 0; - geoms.forEach(geom => { - total_size += geom.getAttribute('position').array.length; - }); - const pos = new Float32Array(total_size), - norm = new Float32Array(total_size); - let indx = 0; + return Promise.all(promises).then(() => this); + }).then(() => { + if (!draw_header) + return; - geoms.forEach(geom => { - const p1 = geom.getAttribute('position').array, - n1 = geom.getAttribute('normal').array; - for (let i = 0; i < p1.length; ++i, ++indx) { - pos[indx] = p1[i]; - norm[indx] = n1[i]; - } - }); + const w = Math.round(width * 0.5), + h = Math.round(pad_height * 0.04), + lbl_g = text_g.append('svg:g'); - const fullgeom = new BufferGeometry(); - fullgeom.setAttribute('position', new BufferAttribute(pos, 3)); - fullgeom.setAttribute('normal', new BufferAttribute(norm, 3)); - return fullgeom; -} + makeTranslate(lbl_g, Math.round(width * 0.25), Math.round(-pad_height * 0.02)); -/** @summary Text 3d axis visibility - * @private */ -function testAxisVisibility(camera, toplevel, fb = false, bb = false) { - let top; - if (toplevel?.children) { - for (let n = 0; n < toplevel.children.length; ++n) { - top = toplevel.children[n]; - if (top.axis_draw) break; - top = undefined; - } - } + this.drawBorder(lbl_g, w, h); - if (!top) return; + lbl_g.append('svg:path') + .attr('d', `M${0},${0}h${w}v${h}h${-w}z`) + .call(this.fillatt.func) + .call(this.lineatt.func); - if (!camera) { - // this is case when axis drawing want to be removed - toplevel.remove(top); - return; + return this.startTextDrawingAsync(this.textatt.font, 0.9 * h, lbl_g) + .then(() => this.drawText({ align: 22, x: 0, y: 0, width: w, height: h, text: pt.fLabel, color: this.textatt.color, draw_g: lbl_g })) + .then(() => promises.push(this.finishTextDrawing(lbl_g))); + }).then(() => { return this; }); } - const pos = camera.position; - let qudrant = 1; - if ((pos.x < 0) && (pos.y >= 0)) qudrant = 2; - if ((pos.x >= 0) && (pos.y >= 0)) qudrant = 3; - if ((pos.x >= 0) && (pos.y < 0)) qudrant = 4; + /** @summary Method used to convert value to string according specified format + * @desc format can be like 5.4g or 4.2e or 6.4f or 'stat' or 'fit' or 'entries' */ + format(value, fmt) { + if (!fmt) + fmt = 'stat'; - const testVisible = (id, range) => { - if (id <= qudrant) id += 4; - return (id > qudrant) && (id < qudrant+range); - }, handleZoomMesh = obj3d => { - for (let k = 0; k < obj3d.children?.length; ++k) { - if (obj3d.children[k].zoom !== undefined) - obj3d.children[k].zoom_disabled = !obj3d.visible; - } - }; + const pave = this.getObject(); - for (let n = 0; n < top.children.length; ++n) { - const chld = top.children[n]; - if (chld.grid) - chld.visible = bb && testVisible(chld.grid, 3); - else if (chld.zid) { - chld.visible = testVisible(chld.zid, 2); - handleZoomMesh(chld); - } else if (chld.xyid) { - chld.visible = testVisible(chld.xyid, 3); - handleZoomMesh(chld); - } else if (chld.xyboxid) { - let range = 5, shift = 0; - if (bb && !fb) { range = 3; shift = -2; } else - if (fb && !bb) range = 3; else - if (!fb && !bb) range = (chld.bottom ? 3 : 0); - chld.visible = testVisible(chld.xyboxid + shift, range); - if (!chld.visible && chld.bottom && bb) - chld.visible = testVisible(chld.xyboxid, 3); - } else if (chld.zboxid) { - let range = 2, shift = 0; - if (fb && bb) range = 5; else - if (bb && !fb) range = 4; else - if (!bb && fb) { shift = -2; range = 4; } - chld.visible = testVisible(chld.zboxid + shift, range); + switch (fmt) { + case 'stat': + fmt = pave.fStatFormat || gStyle.fStatFormat; + break; + case 'fit': + fmt = pave.fFitFormat || gStyle.fFitFormat; + break; + case 'entries': + if ((Math.abs(value) < 1e9) && (Math.round(value) === value)) + return value.toFixed(0); + fmt = '14.7g'; + break; } + + return floatToString(value, fmt || '6.4g'); } -} + /** @summary Draw TLegend object */ + drawLegend(w, h) { + const legend = this.getObject(), + nlines = legend.fPrimitives.arr.length, + ncols = Math.max(1, legend.fNColumns); + let nrows = Math.round(nlines / ncols), + any_text = false, + custom_textg = false; // each text entry has own attributes -function convertLegoBuf(painter, pos, binsx, binsy) { - if (painter.options.System === kCARTESIAN) - return pos; - const fp = painter.getFramePainter(); - let kx = 1/fp.size_x3d, ky = 1/fp.size_y3d; - if (binsx && binsy) { - kx *= binsx/(binsx-1); - ky *= binsy/(binsy-1); - } + if (nrows * ncols < nlines) + nrows++; - if (painter.options.System === kPOLAR) { - for (let i = 0; i < pos.length; i += 3) { - const angle = (1 - pos[i] * kx) * Math.PI, - radius = 0.5 + 0.5 * pos[i + 1] * ky; + const isEmpty = entry => !entry.fObject && !entry.fOption && (!entry.fLabel || !entry.fLabel.trim()); - pos[i] = Math.cos(angle) * radius * fp.size_x3d; - pos[i+1] = Math.sin(angle) * radius * fp.size_y3d; + for (let ii = 0; ii < nlines; ++ii) { + const entry = legend.fPrimitives.arr[ii]; + if (isEmpty(entry)) { + if (ncols === 1) + nrows--; + } else if (entry.fLabel) { + any_text = true; + if ((entry.fTextFont && (entry.fTextFont !== legend.fTextFont)) || + (entry.fTextSize && (entry.fTextSize !== legend.fTextSize))) + custom_textg = true; + } } - } else if (painter.options.System === kCYLINDRICAL) { - for (let i = 0; i < pos.length; i += 3) { - const angle = (1 - pos[i] * kx) * Math.PI, - radius = 0.5 + pos[i + 2]/fp.size_z3d/4; - pos[i] = Math.cos(angle) * radius * fp.size_x3d; - pos[i+2] = (1 + Math.sin(angle) * radius) * fp.size_z3d; - } - } else if (painter.options.System === kSPHERICAL) { - for (let i = 0; i < pos.length; i += 3) { - const phi = (1 + pos[i] * kx) * Math.PI, - theta = pos[i+1] * ky * Math.PI, - radius = 0.5 + pos[i+2]/fp.size_z3d/4; + if (nrows < 1) + nrows = 1; - pos[i] = radius * Math.cos(theta) * Math.cos(phi) * fp.size_x3d; - pos[i+1] = radius * Math.cos(theta) * Math.sin(phi) * fp.size_y3d; - pos[i+2] = (1 + radius * Math.sin(theta)) * fp.size_z3d; - } - } else if (painter.options.System === kRAPIDITY) { - for (let i = 0; i < pos.length; i += 3) { - const phi = (1 - pos[i] * kx) * Math.PI, - theta = pos[i+1] * ky * Math.PI, - radius = 0.5 + pos[i+2]/fp.size_z3d/4; + const padding_x = Math.round(0.03 * w / ncols), + padding_y = Math.round(0.03 * h), + row_height = (h - 2 * padding_y) / (nrows + (nrows - 1) * legend.fEntrySeparation), + gap_y = row_height * legend.fEntrySeparation; - pos[i] = radius * Math.cos(phi) * fp.size_x3d; - pos[i+1] = radius * Math.sin(theta) / Math.cos(theta) * fp.size_y3d / 2; - pos[i+2] = (1 + radius * Math.sin(phi)) * fp.size_z3d; + let gap_x = padding_x, + column_width0 = (w - 2 * padding_x - (ncols - 1) * gap_x) / ncols; + + if (legend.fColumnSeparation) { + column_width0 = (w - 2 * padding_x) / (ncols + (ncols - 1) * legend.fColumnSeparation); + gap_x = column_width0 * legend.fColumnSeparation; } - } - return pos; -} + // calculate positions of columns by weight - means more letters, more weight + const column_pos = new Array(ncols + 1).fill(padding_x), + column_boxwidth = column_width0 * legend.fMargin; + if (ncols > 1) { + const column_weight = new Array(ncols).fill(1), + space_for_text = w - 2 * padding_x - (ncols - 1) * gap_x - ncols * column_boxwidth; -function createLegoGeom(painter, positions, normals, binsx, binsy) { - const geometry = new BufferGeometry(); - if (painter.options.System === kCARTESIAN) { - geometry.setAttribute('position', new BufferAttribute(positions, 3)); - if (normals) - geometry.setAttribute('normal', new BufferAttribute(normals, 3)); - else - geometry.computeVertexNormals(); - } else { - convertLegoBuf(painter, positions, binsx, binsy); - geometry.setAttribute('position', new BufferAttribute(positions, 3)); - geometry.computeVertexNormals(); - } + for (let ii = 0; ii < nlines; ++ii) { + const entry = legend.fPrimitives.arr[ii]; + if (isEmpty(entry)) + continue; // let discard empty entry + const icol = ii % ncols; + column_weight[icol] = Math.max(column_weight[icol], entry.fLabel.length); + } - return geometry; -} + let sum_weight = 0; + for (let icol = 0; icol < ncols; ++icol) + sum_weight += column_weight[icol]; -function create3DCamera(fp, orthographic) { - if (fp.camera) { - fp.scene.remove(fp.camera); - disposeThreejsObject(fp.camera); - delete fp.camera; - } + for (let icol = 0; icol < ncols - 1; ++icol) + column_pos[icol + 1] = column_pos[icol] + column_boxwidth + column_weight[icol] / sum_weight * space_for_text + gap_x; + } + column_pos[ncols] = w - padding_x; - if (orthographic) - fp.camera = new OrthographicCamera(-1.3*fp.size_x3d, 1.3*fp.size_x3d, 2.3*fp.size_z3d, -0.7*fp.size_z3d, 0.001, 40*fp.size_z3d); - else - fp.camera = new PerspectiveCamera(45, fp.scene_width / fp.scene_height, 1, 40*fp.size_z3d); + let font_size = row_height, + max_font_size = 0, // not limited in the beginning + any_opt = false; - fp.camera.up.set(0, 0, 1); + this.createAttText({ attr: legend, can_rotate: false }); - fp.pointLight = new DirectionalLight(0xffffff, 3); - fp.pointLight.position.set(fp.size_x3d/2, fp.size_y3d/2, fp.size_z3d/2); - fp.camera.add(fp.pointLight); - fp.lookat = new Vector3(0, 0, orthographic ? 0.3*fp.size_z3d : 0.8*fp.size_z3d); - fp.scene.add(fp.camera); -} + const pp = this.getPadPainter(), + tsz = this.textatt.getSize(pp); + if (tsz && (tsz < font_size)) + font_size = max_font_size = tsz; -/** @summary Set default camera position - * @private */ -function setCameraPosition(fp, first_time) { - const pad = fp.getPadPainter().getRootPad(true), - kz = fp.camera.isOrthographicCamera ? 1 : 1.4; - let max3dx = Math.max(0.75*fp.size_x3d, fp.size_z3d), - max3dy = Math.max(0.75*fp.size_y3d, fp.size_z3d); + const text_promises = [], + pr = any_text && !custom_textg ? this.startTextDrawingAsync(this.textatt.font, font_size, undefined, max_font_size) : Promise.resolve(); - if (first_time) { - if (max3dx === max3dy) - fp.camera.position.set(-1.6*max3dx, -3.5*max3dy, kz*fp.size_z3d); - else if (max3dx > max3dy) - fp.camera.position.set(-2*max3dx, -3.5*max3dy, kz*fp.size_z3d); - else - fp.camera.position.set(-3.5*max3dx, -2*max3dy, kz*fp.size_z3d); - } + return pr.then(() => { + for (let ii = 0, i = -1; ii < nlines; ++ii) { + const entry = legend.fPrimitives.arr[ii]; + if (isEmpty(entry)) + continue; // let discard empty entry - if (pad && (first_time || !fp.zoomChangedInteractive())) { - if (Number.isFinite(pad.fTheta) && Number.isFinite(pad.fPhi) && ((pad.fTheta !== fp.camera_Theta) || (pad.fPhi !== fp.camera_Phi))) { - fp.camera_Phi = pad.fPhi; - fp.camera_Theta = pad.fTheta; - max3dx = 3*Math.max(fp.size_x3d, fp.size_z3d); - max3dy = 3*Math.max(fp.size_y3d, fp.size_z3d); - const phi = (270-pad.fPhi)/180*Math.PI, theta = (pad.fTheta-10)/180*Math.PI; - fp.camera.position.set(max3dx*Math.cos(phi)*Math.cos(theta), - max3dy*Math.sin(phi)*Math.cos(theta), - fp.size_z3d + (kz-0.9)*(max3dx+max3dy)*Math.sin(theta)); - first_time = true; - } - } + if (ncols === 1) + ++i; + else + i = ii; + + const lopt = entry.fOption.toLowerCase(), + icol = i % ncols, irow = (i - icol) / ncols, + x0 = Math.round(column_pos[icol]), + y0 = Math.round(padding_y + irow * (row_height + gap_y)), + tpos_x = Math.round(x0 + column_boxwidth), + mid_x = Math.round(x0 + (column_boxwidth - padding_x) / 2), + box_y = Math.round(y0 + row_height * 0.1), + box_height = Math.round(row_height * 0.8), + mid_y = Math.round(y0 + row_height * 0.5), // center line + mo = entry.fObject, + draw_fill = lopt.indexOf('f') !== -1, + draw_line = lopt.indexOf('l') !== -1, + draw_error = lopt.indexOf('e') !== -1, + draw_marker = lopt.indexOf('p') !== -1; + + let o_fill = entry, o_marker = entry, o_line = entry, + painter = null, isany = false; + + if (isObject(mo)) { + if ('fLineColor' in mo) + o_line = mo; + if ('fFillColor' in mo) + o_fill = mo; + if ('fMarkerColor' in mo) + o_marker = mo; + painter = pp.findPainterFor(mo); + } - if (first_time) - fp.camera.lookAt(fp.lookat); + // Draw fill pattern (in a box) + if (draw_fill) { + const fillatt = painter?.fillatt?.used ? painter.fillatt : this.createAttFill(o_fill); + let lineatt; + if (!draw_line && !draw_error && !draw_marker) { + lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); + if (lineatt.empty()) + lineatt = null; + } - if (first_time && fp.camera.isOrthographicCamera && fp.scene_width && fp.scene_height) { - const screen_ratio = fp.scene_width / fp.scene_height, - szx = fp.camera.right - fp.camera.left, szy = fp.camera.top - fp.camera.bottom; + if (!fillatt.empty() || lineatt) { + isany = true; + // define x,y as the center of the symbol for this entry + this.appendPath(`M${x0},${box_y}v${box_height}h${tpos_x - padding_x - x0}v${-box_height}z`) + .call(fillatt.func) + .call(lineatt ? lineatt.func : () => {}); + } + } - if (screen_ratio > szx / szy) { - // screen wider than actual geometry - const m = (fp.camera.right + fp.camera.left) / 2; - fp.camera.left = m - szy * screen_ratio / 2; - fp.camera.right = m + szy * screen_ratio / 2; - } else { - // screen heigher than actual geometry - const m = (fp.camera.top + fp.camera.bottom) / 2; - fp.camera.top = m + szx / screen_ratio / 2; - fp.camera.bottom = m - szx / screen_ratio / 2; - } - } + // Draw line and/or error (when specified) + if (draw_line || draw_error) { + const lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); + if (!lineatt.empty()) { + isany = true; + if (draw_line) { + this.appendPath(`M${x0},${mid_y}h${tpos_x - padding_x - x0}`) + .call(lineatt.func); + } + if (draw_error) { + let endcaps = 0, edx = row_height * 0.05; + if (isFunc(painter?.getHisto) && painter.options?.ErrorKind === 1) + endcaps = 1; // draw bars for e1 option in histogram + else if (isFunc(painter?.getGraph) && mo?.fLineWidth !== undefined && mo?.fMarkerSize !== undefined) { + endcaps = painter.options?.Ends ?? 1; // default is 1 + edx = mo.fLineWidth + gStyle.fEndErrorSize; + if (endcaps > 1) + edx = Math.max(edx, mo.fMarkerSize * 8 * 0.66); + } - fp.camera.updateProjectionMatrix(); -} + const eoff = (endcaps === 3) ? 0.2 : 0, + ey1 = Math.round(y0 + row_height * eoff), + ey2 = Math.round(y0 + row_height * (1 - eoff)), + edy = Math.round(edx * 0.66); + edx = Math.round(edx); + let path = `M${mid_x},${ey1}V${ey2}`; + switch (endcaps) { + case 1: path += `M${mid_x - edx},${ey1}h${2 * edx}M${mid_x - edx},${ey2}h${2 * edx}`; break; // bars + case 2: path += `M${mid_x - edx},${ey1 + edy}v${-edy}h${2 * edx}v${edy}M${mid_x - edx},${ey2 - edy}v${edy}h${2 * edx}v${-edy}`; break; // ] + case 3: path += `M${mid_x - edx},${ey1}h${2 * edx}l${-edx},${-edy}zM${mid_x - edx},${ey2}h${2 * edx}l${-edx},${edy}z`; break; // triangle + case 4: path += `M${mid_x - edx},${ey1 + edy}l${edx},${-edy}l${edx},${edy}M${mid_x - edx},${ey2 - edy}l${edx},${edy}l${edx},${-edy}`; break; // arrow + } + this.appendPath(path) + .call(lineatt.func) + .style('fill', endcaps > 1 ? 'none' : null); + } + } + } -function create3DControl(fp) { - fp.control = createOrbitControl(fp, fp.camera, fp.scene, fp.renderer, fp.lookat); + // Draw Poly marker + if (draw_marker) { + const marker = painter?.markeratt?.used ? painter.markeratt : this.createAttMarker(o_marker); + if (!marker.empty()) { + isany = true; + this.appendPath(marker.create(mid_x, mid_y)) + .call(marker.func); + } + } - const frame_painter = fp, obj_painter = fp.getMainPainter(); + // special case - nothing draw, try to show rect with line attributes + if (!isany && painter?.lineatt && !painter.lineatt.empty()) { + this.appendPath(`M${x0},${box_y}v${box_height}h${tpos_x - padding_x - x0}v${-box_height}z`) + .style('fill', 'none') + .call(painter.lineatt.func); + } - fp.control.processMouseMove = function(intersects) { - let tip = null, mesh = null, zoom_mesh = null; - const handle_tooltip = frame_painter.isTooltipAllowed(); + let pos_x = tpos_x; + if (isStr(lopt) && (lopt.toLowerCase() !== 'h')) + any_opt = true; + else if (!any_opt) + pos_x = x0; + + if (entry.fLabel) { + const textatt = this.createAttText({ attr: entry, std: false, attr_alt: legend }), + arg = { + draw_g: this.getG(), align: textatt.align, + x: pos_x, width: Math.round(column_pos[icol + 1] - pos_x), + y: y0, height: Math.round(row_height), + scale: (custom_textg && !entry.fTextSize) || !legend.fTextSize, + text: entry.fLabel, color: textatt.color + }; + if (custom_textg) { + arg.draw_g = this.getG().append('svg:g'); + text_promises.push(this.startTextDrawingAsync(textatt.font, textatt.getSize(pp), arg.draw_g, max_font_size) + .then(() => this.drawText(arg)) + .then(() => this.finishTextDrawing(arg.draw_g))); + } else + this.drawText(arg); + } + } - for (let i = 0; i < intersects.length; ++i) { - if (handle_tooltip && isFunc(intersects[i].object?.tooltip)) { - tip = intersects[i].object.tooltip(intersects[i]); - if (tip) { mesh = intersects[i].object; break; } - } else if (intersects[i].object?.zoom && !zoom_mesh) - zoom_mesh = intersects[i].object; - } + if (any_text && !custom_textg) + text_promises.push(this.finishTextDrawing()); - if (tip && !tip.use_itself) { - const delta_x = 1e-4*frame_painter.size_x3d, - delta_y = 1e-4*frame_painter.size_y3d, - delta_z = 1e-4*frame_painter.size_z3d; - if ((tip.x1 > tip.x2) || (tip.y1 > tip.y2) || (tip.z1 > tip.z2)) console.warn('check 3D hints coordinates'); - tip.x1 -= delta_x; tip.x2 += delta_x; - tip.y1 -= delta_y; tip.y2 += delta_y; - tip.z1 -= delta_z; tip.z2 += delta_z; - } + // rescale after all entries are shown + return Promise.all(text_promises); + }); + } - frame_painter.highlightBin3D(tip, mesh); + /** @summary Returns true if palette drawn in vertical direction */ + isPaletteVertical() { return this.#palette_vertical; } - if (!tip && zoom_mesh && isFunc(frame_painter.get3dZoomCoord)) { - let axis_name = zoom_mesh.zoom; - const pnt = zoom_mesh.globalIntersect(this.raycaster), - axis_value = frame_painter.get3dZoomCoord(pnt, axis_name); + /** @summary draw color palette with axis */ + drawPaletteAxis(s_width, s_height, arg) { + const palette = this.getObject(), + axis = palette.fAxis, + g = this.getG(), + can_move = isStr(arg) && (arg.indexOf('can_move') >= 0), + postpone_draw = isStr(arg) && (arg.indexOf('postpone') >= 0), + cjust = isStr(arg) && (arg.indexOf('cjust') >= 0), + bring_stats_front = isStr(arg) && (arg.indexOf('bring_stats_front') >= 0), + pp = this.getPadPainter(), + width = pp.getPadWidth(), + height = pp.getPadHeight(), + pad = pp.getRootPad(true), + main = palette.$main_painter || this.getMainPainter(), + fp = this.getFramePainter(), + contour = main.getContour(false), + levels = contour?.getLevels(), + is_th3 = isFunc(main.getDimension) && (main.getDimension() === 3), + is_scatter = isFunc(main.getZaxis), + log = pad?.fLogv ?? (is_th3 ? false : pad?.fLogz), + draw_palette = main.getHistPalette(), + zaxis = is_scatter ? main.getZaxis() : main.getObject()?.fZaxis, + sizek = pad?.fTickz ? 0.35 : 0.7; - if ((axis_name === 'z') && zoom_mesh.use_y_for_z) axis_name = 'y'; + let zmin = 0, zmax = 100, gzmin, gzmax, axis_transform, axis_second = 0; - return { name: axis_name, - title: 'axis object', - line: axis_name + ' : ' + frame_painter.axisAsText(axis_name, axis_value), - only_status: true }; - } + this.#palette_vertical = (palette.fX2NDC - palette.fX1NDC) < (palette.fY2NDC - palette.fY1NDC); - return tip?.lines ? tip : ''; - }; + axis.fTickSize = 0.03; // adjust axis ticks size - fp.control.processMouseLeave = function() { - frame_painter.highlightBin3D(null); - }; + if ((typeof zaxis?.fLabelOffset !== 'undefined') && !is_th3) { + axis.fBits = zaxis.fBits & ~EAxisBits.kTickMinus & ~EAxisBits.kTickPlus; + axis.fTitle = zaxis.fTitle; + axis.fTickSize = zaxis.fTickLength; + axis.fTitleSize = zaxis.fTitleSize; + axis.fTitleOffset = zaxis.fTitleOffset; + axis.fTextColor = zaxis.fTitleColor; + axis.fTextFont = zaxis.fTitleFont; + axis.fLineColor = zaxis.fAxisColor; + axis.fLabelSize = zaxis.fLabelSize; + axis.fLabelColor = zaxis.fLabelColor; + axis.fLabelFont = zaxis.fLabelFont; + axis.fLabelOffset = zaxis.fLabelOffset; + this.z_handle.setHistPainter(main, is_scatter ? 'hist#z' : 'z'); + this.z_handle.source_axis = zaxis; + } - fp.control.contextMenu = function(pos, intersects) { - let kind = 'painter', p = obj_painter; - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - const mesh = intersects[n].object; - if (mesh.zoom) { kind = mesh.zoom; p = null; break; } - if (isFunc(mesh.painter?.fillContextMenu)) { - p = mesh.painter; break; + if (contour && fp && !is_th3) { + if ((fp.zmin !== undefined) && (fp.zmax !== undefined) && (fp.zmin !== fp.zmax)) { + gzmin = fp.zmin; + gzmax = fp.zmax; + zmin = fp.zoom_zmin; + zmax = fp.zoom_zmax; + if (zmin === zmax) { + zmin = gzmin; + zmax = gzmax; } + } else { + zmin = levels.at(0); + zmax = levels.at(-1); } + } else if ((main.gmaxbin !== undefined) && (main.gminbin !== undefined)) { + // this is case of TH2 (needs only for size adjustment) + zmin = main.gminbin; + zmax = main.gmaxbin; + } else if ((main.hmin !== undefined) && (main.hmax !== undefined)) { + // this is case of TH1 + zmin = main.hmin; + zmax = main.hmax; } - const fp = obj_painter.getFramePainter(); - if (isFunc(fp?.showContextMenu)) - fp.showContextMenu(kind, pos, p); - }; -} - -/** @summary Create all necessary components for 3D drawings in frame painter - * @return {Promise} when render3d !== -1 - * @private */ -function create3DScene(render3d, x3dscale, y3dscale, orthographic) { - if (render3d === -1) { - if (!this.mode3d) return; + g.selectAll('rect').style('fill', 'white'); - if (!isFunc(this.clear3dCanvas)) { - console.error(`Strange, why mode3d=${this.mode3d} is configured!!!!`); - return; + if ((gzmin === undefined) || (gzmax === undefined) || (gzmin === gzmax)) { + gzmin = zmin; + gzmax = zmax; } - testAxisVisibility(null, this.toplevel); - - this.clear3dCanvas(); - - disposeThreejsObject(this.scene); - this.control?.cleanup(); - - cleanupRender3D(this.renderer); - - delete this.size_x3d; - delete this.size_y3d; - delete this.size_z3d; - delete this.tooltip_mesh; - delete this.scene; - delete this.toplevel; - delete this.camera; - delete this.pointLight; - delete this.renderer; - delete this.control; - if (this.render_tmout) { - clearTimeout(this.render_tmout); - delete this.render_tmout; + if (this.#palette_vertical) { + this.#swap_side = palette.fX2NDC < 0.5; + axis.fChopt = 'S+' + (this.#swap_side ? 'R' : 'L'); // clearly configure text align + this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, true, [0, s_height], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_width * sizek), swap_side: this.#swap_side, minposbin: main.gminposbin }); + axis_transform = this.#swap_side ? null : `translate(${s_width})`; + if (pad?.fTickz) + axis_second = this.#swap_side ? s_width : -s_width; + } else { + this.#swap_side = palette.fY1NDC > 0.5; + axis.fChopt = 'S+'; + this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, false, [0, s_width], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_height * sizek), swap_side: this.#swap_side, minposbin: main.gminposbin }); + axis_transform = this.#swap_side ? null : `translate(0,${s_height})`; + if (pad?.fTickz) + axis_second = this.#swap_side ? s_height : -s_height; } - this.mode3d = false; + if (!contour || !draw_palette || postpone_draw) { + // we need such rect to correctly calculate size + this.appendPath(`M0,0H${s_width}V${s_height}H0Z`) + .style('fill', 'white'); + } else { + for (let i = 0; i < levels.length - 1; ++i) { + let z0 = Math.round(this.z_handle.gr(levels[i])), + z1 = Math.round(this.z_handle.gr(levels[i + 1])), + portion = 0.5, d; - return; - } + // when not full range fit to the drawn range, + // calculate portion value that it approximately in the + // middle of the still visible area - this.mode3d = true; // indicate 3d mode as hist painter does + if (this.#palette_vertical) { + if ((z1 >= s_height) || (z0 < 0)) + continue; + z0 += 1; // ensure correct gap filling between colors - if ('toplevel' in this) { - // it is indication that all 3D object created, just replace it with empty - this.scene.remove(this.toplevel); - disposeThreejsObject(this.toplevel); - delete this.tooltip_mesh; - delete this.toplevel; - if (this.control) this.control.HideTooltip(); + if (z0 > s_height) { + if (z0 > z1 + 1) + portion = 0.5 * (s_height - z1) / (z0 - z1 - 1); + z0 = s_height; + if (z1 < 0) + z1 = 0; + } else if (z1 < 0) { + if (z0 > 1) + portion = 1 - 0.5 * z0 / (z0 - z1 - 1); + z1 = 0; + } + d = `M0,${z1}H${s_width}V${z0}H0Z`; + } else { + if ((z0 >= s_width) || (z1 < 0)) + continue; + z1 += 1; // ensure correct gap filling between colors - const newtop = new Object3D(); - this.scene.add(newtop); - this.toplevel = newtop; + if (z1 > s_width) { + if (z1 > z0 + 1) + portion = 1 - 0.5 * (s_width - z0) / (z1 - z0 - 1); + z1 = s_width; + if (z0 < 0) + z0 = 0; + } else if (z0 < 0) { + if (z1 > 1) + portion = 0.5 * (z1 - 1) / (z1 - z0 - 1); + z0 = 0; + } + d = `M${z0},0V${s_height}H${z1}V0Z`; + } - this.resize3D(); // set actual sizes + const lvl = levels[i] * portion + levels[i + 1] * (1 - portion), + col = contour.getPaletteColor(draw_palette, lvl); + if (!col) + continue; - setCameraPosition(this, false); + // console.log('z0, z1', z0, z1, 'height', s_height, 'col', col, 'portion', portion) - return Promise.resolve(true); - } + const r = this.appendPath(d) + .style('fill', col) + .property('fill0', col) + .property('fill1', rgb(col).darker(0.5).formatRgb()); - render3d = getRender3DKind(render3d, this.isBatchMode()); + if (this.isBatchMode()) + continue; - assign3DHandler(this); + if (this.isTooltipAllowed()) { + r.on('mouseover', function() { + select(this).transition().duration(100).style('fill', select(this).property('fill1')); + }).on('mouseout', function() { + select(this).transition().duration(100).style('fill', select(this).property('fill0')); + }).append('svg:title').text(this.z_handle.axisAsText(levels[i]) + ' - ' + this.z_handle.axisAsText(levels[i + 1])); + } - const sz = this.getSizeFor3d(undefined, render3d); + if (settings.Zooming) + r.on('dblclick', () => this.getFramePainter().unzoomSingle('z')); + } + } - this.size_z3d = 100; - this.size_x3d = this.size_y3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width/sz.height*this.size_z3d) : this.size_z3d; - if (x3dscale) this.size_x3d *= x3dscale; - if (y3dscale) this.size_y3d *= y3dscale; + if (bring_stats_front) + this.getPadPainter()?.findPainterFor(null, '', clTPaveStats)?.bringToFront(); - // three.js 3D drawing - this.scene = new Scene(); - // scene.fog = new Fog(0xffffff, 500, 3000); + return this.z_handle.drawAxis(g, s_width, s_height, axis_transform, axis_second).then(() => { + let rect; + if (can_move) { + if (settings.ApproxTextSize || isNodeJs()) { + // for batch testing provide approx estimation + rect = { x: this.#pave_x, y: this.#pave_y, width: s_width, height: s_height }; + const fsz = this.z_handle.labelsFont?.size || 14; + if (this.#palette_vertical) { + const dx = (this.z_handle._maxlbllen || 3) * 0.6 * fsz; + rect.width += dx; + if (this.#swap_side) + rect.x -= dx; + } else { + rect.height += fsz; + if (this.#swap_side) + rect.y -= fsz; + } + } else if ('getBoundingClientRect' in g.node()) + rect = g.node().getBoundingClientRect(); + } + if (!rect) + return this; - this.toplevel = new Object3D(); - this.scene.add(this.toplevel); - this.scene_width = sz.width; - this.scene_height = sz.height; - this.scene_x = sz.x ?? 0; - this.scene_y = sz.y ?? 0; + if (this.#palette_vertical) { + const shift = (this.#pave_x + parseInt(rect.width)) - Math.round(0.995 * width) + 3; - this.camera_Phi = 30; - this.camera_Theta = 30; + if (shift > 0) { + this.#pave_x -= shift; + makeTranslate(g, this.#pave_x, this.#pave_y); + palette.fX1NDC -= shift / width; + palette.fX2NDC -= shift / width; + } + } else { + const shift = Math.round((1.05 - gStyle.fTitleY) * height) - rect.y; + if (shift > 0) { + this.#pave_y += shift; + makeTranslate(g, this.#pave_x, this.#pave_y); + palette.fY1NDC -= shift / height; + palette.fY2NDC -= shift / height; + } + } - create3DCamera(this, orthographic); + return this; + }); + } - setCameraPosition(this, true); + /** @summary Add interactive methods for palette drawing */ + interactivePaletteAxis(s_width, s_height) { + let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect = null; - return createRender3D(this.scene_width, this.scene_height, render3d).then(r => { - this.renderer = r; + const moveRectSel = evnt => { + if (!doing_zoom) + return; + evnt.preventDefault(); - this.webgl = (render3d === constants$1.Render3D.WebGL); - this.add3dCanvas(sz, this.renderer.jsroot_dom, this.webgl); + const m = pointer(evnt, this.getG().node()); + if (this.#palette_vertical) { + sel2 = Math.min(Math.max(m[1], 0), s_height); + zoom_rect.attr('y', Math.min(sel1, sel2)) + .attr('height', Math.abs(sel2 - sel1)); + } else { + sel2 = Math.min(Math.max(m[0], 0), s_width); + zoom_rect.attr('x', Math.min(sel1, sel2)) + .attr('width', Math.abs(sel2 - sel1)); + } + }, endRectSel = evnt => { + if (!doing_zoom) + return; - this.first_render_tm = 0; - this.enable_highlight = false; + evnt.preventDefault(); + select(window).on('mousemove.colzoomRect', null) + .on('mouseup.colzoomRect', null); + zoom_rect.remove(); + zoom_rect = null; + doing_zoom = false; - if (!this.isBatchMode() && this.webgl && !isNodeJs()) - create3DControl(this); + const z1 = this.z_handle.revertPoint(sel1), + z2 = this.z_handle.revertPoint(sel2); - return this; - }); -} + this.getFramePainter().zoomSingle('z', Math.min(z1, z2), Math.max(z1, z2), true); + }, startRectSel = evnt => { + // ignore when touch selection is activated + if (doing_zoom) + return; + doing_zoom = true; -/** @summary Change camera kind in frame painter - * @private */ -function change3DCamera(orthographic) { - let has_control = false; - if (this.control) { - this.control.cleanup(); - delete this.control; - has_control = true; - } + evnt.preventDefault(); + evnt.stopPropagation(); - create3DCamera(this, orthographic); - setCameraPosition(this, true); + const origin = pointer(evnt, this.getG().node()); - if (has_control) - create3DControl(this); + zoom_rect = this.getG().append('svg:rect').attr('id', 'colzoomRect').call(addHighlightStyle, true); - this.render3D(); -} + if (this.#palette_vertical) { + sel1 = sel2 = origin[1]; + zoom_rect.attr('x', '0') + .attr('width', s_width) + .attr('y', sel1) + .attr('height', 1); + } else { + sel1 = sel2 = origin[0]; + zoom_rect.attr('x', sel1) + .attr('width', 1) + .attr('y', 0) + .attr('height', s_height); + } -/** @summary Add 3D mesh to frame painter - * @private */ -function add3DMesh(mesh, painter, the_only) { - if (!mesh) - return; - if (!this.toplevel) - return console.error('3D objects are not yet created in the frame'); - if (painter && the_only) - this.remove3DMeshes(painter); - this.toplevel.add(mesh); - mesh._painter = painter; -} + select(window).on('mousemove.colzoomRect', moveRectSel) + .on('mouseup.colzoomRect', endRectSel, true); + }; -/** @summary Remove 3D meshed for specified painter - * @private */ -function remove3DMeshes(painter) { - if (!painter || !this.toplevel) - return; - let i = this.toplevel.children.length; + if (settings.Zooming) { + this.getG().selectAll('.axis_zoom') + .on('mousedown', startRectSel) + .on('dblclick', () => this.getFramePainter().zoomSingle('z', 0, 0, true)); + } - while (i > 0) { - const mesh = this.toplevel.children[--i]; - if (mesh._painter === painter) { - this.toplevel.remove(mesh); - disposeThreejsObject(mesh); + if (settings.ZoomWheel) { + this.getG().on('wheel', evnt => { + const pos = pointer(evnt, this.getG().node()), + coord = this.#palette_vertical ? (1 - pos[1] / s_height) : pos[0] / s_width, + item = this.z_handle.analyzeWheelEvent(evnt, coord); + if (item?.changed) + this.getFramePainter().zoomSingle('z', item.min, item.max, true); + }); } } -} + /** @summary Fill context menu items for the TPave object */ + fillContextMenuItems(menu) { + const pave = this.getObject(), + set_opt = this.isStats() ? 'SetOption' : 'SetDrawOption'; -/** @summary call 3D rendering of the frame - * @param {number} tmout - specifies delay, after which actual rendering will be invoked - * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings - * superimposed with each other. - * If tmeout <= 0, rendering performed immediately - * If tmout === -1111, immediate rendering with SVG renderer is performed - * @private */ -function render3D(tmout) { - if (tmout === -1111) { - // special handling for direct SVG renderer - const doc = getDocument(), - rrr = createSVGRenderer(false, 0, doc); - rrr.setSize(this.scene_width, this.scene_height); - rrr.render(this.scene, this.camera); - if (rrr.makeOuterHTML) { - // use text mode, it is faster - const d = doc.createElement('div'); - d.innerHTML = rrr.makeOuterHTML(); - return d.childNodes[0]; + menu.sub('Shadow'); + menu.addSizeMenu('size', 0, 12, 1, pave.fBorderSize, arg => { + pave.fBorderSize = arg; + this.interactiveRedraw(true, `exec:SetBorderSize(${arg})`); + }); + menu.addColorMenu('color', pave.fShadowColor, arg => { + pave.fShadowColor = arg; + this.interactiveRedraw(true, getColorExec(arg, 'SetShadowColor')); + }); + const posarr = ['nb', 'tr', 'tl', 'br', 'bl']; + let value = '', opt = this.getPaveDrawOption(), remain = opt; + posarr.forEach(nn => { + const p = remain.indexOf(nn); + if ((p >= 0) && !value) { + value = nn; + remain = remain.slice(0, p) + remain.slice(p + nn.length); + } + }); + menu.addSelectMenu('positon', posarr, value || 'nb', arg => { + arg += remain; + this.setPaveDrawOption(arg); + this.interactiveRedraw(true, `exec:${set_opt}("${arg}")`); + }, 'Direction of pave shadow or nb - off'); + menu.endsub(); + + menu.sub('Corner'); + const parc = opt.toLowerCase().indexOf('arc'); + menu.addchk(parc >= 0, 'arc', flag => { + if (flag) + opt += ' arc'; + else + opt = opt.slice(0, parc) + opt.slice(parc + 3); + this.setPaveDrawOption(opt); + this.interactiveRedraw(true, `exec:${set_opt}("${opt}")`); + }, 'Usage of ARC draw option'); + menu.addSizeMenu('radius', 0, 0.2, 0.02, pave.fCornerRadius, val => { + pave.fCornerRadius = val; + this.interactiveRedraw(true, `exec:SetCornerRadius(${val})`); + }, 'Corner radius when ARC is enabled'); + menu.endsub(); + + if (this.isStats() || this.isPaveText() || this.isPavesText()) { + menu.add('Label', () => menu.input('Enter new label', pave.fLabel).then(lbl => { + pave.fLabel = lbl; + this.interactiveRedraw('pad', `exec:SetLabel("${lbl}")`); + })); + menu.addSizeMenu('Margin', 0, 0.2, 0.02, pave.fMargin, val => { + pave.fMargin = val; + this.interactiveRedraw(true, `exec:SetMargin(${val})`); + }); } - return rrr.domElement; - } - if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout + if (this.isStats()) { + menu.add('Default position', () => { + pave.fX2NDC = gStyle.fStatX; + pave.fX1NDC = pave.fX2NDC - gStyle.fStatW; + pave.fY2NDC = gStyle.fStatY; + pave.fY1NDC = pave.fY2NDC - gStyle.fStatH; + pave.fInit = 1; + this.interactiveRedraw(true, 'pave_moved'); + }); - const batch_mode = this.isBatchMode(); + menu.add('Save to gStyle', () => { + gStyle.fStatX = pave.fX2NDC; + gStyle.fStatW = pave.fX2NDC - pave.fX1NDC; + gStyle.fStatY = pave.fY2NDC; + gStyle.fStatH = pave.fY2NDC - pave.fY1NDC; + this.fillatt?.saveToStyle('fStatColor', 'fStatStyle'); + gStyle.fStatTextColor = pave.fTextColor; + gStyle.fStatFontSize = pave.fTextSize; + gStyle.fStatFont = pave.fTextFont; + gStyle.fFitFormat = pave.fFitFormat; + gStyle.fStatFormat = pave.fStatFormat; + gStyle.fOptStat = pave.fOptStat; + gStyle.fOptFit = pave.fOptFit; + }, 'Store stats attributes to gStyle'); - if ((tmout > 0) && !this.usesvg && !batch_mode) { - if (!this.render_tmout) - this.render_tmout = setTimeout(() => this.render3D(0), tmout); - return; - } + menu.separator(); - if (this.render_tmout) { - clearTimeout(this.render_tmout); - delete this.render_tmout; - } + menu.add('SetStatFormat', () => { + menu.input('Enter StatFormat', pave.fStatFormat).then(fmt => { + if (fmt) { + pave.fStatFormat = fmt; + this.interactiveRedraw(true, `exec:SetStatFormat("${fmt}")`); + } + }); + }); + menu.add('SetFitFormat', () => { + menu.input('Enter FitFormat', pave.fFitFormat).then(fmt => { + if (fmt) { + pave.fFitFormat = fmt; + this.interactiveRedraw(true, `exec:SetFitFormat("${fmt}")`); + } + }); + }); + menu.sub('SetOptStat', () => { + menu.input('Enter OptStat', pave.fOptStat, 'int').then(fmt => { + pave.fOptStat = fmt; + this.interactiveRedraw(true, `exec:SetOptStat(${fmt})`); + }); + }); + const addStatOpt = (pos, name) => { + let sopt = (pos < 10) ? pave.fOptStat : pave.fOptFit; + sopt = parseInt(parseInt(sopt) / parseInt(Math.pow(10, pos % 10))) % 10; + menu.addchk(sopt, name, sopt * 100 + pos, arg => { + const oldopt = parseInt(arg / 100); + let newopt = (arg % 100 < 10) ? pave.fOptStat : pave.fOptFit; + newopt -= (oldopt > 0 ? oldopt : -1) * parseInt(Math.pow(10, arg % 10)); + if (arg % 100 < 10) { + pave.fOptStat = newopt; + this.interactiveRedraw(true, `exec:SetOptStat(${newopt})`); + } else { + pave.fOptFit = newopt; + this.interactiveRedraw(true, `exec:SetOptFit(${newopt})`); + } + }); + }; + + addStatOpt(0, 'Histogram name'); + addStatOpt(1, 'Entries'); + addStatOpt(2, 'Mean'); + addStatOpt(3, 'Std Dev'); + addStatOpt(4, 'Underflow'); + addStatOpt(5, 'Overflow'); + addStatOpt(6, 'Integral'); + addStatOpt(7, 'Skewness'); + addStatOpt(8, 'Kurtosis'); + menu.endsub(); - if (!this.renderer) return; + menu.sub('SetOptFit', () => { + menu.input('Enter OptStat', pave.fOptFit, 'int').then(fmt => { + pave.fOptFit = fmt; + this.interactiveRedraw(true, `exec:SetOptFit(${fmt})`); + }); + }); + addStatOpt(10, 'Fit parameters'); + addStatOpt(11, 'Par errors'); + addStatOpt(12, 'Chi square / NDF'); + addStatOpt(13, 'Probability'); + menu.endsub(); + + menu.separator(); + } else if (this.isPaveText() || this.isPavesText()) { + if (this.isPavesText()) { + menu.addSizeMenu('Paves', 1, 10, 1, pave.fNpaves, val => { + pave.fNpaves = val; + this.interactiveRedraw(true, `exec:SetNpaves(${val})`); + }); + } - beforeRender3D(this.renderer); + if (this.isTitle()) { + menu.add('Default position', () => { + pave.fX1NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX - gStyle.fTitleW / 2 : gStyle.fPadLeftMargin; + pave.fY1NDC = gStyle.fTitleY - Math.min(gStyle.fTitleFontSize * 1.1, 0.06); + pave.fX2NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX + gStyle.fTitleW / 2 : 1 - gStyle.fPadRightMargin; + pave.fY2NDC = gStyle.fTitleY; + pave.fInit = 1; + this.interactiveRedraw(true, 'pave_moved'); + }); - const tm1 = new Date(); + menu.add('Save to gStyle', () => { + gStyle.fTitleX = (pave.fX2NDC + pave.fX1NDC) / 2; + gStyle.fTitleY = pave.fY2NDC; + this.fillatt?.saveToStyle('fTitleColor', 'fTitleStyle'); + gStyle.fTitleTextColor = pave.fTextColor; + gStyle.fTitleFontSize = pave.fTextSize; + gStyle.fTitleFont = pave.fTextFont; + }, 'Store title position and graphical attributes to gStyle'); + } + } else if (pave._typename === clTLegend) { + menu.sub('Legend'); + menu.add('Autoplace', () => { + this.autoPlaceLegend(pave, this.getPadPainter()?.getRootPad(true), true).then(res => { + if (res) + this.interactiveRedraw(true, 'pave_moved'); + }); + }); + menu.addSizeMenu('Entry separation', 0, 1, 0.1, pave.fEntrySeparation, v => { + pave.fEntrySeparation = v; + this.interactiveRedraw(true, `exec:SetEntrySeparation(${v})`); + }, 'Vertical entries separation, meaningful values between 0 and 1'); + menu.addSizeMenu('Columns separation', 0, 1, 0.1, pave.fColumnSeparation, v => { + pave.fColumnSeparation = v; + this.interactiveRedraw(true, `exec:SetColumnSeparation(${v})`); + }, 'Horizontal columns separation, meaningful values between 0 and 1'); + menu.addSizeMenu('Num columns', 1, 7, 1, pave.fNColumns, v => { + pave.fNColumns = v; + this.interactiveRedraw(true, `exec:SetNColumns(${v})`); + }, 'Number of columns in the legend'); + menu.endsub(); + } + } - testAxisVisibility(this.camera, this.toplevel, this.opt3d?.FrontBox, this.opt3d?.BackBox); + /** @summary Show pave context menu */ + paveContextMenu(evnt) { + if (this.z_handle) { + const fp = this.getFramePainter(); + if (isFunc(fp?.showContextMenu)) + fp.showContextMenu('pal', evnt); + } else + showPainterMenu(evnt, this); + } - // do rendering, most consuming time - this.renderer.render(this.scene, this.camera); + /** @summary Returns true when stat box is drawn */ + isStats() { + return this.matchObjectType(clTPaveStats); + } - afterRender3D(this.renderer); + /** @summary Returns true when stat box is drawn */ + isPaveText() { + return this.matchObjectType(clTPaveText); + } - const tm2 = new Date(); + /** @summary Returns true when stat box is drawn */ + isPavesText() { + return this.matchObjectType(clTPavesText); + } - if (this.first_render_tm === 0) { - this.first_render_tm = tm2.getTime() - tm1.getTime(); - this.enable_highlight = (this.first_render_tm < 1200) && this.isTooltipAllowed(); - if (this.first_render_tm > 500) - console.log(`three.js r${REVISION}, first render tm = ${this.first_render_tm}`); + /** @summary Returns true when stat box is drawn */ + isPalette() { + return this.matchObjectType(clTPaletteAxis); } - if (this.processRender3D) { - this.getPadPainter()?.painters?.forEach(objp => { - if (isFunc(objp.handleRender3D)) - objp.handleRender3D(); - }); + /** @summary Returns true when title is drawn */ + isTitle() { + return this.isPaveText() && (this.getObject()?.fName === kTitle); } -} -/** @summary Check is 3D drawing need to be resized - * @private */ -function resize3D() { - const sz = this.getSizeFor3d(this.access3dKind()); + /** @summary Clear text in the pave */ + clearPave() { + this.getObject().Clear(); + } - this.apply3dSize(sz); + /** @summary Add text to pave */ + addText(txt) { + this.getObject().AddText(txt); + } - if ((this.scene_width === sz.width) && (this.scene_height === sz.height)) return false; + /** @summary Remade version of THistPainter::GetBestFormat + * @private */ + getBestFormat(tv, e) { + const ie = tv.indexOf('e'), id = tv.indexOf('.'); - if ((sz.width < 10) || (sz.height < 10)) return false; + if (ie >= 0) + return (tv.indexOf('+') < 0) || (e >= 1) ? `.${ie - id - 1}e` : '.1f'; + if (id < 0) + return '.1f'; - this.scene_width = sz.width; - this.scene_height = sz.height; + return `.${tv.length - id - 1}f`; + } - this.camera.aspect = this.scene_width / this.scene_height; - this.camera.updateProjectionMatrix(); + /** @summary Fill function parameters */ + fillFunctionStat(f1, dofit, ndim = 1) { + this.#has_fit = dofit && f1; - this.renderer.setSize(this.scene_width, this.scene_height); + if (!this.#has_fit) + return false; - return true; -} + this.#fit_dim = ndim; + this.#fit_cnt = 0; -/** @summary Hilight bin in frame painter 3D drawing - * @private */ -function highlightBin3D(tip, selfmesh) { - const want_remove = !tip || (tip.x1 === undefined) || !this.enable_highlight; - let changed = false, tooltip_mesh = null, changed_self = true, mainp = this.getMainPainter(); + const print_fval = (ndim === 1) ? dofit % 10 : 1, + print_ferrors = (ndim === 1) ? Math.floor(dofit / 10) % 10 : 1, + print_fchi2 = (ndim === 1) ? Math.floor(dofit / 100) % 10 : 1, + print_fprob = (ndim === 1) ? Math.floor(dofit / 1000) % 10 : 0; - if (mainp && (!mainp.provideUserTooltip || !mainp.hasUserTooltip())) mainp = null; + if (print_fchi2) { + this.addText('#chi^{2} / ndf = ' + this.format(f1.fChisquare, 'fit') + ' / ' + f1.fNDF); + this.#fit_cnt++; + } + if (print_fprob) { + this.addText('Prob = ' + this.format(Prob(f1.fChisquare, f1.fNDF))); + this.#fit_cnt++; + } + if (print_fval) { + for (let n = 0; n < f1.GetNumPars(); ++n) { + const parname = f1.GetParName(n); + let parvalue = f1.GetParValue(n), parerr = f1.GetParError(n); + if (parvalue === undefined) { + parvalue = ''; + parerr = null; + } else { + parvalue = this.format(Number(parvalue), 'fit'); + if (print_ferrors && (parerr !== undefined)) { + parerr = floatToString(parerr, this.getBestFormat(parvalue, parerr)); + if (!Number(parerr) && f1.GetParError(n)) + parerr = floatToString(f1.GetParError(n), '4.2g'); + } + } - if (this.tooltip_selfmesh) { - changed_self = (this.tooltip_selfmesh !== selfmesh); - this.tooltip_selfmesh.material.color = this.tooltip_selfmesh.save_color; - delete this.tooltip_selfmesh; - changed = true; - } + if (print_ferrors && parerr) + this.addText(`${parname} = ${parvalue} #pm ${parerr}`); + else + this.addText(`${parname} = ${parvalue}`); + this.#fit_cnt++; + } + } - if (this.tooltip_mesh) { - tooltip_mesh = this.tooltip_mesh; - this.toplevel.remove(this.tooltip_mesh); - delete this.tooltip_mesh; - changed = true; + return true; } - if (want_remove) { - if (changed) this.render3D(); - if (changed && mainp) mainp.provideUserTooltip(null); - return; + /** @summary Is dummy pos of the pave painter */ + isDummyPos(p) { + return !p ? true : !p.fInit && !p.fX1 && !p.fX2 && !p.fY1 && !p.fY2 && !p.fX1NDC && !p.fX2NDC && !p.fY1NDC && !p.fY2NDC; } - if (tip.use_itself) { - selfmesh.save_color = selfmesh.material.color; - selfmesh.material.color = new Color(tip.color); - this.tooltip_selfmesh = selfmesh; - changed = changed_self; - } else { - changed = true; - - const indicies = Box3D.Indexes, - normals = Box3D.Normals, - vertices = Box3D.Vertices, - color = new Color(tip.color ? tip.color : 0xFF0000), - opacity = tip.opacity || 1; - - let pos, norm; - - if (!tooltip_mesh) { - pos = new Float32Array(indicies.length*3); - norm = new Float32Array(indicies.length*3); - const geom = new BufferGeometry(); - geom.setAttribute('position', new BufferAttribute(pos, 3)); - geom.setAttribute('normal', new BufferAttribute(norm, 3)); - const material = new MeshBasicMaterial({ color, opacity, vertexColors: false }); - tooltip_mesh = new Mesh(geom, material); - } else { - pos = tooltip_mesh.geometry.attributes.position.array; - tooltip_mesh.geometry.attributes.position.needsUpdate = true; - tooltip_mesh.material.color = color; - tooltip_mesh.material.opacity = opacity; - } + /** @summary Update TPave object */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; - if (tip.x1 === tip.x2) console.warn(`same tip X ${tip.x1} ${tip.x2}`); - if (tip.y1 === tip.y2) console.warn(`same tip Y ${tip.y1} ${tip.y2}`); - if (tip.z1 === tip.z2) tip.z2 = tip.z1 + 0.0001; // avoid zero faces + const pave = this.getObject(), + is_auto = opt === kAutoPlace; - for (let k = 0, nn = -3; k < indicies.length; ++k) { - const vert = vertices[indicies[k]]; - pos[k*3] = tip.x1 + vert.x * (tip.x2 - tip.x1); - pos[k*3+1] = tip.y1 + vert.y * (tip.y2 - tip.y1); - pos[k*3+2] = tip.z1 + vert.z * (tip.z2 - tip.z1); + if (!pave.$modifiedNDC && !this.isDummyPos(obj)) { + // if position was not modified interactively, update from source object - if (norm) { - if (k % 6 === 0) nn += 3; - norm[k*3] = normals[nn]; - norm[k*3+1] = normals[nn+1]; - norm[k*3+2] = normals[nn+2]; + if (this.stored && !obj.fInit && (this.stored.fX1 === obj.fX1) && + (this.stored.fX2 === obj.fX2) && (this.stored.fY1 === obj.fY1) && (this.stored.fY2 === obj.fY2)) { + // case when source object not initialized and original coordinates are not changed + // take over only modified NDC coordinate, used in tutorials/graphics/canvas.C + if (this.stored.fX1NDC !== obj.fX1NDC) + pave.fX1NDC = obj.fX1NDC; + if (this.stored.fX2NDC !== obj.fX2NDC) + pave.fX2NDC = obj.fX2NDC; + if (this.stored.fY1NDC !== obj.fY1NDC) + pave.fY1NDC = obj.fY1NDC; + if (this.stored.fY2NDC !== obj.fY2NDC) + pave.fY2NDC = obj.fY2NDC; + } else { + pave.fInit = obj.fInit; + pave.fX1 = obj.fX1; + pave.fX2 = obj.fX2; + pave.fY1 = obj.fY1; + pave.fY2 = obj.fY2; + pave.fX1NDC = obj.fX1NDC; + pave.fX2NDC = obj.fX2NDC; + pave.fY1NDC = obj.fY1NDC; + pave.fY2NDC = obj.fY2NDC; } - } - this.tooltip_mesh = tooltip_mesh; - this.toplevel.add(tooltip_mesh); - if (tip.$painter && tip.$painter.options.System !== kCARTESIAN) { - convertLegoBuf(tip.$painter, pos); - tooltip_mesh.geometry.computeVertexNormals(); + this.stored = Object.assign({}, obj); // store latest coordinates } - } - - if (changed) this.render3D(); - - if (changed && tip.$projection && isFunc(tip.$painter?.redrawProjection)) - tip.$painter.redrawProjection(tip.ix-1, tip.ix, tip.iy-1, tip.iy); - - if (changed && mainp?.getObject()) { - mainp.provideUserTooltip({ obj: mainp.getObject(), name: mainp.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1+tip.x2)/2, gry: (tip.y1+tip.y2)/2, grz: (tip.z1+tip.z2)/2 }); - } -} -/** @summary Set options used for 3D drawings - * @private */ -function set3DOptions(hopt) { - this.opt3d = hopt; -} - -/** @summary Draw axes in 3D mode - * @private */ -function drawXYZ(toplevel, AxisPainter, opts) { - if (!opts) opts = {}; + pave.fOption = obj.fOption; + pave.fBorderSize = obj.fBorderSize; + if (pave.fTextColor !== undefined && obj.fTextColor !== undefined) { + pave.fTextAngle = obj.fTextAngle; + pave.fTextSize = obj.fTextSize; + pave.fTextAlign = obj.fTextAlign; + pave.fTextColor = obj.fTextColor; + pave.fTextFont = obj.fTextFont; + } - if (opts.drawany === false) - opts.draw = false; - else - opts.drawany = true; + switch (obj._typename) { + case clTDiamond: + case clTPaveText: + pave.fLines = clone(obj.fLines); + break; + case clTPavesText: + pave.fLines = clone(obj.fLines); + pave.fNpaves = obj.fNpaves; + break; + case clTPaveLabel: + case clTPaveClass: + pave.fLabel = obj.fLabel; + break; + case clTPaveStats: + pave.fOptStat = obj.fOptStat; + pave.fOptFit = obj.fOptFit; + break; + case clTLegend: { + const oldprim = pave.fPrimitives; + pave.fPrimitives = obj.fPrimitives; + pave.fNColumns = obj.fNColumns; + this.AutoPlace = is_auto; + if (oldprim?.arr?.length && (oldprim?.arr?.length === pave.fPrimitives?.arr?.length)) { + // try to sync object reference, new object does not displayed automatically + // in ideal case one should use snapids in the entries + for (let k = 0; k < oldprim.arr.length; ++k) { + const oldobj = oldprim.arr[k].fObject, newobj = pave.fPrimitives.arr[k].fObject; + if (oldobj && newobj && oldobj._typename === newobj._typename && oldobj.fName === newobj.fName) + pave.fPrimitives.arr[k].fObject = oldobj; + } + } + return true; + } + case clTPaletteAxis: + pave.fBorderSize = 1; + pave.fShadowColor = 0; + break; + default: + return false; + } - const pad = opts.v7 ? null : this.getPadPainter().getRootPad(true); - let grminx = -this.size_x3d, grmaxx = this.size_x3d, - grminy = -this.size_y3d, grmaxy = this.size_y3d, - grminz = 0, grmaxz = 2*this.size_z3d, - scalingSize = this.size_z3d, - xmin = this.xmin, xmax = this.xmax, - ymin = this.ymin, ymax = this.ymax, - zmin = this.zmin, zmax = this.zmax, - y_zoomed = false, z_zoomed = false; + this.storeDrawOpt(is_auto ? kDefaultDrawOpt : opt); - if (!this.size_z3d) { - grminx = this.xmin; grmaxx = this.xmax; - grminy = this.ymin; grmaxy = this.ymax; - grminz = this.zmin; grmaxz = this.zmax; - scalingSize = (grmaxz - grminz); + return true; } - if (('zoom_xmin' in this) && ('zoom_xmax' in this) && (this.zoom_xmin !== this.zoom_xmax)) { - xmin = this.zoom_xmin; xmax = this.zoom_xmax; - } - if (('zoom_ymin' in this) && ('zoom_ymax' in this) && (this.zoom_ymin !== this.zoom_ymax)) { - ymin = this.zoom_ymin; ymax = this.zoom_ymax; y_zoomed = true; - } - if (('zoom_zmin' in this) && ('zoom_zmax' in this) && (this.zoom_zmin !== this.zoom_zmax)) { - zmin = this.zoom_zmin; zmax = this.zoom_zmax; z_zoomed = true; + /** @summary redraw pave object */ + async redraw() { + return this.drawPave(); } - if (opts.use_y_for_z) { - this.zmin = this.ymin; this.zmax = this.ymax; - zmin = ymin; zmax = ymax; z_zoomed = y_zoomed; - // if (!z_zoomed && (this.hmin!==this.hmax)) { zmin = this.hmin; zmax = this.hmax; } - ymin = 0; ymax = 1; + /** @summary cleanup pave painter */ + cleanup() { + this.z_handle?.cleanup(); + delete this.z_handle; + const pp = this.getObject(); + if (pp) + delete pp.$main_painter; + super.cleanup(); } - // z axis range used for lego plot - this.lego_zmin = zmin; this.lego_zmax = zmax; - - // factor 1.1 used in ROOT for lego plots - if ((opts.zmult !== undefined) && !z_zoomed) zmax *= opts.zmult; + /** @summary Set position of title + * @private */ + setTitlePosition(pave, text_width, text_height) { + const posx = gStyle.fTitleX, posy = gStyle.fTitleY, + valign = gStyle.fTitleAlign % 10, halign = (gStyle.fTitleAlign - valign) / 10; + let w = gStyle.fTitleW, h = gStyle.fTitleH, need_readjust = false; + + if (h <= 0) { + if (text_height) + h = 1.1 * text_height / this.getPadPainter().getPadHeight(); + else { + h = 0.05; + need_readjust = true; + } + } - this.x_handle = new AxisPainter(null, this.xaxis); - if (opts.v7) { - this.x_handle.setPadName(this.getPadName()); - this.x_handle.snapid = this.snapid; - } - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, xmin, xmax, false, [grminx, grmaxx], - { log: pad?.fLogx ?? 0, reverse: opts.reverse_x }); - this.x_handle.assignFrameMembers(this, 'x'); - this.x_handle.extractDrawAttributes(scalingSize); + if (w <= 0) { + if (text_width) + w = Math.min(0.7, 0.02 + text_width / this.getPadPainter().getPadWidth()); + else { + w = 0.5; + need_readjust = true; + } + } - this.y_handle = new AxisPainter(null, this.yaxis); - if (opts.v7) { - this.y_handle.setPadName(this.getPadName()); - this.y_handle.snapid = this.snapid; - } - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, ymin, ymax, false, [grminy, grmaxy], - { log: pad && !opts.use_y_for_z ? pad.fLogy : 0, reverse: opts.reverse_y }); - this.y_handle.assignFrameMembers(this, 'y'); - this.y_handle.extractDrawAttributes(scalingSize); + pave.fX1NDC = halign < 2 ? posx : (halign > 2 ? posx - w : posx - w / 2); + pave.fY1NDC = valign < 2 ? posy : (valign > 2 ? posy - h : posy - h / 2); + pave.fX2NDC = pave.fX1NDC + w; + pave.fY2NDC = pave.fY1NDC + h; + pave.fInit = 1; - this.z_handle = new AxisPainter(null, this.zaxis); - if (opts.v7) { - this.z_handle.setPadName(this.getPadName()); - this.z_handle.snapid = this.snapid; + return need_readjust; } - this.z_handle.configureAxis('zaxis', this.zmin, this.zmax, zmin, zmax, false, [grminz, grmaxz], - { log: ((opts.use_y_for_z || (opts.ndim === 2)) ? pad?.fLogv : undefined) ?? pad?.fLogz ?? 0, - reverse: opts.reverse_z }); - this.z_handle.assignFrameMembers(this, 'z'); - this.z_handle.extractDrawAttributes(scalingSize); - - this.setRootPadRange(pad, true); // set some coordinates typical for 3D projections in ROOT - - const textMaterials = {}, lineMaterials = {}, - xticks = this.x_handle.createTicks(false, true), - yticks = this.y_handle.createTicks(false, true), - zticks = this.z_handle.createTicks(false, true); - let text_scale = 1; - - function getLineMaterial(handle, kind) { - const col = ((kind === 'ticks') ? handle.ticksColor : handle.lineatt.color) || 'black', - linewidth = (kind === 'ticks') ? handle.ticksWidth : handle.lineatt.width, - name = `${col}_${linewidth}`; - if (!lineMaterials[name]) - lineMaterials[name] = new LineBasicMaterial(getMaterialArgs(col, { linewidth, vertexColors: false })); - return lineMaterials[name]; + /** @summary Returns true if object is supported */ + static canDraw(obj) { + const typ = obj?._typename; + return typ === clTPave || typ === clTPaveLabel || typ === clTPaveClass || typ === clTPaveStats || typ === clTPaveText || + typ === clTPavesText || typ === clTDiamond || typ === clTLegend || typ === clTPaletteAxis; } - function getTextMaterial(handle, kind, custom_color) { - const col = custom_color || ((kind === 'title') ? handle.titleFont?.color : handle.labelsFont?.color) || 'black'; - if (!textMaterials[col]) - textMaterials[col] = new MeshBasicMaterial(getMaterialArgs(col, { vertexColors: false })); - return textMaterials[col]; - } + /** @summary Draw TPave */ + static async draw(dom, pave, opt) { + const arg_opt = opt, + pos_title = (opt === kPosTitle), + is_auto = (opt === kAutoPlace); + if (pos_title || is_auto || (isStr(opt) && (opt.indexOf(';') >= 0))) + opt = ''; // use default - or stored in TPave itself - // main element, where all axis elements are placed - const top = new Object3D(); - top.axis_draw = true; // mark element as axis drawing - toplevel.add(top); + const painter = new TPavePainter(dom, pave, opt); - let ticks = [], lbls = [], maxtextheight = 0; + return ensureTCanvas(painter, false).then(() => { + if (painter.isTitle()) { + const pp = painter.getPadPainter(), + prev_painter = pp.findPainterFor(null, kTitle, clTPaveText); + if (prev_painter && (prev_painter !== painter)) + pp.removePrimitive(prev_painter); + else if (pos_title || painter.isDummyPos(pave)) { + if (painter.setTitlePosition(pave)) + painter.$postitle = true; + } + } else if (pave._typename === clTPaletteAxis) { + pave.fBorderSize = 1; + pave.fShadowColor = 0; - while (xticks.next()) { - const grx = xticks.grpos; - let is_major = xticks.kind === 1, - lbl = this.x_handle.format(xticks.tick, 2); + // check some default values of TGaxis object, otherwise axis will not be drawn + if (pave.fAxis) { + if (!pave.fAxis.fChopt) + pave.fAxis.fChopt = '+'; + if (!pave.fAxis.fNdiv) + pave.fAxis.fNdiv = 12; + if (!pave.fAxis.fLabelOffset) + pave.fAxis.fLabelOffset = 0.005; + } - if (xticks.last_major()) { - if (!this.x_handle.fTitle) lbl = 'x'; - } else if (lbl === null) { - is_major = false; lbl = ''; - } + painter.z_handle = new TAxisPainter(painter.getPadPainter(), pave.fAxis, true); - if (is_major && lbl && opts.draw) { - const mod = xticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; + painter.UseContextMenu = true; + } else if (pave._typename === clTLegend) + painter.AutoPlace = is_auto; - const text3d = createTextGeometry(this, lbl, this.x_handle.labelsFont.size); - text3d.computeBoundingBox(); - const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - text3d.center = true; // place central + painter.NoFillStats = pave.fName !== 'stats'; - text3d.offsety = this.x_handle.labelsOffset + (grmaxy - grminy) * 0.005; + return painter.drawPave(arg_opt); + }).then(() => { + const adjust_title = painter.$postitle && painter.$titlebox; - maxtextheight = Math.max(maxtextheight, draw_height); + if (adjust_title) + painter.setTitlePosition(pave, painter.$titlebox.width, painter.$titlebox.height); - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); - text3d.grx = grx; - lbls.push(text3d); + delete painter.$postitle; + delete painter.$titlebox; - let space = 0; - if (!xticks.last_major()) { - space = Math.abs(xticks.next_major_grpos() - grx); - if ((draw_width > 0) && (space > 0)) - text_scale = Math.min(text_scale, 0.9*space/draw_width); - } + return adjust_title ? painter.drawPave(arg_opt) : painter; + }); + } - if (this.x_handle.isCenteredLabels()) { - if (!space) space = Math.min(grx - grminx, grmaxx - grx); - text3d.grx += space/2; - } - } +} // class TPavePainter - ticks.push(grx, 0, 0, grx, this.x_handle.ticksSize*(is_major ? -1 : -0.6), 0); - } - if (this.x_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.x_handle.fTitle, this.x_handle.titleFont.size); - text3d.computeBoundingBox(); - text3d.center = this.x_handle.titleCenter; - text3d.opposite = this.x_handle.titleOpposite; - text3d.offsety = 1.6 * this.x_handle.titleOffset + (grmaxy - grminy) * 0.005; - text3d.grx = (grminx + grmaxx)/2; // default position for centered title - text3d.kind = 'title'; - lbls.push(text3d); - } +/** @summary Draw object title + * @return {Promise} with painter */ +async function drawObjectTitle(painter, first_time, is_enabled, is_draw) { + if (!is_enabled) + return painter; - this.get3dZoomCoord = function(point, kind) { - // return axis coordinate from intersection point with axis geometry - const min = this[`scale_${kind}min`], max = this[`scale_${kind}max`]; - let pos = point[kind]; + const st = gStyle, + obj = painter.getObject(), + pp = painter.getPadPainter(), + draw_title = is_draw && (st.fOptTitle > 0); - switch (kind) { - case 'x': pos = (pos + this.size_x3d)/2/this.size_x3d; break; - case 'y': pos = (pos + this.size_y3d)/2/this.size_y3d; break; - case 'z': pos = pos/2/this.size_z3d; break; + if (first_time) { + let pt = pp.findInPrimitives(kTitle, clTPaveText); + if (pt) { + pt.Clear(); + if (draw_title) + pt.AddText(obj.fTitle); + return painter; } - if (this['log'+kind]) - pos = Math.exp(Math.log(min) + pos*(Math.log(max)-Math.log(min))); - else - pos = min + pos*(max-min); - return pos; - }; + pt = create$1(clTPaveText); + Object.assign(pt, { + fName: kTitle, fOption: 'blNDC', fFillColor: st.fTitleColor, fFillStyle: st.fTitleStyle, fBorderSize: st.fTitleBorderSize, + fTextFont: st.fTitleFont, fTextSize: st.fTitleFontSize, fTextColor: st.fTitleTextColor, fTextAlign: 22 + }); - const createZoomMesh = (kind, size_3d, use_y_for_z) => { - const geom = new BufferGeometry(), tsz = Math.max(this[kind+'_handle'].ticksSize, 0.005 * size_3d); - let positions; - if (kind === 'z') - positions = new Float32Array([0, 0, 0, tsz*4, 0, 2*size_3d, tsz*4, 0, 0, 0, 0, 0, 0, 0, 2*size_3d, tsz*4, 0, 2*size_3d]); - else - positions = new Float32Array([-size_3d, 0, 0, size_3d, -tsz*4, 0, size_3d, 0, 0, -size_3d, 0, 0, -size_3d, -tsz*4, 0, size_3d, -tsz*4, 0]); + if (draw_title) + pt.AddText(obj.fTitle); - geom.setAttribute('position', new BufferAttribute(positions, 3)); - geom.computeVertexNormals(); + return TPavePainter.draw(pp, pt, kPosTitle).then(p => { + p?.setSecondaryId(painter, kTitle); + return painter; + }); + } - const material = new MeshBasicMaterial({ transparent: true, vertexColors: false, side: DoubleSide, opacity: 0 }), - mesh = new Mesh(geom, material); - mesh.zoom = kind; - mesh.size_3d = size_3d; - mesh.tsz = tsz; - mesh.use_y_for_z = use_y_for_z; - if (kind === 'y') mesh.rotateZ(Math.PI/2).rotateX(Math.PI); + const tpainter = pp?.findPainterFor(null, kTitle, clTPaveText), + pt = tpainter?.getObject(); - mesh.v1 = new Vector3(positions[0], positions[1], positions[2]); - mesh.v2 = new Vector3(positions[6], positions[7], positions[8]); - mesh.v3 = new Vector3(positions[3], positions[4], positions[5]); + if (!tpainter || !pt) + return painter; - mesh.globalIntersect = function(raycaster) { - if (!this.v1 || !this.v2 || !this.v3) return undefined; + pt.Clear(); + if (draw_title) + pt.AddText(obj.fTitle); + return tpainter.redraw().then(() => painter); +} - const plane = new Plane(); - plane.setFromCoplanarPoints(this.v1, this.v2, this.v3); - plane.applyMatrix4(this.matrixWorld); +var TPavePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TPavePainter: TPavePainter, +drawObjectTitle: drawObjectTitle, +kPosTitle: kPosTitle +}); - const v1 = raycaster.ray.origin.clone(), - v2 = v1.clone().addScaledVector(raycaster.ray.direction, 1e10), - pnt = plane.intersectLine(new Line3(v1, v2), new Vector3()); +const kCARTESIAN = 1, kPOLAR = 2, kCYLINDRICAL = 3, kSPHERICAL = 4, kRAPIDITY = 5, + kNormal$1 = 0, kPoisson = 1, kPoisson2 = 2, + kOnlyCheck = 'only-check'; +/** + * @summary Class to decode histograms draw options + * @desc All options started from capital letter are major drawing options + * any other draw options are internal settings. + * @private + */ - if (!pnt) return undefined; +class THistDrawOptions { - let min = -this.size_3d, max = this.size_3d; - if (this.zoom === 'z') { min = 0; max = 2*this.size_3d; } + constructor() { this.reset(); } - if (pnt[this.zoom] < min) - pnt[this.zoom] = min; - else if (pnt[this.zoom] > max) - pnt[this.zoom] = max; + /** @summary Reset hist draw options */ + reset() { + Object.assign(this, { + Axis: 0, RevX: false, RevY: false, SymlogX: 0, SymlogY: 0, xticks: null, yticks: null, + Bar: false, BarStyle: 0, Curve: false, + Hist: 1, Line: false, Fill: false, + Error: 0, ErrorKind: -1, errorX: gStyle.fErrorX, + Mark: false, Same: false, Scat: false, ScatCoef: 1.0, Func: true, AllFunc: false, + Arrow: false, Box: false, BoxStyle: 0, + Text: false, TextAngle: 0, TextKind: '', Char: 0, Color: false, Contour: 0, Cjust: false, + Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0, Ortho: gStyle.fOrthoCamera, + Spec: false, Pie: false, List: false, Zscale: false, Zvert: true, PadPalette: false, + Candle: '', Violin: '', Scaled: null, Circular: 0, Poisson: kNormal$1, + GLBox: 0, GLColor: false, Project: '', ProfileProj: '', Profile2DProj: '', System: kCARTESIAN, + AutoColor: false, NoStat: false, ForceStat: false, PadStats: false, PadTitle: false, AutoZoom: false, + HighRes: 0, Zero: 1, Palette: 0, BaseLine: false, ShowEmpty: false, + Optimize: settings.OptimizeDraw, + Mode3D: false, x3dscale: 1, y3dscale: 1, SwapXY: false, + Render3D: constants$1.Render3D.Default, + FrontBox: true, BackBox: true, + need_fillcol: false, + minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null, + IgnoreMainScale: false, IgnorePalette: false + }); + } - return pnt; - }; + isCartesian() { return this.System === kCARTESIAN; } - mesh.showSelection = function(pnt1, pnt2) { - // used to show selection + is3d() { return this.Lego || this.Surf; } - const kind = this.zoom; - let tgtmesh = this.children ? this.children[0] : null, gg; - if (!pnt1 || !pnt2) { - if (tgtmesh) { - this.remove(tgtmesh); - disposeThreejsObject(tgtmesh); - } - return tgtmesh; + /** @summary Base on sumw2 values (re)set some basic draw options, only for 1dim hist */ + decodeSumw2(histo, force) { + const len = histo.fSumw2?.length ?? 0; + let isany = false; + for (let n = 0; n < len; ++n) { + if (histo.fSumw2[n] > 0) { + isany = true; + break; } + } - if (!this.geometry) return false; - - if (!tgtmesh) { - gg = this.geometry.clone(); - const pos = gg.getAttribute('position').array; + if (Number.isInteger(this.Error) || force) + this.Error = isany ? 1 : 0; - // original vertices [0, 2, 1, 0, 3, 2] - if (kind === 'z') pos[6] = pos[3] = pos[15] = this.tsz; - else pos[4] = pos[16] = pos[13] = -this.tsz; - tgtmesh = new Mesh(gg, new MeshBasicMaterial({ color: 0xFF00, side: DoubleSide, vertexColors: false })); - this.add(tgtmesh); - } else - gg = tgtmesh.geometry; + if (Number.isInteger(this.Hist) || force) + this.Hist = isany ? 0 : 1; + if (Number.isInteger(this.Zero) || force) + this.Zero = isany ? 0 : 1; + } - const pos = gg.getAttribute('position').array; + /** @summary Is palette can be used with current draw options */ + canHavePalette() { + if (this.ndim === 3) + return this.BoxStyle === 12 || this.BoxStyle === 13 || this.GLBox === 12; - if (kind === 'z') { - pos[2] = pos[11] = pos[8] = pnt1[kind]; - pos[5] = pos[17] = pos[14] = pnt2[kind]; - } else { - pos[0] = pos[9] = pos[12] = pnt1[kind]; - pos[6] = pos[3] = pos[15] = pnt2[kind]; - } + if (this.ndim === 1) + return this.Lego === 12 || this.Lego === 14; - gg.getAttribute('position').needsUpdate = true; + if (this.Mode3D) + return this.Lego === 12 || this.Lego === 14 || this.Surf === 11 || this.Surf === 12; + if (this.Color || this.Contour || this.Hist || this.Axis) return true; - }; - - return mesh; - }; - let xcont = new Object3D(), xtickslines; - xcont.position.set(0, grminy, grminz); - xcont.rotation.x = 1/4*Math.PI; - xcont.xyid = 2; - - if (opts.draw) { - xtickslines = createLineSegments(ticks, getLineMaterial(this.x_handle, 'ticks')); - xcont.add(xtickslines); + return !this.Scat && !this.Box && !this.Arrow && !this.Proj && !this.Candle && !this.Violin && !this.Text; } - lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posx = lbl.center ? lbl.grx - w/2 : (lbl.opposite ? grminx : grmaxx - w), - m = new Matrix4(); + /** @summary Decode histogram draw options */ + decode(opt, hdim, histo, pp, pad, painter) { + this.orginal = opt; // will be overwritten by storeDrawOpt call - // matrix to swap y and z scales and shift along z to its position - m.set(text_scale, 0, 0, posx, - 0, text_scale, 0, -maxtextheight*text_scale - this.x_handle.ticksSize - lbl.offsety, - 0, 0, 1, 0, - 0, 0, 0, 1); + this.cutg_name = ''; + if (isStr(opt) && (hdim === 2)) { + const p1 = opt.lastIndexOf('['), p2 = opt.lastIndexOf(']'); + if ((p1 >= 0) && (p2 > p1 + 1) && (opt.at(p1 - 1) !== ':')) { + this.cutg_name = opt.slice(p1 + 1, p2); + opt = opt.slice(0, p1) + opt.slice(p2 + 1); + this.cutg = pp?.findInPrimitives(this.cutg_name, clTCutG); + if (this.cutg) + this.cutg.$redraw_pad = true; + } + } - const mesh = new Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); - mesh.applyMatrix4(m); - xcont.add(mesh); - }); + const d = new DrawOptions(opt); - if (opts.zoom && opts.drawany) - xcont.add(createZoomMesh('x', this.size_x3d)); - top.add(xcont); + if (hdim === 1) + this.decodeSumw2(histo, true); - xcont = new Object3D(); - xcont.position.set(0, grmaxy, grminz); - xcont.rotation.x = 3/4*Math.PI; + this.ndim = hdim || 1; // keep dimensions, used for now in GED - if (opts.draw) - xcont.add(new LineSegments(xtickslines.geometry, xtickslines.material)); + // for old web canvas json + // TODO: remove in version 8 + d.check('USE_PAD_TITLE'); + d.check('USE_PAD_PALETTE'); + d.check('USE_PAD_STATS'); - lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posx = (lbl.center ? lbl.grx + w/2 : lbl.opposite ? grminx + w : grmaxx), - m = new Matrix4(); + if (d.check('IGNORE_PALETTE')) + this.IgnorePalette = true; - // matrix to swap y and z scales and shift along z to its position - m.set(-text_scale, 0, 0, posx, - 0, text_scale, 0, -maxtextheight*text_scale - this.x_handle.ticksSize - lbl.offsety, - 0, 0, -1, 0, - 0, 0, 0, 1); - const mesh = new Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); - mesh.applyMatrix4(m); - xcont.add(mesh); - }); + if (d.check('PAL', true)) + this.Palette = d.partAsInt(); + // this is zooming of histogram content + if (d.check('MINIMUM:', true)) { + this.ominimum = true; + this.minimum = parseFloat(d.part); + } else { + this.ominimum = false; + this.minimum = histo.fMinimum; + } + if (d.check('MAXIMUM:', true)) { + this.omaximum = true; + this.maximum = parseFloat(d.part); + } else { + this.omaximum = false; + this.maximum = histo.fMaximum; + } + if (!this.ominimum && !this.omaximum && this.minimum === this.maximum) + this.minimum = this.maximum = kNoZoom; - xcont.xyid = 4; - if (opts.zoom && opts.drawany) - xcont.add(createZoomMesh('x', this.size_x3d)); - top.add(xcont); + this.ohmin = d.check('HMIN:', true); + this.hmin = this.ohmin ? parseFloat(d.part) : undefined; + this.ohmax = d.check('HMAX:', true); + this.hmax = this.ohmax ? parseFloat(d.part) : undefined; + this.zoom_min_max = d.check('ZOOM_MIN_MAX'); - lbls = []; text_scale = 1; maxtextheight = 0; ticks = []; + // let configure histogram titles - only for debug purposes + if (d.check('HTITLE:', true)) + histo.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('XTITLE:', true)) + histo.fXaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('YTITLE:', true)) + histo.fYaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('ZTITLE:', true)) + histo.fZaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('POISSON2')) + this.Poisson = kPoisson2; + if (d.check('POISSON')) + this.Poisson = kPoisson; + + if (d.check('SHOWEMPTY')) + this.ShowEmpty = true; + + if (d.check('NOOPTIMIZE')) + this.Optimize = 0; + if (d.check('OPTIMIZE')) + this.Optimize = 2; + + if (d.check('AUTOCOL')) + this.AutoColor = true; + if (d.check('AUTOZOOM')) + this.AutoZoom = true; + + if (d.check('OPTSTAT', true)) + this.optstat = d.partAsInt(); + if (d.check('OPTFIT', true)) + this.optfit = d.partAsInt(); + + if (d.check('XTICKS:', 'array')) + this.xticks = d.array; + if ((this.ndim > 1) && d.check('YTICKS:', 'array')) + this.yticks = d.array; + + if (this.optstat || this.optfit) + histo?.SetBit(kNoStats, false); + + if (d.check('ALLBINS') && histo) { + histo.fXaxis.fFirst = 0; + histo.fXaxis.fLast = histo.fXaxis.fNbins + 1; + histo.fXaxis.SetBit(EAxisBits.kAxisRange); + if (this.ndim > 1) { + histo.fYaxis.fFirst = 0; + histo.fYaxis.fLast = histo.fYaxis.fNbins + 1; + histo.fYaxis.SetBit(EAxisBits.kAxisRange); + } + if (this.ndim > 2) { + histo.fZaxis.fFirst = 0; + histo.fZaxis.fLast = histo.fZaxis.fNbins + 1; + histo.fZaxis.SetBit(EAxisBits.kAxisRange); + } + } + + if (d.check('NOSTAT')) + this.NoStat = true; + if (d.check('STAT')) + this.ForceStat = true; + + if (d.check('NOTOOLTIP')) + painter?.setTooltipAllowed(false); + if (d.check('TOOLTIP')) + painter?.setTooltipAllowed(true); + + if (d.check('SYMLOGX', true)) + this.SymlogX = d.partAsInt(0, 3); + if (d.check('SYMLOGY', true)) + this.SymlogY = d.partAsInt(0, 3); + + if (d.check('X3DSC', true)) + this.x3dscale = d.partAsInt(0, 100) / 100; + if (d.check('Y3DSC', true)) + this.y3dscale = d.partAsInt(0, 100) / 100; + + if (d.check('PERSPECTIVE') || d.check('PERSP')) + this.Ortho = false; + if (d.check('ORTHO')) + this.Ortho = true; - while (yticks.next()) { - const gry = yticks.grpos; - let is_major = (yticks.kind === 1), - lbl = this.y_handle.format(yticks.tick, 2); + let lx = 0, ly = 0, check3dbox = ''; + if (d.check('LOG2XY')) + lx = ly = 2; + if (d.check('LOGXY')) + lx = ly = 1; + if (d.check('LOG2X')) + lx = 2; + if (d.check('LOGX')) + lx = 1; + if (d.check('LOG2Y')) + ly = 2; + if (d.check('LOGY')) + ly = 1; + if (lx && pad) { + pad.fLogx = lx; + pad.fUxmin = 0; + pad.fUxmax = 1; + pad.fX1 = 0; + pad.fX2 = 1; + } + if (ly && pad) { + pad.fLogy = ly; + pad.fUymin = 0; + pad.fUymax = 1; + pad.fY1 = 0; + pad.fY2 = 1; + } + if (d.check('LOG2Z') && pad) + pad.fLogz = 2; + if (d.check('LOGZ') && pad) + pad.fLogz = 1; + if (d.check('LOGV') && pad) + pad.fLogv = 1; // fictional member, can be introduced in ROOT + if (d.check('GRIDXY') && pad) + pad.fGridx = pad.fGridy = 1; + if (d.check('GRIDX') && pad) + pad.fGridx = 1; + if (d.check('GRIDY') && pad) + pad.fGridy = 1; + if (d.check('TICKXY2') && pad) + pad.fTickx = pad.fTicky = 2; + if (d.check('TICKX2') && pad) + pad.fTickx = 2; + if (d.check('TICKY2') && pad) + pad.fTicky = 2; + if (d.check('TICKZ2') && pad) + pad.fTickz = 2; + if (d.check('TICKXY') && pad) + pad.fTickx = pad.fTicky = 1; + if (d.check('TICKX') && pad) + pad.fTickx = 1; + if (d.check('TICKY') && pad) + pad.fTicky = 1; + if (d.check('TICKZ') && pad) + pad.fTickz = 1; + if (d.check('GRAYSCALE')) + pp?.setGrayscale(true); - if (yticks.last_major()) { - if (!this.y_handle.fTitle) lbl = 'y'; - } else if (lbl === null) { - is_major = false; lbl = ''; + if (d.check('FILL_', 'color')) { + this.histoFillColor = d.color; + this.histoFillPattern = 1001; } - if (is_major && lbl && opts.draw) { - const mod = yticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; - - const text3d = createTextGeometry(this, lbl, this.y_handle.labelsFont.size); - text3d.computeBoundingBox(); - const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - text3d.center = true; + if (d.check('FILLPAT_', true)) + this.histoFillPattern = d.partAsInt(); - maxtextheight = Math.max(maxtextheight, draw_height); + if (d.check('LINE_', 'color')) + this.histoLineColor = painter.getColor(d.color); - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); - text3d.gry = gry; - text3d.offsetx = this.y_handle.labelsOffset + (grmaxx - grminx) * 0.005; - lbls.push(text3d); + if (d.check('WIDTH_', true)) + this.histoLineWidth = d.partAsInt(); - let space = 0; - if (!yticks.last_major()) { - space = Math.abs(yticks.next_major_grpos() - gry); - if (draw_width > 0) - text_scale = Math.min(text_scale, 0.9*space/draw_width); - } - if (this.y_handle.isCenteredLabels()) { - if (!space) space = Math.min(gry - grminy, grmaxy - gry); - text3d.gry += space/2; - } - } - ticks.push(0, gry, 0, this.y_handle.ticksSize*(is_major ? -1 : -0.6), gry, 0); - } + if (d.check('XAXIS_', 'color')) + histo.fXaxis.fAxisColor = histo.fXaxis.fLabelColor = histo.fXaxis.fTitleColor = d.color; - if (this.y_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.y_handle.fTitle, this.y_handle.titleFont.size); - text3d.computeBoundingBox(); - text3d.center = this.y_handle.titleCenter; - text3d.opposite = this.y_handle.titleOpposite; - text3d.offsetx = 1.6 * this.y_handle.titleOffset + (grmaxx - grminx) * 0.005; - text3d.gry = (grminy + grmaxy)/2; // default position for centered title - text3d.kind = 'title'; - lbls.push(text3d); - } + if (d.check('YAXIS_', 'color')) + histo.fYaxis.fAxisColor = histo.fYaxis.fLabelColor = histo.fYaxis.fTitleColor = d.color; - if (!opts.use_y_for_z) { - let yticksline, ycont = new Object3D(); - ycont.position.set(grminx, 0, grminz); - ycont.rotation.y = -1/4*Math.PI; - if (opts.draw) { - yticksline = createLineSegments(ticks, getLineMaterial(this.y_handle, 'ticks')); - ycont.add(yticksline); + if (d.check('X+')) { + this.AxisPos = 10; + this.second_x = Boolean(painter?.getMainPainter()); + } + if (d.check('Y+')) { + this.AxisPos += 1; + this.second_y = Boolean(painter?.getMainPainter()); } - lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posy = lbl.center ? lbl.gry + w/2 : (lbl.opposite ? grminy + w : grmaxy), - m = new Matrix4(); - // matrix to swap y and z scales and shift along z to its position - m.set(0, text_scale, 0, -maxtextheight*text_scale - this.y_handle.ticksSize - lbl.offsetx, - -text_scale, 0, 0, posy, - 0, 0, 1, 0, - 0, 0, 0, 1); + if (d.check('SAME0')) + this.Same = this.IgnoreMainScale = true; + if (d.check('SAMES')) + this.Same = this.ForceStat = true; + if (d.check('SAME')) + this.Same = this.Func = true; - const mesh = new Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); - mesh.applyMatrix4(m); - ycont.add(mesh); - }); + if (d.check('SPEC')) + this.Spec = true; // not used - ycont.xyid = 3; - if (opts.zoom && opts.drawany) - ycont.add(createZoomMesh('y', this.size_y3d)); - top.add(ycont); + if (d.check('BASE0') || d.check('MIN0')) + this.BaseLine = 0; + else if (gStyle.fHistMinimumZero) + this.BaseLine = 0; - ycont = new Object3D(); - ycont.position.set(grmaxx, 0, grminz); - ycont.rotation.y = -3/4*Math.PI; - if (opts.draw) - ycont.add(new LineSegments(yticksline.geometry, yticksline.material)); + if (d.check('PIE')) + this.Pie = true; // not used - lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posy = lbl.center ? lbl.gry - w/2 : (lbl.opposite ? grminy : grmaxy - w), - m = new Matrix4(); - m.set(0, text_scale, 0, -maxtextheight*text_scale - this.y_handle.ticksSize - lbl.offsetx, - text_scale, 0, 0, posy, - 0, 0, -1, 0, - 0, 0, 0, 1); + if (d.check('CANDLE', true)) + this.Candle = d.part || '1'; + if (d.check('VIOLIN', true)) { + this.Violin = d.part || '1'; + delete this.Candle; + } + if (d.check('NOSCALED')) + this.Scaled = false; + if (d.check('SCALED')) + this.Scaled = true; - const mesh = new Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); - mesh.applyMatrix4(m); - ycont.add(mesh); - }); - ycont.xyid = 1; - if (opts.zoom && opts.drawany) - ycont.add(createZoomMesh('y', this.size_y3d)); - top.add(ycont); - } + if (d.check('GLBOX', true)) + this.GLBox = 10 + d.partAsInt(); + if (d.check('GLCOL')) + this.GLColor = true; - lbls = []; text_scale = 1; ticks = []; // just array, will be used for the buffer geometry + d.check('GL'); // suppress GL - let zgridx = null, zgridy = null, lastmajorz = null, maxzlblwidth = 0; + if (d.check('CIRCULAR', true) || d.check('CIRC', true)) { + this.Circular = 11; + if (d.part.indexOf('0') >= 0) + this.Circular = 10; // black and white + if (d.part.indexOf('1') >= 0) + this.Circular = 11; // color + if (d.part.indexOf('2') >= 0) + this.Circular = 12; // color and width + } - if (this.size_z3d && opts.drawany) { - zgridx = []; zgridy = []; - } + this.Chord = d.check('CHORD'); - while (zticks.next()) { - const grz = zticks.grpos; - let is_major = (zticks.kind === 1), - lbl = this.z_handle.format(zticks.tick, 2); + if (d.check('LEGO', true)) { + this.Lego = 1; + if (d.part.indexOf('0') >= 0) + this.Zero = false; + if (d.part.indexOf('1') >= 0) + this.Lego = 11; + if (d.part.indexOf('2') >= 0) + this.Lego = 12; + if (d.part.indexOf('3') >= 0) + this.Lego = 13; + if (d.part.indexOf('4') >= 0) + this.Lego = 14; + check3dbox = d.part; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; + } - if (lbl === null) { is_major = false; lbl = ''; } + if (d.check('R3D_', true)) + this.Render3D = constants$1.Render3D.fromString(d.part.toLowerCase()); - if (is_major && lbl && opts.draw) { - const mod = zticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; + if (d.check('POL')) + this.System = kPOLAR; + if (d.check('CYL')) + this.System = kCYLINDRICAL; + if (d.check('SPH')) + this.System = kSPHERICAL; + if (d.check('PSR')) + this.System = kRAPIDITY; - const text3d = createTextGeometry(this, lbl, this.z_handle.labelsFont.size); - text3d.computeBoundingBox(); - const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - text3d.translate(-draw_width, -draw_height/2, 0); + if (d.check('SURF', true)) { + this.Surf = d.partAsInt(10, 1); + check3dbox = d.part; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; + } - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); - text3d.grz = grz; - lbls.push(text3d); + if (d.check('TF3', true)) + check3dbox = d.part; - if ((lastmajorz !== null) && (draw_height > 0)) - text_scale = Math.min(text_scale, 0.9*(grz - lastmajorz)/draw_height); + if (d.check('ISO', true)) + check3dbox = d.part; - maxzlblwidth = Math.max(maxzlblwidth, draw_width); + if (d.check('LIST')) + this.List = true; // not used - lastmajorz = grz; + if (d.check('CONT', true) && (hdim > 1)) { + this.Contour = 1; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; + if (d.part.indexOf('1') >= 0) + this.Contour = 11; + else if (d.part.indexOf('2') >= 0) + this.Contour = 12; + else if (d.part.indexOf('3') >= 0) + this.Contour = 13; + else if (d.part.indexOf('4') >= 0) + this.Contour = 14; } - // create grid - if (zgridx && is_major) - zgridx.push(grminx, 0, grz, grmaxx, 0, grz); - - if (zgridy && is_major) - zgridy.push(0, grminy, grz, 0, grmaxy, grz); - - ticks.push(0, 0, grz, this.z_handle.ticksSize*(is_major ? 1 : 0.6), 0, grz); - } - - if (zgridx && (zgridx.length > 0)) { - const material = new LineDashedMaterial({ color: this.x_handle.ticksColor, dashSize: 2, gapSize: 2 }), - lines1 = createLineSegments(zgridx, material); + // decode bar/hbar option + if (d.check('HBAR', true)) + this.BarStyle = 20; + else if (d.check('BAR', true)) + this.BarStyle = 10; + if (this.BarStyle > 0) { + this.Hist = false; + this.need_fillcol = true; + this.BarStyle += d.partAsInt(); + } - lines1.position.set(0, grmaxy, 0); - lines1.grid = 2; // mark as grid - lines1.visible = false; - top.add(lines1); + if (d.check('ARR')) + this.Arrow = true; - const lines2 = new LineSegments(lines1.geometry, material); - lines2.position.set(0, grminy, 0); - lines2.grid = 4; // mark as grid - lines2.visible = false; - top.add(lines2); - } + if (d.check('BOX', true)) { + this.BoxStyle = 10; + if (d.part.indexOf('1') >= 0) + this.BoxStyle = 11; + else if (d.part.indexOf('2') >= 0) + this.BoxStyle = 12; + else if (d.part.indexOf('3') >= 0) + this.BoxStyle = 13; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; + } - if (zgridy && (zgridy.length > 0)) { - const material = new LineDashedMaterial({ color: this.y_handle.ticksColor, dashSize: 2, gapSize: 2 }), - lines1 = createLineSegments(zgridy, material); + this.Box = this.BoxStyle > 0; - lines1.position.set(grmaxx, 0, 0); - lines1.grid = 3; // mark as grid - lines1.visible = false; - top.add(lines1); + if (d.check('CJUST')) + this.Cjust = true; + if (d.check('COL7')) + this.Color = 7; // special color mode with use of bar offset + if (d.check('COL')) + this.Color = true; + if (d.check('CHAR')) + this.Char = 1; + if (d.check('ALLFUNC')) + this.AllFunc = true; + if (d.check('FUNC')) { + this.Func = true; + this.Hist = false; + } + if (d.check('HAXISG')) { + this.Axis = 3; + this.SwapXY = 1; + } + if (d.check('HAXIS')) { + this.Axis = 1; + this.SwapXY = 1; + } + if (d.check('HAXIG')) { + this.Axis = 2; + this.SwapXY = 1; + } + if (d.check('AXISG')) + this.Axis = 3; + if (d.check('AXIS')) + this.Axis = 1; + if (d.check('AXIG')) + this.Axis = 2; - const lines2 = new LineSegments(lines1.geometry, material); - lines2.position.set(grminx, 0, 0); - lines2.grid = 1; // mark as grid - lines2.visible = false; - top.add(lines2); - } + if (d.check('TEXT', true)) { + this.Text = true; + this.Hist = false; + this.TextAngle = Math.min(d.partAsInt(), 90); + if (d.part.indexOf('N') >= 0) + this.TextKind = 'N'; + if (d.part.indexOf('E0') >= 0) + this.TextLine = true; + if (d.part.indexOf('E') >= 0) + this.TextKind = 'E'; + } - const zcont = [], zticksline = opts.draw ? createLineSegments(ticks, getLineMaterial(this.z_handle, 'ticks')) : null; - for (let n = 0; n < 4; ++n) { - zcont.push(new Object3D()); + if (d.check('SCAT=', true)) { + this.Scat = true; + this.ScatCoef = parseFloat(d.part); + if (!Number.isFinite(this.ScatCoef) || (this.ScatCoef <= 0)) + this.ScatCoef = 1.0; + } - lbls.forEach((lbl, indx) => { - const m = new Matrix4(); - let grz = lbl.grz; + if (d.check('SCAT')) + this.Scat = true; - if (this.z_handle.isCenteredLabels()) { - if (indx < lbls.length - 1) - grz = (grz + lbls[indx+1].grz) / 2; - else if (indx > 0) - grz = Math.min(1.5*grz - lbls[indx-1].grz*0.5, grmaxz); - } + if (d.check('TRI', true)) { + this.Color = false; + this.Tri = 1; + check3dbox = d.part; + if (d.part.indexOf('ERR') >= 0) + this.Error = true; + } + + if (d.check('AITOFF')) + this.Proj = 1; + if (d.check('MERCATOR')) + this.Proj = 2; + if (d.check('SINUSOIDAL')) + this.Proj = 3; + if (d.check('PARABOLIC')) + this.Proj = 4; + if (d.check('MOLLWEIDE')) + this.Proj = 5; + if (this.Proj > 0) + this.Contour = 14; + + if (d.check('PROJXY', true)) { + let flag = true; + if ((histo?._typename === clTProfile2D) && d.part && !Number.isInteger(Number.parseInt(d.part))) { + this.Profile2DProj = d.part; + flag = d.check('PROJXY', true); // allow projxy with projected profile2d + } + if (flag) + this.Project = 'XY' + d.partAsInt(0, 1); + } + + if (d.check('PROJX', true)) { + if (histo?._typename === clTProfile) + this.ProfileProj = d.part || 'B'; + else + this.Project = 'X' + d.part; + } + if (d.check('PROJY', true)) + this.Project = 'Y' + d.part; + if (d.check('PROJ')) + this.Project = 'Y1'; - // matrix to swap y and z scales and shift along z to its position - m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + this.z_handle.labelsOffset, - 0, 0, 1, 0, - 0, text_scale, 0, grz); - const mesh = new Mesh(lbl, getTextMaterial(this.z_handle)); - mesh.applyMatrix4(m); - zcont[n].add(mesh); - }); + if (check3dbox) { + if (check3dbox.indexOf('FB') >= 0) + this.FrontBox = false; + if (check3dbox.indexOf('BB') >= 0) + this.BackBox = false; + } - if (this.z_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.z_handle.fTitle, this.z_handle.titleFont.size); - text3d.computeBoundingBox(); - const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - posz = this.z_handle.titleCenter ? (grmaxz + grminz - draw_width)/2 : (this.z_handle.titleOpposite ? grminz : grmaxz - draw_width); + if ((hdim === 3) && d.check('FB')) + this.FrontBox = false; + if ((hdim === 3) && d.check('BB')) + this.BackBox = false; - text3d.rotateZ(Math.PI/2); + if (d.check('PFC') && !this._pfc) + this._pfc = 2; + if ((d.check('PLC') || this.AutoColor) && !this._plc) + this._plc = 2; + if (d.check('PMC') && !this._pmc) + this._pmc = 2; - const m = new Matrix4(); - m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + maxzlblwidth + this.z_handle.titleOffset, - 0, 0, 1, 0, - 0, text_scale, 0, posz); - const mesh = new Mesh(text3d, getTextMaterial(this.z_handle, 'title')); - mesh.applyMatrix4(m); - zcont[n].add(mesh); - } + const check_axis_bit = (aopt, axis, bit) => { + // ignore Z scale options for 2D plots + if ((axis === 'fZaxis') && (hdim < 3) && !this.Lego && !this.Surf) + return; + let flag = d.check(aopt); + if (pad && pad['$' + aopt]) { + flag = true; + pad['$' + aopt] = undefined; + } + if (flag && histo) + histo[axis].SetBit(bit, true); + }; - if (opts.draw && zticksline) - zcont[n].add(n === 0 ? zticksline : new LineSegments(zticksline.geometry, zticksline.material)); + check_axis_bit('OTX', 'fXaxis', EAxisBits.kOppositeTitle); + check_axis_bit('OTY', 'fYaxis', EAxisBits.kOppositeTitle); + check_axis_bit('OTZ', 'fZaxis', EAxisBits.kOppositeTitle); + check_axis_bit('CTX', 'fXaxis', EAxisBits.kCenterTitle); + check_axis_bit('CTY', 'fYaxis', EAxisBits.kCenterTitle); + check_axis_bit('CTZ', 'fZaxis', EAxisBits.kCenterTitle); + check_axis_bit('MLX', 'fXaxis', EAxisBits.kMoreLogLabels); + check_axis_bit('MLY', 'fYaxis', EAxisBits.kMoreLogLabels); + check_axis_bit('MLZ', 'fZaxis', EAxisBits.kMoreLogLabels); + check_axis_bit('NOEX', 'fXaxis', EAxisBits.kNoExponent); + check_axis_bit('NOEY', 'fYaxis', EAxisBits.kNoExponent); + check_axis_bit('NOEZ', 'fZaxis', EAxisBits.kNoExponent); - if (opts.zoom && opts.drawany) - zcont[n].add(createZoomMesh('z', this.size_z3d, opts.use_y_for_z)); + if (d.check('RX') || pad?.$RX) + this.RevX = true; + if (d.check('RY') || pad?.$RY) + this.RevY = true; - zcont[n].zid = n + 2; - top.add(zcont[n]); - } + if (d.check('L')) { + this.Line = true; + this.Hist = false; + } + if (d.check('F')) { + this.Fill = true; + this.need_fillcol = true; + } - zcont[0].position.set(grminx, grmaxy, 0); - zcont[0].rotation.z = 3/4*Math.PI; + if (d.check('A')) + this.Axis = -1; + if (pad?.$ratio_pad === 'up') { + if (!this.Same) + this.Axis = 0; // draw both axes + histo.fXaxis.fLabelSize = 0; + histo.fXaxis.fTitle = ''; + histo.fYaxis.$use_top_pad = true; + } else if (pad?.$ratio_pad === 'low') { + if (!this.Same) + this.Axis = 0; // draw both axes + histo.fXaxis.$use_top_pad = true; + histo.fYaxis.$use_top_pad = true; + histo.fXaxis.fTitle = 'x'; + const fp = painter?.getCanvPainter().findPainterFor(null, 'upper_pad', clTPad)?.getFramePainter(); + if (fp) { + painter.zoom_xmin = fp.scale_xmin; + painter.zoom_xmax = fp.scale_xmax; + } + } - zcont[1].position.set(grmaxx, grmaxy, 0); - zcont[1].rotation.z = 1/4*Math.PI; + if (d.check('B1')) { + this.BarStyle = 1; + this.BaseLine = 0; + this.Hist = false; + this.need_fillcol = true; + } + if (d.check('B')) { + this.BarStyle = 1; + this.Hist = false; + this.need_fillcol = true; + } + if (d.check('C')) { + this.Curve = true; + this.Hist = false; + } + if (d.check('][')) { + this.Off = 1; + this.Hist = true; + } - zcont[2].position.set(grmaxx, grminy, 0); - zcont[2].rotation.z = -1/4*Math.PI; + if (d.check('HIST')) { + this.Hist = true; + this.Func = true; + this.Error = false; + } - zcont[3].position.set(grminx, grminy, 0); - zcont[3].rotation.z = -3/4*Math.PI; + this.Bar = (this.BarStyle > 0); - if (!opts.drawany) - return; + delete this.MarkStyle; // remove mark style if any - const linex_material = getLineMaterial(this.x_handle), - linex_geom = createLineSegments([grminx, 0, 0, grmaxx, 0, 0], linex_material, null, true); - for (let n = 0; n < 2; ++n) { - let line = new LineSegments(linex_geom, linex_material); - line.position.set(0, grminy, n === 0 ? grminz : grmaxz); - line.xyboxid = 2; line.bottom = (n === 0); - top.add(line); + if (d.check('P0')) { + this.Mark = true; + this.Hist = false; + this.Zero = true; + } + if (d.check('P')) { + this.Mark = true; + this.Hist = false; + this.Zero = false; + } + if (d.check('HZ')) { + this.Zscale = true; + this.Zvert = false; + } + if (d.check('Z')) + this.Zscale = true; + if (d.check('*')) { + this.Mark = true; + this.MarkStyle = 3; + this.Hist = false; + } + if (d.check('H')) + this.Hist = true; - line = new LineSegments(linex_geom, linex_material); - line.position.set(0, grmaxy, n === 0 ? grminz : grmaxz); - line.xyboxid = 4; line.bottom = (n === 0); - top.add(line); - } + if (d.check('E', true)) { + this.Error = true; + if (hdim === 1) { + this.Zero = false; // do not draw empty bins with errors + if (this.Hist === 1) + this.Hist = false; + if (Number.isInteger(parseInt(d.part[0]))) + this.ErrorKind = parseInt(d.part[0]); + if ((this.ErrorKind === 3) || (this.ErrorKind === 4)) + this.need_fillcol = true; + if (this.ErrorKind === 0) + this.Zero = true; // enable drawing of empty bins + if (d.part.indexOf('X0') >= 0) + this.errorX = 0; + } + } + if (d.check('9')) + this.HighRes = 1; + if (d.check('0')) + this.Zero = false; + if (this.Color && d.check('1')) + this.Zero = false; - const liney_material = getLineMaterial(this.y_handle), - liney_geom = createLineSegments([0, grminy, 0, 0, grmaxy, 0], liney_material, null, true); - for (let n = 0; n < 2; ++n) { - let line = new LineSegments(liney_geom, liney_material); - line.position.set(grminx, 0, n === 0 ? grminz : grmaxz); - line.xyboxid = 3; line.bottom = (n === 0); - top.add(line); + // flag identifies 3D drawing mode for histogram + if ((this.Lego > 0) || (hdim === 3) || (((this.Surf > 0) || this.Error) && (hdim === 2))) + this.Mode3D = true; - line = new LineSegments(liney_geom, liney_material); - line.position.set(grmaxx, 0, n === 0 ? grminz : grmaxz); - line.xyboxid = 1; line.bottom = (n === 0); - top.add(line); - } + // default draw options for TF1 is line and fill + if (painter?.isTF1() && (hdim === 1) && (this.Hist === 1) && !this.Line && !this.Fill && !this.Curve && !this.Mark) { + this.Hist = false; + this.Curve = settings.FuncAsCurve; + this.Line = !this.Curve; + this.Fill = true; + } - const linez_material = getLineMaterial(this.z_handle), - linez_geom = createLineSegments([0, 0, grminz, 0, 0, grmaxz], linez_material, null, true); - for (let n = 0; n < 4; ++n) { - const line = new LineSegments(linez_geom, linez_material); - line.zboxid = zcont[n].zid; - line.position.copy(zcont[n].position); - top.add(line); + if ((this.Surf === 15) && (this.System === kPOLAR || this.System === kCARTESIAN)) + this.Surf = 13; } -} + /** @summary Is X/Y swap is configured */ + swap_xy() { return this.BarStyle >= 20 || this.SwapXY; } -/** @summary Converts 3D coordiante to the pad NDC - * @private */ -function convert3DtoPadNDC(x, y, z) { - x = this.x_handle.gr(x); - y = this.y_handle.gr(y); - z = this.z_handle.gr(z); - - const vector = new Vector3().set(x, y, z); + /** @summary Tries to reconstruct string with hist draw options */ + asString(is_main_hist, pad) { + let res = '', zopt = ''; + if (this.Zscale) + zopt = this.Zvert ? 'Z' : 'HZ'; + if (this.Mode3D) { + if (this.Lego) { + res = 'LEGO'; + if (!this.Zero) + res += '0'; + if (this.Lego > 10) + res += (this.Lego - 10); + res += zopt; + } else if (this.Surf) { + res = 'SURF' + (this.Surf - 10); + res += zopt; + } + if (!this.FrontBox) + res += 'FB'; + if (!this.BackBox) + res += 'BB'; - // map to normalized device coordinate (NDC) space - vector.project(this.camera); + if (this.x3dscale !== 1) + res += `_X3DSC${Math.round(this.x3dscale * 100)}`; + if (this.y3dscale !== 1) + res += `_Y3DSC${Math.round(this.y3dscale * 100)}`; + } else { + if (this.Candle) + res = 'CANDLE' + this.Candle; + else if (this.Violin) + res = 'VIOLIN' + this.Violin; + else if (this.Scat) + res = 'SCAT'; + else if (this.Color) { + res = 'COL'; + if (!this.Zero) + res += '0'; + res += zopt; + if (this.Axis < 0) + res += 'A'; + } else if (this.Contour) { + res = 'CONT'; + if (this.Contour > 10) + res += (this.Contour - 10); + res += zopt; + } else if (this.Bar) + res = (this.BaseLine === false) ? 'B' : 'B1'; + else if (this.Mark) + res = this.Zero ? 'P0' : 'P'; // here invert logic with 0 + else if (this.Line) { + res += 'L'; + if (this.Fill) + res += 'F'; + } else if (this.Off) + res = ']['; - vector.x = (vector.x + 1) / 2; - vector.y = (vector.y + 1) / 2; + if (this.Error) { + res += 'E'; + if (this.ErrorKind >= 0) + res += this.ErrorKind; + if (this.errorX === 0) + res += 'X0'; + } - const pp = this.getPadPainter(), - pw = pp?.getPadWidth(), - ph = pp?.getPadHeight(); + if (this.Cjust) + res += ' CJUST'; - if (pw && ph) { - vector.x = (this.scene_x + vector.x * this.scene_width) / pw; - vector.y = (this.scene_y + vector.y * this.scene_height) / ph; - } + if (this.Hist === true) + res += 'HIST'; - return vector; -} + if (this.Text) { + res += 'TEXT'; + if (this.TextAngle) + res += this.TextAngle; + res += this.TextKind; + } + } -/** @summary Assign 3D methods for frame painter - * @private */ -function assignFrame3DMethods(fpainter) { - Object.assign(fpainter, { create3DScene, add3DMesh, remove3DMeshes, render3D, resize3D, change3DCamera, highlightBin3D, set3DOptions, drawXYZ, convert3DtoPadNDC }); -} + if (this.Palette && this.canHavePalette()) + res += `_PAL${this.Palette}`; -/** @summary Draw histograms in 3D mode - * @private */ -function drawBinsLego(painter, is_v7 = false) { - if (!painter.draw_content) return; + if (this.is3d() && this.Ortho && is_main_hist) + res += '_ORTHO'; - // Perform TH1/TH2 lego plot with BufferGeometry + if (this.ProfileProj) + res += '_PROJX' + this.ProfileProj; - const vertices = Box3D.Vertices, - indicies = Box3D.Indexes, - vnormals = Box3D.Normals, - segments = Box3D.Segments, - // reduced line segments - rsegments = [0, 1, 1, 2, 2, 3, 3, 0], - // reduced vertices - rvertices = [new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0)], - main = painter.getFramePainter(), - handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), - test_cutg = painter.options.cutg, - i1 = handle.i1, i2 = handle.i2, j1 = handle.j1, j2 = handle.j2, - histo = painter.getHisto(), - basehisto = histo.$baseh, - split_faces = (painter.options.Lego === 11) || (painter.options.Lego === 13), // split each layer on two parts - use16indx = (histo.getBin(i2, j2) < 0xFFFF); // if bin ID fit into 16 bit, use smaller arrays for intersect indexes + if (this.Profile2DProj) + res += '_PROJXY' + this.Profile2DProj; - if ((i1 >= i2) || (j1 >= j2)) return; + if (this.Proj) + res += '_PROJ' + this.Proj; - let zmin, zmax, i, j, k, vert, x1, x2, y1, y2, binz1, binz2, reduced, nobottom, notop, - axis_zmin = main.z_handle.getScaleMin(), - axis_zmax = main.z_handle.getScaleMax(); + if (this.ShowEmpty) + res += '_SHOWEMPTY'; - const getBinContent = (ii, jj, level) => { - // return bin content in binz1, binz2, reduced flags - // return true if bin should be displayed + if (this.Same) + res += this.ForceStat ? 'SAMES' : 'SAME'; + else if (is_main_hist && res) { + if (this.ForceStat || (this.StatEnabled === true)) + res += '_STAT'; + else if (this.NoStat || (this.StatEnabled === false)) + res += '_NOSTAT'; + } - binz2 = histo.getBinContent(ii+1, jj+1); - if (basehisto) - binz1 = basehisto.getBinContent(ii+1, jj+1); - else if (painter.options.BaseLine !== false) - binz1 = painter.options.BaseLine; - else - binz1 = painter.options.Zero ? axis_zmin : 0; - if (binz2 < binz1) - [binz1, binz2] = [binz2, binz1]; + if (is_main_hist && pad && res) { + if (pad.fLogx === 2) + res += '_LOG2X'; + else if (pad.fLogx) + res += '_LOGX'; + if (pad.fLogy === 2) + res += '_LOG2Y'; + else if (pad.fLogy) + res += '_LOGY'; + if (pad.fLogz === 2) + res += '_LOG2Z'; + else if (pad.fLogz) + res += '_LOGZ'; + if (pad.fGridx) + res += '_GRIDX'; + if (pad.fGridy) + res += '_GRIDY'; + if (pad.fTickx === 2) + res += '_TICKX2'; + else if (pad.fTickx) + res += '_TICKX'; + if (pad.fTicky === 2) + res += '_TICKY2'; + else if (pad.fTicky) + res += '_TICKY'; + if (pad.fTickz === 2) + res += '_TICKZ2'; + else if (pad.fTickz) + res += '_TICKZ'; + } - if ((binz1 >= zmax) || (binz2 < zmin)) return false; + if (this.cutg_name) + res += ` [${this.cutg_name}]`; - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(ii + 0.5), - histo.fYaxis.GetBinCoord(jj + 0.5))) return false; + return res; + } - reduced = (binz2 === zmin) || (binz1 >= binz2); + /** @return true if hmin and hmax values where specified */ + exact_values_range() { return this.ohmin && this.ohmax; } - if (!reduced || (level > 0)) return true; +} // class THistDrawOptions - if (basehisto) return false; // do not draw empty bins on top of other bins - if (painter.options.Zero || (axis_zmin > 0)) return true; +/** + * @summary Handle for histogram contour + * + * @private + */ - return painter._show_empty_bins; - }; +class HistContour { - let levels = [axis_zmin, axis_zmax], palette = null; + constructor(zmin, zmax) { + this.arr = []; + this.colzmin = zmin; + this.colzmax = zmax; + this.below_min_indx = -1; + this.exact_min_indx = 0; + } - // DRAW ALL CUBES + /** @summary Returns contour levels */ + getLevels() { return this.arr; } - if ((painter.options.Lego === 12) || (painter.options.Lego === 14)) { - // drawing colors levels, axis can not exceed palette + /** @summary Create normal contour levels */ + createNormal(nlevels, log_scale, zminpositive) { + if (log_scale) { + if (this.colzmax <= 0) + this.colzmax = 1.0; + if (this.colzmin <= 0) { + if ((zminpositive === undefined) || (zminpositive <= 0)) + this.colzmin = 0.0001 * this.colzmax; + else + this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3 * zminpositive : 1; + } + if (this.colzmin >= this.colzmax) + this.colzmin = 0.0001 * this.colzmax; - if (is_v7) { - palette = main.getHistPalette(); - painter.createContour(main, palette, { full_z_range: true }); - levels = palette.getContour(); - axis_zmin = levels[0]; - axis_zmax = levels[levels.length-1]; + const logmin = Math.log(this.colzmin) / Math.log(10), + logmax = Math.log(this.colzmax) / Math.log(10), + dz = (logmax - logmin) / nlevels; + this.arr.push(this.colzmin); + for (let level = 1; level < nlevels; level++) + this.arr.push(Math.exp((logmin + dz * level) * Math.log(10))); + this.arr.push(this.colzmax); + this.custom = true; } else { - const cntr = painter.createContour(histo.fContour ? histo.fContour.length : 20, main.lego_zmin, main.lego_zmax); - levels = cntr.arr; - palette = painter.getHistPalette(); - // axis_zmin = levels[0]; - // axis_zmax = levels[levels.length-1]; + if ((this.colzmin === this.colzmax) && this.colzmin) { + this.colzmax += 0.01 * Math.abs(this.colzmax); + this.colzmin -= 0.01 * Math.abs(this.colzmin); + } + const dz = (this.colzmax - this.colzmin) / nlevels; + for (let level = 0; level <= nlevels; level++) + this.arr.push(this.colzmin + dz * level); } } - for (let nlevel = 0; nlevel < levels.length-1; ++nlevel) { - zmin = levels[nlevel]; - zmax = levels[nlevel+1]; - - // artificially extend last level of color palette to maximal visible value - if (palette && (nlevel === levels.length-2) && zmax < axis_zmax) zmax = axis_zmax; + /** @summary Create custom contour levels */ + createCustom(levels) { + this.custom = true; + for (let n = 0; n < levels.length; ++n) + this.arr.push(levels[n]); - const grzmin = main.grz(zmin), grzmax = main.grz(zmax); - let z1 = 0, z2 = 0, numvertices = 0, num2vertices = 0; + if (this.colzmax > this.arr.at(-1)) + this.arr.push(this.colzmax); + } - // now calculate size of buffer geometry for boxes + /** @summary Configure indices */ + configIndicies(below_min, exact_min) { + this.below_min_indx = below_min; + this.exact_min_indx = exact_min; + } - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, nlevel)) continue; + /** @summary Get index based on z value */ + getContourIndex(zc) { + // bins less than zmin not drawn + if (zc < this.colzmin) + return this.below_min_indx; - nobottom = !reduced && (nlevel > 0); - notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2); + // if bin content exactly zmin, draw it when col0 specified or when content is positive + if (zc === this.colzmin) + return this.exact_min_indx; - numvertices += (reduced ? 12 : indicies.length); - if (nobottom) numvertices -= 6; - if (notop) numvertices -= 6; + if (!this.custom) + return Math.floor(0.01 + (zc - this.colzmin) * (this.arr.length - 1) / (this.colzmax - this.colzmin)); - if (split_faces && !reduced) { - numvertices -= 12; - num2vertices += 12; - } - } + let l = 0, r = this.arr.length - 1; + if (zc < this.arr[0]) + return -1; + if (zc >= this.arr[r]) + return r; + while (l < r - 1) { + const mid = Math.round((l + r) / 2); + if (this.arr[mid] > zc) + r = mid; + else + l = mid; } + return l; + } - const positions = new Float32Array(numvertices*3), - normals = new Float32Array(numvertices*3), - face_to_bins_index = use16indx ? new Uint16Array(numvertices/3) : new Uint32Array(numvertices/3), - pos2 = (num2vertices === 0) ? null : new Float32Array(num2vertices*3), - norm2 = (num2vertices === 0) ? null : new Float32Array(num2vertices*3), - face_to_bins_indx2 = (num2vertices === 0) ? null : (use16indx ? new Uint16Array(num2vertices/3) : new Uint32Array(num2vertices/3)); + /** @summary Get palette color */ + getPaletteColor(palette, zc) { + const zindx = this.getContourIndex(zc); + if (zindx < 0) + return null; + const pindx = palette.calcColorIndex(zindx, this.arr.length); + return palette.getColor(pindx); + } - let v = 0, v2 = 0, vert, k, nn; + /** @summary Get palette index */ + getPaletteIndex(palette, zc) { + const zindx = this.getContourIndex(zc); + return (zindx < 0) ? null : palette.calcColorIndex(zindx, this.arr.length); + } - for (i = i1; i < i2; ++i) { - x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1] - handle.grx[i]); - x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1] - handle.grx[i]); - for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, nlevel)) continue; +} // class HistContour - nobottom = !reduced && (nlevel > 0); - notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2); +/** + * @summary Handle for updating of secondary functions + * + * @private + */ - y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]); - y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]); +class FunctionsHandler { - z1 = (binz1 <= zmin) ? grzmin : main.grz(binz1); - z2 = (binz2 > zmax) ? grzmax : main.grz(binz2); + #extra_painters; + #newfuncs; // array of functions + #newopts; // array of options + #painter; // object painter to which functions belongs + #pad_painter; // pad painter - nn = 0; // counter over the normals, each normals correspond to 6 vertices - k = 0; // counter over vertices + constructor(painter, pp, funcs, statpainter, update_statpainter) { + this.#painter = painter; + this.#pad_painter = pp; - if (reduced) { - // we skip all side faces, keep only top and bottom - nn += 12; - k += 24; - } + const painters = [], update_painters = [], + only_draw = (statpainter === true); - const bin_index = histo.getBin(i+1, j+1); - let size = indicies.length; - if (nobottom) size -= 6; + this.#newfuncs = []; + this.#newopts = []; - // array over all vertices of the single bin - while (k < size) { - vert = vertices[indicies[k]]; + // find painters associated with histogram/graph/... + if (!only_draw) { + pp?.forEachPainterInPad(objp => { + if (objp.isSecondary(painter) && objp.getSecondaryId()?.match(/^func_|^indx_/)) + painters.push(objp); + }, 'objects'); + } - if (split_faces && (k < 12)) { - pos2[v2] = x1 + vert.x * (x2 - x1); - pos2[v2+1] = y1 + vert.y * (y2 - y1); - pos2[v2+2] = z1 + vert.z * (z2 - z1); + for (let n = 0; n < funcs?.arr.length; ++n) { + const func = funcs.arr[n], fopt = funcs.opt[n]; + if (!func?._typename) + continue; + if (isFunc(painter.needDrawFunc) && !painter.needDrawFunc(painter.getObject(), func)) + continue; - norm2[v2] = vnormals[nn]; - norm2[v2+1] = vnormals[nn+1]; - norm2[v2+2] = vnormals[nn+2]; - if (v2 % 9 === 0) face_to_bins_indx2[v2/9] = bin_index; // remember which bin corresponds to the face - v2 += 3; - } else { - positions[v] = x1 + vert.x * (x2 - x1); - positions[v+1] = y1 + vert.y * (y2 - y1); - positions[v+2] = z1 + vert.z * (z2 - z1); + let funcpainter = null, func_indx = -1; - normals[v] = vnormals[nn]; - normals[v+1] = vnormals[nn+1]; - normals[v+2] = vnormals[nn+2]; - if (v % 9 === 0) face_to_bins_index[v/9] = bin_index; // remember which bin corresponds to the face - v += 3; + if (!only_draw) { + // try to find matching object in associated list of painters + for (let i = 0; i < painters.length; ++i) { + if (painters[i].matchObjectType(func._typename) && (painters[i].getObjectName() === func.fName)) { + funcpainter = painters[i]; + func_indx = i; + break; } + } + // or just in generic list of painted objects + if (!funcpainter && func.fName) + funcpainter = pp?.findPainterFor(null, func.fName, func._typename); + } - ++k; - - if (k % 6 === 0) { - nn += 3; - if (notop && (k === indicies.length - 12)) { - k += 6; nn += 3; // jump over notop indexes - } - } + if (funcpainter) { + funcpainter.updateObject(func, fopt); + if (func_indx >= 0) { + painters.splice(func_indx, 1); + update_painters.push(funcpainter); } + } else { + // use arrays index while index is important + this.#newfuncs[n] = func; + this.#newopts[n] = fopt; } } - const geometry = createLegoGeom(painter, positions, normals); - let rootcolor = is_v7 ? 3 : histo.fFillColor, - fcolor = painter.getColor(rootcolor); - - if (palette) - fcolor = is_v7 ? palette.getColor(nlevel) : palette.calcColor(nlevel, levels.length); - else if ((painter.options.Lego === 1) || (rootcolor < 2)) { - rootcolor = 1; - fcolor = 'white'; + // stat painter has to be kept even when no object exists in the list + if (isObject(statpainter)) { + const indx = painters.indexOf(statpainter); + if (indx >= 0) + painters.splice(indx, 1); + if (update_statpainter && (update_painters.indexOf(statpainter) < 0)) + update_painters.push(statpainter); } - const material = new MeshBasicMaterial(getMaterialArgs(fcolor, { vertexColors: false })), - mesh = new Mesh(geometry, material); + // remove all function which are not found in new list of functions + if (painters.length) + pp?.cleanPrimitives(p => painters.indexOf(p) >= 0); - mesh.face_to_bins_index = face_to_bins_index; - mesh.painter = painter; - mesh.zmin = axis_zmin; - mesh.zmax = axis_zmax; - mesh.baseline = (painter.options.BaseLine !== false) ? painter.options.BaseLine : (painter.options.Zero ? axis_zmin : 0); - mesh.tip_color = (rootcolor=== 3) ? 0xFF0000 : 0x00FF00; - mesh.handle = handle; + if (update_painters.length) + this.#extra_painters = update_painters; + } - mesh.tooltip = function(intersect) { - if ((intersect.faceIndex < 0) || (intersect.faceIndex >= this.face_to_bins_index.length)) return null; + /** @summary Draw/update functions selected before */ + drawNext(indx) { + if (this.#extra_painters) { + const p = this.#extra_painters.shift(); + if (!this.#extra_painters.length) + this.#extra_painters = undefined; + return getPromise(p.redraw()).then(() => this.drawNext(0)); + } - const p = this.painter, - handle = this.handle, - main = p.getFramePainter(), - histo = p.getHisto(), - tip = p.get3DToolTip(this.face_to_bins_index[intersect.faceIndex]), - x1 = Math.min(main.size_x3d, Math.max(-main.size_x3d, handle.grx[tip.ix-1] + handle.xbar1*(handle.grx[tip.ix] - handle.grx[tip.ix-1]))), - x2 = Math.min(main.size_x3d, Math.max(-main.size_x3d, handle.grx[tip.ix-1] + handle.xbar2*(handle.grx[tip.ix] - handle.grx[tip.ix-1]))), - y1 = Math.min(main.size_y3d, Math.max(-main.size_y3d, handle.gry[tip.iy-1] + handle.ybar1*(handle.gry[tip.iy] - handle.gry[tip.iy-1]))), - y2 = Math.min(main.size_y3d, Math.max(-main.size_y3d, handle.gry[tip.iy-1] + handle.ybar2*(handle.gry[tip.iy] - handle.gry[tip.iy-1]))); - - tip.x1 = Math.min(x1, x2); - tip.x2 = Math.max(x1, x2); - tip.y1 = Math.min(y1, y2); - tip.y2 = Math.max(y1, y2); - - let binz1 = this.baseline, binz2 = tip.value; - if (histo.$baseh) binz1 = histo.$baseh.getBinContent(tip.ix, tip.iy); - if (binz2 < binz1) [binz1, binz2] = [binz2, binz1]; - - tip.z1 = main.grz(Math.max(this.zmin, binz1)); - tip.z2 = main.grz(Math.min(this.zmax, binz2)); + if (!this.#newfuncs || (indx >= this.#newfuncs.length)) { + this.#newfuncs = this.#newopts = undefined; + return Promise.resolve(this.#painter); // simplify drawing + } - tip.color = this.tip_color; - tip.$painter = p; - tip.$projection = p.is_projection && (p.getDimension() === 2); + const func = this.#newfuncs[indx], fopt = this.#newopts[indx]; - return tip; - }; + if (!func || this.#pad_painter?.findPainterFor(func)) + return this.drawNext(indx + 1); - main.add3DMesh(mesh); + const func_id = func?.fName ? `func_${func.fName}` : `indx_${indx}`; - if (num2vertices > 0) { - const geom2 = createLegoGeom(painter, pos2, norm2), - color2 = (rootcolor < 2) ? new Color(0xFF0000) : new Color(rgb(fcolor).darker(0.5).toString()), - material2 = new MeshBasicMaterial({ color: color2, vertexColors: false }), - mesh2 = new Mesh(geom2, material2); - mesh2.face_to_bins_index = face_to_bins_indx2; - mesh2.painter = painter; - mesh2.handle = mesh.handle; - mesh2.tooltip = mesh.tooltip; - mesh2.zmin = mesh.zmin; - mesh2.zmax = mesh.zmax; - mesh2.baseline = mesh.baseline; - mesh2.tip_color = mesh.tip_color; + // Required to correctly draw multiple stats boxes + // TODO: set reference via weak pointer + func.$main_painter = this.#painter; - main.add3DMesh(mesh2); - } - } + const promise = TPavePainter.canDraw(func) + ? TPavePainter.draw(this.#pad_painter, func, fopt) + : this.#pad_painter.drawObject(this.#pad_painter, func, fopt); - // lego3 or lego4 do not draw border lines - if (painter.options.Lego > 12) return; + return promise.then(fpainter => { + fpainter.setSecondaryId(this.#painter, func_id); + return this.drawNext(indx + 1); + }); + } - // DRAW LINE BOXES +} // class FunctionsHandler - let numlinevertices = 0, numsegments = 0; - zmax = axis_zmax; zmin = axis_zmin; +// TH1 bits +// kNoStats = BIT(9), don't draw stats box +const kUserContour = BIT(10), // user specified contour levels +// kCanRebin = BIT(11), // can rebin axis +// kLogX = BIT(15), // X-axis in log scale + kIsZoomed$1 = BIT(16), // bit set when zooming on Y axis + kNoTitle$1 = BIT(17); // don't draw the histogram title +// kIsAverage = BIT(18); // Bin contents are average (used by Add) - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, 0)) continue; +/** + * @summary Basic painter for histogram classes + * @private + */ - // calculate required buffer size for line segments - numlinevertices += (reduced ? rvertices.length : vertices.length); - numsegments += (reduced ? rsegments.length : segments.length); - } +class THistPainter extends ObjectPainter { + + #doing_redraw_palette; // set during redrawing of palette + #ignore_frame; // true when drawing without frame functionality + #color_palette; // color palette used in histogram + #auto_exec; // can be reused when sending option back to server + #funcs_handler; // special instance for functions drawing + #contour; // histogram colors contour + #create_stats; // if stats was created by painter + + /** @summary Constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} histo - TH1 derived histogram object */ + constructor(dom, histo) { + super(dom, histo); + this.draw_content = true; + this.nbinsx = this.nbinsy = 0; + this.mode3d = false; } - // On some platforms vertex index required to be Uint16 array - // While we cannot use index for large vertex list - // skip index usage at all. It happens for relatively large histograms (100x100 bins) - const uselineindx = (numlinevertices <= 0xFFF0); + /** @summary Returns histogram object */ + getHisto() { return this.getObject(); } - if (!uselineindx) numlinevertices = numsegments*3; + /** @summary Returns histogram axis */ + getAxis(name) { + const histo = this.getObject(); + switch (name) { + case 'x': return histo?.fXaxis; + case 'y': return histo?.fYaxis; + case 'z': return histo?.fZaxis; + } + return null; + } - const lpositions = new Float32Array(numlinevertices * 3), - lindicies = uselineindx ? new Uint16Array(numsegments) : null, - grzmin = main.grz(axis_zmin), - grzmax = main.grz(axis_zmax); - let z1 = 0, z2 = 0, ll = 0, ii = 0; + /** @summary Returns true if TProfile */ + isTProfile() { return this.matchObjectType(clTProfile); } - for (i = i1; i < i2; ++i) { - x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1] - handle.grx[i]); - x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1] - handle.grx[i]); - for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, 0)) continue; + /** @summary Returns true if histogram drawn instead of TF1/TF2 object */ + isTF1() { return false; } - y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]); - y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]); + /** @summary Returns true if TH2Poly */ + isTH2Poly() { + return this.matchObjectType(/^TH2Poly/) || this.matchObjectType(/^TProfile2Poly/); + } - z1 = (binz1 <= axis_zmin) ? grzmin : main.grz(binz1); - z2 = (binz2 > axis_zmax) ? grzmax : main.grz(binz2); + /** @summary Clear 3d drawings - if any */ + clear3DScene() { + const fp = this.getFramePainter(); + if (isFunc(fp?.create3DScene)) + fp.create3DScene(-1); + this.mode3d = false; + } - const seg = reduced ? rsegments : segments, - vvv = reduced ? rvertices : vertices; + /** @summary Cleanup histogram painter */ + cleanup() { + this.clear3DScene(); - if (uselineindx) { - // array of indicies for the lines, to avoid duplication of points - for (k = 0; k < seg.length; ++k) { - // intersect_index[ii] = bin_index; - lindicies[ii++] = ll/3 + seg[k]; - } + this.clearHistPalette(); + this.#contour = undefined; - for (k = 0; k < vvv.length; ++k) { - vert = vvv[k]; - lpositions[ll] = x1 + vert.x * (x2 - x1); - lpositions[ll+1] = y1 + vert.y * (y2 - y1); - lpositions[ll+2] = z1 + vert.z * (z2 - z1); - ll += 3; - } - } else { - // copy only vertex positions - for (k = 0; k < seg.length; ++k) { - vert = vvv[seg[k]]; - lpositions[ll] = x1 + vert.x * (x2 - x1); - lpositions[ll+1] = y1 + vert.y * (y2 - y1); - lpositions[ll+2] = z1 + vert.z * (z2 - z1); - // intersect_index[ll/3] = bin_index; - ll += 3; - } - } - } + super.cleanup(); } - // create boxes - const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), - material = new LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), - line = createLineSegments(convertLegoBuf(painter, lpositions), material, uselineindx ? lindicies : null); - - /* - line.painter = painter; - line.intersect_index = intersect_index; - line.tooltip = function(intersect) { - if ((intersect.index < 0) || (intersect.index >= this.intersect_index.length)) return null; - return this.painter.get3DToolTip(this.intersect_index[intersect.index]); + /** @summary Returns number of histogram dimensions */ + getDimension() { + const histo = this.getHisto(); + if (!histo) + return 0; + if (histo._typename.match(/^TH2/)) + return 2; + if (histo._typename === clTProfile2D) + return 2; + if (histo._typename.match(/^TH3/)) + return 3; + if (histo._typename === clTProfile3D) + return 3; + if (this.isTH2Poly()) + return 2; + return 1; } - */ - main.add3DMesh(line); -} + /** @summary Decode options string opt and fill the option structure */ + decodeOptions(opt) { + const histo = this.getHisto(), + hdim = this.getDimension(), + pp = this.getPadPainter(), + pad = pp?.getRootPad(true); + let o = this.getOptions(true); -/** @summary Draw TH2 histogram in error mode - * @private */ -function drawBinsError3D(painter, is_v7 = false) { - const main = painter.getFramePainter(), - histo = painter.getHisto(), - handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), - zmin = main.z_handle.getScaleMin(), - zmax = main.z_handle.getScaleMax(), - test_cutg = painter.options.cutg; - let i, j, bin, binz, binerr, x1, y1, x2, y2, z1, z2, - nsegments = 0, lpos = null, binindx = null, lindx = 0; + if (!o?.reset) + o = this.setOptions(new THistDrawOptions(), true); + else + o.reset(); - const check_skip_min = () => { - // return true if minimal histogram value should be skipped - if (painter.options.Zero || (zmin > 0)) return false; - return !painter._show_empty_bins; - }; + // when changing draw option, reset attributes usage + this.lineatt?.setUsed(false); + this.fillatt?.setUsed(false); + this.markeratt?.setUsed(false); - // loop over the points - first loop counts points, second fill arrays - for (let loop = 0; loop < 2; ++loop) { - for (i = handle.i1; i < handle.i2; ++i) { - x1 = handle.grx[i]; - x2 = handle.grx[i + 1]; - for (j = handle.j1; j < handle.j2; ++j) { - binz = histo.getBinContent(i + 1, j + 1); - if ((binz < zmin) || (binz > zmax)) continue; - if ((binz === zmin) && check_skip_min()) continue; + o.decode(opt || histo.fOption, hdim, histo, pp, pad, this); - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + this.storeDrawOpt(opt); // opt will be return as default draw option, used in web canvas + } - // just count number of segments - if (loop === 0) { nsegments += 3; continue; } + /** @summary Copy draw options from other painter */ + copyOptionsFrom(src) { + if (src === this) + return; + const o = this.getOptions(), o0 = src.getOptions(); - bin = histo.getBin(i + 1, j + 1); - binerr = histo.getBinError(bin); - binindx[lindx / 18] = bin; + o.Mode3D = o0.Mode3D; + o.Zero = o0.Zero; + if (o0.Mode3D) { + o.Lego = o0.Lego; + o.Surf = o0.Surf; + } else { + o.Color = o0.Color; + o.Contour = o0.Contour; + } + } - y1 = handle.gry[j]; - y2 = handle.gry[j + 1]; + /** @summary copy draw options to all other histograms in the pad */ + copyOptionsToOthers() { + this.forEachPainter(painter => { + if ((painter !== this) && isFunc(painter.copyOptionsFrom)) + painter.copyOptionsFrom(this); + }, 'objects'); + } - z1 = main.grz((binz - binerr < zmin) ? zmin : binz - binerr); - z2 = main.grz((binz + binerr > zmax) ? zmax : binz + binerr); + /** @summary Scan histogram content + * @abstract */ + scanContent(/* when_axis_changed */) { + // function will be called once new histogram or + // new histogram content is assigned + // one should find min, max, bins number, content min/max values + // if when_axis_changed === true specified, content will be scanned after axis zoom changed + } - lpos[lindx] = x1; lpos[lindx + 3] = x2; - lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; - lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; - lindx += 6; + /** @summary Check pad ranges when drawing of frame axes will be performed + * @desc Only if histogram is main painter and drawn with SAME option, pad range can be used + * In all other cases configured range must be derived from histogram itself */ + checkPadRange() { + if (this.isMainPainter()) + this.check_pad_range = this.getOptions().Same ? 'pad_range' : true; + } - lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; - lpos[lindx + 1] = y1; lpos[lindx + 4] = y2; - lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; - lindx += 6; + /** @summary Create necessary histogram draw attributes */ + createHistDrawAttributes(only_check_auto) { + const histo = this.getHisto(), o = this.getOptions(); - lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; - lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; - lpos[lindx + 2] = z1; lpos[lindx + 5] = z2; - lindx += 6; + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(histo.$num_histos); + this.#auto_exec = ''; + if (o._pfc > 1) { + o._pfc = 1; + histo.fFillColor = icolor; + this.#auto_exec += `SetFillColor(${icolor});;`; + this.deleteAttr('fill'); + } + if (o._plc > 1) { + o._plc = 1; + histo.fLineColor = icolor; + this.#auto_exec += `SetLineColor(${icolor});;`; + this.deleteAttr('line'); + } + if (o._pmc > 1) { + o._pmc = 1; + histo.fMarkerColor = icolor; + this.#auto_exec += `SetMarkerColor(${icolor});;`; + this.deleteAttr('marker'); + } } } - if (loop === 0) { - if (nsegments === 0) return; - lpos = new Float32Array(nsegments * 6); - binindx = new Int32Array(nsegments / 3); + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttFill({ attr: histo, color: o.histoFillColor, pattern: o.histoFillPattern, kind: 1 }); + this.createAttLine({ attr: histo, color0: o.histoLineColor, width: o.histoLineWidth }); } } - // create lines - const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), - material = new LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), - line = createLineSegments(lpos, material); + /** @summary Update axes attributes in target histogram + * @private */ + updateAxes(tgt_histo, src_histo, fp) { + const copyTAxisMembers = (tgt, src, copy_zoom) => { + tgt.fTitle = src.fTitle; + tgt.fLabels = src.fLabels; + tgt.fXmin = src.fXmin; + tgt.fXmax = src.fXmax; + tgt.fTimeDisplay = src.fTimeDisplay; + tgt.fTimeFormat = src.fTimeFormat; + tgt.fAxisColor = src.fAxisColor; + tgt.fLabelColor = src.fLabelColor; + tgt.fLabelFont = src.fLabelFont; + tgt.fLabelOffset = src.fLabelOffset; + tgt.fLabelSize = src.fLabelSize; + tgt.fNdivisions = src.fNdivisions; + tgt.fTickLength = src.fTickLength; + tgt.fTitleColor = src.fTitleColor; + tgt.fTitleFont = src.fTitleFont; + tgt.fTitleOffset = src.fTitleOffset; + tgt.fTitleSize = src.fTitleSize; + if (copy_zoom) { + tgt.fFirst = src.fFirst; + tgt.fLast = src.fLast; + tgt.fBits = src.fBits; + } + }; - line.painter = painter; - line.intersect_index = binindx; - line.zmin = zmin; - line.zmax = zmax; - line.tip_color = (histo.fLineColor === 3) ? 0xFF0000 : 0x00FF00; + copyTAxisMembers(tgt_histo.fXaxis, src_histo.fXaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('x')); + copyTAxisMembers(tgt_histo.fYaxis, src_histo.fYaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('y')); + copyTAxisMembers(tgt_histo.fZaxis, src_histo.fZaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('z')); + } - line.tooltip = function(intersect) { - const pos = Math.floor(intersect.index / 6); - if ((pos < 0) || (pos >= this.intersect_index.length)) return null; - const p = this.painter, - histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.intersect_index[pos]), - x1 = Math.min(main.size_x3d, Math.max(-main.size_x3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix)))), - x2 = Math.min(main.size_x3d, Math.max(-main.size_x3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1)))), - y1 = Math.min(main.size_y3d, Math.max(-main.size_y3d, main.gry(histo.fYaxis.GetBinLowEdge(tip.iy)))), - y2 = Math.min(main.size_y3d, Math.max(-main.size_y3d, main.gry(histo.fYaxis.GetBinLowEdge(tip.iy+1)))); + /** @summary Update histogram object + * @param obj - new histogram instance + * @param opt - new drawing option (optional) + * @return {Boolean} - true if histogram was successfully updated */ + updateObject(obj, opt) { + const histo = this.getHisto(), + fp = this.getFramePainter(), + pp = this.getPadPainter(), + o = this.getOptions(); - tip.x1 = Math.min(x1, x2); - tip.x2 = Math.max(x1, x2); - tip.y1 = Math.min(y1, y2); - tip.y2 = Math.max(y1, y2); + if (obj !== histo) { + if (!this.matchObjectType(obj)) + return false; - tip.z1 = main.grz(tip.value-tip.error < this.zmin ? this.zmin : tip.value-tip.error); - tip.z2 = main.grz(tip.value+tip.error > this.zmax ? this.zmax : tip.value+tip.error); + // simple replace of object does not help - one can have different + // complex relations between histogram and stat box, histogram and colz axis, + // one could have THStack or TMultiGraph object + // The only that could be done is update of content - tip.color = this.tip_color; + const statpainter = pp?.findPainterFor(this.findStat()); - return tip; - }; + // copy histogram bits + if (histo.TestBit(kNoStats) !== obj.TestBit(kNoStats)) { + histo.SetBit(kNoStats, obj.TestBit(kNoStats)); + // here check only stats bit + if (statpainter) { + statpainter.Enabled = !histo.TestBit(kNoStats) && !o.NoStat; // && (!o.Same || o.ForceStat) + // remove immediately when redraw not called for disabled stats + if (!statpainter.Enabled) + statpainter.removeG(); + } + } - main.add3DMesh(line); -} + histo.SetBit(kIsZoomed$1, obj.TestBit(kIsZoomed$1)); -/** @summary Draw TH2 as 3D contour plot - * @private */ -function drawBinsContour3D(painter, realz = false, is_v7 = false) { - // for contour plots one requires handle with full range - const main = painter.getFramePainter(), - handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 100, middle: 0 }), - histo = painter.getHisto(), // get levels - levels = painter.getContourLevels(), // init contour if not exists - palette = painter.getHistPalette(), - pnts = []; - let layerz = 2*main.size_z3d; + // special treatment for web canvas - also name can be changed + if (this.hasSnapId()) { + histo.fName = obj.fName; + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + } - buildHist2dContour(histo, handle, levels, palette, - (colindx, xp, yp, iminus, iplus, ilevel) => { - // ignore less than three points - if (iplus - iminus < 3) return; + if (!o._pfc) + histo.fFillColor = obj.fFillColor; + histo.fFillStyle = obj.fFillStyle; + if (!o._plc) + histo.fLineColor = obj.fLineColor; + histo.fLineStyle = obj.fLineStyle; + histo.fLineWidth = obj.fLineWidth; + if (!o._pmc) + histo.fMarkerColor = obj.fMarkerColor; + histo.fMarkerSize = obj.fMarkerSize; + histo.fMarkerStyle = obj.fMarkerStyle; - if (realz) { - layerz = main.grz(levels[ilevel]); - if ((layerz < 0) || (layerz > 2*main.size_z3d)) return; - } + histo.fEntries = obj.fEntries; + histo.fTsumw = obj.fTsumw; + histo.fTsumwx = obj.fTsumwx; + histo.fTsumwx2 = obj.fTsumwx2; + histo.fXaxis.fNbins = obj.fXaxis.fNbins; + if (this.getDimension() > 1) { + histo.fTsumwy = obj.fTsumwy; + histo.fTsumwy2 = obj.fTsumwy2; + histo.fTsumwxy = obj.fTsumwxy; + histo.fYaxis.fNbins = obj.fYaxis.fNbins; + if (this.getDimension() > 2) { + histo.fTsumwz = obj.fTsumwz; + histo.fTsumwz2 = obj.fTsumwz2; + histo.fTsumwxz = obj.fTsumwxz; + histo.fTsumwyz = obj.fTsumwyz; + histo.fZaxis.fNbins = obj.fZaxis.fNbins; + } + } - for (let i=iminus; i (value < axis_zmin) ? -0.1 : main.grz(value), - main_grz_min = 0, main_grz_max = 2*main.size_z3d; + if (!o.ominimum) + o.minimum = histo.fMinimum; + if (!o.omaximum) + o.maximum = histo.fMaximum; - let handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1, middle: 0.5, - cutg: isFunc(painter.options?.cutg?.IsInside) ? painter.options?.cutg : null }); - if ((handle.i2 - handle.i1 < 2) || (handle.j2 - handle.j1 < 2)) return; + if (this.getDimension() === 1) + o.decodeSumw2(histo); - let ilevels = null, levels = null, palette = null; + if (this.isTProfile()) + histo.fBinEntries = obj.fBinEntries; + else if (this.isTH2Poly()) + histo.fBins = obj.fBins; - handle.dolines = true; + // remove old functions, update existing, prepare to draw new one + this.#funcs_handler = new FunctionsHandler(this, pp, obj.fFunctions, statpainter, this.#create_stats); - if (is_v7) { - let need_palette = 0; - switch (painter.options.Surf) { - case 11: need_palette = 2; break; - case 12: - case 15: // make surf5 same as surf2 - case 17: need_palette = 2; handle.dolines = false; break; - case 14: handle.dolines = false; handle.donormals = true; break; - case 16: need_palette = 1; handle.dogrid = true; handle.dolines = false; break; - default: ilevels = main.z_handle.createTicks(true); handle.dogrid = true; break; - } + const changed_opt = (histo.fOption !== obj.fOption); + histo.fOption = obj.fOption; - if (need_palette > 0) { - palette = main.getHistPalette(); - if (need_palette === 2) - painter.createContour(main, palette, { full_z_range: true }); - ilevels = palette.getContour(); - } - } else { - switch (painter.options.Surf) { - case 11: ilevels = painter.getContourLevels(); palette = painter.getHistPalette(); break; - case 12: - case 15: // make surf5 same as surf2 - case 17: ilevels = painter.getContourLevels(); palette = painter.getHistPalette(); handle.dolines = false; break; - case 14: handle.dolines = false; handle.donormals = true; break; - case 16: ilevels = painter.getContourLevels(); handle.dogrid = true; handle.dolines = false; break; - default: ilevels = main.z_handle.createTicks(true); handle.dogrid = true; break; + if (((opt !== undefined) && (o.original !== opt)) || changed_opt) + this.decodeOptions(opt || histo.fOption); } - } - if (ilevels) { - // recalculate levels into graphical coordinates - levels = new Float32Array(ilevels.length); - for (let ll = 0; ll < ilevels.length; ++ll) - levels[ll] = main_grz(ilevels[ll]); - } else - levels = [main_grz_min, main_grz_max]; // just cut top/bottom parts + if (!o.ominimum) + o.minimum = histo.fMinimum; + if (!o.omaximum) + o.maximum = histo.fMaximum; + if (!o.ominimum && !o.omaximum && o.minimum === o.maximum) + o.minimum = o.maximum = kNoZoom; - handle.grz = main_grz; - handle.grz_min = main_grz_min; - handle.grz_max = main_grz_max; + if (!fp || !fp.zoomChangedInteractive()) + this.checkPadRange(); - buildSurf3D(histo, handle, ilevels, (lvl, pos, normindx) => { - const geometry = createLegoGeom(painter, pos, null, handle.i2 - handle.i1, handle.j2 - handle.j1), - normals = geometry.getAttribute('normal').array; + this.scanContent(); - // recalculate normals - if (handle.donormals && (lvl === 1)) { - for (let ii = handle.i1; ii < handle.i2; ++ii) { - for (let jj = handle.j1; jj < handle.j2; ++jj) { - const bin = ((ii-handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; + this.histogram_updated = true; // indicate that object updated - if (normindx[bin] === -1) continue; // nothing there + return true; + } - const beg = (normindx[bin] >= 0) ? bin : bin + 9 + normindx[bin], - end = bin + 8; - let sumx = 0, sumy = 0, sumz = 0; + /** @summary Access or modify histogram min/max + * @private */ + accessMM(ismin, v) { + const name = ismin ? 'minimum' : 'maximum', + o = this.getOptions(); + if (v === undefined) + return o[name]; - for (let kk = beg; kk < end; ++kk) { - const indx = normindx[kk]; - if (indx < 0) return console.error('FAILURE in NORMALS RECALCULATIONS'); - sumx += normals[indx]; - sumy += normals[indx+1]; - sumz += normals[indx+2]; - } + o[name] = v; - sumx = sumx/(end-beg); sumy = sumy/(end-beg); sumz = sumz/(end-beg); + this.interactiveRedraw('pad', ismin ? `exec:SetMinimum(${v})` : `exec:SetMaximum(${v})`); + } - for (let kk = beg; kk < end; ++kk) { - const indx = normindx[kk]; - normals[indx] = sumx; - normals[indx+1] = sumy; - normals[indx+2] = sumz; + /** @summary Extract axes bins and ranges + * @desc here functions are defined to convert index to axis value and back + * was introduced to support non-equidistant bins */ + extractAxesProperties(ndim) { + const assignTAxisFuncs = axis => { + if (axis.fXbins.length >= axis.fNbins) { + axis.GetBinCoord = function(bin) { + const indx = Math.round(bin); + if (indx <= 0) + return this.fXmin; + if (indx > this.fNbins) + return this.fXmax; + if (indx === bin) + return this.fXbins[indx]; + const indx2 = (bin < indx) ? indx - 1 : indx + 1; + return this.fXbins[indx] * Math.abs(bin - indx2) + this.fXbins[indx2] * Math.abs(bin - indx); + }; + axis.FindBin = function(x, add) { + for (let k = 1; k < this.fXbins.length; ++k) { + if (x < this.fXbins[k]) + return Math.floor(k - 1 + add); } - } + return this.fNbins; + }; + } else { + axis.$binwidth = (axis.fXmax - axis.fXmin) / (axis.fNbins || 1); + axis.GetBinCoord = function(bin) { return this.fXmin + bin * this.$binwidth; }; + axis.FindBin = function(x, add) { return Math.floor((x - this.fXmin) / this.$binwidth + add); }; } + }; + + this.nbinsx = this.nbinsy = this.nbinsz = 0; + + const histo = this.getHisto(), + o = this.getOptions(); + + this.nbinsx = histo.fXaxis.fNbins; + this.xmin = histo.fXaxis.fXmin; + this.xmax = histo.fXaxis.fXmax; + if (histo.fXaxis.TestBit(EAxisBits.kAxisRange) && (histo.fXaxis.fFirst !== histo.fXaxis.fLast)) { + if (histo.fXaxis.fFirst === 0) + this.xmin = histo.fXaxis.GetBinLowEdge(0); + if (histo.fXaxis.fLast === this.nbinsx + 1) + this.xmax = histo.fXaxis.GetBinLowEdge(this.nbinsx + 2); } - let color, material; - if (is_v7) - color = palette?.getColor(lvl-1) ?? painter.getColor(5); - else if (palette) - color = palette.calcColor(lvl, levels.length); - else { - color = histo.fFillColor > 1 ? painter.getColor(histo.fFillColor) : 'white'; - if ((painter.options.Surf === 14) && (histo.fFillColor < 2)) color = painter.getColor(48); + assignTAxisFuncs(histo.fXaxis); + + this.ymin = histo.fYaxis.fXmin; + this.ymax = histo.fYaxis.fXmax; + + if (ndim === 1 && o.exact_values_range()) { + this.ymin = o.hmin; + this.ymax = o.hmax; } - if (!color) color = 'white'; - if (painter.options.Surf === 14) - material = new MeshLambertMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })); - else - material = new MeshBasicMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })); + if (ndim > 1) { + this.nbinsy = histo.fYaxis.fNbins; + if (histo.fYaxis.TestBit(EAxisBits.kAxisRange) && (histo.fYaxis.fFirst !== histo.fYaxis.fLast)) { + if (histo.fYaxis.fFirst === 0) + this.ymin = histo.fYaxis.GetBinLowEdge(0); + if (histo.fYaxis.fLast === this.nbinsy + 1) + this.ymax = histo.fYaxis.GetBinLowEdge(this.nbinsy + 2); + } + assignTAxisFuncs(histo.fYaxis); - const mesh = new Mesh(geometry, material); + this.zmin = histo.fZaxis.fXmin; + this.zmax = histo.fZaxis.fXmax; - main.add3DMesh(mesh); + if ((ndim === 2) && o.ohmin && o.ohmax) { + this.zmin = o.hmin; + this.zmax = o.hmax; + } + } - mesh.painter = painter; // to let use it with context menu - }, (isgrid, lpos) => { - const color = painter.getColor(histo.fLineColor) ?? 'white'; - let material; + if (ndim > 2) { + this.nbinsz = histo.fZaxis.fNbins; + if (histo.fZaxis.TestBit(EAxisBits.kAxisRange) && (histo.fZaxis.fFirst !== histo.fZaxis.fLast)) { + if (histo.fZaxis.fFirst === 0) + this.zmin = histo.fZaxis.GetBinLowEdge(0); + if (histo.fZaxis.fLast === this.nbinsz + 1) + this.zmax = histo.fZaxis.GetBinLowEdge(this.nbinsz + 2); + } + assignTAxisFuncs(histo.fZaxis); + } + } - if (isgrid) { - material = (painter.options.Surf === 1) - ? new LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 }) - : new LineBasicMaterial(getMaterialArgs(color)); - } else - material = new LineBasicMaterial(getMaterialArgs(color, { linewidth: histo.fLineWidth })); + /** @summary Draw axes for histogram + * @desc axes can be drawn only for main histogram */ + async drawAxes() { + const fp = this.getFramePainter(); + if (!fp) + return false; + const histo = this.getHisto(), + o = this.getOptions(); - const line = createLineSegments(convertLegoBuf(painter, lpos, handle.i2 - handle.i1, handle.j2 - handle.j1), material); - line.painter = painter; - main.add3DMesh(line); - }); + // artificially add y range to display axes + if (this.ymin === this.ymax) + this.ymax += 1; - if (painter.options.Surf === 17) - drawBinsContour3D(painter, false, is_v7); + if (!this.isMainPainter()) { + const opts = { + second_x: (o.AxisPos >= 10), + second_y: (o.AxisPos % 10) === 1, + hist_painter: this + }; - if (painter.options.Surf === 13) { - handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 100, middle: 0 }); + if ((!opts.second_x && !opts.second_y) || fp.hasDrawnAxes(opts.second_x, opts.second_y)) + return false; - // get levels - const levels = painter.getContourLevels(), // init contour - palette = painter.getHistPalette(); - let lastcolindx = -1, layerz = main_grz_max; + fp.setAxes2Ranges(opts.second_x, histo.fXaxis, this.xmin, this.xmax, opts.second_y, histo.fYaxis, this.ymin, this.ymax); - buildHist2dContour(histo, handle, levels, palette, - (colindx, xp, yp, iminus, iplus) => { - // no need for duplicated point - if ((xp[iplus] === xp[iminus]) && (yp[iplus] === yp[iminus])) iplus--; + fp.createXY2(opts); - // ignore less than three points - if (iplus - iminus < 3) return; + return fp.drawAxes2(opts.second_x, opts.second_y); + } - const pnts = []; + fp.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0); - for (let i = iminus; i <= iplus; ++i) { - if ((i === iminus) || (xp[i] !== xp[i-1]) || (yp[i] !== yp[i-1])) - pnts.push(new Vector2(xp[i], yp[i])); - } + fp.createXY({ + ndim: this.getDimension(), + check_pad_range: this.check_pad_range, + zoom_xmin: this.zoom_xmin, + zoom_xmax: this.zoom_xmax, + zoom_ymin: this.zoom_ymin, + zoom_ymax: this.zoom_ymax, + xmin_nz: histo.$xmin_nz, + ymin_nz: this.ymin_nz ?? histo.$ymin_nz, + swap_xy: o.swap_xy(), + xticks: o.xticks, + yticks: o.yticks, + reverse_x: o.RevX, + reverse_y: o.RevY, + symlog_x: o.SymlogX, + symlog_y: o.SymlogY, + Proj: o.Proj, + extra_y_space: o.Text && (o.BarStyle > 0), + hist_painter: this + }); - if (pnts.length < 3) return; + delete this.check_pad_range; + delete this.zoom_xmin; + delete this.zoom_xmax; + delete this.zoom_ymin; + delete this.zoom_ymax; - const faces = ShapeUtils.triangulateShape(pnts, []); + if (o.Same) + return false; - if (!faces || (faces.length === 0)) return; + const disable_axis_draw = (o.Axis < 0) || (o.Axis === 2); - if ((lastcolindx < 0) || (lastcolindx !== colindx)) { - lastcolindx = colindx; - layerz += 5e-5 * main_grz_max; // change layers Z - } + return fp.drawAxes(false, disable_axis_draw, disable_axis_draw, + o.AxisPos, o.Zscale && o.Zvert, + o.Zscale && !o.Zvert, o.Axis !== 1); + } - const pos = new Float32Array(faces.length*9), - norm = new Float32Array(faces.length*9); - let indx = 0; + /** @summary Inform web canvas that something changed in the histogram */ + processOnlineChange(kind) { + const cp = this.getCanvPainter(); + if (isFunc(cp?.processChanges)) + cp.processChanges(kind, this); + } - for (let n = 0; n < faces.length; ++n) { - const face = faces[n]; - for (let v = 0; v < 3; ++v) { - const pnt = pnts[face[v]]; - pos[indx] = pnt.x; - pos[indx+1] = pnt.y; - pos[indx+2] = layerz; - norm[indx] = 0; - norm[indx+1] = 0; - norm[indx+2] = 1; + /** @summary Fill option object used in TWebCanvas */ + fillWebObjectOptions(res) { + if (this.#auto_exec && res) { + res.fcust = 'auto_exec:' + this.#auto_exec; + this.#auto_exec = undefined; + } + } - indx += 3; - } - } + /** @summary Toggle histogram title drawing */ + toggleTitle(arg) { + const histo = this.getHisto(); + if (!this.isMainPainter() || !histo) + return false; + if (arg === kOnlyCheck) + return !histo.TestBit(kNoTitle$1); + histo.InvertBit(kNoTitle$1); + this.updateHistTitle().then(() => this.processOnlineChange(`exec:SetBit(TH1::kNoTitle,${histo.TestBit(kNoTitle$1) ? 1 : 0})`)); + } - const geometry = createLegoGeom(painter, pos, norm, handle.i2 - handle.i1, handle.j2 - handle.j1), - material = new MeshBasicMaterial(getMaterialArgs(palette.getColor(colindx), { side: DoubleSide, opacity: 0.5, vertexColors: false })), - mesh = new Mesh(geometry, material); - mesh.painter = painter; - main.add3DMesh(mesh); - } - ); + /** @summary Only redraw histogram title + * @return {Promise} with painter */ + async updateHistTitle(first_time) { + const o = this.getOptions(), + histo = this.getHisto(); + return drawObjectTitle(this, first_time, this.isMainPainter() && !o.Same && (o.Axis <= 0), !histo.TestBit(kNoTitle$1)); } -} -/** @summary Assign `evalPar` function for TF1 object - * @private */ + /** @summary Draw histogram title + * @return {Promise} with painter */ + async drawHistTitle() { return this.updateHistTitle(true); } -function proivdeEvalPar(obj, check_save) { - obj.$math = jsroot_math; + /** @summary Live change and update of title drawing + * @desc Used from the GED */ + processTitleChange(arg) { + const histo = this.getHisto(), + tpainter = this.getPadPainter()?.findPainterFor(null, kTitle); - let _func = obj.fTitle, isformula = false, pprefix = '['; - if (_func === 'gaus') _func = 'gaus(0)'; - if (isStr(obj.fFormula?.fFormula)) { - if (obj.fFormula.fFormula.indexOf('[](double*x,double*p)') === 0) { - isformula = true; pprefix = 'p['; - _func = obj.fFormula.fFormula.slice(21); - } else { - _func = obj.fFormula.fFormula; - pprefix = '[p'; - } - - if (obj.fFormula.fClingParameters && obj.fFormula.fParams) { - obj.fFormula.fParams.forEach(pair => { - const regex = new RegExp(`(\\[${pair.first}\\])`, 'g'), - parvalue = obj.fFormula.fClingParameters[pair.second]; - _func = _func.replace(regex, (parvalue < 0) ? `(${parvalue})` : parvalue); - }); - } + if (!histo || !tpainter) + return null; + + if (arg === 'check') + return (!this.isMainPainter() || this.getOptions().Same) ? null : histo; + + tpainter.clearPave(); + tpainter.addText(histo.fTitle); + + tpainter.redraw(); + + this.submitCanvExec(`SetTitle("${histo.fTitle}")`); } - if (!_func) - return !check_save || (obj.fSave?.length > 2); + /** @summary Update statistics when web canvas is drawn */ + updateStatWebCanvas() { + if (!this.hasSnapId()) + return; - obj.formulas?.forEach(entry => { - _func = _func.replaceAll(entry.fName, entry.fTitle); - }); + const stat = this.findStat(), + statpainter = this.getPadPainter()?.findPainterFor(stat); - _func = _func.replace(/\b(TMath::SinH)\b/g, 'Math.sinh') - .replace(/\b(TMath::CosH)\b/g, 'Math.cosh') - .replace(/\b(TMath::TanH)\b/g, 'Math.tanh') - .replace(/\b(TMath::ASinH)\b/g, 'Math.asinh') - .replace(/\b(TMath::ACosH)\b/g, 'Math.acosh') - .replace(/\b(TMath::ATanH)\b/g, 'Math.atanh') - .replace(/\b(TMath::ASin)\b/g, 'Math.asin') - .replace(/\b(TMath::ACos)\b/g, 'Math.acos') - .replace(/\b(TMath::Atan)\b/g, 'Math.atan') - .replace(/\b(TMath::ATan2)\b/g, 'Math.atan2') - .replace(/\b(sin|SIN|TMath::Sin)\b/g, 'Math.sin') - .replace(/\b(cos|COS|TMath::Cos)\b/g, 'Math.cos') - .replace(/\b(tan|TAN|TMath::Tan)\b/g, 'Math.tan') - .replace(/\b(exp|EXP|TMath::Exp)\b/g, 'Math.exp') - .replace(/\b(log|LOG|TMath::Log)\b/g, 'Math.log') - .replace(/\b(log10|LOG10|TMath::Log10)\b/g, 'Math.log10') - .replace(/\b(pow|POW|TMath::Power)\b/g, 'Math.pow') - .replace(/\b(pi|PI)\b/g, 'Math.PI') - .replace(/\b(abs|ABS|TMath::Abs)\b/g, 'Math.abs') - .replace(/\bsqrt\(/g, 'Math.sqrt(') - .replace(/\bxygaus\(/g, 'this.$math.gausxy(this, x, y, ') - .replace(/\bgaus\(/g, 'this.$math.gaus(this, x, ') - .replace(/\bgausn\(/g, 'this.$math.gausn(this, x, ') - .replace(/\bexpo\(/g, 'this.$math.expo(this, x, ') - .replace(/\blandau\(/g, 'this.$math.landau(this, x, ') - .replace(/\blandaun\(/g, 'this.$math.landaun(this, x, ') - .replace(/\b(TMath::|ROOT::Math::)/g, 'this.$math.'); - - if (_func.match(/^pol[0-9]$/) && (parseInt(_func[3]) === obj.fNpar - 1)) { - _func = '[0]'; - for (let k = 1; k < obj.fNpar; ++k) - _func += ` + [${k}] * `+ ((k === 1) ? 'x' : `Math.pow(x,${k})`); + if (statpainter && !statpainter.hasSnapId()) + statpainter.redraw(); } - if (_func.match(/^chebyshev[0-9]$/) && (parseInt(_func[9]) === obj.fNpar - 1)) { - _func = `this.$math.ChebyshevN(${obj.fNpar-1}, x, `; - for (let k = 0; k < obj.fNpar; ++k) - _func += (k === 0 ? '[' : ', ') + `[${k}]`; - _func += '])'; + /** @summary Find stats box in list of functions */ + findStat() { + return this.findFunction(clTPaveStats, 'stats'); } - for (let i = 0; i < obj.fNpar; ++i) - _func = _func.replaceAll(pprefix + i + ']', `(${obj.GetParValue(i)})`); + /** @summary Toggle stat box drawing + * @private */ + toggleStat(arg) { + const pp = this.getPadPainter(); + let stat = this.findStat(), statpainter; - for (let n = 2; n < 10; ++n) - _func = _func.replaceAll(`x^${n}`, `Math.pow(x,${n})`); + if (!arg) + arg = ''; - if (isformula) { - _func = _func.replace(/x\[0\]/g, 'x'); - if (obj._typename === clTF3) { - _func = _func.replace(/x\[1\]/g, 'y'); - _func = _func.replace(/x\[2\]/g, 'z'); - obj.evalPar = new Function('x', 'y', 'z', _func).bind(obj); - } else if (obj._typename === clTF2) { - _func = _func.replace(/x\[1\]/g, 'y'); - obj.evalPar = new Function('x', 'y', _func).bind(obj); + if (!stat) { + if (arg.indexOf('-check') > 0) + return false; + // when stat box created first time, one need to draw it + stat = this.createStat(true); } else - obj.evalPar = new Function('x', _func).bind(obj); - } else if (obj._typename === clTF3) - obj.evalPar = new Function('x', 'y', 'z', 'return ' + _func).bind(obj); - else if (obj._typename === clTF2) - obj.evalPar = new Function('x', 'y', 'return ' + _func).bind(obj); - else - obj.evalPar = new Function('x', 'return ' + _func).bind(obj); + statpainter = pp.findPainterFor(stat); - return true; -} + if (arg === kOnlyCheck) + return statpainter?.Enabled || false; -/** @summary Get interpolation in saved buffer - * @desc Several checks must be done before function can be used - * @private */ -function _getTF1Save(func, x) { - const np = func.fSave.length - 3, - xmin = func.fSave[np + 1], - xmax = func.fSave[np + 2], - dx = (xmax - xmin) / np; - if (x < xmin) - return func.fSave[0]; - if (x > xmax) - return func.fSave[np]; + if (arg === 'fitpar-check') + return stat?.fOptFit || false; - const bin = Math.min(np - 1, Math.floor((x - xmin) / dx)); - let xlow = xmin + bin * dx, - xup = xlow + dx, - ylow = func.fSave[bin], - yup = func.fSave[bin + 1]; + if (arg === 'fitpar-toggle') { + if (!stat) + return false; + stat.fOptFit = stat.fOptFit ? 0 : 1111; // for websocket command should be send to server + statpainter?.redraw(); + return true; + } - if (!Number.isFinite(ylow) && (bin < np - 1)) { - xlow += dx; xup += dx; - ylow = yup; yup = func.fSave[bin + 2]; - } else if (!Number.isFinite(yup) && (bin > 0)) { - xup -= dx; xlow -= dx; - yup = ylow; ylow = func.fSave[bin - 1]; - } + let has_stats; - return ((xup * ylow - xlow * yup) + x * (yup - ylow)) / dx; -} + if (statpainter) { + statpainter.Enabled = !statpainter.Enabled; + this.getOptions().StatEnabled = statpainter.Enabled; // used only for interactive + // when stat box is drawn, it always can be drawn individually while it + // should be last for colz redrawPad is used + statpainter.redraw(); + has_stats = statpainter.Enabled; + } else { + // return promise which will be used to process + has_stats = TPavePainter.draw(pp, stat); + } -/** @summary Provide TF1 value - * @desc First try evaluate, if not possible - check saved buffer - * @private */ -function getTF1Value(func, x, skip_eval = undefined) { - let y = 0, iserr = false; - if (!func) - return 0; + this.processOnlineChange(`exec:SetBit(TH1::kNoStats,${has_stats ? 0 : 1})`, this); - if (!skip_eval && !func.evalPar) { - try { - if (!proivdeEvalPar(func)) - iserr = true; - } catch { - iserr = true; - } + return has_stats; } - if (func.evalPar && !iserr) { - try { - y = func.evalPar(x); - return y; - } catch { - y = 0; - } + /** @summary Returns true if stats box fill can be ignored */ + isIgnoreStatsFill() { + return !this.getObject() || (!this.draw_content && !this.#create_stats && !this.hasSnapId()); } - const np = func.fSave.length - 3; - if ((np < 2) || (func.fSave[np + 1] === func.fSave[np + 2])) return 0; - return _getTF1Save(func, x); -} - -const PadDrawOptions = ['LOGXY', 'LOGX', 'LOGY', 'LOGZ', 'LOGV', 'LOG', 'LOG2X', 'LOG2Y', 'LOG2', - 'LNX', 'LNY', 'LN', 'GRIDXY', 'GRIDX', 'GRIDY', 'TICKXY', 'TICKX', 'TICKY', 'TICKZ', 'FB', 'GRAYSCALE']; + /** @summary Create stat box for histogram if required */ + createStat(force) { + const histo = this.getHisto(), + o = this.getOptions(); + if (!histo) + return null; -/** - * @summary Painter for TH1 classes - * @private - */ + if (!force && !o.ForceStat) { + if (o.NoStat || histo.TestBit(kNoStats) || !settings.AutoStat) + return null; + if (!this.isMainPainter()) + return null; + } -let TH1Painter$2 = class TH1Painter extends THistPainter { + const st = gStyle; + let stats = this.findStat(), + optstat = o.optstat, + optfit = o.optfit; - /** @summary Convert TH1K into normal binned histogram */ - convertTH1K() { - const histo = this.getObject(); - if (histo.fReady) return; + if (optstat !== undefined) { + if (stats) + stats.fOptStat = optstat; + o.optstat = undefined; + } else + optstat = histo.$custom_stat || st.fOptStat; - const arr = histo.fArray, entries = histo.fEntries; // array of values - histo.fNcells = histo.fXaxis.fNbins + 2; - histo.fArray = new Float64Array(histo.fNcells).fill(0); - for (let n = 0; n < histo.fNIn; ++n) - histo.Fill(arr[n]); - histo.fReady = 1; - histo.fEntries = entries; - } + if (optfit !== undefined) { + if (stats) + stats.fOptFit = optfit; + o.optfit = undefined; + } else + optfit = st.fOptFit; - /** @summary Scan content of 1-D histogram - * @desc Detect min/max values for x and y axis - * @param {boolean} when_axis_changed - true when zooming was changed, some checks may be skipped */ - scanContent(when_axis_changed) { - if (when_axis_changed && !this.nbinsx) - when_axis_changed = false; + if (!stats && !optstat && !optfit) + return null; - if (this.isTH1K()) - this.convertTH1K(); + this.#create_stats = true; - const histo = this.getHisto(); + if (stats) + return stats; - if (!when_axis_changed) - this.extractAxesProperties(1); + stats = create$1(clTPaveStats); + Object.assign(stats, { + fName: 'stats', fOptStat: optstat, fOptFit: optfit, + fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, + fTextAlign: 12 + }); - const left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'), - pad_logy = this.getPadPainter()?.getPadLog(this.options.BarStyle >= 20 ? 'x' : 'y'), - f1 = this.options.Func ? this.findFunction(clTF1) : null; + stats.AddText(histo.fName); - if (when_axis_changed && (left === this.scan_xleft) && (right === this.scan_xright)) - return; + this.addFunction(stats); - // Paint histogram axis only - this.draw_content = !(this.options.Axis > 0); + return stats; + } - this.scan_xleft = left; - this.scan_xright = right; + /** @summary Find function in histogram list of functions */ + findFunction(type_name, obj_name) { + const funcs = this.getHisto()?.fFunctions?.arr; + if (!funcs) + return null; - const profile = this.isTProfile(); - let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0, first = true, value, err; + for (let i = 0; i < funcs.length; ++i) { + const f = funcs[i]; + if (obj_name && (f.fName !== obj_name)) + continue; + if (f._typename === type_name) + return f; + } - for (let i = 0; i < this.nbinsx; ++i) { - value = histo.getBinContent(i + 1); - hsum += profile ? histo.fBinEntries[i + 1] : value; + return null; + } - if ((i < left) || (i >= right)) - continue; + /** @summary Add function to histogram list of functions */ + addFunction(obj, asfirst) { + const histo = this.getHisto(); + if (!histo || !obj) + return; - if ((value > 0) && ((hmin_nz === 0) || (value < hmin_nz))) - hmin_nz = value; + if (!histo.fFunctions) + histo.fFunctions = create$1(clTList); - if (first) { - hmin = hmax = value; - first = false; - } + if (asfirst) + histo.fFunctions.AddFirst(obj); + else + histo.fFunctions.Add(obj); + } - err = this.options.Error ? histo.getBinError(i + 1) : 0; + /** @summary Check if such function should be drawn directly */ + needDrawFunc(histo, func) { + const o = this.getOptions(); - hmin = Math.min(hmin, value - err); - hmax = Math.max(hmax, value + err); + if (func._typename === clTPaveStats) + return (func.fName !== 'stats') || (!histo.TestBit(kNoStats) && !o.NoStat); // && (!o.Same || o.ForceStat)) - if (f1) { - // similar code as in THistPainter, line 7196 - const x = histo.fXaxis.GetBinCenter(i + 1), - v = getTF1Value(f1, x); - if (v !== undefined) { - hmax = Math.max(hmax, v); - if (pad_logy && (value > 0) && (v > 0.3 * value)) - hmin_nz = Math.min(hmin_nz, v); - } - } - } + if ((func._typename === clTF1) || (func._typename === clTF2)) + return o.AllFunc || !func.TestBit(BIT(9)); // TF1::kNotDraw - // account overflow/underflow bins - if (profile) - hsum += histo.fBinEntries[0] + histo.fBinEntries[this.nbinsx + 1]; - else - hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1); + if ((func._typename === 'TGraphDelaunay') || (func._typename === 'TGraphDelaunay2D')) + return false; // do not try to draw delaunay classes - this.stat_entries = (histo.fEntries > 1) ? histo.fEntries : hsum; + return func._typename !== clTPaletteAxis; + } - this.hmin = hmin; - this.hmax = hmax; + /** @summary Method draws functions from the histogram list of functions + * @return {Promise} fulfilled when drawing is ready */ + async drawFunctions() { + const handler = new FunctionsHandler(this, this.getPadPainter(), this.getHisto().fFunctions, true); + return handler.drawNext(0); // returns this painter + } - // this.ymin_nz = hmin_nz; // value can be used to show optimal log scale + /** @summary Method used to update functions which are prepared before + * @return {Promise} fulfilled when drawing is ready */ + async updateFunctions() { + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; + return res; + } - if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) - this.draw_content = false; + /** @summary Returns selected index for specified axis + * @desc be aware - here indexes starts from 0 */ + getSelectIndex(axis, side, add) { + let indx, taxis = this.getAxis(axis); + const nbin = this[`nbins${axis}`] ?? 0, + o = this.getOptions(); + + if (o.second_x && axis === 'x') + axis = 'x2'; + if (o.second_y && axis === 'y') + axis = 'y2'; + const fp = this.getFramePainter(), + min = fp ? fp[`zoom_${axis}min`] : 0, + max = fp ? fp[`zoom_${axis}max`] : 0; - let set_zoom = false; + if ((min !== max) && taxis) { + if (side === 'left') + indx = taxis.FindBin(min, add || 0); + else + indx = taxis.FindBin(max, (add || 0) + 0.5); + if (indx < 0) + indx = 0; + else if (indx > nbin) + indx = nbin; + } else + indx = (side === 'left') ? 0 : nbin; - if (this.draw_content || (this.isMainPainter() && (this.options.Axis > 0) && !this.options.ohmin && !this.options.ohmax && histo.fMinimum === kNoZoom && histo.fMaximum === kNoZoom)) { - if (hmin >= hmax) { - if (hmin === 0) { - this.ymin = 0; this.ymax = 1; - } else if (hmin < 0) { - this.ymin = 2 * hmin; this.ymax = 0; - } else { - this.ymin = 0; this.ymax = hmin * 2; - } - } else { - if (pad_logy) { - this.ymin = (hmin_nz || hmin) * 0.5; - this.ymax = hmax*2*(0.9/0.95); - } else { - this.ymin = hmin; - this.ymax = hmax; - } - } + // TAxis object of histogram, where user range can be stored + if (taxis) { + if ((taxis.fFirst === taxis.fLast) || !taxis.TestBit(EAxisBits.kAxisRange) || + ((taxis.fFirst === 1) && (taxis.fLast === nbin))) + taxis = null; } - // final adjustment like in THistPainter.cxx line 7309 - if (!this._exact_y_range && !this._set_y_range && !pad_logy) { - if ((this.options.BaseLine !== false) && (this.ymin >= 0)) - this.ymin = 0; - else { - const positive = (this.ymin >= 0); - this.ymin -= gStyle.fHistTopMargin*(this.ymax-this.ymin); - if (positive && (this.ymin < 0)) - this.ymin = 0; - } - this.ymax += gStyle.fHistTopMargin*(this.ymax-this.ymin); + if (side === 'left') { + indx = Math.max(indx, 0); + if (taxis && (taxis.fFirst > 1) && (indx < taxis.fFirst)) + indx = taxis.fFirst - 1; + else if (taxis?.fFirst === 0) // showing underflow bin + indx = -1; + } else { + indx = Math.min(indx, nbin); + if (taxis && (taxis.fLast <= nbin) && (indx > taxis.fLast)) + indx = taxis.fLast; + else if (taxis?.fLast === nbin + 1) + indx = nbin + 1; } - hmin = this.options.minimum; - hmax = this.options.maximum; + return indx; + } - if ((hmin === hmax) && (hmin !== kNoZoom)) { - if (hmin < 0) { - hmin *= 2; hmax = 0; - } else { - hmin = 0; hmax *= 2; - if (!hmax) hmax = 1; - } - } + /** @summary Unzoom user range if any */ + unzoomUserRange(dox, doy, doz) { + const histo = this.getHisto(), + o = this.getOptions(); - this._set_y_range = false; + if (!histo) + return false; - if ((hmin !== kNoZoom) && (hmax !== kNoZoom) && !this.draw_content && - ((this.ymin === this.ymax) || (this.ymin > hmin) || (this.ymax < hmax))) { - this.ymin = hmin; - this.ymax = hmax; - this._set_y_range = true; - } else { - if (hmin !== kNoZoom) { - this._set_y_range = true; - if (hmin < this.ymin) - this.ymin = hmin; - set_zoom = true; - } - if (hmax !== kNoZoom) { - this._set_y_range = true; - if (hmax > this.ymax) - this.ymax = hmax; - set_zoom = true; - } - } + let res = false; - // always set zoom when hmin/hmax is configured - // fMinimum/fMaximum values is a way how ROOT handles Y scale zooming for TH1 + const unzoomTAxis = obj => { + if (!obj || !obj.TestBit(EAxisBits.kAxisRange)) + return false; + if (obj.fFirst === obj.fLast) + return false; + if ((obj.fFirst <= 1) && (obj.fLast >= obj.fNbins)) + return false; + obj.InvertBit(EAxisBits.kAxisRange); + return true; + }, uzoomMinMax = ndim => { + if (this.getDimension() !== ndim) + return false; + if ((o.minimum === kNoZoom) && (o.maximum === kNoZoom)) + return false; + if (!this.draw_content) + return false; // if not drawing content, not change min/max + o.minimum = o.maximum = kNoZoom; + this.scanContent(); // to reset ymin/ymax + return true; + }; - if (!when_axis_changed) { - if (set_zoom) { - this.zoom_ymin = (hmin === kNoZoom) ? this.ymin : hmin; - this.zoom_ymax = (hmax === kNoZoom) ? this.ymax : hmax; - } else { - delete this.zoom_ymin; - delete this.zoom_ymax; - } - } + if (dox && unzoomTAxis(histo.fXaxis)) + res = true; + if (doy && (unzoomTAxis(histo.fYaxis) || uzoomMinMax(1))) + res = true; + if (doz && (unzoomTAxis(histo.fZaxis) || uzoomMinMax(2))) + res = true; - // used in FramePainter.isAllowedDefaultYZooming - this.wheel_zoomy = (this.getDimension() > 1) || !this.draw_content; + return res; } - /** @summary Count histogram statistic */ - countStat(cond, count_skew) { - const profile = this.isTProfile(), - histo = this.getHisto(), xaxis = histo.fXaxis, - left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'), - fp = this.getFramePainter(), - res = { name: histo.fName, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, - entries: this.stat_entries, eff_entries: 0, xmax: 0, wmax: 0, skewx: 0, skewd: 0, kurtx: 0, kurtd: 0 }, - has_counted_stat = !fp.isAxisZoomed('x') && (Math.abs(histo.fTsumw) > 1e-300); - let stat_sumw = 0, stat_sumw2 = 0, stat_sumwx = 0, stat_sumwx2 = 0, stat_sumwy = 0, stat_sumwy2 = 0, - i, xx = 0, w = 0, xmax = null, wmax = null; - - if (!isFunc(cond)) cond = null; - - for (i = left; i < right; ++i) { - xx = xaxis.GetBinCoord(i + 0.5); + /** @summary Add different interactive handlers + * @desc only first (main) painter in list allowed to add interactive functionality + * Most of interactivity now handled by frame + * @return {Promise} for ready */ + async addInteractivity() { + const ismain = this.isMainPainter(), + second_axis = (this.getOptions().AxisPos > 0), + fp = (ismain || second_axis) ? this.getFramePainter() : null; + return fp?.addInteractivity(!ismain && second_axis) ?? false; + } - if (cond && !cond(xx)) continue; + /** @summary Invoke dialog to enter and modify user range */ + changeUserRange(menu, arg) { + const histo = this.getHisto(), + taxis = histo ? histo[`f${arg}axis`] : null; + if (!taxis) + return; - if (profile) { - w = histo.fBinEntries[i + 1]; - stat_sumwy += histo.fArray[i + 1]; - stat_sumwy2 += histo.fSumw2[i + 1]; - } else - w = histo.getBinContent(i + 1); + let curr = `[1,${taxis.fNbins}]`; + if (taxis.TestBit(EAxisBits.kAxisRange)) + curr = `[${taxis.fFirst},${taxis.fLast}]`; + menu.input(`Enter user range for axis ${arg} like [1,${taxis.fNbins}]`, curr).then(res => { + if (!res) + return; + res = JSON.parse(res); + if (!res || (res.length !== 2)) + return; + const first = parseInt(res[0]), + last = parseInt(res[1]); + if (!Number.isInteger(first) || !Number.isInteger(last)) + return; + taxis.fFirst = first; + taxis.fLast = last; + taxis.SetBit(EAxisBits.kAxisRange, (taxis.fFirst < taxis.fLast) && (taxis.fFirst >= 1) && (taxis.fLast <= taxis.fNbins)); - if ((xmax === null) || (w > wmax)) { - xmax = xx; - wmax = w; - } + this.interactiveRedraw(); + }); + } - if (!has_counted_stat) { - stat_sumw += w; - stat_sumw2 += w * w; - stat_sumwx += w * xx; - stat_sumwx2 += w * xx**2; - } - } + /** @summary Start dialog to modify range of axis where histogram values are displayed */ + changeValuesRange(menu) { + const o = this.getOptions(); - // when no range selection done, use original statistic from histogram - if (has_counted_stat) { - stat_sumw = histo.fTsumw; - stat_sumw2 = histo.fTsumw2; - stat_sumwx = histo.fTsumwx; - stat_sumwx2 = histo.fTsumwx2; - } + let curr; + if ((o.minimum !== kNoZoom) && (o.maximum !== kNoZoom)) + curr = `[${o.minimum},${o.maximum}]`; + else + curr = `[${this.gminbin},${this.gmaxbin}]`; - res.integral = stat_sumw; + menu.input('Enter min/max hist values or empty string to reset', curr).then(res => { + res = res ? JSON.parse(res) : []; - res.eff_entries = stat_sumw2 ? stat_sumw*stat_sumw/stat_sumw2 : Math.abs(stat_sumw); + if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) + o.minimum = o.maximum = kNoZoom; + else { + o.minimum = res[0]; + o.maximum = res[1]; + } - if (Math.abs(stat_sumw) > 1e-300) { - res.meanx = stat_sumwx / stat_sumw; - res.meany = stat_sumwy / stat_sumw; - res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany**2)); - } + this.interactiveRedraw(); + }); + } - if (xmax !== null) { - res.xmax = xmax; - res.wmax = wmax; - } + /** @summary Execute histogram menu command + * @desc Used to catch standard menu items and provide local implementation */ + executeMenuCommand(method, args) { + if (super.executeMenuCommand(method, args)) + return true; - if (count_skew) { - let sum3 = 0, sum4 = 0, np = 0; - for (i = left; i < right; ++i) { - xx = xaxis.GetBinCoord(i + 0.5); - if (cond && !cond(xx)) continue; - w = profile ? histo.fBinEntries[i + 1] : histo.getBinContent(i + 1); - np += w; - sum3 += w * Math.pow(xx - res.meanx, 3); - sum4 += w * Math.pow(xx - res.meanx, 4); + if (method.fClassName === clTAxis) { + const p = isStr(method.$execid) ? method.$execid.indexOf('#') : -1, + kind = p > 0 ? method.$execid.slice(p + 1) : 'x', + fp = this.getFramePainter(); + if (method.fName === 'UnZoom') { + fp?.unzoom(kind); + return true; + } else if (method.fName === 'SetRange') { + const axis = fp?.getAxis(kind), bins = JSON.parse(`[${args}]`); + if (axis && bins?.length === 2) + fp?.zoom(kind, axis.GetBinLowEdge(bins[0]), axis.GetBinLowEdge(bins[1] + 1)); + // let execute command on server + } else if (method.fName === 'SetRangeUser') { + const values = JSON.parse(`[${args}]`); + if (values?.length === 2) + fp?.zoom(kind, values[0], values[1]); + // let execute command on server } - - const stddev3 = Math.pow(res.rmsx, 3), stddev4 = Math.pow(res.rmsx, 4); - if (np * stddev3 !== 0) - res.skewx = sum3 / (np * stddev3); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; - if (np * stddev4 !== 0) - res.kurtx = sum4 / (np * stddev4) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; } - return res; + return false; } - /** @summary Fill stat box */ - fillStatistic(stat, dostat, dofit) { - // no need to refill statistic if histogram is dummy - if (this.isIgnoreStatsFill()) return false; - - if (dostat === 1) dostat = 1111; - if (dofit === 1) dofit = 111; - + /** @summary Fill histogram context menu */ + fillContextMenuItems(menu) { const histo = this.getHisto(), - print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10, - data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); - + fp = this.getFramePainter(), + o = this.getOptions(); - // make empty at the beginning - stat.clearPave(); + if (!histo) + return; - if (print_name > 0) - stat.addText(data.name); + if ((o.Axis <= 0) && !this.isTF1()) + menu.addchk(this.toggleStat(kOnlyCheck), 'Show statbox', () => this.toggleStat()); - if (this.isTProfile()) { - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (this.isMainPainter()) { + menu.sub('Title'); + menu.addchk(this.toggleTitle(kOnlyCheck), 'Show', () => this.toggleTitle()); + menu.add('Edit', () => menu.input('Enter histogram title', histo.fTitle).then(res => { + setHistogramTitle(histo, res); + this.interactiveRedraw(); + })); + menu.endsub(); + } - if (print_mean > 0) { - stat.addText('Mean = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); + if (this.draw_content) { + if (this.getDimension() === 1) + menu.add('User range X', () => this.changeUserRange(menu, 'X')); + else { + menu.sub('User ranges'); + menu.add('X', () => this.changeUserRange(menu, 'X')); + menu.add('Y', () => this.changeUserRange(menu, 'Y')); + if (this.getDimension() > 2) + menu.add('Z', () => this.changeUserRange(menu, 'Z')); + else + menu.add('Values', () => this.changeValuesRange(menu)); + menu.endsub(); } - if (print_rms > 0) { - stat.addText('Std Dev = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - } - } else { - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (isFunc(this.fillHistContextMenu)) + this.fillHistContextMenu(menu); - if (print_mean > 0) - stat.addText('Mean = ' + stat.format(data.meanx)); + menu.addRedrawMenu(this.getPrimary()); + } - if (print_rms > 0) - stat.addText('Std Dev = ' + stat.format(data.rmsx)); + if (o.Mode3D) { + // menu for 3D drawings - if (print_under > 0) - stat.addText('Underflow = ' + stat.format((histo.fArray.length > 0) ? histo.fArray[0] : 0, 'entries')); + if (menu.size() > 0) + menu.separator(); - if (print_over > 0) - stat.addText('Overflow = ' + stat.format((histo.fArray.length > 0) ? histo.fArray[histo.fArray.length - 1] : 0, 'entries')); + const main = this.getMainPainter() || this; - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); + menu.addchk(main.isTooltipAllowed(), 'Show tooltips', () => main.setTooltipAllowed('toggle')); - if (print_skew === 2) - stat.addText(`Skewness = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); - else if (print_skew > 0) - stat.addText(`Skewness = ${stat.format(data.skewx)}`); + menu.addchk(fp?.enable_highlight, 'Highlight bins', () => { + fp.enable_highlight = !fp.enable_highlight; + if (!fp.enable_highlight && fp.mode3d && isFunc(fp.highlightBin3D)) + fp.highlightBin3D(null); + }); - if (print_kurt === 2) - stat.addText(`Kurtosis = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); - else if (print_kurt > 0) - stat.addText(`Kurtosis = ${stat.format(data.kurtx)}`); - } + if (isFunc(fp?.render3D)) { + menu.addchk(main.options.FrontBox, 'Front box', () => { + main.options.FrontBox = !main.options.FrontBox; + fp.render3D(); + }); + menu.addchk(main.options.BackBox, 'Back box', () => { + main.options.BackBox = !main.options.BackBox; + fp.render3D(); + }); + menu.addchk(fp.camera?.isOrthographicCamera, 'Orthographic camera', flag => { + main.options.Ortho = flag; + fp.change3DCamera(flag); + }); + } + + if (this.draw_content) { + menu.addchk(!o.Zero, 'Suppress zeros', () => { + o.Zero = !o.Zero; + this.interactiveRedraw('pad'); + }); - if (dofit) stat.fillFunctionStat(this.findFunction(clTF1), dofit, 1); + if ((o.Lego === 12) || (o.Lego === 14)) { + menu.addchk(o.Zscale, 'Z scale', () => this.toggleColz()); + this.fillPaletteMenu(menu, true); + } + } - return true; + if (isFunc(main.control?.reset)) + menu.add('Reset camera', () => main.control.reset()); + } + + if (this.histogram_updated && fp.zoomChangedInteractive()) + menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); } - /** @summary Draw histogram as bars */ - async drawBars(funcs, height) { - const left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 1), - histo = this.getHisto(), xaxis = histo.fXaxis, - show_text = this.options.Text; - let text_col, text_angle, text_size, - i, x1, x2, grx1, grx2, y, gry1, gry2, w, - bars = '', barsl = '', barsr = '', - side = (this.options.BarStyle > 10) ? this.options.BarStyle % 10 : 0; + /** @summary Returns snap id for object or sub-element + * @private */ + getSnapId(subelem) { + if (this.isTF1() && (subelem === 'x' || subelem === 'y' || subelem === 'z')) + subelem = 'hist#' + subelem; + return super.getSnapId(subelem); + } - if (side > 4) side = 4; - gry2 = funcs.swap_xy ? 0 : height; - if (Number.isFinite(this.options.BaseLine)) { - if (this.options.BaseLine >= funcs.scale_ymin) - gry2 = Math.round(funcs.gry(this.options.BaseLine)); - } + /** @summary Auto zoom into histogram non-empty range + * @abstract */ + autoZoom() {} - if (show_text) { - text_col = this.getColor(histo.fMarkerColor); - text_angle = -1*this.options.TextAngle; - text_size = 20; + /** @summary Process click on histogram-defined buttons */ + clickButton(funcname) { + const fp = this.getFramePainter(); + if (!this.isMainPainter() || !fp) + return false; - if ((histo.fMarkerSize !== 1) && text_angle) - text_size = 0.02*height*histo.fMarkerSize; + switch (funcname) { + case 'ToggleZoom': + if ((fp.zoom_xmin !== fp.zoom_xmax) || (fp.zoom_ymin !== fp.zoom_ymax) || (fp.zoom_zmin !== fp.zoom_zmax)) { + const pr = fp.unzoom(); + fp.zoomChangedInteractive('reset'); + return pr; + } + if (this.draw_content) + return this.autoZoom(); + break; + case 'ToggleLogX': return fp.toggleAxisLog('x'); + case 'ToggleLogY': return fp.toggleAxisLog('y'); + case 'ToggleLogZ': return fp.toggleAxisLog('z'); + case 'ToggleStatBox': return getPromise(this.toggleStat()); + case 'ToggleColorZ': return this.toggleColz(); + } + return false; + } + + /** @summary Fill pad toolbar with histogram-related functions */ + fillToolbar(not_shown) { + const pp = this.getPadPainter(); + if (!pp) + return; + + pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); + pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); + pp.addPadButton('arrow_up', 'Toggle log y', 'ToggleLogY', 'PageUp'); + if (this.getDimension() > 1) + pp.addPadButton('arrow_diag', 'Toggle log z', 'ToggleLogZ'); + pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); + if (!not_shown) + pp.showPadButtons(); + } - this.startTextDrawing(42, text_size, this.draw_g, text_size); + /** @summary Returns tooltip information for 3D drawings */ + get3DToolTip(indx) { + const histo = this.getHisto(), + tip = { bin: indx, name: histo.fName, title: histo.fTitle }; + switch (this.getDimension()) { + case 1: + tip.ix = indx; + tip.iy = 1; + tip.value = histo.getBinContent(tip.ix); + tip.error = histo.getBinError(indx); + tip.lines = this.getBinTooltips(indx - 1); + break; + case 2: + tip.ix = indx % (this.nbinsx + 2); + tip.iy = (indx - tip.ix) / (this.nbinsx + 2); + tip.value = histo.getBinContent(tip.ix, tip.iy); + tip.error = histo.getBinError(indx); + tip.lines = this.getBinTooltips(tip.ix - 1, tip.iy - 1); + break; + case 3: + tip.ix = indx % (this.nbinsx + 2); + tip.iy = ((indx - tip.ix) / (this.nbinsx + 2)) % (this.nbinsy + 2); + tip.iz = (indx - tip.ix - tip.iy * (this.nbinsx + 2)) / (this.nbinsx + 2) / (this.nbinsy + 2); + tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); + tip.error = histo.getBinError(indx); + tip.lines = this.getBinTooltips(tip.ix - 1, tip.iy - 1, tip.iz - 1); + break; } - for (i = left; i < right; ++i) { - x1 = xaxis.GetBinLowEdge(i+1); - x2 = xaxis.GetBinLowEdge(i+2); + return tip; + } - if (funcs.logx && (x2 <= 0)) continue; + /** @summary Create contour object for histogram */ + createContour(nlevels, zmin, zmax, zminpositive, custom_levels) { + const cntr = new HistContour(zmin, zmax), + ndim = this.getDimension(), + is_th2poly = this.isTH2Poly(), + fp = this.getFramePainter(), + o = this.getOptions(); - grx1 = Math.round(funcs.grx(x1)); - grx2 = Math.round(funcs.grx(x2)); + if (custom_levels) + cntr.createCustom(custom_levels); + else { + if (nlevels < 2) + nlevels = gStyle.fNumberContours; + const pad = this.getPadPainter()?.getRootPad(true), + logv = pad?.fLogv ?? ((ndim === 2) && pad?.fLogz); - y = histo.getBinContent(i+1); - if (funcs.logy && (y < funcs.scale_ymin)) continue; - gry1 = Math.round(funcs.gry(y)); + cntr.createNormal(nlevels, logv ?? 0, zminpositive); + } - w = grx2 - grx1; - grx1 += Math.round(histo.fBarOffset/1000*w); - w = Math.round(histo.fBarWidth/1000*w); + cntr.configIndicies(o.Zero && !is_th2poly ? -1 : 0, cntr.colzmin || !o.Zero || is_th2poly ? 0 : -1); - if (funcs.swap_xy) - bars += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - else - bars += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; + if (fp && (ndim < 3) && !fp.mode3d) { + fp.zmin = cntr.colzmin; + fp.zmax = cntr.colzmax; + } - if (side > 0) { - grx2 = grx1 + w; - w = Math.round(w * side / 10); - if (funcs.swap_xy) { - barsl += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - barsr += `M${gry2},${grx2}h${gry1-gry2}v${-w}h${gry2-gry1}z`; - } else { - barsl += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - barsr += `M${grx2},${gry1}h${-w}v${gry2-gry1}h${w}z`; - } - } + this.#contour = cntr; + return cntr; + } - if (show_text && y) { - const text = (y === Math.round(y)) ? y.toString() : floatToString(y, gStyle.fPaintTextFormat); + /** @summary Return Z-scale ranges to create contour */ + #getContourRanges(main, fp) { + const o = this.getOptions(), + src = (this !== main) && ((main?.minbin !== undefined) || main?.options.ohmin) && !o.IgnoreMainScale && !main?.tt_handle?.ScatterPlot ? main : this; + let apply_min, zmin = src.minbin, zmax = src.maxbin, zminpos = src.minposbin; - if (funcs.swap_xy) - this.drawText({ align: 12, x: Math.round(gry1 + text_size/2), y: Math.round(grx1+0.1), height: Math.round(w*0.8), text, color: text_col, latex: 0 }); - else if (text_angle) - this.drawText({ align: 12, x: grx1+w/2, y: Math.round(gry1 - 2 - text_size/5), width: 0, height: 0, rotate: text_angle, text, color: text_col, latex: 0 }); - else - this.drawText({ align: 22, x: Math.round(grx1 + w*0.1), y: Math.round(gry1 - 2 - text_size), width: Math.round(w*0.8), height: text_size, text, color: text_col, latex: 0 }); + if (zmin === zmax) { + if (src.options.ohmin && src.options.ohmax) { + zmin = src.options.hmin; + zmax = src.options.hmax; + zminpos = Math.max(zmin, zmax * 1e-10); + } else { + zmin = src.gminbin; + zmax = src.gmaxbin; + zminpos = src.gminposbin; } } - if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) - .call(this.fillatt.func); + let gzmin = zmin, gzmax = zmax; + if (o.minimum !== kNoZoom) { + zmin = o.minimum; + gzmin = Math.min(gzmin, zmin); + apply_min = true; } - - if (barsl) { - this.draw_g.append('svg:path') - .attr('d', barsl) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); + if (o.maximum !== kNoZoom) { + zmax = o.maximum; + gzmax = Math.max(gzmax, zmax); + apply_min = false; } - if (barsr) { - this.draw_g.append('svg:path') - .attr('d', barsr) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatHex()); + if (zmin >= zmax) { + if (apply_min || !zmin) + zmax = zmin + 1; + else + zmin = zmax - 1; } - if (show_text) - return this.finishTextDrawing(); - } - - /** @summary Draw histogram as filled errors */ - drawFilledErrors(funcs) { - const left = this.getSelectIndex('x', 'left', 0), - right = this.getSelectIndex('x', 'right', 0), - histo = this.getHisto(), bins1 = [], bins2 = []; - - for (let i = left; i < right; ++i) { - const x = histo.fXaxis.GetBinCoord(i+0.5); - if (funcs.logx && (x <= 0)) continue; - const grx = Math.round(funcs.grx(x)), - y = histo.getBinContent(i+1), - yerr = histo.getBinError(i+1); - if (funcs.logy && (y-yerr < funcs.scale_ymin)) continue; - - bins1.push({ grx, gry: Math.round(funcs.gry(y + yerr)) }); - bins2.unshift({ grx, gry: Math.round(funcs.gry(y - yerr)) }); + if (fp?.zoomChangedInteractive('z')) { + const mod = (fp.zoom_zmin !== fp.zoom_zmax); + zmin = mod ? fp.zoom_zmin : gzmin; + zmax = mod ? fp.zoom_zmax : gzmax; } - const line = this.options.ErrorKind !== 4, - path1 = buildSvgCurve(bins1, { line }), - path2 = buildSvgCurve(bins2, { line, cmd: 'L' }); - - this.draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(this.fillatt.func); + return { zmin, zmax, zminpos, gzmin, gzmax }; } - /** @summary Draw TH1 as hist/line/curve - * @return Promise or scalar value */ - drawNormal(funcs, width, height) { - const left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 2), - histo = this.getHisto(), - want_tooltip = !this.isBatchMode() && settings.Tooltip, - xaxis = histo.fXaxis, - exclude_zero = !this.options.Zero, - show_errors = this.options.Error, - show_curve = this.options.Curve, - show_text = this.options.Text, - text_profile = show_text && (this.options.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, - grpnts = []; - let res = '', lastbin = false, - show_markers = this.options.Mark, - show_line = this.options.Line, - startx, startmidx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, - path_fill = null, path_err = null, path_marker = null, path_line = '', - hints_err = null, hints_marker = null, hsz = 5, - do_marker = false, do_err = false, - dend = 0, dlw = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx, lx, ly, mmx1, mmx2, - text_col, text_angle, text_size; + /** @summary Return contour object */ + getContour(force_recreate) { + if ((force_recreate === false) || (this.#contour && (force_recreate !== true))) + return this.#contour; - if (show_errors && !show_markers && (histo.fMarkerStyle > 1)) - show_markers = true; + const main = this.getMainPainter(), + fp = this.getFramePainter(), + main_cont = main?.getContour(false); - if (this.options.ErrorKind === 2) { - if (this.fillatt.empty()) show_markers = true; - else path_fill = ''; - } else if (show_errors) { - show_line = false; - path_err = ''; - hints_err = want_tooltip ? '' : null; - do_err = true; + if (main_cont && (main !== this) && !this.getOptions().IgnoreMainScale) { + this.#contour = main_cont; + return main_cont; } - dlw = this.lineatt.width + gStyle.fEndErrorSize; - if (this.options.ErrorKind === 1) - dend = Math.floor((this.lineatt.width-1)/2); + // if not initialized, first create contour array + // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz + // when no same0 draw option specified, use main painter for creating contour, also ignore scatter drawing for main painter + const histo = this.getObject(), + r = this.#getContourRanges(main, fp); + let nlevels = 0, custom_levels; - if (show_markers) { - // draw markers also when e2 option was specified - let style = this.options.MarkStyle; - if (!style && (histo.fMarkerStyle === 1)) style = 8; // as in recent ROOT changes - this.createAttMarker({ attr: histo, style }); // when style not configured, it will be ignored - if (this.markeratt.size > 0) { - // simply use relative move from point, can optimize in the future - path_marker = ''; - do_marker = true; - this.markeratt.resetPos(); - if ((hints_err === null) && want_tooltip && (!this.markeratt.fill || (this.markeratt.getFullSize() < 7))) { - hints_marker = ''; - hsz = Math.max(5, Math.round(this.markeratt.getFullSize()*0.7)); - } - } else - show_markers = false; + if (histo.fContour?.length > 1) { + if (histo.TestBit(kUserContour)) + custom_levels = histo.fContour; + else + nlevels = histo.fContour.length; } - const draw_markers = show_errors || show_markers, - draw_any_but_hist = draw_markers || show_text || show_line || show_curve, - draw_hist = this.options.Hist && (!this.lineatt.empty() || !this.fillatt.empty()); - - if (!draw_hist && !draw_any_but_hist) - return this.removeG(); + const cntr = this.createContour(nlevels, r.zmin, r.zmax, r.zminpos, custom_levels); - if (show_text) { - text_col = this.getColor(histo.fMarkerColor); - text_angle = -1*this.options.TextAngle; - text_size = 20; + if ((this.getDimension() < 3) && fp) { + fp.zmin = r.gzmin; + fp.zmax = r.gzmax; - if ((histo.fMarkerSize !== 1) && text_angle) - text_size = 0.02*height*histo.fMarkerSize; - - if (!text_angle && !this.options.TextKind) { - const space = width / (right - left + 1); - if (space < 3 * text_size) { - text_angle = 270; - text_size = Math.round(space*0.7); - } - } - - this.startTextDrawing(42, text_size, this.draw_g, text_size); - } - - // if there are too many points, exclude many vertical drawings at the same X position - // instead define min and max value and made min-max drawing - const use_minmax = draw_any_but_hist || ((right - left) > 3*width), - - // just to get correct values for the specified bin - extract_bin = bin => { - bincont = histo.getBinContent(bin+1); - if (exclude_zero && (bincont === 0)) return false; - mx1 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin+1))); - mx2 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin+2))); - midx = Math.round((mx1 + mx2) / 2); - if (startmidx === undefined) startmidx = midx; - my = Math.round(funcs.gry(bincont)); - if (show_errors) { - binerr = histo.getBinError(bin+1); - yerr1 = Math.round(my - funcs.gry(bincont + binerr)); // up - yerr2 = Math.round(funcs.gry(bincont - binerr) - my); // down + if ((r.gzmin !== cntr.colzmin) || (r.gzmax !== cntr.colzmax)) { + fp.zoom_zmin = cntr.colzmin; + fp.zoom_zmax = cntr.colzmax; } else - yerr1 = yerr2 = 20; - - return true; - }, draw_errbin = () => { - let edx = 5; - if (this.options.errorX > 0) { - edx = Math.round((mx2 - mx1) * this.options.errorX); - mmx1 = midx - edx; - mmx2 = midx + edx; - if (this.options.ErrorKind === 1) - path_err += `M${mmx1+dend},${my-dlw}v${2*dlw}m0,-${dlw}h${mmx2-mmx1-2*dend}m0,-${dlw}v${2*dlw}`; - else - path_err += `M${mmx1+dend},${my}h${mmx2-mmx1-2*dend}`; - } - if (this.options.ErrorKind === 1) - path_err += `M${midx-dlw},${my-yerr1+dend}h${2*dlw}m${-dlw},0v${yerr1+yerr2-2*dend}m${-dlw},0h${2*dlw}`; - else - path_err += `M${midx},${my-yerr1+dend}v${yerr1+yerr2-2*dend}`; - if (hints_err !== null) { - const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); - hints_err += `M${midx-edx},${my-he1}h${2*edx}v${he1+he2}h${-2*edx}z`; - } - }, draw_bin = bin => { - if (extract_bin(bin)) { - if (show_text) { - const cont = text_profile ? histo.fBinEntries[bin+1] : bincont; - - if (cont !== 0) { - const lbl = (cont === Math.round(cont)) ? cont.toString() : floatToString(cont, gStyle.fPaintTextFormat); + fp.zoom_zmin = fp.zoom_zmax = 0; + } - if (text_angle) - this.drawText({ align: 12, x: midx, y: Math.round(my - 2 - text_size/5), width: 0, height: 0, rotate: text_angle, text: lbl, color: text_col, latex: 0 }); - else - this.drawText({ align: 22, x: Math.round(mx1 + (mx2-mx1)*0.1), y: Math.round(my-2-text_size), width: Math.round((mx2-mx1)*0.8), height: text_size, text: lbl, color: text_col, latex: 0 }); - } - } + return cntr; + } - if (show_line) { - if (path_line.length === 0) - path_line = `M${midx},${my}`; - else if (lx === midx) - path_line += `v${my-ly}`; - else if (ly === my) - path_line += `h${midx-lx}`; - else - path_line += `l${midx-lx},${my-ly}`; - lx = midx; ly = my; - } else if (show_curve) - grpnts.push({ grx: (mx1 + mx2) / 2, gry: funcs.gry(bincont) }); - - if (draw_markers) { - if ((my >= -yerr1) && (my <= height + yerr2)) { - if (path_fill !== null) - path_fill += `M${mx1},${my-yerr1}h${mx2-mx1}v${yerr1+yerr2+1}h${mx1-mx2}z`; - if ((path_marker !== null) && do_marker) { - path_marker += this.markeratt.create(midx, my); - if (hints_marker !== null) - hints_marker += `M${midx-hsz},${my-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; - } + /** @summary Return levels from contour object */ + getContourLevels(force_recreate) { + return this.getContour(force_recreate).getLevels(); + } - if ((path_err !== null) && do_err) - draw_errbin(); - } - } - } - }; + /** @summary Returns color palette associated with histogram + * @desc Create if required, checks pad and canvas for custom palette */ + getHistPalette(force) { + let pal = force ? null : this.#color_palette; + if (pal) + return pal; + const pp = this.getPadPainter(), + o = this.getOptions(); - // check if we should draw markers or error marks directly, skipping optimization - if (do_marker || do_err) { - if (!settings.OptimizeDraw || ((right-left < 50000) && (settings.OptimizeDraw === 1))) { - for (i = left; i < right; ++i) { - if (extract_bin(i)) { - if (path_marker !== null) - path_marker += this.markeratt.create(midx, my); - if (hints_marker !== null) - hints_marker += `M${midx-hsz},${my-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; - if (path_err !== null) - draw_errbin(); - } - } - do_err = do_marker = false; - } + if (!o.Palette) { + if (isFunc(pp?.getCustomPalette)) + pal = pp.getCustomPalette(); } + if (!pal) + pal = getColorPalette(o.Palette, pp?.isGrayscale()); + this.#color_palette = pal; + return pal; + } + /** @summary Remove palette */ + clearHistPalette() { + this.#color_palette = undefined; + } - for (i = left; i <= right; ++i) { - x = xaxis.GetBinLowEdge(i+1); + /** @summary Fill menu entries for palette */ + fillPaletteMenu(menu, only_palette) { + const o = this.getOptions(); - if (this.logx && (x <= 0)) continue; + menu.addPaletteMenu(o.Palette || settings.Palette, arg => { + o.Palette = parseInt(arg); + this.getHistPalette(true); + this.redraw(); // redraw histogram + }); + if (!only_palette) { + menu.add('Default position', () => { + this.drawColorPalette(o.Zscale, false, true) + .then(() => this.processOnlineChange('drawopt')); + }, 'Set default position for palette'); - grx = Math.round(funcs.grx(x)); + const pal = this.findFunction(clTPaletteAxis), + is_vert = !pal ? true : pal.fX2NDC - pal.fX1NDC < pal.fY2NDC - pal.fY1NDC; + menu.addchk(is_vert, 'Vertical', flag => { + o.Zvert = flag; + this.drawColorPalette(o.Zscale, false, 'toggle') + .then(() => this.processOnlineChange('drawopt')); + }, 'Toggle palette vertical/horizontal flag'); - lastbin = (i === right); + menu.add('Bring to front', () => this.getPadPainter()?.findPainterFor(pal)?.bringToFront()); + } + } - if (lastbin && (left curry_max) - bestimin = i; - - curry_min = Math.min(curry_min, gry); - curry_max = Math.max(curry_max, gry); - curry = gry; - } else { - if (draw_any_but_hist) { - if (bestimin === bestimax) - draw_bin(bestimin); - else if (bestimin < bestimax) { - draw_bin(bestimin); draw_bin(bestimax); - } else { - draw_bin(bestimax); draw_bin(bestimin); - } - } + /** @summary draw color palette + * @return {Promise} when done */ + async drawColorPalette(enabled, postpone_draw, can_move) { + const o = this.getOptions(), + do_toggle = can_move === 'toggle'; - // when several points at same X differs, need complete logic - if (draw_hist && ((curry_min !== curry_max) || (prevy !== curry_min))) { - if (prevx !== currx) - res += 'h'+(currx-prevx); + // in special cases like scatter palette drawing is ignored + if (o.IgnorePalette) + return null; - if (curry === curry_min) { - if (curry_max !== prevy) - res += 'v' + (curry_max - prevy); - if (curry_min !== curry_max) - res += 'v' + (curry_min - curry_max); - } else { - if (curry_min !== prevy) - res += 'v' + (curry_min - prevy); - if (curry_max !== curry_min) - res += 'v' + (curry_max - curry_min); - if (curry !== curry_max) - res += 'v' + (curry - curry_max); - } + // only when create new palette, one could change frame size + const mp = this.getMainPainter(); + if (mp && (mp !== this) && (mp.draw_content !== false) && mp.options.Zscale) + return null; - prevx = currx; - prevy = curry; - } + const pp = this.getPadPainter(); + let pal = this.findFunction(clTPaletteAxis), + pal_painter = pp?.findPainterFor(pal); - if (lastbin && (prevx !== grx)) - res += 'h' + (grx-prevx); + const found_in_func = Boolean(pal); - bestimin = bestimax = i; - curry_min = curry_max = curry = gry; - currx = grx; - } - // end of use_minmax - } else if ((gry !== curry) || lastbin) { - if (grx !== currx) res += `h${grx-currx}`; - if (gry !== curry) res += `v${gry-curry}`; - curry = gry; - currx = grx; + if (!pal_painter && !pal && !o.Axis) { + pal_painter = pp?.findPainterFor(undefined, undefined, clTPaletteAxis); + if (pal_painter) { + pal = pal_painter.getObject(); + // add to list of functions + this.addFunction(pal, true); } } - const fill_for_interactive = want_tooltip && this.fillatt.empty() && draw_hist && !draw_markers && !show_line && !show_curve, - add_hist = () => { - this.draw_g.append('svg:path') - .attr('d', res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) - .style('stroke-linejoin', 'miter') - .call(this.lineatt.func) - .call(this.fillatt.func); - }; - let h0 = height + 3; - if (!fill_for_interactive) { - const gry0 = Math.round(funcs.gry(0)); - if (gry0 <= 0) - h0 = -3; - else if (gry0 < height) - h0 = gry0; - } - const close_path = `L${currx},${h0}H${startx}Z`; - - if (res && draw_hist && !this.fillatt.empty()) { - add_hist(); - res = ''; - } + if (!enabled) { + if (pal_painter && !o.Same) { + o.Zvert = pal_painter.isPaletteVertical(); + pal_painter.Enabled = false; + pal_painter.removeG(); // completely remove drawing without need to redraw complete pad + } - if (draw_markers || show_line || show_curve) { - if (!path_line && grpnts.length) - path_line = buildSvgCurve(grpnts); + return null; + } - if (path_fill) { - this.draw_g.append('svg:path') - .attr('d', path_fill) - .call(this.fillatt.func); - } else if (path_line && !this.fillatt.empty() && !draw_hist) { - this.draw_g.append('svg:path') - .attr('d', path_line + `L${midx},${h0}H${startmidx}Z`) - .call(this.fillatt.func); - } + if (!pal) { + pal = create$1(clTPaletteAxis); - if (path_err) { - this.draw_g.append('svg:path') - .attr('d', path_err) - .call(this.lineatt.func); - } + if (!can_move) + can_move = !o.Same; - if (hints_err) { - this.draw_g.append('svg:path') - .attr('d', hints_err) - .style('fill', 'none') - .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); - } + pal.fInit = 1; + pal.$can_move = can_move; + pal.$generated = true; - if (path_line) { - this.draw_g.append('svg:path') - .attr('d', path_line) - .style('fill', 'none') - .call(this.lineatt.func); - } + if (o.Zvert) + Object.assign(pal, { fX1NDC: 1.005 - gStyle.fPadRightMargin, fX2NDC: 1.045 - gStyle.fPadRightMargin, fY1NDC: gStyle.fPadBottomMargin, fY2NDC: 1 - gStyle.fPadTopMargin }); + else + Object.assign(pal, { fX1NDC: gStyle.fPadLeftMargin, fX2NDC: 1 - gStyle.fPadRightMargin, fY1NDC: 1.005 - gStyle.fPadTopMargin, fY2NDC: 1.045 - gStyle.fPadTopMargin }); - if (path_marker) { - this.draw_g.append('svg:path') - .attr('d', path_marker) - .call(this.markeratt.func); - } + Object.assign(pal.fAxis, { fChopt: '+', fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11 }); - if (hints_marker) { - this.draw_g.append('svg:path') - .attr('d', hints_marker) - .style('fill', 'none') - .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); + if (this.getDimension() === 2) { + const zaxis = this.getHisto().fZaxis; + Object.assign(pal.fAxis, { + fTitle: zaxis.fTitle, fTitleSize: zaxis.fTitleSize, + fTitleOffset: zaxis.fTitleOffset, fTitleColor: zaxis.fTitleColor, + fLineColor: zaxis.fAxisColor, fTextSize: zaxis.fLabelSize, + fTextColor: zaxis.fLabelColor, fTextFont: zaxis.fLabelFont, + fLabelOffset: zaxis.fLabelOffset + }); } - } - if (res && draw_hist) - add_hist(); + // place colz in the beginning, that stat box is always drawn on the top + this.addFunction(pal, true); + } else if ((pal_painter?.isPaletteVertical() !== undefined) && !do_toggle) + o.Zvert = pal_painter.isPaletteVertical(); - if (show_text) - return this.finishTextDrawing(); - } + const fp = this.getFramePainter(); - /** @summary Draw TH1 bins in SVG element - * @return Promise or scalar value */ - draw1DBins() { - this.createHistDrawAttributes(); + // keep palette width + if (can_move && fp && pal.$can_move) { + if (o.Zvert) { + if (do_toggle) { + const d = pal.fY2NDC - pal.fY1NDC; + pal.fX1NDC = fp.fX2NDC + 0.005; + pal.fX2NDC = pal.fX1NDC + d; + } + if (pal.fX1NDC > (fp.fX1NDC + fp.fX2NDC) * 0.5) { + pal.fX2NDC = fp.fX2NDC + 0.005 + (pal.fX2NDC - pal.fX1NDC); + pal.fX1NDC = fp.fX2NDC + 0.005; + } else { + pal.fX1NDC = fp.fX1NDC - 0.03 - (pal.fX2NDC - pal.fX1NDC); + pal.fX2NDC = fp.fX1NDC - 0.03; + } + pal.fY1NDC = fp.fY1NDC; + pal.fY2NDC = fp.fY2NDC; + } else { + if (do_toggle) { + const d = pal.fX2NDC - pal.fX1NDC; + pal.fY1NDC = fp.fY2NDC + 0.005; + pal.fY2NDC = pal.fY1NDC + d; + } - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - width = pmain.getFrameWidth(), height = pmain.getFrameHeight(); + pal.fX1NDC = fp.fX1NDC; + pal.fX2NDC = fp.fX2NDC; + if (pal.fY2NDC > (fp.fY1NDC + fp.fY2NDC) * 0.5) { + pal.fY2NDC = fp.fY2NDC + 0.005 + (pal.fY2NDC - pal.fY1NDC); + pal.fY1NDC = fp.fY2NDC + 0.005; + } else { + pal.fY1NDC = fp.fY1NDC - 0.05 - (pal.fY2NDC - pal.fY1NDC); + pal.fY2NDC = fp.fY1NDC - 0.05; + } + } + } - if (!this.draw_content || (width <= 0) || (height <= 0)) - return this.removeG(); + // required for z scale setting + // TODO: use weak reference (via pad list of painters and any kind of string) + pal.$main_painter = this; - this.createG(true); + let arg = 'bring_stats_front', pr; + if (postpone_draw) + arg += ';postpone'; + if (can_move && !this.#doing_redraw_palette) + arg += ';can_move'; + if (o.Cjust) + arg += ';cjust'; - if (this.options.Bar) { - return this.drawBars(funcs, height).then(() => { - if (this.options.ErrorKind === 1) - return this.drawNormal(funcs, width, height); + if (!pal_painter) { + // when histogram drawn on sub pad, let draw new axis object on the same pad + pr = TPavePainter.draw(pp, pal, arg).then(_palp => { + pal_painter = _palp; + pal_painter.setSecondaryId(this, found_in_func && !pal.$generated ? `func_${pal.fName}` : undefined); }); + } else { + pal_painter.Enabled = true; + // real drawing will be perform at the end + if (postpone_draw) + return pal_painter; + pr = pal_painter.drawPave(arg); } - if ((this.options.ErrorKind === 3) || (this.options.ErrorKind === 4)) - return this.drawFilledErrors(funcs); + return pr.then(() => { + // mark painter as secondary - not in list of TCanvas primitives + o.Zvert = pal_painter.isPaletteVertical(); - return this.drawNormal(funcs, width, height); - } + // make dummy redraw, palette will be updated only from histogram painter + pal_painter.redraw = () => {}; - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(bin) { - const tips = [], - name = this.getObjectHint(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - histo = this.getHisto(), - x1 = histo.fXaxis.GetBinLowEdge(bin+1), - x2 = histo.fXaxis.GetBinLowEdge(bin+2), - xlbl = this.getAxisBinTip('x', histo.fXaxis, bin); - let cont = histo.getBinContent(bin+1); + let need_redraw = false; - if (name) tips.push(name); + // special code to adjust frame position to actual position of palette + if (can_move && fp && !this.#doing_redraw_palette) { + const pad = pp?.getRootPad(true); - if (this.options.Error || this.options.Mark || this.isTF1()) { - tips.push(`x = ${xlbl}`, `y = ${funcs.axisAsText('y', cont)}`); - if (this.options.Error) { - if (xlbl[0] === '[') tips.push(`error x = ${((x2 - x1) / 2).toPrecision(4)}`); - tips.push(`error y = ${histo.getBinError(bin + 1).toPrecision(4)}`); - } - } else { - tips.push(`bin = ${bin+1}`, `x = ${xlbl}`); - if (histo.$baseh) cont -= histo.$baseh.getBinContent(bin+1); - if (cont === Math.round(cont)) - tips.push(`entries = ${cont}`); - else - tips.push(`entries = ${floatToString(cont, gStyle.fStatFormat)}`); - } + if (o.Zvert) { + if ((pal.fX1NDC > 0.5) && (fp.fX2NDC > pal.fX1NDC)) { + need_redraw = true; + fp.fX2NDC = pal.fX1NDC - 0.01; - return tips; - } + if (fp.fX1NDC > fp.fX2NDC - 0.1) + fp.fX1NDC = Math.max(0, fp.fX2NDC - 0.1); + } else if ((pal.fX2NDC < 0.5) && (fp.fX1NDC < pal.fX2NDC)) { + need_redraw = true; + fp.fX1NDC = pal.fX2NDC + 0.05; + if (fp.fX2NDC < fp.fX1NDC + 0.1) + fp.fX2NDC = Math.min(1, fp.fX1NDC + 0.1); + } + if (need_redraw && pad) { + pad.fLeftMargin = fp.fX1NDC; + pad.fRightMargin = 1 - fp.fX2NDC; + } + } else { + if ((pal.fY1NDC > 0.5) && (fp.fY2NDC > pal.fY1NDC)) { + need_redraw = true; + fp.fY2NDC = pal.fY1NDC - 0.01; + if (fp.fY1NDC > fp.fY2NDC - 0.1) + fp.fY1NDC = Math.max(0, fp.fXYNDC - 0.1); + } else if ((pal.fY2NDC < 0.5) && (fp.fY1NDC < pal.fY2NDC)) { + need_redraw = true; + fp.fY1NDC = pal.fY2NDC + 0.05; + if (fp.fXYNDC < fp.fY1NDC + 0.1) + fp.fY2NDC = Math.min(1, fp.fY1NDC + 0.1); + } + if (need_redraw && pad) { + pad.fTopMargin = fp.fY1NDC; + pad.fBottomMargin = 1 - fp.fY2NDC; + } + } + } - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - if (!pnt || !this.draw_content || !this.draw_g || this.options.Mode3D) { - this.draw_g?.selectChild('.tooltip_bin').remove(); - return null; - } + if (!need_redraw) + return pal_painter; - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - histo = this.getHisto(), - left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 2); - let width = pmain.getFrameWidth(), - height = pmain.getFrameHeight(), - findbin = null, show_rect, - grx1, grx2, gry1, gry2, gapx = 2, - l = left, r = right, pnt_x = pnt.x, pnt_y = pnt.y; + this.#doing_redraw_palette = true; - const GetBinGrX = i => { - const xx = histo.fXaxis.GetBinLowEdge(i+1); - return (funcs.logx && (xx <= 0)) ? null : funcs.grx(xx); - }, GetBinGrY = i => { - const yy = histo.getBinContent(i + 1); - if (funcs.logy && (yy < funcs.scale_ymin)) - return funcs.swap_xy ? -1000 : 10*height; - return Math.round(funcs.gry(yy)); - }; + fp.redraw(); - if (funcs.swap_xy) - [pnt_x, pnt_y, width, height] = [pnt_y, pnt_x, height, width]; + const pr2 = !postpone_draw ? this.redraw() : Promise.resolve(true); + return pr2.then(() => { + this.#doing_redraw_palette = undefined; + return pal_painter; + }); + }); + } - const descent_order = funcs.swap_xy !== pmain.x_handle.reverse; + /** @summary Toggle color z palette drawing */ + toggleColz() { + const o = this.getOptions(); - while (l < r-1) { - const m = Math.round((l+r)*0.5), xx = GetBinGrX(m); - if ((xx === null) || (xx < pnt_x - 0.5)) - if (descent_order) r = m; else l = m; - else if (xx > pnt_x + 0.5) - if (descent_order) l = m; else r = m; - else { l++; r--; } + if (o.canHavePalette()) { + o.Zscale = !o.Zscale; + return this.drawColorPalette(o.Zscale, false, true) + .then(() => this.processOnlineChange('drawopt')); } + } - findbin = r = l; - grx1 = GetBinGrX(findbin); - - if (descent_order) { - while ((l > left) && (GetBinGrX(l-1) < grx1 + 2)) --l; - while ((r < right) && (GetBinGrX(r+1) > grx1 - 2)) ++r; - } else { - while ((l > left) && (GetBinGrX(l-1) > grx1 - 2)) --l; - while ((r < right) && (GetBinGrX(r+1) < grx1 + 2)) ++r; - } + /** @summary Toggle 3D drawing mode */ + toggleMode3D() { + const o = this.getOptions(); - if (l < r) { - // many points can be assigned with the same cursor position - // first try point around mouse y - let best = height; - for (let m = l; m <= r; m++) { - const dist = Math.abs(GetBinGrY(m) - pnt_y); - if (dist < best) { best = dist; findbin = m; } - } + o.Mode3D = !o.Mode3D; - // if best distance still too far from mouse position, just take from between - if (best > height/10) - findbin = Math.round(l + (r-l) / height * pnt_y); + if (o.Mode3D) { + if (!o.Surf && !o.Lego && !o.Error) { + if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) + o.Lego = o.Scat ? 13 : 14; + else + o.Lego = o.Scat ? 1 : 12; - grx1 = GetBinGrX(findbin); + o.Zero = false; // do not show zeros by default + } } - grx1 = Math.round(grx1); - grx2 = Math.round(GetBinGrX(findbin+1)); + this.copyOptionsToOthers(); + return this.interactiveRedraw('pad', 'drawopt'); + } - if (this.options.Bar) { - const w = grx2 - grx1; - grx1 += Math.round(histo.fBarOffset / 1000 * w); - grx2 = grx1 + Math.round(histo.fBarWidth / 1000 * w); + /** @summary Get graphics conversion functions for this histogram */ + getHistGrFuncs(rounding = true) { + let funcs; + if (this.isUseFrame()) { + const o = this.getOptions(); + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); + if (funcs) + return funcs; } - if (grx1 > grx2) - [grx1, grx2] = [grx2, grx1]; + funcs = this.getAxisToSvgFunc(false, rounding, false) || { x: v => v, y: v => v }; - const midx = Math.round((grx1 + grx2) / 2), - midy = gry1 = gry2 = GetBinGrY(findbin); + funcs.$painter = this; + funcs.grx = funcs.x; + funcs.gry = funcs.y; + funcs.logx = funcs.pad?.fLogx; + funcs.logy = funcs.pad?.fLogy; + funcs.swap_xy = function() { return this.fp?.swap_xy() ?? false; }; + funcs.getFrameWidth = function() { return this.$painter.getPadPainter().getPadWidth(); }; + funcs.getFrameHeight = function() { return this.$painter.getPadPainter().getPadHeight(); }; + funcs.isAxisZoomed = function() { return false; }; + funcs.revertAxis = function(name, v) { return this.$painter.svgToAxis(name, v); }; + funcs.axisAsText = function(_name, v) { return v.toString(); }; + return funcs; + } - if (this.options.Bar) { - show_rect = true; + /** @summary Prepare handle for color draw */ + prepareDraw(args) { + if (!args) + args = { rounding: true, extra: 0, middle: 0 }; - gapx = 0; + if (args.extra === undefined) + args.extra = 0; + if (args.middle === undefined) + args.middle = 0; + if (args.pixel_density) + args.rounding = true; - gry1 = Math.round(funcs.gry(((this.options.BaseLine !== false) && (this.options.BaseLine > funcs.scale_ymin)) ? this.options.BaseLine : funcs.scale_ymin)); + const histo = this.getHisto(), + o = this.getOptions(), + xaxis = histo.fXaxis, + yaxis = histo.fYaxis, + funcs = this.getHistGrFuncs(args.rounding), + hdim = this.getDimension(), + res = { + i1: args.nozoom ? 0 : this.getSelectIndex('x', 'left', 0 - args.extra), + i2: args.nozoom ? this.nbinsx : this.getSelectIndex('x', 'right', 1 + args.extra), + j1: (hdim === 1) ? 0 : (args.nozoom ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra)), + j2: (hdim === 1) ? 1 : (args.nozoom ? this.nbinsy : this.getSelectIndex('y', 'right', 1 + args.extra)), + min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1, + width: funcs?.getFrameWidth() ?? 600, + height: funcs?.getFrameHeight() ?? 400 + }; - if (gry1 > gry2) - [gry1, gry2] = [gry2, gry1]; + if (args.use3d && !funcs.size_x3d || !funcs.size_y3d) + args.use3d = false; - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if ((this.options.Error && (this.options.Hist !== true)) || this.options.Mark || this.options.Line || this.options.Curve) { - show_rect = !this.isTF1(); + if (args.cutg) { + // if using cutg - define rectangular region + let i1 = res.i2, i2 = res.i1, j1 = res.j2, j2 = res.j1; + for (let ii = res.i1; ii < res.i2; ++ii) { + for (let jj = res.j1; jj < res.j2; ++jj) { + if (args.cutg.IsInside(xaxis.GetBinCoord(ii + args.middle), yaxis.GetBinCoord(jj + args.middle))) { + i1 = Math.min(i1, ii); + i2 = Math.max(i2, ii + 1); + j1 = Math.min(j1, jj); + j2 = Math.max(j2, jj + 1); + } + } + } - let msize = 3; - if (this.markeratt) msize = Math.max(msize, this.markeratt.getFullSize()); + Object.assign(res, { i1, i2, j1, j2 }); + } - if (this.options.Error) { - const cont = histo.getBinContent(findbin+1), - binerr = histo.getBinError(findbin+1); + let i, j, x, y, binz, binarea; - gry1 = Math.round(funcs.gry(cont + binerr)); // up - gry2 = Math.round(funcs.gry(cont - binerr)); // down + res.grx = res.i1 < 0 ? {} : new Float32Array(res.i2 + 1); + res.gry = res.j1 < 0 ? {} : new Float32Array(res.j2 + 1); - if ((cont === 0) && this.isTProfile()) findbin = null; + if ((typeof histo.fBarOffset === 'number') && (typeof histo.fBarWidth === 'number') && (histo.fBarOffset || (histo.fBarWidth !== 1000))) { + if (histo.fBarOffset <= 1000) + res.xbar1 = res.ybar1 = 0.001 * histo.fBarOffset; + else if (histo.fBarOffset <= 3000) + res.xbar1 = 0.001 * (histo.fBarOffset - 2000); + else if (histo.fBarOffset <= 5000) + res.ybar1 = 0.001 * (histo.fBarOffset - 4000); - const dx = (grx2-grx1)*this.options.errorX; - grx1 = Math.round(midx - dx); - grx2 = Math.round(midx + dx); - } + if (histo.fBarWidth <= 1000) { + res.xbar2 = Math.min(1, res.xbar1 + 0.001 * histo.fBarWidth); + res.ybar2 = Math.min(1, res.ybar1 + 0.001 * histo.fBarWidth); + } else if (histo.fBarWidth <= 3000) + res.xbar2 = Math.min(1, res.xbar1 + 0.001 * (histo.fBarWidth - 2000)); + else if (histo.fBarWidth <= 5000) + res.ybar2 = Math.min(1, res.ybar1 + 0.001 * (histo.fBarWidth - 4000)); + } - // show at least 6 pixels as tooltip rect - if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; } + if (args.original) { + res.original = true; + res.origx = res.i1 < 0 ? {} : new Float32Array(res.i2 + 1); + res.origy = res.j1 < 0 ? {} : new Float32Array(res.j2 + 1); + } - gry1 = Math.min(gry1, midy - msize); - gry2 = Math.max(gry2, midy + msize); - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else { - // if histogram alone, use old-style with rects - // if there are too many points at pixel, use circle - show_rect = (pnt.nproc === 1) && (right-left < width); - - if (show_rect) { - gry2 = height; + // calculate graphical coordinates in advance + for (i = res.i1; i <= res.i2; ++i) { + x = xaxis.GetBinCoord(i + args.middle); + if (funcs.logx && (x <= 0)) { + res.i1 = i + 1; + continue; + } + if (res.origx) + res.origx[i] = x; + res.grx[i] = funcs.grx(x); + if (args.rounding) + res.grx[i] = Math.round(res.grx[i]); - if (!this.fillatt.empty()) { - gry2 = Math.min(height, Math.max(0, Math.round(funcs.gry(0)))); - if (gry2 < gry1) - [gry1, gry2] = [gry2, gry1]; + if (args.use3d) { + if (res.grx[i] < -funcs.size_x3d) { + res.grx[i] = -funcs.size_x3d; + if (o.RevX) + res.i2 = i; + else + res.i1 = i; + } + if (res.grx[i] > funcs.size_x3d) { + res.grx[i] = funcs.size_x3d; + if (o.RevX) + res.i1 = i; + else + res.i2 = i; } - - // for mouse events pointer should be between y1 and y2 - if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) findbin = null; } } - if (findbin !== null) { - // if bin on boundary found, check that x position is ok - if ((findbin === left) && (grx1 > pnt_x + gapx)) - findbin = null; - else if ((findbin === right-1) && (grx2 < pnt_x - gapx)) - findbin = null; - else if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) - findbin = null; // if bars option used check that bar is not match - else if (!this.options.Zero && (histo.getBinContent(findbin+1) === 0)) - findbin = null; // exclude empty bin if empty bins suppressed + if (hdim === 1) { + res.gry[0] = funcs.gry(0); + res.gry[1] = funcs.gry(1); + } else { + for (j = res.j1; j <= res.j2; ++j) { + y = yaxis.GetBinCoord(j + args.middle); + if (funcs.logy && (y <= 0)) { + res.j1 = j + 1; + continue; + } + if (res.origy) + res.origy[j] = y; + res.gry[j] = funcs.gry(y); + if (args.rounding) + res.gry[j] = Math.round(res.gry[j]); + + if (args.use3d) { + if (res.gry[j] < -funcs.size_y3d) { + res.gry[j] = -funcs.size_y3d; + if (o.RevY) + res.j2 = j; + else + res.j1 = j; + } + if (res.gry[j] > funcs.size_y3d) { + res.gry[j] = funcs.size_y3d; + if (o.RevY) + res.j1 = j; + else + res.j2 = j; + } + } + } } - let ttrect = this.draw_g.selectChild('.tooltip_bin'); + // find min/max values in selected range + let is_first = true; + this.minposbin = 0; - if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) { - ttrect.remove(); - return null; + for (i = res.i1; i < res.i2; ++i) { + for (j = res.j1; j < res.j2; ++j) { + binz = histo.getBinContent(i + 1, j + 1); + res.sumz += binz; + if (args.pixel_density) { + binarea = (res.grx[i + 1] - res.grx[i]) * (res.gry[j] - res.gry[j + 1]); + if (binarea <= 0) + continue; + res.max = Math.max(res.max, binz); + if ((binz > 0) && ((binz < res.min) || (res.min === 0))) + res.min = binz; + binz /= binarea; + } + if (is_first) { + this.maxbin = this.minbin = binz; + is_first = false; + } else { + this.maxbin = Math.max(this.maxbin, binz); + this.minbin = Math.min(this.minbin, binz); + } + if ((binz > 0) && ((this.minposbin === 0) || (binz < this.minposbin))) + this.minposbin = binz; + } } - const res = { name: this.getObjectName(), title: histo.fTitle, - x: midx, y: midy, exact: true, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(findbin) }; + if (is_first) + this.maxbin = this.minbin = 0; - if (pnt.disabled) { - // case when tooltip should not highlight bin - ttrect.remove(); - res.changed = true; - } else if (show_rect) { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } + // force recalculation of z levels + this.#contour = undefined; - res.changed = ttrect.property('current_bin') !== findbin; + if (args.zrange) + Object.assign(res, this.#getContourRanges(this.getMainPainter(), this.getFramePainter())); - if (res.changed) { - ttrect.attr('x', funcs.swap_xy ? gry1 : grx1) - .attr('width', funcs.swap_xy ? gry2-gry1 : grx2-grx1) - .attr('y', funcs.swap_xy ? grx1 : gry1) - .attr('height', funcs.swap_xy ? grx2-grx1 : gry2-gry1) - .style('opacity', '0.3') - .property('current_bin', findbin); - } + return res; + } - res.exact = (Math.abs(midy - pnt_y) <= 5) || ((pnt_y >= gry1) && (pnt_y <= gry2)); + /** @summary Get tip text for axis bin */ + getAxisBinTip(name, axis, bin) { + const funcs = this.getHistGrFuncs(), + handle = funcs[`${name}_handle`], + x1 = axis.GetBinLowEdge(bin + 1); - res.menu = res.exact; // one could show context menu when histogram is selected - // distance to middle point, use to decide which menu to activate - res.menu_dist = Math.sqrt((midx-pnt_x)**2 + (midy-pnt_y)**2); - } else { - const radius = this.lineatt.width + 3; + if (handle.kind === kAxisLabels) + return funcs.axisAsText(name, x1); - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .attr('r', radius) - .call(this.lineatt.func) - .call(this.fillatt.func); - } + const x2 = axis.GetBinLowEdge(bin + 2); - res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius); + if ((handle.kind === kAxisTime) || this.isTF1()) + return funcs.axisAsText(name, (x1 + x2) / 2); - res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram - res.menu_dist = Math.sqrt((midx-pnt.x)**2 + (midy-pnt.y)**2); + return `[${funcs.axisAsText(name, x1)}, ${funcs.axisAsText(name, x2)})`; + } - res.changed = ttrect.property('current_bin') !== findbin; + /** @summary Internal method to extract up/down errors for the bin + * @private */ + getBinErrors(histo, bin, content) { + const err = histo.getBinError(bin), + res = { low: err, up: err }, + kind = this.getOptions().Poisson || histo.fBinStatErrOpt; - if (res.changed) { - ttrect.attr('cx', midx) - .attr('cy', midy) - .property('current_bin', findbin); - } - } + if (!kind || (histo.fSumw2.fN && histo.fTsumw !== histo.fTsumw2) || (content < 0)) + return res; - if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: findbin, cont: histo.getBinContent(findbin+1), - grx: midx, gry: midy }; - } + const alpha = (kind === kPoisson2) ? 0.05 : 1 - 0.682689492, + n = Math.round(content); + res.poisson = true; // indicate poisson error + res.low = (n === 0) ? 0 : content - gamma_quantile(alpha / 2, n, 1); + res.up = gamma_quantile_c(alpha / 2, n + 1, 1) - content; return res; } - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - menu.add('Auto zoom-in', () => this.autoZoom()); + /** @summary Check assign as main painter + * @private */ + _checkAssign() { + const has_main = this.getPadPainter()?.getMainPainter(); + if (this.getOptions().Same) + this.#ignore_frame = !has_main; + else if (!has_main) + this.setAsMainPainter(); + } - const opts = this.getSupportedDrawOptions(); + /** @summary Return true when drawn normally on the frame + * @private */ + isUseFrame() { return !this.#ignore_frame; } - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); + /** @summary generic draw function for histograms + * @private */ + static async _drawHist(painter, opt) { + const need_frame = !isStr(opt) || (opt.toLowerCase().indexOf('same') < 0); + return ensureTCanvas(painter, need_frame).then(() => { + painter.decodeOptions(opt); - this.decodeOptions(arg); + painter._checkAssign(); - if (this.options.need_fillcol && this.fillatt?.empty()) - this.fillatt.change(5, 1001); + if (painter.isTH2Poly()) { + if (painter.options.Mode3D) + painter.options.Lego = 12; // lego always 12 + } - // redraw all objects in pad, inform dependent objects - this.interactiveRedraw('pad', 'drawopt'); - }); + painter.checkPadRange(); - if (!this.snapid && !this.isTProfile() && !this.isTF1()) - menu.addRebinMenu(sz => this.rebinHist(sz)); - } + painter.scanContent(); - /** @summary Rebin histogram, used via context menu */ - rebinHist(sz) { - const histo = this.getHisto(), - xaxis = histo.fXaxis, - nbins = Math.floor(xaxis.fNbins/ sz); - if (nbins < 2) return; + painter.createStat(); // only when required - const arr = new Array(nbins+2), - xbins = (xaxis.fXbins.length > 0) ? new Array(nbins) : null; + return painter.callDrawFunc(); + }).then(() => { + return painter.drawFunctions(); + }).then(() => { + return painter.drawHistTitle(); + }).then(() => { + if (!painter.Mode3D && painter.options.AutoZoom) + return painter.autoZoom(); + }).then(() => { + if (painter.options.Project && !painter.mode3d && isFunc(painter.toggleProjection)) + return painter.toggleProjection(painter.options.Project); + }).then(() => { + painter.fillToolbar(); + return painter; + }); + } - arr[0] = histo.fArray[0]; - let indx = 1; +} // class THistPainter - for (let i = 1; i <= nbins; ++i) { - if (xbins) xbins[i-1] = xaxis.fXbins[indx-1]; - let sum = 0; - for (let k = 0; k < sz; ++k) - sum += histo.fArray[indx++]; - arr[i] = sum; - } +/** @summary Build histogram contour lines + * @private */ +function buildHist2dContour(histo, handle, levels, palette, contour_func) { + const kMAXCONTOUR = 2004, + kMAXCOUNT = 2000, + // arguments used in the PaintContourLine + xarr = new Float32Array(2 * kMAXCONTOUR), + yarr = new Float32Array(2 * kMAXCONTOUR), + itarr = new Int32Array(2 * kMAXCONTOUR), + nlevels = levels.length, + first_level = levels[0], last_level = levels[nlevels - 1], + polys = [], + x = [0, 0, 0, 0], y = [0, 0, 0, 0], zc = [0, 0, 0, 0], ir = [0, 0, 0, 0], + arrx = handle.grx, + arry = handle.gry; - if (xbins) { - if (indx <= xaxis.fXbins.length) - xaxis.fXmax = xaxis.fXbins[indx-1]; - xaxis.fXbins = xbins; - } else - xaxis.fXmax = xaxis.fXmin + (xaxis.fXmax - xaxis.fXmin) / xaxis.fNbins * nbins * sz; + let lj = 0; + const LinearSearch = zvalue => { + if (zvalue >= last_level) + return nlevels - 1; - xaxis.fNbins = nbins; + for (let kk = 0; kk < nlevels; ++kk) { + if (zvalue < levels[kk]) + return kk - 1; + } + return nlevels - 1; + }, BinarySearch = zvalue => { + if (zvalue < first_level) + return -1; + if (zvalue >= last_level) + return nlevels - 1; - let overflow = 0; - while (indx < histo.fArray.length) - overflow += histo.fArray[indx++]; - arr[nbins+1] = overflow; + let l = 0, r = nlevels - 1, m; + while (r - l > 1) { + m = Math.round((r + l) / 2); + if (zvalue < levels[m]) + r = m; + else + l = m; + } + return l; + }, LevelSearch = nlevels < 10 ? LinearSearch : BinarySearch; + /* eslint-disable-next-line one-var */ + const PaintContourLine = (elev1, icont1, x1, y1, elev2, icont2, x2, y2) => { + /* Double_t *xarr, Double_t *yarr, Int_t *itarr, Double_t *levels */ + const vert = (x1 === x2), + tlen = vert ? (y2 - y1) : (x2 - x1), + tdif = elev2 - elev1; + let n = icont1 + 1, ii = lj - 1, icount = 0, + xlen, pdif, diff, elev; + const maxii = ii + kMAXCONTOUR / 2 - 3; - histo.fArray = arr; - histo.fSumw2 = []; + while (n <= icont2 && ii <= maxii) { + // elev = fH->GetContourLevel(n); + elev = levels[n]; + diff = elev - elev1; + pdif = diff / tdif; + xlen = tlen * pdif; + if (vert) { + xarr[ii] = x1; + yarr[ii] = y1 + xlen; + } else { + xarr[ii] = x1 + xlen; + yarr[ii] = y1; + } + itarr[ii] = n; + icount++; + ii += 2; + n++; + } + return icount; + }; - this.scanContent(); + let ipoly, poly, npmax = 0, + i, j, k, m, n, ljfill, count, + xsave, ysave, itars, ix, jx; - this.interactiveRedraw('pad'); - } + for (j = handle.j1; j < handle.j2 - 1; ++j) { + y[1] = y[0] = (arry[j] + arry[j + 1]) / 2; + y[3] = y[2] = (arry[j + 1] + arry[j + 2]) / 2; - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - let left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 1); - const dist = right - left, - histo = this.getHisto(); + for (i = handle.i1; i < handle.i2 - 1; ++i) { + zc[0] = histo.getBinContent(i + 1, j + 1); + zc[1] = histo.getBinContent(i + 2, j + 1); + zc[2] = histo.getBinContent(i + 2, j + 2); + zc[3] = histo.getBinContent(i + 1, j + 2); - if ((dist === 0) || !histo) return; + for (k = 0; k < 4; k++) + ir[k] = LevelSearch(zc[k]); - // first find minimum - let min = histo.getBinContent(left + 1); - for (let indx = left; indx < right; ++indx) - min = Math.min(min, histo.getBinContent(indx+1)); - if (min > 0) return; // if all points positive, no chance for autoscale + if ((ir[0] !== ir[1]) || (ir[1] !== ir[2]) || (ir[2] !== ir[3]) || (ir[3] !== ir[0])) { + x[3] = x[0] = (arrx[i] + arrx[i + 1]) / 2; + x[2] = x[1] = (arrx[i + 1] + arrx[i + 2]) / 2; - while ((left < right) && (histo.getBinContent(left+1) <= min)) ++left; - while ((left < right) && (histo.getBinContent(right) <= min)) --right; + n = zc[0] <= zc[1] ? 0 : 1; + m = zc[2] <= zc[3] ? 2 : 3; + if (zc[n] > zc[m]) + n = m; + n++; + lj = 1; + for (ix = 1; ix <= 4; ix++) { + m = n % 4 + 1; + ljfill = PaintContourLine(zc[n - 1], ir[n - 1], x[n - 1], y[n - 1], zc[m - 1], ir[m - 1], x[m - 1], y[m - 1]); + lj += 2 * ljfill; + n = m; + } - // if singular bin - if ((left === right-1) && (left > 2) && (right < this.nbinsx-2)) { - --left; ++right; - } + n = zc[0] <= zc[1] ? 0 : 1; + m = zc[2] <= zc[3] ? 2 : 3; + if (zc[n] > zc[m]) + n = m; + n++; + lj = 2; + for (ix = 1; ix <= 4; ix++) { + m = (n === 1) ? 4 : n - 1; + ljfill = PaintContourLine(zc[n - 1], ir[n - 1], x[n - 1], y[n - 1], zc[m - 1], ir[m - 1], x[m - 1], y[m - 1]); + lj += 2 * ljfill; + n = m; + } + // Re-order endpoints - if ((right - left < dist) && (left < right)) - return this.getFramePainter().zoom(histo.fXaxis.GetBinLowEdge(left+1), histo.fXaxis.GetBinLowEdge(right+1)); - } + count = 0; + for (ix = 1; ix <= lj - 5; ix += 2) { + // count = 0; + while (itarr[ix - 1] !== itarr[ix]) { + xsave = xarr[ix]; + ysave = yarr[ix]; + itars = itarr[ix]; + for (jx = ix; jx <= lj - 5; jx += 2) { + xarr[jx] = xarr[jx + 2]; + yarr[jx] = yarr[jx + 2]; + itarr[jx] = itarr[jx + 2]; + } + xarr[lj - 3] = xsave; + yarr[lj - 3] = ysave; + itarr[lj - 3] = itars; + if (count > kMAXCOUNT) + break; + count++; + } + } - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - const histo = this.getHisto(); + if (count > 100) + continue; - if ((axis === 'x') && histo && (histo.fXaxis.FindBin(max, 0.5) - histo.fXaxis.FindBin(min, 0) > 1)) return true; + for (ix = 1; ix <= lj - 2; ix += 2) { + ipoly = itarr[ix - 1]; - if ((axis === 'y') && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true; + if ((ipoly >= 0) && (ipoly < levels.length)) { + poly = polys[ipoly]; + if (!poly) + poly = polys[ipoly] = createTPolyLine(kMAXCONTOUR * 4, true); + + const np = poly.fLastPoint; + if (np < poly.fN - 2) { + poly.fX[np + 1] = Math.round(xarr[ix - 1]); + poly.fY[np + 1] = Math.round(yarr[ix - 1]); + poly.fX[np + 2] = Math.round(xarr[ix]); + poly.fY[np + 2] = Math.round(yarr[ix]); + poly.fLastPoint = np + 2; + npmax = Math.max(npmax, poly.fLastPoint + 1); + } + } + } + } // end of if (ir[0] + } // end of j + } // end of i - return false; + const polysort = new Int32Array(levels.length); + let first = 0; + // find first positive contour + for (ipoly = 0; ipoly < levels.length; ipoly++) { + if (levels[ipoly] >= 0) { + first = ipoly; + break; + } } - /** @summary Call drawing function depending from 3D mode */ - async callDrawFunc(reason) { - const main = this.getMainPainter(), - fp = this.getFramePainter(); + // store negative contours from 0 to minimum, then all positive contours + k = 0; + for (ipoly = first - 1; ipoly >= 0; ipoly--) + polysort[k++] = ipoly; + for (ipoly = first; ipoly < levels.length; ipoly++) + polysort[k++] = ipoly; - if ((main !== this) && fp && (fp.mode3d !== this.options.Mode3D)) - this.copyOptionsFrom(main); + const xp = new Float32Array(2 * npmax), + yp = new Float32Array(2 * npmax), + has_func = isFunc(palette.calcColorIndex); // rcanvas for v7 - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } + for (k = 0; k < levels.length; ++k) { + ipoly = polysort[k]; + poly = polys[ipoly]; + if (!poly) + continue; - /** @summary Performs 2D drawing of histogram - * @return {Promise} when ready */ - async draw2D(/* reason */) { - this.clear3DScene(); + const colindx = has_func ? palette.calcColorIndex(ipoly, levels.length) : ipoly, + xx = poly.fX, yy = poly.fY, np2 = poly.fLastPoint + 1, + xmin = 0, ymin = 0; + let istart = 0, iminus, iplus, nadd; - this.scanContent(true); + while (true) { + iminus = npmax; + iplus = iminus + 1; + xp[iminus] = xx[istart]; + yp[iminus] = yy[istart]; + xp[iplus] = xx[istart + 1]; + yp[iplus] = yy[istart + 1]; + xx[istart] = xx[istart + 1] = xmin; + yy[istart] = yy[istart + 1] = ymin; + while (true) { + nadd = 0; + for (i = 2; i < np2; i += 2) { + if ((iplus < 2 * npmax - 1) && (xx[i] === xp[iplus]) && (yy[i] === yp[iplus])) { + iplus++; + xp[iplus] = xx[i + 1]; + yp[iplus] = yy[i + 1]; + xx[i] = xx[i + 1] = xmin; + yy[i] = yy[i + 1] = ymin; + nadd++; + } + if ((iminus > 0) && (xx[i + 1] === xp[iminus]) && (yy[i + 1] === yp[iminus])) { + iminus--; + xp[iminus] = xx[i]; + yp[iminus] = yy[i]; + xx[i] = xx[i + 1] = xmin; + yy[i] = yy[i + 1] = ymin; + nadd++; + } + } + if (nadd === 0) + break; + } - const pr = this.isMainPainter() ? this.drawColorPalette(false) : Promise.resolve(true); + if ((iminus + 1 < iplus) && (iminus >= 0)) + contour_func(colindx, xp, yp, iminus, iplus, ipoly); - return pr.then(() => this.drawAxes()) - .then(() => this.draw1DBins()) - .then(() => this.updateFunctions()) - .then(() => this.updateHistTitle()) - .then(() => { - this.updateStatWebCanvas(); - return this.addInteractivity(); - }); - } + istart = 0; + for (i = 2; i < np2; i += 2) { + if (xx[i] !== xmin && yy[i] !== ymin) { + istart = i; + break; + } + } - /** @summary Should performs 3D drawing of histogram - * @desc Disable in 2D case, just draw with default options - * @return {Promise} when ready */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/TH1Painter.mjs'); - return this.draw2D(reason); + if (istart === 0) + break; + } } +} - /** @summary Redraw histogram */ - redraw(reason) { - return this.callDrawFunc(reason); - } +/** @summary Handle 3D triangles with color levels */ - /** @summary draw TH1 object */ - static async draw(dom, histo, opt) { - return THistPainter._drawHist(new TH1Painter(dom, histo), opt); - } +class Triangles3DHandler { -}; // class TH1Painter + constructor(ilevels, grz, grz_min, grz_max, dolines, donormals, dogrid) { + let levels = [grz_min, grz_max]; // just cut top/bottom parts -/** @summary Draw 1-D histogram in 3D - * @private */ + if (ilevels) { + // recalculate levels into graphical coordinates + levels = new Float32Array(ilevels.length); + for (let ll = 0; ll < ilevels.length; ++ll) + levels[ll] = grz(ilevels[ll]); + } -class TH1Painter extends TH1Painter$2 { + Object.assign(this, { grz_min, grz_max, dolines, donormals, dogrid }); - /** @summary draw TH1 object in 3D mode */ - draw3D(reason) { - this.mode3d = true; + this.loop = 0; - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(), // is main histogram - histo = this.getHisto(), - zmult = 1 + 2*gStyle.fHistTopMargin; - let pr = Promise.resolve(true); + const nfaces = [], posbuf = [], posbufindx = [], // buffers for faces + pntbuf = new Float32Array(6 * 3), // maximal 6 points + gridpnts = new Float32Array(2 * 3), + levels_eps = (levels.at(-1) - levels.at(0)) / levels.length / 1e2; + let nsegments = 0, lpos = null, lindx = 0, // buffer for lines + ngridsegments = 0, grid = null, gindx = 0, // buffer for grid lines segments + normindx = [], // buffer to remember place of vertex for each bin + pntindx = 0, lastpart = 0, gridcnt = 0; - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - } else { - this.createHistDrawAttributes(true); + function checkSide(z, level1, level2, eps) { + return (z < level1 - eps) ? -1 : (z > level2 + eps ? 1 : 0); + } - this.scanContent(true); // may be required for axis drawings + this.createNormIndex = function(handle) { + // for each bin maximal 8 points reserved + if (handle.donormals) + normindx = new Int32Array((handle.i2 - handle.i1) * (handle.j2 - handle.j1) * 8).fill(-1); + }; - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { use_y_for_z: true, zmult, zoom: settings.Zooming, ndim: 1, - draw: (this.options.Axis !== -1), drawany: this.options.isCartesian() }); - }); + this.createBuffers = function() { + if (!this.loop) + return; + + for (let lvl = 1; lvl < levels.length; ++lvl) { + if (nfaces[lvl]) { + posbuf[lvl] = new Float32Array(nfaces[lvl] * 9); + posbufindx[lvl] = 0; + } } + if (this.dolines && (nsegments > 0)) + lpos = new Float32Array(nsegments * 6); + if (this.dogrid && (ngridsegments > 0)) + grid = new Float32Array(ngridsegments * 6); + }; - if (main.mode3d) { - pr = pr.then(() => { - drawBinsLego(this); - main.render3D(); - this.updateStatWebCanvas(); - main.addKeysHandler(); - }); + this.addLineSegment = function(x1, y1, z1, x2, y2, z2) { + if (!this.dolines) + return; + const side1 = checkSide(z1, this.grz_min, this.grz_max, 0), + side2 = checkSide(z2, this.grz_min, this.grz_max, 0); + if ((side1 === side2) && side1) + return; + if (!this.loop) + return ++nsegments; + + if (side1) { + const diff = z2 - z1; + z1 = (side1 < 0) ? this.grz_min : this.grz_max; + x1 = x2 - (x2 - x1) / diff * (z2 - z1); + y1 = y2 - (y2 - y1) / diff * (z2 - z1); + } + if (side2) { + const diff = z1 - z2; + z2 = (side2 < 0) ? this.grz_min : this.grz_max; + x2 = x1 - (x1 - x2) / diff * (z1 - z2); + y2 = y1 - (y1 - y2) / diff * (z1 - z2); } - } - if (is_main) - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && ((this.options.Lego === 12) || (this.options.Lego === 14)))); + lpos[lindx] = x1; + lpos[lindx + 1] = y1; + lpos[lindx + 2] = z1; + lindx += 3; + lpos[lindx] = x2; + lpos[lindx + 1] = y2; + lpos[lindx + 2] = z2; + lindx += 3; + }; - return pr.then(() => this.updateFunctions()) - .then(() => this.updateHistTitle()) - .then(() => this); - } + function addCrossingPoint(xx1, yy1, zz1, xx2, yy2, zz2, crossz, with_grid) { + if (pntindx >= pntbuf.length) + console.log('more than 6 points???'); - /** @summary draw TH1 object */ - static async draw(dom, histo, opt) { - return THistPainter._drawHist(new TH1Painter(dom, histo), opt); - } + const part = (crossz - zz1) / (zz2 - zz1); + let shift = 3; + if (lastpart && (Math.abs(part) < Math.abs(lastpart))) { + // while second crossing point closer than first to original, move it in memory + pntbuf[pntindx] = pntbuf[pntindx - 3]; + pntbuf[pntindx + 1] = pntbuf[pntindx - 2]; + pntbuf[pntindx + 2] = pntbuf[pntindx - 1]; + pntindx -= 3; + shift = 6; + } -} // class TH1Painter + pntbuf[pntindx] = xx1 + part * (xx2 - xx1); + pntbuf[pntindx + 1] = yy1 + part * (yy2 - yy1); + pntbuf[pntindx + 2] = crossz; -var TH1Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TH1Painter: TH1Painter -}); + if (with_grid && grid) { + gridpnts[gridcnt] = pntbuf[pntindx]; + gridpnts[gridcnt + 1] = pntbuf[pntindx + 1]; + gridpnts[gridcnt + 2] = pntbuf[pntindx + 2]; + gridcnt += 3; + } -/** @summary Draw TH2Poly histogram as lego - * @private */ -function drawTH2PolyLego(painter) { - const histo = painter.getHisto(), - pmain = painter.getFramePainter(), - axis_zmin = pmain.z_handle.getScaleMin(), - axis_zmax = pmain.z_handle.getScaleMax(), - len = histo.fBins.arr.length, - z0 = pmain.grz(axis_zmin); - let colindx, bin, i, z1; + pntindx += shift; + lastpart = part; + } - // use global coordinates - painter.maxbin = painter.gmaxbin; - painter.minbin = painter.gminbin; - painter.minposbin = painter.gminposbin; + function rememberVertex(indx, handle, ii, jj) { + const bin = ((ii - handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; - const cntr = painter.getContour(true), palette = painter.getHistPalette(); + if (normindx[bin] >= 0) + return console.error('More than 8 vertexes for the bin'); - for (i = 0; i < len; ++i) { - bin = histo.fBins.arr[i]; - if (bin.fContent < axis_zmin) continue; + const pos = bin + 8 + normindx[bin]; // position where write index + normindx[bin]--; + normindx[pos] = indx; // at this moment index can be overwritten, means all 8 position are there + } - colindx = cntr.getPaletteIndex(palette, bin.fContent); - if (colindx === null) continue; + this.addMainTriangle = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, is_first, handle, i, j) { + for (let lvl = 1; lvl < levels.length; ++lvl) { + let side1 = checkSide(z1, levels[lvl - 1], levels[lvl], levels_eps), + side2 = checkSide(z2, levels[lvl - 1], levels[lvl], levels_eps), + side3 = checkSide(z3, levels[lvl - 1], levels[lvl], levels_eps), + side_sum = side1 + side2 + side3; - // check if bin outside visible range - if ((bin.fXmin > pmain.scale_xmax) || (bin.fXmax < pmain.scale_xmin) || - (bin.fYmin > pmain.scale_ymax) || (bin.fYmax < pmain.scale_ymin)) continue; + // always show top segments + if ((lvl > 1) && (lvl === levels.length - 1) && (side_sum === 3) && (z1 <= this.grz_max)) + side1 = side2 = side3 = side_sum = 0; - z1 = pmain.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent); - const all_pnts = [], all_faces = []; - let ngraphs = 1, gr = bin.fPoly, nfaces = 0; + if (side_sum === 3) + continue; + if (side_sum === -3) + return; - if (gr._typename === clTMultiGraph) { - ngraphs = bin.fPoly.fGraphs.arr.length; - gr = null; - } + if (!this.loop) { + let npnts = Math.abs(side2 - side1) + Math.abs(side3 - side2) + Math.abs(side1 - side3); + if (side1 === 0) + ++npnts; + if (side2 === 0) + ++npnts; + if (side3 === 0) + ++npnts; - for (let ngr = 0; ngr < ngraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if ((npnts === 1) || (npnts === 2)) + console.error(`FOUND npnts = ${npnts}`); - const x = gr.fX, y = gr.fY; - let npnts = gr.fNpoints; - while ((npnts>2) && (x[0]===x[npnts-1]) && (y[0]===y[npnts-1])) --npnts; + if (npnts > 2) { + if (nfaces[lvl] === undefined) + nfaces[lvl] = 0; + nfaces[lvl] += npnts - 2; + } - let pnts, faces; + // check if any(contours for given level exists + if (((side1 > 0) || (side2 > 0) || (side3 > 0)) && + ((side1 !== side2) || (side2 !== side3) || (side3 !== side1))) + ++ngridsegments; - for (let ntry = 0; ntry < 2; ++ntry) { - // run two loops - on the first try to compress data, on second - run as is (removing duplication) + continue; + } - let lastx, lasty, currx, curry, - dist2 = pmain.size_x3d*pmain.size_z3d; - const dist2limit = (ntry > 0) ? 0 : dist2/1e6; + gridcnt = 0; - pnts = []; faces = null; + pntindx = 0; + if (side1 === 0) { + pntbuf[pntindx] = x1; + pntbuf[pntindx + 1] = y1; + pntbuf[pntindx + 2] = z1; + pntindx += 3; + } - for (let vert = 0; vert < npnts; ++vert) { - currx = pmain.grx(x[vert]); - curry = pmain.gry(y[vert]); - if (vert > 0) - dist2 = (currx-lastx)*(currx-lastx) + (curry-lasty)*(curry-lasty); - if (dist2 > dist2limit) { - pnts.push(new Vector2(currx, curry)); - lastx = currx; - lasty = curry; - } + if (side1 !== side2) { + // order is important, should move from 1->2 point, checked via lastpart + lastpart = 0; + if ((side1 < 0) || (side2 < 0)) + addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl - 1]); + if ((side1 > 0) || (side2 > 0)) + addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl], true); } - try { - if (pnts.length > 2) - faces = ShapeUtils.triangulateShape(pnts, []); - } catch (e) { - faces = null; + if (side2 === 0) { + pntbuf[pntindx] = x2; + pntbuf[pntindx + 1] = y2; + pntbuf[pntindx + 2] = z2; + pntindx += 3; } - if (faces && (faces.length > pnts.length-3)) break; - } - - if (faces?.length && pnts) { - all_pnts.push(pnts); - all_faces.push(faces); + if (side2 !== side3) { + // order is important, should move from 2->3 point, checked via lastpart + lastpart = 0; + if ((side2 < 0) || (side3 < 0)) + addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl - 1]); + if ((side2 > 0) || (side3 > 0)) + addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl], true); + } - nfaces += faces.length * 2; - if (z1 > z0) nfaces += pnts.length*2; - } - } + if (side3 === 0) { + pntbuf[pntindx] = x3; + pntbuf[pntindx + 1] = y3; + pntbuf[pntindx + 2] = z3; + pntindx += 3; + } - const pos = new Float32Array(nfaces*9); - let indx = 0; + if (side3 !== side1) { + // order is important, should move from 3->1 point, checked via lastpart + lastpart = 0; + if ((side3 < 0) || (side1 < 0)) + addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl - 1]); + if ((side3 > 0) || (side1 > 0)) + addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl], true); + } - for (let ngr = 0; ngr < all_pnts.length; ++ngr) { - const pnts = all_pnts[ngr], faces = all_faces[ngr]; + if (pntindx === 0) + continue; + if (pntindx < 9) { + console.log(`found ${pntindx / 3} points, must be at least 3`); + continue; + } - for (let layer = 0; layer < 2; ++layer) { - for (let n = 0; n < faces.length; ++n) { - const face = faces[n], - pnt1 = pnts[face[0]], - pnt2 = pnts[face[layer === 0 ? 2 : 1]], - pnt3 = pnts[face[layer === 0 ? 1 : 2]]; + if (grid && (gridcnt === 6)) { + for (let jj = 0; jj < 6; ++jj) + grid[gindx + jj] = gridpnts[jj]; + gindx += 6; + } - pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + // if three points and surf === 14, remember vertex for each point - pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + const buf = posbuf[lvl]; + let s = posbufindx[lvl]; + if (this.donormals && (pntindx === 9)) { + rememberVertex(s, handle, i, j); + rememberVertex(s + 3, handle, i + 1, is_first ? j + 1 : j); + rememberVertex(s + 6, handle, is_first ? i : i + 1, j + 1); + } - pos[indx] = pnt3.x; - pos[indx+1] = pnt3.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + for (let k1 = 3; k1 < pntindx - 3; k1 += 3) { + buf[s] = pntbuf[0]; + buf[s + 1] = pntbuf[1]; + buf[s + 2] = pntbuf[2]; + s += 3; + buf[s] = pntbuf[k1]; + buf[s + 1] = pntbuf[k1 + 1]; + buf[s + 2] = pntbuf[k1 + 2]; + s += 3; + buf[s] = pntbuf[k1 + 3]; + buf[s + 1] = pntbuf[k1 + 4]; + buf[s + 2] = pntbuf[k1 + 5]; + s += 3; } + posbufindx[lvl] = s; } + }; - if (z1>z0) { - for (let n = 0; n < pnts.length; ++n) { - const pnt1 = pnts[n], pnt2 = pnts[n > 0 ? n - 1 : pnts.length - 1]; + this.callFuncs = function(meshFunc, linesFunc) { + for (let lvl = 1; lvl < levels.length; ++lvl) { + if (posbuf[lvl] && meshFunc) + meshFunc(lvl, posbuf[lvl], normindx); + } - pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z0; - indx+=3; + if (lpos && linesFunc) { + if (nsegments * 6 !== lindx) + console.error(`SURF lines mismmatch nsegm=${nsegments} lindx=${lindx} diff=${nsegments * 6 - lindx}`); + linesFunc(false, lpos); + } - pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z0; - indx+=3; + if (grid && linesFunc) { + if (ngridsegments * 6 !== gindx) + console.error(`SURF grid draw mismatch ngridsegm=${ngridsegments} gindx=${gindx} diff=${ngridsegments * 6 - gindx}`); + linesFunc(true, grid); + } + }; + } - pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z1; - indx+=3; +} // class Triangles3DHandler - pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z0; - indx+=3; - pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z1; - indx+=3; +/** @summary Build 3d surface + * @desc Make it independent from three.js to be able reuse it for 2D case + * @private */ +function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { + const main_grz = handle.grz, + arrx = handle.original ? handle.origx : handle.grx, + arry = handle.original ? handle.origy : handle.gry, + triangles = new Triangles3DHandler(ilevels, handle.grz, handle.grz_min, handle.grz_max, handle.dolines, handle.donormals, handle.dogrid); + let i, j, x1, x2, y1, y2, z11, z12, z21, z22; - pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z1; - indx+=3; - } - } - } + triangles.createNormIndex(handle); - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(pos, 3)); - geometry.computeVertexNormals(); + for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { + triangles.createBuffers(); - const material = new MeshBasicMaterial(getMaterialArgs(painter._color_palette?.getColor(colindx), { vertexColors: false })), - mesh = new Mesh(geometry, material); + for (i = handle.i1; i < handle.i2 - 1; ++i) { + x1 = handle.original ? 0.5 * (arrx[i] + arrx[i + 1]) : arrx[i]; + x2 = handle.original ? 0.5 * (arrx[i + 1] + arrx[i + 2]) : arrx[i + 1]; + for (j = handle.j1; j < handle.j2 - 1; ++j) { + y1 = handle.original ? 0.5 * (arry[j] + arry[j + 1]) : arry[j]; + y2 = handle.original ? 0.5 * (arry[j + 1] + arry[j + 2]) : arry[j + 1]; - pmain.add3DMesh(mesh); + z11 = main_grz(histo.getBinContent(i + 1, j + 1)); + z12 = main_grz(histo.getBinContent(i + 1, j + 2)); + z21 = main_grz(histo.getBinContent(i + 2, j + 1)); + z22 = main_grz(histo.getBinContent(i + 2, j + 2)); - mesh.painter = painter; - mesh.bins_index = i; - mesh.draw_z0 = z0; - mesh.draw_z1 = z1; - mesh.tip_color = 0x00FF00; + triangles.addMainTriangle(x1, y1, z11, x2, y2, z22, x1, y2, z12, true, handle, i, j); - mesh.tooltip = function(/* intersects */) { - const p = this.painter, main = p.getFramePainter(), - bin = p.getObject().fBins.arr[this.bins_index], - - tip = { - use_itself: true, // indicate that use mesh itself for highlighting - x1: main.grx(bin.fXmin), - x2: main.grx(bin.fXmax), - y1: main.gry(bin.fYmin), - y2: main.gry(bin.fYmax), - z1: this.draw_z0, - z2: this.draw_z1, - bin: this.bins_index, - value: bin.fContent, - color: this.tip_color, - lines: p.getPolyBinTooltips(this.bins_index) - }; + triangles.addMainTriangle(x1, y1, z11, x2, y1, z21, x2, y2, z22, false, handle, i, j); - return tip; - }; - } -} + triangles.addLineSegment(x1, y2, z12, x1, y1, z11); + triangles.addLineSegment(x1, y1, z11, x2, y1, z21); -/** @summary Draw 2-D histogram in 3D - * @private */ -class TH2Painter extends TH2Painter$2 { + if (i === handle.i2 - 2) + triangles.addLineSegment(x2, y1, z21, x2, y2, z22); + if (j === handle.j2 - 2) + triangles.addLineSegment(x1, y2, z12, x2, y2, z22); + } + } + } - /** @summary draw TH2 object in 3D mode */ - async draw3D(reason) { - this.mode3d = true; + triangles.callFuncs(meshFunc, linesFunc); +} - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(), // is main histogram - histo = this.getHisto(); - let pr = Promise.resolve(true); - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - } else { - const pad = this.getPadPainter().getRootPad(true), - logz = pad?.fLogv ?? pad?.fLogz; - let zmult = 1; +/** + * @summary Painter for TH2 classes + * @private + */ - if (this.options.minimum !== kNoZoom && this.options.maximum !== kNoZoom) { - this.zmin = this.options.minimum; - this.zmax = this.options.maximum; - } else if (this.draw_content || (this.gmaxbin !== 0)) { - this.zmin = logz ? this.gminposbin * 0.3 : this.gminbin; - this.zmax = this.gmaxbin; - zmult = 1 + 2*gStyle.fHistTopMargin; - } +let TH2Painter$2 = class TH2Painter extends THistPainter { - if (logz && (this.zmin <= 0)) - this.zmin = this.zmax * 1e-5; + #projection_kind; // kind of enabled histogram projection + #projection_widthX; // X width of projection + #projection_widthY; // Y width of projection + #can_move_colz; // temporary flag for readjust palette positions + #hide_frame; // hide frame when drawing + #chord; // zooming for chord drawing - this.createHistDrawAttributes(true); + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { zmult, zoom: settings.Zooming, ndim: 2, - draw: this.options.Axis !== -1, drawany: this.options.isCartesian(), - reverse_x: this.options.RevX, reverse_y: this.options.RevY }); - }); - } + /** @summary cleanup painter */ + cleanup() { + delete this.tt_handle; + this.#chord = undefined; + super.cleanup(); + } - if (main.mode3d) { - pr = pr.then(() => { - if (this.draw_content) { - if (this.isTH2Poly()) - drawTH2PolyLego(this); - else if (this.options.Contour) - drawBinsContour3D(this, true); - else if (this.options.Surf) - drawBinsSurf3D(this); - else if (this.options.Error) - drawBinsError3D(this); - else - drawBinsLego(this); - } else if (this.options.Axis && this.options.Zscale) { - this.getContourLevels(true); - this.getHistPalette(); - } - main.render3D(); - this.updateStatWebCanvas(); - main.addKeysHandler(); - }); + /** @summary Returns histogram + * @desc Also assigns custom getBinContent method for TProfile2D if PROJXY options specified */ + getHisto() { + const histo = super.getHisto(); + if (histo?._typename === clTProfile2D) { + if (!histo.$getBinContent) + histo.$getBinContent = histo.getBinContent; + switch (this.getOptions().Profile2DProj) { + case 'B': + histo.getBinContent = histo.getBinEntries; + break; + case 'C=E': + histo.getBinContent = function(i, j) { return this.getBinError(this.getBin(i, j)); }; + break; + case 'W': + histo.getBinContent = function(i, j) { return this.$getBinContent(i, j) * this.getBinEntries(i, j); }; + break; + default: + histo.getBinContent = histo.$getBinContent; + break; } } + return histo; + } - // (re)draw palette by resize while canvas may change dimension - if (is_main) { - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && ((this.options.Lego === 12) || (this.options.Lego === 14) || - (this.options.Surf === 11) || (this.options.Surf === 12)))); - } + /** @summary Returns if projection is used */ + isProjection() { return this.#projection_kind; } - return pr.then(() => this.updateFunctions()) - .then(() => this.updateHistTitle()) - .then(() => this); - } + /** @summary Toggle projection */ + toggleProjection(kind, width) { + if ((kind === 'Projections') || (kind === 'Off')) + kind = ''; - /** @summary draw TH2 object */ - static async draw(dom, histo, opt) { - return THistPainter._drawHist(new TH2Painter(dom, histo), opt); - } + const parseWidth = arg => { + if ((arg === 'all') || (arg === 'ALL')) + return 10000; + const res = parseInt(arg); + return res && Number.isInteger(res) ? res : 1; + }; -} // class TH2Painter + let widthX = width, widthY = width; -var TH2Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TH2Painter: TH2Painter -}); + if (isStr(kind) && (kind.indexOf('XY') === 0)) { + const ws = (kind.length > 2) ? kind.slice(2) : ''; + kind = 'XY'; + widthX = widthY = parseInt(ws) || 1; + } else if (isStr(kind) && (kind.length > 1)) { + const ps = kind.indexOf('_'); + if ((ps > 0) && (kind[0] === 'X') && (kind[ps + 1] === 'Y')) { + widthX = parseWidth(kind.slice(1, ps)); + widthY = parseWidth(kind.slice(ps + 2)); + kind = 'XY'; + } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps + 1] === 'X')) { + widthY = parseWidth(kind.slice(1, ps)); + widthX = parseWidth(kind.slice(ps + 2)); + kind = 'XY'; + } else { + widthX = widthY = parseWidth(kind.slice(1)); + kind = kind[0]; + } + } -/** - * @summary Painter for TH3 classes - * @private - */ + if (!widthX && !widthY) + widthX = widthY = 1; -class TH3Painter extends THistPainter { + if (kind && (this.#projection_kind === kind)) { + if ((this.#projection_widthX === widthX) && (this.#projection_widthY === widthY)) + kind = ''; + else { + this.#projection_widthX = widthX; + this.#projection_widthY = widthY; + return; + } + } - /** @summary Returns number of histogram dimensions */ - getDimension() { return 3; } + delete this.proj_hist; - /** @summary Scan TH3 histogram content */ - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) return; + const new_proj = (this.#projection_kind === kind) ? '' : kind; + this.#projection_widthX = widthX; + this.#projection_widthY = widthY; + this.#projection_kind = ''; // avoid projection handling until area is created - const histo = this.getHisto(); + return this.provideSpecialDrawArea(new_proj).then(() => { + this.#projection_kind = new_proj; + return this.redrawProjection(); + }); + } - this.extractAxesProperties(3); + /** @summary Redraw projection */ + async redrawProjection(ii1, ii2, jj1, jj2) { + if (!this.#projection_kind) + return false; - // global min/max, used at the moment in 3D drawing - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1, 1); - this.gminposbin = null; + if (jj2 === undefined) { + if (!this.tt_handle) + return; + ii1 = Math.round((this.tt_handle.i1 + this.tt_handle.i2) / 2); + ii2 = ii1 + 1; + jj1 = Math.round((this.tt_handle.j1 + this.tt_handle.j2) / 2); + jj2 = jj1 + 1; + } - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) { - for (let k = 0; k < this.nbinsz; ++k) { - const bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content < this.gminbin) - this.gminbin = bin_content; - else if (bin_content > this.gmaxbin) - this.gmaxbin = bin_content; + const canp = this.getCanvPainter(); - if ((bin_content > 0) && ((this.gminposbin === null) || (this.gminposbin > bin_content))) - this.gminposbin = bin_content; - } + if (canp && !canp.isReadonly() && this.hasSnapId()) { + // this is when projection should be created on the server side + if (((this.#projection_kind === 'X') || (this.#projection_kind === 'XY')) && !canp.websocketTimeout('projX')) { + if (canp.sendWebsocket(`EXECANDSEND:DXPROJ:${this.getSnapId()}:ProjectionX("_projx",${jj1 + 1},${jj2},"")`)) + canp.websocketTimeout('projX', 1000); + } + if (((this.#projection_kind === 'Y') || (this.#projection_kind === 'XY')) && !canp.websocketTimeout('projY')) { + if (canp.sendWebsocket(`EXECANDSEND:DYPROJ:${this.getSnapId()}:ProjectionY("_projy",${ii1 + 1},${ii2},"")`)) + canp.websocketTimeout('projY', 1000); } + return true; } - if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; + if (this.doing_projection) + return false; - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); + this.doing_projection = true; - this.transferFunc = this.findFunction(clTF1, 'TransferFunction'); - if (this.transferFunc && !this.transferFunc.TestBit(BIT(9))) // TF1::kNotDraw - this.transferFunc.InvertBit(BIT(9)); - } + const histo = this.getHisto(), + createXProject = () => { + const p = createHistogram(clTH1D, this.nbinsx); + Object.assign(p.fXaxis, histo.fXaxis); + p.fName = 'xproj'; + p.fTitle = 'X projection'; + return p; + }, + createYProject = () => { + const p = createHistogram(clTH1D, this.nbinsy); + Object.assign(p.fXaxis, histo.fYaxis); + p.fName = 'yproj'; + p.fTitle = 'Y projection'; + return p; + }, + fillProjectHist = (kind, p) => { + let first = 0, last = -1; + if (kind === 'X') { + for (let i = 0; i < this.nbinsx; ++i) { + let sum = 0; + for (let j = jj1; j < jj2; ++j) + sum += histo.getBinContent(i + 1, j + 1); + p.setBinContent(i + 1, sum); + } + p.fTitle = 'X projection ' + (jj1 + 1 === jj2 ? `bin ${jj2}` : `bins [${jj1 + 1} .. ${jj2}]`); + if (this.tt_handle) { + first = this.tt_handle.i1 + 1; + last = this.tt_handle.i2; + } + } else { + for (let j = 0; j < this.nbinsy; ++j) { + let sum = 0; + for (let i = ii1; i < ii2; ++i) + sum += histo.getBinContent(i + 1, j + 1); + p.setBinContent(j + 1, sum); + } + p.fTitle = 'Y projection ' + (ii1 + 1 === ii2 ? `bin ${ii2}` : `bins [${ii1 + 1} .. ${ii2}]`); + if (this.tt_handle) { + first = this.tt_handle.j1 + 1; + last = this.tt_handle.j2; + } + } - /** @summary Count TH3 statistic */ - countStat(cond, count_skew) { - const histo = this.getHisto(), xaxis = histo.fXaxis, yaxis = histo.fYaxis, zaxis = histo.fZaxis, - i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - fp = this.getFramePainter(), - res = { name: histo.fName, entries: 0, eff_entries: 0, integral: 0, - meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0, - skewx: 0, skewy: 0, skewz: 0, skewd: 0, kurtx: 0, kurty: 0, kurtz: 0, kurtd: 0 }, - has_counted_stat = (Math.abs(histo.fTsumw) > 1e-300) && !fp.isAxisZoomed('x') && !fp.isAxisZoomed('y') && !fp.isAxisZoomed('z'); - let xi, yi, zi, xx, xside, yy, yside, zz, zside, cont, - stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0; + if (first < last) { + p.fXaxis.fFirst = first; + p.fXaxis.fLast = last; + p.fXaxis.SetBit(EAxisBits.kAxisRange, (first !== 1) || (last !== p.fXaxis.fNbins)); + } - if (!isFunc(cond)) cond = null; + // reset statistic before display + p.fEntries = 0; + p.fTsumw = 0; + }; - for (xi = 0; xi < this.nbinsx+2; ++xi) { - xx = xaxis.GetBinCoord(xi - 0.5); - xside = (xi < i1) ? 0 : (xi > i2 ? 2 : 1); + if (!this.proj_hist) { + switch (this.#projection_kind) { + case 'X': + this.proj_hist = createXProject(); + break; + case 'XY': + this.proj_hist = createXProject(); + this.proj_hist2 = createYProject(); + break; + default: + this.proj_hist = createYProject(); + } + } - for (yi = 0; yi < this.nbinsy+2; ++yi) { - yy = yaxis.GetBinCoord(yi - 0.5); - yside = (yi < j1) ? 0 : (yi > j2 ? 2 : 1); + if (this.#projection_kind === 'XY') { + fillProjectHist('X', this.proj_hist); + fillProjectHist('Y', this.proj_hist2); + return this.drawInSpecialArea(this.proj_hist, '', 'X') + .then(() => this.drawInSpecialArea(this.proj_hist2, '', 'Y')) + .then(res => { + delete this.doing_projection; + return res; + }); + } - for (zi = 0; zi < this.nbinsz+2; ++zi) { - zz = zaxis.GetBinCoord(zi - 0.5); - zside = (zi < k1) ? 0 : (zi > k2 ? 2 : 1); + fillProjectHist(this.#projection_kind, this.proj_hist); - if (cond && !cond(xx, yy, zz)) continue; + return this.drawInSpecialArea(this.proj_hist).then(res => { + delete this.doing_projection; + return res; + }); + } - cont = histo.getBinContent(xi, yi, zi); - res.entries += cont; + /** @summary Execute TH2 menu command + * @desc Used to catch standard menu items and provide local implementation */ + executeMenuCommand(method, args) { + if (super.executeMenuCommand(method, args)) + return true; - if (!has_counted_stat && (xside === 1) && (yside === 1) && (zside === 1)) { - stat_sum0 += cont; - stat_sumw2 += cont * cont; - stat_sumx1 += xx * cont; - stat_sumy1 += yy * cont; - stat_sumz1 += zz * cont; - stat_sumx2 += xx**2 * cont; - stat_sumy2 += yy**2 * cont; - stat_sumz2 += zz**2 * cont; - } - } - } + if ((method.fName === 'SetShowProjectionX') || (method.fName === 'SetShowProjectionY')) { + this.toggleProjection(method.fName[17], args && parseInt(args) ? parseInt(args) : 1); + return true; } - if (has_counted_stat) { - stat_sum0 = histo.fTsumw; - stat_sumw2 = histo.fTsumw2; - stat_sumx1 = histo.fTsumwx; - stat_sumx2 = histo.fTsumwx2; - stat_sumy1 = histo.fTsumwy; - stat_sumy2 = histo.fTsumwy2; - stat_sumz1 = histo.fTsumwz; - stat_sumz2 = histo.fTsumwz2; + if (method.fName === 'SetShowProjectionXY') { + this.toggleProjection('X' + args.replaceAll(',', '_Y')); + return true; } - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.meanz = stat_sumz1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx * res.meanx)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany * res.meany)); - res.rmsz = Math.sqrt(Math.abs(stat_sumz2 / stat_sum0 - res.meanz * res.meanz)); - } + return false; + } - res.integral = stat_sum0; + /** @summary Fill histogram context menu */ + fillHistContextMenu(menu) { + if (!this.isTH2Poly() && this.getPadPainter()?.isCanvas()) { + let kind = this.#projection_kind || ''; + if (kind) + kind += this.#projection_widthX; + if ((this.#projection_widthX !== this.#projection_widthY) && (this.#projection_kind === 'XY')) + kind = `X${this.#projection_widthX}_Y${this.#projection_widthY}`; + + const sizes = ['1', '2', '3', '5', '10', 'all']; + if (kind) + sizes.unshift(''); + + menu.sub('Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); + ['X', 'Y', 'XY'].forEach(name => { + menu.column(); + sizes.forEach(sz => { + const id = sz ? name + sz : 'Off'; + menu.addchk(kind === id, id, id, arg => this.toggleProjection(arg)); + }); + menu.endcolumn(); + }); + menu.endsub(); + } - if (histo.fEntries > 1) - res.entries = histo.fEntries; + if (!this.isTH2Poly()) + menu.add('Auto zoom-in', () => this.autoZoom()); - res.eff_entries = stat_sumw2 ? stat_sum0*stat_sum0/stat_sumw2 : Math.abs(stat_sum0); + const opts = this.getSupportedDrawOptions(), + o = this.getOptions(); - if (count_skew && !this.isTH2Poly()) { - let sumx3 = 0, sumy3 = 0, sumz3 = 0, sumx4 = 0, sumy4 = 0, sumz4 = 0, np = 0, w = 0; - for (let xi = i1; xi < i2; ++xi) { - xx = xaxis.GetBinCoord(xi + 0.5); - for (let yi = j1; yi < j2; ++yi) { - yy = yaxis.GetBinCoord(yi + 0.5); - for (let zi = k1; zi < k2; ++zi) { - zz = zaxis.GetBinCoord(zi + 0.5); - if (cond && !cond(xx, yy, zz)) continue; - w = histo.getBinContent(xi + 1, yi + 1, zi + 1); - np += w; - sumx3 += w * Math.pow(xx - res.meanx, 3); - sumy3 += w * Math.pow(yy - res.meany, 3); - sumz3 += w * Math.pow(zz - res.meany, 3); - sumx4 += w * Math.pow(xx - res.meanx, 4); - sumy4 += w * Math.pow(yy - res.meany, 4); - sumz4 += w * Math.pow(yy - res.meany, 4); - } - } - } + menu.addDrawMenu('Draw with', opts, arg => { + if (arg.indexOf(kInspect) === 0) + return this.showInspector(arg); + const oldProject = o.Project; + this.decodeOptions(arg); + if ((oldProject === o.Project) || this.mode3d) + this.interactiveRedraw('pad', 'drawopt'); + else + this.toggleProjection(o.Project); + }); - const stddev3x = Math.pow(res.rmsx, 3), - stddev3y = Math.pow(res.rmsy, 3), - stddev3z = Math.pow(res.rmsz, 3), - stddev4x = Math.pow(res.rmsx, 4), - stddev4y = Math.pow(res.rmsy, 4), - stddev4z = Math.pow(res.rmsz, 4); + if (o.Color || o.Contour || o.Hist || o.Surf || o.Lego === 12 || o.Lego === 14) + this.fillPaletteMenu(menu, true); + } - if (np * stddev3x !== 0) - res.skewx = sumx3 / (np * stddev3x); - if (np * stddev3y !== 0) - res.skewy = sumy3 / (np * stddev3y); - if (np * stddev3z !== 0) - res.skewz = sumz3 / (np * stddev3z); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; + /** @summary Process click on histogram-defined buttons */ + clickButton(funcname) { + const res = super.clickButton(funcname); + if (res) + return res; - if (np * stddev4x !== 0) - res.kurtx = sumx4 / (np * stddev4x) - 3; - if (np * stddev4y !== 0) - res.kurty = sumy4 / (np * stddev4y) - 3; - if (np * stddev4z !== 0) - res.kurtz = sumz4 / (np * stddev4z) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; + if (this.isMainPainter()) { + switch (funcname) { + case 'ToggleColor': return this.toggleColor(); + case 'Toggle3D': return this.toggleMode3D(); + } } - return res; + // all methods here should not be processed further + return false; } - /** @summary Fill TH3 statistic in stat box */ - fillStatistic(stat, dostat, dofit) { - // no need to refill statistic if histogram is dummy - if (this.isIgnoreStatsFill()) - return false; + /** @summary Fill pad toolbar with histogram-related functions */ + fillToolbar() { + super.fillToolbar(true); - if (dostat === 1) dostat = 1111; + const pp = this.getPadPainter(), + o = this.getOptions(); + if (!pp) + return; - const print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10, - data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); - // print_under = Math.floor(dostat / 10000) % 10, - // print_over = Math.floor(dostat / 100000) % 10; + if (!this.isTH2Poly() && !o.Axis) + pp.addPadButton('th2color', 'Toggle color', 'ToggleColor'); + if (!o.Axis) + pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); + pp.addPadButton('th2draw3d', 'Toggle 3D mode', 'Toggle3D'); + pp.showPadButtons(); + } - stat.clearPave(); + /** @summary Toggle color drawing mode */ + toggleColor() { + const o = this.getOptions(); + if (o.Mode3D) { + o.Mode3D = false; + o.Color = true; + } else { + o.Color = !o.Color; + o.Scat = !o.Color; + } - if (print_name > 0) - stat.addText(data.name); + this.#can_move_colz = true; // indicate that next redraw can move Z scale - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + this.copyOptionsToOthers(); - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); - stat.addText('Mean z = ' + stat.format(data.meanz)); - } + return this.interactiveRedraw('pad', 'drawopt'); + } - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - stat.addText('Std Dev z = ' + stat.format(data.rmsz)); - } + /** @summary Perform automatic zoom inside non-zero region of histogram */ + autoZoom() { + if (this.isTH2Poly()) + return; // not implemented - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); + const i1 = this.getSelectIndex('x', 'left', -1), + i2 = this.getSelectIndex('x', 'right', 1), + j1 = this.getSelectIndex('y', 'left', -1), + j2 = this.getSelectIndex('y', 'right', 1), + histo = this.getHisto(); - if (print_skew === 2) { - stat.addText(`Skewness x = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); - stat.addText(`Skewness y = ${stat.format(data.skewy)} #pm ${stat.format(data.skewd)}`); - stat.addText(`Skewness z = ${stat.format(data.skewz)} #pm ${stat.format(data.skewd)}`); - } else if (print_skew > 0) { - stat.addText(`Skewness x = ${stat.format(data.skewx)}`); - stat.addText(`Skewness y = ${stat.format(data.skewy)}`); - stat.addText(`Skewness z = ${stat.format(data.skewz)}`); - } + if ((i1 === i2) || (j1 === j2)) + return; - if (print_kurt === 2) { - stat.addText(`Kurtosis x = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); - stat.addText(`Kurtosis y = ${stat.format(data.kurty)} #pm ${stat.format(data.kurtd)}`); - stat.addText(`Kurtosis z = ${stat.format(data.kurtz)} #pm ${stat.format(data.kurtd)}`); - } else if (print_kurt > 0) { - stat.addText(`Kurtosis x = ${stat.format(data.kurtx)}`); - stat.addText(`Kurtosis y = ${stat.format(data.kurty)}`); - stat.addText(`Kurtosis z = ${stat.format(data.kurtz)}`); + // first find minimum + let min = histo.getBinContent(i1 + 1, j1 + 1); + for (let i = i1; i < i2; ++i) { + for (let j = j1; j < j2; ++j) + min = Math.min(min, histo.getBinContent(i + 1, j + 1)); } + if (min > 0) + return; // if all points positive, no chance for auto-scale - if (dofit) stat.fillFunctionStat(this.findFunction(clTF3), dofit, 3); + let ileft = i2, iright = i1, jleft = j2, jright = j1; - return true; - } + for (let i = i1; i < i2; ++i) { + for (let j = j1; j < j2; ++j) { + if (histo.getBinContent(i + 1, j + 1) > min) { + if (i < ileft) + ileft = i; + if (i >= iright) + iright = i + 1; + if (j < jleft) + jleft = j; + if (j >= jright) + jright = j + 1; + } + } + } - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(ix, iy, iz) { - const lines = [], histo = this.getHisto(); + let xmin, xmax, ymin, ymax, isany = false; - lines.push(this.getObjectHint(), - `x = ${this.getAxisBinTip('x', histo.fXaxis, ix)} xbin=${ix+1}`, - `y = ${this.getAxisBinTip('y', histo.fYaxis, iy)} ybin=${iy+1}`, - `z = ${this.getAxisBinTip('z', histo.fZaxis, iz)} zbin=${iz+1}`); + if ((ileft === iright - 1) && (ileft > i1 + 1) && (iright < i2 - 1)) { + ileft--; + iright++; + } + if ((jleft === jright - 1) && (jleft > j1 + 1) && (jright < j2 - 1)) { + jleft--; + jright++; + } - const binz = histo.getBinContent(ix+1, iy+1, iz+1); - if (binz === Math.round(binz)) - lines.push(`entries = ${binz}`); - else - lines.push(`entries = ${floatToString(binz, gStyle.fStatFormat)}`); + if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { + xmin = histo.fXaxis.GetBinLowEdge(ileft + 1); + xmax = histo.fXaxis.GetBinLowEdge(iright + 1); + isany = true; + } - if (this.matchObjectType(clTProfile3D)) { - const errz = histo.getBinError(histo.getBin(ix+1, iy+1, iz+1)); - lines.push('error = ' + ((errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat))); + if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { + ymin = histo.fYaxis.GetBinLowEdge(jleft + 1); + ymax = histo.fYaxis.GetBinLowEdge(jright + 1); + isany = true; } - return lines; + if (isany) + return this.getFramePainter()?.zoom(xmin, xmax, ymin, ymax); } - /** @summary draw 3D histogram as scatter plot - * @desc If there are too many points, box will be displayed - * @return {Promise|false} either Promise or just false that drawing cannot be performed */ - draw3DScatter() { + /** @summary Scan TH2 histogram content */ + scanContent(when_axis_changed) { + // no need to re-scan histogram while result does not depend from axis selection + if (when_axis_changed && this.nbinsx && this.nbinsy) + return; + const histo = this.getObject(), - main = this.getFramePainter(), - i1 = this.getSelectIndex('x', 'left', 0.5), - i2 = this.getSelectIndex('x', 'right', 0), - j1 = this.getSelectIndex('y', 'left', 0.5), - j2 = this.getSelectIndex('y', 'right', 0), - k1 = this.getSelectIndex('z', 'left', 0.5), - k2 = this.getSelectIndex('z', 'right', 0); - let i, j, k, bin_content; + o = this.getOptions(); + let i, j; - if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) - return Promise.resolve(true); + this.extractAxesProperties(2); - // scale down factor if too large values - const coef = (this.gmaxbin > 1000) ? 1000/this.gmaxbin : 1, - content_lmt = Math.max(0, this.gminbin); - let numpixels = 0, sumz = 0; + if (this.isTH2Poly()) { + this.gminposbin = null; + this.gminbin = this.gmaxbin = 0; - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - sumz += bin_content; - if (bin_content <= content_lmt) continue; - numpixels += Math.round(bin_content*coef); + for (let n = 0, len = histo.fBins.arr.length; n < len; ++n) { + const bin_content = histo.fBins.arr[n].fContent; + if (n === 0) + this.gminbin = this.gmaxbin = bin_content; + + if (bin_content < this.gminbin) + this.gminbin = bin_content; + else if (bin_content > this.gmaxbin) + this.gmaxbin = bin_content; + + if ((bin_content > 0) && ((this.gminposbin === null) || (this.gminposbin > bin_content))) + this.gminposbin = bin_content; + } + } else { + // global min/max, used at the moment in 3D drawing + this.gminbin = this.gmaxbin = histo.getBinContent(1, 1); + this.gminposbin = null; + for (i = 0; i < this.nbinsx; ++i) { + for (j = 0; j < this.nbinsy; ++j) { + const bin_content = histo.getBinContent(i + 1, j + 1); + if (bin_content < this.gminbin) + this.gminbin = bin_content; + else if (bin_content > this.gmaxbin) + this.gmaxbin = bin_content; + if (bin_content > 0) { + if ((this.gminposbin === null) || (this.gminposbin > bin_content)) + this.gminposbin = bin_content; + } } } } - // too many pixels - use box drawing - if (numpixels > (main.webgl ? 100000 : 30000)) - return false; - - const pnts = new PointsCreator(numpixels, main.webgl, main.size_x3d/200), - bins = new Int32Array(numpixels), - rnd = new TRandom(sumz); - let nbin = 0; - - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content <= content_lmt) continue; - const num = Math.round(bin_content*coef); - - for (let n = 0; n < num; ++n) { - const binx = histo.fXaxis.GetBinCoord(i + rnd.random()), - biny = histo.fYaxis.GetBinCoord(j + rnd.random()), - binz = histo.fZaxis.GetBinCoord(k + rnd.random()); + // this value used for logz scale drawing + if ((this.gminposbin === null) && (this.gmaxbin > 0)) + this.gminposbin = this.gmaxbin * 1e-4; - // remember bin index for tooltip - bins[nbin++] = histo.getBin(i+1, j+1, k+1); + let is_content = this.gmaxbin || this.gminbin; - pnts.addPoint(main.grx(binx), main.gry(biny), main.grz(binz)); + // for TProfile2D show empty bin if there are entries for it + if (!is_content && (histo._typename === clTProfile2D)) { + for (i = 0; i < this.nbinsx && !is_content; ++i) { + for (j = 0; j < this.nbinsy; ++j) { + if (histo.getBinEntries(i + 1, j + 1)) { + is_content = true; + break; } } } } - return pnts.createPoints({ color: this.getColor(histo.fMarkerColor) }).then(mesh => { - main.add3DMesh(mesh); - - mesh.bins = bins; - mesh.painter = this; - mesh.tip_color = histo.fMarkerColor === 3 ? 0xFF0000 : 0x00FF00; - - mesh.tooltip = function(intersect) { - const indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]); - - tip.x1 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix)); - tip.x2 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1)); - tip.y1 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy)); - tip.y2 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy+1)); - tip.z1 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz)); - tip.z2 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz+1)); - tip.color = this.tip_color; - tip.opacity = 0.3; - - return tip; - }; - - return true; - }); + if (o.Axis > 0) { + // Paint histogram axis only + this.draw_content = false; + } else if (this.isTH2Poly()) { + this.draw_content = is_content || o.Line || o.Fill || o.Mark; + if (!this.draw_content && o.Zero) { + this.draw_content = true; + o.Line = 1; + } + } else + this.draw_content = is_content || o.ShowEmpty; } - /** @summary Drawing of 3D histogram */ - async draw3DBins() { - if (!this.draw_content) - return false; - - let box_option = this.options.Box ? this.options.BoxStyle : 0; - - if (!box_option && this.options.Scat) { - const promise = this.draw3DScatter(); - if (promise !== false) return promise; - box_option = 12; // fall back to box2 draw option - } else if (!box_option && !this.options.GLBox && !this.options.GLColor && !this.options.Lego) - box_option = 12; // default draw option + /** @summary Provide histogram min/max used to create canvas ranges + * @private */ + getUserRanges() { + const histo = this.getHisto(); + return { minx: histo.fXaxis.fXmin, maxx: histo.fXaxis.fXmax, miny: histo.fYaxis.fXmin, maxy: histo.fYaxis.fXmax }; + } - const histo = this.getHisto(), - main = this.getFramePainter(); + /** @summary Count TH2 histogram statistic + * @desc Optionally one could provide condition function to select special range */ + countStat(cond, count_skew) { + const histo = this.getHisto(), o = this.getOptions(), + xaxis = histo.fXaxis, yaxis = histo.fYaxis, + funcs = this.getHistGrFuncs(), + res = { + name: histo.fName, entries: 0, eff_entries: 0, integral: 0, + meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], + xmax: 0, ymax: 0, wmax: null, skewx: 0, skewy: 0, skewd: 0, kurtx: 0, kurty: 0, kurtd: 0 + }, + has_counted_stat = !funcs.isAxisZoomed('x') && !funcs.isAxisZoomed('y') && (Math.abs(histo.fTsumw) > 1e-300) && !cond && !o.cutg; + let stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, + stat_sumx2 = 0, stat_sumy2 = 0, + xside, yside, xx, yy, zz, xleft, xright, yleft, yright; - let buffer_size = 0, use_lambert = false, - use_helper = false, use_colors = false, use_opacity = 1, exclude_content = -1, - logv = this.getPadPainter()?.getRootPad()?.fLogv, - use_scale = true, scale_offset = 0, - single_bin_verts, single_bin_norms, - fillcolor = this.getColor(histo.fFillColor), - tipscale = 0.5; + if (!isFunc(cond) && o.cutg) + cond = (x, y) => o.cutg.IsInside(x, y); - if (!box_option && this.options.Lego) - box_option = (this.options.Lego === 1) ? 10 : this.options.Lego; + if (this.isTH2Poly()) { + const len = histo.fBins.arr.length; + let i, bin, n, gr, ngr, numgraphs, numpoints; - if ((this.options.GLBox === 11) || (this.options.GLBox === 12)) { - tipscale = 0.4; - use_lambert = true; - if (this.options.GLBox === 12) use_colors = true; + for (i = 0; i < len; ++i) { + bin = histo.fBins.arr[i]; - const geom = main.webgl ? new SphereGeometry(0.5, 16, 12) : new SphereGeometry(0.5, 8, 6); - geom.applyMatrix4(new Matrix4().makeRotationX(Math.PI/2)); - geom.computeVertexNormals(); + xside = (bin.fXmin > funcs.scale_xmax) ? 2 : (bin.fXmax < funcs.scale_xmin ? 0 : 1); + yside = (bin.fYmin > funcs.scale_ymax) ? 2 : (bin.fYmax < funcs.scale_ymin ? 0 : 1); - const indx = geom.getIndex().array, - pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; + xx = yy = numpoints = 0; + gr = bin.fPoly; + numgraphs = 1; + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } - buffer_size = indx.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); + for (ngr = 0; ngr < numgraphs; ++ngr) { + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; - for (let k = 0; k < indx.length; ++k) { - const iii = indx[k]*3; - single_bin_verts[k*3] = pos[iii]; - single_bin_verts[k*3+1] = pos[iii+1]; - single_bin_verts[k*3+2] = pos[iii+2]; - single_bin_norms[k*3] = norm[iii]; - single_bin_norms[k*3+1] = norm[iii+1]; - single_bin_norms[k*3+2] = norm[iii+2]; - } - } else { - const indicies = Box3D.Indexes, - normals = Box3D.Normals, - vertices = Box3D.Vertices; + for (n = 0; n < gr.fNpoints; ++n) { + ++numpoints; + xx += gr.fX[n]; + yy += gr.fY[n]; + } + } - buffer_size = indicies.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); + if (numpoints > 1) { + xx /= numpoints; + yy /= numpoints; + } - for (let k = 0, nn = -3; k < indicies.length; ++k) { - const vert = vertices[indicies[k]]; - single_bin_verts[k*3] = vert.x-0.5; - single_bin_verts[k*3+1] = vert.y-0.5; - single_bin_verts[k*3+2] = vert.z-0.5; + zz = bin.fContent; - if (k%6 === 0) nn+=3; - single_bin_norms[k*3] = normals[nn]; - single_bin_norms[k*3+1] = normals[nn+1]; - single_bin_norms[k*3+2] = normals[nn+2]; - } - use_helper = true; + res.entries += zz; - if (box_option === 12) - use_colors = true; - else if (box_option === 13) { - use_colors = true; - use_helper = false; - } else if (this.options.GLColor) { - use_colors = true; - use_opacity = 0.5; - use_scale = false; - use_helper = false; - exclude_content = 0; - use_lambert = true; - } - } + res.matrix[yside * 3 + xside] += zz; - this._box_option = box_option; + if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) + continue; - if (use_scale && logv) { - if (this.gminposbin && (this.gmaxbin > this.gminposbin)) { - scale_offset = Math.log(this.gminposbin) - 0.1; - use_scale = 1/(Math.log(this.gmaxbin) - scale_offset); - } else { - logv = 0; - use_scale = 1; - } - } else if (use_scale) - use_scale = (this.gminbin || this.gmaxbin) ? 1 / Math.max(Math.abs(this.gminbin), Math.abs(this.gmaxbin)) : 1; + if ((res.wmax === null) || (zz > res.wmax)) { + res.wmax = zz; + res.xmax = xx; + res.ymax = yy; + } - const get_bin_weight = content => { - if ((exclude_content >= 0) && (content < exclude_content)) return 0; - if (!use_scale) return 1; - if (logv) { - if (content <= 0) return 0; - content = Math.log(content) - scale_offset; + if (!has_counted_stat) { + stat_sum0 += zz; + stat_sumw2 += zz * zz; + stat_sumx1 += xx * zz; + stat_sumy1 += yy * zz; + stat_sumx2 += xx * xx * zz; + stat_sumy2 += yy * yy * zz; + } } - return Math.pow(Math.abs(content*use_scale), 0.3333); - }, i1 = this.getSelectIndex('x', 'left', 0.5), - i2 = this.getSelectIndex('x', 'right', 0), - j1 = this.getSelectIndex('y', 'left', 0.5), - j2 = this.getSelectIndex('y', 'right', 0), - k1 = this.getSelectIndex('z', 'left', 0.5), - k2 = this.getSelectIndex('z', 'right', 0); + } else { + xleft = this.getSelectIndex('x', 'left'); + xright = this.getSelectIndex('x', 'right'); + yleft = this.getSelectIndex('y', 'left'); + yright = this.getSelectIndex('y', 'right'); - if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) - return false; + for (let xi = 0; xi <= this.nbinsx + 1; ++xi) { + xside = (xi <= xleft) ? 0 : (xi > xright ? 2 : 1); + xx = xaxis.GetBinCoord(xi - 0.5); - const cols_size = {}, cols_sequence = {}, - cntr = use_colors ? this.getContour() : null, - palette = use_colors ? this.getHistPalette() : null; - let nbins = 0, i, j, k, wei, bin_content, num_colors = 0, transfer = null; - - if (this.transferFunc && proivdeEvalPar(this.transferFunc, true)) - transfer = this.transferFunc; - const getOpacityIndex = colindx => { - const bin_opactity = getTF1Value(transfer, bin_content, false) * 3; // try to get opacity - if (!bin_opactity || (bin_opactity < 0) || (bin_opactity >= 1)) - return colindx; - return colindx + Math.round(bin_opactity * 200) * 10000; // 200 steps between 0..1 - }; + for (let yi = 0; yi <= this.nbinsy + 1; ++yi) { + yside = (yi <= yleft) ? 0 : (yi > yright ? 2 : 1); + yy = yaxis.GetBinCoord(yi - 0.5); - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) continue; + zz = histo.getBinContent(xi, yi); - wei = get_bin_weight(bin_content); - if (wei < 1e-3) continue; // do not draw empty or very small bins + res.entries += zz; - nbins++; + res.matrix[yside * 3 + xside] += zz; - if (!use_colors) continue; + if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) + continue; - let colindx = cntr.getPaletteIndex(palette, bin_content); - if (colindx !== null) { - if (transfer) - colindx = getOpacityIndex(colindx); + if ((res.wmax === null) || (zz > res.wmax)) { + res.wmax = zz; + res.xmax = xx; + res.ymax = yy; + } - if (cols_size[colindx] === undefined) { - cols_size[colindx] = 0; - cols_sequence[colindx] = num_colors++; - } - cols_size[colindx] += 1; - } else - console.error(`not found color for value = ${bin_content}`); + if (!has_counted_stat) { + stat_sum0 += zz; + stat_sumw2 += zz * zz; + stat_sumx1 += xx * zz; + stat_sumy1 += yy * zz; + stat_sumx2 += xx ** 2 * zz; + stat_sumy2 += yy ** 2 * zz; + } } } } - if (!use_colors) { - cols_size[0] = nbins; - num_colors = 1; - cols_sequence[0] = 0; + if (has_counted_stat) { + stat_sum0 = histo.fTsumw; + stat_sumw2 = histo.fTsumw2; + stat_sumx1 = histo.fTsumwx; + stat_sumx2 = histo.fTsumwx2; + stat_sumy1 = histo.fTsumwy; + stat_sumy2 = histo.fTsumwy2; } - const cols_nbins = new Array(num_colors), - bin_verts = new Array(num_colors), - bin_norms = new Array(num_colors), - bin_tooltips = new Array(num_colors), - helper_kind = new Array(num_colors), - helper_indexes = new Array(num_colors), // helper_kind === 1, use original vertices - helper_positions = new Array(num_colors); // helper_kind === 2, all vertices copied into separate buffer - - for (const colindx in cols_size) { - nbins = cols_size[colindx]; // how many bins with specified color - const nseq = cols_sequence[colindx]; + if (Math.abs(stat_sum0) > 1e-300) { + res.meanx = stat_sumx1 / stat_sum0; + res.meany = stat_sumy1 / stat_sum0; + res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx ** 2)); + res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany ** 2)); + } - cols_nbins[nseq] = helper_kind[nseq] = 0; // counter for the filled bins + if (res.wmax === null) + res.wmax = 0; + res.integral = stat_sum0; - // 1 - use same vertices to create helper, one can use maximal 64K vertices - // 2 - all vertices copied into separate buffer - if (use_helper) - helper_kind[nseq] = (nbins * buffer_size / 3 > 0xFFF0) ? 2 : 1; + if (histo.fEntries > 0) + res.entries = histo.fEntries; - bin_verts[nseq] = new Float32Array(nbins * buffer_size); - bin_norms[nseq] = new Float32Array(nbins * buffer_size); - bin_tooltips[nseq] = new Int32Array(nbins); + res.eff_entries = stat_sumw2 ? stat_sum0 * stat_sum0 / stat_sumw2 : Math.abs(stat_sum0); - if (helper_kind[nseq] === 1) - helper_indexes[nseq] = new Uint16Array(nbins * Box3D.MeshSegments.length); + if (count_skew && !this.isTH2Poly()) { + let sumx3 = 0, sumy3 = 0, sumx4 = 0, sumy4 = 0, np = 0, w; + for (let xi = xleft; xi < xright; ++xi) { + xx = xaxis.GetBinCoord(xi + 0.5); + for (let yi = yleft; yi < yright; ++yi) { + yy = yaxis.GetBinCoord(yi + 0.5); + if (cond && !cond(xx, yy)) + continue; + w = histo.getBinContent(xi + 1, yi + 1); + np += w; + sumx3 += w * Math.pow(xx - res.meanx, 3); + sumy3 += w * Math.pow(yy - res.meany, 3); + sumx4 += w * Math.pow(xx - res.meanx, 4); + sumy4 += w * Math.pow(yy - res.meany, 4); + } + } - if (helper_kind[nseq] === 2) - helper_positions[nseq] = new Float32Array(nbins * Box3D.Segments.length * 3); + const stddev3x = Math.pow(res.rmsx, 3), + stddev3y = Math.pow(res.rmsy, 3), + stddev4x = Math.pow(res.rmsx, 4), + stddev4y = Math.pow(res.rmsy, 4); + if (np * stddev3x) + res.skewx = sumx3 / (np * stddev3x); + if (np * stddev3y) + res.skewy = sumy3 / (np * stddev3y); + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; + if (np * stddev4x) + res.kurtx = sumx4 / (np * stddev4x) - 3; + if (np * stddev4y) + res.kurty = sumy4 / (np * stddev4y) - 3; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; } - let grx1, grx2, gry1, gry2, grz1, grz2; + return res; + } - for (i = i1; i < i2; ++i) { - grx1 = main.grx(histo.fXaxis.GetBinLowEdge(i+1)); - grx2 = main.grx(histo.fXaxis.GetBinLowEdge(i+2)); - for (j = j1; j < j2; ++j) { - gry1 = main.gry(histo.fYaxis.GetBinLowEdge(j+1)); - gry2 = main.gry(histo.fYaxis.GetBinLowEdge(j+2)); - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) continue; + /** @summary Fill TH2 statistic in stat box */ + fillStatistic(stat, dostat, dofit) { + // no need to refill statistic if histogram is dummy + if (this.isIgnoreStatsFill()) + return false; - wei = get_bin_weight(bin_content); - if (wei < 1e-3) continue; // do not show very small bins + if (dostat === 1) + dostat = 1111; - let nseq = 0; - if (use_colors) { - let colindx = cntr.getPaletteIndex(palette, bin_content); - if (colindx === null) continue; - if (transfer) - colindx = getOpacityIndex(colindx); - nseq = cols_sequence[colindx]; - } + const print_name = Math.floor(dostat % 10), + print_entries = Math.floor(dostat / 10) % 10, + print_mean = Math.floor(dostat / 100) % 10, + print_rms = Math.floor(dostat / 1000) % 10, + print_under = Math.floor(dostat / 10000) % 10, + print_over = Math.floor(dostat / 100000) % 10, + print_integral = Math.floor(dostat / 1000000) % 10, + print_skew = Math.floor(dostat / 10000000) % 10, + print_kurt = Math.floor(dostat / 100000000) % 10, + data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); + + stat.clearPave(); - nbins = cols_nbins[nseq]; + if (print_name > 0) + stat.addText(data.name); - grz1 = main.grz(histo.fZaxis.GetBinLowEdge(k+1)); - grz2 = main.grz(histo.fZaxis.GetBinLowEdge(k+2)); + if (print_entries > 0) + stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - // remember bin index for tooltip - bin_tooltips[nseq][nbins] = histo.getBin(i+1, j+1, k+1); + if (print_mean > 0) { + stat.addText('Mean x = ' + stat.format(data.meanx)); + stat.addText('Mean y = ' + stat.format(data.meany)); + } - const bin_v = bin_verts[nseq], bin_n = bin_norms[nseq]; - let vvv = nbins * buffer_size; + if (print_rms > 0) { + stat.addText('Std Dev x = ' + stat.format(data.rmsx)); + stat.addText('Std Dev y = ' + stat.format(data.rmsy)); + } - // Grab the coordinates and scale that are being assigned to each bin - for (let vi = 0; vi < buffer_size; vi+=3, vvv+=3) { - bin_v[vvv] = (grx2 + grx1) / 2 + single_bin_verts[vi] * (grx2 - grx1) * wei; - bin_v[vvv+1] = (gry2 + gry1) / 2 + single_bin_verts[vi+1] * (gry2 - gry1) * wei; - bin_v[vvv+2] = (grz2 + grz1) / 2 + single_bin_verts[vi+2] * (grz2 - grz1) * wei; + if (print_integral > 0) + stat.addText('Integral = ' + stat.format(data.matrix[4], 'entries')); - bin_n[vvv] = single_bin_norms[vi]; - bin_n[vvv+1] = single_bin_norms[vi+1]; - bin_n[vvv+2] = single_bin_norms[vi+2]; - } + if (print_skew === 2) { + stat.addText(`Skewness x = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); + stat.addText(`Skewness y = ${stat.format(data.skewy)} #pm ${stat.format(data.skewd)}`); + } else if (print_skew > 0) { + stat.addText(`Skewness x = ${stat.format(data.skewx)}`); + stat.addText(`Skewness y = ${stat.format(data.skewy)}`); + } - if (helper_kind[nseq] === 1) { - // reuse vertices created for the mesh - const helper_segments = Box3D.MeshSegments; - vvv = nbins * helper_segments.length; - const shift = Math.round(nbins * buffer_size / 3), - helper_i = helper_indexes[nseq]; - for (let n = 0; n < helper_segments.length; ++n) - helper_i[vvv + n] = shift + helper_segments[n]; - } + if (print_kurt === 2) { + stat.addText(`Kurtosis x = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); + stat.addText(`Kurtosis y = ${stat.format(data.kurty)} #pm ${stat.format(data.kurtd)}`); + } else if (print_kurt > 0) { + stat.addText(`Kurtosis x = ${stat.format(data.kurtx)}`); + stat.addText(`Kurtosis y = ${stat.format(data.kurty)}`); + } - if (helper_kind[nseq] === 2) { - const helper_segments = Box3D.Segments, - helper_p = helper_positions[nseq]; - vvv = nbins * helper_segments.length * 3; - for (let n = 0; n < helper_segments.length; ++n, vvv += 3) { - const vert = Box3D.Vertices[helper_segments[n]]; - helper_p[vvv] = (grx2 + grx1) / 2 + (vert.x - 0.5) * (grx2 - grx1) * wei; - helper_p[vvv+1] = (gry2 + gry1) / 2 + (vert.y - 0.5) * (gry2 - gry1) * wei; - helper_p[vvv+2] = (grz2 + grz1) / 2 + (vert.z - 0.5) * (grz2 - grz1) * wei; - } - } + if ((print_under > 0) || (print_over > 0)) { + const get = i => data.matrix[i].toFixed(0); - cols_nbins[nseq] = nbins+1; - } - } + stat.addText(`${get(6)} | ${get(7)} | ${get(7)}`); + stat.addText(`${get(3)} | ${get(4)} | ${get(5)}`); + stat.addText(`${get(0)} | ${get(1)} | ${get(2)}`); } - for (const colindx in cols_size) { - const nseq = cols_sequence[colindx], - all_bins_buffgeom = new BufferGeometry(); // BufferGeometries that store geometry of all bins + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF2), dofit, 2); - // Create mesh from bin buffergeometry - all_bins_buffgeom.setAttribute('position', new BufferAttribute(bin_verts[nseq], 3)); - all_bins_buffgeom.setAttribute('normal', new BufferAttribute(bin_norms[nseq], 3)); + return true; + } - let opacity = use_opacity; + /** @summary Draw TH2 bins as colors */ + drawBinsColor() { + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw(), + cntr = this.getContour(), + palette = this.getHistPalette(), + entries = [], + has_sumw2 = histo.fSumw2?.length, + show_empty = o.ShowEmpty, + can_merge_x = (o.Color !== 7) || ((handle.xbar1 === 0) && (handle.xbar2 === 1)), + can_merge_y = (o.Color !== 7) || ((handle.ybar1 === 0) && (handle.ybar2 === 1)), + colindx0 = cntr.getPaletteIndex(palette, 0); - if (use_colors) { - fillcolor = this._color_palette.getColor(colindx % 10000); - if (colindx > 10000) opacity = Math.floor(colindx / 10000) / 200; - } + let dx, dy, x1, y2, binz, is_zero, colindx, last_entry = null, + skip_zero = !o.Zero, skip_bin; - const material = use_lambert - ? new MeshLambertMaterial({ color: fillcolor, opacity, transparent: opacity < 1, vertexColors: false }) - : new MeshBasicMaterial({ color: fillcolor, opacity, transparent: opacity < 1, vertexColors: false }), - combined_bins = new Mesh(all_bins_buffgeom, material); + const test_cutg = o.cutg, + flush_last_entry = () => { + last_entry.path += `h${dx}v${last_entry.y1 - last_entry.y2}h${-dx}z`; + last_entry = null; + }; - combined_bins.bins = bin_tooltips[nseq]; - combined_bins.bins_faces = buffer_size/9; - combined_bins.painter = this; - combined_bins.tipscale = tipscale; - combined_bins.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; - combined_bins.get_weight = get_bin_weight; + // check in the beginning if zero can be skipped + if (!skip_zero && !show_empty && (colindx0 === null)) + skip_zero = true; - combined_bins.tooltip = function(intersect) { - const indx = Math.floor(intersect.faceIndex / this.bins_faces); - if ((indx < 0) || (indx >= this.bins.length)) return null; + // special check for TProfile2D - empty bin with no entries shown + if (skip_zero && (histo?._typename === clTProfile2D)) + skip_zero = 1; - const p = this.painter, - histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]), - grx1 = main.grx(histo.fXaxis.GetBinCoord(tip.ix-1)), - grx2 = main.grx(histo.fXaxis.GetBinCoord(tip.ix)), - gry1 = main.gry(histo.fYaxis.GetBinCoord(tip.iy-1)), - gry2 = main.gry(histo.fYaxis.GetBinCoord(tip.iy)), - grz1 = main.grz(histo.fZaxis.GetBinCoord(tip.iz-1)), - grz2 = main.grz(histo.fZaxis.GetBinCoord(tip.iz)), - wei2 = this.get_weight(tip.value) * this.tipscale; + // now start build + for (let i = handle.i1; i < handle.i2; ++i) { + dx = (handle.grx[i + 1] - handle.grx[i]) || 1; + if (can_merge_x) + x1 = handle.grx[i]; + else { + x1 = Math.round(handle.grx[i] + dx * handle.xbar1); + dx = Math.round(dx * (handle.xbar2 - handle.xbar1)) || 1; + } - tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; - tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; - tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; - tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; - tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; - tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; - tip.color = this.tip_color; + for (let j = handle.j2 - 1; j >= handle.j1; --j) { + binz = histo.getBinContent(i + 1, j + 1); + is_zero = (binz === 0) && (!has_sumw2 || histo.fSumw2[histo.getBin(i + 1, j + 1)] === 0); - return tip; - }; + skip_bin = is_zero && ((skip_zero === 1) ? !histo.getBinEntries(i + 1, j + 1) : skip_zero); - main.add3DMesh(combined_bins); + if (skip_bin || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) { + if (last_entry) + flush_last_entry(); + continue; + } - if (helper_kind[nseq] > 0) { - const helper_material = new LineBasicMaterial({ color: this.getColor(histo.fLineColor) }), - lines = (helper_kind[nseq] === 1) - // reuse positions from the mesh - only special index was created - ? createLineSegments(bin_verts[nseq], helper_material, helper_indexes[nseq]) - : createLineSegments(helper_positions[nseq], helper_material); + colindx = cntr.getPaletteIndex(palette, binz); - main.add3DMesh(lines); - } - } + if (colindx === null) { + if (is_zero && (show_empty || (skip_zero === 1))) + colindx = colindx0 || 0; + else { + if (last_entry) + flush_last_entry(); + continue; + } + } - return true; - } + dy = (handle.gry[j] - handle.gry[j + 1]) || 1; + if (can_merge_y) + y2 = handle.gry[j + 1]; + else { + y2 = Math.round(handle.gry[j] - dy * handle.ybar2); + dy = Math.round(dy * (handle.ybar2 - handle.ybar1)) || 1; + } - /** @summary Redraw TH3 histogram */ - async redraw(reason) { - const main = this.getFramePainter(), // who makes axis and 3D drawing - histo = this.getHisto(); - let pr = Promise.resolve(true); + const cmd1 = `M${x1},${y2}`; + let entry = entries[colindx]; + if (!entry) + entry = entries[colindx] = { path: cmd1 }; + else if (can_merge_y && (entry === last_entry)) { + entry.y1 = y2 + dy; + continue; + } else { + const ddx = x1 - entry.x1, ddy = y2 - entry.y2; + if (ddx || ddy) { + const cmd2 = `m${ddx},${ddy}`; + entry.path += (cmd2.length < cmd1.length) ? cmd2 : cmd1; + } + } + if (last_entry) + flush_last_entry(); - if (reason === 'resize') { - if (main.resize3D()) main.render3D(); - } else { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { zoom: settings.Zooming, ndim: 3, - draw: this.options.Axis !== -1, drawany: this.options.isCartesian() }); - return this.draw3DBins(); - }).then(() => { - main.render3D(); - this.updateStatWebCanvas(); - main.addKeysHandler(); - }); + entry.x1 = x1; + entry.y2 = y2; + + if (can_merge_y) { + entry.y1 = y2 + dy; + last_entry = entry; + } else + entry.path += `h${dx}v${dy}h${-dx}z`; + } + if (last_entry) + flush_last_entry(); } - if (this.isMainPainter()) - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && (this._box_option === 12 || this._box_option === 13))); + entries.forEach((entry, ecolindx) => { + if (entry) + this.appendPath(entry.path).attr('fill', palette.getColor(ecolindx)); + }); - return pr.then(() => this.updateFunctions()) - .then(() => this.updateHistTitle()) - .then(() => this); + return handle; } - /** @summary Fill pad toolbar with TH3-related functions */ - fillToolbar() { - const pp = this.getPadPainter(); - if (!pp) return; - - pp.addPadButton('auto_zoom', 'Unzoom all axes', 'ToggleZoom', 'Ctrl *'); - if (this.draw_content) - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - pp.showPadButtons(); - } + /** @summary Draw TH2 bins as colors in polar coordinates */ + drawBinsPolar() { + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw(), + cntr = this.getContour(), + palette = this.getHistPalette(), + entries = [], + has_sumw2 = histo.fSumw2?.length, + show_empty = o.ShowEmpty, + colindx0 = cntr.getPaletteIndex(palette, 0); - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - let obj = this.getHisto(); - if (obj) obj = obj[`f${axis.toUpperCase()}axis`]; - return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); - } + let binz, is_zero, colindx, + skip_zero = !o.Zero, skip_bin; - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - const i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - histo = this.getObject(); - let i, j, k; + const test_cutg = o.cutg; - if ((i1 === i2) || (j1 === j2) || (k1 === k2)) return; + // check in the beginning if zero can be skipped + if (!skip_zero && !show_empty && (colindx0 === null)) + skip_zero = true; + + // special check for TProfile2D - empty bin with no entries shown + if (skip_zero && (histo?._typename === clTProfile2D)) + skip_zero = 1; + + handle.getBinPath = function(i, j) { + const a1 = 2 * Math.PI * Math.max(0, this.grx[i]) / this.width, + a2 = 2 * Math.PI * Math.min(this.grx[i + 1], this.width) / this.width, + r2 = Math.min(this.gry[j], this.height) / this.height, + r1 = Math.max(0, this.gry[j + 1]) / this.height, + side = a2 - a1 > Math.PI ? 1 : 0; // handle very large sector + + // do not process bins outside visible range + if ((a2 <= a1) || (r2 <= r1)) + return ''; + + const x0 = this.width / 2, y0 = this.height / 2, + rx1 = r1 * this.width / 2, + rx2 = r2 * this.width / 2, + ry1 = r1 * this.height / 2, + ry2 = r2 * this.height / 2, + x11 = x0 + rx1 * Math.cos(a1), + x12 = x0 + rx1 * Math.cos(a2), + y11 = y0 + ry1 * Math.sin(a1), + y12 = y0 + ry1 * Math.sin(a2), + x21 = x0 + rx2 * Math.cos(a1), + x22 = x0 + rx2 * Math.cos(a2), + y21 = y0 + ry2 * Math.sin(a1), + y22 = y0 + ry2 * Math.sin(a2); + + return `M${x11.toFixed(2)},${y11.toFixed(2)}` + + `A${rx1.toFixed(2)},${ry1.toFixed(2)},0,${side},1,${x12.toFixed(2)},${y12.toFixed(2)}` + + `L${x22.toFixed(2)},${y22.toFixed(2)}` + + `A${rx2.toFixed(2)},${ry2.toFixed(2)},0,${side},0,${x21.toFixed(2)},${y21.toFixed(2)}Z`; + }; - // first find minimum - let min = histo.getBinContent(i1+1, j1+1, k1+1); - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) - min = Math.min(min, histo.getBinContent(i+1, j+1, k+1)); - } - } - if (min > 0) return; // if all points positive, no chance for autoscale + handle.findBin = function(x, y) { + const x0 = this.width / 2, y0 = this.height / 2; + let angle = Math.atan2((y - y0) / this.height, (x - x0) / this.width), i, j; + const radius = Math.abs(Math.cos(angle)) > 0.5 ? (x - x0) / Math.cos(angle) / this.width * 2 : (y - y0) / Math.sin(angle) / this.height * 2; - let ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1; + if (angle < 0) + angle += 2 * Math.PI; - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - if (histo.getBinContent(i+1, j+1, k+1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - if (k < kleft) kleft = k; - if (k >= kright) kright = k + 1; - } - } + for (i = this.i1; i < this.i2; ++i) { + const a1 = 2 * Math.PI * this.grx[i] / this.width, + a2 = 2 * Math.PI * this.grx[i + 1] / this.width; + if ((a1 <= angle) && (angle <= a2)) + break; } - } - let xmin, xmax, ymin, ymax, zmin, zmax, isany = false; + for (j = this.j1; j < this.j2; ++j) { + const r2 = this.gry[j] / this.height, + r1 = this.gry[j + 1] / this.height; + if ((r1 <= radius) && (radius <= r2)) + break; + } - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } - if ((kleft === kright-1) && (kleft > k1+1) && (kright < k2-1)) { kleft--; kright++; } + return { i, j }; + }; - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = histo.fXaxis.GetBinLowEdge(ileft+1); - xmax = histo.fXaxis.GetBinLowEdge(iright+1); - isany = true; - } + // now start build + for (let i = handle.i1; i < handle.i2; ++i) { + for (let j = handle.j2 - 1; j >= handle.j1; --j) { + binz = histo.getBinContent(i + 1, j + 1); + is_zero = (binz === 0) && (!has_sumw2 || histo.fSumw2[histo.getBin(i + 1, j + 1)] === 0); - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = histo.fYaxis.GetBinLowEdge(jleft+1); - ymax = histo.fYaxis.GetBinLowEdge(jright+1); - isany = true; - } + skip_bin = is_zero && ((skip_zero === 1) ? !histo.getBinEntries(i + 1, j + 1) : skip_zero); - if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) { - zmin = histo.fZaxis.GetBinLowEdge(kleft+1); - zmax = histo.fZaxis.GetBinLowEdge(kright+1); - isany = true; - } + if (skip_bin || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) + continue; - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax, zmin, zmax); - } + colindx = cntr.getPaletteIndex(palette, binz); - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - const opts = this.getSupportedDrawOptions(); + if (colindx === null) { + if (is_zero && (show_empty || (skip_zero === 1))) + colindx = colindx0 || 0; + else + continue; + } - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); + const cmd = handle.getBinPath(i, j); + if (!cmd) + continue; - this.decodeOptions(arg); + const entry = entries[colindx]; + if (!entry) + entries[colindx] = { path: cmd }; + else + entry.path += cmd; + } + } - this.interactiveRedraw(true, 'drawopt'); + entries.forEach((entry, ecolindx) => { + if (entry) + this.appendPath(entry.path).attr('fill', palette.getColor(ecolindx)); }); + + return handle; } - /** @summary draw TH3 object */ - static async draw(dom, histo, opt) { - const painter = new TH3Painter(dom, histo); - painter.mode3d = true; + /** @summary Draw histogram bins with projection function */ + drawBinsProjected() { + const handle = this.prepareDraw({ rounding: false, nozoom: true, extra: 100, original: true }), + funcs = this.getHistGrFuncs(), + ilevels = this.getContourLevels(), + palette = this.getHistPalette(), + func = isFunc(funcs.getProjectionFunc) ? funcs.getProjectionFunc() : (x, y) => { return { x, y }; }; - return ensureTCanvas(painter, '3d').then(() => { - painter.setAsMainPainter(); - painter.decodeOptions(opt); - painter.checkPadRange(); - painter.scanContent(); - painter.createStat(); // only when required - return painter.redraw(); - }) - .then(() => painter.drawFunctions()) - .then(() => { - painter.fillToolbar(); - return painter; - }); - } + handle.grz = z => z; + handle.grz_min = ilevels.at(0); + handle.grz_max = ilevels.at(-1); -} // class TH3Painter + buildSurf3D(this.getHisto(), handle, ilevels, (lvl, pos) => { + let dd = '', lastx, lasty; -var TH3Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TH3Painter: TH3Painter -}); + for (let i = 0; i < pos.length; i += 3) { + const pnt = func(pos[i], pos[i + 1]), + x = Math.round(funcs.grx(pnt.x)), + y = Math.round(funcs.gry(pnt.y)); -/// CSG library for THREE.js + if (i === 0) + dd = `M${x},${y}`; + else { + if ((x === lastx) && (y === lasty)) + continue; + if (i % 9 === 0) + dd += `m${x - lastx},${y - lasty}`; + else if (y === lasty) + dd += `h${x - lastx}`; + else if (x === lastx) + dd += `v${y - lasty}`; + else + dd += `l${x - lastx},${y - lasty}`; + } + lastx = x; + lasty = y; + } -const EPSILON = 1e-5, - COPLANAR = 0, - FRONT = 1, - BACK = 2, - SPANNING = FRONT | BACK; + this.appendPath(dd) + .style('fill', palette.calcColor(lvl, ilevels.length)); + }); -class Vertex { + return handle; + } - constructor(x, y, z, nx, ny, nz) { - this.x = x; - this.y = y; - this.z = z; - this.nx = nx; - this.ny = ny; - this.nz = nz; - } + /** @summary Draw histogram bins as contour */ + drawBinsContour() { + const handle = this.prepareDraw({ rounding: false, extra: 100 }), + levels = this.getContourLevels(), + palette = this.getHistPalette(), + o = this.getOptions(); + + /* eslint-disable-next-line one-var */ + const get_segm_intersection = (segm1, segm2) => { + const s10_x = segm1.x2 - segm1.x1, + s10_y = segm1.y2 - segm1.y1, + s32_x = segm2.x2 - segm2.x1, + s32_y = segm2.y2 - segm2.y1, + denom = s10_x * s32_y - s32_x * s10_y; + + if (denom === 0) + return 0; // Collinear + const denomPositive = denom > 0, + s02_x = segm1.x1 - segm2.x1, + s02_y = segm1.y1 - segm2.y1, + s_numer = s10_x * s02_y - s10_y * s02_x; + if ((s_numer < 0) === denomPositive) + return null; // No collision + + const t_numer = s32_x * s02_y - s32_y * s02_x; + if ((t_numer < 0) === denomPositive) + return null; // No collision + + if (((s_numer > denom) === denomPositive) || ((t_numer > denom) === denomPositive)) + return null; // No collision + // Collision detected + const t = t_numer / denom; + return { x: Math.round(segm1.x1 + (t * s10_x)), y: Math.round(segm1.y1 + (t * s10_y)) }; + }; - setnormal(nx, ny, nz) { - this.nx = nx; - this.ny = ny; - this.nz = nz; - } + /* eslint-disable-next-line one-var */ + const buildPath = (xp, yp, iminus, iplus, do_close, check_rapair) => { + let cmd = '', lastx, lasty, x0, y0, isany = false, matched, x, y; + for (let i = iminus; i <= iplus; ++i) { + x = Math.round(xp[i]); + y = Math.round(yp[i]); + if (!cmd) { + cmd = `M${x},${y}`; + x0 = x; + y0 = y; + } else if ((i === iplus) && (iminus !== iplus) && (x === x0) && (y === y0)) { + if (!isany) + return ''; // all same points + cmd += 'z'; + do_close = false; + matched = true; + } else { + const dx = x - lastx, dy = y - lasty; + if (dx) { + isany = true; + cmd += dy ? `l${dx},${dy}` : `h${dx}`; + } else if (dy) { + isany = true; + cmd += `v${dy}`; + } + } - clone() { - return new Vertex(this.x, this.y, this.z, this.nx, this.ny, this.nz); - } + lastx = x; + lasty = y; + } - add(vertex) { - this.x += vertex.x; - this.y += vertex.y; - this.z += vertex.z; - return this; - } + if (!do_close || matched || !check_rapair) + return do_close ? cmd + 'z' : cmd; - subtract(vertex) { - this.x -= vertex.x; - this.y -= vertex.y; - this.z -= vertex.z; - return this; - } + // try to build path which fills area to outside borders + const points = [{ x: 0, y: 0 }, { x: handle.width, y: 0 }, { x: handle.width, y: handle.height }, { x: 0, y: handle.height }]; - // multiplyScalar( scalar ) { - // this.x *= scalar; - // this.y *= scalar; - // this.z *= scalar; - // return this; - // } + /* eslint-disable-next-line one-var */ + const get_intersect = (indx, di) => { + const segm = { x1: xp[indx], y1: yp[indx], x2: 2 * xp[indx] - xp[indx + di], y2: 2 * yp[indx] - yp[indx + di] }; + for (let i = 0; i < 4; ++i) { + const res = get_segm_intersection(segm, { x1: points[i].x, y1: points[i].y, x2: points[(i + 1) % 4].x, y2: points[(i + 1) % 4].y }); + if (res) { + res.indx = i + 0.5; + return res; + } + } + return null; + }; - // cross( vertex ) { - // let x = this.x, y = this.y, z = this.z, - // vx = vertex.x, vy = vertex.y, vz = vertex.z; - // - // this.x = y * vz - z * vy; - // this.y = z * vx - x * vz; - // this.z = x * vy - y * vx; - // - // return this; - // } + let pnt1, pnt2; + iminus--; + while ((iminus < iplus - 1) && !pnt1) + pnt1 = get_intersect(++iminus, 1); + if (!pnt1) + return ''; + iplus++; + while ((iminus < iplus - 1) && !pnt2) + pnt2 = get_intersect(--iplus, -1); + if (!pnt2) + return ''; - cross3(vx, vy, vz) { - const x = this.x, y = this.y, z = this.z; + // TODO: now side is always same direction, could be that side should be checked more precise - this.x = y * vz - z * vy; - this.y = z * vx - x * vz; - this.z = x * vy - y * vx; + let dd = buildPath(xp, yp, iminus, iplus), + indx = pnt2.indx; + const side = 1, step = side * 0.5; - return this; - } + dd += `L${pnt2.x},${pnt2.y}`; + while (Math.abs(indx - pnt1.indx) > 0.1) { + indx = Math.round(indx + step) % 4; + dd += `L${points[indx].x},${points[indx].y}`; + indx += step; + } + return dd + `L${pnt1.x},${pnt1.y}z`; + }; - normalize() { - const length = Math.sqrt(this.x**2 + this.y**2 + this.z**2); + if (o.Contour === 14) { + this.appendPath(`M0,0h${handle.width}v${handle.height}h${-handle.width}z`) + .style('fill', palette.calcColor(0, levels.length)); + } - this.x /= length; - this.y /= length; - this.z /= length; + buildHist2dContour(this.getHisto(), handle, levels, palette, (colindx, xp, yp, iminus, iplus, ipoly) => { + const icol = palette.getColor(colindx); + let fillcolor = icol, lineatt; - return this; - } + switch (o.Contour) { + case 1: + break; + case 11: + fillcolor = 'none'; + lineatt = this.createAttLine({ color: icol, std: false }); + break; + case 12: + fillcolor = 'none'; + lineatt = this.createAttLine({ color: 1, style: (ipoly % 5 + 1), width: 1, std: false }); + break; + case 13: + fillcolor = 'none'; + lineatt = this.lineatt; + break; + } - dot(vertex) { - return this.x*vertex.x + this.y*vertex.y + this.z*vertex.z; - } + const dd = buildPath(xp, yp, iminus, iplus, fillcolor !== 'none', true); + if (dd) { + this.appendPath(dd) + .style('fill', fillcolor) + .call(lineatt ? lineatt.func : () => {}); + } + }); - diff(vertex) { - const dx = (this.x - vertex.x), - dy = (this.y - vertex.y), - dz = (this.z - vertex.z), - len2 = this.x**2 + this.y**2 + this.z**2; + handle.hide_only_zeros = true; // text drawing suppress only zeros - return (dx**2 + dy**2 + dz**2) / (len2 > 0 ? len2 : 1e-10); + return handle; } -/* - lerp( a, t ) { - this.add( - a.clone().subtract( this ).multiplyScalar( t ) - ); - - this.normal.add( - a.normal.clone().sub( this.normal ).multiplyScalar( t ) - ); - - //this.uv.add( - // a.uv.clone().sub( this.uv ).multiplyScalar( t ) - //); - - return this; - }; - - interpolate( other, t ) { - return this.clone().lerp( other, t ); - }; -*/ - - interpolate(a, t) { - const 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); + getGrNPoints(gr) { + const x = gr.fX, y = gr.fY; + let npnts = gr.fNpoints; + if ((npnts > 2) && (x[0] === x[npnts - 1]) && (y[0] === y[npnts - 1])) + npnts--; + return npnts; } - applyMatrix4(m) { - // input: Matrix4 affine matrix - - let x = this.x, y = this.y, z = this.z; - const e = m.elements; + /** @summary Create single graph path from TH2PolyBin */ + createPolyGr(funcs, gr, textbin) { + let grcmd = '', acc_x = 0, acc_y = 0; - 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]; + const x = gr.fX, y = gr.fY, flush = () => { + if (acc_x) { + grcmd += 'h' + acc_x; + acc_x = 0; + } + if (acc_y) { + grcmd += 'v' + acc_y; + acc_y = 0; + } + }, addPoint = (x1, y1, x2, y2) => { + const len = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2); + textbin.sumx += (x1 + x2) * len / 2; + textbin.sumy += (y1 + y2) * len / 2; + textbin.sum += len; + }, npnts = this.getGrNPoints(gr); - x = this.nx; y = this.ny; z = this.nz; + if (npnts < 2) + return ''; - 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; + const grx0 = Math.round(funcs.grx(x[0])), + gry0 = Math.round(funcs.gry(y[0])); + let grx = grx0, gry = gry0; - return this; - } + for (let n = 1; n < npnts; ++n) { + const nextx = Math.round(funcs.grx(x[n])), + nexty = Math.round(funcs.gry(y[n])), + dx = nextx - grx, + dy = nexty - gry; -} // class Vertex + if (textbin) + addPoint(grx, gry, nextx, nexty); + if (dx || dy) { + if (dx === 0) { + if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) + flush(); + acc_y += dy; + } else if (dy === 0) { + if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) + flush(); + acc_x += dx; + } else { + flush(); + grcmd += `l${dx},${dy}`; + } + grx = nextx; + gry = nexty; + } + } -class Polygon { + if (textbin) + addPoint(grx, gry, grx0, gry0); + flush(); - constructor(vertices, parent, more) { - this.vertices = vertices || []; - this.nsign = 1; - if (parent) - this.copyProperties(parent, more); - else if (this.vertices.length > 0) - this.calculateProperties(); + return grcmd ? `M${grx0},${gry0}` + grcmd + 'z' : ''; } - copyProperties(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; + /** @summary Create path for complete TH2PolyBin */ + createPolyBin(funcs, bin) { + const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; + let cmd = ''; + for (let k = 0; k < arr.length; ++k) + cmd += this.createPolyGr(funcs, arr[k]); + return cmd; } - calculateProperties(force) { - if (this.normal && !force) return; - - const a = this.vertices[0], - b = this.vertices[1], - c = this.vertices[2]; - - this.nsign = 1; + /** @summary draw TH2Poly bins */ + async drawPolyBins() { + const histo = this.getHisto(), + o = this.getOptions(), + funcs = this.getHistGrFuncs(), + draw_colors = o.Color || (!o.Line && !o.Fill && !o.Text && !o.Mark), + draw_lines = o.Line || (o.Text && !draw_colors), + draw_fill = o.Fill && !draw_colors, + draw_mark = o.Mark, + h = funcs.getFrameHeight(), + textbins = [], + len = histo.fBins.arr.length; + let colindx, cmd, + full_cmd = '', allmarkers_cmd = '', + bin, item, i, gr0 = null, + lineatt_match = draw_lines, + fillatt_match = draw_fill, + markatt_match = draw_mark; - // this.normal = b.clone().subtract(a).cross(c.clone().subtract(a)).normalize(); + // force recalculations of contours + // use global coordinates + this.maxbin = this.gmaxbin; + this.minbin = this.gminbin; + this.minposbin = this.gminposbin; - this.normal = new Vertex(b.x - a.x, b.y - a.y, b.z - a.z, 0, 0, 0).cross3(c.x - a.x, c.y - a.y, c.z - a.z).normalize(); + const cntr = draw_colors ? this.getContour(true) : null, + palette = cntr ? this.getHistPalette() : null, + rejectBin = bin2 => { + // check if bin outside visible range + return ((bin2.fXmin > funcs.scale_xmax) || (bin2.fXmax < funcs.scale_xmin) || + (bin2.fYmin > funcs.scale_ymax) || (bin2.fYmax < funcs.scale_ymin)); + }; - this.w = this.normal.dot(a); - return this; - } + // check if similar fill attributes + for (i = 0; i < len; ++i) { + bin = histo.fBins.arr[i]; + if (rejectBin(bin)) + continue; - clone() { - const vertice_count = this.vertices.length, - vertices = []; + const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; + for (let k = 0; k < arr.length; ++k) { + const gr = arr[k]; + if (!gr0) { + gr0 = gr; + continue; + } + if (lineatt_match && ((gr0.fLineColor !== gr.fLineColor) || (gr0.fLineWidth !== gr.fLineWidth) || (gr0.fLineStyle !== gr.fLineStyle))) + lineatt_match = false; + if (fillatt_match && ((gr0.fFillColor !== gr.fFillColor) || (gr0.fFillStyle !== gr.fFillStyle))) + fillatt_match = false; + if (markatt_match && ((gr0.fMarkerColor !== gr.fMarkerColor) || (gr0.fMarkerStyle !== gr.fMarkerStyle) || (gr0.fMarkerSize !== gr.fMarkerSize))) + markatt_match = false; + } + if (!lineatt_match && !fillatt_match && !markatt_match) + break; + } - for (let i = 0; i < vertice_count; ++i) - vertices.push(this.vertices[i].clone()); + // do not try color draw optimization as with plain th2 while + // bins are not rectangular and drawings artifacts are nasty + // therefore draw each bin separately when doing color draw + const lineatt0 = lineatt_match && gr0 ? this.createAttLine(gr0) : null, + fillatt0 = fillatt_match && gr0 ? this.createAttFill(gr0) : null, + markeratt0 = markatt_match && gr0 ? this.createAttMarker({ attr: gr0, style: o.MarkStyle, std: false }) : null, + optimize_draw = !draw_colors && (draw_lines ? lineatt_match : true) && (draw_fill ? fillatt_match : true); - return new Polygon(vertices, this); - } + // draw bins + for (i = 0; i < len; ++i) { + bin = histo.fBins.arr[i]; + if (rejectBin(bin)) + continue; - flip() { - /// normal is not changed, only sign variable - // this.normal.multiplyScalar( -1 ); - // this.w *= -1; + const draw_bin = bin.fContent || o.Zero, + arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; - this.nsign *= -1; + colindx = draw_colors && draw_bin ? cntr.getPaletteIndex(palette, bin.fContent) : null; - this.vertices.reverse(); + const textbin = o.Text && draw_bin ? { bin, sumx: 0, sumy: 0, sum: 0 } : null; - return this; - } + for (let k = 0; k < arr.length; ++k) { + const gr = arr[k]; + if (markeratt0) { + const npnts = this.getGrNPoints(gr); + for (let n = 0; n < npnts; ++n) + allmarkers_cmd += markeratt0.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); + } - classifyVertex(vertex) { - const side_value = this.nsign * (this.normal.dot(vertex) - this.w); + cmd = this.createPolyGr(funcs, gr, textbin); + if (!cmd) + continue; - if (side_value < -EPSILON) return BACK; - if (side_value > EPSILON) return FRONT; - return COPLANAR; - } + if (optimize_draw) + full_cmd += cmd; + else if ((colindx !== null) || draw_fill || draw_lines) { + item = this.appendPath(cmd); + if (draw_colors && (colindx !== null)) + item.style('fill', palette.getColor(colindx)); + else if (draw_fill) + item.call(this.createAttFill(gr).func); + else + item.style('fill', 'none'); + if (draw_lines) + item.call(this.createAttLine(gr).func); + } + } // loop over graphs - classifySide(polygon) { - let num_positive = 0, num_negative = 0; - const vertice_count = polygon.vertices.length; + if (textbin?.sum) + textbins.push(textbin); + } // loop over bins - for (let i = 0; i < vertice_count; ++i) { - const classification = this.classifyVertex(polygon.vertices[i]); - if (classification === FRONT) - ++num_positive; - else if (classification === BACK) - ++num_negative; + if (optimize_draw) { + item = this.appendPath(full_cmd); + if (draw_fill && fillatt0) + item.call(fillatt0.func); + else + item.style('fill', 'none'); + if (draw_lines && lineatt0) + item.call(lineatt0.func); } - 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 (markeratt0 && !markeratt0.empty() && allmarkers_cmd) { + this.appendPath(allmarkers_cmd) + .call(markeratt0.func); + } else if (draw_mark) { + for (i = 0; i < len; ++i) { + bin = histo.fBins.arr[i]; + if (rejectBin(bin)) + continue; - splitPolygon(polygon, coplanar_front, coplanar_back, front, back) { - const classification = this.classifySide(polygon); + const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; - if (classification === COPLANAR) + for (let k = 0; k < arr.length; ++k) { + const gr = arr[k], npnts = this.getGrNPoints(gr), + markeratt = this.createAttMarker({ attr: gr, style: o.MarkStyle, std: false }); + if (!npnts || markeratt.empty()) + continue; - ((this.nsign * polygon.nsign * this.normal.dot(polygon.normal) > 0) ? coplanar_front : coplanar_back).push(polygon); + let cmdm = ''; + for (let n = 0; n < npnts; ++n) + cmdm += markeratt.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); - else if (classification === FRONT) + this.appendPath(cmdm) + .call(markeratt.func); + } // loop over graphs + } // loop over bins + } - front.push(polygon); + let pr = Promise.resolve(); - else if (classification === BACK) + if (textbins.length) { + const color = this.getColor(histo.fMarkerColor), + rotate = -1 * o.TextAngle, + text_g = this.getG().append('svg:g').attr('class', 'th2poly_text'), + text_size = ((histo.fMarkerSize !== 1) && rotate) ? Math.round(0.02 * h * histo.fMarkerSize) : 12; - back.push(polygon); + pr = this.startTextDrawingAsync(42, text_size, text_g, text_size).then(() => { + for (i = 0; i < textbins.length; ++i) { + const textbin = textbins[i]; - else { - const vertice_count = polygon.vertices.length, - nnx = this.normal.x, - nny = this.normal.y, - nnz = this.normal.z, - f = [], b = []; - let i, j, ti, tj, vi, vj, t, v; + bin = textbin.bin; - 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 (textbin.sum > 0) { + textbin.midx = Math.round(textbin.sumx / textbin.sum); + textbin.midy = Math.round(textbin.sumy / textbin.sum); + } else { + textbin.midx = Math.round(funcs.grx((bin.fXmin + bin.fXmax) / 2)); + textbin.midy = Math.round(funcs.gry((bin.fYmin + bin.fYmax) / 2)); + } - 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 ); + let text; - 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)); + if (!o.TextKind) + text = (Math.round(bin.fContent) === bin.fContent) ? bin.fContent.toString() : floatToString(bin.fContent, gStyle.fPaintTextFormat); + else { + text = bin.fPoly?.fName; + if (!text || (text === 'Graph')) + text = bin.fNumber.toString(); + } - v = vi.interpolate(vj, t); - f.push(v); - b.push(v); + this.drawText({ align: 22, x: textbin.midx, y: textbin.midy, rotate, text, color, latex: 0, draw_g: text_g }); } - } - // 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, polygon, true)); - if (b.length >= 3) back.push(new Polygon(b, polygon, true)); + return this.finishTextDrawing(text_g, true); + }); } + + return pr.then(() => { return { poly: true }; }); } -} // class Polygon + /** @summary Draw TH2 bins as text */ + async drawBinsText(handle) { + if (!handle) + handle = this.prepareDraw({ rounding: false }); + + const histo = this.getHisto(), + o = this.getOptions(), + test_cutg = o.cutg, + color = this.getColor(histo.fMarkerColor), + rotate = -1 * o.TextAngle, + draw_g = this.getG().append('svg:g').attr('class', 'th2_text'), + show_err = (o.TextKind === 'E'), + latex = (show_err && !o.TextLine) ? 1 : 0, + text_offset = histo.fBarOffset * 1e-3, + text_size = ((histo.fMarkerSize === 1) || !rotate) ? 20 : Math.round(0.02 * histo.fMarkerSize * handle.height); + + return this.startTextDrawingAsync(42, text_size, draw_g, text_size).then(() => { + for (let i = handle.i1; i < handle.i2; ++i) { + const binw = handle.grx[i + 1] - handle.grx[i]; + for (let j = handle.j1; j < handle.j2; ++j) { + const binz = histo.getBinContent(i + 1, j + 1); + if ((binz === 0) && !o.ShowEmpty) + continue; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; -class Node { + const binh = handle.gry[j] - handle.gry[j + 1]; - constructor(polygons, nodeid) { - this.polygons = []; - this.front = this.back = undefined; + let text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); - if (!polygons) return; + if (show_err) { + const errs = this.getBinErrors(histo, histo.getBin(i + 1, j + 1), binz); + if (errs.poisson) { + const lble = `-${floatToString(errs.low, gStyle.fPaintTextFormat)} +${floatToString(errs.up, gStyle.fPaintTextFormat)}`; + if (o.TextLine) + text += ' ' + lble; + else + text = `#splitmline{${text}}{${lble}}`; + } else { + const lble = (errs.up === Math.round(errs.up)) ? errs.up.toString() : floatToString(errs.up, gStyle.fPaintTextFormat); + if (o.TextLine) + text += '\xB1' + lble; + else + text = `#splitmline{${text}}{#pm${lble}}`; + } + } - this.divider = polygons[0].clone(); + let x, y, width, height; - const polygon_count = polygons.length, - front = [], back = []; + if (rotate) { + x = Math.round(handle.grx[i] + binw * 0.5); + y = Math.round(handle.gry[j + 1] + binh * (0.5 + text_offset)); + width = height = 0; + } else { + x = Math.round(handle.grx[i] + binw * 0.1); + y = Math.round(handle.gry[j + 1] + binh * (0.1 + text_offset)); + width = Math.round(binw * 0.8); + height = Math.round(binh * 0.8); + } - for (let i = 0; i < polygon_count; ++i) { - if (nodeid !== undefined) { - polygons[i].id = nodeid++; - delete polygons[i].parent; + this.drawText({ align: 22, x, y, width, height, rotate, text, color, latex, draw_g }); + } } - // by difinition polygon should be COPLANAR for itself - if (i === 0) - this.polygons.push(polygons[0]); - else - this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); - } + handle.hide_only_zeros = true; // text drawing suppress only zeros - if (nodeid !== undefined) this.maxnodeid = nodeid; + return this.finishTextDrawing(draw_g, true); + }).then(() => handle); + } - if (front.length > 0) - this.front = new Node(front); + /** @summary Draw TH2 bins as arrows */ + drawBinsArrow() { + const histo = this.getHisto(), + o = this.getOptions(), + test_cutg = o.cutg, + handle = this.prepareDraw({ rounding: false }), + cntr = o.Color ? this.getContour() : null, + palette = o.Color ? this.getHistPalette() : null, + scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1]) / (handle.i2 - handle.i1 + 1) / 2, + scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1]) / (handle.j2 - handle.j1 + 1) / 2, + makeLine = (dx, dy) => { return dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); }, + entries = []; + let dn = 1e-30, dx, dy, xc, yc, plain = '', + dxn, dyn, x1, x2, y1, y2; - if (back.length > 0) - this.back = new Node(back); - } + for (let loop = 0; loop < 2; ++loop) { + for (let i = handle.i1; i < handle.i2; ++i) { + for (let j = handle.j1; j < handle.j2; ++j) { + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; - // isConvex(polygons) { - // let 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; - // } + const bincont = histo.getBinContent(i + 1, j + 1); - build(polygons) { - const polygon_count = polygons.length, - front = [], back = []; - let first = 0; + if (i === handle.i1) + dx = histo.getBinContent(i + 2, j + 1) - bincont; + else if (i === handle.i2 - 1) + dx = bincont - histo.getBinContent(i, j + 1); + else + dx = 0.5 * (histo.getBinContent(i + 2, j + 1) - histo.getBinContent(i, j + 1)); - if (!this.divider) { - this.divider = polygons[0].clone(); - this.polygons.push(polygons[0]); - first = 1; - } + if (j === handle.j1) + dy = histo.getBinContent(i + 1, j + 2) - bincont; + else if (j === handle.j2 - 1) + dy = bincont - histo.getBinContent(i + 1, j); + else + dy = 0.5 * (histo.getBinContent(i + 1, j + 2) - histo.getBinContent(i + 1, j)); - for (let i = first; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + if (loop === 0) + dn = Math.max(dn, Math.abs(dx), Math.abs(dy)); + else { + xc = (handle.grx[i] + handle.grx[i + 1]) / 2; + yc = (handle.gry[j] + handle.gry[j + 1]) / 2; + dxn = scale_x * dx / dn; + dyn = scale_y * dy / dn; + x1 = xc - dxn; + x2 = xc + dxn; + y1 = yc - dyn; + y2 = yc + dyn; + dx = Math.round(x2 - x1); + dy = Math.round(y2 - y1); - if (front.length > 0) { - if (!this.front) this.front = new Node(); - this.front.build(front); + if (dx || dy) { + let cmd = `M${Math.round(x1)},${Math.round(y1)}${makeLine(dx, dy)}`; + + if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { + const anr = Math.sqrt(9 / (dx ** 2 + dy ** 2)), + si = Math.round(anr * (dx + dy)), + co = Math.round(anr * (dx - dy)); + if (si || co) + cmd += `m${-si},${co}${makeLine(si, -co)}${makeLine(-co, -si)}`; + } + + if (palette && cntr) { + const colindx = cntr.getPaletteIndex(palette, bincont); + if (colindx !== null) { + const entry = entries[colindx]; + if (!entry) + entries[colindx] = { path: cmd }; + else + entry.path += cmd; + } + } else + plain += cmd; + } + } + } + } } - if (back.length > 0) { - if (!this.back) this.back = new Node(); - this.back.build(back); + if (plain) { + this.appendPath(plain) + .style('fill', 'none') + .call(this.lineatt.func); } - } - collectPolygons(arr) { - if (arr === undefined) - arr = []; - const len = this.polygons.length; - for (let i = 0; i < len; ++i) - arr.push(this.polygons[i]); - this.front?.collectPolygons(arr); - this.back?.collectPolygons(arr); - return arr; - } + entries.forEach((entry, colindx) => { + if (entry) { + const col0 = this.lineatt.color; + this.lineatt.color = palette.getColor(colindx); + this.appendPath(entry.path) + .attr('fill', 'none') + .call(this.lineatt.func); + this.lineatt.color = col0; + } + }); - numPolygons() { - return this.polygons.length + (this.front?.numPolygons() || 0) + (this.back?.numPolygons() || 0); + return handle; } - clone() { - const node = new Node(); - - node.divider = this.divider?.clone(); - node.polygons = this.polygons.map(polygon => polygon.clone()); - node.front = this.front?.clone(); - node.back = this.back?.clone(); - - return node; - } + /** @summary Draw TH2 bins as boxes */ + drawBinsBox() { + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw({ rounding: false, zrange: true }), + absmax = Math.max(Math.abs(handle.zmin), Math.abs(handle.zmax)), + absmin = Math.max(0, handle.zmin), + pad = this.getPadPainter().getRootPad(true), + test_cutg = o.cutg; - invert() { - const polygon_count = this.polygons.length; + let i, j, binz, absz, res = '', cross = '', btn1 = '', btn2 = '', + zdiff, dgrx, dgry, xx, yy, ww, hh, xyfactor, + uselogz = false, logmin = 0; - for (let i = 0; i < polygon_count; ++i) - this.polygons[i].flip(); + if ((pad?.fLogv ?? pad?.fLogz) && (absmax > 0)) { + uselogz = true; + const logmax = Math.log(absmax); + if (absmin > 0) + logmin = Math.log(absmin); + else if ((handle.zminpos >= 1) && (handle.zminpos < 100)) + logmin = Math.log(0.7); + else + logmin = (handle.zminpos > 0) ? Math.log(0.7 * handle.zminpos) : logmax - 10; + if (logmin >= logmax) + logmin = logmax - 10; + xyfactor = 1.0 / (logmax - logmin); + } else + xyfactor = 1.0 / (absmax - absmin); - this.divider.flip(); - if (this.front) this.front.invert(); - if (this.back) this.back.invert(); - const temp = this.front; - this.front = this.back; - this.back = temp; + // now start build + for (i = handle.i1; i < handle.i2; ++i) { + for (j = handle.j1; j < handle.j2; ++j) { + binz = histo.getBinContent(i + 1, j + 1); + absz = Math.abs(binz); + if ((absz === 0) || (absz < absmin)) + continue; - return this; - } + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; - clipPolygons(polygons) { - if (!this.divider) return polygons.slice(); + zdiff = uselogz ? ((absz > 0) ? Math.log(absz) - logmin : 0) : (absz - absmin); + // area of the box should be proportional to absolute bin content + zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor))); + // avoid oversized bins + if (zdiff < 0) + zdiff = 0; - const polygon_count = polygons.length; - let front = [], back = []; + ww = handle.grx[i + 1] - handle.grx[i]; + hh = handle.gry[j] - handle.gry[j + 1]; - for (let i = 0; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], front, back, front, back); + dgrx = zdiff * ww; + dgry = zdiff * hh; - if (this.front) front = this.front.clipPolygons(front); - if (this.back) back = this.back.clipPolygons(back); - else back = []; + xx = Math.round(handle.grx[i] + dgrx); + yy = Math.round(handle.gry[j + 1] + dgry); - return front.concat(back); - } + ww = Math.max(Math.round(ww - 2 * dgrx), 1); + hh = Math.max(Math.round(hh - 2 * dgry), 1); - clipTo(node) { - this.polygons = node.clipPolygons(this.polygons); - if (this.front) this.front.clipTo(node); - if (this.back) this.back.clipTo(node); - } + res += `M${xx},${yy}v${hh}h${ww}v${-hh}z`; - } // class Node + if ((binz < 0) && (o.BoxStyle === 10)) + cross += `M${xx},${yy}l${ww},${hh}m0,${-hh}l${-ww},${hh}`; + if ((o.BoxStyle === 11) && (ww > 5) && (hh > 5)) { + const arr = getBoxDecorations(xx, yy, ww, hh, binz < 0 ? -1 : 1, Math.round(ww * 0.1), Math.round(hh * 0.1)); + btn1 += arr[0]; + btn2 += arr[1]; + } + } + } -function createBufferGeometry(polygons) { - const polygon_count = polygons.length; - let i, j, buf_size = 0; + if (res) { + const elem = this.appendPath(res).call(this.fillatt.func); + if ((o.BoxStyle !== 11) && this.fillatt.empty()) + elem.call(this.lineatt.func); + } - for (i = 0; i < polygon_count; ++i) - buf_size += (polygons[i].vertices.length - 2) * 9; + if (btn1 && this.fillatt.hasColor()) { + this.appendPath(btn1) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); + } - const positions_buf = new Float32Array(buf_size), - normals_buf = new Float32Array(buf_size); - let iii = 0, polygon; + if (btn2) { + this.appendPath(btn2) + .call(this.fillatt.func) + .style('fill', !this.fillatt.hasColor() ? 'red' : rgb(this.fillatt.color).darker(0.5).formatRgb()); + } - function CopyVertex(vertex) { - positions_buf[iii] = vertex.x; - positions_buf[iii+1] = vertex.y; - positions_buf[iii+2] = vertex.z; + if (cross) { + const elem = this.appendPath(cross).style('fill', 'none'); + if (!this.lineatt.empty()) + elem.call(this.lineatt.func); + else + elem.style('stroke', 'black'); + } - 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; + return handle; } - 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]); - } - } + /** @summary Draw histogram bins as candle plot */ + drawBinsCandle() { + const kNoOption = 0, + kBox = 1, + kMedianLine = 10, + kMedianNotched = 20, + kMedianCircle = 30, + kMeanLine = 100, + kMeanCircle = 300, + kWhiskerAll = 1000, + kWhisker15 = 2000, + kAnchor = 10000, + kPointsOutliers = 100000, + kPointsAll = 200000, + kPointsAllScat = 300000, + kHistoLeft = 1000000, + kHistoRight = 2000000, + kHistoViolin = 3000000, + kHistoZeroIndicator = 10000000, + kHorizontal = 100000000, + fallbackCandle = kBox + kMedianLine + kMeanCircle + kWhiskerAll + kAnchor, + fallbackViolin = kMeanCircle + kWhiskerAll + kHistoViolin + kHistoZeroIndicator; - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(positions_buf, 3)); - geometry.setAttribute('normal', new BufferAttribute(normals_buf, 3)); + let fOption = kNoOption; - // geometry.computeVertexNormals(); - return geometry; -} + const o = this.getOptions(), isOption = opt => { + let mult = 1; + while (opt >= mult) + mult *= 10; + mult /= 10; + return Math.floor(fOption / mult) % 10 === Math.floor(opt / mult); + }, parseOption = (opt, is_candle) => { + let direction = '', preset = '', res = kNoOption; + const c0 = opt[0], c1 = opt[1]; + if (c0 >= 'A' && c0 <= 'Z') + direction = c0; + if (c0 >= '1' && c0 <= '9') + preset = c0; + if (c1 >= 'A' && c1 <= 'Z' && preset) + direction = c1; + if (c1 >= '1' && c1 <= '9' && direction) + preset = c1; -class Geometry { + if (is_candle) { + switch (preset) { + case '1': res += fallbackCandle; break; + case '2': res += kBox + kMeanLine + kMedianLine + kWhisker15 + kAnchor + kPointsOutliers; break; + case '3': res += kBox + kMeanCircle + kMedianLine + kWhisker15 + kAnchor + kPointsOutliers; break; + case '4': res += kBox + kMeanCircle + kMedianNotched + kWhisker15 + kAnchor + kPointsOutliers; break; + case '5': res += kBox + kMeanLine + kMedianLine + kWhisker15 + kAnchor + kPointsAll; break; + case '6': res += kBox + kMeanCircle + kMedianLine + kWhisker15 + kAnchor + kPointsAllScat; break; + default: res += fallbackCandle; + } + } else { + switch (preset) { + case '1': res += fallbackViolin; break; + case '2': res += kMeanCircle + kWhisker15 + kHistoViolin + kHistoZeroIndicator + kPointsOutliers; break; + default: res += fallbackViolin; + } + } - constructor(geometry, transfer_matrix, nodeid, flippedMesh) { - // Convert BufferGeometry to ThreeBSP + const l = opt.indexOf('('), r = opt.lastIndexOf(')'); + if ((l >= 0) && (r > l + 1)) + res = parseInt(opt.slice(l + 1, r)); - if (geometry instanceof 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 Matrix4; - return this; - } else if (geometry instanceof BufferGeometry) { - const pos_buf = geometry.getAttribute('position').array, - norm_buf = geometry.getAttribute('normal').array, - polygons = []; - let polygon, vert1, vert2, vert3; + fOption = res; - for (let i=0; i < pos_buf.length; i+=9) { - polygon = new Polygon(); + if ((direction === 'Y' || direction === 'H') && !isOption(kHorizontal)) + fOption += kHorizontal; + }, extractQuantiles = (xx, proj, prob) => { + let integral = 0, cnt = 0, sum1 = 0; + const res = { max: 0, first: -1, last: -1, entries: 0 }; - 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); + for (let j = 0; j < proj.length; ++j) { + if (proj[j] > 0) { + res.max = Math.max(res.max, proj[j]); + if (res.first < 0) + res.first = j; + res.last = j; + } + integral += proj[j]; + sum1 += proj[j] * (xx[j] + xx[j + 1]) / 2; + } + if (integral <= 0) + return null; - 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); + res.entries = integral; + res.mean = sum1 / integral; + res.quantiles = new Array(prob.length); + res.indx = new Array(prob.length); - 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 (let j = 0, sum = 0, nextv = 0; j < proj.length; ++j) { + const v = nextv; + let x = xx[j]; - if (flippedMesh) polygon.vertices.push(vert1, vert3, vert2); - else polygon.vertices.push(vert1, vert2, vert3); + // special case - flat integral with const value + if ((v === prob[cnt]) && (proj[j] === 0) && (v < 0.99)) { + while ((proj[j] === 0) && (j < proj.length)) + j++; + x = (xx[j] + x) / 2; // this will be mid value + } - polygon.calculateProperties(true); - polygons.push(polygon); + sum += proj[j]; + nextv = sum / integral; + while ((prob[cnt] >= v) && (prob[cnt] < nextv)) { + res.indx[cnt] = j; + res.quantiles[cnt] = x + ((prob[cnt] - v) / (nextv - v)) * (xx[j + 1] - x); + if (cnt++ === prob.length) + return res; + x = xx[j]; + } } - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; - } else if (geometry.polygons && (geometry.polygons[0] instanceof Polygon)) { - const polygons = geometry.polygons; + while (cnt < prob.length) { + res.indx[cnt] = proj.length - 1; + res.quantiles[cnt++] = xx.at(-1); + } - for (let i = 0; i < polygons.length; ++i) { - const polygon = polygons[i]; - if (transfer_matrix) { - const new_vertices = []; + return res; + }; - for (let n = 0; n < polygon.vertices.length; ++n) - new_vertices.push(polygon.vertices[n].clone().applyMatrix4(transfer_matrix)); + if (o.Candle) + parseOption(o.Candle, true); + else if (o.Violin) + parseOption(o.Violin, false); - polygon.vertices = new_vertices; - } + const histo = this.getHisto(), + handle = this.prepareDraw(), + cp = this.getCanvPainter(), + funcs = this.getHistGrFuncs(), + swapXY = isOption(kHorizontal); + let scaledViolin = gStyle.fViolinScaled, + scaledCandle = gStyle.fCandleScaled, + maxContent = 0, + markers = '', cmarkers = '', attrcmarkers = null; - polygon.calculateProperties(transfer_matrix); + if (o.Scaled !== null) + scaledViolin = scaledCandle = o.Scaled; + else if (cp?.online_canvas) ; else if (histo.fTitle.indexOf('unscaled') >= 0) + scaledViolin = scaledCandle = false; + else if (histo.fTitle.indexOf('scaled') >= 0) + scaledViolin = scaledCandle = true; + + if (scaledViolin && (isOption(kHistoRight) || isOption(kHistoLeft) || isOption(kHistoViolin))) { + for (let i = 0; i < this.nbinsx; ++i) { + for (let j = 0; j < this.nbinsy; ++j) + maxContent = Math.max(maxContent, histo.getBinContent(i + 1, j + 1)); } + } - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; - } else - throw Error('ThreeBSP: Given geometry is unsupported'); + const make_path = (...a) => { + if (a[1] === 'array') + a = a[0]; + const l = a.length; + let i = 2, xx = a[0], yy = a[1], + res = swapXY ? `M${yy},${xx}` : `M${xx},${yy}`; + while (i < l) { + switch (a[i]) { + case 'Z': + return res + 'z'; + case 'V': + if (yy !== a[i + 1]) { + res += (swapXY ? 'h' : 'v') + (a[i + 1] - yy); + yy = a[i + 1]; + } + break; + case 'H': + if (xx !== a[i + 1]) { + res += (swapXY ? 'v' : 'h') + (a[i + 1] - xx); + xx = a[i + 1]; + } + break; + default: + res += swapXY ? `l${a[i + 1] - yy},${a[i] - xx}` : `l${a[i] - xx},${a[i + 1] - yy}`; + xx = a[i]; + yy = a[i + 1]; + } + i += 2; + } + return res; + }, make_marker = (x, y) => { + if (!markers) { + const mw = gStyle.fCandleCrossLineWidth ?? 1; + this.createAttMarker({ attr: histo, style: isOption(kPointsAllScat) ? 0 : (mw === 1 ? 5 : 18 * mw + 16) }); + this.markeratt.resetPos(); + } + markers += swapXY ? this.markeratt.create(y, x) : this.markeratt.create(x, y); + }, make_cmarker = (x, y) => { + if (!attrcmarkers) { + const mw = gStyle.fCandleCircleLineWidth ?? 1; + attrcmarkers = this.createAttMarker({ attr: histo, style: (mw === 1 ? 24 : 18 * mw + 17), std: false }); + attrcmarkers.resetPos(); + } + cmarkers += swapXY ? attrcmarkers.create(y, x) : attrcmarkers.create(x, y); + }; + if (histo.fMarkerColor === 1) + histo.fMarkerColor = histo.fLineColor; - const polygons = [], nfaces = geometry.faces.length; - let face, polygon, vertex, normal, useVertexNormals; + handle.candle = []; // array of drawn points - for (let i = 0; i < nfaces; ++i) { - face = geometry.faces[i]; - normal = face.normal; - // faceVertexUvs = geometry.faceVertexUvs[0][i]; - polygon = new Polygon(); + let xx, bars = '', lines = '', dashed_lines = '', hists = '', hlines = '', + proj, maxIntegral = 0; - useVertexNormals = face.vertexNormals && (face.vertexNormals.length === 3); + // Determining the quintiles + const wRange = gStyle.fCandleWhiskerRange, bRange = gStyle.fCandleBoxRange, + prob = [(wRange >= 1) ? 1e-15 : 0.5 - wRange / 2.0, + (bRange >= 1) ? 1e-14 : 0.5 - bRange / 2.0, + 0.5, + (bRange >= 1) ? 1 - 1e-14 : 0.5 + bRange / 2.0, + (wRange >= 1) ? 1 - 1e-15 : 0.5 + wRange / 2.0]; + + /* eslint-disable-next-line one-var */ + const produceCandlePoint = (bin_indx, grx_left, grx_right, xindx1, xindx2) => { + const res = extractQuantiles(xx, proj, prob); + if (!res) + return; - vertex = geometry.vertices[face.a]; - if (useVertexNormals) normal = face.vertexNormals[0]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); + const pnt = { bin: bin_indx, swapXY, fBoxDown: res.quantiles[1], fMedian: res.quantiles[2], fBoxUp: res.quantiles[3] }, + iqr = pnt.fBoxUp - pnt.fBoxDown; + let fWhiskerDown = res.quantiles[0], fWhiskerUp = res.quantiles[4]; - vertex = geometry.vertices[face.b]; - if (useVertexNormals) normal = face.vertexNormals[1]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); + if (isOption(kWhisker15)) { // Improved whisker definition, with 1.5*iqr + let pos = pnt.fBoxDown - 1.5 * iqr, indx = res.indx[1]; + while ((xx[indx] > pos) && (indx > 0)) + indx--; + while (!proj[indx]) + indx++; + fWhiskerDown = xx[indx]; // use lower edge here + pos = pnt.fBoxUp + 1.5 * iqr; indx = res.indx[3]; + while ((xx[indx] < pos) && (indx < proj.length)) + indx++; + while (!proj[indx]) + indx--; + fWhiskerUp = xx[indx + 1]; // use upper index edge here + } - vertex = geometry.vertices[face.c]; - if (useVertexNormals) normal = face.vertexNormals[2]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); + const fMean = res.mean, + fMedianErr = 1.57 * iqr / Math.sqrt(res.entries); - polygon.calculateProperties(true); - polygons.push(polygon); - } + // estimate quantiles... simple function... not so nice as GetQuantiles - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - } + // exclude points with negative y when log scale is specified + if ((fWhiskerDown <= 0) && ((swapXY && funcs.logx) || (!swapXY && funcs.logy))) + return; - subtract(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); + const w = (grx_right - grx_left); + let candleWidth, histoWidth, + center = (grx_left + grx_right) / 2 + histo.fBarOffset / 1000 * w; + if ((histo.fBarWidth > 0) && (histo.fBarWidth !== 1000)) + candleWidth = histoWidth = w * histo.fBarWidth / 1000; + else { + candleWidth = w * 0.66; + histoWidth = w * 0.8; + } - a.invert(); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - a.invert(); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } + if (scaledViolin && (maxContent > 0)) + histoWidth *= res.max / maxContent; + if (scaledCandle && (maxIntegral > 0)) + candleWidth *= res.entries / maxIntegral; - union(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); + pnt.x1 = Math.round(center - candleWidth / 2); + pnt.x2 = Math.round(center + candleWidth / 2); + center = Math.round(center); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } + const x1d = Math.round(center - candleWidth / 3), + x2d = Math.round(center + candleWidth / 3), + ff = swapXY ? funcs.grx : funcs.gry; - intersect(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); + pnt.yy1 = Math.round(ff(fWhiskerUp)); + pnt.y1 = Math.round(ff(pnt.fBoxUp)); + pnt.y0 = Math.round(ff(pnt.fMedian)); + pnt.y2 = Math.round(ff(pnt.fBoxDown)); + pnt.yy2 = Math.round(ff(fWhiskerDown)); - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.collectPolygons()); - a.invert(); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } + const y0m = Math.round(ff(fMean)), + y01 = Math.round(ff(pnt.fMedian + fMedianErr)), + y02 = Math.round(ff(pnt.fMedian - fMedianErr)); - tryToCompress(polygons) { - if (this.maxid === undefined) return; + if (isOption(kHistoZeroIndicator)) + hlines += make_path(center, Math.round(ff(xx[xindx1])), 'V', Math.round(ff(xx[xindx2]))); - const arr = []; - let parts, foundpair, - nreduce = 0, n, len = polygons.length, - p, p1, p2, i1, i2; + if (isOption(kMedianLine)) + lines += make_path(pnt.x1, pnt.y0, 'H', pnt.x2); + else if (isOption(kMedianNotched)) + lines += make_path(x1d, pnt.y0, 'H', x2d); + else if (isOption(kMedianCircle)) + make_cmarker(center, pnt.y0); - // 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] = []; + if (isOption(kMeanCircle)) + make_cmarker(center, y0m); + else if (isOption(kMeanLine)) + dashed_lines += make_path(pnt.x1, y0m, 'H', pnt.x2); - arr[p.id].push(p); - } + if (isOption(kBox)) { + if (isOption(kMedianNotched)) + bars += make_path(pnt.x1, pnt.y1, 'V', y01, x1d, pnt.y0, pnt.x1, y02, 'V', pnt.y2, 'H', pnt.x2, 'V', y02, x2d, pnt.y0, pnt.x2, y01, 'V', pnt.y1, 'Z'); + else + bars += make_path(pnt.x1, pnt.y1, 'V', pnt.y2, 'H', pnt.x2, 'V', pnt.y1, 'Z'); + } - for (n = 0; n < arr.length; ++n) { - parts = arr[n]; - if (parts === undefined) continue; + if (isOption(kAnchor)) // Draw the anchor line + lines += make_path(pnt.x1, pnt.yy1, 'H', pnt.x2) + make_path(pnt.x1, pnt.yy2, 'H', pnt.x2); - len = parts.length; + if (isOption(kWhiskerAll) && !isOption(kHistoZeroIndicator)) { // Whiskers are dashed + dashed_lines += make_path(center, pnt.y1, 'V', pnt.yy1) + make_path(center, pnt.y2, 'V', pnt.yy2); + } else if ((isOption(kWhiskerAll) && isOption(kHistoZeroIndicator)) || isOption(kWhisker15)) + lines += make_path(center, pnt.y1, 'V', pnt.yy1) + make_path(center, pnt.y2, 'V', pnt.yy2); - foundpair = (len > 1); - while (foundpair) { - foundpair = false; + if (isOption(kPointsOutliers) || isOption(kPointsAll) || isOption(kPointsAllScat)) { + // reset seed for each projection to have always same pixels + const rnd = new TRandom(bin_indx * 7521 + Math.round(res.integral)), + show_all = !isOption(kPointsOutliers), + show_scat = isOption(kPointsAllScat); + for (let ii = 0; ii < proj.length; ++ii) { + const bin_content = proj[ii], binx = (xx[ii] + xx[ii + 1]) / 2; + let marker_x = center, marker_y; - for (i1 = 0; i1 < len-1; ++i1) { - p1 = parts[i1]; - if (!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 (!bin_content) + continue; + if (!show_all && (binx >= fWhiskerDown) && (binx <= fWhiskerUp)) + continue; - if (nreduce > 0) { - polygons.splice(0, polygons.length); + for (let k = 0; k < bin_content; k++) { + if (show_scat) + marker_x = center + Math.round(((rnd.random() - 0.5) * candleWidth)); - 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]); + if ((bin_content === 1) && !show_scat) + marker_y = Math.round(ff(binx)); + else + marker_y = Math.round(ff(xx[ii] + rnd.random() * (xx[ii + 1] - xx[ii]))); + + make_marker(marker_x, marker_y); + } } } - } - } - direct_subtract(other_tree) { - const 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; - } + if ((isOption(kHistoRight) || isOption(kHistoLeft) || isOption(kHistoViolin)) && (res.max > 0) && (res.first >= 0)) { + const arr = [], scale = (swapXY ? -0.5 : 0.5) * histoWidth / res.max; - direct_union(other_tree) { - const a = this.tree, - b = other_tree.tree; + xindx1 = Math.max(xindx1, res.first); + xindx2 = Math.min(xindx2 - 1, res.last); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - return this; - } + if (isOption(kHistoRight) || isOption(kHistoViolin)) { + let prev_x = center, prev_y = Math.round(ff(xx[xindx1])); + arr.push(prev_x, prev_y); + for (let ii = xindx1; ii <= xindx2; ii++) { + const curr_x = Math.round(center + scale * proj[ii]), + curr_y = Math.round(ff(xx[ii + 1])); + if (curr_x !== prev_x) { + if (ii !== xindx1) + arr.push('V', prev_y); + arr.push('H', curr_x); + } + prev_x = curr_x; + prev_y = curr_y; + } + arr.push('V', prev_y); + } - direct_intersect(other_tree) { - const a = this.tree, - b = other_tree.tree; + if (isOption(kHistoLeft) || isOption(kHistoViolin)) { + let prev_x = center, prev_y = Math.round(ff(xx[xindx2 + 1])); + if (!arr.length) + arr.push(prev_x, prev_y); + for (let ii = xindx2; ii >= xindx1; ii--) { + const curr_x = Math.round(center - scale * proj[ii]), + curr_y = Math.round(ff(xx[ii])); + if (curr_x !== prev_x) { + if (ii !== xindx2) + arr.push('V', prev_y); + arr.push('H', curr_x); + } + prev_x = curr_x; + prev_y = curr_y; + } + arr.push('V', prev_y); + } - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.collectPolygons()); - a.invert(); - return this; - } + arr.push('H', center); // complete histogram - cut_from_plane(other_tree) { - // just cut peaces from second geometry, which just simple plane + hists += make_path(arr, 'array'); - const a = this.tree, - b = other_tree.tree; + if (!this.fillatt.empty()) + hists += 'Z'; + } - a.invert(); - b.clipTo(a); + handle.candle.push(pnt); // keep point for the tooltip + }; - return this; - } + if (swapXY) { + xx = new Array(this.nbinsx + 1); + proj = new Array(this.nbinsx); + for (let i = 0; i < this.nbinsx + 1; ++i) + xx[i] = histo.fXaxis.GetBinLowEdge(i + 1); - scale(x, y, z) { - // try to scale as BufferGeometry - const polygons = this.tree.collectPolygons(); + if (scaledCandle) { + for (let j = 0; j < this.nbinsy; ++j) { + let sum = 0; + for (let i = 0; i < this.nbinsx; ++i) + sum += histo.getBinContent(i + 1, j + 1); + maxIntegral = Math.max(maxIntegral, sum); + } + } - for (let i = 0; i < polygons.length; ++i) { - const polygon = polygons[i]; - for (let k = 0; k < polygon.vertices.length; ++k) { - const v = polygon.vertices[k]; - v.x *= x; - v.y *= y; - v.z *= z; + for (let j = handle.j1; j < handle.j2; ++j) { + for (let i = 0; i < this.nbinsx; ++i) + proj[i] = histo.getBinContent(i + 1, j + 1); + + produceCandlePoint(j, handle.gry[j + 1], handle.gry[j], handle.i1, handle.i2); } - polygon.calculateProperties(true); - } - } + } else { + xx = new Array(this.nbinsy + 1); + proj = new Array(this.nbinsy); - toPolygons() { - const polygons = this.tree.collectPolygons(); + for (let j = 0; j < this.nbinsy + 1; ++j) + xx[j] = histo.fYaxis.GetBinLowEdge(j + 1); - this.tryToCompress(polygons); + if (scaledCandle) { + for (let i = 0; i < this.nbinsx; ++i) { + let sum = 0; + for (let j = 0; j < this.nbinsy; ++j) + sum += histo.getBinContent(i + 1, j + 1); + maxIntegral = Math.max(maxIntegral, sum); + } + } - for (let i = 0; i < polygons.length; ++i) { - delete polygons[i].id; - delete polygons[i].parent; - } + // loop over visible x-bins + for (let i = handle.i1; i < handle.i2; ++i) { + for (let j = 0; j < this.nbinsy; ++j) + proj[j] = histo.getBinContent(i + 1, j + 1); - return polygons; - } + produceCandlePoint(i, handle.grx[i], handle.grx[i + 1], handle.j1, handle.j2); + } + } - toBufferGeometry() { - return createBufferGeometry(this.toPolygons()); - } + if (hlines && (histo.fFillColor > 0)) { + this.appendPath(hlines) + .style('stroke', this.getColor(histo.fFillColor)); + } - toMesh(material) { - const geometry = this.toBufferGeometry(), - mesh = new Mesh(geometry, material); + const hline_color = (isOption(kHistoZeroIndicator) && histo.fFillStyle) ? this.fillatt.color : this.lineatt.color; + if (hists && (!this.fillatt.empty() || (hline_color !== 'none'))) { + this.appendPath(hists) + .style('stroke', (hline_color !== 'none') ? hline_color : null) + .style('pointer-events', this.isBatchMode() ? null : 'visibleFill') + .call(this.fillatt.func); + } - if (this.matrix) { - mesh.position.setFromMatrixPosition(this.matrix); - mesh.rotation.setFromRotationMatrix(this.matrix); + if (bars) { + this.appendPath(bars) + .call(this.lineatt.func) + .call(this.fillatt.func); } - return mesh; - } + if (lines) { + this.appendPath(lines) + .call(this.lineatt.func) + .style('fill', 'none'); + } -} // class Geometry + if (dashed_lines) { + const dashed = this.createAttLine({ attr: histo, style: 2, std: false, color: kBlack }); + this.appendPath(dashed_lines) + .call(dashed.func) + .style('fill', 'none'); + } -/** @summary create geometry to make cut on specified axis - * @private */ -function createNormal(axis_name, pos, size) { - if (!size || (size < 10000)) size = 10000; + if (cmarkers) { + this.appendPath(cmarkers) + .call(attrcmarkers.func); + } - let vertices; + if (markers) { + this.appendPath(markers) + .call(this.markeratt.func); + } - switch (axis_name) { - case 'x': - vertices = [new Vertex(pos, -3*size, size, 1, 0, 0), - new Vertex(pos, size, -3*size, 1, 0, 0), - new Vertex(pos, size, size, 1, 0, 0)]; - break; - case 'y': - vertices = [new Vertex(-3*size, pos, size, 0, 1, 0), - new Vertex(size, pos, size, 0, 1, 0), - new Vertex(size, pos, -3*size, 0, 1, 0)]; - break; - // case 'z': - default: - vertices = [new Vertex(-3*size, size, pos, 0, 0, 1), - new Vertex(size, -3*size, pos, 0, 0, 1), - new Vertex(size, size, pos, 0, 0, 1)]; + return handle; } - const node = new Node([new Polygon(vertices)]); - - return new Geometry(node); -} - -const cfg = { - GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes - CompressComp: true // use faces compression in composite shapes -}; - -function geoCfg(name, value) { - if (value === undefined) - return cfg[name]; + /** @summary Draw TH2 bins as scatter plot */ + drawBinsScatter() { + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw({ rounding: true, pixel_density: true }), + test_cutg = o.cutg, + colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [], + scale = o.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), + rnd = new TRandom(handle.sumz); + let colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1.0; - cfg[name] = value; -} + handle.ScatterPlot = true; -const kindGeo = 0, // TGeoNode / TGeoShape - kindEve = 1, // TEveShape / TEveGeoShapeExtract - kindShape = 2, // special kind for single shape handling + if (scale * handle.sumz < 1e5) { + // one can use direct drawing of scatter plot without any patterns -/** @summary TGeo-related bits - * @private */ - geoBITS = { - kVisOverride: BIT(0), // volume's vis. attributes are overwritten - kVisNone: BIT(1), // the volume/node is invisible, as well as daughters - kVisThis: BIT(2), // this volume/node is visible - kVisDaughters: BIT(3), // all leaves are visible - kVisOneLevel: BIT(4), // first level daughters are visible (not used) - kVisStreamed: BIT(5), // true if attributes have been streamed - kVisTouched: BIT(6), // true if attributes are changed after closing geom - kVisOnScreen: BIT(7), // true if volume is visible on screen - kVisContainers: BIT(12), // all containers visible - kVisOnly: BIT(13), // just this visible - kVisBranch: BIT(14), // only a given branch visible - kVisRaytrace: BIT(15) // raytracing flag -}, + this.createAttMarker({ attr: histo }); - clTGeoBBox = 'TGeoBBox', - clTGeoArb8 = 'TGeoArb8', - clTGeoCone = 'TGeoCone', - clTGeoConeSeg = 'TGeoConeSeg', - clTGeoTube = 'TGeoTube', - clTGeoTubeSeg = 'TGeoTubeSeg', - clTGeoCtub = 'TGeoCtub', - clTGeoTrd1 = 'TGeoTrd1', - clTGeoTrd2 = 'TGeoTrd2', - clTGeoPara = 'TGeoPara', - clTGeoParaboloid = 'TGeoParaboloid', - clTGeoPcon = 'TGeoPcon', - clTGeoPgon = 'TGeoPgon', - clTGeoShapeAssembly = 'TGeoShapeAssembly', - clTGeoSphere = 'TGeoSphere', - clTGeoTorus = 'TGeoTorus', - clTGeoXtru = 'TGeoXtru', - clTGeoTrap = 'TGeoTrap', - clTGeoGtra = 'TGeoGtra', - clTGeoEltu = 'TGeoEltu', - clTGeoHype = 'TGeoHype', - clTGeoCompositeShape = 'TGeoCompositeShape', - clTGeoHalfSpace = 'TGeoHalfSpace', - clTGeoScaledShape = 'TGeoScaledShape'; + this.markeratt.resetPos(); -/** @summary Test fGeoAtt bits - * @private */ -function testGeoBit(volume, f) { - const att = volume.fGeoAtt; - return att === undefined ? false : ((att & f) !== 0); -} + let path = ''; + for (i = handle.i1; i < handle.i2; ++i) { + cw = handle.grx[i + 1] - handle.grx[i]; + for (j = handle.j1; j < handle.j2; ++j) { + ch = handle.gry[j] - handle.gry[j + 1]; + binz = histo.getBinContent(i + 1, j + 1); -/** @summary Set fGeoAtt bit - * @private */ -function setGeoBit(volume, f, value) { - if (volume.fGeoAtt === undefined) return; - volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); -} + const npix = Math.round(scale * binz); + if (npix <= 0) + continue; -/** @summary Toggle fGeoAttBit - * @private */ -function toggleGeoBit(volume, f) { - if (volume.fGeoAtt !== undefined) - volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff); -} + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; -/** @summary Implementation of TGeoVolume::InvisibleAll - * @private */ -function setInvisibleAll(volume, flag) { - if (flag === undefined) flag = true; + for (let k = 0; k < npix; ++k) { + path += this.markeratt.create( + Math.round(handle.grx[i] + cw * rnd.random()), + Math.round(handle.gry[j + 1] + ch * rnd.random())); + } + } + } - setGeoBit(volume, geoBITS.kVisThis, !flag); - // setGeoBit(this, geoBITS.kVisDaughters, !flag); + this.appendPath(path) + .call(this.markeratt.func); - if (volume.fNodes) { - for (let n = 0; n < volume.fNodes.arr.length; ++n) { - const sub = volume.fNodes.arr[n].fVolume; - setGeoBit(sub, geoBITS.kVisThis, !flag); - // setGeoBit(sub, geoBITS.kVisDaughters, !flag); + return handle; } - } -} -const _warn_msgs = {}; + // limit filling factor, do not try to produce as many points as filled area; + if (this.maxbin > 0.7) + factor = 0.7 / this.maxbin; -/** @summary method used to avoid duplication of warnings - * @private */ -function geoWarn(msg) { - if (_warn_msgs[msg] !== undefined) return; - _warn_msgs[msg] = true; - console.warn(msg); -} + const nlevels = Math.round(handle.max - handle.min), + cntr = this.createContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin); -/** @summary Analyze TGeo node kind - * @desc 0 - TGeoNode - * 1 - TEveGeoNode - * -1 - unsupported - * @return detected node kind - * @private */ -function getNodeKind(obj) { - if (!isObject(obj)) return -1; - return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo; -} + // now start build + for (i = handle.i1; i < handle.i2; ++i) { + for (j = handle.j1; j < handle.j2; ++j) { + binz = histo.getBinContent(i + 1, j + 1); + if ((binz <= 0) || (binz < this.minbin)) + continue; -/** @summary Returns number of shapes - * @desc Used to count total shapes number in composites - * @private */ -function countNumShapes(shape) { - if (!shape) return 0; - if (shape._typename !== clTGeoCompositeShape) return 1; - return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight); -} + cw = handle.grx[i + 1] - handle.grx[i]; + ch = handle.gry[j] - handle.gry[j + 1]; + if (cw * ch <= 0) + continue; + colindx = cntr.getContourIndex(binz / cw / ch); + if (colindx < 0) + continue; -/** @summary Returns geo object name - * @desc Can appends some special suffixes - * @private */ -function getObjectName(obj) { - return obj?.fName ? (obj.fName + (obj.$geo_suffix || '')) : ''; -} + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5))) + continue; -/** @summary Check duplicates - * @private */ -function checkDuplicates(parent, chlds) { - if (parent) { - if (parent.$geo_checked) return; - parent.$geo_checked = true; - } + cmd1 = `M${handle.grx[i]},${handle.gry[j + 1]}`; + if (colPaths[colindx] === undefined) { + colPaths[colindx] = cmd1; + cell_w[colindx] = cw; + cell_h[colindx] = ch; + } else { + cmd2 = `m${handle.grx[i] - currx[colindx]},${handle.gry[j + 1] - curry[colindx]}`; + colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1; + cell_w[colindx] = Math.max(cell_w[colindx], cw); + cell_h[colindx] = Math.max(cell_h[colindx], ch); + } - const names = [], cnts = []; - for (let k = 0; k < chlds.length; ++k) { - const chld = chlds[k]; - if (!chld?.fName) continue; - if (!chld.$geo_suffix) { - const indx = names.indexOf(chld.fName); - if (indx >= 0) { - let cnt = cnts[indx] || 1; - while (names.indexOf(chld.fName+'#'+cnt) >= 0) ++cnt; - chld.$geo_suffix = '#' + cnt; - cnts[indx] = cnt+1; + currx[colindx] = handle.grx[i]; + curry[colindx] = handle.gry[j + 1]; + + colPaths[colindx] += `v${ch}h${cw}v${-ch}z`; } } - names.push(getObjectName(chld)); - } -} - - -/** @summary Create normal to plane, defined with three points - * @private */ -function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - const pA = new Vector3(x1, y1, z1), - pB = new Vector3(x2, y2, z2), - pC = new Vector3(x3, y3, z3), - cb = new Vector3(), - ab = new Vector3(); - - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - - return cb; -} - -// ========================================================================== - -/** - * @summary Helper class for geometry creation - * - * @private - */ - -class GeometryCreator { - - /** @summary Constructor - * @param numfaces - number of faces */ - constructor(numfaces) { - this.nfaces = numfaces; - this.indx = 0; - this.pos = new Float32Array(numfaces*9); - this.norm = new Float32Array(numfaces*9); - } - /** @summary Add face with 3 vertices */ - addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - const 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; - } + const pp = this.getPadPainter(), + layer = pp.selectChild('.main_layer'); + let defs = layer.selectChild('defs'); + if (defs.empty() && colPaths.length) + defs = layer.insert('svg:defs', ':first-child'); - /** @summary Start polygon */ - startPolygon() {} + this.createAttMarker({ attr: histo }); - /** @summary Stop polygon */ - stopPolygon() {} + for (colindx = 0; colindx < colPaths.length; ++colindx) { + if ((colPaths[colindx] !== undefined) && (colindx < cntr.arr.length)) { + const pattern_id = (pp.getPadName() || 'canv') + `_scatter_${colindx}`; + let pattern = defs.selectChild(`#${pattern_id}`); + if (pattern.empty()) { + pattern = defs.append('svg:pattern') + .attr('id', pattern_id) + .attr('patternUnits', 'userSpaceOnUse'); + } else + pattern.selectAll('*').remove(); - /** @summary Add face with 4 vertices - * @desc 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 */ - addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) { - let indx = this.indx; - const pos = this.pos; + let npix = Math.round(factor * cntr.arr[colindx] * cell_w[colindx] * cell_h[colindx]); + npix = Math.max(1, npix); - 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; - } + const arrx = new Float32Array(npix), arry = new Float32Array(npix); - 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; - } + if (npix === 1) + arrx[0] = arry[0] = 0.5; + else { + for (let n = 0; n < npix; ++n) { + arrx[n] = rnd.random(); + arry[n] = rnd.random(); + } + } - this.last4 = (indx !== this.indx + 9); - this.indx = indx; - } + this.markeratt.resetPos(); - /** @summary Specify normal for face with 4 vertices - * @desc same as addFace4, assign normals for each individual vertex - * reduce has same meaning and should be the same */ - setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4, reduce) { - if (this.last4 && reduce) - return console.error('missmatch between addFace4 and setNormal4 calls'); + let path = ''; + for (let n = 0; n < npix; ++n) + path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]); - let indx = this.indx - (this.last4 ? 18 : 9); - const norm = this.norm; + pattern.attr('width', cell_w[colindx]) + .attr('height', cell_h[colindx]) + .append('svg:path') + .attr('d', path) + .call(this.markeratt.func); - 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; + this.appendPath(colPaths[colindx]) + .attr('scatter-index', colindx) + .style('fill', `url(#${pattern_id})`); + } } - 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; - } + return handle; } - /** @summary Recalculate Z with provided func */ - recalcZ(func) { - const pos = this.pos, - last = this.indx; - let indx = last - (this.last4 ? 18 : 9); + /** @summary Draw TH2 bins in 2D mode */ + draw2DBins() { + const o = this.getOptions(); - while (indx < last) { - pos[indx+2] = func(pos[indx], pos[indx+1], pos[indx+2]); - indx+=3; - } - } + if (this.#hide_frame && this.isMainPainter()) { + this.getPadPainter().getFrameSvg().style('display', null); + this.#hide_frame = undefined; + } else if (o.Same && !this.isUseFrame()) + this.getPadPainter().getFrameSvg().style('display', 'none'); - /** @summary Caclualte normal */ - calcNormal() { - if (!this.cb) { - this.pA = new Vector3(); - this.pB = new Vector3(); - this.pC = new Vector3(); - this.cb = new Vector3(); - this.ab = new Vector3(); + if (!this.draw_content) { + if (o.Zscale && o.ohmin && o.ohmax) { + this.getContour(true); + this.getHistPalette(); + } + return this.removeG(); } - 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); - } + this.createHistDrawAttributes(); - /** @summary Set normal */ - setNormal(nx, ny, nz) { - let indx = this.indx - 9; - const norm = this.norm; + this.createG(this.isUseFrame()); - 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; + let handle, pr; - 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.isTH2Poly()) + pr = this.drawPolyBins(); + else { + if (o.Scat) + handle = this.drawBinsScatter(); - /** @summary Set normal - * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ - setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce) { - if (reduce === undefined) reduce = 0; + if (o.System === kPOLAR) + handle = this.drawBinsPolar(); + else if (o.Arrow) + handle = this.drawBinsArrow(); + else if (o.Color) + handle = this.drawBinsColor(); + else if (o.Box) + handle = this.drawBinsBox(); + else if (o.Proj) + handle = this.drawBinsProjected(); + else if (o.Contour) + handle = this.drawBinsContour(); + else if (o.Candle || o.Violin) + handle = this.drawBinsCandle(); - let indx = this.indx - ((reduce > 0) ? 9 : 18); - const norm = this.norm; + if (o.Text) + pr = this.drawBinsText(handle); - 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 (!handle && !pr) + handle = this.drawBinsColor(); } - 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; - } + if (handle) + this.tt_handle = handle; + else if (pr) + return pr.then(tt => { this.tt_handle = tt; }); } - /** @summary Create geometry */ - create() { - if (this.nfaces !== this.indx/9) - console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx/9} number of faces`); + /** @summary Draw TH2 in circular mode */ + async drawBinsCircular() { + this.#hide_frame = true; - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(this.pos, 3)); - geometry.setAttribute('normal', new BufferAttribute(this.norm, 3)); - return geometry; - } + const pp = this.getPadPainter(), + rect = pp.getFrameRect(), + hist = this.getHisto(), + circ = this.getOptions().Circular, + palette = circ > 10 ? this.getHistPalette() : null, + text_size = 20, + circle_size = 16, + axis = hist.fXaxis, + g = this.createG(), + getBinLabel = indx => { + if (axis.fLabels) { + for (let i = 0; i < axis.fLabels.arr.length; ++i) { + const tstr = axis.fLabels.arr[i]; + if (tstr.fUniqueID === indx + 1) + return tstr.fString; + } + } + return indx.toString(); + }; -} + pp.getFrameSvg().style('display', 'none'); -// ================================================================================ + this.assignChordCircInteractive(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); -/** @summary Helper class for CsgGeometry creation - * - * @private - */ + const nbins = Math.min(this.nbinsx, this.nbinsy); -class PolygonsCreator { + return this.startTextDrawingAsync(42, text_size, g).then(() => { + const pnts = []; - /** @summary constructor */ - constructor() { - this.polygons = []; - } + for (let n = 0; n < nbins; n++) { + const a = (0.5 - n / nbins) * Math.PI * 2, + cx = Math.round((0.9 * rect.width / 2 - 2 * circle_size) * Math.cos(a)), + cy = Math.round((0.9 * rect.height / 2 - 2 * circle_size) * Math.sin(a)), + x = Math.round(0.9 * rect.width / 2 * Math.cos(a)), + y = Math.round(0.9 * rect.height / 2 * Math.sin(a)), + color = palette?.calcColor(n, nbins) ?? 'black'; + let rotate = Math.round(a / Math.PI * 180), align = 12; - /** @summary Start polygon */ - startPolygon(normal) { - this.multi = 1; - this.mnormal = normal; - } + pnts.push({ x: cx, y: cy, a, color }); // remember points coordinates - /** @summary Stop polygon */ - stopPolygon() { - if (!this.multi) return; - this.multi = 0; - console.error('Polygon should be already closed at this moment'); - } + if ((rotate < -90) || (rotate > 90)) { + rotate += 180; + align = 32; + } - /** @summary Add face with 3 vertices */ - addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - this.addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2); - } + const s2 = Math.round(text_size / 2), s1 = 2 * s2; - /** @summary Add face with 4 vertices - * @desc 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 */ - addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) { - if (reduce === undefined) reduce = 0; + g.append('path') + .attr('d', `M${cx - s2},${cy} a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,${-s1},0z`) + .style('stroke', color) + .style('fill', 'none'); - this.v1 = new Vertex(x1, y1, z1, 0, 0, 0); - this.v2 = (reduce === 1) ? null : new Vertex(x2, y2, z2, 0, 0, 0); - this.v3 = new Vertex(x3, y3, z3, 0, 0, 0); - this.v4 = (reduce === 2) ? null : new Vertex(x4, y4, z4, 0, 0, 0); + this.drawText({ align, rotate, x, y, text: getBinLabel(n) }); + } - this.reduce = reduce; + const max_width = circle_size / 2; + let max_value = 0, min_value = 0; + if (circ > 11) { + for (let i = 0; i < nbins - 1; ++i) { + for (let j = i + 1; j < nbins; ++j) { + const cont = hist.getBinContent(i + 1, j + 1); + if (cont > 0) { + max_value = Math.max(max_value, cont); + if (!min_value || (cont < min_value)) + min_value = cont; + } + } + } + } - if (this.multi) { - if (reduce !== 2) console.error('polygon not supported for not-reduced faces'); + for (let i = 0; i < nbins - 1; ++i) { + const pi = pnts[i]; + let path = ''; - let polygon; + for (let j = i + 1; j < nbins; ++j) { + const cont = hist.getBinContent(i + 1, j + 1); + if (cont <= 0) + continue; - if (this.multi++ === 1) { - polygon = new Polygon(); + const pj = pnts[j], + a = (pi.a + pj.a) / 2, + qr = 0.5 * (1 - Math.abs(pi.a - pj.a) / Math.PI), // how far Q point will be away from center + qx = Math.round(qr * rect.width / 2 * Math.cos(a)), + qy = Math.round(qr * rect.height / 2 * Math.sin(a)); - 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 - const last = this.mnormal ? polygon.vertices[polygon.vertices.length-1] : polygon.vertices[0], - comp = this.mnormal ? this.v2 : this.v3; + path += `M${pi.x},${pi.y}Q${qx},${qy},${pj.x},${pj.y}`; - if (comp.diff(last) > 1e-12) - console.error('vertex missmatch when building polygon'); + if ((circ > 11) && (max_value > min_value)) { + const width = Math.round((cont - min_value) / (max_value - min_value) * (max_width - 1) + 1); + g.append('path').attr('d', path).style('stroke', pi.color).style('stroke-width', width).style('fill', 'none'); + path = ''; + } + } + if (path) + g.append('path').attr('d', path).style('stroke', pi.color).style('fill', 'none'); } - const first = this.mnormal ? polygon.vertices[0] : polygon.vertices[polygon.vertices.length-1], - next = this.mnormal ? this.v3 : this.v2; + return this.finishTextDrawing(); + }).then(() => { + if (!this.isBatchMode()) { + g.insert('path', ':first-child') + .attr('d', `M${-rect.width / 2},${-rect.height / 2}h${rect.width}v${rect.height}h${-rect.width}z`) + .style('opacity', 0) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } - if (next.diff(first) < 1e-12) { - // console.log(`polygon closed!!! nvertices = ${polygon.vertices.length}`); - this.multi = 0; - } else - if (this.mnormal) - polygon.vertices.push(this.v3); - else - polygon.vertices.unshift(this.v2); + return this; + }); + } + /** @summary Prepare translation and assign interactive handler */ + assignChordCircInteractive(midx, midy) { + if (!this.#chord) + this.#chord = { x: 0, y: 0, zoom: 1 }; - return; - } + makeTranslate(this.getG(), midx + this.#chord.x, midy + this.#chord.y, this.#chord.zoom); - const polygon = new Polygon(); + if (this.isBatchMode()) + return; - 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; + if (settings.Zooming && settings.ZoomWheel) { + this.getG().on('wheel', evnt => { + const pos = pointer(evnt, this.getG().node()), + delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail), + prev_zoom = this.#chord.zoom; + + this.#chord.zoom *= (delta > 0) ? 0.8 : 1.2; + this.#chord.x += pos[0] * (prev_zoom - this.#chord.zoom); + this.#chord.y += pos[1] * (prev_zoom - this.#chord.zoom); + + makeTranslate(this.getG(), midx + this.#chord.x, midy + this.#chord.y, this.#chord.zoom); + }).on('dblclick', () => { + this.#chord.x = this.#chord.y = 0; + this.#chord.zoom = 1; + makeTranslate(this.getG(), midx, midy); + }); } - this.polygons.push(polygon); - } - - /** @summary Specify normal for face with 4 vertices - * @desc same as addFace4, assign normals for each individual vertex - * reduce has same meaning and should be the same */ - setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) { - 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); + assignContextMenu(this); } - /** @summary Set normal - * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ - setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) { - 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); - } + /** @summary Draw histogram bins as chord diagram */ + async drawBinsChord() { + this.getPadPainter().getFrameSvg().style('display', 'none'); + this.#hide_frame = true; - /** @summary Calculate normal */ - calcNormal() { - if (!this.cb) { - this.pA = new Vector3(); - this.pB = new Vector3(); - this.pC = new Vector3(); - this.cb = new Vector3(); - this.ab = new Vector3(); + const used = [], + nbins = Math.min(this.nbinsx, this.nbinsy), + hist = this.getHisto(); + let fullsum = 0, isint = true; + for (let i = 0; i < nbins; ++i) { + let sum = 0; + for (let j = 0; j < nbins; ++j) { + const cont = hist.getBinContent(i + 1, j + 1); + if (cont > 0) { + sum += cont; + if (isint && (Math.round(cont) !== cont)) + isint = false; + } + } + if (sum > 0) + used.push(i); + fullsum += sum; } - 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); - } + // do not show less than 2 elements + if (used.length < 2) + return true; - 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); - } - - /** @summary Set normal */ - setNormal(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); - } + let ndig = 0, tickStep = 1; + const rect = this.getPadPainter().getFrameRect(), + midx = Math.round(rect.x + rect.width / 2), + midy = Math.round(rect.y + rect.height / 2), + palette = this.getHistPalette(), + outerRadius = Math.max(10, Math.min(rect.width, rect.height) * 0.5 - 60), + innerRadius = Math.max(2, outerRadius - 10), + data = [], labels = [], + formatValue = v => v.toString(), + formatTicks = v => { return (ndig > 3) ? v.toExponential(0) : v.toFixed(ndig); }, + d3_descending = (a, b) => { return b < a ? -1 : b > a ? 1 : b >= a ? 0 : Number.NaN; }; - /** @summary Recalculate Z with provided func */ - recalcZ(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); - } + if (!isint && fullsum < 10) { + const lstep = Math.round(Math.log10(fullsum) - 2.3); + ndig = -lstep; + tickStep = Math.pow(10, lstep); + } else if (fullsum > 200) { + const lstep = Math.round(Math.log10(fullsum) - 2.3); + tickStep = Math.pow(10, lstep); + } - /** @summary Create geometry - * @private */ - create() { - return { polygons: this.polygons }; - } + if (tickStep * 250 < fullsum) + tickStep *= 5; + else if (tickStep * 100 < fullsum) + tickStep *= 2; -} + for (let i = 0; i < used.length; ++i) { + data[i] = []; + for (let j = 0; j < used.length; ++j) + data[i].push(hist.getBinContent(used[i] + 1, used[j] + 1)); + const axis = hist.fXaxis; + let lbl = 'indx_' + used[i].toString(); + if (axis.fLabels) { + for (let k = 0; k < axis.fLabels.arr.length; ++k) { + const tstr = axis.fLabels.arr[k]; + if (tstr.fUniqueID === used[i] + 1) { + lbl = tstr.fString; + break; + } + } + } + labels.push(lbl); + } -// ================= all functions to create geometry =================================== + const g = this.createG(); -/** @summary Creates cube geometrey - * @private */ -function createCubeBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + this.assignChordCircInteractive(midx, midy); - const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ, - creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(12); + const chord$1 = chord() + .padAngle(10 / innerRadius) + .sortSubgroups(d3_descending) + .sortChords(d3_descending), + chords = chord$1(data), + group = g.append('g') + .attr('font-size', 10) + .attr('font-family', 'sans-serif') + .selectAll('g') + .data(chords.groups) + .join('g'), + arc$1 = arc().innerRadius(innerRadius).outerRadius(outerRadius), + ribbon = ribbon$1().radius(innerRadius - 1).padAngle(1 / innerRadius); - creator.addFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); creator.setNormal(1, 0, 0); + function ticks({ startAngle, endAngle, value }) { + const k = (endAngle - startAngle) / value, + arr = []; + for (let z = 0; z <= value; z += tickStep) + arr.push({ value: z, angle: z * k + startAngle }); + return arr; + } - creator.addFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); creator.setNormal(-1, 0, 0); + group.append('path') + .attr('fill', d => palette.calcColor(d.index, used.length)) + .attr('d', arc$1); - creator.addFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); creator.setNormal(0, 1, 0); + group.append('title').text(d => `${labels[d.index]} ${formatValue(d.value)}`); - creator.addFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); creator.setNormal(0, -1, 0); + const groupTick = group.append('g') + .selectAll('g') + .data(ticks) + .join('g') + .attr('transform', d => `rotate(${Math.round(d.angle * 180 / Math.PI - 90)}) translate(${outerRadius})`); + groupTick.append('line') + .attr('stroke', 'currentColor') + .attr('x2', 6); - creator.addFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); creator.setNormal(0, 0, 1); + groupTick.append('text') + .attr('x', 8) + .attr('dy', '0.35em') + .attr('transform', d => { return (d.angle > Math.PI) ? 'rotate(180) translate(-16)' : null; }) + .attr('text-anchor', d => { return (d.angle > Math.PI) ? 'end' : null; }) + .text(d => formatTicks(d.value)); - creator.addFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); creator.setNormal(0, 0, -1); + group.select('text') + .attr('font-weight', 'bold') + .text(function(d) { + return this.getAttribute('text-anchor') === 'end' ? `↑ ${labels[d.index]}` : `${labels[d.index]} ↓`; + }); - return creator.create(); -} + g.append('g') + .attr('fill-opacity', 0.8) + .selectAll('path') + .data(chords) + .join('path') + .style('mix-blend-mode', 'multiply') + .attr('fill', d => palette.calcColor(d.source.index, used.length)) + .attr('d', ribbon) + .append('title') + .text(d => `${formatValue(d.source.value)} ${labels[d.target.index]} → ${labels[d.source.index]}${d.source.index === d.target.index ? '' : `\n${formatValue(d.target.value)} ${labels[d.source.index]} → ${labels[d.target.index]}`}`); -/** @summary Creates 8 edges geometry - * @private */ -function create8edgesBuffer(v, faces_limit) { - const 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], - creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12); + if (!this.isBatchMode()) { + g.insert('ellipse', ':first-child') + .attr('cx', 0) + .attr('cy', 0) + .attr('rx', outerRadius * 1.2) + .attr('ry', outerRadius * 1.2) + .style('opacity', 0) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } - for (let n = 0; n < indicies.length; n += 4) { - const 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 true; } - return creator.create(); -} + /** @summary Provide text information (tooltips) for histogram bin */ + getBinTooltips(i, j) { + const histo = this.getHisto(), + profile2d = this.matchObjectType(clTProfile2D) && isFunc(histo.getBinEntries), + bincontent = histo.getBinContent(i + 1, j + 1); + let binz = bincontent; -/** @summary Creates PARA geometrey - * @private */ -function createParaBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + if (histo.$baseh) + binz -= histo.$baseh.getBinContent(i + 1, j + 1); - const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, 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]; + const lines = [this.getObjectHint(), + 'x = ' + this.getAxisBinTip('x', histo.fXaxis, i), + 'y = ' + this.getAxisBinTip('y', histo.fYaxis, j), + `bin = ${histo.getBin(i + 1, j + 1)} x: ${i + 1} y: ${j + 1}`, + 'content = ' + ((binz === Math.round(binz)) ? binz : floatToString(binz, gStyle.fStatFormat))]; + + if ((this.getOptions().TextKind === 'E') || profile2d || histo.fSumw2?.length) { + const errs = this.getBinErrors(histo, histo.getBin(i + 1, j + 1), bincontent); + if (errs.poisson) + lines.push('error low = ' + floatToString(errs.low, gStyle.fPaintTextFormat), 'error up = ' + floatToString(errs.up, gStyle.fPaintTextFormat)); + else + lines.push('error = ' + floatToString(errs.up, gStyle.fPaintTextFormat)); + } - return create8edgesBuffer(v, faces_limit); -} + if (profile2d) { + const entries = histo.getBinEntries(i + 1, j + 1); + lines.push('entries = ' + ((entries === Math.round(entries)) ? entries : floatToString(entries, gStyle.fStatFormat))); + } -/** @summary Creates Ttrapezoid geometrey - * @private */ -function createTrapezoidBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + return lines; + } - let y1, y2; - if (shape._typename === clTGeoTrd1) - y1 = y2 = shape.fDY; - else { - y1 = shape.fDy1; y2 = shape.fDy2; + /** @summary Provide text information (tooltips) for candle bin */ + getCandleTooltips(p) { + const funcs = this.getHistGrFuncs(), + histo = this.getHisto(); + + return [this.getObjectHint(), + p.swapXY ? 'y = ' + funcs.axisAsText('y', histo.fYaxis.GetBinLowEdge(p.bin + 1)) + : 'x = ' + funcs.axisAsText('x', histo.fXaxis.GetBinLowEdge(p.bin + 1)), + 'm-25% = ' + floatToString(p.fBoxDown, gStyle.fStatFormat), + 'median = ' + floatToString(p.fMedian, gStyle.fStatFormat), + 'm+25% = ' + floatToString(p.fBoxUp, gStyle.fStatFormat)]; } - const 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 - ]; + /** @summary Provide text information (tooltips) for poly bin */ + getPolyBinTooltips(binindx, realx, realy) { + const histo = this.getHisto(), + bin = histo.fBins.arr[binindx], + funcs = this.getHistGrFuncs(), + lines = []; + let binname = bin.fPoly.fName, numpoints = 0; - return create8edgesBuffer(v, faces_limit); -} + if (binname === 'Graph') + binname = ''; + if (!binname) + binname = bin.fNumber; + if ((realx === undefined) && (realy === undefined)) { + realx = realy = 0; + let gr = bin.fPoly, numgraphs = 1; + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } -/** @summary Creates arb8 geometrey - * @private */ -function createArb8Buffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + for (let ngr = 0; ngr < numgraphs; ++ngr) { + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; - const 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, 3, 7, 4, 4, 0, 3, - 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6, - 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; + for (let n = 0; n < gr.fNpoints; ++n) { + ++numpoints; + realx += gr.fX[n]; + realy += gr.fY[n]; + } + } - // detect same vertices on both Z-layers - for (let side = 0; side < vertices.length; side += vertices.length/2) { - for (let n1 = side; n1 < side + vertices.length/2 - 3; n1+=3) { - for (let 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 (let k=0; k 1) { + realx /= numpoints; + realy /= numpoints; } } - } - const map = []; // list of existing faces (with all rotations) - let numfaces = 0; + lines.push(this.getObjectHint(), + 'x = ' + funcs.axisAsText('x', realx), + 'y = ' + funcs.axisAsText('y', realy)); + if (numpoints > 0) + lines.push('npnts = ' + numpoints); + lines.push(`bin = ${binname}`); + if (bin.fContent === Math.round(bin.fContent)) + lines.push('content = ' + bin.fContent); + else + lines.push('content = ' + floatToString(bin.fContent, gStyle.fStatFormat)); + return lines; + } - for (let k = 0; k < indicies.length; k += 3) { - const 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]; + /** @summary Process tooltip event */ + processTooltipEvent(pnt) { + const histo = this.getHisto(), + o = this.getOptions(), + h = this.tt_handle; + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - 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++; + if (!pnt || !this.draw_content || !this.getG() || !h || o.Proj) { + ttrect?.remove(); + return null; } - } - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + if (h.poly) { + // process tooltips from TH2Poly + const funcs = this.getHistGrFuncs(), + realx = funcs.revertAxis('x', pnt.x), + realy = funcs.revertAxis('y', pnt.y); + let foundindx = -1, bin; - for (let n = 0; n < indicies.length; n += 6) { - const 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; - let norm = null; + if ((realx !== undefined) && (realy !== undefined)) { + const len = histo.fBins.arr.length; - 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 Vector3(0, 0, 1); - else if (n === 30) - norm = new Vector3(0, 0, -1); - else { - const norm1 = produceNormal(vertices[i1], vertices[i1+1], vertices[i1+2], - vertices[i2], vertices[i2+1], vertices[i2+2], - vertices[i3], vertices[i3+1], vertices[i3+2]); + for (let i = 0; (i < len) && (foundindx < 0); ++i) { + bin = histo.fBins.arr[i]; - norm1.normalize(); + // found potential bins candidate + if ((realx < bin.fXmin) || (realx > bin.fXmax) || (realy < bin.fYmin) || (realy > bin.fYmax)) + continue; - const norm2 = produceNormal(vertices[i4], vertices[i4+1], vertices[i4+2], - vertices[i5], vertices[i5+1], vertices[i5+2], - vertices[i6], vertices[i6+1], vertices[i6+2]); + // ignore empty bins with col0 option + if (!bin.fContent && !o.Zero) + continue; - norm2.normalize(); + let gr = bin.fPoly, numgraphs = 1; + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } - if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1; + for (let ngr = 0; ngr < numgraphs; ++ngr) { + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; + if (gr.IsInside(realx, realy)) { + foundindx = i; + break; + } + } + } } - } - 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 { - 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(); + if (foundindx < 0) { + ttrect.remove(); + return null; } - } - } - - return creator.create(); -} - -/** @summary Creates sphere geometrey - * @private */ -function createSphereBuffer(shape, faces_limit) { - const radius = [shape.fRmax, shape.fRmin], - phiStart = shape.fPhi1, - phiLength = shape.fPhi2 - shape.fPhi1, - thetaStart = shape.fTheta1, - thetaLength = shape.fTheta2 - shape.fTheta1, - noInside = (radius[1] <= 0); - let widthSegments = shape.fNseg, - heightSegments = shape.fNz; - - if (faces_limit > 0) { - const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; - if (fact > 1.0) { - widthSegments = Math.max(4, Math.floor(widthSegments/Math.sqrt(fact))); - heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(fact))); - } - } + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + exact: true, menu: true, + lines: this.getPolyBinTooltips(foundindx, realx, realy) + }; - let numoutside = widthSegments * heightSegments * 2, - numtop = widthSegments * (noInside ? 1 : 2), - numbottom = widthSegments * (noInside ? 1 : 2); - const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4), - epsilon = 1e-10; + if (pnt.disabled) { + ttrect.remove(); + res.changed = true; + } else { + if (ttrect.empty()) { + ttrect = this.appendPath() + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle); + } - if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + res.changed = ttrect.property('current_bin') !== foundindx; - const _sinp = new Float32Array(widthSegments+1), - _cosp = new Float32Array(widthSegments+1), - _sint = new Float32Array(heightSegments+1), - _cost = new Float32Array(heightSegments+1); + if (res.changed) { + ttrect.attr('d', this.createPolyBin(funcs, bin)) + .style('opacity', '0.7') + .property('current_bin', foundindx); + } + } - for (let n = 0; n <= heightSegments; ++n) { - const theta = (thetaStart + thetaLength/heightSegments*n)*Math.PI/180; - _sint[n] = Math.sin(theta); - _cost[n] = Math.cos(theta); - } + if (res.changed) { + res.user_info = { + obj: histo, name: histo.fName, + bin: foundindx, + cont: bin.fContent, + grx: pnt.x, gry: pnt.y + }; + } + return res; + } else if (h.candle) { + // process tooltips for candle - for (let n = 0; n <= widthSegments; ++n) { - const phi = (phiStart + phiLength/widthSegments*n)*Math.PI/180; - _sinp[n] = Math.sin(phi); - _cosp[n] = Math.cos(phi); - } + let i, p, match; - if (Math.abs(_sint[0]) <= epsilon) { numoutside -= widthSegments; numtop = 0; } - if (Math.abs(_sint[heightSegments]) <= epsilon) { numoutside -= widthSegments; numbottom = 0; } + for (i = 0; i < h.candle.length; ++i) { + p = h.candle[i]; + match = p.swapXY + ? ((p.x1 <= pnt.y) && (pnt.y <= p.x2) && (p.yy1 >= pnt.x) && (pnt.x >= p.yy2)) + : ((p.x1 <= pnt.x) && (pnt.x <= p.x2) && (p.yy1 <= pnt.y) && (pnt.y <= p.yy2)); + if (match) + break; + } - const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut, - creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + if (!match) { + ttrect.remove(); + return null; + } - for (let side = 0; side < 2; ++side) { - if ((side === 1) && noInside) break; + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getCandleTooltips(p), exact: true, menu: true + }; - const r = radius[side], - s = (side === 0) ? 1 : -1, - d1 = 1 - side, d2 = 1 - d1; + if (pnt.disabled) { + ttrect.remove(); + res.changed = true; + } else { + if (ttrect.empty()) { + ttrect = this.appendPath() + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle) + .style('opacity', '0.7'); + } - // use direct algorithm for the sphere - here normals and position can be calculated directly - for (let k = 0; k < heightSegments; ++k) { - const k1 = k + d1, k2 = k + d2; - let skip = 0; - if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else - if (Math.abs(_sint[k2]) <= epsilon) skip = 2; + res.changed = ttrect.property('current_bin') !== i; - for (let 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); + if (res.changed) { + ttrect.attr('d', p.swapXY ? `M${p.yy1},${p.x1}H${p.yy2}V${p.x2}H${p.yy1}Z` : `M${p.x1},${p.yy1}H${p.x2}V${p.yy2}H${p.x1}Z`) + .property('current_bin', i); + } } - } - } - // top/bottom - for (let side = 0; side <= heightSegments; side += heightSegments) { - if (Math.abs(_sint[side]) >= epsilon) { - const ss = _sint[side], cc = _cost[side], - d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; - for (let 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(); + if (res.changed) { + res.user_info = { + obj: histo, name: histo.fName, + bin: i + 1, cont: p.fMedian, binx: i + 1, biny: 1, + grx: pnt.x, gry: pnt.y + }; } + + return res; } - } - // cut left/right sides - if (phiLength < 360) { - for (let side=0; side<=widthSegments; side+=widthSegments) { - const ss = _sinp[side], cc = _cosp[side], - d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; + const fp = this.getFramePainter(); + let i, j, binz = 0, colindx = null, is_pol = false, + i1, i2, j1, j2, x1, x2, y1, y2; - for (let k=0; k h.grx[i]) || (pnt.x < h.grx[i + 1])); ++i); + else + for (i = h.i1; (i < h.i2) && ((pnt.x < h.grx[i]) || (pnt.x > h.grx[i + 1])); ++i); - return creator.create(); -} -/** @summary Creates tube geometrey - * @private */ -function createTubeBuffer(shape, faces_limit) { - let outerR, innerR; // inner/outer tube radius - if ((shape._typename === clTGeoCone) || (shape._typename === clTGeoConeSeg)) { - outerR = [shape.fRmax2, shape.fRmax1]; - innerR = [shape.fRmin2, shape.fRmin1]; - } else { - outerR = [shape.fRmax, shape.fRmax]; - innerR = [shape.fRmin, shape.fRmin]; - } + if (fp.reverse_y()) + for (j = h.j1; (j < h.j2) && ((pnt.y > h.gry[j + 1]) || (pnt.y < h.gry[j])); ++j); + else + for (j = h.j1; (j < h.j2) && ((pnt.y < h.gry[j + 1]) || (pnt.y > h.gry[j])); ++j); + } - const hasrmin = (innerR[0] > 0) || (innerR[1] > 0); - let thetaStart = 0, thetaLength = 360; + if ((i < h.i2) && (j < h.j2)) { + i1 = i; + i2 = i + 1; + j1 = j; + j2 = j + 1; + x1 = h.grx[i1]; + x2 = h.grx[i2]; + y1 = h.gry[j2]; + y2 = h.gry[j1]; - if ((shape._typename === clTGeoConeSeg) || (shape._typename === clTGeoTubeSeg) || (shape._typename === clTGeoCtub)) { - thetaStart = shape.fPhi1; - thetaLength = shape.fPhi2 - shape.fPhi1; - } + let match = true; - const radiusSegments = Math.max(4, Math.round(thetaLength/cfg.GradPerSegm)); + if (o.Color && !is_pol) { + // take into account bar settings + const dx = x2 - x1, dy = y2 - y1; + x2 = Math.round(x1 + dx * h.xbar2); + x1 = Math.round(x1 + dx * h.xbar1); + y2 = Math.round(y1 + dy * h.ybar2); + y1 = Math.round(y1 + dy * h.ybar1); + if (fp.reverse_x()) { + if ((pnt.x > x1) || (pnt.x <= x2)) + match = false; + } else if ((pnt.x < x1) || (pnt.x >= x2)) + match = false; + + if (fp.reverse_y()) { + if ((pnt.y > y1) || (pnt.y <= y2)) + match = false; + } else if ((pnt.y < y1) || (pnt.y >= y2)) + match = false; + } + + binz = histo.getBinContent(i + 1, j + 1); + if (this.#projection_kind) + colindx = 0; // just to avoid hide + else if (!match) + colindx = null; + else if (h.hide_only_zeros) + colindx = (binz === 0) && !o.ShowEmpty ? null : 0; + else { + colindx = this.getContour().getPaletteIndex(this.getHistPalette(), binz); + if ((colindx === null) && (binz === 0) && + (o.ShowEmpty || (histo._typename === clTProfile2D && histo.getBinEntries(i + 1, j + 1)))) + colindx = 0; + } + } - // external surface - let numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); + if (colindx === null) { + ttrect.remove(); + return null; + } - // internal surface - if (hasrmin) - numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getBinTooltips(i, j), exact: true, menu: true + }; - // 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 (o.Color) + res.color2 = this.getHistPalette().getColor(colindx); - if (thetaLength < 360) - numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); + if (pnt.disabled && !this.#projection_kind) { + ttrect.remove(); + res.changed = true; + } else { + if (ttrect.empty()) { + ttrect = this.appendPath() + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle); + } - if (faces_limit < 0) return numfaces; + let binid = i * 10000 + j, path; - const phi0 = thetaStart*Math.PI/180, - dphi = thetaLength/radiusSegments*Math.PI/180, - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); + if (this.#projection_kind) { + const pwx = this.#projection_widthX || 1, ddx = (pwx - 1) / 2; + if ((this.#projection_kind.indexOf('X')) >= 0 && (pwx > 1)) { + if (j2 + ddx >= h.j2) { + j2 = Math.min(Math.round(j2 + ddx), h.j2); + j1 = Math.max(j2 - pwx, h.j1); + } else { + j1 = Math.max(Math.round(j1 - ddx), h.j1); + j2 = Math.min(j1 + pwx, h.j2); + } + } + const pwy = this.#projection_widthY || 1, ddy = (pwy - 1) / 2; + if ((this.#projection_kind.indexOf('Y')) >= 0 && (pwy > 1)) { + if (i2 + ddy >= h.i2) { + i2 = Math.min(Math.round(i2 + ddy), h.i2); + i1 = Math.max(i2 - pwy, h.i1); + } else { + i1 = Math.max(Math.round(i1 - ddy), h.i1); + i2 = Math.min(i1 + pwy, h.i2); + } + } + } - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0+seg*dphi); - _sin[seg] = Math.sin(phi0+seg*dphi); - } + if (is_pol) + path = h.getBinPath(i, j); + else if (this.#projection_kind === 'X') { + x1 = 0; + x2 = fp.getFrameWidth(); + y1 = h.gry[j2]; + y2 = h.gry[j1]; + binid = j1 * 777 + j2 * 333; + } else if (this.#projection_kind === 'Y') { + y1 = 0; + y2 = fp.getFrameHeight(); + x1 = h.grx[i1]; + x2 = h.grx[i2]; + binid = i1 * 777 + i2 * 333; + } else if (this.#projection_kind === 'XY') { + y1 = h.gry[j2]; + y2 = h.gry[j1]; + x1 = h.grx[i1]; + x2 = h.grx[i2]; + binid = i1 * 789 + i2 * 653 + j1 * 12345 + j2 * 654321; + path = `M${x1},0H${x2}V${y1}H${fp.getFrameWidth()}V${y2}H${x2}V${fp.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; + } - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), - calcZ = (shape._typename !== clTGeoCtub) - ? null - : (x, y, z) => { - const arr = (z < 0) ? shape.fNlow : shape.fNhigh; - return ((z < 0) ? -shape.fDz : shape.fDz) - (x*arr[0] + y*arr[1]) / arr[2]; - }; + res.changed = ttrect.property('current_bin') !== binid; - // create outer/inner tube - for (let side = 0; side < 2; ++side) { - if ((side === 1) && !hasrmin) break; + if (res.changed) { + ttrect.attr('d', path || `M${x1},${y1}H${x2}V${y2}H${x1}Z`) + .style('opacity', '0.7') + .property('current_bin', binid); + } - const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side; - let nxy = 1, nz = 0; + if (this.#projection_kind && res.changed) + this.redrawProjection(i1, i2, j1, j2); + } - if (R[0] !== R[1]) { - const angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ); - nxy = Math.cos(angle); - nz = Math.sin(angle); + if (res.changed) { + res.user_info = { + obj: histo, name: histo.fName, + bin: histo.getBin(i + 1, j + 1), + cont: binz, binx: i + 1, biny: j + 1, + grx: pnt.x, gry: pnt.y + }; } - if (side === 1) { nxy *= -1; nz *= -1; } + return res; + } - const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0); + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + const o = this.getOptions(); - for (let 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 (o.Proj) + return true; - if (calcZ) creator.recalcZ(calcZ); + // z-scale zooming allowed only if special ignore-palette is not provided + if (axis === 'z') { + if (this.mode3d) + return true; + if (o.IgnorePalette) + return false; - creator.setNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz, - nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz, - reduce); - } - } + const fp = this.getFramePainter(), + nlevels = Math.max(2 * gStyle.fNumberContours, 100), + pad = this.getPadPainter().getRootPad(true), + logv = pad?.fLogv ?? pad?.fLogz; - // create upper/bottom part - for (let side = 0; side < 2; ++side) { - if (outerR[side] <= 0) continue; + if (!fp || (fp.zmin === fp.zmax)) + return true; - const 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 (let 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); + if (logv && (fp.zmin > 0) && (min > 0)) + return nlevels * Math.log(max / min) > Math.log(fp.zmax / fp.zmin); + + return (fp.zmax - fp.zmin) < (max - min) * nlevels; } - creator.stopPolygon(); + let obj = this.getHisto(); + if (obj) + obj = (axis === 'y') ? obj.fYaxis : obj.fXaxis; + + return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); } - // 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(); + /** @summary Complete palette drawing */ + completePalette(pp) { + if (!pp) + return true; - 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)); + const o = this.getOptions(); + pp.$main_painter = this; + o.Zvert = pp.isPaletteVertical(); - if (calcZ) creator.recalcZ(calcZ); - creator.calcNormal(); + // redraw palette till the end when contours are available + return pp.drawPave(o.Cjust ? 'cjust' : ''); } - return creator.create(); -} - -/** @summary Creates eltu geometrey - * @private */ -function createEltuBuffer(shape, faces_limit) { - const radiusSegments = Math.max(4, Math.round(360/cfg.GradPerSegm)); - - if (faces_limit < 0) return radiusSegments*4; + /** @summary Performs 2D drawing of histogram + * @return {Promise} when ready */ + async draw2D(/* reason */) { + this.clear3DScene(); - // calculate all sin/cos tables in advance - const x = new Float32Array(radiusSegments+1), - y = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - const phi = seg/radiusSegments*2*Math.PI; - x[seg] = shape.fRmin*Math.cos(phi); - y[seg] = shape.fRmax*Math.sin(phi); - } + const o = this.getOptions(), + need_palette = o.Zscale && o.canHavePalette() && this.isUseFrame(); - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments*4); - let nx1, ny1, nx2 = 1, ny2 = 0; - - // create tube faces - for (let 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; - const dist = Math.sqrt(nx2**2 + ny2**2); - nx2 = nx2 / dist; ny2 = ny2/dist; + // draw new palette, resize frame if required + return this.drawColorPalette(need_palette, true, this.#can_move_colz).then(async pp => { + this.#can_move_colz = undefined; + let pr; + if (o.Circular && this.isMainPainter()) + pr = this.drawBinsCircular(); + else if (o.Chord && this.isMainPainter()) + pr = this.drawBinsChord(); + else + pr = this.drawAxes().then(() => this.draw2DBins()); - creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); + return pr.then(() => this.completePalette(pp)); + }).then(() => this.updateFunctions()) + .then(() => this.updateHistTitle()) + .then(() => { + this.updateStatWebCanvas(); + return this.addInteractivity(); + }); } - // create top/bottom sides - for (let side = 0; side < 2; ++side) { - const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; - for (let seg=0; seg 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); + if ((main !== this) && fp && (fp.mode3d !== o.Mode3D)) + this.copyOptionsFrom(main); - if (faces_limit < 0) return numfaces; + if (!o.Mode3D) + return this.draw2D(reason); - 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)); + return this.draw3D(reason).catch(err => { + const cp = this.getCanvPainter(); + if (isFunc(cp?.showConsoleError)) + cp.showConsoleError(err); + else + console.error('Fail to draw histogram in 3D - back to 2D'); + o.Mode3D = false; + return this.draw2D(reason); + }); } - const _sinr = new Float32Array(radialSegments+1), - _cosr = new Float32Array(radialSegments+1), - _sint = new Float32Array(tubularSegments+1), - _cost = new Float32Array(tubularSegments+1); - - for (let n = 0; n <= radialSegments; ++n) { - _sinr[n] = Math.sin(n/radialSegments*2*Math.PI); - _cosr[n] = Math.cos(n/radialSegments*2*Math.PI); + /** @summary Redraw histogram */ + async redraw(reason) { + return this.callDrawFunc(reason); } - for (let t = 0; t <= tubularSegments; ++t) { - const angle = (shape.fPhi1 + shape.fDphi*t/tubularSegments)/180*Math.PI; - _sint[t] = Math.sin(angle); - _cost[t] = Math.cos(angle); + /** @summary draw TH2 object in 2D only */ + static async draw(dom, histo, opt) { + return THistPainter._drawHist(new TH2Painter(dom, histo), opt); } - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), - // use vectors for normals calculation - p1 = new Vector3(), p2 = new Vector3(), p3 = new Vector3(), p4 = new Vector3(), - n1 = new Vector3(), n2 = new Vector3(), n3 = new Vector3(), n4 = new Vector3(), - center1 = new Vector3(), center2 = new Vector3(); +}; // class TH2Painter - for (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; - const tube = (side > 0) ? shape.fRmin : shape.fRmax, - d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; +/** @summary Text 3d axis visibility + * @private */ +function testAxisVisibility(camera, toplevel, fb = false, bb = false) { + let top; + if (toplevel?.children) { + for (let n = 0; n < toplevel.children.length; ++n) { + top = toplevel.children[n]; + if (top.axis_draw) + break; + top = undefined; + } + } - for (let t = 0; t < tubularSegments; ++t) { - const 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]; + if (!top) + return; - for (let 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]; + if (!camera) { + // this is case when axis drawing want to be removed + toplevel.remove(top); + return; + } - 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); + const pos = camera.position; + let qudrant = 1; + if ((pos.x < 0) && (pos.y >= 0)) + qudrant = 2; + else if ((pos.x >= 0) && (pos.y >= 0)) + qudrant = 3; + else if ((pos.x >= 0) && (pos.y < 0)) + qudrant = 4; - n1.subVectors(p1, center1).normalize(); - n2.subVectors(p2, center1).normalize(); - n3.subVectors(p3, center2).normalize(); - n4.subVectors(p4, center2).normalize(); + const testVisible = (id, range) => { + if (id <= qudrant) + id += 4; + return (id > qudrant) && (id < qudrant + range); + }, handleZoomMesh = obj3d => { + for (let k = 0; k < obj3d.children?.length; ++k) { + if (obj3d.children[k].zoom !== undefined) + obj3d.children[k].zoom_disabled = !obj3d.visible; + } + }; - 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); + for (let n = 0; n < top.children.length; ++n) { + const chld = top.children[n]; + if (chld.grid) + chld.visible = bb && testVisible(chld.grid, 3); + else if (chld.zid) { + chld.visible = testVisible(chld.zid, 2); + handleZoomMesh(chld); + } else if (chld.xyid) { + chld.visible = testVisible(chld.xyid, 3); + handleZoomMesh(chld); + } else if (chld.xyboxid) { + let range = 5, shift = 0; + if (bb && !fb) { + range = 3; + shift = -2; + } else if (fb && !bb) + range = 3; + else if (!fb && !bb) + range = chld.bottom ? 3 : 0; + chld.visible = testVisible(chld.xyboxid + shift, range); + if (!chld.visible && chld.bottom && bb) + chld.visible = testVisible(chld.xyboxid, 3); + } else if (chld.zboxid) { + let range = 2, shift = 0; + if (fb && bb) + range = 5; + else if (bb && !fb) + range = 4; + else if (!bb && fb) { + shift = -2; + range = 4; } + chld.visible = testVisible(chld.zboxid + shift, range); } } +} - if (shape.fDphi !== 360) { - for (let t = 0; t <= tubularSegments; t += tubularSegments) { - const 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 (let 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); - } - } + +function convertLegoBuf(painter, pos, binsx, binsy) { + if (painter.options.System === kCARTESIAN) + return pos; + const fp = painter.getFramePainter(); + let kx = 1 / fp.size_x3d, ky = 1 / fp.size_y3d; + if (binsx && binsy) { + kx *= binsx / (binsx - 1); + ky *= binsy / (binsy - 1); } - return creator.create(); -} + if (painter.options.System === kPOLAR) { + for (let i = 0; i < pos.length; i += 3) { + const angle = (1 - pos[i] * kx) * Math.PI, + radius = 0.5 + 0.5 * pos[i + 1] * ky; + pos[i] = Math.cos(angle) * radius * fp.size_x3d; + pos[i + 1] = Math.sin(angle) * radius * fp.size_y3d; + } + } else if (painter.options.System === kCYLINDRICAL) { + for (let i = 0; i < pos.length; i += 3) { + const angle = (1 - pos[i] * kx) * Math.PI, + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; -/** @summary Creates polygon geometrey - * @private */ -function createPolygonBuffer(shape, faces_limit) { - const thetaStart = shape.fPhi1, - thetaLength = shape.fDphi; - let radiusSegments, factor; + pos[i] = Math.cos(angle) * radius * fp.size_x3d; + pos[i + 2] = (1 + Math.sin(angle) * radius) * fp.size_z3d; + } + } else if (painter.options.System === kSPHERICAL) { + for (let i = 0; i < pos.length; i += 3) { + const phi = (1 + pos[i] * kx) * Math.PI, + theta = pos[i + 1] * ky * Math.PI, + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; - if (shape._typename === clTGeoPgon) { - radiusSegments = shape.fNedges; - factor = 1.0 / Math.cos(Math.PI/180 * thetaLength / radiusSegments / 2); + pos[i] = radius * Math.cos(theta) * Math.cos(phi) * fp.size_x3d; + pos[i + 1] = radius * Math.cos(theta) * Math.sin(phi) * fp.size_y3d; + pos[i + 2] = (1 + radius * Math.sin(theta)) * fp.size_z3d; + } + } else if (painter.options.System === kRAPIDITY) { + for (let i = 0; i < pos.length; i += 3) { + const phi = (1 - pos[i] * kx) * Math.PI, + theta = pos[i + 1] * ky * Math.PI, + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; + + pos[i] = radius * Math.cos(phi) * fp.size_x3d; + pos[i + 1] = radius * Math.sin(theta) / Math.cos(theta) * fp.size_y3d / 2; + pos[i + 2] = (1 + radius * Math.sin(phi)) * fp.size_z3d; + } + } + + return pos; +} + +function createLegoGeom(painter, positions, normals, binsx, binsy) { + const geometry = new THREE.BufferGeometry(); + if (painter.options.System === kCARTESIAN) { + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + if (normals) + geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3)); + else + geometry.computeVertexNormals(); } else { - radiusSegments = Math.max(5, Math.round(thetaLength/cfg.GradPerSegm)); - factor = 1; + convertLegoBuf(painter, positions, binsx, binsy); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.computeVertexNormals(); } - const usage = new Int16Array(2*shape.fNz); - let numusedlayers = 0, hasrmin = false; + return geometry; +} - for (let layer = 0; layer < shape.fNz; ++layer) - if (shape.fRmin[layer] > 0) hasrmin = true; +function create3DCamera(fp, orthographic) { + if (fp.camera) { + fp.scene.remove(fp.camera); + disposeThreejsObject(fp.camera); + delete fp.camera; + } - // return very rough estimation, number of faces may be much less - if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz-1); + if (orthographic) + fp.camera = new THREE.OrthographicCamera(-1.3 * fp.size_x3d, 1.3 * fp.size_x3d, 2.3 * fp.size_z3d, -0.7 * fp.size_z3d, 0.001, 40 * fp.size_z3d); + else + fp.camera = new THREE.PerspectiveCamera(45, fp.scene_width / fp.scene_height, 1, 40 * fp.size_z3d); - // coordinate of point on cut edge (x,z) - const pnts = (thetaLength === 360) ? null : []; + fp.camera.up.set(0, 0, 1); - // first analyse levels - if we need to create all of them - for (let side = 0; side < 2; ++side) { - const rside = (side === 0) ? 'fRmax' : 'fRmin'; + fp.pointLight = new THREE.DirectionalLight(0xffffff, 3); + fp.pointLight.position.set(fp.size_x3d / 2, fp.size_y3d / 2, fp.size_z3d / 2); + fp.camera.add(fp.pointLight); + fp.lookat = new THREE.Vector3(0, 0, orthographic ? 0.3 * fp.size_z3d : 0.8 * fp.size_z3d); + fp.scene.add(fp.camera); +} - for (let layer=0; layer < shape.fNz; ++layer) { - // first create points for the layer - const layerz = shape.fZ[layer], rad = shape[rside][layer]; +/** @summary Returns camera default position + * @private */ +function getCameraDefaultPosition(fp, first_time) { + const pad = fp.getPadPainter()?.getRootPad(true), + kz = fp.camera.isOrthographicCamera ? 1 : 1.4; + let max3dx = Math.max(0.75 * fp.size_x3d, fp.size_z3d), + max3dy = Math.max(0.75 * fp.size_y3d, fp.size_z3d), + pos = null; - usage[layer*2+side] = 0; + if (first_time) { + pos = new THREE.Vector3(); + if (max3dx === max3dy) + pos.set(-1.6 * max3dx, -3.5 * max3dy, kz * fp.size_z3d); + else if (max3dx > max3dy) + pos.set(-2 * max3dx, -3.5 * max3dy, kz * fp.size_z3d); + else + pos.set(-3.5 * max3dx, -2 * max3dy, kz * fp.size_z3d); + } - 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 + if (pad && (first_time || !fp.zoomChangedInteractive())) { + if (Number.isFinite(pad.fTheta) && Number.isFinite(pad.fPhi) && ((pad.fTheta !== fp.camera_Theta) || (pad.fPhi !== fp.camera_Phi))) { + if (!pos) + pos = new THREE.Vector3(); + max3dx = 3 * Math.max(fp.size_x3d, fp.size_z3d); + max3dy = 3 * Math.max(fp.size_y3d, fp.size_z3d); + const phi = (270 - pad.fPhi) / 180 * Math.PI, + theta = (pad.fTheta - 10) / 180 * Math.PI; + pos.set(max3dx * Math.cos(phi) * Math.cos(theta), + max3dy * Math.sin(phi) * Math.cos(theta), + fp.size_z3d + (kz - 0.9) * (max3dx + max3dy) * Math.sin(theta)); + } + } - continue; - } - } + return pos; +} - if ((layer > 0) && ((side === 0) || hasrmin)) { - usage[layer*2+side] = 1; - numusedlayers++; - } +/** @summary Set default camera position + * @private */ +function setCameraPosition(fp, first_time) { + const pos = getCameraDefaultPosition(fp, first_time); - if (pnts !== null) { - if (side === 0) - pnts.push(new Vector2(factor*rad, layerz)); - else if (rad < shape.fRmax[layer]) - pnts.unshift(new Vector2(factor*rad, layerz)); - } - } + if (pos) { + fp.camera.position.copy(pos); + first_time = true; } - let 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); + if (first_time) + fp.camera.lookAt(fp.lookat); - let cut_faces = null; + if (first_time && fp.camera.isOrthographicCamera && fp.scene_width && fp.scene_height) { + const screen_ratio = fp.scene_width / fp.scene_height, + szx = fp.camera.right - fp.camera.left, + szy = fp.camera.top - fp.camera.bottom; - if (pnts !== null) { - if (pnts.length === shape.fNz * 2) { - // special case - all layers are there, create faces ourself - cut_faces = []; - for (let layer = shape.fNz-1; layer > 0; --layer) { - if (shape.fZ[layer] === shape.fZ[layer-1]) continue; - const right = 2*shape.fNz - 1 - layer; - cut_faces.push([right, layer - 1, layer]); - cut_faces.push([right, right + 1, layer-1]); - } + if (screen_ratio > szx / szy) { + // screen wider than actual geometry + const m = (fp.camera.right + fp.camera.left) / 2; + fp.camera.left = m - szy * screen_ratio / 2; + fp.camera.right = m + szy * screen_ratio / 2; } else { - // let three.js calculate our faces - // console.log(`triangulate polygon ${shape.fShapeId}`); - cut_faces = ShapeUtils.triangulateShape(pnts, []); + // screen higher than actual geometry + const m = (fp.camera.top + fp.camera.bottom) / 2; + fp.camera.top = m + szx / screen_ratio / 2; + fp.camera.bottom = m - szx / screen_ratio / 2; } - numfaces += cut_faces.length*2; } - const phi0 = thetaStart*Math.PI/180, dphi = thetaLength/radiusSegments*Math.PI/180, - // calculate all sin/cos tables in advance - _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); - } + fp.camera.updateProjectionMatrix(); +} - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); +function getCameraPosition(fp) { + const p = fp.camera.position, p0 = fp.lookat, + dist = p.distanceTo(p0), + dist_xy = Math.sqrt((p.x - p0.x) ** 2 + (p.y - p0.y) ** 2), + new_theta = Math.atan2((p.z - p0.z) / dist, dist_xy / dist) / Math.PI * 180, + new_phi = 270 - Math.atan2((p.y - p0.y) / dist_xy, (p.x - p0.x) / dist_xy) / Math.PI * 180, + pad = fp.getPadPainter()?.getRootPad(true); - // add sides - for (let side = 0; side < 2; ++side) { - const rside = (side === 0) ? 'fRmax' : 'fRmin', - d1 = 1 - side, d2 = side; - let z1 = shape.fZ[0], r1 = factor*shape[rside][0]; + fp.camera_Phi = new_phi >= 360 ? new_phi - 360 : new_phi; + fp.camera_Theta = new_theta; - for (let layer = 0; layer < shape.fNz; ++layer) { - if (usage[layer*2+side] === 0) continue; + if (pad && Number.isFinite(fp.camera_Phi) && Number.isFinite(fp.camera_Theta)) { + pad.fPhi = fp.camera_Phi; + pad.fTheta = fp.camera_Theta; + } +} - const z2 = shape.fZ[layer], r2 = factor*shape[rside][layer]; - let nxy = 1, nz = 0; +function create3DControl(fp) { + fp.control = createOrbitControl(fp, fp.camera, fp.scene, fp.renderer, fp.lookat); - if ((r2 !== r1)) { - const angle = Math.atan2((r2-r1), (z2-z1)); - nxy = Math.cos(angle); - nz = Math.sin(angle); - } + const frame_painter = fp, obj_painter = fp.getMainPainter(); - if (side > 0) { nxy*=-1; nz*=-1; } + if (fp.access3dKind() === constants$1.Embed3D.Embed) { + // tooltip scaling only need when GL canvas embed into + const scale = fp.getCanvPainter()?.getPadScale(); + if (scale) + fp.control.tooltip?.setScale(scale); + } - for (let 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); - } + fp.control.processMouseMove = function(intersects) { + let tip = null, mesh = null, zoom_mesh = null; + const handle_tooltip = frame_painter.isTooltipAllowed(); - z1 = z2; r1 = r2; + for (let i = 0; i < intersects.length; ++i) { + if (handle_tooltip && isFunc(intersects[i].object?.tooltip)) { + tip = intersects[i].object.tooltip(intersects[i]); + if (tip) { + mesh = intersects[i].object; + break; + } + } else if (intersects[i].object?.zoom && !zoom_mesh) + zoom_mesh = intersects[i].object; } - } - // add top/bottom - for (let layer = 0; layer < shape.fNz; layer += (shape.fNz-1)) { - const rmin = factor*shape.fRmin[layer], rmax = factor*shape.fRmax[layer]; + if (tip && !tip.use_itself) { + const delta_x = 1e-4 * frame_painter.size_x3d, + delta_y = 1e-4 * frame_painter.size_y3d, + delta_z = 1e-4 * frame_painter.size_z3d; + if ((tip.x1 > tip.x2) || (tip.y1 > tip.y2) || (tip.z1 > tip.z2)) + console.warn('check 3D hints coordinates'); + tip.x1 -= delta_x; + tip.x2 += delta_x; + tip.y1 -= delta_y; + tip.y2 += delta_y; + tip.z1 -= delta_z; + tip.z2 += delta_z; + } - if (rmin === rmax) continue; + frame_painter.highlightBin3D(tip, mesh); - const layerz = shape.fZ[layer], - d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1, - normalz = (layer === 0) ? -1: 1; + if (!tip && zoom_mesh && isFunc(frame_painter.get3dZoomCoord)) { + let axis_name = zoom_mesh.zoom; + const pnt = zoom_mesh.globalIntersect(this.raycaster), + axis_value = frame_painter.get3dZoomCoord(pnt, axis_name); - if (!hasrmin && !cut_faces) - creator.startPolygon(layer > 0); + if ((axis_name === 'z') && zoom_mesh.use_y_for_z) + axis_name = 'y'; - for (let 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 { + name: axis_name, + title: 'axis object', + line: axis_name + ' : ' + frame_painter.axisAsText(axis_name, axis_value), + only_status: true + }; } - creator.stopPolygon(); - } - - if (cut_faces) { - for (let seg = 0; seg <= radiusSegments; seg += radiusSegments) { - const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; - for (let n=0; n 0) { - const fact = 2*radiusSegments*(heightSegments+1) / faces_limit; - if (fact > 1.0) { - radiusSegments = Math.max(5, Math.floor(radiusSegments/Math.sqrt(fact))); - heightSegments = Math.max(5, Math.floor(heightSegments/Math.sqrt(fact))); - } + const newtop = new THREE.Object3D(); + this.scene.add(newtop); + this.toplevel = newtop; + + this.resize3D(); // set actual sizes + + setCameraPosition(this, false); + + return Promise.resolve(true); } - const rmin = shape.fRlo, rmax = shape.fRhi; - let numfaces = (heightSegments+1) * radiusSegments*2; + render3d = getRender3DKind(render3d, this.isBatchMode()); - if (rmin === 0) numfaces -= radiusSegments*2; // complete layer - if (rmax === 0) numfaces -= radiusSegments*2; // complete layer + assign3DHandler(this); - if (faces_limit < 0) return numfaces; + const sz = this.getSizeFor3d(undefined, render3d); - let zmin = -shape.fDZ, zmax = shape.fDZ; + this.size_z3d = 100; + this.x3dscale = x3dscale || 1; + this.y3dscale = y3dscale || 1; + const xy3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width / sz.height * this.size_z3d) : this.size_z3d; + this.size_x3d = xy3d * this.x3dscale; + this.size_y3d = xy3d * this.y3dscale; - // 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; + return importThreeJs().then(() => { + // three.js 3D drawing + this.scene = new THREE.Scene(); + // scene.fog = new Fog(0xffffff, 500, 3000); + this.toplevel = new THREE.Object3D(); + this.scene.add(this.toplevel); + this.scene_width = sz.width; + this.scene_height = sz.height; + this.scene_x = sz.x ?? 0; + this.scene_y = sz.y ?? 0; - const ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax), + this.camera_Phi = 30; + this.camera_Theta = 30; - // calculate all sin/cos tables in advance - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); - _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); - } + create3DCamera(this, orthographic); - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - let lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; + setCameraPosition(this, true); - for (let layer = 0; layer <= heightSegments + 1; ++layer) { - let layerz = 0, radius = 0, nxy = 0, nz = -1; + return createRender3D(this.scene_width, this.scene_height, render3d); + }).then(r => { + this.renderer = r; + if (!r) + return this; - if ((layer === 0) && (rmin === 0)) continue; + this.webgl = r.jsroot_render3d === constants$1.Render3D.WebGL; + this.add3dCanvas(sz, r.jsroot_dom, this.webgl); - if ((layer === heightSegments + 1) && (lastr === 0)) break; + this.first_render_tm = 0; + this.enable_highlight = false; - 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: { - const tt = Math.tan(ttmin + (ttmax-ttmin) * layer / heightSegments), - delta = tt**2 - 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 (!this.isBatchMode() && this.webgl && !isNodeJs()) + create3DControl(this); - nxy = shape.fA * radius; - nz = (shape.fA > 0) ? -1 : 1; + return this; + }); +} - const skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0); +/** @summary Change camera kind in frame painter + * @private */ +function change3DCamera(orthographic) { + let has_control = false; + if (this.control) { + this.control.cleanup(); + delete this.control; + has_control = true; + } - for (let 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); + create3DCamera(this, orthographic); + setCameraPosition(this, true); - // 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); - } + if (has_control) + create3DControl(this); - lastz = layerz; lastr = radius; - lastnxy = nxy; lastnz = nz; + this.render3D(); +} + +/** @summary Add 3D mesh to frame painter + * @private */ +function add3DMesh(mesh, painter, the_only) { + if (!mesh) + return; + if (!this.toplevel) + return console.error('3D objects are not yet created in the frame'); + if (painter && the_only) + this.remove3DMeshes(painter); + this.toplevel.add(mesh); + mesh.painter = painter; +} + +/** @summary Returns all 3D meshed for specific + * @private */ +function get3DMeshes(painter) { + const arr = []; + if (!painter || !this.toplevel) + return arr; + for (let i = 0; i < this.toplevel.children.length; ++i) { + const mesh = this.toplevel.children[i]; + if (mesh.painter === painter) + arr.push(mesh); } + return arr; +} - return creator.create(); +/** @summary Remove 3D meshed for specified painter + * @private */ +function remove3DMeshes(painter) { + const arr = this.get3DMeshes(painter); + arr.forEach(mesh => { + this.toplevel.remove(mesh); + disposeThreejsObject(mesh); + }); } -/** @summary Creates hype geometrey +/** @summary call 3D rendering of the frame + * @param {number} tmout - specifies delay, after which actual rendering will be invoked + * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings + * superimposed with each other. + * If tmout <= 0, rendering performed immediately + * If tmout === -1111, immediate rendering with SVG renderer is performed * @private */ -function createHypeBuffer(shape, faces_limit) { - if ((shape.fTin === 0) && (shape.fTout === 0)) - return createTubeBuffer(shape, faces_limit); +function render3D(tmout) { + if (tmout === -1111) { + // special handling for direct SVG renderer + const doc = getDocument(), + rrr = createSVGRenderer(false, 0); + rrr.setSize(this.scene_width, this.scene_height); + rrr.render(this.scene, this.camera); + if (rrr.makeOuterHTML) { + // use text mode, it is faster + const d = doc.createElement('div'); + d.innerHTML = rrr.makeOuterHTML(); + return d.childNodes[0]; + } + return rrr.domElement; + } - let radiusSegments = Math.max(4, Math.round(360/cfg.GradPerSegm)), - heightSegments = 30, - numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); + if (tmout === undefined) + tmout = 5; // by default, rendering happens with timeout - if (faces_limit < 0) return numfaces; + const batch_mode = this.isBatchMode(); - 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); + if ((tmout > 0) && !this.usesvg && !batch_mode) { + if (!this.render_tmout) + this.render_tmout = setTimeout(() => this.render3D(0), tmout); + return; } - // calculate all sin/cos tables in advance - const _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); - _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); + if (this.render_tmout) { + clearTimeout(this.render_tmout); + delete this.render_tmout; } - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + if (!this.renderer) + return; - // in-out side - for (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; - - const r0 = (side > 0) ? shape.fRmin : shape.fRmax, - tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, - d1 = 1- side, d2 = 1 - d1; - - // vertical layers - for (let layer = 0; layer < heightSegments; ++layer) { - const z1 = -shape.fDz + layer/heightSegments*2*shape.fDz, - z2 = -shape.fDz + (layer+1)/heightSegments*2*shape.fDz, - r1 = Math.sqrt(r0**2 + tsq*z1**2), - r2 = Math.sqrt(r0**2 + tsq*z2**2); + beforeRender3D(this.renderer); - for (let 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(); - } - } - } + const tm1 = new Date(); - // add caps - for (let layer = 0; layer < 2; ++layer) { - const z = (layer === 0) ? shape.fDz : -shape.fDz, - r1 = Math.sqrt(shape.fRmax**2 + shape.fToutsq*z**2), - r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin**2 + shape.fTinsq*z**2) : 0, - skip = (shape.fRmin > 0) ? 0 : 1, - d1 = 1 - layer, d2 = 1 - d1; - for (let 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); - } - } + testAxisVisibility(this.camera, this.toplevel, this.opt3d?.FrontBox, this.opt3d?.BackBox); - return creator.create(); -} + // do rendering, most consuming time + this.renderer.render(this.scene, this.camera); -/** @summary Creates tessalated geometrey - * @private */ -function createTessellatedBuffer(shape, faces_limit) { - let numfaces = 0; - for (let i = 0; i < shape.fFacets.length; ++i) - numfaces += (shape.fFacets[i].fNvert === 4) ? 2 : 1; - if (faces_limit < 0) return numfaces; + afterRender3D(this.renderer); - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + const tm2 = new Date(); - for (let i = 0; i < shape.fFacets.length; ++i) { - const f = shape.fFacets[i], - v0 = shape.fVertices[f.fIvert[0]].fVec, - v1 = shape.fVertices[f.fIvert[1]].fVec, - v2 = shape.fVertices[f.fIvert[2]].fVec; + if (this.first_render_tm === 0) { + this.first_render_tm = tm2.getTime() - tm1.getTime(); + this.enable_highlight = (this.first_render_tm < 1200) && this.isTooltipAllowed(); + if (this.first_render_tm > 500) + console.log(`three.js r${THREE.REVISION}, first render tm = ${this.first_render_tm}`); + } else + getCameraPosition(this); - if (f.fNvert === 4) { - const v3 = shape.fVertices[f.fIvert[3]].fVec; - creator.addFace4(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], v3[0], v3[1], v3[2]); - creator.calcNormal(); - } else { - creator.addFace3(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2]); - creator.calcNormal(); - } + if (this.processRender3D) { + this.forEachPainter(objp => { + if (isFunc(objp.handleRender3D)) + objp.handleRender3D(); + }, 'objects'); } - - return creator.create(); } -/** @summary Creates Matrix4 from TGeoMatrix +/** @summary Returns assigned render object * @private */ -function createMatrix(matrix) { - if (!matrix) return null; +function getRenderer() { return this.renderer; } - let translation, rotation, scale; +/** @summary Check is 3D drawing need to be resized + * @private */ +function resize3D() { + const sz = this.getSizeFor3d(this.access3dKind()); - 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 - // eslint-disable-next-line no-fallthrough - 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}`); - } + this.apply3dSize(sz); - if (!translation && !rotation && !scale) return null; + if ((this.scene_width === sz.width) && (this.scene_height === sz.height)) + return false; - const res = new Matrix4(); + if ((sz.width < 10) || (sz.height < 10)) + return false; - 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); - } + this.scene_width = sz.width; + this.scene_height = sz.height; - if (translation) - res.setPosition(translation[0], translation[1], translation[2]); + this.camera.aspect = this.scene_width / this.scene_height; + this.camera.updateProjectionMatrix(); - if (scale) - res.scale(new Vector3(scale[0], scale[1], scale[2])); + this.renderer.setSize(this.scene_width, this.scene_height); - return res; + const xy3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width / sz.height * this.size_z3d) : this.size_z3d, + x3d = xy3d * this.x3dscale, + y3d = xy3d * this.y3dscale; + + if ((Math.abs(x3d - this.size_x3d) > 0.15 * this.size_z3d) || (Math.abs(y3d - this.size_y3d) > 0.15 * this.size_z3d)) { + this.size_x3d = x3d; + this.size_y3d = y3d; + this.control?.position0?.copy(getCameraDefaultPosition(this, true)); + return 1; // indicate significant resize + } + + return true; } -/** @summary Creates transformation matrix for TGeoNode - * @desc created after node visibility flag is checked and volume cut is performed +/** @summary Highlight bin in frame painter 3D drawing * @private */ -function getNodeMatrix(kind, node) { - let matrix = null; +function highlightBin3D(tip, selfmesh) { + const want_remove = !tip || (tip.x1 === undefined) || !this.enable_highlight; + let changed = false, tooltip_mesh = null, changed_self = true, mainp = this.getMainPainter(); - if (kind === kindEve) { - // special handling for EVE nodes + if (!mainp?.provideUserTooltip || !mainp?.hasUserTooltip()) + mainp = null; - matrix = new Matrix4(); + if (this.tooltip_selfmesh) { + changed_self = (this.tooltip_selfmesh !== selfmesh); + this.tooltip_selfmesh.material.color = this.tooltip_selfmesh.save_color; + delete this.tooltip_selfmesh; + changed = true; + } - if (node.fTrans) { - 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(node.fTrans[12], node.fTrans[13], node.fTrans[14]); + if (this.tooltip_mesh) { + tooltip_mesh = this.tooltip_mesh; + this.toplevel.remove(this.tooltip_mesh); + delete this.tooltip_mesh; + changed = true; + } + + if (want_remove) { + if (changed) { + this.render3D(); + mainp?.provideUserTooltip(null); } - } else if (node.fMatrix) - matrix = createMatrix(node.fMatrix); - else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) { - const kPatternReflected = BIT(14); - if ((node.fFinder.fBits & kPatternReflected) !== 0) - geoWarn('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': { - const _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; - - matrix = new Matrix4(); - - switch (node.fFinder._typename[node.fFinder._typename.length-1]) { - case 'X': matrix.setPosition(_shift, 0, 0); break; - case 'Y': matrix.setPosition(0, _shift, 0); break; - case 'Z': matrix.setPosition(0, 0, _shift); break; - } - break; - } + return; + } - case 'TGeoPatternCylPhi': { - const phi = (Math.PI/180)*(node.fFinder.fStart+(node.fIndex+0.5)*node.fFinder.fStep), - _cos = Math.cos(phi), _sin = Math.sin(phi); + if (tip.use_itself) { + selfmesh.save_color = selfmesh.material.color; + selfmesh.material.color = new THREE.Color(tip.color); + this.tooltip_selfmesh = selfmesh; + changed = changed_self; + } else { + changed = true; - matrix = new Matrix4(); + const indicies = Box3D.Indexes, + normals = Box3D.Normals, + vertices = Box3D.Vertices, + color = new THREE.Color(tip.color ? tip.color : 0xFF0000), + opacity = tip.opacity || 1; - matrix.set(_cos, -_sin, 0, 0, - _sin, _cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - break; - } + let pos, norm; - case 'TGeoPatternCylR': - // seems to be, require no transformation - matrix = new Matrix4(); - break; + if (!tooltip_mesh) { + pos = new Float32Array(indicies.length * 3); + norm = new Float32Array(indicies.length * 3); + const geom = new THREE.BufferGeometry(); + geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + geom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); + const material = new THREE.MeshBasicMaterial({ color, opacity, vertexColors: false }); + tooltip_mesh = new THREE.Mesh(geom, material); + } else { + pos = tooltip_mesh.geometry.attributes.position.array; + tooltip_mesh.geometry.attributes.position.needsUpdate = true; + tooltip_mesh.material.color = color; + tooltip_mesh.material.opacity = opacity; + } - case 'TGeoPatternTrapZ': { - const dz = node.fFinder.fStart + (node.fIndex+0.5)*node.fFinder.fStep; - matrix = new Matrix4(); - matrix.setPosition(node.fFinder.fTxz*dz, node.fFinder.fTyz*dz, dz); - break; - } + if (tip.x1 === tip.x2) + console.warn(`same tip X ${tip.x1} ${tip.x2}`); + if (tip.y1 === tip.y2) + console.warn(`same tip Y ${tip.y1} ${tip.y2}`); + if (tip.z1 === tip.z2) + tip.z2 = tip.z1 + 0.0001; // avoid zero faces - default: - geoWarn(`Unsupported pattern type ${node.fFinder._typename}`); - break; + for (let k = 0, nn = -3; k < indicies.length; ++k) { + const vert = vertices[indicies[k]]; + pos[k * 3] = tip.x1 + vert.x * (tip.x2 - tip.x1); + pos[k * 3 + 1] = tip.y1 + vert.y * (tip.y2 - tip.y1); + pos[k * 3 + 2] = tip.z1 + vert.z * (tip.z2 - tip.z1); + + if (norm) { + if (k % 6 === 0) + nn += 3; + norm[k * 3] = normals[nn]; + norm[k * 3 + 1] = normals[nn + 1]; + norm[k * 3 + 2] = normals[nn + 2]; + } } - } + this.tooltip_mesh = tooltip_mesh; + this.toplevel.add(tooltip_mesh); - return matrix; -} + if (tip.$painter && tip.$painter.options.System !== kCARTESIAN) { + convertLegoBuf(tip.$painter, pos); + tooltip_mesh.geometry.computeVertexNormals(); + } + } -/** @summary Returns number of faces for provided geometry - * @param {Object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons - * @private */ -function numGeometryFaces(geom) { - if (!geom) return 0; + if (changed) + this.render3D(); - if (geom instanceof Geometry) - return geom.tree.numPolygons(); + if (changed && tip.$projection && isFunc(tip.$painter?.redrawProjection)) + tip.$painter.redrawProjection(tip.ix - 1, tip.ix, tip.iy - 1, tip.iy); - // special array of polygons - if (geom.polygons) - return geom.polygons.length; + if (changed && mainp?.getObject()) { + mainp.provideUserTooltip({ + obj: mainp.getObject(), name: mainp.getObject().fName, + bin: tip.bin, cont: tip.value, + binx: tip.ix, biny: tip.iy, binz: tip.iz, + grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 + }); + } +} - const attr = geom.getAttribute('position'); - return attr?.count ? Math.round(attr.count / 3) : 0; +/** @summary Set options used for 3D drawings + * @private */ +function set3DOptions(hopt) { + this.opt3d = hopt; } -/** @summary Returns geometry bounding box +/** @summary Draw axes in 3D mode * @private */ -function geomBoundingBox(geom) { - if (!geom) return null; +function drawXYZ(toplevel, AxisPainter, opts) { + if (!opts) + opts = { ndim: 2 }; - let polygons = null; + if (opts.drawany === false) + opts.draw = false; + else + opts.drawany = true; - if (geom instanceof Geometry) - polygons = geom.tree.collectPolygons(); - else if (geom.polygons) - polygons = geom.polygons; + const pad = opts.v7 ? null : this.getPadPainter()?.getRootPad(true); + let grminx = -this.size_x3d, grmaxx = this.size_x3d, + grminy = -this.size_y3d, grmaxy = this.size_y3d, + grminz = 0, grmaxz = 2 * this.size_z3d, + scalingSize = this.size_z3d, + xmin = this.xmin, xmax = this.xmax, + ymin = this.ymin, ymax = this.ymax, + zmin = this.zmin, zmax = this.zmax, + y_zoomed = false, z_zoomed = false; - if (polygons !== null) { - const box = new Box3(); - for (let n = 0; n < polygons.length; ++n) { - const polygon = polygons[n], nvert = polygon.vertices.length; - for (let k = 0; k < nvert; ++k) - box.expandByPoint(polygon.vertices[k]); - } - return box; + if (!this.size_z3d) { + grminx = this.xmin; + grmaxx = this.xmax; + grminy = this.ymin; + grmaxy = this.ymax; + grminz = this.zmin; + grmaxz = this.zmax; + scalingSize = (grmaxz - grminz); } - if (!geom.boundingBox) - geom.computeBoundingBox(); + if (('zoom_xmin' in this) && ('zoom_xmax' in this) && (this.zoom_xmin !== this.zoom_xmax)) { + xmin = this.zoom_xmin; + xmax = this.zoom_xmax; + } + if (('zoom_ymin' in this) && ('zoom_ymax' in this) && (this.zoom_ymin !== this.zoom_ymax)) { + ymin = this.zoom_ymin; + ymax = this.zoom_ymax; + y_zoomed = true; + } + if (('zoom_zmin' in this) && ('zoom_zmax' in this) && (this.zoom_zmin !== this.zoom_zmax)) { + zmin = this.zoom_zmin; + zmax = this.zoom_zmax; + z_zoomed = true; + } - return geom.boundingBox.clone(); -} + if (opts.use_y_for_z) { + this.zmin = this.ymin; + this.zmax = this.ymax; + zmin = ymin; + zmax = ymax; + z_zoomed = y_zoomed; + ymin = 0; + ymax = 1; + } -/** @summary Creates half-space geometry for given shape - * @desc Just big-enough triangle to make BSP calculations - * @private */ -function createHalfSpace(shape, geom) { - if (!shape?.fN || !shape?.fP) return null; + // z axis range used for lego plot + this.lego_zmin = zmin; + this.lego_zmax = zmax; - const vertex = new Vector3(shape.fP[0], shape.fP[1], shape.fP[2]), - normal = new Vector3(shape.fN[0], shape.fN[1], shape.fN[2]); + // factor 1.1 used in ROOT for lego plots + if ((opts.zmult !== undefined) && !z_zoomed) + zmax *= opts.zmult; - normal.normalize(); + this.x_handle = new AxisPainter(null, this.xaxis); + if (opts.v7) { + this.x_handle.setPadPainter(this.getPadPainter()); + this.x_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.x_handle.setHistPainter(opts.hist_painter, 'x'); - let sz = 1e10; - if (geom) { - // using real size of other geometry, we probably improve precision - const box = geomBoundingBox(geom); - if (box) sz = box.getSize(new Vector3()).length() * 1000; - } - - const v0 = new Vector3(-sz, -sz/2, 0), - v1 = new Vector3(0, sz, 0), - v2 = new Vector3(sz, -sz/2, 0), - v3 = new Vector3(0, 0, -sz), - geometry = new BufferGeometry(), - positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z, - v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z, - v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, - v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]); - geometry.setAttribute('position', new BufferAttribute(positions, 3)); - geometry.computeVertexNormals(); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, xmin, xmax, false, [grminx, grmaxx], + { log: pad?.fLogx ?? 0, reverse: opts.reverse_x, logcheckmin: true }); + this.x_handle.assignFrameMembers(this, 'x'); + this.x_handle.extractDrawAttributes(scalingSize); - geometry.lookAt(normal); - geometry.computeVertexNormals(); + this.y_handle = new AxisPainter(null, this.yaxis); + if (opts.v7) { + this.y_handle.setPadPainter(this.getPadPainter()); + this.y_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.y_handle.setHistPainter(opts.hist_painter, 'y'); + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, ymin, ymax, false, [grminy, grmaxy], + { log: pad && !opts.use_y_for_z ? pad.fLogy : 0, reverse: opts.reverse_y, logcheckmin: opts.ndim > 1 }); + this.y_handle.assignFrameMembers(this, 'y'); + this.y_handle.extractDrawAttributes(scalingSize); - for (let k = 0; k < positions.length; k += 3) { - positions[k] = positions[k] + vertex.x; - positions[k+1] = positions[k+1] + vertex.y; - positions[k+2] = positions[k+2] + vertex.z; - } + this.z_handle = new AxisPainter(null, this.zaxis); + if (opts.v7) { + this.z_handle.setPadPainter(this.getPadPainter()); + this.z_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.z_handle.setHistPainter(opts.hist_painter, 'z'); + this.z_handle.configureAxis('zaxis', this.zmin, this.zmax, zmin, zmax, false, [grminz, grmaxz], { + value_axis: (opts.ndim === 1) || (opts.ndim === 2), + log: ((opts.use_y_for_z || (opts.ndim === 2)) ? pad?.fLogv : undefined) ?? pad?.fLogz ?? 0, + reverse: opts.reverse_z, logcheckmin: opts.ndim > 2 + }); + this.z_handle.assignFrameMembers(this, 'z'); + this.z_handle.extractDrawAttributes(scalingSize); - return geometry; -} + this.setRootPadRange(pad, true); // set some coordinates typical for 3D projections in ROOT -/** @summary Returns number of faces for provided geometry - * @param geom - can be BufferGeometry, CsgGeometry or interim array of polygons - * @private */ -function countGeometryFaces(geom) { - if (!geom) return 0; + const textMaterials = {}, lineMaterials = {}, + xticks = this.x_handle.createTicks(false, true), + yticks = this.y_handle.createTicks(false, true), + zticks = this.z_handle.createTicks(false, true); + let text_scale = 1; - if (geom instanceof Geometry) - return geom.tree.numPolygons(); + function getLineMaterial(handle, kind) { + const col = ((kind === 'ticks') ? handle.ticksColor : handle.lineatt.color) || 'black', + linewidth = (kind === 'ticks') ? handle.ticksWidth : handle.lineatt.width, + name = `${col}_${linewidth}`; + if (!lineMaterials[name]) + lineMaterials[name] = new THREE.LineBasicMaterial(getMaterialArgs(col, { linewidth, vertexColors: false })); + return lineMaterials[name]; + } - // special array of polygons - if (geom.polygons) - return geom.polygons.length; + function getTextMaterial(handle, kind, custom_color) { + const col = custom_color || ((kind === 'title') ? handle.titleFont?.color : handle.labelsFont?.color) || 'black'; + if (!textMaterials[col]) + textMaterials[col] = new THREE.MeshBasicMaterial(getMaterialArgs(col, { vertexColors: false })); + return textMaterials[col]; + } - const attr = geom.getAttribute('position'); - return attr?.count ? Math.round(attr.count / 3) : 0; -} + // main element, where all axis elements are placed + const top = new THREE.Object3D(); + top.axis_draw = true; // mark element as axis drawing + toplevel.add(top); -/** @summary Creates geometrey for composite shape - * @private */ -function createComposite(shape, faces_limit) { - if (faces_limit < 0) { - return createGeometry(shape.fNode.fLeft, -1) + - createGeometry(shape.fNode.fRight, -1); - } + let ticks = [], lbls = [], maxtextheight = 0, maxtextwidth = 0; + const center_x = this.x_handle.isCenteredLabels(), + rotate_x = this.x_handle.isRotateLabels(); - let geom1, geom2, return_bsp = false; - const matrix1 = createMatrix(shape.fNode.fLeftMat), - matrix2 = createMatrix(shape.fNode.fRightMat); + while (xticks.next()) { + const grx = xticks.grpos; + let is_major = xticks.kind === 1, + lbl = this.x_handle.format(xticks.tick, 2); - if (faces_limit === 0) faces_limit = 4000; - else return_bsp = true; + if (xticks.last_major()) { + if (!this.x_handle.fTitle) + lbl = 'x'; + } else if (lbl === null) { + is_major = false; + lbl = ''; + } - if (matrix1 && (matrix1.determinant() < -0.9)) - geoWarn('Axis reflection in left composite shape - not supported'); + if (is_major && lbl && opts.draw && (!center_x || !xticks.last_major())) { + const mod = xticks.get_modifier(); + if (mod?.fLabText) + lbl = mod.fLabText; - if (matrix2 && (matrix2.determinant() < -0.9)) - geoWarn('Axis reflections in right composite shape - not supported'); + const text3d = createLatexGeometry(this, lbl, this.x_handle.labelsFont.size); + text3d.computeBoundingBox(); + const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, + draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + text3d.center = true; // place central - if (shape.fNode.fLeft._typename === clTGeoHalfSpace) - geom1 = createHalfSpace(shape.fNode.fLeft); - else - geom1 = createGeometry(shape.fNode.fLeft, faces_limit); + text3d.offsety = this.x_handle.labelsOffset + (grmaxy - grminy) * 0.005; - if (!geom1) return null; + maxtextwidth = Math.max(maxtextwidth, draw_width); + maxtextheight = Math.max(maxtextheight, draw_height); - let n1 = countGeometryFaces(geom1), n2 = 0; - if (geom1._exceed_limit) n1 += faces_limit; + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); + text3d.grx = grx; + lbls.push(text3d); - if (n1 < faces_limit) { - if (shape.fNode.fRight._typename === clTGeoHalfSpace) - geom2 = createHalfSpace(shape.fNode.fRight, geom1); - else - geom2 = createGeometry(shape.fNode.fRight, faces_limit); + let space = 0; + if (!xticks.last_major()) { + space = Math.abs(xticks.next_major_grpos() - grx); + if ((draw_width > 0) && (space > 0)) + text_scale = Math.min(text_scale, 0.9 * space / draw_width); + } + if (rotate_x) + text3d.rotate = 1; + if (center_x) { + if (!space) + space = Math.min(grx - grminx, grmaxx - grx); + text3d.grx += space / 2; + } + } - n2 = countGeometryFaces(geom2); + ticks.push(grx, 0, 0, grx, this.x_handle.ticksSize * (is_major ? -1 : -0.6), 0); } - if ((n1 + n2 >= faces_limit) || !geom2) { - if (geom1.polygons) - geom1 = createBufferGeometry(geom1.polygons); - if (matrix1) geom1.applyMatrix4(matrix1); - geom1._exceed_limit = true; - return geom1; + if (this.x_handle.fTitle && opts.draw) { + const text3d = createLatexGeometry(this, this.x_handle.fTitle, this.x_handle.titleFont.size); + text3d.computeBoundingBox(); + text3d.center = this.x_handle.titleCenter; + text3d.opposite = this.x_handle.titleOpposite; + text3d.offsety = 1.6 * this.x_handle.titleOffset + (grmaxy - grminy) * 0.005; + text3d.grx = (grminx + grmaxx) / 2; // default position for centered title + text3d.kind = 'title'; + if (this.x_handle.isRotateTitle()) + text3d.rotate = 2; + + lbls.push(text3d); } - let bsp1 = new Geometry(geom1, matrix1, cfg.CompressComp ? 0 : undefined); + this.get3dZoomCoord = function(point, kind) { + // return axis coordinate from intersection point with axis geometry + const min = this[`scale_${kind}min`], max = this[`scale_${kind}max`]; + let pos = point[kind]; - const bsp2 = new Geometry(geom2, matrix2, bsp1.maxid); + switch (kind) { + case 'x': pos = (pos + this.size_x3d) / 2 / this.size_x3d; break; + case 'y': pos = (pos + this.size_y3d) / 2 / this.size_y3d; break; + case 'z': pos = pos / 2 / this.size_z3d; break; + } + if (this['log' + kind]) + pos = Math.exp(Math.log(min) + pos * (Math.log(max) - Math.log(min))); + else + pos = min + pos * (max - min); - // take over maxid from both geometries - bsp1.maxid = bsp2.maxid; + return pos; + }; - 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: - geoWarn('unsupported bool operation ' + shape.fNode._typename + ', use first geom'); - } + const createZoomMesh = (kind, size_3d, use_y_for_z) => { + const geom = new THREE.BufferGeometry(), tsz = Math.max(this[kind + '_handle'].ticksSize, 0.005 * size_3d); + let positions; + if (kind === 'z') + positions = new Float32Array([0, 0, 0, tsz * 4, 0, 2 * size_3d, tsz * 4, 0, 0, 0, 0, 0, 0, 0, 2 * size_3d, tsz * 4, 0, 2 * size_3d]); + else + positions = new Float32Array([-size_3d, 0, 0, size_3d, -tsz * 4, 0, size_3d, 0, 0, -size_3d, 0, 0, -size_3d, -tsz * 4, 0, size_3d, -tsz * 4, 0]); - if (countGeometryFaces(bsp1) === 0) { - geoWarn('Zero faces in comp shape' + - ` left: ${shape.fNode.fLeft._typename} ${countGeometryFaces(geom1)} faces` + - ` right: ${shape.fNode.fRight._typename} ${countGeometryFaces(geom2)} faces` + - ' use first'); - bsp1 = new Geometry(geom1, matrix1); - } + geom.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geom.computeVertexNormals(); - return return_bsp ? { polygons: bsp1.toPolygons() } : bsp1.toBufferGeometry(); -} + const material = new THREE.MeshBasicMaterial({ transparent: true, vertexColors: false, side: THREE.DoubleSide, opacity: 0 }), + mesh = new THREE.Mesh(geom, material); + mesh.zoom = kind; + mesh.size_3d = size_3d; + mesh.tsz = tsz; + mesh.use_y_for_z = use_y_for_z; + if (kind === 'y') + mesh.rotateZ(Math.PI / 2).rotateX(Math.PI); -/** @summary Try to create projected geometry - * @private */ -function projectGeometry(geom, matrix, projection, position, flippedMesh) { - if (!geom.boundingBox) geom.computeBoundingBox(); + mesh.v1 = new THREE.Vector3(positions[0], positions[1], positions[2]); + mesh.v2 = new THREE.Vector3(positions[6], positions[7], positions[8]); + mesh.v3 = new THREE.Vector3(positions[3], positions[4], positions[5]); - const box = geom.boundingBox.clone(); + mesh.globalIntersect = function(raycaster) { + if (!this.v1 || !this.v2 || !this.v3) + return undefined; - box.applyMatrix4(matrix); + const plane = new THREE.Plane(); + plane.setFromCoplanarPoints(this.v1, this.v2, this.v3); + plane.applyMatrix4(this.matrixWorld); - if (!position) position = 0; + const v1 = raycaster.ray.origin.clone(), + v2 = v1.clone().addScaledVector(raycaster.ray.direction, 1e10), + pnt = plane.intersectLine(new THREE.Line3(v1, v2), new THREE.Vector3()); - if (((box.min[projection] >= position) && (box.max[projection] >= position)) || - ((box.min[projection] <= position) && (box.max[projection] <= position))) - return null; // not interesting + if (!pnt) + return undefined; + let min = -this.size_3d, max = this.size_3d; + if (this.zoom === 'z') { + min = 0; + max = 2 * this.size_3d; + } - const bsp1 = new 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)); - let size = 10000; + if (pnt[this.zoom] < min) + pnt[this.zoom] = min; + else if (pnt[this.zoom] > max) + pnt[this.zoom] = max; - 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; - } + return pnt; + }; - const bsp2 = createNormal(projection, position, size); + mesh.showSelection = function(pnt1, pnt2) { + // used to show selection - bsp1.cut_from_plane(bsp2); + let tgtmesh = this.children ? this.children[0] : null, gg; + if (!pnt1 || !pnt2) { + if (tgtmesh) { + this.remove(tgtmesh); + disposeThreejsObject(tgtmesh); + } + return tgtmesh; + } - return bsp2.toBufferGeometry(); -} + if (!this.geometry) + return false; -/** @summary Creates geometry model for the provided shape - * @param {Object} shape - instance of TGeoShape object - * @param {Number} limit - defines return value, see details - * @desc - * - if limit === 0 (or undefined) returns BufferGeometry - * - if limit < 0 just returns estimated number of faces - * - if limit > 0 return list of CsgPolygons (used only for composite shapes) - * @private */ -function createGeometry(shape, limit) { - if (limit === undefined) limit = 0; + if (!tgtmesh) { + gg = this.geometry.clone(); + const pos = gg.getAttribute('position').array; - try { - switch (shape._typename) { - case clTGeoBBox: return createCubeBuffer(shape, limit); - case clTGeoPara: return createParaBuffer(shape, limit); - case clTGeoTrd1: - case clTGeoTrd2: return createTrapezoidBuffer(shape, limit); - case clTGeoArb8: - case clTGeoTrap: - case clTGeoGtra: return createArb8Buffer(shape, limit); - case clTGeoSphere: return createSphereBuffer(shape, limit); - case clTGeoCone: - case clTGeoConeSeg: - case clTGeoTube: - case clTGeoTubeSeg: - case clTGeoCtub: return createTubeBuffer(shape, limit); - case clTGeoEltu: return createEltuBuffer(shape, limit); - case clTGeoTorus: return createTorusBuffer(shape, limit); - case clTGeoPcon: - case clTGeoPgon: return createPolygonBuffer(shape, limit); - case clTGeoXtru: return createXtruBuffer(shape, limit); - case clTGeoParaboloid: return createParaboloidBuffer(shape, limit); - case clTGeoHype: return createHypeBuffer(shape, limit); - case 'TGeoTessellated': return createTessellatedBuffer(shape, limit); - case clTGeoCompositeShape: return createComposite(shape, limit); - case clTGeoShapeAssembly: break; - case clTGeoScaledShape: { - const res = createGeometry(shape.fShape, limit); - if (shape.fScale && (limit >= 0) && isFunc(res?.scale)) - res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]); - return res; + // original vertices [0, 2, 1, 0, 3, 2] + if (this.zoom === 'z') + pos[6] = pos[3] = pos[15] = this.tsz; + else + pos[4] = pos[16] = pos[13] = -this.tsz; + tgtmesh = new THREE.Mesh(gg, new THREE.MeshBasicMaterial({ color: 0xFF00, side: THREE.DoubleSide, vertexColors: false })); + this.add(tgtmesh); + } else + gg = tgtmesh.geometry; + + const pos = gg.getAttribute('position').array; + + if (this.zoom === 'z') { + pos[2] = pos[11] = pos[8] = pnt1[this.zoom]; + pos[5] = pos[17] = pos[14] = pnt2[this.zoom]; + } else { + pos[0] = pos[9] = pos[12] = pnt1[this.zoom]; + pos[6] = pos[3] = pos[15] = pnt2[this.zoom]; } - case clTGeoHalfSpace: - if (limit < 0) return 1; // half space if just plane used in composite - // no break here - warning should appear - // eslint-disable-next-line no-fallthrough - default: - geoWarn(`unsupported shape type ${shape._typename}`); - } - } catch (e) { - let 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; - } - geoWarn(`${shape._typename} err: ${e.message} ${place}`); - } - return limit < 0 ? 0 : null; -} + gg.getAttribute('position').needsUpdate = true; + return true; + }; -/** @summary Create single shape from EVE7 render date - * @private */ -function makeEveGeometry(rd) { - let off = 0; + return mesh; + }; - if (rd.sz[0]) { - rd.vtxBuff = new Float32Array(rd.raw.buffer, off, rd.sz[0]); - off += rd.sz[0]*4; - } + let xcont = new THREE.Object3D(), xtickslines; + xcont.position.set(0, grminy, grminz); + xcont.rotation.x = 1 / 4 * Math.PI; + xcont.xyid = 2; + xcont.painter = this.x_handle; - if (rd.sz[1]) { - // normals were not used - // rd.nrmBuff = new Float32Array(rd.raw.buffer, off, rd.sz[1]); - off += rd.sz[1]*4; + if (opts.draw) { + xtickslines = createLineSegments(ticks, getLineMaterial(this.x_handle, 'ticks')); + xcont.add(xtickslines); } - if (rd.sz[2]) { - // these are special values in the buffer begin - rd.prefixBuf = new Uint32Array(rd.raw.buffer, off, 2); - off += 2*4; - rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2]-2); - // off += (rd.sz[2]-2)*4; - } + lbls.forEach(lbl => { + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = lbl.center ? lbl.grx - w / 2 : (lbl.opposite ? grminx : grmaxx - w), + posy = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.x_handle.ticksSize - lbl.offsety, + m = new THREE.Matrix4(); - const GL_TRIANGLES = 4; // same as in EVE7 + // matrix to swap y and z scales and shift along z to its position + m.set(text_scale, 0, 0, posx, + 0, text_scale, 0, posy, + 0, 0, 1, 0, + 0, 0, 0, 1); - if (rd.prefixBuf[0] !== GL_TRIANGLES) - throw Error('Expect triangles first.'); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); - const nVert = 3 * rd.prefixBuf[1]; // number of vertices to draw + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); - if (rd.idxBuff.length !== nVert) - throw Error('Expect single list of triangles in index buffer.'); + mesh.applyMatrix4(m); + xcont.add(mesh); + }); - const body = new BufferGeometry(); - body.setAttribute('position', new BufferAttribute(rd.vtxBuff, 3)); - body.setIndex(new BufferAttribute(rd.idxBuff, 1)); - body.computeVertexNormals(); + if (opts.zoom && opts.drawany) + xcont.add(createZoomMesh('x', this.size_x3d)); + top.add(xcont); - return body; -} + xcont = new THREE.Object3D(); + xcont.position.set(0, grmaxy, grminz); + xcont.rotation.x = 3 / 4 * Math.PI; + xcont.painter = this.x_handle; -/** @summary Create single shape from geometry veiwer render date - * @private */ -function makeViewerGeometry(rd) { - const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength/4), + if (opts.draw) + xcont.add(new THREE.LineSegments(xtickslines.geometry, xtickslines.material)); - body = new BufferGeometry(); - body.setAttribute('position', new BufferAttribute(vtxBuff, 3)); - body.setIndex(new BufferAttribute(new Uint32Array(rd.idx), 1)); - body.computeVertexNormals(); - return body; -} + lbls.forEach(lbl => { + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = lbl.center ? lbl.grx + w / 2 : (lbl.opposite ? grminx + w : grmaxx), + posy = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.x_handle.ticksSize - lbl.offsety, + m = new THREE.Matrix4(); -/** @summary Create single shape from provided raw data from web viewer. - * @desc If nsegm changed, shape will be recreated - * @private */ -function createServerGeometry(rd, nsegm) { - if (rd.server_shape && ((rd.nsegm === nsegm) || !rd.shape)) - return rd.server_shape; + // matrix to swap y and z scales and shift along z to its position + m.set(-text_scale, 0, 0, posx, + 0, text_scale, 0, posy, + 0, 0, -1, 0, + 0, 0, 0, 1); - rd.nsegm = nsegm; + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); + mesh.applyMatrix4(m); + xcont.add(mesh); + }); - let g = null; + xcont.xyid = 4; + if (opts.zoom && opts.drawany) + xcont.add(createZoomMesh('x', this.size_x3d)); + top.add(xcont); - if (rd.shape) { - // case when TGeoShape provided as is - g = createGeometry(rd.shape); - } else { - if (!rd.raw?.buffer) { - console.error('No raw data at all'); - return null; - } + lbls = []; + text_scale = 1; + maxtextwidth = maxtextheight = 0; + ticks = []; - if (rd.sz) - g = makeEveGeometry(rd); - else - g = makeViewerGeometry(rd); - } + const center_y = this.y_handle.isCenteredLabels(), + rotate_y = this.y_handle.isRotateLabels(); - // shape handle is similar to created in TGeoPainter - return { - _typename: '$$Shape$$', // indicate that shape can be used as is - ready: true, - geom: g, - nfaces: numGeometryFaces(g) - }; -} + while (yticks.next()) { + const gry = yticks.grpos; + let is_major = (yticks.kind === 1), + lbl = this.y_handle.format(yticks.tick, 2); -/** @summary Provides info about geo object, used for tooltip info - * @param {Object} obj - any kind of TGeo-related object like shape or node or volume - * @private */ -function provideObjectInfo(obj) { - let info = [], shape = null; + if (yticks.last_major()) { + if (!this.y_handle.fTitle) + lbl = 'y'; + } else if (lbl === null) { + is_major = false; + lbl = ''; + } - 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 (is_major && lbl && opts.draw && (!center_y || !yticks.last_major())) { + const mod = yticks.get_modifier(); + if (mod?.fLabText) + lbl = mod.fLabText; - if (!shape) { - info.push(obj._typename); - return info; - } + const text3d = createLatexGeometry(this, lbl, this.y_handle.labelsFont.size); + text3d.computeBoundingBox(); + const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, + draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + text3d.center = true; - const sz = Math.max(shape.fDX, shape.fDY, shape.fDZ), - useexp = (sz > 1e7) || (sz < 1e-7), - 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); - }; + maxtextwidth = Math.max(maxtextwidth, draw_width); + maxtextheight = Math.max(maxtextheight, draw_height); - info.push(shape._typename); + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); + text3d.gry = gry; + text3d.offsetx = this.y_handle.labelsOffset + (grmaxx - grminx) * 0.005; + lbls.push(text3d); - info.push(`DX=${conv(shape.fDX)} DY=${conv(shape.fDY)} DZ=${conv(shape.fDZ)}`); + let space = 0; + if (!yticks.last_major()) { + space = Math.abs(yticks.next_major_grpos() - gry); + if (draw_width > 0) + text_scale = Math.min(text_scale, 0.9 * space / draw_width); + } + if (center_y) { + if (!space) + space = Math.min(gry - grminy, grmaxy - gry); + text3d.gry += space / 2; + } + if (rotate_y) + text3d.rotate = 1; + } + ticks.push(0, gry, 0, this.y_handle.ticksSize * (is_major ? -1 : -0.6), gry, 0); + } - switch (shape._typename) { - case clTGeoBBox: break; - case clTGeoPara: info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); break; - case clTGeoTrd2: info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy1)}`); // no break - // eslint-disable-next-line no-fallthrough - case clTGeoTrd1: info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx1)}`); break; - case clTGeoArb8: break; - case clTGeoTrap: break; - case clTGeoGtra: break; - case clTGeoSphere: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`, - `Theta1=${shape.fTheta1} Theta2=${shape.fTheta2}`); - break; - case clTGeoConeSeg: - info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - // no break; - // eslint-disable-next-line no-fallthrough - case clTGeoCone: - info.push(`Rmin1=${conv(shape.fRmin1)} Rmax1=${conv(shape.fRmax1)}`, - `Rmin2=${conv(shape.fRmin2)} Rmax2=${conv(shape.fRmax2)}`); - break; - case clTGeoCtub: - case clTGeoTubeSeg: - info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - // no break - // eslint-disable-next-line no-fallthrough - case clTGeoEltu: - case clTGeoTube: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`); - break; - case clTGeoTorus: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `Phi1=${shape.fPhi1} Dphi=${shape.fDphi}`); - break; - case clTGeoPcon: - case clTGeoPgon: break; - case clTGeoXtru: break; - case clTGeoParaboloid: - info.push(`Rlo=${conv(shape.fRlo)} Rhi=${conv(shape.fRhi)}`, - `A=${conv(shape.fA)} B=${conv(shape.fB)}`); - break; - case clTGeoHype: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `StIn=${conv(shape.fStIn)} StOut=${conv(shape.fStOut)}`); - break; - case clTGeoCompositeShape: break; - case clTGeoShapeAssembly: break; - case clTGeoScaledShape: - info = provideObjectInfo(shape.fShape); - if (shape.fScale) - info.unshift(`Scale X=${shape.fScale.fScale[0]} Y=${shape.fScale.fScale[1]} Z=${shape.fScale.fScale[2]}`); - break; + if (this.y_handle.fTitle && opts.draw) { + const text3d = createLatexGeometry(this, this.y_handle.fTitle, this.y_handle.titleFont.size); + text3d.computeBoundingBox(); + text3d.center = this.y_handle.titleCenter; + text3d.opposite = this.y_handle.titleOpposite; + text3d.offsetx = 1.6 * this.y_handle.titleOffset + (grmaxx - grminx) * 0.005; + text3d.gry = (grminy + grmaxy) / 2; // default position for centered title + text3d.kind = 'title'; + if (this.y_handle.isRotateTitle()) + text3d.rotate = 2; + lbls.push(text3d); } - return info; -} + if (!opts.use_y_for_z) { + let yticksline, ycont = new THREE.Object3D(); + ycont.position.set(grminx, 0, grminz); + ycont.rotation.y = -1 / 4 * Math.PI; + ycont.painter = this.y_handle; + if (opts.draw) { + yticksline = createLineSegments(ticks, getLineMaterial(this.y_handle, 'ticks')); + ycont.add(yticksline); + } -/** @summary Creates projection matrix for the camera - * @private */ -function createProjectionMatrix(camera) { - const cameraProjectionMatrix = new Matrix4(); + lbls.forEach(lbl => { + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.y_handle.ticksSize - lbl.offsetx, + posy = lbl.center ? lbl.gry + w / 2 : (lbl.opposite ? grminy + w : grmaxy), + m = new THREE.Matrix4(); + m.set(0, text_scale, 0, posx, + -text_scale, 0, 0, posy, + 0, 0, 1, 0, + 0, 0, 0, 1); - camera.updateMatrixWorld(); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); - camera.matrixWorldInverse.copy(camera.matrixWorld).invert(); - cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + mesh.applyMatrix4(m); + ycont.add(mesh); + }); - return cameraProjectionMatrix; -} + ycont.xyid = 3; + if (opts.zoom && opts.drawany) + ycont.add(createZoomMesh('y', this.size_y3d)); + top.add(ycont); -/** @summary Creates frustum - * @private */ -function createFrustum(source) { - if (!source) return null; + ycont = new THREE.Object3D(); + ycont.position.set(grmaxx, 0, grminz); + ycont.rotation.y = -3 / 4 * Math.PI; + ycont.painter = this.y_handle; + if (opts.draw) + ycont.add(new THREE.LineSegments(yticksline.geometry, yticksline.material)); - if (source instanceof PerspectiveCamera) - source = createProjectionMatrix(source); + lbls.forEach(lbl => { + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.y_handle.ticksSize - lbl.offsetx, + posy = lbl.center ? lbl.gry - w / 2 : (lbl.opposite ? grminy : grmaxy - w), + m = new THREE.Matrix4(); + + m.set(0, text_scale, 0, posx, + text_scale, 0, 0, posy, + 0, 0, -1, 0, + 0, 0, 0, 1); - const frustum = new Frustum(); - frustum.setFromProjectionMatrix(source); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); - 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 - ]); + mesh.applyMatrix4(m); + ycont.add(mesh); + }); + ycont.xyid = 1; + if (opts.zoom && opts.drawany) + ycont.add(createZoomMesh('y', this.size_y3d)); + top.add(ycont); + } - frustum.test = new Vector3(0, 0, 0); + lbls = []; + text_scale = 1; + ticks = []; // just array, will be used for the buffer geometry - frustum.CheckShape = function(matrix, shape) { - const pnt = this.test, len = this.corners.length, corners = this.corners; + let zgridx = null, zgridy = null, lastmajorz = null, maxzlblwidth = 0; - for (let 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; - } + const center_z = this.z_handle.isCenteredLabels(), + rotate_z = this.z_handle.isRotateLabels(); - return false; - }; + if (this.size_z3d && opts.drawany) { + zgridx = []; + zgridy = []; + } - frustum.CheckBox = function(box) { - const pnt = this.test; - let 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 - }; + while (zticks.next()) { + const grz = zticks.grpos; + let is_major = (zticks.kind === 1), + lbl = this.z_handle.format(zticks.tick, 2); - return frustum; -} + if (lbl === null) { + is_major = false; + lbl = ''; + } -/** @summary Create node material - * @private */ -function createMaterial(cfg, args0) { - if (!cfg) cfg = { material_kind: 'lambert' }; + if (is_major && lbl && opts.draw && (!center_z || !zticks.last_major())) { + const mod = zticks.get_modifier(); + if (mod?.fLabText) + lbl = mod.fLabText; - const args = Object.assign({}, args0); + const text3d = createLatexGeometry(this, lbl, this.z_handle.labelsFont.size); + text3d.computeBoundingBox(); + const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, + draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + text3d.translate(-draw_width, -draw_height / 2, 0); - if (args.opacity === undefined) - args.opacity = 1; + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); + text3d.grz = grz; + lbls.push(text3d); - if (cfg.transparency) - args.opacity = Math.min(1 - cfg.transparency, args.opacity); + if ((lastmajorz !== null) && (draw_height > 0)) + text_scale = Math.min(text_scale, 0.9 * (grz - lastmajorz) / draw_height); - args.wireframe = cfg.wireframe ?? false; - if (!args.color) args.color = 'red'; - args.side = FrontSide; - args.transparent = args.opacity < 1; - args.depthWrite = args.opactity === 1; + maxzlblwidth = Math.max(maxzlblwidth, draw_width); - let material; + lastmajorz = grz; + } - if (cfg.material_kind === 'basic') - material = new MeshBasicMaterial(args); - else if (cfg.material_kind === 'depth') { - delete args.color; - material = new MeshDepthMaterial(args); - } else if (cfg.material_kind === 'toon') - material = new MeshToonMaterial(args); - else if (cfg.material_kind === 'matcap') { - delete args.wireframe; - material = new MeshMatcapMaterial(args); - } else if (cfg.material_kind === 'standard') { - args.metalness = cfg.metalness ?? 0.5; - args.roughness = cfg.roughness ?? 0.1; - material = new MeshStandardMaterial(args); - } else if (cfg.material_kind === 'normal') { - delete args.color; - material = new MeshNormalMaterial(args); - } else if (cfg.material_kind === 'physical') { - args.metalness = cfg.metalness ?? 0.5; - args.roughness = cfg.roughness ?? 0.1; - args.reflectivity = cfg.reflectivity ?? 0.5; - args.emissive = args.color; - material = new MeshPhysicalMaterial(args); - } else if (cfg.material_kind === 'phong') { - args.shininess = cfg.shininess ?? 0.9; - material = new MeshPhongMaterial(args); - } else { - args.vertexColors = false; - material = new MeshLambertMaterial(args); + // create grid + if (zgridx && is_major) + zgridx.push(grminx, 0, grz, grmaxx, 0, grz); + + if (zgridy && is_major) + zgridy.push(0, grminy, grz, 0, grmaxy, grz); + + ticks.push(0, 0, grz, this.z_handle.ticksSize * (is_major ? 1 : 0.6), 0, grz); } - if ((material.flatShading !== undefined) && (cfg.flatShading !== undefined)) - material.flatShading = cfg.flatShading; - material.inherentOpacity = args0.opacity ?? 1; - material.inherentArgs = args0; + if (zgridx?.length) { + const material = new THREE.LineDashedMaterial({ color: this.x_handle.ticksColor, dashSize: 2, gapSize: 2 }), + lines1 = createLineSegments(zgridx, material); - return material; -} + lines1.position.set(0, grmaxy, 0); + lines1.grid = 2; // mark as grid + lines1.visible = false; + top.add(lines1); + const lines2 = new THREE.LineSegments(lines1.geometry, material); + lines2.position.set(0, grminy, 0); + lines2.grid = 4; // mark as grid + lines2.visible = false; + top.add(lines2); + } -/** @summary Compares two stacks. - * @return {Number} 0 if same, -1 when stack1 < stack2, +1 when stack1 > stack2 - * @private */ -function compare_stacks(stack1, stack2) { - if (stack1 === stack2) - return 0; - const len1 = stack1?.length ?? 0, - len2 = stack2?.length ?? 0, - len = (len1 < len2) ? len1 : len2; - let indx = 0; - while (indx < len) { - if (stack1[indx] < stack2[indx]) - return -1; - if (stack1[indx] > stack2[indx]) - return 1; - ++indx; + if (zgridy?.length) { + const material = new THREE.LineDashedMaterial({ color: this.y_handle.ticksColor, dashSize: 2, gapSize: 2 }), + lines1 = createLineSegments(zgridy, material); + + lines1.position.set(grmaxx, 0, 0); + lines1.grid = 3; // mark as grid + lines1.visible = false; + top.add(lines1); + + const lines2 = new THREE.LineSegments(lines1.geometry, material); + lines2.position.set(grminx, 0, 0); + lines2.grid = 1; // mark as grid + lines2.visible = false; + top.add(lines2); } - return (len1 < len2) ? -1 : ((len1 > len2) ? 1 : 0); -} + const zcont = [], zticksline = opts.draw ? createLineSegments(ticks, getLineMaterial(this.z_handle, 'ticks')) : null; + for (let n = 0; n < 4; ++n) { + zcont.push(new THREE.Object3D()); -/** @summary Checks if two stack arrays are identical - * @private */ -function isSameStack(stack1, stack2) { - if (!stack1 || !stack2) return false; - if (stack1 === stack2) return true; - if (stack1.length !== stack2.length) return false; - for (let k = 0; k < stack1.length; ++k) - if (stack1[k] !== stack2[k]) return false; - return true; -} + lbls.forEach((lbl, indx) => { + const m = new THREE.Matrix4(), + dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x; + let grz = lbl.grz; -/** - * @summary class for working with cloned nodes - * - * @private - */ + if (center_z) { + if (indx < lbls.length - 1) + grz = (grz + lbls[indx + 1].grz) / 2; + else if (indx > 0) + grz = Math.min(1.5 * grz - lbls[indx - 1].grz * 0.5, grmaxz); + } -class ClonedNodes { + // matrix to swap y and z scales and shift along z to its position + m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + this.z_handle.labelsOffset, + 0, 0, 1, 0, + 0, text_scale, 0, grz); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.z_handle)); + if (rotate_z) + mesh.rotateZ(-Math.PI / 2).translateX(dx / 2); + mesh.applyMatrix4(m); + zcont[n].add(mesh); + }); - /** @summary Constructor */ - 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 - this.vislevel = 4; // maximal depth of nodes visibility aka gGeoManager->SetVisLevel, same default - this.maxnodes = 10000; // maximal number of visisble nodes aka gGeoManager->fMaxVisNodes + if (this.z_handle.fTitle && opts.draw) { + const text3d = createLatexGeometry(this, this.z_handle.fTitle, this.z_handle.titleFont.size); + text3d.computeBoundingBox(); + const dx = text3d.boundingBox.max.x - text3d.boundingBox.min.x, + dy = text3d.boundingBox.max.y - text3d.boundingBox.min.y, + rotate = this.z_handle.isRotateTitle(), + posz = this.z_handle.titleCenter ? (grmaxz + grminz - dx) / 2 : (this.z_handle.titleOpposite ? grminz : grmaxz - dx) + (rotate ? dx : 0), + m = new THREE.Matrix4(); - if (obj) { - if (obj.$geoh) this.toplevel = false; - this.createClones(obj); - } else if (clones) - this.nodes = clones; - } + m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + maxzlblwidth + this.z_handle.titleOffset, + 0, 0, 1, 0, + 0, text_scale, 0, posz); + const mesh = new THREE.Mesh(text3d, getTextMaterial(this.z_handle, 'title')); + mesh.rotateZ(Math.PI * (rotate ? 1.5 : 0.5)); + if (rotate) + mesh.translateY(-dy); - /** @summary Set maximal depth for nodes visibility */ - setVisLevel(lvl) { - this.vislevel = lvl && Number.isInteger(lvl) ? lvl : 4; - } + mesh.applyMatrix4(m); + zcont[n].add(mesh); + } - /** @summary Returns maximal depth for nodes visibility */ - getVisLevel() { - return this.vislevel; - } + if (opts.draw && zticksline) + zcont[n].add(n === 0 ? zticksline : new THREE.LineSegments(zticksline.geometry, zticksline.material)); - /** @summary Set maximal number of visible nodes */ - setMaxVisNodes(v, more) { - this.maxnodes = Number.isFinite(v) ? v : 10000; - if (more && Number.isFinite(more)) - this.maxnodes *= more; + if (opts.zoom && opts.drawany) + zcont[n].add(createZoomMesh('z', this.size_z3d, opts.use_y_for_z)); + + zcont[n].zid = n + 2; + top.add(zcont[n]); + zcont[n].painter = this.z_handle; } - /** @summary Returns configured maximal number of visible nodes */ - getMaxVisNodes() { - return this.maxnodes; + zcont[0].position.set(grminx, grmaxy, 0); + zcont[0].rotation.z = 3 / 4 * Math.PI; + + zcont[1].position.set(grmaxx, grmaxy, 0); + zcont[1].rotation.z = 1 / 4 * Math.PI; + + zcont[2].position.set(grmaxx, grminy, 0); + zcont[2].rotation.z = -1 / 4 * Math.PI; + + zcont[3].position.set(grminx, grminy, 0); + zcont[3].rotation.z = -3 / 4 * Math.PI; + + if (!opts.drawany) + return; + + const linex_material = getLineMaterial(this.x_handle), + linex_geom = createLineSegments([grminx, 0, 0, grmaxx, 0, 0], linex_material, null, true); + for (let n = 0; n < 2; ++n) { + let line = new THREE.LineSegments(linex_geom, linex_material); + line.position.set(0, grminy, n === 0 ? grminz : grmaxz); + line.xyboxid = 2; + line.bottom = (n === 0); + top.add(line); + + line = new THREE.LineSegments(linex_geom, linex_material); + line.position.set(0, grmaxy, n === 0 ? grminz : grmaxz); + line.xyboxid = 4; + line.bottom = (n === 0); + top.add(line); } - /** @summary Set geo painter configuration - used for material creation */ - setConfig(cfg) { - this._cfg = cfg; + const liney_material = getLineMaterial(this.y_handle), + liney_geom = createLineSegments([0, grminy, 0, 0, grmaxy, 0], liney_material, null, true); + for (let n = 0; n < 2; ++n) { + let line = new THREE.LineSegments(liney_geom, liney_material); + line.position.set(grminx, 0, n === 0 ? grminz : grmaxz); + line.xyboxid = 3; + line.bottom = n === 0; + top.add(line); + + line = new THREE.LineSegments(liney_geom, liney_material); + line.position.set(grmaxx, 0, n === 0 ? grminz : grmaxz); + line.xyboxid = 1; + line.bottom = n === 0; + top.add(line); } - /** @summary Insert node into existing array */ - updateNode(node) { - if (node && Number.isInteger(node.id) && (node.id < this.nodes.length)) - this.nodes[node.id] = node; + const linez_material = getLineMaterial(this.z_handle), + linez_geom = createLineSegments([0, 0, grminz, 0, 0, grmaxz], linez_material, null, true); + for (let n = 0; n < 4; ++n) { + const line = new THREE.LineSegments(linez_geom, linez_material); + line.zboxid = zcont[n].zid; + line.position.copy(zcont[n].position); + top.add(line); } +} - /** @summary Returns TGeoShape for element with given indx */ - getNodeShape(indx) { - if (!this.origin || !this.nodes) return null; - const obj = this.origin[indx], clone = this.nodes[indx]; - if (!obj || !clone) return null; - if (clone.kind === kindGeo) { - if (obj.fVolume) return obj.fVolume.fShape; - } else - return obj.fShape; - return null; - } +/** @summary Converts 3D coordinate to the pad NDC + * @private */ +function convert3DtoPadNDC(x, y, z) { + x = this.x_handle.gr(x); + y = this.y_handle.gr(y); + z = this.z_handle.gr(z); - /** @summary function to cleanup as much as possible structures - * @desc Provided parameters drawnodes and drawshapes are arrays created during building of geometry */ - cleanup(drawnodes, drawshapes) { - if (drawnodes) { - for (let n = 0; n < drawnodes.length; ++n) { - delete drawnodes[n].stack; - drawnodes[n] = undefined; - } - } + const vector = new THREE.Vector3().set(x, y, z); - if (drawshapes) { - for (let n = 0; n < drawshapes.length; ++n) { - delete drawshapes[n].geom; - drawshapes[n] = undefined; - } - } + // map to normalized device coordinate (NDC) space + vector.project(this.camera); - if (this.nodes) { - for (let n = 0; n < this.nodes.length; ++n) { - if (this.nodes[n]) - delete this.nodes[n].chlds; - } - } + vector.x = (vector.x + 1) / 2; + vector.y = (vector.y + 1) / 2; - delete this.nodes; - delete this.origin; + const pp = this.getPadPainter(), + pw = pp?.getPadWidth(), + ph = pp?.getPadHeight(); - delete this.sortmap; + if (pw && ph) { + vector.x = (this.scene_x + vector.x * this.scene_width) / pw; + vector.y = (this.scene_y + vector.y * this.scene_height) / ph; } - /** @summary Create complete description for provided Geo object */ - createClones(obj, sublevel, kind) { - if (!sublevel) { - if (obj?._typename === '$$Shape$$') - return this.createClonesForShape(obj); + return vector; +} - this.origin = []; - sublevel = 1; - kind = getNodeKind(obj); - } +/** @summary Assign 3D methods for frame painter + * @private */ +function assignFrame3DMethods(fp) { + Object.assign(fp, { + create3DScene, add3DMesh, get3DMeshes, remove3DMeshes, getRenderer, + render3D, resize3D, change3DCamera, highlightBin3D, set3DOptions, drawXYZ, convert3DtoPadNDC + }); +} - if ((kind < 0) || !obj || ('_refid' in obj)) return; - obj._refid = this.origin.length; - this.origin.push(obj); - if (sublevel > this.maxdepth) this.maxdepth = sublevel; +/** @summary Create 3D objects in the frame + * @private */ +async function crete3DFrame(painter, AxisPainterClass, render3d = constants$1.Render3D.None) { + const fp = painter.getFramePainter(), + o = painter.getOptions(), + histo = painter.getHisto(), + ndim = painter.getDimension(); + + assignFrame3DMethods(fp); + + return fp.create3DScene(render3d, o.x3dscale, o.y3dscale, o.Ortho).then(() => { + fp.setAxesRanges(histo.fXaxis, painter.xmin, painter.xmax, histo.fYaxis, painter.ymin, painter.ymax, histo.fZaxis, painter.zmin, painter.zmax, painter); + fp.set3DOptions(o); + fp.drawXYZ(fp.toplevel, AxisPainterClass, { + ndim, + use_y_for_z: ndim === 1, + hist_painter: painter, + zmult: o.zmult ?? 1, + zoom: (render3d !== constants$1.Render3D.None) && settings.Zooming, + draw: o.Axis !== -1, + drawany: o.isCartesian(), + reverse_x: o.RevX, + reverse_y: o.RevY + }); + return fp; + }); +} - let chlds = null; - if (kind === kindGeo) - chlds = obj.fVolume?.fNodes?.arr || null; - else - chlds = obj.fElements?.arr || null; +function _meshLegoToolTip(intersect) { + if ((intersect.faceIndex < 0) || (intersect.faceIndex >= this.face_to_bins_index.length)) + return null; - if (chlds !== null) { - checkDuplicates(obj, chlds); - for (let i = 0; i < chlds.length; ++i) - this.createClones(chlds[i], sublevel + 1, kind); - } + const p = this.tip_painter; - if (sublevel > 1) return; + if (!p) { + console.error('painter for tip handling is not there'); + return null; + } - this.nodes = []; + const handle = this.handle, + fp = p.getFramePainter(), + histo = p.getHisto(), + tip = p.get3DToolTip(this.face_to_bins_index[intersect.faceIndex]), + x1 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, handle.grx[tip.ix - 1] + handle.xbar1 * (handle.grx[tip.ix] - handle.grx[tip.ix - 1]))), + x2 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, handle.grx[tip.ix - 1] + handle.xbar2 * (handle.grx[tip.ix] - handle.grx[tip.ix - 1]))), + y1 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, handle.gry[tip.iy - 1] + handle.ybar1 * (handle.gry[tip.iy] - handle.gry[tip.iy - 1]))), + y2 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, handle.gry[tip.iy - 1] + handle.ybar2 * (handle.gry[tip.iy] - handle.gry[tip.iy - 1]))); - const sortarr = []; + tip.x1 = Math.min(x1, x2); + tip.x2 = Math.max(x1, x2); + tip.y1 = Math.min(y1, y2); + tip.y2 = Math.max(y1, y2); - // first create nodes objects - for (let id = 0; id < this.origin.length; ++id) { - // let obj = this.origin[id]; - const node = { id, kind, vol: 0, nfaces: 0 }; - this.nodes.push(node); - sortarr.push(node); // array use to produce sortmap - } + let binz1 = this.baseline, binz2 = tip.value; + if (histo.$baseh) + binz1 = histo.$baseh.getBinContent(tip.ix, tip.iy); + if (binz2 < binz1) + [binz1, binz2] = [binz2, binz1]; - // than fill children lists - for (let n = 0; n < this.origin.length; ++n) { - const obj = this.origin[n], clone = this.nodes[n]; - let chlds = null, shape = null; + tip.z1 = fp.grz(Math.max(this.zmin, binz1)); + tip.z2 = fp.grz(Math.min(this.zmax, binz2)); - if (kind === kindEve) { - 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; - } + tip.color = this.tip_color; + tip.$painter = p; + tip.$projection = (p.getDimension() === 2) && isFunc(p.isProjection) && p.isProjection(); - 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) { - let 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 (clone.matrix && (kind === kindEve)) // deepscan-disable-line INSUFFICIENT_NULL_CHECK - clone.abs_matrix = true; - } - if (shape) { - clone.fDX = shape.fDX; - clone.fDY = shape.fDY; - clone.fDZ = shape.fDZ; - clone.vol = Math.sqrt(shape.fDX**2 + shape.fDY**2 + shape.fDZ**2); - if (shape.$nfaces === undefined) - shape.$nfaces = createGeometry(shape, -1); - clone.nfaces = shape.$nfaces; - if (clone.nfaces <= 0) clone.vol = 0; - } - - if (!chlds) continue; + return tip; +} - // in cloned object children is only list of ids - clone.chlds = new Array(chlds.length); - for (let k = 0; k < chlds.length; ++k) - clone.chlds[k] = chlds[k]._refid; - } +/** @summary Draw histograms in 3D mode + * @private */ +function drawBinsLego(painter, is_v7 = false) { + if (!painter.draw_content) + return; - // remove _refid identifiers from original objects - for (let n = 0; n < this.origin.length; ++n) - delete this.origin[n]._refid; + // Perform TH1/TH2 lego plot with BufferGeometry - // do sorting once - sortarr.sort((a, b) => b.vol - a.vol); + const vertices = Box3D.Vertices, + indicies = Box3D.Indexes, + vnormals = Box3D.Normals, + segments = Box3D.Segments, + // reduced line segments + rsegments = [0, 1, 1, 2, 2, 3, 3, 0], + // reduced vertices + rvertices = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 1, 0), new THREE.Vector3(1, 0, 0)], + fp = painter.getFramePainter(), + handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), + test_cutg = painter.options.cutg, + i1 = handle.i1, i2 = handle.i2, j1 = handle.j1, j2 = handle.j2, + histo = painter.getHisto(), + basehisto = histo.$baseh, + split_faces = (painter.options.Lego === 11) || (painter.options.Lego === 13), // split each layer on two parts + use16indx = (histo.getBin(i2, j2) < 0xFFFF); // if bin ID fit into 16 bit, use smaller arrays for intersect indexes - // remember sort map and also sortid - this.sortmap = new Array(this.nodes.length); - for (let n = 0; n < this.nodes.length; ++n) { - this.sortmap[n] = sortarr[n].id; - sortarr[n].sortid = n; - } - } + if ((i1 >= i2) || (j1 >= j2)) + return; - /** @summary Create elementary item with single already existing shape - * @desc used by details view of geometry shape */ - createClonesForShape(obj) { - this.origin = []; + let zmin, zmax, i, j, k, vert, binz1, binz2, reduced, nobottom, notop, + axis_zmin = fp.z_handle.getScaleMin(), + axis_zmax = fp.z_handle.getScaleMax(); - // indicate that just plain shape is used - this.plain_shape = obj; + const getBinContent = (ii, jj, level) => { + // return bin content in binz1, binz2, reduced flags + // return true if bin should be displayed - const node = { - id: 0, sortid: 0, kind: kindShape, - name: 'Shape', - nfaces: obj.nfaces, - fDX: 1, fDY: 1, fDZ: 1, vol: 1, - vis: true - }; + binz2 = histo.getBinContent(ii + 1, jj + 1); + if (basehisto) + binz1 = basehisto.getBinContent(ii + 1, jj + 1); + else if (painter.options.BaseLine !== false) + binz1 = painter.options.BaseLine; + else + binz1 = painter.options.Zero ? axis_zmin : 0; + if (binz2 < binz1) + [binz1, binz2] = [binz2, binz1]; - this.nodes = [node]; - } + if ((binz1 >= zmax) || (binz2 < zmin)) + return false; - /** @summary Count all visisble nodes */ - countVisibles() { - const len = this.nodes?.length || 0; - let cnt = 0; - for (let k = 0; k < len; ++k) - if (this.nodes[k].vis) cnt++; - return cnt; - } + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(ii + 0.5), histo.fYaxis.GetBinCoord(jj + 0.5))) + return false; - /** @summary Mark visisble nodes. - * @desc Set only basic flags, actual visibility depends from hierarchy */ - markVisibles(on_screen, copy_bits, hide_top_volume) { - if (this.plain_shape) - return 1; - if (!this.origin || !this.nodes) - return 0; + reduced = (binz2 === zmin) || (binz1 >= binz2); - let res = 0; + if (!reduced || (level > 0)) + return true; - for (let n = 0; n < this.nodes.length; ++n) { - const clone = this.nodes[n], obj = this.origin[n]; + if (basehisto) + return false; // do not draw empty bins on top of other bins - clone.vis = 0; // 1 - only with last level - delete clone.nochlds; + if (painter.options.Zero || (axis_zmin > 0)) + return true; - if (clone.kind === kindGeo) { - if (obj.fVolume) { - if (on_screen) { - // on screen bits used always, childs always checked - clone.vis = testGeoBit(obj.fVolume, geoBITS.kVisOnScreen) ? 99 : 0; + return painter.options.ShowEmpty; + }; - if ((n === 0) && clone.vis && hide_top_volume) clone.vis = 0; + let levels = [axis_zmin, axis_zmax], palette = null; - if (copy_bits) { - setGeoBit(obj.fVolume, geoBITS.kVisNone, false); - setGeoBit(obj.fVolume, geoBITS.kVisThis, (clone.vis > 0)); - setGeoBit(obj.fVolume, geoBITS.kVisDaughters, true); - setGeoBit(obj, geoBITS.kVisDaughters, true); - } - } else { - clone.vis = !testGeoBit(obj.fVolume, geoBITS.kVisNone) && testGeoBit(obj.fVolume, geoBITS.kVisThis) ? 99 : 0; + // DRAW ALL CUBES - if (!testGeoBit(obj, geoBITS.kVisDaughters) || !testGeoBit(obj.fVolume, geoBITS.kVisDaughters)) - clone.nochlds = true; + if ((painter.options.Lego === 12) || (painter.options.Lego === 14)) { + // drawing colors levels, axis can not exceed palette - // node with childs only shown in case if it is last level in hierarchy - if ((clone.vis > 0) && clone.chlds && !clone.nochlds) - clone.vis = 1; + if (is_v7) { + palette = fp.getHistPalette(); + painter.createContour(fp, palette, { full_z_range: true }); + levels = palette.getContour(); + axis_zmin = levels.at(0); + axis_zmax = levels.at(-1); + } else { + const cntr = painter.createContour(histo.fContour ? histo.fContour.length : 20, fp.lego_zmin, fp.lego_zmax); + levels = cntr.arr; + palette = painter.getHistPalette(); + } + } - // special handling for top node - if (n === 0) { - if (hide_top_volume) clone.vis = 0; - delete clone.nochlds; - } - } - } - } else { - clone.vis = obj.fRnrSelf ? 99 : 0; + for (let nlevel = 0; nlevel < levels.length - 1; ++nlevel) { + zmin = levels[nlevel]; + zmax = levels[nlevel + 1]; - // when the only node is selected, draw it - if ((n === 0) && (this.nodes.length === 1)) clone.vis = 99; + // artificially extend last level of color palette to maximal visible value + if (palette && (nlevel === levels.length - 2) && zmax < axis_zmax) + zmax = axis_zmax; - this.vislevel = 9999; // automatically take all volumes - } + const grzmin = fp.grz(zmin), grzmax = fp.grz(zmax); + let numvertices = 0, num2vertices = 0; - // shape with zero volume or without faces will not be observed - if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = 0; + // now calculate size of buffer geometry for boxes - if (clone.vis) res++; - } + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + if (!getBinContent(i, j, nlevel)) + continue; - return res; - } + nobottom = !reduced && (nlevel > 0); + notop = !reduced && (binz2 > zmax) && (nlevel < levels.length - 2); - /** @summary After visibility flags is set, produce idshift for all nodes as it would be maximum level */ - produceIdShifts() { - for (let k = 0; k < this.nodes.length; ++k) - this.nodes[k].idshift = -1; + numvertices += (reduced ? 12 : indicies.length); + if (nobottom) + numvertices -= 6; + if (notop) + numvertices -= 6; - function scan_func(nodes, node) { - if (node.idshift < 0) { - node.idshift = 0; - if (node.chlds) { - for (let k = 0; k 0); + notop = !reduced && (binz2 > zmax) && (nlevel < levels.length - 2); - if (res > 0) { - if (!do_clear) - this.fVisibility.splice(indx, 0, { visible: on, stack }); - return; - } - } + const y1 = handle.gry[j] + handle.ybar1 * (handle.gry[j + 1] - handle.gry[j]), + y2 = handle.gry[j] + handle.ybar2 * (handle.gry[j + 1] - handle.gry[j]), + z1 = (binz1 <= zmin) ? grzmin : fp.grz(binz1), + z2 = (binz2 > zmax) ? grzmax : fp.grz(binz2); - if (!do_clear) - this.fVisibility.push({ visible: on, stack }); - } + nn = 0; // counter over the normals, each normals correspond to 6 vertices + k = 0; // counter over vertices - /** @summary Get visibility item for physical node */ - getPhysNodeVisibility(stack) { - if (!stack || !this.fVisibility) - return null; - for (let indx = 0; indx < this.fVisibility.length; ++indx) { - const item = this.fVisibility[indx], - res = compare_stacks(item.stack, stack); - if (res === 0) - return item; - if (res > 0) - return null; - } + if (reduced) { + // we skip all side faces, keep only top and bottom + nn += 12; + k += 24; + } - return null; - } + const bin_index = histo.getBin(i + 1, j + 1); + let size = indicies.length; + if (nobottom) + size -= 6; - /** @summary Scan visible nodes in hierarchy, starting from nodeid - * @desc Each entry in hierarchy get its unique id, which is not changed with visibility flags */ - scanVisible(arg, vislvl) { - if (!this.nodes) return 0; + // array over all vertices of the single bin + while (k < size) { + vert = vertices[indicies[k]]; - if (vislvl === undefined) { - if (!arg) arg = {}; + if (split_faces && (k < 12)) { + pos2[v2] = x1 + vert.x * (x2 - x1); + pos2[v2 + 1] = y1 + vert.y * (y2 - y1); + pos2[v2 + 2] = z1 + vert.z * (z2 - z1); - vislvl = arg.vislvl || this.vislevel || 4; // default 3 in ROOT - if (vislvl > 88) vislvl = 88; + norm2[v2] = vnormals[nn]; + norm2[v2 + 1] = vnormals[nn + 1]; + norm2[v2 + 2] = vnormals[nn + 2]; + if (v2 % 9 === 0) + face_to_bins_indx2[v2 / 9] = bin_index; // remember which bin corresponds to the face + v2 += 3; + } else { + positions[v] = x1 + vert.x * (x2 - x1); + positions[v + 1] = y1 + vert.y * (y2 - y1); + positions[v + 2] = z1 + vert.z * (z2 - z1); - 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) { - const entry = { nodeid: this.nodeid, seqid: this.counter, stack: new Array(this.last) }; - if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be built as first - for (let n = 0; n < this.last; ++n) - entry.stack[n] = this.stack[n+1]; // copy stack - return entry; - }; + normals[v] = vnormals[nn]; + normals[v + 1] = vnormals[nn + 1]; + normals[v + 2] = vnormals[nn + 2]; + if (v % 9 === 0) + face_to_bins_index[v / 9] = bin_index; // remember which bin corresponds to the face + v += 3; + } - if (arg.domatrix) { - arg.matrices = []; - arg.mpool = [new Matrix4()]; // pool of Matrix objects to avoid permanent creation - arg.getmatrix = function() { return this.matrices[this.last]; }; - } + ++k; - if (this.fVisibility?.length) { - arg.vindx = 0; - arg.varray = this.fVisibility; - arg.vstack = arg.varray[arg.vindx].stack; - arg.testPhysVis = function() { - if (!this.vstack || (this.vstack?.length !== this.last)) - return undefined; - for (let n = 0; n < this.last; ++n) { - if (this.vstack[n] !== this.stack[n+1]) - return undefined; + if (k % 6 === 0) { + nn += 3; + if (notop && (k === indicies.length - 12)) { + k += 6; + nn += 3; // jump over no-top indexes + } } - const res = this.varray[this.vindx++].visible; - this.vstack = this.vindx < this.varray.length ? this.varray[this.vindx].stack : null; - return res; - }; + } } } - const node = this.nodes[arg.nodeid]; - let res = 0; - - if (arg.domatrix) { - if (!arg.mpool[arg.last+1]) - arg.mpool[arg.last+1] = new Matrix4(); + const geometry = createLegoGeom(painter, positions, normals); + let rootcolor = is_v7 ? 3 : histo.fFillColor, + fcolor = painter.getColor(rootcolor); - const prnt = (arg.last > 0) ? arg.matrices[arg.last-1] : new 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; + if (palette) + fcolor = is_v7 ? palette.getColor(nlevel) : palette.calcColor(nlevel, levels.length); + else if ((painter.options.Lego === 1) || (rootcolor < 2)) { + rootcolor = 1; + fcolor = 'white'; } - let node_vis = node.vis, node_nochlds = node.nochlds; + const material = new THREE.MeshBasicMaterial(getMaterialArgs(fcolor, { vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); - if ((arg.nodeid === 0) && arg.main_visible) - node_vis = vislvl + 1; - else if (arg.testPhysVis) { - const res = arg.testPhysVis(); - if (res !== undefined) { - node_vis = res && !node.chlds ? vislvl + 1 : 0; - node_nochlds = !res; - } - } + mesh.face_to_bins_index = face_to_bins_index; + mesh.tip_painter = painter; + mesh.zmin = axis_zmin; + mesh.zmax = axis_zmax; + mesh.baseline = (painter.options.BaseLine !== false) ? painter.options.BaseLine : (painter.options.Zero ? axis_zmin : 0); + mesh.tip_color = (rootcolor === 3) ? 0xFF0000 : 0x00FF00; + mesh.handle = handle; + mesh.tooltip = _meshLegoToolTip; - if (node_nochlds) - vislvl = 0; + fp.add3DMesh(mesh); - if (node_vis > vislvl) { - if (!arg.func || arg.func(node)) - res++; + if (num2vertices > 0) { + const geom2 = createLegoGeom(painter, pos2, norm2), + color2 = new THREE.Color(rootcolor < 2 ? 0xFF0000 : rgb(fcolor).darker(0.5).toString()), + material2 = new THREE.MeshBasicMaterial({ color: color2, vertexColors: false }), + mesh2 = new THREE.Mesh(geom2, material2); + mesh2.face_to_bins_index = face_to_bins_indx2; + mesh2.tip_painter = painter; + mesh2.handle = mesh.handle; + mesh2.tooltip = _meshLegoToolTip; + mesh2.zmin = mesh.zmin; + mesh2.zmax = mesh.zmax; + mesh2.baseline = mesh.baseline; + mesh2.tip_color = mesh.tip_color; + + fp.add3DMesh(mesh2); } + } - arg.counter++; + // lego3 or lego4 do not draw border lines + if (painter.options.Lego > 12) + return; - if ((vislvl > 0) && node.chlds) { - arg.last++; - for (let 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 - res += this.scanVisible(arg, vislvl-1); - } - arg.last--; - } else - arg.counter += (node.idshift || 0); + // DRAW LINE BOXES + let numlinevertices = 0, numsegments = 0; - 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; - delete arg.vindx; - delete arg.varray; - delete arg.vstack; - delete arg.testPhysVis; - } + zmax = axis_zmax; + zmin = axis_zmin; - return res; - } + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + if (!getBinContent(i, j, 0)) + continue; - /** @summary Return node name with given id. - * @desc Either original object or description is used */ - getNodeName(nodeid) { - if (this.origin) { - const obj = this.origin[nodeid]; - return obj ? getObjectName(obj) : ''; + // calculate required buffer size for line segments + numlinevertices += (reduced ? rvertices.length : vertices.length); + numsegments += (reduced ? rsegments.length : segments.length); } - const node = this.nodes[nodeid]; - return node ? node.name : ''; } - /** @summary Returns description for provided stack - * @desc If specified, absolute matrix is also calculated */ - resolveStack(stack, withmatrix) { - const res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix || '' }; - - // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ''; - - if (withmatrix) { - res.matrix = new Matrix4(); - if (res.node.matrix) res.matrix.fromArray(res.node.matrix); - } + // On some platforms vertex index required to be Uint16 array + // While we cannot use index for large vertex list + // skip index usage at all. It happens for relatively large histograms (100x100 bins) + const uselineindx = (numlinevertices <= 0xFFF0); - if (this.origin) - res.obj = this.origin[0]; + if (!uselineindx) + numlinevertices = numsegments * 3; - // if (!res.name) - // res.name = this.getNodeName(0); + const lpositions = new Float32Array(numlinevertices * 3), + lindicies = uselineindx ? new Uint16Array(numsegments) : null, + grzmin = fp.grz(axis_zmin), + grzmax = fp.grz(axis_zmax); + let ll = 0, ii = 0; - if (stack) { - for (let lvl = 0; lvl < stack.length; ++lvl) { - res.id = res.node.chlds[stack[lvl]]; - res.node = this.nodes[res.id]; + for (i = i1; i < i2; ++i) { + const x1 = handle.grx[i] + handle.xbar1 * (handle.grx[i + 1] - handle.grx[i]), + x2 = handle.grx[i] + handle.xbar2 * (handle.grx[i + 1] - handle.grx[i]); + for (j = j1; j < j2; ++j) { + if (!getBinContent(i, j, 0)) + continue; - if (this.origin) - res.obj = this.origin[res.id]; + const y1 = handle.gry[j] + handle.ybar1 * (handle.gry[j + 1] - handle.gry[j]), + y2 = handle.gry[j] + handle.ybar2 * (handle.gry[j + 1] - handle.gry[j]), + z1 = (binz1 <= axis_zmin) ? grzmin : fp.grz(binz1), + z2 = (binz2 > axis_zmax) ? grzmax : fp.grz(binz2), + seg = reduced ? rsegments : segments, + vvv = reduced ? rvertices : vertices; - const subname = this.getNodeName(res.id); - if (subname) { - if (res.name) res.name += '/'; - res.name += subname; + if (uselineindx) { + // array of indices for the lines, to avoid duplication of points + for (k = 0; k < seg.length; ++k) { + // intersect_index[ii] = bin_index; + lindicies[ii++] = ll / 3 + seg[k]; } - if (withmatrix && res.node.matrix) - res.matrix.multiply(new Matrix4().fromArray(res.node.matrix)); + for (k = 0; k < vvv.length; ++k) { + vert = vvv[k]; + lpositions[ll] = x1 + vert.x * (x2 - x1); + lpositions[ll + 1] = y1 + vert.y * (y2 - y1); + lpositions[ll + 2] = z1 + vert.z * (z2 - z1); + ll += 3; + } + } else { + // copy only vertex positions + for (k = 0; k < seg.length; ++k) { + vert = vvv[seg[k]]; + lpositions[ll] = x1 + vert.x * (x2 - x1); + lpositions[ll + 1] = y1 + vert.y * (y2 - y1); + lpositions[ll + 2] = z1 + vert.z * (z2 - z1); + // intersect_index[ll/3] = bin_index; + ll += 3; + } } } - - return res; } - /** @summary Provide stack name - * @desc Stack name includes full path to the physical node which is identified by stack */ - getStackName(stack) { - return this.resolveStack(stack).name; + // create boxes + const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), + material = new THREE.LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), + line = createLineSegments(convertLegoBuf(painter, lpositions), material, uselineindx ? lindicies : null); + + /* + line.painter = painter; + line.intersect_index = intersect_index; + line.tooltip = function(intersect) { + if ((intersect.index < 0) || (intersect.index >= this.intersect_index.length)) + return null; + return this.painter.get3DToolTip(this.intersect_index[intersect.index]); } + */ - /** @summary Create stack array based on nodes ids array. - * @desc Ids list should correspond to existing nodes hierarchy */ - buildStackByIds(ids) { - if (!ids) return null; + fp.add3DMesh(line); +} - if (ids[0] !== 0) { - console.error('wrong ids - first should be 0'); - return null; - } +function _lineErrToolTip(intersect) { + const pos = Math.floor(intersect.index / 6); + if ((pos < 0) || (pos >= this.intersect_index.length)) + return null; + const p = this.tip_painter, + histo = p.getHisto(), + fp = p.getFramePainter(), + tip = p.get3DToolTip(this.intersect_index[pos]), + tx1 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, fp.grx(histo.fXaxis.GetBinLowEdge(tip.ix)))), + tx2 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, fp.grx(histo.fXaxis.GetBinLowEdge(tip.ix + 1)))), + ty1 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, fp.gry(histo.fYaxis.GetBinLowEdge(tip.iy)))), + ty2 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, fp.gry(histo.fYaxis.GetBinLowEdge(tip.iy + 1)))); - let node = this.nodes[0]; - const stack = []; + tip.x1 = Math.min(tx1, tx2); + tip.x2 = Math.max(tx1, tx2); + tip.y1 = Math.min(ty1, ty2); + tip.y2 = Math.max(ty1, ty2); - for (let k = 1; k < ids.length; ++k) { - const nodeid = ids[k]; - if (!node) return null; - const chindx = node.chlds.indexOf(nodeid); - if (chindx < 0) { - console.error(`wrong nodes ids ${ids[k]} is not child of ${ids[k-1]}`); - return null; - } + tip.z1 = fp.grz(tip.value - tip.error < this.zmin ? this.zmin : tip.value - tip.error); + tip.z2 = fp.grz(tip.value + tip.error > this.zmax ? this.zmax : tip.value + tip.error); - stack.push(chindx); - node = this.nodes[nodeid]; - } + tip.color = this.tip_color; - return stack; - } + return tip; +} - /** @summary Retuns ids array which correspond to the stack */ - buildIdsByStack(stack) { - if (!stack) return null; - let node = this.nodes[0]; - const ids = [0]; - for (let k = 0; k < stack.length; ++k) { - const id = node.chlds[stack[k]]; - ids.push(id); - node = this.nodes[id]; - } - return ids; - } +/** @summary Draw TH2 histogram in error mode + * @private */ +function drawBinsError3D(painter, is_v7 = false) { + const fp = painter.getFramePainter(), + histo = painter.getHisto(), + handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), + zmin = fp.z_handle.getScaleMin(), + zmax = fp.z_handle.getScaleMax(), + test_cutg = painter.options.cutg; + let i, j, bin, binz, errs, x1, y1, x2, y2, z1, z2, + nsegments = 0, lpos = null, binindx = null, lindx = 0; - /** @summary Retuns node id by stack */ - getNodeIdByStack(stack) { - if (!stack || !this.nodes) - return -1; - let node = this.nodes[0], id = 0; - for (let k = 0; k < stack.length; ++k) { - id = node.chlds[stack[k]]; - node = this.nodes[id]; - } - return id; - } + const check_skip_min = () => { + // return true if minimal histogram value should be skipped + if (painter.options.Zero || (zmin > 0)) + return false; + return !painter.options.ShowEmpty; + }; - /** @summary Returns true if stack includes at any place provided nodeid */ - isIdInStack(nodeid, stack) { - if (!nodeid) return true; + // loop over the points - first loop counts points, second fill arrays + for (let loop = 0; loop < 2; ++loop) { + for (i = handle.i1; i < handle.i2; ++i) { + x1 = handle.grx[i]; + x2 = handle.grx[i + 1]; + for (j = handle.j1; j < handle.j2; ++j) { + binz = histo.getBinContent(i + 1, j + 1); + if ((binz < zmin) || (binz > zmax)) + continue; + if ((binz === zmin) && check_skip_min()) + continue; - let node = this.nodes[0], id = 0; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5))) + continue; - for (let lvl = 0; lvl < stack.length; ++lvl) { - id = node.chlds[stack[lvl]]; - if (id === nodeid) return true; - node = this.nodes[id]; - } + // just count number of segments + if (loop === 0) { + nsegments += 3; + continue; + } - return false; - } + bin = histo.getBin(i + 1, j + 1); + errs = painter.getBinErrors(histo, bin, binz); + binindx[lindx / 18] = bin; - /** @summary Find stack by name which include names of all parents */ - findStackByName(fullname) { - const names = fullname.split('/'), stack = []; - let currid = 0; + y1 = handle.gry[j]; + y2 = handle.gry[j + 1]; - if (this.getNodeName(currid) !== names[0]) return null; + z1 = fp.grz((binz - errs.low < zmin) ? zmin : binz - errs.low); + z2 = fp.grz((binz + errs.up > zmax) ? zmax : binz + errs.up); - for (let n = 1; n < names.length; ++n) { - const node = this.nodes[currid]; - if (!node.chlds) return null; + lpos[lindx] = x1; + lpos[lindx + 3] = x2; + lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; + lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; + lindx += 6; - for (let k = 0; k < node.chlds.length; ++k) { - const chldid = node.chlds[k]; - if (this.getNodeName(chldid) === names[n]) { - stack.push(k); - currid = chldid; - break; - } - } + lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; + lpos[lindx + 1] = y1; + lpos[lindx + 4] = y2; + lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; + lindx += 6; - // no new entry - not found stack - if (stack.length === n - 1) return null; + lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; + lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; + lpos[lindx + 2] = z1; + lpos[lindx + 5] = z2; + lindx += 6; + } } - return stack; + if (loop === 0) { + if (nsegments === 0) + return; + lpos = new Float32Array(nsegments * 6); + binindx = new Int32Array(nsegments / 3); + } } - /** @summary Set usage of default ROOT colors */ - setDefaultColors(on) { - this.use_dflt_colors = on; - if (this.use_dflt_colors && !this.dflt_table) { - const dflt = { kWhite: 0, kBlack: 1, kGray: 920, - kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kCyan: 432, - kOrange: 800, kSpring: 820, kTeal: 840, kAzure: 860, kViolet: 880, kPink: 900 }, + // create lines + const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), + material = new THREE.LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), + line = createLineSegments(lpos, material); - nmax = 110, col = []; - for (let i=0; i { + // ignore less than three points + if (iplus - iminus < 3) + return; - if (clone.kind === kindShape) { - const prop = { name: clone.name, nname: clone.name, shape: null, material: null, chlds: null }, - opacity = entry.opacity || 1, col = entry.color || '#0000FF'; - prop.fillcolor = new Color(col[0] === '#' ? col : `rgb(${col})`); - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); - return prop; + if (realz) { + layerz = fp.grz(levels[ilevel]); + if ((layerz < 0) || (layerz > 2 * fp.size_z3d)) + return; } - if (!this.origin) { - console.error('origin not there - kind', clone.kind, entry.nodeid, clone); - return null; + for (let i = iminus; i < iplus; ++i) { + pnts.push(xp[i], yp[i], layerz); + pnts.push(xp[i + 1], yp[i + 1], layerz); } + }); - const node = this.origin[entry.nodeid]; + const lines = createLineSegments(pnts, create3DLineMaterial(painter, is_v7 ? 'line_' : histo)); + fp.add3DMesh(lines); +} - if (clone.kind === kindEve) { - // special handling for EVE nodes +/** @summary Draw TH2 histograms in surf mode + * @private */ +function drawBinsSurf3D(painter, is_v7 = false) { + const histo = painter.getHisto(), + fp = painter.getFramePainter(), + axis_zmin = fp.z_handle.getScaleMin(), + main_grz = !fp.logz ? fp.grz : value => { return (value < axis_zmin) ? -0.1 : fp.grz(value); }, + main_grz_min = 0, main_grz_max = 2 * fp.size_z3d; + + let handle = painter.prepareDraw({ + rounding: false, use3d: true, extra: 1, middle: 0.5, + cutg: isFunc(painter.options?.cutg?.IsInside) ? painter.options?.cutg : null + }); + if ((handle.i2 - handle.i1 < 2) || (handle.j2 - handle.j1 < 2)) + return; - const prop = { name: getObjectName(node), nname: getObjectName(node), shape: node.fShape, material: null, chlds: null }; + let ilevels = null, levels = null, palette = null; - if (node.fElements !== null) prop.chlds = node.fElements.arr; + handle.dolines = true; - { - const opacity = Math.min(1, node.fRGBA[3]); - prop.fillcolor = new Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); - } + if (is_v7) { + let need_palette = 0; + switch (painter.options.Surf) { + case 11: + need_palette = 2; + break; + case 12: + case 15: // make surf5 same as surf2 + case 17: + need_palette = 2; + handle.dolines = false; + break; + case 14: + handle.dolines = false; + handle.donormals = true; + break; + case 16: + need_palette = 1; + handle.dogrid = true; + handle.dolines = false; + break; + default: + ilevels = fp.z_handle.createTicks(true); + handle.dogrid = true; + break; + } - return prop; + if (need_palette > 0) { + palette = fp.getHistPalette(); + if (need_palette === 2) + painter.createContour(fp, palette, { full_z_range: true }); + ilevels = palette.getContour(); + } + } else { + switch (painter.options.Surf) { + case 11: + ilevels = painter.getContourLevels(); + palette = painter.getHistPalette(); + break; + case 12: + case 15: // make surf5 same as surf2 + case 17: + ilevels = painter.getContourLevels(); + palette = painter.getHistPalette(); + handle.dolines = false; + break; + case 14: + handle.dolines = false; + handle.donormals = true; + break; + case 16: + ilevels = painter.getContourLevels(); + handle.dogrid = true; + handle.dolines = false; + break; + default: + ilevels = fp.z_handle.createTicks(true); + handle.dogrid = true; + break; } + } - const volume = node.fVolume, - prop = { name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null, - chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth }; + if (ilevels) { + // recalculate levels into graphical coordinates + levels = new Float32Array(ilevels.length); + for (let ll = 0; ll < ilevels.length; ++ll) + levels[ll] = main_grz(ilevels[ll]); + } else + levels = [main_grz_min, main_grz_max]; // just cut top/bottom parts - { - // TODO: maybe correctly extract ROOT colors here? - let opacity = 1.0; - if (!root_colors) root_colors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; - if (entry.custom_color) - prop.fillcolor = entry.custom_color; - else if ((volume.fFillColor > 1) && (volume.fLineColor === 1)) - prop.fillcolor = root_colors[volume.fFillColor]; - else if (volume.fLineColor >= 0) - prop.fillcolor = root_colors[volume.fLineColor]; + handle.grz = main_grz; + handle.grz_min = main_grz_min; + handle.grz_max = main_grz_max; - const mat = volume.fMedium?.fMaterial; + buildSurf3D(histo, handle, ilevels, (lvl, pos, normindx) => { + const geometry = createLegoGeom(painter, pos, null, handle.i2 - handle.i1, handle.j2 - handle.j1), + normals = geometry.getAttribute('normal').array; - if (mat) { - const fillstyle = mat.fFillStyle; - let transparency = (fillstyle >= 3000 && fillstyle <= 3100) ? fillstyle - 3000 : 0; + // recalculate normals + if (handle.donormals && (lvl === 1)) { + for (let ii = handle.i1; ii < handle.i2; ++ii) { + for (let jj = handle.j1; jj < handle.j2; ++jj) { + const bin = ((ii - handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; - if (this.use_dflt_colors) { - const matZ = Math.round(mat.fZ), icol = this.dflt_table[matZ]; - prop.fillcolor = root_colors[icol]; - if (mat.fDensity < 0.1) transparency = 60; - } + if (normindx[bin] === -1) + continue; // nothing there - if (transparency > 0) - opacity = (100 - transparency) / 100; - if (prop.fillcolor === undefined) - prop.fillcolor = root_colors[mat.fFillColor]; + const beg = (normindx[bin] >= 0) ? bin : bin + 9 + normindx[bin], + end = bin + 8; + let sumx = 0, sumy = 0, sumz = 0; + + for (let kk = beg; kk < end; ++kk) { + const indx = normindx[kk]; + if (indx < 0) + return console.error('FAILURE in NORMALS RECALCULATIONS'); + sumx += normals[indx]; + sumy += normals[indx + 1]; + sumz += normals[indx + 2]; + } + + sumx /= end - beg; + sumy /= end - beg; + sumz /= end - beg; + + for (let kk = beg; kk < end; ++kk) { + const indx = normindx[kk]; + normals[indx] = sumx; + normals[indx + 1] = sumy; + normals[indx + 2] = sumz; + } + } } - if (prop.fillcolor === undefined) - prop.fillcolor = 'lightgrey'; + } - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); + let color, material; + if (is_v7) + color = palette?.getColor(lvl - 1) ?? painter.getColor(5); + else if (palette) + color = palette.calcColor(lvl, levels.length); + else { + const indx = painter.options.histoFillColor || histo.fFillColor; + if (painter.options.Surf === 13) + color = 'white'; + else if (painter.options.Surf === 14) + color = indx > 1 ? painter.getColor(indx) : 'grey'; + else + color = indx > 1 ? painter.getColor(indx) : 'white'; } - return prop; - } + if (!color) + color = 'white'; + if (painter.options.Surf === 14) + material = new THREE.MeshLambertMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })); + else + material = new THREE.MeshBasicMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })); - /** @summary Creates hierarchy of Object3D for given stack entry - * @desc Such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing - * also set renderOrder, required to handle transparency */ - createObject3D(stack, toplevel, options) { - let node = this.nodes[0], three_prnt = toplevel, draw_depth = 0; - const force = isObject(options) || (options === 'force'); + const mesh = new THREE.Mesh(geometry, material); - for (let lvl = 0; lvl <= stack.length; ++lvl) { - const nchld = (lvl > 0) ? stack[lvl-1] : 0, - // extract current node - child = (lvl > 0) ? this.nodes[node.chlds[nchld]] : node; - if (!child) { - console.error(`Wrong stack ${JSON.stringify(stack)} for nodes at level ${lvl}, node.id ${node.id}, numnodes ${this.nodes.length}, nchld ${nchld}, numchilds ${node.chlds.length}, chldid ${node.chlds[nchld]}`); - return null; - } + fp.add3DMesh(mesh); - node = child; + mesh.painter = painter; // to let use it with context menu + }, (isgrid, lpos) => { + const color = painter.getColor(histo.fLineColor) ?? 'white'; + let material; - let obj3d; + if (isgrid) { + material = (painter.options.Surf === 1) + ? new THREE.LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 }) + : new THREE.LineBasicMaterial(getMaterialArgs(color)); + } else + material = new THREE.LineBasicMaterial(getMaterialArgs(color, { linewidth: histo.fLineWidth })); - if (three_prnt.children) { - for (let 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; - } + const line = createLineSegments(convertLegoBuf(painter, lpos, handle.i2 - handle.i1, handle.j2 - handle.j1), material); + line.painter = painter; + fp.add3DMesh(line); + }); - if (!force) return null; + if (painter.options.Surf === 17) + drawBinsContour3D(painter, false, is_v7); - obj3d = new Object3D(); + if (painter.options.Surf === 13) { + handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 100, middle: 0 }); - if (this._cfg?.set_names) - obj3d.name = this.getNodeName(node.id); + // get levels + const levels2 = painter.getContourLevels(), // init contour + palette2 = painter.getHistPalette(); + let lastcolindx = -1, layerz = main_grz_max; - if (this._cfg?.set_origin && this.origin) - obj3d.userData = this.origin[node.id]; + buildHist2dContour(histo, handle, levels2, palette2, (colindx, xp, yp, iminus, iplus) => { + // no need for duplicated point + if ((xp[iplus] === xp[iminus]) && (yp[iplus] === yp[iminus])) + iplus--; - if (node.abs_matrix) { - obj3d.absMatrix = new Matrix4(); - obj3d.absMatrix.fromArray(node.matrix); - } else if (node.matrix) { - obj3d.matrix.fromArray(node.matrix); - obj3d.matrix.decompose(obj3d.position, obj3d.quaternion, obj3d.scale); + // ignore less than three points + if (iplus - iminus < 3) + return; + + const pnts = []; + + for (let i = iminus; i <= iplus; ++i) { + if ((i === iminus) || (xp[i] !== xp[i - 1]) || (yp[i] !== yp[i - 1])) + pnts.push(new THREE.Vector2(xp[i], yp[i])); } - // this.accountNodes(obj3d); - obj3d.nchld = nchld; // mark index to find it again later + const faces = pnts.length < 3 ? null : THREE.ShapeUtils.triangulateShape(pnts, []); - // add the mesh to the scene - three_prnt.add(obj3d); + if (!faces?.length) + return; - // this is only for debugging - test inversion of whole geometry - if ((lvl === 0) && isObject(options) && options.scale) { - if ((options.scale.x < 0) || (options.scale.y < 0) || (options.scale.z < 0)) { - obj3d.scale.copy(options.scale); - obj3d.updateMatrix(); - } + if ((lastcolindx < 0) || (lastcolindx !== colindx)) { + lastcolindx = colindx; + layerz += 5e-5 * main_grz_max; // change layers Z } - obj3d.updateMatrixWorld(); - - three_prnt = obj3d; - } + const pos = new Float32Array(faces.length * 9), + norm = new Float32Array(faces.length * 9); + let indx = 0; - if ((options === 'mesh') || (options === 'delete_mesh')) { - let mesh = null; - if (three_prnt) { - for (let n = 0; (n < three_prnt.children.length) && !mesh; ++n) { - const chld = three_prnt.children[n]; - if ((chld.type === 'Mesh') && (chld.nchld === undefined)) mesh = chld; + for (let n = 0; n < faces.length; ++n) { + const face = faces[n]; + for (let v = 0; v < 3; ++v) { + const pnt = pnts[face[v]]; + pos[indx] = pnt.x; + pos[indx + 1] = pnt.y; + pos[indx + 2] = layerz; + norm[indx] = 0; + norm[indx + 1] = 0; + norm[indx + 2] = 1; + + indx += 3; } } - if ((options === 'mesh') || !mesh) return mesh; + const geometry = createLegoGeom(painter, pos, norm, handle.i2 - handle.i1, handle.j2 - handle.j1), + material = new THREE.MeshBasicMaterial(getMaterialArgs(palette2.getColor(colindx), { side: THREE.DoubleSide, opacity: 0.5, vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); + mesh.painter = painter; + fp.add3DMesh(mesh); + }); + } +} - const res = three_prnt; - while (mesh && (mesh !== toplevel)) { - three_prnt = mesh.parent; - three_prnt.remove(mesh); - mesh = (three_prnt.children.length === 0) ? three_prnt : null; - } +/** @summary Assign `evalPar` function for TF1 object + * @private */ - return res; - } +function proivdeEvalPar(obj, check_save) { + obj.$math = jsroot_math; - if (three_prnt) { - three_prnt.$jsroot_drawable = true; - three_prnt.$jsroot_depth = draw_depth; + let _func = obj.fTitle, isformula = false, pprefix = '['; + if (_func === 'gaus') + _func = 'gaus(0)'; + if (isStr(obj.fFormula?.fFormula)) { + if (obj.fFormula.fFormula.indexOf('[](double*x,double*p)') === 0) { + isformula = true; + pprefix = 'p['; + _func = obj.fFormula.fFormula.slice(21); + } else { + _func = obj.fFormula.fFormula; + pprefix = '[p'; } - return three_prnt; + if (obj.fFormula.fClingParameters && obj.fFormula.fParams) { + obj.fFormula.fParams.forEach(pair => { + const regex = new RegExp(`(\\[${pair.first}\\])`, 'g'), + parvalue = obj.fFormula.fClingParameters[pair.second]; + _func = _func.replace(regex, (parvalue < 0) ? `(${parvalue})` : parvalue); + }); + } } - /** @summary Create mesh for single physical node */ - createEntryMesh(ctrl, toplevel, entry, shape, colors) { - if (!shape || !shape.ready) - return null; + if (!_func) + return !check_save || (obj.fSave?.length > 2); - entry.done = true; // mark entry is created - shape.used = true; // indicate that shape was used in building + obj.formulas?.forEach(entry => { + _func = _func.replaceAll(entry.fName, entry.fTitle); + }); - if (!shape.geom || !shape.nfaces) { - // node is visible, but shape does not created - this.createObject3D(entry.stack, toplevel, 'delete_mesh'); - return null; - } + _func = _func.replace(/\b(TMath::SinH)\b/g, 'Math.sinh') + .replace(/\b(TMath::CosH)\b/g, 'Math.cosh') + .replace(/\b(TMath::TanH)\b/g, 'Math.tanh') + .replace(/\b(TMath::ASinH)\b/g, 'Math.asinh') + .replace(/\b(TMath::ACosH)\b/g, 'Math.acosh') + .replace(/\b(TMath::ATanH)\b/g, 'Math.atanh') + .replace(/\b(TMath::ASin)\b/g, 'Math.asin') + .replace(/\b(TMath::ACos)\b/g, 'Math.acos') + .replace(/\b(TMath::Atan)\b/g, 'Math.atan') + .replace(/\b(TMath::ATan2)\b/g, 'Math.atan2') + .replace(/\b(sin|SIN|TMath::Sin)\b/g, 'Math.sin') + .replace(/\b(cos|COS|TMath::Cos)\b/g, 'Math.cos') + .replace(/\b(tan|TAN|TMath::Tan)\b/g, 'Math.tan') + .replace(/\b(exp|EXP|TMath::Exp)\b/g, 'Math.exp') + .replace(/\b(log|LOG|TMath::Log)\b/g, 'Math.log') + .replace(/\b(log10|LOG10|TMath::Log10)\b/g, 'Math.log10') + .replace(/\b(pow|POW|TMath::Power)\b/g, 'Math.pow') + .replace(/\b(pi|PI)\b/g, 'Math.PI') + .replace(/\b(abs|ABS|TMath::Abs)\b/g, 'Math.abs') + .replace(/\bsqrt\(/g, 'Math.sqrt(') + .replace(/\bxygaus\(/g, 'this.$math.gausxy(this, x, y, ') + .replace(/\bgaus\(/g, 'this.$math.gaus(this, x, ') + .replace(/\bgausn\(/g, 'this.$math.gausn(this, x, ') + .replace(/\bexpo\(/g, 'this.$math.expo(this, x, ') + .replace(/\blandau\(/g, 'this.$math.landau(this, x, ') + .replace(/\blandaun\(/g, 'this.$math.landaun(this, x, ') + .replace(/\b(TMath::|ROOT::Math::)/g, 'this.$math.'); - const prop = this.getDrawEntryProperties(entry, colors), - obj3d = this.createObject3D(entry.stack, toplevel, ctrl), - matrix = obj3d.absMatrix || obj3d.matrixWorld; + if (_func.match(/^pol[0-9]$/) && (parseInt(_func[3]) === obj.fNpar - 1)) { + _func = '[0]'; + for (let k = 1; k < obj.fNpar; ++k) + _func += ` + [${k}] * ` + ((k === 1) ? 'x' : `Math.pow(x,${k})`); + } - prop.material.wireframe = ctrl.wireframe; + if (_func.match(/^chebyshev[0-9]$/) && (parseInt(_func[9]) === obj.fNpar - 1)) { + _func = `this.$math.ChebyshevN(${obj.fNpar - 1}, x, `; + for (let k = 0; k < obj.fNpar; ++k) + _func += (k === 0 ? '[' : ', ') + `[${k}]`; + _func += '])'; + } - prop.material.side = ctrl.doubleside ? DoubleSide : FrontSide; + for (let i = 0; i < obj.fNpar; ++i) + _func = _func.replaceAll(pprefix + i + ']', `(${obj.GetParValue(i)})`); - let mesh; - if (matrix.determinant() > -0.9) - mesh = new Mesh(shape.geom, prop.material); - else - mesh = createFlippedMesh(shape, prop.material); + for (let n = 2; n < 10; ++n) + _func = _func.replaceAll(`x^${n}`, `Math.pow(x,${n})`); - obj3d.add(mesh); + if (isformula) { + _func = _func.replace(/x\[0\]/g, 'x'); + if (obj._typename === clTF3) { + _func = _func.replace(/x\[1\]/g, 'y'); + _func = _func.replace(/x\[2\]/g, 'z'); + obj.evalPar = new Function('x', 'y', 'z', _func).bind(obj); + } else if (obj._typename === clTF2) { + _func = _func.replace(/x\[1\]/g, 'y'); + obj.evalPar = new Function('x', 'y', _func).bind(obj); + } else + obj.evalPar = new Function('x', _func).bind(obj); + } else if (obj._typename === clTF3) + obj.evalPar = new Function('x', 'y', 'z', 'return ' + _func).bind(obj); + else if (obj._typename === clTF2) + obj.evalPar = new Function('x', 'y', 'return ' + _func).bind(obj); + else + obj.evalPar = new Function('x', 'return ' + _func).bind(obj); - if (obj3d.absMatrix) { - mesh.matrix.copy(obj3d.absMatrix); - mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale); - mesh.updateMatrixWorld(); - } + return true; +} - // keep full stack of nodes - mesh.stack = entry.stack; - mesh.renderOrder = this.maxdepth - entry.stack.length; // order of transparency handling - if (ctrl.set_names) - mesh.name = this.getNodeName(entry.nodeid); +/** @summary Get interpolation in saved buffer + * @desc Several checks must be done before function can be used + * @private */ +function _getTF1Save(func, x) { + const np = func.fSave.length - 3, + xmin = func.fSave[np + 1], + xmax = func.fSave[np + 2], + dx = (xmax - xmin) / np; + if (x < xmin) + return func.fSave[0]; + if (x > xmax) + return func.fSave[np]; + + const bin = Math.min(np - 1, Math.floor((x - xmin) / dx)); + let xlow = xmin + bin * dx, + xup = xlow + dx, + ylow = func.fSave[bin], + yup = func.fSave[bin + 1]; + + if (!Number.isFinite(ylow) && (bin < np - 1)) { + xlow += dx; + xup += dx; + ylow = yup; + yup = func.fSave[bin + 2]; + } else if (!Number.isFinite(yup) && (bin > 0)) { + xup -= dx; + xlow -= dx; + yup = ylow; + ylow = func.fSave[bin - 1]; + } + + return ((xup * ylow - xlow * yup) + x * (yup - ylow)) / dx; +} - if (ctrl.set_origin) - mesh.userData = prop.volume; +/** @summary Provide TF1 value + * @desc First try evaluate, if not possible - check saved buffer + * @private */ +function getTF1Value(func, x, skip_eval = undefined) { + if (!func) + return 0; - // keep hierarchy level - mesh.$jsroot_order = obj3d.$jsroot_depth; + let iserr = false; - if (ctrl.info?.num_meshes !== undefined) { - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces; + if (!skip_eval && !func.evalPar) { + try { + if (!proivdeEvalPar(func)) + iserr = true; + } catch { + iserr = true; } - - // set initial render order, when camera moves, one must refine it - // mesh.$jsroot_order = mesh.renderOrder = - // this._clones.maxdepth - ((obj3d.$jsroot_depth !== undefined) ? obj3d.$jsroot_depth : entry.stack.length); - - return mesh; } - /** @summary Check if instancing can be used for the nodes */ - createInstancedMeshes(ctrl, toplevel, draw_nodes, build_shapes, colors) { - if (ctrl.instancing < 0) - return false; + if (func.evalPar && !iserr) { + try { + return func.evalPar(x); + } catch { + /* eslint-disable-next-line no-useless-assignment */ + iserr = true; + } + } - // first delete previous data - const used_shapes = []; - let max_entries = 1; + const np = func.fSave.length - 3; + return (np < 2) || (func.fSave[np + 1] === func.fSave[np + 2]) ? 0 : _getTF1Save(func, x); +} - for (let n = 0; n < draw_nodes.length; ++n) { - const entry = draw_nodes[n]; - if (entry.done) continue; +const PadDrawOptions = ['LOGXY', 'LOGX', 'LOGY', 'LOGZ', 'LOGV', 'LOG', 'LOG2X', 'LOG2Y', 'LOG2', + 'LNX', 'LNY', 'LN', 'GRIDXY', 'GRIDX', 'GRIDY', + 'TICKXY2', 'TICKX2', 'TICKY2', 'TICKXY', 'TICKX', 'TICKY', 'TICKZ', 'FB', 'GRAYSCALE']; - /// shape can be provided with entry itself - const shape = entry.server_shape || build_shapes[entry.shapeid]; - if (!shape || !shape.ready) { - console.warn(`Problem with shape id ${entry.shapeid} when building`); - return false; - } +/** + * @summary Painter for TH1 classes + * @private + */ - // ignore shape without geometry - if (!shape.geom || !shape.nfaces) - continue; +let TH1Painter$2 = class TH1Painter extends THistPainter { - if (shape.instances === undefined) { - shape.instances = []; - used_shapes.push(shape); + /** @summary Returns histogram + * @desc Also assigns custom getBinContent method for TProfile if PROJX options specified */ + getHisto() { + const histo = super.getHisto(); + if (histo?._typename === clTProfile) { + if (!histo.$getBinContent) + histo.$getBinContent = histo.getBinContent; + switch (this.getOptions().ProfileProj) { + case 'B': + histo.getBinContent = histo.getBinEntries; + break; + case 'C=E': + histo.getBinContent = histo.getBinError; + break; + case 'W': + histo.getBinContent = function(i) { return this.$getBinContent(i) * this.getBinEntries(i); }; + break; + default: + histo.getBinContent = histo.$getBinContent; + break; } - - const instance = shape.instances.find(i => i.nodeid === entry.nodeid); - - if (instance) { - instance.entries.push(entry); - max_entries = Math.max(max_entries, instance.entries.length); - } else - shape.instances.push({ nodeid: entry.nodeid, entries: [entry] }); - } - - const make_sense = ctrl.instancing > 0 ? (max_entries > 2) : (draw_nodes.length > 10000) && (max_entries > 10); - - if (!make_sense) { - used_shapes.forEach(shape => { delete shape.instances; }); - return false; } + return histo; + } - used_shapes.forEach(shape => { - shape.used = true; - shape.instances.forEach(instance => { - const entry0 = instance.entries[0], - prop = this.getDrawEntryProperties(entry0, colors); + /** @summary Scan content of 1-D histogram + * @desc Detect min/max values for x and y axis + * @param {boolean} when_axis_changed - true when zooming was changed, some checks may be skipped */ + scanContent(when_axis_changed) { + if (when_axis_changed && !this.nbinsx) + when_axis_changed = false; - prop.material.wireframe = ctrl.wireframe; + const histo = this.getHisto(), + o = this.getOptions(); - prop.material.side = ctrl.doubleside ? DoubleSide : FrontSide; + if (!when_axis_changed) + this.extractAxesProperties(1); - if (instance.entries.length === 1) - this.createEntryMesh(ctrl, toplevel, entry0, shape, colors); - else { - const arr1 = [], arr2 = [], stacks1 = [], stacks2 = [], names1 = [], names2 = []; + const left = this.getSelectIndex('x', 'left'), + right = this.getSelectIndex('x', 'right'), + pad_logy = this.getPadPainter()?.getPadLog(o.swap_xy() ? 'x' : 'y'), + f1 = o.Func ? this.findFunction(clTF1) : null; - instance.entries.forEach(entry => { - const info = this.resolveStack(entry.stack, true); - if (info.matrix.determinant() > -0.9) { - arr1.push(info.matrix); - stacks1.push(entry.stack); - names1.push(this.getNodeName(entry.nodeid)); - } else { - arr2.push(info.matrix); - stacks2.push(entry.stack); - names2.push(this.getNodeName(entry.nodeid)); - } - entry.done = true; - }); + if (when_axis_changed && (left === this.scan_xleft) && (right === this.scan_xright)) + return; - if (arr1.length > 0) { - const mesh1 = new InstancedMesh(shape.geom, prop.material, arr1.length); + // Paint histogram axis only + this.draw_content = !(o.Axis > 0); - mesh1.stacks = stacks1; - arr1.forEach((matrix, i) => mesh1.setMatrixAt(i, matrix)); + this.scan_xleft = left; + this.scan_xright = right; - toplevel.add(mesh1); + const is_profile = this.isTProfile(), + imin = Math.min(0, left), + imax = Math.max(this.nbinsx, right); + let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0, first = true, value, errs = { low: 0, up: 0 }; - mesh1.renderOrder = 1; + for (let i = imin; i < imax; ++i) { + value = histo.getBinContent(i + 1); + hsum += is_profile ? histo.fBinEntries[i + 1] : value; - if (ctrl.set_names) { - mesh1.name = names1[0]; - mesh1.names = names1; - } + if ((i < left) || (i >= right)) + continue; - if (ctrl.set_origin) - mesh1.userData = prop.volume; + if ((value > 0) && ((hmin_nz === 0) || (value < hmin_nz))) + hmin_nz = value; - mesh1.$jsroot_order = 1; - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces*arr1.length; - } + if (first) { + hmin = hmax = value; + first = false; + } - if (arr2.length > 0) { - if (shape.geomZ === undefined) - shape.geomZ = createFlippedGeom(shape.geom); + if (o.Error) + errs = this.getBinErrors(histo, i + 1, value); - const mesh2 = new InstancedMesh(shape.geomZ, prop.material, arr2.length); + hmin = Math.min(hmin, value - errs.low); + hmax = Math.max(hmax, value + errs.up); - mesh2.stacks = stacks2; - const m = new Matrix4().makeScale(1, 1, -1); - arr2.forEach((matrix, i) => { - mesh2.setMatrixAt(i, matrix.multiply(m)); - }); - mesh2._flippedMesh = true; + if (f1) { + // similar code as in THistPainter, line 7196 + const x = histo.fXaxis.GetBinCenter(i + 1), + v = getTF1Value(f1, x); + if (v !== undefined) { + hmax = Math.max(hmax, v); + if (pad_logy && (value > 0) && (v > 0.3 * value)) + hmin_nz = Math.min(hmin_nz, v); + } + } + } - toplevel.add(mesh2); + // account overflow/underflow bins + if (is_profile) + hsum += histo.fBinEntries[0] + histo.fBinEntries[this.nbinsx + 1]; + else + hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1); - mesh2.renderOrder = 1; - if (ctrl.set_names) { - mesh2.name = names2[0]; - mesh2.names = names2; - } - if (ctrl.set_origin) - mesh2.userData = prop.volume; + this.stat_entries = hsum; - mesh2.$jsroot_order = 1; - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces*arr2.length; - } - } - }); + this.hmin = hmin; + this.hmax = hmax; - delete shape.instances; - }); + // this.ymin_nz = hmin_nz; // value can be used to show optimal log scale - return true; - } + if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) + this.draw_content = false; - /** @summary Get volume boundary */ - getVolumeBoundary(viscnt, facelimit, nodeslimit) { - const result = { min: 0, max: 1, sortidcut: 0 }; + let set_zoom = false; - if (!this.sortmap) { - console.error('sorting map do not exist'); - return result; + if (this.draw_content || (this.isMainPainter() && (o.Axis > 0) && !o.ohmin && !o.ohmax && (histo.fMinimum === kNoZoom) && (histo.fMaximum === kNoZoom))) { + if (hmin >= hmax) { + if (hmin === 0) { + this.ymin = 0; + this.ymax = 1; + } else if (hmin < 0) { + this.ymin = 2 * hmin; + this.ymax = 0; + } else { + this.ymin = 0; + this.ymax = hmin * 2; + } + } else if (pad_logy) { + this.ymin = (hmin_nz || hmin) * 0.5; + this.ymax = hmax * 2 * (0.9 / 0.95); + } else { + this.ymin = hmin; + this.ymax = hmax; + } } - let maxNode, currNode, cnt=0, facecnt=0; + hmin = o.minimum; + hmax = o.maximum; - for (let n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { - const 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 ((hmin === hmax) && (hmin !== kNoZoom)) { + if (hmin < 0) { + hmin *= 2; + hmax = 0; + } else { + hmin = 0; + hmax = 2 * hmax || 1; + } } - if (!currNode) { - console.error('no volumes selected'); - return result; - } + let fix_min = false, fix_max = false; - // 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; - } + if (o.ohmin && o.ohmax && !this.draw_content) { + // case of hstack drawing, zooming allowed only when flag is provided - /** @summary Collects visible nodes, using maxlimit - * @desc One can use map to define cut based on the volume or serious of cuts */ - collectVisibles(maxnumfaces, frustum) { - // in simple case shape as it is - if (this.plain_shape) - return { lst: [{ nodeid: 0, seqid: 0, stack: [], factor: 1, shapeid: 0, server_shape: this.plain_shape }], complete: true }; + if (o.zoom_min_max) { + if ((hmin !== kNoZoom) && (hmin <= this.ymin)) + hmin = kNoZoom; + if ((hmax !== kNoZoom) && (hmax >= this.ymax)) + hmax = kNoZoom; + set_zoom = true; + } else + hmin = hmax = kNoZoom; + } else if ((hmin !== kNoZoom) && (hmax !== kNoZoom) && !this.draw_content && + ((this.ymin === this.ymax) || (this.ymin > hmin) || (this.ymax < hmax))) { + // often appears with TF1 painter where Y range is not set properly + this.ymin = hmin; + this.ymax = hmax; + fix_min = fix_max = true; + } else { + if (hmin !== kNoZoom) { + fix_min = true; + if (hmin < this.ymin) + this.ymin = hmin; + set_zoom = true; + } + if (hmax !== kNoZoom) { + fix_max = true; + if (hmax > this.ymax) + this.ymax = hmax; + set_zoom = true; + } + } - const arg = { - facecnt: 0, - viscnt: new Array(this.nodes.length), // counter for each node - vislvl: this.getVisLevel(), - reset() { - this.total = 0; - this.facecnt = 0; - this.viscnt.fill(0); - }, - // nodes: this.nodes, - func(node) { - this.total++; - this.facecnt += node.nfaces; - this.viscnt[node.id]++; - return true; + // final adjustment like in THistPainter.cxx line 7309 + if (!o.exact_values_range() && !pad_logy) { + if (!fix_min) { + if ((o.BaseLine !== false) && (this.ymin >= 0)) + this.ymin = 0; + else { + const positive = (this.ymin >= 0); + this.ymin -= gStyle.fHistTopMargin * (this.ymax - this.ymin); + if (positive && (this.ymin < 0)) + this.ymin = 0; + } } - }; + if (!fix_max) + this.ymax += gStyle.fHistTopMargin * (this.ymax - this.ymin); + } - arg.reset(); + // always set zoom when hmin/hmax is configured + // fMinimum/fMaximum values is a way how ROOT handles Y scale zooming for TH1 - let total = this.scanVisible(arg); - if ((total === 0) && (this.nodes[0].vis < 2) && !this.nodes[0].nochlds) { - // try to draw only main node by default - arg.reset(); - arg.main_visible = true; - total = this.scanVisible(arg); + if (!when_axis_changed) { + if (set_zoom && ((hmin !== kNoZoom) || (hmax !== kNoZoom))) { + this.zoom_ymin = (hmin === kNoZoom) ? this.ymin : hmin; + this.zoom_ymax = (hmax === kNoZoom) ? this.ymax : hmax; + } else { + delete this.zoom_ymin; + delete this.zoom_ymax; + } } + } - const maxnumnodes = this.getMaxVisNodes(); + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return (this.getDimension() > 1) || !this.draw_content; } - if (maxnumnodes > 0) { - while ((total > maxnumnodes) && (arg.vislvl > 1)) { - arg.vislvl--; - arg.reset(); - total = this.scanVisible(arg); + /** @summary Provide histogram min/max used to create canvas ranges + * @private */ + getUserRanges() { + const histo = this.getHisto(); + + let miny = 0, maxy = 0; + + for (let i = 0; i < histo.fXaxis.fNbins; ++i) { + const value = histo.getBinContent(i + 1); + if (i === 0) + miny = maxy = value; + else { + miny = Math.min(miny, value); + maxy = Math.max(maxy, value); } } - this.actual_level = arg.vislvl; // not used, can be shown somewhere in the gui + if (histo.fMinimum !== kNoZoom) + miny = histo.fMinimum; - let minVol = 0, maxVol = 0, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1; + if (histo.fMaximum !== kNoZoom) + maxy = histo.fMaximum; - if (arg.facecnt > maxnumfaces) { - const bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), - bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0), - // define minimal volume, which always to shown - boundary = this.getVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); + if (maxy <= miny) + maxy = miny + 1; - minVol = boundary.min; - maxVol = boundary.max; - sortidcut = boundary.sortidcut; + return { minx: histo.fXaxis.fXmin, maxx: histo.fXaxis.fXmax, miny, maxy }; + } - 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; - } - } + /** @summary Count histogram statistic */ + countStat(cond, count_skew) { + const profile = this.isTProfile(), + histo = this.getHisto(), xaxis = histo.fXaxis, + left = this.getSelectIndex('x', 'left'), + right = this.getSelectIndex('x', 'right'), + fp = this.getFramePainter(), + res = { + name: histo.fName, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, + entries: (histo.fEntries > 0) ? histo.fEntries : this.stat_entries, + eff_entries: 0, xmax: 0, wmax: 0, skewx: 0, skewd: 0, kurtx: 0, kurtd: 0 + }, + has_counted_stat = !fp?.isAxisZoomed('x') && (Math.abs(histo.fTsumw) > 1e-300); + let stat_sumw = 0, stat_sumw2 = 0, stat_sumwx = 0, stat_sumwx2 = 0, stat_sumwy = 0, stat_sumwy2 = 0, + i, xx, w, xmax = null, wmax = null; - return true; - }; + if (!isFunc(cond)) + cond = null; - for (let n = 0; n < arg.viscnt.length; ++n) - arg.viscnt[n] = 0; + for (i = left; i < right; ++i) { + xx = xaxis.GetBinCoord(i + 0.5); - this.scanVisible(arg); + if (cond && !cond(xx)) + continue; - if (arg.totalcam > maxnumfaces*0.2) - camVol = this.getVolumeBoundary(arg.viscnt, maxnumfaces*0.2, maxnumnodes*0.2).min; - else - camVol = 0; + if (profile) { + w = histo.fBinEntries[i + 1]; + stat_sumwy += histo.fArray[i + 1]; + stat_sumwy2 += histo.fSumw2[i + 1]; + } else + w = histo.getBinContent(i + 1); - camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); + if ((xmax === null) || (w > wmax)) { + xmax = xx; + wmax = w; + } - // console.log(`Limit for camera ${camVol} faces in camera view ${arg.totalcam}`); + if (!has_counted_stat) { + stat_sumw += w; + stat_sumw2 += w * w; + stat_sumwx += w * xx; + stat_sumwx2 += w * xx ** 2; } } - 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; - }; + // when no range selection done, use original statistic from histogram + if (has_counted_stat) { + stat_sumw = histo.fTsumw; + stat_sumw2 = histo.fTsumw2; + stat_sumwx = histo.fTsumwx; + stat_sumwx2 = histo.fTsumwx2; + } - this.scanVisible(arg); + res.integral = stat_sumw; - return { lst: arg.items, complete: minVol === 0 }; - } + res.eff_entries = stat_sumw2 ? stat_sumw * stat_sumw / stat_sumw2 : Math.abs(stat_sumw); - /** @summary Merge list of drawn objects - * @desc In current list we should mark if object already exists - * from previous list we should collect objects which are not there */ - mergeVisibles(current, prev) { - let indx2 = 0; - const del = []; - for (let 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 (Math.abs(stat_sumw) > 1e-300) { + res.meanx = stat_sumwx / stat_sumw; + res.meany = stat_sumwy / stat_sumw; + res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx ** 2)); + res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany ** 2)); + } + if (xmax !== null) { + res.xmax = xmax; + res.wmax = wmax; + } - if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { - if (prev[indx2].done) current[indx1].done = true; // copy ready flag - indx2++; + if (count_skew) { + let sum3 = 0, sum4 = 0, np = 0; + for (i = left; i < right; ++i) { + xx = xaxis.GetBinCoord(i + 0.5); + if (cond && !cond(xx)) + continue; + w = profile ? histo.fBinEntries[i + 1] : histo.getBinContent(i + 1); + np += w; + sum3 += w * Math.pow(xx - res.meanx, 3); + sum4 += w * Math.pow(xx - res.meanx, 4); } - } - // remove rest - while (indx2 < prev.length) - del.push(prev[indx2++]); + const stddev3 = Math.pow(res.rmsx, 3), stddev4 = Math.pow(res.rmsx, 4); + if (np * stddev3) + res.skewx = sum3 / (np * stddev3); + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; + if (np * stddev4) + res.kurtx = sum4 / (np * stddev4) - 3; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; + } - return del; + return res; } - /** @summary Collect all uniques shapes which should be built - * @desc Check if same shape used many times for drawing */ - collectShapes(lst) { - // nothing else - just that single shape - if (this.plain_shape) - return [this.plain_shape]; + /** @summary Fill stat box */ + fillStatistic(stat, dostat, dofit) { + // no need to refill statistic if histogram is dummy + if (this.isIgnoreStatsFill()) + return false; - const shapes = []; + if (dostat === 1) + dostat = 1111; + if (dofit === 1) + dofit = 111; - for (let i = 0; i < lst.length; ++i) { - const entry = lst[i], - shape = this.getNodeShape(entry.nodeid); + const histo = this.getHisto(), + print_name = dostat % 10, + print_entries = Math.floor(dostat / 10) % 10, + print_mean = Math.floor(dostat / 100) % 10, + print_rms = Math.floor(dostat / 1000) % 10, + print_under = Math.floor(dostat / 10000) % 10, + print_over = Math.floor(dostat / 100000) % 10, + print_integral = Math.floor(dostat / 1000000) % 10, + print_skew = Math.floor(dostat / 10000000) % 10, + print_kurt = Math.floor(dostat / 100000000) % 10, + data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); - if (!shape) continue; // strange, but avoid misleading - if (shape._id === undefined) { - shape._id = shapes.length; + // make empty at the beginning + stat.clearPave(); - shapes.push({ id: shape._id, shape, vol: this.nodes[entry.nodeid].vol, refcnt: 1, factor: 1, ready: false }); + if (print_name > 0) + stat.addText(data.name); - // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol }); - } else - shapes[shape._id].refcnt++; + if (this.isTProfile()) { + if (print_entries > 0) + stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (print_mean > 0) { + stat.addText('Mean = ' + stat.format(data.meanx)); + stat.addText('Mean y = ' + stat.format(data.meany)); + } - entry.shape = shapes[shape._id]; // remember shape used + if (print_rms > 0) { + stat.addText('Std Dev = ' + stat.format(data.rmsx)); + stat.addText('Std Dev y = ' + stat.format(data.rmsy)); + } + } else { + if (print_entries > 0) + stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - // use maximal importance factor to push element to the front - if (entry.factor && (entry.factor>entry.shape.factor)) - entry.shape.factor = entry.factor; - } + if (print_mean > 0) + stat.addText('Mean = ' + stat.format(data.meanx)); - // now sort shapes in volume decrease order - shapes.sort((a, b) => b.vol*b.factor - a.vol*a.factor); + if (print_rms > 0) + stat.addText('Std Dev = ' + stat.format(data.rmsx)); - // now set new shape ids according to the sorted order and delete temporary field - for (let n = 0; n < shapes.length; ++n) { - const item = shapes[n]; - item.id = n; // set new ID - delete item.shape._id; // remove temporary field - } + if (print_under > 0) + stat.addText('Underflow = ' + stat.format(histo.fArray.length ? histo.fArray[0] : 0, 'entries')); - // as last action set current shape id to each entry - for (let i = 0; i < lst.length; ++i) { - const entry = lst[i]; - if (entry.shape) { - entry.shapeid = entry.shape.id; // keep only id for the entry - delete entry.shape; // remove direct references - } + if (print_over > 0) + stat.addText('Overflow = ' + stat.format(histo.fArray.length ? histo.fArray.at(-1) : 0, 'entries')); + + if (print_integral > 0) + stat.addText('Integral = ' + stat.format(data.integral, 'entries')); + + if (print_skew === 2) + stat.addText(`Skewness = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); + else if (print_skew > 0) + stat.addText(`Skewness = ${stat.format(data.skewx)}`); + + if (print_kurt === 2) + stat.addText(`Kurtosis = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); + else if (print_kurt > 0) + stat.addText(`Kurtosis = ${stat.format(data.kurtx)}`); } - return shapes; + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF1), dofit, 1); + + return true; } - /** @summary Merge shape lists */ - mergeShapesLists(oldlst, newlst) { - if (!oldlst) return newlst; + /** @summary Get baseline for bar drawings */ + getBarBaseline(funcs, height) { + const o = this.getOptions(); + let gry = funcs.swap_xy() ? 0 : height; + if (Number.isFinite(o.BaseLine) && (o.BaseLine >= funcs.scale_ymin)) + gry = Math.round(funcs.gry(o.BaseLine)); + return gry; + } - // set geometry to shape object itself - for (let n = 0; n < oldlst.length; ++n) { - const item = oldlst[n]; + /** @summary Draw histogram as bars */ + async drawBars(funcs, height) { + const left = this.getSelectIndex('x', 'left', -1), + right = this.getSelectIndex('x', 'right', 1), + histo = this.getHisto(), + o = this.getOptions(), + xaxis = histo.fXaxis, + show_text = o.Text; + let text_col, text_angle, text_size, + side = (o.BarStyle > 10) ? o.BarStyle % 10 : 0, + pr = Promise.resolve(); - item.shape._geom = item.geom; - delete item.geom; + if (side > 4) + side = 4; + const gry2 = this.getBarBaseline(funcs, height); - if (item.geomZ !== undefined) { - item.shape._geomZ = item.geomZ; - delete item.geomZ; - } + if (show_text) { + text_col = this.getColor(histo.fMarkerColor); + text_angle = -1 * o.TextAngle; + text_size = 20; + + if ((histo.fMarkerSize !== 1) && text_angle) + text_size = 0.02 * height * histo.fMarkerSize; + + pr = this.startTextDrawingAsync(42, text_size, undefined, text_size); } - // take from shape (if match) - for (let n = 0; n < newlst.length; ++n) { - const item = newlst[n]; + return pr.then(() => { + let bars = '', barsl = '', barsr = ''; - if (item.shape._geom !== undefined) { - item.geom = item.shape._geom; - delete item.shape._geom; - } + for (let i = left; i < right; ++i) { + const x1 = xaxis.GetBinLowEdge(i + 1), + x2 = xaxis.GetBinLowEdge(i + 2); - if (item.shape._geomZ !== undefined) { - item.geomZ = item.shape._geomZ; - delete item.shape._geomZ; - } - } + if (funcs.logx && (x2 <= 0)) + continue; - // now delete all unused geometries - for (let n = 0; n < oldlst.length; ++n) { - const item = oldlst[n]; - delete item.shape._geom; - delete item.shape._geomZ; - } + let grx1 = Math.round(funcs.grx(x1)), + grx2 = Math.round(funcs.grx(x2)), + w = grx2 - grx1; + const y = histo.getBinContent(i + 1); - return newlst; - } + if (funcs.logy && (y < funcs.scale_ymin)) + continue; + const gry1 = Math.round(funcs.gry(y)); - /** @summary Build shapes */ - buildShapes(lst, limit, timelimit) { - let created = 0; - const tm1 = new Date().getTime(), - res = { done: false, shapes: 0, faces: 0, notusedshapes: 0 }; + grx1 += Math.round(histo.fBarOffset / 1000 * w); + w = Math.round(histo.fBarWidth / 1000 * w); - for (let n = 0; n < lst.length; ++n) { - const item = lst[n]; + if (funcs.swap_xy()) + bars += `M${gry2},${grx1}h${gry1 - gry2}v${w}h${gry2 - gry1}z`; + else + bars += `M${grx1},${gry1}h${w}v${gry2 - gry1}h${-w}z`; + + if (side > 0) { + grx2 = grx1 + w; + w = Math.round(w * side / 10); + if (funcs.swap_xy()) { + barsl += `M${gry2},${grx1}h${gry1 - gry2}v${w}h${gry2 - gry1}z`; + barsr += `M${gry2},${grx2}h${gry1 - gry2}v${-w}h${gry2 - gry1}z`; + } else { + barsl += `M${grx1},${gry1}h${w}v${gry2 - gry1}h${-w}z`; + barsr += `M${grx2},${gry1}h${-w}v${gry2 - gry1}h${w}z`; + } + } - // if enough faces are produced, nothing else is required - if (res.done) { item.ready = true; continue; } + if (show_text && y) { + const text = (y === Math.round(y)) ? y.toString() : floatToString(y, gStyle.fPaintTextFormat); - if (!item.ready) { - item._typename = '$$Shape$$'; // let reuse item for direct drawing - item.ready = true; - if (item.geom === undefined) { - item.geom = createGeometry(item.shape); - if (item.geom) created++; // indicate that at least one shape was created + if (funcs.swap_xy()) + this.drawText({ align: 12, x: Math.round(gry1 + text_size / 2), y: Math.round(grx1 + 0.1), height: Math.round(w * 0.8), text, color: text_col, latex: 0 }); + else if (text_angle) + this.drawText({ align: 12, x: grx1 + w / 2, y: Math.round(gry1 - 2 - text_size / 5), width: 0, height: 0, rotate: text_angle, text, color: text_col, latex: 0 }); + else + this.drawText({ align: 22, x: Math.round(grx1 + w * 0.1), y: Math.round(gry1 - 2 - text_size), width: Math.round(w * 0.8), height: text_size, text, color: text_col, latex: 0 }); } - item.nfaces = countGeometryFaces(item.geom); } - res.shapes++; - if (!item.used) res.notusedshapes++; - res.faces += item.nfaces * item.refcnt; + if (bars) { + this.appendPath(bars) + .call(this.fillatt.func); + } - if (res.faces >= limit) - res.done = true; - else if ((created > 0.01*lst.length) && (timelimit !== undefined)) { - const tm2 = new Date().getTime(); - if (tm2-tm1 > timelimit) return res; + if (barsl) { + this.appendPath(barsl) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); } - } - res.done = true; + if (barsr) { + this.appendPath(barsr) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); + } - return res; + if (show_text) + return this.finishTextDrawing(); + }); } - /** @summary Format REveGeomNode data to be able use it in list of clones - * @private */ - static formatServerElement(elem) { - elem.kind = 2; // special element for geom viewer, used in TGeoPainter - elem.vis = 2; // visibility is alwys on - const m = elem.matr; - delete elem.matr; - if (!m?.length) return elem; + /** @summary Draw histogram as filled errors */ + drawFilledErrors(funcs) { + const left = this.getSelectIndex('x', 'left', 0), + right = this.getSelectIndex('x', 'right', 0), + histo = this.getHisto(), bins1 = [], bins2 = []; - if (m.length === 16) - elem.matrix = m; - else { - const nm = elem.matrix = new Array(16); - nm.fill(0); - nm[0] = nm[5] = nm[10] = nm[15] = 1; + for (let i = left; i < right; ++i) { + const x = histo.fXaxis.GetBinCoord(i + 0.5); + if (funcs.logx && (x <= 0)) + continue; + const grx = Math.round(funcs.grx(x)), + y = histo.getBinContent(i + 1), + yerrs = this.getBinErrors(histo, i + 1, y); + if (funcs.logy && (y - yerrs.low < funcs.scale_ymin)) + continue; - if (m.length === 3) { - // translation martix - nm[12] = m[0]; nm[13] = m[1]; nm[14] = m[2]; - } else if (m.length === 4) { - // scale matrix - nm[0] = m[0]; nm[5] = m[1]; nm[10] = m[2]; nm[15] = m[3]; - } else if (m.length === 9) { - // rotation matrix - nm[0] = m[0]; nm[4] = m[1]; nm[8] = m[2]; - nm[1] = m[3]; nm[5] = m[4]; nm[9] = m[5]; - nm[2] = m[6]; nm[6] = m[7]; nm[10] = m[8]; - } else - console.error(`wrong number of elements ${m.length} in the matrix`); + bins1.push({ grx, gry: Math.round(funcs.gry(y + yerrs.up)) }); + bins2.unshift({ grx, gry: Math.round(funcs.gry(y - yerrs.low)) }); } - return elem; + + const line = this.getOptions().ErrorKind !== 4, + path1 = buildSvgCurve(bins1, { line }), + path2 = buildSvgCurve(bins2, { line, cmd: 'L' }); + + this.appendPath(path1 + path2 + 'Z') + .call(this.fillatt.func); } -} + /** @summary Draw TH1 as hist/line/curve + * @return Promise or scalar value */ + async drawNormal(funcs, width, height) { + const left = this.getSelectIndex('x', 'left', -1), + right = this.getSelectIndex('x', 'right', 2), + histo = this.getHisto(), + o = this.getOptions(), + want_tooltip = !this.isBatchMode() && settings.Tooltip, + xaxis = histo.fXaxis, + exclude_zero = !o.Zero, + show_errors = o.Error, + show_curve = o.Curve, + show_text = o.Text, + text_profile = show_text && (o.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, + grpnts = []; + let res = '', lastbin = false, + show_markers = o.Mark, + show_line = o.Line, + startx, startmidx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, + path_fill = null, path_err = null, path_marker = null, path_line = '', + hints_err = null, hints_text = null, hints_marker = null, hsz = 5, + do_marker = false, do_err = false, + dend = 0, dlw = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx, lx, ly, mmx1, mmx2, + text_col, text_angle, text_size, + pr = Promise.resolve(); -function createFlippedGeom(geom) { - let pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; - const index = geom.getIndex(); + if (show_errors && !show_markers && (histo.fMarkerStyle > 1)) + show_markers = true; - if (index) { - // we need to unfold all points to - const arr = index.array, - i0 = geom.drawRange.start; - let ilen = geom.drawRange.count; - if (i0 + ilen > arr.length) ilen = arr.length - i0; + if (o.ErrorKind === 2) { + if (this.fillatt.empty()) + show_markers = true; + else + path_fill = ''; + } else if (show_errors) { + show_line = false; + path_err = ''; + hints_err = want_tooltip ? '' : null; + do_err = true; + } - const dpos = new Float32Array(ilen*3), dnorm = new Float32Array(ilen*3); - for (let ii = 0; ii < ilen; ++ii) { - const k = arr[i0 + ii]; - if ((k < 0) || (k*3 >= pos.length)) - console.log(`strange index ${k*3} totallen = ${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]; + dlw = this.lineatt.width + gStyle.fEndErrorSize; + if (o.ErrorKind === 1) + dend = Math.floor((this.lineatt.width - 1) / 2); + + if (show_markers) { + // draw markers also when e2 option was specified + this.createAttMarker({ attr: histo, style: o.MarkStyle }); // when style not configured, it will be ignored + if (this.markeratt.size > 0) { + // simply use relative move from point, can optimize in the future + path_marker = ''; + do_marker = true; + this.markeratt.resetPos(); + if ((hints_err === null) && want_tooltip && (!this.markeratt.fill || (this.markeratt.getFullSize() < 7))) { + hints_marker = ''; + hsz = Math.max(5, Math.round(this.markeratt.getFullSize() * 0.7)); + } + } else + show_markers = false; } - pos = dpos; norm = dnorm; - } + const draw_markers = show_errors || show_markers, + draw_any_but_hist = draw_markers || show_text || show_line || show_curve, + draw_hist = o.Hist && (!this.lineatt.empty() || !this.fillatt.empty()), + check_sumw2 = show_errors && histo.fSumw2?.length, + // if there are too many points, exclude many vertical drawings at the same X position + // instead define min and max value and made min-max drawing + use_minmax = draw_any_but_hist || ((right - left) > 3 * width); - const len = pos.length, - newpos = new Float32Array(len), - newnorm = new Float32Array(len); + if (!draw_hist && !draw_any_but_hist) + return this.removeG(); - // we should swap second and third point in each face - for (let n = 0, shift = 0; n < len; n += 3) { - newpos[n] = pos[n+shift]; - newpos[n+1] = pos[n+1+shift]; - newpos[n+2] = -pos[n+2+shift]; + if (show_text) { + text_col = this.getColor(histo.fMarkerColor); + text_angle = -1 * o.TextAngle; + text_size = 20; - newnorm[n] = norm[n+shift]; - newnorm[n+1] = norm[n+1+shift]; - newnorm[n+2] = -norm[n+2+shift]; + if ((histo.fMarkerSize !== 1) && text_angle) + text_size = 0.02 * height * histo.fMarkerSize; - shift+=3; if (shift===6) shift=-3; // values 0,3,-3 - } + if (!text_angle && !o.TextKind) { + const space = width / (right - left + 1); + if (space < 3 * text_size) { + text_angle = 270; + text_size = Math.round(space * 0.7); + } + } - const geomZ = new BufferGeometry(); - geomZ.setAttribute('position', new BufferAttribute(newpos, 3)); - geomZ.setAttribute('normal', new BufferAttribute(newnorm, 3)); + if (want_tooltip && !draw_hist) + hints_text = ''; - return geomZ; -} + pr = this.startTextDrawingAsync(42, text_size, undefined, text_size); + } + return pr.then(() => { + // just to get correct values for the specified bin + const extract_bin = bin => { + bincont = histo.getBinContent(bin + 1); + if (exclude_zero && (bincont === 0) && (!check_sumw2 || !histo.fSumw2[bin + 1])) + return false; + mx1 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin + 1))); + mx2 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin + 2))); + midx = Math.round((mx1 + mx2) / 2); + if (startmidx === undefined) + startmidx = midx; + my = Math.round(funcs.gry(bincont)); + if (show_errors) { + binerr = this.getBinErrors(histo, bin + 1, bincont); + yerr1 = Math.round(my - funcs.gry(bincont + binerr.up)); // up + yerr2 = Math.round(funcs.gry(bincont - binerr.low) - my); // low + } else + yerr1 = yerr2 = 20; -/** @summary Create flipped mesh for the shape - * @desc When transformation matrix includes one or several inversion of axis, - * one should inverse geometry object, otherwise three.js cannot correctly draw it - * @param {Object} shape - TGeoShape object - * @param {Object} material - material - * @private */ -function createFlippedMesh(shape, material) { - if (shape.geomZ === undefined) - shape.geomZ = createFlippedGeom(shape.geom); + return true; + }, draw_errbin = () => { + let edx = 5; + if (o.errorX > 0) { + edx = Math.round((mx2 - mx1) * o.errorX); + mmx1 = midx - edx; + mmx2 = midx + edx; + if (o.ErrorKind === 1) + path_err += `M${mmx1 + dend},${my - dlw}v${2 * dlw}m0,-${dlw}h${mmx2 - mmx1 - 2 * dend}m0,-${dlw}v${2 * dlw}`; + else + path_err += `M${mmx1 + dend},${my}h${mmx2 - mmx1 - 2 * dend}`; + } + if (o.ErrorKind === 1) + path_err += `M${midx - dlw},${my - yerr1 + dend}h${2 * dlw}m${-dlw},0v${yerr1 + yerr2 - 2 * dend}m${-dlw},0h${2 * dlw}`; + else + path_err += `M${midx},${my - yerr1 + dend}v${yerr1 + yerr2 - 2 * dend}`; + if (hints_err !== null) { + const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); + hints_err += `M${midx - edx},${my - he1}h${2 * edx}v${he1 + he2}h${ -2 * edx}z`; + } + }, draw_marker = () => { + if (funcs.swap_xy()) { + path_marker += this.markeratt.create(my, midx); + if (hints_marker !== null) + hints_marker += `M${my - hsz},${midx - hsz}v${2 * hsz}h${2 * hsz}v${ -2 * hsz}z`; + } else { + path_marker += this.markeratt.create(midx, my); + if (hints_marker !== null) + hints_marker += `M${midx - hsz},${my - hsz}h${2 * hsz}v${2 * hsz}h${ -2 * hsz}z`; + } + }, draw_bin = bin => { + if (extract_bin(bin)) { + if (show_text) { + const cont = text_profile ? histo.fBinEntries[bin + 1] : bincont; + + if (cont) { + const arg = text_angle + ? { align: 12, x: midx, y: Math.round(my - 2 - text_size / 5), width: 0, height: 0, rotate: text_angle } + : { align: 22, x: Math.round(mx1 + (mx2 - mx1) * 0.1), y: Math.round(my - 2 - text_size), width: Math.round((mx2 - mx1) * 0.8), height: text_size }; + arg.text = (cont === Math.round(cont)) ? cont.toString() : floatToString(cont, gStyle.fPaintTextFormat); + arg.color = text_col; + arg.latex = 0; + if (funcs.swap_xy()) { + arg.x = my; + arg.y = Math.round(midx - text_size / 2); + } + this.drawText(arg); + if (hints_text !== null) + hints_text += `M${mx1},${my - hsz}v${2 * hsz}h${mx2 - mx1}v${ -2 * hsz}z`; + } + } - const mesh = new Mesh(shape.geomZ, material); - mesh.scale.copy(new Vector3(1, 1, -1)); - mesh.updateMatrix(); + if (show_line) { + if (funcs.swap_xy()) + path_line += (path_line ? 'L' : 'M') + `${my},${midx}`; // no optimization + else if (!path_line) + path_line = `M${midx},${my}`; + else if (lx === midx) + path_line += `v${my - ly}`; + else if (ly === my) + path_line += `h${midx - lx}`; + else + path_line += `l${midx - lx},${my - ly}`; + lx = midx; + ly = my; + } else if (show_curve) + grpnts.push({ grx: (mx1 + mx2) / 2, gry: funcs.gry(bincont) }); + + if (draw_markers) { + if ((my >= -yerr1) && (my <= height + yerr2)) { + if (path_fill !== null) + path_fill += `M${mx1},${my - yerr1}h${mx2 - mx1}v${yerr1 + yerr2 + 1}h${mx1 - mx2}z`; + if ((path_marker !== null) && do_marker) + draw_marker(); + if ((path_err !== null) && do_err) + draw_errbin(); + } + } + } + }; - mesh._flippedMesh = true; + // check if we should draw markers or error marks directly, skipping optimization + if (do_marker || do_err) { + if (!settings.OptimizeDraw || ((right - left < 50000) && (settings.OptimizeDraw === 1))) { + for (i = left; i < right; ++i) { + if (extract_bin(i)) { + if (path_marker !== null) + draw_marker(); + if (path_err !== null) + draw_errbin(); + } + } + do_err = do_marker = false; + } + } - return mesh; -} + for (i = left; i <= right; ++i) { + x = xaxis.GetBinLowEdge(i + 1); -/** @summary extract code of Box3.expandByObject - * @desc Major difference - do not traverse hierarchy, support InstancedMesh - * @private */ -function getBoundingBox(node, box3, local_coordinates) { - if (!node?.geometry) return box3; + if (this.logx && (x <= 0)) + continue; - if (!box3) box3 = new Box3().makeEmpty(); + grx = Math.round(funcs.grx(x)); - if (node.isInstancedMesh) { - const m = new Matrix4(), b = new Box3().makeEmpty(); + lastbin = (i === right); - node.geometry.computeBoundingBox(); + if (lastbin && (left < right)) + gry = curry; + else { + y = histo.getBinContent(i + 1); + gry = Math.round(funcs.gry(y)); + } - for (let i = 0; i < node.count; i++) { - node.getMatrixAt(i, m); - b.copy(node.geometry.boundingBox).applyMatrix4(m); - box3.union(b); - } - return box3; - } + if (!res) { + bestimin = bestimax = i; + prevx = startx = currx = grx; + prevy = curry_min = curry_max = curry = gry; + res = `M${currx},${curry}`; + } else if (use_minmax) { + if ((grx === currx) && !lastbin) { + if (gry < curry_min) + bestimax = i; + else if (gry > curry_max) + bestimin = i; + + curry_min = Math.min(curry_min, gry); + curry_max = Math.max(curry_max, gry); + curry = gry; + } else { + if (draw_any_but_hist) { + if (bestimin === bestimax) + draw_bin(bestimin); + else if (bestimin < bestimax) { + draw_bin(bestimin); + draw_bin(bestimax); + } else { + draw_bin(bestimax); + draw_bin(bestimin); + } + } - if (!local_coordinates) node.updateWorldMatrix(false, false); + // when several points at same X differs, need complete logic + if (draw_hist && ((curry_min !== curry_max) || (prevy !== curry_min))) { + if (prevx !== currx) + res += 'h' + (currx - prevx); - const v1 = new Vector3(), attribute = node.geometry.attributes?.position; + if (curry === curry_min) { + if (curry_max !== prevy) + res += 'v' + (curry_max - prevy); + if (curry_min !== curry_max) + res += 'v' + (curry_min - curry_max); + } else { + if (curry_min !== prevy) + res += 'v' + (curry_min - prevy); + if (curry_max !== curry_min) + res += 'v' + (curry_max - curry_min); + if (curry !== curry_max) + res += 'v' + (curry - curry_max); + } - if (attribute !== undefined) { - for (let i = 0, l = attribute.count; i < l; i++) { - // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); - v1.fromBufferAttribute(attribute, i); - if (!local_coordinates) v1.applyMatrix4(node.matrixWorld); - box3.expandByPoint(v1); - } - } + prevx = currx; + prevy = curry; + } - return box3; -} + if (lastbin && (prevx !== grx)) + res += 'h' + (grx - prevx); -/** @summary Cleanup shape entity - * @private */ -function cleanupShape(shape) { - if (!shape) return; + bestimin = bestimax = i; + curry_min = curry_max = curry = gry; + currx = grx; + } + // end of use_minmax + } else if ((gry !== curry) || lastbin) { + if (grx !== currx) + res += `h${grx - currx}`; + if (gry !== curry) + res += `v${gry - curry}`; + curry = gry; + currx = grx; + } + } - if (isFunc(shape.geom?.dispose)) - shape.geom.dispose(); + const fill_for_interactive = want_tooltip && this.fillatt.empty() && draw_hist && !draw_markers && !show_line && !show_curve && this.isUseFrame(); + let h0 = height + 3; + if (!fill_for_interactive) { + const gry0 = Math.round(funcs.gry(0)); + if (gry0 <= 0) + h0 = -3; + else if (gry0 < height) + h0 = gry0; + } + const close_path = `L${currx},${h0}H${startx}Z`, add_hist = () => { + this.appendPath(res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) + .style('stroke-linejoin', 'miter') + .call(this.lineatt.func) + .call(this.fillatt.func); + }; - if (isFunc(shape.geomZ?.dispose)) - shape.geomZ.dispose(); + if (res && draw_hist && !this.fillatt.empty()) { + add_hist(); + res = ''; + } - delete shape.geom; - delete shape.geomZ; -} + if (draw_markers || show_line || show_curve) { + if (!path_line && grpnts.length) { + if (funcs.swap_xy()) + grpnts.forEach(pnt => { [pnt.grx, pnt.gry] = [pnt.gry, pnt.grx]; }); + path_line = buildSvgCurve(grpnts); + } -/** @summary Set rendering order for created hierarchy - * @desc depending from provided method sort differently objects - * @param toplevel - top element - * @param origin - camera position used to provide sorting - * @param method - name of sorting method like 'pnt', 'ray', 'size', 'dflt' */ -function produceRenderOrder(toplevel, origin, method, clones) { - const raycast = new Raycaster(); + if (path_fill) { + this.appendPath(path_fill) + .call(this.fillatt.func); + } else if (path_line && !this.fillatt.empty() && !draw_hist) { + this.appendPath(path_line + `L${midx},${h0}H${startmidx}Z`) + .call(this.fillatt.func); + } - function setdefaults(top) { - if (!top) return; - top.traverse(obj => { - obj.renderOrder = obj.defaultOrder || 0; - if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled - }); - } + if (path_err) { + this.appendPath(path_err) + .call(this.lineatt.func); + } - function traverse(obj, lvl, arr) { - // traverse hierarchy and extract all children of given level - // if (obj.$jsroot_depth === undefined) return; + if (hints_err) { + this.appendPath(hints_err) + .style('fill', 'none') + .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); + } - if (!obj.children) return; + if (path_line) { + this.appendPath(path_line) + .style('fill', 'none') + .call(this.lineatt.func); + } - for (let k = 0; k < obj.children.length; ++k) { - const 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); + if (path_marker) { + this.appendPath(path_marker) + .call(this.markeratt.func); } - } 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 (hints_marker) { + this.appendPath(hints_marker) + .style('fill', 'none') + .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); + } + } - if (arr.length > 1000) { - // too many of them, just set basic level and exit - for (let i = 0; i < arr.length; ++i) - arr[i].renderOrder = (minorder + maxorder)/2; - return false; - } + if (res && draw_hist) + add_hist(); - const tmp_vect = new Vector3(); + if (hints_text) { + this.appendPath(hints_text) + .style('fill', 'none') + .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); + } - // first calculate distance to the camera - // it gives preliminary order of volumes - for (let i = 0; i < arr.length; ++i) { - const mesh = arr[i]; - let box3 = mesh.$jsroot_box3; + if (show_text) + return this.finishTextDrawing(); + }); + } - if (!box3) - mesh.$jsroot_box3 = box3 = getBoundingBox(mesh); + /** @summary Draw TH1 bins in SVG element + * @return Promise or scalar value */ + draw1DBins() { + const o = this.getOptions(); + if (o.Same && !this.isUseFrame()) + this.getPadPainter().getFrameSvg().style('display', 'none'); - if (method === 'size') { - const sz = box3.getSize(new Vector3()); - mesh.$jsroot_distance = sz.x*sz.y*sz.z; - continue; - } + this.createHistDrawAttributes(); - if (method === 'pnt') { - mesh.$jsroot_distance = origin.distanceTo(box3.getCenter(tmp_vect)); - continue; - } + const funcs = this.getHistGrFuncs(), + width = funcs.getFrameWidth(), + height = funcs.getFrameHeight(); - let dist = Math.min(origin.distanceTo(box3.min), origin.distanceTo(box3.max)); - const pnt = new Vector3(box3.min.x, box3.min.y, box3.max.z); + if (!this.draw_content || (width <= 0) || (height <= 0)) + return this.removeG(); - 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)); + this.createG(this.isUseFrame()); - mesh.$jsroot_distance = dist; + if (o.Bar) { + return this.drawBars(funcs, height).then(() => { + if (o.ErrorKind === 1) + return this.drawNormal(funcs, width, height); + }); } - arr.sort((a, b) => a.$jsroot_distance - b.$jsroot_distance); + if ((o.ErrorKind === 3) || (o.ErrorKind === 4)) + return this.drawFilledErrors(funcs); - const resort = new Array(arr.length); + return this.drawNormal(funcs, width, height); + } - for (let i = 0; i < arr.length; ++i) { - arr[i].$jsroot_index = i; - resort[i] = arr[i]; - } + /** @summary Provide text information (tooltips) for histogram bin */ + getBinTooltips(bin) { + const tips = [], + name = this.getObjectHint(), + funcs = this.getHistGrFuncs(), + histo = this.getHisto(), + o = this.getOptions(), + x1 = histo.fXaxis.GetBinLowEdge(bin + 1), + x2 = histo.fXaxis.GetBinLowEdge(bin + 2), + xlbl = this.getAxisBinTip('x', histo.fXaxis, bin); + let cont = histo.getBinContent(bin + 1); - if (method === 'ray') { - for (let i=arr.length - 1; i >= 0; --i) { - const mesh = arr[i], box3 = mesh.$jsroot_box3; - let intersects, direction = box3.getCenter(tmp_vect); + if (name) + tips.push(name); - for (let ntry = 0; ntry < 2; ++ntry) { - direction.sub(origin).normalize(); + if (o.Error || o.Mark || this.isTF1()) { + tips.push(`x = ${xlbl}`, `y = ${funcs.axisAsText('y', cont)}`); + if (o.Error) { + if (xlbl[0] === '[') + tips.push(`error x = ${((x2 - x1) / 2).toPrecision(4)}`); + const errs = this.getBinErrors(histo, bin + 1, cont); + if (errs.poisson) + tips.push(`error low = ${errs.low.toPrecision(4)}`, `error up = ${errs.up.toPrecision(4)}`); + else + tips.push(`error y = ${errs.up.toPrecision(4)}`); + } + } else { + tips.push(`bin = ${bin + 1}`, `x = ${xlbl}`); + if (histo.$baseh) + cont -= histo.$baseh.getBinContent(bin + 1); + if (cont === Math.round(cont)) + tips.push(`entries = ${cont}`); + else + tips.push(`entries = ${floatToString(cont, gStyle.fStatFormat)}`); + } - raycast.set(origin, direction); + return tips; + } - intersects = raycast.intersectObjects(arr, false) || []; // only plain array - const unique = []; + /** @summary Process tooltip event */ + processTooltipEvent(pnt) { + const o = this.getOptions(); + if (!pnt || !this.draw_content || !this.getG() || o.Mode3D) { + this.getG()?.selectChild('.tooltip_bin').remove(); + return null; + } - for (let 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 - } + const funcs = this.getHistGrFuncs(), + histo = this.getHisto(), + left = this.getSelectIndex('x', 'left', -1), + right = this.getSelectIndex('x', 'right', 2), + draw_hist = this.options.Hist && (!this.lineatt.empty() || !this.fillatt.empty()); + let width = funcs.getFrameWidth(), + height = funcs.getFrameHeight(), + show_rect, grx1, grx2, gry1, gry2, gapx = 2, + l = left, r = right, pnt_x = pnt.x, pnt_y = pnt.y; - intersects = unique; + const GetBinGrX = i => { + const xx = histo.fXaxis.GetBinLowEdge(i + 1); + return (funcs.logx && (xx <= 0)) ? null : funcs.grx(xx); + }, GetBinGrY = i => { + const yy = histo.getBinContent(i + 1); + if (funcs.logy && (yy < funcs.scale_ymin)) + return funcs.swap_xy() ? -1e3 : 10 * height; + return Math.round(funcs.gry(yy)); + }; - if ((intersects.indexOf(mesh) < 0) && (ntry > 0)) - console.log(`MISS ${clones?.resolveStack(mesh.stack)?.name}`); + if (funcs.swap_xy()) + [pnt_x, pnt_y, width, height] = [pnt_y, pnt_x, height, width]; - if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break; + const descent_order = funcs.x_handle && (funcs.swap_xy() !== funcs.x_handle.reverse); - const pos = mesh.geometry.attributes.position.array; + while (l < r - 1) { + const m = Math.round((l + r) * 0.5), xx = GetBinGrX(m); + if ((xx === null) || (xx < pnt_x - 0.5)) { + if (descent_order) + r = m; + else + l = m; + } else if (xx > pnt_x + 0.5) { + if (descent_order) + l = m; + else + r = m; + } else { + l++; + r--; + } + } - direction = new Vector3((pos[0]+pos[3]+pos[6])/3, (pos[1]+pos[4]+pos[7])/3, (pos[2]+pos[5]+pos[8])/3); + let findbin = r = l; + grx1 = GetBinGrX(findbin); - direction.applyMatrix4(mesh.matrixWorld); - } + if (descent_order) { + while ((l > left) && (GetBinGrX(l - 1) < grx1 + 2)) + --l; + while ((r < right) && (GetBinGrX(r + 1) > grx1 - 2)) + ++r; + } else { + while ((l > left) && (GetBinGrX(l - 1) > grx1 - 2)) + --l; + while ((r < right) && (GetBinGrX(r + 1) < grx1 + 2)) + ++r; + } - // now push first object in intersects to the front - for (let k1 = 0; k1 < intersects.length - 1; ++k1) { - const mesh1 = intersects[k1], mesh2 = intersects[k1+1], - i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; - if (i1 < i2) continue; - for (let ii = i2; ii < i1; ++ii) { - resort[ii] = resort[ii+1]; - resort[ii].$jsroot_index = ii; - } - resort[i1] = mesh2; - mesh2.$jsroot_index = i1; + if (l < r) { + // many points can be assigned with the same cursor position + // first try point around mouse y + let best = height; + for (let m = l; m <= r; m++) { + const dist = Math.abs(GetBinGrY(m) - pnt_y); + if (dist < best) { + best = dist; + findbin = m; } } + + // if best distance still too far from mouse position, just take from between + if (best > height / 10) + findbin = Math.round(l + (r - l) / height * pnt_y); + + grx1 = GetBinGrX(findbin); } - for (let i = 0; i < resort.length; ++i) { - resort[i].renderOrder = Math.round(maxorder - (i+1) / (resort.length + 1) * (maxorder - minorder)); - delete resort[i].$jsroot_index; - delete resort[i].$jsroot_distance; + grx1 = Math.round(grx1); + grx2 = Math.round(GetBinGrX(findbin + 1)); + + if (o.Bar) { + const w = grx2 - grx1; + grx1 += Math.round(histo.fBarOffset / 1000 * w); + grx2 = grx1 + Math.round(histo.fBarWidth / 1000 * w); } - return true; - } + if (grx1 > grx2) + [grx1, grx2] = [grx2, grx1]; - function process(obj, lvl, minorder, maxorder) { - const arr = []; - let did_sort = false; + const midx = Math.round((grx1 + grx2) / 2), + midy = gry1 = gry2 = GetBinGrY(findbin); - traverse(obj, lvl, arr); + if (o.Bar) { + show_rect = true; + gapx = 0; + gry1 = this.getBarBaseline(funcs, height); + if (gry1 > gry2) + [gry1, gry2] = [gry2, gry1]; + if (!pnt.touch && (pnt.nproc === 1) && ((pnt_y < gry1) || (pnt_y > gry2))) + findbin = null; + } else if ((o.Error && (o.Hist !== true)) || o.Mark || o.Line || o.Curve || (o.Text && !draw_hist)) { + show_rect = !this.isTF1(); + + let msize = 3; + if (this.markeratt) + msize = Math.max(msize, this.markeratt.getFullSize()); - if (!arr.length) return; + if (o.Error) { + const cont = histo.getBinContent(findbin + 1), + binerrs = this.getBinErrors(histo, findbin + 1, cont); - if (minorder === maxorder) { - for (let k = 0; k < arr.length; ++k) - arr[k].renderOrder = minorder; + gry1 = Math.round(funcs.gry(cont + binerrs.up)); // up + gry2 = Math.round(funcs.gry(cont - binerrs.low)); // low + + if ((cont === 0) && this.isTProfile()) + findbin = null; + + const dx = (grx2 - grx1) * o.errorX; + grx1 = Math.round(midx - dx); + grx2 = Math.round(midx + dx); + } + + // show at least 6 pixels as tooltip rect + if (grx2 - grx1 < 2 * msize) { + grx1 = midx - msize; + grx2 = midx + msize; + } + + gry1 = Math.min(gry1, midy - msize); + gry2 = Math.max(gry2, midy + msize); + + if (!pnt.touch && (pnt.nproc === 1)) { + if ((pnt_y < gry1) || (pnt_y > gry2)) + findbin = null; + } } else { - did_sort = sort(arr, minorder, maxorder); - if (!did_sort) minorder = maxorder = (minorder + maxorder) / 2; - } + // if histogram alone, use old-style with rects + // if there are too many points at pixel, use circle + show_rect = (pnt.nproc === 1) && (right - left < width); - for (let k = 0; k < arr.length; ++k) { - const next = arr[k].parent; - let min = minorder, max = maxorder; + if (show_rect) { + gry2 = height; - if (did_sort) { - max = arr[k].renderOrder; - min = max - (maxorder - minorder) / (arr.length + 2); + if (!this.fillatt.empty()) { + gry2 = Math.min(height, Math.max(0, Math.round(funcs.gry(0)))); + if (gry2 < gry1) + [gry1, gry2] = [gry2, gry1]; + } + + // for mouse events pointer should be between y1 and y2 + if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) + findbin = null; } + } - process(next, lvl+1, min, max); + if (findbin !== null) { + // if bin on boundary found, check that x position is ok + if ((findbin === left) && (grx1 > pnt_x + gapx)) + findbin = null; + else if ((findbin === right - 1) && (grx2 < pnt_x - gapx)) + findbin = null; + else if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) + findbin = null; // if bars option used check that bar is not match + else if (!o.Zero && (histo.getBinContent(findbin + 1) === 0) && (histo.getBinError(findbin + 1) === 0)) + findbin = null; // exclude empty bin if empty bins suppressed } - } - if (!method || (method === 'dflt')) - setdefaults(toplevel); - else - process(toplevel, 0, 1, 1000000); -} + let ttrect = this.getG().selectChild('.tooltip_bin'); -/** @summary provide icon name for the shape - * @private */ -function getShapeIcon(shape) { - switch (shape._typename) { - case clTGeoArb8: return 'img_geoarb8'; - case clTGeoCone: return 'img_geocone'; - case clTGeoConeSeg: return 'img_geoconeseg'; - case clTGeoCompositeShape: return 'img_geocomposite'; - case clTGeoTube: return 'img_geotube'; - case clTGeoTubeSeg: return 'img_geotubeseg'; - case clTGeoPara: return 'img_geopara'; - case clTGeoParaboloid: return 'img_geoparab'; - case clTGeoPcon: return 'img_geopcon'; - case clTGeoPgon: return 'img_geopgon'; - case clTGeoShapeAssembly: return 'img_geoassembly'; - case clTGeoSphere: return 'img_geosphere'; - case clTGeoTorus: return 'img_geotorus'; - case clTGeoTrd1: return 'img_geotrd1'; - case clTGeoTrd2: return 'img_geotrd2'; - case clTGeoXtru: return 'img_geoxtru'; - case clTGeoTrap: return 'img_geotrap'; - case clTGeoGtra: return 'img_geogtra'; - case clTGeoEltu: return 'img_geoeltu'; - case clTGeoHype: return 'img_geohype'; - case clTGeoCtub: return 'img_geoctub'; - } - return 'img_geotube'; -} + if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) { + ttrect.remove(); + return null; + } -/** - * lil-gui - * https://lil-gui.georgealways.com - * @version 0.18.2 - * @author George Michael Brower - * @license MIT - */ + const res = { + name: this.getObjectName(), title: histo.fTitle, + x: midx, y: midy, exact: true, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getBinTooltips(findbin) + }; -/** - * Base class for all controllers. - */ -class Controller { + if (pnt.disabled) { + // case when tooltip should not highlight bin + ttrect.remove(); + res.changed = true; + } else if (show_rect) { + if (ttrect.empty()) { + ttrect = this.getG().append('svg:rect') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle); + } - constructor( parent, object, property, className, widgetTag = 'div' ) { + res.changed = ttrect.property('current_bin') !== findbin; - /** - * The GUI that contains this controller. - * @type {GUI} - */ - this.parent = parent; + if (res.changed) { + ttrect.attr('x', funcs.swap_xy() ? gry1 : grx1) + .attr('width', funcs.swap_xy() ? gry2 - gry1 : grx2 - grx1) + .attr('y', funcs.swap_xy() ? grx1 : gry1) + .attr('height', funcs.swap_xy() ? grx2 - grx1 : gry2 - gry1) + .style('opacity', '0.3') + .property('current_bin', findbin); + } - /** - * The object this controller will modify. - * @type {object} - */ - this.object = object; + res.exact = (Math.abs(midy - pnt_y) <= 5) || ((pnt_y >= gry1) && (pnt_y <= gry2)); - /** - * The name of the property to control. - * @type {string} - */ - this.property = property; + res.menu = res.exact; // one could show context menu when histogram is selected + // distance to middle point, use to decide which menu to activate + res.menu_dist = Math.sqrt((midx - pnt_x) ** 2 + (midy - pnt_y) ** 2); + } else { + const radius = this.lineatt.width + 3; - /** - * Used to determine if the controller is disabled. - * Use `controller.disable( true|false )` to modify this value - * @type {boolean} - */ - this._disabled = false; + if (ttrect.empty()) { + ttrect = this.getG().append('svg:circle') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .attr('r', radius) + .call(this.lineatt.func) + .call(this.fillatt.func); + } - /** - * Used to determine if the Controller is hidden. - * Use `controller.show()` or `controller.hide()` to change this. - * @type {boolean} - */ - this._hidden = false; + res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius); - /** - * The value of `object[ property ]` when the controller was created. - * @type {any} - */ - this.initialValue = this.getValue(); + res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram + res.menu_dist = Math.sqrt((midx - pnt.x) ** 2 + (midy - pnt.y) ** 2); - /** - * The outermost container DOM element for this controller. - * @type {HTMLElement} - */ - this.domElement = document.createElement( 'div' ); - this.domElement.classList.add( 'controller' ); - this.domElement.classList.add( className ); + res.changed = ttrect.property('current_bin') !== findbin; - /** - * The DOM element that contains the controller's name. - * @type {HTMLElement} - */ - this.$name = document.createElement( 'div' ); - this.$name.classList.add( 'name' ); + if (res.changed) { + ttrect.attr('cx', midx) + .attr('cy', midy) + .property('current_bin', findbin); + } + } - Controller.nextNameID = Controller.nextNameID || 0; - this.$name.id = `lil-gui-name-${++Controller.nextNameID}`; + if (res.changed) { + res.user_info = { + obj: histo, name: histo.fName, + bin: findbin, cont: histo.getBinContent(findbin + 1), + grx: midx, gry: midy + }; + } - /** - * The DOM element that contains the controller's "widget" (which differs by controller type). - * @type {HTMLElement} - */ - this.$widget = document.createElement( widgetTag ); - this.$widget.classList.add( 'widget' ); + return res; + } - /** - * The DOM element that receives the disabled attribute when using disable() - * @type {HTMLElement} - */ - this.$disable = this.$widget; + /** @summary Fill histogram context menu */ + fillHistContextMenu(menu) { + menu.add('Auto zoom-in', () => this.autoZoom()); - this.domElement.appendChild( this.$name ); - this.domElement.appendChild( this.$widget ); + const opts = this.getSupportedDrawOptions(); - // Don't fire global key events while typing in a controller - this.domElement.addEventListener( 'keydown', e => e.stopPropagation() ); - this.domElement.addEventListener( 'keyup', e => e.stopPropagation() ); + menu.addDrawMenu('Draw with', opts, arg => { + if (arg.indexOf(kInspect) === 0) + return this.showInspector(arg); - this.parent.children.push( this ); - this.parent.controllers.push( this ); + this.decodeOptions(arg); - this.parent.$children.appendChild( this.domElement ); + if (this.getOptions().need_fillcol && this.fillatt?.empty()) + this.fillatt.change(5, 1001); - this._listenCallback = this._listenCallback.bind( this ); + // redraw all objects in pad, inform dependent objects + this.interactiveRedraw('pad', 'drawopt'); + }); - this.name( property ); + if (!this.hasSnapId() && !this.isTProfile() && !this.isTF1()) + menu.addRebinMenu(sz => this.rebinHist(sz)); + } - } + /** @summary Rebin histogram, used via context menu */ + rebinHist(sz) { + const histo = this.getHisto(), + xaxis = histo.fXaxis, + nbins = Math.floor(xaxis.fNbins / sz); + if (nbins < 2) + return; - /** - * Sets the name of the controller and its label in the GUI. - * @param {string} name - * @returns {this} - */ - name( name ) { - /** - * The controller's name. Use `controller.name( 'Name' )` to modify this value. - * @type {string} - */ - this._name = name; - this.$name.innerHTML = name; - return this; - } + const arr = new Array(nbins + 2), + xbins = xaxis.fXbins.length ? new Array(nbins) : null; - /** - * Pass a function to be called whenever the value is modified by this controller. - * The function receives the new value as its first parameter. The value of `this` will be the - * controller. - * - * For function controllers, the `onChange` callback will be fired on click, after the function - * executes. - * @param {Function} callback - * @returns {this} - * @example - * const controller = gui.add( object, 'property' ); - * - * controller.onChange( function( v ) { - * console.log( 'The value is now ' + v ); - * console.assert( this === controller ); - * } ); - */ - onChange( callback ) { - /** - * Used to access the function bound to `onChange` events. Don't modify this value directly. - * Use the `controller.onChange( callback )` method instead. - * @type {Function} - */ - this._onChange = callback; - return this; - } + arr[0] = histo.fArray[0]; + let indx = 1; - /** - * Calls the onChange methods of this controller and its parent GUI. - * @protected - */ - _callOnChange() { + for (let i = 1; i <= nbins; ++i) { + if (xbins) + xbins[i - 1] = xaxis.fXbins[indx - 1]; + let sum = 0; + for (let k = 0; k < sz; ++k) + sum += histo.fArray[indx++]; + arr[i] = sum; + } - this.parent._callOnChange( this ); + if (xbins) { + if (indx <= xaxis.fXbins.length) + xaxis.fXmax = xaxis.fXbins[indx - 1]; + xaxis.fXbins = xbins; + } else + xaxis.fXmax = xaxis.fXmin + (xaxis.fXmax - xaxis.fXmin) / xaxis.fNbins * nbins * sz; - if ( this._onChange !== undefined ) { - this._onChange.call( this, this.getValue() ); - } - this._changed = true; + xaxis.fNbins = nbins; - } + let overflow = 0; + while (indx < histo.fArray.length) + overflow += histo.fArray[indx++]; + arr[nbins + 1] = overflow; - /** - * Pass a function to be called after this controller has been modified and loses focus. - * @param {Function} callback - * @returns {this} - * @example - * const controller = gui.add( object, 'property' ); - * - * controller.onFinishChange( function( v ) { - * console.log( 'Changes complete: ' + v ); - * console.assert( this === controller ); - * } ); - */ - onFinishChange( callback ) { - /** - * Used to access the function bound to `onFinishChange` events. Don't modify this value - * directly. Use the `controller.onFinishChange( callback )` method instead. - * @type {Function} - */ - this._onFinishChange = callback; - return this; - } + histo.fArray = arr; + histo.fSumw2 = []; - /** - * Should be called by Controller when its widgets lose focus. - * @protected - */ - _callOnFinishChange() { + this.scanContent(); - if ( this._changed ) { + this.interactiveRedraw('pad'); + } - this.parent._callOnFinishChange( this ); + /** @summary Perform automatic zoom inside non-zero region of histogram */ + autoZoom() { + let left = this.getSelectIndex('x', 'left', -1), + right = this.getSelectIndex('x', 'right', 1); + const dist = right - left, + histo = this.getHisto(); - if ( this._onFinishChange !== undefined ) { - this._onFinishChange.call( this, this.getValue() ); - } + if ((dist === 0) || !histo) + return; - } + // first find minimum + let min = histo.getBinContent(left + 1); + for (let indx = left; indx < right; ++indx) + min = Math.min(min, histo.getBinContent(indx + 1)); + if (min > 0) + return; // if all points positive, no chance for auto-scale - this._changed = false; + while ((left < right) && (histo.getBinContent(left + 1) <= min)) ++left; + while ((left < right) && (histo.getBinContent(right) <= min)) --right; - } + // if singular bin + if ((left === right - 1) && (left > 2) && (right < this.nbinsx - 2)) { + --left; ++right; + } - /** - * Sets the controller back to its initial value. - * @returns {this} - */ - reset() { - this.setValue( this.initialValue ); - this._callOnFinishChange(); - return this; - } + if ((right - left < dist) && (left < right)) + return this.getFramePainter().zoom(histo.fXaxis.GetBinLowEdge(left + 1), histo.fXaxis.GetBinLowEdge(right + 1)); + } - /** - * Enables this controller. - * @param {boolean} enabled - * @returns {this} - * @example - * controller.enable(); - * controller.enable( false ); // disable - * controller.enable( controller._disabled ); // toggle - */ - enable( enabled = true ) { - return this.disable( !enabled ); - } + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + const histo = this.getHisto(); - /** - * Disables this controller. - * @param {boolean} disabled - * @returns {this} - * @example - * controller.disable(); - * controller.disable( false ); // enable - * controller.disable( !controller._disabled ); // toggle - */ - disable( disabled = true ) { + if ((axis === 'x') && histo && (histo.fXaxis.FindBin(max, 0.5) - histo.fXaxis.FindBin(min, 0) > 1)) + return true; - if ( disabled === this._disabled ) return this; + if ((axis === 'y') && (Math.abs(max - min) > Math.abs(this.ymax - this.ymin) * 1e-6)) + return true; - this._disabled = disabled; + return false; + } - this.domElement.classList.toggle( 'disabled', disabled ); - this.$disable.toggleAttribute( 'disabled', disabled ); + /** @summary Performs 2D drawing of histogram + * @return {Promise} when ready */ + async draw2D(reason) { + this.clear3DScene(); - return this; + this.scanContent(reason === 'zoom'); - } + const pr = this.isMainPainter() ? this.drawColorPalette(false) : Promise.resolve(true); - /** - * Shows the Controller after it's been hidden. - * @param {boolean} show - * @returns {this} - * @example - * controller.show(); - * controller.show( false ); // hide - * controller.show( controller._hidden ); // toggle - */ - show( show = true ) { + return pr.then(() => this.drawAxes()) + .then(() => this.draw1DBins()) + .then(() => this.updateFunctions()) + .then(() => this.updateHistTitle()) + .then(() => { + this.updateStatWebCanvas(); + return this.addInteractivity(); + }); + } - this._hidden = !show; + /** @summary Should performs 3D drawing of histogram + * @desc Disable in 2D case, just draw with default options + * @return {Promise} when ready */ + async draw3D(reason) { + console.log('3D drawing is disabled, load ./hist/TH1Painter.mjs'); + return this.draw2D(reason); + } - this.domElement.style.display = this._hidden ? 'none' : ''; + /** @summary Call drawing function depending from 3D mode */ + async callDrawFunc(reason) { + const main = this.getMainPainter(), + fp = this.getFramePainter(), + o = this.getOptions(); - return this; + if ((main !== this) && fp && (fp.mode3d !== o.Mode3D)) + this.copyOptionsFrom(main); - } + if (!o.Mode3D) + return this.draw2D(reason); - /** - * Hides the Controller. - * @returns {this} - */ - hide() { - return this.show( false ); - } + return this.draw3D(reason).catch(err => { + const cp = this.getCanvPainter(); + if (isFunc(cp?.showConsoleError)) + cp.showConsoleError(err); + else + console.error('Fail to draw histogram in 3D - back to 2D'); + o.Mode3D = false; + return this.draw2D(reason); + }); + } - /** - * Destroys this controller and replaces it with a new option controller. Provided as a more - * descriptive syntax for `gui.add`, but primarily for compatibility with dat.gui. - * - * Use caution, as this method will destroy old references to this controller. It will also - * change controller order if called out of sequence, moving the option controller to the end of - * the GUI. - * @example - * // safe usage - * - * gui.add( object1, 'property' ).options( [ 'a', 'b', 'c' ] ); - * gui.add( object2, 'property' ); - * - * // danger - * - * const c = gui.add( object1, 'property' ); - * gui.add( object2, 'property' ); - * - * c.options( [ 'a', 'b', 'c' ] ); - * // controller is now at the end of the GUI even though it was added first - * - * assert( c.parent.children.indexOf( c ) === -1 ) - * // c references a controller that no longer exists - * - * @param {object|Array} options - * @returns {Controller} - */ - options( options ) { - const controller = this.parent.add( this.object, this.property, options ); - controller.name( this._name ); - this.destroy(); - return controller; - } + /** @summary Redraw histogram */ + redraw(reason) { + return this.callDrawFunc(reason); + } - /** - * Sets the minimum value. Only works on number controllers. - * @param {number} min - * @returns {this} - */ - min( min ) { - return this; - } + /** @summary draw TH1 object in 2D only */ + static async draw(dom, histo, opt) { + return THistPainter._drawHist(new TH1Painter(dom, histo), opt); + } - /** - * Sets the maximum value. Only works on number controllers. - * @param {number} max - * @returns {this} - */ - max( max ) { - return this; - } +}; // class TH1Painter - /** - * Values set by this controller will be rounded to multiples of `step`. Only works on number - * controllers. - * @param {number} step - * @returns {this} - */ - step( step ) { - return this; - } +/** @summary Draw 1-D histogram in 3D + * @private */ - /** - * Rounds the displayed value to a fixed number of decimals, without affecting the actual value - * like `step()`. Only works on number controllers. - * @example - * gui.add( object, 'property' ).listen().decimals( 4 ); - * @param {number} decimals - * @returns {this} - */ - decimals( decimals ) { - return this; - } +class TH1Painter extends TH1Painter$2 { - /** - * Calls `updateDisplay()` every animation frame. Pass `false` to stop listening. - * @param {boolean} listen - * @returns {this} - */ - listen( listen = true ) { + /** @summary draw TH1 object in 3D mode */ + async draw3D(reason) { + this.mode3d = true; - /** - * Used to determine if the controller is currently listening. Don't modify this value - * directly. Use the `controller.listen( true|false )` method instead. - * @type {boolean} - */ - this._listening = listen; + const fp = this.getFramePainter(), // who makes axis drawing + is_main = this.isMainPainter(), // is main histogram + o = this.getOptions(); - if ( this._listenCallbackID !== undefined ) { - cancelAnimationFrame( this._listenCallbackID ); - this._listenCallbackID = undefined; - } + o.zmult = 1 + 2 * gStyle.fHistTopMargin; + let pr = Promise.resolve(true), full_draw = true; - if ( this._listening ) { - this._listenCallback(); - } + if (reason === 'resize') { + const res = is_main ? fp.resize3D() : false; + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); + } + } - return this; + if (full_draw) { + this.createHistDrawAttributes(true); - } + this.scanContent(reason === 'zoom'); // may be required for axis drawings - _listenCallback() { + if (is_main) + pr = crete3DFrame(this, TAxisPainter, o.Render3D); - this._listenCallbackID = requestAnimationFrame( this._listenCallback ); + if (fp.mode3d) { + pr = pr.then(() => { + drawBinsLego(this); + fp.render3D(); + this.updateStatWebCanvas(); + fp.addKeysHandler(); + }); + } + } - // To prevent framerate loss, make sure the value has changed before updating the display. - // Note: save() is used here instead of getValue() only because of ColorController. The !== operator - // won't work for color objects or arrays, but ColorController.save() always returns a string. + if (is_main) + pr = pr.then(() => this.drawColorPalette(o.Zscale && o.canHavePalette())); - const curValue = this.save(); + return pr.then(() => this.updateFunctions()) + .then(() => this.updateHistTitle()) + .then(() => this); + } - if ( curValue !== this._listenPrevValue ) { - this.updateDisplay(); - } + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt) { + const painter = new TH1Painter(null, histo); + painter.decodeOptions(opt); + painter.scanContent(); - this._listenPrevValue = curValue; + painter.createHistDrawAttributes(true); + painter.options.zmult = 1 + 2 * gStyle.fHistTopMargin; - } + const fp = new TFramePainter(null, null); + painter.getFramePainter = () => fp; - /** - * Returns `object[ property ]`. - * @returns {any} - */ - getValue() { - return this.object[ this.property ]; - } + return crete3DFrame(painter, TAxisPainter) + .then(() => drawBinsLego(painter)) + .then(() => fp.create3DScene(-1, true)); + } - /** - * Sets the value of `object[ property ]`, invokes any `onChange` handlers and updates the display. - * @param {any} value - * @returns {this} - */ - setValue( value ) { - this.object[ this.property ] = value; - this._callOnChange(); - this.updateDisplay(); - return this; - } - /** - * Updates the display to keep it in sync with the current value. Useful for updating your - * controllers when their values have been modified outside of the GUI. - * @returns {this} - */ - updateDisplay() { - return this; - } + /** @summary draw TH1 object */ + static async draw(dom, histo, opt) { + return THistPainter._drawHist(new TH1Painter(dom, histo), opt); + } - load( value ) { - this.setValue( value ); - this._callOnFinishChange(); - return this; - } +} // class TH1Painter - save() { - return this.getValue(); - } +var TH1Painter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TH1Painter: TH1Painter +}); - /** - * Destroys this controller and removes it from the parent GUI. - */ - destroy() { - this.listen( false ); - this.parent.children.splice( this.parent.children.indexOf( this ), 1 ); - this.parent.controllers.splice( this.parent.controllers.indexOf( this ), 1 ); - this.parent.$children.removeChild( this.domElement ); - } +/** @summary Draw TH2Poly histogram as lego + * @private */ +function drawTH2PolyLego(painter) { + const histo = painter.getHisto(), + fp = painter.getFramePainter(), + axis_zmin = fp.z_handle.getScaleMin(), + axis_zmax = fp.z_handle.getScaleMax(), + len = histo.fBins.arr.length, + z0 = fp.grz(axis_zmin); + let colindx, bin, i, z1; -} + // use global coordinates + painter.maxbin = painter.gmaxbin; + painter.minbin = painter.gminbin; + painter.minposbin = painter.gminposbin; -class BooleanController extends Controller { + const cntr = painter.getContour(true), palette = painter.getHistPalette(); - constructor( parent, object, property ) { + for (i = 0; i < len; ++i) { + bin = histo.fBins.arr[i]; + if (bin.fContent < axis_zmin) + continue; - super( parent, object, property, 'boolean', 'label' ); + colindx = cntr.getPaletteIndex(palette, bin.fContent); + if (colindx === null) + continue; - this.$input = document.createElement( 'input' ); - this.$input.setAttribute( 'type', 'checkbox' ); - this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + // check if bin outside visible range + if ((bin.fXmin > fp.scale_xmax) || (bin.fXmax < fp.scale_xmin) || + (bin.fYmin > fp.scale_ymax) || (bin.fYmax < fp.scale_ymin)) + continue; - this.$widget.appendChild( this.$input ); + z1 = fp.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent); - this.$input.addEventListener( 'change', () => { - this.setValue( this.$input.checked ); - this._callOnFinishChange(); - } ); + const all_pnts = [], all_faces = []; + let ngraphs = 1, gr = bin.fPoly, nfaces = 0; - this.$disable = this.$input; + if (gr._typename === clTMultiGraph) { + ngraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } - this.updateDisplay(); + for (let ngr = 0; ngr < ngraphs; ++ngr) { + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; - } + const x = gr.fX, y = gr.fY; + let npnts = gr.fNpoints; + while ((npnts > 2) && (x[0] === x[npnts - 1]) && (y[0] === y[npnts - 1])) --npnts; - updateDisplay() { - this.$input.checked = this.getValue(); - return this; - } + let pnts, faces; -} + for (let ntry = 0; ntry < 2; ++ntry) { + // run two loops - on the first try to compress data, on second - run as is (removing duplication) -function normalizeColorString( string ) { + let lastx, lasty, currx, curry, + dist2 = fp.size_x3d * fp.size_z3d; + const dist2limit = (ntry > 0) ? 0 : dist2 / 1e6; - let match, result; + pnts = []; + faces = null; - if ( match = string.match( /(#|0x)?([a-f0-9]{6})/i ) ) { + for (let vert = 0; vert < npnts; ++vert) { + currx = fp.grx(x[vert]); + curry = fp.gry(y[vert]); + if (vert > 0) + dist2 = (currx - lastx) ** 2 + (curry - lasty) ** 2; + if (dist2 > dist2limit) { + pnts.push(new THREE.Vector2(currx, curry)); + lastx = currx; + lasty = curry; + } + } - result = match[ 2 ]; + try { + if (pnts.length > 2) + faces = THREE.ShapeUtils.triangulateShape(pnts, []); + } catch { + faces = null; + } - } else if ( match = string.match( /rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/ ) ) { + if (faces && (faces.length > pnts.length - 3)) + break; + } - result = parseInt( match[ 1 ] ).toString( 16 ).padStart( 2, 0 ) - + parseInt( match[ 2 ] ).toString( 16 ).padStart( 2, 0 ) - + parseInt( match[ 3 ] ).toString( 16 ).padStart( 2, 0 ); + if (faces?.length && pnts) { + all_pnts.push(pnts); + all_faces.push(faces); - } else if ( match = string.match( /^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i ) ) { + nfaces += faces.length * 2; + if (z1 > z0) + nfaces += pnts.length * 2; + } + } - result = match[ 1 ] + match[ 1 ] + match[ 2 ] + match[ 2 ] + match[ 3 ] + match[ 3 ]; + const pos = new Float32Array(nfaces * 9); + let indx = 0; - } + for (let ngr = 0; ngr < all_pnts.length; ++ngr) { + const pnts = all_pnts[ngr], faces = all_faces[ngr]; - if ( result ) { - return '#' + result; - } + for (let layer = 0; layer < 2; ++layer) { + for (let n = 0; n < faces.length; ++n) { + const face = faces[n], + pnt1 = pnts[face[0]], + pnt2 = pnts[face[layer === 0 ? 2 : 1]], + pnt3 = pnts[face[layer === 0 ? 1 : 2]]; - return false; + pos[indx] = pnt1.x; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; -} + pos[indx] = pnt2.x; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; -const STRING = { - isPrimitive: true, - match: v => typeof v === 'string', - fromHexString: normalizeColorString, - toHexString: normalizeColorString -}; + pos[indx] = pnt3.x; + pos[indx + 1] = pnt3.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; + } + } -const INT = { - isPrimitive: true, - match: v => typeof v === 'number', - fromHexString: string => parseInt( string.substring( 1 ), 16 ), - toHexString: value => '#' + value.toString( 16 ).padStart( 6, 0 ) -}; + if (z1 > z0) { + for (let n = 0; n < pnts.length; ++n) { + const pnt1 = pnts.at(n), pnt2 = pnts.at(n > 0 ? n - 1 : -1); -const ARRAY = { - isPrimitive: false, + pos[indx] = pnt1.x; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z0; + indx += 3; - // The arrow function is here to appease tree shakers like esbuild or webpack. - // See https://esbuild.github.io/api/#tree-shaking - match: v => Array.isArray( v ), + pos[indx] = pnt2.x; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z0; + indx += 3; - fromHexString( string, target, rgbScale = 1 ) { + pos[indx] = pnt2.x; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z1; + indx += 3; - const int = INT.fromHexString( string ); + pos[indx] = pnt1.x; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z0; + indx += 3; - target[ 0 ] = ( int >> 16 & 255 ) / 255 * rgbScale; - target[ 1 ] = ( int >> 8 & 255 ) / 255 * rgbScale; - target[ 2 ] = ( int & 255 ) / 255 * rgbScale; + pos[indx] = pnt2.x; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z1; + indx += 3; - }, - toHexString( [ r, g, b ], rgbScale = 1 ) { + pos[indx] = pnt1.x; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z1; + indx += 3; + } + } + } - rgbScale = 255 / rgbScale; + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + geometry.computeVertexNormals(); - const int = ( r * rgbScale ) << 16 ^ - ( g * rgbScale ) << 8 ^ - ( b * rgbScale ) << 0; + const material = new THREE.MeshBasicMaterial(getMaterialArgs(painter.getHistPalette()?.getColor(colindx), { vertexColors: false, side: THREE.DoubleSide })), + mesh = new THREE.Mesh(geometry, material); - return INT.toHexString( int ); + fp.add3DMesh(mesh); - } -}; + mesh.painter = painter; + mesh.bins_index = i; + mesh.draw_z0 = z0; + mesh.draw_z1 = z1; + mesh.tip_color = 0x00FF00; -const OBJECT = { - isPrimitive: false, - match: v => Object( v ) === v, - fromHexString( string, target, rgbScale = 1 ) { + mesh.tooltip = function(/* intersects */) { + const p = this.painter, + tbin = p.getObject().fBins.arr[this.bins_index]; + return { + use_itself: true, // indicate that use mesh itself for highlighting + x1: fp.grx(tbin.fXmin), + x2: fp.grx(tbin.fXmax), + y1: fp.gry(tbin.fYmin), + y2: fp.gry(tbin.fYmax), + z1: this.draw_z0, + z2: this.draw_z1, + bin: this.bins_index, + value: bin.fContent, + color: this.tip_color, + lines: p.getPolyBinTooltips(this.bins_index) + }; + }; + } +} - const int = INT.fromHexString( string ); +/** @summary Draw 2-D histogram in 3D + * @private */ +class TH2Painter extends TH2Painter$2 { - target.r = ( int >> 16 & 255 ) / 255 * rgbScale; - target.g = ( int >> 8 & 255 ) / 255 * rgbScale; - target.b = ( int & 255 ) / 255 * rgbScale; + /** @summary Check range for 3D + * @private */ + checkRangeFor3D(o) { + const pad = this.getPadPainter()?.getRootPad(true), + logz = pad?.fLogv ?? pad?.fLogz; + let zmult = 1; - }, - toHexString( { r, g, b }, rgbScale = 1 ) { + if (o.ohmin && o.ohmax) { + this.zmin = o.hmin; + this.zmax = o.hmax; + } else if (o.minimum !== kNoZoom && o.maximum !== kNoZoom) { + this.zmin = o.minimum; + this.zmax = o.maximum; + } else if (this.draw_content || this.gmaxbin) { + this.zmin = logz ? this.gminposbin * 0.3 : this.gminbin; + this.zmax = this.gmaxbin; + zmult = 1 + 2 * gStyle.fHistTopMargin; + } - rgbScale = 255 / rgbScale; + if (logz && (this.zmin <= 0)) + this.zmin = this.zmax * 1e-5; - const int = ( r * rgbScale ) << 16 ^ - ( g * rgbScale ) << 8 ^ - ( b * rgbScale ) << 0; + this.createHistDrawAttributes(true); + return zmult; + } - return INT.toHexString( int ); + /** @summary Create 3D object for histogram bins + * @private */ + draw3DBins(o) { + if (this.isTH2Poly()) + drawTH2PolyLego(this); + else if (o.Contour) + drawBinsContour3D(this, true); + else if (o.Surf) + drawBinsSurf3D(this); + else if (o.Error) + drawBinsError3D(this); + else + drawBinsLego(this); + } - } -}; + /** @summary draw TH2 object in 3D mode */ + async draw3D(reason) { + this.mode3d = true; -const FORMATS = [ STRING, INT, ARRAY, OBJECT ]; + const fp = this.getFramePainter(), // who makes axis drawing + is_main = this.isMainPainter(), // is main histogram + o = this.getOptions(); -function getColorFormat( value ) { - return FORMATS.find( format => format.match( value ) ); -} + let pr = Promise.resolve(true), full_draw = true; -class ColorController extends Controller { + if (reason === 'resize') { + const res = is_main ? fp.resize3D() : false; + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); + } + } - constructor( parent, object, property, rgbScale ) { + if (full_draw) { + o.zmult = this.checkRangeFor3D(o); - super( parent, object, property, 'color' ); + if (is_main) + pr = crete3DFrame(this, TAxisPainter, o.Render3D); - this.$input = document.createElement( 'input' ); - this.$input.setAttribute( 'type', 'color' ); - this.$input.setAttribute( 'tabindex', -1 ); - this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + if (fp.mode3d) { + pr = pr.then(() => { + if (this.draw_content) + this.draw3DBins(o); + else if (o.Axis && o.Zscale) { + this.getContourLevels(true); + this.getHistPalette(); + } + fp.render3D(); + this.updateStatWebCanvas(); + fp.addKeysHandler(); + }); + } + } - this.$text = document.createElement( 'input' ); - this.$text.setAttribute( 'type', 'text' ); - this.$text.setAttribute( 'spellcheck', 'false' ); - this.$text.setAttribute( 'aria-labelledby', this.$name.id ); + // (re)draw palette by resize while canvas may change dimension + if (is_main) { + pr = pr.then(() => this.drawColorPalette(o.Zscale && ((o.Lego === 12) || (o.Lego === 14) || + (o.Surf === 11) || (o.Surf === 12)))); + } - this.$display = document.createElement( 'div' ); - this.$display.classList.add( 'display' ); + return pr.then(() => this.updateFunctions()) + .then(() => this.updateHistTitle()) + .then(() => this); + } - this.$display.appendChild( this.$input ); - this.$widget.appendChild( this.$display ); - this.$widget.appendChild( this.$text ); + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt, get_painter) { + const painter = new TH2Painter(null, histo); + painter.decodeOptions(opt); - this._format = getColorFormat( this.initialValue ); - this._rgbScale = rgbScale; + const o = painter.getOptions(); + if (painter.isTH2Poly()) + o.Lego = 12; + painter.scanContent(); - this._initialValueHexString = this.save(); - this._textFocused = false; + o.zmult = painter.checkRangeFor3D(o); - this.$input.addEventListener( 'input', () => { - this._setValueFromHexString( this.$input.value ); - } ); + const fp = new TFramePainter(null, null); + // return dummy frame painter as result + painter.getFramePainter = () => fp; - this.$input.addEventListener( 'blur', () => { - this._callOnFinishChange(); - } ); + return crete3DFrame(painter, TAxisPainter).then(() => { + if (painter.draw_content) + painter.draw3DBins(o); - this.$text.addEventListener( 'input', () => { - const tryParse = normalizeColorString( this.$text.value ); - if ( tryParse ) { - this._setValueFromHexString( tryParse ); - } - } ); + return get_painter ? painter : fp.create3DScene(-1, true); + }); + } - this.$text.addEventListener( 'focus', () => { - this._textFocused = true; - this.$text.select(); - } ); + /** @summary draw TH2 object */ + static async draw(dom, histo, opt) { + return THistPainter._drawHist(new TH2Painter(dom, histo), opt); + } - this.$text.addEventListener( 'blur', () => { - this._textFocused = false; - this.updateDisplay(); - this._callOnFinishChange(); - } ); +} // class TH2Painter - this.$disable = this.$text; +var TH2Painter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TH2Painter: TH2Painter +}); - this.updateDisplay(); +/** + * @summary Painter for TH3 classes + * @private + */ - } +class TH3Painter extends THistPainter { - reset() { - this._setValueFromHexString( this._initialValueHexString ); - return this; - } + #box_option; // actual box option - _setValueFromHexString( value ) { + /** @summary Returns number of histogram dimensions */ + getDimension() { return 3; } - if ( this._format.isPrimitive ) { + /** @summary Scan TH3 histogram content */ + scanContent(when_axis_changed) { + // no need to re-scan histogram while result does not depend from axis selection + if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) + return; - const newValue = this._format.fromHexString( value ); - this.setValue( newValue ); + const histo = this.getHisto(); - } else { + this.extractAxesProperties(3); - this._format.fromHexString( value, this.getValue(), this._rgbScale ); - this._callOnChange(); - this.updateDisplay(); + // global min/max, used at the moment in 3D drawing + this.gminbin = this.gmaxbin = histo.getBinContent(1, 1, 1); + this.gminposbin = null; - } + for (let i = 0; i < this.nbinsx; ++i) { + for (let j = 0; j < this.nbinsy; ++j) { + for (let k = 0; k < this.nbinsz; ++k) { + const bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + if (bin_content < this.gminbin) + this.gminbin = bin_content; + else if (bin_content > this.gmaxbin) + this.gmaxbin = bin_content; - } + if ((bin_content > 0) && ((this.gminposbin === null) || (this.gminposbin > bin_content))) + this.gminposbin = bin_content; + } + } + } - save() { - return this._format.toHexString( this.getValue(), this._rgbScale ); - } + if ((this.gminposbin === null) && (this.gmaxbin > 0)) + this.gminposbin = this.gmaxbin * 1e-4; - load( value ) { - this._setValueFromHexString( value ); - this._callOnFinishChange(); - return this; - } + this.draw_content = this.gmaxbin || this.gminbin; - updateDisplay() { - this.$input.value = this._format.toHexString( this.getValue(), this._rgbScale ); - if ( !this._textFocused ) { - this.$text.value = this.$input.value.substring( 1 ); - } - this.$display.style.backgroundColor = this.$input.value; - return this; - } + this.transferFunc = this.findFunction(clTF1, 'TransferFunction'); + this.transferFunc?.SetBit(BIT(9), true); // TF1::kNotDraw + } -} + /** @summary Count TH3 statistic */ + countStat(cond, count_skew) { + const histo = this.getHisto(), xaxis = histo.fXaxis, yaxis = histo.fYaxis, zaxis = histo.fZaxis, + i1 = this.getSelectIndex('x', 'left'), + i2 = this.getSelectIndex('x', 'right'), + j1 = this.getSelectIndex('y', 'left'), + j2 = this.getSelectIndex('y', 'right'), + k1 = this.getSelectIndex('z', 'left'), + k2 = this.getSelectIndex('z', 'right'), + fp = this.getFramePainter(), + res = { + name: histo.fName, entries: 0, eff_entries: 0, integral: 0, + meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0, + skewx: 0, skewy: 0, skewz: 0, skewd: 0, kurtx: 0, kurty: 0, kurtz: 0, kurtd: 0 + }, + has_counted_stat = (Math.abs(histo.fTsumw) > 1e-300) && !fp.isAxisZoomed('x') && !fp.isAxisZoomed('y') && !fp.isAxisZoomed('z'); + let xi, yi, zi, xx, xside, yy, yside, zz, zside, cont, + stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, + stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0; -class FunctionController extends Controller { + if (!isFunc(cond)) + cond = null; - constructor( parent, object, property ) { + for (xi = 0; xi < this.nbinsx + 2; ++xi) { + xx = xaxis.GetBinCoord(xi - 0.5); + xside = (xi < i1) ? 0 : (xi > i2 ? 2 : 1); - super( parent, object, property, 'function' ); + for (yi = 0; yi < this.nbinsy + 2; ++yi) { + yy = yaxis.GetBinCoord(yi - 0.5); + yside = (yi < j1) ? 0 : (yi > j2 ? 2 : 1); - // Buttons are the only case where widget contains name - this.$button = document.createElement( 'button' ); - this.$button.appendChild( this.$name ); - this.$widget.appendChild( this.$button ); + for (zi = 0; zi < this.nbinsz + 2; ++zi) { + zz = zaxis.GetBinCoord(zi - 0.5); + zside = (zi < k1) ? 0 : (zi > k2 ? 2 : 1); - this.$button.addEventListener( 'click', e => { - e.preventDefault(); - this.getValue().call( this.object ); - this._callOnChange(); - } ); + if (cond && !cond(xx, yy, zz)) + continue; - // enables :active pseudo class on mobile - this.$button.addEventListener( 'touchstart', () => {}, { passive: true } ); + cont = histo.getBinContent(xi, yi, zi); + res.entries += cont; - this.$disable = this.$button; + if (!has_counted_stat && (xside === 1) && (yside === 1) && (zside === 1)) { + stat_sum0 += cont; + stat_sumw2 += cont * cont; + stat_sumx1 += xx * cont; + stat_sumy1 += yy * cont; + stat_sumz1 += zz * cont; + stat_sumx2 += xx ** 2 * cont; + stat_sumy2 += yy ** 2 * cont; + stat_sumz2 += zz ** 2 * cont; + } + } + } + } - } + if (has_counted_stat) { + stat_sum0 = histo.fTsumw; + stat_sumw2 = histo.fTsumw2; + stat_sumx1 = histo.fTsumwx; + stat_sumx2 = histo.fTsumwx2; + stat_sumy1 = histo.fTsumwy; + stat_sumy2 = histo.fTsumwy2; + stat_sumz1 = histo.fTsumwz; + stat_sumz2 = histo.fTsumwz2; + } -} + if (Math.abs(stat_sum0) > 1e-300) { + res.meanx = stat_sumx1 / stat_sum0; + res.meany = stat_sumy1 / stat_sum0; + res.meanz = stat_sumz1 / stat_sum0; + res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx * res.meanx)); + res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany * res.meany)); + res.rmsz = Math.sqrt(Math.abs(stat_sumz2 / stat_sum0 - res.meanz * res.meanz)); + } -class NumberController extends Controller { + res.integral = stat_sum0; - constructor( parent, object, property, min, max, step ) { + if (histo.fEntries > 0) + res.entries = histo.fEntries; - super( parent, object, property, 'number' ); + res.eff_entries = stat_sumw2 ? stat_sum0 * stat_sum0 / stat_sumw2 : Math.abs(stat_sum0); - this._initInput(); + if (count_skew && !this.isTH2Poly()) { + let sumx3 = 0, sumy3 = 0, sumz3 = 0, sumx4 = 0, sumy4 = 0, sumz4 = 0, np = 0; + for (xi = i1; xi < i2; ++xi) { + xx = xaxis.GetBinCoord(xi + 0.5); + for (yi = j1; yi < j2; ++yi) { + yy = yaxis.GetBinCoord(yi + 0.5); + for (zi = k1; zi < k2; ++zi) { + zz = zaxis.GetBinCoord(zi + 0.5); + if (cond && !cond(xx, yy, zz)) + continue; + const w = histo.getBinContent(xi + 1, yi + 1, zi + 1); + np += w; + sumx3 += w * Math.pow(xx - res.meanx, 3); + sumy3 += w * Math.pow(yy - res.meany, 3); + sumz3 += w * Math.pow(zz - res.meany, 3); + sumx4 += w * Math.pow(xx - res.meanx, 4); + sumy4 += w * Math.pow(yy - res.meany, 4); + sumz4 += w * Math.pow(yy - res.meany, 4); + } + } + } - this.min( min ); - this.max( max ); + const stddev3x = Math.pow(res.rmsx, 3), + stddev3y = Math.pow(res.rmsy, 3), + stddev3z = Math.pow(res.rmsz, 3), + stddev4x = Math.pow(res.rmsx, 4), + stddev4y = Math.pow(res.rmsy, 4), + stddev4z = Math.pow(res.rmsz, 4); - const stepExplicit = step !== undefined; - this.step( stepExplicit ? step : this._getImplicitStep(), stepExplicit ); + if (np * stddev3x) + res.skewx = sumx3 / (np * stddev3x); + if (np * stddev3y) + res.skewy = sumy3 / (np * stddev3y); + if (np * stddev3z) + res.skewz = sumz3 / (np * stddev3z); + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; - this.updateDisplay(); + if (np * stddev4x) + res.kurtx = sumx4 / (np * stddev4x) - 3; + if (np * stddev4y) + res.kurty = sumy4 / (np * stddev4y) - 3; + if (np * stddev4z) + res.kurtz = sumz4 / (np * stddev4z) - 3; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; + } - } + return res; + } - decimals( decimals ) { - this._decimals = decimals; - this.updateDisplay(); - return this; - } + /** @summary Fill TH3 statistic in stat box */ + fillStatistic(stat, dostat, dofit) { + // no need to refill statistic if histogram is dummy + if (this.isIgnoreStatsFill()) + return false; - min( min ) { - this._min = min; - this._onUpdateMinMax(); - return this; - } + if (dostat === 1) + dostat = 1111; - max( max ) { - this._max = max; - this._onUpdateMinMax(); - return this; - } + const print_name = dostat % 10, + print_entries = Math.floor(dostat / 10) % 10, + print_mean = Math.floor(dostat / 100) % 10, + print_rms = Math.floor(dostat / 1000) % 10, + print_integral = Math.floor(dostat / 1000000) % 10, + print_skew = Math.floor(dostat / 10000000) % 10, + print_kurt = Math.floor(dostat / 100000000) % 10, + data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); - step( step, explicit = true ) { - this._step = step; - this._stepExplicit = explicit; - return this; - } + stat.clearPave(); - updateDisplay() { + if (print_name > 0) + stat.addText(data.name); - const value = this.getValue(); + if (print_entries > 0) + stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - if ( this._hasSlider ) { + if (print_mean > 0) { + stat.addText('Mean x = ' + stat.format(data.meanx)); + stat.addText('Mean y = ' + stat.format(data.meany)); + stat.addText('Mean z = ' + stat.format(data.meanz)); + } - let percent = ( value - this._min ) / ( this._max - this._min ); - percent = Math.max( 0, Math.min( percent, 1 ) ); + if (print_rms > 0) { + stat.addText('Std Dev x = ' + stat.format(data.rmsx)); + stat.addText('Std Dev y = ' + stat.format(data.rmsy)); + stat.addText('Std Dev z = ' + stat.format(data.rmsz)); + } - this.$fill.style.width = percent * 100 + '%'; + if (print_integral > 0) + stat.addText('Integral = ' + stat.format(data.integral, 'entries')); - } + if (print_skew === 2) { + stat.addText(`Skewness x = ${stat.format(data.skewx)} #pm ${stat.format(data.skewd)}`); + stat.addText(`Skewness y = ${stat.format(data.skewy)} #pm ${stat.format(data.skewd)}`); + stat.addText(`Skewness z = ${stat.format(data.skewz)} #pm ${stat.format(data.skewd)}`); + } else if (print_skew > 0) { + stat.addText(`Skewness x = ${stat.format(data.skewx)}`); + stat.addText(`Skewness y = ${stat.format(data.skewy)}`); + stat.addText(`Skewness z = ${stat.format(data.skewz)}`); + } - if ( !this._inputFocused ) { - this.$input.value = this._decimals === undefined ? value : value.toFixed( this._decimals ); - } + if (print_kurt === 2) { + stat.addText(`Kurtosis x = ${stat.format(data.kurtx)} #pm ${stat.format(data.kurtd)}`); + stat.addText(`Kurtosis y = ${stat.format(data.kurty)} #pm ${stat.format(data.kurtd)}`); + stat.addText(`Kurtosis z = ${stat.format(data.kurtz)} #pm ${stat.format(data.kurtd)}`); + } else if (print_kurt > 0) { + stat.addText(`Kurtosis x = ${stat.format(data.kurtx)}`); + stat.addText(`Kurtosis y = ${stat.format(data.kurty)}`); + stat.addText(`Kurtosis z = ${stat.format(data.kurtz)}`); + } - return this; + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF3), dofit, 3); - } + return true; + } - _initInput() { + /** @summary Provide text information (tooltips) for histogram bin */ + getBinTooltips(ix, iy, iz) { + const lines = [], histo = this.getHisto(); - this.$input = document.createElement( 'input' ); - this.$input.setAttribute( 'type', 'text' ); - this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + lines.push(this.getObjectHint(), + `x = ${this.getAxisBinTip('x', histo.fXaxis, ix)} xbin=${ix + 1}`, + `y = ${this.getAxisBinTip('y', histo.fYaxis, iy)} ybin=${iy + 1}`, + `z = ${this.getAxisBinTip('z', histo.fZaxis, iz)} zbin=${iz + 1}`); - // On touch devices only, use input[type=number] to force a numeric keyboard. - // Ideally we could use one input type everywhere, but [type=number] has quirks - // on desktop, and [inputmode=decimal] has quirks on iOS. - // See https://github.com/georgealways/lil-gui/pull/16 + const binz = histo.getBinContent(ix + 1, iy + 1, iz + 1); + if (binz === Math.round(binz)) + lines.push(`entries = ${binz}`); + else + lines.push(`entries = ${floatToString(binz, gStyle.fStatFormat)}`); - const isTouch = window.matchMedia( '(pointer: coarse)' ).matches; + if (this.matchObjectType(clTProfile3D)) { + const errz = histo.getBinError(histo.getBin(ix + 1, iy + 1, iz + 1)); + lines.push('error = ' + ((errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat))); + } - if ( isTouch ) { - this.$input.setAttribute( 'type', 'number' ); - this.$input.setAttribute( 'step', 'any' ); - } + return lines; + } - this.$widget.appendChild( this.$input ); + /** @summary draw 3D histogram as scatter plot + * @desc If there are too many points, box will be displayed + * @return {Promise|false} either Promise or just false that drawing cannot be performed */ + draw3DScatter() { + const histo = this.getObject(), + fp = this.getFramePainter(), + i1 = this.getSelectIndex('x', 'left', 0.5), + i2 = this.getSelectIndex('x', 'right', 0), + j1 = this.getSelectIndex('y', 'left', 0.5), + j2 = this.getSelectIndex('y', 'right', 0), + k1 = this.getSelectIndex('z', 'left', 0.5), + k2 = this.getSelectIndex('z', 'right', 0); + let i, j, k, bin_content; - this.$disable = this.$input; + if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) + return Promise.resolve(true); - const onInput = () => { + // scale down factor if too large values + const coef = (this.gmaxbin > 1000) ? 1000 / this.gmaxbin : 1, + content_lmt = Math.max(0, this.gminbin); + let numpixels = 0, sumz = 0; - let value = parseFloat( this.$input.value ); + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + for (k = k1; k < k2; ++k) { + bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + sumz += bin_content; + if (bin_content > content_lmt) + numpixels += Math.round(bin_content * coef); + } + } + } - if ( isNaN( value ) ) return; + // too many pixels - use box drawing + if (numpixels > (fp.webgl ? 100000 : 30000)) + return false; - if ( this._stepExplicit ) { - value = this._snap( value ); - } + const pnts = new PointsCreator(numpixels, fp.webgl, fp.size_x3d / 200), + bins = new Int32Array(numpixels), + rnd = new TRandom(sumz); + let nbin = 0; - this.setValue( this._clamp( value ) ); + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + for (k = k1; k < k2; ++k) { + bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + if (bin_content <= content_lmt) + continue; + const num = Math.round(bin_content * coef); - }; + for (let n = 0; n < num; ++n) { + const binx = histo.fXaxis.GetBinCoord(i + rnd.random()), + biny = histo.fYaxis.GetBinCoord(j + rnd.random()), + binz = histo.fZaxis.GetBinCoord(k + rnd.random()); - // Keys & mouse wheel - // --------------------------------------------------------------------- + // remember bin index for tooltip + bins[nbin++] = histo.getBin(i + 1, j + 1, k + 1); - const increment = delta => { + pnts.addPoint(fp.grx(binx), fp.gry(biny), fp.grz(binz)); + } + } + } + } - const value = parseFloat( this.$input.value ); + return pnts.createPoints({ color: this.getColor(histo.fMarkerColor) }).then(mesh => { + fp.add3DMesh(mesh); - if ( isNaN( value ) ) return; + mesh.bins = bins; + mesh.tip_painter = this; + mesh.tip_color = histo.fMarkerColor === 3 ? 0xFF0000 : 0x00FF00; - this._snapClampSetValue( value + delta ); + mesh.tooltip = function(intersect) { + const indx = Math.floor(intersect.index / this.nvertex); + if ((indx < 0) || (indx >= this.bins.length)) + return null; - // Force the input to updateDisplay when it's focused - this.$input.value = this.getValue(); + const p = this.tip_painter, + thisto = p.getHisto(), + tip = p.get3DToolTip(this.bins[indx]); - }; + tip.x1 = fp.grx(thisto.fXaxis.GetBinLowEdge(tip.ix)); + tip.x2 = fp.grx(thisto.fXaxis.GetBinLowEdge(tip.ix + 1)); + tip.y1 = fp.gry(thisto.fYaxis.GetBinLowEdge(tip.iy)); + tip.y2 = fp.gry(thisto.fYaxis.GetBinLowEdge(tip.iy + 1)); + tip.z1 = fp.grz(thisto.fZaxis.GetBinLowEdge(tip.iz)); + tip.z2 = fp.grz(thisto.fZaxis.GetBinLowEdge(tip.iz + 1)); + tip.color = this.tip_color; + tip.opacity = 0.3; - const onKeyDown = e => { - // Using `e.key` instead of `e.code` also catches NumpadEnter - if ( e.key === 'Enter' ) { - this.$input.blur(); - } - if ( e.code === 'ArrowUp' ) { - e.preventDefault(); - increment( this._step * this._arrowKeyMultiplier( e ) ); - } - if ( e.code === 'ArrowDown' ) { - e.preventDefault(); - increment( this._step * this._arrowKeyMultiplier( e ) * -1 ); - } - }; + return tip; + }; - const onWheel = e => { - if ( this._inputFocused ) { - e.preventDefault(); - increment( this._step * this._normalizeMouseWheel( e ) ); - } - }; + return true; + }); + } - // Vertical drag - // --------------------------------------------------------------------- + /** @summary Drawing of 3D histogram */ + async draw3DBins() { + if (!this.draw_content) + return false; - let testingForVerticalDrag = false, - initClientX, - initClientY, - prevClientY, - initValue, - dragDelta; + const o = this.getOptions(); - // Once the mouse is dragged more than DRAG_THRESH px on any axis, we decide - // on the user's intent: horizontal means highlight, vertical means drag. - const DRAG_THRESH = 5; + let box_option = o.BoxStyle; - const onMouseDown = e => { + if (!box_option && o.Scat) { + const promise = this.draw3DScatter(); + if (promise !== false) + return promise; + box_option = 12; // fall back to box2 draw option + } else if (!box_option && !o.GLBox && !o.GLColor && !o.Lego) + box_option = 12; // default draw option - initClientX = e.clientX; - initClientY = prevClientY = e.clientY; - testingForVerticalDrag = true; + const histo = this.getHisto(), + fp = this.getFramePainter(); - initValue = this.getValue(); - dragDelta = 0; + let use_lambert = false, + use_helper = false, use_colors = false, use_opacity = 1, exclude_content = -1, + logv = this.getPadPainter()?.getRootPad()?.fLogv, + use_scale = true, scale_offset = 0, + fillcolor = this.getColor(histo.fFillColor), + tipscale = 0.5, single_bin_geom; - window.addEventListener( 'mousemove', onMouseMove ); - window.addEventListener( 'mouseup', onMouseUp ); + if (!box_option && o.Lego) + box_option = (o.Lego === 1) ? 10 : o.Lego; - }; + if ((o.GLBox === 11) || (o.GLBox === 12)) { + tipscale = 0.4; + use_lambert = true; + if (o.GLBox === 12) + use_colors = true; - const onMouseMove = e => { + single_bin_geom = new THREE.SphereGeometry(0.5, fp.webgl ? 16 : 8, fp.webgl ? 12 : 6); + single_bin_geom.applyMatrix4(new THREE.Matrix4().makeRotationX(Math.PI / 2)); + single_bin_geom.computeVertexNormals(); + } else { + const indicies = Box3D.Indexes, + normals = Box3D.Normals, + vertices = Box3D.Vertices, + buffer_size = indicies.length * 3, + single_bin_verts = new Float32Array(buffer_size), + single_bin_norms = new Float32Array(buffer_size); - if ( testingForVerticalDrag ) { + for (let k = 0, nn = -3; k < indicies.length; ++k) { + const vert = vertices[indicies[k]]; + single_bin_verts[k * 3] = vert.x - 0.5; + single_bin_verts[k * 3 + 1] = vert.y - 0.5; + single_bin_verts[k * 3 + 2] = vert.z - 0.5; - const dx = e.clientX - initClientX; - const dy = e.clientY - initClientY; + if (k % 6 === 0) + nn += 3; + single_bin_norms[k * 3] = normals[nn]; + single_bin_norms[k * 3 + 1] = normals[nn + 1]; + single_bin_norms[k * 3 + 2] = normals[nn + 2]; + } + use_helper = true; - if ( Math.abs( dy ) > DRAG_THRESH ) { + if (box_option === 12) + use_colors = true; + else if (box_option === 13) { + use_colors = true; + use_helper = false; + } else if (o.GLColor) { + use_colors = true; + use_opacity = 0.5; + use_scale = false; + use_helper = false; + exclude_content = 0; + use_lambert = true; + } - e.preventDefault(); - this.$input.blur(); - testingForVerticalDrag = false; - this._setDraggingStyle( true, 'vertical' ); + single_bin_geom = new THREE.BufferGeometry(); + single_bin_geom.setAttribute('position', new THREE.BufferAttribute(single_bin_verts, 3)); + single_bin_geom.setAttribute('normal', new THREE.BufferAttribute(single_bin_norms, 3)); + } - } else if ( Math.abs( dx ) > DRAG_THRESH ) { + this.#box_option = box_option; - onMouseUp(); + if (use_scale && logv) { + if (this.gminposbin && (this.gmaxbin > this.gminposbin)) { + scale_offset = Math.log(this.gminposbin) - 0.1; + use_scale = 1 / (Math.log(this.gmaxbin) - scale_offset); + } else { + logv = 0; + use_scale = 1; + } + } else if (use_scale) + use_scale = (this.gminbin || this.gmaxbin) ? 1 / Math.max(Math.abs(this.gminbin), Math.abs(this.gmaxbin)) : 1; - } + const get_bin_weight = content => { + if ((exclude_content >= 0) && (content < exclude_content)) + return 0; + if (!use_scale) + return 1; + if (logv) { + if (content <= 0) + return 0; + content = Math.log(content) - scale_offset; + } + return Math.pow(Math.abs(content * use_scale), 0.3333); + }; + // eslint-disable-next-line one-var + const i1 = this.getSelectIndex('x', 'left', 0.5), + i2 = this.getSelectIndex('x', 'right', 0), + j1 = this.getSelectIndex('y', 'left', 0.5), + j2 = this.getSelectIndex('y', 'right', 0), + k1 = this.getSelectIndex('z', 'left', 0.5), + k2 = this.getSelectIndex('z', 'right', 0); - } + if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) + return false; - // This isn't an else so that the first move counts towards dragDelta - if ( !testingForVerticalDrag ) { + const cntr = use_colors ? this.getContour() : null, + palette = use_colors ? this.getHistPalette() : null, + bins_matrixes = [], bins_colors = [], bins_ids = [], negative_matrixes = [], bin_opacities = [], + transfer = (this.transferFunc && proivdeEvalPar(this.transferFunc, true)) ? this.transferFunc : null; - const dy = e.clientY - prevClientY; + for (let i = i1; i < i2; ++i) { + const grx1 = fp.grx(histo.fXaxis.GetBinLowEdge(i + 1)), + grx2 = fp.grx(histo.fXaxis.GetBinLowEdge(i + 2)); + for (let j = j1; j < j2; ++j) { + const gry1 = fp.gry(histo.fYaxis.GetBinLowEdge(j + 1)), + gry2 = fp.gry(histo.fYaxis.GetBinLowEdge(j + 2)); + for (let k = k1; k < k2; ++k) { + const bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + if (!o.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) + continue; - dragDelta -= dy * this._step * this._arrowKeyMultiplier( e ); + const wei = get_bin_weight(bin_content); + if (wei < 1e-3) + continue; // do not show very small bins - // Clamp dragDelta so we don't have 'dead space' after dragging past bounds. - // We're okay with the fact that bounds can be undefined here. - if ( initValue + dragDelta > this._max ) { - dragDelta = this._max - initValue; - } else if ( initValue + dragDelta < this._min ) { - dragDelta = this._min - initValue; - } + if (use_colors) { + const colindx = cntr.getPaletteIndex(palette, bin_content); + if (colindx === null) + continue; + bins_colors.push(palette.getColor(colindx)); + if (transfer) { + const op = getTF1Value(transfer, bin_content, false) * 3; + bin_opacities.push((!op || op < 0) ? 0 : (op > 1 ? 1 : op)); + } + } - this._snapClampSetValue( initValue + dragDelta ); + const grz1 = fp.grz(histo.fZaxis.GetBinLowEdge(k + 1)), + grz2 = fp.grz(histo.fZaxis.GetBinLowEdge(k + 2)); - } + // remember bin index for tooltip + bins_ids.push(histo.getBin(i + 1, j + 1, k + 1)); + + const bin_matrix = new THREE.Matrix4(); + bin_matrix.scale(new THREE.Vector3((grx2 - grx1) * wei, (gry2 - gry1) * wei, (grz2 - grz1) * wei)); + bin_matrix.setPosition((grx2 + grx1) / 2, (gry2 + gry1) / 2, (grz2 + grz1) / 2); + bins_matrixes.push(bin_matrix); + if (bin_content < 0) + negative_matrixes.push(bin_matrix); + } + } + } - prevClientY = e.clientY; + function _getBinTooltip(intersect) { + let binid = this.binid; - }; + if (binid === undefined) { + if ((intersect.instanceId === undefined) || (intersect.instanceId >= this.bins.length)) + return; + binid = this.bins[intersect.instanceId]; + } + + const p = this.tip_painter, + thisto = p.getHisto(), + tip = p.get3DToolTip(binid), + grx1 = fp.grx(thisto.fXaxis.GetBinCoord(tip.ix - 1)), + grx2 = fp.grx(thisto.fXaxis.GetBinCoord(tip.ix)), + gry1 = fp.gry(thisto.fYaxis.GetBinCoord(tip.iy - 1)), + gry2 = fp.gry(thisto.fYaxis.GetBinCoord(tip.iy)), + grz1 = fp.grz(thisto.fZaxis.GetBinCoord(tip.iz - 1)), + grz2 = fp.grz(thisto.fZaxis.GetBinCoord(tip.iz)), + wei2 = this.get_weight(tip.value) * this.tipscale; + + tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; + tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; + tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; + tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; + tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; + tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; + tip.color = this.tip_color; - const onMouseUp = () => { - this._setDraggingStyle( false, 'vertical' ); - this._callOnFinishChange(); - window.removeEventListener( 'mousemove', onMouseMove ); - window.removeEventListener( 'mouseup', onMouseUp ); - }; + return tip; + } - // Focus state & onFinishChange - // --------------------------------------------------------------------- + if (use_colors && (transfer || (use_opacity !== 1))) { + // create individual meshes for each bin + for (let n = 0; n < bins_matrixes.length; ++n) { + const opacity = transfer ? bin_opacities[n] : use_opacity, + color = new THREE.Color(bins_colors[n]), + material = use_lambert ? new THREE.MeshLambertMaterial({ color, opacity, transparent: opacity < 1, vertexColors: false }) + : new THREE.MeshBasicMaterial({ color, opacity, transparent: opacity < 1, vertexColors: false }), + bin_mesh = new THREE.Mesh(single_bin_geom, material); - const onFocus = () => { - this._inputFocused = true; - }; + bin_mesh.applyMatrix4(bins_matrixes[n]); - const onBlur = () => { - this._inputFocused = false; - this.updateDisplay(); - this._callOnFinishChange(); - }; + bin_mesh.tip_painter = this; + bin_mesh.binid = bins_ids[n]; + bin_mesh.tipscale = tipscale; + bin_mesh.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; + bin_mesh.get_weight = get_bin_weight; + bin_mesh.tooltip = _getBinTooltip; - this.$input.addEventListener( 'input', onInput ); - this.$input.addEventListener( 'keydown', onKeyDown ); - this.$input.addEventListener( 'wheel', onWheel, { passive: false } ); - this.$input.addEventListener( 'mousedown', onMouseDown ); - this.$input.addEventListener( 'focus', onFocus ); - this.$input.addEventListener( 'blur', onBlur ); + fp.add3DMesh(bin_mesh); + } + } else { + if (use_colors) + fillcolor = new THREE.Color(1, 1, 1); - } + const material = use_lambert ? new THREE.MeshLambertMaterial({ color: fillcolor, vertexColors: false }) + : new THREE.MeshBasicMaterial({ color: fillcolor, vertexColors: false }), + all_bins_mesh = new THREE.InstancedMesh(single_bin_geom, material, bins_matrixes.length); - _initSlider() { + for (let n = 0; n < bins_matrixes.length; ++n) { + all_bins_mesh.setMatrixAt(n, bins_matrixes[n]); + if (use_colors) + all_bins_mesh.setColorAt(n, new THREE.Color(bins_colors[n])); + } - this._hasSlider = true; + all_bins_mesh.tip_painter = this; + all_bins_mesh.bins = bins_ids; + all_bins_mesh.tipscale = tipscale; + all_bins_mesh.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; + all_bins_mesh.get_weight = get_bin_weight; + all_bins_mesh.tooltip = _getBinTooltip; - // Build DOM - // --------------------------------------------------------------------- + fp.add3DMesh(all_bins_mesh); + } - this.$slider = document.createElement( 'div' ); - this.$slider.classList.add( 'slider' ); + if (use_helper) { + const helper_material = new THREE.LineBasicMaterial({ color: this.getColor(histo.fLineColor) }); + function addLines(segments, matrixes) { + if (!matrixes) + return; + const positions = new Float32Array(matrixes.length * segments.length * 3); + for (let i = 0, vvv = 0; i < matrixes.length; ++i) { + const m = matrixes[i].elements; + for (let n = 0; n < segments.length; ++n, vvv += 3) { + const vert = Box3D.Vertices[segments[n]]; + positions[vvv] = m[12] + (vert.x - 0.5) * m[0]; + positions[vvv + 1] = m[13] + (vert.y - 0.5) * m[5]; + positions[vvv + 2] = m[14] + (vert.z - 0.5) * m[10]; + } + } + fp.add3DMesh(createLineSegments(positions, helper_material)); + } - this.$fill = document.createElement( 'div' ); - this.$fill.classList.add( 'fill' ); + addLines(Box3D.Segments, bins_matrixes); + addLines(Box3D.Crosses, negative_matrixes); + } - this.$slider.appendChild( this.$fill ); - this.$widget.insertBefore( this.$slider, this.$input ); + return true; + } - this.domElement.classList.add( 'hasSlider' ); - // Map clientX to value - // --------------------------------------------------------------------- + /** @summary Redraw TH3 histogram */ + async redraw(reason) { + const fp = this.getFramePainter(), // who makes axis and 3D drawing + o = this.getOptions(); - const map = ( v, a, b, c, d ) => { - return ( v - a ) / ( b - a ) * ( d - c ) + c; - }; + let pr = Promise.resolve(true), full_draw = true; - const setValueFromX = clientX => { - const rect = this.$slider.getBoundingClientRect(); - let value = map( clientX, rect.left, rect.right, this._min, this._max ); - this._snapClampSetValue( value ); - }; + if (reason === 'resize') { + const res = fp.resize3D(); + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); + } + } - // Mouse drag - // --------------------------------------------------------------------- + if (full_draw) { + pr = crete3DFrame(this, TAxisPainter, o.Render3D) + .then(() => this.draw3DBins()) + .then(() => { + fp.render3D(); + this.updateStatWebCanvas(); + fp.addKeysHandler(); + }); + } - const mouseDown = e => { - this._setDraggingStyle( true ); - setValueFromX( e.clientX ); - window.addEventListener( 'mousemove', mouseMove ); - window.addEventListener( 'mouseup', mouseUp ); - }; + if (this.isMainPainter()) + pr = pr.then(() => this.drawColorPalette(o.Zscale && (this.#box_option === 12 || this.#box_option === 13 || o.GLBox === 12))); - const mouseMove = e => { - setValueFromX( e.clientX ); - }; + return pr.then(() => this.updateFunctions()) + .then(() => this.updateHistTitle()) + .then(() => this); + } - const mouseUp = () => { - this._callOnFinishChange(); - this._setDraggingStyle( false ); - window.removeEventListener( 'mousemove', mouseMove ); - window.removeEventListener( 'mouseup', mouseUp ); - }; + /** @summary Fill pad toolbar with TH3-related functions */ + fillToolbar() { + const pp = this.getPadPainter(); + if (!pp) + return; - // Touch drag - // --------------------------------------------------------------------- + pp.addPadButton('auto_zoom', 'Unzoom all axes', 'ToggleZoom', 'Ctrl *'); + if (this.draw_content) + pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); + pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); + pp.showPadButtons(); + } - let testingForScroll = false, prevClientX, prevClientY; + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + let obj = this.getHisto(); + if (obj) + obj = obj[`f${axis.toUpperCase()}axis`]; + return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); + } - const beginTouchDrag = e => { - e.preventDefault(); - this._setDraggingStyle( true ); - setValueFromX( e.touches[ 0 ].clientX ); - testingForScroll = false; - }; + /** @summary Perform automatic zoom inside non-zero region of histogram */ + autoZoom() { + const i1 = this.getSelectIndex('x', 'left'), + i2 = this.getSelectIndex('x', 'right'), + j1 = this.getSelectIndex('y', 'left'), + j2 = this.getSelectIndex('y', 'right'), + k1 = this.getSelectIndex('z', 'left'), + k2 = this.getSelectIndex('z', 'right'), + histo = this.getObject(); + let i, j, k; - const onTouchStart = e => { + if ((i1 === i2) || (j1 === j2) || (k1 === k2)) + return; - if ( e.touches.length > 1 ) return; + // first find minimum + let min = histo.getBinContent(i1 + 1, j1 + 1, k1 + 1); + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + for (k = k1; k < k2; ++k) + min = Math.min(min, histo.getBinContent(i + 1, j + 1, k + 1)); + } + } + if (min > 0) + return; // if all points positive, no chance for auto-scale - // If we're in a scrollable container, we should wait for the first - // touchmove to see if the user is trying to slide or scroll. - if ( this._hasScrollBar ) { + let ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1; - prevClientX = e.touches[ 0 ].clientX; - prevClientY = e.touches[ 0 ].clientY; - testingForScroll = true; + for (i = i1; i < i2; ++i) { + for (j = j1; j < j2; ++j) { + for (k = k1; k < k2; ++k) { + if (histo.getBinContent(i + 1, j + 1, k + 1) > min) { + if (i < ileft) + ileft = i; + if (i >= iright) + iright = i + 1; + if (j < jleft) + jleft = j; + if (j >= jright) + jright = j + 1; + if (k < kleft) + kleft = k; + if (k >= kright) + kright = k + 1; + } + } + } + } - } else { + let xmin, xmax, ymin, ymax, zmin, zmax, isany = false; - // Otherwise, we can set the value straight away on touchstart. - beginTouchDrag( e ); + if ((ileft === iright - 1) && (ileft > i1 + 1) && (iright < i2 - 1)) { + ileft--; + iright++; + } + if ((jleft === jright - 1) && (jleft > j1 + 1) && (jright < j2 - 1)) { + jleft--; + jright++; + } + if ((kleft === kright - 1) && (kleft > k1 + 1) && (kright < k2 - 1)) { + kleft--; + kright++; + } - } + if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { + xmin = histo.fXaxis.GetBinLowEdge(ileft + 1); + xmax = histo.fXaxis.GetBinLowEdge(iright + 1); + isany = true; + } - window.addEventListener( 'touchmove', onTouchMove, { passive: false } ); - window.addEventListener( 'touchend', onTouchEnd ); + if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { + ymin = histo.fYaxis.GetBinLowEdge(jleft + 1); + ymax = histo.fYaxis.GetBinLowEdge(jright + 1); + isany = true; + } - }; + if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) { + zmin = histo.fZaxis.GetBinLowEdge(kleft + 1); + zmax = histo.fZaxis.GetBinLowEdge(kright + 1); + isany = true; + } - const onTouchMove = e => { + if (isany) + return this.getFramePainter().zoom(xmin, xmax, ymin, ymax, zmin, zmax); + } - if ( testingForScroll ) { + /** @summary Fill histogram context menu */ + fillHistContextMenu(menu) { + const opts = this.getSupportedDrawOptions(); - const dx = e.touches[ 0 ].clientX - prevClientX; - const dy = e.touches[ 0 ].clientY - prevClientY; + menu.addDrawMenu('Draw with', opts, arg => { + if (arg.indexOf(kInspect) === 0) + return this.showInspector(arg); - if ( Math.abs( dx ) > Math.abs( dy ) ) { + this.decodeOptions(arg); - // We moved horizontally, set the value and stop checking. - beginTouchDrag( e ); + this.interactiveRedraw(true, 'drawopt'); + }); + } - } else { + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt) { + const painter = new TH3Painter(null, histo); + painter.mode3d = true; + painter.decodeOptions(opt); + painter.scanContent(); - // This was, in fact, an attempt to scroll. Abort. - window.removeEventListener( 'touchmove', onTouchMove ); - window.removeEventListener( 'touchend', onTouchEnd ); + const fp = new TFramePainter(null, null); + painter.getFramePainter = () => fp; - } + return crete3DFrame(painter, TAxisPainter) + .then(() => painter.draw3DBins()) + .then(() => fp.create3DScene(-1, true)); + } - } else { + /** @summary draw TH3 object */ + static async draw(dom, histo, opt) { + const painter = new TH3Painter(dom, histo); + painter.mode3d = true; - e.preventDefault(); - setValueFromX( e.touches[ 0 ].clientX ); + return ensureTCanvas(painter, '3d').then(() => { + painter.setAsMainPainter(); + painter.decodeOptions(opt); + painter.checkPadRange(); + painter.scanContent(); + painter.createStat(); // only when required + return painter.redraw(); + }) + .then(() => painter.drawFunctions()) + .then(() => { + painter.fillToolbar(); + return painter; + }); + } - } +} // class TH3Painter - }; +var TH3Painter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TH3Painter: TH3Painter +}); - const onTouchEnd = () => { - this._callOnFinishChange(); - this._setDraggingStyle( false ); - window.removeEventListener( 'touchmove', onTouchMove ); - window.removeEventListener( 'touchend', onTouchEnd ); - }; +const kNotEditable = BIT(18), // bit set if graph is non editable + clTGraphErrors = 'TGraphErrors', + clTGraphAsymmErrors = 'TGraphAsymmErrors', + clTGraphBentErrors = 'TGraphBentErrors', + clTGraphMultiErrors = 'TGraphMultiErrors'; - // Mouse wheel - // --------------------------------------------------------------------- +/** + * @summary Painter for TGraph object. + * + * @private + */ - // We have to use a debounced function to call onFinishChange because - // there's no way to tell when the user is "done" mouse-wheeling. - const callOnFinishChange = this._callOnFinishChange.bind( this ); - const WHEEL_DEBOUNCE_TIME = 400; - let wheelFinishChangeTimeout; +let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { - const onWheel = e => { + #bins; // extracted graph bins + #barwidth; // width of each bar + #baroffset; // offset of each bar + #redraw_hist; // indicate that histogram need to be redrawn + #auto_exec; // can be reused when sending option back to server + #funcs_handler; // special instance for functions drawing + #frame_layer; // frame layer used for drawing + #cutg; // is cutg object + #cutg_lastsame; // indicate that last point is same as first + #own_histogram; // if histogram created by TGraphPainter + #marker_size; // used marker size + #move_binindx; // index of moving bin + #move_funcs; // moving functions + #move_bin; // moving bin + #move_x0; // initial x position + #move_y0; // initial y position + #pos_dx; // accumulated x change + #pos_dy; // accumulated y change + #has_errors; // if has errors + #is_bent; // if graph has bent errors + #draw_kind; // way how graph is drawn - // ignore vertical wheels if there's a scrollbar - const isVertical = Math.abs( e.deltaX ) < Math.abs( e.deltaY ); - if ( isVertical && this._hasScrollBar ) return; + constructor(dom, graph) { + super(dom, graph); + this.axes_draw = false; // indicate if graph histogram was drawn for axes + this.xmin = this.ymin = this.xmax = this.ymax = 0; + this.#is_bent = (graph._typename === clTGraphBentErrors); + this.#has_errors = (graph._typename === clTGraphErrors) || + (graph._typename === clTGraphMultiErrors) || + (graph._typename === clTGraphAsymmErrors) || + this.#is_bent || graph._typename.match(/^RooHist/); + this.#draw_kind = ''; + } - e.preventDefault(); + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } - // set value - const delta = this._normalizeMouseWheel( e ) * this._step; - this._snapClampSetValue( this.getValue() + delta ); + /** @summary Return drawn graph object */ + getGraph() { return this.getObject(); } - // force the input to updateDisplay when it's focused - this.$input.value = this.getValue(); + /** @summary Return histogram object used for axis drawings */ + getHistogram() { return this.getObject()?.fHistogram; } - // debounce onFinishChange - clearTimeout( wheelFinishChangeTimeout ); - wheelFinishChangeTimeout = setTimeout( callOnFinishChange, WHEEL_DEBOUNCE_TIME ); + /** @summary Return true if histogram not present or has dummy ranges (for requested axis) */ + isDummyHistogram(check_axis) { + const histo = this.getHistogram(); + if (!histo) + return true; - }; + let is_normal = false; + if (check_axis !== 'y') + is_normal ||= (histo.fXaxis.fXmin !== 0.0011) || (histo.fXaxis.fXmax !== 1.1); - this.$slider.addEventListener( 'mousedown', mouseDown ); - this.$slider.addEventListener( 'touchstart', onTouchStart, { passive: false } ); - this.$slider.addEventListener( 'wheel', onWheel, { passive: false } ); + if (check_axis !== 'x') { + is_normal ||= (histo.fYaxis.fXmin !== 0.0011) || (histo.fYaxis.fXmax !== 1.1) || + (histo.fMinimum !== 0.0011) || (histo.fMaximum !== 1.1); + } - } + return !is_normal; + } - _setDraggingStyle( active, axis = 'horizontal' ) { - if ( this.$slider ) { - this.$slider.classList.toggle( 'active', active ); - } - document.body.classList.toggle( 'lil-gui-dragging', active ); - document.body.classList.toggle( `lil-gui-${axis}`, active ); - } + /** @summary Set histogram object to graph */ + setHistogram(histo) { + const obj = this.getObject(); + if (obj) + obj.fHistogram = histo; + } - _getImplicitStep() { + /** @summary Is TScatter object */ + isScatter() { return false; } - if ( this._hasMin && this._hasMax ) { - return ( this._max - this._min ) / 1000; - } + /** @summary Redraw graph + * @desc may redraw histogram which was used to draw axes + * @return {Promise} for ready */ + async redraw() { + let promise = Promise.resolve(true); - return 0.1; + if (this.#redraw_hist) { + this.#redraw_hist = undefined; + const hist_painter = this.getMainPainter(); + if (hist_painter?.isSecondary(this) && this.axes_draw) + promise = hist_painter.redraw(); + } - } + return promise.then(() => this.drawGraph()).then(() => { + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; + return res; + }); + } - _onUpdateMinMax() { + /** @summary Cleanup graph painter */ + cleanup() { + this.#bins = undefined; + this.#own_histogram = undefined; + super.cleanup(); + } - if ( !this._hasSlider && this._hasMin && this._hasMax ) { + /** @summary Returns object if this drawing TGraphMultiErrors object */ + get_gme() { + const graph = this.getGraph(); + return graph?._typename === clTGraphMultiErrors ? graph : null; + } - // If this is the first time we're hearing about min and max - // and we haven't explicitly stated what our step is, let's - // update that too. - if ( !this._stepExplicit ) { - this.step( this._getImplicitStep(), false ); - } + /** @summary Decode options */ + decodeOptions(opt, first_time) { + if (isStr(opt) && (opt.indexOf('same ') === 0)) + opt = opt.slice(5); - this._initSlider(); - this.updateDisplay(); + const graph = this.getGraph(), + is_gme = Boolean(this.get_gme()), + has_main = first_time ? Boolean(this.getMainPainter()) : !this.axes_draw; - } + function decodeBlock(d, res) { + Object.assign(res, { Line: 0, Curve: 0, Rect: 0, Mark: 0, Bar: 0, OutRange: 0, EF: 0, Fill: 0, MainError: 1, Ends: 1, ScaleErrX: 1 }); - } + if (is_gme && d.check('S=', true)) + res.ScaleErrX = d.partAsFloat(); + + if (d.check('L')) + res.Line = 1; + if (d.check('F')) + res.Fill = 1; + if (d.check('CC')) + res.Curve = 2; // draw all points without reduction + if (d.check('C')) + res.Curve = 1; + if (d.check('*')) + res.Mark = 103; + if (d.check('P0')) + res.Mark = 104; + if (d.check('P')) + res.Mark = 1; + if (d.check('B')) { + res.Bar = 1; + res.Errors = 0; + } + if (d.check('Z')) { + res.Errors = 1; + res.Ends = 0; + } + if (d.check('||')) { + res.Errors = 1; + res.MainError = 0; + res.Ends = 1; + } + if (d.check('[]')) { + res.Errors = 1; + res.MainError = 0; + res.Ends = 2; + } + if (d.check('|>')) { + res.Errors = 1; + res.Ends = 3; + } + if (d.check('>')) { + res.Errors = 1; + res.Ends = 4; + } + if (d.check('0')) { + res.Mark = 1; + res.Errors = 1; + res.OutRange = 1; + } + if (d.check('1') && (res.Bar === 1)) + res.Bar = 2; + if (d.check('2')) { + res.Rect = 1; + res.Errors = 0; + } + if (d.check('3')) { + res.EF = 1; + res.Errors = 0; + } + if (d.check('4')) { + res.EF = 2; + res.Errors = 0; + } + if (d.check('5')) { + res.Rect = 2; + res.Errors = 0; + } + if (d.check('X')) + res.Errors = 0; + } - _normalizeMouseWheel( e ) { + const res = this.setOptions({ + Axis: '', NoOpt: 0, PadStats: false, PadPalette: false, original: opt, + second_x: false, second_y: false, individual_styles: false + }); - let { deltaX, deltaY } = e; + let blocks_gme = []; - // Safari and Chrome report weird non-integral values for a notched wheel, - // but still expose actual lines scrolled via wheelDelta. Notched wheels - // should behave the same way as arrow keys. - if ( Math.floor( e.deltaY ) !== e.deltaY && e.wheelDelta ) { - deltaX = 0; - deltaY = -e.wheelDelta / 120; - deltaY *= this._stepExplicit ? 1 : 10; - } + if (is_gme && opt) { + if (opt.indexOf(';') > 0) { + blocks_gme = opt.split(';'); + opt = blocks_gme.shift(); + } else if (opt.indexOf('_') > 0) { + blocks_gme = opt.split('_'); + opt = blocks_gme.shift(); + } + } - const wheel = deltaX + -deltaY; + let d = new DrawOptions(opt), hopt = ''; - return wheel; + PadDrawOptions.forEach(name => { + if (d.check(name)) + hopt += ';' + name; + }); + if (d.check('XAXIS_', true)) + hopt += ';XAXIS_' + d.part; + if (d.check('YAXIS_', true)) + hopt += ';YAXIS_' + d.part; - } + if (d.empty()) { + res.original = has_main ? 'lp' : 'alp'; + d = new DrawOptions(res.original); + } - _arrowKeyMultiplier( e ) { + if (d.check('FILL_', 'color')) { + res.graphFillColor = d.color; + res.graphFillPattern = 1001; + } - let mult = this._stepExplicit ? 1 : 10; + if (d.check('FILLPAT_', true)) + res.graphFillPattern = d.partAsInt(); - if ( e.shiftKey ) { - mult *= 10; - } else if ( e.altKey ) { - mult /= 10; - } + if (d.check('LINE_', 'color')) + res.graphLineColor = this.getColor(d.color); - return mult; + if (d.check('WIDTH_', true)) + res.graphLineWidth = d.partAsInt(); - } + if (d.check('NOOPT')) + res.NoOpt = 1; - _snap( value ) { + if (d.check('POS3D_', true)) + res.pos3d = d.partAsInt() - 0.5; - // This would be the logical way to do things, but floating point errors. - // return Math.round( value / this._step ) * this._step; + if (d.check('PFC') && !res._pfc) + res._pfc = 2; + if (d.check('PLC') && !res._plc) + res._plc = 2; + if (d.check('PMC') && !res._pmc) + res._pmc = 2; - // Using inverse step solves a lot of them, but not all - // const inverseStep = 1 / this._step; - // return Math.round( value * inverseStep ) / inverseStep; + if (d.check('A')) + res.Axis = d.check('I') ? 'A;' : ' '; // I means invisible axis + if (d.check('X+')) { + res.Axis += 'X+'; + res.second_x = has_main; + } + if (d.check('Y+')) { + res.Axis += 'Y+'; + res.second_y = has_main; + } + if (d.check('RX')) + res.Axis += 'RX'; + if (d.check('RY')) + res.Axis += 'RY'; - // Not happy about this, but haven't seen it break. - const r = Math.round( value / this._step ) * this._step; - return parseFloat( r.toPrecision( 15 ) ); + if (is_gme) { + res.blocks = []; + res.skip_errors_x0 = res.skip_errors_y0 = false; + if (d.check('X0')) + res.skip_errors_x0 = true; + if (d.check('Y0')) + res.skip_errors_y0 = true; + } - } + decodeBlock(d, res); - _clamp( value ) { - // either condition is false if min or max is undefined - if ( value < this._min ) value = this._min; - if ( value > this._max ) value = this._max; - return value; - } + if (is_gme && d.check('S')) + res.individual_styles = true; - _snapClampSetValue( value ) { - this.setValue( this._clamp( this._snap( value ) ) ); - } + if (res.Errors === undefined) + res.Errors = this.#has_errors && (!is_gme || !blocks_gme.length) ? 1 : 0; - get _hasScrollBar() { - const root = this.parent.root.$children; - return root.scrollHeight > root.clientHeight; - } + // special case - one could use svg:path to draw many pixels ( + if ((res.Mark === 1) && (graph.fMarkerStyle === 1)) + res.Mark = 101; - get _hasMin() { - return this._min !== undefined; - } + // if no drawing option is selected and if opt === '' nothing is done. + if ((res.Line + res.Fill + res.Curve + res.Mark + res.Bar + res.EF + res.Rect + res.Errors === 0) && d.empty()) + res.Line = 1; - get _hasMax() { - return this._max !== undefined; - } + if (this.matchObjectType(clTGraphErrors)) { + const len = graph.fEX.length; + let m = 0; + for (let k = 0; k < len; ++k) + m = Math.max(m, graph.fEX[k], graph.fEY[k]); + if (m < 1e-100) + res.Errors = 0; + } -} + this.#cutg = this.matchObjectType(clTCutG); + this.#cutg_lastsame = this.#cutg && (graph.fNpoints > 3) && + (graph.fX[0] === graph.fX[graph.fNpoints - 1]) && (graph.fY[0] === graph.fY[graph.fNpoints - 1]); -class OptionController extends Controller { + if (!res.Axis) { + // check if axis should be drawn + // either graph drawn directly or + // graph is first object in list of primitives + const pad = this.getPadPainter()?.getRootPad(true); + if (!pad || (pad?.fPrimitives?.arr[0] === this.getObject())) + res.Axis = ' '; + } - constructor( parent, object, property, options ) { + res.Axis += hopt; - super( parent, object, property, 'option' ); + for (let bl = 0; bl < blocks_gme.length; ++bl) { + const subd = new DrawOptions(blocks_gme[bl]), subres = {}; + decodeBlock(subd, subres); + subres.skip_errors_x0 = res.skip_errors_x0; + subres.skip_errors_y0 = res.skip_errors_y0; + res.blocks.push(subres); + } + } - this.$select = document.createElement( 'select' ); - this.$select.setAttribute( 'aria-labelledby', this.$name.id ); + /** @summary Return prepared graph bins + * @protected */ + _getBins() { return this.#bins; } - this.$display = document.createElement( 'div' ); - this.$display.classList.add( 'display' ); + /** @summary Create bins for TF1 drawing */ + createBins() { + const gr = this.getGraph(), + o = this.getOptions(); + if (!gr) + return; - this._values = Array.isArray( options ) ? options : Object.values( options ); - this._names = Array.isArray( options ) ? options : Object.keys( options ); + let kind = 0, npoints = gr.fNpoints; + if (this.#cutg && this.#cutg_lastsame) + npoints--; - this._names.forEach( name => { - const $option = document.createElement( 'option' ); - $option.innerHTML = name; - this.$select.appendChild( $option ); - } ); + if (gr._typename === clTGraphErrors) + kind = 1; + else if (gr._typename === clTGraphMultiErrors) + kind = 2; + else if (gr._typename === clTGraphAsymmErrors || gr._typename === clTGraphBentErrors || gr._typename.match(/^RooHist/)) + kind = 3; - this.$select.addEventListener( 'change', () => { - this.setValue( this._values[ this.$select.selectedIndex ] ); - this._callOnFinishChange(); - } ); + this.#bins = new Array(npoints); - this.$select.addEventListener( 'focus', () => { - this.$display.classList.add( 'focus' ); - } ); + for (let p = 0; p < npoints; ++p) { + const bin = this.#bins[p] = { x: gr.fX[p], y: gr.fY[p], indx: p }; + switch (kind) { + case 1: + bin.exlow = bin.exhigh = gr.fEX[p]; + bin.eylow = bin.eyhigh = gr.fEY[p]; + break; + case 2: + bin.exlow = gr.fExL[p]; + bin.exhigh = gr.fExH[p]; + bin.eylow = gr.fEyL[0][p]; + bin.eyhigh = gr.fEyH[0][p]; + break; + case 3: + bin.exlow = gr.fEXlow[p]; + bin.exhigh = gr.fEXhigh[p]; + bin.eylow = gr.fEYlow[p]; + bin.eyhigh = gr.fEYhigh[p]; + break; + } - this.$select.addEventListener( 'blur', () => { - this.$display.classList.remove( 'focus' ); - } ); + if (p === 0) { + this.xmin = this.xmax = bin.x; + this.ymin = this.ymax = bin.y; + } - this.$widget.appendChild( this.$select ); - this.$widget.appendChild( this.$display ); + if (kind > 0) { + this.xmin = Math.min(this.xmin, bin.x - bin.exlow, bin.x + bin.exhigh); + this.xmax = Math.max(this.xmax, bin.x - bin.exlow, bin.x + bin.exhigh); + this.ymin = Math.min(this.ymin, bin.y - bin.eylow, bin.y + bin.eyhigh); + this.ymax = Math.max(this.ymax, bin.y - bin.eylow, bin.y + bin.eyhigh); + } else { + this.xmin = Math.min(this.xmin, bin.x); + this.xmax = Math.max(this.xmax, bin.x); + this.ymin = Math.min(this.ymin, bin.y); + this.ymax = Math.max(this.ymax, bin.y); + } + } - this.$disable = this.$select; + // workaround, are there better way to show marker at 0,0 on the top of the frame? + this.#frame_layer = true; + if ((this.xmin === 0) && (this.ymin === 0) && (npoints > 0) && (this.#bins[0].x === 0) && (this.#bins[0].y === 0) && + o.Mark && !o.Line && !o.Curve && !o.Fill) + this.#frame_layer = 'upper_layer'; + } - this.updateDisplay(); + /** @summary Return margins for histogram ranges */ + getHistRangeMargin() { return 0.1; } - } + /** @summary Create histogram for graph + * @desc graph bins should be created when calling this function + * @param {boolean} [set_x] - set X axis range + * @param {boolean} [set_y] - set Y axis range */ + createHistogram(set_x = true, set_y = true) { + const graph = this.getGraph(), + xmin = this.xmin, + margin = this.getHistRangeMargin(); + let xmax = this.xmax, ymin = this.ymin, ymax = this.ymax; - updateDisplay() { - const value = this.getValue(); - const index = this._values.indexOf( value ); - this.$select.selectedIndex = index; - this.$display.innerHTML = index === -1 ? value : this._names[ index ]; - return this; - } + if (xmin >= xmax) + xmax = xmin + 1; + if (ymin >= ymax) + ymax = ymin + 1; + const dx = (xmax - xmin) * margin, dy = (ymax - ymin) * margin; + let uxmin = xmin - dx, uxmax = xmax + dx, + minimum = ymin - dy, maximum = ymax + dy; -} + if ((ymin > 0) && (minimum <= 0)) + minimum = (1 - margin) * ymin; + if ((ymax < 0) && (maximum >= 0)) + maximum = (1 - margin) * ymax; -class StringController extends Controller { + const minimum0 = minimum, maximum0 = maximum; + let histo = this.getHistogram(); - constructor( parent, object, property ) { + if (!this.isScatter() && !histo?.fXaxis.fTimeDisplay) { + const pad_logx = this.getPadPainter()?.getPadLog('x'); - super( parent, object, property, 'string' ); + if ((uxmin < 0) && (xmin >= 0)) + uxmin = pad_logx ? xmin * (1 - margin) : 0; + if ((uxmax > 0) && (xmax <= 0)) + uxmax = pad_logx ? (1 + margin) * xmax : 0; + } - this.$input = document.createElement( 'input' ); - this.$input.setAttribute( 'type', 'text' ); - this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + if (!histo) { + histo = this.isScatter() ? createHistogram(clTH2F, 30, 30) : createHistogram(clTH1F, 100); + histo.fName = graph.fName + '_h'; + histo.fBits |= kNoStats; + this.#own_histogram = true; + this.setHistogram(histo); + } else if ((histo.fMaximum !== kNoZoom) && (histo.fMinimum !== kNoZoom) && !this.isDummyHistogram('y')) { + minimum = histo.fMinimum; + maximum = histo.fMaximum; + } - this.$input.addEventListener( 'input', () => { - this.setValue( this.$input.value ); - } ); + if (graph.fMinimum !== kNoZoom) + minimum = ymin = graph.fMinimum; + if (graph.fMaximum !== kNoZoom) + maximum = graph.fMaximum; + if ((minimum < 0) && (ymin >= 0)) + minimum = (1 - margin) * ymin; + if ((ymax < 0) && (maximum >= 0)) + maximum = (1 - margin) * ymax; - this.$input.addEventListener( 'keydown', e => { - if ( e.code === 'Enter' ) { - this.$input.blur(); - } - } ); + setHistogramTitle(histo, this.getObject().fTitle); - this.$input.addEventListener( 'blur', () => { - this._callOnFinishChange(); - } ); + if (set_x && !histo.fXaxis.fLabels) { + histo.fXaxis.fXmin = uxmin; + histo.fXaxis.fXmax = uxmax; + } - this.$widget.appendChild( this.$input ); + if (set_y && !histo.fYaxis.fLabels) { + histo.fYaxis.fXmin = Math.min(minimum0, minimum); + histo.fYaxis.fXmax = Math.max(maximum0, maximum); + if (!this.isScatter()) { + histo.fMinimum = minimum; + histo.fMaximum = maximum; + } + } - this.$disable = this.$input; + histo.$xmin_nz = xmin > 0 ? xmin : undefined; + histo.$ymin_nz = ymin > 0 ? ymin : undefined; - this.updateDisplay(); + return histo; + } - } + /** @summary Check if user range can be un-zommed + * @desc Used when graph points covers larger range than provided histogram */ + unzoomUserRange(dox, doy /* , doz */) { + const graph = this.getGraph(); + if (this.#own_histogram || !graph) + return false; - updateDisplay() { - this.$input.value = this.getValue(); - return this; - } + const histo = this.getHistogram(); -} + dox = dox && histo && ((histo.fXaxis.fXmin > this.xmin) || (histo.fXaxis.fXmax < this.xmax)); + doy = doy && histo && ((histo.fYaxis.fXmin > this.ymin) || (histo.fYaxis.fXmax < this.ymax)); + if (!dox && !doy) + return false; -const stylesheet = `.lil-gui { - font-family: var(--font-family); - font-size: var(--font-size); - line-height: 1; - font-weight: normal; - font-style: normal; - text-align: left; - background-color: var(--background-color); - color: var(--text-color); - user-select: none; - -webkit-user-select: none; - touch-action: manipulation; - --background-color: #1f1f1f; - --text-color: #ebebeb; - --title-background-color: #111111; - --title-text-color: #ebebeb; - --widget-color: #424242; - --hover-color: #4f4f4f; - --focus-color: #595959; - --number-color: #2cc9ff; - --string-color: #a2db3c; - --font-size: 11px; - --input-font-size: 11px; - --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; - --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace; - --padding: 4px; - --spacing: 4px; - --widget-height: 20px; - --title-height: calc(var(--widget-height) + var(--spacing) * 1.25); - --name-width: 45%; - --slider-knob-width: 2px; - --slider-input-width: 27%; - --color-input-width: 27%; - --slider-input-min-width: 45px; - --color-input-min-width: 45px; - --folder-indent: 7px; - --widget-padding: 0 0 0 3px; - --widget-border-radius: 2px; - --checkbox-size: calc(0.75 * var(--widget-height)); - --scrollbar-width: 5px; -} -.lil-gui, .lil-gui * { - box-sizing: border-box; - margin: 0; - padding: 0; -} -.lil-gui.root { - width: var(--width, 245px); - display: flex; - flex-direction: column; -} -.lil-gui.root > .title { - background: var(--title-background-color); - color: var(--title-text-color); -} -.lil-gui.root > .children { - overflow-x: hidden; - overflow-y: auto; -} -.lil-gui.root > .children::-webkit-scrollbar { - width: var(--scrollbar-width); - height: var(--scrollbar-width); - background: var(--background-color); -} -.lil-gui.root > .children::-webkit-scrollbar-thumb { - border-radius: var(--scrollbar-width); - background: var(--focus-color); -} -@media (pointer: coarse) { - .lil-gui.allow-touch-styles, .lil-gui.allow-touch-styles .lil-gui { - --widget-height: 28px; - --padding: 6px; - --spacing: 6px; - --font-size: 13px; - --input-font-size: 16px; - --folder-indent: 10px; - --scrollbar-width: 7px; - --slider-input-min-width: 50px; - --color-input-min-width: 65px; - } -} -.lil-gui.force-touch-styles, .lil-gui.force-touch-styles .lil-gui { - --widget-height: 28px; - --padding: 6px; - --spacing: 6px; - --font-size: 13px; - --input-font-size: 16px; - --folder-indent: 10px; - --scrollbar-width: 7px; - --slider-input-min-width: 50px; - --color-input-min-width: 65px; -} -.lil-gui.autoPlace { - max-height: 100%; - position: fixed; - top: 0; - right: 15px; - z-index: 1001; -} + this.createHistogram(dox, doy); + this.getMainPainter()?.extractAxesProperties(1); // just to enforce ranges extraction -.lil-gui .controller { - display: flex; - align-items: center; - padding: 0 var(--padding); - margin: var(--spacing) 0; -} -.lil-gui .controller.disabled { - opacity: 0.5; -} -.lil-gui .controller.disabled, .lil-gui .controller.disabled * { - pointer-events: none !important; -} -.lil-gui .controller > .name { - min-width: var(--name-width); - flex-shrink: 0; - white-space: pre; - padding-right: var(--spacing); - line-height: var(--widget-height); -} -.lil-gui .controller .widget { - position: relative; - display: flex; - align-items: center; - width: 100%; - min-height: var(--widget-height); -} -.lil-gui .controller.string input { - color: var(--string-color); -} -.lil-gui .controller.boolean .widget { - cursor: pointer; -} -.lil-gui .controller.color .display { - width: 100%; - height: var(--widget-height); - border-radius: var(--widget-border-radius); - position: relative; -} -@media (hover: hover) { - .lil-gui .controller.color .display:hover:before { - content: " "; - display: block; - position: absolute; - border-radius: var(--widget-border-radius); - border: 1px solid #fff9; - top: 0; - right: 0; - bottom: 0; - left: 0; - } -} -.lil-gui .controller.color input[type=color] { - opacity: 0; - width: 100%; - height: 100%; - cursor: pointer; -} -.lil-gui .controller.color input[type=text] { - margin-left: var(--spacing); - font-family: var(--font-family-mono); - min-width: var(--color-input-min-width); - width: var(--color-input-width); - flex-shrink: 0; -} -.lil-gui .controller.option select { - opacity: 0; - position: absolute; - width: 100%; - max-width: 100%; -} -.lil-gui .controller.option .display { - position: relative; - pointer-events: none; - border-radius: var(--widget-border-radius); - height: var(--widget-height); - line-height: var(--widget-height); - max-width: 100%; - overflow: hidden; - word-break: break-all; - padding-left: 0.55em; - padding-right: 1.75em; - background: var(--widget-color); -} -@media (hover: hover) { - .lil-gui .controller.option .display.focus { - background: var(--focus-color); - } -} -.lil-gui .controller.option .display.active { - background: var(--focus-color); -} -.lil-gui .controller.option .display:after { - font-family: "lil-gui"; - content: "↕"; - position: absolute; - top: 0; - right: 0; - bottom: 0; - padding-right: 0.375em; -} -.lil-gui .controller.option .widget, -.lil-gui .controller.option select { - cursor: pointer; -} -@media (hover: hover) { - .lil-gui .controller.option .widget:hover .display { - background: var(--hover-color); - } -} -.lil-gui .controller.number input { - color: var(--number-color); -} -.lil-gui .controller.number.hasSlider input { - margin-left: var(--spacing); - width: var(--slider-input-width); - min-width: var(--slider-input-min-width); - flex-shrink: 0; -} -.lil-gui .controller.number .slider { - width: 100%; - height: var(--widget-height); - background-color: var(--widget-color); - border-radius: var(--widget-border-radius); - padding-right: var(--slider-knob-width); - overflow: hidden; - cursor: ew-resize; - touch-action: pan-y; -} -@media (hover: hover) { - .lil-gui .controller.number .slider:hover { - background-color: var(--hover-color); - } -} -.lil-gui .controller.number .slider.active { - background-color: var(--focus-color); -} -.lil-gui .controller.number .slider.active .fill { - opacity: 0.95; -} -.lil-gui .controller.number .fill { - height: 100%; - border-right: var(--slider-knob-width) solid var(--number-color); - box-sizing: content-box; -} + return true; + } -.lil-gui-dragging .lil-gui { - --hover-color: var(--widget-color); -} -.lil-gui-dragging * { - cursor: ew-resize !important; -} + /** @summary Returns true if graph drawing can be optimize */ + canOptimize() { + return (settings.OptimizeDraw > 0) && !this.getOptions().NoOpt; + } -.lil-gui-dragging.lil-gui-vertical * { - cursor: ns-resize !important; -} + /** @summary Returns optimized bins - if optimization enabled */ + optimizeBins(maxpnt, filter_func) { + if ((this.#bins.length < 30) && !filter_func) + return this.#bins; -.lil-gui .title { - height: var(--title-height); - line-height: calc(var(--title-height) - 4px); - font-weight: 600; - padding: 0 var(--padding); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - outline: none; - text-decoration-skip: objects; -} -.lil-gui .title:before { - font-family: "lil-gui"; - content: "▾"; - padding-right: 2px; - display: inline-block; -} -.lil-gui .title:active { - background: var(--title-background-color); - opacity: 0.75; -} -@media (hover: hover) { - body:not(.lil-gui-dragging) .lil-gui .title:hover { - background: var(--title-background-color); - opacity: 0.85; - } - .lil-gui .title:focus { - text-decoration: underline var(--focus-color); - } -} -.lil-gui.root > .title:focus { - text-decoration: none !important; -} -.lil-gui.closed > .title:before { - content: "▸"; -} -.lil-gui.closed > .children { - transform: translateY(-7px); - opacity: 0; -} -.lil-gui.closed:not(.transition) > .children { - display: none; -} -.lil-gui.transition > .children { - transition-duration: 300ms; - transition-property: height, opacity, transform; - transition-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); - overflow: hidden; - pointer-events: none; -} -.lil-gui .children:empty:before { - content: "Empty"; + let selbins = null; + if (isFunc(filter_func)) { + for (let n = 0; n < this.#bins.length; ++n) { + if (filter_func(this.#bins[n], n)) { + if (!selbins) + selbins = (n === 0) ? [] : this.#bins.slice(0, n); + } else if (selbins) + selbins.push(this.#bins[n]); + } + } + if (!selbins) + selbins = this.#bins; + + if (!maxpnt) + maxpnt = 500000; + + if ((selbins.length < maxpnt) || !this.canOptimize()) + return selbins; + const optbins = [], step = Math.max(2, Math.floor(selbins.length / maxpnt)); + for (let n = 0; n < selbins.length; n += step) + optbins.push(selbins[n]); + + return optbins; + } + + /** @summary Check if such function should be drawn directly */ + needDrawFunc(graph, func) { + if (func._typename === clTPaveStats) + return (func.fName !== 'stats') || !graph.TestBit(kNoStats); // kNoStats is same for graph and histogram + + if ((func._typename === clTF1) || (func._typename === clTF2)) + return !func.TestBit(BIT(9)); // TF1::kNotDraw + + return true; + } + + /** @summary Returns tooltip for specified bin */ + getTooltips(d) { + const fp = this.get_fp(), lines = [], o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + gme = this.get_gme(); + + lines.push(this.getObjectHint()); + + if (d && funcs) { + if (d.indx !== undefined) + lines.push('p = ' + d.indx); + lines.push('x = ' + funcs.axisAsText('x', d.x), 'y = ' + funcs.axisAsText('y', d.y)); + if (gme) + lines.push('error x = -' + funcs.axisAsText('x', gme.fExL[d.indx]) + '/+' + funcs.axisAsText('x', gme.fExH[d.indx])); + else if (o.Errors && (funcs.x_handle.kind === kAxisNormal) && (d.exlow || d.exhigh)) + lines.push('error x = -' + funcs.axisAsText('x', d.exlow) + '/+' + funcs.axisAsText('x', d.exhigh)); + + if (gme) { + for (let ny = 0; ny < gme.fNYErrors; ++ny) + lines.push(`error y${ny} = -${funcs.axisAsText('y', gme.fEyL[ny][d.indx])}/+${funcs.axisAsText('y', gme.fEyH[ny][d.indx])}`); + } else if ((o.Errors || (o.EF > 0)) && (funcs.y_handle.kind === kAxisNormal) && (d.eylow || d.eyhigh)) + lines.push('error y = -' + funcs.axisAsText('y', d.eylow) + '/+' + funcs.axisAsText('y', d.eyhigh)); + } + return lines; + } + + /** @summary Provide frame painter for graph + * @desc If not exists, emulate its behavior */ + get_fp() { + let fp = this.getFramePainter(); + + if (fp?.grx && fp?.gry) + return fp; + + // FIXME: check if needed, can be removed easily + const pp = this.getPadPainter(), + rect = pp?.getPadRect() || { width: 800, height: 600 }; + + fp = { + pad_layer: true, + pad: pp?.getRootPad(true) ?? create$1(clTPad), + pw: rect.width, + ph: rect.height, + fX1NDC: 0.1, fX2NDC: 0.9, fY1NDC: 0.1, fY2NDC: 0.9, + getFrameWidth() { return this.pw; }, + getFrameHeight() { return this.ph; }, + grx(value) { + if (this.pad.fLogx) + value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; + else + value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); + return value * this.pw; + }, + gry(value) { + if (this.pad.fLogv ?? this.pad.fLogy) + value = (value > 0) ? Math.log10(value) : this.pad.fUymin; + else + value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); + return (1 - value) * this.ph; + }, + revertAxis(name, v) { + if (name === 'x') + return v / this.pw * (this.pad.fX2 - this.pad.fX1) + this.pad.fX1; + if (name === 'y') + return (1 - v / this.ph) * (this.pad.fY2 - this.pad.fY1) + this.pad.fY1; + return v; + }, + getGrFuncs() { return this; } + }; + + return fp; + } + + /** @summary append exclusion area to created path */ + appendExclusion(is_curve, path, drawbins, excl_width) { + const extrabins = []; + for (let n = drawbins.length - 1; n >= 0; --n) { + const bin = drawbins[n], + dlen = Math.sqrt(bin.dgrx ** 2 + bin.dgry ** 2); + if (dlen > 1e-10) { + // shift point + bin.grx += excl_width * bin.dgry / dlen; + bin.gry -= excl_width * bin.dgrx / dlen; + } + extrabins.push(bin); + } + + const path2 = buildSvgCurve(extrabins, { cmd: 'L', line: !is_curve }); + + this.appendPath(path + path2 + 'Z') + .call(this.fillatt.func) + .style('opacity', 0.75); + } + + /** @summary draw TGraph bins with specified options + * @desc Can be called several times */ + drawBins(funcs, options, draw_g, w, h, lineatt, fillatt, main_block) { + const graph = this.getGraph(); + if (!graph?.fNpoints) + return; + + let excl_width = 0, drawbins = null; + // if markers or errors drawn - no need handle events for line drawing + // this improves interactivity like zooming around graph points + const line_events_handling = !this.isBatchMode() && (options.Line || options.Errors) ? 'none' : null; + + if (main_block && lineatt.excl_side) { + excl_width = lineatt.excl_width; + if ((lineatt.width > 0) && !options.Line && !options.Curve) + options.Line = 1; + } + + if (options.EF) { + drawbins = this.optimizeBins((options.EF > 1) ? 20000 : 0); + + // build lower part + for (let n = 0; n < drawbins.length; ++n) { + const bin = drawbins[n]; + bin.grx = funcs.grx(bin.x); + bin.gry = funcs.gry(bin.y - bin.eylow); + } + + const path1 = buildSvgCurve(drawbins, { line: options.EF < 2, qubic: true }), + bins2 = []; + + for (let n = drawbins.length - 1; n >= 0; --n) { + const bin = drawbins[n]; + bin.gry = funcs.gry(bin.y + bin.eyhigh); + bins2.push(bin); + } + + // build upper part (in reverse direction) + const path2 = buildSvgCurve(bins2, { line: options.EF < 2, cmd: 'L', qubic: true }), + area = draw_g.append('svg:path') + .attr('d', path1 + path2 + 'Z') + .call(fillatt.func); + + // Let behaves as ROOT - see JIRA ROOT-8131 + if (fillatt.empty() && fillatt.colorindx) + area.style('stroke', this.getColor(fillatt.colorindx)); + if (main_block) + this.#draw_kind = 'lines'; + } + + if (options.Line || options.Fill) { + let close_symbol = ''; + if (this.#cutg) { + close_symbol = 'Z'; + if (!options.original) + options.Fill = 1; + } + + if (options.Fill) { + close_symbol = 'Z'; // always close area if we want to fill it + excl_width = 0; + } + + if (!drawbins) + drawbins = this.optimizeBins(0); + + for (let n = 0; n < drawbins.length; ++n) { + const bin = drawbins[n]; + bin.grx = funcs.grx(bin.x); + bin.gry = funcs.gry(bin.y); + } + + const path = buildSvgCurve(drawbins, { line: true, calc: excl_width }); + + if (excl_width) + this.appendExclusion(false, path, drawbins, excl_width); + + const elem = draw_g.append('svg:path') + .attr('d', path + close_symbol) + .style('pointer-events', line_events_handling); + if (options.Line) + elem.call(lineatt.func); + + if (options.Fill) + elem.call(fillatt.func); + else + elem.style('fill', 'none'); + + if (main_block) + this.#draw_kind = 'lines'; + } + + if (options.Curve) { + let curvebins = drawbins; + if ((this.#draw_kind !== 'lines') || !curvebins || ((options.Curve === 1) && (curvebins.length > 20000))) { + curvebins = this.optimizeBins((options.Curve === 1) ? 20000 : 0); + for (let n = 0; n < curvebins.length; ++n) { + const bin = curvebins[n]; + bin.grx = funcs.grx(bin.x); + bin.gry = funcs.gry(bin.y); + } + } + + const path = buildSvgCurve(curvebins, { qubic: !excl_width }); + if (excl_width) + this.appendExclusion(true, path, curvebins, excl_width); + + draw_g.append('svg:path') + .attr('d', path) + .call(lineatt.func) + .style('fill', 'none') + .style('pointer-events', line_events_handling); + if (main_block) + this.#draw_kind = 'lines'; // handled same way as lines + } + + let nodes = null; + + if (options.Errors || options.Rect || options.Bar) { + drawbins = this.optimizeBins(5000, (pnt, i) => { + const grx = funcs.grx(pnt.x); + + // when drawing bars, take all points + if (!options.Bar && ((grx < 0) || (grx > w))) + return true; + + const gry = funcs.gry(pnt.y); + + if (!options.Bar && !options.OutRange && ((gry < 0) || (gry > h))) + return true; + + pnt.grx1 = Math.round(grx); + pnt.gry1 = Math.round(gry); + + if (this.#has_errors) { + pnt.grx0 = Math.round(funcs.grx(pnt.x - options.ScaleErrX * pnt.exlow) - grx); + pnt.grx2 = Math.round(funcs.grx(pnt.x + options.ScaleErrX * pnt.exhigh) - grx); + pnt.gry0 = Math.round(funcs.gry(pnt.y - pnt.eylow) - gry); + pnt.gry2 = Math.round(funcs.gry(pnt.y + pnt.eyhigh) - gry); + + if (this.#is_bent) { + pnt.grdx0 = Math.round(funcs.gry(pnt.y + graph.fEXlowd[i]) - gry); + pnt.grdx2 = Math.round(funcs.gry(pnt.y + graph.fEXhighd[i]) - gry); + pnt.grdy0 = Math.round(funcs.grx(pnt.x + graph.fEYlowd[i]) - grx); + pnt.grdy2 = Math.round(funcs.grx(pnt.x + graph.fEYhighd[i]) - grx); + } else + pnt.grdx0 = pnt.grdx2 = pnt.grdy0 = pnt.grdy2 = 0; + } + + return false; + }); + + if (main_block) + this.#draw_kind = 'nodes'; + + nodes = draw_g.selectAll('.grpoint') + .data(drawbins) + .enter() + .append('svg:g') + .attr('class', 'grpoint') + .attr('transform', d => makeTranslate(d.grx1, d.gry1)); + } + + if (options.Bar) { + // calculate bar width + + let xmin = 0, xmax = 0; + for (let i = 0; i < drawbins.length; ++i) { + if (i === 0) + xmin = xmax = drawbins[i].grx1; + else { + xmin = Math.min(xmin, drawbins[i].grx1); + xmax = Math.max(xmax, drawbins[i].grx1); + } + } + + const sz0 = drawbins.length < 2 ? w / 4 : (xmax - xmin) / drawbins.length, + bw = sz0 * gStyle.fBarWidth, + boff = sz0 * gStyle.fBarOffset, + yy0 = Math.round(funcs.gry(0)); + let usefill = fillatt; + + if (main_block) { + const fp = this.getFramePainter(), + fpcol = !fp?.fillatt?.empty() ? fp.fillatt.getFillColor() : -1; + + if (fpcol === fillatt.getFillColor()) + usefill = this.createAttFill({ color: fpcol === 'white' ? kBlack : kWhite, pattern: 1001, std: false }); + } + + nodes.append('svg:path') + .attr('d', d => { + d.bar = true; // element drawn as bar + const dx = bw > 1 ? Math.round(boff - bw / 2) : 0, + dw = bw > 1 ? Math.round(bw) : 1, + dy = (options.Bar !== 1) ? 0 : ((d.gry1 > yy0) ? yy0 - d.gry1 : 0), + dh = (options.Bar !== 1) ? (h > d.gry1 ? h - d.gry1 : 0) : Math.abs(yy0 - d.gry1); + return `M${dx},${dy}h${dw}v${dh}h${-dw}z`; + }) + .call(usefill.func); + + this.#barwidth = bw; + this.#baroffset = boff; + } + + if (options.Rect) { + nodes.filter(d => (d.exlow > 0) && (d.exhigh > 0) && (d.eylow > 0) && (d.eyhigh > 0)) + .append('svg:path') + .attr('d', d => { + d.rect = true; + return `M${d.grx0},${d.gry0}H${d.grx2}V${d.gry2}H${d.grx0}Z`; + }) + .call(fillatt.func) + .call(options.Rect === 2 ? lineatt.func : () => {}); + } + + this.error_size = 0; + + if (options.Errors) { + // to show end of error markers, use line width attribute + let lw = lineatt.width + gStyle.fEndErrorSize; + const vv = options.Ends ? `m0,${lw}v${ -2 * lw}` : '', + hh = options.Ends ? `m${lw},0h${ -2 * lw}` : ''; + let vleft = vv, vright = vv, htop = hh, hbottom = hh, bb; + + const mainLine = (dx, dy) => { + if (!options.MainError) + return `M${dx},${dy}`; + const res = 'M0,0'; + if (dx) + return res + (dy ? `L${dx},${dy}` : `H${dx}`); + return dy ? res + `V${dy}` : res; + }; + + switch (options.Ends) { + case 2: // option [] + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `m${bb},${lw}h${-bb}v${ -2 * lw}h${bb}`; + vright = `m${-bb},${lw}h${bb}v${ -2 * lw}h${-bb}`; + htop = `m${-lw},${bb}v${-bb}h${2 * lw}v${bb}`; + hbottom = `m${-lw},${-bb}v${bb}h${2 * lw}v${-bb}`; + break; + case 3: // option |> + lw = Math.max(lw, Math.round(graph.fMarkerSize * 8 * 0.66)); + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `l${bb},${lw}v${ -2 * lw}l${-bb},${lw}`; + vright = `l${-bb},${lw}v${ -2 * lw}l${bb},${lw}`; + htop = `l${-lw},${bb}h${2 * lw}l${-lw},${-bb}`; + hbottom = `l${-lw},${-bb}h${2 * lw}l${-lw},${bb}`; + break; + case 4: // option > + lw = Math.max(lw, Math.round(graph.fMarkerSize * 8 * 0.66)); + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `l${bb},${lw}m0,${ -2 * lw}l${-bb},${lw}`; + vright = `l${-bb},${lw}m0,${ -2 * lw}l${bb},${lw}`; + htop = `l${-lw},${bb}m${2 * lw},0l${-lw},${-bb}`; + hbottom = `l${-lw},${-bb}m${2 * lw},0l${-lw},${bb}`; + break; + } + + this.error_size = lw; + + lw = Math.floor((lineatt.width - 1) / 2); // one should take into account half of end-cup line width + + let visible = nodes.filter(d => (d.exlow > 0) || (d.exhigh > 0) || (d.eylow > 0) || (d.eyhigh > 0)); + if (options.skip_errors_x0 || options.skip_errors_y0) + visible = visible.filter(d => (d.x || !options.skip_errors_x0) && (d.y || !options.skip_errors_y0)); + + if (!this.isBatchMode() && settings.Tooltip && main_block) { + visible.append('svg:path') + .attr('d', d => `M${d.grx0},${d.gry0}h${d.grx2 - d.grx0}v${d.gry2 - d.gry0}h${d.grx0 - d.grx2}z`) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } + + visible.append('svg:path') + .attr('d', d => { + d.error = true; + return ((d.exlow > 0) ? mainLine(d.grx0 + lw, d.grdx0) + vleft : '') + + ((d.exhigh > 0) ? mainLine(d.grx2 - lw, d.grdx2) + vright : '') + + ((d.eylow > 0) ? mainLine(d.grdy0, d.gry0 - lw) + hbottom : '') + + ((d.eyhigh > 0) ? mainLine(d.grdy2, d.gry2 + lw) + htop : ''); + }) + .style('fill', 'none') + .call(lineatt.func); + } + + if (options.Mark) { + // for tooltips use markers only if nodes were not created + this.createAttMarker({ attr: graph, style: options.Mark - 100 }); + + this.#marker_size = this.markeratt.getFullSize(); + + this.markeratt.resetPos(); + + const want_tooltip = !this.isBatchMode() && settings.Tooltip && (!this.markeratt.fill || (this.#marker_size < 7)) && !nodes && main_block, + hsz = Math.max(5, Math.round(this.#marker_size * 0.7)), + maxnummarker = 1000000 / (this.markeratt.getMarkerLength() + 7); // let produce SVG at maximum 1MB + + let path = '', pnt, grx, gry, + hints_marker = '', step = 1; + + if (!drawbins) + drawbins = this.optimizeBins(maxnummarker); + else if (this.canOptimize() && (drawbins.length > 1.5 * maxnummarker)) + step = Math.min(2, Math.round(drawbins.length / maxnummarker)); + + for (let n = 0; n < drawbins.length; n += step) { + pnt = drawbins[n]; + grx = funcs.grx(pnt.x); + if ((grx > -this.#marker_size) && (grx < w + this.#marker_size)) { + gry = funcs.gry(pnt.y); + if ((gry > -this.#marker_size) && (gry < h + this.#marker_size)) { + path += this.markeratt.create(grx, gry); + if (want_tooltip) + hints_marker += `M${grx - hsz},${gry - hsz}h${2 * hsz}v${2 * hsz}h${ -2 * hsz}z`; + } + } + } + + if (path) { + draw_g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); + if ((nodes === null) && (this.#draw_kind === 'none') && main_block) + this.#draw_kind = (options.Mark === 101) ? 'path' : 'mark'; + } + if (want_tooltip && hints_marker) { + draw_g.append('svg:path') + .attr('d', hints_marker) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } + } + } + + /** @summary append TGraphQQ part */ + appendQQ(funcs, graph) { + const xqmin = Math.max(funcs.scale_xmin, graph.fXq1), + xqmax = Math.min(funcs.scale_xmax, graph.fXq2), + yqmin = Math.max(funcs.scale_ymin, graph.fYq1), + yqmax = Math.min(funcs.scale_ymax, graph.fYq2), + makeLine = (x1, y1, x2, y2) => `M${funcs.grx(x1)},${funcs.gry(y1)}L${funcs.grx(x2)},${funcs.gry(y2)}`, + yxmin = (graph.fYq2 - graph.fYq1) * (funcs.scale_xmin - graph.fXq1) / (graph.fXq2 - graph.fXq1) + graph.fYq1, + yxmax = (graph.fYq2 - graph.fYq1) * (funcs.scale_xmax - graph.fXq1) / (graph.fXq2 - graph.fXq1) + graph.fYq1; + + let path2; + if (yxmin < funcs.scale_ymin) { + const xymin = (graph.fXq2 - graph.fXq1) * (funcs.scale_ymin - graph.fYq1) / (graph.fYq2 - graph.fYq1) + graph.fXq1; + path2 = makeLine(xymin, funcs.scale_ymin, xqmin, yqmin); + } else + path2 = makeLine(funcs.scale_xmin, yxmin, xqmin, yqmin); + + if (yxmax > funcs.scale_ymax) { + const xymax = (graph.fXq2 - graph.fXq1) * (funcs.scale_ymax - graph.fYq1) / (graph.fYq2 - graph.fYq1) + graph.fXq1; + path2 += makeLine(xqmax, yqmax, xymax, funcs.scale_ymax); + } else + path2 += makeLine(xqmax, yqmax, funcs.scale_xmax, yxmax); + + const latt1 = this.createAttLine({ style: 1, width: 1, color: kBlack, std: false }), + latt2 = this.createAttLine({ style: 2, width: 1, color: kBlack, std: false }); + + this.appendPath(makeLine(xqmin, yqmin, xqmax, yqmax)) + .call(latt1.func) + .style('fill', 'none'); + + this.appendPath(path2) + .call(latt2.func) + .style('fill', 'none'); + } + + drawBins3D(/* fp, graph */) { + console.log('Load ./hist/TGraphPainter.mjs to draw graph in 3D'); + } + + /** @summary Create necessary histogram draw attributes */ + createGraphDrawAttributes(only_check_auto) { + const graph = this.getGraph(), o = this.getOptions(); + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(graph.$num_graphs); + this.#auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { + o._pfc = 1; + graph.fFillColor = icolor; + this.#auto_exec += `SetFillColor(${icolor});;`; + this.deleteAttr('fill'); + } + if (o._plc > 1) { + o._plc = 1; + graph.fLineColor = icolor; + this.#auto_exec += `SetLineColor(${icolor});;`; + this.deleteAttr('line'); + } + if (o._pmc > 1) { + o._pmc = 1; + graph.fMarkerColor = icolor; + this.#auto_exec += `SetMarkerColor(${icolor});;`; + this.deleteAttr('marker'); + } + } + } + + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttLine({ attr: graph, can_excl: true, color0: o.graphLineColor, width: o.graphLineWidth }); + this.createAttFill({ attr: graph, color: o.graphFillColor, pattern: o.graphFillPattern, }); + } + } + + /** @summary draw TGraph */ + drawGraph() { + const fp = this.get_fp(), + graph = this.getGraph(), + o = this.getOptions(); + if (!fp) + return; + + // special mode for TMultiGraph 3d drawing + if (o.pos3d) + return this.drawBins3D(fp, graph); + + const is_gme = Boolean(this.get_gme()), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + w = funcs.getFrameWidth(), + h = funcs.getFrameHeight(), + g = this.createG(fp.pad_layer ? false : this.#frame_layer); + + this.createGraphDrawAttributes(); + + this.fillatt.used = false; // mark used only when really used + + this.#draw_kind = 'none'; // indicate if special svg:g were created for each bin + this.#marker_size = 0; // indicate if markers are drawn + const draw_g = is_gme ? g.append('svg:g') : g; + + this.drawBins(funcs, o, draw_g, w, h, this.lineatt, this.fillatt, true); + + if (graph._typename === 'TGraphQQ') + this.appendQQ(funcs, graph); + + if (is_gme) { + const extract_gme_errors = nblock => { + this.#bins?.forEach(bin => { + bin.eylow = graph.fEyL[nblock][bin.indx]; + bin.eyhigh = graph.fEyH[nblock][bin.indx]; + }); + }; + + for (let k = 0; k < graph.fNYErrors; ++k) { + const lineatt = !o.individual_styles ? this.lineatt : this.createAttLine({ attr: graph.fAttLine[k], std: false }), + fillatt = !o.individual_styles ? this.fillatt : this.createAttFill({ attr: graph.fAttFill[k], std: false }), + sub_g = g.append('svg:g'), + options = k < o.blocks.length ? o.blocks[k] : o; + extract_gme_errors(k); + this.drawBins(funcs, options, sub_g, w, h, lineatt, fillatt); + } + extract_gme_errors(0); // ensure that first block kept at the end + } + + if (!this.isBatchMode()) { + addMoveHandler(this, this.testEditable()); + assignContextMenu(this, kNoReorder); + } + } + + /** @summary Provide tooltip at specified point */ + extractTooltip(pnt) { + if (!pnt) + return null; + + if ((this.#draw_kind === 'lines') || (this.#draw_kind === 'path') || (this.#draw_kind === 'mark')) + return this.extractTooltipForPath(pnt); + + if (this.#draw_kind !== 'nodes') + return null; + + const fp = this.get_fp(), + o = this.getOptions(), + height = fp.getFrameHeight(), + bw = this.#barwidth, + boff = this.#baroffset, + esz = this.error_size, + isbar1 = (o.Bar === 1), + funcs = isbar1 ? fp.getGrFuncs(o.second_x, o.second_y) : null, + msize = this.#marker_size ? Math.round(this.#marker_size / 2 + 1.5) : 0; + let findbin = null, best_dist2 = 1e10, best = null; + + this.getG().selectAll('.grpoint').each(function() { + const d = select(this).datum(); + if (d === undefined) + return; + let dist2 = (pnt.x - d.grx1) ** 2; + if (pnt.nproc === 1) + dist2 += (pnt.y - d.gry1) ** 2; + if (dist2 >= best_dist2) + return; + + let rect; + + if (d.error || d.rect || d.marker) { + rect = { + x1: Math.min(-esz, d.grx0, -msize), + x2: Math.max(esz, d.grx2, msize), + y1: Math.min(-esz, d.gry2, -msize), + y2: Math.max(esz, d.gry0, msize) + }; + } else if (d.bar) { + rect = { + x1: boff - bw / 2, + x2: boff + bw / 2, + y1: 0, + y2: height - d.gry1 + }; + + if (isbar1) { + const yy0 = funcs.gry(0); + rect.y1 = (d.gry1 > yy0) ? yy0 - d.gry1 : 0; + rect.y2 = (d.gry1 > yy0) ? 0 : yy0 - d.gry1; + } + } else + rect = { x1: -5, x2: 5, y1: -5, y2: 5 }; + + const matchx = (pnt.x >= d.grx1 + rect.x1) && (pnt.x <= d.grx1 + rect.x2), + matchy = (pnt.y >= d.gry1 + rect.y1) && (pnt.y <= d.gry1 + rect.y2); + + if (matchx && (matchy || (pnt.nproc > 1))) { + best_dist2 = dist2; + findbin = this; + best = rect; + best.exact = /* matchx && */ matchy; + } + }); + + if (findbin === null) + return null; + + const d = select(findbin).datum(), + gr = this.getGraph(), + res = { + name: gr.fName, title: gr.fTitle, + x: d.grx1, y: d.gry1, + color1: this.lineatt.color, + lines: this.getTooltips(d), + rect: best, d3bin: findbin + }; + + res.user_info = { obj: gr, name: gr.fName, bin: d.indx, cont: d.y, grx: d.grx1, gry: d.gry1 }; + + if (this.fillatt?.used && !this.fillatt?.empty()) + res.color2 = this.fillatt.getFillColor(); + + if (best.exact) + res.exact = true; + res.menu = res.exact; // activate menu only when exactly locate bin + res.menu_dist = 3; // distance always fixed + res.bin = d; + res.binindx = d.indx; + + return res; + } + + /** @summary Show tooltip */ + showTooltip(hint) { + let ttrect = this.getG()?.selectChild('.tooltip_bin'); + + if (!hint || !this.getG()) { + ttrect?.remove(); + return; + } + + if (hint.usepath) + return this.showTooltipForPath(hint); + + const d = select(hint.d3bin).datum(); + + if (ttrect.empty()) { + ttrect = this.getG().append('svg:rect') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle); + } + + hint.changed = ttrect.property('current_bin') !== hint.d3bin; + + if (hint.changed) { + ttrect.attr('x', d.grx1 + hint.rect.x1) + .attr('width', hint.rect.x2 - hint.rect.x1) + .attr('y', d.gry1 + hint.rect.y1) + .attr('height', hint.rect.y2 - hint.rect.y1) + .style('opacity', '0.3') + .property('current_bin', hint.d3bin); + } + } + + /** @summary Process tooltip event */ + processTooltipEvent(pnt) { + const hint = this.extractTooltip(pnt); + if (!pnt || !pnt.disabled) + this.showTooltip(hint); + return hint; + } + + /** @summary Find best bin index for specified point */ + findBestBin(pnt) { + if (!this.#bins) + return null; + + const islines = (this.#draw_kind === 'lines'), + o = this.getOptions(), + funcs = this.get_fp().getGrFuncs(o.second_x, o.second_y); + let bestindx = -1, + bestbin = null, + bestdist = 1e10, + dist, grx, gry, n, bin; + + for (n = 0; n < this.#bins.length; ++n) { + bin = this.#bins[n]; + + grx = funcs.grx(bin.x); + gry = funcs.gry(bin.y); + + dist = (pnt.x - grx) ** 2 + (pnt.y - gry) ** 2; + + if (dist < bestdist) { + bestdist = dist; + bestbin = bin; + bestindx = n; + } + } + + // check last point + if ((bestdist > 100) && islines) + bestbin = null; + + const radius = Math.max(this.lineatt.width + 3, 4, this.#marker_size); + + if (bestbin) + bestdist = Math.sqrt((pnt.x - funcs.grx(bestbin.x)) ** 2 + (pnt.y - funcs.gry(bestbin.y)) ** 2); + + if (!islines && (bestdist > radius)) + bestbin = null; + + if (!bestbin) + bestindx = -1; + + const res = { bin: bestbin, indx: bestindx, dist: bestdist, radius: Math.round(radius) }; + + if (!bestbin && islines) { + bestdist = 1e10; + + const is_inside = (x, x1, x2) => ((x1 >= x) && (x >= x2)) || ((x1 <= x) && (x <= x2)); + + let bin0 = this.#bins[0], grx0 = funcs.grx(bin0.x), gry0, posy; + for (n = 1; n < this.#bins.length; ++n) { + bin = this.#bins[n]; + grx = funcs.grx(bin.x); + + if (is_inside(pnt.x, grx0, grx)) { + // if inside interval, check Y distance + gry0 = funcs.gry(bin0.y); + gry = funcs.gry(bin.y); + + if (Math.abs(grx - grx0) < 1) { + // very close x - check only y + posy = pnt.y; + dist = is_inside(pnt.y, gry0, gry) ? 0 : Math.min(Math.abs(pnt.y - gry0), Math.abs(pnt.y - gry)); + } else { + posy = gry0 + (pnt.x - grx0) / (grx - grx0) * (gry - gry0); + dist = Math.abs(posy - pnt.y); + } + + if (dist < bestdist) { + bestdist = dist; + res.linex = pnt.x; + res.liney = posy; + } + } + + bin0 = bin; + grx0 = grx; + } + + if (bestdist < radius * 0.5) { + res.linedist = bestdist; + res.closeline = true; + } + } + + return res; + } + + /** @summary Check editable flag for TGraph + * @desc if arg specified changes or toggles editable flag */ + testEditable(arg) { + const obj = this.getGraph(); + if (!isFunc(obj?.TestBit)) + return false; + if ((arg === 'toggle') || (arg !== undefined)) + obj.SetBit(kNotEditable, !arg); + return !obj.TestBit(kNotEditable); + } + + /** @summary Provide tooltip at specified point for path-based drawing */ + extractTooltipForPath(pnt) { + if (!this.#bins) + return null; + + const best = this.findBestBin(pnt); + + if (!best || (!best.bin && !best.closeline)) + return null; + + const islines = (this.#draw_kind === 'lines'), + ismark = (this.#draw_kind === 'mark'), + fp = this.get_fp(), + o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + gr = this.getGraph(), + res = { + name: gr.fName, title: gr.fTitle, + x: best.bin ? funcs.grx(best.bin.x) : best.linex, + y: best.bin ? funcs.gry(best.bin.y) : best.liney, + color1: this.lineatt.color, + lines: this.getTooltips(best.bin), + usepath: true, ismark, islines + }; + + res.user_info = { obj: gr, name: gr.fName, bin: 0, cont: 0, grx: res.x, gry: res.y }; + + if (best.closeline) { + res.menu = res.exact = true; + res.menu_dist = best.linedist; + } else if (best.bin) { + if (o.EF && islines) { + res.gry1 = funcs.gry(best.bin.y - best.bin.eylow); + res.gry2 = funcs.gry(best.bin.y + best.bin.eyhigh); + } else + res.gry1 = res.gry2 = funcs.gry(best.bin.y); + + + res.binindx = best.indx; + res.bin = best.bin; + res.radius = best.radius; + res.user_info.bin = best.indx; + res.user_info.cont = best.bin.y; + + res.exact = (Math.abs(pnt.x - res.x) <= best.radius) && + ((Math.abs(pnt.y - res.gry1) <= best.radius) || (Math.abs(pnt.y - res.gry2) <= best.radius)); + + res.menu = res.exact; + res.menu_dist = Math.sqrt((pnt.x - res.x) ** 2 + Math.min(Math.abs(pnt.y - res.gry1), Math.abs(pnt.y - res.gry2)) ** 2); + } + + if (this.fillatt?.used && !this.fillatt?.empty()) + res.color2 = this.fillatt.getFillColor(); + + if (!islines) { + res.color1 = this.getColor(gr.fMarkerColor); + if (!res.color2) + res.color2 = res.color1; + } + + return res; + } + + /** @summary Show tooltip for path drawing */ + showTooltipForPath(hint) { + let ttbin = this.getG()?.selectChild('.tooltip_bin'); + + if (!hint?.bin || !this.getG()) { + ttbin?.remove(); + return; + } + + if (ttbin.empty()) + ttbin = this.getG().append('svg:g').attr('class', 'tooltip_bin'); + + hint.changed = ttbin.property('current_bin') !== hint.bin; + + if (hint.changed) { + ttbin.selectAll('*').remove(); // first delete all children + ttbin.property('current_bin', hint.bin); + + if (hint.ismark) { + ttbin.append('svg:rect') + .style('pointer-events', 'none') + .call(addHighlightStyle) + .style('opacity', '0.3') + .attr('x', Math.round(hint.x - hint.radius)) + .attr('y', Math.round(hint.y - hint.radius)) + .attr('width', 2 * hint.radius) + .attr('height', 2 * hint.radius); + } else { + ttbin.append('svg:circle').attr('cy', Math.round(hint.gry1)); + if (Math.abs(hint.gry1 - hint.gry2) > 1) + ttbin.append('svg:circle').attr('cy', Math.round(hint.gry2)); + + const o = this.getOptions(), + elem = ttbin.selectAll('circle') + .attr('r', hint.radius) + .attr('cx', Math.round(hint.x)); + + if (!hint.islines) + elem.style('stroke', hint.color1 === 'black' ? 'green' : 'black').style('fill', 'none'); + else { + if (o.Line || o.Curve) + elem.call(this.lineatt.func); + else + elem.style('stroke', 'black'); + if (o.Fill) + elem.call(this.fillatt.func); + else + elem.style('fill', 'none'); + } + } + } + } + + /** @summary Check if graph moving is enabled */ + moveEnabled() { + return this.testEditable(); + } + + /** @summary Start moving of TGraph */ + moveStart(x, y, evnt) { + const o = this.getOptions(); + this.#pos_dx = this.#pos_dy = 0; + this.#move_funcs = this.get_fp().getGrFuncs(o.second_x, o.second_y); + const hint = evnt?.shiftKey ? null : this.extractTooltip({ x, y }); + if (hint?.exact && (hint.binindx !== undefined)) { + this.#move_binindx = hint.binindx; + this.#move_bin = hint.bin; + this.#move_x0 = this.#move_funcs.grx(this.#move_bin.x); + this.#move_y0 = this.#move_funcs.gry(this.#move_bin.y); + } else + this.#move_binindx = undefined; + } + + /** @summary Perform moving */ + moveDrag(dx, dy) { + this.#pos_dx += dx; + this.#pos_dy += dy; + + if (this.#move_binindx === undefined) + makeTranslate(this.getG(), this.#pos_dx, this.#pos_dy); + else if (this.#move_funcs && this.#move_bin) { + this.#move_bin.x = this.#move_funcs.revertAxis('x', this.#move_x0 + this.#pos_dx); + this.#move_bin.y = this.#move_funcs.revertAxis('y', this.#move_y0 + this.#pos_dy); + this.drawGraph(); + } + } + + /** @summary Complete moving */ + moveEnd(not_changed) { + const graph = this.getGraph(), last = graph?.fNpoints - 1; + let exec = ''; + + const changeBin = bin => { + exec += `SetPoint(${bin.indx},${bin.x},${bin.y});;`; + graph.fX[bin.indx] = bin.x; + graph.fY[bin.indx] = bin.y; + if ((bin.indx === 0) && this.#cutg_lastsame) { + exec += `SetPoint(${last},${bin.x},${bin.y});;`; + graph.fX[last] = bin.x; + graph.fY[last] = bin.y; + } + }; + + if (this.#move_binindx === undefined) { + this.getG().attr('transform', null); + + if (this.#move_funcs && this.#bins && !not_changed) { + for (let k = 0; k < this.#bins.length; ++k) { + const bin = this.#bins[k]; + bin.x = this.#move_funcs.revertAxis('x', this.#move_funcs.grx(bin.x) + this.#pos_dx); + bin.y = this.#move_funcs.revertAxis('y', this.#move_funcs.gry(bin.y) + this.#pos_dy); + changeBin(bin); + } + if (graph.$redraw_pad) + this.redrawPad(); + else + this.drawGraph(); + } + } else { + changeBin(this.#move_bin); + this.#move_binindx = undefined; + if (graph.$redraw_pad) + this.redrawPad(); + } + + this.#move_funcs = undefined; + + if (exec && !not_changed) + this.submitCanvExec(exec); + } + + /** @summary Fill option object used in TWebCanvas */ + fillWebObjectOptions(res) { + if (this.#auto_exec && res) { + res.fcust = 'auto_exec:' + this.#auto_exec; + this.#auto_exec = undefined; + } + } + + /** @summary Fill context menu */ + fillContextMenuItems(menu) { + if (!this.hasSnapId()) { + menu.addchk(this.testEditable(), 'Editable', () => { + this.testEditable('toggle'); + this.drawGraph(); + }); + if (this.axes_draw) { + menu.add('Title', () => menu.input('Enter graph title', this.getObject().fTitle).then(res => { + this.getObject().fTitle = res; + const hist_painter = this.getMainPainter(); + if (hist_painter?.isSecondary(this)) { + setHistogramTitle(hist_painter.getHisto(), res); + this.interactiveRedraw('pad'); + } + })); + } + menu.addRedrawMenu(this.getPrimary()); + } + } + + /** @summary Execute menu command + * @private */ + executeMenuCommand(method, args) { + if (super.executeMenuCommand(method, args)) + return true; + + const canp = this.getCanvPainter(), fp = this.get_fp(); + + if ((method.fName === 'RemovePoint') || (method.fName === 'InsertPoint')) { + if (!canp || canp.isReadonly()) + return true; // ignore function + + const pnt = isFunc(fp?.getLastEventPos) ? fp.getLastEventPos() : null, + hint = this.extractTooltip(pnt); + + if (method.fName === 'InsertPoint') { + if (pnt) { + const o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + userx = funcs.revertAxis('x', pnt.x) ?? 0, + usery = funcs.revertAxis('y', pnt.y) ?? 0; + this.submitCanvExec(`AddPoint(${userx.toFixed(3)}, ${usery.toFixed(3)})`, method.$execid); + } + } else if (method.$execid && (hint?.binindx !== undefined)) + this.submitCanvExec(`RemovePoint(${hint.binindx})`, method.$execid); + + return true; // call is processed + } + + return false; + } + + /** @summary Update TGraph object members + * @private */ + _updateMembers(graph, obj) { + graph.fBits = obj.fBits; + graph.fTitle = obj.fTitle; + graph.fX = obj.fX; + graph.fY = obj.fY; + ['fEX', 'fEY', 'fExL', 'fExH', 'fEXlow', 'fEXhigh', 'fEYlow', 'fEYhigh', + 'fEXlowd', 'fEXhighd', 'fEYlowd', 'fEYhighd'].forEach(member => { + if (obj[member] !== undefined) + graph[member] = obj[member]; + }); + graph.fNpoints = obj.fNpoints; + graph.fMinimum = obj.fMinimum; + graph.fMaximum = obj.fMaximum; + + const o = this.getOptions(); + + if (this.hasSnapId()) + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + + if (!o._pfc) + graph.fFillColor = obj.fFillColor; + graph.fFillStyle = obj.fFillStyle; + if (!o._plc) + graph.fLineColor = obj.fLineColor; + graph.fLineStyle = obj.fLineStyle; + graph.fLineWidth = obj.fLineWidth; + if (!o._pmc) + graph.fMarkerColor = obj.fMarkerColor; + graph.fMarkerSize = obj.fMarkerSize; + graph.fMarkerStyle = obj.fMarkerStyle; + + return obj.fFunctions; + } + + /** @summary Update TGraph object */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; + + const o = this.getOptions(); + + if (opt && (opt !== o.original)) + this.decodeOptions(opt); + + const new_funcs = this._updateMembers(this.getObject(), obj); + + this.createBins(); + + this.#redraw_hist = undefined; + + // if our own histogram was used as axis drawing, we need update histogram as well + if (this.axes_draw) { + const histo = this.createHistogram(), + hist_painter = this.getMainPainter(); + if (hist_painter?.isSecondary(this)) { + hist_painter.updateObject(histo, o.Axis); + this.#redraw_hist = true; + } + } + + this.#funcs_handler = new FunctionsHandler(this, this.getPadPainter(), new_funcs); + + return true; + } + + /** @summary Checks if it makes sense to zoom inside specified axis range + * @desc allow to zoom TGraph only when at least one point in the range */ + canZoomInside(axis, min, max) { + const gr = this.getGraph(); + if (!gr || ((axis !== 'x') && (axis !== 'y'))) + return false; + + let arr = gr.fX; + if (this.isScatter()) + arr = (axis === 'x') ? gr.fX : gr.fY; + else if (axis !== (this.getOptions().pos3d ? 'y' : 'x')) + return false; + + for (let n = 0; n < gr.fNpoints; ++n) { + if ((min < arr[n]) && (arr[n] < max)) + return true; + } + + return false; + } + + /** @summary Process click on graph-defined buttons */ + clickButton(funcname) { + if (funcname !== 'ToggleZoom') + return false; + + if ((this.xmin === this.xmax) && (this.ymin === this.ymax)) + return false; + + return this.getFramePainter()?.zoom(this.xmin, this.xmax, this.ymin, this.ymax); + } + + /** @summary Find TF1/TF2 in TGraph list of functions */ + findFunc() { + return this.getGraph()?.fFunctions?.arr?.find(func => (func._typename === clTF1) || (func._typename === clTF2)); + } + + /** @summary Find stat box in TGraph list of functions */ + findStat() { + return this.getGraph()?.fFunctions?.arr?.find(func => (func._typename === clTPaveStats) && (func.fName === 'stats')); + } + + /** @summary Create stat box */ + createStat() { + const func = this.findFunc(); + if (!func) + return null; + + let stats = this.findStat(); + if (stats) + return stats; + + const st = gStyle; + + // do not create stats box when drawing canvas + if (!st.fOptFit || this.getCanvPainter()?.getRootPad(true)?.fPrimitives?.arr.length) + return null; + + stats = create$1(clTPaveStats); + Object.assign(stats, { + fName: 'stats', fOptStat: 0, fOptFit: st.fOptFit, fBorderSize: 1, + fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, + fFillColor: st.fStatColor, fFillStyle: st.fStatStyle + }); + + stats.fTextAngle = 0; + stats.fTextSize = st.fStatFontSize; // 9 ?? + stats.fTextAlign = 12; + stats.fTextColor = st.fStatTextColor; + stats.fTextFont = st.fStatFont; + + stats.AddText(func.fName); + + // while TF1 was found, one can be sure that stats is existing + this.getGraph().fFunctions.Add(stats); + + return stats; + } + + /** @summary Fill statistic */ + fillStatistic(stat, _dostat, dofit) { + const func = this.findFunc(); + if (!func || !dofit) + return false; + + stat.clearPave(); + stat.fillFunctionStat(func, (dofit === 1) ? 111 : dofit, 1); + return true; + } + + /** @summary Draw axis histogram + * @private */ + async drawAxisHisto() { + const set_x = this.isDummyHistogram('x'), + set_y = this.isDummyHistogram('y'), + histo = this.createHistogram(set_x, set_y); + return TH1Painter$2.draw(this.getDrawDom(), histo, this.getOptions().Axis); + } + + /** @summary Draw TGraph + * @private */ + static async _drawGraph(painter, opt) { + painter.decodeOptions(opt, true); + painter.createBins(); + painter.createStat(); + const graph = painter.getGraph(); + if (!settings.DragGraphs) + graph?.SetBit(kNotEditable, true); + + let promise = Promise.resolve(); + + if ((!painter.getMainPainter() || painter.options.second_x || painter.options.second_y) && painter.options.Axis) { + promise = painter.drawAxisHisto().then(hist_painter => { + hist_painter?.setSecondaryId(painter, 'hist'); + painter.axes_draw = Boolean(hist_painter); + }); + } + + return promise.then(() => { + painter.addToPadPrimitives(); + return painter.drawGraph(); + }).then(() => { + const handler = new FunctionsHandler(painter, painter.getPadPainter(), graph.fFunctions, true); + return handler.drawNext(0); // returns painter + }); + } + + /** @summary Draw TGraph in 2D only */ + static async draw(dom, graph, opt) { + return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); + } + +}; // class TGraphPainter + +var TGraphPainter$2 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TGraphPainter: TGraphPainter$1, +clTGraphAsymmErrors: clTGraphAsymmErrors +}); + +class TGraphPainter extends TGraphPainter$1 { + + /** @summary Draw TGraph points in 3D + * @private */ + drawBins3D(fp, graph) { + if (!fp.mode3d || !fp.grx || !fp.gry || !fp.grz || !fp.toplevel) + return console.log('Frame painter missing base 3d elements'); + + const o = this.getOptions(); + if ((fp.zoom_xmin !== fp.zoom_xmax) && ((o.pos3d < fp.zoom_xmin) || (o.pos3d > fp.zoom_xmax))) + return; + + this.createGraphDrawAttributes(true); + + const drawbins = this.optimizeBins(1000); + let first = 0, last = drawbins.length - 1; + + if (fp.zoom_ymin !== fp.zoom_ymax) { + while ((first < last) && (drawbins[first].x < fp.zoom_ymin)) + first++; + while ((first < last) && (drawbins[last].x > fp.zoom_ymax)) + last--; + } + + if (first === last) + return; + + const pnts = [], grx = fp.grx(o.pos3d); + let p0 = drawbins[first]; + + for (let n = first + 1; n <= last; ++n) { + const p1 = drawbins[n]; + pnts.push(grx, fp.gry(p0.x), fp.grz(p0.y), + grx, fp.gry(p1.x), fp.grz(p1.y)); + p0 = p1; + } + + const lines = createLineSegments(pnts, create3DLineMaterial(this, graph)); + + fp.add3DMesh(lines, this, true); + + fp.render3D(100); + } + + /** @summary Draw axis histogram + * @private */ + async drawAxisHisto() { + return TH1Painter.draw(this.getDrawDom(), this.createHistogram(), this.getOptions().Axis); + } + + /** @summary Draw TGraph */ + static async draw(dom, graph, opt) { + return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); + } + +} // class TGraphPainter + +class RTreeMapTooltip { + + static CONSTANTS = { DELAY: 0, OFFSET_X: 10, OFFSET_Y: -10, PADDING: 8, BORDER_RADIUS: 4 }; + + constructor(painter) + { + this.painter = painter; + this.tooltip = null; + this.content = ''; + this.x = 0; + this.y = 0; + } + + cleanup() { + if (this.tooltip !== null) { + document.body.removeChild(this.tooltip); + this.tooltip = null; + } + } + + createTooltip() + { + if (this.tooltip) + return; + + this.tooltip = document.createElement('div'); + this.tooltip.style.cssText = ` + position: absolute; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: ${RTreeMapTooltip.CONSTANTS.PADDING}px; + border-radius: ${RTreeMapTooltip.CONSTANTS.BORDER_RADIUS}px; + font-size: 12px; + pointer-events: none; + z-index: 10000; + opacity: 0; + transition: opacity 0.2s; + max-width: 200px; + word-wrap: break-word; + `; + document.body.appendChild(this.tooltip); + } + + showTooltip() + { + if (!this.tooltip) + this.createTooltip(); + + this.tooltip.innerHTML = this.content; + this.tooltip.style.left = (this.x + RTreeMapTooltip.CONSTANTS.OFFSET_X) + 'px'; + this.tooltip.style.top = (this.y + RTreeMapTooltip.CONSTANTS.OFFSET_Y) + 'px'; + this.tooltip.style.opacity = '1'; + } + + hideTooltip() + { + if (this.tooltip) + this.tooltip.style.opacity = '0'; + } + + generateTooltipContent(node) + { + const isLeaf = node.fNChildren === 0; + let content = (node.fName.length > 0) ? `${node.fName}
` : ''; + + content += `${(isLeaf ? 'Column' : 'Field')}
`; + content += `Size: ${this.painter.getDataStr(node.fSize)}
`; + + if (isLeaf && node.fType !== undefined) + content += `Type: ${node.fType}
`; + + if (!isLeaf) + content += `Children: ${node.fNChildren}
`; + + const obj = this.painter.getObject(); + if (obj.fNodes && obj.fNodes.length > 0) { + const totalSize = obj.fNodes[0].fSize, + percentage = ((node.fSize / totalSize) * 100).toFixed(2); + content += `Disk Usage: ${percentage}%`; + } + + return content; + } + +} + +function computeFnv(str) { + const FNV_offset = 14695981039346656037n, FNV_prime = 1099511628211n; + let h = FNV_offset; + for (let i = 0; i < str.length; ++i) { + const octet = BigInt(str.charCodeAt(i) & 0xFF); + h ^= octet; + h *= FNV_prime; + } + return h; +} + +class RTreeMapPainter extends ObjectPainter { + + static CONSTANTS = { + STROKE_WIDTH: 0.15, + STROKE_COLOR: 'black', + + COLOR_HOVER_BOOST: 10, + + DATA_UNITS: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], + + MAIN_TREEMAP: { LEFT: 0.025, BOTTOM: 0.05, RIGHT: 0.825, TOP: 0.9 }, + + LEGEND: { + START_Y: 0.835, + ITEM_HEIGHT: 0.05, + BOX_WIDTH: 0.05, + TEXT_OFFSET_X: 0.01, + TEXT_OFFSET_Y: 0.01, + TEXT_LINE_SPACING: 0.015, + MAX_ITEMS: 10 + }, + + TEXT: { SIZE_VW: 0.6, MIN_RECT_WIDTH: 0.025, MIN_RECT_HEIGHT: 0.05, PADDING: 10, LEAF_OFFSET_Y: 0.015 }, + + INDENT: 0.005, + LEGEND_INDENT_MULTIPLIER: 4 + }; + + constructor(dom, obj, opt) { + super(dom, obj, opt); + this.tooltip = new RTreeMapTooltip(this); + this.rootIndex = 0; + this.parentIndices = []; + } + + cleanup() { + if (this._frame_hidden) { + delete this._frame_hidden; + this.getPadPainter()?.getFrameSvg().style('display', null); + } + + this.tooltip.cleanup(); + super.cleanup(); + } + + appendRect(begin, end, color, strokeColor = RTreeMapPainter.CONSTANTS.STROKE_COLOR, strokeWidth = RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node = null) { + const rect = this.getG() + .append('rect') + .attr('x', this.axisToSvg('x', begin.x, this.isndc)) + .attr('y', this.axisToSvg('y', begin.y, this.isndc)) + .attr('width', `${Math.abs(end.x - begin.x) * 100}%`) + .attr('height', `${Math.abs(end.y - begin.y) * 100}%`) + .attr('fill', color) + .attr('stroke', strokeColor) + .attr('stroke-width', strokeWidth) + .attr('pointer-events', 'fill'); + + if (node) { + rect.datum(node); + this.attachPointerEventsTreeMap(rect, node); + } + return rect; + } + + appendText(content, pos, size, color, anchor = 'start') { + return this.getG() + .append('text') + .attr('x', this.axisToSvg('x', pos.x, this.isndc)) + .attr('y', this.axisToSvg('y', pos.y, this.isndc)) + .attr('font-size', `${size}vw`) + .attr('fill', color) + .attr('text-anchor', anchor) + .attr('pointer-events', 'none') + .text(content); + } + + getRgbList(rgbStr) { return rgbStr.slice(4, -1).split(',').map((x) => parseInt(x)); } + + toRgbStr(rgbList) { return `rgb(${rgbList.join()})`; } + + attachPointerEventsTreeMap(element, node) { + const original_color = element.attr('fill'), hovered_color = this.toRgbStr(this.getRgbList(original_color) + .map((color) => Math.min(color + RTreeMapPainter.CONSTANTS.COLOR_HOVER_BOOST, 255))), + mouseenter = () => { + element.attr('fill', hovered_color); + this.tooltip.content = this.tooltip.generateTooltipContent(node); + this.tooltip.x = 0; + this.tooltip.y = 0; + }, + mouseleave = () => { + element.attr('fill', original_color); + this.tooltip.hideTooltip(); + }, + mousemove = (event) => { + this.tooltip.x = event.pageX; + this.tooltip.y = event.pageY; + this.tooltip.showTooltip(); + }, + click = () => { + const obj = this.getObject(), nodeIndex = obj.fNodes.findIndex((elem) => elem === node); + if (nodeIndex === this.rootIndex) + this.rootIndex = this.parentIndices[nodeIndex]; + else { + let parentIndex = nodeIndex; + while (this.parentIndices[parentIndex] !== this.rootIndex) + parentIndex = this.parentIndices[parentIndex]; + this.rootIndex = parentIndex; + if (obj.fNodes[parentIndex].fNChildren === 0) + this.rootIndex = this.parentIndices[nodeIndex]; + } + this.redraw(); + }; + this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove, click }); + } + + attachPointerEventsLegend(element, type) { + const rects = this.getG().selectAll('rect'), + mouseenter = () => { rects.filter((node) => node !== undefined && node.fType !== type).attr('opacity', '0.5'); }, + mouseleave = () => { rects.attr('opacity', '1'); }; + this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove: () => {}, click: () => {} }); + } + + attachPointerEvents(element, events) { + for (const [key, value] of Object.entries(events)) + element.on(key, value); + } + + computeColor(n) { + const hash = Number(computeFnv(String(n)) & 0xFFFFFFFFn), + r = (hash >> 16) & 0xFF, + g = (hash >> 8) & 0xFF, + b = hash & 0xFF; + return this.toRgbStr([r, g, b]); + } + + getDataStr(bytes) { + const units = RTreeMapPainter.CONSTANTS.DATA_UNITS, + order = Math.floor(Math.log10(bytes) / 3), + finalSize = bytes / Math.pow(1000, order); + return `${finalSize.toFixed(2)}${units[order]}`; + } + + computeWorstRatio(row, width, height, totalSize, horizontalRows) { + if (row.length === 0) + return 0; + + const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); + if (sumRow === 0) + return 0; + + let worstRatio = 0; + for (const child of row) { + const ratio = horizontalRows ? (child.fSize * width * totalSize) / (sumRow * sumRow * height) + : (child.fSize * height * totalSize) / (sumRow * sumRow * width), + aspectRatio = Math.max(ratio, 1 / ratio); + if (aspectRatio > worstRatio) + worstRatio = aspectRatio; + } + return worstRatio; + } + + squarifyChildren(children, rect, horizontalRows, totalSize) { + const width = rect.topRight.x - rect.bottomLeft.x, + height = rect.topRight.y - rect.bottomLeft.y, + remaining = [...children].sort((a, b) => b.fSize - a.fSize), + result = [], + remainingBegin = { ...rect.bottomLeft }; + + while (remaining.length > 0) { + const row = []; + let currentWorstRatio = Infinity; + const remainingWidth = rect.topRight.x - remainingBegin.x, + remainingHeight = rect.topRight.y - remainingBegin.y; + + if (remainingWidth <= 0 || remainingHeight <= 0) + break; + + while (remaining.length > 0) { + row.push(remaining.shift()); + const newWorstRatio = + this.computeWorstRatio(row, remainingWidth, remainingHeight, totalSize, horizontalRows); + if (newWorstRatio > currentWorstRatio) { + remaining.unshift(row.pop()); + break; + } + currentWorstRatio = newWorstRatio; + } + + const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); + if (sumRow === 0) + continue; + + const dimension = horizontalRows ? (sumRow / totalSize * height) : (sumRow / totalSize * width); + let position = 0; + + for (const child of row) { + const childDimension = child.fSize / sumRow * (horizontalRows ? width : height), + childBegin = horizontalRows ? { x: remainingBegin.x + position, y: remainingBegin.y } + : { x: remainingBegin.x, y: remainingBegin.y + position }, + childEnd = horizontalRows + ? { x: remainingBegin.x + position + childDimension, y: remainingBegin.y + dimension } + : { x: remainingBegin.x + dimension, y: remainingBegin.y + position + childDimension }; + + result.push({ node: child, rect: { bottomLeft: childBegin, topRight: childEnd } }); + position += childDimension; + } + + if (horizontalRows) + remainingBegin.y += dimension; + else + remainingBegin.x += dimension; + } + return result; + } + + drawLegend() { + const obj = this.getObject(), + diskMap = {}; + + let stack = [this.rootIndex]; + while (stack.length > 0) { + const node = obj.fNodes[stack.pop()]; + if (node.fNChildren === 0) + diskMap[node.fType] = (diskMap[node.fType] || 0) + node.fSize; + stack = stack.concat(Array.from({ length: node.fNChildren }, (_, a) => a + node.fChildrenIdx)); + } + + const diskEntries = Object.entries(diskMap) + .sort((a, b) => b[1] - a[1]) + .slice(0, RTreeMapPainter.CONSTANTS.LEGEND.MAX_ITEMS) + .filter(([, size]) => size > 0), + + legend = RTreeMapPainter.CONSTANTS.LEGEND; + + diskEntries.forEach(([typeName, size], index) => { + const posY = legend.START_Y - index * legend.ITEM_HEIGHT, + posX = legend.START_Y + legend.ITEM_HEIGHT + legend.TEXT_OFFSET_X, + textSize = RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, + + rect = this.appendRect({ x: legend.START_Y, y: posY }, + { x: legend.START_Y + legend.ITEM_HEIGHT, y: posY - legend.ITEM_HEIGHT }, + this.computeColor(typeName)); + this.attachPointerEventsLegend(rect, typeName); + + const diskOccupPercent = `${(size / obj.fNodes[this.rootIndex].fSize * 100).toFixed(2)}%`, + diskOccup = `(${this.getDataStr(size)} / ${this.getDataStr(obj.fNodes[this.rootIndex].fSize)})`; + + [typeName, diskOccup, diskOccupPercent].forEach( + (content, i) => + this.appendText(content, { x: posX, y: posY - legend.TEXT_OFFSET_Y - legend.TEXT_LINE_SPACING * (i) }, + textSize, 'black')); + }); + } + + trimText(textElement, rect) { + const nodeElem = textElement.node(); + let textContent = nodeElem.textContent; + const availablePx = Math.abs(this.axisToSvg('x', rect.topRight.x, this.isndc) - + this.axisToSvg('x', rect.bottomLeft.x, this.isndc)) - + RTreeMapPainter.CONSTANTS.TEXT.PADDING; + + while (nodeElem.getComputedTextLength && nodeElem.getComputedTextLength() > availablePx && textContent.length > 0) { + textContent = textContent.slice(0, -1); + nodeElem.textContent = textContent + '…'; + } + return textContent; + } + + drawTreeMap(node, rect, depth = 0) { + const isLeaf = node.fNChildren === 0, + color = isLeaf ? this.computeColor(node.fType) : 'rgb(100,100,100)'; + this.appendRect({ x: rect.bottomLeft.x, y: rect.topRight.y }, { x: rect.topRight.x, y: rect.bottomLeft.y }, color, + RTreeMapPainter.CONSTANTS.STROKE_COLOR, RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node); + + const rectWidth = rect.topRight.x - rect.bottomLeft.x, + rectHeight = rect.topRight.y - rect.bottomLeft.y, + labelBase = `${node.fName} (${this.getDataStr(node.fSize)})`, + + textConstants = RTreeMapPainter.CONSTANTS.TEXT, + textSize = (rectWidth <= textConstants.MIN_RECT_WIDTH || rectHeight <= textConstants.MIN_RECT_HEIGHT) + ? 0 + : textConstants.SIZE_VW; + + if (textSize > 0) { + const textElement = this.appendText(labelBase, { + x: rect.bottomLeft.x + (isLeaf ? rectWidth / 2 : RTreeMapPainter.CONSTANTS.INDENT), + y: isLeaf ? (rect.bottomLeft.y + rect.topRight.y) / 2 : (rect.topRight.y - textConstants.LEAF_OFFSET_Y) + }, + textSize, 'white', isLeaf ? 'middle' : 'start'); + textElement.textContent = this.trimText(textElement, rect); + } + + if (!isLeaf && node.fNChildren > 0) { + const obj = this.getObject(), + children = obj.fNodes.slice(node.fChildrenIdx, node.fChildrenIdx + node.fNChildren), + totalSize = children.reduce((sum, child) => sum + child.fSize, 0); + + if (totalSize > 0) { + const indent = RTreeMapPainter.CONSTANTS.INDENT, + innerRect = { + bottomLeft: { x: rect.bottomLeft.x + indent, y: rect.bottomLeft.y + indent }, + topRight: { + x: rect.topRight.x - indent, + y: rect.topRight.y - indent * RTreeMapPainter.CONSTANTS.LEGEND_INDENT_MULTIPLIER + } + }, + + width = innerRect.topRight.x - innerRect.bottomLeft.x, + height = innerRect.topRight.y - innerRect.bottomLeft.y, + horizontalRows = width > height, + + rects = this.squarifyChildren(children, innerRect, horizontalRows, totalSize); + rects.forEach( + ({ node: childNode, rect: childRect }) => { this.drawTreeMap(childNode, childRect, depth + 1); }); + } + } + } + + createParentIndices() { + const obj = this.getObject(); + this.parentIndices = new Array(obj.fNodes.length).fill(0); + obj.fNodes.forEach((node, index) => { + for (let i = node.fChildrenIdx; i < node.fChildrenIdx + node.fNChildren; i++) + this.parentIndices[i] = index; + }); + } + + getDirectory() { + const obj = this.getObject(); + let result = '', + currentIndex = this.rootIndex; + while (currentIndex !== 0) { + result = obj.fNodes[currentIndex].fName + '/' + result; + currentIndex = this.parentIndices[currentIndex]; + } + return result; + } + + redraw() { + const svg = this.getPadPainter().getFrameSvg(); + if (!svg.empty()) { + svg.style('display', 'none'); + this._frame_hidden = true; + } + + const obj = this.getObject(); + this.createG(); + this.isndc = true; + + if (obj.fNodes && obj.fNodes.length > 0) { + this.createParentIndices(); + const mainArea = RTreeMapPainter.CONSTANTS.MAIN_TREEMAP; + this.drawTreeMap( + obj.fNodes[this.rootIndex], + { bottomLeft: { x: mainArea.LEFT, y: mainArea.BOTTOM }, topRight: { x: mainArea.RIGHT, y: mainArea.TOP } }); + this.drawLegend(); + this.appendText(this.getDirectory(), { x: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.LEFT, y: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.TOP + 0.01 }, + RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, 'black'); + } + return this; + } + + static async draw(dom, obj, opt) { + const painter = new RTreeMapPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } + +} // class RTreeMapPainter + +var RTreeMapPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +RTreeMapPainter: RTreeMapPainter +}); + +// CSG library for THREE.js + + +const EPSILON = 1e-5, + COPLANAR = 0, + FRONT = 1, + BACK = 2, + SPANNING = FRONT | BACK; + +class Vertex { + + constructor(x, y, z, nx, ny, nz) { + this.x = x; + this.y = y; + this.z = z; + this.nx = nx; + this.ny = ny; + this.nz = nz; + } + + setnormal(nx, ny, nz) { + this.nx = nx; + this.ny = ny; + this.nz = nz; + } + + clone() { + return new Vertex(this.x, this.y, this.z, this.nx, this.ny, this.nz); + } + + add(vertex) { + this.x += vertex.x; + this.y += vertex.y; + this.z += vertex.z; + return this; + } + + subtract(vertex) { + this.x -= vertex.x; + this.y -= vertex.y; + this.z -= vertex.z; + return this; + } + + // multiplyScalar( scalar ) { + // this.x *= scalar; + // this.y *= scalar; + // this.z *= scalar; + // return this; + // } + + // cross( vertex ) { + // let x = this.x, y = this.y, z = this.z, + // vx = vertex.x, vy = vertex.y, vz = vertex.z; + // + // this.x = y * vz - z * vy; + // this.y = z * vx - x * vz; + // this.z = x * vy - y * vx; + // + // return this; + // } + + cross3(vx, vy, vz) { + const x = this.x, y = this.y, z = this.z; + + this.x = y * vz - z * vy; + this.y = z * vx - x * vz; + this.z = x * vy - y * vx; + + return this; + } + + + normalize() { + const length = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2); + + this.x /= length; + this.y /= length; + this.z /= length; + + return this; + } + + dot(vertex) { + return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; + } + + diff(vertex) { + const dx = (this.x - vertex.x), + dy = (this.y - vertex.y), + dz = (this.z - vertex.z), + len2 = this.x ** 2 + this.y ** 2 + this.z ** 2; + + return (dx ** 2 + dy ** 2 + dz ** 2) / (len2 > 0 ? len2 : 1e-10); + } + + /* + lerp( a, t ) { + this.add( + a.clone().subtract( this ).multiplyScalar( t ) + ); + + this.normal.add( + a.normal.clone().sub( this.normal ).multiplyScalar( t ) + ); + + //this.uv.add( + // a.uv.clone().sub( this.uv ).multiplyScalar( t ) + //); + + return this; + }; + + interpolate( other, t ) { + return this.clone().lerp( other, t ); + }; +*/ + + interpolate(a, t) { + const 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); + } + + applyMatrix4(m) { + // input: Matrix4 affine matrix + + let x = this.x, y = this.y, z = this.z; + const 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]; + + 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; + + return this; + } + +} // class Vertex + + +let Polygon$1 = class Polygon { + + constructor(vertices, parent, more) { + this.vertices = vertices || []; + this.nsign = 1; + if (parent) + this.copyProperties(parent, more); + else if (this.vertices.length) + this.calculateProperties(); + } + + copyProperties(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; + } + + calculateProperties(force) { + if (this.normal && !force) + return; + + const 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.normal = new Vertex(b.x - a.x, b.y - a.y, b.z - a.z, 0, 0, 0).cross3(c.x - a.x, c.y - a.y, c.z - a.z).normalize(); + + this.w = this.normal.dot(a); + return this; + } + + clone() { + const vertice_count = this.vertices.length, + vertices = []; + + for (let i = 0; i < vertice_count; ++i) + vertices.push(this.vertices[i].clone()); + + return new Polygon(vertices, this); + } + + flip() { + // normal is not changed, only sign variable + // this.normal.multiplyScalar( -1 ); + // this.w *= -1; + + this.nsign *= -1; + + this.vertices.reverse(); + + return this; + } + + classifyVertex(vertex) { + const side_value = this.nsign * (this.normal.dot(vertex) - this.w); + if (side_value < -EPSILON) + return BACK; + if (side_value > EPSILON) + return FRONT; + return COPLANAR; + } + + classifySide(polygon) { + let num_positive = 0, num_negative = 0; + const vertice_count = polygon.vertices.length; + + for (let i = 0; i < vertice_count; ++i) { + const classification = this.classifyVertex(polygon.vertices[i]); + if (classification === FRONT) + ++num_positive; + else if (classification === BACK) + ++num_negative; + } + + 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; + } + + splitPolygon(polygon, coplanar_front, coplanar_back, front, back) { + const 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 { + const vertice_count = polygon.vertices.length, + nnx = this.normal.x, + nny = this.normal.y, + nnz = this.normal.z, + f = [], b = []; + let i, j, ti, tj, vi, vj, t, v; + + 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 Polygon(f).calculateProperties()); + // if ( b.length >= 3 ) + // back.push(new Polygon(b).calculateProperties()); + if (f.length >= 3) + front.push(new Polygon(f, polygon, true)); + if (b.length >= 3) + back.push(new Polygon(b, polygon, true)); + } + } + +}; // class Polygon + + +class Node { + + constructor(polygons, nodeid) { + this.polygons = []; + this.front = this.back = undefined; + + if (!polygons) + return; + + this.divider = polygons[0].clone(); + + const polygon_count = polygons.length, + front = [], back = []; + + for (let i = 0; i < polygon_count; ++i) { + if (nodeid !== undefined) { + polygons[i].id = nodeid++; + delete polygons[i].parent; + } + + // by definition polygon should be COPLANAR for itself + if (i === 0) + this.polygons.push(polygons[0]); + else + this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + } + + if (nodeid !== undefined) + this.maxnodeid = nodeid; + + if (front.length) + this.front = new Node(front); + + if (back.length) + this.back = new Node(back); + } + + // isConvex(polygons) { + // let 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; + // } + + build(polygons) { + const polygon_count = polygons.length, + front = [], back = []; + let first = 0; + + if (!this.divider) { + this.divider = polygons[0].clone(); + this.polygons.push(polygons[0]); + first = 1; + } + + for (let i = first; i < polygon_count; ++i) + this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + + if (front.length) { + if (!this.front) + this.front = new Node(); + this.front.build(front); + } + + if (back.length) { + if (!this.back) + this.back = new Node(); + this.back.build(back); + } + } + + collectPolygons(arr) { + if (arr === undefined) + arr = []; + const len = this.polygons.length; + for (let i = 0; i < len; ++i) + arr.push(this.polygons[i]); + this.front?.collectPolygons(arr); + this.back?.collectPolygons(arr); + return arr; + } + + numPolygons() { + return this.polygons.length + (this.front?.numPolygons() || 0) + (this.back?.numPolygons() || 0); + } + + clone() { + const node = new Node(); + + node.divider = this.divider?.clone(); + node.polygons = this.polygons.map(polygon => polygon.clone()); + node.front = this.front?.clone(); + node.back = this.back?.clone(); + + return node; + } + + invert() { + const polygon_count = this.polygons.length; + + for (let 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(); + + const temp = this.front; + this.front = this.back; + this.back = temp; + + return this; + } + + clipPolygons(polygons) { + if (!this.divider) + return polygons.slice(); + + const polygon_count = polygons.length; + let front = [], back = []; + + for (let i = 0; i < polygon_count; ++i) + this.divider.splitPolygon(polygons[i], front, back, front, back); + + if (this.front) + front = this.front.clipPolygons(front); + + back = this.back?.clipPolygons(back) ?? []; + + return front.concat(back); + } + + clipTo(node) { + this.polygons = node.clipPolygons(this.polygons); + this.front?.clipTo(node); + this.back?.clipTo(node); + } + +} // class Node + + +function createBufferGeometry(polygons) { + const polygon_count = polygons.length; + let i, j, buf_size = 0; + + for (i = 0; i < polygon_count; ++i) + buf_size += (polygons[i].vertices.length - 2) * 9; + + const positions_buf = new Float32Array(buf_size), + normals_buf = new Float32Array(buf_size); + let 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]); + } + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); + geometry.setAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); + + // geometry.computeVertexNormals(); + return geometry; +} + + +class Geometry { + + constructor(geometry, transfer_matrix, nodeid, flippedMesh) { + // Convert BufferGeometry to ThreeBSP + + 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 Matrix4; + return; + } else if (geometry instanceof THREE.BufferGeometry) { + const pos_buf = geometry.getAttribute('position').array, + norm_buf = geometry.getAttribute('normal').array, + polygons = []; + let polygon, vert1, vert2, vert3; + + for (let i = 0; i < pos_buf.length; i += 9) { + polygon = new Polygon$1(); + + 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); + + 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); + + 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); + + if (flippedMesh) + polygon.vertices.push(vert1, vert3, vert2); + else + polygon.vertices.push(vert1, vert2, vert3); + + polygon.calculateProperties(true); + polygons.push(polygon); + } + + this.tree = new Node(polygons, nodeid); + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; + return; + } else if (geometry.polygons && (geometry.polygons[0] instanceof Polygon$1)) { + const polygons = geometry.polygons; + + for (let i = 0; i < polygons.length; ++i) { + const polygon = polygons[i]; + if (transfer_matrix) { + const new_vertices = []; + + for (let n = 0; n < polygon.vertices.length; ++n) + new_vertices.push(polygon.vertices[n].clone().applyMatrix4(transfer_matrix)); + + polygon.vertices = new_vertices; + } + + polygon.calculateProperties(transfer_matrix); + } + + this.tree = new Node(polygons, nodeid); + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; + return; + } else + throw Error('ThreeBSP: Given geometry is unsupported'); + + const polygons = [], nfaces = geometry.faces.length; + let face, polygon, vertex, normal, useVertexNormals; + + for (let i = 0; i < nfaces; ++i) { + face = geometry.faces[i]; + normal = face.normal; + // faceVertexUvs = geometry.faceVertexUvs[0][i]; + polygon = new Polygon$1(); + + useVertexNormals = face.vertexNormals && (face.vertexNormals.length === 3); + + vertex = geometry.vertices[face.a]; + if (useVertexNormals) + normal = face.vertexNormals[0]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.b]; + if (useVertexNormals) + normal = face.vertexNormals[1]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.c]; + if (useVertexNormals) + normal = face.vertexNormals[2]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); + + polygon.calculateProperties(true); + polygons.push(polygon); + } + + this.tree = new Node(polygons, nodeid); + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; + } + + subtract(other_tree) { + let a = this.tree.clone(); + const b = other_tree.tree.clone(); + + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons()); + a.invert(); + a = new Geometry(a); + a.matrix = this.matrix; + return a; + } + + union(other_tree) { + let a = this.tree.clone(); + const b = other_tree.tree.clone(); + + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons()); + a = new Geometry(a); + a.matrix = this.matrix; + return a; + } + + intersect(other_tree) { + let a = this.tree.clone(); + const b = other_tree.tree.clone(); + + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.collectPolygons()); + a.invert(); + a = new Geometry(a); + a.matrix = this.matrix; + return a; + } + + tryToCompress(polygons) { + if (this.maxid === undefined) + return; + + const arr = []; + let 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?.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); + + 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]); + } + } + } + } + } + + direct_subtract(other_tree) { + const 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; + } + + direct_union(other_tree) { + const 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; + } + + direct_intersect(other_tree) { + const 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; + } + + cut_from_plane(other_tree) { + // just cut peaces from second geometry, which just simple plane + + const a = this.tree, + b = other_tree.tree; + + a.invert(); + b.clipTo(a); + + return this; + } + + scale(x, y, z) { + // try to scale as BufferGeometry + const polygons = this.tree.collectPolygons(); + + for (let i = 0; i < polygons.length; ++i) { + const polygon = polygons[i]; + for (let k = 0; k < polygon.vertices.length; ++k) { + const v = polygon.vertices[k]; + v.x *= x; + v.y *= y; + v.z *= z; + } + polygon.calculateProperties(true); + } + } + + toPolygons() { + const polygons = this.tree.collectPolygons(); + + this.tryToCompress(polygons); + + for (let i = 0; i < polygons.length; ++i) { + delete polygons[i].id; + delete polygons[i].parent; + } + + return polygons; + } + + toBufferGeometry() { + return createBufferGeometry(this.toPolygons()); + } + + toMesh(material) { + const geometry = this.toBufferGeometry(), + mesh = new THREE.Mesh(geometry, material); + + if (this.matrix) { + mesh.position.setFromMatrixPosition(this.matrix); + mesh.rotation.setFromRotationMatrix(this.matrix); + } + + return mesh; + } + +} // class Geometry + +/** @summary create geometry to make cut on specified axis + * @private */ +function createNormal(axis_name, pos, size) { + if (!size || (size < 10000)) + size = 10000; + + let vertices; + + switch (axis_name) { + case 'x': + vertices = [new Vertex(pos, -3 * size, size, 1, 0, 0), + new Vertex(pos, size, -3 * size, 1, 0, 0), + new Vertex(pos, size, size, 1, 0, 0)]; + break; + case 'y': + vertices = [new Vertex(-3 * size, pos, size, 0, 1, 0), + new Vertex(size, pos, size, 0, 1, 0), + new Vertex(size, pos, -3 * size, 0, 1, 0)]; + break; + // case 'z': + default: + vertices = [new Vertex(-3 * size, size, pos, 0, 0, 1), + new Vertex(size, -3 * size, pos, 0, 0, 1), + new Vertex(size, size, pos, 0, 0, 1)]; + } + + const node = new Node([new Polygon$1(vertices)]); + + return new Geometry(node); +} + +const _cfg = { + GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes + CompressComp: true // use faces compression in composite shapes +}, kShapeType = '$$Shape$$'; + +/** @summary Returns or set geometry config values + * @desc Supported 'GradPerSegm' and 'CompressComp' + * @private */ + +function geoCfg(name, value) { + if (value === undefined) + return _cfg[name]; + + _cfg[name] = value; +} + +const kindGeo = 0, // TGeoNode / TGeoShape + kindEve = 1, // TEveShape / TEveGeoShapeExtract + kindShape = 2, // special kind for single shape handling + + kGetMesh = 'mesh', // return mesh from createObject3D + kDeleteMesh = 'delete_mesh', // delete mesh in createObject3D + + /** @summary TGeo-related bits + * @private */ + geoBITS = { + kVisNone: BIT(1), // the volume/node is invisible, as well as daughters + kVisThis: BIT(2), // this volume/node is visible + kVisDaughters: BIT(3), // all leaves are visible + kVisOnScreen: BIT(7)}, + + clTGeoBBox = 'TGeoBBox', + clTGeoArb8 = 'TGeoArb8', + clTGeoCone = 'TGeoCone', + clTGeoConeSeg = 'TGeoConeSeg', + clTGeoTube = 'TGeoTube', + clTGeoTubeSeg = 'TGeoTubeSeg', + clTGeoCtub = 'TGeoCtub', + clTGeoTrd1 = 'TGeoTrd1', + clTGeoTrd2 = 'TGeoTrd2', + clTGeoPara = 'TGeoPara', + clTGeoParaboloid = 'TGeoParaboloid', + clTGeoPcon = 'TGeoPcon', + clTGeoPgon = 'TGeoPgon', + clTGeoShapeAssembly = 'TGeoShapeAssembly', + clTGeoSphere = 'TGeoSphere', + clTGeoTorus = 'TGeoTorus', + clTGeoXtru = 'TGeoXtru', + clTGeoTrap = 'TGeoTrap', + clTGeoGtra = 'TGeoGtra', + clTGeoEltu = 'TGeoEltu', + clTGeoHype = 'TGeoHype', + clTGeoCompositeShape = 'TGeoCompositeShape', + clTGeoHalfSpace = 'TGeoHalfSpace', + clTGeoScaledShape = 'TGeoScaledShape'; + +/** @summary Test fGeoAtt bits + * @private */ +function testGeoBit(volume, f) { + const att = volume.fGeoAtt; + return att === undefined ? false : Boolean(att & f); +} + +/** @summary Set fGeoAtt bit + * @private */ +function setGeoBit(volume, f, value) { + if (volume.fGeoAtt !== undefined) + volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); +} + +/** @summary Toggle fGeoAttBit + * @private */ +function toggleGeoBit(volume, f) { + if (volume.fGeoAtt !== undefined) + volume.fGeoAtt ^= f & 0xffffff; +} + +/** @summary Implementation of TGeoVolume::InvisibleAll + * @private */ +function setInvisibleAll(volume, flag = true) { + setGeoBit(volume, geoBITS.kVisThis, !flag); + // setGeoBit(this, geoBITS.kVisDaughters, !flag); + + if (volume.fNodes) { + for (let n = 0; n < volume.fNodes.arr.length; ++n) { + const sub = volume.fNodes.arr[n].fVolume; + setGeoBit(sub, geoBITS.kVisThis, !flag); + // setGeoBit(sub, geoBITS.kVisDaughters, !flag); + } + } +} + +const _warn_msgs = {}; + +/** @summary method used to avoid duplication of warnings + * @private */ +function geoWarn(msg) { + if (_warn_msgs[msg] === undefined) { + _warn_msgs[msg] = true; + console.warn(msg); + } +} + +/** @summary Analyze TGeo node kind + * @desc 0 - TGeoNode + * 1 - TEveGeoNode + * -1 - unsupported + * @return detected node kind + * @private */ +function getNodeKind(obj) { + if (!isObject(obj)) + return -1; + return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo; +} + +/** @summary Returns number of shapes + * @desc Used to count total shapes number in composites + * @private */ +function countNumShapes(shape) { + if (!shape) + return 0; + if (shape._typename !== clTGeoCompositeShape) + return 1; + return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight); +} + + +/** @summary Returns geo object name + * @desc Can appends some special suffixes + * @private */ +function getObjectName(obj) { + return obj?.fName ? (obj.fName + (obj.$geo_suffix || '')) : ''; +} + +/** @summary Check duplicates + * @private */ +function checkDuplicates(parent, chlds) { + if (parent) { + if (parent.$geo_checked) + return; + parent.$geo_checked = true; + } + + const names = [], cnts = []; + for (let k = 0; k < chlds.length; ++k) { + const chld = chlds[k]; + if (!chld?.fName) + continue; + if (!chld.$geo_suffix) { + const indx = names.indexOf(chld.fName); + if (indx >= 0) { + let cnt = cnts[indx] || 1; + while (names.indexOf(chld.fName + '#' + cnt) >= 0) ++cnt; + chld.$geo_suffix = '#' + cnt; + cnts[indx] = cnt + 1; + } + } + names.push(getObjectName(chld)); + } +} + + +/** @summary Create normal to plane, defined with three points + * @private */ +function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) { + const 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(); + + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + + return cb; +} + +// ========================================================================== + +/** + * @summary Helper class for geometry creation + * + * @private + */ + +class GeometryCreator { + + /** @summary Constructor + * @param numfaces - number of faces */ + constructor(numfaces) { + this.nfaces = numfaces; + this.indx = 0; + this.pos = new Float32Array(numfaces * 9); + this.norm = new Float32Array(numfaces * 9); + } + + /** @summary Add face with 3 vertices */ + addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { + const 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; + } + + /** @summary Start polygon */ + startPolygon() {} + + /** @summary Stop polygon */ + stopPolygon() {} + + /** @summary Add face with 4 vertices + * @desc 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 */ + addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) { + let indx = this.indx; + const pos = this.pos; + + 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 (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; + } + + this.last4 = (indx !== this.indx + 9); + this.indx = indx; + } + + /** @summary Specify normal for face with 4 vertices + * @desc same as addFace4, assign normals for each individual vertex + * reduce has same meaning and should be the same */ + setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4, reduce) { + if (this.last4 && reduce) + return console.error('missmatch between addFace4 and setNormal4 calls'); + + let indx = this.indx - (this.last4 ? 18 : 9); + const norm = this.norm; + + 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; + } + + 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; + } + } + + /** @summary Recalculate Z with provided func */ + recalcZ(func) { + const pos = this.pos, + last = this.indx; + let indx = last - (this.last4 ? 18 : 9); + + while (indx < last) { + pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); + indx += 3; + } + } + + /** @summary Calculate normal */ + calcNormal() { + 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.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); + } + + /** @summary Set normal */ + setNormal(nx, ny, nz) { + let indx = this.indx - 9; + const 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; + } + } + + /** @summary Set normal + * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ + setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce = 0) { + let indx = this.indx - ((reduce > 0) ? 9 : 18); + const 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 !== 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; + } + } + + /** @summary Create geometry */ + create() { + if (this.nfaces !== this.indx / 9) + console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx / 9} number of faces`); + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); + geometry.setAttribute('normal', new THREE.BufferAttribute(this.norm, 3)); + return geometry; + } + +} + +// ================================================================================ + +/** @summary Helper class for CsgGeometry creation + * + * @private + */ + +class PolygonsCreator { + + /** @summary constructor */ + constructor() { + this.polygons = []; + } + + /** @summary Start polygon */ + startPolygon(normal) { + this.multi = 1; + this.mnormal = normal; + } + + /** @summary Stop polygon */ + stopPolygon() { + if (!this.multi) + return; + this.multi = 0; + console.error('Polygon should be already closed at this moment'); + } + + /** @summary Add face with 3 vertices */ + addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { + this.addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2); + } + + /** @summary Add face with 4 vertices + * @desc 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 */ + addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce = 0) { + this.v1 = new Vertex(x1, y1, z1, 0, 0, 0); + this.v2 = (reduce === 1) ? null : new Vertex(x2, y2, z2, 0, 0, 0); + this.v3 = new Vertex(x3, y3, z3, 0, 0, 0); + this.v4 = (reduce === 2) ? null : new Vertex(x4, y4, z4, 0, 0, 0); + this.reduce = reduce; + + if (this.multi) { + if (reduce !== 2) + console.error('polygon not supported for not-reduced faces'); + + let polygon; + + if (this.multi++ === 1) { + polygon = new Polygon$1(); + + polygon.vertices.push(this.mnormal ? this.v2 : this.v3); + this.polygons.push(polygon); + } else { + polygon = this.polygons.at(-1); + // check that last vertex equals to v2 + const last = this.mnormal ? polygon.vertices.at(-1) : polygon.vertices.at(0), + comp = this.mnormal ? this.v2 : this.v3; + + if (comp.diff(last) > 1e-12) + console.error('vertex missmatch when building polygon'); + } + + const first = this.mnormal ? polygon.vertices[0] : polygon.vertices.at(-1), + next = this.mnormal ? this.v3 : this.v2; + + if (next.diff(first) < 1e-12) + this.multi = 0; + else if (this.mnormal) + polygon.vertices.push(this.v3); + else + polygon.vertices.unshift(this.v2); + } else { + const polygon = new Polygon$1(); + 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); + } + } + + /** @summary Specify normal for face with 4 vertices + * @desc same as addFace4, assign normals for each individual vertex + * reduce has same meaning and should be the same */ + setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) { + this.v1.setnormal(nx1, ny1, nz1); + this.v2?.setnormal(nx2, ny2, nz2); + this.v3.setnormal(nx3, ny3, nz3); + this.v4?.setnormal(nx4, ny4, nz4); + } + + /** @summary Set normal + * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ + setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) { + this.v1.setnormal(nx12, ny12, nz12); + this.v2?.setnormal(nx12, ny12, nz12); + this.v3.setnormal(nx34, ny34, nz34); + this.v4?.setnormal(nx34, ny34, nz34); + } + + /** @summary Calculate normal */ + calcNormal() { + 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); + } + + /** @summary Set normal */ + setNormal(nx, ny, nz) { + this.v1.setnormal(nx, ny, nz); + this.v2?.setnormal(nx, ny, nz); + this.v3.setnormal(nx, ny, nz); + this.v4?.setnormal(nx, ny, nz); + } + + /** @summary Recalculate Z with provided func */ + recalcZ(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); + } + + /** @summary Create geometry + * @private */ + create() { + return { polygons: this.polygons }; + } + +} + +// ================= all functions to create geometry =================================== + +/** @summary Creates cube geometry + * @private */ +function createCubeBuffer(shape, faces_limit) { + if (faces_limit < 0) + return 12; + + const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ, + 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(); +} + +/** @summary Creates 8 edges geometry + * @private */ +function create8edgesBuffer(v, faces_limit) { + const 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], + creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12); + + for (let n = 0; n < indicies.length; n += 4) { + const 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(); +} + +/** @summary Creates PARA geometry + * @private */ +function createParaBuffer(shape, faces_limit) { + if (faces_limit < 0) + return 12; + + const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, 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); +} + +/** @summary Creates trapezoid geometry + * @private */ +function createTrapezoidBuffer(shape, faces_limit) { + if (faces_limit < 0) + return 12; + + let y1, y2; + if (shape._typename === clTGeoTrd1) + y1 = y2 = shape.fDY; + else { + y1 = shape.fDy1; + y2 = shape.fDy2; + } + + const 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); +} + + +/** @summary Creates arb8 geometry + * @private */ +function createArb8Buffer(shape, faces_limit) { + if (faces_limit < 0) + return 12; + + const 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, 3, 7, 4, 4, 0, 3, + 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6, + 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; + + // detect same vertices on both Z-layers + for (let side = 0; side < vertices.length; side += vertices.length / 2) { + for (let n1 = side; n1 < side + vertices.length / 2 - 3; n1 += 3) { + for (let 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 (let k = 0; k < indicies.length; ++k) { + if (indicies[k] === n2 / 3) + indicies[k] = n1 / 3; + } + } + } + } + } + + const map = []; // list of existing faces (with all rotations) + let numfaces = 0; + + for (let k = 0; k < indicies.length; k += 3) { + const 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++; + } + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + + for (let n = 0; n < indicies.length; n += 6) { + const 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; + let 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 { + const norm1 = produceNormal(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(); + + const norm2 = produceNormal(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; + } + } + + 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 { + 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(); + } + } + } + + return creator.create(); +} + +/** @summary Creates sphere geometry + * @private */ +function createSphereBuffer(shape, faces_limit) { + const radius = [shape.fRmax, shape.fRmin], + phiStart = shape.fPhi1, + phiLength = shape.fPhi2 - shape.fPhi1, + thetaStart = shape.fTheta1, + thetaLength = shape.fTheta2 - shape.fTheta1, + noInside = (radius[1] <= 0); + let widthSegments = shape.fNseg, + heightSegments = shape.fNz; + + if (faces_limit > 0) { + const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; + + if (fact > 1.0) { + widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); + heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); + } + } + + let numoutside = widthSegments * heightSegments * 2, + numtop = widthSegments * (noInside ? 1 : 2), + numbottom = widthSegments * (noInside ? 1 : 2); + const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4), + epsilon = 1e-10; + + if (faces_limit < 0) + return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + + const _sinp = new Float32Array(widthSegments + 1), + _cosp = new Float32Array(widthSegments + 1), + _sint = new Float32Array(heightSegments + 1), + _cost = new Float32Array(heightSegments + 1); + + for (let n = 0; n <= heightSegments; ++n) { + const theta = (thetaStart + thetaLength / heightSegments * n) * Math.PI / 180; + _sint[n] = Math.sin(theta); + _cost[n] = Math.cos(theta); + } + + for (let n = 0; n <= widthSegments; ++n) { + const 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; + } + + const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut, + creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + + for (let side = 0; side < 2; ++side) { + if ((side === 1) && noInside) + break; + + const 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 (let k = 0; k < heightSegments; ++k) { + const k1 = k + d1, k2 = k + d2; + let skip = 0; + if (Math.abs(_sint[k1]) <= epsilon) + skip = 1; + else if (Math.abs(_sint[k2]) <= epsilon) + skip = 2; + + for (let 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 (let side = 0; side <= heightSegments; side += heightSegments) { + if (Math.abs(_sint[side]) >= epsilon) { + const ss = _sint[side], cc = _cost[side], + d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; + for (let 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 (let side = 0; side <= widthSegments; side += widthSegments) { + const ss = _sinp[side], cc = _cosp[side], + d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; + + for (let 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(); +} + +/** @summary Creates tube geometry + * @private */ +function createTubeBuffer(shape, faces_limit) { + let outerR, innerR; // inner/outer tube radius + if ((shape._typename === clTGeoCone) || (shape._typename === clTGeoConeSeg)) { + outerR = [shape.fRmax2, shape.fRmax1]; + innerR = [shape.fRmin2, shape.fRmin1]; + } else { + outerR = [shape.fRmax, shape.fRmax]; + innerR = [shape.fRmin, shape.fRmin]; + } + + const hasrmin = (innerR[0] > 0) || (innerR[1] > 0); + let thetaStart = 0, thetaLength = 360; + + if ((shape._typename === clTGeoConeSeg) || (shape._typename === clTGeoTubeSeg) || (shape._typename === clTGeoCtub)) { + thetaStart = shape.fPhi1; + thetaLength = shape.fPhi2 - shape.fPhi1; + } + + const radiusSegments = Math.max(4, Math.round(thetaLength / _cfg.GradPerSegm)); + + // external surface + let 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; + + const phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + _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); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), + calcZ = (shape._typename !== clTGeoCtub) ? null : (x, y, z) => { + const 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 (let side = 0; side < 2; ++side) { + if ((side === 1) && !hasrmin) + break; + + const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side; + let nxy = 1, nz = 0; + + if (R[0] !== R[1]) { + const 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; + } + + const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0); + + for (let 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 (let side = 0; side < 2; ++side) { + if (outerR[side] <= 0) + continue; + + const 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 (let 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); + } + + 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(); +} + +/** @summary Creates eltu geometry + * @private */ +function createEltuBuffer(shape, faces_limit) { + const radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)); + + if (faces_limit < 0) + return radiusSegments * 4; + + // calculate all sin/cos tables in advance + const x = new Float32Array(radiusSegments + 1), + y = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + const phi = seg / radiusSegments * 2 * Math.PI; + x[seg] = shape.fRmin * Math.cos(phi); + y[seg] = shape.fRmax * Math.sin(phi); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments * 4); + let nx1, ny1, nx2 = 1, ny2 = 0; + + // create tube faces + for (let 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; + const dist = Math.sqrt(nx2 ** 2 + ny2 ** 2); + nx2 /= dist; + ny2 /= dist; + + creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); + } + + // create top/bottom sides + for (let side = 0; side < 2; ++side) { + const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; + for (let 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(); +} + +/** @summary Creates torus geometry + * @private */ +function createTorusBuffer(shape, faces_limit) { + const radius = shape.fR; + let radialSegments = Math.max(6, Math.round(360 / _cfg.GradPerSegm)), + tubularSegments = Math.max(8, Math.round(shape.fDphi / _cfg.GradPerSegm)), + 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)); + } + + const _sinr = new Float32Array(radialSegments + 1), + _cosr = new Float32Array(radialSegments + 1), + _sint = new Float32Array(tubularSegments + 1), + _cost = new Float32Array(tubularSegments + 1); + + for (let n = 0; n <= radialSegments; ++n) { + _sinr[n] = Math.sin(n / radialSegments * 2 * Math.PI); + _cosr[n] = Math.cos(n / radialSegments * 2 * Math.PI); + } + + for (let t = 0; t <= tubularSegments; ++t) { + const angle = (shape.fPhi1 + shape.fDphi * t / tubularSegments) / 180 * Math.PI; + _sint[t] = Math.sin(angle); + _cost[t] = Math.cos(angle); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), + // use vectors for normals calculation + 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 (let side = 0; side < 2; ++side) { + if ((side > 0) && (shape.fRmin <= 0)) + break; + const tube = (side > 0) ? shape.fRmin : shape.fRmax, + d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; + + for (let t = 0; t < tubularSegments; ++t) { + const 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 (let 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 (let t = 0; t <= tubularSegments; t += tubularSegments) { + const 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 (let 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(); +} + + +/** @summary Creates polygon geometry + * @private */ +function createPolygonBuffer(shape, faces_limit) { + const thetaStart = shape.fPhi1, + thetaLength = shape.fDphi; + let radiusSegments, factor; + + if (shape._typename === clTGeoPgon) { + radiusSegments = shape.fNedges; + factor = 1.0 / Math.cos(Math.PI / 180 * thetaLength / radiusSegments / 2); + } else { + radiusSegments = Math.max(5, Math.round(thetaLength / _cfg.GradPerSegm)); + factor = 1; + } + + const usage = new Int16Array(2 * shape.fNz); + let numusedlayers = 0, hasrmin = false; + + for (let layer = 0; layer < shape.fNz; ++layer) + hasrmin = hasrmin || (shape.fRmin[layer] > 0); + + // 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) + const pnts = (thetaLength === 360) ? null : []; + + // first analyze levels - if we need to create all of them + for (let side = 0; side < 2; ++side) { + const rside = (side === 0) ? 'fRmax' : 'fRmin'; + + for (let layer = 0; layer < shape.fNz; ++layer) { + // first create points for the layer + const 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(factor * rad, layerz)); + else if (rad < shape.fRmax[layer]) + pnts.unshift(new THREE.Vector2(factor * rad, layerz)); + } + } + } + + let 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); + + let cut_faces = null; + + if (pnts !== null) { + if (pnts.length === shape.fNz * 2) { + // special case - all layers are there, create faces ourself + cut_faces = []; + for (let layer = shape.fNz - 1; layer > 0; --layer) { + if (shape.fZ[layer] === shape.fZ[layer - 1]) + continue; + const 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 + cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); + } + numfaces += cut_faces.length * 2; + } + + const phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + // calculate all sin/cos tables in advance + _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); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + + // add sides + for (let side = 0; side < 2; ++side) { + const rside = (side === 0) ? 'fRmax' : 'fRmin', + d1 = 1 - side, d2 = side; + let z1 = shape.fZ[0], r1 = factor * shape[rside][0]; + + for (let layer = 0; layer < shape.fNz; ++layer) { + if (usage[layer * 2 + side] === 0) + continue; + + const z2 = shape.fZ[layer], r2 = factor * shape[rside][layer]; + let nxy = 1, nz = 0; + + if ((r2 !== r1)) { + const angle = Math.atan2((r2 - r1), (z2 - z1)); + nxy = Math.cos(angle); + nz = Math.sin(angle); + } + + if (side > 0) { + nxy *= -1; + nz *= -1; + } + + for (let 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 (let layer = 0; layer < shape.fNz; layer += (shape.fNz - 1)) { + const rmin = factor * shape.fRmin[layer], rmax = factor * shape.fRmax[layer]; + + if (rmin === rmax) + continue; + + const 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 (let 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 (let seg = 0; seg <= radiusSegments; seg += radiusSegments) { + const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; + for (let n = 0; n < cut_faces.length; ++n) { + const 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(); +} + +/** @summary Creates xtru geometry + * @private */ +function createXtruBuffer(shape, faces_limit) { + let nfaces = (shape.fNz - 1) * shape.fNvert * 2; + + if (faces_limit < 0) + return nfaces + shape.fNvert * 3; + + // create points + const pnts = []; + for (let vert = 0; vert < shape.fNvert; ++vert) + pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); + + let faces = THREE.ShapeUtils.triangulateShape(pnts, []); + if (faces.length < pnts.length - 2) { + geoWarn(`Problem with XTRU shape ${shape.fName} with ${pnts.length} vertices`); + faces = []; + } else + nfaces += faces.length * 2; + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(nfaces); + + for (let layer = 0; layer < shape.fNz - 1; ++layer) { + const 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) { + const 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)) { + const z = shape.fZ[layer], scale = shape.fScale[layer], + x0 = shape.fX0[layer], y0 = shape.fY0[layer]; + + for (let n = 0; n < faces.length; ++n) { + const face = faces[n], + pnt1 = pnts[face[0]], + pnt2 = pnts[face[layer === 0 ? 2 : 1]], + pnt3 = pnts[face[layer === 0 ? 1 : 2]]; + + 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(); +} + +/** @summary Creates para geometry + * @private */ +function createParaboloidBuffer(shape, faces_limit) { + let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), + heightSegments = 30; + + if (faces_limit > 0) { + const fact = 2 * radiusSegments * (heightSegments + 1) / faces_limit; + if (fact > 1.0) { + radiusSegments = Math.max(5, Math.floor(radiusSegments / Math.sqrt(fact))); + heightSegments = Math.max(5, Math.floor(heightSegments / Math.sqrt(fact))); + } + } + + const rmin = shape.fRlo, rmax = shape.fRhi; + let 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; + + let zmin = -shape.fDZ, zmax = shape.fDZ; + + // if no radius at -z, find intersection + if (shape.fA >= 0) + zmin = Math.max(zmin, shape.fB); + else + zmax = Math.min(shape.fB, zmax); + + const ttmin = Math.atan2(zmin, rmin), + ttmax = Math.atan2(zmax, rmax), + // calculate all sin/cos tables in advance + _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + let lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; + + for (let layer = 0; layer <= heightSegments + 1; ++layer) { + if ((layer === 0) && (rmin === 0)) + continue; + + if ((layer === heightSegments + 1) && (lastr === 0)) + break; + + let layerz, radius; + + 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: { + const tt = Math.tan(ttmin + (ttmax - ttmin) * layer / heightSegments), + delta = tt ** 2 - 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; + break; + } + } + + const nxy = shape.fA * radius, + nz = (shape.fA > 0) ? -1 : 1, + skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0); + + for (let 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(); +} + +/** @summary Creates hype geometry + * @private */ +function createHypeBuffer(shape, faces_limit) { + if ((shape.fTin === 0) && (shape.fTout === 0)) + return createTubeBuffer(shape, faces_limit); + + let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), + heightSegments = 30, + 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 + const _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); + } + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + + // in-out side + for (let side = 0; side < 2; ++side) { + if ((side > 0) && (shape.fRmin <= 0)) + break; + + const r0 = (side > 0) ? shape.fRmin : shape.fRmax, + tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, + d1 = 1 - side, d2 = 1 - d1; + + // vertical layers + for (let layer = 0; layer < heightSegments; ++layer) { + const z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, + z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, + r1 = Math.sqrt(r0 ** 2 + tsq * z1 ** 2), + r2 = Math.sqrt(r0 ** 2 + tsq * z2 ** 2); + + for (let 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(); + } + } + } + + // add caps + for (let layer = 0; layer < 2; ++layer) { + const z = (layer === 0) ? shape.fDz : -shape.fDz, + r1 = Math.sqrt(shape.fRmax ** 2 + shape.fToutsq * z ** 2), + r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin ** 2 + shape.fTinsq * z ** 2) : 0, + skip = (shape.fRmin > 0) ? 0 : 1, + d1 = 1 - layer, d2 = 1 - d1; + for (let 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(); +} + +/** @summary Creates tessellated geometry + * @private */ +function createTessellatedBuffer(shape, faces_limit) { + let numfaces = 0; + for (let i = 0; i < shape.fFacets.length; ++i) + numfaces += (shape.fFacets[i].fNvert === 4) ? 2 : 1; + if (faces_limit < 0) + return numfaces; + + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); + + for (let i = 0; i < shape.fFacets.length; ++i) { + const f = shape.fFacets[i], + v0 = shape.fVertices[f.fIvert[0]].fVec, + v1 = shape.fVertices[f.fIvert[1]].fVec, + v2 = shape.fVertices[f.fIvert[2]].fVec; + + if (f.fNvert === 4) { + const v3 = shape.fVertices[f.fIvert[3]].fVec; + creator.addFace4(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], v3[0], v3[1], v3[2]); + creator.calcNormal(); + } else { + creator.addFace3(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2]); + creator.calcNormal(); + } + } + + return creator.create(); +} + +/** @summary Creates Matrix4 from TGeoMatrix + * @private */ +function createMatrix(matrix) { + if (!matrix) + return null; + + let translation, rotation, scale; + + 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 + // eslint-disable-next-line no-fallthrough + case 'TGeoCombiTrans': + translation = matrix.fTranslation; + 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; + + const 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, + 0, 0, 0, 1); + } + + if (translation) + res.setPosition(translation[0], translation[1], translation[2]); + + if (scale) + res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); + + return res; +} + +/** @summary Creates transformation matrix for TGeoNode + * @desc created after node visibility flag is checked and volume cut is performed + * @private */ +function getNodeMatrix(kind, node) { + let matrix = null; + + if (kind === kindEve) { + // special handling for EVE nodes + + matrix = new THREE.Matrix4(); + if (node.fTrans) { + 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(node.fTrans[12], node.fTrans[13], node.fTrans[14]); + } + } else if (node.fMatrix) + matrix = createMatrix(node.fMatrix); + else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) { + const kPatternReflected = BIT(14), + finder = node.fFinder, + typ = finder._typename; + if (finder.fBits & kPatternReflected) + geoWarn(`Unsupported reflected pattern ${typ}`); + if (typ.indexOf('TGeoPattern')) + geoWarn(`Abnormal pattern type ${typ}`); + const part = typ.slice(11); + matrix = new THREE.Matrix4(); + switch (part) { + case 'X': + case 'Y': + case 'Z': + case 'ParaX': + case 'ParaY': + case 'ParaZ': { + const _shift = finder.fStart + (node.fIndex + 0.5) * finder.fStep; + switch (part.at(-1)) { + case 'X': matrix.setPosition(_shift, 0, 0); break; + case 'Y': matrix.setPosition(0, _shift, 0); break; + case 'Z': matrix.setPosition(0, 0, _shift); break; + } + break; + } + case 'CylPhi': { + const phi = (Math.PI / 180) * (finder.fStart + (node.fIndex + 0.5) * finder.fStep), + _cos = Math.cos(phi), _sin = Math.sin(phi); + matrix.set(_cos, -_sin, 0, 0, + _sin, _cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + break; + } + case 'CylR': + // seems to be, require no transformation + break; + case 'TrapZ': { + const dz = finder.fStart + (node.fIndex + 0.5) * finder.fStep; + matrix.setPosition(finder.fTxz * dz, finder.fTyz * dz, dz); + break; + } + // case 'CylR': break; + // case 'SphR': break; + // case 'SphTheta': break; + // case 'SphPhi': break; + // case 'Honeycomb': break; + default: + geoWarn(`Unsupported pattern type ${typ}`); + break; + } + } + + return matrix; +} + +/** @summary Returns number of faces for provided geometry + * @param geom - can be BufferGeometry, CsgGeometry or interim array of polygons + * @private */ +function numGeometryFaces(geom) { + if (!geom) + return 0; + + if (geom instanceof Geometry) + return geom.tree.numPolygons(); + + // special array of polygons + if (geom.polygons) + return geom.polygons.length; + + const attr = geom.getAttribute('position'); + return attr?.count ? Math.round(attr.count / 3) : 0; +} + +/** @summary Returns geometry bounding box + * @private */ +function geomBoundingBox(geom) { + if (!geom) + return null; + + let polygons = null; + + if (geom instanceof Geometry) + polygons = geom.tree.collectPolygons(); + else if (geom.polygons) + polygons = geom.polygons; + + if (polygons !== null) { + const box = new THREE.Box3(); + for (let n = 0; n < polygons.length; ++n) { + const polygon = polygons[n], nvert = polygon.vertices.length; + for (let k = 0; k < nvert; ++k) + box.expandByPoint(polygon.vertices[k]); + } + return box; + } + + if (!geom.boundingBox) + geom.computeBoundingBox(); + + return geom.boundingBox.clone(); +} + +/** @summary Creates half-space geometry for given shape + * @desc Just big-enough triangle to make BSP calculations + * @private */ +function createHalfSpace(shape, geom) { + if (!shape?.fN || !shape?.fP) + return null; + + const vertex = new THREE.Vector3(shape.fP[0], shape.fP[1], shape.fP[2]), + normal = new THREE.Vector3(shape.fN[0], shape.fN[1], shape.fN[2]); + + normal.normalize(); + + let sz = 1e10; + if (geom) { + // using real size of other geometry, we probably improve precision + const box = geomBoundingBox(geom); + if (box) + sz = box.getSize(new THREE.Vector3()).length() * 1000; + } + + const v0 = new THREE.Vector3(-sz, -sz / 2, 0), + v1 = new THREE.Vector3(0, sz, 0), + v2 = new THREE.Vector3(sz, -sz / 2, 0), + v3 = new THREE.Vector3(0, 0, -sz), + geometry = new THREE.BufferGeometry(), + positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z, + v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z, + v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, + v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.computeVertexNormals(); + + geometry.lookAt(normal); + geometry.computeVertexNormals(); + + for (let k = 0; k < positions.length; k += 3) { + positions[k] += vertex.x; + positions[k + 1] += vertex.y; + positions[k + 2] += vertex.z; + } + + return geometry; +} + +let createGeometry = null; + +/** @summary Creates geometry for composite shape + * @private */ +function createComposite(shape, faces_limit) { + if (faces_limit < 0) { + return createGeometry(shape.fNode.fLeft, -1) + + createGeometry(shape.fNode.fRight, -1); + } + + let geom1, geom2, return_bsp = false; + const matrix1 = createMatrix(shape.fNode.fLeftMat), + matrix2 = createMatrix(shape.fNode.fRightMat); + + if (faces_limit === 0) + faces_limit = 4000; + else + return_bsp = true; + + if (matrix1 && (matrix1.determinant() < -0.9)) + geoWarn('Axis reflection in left composite shape - not supported'); + + if (matrix2 && (matrix2.determinant() < -0.9)) + geoWarn('Axis reflections in right composite shape - not supported'); + + if (shape.fNode.fLeft._typename === clTGeoHalfSpace) + geom1 = createHalfSpace(shape.fNode.fLeft); + else + geom1 = createGeometry(shape.fNode.fLeft, faces_limit); + + if (!geom1) + return null; + + let n1 = numGeometryFaces(geom1), n2 = 0; + if (geom1._exceed_limit) + n1 += faces_limit; + + if (n1 < faces_limit) { + if (shape.fNode.fRight._typename === clTGeoHalfSpace) + geom2 = createHalfSpace(shape.fNode.fRight, geom1); + else + geom2 = createGeometry(shape.fNode.fRight, faces_limit); + + + n2 = numGeometryFaces(geom2); + } + + if ((n1 + n2 >= faces_limit) || !geom2) { + if (geom1.polygons) + geom1 = createBufferGeometry(geom1.polygons); + if (matrix1) + geom1.applyMatrix4(matrix1); + geom1._exceed_limit = true; + return geom1; + } + + let bsp1 = new Geometry(geom1, matrix1, _cfg.CompressComp ? 0 : undefined); + + const bsp2 = new 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: + geoWarn(`unsupported bool operation ${shape.fNode._typename}, use first geom`); + break; + } + + if (numGeometryFaces(bsp1) === 0) { + geoWarn('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 Geometry(geom1, matrix1); + } + + return return_bsp ? { polygons: bsp1.toPolygons() } : bsp1.toBufferGeometry(); +} + +/** @summary Try to create projected geometry + * @private */ +function projectGeometry(geom, matrix, projection, position = 0, flippedMesh = false) { + if (!geom.boundingBox) + geom.computeBoundingBox(); + + const box = geom.boundingBox.clone(); + + box.applyMatrix4(matrix); + + if (((box.min[projection] >= position) && (box.max[projection] >= position)) || + ((box.min[projection] <= position) && (box.max[projection] <= position))) + return null; // not interesting + + + const bsp1 = new 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)); + let 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; + } + + const bsp2 = createNormal(projection, position, size); + + bsp1.cut_from_plane(bsp2); + + return bsp2.toBufferGeometry(); +} + +/** @summary Creates geometry model for the provided shape + * @param {Object} shape - instance of TGeoShape object + * @param {Number} limit - defines return value, see details + * @desc + * - if limit === 0 returns BufferGeometry + * - if limit < 0 just returns estimated number of faces + * - if limit > 0 return list of CsgPolygons (used only for composite shapes) + * @private */ +createGeometry = function(shape, limit = 0) { + try { + switch (shape._typename) { + case clTGeoBBox: return createCubeBuffer(shape, limit); + case clTGeoPara: return createParaBuffer(shape, limit); + case clTGeoTrd1: + case clTGeoTrd2: return createTrapezoidBuffer(shape, limit); + case clTGeoArb8: + case clTGeoTrap: + case clTGeoGtra: return createArb8Buffer(shape, limit); + case clTGeoSphere: return createSphereBuffer(shape, limit); + case clTGeoCone: + case clTGeoConeSeg: + case clTGeoTube: + case clTGeoTubeSeg: + case clTGeoCtub: return createTubeBuffer(shape, limit); + case clTGeoEltu: return createEltuBuffer(shape, limit); + case clTGeoTorus: return createTorusBuffer(shape, limit); + case clTGeoPcon: + case clTGeoPgon: return createPolygonBuffer(shape, limit); + case clTGeoXtru: return createXtruBuffer(shape, limit); + case clTGeoParaboloid: return createParaboloidBuffer(shape, limit); + case clTGeoHype: return createHypeBuffer(shape, limit); + case 'TGeoTessellated': return createTessellatedBuffer(shape, limit); + case clTGeoCompositeShape: return createComposite(shape, limit); + case clTGeoShapeAssembly: break; + case clTGeoScaledShape: { + const res = createGeometry(shape.fShape, limit); + if (shape.fScale && (limit >= 0) && isFunc(res?.scale)) + res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]); + return res; + } + case clTGeoHalfSpace: + if (limit < 0) + return 1; // half space if just plane used in composite + // eslint-disable-next-line no-fallthrough + default: + geoWarn(`unsupported shape type ${shape._typename}`); + } + } catch (e) { + let place = ''; + if (e.stack !== undefined) { + place = e.stack.split('\n')[0]; + place = place.indexOf(e.message) >= 0 ? e.stack.split('\n')[1] : 'at: ' + place; + } + geoWarn(`${shape._typename} err: ${e.message} ${place}`); + } + + return limit < 0 ? 0 : null; +}; + + +/** @summary Create single shape from EVE7 render date + * @private */ +function makeEveGeometry(rd) { + let off = 0; + + if (rd.sz[0]) { + rd.vtxBuff = new Float32Array(rd.raw.buffer, off, rd.sz[0]); + off += rd.sz[0] * 4; + } + + if (rd.sz[1]) { + // normals were not used + // rd.nrmBuff = new Float32Array(rd.raw.buffer, off, rd.sz[1]); + off += rd.sz[1] * 4; + } + + if (rd.sz[2]) { + // these are special values in the buffer begin + rd.prefixBuf = new Uint32Array(rd.raw.buffer, off, 2); + off += 2 * 4; + rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2] - 2); + // off += (rd.sz[2]-2)*4; + } + + const GL_TRIANGLES = 4; // same as in EVE7 + + if (rd.prefixBuf[0] !== GL_TRIANGLES) + throw Error('Expect triangles first.'); + + const nVert = 3 * rd.prefixBuf[1]; // number of vertices to draw + + if (rd.idxBuff.length !== nVert) + throw Error('Expect single list of triangles in index buffer.'); + + const body = new THREE.BufferGeometry(); + body.setAttribute('position', new THREE.BufferAttribute(rd.vtxBuff, 3)); + body.setIndex(new THREE.BufferAttribute(rd.idxBuff, 1)); + body.computeVertexNormals(); + + return body; +} + +/** @summary Create single shape from geometry viewer render date + * @private */ +function makeViewerGeometry(rd) { + const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength / 4), + body = new THREE.BufferGeometry(); + body.setAttribute('position', new THREE.BufferAttribute(vtxBuff, 3)); + body.setIndex(new THREE.BufferAttribute(new Uint32Array(rd.idx), 1)); + body.computeVertexNormals(); + return body; +} + +/** @summary Create single shape from provided raw data from web viewer. + * @desc If nsegm changed, shape will be recreated + * @private */ +function createServerGeometry(rd, nsegm) { + if (rd.server_shape && ((rd.nsegm === nsegm) || !rd.shape)) + return rd.server_shape; + + rd.nsegm = nsegm; + + let geom; + + if (rd.shape) { + // case when TGeoShape provided as is + geom = createGeometry(rd.shape); + } else { + if (!rd.raw?.buffer) { + console.error('No raw data at all'); + return null; + } + + geom = rd.sz ? makeEveGeometry(rd) : makeViewerGeometry(rd); + } + + // shape handle is similar to created in TGeoPainter + return { + _typename: kShapeType, // indicate that shape can be used as is + ready: true, + geom, + nfaces: numGeometryFaces(geom) + }; +} + +/** @summary Provides info about geo object, used for tooltip info + * @param {Object} obj - any kind of TGeo-related object like shape or node or volume + * @private */ +function provideObjectInfo(obj) { + let info = [], shape = null; + + 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 (!shape) { + info.push(obj._typename); + return info; + } + + const sz = Math.max(shape.fDX, shape.fDY, shape.fDZ), + useexp = (sz > 1e7) || (sz < 1e-7), + 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 clTGeoPara: + info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); + break; + case clTGeoTrd2: + info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy1)}`); // no break + // eslint-disable-next-line no-fallthrough + case clTGeoTrd1: + info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx1)}`); + break; + case clTGeoSphere: + info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, + `Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`, + `Theta1=${shape.fTheta1} Theta2=${shape.fTheta2}`); + break; + case clTGeoConeSeg: + info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); + // eslint-disable-next-line no-fallthrough + case clTGeoCone: + info.push(`Rmin1=${conv(shape.fRmin1)} Rmax1=${conv(shape.fRmax1)}`, + `Rmin2=${conv(shape.fRmin2)} Rmax2=${conv(shape.fRmax2)}`); + break; + case clTGeoCtub: + case clTGeoTubeSeg: + info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); + // eslint-disable-next-line no-fallthrough + case clTGeoEltu: + case clTGeoTube: + info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`); + break; + case clTGeoTorus: + info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, + `Phi1=${shape.fPhi1} Dphi=${shape.fDphi}`); + break; + case clTGeoParaboloid: + info.push(`Rlo=${conv(shape.fRlo)} Rhi=${conv(shape.fRhi)}`, + `A=${conv(shape.fA)} B=${conv(shape.fB)}`); + break; + case clTGeoHype: + info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, + `StIn=${conv(shape.fStIn)} StOut=${conv(shape.fStOut)}`); + break; + case clTGeoScaledShape: + info = provideObjectInfo(shape.fShape); + if (shape.fScale) + info.unshift(`Scale X=${shape.fScale.fScale[0]} Y=${shape.fScale.fScale[1]} Z=${shape.fScale.fScale[2]}`); + break; + case clTGeoPcon: + case clTGeoPgon: + info.push(`Phi1=${conv(shape.fPhi1)} Dphi=${conv(shape.fDphi)}`, + `Nz=${shape.fNz}`); + break; + case clTGeoXtru: + info.push(`Nz=${shape.fNz} Nvert=${shape.fNvert}`); + break; + case clTGeoCompositeShape: + info.push(`Type ${shape.fNode?._typename}`, + `Left=${shape.fNode?.fLeft?._typename} Right=${shape.fNode?.fRight?._typename}`); + break; + case clTGeoBBox: + info.push(`Origin=[${conv(shape.fOrigin[0])},${conv(shape.fOrigin[1])},${conv(shape.fOrigin[2])}]`); + break; + case clTGeoArb8: + break; + case clTGeoGtra: + info.push(`TwistAngle=${conv(shape.fTwistAngle)}`); + // eslint-disable-next-line no-fallthrough + case clTGeoTrap: + info.push(`Phi=${conv(shape.fPhi)} Theta=${conv(shape.fTheta)}`); + break; + } + + return info; +} + +/** @summary Creates projection matrix for the camera + * @private */ +function createProjectionMatrix(camera) { + const cameraProjectionMatrix = new THREE.Matrix4(); + + camera.updateMatrixWorld(); + + camera.matrixWorldInverse.copy(camera.matrixWorld).invert(); + cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + + return cameraProjectionMatrix; +} + +/** @summary Creates frustum + * @private */ +function createFrustum(source) { + if (!source) + return null; + + if (source instanceof THREE.PerspectiveCamera) + source = createProjectionMatrix(source); + + const frustum = new THREE.Frustum(); + frustum.setFromProjectionMatrix(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) { + const pnt = this.test, len = this.corners.length, corners = this.corners; + + for (let 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; + }; + + frustum.CheckPoint = function(x, y, z) { + return this.containsPoint(this.test.set(x, y, z)) ? 1 : 0; + }; + + frustum.CheckBox = function(box) { + // only if 6 edges and more are seen, we think that box is fully visible + return this.CheckPoint(box.min.x, box.min.y, box.min.z) + + this.CheckPoint(box.min.x, box.min.y, box.max.z) + + this.CheckPoint(box.min.x, box.max.y, box.min.z) + + this.CheckPoint(box.min.x, box.max.y, box.max.z) + + this.CheckPoint(box.max.x, box.max.y, box.max.z) + + this.CheckPoint(box.max.x, box.min.y, box.max.z) + + this.CheckPoint(box.max.x, box.max.y, box.min.z) + + this.CheckPoint(box.max.x, box.max.y, box.max.z) > 5; + }; + + return frustum; +} + +/** @summary Create node material + * @private */ +function createMaterial(cfg, args0) { + if (!cfg) + cfg = { material_kind: 'lambert' }; + + const args = Object.assign({}, args0); + + if (args.opacity === undefined) + args.opacity = 1; + + if (cfg.transparency) + args.opacity = Math.min(1 - cfg.transparency, args.opacity); + + args.wireframe = cfg.wireframe ?? false; + if (!args.color) + args.color = 'red'; + args.side = THREE.FrontSide; + args.transparent = args.opacity < 1; + args.depthWrite = args.opactity === 1; + + let material; + + if (cfg.material_kind === 'basic') + material = new THREE.MeshBasicMaterial(args); + else if (cfg.material_kind === 'depth') { + delete args.color; + material = new THREE.MeshDepthMaterial(args); + } else if (cfg.material_kind === 'toon') + material = new THREE.MeshToonMaterial(args); + else if (cfg.material_kind === 'matcap') { + delete args.wireframe; + material = new THREE.MeshMatcapMaterial(args); + } else if (cfg.material_kind === 'standard') { + args.metalness = cfg.metalness ?? 0.5; + args.roughness = cfg.roughness ?? 0.1; + material = new THREE.MeshStandardMaterial(args); + } else if (cfg.material_kind === 'normal') { + delete args.color; + material = new THREE.MeshNormalMaterial(args); + } else if (cfg.material_kind === 'physical') { + args.metalness = cfg.metalness ?? 0.5; + args.roughness = cfg.roughness ?? 0.1; + args.reflectivity = cfg.reflectivity ?? 0.5; + args.emissive = args.color; + material = new THREE.MeshPhysicalMaterial(args); + } else if (cfg.material_kind === 'phong') { + args.shininess = cfg.shininess ?? 0.9; + material = new THREE.MeshPhongMaterial(args); + } else { + args.vertexColors = false; + material = new THREE.MeshLambertMaterial(args); + } + + if ((material.flatShading !== undefined) && (cfg.flatShading !== undefined)) + material.flatShading = cfg.flatShading; + material.inherentOpacity = args0.opacity ?? 1; + material.inherentArgs = args0; + + return material; +} + +/** @summary Compares two stacks. + * @return {Number} 0 if same, -1 when stack1 < stack2, +1 when stack1 > stack2 + * @private */ +function compare_stacks(stack1, stack2) { + if (stack1 === stack2) + return 0; + const len1 = stack1?.length ?? 0, + len2 = stack2?.length ?? 0, + len = (len1 < len2) ? len1 : len2; + let indx = 0; + while (indx < len) { + if (stack1[indx] < stack2[indx]) + return -1; + if (stack1[indx] > stack2[indx]) + return 1; + ++indx; + } + + return (len1 < len2) ? -1 : ((len1 > len2) ? 1 : 0); +} + +/** @summary Checks if two stack arrays are identical + * @private */ +function isSameStack(stack1, stack2) { + if (!stack1 || !stack2) + return false; + if (stack1 === stack2) + return true; + if (stack1.length !== stack2.length) + return false; + for (let k = 0; k < stack1.length; ++k) { + if (stack1[k] !== stack2[k]) + return false; + } + return true; +} + + +function createFlippedGeom(geom) { + let pos = geom.getAttribute('position').array, + norm = geom.getAttribute('normal').array; + const index = geom.getIndex(); + + if (index) { + // we need to unfold all points to + const arr = index.array, + i0 = geom.drawRange.start; + let ilen = geom.drawRange.count; + if (i0 + ilen > arr.length) + ilen = arr.length - i0; + + const dpos = new Float32Array(ilen * 3), + dnorm = new Float32Array(ilen * 3); + for (let ii = 0; ii < ilen; ++ii) { + const k = arr[i0 + ii]; + if ((k < 0) || (k * 3 >= pos.length)) + console.log(`strange index ${k * 3} totallen = ${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; + } + + const len = pos.length, + newpos = new Float32Array(len), + newnorm = new Float32Array(len); + + // we should swap second and third point in each face + for (let n = 0, shift = 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 + } + + const geomZ = new THREE.BufferGeometry(); + geomZ.setAttribute('position', new THREE.BufferAttribute(newpos, 3)); + geomZ.setAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); + + return geomZ; +} + + +/** @summary Create flipped mesh for the shape + * @desc When transformation matrix includes one or several inversion of axis, + * one should inverse geometry object, otherwise three.js cannot correctly draw it + * @param {Object} shape - TGeoShape object + * @param {Object} material - material + * @private */ +function createFlippedMesh(shape, material) { + if (shape.geomZ === undefined) + shape.geomZ = createFlippedGeom(shape.geom); + + const mesh = new THREE.Mesh(shape.geomZ, material); + mesh.scale.copy(new THREE.Vector3(1, 1, -1)); + mesh.updateMatrix(); + + mesh._flippedMesh = true; + + return mesh; +} + + +/** + * @summary class for working with cloned nodes + * + * @private + */ + +class ClonedNodes { + + /** @summary Constructor */ + 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 + this.vislevel = 4; // maximal depth of nodes visibility aka gGeoManager->SetVisLevel, same default + this.maxnodes = 10000; // maximal number of visible nodes aka gGeoManager->fMaxVisNodes + + if (obj) { + if (obj.$geoh) + this.toplevel = false; + this.createClones(obj); + } else if (clones) + this.nodes = clones; + } + + /** @summary Set maximal depth for nodes visibility */ + setVisLevel(lvl) { + this.vislevel = lvl && Number.isInteger(lvl) ? lvl : 4; + } + + /** @summary Returns maximal depth for nodes visibility */ + getVisLevel() { + return this.vislevel; + } + + /** @summary Set maximal number of visible nodes + * @desc By default 10000 nodes will be visualized */ + setMaxVisNodes(v, more) { + this.maxnodes = (v === Infinity) ? 1e9 : (Number.isFinite(v) ? v : 10000); + if (more && Number.isFinite(more)) + this.maxnodes *= more; + } + + /** @summary Returns configured maximal number of visible nodes */ + getMaxVisNodes() { + return this.maxnodes; + } + + /** @summary Set geo painter configuration - used for material creation */ + setConfig(cfg) { + this._cfg = cfg; + } + + /** @summary Insert node into existing array */ + updateNode(node) { + if (node && Number.isInteger(node.id) && (node.id < this.nodes.length)) + this.nodes[node.id] = node; + } + + /** @summary Returns TGeoShape for element with given indx */ + getNodeShape(indx) { + if (!this.origin || !this.nodes) + return null; + const obj = this.origin[indx], clone = this.nodes[indx]; + if (!obj || !clone) + return null; + return clone.kind === kindGeo ? obj.fVolume?.fShape : obj.fShape; + } + + /** @summary function to cleanup as much as possible structures + * @desc Provided parameters drawnodes and drawshapes are arrays created during building of geometry */ + cleanup(drawnodes, drawshapes) { + if (drawnodes) { + for (let n = 0; n < drawnodes.length; ++n) { + delete drawnodes[n].stack; + drawnodes[n] = undefined; + } + } + + if (drawshapes) { + for (let n = 0; n < drawshapes.length; ++n) { + delete drawshapes[n].geom; + drawshapes[n] = undefined; + } + } + + if (this.nodes) { + for (let n = 0; n < this.nodes.length; ++n) { + if (this.nodes[n]) + delete this.nodes[n].chlds; + } + } + + delete this.nodes; + delete this.origin; + + delete this.sortmap; + } + + /** @summary Create complete description for provided Geo object */ + createClones(obj, sublevel, kind) { + if (!sublevel) { + if (obj?._typename === kShapeType) + return this.createClonesForShape(obj); + + this.origin = []; + sublevel = 1; + kind = getNodeKind(obj); + } + + if ((kind < 0) || !obj || ('_refid' in obj)) + return; + + obj._refid = this.origin.length; + this.origin.push(obj); + this.maxdepth = Math.max(this.maxdepth, sublevel); + + let chlds; + if (kind === kindGeo) + chlds = obj.fVolume?.fNodes?.arr || null; + else + chlds = obj.fElements?.arr || null; + + if (chlds !== null) { + checkDuplicates(obj, chlds); + for (let i = 0; i < chlds.length; ++i) + this.createClones(chlds[i], sublevel + 1, kind); + } + + if (sublevel > 1) + return; + + this.nodes = []; + + const sortarr = []; + + // first create nodes objects + for (let id = 0; id < this.origin.length; ++id) { + // let obj = this.origin[id]; + const node = { id, kind, vol: 0, nfaces: 0 }; + this.nodes.push(node); + sortarr.push(node); // array use to produce sortmap + } + + // than fill children lists + for (let n = 0; n < this.origin.length; ++n) { + const obj2 = this.origin[n], + clone = this.nodes[n], + shape = kind === kindEve ? obj2.fShape : obj2.fVolume.fShape, + chlds2 = kind === kindEve ? obj2.fElements?.arr : obj2.fVolume.fNodes?.arr, + matrix = getNodeMatrix(kind, obj2); + + if (matrix) { + clone.matrix = matrix.elements; // take only matrix elements, matrix will be constructed in worker + if (clone.matrix && (clone.matrix[0] === 1)) { + let 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 (clone.matrix && (kind === kindEve)) + clone.abs_matrix = true; + } + if (shape) { + clone.fDX = shape.fDX; + clone.fDY = shape.fDY; + clone.fDZ = shape.fDZ; + clone.vol = Math.sqrt(shape.fDX ** 2 + shape.fDY ** 2 + shape.fDZ ** 2); + if (shape.$nfaces === undefined) + shape.$nfaces = createGeometry(shape, -1); + clone.nfaces = shape.$nfaces; + if (clone.nfaces <= 0) + clone.vol = 0; + } + + if (chlds2) { + // in cloned object children is only list of ids + clone.chlds = new Array(chlds2.length); + for (let k = 0; k < chlds2.length; ++k) + clone.chlds[k] = chlds2[k]._refid; + } + } + + // remove _refid identifiers from original objects + for (let n = 0; n < this.origin.length; ++n) + delete this.origin[n]._refid; + + // do sorting once + sortarr.sort((a, b) => b.vol - a.vol); + + // remember sort map and also sortid + this.sortmap = new Array(this.nodes.length); + for (let n = 0; n < this.nodes.length; ++n) { + this.sortmap[n] = sortarr[n].id; + sortarr[n].sortid = n; + } + } + + /** @summary Create elementary item with single already existing shape + * @desc used by details view of geometry shape */ + createClonesForShape(obj) { + this.origin = []; + + // indicate that just plain shape is used + this.plain_shape = obj; + + this.nodes = [{ + id: 0, sortid: 0, kind: kindShape, + name: 'Shape', + nfaces: obj.nfaces, + fDX: 1, fDY: 1, fDZ: 1, vol: 1, + vis: true + }]; + } + + /** @summary Count all visible nodes */ + countVisibles() { + const len = this.nodes?.length || 0; + let cnt = 0; + for (let k = 0; k < len; ++k) { + if (this.nodes[k].vis) + cnt++; + } + return cnt; + } + + /** @summary Mark visible nodes. + * @desc Set only basic flags, actual visibility depends from hierarchy */ + markVisibles(on_screen, copy_bits, hide_top_volume) { + if (this.plain_shape) + return 1; + if (!this.origin || !this.nodes) + return 0; + + let res = 0; + + for (let n = 0; n < this.nodes.length; ++n) { + const clone = this.nodes[n], obj = this.origin[n]; + + clone.vis = 0; // 1 - only with last level + delete clone.nochlds; + + if (clone.kind === kindGeo) { + if (obj.fVolume) { + if (on_screen) { + // on screen bits used always, childs always checked + clone.vis = testGeoBit(obj.fVolume, geoBITS.kVisOnScreen) ? 99 : 0; + + if ((n === 0) && clone.vis && hide_top_volume) + clone.vis = 0; + + if (copy_bits) { + setGeoBit(obj.fVolume, geoBITS.kVisNone, false); + setGeoBit(obj.fVolume, geoBITS.kVisThis, (clone.vis > 0)); + setGeoBit(obj.fVolume, geoBITS.kVisDaughters, true); + setGeoBit(obj, geoBITS.kVisDaughters, true); + } + } else { + clone.vis = !testGeoBit(obj.fVolume, geoBITS.kVisNone) && testGeoBit(obj.fVolume, geoBITS.kVisThis) ? 99 : 0; + + if (!testGeoBit(obj, geoBITS.kVisDaughters) || !testGeoBit(obj.fVolume, geoBITS.kVisDaughters)) + clone.nochlds = true; + + // node with childs only shown in case if it is last level in hierarchy + if ((clone.vis > 0) && clone.chlds && !clone.nochlds) + clone.vis = 1; + + // special handling for top node + if (n === 0) { + if (hide_top_volume) + clone.vis = 0; + delete clone.nochlds; + } + } + } + } else { + clone.vis = obj.fRnrSelf ? 99 : 0; + + // when the only node is selected, draw it + if ((n === 0) && (this.nodes.length === 1)) + clone.vis = 99; + + this.vislevel = 9999; // automatically take all volumes + } + + // shape with zero volume or without faces will not be observed + if ((clone.vol <= 0) || (clone.nfaces <= 0)) + clone.vis = 0; + + if (clone.vis) + res++; + } + + return res; + } + + /** @summary After visibility flags is set, produce id shifts for all nodes as it would be maximum level */ + produceIdShifts() { + for (let k = 0; k < this.nodes.length; ++k) + this.nodes[k].idshift = -1; + + function scan_func(nodes, node) { + if (node.idshift < 0) { + node.idshift = 0; + if (node.chlds) { + for (let k = 0; k < node.chlds.length; ++k) + node.idshift += scan_func(nodes, nodes[node.chlds[k]]); + } + } + + return node.idshift + 1; + } + + scan_func(this.nodes, this.nodes[0]); + } + + /** @summary Extract only visibility flags + * @desc Used to transfer them to the worker */ + getVisibleFlags() { + const res = new Array(this.nodes.length); + for (let n = 0; n < this.nodes.length; ++n) + res[n] = { vis: this.nodes[n].vis, nochlds: this.nodes[n].nochlds }; + return res; + } + + /** @summary Assign only visibility flags, extracted with getVisibleFlags */ + setVisibleFlags(flags) { + if (!this.nodes || !flags || !flags.length !== this.nodes.length) + return 0; + + let res = 0; + for (let n = 0; n < this.nodes.length; ++n) { + const clone = this.nodes[n]; + clone.vis = flags[n].vis; + clone.nochlds = flags[n].nochlds; + if (clone.vis) + res++; + } + + return res; + } + + /** @summary Set visibility flag for physical node + * @desc Trying to reimplement functionality in the RGeomViewer */ + setPhysNodeVisibility(stack, on) { + let do_clear = false; + if (on === 'clearall') { + delete this.fVisibility; + return; + } else if (on === 'clear') { + do_clear = true; + if (!this.fVisibility) + return; + } else + on = Boolean(on); + if (!stack) + return; + + if (!this.fVisibility) + this.fVisibility = []; + + for (let indx = 0; indx < this.fVisibility.length; ++indx) { + const item = this.fVisibility[indx], + res = compare_stacks(item.stack, stack); + + if (res === 0) { + if (do_clear) { + this.fVisibility.splice(indx, 1); + if (!this.fVisibility.length) + delete this.fVisibility; + } else + item.visible = on; + + return; + } + + if (res > 0) { + if (!do_clear) + this.fVisibility.splice(indx, 0, { visible: on, stack }); + return; + } + } + + if (!do_clear) + this.fVisibility.push({ visible: on, stack }); + } + + /** @summary Get visibility item for physical node */ + getPhysNodeVisibility(stack) { + if (!stack || !this.fVisibility) + return null; + for (let indx = 0; indx < this.fVisibility.length; ++indx) { + const item = this.fVisibility[indx], + res = compare_stacks(item.stack, stack); + if (res === 0) + return item; + if (res > 0) + return null; + } + + return null; + } + + /** @summary Scan visible nodes in hierarchy, starting from nodeid + * @desc Each entry in hierarchy get its unique id, which is not changed with visibility flags */ + scanVisible(arg, vislvl) { + if (!this.nodes) + return 0; + + if (vislvl === undefined) { + if (!arg) + arg = {}; + + vislvl = arg.vislvl || this.vislevel || 4; // default 3 in ROOT + if (vislvl > 88) + vislvl = 88; + + 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) { + const entry = { nodeid: this.nodeid, seqid: this.counter, stack: new Array(this.last) }; + if (factor) + entry.factor = factor; // factor used to indicate importance of entry, will be built as first + for (let 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 (this.fVisibility?.length) { + arg.vindx = 0; + arg.varray = this.fVisibility; + arg.vstack = arg.varray[arg.vindx].stack; + arg.testPhysVis = function() { + if (!this.vstack || (this.vstack?.length !== this.last)) + return undefined; + for (let n = 0; n < this.last; ++n) { + if (this.vstack[n] !== this.stack[n + 1]) + return undefined; + } + const res = this.varray[this.vindx++].visible; + this.vstack = this.vindx < this.varray.length ? this.varray[this.vindx].stack : null; + return res; + }; + } + } + + const node = this.nodes[arg.nodeid]; + let res = 0; + + if (arg.domatrix) { + if (!arg.mpool[arg.last + 1]) + arg.mpool[arg.last + 1] = new THREE.Matrix4(); + + const 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; + } + + let node_vis = node.vis, node_nochlds = node.nochlds; + + if ((arg.nodeid === 0) && arg.main_visible) + node_vis = vislvl + 1; + else if (arg.testPhysVis) { + const res2 = arg.testPhysVis(); + if (res2 !== undefined) { + node_vis = res2 && !node.chlds ? vislvl + 1 : 0; + node_nochlds = !res2; + } + } + + if (node_nochlds) + vislvl = 0; + + if (node_vis > vislvl) { + if (!arg.func || arg.func(node)) + res++; + } + + arg.counter++; + + if ((vislvl > 0) && node.chlds) { + arg.last++; + for (let 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 + res += this.scanVisible(arg, vislvl - 1); + } + arg.last--; + } else + arg.counter += (node.idshift || 0); + + + 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; + delete arg.vindx; + delete arg.varray; + delete arg.vstack; + delete arg.testPhysVis; + } + + return res; + } + + /** @summary Return node name with given id. + * @desc Either original object or description is used */ + getNodeName(nodeid) { + if (this.origin) { + const obj = this.origin[nodeid]; + return obj ? getObjectName(obj) : ''; + } + const node = this.nodes[nodeid]; + return node ? node.name : ''; + } + + /** @summary Returns description for provided stack + * @desc If specified, absolute matrix is also calculated */ + resolveStack(stack, withmatrix) { + const res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix || '' }; + + if (withmatrix) { + res.matrix = new THREE.Matrix4(); + if (res.node.matrix) + res.matrix.fromArray(res.node.matrix); + } + + if (this.origin) + res.obj = this.origin[0]; + + // if (!res.name) + // res.name = this.getNodeName(0); + + if (stack) { + for (let lvl = 0; lvl < stack.length; ++lvl) { + res.id = res.node.chlds[stack[lvl]]; + res.node = this.nodes[res.id]; + + if (this.origin) + res.obj = this.origin[res.id]; + + const 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; + } + + /** @summary Provide stack name + * @desc Stack name includes full path to the physical node which is identified by stack */ + getStackName(stack) { + return this.resolveStack(stack).name; + } + + /** @summary Create stack array based on nodes ids array. + * @desc Ids list should correspond to existing nodes hierarchy */ + buildStackByIds(ids) { + if (!ids) + return null; + + if (ids[0]) { + console.error('wrong ids - first should be 0'); + return null; + } + + let node = this.nodes[0]; + const stack = []; + + for (let k = 1; k < ids.length; ++k) { + const nodeid = ids[k]; + if (!node) + return null; + const 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; + } + + /** @summary Returns ids array which correspond to the stack */ + buildIdsByStack(stack) { + if (!stack) + return null; + let node = this.nodes[0]; + const ids = [0]; + for (let k = 0; k < stack.length; ++k) { + const id = node.chlds[stack[k]]; + ids.push(id); + node = this.nodes[id]; + } + return ids; + } + + /** @summary Returns node id by stack */ + getNodeIdByStack(stack) { + if (!stack || !this.nodes) + return -1; + let node = this.nodes[0], id = 0; + for (let k = 0; k < stack.length; ++k) { + id = node.chlds[stack[k]]; + node = this.nodes[id]; + } + return id; + } + + /** @summary Returns true if stack includes at any place provided nodeid */ + isIdInStack(nodeid, stack) { + if (!nodeid) + return true; + + let node = this.nodes[0]; + + for (let lvl = 0; lvl < stack.length; ++lvl) { + const id = node.chlds[stack[lvl]]; + if (id === nodeid) + return true; + node = this.nodes[id]; + } + + return false; + } + + /** @summary Find stack by name which include names of all parents */ + findStackByName(fullname) { + const names = fullname.split('/'), stack = []; + let currid = 0; + + if (this.getNodeName(currid) !== names[0]) + return null; + + for (let n = 1; n < names.length; ++n) { + const node = this.nodes[currid]; + if (!node.chlds) + return null; + + for (let k = 0; k < node.chlds.length; ++k) { + const chldid = node.chlds[k]; + if (this.getNodeName(chldid) === names[n]) { + stack.push(k); + currid = chldid; + break; + } + } + + // no new entry - not found stack + if (stack.length === n - 1) + return null; + } + + return stack; + } + + /** @summary Set usage of default ROOT colors */ + setDefaultColors(on) { + this.use_dflt_colors = on; + if (this.use_dflt_colors && !this.dflt_table) { + const nmax = 110, col = [], dflt = { kGray: 920, + kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kOrange: 800}; + for (let i = 0; i < nmax; i++) + col.push(dflt.kGray); + + // here we should create a new TColor with the same rgb as in the default + // ROOT colors used below + col[3] = dflt.kYellow - 10; + col[4] = col[5] = dflt.kGreen - 10; + col[6] = col[7] = dflt.kBlue - 7; + col[8] = col[9] = dflt.kMagenta - 3; + col[10] = col[11] = dflt.kRed - 10; + col[12] = dflt.kGray + 1; + col[13] = dflt.kBlue - 10; + col[14] = dflt.kOrange + 7; + col[16] = dflt.kYellow + 1; + col[20] = dflt.kYellow - 10; + col[24] = col[25] = col[26] = dflt.kBlue - 8; + col[29] = dflt.kOrange + 9; + col[79] = dflt.kOrange - 2; + + this.dflt_table = col; + } + } + + /** @summary Provide different properties of draw entry nodeid + * @desc Only if node visible, material will be created */ + getDrawEntryProperties(entry, root_colors) { + const clone = this.nodes[entry.nodeid]; + + if (clone.kind === kindShape) { + const prop = { name: clone.name, nname: clone.name, shape: null, material: null, chlds: null }, + opacity = entry.opacity || 1, col = entry.color || '#0000FF'; + prop.fillcolor = new THREE.Color(col[0] === '#' ? col : `rgb(${col})`); + prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); + return prop; + } + + if (!this.origin) { + console.error(`origin not there - kind ${clone.kind} id ${entry.nodeid}`); + return null; + } + + const node = this.origin[entry.nodeid]; + + if (clone.kind === kindEve) { + // special handling for EVE nodes + + const prop = { name: getObjectName(node), nname: getObjectName(node), shape: node.fShape, material: null, chlds: null }; + + if (node.fElements !== null) + prop.chlds = node.fElements.arr; + + { + const opacity = Math.min(1, node.fRGBA[3]); + prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); + prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); + } + + return prop; + } + + const volume = node.fVolume, prop = { + name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null, + chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth + }; + + { + let opacity = 1.0; + if (!root_colors) + root_colors = getRootColors(); + + if (entry.custom_color) + prop.fillcolor = entry.custom_color; + else if ((volume.fFillColor > 1) && (volume.fLineColor === 1)) + prop.fillcolor = root_colors[volume.fFillColor]; + else if (volume.fLineColor >= 0) + prop.fillcolor = root_colors[volume.fLineColor]; + + const mat = volume.fMedium?.fMaterial; + + if (mat) { + const fillstyle = mat.fFillStyle; + let transparency = (fillstyle >= 3000 && fillstyle <= 3100) ? fillstyle - 3000 : 0; + + if (this.use_dflt_colors) { + const matZ = Math.round(mat.fZ), icol = this.dflt_table[matZ]; + prop.fillcolor = root_colors[icol]; + if (mat.fDensity < 0.1) + transparency = 60; + } + + if (transparency > 0) + opacity = (100 - transparency) / 100; + if (prop.fillcolor === undefined) + prop.fillcolor = root_colors[mat.fFillColor]; + } + if (prop.fillcolor === undefined) + prop.fillcolor = 'lightgrey'; + + prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); + } + + return prop; + } + + /** @summary Creates hierarchy of Object3D for given stack entry + * @desc Such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing + * also set renderOrder, required to handle transparency */ + createObject3D(stack, toplevel, options) { + let node = this.nodes[0], three_prnt = toplevel, draw_depth = 0; + const force = isObject(options) || (options === 'force'); + + for (let lvl = 0; lvl <= stack.length; ++lvl) { + const nchld = (lvl > 0) ? stack[lvl - 1] : 0, + // extract current node + child = (lvl > 0) ? this.nodes[node.chlds[nchld]] : node; + if (!child) { + console.error(`Wrong stack ${JSON.stringify(stack)} for nodes at level ${lvl}, node.id ${node.id}, numnodes ${this.nodes.length}, nchld ${nchld}, numchilds ${node.chlds.length}, chldid ${node.chlds[nchld]}`); + return null; + } + + node = child; + + let obj3d; + + if (three_prnt.children) { + for (let 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 (this._cfg?.set_names) + obj3d.name = this.getNodeName(node.id); + + if (this._cfg?.set_origin && this.origin) + obj3d.userData = this.origin[node.id]; + + if (node.abs_matrix) { + obj3d.absMatrix = new THREE.Matrix4(); + obj3d.absMatrix.fromArray(node.matrix); + } else if (node.matrix) { + 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) && isObject(options) && 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 ((options === kGetMesh) || (options === kDeleteMesh)) { + let mesh = null; + if (three_prnt) { + for (let n = 0; (n < three_prnt.children.length) && !mesh; ++n) { + const chld = three_prnt.children[n]; + if ((chld.type === 'Mesh') && (chld.nchld === undefined)) + mesh = chld; + } + } + + if ((options === kGetMesh) || !mesh) + return mesh; + + const res = three_prnt; + while (mesh && (mesh !== toplevel)) { + three_prnt = mesh.parent; + three_prnt.remove(mesh); + mesh = !three_prnt.children.length ? three_prnt : null; + } + + return res; + } + + if (three_prnt) { + three_prnt.$jsroot_drawable = true; + three_prnt.$jsroot_depth = draw_depth; + } + + return three_prnt; + } + + /** @summary Create mesh for single physical node */ + createEntryMesh(ctrl, toplevel, entry, shape, colors) { + if (!shape || !shape.ready) + return null; + + entry.done = true; // mark entry is created + shape.used = true; // indicate that shape was used in building + + if (!shape.geom || !shape.nfaces) { + // node is visible, but shape does not created + this.createObject3D(entry.stack, toplevel, kDeleteMesh); + return null; + } + + const prop = this.getDrawEntryProperties(entry, colors), + obj3d = this.createObject3D(entry.stack, toplevel, ctrl), + matrix = obj3d.absMatrix || obj3d.matrixWorld; + + prop.material.wireframe = ctrl.wireframe; + + prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; + + let mesh; + if (matrix.determinant() > -0.9) + mesh = new THREE.Mesh(shape.geom, prop.material); + else + mesh = createFlippedMesh(shape, prop.material); + + obj3d.add(mesh); + + if (obj3d.absMatrix) { + mesh.matrix.copy(obj3d.absMatrix); + mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale); + mesh.updateMatrixWorld(); + } + + // keep full stack of nodes + mesh.stack = entry.stack; + mesh.renderOrder = this.maxdepth - entry.stack.length; // order of transparency handling + + if (ctrl.set_names) + mesh.name = this.getNodeName(entry.nodeid); + + if (ctrl.set_origin) + mesh.userData = prop.volume; + + // keep hierarchy level + mesh.$jsroot_order = obj3d.$jsroot_depth; + + if (ctrl.info?.num_meshes !== undefined) { + ctrl.info.num_meshes++; + ctrl.info.num_faces += shape.nfaces; + } + + // set initial render order, when camera moves, one must refine it + // mesh.$jsroot_order = mesh.renderOrder = + // this._clones.maxdepth - ((obj3d.$jsroot_depth !== undefined) ? obj3d.$jsroot_depth : entry.stack.length); + + return mesh; + } + + /** @summary Check if instancing can be used for the nodes */ + createInstancedMeshes(ctrl, toplevel, draw_nodes, build_shapes, colors) { + if (ctrl.instancing < 0) + return false; + + // first delete previous data + const used_shapes = []; + let max_entries = 1; + + for (let n = 0; n < draw_nodes.length; ++n) { + const entry = draw_nodes[n]; + if (entry.done) + continue; + + // shape can be provided with entry itself + const shape = entry.server_shape || build_shapes[entry.shapeid]; + if (!shape || !shape.ready) { + console.warn(`Problem with shape id ${entry.shapeid} when building`); + return false; + } + + // ignore shape without geometry + if (!shape.geom || !shape.nfaces) + continue; + + if (shape.instances === undefined) { + shape.instances = []; + used_shapes.push(shape); + } + + const instance = shape.instances.find(i => i.nodeid === entry.nodeid); + + if (instance) { + instance.entries.push(entry); + max_entries = Math.max(max_entries, instance.entries.length); + } else + shape.instances.push({ nodeid: entry.nodeid, entries: [entry] }); + } + + const make_sense = ctrl.instancing > 0 ? (max_entries > 2) : (draw_nodes.length > 10000) && (max_entries > 10); + + if (!make_sense) { + used_shapes.forEach(shape => { delete shape.instances; }); + return false; + } + + used_shapes.forEach(shape => { + shape.used = true; + shape.instances.forEach(instance => { + const entry0 = instance.entries[0], + prop = this.getDrawEntryProperties(entry0, colors); + + prop.material.wireframe = ctrl.wireframe; + + prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; + + if (instance.entries.length === 1) + this.createEntryMesh(ctrl, toplevel, entry0, shape, colors); + else { + const arr1 = [], arr2 = [], stacks1 = [], stacks2 = [], names1 = [], names2 = []; + + instance.entries.forEach(entry => { + const info = this.resolveStack(entry.stack, true); + if (info.matrix.determinant() > -0.9) { + arr1.push(info.matrix); + stacks1.push(entry.stack); + names1.push(this.getNodeName(entry.nodeid)); + } else { + arr2.push(info.matrix); + stacks2.push(entry.stack); + names2.push(this.getNodeName(entry.nodeid)); + } + entry.done = true; + }); + + if (arr1.length) { + const mesh1 = new THREE.InstancedMesh(shape.geom, prop.material, arr1.length); + + mesh1.stacks = stacks1; + arr1.forEach((matrix, i) => mesh1.setMatrixAt(i, matrix)); + + toplevel.add(mesh1); + + mesh1.renderOrder = 1; + + if (ctrl.set_names) { + mesh1.name = names1[0]; + mesh1.names = names1; + } + + if (ctrl.set_origin) + mesh1.userData = prop.volume; + + mesh1.$jsroot_order = 1; + ctrl.info.num_meshes++; + ctrl.info.num_faces += shape.nfaces * arr1.length; + } + + if (arr2.length) { + if (shape.geomZ === undefined) + shape.geomZ = createFlippedGeom(shape.geom); + + const mesh2 = new THREE.InstancedMesh(shape.geomZ, prop.material, arr2.length); + + mesh2.stacks = stacks2; + const m = new THREE.Matrix4().makeScale(1, 1, -1); + arr2.forEach((matrix, i) => { + mesh2.setMatrixAt(i, matrix.multiply(m)); + }); + mesh2._flippedMesh = true; + + toplevel.add(mesh2); + + mesh2.renderOrder = 1; + if (ctrl.set_names) { + mesh2.name = names2[0]; + mesh2.names = names2; + } + if (ctrl.set_origin) + mesh2.userData = prop.volume; + + mesh2.$jsroot_order = 1; + ctrl.info.num_meshes++; + ctrl.info.num_faces += shape.nfaces * arr2.length; + } + } + }); + + delete shape.instances; + }); + + return true; + } + + /** @summary Get volume boundary */ + getVolumeBoundary(viscnt, facelimit, nodeslimit) { + const result = { min: 0, max: 1, sortidcut: 0 }; + + if (!this.sortmap) { + console.error('sorting map do not exist'); + return result; + } + + let maxNode, currNode, cnt = 0, facecnt = 0; + + for (let n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { + const 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; + } + + result.max = maxNode.vol; + result.min = currNode.vol; + result.sortidcut = currNode.sortid; // latest node is not included + return result; + } + + /** @summary Collects visible nodes, using maxlimit + * @desc One can use map to define cut based on the volume or serious of cuts */ + collectVisibles(maxnumfaces, frustum) { + // in simple case shape as it is + if (this.plain_shape) + return { lst: [{ nodeid: 0, seqid: 0, stack: [], factor: 1, shapeid: 0, server_shape: this.plain_shape }], complete: true }; + + const arg = { + facecnt: 0, + viscnt: new Array(this.nodes.length), // counter for each node + vislvl: this.getVisLevel(), + reset() { + this.total = 0; + this.facecnt = 0; + this.viscnt.fill(0); + }, + func(node) { + this.total++; + this.facecnt += node.nfaces; + this.viscnt[node.id]++; + return true; + } + }; + + arg.reset(); + + let total = this.scanVisible(arg); + if ((total === 0) && (this.nodes[0].vis < 2) && !this.nodes[0].nochlds) { + // try to draw only main node by default + arg.reset(); + arg.main_visible = true; + total = this.scanVisible(arg); + } + + const maxnumnodes = this.getMaxVisNodes(); + + if (maxnumnodes > 0) { + while ((total > maxnumnodes) && (arg.vislvl > 1)) { + arg.vislvl--; + arg.reset(); + total = this.scanVisible(arg); + } + } + + this.actual_level = arg.vislvl; // not used, can be shown somewhere in the gui + + let minVol = 0, maxVol, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1; + + if (arg.facecnt > maxnumfaces) { + const bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), + bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0), + // define minimal volume, which always to shown + 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 (let 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); + } + } + + 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 }; + } + + /** @summary Merge list of drawn objects + * @desc In current list we should mark if object already exists + * from previous list we should collect objects which are not there */ + mergeVisibles(current, prev) { + let indx2 = 0; + const del = []; + for (let 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; + } + + /** @summary Collect all uniques shapes which should be built + * @desc Check if same shape used many times for drawing */ + collectShapes(lst) { + // nothing else - just that single shape + if (this.plain_shape) + return [this.plain_shape]; + + const shapes = []; + + for (let i = 0; i < lst.length; ++i) { + const entry = lst[i], + 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, 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((a, b) => b.vol * b.factor - a.vol * a.factor); + + // now set new shape ids according to the sorted order and delete temporary field + for (let n = 0; n < shapes.length; ++n) { + const 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 (let i = 0; i < lst.length; ++i) { + const entry = lst[i]; + if (entry.shape) { + entry.shapeid = entry.shape.id; // keep only id for the entry + delete entry.shape; // remove direct references + } + } + + return shapes; + } + + /** @summary Merge shape lists */ + mergeShapesLists(oldlst, newlst) { + if (!oldlst) + return newlst; + + // set geometry to shape object itself + for (let n = 0; n < oldlst.length; ++n) { + const 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 (let n = 0; n < newlst.length; ++n) { + const 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 (let n = 0; n < oldlst.length; ++n) { + const item = oldlst[n]; + delete item.shape._geom; + delete item.shape._geomZ; + } + + return newlst; + } + + /** @summary Build shapes */ + buildShapes(lst, limit, timelimit) { + let created = 0; + const tm1 = new Date().getTime(), + res = { done: false, shapes: 0, faces: 0, notusedshapes: 0 }; + + for (let n = 0; n < lst.length; ++n) { + const item = lst[n]; + + // if enough faces are produced, nothing else is required + if (res.done) { + item.ready = true; + continue; + } + + if (!item.ready) { + item._typename = kShapeType; // let reuse item for direct drawing + item.ready = true; + if (item.geom === undefined) { + item.geom = createGeometry(item.shape); + if (item.geom) + created++; // indicate that at least one shape was created + } + item.nfaces = numGeometryFaces(item.geom); + } + + 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)) { + const tm2 = new Date().getTime(); + if (tm2 - tm1 > timelimit) + return res; + } + } + + res.done = true; + + return res; + } + + /** @summary Format REveGeomNode data to be able use it in list of clones + * @private */ + static formatServerElement(elem) { + elem.kind = 2; // special element for geom viewer, used in TGeoPainter + elem.vis = 2; // visibility is always on + const m = elem.matr; + delete elem.matr; + if (!m?.length) + return elem; + + if (m.length === 16) + elem.matrix = m; + else { + const nm = elem.matrix = new Array(16); + nm.fill(0); + nm[0] = nm[5] = nm[10] = nm[15] = 1; + + if (m.length === 3) { + // translation matrix + nm[12] = m[0]; + nm[13] = m[1]; + nm[14] = m[2]; + } else if (m.length === 4) { + // scale matrix + nm[0] = m[0]; + nm[5] = m[1]; + nm[10] = m[2]; + nm[15] = m[3]; + } else if (m.length === 9) { + // rotation matrix + nm[0] = m[0]; + nm[4] = m[1]; + nm[8] = m[2]; + nm[1] = m[3]; + nm[5] = m[4]; + nm[9] = m[5]; + nm[2] = m[6]; + nm[6] = m[7]; + nm[10] = m[8]; + } else + console.error(`wrong number of elements ${m.length} in the matrix`); + } + return elem; + } + +} // class ClonedNodes + +/** @summary extract code of Box3.expandByObject + * @desc Major difference - do not traverse hierarchy, support InstancedMesh + * @private */ +function getBoundingBox(node, box3, local_coordinates) { + if (!node?.geometry) + return box3; + + if (!box3) + box3 = new THREE.Box3().makeEmpty(); + + if (node.isInstancedMesh) { + const m = new THREE.Matrix4(), b = new THREE.Box3().makeEmpty(); + + node.geometry.computeBoundingBox(); + + for (let i = 0; i < node.count; i++) { + node.getMatrixAt(i, m); + b.copy(node.geometry.boundingBox).applyMatrix4(m); + box3.union(b); + } + return box3; + } + + if (!local_coordinates) + node.updateWorldMatrix(false, false); + + const v1 = new THREE.Vector3(), attribute = node.geometry.attributes?.position; + + if (attribute !== undefined) { + for (let i = 0, l = attribute.count; i < l; i++) { + // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); + v1.fromBufferAttribute(attribute, i); + if (!local_coordinates) + v1.applyMatrix4(node.matrixWorld); + box3.expandByPoint(v1); + } + } + + return box3; +} + +/** @summary Cleanup shape entity + * @private */ +function cleanupShape(shape) { + if (!shape) + return; + + if (isFunc(shape.geom?.dispose)) + shape.geom.dispose(); + + if (isFunc(shape.geomZ?.dispose)) + shape.geomZ.dispose(); + + delete shape.geom; + delete shape.geomZ; +} + +/** @summary Set rendering order for created hierarchy + * @desc depending from provided method sort differently objects + * @param toplevel - top element + * @param origin - camera position used to provide sorting + * @param method - name of sorting method like 'pnt', 'ray', 'size', 'dflt' */ +function produceRenderOrder(toplevel, origin, method, clones) { + const raycast = new THREE.Raycaster(); + + function setdefaults(top) { + top?.traverse(obj => { + obj.renderOrder = obj.defaultOrder || 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 (let k = 0; k < obj.children.length; ++k) { + const 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 > 1000) { + // too many of them, just set basic level and exit + for (let i = 0; i < arr.length; ++i) + arr[i].renderOrder = (minorder + maxorder) / 2; + return false; + } + + const tmp_vect = new THREE.Vector3(); + + // first calculate distance to the camera + // it gives preliminary order of volumes + for (let i = 0; i < arr.length; ++i) { + const mesh = arr[i]; + let box3 = mesh.$jsroot_box3; + + if (!box3) + mesh.$jsroot_box3 = box3 = getBoundingBox(mesh); + + if (method === 'size') { + const sz = box3.getSize(new THREE.Vector3()); + mesh.$jsroot_distance = sz.x * sz.y * sz.z; + continue; + } + + if (method === 'pnt') { + mesh.$jsroot_distance = origin.distanceTo(box3.getCenter(tmp_vect)); + continue; + } + + let dist = Math.min(origin.distanceTo(box3.min), origin.distanceTo(box3.max)); + const 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((a, b) => a.$jsroot_distance - b.$jsroot_distance); + + const resort = new Array(arr.length); + + for (let i = 0; i < arr.length; ++i) { + arr[i].$jsroot_index = i; + resort[i] = arr[i]; + } + + if (method === 'ray') { + for (let i = arr.length - 1; i >= 0; --i) { + const mesh = arr[i], box3 = mesh.$jsroot_box3; + let intersects, direction = box3.getCenter(tmp_vect); + + for (let ntry = 0; ntry < 2; ++ntry) { + direction.sub(origin).normalize(); + + raycast.set(origin, direction); + + intersects = raycast.intersectObjects(arr, false) || []; // only plain array + const unique = []; + + for (let k1 = 0; k1 < intersects.length; ++k1) { + if (unique.indexOf(intersects[k1].object) < 0) + unique.push(intersects[k1].object); + } + + intersects = unique; + + if ((intersects.indexOf(mesh) < 0) && (ntry > 0)) + console.log(`MISS ${clones?.resolveStack(mesh.stack)?.name}`); + + if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) + break; + + const 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 (let k1 = 0; k1 < intersects.length - 1; ++k1) { + const mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], + i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; + if (i1 < i2) + continue; + for (let ii = i2; ii < i1; ++ii) { + resort[ii] = resort[ii + 1]; + resort[ii].$jsroot_index = ii; + } + resort[i1] = mesh2; + mesh2.$jsroot_index = i1; + } + } + } + + for (let i = 0; i < resort.length; ++i) { + resort[i].renderOrder = Math.round(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) { + const arr = []; + let did_sort = false; + + traverse(obj, lvl, arr); + + if (!arr.length) + return; + + if (minorder === maxorder) { + for (let 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 (let k = 0; k < arr.length; ++k) { + const next = arr[k].parent; + let 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); +} + +/** @summary provide icon name for the shape + * @private */ +function getShapeIcon(shape) { + switch (shape._typename) { + case clTGeoArb8: return 'img_geoarb8'; + case clTGeoCone: return 'img_geocone'; + case clTGeoConeSeg: return 'img_geoconeseg'; + case clTGeoCompositeShape: return 'img_geocomposite'; + case clTGeoTube: return 'img_geotube'; + case clTGeoTubeSeg: return 'img_geotubeseg'; + case clTGeoPara: return 'img_geopara'; + case clTGeoParaboloid: return 'img_geoparab'; + case clTGeoPcon: return 'img_geopcon'; + case clTGeoPgon: return 'img_geopgon'; + case clTGeoShapeAssembly: return 'img_geoassembly'; + case clTGeoSphere: return 'img_geosphere'; + case clTGeoTorus: return 'img_geotorus'; + case clTGeoTrd1: return 'img_geotrd1'; + case clTGeoTrd2: return 'img_geotrd2'; + case clTGeoXtru: return 'img_geoxtru'; + case clTGeoTrap: return 'img_geotrap'; + case clTGeoGtra: return 'img_geogtra'; + case clTGeoEltu: return 'img_geoeltu'; + case clTGeoHype: return 'img_geohype'; + case clTGeoCtub: return 'img_geoctub'; + } + return 'img_geotube'; +} + +/** + * lil-gui + * https://lil-gui.georgealways.com + * @version 0.20.0 + * @author George Michael Brower + * @license MIT + */ + +/** + * Base class for all controllers. + */ +class Controller { + + constructor( parent, object, property, className, elementType = 'div' ) { + + /** + * The GUI that contains this controller. + * @type {GUI} + */ + this.parent = parent; + + /** + * The object this controller will modify. + * @type {object} + */ + this.object = object; + + /** + * The name of the property to control. + * @type {string} + */ + this.property = property; + + /** + * Used to determine if the controller is disabled. + * Use `controller.disable( true|false )` to modify this value. + * @type {boolean} + */ + this._disabled = false; + + /** + * Used to determine if the Controller is hidden. + * Use `controller.show()` or `controller.hide()` to change this. + * @type {boolean} + */ + this._hidden = false; + + /** + * The value of `object[ property ]` when the controller was created. + * @type {any} + */ + this.initialValue = this.getValue(); + + /** + * The outermost container DOM element for this controller. + * @type {HTMLElement} + */ + this.domElement = document.createElement( elementType ); + this.domElement.classList.add( 'controller' ); + this.domElement.classList.add( className ); + + /** + * The DOM element that contains the controller's name. + * @type {HTMLElement} + */ + this.$name = document.createElement( 'div' ); + this.$name.classList.add( 'name' ); + + Controller.nextNameID = Controller.nextNameID || 0; + this.$name.id = `lil-gui-name-${++Controller.nextNameID}`; + + /** + * The DOM element that contains the controller's "widget" (which differs by controller type). + * @type {HTMLElement} + */ + this.$widget = document.createElement( 'div' ); + this.$widget.classList.add( 'widget' ); + + /** + * The DOM element that receives the disabled attribute when using disable(). + * @type {HTMLElement} + */ + this.$disable = this.$widget; + + this.domElement.appendChild( this.$name ); + this.domElement.appendChild( this.$widget ); + + // Don't fire global key events while typing in a controller + this.domElement.addEventListener( 'keydown', e => e.stopPropagation() ); + this.domElement.addEventListener( 'keyup', e => e.stopPropagation() ); + + this.parent.children.push( this ); + this.parent.controllers.push( this ); + + this.parent.$children.appendChild( this.domElement ); + + this._listenCallback = this._listenCallback.bind( this ); + + this.name( property ); + + } + + /** + * Sets the name of the controller and its label in the GUI. + * @param {string} name + * @returns {this} + */ + name( name ) { + /** + * The controller's name. Use `controller.name( 'Name' )` to modify this value. + * @type {string} + */ + this._name = name; + this.$name.textContent = name; + return this; + } + + /** + * Pass a function to be called whenever the value is modified by this controller. + * The function receives the new value as its first parameter. The value of `this` will be the + * controller. + * + * For function controllers, the `onChange` callback will be fired on click, after the function + * executes. + * @param {Function} callback + * @returns {this} + * @example + * const controller = gui.add( object, 'property' ); + * + * controller.onChange( function( v ) { + * console.log( 'The value is now ' + v ); + * console.assert( this === controller ); + * } ); + */ + onChange( callback ) { + /** + * Used to access the function bound to `onChange` events. Don't modify this value directly. + * Use the `controller.onChange( callback )` method instead. + * @type {Function} + */ + this._onChange = callback; + return this; + } + + /** + * Calls the onChange methods of this controller and its parent GUI. + * @protected + */ + _callOnChange() { + + this.parent._callOnChange( this ); + + if ( this._onChange !== undefined ) { + this._onChange.call( this, this.getValue() ); + } + + this._changed = true; + + } + + /** + * Pass a function to be called after this controller has been modified and loses focus. + * @param {Function} callback + * @returns {this} + * @example + * const controller = gui.add( object, 'property' ); + * + * controller.onFinishChange( function( v ) { + * console.log( 'Changes complete: ' + v ); + * console.assert( this === controller ); + * } ); + */ + onFinishChange( callback ) { + /** + * Used to access the function bound to `onFinishChange` events. Don't modify this value + * directly. Use the `controller.onFinishChange( callback )` method instead. + * @type {Function} + */ + this._onFinishChange = callback; + return this; + } + + /** + * Should be called by Controller when its widgets lose focus. + * @protected + */ + _callOnFinishChange() { + + if ( this._changed ) { + + this.parent._callOnFinishChange( this ); + + if ( this._onFinishChange !== undefined ) { + this._onFinishChange.call( this, this.getValue() ); + } + + } + + this._changed = false; + + } + + /** + * Sets the controller back to its initial value. + * @returns {this} + */ + reset() { + this.setValue( this.initialValue ); + this._callOnFinishChange(); + return this; + } + + /** + * Enables this controller. + * @param {boolean} enabled + * @returns {this} + * @example + * controller.enable(); + * controller.enable( false ); // disable + * controller.enable( controller._disabled ); // toggle + */ + enable( enabled = true ) { + return this.disable( !enabled ); + } + + /** + * Disables this controller. + * @param {boolean} disabled + * @returns {this} + * @example + * controller.disable(); + * controller.disable( false ); // enable + * controller.disable( !controller._disabled ); // toggle + */ + disable( disabled = true ) { + + if ( disabled === this._disabled ) return this; + + this._disabled = disabled; + + this.domElement.classList.toggle( 'disabled', disabled ); + this.$disable.toggleAttribute( 'disabled', disabled ); + + return this; + + } + + /** + * Shows the Controller after it's been hidden. + * @param {boolean} show + * @returns {this} + * @example + * controller.show(); + * controller.show( false ); // hide + * controller.show( controller._hidden ); // toggle + */ + show( show = true ) { + + this._hidden = !show; + + this.domElement.style.display = this._hidden ? 'none' : ''; + + return this; + + } + + /** + * Hides the Controller. + * @returns {this} + */ + hide() { + return this.show( false ); + } + + /** + * Changes this controller into a dropdown of options. + * + * Calling this method on an option controller will simply update the options. However, if this + * controller was not already an option controller, old references to this controller are + * destroyed, and a new controller is added to the end of the GUI. + * @example + * // safe usage + * + * gui.add( obj, 'prop1' ).options( [ 'a', 'b', 'c' ] ); + * gui.add( obj, 'prop2' ).options( { Big: 10, Small: 1 } ); + * gui.add( obj, 'prop3' ); + * + * // danger + * + * const ctrl1 = gui.add( obj, 'prop1' ); + * gui.add( obj, 'prop2' ); + * + * // calling options out of order adds a new controller to the end... + * const ctrl2 = ctrl1.options( [ 'a', 'b', 'c' ] ); + * + * // ...and ctrl1 now references a controller that doesn't exist + * assert( ctrl2 !== ctrl1 ) + * @param {object|Array} options + * @returns {Controller} + */ + options( options ) { + const controller = this.parent.add( this.object, this.property, options ); + controller.name( this._name ); + this.destroy(); + return controller; + } + + /** + * Sets the minimum value. Only works on number controllers. + * @param {number} min + * @returns {this} + */ + min( min ) { + return this; + } + + /** + * Sets the maximum value. Only works on number controllers. + * @param {number} max + * @returns {this} + */ + max( max ) { + return this; + } + + /** + * Values set by this controller will be rounded to multiples of `step`. Only works on number + * controllers. + * @param {number} step + * @returns {this} + */ + step( step ) { + return this; + } + + /** + * Rounds the displayed value to a fixed number of decimals, without affecting the actual value + * like `step()`. Only works on number controllers. + * @example + * gui.add( object, 'property' ).listen().decimals( 4 ); + * @param {number} decimals + * @returns {this} + */ + decimals( decimals ) { + return this; + } + + /** + * Calls `updateDisplay()` every animation frame. Pass `false` to stop listening. + * @param {boolean} listen + * @returns {this} + */ + listen( listen = true ) { + + /** + * Used to determine if the controller is currently listening. Don't modify this value + * directly. Use the `controller.listen( true|false )` method instead. + * @type {boolean} + */ + this._listening = listen; + + if ( this._listenCallbackID !== undefined ) { + cancelAnimationFrame( this._listenCallbackID ); + this._listenCallbackID = undefined; + } + + if ( this._listening ) { + this._listenCallback(); + } + + return this; + + } + + _listenCallback() { + + this._listenCallbackID = requestAnimationFrame( this._listenCallback ); + + // To prevent framerate loss, make sure the value has changed before updating the display. + // Note: save() is used here instead of getValue() only because of ColorController. The !== operator + // won't work for color objects or arrays, but ColorController.save() always returns a string. + + const curValue = this.save(); + + if ( curValue !== this._listenPrevValue ) { + this.updateDisplay(); + } + + this._listenPrevValue = curValue; + + } + + /** + * Returns `object[ property ]`. + * @returns {any} + */ + getValue() { + return this.object[ this.property ]; + } + + /** + * Sets the value of `object[ property ]`, invokes any `onChange` handlers and updates the display. + * @param {any} value + * @returns {this} + */ + setValue( value ) { + + if ( this.getValue() !== value ) { + + this.object[ this.property ] = value; + this._callOnChange(); + this.updateDisplay(); + + } + + return this; + + } + + /** + * Updates the display to keep it in sync with the current value. Useful for updating your + * controllers when their values have been modified outside of the GUI. + * @returns {this} + */ + updateDisplay() { + return this; + } + + load( value ) { + this.setValue( value ); + this._callOnFinishChange(); + return this; + } + + save() { + return this.getValue(); + } + + /** + * Destroys this controller and removes it from the parent GUI. + */ + destroy() { + this.listen( false ); + this.parent.children.splice( this.parent.children.indexOf( this ), 1 ); + this.parent.controllers.splice( this.parent.controllers.indexOf( this ), 1 ); + this.parent.$children.removeChild( this.domElement ); + } + +} + +class BooleanController extends Controller { + + constructor( parent, object, property ) { + + super( parent, object, property, 'boolean', 'label' ); + + this.$input = document.createElement( 'input' ); + this.$input.setAttribute( 'type', 'checkbox' ); + this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + + this.$widget.appendChild( this.$input ); + + this.$input.addEventListener( 'change', () => { + this.setValue( this.$input.checked ); + this._callOnFinishChange(); + } ); + + this.$disable = this.$input; + + this.updateDisplay(); + + } + + updateDisplay() { + this.$input.checked = this.getValue(); + return this; + } + +} + +function normalizeColorString( string ) { + + let match, result; + + if ( match = string.match( /(#|0x)?([a-f0-9]{6})/i ) ) { + + result = match[ 2 ]; + + } else if ( match = string.match( /rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/ ) ) { + + result = parseInt( match[ 1 ] ).toString( 16 ).padStart( 2, 0 ) + + parseInt( match[ 2 ] ).toString( 16 ).padStart( 2, 0 ) + + parseInt( match[ 3 ] ).toString( 16 ).padStart( 2, 0 ); + + } else if ( match = string.match( /^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i ) ) { + + result = match[ 1 ] + match[ 1 ] + match[ 2 ] + match[ 2 ] + match[ 3 ] + match[ 3 ]; + + } + + if ( result ) { + return '#' + result; + } + + return false; + +} + +const STRING = { + isPrimitive: true, + match: v => typeof v === 'string', + fromHexString: normalizeColorString, + toHexString: normalizeColorString +}; + +const INT = { + isPrimitive: true, + match: v => typeof v === 'number', + fromHexString: string => parseInt( string.substring( 1 ), 16 ), + toHexString: value => '#' + value.toString( 16 ).padStart( 6, 0 ) +}; + +const ARRAY = { + isPrimitive: false, + + // The arrow function is here to appease tree shakers like esbuild or webpack. + // See https://esbuild.github.io/api/#tree-shaking + match: v => Array.isArray( v ), + + fromHexString( string, target, rgbScale = 1 ) { + + const int = INT.fromHexString( string ); + + target[ 0 ] = ( int >> 16 & 255 ) / 255 * rgbScale; + target[ 1 ] = ( int >> 8 & 255 ) / 255 * rgbScale; + target[ 2 ] = ( int & 255 ) / 255 * rgbScale; + + }, + toHexString( [ r, g, b ], rgbScale = 1 ) { + + rgbScale = 255 / rgbScale; + + const int = ( r * rgbScale ) << 16 ^ + ( g * rgbScale ) << 8 ^ + ( b * rgbScale ) << 0; + + return INT.toHexString( int ); + + } +}; + +const OBJECT = { + isPrimitive: false, + match: v => Object( v ) === v, + fromHexString( string, target, rgbScale = 1 ) { + + const int = INT.fromHexString( string ); + + target.r = ( int >> 16 & 255 ) / 255 * rgbScale; + target.g = ( int >> 8 & 255 ) / 255 * rgbScale; + target.b = ( int & 255 ) / 255 * rgbScale; + + }, + toHexString( { r, g, b }, rgbScale = 1 ) { + + rgbScale = 255 / rgbScale; + + const int = ( r * rgbScale ) << 16 ^ + ( g * rgbScale ) << 8 ^ + ( b * rgbScale ) << 0; + + return INT.toHexString( int ); + + } +}; + +const FORMATS = [ STRING, INT, ARRAY, OBJECT ]; + +function getColorFormat( value ) { + return FORMATS.find( format => format.match( value ) ); +} + +class ColorController extends Controller { + + constructor( parent, object, property, rgbScale ) { + + super( parent, object, property, 'color' ); + + this.$input = document.createElement( 'input' ); + this.$input.setAttribute( 'type', 'color' ); + this.$input.setAttribute( 'tabindex', -1 ); + this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + + this.$text = document.createElement( 'input' ); + this.$text.setAttribute( 'type', 'text' ); + this.$text.setAttribute( 'spellcheck', 'false' ); + this.$text.setAttribute( 'aria-labelledby', this.$name.id ); + + this.$display = document.createElement( 'div' ); + this.$display.classList.add( 'display' ); + + this.$display.appendChild( this.$input ); + this.$widget.appendChild( this.$display ); + this.$widget.appendChild( this.$text ); + + this._format = getColorFormat( this.initialValue ); + this._rgbScale = rgbScale; + + this._initialValueHexString = this.save(); + this._textFocused = false; + + this.$input.addEventListener( 'input', () => { + this._setValueFromHexString( this.$input.value ); + } ); + + this.$input.addEventListener( 'blur', () => { + this._callOnFinishChange(); + } ); + + this.$text.addEventListener( 'input', () => { + const tryParse = normalizeColorString( this.$text.value ); + if ( tryParse ) { + this._setValueFromHexString( tryParse ); + } + } ); + + this.$text.addEventListener( 'focus', () => { + this._textFocused = true; + this.$text.select(); + } ); + + this.$text.addEventListener( 'blur', () => { + this._textFocused = false; + this.updateDisplay(); + this._callOnFinishChange(); + } ); + + this.$disable = this.$text; + + this.updateDisplay(); + + } + + reset() { + this._setValueFromHexString( this._initialValueHexString ); + return this; + } + + _setValueFromHexString( value ) { + + if ( this._format.isPrimitive ) { + + const newValue = this._format.fromHexString( value ); + this.setValue( newValue ); + + } else { + + this._format.fromHexString( value, this.getValue(), this._rgbScale ); + this._callOnChange(); + this.updateDisplay(); + + } + + } + + save() { + return this._format.toHexString( this.getValue(), this._rgbScale ); + } + + load( value ) { + this._setValueFromHexString( value ); + this._callOnFinishChange(); + return this; + } + + updateDisplay() { + this.$input.value = this._format.toHexString( this.getValue(), this._rgbScale ); + if ( !this._textFocused ) { + this.$text.value = this.$input.value.substring( 1 ); + } + this.$display.style.backgroundColor = this.$input.value; + return this; + } + +} + +class FunctionController extends Controller { + + constructor( parent, object, property ) { + + super( parent, object, property, 'function' ); + + // Buttons are the only case where widget contains name + this.$button = document.createElement( 'button' ); + this.$button.appendChild( this.$name ); + this.$widget.appendChild( this.$button ); + + this.$button.addEventListener( 'click', e => { + e.preventDefault(); + this.getValue().call( this.object ); + this._callOnChange(); + } ); + + // enables :active pseudo class on mobile + this.$button.addEventListener( 'touchstart', () => {}, { passive: true } ); + + this.$disable = this.$button; + + } + +} + +class NumberController extends Controller { + + constructor( parent, object, property, min, max, step ) { + + super( parent, object, property, 'number' ); + + this._initInput(); + + this.min( min ); + this.max( max ); + + const stepExplicit = step !== undefined; + this.step( stepExplicit ? step : this._getImplicitStep(), stepExplicit ); + + this.updateDisplay(); + + } + + decimals( decimals ) { + this._decimals = decimals; + this.updateDisplay(); + return this; + } + + min( min ) { + this._min = min; + this._onUpdateMinMax(); + return this; + } + + max( max ) { + this._max = max; + this._onUpdateMinMax(); + return this; + } + + step( step, explicit = true ) { + this._step = step; + this._stepExplicit = explicit; + return this; + } + + updateDisplay() { + + const value = this.getValue(); + + if ( this._hasSlider ) { + + let percent = ( value - this._min ) / ( this._max - this._min ); + percent = Math.max( 0, Math.min( percent, 1 ) ); + + this.$fill.style.width = percent * 100 + '%'; + + } + + if ( !this._inputFocused ) { + this.$input.value = this._decimals === undefined ? value : value.toFixed( this._decimals ); + } + + return this; + + } + + _initInput() { + + this.$input = document.createElement( 'input' ); + this.$input.setAttribute( 'type', 'text' ); + this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + + // On touch devices only, use input[type=number] to force a numeric keyboard. + // Ideally we could use one input type everywhere, but [type=number] has quirks + // on desktop, and [inputmode=decimal] has quirks on iOS. + // See https://github.com/georgealways/lil-gui/pull/16 + + const isTouch = window.matchMedia( '(pointer: coarse)' ).matches; + + if ( isTouch ) { + this.$input.setAttribute( 'type', 'number' ); + this.$input.setAttribute( 'step', 'any' ); + } + + this.$widget.appendChild( this.$input ); + + this.$disable = this.$input; + + const onInput = () => { + + let value = parseFloat( this.$input.value ); + + if ( isNaN( value ) ) return; + + if ( this._stepExplicit ) { + value = this._snap( value ); + } + + this.setValue( this._clamp( value ) ); + + }; + + // Keys & mouse wheel + // --------------------------------------------------------------------- + + const increment = delta => { + + const value = parseFloat( this.$input.value ); + + if ( isNaN( value ) ) return; + + this._snapClampSetValue( value + delta ); + + // Force the input to updateDisplay when it's focused + this.$input.value = this.getValue(); + + }; + + const onKeyDown = e => { + // Using `e.key` instead of `e.code` also catches NumpadEnter + if ( e.key === 'Enter' ) { + this.$input.blur(); + } + if ( e.code === 'ArrowUp' ) { + e.preventDefault(); + increment( this._step * this._arrowKeyMultiplier( e ) ); + } + if ( e.code === 'ArrowDown' ) { + e.preventDefault(); + increment( this._step * this._arrowKeyMultiplier( e ) * -1 ); + } + }; + + const onWheel = e => { + if ( this._inputFocused ) { + e.preventDefault(); + increment( this._step * this._normalizeMouseWheel( e ) ); + } + }; + + // Vertical drag + // --------------------------------------------------------------------- + + let testingForVerticalDrag = false, + initClientX, + initClientY, + prevClientY, + initValue, + dragDelta; + + // Once the mouse is dragged more than DRAG_THRESH px on any axis, we decide + // on the user's intent: horizontal means highlight, vertical means drag. + const DRAG_THRESH = 5; + + const onMouseDown = e => { + + initClientX = e.clientX; + initClientY = prevClientY = e.clientY; + testingForVerticalDrag = true; + + initValue = this.getValue(); + dragDelta = 0; + + window.addEventListener( 'mousemove', onMouseMove ); + window.addEventListener( 'mouseup', onMouseUp ); + + }; + + const onMouseMove = e => { + + if ( testingForVerticalDrag ) { + + const dx = e.clientX - initClientX; + const dy = e.clientY - initClientY; + + if ( Math.abs( dy ) > DRAG_THRESH ) { + + e.preventDefault(); + this.$input.blur(); + testingForVerticalDrag = false; + this._setDraggingStyle( true, 'vertical' ); + + } else if ( Math.abs( dx ) > DRAG_THRESH ) { + + onMouseUp(); + + } + + } + + // This isn't an else so that the first move counts towards dragDelta + if ( !testingForVerticalDrag ) { + + const dy = e.clientY - prevClientY; + + dragDelta -= dy * this._step * this._arrowKeyMultiplier( e ); + + // Clamp dragDelta so we don't have 'dead space' after dragging past bounds. + // We're okay with the fact that bounds can be undefined here. + if ( initValue + dragDelta > this._max ) { + dragDelta = this._max - initValue; + } else if ( initValue + dragDelta < this._min ) { + dragDelta = this._min - initValue; + } + + this._snapClampSetValue( initValue + dragDelta ); + + } + + prevClientY = e.clientY; + + }; + + const onMouseUp = () => { + this._setDraggingStyle( false, 'vertical' ); + this._callOnFinishChange(); + window.removeEventListener( 'mousemove', onMouseMove ); + window.removeEventListener( 'mouseup', onMouseUp ); + }; + + // Focus state & onFinishChange + // --------------------------------------------------------------------- + + const onFocus = () => { + this._inputFocused = true; + }; + + const onBlur = () => { + this._inputFocused = false; + this.updateDisplay(); + this._callOnFinishChange(); + }; + + this.$input.addEventListener( 'input', onInput ); + this.$input.addEventListener( 'keydown', onKeyDown ); + this.$input.addEventListener( 'wheel', onWheel, { passive: false } ); + this.$input.addEventListener( 'mousedown', onMouseDown ); + this.$input.addEventListener( 'focus', onFocus ); + this.$input.addEventListener( 'blur', onBlur ); + + } + + _initSlider() { + + this._hasSlider = true; + + // Build DOM + // --------------------------------------------------------------------- + + this.$slider = document.createElement( 'div' ); + this.$slider.classList.add( 'slider' ); + + this.$fill = document.createElement( 'div' ); + this.$fill.classList.add( 'fill' ); + + this.$slider.appendChild( this.$fill ); + this.$widget.insertBefore( this.$slider, this.$input ); + + this.domElement.classList.add( 'hasSlider' ); + + // Map clientX to value + // --------------------------------------------------------------------- + + const map = ( v, a, b, c, d ) => { + return ( v - a ) / ( b - a ) * ( d - c ) + c; + }; + + const setValueFromX = clientX => { + const rect = this.$slider.getBoundingClientRect(); + let value = map( clientX, rect.left, rect.right, this._min, this._max ); + this._snapClampSetValue( value ); + }; + + // Mouse drag + // --------------------------------------------------------------------- + + const mouseDown = e => { + this._setDraggingStyle( true ); + setValueFromX( e.clientX ); + window.addEventListener( 'mousemove', mouseMove ); + window.addEventListener( 'mouseup', mouseUp ); + }; + + const mouseMove = e => { + setValueFromX( e.clientX ); + }; + + const mouseUp = () => { + this._callOnFinishChange(); + this._setDraggingStyle( false ); + window.removeEventListener( 'mousemove', mouseMove ); + window.removeEventListener( 'mouseup', mouseUp ); + }; + + // Touch drag + // --------------------------------------------------------------------- + + let testingForScroll = false, prevClientX, prevClientY; + + const beginTouchDrag = e => { + e.preventDefault(); + this._setDraggingStyle( true ); + setValueFromX( e.touches[ 0 ].clientX ); + testingForScroll = false; + }; + + const onTouchStart = e => { + + if ( e.touches.length > 1 ) return; + + // If we're in a scrollable container, we should wait for the first + // touchmove to see if the user is trying to slide or scroll. + if ( this._hasScrollBar ) { + + prevClientX = e.touches[ 0 ].clientX; + prevClientY = e.touches[ 0 ].clientY; + testingForScroll = true; + + } else { + + // Otherwise, we can set the value straight away on touchstart. + beginTouchDrag( e ); + + } + + window.addEventListener( 'touchmove', onTouchMove, { passive: false } ); + window.addEventListener( 'touchend', onTouchEnd ); + + }; + + const onTouchMove = e => { + + if ( testingForScroll ) { + + const dx = e.touches[ 0 ].clientX - prevClientX; + const dy = e.touches[ 0 ].clientY - prevClientY; + + if ( Math.abs( dx ) > Math.abs( dy ) ) { + + // We moved horizontally, set the value and stop checking. + beginTouchDrag( e ); + + } else { + + // This was, in fact, an attempt to scroll. Abort. + window.removeEventListener( 'touchmove', onTouchMove ); + window.removeEventListener( 'touchend', onTouchEnd ); + + } + + } else { + + e.preventDefault(); + setValueFromX( e.touches[ 0 ].clientX ); + + } + + }; + + const onTouchEnd = () => { + this._callOnFinishChange(); + this._setDraggingStyle( false ); + window.removeEventListener( 'touchmove', onTouchMove ); + window.removeEventListener( 'touchend', onTouchEnd ); + }; + + // Mouse wheel + // --------------------------------------------------------------------- + + // We have to use a debounced function to call onFinishChange because + // there's no way to tell when the user is "done" mouse-wheeling. + const callOnFinishChange = this._callOnFinishChange.bind( this ); + const WHEEL_DEBOUNCE_TIME = 400; + let wheelFinishChangeTimeout; + + const onWheel = e => { + + // ignore vertical wheels if there's a scrollbar + const isVertical = Math.abs( e.deltaX ) < Math.abs( e.deltaY ); + if ( isVertical && this._hasScrollBar ) return; + + e.preventDefault(); + + // set value + const delta = this._normalizeMouseWheel( e ) * this._step; + this._snapClampSetValue( this.getValue() + delta ); + + // force the input to updateDisplay when it's focused + this.$input.value = this.getValue(); + + // debounce onFinishChange + clearTimeout( wheelFinishChangeTimeout ); + wheelFinishChangeTimeout = setTimeout( callOnFinishChange, WHEEL_DEBOUNCE_TIME ); + + }; + + this.$slider.addEventListener( 'mousedown', mouseDown ); + this.$slider.addEventListener( 'touchstart', onTouchStart, { passive: false } ); + this.$slider.addEventListener( 'wheel', onWheel, { passive: false } ); + + } + + _setDraggingStyle( active, axis = 'horizontal' ) { + if ( this.$slider ) { + this.$slider.classList.toggle( 'active', active ); + } + document.body.classList.toggle( 'lil-gui-dragging', active ); + document.body.classList.toggle( `lil-gui-${axis}`, active ); + } + + _getImplicitStep() { + + if ( this._hasMin && this._hasMax ) { + return ( this._max - this._min ) / 1000; + } + + return 0.1; + + } + + _onUpdateMinMax() { + + if ( !this._hasSlider && this._hasMin && this._hasMax ) { + + // If this is the first time we're hearing about min and max + // and we haven't explicitly stated what our step is, let's + // update that too. + if ( !this._stepExplicit ) { + this.step( this._getImplicitStep(), false ); + } + + this._initSlider(); + this.updateDisplay(); + + } + + } + + _normalizeMouseWheel( e ) { + + let { deltaX, deltaY } = e; + + // Safari and Chrome report weird non-integral values for a notched wheel, + // but still expose actual lines scrolled via wheelDelta. Notched wheels + // should behave the same way as arrow keys. + if ( Math.floor( e.deltaY ) !== e.deltaY && e.wheelDelta ) { + deltaX = 0; + deltaY = -e.wheelDelta / 120; + deltaY *= this._stepExplicit ? 1 : 10; + } + + const wheel = deltaX + -deltaY; + + return wheel; + + } + + _arrowKeyMultiplier( e ) { + + let mult = this._stepExplicit ? 1 : 10; + + if ( e.shiftKey ) { + mult *= 10; + } else if ( e.altKey ) { + mult /= 10; + } + + return mult; + + } + + _snap( value ) { + + // Make the steps "start" at min or max. + let offset = 0; + if ( this._hasMin ) { + offset = this._min; + } else if ( this._hasMax ) { + offset = this._max; + } + + value -= offset; + + value = Math.round( value / this._step ) * this._step; + + value += offset; + + // Used to prevent "flyaway" decimals like 1.00000000000001 + value = parseFloat( value.toPrecision( 15 ) ); + + return value; + + } + + _clamp( value ) { + // either condition is false if min or max is undefined + if ( value < this._min ) value = this._min; + if ( value > this._max ) value = this._max; + return value; + } + + _snapClampSetValue( value ) { + this.setValue( this._clamp( this._snap( value ) ) ); + } + + get _hasScrollBar() { + const root = this.parent.root.$children; + return root.scrollHeight > root.clientHeight; + } + + get _hasMin() { + return this._min !== undefined; + } + + get _hasMax() { + return this._max !== undefined; + } + +} + +class OptionController extends Controller { + + constructor( parent, object, property, options ) { + + super( parent, object, property, 'option' ); + + this.$select = document.createElement( 'select' ); + this.$select.setAttribute( 'aria-labelledby', this.$name.id ); + + this.$display = document.createElement( 'div' ); + this.$display.classList.add( 'display' ); + + this.$select.addEventListener( 'change', () => { + this.setValue( this._values[ this.$select.selectedIndex ] ); + this._callOnFinishChange(); + } ); + + this.$select.addEventListener( 'focus', () => { + this.$display.classList.add( 'focus' ); + } ); + + this.$select.addEventListener( 'blur', () => { + this.$display.classList.remove( 'focus' ); + } ); + + this.$widget.appendChild( this.$select ); + this.$widget.appendChild( this.$display ); + + this.$disable = this.$select; + + this.options( options ); + + } + + options( options ) { + + this._values = Array.isArray( options ) ? options : Object.values( options ); + this._names = Array.isArray( options ) ? options : Object.keys( options ); + + this.$select.replaceChildren(); + + this._names.forEach( name => { + const $option = document.createElement( 'option' ); + $option.textContent = name; + this.$select.appendChild( $option ); + } ); + + this.updateDisplay(); + + return this; + + } + + updateDisplay() { + const value = this.getValue(); + const index = this._values.indexOf( value ); + this.$select.selectedIndex = index; + this.$display.textContent = index === -1 ? value : this._names[ index ]; + return this; + } + +} + +class StringController extends Controller { + + constructor( parent, object, property ) { + + super( parent, object, property, 'string' ); + + this.$input = document.createElement( 'input' ); + this.$input.setAttribute( 'type', 'text' ); + this.$input.setAttribute( 'spellcheck', 'false' ); + this.$input.setAttribute( 'aria-labelledby', this.$name.id ); + + this.$input.addEventListener( 'input', () => { + this.setValue( this.$input.value ); + } ); + + this.$input.addEventListener( 'keydown', e => { + if ( e.code === 'Enter' ) { + this.$input.blur(); + } + } ); + + this.$input.addEventListener( 'blur', () => { + this._callOnFinishChange(); + } ); + + this.$widget.appendChild( this.$input ); + + this.$disable = this.$input; + + this.updateDisplay(); + + } + + updateDisplay() { + this.$input.value = this.getValue(); + return this; + } + +} + +var stylesheet = `.lil-gui { + font-family: var(--font-family); + font-size: var(--font-size); + line-height: 1; + font-weight: normal; + font-style: normal; + text-align: left; + color: var(--text-color); + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + --background-color: #1f1f1f; + --text-color: #ebebeb; + --title-background-color: #111111; + --title-text-color: #ebebeb; + --widget-color: #424242; + --hover-color: #4f4f4f; + --focus-color: #595959; + --number-color: #2cc9ff; + --string-color: #a2db3c; + --font-size: 11px; + --input-font-size: 11px; + --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; + --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace; + --padding: 4px; + --spacing: 4px; + --widget-height: 20px; + --title-height: calc(var(--widget-height) + var(--spacing) * 1.25); + --name-width: 45%; + --slider-knob-width: 2px; + --slider-input-width: 27%; + --color-input-width: 27%; + --slider-input-min-width: 45px; + --color-input-min-width: 45px; + --folder-indent: 7px; + --widget-padding: 0 0 0 3px; + --widget-border-radius: 2px; + --checkbox-size: calc(0.75 * var(--widget-height)); + --scrollbar-width: 5px; +} +.lil-gui, .lil-gui * { + box-sizing: border-box; + margin: 0; + padding: 0; +} +.lil-gui.root { + width: var(--width, 245px); + display: flex; + flex-direction: column; + background: var(--background-color); +} +.lil-gui.root > .title { + background: var(--title-background-color); + color: var(--title-text-color); +} +.lil-gui.root > .children { + overflow-x: hidden; + overflow-y: auto; +} +.lil-gui.root > .children::-webkit-scrollbar { + width: var(--scrollbar-width); + height: var(--scrollbar-width); + background: var(--background-color); +} +.lil-gui.root > .children::-webkit-scrollbar-thumb { + border-radius: var(--scrollbar-width); + background: var(--focus-color); +} +@media (pointer: coarse) { + .lil-gui.allow-touch-styles, .lil-gui.allow-touch-styles .lil-gui { + --widget-height: 28px; + --padding: 6px; + --spacing: 6px; + --font-size: 13px; + --input-font-size: 16px; + --folder-indent: 10px; + --scrollbar-width: 7px; + --slider-input-min-width: 50px; + --color-input-min-width: 65px; + } +} +.lil-gui.force-touch-styles, .lil-gui.force-touch-styles .lil-gui { + --widget-height: 28px; + --padding: 6px; + --spacing: 6px; + --font-size: 13px; + --input-font-size: 16px; + --folder-indent: 10px; + --scrollbar-width: 7px; + --slider-input-min-width: 50px; + --color-input-min-width: 65px; +} +.lil-gui.autoPlace { + max-height: 100%; + position: fixed; + top: 0; + right: 15px; + z-index: 1001; +} + +.lil-gui .controller { + display: flex; + align-items: center; + padding: 0 var(--padding); + margin: var(--spacing) 0; +} +.lil-gui .controller.disabled { + opacity: 0.5; +} +.lil-gui .controller.disabled, .lil-gui .controller.disabled * { + pointer-events: none !important; +} +.lil-gui .controller > .name { + min-width: var(--name-width); + flex-shrink: 0; + white-space: pre; + padding-right: var(--spacing); + line-height: var(--widget-height); +} +.lil-gui .controller .widget { + position: relative; + display: flex; + align-items: center; + width: 100%; + min-height: var(--widget-height); +} +.lil-gui .controller.string input { + color: var(--string-color); +} +.lil-gui .controller.boolean { + cursor: pointer; +} +.lil-gui .controller.color .display { + width: 100%; + height: var(--widget-height); + border-radius: var(--widget-border-radius); + position: relative; +} +@media (hover: hover) { + .lil-gui .controller.color .display:hover:before { + content: " "; + display: block; + position: absolute; + border-radius: var(--widget-border-radius); + border: 1px solid #fff9; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +} +.lil-gui .controller.color input[type=color] { + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; +} +.lil-gui .controller.color input[type=text] { + margin-left: var(--spacing); + font-family: var(--font-family-mono); + min-width: var(--color-input-min-width); + width: var(--color-input-width); + flex-shrink: 0; +} +.lil-gui .controller.option select { + opacity: 0; + position: absolute; + width: 100%; + max-width: 100%; +} +.lil-gui .controller.option .display { + position: relative; + pointer-events: none; + border-radius: var(--widget-border-radius); + height: var(--widget-height); + line-height: var(--widget-height); + max-width: 100%; + overflow: hidden; + word-break: break-all; + padding-left: 0.55em; + padding-right: 1.75em; + background: var(--widget-color); +} +@media (hover: hover) { + .lil-gui .controller.option .display.focus { + background: var(--focus-color); + } +} +.lil-gui .controller.option .display.active { + background: var(--focus-color); +} +.lil-gui .controller.option .display:after { + font-family: "lil-gui"; + content: "↕"; + position: absolute; + top: 0; + right: 0; + bottom: 0; + padding-right: 0.375em; +} +.lil-gui .controller.option .widget, +.lil-gui .controller.option select { + cursor: pointer; +} +@media (hover: hover) { + .lil-gui .controller.option .widget:hover .display { + background: var(--hover-color); + } +} +.lil-gui .controller.number input { + color: var(--number-color); +} +.lil-gui .controller.number.hasSlider input { + margin-left: var(--spacing); + width: var(--slider-input-width); + min-width: var(--slider-input-min-width); + flex-shrink: 0; +} +.lil-gui .controller.number .slider { + width: 100%; + height: var(--widget-height); + background: var(--widget-color); + border-radius: var(--widget-border-radius); + padding-right: var(--slider-knob-width); + overflow: hidden; + cursor: ew-resize; + touch-action: pan-y; +} +@media (hover: hover) { + .lil-gui .controller.number .slider:hover { + background: var(--hover-color); + } +} +.lil-gui .controller.number .slider.active { + background: var(--focus-color); +} +.lil-gui .controller.number .slider.active .fill { + opacity: 0.95; +} +.lil-gui .controller.number .fill { + height: 100%; + border-right: var(--slider-knob-width) solid var(--number-color); + box-sizing: content-box; +} + +.lil-gui-dragging .lil-gui { + --hover-color: var(--widget-color); +} +.lil-gui-dragging * { + cursor: ew-resize !important; +} + +.lil-gui-dragging.lil-gui-vertical * { + cursor: ns-resize !important; +} + +.lil-gui .title { + height: var(--title-height); + font-weight: 600; + padding: 0 var(--padding); + width: 100%; + text-align: left; + background: none; + text-decoration-skip: objects; +} +.lil-gui .title:before { + font-family: "lil-gui"; + content: "▾"; + padding-right: 2px; + display: inline-block; +} +.lil-gui .title:active { + background: var(--title-background-color); + opacity: 0.75; +} +@media (hover: hover) { + body:not(.lil-gui-dragging) .lil-gui .title:hover { + background: var(--title-background-color); + opacity: 0.85; + } + .lil-gui .title:focus { + text-decoration: underline var(--focus-color); + } +} +.lil-gui.root > .title:focus { + text-decoration: none !important; +} +.lil-gui.closed > .title:before { + content: "▸"; +} +.lil-gui.closed > .children { + transform: translateY(-7px); + opacity: 0; +} +.lil-gui.closed:not(.transition) > .children { + display: none; +} +.lil-gui.transition > .children { + transition-duration: 300ms; + transition-property: height, opacity, transform; + transition-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); + overflow: hidden; + pointer-events: none; +} +.lil-gui .children:empty:before { + content: "Empty"; padding: 0 var(--padding); margin: var(--spacing) 0; display: block; @@ -89673,37640 +114077,70815 @@ const stylesheet = `.lil-gui { line-height: var(--widget-height); opacity: 0.5; } -.lil-gui.root > .children > .lil-gui > .title { - border: 0 solid var(--widget-color); - border-width: 1px 0; - transition: border-color 300ms; +.lil-gui.root > .children > .lil-gui > .title { + border: 0 solid var(--widget-color); + border-width: 1px 0; + transition: border-color 300ms; +} +.lil-gui.root > .children > .lil-gui.closed > .title { + border-bottom-color: transparent; +} +.lil-gui + .controller { + border-top: 1px solid var(--widget-color); + margin-top: 0; + padding-top: var(--spacing); +} +.lil-gui .lil-gui .lil-gui > .title { + border: none; +} +.lil-gui .lil-gui .lil-gui > .children { + border: none; + margin-left: var(--folder-indent); + border-left: 2px solid var(--widget-color); +} +.lil-gui .lil-gui .controller { + border: none; +} + +.lil-gui label, .lil-gui input, .lil-gui button { + -webkit-tap-highlight-color: transparent; +} +.lil-gui input { + border: 0; + outline: none; + font-family: var(--font-family); + font-size: var(--input-font-size); + border-radius: var(--widget-border-radius); + height: var(--widget-height); + background: var(--widget-color); + color: var(--text-color); + width: 100%; +} +@media (hover: hover) { + .lil-gui input:hover { + background: var(--hover-color); + } + .lil-gui input:active { + background: var(--focus-color); + } +} +.lil-gui input:disabled { + opacity: 1; +} +.lil-gui input[type=text], +.lil-gui input[type=number] { + padding: var(--widget-padding); + -moz-appearance: textfield; +} +.lil-gui input[type=text]:focus, +.lil-gui input[type=number]:focus { + background: var(--focus-color); +} +.lil-gui input[type=checkbox] { + appearance: none; + width: var(--checkbox-size); + height: var(--checkbox-size); + border-radius: var(--widget-border-radius); + text-align: center; + cursor: pointer; +} +.lil-gui input[type=checkbox]:checked:before { + font-family: "lil-gui"; + content: "✓"; + font-size: var(--checkbox-size); + line-height: var(--checkbox-size); +} +@media (hover: hover) { + .lil-gui input[type=checkbox]:focus { + box-shadow: inset 0 0 0 1px var(--focus-color); + } +} +.lil-gui button { + outline: none; + cursor: pointer; + font-family: var(--font-family); + font-size: var(--font-size); + color: var(--text-color); + width: 100%; + border: none; +} +.lil-gui .controller button { + height: var(--widget-height); + text-transform: none; + background: var(--widget-color); + border-radius: var(--widget-border-radius); +} +@media (hover: hover) { + .lil-gui .controller button:hover { + background: var(--hover-color); + } + .lil-gui .controller button:focus { + box-shadow: inset 0 0 0 1px var(--focus-color); + } +} +.lil-gui .controller button:active { + background: var(--focus-color); +} + +@font-face { + font-family: "lil-gui"; + src: url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff"); +}`; + +function _injectStyles( cssContent ) { + const injected = document.createElement( 'style' ); + injected.innerHTML = cssContent; + const before = document.querySelector( 'head link[rel=stylesheet], head style' ); + if ( before ) { + document.head.insertBefore( injected, before ); + } else { + document.head.appendChild( injected ); + } +} + + +let stylesInjected = false; + +class GUI { + + /** + * Creates a panel that holds controllers. + * @example + * new GUI(); + * new GUI( { container: document.getElementById( 'custom' ) } ); + * + * @param {object} [options] + * @param {boolean} [options.autoPlace=true] + * Adds the GUI to `document.body` and fixes it to the top right of the page. + * + * @param {HTMLElement} [options.container] + * Adds the GUI to this DOM element. Overrides `autoPlace`. + * + * @param {number} [options.width=245] + * Width of the GUI in pixels, usually set when name labels become too long. Note that you can make + * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }`. + * + * @param {string} [options.title=Controls] + * Name to display in the title bar. + * + * @param {boolean} [options.closeFolders=false] + * Pass `true` to close all folders in this GUI by default. + * + * @param {boolean} [options.injectStyles=true] + * Injects the default stylesheet into the page if this is the first GUI. + * Pass `false` to use your own stylesheet. + * + * @param {number} [options.touchStyles=true] + * Makes controllers larger on touch devices. Pass `false` to disable touch styles. + * + * @param {GUI} [options.parent] + * Adds this GUI as a child in another GUI. Usually this is done for you by `addFolder()`. + */ + constructor( { + parent, + autoPlace = parent === undefined, + container, + width, + title = 'Controls', + closeFolders = false, + injectStyles = true, + touchStyles = true + } = {} ) { + + /** + * The GUI containing this folder, or `undefined` if this is the root GUI. + * @type {GUI} + */ + this.parent = parent; + + /** + * The top level GUI containing this folder, or `this` if this is the root GUI. + * @type {GUI} + */ + this.root = parent ? parent.root : this; + + /** + * The list of controllers and folders contained by this GUI. + * @type {Array} + */ + this.children = []; + + /** + * The list of controllers contained by this GUI. + * @type {Array} + */ + this.controllers = []; + + /** + * The list of folders contained by this GUI. + * @type {Array} + */ + this.folders = []; + + /** + * Used to determine if the GUI is closed. Use `gui.open()` or `gui.close()` to change this. + * @type {boolean} + */ + this._closed = false; + + /** + * Used to determine if the GUI is hidden. Use `gui.show()` or `gui.hide()` to change this. + * @type {boolean} + */ + this._hidden = false; + + /** + * The outermost container element. + * @type {HTMLElement} + */ + this.domElement = document.createElement( 'div' ); + this.domElement.classList.add( 'lil-gui' ); + + /** + * The DOM element that contains the title. + * @type {HTMLElement} + */ + this.$title = document.createElement( 'button' ); + this.$title.classList.add( 'title' ); + this.$title.setAttribute( 'aria-expanded', true ); + + this.$title.addEventListener( 'click', () => this.openAnimated( this._closed ) ); + + // enables :active pseudo class on mobile + this.$title.addEventListener( 'touchstart', () => {}, { passive: true } ); + + /** + * The DOM element that contains children. + * @type {HTMLElement} + */ + this.$children = document.createElement( 'div' ); + this.$children.classList.add( 'children' ); + + this.domElement.appendChild( this.$title ); + this.domElement.appendChild( this.$children ); + + this.title( title ); + + if ( this.parent ) { + + this.parent.children.push( this ); + this.parent.folders.push( this ); + + this.parent.$children.appendChild( this.domElement ); + + // Stop the constructor early, everything onward only applies to root GUI's + return; + + } + + this.domElement.classList.add( 'root' ); + + if ( touchStyles ) { + this.domElement.classList.add( 'allow-touch-styles' ); + } + + // Inject stylesheet if we haven't done that yet + if ( !stylesInjected && injectStyles ) { + _injectStyles( stylesheet ); + stylesInjected = true; + } + + if ( container ) { + + container.appendChild( this.domElement ); + + } else if ( autoPlace ) { + + this.domElement.classList.add( 'autoPlace' ); + document.body.appendChild( this.domElement ); + + } + + if ( width ) { + this.domElement.style.setProperty( '--width', width + 'px' ); + } + + this._closeFolders = closeFolders; + + } + + /** + * Adds a controller to the GUI, inferring controller type using the `typeof` operator. + * @example + * gui.add( object, 'property' ); + * gui.add( object, 'number', 0, 100, 1 ); + * gui.add( object, 'options', [ 1, 2, 3 ] ); + * + * @param {object} object The object the controller will modify. + * @param {string} property Name of the property to control. + * @param {number|object|Array} [$1] Minimum value for number controllers, or the set of + * selectable values for a dropdown. + * @param {number} [max] Maximum value for number controllers. + * @param {number} [step] Step value for number controllers. + * @returns {Controller} + */ + add( object, property, $1, max, step ) { + + if ( Object( $1 ) === $1 ) { + + return new OptionController( this, object, property, $1 ); + + } + + const initialValue = object[ property ]; + + switch ( typeof initialValue ) { + + case 'number': + + return new NumberController( this, object, property, $1, max, step ); + + case 'boolean': + + return new BooleanController( this, object, property ); + + case 'string': + + return new StringController( this, object, property ); + + case 'function': + + return new FunctionController( this, object, property ); + + } + + console.error( `gui.add failed + property:`, property, ` + object:`, object, ` + value:`, initialValue ); + + } + + /** + * Adds a color controller to the GUI. + * @example + * params = { + * cssColor: '#ff00ff', + * rgbColor: { r: 0, g: 0.2, b: 0.4 }, + * customRange: [ 0, 127, 255 ], + * }; + * + * gui.addColor( params, 'cssColor' ); + * gui.addColor( params, 'rgbColor' ); + * gui.addColor( params, 'customRange', 255 ); + * + * @param {object} object The object the controller will modify. + * @param {string} property Name of the property to control. + * @param {number} rgbScale Maximum value for a color channel when using an RGB color. You may + * need to set this to 255 if your colors are too bright. + * @returns {Controller} + */ + addColor( object, property, rgbScale = 1 ) { + return new ColorController( this, object, property, rgbScale ); + } + + /** + * Adds a folder to the GUI, which is just another GUI. This method returns + * the nested GUI so you can add controllers to it. + * @example + * const folder = gui.addFolder( 'Position' ); + * folder.add( position, 'x' ); + * folder.add( position, 'y' ); + * folder.add( position, 'z' ); + * + * @param {string} title Name to display in the folder's title bar. + * @returns {GUI} + */ + addFolder( title ) { + const folder = new GUI( { parent: this, title } ); + if ( this.root._closeFolders ) folder.close(); + return folder; + } + + /** + * Recalls values that were saved with `gui.save()`. + * @param {object} obj + * @param {boolean} recursive Pass false to exclude folders descending from this GUI. + * @returns {this} + */ + load( obj, recursive = true ) { + + if ( obj.controllers ) { + + this.controllers.forEach( c => { + + if ( c instanceof FunctionController ) return; + + if ( c._name in obj.controllers ) { + c.load( obj.controllers[ c._name ] ); + } + + } ); + + } + + if ( recursive && obj.folders ) { + + this.folders.forEach( f => { + + if ( f._title in obj.folders ) { + f.load( obj.folders[ f._title ] ); + } + + } ); + + } + + return this; + + } + + /** + * Returns an object mapping controller names to values. The object can be passed to `gui.load()` to + * recall these values. + * @example + * { + * controllers: { + * prop1: 1, + * prop2: 'value', + * ... + * }, + * folders: { + * folderName1: { controllers, folders }, + * folderName2: { controllers, folders } + * ... + * } + * } + * + * @param {boolean} recursive Pass false to exclude folders descending from this GUI. + * @returns {object} + */ + save( recursive = true ) { + + const obj = { + controllers: {}, + folders: {} + }; + + this.controllers.forEach( c => { + + if ( c instanceof FunctionController ) return; + + if ( c._name in obj.controllers ) { + throw new Error( `Cannot save GUI with duplicate property "${c._name}"` ); + } + + obj.controllers[ c._name ] = c.save(); + + } ); + + if ( recursive ) { + + this.folders.forEach( f => { + + if ( f._title in obj.folders ) { + throw new Error( `Cannot save GUI with duplicate folder "${f._title}"` ); + } + + obj.folders[ f._title ] = f.save(); + + } ); + + } + + return obj; + + } + + /** + * Opens a GUI or folder. GUI and folders are open by default. + * @param {boolean} open Pass false to close. + * @returns {this} + * @example + * gui.open(); // open + * gui.open( false ); // close + * gui.open( gui._closed ); // toggle + */ + open( open = true ) { + + this._setClosed( !open ); + + this.$title.setAttribute( 'aria-expanded', !this._closed ); + this.domElement.classList.toggle( 'closed', this._closed ); + + return this; + + } + + /** + * Closes the GUI. + * @returns {this} + */ + close() { + return this.open( false ); + } + + _setClosed( closed ) { + if ( this._closed === closed ) return; + this._closed = closed; + this._callOnOpenClose( this ); + } + + /** + * Shows the GUI after it's been hidden. + * @param {boolean} show + * @returns {this} + * @example + * gui.show(); + * gui.show( false ); // hide + * gui.show( gui._hidden ); // toggle + */ + show( show = true ) { + + this._hidden = !show; + + this.domElement.style.display = this._hidden ? 'none' : ''; + + return this; + + } + + /** + * Hides the GUI. + * @returns {this} + */ + hide() { + return this.show( false ); + } + + openAnimated( open = true ) { + + // set state immediately + this._setClosed( !open ); + + this.$title.setAttribute( 'aria-expanded', !this._closed ); + + // wait for next frame to measure $children + requestAnimationFrame( () => { + + // explicitly set initial height for transition + const initialHeight = this.$children.clientHeight; + this.$children.style.height = initialHeight + 'px'; + + this.domElement.classList.add( 'transition' ); + + const onTransitionEnd = e => { + if ( e.target !== this.$children ) return; + this.$children.style.height = ''; + this.domElement.classList.remove( 'transition' ); + this.$children.removeEventListener( 'transitionend', onTransitionEnd ); + }; + + this.$children.addEventListener( 'transitionend', onTransitionEnd ); + + // todo: this is wrong if children's scrollHeight makes for a gui taller than maxHeight + const targetHeight = !open ? 0 : this.$children.scrollHeight; + + this.domElement.classList.toggle( 'closed', !open ); + + requestAnimationFrame( () => { + this.$children.style.height = targetHeight + 'px'; + } ); + + } ); + + return this; + + } + + /** + * Change the title of this GUI. + * @param {string} title + * @returns {this} + */ + title( title ) { + /** + * Current title of the GUI. Use `gui.title( 'Title' )` to modify this value. + * @type {string} + */ + this._title = title; + this.$title.textContent = title; + return this; + } + + /** + * Resets all controllers to their initial values. + * @param {boolean} recursive Pass false to exclude folders descending from this GUI. + * @returns {this} + */ + reset( recursive = true ) { + const controllers = recursive ? this.controllersRecursive() : this.controllers; + controllers.forEach( c => c.reset() ); + return this; + } + + /** + * Pass a function to be called whenever a controller in this GUI changes. + * @param {function({object:object, property:string, value:any, controller:Controller})} callback + * @returns {this} + * @example + * gui.onChange( event => { + * event.object // object that was modified + * event.property // string, name of property + * event.value // new value of controller + * event.controller // controller that was modified + * } ); + */ + onChange( callback ) { + /** + * Used to access the function bound to `onChange` events. Don't modify this value + * directly. Use the `gui.onChange( callback )` method instead. + * @type {Function} + */ + this._onChange = callback; + return this; + } + + _callOnChange( controller ) { + + if ( this.parent ) { + this.parent._callOnChange( controller ); + } + + if ( this._onChange !== undefined ) { + this._onChange.call( this, { + object: controller.object, + property: controller.property, + value: controller.getValue(), + controller + } ); + } + } + + /** + * Pass a function to be called whenever a controller in this GUI has finished changing. + * @param {function({object:object, property:string, value:any, controller:Controller})} callback + * @returns {this} + * @example + * gui.onFinishChange( event => { + * event.object // object that was modified + * event.property // string, name of property + * event.value // new value of controller + * event.controller // controller that was modified + * } ); + */ + onFinishChange( callback ) { + /** + * Used to access the function bound to `onFinishChange` events. Don't modify this value + * directly. Use the `gui.onFinishChange( callback )` method instead. + * @type {Function} + */ + this._onFinishChange = callback; + return this; + } + + _callOnFinishChange( controller ) { + + if ( this.parent ) { + this.parent._callOnFinishChange( controller ); + } + + if ( this._onFinishChange !== undefined ) { + this._onFinishChange.call( this, { + object: controller.object, + property: controller.property, + value: controller.getValue(), + controller + } ); + } + } + + /** + * Pass a function to be called when this GUI or its descendants are opened or closed. + * @param {function(GUI)} callback + * @returns {this} + * @example + * gui.onOpenClose( changedGUI => { + * console.log( changedGUI._closed ); + * } ); + */ + onOpenClose( callback ) { + this._onOpenClose = callback; + return this; + } + + _callOnOpenClose( changedGUI ) { + if ( this.parent ) { + this.parent._callOnOpenClose( changedGUI ); + } + + if ( this._onOpenClose !== undefined ) { + this._onOpenClose.call( this, changedGUI ); + } + } + + /** + * Destroys all DOM elements and event listeners associated with this GUI. + */ + destroy() { + + if ( this.parent ) { + this.parent.children.splice( this.parent.children.indexOf( this ), 1 ); + this.parent.folders.splice( this.parent.folders.indexOf( this ), 1 ); + } + + if ( this.domElement.parentElement ) { + this.domElement.parentElement.removeChild( this.domElement ); + } + + Array.from( this.children ).forEach( c => c.destroy() ); + + } + + /** + * Returns an array of controllers contained by this GUI and its descendents. + * @returns {Controller[]} + */ + controllersRecursive() { + let controllers = Array.from( this.controllers ); + this.folders.forEach( f => { + controllers = controllers.concat( f.controllersRecursive() ); + } ); + return controllers; + } + + /** + * Returns an array of folders contained by this GUI and its descendents. + * @returns {GUI[]} + */ + foldersRecursive() { + let folders = Array.from( this.folders ); + this.folders.forEach( f => { + folders = folders.concat( f.foldersRecursive() ); + } ); + return folders; + } + +} + +const _ENTIRE_SCENE = 0, _BLOOM_SCENE = 1, + clTGeoManager = 'TGeoManager', clTEveGeoShapeExtract = 'TEveGeoShapeExtract', + clTGeoOverlap = 'TGeoOverlap', clTGeoVolumeAssembly = 'TGeoVolumeAssembly', + clTEveTrack = 'TEveTrack', clTEvePointSet = 'TEvePointSet', + clREveGeoShapeExtract = `${nsREX}REveGeoShapeExtract`; + +/** @summary Function used to build hierarchy of elements of overlap object + * @private */ +function buildOverlapVolume(overlap) { + const vol = create$1(clTGeoVolume); + + setGeoBit(vol, geoBITS.kVisDaughters, true); + vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy + vol.fName = ''; + + const node1 = create$1(clTGeoNodeMatrix); + node1.fName = overlap.fVolume1.fName || 'Overlap1'; + node1.fMatrix = overlap.fMatrix1; + node1.fVolume = overlap.fVolume1; + // node1.fVolume.fLineColor = 2; // color assigned with ctrl.split_colors + + const node2 = create$1(clTGeoNodeMatrix); + node2.fName = overlap.fVolume2.fName || 'Overlap2'; + node2.fMatrix = overlap.fMatrix2; + node2.fVolume = overlap.fVolume2; + // node2.fVolume.fLineColor = 3; // color assigned with ctrl.split_colors + + vol.fNodes = create$1(clTList); + vol.fNodes.Add(node1); + vol.fNodes.Add(node2); + + return vol; +} + +let $comp_col_cnt = 0; + +/** @summary Function used to build hierarchy of elements of composite shapes + * @private */ +function buildCompositeVolume(comp, maxlvl, side) { + if (maxlvl === undefined) + maxlvl = 1; + if (!side) { + $comp_col_cnt = 0; + side = ''; + } + + const vol = create$1(clTGeoVolume); + setGeoBit(vol, geoBITS.kVisThis, true); + setGeoBit(vol, geoBITS.kVisDaughters, true); + + if ((side && (comp._typename !== clTGeoCompositeShape)) || (maxlvl <= 0)) { + vol.fName = side; + vol.fLineColor = ($comp_col_cnt++ % 8) + 2; + vol.fShape = comp; + return vol; + } + + if (side) + side += '/'; + vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy + vol.fName = ''; + + const node1 = create$1(clTGeoNodeMatrix); + setGeoBit(node1, geoBITS.kVisThis, true); + setGeoBit(node1, geoBITS.kVisDaughters, true); + node1.fName = 'Left'; + node1.fMatrix = comp.fNode.fLeftMat; + node1.fVolume = buildCompositeVolume(comp.fNode.fLeft, maxlvl - 1, side + 'Left'); + + const node2 = create$1(clTGeoNodeMatrix); + setGeoBit(node2, geoBITS.kVisThis, true); + setGeoBit(node2, geoBITS.kVisDaughters, true); + node2.fName = 'Right'; + node2.fMatrix = comp.fNode.fRightMat; + node2.fVolume = buildCompositeVolume(comp.fNode.fRight, maxlvl - 1, side + 'Right'); + + vol.fNodes = create$1(clTList); + vol.fNodes.Add(node1); + vol.fNodes.Add(node2); + + if (!side) $comp_col_cnt = 0; + + return vol; } -.lil-gui.root > .children > .lil-gui.closed > .title { - border-bottom-color: transparent; + + +/** @summary Provides 3D rendering configuration from histogram painter + * @return {Object} with scene, renderer and other attributes + * @private */ +function getHistPainter3DCfg(painter) { + const main = painter?.getFramePainter(); + if (painter?.mode3d && isFunc(main?.create3DScene) && main?.renderer) { + let scale_x = 1, scale_y = 1, scale_z = 1, + offset_x = 0, offset_y = 0, offset_z = 0; + + if (main.scale_xmax > main.scale_xmin) { + scale_x = 2 * main.size_x3d / (main.scale_xmax - main.scale_xmin); + offset_x = (main.scale_xmax + main.scale_xmin) / 2 * scale_x; + } + + if (main.scale_ymax > main.scale_ymin) { + scale_y = 2 * main.size_y3d / (main.scale_ymax - main.scale_ymin); + offset_y = (main.scale_ymax + main.scale_ymin) / 2 * scale_y; + } + + if (main.scale_zmax > main.scale_zmin) { + scale_z = 2 * main.size_z3d / (main.scale_zmax - main.scale_zmin); + offset_z = (main.scale_zmax + main.scale_zmin) / 2 * scale_z - main.size_z3d; + } + + return { + webgl: main.webgl, + scene: main.scene, + scene_width: main.scene_width, + scene_height: main.scene_height, + toplevel: main.toplevel, + renderer: main.renderer, + camera: main.camera, + scale_x, scale_y, scale_z, + offset_x, offset_y, offset_z + }; + } } -.lil-gui + .controller { - border-top: 1px solid var(--widget-color); - margin-top: 0; - padding-top: var(--spacing); + + +/** @summary find item with geometry painter + * @private */ +function findItemWithGeoPainter(hitem, test_changes) { + while (hitem) { + if (isFunc(hitem._painter?.testGeomChanges)) { + if (test_changes) + hitem._painter.testGeomChanges(); + return hitem; + } + hitem = hitem._parent; + } + return null; } -.lil-gui .lil-gui .lil-gui > .title { - border: none; + +/** @summary provide css style for geo object + * @private */ +function provideVisStyle(obj) { + if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) + return obj.fRnrSelf ? ' geovis_this' : ''; + + const vis = !testGeoBit(obj, geoBITS.kVisNone) && testGeoBit(obj, geoBITS.kVisThis); + let chld = testGeoBit(obj, geoBITS.kVisDaughters); + + if (chld && !obj.fNodes?.arr?.length) + chld = false; + + if (vis && chld) + return ' geovis_all'; + if (vis) + return ' geovis_this'; + if (chld) + return ' geovis_daughters'; + return ''; } -.lil-gui .lil-gui .lil-gui > .children { - border: none; - margin-left: var(--folder-indent); - border-left: 2px solid var(--widget-color); + + +/** @summary update icons + * @private */ +function updateBrowserIcons(obj, hpainter) { + if (!obj || !hpainter) + return; + + hpainter.forEachItem(m => { + // update all items with that volume + if ((obj === m._volume) || (obj === m._geoobj)) { + m._icon = m._icon.split(' ')[0] + provideVisStyle(obj); + hpainter.updateTreeNode(m); + } + }); } -.lil-gui .lil-gui .controller { - border: none; + + +/** @summary Return stack for the item from list of intersection + * @private */ +function getIntersectStack(item) { + const obj = item?.object; + if (!obj) + return null; + if (obj.stack) + return obj.stack; + if (obj.stacks && item.instanceId !== undefined && item.instanceId < obj.stacks.length) + return obj.stacks[item.instanceId]; } -.lil-gui input { - -webkit-tap-highlight-color: transparent; - border: 0; - outline: none; - font-family: var(--font-family); - font-size: var(--input-font-size); - border-radius: var(--widget-border-radius); - height: var(--widget-height); - background: var(--widget-color); - color: var(--text-color); - width: 100%; -} -@media (hover: hover) { - .lil-gui input:hover { - background: var(--hover-color); - } - .lil-gui input:active { - background: var(--focus-color); - } -} -.lil-gui input:disabled { - opacity: 1; -} -.lil-gui input[type=text], -.lil-gui input[type=number] { - padding: var(--widget-padding); -} -.lil-gui input[type=text]:focus, -.lil-gui input[type=number]:focus { - background: var(--focus-color); -} -.lil-gui input::-webkit-outer-spin-button, -.lil-gui input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.lil-gui input[type=number] { - -moz-appearance: textfield; -} -.lil-gui input[type=checkbox] { - appearance: none; - -webkit-appearance: none; - height: var(--checkbox-size); - width: var(--checkbox-size); - border-radius: var(--widget-border-radius); - text-align: center; - cursor: pointer; -} -.lil-gui input[type=checkbox]:checked:before { - font-family: "lil-gui"; - content: "✓"; - font-size: var(--checkbox-size); - line-height: var(--checkbox-size); -} -@media (hover: hover) { - .lil-gui input[type=checkbox]:focus { - box-shadow: inset 0 0 0 1px var(--focus-color); - } -} -.lil-gui button { - -webkit-tap-highlight-color: transparent; - outline: none; - cursor: pointer; - font-family: var(--font-family); - font-size: var(--font-size); - color: var(--text-color); - width: 100%; - height: var(--widget-height); - text-transform: none; - background: var(--widget-color); - border-radius: var(--widget-border-radius); - border: 1px solid var(--widget-color); - text-align: center; - line-height: calc(var(--widget-height) - 4px); -} -@media (hover: hover) { - .lil-gui button:hover { - background: var(--hover-color); - border-color: var(--hover-color); - } - .lil-gui button:focus { - border-color: var(--focus-color); - } -} -.lil-gui button:active { - background: var(--focus-color); +/** + * @summary Toolbar for geometry painter + * + * @private + */ + +class Toolbar { + + /** @summary constructor */ + constructor(container, bright, buttons) { + this.bright = bright; + this.buttons = buttons; + this.element = container.append('div').attr('style', 'float: left; box-sizing: border-box; position: relative; bottom: 23px; vertical-align: middle; padding-left: 5px'); + } + + /** @summary add buttons */ + createButtons() { + const buttonsNames = []; + + this.buttons.forEach(buttonConfig => { + const buttonName = buttonConfig.name; + if (!buttonName) + throw new Error('must provide button name in button config'); + if (buttonsNames.indexOf(buttonName) !== -1) + throw new Error(`button name ${buttonName} is taken`); + + buttonsNames.push(buttonName); + + const title = buttonConfig.title || buttonConfig.name; + + if (!isFunc(buttonConfig.click)) + throw new Error('must provide button click() function in button config'); + + ToolbarIcons.createSVG(this.element, ToolbarIcons[buttonConfig.icon], 16, title, this.bright) + .on('click', buttonConfig.click) + .style('position', 'relative') + .style('padding', '3px 1px'); + }); + } + + /** @summary change brightness */ + changeBrightness(bright) { + if (this.bright !== bright) { + this.element.selectAll('*').remove(); + this.bright = bright; + this.createButtons(); + } + } + + /** @summary cleanup toolbar */ + cleanup() { + this.element?.remove(); + delete this.element; + } + +} // class ToolBar + + +/** + * @summary geometry drawing control + * + * @private + */ + +class GeoDrawingControl extends InteractiveControl { + + constructor(mesh, bloom) { + super(); + this.mesh = mesh?.material ? mesh : null; + this.bloom = bloom; + } + + /** @summary set highlight */ + setHighlight(col, indx) { + return this.drawSpecial(col, indx); + } + + /** @summary draw special */ + drawSpecial(col, indx) { + const c = this.mesh; + if (!c?.material) + return; + + if (c.isInstancedMesh) { + if (c._highlight_mesh) { + c.remove(c._highlight_mesh); + delete c._highlight_mesh; + } + + if (col && indx !== undefined) { + const h = new THREE.Mesh(c.geometry, c.material.clone()); + + if (this.bloom) { + h.layers.enable(_BLOOM_SCENE); + h.material.emissive = new THREE.Color(0x00ff00); + } else { + h.material.color = new THREE.Color(col); + h.material.opacity = 1.0; + } + const m = new THREE.Matrix4(); + c.getMatrixAt(indx, m); + h.applyMatrix4(m); + c.add(h); + + h.jsroot_special = true; // exclude from intersections + + c._highlight_mesh = h; + } + return true; + } + + if (col) { + if (!c.origin) { + c.origin = { + color: c.material.color, + emissive: c.material.emissive, + opacity: c.material.opacity, + width: c.material.linewidth, + size: c.material.size + }; + } + if (this.bloom) { + c.layers.enable(_BLOOM_SCENE); + c.material.emissive = new THREE.Color(0x00ff00); + } else { + c.material.color = new THREE.Color(col); + c.material.opacity = 1.0; + } + + if (c.hightlightWidthScale && !browser.isWin) + c.material.linewidth = c.origin.width * c.hightlightWidthScale; + if (c.highlightScale) + c.material.size = c.origin.size * c.highlightScale; + return true; + } else if (c.origin) { + if (this.bloom) { + c.material.emissive = c.origin.emissive; + c.layers.enable(_ENTIRE_SCENE); + } else { + c.material.color = c.origin.color; + c.material.opacity = c.origin.opacity; + } + if (c.hightlightWidthScale) + c.material.linewidth = c.origin.width; + if (c.highlightScale) + c.material.size = c.origin.size; + return true; + } + } + +} // class GeoDrawingControl + + +const stageInit = 0, stageCollect = 1, stageWorkerCollect = 2, stageAnalyze = 3, stageCollShapes = 4, + stageStartBuild = 5, stageWorkerBuild = 6, stageBuild = 7, stageBuildReady = 8, stageWaitMain = 9, stageBuildProj = 10; + +/** + * @summary Painter class for geometries drawing + * + * @private + */ + +class TGeoPainter extends ObjectPainter { + + #geo_manager; // TGeoManager instance - if assigned in drawing + #superimpose; // set when superimposed with other 3D drawings + #complete_handler; // callback, assigned by ROOT GeomViewer + #geom_viewer; // true when processing drawing from ROOT GeomViewer + #extra_objects; // TList with extra objects configured for drawing + #webgl; // true when normal WebGL drawing is enabled + #worker; // extra Worker to run different calculations + #worker_ready; // is worker started and initialized + #worker_jobs; // number of submitted to worker jobs + #clones; // instance of ClonedNodes + #clones_owner; // is instance managed by the painter + #draw_nodes; // drawn nodes from geometry + #build_shapes; // build shapes required for drawings + #current_face_limit; // current face limit + #draw_all_nodes; // flag using in drawing + #draw_nodes_again; // flag set if drawing should be started again when completed + #drawing_ready; // if drawing completed + #drawing_log; // current drawing log information + #draw_resolveFuncs; // resolve call-backs for start drawing calls + #start_drawing_time; // time when drawing started + #start_render_tm; // time when rendering started + #first_render_tm; // first time when rendering was performed + #last_render_tm; // last time when rendering was performed + #last_render_meshes; // last value of rendered meshes + #render_resolveFuncs; // resolve call-backs from render calls + #render_tmout; // timeout used in Render3D() + #first_drawing; // true when very first drawing is performed + #full_redrawing; // if full redraw must be performed + #last_clip_cfg; // last config of clip panels + #redraw_timer; // timer used in redraw + #did_update; // flag used in update + #custom_bounding_box; // custom bounding box for extra objects + #clip_planes; // clip planes + #central_painter; // pointer on central painter + #subordinate_painters; // list of subordinate painters + #effectComposer; // extra composer for special effects, used in EVE + #bloomComposer; // extra composer for bloom highlight + #new_draw_nodes; // temporary list of new draw nodes + #new_append_nodes; // temporary list of new append nodes + #more_nodes; // more nodes from ROOT geometry viewer + #provided_more_nodes; // staged more nodes from ROOT geometry viewer + #on_pad; // true when drawing done on TPad + #last_manifest; // last node which was "manifest" via menu + #last_hidden; // last node which was "hidden" via menu + #gui; // dat.GUI instance + #toolbar; // tool buttons + #controls; // orbit control + #highlight_handlers; // highlight handlers + #animating; // set when animation started + #last_camera_position; // last camera position + #fullgeom_proj; // full geometry to produce projection + #selected_mesh; // used in highlight + #fog; // fog for the scene + #lookat; // position to which camera looks at + #scene_size; // stored scene size to control changes + #scene_width; // actual scene width + #scene_height; // actual scene width + #fit_main_area; // true if drawing must fit to DOM + #overall_size; // overall size + #scene; // three.js Scene object + #camera; // three.js camera object + #renderer; // three.js renderer object + #toplevel; // three.js top level Object3D + #camera0pos; // camera on opposite side + #vrControllers; // used in VR display + #controllersMeshes; // used in VR display + #dolly; // used in VR display + #vrDisplay; // used in VR display + #vr_camera_position; // used in VR display + #vr_camera_rotation; // used in VR display + #vr_camera_near; // used in VR display + #standingMatrix; // used in VR display + #raycasterEnd; // used in VR display + #raycasterOrigin; // used in VR display + #adjust_camera_with_render; // flag to readjust camera when rendering + #did_cleanup; // flag set after cleanup + + /** @summary Constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} obj - supported TGeo object */ + constructor(dom, obj) { + let gm; + if (obj?._typename === clTGeoManager) { + gm = obj; + obj = obj.fMasterVolume; + } + + if (obj?._typename && (obj._typename.indexOf(clTGeoVolume) === 0)) + obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + + super(dom, obj); + + this.#superimpose = Boolean(getHistPainter3DCfg(this.getMainPainter())); + this.#geo_manager = gm; + + this._no_default_title = true; // do not set title to main DIV + this.mode3d = true; // indication of 3D mode + this.drawing_stage = stageInit; // + this.#drawing_log = 'Init'; + this.ctrl = { + clipIntersect: true, + clipVisualize: false, + clip: [{ name: 'x', enabled: false, value: 0, min: -100, max: 100, step: 1 }, + { name: 'y', enabled: false, value: 0, min: -100, max: 100, step: 1 }, + { name: 'z', enabled: false, value: 0, min: -100, max: 100, step: 1 }], + _highlight: 0, + highlight: 0, + highlight_bloom: 0, + highlight_scene: 0, + highlight_color: '#00ff00', + bloom_strength: 1.5, + more: 1, + maxfaces: 0, + vislevel: undefined, + maxnodes: undefined, + dflt_colors: false, + + info: { num_meshes: 0, num_faces: 0, num_shapes: 0 }, + depthTest: true, + depthMethod: 'dflt', + select_in_view: false, + update_browser: true, + use_fog: false, + light: { kind: 'points', top: false, bottom: false, left: false, right: false, front: false, specular: true, power: 1 }, + lightKindItems: [ + { name: 'AmbientLight', value: 'ambient' }, + { name: 'DirectionalLight', value: 'points' }, + { name: 'HemisphereLight', value: 'hemisphere' }, + { name: 'Ambient + Point', value: 'mix' } + ], + trans_radial: 0, + trans_z: 0, + scale: new THREE.Vector3(1, 1, 1), + zoom: 1.0, rotatey: 0, rotatez: 0, + depthMethodItems: [ + { name: 'Default', value: 'dflt' }, + { name: 'Raytraicing', value: 'ray' }, + { name: 'Boundary box', value: 'box' }, + { name: 'Mesh size', value: 'size' }, + { name: 'Central point', value: 'pnt' } + ], + cameraKindItems: [ + { name: 'Perspective', value: 'perspective' }, + { name: 'Perspective (Floor XOZ)', value: 'perspXOZ' }, + { name: 'Perspective (Floor YOZ)', value: 'perspYOZ' }, + { name: 'Perspective (Floor XOY)', value: 'perspXOY' }, + { name: 'Orthographic (XOY)', value: 'orthoXOY' }, + { name: 'Orthographic (XOZ)', value: 'orthoXOZ' }, + { name: 'Orthographic (ZOY)', value: 'orthoZOY' }, + { name: 'Orthographic (ZOX)', value: 'orthoZOX' }, + { name: 'Orthographic (XnOY)', value: 'orthoXNOY' }, + { name: 'Orthographic (XnOZ)', value: 'orthoXNOZ' }, + { name: 'Orthographic (ZnOY)', value: 'orthoZNOY' }, + { name: 'Orthographic (ZnOX)', value: 'orthoZNOX' } + ], + cameraOverlayItems: [ + { name: 'None', value: 'none' }, + { name: 'Bar', value: 'bar' }, + { name: 'Axis', value: 'axis' }, + { name: 'Grid', value: 'grid' }, + { name: 'Grid background', value: 'gridb' }, + { name: 'Grid foreground', value: 'gridf' } + ], + camera_kind: 'perspective', + camera_overlay: 'gridb', + rotate: false, + background: settings.DarkMode ? '#000000' : '#ffffff', + can_rotate: true, + _axis: 0, + instancing: 0, + _count: false, + // material properties + wireframe: false, + transparency: 0, + flatShading: false, + roughness: 0.5, + metalness: 0.5, + shininess: 0, + reflectivity: 0.5, + material_kind: 'lambert', + materialKinds: [ + { name: 'MeshLambertMaterial', value: 'lambert', emissive: true, props: [{ name: 'flatShading' }] }, + { name: 'MeshBasicMaterial', value: 'basic' }, + { name: 'MeshStandardMaterial', value: 'standard', emissive: true, + props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }] }, + { name: 'MeshPhysicalMaterial', value: 'physical', emissive: true, + props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }, { name: 'reflectivity', min: 0, max: 1, step: 0.001 }] }, + { name: 'MeshPhongMaterial', value: 'phong', emissive: true, + props: [{ name: 'flatShading' }, { name: 'shininess', min: 0, max: 100, step: 0.1 }] }, + { name: 'MeshNormalMaterial', value: 'normal', props: [{ name: 'flatShading' }] }, + { name: 'MeshDepthMaterial', value: 'depth' }, + { name: 'MeshMatcapMaterial', value: 'matcap' }, + { name: 'MeshToonMaterial', value: 'toon' } + ], + getMaterialCfg() { + let cfg; + this.materialKinds.forEach(item => { + if (item.value === this.material_kind) + cfg = item; + }); + return cfg; + } + }; + + this.#draw_resolveFuncs = []; + this.#render_resolveFuncs = []; + + this.cleanup(true); + } + + /** @summary Returns scene */ + getScene() { return this.#scene; } + + /** @summary Returns camera */ + getCamera() { return this.#camera; } + + /** @summary Returns renderer */ + getRenderer() { return this.#renderer; } + + /** @summary Returns top Object3D instance */ + getTopObject3D() { return this.#toplevel; } + + /** @summary Assign geometry viewer mode + * @private */ + setGeomViewer(on) { this.#geom_viewer = on; } + + /** @summary Assign or remove subordinate painter */ + assignSubordinate(painter, do_assign = true) { + if (this.#subordinate_painters === undefined) + this.#subordinate_painters = []; + const pos = this.#subordinate_painters.indexOf(painter); + if (do_assign && pos < 0) + this.#subordinate_painters.push(painter); + else if (!do_assign && pos >= 0) + this.#subordinate_painters.splice(pos, 1); + } + + /** @summary Return list of subordinate painters */ + getSubordinates() { return this.#subordinate_painters ?? []; } + + /** @summary Assign or cleanup central painter */ + assignCentral(painter, do_assign = true) { + if (do_assign) + this.#central_painter = painter; + else if (this.#central_painter === painter) + this.#central_painter = undefined; + } + + /** @summary Returns central painter */ + getCentral() { return this.#central_painter; } + + /** @summary Returns extra objects configured for drawing */ + getExtraObjects() { return this.#extra_objects; } + + /** @summary Function called by framework when dark mode is changed + * @private */ + changeDarkMode(mode) { + if ((this.ctrl.background === '#000000') || (this.ctrl.background === '#ffffff')) + this.changedBackground((mode ?? settings.DarkMode) ? '#000000' : '#ffffff'); + } + + /** @summary Change drawing stage + * @private */ + changeStage(value, msg) { + this.drawing_stage = value; + if (!msg) { + switch (value) { + case stageInit: msg = 'Building done'; break; + case stageCollect: msg = 'collect visibles'; break; + case stageWorkerCollect: msg = 'worker collect visibles'; break; + case stageAnalyze: msg = 'Analyse visibles'; break; + case stageCollShapes: msg = 'collect shapes for building'; break; + case stageStartBuild: msg = 'Start build shapes'; break; + case stageWorkerBuild: msg = 'Worker build shapes'; break; + case stageBuild: msg = 'Build shapes'; break; + case stageBuildReady: msg = 'Build ready'; break; + case stageWaitMain: msg = 'Wait for main painter'; break; + case stageBuildProj: msg = 'Build projection'; break; + default: msg = `stage ${value}`; + } + } + this.#drawing_log = msg; + } + + /** @summary Check drawing stage */ + isStage(value) { return value === this.drawing_stage; } + + isBatchMode() { return isBatchMode() || this.batch_mode; } + + getControls() { return this.#controls; } + + /** @summary Create toolbar */ + createToolbar() { + if (this.#toolbar || !this.#webgl || this.ctrl.notoolbar || this.isBatchMode()) + return; + const buttonList = [{ + name: 'toImage', + title: 'Save as PNG', + icon: 'camera', + click: () => this.createSnapshot() + }, { + name: 'control', + title: 'Toggle control UI', + icon: 'rect', + click: () => this.showControlGui('toggle') + }, { + name: 'enlarge', + title: 'Enlarge geometry drawing', + icon: 'circle', + click: () => this.toggleEnlarge() + }]; + + // Only show VR icon if WebVR API available. + if (navigator.getVRDisplays) { + buttonList.push({ + name: 'entervr', + title: 'Enter VR (It requires a VR Headset connected)', + icon: 'vrgoggles', + click: () => this.toggleVRMode() + }); + this.initVRMode(); + } + + if (settings.ContextMenu) { + buttonList.push({ + name: 'menu', + title: 'Show context menu', + icon: 'question', + click: evnt => { + evnt.preventDefault(); + evnt.stopPropagation(); + + if (closeMenu()) + return; + + createMenu(evnt, this).then(menu => { + menu.painter.fillContextMenu(menu); + menu.show(); + }); + } + }); + } + + const bkgr = new THREE.Color(this.ctrl.background); + + this.#toolbar = new Toolbar(this.selectDom(), (bkgr.r + bkgr.g + bkgr.b) < 1, buttonList); + + this.#toolbar.createButtons(); + } + + /** @summary Initialize VR mode */ + initVRMode() { + // Dolly contains camera and controllers in VR Mode + // Allows moving the user in the scene + this.#dolly = new THREE.Group(); + this.#scene.add(this.#dolly); + this.#standingMatrix = new THREE.Matrix4(); + + // Raycaster temp variables to avoid one per frame allocation. + this.#raycasterEnd = new THREE.Vector3(); + this.#raycasterOrigin = new THREE.Vector3(); + + navigator.getVRDisplays().then(displays => { + const vrDisplay = displays[0]; + if (!vrDisplay) + return; + this.#renderer.vr.setDevice(vrDisplay); + this.#vrDisplay = vrDisplay; + if (vrDisplay.stageParameters) + this.#standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); + + this.initVRControllersGeometry(); + }); + } + + /** @summary Init VR controllers geometry + * @private */ + initVRControllersGeometry() { + const geometry = new THREE.SphereGeometry(0.025, 18, 36), + material = new THREE.MeshBasicMaterial({ color: 'grey', vertexColors: false }), + rayMaterial = new THREE.MeshBasicMaterial({ color: 'fuchsia', vertexColors: false }), + rayGeometry = new THREE.BoxGeometry(0.001, 0.001, 2), + ray1Mesh = new THREE.Mesh(rayGeometry, rayMaterial), + ray2Mesh = new THREE.Mesh(rayGeometry, rayMaterial), + sphere1 = new THREE.Mesh(geometry, material), + sphere2 = new THREE.Mesh(geometry, material); + + this.#controllersMeshes = []; + this.#controllersMeshes.push(sphere1); + this.#controllersMeshes.push(sphere2); + ray1Mesh.position.z -= 1; + ray2Mesh.position.z -= 1; + sphere1.add(ray1Mesh); + sphere2.add(ray2Mesh); + this.#dolly.add(sphere1); + this.#dolly.add(sphere2); + // Controller mesh hidden by default + sphere1.visible = false; + sphere2.visible = false; + } + + /** @summary Update VR controllers list + * @private */ + updateVRControllersList() { + const gamepads = navigator.getGamepads && navigator.getGamepads(); + // Has controller list changed? + if (this.#vrControllers && (gamepads.length === this.#vrControllers.length)) + return; + // Hide meshes. + this.#controllersMeshes.forEach(mesh => { mesh.visible = false; }); + this.#vrControllers = []; + for (let i = 0; i < gamepads.length; ++i) { + if (!gamepads[i] || !gamepads[i].pose) + continue; + this.#vrControllers.push({ + gamepad: gamepads[i], + mesh: this.#controllersMeshes[i] + }); + this.#controllersMeshes[i].visible = true; + } + } + + /** @summary Process VR controller intersection + * @private */ + processVRControllerIntersections() { + let intersects = []; + for (let i = 0; i < this.#vrControllers.length; ++i) { + const controller = this.#vrControllers[i].mesh, + end = controller.localToWorld(this.#raycasterEnd.set(0, 0, -1)), + origin = controller.localToWorld(this.#raycasterOrigin.set(0, 0, 0)); + end.sub(origin).normalize(); + intersects = intersects.concat(this.#controls.getOriginDirectionIntersects(origin, end)); + } + // Remove duplicates. + intersects = intersects.filter((item, pos) => { return intersects.indexOf(item) === pos; }); + this.#controls.processMouseMove(intersects); + } + + /** @summary Update VR controllers + * @private */ + updateVRControllers() { + this.updateVRControllersList(); + // Update pose. + for (let i = 0; i < this.#vrControllers.length; ++i) { + const controller = this.#vrControllers[i], + orientation = controller.gamepad.pose.orientation, + position = controller.gamepad.pose.position, + controllerMesh = controller.mesh; + if (orientation) + controllerMesh.quaternion.fromArray(orientation); + if (position) + controllerMesh.position.fromArray(position); + controllerMesh.updateMatrix(); + controllerMesh.applyMatrix4(this.#standingMatrix); + controllerMesh.matrixWorldNeedsUpdate = true; + } + this.processVRControllerIntersections(); + } + + /** @summary Toggle VR mode + * @private */ + toggleVRMode() { + if (!this.#vrDisplay) + return; + // Toggle VR mode off + if (this.#vrDisplay.isPresenting) { + this.exitVRMode(); + return; + } + this.#vr_camera_position = this.#camera.position.clone(); + this.#vr_camera_rotation = this.#camera.rotation.clone(); + this.#vrDisplay.requestPresent([{ source: this.#renderer.domElement }]).then(() => { + this.#vr_camera_near = this.#camera.near; + this.#dolly.position.set(this.#camera.position.x / 4, -this.#camera.position.y / 8, -this.#camera.position.z / 4); + this.#camera.position.set(0, 0, 0); + this.#dolly.add(this.#camera); + this.#camera.near = 0.1; + this.#camera.updateProjectionMatrix(); + this.#renderer.vr.enabled = true; + this.#renderer.setAnimationLoop(() => { + this.updateVRControllers(); + this.render3D(0); + }); + }); + this.#renderer.vr.enabled = true; + + window.addEventListener('keydown', evnt => { + // Esc Key turns VR mode off + if (evnt.code === 'Escape') + this.exitVRMode(); + }); + } + + /** @summary Exit VR mode + * @private */ + exitVRMode() { + if (!this.#vrDisplay.isPresenting) + return; + this.#renderer.vr.enabled = false; + this.#dolly.remove(this.#camera); + this.#scene.add(this.#camera); + // Restore Camera pose + this.#camera.position.copy(this.#vr_camera_position); + this.#vr_camera_position = undefined; + this.#camera.rotation.copy(this.#vr_camera_rotation); + this.#vr_camera_rotation = undefined; + this.#camera.near = this.#vr_camera_near; + this.#camera.updateProjectionMatrix(); + this.#vrDisplay.exitPresent(); + } + + /** @summary Returns main geometry object */ + getGeometry() { return this.getObject(); } + + /** @summary Modify visibility of provided node by name */ + modifyVisisbility(name, sign) { + if (getNodeKind(this.getGeometry()) !== kindGeo) + return; + + if (!name) + return setGeoBit(this.getGeometry().fVolume, geoBITS.kVisThis, (sign === '+')); + + let regexp, exact = false; + + // arg.node.fVolume + if (name.indexOf('*') < 0) { + regexp = new RegExp('^' + name + '$'); + exact = true; + } else { + regexp = new RegExp('^' + name.split('*').join('.*') + '$'); + exact = false; + } + + this.findNodeWithVolume(regexp, arg => { + setInvisibleAll(arg.node.fVolume, (sign !== '+')); + return exact ? arg : null; // continue search if not exact expression provided + }); + } + + /** @summary Decode drawing options */ + decodeOptions(opt) { + if (!isStr(opt)) + opt = ''; + + if (this.#superimpose && (opt.indexOf('same') === 0)) + opt = opt.slice(4); + + const res = this.ctrl, macro = opt.indexOf('macro:'); + + if (macro >= 0) { + let separ = opt.indexOf(';', macro + 6); + if (separ < 0) + separ = opt.length; + res.script_name = opt.slice(macro + 6, separ); + opt = opt.slice(0, macro) + opt.slice(separ + 1); + console.log(`script ${res.script_name} rest ${opt}`); + } + + while (true) { + const pp = opt.indexOf('+'), pm = opt.indexOf('-'); + if ((pp < 0) && (pm < 0)) + break; + let p1 = pp, sign = '+'; + if ((p1 < 0) || ((pm >= 0) && (pm < pp))) { + p1 = pm; + sign = '-'; + } + + let p2 = p1 + 1; + const regexp = /[,; .]/; + while ((p2 < opt.length) && !regexp.test(opt[p2]) && (opt[p2] !== '+') && (opt[p2] !== '-')) + p2++; + + const name = opt.substring(p1 + 1, p2); + opt = opt.slice(0, p1) + opt.slice(p2); + + this.modifyVisisbility(name, sign); + } + + const d = new DrawOptions(opt); + + if (d.check('MAIN')) + res.is_main = true; + + if (d.check('DUMMY')) + res.dummy = true; + + if (d.check('TRACKS')) + res.tracks = true; // only for TGeoManager + if (d.check('SHOWTOP')) + res.showtop = true; // only for TGeoManager + if (d.check('NO_SCREEN')) + res.no_screen = true; // ignore kVisOnScreen bits for visibility + + if (d.check('NOINSTANCING')) + res.instancing = -1; // disable usage of InstancedMesh + if (d.check('INSTANCING')) + res.instancing = 1; // force usage of InstancedMesh + + if (d.check('ORTHO_CAMERA')) { + res.camera_kind = 'orthoXOY'; + res.can_rotate = 0; + } + if (d.check('ORTHO', true)) { + res.camera_kind = 'ortho' + d.part; + res.can_rotate = 0; + } + if (d.check('OVERLAY', true)) + res.camera_overlay = d.part.toLowerCase(); + if (d.check('CAN_ROTATE')) + res.can_rotate = true; + if (d.check('PERSPECTIVE')) { + res.camera_kind = 'perspective'; + res.can_rotate = true; + } + if (d.check('PERSP', true)) { + res.camera_kind = 'persp' + d.part; + res.can_rotate = true; + } + if (d.check('MOUSE_CLICK')) + res.mouse_click = true; + + if (d.check('DEPTHRAY') || d.check('DRAY')) + res.depthMethod = 'ray'; + if (d.check('DEPTHBOX') || d.check('DBOX')) + res.depthMethod = 'box'; + if (d.check('DEPTHPNT') || d.check('DPNT')) + res.depthMethod = 'pnt'; + if (d.check('DEPTHSIZE') || d.check('DSIZE')) + res.depthMethod = 'size'; + if (d.check('DEPTHDFLT') || d.check('DDFLT')) + res.depthMethod = 'dflt'; + + if (d.check('ZOOM', true)) + res.zoom = d.partAsFloat(0, 100) / 100; + if (d.check('ROTY', true)) + res.rotatey = d.partAsFloat(); + if (d.check('ROTZ', true)) + res.rotatez = d.partAsFloat(); + + if (d.check('PHONG')) + res.material_kind = 'phong'; + if (d.check('LAMBERT')) + res.material_kind = 'lambert'; + if (d.check('MATCAP')) + res.material_kind = 'matcap'; + if (d.check('TOON')) + res.material_kind = 'toon'; + + if (d.check('AMBIENT')) + res.light.kind = 'ambient'; + + const getCamPart = () => { + let neg = 1; + if (d.part[0] === 'N') { + neg = -1; + d.part = d.part.slice(1); + } + return neg * d.partAsFloat(); + }; + + if (d.check('CAMX', true)) + res.camx = getCamPart(); + if (d.check('CAMY', true)) + res.camy = getCamPart(); + if (d.check('CAMZ', true)) + res.camz = getCamPart(); + if (d.check('CAMLX', true)) + res.camlx = getCamPart(); + if (d.check('CAMLY', true)) + res.camly = getCamPart(); + if (d.check('CAMLZ', true)) + res.camlz = getCamPart(); + + if (d.check('BLACK')) + res.background = '#000000'; + if (d.check('WHITE')) + res.background = '#FFFFFF'; + + if (d.check('BKGR_', true)) { + let bckgr = null; + if (d.partAsInt(1) > 0) + bckgr = getColor(d.partAsInt()); + else { + for (let col = 0; col < 8; ++col) { + if (getColor(col).toUpperCase() === d.part) + bckgr = getColor(col); + } + } + if (bckgr) + res.background = '#' + new THREE.Color(bckgr).getHexString(); + } + + if (d.check('R3D_', true)) + res.Render3D = constants$1.Render3D.fromString(d.part.toLowerCase()); + + if (d.check('MORE', true)) + res.more = d.partAsInt(0, 2) ?? 2; + if (d.check('ALL')) { + res.more = 100; + res.vislevel = 99; + } + + if (d.check('VISLVL', true)) + res.vislevel = d.partAsInt(); + if (d.check('MAXNODES', true)) + res.maxnodes = d.partAsInt(); + if (d.check('MAXFACES', true)) + res.maxfaces = d.partAsInt(); + + if (d.check('CONTROLS') || d.check('CTRL')) + res.show_controls = true; + + if (d.check('CLIPXYZ')) + res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; + if (d.check('CLIPX')) + res.clip[0].enabled = true; + if (d.check('CLIPY')) + res.clip[1].enabled = true; + if (d.check('CLIPZ')) + res.clip[2].enabled = true; + if (d.check('CLIP')) + res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; + + if (d.check('PROJX', true)) { + res.project = 'x'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } + if (d.check('PROJY', true)) { + res.project = 'y'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } + if (d.check('PROJZ', true)) { + res.project = 'z'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } + + if (d.check('DFLT_COLORS') || d.check('DFLT')) + res.dflt_colors = true; + d.check('SSAO'); // deprecated + if (d.check('NOBLOOM')) + res.highlight_bloom = false; + if (d.check('BLOOM')) + res.highlight_bloom = true; + if (d.check('OUTLINE')) + res.outline = true; + + if (d.check('NOWORKER')) + res.use_worker = -1; + if (d.check('WORKER')) + res.use_worker = 1; + + if (d.check('NOFOG')) + res.use_fog = false; + if (d.check('FOG')) + res.use_fog = true; + + if (d.check('NOHIGHLIGHT') || d.check('NOHIGH')) + res.highlight_scene = res.highlight = false; + if (d.check('HIGHLIGHT')) + res.highlight_scene = res.highlight = true; + if (d.check('HSCENEONLY')) { + res.highlight_scene = true; + res.highlight = false; + } + if (d.check('NOHSCENE')) + res.highlight_scene = false; + if (d.check('HSCENE')) + res.highlight_scene = true; + + if (d.check('WIREFRAME') || d.check('WIRE')) + res.wireframe = true; + if (d.check('ROTATE')) + res.rotate = true; + + if (d.check('INVX') || d.check('INVERTX')) + res.scale.x = -1; + if (d.check('INVY') || d.check('INVERTY')) + res.scale.y = -1; + if (d.check('INVZ') || d.check('INVERTZ')) + res.scale.z = -1; + + if (d.check('COUNT')) + res._count = true; + + if (d.check('TRANSP', true)) + res.transparency = d.partAsInt(0, 100) / 100; + + if (d.check('OPACITY', true)) + res.transparency = 1 - d.partAsInt(0, 100) / 100; + + if (d.check('AXISCENTER') || d.check('AXISC') || d.check('AC')) + res._axis = 2; + if (d.check('AXIS') || d.check('A')) + res._axis = 1; + + if (d.check('TRR', true)) + res.trans_radial = d.partAsInt() / 100; + if (d.check('TRZ', true)) + res.trans_z = d.partAsInt() / 100; + + if (d.check('W')) + res.wireframe = true; + if (d.check('Y')) + res._yup = true; + if (d.check('Z')) + res._yup = false; + + // when drawing geometry without TCanvas, yup = true by default + if (res._yup === undefined) + res._yup = this.getCanvSvg().empty(); + + // let reuse for storing origin options + this.setOptions(res, true); + } + + /** @summary Activate specified items in the browser */ + activateInBrowser(names, force) { + if (isStr(names)) + names = [names]; + + const h = this.getHPainter(); + + if (h) { + // show browser if it not visible + + h.activateItems(names, force); + + // if highlight in the browser disabled, suppress in few seconds + if (!this.ctrl.update_browser) + setTimeout(() => h.activateItems([]), 2000); + } + } + + /** @summary Return ClonedNodes instance from the painter */ + getClones() { return this.#clones; } + + /** @summary Assign clones, created outside. + * @desc Used by ROOT geometry painter, where clones are handled by the server */ + assignClones(clones, owner = true) { + if (this.#clones_owner) + this.#clones?.cleanup(this.#draw_nodes, this.#build_shapes); + this.#draw_nodes = undefined; + this.#build_shapes = undefined; + this.#drawing_ready = false; + + this.#clones = clones; + this.#clones_owner = owner; + } + + /** @summary method used to check matrix calculations performance with current three.js model */ + testMatrixes() { + let errcnt = 0, totalcnt = 0, totalmax = 0; + + const arg = { + domatrix: true, + func: (/* node */) => { + let m2 = this.getmatrix(); + const entry = this.copyStack(), + mesh = this.#clones.createObject3D(entry.stack, this.#toplevel, kGetMesh); + if (!mesh) + return true; + + totalcnt++; + + const m1 = mesh.matrixWorld; + if (m1.equals(m2)) + return true; + if ((m1.determinant() > 0) && (m2.determinant() < -0.9)) { + const flip = new THREE.Vector3(1, 1, -1); + m2 = m2.clone().scale(flip); + if (m1.equals(m2)) + return true; + } + + let max = 0; + for (let k = 0; k < 16; ++k) + max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k])); + + totalmax = Math.max(max, totalmax); + + if (max < 1e-4) + return true; + + console.log(`${this.#clones.resolveStack(entry.stack).name} maxdiff ${max} determ ${m1.determinant()} ${m2.determinant()}`); + + errcnt++; + + return false; + } + }, tm1 = new Date().getTime(); + + this.#clones.scanVisible(arg); + + const tm2 = new Date().getTime(); + + console.log(`Compare matrixes total ${totalcnt} errors ${errcnt} takes ${tm2 - tm1} maxdiff ${totalmax}`); + } + + /** @summary Fill context menu */ + fillContextMenu(menu) { + menu.header('Draw options'); + + menu.addchk(this.ctrl.update_browser, 'Browser update', () => { + this.ctrl.update_browser = !this.ctrl.update_browser; + if (!this.ctrl.update_browser) + this.activateInBrowser([]); + }); + menu.addchk(this.ctrl.show_controls, 'Show Controls', () => this.showControlGui('toggle')); + + menu.sub('Show axes', () => this.setAxesDraw('toggle')); + menu.addchk(this.ctrl._axis === 0, 'off', 0, arg => this.setAxesDraw(parseInt(arg))); + menu.addchk(this.ctrl._axis === 1, 'side', 1, arg => this.setAxesDraw(parseInt(arg))); + menu.addchk(this.ctrl._axis === 2, 'center', 2, arg => this.setAxesDraw(parseInt(arg))); + menu.endsub(); + + if (this.#geo_manager) + menu.addchk(this.ctrl.showtop, 'Show top volume', () => this.setShowTop(!this.ctrl.showtop)); + + menu.addchk(this.ctrl.wireframe, 'Wire frame', () => this.toggleWireFrame()); + + if (!this.getCanvPainter()) + menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + + menu.sub('Highlight'); + + menu.addchk(!this.ctrl.highlight, 'Off', () => { + this.ctrl.highlight = false; + this.changedHighlight(); + }); + menu.addchk(this.ctrl.highlight && !this.ctrl.highlight_bloom, 'Normal', () => { + this.ctrl.highlight = true; + this.ctrl.highlight_bloom = false; + this.changedHighlight(); + }); + menu.addchk(this.ctrl.highlight && this.ctrl.highlight_bloom, 'Bloom', () => { + this.ctrl.highlight = true; + this.ctrl.highlight_bloom = true; + this.changedHighlight(); + }); + + menu.separator(); + + menu.addchk(this.ctrl.highlight_scene, 'Scene', flag => { + this.ctrl.highlight_scene = flag; + this.changedHighlight(); + }); + + menu.endsub(); + + menu.sub('Camera'); + menu.add('Reset position', () => this.focusCamera()); + if (!this.ctrl.project) + menu.addchk(this.ctrl.rotate, 'Autorotate', () => this.setAutoRotate(!this.ctrl.rotate)); + + if (!this.#geom_viewer) { + menu.addchk(this.canRotateCamera(), 'Can rotate', () => this.changeCanRotate(!this.ctrl.can_rotate)); + + menu.add('Get position', () => menu.info('Position (as url)', '&opt=' + this.produceCameraUrl())); + if (!this.isOrthoCamera()) { + menu.add('Absolute position', () => { + const url = this.produceCameraUrl(true), p = url.indexOf('camlx'); + menu.info('Position (as url)', '&opt=' + ((p < 0) ? url : url.slice(0, p) + '\n' + url.slice(p))); + }); + } + + menu.sub('Kind'); + this.ctrl.cameraKindItems.forEach(item => + menu.addchk(this.ctrl.camera_kind === item.value, item.name, item.value, arg => { + this.ctrl.camera_kind = arg; + this.changeCamera(); + })); + menu.endsub(); + + if (this.isOrthoCamera()) { + menu.sub('Overlay'); + this.ctrl.cameraOverlayItems.forEach(item => + menu.addchk(this.ctrl.camera_overlay === item.value, item.name, item.value, arg => { + this.ctrl.camera_overlay = arg; + this.changeCamera(); + })); + menu.endsub(); + } + } + menu.endsub(); + + menu.addchk(this.ctrl.select_in_view, 'Select in view', () => { + this.ctrl.select_in_view = !this.ctrl.select_in_view; + if (this.ctrl.select_in_view) + this.startDrawGeometry(); + }); + } + + /** @summary Method used to set transparency for all geometrical shapes + * @param {number|Function} transparency - one could provide function + * @param {boolean} [skip_render] - if specified, do not perform rendering */ + changedGlobalTransparency(transparency) { + const func = isFunc(transparency) ? transparency : null; + if (func || (transparency === undefined)) + transparency = this.ctrl.transparency; + + this.#toplevel?.traverse(node => { + // ignore all kind of extra elements + if (node?.material?.inherentOpacity === undefined) + return; + + const t = func ? func(node) : undefined; + if (t !== undefined) + node.material.opacity = 1 - t; + else + node.material.opacity = Math.min(1 - (transparency || 0), node.material.inherentOpacity); + + node.material.depthWrite = node.material.opacity === 1; + node.material.transparent = node.material.opacity < 1; + }); + + this.render3D(); + } + + /** @summary Method used to interactively change material kinds */ + changedMaterial() { + this.#toplevel?.traverse(node => { + // ignore all kind of extra elements + if (node.material?.inherentArgs !== undefined) + node.material = createMaterial(this.ctrl, node.material.inherentArgs); + }); + + this.render3D(-1); + } + + /** @summary Change for all materials that property */ + changeMaterialProperty(name) { + const value = this.ctrl[name]; + if (value === undefined) + return console.error('No property ', name); + + this.#toplevel?.traverse(node => { + // ignore all kind of extra elements + if (node.material?.inherentArgs === undefined) + return; + + if (node.material[name] !== undefined) { + node.material[name] = value; + node.material.needsUpdate = true; + } + }); + + this.render3D(); + } + + /** @summary Reset transformation */ + resetTransformation() { + this.changedTransformation('reset'); + } + + /** @summary Method should be called when transformation parameters were changed */ + changedTransformation(arg) { + if (!this.#toplevel) + return; + + const ctrl = this.ctrl, + translation = new THREE.Matrix4(), + vect2 = new THREE.Vector3(); + + if (arg === 'reset') + ctrl.trans_z = ctrl.trans_radial = 0; + + this.#toplevel.traverse(mesh => { + if (mesh.stack !== undefined) { + const node = mesh.parent; + + if (arg === 'reset') { + if (node.matrix0) { + node.matrix.copy(node.matrix0); + node.matrix.decompose(node.position, node.quaternion, node.scale); + node.matrixWorldNeedsUpdate = true; + } + delete node.matrix0; + delete node.vect0; + delete node.vect1; + delete node.minvert; + return; + } + + if (node.vect0 === undefined) { + node.matrix0 = node.matrix.clone(); + node.minvert = new THREE.Matrix4().copy(node.matrixWorld).invert(); + + const box3 = getBoundingBox(mesh, null, true), + signz = mesh._flippedMesh ? -1 : 1; + + // real center of mesh in local coordinates + node.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld); + node.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(node.minvert); + } + + vect2.set(ctrl.trans_radial * node.vect0.x, ctrl.trans_radial * node.vect0.y, ctrl.trans_z * node.vect0.z).applyMatrix4(node.minvert).sub(node.vect1); + + node.matrix.multiplyMatrices(node.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); + node.matrix.decompose(node.position, node.quaternion, node.scale); + node.matrixWorldNeedsUpdate = true; + } else if (mesh.stacks !== undefined) { + mesh.instanceMatrix.needsUpdate = true; + + if (arg === 'reset') { + mesh.trans?.forEach((item, i) => { + mesh.setMatrixAt(i, item.matrix0); + }); + delete mesh.trans; + return; + } + + if (mesh.trans === undefined) { + mesh.trans = new Array(mesh.count); + + mesh.geometry.computeBoundingBox(); + + for (let i = 0; i < mesh.count; i++) { + const item = { + matrix0: new THREE.Matrix4(), + minvert: new THREE.Matrix4() + }; + + mesh.trans[i] = item; + + mesh.getMatrixAt(i, item.matrix0); + item.minvert.copy(item.matrix0).invert(); + + const box3 = new THREE.Box3().copy(mesh.geometry.boundingBox).applyMatrix4(item.matrix0); + + item.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, (box3.max.z + box3.min.z) / 2); + item.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(item.minvert); + } + } + + const mm = new THREE.Matrix4(); + + mesh.trans?.forEach((item, i) => { + vect2.set(ctrl.trans_radial * item.vect0.x, ctrl.trans_radial * item.vect0.y, ctrl.trans_z * item.vect0.z).applyMatrix4(item.minvert).sub(item.vect1); + + mm.multiplyMatrices(item.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); + + mesh.setMatrixAt(i, mm); + }); + } + }); + + this.#toplevel.updateMatrixWorld(); + + // axes drawing always triggers rendering + if (arg !== 'norender') + this.drawAxesAndOverlay(); + } + + /** @summary Should be called when auto rotate property changed */ + changedAutoRotate() { + this.autorotate(2.5); + } + + /** @summary Method should be called when changing axes drawing */ + changedAxes() { + if (isStr(this.ctrl._axis)) + this.ctrl._axis = parseInt(this.ctrl._axis); + + this.drawAxesAndOverlay(); + } + + /** @summary Method should be called to change background color */ + changedBackground(val) { + if (val !== undefined) + this.ctrl.background = val; + this.#scene.background = new THREE.Color(this.ctrl.background); + this.#renderer.setClearColor(this.#scene.background, 1); + this.render3D(0); + + if (this.#toolbar) { + const bkgr = new THREE.Color(this.ctrl.background); + this.#toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1); + } + } + + /** @summary Display control GUI */ + showControlGui(on) { + // while complete geo drawing can be removed until dat is loaded - just check and ignore callback + if (!this.ctrl) + return; + + if (on === 'toggle') + on = !this.#gui; + else if (on === undefined) + on = this.ctrl.show_controls; + + this.ctrl.show_controls = on; + + if (this.#gui) { + if (!on) { + this.#gui.destroy(); + this.#gui = undefined; + } + return; + } + + if (!on || !this.#renderer) + return; + + const main = this.selectDom(); + if (main.style('position') === 'static') + main.style('position', 'relative'); + + this.#gui = new GUI({ + container: main.node(), closeFolders: true, + width: Math.min(300, this.#scene_width / 2), + title: 'Settings' + }); + + const dom = this.#gui.domElement; + dom.style.position = 'absolute'; + dom.style.top = 0; + dom.style.right = 0; + + this.#gui.painter = this; + + const makeLil = items => { + const lil = {}; + items.forEach(i => { lil[i.name] = i.value; }); + return lil; + }; + + if (!this.ctrl.project) { + const selection = this.#gui.addFolder('Selection'); + + if (!this.ctrl.maxnodes) + this.ctrl.maxnodes = this.#clones?.getMaxVisNodes() ?? 10000; + if (!this.ctrl.vislevel) + this.ctrl.vislevel = this.#clones?.getVisLevel() ?? 3; + if (!this.ctrl.maxfaces) + this.ctrl.maxfaces = 200000 * this.ctrl.more; + this.ctrl.more = 1; + + selection.add(this.ctrl, 'vislevel', 1, 99, 1) + .name('Visibility level') + .listen().onChange(() => this.startRedraw(500)); + selection.add(this.ctrl, 'maxnodes', 0, 500000, 1000) + .name('Visible nodes') + .listen().onChange(() => this.startRedraw(500)); + selection.add(this.ctrl, 'maxfaces', 0, 5000000, 100000) + .name('Max faces') + .listen().onChange(() => this.startRedraw(500)); + } + + if (this.ctrl.project) { + const bound = this.getGeomBoundingBox(this.getProjectionSource(), 0.01), + axis = this.ctrl.project; + + if (this.ctrl.projectPos === undefined) + this.ctrl.projectPos = (bound.min[axis] + bound.max[axis]) / 2; + + this.#gui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis]) + .name(axis.toUpperCase() + ' projection') + .onChange(() => this.startDrawGeometry()); + } else { + // Clipping Options + + const clipFolder = this.#gui.addFolder('Clipping'); + + for (let naxis = 0; naxis < 3; ++naxis) { + const cc = this.ctrl.clip[naxis], + axisC = cc.name.toUpperCase(); + + clipFolder.add(cc, 'enabled') + .name('Enable ' + axisC) + .listen().onChange(() => this.changedClipping(-1)); + + clipFolder.add(cc, 'value', cc.min, cc.max, cc.step) + .name(axisC + ' position') + .listen().onChange(() => this.changedClipping(naxis)); + } + + clipFolder.add(this.ctrl, 'clipIntersect').name('Clip intersection') + .onChange(() => this.changedClipping(-1)); + + clipFolder.add(this.ctrl, 'clipVisualize').name('Visualize') + .onChange(() => this.changedClipping(-1)); + } + + // Scene Options + + const scene = this.#gui.addFolder('Scene'); + // following items used in handlers and cannot be constants + let light_pnts = null, strength = null, hcolor = null, overlay = null; + + scene.add(this.ctrl.light, 'kind', makeLil(this.ctrl.lightKindItems)).name('Light') + .listen().onChange(() => { + light_pnts.show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points'); + this.changedLight(); + }); + + this.ctrl.light._pnts = this.ctrl.light.specular ? 0 : (this.ctrl.light.front ? 1 : 2); + light_pnts = scene.add(this.ctrl.light, '_pnts', { specular: 0, front: 1, box: 2 }) + .name('Positions') + .show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points') + .onChange(v => { + this.ctrl.light.specular = (v === 0); + this.ctrl.light.front = (v === 1); + this.ctrl.light.top = this.ctrl.light.bottom = this.ctrl.light.left = this.ctrl.light.right = (v === 2); + this.changedLight(); + }); + + scene.add(this.ctrl.light, 'power', 0, 10, 0.01).name('Power') + .listen().onChange(() => this.changedLight()); + + scene.add(this.ctrl, 'use_fog').name('Fog') + .listen().onChange(() => this.changedUseFog()); + + // Appearance Options + + const appearance = this.#gui.addFolder('Appearance'); + + this.ctrl._highlight = !this.ctrl.highlight ? 0 : this.ctrl.highlight_bloom ? 2 : 1; + appearance.add(this.ctrl, '_highlight', { none: 0, normal: 1, bloom: 2 }).name('Highlight Selection') + .listen().onChange(() => { + this.changedHighlight(this.ctrl._highlight); + strength.show(this.ctrl._highlight === 2); + hcolor.show(this.ctrl._highlight === 1); + }); + + hcolor = appearance.addColor(this.ctrl, 'highlight_color').name('Hightlight color') + .show(this.ctrl._highlight === 1); + strength = appearance.add(this.ctrl, 'bloom_strength', 0, 3).name('Bloom strength') + .listen().onChange(() => this.changedHighlight()) + .show(this.ctrl._highlight === 2); + + appearance.addColor(this.ctrl, 'background').name('Background') + .onChange(col => this.changedBackground(col)); + + appearance.add(this.ctrl, '_axis', { none: 0, side: 1, center: 2 }).name('Axes') + .onChange(() => this.changedAxes()); + + if (!this.ctrl.project) { + appearance.add(this.ctrl, 'rotate').name('Autorotate') + .listen().onChange(() => this.changedAutoRotate()); + } + + // Material options + + const material = this.#gui.addFolder('Material'); + let material_props = []; + + const addMaterialProp = () => { + material_props.forEach(f => f.destroy()); + material_props = []; + + const props = this.ctrl.getMaterialCfg()?.props; + if (!props) + return; + + props.forEach(prop => { + const f = material.add(this.ctrl, prop.name, prop.min, prop.max, prop.step).onChange(() => { + this.changeMaterialProperty(prop.name); + }); + material_props.push(f); + }); + }; + + material.add(this.ctrl, 'material_kind', makeLil(this.ctrl.materialKinds)).name('Kind') + .listen().onChange(() => { + addMaterialProp(); + this.ensureBloom(false); + this.changedMaterial(); + this.changedHighlight(); // for some materials bloom will not work + }); + + material.add(this.ctrl, 'transparency', 0, 1, 0.001).name('Transparency') + .listen().onChange(value => this.changedGlobalTransparency(value)); + + material.add(this.ctrl, 'wireframe').name('Wireframe') + .listen().onChange(() => this.changedWireFrame()); + + material.add(this, 'showMaterialDocu').name('Docu from threejs.org'); + + addMaterialProp(); + + + // Camera options + const camera = this.#gui.addFolder('Camera'); + + camera.add(this.ctrl, 'camera_kind', makeLil(this.ctrl.cameraKindItems)) + .name('Kind').listen().onChange(() => { + overlay.show(this.ctrl.camera_kind.indexOf('ortho') === 0); + this.changeCamera(); + }); + + camera.add(this.ctrl, 'can_rotate').name('Can rotate') + .listen().onChange(() => this.changeCanRotate()); + + camera.add(this, 'focusCamera').name('Reset position'); + + overlay = camera.add(this.ctrl, 'camera_overlay', makeLil(this.ctrl.cameraOverlayItems)) + .name('Overlay').listen().onChange(() => this.changeCamera()) + .show(this.ctrl.camera_kind.indexOf('ortho') === 0); + + // Advanced Options + if (this.#webgl) { + const advanced = this.#gui.addFolder('Advanced'); + + advanced.add(this.ctrl, 'depthTest').name('Depth test') + .listen().onChange(() => this.changedDepthTest()); + + advanced.add(this.ctrl, 'depthMethod', makeLil(this.ctrl.depthMethodItems)) + .name('Rendering order') + .onChange(method => this.changedDepthMethod(method)); + + advanced.add(this, 'resetAdvanced').name('Reset'); + } + + // Transformation Options + if (!this.ctrl.project) { + const transform = this.#gui.addFolder('Transform'); + transform.add(this.ctrl, 'trans_z', 0.0, 3.0, 0.01) + .name('Z axis') + .listen().onChange(() => this.changedTransformation()); + transform.add(this.ctrl, 'trans_radial', 0.0, 3.0, 0.01) + .name('Radial') + .listen().onChange(() => this.changedTransformation()); + + transform.add(this, 'resetTransformation').name('Reset'); + if (this.ctrl.trans_z || this.ctrl.trans_radial) + transform.open(); + } + } + + /** @summary show material documentation from https://threejs.org */ + showMaterialDocu() { + const cfg = this.ctrl.getMaterialCfg(); + if (cfg?.name && typeof window !== 'undefined') + window.open('https://threejs.org/docs/index.html#api/en/materials/' + cfg.name, '_blank'); + } + + /** @summary Should be called when configuration of highlight is changed */ + changedHighlight(arg) { + if (arg !== undefined) { + this.ctrl.highlight = Boolean(arg); + if (this.ctrl.highlight) + this.ctrl.highlight_bloom = (arg === 2); + } + + this.ensureBloom(); + + if (!this.ctrl.highlight) + this.highlightMesh(null); + + this.getSubordinates()?.forEach(p => { + p.ctrl.highlight = this.ctrl.highlight; + p.ctrl.highlight_bloom = this.ctrl.highlight_bloom; + p.ctrl.bloom_strength = this.ctrl.bloom_strength; + p.changedHighlight(); + }); + } + + /** @summary Handle change of can rotate */ + changeCanRotate(on) { + if (on !== undefined) + this.ctrl.can_rotate = on; + if (this.#controls) + this.#controls.enableRotate = this.ctrl.can_rotate; + } + + /** @summary Change use fog property */ + changedUseFog() { + this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; + + this.render3D(); + } + + /** @summary Handle change of camera kind */ + changeCamera() { + // force control recreation + if (this.#controls) { + this.#controls.cleanup(); + this.#controls = undefined; + } + + this.ensureBloom(false); + + // recreate camera + this.createCamera(); + + this.createSpecialEffects(); + + // set proper position + this.adjustCameraPosition(true); + + // this.drawOverlay(); + + this.addOrbitControls(); + + this.render3D(); + + // this.#scene_size = undefined; // ensure reassign of camera position + + // this.#first_drawing = true; + // this.startDrawGeometry(true); + } + + /** @summary create bloom effect */ + ensureBloom(on) { + if (on === undefined) { + if (this.ctrl.highlight_bloom === 0) + this.ctrl.highlight_bloom = this.#webgl && !browser.android; + + on = this.ctrl.highlight_bloom && this.ctrl.getMaterialCfg()?.emissive; + } + + if (on && !this.#bloomComposer) { + this.#camera.layers.enable(_BLOOM_SCENE); + this.#bloomComposer = new THREE.EffectComposer(this.#renderer); + this.#bloomComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); + const pass = new THREE.UnrealBloomPass(new THREE.Vector2(this.#scene_width, this.#scene_height), 1.5, 0.4, 0.85); + pass.threshold = 0; + pass.radius = 0; + pass.renderToScreen = true; + this.#bloomComposer.addPass(pass); + this.#renderer.autoClear = false; + } else if (!on && this.#bloomComposer) { + this.#bloomComposer.dispose(); + this.#bloomComposer = undefined; + if (this.#renderer) + this.#renderer.autoClear = true; + this.#camera?.layers.disable(_BLOOM_SCENE); + this.#camera?.layers.set(_ENTIRE_SCENE); + } + + if (this.#bloomComposer?.passes) + this.#bloomComposer.passes[1].strength = this.ctrl.bloom_strength; + } + + + /** @summary Show context menu for orbit control + * @private */ + orbitContext(evnt, intersects) { + createMenu(evnt, this).then(menu => { + let numitems = 0, numnodes = 0, cnt = 0; + if (intersects) { + for (let n = 0; n < intersects.length; ++n) { + if (getIntersectStack(intersects[n])) + numnodes++; + if (intersects[n].geo_name) + numitems++; + } + } + + if (numnodes + numitems === 0) + this.fillContextMenu(menu); + else { + const many = (numnodes + numitems) > 1; + + if (many) + menu.header((numitems > 0) ? 'Items' : 'Nodes'); + + for (let n = 0; n < intersects.length; ++n) { + const obj = intersects[n].object, + stack = getIntersectStack(intersects[n]); + let name, itemname, hdr; + + if (obj.geo_name) { + itemname = obj.geo_name; + if (itemname.indexOf('') === 0) + itemname = (this.getItemName() || 'top') + itemname.slice(6); + name = itemname.slice(itemname.lastIndexOf('/') + 1); + if (!name) + name = itemname; + hdr = name; + } else if (stack) { + name = this.#clones.getStackName(stack); + itemname = this.getStackFullName(stack); + hdr = this.getItemName(); + if (name.indexOf('Nodes/') === 0) + hdr = name.slice(6); + else if (name) + hdr = name; + else if (!hdr) + hdr = 'header'; + } else + continue; + + + cnt++; + + menu.add((many ? 'sub:' : 'header:') + hdr, itemname, arg => this.activateInBrowser([arg], true)); + + menu.add('Browse', itemname, arg => this.activateInBrowser([arg], true)); + + if (this.getHPainter()) + menu.add('Inspect', itemname, arg => this.getHPainter().display(arg, kInspect)); + + if (isFunc(this.hidePhysicalNode)) { + menu.add('Hide', itemname, arg => this.hidePhysicalNode([arg])); + if (cnt > 1) { + menu.add('Hide all before', n, indx => { + const items = []; + for (let i = 0; i < indx; ++i) { + const stack2 = getIntersectStack(intersects[i]); + if (stack2) + items.push(this.getStackFullName(stack2)); + } + this.hidePhysicalNode(items); + }); + } + } else if (obj.geo_name) { + menu.add('Hide', n, indx => { + const mesh = intersects[indx].object; + mesh.visible = false; // just disable mesh + if (mesh.geo_object) + mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw + menu.painter.render3D(); + }, 'Hide this physical node'); + + if (many) + menu.endsub(); + + continue; + } + + const wireframe = this.accessObjectWireFrame(obj); + if (wireframe !== undefined) { + menu.addchk(wireframe, 'Wireframe', n, indx => { + const m = intersects[indx].object.material; + m.wireframe = !m.wireframe; + this.render3D(); + }, 'Toggle wireframe mode for the node'); + } + + if (cnt > 1) { + menu.add('Manifest', n, indx => { + if (this.#last_manifest) + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; + + this.#last_hidden?.forEach(obj2 => { obj2.visible = true; }); + + this.#last_hidden = []; + + for (let i = 0; i < indx; ++i) + this.#last_hidden.push(intersects[i].object); + + this.#last_hidden.forEach(obj2 => { obj2.visible = false; }); + + this.#last_manifest = intersects[indx].object.material; + + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; + + this.render3D(); + }, 'Manifest selected node'); + } + + menu.add('Focus', n, indx => { + this.focusCamera(intersects[indx].object); + }); + + if (!this.#geom_viewer) { + menu.add('Hide', n, indx => { + const resolve = this.#clones.resolveStack(intersects[indx].object.stack); + if (resolve.obj && (resolve.node.kind === kindGeo) && resolve.obj.fVolume) { + setGeoBit(resolve.obj.fVolume, geoBITS.kVisThis, false); + updateBrowserIcons(resolve.obj.fVolume, this.getHPainter()); + } else if (resolve.obj && (resolve.node.kind === kindEve)) { + resolve.obj.fRnrSelf = false; + updateBrowserIcons(resolve.obj, this.getHPainter()); + } + + this.testGeomChanges();// while many volumes may disappear, recheck all of them + }, 'Hide all logical nodes of that kind'); + menu.add('Hide only this', n, indx => { + this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[indx]), false); + this.testGeomChanges(); + }, 'Hide only this physical node'); + if (n > 1) { + menu.add('Hide all before', n, indx => { + for (let k = 0; k < indx; ++k) + this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[k]), false); + this.testGeomChanges(); + }, 'Hide all physical nodes before that'); + } + } + + if (many) + menu.endsub(); + } + } + menu.show(); + }); + } + + /** @summary Filter some objects from three.js intersects array */ + filterIntersects(intersects) { + if (!intersects?.length) + return intersects; + + // check redirection + for (let n = 0; n < intersects.length; ++n) { + if (intersects[n].object.geo_highlight) + intersects[n].object = intersects[n].object.geo_highlight; + } + + // remove all elements without stack - indicator that this is geometry object + // also remove all objects which are mostly transparent + for (let n = intersects.length - 1; n >= 0; --n) { + const obj = intersects[n].object; + let unique = obj.visible && (getIntersectStack(intersects[n]) || (obj.geo_name !== undefined)); + + if (unique && (obj.material?.opacity !== undefined)) + unique = obj.material.opacity >= 0.1; + + if (obj.jsroot_special) + unique = false; + + for (let k = 0; (k < n) && unique; ++k) { + if (intersects[k].object === obj) + unique = false; + } + + if (!unique) + intersects.splice(n, 1); + } + + const clip = this.ctrl.clip; + + if (clip[0].enabled || clip[1].enabled || clip[2].enabled) { + const clippedIntersects = []; + + for (let i = 0; i < intersects.length; ++i) { + const point = intersects[i].point, special = (intersects[i].object.type === 'Points'); + let clipped = true; + + if (clip[0].enabled && ((this.#clip_planes[0].normal.dot(point) > this.#clip_planes[0].constant) ^ special)) + clipped = false; + if (clip[1].enabled && ((this.#clip_planes[1].normal.dot(point) > this.#clip_planes[1].constant) ^ special)) + clipped = false; + if (clip[2].enabled && (this.#clip_planes[2].normal.dot(point) > this.#clip_planes[2].constant)) + clipped = false; + + if (!clipped) + clippedIntersects.push(intersects[i]); + } + + intersects = clippedIntersects; + } + + return intersects; + } + + /** @summary test camera position + * @desc function analyzes camera position and start redraw of geometry + * if objects in view may be changed */ + testCameraPositionChange() { + if (!this.ctrl.select_in_view || this.#draw_all_nodes) + return; + + const matrix = createProjectionMatrix(this.#camera), + frustum = createFrustum(matrix); + + // check if overall bounding box seen + if (!frustum.CheckBox(this.getGeomBoundingBox())) + this.startDrawGeometry(); + } + + /** @summary Resolve stack */ + resolveStack(stack) { + return this.#clones && stack ? this.#clones.resolveStack(stack) : null; + } + + /** @summary Returns stack full name + * @desc Includes item name of top geo object */ + getStackFullName(stack) { + const mainitemname = this.getItemName(), + sub = this.resolveStack(stack); + if (!sub || !sub.name) + return mainitemname; + return mainitemname ? mainitemname + '/' + sub.name : sub.name; + } + + /** @summary Add handler which will be called when element is highlighted in geometry drawing + * @desc Handler should have highlightMesh function with same arguments as TGeoPainter */ + addHighlightHandler(handler) { + if (!isFunc(handler?.highlightMesh)) + return; + if (!this.#highlight_handlers) + this.#highlight_handlers = []; + this.#highlight_handlers.push(handler); + } + + /** @summary perform mesh highlight */ + highlightMesh(active_mesh, color, geo_object, geo_index, geo_stack, no_recursive) { + if (geo_object) { + active_mesh = active_mesh ? [active_mesh] : []; + const extras = this.getExtrasContainer(); + if (extras) { + extras.traverse(obj3d => { + if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d) < 0)) + active_mesh.push(obj3d); + }); + } + } else if (geo_stack && this.#toplevel) { + active_mesh = []; + this.#toplevel.traverse(mesh => { + if ((mesh instanceof THREE.Mesh) && isSameStack(mesh.stack, geo_stack)) + active_mesh.push(mesh); + }); + } else + active_mesh = active_mesh ? [active_mesh] : []; + + if (!active_mesh.length) + active_mesh = null; + + if (active_mesh) { + // check if highlight is disabled for correspondent objects kinds + if (active_mesh[0].geo_object) { + if (!this.ctrl.highlight_scene) + active_mesh = null; + } else + if (!this.ctrl.highlight) + active_mesh = null; + } + + if (!no_recursive) { + // check all other painters + + if (active_mesh) { + if (!geo_object) + geo_object = active_mesh[0].geo_object; + if (!geo_stack) + geo_stack = active_mesh[0].stack; + } + + const lst = this.#highlight_handlers || this.getCentral()?.getSubordinates().concat([this.getCentral()]) || this.getSubordinates(); + + for (let k = 0; k < lst?.length; ++k) { + if (lst[k] !== this) + lst[k].highlightMesh(null, color, geo_object, geo_index, geo_stack, true); + } + } + + const curr_mesh = this.#selected_mesh; + if (!curr_mesh && !active_mesh) + return false; + + const get_ctrl = mesh => { return mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh, this.ctrl.highlight_bloom && this.#bloomComposer); }; + + let same = false; + + // check if selections are the same + if (curr_mesh && active_mesh && (curr_mesh.length === active_mesh.length)) { + same = true; + for (let k = 0; (k < curr_mesh.length) && same; ++k) { + if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) + same = false; + } + } + if (same) + return Boolean(curr_mesh); + + curr_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight()); + + this.#selected_mesh = active_mesh; + + active_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight(color || new THREE.Color(this.ctrl.highlight_color), geo_index)); + + this.render3D(0); + + return Boolean(active_mesh); + } + + /** @summary handle mouse click event */ + processMouseClick(pnt, intersects, evnt) { + if (!intersects.length) + return; + + const mesh = intersects[0].object; + if (!mesh.get_ctrl) + return; + + const ctrl = mesh.get_ctrl(), + click_indx = ctrl.extractIndex(intersects[0]); + + ctrl.evnt = evnt; + + if (ctrl.setSelected('blue', click_indx)) + this.render3D(); + + ctrl.evnt = null; + } + + /** @summary Configure mouse delay, required for complex geometries */ + setMouseTmout(val) { + if (this.ctrl) + this.ctrl.mouse_tmout = val; + + if (this.#controls) + this.#controls.mouse_tmout = val; + } + + /** @summary Configure depth method, used for render order production. + * @param {string} method - Allowed values: 'ray', 'box','pnt', 'size', 'dflt' */ + setDepthMethod(method) { + if (this.ctrl) + this.ctrl.depthMethod = method; + } + + /** @summary Returns if camera can rotated */ + canRotateCamera() { + if (this.ctrl.can_rotate === false) + return false; + if (!this.ctrl.can_rotate && (this.isOrthoCamera() || this.ctrl.project)) + return false; + return true; + } + + /** @summary Add orbit control */ + addOrbitControls() { + if (this.#controls || !this.#webgl || this.isBatchMode() || this.#superimpose || isNodeJs()) + return; + + if (!this.getCanvPainter()) + this.setTooltipAllowed(settings.Tooltip); + + this.#controls = createOrbitControl(this, this.#camera, this.#scene, this.#renderer, this.#lookat); + + this.#controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing + + if (!this.canRotateCamera()) + this.#controls.enableRotate = false; + + this.#controls.contextMenu = this.orbitContext.bind(this); + + this.#controls.processMouseMove = intersects => { + // painter already cleaned up, ignore any incoming events + if (!this.ctrl || !this.#controls) + return; + + let active_mesh = null, tooltip = null, resolve = null, names = [], geo_object, geo_index, geo_stack; + + // try to find mesh from intersections + for (let k = 0; k < intersects.length; ++k) { + const obj = intersects[k].object, stack = getIntersectStack(intersects[k]); + if (!obj || !obj.visible) + continue; + let info = null; + if (obj.geo_object) + info = obj.geo_name; + else if (stack) + info = this.getStackFullName(stack); + if (!info) + continue; + + if (info.indexOf('') === 0) + info = this.getItemName() + info.slice(6); + + names.push(info); + + if (!active_mesh) { + active_mesh = obj; + tooltip = info; + geo_object = obj.geo_object; + if (obj.get_ctrl) { + geo_index = obj.get_ctrl().extractIndex(intersects[k]); + if ((geo_index !== undefined) && isStr(tooltip)) + tooltip += ' indx:' + JSON.stringify(geo_index); + } + geo_stack = stack; + + if (geo_stack) { + resolve = this.resolveStack(geo_stack); + if (obj.stacks) + geo_index = intersects[k].instanceId; + } + } + } + + this.highlightMesh(active_mesh, undefined, geo_object, geo_index); + + if (this.ctrl.update_browser) { + if (this.ctrl.highlight && tooltip) + names = [tooltip]; + this.activateInBrowser(names); + } + + if (!resolve?.obj) + return tooltip; + + const lines = provideObjectInfo(resolve.obj); + lines.unshift(tooltip); + + return { name: resolve.obj.fName, title: resolve.obj.fTitle || resolve.obj._typename, lines }; + }; + + this.#controls.processMouseLeave = function() { + this.processMouseMove([]); // to disable highlight and reset browser + }; + + this.#controls.processDblClick = () => { + // painter already cleaned up, ignore any incoming events + if (!this.ctrl || !this.#controls) + return; + + if (this.#last_manifest) { + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; + if (this.#last_hidden) + this.#last_hidden.forEach(obj => { obj.visible = true; }); + this.#last_hidden = undefined; + this.#last_manifest = undefined; + } else + this.adjustCameraPosition(true); + + this.render3D(); + }; + } + + /** @summary Main function in geometry creation loop + * @desc Returns: + * - false when nothing todo + * - true if one could perform next action immediately + * - 1 when call after short timeout required + * - 2 when call must be done from processWorkerReply */ + nextDrawAction() { + if (!this.#clones || this.isStage(stageInit)) + return false; + + if (this.isStage(stageCollect)) { + if (this.#geom_viewer) { + this.#draw_all_nodes = false; + this.changeStage(stageAnalyze); + return true; + } + + // wait until worker is really started + if (this.ctrl.use_worker > 0) { + if (!this.#worker) { + this.startWorker(); + return 1; + } + if (!this.#worker_ready) + return 1; + } + + // first copy visibility flags and check how many unique visible nodes exists + let numvis = this.#first_drawing ? this.#clones.countVisibles() : 0, + matrix = null, frustum = null; + + if (!numvis) + numvis = this.#clones.markVisibles(false, false, Boolean(this.#geo_manager) && !this.ctrl.showtop); + + if (this.ctrl.select_in_view && !this.#first_drawing) { + // extract camera projection matrix for selection + + matrix = createProjectionMatrix(this.#camera); + + frustum = createFrustum(matrix); + + // check if overall bounding box seen + if (frustum.CheckBox(this.getGeomBoundingBox())) { + matrix = null; // not use camera for the moment + frustum = null; + } + } + + this.#current_face_limit = this.ctrl.maxfaces; + if (matrix) + this.#current_face_limit *= 1.25; + + // here we decide if we need worker for the drawings + // main reason - too large geometry and large time to scan all camera positions + let need_worker = !this.isBatchMode() && browser.isChrome && ((numvis > 10000) || (matrix && (this.#clones.scanVisible() > 1e5))); + + // worker does not work when starting from file system + if (need_worker && exports.source_dir.indexOf('file://') === 0) { + console.log('disable worker for jsroot from file system'); + need_worker = false; + } + + if (need_worker && !this.#worker && (this.ctrl.use_worker >= 0)) + this.startWorker(); // we starting worker, but it may not be ready so fast + + if (!need_worker || !this.#worker_ready) { + const res = this.#clones.collectVisibles(this.#current_face_limit, frustum); + this.#new_draw_nodes = res.lst; + this.#draw_all_nodes = res.complete; + this.changeStage(stageAnalyze); + return true; + } + + const job = { + collect: this.#current_face_limit, // indicator for the command + flags: this.#clones.getVisibleFlags(), + matrix: matrix ? matrix.elements : null, + vislevel: this.#clones.getVisLevel(), + maxvisnodes: this.#clones.getMaxVisNodes() + }; + + this.submitToWorker(job); + + this.changeStage(stageWorkerCollect); + + return 2; // we now waiting for the worker reply + } + + if (this.isStage(stageWorkerCollect)) { + // do nothing, we are waiting for worker reply + return 2; + } + + if (this.isStage(stageAnalyze)) { + // here we merge new and old list of nodes for drawing, + // normally operation is fast and can be implemented with one c + + if (this.#new_append_nodes) { + this.#new_draw_nodes = this.#draw_nodes.concat(this.#new_append_nodes); + + this.#new_append_nodes = undefined; + } else if (this.#draw_nodes) { + let del; + if (this.#geom_viewer) + del = this.#draw_nodes; + else + del = this.#clones.mergeVisibles(this.#new_draw_nodes, this.#draw_nodes); + + // remove should be fast, do it here + for (let n = 0; n < del.length; ++n) + this.#clones.createObject3D(del[n].stack, this.#toplevel, kDeleteMesh); + + if (del.length) + this.#drawing_log = `Delete ${del.length} nodes`; + } + + this.#draw_nodes = this.#new_draw_nodes; + this.#new_draw_nodes = undefined; + this.changeStage(stageCollShapes); + return true; + } + + if (this.isStage(stageCollShapes)) { + // collect shapes + const shapes = this.#clones.collectShapes(this.#draw_nodes); + + // merge old and new list with produced shapes + this.#build_shapes = this.#clones.mergeShapesLists(this.#build_shapes, shapes); + + this.changeStage(stageStartBuild); + return true; + } + + if (this.isStage(stageStartBuild)) { + // this is building of geometries, + // one can ask worker to build them or do it ourself + + if (this.canSubmitToWorker()) { + const job = { limit: this.#current_face_limit, shapes: [] }; + let cnt = 0; + for (let n = 0; n < this.#build_shapes.length; ++n) { + const item = this.#build_shapes[n]; + // only submit not-done items + if (item.ready || item.geom) { + // this is place holder for existing geometry + job.shapes.push({ id: item.id, ready: true, nfaces: numGeometryFaces(item.geom), refcnt: item.refcnt }); + } else { + job.shapes.push(clone(item, null, true)); + cnt++; + } + } + + if (cnt > 0) { + // only if some geom missing, submit job to the worker + this.submitToWorker(job); + this.changeStage(stageWorkerBuild); + return 2; + } + } + + this.changeStage(stageBuild); + } + + if (this.isStage(stageWorkerBuild)) { + // waiting shapes from the worker, worker should activate our code + return 2; + } + + if (this.isStage(stageBuild) || this.isStage(stageBuildReady)) { + if (this.isStage(stageBuild)) { + // building shapes + + const res = this.#clones.buildShapes(this.#build_shapes, this.#current_face_limit, 500); + if (res.done) { + this.ctrl.info.num_shapes = this.#build_shapes.length; + this.changeStage(stageBuildReady); + } else { + this.ctrl.info.num_shapes = res.shapes; + this.#drawing_log = `Creating: ${res.shapes} / ${this.#build_shapes.length} shapes, ${res.faces} faces`; + return true; + } + } + + // final stage, create all meshes + + const tm0 = new Date().getTime(), + toplevel = this.ctrl.project ? this.#fullgeom_proj : this.#toplevel; + let build_instanced = false, ready = true; + + if (!this.ctrl.project) + build_instanced = this.#clones.createInstancedMeshes(this.ctrl, toplevel, this.#draw_nodes, this.#build_shapes, getRootColors()); + + if (!build_instanced) { + for (let n = 0; n < this.#draw_nodes.length; ++n) { + const entry = this.#draw_nodes[n]; + if (entry.done) + continue; + + // shape can be provided with entry itself + const shape = entry.server_shape || this.#build_shapes[entry.shapeid]; + + this.createEntryMesh(entry, shape, toplevel); + + const tm1 = new Date().getTime(); + if (tm1 - tm0 > 500) { + ready = false; + break; + } + } + } + + if (ready) { + if (this.ctrl.project) { + this.changeStage(stageBuildProj); + return true; + } + this.changeStage(stageInit); + return false; + } + + if (!this.isStage(stageBuild)) + this.#drawing_log = `Building meshes ${this.ctrl.info.num_meshes} / ${this.ctrl.info.num_faces}`; + return true; + } + + if (this.isStage(stageWaitMain)) { + // wait for main painter to be ready + if (!this.getCentral()) { + this.changeStage(stageInit, 'Lost main painter'); + return false; + } + if (!this.getCentral().isDrawingReady()) + return 1; + + this.changeStage(stageBuildProj); // just do projection + } + + if (this.isStage(stageBuildProj)) { + this.doProjection(); + this.changeStage(stageInit); + return false; + } + + console.error(`never come here, stage ${this.drawing_stage}`); + + return false; + } + + /** @summary Insert appropriate mesh for given entry */ + createEntryMesh(entry, shape, toplevel) { + // workaround for the TGeoOverlap, where two branches should get predefined color + if (this.ctrl.split_colors && entry.stack) { + if (entry.stack[0] === 0) + entry.custom_color = 'green'; + else if (entry.stack[0] === 1) + entry.custom_color = 'blue'; + } + + this.#clones.createEntryMesh(this.ctrl, toplevel, entry, shape, getRootColors()); + + return true; + } + + /** @summary used by geometry viewer to show more nodes + * @desc These nodes excluded from selection logic and always inserted into the model + * Shape already should be created and assigned to the node */ + appendMoreNodes(nodes, from_drawing) { + if (!this.isStage(stageInit) && !from_drawing) { + this.#provided_more_nodes = nodes; + return; + } + + // delete old nodes + if (this.#more_nodes) { + for (let n = 0; n < this.#more_nodes.length; ++n) { + const entry = this.#more_nodes[n], + obj3d = this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); + disposeThreejsObject(obj3d); + cleanupShape(entry.server_shape); + delete entry.server_shape; + } + this.#more_nodes = undefined; + } + + if (!nodes) + return; + + const real_nodes = []; + for (let k = 0; k < nodes.length; ++k) { + const entry = nodes[k], + shape = entry.server_shape; + if (!shape?.ready) + continue; + + if (this.createEntryMesh(entry, shape, this.#toplevel)) + real_nodes.push(entry); + } + + // remember additional nodes only if they include shape - otherwise one can ignore them + if (real_nodes.length) + this.#more_nodes = real_nodes; + + if (!from_drawing) + this.render3D(); + } + + /** @summary Returns hierarchy of 3D objects used to produce projection. + * @desc Typically external master painter is used, but also internal data can be used */ + getProjectionSource() { + if (this.#clones_owner) + return this.#fullgeom_proj; + if (!this.getCentral()?.isDrawingReady()) { + console.warn('MAIN PAINTER NOT READY WHEN DO PROJECTION'); + return null; + } + return this.getCentral().#toplevel; + } + + /** @summary Extend custom geometry bounding box */ + extendCustomBoundingBox(box) { + if (!box) + return; + if (!this.#custom_bounding_box) + this.#custom_bounding_box = new THREE.Box3().makeEmpty(); + + const origin = this.#custom_bounding_box.clone(); + this.#custom_bounding_box.union(box); + + if (!this.#custom_bounding_box.equals(origin)) + this.#adjust_camera_with_render = true; + } + + /** @summary Calculate geometry bounding box */ + getGeomBoundingBox(topitem, scalar) { + const box3 = new THREE.Box3(), check_any = !this.#clones; + if (topitem === undefined) + topitem = this.#toplevel; + + box3.makeEmpty(); + + if (this.#custom_bounding_box && (topitem === this.#toplevel)) { + box3.union(this.#custom_bounding_box); + return box3; + } + + if (!topitem) { + box3.min.x = box3.min.y = box3.min.z = -1; + box3.max.x = box3.max.y = box3.max.z = 1; + return box3; + } + + topitem.traverse(mesh => { + if (check_any || (mesh.stack && (mesh instanceof THREE.Mesh)) || + (mesh.main_track && (mesh instanceof THREE.LineSegments)) || (mesh.stacks && (mesh instanceof THREE.InstancedMesh))) + getBoundingBox(mesh, box3); + }); + + if (scalar === 'original') { + box3.translate(new THREE.Vector3(-topitem.position.x, -topitem.position.y, -topitem.position.z)); + box3.min.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); + box3.max.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); + } else if (scalar !== undefined) + box3.expandByVector(box3.getSize(new THREE.Vector3()).multiplyScalar(scalar)); + + return box3; + } + + /** @summary Create geometry projection */ + doProjection() { + const toplevel = this.getProjectionSource(); + + if (!toplevel) + return false; + + disposeThreejsObject(this.#toplevel, true); + + // let axis = this.ctrl.project; + + if (this.ctrl.projectPos === undefined) { + const bound = this.getGeomBoundingBox(toplevel), + min = bound.min[this.ctrl.project], max = bound.max[this.ctrl.project]; + let mean = (min + max) / 2; + + if ((min < 0) && (max > 0) && (Math.abs(mean) < 0.2 * Math.max(-min, max))) + mean = 0; // if middle is around 0, use 0 + + this.ctrl.projectPos = mean; + } + + toplevel.traverse(mesh => { + if (!(mesh instanceof THREE.Mesh) || !mesh.stack) + return; + + const geom2 = projectGeometry(mesh.geometry, mesh.parent.absMatrix || mesh.parent.matrixWorld, this.ctrl.project, this.ctrl.projectPos, mesh._flippedMesh); + if (geom2) { + const mesh2 = new THREE.Mesh(geom2, mesh.material.clone()); + this.#toplevel.add(mesh2); + mesh2.stack = mesh.stack; + } + }); + + return true; + } + + /** @summary Should be invoked when light configuration changed */ + changedLight(box) { + if (!this.#camera) + return; + + const need_render = !box; + + if (!box) + box = this.getGeomBoundingBox(); + + const sizex = box.max.x - box.min.x, + sizey = box.max.y - box.min.y, + sizez = box.max.z - box.min.z, + plights = [], p = (this.ctrl.light.power ?? 1) * 0.5; + + if (this.#camera._lights !== this.ctrl.light.kind) { + // remove all childs and recreate only necessary lights + disposeThreejsObject(this.#camera, true); + + this.#camera._lights = this.ctrl.light.kind; + + switch (this.#camera._lights) { + case 'ambient': + this.#camera.add(new THREE.AmbientLight(0xefefef, p)); + break; + case 'hemisphere': + this.#camera.add(new THREE.HemisphereLight(0xffffbb, 0x080820, p)); + break; + case 'mix': + this.#camera.add(new THREE.AmbientLight(0xefefef, p)); + // eslint-disable-next-line no-fallthrough + default: // 6 point lights + for (let n = 0; n < 6; ++n) { + const l = new THREE.DirectionalLight(0xefefef, p); + this.#camera.add(l); + l._id = n; + } + } + } + + for (let k = 0; k < this.#camera.children.length; ++k) { + const light = this.#camera.children[k]; + let enabled = false; + if (light.isAmbientLight || light.isHemisphereLight) { + light.intensity = p; + continue; + } + if (!light.isDirectionalLight) + continue; + switch (light._id) { + case 0: + light.position.set(sizex / 5, sizey / 5, sizez / 5); + enabled = this.ctrl.light.specular; + break; + case 1: + light.position.set(0, 0, sizez / 2); + enabled = this.ctrl.light.front; + break; + case 2: + light.position.set(0, 2 * sizey, 0); + enabled = this.ctrl.light.top; + break; + case 3: + light.position.set(0, -2 * sizey, 0); + enabled = this.ctrl.light.bottom; + break; + case 4: + light.position.set(-2 * sizex, 0, 0); + enabled = this.ctrl.light.left; + break; + case 5: + light.position.set(2 * sizex, 0, 0); + enabled = this.ctrl.light.right; + break; + } + light.power = enabled ? p * Math.PI * 4 : 0; + if (enabled) + plights.push(light); + } + + // keep light power of all sources constant + plights.forEach(ll => { ll.power = p * 4 * Math.PI / plights.length; }); + + if (need_render) + this.render3D(); + } + + /** @summary Returns true if orthographic camera is used */ + isOrthoCamera() { + return this.ctrl.camera_kind.indexOf('ortho') === 0; + } + + /** @summary Create configured camera */ + createCamera() { + if (this.#camera) { + this.#scene.remove(this.#camera); + disposeThreejsObject(this.#camera); + this.#camera = undefined; + } + + if (this.isOrthoCamera()) + this.#camera = new THREE.OrthographicCamera(-this.#scene_width / 2, this.#scene_width / 2, this.#scene_height / 2, -this.#scene_height / 2, 1, 10000); + else { + this.#camera = new THREE.PerspectiveCamera(25, this.#scene_width / this.#scene_height, 1, 10000); + this.#camera.up = this.ctrl._yup ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(0, 0, 1); + } + + // Light - add default directional light, adjust later + const light = new THREE.DirectionalLight(0xefefef, 0.1); + light.position.set(10, 10, 10); + this.#camera.add(light); + + this.#scene.add(this.#camera); + } + + /** @summary Create special effects */ + createSpecialEffects() { + if (this.#webgl && this.ctrl.outline && isFunc(this.createOutline)) { + // code used with jsroot-based geometry drawing in EVE7, not important any longer + this.#effectComposer = new THREE.EffectComposer(this.#renderer); + this.#effectComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); + this.createOutline(this.#scene, this.#camera, this.#scene_width, this.#scene_height); + } + + this.ensureBloom(); + } + + /** @summary Return current effect composer */ + getEffectComposer() { return this.#effectComposer; } + + /** @summary Initial scene creation */ + async createScene(w, h, render3d) { + if (this.#superimpose) { + const cfg = getHistPainter3DCfg(this.getMainPainter()); + + if (cfg?.renderer) { + this.#scene = cfg.scene; + this.#scene_width = cfg.scene_width; + this.#scene_height = cfg.scene_height; + this.#renderer = cfg.renderer; + this.#webgl = (this.#renderer.jsroot_render3d === constants$1.Render3D.WebGL); + + this.#toplevel = new THREE.Object3D(); + this.#scene.add(this.#toplevel); + + if (cfg.scale_x || cfg.scale_y || cfg.scale_z) + this.#toplevel.scale.set(cfg.scale_x, cfg.scale_y, cfg.scale_z); + if (cfg.offset_x || cfg.offset_y || cfg.offset_z) + this.#toplevel.position.set(cfg.offset_x, cfg.offset_y, cfg.offset_z); + this.#toplevel.updateMatrix(); + this.#toplevel.updateMatrixWorld(); + + this.#camera = cfg.camera; + } + + return this.#renderer?.jsroot_dom; + } + + return importThreeJs().then(() => { + // three.js 3D drawing + this.#scene = new THREE.Scene(); + this.#fog = new THREE.Fog(0xffffff, 1, 10000); + this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; + + this.#scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0x7000ff, vertexColors: false, transparent: true, opacity: 0.2, depthTest: false }); + + this.#scene_width = w; + this.#scene_height = h; + + this.createCamera(); + + this.#selected_mesh = null; + + this.#overall_size = 10; + + this.#toplevel = new THREE.Object3D(); + + this.#scene.add(this.#toplevel); + + this.#scene.background = new THREE.Color(this.ctrl.background); + + return createRender3D(w, h, render3d, { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true }); + }).then(r => { + this.#renderer = r; + + if (this.batch_format) + r.jsroot_image_format = this.batch_format; + + this.#webgl = (r.jsroot_render3d === constants$1.Render3D.WebGL); + + if (isFunc(r.setPixelRatio) && !isNodeJs() && !browser.android) + r.setPixelRatio(window.devicePixelRatio); + r.setSize(w, h, !this.#fit_main_area); + r.localClippingEnabled = true; + + r.setClearColor(this.#scene.background, 1); + + if (this.#fit_main_area && this.#webgl) { + r.domElement.style.width = '100%'; + r.domElement.style.height = '100%'; + const main = this.selectDom(); + if (main.style('position') === 'static') + main.style('position', 'relative'); + } + + this.#animating = false; + + this.ctrl.doubleside = false; // both sides need for clipping + this.createSpecialEffects(); + + if (this.#fit_main_area && !this.#webgl) { + // create top-most SVG for geometry drawings + const doc = getDocument(), + svg = doc.createElementNS(nsSVG, 'svg'); + svg.setAttribute('width', w); + svg.setAttribute('height', h); + svg.appendChild(this.#renderer.jsroot_dom); + return svg; + } + + return this.#renderer.jsroot_dom; + }); + } + + /** @summary Start geometry drawing */ + startDrawGeometry(force) { + if (!force && !this.isStage(stageInit)) { + this.#draw_nodes_again = true; + return; + } + + if (this.#clones_owner) + this.#clones?.setDefaultColors(this.ctrl.dflt_colors); + + this.#last_render_tm = this.#start_render_tm = new Date().getTime(); + this.#last_render_meshes = 0; + this.changeStage(stageCollect); + this.#drawing_ready = false; + this.ctrl.info.num_meshes = 0; + this.ctrl.info.num_faces = 0; + this.ctrl.info.num_shapes = 0; + this.#selected_mesh = null; + + if (this.ctrl.project) { + if (this.#clones_owner) { + if (this.#fullgeom_proj) + this.changeStage(stageBuildProj); + else + this.#fullgeom_proj = new THREE.Object3D(); + } else + this.changeStage(stageWaitMain); + } + + this.#last_manifest = undefined; + this.#last_hidden = undefined; // clear list of hidden objects + + this.#draw_nodes_again = undefined; // forget about such flag + + this.continueDraw(); + } + + /** @summary reset all kind of advanced features like depth test */ + resetAdvanced() { + this.ctrl.depthTest = true; + this.ctrl.clipIntersect = true; + this.ctrl.depthMethod = 'ray'; + + this.changedDepthMethod('norender'); + this.changedDepthTest(); + } + + /** @summary returns maximal dimension */ + getOverallSize(force) { + if (!this.#overall_size || force || this.#custom_bounding_box) { + const box = this.getGeomBoundingBox(); + + // if detect of coordinates fails - ignore + if (!Number.isFinite(box.min.x)) + return 1000; + + this.#overall_size = 2 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z); + } + + return this.#overall_size; + } + + /** @summary Create png image with drawing snapshot. */ + createSnapshot(filename) { + if (!this.#renderer) + return; + this.render3D(0); + const dataUrl = this.#renderer.domElement.toDataURL('image/png'); + if (filename === 'asis') + return dataUrl; + dataUrl.replace('image/png', 'image/octet-stream'); + const doc = getDocument(), + link = doc.createElement('a'); + if (isStr(link.download)) { + doc.body.appendChild(link); // Firefox requires the link to be in the body + link.download = filename || 'geometry.png'; + link.href = dataUrl; + link.click(); + doc.body.removeChild(link); // remove the link when done + } + } + + /** @summary Returns url parameters defining camera position. + * @desc Either absolute position are provided (arg === true) or zoom, roty, rotz parameters */ + produceCameraUrl(arg) { + if (!this.#camera) + return ''; + + if (this.#camera.isOrthographicCamera) { + const zoom = Math.round(this.#camera.zoom * 100); + return this.ctrl.camera_kind + (zoom === 100 ? '' : `,zoom=${zoom}`); + } + + let kind = ''; + if (this.ctrl.camera_kind !== 'perspective') + kind = this.ctrl.camera_kind + ','; + + if (arg === true) { + const p = this.#camera.position, t = this.#controls?.target; + if (!p || !t) + return ''; + + const conv = v => { + let s = ''; + if (v < 0) { + s = 'n'; + v = -v; + } + return s + v.toFixed(0); + }; + + let res = `${kind}camx${conv(p.x)},camy${conv(p.y)},camz${conv(p.z)}`; + if (t.x || t.y || t.z) + res += `,camlx${conv(t.x)},camly${conv(t.y)},camlz${conv(t.z)}`; + return res; + } + + if (!this.#lookat || !this.#camera0pos) + return ''; + + const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), + pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat), + zoom = Math.min(10000, Math.max(1, this.ctrl.zoom * pos2.length() / pos1.length() * 100)); + + pos1.normalize(); + pos2.normalize(); + + const quat = new THREE.Quaternion(), euler = new THREE.Euler(); + + quat.setFromUnitVectors(pos1, pos2); + euler.setFromQuaternion(quat, 'YZX'); + + let roty = euler.y / Math.PI * 180, + rotz = euler.z / Math.PI * 180; + + if (roty < 0) + roty += 360; + if (rotz < 0) + rotz += 360; + return `${kind}roty${roty.toFixed(0)},rotz${rotz.toFixed(0)},zoom${zoom.toFixed(0)}`; + } + + /** @summary Calculates current zoom factor */ + calculateZoom() { + if (this.#camera0pos && this.#camera && this.#lookat) { + const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), + pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat); + return pos2.length() / pos1.length(); + } + + return 0; + } + + /** @summary Place camera to default position, + * @param arg - true forces camera readjustment, 'first' is called when suppose to be first after complete drawing + * @param keep_zoom - tries to keep zooming factor of the camera */ + adjustCameraPosition(arg, keep_zoom) { + if (!this.#toplevel || this.#superimpose) + return; + + const force = (arg === true), + first_time = (arg === 'first') || force, + only_set = (arg === 'only_set'), + box = this.getGeomBoundingBox(); + + // let box2 = new THREE.Box3().makeEmpty(); + // box2.expandByObject(this.#toplevel, true); + // console.log('min,max', box.min.x, box.max.x, box.min.y, box.max.y, box.min.z, box.max.z ); + + // if detect of coordinates fails - ignore + if (!Number.isFinite(box.min.x)) { + console.log('FAILS to get geometry bounding box'); + return; + } + + const sizex = box.max.x - box.min.x, + sizey = box.max.y - box.min.y, + sizez = box.max.z - box.min.z, + midx = (box.max.x + box.min.x) / 2, + midy = (box.max.y + box.min.y) / 2, + midz = (box.max.z + box.min.z) / 2, + more = this.ctrl._axis || (this.ctrl.camera_overlay === 'bar') ? 0.2 : 0.1; + + if (this.#scene_size && !force) { + const test = (v1, v2, scale) => { + if (!scale) + scale = Math.abs((v1 + v2) / 2); + return scale <= 1e-20 ? true : Math.abs(v2 - v1) / scale > 0.01; + }, d = this.#scene_size; + if (!test(sizex, d.sizex) && !test(sizey, d.sizey) && !test(sizez, d.sizez) && + !test(midx, d.midx, d.sizex) && !test(midy, d.midy, d.sizey) && !test(midz, d.midz, d.sizez)) { + if (this.ctrl.select_in_view) + this.startDrawGeometry(); + return; + } + } + + this.#scene_size = { sizex, sizey, sizez, midx, midy, midz }; + + this.#overall_size = 2 * Math.max(sizex, sizey, sizez); + + this.#camera.near = this.#overall_size / 350; + this.#camera.far = this.#overall_size * 100; + this.#fog.near = this.#overall_size * 0.5; + this.#fog.far = this.#overall_size * 5; + + if (first_time) { + for (let naxis = 0; naxis < 3; ++naxis) { + const cc = this.ctrl.clip[naxis]; + cc.min = box.min[cc.name]; + cc.max = box.max[cc.name]; + const sz = cc.max - cc.min; + cc.max += sz * 0.01; + cc.min -= sz * 0.01; + if (sz > 100) + cc.step = 0.1; + else if (sz > 1) + cc.step = 0.001; + else + cc.step = undefined; + + if (!cc.value) + cc.value = (cc.min + cc.max) / 2; + else if (cc.value < cc.min) + cc.value = cc.min; + else if (cc.value > cc.max) + cc.value = cc.max; + } + } + + let k = 2 * this.ctrl.zoom; + const max_all = Math.max(sizex, sizey, sizez), + sign = this.ctrl.camera_kind.indexOf('N') > 0 ? -1 : 1; + + this.#lookat = new THREE.Vector3(midx, midy, midz); + this.#camera0pos = new THREE.Vector3(-2 * max_all, 0, 0); // virtual 0 position, where rotation starts + + this.#camera.updateMatrixWorld(); + this.#camera.updateProjectionMatrix(); + + if ((this.ctrl.rotatey || this.ctrl.rotatez) && this.ctrl.can_rotate) { + const prev_zoom = this.calculateZoom(); + if (keep_zoom && prev_zoom) + k = 2 * prev_zoom; + + const euler = new THREE.Euler(0, this.ctrl.rotatey / 180 * Math.PI, this.ctrl.rotatez / 180 * Math.PI, 'YZX'); + + this.#camera.position.set(-k * max_all, 0, 0); + this.#camera.position.applyEuler(euler); + this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); + + if (keep_zoom && prev_zoom) { + const actual_zoom = this.calculateZoom(); + k *= prev_zoom / actual_zoom; + + this.#camera.position.set(-k * max_all, 0, 0); + this.#camera.position.applyEuler(euler); + this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); + } + } else if (this.ctrl.camx !== undefined && this.ctrl.camy !== undefined && this.ctrl.camz !== undefined) { + this.#camera.position.set(this.ctrl.camx, this.ctrl.camy, this.ctrl.camz); + this.#lookat.set(this.ctrl.camlx || 0, this.ctrl.camly || 0, this.ctrl.camlz || 0); + this.ctrl.camx = this.ctrl.camy = this.ctrl.camz = this.ctrl.camlx = this.ctrl.camly = this.ctrl.camlz = undefined; + } else if ((this.ctrl.camera_kind === 'orthoXOY') || (this.ctrl.camera_kind === 'orthoXNOY')) { + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(sign < 0 ? midx * 2 : 0, 0, midz + sign * sizez * 2); + this.#lookat.set(sign < 0 ? midx * 2 : 0, 0, midz); + this.#camera.left = box.min.x - more * sizex; + this.#camera.right = box.max.x + more * sizex; + this.#camera.top = box.max.y + more * sizey; + this.#camera.bottom = box.min.y - more * sizey; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midz, sizez / 2]; + } else if ((this.ctrl.camera_kind === 'orthoXOZ') || (this.ctrl.camera_kind === 'orthoXNOZ')) { + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(sign < 0 ? midx * 2 : 0, midy - sign * sizey * 2, 0); + this.#lookat.set(sign < 0 ? midx * 2 : 0, midy, 0); + this.#camera.left = box.min.x - more * sizex; + this.#camera.right = box.max.x + more * sizex; + this.#camera.top = box.max.z + more * sizez; + this.#camera.bottom = box.min.z - more * sizez; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [0, 2, 1]; + this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midy, -sizey / 2]; + } else if ((this.ctrl.camera_kind === 'orthoZOY') || (this.ctrl.camera_kind === 'orthoZNOY')) { + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - sign * sizex * 2, 0, sign < 0 ? midz * 2 : 0); + this.#lookat.set(midx, 0, sign < 0 ? midz * 2 : 0); + this.#camera.left = box.min.z - more * sizez; + this.#camera.right = box.max.z + more * sizez; + this.#camera.top = box.max.y + more * sizey; + this.#camera.bottom = box.min.y - more * sizey; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [2, 1, 0]; + this.#camera.orthoRotation = geom => geom.rotateY(-Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midx, -sizex / 2]; + } else if ((this.ctrl.camera_kind === 'orthoZOX') || (this.ctrl.camera_kind === 'orthoZNOX')) { + this.#camera.up.set(1, 0, 0); + this.#camera.position.set(0, midy - sign * sizey * 2, sign > 0 ? midz * 2 : 0); + this.#lookat.set(0, midy, sign > 0 ? midz * 2 : 0); + this.#camera.left = box.min.z - more * sizez; + this.#camera.right = box.max.z + more * sizez; + this.#camera.top = box.max.x + more * sizex; + this.#camera.bottom = box.min.x - more * sizex; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [2, 0, 1]; + this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2).rotateY(Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midy, -sizey / 2]; + } else if (this.ctrl.project) { + switch (this.ctrl.project) { + case 'x': this.#camera.position.set(k * 1.5 * Math.max(sizey, sizez), 0, 0); break; + case 'y': this.#camera.position.set(0, k * 1.5 * Math.max(sizex, sizez), 0); break; + case 'z': this.#camera.position.set(0, 0, k * 1.5 * Math.max(sizex, sizey)); break; + } + } else if (this.ctrl.camera_kind === 'perspXOZ') { + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - 3 * max_all, midy, midz); + } else if (this.ctrl.camera_kind === 'perspYOZ') { + this.#camera.up.set(1, 0, 0); + this.#camera.position.set(midx, midy - 3 * max_all, midz); + } else if (this.ctrl.camera_kind === 'perspXOY') { + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(midx - 3 * max_all, midy, midz); + } else if (this.ctrl._yup) { + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - k * Math.max(sizex, sizez), midy + k * sizey, midz - k * Math.max(sizex, sizez)); + } else { + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(midx - k * Math.max(sizex, sizey), midy - k * Math.max(sizex, sizey), midz + k * sizez); + } + + if (this.#camera.isOrthographicCamera && this.isOrthoCamera() && this.#scene_width && this.#scene_height) { + const screen_ratio = this.#scene_width / this.#scene_height, + szx = this.#camera.right - this.#camera.left, szy = this.#camera.top - this.#camera.bottom; + + if (screen_ratio > szx / szy) { + // screen wider than actual geometry + const m = (this.#camera.right + this.#camera.left) / 2; + this.#camera.left = m - szy * screen_ratio / 2; + this.#camera.right = m + szy * screen_ratio / 2; + } else { + // screen higher than actual geometry + const m = (this.#camera.top + this.#camera.bottom) / 2; + this.#camera.top = m + szx / screen_ratio / 2; + this.#camera.bottom = m - szx / screen_ratio / 2; + } + } + + this.#camera.lookAt(this.#lookat); + this.#camera.updateProjectionMatrix(); + + this.changedLight(box); + + if (this.#controls) { + this.#controls.target.copy(this.#lookat); + if (!only_set) + this.#controls.update(); + } + + // recheck which elements to draw + if (this.ctrl.select_in_view && !only_set) + this.startDrawGeometry(); + } + + /** @summary Specifies camera position as rotation around geometry center */ + setCameraPosition(rotatey, rotatez, zoom) { + if (!this.ctrl) + return; + this.ctrl.rotatey = rotatey || 0; + this.ctrl.rotatez = rotatez || 0; + let preserve_zoom = false; + if (zoom && Number.isFinite(zoom)) + this.ctrl.zoom = zoom; + else + preserve_zoom = true; + + this.adjustCameraPosition(false, preserve_zoom); + } + + /** @summary Specifies camera position and point to which it looks to + @desc Both specified in absolute coordinates */ + setCameraPositionAndLook(camx, camy, camz, lookx, looky, lookz) { + if (!this.ctrl) + return; + this.ctrl.camx = camx; + this.ctrl.camy = camy; + this.ctrl.camz = camz; + this.ctrl.camlx = lookx; + this.ctrl.camly = looky; + this.ctrl.camlz = lookz; + this.adjustCameraPosition(false); + } + + /** @summary focus on item */ + focusOnItem(itemname) { + if (!itemname || !this.#clones) + return; + + const stack = this.#clones.findStackByName(itemname); + + if (stack) + this.focusCamera(this.#clones.resolveStack(stack, true), false); + } + + /** @summary focus camera on specified position */ + focusCamera(focus, autoClip) { + if (this.ctrl.project || this.isOrthoCamera()) { + this.adjustCameraPosition(true); + return this.render3D(); + } + + let box = new THREE.Box3(); + if (focus === undefined) + box = this.getGeomBoundingBox(); + else if (focus instanceof THREE.Mesh) + box.setFromObject(focus); + else { + const center = new THREE.Vector3().setFromMatrixPosition(focus.matrix), + node = focus.node, + halfDelta = new THREE.Vector3(node.fDX, node.fDY, node.fDZ).multiplyScalar(0.5); + box.min = center.clone().sub(halfDelta); + box.max = center.clone().add(halfDelta); + } + + const sizex = box.max.x - box.min.x, + sizey = box.max.y - box.min.y, + sizez = box.max.z - box.min.z, + midx = (box.max.x + box.min.x) / 2, + midy = (box.max.y + box.min.y) / 2, + midz = (box.max.z + box.min.z) / 2; + + let position, frames = 50, step = 0; + if (this.ctrl._yup) + position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizez), midy + 2 * sizey, midz - 2 * Math.max(sizex, sizez)); + else + position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizey), midy - 2 * Math.max(sizex, sizey), midz + 2 * sizez); + + const target = new THREE.Vector3(midx, midy, midz), + oldTarget = this.#controls.target, + // Amount to change camera position at each step + posIncrement = position.sub(this.#camera.position).divideScalar(frames), + // Amount to change 'lookAt' so it will end pointed at target + targetIncrement = target.sub(oldTarget).divideScalar(frames); + + autoClip = autoClip && this.#webgl; + + // Automatic Clipping + if (autoClip) { + for (let axis = 0; axis < 3; ++axis) { + const cc = this.ctrl.clip[axis]; + if (!cc.enabled) { + cc.value = cc.min; + cc.enabled = true; + } + cc.inc = ((cc.min + cc.max) / 2 - cc.value) / frames; + } + this.updateClipping(); + } + + this.#animating = true; + + const animate = () => { + if (this.#animating === undefined) + return; + + if (this.#animating) + requestAnimationFrame(animate); + else if (!this.#geom_viewer) + this.startDrawGeometry(); + const smoothFactor = -Math.cos((2.0 * Math.PI * step) / frames) + 1.0; + this.#camera.position.add(posIncrement.clone().multiplyScalar(smoothFactor)); + oldTarget.add(targetIncrement.clone().multiplyScalar(smoothFactor)); + this.#lookat = oldTarget; + this.#camera.lookAt(this.#lookat); + this.#camera.updateProjectionMatrix(); + + const tm1 = new Date().getTime(); + if (autoClip) { + for (let axis = 0; axis < 3; ++axis) + this.ctrl.clip[axis].value += this.ctrl.clip[axis].inc * smoothFactor; + this.updateClipping(); + } else + this.render3D(0); + + const tm2 = new Date().getTime(); + if ((step === 0) && (tm2 - tm1 > 200)) + frames = 20; + step++; + this.#animating = step < frames; + }; + + animate(); + + // this.#controls.update(); + } + + /** @summary activate auto rotate */ + autorotate(speed) { + const rotSpeed = (speed === undefined) ? 2.0 : speed; + let last = new Date(); + + const animate = () => { + if (!this.#renderer || !this.ctrl) + return; + + const current = new Date(); + + if (this.ctrl.rotate) + requestAnimationFrame(animate); + + if (this.#controls) { + this.#controls.autoRotate = this.ctrl.rotate; + this.#controls.autoRotateSpeed = rotSpeed * (current.getTime() - last.getTime()) / 16.6666; + this.#controls.update(); + } + last = new Date(); + this.render3D(0); + }; + + if (this.#webgl) + animate(); + } + + /** @summary called at the end of scene drawing */ + completeScene() {} + + /** @summary Drawing with 'count' option + * @desc Scans hierarchy and check for unique nodes + * @return {Promise} with object drawing ready */ + async drawCount(unqievis, clonetm) { + const makeTime = tm => (this.isBatchMode() ? 'anytime' : tm.toString()) + ' ms', + + res = ['Unique nodes: ' + this.#clones.nodes.length, + 'Unique visible: ' + unqievis, + 'Time to clone: ' + makeTime(clonetm)]; + + // need to fill cached value line numvischld + this.#clones.scanVisible(); + + let nshapes = 0; + const arg = { + clones: this.#clones, + cnt: [], + func(node) { + if (this.cnt[this.last] === undefined) + this.cnt[this.last] = 1; + else + this.cnt[this.last]++; + + nshapes += countNumShapes(this.clones.getNodeShape(node.id)); + return true; + } + }; + + let tm1 = new Date().getTime(), + numvis = this.#clones.scanVisible(arg), + tm2 = new Date().getTime(); + + res.push(`Total visible nodes: ${numvis}`, `Total shapes: ${nshapes}`); + + for (let lvl = 0; lvl < arg.cnt.length; ++lvl) { + if (arg.cnt[lvl] !== undefined) + res.push(` lvl${lvl}: ${arg.cnt[lvl]}`); + } + + res.push(`Time to scan: ${makeTime(tm2 - tm1)}`, '', 'Check timing for matrix calculations ...'); + + const elem = this.selectDom().style('overflow', 'auto'); + + if (this.isBatchMode()) + elem.property('_json_object_', res); + else + res.forEach(str => elem.append('p').text(str)); + + return postponePromise(() => { + arg.domatrix = true; + tm1 = new Date().getTime(); + numvis = this.#clones.scanVisible(arg); + tm2 = new Date().getTime(); + + const last_str = `Time to scan with matrix: ${makeTime(tm2 - tm1)}`; + if (this.isBatchMode()) + res.push(last_str); + else + elem.append('p').text(last_str); + return this; + }, 100); + } + + /** @summary Handle drop operation + * @desc opt parameter can include function name like opt$func_name + * Such function should be possible to find via {@link findFunction} + * Function has to return Promise with objects to draw on geometry + * By default function with name 'extract_geo_tracks' is checked + * @return {Promise} handling of drop operation */ + async performDrop(obj, itemname, hitem, opt) { + if (obj?.$kind === clTTree) { + // drop tree means function call which must extract tracks from provided tree + + let funcname = 'extract_geo_tracks'; + + if (opt && opt.indexOf('$') > 0) { + funcname = opt.slice(0, opt.indexOf('$')); + opt = opt.slice(opt.indexOf('$') + 1); + } + + const func = findFunction(funcname); + + if (!func) + return Promise.reject(Error(`Function ${funcname} not found`)); + + return func(obj, opt).then(tracks => { + if (!tracks) + return this; + + // FIXME: probably tracks should be remembered? + return this.drawExtras(tracks, '', false).then(() => { + this.updateClipping(true); + return this.render3D(100); + }); + }); + } + + return this.drawExtras(obj, itemname).then(is_any => { + if (!is_any) + return this; + + if (hitem) + hitem._painter = this; // set for the browser item back pointer + + return this.render3D(100); + }); + } + + /** @summary function called when mouse is going over the item in the browser */ + mouseOverHierarchy(on, itemname, hitem) { + if (!this.ctrl) + return; // protection for cleaned-up painter + + const obj = hitem._obj; + + // let's highlight tracks and hits only for the time being + if (!obj || (obj._typename !== clTEveTrack && obj._typename !== clTEvePointSet && obj._typename !== clTPolyMarker3D)) + return; + + this.highlightMesh(null, 0x00ff00, on ? obj : null); + } + + /** @summary clear extra drawn objects like tracks or hits */ + clearExtras() { + this.getExtrasContainer('delete'); + this.#extra_objects = undefined; // workaround, later will be normal function + this.render3D(); + } + + /** @summary Register extra objects like tracks or hits + * @desc Rendered after main geometry volumes are created + * Check if object already exists to prevent duplication */ + addExtra(obj, itemname) { + if (!this.#extra_objects) + this.#extra_objects = create$1(clTList); + + if (this.#extra_objects.arr.indexOf(obj) >= 0) + return false; + + this.#extra_objects.Add(obj, itemname); + + delete obj.$hidden_via_menu; // remove previous hidden property + + return true; + } + + /** @summary manipulate visibility of extra objects, used for HierarchyPainter + * @private */ + extraObjectVisible(hpainter, hitem, toggle) { + if (!this.#extra_objects) + return; + + const itemname = hpainter.itemFullName(hitem); + let indx = this.#extra_objects.opt.indexOf(itemname); + + if ((indx < 0) && hitem._obj) { + indx = this.#extra_objects.arr.indexOf(hitem._obj); + // workaround - if object found, replace its name + if (indx >= 0) + this.#extra_objects.opt[indx] = itemname; + } + + if (indx < 0) + return; + + const obj = this.#extra_objects.arr[indx]; + let res = Boolean(obj.$hidden_via_menu); + + if (toggle) { + obj.$hidden_via_menu = res; + res = !res; + + let mesh = null; + // either found painted object or just draw once again + this.#toplevel.traverse(node => { + if (node.geo_object === obj) + mesh = node; + }); + + if (mesh) { + mesh.visible = res; + this.render3D(); + } else if (res) { + this.drawExtras(obj, '', false).then(() => { + this.updateClipping(true); + this.render3D(); + }); + } + } + + return res; + } + + /** @summary Draw extra object like tracks + * @return {Promise} for ready */ + async drawExtras(obj, itemname, add_objects, not_wait_render) { + // if object was hidden via menu, do not redraw it with next draw call + if (!obj?._typename || (!add_objects && obj.$hidden_via_menu)) + return false; + + let do_render = false; + if (add_objects === undefined) { + add_objects = true; + do_render = true; + } else if (not_wait_render) + do_render = true; + + + let promise = false; + + if ((obj._typename === clTList) || (obj._typename === clTObjArray)) { + if (!obj.arr) + return false; + const parr = []; + for (let n = 0; n < obj.arr.length; ++n) { + const sobj = obj.arr[n]; + let sname = obj.opt ? obj.opt[n] : ''; + if (!sname) + sname = (itemname || '') + `/[${n}]`; + parr.push(this.drawExtras(sobj, sname, add_objects)); + } + promise = Promise.all(parr).then(ress => ress.indexOf(true) >= 0); + } else if (obj._typename === 'Mesh') { + // adding mesh as is + this.addToExtrasContainer(obj); + promise = Promise.resolve(true); + } else if (obj._typename === 'TGeoTrack') { + if (!add_objects || this.addExtra(obj, itemname)) + promise = this.drawGeoTrack(obj, itemname); + } else if (obj._typename === clTPolyLine3D) { + if (!add_objects || this.addExtra(obj, itemname)) + promise = this.drawPolyLine(obj, itemname); + } else if ((obj._typename === clTEveTrack) || (obj._typename === `${nsREX}REveTrack`)) { + if (!add_objects || this.addExtra(obj, itemname)) + promise = this.drawEveTrack(obj, itemname); + } else if ((obj._typename === clTEvePointSet) || (obj._typename === `${nsREX}REvePointSet`) || (obj._typename === clTPolyMarker3D)) { + if (!add_objects || this.addExtra(obj, itemname)) + promise = this.drawHit(obj, itemname); + } else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { + if (!add_objects || this.addExtra(obj, itemname)) + promise = this.drawExtraShape(obj, itemname); + } + + return getPromise(promise).then(is_any => { + if (!is_any || !do_render) + return is_any; + + this.updateClipping(true); + + const pr = this.render3D(100, not_wait_render ? 'nopromise' : false); + + return not_wait_render ? this : pr; + }); + } + + /** @summary returns container for extra objects */ + getExtrasContainer(action, name) { + if (!this.#toplevel) + return null; + + if (!name) + name = 'tracks'; + + let extras = null; + const lst = []; + for (let n = 0; n < this.#toplevel.children.length; ++n) { + const chld = this.#toplevel.children[n]; + if (!chld._extras) + continue; + if (action === 'collect') { + lst.push(chld); + continue; + } + if (chld._extras === name) { + extras = chld; + break; + } + } + + if (action === 'collect') { + for (let k = 0; k < lst.length; ++k) + this.#toplevel.remove(lst[k]); + return lst; + } + + if (action === 'delete') { + if (extras) + this.#toplevel.remove(extras); + disposeThreejsObject(extras); + return null; + } + + if ((action !== 'get') && !extras) { + extras = new THREE.Object3D(); + extras._extras = name; + this.#toplevel.add(extras); + } + + return extras; + } + + /** @summary add object to extras container. + * @desc If fail, dispose object */ + addToExtrasContainer(obj, name) { + const container = this.getExtrasContainer('', name); + if (container) + container.add(obj); + else { + console.warn('Fail to add object to extras'); + disposeThreejsObject(obj); + } + } + + /** @summary drawing TGeoTrack */ + drawGeoTrack(track, itemname) { + if (!track?.fNpoints) + return false; + + const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), // line width not supported on windows + color = getColor(track.fLineColor) || '#ff00ff', + npoints = Math.round(track.fNpoints / 4), // each track point has [x,y,z,t] coordinate + buf = new Float32Array((npoints - 1) * 6), + projv = this.ctrl.projectPos, + projx = (this.ctrl.project === 'x'), + projy = (this.ctrl.project === 'y'), + projz = (this.ctrl.project === 'z'); + + for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : track.fPoints[k * 4]; + buf[pos + 1] = projy ? projv : track.fPoints[k * 4 + 1]; + buf[pos + 2] = projz ? projv : track.fPoints[k * 4 + 2]; + buf[pos + 3] = projx ? projv : track.fPoints[k * 4 + 4]; + buf[pos + 4] = projy ? projv : track.fPoints[k * 4 + 5]; + buf[pos + 5] = projz ? projv : track.fPoints[k * 4 + 6]; + } + + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), + line = createLineSegments(buf, lineMaterial); + + line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front + line.geo_name = itemname; + line.geo_object = track; + line.hightlightWidthScale = 2; + + if (itemname?.indexOf('/Tracks') === 0) + line.main_track = true; + + this.addToExtrasContainer(line); + + return true; + } + + /** @summary drawing TPolyLine3D */ + drawPolyLine(line, itemname) { + if (!line) + return false; + + const linewidth = browser.isWin ? 1 : (line.fLineWidth || 1), + color = getColor(line.fLineColor) || '#ff00ff', + npoints = line.fN, + fP = line.fP, + buf = new Float32Array((npoints - 1) * 6), + projv = this.ctrl.projectPos, + projx = (this.ctrl.project === 'x'), + projy = (this.ctrl.project === 'y'), + projz = (this.ctrl.project === 'z'); + + for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : fP[k * 3]; + buf[pos + 1] = projy ? projv : fP[k * 3 + 1]; + buf[pos + 2] = projz ? projv : fP[k * 3 + 2]; + buf[pos + 3] = projx ? projv : fP[k * 3 + 3]; + buf[pos + 4] = projy ? projv : fP[k * 3 + 4]; + buf[pos + 5] = projz ? projv : fP[k * 3 + 5]; + } + + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), + line3d = createLineSegments(buf, lineMaterial); + + line3d.defaultOrder = line3d.renderOrder = 1000000; // to bring line to the front + line3d.geo_name = itemname; + line3d.geo_object = line; + line3d.hightlightWidthScale = 2; + + this.addToExtrasContainer(line3d); + + return true; + } + + /** @summary Drawing TEveTrack */ + drawEveTrack(track, itemname) { + if (!track || (track.fN <= 0)) + return false; + + const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), + color = getColor(track.fLineColor) || '#ff00ff', + buf = new Float32Array((track.fN - 1) * 6), + projv = this.ctrl.projectPos, + projx = (this.ctrl.project === 'x'), + projy = (this.ctrl.project === 'y'), + projz = (this.ctrl.project === 'z'); + + for (let k = 0, pos = 0; k < track.fN - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : track.fP[k * 3]; + buf[pos + 1] = projy ? projv : track.fP[k * 3 + 1]; + buf[pos + 2] = projz ? projv : track.fP[k * 3 + 2]; + buf[pos + 3] = projx ? projv : track.fP[k * 3 + 3]; + buf[pos + 4] = projy ? projv : track.fP[k * 3 + 4]; + buf[pos + 5] = projz ? projv : track.fP[k * 3 + 5]; + } + + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), + line = createLineSegments(buf, lineMaterial); + + line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front + line.geo_name = itemname; + line.geo_object = track; + line.hightlightWidthScale = 2; + + this.addToExtrasContainer(line); + + return true; + } + + /** @summary Drawing different hits types like TPolyMarker3D */ + async drawHit(hit, itemname) { + if (!hit || !hit.fN || (hit.fN < 0)) + return false; + + // make hit size scaling factor of overall geometry size + // otherwise it is not possible to correctly see hits at all + const nhits = hit.fN, + projv = this.ctrl.projectPos, + projx = (this.ctrl.project === 'x'), + projy = (this.ctrl.project === 'y'), + projz = (this.ctrl.project === 'z'), + hit_scale = Math.max(hit.fMarkerSize * this.getOverallSize() * (this.getOptions().dummy ? 0.015 : 0.005), 0.2), + pnts = new PointsCreator(nhits, this.#webgl, hit_scale); + + for (let i = 0; i < nhits; i++) { + pnts.addPoint(projx ? projv : hit.fP[i * 3], + projy ? projv : hit.fP[i * 3 + 1], + projz ? projv : hit.fP[i * 3 + 2]); + } + + return pnts.createPoints({ color: getColor(hit.fMarkerColor) || '#0000ff', style: hit.fMarkerStyle }).then(mesh => { + mesh.defaultOrder = mesh.renderOrder = 1000000; // to bring points to the front + mesh.highlightScale = 2; + mesh.geo_name = itemname; + mesh.geo_object = hit; + this.addToExtrasContainer(mesh); + return true; // indicate that rendering should be done + }); + } + + /** @summary Draw extra shape on the geometry */ + drawExtraShape(obj, itemname) { + // eslint-disable-next-line no-use-before-define + const mesh = build(obj); + if (!mesh) + return false; + + mesh.geo_name = itemname; + mesh.geo_object = obj; + + this.addToExtrasContainer(mesh); + return true; + } + + /** @summary Search for specified node + * @private */ + findNodeWithVolume(name, action, prnt, itemname, volumes) { + let first_level = false, res = null; + + if (!prnt) { + prnt = this.getGeometry(); + if (!prnt && (getNodeKind(prnt) !== kindGeo)) + return null; + itemname = this.#geo_manager ? prnt.fName : ''; + first_level = true; + volumes = []; + } else { + if (itemname) + itemname += '/'; + itemname += prnt.fName; + } + + if (!prnt.fVolume || prnt.fVolume._searched) + return null; + + if (name.test(prnt.fVolume.fName)) { + res = action({ node: prnt, item: itemname }); + if (res) + return res; + } + + prnt.fVolume._searched = true; + volumes.push(prnt.fVolume); + + if (prnt.fVolume.fNodes) { + for (let n = 0, len = prnt.fVolume.fNodes.arr.length; n < len; ++n) { + res = this.findNodeWithVolume(name, action, prnt.fVolume.fNodes.arr[n], itemname, volumes); + if (res) + break; + } + } + + if (first_level) { + for (let n = 0, len = volumes.length; n < len; ++n) + delete volumes[n]._searched; + } + + return res; + } + + /** @summary Search for created shape for nodeid + * @desc Used in ROOT geometry painter */ + findNodeShape(nodeid) { + if ((nodeid !== undefined) && !this.#draw_nodes) { + for (let k = 0; k < this.#draw_nodes.length; ++k) { + const item = this.geo_painter._draw_nodes[k]; + if ((item.nodeid === nodeid) && item.server_shape) + return item.server_shape; + } + } + } + + /** @summary Process script option - load and execute some gGeoManager-related calls */ + async loadMacro(script_name) { + const result = { obj: this.getGeometry(), prefix: '' }; + + if (this.#geo_manager) + result.prefix = result.obj.fName; + + if (!script_name || (script_name.length < 3) || (getNodeKind(result.obj) !== kindGeo)) + return result; + + const mgr = { + GetVolume: name => { + const regexp = new RegExp('^' + name + '$'), + currnode = this.findNodeWithVolume(regexp, arg => arg); + + if (!currnode) + console.log(`Did not found ${name} volume`); + + // return proxy object with several methods, typically used in ROOT geom scripts + return { + found: currnode, + fVolume: currnode?.node?.fVolume, + InvisibleAll(flag) { + setInvisibleAll(this.fVolume, flag); + }, + Draw() { + if (!this.found || !this.fVolume) + return; + result.obj = this.found.node; + result.prefix = this.found.item; + console.log(`Select volume for drawing ${this.fVolume.fName} ${result.prefix}`); + }, + SetTransparency(lvl) { + if (this.fVolume?.fMedium?.fMaterial) + this.fVolume.fMedium.fMaterial.fFillStyle = 3000 + lvl; + }, + SetLineColor(col) { + if (this.fVolume) + this.fVolume.fLineColor = col; + } + }; + }, + + DefaultColors: () => { + this.ctrl.dflt_colors = true; + }, + + SetMaxVisNodes: limit => { + if (!this.ctrl.maxnodes) + this.ctrl.maxnodes = parseInt(limit) || 0; + }, + + SetVisLevel: limit => { + if (!this.ctrl.vislevel) + this.ctrl.vislevel = parseInt(limit) || 0; + } + }; + + showProgress(`Loading macro ${script_name}`); + + return httpRequest(script_name, 'text').then(script => { + const lines = script.split('\n'); + let indx = 0; + + while (indx < lines.length) { + let line = lines[indx++].trim(); + + if (line.indexOf('//') === 0) + continue; + + if (line.indexOf('gGeoManager') < 0) + continue; + line = line.replace('->GetVolume', '.GetVolume'); + line = line.replace('->InvisibleAll', '.InvisibleAll'); + line = line.replace('->SetMaxVisNodes', '.SetMaxVisNodes'); + line = line.replace('->DefaultColors', '.DefaultColors'); + line = line.replace('->Draw', '.Draw'); + line = line.replace('->SetTransparency', '.SetTransparency'); + line = line.replace('->SetLineColor', '.SetLineColor'); + line = line.replace('->SetVisLevel', '.SetVisLevel'); + if (line.indexOf('->') >= 0) + continue; + + try { + const func = new Function('gGeoManager', line); + func(mgr); + } catch { + console.error(`Problem by processing ${line}`); + } + } + + return result; + }).catch(() => { + console.error(`Fail to load ${script_name}`); + return result; + }); + } + + /** @summary Extract shapes from draw message of geometry painter + * @desc For the moment used in batch production */ + extractRawShapes(draw_msg, recreate) { + let nodes = null, old_gradpersegm = 0; + + // array for descriptors for each node + // if array too large (>1M), use JS object while only ~1K nodes are expected to be used + if (recreate) + nodes = (draw_msg.numnodes > 1e6) ? { length: draw_msg.numnodes } : new Array(draw_msg.numnodes); // array for all nodes + + draw_msg.nodes.forEach(node => { + node = ClonedNodes.formatServerElement(node); + if (nodes) + nodes[node.id] = node; + else + this.#clones.updateNode(node); + }); + + if (recreate) { + this.assignClones(new ClonedNodes(null, nodes), true); + this.#clones.name_prefix = this.#clones.getNodeName(0); + this.#clones.setConfig(this.ctrl); + + // normally only need when making selection, not used in geo viewer + // this.geo#clones.setMaxVisNodes(draw_msg.maxvisnodes); + // this.geo#clones.setVisLevel(draw_msg.vislevel); + // TODO: provide from server + this.#clones.maxdepth = 20; + } + + let nsegm = 0; + if (draw_msg.cfg) + nsegm = draw_msg.cfg.nsegm; + + if (nsegm) { + old_gradpersegm = geoCfg('GradPerSegm'); + geoCfg('GradPerSegm', 360 / Math.max(nsegm, 6)); + } + + for (let cnt = 0; cnt < draw_msg.visibles.length; ++cnt) { + const item = draw_msg.visibles[cnt], rd = item.ri; + + // entry may be provided without shape - it is ok + if (rd) + item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); + } + + if (old_gradpersegm) + geoCfg('GradPerSegm', old_gradpersegm); + + return true; + } + + /** @summary Prepare drawings + * @desc Return value used as promise for painter */ + async prepareObjectDraw(draw_obj, name_prefix) { + // if did cleanup - ignore all kind of activity + if (this.#did_cleanup) + return null; + + if (name_prefix === '__geom_viewer_append__') { + this.#new_append_nodes = draw_obj; + this.ctrl.use_worker = 0; + this.setGeomViewer(true); // indicate that working with geom viewer + } else if ((name_prefix === '__geom_viewer_selection__') && this.#clones) { + // these are selection done from geom viewer + this.#new_draw_nodes = draw_obj; + this.ctrl.use_worker = 0; + this.setGeomViewer(true); // indicate that working with geom viewer + } else if (this.getCentral()) + this.assignClones(this.getCentral().getClones(), false); + else if (!draw_obj) + this.assignClones(undefined, undefined); + else { + this.#start_drawing_time = new Date().getTime(); + this.assignClones(new ClonedNodes(draw_obj), true); + let lvl = this.ctrl.vislevel, maxnodes = this.ctrl.maxnodes; + if (this.#geo_manager) { + if (!lvl && this.#geo_manager.fVisLevel) + lvl = this.#geo_manager.fVisLevel; + if (!maxnodes) + maxnodes = this.#geo_manager.fMaxVisNodes; + } + + this.#clones.setVisLevel(lvl); + this.#clones.setMaxVisNodes(maxnodes, this.ctrl.more); + this.#clones.setConfig(this.ctrl); + + this.#clones.name_prefix = name_prefix; + + const hide_top_volume = Boolean(this.#geo_manager) && !this.ctrl.showtop; + let uniquevis = this.ctrl.no_screen ? 0 : this.#clones.markVisibles(true, false, hide_top_volume); + + if (uniquevis <= 0) + uniquevis = this.#clones.markVisibles(false, false, hide_top_volume); + else + uniquevis = this.#clones.markVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits + + this.#clones.produceIdShifts(); + + const spent = new Date().getTime() - this.#start_drawing_time; + + if (!this.#scene && (settings.Debug || spent > 1000)) + console.log(`Creating clones ${this.#clones.nodes.length} takes ${spent} ms uniquevis ${uniquevis}`); + + if (this.ctrl._count) + return this.drawCount(uniquevis, spent); + } + + let promise = Promise.resolve(true); + + if (!this.#scene) { + this.#first_drawing = true; + + const pp = this.getPadPainter(); + + this.#on_pad = Boolean(pp); + + if (this.#on_pad) { + let size, render3d, fp; + promise = ensureTCanvas(this, '3d').then(() => { + if (pp.fillatt?.color) + this.ctrl.background = pp.fillatt.color; + fp = this.getFramePainter(); + + this.batch_mode = pp.isBatchMode(); + + render3d = getRender3DKind(undefined, this.batch_mode); + assign3DHandler(fp); + fp.mode3d = true; + + size = fp.getSizeFor3d(undefined, render3d); + + this.#fit_main_area = (size.can3d === -1); + + return this.createScene(size.width, size.height, render3d) + .then(dom => fp.add3dCanvas(size, dom, render3d === constants$1.Render3D.WebGL)); + }); + } else { + const dom = this.selectDom('origin'); + + this.batch_mode = isBatchMode() || (!dom.empty() && dom.property('_batch_mode')); + this.batch_format = dom.property('_batch_format'); + + const render3d = getRender3DKind(this.getOptions().Render3D, this.batch_mode); + + // activate worker + if ((this.ctrl.use_worker > 0) && !this.batch_mode) + this.startWorker(); + + assign3DHandler(this); + + const size = this.getSizeFor3d(undefined, render3d); + + this.#fit_main_area = (size.can3d === -1); + + promise = this.createScene(size.width, size.height, render3d) + .then(dom2 => this.add3dCanvas(size, dom2, this.#webgl)); + } + } + + return promise.then(() => { + // this is limit for the visible faces, number of volumes does not matter + if (this.#first_drawing && !this.ctrl.maxfaces) + this.ctrl.maxfaces = 200000 * this.ctrl.more; + + // set top painter only when first child exists + this.setAsMainPainter(); + + this.createToolbar(); + + // just draw extras and complete drawing if there are no main model + if (!this.#clones) + return this.completeDraw(); + + return new Promise(resolveFunc => { + this.#draw_resolveFuncs.push(resolveFunc); + this.showDrawInfo('Drawing geometry'); + this.startDrawGeometry(true); + }); + }); + } + + /** @summary methods show info when first geometry drawing is performed */ + showDrawInfo(msg) { + if (this.isBatchMode() || !this.#first_drawing || !this.#start_drawing_time) + return; + + const main = this.#renderer.domElement.parentNode; + if (!main) + return; + + let info = main.querySelector('.geo_info'); + + if (!msg) + info?.remove(); + else { + const spent = (new Date().getTime() - this.#start_drawing_time) * 1e-3; + if (!info) { + info = getDocument().createElement('p'); + info.setAttribute('class', 'geo_info'); + info.setAttribute('style', 'position: absolute; text-align: center; vertical-align: middle; top: 45%; left: 40%; color: red; font-size: 150%;'); + main.append(info); + } + info.innerText = `${msg}, ${spent.toFixed(1)}s`; + } + } + + /** @summary Reentrant method to perform geometry drawing step by step */ + continueDraw() { + // nothing to do - exit + if (this.isStage(stageInit)) + return; + + const tm0 = new Date().getTime(), + interval = this.#first_drawing ? 1000 : 200; + let now = tm0; + + while (true) { + const res = this.nextDrawAction(); + if (!res) + break; + + now = new Date().getTime(); + + // stop creation after 100 sec, render as is + if (now - this.#start_render_tm > 1e5) { + this.changeStage(stageInit, 'Abort build after 100s'); + break; + } + + // if we are that fast, do next action + if ((res === true) && (now - tm0 < interval)) + continue; + + if ((now - tm0 > interval) || (res === 1) || (res === 2)) { + showProgress(this.#drawing_log); + + this.showDrawInfo(this.#drawing_log); + + if (this.#first_drawing && this.#webgl && (this.ctrl.info.num_meshes - this.#last_render_meshes > 100) && (now - this.#last_render_tm > 2.5 * interval)) { + this.adjustCameraPosition(); + this.render3D(-1); + this.#last_render_meshes = this.ctrl.info.num_meshes; + } + if (res !== 2) + setTimeout(() => this.continueDraw(), (res === 1) ? 100 : 1); + + return; + } + } + + const take_time = now - this.#start_render_tm; + + if ((this.#first_drawing || this.#full_redrawing) && (settings.Debug || take_time > 1000)) + console.log(`Create tm = ${take_time} meshes ${this.ctrl.info.num_meshes} faces ${this.ctrl.info.num_faces}`); + + if (take_time > 300) { + showProgress('Rendering geometry'); + this.showDrawInfo('Rendering'); + return setTimeout(() => this.completeDraw(true), 10); + } + + this.completeDraw(true); + } + + /** @summary Checks camera position and recalculate rendering order if needed + * @param force - if specified, forces calculations of render order */ + testCameraPosition(force) { + this.#camera.updateMatrixWorld(); + + this.drawOverlay(); + + const origin = this.#camera.position.clone(); + if (!force && this.#last_camera_position) { + // if camera position does not changed a lot, ignore such change + const dist = this.#last_camera_position.distanceTo(origin); + if (dist < (this.#overall_size || 1000) * 1e-4) + return; + } + + this.#last_camera_position = origin; // remember current camera position + + if (this.ctrl._axis) { + const vect = (this.#controls?.target || this.#lookat).clone().sub(this.#camera.position).normalize(); + this.getExtrasContainer('get', 'axis')?.traverse(obj3d => { + if (isFunc(obj3d._axis_flip)) + obj3d._axis_flip(vect); + }); + } + + if (!this.ctrl.project) + produceRenderOrder(this.#toplevel, origin, this.ctrl.depthMethod, this.#clones); + } + + /** @summary Call 3D rendering of the geometry + * @param tmout - specifies delay, after which actual rendering will be invoked + * @param [measure] - when true, for the first time printout rendering time + * @return {Promise} when tmout bigger than 0 is specified + * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings + * superimposed with each other. If tmout <= 0, rendering performed immediately + * Several special values are used: + * -1 - force recheck of rendering order based on camera position */ + render3D(tmout, measure) { + if (!this.#renderer) { + if (this.#did_cleanup) + console.warn('try to render after cleanup'); + else + console.warn('renderer object not exists - check code'); + return this; + } + + const ret_promise = (tmout !== undefined) && (tmout > 0) && (measure !== 'nopromise'); + + if (tmout === undefined) + tmout = 5; // by default, rendering happens with timeout + + if ((tmout > 0) && this.#webgl) { + if (this.isBatchMode()) + tmout = 1; // use minimal timeout in batch mode + if (ret_promise) { + return new Promise(resolveFunc => { + this.#render_resolveFuncs.push(resolveFunc); + if (!this.#render_tmout) + this.#render_tmout = setTimeout(() => this.render3D(0), tmout); + }); + } + + if (!this.#render_tmout) + this.#render_tmout = setTimeout(() => this.render3D(0), tmout); + return this; + } + + if (this.#render_tmout) { + clearTimeout(this.#render_tmout); + this.#render_tmout = undefined; + } + + beforeRender3D(this.#renderer); + + const tm1 = new Date(); + + if (this.#adjust_camera_with_render) { + this.adjustCameraPosition('only_set'); + this.#adjust_camera_with_render = undefined; + } + + this.testCameraPosition(tmout === -1); + + // its needed for outlinePass - do rendering, most consuming time + if (this.#webgl && this.#effectComposer?.passes) + this.#effectComposer.render(); + else if (this.#webgl && this.#bloomComposer?.passes) { + this.#renderer.clear(); + this.#camera.layers.set(_BLOOM_SCENE); + this.#bloomComposer.render(); + this.#renderer.clearDepth(); + this.#camera.layers.set(_ENTIRE_SCENE); + this.#renderer.render(this.#scene, this.#camera); + } else + this.#renderer.render(this.#scene, this.#camera); + + const tm2 = new Date(); + + this.#last_render_tm = tm2.getTime(); + + if ((this.#first_render_tm === 0) && (measure === true)) { + this.#first_render_tm = tm2.getTime() - tm1.getTime(); + if (this.#first_render_tm > 500) + console.log(`three.js r${THREE.REVISION}, first render tm = ${this.#first_render_tm}`); + } + + afterRender3D(this.#renderer); + + const arr = this.#render_resolveFuncs; + this.#render_resolveFuncs = []; + arr?.forEach(func => func(this)); + } + + /** @summary Start geo worker */ + async startWorker() { + if (this.#worker) + return; + + this.#worker_ready = false; + this.#worker_jobs = 0; // counter how many requests send to worker + + let pr; + + if (isNodeJs()) { + pr = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h => { + const wrk = new h.Worker(exports.source_dir.slice(7) + 'modules/geom/nodeworker.mjs', { type: 'module' }); + wrk.on('message', msg => this.processWorkerReply(msg)); + return wrk; + }); + } else { + // Finally use ES6 module, see https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/ + const wrk = new Worker(exports.source_dir + 'modules/geom/geoworker.mjs', { type: 'module' }); + wrk.onmessage = e => this.processWorkerReply(e?.data); + pr = Promise.resolve(wrk); + } + + return pr.then(wrk => { + this.#worker = wrk; + // send initialization message with clones + wrk.postMessage({ + init: true, // indicate init command for worker + tm0: new Date().getTime(), + vislevel: this.#clones.getVisLevel(), + maxvisnodes: this.#clones.getMaxVisNodes(), + clones: this.#clones.nodes, + sortmap: this.#clones.sortmap + }); + }); + } + + /** @summary check if one can submit request to worker + * @private */ + canSubmitToWorker(force) { + return this.#worker ? this.#worker_ready && ((this.#worker_jobs === 0) || force) : false; + } + + /** @summary submit request to worker + * @private */ + submitToWorker(job) { + if (!this.#worker) + return false; + + this.#worker_jobs++; + job.tm0 = new Date().getTime(); + this.#worker.postMessage(job); + } + + /** @summary process reply from worker + * @private */ + processWorkerReply(job) { + if (!job || !isObject(job)) + return; + + if (job.log) + return console.log(`geo: ${job.log}`); + + if (job.progress) + return showProgress(job.progress); + + job.tm3 = new Date().getTime(); + + if (job.init) { + this.#worker_ready = true; + return; + } + + this.#worker_jobs--; + + if ('collect' in job) { + this.#new_draw_nodes = job.new_nodes; + this.#draw_all_nodes = job.complete; + this.changeStage(stageAnalyze); + // invoke methods immediately + return this.continueDraw(); + } + + if ('shapes' in job) { + for (let n = 0; n < job.shapes.length; ++n) { + const item = job.shapes[n], + origin = this.#build_shapes[n]; + + if (item.buf_pos && item.buf_norm) { + if (!item.buf_pos.length) + origin.geom = null; + else if (item.buf_pos.length !== item.buf_norm.length) { + console.error(`item.buf_pos.length ${item.buf_pos.length} !== item.buf_norm.length ${item.buf_norm.length}`); + origin.geom = null; + } else { + origin.geom = new THREE.BufferGeometry(); + + origin.geom.setAttribute('position', new THREE.BufferAttribute(item.buf_pos, 3)); + origin.geom.setAttribute('normal', new THREE.BufferAttribute(item.buf_norm, 3)); + } + + origin.ready = true; + origin.nfaces = item.nfaces; + } + } + + job.tm4 = new Date().getTime(); + + this.changeStage(stageBuild); // first check which shapes are used, than build meshes + + // invoke methods immediately + return this.continueDraw(); + } + } + + /** @summary start draw geometries on central and all subordinate painters + * @private */ + testGeomChanges() { + if (this.getCentral()) { + console.warn('Get testGeomChanges call for subordinate painter'); + return this.getCentral().testGeomChanges(); + } + this.startDrawGeometry(); + this.getSubordinates()?.forEach(p => p.startDrawGeometry()); + } + + /** @summary Draw axes and camera overlay */ + drawAxesAndOverlay(norender) { + const res1 = this.drawAxes(), + res2 = this.drawOverlay(); + + if (!res1 && !res2) + return norender ? null : this.render3D(); + + return this.changedDepthMethod(norender ? 'norender' : undefined); + } + + /** @summary Draw overlay for the orthographic cameras */ + drawOverlay() { + this.getExtrasContainer('delete', 'overlay'); + if (!this.isOrthoCamera() || (this.ctrl.camera_overlay === 'none')) + return false; + + const zoom = 0.5 / this.#camera.zoom, + midx = (this.#camera.left + this.#camera.right) / 2, + midy = (this.#camera.bottom + this.#camera.top) / 2, + xmin = midx - (this.#camera.right - this.#camera.left) * zoom, + xmax = midx + (this.#camera.right - this.#camera.left) * zoom, + ymin = midy - (this.#camera.top - this.#camera.bottom) * zoom, + ymax = midy + (this.#camera.top - this.#camera.bottom) * zoom, + tick_size = (ymax - ymin) * 0.02, + text_size = (ymax - ymin) * 0.015, + grid_gap = (ymax - ymin) * 0.001, + x1 = xmin + text_size * 5, x2 = xmax - text_size * 5, + y1 = ymin + text_size * 3, y2 = ymax - text_size * 3, + x_handle = new TAxisPainter(null, create$1(clTAxis)); + + x_handle.configureAxis('xaxis', x1, x2, x1, x2, false, [x1, x2], + { log: 0, reverse: false }); + const y_handle = new TAxisPainter(null, create$1(clTAxis)); + y_handle.configureAxis('yaxis', y1, y2, y1, y2, false, [y1, y2], + { log: 0, reverse: false }); + + const ii = this.#camera.orthoIndicies ?? [0, 1, 2]; + let buf, pos, midZ = 0, gridZ = 0; + + if (this.#camera.orthoZ) + gridZ = midZ = this.#camera.orthoZ[0]; + + const addPoint = (x, y, z) => { + buf[pos + ii[0]] = x; + buf[pos + ii[1]] = y; + buf[pos + ii[2]] = z ?? gridZ; + pos += 3; + }, createText = (lbl, size) => { + const text3d = createTextGeometry(lbl, size); + text3d.computeBoundingBox(); + text3d._width = text3d.boundingBox.max.x - text3d.boundingBox.min.x; + text3d._height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + + text3d.translate(-text3d._width / 2, -text3d._height / 2, 0); + if (this.#camera.orthoSign < 0) + text3d.rotateY(Math.PI); + + if (isFunc(this.#camera.orthoRotation)) + this.#camera.orthoRotation(text3d); + + return text3d; + }, createTextMesh = (geom, material, x, y, z) => { + const tgt = [0, 0, 0]; + tgt[ii[0]] = x; + tgt[ii[1]] = y; + tgt[ii[2]] = z ?? gridZ; + const mesh = new THREE.Mesh(geom, material); + mesh.translateX(tgt[0]).translateY(tgt[1]).translateZ(tgt[2]); + return mesh; + }; + + if (this.ctrl.camera_overlay === 'bar') { + const container = this.getExtrasContainer('create', 'overlay'); + + let ox1 = xmin * 0.15 + xmax * 0.85, + ox2 = xmin * 0.05 + xmax * 0.95; + const oy1 = ymax * 0.9 + ymin * 0.1, + oy2 = ymax * 0.86 + ymin * 0.14, + ticks = x_handle.createTicks(); + + if (ticks.major?.length > 1) { + ox1 = ticks.major.at(-2); + ox2 = ticks.major.at(-1); + } + + buf = new Float32Array(3 * 6); + pos = 0; + + addPoint(ox1, oy1, midZ); + addPoint(ox1, oy2, midZ); + + addPoint(ox1, (oy1 + oy2) / 2, midZ); + addPoint(ox2, (oy1 + oy2) / 2, midZ); + + addPoint(ox2, oy1, midZ); + addPoint(ox2, oy2, midZ); + + const lineMaterial = new THREE.LineBasicMaterial({ color: 'green' }), + textMaterial = new THREE.MeshBasicMaterial({ color: 'green', vertexColors: false }); + + container.add(createLineSegments(buf, lineMaterial)); + + const text3d = createText(x_handle.format(ox2 - ox1, true), Math.abs(oy2 - oy1)); + + container.add(createTextMesh(text3d, textMaterial, (ox2 + ox1) / 2, (oy1 + oy2) / 2 + text3d._height * 0.8, midZ)); + return true; + } + + const show_grid = this.ctrl.camera_overlay.indexOf('grid') === 0; + + if (show_grid && this.#camera.orthoZ) { + if (this.ctrl.camera_overlay === 'gridf') + gridZ += this.#camera.orthoSign * this.#camera.orthoZ[1]; + else if (this.ctrl.camera_overlay === 'gridb') + gridZ -= this.#camera.orthoSign * this.#camera.orthoZ[1]; + } + + if ((this.ctrl.camera_overlay === 'axis') || show_grid) { + const container = this.getExtrasContainer('create', 'overlay'), + lineMaterial = new THREE.LineBasicMaterial({ color: new THREE.Color('black') }), + gridMaterial1 = show_grid ? new THREE.LineBasicMaterial({ color: new THREE.Color(0xbbbbbb) }) : null, + gridMaterial2 = show_grid ? new THREE.LineDashedMaterial({ color: new THREE.Color(0xdddddd), dashSize: grid_gap, gapSize: grid_gap }) : null, + textMaterial = new THREE.MeshBasicMaterial({ color: 'black', vertexColors: false }), + xticks = x_handle.createTicks(); + + while (xticks.next()) { + const x = xticks.tick, k = (xticks.kind === 1) ? 1.0 : 0.6; + + if (show_grid) { + buf = new Float32Array(2 * 3); + pos = 0; + addPoint(x, ymax - k * tick_size - grid_gap); + addPoint(x, ymin + k * tick_size + grid_gap); + container.add(createLineSegments(buf, xticks.kind === 1 ? gridMaterial1 : gridMaterial2)); + } + + buf = new Float32Array(4 * 3); + pos = 0; + addPoint(x, ymax); + addPoint(x, ymax - k * tick_size); + addPoint(x, ymin); + addPoint(x, ymin + k * tick_size); + + container.add(createLineSegments(buf, lineMaterial)); + + if (xticks.kind !== 1) + continue; + + const text3d = createText(x_handle.format(x, true), text_size); + + container.add(createTextMesh(text3d, textMaterial, x, ymax - tick_size - text_size / 2 - text3d._height / 2)); + + container.add(createTextMesh(text3d, textMaterial, x, ymin + tick_size + text_size / 2 + text3d._height / 2)); + } + + const yticks = y_handle.createTicks(); + + while (yticks.next()) { + const y = yticks.tick, k = (yticks.kind === 1) ? 1.0 : 0.6; + + if (show_grid) { + buf = new Float32Array(2 * 3); + pos = 0; + addPoint(xmin + k * tick_size + grid_gap, y); + addPoint(xmax - k * tick_size - grid_gap, y); + container.add(createLineSegments(buf, yticks.kind === 1 ? gridMaterial1 : gridMaterial2)); + } + + buf = new Float32Array(4 * 3); + pos = 0; + addPoint(xmin, y); + addPoint(xmin + k * tick_size, y); + addPoint(xmax, y); + addPoint(xmax - k * tick_size, y); + + container.add(createLineSegments(buf, lineMaterial)); + + if (yticks.kind !== 1) + continue; + + const text3d = createText(y_handle.format(y, true), text_size); + + container.add(createTextMesh(text3d, textMaterial, xmin + tick_size + text_size / 2 + text3d._width / 2, y)); + + container.add(createTextMesh(text3d, textMaterial, xmax - tick_size - text_size / 2 - text3d._width / 2, y)); + } + + return true; + } + + return false; + } + + /** @summary Draw axes if configured, otherwise just remove completely */ + drawAxes() { + this.getExtrasContainer('delete', 'axis'); + + if (!this.ctrl._axis) + return false; + + const box = this.getGeomBoundingBox(this.#toplevel, this.#superimpose ? 'original' : undefined), + container = this.getExtrasContainer('create', 'axis'), + text_size = 0.02 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z), + center = [0, 0, 0], + names = ['x', 'y', 'z'], + labels = ['X', 'Y', 'Z'], + colors = ['red', 'green', 'blue'], + ortho = this.isOrthoCamera(), + ckind = this.ctrl.camera_kind ?? 'perspective'; + + if (this.ctrl._axis === 2) { + for (let naxis = 0; naxis < 3; ++naxis) { + const name = names[naxis]; + if ((box.min[name] <= 0) && (box.max[name] >= 0)) + continue; + center[naxis] = (box.min[name] + box.max[name]) / 2; + } + } + + for (let naxis = 0; naxis < 3; ++naxis) { + // exclude axis which is not seen + if (ortho && ckind.indexOf(labels[naxis]) < 0) + continue; + + const buf = new Float32Array(6), + color = colors[naxis], + name = names[naxis], + valueToString = val => { + if (!val) + return '0'; + const lg = Math.log10(Math.abs(val)); + if (lg < 0) { + if (lg > -1) + return val.toFixed(2); + if (lg > -2) + return val.toFixed(3); + } else { + if (lg < 2) + return val.toFixed(1); + if (lg < 4) + return val.toFixed(0); + } + return val.toExponential(2); + }, + lbl = valueToString(box.max[name]) + ' ' + labels[naxis]; + + buf[0] = box.min.x; + buf[1] = box.min.y; + buf[2] = box.min.z; + + buf[3] = box.min.x; + buf[4] = box.min.y; + buf[5] = box.min.z; + + switch (naxis) { + case 0: buf[3] = box.max.x; break; + case 1: buf[4] = box.max.y; break; + case 2: buf[5] = box.max.z; break; + } + + if (this.ctrl._axis === 2) { + for (let k = 0; k < 6; ++k) { + if ((k % 3) !== naxis) + buf[k] = center[k % 3]; + } + } + + const lineMaterial = new THREE.LineBasicMaterial({ color }); + let mesh = createLineSegments(buf, lineMaterial); + + mesh._no_clip = true; // skip from clipping + + container.add(mesh); + + const textMaterial = new THREE.MeshBasicMaterial({ color, vertexColors: false }); + + if ((center[naxis] === 0) && (center[naxis] >= box.min[name]) && (center[naxis] <= box.max[name])) { + if ((this.ctrl._axis !== 2) || (naxis === 0)) { + const geom = ortho ? new THREE.CircleGeometry(text_size * 0.25) : new THREE.SphereGeometry(text_size * 0.25); + mesh = new THREE.Mesh(geom, textMaterial); + mesh.translateX(naxis === 0 ? center[0] : buf[0]); + mesh.translateY(naxis === 1 ? center[1] : buf[1]); + mesh.translateZ(naxis === 2 ? center[2] : buf[2]); + mesh._no_clip = true; + container.add(mesh); + } + } + + let text3d = createTextGeometry(lbl, text_size); + mesh = new THREE.Mesh(text3d, textMaterial); + mesh._no_clip = true; // skip from clipping + + function setSideRotation(mesh2, normal) { + mesh2._other_side = false; + mesh2._axis_norm = normal ?? new THREE.Vector3(1, 0, 0); + mesh2._axis_flip = function(vect) { + const other_side = vect.dot(this._axis_norm) < 0; + if (this._other_side !== other_side) { + this._other_side = other_side; + this.rotateY(Math.PI); + } + }; + } + + function setTopRotation(mesh2, first_angle = -1) { + mesh2._last_angle = first_angle; + mesh2._axis_flip = function(vect) { + let angle; + switch (this._axis_name) { + case 'x': angle = -Math.atan2(vect.y, vect.z); break; + case 'y': angle = -Math.atan2(vect.z, vect.x); break; + default: angle = Math.atan2(vect.y, vect.x); + } + angle = Math.round(angle / Math.PI * 2 + 2) % 4; + if (this._last_angle !== angle) { + this.rotateX((angle - this._last_angle) * Math.PI / 2); + this._last_angle = angle; + } + }; + } + + let textbox = new THREE.Box3().setFromObject(mesh); + + text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); + + mesh.translateX(buf[3]); + mesh.translateY(buf[4]); + mesh.translateZ(buf[5]); + + mesh._axis_name = name; + + if (naxis === 0) { + if (ortho && ckind.indexOf('OX') > 0) + setTopRotation(mesh, 0); + else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) + setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); + else { + setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); + mesh.rotateX(Math.PI / 2); + } + + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); + } else if (naxis === 1) { + if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { + setTopRotation(mesh, 2); + mesh.rotateX(-Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); + } else { + setSideRotation(mesh); + mesh.rotateX(Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); + } + } else if (naxis === 2) { + if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { + const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); + setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); + mesh.rotateY(-Math.PI / 2); + if (zox) + mesh.rotateX(-Math.PI / 2); + } else { + setTopRotation(mesh); + mesh.rotateX(Math.PI / 2); + mesh.rotateZ(Math.PI / 2); + } + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); + } + + container.add(mesh); + + text3d = createTextGeometry(valueToString(box.min[name]), text_size); + + mesh = new THREE.Mesh(text3d, textMaterial); + mesh._no_clip = true; // skip from clipping + textbox = new THREE.Box3().setFromObject(mesh); + + text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); + + mesh._axis_name = name; + + mesh.translateX(buf[0]); + mesh.translateY(buf[1]); + mesh.translateZ(buf[2]); + + if (naxis === 0) { + if (ortho && ckind.indexOf('OX') > 0) + setTopRotation(mesh, 0); + else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) + setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); + else { + setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); + mesh.rotateX(Math.PI / 2); + } + mesh.translateX(-text_size * 0.5 - textbox.max.x * 0.5); + } else if (naxis === 1) { + if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { + setTopRotation(mesh, 2); + mesh.rotateX(-Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); + } else { + setSideRotation(mesh); + mesh.rotateX(Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(textbox.max.x * 0.5 + text_size * 0.5); + } + } else if (naxis === 2) { + if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { + const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); + setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); + mesh.rotateY(-Math.PI / 2); + if (zox) + mesh.rotateX(-Math.PI / 2); + } else { + setTopRotation(mesh); + mesh.rotateX(Math.PI / 2); + mesh.rotateZ(Math.PI / 2); + } + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); + } + + container.add(mesh); + } + + // after creating axes trigger rendering and recalculation of depth + return true; + } + + /** @summary Set axes visibility 0 - off, 1 - on, 2 - centered */ + setAxesDraw(on) { + if (on === 'toggle') + this.ctrl._axis = this.ctrl._axis ? 0 : 1; + else + this.ctrl._axis = (typeof on === 'number') ? on : (on ? 1 : 0); + return this.drawAxesAndOverlay(); + } + + /** @summary Set auto rotate mode */ + setAutoRotate(on) { + if (this.ctrl.project) + return; + if (on !== undefined) + this.ctrl.rotate = on; + this.autorotate(2.5); + } + + /** @summary Toggle wireframe mode */ + toggleWireFrame() { + this.ctrl.wireframe = !this.ctrl.wireframe; + this.changedWireFrame(); + } + + /** @summary Specify wireframe mode */ + setWireFrame(on) { + this.ctrl.wireframe = Boolean(on); + this.changedWireFrame(); + } + + /** @summary Specify showtop draw options, relevant only for TGeoManager */ + setShowTop(on) { + this.ctrl.showtop = Boolean(on); + return this.startRedraw(); + } + + /** @summary Should be called when configuration of particular axis is changed */ + changedClipping(naxis = -1) { + if ((naxis < 0) || this.ctrl.clip[naxis]?.enabled) + this.updateClipping(false, true); + } + + /** @summary Should be called when depth test flag is changed */ + changedDepthTest() { + if (!this.#toplevel) + return; + const flag = this.ctrl.depthTest; + this.#toplevel.traverse(node => { + if (node instanceof THREE.Mesh) + node.material.depthTest = flag; + }); + + this.render3D(0); + } + + /** @summary Should be called when depth method is changed */ + changedDepthMethod(arg) { + // force recalculation of render order + this.#last_camera_position = undefined; + if (arg !== 'norender') + return this.render3D(); + } + + /** @summary Assign clipping attributes to the meshes - supported only for webgl */ + updateClipping(without_render, force_traverse) { + // do not try clipping with SVG renderer + if (this.#renderer?.jsroot_render3d === constants$1.Render3D.SVG) + return; + + if (!this.#clip_planes) { + this.#clip_planes = [new THREE.Plane(new THREE.Vector3(1, 0, 0), 0), + new THREE.Plane(new THREE.Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0), + new THREE.Plane(new THREE.Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0)]; + } + + const clip = this.ctrl.clip, + clip_constants = [-1 * clip[0].value, clip[1].value, (this.ctrl._yup ? -1 : 1) * clip[2].value], + container = this.getExtrasContainer(this.ctrl.clipVisualize ? '' : 'delete', 'clipping'); + let panels = [], changed = false, + clip_cfg = this.ctrl.clipIntersect ? 16 : 0; + + for (let k = 0; k < 3; ++k) { + if (clip[k].enabled) + clip_cfg += 2 << k; + if (this.#clip_planes[k].constant !== clip_constants[k]) { + if (clip[k].enabled) + changed = true; + this.#clip_planes[k].constant = clip_constants[k]; + } + if (clip[k].enabled) + panels.push(this.#clip_planes[k]); + + if (container && clip[k].enabled) { + const helper = new THREE.PlaneHelper(this.#clip_planes[k], (clip[k].max - clip[k].min)); + helper._no_clip = true; + container.add(helper); + } + } + if (!panels.length) + panels = null; + + if (this.#last_clip_cfg !== clip_cfg) + changed = true; + + this.#last_clip_cfg = clip_cfg; + + const any_clipping = Boolean(panels), ci = this.ctrl.clipIntersect, + material_side = any_clipping ? THREE.DoubleSide : THREE.FrontSide; + + if (force_traverse || changed) { + this.#scene.traverse(node => { + if (!node._no_clip && (node.material?.clippingPlanes !== undefined)) { + if (node.material.clippingPlanes !== panels) { + node.material.clipIntersection = ci; + node.material.clippingPlanes = panels; + node.material.needsUpdate = true; + } + + if (node.material.emissive !== undefined) { + if (node.material.side !== material_side) { + node.material.side = material_side; + node.material.needsUpdate = true; + } + } + } + }); + } + + this.ctrl.doubleside = any_clipping; + + if (!without_render) + this.render3D(0); + + return changed; + } + + /** @summary Assign callback, invoked every time when drawing is completed + * @desc Used together with web-based geometry viewer + * @private */ + setCompleteHandler(callback) { + this.#complete_handler = callback; + } + + /** @summary Completes drawing procedure + * @return {Promise} for ready */ + async completeDraw(close_progress) { + let first_time = false, full_redraw = false, check_extras = true; + + if (!this.ctrl) { + console.warn('ctrl object does not exist in completeDraw - something went wrong'); + return this; + } + + let promise; + + if (!this.#clones) { + check_extras = false; + // if extra object where append, redraw them at the end + this.getExtrasContainer('delete'); // delete old container + promise = this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); + } else if ((this.#first_drawing || this.#full_redrawing) && this.ctrl.tracks && this.#geo_manager) + promise = this.drawExtras(this.#geo_manager.fTracks, '/Tracks'); + else + promise = Promise.resolve(true); + + return promise.then(() => { + if (this.#full_redrawing) { + this.adjustCameraPosition('first'); + this.#full_redrawing = false; + full_redraw = true; + this.changedDepthMethod('norender'); + } + + if (this.#first_drawing) { + this.adjustCameraPosition('first'); + this.showDrawInfo(); + this.#first_drawing = false; + first_time = true; + full_redraw = true; + } + + if (first_time) + this.completeScene(); + + if (full_redraw && (this.ctrl.trans_radial || this.ctrl.trans_z)) + this.changedTransformation('norender'); + + if (full_redraw) + return this.drawAxesAndOverlay(true); + }).then(() => { + this.#scene.overrideMaterial = null; + + if (this.#provided_more_nodes !== undefined) { + this.appendMoreNodes(this.#provided_more_nodes, true); + this.#provided_more_nodes = undefined; + } + + if (check_extras) { + // if extra object where append, redraw them at the end + this.getExtrasContainer('delete'); // delete old container + return this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); + } + }).then(() => { + this.updateClipping(true); // do not render + + this.render3D(0, true); + + if (close_progress) + showProgress(); + + this.addOrbitControls(); + + if (first_time && !this.isBatchMode()) { + // after first draw check if highlight can be enabled + if (this.ctrl.highlight === 0) + this.ctrl.highlight = (this.#first_render_tm < 1000); + + // also highlight of scene object can be assigned at the first draw + if (this.ctrl.highlight_scene === 0) + this.ctrl.highlight_scene = this.ctrl.highlight; + + // if rotation was enabled, do it + if (this.#webgl && this.ctrl.rotate && !this.ctrl.project) + this.autorotate(2.5); + if (this.#webgl && this.ctrl.show_controls) + this.showControlGui(true); + } + + this.setAsMainPainter(); + + const arr = this.#draw_resolveFuncs; + this.#draw_resolveFuncs = []; + arr?.forEach(func => func(this)); + + if (isFunc(this.#complete_handler)) + this.#complete_handler(this); + + if (this.#draw_nodes_again) + this.startDrawGeometry(); // relaunch drawing + else + this.#drawing_ready = true; // indicate that drawing is completed + + return this; + }); + } + + /** @summary Returns true if geometry drawing is completed */ + isDrawingReady() { return this.#drawing_ready ?? false; } + + /** @summary Remove already drawn node. Used by geom viewer */ + removeDrawnNode(nodeid) { + if (!this.#draw_nodes) + return; + + const new_nodes = []; + + for (let n = 0; n < this.#draw_nodes.length; ++n) { + const entry = this.#draw_nodes[n]; + if ((entry.nodeid === nodeid) || this.#clones.isIdInStack(nodeid, entry.stack)) + this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); + else + new_nodes.push(entry); + } + + if (new_nodes.length < this.#draw_nodes.length) { + this.#draw_nodes = new_nodes; + this.render3D(); + } + } + + /** @summary Cleanup geometry painter */ + cleanup(first_time) { + if (!first_time) { + let can3d = 0; + + if (!this.#superimpose) { + this.clearTopPainter(); // remove as pointer + + if (this.#on_pad) { + const fp = this.getFramePainter(); + if (fp?.mode3d) { + fp.clear3dCanvas(); + fp.mode3d = false; + } + } else if (isFunc(this.clear3dCanvas)) + can3d = this.clear3dCanvas(); // remove 3d canvas from main HTML element + + disposeThreejsObject(this.#scene); + } + + this.#toolbar?.cleanup(); // remove toolbar + + disposeThreejsObject(this.#fullgeom_proj); + + this.#controls?.cleanup(); + + this.#gui?.destroy(); + + this.#worker?.terminate(); + + this.#animating = undefined; + + const obj = this.getGeometry(); + if (obj && this.ctrl.is_main) { + if (obj.$geo_painter === this) + delete obj.$geo_painter; + else if (obj.fVolume?.$geo_painter === this) + delete obj.fVolume.$geo_painter; + } + + this.#central_painter?.assignSubordinate(this, false); + this.#subordinate_painters?.forEach(p => { + p.assignCentral(this, false); + if (p.getClones() === this.getClones()) + p.assignClones(undefined, undefined); + }); + + this.#geo_manager = undefined; + this.#highlight_handlers = undefined; + + super.cleanup(); + + delete this.ctrl; + + this.#did_cleanup = true; + + if (can3d < 0) + this.selectDom().html(''); + } + + this.#central_painter = undefined; + this.#subordinate_painters = []; + + const arr = this.#render_resolveFuncs; + this.#render_resolveFuncs = []; + arr?.forEach(func => func(this)); + + if (!this.#superimpose) + cleanupRender3D(this.#renderer); + + this.ensureBloom(false); + this.#effectComposer = undefined; + + this.#scene = undefined; + this.#scene_size = undefined; + this.#scene_width = 0; + this.#scene_height = 0; + this.#renderer = null; + this.#toplevel = null; + this.#fullgeom_proj = undefined; + this.#fog = undefined; + this.#camera = undefined; + this.#camera0pos = undefined; + this.#lookat = undefined; + this.#selected_mesh = undefined; + this.#custom_bounding_box = undefined; + + this.assignClones(undefined, undefined); + this.#new_draw_nodes = undefined; + this.#new_append_nodes = undefined; + this.#last_camera_position = undefined; + + this.#on_pad = undefined; + + this.#start_render_tm = 0; + this.#first_render_tm = 0; // time needed for first rendering + this.#last_render_tm = 0; + + this.changeStage(stageInit, 'cleanup'); + this.#drawing_log = undefined; + + this.#gui = undefined; + this.#controls = undefined; + this.#toolbar = undefined; + + this.#worker = undefined; + } + + /** @summary perform resize */ + performResize(width, height) { + if ((this.#scene_width === width) && (this.#scene_height === height)) + return false; + if ((width < 10) || (height < 10)) + return false; + + this.#scene_width = width; + this.#scene_height = height; + + if (this.#camera && this.#renderer) { + if (this.#camera.isPerspectiveCamera) + this.#camera.aspect = this.#scene_width / this.#scene_height; + else if (this.#camera.isOrthographicCamera) + this.adjustCameraPosition(true, true); + this.#camera.updateProjectionMatrix(); + this.#renderer.setSize(this.#scene_width, this.#scene_height, !this.#fit_main_area); + this.#effectComposer?.setSize(this.#scene_width, this.#scene_height); + this.#bloomComposer?.setSize(this.#scene_width, this.#scene_height); + + if (this.isStage(stageInit)) + this.render3D(); + } + + return true; + } + + /** @summary Check if HTML element was resized and drawing need to be adjusted */ + checkResize(arg) { + const cp = this.getCanvPainter(); + + // firefox is the only browser which correctly supports resize of embedded canvas, + // for others we should force canvas redrawing at every step + if (cp && !cp.checkCanvasResize(arg)) + return false; + + const sz = this.getSizeFor3d(); + return this.performResize(sz.width, sz.height); + } + + /** @summary Toggle enlarge state */ + toggleEnlarge() { + if (this.enlargeMain('toggle')) + this.checkResize(); + } + + /** @summary either change mesh wireframe or return current value + * @return undefined when wireframe cannot be accessed + * @private */ + accessObjectWireFrame(obj, on) { + if (!obj?.material) + return; + + if ((on !== undefined) && obj.stack) + obj.material.wireframe = on; + + return obj.material.wireframe; + } + + /** @summary handle wireframe flag change in GUI + * @private */ + changedWireFrame() { + this.#scene?.traverse(obj => this.accessObjectWireFrame(obj, this.ctrl.wireframe)); + + this.render3D(); + } + + /** @summary Update object in geo painter */ + updateObject(obj) { + if ((obj === 'same') || !obj?._typename) + return false; + if (obj === this.getObject()) + return true; + + let gm; + if (obj._typename === clTGeoManager) { + gm = obj; + obj = obj.fMasterVolume; + } + + if (obj._typename.indexOf(clTGeoVolume) === 0) + obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + + if (this.#geo_manager && gm) { + this.#geo_manager = gm; + this.assignObject(obj); + this.#did_update = true; + return true; + } + + if (!this.matchObjectType(obj._typename)) + return false; + + this.assignObject(obj); + this.#did_update = true; + return true; + } + + /** @summary Cleanup TGeo drawings */ + clearDrawings() { + this.assignClones(undefined, undefined); + + this.#extra_objects = undefined; + this.#last_clip_cfg = undefined; + + // only remove all childs from top level object + disposeThreejsObject(this.#toplevel, true); + + this.#full_redrawing = true; + } + + /** @summary Redraw TGeo object inside TPad */ + redraw() { + if (this.#superimpose) { + const cfg = getHistPainter3DCfg(this.getMainPainter()); + + if (cfg) { + this.#toplevel.scale.set(cfg.scale_x ?? 1, cfg.scale_y ?? 1, cfg.scale_z ?? 1); + this.#toplevel.position.set(cfg.offset_x ?? 0, cfg.offset_y ?? 0, cfg.offset_z ?? 0); + this.#toplevel.updateMatrix(); + this.#toplevel.updateMatrixWorld(); + } + } + + if (this.#did_update) + return this.startRedraw(); + + const fp = this.#on_pad ? this.getFramePainter() : null; + if (!fp) + return Promise.resolve(false); + const sz = fp.getSizeFor3d(fp.access3dKind()); + fp.apply3dSize(sz); + return this.performResize(sz.width, sz.height); + } + + /** @summary Redraw TGeo object */ + redrawObject(obj, opt) { + if (!this.updateObject(obj, opt)) + return false; + + return this.startRedraw(); + } + + /** @summary Start geometry redraw */ + startRedraw(tmout) { + if (this.#redraw_timer) { + clearTimeout(this.#redraw_timer); + this.#redraw_timer = undefined; + } + + if (tmout) { + this.#redraw_timer = setTimeout(() => this.startRedraw(), tmout); + return; + } + + this.#did_update = undefined; + + this.clearDrawings(); + const draw_obj = this.getGeometry(), + name_prefix = this.#geo_manager ? draw_obj.fName : ''; + return this.prepareObjectDraw(draw_obj, name_prefix); + } + + /** @summary Build three.js object */ + static build3d(obj, sopt) { + if (!obj) + return null; + + let opt = sopt || {}; + if (isStr(sopt)) { + const painter = new TGeoPainter(null, obj); + painter.decodeOptions(sopt); + opt = painter.ctrl; + opt.numfaces = opt.maxfaces; + opt.numnodes = opt.maxnodes; + } + + if (!opt.numfaces) + opt.numfaces = 100000; + if (!opt.numnodes) + opt.numnodes = 1000; + if (!opt.frustum) + opt.frustum = null; + + opt.res_mesh = opt.res_faces = 0; + + if (opt.instancing === undefined) + opt.instancing = -1; + + opt.info = { num_meshes: 0, num_faces: 0 }; + + let clones, visibles; + + if (obj.visibles && obj.nodes && obj.numnodes) { + // case of draw message from geometry viewer + + const nodes = obj.numnodes > 1e6 ? { length: obj.numnodes } : new Array(obj.numnodes); + + obj.nodes.forEach(node => { + nodes[node.id] = ClonedNodes.formatServerElement(node); + }); + + clones = new ClonedNodes(null, nodes); + clones.name_prefix = clones.getNodeName(0); + + // normally only need when making selection, not used in geo viewer + // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); + // this.geo_clones.setVisLevel(draw_msg.vislevel); + // TODO: provide from server + clones.maxdepth = 20; + + const nsegm = obj.cfg?.nsegm || 30; + + for (let cnt = 0; cnt < obj.visibles.length; ++cnt) { + const item = obj.visibles[cnt], rd = item.ri; + + // entry may be provided without shape - it is ok + if (rd) + item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); + } + + visibles = obj.visibles; + } else { + let shape = null, hide_top = false; + + if (('fShapeBits' in obj) && ('fShapeId' in obj)) { + shape = obj; + obj = null; + } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) + shape = obj.fShape; + else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) + shape = obj.fShape; + else if (obj._typename === clTGeoManager) { + obj = obj.fMasterVolume; + hide_top = !opt.showtop; + shape = obj.fShape; + } else if (obj.fVolume) + shape = obj.fVolume.fShape; + else + obj = null; + + if (opt.composite && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) + obj = buildCompositeVolume(shape); + + if (!obj && shape) + obj = Object.assign(create$1(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); + + if (!obj) + return null; + + if (obj._typename.indexOf(clTGeoVolume) === 0) + obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + + clones = new ClonedNodes(obj); + clones.setVisLevel(opt.vislevel); + clones.setMaxVisNodes(opt.numnodes); + + if (opt.dflt_colors) + clones.setDefaultColors(true); + + const uniquevis = opt.no_screen ? 0 : clones.markVisibles(true); + if (uniquevis <= 0) + clones.markVisibles(false, false, hide_top); + else + clones.markVisibles(true, true, hide_top); // copy bits once and use normal visibility bits + + clones.produceIdShifts(); + + // collect visible nodes + const res = clones.collectVisibles(opt.numfaces, opt.frustum); + + visibles = res.lst; + } + + if (!opt.material_kind) + opt.material_kind = 'lambert'; + if (opt.set_names === undefined) + opt.set_names = true; + + clones.setConfig(opt); + + // collect shapes + const shapes = clones.collectShapes(visibles); + + clones.buildShapes(shapes, opt.numfaces); + + const toplevel = new THREE.Object3D(); + toplevel.clones = clones; // keep reference on JSROOT data + + const colors = getRootColors(); + + if (clones.createInstancedMeshes(opt, toplevel, visibles, shapes, colors)) + return toplevel; + + for (let n = 0; n < visibles.length; ++n) { + const entry = visibles[n]; + if (entry.done) + continue; + + const shape = entry.server_shape || shapes[entry.shapeid]; + if (!shape.ready) { + console.warn('shape marked as not ready when it should'); + break; + } + + clones.createEntryMesh(opt, toplevel, entry, shape, colors); + } + + return toplevel; + } + + /** @summary draw TGeo object */ + static async draw(dom, obj, opt) { + if (!obj) + return null; + + let shape = null, extras = null, extras_path = '', is_eve = false; + + if (('fShapeBits' in obj) && ('fShapeId' in obj)) { + shape = obj; + obj = null; + } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) + shape = obj.fShape; + else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { + shape = obj.fShape; + is_eve = true; + } else if (obj._typename === clTGeoManager) + shape = obj.fMasterVolume.fShape; + else if (obj._typename === clTGeoOverlap) { + extras = obj.fMarker; + extras_path = '/Marker'; + obj = buildOverlapVolume(obj); + if (!opt) + opt = 'wire'; + } else if ('fVolume' in obj) + shape = obj.fVolume?.fShape; + else + obj = null; + + if (isStr(opt) && opt.indexOf('comp') === 0 && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { + let maxlvl = 1; + opt = opt.slice(4); + if (opt[0] === 'x') { + maxlvl = 999; + opt = opt.slice(1) + '_vislvl999'; + } + obj = buildCompositeVolume(shape, maxlvl); + } + + if (!obj && shape) + obj = Object.assign(create$1(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); + + if (!obj) + return null; + + // eslint-disable-next-line no-use-before-define + const painter = createGeoPainter(dom, obj, opt); + + if (painter.ctrl.is_main && !obj.$geo_painter) + obj.$geo_painter = painter; + + if (!painter.ctrl.is_main && painter.ctrl.project && obj.$geo_painter) { + painter.assignCentral(obj.$geo_painter); + obj.$geo_painter.assignSubordinate(painter); + } + + if (is_eve && (!painter.ctrl.vislevel || (painter.ctrl.vislevel < 9))) + painter.ctrl.vislevel = 9; + + if (extras) { + painter.ctrl.split_colors = true; + painter.addExtra(extras, extras_path); + } + + return painter.loadMacro(painter.ctrl.script_name).then(arg => painter.prepareObjectDraw(arg.obj, arg.prefix)); + } + +} // class TGeoPainter + + +let add_settings = false; + +/** @summary Get icon for the browser + * @private */ +function getBrowserIcon(hitem, hpainter) { + let icon = ''; + switch (hitem._kind) { + case getKindForType(clTEveTrack): icon = 'img_evetrack'; break; + case getKindForType(clTEvePointSet): icon = 'img_evepoints'; break; + case getKindForType(clTPolyMarker3D): icon = 'img_evepoints'; break; + } + if (icon) { + const drawitem = findItemWithGeoPainter(hitem); + if (drawitem?._painter?.extraObjectVisible(hpainter, hitem)) + icon += ' geovis_this'; + } + return icon; } -@font-face { - font-family: "lil-gui"; - src: url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff"); -}`; +/** @summary handle click on browser icon + * @private */ +function browserIconClick(hitem, hpainter) { + if (hitem._volume) { + if (hitem._more && hitem._volume.fNodes?.arr?.length) + toggleGeoBit(hitem._volume, geoBITS.kVisDaughters); + else + toggleGeoBit(hitem._volume, geoBITS.kVisThis); -function _injectStyles( cssContent ) { - const injected = document.createElement( 'style' ); - injected.innerHTML = cssContent; - const before = document.querySelector( 'head link[rel=stylesheet], head style' ); - if ( before ) { - document.head.insertBefore( injected, before ); - } else { - document.head.appendChild( injected ); - } + updateBrowserIcons(hitem._volume, hpainter); + + findItemWithGeoPainter(hitem, true); + return false; // no need to update icon - we did it ourself + } + + if (hitem._geoobj && ((hitem._geoobj._typename === clTEveGeoShapeExtract) || (hitem._geoobj._typename === clREveGeoShapeExtract))) { + hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf; + + updateBrowserIcons(hitem._geoobj, hpainter); + findItemWithGeoPainter(hitem, true); + return false; // no need to update icon - we did it ourself + } + + // first check that geo painter assigned with the item + const drawitem = findItemWithGeoPainter(hitem), + newstate = drawitem?._painter?.extraObjectVisible(hpainter, hitem, true); + + // return true means browser should update icon for the item + return newstate !== undefined; } -let stylesInjected = false; -class GUI { +/** @summary Create geo-related css entries + * @private */ +function injectGeoStyle() { + if (!add_settings && isFunc(internals.addDrawFunc)) { + add_settings = true; + // indication that draw and hierarchy is loaded, create css + internals.addDrawFunc({ name: clTEvePointSet, icon_get: getBrowserIcon, icon_click: browserIconClick }); + internals.addDrawFunc({ name: clTEveTrack, icon_get: getBrowserIcon, icon_click: browserIconClick }); + } + + function img(name, code) { + return `.jsroot .img_${name} { display: inline-block; height: 16px; width: 16px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ${code}'); }`; + } + + injectStyle(` +${img('geoarb8', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB1SURBVBjTdY6rEYAwEETTy6lzK8/Fo+Jj18dTAjUgaQGfGiggtRDE8RtY93Zu514If2nzk2ux9c5TZkwXbiWTUavzws69oBfpYBrMT4r0Jhsw+QfRgQSw+CaKRsKsnV+SaF8MN49RBSgPUxO85PMl5n4tfGUH2gghs2uPAeQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geocombi', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAlUlEQVQoz5VQMQ4CMQyzEUNnBqT7Bo+4nZUH8gj+welWJsQDkHoCEYakTXMHSFiq2jqu4xRAEl2A7w4myWzpzCSZRZ658ldKu1hPnFsequBIc/hcLli3l52MAIANtpWrDsv8waGTW6BPuFtsdZArXyFuj33TQpazGEQF38phipnLgItxRcAoOeNpzv4PTXnC42fb//AGI5YqfQAU8dkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geocone', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACRSURBVBjTdY+xDcNACEVvEm/ggo6Olva37IB0C3iEzJABvAHFTXBDeJRwthMnUvylk44vPjxK+afeokX0flQhJO7L4pafSOMxzaxIKc/Tc7SIjNLyieyZSjBzc4DqMZI0HTMonWPBNlogOLeuewbg9c0hOiIqH7DKmTCuFykjHe4XOzQ58XVMGxzt575tKzd6AX9yMkcWyPlsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geogtra', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACCSURBVBjTVc+hDQMxDAVQD1FyqCQk0MwsCwQEG3+eCW6B0FvheDboFMGepTlVitPP/Cz5y0S/mNkw8pySU9INJDDH4vM4Usm5OrQXasXtkA+tQF+zxfcDY8EVwgNeiwmA37TEccK5oLOwQtuCj7BM2Fq7iGrxVqJbSsH+GzXs+798AThwKMh3/6jDAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geomedium', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFAAAAAAAAMDAww8PDWKj/////gICAG0/C4AAAAAF0Uk5TAEDm2GYAAAABYktHRAX4b+nHAAAACXBIWXMAAABIAAAASABGyWs+AAAAXElEQVQI102MwRGAMAgEuQ6IDwvQCjQdhAl/H7ED038JHhkd3dcOLAgESFARaAqnEB3yrj6QSEym1RbbOKinN+8q2Esui1GaX7VXSi4RUbxHRbER8X6O5Pg/fLgBBzMN8HfXD3AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geopara', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABtSURBVBjTY2DADq5MT7+CzD9kaKjp+QhJYIWqublhMbKAgpOnZxWSQJdsVJTndCSBKoWoAM/VSALpqlEBAYeQBKJAAsi2BGgCBZDdEWUYFZCOLFBlGOWJ7AyGFeaotjIccopageK3R12PGHABACTYHWd0tGw6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('georotation', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAiklEQVQoz2NgYGBgYGDg+A/BmIAFIvyDEbs0AwMTAwHACLPiB5QVBTdpGSOSCZjScDcgc4z+32BgYGBgEGIQw3QDLkdCTZD8/xJFeBfDVxQT/j9n/MeIrMCNIRBJwX8GRuzGM/yHKMAljeILNFOuMTyEisEUMKIqucrwB2oyIhyQpH8y/MZrLWkAAHFzIHIc0Q5yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geotranslation', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABESURBVBjTY2DgYGAAYzjgAAIQgSLAgSwAAcrWUUCAJBAVhSpgBAQumALGCJPAAsriHIS0IAQ4UAU4cGphQBWwZSAOAADGJBKdZk/rHQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geotrd2', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABsSURBVBjTbY+xDcAwCARZx6UraiaAmpoRvIIb75PWI2QITxIiRQKk0CCO/xcA/NZ9LRs7RkJEYg3QxczUwoGsXiMAoe8lAelqRWFNKpiNXZLAalRDd0f3TMgeMckABKsCDmu+442RddeHz9cf9jUkW8smGn8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geovolume', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAB5QTFRFAAAAMDAw///Ay8uc/7+Q/4BgmJh4gIDgAAD/////CZb2ugAAAAF0Uk5TAEDm2GYAAAABYktHRAnx2aXsAAAACXBIWXMAAABIAAAASABGyWs+AAAAR0lEQVQI12NggAEBIBAEQgYGQUYQAyIGIhgwAZMSGCgwMJuEKimFOhswsKWAGG4JDGxJIBk1EEO9o6NIDVkEpgauC24ODAAASQ8Pkj/retYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geoassembly', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAA9QTFRFAAAAMDAw/wAAAAD/////jEo0BQAAAAF0Uk5TAEDm2GYAAAABYktHRASPaNlRAAAACXBIWXMAAABIAAAASABGyWs+AAAAOklEQVQI12NggAFGRgEgEBRgEBSAMhgYGQQEgAR+oARGDIwCIAYjUL0A2DQQg9nY2ABVBKoGrgsDAADxzgNboMz8zQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geocomposite', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABuSURBVBjTY2AgF2hqgQCCr+0V4O7hFmgCF7CJyKysKkmxhfGNLaw9SppqAi2gfMuY5Agrl+ZaC6iAUXRJZX6Ic0klTMA5urapPFY5NRcmYKFqWl8S5RobBRNg0PbNT3a1dDGH8RlM3LysTRjIBwAG6xrzJt11BAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geoctub', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACESURBVBjTdc+xDcMwDARA7cKKHTuWX37LHaw+vQbQAJomA7j2DB7FhCMFCZB8pxPwJEv5kQcZW+3HencRBekak4aaMQIi8YJdAQ1CMeE0UBkuaLMETklQ9Alhka0JzzXWqLVBuQYPpWcVuBbZjZafNRYcDk9o/b07bvhINz+/zxu1/M0FSRcmAk/HaIcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geohype', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACKSURBVBjTbU+rFQQhDKQSDDISEYuMREfHx6eHKMpYuf5qoIQt5bgDblfcuJk3nySEhSvceDV3c/ejT66lspopE9pXyIlkCrHMBACpu1DClekQAREi/loviCnF/NhRwJLaQ6hVhPjB8bOCsjlnNnNl0FWJVWxAqGzHONRHpu5Ml+nQ+8GzNW9n+Is3eg80Nk0iiwoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geomixture', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFAAAAAAAAKysrVVUA//8B//8AgICAqqpV398gv79A////VYJtlwAAAAF0Uk5TAEDm2GYAAAABYktHRApo0PRWAAAACXBIWXMAAABIAAAASABGyWs+AAAAXklEQVQI12NgwASCQsJCgoZAhoADq1tKIJAhEpDGxpYIZKgxsLElgBhibAkOCY4gKTaGkPRGIEPUIYEBrEaAIY0tDawmgYWNgREkkjCVjRWkWCUhLY0FJCIIBljsBgCZTAykgaRiRwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geopcon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACJSURBVBjTdc+hGcQwCIZhhjl/rkgWiECj8XgGyAbZoD5LdIRMkEnKkV575n75Pp8AgLU54dmh6mauelyAL2Qzxfe2sklioq6FacFAcRFXYhwJHdU5rDD2hEYB/CmoJVRMiIJqgtENuoqA8ltAlYAqRH4d1tGkwzTqN2gA7Nv+fUwkgZ/3mg34txM+szzATJS1HQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geosphere', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACFSURBVBjTdY+xEcQwCAQp5QNFjpQ5vZACFBFTADFFfKYCXINzlUAJruXll2ekxDAEt9zcANFbXb2mqm56dxsymAH0yccAJaeNi0h5QGyfxGJmivMPjj0nmLsbRmyFCss3rlbpcUjfS8wLUNRcJyCF6uqg2IvYCnoKC7f1kSbA6riTz7evfwj3Ml+H3KBqAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geotrap', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB5SURBVBjTbY+hFYAwDETZB1OJi4yNPp0JqjtAZ2AELL5DdABmIS2PtLxHXH7u7l2W5W+uHMHpGiCHLYR1yw4SCZMIXBOJWVSjK7QDDAu4g8OBmAKK4sAEDdR3rw8YmcUcrEijKKhl7lN1IQPn9ExlgU6/WEyc75+5AYK0KY5oHBDfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geotubeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACBSURBVBjTdc+hEcQwDARA12P6QFBQ9LDwcXEVkA7SQTr4BlJBakgpsWdsh/wfux3NSCrlV86Mlrxmz1pBWq3bAHwETohxABVmDZADQp1BE+wDNnGywzHgmHDOreJNTDH3Xn3CVX0dpu2MHcIFBkYp/gKsQ8SCQ72V+36/+2aWf3kAQfgshnpXF0wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geoxtru', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABcSURBVBjTY2AgEmhpeZV56vmWwQW00QUYwAJlSAI6XmVqukh8PT1bT03PchhXX09Pr9wQIQDiJ+ZowgWAXD3bck+QQDlCQTkDQgCoxA/ERBKwhbDglgA1lDMQDwCc/Rvq8nYsWgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geobbox', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTVc+hEYAwDAXQLlNRF1tVGxn9NRswQiSSCdgDyQBM0FlIIb2WuL77uf6E8E0N02wKYRwDciTKREVvB04GuZSyOMCABRB1WGzF3uDNQTvs/RcDtJXT4fSEXA5XoiQt0ttVSm8Co2psIOvoimjAOqBmFtH5wEP2373TPIvTK1nrpULXAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geoconeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB4SURBVBjTdc6hEcAgDAXQbFNZXHQkFlkd/30myAIMwAws0gmYpVzvoFyv/S5P/B+izzQ387ZA2pkDnvsU1SQLVIFrOM4JFmEaYp2gCQbmPEGODhJ8jt7Am47hwgrzInGAifa/elUZnQLY00iU30BZAV+BWi2VfnIBv1osbHH8jX0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} +${img('geoeltu', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTdY+hFYUwDEU7xq9CIXC4uNjY6KczQXeoYgVMR2ABRmCGjvIp/6dgiEruueedvBDuOR57LQnKyc8CJmKO+N8bieIUPtmBWjIIx8XDBHYCipsnql1g2D0UP2OoDqwBncf+RdZmzFMHizRjog7KZYzawd4Ay93lEAPWR7WAvNbwMl/XwSxBV8qCjgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geomaterial', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAbElEQVQoz62QMRbAIAhDP319Xon7j54qHSyCtaMZFCUkRjgDIdRU9yZUCfg8ut5aAHdcxtoNurmgA3ABNKIR9KimhSukPe2qxcCYC0pfFXx/aFWo7i42KKItOpopqvvnLzJmtlZTS7EfGAfwAM4EQbLIGV0sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geoparab', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbY+xDYAwDAQ9UAp3X7p0m9o9dUZgA9oMwAjpMwMzMAnYBAQSX9mn9+tN9KOtzsWsLOvYCziUGNX3nnCLJRzKPgeYrhPW7FJNLUB3YJazYKQKTnBaxgXRzNmJcrt7XCHQp9kEB1wfELEir/KGj4Foh8A+/zW1nf51AFabKZuWK+mNAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geopgon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABwSURBVBjTY2AgDlwAAzh3sX1sPRDEeuwDc+8V2dsHgQQ8LCzq74HkLSzs7Yva2tLt7S3sN4MNiDUGKQmysCi6BzWkzcI+PdY+aDPCljZlj1iFOUjW1tvHLjYuQhJIt5/DcAFZYLH9YnSn7iPST9gAACbsJth21haFAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geotorus', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTjY+hFcMwDEQ9SkFggXGIoejhw+LiGkBDlHoAr+AhgjNL5byChuXeE7gvPelUyjOds/f5Zw0ggfj5KVCPMBWeyx+SbQ1XUriAC2XfpWWxjQQEZasRtRHiCUAj3qN4JaolUJppzh4q7dUTdHFXW/tH9OuswWm3nI7tc08+/eGLl758ey9KpKrNOQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('geotrd1', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbc6xDQMhDAVQ9qH6lUtal65/zQ5IDMAMmYAZrmKGm4FJzlEQQUo+bvwkG4fwm9lbodV7w40Y4WGfSxQiXiJlQfZOjWRb8Ioi3tKuBQMCo7+9N72BzPsfAuoTdUP9QN8wgOQwvsfWmHzpeT5BKydMNW0nhJGvGf7mAc5WKO9e5N2dAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} +${img('geotube', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTRc+tEcAwCAXgLFNbWeSzSDQazw5doWNUZIOM0BEyS/NHy10E30HyklKvWnJ+0le3sJoKn3X2z7GRuvG++YRyMMDt0IIKUXMzxbnugJi5m9K1gNnGBOUFElAWGMaKIKI4xoQggl00gT+A9hXWgDwnfqgsHRAx2m+8bfjfdyrx5AtsSjpwu+M2RgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} +${img('evepoints', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABJQTFRF////n4mJcEdKRDMzcEdH////lLE/CwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAABIAAAASABGyWs+AAAAI0lEQVQI12NgIAowIpgKEJIZLiAgAKWZGQzQ9UGlWIizBQgAN4IAvGtVrTcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDktMDJUMTU6MDQ6MzgrMDI6MDDPyc7hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTA5LTAyVDE1OjA0OjM4KzAyOjAwvpR2XQAAAABJRU5ErkJggg==')} +${img('evetrack', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAqElEQVQoz32RMQrCQBBFf4IgSMB0IpGkMpVHCFh7BbHIGTyVhU0K8QYewEKsbVJZaCUiPAsXV8Puzhaz7H8zs5+JUDjikLilQr5zpCRl5xMXZNScQE5gSMGaz70jjUAJcw5c3UBMTsUe+9Kzf065SbropeLXimWfDIgoab/tOyPGzOhz53+oSWcSGh7UdB2ZNKXBZdgAuUdEKJYmrEILyVgG6pE2tEHgDfe42rbjYzSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTAyVDE1OjA0OjQ3KzAyOjAwM0S3EQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wOS0wMlQxNTowNDo0NyswMjowMEIZD60AAAAASUVORK5CYII=')} +.jsroot .geovis_this { background-color: lightgreen; } +.jsroot .geovis_daughters { background-color: lightblue; } +.jsroot .geovis_all { background-color: yellow; }`); +} + - /** - * Creates a panel that holds controllers. - * @example - * new GUI(); - * new GUI( { container: document.getElementById( 'custom' ) } ); - * - * @param {object} [options] - * @param {boolean} [options.autoPlace=true] - * Adds the GUI to `document.body` and fixes it to the top right of the page. - * - * @param {HTMLElement} [options.container] - * Adds the GUI to this DOM element. Overrides `autoPlace`. - * - * @param {number} [options.width=245] - * Width of the GUI in pixels, usually set when name labels become too long. Note that you can make - * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }` - * - * @param {string} [options.title=Controls] - * Name to display in the title bar. - * - * @param {boolean} [options.closeFolders=false] - * Pass `true` to close all folders in this GUI by default. - * - * @param {boolean} [options.injectStyles=true] - * Injects the default stylesheet into the page if this is the first GUI. - * Pass `false` to use your own stylesheet. - * - * @param {number} [options.touchStyles=true] - * Makes controllers larger on touch devices. Pass `false` to disable touch styles. - * - * @param {GUI} [options.parent] - * Adds this GUI as a child in another GUI. Usually this is done for you by `addFolder()`. - * - */ - constructor( { - parent, - autoPlace = parent === undefined, - container, - width, - title = 'Controls', - closeFolders = false, - injectStyles = true, - touchStyles = true - } = {} ) { +/** @summary Create geo painter + * @private */ +function createGeoPainter(dom, obj, opt) { + injectGeoStyle(); - /** - * The GUI containing this folder, or `undefined` if this is the root GUI. - * @type {GUI} - */ - this.parent = parent; + geoCfg('GradPerSegm', settings.GeoGradPerSegm); + geoCfg('CompressComp', settings.GeoCompressComp); - /** - * The top level GUI containing this folder, or `this` if this is the root GUI. - * @type {GUI} - */ - this.root = parent ? parent.root : this; + const painter = new TGeoPainter(dom, obj); - /** - * The list of controllers and folders contained by this GUI. - * @type {Array} - */ - this.children = []; + painter.decodeOptions(opt); // indicator of initialization - /** - * The list of controllers contained by this GUI. - * @type {Array} - */ - this.controllers = []; + return painter; +} - /** - * The list of folders contained by this GUI. - * @type {Array} - */ - this.folders = []; - /** - * Used to determine if the GUI is closed. Use `gui.open()` or `gui.close()` to change this. - * @type {boolean} - */ - this._closed = false; +/** @summary provide menu for geo object + * @private */ +function provideMenu(menu, item, hpainter) { + if (!item._geoobj) + return false; - /** - * Used to determine if the GUI is hidden. Use `gui.show()` or `gui.hide()` to change this. - * @type {boolean} - */ - this._hidden = false; + const obj = item._geoobj, vol = item._volume, + iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)); - /** - * The outermost container element. - * @type {HTMLElement} - */ - this.domElement = document.createElement( 'div' ); - this.domElement.classList.add( 'lil-gui' ); + if (!vol && !iseve) + return false; - /** - * The DOM element that contains the title. - * @type {HTMLElement} - */ - this.$title = document.createElement( 'div' ); - this.$title.classList.add( 'title' ); - this.$title.setAttribute( 'role', 'button' ); - this.$title.setAttribute( 'aria-expanded', true ); - this.$title.setAttribute( 'tabindex', 0 ); + menu.separator(); - this.$title.addEventListener( 'click', () => this.openAnimated( this._closed ) ); - this.$title.addEventListener( 'keydown', e => { - if ( e.code === 'Enter' || e.code === 'Space' ) { - e.preventDefault(); - this.$title.click(); - } - } ); + const scanEveVisible = (obj2, arg, skip_this) => { + if (!arg) + arg = { visible: 0, hidden: 0 }; - // enables :active pseudo class on mobile - this.$title.addEventListener( 'touchstart', () => {}, { passive: true } ); + if (!skip_this) { + if (arg.assign !== undefined) + obj2.fRnrSelf = arg.assign; + else if (obj2.fRnrSelf) + arg.vis++; + else + arg.hidden++; + } - /** - * The DOM element that contains children. - * @type {HTMLElement} - */ - this.$children = document.createElement( 'div' ); - this.$children.classList.add( 'children' ); + if (obj2.fElements) { + for (let n = 0; n < obj2.fElements.arr.length; ++n) + scanEveVisible(obj2.fElements.arr[n], arg, false); + } - this.domElement.appendChild( this.$title ); - this.domElement.appendChild( this.$children ); + return arg; + }, toggleEveVisibility = arg => { + if (arg === 'self') { + obj.fRnrSelf = !obj.fRnrSelf; + item._icon = item._icon.split(' ')[0] + provideVisStyle(obj); + hpainter.updateTreeNode(item); + } else { + scanEveVisible(obj, { assign: (arg === 'true') }, true); + hpainter.forEachItem(m => { + // update all child items + if (m._geoobj && m._icon) { + m._icon = item._icon.split(' ')[0] + provideVisStyle(m._geoobj); + hpainter.updateTreeNode(m); + } + }, item); + } - this.title( title ); + findItemWithGeoPainter(item, true); + }, toggleMenuBit = arg => { + toggleGeoBit(vol, arg); + const newname = item._icon.split(' ')[0] + provideVisStyle(vol); + hpainter.forEachItem(m => { + // update all items with that volume + if (item._volume === m._volume) { + m._icon = newname; + hpainter.updateTreeNode(m); + } + }); - if ( this.parent ) { + hpainter.updateTreeNode(item); + findItemWithGeoPainter(item, true); + }, drawitem = findItemWithGeoPainter(item), + fullname = drawitem ? hpainter.itemFullName(item, drawitem) : ''; - this.parent.children.push( this ); - this.parent.folders.push( this ); + if ((item._geoobj._typename.indexOf(clTGeoNode) === 0) && drawitem) { + menu.add('Focus', () => { + if (drawitem && isFunc(drawitem._painter?.focusOnItem)) + drawitem._painter.focusOnItem(fullname); + }); + } - this.parent.$children.appendChild( this.domElement ); + if (iseve) { + menu.addchk(obj.fRnrSelf, 'Visible', 'self', toggleEveVisibility); + const res = scanEveVisible(obj, undefined, true); + if (res.hidden + res.visible > 0) + menu.addchk((res.hidden === 0), 'Daughters', res.hidden ? 'true' : 'false', toggleEveVisibility); + } else { + const stack = drawitem?._painter?.getClones()?.findStackByName(fullname), + phys_vis = stack ? drawitem._painter.getClones().getPhysNodeVisibility(stack) : null, + is_visible = testGeoBit(vol, geoBITS.kVisThis); - // Stop the constructor early, everything onward only applies to root GUI's - return; + menu.addchk(testGeoBit(vol, geoBITS.kVisNone), 'Invisible', geoBITS.kVisNone, toggleMenuBit); + if (stack) { + const changePhysVis = arg => { + drawitem._painter.getClones().setPhysNodeVisibility(stack, (arg === 'off') ? false : arg); + findItemWithGeoPainter(item, true); + }; - } + menu.sub('Physical vis', 'Physical node visibility - only for this instance'); + menu.addchk(phys_vis?.visible, 'on', 'on', changePhysVis, 'Enable visibility of phys node'); + menu.addchk(phys_vis && !phys_vis.visible, 'off', 'off', changePhysVis, 'Disable visibility of physical node'); + menu.add('reset', 'clear', changePhysVis, 'Reset custom visibility of physical node'); + menu.add('reset all', 'clearall', changePhysVis, 'Reset all custom settings for all nodes'); + menu.endsub(); + } - this.domElement.classList.add( 'root' ); + menu.addchk(is_visible, 'Logical vis', + geoBITS.kVisThis, toggleMenuBit, 'Logical node visibility - all instances'); + menu.addchk(testGeoBit(vol, geoBITS.kVisDaughters), 'Daughters', + geoBITS.kVisDaughters, toggleMenuBit, 'Logical node daugthers visibility'); + } - if ( touchStyles ) { - this.domElement.classList.add( 'allow-touch-styles' ); - } + return true; +} - // Inject stylesheet if we haven't done that yet - if ( !stylesInjected && injectStyles ) { - _injectStyles( stylesheet ); - stylesInjected = true; - } +let createItem = null; - if ( container ) { +/** @summary create list entity for geo object + * @private */ +function createList(parent, lst, name, title) { + if (!lst?.arr?.length) + return; - container.appendChild( this.domElement ); + const list_item = { + _name: name, + _kind: getKindForType(clTList), + _title: title, + _more: true, + _geoobj: lst, + _parent: parent, + _get(item /* , itemname */) { + return Promise.resolve(item._geoobj || null); + }, + _expand(node, lst2) { + // only childs - } else if ( autoPlace ) { + if (lst2.fVolume) + lst2 = lst2.fVolume.fNodes; - this.domElement.classList.add( 'autoPlace' ); - document.body.appendChild( this.domElement ); + if (!lst2.arr) + return false; - } + node._childs = []; - if ( width ) { - this.domElement.style.setProperty( '--width', width + 'px' ); - } + checkDuplicates(null, lst2.arr); - this._closeFolders = closeFolders; + for (const n in lst2.arr) + createItem(node, lst2.arr[n]); - } + return true; + } + }; - /** - * Adds a controller to the GUI, inferring controller type using the `typeof` operator. - * @example - * gui.add( object, 'property' ); - * gui.add( object, 'number', 0, 100, 1 ); - * gui.add( object, 'options', [ 1, 2, 3 ] ); - * - * @param {object} object The object the controller will modify. - * @param {string} property Name of the property to control. - * @param {number|object|Array} [$1] Minimum value for number controllers, or the set of - * selectable values for a dropdown. - * @param {number} [max] Maximum value for number controllers. - * @param {number} [step] Step value for number controllers. - * @returns {Controller} - */ - add( object, property, $1, max, step ) { + if (!parent._childs) + parent._childs = []; + parent._childs.push(list_item); +} - if ( Object( $1 ) === $1 ) { - return new OptionController( this, object, property, $1 ); +/** @summary Expand geo object + * @private */ +function expandGeoObject(parent, obj) { + injectGeoStyle(); - } + if (!parent || !obj) + return false; - const initialValue = object[ property ]; + const isnode = (obj._typename.indexOf(clTGeoNode) === 0), + isvolume = (obj._typename.indexOf(clTGeoVolume) === 0), + ismanager = (obj._typename === clTGeoManager), + iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)), + isoverlap = (obj._typename === clTGeoOverlap); - switch ( typeof initialValue ) { + if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) + return false; - case 'number': + if (parent._childs) + return true; - return new NumberController( this, object, property, $1, max, step ); + if (ismanager) { + createList(parent, obj.fMaterials, 'Materials', 'list of materials'); + createList(parent, obj.fMedia, 'Media', 'list of media'); + createList(parent, obj.fTracks, 'Tracks', 'list of tracks'); + createList(parent, obj.fOverlaps, 'Overlaps', 'list of detected overlaps'); + createItem(parent, obj.fMasterVolume); + return true; + } - case 'boolean': + if (isoverlap) { + createItem(parent, obj.fVolume1); + createItem(parent, obj.fVolume2); + createItem(parent, obj.fMarker, 'Marker'); + return true; + } - return new BooleanController( this, object, property ); + let volume, subnodes, shape; - case 'string': + if (iseve) { + subnodes = obj.fElements?.arr; + shape = obj.fShape; + } else { + volume = isnode ? obj.fVolume : obj; + subnodes = volume?.fNodes?.arr; + shape = volume?.fShape; + } - return new StringController( this, object, property ); + if (!subnodes && (shape?._typename === clTGeoCompositeShape) && shape?.fNode) { + if (!parent._childs) { + createItem(parent, shape.fNode.fLeft, 'Left'); + createItem(parent, shape.fNode.fRight, 'Right'); + } - case 'function': + return true; + } - return new FunctionController( this, object, property ); + if (!subnodes) + return false; - } + checkDuplicates(obj, subnodes); - console.error( `gui.add failed - property:`, property, ` - object:`, object, ` - value:`, initialValue ); + for (let i = 0; i < subnodes.length; ++i) + createItem(parent, subnodes[i]); - } + return true; +} - /** - * Adds a color controller to the GUI. - * @example - * params = { - * cssColor: '#ff00ff', - * rgbColor: { r: 0, g: 0.2, b: 0.4 }, - * customRange: [ 0, 127, 255 ], - * }; - * - * gui.addColor( params, 'cssColor' ); - * gui.addColor( params, 'rgbColor' ); - * gui.addColor( params, 'customRange', 255 ); - * - * @param {object} object The object the controller will modify. - * @param {string} property Name of the property to control. - * @param {number} rgbScale Maximum value for a color channel when using an RGB color. You may - * need to set this to 255 if your colors are too bright. - * @returns {Controller} - */ - addColor( object, property, rgbScale = 1 ) { - return new ColorController( this, object, property, rgbScale ); - } - /** - * Adds a folder to the GUI, which is just another GUI. This method returns - * the nested GUI so you can add controllers to it. - * @example - * const folder = gui.addFolder( 'Position' ); - * folder.add( position, 'x' ); - * folder.add( position, 'y' ); - * folder.add( position, 'z' ); - * - * @param {string} title Name to display in the folder's title bar. - * @returns {GUI} - */ - addFolder( title ) { - const folder = new GUI( { parent: this, title } ); - if ( this.root._closeFolders ) folder.close(); - return folder; - } +/** @summary create hierarchy item for geo object + * @private */ +createItem = function(node, obj, name) { + const sub = { + _kind: getKindForType(obj._typename), + _name: name || getObjectName(obj), + _title: obj.fTitle, + _parent: node, + _geoobj: obj, + _get(item /* ,itemname */) { + // mark object as belong to the hierarchy, require to + if (item._geoobj) + item._geoobj.$geoh = true; + return Promise.resolve(item._geoobj); + } + }; + let volume, shape, subnodes, iseve = false; - /** - * Recalls values that were saved with `gui.save()`. - * @param {object} obj - * @param {boolean} recursive Pass false to exclude folders descending from this GUI. - * @returns {this} - */ - load( obj, recursive = true ) { + if (obj._typename === 'TGeoMaterial') + sub._icon = 'img_geomaterial'; + else if (obj._typename === 'TGeoMedium') + sub._icon = 'img_geomedium'; + else if (obj._typename === 'TGeoMixture') + sub._icon = 'img_geomixture'; + else if ((obj._typename.indexOf(clTGeoNode) === 0) && obj.fVolume) { + sub._title = 'node:' + obj._typename; + if (obj.fTitle) + sub._title += ' ' + obj.fTitle; + volume = obj.fVolume; + } else if (obj._typename.indexOf(clTGeoVolume) === 0) + volume = obj; + else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { + iseve = true; + shape = obj.fShape; + subnodes = obj.fElements ? obj.fElements.arr : null; + } else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) + shape = obj; - if ( obj.controllers ) { + if (volume) { + shape = volume.fShape; + subnodes = volume.fNodes ? volume.fNodes.arr : null; + } - this.controllers.forEach( c => { + if (volume || shape || subnodes) { + if (volume) + sub._volume = volume; - if ( c instanceof FunctionController ) return; + if (subnodes) { + sub._more = true; + sub._expand = expandGeoObject; + } else if (shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { + sub._more = true; + sub._shape = shape; + sub._expand = function(node2 /* , obj */) { + createItem(node2, node2._shape.fNode.fLeft, 'Left'); + createItem(node2, node2._shape.fNode.fRight, 'Right'); + return true; + }; + } - if ( c._name in obj.controllers ) { - c.load( obj.controllers[ c._name ] ); - } + if (!sub._title && (obj._typename !== clTGeoVolume)) + sub._title = obj._typename; - } ); + if (shape) { + if (sub._title === '') + sub._title = shape._typename; - } + sub._icon = getShapeIcon(shape); + } else + sub._icon = sub._more ? 'img_geocombi' : 'img_geobbox'; - if ( recursive && obj.folders ) { + if (volume) + sub._icon += provideVisStyle(volume); + else if (iseve) + sub._icon += provideVisStyle(obj); - this.folders.forEach( f => { + sub._menu = provideMenu; + sub._icon_click = browserIconClick; + } - if ( f._title in obj.folders ) { - f.load( obj.folders[ f._title ] ); - } + if (!node._childs) + node._childs = []; - } ); + if (!sub._name) { + if (isStr(node._name)) { + sub._name = node._name; + if (sub._name.at(-1) === 's') + sub._name = sub._name.slice(0, sub._name.length - 1); + sub._name += '_' + node._childs.length; + } else + sub._name = 'item_' + node._childs.length; + } - } + node._childs.push(sub); - return this; + return sub; +}; - } +/** @summary Draw dummy geometry + * @private */ +async function drawDummy3DGeom(painter) { + const shape = create$1(clTNamed); + shape._typename = clTGeoBBox; + shape.fDX = 1e-10; + shape.fDY = 1e-10; + shape.fDZ = 1e-10; + shape.fShapeId = 1; + shape.fShapeBits = 0; + shape.fOrigin = [0, 0, 0]; - /** - * Returns an object mapping controller names to values. The object can be passed to `gui.load()` to - * recall these values. - * @example - * { - * controllers: { - * prop1: 1, - * prop2: 'value', - * ... - * }, - * folders: { - * folderName1: { controllers, folders }, - * folderName2: { controllers, folders } - * ... - * } - * } - * - * @param {boolean} recursive Pass false to exclude folders descending from this GUI. - * @returns {object} - */ - save( recursive = true ) { + const obj = Object.assign(create$1(clTNamed), { + _typename: clTEveGeoShapeExtract, + fTrans: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + fShape: shape, fRGBA: [0, 0, 0, 0], fElements: null, fRnrSelf: false + }), + pp = painter.getPadPainter(), + pad = pp?.getRootPad(true), + opt = 'dummy;' + (pad?.fFillColor && (pad?.fFillStyle > 1000) ? 'bkgr_' + pad.fFillColor : ''); - const obj = { - controllers: {}, - folders: {} - }; + return TGeoPainter.draw(pp || painter.getDom(), obj, opt); +} - this.controllers.forEach( c => { +/** @summary Direct draw function for TAxis3D + * @private */ +function drawAxis3D() { + const main = this.getMainPainter(); - if ( c instanceof FunctionController ) return; + if (isFunc(main?.setAxesDraw)) + return main.setAxesDraw(true); - if ( c._name in obj.controllers ) { - throw new Error( `Cannot save GUI with duplicate property "${c._name}"` ); - } + console.error('no geometry painter found to toggle TAxis3D drawing'); +} - obj.controllers[ c._name ] = c.save(); +/** @summary Build three.js model for given geometry object + * @param {Object} obj - TGeo-related object + * @param {Object} [opt] - options + * @param {Number} [opt.vislevel] - visibility level like TGeoManager, when not specified - show all + * @param {Number} [opt.numnodes=1000] - maximal number of visible nodes + * @param {Number} [opt.numfaces=100000] - approx maximal number of created triangles + * @param {Number} [opt.instancing=-1] - <0 disable use of InstancedMesh, =0 only for large geometries, >0 enforce usage of InstancedMesh + * @param {boolean} [opt.doubleside=false] - use double-side material + * @param {boolean} [opt.wireframe=false] - show wireframe for created shapes + * @param {boolean} [opt.transparency=0] - make nodes transparent + * @param {boolean} [opt.dflt_colors=false] - use default ROOT colors + * @param {boolean} [opt.set_names=true] - set names to all Object3D instances + * @param {boolean} [opt.set_origin=false] - set TGeoNode/TGeoVolume as Object3D.userData + * @return {object} Object3D with created model + * @example + * import { build } from 'https://root.cern/js/latest/modules/geom/TGeoPainter.mjs'; + * let obj3d = build(obj); + * // this is three.js object and can be now inserted in the scene + */ +function build(obj, opt) { + return TGeoPainter.build3d(obj, opt); +} - } ); +var TGeoPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +ClonedNodes: ClonedNodes, +GeoDrawingControl: GeoDrawingControl, +TGeoPainter: TGeoPainter, +build: build, +createGeoPainter: createGeoPainter, +drawAxis3D: drawAxis3D, +drawDummy3DGeom: drawDummy3DGeom, +expandGeoObject: expandGeoObject, +produceRenderOrder: produceRenderOrder +}); - if ( recursive ) { +const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObject', + clTStreamerSTL = 'TStreamerSTL', clTStreamerInfoList = 'TStreamerInfoList', + clTDirectory = 'TDirectory', clTDirectoryFile = 'TDirectoryFile', + clTQObject = 'TQObject', clTBasket = 'TBasket', clTDatime = 'TDatime', + nameStreamerInfo = 'StreamerInfo', - this.folders.forEach( f => { + kChar = 1, kShort = 2, kInt = 3, kLong = 4, kFloat = 5, kCounter = 6, + kCharStar = 7, kDouble = 8, kDouble32 = 9, kLegacyChar = 10, + kUChar = 11, kUShort = 12, kUInt = 13, kULong = 14, kBits = 15, + kLong64 = 16, kULong64 = 17, kBool = 18, kFloat16 = 19, - if ( f._title in obj.folders ) { - throw new Error( `Cannot save GUI with duplicate folder "${f._title}"` ); - } + kBase = 0, kOffsetL = 20, kOffsetP = 40, + kObject = 61, kAny = 62, kObjectp = 63, kObjectP = 64, kTString = 65, + kTObject = 66, kTNamed = 67, kAnyp = 68, kAnyP = 69, - obj.folders[ f._title ] = f.save(); + /* kAnyPnoVT: 70, */ + kSTLp = 71, + /* kSkip = 100, kSkipL = 120, kSkipP = 140, kConv = 200, kConvL = 220, kConvP = 240, */ + kSTL = 300, /* kSTLstring = 365, */ - } ); + kStreamer = 500, kStreamLoop = 501, - } + kMapOffset = 2, kByteCountMask = 0x40000000, kNewClassTag = 0xFFFFFFFF, kClassMask = 0x80000000, - return obj; + // constants of bits in version + kStreamedMemberWise = BIT(14), - } + // constants used for coding type of STL container + kNotSTL = 0, kSTLvector = 1, kSTLlist = 2, kSTLdeque = 3, kSTLmap = 4, kSTLmultimap = 5, + kSTLset = 6, kSTLmultiset = 7, kSTLbitset = 8, + // kSTLforwardlist = 9, kSTLunorderedset = 10, kSTLunorderedmultiset = 11, kSTLunorderedmap = 12, + // kSTLunorderedmultimap = 13, kSTLend = 14 + kBaseClass = 'BASE', - /** - * Opens a GUI or folder. GUI and folders are open by default. - * @param {boolean} open Pass false to close - * @returns {this} - * @example - * gui.open(); // open - * gui.open( false ); // close - * gui.open( gui._closed ); // toggle - */ - open( open = true ) { + // name of base IO types + BasicTypeNames = [kBaseClass, 'char', 'short', 'int', 'long', 'float', 'int', 'const char*', 'double', 'Double32_t', + 'char', 'unsigned char', 'unsigned short', 'unsigned', 'unsigned long', 'unsigned', 'Long64_t', 'ULong64_t', 'bool', 'Float16_t'], - this._setClosed( !open ); + // names of STL containers + StlNames = ['', 'vector', 'list', 'deque', 'map', 'multimap', 'set', 'multiset', 'bitset'], - this.$title.setAttribute( 'aria-expanded', !this._closed ); - this.domElement.classList.toggle( 'closed', this._closed ); + // TObject bits + kIsReferenced = BIT(4), kHasUUID = BIT(5), - return this; + // gap in http which can be merged into single http request + kMinimalHttpGap = 128; - } - /** - * Closes the GUI. - * @returns {this} - */ - close() { - return this.open( false ); - } +/** @summary Custom streamers for root classes + * @desc map of user-streamer function like func(buf,obj) + * or alias (classname) which can be used to read that function + * or list of read functions + * @private */ +/* eslint-disable one-var */ +const CustomStreamers = { + TObject(buf, obj) { + obj.fUniqueID = buf.ntou4(); + obj.fBits = buf.ntou4(); + if (obj.fBits & kIsReferenced) + buf.ntou2(); // skip pid + }, - _setClosed( closed ) { - if ( this._closed === closed ) return; - this._closed = closed; - this._callOnOpenClose( this ); - } + TNamed: [ + { + basename: clTObject, base: 1, + func(buf, obj) { + if (!obj._typename) + obj._typename = clTNamed; + buf.classStreamer(obj, clTObject); + } + }, + { name: 'fName', func(buf, obj) { obj.fName = buf.readTString(); } }, + { name: 'fTitle', func(buf, obj) { obj.fTitle = buf.readTString(); } } + ], - /** - * Shows the GUI after it's been hidden. - * @param {boolean} show - * @returns {this} - * @example - * gui.show(); - * gui.show( false ); // hide - * gui.show( gui._hidden ); // toggle - */ - show( show = true ) { + TObjString: [ + { + basename: clTObject, base: 1, + func(buf, obj) { + if (!obj._typename) + obj._typename = clTObjString; + buf.classStreamer(obj, clTObject); + } + }, + { name: 'fString', func(buf, obj) { obj.fString = buf.readTString(); } } + ], - this._hidden = !show; + TClonesArray(buf, list) { + if (!list._typename) + list._typename = clTClonesArray; + list.$kind = clTClonesArray; + list.name = ''; + const ver = buf.last_read_version; + if (ver > 2) + buf.classStreamer(list, clTObject); + if (ver > 1) + list.name = buf.readTString(); + let classv = buf.readTString(), clv = 0; + const pos = classv.lastIndexOf(';'); - this.domElement.style.display = this._hidden ? 'none' : ''; + if (pos > 0) { + clv = Number.parseInt(classv.slice(pos + 1)); + classv = classv.slice(0, pos); + } - return this; + let nobjects = buf.ntou4(); + if (nobjects < 0) + nobjects = -nobjects; // for backward compatibility - } + list.arr = new Array(nobjects); + list.fLast = nobjects - 1; + list.fLowerBound = buf.ntou4(); - /** - * Hides the GUI. - * @returns {this} - */ - hide() { - return this.show( false ); - } + let streamer = buf.fFile.getStreamer(classv, { val: clv }); + streamer = buf.fFile.getSplittedStreamer(streamer); - openAnimated( open = true ) { + if (!streamer) + console.log(`Cannot get member-wise streamer for ${classv}:${clv}`); + else { + // create objects + for (let n = 0; n < nobjects; ++n) + list.arr[n] = { _typename: classv }; - // set state immediately - this._setClosed( !open ); + // call streamer for all objects member-wise + for (let k = 0; k < streamer.length; ++k) { + for (let n = 0; n < nobjects; ++n) + streamer[k].func(buf, list.arr[n]); + } + } + }, - this.$title.setAttribute( 'aria-expanded', !this._closed ); + TMap(buf, map) { + if (!map._typename) + map._typename = clTMap; + map.name = ''; + map.arr = []; + const ver = buf.last_read_version; + if (ver > 2) + buf.classStreamer(map, clTObject); + if (ver > 1) + map.name = buf.readTString(); - // wait for next frame to measure $children - requestAnimationFrame( () => { + const nobjects = buf.ntou4(); + // create objects + for (let n = 0; n < nobjects; ++n) { + const obj = { _typename: 'TPair' }; + obj.first = buf.readObjectAny(); + obj.second = buf.readObjectAny(); + if (obj.first) + map.arr.push(obj); + } + }, - // explicitly set initial height for transition - const initialHeight = this.$children.clientHeight; - this.$children.style.height = initialHeight + 'px'; + TTreeIndex(buf, obj) { + const ver = buf.last_read_version; + obj._typename = 'TTreeIndex'; + buf.classStreamer(obj, 'TVirtualIndex'); + obj.fMajorName = buf.readTString(); + obj.fMinorName = buf.readTString(); + obj.fN = buf.ntoi8(); + obj.fIndexValues = buf.readFastArray(obj.fN, kLong64); + if (ver > 1) + obj.fIndexValuesMinor = buf.readFastArray(obj.fN, kLong64); + obj.fIndex = buf.readFastArray(obj.fN, kLong64); + }, - this.domElement.classList.add( 'transition' ); + TRefArray(buf, obj) { + obj._typename = 'TRefArray'; + buf.classStreamer(obj, clTObject); + obj.name = buf.readTString(); + const nobj = buf.ntoi4(); + obj.fLast = nobj - 1; + obj.fLowerBound = buf.ntoi4(); + /* const pidf = */ buf.ntou2(); + obj.fUIDs = buf.readFastArray(nobj, kUInt); + }, - const onTransitionEnd = e => { - if ( e.target !== this.$children ) return; - this.$children.style.height = ''; - this.domElement.classList.remove( 'transition' ); - this.$children.removeEventListener( 'transitionend', onTransitionEnd ); - }; + TCanvas(buf, obj) { + obj._typename = clTCanvas; + buf.classStreamer(obj, clTPad); + obj.fDISPLAY = buf.readTString(); + obj.fDoubleBuffer = buf.ntoi4(); + obj.fRetained = buf.ntobool(); + obj.fXsizeUser = buf.ntoi4(); + obj.fYsizeUser = buf.ntoi4(); + obj.fXsizeReal = buf.ntoi4(); + obj.fYsizeReal = buf.ntoi4(); + obj.fWindowTopX = buf.ntoi4(); + obj.fWindowTopY = buf.ntoi4(); + obj.fWindowWidth = buf.ntoi4(); + obj.fWindowHeight = buf.ntoi4(); + obj.fCw = buf.ntou4(); + obj.fCh = buf.ntou4(); + obj.fCatt = buf.classStreamer({}, clTAttCanvas); + buf.ntou1(); // ignore b << TestBit(kMoveOpaque); + buf.ntou1(); // ignore b << TestBit(kResizeOpaque); + obj.fHighLightColor = buf.ntoi2(); + obj.fBatch = buf.ntobool(); + buf.ntou1(); // ignore b << TestBit(kShowEventStatus); + buf.ntou1(); // ignore b << TestBit(kAutoExec); + buf.ntou1(); // ignore b << TestBit(kMenuBar); + }, - this.$children.addEventListener( 'transitionend', onTransitionEnd ); + TObjArray(buf, list) { + if (!list._typename) + list._typename = clTObjArray; + list.$kind = clTObjArray; + list.name = ''; + const ver = buf.last_read_version; + if (ver > 2) + buf.classStreamer(list, clTObject); + if (ver > 1) + list.name = buf.readTString(); + const nobjects = buf.ntou4(); + let i = 0; + list.arr = new Array(nobjects); + list.fLast = nobjects - 1; + list.fLowerBound = buf.ntou4(); + while (i < nobjects) + list.arr[i++] = buf.readObjectAny(); + }, - // todo: this is wrong if children's scrollHeight makes for a gui taller than maxHeight - const targetHeight = !open ? 0 : this.$children.scrollHeight; + TPolyMarker3D(buf, marker) { + const ver = buf.last_read_version; + buf.classStreamer(marker, clTObject); + buf.classStreamer(marker, clTAttMarker); + marker.fN = buf.ntoi4(); + marker.fP = buf.readFastArray(marker.fN * 3, kFloat); + marker.fOption = buf.readTString(); + marker.fName = (ver > 1) ? buf.readTString() : clTPolyMarker3D; + }, - this.domElement.classList.toggle( 'closed', !open ); + TPolyLine3D(buf, obj) { + buf.classStreamer(obj, clTObject); + buf.classStreamer(obj, clTAttLine); + obj.fN = buf.ntoi4(); + obj.fP = buf.readFastArray(obj.fN * 3, kFloat); + obj.fOption = buf.readTString(); + }, - requestAnimationFrame( () => { - this.$children.style.height = targetHeight + 'px'; - } ); + TStreamerInfo(buf, obj) { + buf.classStreamer(obj, clTNamed); + obj.fCheckSum = buf.ntou4(); + obj.fClassVersion = buf.ntou4(); + obj.fElements = buf.readObjectAny(); + }, - } ); + TStreamerElement(buf, element) { + const ver = buf.last_read_version; + buf.classStreamer(element, clTNamed); + element.fType = buf.ntou4(); + element.fSize = buf.ntou4(); + element.fArrayLength = buf.ntou4(); + element.fArrayDim = buf.ntou4(); + element.fMaxIndex = buf.readFastArray((ver === 1) ? buf.ntou4() : 5, kUInt); + element.fTypeName = buf.readTString(); - return this; + if ((element.fType === kUChar) && ((element.fTypeName === 'Bool_t') || (element.fTypeName === 'bool'))) + element.fType = kBool; - } + element.fXmin = element.fXmax = element.fFactor = 0; + if (ver === 3) { + element.fXmin = buf.ntod(); + element.fXmax = buf.ntod(); + element.fFactor = buf.ntod(); + } else if ((ver > 3) && (element.fBits & BIT(6))) { // kHasRange + let p1 = element.fTitle.indexOf('['); + if ((p1 >= 0) && (element.fType > kOffsetP)) + p1 = element.fTitle.indexOf('[', p1 + 1); + const p2 = element.fTitle.indexOf(']', p1 + 1); - /** - * Change the title of this GUI. - * @param {string} title - * @returns {this} - */ - title( title ) { - /** - * Current title of the GUI. Use `gui.title( 'Title' )` to modify this value. - * @type {string} - */ - this._title = title; - this.$title.innerHTML = title; - return this; - } + if ((p1 >= 0) && (p2 >= p1 + 2)) { + const arr = element.fTitle.slice(p1 + 1, p2).split(','); + let nbits = 32; + if (!arr || arr.length < 2) + throw new Error(`Problem to decode range setting from streamer element title ${element.fTitle}`); - /** - * Resets all controllers to their initial values. - * @param {boolean} recursive Pass false to exclude folders descending from this GUI. - * @returns {this} - */ - reset( recursive = true ) { - const controllers = recursive ? this.controllersRecursive() : this.controllers; - controllers.forEach( c => c.reset() ); - return this; - } + if (arr.length === 3) + nbits = parseInt(arr[2]); + if (!Number.isInteger(nbits) || (nbits < 2) || (nbits > 32)) + nbits = 32; - /** - * Pass a function to be called whenever a controller in this GUI changes. - * @param {function({object:object, property:string, value:any, controller:Controller})} callback - * @returns {this} - * @example - * gui.onChange( event => { - * event.object // object that was modified - * event.property // string, name of property - * event.value // new value of controller - * event.controller // controller that was modified - * } ); - */ - onChange( callback ) { - /** - * Used to access the function bound to `onChange` events. Don't modify this value - * directly. Use the `gui.onChange( callback )` method instead. - * @type {Function} - */ - this._onChange = callback; - return this; - } + const parse_range = val => { + if (!val) + return 0; + if (val.indexOf('pi') < 0) + return parseFloat(val); + val = val.trim(); + let sign = 1; + if (val[0] === '-') { + sign = -1; + val = val.slice(1); + } + switch (val) { + case '2pi': + case '2*pi': + case 'twopi': return sign * 2 * Math.PI; + case 'pi/2': return sign * Math.PI / 2; + case 'pi/4': return sign * Math.PI / 4; + } + return sign * Math.PI; + }; - _callOnChange( controller ) { + element.fXmin = parse_range(arr[0]); + element.fXmax = parse_range(arr[1]); - if ( this.parent ) { - this.parent._callOnChange( controller ); - } + // avoid usage of 1 << nbits, while only works up to 32 bits + const bigint = ((nbits >= 0) && (nbits < 32)) ? Math.pow(2, nbits) : 0xffffffff; + if (element.fXmin < element.fXmax) + element.fFactor = bigint / (element.fXmax - element.fXmin); + else if (nbits < 15) + element.fXmin = nbits; + } + } + }, - if ( this._onChange !== undefined ) { - this._onChange.call( this, { - object: controller.object, - property: controller.property, - value: controller.getValue(), - controller - } ); - } - } + TStreamerBase(buf, elem) { + const ver = buf.last_read_version; + buf.classStreamer(elem, clTStreamerElement); + if (ver > 2) + elem.fBaseVersion = buf.ntou4(); + }, - /** - * Pass a function to be called whenever a controller in this GUI has finished changing. - * @param {function({object:object, property:string, value:any, controller:Controller})} callback - * @returns {this} - * @example - * gui.onFinishChange( event => { - * event.object // object that was modified - * event.property // string, name of property - * event.value // new value of controller - * event.controller // controller that was modified - * } ); - */ - onFinishChange( callback ) { - /** - * Used to access the function bound to `onFinishChange` events. Don't modify this value - * directly. Use the `gui.onFinishChange( callback )` method instead. - * @type {Function} - */ - this._onFinishChange = callback; - return this; - } + TStreamerSTL(buf, elem) { + buf.classStreamer(elem, clTStreamerElement); + elem.fSTLtype = buf.ntou4(); + elem.fCtype = buf.ntou4(); - _callOnFinishChange( controller ) { + if ((elem.fSTLtype === kSTLmultimap) && + ((elem.fTypeName.indexOf('std::set') === 0) || (elem.fTypeName.indexOf('set') === 0))) + elem.fSTLtype = kSTLset; - if ( this.parent ) { - this.parent._callOnFinishChange( controller ); - } + if ((elem.fSTLtype === kSTLset) && + ((elem.fTypeName.indexOf('std::multimap') === 0) || (elem.fTypeName.indexOf('multimap') === 0))) + elem.fSTLtype = kSTLmultimap; + }, - if ( this._onFinishChange !== undefined ) { - this._onFinishChange.call( this, { - object: controller.object, - property: controller.property, - value: controller.getValue(), - controller - } ); - } - } + TStreamerSTLstring(buf, elem) { + if (buf.last_read_version > 0) + buf.classStreamer(elem, clTStreamerSTL); + }, - /** - * Pass a function to be called when this GUI or its descendants are opened or closed. - * @param {function(GUI)} callback - * @returns {this} - * @example - * gui.onOpenClose( changedGUI => { - * console.log( changedGUI._closed ); - * } ); - */ - onOpenClose( callback ) { - this._onOpenClose = callback; - return this; - } + TList(buf, obj) { + // stream all objects in the list from the I/O buffer + if (!obj._typename) + obj._typename = this.typename; + obj.$kind = clTList; // all derived classes will be marked as well + if (buf.last_read_version > 3) { + buf.classStreamer(obj, clTObject); + obj.name = buf.readTString(); + const nobjects = buf.ntou4(); + obj.arr = new Array(nobjects); + obj.opt = new Array(nobjects); + for (let i = 0; i < nobjects; ++i) { + obj.arr[i] = buf.readObjectAny(); + obj.opt[i] = buf.readTString(); + } + } else { + obj.name = ''; + obj.arr = []; + obj.opt = []; + } + }, - _callOnOpenClose( changedGUI ) { - if ( this.parent ) { - this.parent._callOnOpenClose( changedGUI ); - } + THashList: clTList, + + TStreamerLoop(buf, elem) { + if (buf.last_read_version > 1) { + buf.classStreamer(elem, clTStreamerElement); + elem.fCountVersion = buf.ntou4(); + elem.fCountName = buf.readTString(); + elem.fCountClass = buf.readTString(); + } + }, - if ( this._onOpenClose !== undefined ) { - this._onOpenClose.call( this, changedGUI ); - } - } + TStreamerBasicPointer: 'TStreamerLoop', - /** - * Destroys all DOM elements and event listeners associated with this GUI - */ - destroy() { + TStreamerObject(buf, elem) { + if (buf.last_read_version > 1) + buf.classStreamer(elem, clTStreamerElement); + }, - if ( this.parent ) { - this.parent.children.splice( this.parent.children.indexOf( this ), 1 ); - this.parent.folders.splice( this.parent.folders.indexOf( this ), 1 ); - } + TStreamerBasicType: clTStreamerObject, + TStreamerObjectAny: clTStreamerObject, + TStreamerString: clTStreamerObject, + TStreamerObjectPointer: clTStreamerObject, - if ( this.domElement.parentElement ) { - this.domElement.parentElement.removeChild( this.domElement ); - } + TStreamerObjectAnyPointer(buf, elem) { + if (buf.last_read_version > 0) + buf.classStreamer(elem, clTStreamerElement); + }, - Array.from( this.children ).forEach( c => c.destroy() ); + TTree: { + name: '$file', + func(buf, obj) { + obj.$kind = clTTree; + obj.$file = buf.fFile; + } + }, - } + TBranch(buf, obj) { + const v = buf.last_read_version; + if (v > 9) + buf.streamClassMembers(obj, 'TBranch', v); + else { + buf.classStreamer(obj, clTNamed); + if (v > 7) + buf.classStreamer(obj, clTAttFill); + obj.fCompress = buf.ntoi4(); + obj.fBasketSize = buf.ntoi4(); + obj.fEntryOffsetLen = buf.ntoi4(); + obj.fWriteBasket = buf.ntoi4(); + obj.fEntryNumber = buf.ntoi4(); + obj.fOffset = buf.ntoi4(); + obj.fMaxBaskets = buf.ntoi4(); + if (v > 6) + obj.fSplitLevel = buf.ntoi4(); + obj.fEntries = buf.ntod(); + obj.fTotBytes = buf.ntod(); + obj.fZipBytes = buf.ntod(); + obj.fBranches = buf.classStreamer({}, clTObjArray); + obj.fLeaves = buf.classStreamer({}, clTObjArray); + obj.fBaskets = buf.classStreamer({}, clTObjArray); + buf.ntoi1(); // isArray + obj.fBasketBytes = buf.readFastArray(obj.fMaxBaskets, kInt); + buf.ntoi1(); // isArray + obj.fBasketEntry = buf.readFastArray(obj.fMaxBaskets, kInt); + const isArray = buf.ntoi1(); + obj.fBasketSeek = buf.readFastArray(obj.fMaxBaskets, isArray === 2 ? kLong64 : kInt); + obj.fFileName = buf.readTString(); + } + }, - /** - * Returns an array of controllers contained by this GUI and its descendents. - * @returns {Controller[]} - */ - controllersRecursive() { - let controllers = Array.from( this.controllers ); - this.folders.forEach( f => { - controllers = controllers.concat( f.controllersRecursive() ); - } ); - return controllers; - } + 'ROOT::RNTuple': { + name: '$file', + func(buf, obj) { + obj.$kind = 'ROOT::RNTuple'; + obj.$file = buf.fFile; + } + }, - /** - * Returns an array of folders contained by this GUI and its descendents. - * @returns {GUI[]} - */ - foldersRecursive() { - let folders = Array.from( this.folders ); - this.folders.forEach( f => { - folders = folders.concat( f.foldersRecursive() ); - } ); - return folders; - } + RooRealVar(buf, obj) { + const v = buf.last_read_version; + buf.classStreamer(obj, 'RooAbsRealLValue'); + if (v === 1) { + // skip fitMin, fitMax, fitBins + buf.ntod(); + buf.ntod(); + buf.ntoi4(); + } + obj._error = buf.ntod(); + obj._asymErrLo = buf.ntod(); + obj._asymErrHi = buf.ntod(); + if (v >= 2) + obj._binning = buf.readObjectAny(); + if (v === 3) + obj._sharedProp = buf.readObjectAny(); + if (v >= 4) + obj._sharedProp = buf.classStreamer({}, 'RooRealVarSharedProperties'); + }, -} + RooAbsBinning(buf, obj) { + buf.classStreamer(obj, (buf.last_read_version === 1) ? clTObject : clTNamed); + buf.classStreamer(obj, 'RooPrintable'); + }, -const _ENTIRE_SCENE = 0, _BLOOM_SCENE = 1, - clTGeoManager = 'TGeoManager', clTEveGeoShapeExtract = 'TEveGeoShapeExtract', - clTGeoOverlap = 'TGeoOverlap', clTGeoVolumeAssembly = 'TGeoVolumeAssembly', - clTEveTrack = 'TEveTrack', clTEvePointSet = 'TEvePointSet', - clREveGeoShapeExtract = `${nsREX}REveGeoShapeExtract`; + RooCategory(buf, obj) { + const v = buf.last_read_version; + buf.classStreamer(obj, 'RooAbsCategoryLValue'); + obj._sharedProp = (v === 1) ? buf.readObjectAny() : buf.classStreamer({}, 'RooCategorySharedProperties'); + }, -/** @summary Function used to build hierarchy of elements of overlap object - * @private */ -function buildOverlapVolume(overlap) { - const vol = create$1(clTGeoVolume); + 'RooWorkspace::CodeRepo': (buf /* , obj */) => { + const sz = (buf.last_read_version === 2) ? 3 : 2; + for (let i = 0; i < sz; ++i) { + let cnt = buf.ntoi4() * ((i === 0) ? 4 : 3); + while (cnt--) + buf.readTString(); + } + }, - setGeoBit(vol, geoBITS.kVisDaughters, true); - vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy - vol.fName = ''; + RooLinkedList(buf, obj) { + const v = buf.last_read_version; + buf.classStreamer(obj, clTObject); + let size = buf.ntoi4(); + obj.arr = create$1(clTList); + while (size--) + obj.arr.Add(buf.readObjectAny()); + if (v > 1) + obj._name = buf.readTString(); + }, - const node1 = create$1(clTGeoNodeMatrix); - node1.fName = overlap.fVolume1.fName || 'Overlap1'; - node1.fMatrix = overlap.fMatrix1; - node1.fVolume = overlap.fVolume1; - // node1.fVolume.fLineColor = 2; // color assigned with _splitColors + TImagePalette: [ + { + basename: clTObject, base: 1, func(buf, obj) { + if (!obj._typename) + obj._typename = clTImagePalette; + buf.classStreamer(obj, clTObject); + } + }, + { name: 'fNumPoints', func(buf, obj) { obj.fNumPoints = buf.ntou4(); } }, + { name: 'fPoints', func(buf, obj) { obj.fPoints = buf.readFastArray(obj.fNumPoints, kDouble); } }, + { name: 'fColorRed', func(buf, obj) { obj.fColorRed = buf.readFastArray(obj.fNumPoints, kUShort); } }, + { name: 'fColorGreen', func(buf, obj) { obj.fColorGreen = buf.readFastArray(obj.fNumPoints, kUShort); } }, + { name: 'fColorBlue', func(buf, obj) { obj.fColorBlue = buf.readFastArray(obj.fNumPoints, kUShort); } }, + { name: 'fColorAlpha', func(buf, obj) { obj.fColorAlpha = buf.readFastArray(obj.fNumPoints, kUShort); } } + ], - const node2 = create$1(clTGeoNodeMatrix); - node2.fName = overlap.fVolume2.fName || 'Overlap2'; - node2.fMatrix = overlap.fMatrix2; - node2.fVolume = overlap.fVolume2; - // node2.fVolume.fLineColor = 3; // color assigned with _splitColors + TAttImage: [ + { name: 'fImageQuality', func(buf, obj) { obj.fImageQuality = buf.ntoi4(); } }, + { name: 'fImageCompression', func(buf, obj) { obj.fImageCompression = buf.ntou4(); } }, + { name: 'fConstRatio', func(buf, obj) { obj.fConstRatio = buf.ntobool(); } }, + { name: 'fPalette', func(buf, obj) { obj.fPalette = buf.classStreamer({}, clTImagePalette); } } + ], - vol.fNodes = create$1(clTList); - vol.fNodes.Add(node1); - vol.fNodes.Add(node2); + TASImage(buf, obj) { + if ((buf.last_read_version === 1) && (buf.fFile.fVersion > 0) && (buf.fFile.fVersion < 50000)) + return console.warn('old TASImage version - not yet supported'); - return vol; -} + buf.classStreamer(obj, clTNamed); -let $comp_col_cnt = 0; + if (buf.ntobool()) { + const size = buf.ntoi4(); + obj.fPngBuf = buf.readFastArray(size, kUChar); + } else { + buf.classStreamer(obj, 'TAttImage'); + obj.fWidth = buf.ntoi4(); + obj.fHeight = buf.ntoi4(); + obj.fImgBuf = buf.readFastArray(obj.fWidth * obj.fHeight, kDouble); + } + }, -/** @summary Function used to build hierarchy of elements of composite shapes - * @private */ -function buildCompositeVolume(comp, maxlvl, side) { - if (maxlvl === undefined) maxlvl = 1; - if (!side) { - $comp_col_cnt = 0; - side = ''; - } + TMaterial(buf, obj) { + const v = buf.last_read_version; + buf.classStreamer(obj, clTNamed); + obj.fNumber = buf.ntoi4(); + obj.fA = buf.ntof(); + obj.fZ = buf.ntof(); + obj.fDensity = buf.ntof(); + if (v > 2) { + buf.classStreamer(obj, clTAttFill); + obj.fRadLength = buf.ntof(); + obj.fInterLength = buf.ntof(); + } else + obj.fRadLength = obj.fInterLength = 0; + }, - const vol = create$1(clTGeoVolume); - setGeoBit(vol, geoBITS.kVisThis, true); - setGeoBit(vol, geoBITS.kVisDaughters, true); + TMixture(buf, obj) { + buf.classStreamer(obj, 'TMaterial'); + obj.fNmixt = buf.ntoi4(); + obj.fAmixt = buf.readFastArray(buf.ntoi4(), kFloat); + obj.fZmixt = buf.readFastArray(buf.ntoi4(), kFloat); + obj.fWmixt = buf.readFastArray(buf.ntoi4(), kFloat); + }, - if ((side && (comp._typename !== clTGeoCompositeShape)) || (maxlvl <= 0)) { - vol.fName = side; - vol.fLineColor = ($comp_col_cnt++ % 8) + 2; - vol.fShape = comp; - return vol; - } + TVirtualPerfStats: clTObject, // use directly TObject streamer - if (side) side += '/'; - vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy - vol.fName = ''; + TMethodCall: clTObject +}; - const node1 = create$1(clTGeoNodeMatrix); - setGeoBit(node1, geoBITS.kVisThis, true); - setGeoBit(node1, geoBITS.kVisDaughters, true); - node1.fName = 'Left'; - node1.fMatrix = comp.fNode.fLeftMat; - node1.fVolume = buildCompositeVolume(comp.fNode.fLeft, maxlvl-1, side + 'Left'); - const node2 = create$1(clTGeoNodeMatrix); - setGeoBit(node2, geoBITS.kVisThis, true); - setGeoBit(node2, geoBITS.kVisDaughters, true); - node2.fName = 'Right'; - node2.fMatrix = comp.fNode.fRightMat; - node2.fVolume = buildCompositeVolume(comp.fNode.fRight, maxlvl-1, side + 'Right'); +/** @summary Add custom streamer + * @public */ +function addUserStreamer(type, user_streamer) { + CustomStreamers[type] = user_streamer; +} - vol.fNodes = create$1(clTList); - vol.fNodes.Add(node1); - vol.fNodes.Add(node2); +/** @summary these are streamers which do not handle version regularly + * @desc used for special classes like TRef or TBasket + * @private */ +const DirectStreamers = { + // do nothing for these classes + TQObject() {}, + TGraphStruct() {}, + TGraphNode() {}, + TGraphEdge() {}, - if (!side) $comp_col_cnt = 0; + TDatime(buf, obj) { + obj.fDatime = buf.ntou4(); + }, - return vol; -} + TKey(buf, key) { + key.fNbytes = buf.ntoi4(); + key.fVersion = buf.ntoi2(); + key.fObjlen = buf.ntou4(); + key.fDatime = buf.classStreamer({}, clTDatime); + key.fKeylen = buf.ntou2(); + key.fCycle = buf.ntou2(); + if (key.fVersion > 1000) { + key.fSeekKey = buf.ntou8(); + buf.shift(8); // skip seekPdir + } else { + key.fSeekKey = buf.ntou4(); + buf.shift(4); // skip seekPdir + } + key.fClassName = buf.readTString(); + key.fName = buf.readTString(); + key.fTitle = buf.readTString(); + }, + TDirectory(buf, dir) { + const version = buf.ntou2(); + dir.fDatimeC = buf.classStreamer({}, clTDatime); + dir.fDatimeM = buf.classStreamer({}, clTDatime); + dir.fNbytesKeys = buf.ntou4(); + dir.fNbytesName = buf.ntou4(); + dir.fSeekDir = (version > 1000) ? buf.ntou8() : buf.ntou4(); + dir.fSeekParent = (version > 1000) ? buf.ntou8() : buf.ntou4(); + dir.fSeekKeys = (version > 1000) ? buf.ntou8() : buf.ntou4(); + // if ((version % 1000) > 2) buf.shift(18); // skip fUUID + }, -/** @summary Provides 3D rendering configuration from histogram painter - * @return {Object} with scene, renderer and other attributes - * @private */ -function getHistPainter3DCfg(painter) { - const main = painter?.getFramePainter(); - if (painter?.mode3d && isFunc(main?.create3DScene) && main?.renderer) { - let scale_x = 1, scale_y = 1, scale_z = 1, - offset_x = 0, offset_y = 0, offset_z = 0; + TRef(buf, obj) { + buf.classStreamer(obj, clTObject); + if (obj.fBits & kHasUUID) + obj.fUUID = buf.readTString(); + else + obj.fPID = buf.ntou2(); + }, - if (main.scale_xmax > main.scale_xmin) { - scale_x = 2 * main.size_x3d/(main.scale_xmax - main.scale_xmin); - offset_x = (main.scale_xmax + main.scale_xmin) / 2 * scale_x; + 'TMatrixTSym': (buf, obj) => { + buf.classStreamer(obj, 'TMatrixTBase'); + obj.fElements = new Float32Array(obj.fNelems); + const arr = buf.readFastArray((obj.fNrows * (obj.fNcols + 1)) / 2, kFloat); + for (let i = 0, cnt = 0; i < obj.fNrows; ++i) { + for (let j = i; j < obj.fNcols; ++j) + obj.fElements[j * obj.fNcols + i] = obj.fElements[i * obj.fNcols + j] = arr[cnt++]; } + }, - if (main.scale_ymax > main.scale_ymin) { - scale_y = 2 * main.size_y3d/(main.scale_ymax - main.scale_ymin); - offset_y = (main.scale_ymax + main.scale_ymin) / 2 * scale_y; + 'TMatrixTSym': (buf, obj) => { + buf.classStreamer(obj, 'TMatrixTBase'); + obj.fElements = new Float64Array(obj.fNelems); + const arr = buf.readFastArray((obj.fNrows * (obj.fNcols + 1)) / 2, kDouble); + for (let i = 0, cnt = 0; i < obj.fNrows; ++i) { + for (let j = i; j < obj.fNcols; ++j) + obj.fElements[j * obj.fNcols + i] = obj.fElements[i * obj.fNcols + j] = arr[cnt++]; } + } +}; - if (main.scale_zmax > main.scale_zmin) { - scale_z = 2 * main.size_z3d/(main.scale_zmax - main.scale_zmin); - offset_z = (main.scale_zmax + main.scale_zmin) / 2 * scale_z - main.size_z3d; +/** @summary Returns type id by its name + * @private */ +function getTypeId(typname, norecursion) { + switch (typname) { + case 'bool': + case 'Bool_t': return kBool; + case 'char': + case 'signed char': + case 'Char_t': return kChar; + case 'Color_t': + case 'Style_t': + case 'Width_t': + case 'short': + case 'Short_t': return kShort; + case 'int': + case 'EErrorType': + case 'Int_t': return kInt; + case 'long': + case 'Long_t': return kLong; + case 'float': + case 'Float_t': return kFloat; + case 'double': + case 'Double_t': return kDouble; + case 'unsigned char': + case 'UChar_t': return kUChar; + case 'unsigned short': + case 'UShort_t': return kUShort; + case 'unsigned': + case 'unsigned int': + case 'UInt_t': return kUInt; + case 'unsigned long': + case 'ULong_t': return kULong; + case 'int64_t': + case 'long long': + case 'Long64_t': return kLong64; + case 'uint64_t': + case 'unsigned long long': + case 'ULong64_t': return kULong64; + case 'Double32_t': return kDouble32; + case 'Float16_t': return kFloat16; + case 'char*': + case 'const char*': + case 'const Char_t*': return kCharStar; + } + + if (!norecursion) { + const replace = CustomStreamers[typname]; + if (isStr(replace)) + return getTypeId(replace, true); + } + + return -1; +} + +/** @summary Analyze and returns arrays kind + * @return 0 if TString (or equivalent), positive value - some basic type, -1 - any other kind + * @private */ +function getArrayKind(type_name) { + if ((type_name === clTString) || (type_name === 'string') || + (CustomStreamers[type_name] === clTString)) + return 0; + if ((type_name.length < 7) || type_name.indexOf('TArray')) + return -1; + if (type_name.length === 7) { + switch (type_name[6]) { + case 'I': return kInt; + case 'D': return kDouble; + case 'F': return kFloat; + case 'S': return kShort; + case 'C': return kChar; + case 'L': return kLong; + default: return -1; } + } - return { - webgl: main.webgl, - scene: main.scene, - scene_width: main.scene_width, - scene_height: main.scene_height, - toplevel: main.toplevel, - renderer: main.renderer, - camera: main.camera, - scale_x, scale_y, scale_z, - offset_x, offset_y, offset_z - }; - } + return type_name === 'TArrayL64' ? kLong64 : -1; } +// eslint-disable-next-line prefer-const +let createPairStreamer; -/** @summary create list entity for geo object - * @private */ -function createList(parent, lst, name, title) { - if (!lst?.arr?.length) return; +/** @summary create element of the streamer + * @private */ +function createStreamerElement(name, typename, file) { + const elem = { + _typename: clTStreamerElement, fName: name, fTypeName: typename, + fType: 0, fSize: 0, fArrayLength: 0, fArrayDim: 0, fMaxIndex: [0, 0, 0, 0, 0], + fXmin: 0, fXmax: 0, fFactor: 0 + }; - const list_item = { - _name: name, - _kind: prROOT + clTList, - _title: title, - _more: true, - _geoobj: lst, - _parent: parent, - _get(item /*, itemname */) { - return Promise.resolve(item._geoobj || null); - }, - _expand(node, lst) { - // only childs + if (isStr(typename)) { + elem.fType = getTypeId(typename); + if ((elem.fType < 0) && file && file.fBasicTypes[typename]) + elem.fType = file.fBasicTypes[typename]; + } else { + elem.fType = typename; + typename = elem.fTypeName = BasicTypeNames[elem.fType] || 'int'; + } - if (lst.fVolume) - lst = lst.fVolume.fNodes; + if (elem.fType > 0) + return elem; // basic type - if (!lst.arr) return false; + // check if there are STL containers + const pos = typename.indexOf('<'); + let stltype = kNotSTL; + if ((pos > 0) && (typename.indexOf('>') > pos + 2)) { + for (let stl = 1; stl < StlNames.length; ++stl) { + if (typename.slice(0, pos) === StlNames[stl]) { + stltype = stl; + break; + } + } + } - node._childs = []; + if (stltype !== kNotSTL) { + elem._typename = clTStreamerSTL; + elem.fType = kStreamer; + elem.fSTLtype = stltype; + elem.fCtype = 0; + return elem; + } - checkDuplicates(null, lst.arr); + if ((pos > 0) && (typename.slice(0, pos) === 'pair') && file && isFunc(createPairStreamer)) + createPairStreamer(typename, file); - for (const n in lst.arr) - createItem(node, lst.arr[n]); + const isptr = typename.at(-1) === '*'; - return true; - } - }; + if (isptr) + elem.fTypeName = typename = typename.slice(0, typename.length - 1); - if (!parent._childs) - parent._childs = []; - parent._childs.push(list_item); -} + if (getArrayKind(typename) === 0) { + elem.fType = kTString; + return elem; + } + elem.fType = isptr ? kAnyP : kAny; -/** @summary Expand geo object + return elem; +} + +/** @summary Function to read vector element in the streamer * @private */ -function expandGeoObject(parent, obj) { - injectGeoStyle(); +function readVectorElement(buf) { + if (this.member_wise) { + const n = buf.ntou4(), ver = this.stl_version; - if (!parent || !obj) return false; + if (n === 0) + return []; // for empty vector no need to search split streamers - const isnode = (obj._typename.indexOf(clTGeoNode) === 0), - isvolume = (obj._typename.indexOf(clTGeoVolume) === 0), - ismanager = (obj._typename === clTGeoManager), - iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)), - isoverlap = (obj._typename === clTGeoOverlap); + if (n > 1000000) + throw new Error(`member-wise streaming of ${this.conttype} num ${n} member ${this.name}`); + + let streamer; + + if ((ver.val === this.member_ver) && (ver.checksum === this.member_checksum)) + streamer = this.member_streamer; + else { + streamer = buf.fFile.getStreamer(this.conttype, ver); - if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) return false; + this.member_streamer = streamer = buf.fFile.getSplittedStreamer(streamer); + this.member_ver = ver.val; + this.member_checksum = ver.checksum; + } - if (parent._childs) return true; + const res = new Array(n); + let i, k, member; - if (ismanager) { - createList(parent, obj.fMaterials, 'Materials', 'list of materials'); - createList(parent, obj.fMedia, 'Media', 'list of media'); - createList(parent, obj.fTracks, 'Tracks', 'list of tracks'); - createList(parent, obj.fOverlaps, 'Overlaps', 'list of detected overlaps'); - createItem(parent, obj.fMasterVolume); - return true; + for (i = 0; i < n; ++i) + res[i] = { _typename: this.conttype }; // create objects + if (!streamer) + console.error(`Fail to create split streamer for ${this.conttype} need to read ${n} objects version ${ver}`); + else { + for (k = 0; k < streamer.length; ++k) { + member = streamer[k]; + if (member.split_func) + member.split_func(buf, res, n); + else { + for (i = 0; i < n; ++i) + member.func(buf, res[i]); + } + } + } + return res; } - if (isoverlap) { - createItem(parent, obj.fVolume1); - createItem(parent, obj.fVolume2); - createItem(parent, obj.fMarker, 'Marker'); - return true; - } + const n = buf.ntou4(), res = new Array(n); + let i = 0; - let volume, subnodes, shape; + if (n > 200000) { + console.error(`vector streaming for ${this.conttype} at ${n}`); + return res; + } - if (iseve) { - subnodes = obj.fElements?.arr; - shape = obj.fShape; + if (this.arrkind > 0) { + while (i < n) + res[i++] = buf.readFastArray(buf.ntou4(), this.arrkind); + } else if (this.arrkind === 0) { + while (i < n) + res[i++] = buf.readTString(); + } else if (this.isptr) { + while (i < n) + res[i++] = buf.readObjectAny(); + } else if (this.submember) { + while (i < n) + res[i++] = this.submember.readelem(buf); } else { - volume = isnode ? obj.fVolume : obj; - subnodes = volume?.fNodes?.arr; - shape = volume?.fShape; + while (i < n) + res[i++] = buf.classStreamer({}, this.conttype); } - if (!subnodes && (shape?._typename === clTGeoCompositeShape) && shape?.fNode) { - if (!parent._childs) { // deepscan-disable-line - createItem(parent, shape.fNode.fLeft, 'Left'); - createItem(parent, shape.fNode.fRight, 'Right'); - } + return res; +} - return true; +/** @summary Create streamer info for pair object + * @private */ +createPairStreamer = function(typename, file) { + let si = file.findStreamerInfo(typename); + if (si) + return si; + let p1 = typename.indexOf('<'); + const p2 = typename.lastIndexOf('>'); + function getNextName() { + let res = '', p = p1 + 1, cnt = 0; + while ((p < p2) && (cnt >= 0)) { + switch (typename[p]) { + case '<': + cnt++; + break; + case ',': + if (cnt === 0) + cnt--; + break; + case '>': + cnt--; + break; + } + if (cnt >= 0) + res += typename[p]; + p++; + } + p1 = p - 1; + return res.trim(); } + si = { _typename: 'TStreamerInfo', fClassVersion: 0, fName: typename, fElements: create$1(clTList) }; + si.fElements.Add(createStreamerElement('first', getNextName(), file)); + si.fElements.Add(createStreamerElement('second', getNextName(), file)); + file.fStreamerInfos.arr.push(si); + return si; +}; - if (!subnodes) return false; - - checkDuplicates(obj, subnodes); - - for (let i = 0; i < subnodes.length; ++i) - createItem(parent, subnodes[i]); +/** @summary Function creates streamer for std::pair object + * @private */ +function getPairStreamer(si, typname, file) { + if (!si) + si = createPairStreamer(typname, file); - return true; -} + const streamer = file.getStreamer(typname, null, si); + if (!streamer) + return null; + if (streamer.length !== 2) { + console.error(`Streamer for pair class contains ${streamer.length} elements`); + return null; + } -/** @summary find item with 3d painter - * @private */ -function findItemWithPainter(hitem, funcname) { - while (hitem) { - if (hitem._painter?._camera) { - if (funcname && isFunc(hitem._painter[funcname])) - hitem._painter[funcname](); - return hitem; + for (let nn = 0; nn < 2; ++nn) { + if (streamer[nn].readelem && !streamer[nn].pair_name) { + streamer[nn].pair_name = (nn === 0) ? 'first' : 'second'; + streamer[nn].func = function(buf, obj) { + obj[this.pair_name] = this.readelem(buf); + }; } - hitem = hitem._parent; } - return null; + + return streamer; } -/** @summary provide css style for geo object +/** @summary Function used in streamer to read std::map object * @private */ -function provideVisStyle(obj) { - if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) - return obj.fRnrSelf ? ' geovis_this' : ''; +function readMapElement(buf) { + let streamer = this.streamer; - const vis = !testGeoBit(obj, geoBITS.kVisNone) && testGeoBit(obj, geoBITS.kVisThis); - let chld = testGeoBit(obj, geoBITS.kVisDaughters); + if (this.member_wise) { + // when member-wise streaming is used, version is written + const si = buf.fFile.findStreamerInfo(this.pairtype, this.stl_version.val, this.stl_version.checksum); - if (chld && !obj.fNodes?.arr?.length) chld = false; + if (si && (this.si !== si)) { + streamer = getPairStreamer(si, this.pairtype, buf.fFile); + if (streamer?.length !== 2) { + console.log(`Fail to produce streamer for ${this.pairtype}`); + return null; + } + } + } - if (vis && chld) return ' geovis_all'; - if (vis) return ' geovis_this'; - if (chld) return ' geovis_daughters'; - return ''; -} + const n = buf.ntoi4(), res = new Array(n); + // no extra data written for empty map + if (n === 0) + return res; -/** @summary update icons - * @private */ -function updateBrowserIcons(obj, hpainter) { - if (!obj || !hpainter) return; + if (this.member_wise && (buf.remain() >= 6)) { + if (buf.ntoi2() === kStreamedMemberWise) + buf.shift(4); // skip checksum + else + buf.shift(-2); // rewind + } - hpainter.forEachItem(m => { - // update all items with that volume - if ((obj === m._volume) || (obj === m._geoobj)) { - m._icon = m._icon.split(' ')[0] + provideVisStyle(obj); - hpainter.updateTreeNode(m); + for (let i = 0; i < n; ++i) { + res[i] = { _typename: this.pairtype }; + streamer[0].func(buf, res[i]); + if (!this.member_wise) + streamer[1].func(buf, res[i]); + } + + // due-to member-wise streaming second element read after first is completed + if (this.member_wise) { + if (buf.remain() >= 6) { + if (buf.ntoi2() === kStreamedMemberWise) + buf.shift(4); // skip checksum + else + buf.shift(-2); // rewind } - }); + for (let i = 0; i < n; ++i) + streamer[1].func(buf, res[i]); + } + + return res; } -/** @summary Return stack for the item from list of intersection +/** @summary create member entry for streamer element + * @desc used for reading of data * @private */ -function getIntersectStack(item) { - const obj = item?.object; - if (!obj) return null; - if (obj.stack) - return obj.stack; - if (obj.stacks && item.instanceId !== undefined && item.instanceId < obj.stacks.length) - return obj.stacks[item.instanceId]; -} +function createMemberStreamer(element, file) { + const member = { + name: element.fName, type: element.fType, + fArrayLength: element.fArrayLength, + fArrayDim: element.fArrayDim, + fMaxIndex: element.fMaxIndex + }; -/** - * @summary Toolbar for geometry painter - * - * @private - */ + if (element.fTypeName === kBaseClass) { + if (getArrayKind(member.name) > 0) { + // this is workaround for arrays as base class + // we create 'fArray' member, which read as any other data member + member.name = 'fArray'; + member.type = kAny; + } else { + // create streamer for base class + member.type = kBase; + // this.getStreamer(element.fName); + } + } -class Toolbar { + switch (member.type) { + case kBase: + member.base = element.fBaseVersion; // indicate base class + member.basename = element.fName; // keep class name + member.func = function(buf, obj) { buf.classStreamer(obj, this.basename); }; + break; + case kShort: + member.func = function(buf, obj) { obj[this.name] = buf.ntoi2(); }; + break; + case kInt: + case kCounter: + member.func = function(buf, obj) { obj[this.name] = buf.ntoi4(); }; + break; + case kLong: + case kLong64: + member.func = function(buf, obj) { obj[this.name] = buf.ntoi8(); }; + break; + case kDouble: + member.func = function(buf, obj) { obj[this.name] = buf.ntod(); }; + break; + case kFloat: + member.func = function(buf, obj) { obj[this.name] = buf.ntof(); }; + break; + case kLegacyChar: + case kUChar: + member.func = function(buf, obj) { obj[this.name] = buf.ntou1(); }; + break; + case kUShort: + member.func = function(buf, obj) { obj[this.name] = buf.ntou2(); }; + break; + case kBits: + case kUInt: + member.func = function(buf, obj) { obj[this.name] = buf.ntou4(); }; + break; + case kULong64: + case kULong: + member.func = function(buf, obj) { obj[this.name] = buf.ntou8(); }; + break; + case kBool: + member.func = function(buf, obj) { obj[this.name] = buf.ntobool(); }; + break; + case kOffsetL + kBool: + case kOffsetL + kInt: + case kOffsetL + kCounter: + case kOffsetL + kDouble: + case kOffsetL + kUChar: + case kOffsetL + kShort: + case kOffsetL + kUShort: + case kOffsetL + kBits: + case kOffsetL + kUInt: + case kOffsetL + kULong: + case kOffsetL + kULong64: + case kOffsetL + kLong: + case kOffsetL + kLong64: + case kOffsetL + kFloat: + if (element.fArrayDim < 2) { + member.arrlength = element.fArrayLength; + member.func = function(buf, obj) { + obj[this.name] = buf.readFastArray(this.arrlength, this.type - kOffsetL); + }; + } else { + member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; + member.minus1 = true; + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => + buf2.readFastArray(handle.arrlength, handle.type - kOffsetL)); + }; + } + break; + case kOffsetL + kChar: + if (element.fArrayDim < 2) { + member.arrlength = element.fArrayLength; + member.func = function(buf, obj) { + obj[this.name] = buf.readFastString(this.arrlength); + }; + } else { + member.minus1 = true; // one dimension used for char* + member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => + buf2.readFastString(handle.arrlength)); + }; + } + break; + case kOffsetP + kBool: + case kOffsetP + kInt: + case kOffsetP + kDouble: + case kOffsetP + kUChar: + case kOffsetP + kShort: + case kOffsetP + kUShort: + case kOffsetP + kBits: + case kOffsetP + kUInt: + case kOffsetP + kULong: + case kOffsetP + kULong64: + case kOffsetP + kLong: + case kOffsetP + kLong64: + case kOffsetP + kFloat: + member.cntname = element.fCountName; + member.func = function(buf, obj) { + obj[this.name] = (buf.ntou1() === 1) ? buf.readFastArray(obj[this.cntname], this.type - kOffsetP) : []; + }; + break; + case kOffsetP + kChar: + member.cntname = element.fCountName; + member.func = function(buf, obj) { + obj[this.name] = (buf.ntou1() === 1) ? buf.readFastString(obj[this.cntname]) : null; + }; + break; + case kDouble32: + case kOffsetL + kDouble32: + case kOffsetP + kDouble32: + member.double32 = true; + // eslint-disable-next-line no-fallthrough + case kFloat16: + case kOffsetL + kFloat16: + case kOffsetP + kFloat16: + if (element.fFactor) { + member.factor = 1 / element.fFactor; + member.min = element.fXmin; + member.read = function(buf) { return buf.ntou4() * this.factor + this.min; }; + } else + if ((element.fXmin === 0) && member.double32) + member.read = function(buf) { return buf.ntof(); }; + else { + member.nbits = Math.round(element.fXmin); + if (member.nbits === 0) + member.nbits = 12; + member.dv = new DataView(new ArrayBuffer(8), 0); // used to cast from uint32 to float32 + member.read = function(buf) { + const theExp = buf.ntou1(), theMan = buf.ntou2(); + this.dv.setUint32(0, (theExp << 23) | ((theMan & ((1 << (this.nbits + 1)) - 1)) << (23 - this.nbits))); + return ((1 << (this.nbits + 1) & theMan) ? -1 : 1) * this.dv.getFloat32(0); + }; + } - /** @summary constructor */ - constructor(container, bright, buttons) { - this.bright = bright; - this.buttons = buttons; - this.element = container.append('div').attr('style', 'float: left; box-sizing: border-box; position: relative; bottom: 23px; vertical-align: middle; padding-left: 5px'); - } + member.readarr = function(buf, len) { + const arr = this.double32 ? new Float64Array(len) : new Float32Array(len); + for (let n = 0; n < len; ++n) + arr[n] = this.read(buf); + return arr; + }; - /** @summary add buttons */ - createButtons() { - const buttonsNames = []; + if (member.type < kOffsetL) + member.func = function(buf, obj) { obj[this.name] = this.read(buf); }; + else + if (member.type > kOffsetP) { + member.cntname = element.fCountName; + member.func = function(buf, obj) { + obj[this.name] = (buf.ntou1() === 1) ? this.readarr(buf, obj[this.cntname]) : null; + }; + } else + if (element.fArrayDim < 2) { + member.arrlength = element.fArrayLength; + member.func = function(buf, obj) { obj[this.name] = this.readarr(buf, this.arrlength); }; + } else { + member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; + member.minus1 = true; + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => handle.readarr(buf2, handle.arrlength)); + }; + } + break; - this.buttons.forEach(buttonConfig => { - const buttonName = buttonConfig.name; - if (!buttonName) - throw new Error('must provide button name in button config'); - if (buttonsNames.indexOf(buttonName) !== -1) - throw new Error(`button name ${buttonName} is taken`); + case kAnyP: + case kObjectP: + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, buf2 => buf2.readObjectAny()); + }; + break; - buttonsNames.push(buttonName); + case kAny: + case kAnyp: + case kObjectp: + case kObject: { + let classname = (element.fTypeName === kBaseClass) ? element.fName : element.fTypeName; + if (classname.at(-1) === '*') + classname = classname.slice(0, classname.length - 1); - const title = buttonConfig.title || buttonConfig.name; + const arrkind = getArrayKind(classname); - if (!isFunc(buttonConfig.click)) - throw new Error('must provide button click() function in button config'); + if (arrkind > 0) { + member.arrkind = arrkind; + member.func = function(buf, obj) { obj[this.name] = buf.readFastArray(buf.ntou4(), this.arrkind); }; + } else if (arrkind === 0) + member.func = function(buf, obj) { obj[this.name] = buf.readTString(); }; + else { + member.classname = classname; - ToolbarIcons.createSVG(this.element, ToolbarIcons[buttonConfig.icon], 16, title, this.bright) - .on('click', buttonConfig.click) - .style('position', 'relative') - .style('padding', '3px 1px'); - }); - } + if (element.fArrayLength > 1) { + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => buf2.classStreamer({}, handle.classname)); + }; + } else { + member.func = function(buf, obj) { + obj[this.name] = buf.classStreamer({}, this.classname); + }; + } + } + break; + } + case kOffsetL + kObject: + case kOffsetL + kAny: + case kOffsetL + kAnyp: + case kOffsetL + kObjectp: { + let classname = element.fTypeName; + if (classname.at(-1) === '*') + classname = classname.slice(0, classname.length - 1); - /** @summary change brightness */ - changeBrightness(bright) { - if (this.bright === bright) return; - this.element.selectAll('*').remove(); - this.bright = bright; - this.createButtons(); - } + member.arrkind = getArrayKind(classname); + if (member.arrkind < 0) + member.classname = classname; + member.func = function(buf, obj) { + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => { + if (handle.arrkind > 0) + return buf2.readFastArray(buf.ntou4(), handle.arrkind); + if (handle.arrkind === 0) + return buf2.readTString(); + return buf2.classStreamer({}, handle.classname); + }); + }; + break; + } + case kChar: + member.func = function(buf, obj) { obj[this.name] = buf.ntoi1(); }; + break; + case kCharStar: + member.func = function(buf, obj) { + const len = buf.ntoi4(); + obj[this.name] = buf.substring(buf.o, buf.o + len); + buf.o += len; + }; + break; + case kTString: + member.func = function(buf, obj) { obj[this.name] = buf.readTString(); }; + break; + case kTObject: + case kTNamed: + member.typename = element.fTypeName; + member.func = function(buf, obj) { obj[this.name] = buf.classStreamer({}, this.typename); }; + break; + case kOffsetL + kTString: + case kOffsetL + kTObject: + case kOffsetL + kTNamed: + member.typename = element.fTypeName; + member.func = function(buf, obj) { + const ver = buf.readVersion(); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => { + if (handle.typename === clTString) + return buf2.readTString(); + return buf2.classStreamer({}, handle.typename); + }); + buf.checkByteCount(ver, this.typename + '[]'); + }; + break; + case kStreamLoop: + case kOffsetL + kStreamLoop: + member.typename = element.fTypeName; + member.cntname = element.fCountName; - /** @summary cleanup toolbar */ - cleanup() { - this.element?.remove(); - delete this.element; - } + if (member.typename.lastIndexOf('**') > 0) { + member.typename = member.typename.slice(0, member.typename.lastIndexOf('**')); + member.isptrptr = true; + } else { + member.typename = member.typename.slice(0, member.typename.lastIndexOf('*')); + member.isptrptr = false; + } -} // class ToolBar + if (member.isptrptr) + member.readitem = function(buf) { return buf.readObjectAny(); }; + else { + member.arrkind = getArrayKind(member.typename); + if (member.arrkind > 0) + member.readitem = function(buf) { return buf.readFastArray(buf.ntou4(), this.arrkind); }; + else if (member.arrkind === 0) + member.readitem = function(buf) { return buf.readTString(); }; + else + member.readitem = function(buf) { return buf.classStreamer({}, this.typename); }; + } + if (member.readitem !== undefined) { + member.read_loop = function(buf, cnt) { + return buf.readNdimArray(this, (buf2, member2) => { + const itemarr = new Array(cnt); + for (let i = 0; i < cnt; ++i) + itemarr[i] = member2.readitem(buf2); + return itemarr; + }); + }; -/** - * @summary geometry drawing control - * - * @private - */ + member.func = function(buf, obj) { + const ver = buf.readVersion(), + res = this.read_loop(buf, obj[this.cntname]); + obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; + }; + member.branch_func = function(buf, obj) { + // this is special functions, used by branch in the STL container + const ver = buf.readVersion(), sz0 = obj[this.stl_size], res = new Array(sz0); -class GeoDrawingControl extends InteractiveControl { + for (let loop0 = 0; loop0 < sz0; ++loop0) { + const cnt = obj[this.cntname][loop0]; + res[loop0] = this.read_loop(buf, cnt); + } + obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; + }; - constructor(mesh, bloom) { - super(); - this.mesh = mesh?.material ? mesh : null; - this.bloom = bloom; - } + member.objs_branch_func = function(buf, obj) { + // special function when branch read as part of complete object + // objects already preallocated and only appropriate member must be set + // see code in JSRoot.tree.js for reference - /** @summary set highlight */ - setHighlight(col, indx) { - return this.drawSpecial(col, indx); - } + const ver = buf.readVersion(), + arr = obj[this.name0]; // objects array where reading is done - /** @summary draw special */ - drawSpecial(col, indx) { - const c = this.mesh; - if (!c?.material) return; + for (let loop0 = 0; loop0 < arr.length; ++loop0) { + const obj1 = this.get(arr, loop0), cnt = obj1[this.cntname]; + obj1[this.name] = this.read_loop(buf, cnt); + } - if (c.isInstancedMesh) { - if (c._highlight_mesh) { - c.remove(c._highlight_mesh); - delete c._highlight_mesh; + buf.checkByteCount(ver, this.typename); + }; + } else { + console.error(`fail to provide function for ${element.fName} (${element.fTypeName}) typ = ${element.fType}`); + member.func = function(buf, obj) { + const ver = buf.readVersion(); + buf.checkByteCount(ver); + obj[this.name] = null; + }; } - if (col && indx !== undefined) { - const h = new Mesh(c.geometry, c.material.clone()); - - if (this.bloom) { - h.layers.enable(_BLOOM_SCENE); - h.material.emissive = new Color(0x00ff00); - } else { - h.material.color = new Color(col); - h.material.opacity = 1.0; - } - const m = new Matrix4(); - c.getMatrixAt(indx, m); - h.applyMatrix4(m); - c.add(h); + break; - h.jsroot_special = true; // exclude from intersections + case kStreamer: { + member.typename = element.fTypeName; - c._highlight_mesh = h; - } - return true; - } + const stl = (element.fSTLtype || 0) % 40; + if ((element._typename === 'TStreamerSTLstring') || + (member.typename === 'string') || (member.typename === 'string*')) + member.readelem = buf => buf.readTString(); + else if ((stl === kSTLvector) || (stl === kSTLlist) || + (stl === kSTLdeque) || (stl === kSTLset) || (stl === kSTLmultiset)) { + const p1 = member.typename.indexOf('<'), + p2 = member.typename.lastIndexOf('>'); - if (col) { - if (!c.origin) { - c.origin = { - color: c.material.color, - emissive: c.material.emissive, - opacity: c.material.opacity, - width: c.material.linewidth, - size: c.material.size - }; - } - if (this.bloom) { - c.layers.enable(_BLOOM_SCENE); - c.material.emissive = new Color(0x00ff00); - } else { - c.material.color = new Color(col); - c.material.opacity = 1.0; - } + member.conttype = member.typename.slice(p1 + 1, p2).trim(); + member.typeid = getTypeId(member.conttype); + if ((member.typeid < 0) && file.fBasicTypes[member.conttype]) { + member.typeid = file.fBasicTypes[member.conttype]; + console.log(`Reuse basic type ${member.conttype} from file streamer infos`); + } - if (c.hightlightWidthScale && !browser.isWin) - c.material.linewidth = c.origin.width * c.hightlightWidthScale; - if (c.highlightScale) - c.material.size = c.origin.size * c.highlightScale; - return true; - } else if (c.origin) { - if (this.bloom) { - c.material.emissive = c.origin.emissive; - c.layers.enable(_ENTIRE_SCENE); - } else { - c.material.color = c.origin.color; - c.material.opacity = c.origin.opacity; - } - if (c.hightlightWidthScale) - c.material.linewidth = c.origin.width; - if (c.highlightScale) - c.material.size = c.origin.size; - return true; - } - } + // check + if (element.fCtype && (element.fCtype < 20) && (element.fCtype !== member.typeid)) { + console.warn(`Contained type ${member.conttype} not recognized as basic type ${element.fCtype} FORCE`); + member.typeid = element.fCtype; + } -} // class GeoDrawingControl + if (member.typeid > 0) { + member.readelem = function(buf) { + return buf.readFastArray(buf.ntoi4(), this.typeid); + }; + } else { + member.isptr = false; + if (member.conttype.at(-1) === '*') { + member.isptr = true; + member.conttype = member.conttype.slice(0, member.conttype.length - 1); + } -const stageInit = 0, stageCollect = 1, stageWorkerCollect = 2, stageAnalyze = 3, stageCollShapes = 4, - stageStartBuild = 5, stageWorkerBuild = 6, stageBuild = 7, stageBuildReady = 8, stageWaitMain = 9, stageBuildProj = 10; + if (element.fCtype === kObjectp) + member.isptr = true; -/** - * @summary Painter class for geometries drawing - * - * @private - */ + member.arrkind = getArrayKind(member.conttype); -class TGeoPainter extends ObjectPainter { + member.readelem = readVectorElement; - /** @summary Constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} obj - supported TGeo object */ - constructor(dom, obj) { - let gm; - if (obj?._typename === clTGeoManager) { - gm = obj; - obj = obj.fMasterVolume; - } + if (!member.isptr && (member.arrkind < 0)) { + const subelem = createStreamerElement('temp', member.conttype, file); + if (subelem.fType === kStreamer) { + subelem.$fictional = true; + member.submember = createMemberStreamer(subelem, file); + } + } + } + } else if ((stl === kSTLmap) || (stl === kSTLmultimap)) { + const p1 = member.typename.indexOf('<'), + p2 = member.typename.lastIndexOf('>'); - if (obj?._typename && (obj._typename.indexOf(clTGeoVolume) === 0)) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + member.pairtype = 'pair<' + member.typename.slice(p1 + 1, p2) + '>'; - super(dom, obj); + // remember found streamer info from the file - + // most probably it is the only one which should be used + member.si = file.findStreamerInfo(member.pairtype); - if (getHistPainter3DCfg(this.getMainPainter())) - this.superimpose = true; + member.streamer = getPairStreamer(member.si, member.pairtype, file); - if (gm) this.geo_manager = gm; + if (!member.streamer || (member.streamer.length !== 2)) { + console.error(`Fail to build streamer for pair ${member.pairtype}`); + delete member.streamer; + } - this.no_default_title = true; // do not set title to main DIV - this.mode3d = true; // indication of 3D mode - this.drawing_stage = stageInit; // - this.drawing_log = 'Init'; - this.ctrl = { - clipIntersect: true, - clipVisualize: false, - clip: [{ name: 'x', enabled: false, value: 0, min: -100, max: 100, step: 1 }, - { name: 'y', enabled: false, value: 0, min: -100, max: 100, step: 1 }, - { name: 'z', enabled: false, value: 0, min: -100, max: 100, step: 1 }], - _highlight: 0, - highlight: 0, - highlight_bloom: 0, - highlight_scene: 0, - highlight_color: '#00ff00', - bloom_strength: 1.5, - more: 1, - maxfaces: 0, - vislevel: undefined, - maxnodes: undefined, - dflt_colors: false, + if (member.streamer) + member.readelem = readMapElement; + } else if (stl === kSTLbitset) + member.readelem = (buf /* , obj */) => buf.readFastArray(buf.ntou4(), kBool); - info: { num_meshes: 0, num_faces: 0, num_shapes: 0 }, - depthTest: true, - depthMethod: 'dflt', - select_in_view: false, - update_browser: true, - use_fog: false, - light: { kind: 'points', top: false, bottom: false, left: false, right: false, front: false, specular: true, power: 1 }, - lightKindItems: [ - { name: 'AmbientLight', value: 'ambient' }, - { name: 'DirectionalLight', value: 'points' }, - { name: 'HemisphereLight', value: 'hemisphere' }, - { name: 'Ambient + Point', value: 'mix' } - ], - trans_radial: 0, - trans_z: 0, - scale: new Vector3(1, 1, 1), - zoom: 1.0, rotatey: 0, rotatez: 0, - depthMethodItems: [ - { name: 'Default', value: 'dflt' }, - { name: 'Raytraicing', value: 'ray' }, - { name: 'Boundary box', value: 'box' }, - { name: 'Mesh size', value: 'size' }, - { name: 'Central point', value: 'pnt' } - ], - cameraKindItems: [ - { name: 'Perspective', value: 'perspective' }, - { name: 'Perspective (Floor XOZ)', value: 'perspXOZ' }, - { name: 'Perspective (Floor YOZ)', value: 'perspYOZ' }, - { name: 'Perspective (Floor XOY)', value: 'perspXOY' }, - { name: 'Orthographic (XOY)', value: 'orthoXOY' }, - { name: 'Orthographic (XOZ)', value: 'orthoXOZ' }, - { name: 'Orthographic (ZOY)', value: 'orthoZOY' }, - { name: 'Orthographic (ZOX)', value: 'orthoZOX' }, - { name: 'Orthographic (XnOY)', value: 'orthoXNOY' }, - { name: 'Orthographic (XnOZ)', value: 'orthoXNOZ' }, - { name: 'Orthographic (ZnOY)', value: 'orthoZNOY' }, - { name: 'Orthographic (ZnOX)', value: 'orthoZNOX' } - ], - cameraOverlayItems: [ - { name: 'None', value: 'none' }, - { name: 'Bar', value: 'bar' }, - { name: 'Axis', value: 'axis' }, - { name: 'Grid', value: 'grid' }, - { name: 'Grid background', value: 'gridb' }, - { name: 'Grid foreground', value: 'gridf' } - ], - camera_kind: 'perspective', - camera_overlay: 'gridb', - rotate: false, - background: settings.DarkMode ? '#000000' : '#ffffff', - can_rotate: true, - _axis: 0, - instancing: 0, - _count: false, - // material properties - wireframe: false, - transparency: 0, - flatShading: false, - roughness: 0.5, - metalness: 0.5, - shininess: 0, - reflectivity: 0.5, - material_kind: 'lambert', - materialKinds: [ - { name: 'MeshLambertMaterial', value: 'lambert', emissive: true, props: [{ name: 'flatShading' }] }, - { name: 'MeshBasicMaterial', value: 'basic' }, - { name: 'MeshStandardMaterial', value: 'standard', emissive: true, - props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }] }, - { name: 'MeshPhysicalMaterial', value: 'physical', emissive: true, - props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }, { name: 'reflectivity', min: 0, max: 1, step: 0.001 }] }, - { name: 'MeshPhongMaterial', value: 'phong', emissive: true, - props: [{ name: 'flatShading' }, { name: 'shininess', min: 0, max: 100, step: 0.1 }] }, - { name: 'MeshNormalMaterial', value: 'normal', props: [{ name: 'flatShading' }] }, - { name: 'MeshDepthMaterial', value: 'depth' }, - { name: 'MeshMatcapMaterial', value: 'matcap' }, - { name: 'MeshToonMaterial', value: 'toon' } - ], - getMaterialCfg: function() { - let cfg; - this.materialKinds.forEach(item => { - if (item.value === this.material_kind) - cfg = item; - }); - return cfg; - } - }; + if (!member.readelem) { + console.error(`failed to create streamer for element ${member.typename} ${member.name} element ${element._typename} STL type ${element.fSTLtype}`); + member.func = function(buf, obj) { + const ver = buf.readVersion(); + buf.checkByteCount(ver); + obj[this.name] = null; + }; + } else if (!element.$fictional) { + member.read_version = function(buf, cnt) { + if (cnt === 0) + return null; + const ver = buf.readVersion(); + this.member_wise = Boolean(ver.val & kStreamedMemberWise); + + this.stl_version = undefined; + if (this.member_wise) { + ver.val &= ~kStreamedMemberWise; + this.stl_version = { val: buf.ntoi2() }; + if (this.stl_version.val <= 0) + this.stl_version.checksum = buf.ntou4(); + } + return ver; + }; + member.func = function(buf, obj) { + const ver = this.read_version(buf), + res = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; + }; + member.branch_func = function(buf, obj) { + // special function to read data from STL branch + const cnt = obj[this.stl_size], + ver = this.read_version(buf, cnt), + arr = new Array(cnt); - this.cleanup(true); - } + for (let n = 0; n < cnt; ++n) + arr[n] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - /** @summary Function callled by framework when dark mode is changed - * @private */ - changeDarkMode(mode) { - if ((this.ctrl.background === '#000000') || (this.ctrl.background === '#ffffff')) - this.changedBackground((mode ?? settings.DarkMode) ? '#000000' : '#ffffff'); - } + if (ver) + buf.checkByteCount(ver, `branch ${this.typename}`); - /** @summary Change drawing stage - * @private */ - changeStage(value, msg) { - this.drawing_stage = value; - if (!msg) { - switch (value) { - case stageInit: msg = 'Building done'; break; - case stageCollect: msg = 'collect visibles'; break; - case stageWorkerCollect: msg = 'worker collect visibles'; break; - case stageAnalyze: msg = 'Analyse visibles'; break; - case stageCollShapes: msg = 'collect shapes for building'; break; - case stageStartBuild: msg = 'Start build shapes'; break; - case stageWorkerBuild: msg = 'Worker build shapes'; break; - case stageBuild: msg = 'Build shapes'; break; - case stageBuildReady: msg = 'Build ready'; break; - case stageWaitMain: msg = 'Wait for main painter'; break; - case stageBuildProj: msg = 'Build projection'; break; - default: msg = `stage ${value}`; + obj[this.name] = arr; + }; + member.split_func = function(buf, arr, n) { + // function to read array from member-wise streaming + const ver = this.read_version(buf); + for (let i = 0; i < n; ++i) + arr[i][this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + buf.checkByteCount(ver, this.typename); + }; + member.objs_branch_func = function(buf, obj) { + // special function when branch read as part of complete object + // objects already preallocated and only appropriate member must be set + // see code in JSRoot.tree.js for reference + + const arr = obj[this.name0], // objects array where reading is done + ver = this.read_version(buf, arr.length); + + for (let n = 0; n < arr.length; ++n) { + const obj1 = this.get(arr, n); + obj1[this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + } + + if (ver) + buf.checkByteCount(ver, `branch ${this.typename}`); + }; } + break; } - this.drawing_log = msg; + + default: + console.error(`fail to provide function for ${element.fName} (${element.fTypeName}) typ = ${element.fType}`); + + member.func = function(/* buf, obj */) {}; // do nothing, fix in the future } - /** @summary Check drawing stage */ - isStage(value) { return value === this.drawing_stage; } + return member; +} - isBatchMode() { return isBatchMode() || this.batch_mode; } - /** @summary Create toolbar */ - createToolbar() { - if (this._toolbar || !this._webgl || this.ctrl.notoolbar || this.isBatchMode()) return; - const buttonList = [{ - name: 'toImage', - title: 'Save as PNG', - icon: 'camera', - click: () => this.createSnapshot() - }, { - name: 'control', - title: 'Toggle control UI', - icon: 'rect', - click: () => this.showControlGui('toggle') - }, { - name: 'enlarge', - title: 'Enlarge geometry drawing', - icon: 'circle', - click: () => this.toggleEnlarge() - }]; +/** @summary Let directly assign methods when doing I/O + * @private */ +function addClassMethods(clname, streamer) { + if (streamer === null) + return streamer; - // Only show VR icon if WebVR API available. - if (navigator.getVRDisplays) { - buttonList.push({ - name: 'entervr', - title: 'Enter VR (It requires a VR Headset connected)', - icon: 'vrgoggles', - click: () => this.toggleVRMode() - }); - this.initVRMode(); + const methods = getMethods(clname); + + if (methods) { + for (const key in methods) { + if (isFunc(methods[key]) || (key.indexOf('_') === 0)) + streamer.push({ name: key, method: methods[key], func(_buf, obj) { obj[this.name] = this.method; } }); } + } - if (settings.ContextMenu) { - buttonList.push({ - name: 'menu', - title: 'Show context menu', - icon: 'question', - click: evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); + return streamer; +} - if (closeMenu()) return; - createMenu(evnt, this).then(menu => { - menu.painter.fillContextMenu(menu); - menu.show(); - }); - } - }); - } +/* Copyright (C) 1999 Masanao Izumo + * Version: 1.0.0.1 + * LastModified: Dec 25 1999 + * original: http://www.onicos.com/staff/iz/amuse/javascript/expert/inflate.txt + */ - const bkgr = new Color(this.ctrl.background); +/* constant parameters */ +const + zip_WSIZE = 32768, // Sliding Window size - this._toolbar = new Toolbar(this.selectDom(), (bkgr.r + bkgr.g + bkgr.b) < 1, buttonList); + /* constant tables (inflate) */ + zip_MASK_BITS = [0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff], - this._toolbar.createButtons(); - } + // Tables for deflate from PKZIP's appnote.txt. + // Copy lengths for literal codes 257..285 + zip_cplens = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], - /** @summary Initialize VR mode */ - initVRMode() { - // Dolly contains camera and controllers in VR Mode - // Allows moving the user in the scene - this._dolly = new Group(); - this._scene.add(this._dolly); - this._standingMatrix = new Matrix4(); + /* note: see note #13 above about the 258 in this list. */ + // Extra bits for literal codes 257..285 + zip_cplext = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99], // 99==invalid - // Raycaster temp variables to avoid one per frame allocation. - this._raycasterEnd = new Vector3(); - this._raycasterOrigin = new Vector3(); + // Copy offsets for distance codes 0..29 + zip_cpdist = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577], - navigator.getVRDisplays().then(displays => { - const vrDisplay = displays[0]; - if (!vrDisplay) return; - this._renderer.vr.setDevice(vrDisplay); - this._vrDisplay = vrDisplay; - if (vrDisplay.stageParameters) - this._standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); + // Extra bits for distance codes + zip_cpdext = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13], - this.initVRControllersGeometry(); - }); - } + // Order of the bit length code lengths + zip_border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; - /** @summary Init VR controllers geometry - * @private */ - initVRControllersGeometry() { - const geometry = new SphereGeometry(0.025, 18, 36), - material = new MeshBasicMaterial({ color: 'grey', vertexColors: false }), - rayMaterial = new MeshBasicMaterial({ color: 'fuchsia', vertexColors: false }), - rayGeometry = new BoxGeometry(0.001, 0.001, 2), - ray1Mesh = new Mesh(rayGeometry, rayMaterial), - ray2Mesh = new Mesh(rayGeometry, rayMaterial), - sphere1 = new Mesh(geometry, material), - sphere2 = new Mesh(geometry, material); - - this._controllersMeshes = []; - this._controllersMeshes.push(sphere1); - this._controllersMeshes.push(sphere2); - ray1Mesh.position.z -= 1; - ray2Mesh.position.z -= 1; - sphere1.add(ray1Mesh); - sphere2.add(ray2Mesh); - this._dolly.add(sphere1); - this._dolly.add(sphere2); - // Controller mesh hidden by default - sphere1.visible = false; - sphere2.visible = false; - } +function ZIP_inflate(arr, tgt) { + /* variables (inflate) */ + const zip_slide = new Array(2 * zip_WSIZE), + zip_inflate_data = arr, + zip_inflate_datalen = arr.byteLength; + let zip_wp = 0, // current position in slide + zip_fixed_tl = null, // inflate static + zip_fixed_td, // inflate static + zip_fixed_bl, zip_fixed_bd, // inflate static + zip_bit_buf = 0, // bit buffer + zip_bit_len = 0, // bits in bit buffer + zip_method = -1, + zip_eof = false, + zip_copy_leng = 0, + zip_copy_dist = 0, + zip_tl = null, zip_td, // literal/length and distance decoder tables + zip_bl, zip_bd, // number of bits decoded by tl and td + zip_inflate_pos = 0; - /** @summary Update VR controllers list - * @private */ - updateVRControllersList() { - const gamepads = navigator.getGamepads && navigator.getGamepads(); - // Has controller list changed? - if (this.vrControllers && (gamepads.length === this.vrControllers.length)) return; - // Hide meshes. - this._controllersMeshes.forEach(mesh => { mesh.visible = false; }); - this._vrControllers = []; - for (let i = 0; i < gamepads.length; ++i) { - if (!gamepads[i] || !gamepads[i].pose) continue; - this._vrControllers.push({ - gamepad: gamepads[i], - mesh: this._controllersMeshes[i] - }); - this._controllersMeshes[i].visible = true; + function zip_NEEDBITS(n) { + while (zip_bit_len < n) { + if (zip_inflate_pos < zip_inflate_datalen) + zip_bit_buf |= zip_inflate_data[zip_inflate_pos++] << zip_bit_len; + zip_bit_len += 8; } } - /** @summary Process VR controller intersection - * @private */ - processVRControllerIntersections() { - let intersects = []; - for (let i = 0; i < this._vrControllers.length; ++i) { - const controller = this._vrControllers[i].mesh, - end = controller.localToWorld(this._raycasterEnd.set(0, 0, -1)), - origin = controller.localToWorld(this._raycasterOrigin.set(0, 0, 0)); - end.sub(origin).normalize(); - intersects = intersects.concat(this._controls.getOriginDirectionIntersects(origin, end)); - } - // Remove duplicates. - intersects = intersects.filter((item, pos) => { return intersects.indexOf(item) === pos; }); - this._controls.processMouseMove(intersects); + function zip_GETBITS(n) { + return zip_bit_buf & zip_MASK_BITS[n]; } - /** @summary Update VR controllers - * @private */ - updateVRControllers() { - this.updateVRControllersList(); - // Update pose. - for (let i = 0; i < this._vrControllers.length; ++i) { - const controller = this._vrControllers[i], - orientation = controller.gamepad.pose.orientation, - position = controller.gamepad.pose.position, - controllerMesh = controller.mesh; - if (orientation) controllerMesh.quaternion.fromArray(orientation); - if (position) controllerMesh.position.fromArray(position); - controllerMesh.updateMatrix(); - controllerMesh.applyMatrix4(this._standingMatrix); - controllerMesh.matrixWorldNeedsUpdate = true; - } - this.processVRControllerIntersections(); + function zip_DUMPBITS(n) { + zip_bit_buf >>= n; + zip_bit_len -= n; } - /** @summary Toggle VR mode - * @private */ - toggleVRMode() { - if (!this._vrDisplay) return; - // Toggle VR mode off - if (this._vrDisplay.isPresenting) { - this.exitVRMode(); - return; - } - this._previousCameraPosition = this._camera.position.clone(); - this._previousCameraRotation = this._camera.rotation.clone(); - this._vrDisplay.requestPresent([{ source: this._renderer.domElement }]).then(() => { - this._previousCameraNear = this._camera.near; - this._dolly.position.set(this._camera.position.x/4, -this._camera.position.y/8, -this._camera.position.z/4); - this._camera.position.set(0, 0, 0); - this._dolly.add(this._camera); - this._camera.near = 0.1; - this._camera.updateProjectionMatrix(); - this._renderer.vr.enabled = true; - this._renderer.setAnimationLoop(() => { - this.updateVRControllers(); - this.render3D(0); - }); - }); - this._renderer.vr.enabled = true; - - window.addEventListener('keydown', evnt => { - // Esc Key turns VR mode off - if (evnt.code === 'Escape') this.exitVRMode(); - }); - } + /* objects (inflate) */ + function zip_HuftBuild(b, // code lengths in bits (all assumed <= BMAX) + n, // number of codes (assumed <= N_MAX) + s, // number of simple-valued codes (0..s-1) + d, // list of base values for non-simple codes + e, // list of extra bits for non-simple codes + mm) { // maximum lookup bits + const BMAX = 16, // maximum bit length of any code + N_MAX = 288, // maximum number of codes in any set + c = Array(BMAX + 1).fill(0), // bit length count table + lx = Array(BMAX + 1).fill(0), // stack of bits per table + u = Array(BMAX).fill(null), // zip_HuftNode[BMAX][] table stack + v = Array(N_MAX).fill(0), // values in order of bit length + x = Array(BMAX + 1).fill(0), // bit offsets, then code stack + r = { e: 0, b: 0, n: 0, t: null }, // new zip_HuftNode(), // table entry for structure assignment + el = (n > 256) ? b[256] : BMAX, // set length of EOB code, if any + res = { + status: 0, // 0: success, 1: incomplete table, 2: bad input + root: null, // (zip_HuftList) starting table + m: 0 // maximum lookup bits, returns actual + }; + let rr, // temporary variable, use in assignment + a, // counter for codes of length k + f, // i repeats in table every f entries + h, // table level + j, // counter + k, // number of bits in current code + p = b, // pointer into c[], b[], or v[] + pidx = 0, // index of p + q, // (zip_HuftNode) points to current table + w, + xp, // pointer into x or c + y, // number of dummy codes added + z, // number of entries in current table + o, + tail = null, // (zip_HuftList) + i = n; // counter, current code - /** @summary Exit VR mode - * @private */ - exitVRMode() { - if (!this._vrDisplay.isPresenting) return; - this._renderer.vr.enabled = false; - this._dolly.remove(this._camera); - this._scene.add(this._camera); - // Restore Camera pose - this._camera.position.copy(this._previousCameraPosition); - this._previousCameraPosition = undefined; - this._camera.rotation.copy(this._previousCameraRotation); - this._previousCameraRotation = undefined; - this._camera.near = this._previousCameraNear; - this._camera.updateProjectionMatrix(); - this._vrDisplay.exitPresent(); - } + // Generate counts for each bit length + do + c[p[pidx++]]++; // assume all entries <= BMAX + while (--i > 0); - /** @summary Returns main geometry object */ - getGeometry() { - return this.getObject(); - } + if (c[0] === n) // null input--all zero length codes + return res; - /** @summary Modify visibility of provided node by name */ - modifyVisisbility(name, sign) { - if (getNodeKind(this.getGeometry()) !== 0) return; + // Find minimum and maximum length, bound *m by those + for (j = 1; j <= BMAX && !c[j]; ++j); - if (!name) - return setGeoBit(this.getGeometry().fVolume, geoBITS.kVisThis, (sign === '+')); + k = j; // minimum code length + if (mm < j) + mm = j; + for (i = BMAX; i && !c[i]; --i); - let regexp, exact = false; + const g = i; // maximum code length + if (mm > i) + mm = i; - // arg.node.fVolume - if (name.indexOf('*') < 0) { - regexp = new RegExp('^'+name+'$'); - exact = true; - } else { - regexp = new RegExp('^' + name.split('*').join('.*') + '$'); - exact = false; + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; ++j, y <<= 1) { + if ((y -= c[j]) < 0) { + res.status = 2; // bad input: more codes than bits + res.m = mm; + return res; + } + } + if ((y -= c[i]) < 0) { + res.status = 2; + res.m = mm; + return res; } + c[i] += y; - this.findNodeWithVolume(regexp, arg => { - setInvisibleAll(arg.node.fVolume, (sign !== '+')); - return exact ? arg : null; // continue search if not exact expression provided - }); - } + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = c; + pidx = 1; + xp = 2; + while (--i > 0) // note that i == g from above + x[xp++] = (j += p[pidx++]); - /** @summary Decode drawing options */ - decodeOptions(opt) { - if (!isStr(opt)) opt = ''; + // Make a table of values in order of bit lengths + p = b; + pidx = 0; + i = 0; + do { + if ((j = p[pidx++])) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; // set n to length of v - if (this.superimpose && (opt.indexOf('same') === 0)) - opt = opt.slice(4); + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = v; + pidx = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = lx[0] = 0; // no bits decoded yet + q = null; // ditto + z = 0; // ditto - const res = this.ctrl, + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; ++k) { + a = c[k]; + while (a-- > 0) { + // here i is the Huffman code of length k bits for value p[pidx] + // make tables up to required level + while (k > w + lx[1 + h]) { + w += lx[1 + h++]; // add bits already decoded - macro = opt.indexOf('macro:'); - if (macro >= 0) { - let separ = opt.indexOf(';', macro+6); - if (separ < 0) separ = opt.length; - res.script_name = opt.slice(macro+6, separ); - opt = opt.slice(0, macro) + opt.slice(separ+1); - console.log(`script ${res.script_name} rest ${opt}`); - } + // compute minimum size table less than or equal to *m bits + z = (z = g - w) > mm ? mm : z; // upper limit + if ((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + while (++j < z) { // try smaller tables up to z bits + if ((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + if (w + j > el && w < el) + j = el - w; // make EOB code end at table + z = 1 << j; // table entries for j-bit table + lx[1 + h] = j; // set table size in stack + + // allocate and link in new table + q = new Array(z); + for (o = 0; o < z; ++o) + q[o] = { e: 0, b: 0, n: 0, t: null }; // new zip_HuftNode + + if (tail === null) + tail = res.root = { next: null, list: null }; // new zip_HuftList(); + else + tail = tail.next = { next: null, list: null }; // new zip_HuftList(); + tail.next = null; + tail.list = q; + u[h] = q; // table starts after link + + /* connect to last table, if there is one */ + if (h > 0) { + x[h] = i; // save pattern for backing up + r.b = lx[h]; // bits to dump before this table + r.e = 16 + j; // bits in this table + r.t = q; // pointer to this table + j = (i & ((1 << w) - 1)) >> (w - lx[h]); + rr = u[h - 1][j]; + rr.e = r.e; + rr.b = r.b; + rr.n = r.n; + rr.t = r.t; + } + } - while (true) { - const pp = opt.indexOf('+'), pm = opt.indexOf('-'); - if ((pp < 0) && (pm < 0)) break; - let p1 = pp, sign = '+'; - if ((p1 < 0) || ((pm >= 0) && (pm < pp))) { p1 = pm; sign = '-'; } + // set up table entry in r + r.b = k - w; + if (pidx >= n) + r.e = 99; // out of values--invalid code + else if (p[pidx] < s) { + r.e = (p[pidx] < 256 ? 16 : 15); // 256 is end-of-block code + r.n = p[pidx++]; // simple code is just the value + } else { + r.e = e[p[pidx] - s]; // non-simple--look up in lists + r.n = d[p[pidx++] - s]; + } - let p2 = p1+1; - const regexp = /[,; .]/; - while ((p2 < opt.length) && !regexp.test(opt[p2]) && (opt[p2] !== '+') && (opt[p2] !== '-')) p2++; + // fill code-like entries with r // + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) { + rr = q[j]; + rr.e = r.e; + rr.b = r.b; + rr.n = r.n; + rr.t = r.t; + } - const name = opt.substring(p1+1, p2); - opt = opt.slice(0, p1) + opt.slice(p2); + // backwards increment the k-bit code i + for (j = 1 << (k - 1); (i & j); j >>= 1) + i ^= j; + i ^= j; - this.modifyVisisbility(name, sign); + // backup over finished tables + while ((i & ((1 << w) - 1)) !== x[h]) + w -= lx[h--]; // don't need to update q + } } - const d = new DrawOptions(opt); - - if (d.check('MAIN')) res.is_main = true; - - if (d.check('TRACKS')) res.tracks = true; // only for TGeoManager - if (d.check('SHOWTOP')) res.showtop = true; // only for TGeoManager - if (d.check('NO_SCREEN')) res.no_screen = true; // ignore kVisOnScreen bits for visibility + /* return actual size of base table */ + res.m = lx[1]; - if (d.check('NOINSTANCING')) res.instancing = -1; // disable usage of InstancedMesh - if (d.check('INSTANCING')) res.instancing = 1; // force usage of InstancedMesh + /* Return true (1) if we were given an incomplete table */ + res.status = (y && g !== 1) ? 1 : 0; - if (d.check('ORTHO_CAMERA')) { res.camera_kind = 'orthoXOY'; res.can_rotate = 0; } - if (d.check('ORTHO', true)) { res.camera_kind = 'ortho' + d.part; res.can_rotate = 0; } - if (d.check('OVERLAY', true)) res.camera_overlay = d.part.toLowerCase(); - if (d.check('CAN_ROTATE')) res.can_rotate = true; - if (d.check('PERSPECTIVE')) { res.camera_kind = 'perspective'; res.can_rotate = true; } - if (d.check('PERSP', true)) { res.camera_kind = 'persp' + d.part; res.can_rotate = true; } - if (d.check('MOUSE_CLICK')) res.mouse_click = true; + return res; + } - if (d.check('DEPTHRAY') || d.check('DRAY')) res.depthMethod = 'ray'; - if (d.check('DEPTHBOX') || d.check('DBOX')) res.depthMethod = 'box'; - if (d.check('DEPTHPNT') || d.check('DPNT')) res.depthMethod = 'pnt'; - if (d.check('DEPTHSIZE') || d.check('DSIZE')) res.depthMethod = 'size'; - if (d.check('DEPTHDFLT') || d.check('DDFLT')) res.depthMethod = 'dflt'; + /* routines (inflate) */ - if (d.check('ZOOM', true)) res.zoom = d.partAsFloat(0, 100) / 100; - if (d.check('ROTY', true)) res.rotatey = d.partAsFloat(); - if (d.check('ROTZ', true)) res.rotatez = d.partAsFloat(); + function zip_inflate_codes(buff, off, size) { + if (size === 0) + return 0; - if (d.check('PHONG')) res.material_kind = 'phong'; - if (d.check('LAMBERT')) res.material_kind = 'lambert'; - if (d.check('MATCAP')) res.material_kind = 'matcap'; - if (d.check('TOON')) res.material_kind = 'toon'; + /* inflate (decompress) the codes in a deflated (compressed) block. + Return an error code or zero if it all goes ok. */ - if (d.check('AMBIENT')) res.light.kind = 'ambient'; + let e, // table entry flag/number of extra bits + t, // (zip_HuftNode) pointer to table entry + n = 0; - const getCamPart = () => { - let neg = 1; - if (d.part[0] === 'N') { - neg = -1; - d.part = d.part.slice(1); + // inflate the coded data + for (;;) { // do until end of block + zip_NEEDBITS(zip_bl); + t = zip_tl.list[zip_GETBITS(zip_bl)]; + e = t.e; + while (e > 16) { + if (e === 99) + return -1; + zip_DUMPBITS(t.b); + e -= 16; + zip_NEEDBITS(e); + t = t.t[zip_GETBITS(e)]; + e = t.e; } - return neg * d.partAsFloat(); - }; - - if (d.check('CAMX', true)) res.camx = getCamPart(); - if (d.check('CAMY', true)) res.camy = getCamPart(); - if (d.check('CAMZ', true)) res.camz = getCamPart(); - if (d.check('CAMLX', true)) res.camlx = getCamPart(); - if (d.check('CAMLY', true)) res.camly = getCamPart(); - if (d.check('CAMLZ', true)) res.camlz = getCamPart(); - - if (d.check('BLACK')) res.background = '#000000'; - if (d.check('WHITE')) res.background = '#FFFFFF'; + zip_DUMPBITS(t.b); - if (d.check('BKGR_', true)) { - let bckgr = null; - if (d.partAsInt(1) > 0) - bckgr = getColor(d.partAsInt()); - else { - for (let col = 0; col < 8; ++col) { - if (getColor(col).toUpperCase() === d.part) - bckgr = getColor(col); - } + if (e === 16) { // then it's a literal + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] = t.n; + if (n === size) + return size; + continue; } - if (bckgr) res.background = '#' + new Color(bckgr).getHexString(); - } - if (d.check('R3D_', true)) - res.Render3D = constants$1.Render3D.fromString(d.part.toLowerCase()); - - if (d.check('MORE', true)) res.more = d.partAsInt(0, 2) ?? 2; - if (d.check('ALL')) { res.more = 100; res.vislevel = 99; } + // exit if end of block + if (e === 15) + break; - if (d.check('VISLVL', true)) res.vislevel = d.partAsInt(); - if (d.check('MAXNODES', true)) res.maxnodes = d.partAsInt(); - if (d.check('MAXFACES', true)) res.maxfaces = d.partAsInt(); + // it's an EOB or a length - if (d.check('CONTROLS') || d.check('CTRL')) res.show_controls = true; + // get length of block to copy + zip_NEEDBITS(e); + zip_copy_leng = t.n + zip_GETBITS(e); + zip_DUMPBITS(e); - if (d.check('CLIPXYZ')) res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; - if (d.check('CLIPX')) res.clip[0].enabled = true; - if (d.check('CLIPY')) res.clip[1].enabled = true; - if (d.check('CLIPZ')) res.clip[2].enabled = true; - if (d.check('CLIP')) res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; + // decode distance of block to copy + zip_NEEDBITS(zip_bd); + t = zip_td.list[zip_GETBITS(zip_bd)]; + e = t.e; - if (d.check('PROJX', true)) { res.project = 'x'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } - if (d.check('PROJY', true)) { res.project = 'y'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } - if (d.check('PROJZ', true)) { res.project = 'z'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } + while (e > 16) { + if (e === 99) + return -1; + zip_DUMPBITS(t.b); + e -= 16; + zip_NEEDBITS(e); + t = t.t[zip_GETBITS(e)]; + e = t.e; + } + zip_DUMPBITS(t.b); + zip_NEEDBITS(e); + zip_copy_dist = zip_wp - t.n - zip_GETBITS(e); + zip_DUMPBITS(e); - if (d.check('DFLT_COLORS') || d.check('DFLT')) res.dflt_colors = true; - d.check('SSAO'); // deprecated - if (d.check('NOBLOOM')) res.highlight_bloom = false; - if (d.check('BLOOM')) res.highlight_bloom = true; - if (d.check('OUTLINE')) res.outline = true; + // do the copy + while (zip_copy_leng > 0 && n < size) { + --zip_copy_leng; + zip_copy_dist &= zip_WSIZE - 1; + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] = zip_slide[zip_copy_dist++]; + } - if (d.check('NOWORKER')) res.use_worker = -1; - if (d.check('WORKER')) res.use_worker = 1; + if (n === size) + return size; + } - if (d.check('NOFOG')) res.use_fog = false; - if (d.check('FOG')) res.use_fog = true; + zip_method = -1; // done + return n; + } - if (d.check('NOHIGHLIGHT') || d.check('NOHIGH')) res.highlight_scene = res.highlight = false; - if (d.check('HIGHLIGHT')) res.highlight_scene = res.highlight = true; - if (d.check('HSCENEONLY')) { res.highlight_scene = true; res.highlight = false; } - if (d.check('NOHSCENE')) res.highlight_scene = false; - if (d.check('HSCENE')) res.highlight_scene = true; + function zip_inflate_stored(buff, off, size) { + /* 'decompress' an inflated type 0 (stored) block. */ - if (d.check('WIREFRAME') || d.check('WIRE')) res.wireframe = true; - if (d.check('ROTATE')) res.rotate = true; + // go to byte boundary + let n = zip_bit_len & 7; + zip_DUMPBITS(n); - if (d.check('INVX') || d.check('INVERTX')) res.scale.x = -1; - if (d.check('INVY') || d.check('INVERTY')) res.scale.y = -1; - if (d.check('INVZ') || d.check('INVERTZ')) res.scale.z = -1; + // get the length and its complement + zip_NEEDBITS(16); + n = zip_GETBITS(16); + zip_DUMPBITS(16); + zip_NEEDBITS(16); + if (n !== ((~zip_bit_buf) & 0xffff)) + return -1; // error in compressed data + zip_DUMPBITS(16); - if (d.check('COUNT')) res._count = true; + // read and output the compressed data + zip_copy_leng = n; - if (d.check('TRANSP', true)) - res.transparency = d.partAsInt(0, 100)/100; + n = 0; + while (zip_copy_leng > 0 && n < size) { + --zip_copy_leng; + zip_wp &= zip_WSIZE - 1; + zip_NEEDBITS(8); + buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8); + zip_DUMPBITS(8); + } - if (d.check('OPACITY', true)) - res.transparency = 1 - d.partAsInt(0, 100)/100; + if (zip_copy_leng === 0) + zip_method = -1; // done + return n; + } - if (d.check('AXISCENTER') || d.check('AXISC') || d.check('AC')) res._axis = 2; - if (d.check('AXIS') || d.check('A')) res._axis = 1; + function zip_inflate_fixed(buff, off, size) { + /* decompress an inflated type 1 (fixed Huffman codes) block. We should + either replace this with a custom decoder, or at least pre-compute the + Huffman tables. */ - if (d.check('TRR', true)) res.trans_radial = d.partAsInt()/100; - if (d.check('TRZ', true)) res.trans_z = d.partAsInt()/100; + // if first time, set up tables for fixed blocks + if (zip_fixed_tl === null) { + // literal table + const l = Array(288).fill(8, 0, 144).fill(9, 144, 256).fill(7, 256, 280).fill(8, 280, 288); + // make a complete, but wrong code set + zip_fixed_bl = 7; + let h = zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext, zip_fixed_bl); + if (h.status) + throw new Error('HufBuild error: ' + h.status); + zip_fixed_tl = h.root; + zip_fixed_bl = h.m; - if (d.check('W')) res.wireframe = true; - if (d.check('Y')) res._yup = true; - if (d.check('Z')) res._yup = false; + // distance table + l.fill(5, 0, 30); // make an incomplete code set + zip_fixed_bd = 5; - // when drawing geometry without TCanvas, yup = true by default - if (res._yup === undefined) - res._yup = this.getCanvSvg().empty(); + h = zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd); + if (h.status > 1) { + zip_fixed_tl = null; + throw new Error('HufBuild error: ' + h.status); + } + zip_fixed_td = h.root; + zip_fixed_bd = h.m; + } - // let reuse for storing origin options - this.options = res; + zip_tl = zip_fixed_tl; + zip_td = zip_fixed_td; + zip_bl = zip_fixed_bl; + zip_bd = zip_fixed_bd; + return zip_inflate_codes(buff, off, size); } - /** @summary Activate specified items in the browser */ - activateInBrowser(names, force) { - if (isStr(names)) names = [names]; - - if (this._hpainter) { - // show browser if it not visible + function zip_inflate_dynamic(buff, off, size) { + // decompress an inflated type 2 (dynamic Huffman codes) block. + let i, j, // temporary variables + l, // last length + t, // (zip_HuftNode) literal/length code table + h; // (zip_HuftBuild) + const ll = new Array(286 + 30).fill(0); // literal/length and distance code lengths - this._hpainter.activateItems(names, force); + // read in table lengths + zip_NEEDBITS(5); + const nl = 257 + zip_GETBITS(5); // number of literal/length codes + zip_DUMPBITS(5); + zip_NEEDBITS(5); + const nd = 1 + zip_GETBITS(5); // number of distance codes + zip_DUMPBITS(5); + zip_NEEDBITS(4); + const nb = 4 + zip_GETBITS(4); // number of bit length codes + zip_DUMPBITS(4); + if (nl > 286 || nd > 30) + return -1; // bad lengths - // if highlight in the browser disabled, suppress in few seconds - if (!this.ctrl.update_browser) - setTimeout(() => this._hpainter.activateItems([]), 2000); + // read in bit-length-code lengths + for (j = 0; j < nb; ++j) { + zip_NEEDBITS(3); + ll[zip_border[j]] = zip_GETBITS(3); + zip_DUMPBITS(3); } - } - - /** @summary method used to check matrix calculations performance with current three.js model */ - testMatrixes() { - let errcnt = 0, totalcnt = 0, totalmax = 0; - - const arg = { - domatrix: true, - func: (/* node */) => { - let m2 = this.getmatrix(); - const entry = this.copyStack(), - mesh = this._clones.createObject3D(entry.stack, this._toplevel, 'mesh'); - if (!mesh) return true; - - totalcnt++; - - const m1 = mesh.matrixWorld; - if (m1.equals(m2)) return true; - if ((m1.determinant() > 0) && (m2.determinant() < -0.9)) { - const flip = new Vector3(1, 1, -1); - m2 = m2.clone().scale(flip); - if (m1.equals(m2)) return true; - } - - let max = 0; - for (let k = 0; k < 16; ++k) - max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k])); - - totalmax = Math.max(max, totalmax); - - if (max < 1e-4) return true; + for (; j < 19; ++j) + ll[zip_border[j]] = 0; - console.log(`${this._clones.resolveStack(entry.stack).name} maxdiff ${max} determ ${m1.determinant()} ${m2.determinant()}`); + // build decoding table for trees--single level, 7 bit lookup + zip_bl = 7; + h = zip_HuftBuild(ll, 19, 19, null, null, zip_bl); + if (h.status) + return -1; // incomplete code set - errcnt++; + zip_tl = h.root; + zip_bl = h.m; - return false; - } - }, + // read in literal and distance code lengths + const n = nl + nd; // number of lengths to get + i = l = 0; + while (i < n) { + zip_NEEDBITS(zip_bl); + t = zip_tl.list[zip_GETBITS(zip_bl)]; + j = t.b; + zip_DUMPBITS(j); + j = t.n; + if (j < 16) // length of code in bits (0..15) + ll[i++] = l = j; // save last length in l + else if (j === 16) { // repeat last length 3 to 6 times + zip_NEEDBITS(2); + j = 3 + zip_GETBITS(2); + zip_DUMPBITS(2); + if (i + j > n) + return -1; + while (j-- > 0) + ll[i++] = l; + } else if (j === 17) { // 3 to 10 zero length codes + zip_NEEDBITS(3); + j = 3 + zip_GETBITS(3); + zip_DUMPBITS(3); + if (i + j > n) + return -1; + while (j-- > 0) + ll[i++] = 0; + l = 0; + } else { // j == 18: 11 to 138 zero length codes + zip_NEEDBITS(7); + j = 11 + zip_GETBITS(7); + zip_DUMPBITS(7); + if (i + j > n) + return -1; + while (j-- > 0) + ll[i++] = 0; + l = 0; + } + } - tm1 = new Date().getTime(); + // build the decoding tables for literal/length and distance codes + zip_bl = 9; // zip_lbits; + h = zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl); + if (zip_bl === 0) // no literals or lengths + h.status = 1; + if (h.status) + return -1; // incomplete code set + zip_tl = h.root; + zip_bl = h.m; - /* let cnt = */ this._clones.scanVisible(arg); + for (i = 0; i < nd; ++i) + ll[i] = ll[i + nl]; + zip_bd = 6; // zip_dbits; + h = zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd); + zip_td = h.root; + zip_bd = h.m; - const tm2 = new Date().getTime(); + // incomplete distance tree + if ((zip_bd === 0 && nl > 257) || h.status) // lengths but no distances + return -1; - console.log(`Compare matrixes total ${totalcnt} errors ${errcnt} takes ${tm2-tm1} maxdiff ${totalmax}`); + // decompress until an end-of-block code + return zip_inflate_codes(buff, off, size); } - /** @summary Fill context menu */ - fillContextMenu(menu) { - menu.add('header: Draw options'); - - menu.addchk(this.ctrl.update_browser, 'Browser update', () => { - this.ctrl.update_browser = !this.ctrl.update_browser; - if (!this.ctrl.update_browser) this.activateInBrowser([]); - }); - menu.addchk(this.ctrl.show_controls, 'Show Controls', () => this.showControlGui('toggle')); - - menu.add('sub:Show axes', () => this.setAxesDraw('toggle')); - menu.addchk(this.ctrl._axis === 0, 'off', 0, arg => this.setAxesDraw(parseInt(arg))); - menu.addchk(this.ctrl._axis === 1, 'side', 1, arg => this.setAxesDraw(parseInt(arg))); - menu.addchk(this.ctrl._axis === 2, 'center', 2, arg => this.setAxesDraw(parseInt(arg))); - menu.add('endsub:'); - - if (this.geo_manager) - menu.addchk(this.ctrl.showtop, 'Show top volume', () => this.setShowTop(!this.ctrl.showtop)); - - menu.addchk(this.ctrl.wireframe, 'Wire frame', () => this.toggleWireFrame()); + function zip_inflate_internal(buff, off, size) { + // decompress an inflated entry + let n = 0, i; - if (!this.getCanvPainter()) - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + while (n < size) { + if (zip_eof && zip_method === -1) + return n; - menu.add('sub:Highlight'); + if (zip_copy_leng > 0) { + if (zip_method /* zip_STORED_BLOCK */) { + // STATIC_TREES or DYN_TREES + while (zip_copy_leng > 0 && n < size) { + --zip_copy_leng; + zip_copy_dist &= zip_WSIZE - 1; + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] = + zip_slide[zip_copy_dist++]; + } + } else { + while (zip_copy_leng > 0 && n < size) { + --zip_copy_leng; + zip_wp &= zip_WSIZE - 1; + zip_NEEDBITS(8); + buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8); + zip_DUMPBITS(8); + } + if (zip_copy_leng === 0) + zip_method = -1; // done + } + if (n === size) + return n; + } - menu.addchk(!this.ctrl.highlight, 'Off', () => { - this.ctrl.highlight = false; - this.changedHighlight(); - }); - menu.addchk(this.ctrl.highlight && !this.ctrl.highlight_bloom, 'Normal', () => { - this.ctrl.highlight = true; - this.ctrl.highlight_bloom = false; - this.changedHighlight(); - }); - menu.addchk(this.ctrl.highlight && this.ctrl.highlight_bloom, 'Bloom', () => { - this.ctrl.highlight = true; - this.ctrl.highlight_bloom = true; - this.changedHighlight(); - }); + if (zip_method === -1) { + if (zip_eof) + break; - menu.add('separator'); + // read in last block bit + zip_NEEDBITS(1); + if (zip_GETBITS(1)) + zip_eof = true; + zip_DUMPBITS(1); - menu.addchk(this.ctrl.highlight_scene, 'Scene', flag => { - this.ctrl.highlight_scene = flag; - this.changedHighlight(); - }); + // read in block type + zip_NEEDBITS(2); + zip_method = zip_GETBITS(2); + zip_DUMPBITS(2); + zip_tl = null; + zip_copy_leng = 0; + } - menu.add('endsub:'); + switch (zip_method) { + case 0: // zip_STORED_BLOCK + i = zip_inflate_stored(buff, off + n, size - n); + break; - menu.add('sub:Camera'); - menu.add('Reset position', () => this.focusCamera()); - if (!this.ctrl.project) - menu.addchk(this.ctrl.rotate, 'Autorotate', () => this.setAutoRotate(!this.ctrl.rotate)); + case 1: // zip_STATIC_TREES + if (zip_tl !== null) + i = zip_inflate_codes(buff, off + n, size - n); + else + i = zip_inflate_fixed(buff, off + n, size - n); + break; - if (!this._geom_viewer) { - menu.addchk(this.canRotateCamera(), 'Can rotate', () => this.changeCanRotate(!this.ctrl.can_rotate)); + case 2: // zip_DYN_TREES + if (zip_tl !== null) + i = zip_inflate_codes(buff, off + n, size - n); + else + i = zip_inflate_dynamic(buff, off + n, size - n); + break; - menu.add('Get position', () => menu.info('Position (as url)', '&opt=' + this.produceCameraUrl())); - if (!this.isOrthoCamera()) { - menu.add('Absolute position', () => { - const url = this.produceCameraUrl(true), p = url.indexOf('camlx'); - menu.info('Position (as url)', '&opt=' + ((p < 0) ? url : url.slice(0, p) + '\n' + url.slice(p))); - }); + default: // error + i = -1; + break; } - menu.add('sub:Kind'); - this.ctrl.cameraKindItems.forEach(item => - menu.addchk(this.ctrl.camera_kind === item.value, item.name, item.value, arg => { - this.ctrl.camera_kind = arg; - this.changeCamera(); - })); - menu.add('endsub:'); - - if (this.isOrthoCamera()) { - menu.add('sub:Overlay'); - this.ctrl.cameraOverlayItems.forEach(item => - menu.addchk(this.ctrl.camera_overlay === item.value, item.name, item.value, arg => { - this.ctrl.camera_overlay = arg; - this.changeCamera(); - })); - menu.add('endsub:'); - } + if (i === -1) + return zip_eof ? 0 : -1; + n += i; } - menu.add('endsub:'); - - menu.addchk(this.ctrl.select_in_view, 'Select in view', () => { - this.ctrl.select_in_view = !this.ctrl.select_in_view; - if (this.ctrl.select_in_view) this.startDrawGeometry(); - }); - } - - /** @summary Method used to set transparency for all geometrical shapes - * @param {number|Function} transparency - one could provide function - * @param {boolean} [skip_render] - if specified, do not perform rendering */ - changedGlobalTransparency(transparency) { - const func = isFunc(transparency) ? transparency : null; - if (func || (transparency === undefined)) - transparency = this.ctrl.transparency; - - this._toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node?.material?.inherentOpacity === undefined) - return; - - const t = func ? func(node) : undefined; - if (t !== undefined) - node.material.opacity = 1 - t; - else - node.material.opacity = Math.min(1 - (transparency || 0), node.material.inherentOpacity); - - node.material.depthWrite = node.material.opacity === 1; - node.material.transparent = node.material.opacity < 1; - }); - - this.render3D(); - } - - /** @summary Method used to interactively change material kinds */ - changedMaterial() { - this._toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node.material?.inherentArgs !== undefined) - node.material = createMaterial(this.ctrl, node.material.inherentArgs); - }); - - this.render3D(-1); + return n; } - /** @summary Change for all materials that property */ - changeMaterialProperty(name) { - const value = this.ctrl[name]; - if (value === undefined) - return console.error('No property ', name); + let i, cnt = 0; + while ((i = zip_inflate_internal(tgt, cnt, Math.min(1024, tgt.byteLength - cnt))) > 0) + cnt += i; - this._toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node.material?.inherentArgs === undefined) return; + return cnt; +} // function ZIP_inflate - if (node.material[name] !== undefined) { - node.material[name] = value; - node.material.needsUpdate = true; - } - }); +/** + * https://github.com/pierrec/node-lz4/blob/master/lib/binding.js + * + * LZ4 based compression and decompression + * Copyright (c) 2014 Pierre Curto + * MIT Licensed + */ - this.render3D(); - } +/** + * Decode a block. Assumptions: input contains all sequences of a + * chunk, output is large enough to receive the decoded data. + * If the output buffer is too small, an error will be thrown. + * If the returned value is negative, an error occurred at the returned offset. + * + * @param input {Buffer} input data + * @param output {Buffer} output data + * @return {Number} number of decoded bytes + * @private */ +function LZ4_uncompress(input, output, sIdx, eIdx) { + sIdx = sIdx || 0; + eIdx = eIdx || (input.length - sIdx); + // Process each sequence in the incoming data + let j = 0; + for (let i = sIdx, n = eIdx; i < n;) { + const token = input[i++]; - /** @summary Reset transformation */ - resetTransformation() { - this.changedTransformation('reset'); - } + // Literals + let literals_length = (token >> 4); + if (literals_length > 0) { + // length of literals + let l = literals_length + 240; + while (l === 255) { + l = input[i++]; + literals_length += l; + } - /** @summary Method should be called when transformation parameters were changed */ - changedTransformation(arg) { - if (!this._toplevel) return; + // Copy the literals + const end = i + literals_length; + while (i < end) + output[j++] = input[i++]; - const ctrl = this.ctrl, - translation = new Matrix4(), - vect2 = new Vector3(); + // End of buffer? + if (i === n) + return j; + } - if (arg === 'reset') - ctrl.trans_z = ctrl.trans_radial = 0; + // Match copy + // 2 bytes offset (little endian) + const offset = input[i++] | (input[i++] << 8); - this._toplevel.traverse(mesh => { - if (mesh.stack !== undefined) { - const node = mesh.parent; + // 0 is an invalid offset value + if (offset === 0 || offset > j) + return -(i - 2); - if (arg === 'reset') { - if (node.matrix0) { - node.matrix.copy(node.matrix0); - node.matrix.decompose(node.position, node.quaternion, node.scale); - node.matrixWorldNeedsUpdate = true; - } - delete node.matrix0; - delete node.vect0; - delete node.vect1; - delete node.minvert; - return; - } + // length of match copy + let match_length = (token & 0xf), + l = match_length + 240; + while (l === 255) { + l = input[i++]; + match_length += l; + } - if (node.vect0 === undefined) { - node.matrix0 = node.matrix.clone(); - node.minvert = new Matrix4().copy(node.matrixWorld).invert(); + // Copy the match + let pos = j - offset; // position of the match copy in the current output + const end = j + match_length + 4; // minmatch = 4; + while (j < end) + output[j++] = output[pos++]; + } - const box3 = getBoundingBox(mesh, null, true), - signz = mesh._flippedMesh ? -1 : 1; + return j; +} - // real center of mesh in local coordinates - node.vect0 = new Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld); - node.vect1 = new Vector3(0, 0, 0).applyMatrix4(node.minvert); - } - vect2.set(ctrl.trans_radial * node.vect0.x, ctrl.trans_radial * node.vect0.y, ctrl.trans_z * node.vect0.z).applyMatrix4(node.minvert).sub(node.vect1); +/** @summary Reads header envelope, determines zipped size and unzip content + * @return {Promise} with unzipped content + * @private */ +async function R__unzip(arr, tgtsize, noalert, src_shift) { + const HDRSIZE = 9, totallen = arr.byteLength; - node.matrix.multiplyMatrices(node.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); - node.matrix.decompose(node.position, node.quaternion, node.scale); - node.matrixWorldNeedsUpdate = true; - } else if (mesh.stacks !== undefined) { - mesh.instanceMatrix.needsUpdate = true; + let curr = src_shift || 0, fullres = 0, tgtbuf = null; - if (arg === 'reset') { - mesh.trans?.forEach((item, i) => { - mesh.setMatrixAt(i, item.matrix0); - }); - delete mesh.trans; - return; - } + const nextPortion = () => { + while (fullres < tgtsize) { + let fmt = 'unknown', off = 0, CHKSUM = 0; - if (mesh.trans === undefined) { - mesh.trans = new Array(mesh.count); + if (curr + HDRSIZE >= totallen) { + if (!noalert) + console.error('Error R__unzip: header size exceeds buffer size'); + return Promise.resolve(null); + } - mesh.geometry.computeBoundingBox(); + const getCode = o => arr.getUint8(o), + checkChar = (o, symb) => { return getCode(o) === symb.charCodeAt(0); }, + checkFmt = (a, b, c) => { return checkChar(curr, a) && checkChar(curr + 1, b) && (getCode(curr + 2) === c); }; - for (let i = 0; i < mesh.count; i++) { - const item = { - matrix0: new Matrix4(), - minvert: new Matrix4() - }; + if (checkFmt('Z', 'L', 8)) { + fmt = 'new'; + off = 2; + } else if (checkFmt('C', 'S', 8)) + fmt = 'old'; + else if (checkFmt('X', 'Z', 0)) + fmt = 'LZMA'; + else if (checkFmt('Z', 'S', 1)) + fmt = 'ZSTD'; + else if (checkChar(curr, 'L') && checkChar(curr + 1, '4')) { + fmt = 'LZ4'; + CHKSUM = 8; + } - mesh.trans[i] = item; + /* C H E C K H E A D E R */ + if ((fmt !== 'new') && (fmt !== 'old') && (fmt !== 'LZ4') && (fmt !== 'ZSTD') && (fmt !== 'LZMA')) { + if (!noalert) + console.error(`R__unzip: ${fmt} format is not supported!`); + return Promise.resolve(null); + } - mesh.getMatrixAt(i, item.matrix0); - item.minvert.copy(item.matrix0).invert(); + const srcsize = HDRSIZE + ((getCode(curr + 3) & 0xff) | ((getCode(curr + 4) & 0xff) << 8) | ((getCode(curr + 5) & 0xff) << 16)), + uint8arr = new Uint8Array(arr.buffer, arr.byteOffset + curr + HDRSIZE + off + CHKSUM, Math.min(arr.byteLength - curr - HDRSIZE - off - CHKSUM, srcsize - HDRSIZE - CHKSUM)); - const box3 = new Box3().copy(mesh.geometry.boundingBox).applyMatrix4(item.matrix0); + if (!tgtbuf) + tgtbuf = new ArrayBuffer(tgtsize); + const tgt8arr = new Uint8Array(tgtbuf, fullres); - item.vect0 = new Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, (box3.max.z + box3.min.z) / 2); - item.vect1 = new Vector3(0, 0, 0).applyMatrix4(item.minvert); - } + if (fmt === 'ZSTD') { + let promise; + if (internals._ZstdStream) + promise = Promise.resolve(internals._ZstdStream); + else if (internals._ZstdInit !== undefined) + promise = new Promise(resolveFunc => { internals._ZstdInit.push(resolveFunc); }); + else { + internals._ZstdInit = []; + promise = (isNodeJs() ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }) : Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; })) + .then(({ ZstdInit }) => ZstdInit()) + .then(({ ZstdStream }) => { + internals._ZstdStream = ZstdStream; + internals._ZstdInit.forEach(func => func(ZstdStream)); + delete internals._ZstdInit; + return ZstdStream; + }); } - const mm = new Matrix4(); - - mesh.trans?.forEach((item, i) => { - vect2.set(ctrl.trans_radial * item.vect0.x, ctrl.trans_radial * item.vect0.y, ctrl.trans_z * item.vect0.z).applyMatrix4(item.minvert).sub(item.vect1); + return promise.then(ZstdStream => { + const data2 = ZstdStream.decompress(uint8arr), + reslen = data2.length; - mm.multiplyMatrices(item.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); + for (let i = 0; i < reslen; ++i) + tgt8arr[i] = data2[i]; - mesh.setMatrixAt(i, mm); + fullres += reslen; + curr += srcsize; + return nextPortion(); + }); + } else if (fmt === 'LZMA') { + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(lzma => { + const expected_len = (getCode(curr + 6) & 0xff) | ((getCode(curr + 7) & 0xff) << 8) | ((getCode(curr + 8) & 0xff) << 16), + reslen = lzma.decompress(uint8arr, tgt8arr, expected_len); + fullres += reslen; + curr += srcsize; + return nextPortion(); }); } - }); - - this._toplevel.updateMatrixWorld(); - - // axes drawing always triggers rendering - if (arg !== 'norender') - this.drawAxesAndOverlay(); - } - - /** @summary Should be called when autorotate property changed */ - changedAutoRotate() { - this.autorotate(2.5); - } - - /** @summary Method should be called when changing axes drawing */ - changedAxes() { - if (isStr(this.ctrl._axis)) - this.ctrl._axis = parseInt(this.ctrl._axis); - this.drawAxesAndOverlay(); - } + const reslen = (fmt === 'LZ4') ? LZ4_uncompress(uint8arr, tgt8arr) : ZIP_inflate(uint8arr, tgt8arr); - /** @summary Method should be called to change background color */ - changedBackground(val) { - if (val !== undefined) - this.ctrl.background = val; - this._scene.background = new Color(this.ctrl.background); - this._renderer.setClearColor(this._scene.background, 1); - this.render3D(0); + if (reslen <= 0) + break; + fullres += reslen; + curr += srcsize; + } - if (this._toolbar) { - const bkgr = new Color(this.ctrl.background); - this._toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1); + if (fullres !== tgtsize) { + if (!noalert) + console.error(`R__unzip: fail to unzip data expects ${tgtsize}, got ${fullres}`); + return Promise.resolve(null); } - } - /** @summary Display control GUI */ - showControlGui(on) { - // while complete geo drawing can be removed until dat is loaded - just check and ignore callback - if (!this.ctrl) return; + return Promise.resolve(new DataView(tgtbuf)); + }; - if (on === 'toggle') - on = !this._gui; - else if (on === undefined) - on = this.ctrl.show_controls; + return nextPortion(); +} - this.ctrl.show_controls = on; +/** + * @summary Buffer object to read data from TFile + * + * @private + */ - if (this._gui) { - if (!on) { - this._gui.destroy(); - delete this._gui; - } - return; - } +class TBuffer { - if (!on || !this._renderer) - return; + constructor(arr, pos, file, length) { + this._typename = 'TBuffer'; + this.arr = arr; + this.o = pos || 0; + this.fFile = file; + this.length = length || (arr ? arr.byteLength : 0); // use size of array view, blob buffer can be much bigger + this.clearObjectMap(); + this.fTagOffset = 0; + this.last_read_version = 0; + } + /** @summary locate position in the buffer */ + locate(pos) { this.o = pos; } - const main = this.selectDom(); - if (main.style('position') === 'static') - main.style('position', 'relative'); + /** @summary shift position in the buffer */ + shift(cnt) { this.o += cnt; } - this._gui = new GUI({ container: main.node(), closeFolders: true, width: Math.min(300, this._scene_width / 2), - title: 'Settings' }); + /** @summary Returns remaining place in the buffer */ + remain() { return this.length - this.o; } - const dom = this._gui.domElement; - dom.style.position = 'absolute'; - dom.style.top = 0; - dom.style.right = 0; + /** @summary Get mapped object with provided tag */ + getMappedObject(tag) { return this.fObjectMap[tag]; } - this._gui.painter = this; + /** @summary Map object */ + mapObject(tag, obj) { + if (obj !== null) + this.fObjectMap[tag] = obj; + } - const makeLil = items => { - const lil = {}; - items.forEach(i => { lil[i.name] = i.value; }); - return lil; - }; + /** @summary Map class */ + mapClass(tag, classname) { this.fClassMap[tag] = classname; } - if (!this.ctrl.project) { - const selection = this._gui.addFolder('Selection'); + /** @summary Get mapped class with provided tag */ + getMappedClass(tag) { return (tag in this.fClassMap) ? this.fClassMap[tag] : -1; } - if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = this._clones?.getMaxVisNodes() ?? 10000; - if (!this.ctrl.vislevel) - this.ctrl.vislevel = this._clones?.getVisLevel() ?? 3; - if (!this.ctrl.maxfaces) - this.ctrl.maxfaces = 200000 * this.ctrl.more; - this.ctrl.more = 1; + /** @summary Clear objects map */ + clearObjectMap() { + this.fObjectMap = {}; + this.fClassMap = {}; + this.fObjectMap[0] = null; + this.fDisplacement = 0; + } - selection.add(this.ctrl, 'vislevel', 1, 99, 1) - .name('Visibility level') - .listen().onChange(() => this.startRedraw(500)); - selection.add(this.ctrl, 'maxnodes', 0, 500000, 1000) - .name('Visible nodes') - .listen().onChange(() => this.startRedraw(500)); - selection.add(this.ctrl, 'maxfaces', 0, 5000000, 100000) - .name('Max faces') - .listen().onChange(() => this.startRedraw(500)); - } + /** @summary read class version from I/O buffer */ + readVersion() { + const ver = {}, bytecnt = this.ntou4(); // byte count - if (this.ctrl.project) { - const bound = this.getGeomBoundingBox(this.getProjectionSource(), 0.01), - axis = this.ctrl.project; + if (bytecnt & kByteCountMask) + ver.bytecnt = bytecnt - kByteCountMask - 2; // one can check between Read version and end of streamer + else + this.o -= 4; // rollback read bytes, this is old buffer without byte count - if (this.ctrl.projectPos === undefined) - this.ctrl.projectPos = (bound.min[axis] + bound.max[axis])/2; + this.last_read_version = ver.val = this.ntoi2(); + this.last_read_checksum = 0; + ver.off = this.o; - this._gui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis]) - .name(axis.toUpperCase() + ' projection') - .onChange(() => this.startDrawGeometry()); - } else { - // Clipping Options + if ((ver.val <= 0) && ver.bytecnt && (ver.bytecnt >= 4)) { + ver.checksum = this.ntou4(); + if (!this.fFile.findStreamerInfo(undefined, undefined, ver.checksum)) { + // console.error(`Fail to find streamer info with check sum ${ver.checksum} version ${ver.val}`); + this.o -= 4; // not found checksum in the list + delete ver.checksum; // remove checksum + } else + this.last_read_checksum = ver.checksum; + } + return ver; + } - const clipFolder = this._gui.addFolder('Clipping'); + /** @summary Check bytecount after object streaming */ + checkByteCount(ver, where) { + if ((ver.bytecnt !== undefined) && (ver.off + ver.bytecnt !== this.o)) { + if (where) + console.log(`Missmatch in ${where}:${ver.val} bytecount expected = ${ver.bytecnt} got = ${this.o - ver.off}`); + this.o = ver.off + ver.bytecnt; + return false; + } + return true; + } - for (let naxis = 0; naxis < 3; ++naxis) { - const cc = this.ctrl.clip[naxis], - axisC = cc.name.toUpperCase(); + /** @summary Read TString object (or equivalent) + * @desc std::string uses similar binary format */ + readTString() { + let len = this.ntou1(); + // large strings + if (len === 255) + len = this.ntou4(); + if (!len) + return ''; - clipFolder.add(cc, 'enabled') - .name('Enable ' + axisC) - .listen().onChange(() => this.changedClipping(-1)); + const pos = this.o; + this.o += len; - clipFolder.add(cc, 'value', cc.min, cc.max, cc.step) - .name(axisC + ' position') - .listen().onChange(() => this.changedClipping(naxis)); - } + return (this.codeAt(pos) === 0) ? '' : this.substring(pos, pos + len); + } - clipFolder.add(this.ctrl, 'clipIntersect').name('Clip intersection') - .onChange(() => this.changedClipping(-1)); + /** @summary read Char_t array as string + * @desc stops when 0 is found */ + readNullTerminatedString() { + let res = '', code; + while ((code = this.ntou1())) + res += String.fromCharCode(code); + return res; + } - clipFolder.add(this.ctrl, 'clipVisualize').name('Visualize') - .onChange(() => this.changedClipping(-1)); + /** @summary read Char_t array as string */ + readFastString(n) { + let res = '', reading = true; + for (let i = 0; i < n; ++i) { + const code = this.ntou1(); + if (code === 0) + reading = false; + if (reading) + res += String.fromCharCode(code); } - // Scene Options + return res; + } - const scene = this._gui.addFolder('Scene'); + /** @summary read uint8_t */ + ntou1() { return this.arr.getUint8(this.o++); } - scene.add(this.ctrl.light, 'kind', makeLil(this.ctrl.lightKindItems)).name('Light') - .listen().onChange(() => { - light_pnts.show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points'); - this.changedLight(); - }); + /** @summary read boolean */ + ntobool() { return Boolean(this.arr.getUint8(this.o++)); } - this.ctrl.light._pnts = this.ctrl.light.specular ? 0 : (this.ctrl.light.front ? 1 : 2); - const light_pnts = scene.add(this.ctrl.light, '_pnts', { specular: 0, front: 1, box: 2 }) - .name('Positions') - .show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points') - .onChange(v => { - this.ctrl.light.specular = (v === 0); - this.ctrl.light.front = (v === 1); - this.ctrl.light.top = this.ctrl.light.bottom = this.ctrl.light.left = this.ctrl.light.right = (v === 2); - this.changedLight(); - }); + /** @summary read uint16_t */ + ntou2() { + const o = this.o; + this.o += 2; + return this.arr.getUint16(o); + } - scene.add(this.ctrl.light, 'power', 0, 10, 0.01).name('Power') - .listen().onChange(() => this.changedLight()); + /** @summary read uint32_t */ + ntou4() { + const o = this.o; + this.o += 4; + return this.arr.getUint32(o); + } - scene.add(this.ctrl, 'use_fog').name('Fog') - .listen().onChange(() => this.changedUseFog()); + /** @summary read uint64_t */ + ntou8() { + const high = this.arr.getUint32(this.o); + this.o += 4; + const low = this.arr.getUint32(this.o); + this.o += 4; + return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); + } + /** @summary read int8_t */ + ntoi1() { return this.arr.getInt8(this.o++); } - // Appearance Options + /** @summary read int16_t */ + ntoi2() { + const o = this.o; + this.o += 2; + return this.arr.getInt16(o); + } - const appearance = this._gui.addFolder('Appearance'); + /** @summary read int32_t */ + ntoi4() { + const o = this.o; + this.o += 4; + return this.arr.getInt32(o); + } - this.ctrl._highlight = !this.ctrl.highlight ? 0 : this.ctrl.highlight_bloom ? 2 : 1; - appearance.add(this.ctrl, '_highlight', { none: 0, normal: 1, bloom: 2 }).name('Highlight Selection') - .listen().onChange(() => { - this.changedHighlight(this.ctrl._highlight); - strength.show(this.ctrl._highlight === 2); - hcolor.show(this.ctrl._highlight === 1); - }); + /** @summary read int64_t */ + ntoi8() { + const high = this.arr.getUint32(this.o); + this.o += 4; + const low = this.arr.getUint32(this.o); + this.o += 4; + if (high < 0x80000000) + return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); + return (~high < 0x200000) ? (-1 - ((~high) * 0x100000000 + ~low)) : (BigInt(-1) - (BigInt(~high) * BigInt(0x100000000) + BigInt(~low))); + } - const hcolor = appearance.addColor(this.ctrl, 'highlight_color').name('Hightlight color') - .show(this.ctrl._highlight === 1), - strength = appearance.add(this.ctrl, 'bloom_strength', 0, 3).name('Bloom strength') - .listen().onChange(() => this.changedHighlight()) - .show(this.ctrl._highlight === 2); + /** @summary read float */ + ntof() { + const o = this.o; + this.o += 4; + return this.arr.getFloat32(o); + } - appearance.addColor(this.ctrl, 'background').name('Background') - .onChange(col => this.changedBackground(col)); + /** @summary read double */ + ntod() { + const o = this.o; + this.o += 8; + return this.arr.getFloat64(o); + } - appearance.add(this.ctrl, '_axis', { none: 0, side: 1, center: 2 }).name('Axes') - .onChange(() => this.changedAxes()); + /** @summary Reads array of n values from the I/O buffer */ + readFastArray(n, array_type) { + let array, i = 0, o = this.o; + const view = this.arr; + switch (array_type) { + case kDouble: + array = new Float64Array(n); + for (; i < n; ++i, o += 8) + array[i] = view.getFloat64(o); + break; + case kFloat: + array = new Float32Array(n); + for (; i < n; ++i, o += 4) + array[i] = view.getFloat32(o); + break; + case kLong: + case kLong64: + array = new Array(n); + for (; i < n; ++i) + array[i] = this.ntoi8(); + return array; // exit here to avoid conflicts + case kULong: + case kULong64: + array = new Array(n); + for (; i < n; ++i) + array[i] = this.ntou8(); + return array; // exit here to avoid conflicts + case kInt: + case kCounter: + array = new Int32Array(n); + for (; i < n; ++i, o += 4) + array[i] = view.getInt32(o); + break; + case kShort: + array = new Int16Array(n); + for (; i < n; ++i, o += 2) + array[i] = view.getInt16(o); + break; + case kUShort: + array = new Uint16Array(n); + for (; i < n; ++i, o += 2) + array[i] = view.getUint16(o); + break; + case kChar: + array = new Int8Array(n); + for (; i < n; ++i) + array[i] = view.getInt8(o++); + break; + case kBool: + case kUChar: + array = new Uint8Array(n); + for (; i < n; ++i) + array[i] = view.getUint8(o++); + break; + case kTString: + array = new Array(n); + for (; i < n; ++i) + array[i] = this.readTString(); + return array; // exit here to avoid conflicts + case kDouble32: + throw new Error('kDouble32 should not be used in readFastArray'); + case kFloat16: + throw new Error('kFloat16 should not be used in readFastArray'); + // case kBits: + // case kUInt: + default: + array = new Uint32Array(n); + for (; i < n; ++i, o += 4) + array[i] = view.getUint32(o); + break; + } - if (!this.ctrl.project) { - appearance.add(this.ctrl, 'rotate').name('Autorotate') - .listen().onChange(() => this.changedAutoRotate()); + this.o = o; + return array; + } + + /** @summary Check if provided regions can be extracted from the buffer */ + canExtract(place) { + for (let n = 0; n < place.length; n += 2) { + if (place[n] + place[n + 1] > this.length) + return false; } + return true; + } - // Material options + /** @summary Extract area */ + extract(place) { + if (!this.arr?.buffer || !this.canExtract(place)) + return null; + if (place.length === 2) + return new DataView(this.arr.buffer, this.arr.byteOffset + place[0], place[1]); - const material = this._gui.addFolder('Material'); - let material_props = []; + const res = new Array(place.length / 2); + for (let n = 0; n < place.length; n += 2) + res[n / 2] = new DataView(this.arr.buffer, this.arr.byteOffset + place[n], place[n + 1]); + return res; // return array of buffers + } - const addMaterialProp = () => { - material_props.forEach(f => f.destroy()); - material_props = []; + /** @summary Get code at buffer position */ + codeAt(pos) { return this.arr.getUint8(pos); } - const props = this.ctrl.getMaterialCfg()?.props; - if (!props) return; + /** @summary Get part of buffer as string */ + substring(beg, end) { + let res = ''; + for (let n = beg; n < end; ++n) + res += String.fromCharCode(this.arr.getUint8(n)); + return res; + } - props.forEach(prop => { - const f = material.add(this.ctrl, prop.name, prop.min, prop.max, prop.step).onChange(() => { - this.changeMaterialProperty(prop.name); - }); - material_props.push(f); - }); - }; + /** @summary Read buffer as N-dim array */ + readNdimArray(handle, func) { + let ndim = handle.fArrayDim, maxindx = handle.fMaxIndex, res; + if ((ndim < 1) && (handle.fArrayLength > 0)) { + ndim = 1; + maxindx = [handle.fArrayLength]; + } + if (handle.minus1) + --ndim; - material.add(this.ctrl, 'material_kind', makeLil(this.ctrl.materialKinds)).name('Kind') - .listen().onChange(() => { - addMaterialProp(); - this.ensureBloom(false); - this.changedMaterial(); - this.changedHighlight(); // for some materials bloom will not work - }); + if (ndim < 1) + return func(this, handle); + + if (ndim === 1) { + res = new Array(maxindx[0]); + for (let n = 0; n < maxindx[0]; ++n) + res[n] = func(this, handle); + } else if (ndim === 2) { + res = new Array(maxindx[0]); + for (let n = 0; n < maxindx[0]; ++n) { + const res2 = new Array(maxindx[1]); + for (let k = 0; k < maxindx[1]; ++k) + res2[k] = func(this, handle); + res[n] = res2; + } + } else { + const indx = new Array(ndim).fill(0), arr = new Array(ndim); + for (let k = 0; k < ndim; ++k) + arr[k] = []; + res = arr[0]; + while (indx[0] < maxindx[0]) { + let k = ndim - 1; + arr[k].push(func(this, handle)); + ++indx[k]; + while ((indx[k] === maxindx[k]) && (k > 0)) { + indx[k] = 0; + arr[k - 1].push(arr[k]); + arr[k] = []; + ++indx[--k]; + } + } + } - material.add(this.ctrl, 'transparency', 0, 1, 0.001).name('Transparency') - .listen().onChange(value => this.changedGlobalTransparency(value)); + return res; + } - material.add(this.ctrl, 'wireframe').name('Wireframe') - .listen().onChange(() => this.changedWireFrame()); + /** @summary read TKey data */ + readTKey(key) { + if (!key) + key = {}; + this.classStreamer(key, clTKey); + const name = key.fName.replace(/['"]/g, ''); + if (name !== key.fName) { + key.fRealName = key.fName; + key.fName = name; + } + return key; + } - material.add(this, 'showMaterialDocu').name('Docu from threejs.org'); + /** @summary reading basket data + * @desc this is remaining part of TBasket streamer to decode fEntryOffset + * after unzipping of the TBasket data */ + readBasketEntryOffset(basket, offset) { + this.locate(basket.fLast - offset); - addMaterialProp(); + if (this.remain() <= 0) { + if (!basket.fEntryOffset && (basket.fNevBuf <= 1)) + basket.fEntryOffset = [basket.fKeylen]; + if (!basket.fEntryOffset) + console.warn(`No fEntryOffset when expected for basket with ${basket.fNevBuf} entries`); + return; + } + const nentries = this.ntoi4(); + // there is error in file=reco_103.root&item=Events;2/PCaloHits_g4SimHits_EcalHitsEE_Sim.&opt=dump;num:10;first:101 + // it is workaround, but normally I/O should fail here + if ((nentries < 0) || (nentries > this.remain() * 4)) { + console.error(`Error when reading entries offset from basket fNevBuf ${basket.fNevBuf} remains ${this.remain()} want to read ${nentries}`); + if (basket.fNevBuf <= 1) + basket.fEntryOffset = [basket.fKeylen]; + return; + } - // Camera options - const camera = this._gui.addFolder('Camera'); + basket.fEntryOffset = this.readFastArray(nentries, kInt); + if (!basket.fEntryOffset) + basket.fEntryOffset = [basket.fKeylen]; - camera.add(this.ctrl, 'camera_kind', makeLil(this.ctrl.cameraKindItems)) - .name('Kind').listen().onChange(() => { - overlay.show(this.ctrl.camera_kind.indexOf('ortho') === 0); - this.changeCamera(); - }); + if (this.remain() > 0) + basket.fDisplacement = this.readFastArray(this.ntoi4(), kInt); + else + basket.fDisplacement = undefined; + } - camera.add(this.ctrl, 'can_rotate').name('Can rotate') - .listen().onChange(() => this.changeCanRotate()); + /** @summary read class definition from I/O buffer */ + readClass() { + const classInfo = { name: -1 }, + bcnt = this.ntou4(), + startpos = this.o, + tag = !(bcnt & kByteCountMask) || (bcnt === kNewClassTag) ? bcnt : this.ntou4(); - camera.add(this, 'focusCamera').name('Reset position'); + if (!(tag & kClassMask)) { + classInfo.objtag = tag + this.fDisplacement; // indicate that we have deal with objects tag + return classInfo; + } + if (tag === kNewClassTag) { + // got a new class description followed by a new object + classInfo.name = this.readNullTerminatedString(); - const overlay = camera.add(this.ctrl, 'camera_overlay', makeLil(this.ctrl.cameraOverlayItems)) - .name('Overlay').listen().onChange(() => this.changeCamera()) - .show(this.ctrl.camera_kind.indexOf('ortho') === 0); + if (this.getMappedClass(this.fTagOffset + startpos + kMapOffset) === -1) + this.mapClass(this.fTagOffset + startpos + kMapOffset, classInfo.name); + } else { + // got a tag to an already seen class + const clTag = (tag & ~kClassMask) + this.fDisplacement; + classInfo.name = this.getMappedClass(clTag); - // Advanced Options - if (this._webgl) { - const advanced = this._gui.addFolder('Advanced'); + if (classInfo.name === -1) + console.error(`Did not found class with tag ${clTag}`); + } - advanced.add(this.ctrl, 'depthTest').name('Depth test') - .listen().onChange(() => this.changedDepthTest()); + return classInfo; + } - advanced.add(this.ctrl, 'depthMethod', makeLil(this.ctrl.depthMethodItems)) - .name('Rendering order') - .onChange(method => this.changedDepthMethod(method)); + /** @summary Read any object from buffer data */ + readObjectAny() { + const objtag = this.fTagOffset + this.o + kMapOffset, + clRef = this.readClass(); - advanced.add(this, 'resetAdvanced').name('Reset'); - } + // class identified as object and should be handled so + if (clRef.objtag !== undefined) + return this.getMappedObject(clRef.objtag); - // Transformation Options - if (!this.ctrl.project) { - const transform = this._gui.addFolder('Transform'); - transform.add(this.ctrl, 'trans_z', 0.0, 3.0, 0.01) - .name('Z axis') - .listen().onChange(() => this.changedTransformation()); - transform.add(this.ctrl, 'trans_radial', 0.0, 3.0, 0.01) - .name('Radial') - .listen().onChange(() => this.changedTransformation()); + if (clRef.name === -1) + return null; - transform.add(this, 'resetTransformation').name('Reset'); + const arrkind = getArrayKind(clRef.name); + let obj; - if (this.ctrl.trans_z || this.ctrl.trans_radial) transform.open(); + if (arrkind === 0) + obj = this.readTString(); + else if (arrkind > 0) { + // reading array, can map array only afterwards + obj = this.readFastArray(this.ntou4(), arrkind); + this.mapObject(objtag, obj); + } else { + // reading normal object, should map before to + obj = {}; + this.mapObject(objtag, obj); + this.classStreamer(obj, clRef.name); } - } - /** @summary show material docu */ - showMaterialDocu() { - const cfg = this.ctrl.getMaterialCfg(); - if (cfg?.name && typeof window !== 'undefined') - window.open('https://threejs.org/docs/index.html#api/en/materials/' + cfg.name, '_blank'); + return obj; } - /** @summary Should be called when configuration of highlight is changed */ - changedHighlight(arg) { - if (arg !== undefined) { - this.ctrl.highlight = arg !== 0; - if (this.ctrl.highlight) - this.ctrl.highlight_bloom = (arg === 2); - } - - this.ensureBloom(); + /** @summary Invoke streamer for specified class */ + classStreamer(obj, classname) { + if (obj._typename === undefined) + obj._typename = classname; - if (!this.ctrl.highlight) - this.highlightMesh(null); + const direct = DirectStreamers[classname]; + if (direct) { + direct(this, obj); + return obj; + } - this._slave_painters?.forEach(p => { - p.ctrl.highlight = this.ctrl.highlight; - p.ctrl.highlight_bloom = this.ctrl.highlight_bloom; - p.ctrl.bloom_strength = this.ctrl.bloom_strength; - p.changedHighlight(); - }); - } + const ver = this.readVersion(), + streamer = this.fFile.getStreamer(classname, ver); - /** @summary Handle change of can rotate */ - changeCanRotate(on) { - if (on !== undefined) - this.ctrl.can_rotate = on; - if (this._controls) - this._controls.enableRotate = this.ctrl.can_rotate; - } + if (streamer !== null) { + const len = streamer.length; + for (let n = 0; n < len; ++n) + streamer[n].func(this, obj); + } else { + // just skip bytes belonging to not-recognized object + // console.warn(`skip object ${classname}`); + exports.addMethods(obj); + } - /** @summary Change use fog property */ - changedUseFog() { - this._scene.fog = this.ctrl.use_fog ? this._fog : null; + this.checkByteCount(ver, classname); - this.render3D(); + return obj; } - /** @summary Handle change of camera kind */ - changeCamera() { - // force control recreation - if (this._controls) { - this._controls.cleanup(); - delete this._controls; + /** @summary Stream class members using normal streamer */ + streamClassMembers(obj, classname, version) { + const streamer = this.fFile.getStreamer(classname, { val: version }, undefined, true); + if (streamer !== null) { + const len = streamer.length; + for (let n = 0; n < len; ++n) + streamer[n].func(this, obj); } + return obj; + } - this.ensureBloom(false); +} // class TBuffer - // recreate camera - this.createCamera(); +// ============================================================================== - this.createSpecialEffects(); +/** @summary Direct streamer for TBasket, + * @desc uses TBuffer therefore defined later + * @private */ +DirectStreamers[clTBasket] = function(buf, obj) { + buf.classStreamer(obj, clTKey); + const ver = buf.readVersion(); + obj.fBufferSize = buf.ntoi4(); + obj.fNevBufSize = buf.ntoi4(); - // set proper position - this.adjustCameraPosition(true); + if (obj.fNevBufSize < 0) { + obj.fNevBufSize = -obj.fNevBufSize; + obj.fIOBits = buf.ntoi1(); + } - // this.drawOverlay(); + obj.fNevBuf = buf.ntoi4(); + obj.fLast = buf.ntoi4(); - this.addOrbitControls(); + if (obj.fLast > obj.fBufferSize) + obj.fBufferSize = obj.fLast; + const flag = buf.ntoi1(); - this.render3D(); + if (flag === 0) + return; - // delete this._scene_size; // ensure reassign of camera position + if ((flag % 10) !== 2) { + if (obj.fNevBuf) { + obj.fEntryOffset = buf.readFastArray(buf.ntoi4(), kInt); + if ((flag > 20) && (flag < 40)) { + for (let i = 0, kDisplacementMask = 0xFF000000; i < obj.fNevBuf; ++i) + obj.fEntryOffset[i] &= ~kDisplacementMask; + } + } - // this._first_drawing = true; - // this.startDrawGeometry(true); + if (flag > 40) + obj.fDisplacement = buf.readFastArray(buf.ntoi4(), kInt); } - /** @summary create bloom effect */ - ensureBloom(on) { - if (on === undefined) { - if (this.ctrl.highlight_bloom === 0) - this.ctrl.highlight_bloom = this._webgl; - - on = this.ctrl.highlight_bloom && this.ctrl.getMaterialCfg()?.emissive; - } + if ((flag === 1) || (flag > 10)) { + // here is reading of raw data + const sz = (ver.val <= 1) ? buf.ntoi4() : obj.fLast; - if (on && !this._bloomComposer) { - this._camera.layers.enable(_BLOOM_SCENE); - this._bloomComposer = new EffectComposer(this._renderer); - this._bloomComposer.addPass(new RenderPass(this._scene, this._camera)); - const pass = new UnrealBloomPass(new Vector2(this._scene_width, this._scene_height), 1.5, 0.4, 0.85); - pass.threshold = 0; - pass.radius = 0; - pass.renderToScreen = true; - this._bloomComposer.addPass(pass); - this._renderer.autoClear = false; - } else if (!on && this._bloomComposer) { - this._bloomComposer.dispose(); - delete this._bloomComposer; - if (this._renderer) - this._renderer.autoClear = true; - this._camera?.layers.disable(_BLOOM_SCENE); - this._camera?.layers.set(_ENTIRE_SCENE); + if (sz > obj.fKeylen) { + // buffer includes again complete TKey data - exclude it + const blob = buf.extract([buf.o + obj.fKeylen, sz - obj.fKeylen]); + obj.fBufferRef = new TBuffer(blob, 0, buf.fFile, sz - obj.fKeylen); + obj.fBufferRef.fTagOffset = obj.fKeylen; } - if (this._bloomComposer?.passes) - this._bloomComposer.passes[1].strength = this.ctrl.bloom_strength; + buf.shift(sz); } +}; +// ============================================================================== - /** @summary Show context menu for orbit control - * @private */ - orbitContext(evnt, intersects) { - createMenu(evnt, this).then(menu => { - let numitems = 0, numnodes = 0, cnt = 0; - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (getIntersectStack(intersects[n])) numnodes++; - if (intersects[n].geo_name) numitems++; - } - } - - if (numnodes + numitems === 0) - this.fillContextMenu(menu); - else { - const many = (numnodes + numitems) > 1; - - if (many) menu.add('header:' + ((numitems > 0) ? 'Items' : 'Nodes')); +/** + * @summary A class that reads a TDirectory from a buffer. + * + * @private + */ - for (let n = 0; n < intersects.length; ++n) { - const obj = intersects[n].object, - stack = getIntersectStack(intersects[n]); - let name, itemname, hdr; +class TDirectory { - if (obj.geo_name) { - itemname = obj.geo_name; - if (itemname.indexOf('') === 0) - itemname = (this.getItemName() || 'top') + itemname.slice(6); - name = itemname.slice(itemname.lastIndexOf('/')+1); - if (!name) name = itemname; - hdr = name; - } else if (stack) { - name = this._clones.getStackName(stack); - itemname = this.getStackFullName(stack); - hdr = this.getItemName(); - if (name.indexOf('Nodes/') === 0) - hdr = name.slice(6); - else if (name) - hdr = name; - else if (!hdr) - hdr = 'header'; - } else - continue; + /** @summary constructor */ + constructor(file, dirname, cycle) { + this.fFile = file; + this._typename = clTDirectory; + this.dir_name = dirname; + this.dir_cycle = cycle; + this.fKeys = []; + } + /** @summary retrieve a key by its name and cycle in the list of keys */ + getKey(keyname, cycle, only_direct) { + if (typeof cycle !== 'number') + cycle = -1; + let bestkey = null; + for (let i = 0; i < this.fKeys.length; ++i) { + const key = this.fKeys[i]; + if (!key || (key.fName !== keyname)) + continue; + if (key.fCycle === cycle) { + bestkey = key; + break; + } + if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) + bestkey = key; + } + if (bestkey) + return only_direct ? bestkey : Promise.resolve(bestkey); - cnt++; + let pos = keyname.lastIndexOf('/'); + // try to handle situation when object name contains slashed (bad practice anyway) + while (pos > 0) { + const dirname = keyname.slice(0, pos), + subname = keyname.slice(pos + 1), + dirkey = this.getKey(dirname, undefined, true); - menu.add((many ? 'sub:' : 'header:') + hdr, itemname, arg => this.activateInBrowser([arg], true)); + if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) { + return this.fFile.readObject(this.dir_name + '/' + dirname, 1) + .then(newdir => newdir.getKey(subname, cycle)); + } - menu.add('Browse', itemname, arg => this.activateInBrowser([arg], true)); + pos = keyname.lastIndexOf('/', pos - 1); + } - if (this._hpainter) - menu.add('Inspect', itemname, arg => this._hpainter.display(arg, kInspect)); + return only_direct ? null : Promise.reject(Error(`Key not found ${keyname}`)); + } - if (isFunc(this.hidePhysicalNode)) { - menu.add('Hide', itemname, arg => this.hidePhysicalNode([arg])); - if (cnt > 1) { - menu.add('Hide all before', n, indx => { - const items = []; - for (let i = 0; i < indx; ++i) { - const stack = getIntersectStack(intersects[i]); - if (stack) items.push(this.getStackFullName(stack)); - } - this.hidePhysicalNode(items); - }); - } - } else if (obj.geo_name) { - menu.add('Hide', n, indx => { - const mesh = intersects[indx].object; - mesh.visible = false; // just disable mesh - if (mesh.geo_object) mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw - menu.painter.render3D(); - }, 'Hide this physical node'); + /** @summary Read object from the directory + * @param {string} name - object name + * @param {number} [cycle] - cycle number + * @return {Promise} with read object */ + readObject(obj_name, cycle) { + return this.fFile.readObject(this.dir_name + '/' + obj_name, cycle); + } - if (many) menu.add('endsub:'); + /** @summary Read list of keys in directory + * @return {Promise} with TDirectory object */ + async readKeys(objbuf) { + objbuf.classStreamer(this, clTDirectory); - continue; - } + if ((this.fSeekKeys <= 0) || (this.fNbytesKeys <= 0)) + return this; - const wireframe = this.accessObjectWireFrame(obj); - if (wireframe !== undefined) { - menu.addchk(wireframe, 'Wireframe', n, indx => { - const m = intersects[indx].object.material; - m.wireframe = !m.wireframe; - this.render3D(); - }, 'Toggle wireframe mode for the node'); - } + return this.fFile.readBuffer([this.fSeekKeys, this.fNbytesKeys]).then(blob => { + // Read keys of the top directory - if (cnt > 1) { - menu.add('Manifest', n, indx => { - if (this._last_manifest) - this._last_manifest.wireframe = !this._last_manifest.wireframe; + const buf = new TBuffer(blob, 0, this.fFile); - if (this._last_hidden) - this._last_hidden.forEach(obj => { obj.visible = true; }); + buf.readTKey(); + const nkeys = buf.ntoi4(); - this._last_hidden = []; + for (let i = 0; i < nkeys; ++i) + this.fKeys.push(buf.readTKey()); - for (let i = 0; i < indx; ++i) - this._last_hidden.push(intersects[i].object); + this.fFile.fDirectories.push(this); - this._last_hidden.forEach(obj => { obj.visible = false; }); + return this; + }); + } - this._last_manifest = intersects[indx].object.material; +} // class TDirectory - this._last_manifest.wireframe = !this._last_manifest.wireframe; +/** + * @summary Interface to read objects from ROOT files + * + * @desc Use {@link openFile} to create instance of the class + */ - this.render3D(); - }, 'Manifest selected node'); - } +class TFile { - menu.add('Focus', n, indx => { - this.focusCamera(intersects[indx].object); - }); + constructor(url) { + this._typename = clTFile; + this.fEND = 0; + this.fFullURL = url; + this.fURL = url; + // when disabled ('+' at the end of file name), complete file content read with single operation + this.fAcceptRanges = true; + // use additional time stamp parameter for file name to avoid browser caching problem + this.fUseStampPar = settings.UseStamp ? 'stamp=' + (new Date()).getTime() : false; + this.fFileContent = null; // this can be full or partial content of the file (if ranges are not supported or if 1K header read from file) + // stored as TBuffer instance + this.fMaxRanges = settings.MaxRanges || 200; // maximal number of file ranges requested at once + this.fDirectories = []; + this.fKeys = []; + this.fSeekInfo = 0; + this.fNbytesInfo = 0; + this.fTagOffset = 0; + this.fStreamers = 0; + this.fStreamerInfos = null; + this.fFileName = ''; + this.fTimeout = settings.FilesTimeout ?? 0; + this.fStreamers = []; + this.fBasicTypes = {}; // custom basic types, in most case enumerations - if (!this._geom_viewer) { - menu.add('Hide', n, indx => { - const resolve = this._clones.resolveStack(intersects[indx].object.stack); - if (resolve.obj && (resolve.node.kind === kindGeo) && resolve.obj.fVolume) { - setGeoBit(resolve.obj.fVolume, geoBITS.kVisThis, false); - updateBrowserIcons(resolve.obj.fVolume, this._hpainter); - } else if (resolve.obj && (resolve.node.kind === kindEve)) { - resolve.obj.fRnrSelf = false; - updateBrowserIcons(resolve.obj, this._hpainter); - } + if (!isStr(this.fURL)) + return; - this.testGeomChanges();// while many volumes may disappear, recheck all of them - }, 'Hide all logical nodes of that kind'); - menu.add('Hide only this', n, indx => { - this._clones.setPhysNodeVisibility(getIntersectStack(intersects[indx]), false); - this.testGeomChanges(); - }, 'Hide only this physical node'); - if (n > 1) { - menu.add('Hide all before', n, indx => { - for (let k = 0; k < indx; ++k) - this._clones.setPhysNodeVisibility(getIntersectStack(intersects[k]), false); - this.testGeomChanges(); - }, 'Hide all physical nodes before that'); - } - } + if (this.fURL.at(-1) === '+') { + this.fURL = this.fURL.slice(0, this.fURL.length - 1); + this.fAcceptRanges = false; + } - if (many) menu.add('endsub:'); - } - } - menu.show(); - }); - } + if (this.fURL.at(-1) === '^') { + this.fURL = this.fURL.slice(0, this.fURL.length - 1); + this.fSkipHeadRequest = true; + } - /** @summary Filter some objects from three.js intersects array */ - filterIntersects(intersects) { - if (!intersects?.length) - return intersects; + if (this.fURL.at(-1) === '-') { + this.fURL = this.fURL.slice(0, this.fURL.length - 1); + this.fUseStampPar = false; + } - // check redirections - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.geo_highlight) - intersects[n].object = intersects[n].object.geo_highlight; + if (this.fURL.indexOf('file://') === 0) { + this.fUseStampPar = false; + this.fAcceptRanges = false; } - // remove all elements without stack - indicator that this is geometry object - // also remove all objects which are mostly transparent - for (let n = intersects.length - 1; n >= 0; --n) { - const obj = intersects[n].object; - let unique = obj.visible && (getIntersectStack(intersects[n]) || (obj.geo_name !== undefined)); + const pos = Math.max(this.fURL.lastIndexOf('/'), this.fURL.lastIndexOf('\\')); + this.fFileName = pos >= 0 ? this.fURL.slice(pos + 1) : this.fURL; + } - if (unique && obj.material && (obj.material.opacity !== undefined)) - unique = (obj.material.opacity >= 0.1); + /** @summary Set timeout for File instance + * @desc Timeout used when submitting http requests to the server */ + setTimeout(v) { + this.fTimeout = v; + } - if (obj.jsroot_special) unique = false; + /** @summary Assign remap for web servers + * @desc Allows to specify fallback server if main server fails + * @param {Object} remap - looks like { 'https://original.server/': 'https://fallback.server/' } */ + assignRemap(remap) { + if (!remap && !isObject(remap)) + return; - for (let k = 0; (k < n) && unique; ++k) { - if (intersects[k].object === obj) - unique = false; + for (const key in remap) { + if (this.fURL.indexOf(key) === 0) { + this.fURL2 = remap[key] + this.fURL.slice(key.length); + if (!this.fTimeout) + this.fTimeout = 10000; } - - if (!unique) intersects.splice(n, 1); } + } - const clip = this.ctrl.clip; + /** @summary Assign BufferArray with file contentOpen file + * @private */ + assignFileContent(bufArray) { + this.fFileContent = new TBuffer(new DataView(bufArray)); + this.fAcceptRanges = false; + this.fUseStampPar = false; + this.fEND = this.fFileContent.length; + } - if (clip[0].enabled || clip[1].enabled || clip[2].enabled) { - const clippedIntersects = []; + /** @summary Actual file open + * @return {Promise} when file keys are read + * @private */ + async _open() { return this.readKeys(); } - for (let i = 0; i < intersects.length; ++i) { - const point = intersects[i].point, special = (intersects[i].object.type === 'Points'); - let clipped = true; + /** @summary check if requested segments can be reordered or merged + * @private */ + #checkNeedReorder(place) { + let res = false, resort = false; + for (let n = 0; n < place.length - 2; n += 2) { + if (place[n] > place[n + 2]) + res = resort = true; + if (place[n] + place[n + 1] > place[n + 2] - kMinimalHttpGap) + res = true; + } + if (!res) { + return { + place, + blobs: [], + expectedSize(indx) { return this.place[indx + 1]; }, + addBuffer(indx, buf, o) { + this.blobs[indx / 2] = new DataView(buf, o, this.place[indx + 1]); + } + }; + } - if (clip[0].enabled && ((this._clipPlanes[0].normal.dot(point) > this._clipPlanes[0].constant) ^ special)) clipped = false; - if (clip[1].enabled && ((this._clipPlanes[1].normal.dot(point) > this._clipPlanes[1].constant) ^ special)) clipped = false; - if (clip[2].enabled && (this._clipPlanes[2].normal.dot(point) > this._clipPlanes[2].constant)) clipped = false; + res = { place, reorder: [], place_new: [], blobs: [] }; - if (!clipped) clippedIntersects.push(intersects[i]); + for (let n = 0; n < place.length; n += 2) + res.reorder.push({ pos: place[n], len: place[n + 1], indx: [n] }); + + if (resort) + res.reorder.sort((a, b) => { return a.pos - b.pos; }); + + for (let n = 0; n < res.reorder.length - 1; n++) { + const curr = res.reorder[n], + next = res.reorder[n + 1]; + if (curr.pos + curr.len + kMinimalHttpGap > next.pos) { + curr.indx.push(...next.indx); + curr.len = next.pos + next.len - curr.pos; + res.reorder.splice(n + 1, 1); // remove segment + n--; } - - intersects = clippedIntersects; } - return intersects; - } + res.reorder.forEach(elem => res.place_new.push(elem.pos, elem.len)); - /** @summary test camera position - * @desc function analyzes camera position and start redraw of geometry - * if objects in view may be changed */ - testCameraPositionChange() { - if (!this.ctrl.select_in_view || this._draw_all_nodes) return; + res.expectedSize = function(indx) { + return this.reorder[indx / 2].len; + }; - const matrix = createProjectionMatrix(this._camera), - frustum = createFrustum(matrix); + res.addBuffer = function(indx, buf, o) { + const elem = this.reorder[indx / 2], + pos0 = elem.pos; + elem.indx.forEach(indx0 => { + this.blobs[indx0 / 2] = new DataView(buf, o + this.place[indx0] - pos0, this.place[indx0 + 1]); + }); + }; - // check if overall bounding box seen - if (!frustum.CheckBox(this.getGeomBoundingBox())) - this.startDrawGeometry(); + return res; } - /** @summary Resolve stack */ - resolveStack(stack) { - return this._clones && stack ? this._clones.resolveStack(stack) : null; - } + /** @summary read buffer(s) from the file + * @return {Promise} with read buffers + * @private */ + async readBuffer(place, filename, progress_callback) { + if ((this.fFileContent !== null) && !filename && (!this.fAcceptRanges || this.fFileContent.canExtract(place))) + return this.fFileContent.extract(place); - /** @summary Returns stack full name - * @desc Includes item name of top geo object */ - getStackFullName(stack) { - const mainitemname = this.getItemName(), - sub = this.resolveStack(stack); - if (!sub || !sub.name) - return mainitemname; - return mainitemname ? mainitemname + '/' + sub.name : sub.name; - } + const reorder = this.#checkNeedReorder(place); + if (reorder?.place_new) + place = reorder?.place_new; - /** @summary Add handler which will be called when element is highlighted in geometry drawing - * @desc Handler should have highlightMesh function with same arguments as TGeoPainter */ - addHighlightHandler(handler) { - if (!isFunc(handler?.highlightMesh)) return; - if (!this._highlight_handlers) - this._highlight_handlers = []; - this._highlight_handlers.push(handler); - } + let resolveFunc, rejectFunc; - /** @summary perform mesh highlight */ - highlightMesh(active_mesh, color, geo_object, geo_index, geo_stack, no_recursive) { - if (geo_object) { - active_mesh = active_mesh ? [active_mesh] : []; - const extras = this.getExtrasContainer(); - if (extras) { - extras.traverse(obj3d => { - if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d) < 0)) active_mesh.push(obj3d); + const file = this, first_block = (place[0] === 0) && (place.length === 2), + promise = new Promise((resolve, reject) => { + resolveFunc = resolve; + rejectFunc = reject; }); - } - } else if (geo_stack && this._toplevel) { - active_mesh = []; - this._toplevel.traverse(mesh => { - if ((mesh instanceof Mesh) && isSameStack(mesh.stack, geo_stack)) active_mesh.push(mesh); - }); - } else - active_mesh = active_mesh ? [active_mesh] : []; - - if (!active_mesh.length) - active_mesh = null; - - if (active_mesh) { - // check if highlight is disabled for correspondent objects kinds - if (active_mesh[0].geo_object) { - if (!this.ctrl.highlight_scene) active_mesh = null; - } else - if (!this.ctrl.highlight) active_mesh = null; - } - if (!no_recursive) { - // check all other painters + let fileurl, first = 0, last = 0, + // eslint-disable-next-line prefer-const + read_callback, first_req, + first_block_retry = false; - if (active_mesh) { - if (!geo_object) geo_object = active_mesh[0].geo_object; - if (!geo_stack) geo_stack = active_mesh[0].stack; + function setFileUrl(use_second) { + if (use_second) { + console.log('Failure - try to repair with URL2', file.fURL2); + internals.RemapCounter = (internals.RemapCounter ?? 0) + 1; + file.fURL = file.fURL2; + delete file.fURL2; } - const lst = this._highlight_handlers || (!this._main_painter ? this._slave_painters : this._main_painter._slave_painters.concat([this._main_painter])); + fileurl = file.fURL; + if (isStr(filename) && filename) { + const pos = fileurl.lastIndexOf('/'); + fileurl = (pos < 0) ? filename : fileurl.slice(0, pos + 1) + filename; + } + } - for (let k = 0; k < lst?.length; ++k) { - if (lst[k] !== this) - lst[k].highlightMesh(null, color, geo_object, geo_index, geo_stack, true); + function send_new_request(arg) { + if (arg === 'noranges') { + file.fMaxRanges = 1; + last = Math.min(last, first + file.fMaxRanges * 2); + } else if (arg) { + first = last; + last = Math.min(first + file.fMaxRanges * 2, place.length); + if (first >= place.length) + return resolveFunc(reorder.blobs.length === 1 ? reorder.blobs[0] : reorder.blobs); } - } - const curr_mesh = this._selected_mesh; + let fullurl = fileurl, ranges = 'bytes', totalsz = 0; + // try to avoid browser caching by adding stamp parameter to URL + if (file.fUseStampPar) + fullurl += ((fullurl.indexOf('?') < 0) ? '?' : '&') + file.fUseStampPar; - if (!curr_mesh && !active_mesh) return false; + for (let n = first; n < last; n += 2) { + ranges += (n > first ? ',' : '=') + `${place[n]}-${place[n] + place[n + 1] - 1}`; + totalsz += place[n + 1]; // accumulated total size + } + if (last - first > 2) + totalsz += (last - first) * 60; // for multi-range ~100 bytes/per request - const get_ctrl = mesh => mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh, this.ctrl.highlight_bloom && this._bloomComposer); + // when read first block, allow to read more - maybe ranges are not supported and full file content will be returned + if (file.fAcceptRanges && first_block) + totalsz = Math.max(totalsz, 1e5); - let same = false; + return createHttpRequest(fullurl, 'buf', read_callback, undefined, true).then(xhr => { + if (file.fAcceptRanges) { + xhr.setRequestHeader('Range', ranges); + xhr.expected_size = Math.max(Math.round(1.1 * totalsz), totalsz + 200); // 200 if offset for the potential gzip + } - // check if selections are the same - if (curr_mesh && active_mesh && (curr_mesh.length === active_mesh.length)) { - same = true; - for (let k = 0; (k < curr_mesh.length) && same; ++k) - if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) same = false; - } - if (same) return !!curr_mesh; + if (file.fTimeout) + xhr.timeout = file.fTimeout; - if (curr_mesh) { - for (let k = 0; k < curr_mesh.length; ++k) - get_ctrl(curr_mesh[k]).setHighlight(); - } + if (isFunc(progress_callback) && isFunc(xhr.addEventListener)) { + let sum1 = 0, sum2 = 0, sum_total = 0; + for (let n = 1; n < place.length; n += 2) { + sum_total += place[n]; + if (n < first) + sum1 += place[n]; + if (n < last) + sum2 += place[n]; + } + if (!sum_total) + sum_total = 1; - this._selected_mesh = active_mesh; + const progress_offest = sum1 / sum_total, progress_this = (sum2 - sum1) / sum_total; + xhr.addEventListener('progress', oEvent => { + if (oEvent.lengthComputable) { + if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') { + xhr.did_abort = true; + xhr.abort(); + } + } + }); + } else if (first_block_retry && isFunc(xhr.addEventListener)) { + xhr.addEventListener('progress', oEvent => { + if (!oEvent.total) + console.warn('Fail to get file size information'); + else if (oEvent.total > 5e7) { + console.error(`Try to load very large file ${oEvent.total} at once - abort`); + xhr.did_abort = 'large'; + xhr.abort(); + } + }); + } - if (active_mesh) { - for (let k = 0; k < active_mesh.length; ++k) - get_ctrl(active_mesh[k]).setHighlight(color || new Color(this.ctrl.highlight_color), geo_index); + first_req = first_block ? xhr : null; + xhr.send(null); + }); } - this.render3D(0); - - return !!active_mesh; - } - - /** @summary handle mouse click event */ - processMouseClick(pnt, intersects, evnt) { - if (!intersects.length) return; + read_callback = function(res) { + if (!res && first_block) { + // if fail to read file with stamp parameter, try once again without it + if (file.fUseStampPar) { + file.fUseStampPar = false; + return send_new_request(); + } + if (file.fURL2 && (this.did_abort !== 'large')) { + setFileUrl(true); + return send_new_request(); + } + if (file.fAcceptRanges) { + file.fAcceptRanges = false; + first_block_retry = true; + return send_new_request(); + } + } - const mesh = intersects[0].object; - if (!mesh.get_ctrl) return; + if (res && first_req) { + // special workaround for servers like cernbox blocking access to some response headers + // as result, it is not possible to parse multipart responses + if (file.fAcceptRanges && (first_req.status === 206) && (res?.byteLength === place[1]) && !first_req.getResponseHeader('Content-Range') && (file.fMaxRanges > 1)) { + console.warn('Server response with 206 code but browser does not provide access to Content-Range header - setting fMaxRanges = 1, consider to load full file with "filename.root+" argument or adjust server configurations'); + file.fMaxRanges = 1; + } - const ctrl = mesh.get_ctrl(), - click_indx = ctrl.extractIndex(intersects[0]); + // workaround for simpleHTTP + const kind = browser.isFirefox ? first_req.getResponseHeader('Server') : ''; + if (isStr(kind) && kind.indexOf('SimpleHTTP') === 0) { + file.fMaxRanges = 1; + file.fUseStampPar = false; + } + } - ctrl.evnt = evnt; + if (res && first_block && !file.fFileContent) { + // special case - keep content of first request (could be complete file) in memory + file.fFileContent = new TBuffer(isStr(res) ? res : new DataView(res)); - if (ctrl.setSelected('blue', click_indx)) - this.render3D(); + if (!file.fAcceptRanges) + file.fEND = file.fFileContent.length; - ctrl.evnt = null; - } + return resolveFunc(file.fFileContent.extract(place)); + } - /** @summary Configure mouse delay, required for complex geometries */ - setMouseTmout(val) { - if (this.ctrl) - this.ctrl.mouse_tmout = val; + if (!res) { + if (file.fURL2 && (this.did_abort !== 'large')) { + setFileUrl(true); + return send_new_request(); + } + if ((first === 0) && (last > 2) && (file.fMaxRanges > 1)) { + // server return no response with multi request - try to decrease ranges count or fail + if (last / 2 > 200) + file.fMaxRanges = 200; + else if (last / 2 > 50) + file.fMaxRanges = 50; + else if (last / 2 > 20) + file.fMaxRanges = 20; + else if (last / 2 > 5) + file.fMaxRanges = 5; + else + file.fMaxRanges = 1; + last = Math.min(last, file.fMaxRanges * 2); + return send_new_request(); + } + return rejectFunc(Error(`Fail to read with ${place.length / 2} ranges max = ${file.fMaxRanges}`)); + } - if (this._controls) - this._controls.mouse_tmout = val; - } + // if only single segment requested, return result as is + if (last - first === 2) { + reorder.addBuffer(first, res, 0); + return send_new_request(true); + } - /** @summary Configure depth method, used for render order production. - * @param {string} method - Allowed values: 'ray', 'box','pnt', 'size', 'dflt' */ - setDepthMethod(method) { - if (this.ctrl) - this.ctrl.depthMethod = method; - } + // object to access response data + const hdr = this.getResponseHeader('Content-Type'); - /** @summary Returns if camera can rotated */ - canRotateCamera() { - if (this.ctrl.can_rotate === false) - return false; - if (!this.ctrl.can_rotate && (this.isOrthoCamera() || this.ctrl.project)) - return false; - return true; - } + if (!isStr(hdr) || (hdr.indexOf('multipart') < 0)) { + console.error('Did not found multipart in content-type - fallback to single range request'); + return send_new_request('noranges'); + } - /** @summary Add orbit control */ - addOrbitControls() { - if (this._controls || !this._webgl || this.isBatchMode() || this.superimpose || isNodeJs()) return; + // multipart messages requires special handling - if (!this.getCanvPainter()) - this.setTooltipAllowed(settings.Tooltip); + const indx = hdr.indexOf('boundary='); + if (indx <= 0) { + console.error('Did not found boundary id in the response header - fallback to single range request'); + return send_new_request('noranges'); + } - this._controls = createOrbitControl(this, this._camera, this._scene, this._renderer, this._lookat); + let boundary = hdr.slice(indx + 9); + if ((boundary[0] === '"') && (boundary.at(-1) === '"')) + boundary = boundary.slice(1, boundary.length - 1); + boundary = '--' + boundary; - this._controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing + const view = new DataView(res); - if (!this.canRotateCamera()) - this._controls.enableRotate = false; + for (let n = first, o = 0; n < last; n += 2) { + let code1, code2 = view.getUint8(o), nline = 0, line = '', + finish_header = false, segm_start = 0, segm_last = -1; - this._controls.contextMenu = this.orbitContext.bind(this); + while ((o < view.byteLength - 1) && !finish_header && (nline < 5)) { + code1 = code2; + code2 = view.getUint8(o + 1); - this._controls.processMouseMove = intersects => { - // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this._controls) return; + if (((code1 === 13) && (code2 === 10)) || (code1 === 10)) { + if ((line.length > 2) && (line.slice(0, 2) === '--') && (line !== boundary)) + return rejectFunc(Error(`Decode multipart message, expect boundary ${boundary} got ${line}`)); - let active_mesh = null, tooltip = null, resolve = null, names = [], geo_object, geo_index, geo_stack; + line = line.toLowerCase(); - // try to find mesh from intersections - for (let k = 0; k < intersects.length; ++k) { - const obj = intersects[k].object, stack = getIntersectStack(intersects[k]); - if (!obj || !obj.visible) continue; - let info = null; - if (obj.geo_object) - info = obj.geo_name; - else if (stack) - info = this.getStackFullName(stack); - if (!info) continue; + if ((line.indexOf('content-range') >= 0) && (line.indexOf('bytes') > 0)) { + const parts = line.slice(line.indexOf('bytes') + 6).split(/[\s-/]+/); + if (parts.length === 3) { + segm_start = Number.parseInt(parts[0]); + segm_last = Number.parseInt(parts[1]); + // TODO: check for consistency + if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) { + segm_start = 0; + segm_last = -1; + } + } else + console.error(`Fail to decode content-range ${line} ${parts}`); + } - if (info.indexOf('') === 0) - info = this.getItemName() + info.slice(6); + if ((nline > 1) && !line) + finish_header = true; - names.push(info); + nline++; + line = ''; + if (code1 !== 10) { + o++; + code2 = view.getUint8(o + 1); + } + } else + line += String.fromCharCode(code1); + o++; + } - if (!active_mesh) { - active_mesh = obj; - tooltip = info; - geo_object = obj.geo_object; - if (obj.get_ctrl) { - geo_index = obj.get_ctrl().extractIndex(intersects[k]); - if ((geo_index !== undefined) && isStr(tooltip)) - tooltip += ' indx:' + JSON.stringify(geo_index); - } - geo_stack = stack; + const segm_size = segm_last - segm_start + 1; - if (geo_stack) { - resolve = this.resolveStack(geo_stack); - if (obj.stacks) geo_index = intersects[k].instanceId; - } + if (!finish_header || (segm_size <= 0) || (reorder.expectedSize(n) !== segm_size)) { + console.error('Failure decoding multirange header - fallback to single range request'); + return send_new_request('noranges'); } - } - this.highlightMesh(active_mesh, undefined, geo_object, geo_index); + reorder.addBuffer(n, res, o); - if (this.ctrl.update_browser) { - if (this.ctrl.highlight && tooltip) names = [tooltip]; - this.activateInBrowser(names); + o += segm_size; } - if (!resolve?.obj) - return tooltip; - - const lines = provideObjectInfo(resolve.obj); - lines.unshift(tooltip); - - return { name: resolve.obj.fName, title: resolve.obj.fTitle || resolve.obj._typename, lines }; - }; - - this._controls.processMouseLeave = function() { - this.processMouseMove([]); // to disable highlight and reset browser + send_new_request(true); }; - this._controls.processDblClick = () => { - // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this._controls) return; - - if (this._last_manifest) { - this._last_manifest.wireframe = !this._last_manifest.wireframe; - if (this._last_hidden) - this._last_hidden.forEach(obj => { obj.visible = true; }); - delete this._last_hidden; - delete this._last_manifest; - } else - this.adjustCameraPosition(true); + setFileUrl(); - this.render3D(); - }; + return send_new_request(true).then(() => promise); } - /** @summary Main function in geometry creation loop - * @desc Returns: - * - false when nothing todo - * - true if one could perform next action immediately - * - 1 when call after short timeout required - * - 2 when call must be done from processWorkerReply */ - nextDrawAction() { - if (!this._clones || this.isStage(stageInit)) return false; + /** @summary Returns file name */ + getFileName() { return this.fFileName; } - if (this.isStage(stageCollect)) { - if (this._geom_viewer) { - this._draw_all_nodes = false; - this.changeStage(stageAnalyze); - return true; + /** @summary Get directory with given name and cycle + * @desc Function only can be used for already read directories, which are preserved in the memory + * @private */ + getDir(dirname, cycle) { + if ((cycle === undefined) && isStr(dirname)) { + const pos = dirname.lastIndexOf(';'); + if (pos > 0) { + cycle = Number.parseInt(dirname.slice(pos + 1)); + dirname = dirname.slice(0, pos); } + } - // wait until worker is really started - if (this.ctrl.use_worker > 0) { - if (!this._worker) { this.startWorker(); return 1; } - if (!this._worker_ready) return 1; + for (let j = 0; j < this.fDirectories.length; ++j) { + const dir = this.fDirectories[j]; + if (dir.dir_name !== dirname) + continue; + if ((cycle !== undefined) && (dir.dir_cycle !== cycle)) + continue; + return dir; + } + return null; + } + + /** @summary Retrieve a key by its name and cycle in the list of keys + * @desc If only_direct not specified, returns Promise while key keys must be read first from the directory + * @private */ + getKey(keyname, cycle, only_direct) { + if (typeof cycle !== 'number') + cycle = -1; + let bestkey = null; + for (let i = 0; i < this.fKeys.length; ++i) { + const key = this.fKeys[i]; + if (!key || (key.fName !== keyname)) + continue; + if (key.fCycle === cycle) { + bestkey = key; + break; } + if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) + bestkey = key; + } + if (bestkey) + return only_direct ? bestkey : Promise.resolve(bestkey); - // first copy visibility flags and check how many unique visible nodes exists - let numvis = this._first_drawing ? this._clones.countVisibles() : 0, - matrix = null, frustum = null; + let pos = keyname.lastIndexOf('/'); + // try to handle situation when object name contains slashes (bad practice anyway) + while (pos > 0) { + const dirname = keyname.slice(0, pos), + subname = keyname.slice(pos + 1), + dir = this.getDir(dirname); - if (!numvis) - numvis = this._clones.markVisibles(false, false, !!this.geo_manager && !this.ctrl.showtop); + if (dir) + return dir.getKey(subname, cycle, only_direct); - if (this.ctrl.select_in_view && !this._first_drawing) { - // extract camera projection matrix for selection + const dirkey = this.getKey(dirname, undefined, true); + if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) + return this.readObject(dirname).then(newdir => newdir.getKey(subname, cycle)); - matrix = createProjectionMatrix(this._camera); + pos = keyname.lastIndexOf('/', pos - 1); + } - frustum = createFrustum(matrix); + return only_direct ? null : Promise.reject(Error(`Key not found ${keyname}`)); + } - // check if overall bounding box seen - if (frustum.CheckBox(this.getGeomBoundingBox())) { - matrix = null; // not use camera for the moment - frustum = null; - } + /** @summary Read and inflate object buffer described by its key + * @private */ + async readObjBuffer(key) { + return this.readBuffer([key.fSeekKey + key.fKeylen, key.fNbytes - key.fKeylen]).then(blob1 => { + if (key.fObjlen <= key.fNbytes - key.fKeylen) { + const buf = new TBuffer(blob1, 0, this); + buf.fTagOffset = key.fKeylen; + return buf; } - this._current_face_limit = this.ctrl.maxfaces; - if (matrix) this._current_face_limit *= 1.25; + return R__unzip(blob1, key.fObjlen).then(objbuf => { + if (!objbuf) + return Promise.reject(Error(`Fail to UNZIP buffer for ${key.fName}`)); - // here we decide if we need worker for the drawings - // main reason - too large geometry and large time to scan all camera positions - let need_worker = !this.isBatchMode() && browser.isChrome && ((numvis > 10000) || (matrix && (this._clones.scanVisible() > 1e5))); + const buf = new TBuffer(objbuf, 0, this); + buf.fTagOffset = key.fKeylen; + return buf; + }); + }); + } - // worker does not work when starting from file system - if (need_worker && exports.source_dir.indexOf('file://') === 0) { - console.log('disable worker for jsroot from file system'); - need_worker = false; - } + /** @summary Read any object from a root file + * @desc One could specify cycle number in the object name or as separate argument + * @param {string} obj_name - name of object, may include cycle number like 'hpxpy;1' + * @param {number} [cycle] - cycle number, also can be included in obj_name + * @return {Promise} promise with object read + * @example + * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; + * let f = await openFile('https://root.cern/js/files/hsimple.root'); + * let obj = await f.readObject('hpxpy;1'); + * console.log(`Read object of type ${obj._typename}`); */ + async readObject(obj_name, cycle, only_dir) { + const pos = obj_name.lastIndexOf(';'); + if (pos >= 0) { + cycle = Number.parseInt(obj_name.slice(pos + 1)); + obj_name = obj_name.slice(0, pos); + } - if (need_worker && !this._worker && (this.ctrl.use_worker >= 0)) - this.startWorker(); // we starting worker, but it may not be ready so fast + if (typeof cycle !== 'number') + cycle = -1; + // remove leading slashes + while (obj_name.length && (obj_name[0] === '/')) + obj_name = obj_name.slice(1); - if (!need_worker || !this._worker_ready) { - const res = this._clones.collectVisibles(this._current_face_limit, frustum); - this._new_draw_nodes = res.lst; - this._draw_all_nodes = res.complete; - this.changeStage(stageAnalyze); - return true; + // one uses Promises while in some cases we need to + // read sub-directory to get list of keys + // in such situation calls are asynchronous + return this.getKey(obj_name, cycle).then(key => { + if ((obj_name === nameStreamerInfo) && (key.fClassName === clTList)) + return this.fStreamerInfos; + + let isdir = false; + + if ((key.fClassName === clTDirectory || key.fClassName === clTDirectoryFile)) { + const dir = this.getDir(obj_name, cycle); + if (dir) + return dir; + isdir = true; } - const job = { - collect: this._current_face_limit, // indicator for the command - flags: this._clones.getVisibleFlags(), - matrix: matrix ? matrix.elements : null, - vislevel: this._clones.getVisLevel(), - maxvisnodes: this._clones.getMaxVisNodes() - }; + if (!isdir && only_dir) + return Promise.reject(Error(`Key ${obj_name} is not directory}`)); - this.submitToWorker(job); + return this.readObjBuffer(key).then(buf => { + if (isdir) { + const dir = new TDirectory(this, obj_name, cycle); + dir.fTitle = key.fTitle; + return dir.readKeys(buf); + } - this.changeStage(stageWorkerCollect); + const obj = {}; + buf.mapObject(1, obj); // tag object itself with id == 1 + buf.classStreamer(obj, key.fClassName); - return 2; // we now waiting for the worker reply - } + if ((key.fClassName === clTF1) || (key.fClassName === clTF12) || (key.fClassName === clTF2) || (key.fClassName === clTF3)) + return this.#readFormulas(obj); - if (this.isStage(stageWorkerCollect)) { - // do nothing, we are waiting for worker reply - return 2; + return obj; + }); + }); + } + + /** @summary read formulas from the file and add them to TF1/TF2 objects + * @private */ + async #readFormulas(tf1) { + const arr = []; + for (let indx = 0; indx < this.fKeys.length; ++indx) { + if (this.fKeys[indx].fClassName === 'TFormula') + arr.push(this.readObject(this.fKeys[indx].fName, this.fKeys[indx].fCycle)); } - if (this.isStage(stageAnalyze)) { - // here we merge new and old list of nodes for drawing, - // normally operation is fast and can be implemented with one c + return Promise.all(arr).then(formulas => { + formulas.forEach(obj => tf1.addFormula(obj)); + return tf1; + }); + } - if (this._new_append_nodes) { - this._new_draw_nodes = this._draw_nodes.concat(this._new_append_nodes); + /** @summary extract streamer infos from the buffer + * @private */ + extractStreamerInfos(buf) { + if (!buf) + return; - delete this._new_append_nodes; - } else if (this._draw_nodes) { - let del; - if (this._geom_viewer) - del = this._draw_nodes; - else - del = this._clones.mergeVisibles(this._new_draw_nodes, this._draw_nodes); + const lst = {}; + buf.mapObject(1, lst); - // remove should be fast, do it here - for (let n = 0; n < del.length; ++n) - this._clones.createObject3D(del[n].stack, this._toplevel, 'delete_mesh'); + try { + buf.classStreamer(lst, clTList); + } catch (err) { + console.error('Fail extract streamer infos', err); + return; + } - if (del.length > 0) - this.drawing_log = `Delete ${del.length} nodes`; - } + lst._typename = clTStreamerInfoList; - this._draw_nodes = this._new_draw_nodes; - delete this._new_draw_nodes; - this.changeStage(stageCollShapes); - return true; - } + this.fStreamerInfos = lst; - if (this.isStage(stageCollShapes)) { - // collect shapes - const shapes = this._clones.collectShapes(this._draw_nodes); + if (isFunc(internals.addStreamerInfosForPainter)) + internals.addStreamerInfosForPainter(lst); - // merge old and new list with produced shapes - this._build_shapes = this._clones.mergeShapesLists(this._build_shapes, shapes); + for (let k = 0; k < lst.arr.length; ++k) { + const si = lst.arr[k]; + if (!si.fElements) + continue; + for (let l = 0; l < si.fElements.arr.length; ++l) { + const elem = si.fElements.arr[l]; + if (!elem.fTypeName || !elem.fType) + continue; - this.changeStage(stageStartBuild); - return true; - } + let typ = elem.fType, typname = elem.fTypeName; - if (this.isStage(stageStartBuild)) { - // this is building of geometries, - // one can ask worker to build them or do it ourself + if (typ >= 60) { + if ((typ === kStreamer) && (elem._typename === clTStreamerSTL) && elem.fSTLtype && elem.fCtype && (elem.fCtype < 20)) { + const prefix = (StlNames[elem.fSTLtype] || 'undef') + '<'; + if ((typname.indexOf(prefix) === 0) && (typname.at(-1) === '>')) { + typ = elem.fCtype; + typname = typname.slice(prefix.length, typname.length - 1).trim(); - if (this.canSubmitToWorker()) { - const job = { limit: this._current_face_limit, shapes: [] }; - let cnt = 0; - for (let n = 0; n < this._build_shapes.length; ++n) { - let cl = null; - const item = this._build_shapes[n]; - // only submit not-done items - if (item.ready || item.geom) { - // this is place holder for existing geometry - cl = { id: item.id, ready: true, nfaces: countGeometryFaces(item.geom), refcnt: item.refcnt }; - } else { - cl = clone(item, null, true); - cnt++; + if ((elem.fSTLtype === kSTLmap) || (elem.fSTLtype === kSTLmultimap)) { + if (typname.indexOf(',') > 0) + typname = typname.slice(0, typname.indexOf(',')).trim(); + else + continue; + } + } } - - job.shapes.push(cl); + if (typ >= 60) + continue; + } else { + if ((typ > 20) && (typname.at(-1) === '*')) + typname = typname.slice(0, typname.length - 1); + typ %= 20; } - if (cnt > 0) { - /// only if some geom missing, submit job to the worker - this.submitToWorker(job); - this.changeStage(stageWorkerBuild); - return 2; - } - } + const kind = getTypeId(typname); + if ((kind === typ) || + ((typ === kBits) && (kind === kUInt)) || + ((typ === kCounter) && (kind === kInt))) + continue; - this.changeStage(stageBuild); + if (typname && typ && (this.fBasicTypes[typname] !== typ)) + this.fBasicTypes[typname] = typ; + } } + } - if (this.isStage(stageWorkerBuild)) { - // waiting shapes from the worker, worker should activate our code - return 2; - } + /** @summary Read file keys + * @private */ + async readKeys() { + // with the first readbuffer we read bigger amount to create header cache + return this.readBuffer([0, 400]).then(blob => { + const buf = new TBuffer(blob, 0, this); + if (buf.substring(0, 4) !== 'root') + return Promise.reject(Error(`Not a ROOT file ${this.fURL}`)); - if (this.isStage(stageBuild) || this.isStage(stageBuildReady)) { - if (this.isStage(stageBuild)) { - // building shapes + buf.shift(4); - const res = this._clones.buildShapes(this._build_shapes, this._current_face_limit, 500); - if (res.done) { - this.ctrl.info.num_shapes = this._build_shapes.length; - this.changeStage(stageBuildReady); - } else { - this.ctrl.info.num_shapes = res.shapes; - this.drawing_log = `Creating: ${res.shapes} / ${this._build_shapes.length} shapes, ${res.faces} faces`; - return true; - // if (res.notusedshapes < 30) return true; - } + this.fVersion = buf.ntou4(); + this.fBEGIN = buf.ntou4(); + if (this.fVersion < 1000000) { // small file + this.fEND = buf.ntou4(); + this.fSeekFree = buf.ntou4(); + this.fNbytesFree = buf.ntou4(); + buf.shift(4); // const nfree = buf.ntoi4(); + this.fNbytesName = buf.ntou4(); + this.fUnits = buf.ntou1(); + this.fCompress = buf.ntou4(); + this.fSeekInfo = buf.ntou4(); + this.fNbytesInfo = buf.ntou4(); + } else { // new format to support large files + this.fEND = buf.ntou8(); + this.fSeekFree = buf.ntou8(); + this.fNbytesFree = buf.ntou4(); + buf.shift(4); // const nfree = buf.ntou4(); + this.fNbytesName = buf.ntou4(); + this.fUnits = buf.ntou1(); + this.fCompress = buf.ntou4(); + this.fSeekInfo = buf.ntou8(); + this.fNbytesInfo = buf.ntou4(); } - // final stage, create all meshes - - const tm0 = new Date().getTime(), - toplevel = this.ctrl.project ? this._full_geom : this._toplevel; - let build_instanced = false, ready = true; - - if (!this.ctrl.project) - build_instanced = this._clones.createInstancedMeshes(this.ctrl, toplevel, this._draw_nodes, this._build_shapes, getRootColors()); + // empty file + if (!this.fSeekInfo || !this.fNbytesInfo) + return Promise.reject(Error(`File ${this.fURL} does not provide streamer infos`)); - if (!build_instanced) { - for (let n = 0; n < this._draw_nodes.length; ++n) { - const entry = this._draw_nodes[n]; - if (entry.done) continue; + // extra check to prevent reading of corrupted data + if (!this.fNbytesName || this.fNbytesName > 100000) + return Promise.reject(Error(`Cannot read directory info of the file ${this.fURL}`)); - /// shape can be provided with entry itself - const shape = entry.server_shape || this._build_shapes[entry.shapeid]; + // *-*-------------Read directory info + let nbytes = this.fNbytesName + 22; + nbytes += 4; // fDatimeC.Sizeof(); + nbytes += 4; // fDatimeM.Sizeof(); + nbytes += 18; // fUUID.Sizeof(); + // assume that the file may be above 2 Gbytes if file version is > 4 + if (this.fVersion >= 40000) + nbytes += 12; - this.createEntryMesh(entry, shape, toplevel); + // this part typically read from the header, no need to optimize + return this.readBuffer([this.fBEGIN, Math.max(300, nbytes)]); + }).then(blob3 => { + const buf3 = new TBuffer(blob3, 0, this); - const tm1 = new Date().getTime(); - if (tm1 - tm0 > 500) { ready = false; break; } - } - } + // keep only title from TKey data + this.fTitle = buf3.readTKey().fTitle; - if (ready) { - if (this.ctrl.project) { - this.changeStage(stageBuildProj); - return true; - } - this.changeStage(stageInit); - return false; - } + buf3.locate(this.fNbytesName); - if (!this.isStage(stageBuild)) - this.drawing_log = `Building meshes ${this.ctrl.info.num_meshes} / ${this.ctrl.info.num_faces}`; - return true; - } + // we read TDirectory part of TFile + buf3.classStreamer(this, clTDirectory); - if (this.isStage(stageWaitMain)) { - // wait for main painter to be ready + if (!this.fSeekKeys) + return Promise.reject(Error(`Empty keys list in ${this.fURL}`)); - if (!this._main_painter) { - this.changeStage(stageInit, 'Lost main painter'); - return false; - } - if (!this._main_painter._drawing_ready) return 1; + // read with same request keys and streamer infos + return this.readBuffer([this.fSeekKeys, this.fNbytesKeys, this.fSeekInfo, this.fNbytesInfo]); + }).then(blobs => { + const buf4 = new TBuffer(blobs[0], 0, this); - this.changeStage(stageBuildProj); // just do projection - } + buf4.readTKey(); + const nkeys = buf4.ntoi4(); + for (let i = 0; i < nkeys; ++i) + this.fKeys.push(buf4.readTKey()); - if (this.isStage(stageBuildProj)) { - this.doProjection(); - this.changeStage(stageInit); - return false; - } + const buf5 = new TBuffer(blobs[1], 0, this), + si_key = buf5.readTKey(); + if (!si_key) + return Promise.reject(Error(`Fail to read StreamerInfo data in ${this.fURL}`)); - console.error(`never come here, stage ${this.drawing_stage}`); + this.fKeys.push(si_key); + return this.readObjBuffer(si_key); + }).then(blob6 => { + this.extractStreamerInfos(blob6); + return this; + }); + } - return false; + /** @summary Read the directory content from a root file + * @desc If directory was already read - return previously read object + * Same functionality as {@link TFile#readObject} + * @param {string} dir_name - directory name + * @param {number} [cycle] - directory cycle + * @return {Promise} - promise with read directory */ + async readDirectory(dir_name, cycle) { + return this.readObject(dir_name, cycle, true); } - /** @summary Insert appropriate mesh for given entry */ - createEntryMesh(entry, shape, toplevel) { - // workaround for the TGeoOverlap, where two branches should get predefined color - if (this._splitColors && entry.stack) { - if (entry.stack[0] === 0) - entry.custom_color = 'green'; - else if (entry.stack[0] === 1) - entry.custom_color = 'blue'; - } + /** @summary Search streamer info + * @param {string} clanme - class name + * @param {number} [clversion] - class version + * @param {number} [checksum] - streamer info checksum, have to match when specified + * @private */ + findStreamerInfo(clname, clversion, checksum) { + if (!this.fStreamerInfos) + return null; - this._clones.createEntryMesh(this.ctrl, toplevel, entry, shape, getRootColors()); + const arr = this.fStreamerInfos.arr, len = arr.length; - return true; - } + if (checksum !== undefined) { + let cache = this.fStreamerInfos.cache; + if (!cache) + cache = this.fStreamerInfos.cache = {}; + let si = cache[checksum]; + if (si && (!clname || (si.fName === clname))) + return si; - /** @summary used by geometry viewer to show more nodes - * @desc These nodes excluded from selection logic and always inserted into the model - * Shape already should be created and assigned to the node */ - appendMoreNodes(nodes, from_drawing) { - if (!this.isStage(stageInit) && !from_drawing) { - this._provided_more_nodes = nodes; - return; + for (let i = 0; i < len; ++i) { + si = arr[i]; + if (si.fCheckSum === checksum) { + cache[checksum] = si; + if (!clname || (si.fName === clname)) + return si; + } + } + cache[checksum] = null; // checksum did not found, do not try again } - // delete old nodes - if (this._more_nodes) { - for (let n = 0; n < this._more_nodes.length; ++n) { - const entry = this._more_nodes[n], - obj3d = this._clones.createObject3D(entry.stack, this._toplevel, 'delete_mesh'); - disposeThreejsObject(obj3d); - cleanupShape(entry.server_shape); - delete entry.server_shape; + if (clname) { + for (let i = 0; i < len; ++i) { + const si = arr[i]; + if ((si.fName === clname) && ((si.fClassVersion === clversion) || (clversion === undefined))) + return si; } } - delete this._more_nodes; - - if (!nodes) return; + return null; + } - const real_nodes = []; + /** @summary Returns streamer for the class 'clname', + * @desc From the list of streamers or generate it from the streamer infos and add it to the list + * @private */ + getStreamer(clname, ver, s_i, only_plain) { + // these are special cases, which are handled separately + if (clname === clTQObject || clname === clTBasket) + return null; - for (let k = 0; k < nodes.length; ++k) { - const entry = nodes[k], - shape = entry.server_shape; - if (!shape?.ready) continue; + let fullname = clname; - if (this.createEntryMesh(entry, shape, this._toplevel)) - real_nodes.push(entry); + if (ver) { + fullname += (ver.checksum ? `$chksum${ver.checksum}` : `$ver${ver.val}`); + const streamer = this.fStreamers[fullname]; + if (streamer !== undefined) + return streamer; } - // remember additional nodes only if they include shape - otherwise one can ignore them - if (real_nodes.length > 0) - this._more_nodes = real_nodes; - - if (!from_drawing) this.render3D(); - } - - /** @summary Returns hierarchy of 3D objects used to produce projection. - * @desc Typically external master painter is used, but also internal data can be used */ - getProjectionSource() { - if (this._clones_owner) - return this._full_geom; - if (!this._main_painter) { - console.warn('MAIN PAINTER DISAPPER'); - return null; - } - if (!this._main_painter._drawing_ready) { - console.warn('MAIN PAINTER NOT READY WHEN DO PROJECTION'); - return null; - } - return this._main_painter._toplevel; - } + const custom = only_plain ? null : CustomStreamers[clname]; - /** @summary Extend custom geometry bounding box */ - extendCustomBoundingBox(box) { - if (!box) return; - if (!this._customBoundingBox) - this._customBoundingBox = new Box3().makeEmpty(); + // one can define in the user streamers just aliases + if (isStr(custom)) + return this.getStreamer(custom, ver, s_i); - const origin = this._customBoundingBox.clone(); - this._customBoundingBox.union(box); + // streamer is just separate function + if (isFunc(custom)) + return addClassMethods(clname, [{ typename: clname, func: custom }]); - if (!this._customBoundingBox.equals(origin)) - this._adjust_camera_with_render = true; - } + const streamer = []; - /** @summary Calculate geometry bounding box */ - getGeomBoundingBox(topitem, scalar) { - const box3 = new Box3(), check_any = !this._clones; - if (topitem === undefined) - topitem = this._toplevel; + if (isObject(custom)) { + if (!custom.name && !custom.func) + return custom; + streamer.push(custom); // special read entry, add in the beginning of streamer + } - box3.makeEmpty(); + // check element in streamer infos, one can have special cases + if (!s_i) + s_i = this.findStreamerInfo(clname, ver.val, ver.checksum); - if (this._customBoundingBox && (topitem === this._toplevel)) { - box3.union(this._customBoundingBox); - return box3; + if (!s_i) { + delete this.fStreamers[fullname]; + if (!ver.nowarning) + console.warn(`Not found streamer for ${clname} ver ${ver.val} checksum ${ver.checksum} full ${fullname}`); + return null; } - if (!topitem) { - box3.min.x = box3.min.y = box3.min.z = -1; - box3.max.x = box3.max.y = box3.max.z = 1; - return box3; + // special handling for TStyle which has duplicated member name fLineStyle + if (s_i.fName === clTStyle) { + s_i.fElements?.arr.forEach(elem => { + if (elem.fName === 'fLineStyle') + elem.fName = 'fLineStyles'; // like in ROOT JSON now + }); } - topitem.traverse(mesh => { - if (check_any || (mesh.stack && (mesh instanceof Mesh)) || - (mesh.main_track && (mesh instanceof LineSegments)) || (mesh.stacks && (mesh instanceof InstancedMesh))) - getBoundingBox(mesh, box3); - }); + // for each entry in streamer info produce member function + s_i.fElements?.arr.forEach(elem => streamer.push(createMemberStreamer(elem, this))); - if (scalar === 'original') { - box3.translate(new Vector3(-topitem.position.x, -topitem.position.y, -topitem.position.z)); - box3.min.multiply(new Vector3(1/topitem.scale.x, 1/topitem.scale.y, 1/topitem.scale.z)); - box3.max.multiply(new Vector3(1/topitem.scale.x, 1/topitem.scale.y, 1/topitem.scale.z)); - } else if (scalar !== undefined) - box3.expandByVector(box3.getSize(new Vector3()).multiplyScalar(scalar)); + this.fStreamers[fullname] = streamer; - return box3; + return addClassMethods(clname, streamer); } - /** @summary Create geometry projection */ - doProjection() { - const toplevel = this.getProjectionSource(); - - if (!toplevel) return false; - - disposeThreejsObject(this._toplevel, true); - - // let axis = this.ctrl.project; - - if (this.ctrl.projectPos === undefined) { - const bound = this.getGeomBoundingBox(toplevel), - min = bound.min[this.ctrl.project], max = bound.max[this.ctrl.project]; - let mean = (min + max)/2; + /** @summary Here we produce list of members, resolving all base classes + * @private */ + getSplittedStreamer(streamer, tgt) { + if (!streamer) + return tgt; - if ((min < 0) && (max > 0) && (Math.abs(mean) < 0.2*Math.max(-min, max))) mean = 0; // if middle is around 0, use 0 + if (!tgt) + tgt = []; - this.ctrl.projectPos = mean; - } + for (let n = 0; n < streamer.length; ++n) { + const elem = streamer[n]; - toplevel.traverse(mesh => { - if (!(mesh instanceof Mesh) || !mesh.stack) return; + if (elem.base === undefined) { + tgt.push(elem); + continue; + } - const geom2 = projectGeometry(mesh.geometry, mesh.parent.absMatrix || mesh.parent.matrixWorld, this.ctrl.project, this.ctrl.projectPos, mesh._flippedMesh); + if (elem.basename === clTObject) { + tgt.push({ + func(buf, obj) { + buf.ntoi2(); // read version, why it here?? + obj.fUniqueID = buf.ntou4(); + obj.fBits = buf.ntou4(); + if (obj.fBits & kIsReferenced) + buf.ntou2(); // skip pid + } + }); + continue; + } - if (!geom2) return; + const ver = { val: elem.base }; - const mesh2 = new Mesh(geom2, mesh.material.clone()); + if (ver.val === 4294967295) { + // this is -1 and indicates foreign class, need more workarounds + ver.val = 1; // need to search version 1 - that happens when several versions of foreign class exists ??? + } - this._toplevel.add(mesh2); + const parent = this.getStreamer(elem.basename, ver); + if (parent) + this.getSplittedStreamer(parent, tgt); + } - mesh2.stack = mesh.stack; - }); + return tgt; + } - return true; + /** @summary Fully cleanup TFile data + * @private */ + delete() { + this.fDirectories = null; + this.fKeys = null; + this.fStreamers = null; + this.fSeekInfo = 0; + this.fNbytesInfo = 0; + this.fTagOffset = 0; } - /** @summary Should be invoked when light configuration changed */ - changedLight(box) { - if (!this._camera) return; +} // class TFile - const need_render = !box; +// ============================================================= - if (!box) box = this.getGeomBoundingBox(); +/** + * @summary Interface to read local file in the browser + * + * @hideconstructor + * @desc Use {@link openFile} to create instance of the class + * @private + */ - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - plights = [], p = (this.ctrl.light.power ?? 1) * 0.5; +class TLocalFile extends TFile { - if (this._camera._lights !== this.ctrl.light.kind) { - // remove all childs and recreate only necessary lights - disposeThreejsObject(this._camera, true); + constructor(file) { + super(null); + this.fUseStampPar = false; + this.fLocalFile = file; + this.fEND = file.size; + this.fFullURL = file.name; + this.fURL = file.name; + this.fFileName = file.name; + } - this._camera._lights = this.ctrl.light.kind; + /** @summary Open local file + * @return {Promise} after file keys are read */ + async _open() { return this.readKeys(); } - switch (this._camera._lights) { - case 'ambient' : this._camera.add(new AmbientLight(0xefefef, p)); break; - case 'hemisphere' : this._camera.add(new HemisphereLight(0xffffbb, 0x080820, p)); break; - case 'mix': this._camera.add(new AmbientLight(0xefefef, p)); // intentionally without break + /** @summary read buffer from local file + * @return {Promise} with read data */ + async readBuffer(place, filename /* , progress_callback */) { + const file = this.fLocalFile; - // eslint-disable-next-line no-fallthrough - default: // 6 point lights - for (let n = 0; n < 6; ++n) { - const l = new DirectionalLight(0xefefef, p); - this._camera.add(l); - l._id = n; - } + return new Promise((resolve, reject) => { + if (filename) { + reject(Error(`Cannot access other local file ${filename}`)); + return; } - } - for (let k = 0; k < this._camera.children.length; ++k) { - const light = this._camera.children[k]; - let enabled = false; - if (light.isAmbientLight || light.isHemisphereLight) { - light.intensity = p; - continue; - } - if (!light.isDirectionalLight) continue; - switch (light._id) { - case 0: light.position.set(sizex/5, sizey/5, sizez/5); enabled = this.ctrl.light.specular; break; - case 1: light.position.set(0, 0, sizez/2); enabled = this.ctrl.light.front; break; - case 2: light.position.set(0, 2*sizey, 0); enabled = this.ctrl.light.top; break; - case 3: light.position.set(0, -2*sizey, 0); enabled = this.ctrl.light.bottom; break; - case 4: light.position.set(-2*sizex, 0, 0); enabled = this.ctrl.light.left; break; - case 5: light.position.set(2*sizex, 0, 0); enabled = this.ctrl.light.right; break; - } - light.power = enabled ? p*Math.PI*4 : 0; - if (enabled) plights.push(light); - } + const reader = new FileReader(), blobs = []; + let cnt = 0; - // keep light power of all soources constant - plights.forEach(ll => { ll.power = p*4*Math.PI/plights.length; }); + reader.onload = function(evnt) { + const res = new DataView(evnt.target.result); + if (place.length === 2) { + resolve(res); + return; + } - if (need_render) this.render3D(); - } + blobs.push(res); + cnt += 2; + if (cnt >= place.length) { + resolve(blobs); + return; + } + reader.readAsArrayBuffer(file.slice(place[cnt], place[cnt] + place[cnt + 1])); + }; - /** @summary Returns true if orthogarphic camera is used */ - isOrthoCamera() { - return this.ctrl.camera_kind.indexOf('ortho') === 0; + reader.readAsArrayBuffer(file.slice(place[0], place[0] + place[1])); + }); } - /** @summary Create configured camera */ - createCamera() { - if (this._camera) { - this._scene.remove(this._camera); - disposeThreejsObject(this._camera); - delete this._camera; - } +} // class TLocalFile - if (this.isOrthoCamera()) - this._camera = new OrthographicCamera(-this._scene_width/2, this._scene_width/2, this._scene_height/2, -this._scene_height/2, 1, 10000); - else { - this._camera = new PerspectiveCamera(25, this._scene_width / this._scene_height, 1, 10000); - this._camera.up = this.ctrl._yup ? new Vector3(0, 1, 0) : new Vector3(0, 0, 1); - } +/** + * @summary Interface to read file in node.js + * + * @hideconstructor + * @desc Use {@link openFile} to create instance of the class + * @private + */ - // Light - add default directional light, adjust later - const light = new DirectionalLight(0xefefef, 0.1); - light.position.set(10, 10, 10); - this._camera.add(light); +class TNodejsFile extends TFile { - this._scene.add(this._camera); + constructor(filename) { + super(null); + this.fUseStampPar = false; + this.fEND = 0; + this.fFullURL = filename; + this.fURL = filename; + this.fFileName = filename; } - /** @summary Create special effects */ - createSpecialEffects() { - if (this._webgl && this.ctrl.outline && isFunc(this.createOutline)) { - // code used with jsroot-based geometry drawing in EVE7, not important any longer - this._effectComposer = new EffectComposer(this._renderer); - this._effectComposer.addPass(new RenderPass(this._scene, this._camera)); - this.createOutline(this._scene_width, this._scene_height); - } + /** @summary Open file in node.js + * @return {Promise} after file keys are read */ + async _open() { + return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { + this.fs = fs; - this.ensureBloom(); + return new Promise((resolve, reject) => { + this.fs.open(this.fFileName, 'r', (status, fd) => { + if (status) { + console.log(status.message); + reject(Error(`Not possible to open ${this.fFileName} inside node.js`)); + } else { + const stats = this.fs.fstatSync(fd); + this.fEND = stats.size; + this.fd = fd; + this.readKeys().then(resolve).catch(reject); + } + }); + }); + }); } - /** @summary Initial scene creation */ - async createScene(w, h, render3d) { - if (this.superimpose) { - const cfg = getHistPainter3DCfg(this.getMainPainter()); - - if (cfg?.renderer) { - this._scene = cfg.scene; - this._scene_width = cfg.scene_width; - this._scene_height = cfg.scene_height; - this._renderer = cfg.renderer; - this._webgl = (this._renderer.jsroot_render3d === constants$1.Render3D.WebGL); - - this._toplevel = new Object3D(); - this._scene.add(this._toplevel); - - if (cfg.scale_x || cfg.scale_y || cfg.scale_z) - this._toplevel.scale.set(cfg.scale_x, cfg.scale_y, cfg.scale_z); - if (cfg.offset_x || cfg.offset_y || cfg.offset_z) - this._toplevel.position.set(cfg.offset_x, cfg.offset_y, cfg.offset_z); - this._toplevel.updateMatrix(); - this._toplevel.updateMatrixWorld(); - - this._camera = cfg.camera; + /** @summary Read buffer from node.js file + * @return {Promise} with requested blocks */ + async readBuffer(place, filename /* , progress_callback */) { + return new Promise((resolve, reject) => { + if (filename) { + reject(Error(`Cannot access other local file ${filename}`)); + return; } - return this._renderer?.jsroot_dom; - } + if (!this.fs || !this.fd) { + reject(Error(`File is not opened ${this.fFileName}`)); + return; + } - // three.js 3D drawing - this._scene = new Scene(); - this._fog = new Fog(0xffffff, 1, 10000); - this._scene.fog = this.ctrl.use_fog ? this._fog : null; + const blobs = []; + let cnt = 0; - this._scene.overrideMaterial = new MeshLambertMaterial({ color: 0x7000ff, vertexColors: false, transparent: true, opacity: 0.2, depthTest: false }); + const readfunc = (_err, _bytesRead, buf) => { + const res = new DataView(buf.buffer, buf.byteOffset, place[cnt + 1]); + if (place.length === 2) + return resolve(res); + blobs.push(res); + cnt += 2; + if (cnt >= place.length) + return resolve(blobs); + this.fs.read(this.fd, Buffer.alloc(place[cnt + 1]), 0, place[cnt + 1], place[cnt], readfunc); + }; - this._scene_width = w; - this._scene_height = h; + this.fs.read(this.fd, Buffer.alloc(place[1]), 0, place[1], place[0], readfunc); + }); + } - this.createCamera(); +} // class TNodejsFile - this._selected_mesh = null; +/** + * @summary Proxy to read file content + * + * @desc Should implement following methods: + * + * - openFile() - return Promise with true when file can be open normally + * - getFileName() - returns string with file name + * - getFileSize() - returns size of file + * - readBuffer(pos, len) - return promise with DataView for requested position and length + * + * @private + */ - this._overall_size = 10; +class FileProxy { - this._toplevel = new Object3D(); + async openFile() { return false; } + getFileName() { return ''; } + getFileSize() { return 0; } + async readBuffer(/* pos, sz */) { return null; } - this._scene.add(this._toplevel); +} // class FileProxy - this._scene.background = new Color(this.ctrl.background); +/** + * @summary File to use file context via FileProxy + * + * @hideconstructor + * @desc Use {@link openFile} to create instance of the class, providing FileProxy as argument + * @private + */ - return createRender3D(w, h, render3d, { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true }) - .then(r => { - this._renderer = r; +class TProxyFile extends TFile { - if (this.batch_format) - r.jsroot_image_format = this.batch_format; + constructor(proxy) { + super(null); + this.fUseStampPar = false; + this.proxy = proxy; + } - this._webgl = (this._renderer.jsroot_render3d === constants$1.Render3D.WebGL); + /** @summary Open file + * @return {Promise} after file keys are read */ + async _open() { + return this.proxy.openFile().then(res => { + if (!res) + return false; + this.fEND = this.proxy.getFileSize(); + this.fFullURL = this.fURL = this.fFileName = this.proxy.getFileName(); + if (isStr(this.fFileName)) { + const p = this.fFileName.lastIndexOf('/'); + if ((p > 0) && (p < this.fFileName.length - 4)) + this.fFileName = this.fFileName.slice(p + 1); + } + return this.readKeys(); + }); + } - if (this._renderer.setPixelRatio && !isNodeJs()) - this._renderer.setPixelRatio(window.devicePixelRatio); - this._renderer.setSize(w, h, !this._fit_main_area); - this._renderer.localClippingEnabled = true; + /** @summary Read buffer from FileProxy + * @return {Promise} with requested blocks */ + async readBuffer(place, filename /* , progress_callback */) { + if (filename) + return Promise.reject(Error(`Cannot access other file ${filename}`)); - this._renderer.setClearColor(this._scene.background, 1); + if (!this.proxy) + return Promise.reject(Error(`File is not opened ${this.fFileName}`)); - if (this._fit_main_area && this._webgl) { - this._renderer.domElement.style.width = '100%'; - this._renderer.domElement.style.height = '100%'; - const main = this.selectDom(); - if (main.style('position') === 'static') - main.style('position', 'relative'); - } + if (isFunc(this.proxy.readBuffers)) { + return this.proxy.readBuffers(place).then(arr => { + return arr?.length === 1 ? arr[0] : arr; + }); + } - this._animating = false; + if (place.length === 2) + return this.proxy.readBuffer(place[0], place[1]); - this.ctrl.doubleside = false; // both sides need for clipping - this.createSpecialEffects(); + const arr = []; + for (let k = 0; k < place.length; k += 2) + arr.push(this.proxy.readBuffer(place[k], place[k + 1])); + return Promise.all(arr); + } - if (this._fit_main_area && !this._webgl) { - // create top-most SVG for geomtery drawings - const doc = getDocument(), - svg = doc.createElementNS(nsSVG, 'svg'); - svg.setAttribute('width', w); - svg.setAttribute('height', h); - svg.appendChild(this._renderer.jsroot_dom); - return svg; - } +} // class TProxyFile - return this._renderer.jsroot_dom; - }); - } - /** @summary Start geometry drawing */ - startDrawGeometry(force) { - if (!force && !this.isStage(stageInit)) { - this._draw_nodes_again = true; - return; - } +/** @summary Open ROOT file for reading + * @desc Generic method to open ROOT file for reading + * Following kind of arguments can be provided: + * - string with file URL (see example). In node.js environment local file like 'file://hsimple.root' can be specified + * - [File]{@link https://developer.mozilla.org/en-US/docs/Web/API/File} instance which let read local files from browser + * - [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer} instance with complete file content + * - [FileProxy]{@link FileProxy} let access arbitrary files via tiny proxy API + * @param {string|object} arg - argument for file open like url, see details + * @param {object} [opts] - extra arguments + * @param {Number} [opts.timeout=0] - read timeout for http requests in ms + * @param {Object} [opts.remap={}] - http server remap to fallback when main server fails, like { 'https://original.server/': 'https://fallback.server/' } + * @return {object} - Promise with {@link TFile} instance when file is opened + * @example + * + * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; + * let f = await openFile('https://root.cern/js/files/hsimple.root'); + * console.log(`Open file ${f.getFileName()}`); */ +function openFile(arg, opts) { + let file, plain_file; - if (this._clones_owner && this._clones) - this._clones.setDefaultColors(this.ctrl.dflt_colors); + if (isNodeJs() && isStr(arg)) { + if (!arg.indexOf('file://')) + file = new TNodejsFile(arg.slice(7)); + else if (arg.indexOf('http')) + file = new TNodejsFile(arg); + } - this._startm = new Date().getTime(); - this._last_render_tm = this._startm; - this._last_render_meshes = 0; - this.changeStage(stageCollect); - this._drawing_ready = false; - this.ctrl.info.num_meshes = 0; - this.ctrl.info.num_faces = 0; - this.ctrl.info.num_shapes = 0; - this._selected_mesh = null; + if (!file && isObject(arg) && (arg instanceof FileProxy)) + file = new TProxyFile(arg); - if (this.ctrl.project) { - if (this._clones_owner) { - if (this._full_geom) - this.changeStage(stageBuildProj); - else - this._full_geom = new Object3D(); - } else - this.changeStage(stageWaitMain); - } + if (!file && isObject(arg) && (arg instanceof ArrayBuffer)) { + file = new TFile('localfile.root'); + file.assignFileContent(arg); + } - delete this._last_manifest; - delete this._last_hidden; // clear list of hidden objects + if (!file && isObject(arg) && arg.size && arg.name) + file = new TLocalFile(arg); - delete this._draw_nodes_again; // forget about such flag + if (!file) { + file = new TFile(arg); + plain_file = true; + file.assignRemap(settings.FilesRemap); + } - this.continueDraw(); + if (opts && isObject(opts)) { + if (opts.timeout) + file.setTimeout(opts.timeout); + if (plain_file && opts.remap) + file.assignRemap(opts.remap); } - /** @summary reset all kind of advanced features like depth test */ - resetAdvanced() { - this.ctrl.depthTest = true; - this.ctrl.clipIntersect = true; - this.ctrl.depthMethod = 'ray'; + return file._open(); +} - this.changedDepthMethod('norender'); - this.changedDepthTest(); - } +/** @summary Unzip JSON string + * @desc Should be used for buffer produced with TBufferJSON::zipJSON() method + * @param tgtsize - original length of json string + * @param src - string with data returned by TBufferJSON::zipJSON + * @return {Promise} with unzipped string */ + +async function unzipJSON(tgtsize, src) { + const bindata = atob_func(src), + buf = new ArrayBuffer(bindata.length), + bufView = new DataView(buf); + for (let i = 0; i < bindata.length; i++) + bufView.setUint8(i, bindata.charCodeAt(i)); + + return R__unzip(bufView, tgtsize).then(resView => { + let resstr = ''; + for (let i = 0; i < tgtsize; i++) + resstr += String.fromCharCode(resView.getUint8(i)); + return resstr; + }); +} - /** @summary returns maximal dimension */ - getOverallSize(force) { - if (!this._overall_size || force || this._customBoundingBox) { - const box = this.getGeomBoundingBox(); +// special way to assign methods when streaming objects +addClassMethods(clTNamed, CustomStreamers[clTNamed]); +addClassMethods(clTObjString, CustomStreamers[clTObjString]); + +// branch types +const kLeafNode = 0, kBaseClassNode = 1, kObjectNode = 2, kClonesNode = 3, + kSTLNode = 4, kClonesMemberNode = 31, kSTLMemberNode = 41, + // branch bits + // kDoNotProcess = BIT(10), // Active bit for branches + // kIsClone = BIT(11), // to indicate a TBranchClones + // kBranchObject = BIT(12), // branch is a TObject* + // kBranchAny = BIT(17), // branch is an object* + // kAutoDelete = BIT(15), + kDoNotUseBufferMap = BIT(22), // If set, at least one of the entry in the branch will use the buffer's map of classname and objects. + clTBranchElement = 'TBranchElement', clTBranchFunc = 'TBranchFunc'; - // if detect of coordinates fails - ignore - if (!Number.isFinite(box.min.x)) return 1000; +/** + * @summary Class to read data from TTree + * + * @desc Instance of TSelector can be used to access TTree data + */ - this._overall_size = 2 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z); - } +class TSelector { - return this._overall_size; + /** @summary constructor */ + constructor() { + this._branches = []; // list of branches to read + this._names = []; // list of member names for each branch in tgtobj + this._directs = []; // indication if only branch without any children should be read + this._break = 0; + this.tgtobj = {}; } - /** @summary Create png image with drawing snapshot. */ - createSnapshot(filename) { - if (!this._renderer) return; - this.render3D(0); - const dataUrl = this._renderer.domElement.toDataURL('image/png'); - if (filename === 'asis') return dataUrl; - dataUrl.replace('image/png', 'image/octet-stream'); - const doc = getDocument(), - link = doc.createElement('a'); - if (isStr(link.download)) { - doc.body.appendChild(link); // Firefox requires the link to be in the body - link.download = filename || 'geometry.png'; - link.href = dataUrl; - link.click(); - doc.body.removeChild(link); // remove the link when done - } + /** @summary Add branch to the selector + * @desc Either branch name or branch itself should be specified + * Second parameter defines member name in the tgtobj + * If selector.addBranch('px', 'read_px') is called, + * branch will be read into selector.tgtobj.read_px member + * If second parameter not specified, branch name (here 'px') will be used + * If branch object specified as first parameter and second parameter missing, + * then member like 'br0', 'br1' and so on will be assigned + * @param {string|Object} branch - name of branch (or branch object itself} + * @param {string} [name] - member name in tgtobj where data will be read + * @param {boolean} [direct] - if only branch without any children should be read */ + addBranch(branch, name, direct) { + if (!name) + name = isStr(branch) ? branch : `br${this._branches.length}`; + this._branches.push(branch); + this._names.push(name); + this._directs.push(direct); + return this._branches.length - 1; } - /** @summary Returns url parameters defining camera position. - * @desc Either absolute position are provided (arg === true) or zoom, roty, rotz parameters */ - produceCameraUrl(arg) { - if (!this._camera) - return ''; + /** @summary returns number of branches used in selector */ + numBranches() { return this._branches.length; } - if (this._camera.isOrthographicCamera) { - const zoom = Math.round(this._camera.zoom * 100); - return this.ctrl.camera_kind + (zoom === 100 ? '' : `,zoom=${zoom}`); - } + /** @summary returns branch by index used in selector */ + getBranch(indx) { return this._branches[indx]; } - let kind = ''; - if (this.ctrl.camera_kind !== 'perspective') - kind = this.ctrl.camera_kind + ','; + /** @summary returns index of branch + * @private */ + indexOfBranch(branch) { return this._branches.indexOf(branch); } - if (arg === true) { - const p = this._camera?.position, t = this._controls?.target; - if (!p || !t) return ''; + /** @summary returns name of branch + * @private */ + nameOfBranch(indx) { return this._names[indx]; } - const conv = v => { - let s = ''; - if (v < 0) { s = 'n'; v = -v; } - return s + v.toFixed(0); - }; + /** @summary function called during TTree processing + * @abstract + * @param {number} progress - current value between 0 and 1 */ + ShowProgress(/* progress */) {} - let res = `${kind}camx${conv(p.x)},camy${conv(p.y)},camz${conv(p.z)}`; - if (t.x || t.y || t.z) res += `,camlx${conv(t.x)},camly${conv(t.y)},camlz${conv(t.z)}`; - return res; - } + /** @summary call this function to abort processing */ + Abort() { this._break = -1111; } - if (!this._lookat || !this._camera0pos) - return ''; + /** @summary function called before start processing + * @abstract + * @param {object} tree - tree object */ + Begin(/* tree */) {} - const pos1 = new Vector3().add(this._camera0pos).sub(this._lookat), - pos2 = new Vector3().add(this._camera.position).sub(this._lookat), - zoom = Math.min(10000, Math.max(1, this.ctrl.zoom * pos2.length() / pos1.length() * 100)); + /** @summary function called when next entry extracted from the tree + * @abstract + * @param {number} entry - read entry number */ + Process(/* entry */) {} - pos1.normalize(); - pos2.normalize(); + /** @summary function called at the very end of processing + * @abstract + * @param {boolean} res - true if all data were correctly processed */ + Terminate(/* res */) {} - const quat = new Quaternion(), euler = new Euler(); +} // class TSelector - quat.setFromUnitVectors(pos1, pos2); - euler.setFromQuaternion(quat, 'YZX'); +// ================================================================= - let roty = euler.y / Math.PI * 180, - rotz = euler.z / Math.PI * 180; +/** @summary Checks array kind + * @desc return 0 when not array + * 1 - when arbitrary array + * 2 - when plain (1-dim) array with same-type content + * @private */ +function checkArrayPrototype(arr, check_content) { + if (!isObject(arr)) + return 0; - if (roty < 0) roty += 360; - if (rotz < 0) rotz += 360; - return `${kind}roty${roty.toFixed(0)},rotz${rotz.toFixed(0)},zoom${zoom.toFixed(0)}`; - } + const arr_kind = isArrayProto(Object.prototype.toString.apply(arr)); + if (!check_content || (arr_kind !== 1)) + return arr_kind; - /** @summary Calculates current zoom factor */ - calculateZoom() { - if (this._camera0pos && this._camera && this._lookat) { - const pos1 = new Vector3().add(this._camera0pos).sub(this._lookat), - pos2 = new Vector3().add(this._camera.position).sub(this._lookat); - return pos2.length() / pos1.length(); + let typ, plain = true; + for (let k = 0; k < arr.length; ++k) { + const sub = typeof arr[k]; + if (!typ) + typ = sub; + if ((sub !== typ) || (isObject(sub) && checkArrayPrototype(arr[k]))) { + plain = false; + break; } - - return 0; } - /** @summary Place camera to default position, - * @param arg - true forces camera readjustment, 'first' is called when suppose to be first after complete drawing - * @param keep_zoom - tries to keep zomming factor of the camera */ - adjustCameraPosition(arg, keep_zoom) { - if (!this._toplevel || this.superimpose) return; + return plain ? 2 : 1; +} - const force = (arg === true), - first_time = (arg === 'first') || force, - only_set = (arg === 'only_set'), - box = this.getGeomBoundingBox(); +/** + * @summary Class to iterate over array elements + * + * @private + */ - // let box2 = new Box3().makeEmpty(); - // box2.expandByObject(this._toplevel, true); - // console.log('min,max', box.min.x, box.max.x, box.min.y, box.max.y, box.min.z, box.max.z ); +class ArrayIterator { - // if detect of coordinates fails - ignore - if (!Number.isFinite(box.min.x)) { - console.log('FAILS to get geometry bounding box'); - return; - } + /** @summary constructor */ + constructor(arr, select, tgtobj) { + this.object = arr; + this.value = 0; // value always used in iterator + this.arr = []; // all arrays + this.indx = []; // all indexes + this.cnt = -1; // current index counter + this.tgtobj = tgtobj; - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x)/2, - midy = (box.max.y + box.min.y)/2, - midz = (box.max.z + box.min.z)/2, - more = this.ctrl._axis || (this.ctrl.camera_overlay === 'bar') ? 0.2 : 0.1; + if (isObject(select)) + this.select = select; // remember indexes for selection + else + this.select = []; // empty array, undefined for each dimension means iterate over all indexes + } - if (this._scene_size && !force) { - const d = this._scene_size, test = (v1, v2, scale) => { - if (!scale) scale = Math.abs((v1+v2)/2); - return scale <= 1e-20 ? true : Math.abs(v2-v1)/scale > 0.01; - }, - large_change = test(sizex, d.sizex) || test(sizey, d.sizey) || test(sizez, d.sizez) || - test(midx, d.midx, d.sizex) || test(midy, d.midy, d.sizey) || test(midz, d.midz, d.sizez); - if (!large_change) { - if (this.ctrl.select_in_view) - this.startDrawGeometry(); - return; + /** @summary next element */ + next() { + let obj, typ, cnt = this.cnt; + + if (cnt >= 0) { + if (++this.fastindx < this.fastlimit) { + this.value = this.fastarr[this.fastindx]; + return true; } - } - this._scene_size = { sizex, sizey, sizez, midx, midy, midz }; + while (--cnt >= 0) { + if ((this.select[cnt] === undefined) && (++this.indx[cnt] < this.arr[cnt].length)) + break; + } + if (cnt < 0) + return false; + } - this._overall_size = 2 * Math.max(sizex, sizey, sizez); + while (true) { + obj = (cnt < 0) ? this.object : (this.arr[cnt])[this.indx[cnt]]; - this._camera.near = this._overall_size / 350; - this._camera.far = this._overall_size * 100; - this._fog.near = this._overall_size * 0.5; - this._fog.far = this._overall_size * 5; + typ = obj ? typeof obj : 'any'; - if (first_time) { - for (let naxis = 0; naxis < 3; ++naxis) { - const cc = this.ctrl.clip[naxis]; - cc.min = box.min[cc.name]; - cc.max = box.max[cc.name]; - const sz = cc.max - cc.min; - cc.max += sz*0.01; - cc.min -= sz*0.01; - if (sz > 100) - cc.step = 0.1; - else if (sz > 1) - cc.step = 0.001; + if (typ === 'object') { + if (obj._typename !== undefined) { + if (isRootCollection(obj)) { + obj = obj.arr; + typ = 'array'; + } else + typ = 'any'; + } else if (Number.isInteger(obj.length) && (checkArrayPrototype(obj) > 0)) + typ = 'array'; else - cc.step = undefined; + typ = 'any'; + } - if (!cc.value) - cc.value = (cc.min + cc.max) / 2; - else if (cc.value < cc.min) - cc.value = cc.min; - else if (cc.value > cc.max) - cc.value = cc.max; + if (this.select[cnt + 1] === '$self$') { + this.value = obj; + this.fastindx = this.fastlimit = 0; + this.cnt = cnt + 1; + return true; + } + + if ((typ === 'any') && isStr(this.select[cnt + 1])) { + // this is extraction of the member from arbitrary class + this.arr[++cnt] = obj; + this.indx[cnt] = this.select[cnt]; // use member name as index + continue; + } + + if ((typ === 'array') && (obj.length || (this.select[cnt + 1] === '$size$'))) { + this.arr[++cnt] = obj; + switch (this.select[cnt]) { + case undefined: + this.indx[cnt] = 0; + break; + case '$last$': + this.indx[cnt] = obj.length - 1; + break; + case '$size$': + this.value = obj.length; + this.fastindx = this.fastlimit = 0; + this.cnt = cnt; + return true; + default: + if (Number.isInteger(this.select[cnt])) { + this.indx[cnt] = this.select[cnt]; + if (this.indx[cnt] < 0) + this.indx[cnt] = obj.length - 1; + } else { + // this is compile variable as array index - can be any expression + this.select[cnt].produce(this.tgtobj); + this.indx[cnt] = Math.round(this.select[cnt].get(0)); + } + } + } else { + if (cnt < 0) + return false; + + this.value = obj; + if (this.select[cnt] === undefined) { + this.fastarr = this.arr[cnt]; + this.fastindx = this.indx[cnt]; + this.fastlimit = this.fastarr.length; + } else + this.fastindx = this.fastlimit = 0; // no any iteration on that level + + this.cnt = cnt; + return true; } } - let k = 2*this.ctrl.zoom; - const max_all = Math.max(sizex, sizey, sizez), - sign = this.ctrl.camera_kind.indexOf('N') > 0 ? -1 : 1; + // unreachable code + // return false; + } + + /** @summary reset iterator */ + reset() { + this.arr = []; + this.indx = []; + delete this.fastarr; + this.cnt = -1; + this.value = 0; + } - this._lookat = new Vector3(midx, midy, midz); - this._camera0pos = new Vector3(-2*max_all, 0, 0); // virtual 0 position, where rotation starts +} // class ArrayIterator - this._camera.updateMatrixWorld(); - this._camera.updateProjectionMatrix(); - if ((this.ctrl.rotatey || this.ctrl.rotatez) && this.ctrl.can_rotate) { - const prev_zoom = this.calculateZoom(); - if (keep_zoom && prev_zoom) k = 2*prev_zoom; +/** @summary return TStreamerElement associated with the branch - if any + * @desc unfortunately, branch.fID is not number of element in streamer info + * @private */ +function findBrachStreamerElement(branch, file) { + if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) + return null; - const euler = new Euler(0, this.ctrl.rotatey/180*Math.PI, this.ctrl.rotatez/180*Math.PI, 'YZX'); + const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum), + arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null; + if (!arr) + return null; - this._camera.position.set(-k*max_all, 0, 0); - this._camera.position.applyEuler(euler); - this._camera.position.add(new Vector3(midx, midy, midz)); + let match_name = branch.fName, + pos = match_name.indexOf('['); + if (pos > 0) + match_name = match_name.slice(0, pos); + pos = match_name.lastIndexOf('.'); + if (pos > 0) + match_name = match_name.slice(pos + 1); - if (keep_zoom && prev_zoom) { - const actual_zoom = this.calculateZoom(); - k *= prev_zoom/actual_zoom; + function match_elem(elem) { + if (!elem) + return false; + if (elem.fName !== match_name) + return false; + if (elem.fType === branch.fStreamerType) + return true; + if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) + return true; + if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) || + (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) && + (elem.fType === kStreamer)) + return true; + console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`); + return false; + } - this._camera.position.set(-k*max_all, 0, 0); - this._camera.position.applyEuler(euler); - this._camera.position.add(new Vector3(midx, midy, midz)); - } - } else if (this.ctrl.camx !== undefined && this.ctrl.camy !== undefined && this.ctrl.camz !== undefined) { - this._camera.position.set(this.ctrl.camx, this.ctrl.camy, this.ctrl.camz); - this._lookat.set(this.ctrl.camlx || 0, this.ctrl.camly || 0, this.ctrl.camlz || 0); - this.ctrl.camx = this.ctrl.camy = this.ctrl.camz = this.ctrl.camlx = this.ctrl.camly = this.ctrl.camlz = undefined; - } else if ((this.ctrl.camera_kind === 'orthoXOY') || (this.ctrl.camera_kind === 'orthoXNOY')) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(sign < 0 ? midx*2 : 0, 0, midz + sign*sizez*2); - this._lookat.set(sign < 0 ? midx*2 : 0, 0, midz); - this._camera.left = box.min.x - more*sizex; - this._camera.right = box.max.x + more*sizex; - this._camera.top = box.max.y + more*sizey; - this._camera.bottom = box.min.y - more*sizey; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoSign = sign; - this._camera.orthoZ = [midz, sizez/2]; - } else if ((this.ctrl.camera_kind === 'orthoXOZ') || (this.ctrl.camera_kind === 'orthoXNOZ')) { - this._camera.up.set(0, 0, 1); - this._camera.position.set(sign < 0 ? midx*2 : 0, midy - sign*sizey*2, 0); - this._lookat.set(sign < 0 ? midx*2 : 0, midy, 0); - this._camera.left = box.min.x - more*sizex; - this._camera.right = box.max.x + more*sizex; - this._camera.top = box.max.z + more*sizez; - this._camera.bottom = box.min.z - more*sizez; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [0, 2, 1]; - this._camera.orthoRotation = geom => geom.rotateX(Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midy, -sizey/2]; - } else if ((this.ctrl.camera_kind === 'orthoZOY') || (this.ctrl.camera_kind === 'orthoZNOY')) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx - sign*sizex*2, 0, sign < 0 ? midz*2 : 0); - this._lookat.set(midx, 0, sign < 0 ? midz*2 : 0); - this._camera.left = box.min.z - more*sizez; - this._camera.right = box.max.z + more*sizez; - this._camera.top = box.max.y + more*sizey; - this._camera.bottom = box.min.y - more*sizey; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [2, 1, 0]; - this._camera.orthoRotation = geom => geom.rotateY(-Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midx, -sizex/2]; - } else if ((this.ctrl.camera_kind === 'orthoZOX') || (this.ctrl.camera_kind === 'orthoZNOX')) { - this._camera.up.set(1, 0, 0); - this._camera.position.set(0, midy - sign*sizey*2, sign > 0 ? midz*2 : 0); - this._lookat.set(0, midy, sign > 0 ? midz*2 : 0); - this._camera.left = box.min.z - more*sizez; - this._camera.right = box.max.z + more*sizez; - this._camera.top = box.max.x + more*sizex; - this._camera.bottom = box.min.x - more*sizex; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [2, 0, 1]; - this._camera.orthoRotation = geom => geom.rotateX(Math.PI/2).rotateY(Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midy, -sizey/2]; - } else if (this.ctrl.project) { - switch (this.ctrl.project) { - case 'x': this._camera.position.set(k*1.5*Math.max(sizey, sizez), 0, 0); break; - case 'y': this._camera.position.set(0, k*1.5*Math.max(sizex, sizez), 0); break; - case 'z': this._camera.position.set(0, 0, k*1.5*Math.max(sizex, sizey)); break; - } - } else if (this.ctrl.camera_kind === 'perspXOZ') { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx - 3*max_all, midy, midz); - } else if (this.ctrl.camera_kind === 'perspYOZ') { - this._camera.up.set(1, 0, 0); - this._camera.position.set(midx, midy - 3*max_all, midz); - } else if (this.ctrl.camera_kind === 'perspXOY') { - this._camera.up.set(0, 0, 1); - this._camera.position.set(midx - 3*max_all, midy, midz); - } else if (this.ctrl._yup) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx-k*Math.max(sizex, sizez), midy+k*sizey, midz-k*Math.max(sizex, sizez)); - } else { - this._camera.up.set(0, 0, 1); - this._camera.position.set(midx-k*Math.max(sizex, sizey), midy-k*Math.max(sizex, sizey), midz+k*sizez); - } + // first check branch fID - in many cases gut guess + if (match_elem(arr[branch.fID])) + return arr[branch.fID]; - if (this._camera.isOrthographicCamera && this.isOrthoCamera() && this._scene_width && this._scene_height) { - const screen_ratio = this._scene_width / this._scene_height, - szx = this._camera.right - this._camera.left, szy = this._camera.top - this._camera.bottom; + for (let k = 0; k < arr.length; ++k) { + if ((k !== branch.fID) && match_elem(arr[k])) + return arr[k]; + } - if (screen_ratio > szx / szy) { - // screen wider than actual geometry - const m = (this._camera.right + this._camera.left) / 2; - this._camera.left = m - szy * screen_ratio / 2; - this._camera.right = m + szy * screen_ratio / 2; - } else { - // screen heigher than actual geometry - const m = (this._camera.top + this._camera.bottom) / 2; - this._camera.top = m + szx / screen_ratio / 2; - this._camera.bottom = m - szx / screen_ratio / 2; - } - } + console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`); - this._camera.lookAt(this._lookat); - this._camera.updateProjectionMatrix(); + return null; +} - this.changedLight(box); - if (this._controls) { - this._controls.target.copy(this._lookat); - if (!only_set) this._controls.update(); - } +/** @summary return class name of the object, stored in the branch + * @private */ +function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = false) { + if (!branch || (branch._typename !== clTBranchElement)) + return ''; - // recheck which elements to draw - if (this.ctrl.select_in_view && !only_set) - this.startDrawGeometry(); + if ((branch.fType === kLeafNode) && (branch.fID === -2) && (branch.fStreamerType === -1)) { + // object where all sub-branches will be collected + return branch.fClassName; } - /** @summary Specifies camera position as rotation around geometry center */ - setCameraPosition(rotatey, rotatez, zoom) { - if (!this.ctrl) return; - this.ctrl.rotatey = rotatey || 0; - this.ctrl.rotatez = rotatez || 0; - let preserve_zoom = false; - if (zoom && Number.isFinite(zoom)) - this.ctrl.zoom = zoom; - else - preserve_zoom = true; + if (with_clones && branch.fClonesName && ((branch.fType === kClonesNode) || (branch.fType === kSTLNode))) + return branch.fClonesName; - this.adjustCameraPosition(false, preserve_zoom); + const s_elem = findBrachStreamerElement(branch, tree.$file); + if ((branch.fType === kBaseClassNode) && s_elem && (s_elem.fTypeName === kBaseClass)) + return s_elem.fName; + + if (branch.fType === kObjectNode) { + if (s_elem && ((s_elem.fType === kObject) || (s_elem.fType === kAny))) + return s_elem.fTypeName; + return clTObject; } - /** @summary Specifies camera position and point to which it looks to - @desc Both specified in absolute coordinates */ - setCameraPositionAndLook(camx, camy, camz, lookx, looky, lookz) { - if (!this.ctrl) - return; - this.ctrl.camx = camx; - this.ctrl.camy = camy; - this.ctrl.camz = camz; - this.ctrl.camlx = lookx; - this.ctrl.camly = looky; - this.ctrl.camlz = lookz; - this.adjustCameraPosition(false); + if ((branch.fType === kLeafNode) && s_elem && with_leafs) { + if ((s_elem.fType === kObject) || (s_elem.fType === kAny)) + return s_elem.fTypeName; + if (s_elem.fType === kObjectp) + return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1); } - /** @summary focus on item */ - focusOnItem(itemname) { - if (!itemname || !this._clones) return; + return ''; +} - const stack = this._clones.findStackByName(itemname); - if (stack) - this.focusCamera(this._clones.resolveStack(stack, true), false); +/** @summary Get branch with specified id + * @desc All sub-branches checked as well + * @return {Object} branch + * @private */ +function getTreeBranch(tree, id) { + if (!Number.isInteger(id)) + return; + let res, seq = 0; + function scan(obj) { + obj?.fBranches?.arr.forEach(br => { + if (seq++ === id) + res = br; + if (!res) + scan(br); + }); } - /** @summary focus camera on speicifed position */ - focusCamera(focus, autoClip) { - if (this.ctrl.project || this.isOrthoCamera()) { - this.adjustCameraPosition(true); - return this.render3D(); - } + scan(tree); + return res; +} - let box = new Box3(); - if (focus === undefined) - box = this.getGeomBoundingBox(); - else if (focus instanceof Mesh) - box.setFromObject(focus); - else { - const center = new Vector3().setFromMatrixPosition(focus.matrix), - node = focus.node, - halfDelta = new Vector3(node.fDX, node.fDY, node.fDZ).multiplyScalar(0.5); - box.min = center.clone().sub(halfDelta); - box.max = center.clone().add(halfDelta); - } - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x)/2, - midy = (box.max.y + box.min.y)/2, - midz = (box.max.z + box.min.z)/2; +/** @summary Special branch search + * @desc Name can include extra part, which will be returned in the result + * @param {string} name - name of the branch + * @return {Object} with 'branch' and 'rest' members + * @private */ +function findBranchComplex(tree, name, lst = undefined, only_search = false) { + let top_search = false, search = name, res = null; - let position, frames = 50, step = 0; - if (this.ctrl._yup) - position = new Vector3(midx-2*Math.max(sizex, sizez), midy+2*sizey, midz-2*Math.max(sizex, sizez)); - else - position = new Vector3(midx-2*Math.max(sizex, sizey), midy-2*Math.max(sizex, sizey), midz+2*sizez); + if (!lst) { + top_search = true; + lst = tree.fBranches; + const pos = search.indexOf('['); + if (pos > 0) + search = search.slice(0, pos); + } - const target = new Vector3(midx, midy, midz), - oldTarget = this._controls.target, - // Amount to change camera position at each step - posIncrement = position.sub(this._camera.position).divideScalar(frames), - // Amount to change 'lookAt' so it will end pointed at target - targetIncrement = target.sub(oldTarget).divideScalar(frames); + if (!lst?.arr.length) + return null; - autoClip = autoClip && this._webgl; + for (let n = 0; n < lst.arr.length; ++n) { + let brname = lst.arr[n].fName; + if (brname.at(-1) === ']') + brname = brname.slice(0, brname.indexOf('[')); - // Automatic Clipping - if (autoClip) { - for (let axis = 0; axis < 3; ++axis) { - const cc = this.ctrl.clip[axis]; - if (!cc.enabled) { cc.value = cc.min; cc.enabled = true; } - cc.inc = ((cc.min + cc.max) / 2 - cc.value) / frames; - } - this.updateClipping(); + // special case when branch name includes STL map name + if (search.indexOf(brname) && (brname.indexOf('<') > 0)) { + const p1 = brname.indexOf('<'), p2 = brname.lastIndexOf('>'); + brname = brname.slice(0, p1) + brname.slice(p2 + 1); } - this._animating = true; - - // Interpolate // + if (brname === search) { + res = { branch: lst.arr[n], rest: '' }; + break; + } - const animate = () => { - if (this._animating === undefined) return; + if (search.indexOf(brname)) + continue; - if (this._animating) - requestAnimationFrame(animate); - else { - if (!this._geom_viewer) - this.startDrawGeometry(); - } - const smoothFactor = -Math.cos((2.0*Math.PI*step)/frames) + 1.0; - this._camera.position.add(posIncrement.clone().multiplyScalar(smoothFactor)); - oldTarget.add(targetIncrement.clone().multiplyScalar(smoothFactor)); - this._lookat = oldTarget; - this._camera.lookAt(this._lookat); - this._camera.updateProjectionMatrix(); + // this is a case when branch name is in the begin of the search string - const tm1 = new Date().getTime(); - if (autoClip) { - for (let axis = 0; axis < 3; ++axis) - this.ctrl.clip[axis].value += this.ctrl.clip[axis].inc * smoothFactor; - this.updateClipping(); - } else - this.render3D(0); + // check where point is + let pnt = brname.length; + if (brname[pnt - 1] === '.') + pnt--; + if (search[pnt] !== '.') + continue; - const tm2 = new Date().getTime(); - if ((step === 0) && (tm2-tm1 > 200)) frames = 20; - step++; - this._animating = step < frames; - }; + res = findBranchComplex(tree, search, lst.arr[n].fBranches) || + findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches) || + { branch: lst.arr[n], rest: search.slice(pnt) }; - animate(); + break; + } - // this._controls.update(); + if (top_search && !only_search && !res && (search.indexOf('br_') === 0)) { + let p = 3; + while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) + ++p; + const br = (p > 3) ? getTreeBranch(tree, parseInt(search.slice(3, p))) : null; + if (br) + res = { branch: br, rest: search.slice(p) }; } - /** @summary actiavte auto rotate */ - autorotate(speed) { - const rotSpeed = (speed === undefined) ? 2.0 : speed; - let last = new Date(); + if (!top_search || !res) + return res; - const animate = () => { - if (!this._renderer || !this.ctrl) return; + if (name.length > search.length) + res.rest += name.slice(search.length); - const current = new Date(); + return res; +} - if (this.ctrl.rotate) - requestAnimationFrame(animate); - if (this._controls) { - this._controls.autoRotate = this.ctrl.rotate; - this._controls.autoRotateSpeed = rotSpeed * (current.getTime() - last.getTime()) / 16.6666; - this._controls.update(); - } - last = new Date(); - this.render3D(0); - }; +/** @summary Search branch with specified name + * @param {string} name - name of the branch + * @return {Object} found branch + * @private */ +function findBranch(tree, name) { + const res = findBranchComplex(tree, name, tree.fBranches, true); + return (!res || res.rest) ? null : res.branch; +} - if (this._webgl) animate(); - } - /** @summary called at the end of scene drawing */ - completeScene() { +/** summary Returns number of branches in the TTree + * desc Checks also sub-branches in the branches + * return {number} number of branches + * private +function getNumBranches(tree) { + function count(obj) { + if (!obj?.fBranches) + return 0; + let nchld = 0; + obj.fBranches.arr.forEach(sub => { nchld += count(sub); }); + return obj.fBranches.arr.length + nchld; } - /** @summary Drawing with 'count' option - * @desc Scans hieararchy and check for unique nodes - * @return {Promise} with object drawing ready */ - async drawCount(unqievis, clonetm) { - const makeTime = tm => (this.isBatchMode() ? 'anytime' : tm.toString()) + ' ms', + return count(tree); +} +*/ - res = ['Unique nodes: ' + this._clones.nodes.length, - 'Unique visible: ' + unqievis, - 'Time to clone: ' + makeTime(clonetm)]; +/** + * @summary object with single variable in TTree::Draw expression + * + * @private + */ - // need to fill cached value line numvischld - this._clones.scanVisible(); +class TDrawVariable { + + /** @summary constructor */ + constructor(globals) { + this.globals = globals; + + this.code = ''; + this.brindex = []; // index of used branches from selector + this.branches = []; // names of branches in target object + this.brarray = []; // array specifier for each branch + this.func = null; // generic function for variable calculation - let nshapes = 0; - const arg = { - clones: this._clones, - cnt: [], - func(node) { - if (this.cnt[this.last] === undefined) - this.cnt[this.last] = 1; - else - this.cnt[this.last]++; + this.kind = undefined; + this.buf = []; // buffer accumulates temporary values + } - nshapes += countNumShapes(this.clones.getNodeShape(node.id)); + /** @summary Parse variable + * @desc when only_branch specified, its placed in the front of the expression */ + parse(tree, selector, code, only_branch, branch_mode) { + const is_start_symbol = symb => { + if ((symb >= 'A') && (symb <= 'Z')) return true; - } + if ((symb >= 'a') && (symb <= 'z')) + return true; + return (symb === '_'); + }, is_next_symbol = symb => { + if (is_start_symbol(symb)) + return true; + if ((symb >= '0') && (symb <= '9')) + return true; + return false; }; - let tm1 = new Date().getTime(), - numvis = this._clones.scanVisible(arg), - tm2 = new Date().getTime(); - - res.push(`Total visible nodes: ${numvis}`, `Total shapes: ${nshapes}`); - - for (let lvl = 0; lvl < arg.cnt.length; ++lvl) { - if (arg.cnt[lvl] !== undefined) - res.push(` lvl${lvl}: ${arg.cnt[lvl]}`); - } + if (!code) + code = ''; // should be empty string at least - res.push(`Time to scan: ${makeTime(tm2-tm1)}`, '', 'Check timing for matrix calculations ...'); + this.code = (only_branch?.fName ?? '') + code; - const elem = this.selectDom().style('overflow', 'auto'); + let pos = 0, pos2 = 0, br; + while ((pos < code.length) || only_branch) { + let arriter = []; - if (this.isBatchMode()) - elem.property('_json_object_', res); - else - res.forEach(str => elem.append('p').text(str)); + if (only_branch) { + br = only_branch; + only_branch = undefined; + } else { + // first try to find branch + pos2 = pos; + while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) + pos2++; + if (code[pos2] === '$') { + let repl = ''; + switch (code.slice(pos, pos2)) { + case 'LocalEntry': + case 'Entry': repl = 'arg.$globals.entry'; break; + case 'Entries': repl = 'arg.$globals.entries'; break; + } + if (repl) { + code = code.slice(0, pos) + repl + code.slice(pos2 + 1); + pos += repl.length; + continue; + } + } - return postponePromise(() => { - arg.domatrix = true; - tm1 = new Date().getTime(); - numvis = this._clones.scanVisible(arg); - tm2 = new Date().getTime(); + br = selector.findBranch(tree, code.slice(pos, pos2)); + if (!br) { + pos = pos2 + 1; + continue; + } - const last_str = `Time to scan with matrix: ${makeTime(tm2-tm1)}`; - if (this.isBatchMode()) - res.push(last_str); - else - elem.append('p').text(last_str); - return this; - }, 100); - } + // when full id includes branch name, replace only part of extracted expression + if (br.branch && (br.rest !== undefined)) { + pos2 -= br.rest.length; + branch_mode = undefined; // maybe selection of the sub-object done + br = br.branch; + } - /** @summary Handle drop operation - * @desc opt parameter can include function name like opt$func_name - * Such function should be possible to find via {@link findFunction} - * Function has to return Promise with objects to draw on geometry - * By default function with name 'extract_geo_tracks' is checked - * @return {Promise} handling of drop operation */ - async performDrop(obj, itemname, hitem, opt) { - if (obj?.$kind === 'TTree') { - // drop tree means function call which must extract tracks from provided tree + // when code ends with the point - means object itself will be accessed + // sometime branch name itself ends with the point + if ((pos2 >= code.length - 1) && (code.at(-1) === '.')) { + arriter.push('$self$'); + pos2 = code.length; + } + } - let funcname = 'extract_geo_tracks'; + // now extract all levels of iterators + while (pos2 < code.length) { + if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && !arriter.length) { + pos2 += 5; + branch_mode = true; + break; + } - if (opt && opt.indexOf('$') > 0) { - funcname = opt.slice(0, opt.indexOf('$')); - opt = opt.slice(opt.indexOf('$')+1); - } + if (code[pos2] === '.') { + // this is object member + const prev = ++pos2; - const func = findFunction(funcname); + if ((code[prev] === '@') && (code.slice(prev, prev + 5) === '@size')) { + arriter.push('$size$'); + pos2 += 5; + break; + } - if (!func) return Promise.reject(Error(`Function ${funcname} not found`)); + if (!is_start_symbol(code[prev])) { + arriter.push('$self$'); // last point means extraction of object itself + break; + } - return func(obj, opt).then(tracks => { - if (!tracks) return this; + while ((pos2 < code.length) && is_next_symbol(code[pos2])) + pos2++; - // FIXME: probably tracks should be remembered? - return this.drawExtras(tracks, '', false).then(() => { - this.updateClipping(true); - return this.render3D(100); - }); - }); - } + // this is looks like function call - do not need to extract member with + if (code[pos2] === '(') { + pos2 = prev - 1; + break; + } - return this.drawExtras(obj, itemname).then(is_any => { - if (!is_any) return this; + // this is selection of member, but probably we need to activate iterator for ROOT collection + // TODO: if selected member is simple data type - no need to make other checks - just break here + if (!arriter.length && selector.isArrayBranch(tree, br)) + arriter.push(undefined); + arriter.push(code.slice(prev, pos2)); + continue; + } - if (hitem) hitem._painter = this; // set for the browser item back pointer + if (code[pos2] !== '[') + break; - return this.render3D(100); - }); - } + // simple [] + if (code[pos2 + 1] === ']') { + arriter.push(undefined); + pos2 += 2; + continue; + } - /** @summary function called when mouse is going over the item in the browser */ - mouseOverHierarchy(on, itemname, hitem) { - if (!this.ctrl) return; // protection for cleaned-up painter + const prev = pos2++; + let cnt = 0; + while ((pos2 < code.length) && ((code[pos2] !== ']') || (cnt > 0))) { + if (code[pos2] === '[') + cnt++; + else if (code[pos2] === ']') + cnt--; + pos2++; + } + const sub = code.slice(prev + 1, pos2); + switch (sub) { + case '': + case '$all$': arriter.push(undefined); break; + case '$last$': arriter.push('$last$'); break; + case '$size$': arriter.push('$size$'); break; + case '$first$': arriter.push(0); break; + default: + if (Number.isInteger(parseInt(sub))) + arriter.push(parseInt(sub)); + else { + // try to compile code as draw variable + const subvar = new TDrawVariable(this.globals); + if (!subvar.parse(tree, selector, sub)) + return false; + arriter.push(subvar); + } + } + pos2++; + } - const obj = hitem._obj; + if (!arriter.length) + arriter = undefined; + else if ((arriter.length === 1) && (arriter[0] === undefined)) + arriter = true; - // let's highlight tracks and hits only for the time being - if (!obj || (obj._typename !== clTEveTrack && obj._typename !== clTEvePointSet && obj._typename !== clTPolyMarker3D)) return; + let indx = selector.indexOfBranch(br); + if (indx < 0) + indx = selector.addBranch(br, undefined, branch_mode); - this.highlightMesh(null, 0x00ff00, on ? obj : null); - } + branch_mode = undefined; - /** @summary clear extra drawn objects like tracks or hits */ - clearExtras() { - this.getExtrasContainer('delete'); - delete this._extraObjects; // workaround, later will be normal function - this.render3D(); - } + this.brindex.push(indx); + this.branches.push(selector.nameOfBranch(indx)); + this.brarray.push(arriter); - /** @summary Register extra objects like tracks or hits - * @desc Rendered after main geometry volumes are created - * Check if object already exists to prevent duplication */ - addExtra(obj, itemname) { - if (this._extraObjects === undefined) - this._extraObjects = create$1(clTList); + // this is simple case of direct usage of the branch + if ((pos === 0) && (pos2 === code.length) && (this.branches.length === 1)) { + this.direct_branch = true; // remember that branch read as is + return true; + } - if (this._extraObjects.arr.indexOf(obj) >= 0) - return false; + const replace = `arg.var${this.branches.length - 1}`; + code = code.slice(0, pos) + replace + code.slice(pos2); + pos += replace.length; + } - this._extraObjects.Add(obj, itemname); + // support usage of some standard TMath functions + code = code.replace(/TMath::Exp\(/g, 'Math.exp(') + .replace(/TMath::Abs\(/g, 'Math.abs(') + .replace(/TMath::Prob\(/g, 'arg.$math.Prob(') + .replace(/TMath::Gaus\(/g, 'arg.$math.Gaus('); - delete obj.$hidden_via_menu; // remove previous hidden property + this.func = new Function('arg', `return (${code})`); return true; } - /** @summary manipulate visisbility of extra objects, used for HierarchyPainter - * @private */ - extraObjectVisible(hpainter, hitem, toggle) { - if (!this._extraObjects) return; + /** @summary Check if it is dummy variable */ + is_dummy() { return !this.branches.length && !this.func; } - const itemname = hpainter.itemFullName(hitem); - let indx = this._extraObjects.opt.indexOf(itemname); + /** @summary Produce variable + * @desc after reading tree branches into the object, calculate variable value */ + produce(obj) { + this.length = 1; + this.isarray = false; - if ((indx < 0) && hitem._obj) { - indx = this._extraObjects.arr.indexOf(hitem._obj); - // workaround - if object found, replace its name - if (indx >= 0) this._extraObjects.opt[indx] = itemname; + if (this.is_dummy()) { + this.value = 1; // used as dummy weight variable + this.kind = 'number'; + return; } - if (indx < 0) return; - - const obj = this._extraObjects.arr[indx]; - let res = !!obj.$hidden_via_menu; + const arg = { $globals: this.globals, $math: jsroot_math }, arrs = []; + let usearrlen = -1; + for (let n = 0; n < this.branches.length; ++n) { + const name = `var${n}`; + arg[name] = obj[this.branches[n]]; - if (toggle) { - obj.$hidden_via_menu = res; - res = !res; + // try to check if branch is array and need to be iterated + if (this.brarray[n] === undefined) + this.brarray[n] = (checkArrayPrototype(arg[name]) > 0) || isRootCollection(arg[name]); - let mesh = null; - // either found painted object or just draw once again - this._toplevel.traverse(node => { if (node.geo_object === obj) mesh = node; }); + // no array - no pain + if (this.brarray[n] === false) + continue; - if (mesh) { - mesh.visible = res; - this.render3D(); - } else if (res) { - this.drawExtras(obj, '', false).then(() => { - this.updateClipping(true); - this.render3D(); - }); + // check if array can be used as is - one dimension and normal values + if ((this.brarray[n] === true) && (checkArrayPrototype(arg[name], true) === 2)) { + // plain array, can be used as is + arrs[n] = arg[name]; + } else { + const iter = new ArrayIterator(arg[name], this.brarray[n], obj); + arrs[n] = []; + while (iter.next()) + arrs[n].push(iter.value); } + if ((usearrlen < 0) || (usearrlen < arrs[n].length)) + usearrlen = arrs[n].length; } - return res; - } - - /** @summary Draw extra object like tracks - * @return {Promise} for ready */ - async drawExtras(obj, itemname, add_objects, not_wait_render) { - // if object was hidden via menu, do not redraw it with next draw call - if (!obj?._typename || (!add_objects && obj.$hidden_via_menu)) - return false; + if (usearrlen < 0) { + this.value = this.direct_branch ? arg.var0 : this.func(arg); + if (!this.kind) + this.kind = typeof this.value; + return; + } - let do_render = false; - if (add_objects === undefined) { - add_objects = true; - do_render = true; - } else if (not_wait_render) - do_render = true; + if (usearrlen === 0) { + // empty array - no any histogram should be filled + this.length = 0; + this.value = 0; + return; + } + this.length = usearrlen; + this.isarray = true; - let promise = false; + if (this.direct_branch) + this.value = arrs[0]; // just use array + else { + this.value = new Array(usearrlen); - if ((obj._typename === clTList) || (obj._typename === clTObjArray)) { - if (!obj.arr) return false; - const parr = []; - for (let n = 0; n < obj.arr.length; ++n) { - const sobj = obj.arr[n]; - let sname = obj.opt ? obj.opt[n] : ''; - if (!sname) sname = (itemname || '') + `/[${n}]`; - parr.push(this.drawExtras(sobj, sname, add_objects)); + for (let k = 0; k < usearrlen; ++k) { + for (let n = 0; n < this.branches.length; ++n) { + if (arrs[n]) + arg[`var${n}`] = arrs[n][k]; + } + this.value[k] = this.func(arg); } - promise = Promise.all(parr).then(ress => ress.indexOf(true) >= 0); - } else if (obj._typename === 'Mesh') { - // adding mesh as is - this.addToExtrasContainer(obj); - promise = Promise.resolve(true); - } else if (obj._typename === 'TGeoTrack') { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawGeoTrack(obj, itemname); - } else if (obj._typename === clTPolyLine3D) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawPolyLine(obj, itemname); - } else if ((obj._typename === clTEveTrack) || (obj._typename === `${nsREX}REveTrack`)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawEveTrack(obj, itemname); - } else if ((obj._typename === clTEvePointSet) || (obj._typename === `${nsREX}REvePointSet`) || (obj._typename === clTPolyMarker3D)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawHit(obj, itemname); - } else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawExtraShape(obj, itemname); } - return getPromise(promise).then(is_any => { - if (!is_any || !do_render) - return is_any; - - this.updateClipping(true); - - const pr = this.render3D(100, not_wait_render ? 'nopromise' : false); - - return not_wait_render ? this : pr; - }); + if (!this.kind) + this.kind = typeof this.value[0]; } - /** @summary returns container for extra objects */ - getExtrasContainer(action, name) { - if (!this._toplevel) return null; + /** @summary Get variable */ + get(indx) { return this.isarray ? this.value[indx] : this.value; } - if (!name) name = 'tracks'; + /** @summary Append array to the buffer */ + appendArray(tgtarr) { this.buf = this.buf.concat(tgtarr[this.branches[0]]); } - let extras = null; - const lst = []; - for (let n = 0; n < this._toplevel.children.length; ++n) { - const chld = this._toplevel.children[n]; - if (!chld._extras) continue; - if (action === 'collect') { lst.push(chld); continue; } - if (chld._extras === name) { extras = chld; break; } - } +} // class TDrawVariable - if (action === 'collect') { - for (let k = 0; k < lst.length; ++k) - this._toplevel.remove(lst[k]); - return lst; - } - if (action === 'delete') { - if (extras) this._toplevel.remove(extras); - disposeThreejsObject(extras); - return null; - } +/** + * @summary Selector class for TTree::Draw function + * + * @private + */ - if ((action !== 'get') && !extras) { - extras = new Object3D(); - extras._extras = name; - this._toplevel.add(extras); - } +class TDrawSelector extends TSelector { - return extras; - } + /** @summary constructor */ + constructor() { + super(); - /** @summary add object to extras container. - * @desc If fail, dispose object */ - addToExtrasContainer(obj, name) { - const container = this.getExtrasContainer('', name); - if (container) - container.add(obj); - else { - console.warn('Fail to add object to extras'); - disposeThreejsObject(obj); - } + this.ndim = 0; + this.vars = []; // array of expression variables + this.cut = null; // cut variable + this.hist = null; + this.drawopt = ''; + this.hist_name = '$htemp'; + this.draw_title = 'Result of TTree::Draw'; + this.graph = false; + this.hist_args = []; // arguments for histogram creation + this.arr_limit = 1000; // number of accumulated items before create histogram + this.htype = 'F'; + this.monitoring = 0; + this.globals = {}; // object with global parameters, which could be used in any draw expression + this.last_progress = 0; + this.aver_diff = 0; } - /** @summary drawing TGeoTrack */ - drawGeoTrack(track, itemname) { - if (!track?.fNpoints) return false; - - const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), // line width not supported on windows - color = getColor(track.fLineColor) || '#ff00ff', - npoints = Math.round(track.fNpoints/4), // each track point has [x,y,z,t] coordinate - buf = new Float32Array((npoints-1)*6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); + /** @summary Return number of entries in the tree */ + getNumEntries(tree) { return tree?.fEntries || 0; } - for (let k = 0, pos = 0; k < npoints-1; ++k, pos+=6) { - buf[pos] = projx ? projv : track.fPoints[k*4]; - buf[pos+1] = projy ? projv : track.fPoints[k*4+1]; - buf[pos+2] = projz ? projv : track.fPoints[k*4+2]; - buf[pos+3] = projx ? projv : track.fPoints[k*4+4]; - buf[pos+4] = projy ? projv : track.fPoints[k*4+5]; - buf[pos+5] = projz ? projv : track.fPoints[k*4+6]; - } + /** @summary Find branch in the tree */ + findBranch(tree, name) { return findBranchComplex(tree, name); } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), - line = createLineSegments(buf, lineMaterial); + /** @summary Returns true if one can use branch as array */ + isArrayBranch(tree, br) { + if ((br.fType === kClonesNode) || (br.fType === kSTLNode)) + return true; + const objclass = getBranchObjectClass(br, tree, false, true); + if (objclass && isRootCollection(null, objclass)) + return true; + } - line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front - line.geo_name = itemname; - line.geo_object = track; - line.hightlightWidthScale = 2; + /** @summary Set draw selector callbacks */ + setCallback(result_callback, progress_callback) { + this.result_callback = result_callback; + this.progress_callback = progress_callback; + } - if (itemname?.indexOf('/Tracks') === 0) - line.main_track = true; + /** @summary Parse parameters */ + parseParameters(tree, args, expr) { + if (!expr || !isStr(expr)) + return ''; - this.addToExtrasContainer(line); + // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value + let pos = expr.lastIndexOf(';'); + while (pos >= 0) { + let parname = expr.slice(pos + 1), parvalue; + expr = expr.slice(0, pos); + pos = expr.lastIndexOf(';'); - return true; - } + const separ = parname.indexOf(':'); + if (separ > 0) { + parvalue = parname.slice(separ + 1); + parname = parname.slice(0, separ); + } - /** @summary drawing TPolyLine3D */ - drawPolyLine(line, itemname) { - if (!line) return false; + let intvalue = parseInt(parvalue); + if (!parvalue || !Number.isInteger(intvalue)) + intvalue = undefined; - const linewidth = browser.isWin ? 1 : (line.fLineWidth || 1), - color = getColor(line.fLineColor) || '#ff00ff', - npoints = line.fN, - fP = line.fP, - buf = new Float32Array((npoints-1)*6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); + switch (parname) { + case 'elist': + if ((parvalue.at(0) === '[') && (parvalue.at(-1) === ']')) { + parvalue = parvalue.slice(1, parvalue.length - 1).replaceAll(/\s/g, ''); + args.elist = []; + let p = 0, last_v = -1; + const getInt = () => { + const p0 = p; + while ((p < parvalue.length) && (parvalue.charCodeAt(p) >= 48) && (parvalue.charCodeAt(p) < 58)) + p++; + return parseInt(parvalue.slice(p0, p)); + }; - for (let k = 0, pos = 0; k < npoints-1; ++k, pos += 6) { - buf[pos] = projx ? projv : fP[k*3]; - buf[pos+1] = projy ? projv : fP[k*3+1]; - buf[pos+2] = projz ? projv : fP[k*3+2]; - buf[pos+3] = projx ? projv : fP[k*3+3]; - buf[pos+4] = projy ? projv : fP[k*3+4]; - buf[pos+5] = projz ? projv : fP[k*3+5]; + while (p < parvalue.length) { + const v1 = getInt(); + if (v1 <= last_v) { + console.log('position', p); + throw Error(`Wrong entry id ${v1} in elist last ${last_v}`); + } + let v2 = v1; + if (parvalue[p] === '.' && parvalue[p + 1] === '.') { + p += 2; + v2 = getInt(); + if (v2 < v1) + throw Error(`Wrong entry id ${v2} in range ${v1}`); + } + if (parvalue[p] === ',' || p === parvalue.length) { + for (let v = v1; v <= v2; ++v) { + args.elist.push(v); + last_v = v; + } + p++; + } else + throw Error('Wrong syntax for elist'); + } + } + break; + case 'entries': + case 'num': + case 'numentries': + if (parvalue === 'all') + args.numentries = this.getNumEntries(tree); + else if (parvalue === 'half') + args.numentries = Math.round(this.getNumEntries(tree) / 2); + else if (intvalue !== undefined) + args.numentries = intvalue; + break; + case 'first': + if (intvalue !== undefined) + args.firstentry = intvalue; + break; + case 'nmatch': + if (intvalue !== undefined) + this.nmatch = intvalue; + break; + case 'mon': + case 'monitor': + args.monitoring = intvalue ?? 5000; + break; + case 'player': + args.player = true; + break; + case 'dump': + args.dump = true; + break; + case 'staged': + args.staged = true; + break; + case 'maxseg': + case 'maxrange': + if (intvalue) + tree.$file.fMaxRanges = intvalue; + break; + case 'accum': + if (intvalue) + this.arr_limit = intvalue; + break; + case 'htype': + if (parvalue && (parvalue.length === 1)) { + this.htype = parvalue.toUpperCase(); + if (['C', 'S', 'I', 'F', 'L', 'D'].indexOf(this.htype) < 0) + this.htype = 'F'; + } + break; + case 'hbins': + this.hist_nbins = parseInt(parvalue); + if (!Number.isInteger(this.hist_nbins) || (this.hist_nbins <= 3)) + delete this.hist_nbins; + else + this.want_hist = true; + break; + case 'drawopt': + args.drawopt = parvalue; + break; + case 'graph': + args.graph = intvalue || true; + break; + } } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), - line3d = createLineSegments(buf, lineMaterial); - - line3d.defaultOrder = line3d.renderOrder = 1000000; // to bring line to the front - line3d.geo_name = itemname; - line3d.geo_object = line; - line3d.hightlightWidthScale = 2; + pos = expr.lastIndexOf('>>'); + if (pos >= 0) { + let harg = expr.slice(pos + 2).trim(); + expr = expr.slice(0, pos).trim(); + pos = harg.indexOf('('); + if (pos > 0) { + this.hist_name = harg.slice(0, pos); + harg = harg.slice(pos); + } + if (harg === 'dump') + args.dump = true; + else if (harg === 'elist') + args.dump_entries = true; + else if (harg.indexOf('Graph') === 0) + args.graph = true; + else if (pos < 0) { + this.want_hist = true; + this.hist_name = harg; + } else if ((harg[0] === '(') && (harg.at(-1) === ')')) { + this.want_hist = true; + harg = harg.slice(1, harg.length - 1).split(','); + let isok = true; + for (let n = 0; n < harg.length; ++n) { + harg[n] = (n % 3 === 0) ? parseInt(harg[n]) : parseFloat(harg[n]); + if (!Number.isFinite(harg[n])) + isok = false; + } + if (isok) + this.hist_args = harg; + } + } - this.addToExtrasContainer(line3d); + if (args.dump) { + this.dump_values = true; + args.reallocate_objects = true; + if ((args.numentries === undefined) && !args.elist) { + args.numentries = 10; + args._dflt_entries = true; + } + } - return true; + return expr; } - /** @summary Drawing TEveTrack */ - drawEveTrack(track, itemname) { - if (!track || (track.fN <= 0)) return false; - - const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), - color = getColor(track.fLineColor) || '#ff00ff', - buf = new Float32Array((track.fN-1)*6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); + /** @summary Create draw expression for N-dim with cut */ + createDrawExpression(tree, names, cut, args) { + if (args.dump && names.length === 1 && names[0] === 'Entry$') { + args.dump_entries = true; + args.dump = false; + } - for (let k = 0, pos = 0; k < track.fN-1; ++k, pos+=6) { - buf[pos] = projx ? projv : track.fP[k*3]; - buf[pos+1] = projy ? projv : track.fP[k*3+1]; - buf[pos+2] = projz ? projv : track.fP[k*3+2]; - buf[pos+3] = projx ? projv : track.fP[k*3+3]; - buf[pos+4] = projy ? projv : track.fP[k*3+4]; - buf[pos+5] = projz ? projv : track.fP[k*3+5]; + if (args.dump_entries) { + this.dump_entries = true; + this.hist = []; + if (args._dflt_entries) { + delete args._dflt_entries; + delete args.numentries; + } } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), - line = createLineSegments(buf, lineMaterial); + let is_direct = !cut && !this.dump_entries; - line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front - line.geo_name = itemname; - line.geo_object = track; - line.hightlightWidthScale = 2; + this.ndim = names.length; - this.addToExtrasContainer(line); + for (let n = 0; n < this.ndim; ++n) { + this.vars[n] = new TDrawVariable(this.globals); + if (!this.vars[n].parse(tree, this, names[n])) + return false; + if (!this.vars[n].direct_branch) + is_direct = false; + } - return true; - } + this.cut = new TDrawVariable(this.globals); + if (cut && !this.cut.parse(tree, this, cut)) + return false; - /** @summary Drawing different hits types like TPolyMarker3D */ - async drawHit(hit, itemname) { - if (!hit || !hit.fN || (hit.fN < 0)) + if (!this.numBranches()) { + console.warn('no any branch is selected'); return false; + } - // make hit size scaling factor of overall geometry size - // otherwise it is not possible to correctly see hits at all - const nhits = hit.fN, - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'), - hit_scale = Math.max(hit.fMarkerSize * this.getOverallSize() * (this._dummy ? 0.015 : 0.005), 0.2), - pnts = new PointsCreator(nhits, this._webgl, hit_scale); + if (is_direct) + this.ProcessArrays = this.ProcessArraysFunc; - for (let i = 0; i < nhits; i++) { - pnts.addPoint(projx ? projv : hit.fP[i*3], - projy ? projv : hit.fP[i*3+1], - projz ? projv : hit.fP[i*3+2]); - } + this.monitoring = args.monitoring; - return pnts.createPoints({ color: getColor(hit.fMarkerColor) || '#0000ff', style: hit.fMarkerStyle }).then(mesh => { - mesh.defaultOrder = mesh.renderOrder = 1000000; // to bring points to the front - mesh.highlightScale = 2; - mesh.geo_name = itemname; - mesh.geo_object = hit; - this.addToExtrasContainer(mesh); - return true; // indicate that rendering should be done - }); - } + // force TPolyMarker3D drawing for 3D case + if ((this.ndim === 3) && !this.want_hist && !args.dump) + args.graph = true; - /** @summary Draw extra shape on the geometry */ - drawExtraShape(obj, itemname) { - const mesh = build(obj); - if (!mesh) return false; + this.graph = args.graph; - mesh.geo_name = itemname; - mesh.geo_object = obj; + if (args.drawopt !== undefined) + this.drawopt = args.drawopt; + else + args.drawopt = this.drawopt = this.graph ? 'P' : ''; - this.addToExtrasContainer(mesh); return true; } - /** @summary Serach for specified node - * @private */ - findNodeWithVolume(name, action, prnt, itemname, volumes) { - let first_level = false, res = null; - - if (!prnt) { - prnt = this.getGeometry(); - if (!prnt && (getNodeKind(prnt) !== 0)) return null; - itemname = this.geo_manager ? prnt.fName : ''; - first_level = true; - volumes = []; - } else { - if (itemname) itemname += '/'; - itemname += prnt.fName; - } + /** @summary Parse draw expression */ + parseDrawExpression(tree, args) { + // parse complete expression + let expr = this.parseParameters(tree, args, args.expr), cut = ''; - if (!prnt.fVolume || prnt.fVolume._searched) return null; + // parse option for histogram creation + this.draw_title = `drawing '${expr}'`; + if (tree?.fName) + this.draw_title += ` from ${tree.fName}`; - if (name.test(prnt.fVolume.fName)) { - res = action({ node: prnt, item: itemname }); - if (res) return res; + let pos; + if (args.cut) + cut = args.cut; + else { + pos = expr.replace(/TMath::/g, 'TMath__').lastIndexOf('::'); // avoid confusion due-to :: in the namespace + if (pos >= 0) { + cut = expr.slice(pos + 2).trim(); + expr = expr.slice(0, pos).trim(); + } } - prnt.fVolume._searched = true; - volumes.push(prnt.fVolume); + args.parse_expr = expr; + args.parse_cut = cut; - if (prnt.fVolume.fNodes) { - for (let n = 0, len = prnt.fVolume.fNodes.arr.length; n < len; ++n) { - res = this.findNodeWithVolume(name, action, prnt.fVolume.fNodes.arr[n], itemname, volumes); - if (res) break; + // let names = expr.split(':'); // to allow usage of ? operator, we need to handle : as well + let names = [], nbr1 = 0, nbr2 = 0, prev = 0; + for (pos = 0; pos < expr.length; ++pos) { + switch (expr[pos]) { + case '(': + nbr1++; + break; + case ')': + nbr1--; + break; + case '[': + nbr2++; + break; + case ']': + nbr2--; + break; + case ':': + if (expr[pos + 1] === ':') { + pos++; + continue; + } + if (!nbr1 && !nbr2 && (pos > prev)) + names.push(expr.slice(prev, pos)); + prev = pos + 1; + break; } } + if (!nbr1 && !nbr2 && (pos > prev)) + names.push(expr.slice(prev, pos)); - if (first_level) { - for (let n = 0, len = volumes.length; n < len; ++n) - delete volumes[n]._searched; - } + if (args.staged) { + args.staged_names = names; + names = ['Entry$']; + args.dump_entries = true; + } else if (cut && args.dump_entries) + names = ['Entry$']; + else if ((names.length < 1) || (names.length > 3)) + return false; - return res; + return this.createDrawExpression(tree, names, cut, args); } - /** @summary Process script option - load and execute some gGeoManager-related calls */ - async loadMacro(script_name) { - const result = { obj: this.getGeometry(), prefix: '' }; + /** @summary Draw only specified branch */ + drawOnlyBranch(tree, branch, expr, args) { + this.ndim = 1; - if (this.geo_manager) - result.prefix = result.obj.fName; + if (expr.indexOf('dump') === 0) + expr = ';' + expr; - if (!script_name || (script_name.length < 3) || (getNodeKind(result.obj) !== 0)) - return result; + expr = this.parseParameters(tree, args, expr); - const mgr = { - GetVolume: name => { - const regexp = new RegExp('^'+name+'$'), - currnode = this.findNodeWithVolume(regexp, arg => arg); - - if (!currnode) console.log(`Did not found ${name} volume`); - - // return proxy object with several methods, typically used in ROOT geom scripts - return { - found: currnode, - fVolume: currnode?.node?.fVolume, - InvisibleAll(flag) { - setInvisibleAll(this.fVolume, flag); - }, - Draw() { - if (!this.found || !this.fVolume) return; - result.obj = this.found.node; - result.prefix = this.found.item; - console.log(`Select volume for drawing ${this.fVolume.fName} ${result.prefix}`); - }, - SetTransparency(lvl) { - if (this.fVolume?.fMedium?.fMaterial) - this.fVolume.fMedium.fMaterial.fFillStyle = 3000 + lvl; - }, - SetLineColor(col) { - if (this.fVolume) this.fVolume.fLineColor = col; - } - }; - }, + this.monitoring = args.monitoring; - DefaultColors: () => { - this.ctrl.dflt_colors = true; - }, + if (args.dump) { + this.dump_values = true; + args.reallocate_objects = true; + } - SetMaxVisNodes: limit => { - if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = parseInt(limit) || 0; - }, + if (this.dump_values) { + this.hist = []; // array of dump objects + this.leaf = args.leaf; - SetVisLevel: limit => { - if (!this.ctrl.vislevel) - this.ctrl.vislevel = parseInt(limit) || 0; - } - }; + // branch object remains, therefore we need to copy fields to see them all + this.copy_fields = ((args.branch.fLeaves?.arr.length > 1) || args.branch.fBranches?.arr.length) && !args.leaf; - showProgress(`Loading macro ${script_name}`); + this.addBranch(branch, 'br0', args.direct_branch); // add branch - return httpRequest(script_name, 'text').then(script => { - const lines = script.split('\n'); - let indx = 0; + this.Process = this.ProcessDump; - while (indx < lines.length) { - let line = lines[indx++].trim(); + return true; + } - if (line.indexOf('//') === 0) continue; + this.vars[0] = new TDrawVariable(this.globals); + if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch)) + return false; + this.draw_title = `drawing branch ${branch.fName} ${expr ? ' expr:' + expr : ''} from ${tree.fName ?? ''}`; - if (line.indexOf('gGeoManager') < 0) continue; - line = line.replace('->GetVolume', '.GetVolume'); - line = line.replace('->InvisibleAll', '.InvisibleAll'); - line = line.replace('->SetMaxVisNodes', '.SetMaxVisNodes'); - line = line.replace('->DefaultColors', '.DefaultColors'); - line = line.replace('->Draw', '.Draw'); - line = line.replace('->SetTransparency', '.SetTransparency'); - line = line.replace('->SetLineColor', '.SetLineColor'); - line = line.replace('->SetVisLevel', '.SetVisLevel'); - if (line.indexOf('->') >= 0) continue; + this.cut = new TDrawVariable(this.globals); - try { - const func = new Function('gGeoManager', line); - func(mgr); - } catch (err) { - console.error(`Problem by processing ${line}`); - } - } + if (this.vars[0].direct_branch) + this.ProcessArrays = this.ProcessArraysFunc; - return result; - }).catch(() => { - console.error(`Fail to load ${script_name}`); - return result; - }); + return true; } - /** @summary Assign clones, created outside. - * @desc Used by geometry painter, where clones are handled by the server */ - assignClones(clones) { - this._clones_owner = true; - this._clones = clones; + /** @summary Begin processing */ + Begin(tree) { + this.globals.entries = this.getNumEntries(tree); + + if (this.monitoring) + this.lasttm = new Date().getTime(); } - /** @summary Extract shapes from draw message of geometry painter - * @desc For the moment used in batch production */ - extractRawShapes(draw_msg, recreate) { - let nodes = null, old_gradpersegm = 0; + /** @summary Show progress */ + ShowProgress(/* value */) {} - // array for descriptors for each node - // if array too large (>1M), use JS object while only ~1K nodes are expected to be used - if (recreate) { - // if (draw_msg.kind !== 'draw') return false; - nodes = (draw_msg.numnodes > 1e6) ? { length: draw_msg.numnodes } : new Array(draw_msg.numnodes); // array for all nodes + /** @summary Get bins for bits histogram */ + getBitsBins(nbits, res) { + res.nbins = res.max = nbits; + res.fLabels = create$1(clTHashList); + for (let k = 0; k < nbits; ++k) { + const s = create$1(clTObjString); + s.fString = k.toString(); + s.fUniqueID = k + 1; + res.fLabels.Add(s); } + return res; + } - draw_msg.nodes.forEach(node => { - node = ClonedNodes.formatServerElement(node); - if (nodes) - nodes[node.id] = node; - else - this._clones.updateNode(node); - }); - - if (recreate) { - this._clones_owner = true; - this._clones = new ClonedNodes(null, nodes); - this._clones.name_prefix = this._clones.getNodeName(0); - this._clones.setConfig(this.ctrl); + /** @summary Get min.max bins */ + getMinMaxBins(axisid, nbins) { + const res = { min: 0, max: 0, nbins, k: 1, fLabels: null, title: '' }; + if (axisid >= this.ndim) + return res; - // normally only need when making selection, not used in geo viewer - // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo_clones.setVisLevel(draw_msg.vislevel); - // TODO: provide from server - this._clones.maxdepth = 20; - } + const arr = this.vars[axisid].buf; + res.title = this.vars[axisid].code || ''; - let nsegm = 0; - if (draw_msg.cfg) - nsegm = draw_msg.cfg.nsegm; + if (this.vars[axisid].kind === 'object') { + // this is any object type + let typename, similar = true, maxbits = 8; + for (let k = 0; k < arr.length; ++k) { + if (!arr[k]) + continue; + if (!typename) + typename = arr[k]._typename; + if (typename !== arr[k]._typename) + similar = false; // check all object types + if (arr[k].fNbits) + maxbits = Math.max(maxbits, arr[k].fNbits + 1); + } - if (nsegm) { - old_gradpersegm = geoCfg('GradPerSegm'); - geoCfg('GradPerSegm', 360 / Math.max(nsegm, 6)); - } + if (typename && similar) { + if ((typename === 'TBits') && (axisid === 0)) { + this.fill1DHistogram = this.fillTBitsHistogram; + if (maxbits % 8) + maxbits = (maxbits & 0xfff0) + 8; - for (let cnt = 0; cnt < draw_msg.visibles.length; ++cnt) { - const item = draw_msg.visibles[cnt], rd = item.ri; + if ((this.hist_name === 'bits') && (this.hist_args.length === 1) && this.hist_args[0]) + maxbits = this.hist_args[0]; - // entry may be provided without shape - it is ok - if (rd) - item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); + return this.getBitsBins(maxbits, res); + } + } } - if (old_gradpersegm) - geoCfg('GradPerSegm', old_gradpersegm); - - return true; - } - - /** @summary Prepare drawings - * @desc Return value used as promise for painter */ - async prepareObjectDraw(draw_obj, name_prefix) { - // if did cleanup - ignore all kind of activity - if (this.did_cleanup) - return null; - - if (name_prefix === '__geom_viewer_append__') { - this._new_append_nodes = draw_obj; - this.ctrl.use_worker = 0; - this._geom_viewer = true; // indicate that working with geom viewer - } else if ((name_prefix === '__geom_viewer_selection__') && this._clones) { - // these are selection done from geom viewer - this._new_draw_nodes = draw_obj; - this.ctrl.use_worker = 0; - this._geom_viewer = true; // indicate that working with geom viewer - } else if (this._main_painter) { - this._clones_owner = false; - this._clones = this._main_painter._clones; - console.log(`Reuse clones ${this._clones.nodes.length} from main painter`); - } else if (!draw_obj) { - this._clones_owner = false; - this._clones = null; + if (this.vars[axisid].kind === 'boolean') { + res.lbls = ['false', 'true']; + this.fill1DHistogram = this.fillBooleanHistogram; + } else if (this.vars[axisid].kind === 'string') { + res.lbls = []; // all labels + for (let k = 0; k < arr.length; ++k) { + if (res.lbls.indexOf(arr[k]) < 0) + res.lbls.push(arr[k]); + } + res.lbls.sort(); + } else if ((axisid === 0) && (this.hist_name === 'bits') && (this.hist_args.length <= 1)) { + this.fill1DHistogram = this.fillBitsHistogram; + return this.getBitsBins(this.hist_args[0] || 32, res); + } else if (axisid * 3 + 2 < this.hist_args.length) { + res.nbins = this.hist_args[axisid * 3]; + res.min = this.hist_args[axisid * 3 + 1]; + res.max = this.hist_args[axisid * 3 + 2]; } else { - this._start_drawing_time = new Date().getTime(); - this._clones_owner = true; - this._clones = new ClonedNodes(draw_obj); - let lvl = this.ctrl.vislevel, maxnodes = this.ctrl.maxnodes; - if (this.geo_manager) { - if (!lvl && this.geo_manager.fVisLevel) - lvl = this.geo_manager.fVisLevel; - if (!maxnodes) - maxnodes = this.geo_manager.fMaxVisNodes; + let is_any = false; + for (let i = 1; i < arr.length; ++i) { + const v = arr[i]; + if (!Number.isFinite(v)) + continue; + if (is_any) { + res.min = Math.min(res.min, v); + res.max = Math.max(res.max, v); + } else { + res.min = res.max = v; + is_any = true; + } + } + if (!is_any) { + res.min = 0; + res.max = 1; } - this._clones.setVisLevel(lvl); - this._clones.setMaxVisNodes(maxnodes, this.ctrl.more); - this._clones.setConfig(this.ctrl); - - this._clones.name_prefix = name_prefix; - - const hide_top_volume = !!this.geo_manager && !this.ctrl.showtop; - let uniquevis = this.ctrl.no_screen ? 0 : this._clones.markVisibles(true, false, hide_top_volume); - - if (uniquevis <= 0) - uniquevis = this._clones.markVisibles(false, false, hide_top_volume); - else - uniquevis = this._clones.markVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits - - this._clones.produceIdShifts(); - - const spent = new Date().getTime() - this._start_drawing_time; + if (this.hist_nbins) + nbins = res.nbins = this.hist_nbins; - if (!this._scene) - console.log(`Creating clones ${this._clones.nodes.length} takes ${spent} ms uniquevis ${uniquevis}`); + res.isinteger = (Math.round(res.min) === res.min) && (Math.round(res.max) === res.max); + if (res.isinteger) { + for (let k = 0; k < arr.length; ++k) { + if (arr[k] !== Math.round(arr[k])) { + res.isinteger = false; + break; + } + } + } - if (this.ctrl._count) - return this.drawCount(uniquevis, spent); + if (res.isinteger) { + res.min = Math.round(res.min); + res.max = Math.round(res.max); + if (res.max - res.min < nbins * 5) { + res.min -= 1; + res.max += 2; + res.nbins = Math.round(res.max - res.min); + } else { + const range = (res.max - res.min + 2); + let step = Math.floor(range / nbins); + while (step * nbins < range) + step++; + res.max = res.min + nbins * step; + } + } else if (res.min >= res.max) { + res.max = res.min; + if (Math.abs(res.min) < 100) { + res.min -= 1; + res.max += 1; + } else if (res.min > 0) { + res.min *= 0.9; + res.max *= 1.1; + } else { + res.min *= 1.1; + res.max *= 0.9; + } + } else + res.max += (res.max - res.min) / res.nbins; } - let promise = Promise.resolve(true); - - if (!this._scene) { - this._first_drawing = true; - - const pp = this.getPadPainter(); - - this._on_pad = !!pp; - - if (this._on_pad) { - let size, render3d, fp; - promise = ensureTCanvas(this, '3d').then(() => { - if (pp.fillatt?.color) - this.ctrl.background = pp.fillatt.color; - fp = this.getFramePainter(); - - this.batch_mode = pp.isBatchMode(); - - render3d = getRender3DKind(undefined, this.batch_mode); - assign3DHandler(fp); - fp.mode3d = true; - - size = fp.getSizeFor3d(undefined, render3d); - - this._fit_main_area = (size.can3d === -1); - - return this.createScene(size.width, size.height, render3d) - .then(dom => fp.add3dCanvas(size, dom, render3d === constants$1.Render3D.WebGL)); - }); - } else { - const dom = this.selectDom('origin'); + if (res.lbls) { + res.max = res.nbins = res.lbls.length; - this.batch_mode = isBatchMode() || (!dom.empty() && dom.property('_batch_mode')); - this.batch_format = dom.property('_batch_format'); + res.fLabels = create$1(clTHashList); + for (let k = 0; k < res.lbls.length; ++k) { + const s = create$1(clTObjString); + s.fString = res.lbls[k]; + s.fUniqueID = k + 1; + if (s.fString === '') + s.fString = ''; + res.fLabels.Add(s); + } + } - const render3d = getRender3DKind(this.options.Render3D, this.batch_mode); + res.k = res.nbins / (res.max - res.min); - // activate worker - if ((this.ctrl.use_worker > 0) && !this.batch_mode) - this.startWorker(); + res.GetBin = function(value) { + const bin = this.lbls?.indexOf(value) ?? (Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1); + return bin < 0 ? 0 : ((bin > this.nbins) ? this.nbins + 1 : bin + 1); + }; - assign3DHandler(this); + return res; + } - const size = this.getSizeFor3d(undefined, render3d); + /** @summary Create histogram which matches value in dimensions */ + createHistogram(nbins, set_hist = false) { + if (!nbins) + nbins = 20; - this._fit_main_area = (size.can3d === -1); + const x = this.getMinMaxBins(0, nbins), + y = this.getMinMaxBins(1, nbins), + z = this.getMinMaxBins(2, nbins); + let hist = null; - promise = this.createScene(size.width, size.height, render3d) - .then(dom => this.add3dCanvas(size, dom, this._webgl)); - } + switch (this.ndim) { + case 1: hist = createHistogram(clTH1 + this.htype, x.nbins); break; + case 2: hist = createHistogram(clTH2 + this.htype, x.nbins, y.nbins); break; + case 3: hist = createHistogram(clTH3 + this.htype, x.nbins, y.nbins, z.nbins); break; } - return promise.then(() => { - // this is limit for the visible faces, number of volumes does not matter - if (this._first_drawing && !this.ctrl.maxfaces) - this.ctrl.maxfaces = 200000 * this.ctrl.more; - - // set top painter only when first child exists - this.setAsMainPainter(); - - this.createToolbar(); - - // just draw extras and complete drawing if there are no main model - if (!this._clones) - return this.completeDraw(); + hist.fXaxis.fTitle = x.title; + hist.fXaxis.fXmin = x.min; + hist.fXaxis.fXmax = x.max; + hist.fXaxis.fLabels = x.fLabels; - return new Promise(resolveFunc => { - this._resolveFunc = resolveFunc; - this.showDrawInfo('Drawing geometry'); - this.startDrawGeometry(true); - }); - }); - } + if (this.ndim > 1) + hist.fYaxis.fTitle = y.title; + hist.fYaxis.fXmin = y.min; + hist.fYaxis.fXmax = y.max; + hist.fYaxis.fLabels = y.fLabels; - /** @summary methods show info when first geometry drawing is performed */ - showDrawInfo(msg) { - if (this.isBatchMode() || !this._first_drawing || !this._start_drawing_time) return; + if (this.ndim > 2) + hist.fZaxis.fTitle = z.title; + hist.fZaxis.fXmin = z.min; + hist.fZaxis.fXmax = z.max; + hist.fZaxis.fLabels = z.fLabels; - const main = this._renderer.domElement.parentNode; - if (!main) return; + hist.fName = this.hist_name; + hist.fTitle = this.draw_title; + hist.fOption = this.drawopt; + hist.$custom_stat = (this.hist_name === '$htemp') ? 111110 : 111111; - let info = main.querySelector('.geo_info'); + if (set_hist) { + this.hist = hist; + this.x = x; + this.y = y; + this.z = z; + } else + hist.fBits |= kNoStats; - if (!msg) - info?.remove(); - else { - const spent = (new Date().getTime() - this._start_drawing_time)*1e-3; - if (!info) { - info = getDocument().createElement('p'); - info.setAttribute('class', 'geo_info'); - info.setAttribute('style', 'position: absolute; text-align: center; vertical-align: middle; top: 45%; left: 40%; color: red; font-size: 150%;'); - main.append(info); - } - info.innerHTML = `${msg}, ${spent.toFixed(1)}s`; - } + return hist; } - /** @summary Reentrant method to perform geometry drawing step by step */ - continueDraw() { - // nothing to do - exit - if (this.isStage(stageInit)) return; - - const tm0 = new Date().getTime(), - interval = this._first_drawing ? 1000 : 200; - let now = tm0; + /** @summary Create output object - histogram, graph, dump array */ + createOutputObject() { + if (this.hist || !this.vars[0].buf) + return; - while (true) { - const res = this.nextDrawAction(); - if (!res) break; + if (this.dump_values) { + // just create array where dumped values will be collected + this.hist = []; - now = new Date().getTime(); + // reassign fill method + this.fill1DHistogram = this.fill2DHistogram = this.fill3DHistogram = this.dumpValues; + } else if (this.graph) { + const N = this.vars[0].buf.length; + let res = null; - // stop creation after 100 sec, render as is - if (now - this._startm > 1e5) { - this.changeStage(stageInit, 'Abort build after 100s'); - break; + if (this.ndim === 1) { + // A 1-dimensional graph will just have the x axis as an index + res = createTGraph(N, Array.from(Array(N).keys()), this.vars[0].buf); + res.fName = 'Graph'; + res.fTitle = this.draw_title; + } else if (this.ndim === 2) { + res = createTGraph(N, this.vars[0].buf, this.vars[1].buf); + res.fName = 'Graph'; + res.fTitle = this.draw_title; + delete this.vars[1].buf; + } else if (this.ndim === 3) { + res = create$1(clTPolyMarker3D); + res.fN = N; + res.fLastPoint = N - 1; + const arr = new Array(N * 3); + for (let k = 0; k < N; ++k) { + arr[k * 3] = this.vars[0].buf[k]; + arr[k * 3 + 1] = this.vars[1].buf[k]; + arr[k * 3 + 2] = this.vars[2].buf[k]; + } + res.fP = arr; + res.$hist = this.createHistogram(10); + delete this.vars[1].buf; + delete this.vars[2].buf; + res.fName = 'Points'; } - // if we are that fast, do next action - if ((res === true) && (now - tm0 < interval)) continue; - - if ((now - tm0 > interval) || (res === 1) || (res === 2)) { - showProgress(this.drawing_log); + this.hist = res; + } else { + const nbins = [200, 50, 20]; + this.createHistogram(nbins[this.ndim], true); + } - this.showDrawInfo(this.drawing_log); + const var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length; - if (this._first_drawing && this._webgl && (this._num_meshes - this._last_render_meshes > 100) && (now - this._last_render_tm > 2.5*interval)) { - this.adjustCameraPosition(); - this.render3D(-1); - this._last_render_meshes = this.ctrl.info.num_meshes; + if (!this.graph) { + switch (this.ndim) { + case 1: { + for (let n = 0; n < len; ++n) + this.fill1DHistogram(var0[n], cut ? cut[n] : 1); + break; + } + case 2: { + const var1 = this.vars[1].buf; + for (let n = 0; n < len; ++n) + this.fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1); + delete this.vars[1].buf; + break; + } + case 3: { + const var1 = this.vars[1].buf, var2 = this.vars[2].buf; + for (let n = 0; n < len; ++n) + this.fill3DHistogram(var0[n], var1[n], var2[n], cut ? cut[n] : 1); + delete this.vars[1].buf; + delete this.vars[2].buf; + break; } - if (res !== 2) setTimeout(() => this.continueDraw(), (res === 1) ? 100 : 1); - - return; } } - const take_time = now - this._startm; - - if (this._first_drawing || this._full_redrawing) - console.log(`Create tm = ${take_time} meshes ${this.ctrl.info.num_meshes} faces ${this.ctrl.info.num_faces}`); - - if (take_time > 300) { - showProgress('Rendering geometry'); - this.showDrawInfo('Rendering'); - return setTimeout(() => this.completeDraw(true), 10); - } - - this.completeDraw(true); + delete this.vars[0].buf; + delete this.cut.buf; } - /** @summary Checks camera position and recalculate rendering order if needed - * @param force - if specified, forces calculations of render order */ - testCameraPosition(force) { - this._camera.updateMatrixWorld(); + /** @summary Fill TBits histogram */ + fillTBitsHistogram(xvalue, weight) { + if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) + return; - this.drawOverlay(); + const sz = Math.min(xvalue.fNbits + 1, xvalue.fNbytes * 8); - const origin = this._camera.position.clone(); - if (!force && this._last_camera_position) { - // if camera position does not changed a lot, ignore such change - const dist = this._last_camera_position.distanceTo(origin); - if (dist < (this._overall_size || 1000)*1e-4) return; + for (let bit = 0, mask = 1, b = 0; bit < sz; ++bit) { + if (xvalue.fAllBits[b] && mask) { + if (bit <= this.x.nbins) + this.hist.fArray[bit + 1] += weight; + else + this.hist.fArray[this.x.nbins + 1] += weight; + } + + mask *= 2; + if (mask >= 0x100) { + mask = 1; + ++b; + } } + } - this._last_camera_position = origin; // remember current camera position + /** @summary Fill bits histogram */ + fillBitsHistogram(xvalue, weight) { + if (!weight) + return; - if (this.ctrl._axis) { - const vect = (this._controls?.target || this._lookat).clone().sub(this._camera.position).normalize(); - this.getExtrasContainer('get', 'axis')?.traverse(obj3d => { - if (isFunc(obj3d._axis_flip)) - obj3d._axis_flip(vect); - }); + for (let bit = 0, mask = 1; bit < this.x.nbins; ++bit) { + if (xvalue & mask) + this.hist.fArray[bit + 1] += weight; + mask *= 2; } + } - if (!this.ctrl.project) - produceRenderOrder(this._toplevel, origin, this.ctrl.depthMethod, this._clones); + /** @summary Fill boolean histogram */ + fillBooleanHistogram(boolvalue, weight) { + if (!weight) + return; + const xvalue = boolvalue ? 1 : 0; + this.hist.fArray[xvalue + 1] += weight; + this.hist.fTsumw += weight; + this.hist.fTsumwx += weight * xvalue; + this.hist.fTsumwx2 += weight * xvalue * xvalue; } - /** @summary Call 3D rendering of the geometry - * @param tmout - specifies delay, after which actual rendering will be invoked - * @param [measure] - when true, for the first time printout rendering time - * @return {Promise} when tmout bigger than 0 is specified - * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings - * superimposed with each other. If tmeout <= 0, rendering performed immediately - * Several special values are used: - * -1 - force recheck of rendering order based on camera position */ - render3D(tmout, measure) { - if (!this._renderer) { - if (!this.did_cleanup) - console.warn('renderer object not exists - check code'); - else - console.warn('try to render after cleanup'); - return this; + /** @summary Fill 1D histogram */ + fill1DHistogram(xvalue, weight) { + const bin = this.x.GetBin(xvalue); + this.hist.fArray[bin] += weight; + + if (!this.x.lbls && Number.isFinite(xvalue)) { + this.hist.fTsumw += weight; + this.hist.fTsumwx += weight * xvalue; + this.hist.fTsumwx2 += weight * xvalue * xvalue; } + } - const ret_promise = (tmout !== undefined) && (tmout > 0) && (measure !== 'nopromise'); + /** @summary Fill 2D histogram */ + fill2DHistogram(xvalue, yvalue, weight) { + const xbin = this.x.GetBin(xvalue), + ybin = this.y.GetBin(yvalue); - if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout + this.hist.fArray[xbin + (this.x.nbins + 2) * ybin] += weight; + if (!this.x.lbls && !this.y.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue)) { + this.hist.fTsumw += weight; + this.hist.fTsumwx += weight * xvalue; + this.hist.fTsumwy += weight * yvalue; + this.hist.fTsumwx2 += weight * xvalue * xvalue; + this.hist.fTsumwxy += weight * xvalue * yvalue; + this.hist.fTsumwy2 += weight * yvalue * yvalue; + } + } - if ((tmout > 0) && this._webgl) { - if (this.isBatchMode()) tmout = 1; // use minimal timeout in batch mode - if (ret_promise) { - return new Promise(resolveFunc => { - if (!this._render_resolveFuncs) - this._render_resolveFuncs = []; - this._render_resolveFuncs.push(resolveFunc); - if (!this.render_tmout) - this.render_tmout = setTimeout(() => this.render3D(0), tmout); - }); - } + /** @summary Fill 3D histogram */ + fill3DHistogram(xvalue, yvalue, zvalue, weight) { + const xbin = this.x.GetBin(xvalue), + ybin = this.y.GetBin(yvalue), + zbin = this.z.GetBin(zvalue); - if (!this.render_tmout) - this.render_tmout = setTimeout(() => this.render3D(0), tmout); - return this; + this.hist.fArray[xbin + (this.x.nbins + 2) * (ybin + (this.y.nbins + 2) * zbin)] += weight; + if (!this.x.lbls && !this.y.lbls && !this.z.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue) && Number.isFinite(zvalue)) { + this.hist.fTsumw += weight; + this.hist.fTsumwx += weight * xvalue; + this.hist.fTsumwy += weight * yvalue; + this.hist.fTsumwz += weight * zvalue; + this.hist.fTsumwx2 += weight * xvalue * xvalue; + this.hist.fTsumwy2 += weight * yvalue * yvalue; + this.hist.fTsumwz2 += weight * zvalue * zvalue; + this.hist.fTsumwxy += weight * xvalue * yvalue; + this.hist.fTsumwxz += weight * xvalue * zvalue; + this.hist.fTsumwyz += weight * yvalue * zvalue; } + } - if (this.render_tmout) { - clearTimeout(this.render_tmout); - delete this.render_tmout; + /** @summary Dump values */ + dumpValues(v1, v2, v3, v4) { + let obj; + switch (this.ndim) { + case 1: obj = { x: v1, weight: v2 }; break; + case 2: obj = { x: v1, y: v2, weight: v3 }; break; + case 3: obj = { x: v1, y: v2, z: v3, weight: v4 }; break; } - beforeRender3D(this._renderer); + if (this.cut.is_dummy()) { + if (this.ndim === 1) + obj = v1; + else + delete obj.weight; + } - const tm1 = new Date(); + this.hist.push(obj); + } - if (this._adjust_camera_with_render) { - this.adjustCameraPosition('only_set'); - delete this._adjust_camera_with_render; + /** @summary function used when all branches can be read as array + * @desc most typical usage - histogram filling of single branch */ + ProcessArraysFunc(/* entry */) { + if (this.arr_limit || this.graph) { + const var0 = this.vars[0], + var1 = this.vars[1], + var2 = this.vars[2], + len = this.tgtarr.br0.length; + if (!var0.buf.length && (len >= this.arr_limit) && !this.graph) { + // special use case - first array large enough to create histogram directly base on it + var0.buf = this.tgtarr.br0; + if (var1) + var1.buf = this.tgtarr.br1; + if (var2) + var2.buf = this.tgtarr.br2; + } else { + for (let k = 0; k < len; ++k) { + var0.buf.push(this.tgtarr.br0[k]); + if (var1) + var1.buf.push(this.tgtarr.br1[k]); + if (var2) + var2.buf.push(this.tgtarr.br2[k]); + } + } + var0.kind = 'number'; + if (var1) + var1.kind = 'number'; + if (var2) + var2.kind = 'number'; + this.cut.buf = null; // do not create buffer for cuts + if (!this.graph && (var0.buf.length >= this.arr_limit)) { + this.createOutputObject(); + this.arr_limit = 0; + } + } else { + const br0 = this.tgtarr.br0, len = br0.length; + switch (this.ndim) { + case 1: { + for (let k = 0; k < len; ++k) + this.fill1DHistogram(br0[k], 1); + break; + } + case 2: { + const br1 = this.tgtarr.br1; + for (let k = 0; k < len; ++k) + this.fill2DHistogram(br0[k], br1[k], 1); + break; + } + case 3: { + const br1 = this.tgtarr.br1, br2 = this.tgtarr.br2; + for (let k = 0; k < len; ++k) + this.fill3DHistogram(br0[k], br1[k], br2[k], 1); + break; + } + } } + } - this.testCameraPosition(tmout === -1); + /** @summary simple dump of the branch - no need to analyze something */ + ProcessDump(/* entry */) { + const res = this.leaf ? this.tgtobj.br0[this.leaf] : this.tgtobj.br0; - // its needed for outlinePass - do rendering, most consuming time - if (this._webgl && this._effectComposer && (this._effectComposer.passes.length > 0)) - this._effectComposer.render(); - else if (this._webgl && this._bloomComposer && (this._bloomComposer.passes.length > 0)) { - this._renderer.clear(); - this._camera.layers.set(_BLOOM_SCENE); - this._bloomComposer.render(); - this._renderer.clearDepth(); - this._camera.layers.set(_ENTIRE_SCENE); - this._renderer.render(this._scene, this._camera); + if (res && this.copy_fields) { + if (checkArrayPrototype(res) === 0) + this.hist.push(Object.assign({}, res)); + else + this.hist.push(res); } else - this._renderer.render(this._scene, this._camera); + this.hist.push(res); + } + + /** @summary Normal TSelector Process handler */ + Process(entry) { + this.globals.entry = entry; // can be used in any expression + this.cut.produce(this.tgtobj); + if (!this.dump_values && !this.cut.value) + return; - const tm2 = new Date(); + for (let n = 0; n < this.ndim; ++n) + this.vars[n].produce(this.tgtobj); - this.last_render_tm = tm2.getTime(); + const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut; - if ((this.first_render_tm === 0) && (measure === true)) { - this.first_render_tm = tm2.getTime() - tm1.getTime(); - if (this.first_render_tm > 500) - console.log(`three.js r${REVISION}, first render tm = ${this.first_render_tm}`); + if (this.dump_entries) + this.hist.push(entry); + else if (this.graph || this.arr_limit) { + switch (this.ndim) { + case 1: + for (let n0 = 0; n0 < var0.length; ++n0) { + var0.buf.push(var0.get(n0)); + cut.buf?.push(cut.value); + } + break; + case 2: + for (let n0 = 0; n0 < var0.length; ++n0) { + for (let n1 = 0; n1 < var1.length; ++n1) { + var0.buf.push(var0.get(n0)); + var1.buf.push(var1.get(n1)); + cut.buf?.push(cut.value); + } + } + break; + case 3: + for (let n0 = 0; n0 < var0.length; ++n0) { + for (let n1 = 0; n1 < var1.length; ++n1) { + for (let n2 = 0; n2 < var2.length; ++n2) { + var0.buf.push(var0.get(n0)); + var1.buf.push(var1.get(n1)); + var2.buf.push(var2.get(n2)); + cut.buf?.push(cut.value); + } + } + } + break; + } + if (!this.graph && (var0.buf.length >= this.arr_limit)) { + this.createOutputObject(); + this.arr_limit = 0; + } + } else if (this.hist) { + switch (this.ndim) { + case 1: + for (let n0 = 0; n0 < var0.length; ++n0) + this.fill1DHistogram(var0.get(n0), cut.value); + break; + case 2: + for (let n0 = 0; n0 < var0.length; ++n0) { + for (let n1 = 0; n1 < var1.length; ++n1) + this.fill2DHistogram(var0.get(n0), var1.get(n1), cut.value); + } + break; + case 3: + for (let n0 = 0; n0 < var0.length; ++n0) { + for (let n1 = 0; n1 < var1.length; ++n1) { + for (let n2 = 0; n2 < var2.length; ++n2) + this.fill3DHistogram(var0.get(n0), var1.get(n1), var2.get(n2), cut.value); + } + } + break; + } } - afterRender3D(this._renderer); + if (this.monitoring && this.hist && !this.dump_values) { + const now = new Date().getTime(); + if (now - this.lasttm > this.monitoring) { + this.lasttm = now; + if (isFunc(this.progress_callback)) + this.progress_callback(this.hist); + } + } - if (this._render_resolveFuncs) { - const arr = this._render_resolveFuncs; - delete this._render_resolveFuncs; - arr.forEach(func => func(this)); + if ((this.nmatch !== undefined) && (--this.nmatch <= 0)) { + if (!this.hist) + this.createOutputObject(); + this.Abort(); } } - /** @summary Start geo worker */ - startWorker() { - if (this._worker) return; - - this._worker_ready = false; - this._worker_jobs = 0; // counter how many requests send to worker - - // TODO: modules not yet working, see https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/ - this._worker = new Worker(exports.source_dir + 'scripts/geoworker.js' /*, { type: 'module' } */); - - this._worker.onmessage = e => { - if (!isObject(e.data)) return; - - if ('log' in e.data) - return console.log(`geo: ${e.data.log}`); - - if ('progress' in e.data) - return showProgress(e.data.progress); - - e.data.tm3 = new Date().getTime(); - - if ('init' in e.data) { - this._worker_ready = true; - console.log(`Worker ready: ${e.data.tm3 - e.data.tm0}`); - } else - this.processWorkerReply(e.data); - }; - - // send initialization message with clones - this._worker.postMessage({ - init: true, // indicate init command for worker - browser, - tm0: new Date().getTime(), - vislevel: this._clones.getVisLevel(), - maxvisnodes: this._clones.getMaxVisNodes(), - clones: this._clones.nodes, - sortmap: this._clones.sortmap - }); - } + /** @summary Normal TSelector Terminate handler */ + Terminate(res) { + if (res && !this.hist) + this.createOutputObject(); - /** @summary check if one can submit request to worker - * @private */ - canSubmitToWorker(force) { - if (!this._worker) return false; + this.ShowProgress(); - return this._worker_ready && ((this._worker_jobs === 0) || force); + if (isFunc(this.result_callback)) + this.result_callback(this.hist); } - /** @summary submit request to worker - * @private */ - submitToWorker(job) { - if (!this._worker) return false; +} // class TDrawSelector - this._worker_jobs++; - job.tm0 = new Date().getTime(); - this._worker.postMessage(job); - } - /** @summary process reply from worker - * @private */ - processWorkerReply(job) { - this._worker_jobs--; +/** @summary return type name of given member in the class + * @private */ +function defineMemberTypeName(file, parent_class, member_name) { + const s_i = file.findStreamerInfo(parent_class), + arr = s_i?.fElements?.arr; + if (!arr) + return ''; - if ('collect' in job) { - this._new_draw_nodes = job.new_nodes; - this._draw_all_nodes = job.complete; - this.changeStage(stageAnalyze); - // invoke methods immediately - return this.continueDraw(); + let elem = null; + for (let k = 0; k < arr.length; ++k) { + if (arr[k].fTypeName === kBaseClass) { + const res = defineMemberTypeName(file, arr[k].fName, member_name); + if (res) + return res; + } else if (arr[k].fName === member_name) { + elem = arr[k]; + break; } + } - if ('shapes' in job) { - for (let n=0; n { - buf[pos+ii[0]] = x; - buf[pos+ii[1]] = y; - buf[pos+ii[2]] = z ?? gridZ; - pos += 3; - }, createText = (lbl, size) => { - const text3d = new TextGeometry(lbl, { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); - text3d.computeBoundingBox(); - text3d._width = text3d.boundingBox.max.x - text3d.boundingBox.min.x; - text3d._height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; +/** @summary Process selector for the tree + * @desc function similar to the TTree::Process + * @param {object} tree - instance of TTree class + * @param {object} selector - instance of {@link TSelector} class + * @param {object} [args] - different arguments + * @param {number} [args.firstentry] - first entry to process, 0 when not specified + * @param {number} [args.numentries] - number of entries to process, all when not specified + * @param {Array} [args.elist] - arrays of entries id to process + * @return {Promise} with TSelector instance */ +async function treeProcess(tree, selector, args) { + if (!args) + args = {}; - text3d.translate(-text3d._width/2, -text3d._height/2, 0); - if (this._camera.orthoSign < 0) - text3d.rotateY(Math.PI); + if (!selector || !tree.$file || !selector.numBranches()) { + selector?.Terminate(false); + return Promise.reject(Error('required parameter missing for TTree::Process')); + } - if (isFunc(this._camera.orthoRotation)) - this._camera.orthoRotation(text3d); + // central handle with all information required for reading + const handle = { + tree, // keep tree reference + file: tree.$file, // keep file reference + selector, // reference on selector + arr: [], // list of branches + current_entry: -1, // current processed entry + simple_read: true, // all baskets in all used branches are in sync, + process_arrays: true // one can process all branches as arrays + }, createLeafElem = (leaf, name) => { + // function creates TStreamerElement which corresponds to the elementary leaf + let datakind; + switch (leaf._typename) { + case 'TLeafF': datakind = kFloat; break; + case 'TLeafD': datakind = kDouble; break; + case 'TLeafO': datakind = kBool; break; + case 'TLeafB': datakind = leaf.fIsUnsigned ? kUChar : kChar; break; + case 'TLeafS': datakind = leaf.fIsUnsigned ? kUShort : kShort; break; + case 'TLeafI': datakind = leaf.fIsUnsigned ? kUInt : kInt; break; + case 'TLeafL': datakind = leaf.fIsUnsigned ? kULong64 : kLong64; break; + case 'TLeafC': datakind = kTString; break; + default: return null; + } + const elem = createStreamerElement(name || leaf.fName, datakind); + if ((leaf.fLen > 1) && (datakind !== kTString)) { + elem.fType += kOffsetL; + elem.fArrayLength = leaf.fLen; + } + return elem; + }, findInHandle = branch => { + for (let k = 0; k < handle.arr.length; ++k) { + if (handle.arr[k].branch === branch) + return handle.arr[k]; + } + return null; + }; - return text3d; - }, createTextMesh = (geom, material, x, y, z) => { - const tgt = [0, 0, 0]; - tgt[ii[0]] = x; - tgt[ii[1]] = y; - tgt[ii[2]] = z ?? gridZ; - const mesh = new Mesh(geom, material); - mesh.translateX(tgt[0]).translateY(tgt[1]).translateZ(tgt[2]); - return mesh; - }; + let namecnt = 0; - if (this.ctrl.camera_overlay === 'bar') { - const container = this.getExtrasContainer('create', 'overlay'); + function addBranchForReading(branch, target_object, target_name, read_mode) { + // central method to add branch for reading + // read_mode == true - read only this branch + // read_mode == '$child$' is just member of object from for STL or clones array + // read_mode == '' is sub-object from STL or clones array, happens when such new object need to be created + // read_mode == '.member_name' select only reading of member_name instead of complete object - let x1 = xmin * 0.15 + xmax * 0.85, - x2 = xmin * 0.05 + xmax * 0.95; - const y1 = ymax * 0.9 + ymin * 0.1, - y2 = ymax * 0.86 + ymin * 0.14, - ticks = x_handle.createTicks(); + if (isStr(branch)) + branch = findBranch(handle.tree, branch); - if (ticks.major?.length > 1) { - x1 = ticks.major[ticks.major.length-2]; - x2 = ticks.major[ticks.major.length-1]; - } + if (!branch) { + console.error('Did not found branch'); + return null; + } - buf = new Float32Array(3*6); pos = 0; + let item = findInHandle(branch); - addPoint(x1, y1, midZ); - addPoint(x1, y2, midZ); + if (item) { + console.error(`Branch ${branch.fName} already configured for reading`); + if (item.tgt !== target_object) + console.error('Target object differs'); + return null; + } - addPoint(x1, (y1 + y2) / 2, midZ); - addPoint(x2, (y1 + y2) / 2, midZ); + if (!branch.fEntries) { + console.warn(`Branch ${branch.fName} does not have entries`); + return null; + } - addPoint(x2, y1, midZ); - addPoint(x2, y2, midZ); + item = { + branch, + tgt: target_object, // used target object - can be differ for object members + name: target_name, + index: -1, // index in the list of read branches + member: null, // member to read branch + type: 0, // keep type identifier + curr_entry: -1, // last processed entry + raw: null, // raw buffer for reading + basket: null, // current basket object + curr_basket: 0, // number of basket used for processing + read_entry: -1, // last entry which is already read + staged_entry: -1, // entry which is staged for reading + first_readentry: -1, // first entry to read + staged_basket: 0, // last basket staged for reading + eindx: 0, // index of last checked entry when selecting baskets + selected_baskets: [], // array of selected baskets, used when specific events are selected + numentries: branch.fEntries, + numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file + counters: null, // branch indexes used as counters + ascounter: [], // list of other branches using that branch as counter + baskets: [], // array for read baskets, + staged_prev: 0, // entry limit of previous I/O request + staged_now: 0, // entry limit of current I/O request + progress_showtm: 0, // last time when progress was showed + getBasketEntry(k) { + if (!this.branch || (k > this.branch.fMaxBaskets)) + return 0; + const res = (k < this.branch.fMaxBaskets) ? this.branch.fBasketEntry[k] : 0; + if (res) + return res; + const bskt = (k > 0) ? this.branch.fBaskets.arr[k - 1] : null; + return bskt ? (this.branch.fBasketEntry[k - 1] + bskt.fNevBuf) : 0; + }, + getTarget(tgtobj) { + // returns target object which should be used for the branch reading + if (!this.tgt) + return tgtobj; + for (let k = 0; k < this.tgt.length; ++k) { + const sub = this.tgt[k]; + if (!tgtobj[sub.name]) + tgtobj[sub.name] = sub.lst.Create(); + tgtobj = tgtobj[sub.name]; + } + return tgtobj; + }, + getEntry(entry) { + // This should be equivalent to TBranch::GetEntry() method + const shift = entry - this.first_entry; + let off; + if (!this.branch.TestBit(kDoNotUseBufferMap)) + this.raw.clearObjectMap(); + if (this.basket.fEntryOffset) { + off = this.basket.fEntryOffset[shift]; + if (this.basket.fDisplacement) + this.raw.fDisplacement = this.basket.fDisplacement[shift]; + } else + off = this.basket.fKeylen + this.basket.fNevBufSize * shift; + this.raw.locate(off - this.raw.raw_shift); - const lineMaterial = new LineBasicMaterial({ color: 'green' }), - textMaterial = new MeshBasicMaterial({ color: 'green', vertexColors: false }); + // this.member.func(this.raw, this.getTarget(tgtobj)); + } + }; - container.add(createLineSegments(buf, lineMaterial)); + // last basket can be stored directly with the branch + while (item.getBasketEntry(item.numbaskets + 1)) + item.numbaskets++; - const text3d = createText(x_handle.format(x2-x1, true), Math.abs(y2-y1)); + // check all counters if we + const nb_leaves = branch.fLeaves?.arr?.length ?? 0, + leaf = (nb_leaves > 0) ? branch.fLeaves.arr[0] : null, + is_brelem = (branch._typename === clTBranchElement); + let elem = null, // TStreamerElement used to create reader + member = null, // member for actual reading of the branch + child_scan = 0, // scan child branches after main branch is appended + item_cnt = null, item_cnt2 = null, object_class; - container.add(createTextMesh(text3d, textMaterial, (x2 + x1) / 2, (y1 + y2) / 2 + text3d._height * 0.8, midZ)); - return true; - } + if (branch.fBranchCount) { + item_cnt = findInHandle(branch.fBranchCount); - const show_grid = this.ctrl.camera_overlay.indexOf('grid') === 0; + if (!item_cnt) + item_cnt = addBranchForReading(branch.fBranchCount, target_object, '$counter' + namecnt++, true); - if (show_grid && this._camera.orthoZ) { - if (this.ctrl.camera_overlay === 'gridf') - gridZ += this._camera.orthoSign * this._camera.orthoZ[1]; - else if (this.ctrl.camera_overlay === 'gridb') - gridZ -= this._camera.orthoSign * this._camera.orthoZ[1]; - } + if (!item_cnt) { + console.error(`Cannot add counter branch ${branch.fBranchCount.fName}`); + return null; + } - if ((this.ctrl.camera_overlay === 'axis') || show_grid) { - const container = this.getExtrasContainer('create', 'overlay'), - lineMaterial = new LineBasicMaterial({ color: new Color('black') }), - gridMaterial1 = show_grid ? new LineBasicMaterial({ color: new Color(0xbbbbbb) }) : null, - gridMaterial2 = show_grid ? new LineDashedMaterial({ color: new Color(0xdddddd), dashSize: grid_gap, gapSize: grid_gap }) : null, - textMaterial = new MeshBasicMaterial({ color: 'black', vertexColors: false }), - xticks = x_handle.createTicks(); + let BranchCount2 = branch.fBranchCount2; - while (xticks.next()) { - const x = xticks.tick, k = (xticks.kind === 1) ? 1.0 : 0.6; + if (!BranchCount2 && (branch.fBranchCount.fStreamerType === kSTL) && + ((branch.fStreamerType === kStreamLoop) || (branch.fStreamerType === kOffsetL + kStreamLoop))) { + // special case when count member from kStreamLoop not assigned as fBranchCount2 + const elemd = findBrachStreamerElement(branch, handle.file), + arrd = branch.fBranchCount.fBranches.arr; - if (show_grid) { - buf = new Float32Array(2*3); pos = 0; - addPoint(x, ymax - k*tick_size - grid_gap); - addPoint(x, ymin + k*tick_size + grid_gap); - container.add(createLineSegments(buf, xticks.kind === 1 ? gridMaterial1 : gridMaterial2)); + if (elemd?.fCountName && arrd) { + for (let k = 0; k < arrd.length; ++k) { + if (arrd[k].fName === branch.fBranchCount.fName + '.' + elemd.fCountName) { + BranchCount2 = arrd[k]; + break; + } + } } - buf = new Float32Array(4*3); pos = 0; - addPoint(x, ymax); - addPoint(x, ymax - k*tick_size); - addPoint(x, ymin); - addPoint(x, ymin + k*tick_size); - - container.add(createLineSegments(buf, lineMaterial)); - - if (xticks.kind !== 1) continue; - - const text3d = createText(x_handle.format(x, true), text_size); - - container.add(createTextMesh(text3d, textMaterial, x, ymax - tick_size - text_size/2 - text3d._height/2)); - - container.add(createTextMesh(text3d, textMaterial, x, ymin + tick_size + text_size/2 + text3d._height/2)); + if (!BranchCount2) + console.error('Did not found branch for second counter of kStreamLoop element'); } - const yticks = y_handle.createTicks(); + if (BranchCount2) { + item_cnt2 = findInHandle(BranchCount2); - while (yticks.next()) { - const y = yticks.tick, k = (yticks.kind === 1) ? 1.0 : 0.6; + if (!item_cnt2) + item_cnt2 = addBranchForReading(BranchCount2, target_object, '$counter' + namecnt++, true); - if (show_grid) { - buf = new Float32Array(2*3); pos = 0; - addPoint(xmin + k*tick_size + grid_gap, y); - addPoint(xmax - k*tick_size - grid_gap, y); - container.add(createLineSegments(buf, yticks.kind === 1 ? gridMaterial1 : gridMaterial2)); + if (!item_cnt2) { + console.error(`Cannot add counter branch2 ${BranchCount2.fName}`); + return null; } + } + } else if (nb_leaves === 1 && leaf?.fLeafCount) { + const br_cnt = findBranch(handle.tree, leaf.fLeafCount.fName); - buf = new Float32Array(4*3); pos = 0; - addPoint(xmin, y); - addPoint(xmin + k*tick_size, y); - addPoint(xmax, y); - addPoint(xmax - k*tick_size, y); - - container.add(createLineSegments(buf, lineMaterial)); - - if (yticks.kind !== 1) continue; - - const text3d = createText(y_handle.format(y, true), text_size); + if (br_cnt) { + item_cnt = findInHandle(br_cnt); - container.add(createTextMesh(text3d, textMaterial, xmin + tick_size + text_size/2 + text3d._width/2, y)); + if (!item_cnt) + item_cnt = addBranchForReading(br_cnt, target_object, '$counter' + namecnt++, true); - container.add(createTextMesh(text3d, textMaterial, xmax - tick_size - text_size/2 - text3d._width/2, y)); + if (!item_cnt) { + console.error(`Cannot add counter branch ${br_cnt.fName}`); + return null; + } } - - return true; } - return false; - } - - /** @summary Draw axes if configured, otherwise just remove completely */ - drawAxes() { - this.getExtrasContainer('delete', 'axis'); - - if (!this.ctrl._axis) - return false; + function scanBranches(lst, master_target, chld_kind) { + if (!lst?.arr.length) + return true; - const box = this.getGeomBoundingBox(this._toplevel, this.superimpose ? 'original' : undefined), - container = this.getExtrasContainer('create', 'axis'), - text_size = 0.02 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z), - center = [0, 0, 0], - names = ['x', 'y', 'z'], - labels = ['X', 'Y', 'Z'], - colors = ['red', 'green', 'blue'], - ortho = this.isOrthoCamera(), - ckind = this.ctrl.camera_kind ?? 'perspective'; + let match_prefix = branch.fName; + if (match_prefix.at(-1) === '.') + match_prefix = match_prefix.slice(0, match_prefix.length - 1); + if (isStr(read_mode) && (read_mode[0] === '.')) + match_prefix += read_mode; + match_prefix += '.'; - if (this.ctrl._axis === 2) { - for (let naxis = 0; naxis < 3; ++naxis) { - const name = names[naxis]; - if ((box.min[name] <= 0) && (box.max[name] >= 0)) continue; - center[naxis] = (box.min[name] + box.max[name])/2; - } - } + for (let k = 0; k < lst.arr.length; ++k) { + const br = lst.arr[k]; + if ((chld_kind > 0) && (br.fType !== chld_kind)) + continue; - for (let naxis = 0; naxis < 3; ++naxis) { - // exclude axis which is not seen - if (ortho && ckind.indexOf(labels[naxis]) < 0) continue; + if (br.fType === kBaseClassNode) { + if (!scanBranches(br.fBranches, master_target, chld_kind)) + return false; + continue; + } - const buf = new Float32Array(6), - color = colors[naxis], - name = names[naxis], - - valueToString = val => { - if (!val) return '0'; - const lg = Math.log10(Math.abs(val)); - if (lg < 0) { - if (lg > -1) return val.toFixed(2); - if (lg > -2) return val.toFixed(3); - } else { - if (lg < 2) return val.toFixed(1); - if (lg < 4) return val.toFixed(0); + const elem2 = findBrachStreamerElement(br, handle.file); + if (elem2?.fTypeName === kBaseClass) { + // if branch is data of base class, map it to original target + if (br.fTotBytes && !addBranchForReading(br, target_object, target_name, read_mode)) + return false; + if (!scanBranches(br.fBranches, master_target, chld_kind)) + return false; + continue; } - return val.toExponential(2); - }, - lbl = valueToString(box.max[name]) + ' ' + labels[naxis]; + let subname = br.fName, chld_direct = 1; - buf[0] = box.min.x; - buf[1] = box.min.y; - buf[2] = box.min.z; + if (br.fName.indexOf(match_prefix) === 0) + subname = subname.slice(match_prefix.length); + else if (chld_kind > 0) + continue; // for defined children names prefix must be present - buf[3] = box.min.x; - buf[4] = box.min.y; - buf[5] = box.min.z; + let p = subname.indexOf('['); + if (p > 0) + subname = subname.slice(0, p); + p = subname.indexOf('<'); + if (p > 0) + subname = subname.slice(0, p); - switch (naxis) { - case 0: buf[3] = box.max.x; break; - case 1: buf[4] = box.max.y; break; - case 2: buf[5] = box.max.z; break; - } + if (chld_kind > 0) { + chld_direct = '$child$'; + const pp = subname.indexOf('.'); + if (pp > 0) + chld_direct = detectBranchMemberClass(lst, branch.fName + '.' + subname.slice(0, pp + 1), k) || clTObject; + } - if (this.ctrl._axis === 2) { - for (let k = 0; k < 6; ++k) - if ((k % 3) !== naxis) buf[k] = center[k%3]; + if (!addBranchForReading(br, master_target, subname, chld_direct)) + return false; } - const lineMaterial = new LineBasicMaterial({ color }); - let mesh = createLineSegments(buf, lineMaterial); - - mesh._no_clip = true; // skip from clipping - - container.add(mesh); - - const textMaterial = new MeshBasicMaterial({ color, vertexColors: false }); + return true; + } - if ((center[naxis] === 0) && (center[naxis] >= box.min[name]) && (center[naxis] <= box.max[name])) { - if ((this.ctrl._axis !== 2) || (naxis === 0)) { - const geom = ortho ? new CircleGeometry(text_size*0.25) : new SphereGeometry(text_size*0.25); - mesh = new Mesh(geom, textMaterial); - mesh.translateX(naxis === 0 ? center[0] : buf[0]); - mesh.translateY(naxis === 1 ? center[1] : buf[1]); - mesh.translateZ(naxis === 2 ? center[2] : buf[2]); - mesh._no_clip = true; - container.add(mesh); + if (branch._typename === 'TBranchObject') { + member = { + name: target_name, + typename: branch.fClassName, + virtual: leaf.fVirtual, + func(buf, obj) { + const clname = this.virtual ? buf.readFastString(buf.ntou1() + 1) : this.typename; + obj[this.name] = buf.classStreamer({}, clname); } - } + }; + } else if ((branch.fType === kClonesNode) || (branch.fType === kSTLNode)) { + elem = createStreamerElement(target_name, kInt); - let text3d = new TextGeometry(lbl, { font: HelveticerRegularFont, size: text_size, height: 0, curveSegments: 5 }); - mesh = new Mesh(text3d, textMaterial); - mesh._no_clip = true; // skip from clipping + if (!read_mode || (isStr(read_mode) && (read_mode[0] === '.')) || (read_mode === 1)) { + handle.process_arrays = false; - function setSideRotation(mesh, normal) { - mesh._other_side = false; - mesh._axis_norm = normal ?? new Vector3(1, 0, 0); - mesh._axis_flip = function(vect) { - const other_side = vect.dot(this._axis_norm) < 0; - if (this._other_side !== other_side) { - this._other_side = other_side; - this.rotateY(Math.PI); + member = { + name: target_name, + conttype: branch.fClonesName || clTObject, + reallocate: args.reallocate_objects, + func(buf, obj) { + const size = buf.ntoi4(); + let n = 0, arr = obj[this.name]; + if (!arr || this.reallocate) + arr = obj[this.name] = new Array(size); + else { + n = arr.length; + arr.length = size; // reallocate array + } + + while (n < size) + arr[n++] = this.methods.Create(); // create new objects } }; - } - function setTopRotation(mesh, first_angle = -1) { - mesh._last_angle = first_angle; - mesh._axis_flip = function(vect) { - let angle = 0; - switch (this._axis_name) { - case 'x': angle = -Math.atan2(vect.y, vect.z); break; - case 'y': angle = -Math.atan2(vect.z, vect.x); break; - default: angle = Math.atan2(vect.y, vect.x); - } - angle = Math.round(angle / Math.PI * 2 + 2) % 4; - if (this._last_angle !== angle) { - this.rotateX((angle - this._last_angle) * Math.PI/2); - this._last_angle = angle; + if (isStr(read_mode) && (read_mode[0] === '.')) { + member.conttype = detectBranchMemberClass(branch.fBranches, branch.fName + read_mode); + if (!member.conttype) { + console.error(`Cannot select object ${read_mode} in the branch ${branch.fName}`); + return null; } - }; - } + } - let textbox = new Box3().setFromObject(mesh); + member.methods = makeMethodsList(member.conttype); - text3d.translate(-textbox.max.x*0.5, -textbox.max.y/2, 0); + child_scan = (branch.fType === kClonesNode) ? kClonesMemberNode : kSTLMemberNode; + } + } else if ((object_class = getBranchObjectClass(branch, handle.tree))) { + if (read_mode === true) { + console.warn(`Object branch ${object_class} can not have data to be read directly`); + return null; + } - mesh.translateX(buf[3]); - mesh.translateY(buf[4]); - mesh.translateZ(buf[5]); + handle.process_arrays = false; - mesh._axis_name = name; + const newtgt = new Array((target_object?.length || 0) + 1); + for (let l = 0; l < newtgt.length - 1; ++l) + newtgt[l] = target_object[l]; + newtgt[newtgt.length - 1] = { name: target_name, lst: makeMethodsList(object_class) }; - if (naxis === 0) { - if (ortho && ckind.indexOf('OX') > 0) - setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new Vector3(0, 1, 0)); - mesh.rotateX(Math.PI/2); - } + // this kind of branch does not have baskets and not need to be read + return scanBranches(branch.fBranches, newtgt, 0) ? item : null; + } else if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID === -1)) { + elem = createStreamerElement(target_name, branch.fClassName); - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); - } else if (naxis === 1) { - if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { - setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); - } else { - setSideRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); - } - } else if (naxis === 2) { - if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { - const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI/2); - if (zox) mesh.rotateX(-Math.PI/2); + if (elem.fType === kAny) { + const streamer = handle.file.getStreamer(branch.fClassName, { val: branch.fClassVersion, checksum: branch.fCheckSum }); + if (!streamer) { + elem = null; + console.warn('not found streamer!'); } else { - setTopRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateZ(Math.PI/2); + member = { + name: target_name, + typename: branch.fClassName, + streamer, + func(buf, obj) { + const res = { _typename: this.typename }; + for (let n = 0; n < this.streamer.length; ++n) + this.streamer[n].func(buf, res); + obj[this.name] = res; + } + }; } - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); } - container.add(mesh); - - text3d = new TextGeometry(valueToString(box.min[name]), { font: HelveticerRegularFont, size: text_size, height: 0, curveSegments: 5 }); - - mesh = new Mesh(text3d, textMaterial); - mesh._no_clip = true; // skip from clipping - textbox = new Box3().setFromObject(mesh); + // elem.fType = kAnyP; - text3d.translate(-textbox.max.x*0.5, -textbox.max.y/2, 0); + // only STL containers here + // if (!elem.fSTLtype) elem = null; + } else if (is_brelem && (nb_leaves <= 1)) { + elem = findBrachStreamerElement(branch, handle.file); - mesh._axis_name = name; + // this is basic type - can try to solve problem differently + if (!elem && branch.fStreamerType && (branch.fStreamerType < 20)) + elem = createStreamerElement(target_name, branch.fStreamerType); + } else if (nb_leaves === 1) { + // no special constrains for the leaf names - mesh.translateX(buf[0]); - mesh.translateY(buf[1]); - mesh.translateZ(buf[2]); + elem = createLeafElem(leaf, target_name); + } else if ((branch._typename === 'TBranch') && (nb_leaves > 1)) { + // branch with many elementary leaves - if (naxis === 0) { - if (ortho && ckind.indexOf('OX') > 0) - setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new Vector3(0, 1, 0)); - mesh.rotateX(Math.PI/2); - } - mesh.translateX(-text_size*0.5 - textbox.max.x*0.5); - } else if (naxis === 1) { - if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { - setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); - } else { - setSideRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(textbox.max.x*0.5 + text_size*0.5); - } - } else if (naxis === 2) { - if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { - const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI/2); - if (zox) mesh.rotateX(-Math.PI/2); - } else { - setTopRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateZ(Math.PI/2); - } - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); + const leaves = new Array(nb_leaves); + let isok = true; + for (let l = 0; l < nb_leaves; ++l) { + leaves[l] = createMemberStreamer(createLeafElem(branch.fLeaves.arr[l]), handle.file); + if (!leaves[l]) + isok = false; } - container.add(mesh); + if (isok) { + member = { + name: target_name, + leaves, + func(buf, obj) { + let tgt = obj[this.name], l = 0; + if (!tgt) + obj[this.name] = tgt = {}; + while (l < this.leaves.length) + this.leaves[l++].func(buf, tgt); + } + }; + } } - // after creating axes trigger rendering and recalculation of depth - return true; - } - - /** @summary Set axes visibility 0 - off, 1 - on, 2 - centered */ - setAxesDraw(on) { - if (on === 'toggle') - this.ctrl._axis = this.ctrl._axis ? 0 : 1; - else - this.ctrl._axis = (typeof on === 'number') ? on : (on ? 1 : 0); - return this.drawAxesAndOverlay(); - } - - /** @summary Set auto rotate mode */ - setAutoRotate(on) { - if (this.ctrl.project) return; - if (on !== undefined) this.ctrl.rotate = on; - this.autorotate(2.5); - } - - /** @summary Toggle wireframe mode */ - toggleWireFrame() { - this.ctrl.wireframe = !this.ctrl.wireframe; - this.changedWireFrame(); - } - - /** @summary Specify wireframe mode */ - setWireFrame(on) { - this.ctrl.wireframe = !!on; - this.changedWireFrame(); - } - - /** @summary Specify showtop draw options, relevant only for TGeoManager */ - setShowTop(on) { - this.ctrl.showtop = !!on; - this.redrawObject('same'); - } - - /** @summary Should be called when configuration of particular axis is changed */ - changedClipping(naxis = -1) { - if ((naxis < 0) || this.ctrl.clip[naxis]?.enabled) - this.updateClipping(false, true); - } - - /** @summary Should be called when depth test flag is changed */ - changedDepthTest() { - if (!this._toplevel) return; - const flag = this.ctrl.depthTest; - this._toplevel.traverse(node => { - if (node instanceof Mesh) - node.material.depthTest = flag; - }); - - this.render3D(0); - } - - /** @summary Should be called when depth method is changed */ - changedDepthMethod(arg) { - // force recalculatiion of render order - delete this._last_camera_position; - if (arg !== 'norender') - return this.render3D(); - } - - /** @summary Assign clipping attributes to the meshes - supported only for webgl */ - updateClipping(without_render, force_traverse) { - // do not try clipping with SVG renderer - if (this._renderer?.jsroot_render3d === constants$1.Render3D.SVG) return; - - if (!this._clipPlanes) { - this._clipPlanes = [new Plane(new Vector3(1, 0, 0), 0), - new Plane(new Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0), - new Plane(new Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0)]; + if (!elem && !member) { + console.warn(`Not supported branch ${branch.fName} type ${branch._typename}`); + return null; } - const clip = this.ctrl.clip, - clip_constants = [-1 * clip[0].value, clip[1].value, (this.ctrl._yup ? -1 : 1) * clip[2].value], - container = this.getExtrasContainer(this.ctrl.clipVisualize ? '' : 'delete', 'clipping'); - let panels = [], changed = false, - clip_cfg = this.ctrl.clipIntersect ? 16 : 0; - - for (let k = 0; k < 3; ++k) { - if (clip[k].enabled) - clip_cfg += 2 << k; - if (this._clipPlanes[k].constant !== clip_constants[k]) { - if (clip[k].enabled) changed = true; - this._clipPlanes[k].constant = clip_constants[k]; - } - if (clip[k].enabled) - panels.push(this._clipPlanes[k]); + if (!member) { + member = createMemberStreamer(elem, handle.file); - if (container && clip[k].enabled) { - const helper = new PlaneHelper(this._clipPlanes[k], (clip[k].max - clip[k].min)); - helper._no_clip = true; - container.add(helper); + if ((member.base !== undefined) && member.basename) { + // when element represent base class, we need handling which differ from normal IO + member.func = function(buf, obj) { + if (!obj[this.name]) + obj[this.name] = { _typename: this.basename }; + buf.classStreamer(obj[this.name], this.basename); + }; } } - if (panels.length === 0) - panels = null; - - if (this._clipCfg !== clip_cfg) - changed = true; - this._clipCfg = clip_cfg; + if (item_cnt && isStr(read_mode)) { + member.name0 = item_cnt.name; - const any_clipping = !!panels, ci = this.ctrl.clipIntersect, - material_side = any_clipping ? DoubleSide : FrontSide; + const snames = target_name.split('.'); - if (force_traverse || changed) { - this._scene.traverse(node => { - if (!node._no_clip && (node.material?.clippingPlanes !== undefined)) { - if (node.material.clippingPlanes !== panels) { - node.material.clipIntersection = ci; - node.material.clippingPlanes = panels; - node.material.needsUpdate = true; - } + if (snames.length === 1) { + // no point in the name - just plain array of objects + member.get = (arr, n) => arr[n]; + } else if (read_mode === '$child$') { + console.error(`target name ${target_name} contains point, but suppose to be direct child`); + return null; + } else if (snames.length === 2) { + target_name = member.name = snames[1]; + member.name1 = snames[0]; + member.subtype1 = read_mode; + member.methods1 = makeMethodsList(member.subtype1); + member.get = function(arr, n) { + let obj1 = arr[n][this.name1]; + if (!obj1) + obj1 = arr[n][this.name1] = this.methods1.Create(); + return obj1; + }; + } else { + // very complex task - we need to reconstruct several embedded members with their types + // try our best - but not all data types can be reconstructed correctly + // while classname is not enough - there can be different versions - if (node.material.emissive !== undefined) { - if (node.material.side !== material_side) { - node.material.side = material_side; - node.material.needsUpdate = true; - } - } + if (!branch.fParentName) { + console.error(`Not possible to provide more than 2 parts in the target name ${target_name}`); + return null; } - }); - } - - this.ctrl.doubleside = any_clipping; - - if (!without_render) this.render3D(0); - - return changed; - } - - /** @summary Assign callback, invoked every time when drawing is completed - * @desc Used together with web-based geometry viewer - * @private */ - setCompleteHandler(callback) { - this._complete_handler = callback; - } - /** @summary Completes drawing procedure - * @return {Promise} for ready */ - async completeDraw(close_progress) { - let first_time = false, full_redraw = false, check_extras = true; + target_name = member.name = snames.pop(); // use last element + member.snames = snames; // remember all sub-names + member.smethods = []; // and special handles to create missing objects - if (!this.ctrl) { - console.warn('ctrl object does not exist in completeDraw - something went wrong'); - return this; - } + let parent_class = branch.fParentName; // unfortunately, without version - let promise = Promise.resolve(true); + for (let k = 0; k < snames.length; ++k) { + const chld_class = defineMemberTypeName(handle.file, parent_class, snames[k]); + member.smethods[k] = makeMethodsList(chld_class || 'AbstractClass'); + parent_class = chld_class; + } + member.get = function(arr, n) { + let obj1 = arr[n][this.snames[0]]; + if (!obj1) + obj1 = arr[n][this.snames[0]] = this.smethods[0].Create(); + for (let k = 1; k < this.snames.length; ++k) { + let obj2 = obj1[this.snames[k]]; + if (!obj2) + obj2 = obj1[this.snames[k]] = this.smethods[k].Create(); + obj1 = obj2; + } + return obj1; + }; + } - if (!this._clones) { - check_extras = false; - // if extra object where append, redraw them at the end - this.getExtrasContainer('delete'); // delete old container - const extras = (this._main_painter ? this._main_painter._extraObjects : null) || this._extraObjects; - promise = this.drawExtras(extras, '', false); - } else if (this._first_drawing || this._full_redrawing) { - if (this.ctrl.tracks && this.geo_manager) - promise = this.drawExtras(this.geo_manager.fTracks, '/Tracks'); - } + // case when target is sub-object and need to be created before - return promise.then(() => { - if (this._full_redrawing) { - this.adjustCameraPosition('first'); - this._full_redrawing = false; - full_redraw = true; - this.changedDepthMethod('norender'); - } + if (member.objs_branch_func) { + // STL branch provides special function for the reading + member.func = member.objs_branch_func; + } else { + member.func0 = member.func; - if (this._first_drawing) { - this.adjustCameraPosition('first'); - this.showDrawInfo(); - this._first_drawing = false; - first_time = true; - full_redraw = true; + member.func = function(buf, obj) { + const arr = obj[this.name0]; // objects array where reading is done + let n = 0; + while (n < arr.length) + this.func0(buf, this.get(arr, n++)); // read all individual object with standard functions + }; } + } else if (item_cnt) { + handle.process_arrays = false; - if (first_time) - this.completeScene(); + if ((elem.fType === kDouble32) || (elem.fType === kFloat16)) { + // special handling for compressed floats - if (full_redraw && (this.ctrl.trans_radial || this.ctrl.trans_z)) - this.changedTransformation('norender'); + member.stl_size = item_cnt.name; + member.func = function(buf, obj) { + obj[this.name] = this.readarr(buf, obj[this.stl_size]); + }; + } else if (((elem.fType === kOffsetP + kDouble32) || (elem.fType === kOffsetP + kFloat16)) && branch.fBranchCount2) { + // special handling for variable arrays of compressed floats in branch - not tested - if (full_redraw) - return this.drawAxesAndOverlay(true); - }).then(() => { - this._scene.overrideMaterial = null; + member.stl_size = item_cnt.name; + member.arr_size = item_cnt2.name; + member.func = function(buf, obj) { + const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0); + for (let n = 0; n < sz0; ++n) + arr[n] = (buf.ntou1() === 1) ? this.readarr(buf, sz1[n]) : []; + obj[this.name] = arr; + }; + } else if (((elem.fType > 0) && (elem.fType < kOffsetL)) || (elem.fType === kTString) || + (((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL)) && branch.fBranchCount2)) { + // special handling of simple arrays + member = { + name: target_name, + stl_size: item_cnt.name, + type: elem.fType, + func(buf, obj) { + obj[this.name] = buf.readFastArray(obj[this.stl_size], this.type); + } + }; - if (this._provided_more_nodes !== undefined) { - this.appendMoreNodes(this._provided_more_nodes, true); - delete this._provided_more_nodes; - } + if (branch.fBranchCount2) { + member.type -= kOffsetP; + member.arr_size = item_cnt2.name; + member.func = function(buf, obj) { + const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0); + for (let n = 0; n < sz0; ++n) + arr[n] = (buf.ntou1() === 1) ? buf.readFastArray(sz1[n], this.type) : []; + obj[this.name] = arr; + }; + } + } else if ((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL) && member.cntname) + member.cntname = item_cnt.name; + else if (elem.fType === kStreamer) { + // with streamers one need to extend existing array - if (check_extras) { - // if extra object where append, redraw them at the end - this.getExtrasContainer('delete'); // delete old container - const extras = this._main_painter?._extraObjects || this._extraObjects; - return this.drawExtras(extras, '', false); - } - }).then(() => { - this.updateClipping(true); // do not render + if (item_cnt2) + throw new Error('Second branch counter not supported yet with kStreamer'); - this.render3D(0, true); + // function provided by normal I/O + member.func = member.branch_func; + member.stl_size = item_cnt.name; + } else if ((elem.fType === kStreamLoop) || (elem.fType === kOffsetL + kStreamLoop)) { + if (item_cnt2) { + // special solution for kStreamLoop + member.stl_size = item_cnt.name; + member.cntname = item_cnt2.name; + member.func = member.branch_func; // this is special function, provided by base I/O + } else + member.cntname = item_cnt.name; + } else { + member.name = '$stl_member'; - if (close_progress) showProgress(); + let loop_size_name; + if (item_cnt2) { + if (member.cntname) { + loop_size_name = item_cnt2.name; + member.cntname = '$loop_size'; + } else + throw new Error('Second branch counter not used - very BAD'); + } - this.addOrbitControls(); + const stlmember = { + name: target_name, + stl_size: item_cnt.name, + loop_size: loop_size_name, + member0: member, + func(buf, obj) { + const cnt = obj[this.stl_size], arr = new Array(cnt); + for (let n = 0; n < cnt; ++n) { + if (this.loop_size) + obj.$loop_size = obj[this.loop_size][n]; + this.member0.func(buf, obj); + arr[n] = obj.$stl_member; + } + delete obj.$stl_member; + delete obj.$loop_size; + obj[this.name] = arr; + } + }; - if (first_time && !this.isBatchMode()) { - // after first draw check if highlight can be enabled - if (this.ctrl.highlight === 0) - this.ctrl.highlight = (this.first_render_tm < 1000); + member = stlmember; + } + } // if (item_cnt) - // also highlight of scene object can be assigned at the first draw - if (this.ctrl.highlight_scene === 0) - this.ctrl.highlight_scene = this.ctrl.highlight; + // set name used to store result + member.name = target_name; - // if rotation was enabled, do it - if (this._webgl && this.ctrl.rotate && !this.ctrl.project) this.autorotate(2.5); - if (this._webgl && this.ctrl.show_controls) this.showControlGui(true); - } + item.member = member; // member for reading + if (elem) + item.type = elem.fType; + item.index = handle.arr.length; // index in the global list of branches - this.setAsMainPainter(); + if (item_cnt) { + item.counters = [item_cnt.index]; + item_cnt.ascounter.push(item.index); - if (isFunc(this._resolveFunc)) { - this._resolveFunc(this); - delete this._resolveFunc; + if (item_cnt2) { + item.counters.push(item_cnt2.index); + item_cnt2.ascounter.push(item.index); } + } - if (isFunc(this._complete_handler)) - this._complete_handler(this); + handle.arr.push(item); - if (this._draw_nodes_again) - this.startDrawGeometry(); // relaunch drawing - else - this._drawing_ready = true; // indicate that drawing is completed + // now one should add all other child branches + if (child_scan && !scanBranches(branch.fBranches, target_object, child_scan)) + return null; - return this; - }); + return item; } - /** @summary Returns true if geometry drawing is completed */ - isDrawingReady() { - return this._drawing_ready || false; + // main loop to add all branches from selector for reading + for (let nn = 0; nn < selector.numBranches(); ++nn) { + const item = addBranchForReading(selector.getBranch(nn), undefined, selector.nameOfBranch(nn), selector._directs[nn]); + + if (!item) { + selector.Terminate(false); + return Promise.reject(Error(`Fail to add branch ${selector.nameOfBranch(nn)}`)); + } } - /** @summary Remove already drawn node. Used by geom viewer */ - removeDrawnNode(nodeid) { - if (!this._draw_nodes) return; + // check if simple reading can be performed and there are direct data in branch - const new_nodes = []; + for (let h = 1; (h < handle.arr.length) && handle.simple_read; ++h) { + const item = handle.arr[h], item0 = handle.arr[0]; - for (let n = 0; n < this._draw_nodes.length; ++n) { - const entry = this._draw_nodes[n]; - if ((entry.nodeid === nodeid) || this._clones.isIdInStack(nodeid, entry.stack)) - this._clones.createObject3D(entry.stack, this._toplevel, 'delete_mesh'); - else - new_nodes.push(entry); + if ((item.numentries !== item0.numentries) || (item.numbaskets !== item0.numbaskets)) + handle.simple_read = false; + for (let n = 0; n < item.numbaskets; ++n) { + if (item.getBasketEntry(n) !== item0.getBasketEntry(n)) + handle.simple_read = false; } + } - if (new_nodes.length < this._draw_nodes.length) { - this._draw_nodes = new_nodes; - this.render3D(); - } + // now calculate entries range + + handle.firstentry = handle.lastentry = 0; + for (let nn = 0; nn < handle.arr.length; ++nn) { + const branch = handle.arr[nn].branch, + e1 = branch.fFirstEntry ?? (branch.fBasketBytes[0] ? branch.fBasketEntry[0] : 0); + handle.firstentry = Math.max(handle.firstentry, e1); + handle.lastentry = (nn === 0) ? (e1 + branch.fEntries) : Math.min(handle.lastentry, e1 + branch.fEntries); } - /** @summary Cleanup geometry painter */ - cleanup(first_time) { - if (!first_time) { - let can3d = 0; + if (handle.firstentry >= handle.lastentry) { + selector.Terminate(false); + return Promise.reject(Error('No any common events for selected branches')); + } - if (!this.superimpose) { - this.clearTopPainter(); // remove as pointer + handle.process_min = handle.firstentry; + handle.process_max = handle.lastentry; - if (this._on_pad) { - const fp = this.getFramePainter(); - if (fp?.mode3d) { - fp.clear3dCanvas(); - fp.mode3d = false; - } - } else - can3d = this.clear3dCanvas(); // remove 3d canvas from main HTML element + let resolveFunc, rejectFunc; // Promise methods + if (args.elist) { + args.firstentry = args.elist.at(0); + args.numentries = args.elist.at(-1) - args.elist.at(0) + 1; + handle.process_entries = args.elist; + handle.process_entries_indx = 0; + handle.process_arrays = false; // do not use arrays process for selected entries + } - disposeThreejsObject(this._scene); - } + if (Number.isInteger(args.firstentry) && (args.firstentry > handle.firstentry) && (args.firstentry < handle.lastentry)) + handle.process_min = args.firstentry; - this._toolbar?.cleanup(); // remove toolbar + handle.current_entry = handle.staged_now = handle.process_min; - disposeThreejsObject(this._full_geom); + if (Number.isInteger(args.numentries) && (args.numentries > 0)) + handle.process_max = Math.min(handle.process_max, handle.process_min + args.numentries); - this._controls?.cleanup(); + if (isFunc(selector.ProcessArrays) && handle.simple_read) { + // this is indication that selector can process arrays of values + // only strictly-matched tree structure can be used for that - if (this._context_menu) - this._renderer.domElement.removeEventListener('contextmenu', this._context_menu, false); + for (let k = 0; k < handle.arr.length; ++k) { + const elem = handle.arr[k]; + if ((elem.type <= 0) || (elem.type >= kOffsetL) || (elem.type === kCharStar)) + handle.process_arrays = false; + } - this._gui?.destroy(); + if (handle.process_arrays) { + // create other members for fast processing - this._worker?.terminate(); + selector.tgtarr = {}; // object with arrays - delete this._animating; + for (let nn = 0; nn < handle.arr.length; ++nn) { + const item = handle.arr[nn], + elem = createStreamerElement(item.name, item.type); - const obj = this.getGeometry(); - if (obj && this.ctrl.is_main) { - if (obj.$geo_painter === this) - delete obj.$geo_painter; - else if (obj.fVolume?.$geo_painter === this) - delete obj.fVolume.$geo_painter; - } + elem.fType = item.type + kOffsetL; + elem.fArrayLength = 10; + elem.fArrayDim = 1; + elem.fMaxIndex[0] = 10; // 10 if artificial number, will be replaced during reading - if (this._main_painter?._slave_painters) { - const pos = this._main_painter._slave_painters.indexOf(this); - if (pos >= 0) this._main_painter._slave_painters.splice(pos, 1); + item.arrmember = createMemberStreamer(elem, handle.file); } + } + } else + handle.process_arrays = false; - for (let k = 0; k < this._slave_painters?.length; ++k) { - const slave = this._slave_painters[k]; - if (slave?._main_painter === this) slave._main_painter = null; - } + /** read basket with tree data, selecting different files */ + function readBaskets(bitems) { + function extractPlaces() { + // extract places to read and define file name - delete this.geo_manager; - delete this._highlight_handlers; + const places = []; + let filename = ''; - super.cleanup(); + for (let n = 0; n < bitems.length; ++n) { + if (bitems[n].done) + continue; - delete this.ctrl; - delete this.options; + const branch = bitems[n].branch; - this.did_cleanup = true; + if (!places.length) + filename = branch.fFileName; + else if (filename !== branch.fFileName) + continue; - if (can3d < 0) this.selectDom().html(''); - } + bitems[n].selected = true; // mark which item was selected for reading - if (this._slave_painters) { - for (const k in this._slave_painters) { - const slave = this._slave_painters[k]; - slave._main_painter = null; - if (slave._clones === this._clones) slave._clones = null; + places.push(branch.fBasketSeek[bitems[n].basket], branch.fBasketBytes[bitems[n].basket]); } - } - this._main_painter = null; - this._slave_painters = []; - - if (this._render_resolveFuncs) { - this._render_resolveFuncs.forEach(func => func(this)); - delete this._render_resolveFuncs; + return places.length ? { places, filename } : null; } - if (!this.superimpose) - cleanupRender3D(this._renderer); + function readProgress(value) { + if ((handle.staged_prev === handle.staged_now) || (handle.process_max <= handle.process_min)) + return; - this.ensureBloom(false); - delete this._effectComposer; - - delete this._scene; - delete this._scene_size; - this._scene_width = 0; - this._scene_height = 0; - this._renderer = null; - this._toplevel = null; - delete this._full_geom; - delete this._fog; - delete this._camera; - delete this._camera0pos; - delete this._lookat; - delete this._selected_mesh; - - if (this._clones && this._clones_owner) - this._clones.cleanup(this._draw_nodes, this._build_shapes); - delete this._clones; - delete this._clones_owner; - delete this._draw_nodes; - delete this._drawing_ready; - delete this._build_shapes; - delete this._new_draw_nodes; - delete this._new_append_nodes; - delete this._last_camera_position; - - this.first_render_tm = 0; // time needed for first rendering - this.last_render_tm = 0; + const tm = new Date().getTime(); + if (tm - handle.progress_showtm < 500) + return; // no need to show very often + handle.progress_showtm = tm; - this.changeStage(stageInit, 'cleanup'); - delete this.drawing_log; + const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) / + (handle.process_max - handle.process_min); + return handle.selector.ShowProgress(portion); + } - delete this._gui; - delete this._controls; - delete this._context_menu; - delete this._toolbar; + function processBlobs(blobs, places) { + if (!blobs || ((places.length > 2) && (blobs.length * 2 !== places.length))) + return Promise.resolve(null); - delete this._worker; - } + if (places.length === 2) + blobs = [blobs]; - /** @summary perform resize */ - performResize(width, height) { - if ((this._scene_width === width) && (this._scene_height === height)) return false; - if ((width < 10) || (height < 10)) return false; + function doProcessing(k) { + for (; k < bitems.length; ++k) { + if (!bitems[k].selected) + continue; - this._scene_width = width; - this._scene_height = height; + bitems[k].selected = false; + bitems[k].done = true; - if (this._camera && this._renderer) { - if (this._camera.isPerspectiveCamera) - this._camera.aspect = this._scene_width / this._scene_height; - else if (this._camera.isOrthographicCamera) - this.adjustCameraPosition(true, true); - this._camera.updateProjectionMatrix(); - this._renderer.setSize(this._scene_width, this._scene_height, !this._fit_main_area); - this._effectComposer?.setSize(this._scene_width, this._scene_height); - this._bloomComposer?.setSize(this._scene_width, this._scene_height); + const blob = blobs.shift(); + let buf = new TBuffer(blob, 0, handle.file); + const basket = buf.classStreamer({}, clTBasket); - if (this.isStage(stageInit)) - this.render3D(); - } + if (basket.fNbytes !== bitems[k].branch.fBasketBytes[bitems[k].basket]) + console.error(`mismatch in read basket sizes ${basket.fNbytes} != ${bitems[k].branch.fBasketBytes[bitems[k].basket]}`); - return true; - } + // items[k].obj = basket; // keep basket object itself if necessary - /** @summary Check if HTML element was resized and drawing need to be adjusted */ - checkResize(arg) { - const cp = this.getCanvPainter(); + bitems[k].bskt_obj = basket; // only number of entries in the basket are relevant for the moment - // firefox is the only browser which correctly supports resize of embedded canvas, - // for others we should force canvas redrawing at every step - if (cp && !cp.checkCanvasResize(arg)) return false; + if (basket.fKeylen + basket.fObjlen === basket.fNbytes) { + // use data from original blob + buf.raw_shift = 0; - const sz = this.getSizeFor3d(); + bitems[k].raw = buf; // here already unpacked buffer - return this.performResize(sz.width, sz.height); - } + if (bitems[k].branch.fEntryOffsetLen > 0) + buf.readBasketEntryOffset(basket, buf.raw_shift); - /** @summary Toggle enlarge state */ - toggleEnlarge() { - if (this.enlargeMain('toggle')) - this.checkResize(); - } + continue; + } - /** @summary either change mesh wireframe or return current value - * @return undefined when wireframe cannot be accessed - * @private */ - accessObjectWireFrame(obj, on) { - if (!obj?.material) return; + // unpack data and create new blob + return R__unzip(blob, basket.fObjlen, false, buf.o).then(objblob => { + if (objblob) { + buf = new TBuffer(objblob, 0, handle.file); + buf.raw_shift = basket.fKeylen; + buf.fTagOffset = basket.fKeylen; + } else + throw new Error('FAIL TO UNPACK'); - if ((on !== undefined) && obj.stack) - obj.material.wireframe = on; + bitems[k].raw = buf; // here already unpacked buffer - return obj.material.wireframe; - } + if (bitems[k].branch.fEntryOffsetLen > 0) + buf.readBasketEntryOffset(basket, buf.raw_shift); - /** @summary handle wireframe flag change in GUI - * @private */ - changedWireFrame() { - this._scene?.traverse(obj => this.accessObjectWireFrame(obj, this.ctrl.wireframe)); + return doProcessing(k + 1); // continue processing + }); + } - this.render3D(); - } + const req = extractPlaces(); + if (req) + return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs2 => processBlobs(blobs2)).catch(() => null); - /** @summary Update object in geo painter */ - updateObject(obj) { - if ((obj === 'same') || !obj?._typename) - return false; - if (obj === this.getObject()) - return true; + return Promise.resolve(bitems); + } - let gm; - if (obj._typename === clTGeoManager) { - gm = obj; - obj = obj.fMasterVolume; + return doProcessing(0); } - if (obj._typename.indexOf(clTGeoVolume) === 0) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - - if (this.geo_manager && gm) { - this.geo_manager = gm; - this.assignObject(obj); - this._did_update = true; - return true; - } + const req = extractPlaces(); - if (!this.matchObjectType(obj._typename)) - return false; + // extract places where to read + if (req) + return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs => processBlobs(blobs, req.places)).catch(() => { return null; }); - this.assignObject(obj); - this._did_update = true; - return true; + return Promise.resolve(null); } - /** @summary Cleanup TGeo drawings */ - clearDrawings() { - if (this._clones && this._clones_owner) - this._clones.cleanup(this._draw_nodes, this._build_shapes); - delete this._clones; - delete this._clones_owner; - delete this._draw_nodes; - delete this._drawing_ready; - delete this._build_shapes; + let processBaskets = null; - delete this._extraObjects; - delete this._clipCfg; + function readNextBaskets() { + const bitems = [], max_ranges = tree.$file?.fMaxRanges || settings.MaxRanges, + select_entries = handle.process_entries !== undefined; + let total_size = 0, total_nsegm = 0, isany = true, is_direct = false, min_staged = handle.process_max; - // only remove all childs from top level object - disposeThreejsObject(this._toplevel, true); + while (isany && (total_size < settings.TreeReadBunchSize) && (!total_nsegm || (total_nsegm + handle.arr.length <= max_ranges))) { + isany = false; + // very important, loop over branches in reverse order + // let check counter branch after reading of normal branch is prepared + for (let n = handle.arr.length - 1; n >= 0; --n) { + const elem = handle.arr[n]; - this._full_redrawing = true; - } + while (elem.staged_basket < elem.numbaskets) { + const k = elem.staged_basket++, + bskt_emin = elem.getBasketEntry(k), + bskt_emax = k < elem.numbaskets - 1 ? elem.getBasketEntry(k + 1) : bskt_emin + 1e6; - /** @summary Redraw TGeo object inside TPad */ - redraw() { - if (this.superimpose) { - const cfg = getHistPainter3DCfg(this.getMainPainter()); + // first baskets can be ignored + if (bskt_emax <= handle.process_min) + continue; - if (cfg) { - this._toplevel.scale.set(cfg.scale_x ?? 1, cfg.scale_y ?? 1, cfg.scale_z ?? 1); - this._toplevel.position.set(cfg.offset_x ?? 0, cfg.offset_y ?? 0, cfg.offset_z ?? 0); - this._toplevel.updateMatrix(); - this._toplevel.updateMatrixWorld(); - } - } + // no need to read more baskets, process_max is not included + if (bskt_emin >= handle.process_max) + break; - if (this._did_update) - return this.startRedraw(); + if (elem.first_readentry < 0) { + // basket where reading will start + elem.curr_basket = k; - const main = this._on_pad ? this.getFramePainter() : null; - if (!main) - return Promise.resolve(false); - const sz = main.getSizeFor3d(main.access3dKind()); - main.apply3dSize(sz); - return this.performResize(sz.width, sz.height); - } + elem.first_readentry = elem.getBasketEntry(k); // remember which entry will be read first + } else if (select_entries) { + // all entries from process entries are analyzed + if (elem.eindx >= handle.process_entries.length) + break; - /** @summary Redraw TGeo object */ - redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) - return false; + // check if this basket required + if ((handle.process_entries[elem.eindx] < bskt_emin) || (handle.process_entries[elem.eindx] >= bskt_emax)) + continue; - return this.startRedraw(); - } + // when all previous baskets were processed, continue with selected + if (elem.curr_basket < 0) + elem.curr_basket = k; + } - /** @summary Start geometry redraw */ - startRedraw(tmout) { - if (tmout) { - if (this._redraw_timer) - clearTimeout(this._redraw_timer); - this._redraw_timer = setTimeout(() => this.startRedraw(), tmout); - return; - } + if (select_entries) { + // also check next entries which may belong to this basket + do + elem.eindx++; + while ((elem.eindx < handle.process_entries.length) && (handle.process_entries[elem.eindx] >= bskt_emin) && (handle.process_entries[elem.eindx] < bskt_emax)); - delete this._redraw_timer; - delete this._did_update; + // remember which baskets are required + elem.selected_baskets.push(k); + } - this.clearDrawings(); - const draw_obj = this.getGeometry(), - name_prefix = this.geo_manager ? draw_obj.fName : ''; - return this.prepareObjectDraw(draw_obj, name_prefix); - } + // check if basket already loaded in the branch - /** @summary draw TGeo object */ - static async draw(dom, obj, opt) { - if (!obj) return null; + const bitem = { + id: n, // to find which element we are reading + branch: elem.branch, + basket: k, + raw: null // here should be result + }, bskt = elem.branch.fBaskets.arr[k]; + if (bskt) { + bitem.raw = bskt.fBufferRef; + if (bitem.raw) + bitem.raw.locate(0); // reset pointer - same branch may be read several times + else + bitem.raw = new TBuffer(null, 0, handle.file); // create dummy buffer - basket has no data + bitem.raw.raw_shift = bskt.fKeylen; - let shape = null, extras = null, extras_path = '', is_eve = false; + if (bskt.fBufferRef && (elem.branch.fEntryOffsetLen > 0)) + bitem.raw.readBasketEntryOffset(bskt, bitem.raw.raw_shift); - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; obj = null; - } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) - shape = obj.fShape; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - shape = obj.fShape; is_eve = true; - } else if (obj._typename === clTGeoManager) - shape = obj.fMasterVolume.fShape; - else if (obj._typename === clTGeoOverlap) { - extras = obj.fMarker; extras_path = '/Marker'; - obj = buildOverlapVolume(obj); - if (!opt) opt = 'wire'; - } else if ('fVolume' in obj) { - if (obj.fVolume) shape = obj.fVolume.fShape; - } else - obj = null; + bitem.bskt_obj = bskt; + is_direct = true; + elem.baskets[k] = bitem; + } else { + bitems.push(bitem); + total_size += elem.branch.fBasketBytes[k]; + total_nsegm++; + isany = true; + } + elem.staged_entry = elem.getBasketEntry(k + 1); - if (isStr(opt) && opt.indexOf('comp') === 0 && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { - let maxlvl = 1; - opt = opt.slice(4); - if (opt[0] === 'x') { maxlvl = 999; opt = opt.slice(1) + '_vislvl999'; } - obj = buildCompositeVolume(shape, maxlvl); - } + min_staged = Math.min(min_staged, elem.staged_entry); - if (!obj && shape) { - obj = Object.assign(create$1(clTNamed), - { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); + break; + } + } } - if (!obj) return null; + if ((total_size === 0) && !is_direct) { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); + } - const painter = createGeoPainter(dom, obj, opt); + handle.staged_prev = handle.staged_now; + handle.staged_now = min_staged; - if (painter.ctrl.is_main && !obj.$geo_painter) - obj.$geo_painter = painter; + let portion = 0; + if (handle.process_max > handle.process_min) + portion = (handle.staged_prev - handle.process_min) / (handle.process_max - handle.process_min); - if (!painter.ctrl.is_main && painter.ctrl.project && obj.$geo_painter) { - painter._main_painter = obj.$geo_painter; - painter._main_painter._slave_painters.push(painter); + if (handle.selector.ShowProgress(portion) === 'break') { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); } - if (is_eve && (!painter.ctrl.vislevel || (painter.ctrl.vislevel < 9))) - painter.ctrl.vislevel = 9; + handle.progress_showtm = new Date().getTime(); - if (extras) { - painter._splitColors = true; - painter.addExtra(extras, extras_path); - } + if (total_size > 0) + return readBaskets(bitems).then(processBaskets); - return painter.loadMacro(painter.ctrl.script_name).then(arg => painter.prepareObjectDraw(arg.obj, arg.prefix)); + if (is_direct) + return processBaskets([]); // directly process baskets + + throw new Error('No any data is requested - never come here'); } -} // class TGeoPainter + processBaskets = function(bitems) { + // this is call-back when next baskets are read + if (handle.selector._break || (bitems === null)) { + handle.selector.Terminate(false); + return resolveFunc(handle.selector); + } -let add_settings = false; + // redistribute read baskets over branches + for (let n = 0; n < bitems.length; ++n) + handle.arr[bitems[n].id].baskets[bitems[n].basket] = bitems[n]; -/** @summary Create geo-related css entries - * @private */ -function injectGeoStyle() { - if (!add_settings && isFunc(internals.addDrawFunc)) { - add_settings = true; - // indication that draw and hierarchy is loaded, create css - internals.addDrawFunc({ name: clTEvePointSet, icon_get: getBrowserIcon, icon_click: browserIconClick }); - internals.addDrawFunc({ name: clTEveTrack, icon_get: getBrowserIcon, icon_click: browserIconClick }); - } + // now process baskets - function img(name, code) { - return `.jsroot .img_${name} { display: inline-block; height: 16px; width: 16px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ${code}'); }`; - } + let isanyprocessed = false; - injectStyle(` -${img('geoarb8', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB1SURBVBjTdY6rEYAwEETTy6lzK8/Fo+Jj18dTAjUgaQGfGiggtRDE8RtY93Zu514If2nzk2ux9c5TZkwXbiWTUavzws69oBfpYBrMT4r0Jhsw+QfRgQSw+CaKRsKsnV+SaF8MN49RBSgPUxO85PMl5n4tfGUH2gghs2uPAeQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geocombi', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAlUlEQVQoz5VQMQ4CMQyzEUNnBqT7Bo+4nZUH8gj+welWJsQDkHoCEYakTXMHSFiq2jqu4xRAEl2A7w4myWzpzCSZRZ658ldKu1hPnFsequBIc/hcLli3l52MAIANtpWrDsv8waGTW6BPuFtsdZArXyFuj33TQpazGEQF38phipnLgItxRcAoOeNpzv4PTXnC42fb//AGI5YqfQAU8dkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geocone', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACRSURBVBjTdY+xDcNACEVvEm/ggo6Olva37IB0C3iEzJABvAHFTXBDeJRwthMnUvylk44vPjxK+afeokX0flQhJO7L4pafSOMxzaxIKc/Tc7SIjNLyieyZSjBzc4DqMZI0HTMonWPBNlogOLeuewbg9c0hOiIqH7DKmTCuFykjHe4XOzQ58XVMGxzt575tKzd6AX9yMkcWyPlsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geogtra', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACCSURBVBjTVc+hDQMxDAVQD1FyqCQk0MwsCwQEG3+eCW6B0FvheDboFMGepTlVitPP/Cz5y0S/mNkw8pySU9INJDDH4vM4Usm5OrQXasXtkA+tQF+zxfcDY8EVwgNeiwmA37TEccK5oLOwQtuCj7BM2Fq7iGrxVqJbSsH+GzXs+798AThwKMh3/6jDAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geomedium', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFAAAAAAAAMDAww8PDWKj/////gICAG0/C4AAAAAF0Uk5TAEDm2GYAAAABYktHRAX4b+nHAAAACXBIWXMAAABIAAAASABGyWs+AAAAXElEQVQI102MwRGAMAgEuQ6IDwvQCjQdhAl/H7ED038JHhkd3dcOLAgESFARaAqnEB3yrj6QSEym1RbbOKinN+8q2Esui1GaX7VXSi4RUbxHRbER8X6O5Pg/fLgBBzMN8HfXD3AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geopara', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABtSURBVBjTY2DADq5MT7+CzD9kaKjp+QhJYIWqublhMbKAgpOnZxWSQJdsVJTndCSBKoWoAM/VSALpqlEBAYeQBKJAAsi2BGgCBZDdEWUYFZCOLFBlGOWJ7AyGFeaotjIccopageK3R12PGHABACTYHWd0tGw6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('georotation', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAiklEQVQoz2NgYGBgYGDg+A/BmIAFIvyDEbs0AwMTAwHACLPiB5QVBTdpGSOSCZjScDcgc4z+32BgYGBgEGIQw3QDLkdCTZD8/xJFeBfDVxQT/j9n/MeIrMCNIRBJwX8GRuzGM/yHKMAljeILNFOuMTyEisEUMKIqucrwB2oyIhyQpH8y/MZrLWkAAHFzIHIc0Q5yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotranslation', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABESURBVBjTY2DgYGAAYzjgAAIQgSLAgSwAAcrWUUCAJBAVhSpgBAQumALGCJPAAsriHIS0IAQ4UAU4cGphQBWwZSAOAADGJBKdZk/rHQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geotrd2', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABsSURBVBjTbY+xDcAwCARZx6UraiaAmpoRvIIb75PWI2QITxIiRQKk0CCO/xcA/NZ9LRs7RkJEYg3QxczUwoGsXiMAoe8lAelqRWFNKpiNXZLAalRDd0f3TMgeMckABKsCDmu+442RddeHz9cf9jUkW8smGn8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geovolume', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAB5QTFRFAAAAMDAw///Ay8uc/7+Q/4BgmJh4gIDgAAD/////CZb2ugAAAAF0Uk5TAEDm2GYAAAABYktHRAnx2aXsAAAACXBIWXMAAABIAAAASABGyWs+AAAAR0lEQVQI12NggAEBIBAEQgYGQUYQAyIGIhgwAZMSGCgwMJuEKimFOhswsKWAGG4JDGxJIBk1EEO9o6NIDVkEpgauC24ODAAASQ8Pkj/retYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoassembly', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAA9QTFRFAAAAMDAw/wAAAAD/////jEo0BQAAAAF0Uk5TAEDm2GYAAAABYktHRASPaNlRAAAACXBIWXMAAABIAAAASABGyWs+AAAAOklEQVQI12NggAFGRgEgEBRgEBSAMhgYGQQEgAR+oARGDIwCIAYjUL0A2DQQg9nY2ABVBKoGrgsDAADxzgNboMz8zQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geocomposite', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABuSURBVBjTY2AgF2hqgQCCr+0V4O7hFmgCF7CJyKysKkmxhfGNLaw9SppqAi2gfMuY5Agrl+ZaC6iAUXRJZX6Ic0klTMA5urapPFY5NRcmYKFqWl8S5RobBRNg0PbNT3a1dDGH8RlM3LysTRjIBwAG6xrzJt11BAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geoctub', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACESURBVBjTdc+xDcMwDARA7cKKHTuWX37LHaw+vQbQAJomA7j2DB7FhCMFCZB8pxPwJEv5kQcZW+3HencRBekak4aaMQIi8YJdAQ1CMeE0UBkuaLMETklQ9Alhka0JzzXWqLVBuQYPpWcVuBbZjZafNRYcDk9o/b07bvhINz+/zxu1/M0FSRcmAk/HaIcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geohype', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACKSURBVBjTbU+rFQQhDKQSDDISEYuMREfHx6eHKMpYuf5qoIQt5bgDblfcuJk3nySEhSvceDV3c/ejT66lspopE9pXyIlkCrHMBACpu1DClekQAREi/loviCnF/NhRwJLaQ6hVhPjB8bOCsjlnNnNl0FWJVWxAqGzHONRHpu5Ml+nQ+8GzNW9n+Is3eg80Nk0iiwoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geomixture', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFAAAAAAAAKysrVVUA//8B//8AgICAqqpV398gv79A////VYJtlwAAAAF0Uk5TAEDm2GYAAAABYktHRApo0PRWAAAACXBIWXMAAABIAAAASABGyWs+AAAAXklEQVQI12NgwASCQsJCgoZAhoADq1tKIJAhEpDGxpYIZKgxsLElgBhibAkOCY4gKTaGkPRGIEPUIYEBrEaAIY0tDawmgYWNgREkkjCVjRWkWCUhLY0FJCIIBljsBgCZTAykgaRiRwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geopcon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACJSURBVBjTdc+hGcQwCIZhhjl/rkgWiECj8XgGyAbZoD5LdIRMkEnKkV575n75Pp8AgLU54dmh6mauelyAL2Qzxfe2sklioq6FacFAcRFXYhwJHdU5rDD2hEYB/CmoJVRMiIJqgtENuoqA8ltAlYAqRH4d1tGkwzTqN2gA7Nv+fUwkgZ/3mg34txM+szzATJS1HQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geosphere', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACFSURBVBjTdY+xEcQwCAQp5QNFjpQ5vZACFBFTADFFfKYCXINzlUAJruXll2ekxDAEt9zcANFbXb2mqm56dxsymAH0yccAJaeNi0h5QGyfxGJmivMPjj0nmLsbRmyFCss3rlbpcUjfS8wLUNRcJyCF6uqg2IvYCnoKC7f1kSbA6riTz7evfwj3Ml+H3KBqAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotrap', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB5SURBVBjTbY+hFYAwDETZB1OJi4yNPp0JqjtAZ2AELL5DdABmIS2PtLxHXH7u7l2W5W+uHMHpGiCHLYR1yw4SCZMIXBOJWVSjK7QDDAu4g8OBmAKK4sAEDdR3rw8YmcUcrEijKKhl7lN1IQPn9ExlgU6/WEyc75+5AYK0KY5oHBDfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotubeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACBSURBVBjTdc+hEcQwDARA12P6QFBQ9LDwcXEVkA7SQTr4BlJBakgpsWdsh/wfux3NSCrlV86Mlrxmz1pBWq3bAHwETohxABVmDZADQp1BE+wDNnGywzHgmHDOreJNTDH3Xn3CVX0dpu2MHcIFBkYp/gKsQ8SCQ72V+36/+2aWf3kAQfgshnpXF0wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoxtru', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABcSURBVBjTY2AgEmhpeZV56vmWwQW00QUYwAJlSAI6XmVqukh8PT1bT03PchhXX09Pr9wQIQDiJ+ZowgWAXD3bck+QQDlCQTkDQgCoxA/ERBKwhbDglgA1lDMQDwCc/Rvq8nYsWgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geobbox', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTVc+hEYAwDAXQLlNRF1tVGxn9NRswQiSSCdgDyQBM0FlIIb2WuL77uf6E8E0N02wKYRwDciTKREVvB04GuZSyOMCABRB1WGzF3uDNQTvs/RcDtJXT4fSEXA5XoiQt0ttVSm8Co2psIOvoimjAOqBmFtH5wEP2373TPIvTK1nrpULXAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geoconeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB4SURBVBjTdc6hEcAgDAXQbFNZXHQkFlkd/30myAIMwAws0gmYpVzvoFyv/S5P/B+izzQ387ZA2pkDnvsU1SQLVIFrOM4JFmEaYp2gCQbmPEGODhJ8jt7Am47hwgrzInGAifa/elUZnQLY00iU30BZAV+BWi2VfnIBv1osbHH8jX0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoeltu', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTdY+hFYUwDEU7xq9CIXC4uNjY6KczQXeoYgVMR2ABRmCGjvIp/6dgiEruueedvBDuOR57LQnKyc8CJmKO+N8bieIUPtmBWjIIx8XDBHYCipsnql1g2D0UP2OoDqwBncf+RdZmzFMHizRjog7KZYzawd4Ay93lEAPWR7WAvNbwMl/XwSxBV8qCjgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geomaterial', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAbElEQVQoz62QMRbAIAhDP319Xon7j54qHSyCtaMZFCUkRjgDIdRU9yZUCfg8ut5aAHdcxtoNurmgA3ABNKIR9KimhSukPe2qxcCYC0pfFXx/aFWo7i42KKItOpopqvvnLzJmtlZTS7EfGAfwAM4EQbLIGV0sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geoparab', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbY+xDYAwDAQ9UAp3X7p0m9o9dUZgA9oMwAjpMwMzMAnYBAQSX9mn9+tN9KOtzsWsLOvYCziUGNX3nnCLJRzKPgeYrhPW7FJNLUB3YJazYKQKTnBaxgXRzNmJcrt7XCHQp9kEB1wfELEir/KGj4Foh8A+/zW1nf51AFabKZuWK+mNAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geopgon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABwSURBVBjTY2AgDlwAAzh3sX1sPRDEeuwDc+8V2dsHgQQ8LCzq74HkLSzs7Yva2tLt7S3sN4MNiDUGKQmysCi6BzWkzcI+PdY+aDPCljZlj1iFOUjW1tvHLjYuQhJIt5/DcAFZYLH9YnSn7iPST9gAACbsJth21haFAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotorus', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTjY+hFcMwDEQ9SkFggXGIoejhw+LiGkBDlHoAr+AhgjNL5byChuXeE7gvPelUyjOds/f5Zw0ggfj5KVCPMBWeyx+SbQ1XUriAC2XfpWWxjQQEZasRtRHiCUAj3qN4JaolUJppzh4q7dUTdHFXW/tH9OuswWm3nI7tc08+/eGLl758ey9KpKrNOQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geotrd1', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbc6xDQMhDAVQ9qH6lUtal65/zQ5IDMAMmYAZrmKGm4FJzlEQQUo+bvwkG4fwm9lbodV7w40Y4WGfSxQiXiJlQfZOjWRb8Ioi3tKuBQMCo7+9N72BzPsfAuoTdUP9QN8wgOQwvsfWmHzpeT5BKydMNW0nhJGvGf7mAc5WKO9e5N2dAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotube', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTRc+tEcAwCAXgLFNbWeSzSDQazw5doWNUZIOM0BEyS/NHy10E30HyklKvWnJ+0le3sJoKn3X2z7GRuvG++YRyMMDt0IIKUXMzxbnugJi5m9K1gNnGBOUFElAWGMaKIKI4xoQggl00gT+A9hXWgDwnfqgsHRAx2m+8bfjfdyrx5AtsSjpwu+M2RgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('evepoints', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABJQTFRF////n4mJcEdKRDMzcEdH////lLE/CwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAABIAAAASABGyWs+AAAAI0lEQVQI12NgIAowIpgKEJIZLiAgAKWZGQzQ9UGlWIizBQgAN4IAvGtVrTcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDktMDJUMTU6MDQ6MzgrMDI6MDDPyc7hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTA5LTAyVDE1OjA0OjM4KzAyOjAwvpR2XQAAAABJRU5ErkJggg==')} -${img('evetrack', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAqElEQVQoz32RMQrCQBBFf4IgSMB0IpGkMpVHCFh7BbHIGTyVhU0K8QYewEKsbVJZaCUiPAsXV8Puzhaz7H8zs5+JUDjikLilQr5zpCRl5xMXZNScQE5gSMGaz70jjUAJcw5c3UBMTsUe+9Kzf065SbropeLXimWfDIgoab/tOyPGzOhz53+oSWcSGh7UdB2ZNKXBZdgAuUdEKJYmrEILyVgG6pE2tEHgDfe42rbjYzSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTAyVDE1OjA0OjQ3KzAyOjAwM0S3EQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wOS0wMlQxNTowNDo0NyswMjowMEIZD60AAAAASUVORK5CYII=')} -.jsroot .geovis_this { background-color: lightgreen; } -.jsroot .geovis_daughters { background-color: lightblue; } -.jsroot .geovis_all { background-color: yellow; }`); -} + while (true) { + let loopentries = 100000000, n, elem; + // first loop used to check if all required data exists + for (n = 0; n < handle.arr.length; ++n) { + elem = handle.arr[n]; -/** @summary Create geo painter - * @private */ -function createGeoPainter(dom, obj, opt) { - injectGeoStyle(); + if (!elem.raw || !elem.basket || (elem.first_entry + elem.basket.fNevBuf <= handle.current_entry)) { + delete elem.raw; + delete elem.basket; + + if ((elem.curr_basket >= elem.numbaskets)) { + if (n === 0) { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); + } + continue; // ignore non-master branch + } + + // this is single response from the tree, includes branch, basket number, raw data + const bitem = elem.baskets[elem.curr_basket]; + + // basket not read + if (!bitem) { + // no data, but no any event processed - problem + if (!isanyprocessed) { + handle.selector.Terminate(false); + return rejectFunc(Error(`no data for ${elem.branch.fName} basket ${elem.curr_basket}`)); + } + + // try to read next portion of tree data + return readNextBaskets(); + } + + elem.raw = bitem.raw; + elem.basket = bitem.bskt_obj; + // elem.nev = bitem.fNevBuf; // number of entries in raw buffer + elem.first_entry = elem.getBasketEntry(bitem.basket); - geoCfg('GradPerSegm', settings.GeoGradPerSegm); - geoCfg('CompressComp', settings.GeoCompressComp); + bitem.raw = null; // remove reference on raw buffer + bitem.branch = null; // remove reference on the branch + bitem.bskt_obj = null; // remove reference on the branch + elem.baskets[elem.curr_basket++] = undefined; // remove from array - const painter = new TGeoPainter(dom, obj); + if (handle.process_entries !== undefined) { + elem.selected_baskets.shift(); + // -1 means that basket is not yet found for following entries + elem.curr_basket = elem.selected_baskets.length ? elem.selected_baskets[0] : -1; + } + } - painter.decodeOptions(opt); // indicator of initialization + // define how much entries can be processed before next raw buffer will be finished + loopentries = Math.min(loopentries, elem.first_entry + elem.basket.fNevBuf - handle.current_entry); + } - return painter; -} + // second loop extracts all required data + // do not read too much + if (handle.current_entry + loopentries > handle.process_max) + loopentries = handle.process_max - handle.current_entry; -/** @summary provide menu for geo object - * @private */ -function provideMenu(menu, item, hpainter) { - if (!item._geoobj) return false; + if (handle.process_arrays && (loopentries > 1)) { + // special case - read all data from baskets as arrays - const obj = item._geoobj, vol = item._volume, - iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)); + for (n = 0; n < handle.arr.length; ++n) { + elem = handle.arr[n]; - if (!vol && !iseve) return false; + elem.getEntry(handle.current_entry); - menu.add('separator'); + elem.arrmember.arrlength = loopentries; + elem.arrmember.func(elem.raw, handle.selector.tgtarr); - const scanEveVisible = (obj, arg, skip_this) => { - if (!arg) arg = { visible: 0, hidden: 0 }; + elem.raw = null; + } - if (!skip_this) { - if (arg.assign !== undefined) - obj.fRnrSelf = arg.assign; - else if (obj.fRnrSelf) - arg.vis++; - else - arg.hidden++; - } + handle.selector.ProcessArrays(handle.current_entry); - if (obj.fElements) { - for (let n = 0; n < obj.fElements.arr.length; ++n) - scanEveVisible(obj.fElements.arr[n], arg, false); - } + handle.current_entry += loopentries; - return arg; - }, toggleEveVisibility = arg => { - if (arg === 'self') { - obj.fRnrSelf = !obj.fRnrSelf; - item._icon = item._icon.split(' ')[0] + provideVisStyle(obj); - hpainter.updateTreeNode(item); - } else { - scanEveVisible(obj, { assign: (arg === 'true') }, true); - hpainter.forEachItem(m => { - // update all child items - if (m._geoobj && m._icon) { - m._icon = item._icon.split(' ')[0] + provideVisStyle(m._geoobj); - hpainter.updateTreeNode(m); - } - }, item); - } + isanyprocessed = true; + } else { + // main processing loop + while (loopentries > 0) { + for (n = 0; n < handle.arr.length; ++n) { + elem = handle.arr[n]; - findItemWithPainter(item, 'testGeomChanges'); - }, toggleMenuBit = arg => { - toggleGeoBit(vol, arg); - const newname = item._icon.split(' ')[0] + provideVisStyle(vol); - hpainter.forEachItem(m => { - // update all items with that volume - if (item._volume === m._volume) { - m._icon = newname; - hpainter.updateTreeNode(m); - } - }); + // locate buffer offset at proper place + elem.getEntry(handle.current_entry); - hpainter.updateTreeNode(item); - findItemWithPainter(item, 'testGeomChanges'); - }, drawitem = findItemWithPainter(item), - fullname = drawitem ? hpainter.itemFullName(item, drawitem) : ''; + elem.member.func(elem.raw, elem.getTarget(handle.selector.tgtobj)); + } - if ((item._geoobj._typename.indexOf(clTGeoNode) === 0) && drawitem) { - menu.add('Focus', () => { - if (drawitem && isFunc(drawitem._painter?.focusOnItem)) - drawitem._painter.focusOnItem(fullname); - }); - } + handle.selector.Process(handle.current_entry); - if (iseve) { - menu.addchk(obj.fRnrSelf, 'Visible', 'self', toggleEveVisibility); - const res = scanEveVisible(obj, undefined, true); - if (res.hidden + res.visible > 0) - menu.addchk((res.hidden === 0), 'Daughters', res.hidden !== 0 ? 'true' : 'false', toggleEveVisibility); - } else { - const stack = drawitem?._painter?._clones?.findStackByName(fullname), - phys_vis = stack ? drawitem._painter._clones.getPhysNodeVisibility(stack) : null, - is_visible = testGeoBit(vol, geoBITS.kVisThis); + isanyprocessed = true; - menu.addchk(testGeoBit(vol, geoBITS.kVisNone), 'Invisible', geoBITS.kVisNone, toggleMenuBit); - if (stack) { - const changePhysVis = arg => { - drawitem._painter._clones.setPhysNodeVisibility(stack, (arg === 'off') ? false : arg); - findItemWithPainter(item, 'testGeomChanges'); - }; + if (handle.process_entries) { + handle.process_entries_indx++; + if (handle.process_entries_indx >= handle.process_entries.length) { + handle.current_entry++; + loopentries = 0; + } else { + const next_entry = handle.process_entries[handle.process_entries_indx], + diff = next_entry - handle.current_entry; + handle.current_entry = next_entry; + loopentries -= diff; + } + } else { + handle.current_entry++; + loopentries--; + } + } + } - menu.add('sub:Physical vis', 'Physical node visibility - only for this instance'); - menu.addchk(phys_vis?.visible, 'on', 'on', changePhysVis, 'Enable visibility of phys node'); - menu.addchk(phys_vis && !phys_vis.visible, 'off', 'off', changePhysVis, 'Disable visibility of physical node'); - menu.add('reset', 'clear', changePhysVis, 'Reset custom visibility of physical node'); - menu.add('reset all', 'clearall', changePhysVis, 'Reset all custom settings for all nodes'); - menu.add('endsub:'); + if (handle.current_entry >= handle.process_max) { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); + } } + }; - menu.addchk(is_visible, 'Logical vis', - geoBITS.kVisThis, toggleMenuBit, 'Logical node visibility - all instances'); - menu.addchk(testGeoBit(vol, geoBITS.kVisDaughters), 'Daughters', - geoBITS.kVisDaughters, toggleMenuBit, 'Logical node daugthers visibility'); - } + return new Promise((resolve, reject) => { + resolveFunc = resolve; + rejectFunc = reject; - return true; + // call begin before first entry is read + handle.selector.Begin(tree); + + readNextBaskets(); + }); } -/** @summary handle click on browser icon - * @private */ -function browserIconClick(hitem, hpainter) { - if (hitem._volume) { - if (hitem._more && hitem._volume.fNodes?.arr?.length) - toggleGeoBit(hitem._volume, geoBITS.kVisDaughters); - else - toggleGeoBit(hitem._volume, geoBITS.kVisThis); +/** @summary implementation of TTree::Draw + * @param {object|string} args - different setting or simply draw expression + * @param {string} args.expr - draw expression + * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') + * @param {string} [args.drawopt=undefined] - draw options for result histogram + * @param {number} [args.firstentry=0] - first entry to process + * @param {number} [args.numentries=undefined] - number of entries to process, all by default + * @param {Array} [args.elist=undefined] - array of entries id to process, all by default + * @param {Boolean} [args.dump] - dump values of expression instead creating histogram + * @param {Boolean} [args.dump_entries] - if array of entries should be return which match cut condition + * @param {Boolean} [args.staged] - staged processing, first apply cut to select entries and then perform drawing for selected entries + * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing + * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument + * @return {Promise} with produced object */ +async function treeDraw(tree, args) { + if (isStr(args)) + args = { expr: args }; - updateBrowserIcons(hitem._volume, hpainter); + if (!isStr(args.expr)) + args.expr = ''; - findItemWithPainter(hitem, 'testGeomChanges'); - return false; // no need to update icon - we did it ourself - } + if (!args.SelectorClass) + args.SelectorClass = TDrawSelector; + if (!args.processFunction) + args.processFunction = treeProcess; - if (hitem._geoobj && ((hitem._geoobj._typename === clTEveGeoShapeExtract) || (hitem._geoobj._typename === clREveGeoShapeExtract))) { - hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf; + const selector = new args.SelectorClass(); - updateBrowserIcons(hitem._geoobj, hpainter); - findItemWithPainter(hitem, 'testGeomChanges'); - return false; // no need to update icon - we did it ourself - } + if (args.branch) { + if (!selector.drawOnlyBranch(tree, args.branch, args.expr, args)) + return Promise.reject(Error(`Fail to create draw expression ${args.expr} for branch ${args.branch.fName}`)); + } else if (!selector.parseDrawExpression(tree, args)) + return Promise.reject(Error(`Fail to create draw expression ${args.expr}`)); - // first check that geo painter assigned with the item - const drawitem = findItemWithPainter(hitem), - newstate = drawitem?._painter?.extraObjectVisible(hpainter, hitem, true); + selector.setCallback(null, args.progress); - // return true means browser should update icon for the item - return newstate !== undefined; -} + return args.processFunction(tree, selector, args).then(sel => { + if (!args.staged) + return sel; + delete args.dump_entries; -/** @summary Get icon for the browser - * @private */ -function getBrowserIcon(hitem, hpainter) { - let icon = ''; - switch (hitem._kind) { - case prROOT + clTEveTrack: icon = 'img_evetrack'; break; - case prROOT + clTEvePointSet: icon = 'img_evepoints'; break; - case prROOT + clTPolyMarker3D: icon = 'img_evepoints'; break; - } - if (icon) { - const drawitem = findItemWithPainter(hitem); - if (drawitem?._painter?.extraObjectVisible(hpainter, hitem)) - icon += ' geovis_this'; - } - return icon; + const selector2 = new args.SelectorClass(), + args2 = Object.assign({}, args); + args2.staged = false; + args2.elist = sel.hist; // assign entries found in first selection + if (!selector2.createDrawExpression(tree, args.staged_names, '', args2)) + return Promise.reject(Error(`Fail to create final draw expression ${args.expr}`)); + ['arr_limit', 'htype', 'nmatch', 'want_hist', 'hist_nbins', 'hist_name', 'hist_args', 'draw_title'] + .forEach(name => { selector2[name] = selector[name]; }); + return args.processFunction(tree, selector2, args2); + }).then(sel => sel.hist); } - -/** @summary create hierarchy item for geo object +/** @summary Performs generic I/O test for all branches in the TTree + * @desc Used when 'testio' draw option for TTree is specified * @private */ -function createItem(node, obj, name) { - const sub = { - _kind: prROOT + obj._typename, - _name: name || getObjectName(obj), - _title: obj.fTitle, - _parent: node, - _geoobj: obj, - _get(item /* ,itemname */) { - // mark object as belong to the hierarchy, require to - if (item._geoobj) item._geoobj.$geoh = true; - return Promise.resolve(item._geoobj); - } - }; - let volume, shape, subnodes, iseve = false; +function treeIOTest(tree, args) { + const branches = [], names = [], nchilds = []; - if (obj._typename === 'TGeoMaterial') - sub._icon = 'img_geomaterial'; - else if (obj._typename === 'TGeoMedium') - sub._icon = 'img_geomedium'; - else if (obj._typename === 'TGeoMixture') - sub._icon = 'img_geomixture'; - else if ((obj._typename.indexOf(clTGeoNode) === 0) && obj.fVolume) { - sub._title = 'node:' + obj._typename; - if (obj.fTitle) sub._title += ' ' + obj.fTitle; - volume = obj.fVolume; - } else if (obj._typename.indexOf(clTGeoVolume) === 0) - volume = obj; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - iseve = true; - shape = obj.fShape; - subnodes = obj.fElements ? obj.fElements.arr : null; - } else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) - shape = obj; + function collectBranches(obj, prntname = '') { + if (!obj?.fBranches) + return 0; - if (volume) { - shape = volume.fShape; - subnodes = volume.fNodes ? volume.fNodes.arr : null; - } + let cnt = 0; - if (volume || shape || subnodes) { - if (volume) sub._volume = volume; + for (let n = 0; n < obj.fBranches.arr.length; ++n) { + const br = obj.fBranches.arr[n], + name = (prntname ? prntname + '/' : '') + br.fName; + branches.push(br); + names.push(name); + nchilds.push(0); + const pos = nchilds.length - 1; + cnt += (br.fLeaves?.arr?.length ?? 0); + const nchld = collectBranches(br, name); - if (subnodes) { - sub._more = true; - sub._expand = expandGeoObject; - } else if (shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { - sub._more = true; - sub._shape = shape; - sub._expand = function(node /*, obj */) { - createItem(node, node._shape.fNode.fLeft, 'Left'); - createItem(node, node._shape.fNode.fRight, 'Right'); - return true; - }; + cnt += nchld; + nchilds[pos] = nchld; } + return cnt; + } - if (!sub._title && (obj._typename !== clTGeoVolume)) - sub._title = obj._typename; + const numleaves = collectBranches(tree); + let selector; - if (shape) { - if (sub._title === '') - sub._title = shape._typename; + names.push(`Total are ${branches.length} branches with ${numleaves} leaves`); - sub._icon = getShapeIcon(shape); - } else - sub._icon = sub._more ? 'img_geocombi' : 'img_geobbox'; + function testBranch(nbr) { + if (nbr >= branches.length) + return Promise.resolve(true); - if (volume) - sub._icon += provideVisStyle(volume); - else if (iseve) - sub._icon += provideVisStyle(obj); + if (selector?._break || args._break) + return Promise.resolve(true); - sub._menu = provideMenu; - sub._icon_click = browserIconClick; - } + selector = new TSelector(); - if (!node._childs) node._childs = []; + selector.addBranch(branches[nbr], 'br0'); - if (!sub._name) { - if (isStr(node._name)) { - sub._name = node._name; - if (sub._name.lastIndexOf('s') === sub._name.length-1) - sub._name = sub._name.slice(0, sub._name.length-1); - sub._name += '_' + node._childs.length; - } else - sub._name = 'item_' + node._childs.length; - } + selector.Process = function() { + if (this.tgtobj.br0 === undefined) + this.fail = true; + }; - node._childs.push(sub); + selector.Terminate = function(res) { + if (!isStr(res)) + res = (!res || this.fails) ? 'FAIL' : 'ok'; - return sub; -} + names[nbr] = res + ' ' + names[nbr]; + }; -/** @summary Draw dummy geometry - * @private */ -async function drawDummy3DGeom(painter) { - const shape = create$1(clTNamed); - shape._typename = clTGeoBBox; - shape.fDX = 1e-10; - shape.fDY = 1e-10; - shape.fDZ = 1e-10; - shape.fShapeId = 1; - shape.fShapeBits = 0; - shape.fOrigin = [0, 0, 0]; + const br = branches[nbr], + object_class = getBranchObjectClass(br, tree), + num = br.fEntries, + skip_branch = object_class ? (nchilds[nbr] > 100) : !br.fLeaves?.arr?.length; - const obj = Object.assign(create$1(clTNamed), - { _typename: clTEveGeoShapeExtract, - fTrans: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], - fShape: shape, fRGBA: [0, 0, 0, 0], fElements: null, fRnrSelf: false }), - pp = painter.getPadPainter(), - opt = (pp?.pad?.fFillColor && (pp?.pad?.fFillStyle > 1000)) ? 'bkgr_' + pp.pad.fFillColor : ''; + if (skip_branch || (num <= 0)) + return testBranch(nbr + 1); - return TGeoPainter.draw(painter.getDom(), obj, opt) - .then(geop => { geop._dummy = true; return geop; }); -} + const drawargs = { numentries: 10 }, + first = br.fFirstEntry || 0, + last = br.fEntryNumber || (first + num); -/** @summary Direct draw function for TAxis3D - * @private */ -function drawAxis3D() { - const main = this.getMainPainter(); + if (num < drawargs.numentries) + drawargs.numentries = num; + else + drawargs.firstentry = first + Math.round((last - first - drawargs.numentries) * Math.random()); // select randomly - if (isFunc(main?.setAxesDraw)) - return main.setAxesDraw(true); + // keep console output for debug purposes + console.log(`test branch ${br.fName} first ${drawargs.firstentry || 0} num ${drawargs.numentries}`); - console.error('no geometry painter found to toggle TAxis3D drawing'); -} + if (isFunc(args.showProgress)) + args.showProgress(`br ${nbr}/${branches.length} ${br.fName}`); -/** @summary Build three.js model for given geometry object - * @param {Object} obj - TGeo-related object - * @param {Object} [opt] - options - * @param {Number} [opt.vislevel] - visibility level like TGeoManager, when not specified - show all - * @param {Number} [opt.numnodes=1000] - maximal number of visible nodes - * @param {Number} [opt.numfaces=100000] - approx maximal number of created triangles - * @param {Number} [opt.instancing=-1] - <0 disable use of InstancedMesh, =0 only for large geometries, >0 enforce usage of InstancedMesh - * @param {boolean} [opt.doubleside=false] - use double-side material - * @param {boolean} [opt.wireframe=false] - show wireframe for created shapes - * @param {boolean} [opt.transparency=0] - make nodes transparent - * @param {boolean} [opt.dflt_colors=false] - use default ROOT colors - * @param {boolean} [opt.set_names=true] - set names to all Object3D instances - * @param {boolean} [opt.set_origin=false] - set TGeoNode/TGeoVolume as Object3D.userData - * @return {object} Object3D with created model - * @example - * import { build } from 'https://root.cern/js/latest/modules/geom/TGeoPainter.mjs'; - * let obj3d = build(obj); - * // this is three.js object and can be now inserted in the scene - */ -function build(obj, opt) { - if (!obj) return null; + return treeProcess(tree, selector, drawargs).then(() => testBranch(nbr + 1)); + } + + return testBranch(0).then(() => { + if (isFunc(args.showProgress)) + args.showProgress(); - if (!opt) opt = {}; - if (!opt.numfaces) opt.numfaces = 100000; - if (!opt.numnodes) opt.numnodes = 1000; - if (!opt.frustum) opt.frustum = null; + return names; + }); +} - opt.res_mesh = opt.res_faces = 0; +/** @summary Create hierarchy of TTree object + * @private */ +function treeHierarchy(tree_node, obj) { + function createBranchItem(node, branch, tree, parent_branch) { + if (!node || !branch) + return false; - if (opt.instancing === undefined) - opt.instancing = -1; + const nb_branches = branch.fBranches?.arr?.length ?? 0, + nb_leaves = branch.fLeaves?.arr?.length ?? 0; - opt.info = { num_meshes: 0, num_faces: 0 }; + function ClearName(arg) { + const pos = arg.indexOf('['); + if (pos > 0) + arg = arg.slice(0, pos); + if (parent_branch && arg.indexOf(parent_branch.fName) === 0) { + arg = arg.slice(parent_branch.fName.length); + if (arg[0] === '.') + arg = arg.slice(1); + } + return arg; + } - let clones = null, visibles = null; + branch.$tree = tree; // keep tree pointer, later do it more smart - if (obj.visibles && obj.nodes && obj.numnodes) { - // case of draw message from geometry viewer + const subitem = { + _name: ClearName(branch.fName), + _kind: getKindForType(branch._typename), + _title: branch.fTitle, + _obj: branch + }; - const nodes = obj.numnodes > 1e6 ? { length: obj.numnodes } : new Array(obj.numnodes); + if (!node._childs) + node._childs = []; - obj.nodes.forEach(node => { - nodes[node.id] = ClonedNodes.formatServerElement(node); - }); + node._childs.push(subitem); - clones = new ClonedNodes(null, nodes); - clones.name_prefix = clones.getNodeName(0); + if (branch._typename === clTBranchElement) + subitem._title += ` from ${branch.fClassName};${branch.fClassVersion}`; - // normally only need when making selection, not used in geo viewer - // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo_clones.setVisLevel(draw_msg.vislevel); - // TODO: provide from server - clones.maxdepth = 20; + if (nb_branches > 0) { + subitem._more = true; + subitem._expand = function(bnode, bobj) { + // really create all sub-branch items + if (!bobj) + return false; - const nsegm = obj.cfg?.nsegm || 30; + if (!bnode._childs) + bnode._childs = []; - for (let cnt = 0; cnt < obj.visibles.length; ++cnt) { - const item = obj.visibles[cnt], rd = item.ri; + if ((bobj.fLeaves?.arr?.length === 1) && + ((bobj.fType === kClonesNode) || (bobj.fType === kSTLNode))) { + bobj.fLeaves.arr[0].$branch = bobj; + bnode._childs.push({ + _name: '@size', + _title: 'container size', + _kind: getKindForType('TLeafElement'), + _icon: 'img_leaf', + _obj: bobj.fLeaves.arr[0], + _more: false + }); + } - // entry may be provided without shape - it is ok - if (rd) - item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); - } + for (let i = 0; i < bobj.fBranches.arr.length; ++i) + createBranchItem(bnode, bobj.fBranches.arr[i], bobj.$tree, bobj); - visibles = obj.visibles; - } else { - let shape = null, hide_top = false; + const object_class = getBranchObjectClass(bobj, bobj.$tree, true), + methods = object_class ? getMethods(object_class) : null; - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; obj = null; - } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) - shape = obj.fShape; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) - shape = obj.fShape; - else if (obj._typename === clTGeoManager) { - obj = obj.fMasterVolume; - hide_top = !opt.showtop; - shape = obj.fShape; - } else if (obj.fVolume) - shape = obj.fVolume.fShape; - else - obj = null; + if (methods && bobj.fBranches.arr.length) { + for (const key in methods) { + if (!isFunc(methods[key])) + continue; + const s = methods[key].toString(); + if ((s.indexOf('return') > 0) && (s.indexOf('function ()') === 0)) { + bnode._childs.push({ + _name: key + '()', + _title: `function ${key} of class ${object_class}`, + _kind: getKindForType(clTBranchFunc), // fictional class, only for drawing + _obj: { _typename: clTBranchFunc, branch: bobj, func: key }, + _more: false + }); + } + } + } + return true; + }; + return true; + } else if (nb_leaves === 1) { + subitem._icon = 'img_leaf'; + subitem._more = false; + } else if (nb_leaves > 1) { + subitem._childs = []; + for (let j = 0; j < nb_leaves; ++j) { + branch.fLeaves.arr[j].$branch = branch; // keep branch pointer for drawing + const leafitem = { + _name: ClearName(branch.fLeaves.arr[j].fName), + _kind: getKindForType(branch.fLeaves.arr[j]._typename), + _obj: branch.fLeaves.arr[j] + }; + subitem._childs.push(leafitem); + } + } - if (opt.composite && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) - obj = buildCompositeVolume(shape); + return true; + } - if (!obj && shape) - obj = Object.assign(create$1(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); + // protect against corrupted TTree objects + if (obj.fBranches === undefined) + return false; - if (!obj) return null; + tree_node._childs = []; + tree_node._tree = obj; // set reference, will be used later by TTree::Draw - if (obj._typename.indexOf(clTGeoVolume) === 0) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + obj.fBranches?.arr.forEach(branch => createBranchItem(tree_node, branch, obj)); - clones = new ClonedNodes(obj); - clones.setVisLevel(opt.vislevel); - clones.setMaxVisNodes(opt.numnodes); + return true; +} - if (opt.dflt_colors) - clones.setDefaultColors(true); +var tree = /*#__PURE__*/Object.freeze({ +__proto__: null, +TDrawSelector: TDrawSelector, +TDrawVariable: TDrawVariable, +TSelector: TSelector, +clTBranchFunc: clTBranchFunc, +kClonesNode: kClonesNode, +kSTLNode: kSTLNode, +treeDraw: treeDraw, +treeHierarchy: treeHierarchy, +treeIOTest: treeIOTest, +treeProcess: treeProcess +}); - const uniquevis = opt.no_screen ? 0 : clones.markVisibles(true); - if (uniquevis <= 0) - clones.markVisibles(false, false, hide_top); - else - clones.markVisibles(true, true, hide_top); // copy bits once and use normal visibility bits +/** @license + * + * jsPDF - PDF Document creation from JavaScript + * Version 3.0.3 + * + * Copyright (c) 2010-2025 James Hall , https://github.com/MrRio/jsPDF + * 2015-2025 yWorks GmbH, http://www.yworks.com + * 2015-2025 Lukas Holländer , https://github.com/HackbrettXXX + * 2016-2018 Aras Abbasi + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Steven Spungin, https://github.com/Flamenco + * 2014 Kenneth Glassey, https://github.com/Gavvers + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, Flamenco + */ - clones.produceIdShifts(); +const globalObject = globalThis; - // collect visible nodes - const res = clones.collectVisibles(opt.numfaces, opt.frustum); +/** + * A class to parse color values + * @author Stoyan Stefanov + * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} + * @license Use it if you like it + */ - visibles = res.lst; - } +function RGBColor$1(color_string) { + color_string = color_string || ""; + this.ok = false; - if (!opt.material_kind) - opt.material_kind = 'lambert'; - if (opt.set_names === undefined) - opt.set_names = true; + // strip any leading # + if (color_string.charAt(0) == "#") { + // remove # if any + color_string = color_string.substr(1, 6); + } - clones.setConfig(opt); + color_string = color_string.replace(/ /g, ""); + color_string = color_string.toLowerCase(); + + var channels; + + // before getting into regexps, try simple matches + // and overwrite the input + var simple_colors = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "00ffff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000000", + blanchedalmond: "ffebcd", + blue: "0000ff", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "00ffff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dodgerblue: "1e90ff", + feldspar: "d19275", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "ff00ff", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgrey: "d3d3d3", + lightgreen: "90ee90", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslateblue: "8470ff", + lightslategray: "778899", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "00ff00", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "ff00ff", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370d8", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "d87093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + red: "ff0000", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + violetred: "d02090", + wheat: "f5deb3", + white: "ffffff", + whitesmoke: "f5f5f5", + yellow: "ffff00", + yellowgreen: "9acd32" + }; + color_string = simple_colors[color_string] || color_string; - // collect shapes - const shapes = clones.collectShapes(visibles); + // array of color definition objects + var color_defs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ["rgb(123, 234, 45)", "rgb(255,234,245)"], + process: function(bits) { + return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ["#00ff00", "336699"], + process: function(bits) { + return [ + parseInt(bits[1], 16), + parseInt(bits[2], 16), + parseInt(bits[3], 16) + ]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ["#fb0", "f0f"], + process: function(bits) { + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; - clones.buildShapes(shapes, opt.numfaces); + // search through the definitions to find a match + for (var i = 0; i < color_defs.length; i++) { + var re = color_defs[i].re; + var processor = color_defs[i].process; + var bits = re.exec(color_string); + if (bits) { + channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + } - const toplevel = new Object3D(); - toplevel.clones = clones; // keep reference on JSROOT data + // validate/cleanup values + this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r; + this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g; + this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; - const colors = getRootColors(); + // some getters + this.toRGB = function() { + return "rgb(" + this.r + ", " + this.g + ", " + this.b + ")"; + }; + this.toHex = function() { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) r = "0" + r; + if (g.length == 1) g = "0" + g; + if (b.length == 1) b = "0" + b; + return "#" + r + g + b; + }; +} - if (clones.createInstancedMeshes(opt, toplevel, visibles, shapes, colors)) - return toplevel; +let atob$1, btoa$1; - for (let n = 0; n < visibles.length; ++n) { - const entry = visibles[n]; - if (entry.done) continue; +if ((typeof process === 'object') && (typeof process.versions === 'object') && process.versions.node && process.versions.v8) { + atob$1 = str => Buffer.from(str, 'base64').toString('latin1'); + btoa$1 = str => Buffer.from(str, 'latin1').toString('base64'); +} else { + atob$1 = globalThis.atob; + btoa$1 = globalThis.btoa; +} - const shape = entry.server_shape || shapes[entry.shapeid]; - if (!shape.ready) { - console.warn('shape marked as not ready when it should'); - break; - } +function consoleLog() { + if (globalObject.console && typeof globalObject.console.log === "function") { + globalObject.console.log.apply(globalObject.console, arguments); + } +} - clones.createEntryMesh(opt, toplevel, entry, shape, colors); - } +function consoleWarn(str) { + if (globalObject.console) { + if (typeof globalObject.console.warn === "function") { + globalObject.console.warn.apply(globalObject.console, arguments); + } else { + consoleLog.call(null, arguments); + } + } +} - return toplevel; +function consoleError(str) { + if (globalObject.console) { + if (typeof globalObject.console.error === "function") { + globalObject.console.error.apply(globalObject.console, arguments); + } else { + consoleLog(str); + } + } } +var console$1 = { + log: consoleLog, + warn: consoleWarn, + error: consoleError +}; -var TGeoPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -ClonedNodes: ClonedNodes, -GeoDrawingControl: GeoDrawingControl, -TGeoPainter: TGeoPainter, -build: build, -createGeoPainter: createGeoPainter, -drawAxis3D: drawAxis3D, -drawDummy3DGeom: drawDummy3DGeom, -expandGeoObject: expandGeoObject, -produceRenderOrder: produceRenderOrder -}); +/** + * @license + * Joseph Myers does not specify a particular license for his work. + * + * Author: Joseph Myers + * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js + * + * Modified by: Owen Leong + */ -const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObject', - clTStreamerSTL = 'TStreamerSTL', clTStreamerInfoList = 'TStreamerInfoList', - clTDirectory = 'TDirectory', clTDirectoryFile = 'TDirectoryFile', - clTQObject = 'TQObject', clTBasket = 'TBasket', clTDatime = 'TDatime', - nameStreamerInfo = 'StreamerInfo', +function md5cycle(x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); +} + +function cmn(q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); +} + +function ff(a, b, c, d, x, s, t) { + return cmn((b & c) | (~b & d), a, b, x, s, t); +} + +function gg(a, b, c, d, x, s, t) { + return cmn((b & d) | (c & ~d), a, b, x, s, t); +} + +function hh(a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); +} + +function ii(a, b, c, d, x, s, t) { + return cmn(c ^ (b | ~d), a, b, x, s, t); +} + +function md51(s) { + // txt = ''; + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i; + for (i = 64; i <= s.length; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < s.length; i++) + tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + tail[i >> 2] |= 0x80 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i++) tail[i] = 0; + } + tail[14] = n * 8; + md5cycle(state, tail); + return state; +} + +/* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ +function md5blk(s) { + /* I figured global was faster. */ + var md5blks = [], + i; /* Andy King said do it this way. */ + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = + s.charCodeAt(i) + + (s.charCodeAt(i + 1) << 8) + + (s.charCodeAt(i + 2) << 16) + + (s.charCodeAt(i + 3) << 24); + } + return md5blks; +} - kChar = 1, kShort = 2, kInt = 3, kLong = 4, kFloat = 5, kCounter = 6, - kCharStar = 7, kDouble = 8, kDouble32 = 9, kLegacyChar = 10, - kUChar = 11, kUShort = 12, kUInt = 13, kULong = 14, kBits = 15, - kLong64 = 16, kULong64 = 17, kBool = 18, kFloat16 = 19, +var hex_chr = "0123456789abcdef".split(""); - kBase = 0, kOffsetL = 20, kOffsetP = 40, - kObject = 61, kAny = 62, kObjectp = 63, kObjectP = 64, kTString = 65, - kTObject = 66, kTNamed = 67, kAnyp = 68, kAnyP = 69, +function rhex(n) { + var s = "", + j = 0; + for (; j < 4; j++) + s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f]; + return s; +} + +function hex(x) { + for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); + return x.join(""); +} - /* kAnyPnoVT: 70, */ - kSTLp = 71, - /* kSkip = 100, kSkipL = 120, kSkipP = 140, kConv = 200, kConvL = 220, kConvP = 240, */ +// Converts a 4-byte number to byte string +function singleToByteString(n) { + return String.fromCharCode( + (n & 0xff) >> 0, + (n & 0xff00) >> 8, + (n & 0xff0000) >> 16, + (n & 0xff000000) >> 24 + ); +} - kSTL = 300, /* kSTLstring = 365, */ +// Converts an array of numbers to a byte string +function toByteString(x) { + return x.map(singleToByteString).join(""); +} - kStreamer = 500, kStreamLoop = 501, +// Returns the MD5 hash as a byte string +function md5Bin(s) { + return toByteString(md51(s)); +} - kMapOffset = 2, kByteCountMask = 0x40000000, kNewClassTag = 0xFFFFFFFF, kClassMask = 0x80000000, +// Returns MD5 hash as a hex string +function md5(s) { + return hex(md51(s)); +} - // constants of bits in version - kStreamedMemberWise = BIT(14), +var md5Check = md5("hello") != "5d41402abc4b2a76b9719d911017c592"; - // constants used for coding type of STL container - kNotSTL = 0, kSTLvector = 1, kSTLlist = 2, kSTLdeque = 3, kSTLmap = 4, kSTLmultimap = 5, - kSTLset = 6, kSTLmultiset = 7, kSTLbitset = 8, - // kSTLforwardlist = 9, kSTLunorderedset = 10, kSTLunorderedmultiset = 11, kSTLunorderedmap = 12, - // kSTLunorderedmultimap = 13, kSTLend = 14 +function add32(a, b) { + if (md5Check) { + /* if the md5Check does not match + the expected value, we're dealing + with an old browser and need + this function. */ + var lsw = (a & 0xffff) + (b & 0xffff), + msw = (a >> 16) + (b >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); + } else { + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + return (a + b) & 0xffffffff; + } +} - kBaseClass = 'BASE', +/** + * @license + * FPDF is released under a permissive license: there is no usage restriction. + * You may embed it freely in your application (commercial or not), with or + * without modifications. + * + * Reference: http://www.fpdf.org/en/script/script37.php + */ + +function repeat(str, num) { + return new Array(num + 1).join(str); +} + +/** + * Converts a byte string to a hex string + * + * @name rc4 + * @function + * @param {string} key Byte string of encryption key + * @param {string} data Byte string of data to be encrypted + * @returns {string} Encrypted string + */ +function rc4(key, data) { + var lastKey, lastState; + if (key !== lastKey) { + var k = repeat(key, ((256 / key.length) >> 0) + 1); + var state = []; + for (var i = 0; i < 256; i++) { + state[i] = i; + } + var j = 0; + for (var i = 0; i < 256; i++) { + var t = state[i]; + j = (j + t + k.charCodeAt(i)) % 256; + state[i] = state[j]; + state[j] = t; + } + lastKey = key; + lastState = state; + } else { + state = lastState; + } + var length = data.length; + var a = 0; + var b = 0; + var out = ""; + for (var i = 0; i < length; i++) { + a = (a + 1) % 256; + t = state[a]; + b = (b + t) % 256; + state[a] = state[b]; + state[b] = t; + k = state[(state[a] + state[b]) % 256]; + out += String.fromCharCode(data.charCodeAt(i) ^ k); + } + return out; +} + +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + * Author: Owen Leong (@owenl131) + * Date: 15 Oct 2020 + * References: + * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt + * https://github.com/foliojs/pdfkit/blob/master/lib/security.js + * http://www.fpdf.org/en/script/script37.php + */ + +var permissionOptions = { + print: 4, + modify: 8, + copy: 16, + "annot-forms": 32 +}; + +/** + * Initializes encryption settings + * + * @name constructor + * @function + * @param {Array} permissions Permissions allowed for user, "print", "modify", "copy" and "annot-forms". + * @param {String} userPassword Permissions apply to this user. Leaving this empty means the document + * is not password protected but viewer has the above permissions. + * @param {String} ownerPassword Owner has full functionalities to the file. + * @param {String} fileId As hex string, should be same as the file ID in the trailer. + * @example + * var security = new PDFSecurity(["print"]) + */ +function PDFSecurity(permissions, userPassword, ownerPassword, fileId) { + this.v = 1; // algorithm 1, future work can add in more recent encryption schemes + this.r = 2; // revision 2 + + // set flags for what functionalities the user can access + let protection = 192; + permissions.forEach(function(perm) { + if (typeof permissionOptions.perm !== "undefined") { + throw new Error("Invalid permission: " + perm); + } + protection += permissionOptions[perm]; + }); + + // padding is used to pad the passwords to 32 bytes, also is hashed and stored in the final PDF + this.padding = + "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" + + "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; + let paddedUserPassword = (userPassword + this.padding).substr(0, 32); + let paddedOwnerPassword = (ownerPassword + this.padding).substr(0, 32); + + this.O = this.processOwnerPassword(paddedUserPassword, paddedOwnerPassword); + this.P = -((protection ^ 255) + 1); + this.encryptionKey = md5Bin( + paddedUserPassword + + this.O + + this.lsbFirstWord(this.P) + + this.hexToBytes(fileId) + ).substr(0, 5); + this.U = rc4(this.encryptionKey, this.padding); +} + +/** + * Breaks down a 4-byte number into its individual bytes, with the least significant bit first + * + * @name lsbFirstWord + * @function + * @param {number} data 32-bit number + * @returns {Array} + */ +PDFSecurity.prototype.lsbFirstWord = function(data) { + return String.fromCharCode( + (data >> 0) & 0xff, + (data >> 8) & 0xff, + (data >> 16) & 0xff, + (data >> 24) & 0xff + ); +}; + +/** + * Converts a byte string to a hex string + * + * @name toHexString + * @function + * @param {String} byteString Byte string + * @returns {String} + */ +PDFSecurity.prototype.toHexString = function(byteString) { + return byteString + .split("") + .map(function(byte) { + return ("0" + (byte.charCodeAt(0) & 0xff).toString(16)).slice(-2); + }) + .join(""); +}; + +/** + * Converts a hex string to a byte string + * + * @name hexToBytes + * @function + * @param {String} hex Hex string + * @returns {String} + */ +PDFSecurity.prototype.hexToBytes = function(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) + bytes.push(String.fromCharCode(parseInt(hex.substr(c, 2), 16))); + return bytes.join(""); +}; + +/** + * Computes the 'O' field in the encryption dictionary + * + * @name processOwnerPassword + * @function + * @param {String} paddedUserPassword Byte string of padded user password + * @param {String} paddedOwnerPassword Byte string of padded owner password + * @returns {String} + */ +PDFSecurity.prototype.processOwnerPassword = function( + paddedUserPassword, + paddedOwnerPassword +) { + let key = md5Bin(paddedOwnerPassword).substr(0, 5); + return rc4(key, paddedUserPassword); +}; + +/** + * Returns an encryptor function which can take in a byte string and returns the encrypted version + * + * @name encryptor + * @function + * @param {number} objectId + * @param {number} generation Not sure what this is for, you can set it to 0 + * @returns {Function} + * @example + * out("stream"); + * encryptor = security.encryptor(object.id, 0); + * out(encryptor(data)); + * out("endstream"); + */ +PDFSecurity.prototype.encryptor = function(objectId, generation) { + let key = md5Bin( + this.encryptionKey + + String.fromCharCode( + objectId & 0xff, + (objectId >> 8) & 0xff, + (objectId >> 16) & 0xff, + generation & 0xff, + (generation >> 8) & 0xff + ) + ).substr(0, 10); + return function(data) { + return rc4(key, data); + }; +}; - // name of base IO types - BasicTypeNames = [kBaseClass, 'char', 'short', 'int', 'long', 'float', 'int', 'const char*', 'double', 'Double32_t', - 'char', 'unsigned char', 'unsigned short', 'unsigned', 'unsigned long', 'unsigned', 'Long64_t', 'ULong64_t', 'bool', 'Float16_t'], +/** + * Convert string to `PDF Name Object`. + * Detail: PDF Reference 1.3 - Chapter 3.2.4 Name Object + * @param str + */ +function toPDFName(str) { + // eslint-disable-next-line no-control-regex + if (/[^\u0000-\u00ff]/.test(str)) { + // non ascii string + throw new Error( + "Invalid PDF Name Object: " + str + ", Only accept ASCII characters." + ); + } + var result = "", + strLength = str.length; + for (var i = 0; i < strLength; i++) { + var charCode = str.charCodeAt(i); + if ( + charCode < 0x21 || + charCode === 0x23 /* # */ || + charCode === 0x25 /* % */ || + charCode === 0x28 /* ( */ || + charCode === 0x29 /* ) */ || + charCode === 0x2f /* / */ || + charCode === 0x3c /* < */ || + charCode === 0x3e /* > */ || + charCode === 0x5b /* [ */ || + charCode === 0x5d /* ] */ || + charCode === 0x7b /* { */ || + charCode === 0x7d /* } */ || + charCode > 0x7e + ) { + // Char CharCode hexStr paddingHexStr Result + // "\t" 9 9 09 #09 + // " " 32 20 20 #20 + // "©" 169 a9 a9 #a9 + var hexStr = charCode.toString(16), + paddingHexStr = ("0" + hexStr).slice(-2); + + result += "#" + paddingHexStr; + } else { + // Other ASCII printable characters between 0x21 <= X <= 0x7e + result += str[i]; + } + } + return result; +} - // names of STL containers - StlNames = ['', 'vector', 'list', 'deque', 'map', 'multimap', 'set', 'multiset', 'bitset'], +/* eslint-disable no-console */ +/** + * jsPDF's Internal PubSub Implementation. + * Backward compatible rewritten on 2014 by + * Diego Casorran, https://github.com/diegocr + * + * @class + * @name PubSub + * @ignore + */ +function PubSub(context) { + if (typeof context !== "object") { + throw new Error( + "Invalid Context passed to initialize PubSub (jsPDF-module)" + ); + } + var topics = {}; + + this.subscribe = function(topic, callback, once) { + once = once || false; + if ( + typeof topic !== "string" || + typeof callback !== "function" || + typeof once !== "boolean" + ) { + throw new Error( + "Invalid arguments passed to PubSub.subscribe (jsPDF-module)" + ); + } - // TObject bits - kIsReferenced = BIT(4), kHasUUID = BIT(5), + if (!topics.hasOwnProperty(topic)) { + topics[topic] = {}; + } + var token = Math.random().toString(35); + topics[topic][token] = [callback, !!once]; -/** @summary Custom streamers for root classes - * @desc map of user-streamer function like func(buf,obj) - * or alias (classname) which can be used to read that function - * or list of read functions - * @private */ -CustomStreamers = { - TObject(buf, obj) { - obj.fUniqueID = buf.ntou4(); - obj.fBits = buf.ntou4(); - if (obj.fBits & kIsReferenced) buf.ntou2(); // skip pid - }, + return token; + }; - TNamed: [{ - basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTNamed; - buf.classStreamer(obj, clTObject); + this.unsubscribe = function(token) { + for (var topic in topics) { + if (topics[topic][token]) { + delete topics[topic][token]; + if (Object.keys(topics[topic]).length === 0) { + delete topics[topic]; + } + return true; } - }, - { name: 'fName', func(buf, obj) { obj.fName = buf.readTString(); } }, - { name: 'fTitle', func(buf, obj) { obj.fTitle = buf.readTString(); } } - ], + } + return false; + }; - TObjString: [{ - basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTObjString; - buf.classStreamer(obj, clTObject); + this.publish = function(topic) { + if (topics.hasOwnProperty(topic)) { + var args = Array.prototype.slice.call(arguments, 1), + tokens = []; + + for (var token in topics[topic]) { + var sub = topics[topic][token]; + try { + sub[0].apply(context, args); + } catch (ex) { + if (globalObject.console) { + console$1.error("jsPDF PubSub Error", ex.message, ex); + } + } + if (sub[1]) tokens.push(token); } - }, - { name: 'fString', func(buf, obj) { obj.fString = buf.readTString(); } } - ], - - TClonesArray(buf, list) { - if (!list._typename) list._typename = clTClonesArray; - list.$kind = clTClonesArray; - list.name = ''; - const ver = buf.last_read_version; - if (ver > 2) buf.classStreamer(list, clTObject); - if (ver > 1) list.name = buf.readTString(); - let classv = buf.readTString(), clv = 0; - const pos = classv.lastIndexOf(';'); + if (tokens.length) tokens.forEach(this.unsubscribe); + } + }; - if (pos > 0) { - clv = Number.parseInt(classv.slice(pos + 1)); - classv = classv.slice(0, pos); - } + this.getTopics = function() { + return topics; + }; +} - let nobjects = buf.ntou4(); - if (nobjects < 0) nobjects = -nobjects; // for backward compatibility +function GState(parameters) { + if (!(this instanceof GState)) { + return new GState(parameters); + } - list.arr = new Array(nobjects); - list.fLast = nobjects - 1; - list.fLowerBound = buf.ntou4(); + /** + * @name GState#opacity + * @type {any} + */ + /** + * @name GState#stroke-opacity + * @type {any} + */ + var supported = "opacity,stroke-opacity".split(","); + for (var p in parameters) { + if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) { + this[p] = parameters[p]; + } + } + /** + * @name GState#id + * @type {string} + */ + this.id = ""; // set by addGState() + /** + * @name GState#objectNumber + * @type {number} + */ + this.objectNumber = -1; // will be set by putGState() +} + +GState.prototype.equals = function equals(other) { + var ignore = "id,objectNumber,equals"; + var p; + if (!other || typeof other !== typeof this) return false; + var count = 0; + for (p in this) { + if (ignore.indexOf(p) >= 0) continue; + if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false; + if (this[p] !== other[p]) return false; + count++; + } + for (p in other) { + if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--; + } + return count === 0; +}; - let streamer = buf.fFile.getStreamer(classv, { val: clv }); - streamer = buf.fFile.getSplittedStreamer(streamer); +function Pattern$1(gState, matrix) { + this.gState = gState; + this.matrix = matrix; - if (!streamer) - console.log(`Cannot get member-wise streamer for ${classv}:${clv}`); - else { - // create objects - for (let n = 0; n < nobjects; ++n) - list.arr[n] = { _typename: classv }; + this.id = ""; // set by addPattern() + this.objectNumber = -1; // will be set by putPattern() +} - // call streamer for all objects member-wise - for (let k = 0; k < streamer.length; ++k) { - for (let n = 0; n < nobjects; ++n) - streamer[k].func(buf, list.arr[n]); - } - } - }, +function ShadingPattern(type, coords, colors, gState, matrix) { + if (!(this instanceof ShadingPattern)) { + return new ShadingPattern(type, coords, colors, gState, matrix); + } - TMap(buf, map) { - if (!map._typename) map._typename = clTMap; - map.name = ''; - map.arr = []; - const ver = buf.last_read_version; - if (ver > 2) buf.classStreamer(map, clTObject); - if (ver > 1) map.name = buf.readTString(); + // see putPattern() for information how they are realized + this.type = type === "axial" ? 2 : 3; + this.coords = coords; + this.colors = colors; - const nobjects = buf.ntou4(); - // create objects - for (let n = 0; n < nobjects; ++n) { - const obj = { _typename: 'TPair' }; - obj.first = buf.readObjectAny(); - obj.second = buf.readObjectAny(); - if (obj.first) map.arr.push(obj); - } - }, + Pattern$1.call(this, gState, matrix); +} - TTreeIndex(buf, obj) { - const ver = buf.last_read_version; - obj._typename = 'TTreeIndex'; - buf.classStreamer(obj, 'TVirtualIndex'); - obj.fMajorName = buf.readTString(); - obj.fMinorName = buf.readTString(); - obj.fN = buf.ntoi8(); - obj.fIndexValues = buf.readFastArray(obj.fN, kLong64); - if (ver > 1) obj.fIndexValuesMinor = buf.readFastArray(obj.fN, kLong64); - obj.fIndex = buf.readFastArray(obj.fN, kLong64); - }, +function TilingPattern(boundingBox, xStep, yStep, gState, matrix) { + if (!(this instanceof TilingPattern)) { + return new TilingPattern(boundingBox, xStep, yStep, gState, matrix); + } - TRefArray(buf, obj) { - obj._typename = 'TRefArray'; - buf.classStreamer(obj, clTObject); - obj.name = buf.readTString(); - const nobj = buf.ntoi4(); - obj.fLast = nobj - 1; - obj.fLowerBound = buf.ntoi4(); - /* const pidf = */ buf.ntou2(); - obj.fUIDs = buf.readFastArray(nobj, kUInt); - }, + this.boundingBox = boundingBox; + this.xStep = xStep; + this.yStep = yStep; - TCanvas(buf, obj) { - obj._typename = clTCanvas; - buf.classStreamer(obj, clTPad); - obj.fDISPLAY = buf.readTString(); - obj.fDoubleBuffer = buf.ntoi4(); - obj.fRetained = (buf.ntou1() !== 0); - obj.fXsizeUser = buf.ntoi4(); - obj.fYsizeUser = buf.ntoi4(); - obj.fXsizeReal = buf.ntoi4(); - obj.fYsizeReal = buf.ntoi4(); - obj.fWindowTopX = buf.ntoi4(); - obj.fWindowTopY = buf.ntoi4(); - obj.fWindowWidth = buf.ntoi4(); - obj.fWindowHeight = buf.ntoi4(); - obj.fCw = buf.ntou4(); - obj.fCh = buf.ntou4(); - obj.fCatt = buf.classStreamer({}, clTAttCanvas); - buf.ntou1(); // ignore b << TestBit(kMoveOpaque); - buf.ntou1(); // ignore b << TestBit(kResizeOpaque); - obj.fHighLightColor = buf.ntoi2(); - obj.fBatch = (buf.ntou1() !== 0); - buf.ntou1(); // ignore b << TestBit(kShowEventStatus); - buf.ntou1(); // ignore b << TestBit(kAutoExec); - buf.ntou1(); // ignore b << TestBit(kMenuBar); - }, + this.stream = ""; // set by endTilingPattern(); - TObjArray(buf, list) { - if (!list._typename) list._typename = clTObjArray; - list.$kind = clTObjArray; - list.name = ''; - const ver = buf.last_read_version; - if (ver > 2) - buf.classStreamer(list, clTObject); - if (ver > 1) - list.name = buf.readTString(); - const nobjects = buf.ntou4(); - let i = 0; - list.arr = new Array(nobjects); - list.fLast = nobjects - 1; - list.fLowerBound = buf.ntou4(); - while (i < nobjects) - list.arr[i++] = buf.readObjectAny(); - }, + this.cloneIndex = 0; - TPolyMarker3D(buf, marker) { - const ver = buf.last_read_version; - buf.classStreamer(marker, clTObject); - buf.classStreamer(marker, clTAttMarker); - marker.fN = buf.ntoi4(); - marker.fP = buf.readFastArray(marker.fN * 3, kFloat); - marker.fOption = buf.readTString(); - marker.fName = (ver > 1) ? buf.readTString() : clTPolyMarker3D; - }, + Pattern$1.call(this, gState, matrix); +} - TPolyLine3D(buf, obj) { - buf.classStreamer(obj, clTObject); - buf.classStreamer(obj, clTAttLine); - obj.fN = buf.ntoi4(); - obj.fP = buf.readFastArray(obj.fN * 3, kFloat); - obj.fOption = buf.readTString(); - }, +/** + * Creates new jsPDF document object instance. + * @name jsPDF + * @class + * @param {Object} [options] - Collection of settings initializing the jsPDF-instance + * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").
+ * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.
+ * Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" + * units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]. + * @param {string/Array} [options.format=a4] The format of the first page. Can be:
  • a0 - a10
  • b0 - b10
  • c0 - c10
  • dl
  • letter
  • government-letter
  • legal
  • junior-legal
  • ledger
  • tabloid
  • credit-card

+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] + * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used. + * @param {boolean} [options.compress=false] Compress the generated PDF. + * @param {number} [options.precision=16] Precision of the element-positions. + * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it. + * @param {string[]} [options.hotfixes] An array of strings to enable hotfixes such as correct pixel scaling. + * @param {Object} [options.encryption] + * @param {string} [options.encryption.userPassword] Password for the user bound by the given permissions list. + * @param {string} [options.encryption.ownerPassword] Both userPassword and ownerPassword should be set for proper authentication. + * @param {string[]} [options.encryption.userPermissions] Array of permissions "print", "modify", "copy", "annot-forms", accessible by the user. + * @param {number|"smart"} [options.floatPrecision=16] + * @returns {jsPDF} jsPDF-instance + * @description + * ``` + * { + * orientation: 'p', + * unit: 'mm', + * format: 'a4', + * putOnlyUsedFonts:true, + * floatPrecision: 16 // or "smart", default is 16 + * } + * ``` + * + * @constructor + */ +function jsPDF(options) { + var orientation = typeof arguments[0] === "string" ? arguments[0] : "p"; + var unit = arguments[1]; + var format = arguments[2]; + var compressPdf = arguments[3]; + var filters = []; + var userUnit = 1.0; + var precision; + var floatPrecision = 16; + var defaultPathOperation = "S"; + var encryptionOptions = null; + + options = options || {}; + + if (typeof options === "object") { + orientation = options.orientation; + unit = options.unit || unit; + format = options.format || format; + compressPdf = options.compress || options.compressPdf || compressPdf; + encryptionOptions = options.encryption || null; + if (encryptionOptions !== null) { + encryptionOptions.userPassword = encryptionOptions.userPassword || ""; + encryptionOptions.ownerPassword = encryptionOptions.ownerPassword || ""; + encryptionOptions.userPermissions = + encryptionOptions.userPermissions || []; + } + userUnit = + typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0; + if (typeof options.precision !== "undefined") { + precision = options.precision; + } + if (typeof options.floatPrecision !== "undefined") { + floatPrecision = options.floatPrecision; + } + defaultPathOperation = options.defaultPathOperation || "S"; + } - TStreamerInfo(buf, obj) { - buf.classStreamer(obj, clTNamed); - obj.fCheckSum = buf.ntou4(); - obj.fClassVersion = buf.ntou4(); - obj.fElements = buf.readObjectAny(); - }, + filters = + options.filters || (compressPdf === true ? ["FlateEncode"] : filters); - TStreamerElement(buf, element) { - const ver = buf.last_read_version; - buf.classStreamer(element, clTNamed); - element.fType = buf.ntou4(); - element.fSize = buf.ntou4(); - element.fArrayLength = buf.ntou4(); - element.fArrayDim = buf.ntou4(); - element.fMaxIndex = buf.readFastArray((ver === 1) ? buf.ntou4() : 5, kUInt); - element.fTypeName = buf.readTString(); + unit = unit || "mm"; + orientation = ("" + (orientation || "P")).toLowerCase(); + var putOnlyUsedFonts = options.putOnlyUsedFonts || false; + var usedFonts = {}; - if ((element.fType === kUChar) && ((element.fTypeName === 'Bool_t') || (element.fTypeName === 'bool'))) - element.fType = kBool; + var API = { + internal: {}, + __private__: {} + }; - element.fXmin = element.fXmax = element.fFactor = 0; - if (ver === 3) { - element.fXmin = buf.ntod(); - element.fXmax = buf.ntod(); - element.fFactor = buf.ntod(); - } else if ((ver > 3) && (element.fBits & BIT(6))) { // kHasRange - let p1 = element.fTitle.indexOf('['); - if ((p1 >= 0) && (element.fType > kOffsetP)) - p1 = element.fTitle.indexOf('[', p1 + 1); - const p2 = element.fTitle.indexOf(']', p1 + 1); + API.__private__.PubSub = PubSub; - if ((p1 >= 0) && (p2 >= p1 + 2)) { - const arr = element.fTitle.slice(p1+1, p2).split(','); - let nbits = 32; - if (!arr || arr.length < 2) - throw new Error(`Problem to decode range setting from streamer element title ${element.fTitle}`); + var pdfVersion = "1.3"; + var getPdfVersion = (API.__private__.getPdfVersion = function() { + return pdfVersion; + }); - if (arr.length === 3) nbits = parseInt(arr[2]); - if (!Number.isInteger(nbits) || (nbits < 2) || (nbits > 32)) nbits = 32; + API.__private__.setPdfVersion = function(value) { + pdfVersion = value; + }; - const parse_range = val => { - if (!val) return 0; - if (val.indexOf('pi') < 0) return parseFloat(val); - val = val.trim(); - let sign = 1; - if (val[0] === '-') { sign = -1; val = val.slice(1); } - switch (val) { - case '2pi': - case '2*pi': - case 'twopi': return sign * 2 * Math.PI; - case 'pi/2': return sign * Math.PI / 2; - case 'pi/4': return sign * Math.PI / 4; - } - return sign * Math.PI; - }; + // Size in pt of various paper formats + var pageFormats = { + a0: [2383.94, 3370.39], + a1: [1683.78, 2383.94], + a2: [1190.55, 1683.78], + a3: [841.89, 1190.55], + a4: [595.28, 841.89], + a5: [419.53, 595.28], + a6: [297.64, 419.53], + a7: [209.76, 297.64], + a8: [147.4, 209.76], + a9: [104.88, 147.4], + a10: [73.7, 104.88], + b0: [2834.65, 4008.19], + b1: [2004.09, 2834.65], + b2: [1417.32, 2004.09], + b3: [1000.63, 1417.32], + b4: [708.66, 1000.63], + b5: [498.9, 708.66], + b6: [354.33, 498.9], + b7: [249.45, 354.33], + b8: [175.75, 249.45], + b9: [124.72, 175.75], + b10: [87.87, 124.72], + c0: [2599.37, 3676.54], + c1: [1836.85, 2599.37], + c2: [1298.27, 1836.85], + c3: [918.43, 1298.27], + c4: [649.13, 918.43], + c5: [459.21, 649.13], + c6: [323.15, 459.21], + c7: [229.61, 323.15], + c8: [161.57, 229.61], + c9: [113.39, 161.57], + c10: [79.37, 113.39], + dl: [311.81, 623.62], + letter: [612, 792], + "government-letter": [576, 756], + legal: [612, 1008], + "junior-legal": [576, 360], + ledger: [1224, 792], + tabloid: [792, 1224], + "credit-card": [153, 243] + }; - element.fXmin = parse_range(arr[0]); - element.fXmax = parse_range(arr[1]); + API.__private__.getPageFormats = function() { + return pageFormats; + }; - // avoid usage of 1 << nbits, while only works up to 32 bits - const bigint = ((nbits >= 0) && (nbits < 32)) ? Math.pow(2, nbits) : 0xffffffff; - if (element.fXmin < element.fXmax) - element.fFactor = bigint / (element.fXmax - element.fXmin); - else if (nbits < 15) - element.fXmin = nbits; - } - } - }, + var getPageFormat = (API.__private__.getPageFormat = function(value) { + return pageFormats[value]; + }); - TStreamerBase(buf, elem) { - const ver = buf.last_read_version; - buf.classStreamer(elem, clTStreamerElement); - if (ver > 2) elem.fBaseVersion = buf.ntou4(); - }, + format = format || "a4"; - TStreamerSTL(buf, elem) { - buf.classStreamer(elem, clTStreamerElement); - elem.fSTLtype = buf.ntou4(); - elem.fCtype = buf.ntou4(); + var ApiMode = { + COMPAT: "compat", + ADVANCED: "advanced" + }; + var apiMode = ApiMode.COMPAT; + + function advancedAPI() { + // prepend global change of basis matrix + // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix + // that does this job for us (however, texts, images and similar objects must be drawn bottom up)) + this.saveGraphicsState(); + out( + new Matrix( + scaleFactor, + 0, + 0, + -scaleFactor, + 0, + getPageHeight() * scaleFactor + ).toString() + " cm" + ); + this.setFontSize(this.getFontSize() / scaleFactor); - if ((elem.fSTLtype === kSTLmultimap) && - ((elem.fTypeName.indexOf('std::set') === 0) || - (elem.fTypeName.indexOf('set') === 0))) elem.fSTLtype = kSTLset; + // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation + // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here. + defaultPathOperation = "n"; - if ((elem.fSTLtype === kSTLset) && - ((elem.fTypeName.indexOf('std::multimap') === 0) || - (elem.fTypeName.indexOf('multimap') === 0))) elem.fSTLtype = kSTLmultimap; - }, + apiMode = ApiMode.ADVANCED; + } - TStreamerSTLstring(buf, elem) { - if (buf.last_read_version > 0) - buf.classStreamer(elem, clTStreamerSTL); - }, + function compatAPI() { + this.restoreGraphicsState(); + defaultPathOperation = "S"; + apiMode = ApiMode.COMPAT; + } - TList(buf, obj) { - // stream all objects in the list from the I/O buffer - if (!obj._typename) obj._typename = this.typename; - obj.$kind = clTList; // all derived classes will be marked as well - if (buf.last_read_version > 3) { - buf.classStreamer(obj, clTObject); - obj.name = buf.readTString(); - const nobjects = buf.ntou4(); - obj.arr = new Array(nobjects); - obj.opt = new Array(nobjects); - for (let i = 0; i < nobjects; ++i) { - obj.arr[i] = buf.readObjectAny(); - obj.opt[i] = buf.readTString(); - } - } else { - obj.name = ''; - obj.arr = []; - obj.opt = []; - } - }, + /** + * @function combineFontStyleAndFontWeight + * @param {string} fontStyle Fontstyle or variant. Example: "italic". + * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 + * @returns {string} + * @private + */ + var combineFontStyleAndFontWeight = (API.__private__.combineFontStyleAndFontWeight = function( + fontStyle, + fontWeight + ) { + if ( + (fontStyle == "bold" && fontWeight == "normal") || + (fontStyle == "bold" && fontWeight == 400) || + (fontStyle == "normal" && fontWeight == "italic") || + (fontStyle == "bold" && fontWeight == "italic") + ) { + throw new Error("Invalid Combination of fontweight and fontstyle"); + } + if (fontWeight) { + fontStyle = + fontWeight == 400 || fontWeight === "normal" + ? fontStyle === "italic" + ? "italic" + : "normal" + : (fontWeight == 700 || fontWeight === "bold") && + fontStyle === "normal" + ? "bold" + : (fontWeight == 700 ? "bold" : fontWeight) + "" + fontStyle; + } + return fontStyle; + }); - THashList: clTList, + /** + * @callback ApiSwitchBody + * @param {jsPDF} pdf + */ - TStreamerLoop(buf, elem) { - if (buf.last_read_version > 1) { - buf.classStreamer(elem, clTStreamerElement); - elem.fCountVersion = buf.ntou4(); - elem.fCountName = buf.readTString(); - elem.fCountClass = buf.readTString(); - } - }, + /** + * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual + * screen coordinates and the PDF coordinate system. + * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms + * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might + * not support this mode, though. + * Initial mode is "compat". + * + * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to + * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}. + * + * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the + * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject} + * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode. + * + * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. + * The API mode will be switched back automatically afterwards. + * @returns {jsPDF} + * @memberof jsPDF# + * @name advancedAPI + */ + API.advancedAPI = function(body) { + var doSwitch = apiMode === ApiMode.COMPAT; - TStreamerBasicPointer: 'TStreamerLoop', + if (doSwitch) { + advancedAPI.call(this); + } - TStreamerObject(buf, elem) { - if (buf.last_read_version > 1) - buf.classStreamer(elem, clTStreamerElement); - }, + if (typeof body !== "function") { + return this; + } - TStreamerBasicType: clTStreamerObject, - TStreamerObjectAny: clTStreamerObject, - TStreamerString: clTStreamerObject, - TStreamerObjectPointer: clTStreamerObject, + body(this); - TStreamerObjectAnyPointer(buf, elem) { - if (buf.last_read_version > 0) - buf.classStreamer(elem, clTStreamerElement); - }, + if (doSwitch) { + compatAPI.call(this); + } - TTree: { - name: '$file', - func(buf, obj) { obj.$kind = 'TTree'; obj.$file = buf.fFile; } - }, + return this; + }; - RooRealVar(buf, obj) { - const v = buf.last_read_version; - buf.classStreamer(obj, 'RooAbsRealLValue'); - if (v === 1) { buf.ntod(); buf.ntod(); buf.ntoi4(); } // skip fitMin, fitMax, fitBins - obj._error = buf.ntod(); - obj._asymErrLo = buf.ntod(); - obj._asymErrHi = buf.ntod(); - if (v >= 2) obj._binning = buf.readObjectAny(); - if (v === 3) obj._sharedProp = buf.readObjectAny(); - if (v >= 4) obj._sharedProp = buf.classStreamer({}, 'RooRealVarSharedProperties'); - }, + /** + * Switches to "compat" API mode. See {@link advancedAPI} for more details. + * + * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. + * The API mode will be switched back automatically afterwards. + * @return {jsPDF} + * @memberof jsPDF# + * @name compatApi + */ + API.compatAPI = function(body) { + var doSwitch = apiMode === ApiMode.ADVANCED; - RooAbsBinning(buf, obj) { - buf.classStreamer(obj, (buf.last_read_version === 1) ? clTObject : clTNamed); - buf.classStreamer(obj, 'RooPrintable'); - }, + if (doSwitch) { + compatAPI.call(this); + } - RooCategory(buf, obj) { - const v = buf.last_read_version; - buf.classStreamer(obj, 'RooAbsCategoryLValue'); - obj._sharedProp = (v === 1) ? buf.readObjectAny() : buf.classStreamer({}, 'RooCategorySharedProperties'); - }, + if (typeof body !== "function") { + return this; + } - 'RooWorkspace::CodeRepo': (buf /* , obj */) => { - const sz = (buf.last_read_version === 2) ? 3 : 2; - for (let i = 0; i < sz; ++i) { - let cnt = buf.ntoi4() * ((i === 0) ? 4 : 3); - while (cnt--) buf.readTString(); - } - }, + body(this); - RooLinkedList(buf, obj) { - const v = buf.last_read_version; - buf.classStreamer(obj, clTObject); - let size = buf.ntoi4(); - obj.arr = create$1(clTList); - while (size--) - obj.arr.Add(buf.readObjectAny()); - if (v > 1) obj._name = buf.readTString(); - }, + if (doSwitch) { + advancedAPI.call(this); + } - TImagePalette: [ - { - basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTImagePalette; - buf.classStreamer(obj, clTObject); - } - }, - { name: 'fNumPoints', func(buf, obj) { obj.fNumPoints = buf.ntou4(); } }, - { name: 'fPoints', func(buf, obj) { obj.fPoints = buf.readFastArray(obj.fNumPoints, kDouble); } }, - { name: 'fColorRed', func(buf, obj) { obj.fColorRed = buf.readFastArray(obj.fNumPoints, kUShort); } }, - { name: 'fColorGreen', func(buf, obj) { obj.fColorGreen = buf.readFastArray(obj.fNumPoints, kUShort); } }, - { name: 'fColorBlue', func(buf, obj) { obj.fColorBlue = buf.readFastArray(obj.fNumPoints, kUShort); } }, - { name: 'fColorAlpha', func(buf, obj) { obj.fColorAlpha = buf.readFastArray(obj.fNumPoints, kUShort); } } - ], + return this; + }; - TAttImage: [ - { name: 'fImageQuality', func(buf, obj) { obj.fImageQuality = buf.ntoi4(); } }, - { name: 'fImageCompression', func(buf, obj) { obj.fImageCompression = buf.ntou4(); } }, - { name: 'fConstRatio', func(buf, obj) { obj.fConstRatio = (buf.ntou1() !== 0); } }, - { name: 'fPalette', func(buf, obj) { obj.fPalette = buf.classStreamer({}, clTImagePalette); } } - ], + /** + * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}. + * @memberof jsPDF# + * @name isAdvancedAPI + */ + API.isAdvancedAPI = function() { + return apiMode === ApiMode.ADVANCED; + }; - TASImage(buf, obj) { - if ((buf.last_read_version === 1) && (buf.fFile.fVersion > 0) && (buf.fFile.fVersion < 50000)) - return console.warn('old TASImage version - not yet supported'); + var advancedApiModeTrap = function(methodName) { + if (apiMode !== ApiMode.ADVANCED) { + throw new Error( + methodName + + " is only available in 'advanced' API mode. " + + "You need to call advancedAPI() first." + ); + } + }; - buf.classStreamer(obj, clTNamed); + var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function( + number, + parmPrecision + ) { + var tmpPrecision = precision || parmPrecision; + if (isNaN(number) || isNaN(tmpPrecision)) { + throw new Error("Invalid argument passed to jsPDF.roundToPrecision"); + } + return number.toFixed(tmpPrecision).replace(/0+$/, ""); + }); - if (buf.ntou1() !== 0) { - const size = buf.ntoi4(); - obj.fPngBuf = buf.readFastArray(size, kUChar); + // high precision float + var hpf; + if (typeof floatPrecision === "number") { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + return roundToPrecision(number, floatPrecision); + }; + } else if (floatPrecision === "smart") { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + if (number > -1 && number < 1) { + return roundToPrecision(number, 16); } else { - buf.classStreamer(obj, 'TAttImage'); - obj.fWidth = buf.ntoi4(); - obj.fHeight = buf.ntoi4(); - obj.fImgBuf = buf.readFastArray(obj.fWidth * obj.fHeight, kDouble); + return roundToPrecision(number, 5); } - }, + }; + } else { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + return roundToPrecision(number, 16); + }; + } + var f2 = (API.f2 = API.__private__.f2 = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.f2"); + } + return roundToPrecision(number, 2); + }); - TMaterial(buf, obj) { - const v = buf.last_read_version; - buf.classStreamer(obj, clTNamed); - obj.fNumber = buf.ntoi4(); - obj.fA = buf.ntof(); - obj.fZ = buf.ntof(); - obj.fDensity = buf.ntof(); - if (v > 2) { - buf.classStreamer(obj, clTAttFill); - obj.fRadLength = buf.ntof(); - obj.fInterLength = buf.ntof(); - } else - obj.fRadLength = obj.fInterLength = 0; - }, + var f3 = (API.__private__.f3 = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.f3"); + } + return roundToPrecision(number, 3); + }); - TMixture(buf, obj) { - buf.classStreamer(obj, 'TMaterial'); - obj.fNmixt = buf.ntoi4(); - obj.fAmixt = buf.readFastArray(buf.ntoi4(), kFloat); - obj.fZmixt = buf.readFastArray(buf.ntoi4(), kFloat); - obj.fWmixt = buf.readFastArray(buf.ntoi4(), kFloat); - }, + var scale = (API.scale = API.__private__.scale = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.scale"); + } + if (apiMode === ApiMode.COMPAT) { + return number * scaleFactor; + } else if (apiMode === ApiMode.ADVANCED) { + return number; + } + }); - TVirtualPerfStats: clTObject, // use directly TObject streamer + var transformY = function(y) { + if (apiMode === ApiMode.COMPAT) { + return getPageHeight() - y; + } else if (apiMode === ApiMode.ADVANCED) { + return y; + } + }; - TMethodCall: clTObject -}; + var transformScaleY = function(y) { + return scale(transformY(y)); + }; + /** + * @name setPrecision + * @memberof jsPDF# + * @function + * @instance + * @param {string} precision + * @returns {jsPDF} + */ + API.__private__.setPrecision = API.setPrecision = function(value) { + if (typeof parseInt(value, 10) === "number") { + precision = parseInt(value, 10); + } + }; -/** @summary Add custom streamer - * @public */ -function addUserStreamer(type, user_streamer) { - CustomStreamers[type] = user_streamer; -} + var fileId = "00000000000000000000000000000000"; -/** @summary these are streamers which do not handle version regularly - * @desc used for special classes like TRef or TBasket - * @private */ -const DirectStreamers = { - // do nothing for these classes - TQObject() {}, - TGraphStruct() {}, - TGraphNode() {}, - TGraphEdge() {}, + var getFileId = (API.__private__.getFileId = function() { + return fileId; + }); - TDatime(buf, obj) { - obj.fDatime = buf.ntou4(); - }, + var setFileId = (API.__private__.setFileId = function(value) { + if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) { + fileId = value.toUpperCase(); + } else { + fileId = fileId + .split("") + .map(function() { + return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16)); + }) + .join(""); + } + + if (encryptionOptions !== null) { + encryption = new PDFSecurity( + encryptionOptions.userPermissions, + encryptionOptions.userPassword, + encryptionOptions.ownerPassword, + fileId + ); + } + return fileId; + }); + + /** + * @name setFileId + * @memberof jsPDF# + * @function + * @instance + * @param {string} value GUID. + * @returns {jsPDF} + */ + API.setFileId = function(value) { + setFileId(value); + return this; + }; + + /** + * @name getFileId + * @memberof jsPDF# + * @function + * @instance + * + * @returns {string} GUID. + */ + API.getFileId = function() { + return getFileId(); + }; + + var creationDate; + + var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function( + parmDate + ) { + var result = ""; + var tzoffset = parmDate.getTimezoneOffset(), + tzsign = tzoffset < 0 ? "+" : "-", + tzhour = Math.floor(Math.abs(tzoffset / 60)), + tzmin = Math.abs(tzoffset % 60), + timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(""); + + result = [ + "D:", + parmDate.getFullYear(), + padd2(parmDate.getMonth() + 1), + padd2(parmDate.getDate()), + padd2(parmDate.getHours()), + padd2(parmDate.getMinutes()), + padd2(parmDate.getSeconds()), + timeZoneString + ].join(""); + return result; + }); + + var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function( + parmPDFDate + ) { + var year = parseInt(parmPDFDate.substr(2, 4), 10); + var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1; + var date = parseInt(parmPDFDate.substr(8, 2), 10); + var hour = parseInt(parmPDFDate.substr(10, 2), 10); + var minutes = parseInt(parmPDFDate.substr(12, 2), 10); + var seconds = parseInt(parmPDFDate.substr(14, 2), 10); + // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10); + // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10); + + var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0); + return resultingDate; + }); + + var setCreationDate = (API.__private__.setCreationDate = function(date) { + var tmpCreationDateString; + var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/; + if (typeof date === "undefined") { + date = new Date(); + } + + if (date instanceof Date) { + tmpCreationDateString = convertDateToPDFDate(date); + } else if (regexPDFCreationDate.test(date)) { + tmpCreationDateString = date; + } else { + throw new Error("Invalid argument passed to jsPDF.setCreationDate"); + } + creationDate = tmpCreationDateString; + return creationDate; + }); + + var getCreationDate = (API.__private__.getCreationDate = function(type) { + var result = creationDate; + if (type === "jsDate") { + result = convertPDFDateToDate(creationDate); + } + return result; + }); + + /** + * @name setCreationDate + * @memberof jsPDF# + * @function + * @instance + * @param {Object} date + * @returns {jsPDF} + */ + API.setCreationDate = function(date) { + setCreationDate(date); + return this; + }; + + /** + * @name getCreationDate + * @memberof jsPDF# + * @function + * @instance + * @param {Object} type + * @returns {Object} + */ + API.getCreationDate = function(type) { + return getCreationDate(type); + }; - TKey(buf, key) { - key.fNbytes = buf.ntoi4(); - key.fVersion = buf.ntoi2(); - key.fObjlen = buf.ntou4(); - key.fDatime = buf.classStreamer({}, clTDatime); - key.fKeylen = buf.ntou2(); - key.fCycle = buf.ntou2(); - if (key.fVersion > 1000) { - key.fSeekKey = buf.ntou8(); - buf.shift(8); // skip seekPdir - } else { - key.fSeekKey = buf.ntou4(); - buf.shift(4); // skip seekPdir - } - key.fClassName = buf.readTString(); - key.fName = buf.readTString(); - key.fTitle = buf.readTString(); - }, + var padd2 = (API.__private__.padd2 = function(number) { + return ("0" + parseInt(number)).slice(-2); + }); - TDirectory(buf, dir) { - const version = buf.ntou2(); - dir.fDatimeC = buf.classStreamer({}, clTDatime); - dir.fDatimeM = buf.classStreamer({}, clTDatime); - dir.fNbytesKeys = buf.ntou4(); - dir.fNbytesName = buf.ntou4(); - dir.fSeekDir = (version > 1000) ? buf.ntou8() : buf.ntou4(); - dir.fSeekParent = (version > 1000) ? buf.ntou8() : buf.ntou4(); - dir.fSeekKeys = (version > 1000) ? buf.ntou8() : buf.ntou4(); - // if ((version % 1000) > 2) buf.shift(18); // skip fUUID - }, + var padd2Hex = (API.__private__.padd2Hex = function(hexString) { + hexString = hexString.toString(); + return ("00" + hexString).substr(hexString.length); + }); - TBasket(buf, obj) { - buf.classStreamer(obj, clTKey); - const ver = buf.readVersion(); - obj.fBufferSize = buf.ntoi4(); - obj.fNevBufSize = buf.ntoi4(); - obj.fNevBuf = buf.ntoi4(); - obj.fLast = buf.ntoi4(); - if (obj.fLast > obj.fBufferSize) obj.fBufferSize = obj.fLast; - const flag = buf.ntoi1(); + var objectNumber = 0; // 'n' Current object number + var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. + var content = []; + var contentLength = 0; + var additionalObjects = []; + + var pages = []; + var currentPage; + var hasCustomDestination = false; + var outputDestination = content; + + var resetDocument = function() { + //reset fields relevant for objectNumber generation and xref. + objectNumber = 0; + contentLength = 0; + content = []; + offsets = []; + additionalObjects = []; + + rootDictionaryObjId = newObjectDeferred(); + resourceDictionaryObjId = newObjectDeferred(); + }; - if (flag === 0) return; + API.__private__.setCustomOutputDestination = function(destination) { + hasCustomDestination = true; + outputDestination = destination; + }; + var setOutputDestination = function(destination) { + if (!hasCustomDestination) { + outputDestination = destination; + } + }; - if ((flag % 10) !== 2) { - if (obj.fNevBuf) { - obj.fEntryOffset = buf.readFastArray(buf.ntoi4(), kInt); - if ((flag > 20) && (flag < 40)) { - for (let i = 0, kDisplacementMask = 0xFF000000; i < obj.fNevBuf; ++i) - obj.fEntryOffset[i] &= ~kDisplacementMask; - } - } + API.__private__.resetCustomOutputDestination = function() { + hasCustomDestination = false; + outputDestination = content; + }; - if (flag > 40) - obj.fDisplacement = buf.readFastArray(buf.ntoi4(), kInt); - } + var out = (API.__private__.out = function(string) { + string = string.toString(); + contentLength += string.length + 1; + outputDestination.push(string); - if ((flag === 1) || (flag > 10)) { - // here is reading of raw data - const sz = (ver.val <= 1) ? buf.ntoi4() : obj.fLast; + return outputDestination; + }); - if (sz > obj.fKeylen) { - // buffer includes again complete TKey data - exclude it - const blob = buf.extract([buf.o + obj.fKeylen, sz - obj.fKeylen]); - obj.fBufferRef = new TBuffer(blob, 0, buf.fFile, sz - obj.fKeylen); - obj.fBufferRef.fTagOffset = obj.fKeylen; - } + var write = (API.__private__.write = function(value) { + return out( + arguments.length === 1 + ? value.toString() + : Array.prototype.join.call(arguments, " ") + ); + }); - buf.shift(sz); - } - }, + var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) { + var len = data.length, + ab = new ArrayBuffer(len), + u8 = new Uint8Array(ab); - TRef(buf, obj) { - buf.classStreamer(obj, clTObject); - if (obj.fBits & kHasUUID) - obj.fUUID = buf.readTString(); - else - obj.fPID = buf.ntou2(); - }, + while (len--) u8[len] = data.charCodeAt(len); + return ab; + }); - 'TMatrixTSym': (buf, obj) => { - buf.classStreamer(obj, 'TMatrixTBase'); - obj.fElements = new Float32Array(obj.fNelems); - const arr = buf.readFastArray((obj.fNrows * (obj.fNcols + 1)) / 2, kFloat); - for (let i = 0, cnt = 0; i < obj.fNrows; ++i) { - for (let j = i; j < obj.fNcols; ++j) - obj.fElements[j * obj.fNcols + i] = obj.fElements[i * obj.fNcols + j] = arr[cnt++]; - } - }, + var standardFonts = [ + ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"], + ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"], + ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"], + ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"], + ["Courier", "courier", "normal", "WinAnsiEncoding"], + ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"], + ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"], + ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"], + ["Times-Roman", "times", "normal", "WinAnsiEncoding"], + ["Times-Bold", "times", "bold", "WinAnsiEncoding"], + ["Times-Italic", "times", "italic", "WinAnsiEncoding"], + ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"], + ["ZapfDingbats", "zapfdingbats", "normal", null], + ["Symbol", "symbol", "normal", null] + ]; - 'TMatrixTSym': (buf, obj) => { - buf.classStreamer(obj, 'TMatrixTBase'); - obj.fElements = new Float64Array(obj.fNelems); - const arr = buf.readFastArray((obj.fNrows * (obj.fNcols + 1)) / 2, kDouble); - for (let i = 0, cnt = 0; i < obj.fNrows; ++i) { - for (let j = i; j < obj.fNcols; ++j) - obj.fElements[j * obj.fNcols + i] = obj.fElements[i * obj.fNcols + j] = arr[cnt++]; - } - } -}; + API.__private__.getStandardFonts = function() { + return standardFonts; + }; + var activeFontSize = options.fontSize || 16; + + /** + * Sets font size for upcoming text elements. + * + * @param {number} size Font size in points. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFontSize + */ + API.__private__.setFontSize = API.setFontSize = function(size) { + if (apiMode === ApiMode.ADVANCED) { + activeFontSize = size / scaleFactor; + } else { + activeFontSize = size; + } + return this; + }; -/** @summary Returns type id by its name - * @private */ -function getTypeId(typname, norecursion) { - switch (typname) { - case 'bool': - case 'Bool_t': return kBool; - case 'char': - case 'signed char': - case 'Char_t': return kChar; - case 'Color_t': - case 'Style_t': - case 'Width_t': - case 'short': - case 'Short_t': return kShort; - case 'int': - case 'EErrorType': - case 'Int_t': return kInt; - case 'long': - case 'Long_t': return kLong; - case 'float': - case 'Float_t': return kFloat; - case 'double': - case 'Double_t': return kDouble; - case 'unsigned char': - case 'UChar_t': return kUChar; - case 'unsigned short': - case 'UShort_t': return kUShort; - case 'unsigned': - case 'unsigned int': - case 'UInt_t': return kUInt; - case 'unsigned long': - case 'ULong_t': return kULong; - case 'int64_t': - case 'long long': - case 'Long64_t': return kLong64; - case 'uint64_t': - case 'unsigned long long': - case 'ULong64_t': return kULong64; - case 'Double32_t': return kDouble32; - case 'Float16_t': return kFloat16; - case 'char*': - case 'const char*': - case 'const Char_t*': return kCharStar; - } + /** + * Gets the fontsize for upcoming text elements. + * + * @function + * @instance + * @returns {number} + * @memberof jsPDF# + * @name getFontSize + */ + var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() { + if (apiMode === ApiMode.COMPAT) { + return activeFontSize; + } else { + return activeFontSize * scaleFactor; + } + }); - if (!norecursion) { - const replace = CustomStreamers[typname]; - if (isStr(replace)) return getTypeId(replace, true); - } + var R2L = options.R2L || false; + + /** + * Set value of R2L functionality. + * + * @param {boolean} value + * @function + * @instance + * @returns {jsPDF} jsPDF-instance + * @memberof jsPDF# + * @name setR2L + */ + API.__private__.setR2L = API.setR2L = function(value) { + R2L = value; + return this; + }; - return -1; -} + /** + * Get value of R2L functionality. + * + * @function + * @instance + * @returns {boolean} jsPDF-instance + * @memberof jsPDF# + * @name getR2L + */ + API.__private__.getR2L = API.getR2L = function() { + return R2L; + }; -/** @summary create element of the streamer - * @private */ -function createStreamerElement(name, typename, file) { - const elem = { - _typename: clTStreamerElement, fName: name, fTypeName: typename, - fType: 0, fSize: 0, fArrayLength: 0, fArrayDim: 0, fMaxIndex: [0, 0, 0, 0, 0], - fXmin: 0, fXmax: 0, fFactor: 0 - }; + var zoomMode; // default: 1; - if (isStr(typename)) { - elem.fType = getTypeId(typename); - if ((elem.fType < 0) && file && file.fBasicTypes[typename]) - elem.fType = file.fBasicTypes[typename]; - } else { - elem.fType = typename; - typename = elem.fTypeName = BasicTypeNames[elem.fType] || 'int'; - } + var setZoomMode = (API.__private__.setZoomMode = function(zoom) { + var validZoomModes = [ + undefined, + null, + "fullwidth", + "fullheight", + "fullpage", + "original" + ]; - if (elem.fType > 0) return elem; // basic type + if (/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(zoom)) { + zoomMode = zoom; + } else if (!isNaN(zoom)) { + zoomMode = parseInt(zoom, 10); + } else if (validZoomModes.indexOf(zoom) !== -1) { + zoomMode = zoom; + } else { + throw new Error( + 'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' + + zoom + + '" is not recognized.' + ); + } + }); - // check if there are STL containers - const pos = typename.indexOf('<'); - let stltype = kNotSTL; - if ((pos > 0) && (typename.indexOf('>') > pos + 2)) { - for (let stl = 1; stl < StlNames.length; ++stl) { - if (typename.slice(0, pos) === StlNames[stl]) { - stltype = stl; break; - } - } - } + API.__private__.getZoomMode = function() { + return zoomMode; + }; - if (stltype !== kNotSTL) { - elem._typename = clTStreamerSTL; - elem.fType = kStreamer; - elem.fSTLtype = stltype; - elem.fCtype = 0; - return elem; - } + var pageMode; // default: 'UseOutlines'; + var setPageMode = (API.__private__.setPageMode = function(pmode) { + var validPageModes = [ + undefined, + null, + "UseNone", + "UseOutlines", + "UseThumbs", + "FullScreen" + ]; - const isptr = (typename.lastIndexOf('*') === typename.length - 1); + if (validPageModes.indexOf(pmode) == -1) { + throw new Error( + 'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + + pmode + + '" is not recognized.' + ); + } + pageMode = pmode; + }); - if (isptr) - elem.fTypeName = typename = typename.slice(0, typename.length - 1); + API.__private__.getPageMode = function() { + return pageMode; + }; - if (getArrayKind(typename) === 0) { - elem.fType = kTString; - return elem; - } + var layoutMode; // default: 'continuous'; + var setLayoutMode = (API.__private__.setLayoutMode = function(layout) { + var validLayoutModes = [ + undefined, + null, + "continuous", + "single", + "twoleft", + "tworight", + "two" + ]; - elem.fType = isptr ? kAnyP : kAny; + if (validLayoutModes.indexOf(layout) == -1) { + throw new Error( + 'Layout mode must be one of continuous, single, twoleft, tworight. "' + + layout + + '" is not recognized.' + ); + } + layoutMode = layout; + }); - return elem; -} + API.__private__.getLayoutMode = function() { + return layoutMode; + }; + /** + * Set the display mode options of the page like zoom and layout. + * + * @name setDisplayMode + * @memberof jsPDF# + * @function + * @instance + * @param {integer|String} zoom You can pass an integer or percentage as + * a string. 2 will scale the document up 2x, '200%' will scale up by the + * same amount. You can also set it to 'fullwidth', 'fullheight', + * 'fullpage', or 'original'. + * + * Only certain PDF readers support this, such as Adobe Acrobat. + * + * @param {string} layout Layout mode can be: 'continuous' - this is the + * default continuous scroll. 'single' - the single page mode only shows one + * page at a time. 'twoleft' - two column left mode, first page starts on + * the left, and 'tworight' - pages are laid out in two columns, with the + * first page on the right. This would be used for books. + * @param {string} pmode 'UseOutlines' - it shows the + * outline of the document on the left. 'UseThumbs' - shows thumbnails along + * the left. 'FullScreen' - prompts the user to enter fullscreen mode. + * + * @returns {jsPDF} + */ + API.__private__.setDisplayMode = API.setDisplayMode = function( + zoom, + layout, + pmode + ) { + setZoomMode(zoom); + setLayoutMode(layout); + setPageMode(pmode); + return this; + }; -/** @summary Function creates streamer for std::pair object - * @private */ -function getPairStreamer(si, typname, file) { - if (!si) { - if (typname.indexOf('pair') !== 0) return null; - - si = file.findStreamerInfo(typname); - - if (!si) { - let p1 = typname.indexOf('<'); - const p2 = typname.lastIndexOf('>'); - function GetNextName() { - let res = '', p = p1 + 1, cnt = 0; - while ((p < p2) && (cnt >= 0)) { - switch (typname[p]) { - case '<': cnt++; break; - case ',': if (cnt === 0) cnt--; break; - case '>': cnt--; break; - } - if (cnt >= 0) res += typname[p]; - p++; - } - p1 = p - 1; - return res.trim(); - } - si = { _typename: 'TStreamerInfo', fVersion: 1, fName: typname, fElements: create$1(clTList) }; - si.fElements.Add(createStreamerElement('first', GetNextName(), file)); - si.fElements.Add(createStreamerElement('second', GetNextName(), file)); - } - } + var documentProperties = { + title: "", + subject: "", + author: "", + keywords: "", + creator: "" + }; - const streamer = file.getStreamer(typname, null, si); - if (!streamer) return null; + API.__private__.getDocumentProperty = function(key) { + if (Object.keys(documentProperties).indexOf(key) === -1) { + throw new Error("Invalid argument passed to jsPDF.getDocumentProperty"); + } + return documentProperties[key]; + }; - if (streamer.length !== 2) { - console.error(`Streamer for pair class contains ${streamer.length} elements`); - return null; - } + API.__private__.getDocumentProperties = function() { + return documentProperties; + }; - for (let nn = 0; nn < 2; ++nn) { - if (streamer[nn].readelem && !streamer[nn].pair_name) { - streamer[nn].pair_name = (nn === 0) ? 'first' : 'second'; - streamer[nn].func = function(buf, obj) { - obj[this.pair_name] = this.readelem(buf); - }; + /** + * Adds a properties to the PDF document. + * + * @param {Object} A property_name-to-property_value object structure. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setDocumentProperties + */ + API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function( + properties + ) { + // copying only those properties we can render. + for (var property in documentProperties) { + if (documentProperties.hasOwnProperty(property) && properties[property]) { + documentProperties[property] = properties[property]; } - } + } + return this; + }; - return streamer; -} + API.__private__.setDocumentProperty = function(key, value) { + if (Object.keys(documentProperties).indexOf(key) === -1) { + throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty"); + } + return (documentProperties[key] = value); + }; + var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font. + var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont() + var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle + var fontStateStack = []; // + var patterns = {}; // collection of pattern objects + var patternMap = {}; // see fonts + var gStates = {}; // collection of graphic state objects + var gStatesMap = {}; // see fonts + var activeGState = null; + var scaleFactor; // Scale factor + var page = 0; + var pagesContext = []; + var events = new PubSub(API); + var hotfixes = options.hotfixes || []; + + var renderTargets = {}; + var renderTargetMap = {}; + var renderTargetStack = []; + var pageX; + var pageY; + var pageMatrix; // only used for FormObjects + + /** + * A matrix object for 2D homogenous transformations:
+ * | a b 0 |
+ * | c d 0 |
+ * | e f 1 |
+ * pdf multiplies matrices righthand: v' = v x m1 x m2 x ... + * + * @class + * @name Matrix + * @param {number} sx + * @param {number} shy + * @param {number} shx + * @param {number} sy + * @param {number} tx + * @param {number} ty + * @constructor + */ + var Matrix = function(sx, shy, shx, sy, tx, ty) { + if (!(this instanceof Matrix)) { + return new Matrix(sx, shy, shx, sy, tx, ty); + } -/** @summary create member entry for streamer element - * @desc used for reading of data - * @private */ -function createMemberStreamer(element, file) { - const member = { - name: element.fName, type: element.fType, - fArrayLength: element.fArrayLength, - fArrayDim: element.fArrayDim, - fMaxIndex: element.fMaxIndex - }; + if (isNaN(sx)) sx = 1; + if (isNaN(shy)) shy = 0; + if (isNaN(shx)) shx = 0; + if (isNaN(sy)) sy = 1; + if (isNaN(tx)) tx = 0; + if (isNaN(ty)) ty = 0; - if (element.fTypeName === kBaseClass) { - if (getArrayKind(member.name) > 0) { - // this is workaround for arrays as base class - // we create 'fArray' member, which read as any other data member - member.name = 'fArray'; - member.type = kAny; - } else { - // create streamer for base class - member.type = kBase; - // this.getStreamer(element.fName); - } - } + this._matrix = [sx, shy, shx, sy, tx, ty]; + }; - switch (member.type) { - case kBase: - member.base = element.fBaseVersion; // indicate base class - member.basename = element.fName; // keep class name - member.func = function(buf, obj) { buf.classStreamer(obj, this.basename); }; - break; - case kShort: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi2(); }; break; - case kInt: - case kCounter: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi4(); }; break; - case kLong: - case kLong64: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi8(); }; break; - case kDouble: - member.func = function(buf, obj) { obj[this.name] = buf.ntod(); }; break; - case kFloat: - member.func = function(buf, obj) { obj[this.name] = buf.ntof(); }; break; - case kLegacyChar: - case kUChar: - member.func = function(buf, obj) { obj[this.name] = buf.ntou1(); }; break; - case kUShort: - member.func = function(buf, obj) { obj[this.name] = buf.ntou2(); }; break; - case kBits: - case kUInt: - member.func = function(buf, obj) { obj[this.name] = buf.ntou4(); }; break; - case kULong64: - case kULong: - member.func = function(buf, obj) { obj[this.name] = buf.ntou8(); }; break; - case kBool: - member.func = function(buf, obj) { obj[this.name] = buf.ntou1() !== 0; }; break; - case kOffsetL + kBool: - case kOffsetL + kInt: - case kOffsetL + kCounter: - case kOffsetL + kDouble: - case kOffsetL + kUChar: - case kOffsetL + kShort: - case kOffsetL + kUShort: - case kOffsetL + kBits: - case kOffsetL + kUInt: - case kOffsetL + kULong: - case kOffsetL + kULong64: - case kOffsetL + kLong: - case kOffsetL + kLong64: - case kOffsetL + kFloat: - if (element.fArrayDim < 2) { - member.arrlength = element.fArrayLength; - member.func = function(buf, obj) { - obj[this.name] = buf.readFastArray(this.arrlength, this.type - kOffsetL); - }; - } else { - member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; - member.minus1 = true; - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => - buf.readFastArray(handle.arrlength, handle.type - kOffsetL)); - }; - } - break; - case kOffsetL + kChar: - if (element.fArrayDim < 2) { - member.arrlength = element.fArrayLength; - member.func = function(buf, obj) { - obj[this.name] = buf.readFastString(this.arrlength); - }; - } else { - member.minus1 = true; // one dimension used for char* - member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => - buf.readFastString(handle.arrlength)); - }; - } - break; - case kOffsetP + kBool: - case kOffsetP + kInt: - case kOffsetP + kDouble: - case kOffsetP + kUChar: - case kOffsetP + kShort: - case kOffsetP + kUShort: - case kOffsetP + kBits: - case kOffsetP + kUInt: - case kOffsetP + kULong: - case kOffsetP + kULong64: - case kOffsetP + kLong: - case kOffsetP + kLong64: - case kOffsetP + kFloat: - member.cntname = element.fCountName; - member.func = function(buf, obj) { - obj[this.name] = (buf.ntou1() === 1) ? buf.readFastArray(obj[this.cntname], this.type - kOffsetP) : []; - }; - break; - case kOffsetP + kChar: - member.cntname = element.fCountName; - member.func = function(buf, obj) { - obj[this.name] = (buf.ntou1() === 1) ? buf.readFastString(obj[this.cntname]) : null; - }; - break; - case kDouble32: - case kOffsetL + kDouble32: - case kOffsetP + kDouble32: - member.double32 = true; - // eslint-disable-next-line no-fallthrough - case kFloat16: - case kOffsetL + kFloat16: - case kOffsetP + kFloat16: - if (element.fFactor !== 0) { - member.factor = 1 / element.fFactor; - member.min = element.fXmin; - member.read = function(buf) { return buf.ntou4() * this.factor + this.min; }; - } else - if ((element.fXmin === 0) && member.double32) - member.read = function(buf) { return buf.ntof(); }; - else { - member.nbits = Math.round(element.fXmin); - if (member.nbits === 0) member.nbits = 12; - member.dv = new DataView(new ArrayBuffer(8), 0); // used to cast from uint32 to float32 - member.read = function(buf) { - const theExp = buf.ntou1(), theMan = buf.ntou2(); - this.dv.setUint32(0, (theExp << 23) | ((theMan & ((1 << (this.nbits + 1)) - 1)) << (23 - this.nbits))); - return ((1 << (this.nbits + 1) & theMan) ? -1 : 1) * this.dv.getFloat32(0); - }; - } + /** + * @name sx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "sx", { + get: function() { + return this._matrix[0]; + }, + set: function(value) { + this._matrix[0] = value; + } + }); - member.readarr = function(buf, len) { - const arr = this.double32 ? new Float64Array(len) : new Float32Array(len); - for (let n = 0; n < len; ++n) arr[n] = this.read(buf); - return arr; - }; + /** + * @name shy + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "shy", { + get: function() { + return this._matrix[1]; + }, + set: function(value) { + this._matrix[1] = value; + } + }); - if (member.type < kOffsetL) - member.func = function(buf, obj) { obj[this.name] = this.read(buf); }; - else - if (member.type > kOffsetP) { - member.cntname = element.fCountName; - member.func = function(buf, obj) { - obj[this.name] = (buf.ntou1() === 1) ? this.readarr(buf, obj[this.cntname]) : null; - }; - } else - if (element.fArrayDim < 2) { - member.arrlength = element.fArrayLength; - member.func = function(buf, obj) { obj[this.name] = this.readarr(buf, this.arrlength); }; - } else { - member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; - member.minus1 = true; - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => handle.readarr(buf, handle.arrlength)); - }; - } - break; + /** + * @name shx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "shx", { + get: function() { + return this._matrix[2]; + }, + set: function(value) { + this._matrix[2] = value; + } + }); - case kAnyP: - case kObjectP: - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, buf => buf.readObjectAny()); - }; - break; + /** + * @name sy + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "sy", { + get: function() { + return this._matrix[3]; + }, + set: function(value) { + this._matrix[3] = value; + } + }); - case kAny: - case kAnyp: - case kObjectp: - case kObject: { - let classname = (element.fTypeName === kBaseClass) ? element.fName : element.fTypeName; - if (classname[classname.length - 1] === '*') - classname = classname.slice(0, classname.length - 1); + /** + * @name tx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "tx", { + get: function() { + return this._matrix[4]; + }, + set: function(value) { + this._matrix[4] = value; + } + }); - const arrkind = getArrayKind(classname); + /** + * @name ty + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "ty", { + get: function() { + return this._matrix[5]; + }, + set: function(value) { + this._matrix[5] = value; + } + }); - if (arrkind > 0) { - member.arrkind = arrkind; - member.func = function(buf, obj) { obj[this.name] = buf.readFastArray(buf.ntou4(), this.arrkind); }; - } else if (arrkind === 0) - member.func = function(buf, obj) { obj[this.name] = buf.readTString(); }; - else { - member.classname = classname; + Object.defineProperty(Matrix.prototype, "a", { + get: function() { + return this._matrix[0]; + }, + set: function(value) { + this._matrix[0] = value; + } + }); - if (element.fArrayLength > 1) { - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => buf.classStreamer({}, handle.classname)); - }; - } else { - member.func = function(buf, obj) { - obj[this.name] = buf.classStreamer({}, this.classname); - }; - } - } - break; - } - case kOffsetL + kObject: - case kOffsetL + kAny: - case kOffsetL + kAnyp: - case kOffsetL + kObjectp: { - let classname = element.fTypeName; - if (classname[classname.length - 1] === '*') - classname = classname.slice(0, classname.length - 1); + Object.defineProperty(Matrix.prototype, "b", { + get: function() { + return this._matrix[1]; + }, + set: function(value) { + this._matrix[1] = value; + } + }); - member.arrkind = getArrayKind(classname); - if (member.arrkind < 0) member.classname = classname; - member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => { - if (handle.arrkind > 0) return buf.readFastArray(buf.ntou4(), handle.arrkind); - if (handle.arrkind === 0) return buf.readTString(); - return buf.classStreamer({}, handle.classname); - }); - }; - break; - } - case kChar: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi1(); }; break; - case kCharStar: - member.func = function(buf, obj) { - const len = buf.ntoi4(); - obj[this.name] = buf.substring(buf.o, buf.o + len); - buf.o += len; - }; - break; - case kTString: - member.func = function(buf, obj) { obj[this.name] = buf.readTString(); }; - break; - case kTObject: - case kTNamed: - member.typename = element.fTypeName; - member.func = function(buf, obj) { obj[this.name] = buf.classStreamer({}, this.typename); }; - break; - case kOffsetL + kTString: - case kOffsetL + kTObject: - case kOffsetL + kTNamed: - member.typename = element.fTypeName; - member.func = function(buf, obj) { - const ver = buf.readVersion(); - obj[this.name] = buf.readNdimArray(this, (buf, handle) => { - if (handle.typename === clTString) return buf.readTString(); - return buf.classStreamer({}, handle.typename); - }); - buf.checkByteCount(ver, this.typename + '[]'); - }; - break; - case kStreamLoop: - case kOffsetL + kStreamLoop: - member.typename = element.fTypeName; - member.cntname = element.fCountName; + Object.defineProperty(Matrix.prototype, "c", { + get: function() { + return this._matrix[2]; + }, + set: function(value) { + this._matrix[2] = value; + } + }); - if (member.typename.lastIndexOf('**') > 0) { - member.typename = member.typename.slice(0, member.typename.lastIndexOf('**')); - member.isptrptr = true; - } else { - member.typename = member.typename.slice(0, member.typename.lastIndexOf('*')); - member.isptrptr = false; - } + Object.defineProperty(Matrix.prototype, "d", { + get: function() { + return this._matrix[3]; + }, + set: function(value) { + this._matrix[3] = value; + } + }); - if (member.isptrptr) - member.readitem = function(buf) { return buf.readObjectAny(); }; - else { - member.arrkind = getArrayKind(member.typename); - if (member.arrkind > 0) - member.readitem = function(buf) { return buf.readFastArray(buf.ntou4(), this.arrkind); }; - else if (member.arrkind === 0) - member.readitem = function(buf) { return buf.readTString(); }; - else - member.readitem = function(buf) { return buf.classStreamer({}, this.typename); }; - } + Object.defineProperty(Matrix.prototype, "e", { + get: function() { + return this._matrix[4]; + }, + set: function(value) { + this._matrix[4] = value; + } + }); - if (member.readitem !== undefined) { - member.read_loop = function(buf, cnt) { - return buf.readNdimArray(this, (buf2, member2) => { - const itemarr = new Array(cnt); - for (let i = 0; i < cnt; ++i) - itemarr[i] = member2.readitem(buf2); - return itemarr; - }); - }; + Object.defineProperty(Matrix.prototype, "f", { + get: function() { + return this._matrix[5]; + }, + set: function(value) { + this._matrix[5] = value; + } + }); - member.func = function(buf, obj) { - const ver = buf.readVersion(), - res = this.read_loop(buf, obj[this.cntname]); - obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; - }; - member.branch_func = function(buf, obj) { - // this is special functions, used by branch in the STL container - const ver = buf.readVersion(), sz0 = obj[this.stl_size], res = new Array(sz0); + /** + * @name rotation + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "rotation", { + get: function() { + return Math.atan2(this.shx, this.sx); + } + }); - for (let loop0 = 0; loop0 < sz0; ++loop0) { - const cnt = obj[this.cntname][loop0]; - res[loop0] = this.read_loop(buf, cnt); - } - obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; - }; + /** + * @name scaleX + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "scaleX", { + get: function() { + return this.decompose().scale.sx; + } + }); - member.objs_branch_func = function(buf, obj) { - // special function when branch read as part of complete object - // objects already preallocated and only appropriate member must be set - // see code in JSRoot.tree.js for reference + /** + * @name scaleY + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "scaleY", { + get: function() { + return this.decompose().scale.sy; + } + }); - const ver = buf.readVersion(), - arr = obj[this.name0]; // objects array where reading is done + /** + * @name isIdentity + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "isIdentity", { + get: function() { + if (this.sx !== 1) { + return false; + } + if (this.shy !== 0) { + return false; + } + if (this.shx !== 0) { + return false; + } + if (this.sy !== 1) { + return false; + } + if (this.tx !== 0) { + return false; + } + if (this.ty !== 0) { + return false; + } + return true; + } + }); - for (let loop0 = 0; loop0 < arr.length; ++loop0) { - const obj1 = this.get(arr, loop0), cnt = obj1[this.cntname]; - obj1[this.name] = this.read_loop(buf, cnt); - } + /** + * Join the Matrix Values to a String + * + * @function join + * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them. + * @returns {string} A string with all array elements joined. + * @memberof Matrix# + */ + Matrix.prototype.join = function(separator) { + return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty] + .map(hpf) + .join(separator); + }; - buf.checkByteCount(ver, this.typename); - }; - } else { - console.error(`fail to provide function for ${element.fName} (${element.fTypeName}) typ = ${element.fType}`); - member.func = function(buf, obj) { - const ver = buf.readVersion(); - buf.checkByteCount(ver); - obj[this.name] = null; - }; - } + /** + * Multiply the matrix with given Matrix + * + * @function multiply + * @param matrix + * @returns {Matrix} + * @memberof Matrix# + */ + Matrix.prototype.multiply = function(matrix) { + var sx = matrix.sx * this.sx + matrix.shy * this.shx; + var shy = matrix.sx * this.shy + matrix.shy * this.sy; + var shx = matrix.shx * this.sx + matrix.sy * this.shx; + var sy = matrix.shx * this.shy + matrix.sy * this.sy; + var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx; + var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty; + + return new Matrix(sx, shy, shx, sy, tx, ty); + }; - break; + /** + * @function decompose + * @memberof Matrix# + */ + Matrix.prototype.decompose = function() { + var a = this.sx; + var b = this.shy; + var c = this.shx; + var d = this.sy; + var e = this.tx; + var f = this.ty; + + var scaleX = Math.sqrt(a * a + b * b); + a /= scaleX; + b /= scaleX; + + var shear = a * c + b * d; + c -= a * shear; + d -= b * shear; + + var scaleY = Math.sqrt(c * c + d * d); + c /= scaleY; + d /= scaleY; + shear /= scaleY; + + if (a * d < b * c) { + a = -a; + b = -b; + shear = -shear; + scaleX = -scaleX; + } - case kStreamer: { - member.typename = element.fTypeName; + return { + scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0), + translate: new Matrix(1, 0, 0, 1, e, f), + rotate: new Matrix(a, b, -b, a, 0, 0), + skew: new Matrix(1, 0, shear, 1, 0, 0) + }; + }; - const stl = (element.fSTLtype || 0) % 40; - if ((element._typename === 'TStreamerSTLstring') || - (member.typename === 'string') || (member.typename === 'string*')) - member.readelem = buf => buf.readTString(); - else if ((stl === kSTLvector) || (stl === kSTLlist) || - (stl === kSTLdeque) || (stl === kSTLset) || (stl === kSTLmultiset)) { - const p1 = member.typename.indexOf('<'), - p2 = member.typename.lastIndexOf('>'); + /** + * @function toString + * @memberof Matrix# + */ + Matrix.prototype.toString = function(parmPrecision) { + return this.join(" "); + }; - member.conttype = member.typename.slice(p1 + 1, p2).trim(); - member.typeid = getTypeId(member.conttype); - if ((member.typeid < 0) && file.fBasicTypes[member.conttype]) { - member.typeid = file.fBasicTypes[member.conttype]; - console.log(`!!! Reuse basic type ${member.conttype} from file streamer infos`); - } + /** + * @function inversed + * @memberof Matrix# + */ + Matrix.prototype.inversed = function() { + var a = this.sx, + b = this.shy, + c = this.shx, + d = this.sy, + e = this.tx, + f = this.ty; + + var quot = 1 / (a * d - b * c); + + var aInv = d * quot; + var bInv = -b * quot; + var cInv = -c * quot; + var dInv = a * quot; + var eInv = -aInv * e - cInv * f; + var fInv = -bInv * e - dInv * f; + + return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv); + }; - // check - if (element.fCtype && (element.fCtype < 20) && (element.fCtype !== member.typeid)) { - console.warn(`Contained type ${member.conttype} not recognized as basic type ${element.fCtype} FORCE`); - member.typeid = element.fCtype; - } + /** + * @function applyToPoint + * @memberof Matrix# + */ + Matrix.prototype.applyToPoint = function(pt) { + var x = pt.x * this.sx + pt.y * this.shx + this.tx; + var y = pt.x * this.shy + pt.y * this.sy + this.ty; + return new Point(x, y); + }; - if (member.typeid > 0) { - member.readelem = function(buf) { - return buf.readFastArray(buf.ntoi4(), this.typeid); - }; - } else { - member.isptr = false; + /** + * @function applyToRectangle + * @memberof Matrix# + */ + Matrix.prototype.applyToRectangle = function(rect) { + var pt1 = this.applyToPoint(rect); + var pt2 = this.applyToPoint(new Point(rect.x + rect.w, rect.y + rect.h)); + return new Rectangle(pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y); + }; - if (member.conttype.lastIndexOf('*') === member.conttype.length - 1) { - member.isptr = true; - member.conttype = member.conttype.slice(0, member.conttype.length - 1); - } + /** + * Clone the Matrix + * + * @function clone + * @memberof Matrix# + * @name clone + * @instance + */ + Matrix.prototype.clone = function() { + var sx = this.sx; + var shy = this.shy; + var shx = this.shx; + var sy = this.sy; + var tx = this.tx; + var ty = this.ty; + + return new Matrix(sx, shy, shx, sy, tx, ty); + }; - if (element.fCtype === kObjectp) member.isptr = true; + API.Matrix = Matrix; - member.arrkind = getArrayKind(member.conttype); + /** + * Multiplies two matrices. (see {@link Matrix}) + * @param {Matrix} m1 + * @param {Matrix} m2 + * @memberof jsPDF# + * @name matrixMult + */ + var matrixMult = (API.matrixMult = function(m1, m2) { + return m2.multiply(m1); + }); - member.readelem = readVectorElement; + /** + * The identity matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)). + * @type {Matrix} + * @memberof! jsPDF# + * @name identityMatrix + */ + var identityMatrix = new Matrix(1, 0, 0, 1, 0, 0); + API.unitMatrix = API.identityMatrix = identityMatrix; - if (!member.isptr && (member.arrkind < 0)) { - const subelem = createStreamerElement('temp', member.conttype); - if (subelem.fType === kStreamer) { - subelem.$fictional = true; - member.submember = createMemberStreamer(subelem, file); - } - } - } - } else if ((stl === kSTLmap) || (stl === kSTLmultimap)) { - const p1 = member.typename.indexOf('<'), - p2 = member.typename.lastIndexOf('>'); + /** + * Adds a new pattern for later use. + * @param {String} key The key by it can be referenced later. The keys must be unique! + * @param {API.Pattern} pattern The pattern + */ + var addPattern = function(key, pattern) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (patternMap[key]) return; - member.pairtype = 'pair<' + member.typename.slice(p1 + 1, p2) + '>'; + var prefix = pattern instanceof ShadingPattern ? "Sh" : "P"; + var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10); + pattern.id = patternKey; - // remember found streamer info from the file - - // most probably it is the only one which should be used - member.si = file.findStreamerInfo(member.pairtype); + patternMap[key] = patternKey; + patterns[patternKey] = pattern; - member.streamer = getPairStreamer(member.si, member.pairtype, file); + events.publish("addPattern", pattern); + }; - if (!member.streamer || (member.streamer.length !== 2)) { - console.error(`Fail to build streamer for pair ${member.pairtype}`); - delete member.streamer; - } + /** + * A pattern describing a shading pattern. + * + * Only available in "advanced" API mode. + * + * @param {String} type One of "axial" or "radial" + * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points + * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle. + * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes + * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255]. + * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional). + * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system + * and the use coordinate system (optional). + * @constructor + * @extends API.Pattern + */ + API.ShadingPattern = ShadingPattern; + + /** + * A PDF Tiling pattern. + * + * Only available in "advanced" API mode. + * + * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped. + * @param {Number} xStep Horizontal spacing between pattern cells. + * @param {Number} yStep Vertical spacing between pattern cells. + * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional). + * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system + * and the use coordinate system (optional). + * @constructor + * @extends API.Pattern + */ + API.TilingPattern = TilingPattern; + + /** + * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode. + * @param {String} key + * @param {Pattern} pattern + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name addPattern + */ + API.addShadingPattern = function(key, pattern) { + advancedApiModeTrap("addShadingPattern()"); - if (member.streamer) member.readelem = readMapElement; - } else if (stl === kSTLbitset) - member.readelem = (buf /* , obj */) => buf.readFastArray(buf.ntou4(), kBool); + addPattern(key, pattern); + return this; + }; - if (!member.readelem) { - console.error(`failed to create streamer for element ${member.typename} ${member.name} element ${element._typename} STL type ${element.fSTLtype}`); - member.func = function(buf, obj) { - const ver = buf.readVersion(); - buf.checkByteCount(ver); - obj[this.name] = null; - }; - } else - if (!element.$fictional) { - member.read_version = function(buf, cnt) { - if (cnt === 0) return null; - const ver = buf.readVersion(); - this.member_wise = ((ver.val & kStreamedMemberWise) !== 0); - - this.stl_version = undefined; - if (this.member_wise) { - ver.val = ver.val & ~kStreamedMemberWise; - this.stl_version = { val: buf.ntoi2() }; - if (this.stl_version.val <= 0) this.stl_version.checksum = buf.ntou4(); - } - return ver; - }; + /** + * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern} + * gets called. Only available in "advanced" API mode. + * @param {API.Pattern} pattern + * @memberof jsPDF# + * @name beginTilingPattern + */ + API.beginTilingPattern = function(pattern) { + advancedApiModeTrap("beginTilingPattern()"); + + beginNewRenderTarget( + pattern.boundingBox[0], + pattern.boundingBox[1], + pattern.boundingBox[2] - pattern.boundingBox[0], + pattern.boundingBox[3] - pattern.boundingBox[1], + pattern.matrix + ); + }; - member.func = function(buf, obj) { - const ver = this.read_version(buf); + /** + * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called. + * + * Only available in "advanced" API mode. + * + * @param {string} key A unique key that is used to reference this pattern at later use. + * @param {API.Pattern} pattern The pattern to end. + * @memberof jsPDF# + * @name endTilingPattern + */ + API.endTilingPattern = function(key, pattern) { + advancedApiModeTrap("endTilingPattern()"); - let res = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + // retrieve the stream + pattern.stream = pages[currentPage].join("\n"); - if (!buf.checkByteCount(ver, this.typename)) res = null; - obj[this.name] = res; - }; + addPattern(key, pattern); - member.branch_func = function(buf, obj) { - // special function to read data from STL branch - const cnt = obj[this.stl_size], - ver = this.read_version(buf, cnt), - arr = new Array(cnt); + events.publish("endTilingPattern", pattern); - for (let n = 0; n < cnt; ++n) - arr[n] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + // restore state from stack + renderTargetStack.pop().restore(); + }; - if (ver) buf.checkByteCount(ver, `branch ${this.typename}`); + var newObject = (API.__private__.newObject = function() { + var oid = newObjectDeferred(); + newObjectDeferredBegin(oid, true); + return oid; + }); - obj[this.name] = arr; - }; - member.split_func = function(buf, arr, n) { - // function to read array from member-wise streaming - const ver = this.read_version(buf); - for (let i = 0; i < n; ++i) - arr[i][this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - buf.checkByteCount(ver, this.typename); - }; - member.objs_branch_func = function(buf, obj) { - // special function when branch read as part of complete object - // objects already preallocated and only appropriate member must be set - // see code in JSRoot.tree.js for reference + // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data + var newObjectDeferred = (API.__private__.newObjectDeferred = function() { + objectNumber++; + offsets[objectNumber] = function() { + return contentLength; + }; + return objectNumber; + }); - const arr = obj[this.name0], // objects array where reading is done - ver = this.read_version(buf, arr.length); + var newObjectDeferredBegin = function(oid, doOutput) { + doOutput = typeof doOutput === "boolean" ? doOutput : false; + offsets[oid] = contentLength; + if (doOutput) { + out(oid + " 0 obj"); + } + return oid; + }; + // Does not output the object until after the pages have been output. + // Returns an object containing the objectId and content. + // All pages have been added so the object ID can be estimated to start right after. + // This does not modify the current objectNumber; It must be updated after the newObjects are output. + var newAdditionalObject = (API.__private__.newAdditionalObject = function() { + var objId = newObjectDeferred(); + var obj = { + objId: objId, + content: "" + }; + additionalObjects.push(obj); + return obj; + }); - for (let n = 0; n < arr.length; ++n) { - const obj1 = this.get(arr, n); - obj1[this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - } + var rootDictionaryObjId = newObjectDeferred(); + var resourceDictionaryObjId = newObjectDeferred(); + + ///////////////////// + // Private functions + ///////////////////// + + var decodeColorString = (API.__private__.decodeColorString = function(color) { + var colorEncoded = color.split(" "); + if ( + colorEncoded.length === 2 && + (colorEncoded[1] === "g" || colorEncoded[1] === "G") + ) { + // convert grayscale value to rgb so that it can be converted to hex for consistency + var floatVal = parseFloat(colorEncoded[0]); + colorEncoded = [floatVal, floatVal, floatVal, "r"]; + } else if ( + colorEncoded.length === 5 && + (colorEncoded[4] === "k" || colorEncoded[4] === "K") + ) { + // convert CMYK values to rbg so that it can be converted to hex for consistency + var red = (1.0 - colorEncoded[0]) * (1.0 - colorEncoded[3]); + var green = (1.0 - colorEncoded[1]) * (1.0 - colorEncoded[3]); + var blue = (1.0 - colorEncoded[2]) * (1.0 - colorEncoded[3]); + + colorEncoded = [red, green, blue, "r"]; + } + var colorAsRGB = "#"; + for (var i = 0; i < 3; i++) { + colorAsRGB += ( + "0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16) + ).slice(-2); + } + return colorAsRGB; + }); - if (ver) buf.checkByteCount(ver, `branch ${this.typename}`); - }; - } - break; + var encodeColorString = (API.__private__.encodeColorString = function( + options + ) { + var color; + + if (typeof options === "string") { + options = { + ch1: options + }; + } + var ch1 = options.ch1; + var ch2 = options.ch2; + var ch3 = options.ch3; + var ch4 = options.ch4; + var letterArray = + options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"]; + + if (typeof ch1 === "string" && ch1.charAt(0) !== "#") { + var rgbColor = new RGBColor$1(ch1); + if (rgbColor.ok) { + ch1 = rgbColor.toHex(); + } else if (!/^\d*\.?\d*$/.test(ch1)) { + throw new Error( + 'Invalid color "' + ch1 + '" passed to jsPDF.encodeColorString.' + ); } + } + //convert short rgb to long form + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) { + ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3]; + } - default: - console.error(`fail to provide function for ${element.fName} (${element.fTypeName}) typ = ${element.fType}`); + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) { + var hex = parseInt(ch1.substr(1), 16); + ch1 = (hex >> 16) & 255; + ch2 = (hex >> 8) & 255; + ch3 = hex & 255; + } - member.func = function(/* buf, obj */) {}; // do nothing, fix in the future - } + if ( + typeof ch2 === "undefined" || + (typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3) + ) { + // Gray color space. + if (typeof ch1 === "string") { + color = ch1 + " " + letterArray[0]; + } else { + switch (options.precision) { + case 2: + color = f2(ch1 / 255) + " " + letterArray[0]; + break; + case 3: + default: + color = f3(ch1 / 255) + " " + letterArray[0]; + } + } + } else if (typeof ch4 === "undefined" || typeof ch4 === "object") { + // assume RGBA + if (ch4 && !isNaN(ch4.a)) { + //TODO Implement transparency. + //WORKAROUND use white for now, if transparent, otherwise handle as rgb + if (ch4.a === 0) { + color = ["1.", "1.", "1.", letterArray[1]].join(" "); + return color; + } + } + // assume RGB + if (typeof ch1 === "string") { + color = [ch1, ch2, ch3, letterArray[1]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [ + f2(ch1 / 255), + f2(ch2 / 255), + f2(ch3 / 255), + letterArray[1] + ].join(" "); + break; + default: + case 3: + color = [ + f3(ch1 / 255), + f3(ch2 / 255), + f3(ch3 / 255), + letterArray[1] + ].join(" "); + } + } + } else { + // assume CMYK + if (typeof ch1 === "string") { + color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join( + " " + ); + break; + case 3: + default: + color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join( + " " + ); + } + } + } + return color; + }); - return member; -} + var getFilters = (API.__private__.getFilters = function() { + return filters; + }); + var putStream = (API.__private__.putStream = function(options) { + options = options || {}; + var data = options.data || ""; + var filters = options.filters || getFilters(); + var alreadyAppliedFilters = options.alreadyAppliedFilters || []; + var addLength1 = options.addLength1 || false; + var valueOfLength1 = data.length; + var objectId = options.objectId; + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null && typeof objectId == "undefined") { + throw new Error( + "ObjectId must be passed to putStream for file encryption" + ); + } + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } -/** @summary Analyze and returns arrays kind - * @return 0 if TString (or equivalent), positive value - some basic type, -1 - any other kind - * @private */ -function getArrayKind(type_name) { - if ((type_name === clTString) || (type_name === 'string') || - (CustomStreamers[type_name] === clTString)) return 0; - if ((type_name.length < 7) || (type_name.indexOf('TArray') !== 0)) return -1; - if (type_name.length === 7) { - switch (type_name[6]) { - case 'I': return kInt; - case 'D': return kDouble; - case 'F': return kFloat; - case 'S': return kShort; - case 'C': return kChar; - case 'L': return kLong; - default: return -1; + var processedData = {}; + if (filters === true) { + filters = ["FlateEncode"]; + } + var keyValues = options.additionalKeyValues || []; + if (typeof jsPDF.API.processDataByFilters !== "undefined") { + processedData = jsPDF.API.processDataByFilters(data, filters); + } else { + processedData = { data: data, reverseChain: [] }; + } + var filterAsString = + processedData.reverseChain + + (Array.isArray(alreadyAppliedFilters) + ? alreadyAppliedFilters.join(" ") + : alreadyAppliedFilters.toString()); + + if (processedData.data.length !== 0) { + keyValues.push({ + key: "Length", + value: processedData.data.length + }); + if (addLength1 === true) { + keyValues.push({ + key: "Length1", + value: valueOfLength1 + }); } - } + } - return type_name === 'TArrayL64' ? kLong64 : -1; -} + if (filterAsString.length != 0) { + if (filterAsString.split("/").length - 1 === 1) { + keyValues.push({ + key: "Filter", + value: filterAsString + }); + } else { + keyValues.push({ + key: "Filter", + value: "[" + filterAsString + "]" + }); -/** @summary Let directly assign methods when doing I/O - * @private */ -function addClassMethods(clname, streamer) { - if (streamer === null) return streamer; + for (var j = 0; j < keyValues.length; j += 1) { + if (keyValues[j].key === "DecodeParms") { + var decodeParmsArray = []; - const methods = getMethods(clname); + for ( + var i = 0; + i < processedData.reverseChain.split("/").length - 1; + i += 1 + ) { + decodeParmsArray.push("null"); + } - if (methods) { - for (const key in methods) { - if (isFunc(methods[key]) || (key.indexOf('_') === 0)) - streamer.push({ name: key, method: methods[key], func(_buf, obj) { obj[this.name] = this.method; } }); + decodeParmsArray.push(keyValues[j].value); + keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]"; + } + } } - } + } - return streamer; -} + out("<<"); + for (var k = 0; k < keyValues.length; k++) { + out("/" + keyValues[k].key + " " + keyValues[k].value); + } + out(">>"); + if (processedData.data.length !== 0) { + out("stream"); + out(encryptor(processedData.data)); + out("endstream"); + } + }); + var putPage = (API.__private__.putPage = function(page) { + var pageNumber = page.number; + var data = page.data; + var pageObjectNumber = page.objId; + var pageContentsObjId = page.contentsObjId; + + newObjectDeferredBegin(pageObjectNumber, true); + out("< - * Version: 1.0.0.1 - * LastModified: Dec 25 1999 - * original: http://www.onicos.com/staff/iz/amuse/javascript/expert/inflate.txt - */ + if (page.bleedBox !== null) { + out( + "/BleedBox [" + + hpf(page.bleedBox.bottomLeftX) + + " " + + hpf(page.bleedBox.bottomLeftY) + + " " + + hpf(page.bleedBox.topRightX) + + " " + + hpf(page.bleedBox.topRightY) + + "]" + ); + } -/* constant parameters */ -const zip_WSIZE = 32768, // Sliding Window size + if (page.trimBox !== null) { + out( + "/TrimBox [" + + hpf(page.trimBox.bottomLeftX) + + " " + + hpf(page.trimBox.bottomLeftY) + + " " + + hpf(page.trimBox.topRightX) + + " " + + hpf(page.trimBox.topRightY) + + "]" + ); + } -/* constant tables (inflate) */ -zip_MASK_BITS = [ - 0x0000, - 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, - 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff], + if (page.artBox !== null) { + out( + "/ArtBox [" + + hpf(page.artBox.bottomLeftX) + + " " + + hpf(page.artBox.bottomLeftY) + + " " + + hpf(page.artBox.topRightX) + + " " + + hpf(page.artBox.topRightY) + + "]" + ); + } -// Tables for deflate from PKZIP's appnote.txt. - zip_cplens = [ // Copy lengths for literal codes 257..285 - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], + if (typeof page.userUnit === "number" && page.userUnit !== 1.0) { + out("/UserUnit " + page.userUnit); + } -/* note: see note #13 above about the 258 in this list. */ - zip_cplext = [ // Extra bits for literal codes 257..285 - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99], // 99==invalid + events.publish("putPage", { + objId: pageObjectNumber, + pageContext: pagesContext[pageNumber], + pageNumber: pageNumber, + page: data + }); + out("/Contents " + pageContentsObjId + " 0 R"); + out(">>"); + out("endobj"); + // Page content + var pageContent = data.join("\n"); + + if (apiMode === ApiMode.ADVANCED) { + // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again + pageContent += "\nQ"; + } - zip_cpdist = [ // Copy offsets for distance codes 0..29 - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577], + newObjectDeferredBegin(pageContentsObjId, true); + putStream({ + data: pageContent, + filters: getFilters(), + objectId: pageContentsObjId + }); + out("endobj"); + return pageObjectNumber; + }); - zip_cpdext = [ // Extra bits for distance codes - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, - 12, 12, 13, 13], + var putPages = (API.__private__.putPages = function() { + var n, + i, + pageObjectNumbers = []; - zip_border = [ // Order of the bit length code lengths - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + for (n = 1; n <= page; n++) { + pagesContext[n].objId = newObjectDeferred(); + pagesContext[n].contentsObjId = newObjectDeferred(); + } -function ZIP_inflate(arr, tgt) { - /* variables (inflate) */ - const zip_slide = new Array(2 * zip_WSIZE), - zip_inflate_data = arr, - zip_inflate_datalen = arr.byteLength; - let zip_wp = 0, // current position in slide - zip_fixed_tl = null, // inflate static - zip_fixed_td, // inflate static - zip_fixed_bl, zip_fixed_bd, // inflate static - zip_bit_buf = 0, // bit buffer - zip_bit_len = 0, // bits in bit buffer - zip_method = -1, - zip_eof = false, - zip_copy_leng = 0, - zip_copy_dist = 0, - zip_tl = null, zip_td, // literal/length and distance decoder tables - zip_bl, zip_bd, // number of bits decoded by tl and td - zip_inflate_pos = 0; + for (n = 1; n <= page; n++) { + pageObjectNumbers.push( + putPage({ + number: n, + data: pages[n], + objId: pagesContext[n].objId, + contentsObjId: pagesContext[n].contentsObjId, + mediaBox: pagesContext[n].mediaBox, + cropBox: pagesContext[n].cropBox, + bleedBox: pagesContext[n].bleedBox, + trimBox: pagesContext[n].trimBox, + artBox: pagesContext[n].artBox, + userUnit: pagesContext[n].userUnit, + rootDictionaryObjId: rootDictionaryObjId, + resourceDictionaryObjId: resourceDictionaryObjId + }) + ); + } + newObjectDeferredBegin(rootDictionaryObjId, true); + out("<>"); + out("endobj"); + events.publish("postPutPages"); + }); - function zip_NEEDBITS(n) { - while (zip_bit_len < n) { - if (zip_inflate_pos < zip_inflate_datalen) - zip_bit_buf |= zip_inflate_data[zip_inflate_pos++] << zip_bit_len; - zip_bit_len += 8; - } - } + var putFont = function(font) { + events.publish("putFont", { + font: font, + out: out, + newObject: newObject, + putStream: putStream + }); - function zip_GETBITS(n) { - return zip_bit_buf & zip_MASK_BITS[n]; - } + if (font.isAlreadyPutted !== true) { + font.objectNumber = newObject(); + out("<<"); + out("/Type /Font"); + out("/BaseFont /" + toPDFName(font.postScriptName)); + out("/Subtype /Type1"); + if (typeof font.encoding === "string") { + out("/Encoding /" + font.encoding); + } + out("/FirstChar 32"); + out("/LastChar 255"); + out(">>"); + out("endobj"); + } + }; - function zip_DUMPBITS(n) { - zip_bit_buf >>= n; - zip_bit_len -= n; - } + var putFonts = function() { + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + if ( + putOnlyUsedFonts === false || + (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) + ) { + putFont(fonts[fontKey]); + } + } + } + }; - /* objects (inflate) */ - function zip_HuftBuild(b, // code lengths in bits (all assumed <= BMAX) - n, // number of codes (assumed <= N_MAX) - s, // number of simple-valued codes (0..s-1) - d, // list of base values for non-simple codes - e, // list of extra bits for non-simple codes - mm) { // maximum lookup bits - const res = { - status: 0, // 0: success, 1: incomplete table, 2: bad input - root: null, // (zip_HuftList) starting table - m: 0 // maximum lookup bits, returns actual - }, - BMAX = 16, // maximum bit length of any code - N_MAX = 288, // maximum number of codes in any set - c = Array(BMAX+1).fill(0), // bit length count table - lx = Array(BMAX+1).fill(0), // stack of bits per table - u = Array(BMAX).fill(null), // zip_HuftNode[BMAX][] table stack - v = Array(N_MAX).fill(0), // values in order of bit length - x = Array(BMAX+1).fill(0), // bit offsets, then code stack - r = { e: 0, b: 0, n: 0, t: null }, // new zip_HuftNode(), // table entry for structure assignment - el = (n > 256) ? b[256] : BMAX; // set length of EOB code, if any - let rr = null, // temporary variable, use in assignment - a, // counter for codes of length k - f, // i repeats in table every f entries - h, // table level - j, // counter - k, // number of bits in current code - p = b, // pointer into c[], b[], or v[] - pidx = 0, // index of p - q, // (zip_HuftNode) points to current table - w, - xp, // pointer into x or c - y, // number of dummy codes added - z, // number of entries in current table - o, - tail = null, // (zip_HuftList) - i = n; // counter, current code + var putXObject = function(xObject) { + xObject.objectNumber = newObject(); + + var options = []; + options.push({ key: "Type", value: "/XObject" }); + options.push({ key: "Subtype", value: "/Form" }); + options.push({ + key: "BBox", + value: + "[" + + [ + hpf(xObject.x), + hpf(xObject.y), + hpf(xObject.x + xObject.width), + hpf(xObject.y + xObject.height) + ].join(" ") + + "]" + }); + options.push({ + key: "Matrix", + value: "[" + xObject.matrix.toString() + "]" + }); + // TODO: /Resources - // Generate counts for each bit length - do - c[p[pidx++]]++; // assume all entries <= BMAX - while (--i > 0); + var stream = xObject.pages[1].join("\n"); + putStream({ + data: stream, + additionalKeyValues: options, + objectId: xObject.objectNumber + }); + out("endobj"); + }; - if (c[0] === n) // null input--all zero length codes - return res; + var putXObjects = function() { + for (var xObjectKey in renderTargets) { + if (renderTargets.hasOwnProperty(xObjectKey)) { + putXObject(renderTargets[xObjectKey]); + } + } + }; - // Find minimum and maximum length, bound *m by those - for (j = 1; j <= BMAX; ++j) - if (c[j] !== 0) break; + var interpolateAndEncodeRGBStream = function(colors, numberSamples) { + var tValues = []; + var t; + var dT = 1.0 / (numberSamples - 1); + for (t = 0.0; t < 1.0; t += dT) { + tValues.push(t); + } + tValues.push(1.0); + // add first and last control point if not present + if (colors[0].offset != 0.0) { + var c0 = { + offset: 0.0, + color: colors[0].color + }; + colors.unshift(c0); + } + if (colors[colors.length - 1].offset != 1.0) { + var c1 = { + offset: 1.0, + color: colors[colors.length - 1].color + }; + colors.push(c1); + } + var out = ""; + var index = 0; + + for (var i = 0; i < tValues.length; i++) { + t = tValues[i]; + while (t > colors[index + 1].offset) index++; + var a = colors[index].offset; + var b = colors[index + 1].offset; + var d = (t - a) / (b - a); + + var aColor = colors[index].color; + var bColor = colors[index + 1].color; + + out += + padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) + + padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) + + padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16)); + } + return out.trim(); + }; - k = j; // minimum code length - if (mm < j) - mm = j; - for (i = BMAX; i !== 0; --i) - if (c[i] !== 0) break; + var putShadingPattern = function(pattern, numberSamples) { + /* + Axial patterns shade between the two points specified in coords, radial patterns between the inner + and outer circle. + The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now + interpolated to equidistant samples and written to pdf as a sample (type 0) function. + */ + // The number of color samples that should be used to describe the shading. + // The higher, the more accurate the gradient will be. + numberSamples || (numberSamples = 21); + var funcObjectNumber = newObject(); + var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples); + + var options = []; + options.push({ key: "FunctionType", value: "0" }); + options.push({ key: "Domain", value: "[0.0 1.0]" }); + options.push({ key: "Size", value: "[" + numberSamples + "]" }); + options.push({ key: "BitsPerSample", value: "8" }); + options.push({ key: "Range", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); + options.push({ key: "Decode", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); + + putStream({ + data: stream, + additionalKeyValues: options, + alreadyAppliedFilters: ["/ASCIIHexDecode"], + objectId: funcObjectNumber + }); + out("endobj"); + + pattern.objectNumber = newObject(); + out("<< /ShadingType " + pattern.type); + out("/ColorSpace /DeviceRGB"); + var coords = + "/Coords [" + + hpf(parseFloat(pattern.coords[0])) + + " " + // x1 + hpf(parseFloat(pattern.coords[1])) + + " "; // y1 + if (pattern.type === 2) { + // axial + coords += + hpf(parseFloat(pattern.coords[2])) + + " " + // x2 + hpf(parseFloat(pattern.coords[3])); // y2 + } else { + // radial + coords += + hpf(parseFloat(pattern.coords[2])) + + " " + // r1 + hpf(parseFloat(pattern.coords[3])) + + " " + // x2 + hpf(parseFloat(pattern.coords[4])) + + " " + // y2 + hpf(parseFloat(pattern.coords[5])); // r2 + } + coords += "]"; + out(coords); - const g = i; // maximum code length - if (mm > i) - mm = i; + if (pattern.matrix) { + out("/Matrix [" + pattern.matrix.toString() + "]"); + } + out("/Function " + funcObjectNumber + " 0 R"); + out("/Extend [true true]"); + out(">>"); + out("endobj"); + }; - // Adjust last length count to fill out codes, if needed - for (y = 1 << j; j < i; ++j, y <<= 1) { - if ((y -= c[j]) < 0) { - res.status = 2; // bad input: more codes than bits - res.m = mm; - return res; - } - } - if ((y -= c[i]) < 0) { - res.status = 2; - res.m = mm; - return res; - } - c[i] += y; + var putTilingPattern = function(pattern, deferredResourceDictionaryIds) { + var resourcesObjectId = newObjectDeferred(); + var patternObjectId = newObject(); - // Generate starting offsets into the value table for each length - x[1] = j = 0; - p = c; - pidx = 1; - xp = 2; - while (--i > 0) // note that i == g from above - x[xp++] = (j += p[pidx++]); + deferredResourceDictionaryIds.push({ + resourcesOid: resourcesObjectId, + objectOid: patternObjectId + }); - // Make a table of values in order of bit lengths - p = b; pidx = 0; - i = 0; - do { - if ((j = p[pidx++]) !== 0) - v[x[j]++] = i; - } while (++i < n); - n = x[g]; // set n to length of v + pattern.objectNumber = patternObjectId; + var options = []; + options.push({ key: "Type", value: "/Pattern" }); + options.push({ key: "PatternType", value: "1" }); // tiling pattern + options.push({ key: "PaintType", value: "1" }); // colored tiling pattern + options.push({ key: "TilingType", value: "1" }); // constant spacing + options.push({ + key: "BBox", + value: "[" + pattern.boundingBox.map(hpf).join(" ") + "]" + }); + options.push({ key: "XStep", value: hpf(pattern.xStep) }); + options.push({ key: "YStep", value: hpf(pattern.yStep) }); + options.push({ key: "Resources", value: resourcesObjectId + " 0 R" }); + if (pattern.matrix) { + options.push({ + key: "Matrix", + value: "[" + pattern.matrix.toString() + "]" + }); + } - // Generate the Huffman codes and for each, make the table entries - x[0] = i = 0; // first Huffman code is zero - p = v; pidx = 0; // grab values in bit order - h = -1; // no tables yet--level -1 - w = lx[0] = 0; // no bits decoded yet - q = null; // ditto - z = 0; // ditto + putStream({ + data: pattern.stream, + additionalKeyValues: options, + objectId: pattern.objectNumber + }); + out("endobj"); + }; - // go through the bit lengths (k already is bits in shortest code) - for (; k <= g; ++k) { - a = c[k]; - while (a-- > 0) { - // here i is the Huffman code of length k bits for value p[pidx] - // make tables up to required level - while (k > w + lx[1 + h]) { - w += lx[1 + h++]; // add bits already decoded + var putPatterns = function(deferredResourceDictionaryIds) { + var patternKey; + for (patternKey in patterns) { + if (patterns.hasOwnProperty(patternKey)) { + if (patterns[patternKey] instanceof ShadingPattern) { + putShadingPattern(patterns[patternKey]); + } else if (patterns[patternKey] instanceof TilingPattern) { + putTilingPattern(patterns[patternKey], deferredResourceDictionaryIds); + } + } + } + }; - // compute minimum size table less than or equal to *m bits - z = (z = g - w) > mm ? mm : z; // upper limit - if ((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table - // too few codes for k-w bit table - f -= a + 1; // deduct codes from patterns left - xp = k; - while (++j < z) { // try smaller tables up to z bits - if ((f <<= 1) <= c[++xp]) - break; // enough codes to use up j bits - f -= c[xp]; // else deduct codes from patterns - } - } - if (w + j > el && w < el) - j = el - w; // make EOB code end at table - z = 1 << j; // table entries for j-bit table - lx[1 + h] = j; // set table size in stack + var putGState = function(gState) { + gState.objectNumber = newObject(); + out("<<"); + for (var p in gState) { + switch (p) { + case "opacity": + out("/ca " + f2(gState[p])); + break; + case "stroke-opacity": + out("/CA " + f2(gState[p])); + break; + } + } + out(">>"); + out("endobj"); + }; - // allocate and link in new table - q = new Array(z); - for (o = 0; o < z; ++o) - q[o] = { e: 0, b: 0, n: 0, t: null }; // new zip_HuftNode + var putGStates = function() { + var gStateKey; + for (gStateKey in gStates) { + if (gStates.hasOwnProperty(gStateKey)) { + putGState(gStates[gStateKey]); + } + } + }; - if (tail == null) - tail = res.root = { next: null, list: null }; // new zip_HuftList(); - else - tail = tail.next = { next: null, list: null }; // new zip_HuftList(); - tail.next = null; - tail.list = q; - u[h] = q; // table starts after link + var putXobjectDict = function() { + out("/XObject <<"); + for (var xObjectKey in renderTargets) { + if ( + renderTargets.hasOwnProperty(xObjectKey) && + renderTargets[xObjectKey].objectNumber >= 0 + ) { + out( + "/" + + xObjectKey + + " " + + renderTargets[xObjectKey].objectNumber + + " 0 R" + ); + } + } - /* connect to last table, if there is one */ - if (h > 0) { - x[h] = i; // save pattern for backing up - r.b = lx[h]; // bits to dump before this table - r.e = 16 + j; // bits in this table - r.t = q; // pointer to this table - j = (i & ((1 << w) - 1)) >> (w - lx[h]); - rr = u[h-1][j]; - rr.e = r.e; - rr.b = r.b; - rr.n = r.n; - rr.t = r.t; - } - } + // Loop through images, or other data objects + events.publish("putXobjectDict"); + out(">>"); + }; - // set up table entry in r - r.b = k - w; - if (pidx >= n) - r.e = 99; // out of values--invalid code - else if (p[pidx] < s) { - r.e = (p[pidx] < 256 ? 16 : 15); // 256 is end-of-block code - r.n = p[pidx++]; // simple code is just the value - } else { - r.e = e[p[pidx] - s]; // non-simple--look up in lists - r.n = d[p[pidx++] - s]; - } + var putEncryptionDict = function() { + encryption.oid = newObject(); + out("<<"); + out("/Filter /Standard"); + out("/V " + encryption.v); + out("/R " + encryption.r); + out("/U <" + encryption.toHexString(encryption.U) + ">"); + out("/O <" + encryption.toHexString(encryption.O) + ">"); + out("/P " + encryption.P); + out(">>"); + out("endobj"); + }; - // fill code-like entries with r // - f = 1 << (k - w); - for (j = i >> w; j < z; j += f) { - rr = q[j]; - rr.e = r.e; - rr.b = r.b; - rr.n = r.n; - rr.t = r.t; - } + var putFontDict = function() { + out("/Font <<"); - // backwards increment the k-bit code i - for (j = 1 << (k - 1); (i & j) !== 0; j >>= 1) - i ^= j; - i ^= j; + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + if ( + putOnlyUsedFonts === false || + (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) + ) { + out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R"); + } + } + } + out(">>"); + }; - // backup over finished tables - while ((i & ((1 << w) - 1)) !== x[h]) - w -= lx[h--]; // don't need to update q - } + var putShadingPatternDict = function() { + if (Object.keys(patterns).length > 0) { + out("/Shading <<"); + for (var patternKey in patterns) { + if ( + patterns.hasOwnProperty(patternKey) && + patterns[patternKey] instanceof ShadingPattern && + patterns[patternKey].objectNumber >= 0 + ) { + out( + "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" + ); + } } - /* return actual size of base table */ - res.m = lx[1]; + events.publish("putShadingPatternDict"); + out(">>"); + } + }; - /* Return true (1) if we were given an incomplete table */ - res.status = ((y !== 0 && g !== 1) ? 1 : 0); + var putTilingPatternDict = function(objectOid) { + if (Object.keys(patterns).length > 0) { + out("/Pattern <<"); + for (var patternKey in patterns) { + if ( + patterns.hasOwnProperty(patternKey) && + patterns[patternKey] instanceof API.TilingPattern && + patterns[patternKey].objectNumber >= 0 && + patterns[patternKey].objectNumber < objectOid // prevent cyclic dependencies + ) { + out( + "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" + ); + } + } + events.publish("putTilingPatternDict"); + out(">>"); + } + }; - return res; - } + var putGStatesDict = function() { + if (Object.keys(gStates).length > 0) { + var gStateKey; + out("/ExtGState <<"); + for (gStateKey in gStates) { + if ( + gStates.hasOwnProperty(gStateKey) && + gStates[gStateKey].objectNumber >= 0 + ) { + out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R"); + } + } - /* routines (inflate) */ + events.publish("putGStateDict"); + out(">>"); + } + }; - function zip_inflate_codes(buff, off, size) { - if (size === 0) return 0; + var putResourceDictionary = function(objectIds) { + newObjectDeferredBegin(objectIds.resourcesOid, true); + out("<<"); + out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"); + putFontDict(); + putShadingPatternDict(); + putTilingPatternDict(objectIds.objectOid); + putGStatesDict(); + putXobjectDict(); + out(">>"); + out("endobj"); + }; - /* inflate (decompress) the codes in a deflated (compressed) block. - Return an error code or zero if it all goes ok. */ + var putResources = function() { + // FormObjects, Patterns etc. might use other FormObjects/Patterns/Images + // which means their resource dictionaries must contain the already resolved + // object ids. For this reason we defer the serialization of the resource + // dicts until all objects have been serialized and have object ids. + // + // In order to prevent cyclic dependencies (which Adobe Reader doesn't like), + // we only put all oids that are smaller than the oid of the object the + // resource dict belongs to. This is correct behavior, since the streams + // may only use other objects that have already been defined and thus appear + // earlier in their respective collection. + // Currently, this only affects tiling patterns, but a (more) correct + // implementation of FormObjects would also define their own resource dicts. + var deferredResourceDictionaryIds = []; + + putFonts(); + putGStates(); + putXObjects(); + putPatterns(deferredResourceDictionaryIds); + + events.publish("putResources"); + deferredResourceDictionaryIds.forEach(putResourceDictionary); + putResourceDictionary({ + resourcesOid: resourceDictionaryObjId, + objectOid: Number.MAX_SAFE_INTEGER // output all objects + }); + events.publish("postPutResources"); + }; - let e, // table entry flag/number of extra bits - t, // (zip_HuftNode) pointer to table entry - n = 0; + var putAdditionalObjects = function() { + events.publish("putAdditionalObjects"); + for (var i = 0; i < additionalObjects.length; i++) { + var obj = additionalObjects[i]; + newObjectDeferredBegin(obj.objId, true); + out(obj.content); + out("endobj"); + } + events.publish("postPutAdditionalObjects"); + }; - // inflate the coded data - for (;;) { // do until end of block - zip_NEEDBITS(zip_bl); - t = zip_tl.list[zip_GETBITS(zip_bl)]; - e = t.e; - while (e > 16) { - if (e === 99) - return -1; - zip_DUMPBITS(t.b); - e -= 16; - zip_NEEDBITS(e); - t = t.t[zip_GETBITS(e)]; - e = t.e; - } - zip_DUMPBITS(t.b); + var addFontToFontDictionary = function(font) { + fontmap[font.fontName] = fontmap[font.fontName] || {}; + fontmap[font.fontName][font.fontStyle] = font.id; + }; - if (e === 16) { // then it's a literal - zip_wp &= zip_WSIZE - 1; - buff[off + n++] = zip_slide[zip_wp++] = t.n; - if (n === size) - return size; - continue; - } + var addFont = function( + postScriptName, + fontName, + fontStyle, + encoding, + isStandardFont + ) { + var font = { + id: "F" + (Object.keys(fonts).length + 1).toString(10), + postScriptName: postScriptName, + fontName: fontName, + fontStyle: fontStyle, + encoding: encoding, + isStandardFont: isStandardFont || false, + metadata: {} + }; - // exit if end of block - if (e === 15) - break; + events.publish("addFont", { + font: font, + instance: this + }); - // it's an EOB or a length + fonts[font.id] = font; + addFontToFontDictionary(font); + return font.id; + }; - // get length of block to copy - zip_NEEDBITS(e); - zip_copy_leng = t.n + zip_GETBITS(e); - zip_DUMPBITS(e); + var addFonts = function(arrayOfFonts) { + for (var i = 0, l = standardFonts.length; i < l; i++) { + var fontKey = addFont.call( + this, + arrayOfFonts[i][0], + arrayOfFonts[i][1], + arrayOfFonts[i][2], + standardFonts[i][3], + true + ); - // decode distance of block to copy - zip_NEEDBITS(zip_bd); - t = zip_td.list[zip_GETBITS(zip_bd)]; - e = t.e; + if (putOnlyUsedFonts === false) { + usedFonts[fontKey] = true; + } + // adding aliases for standard fonts, this time matching the capitalization + var parts = arrayOfFonts[i][0].split("-"); + addFontToFontDictionary({ + id: fontKey, + fontName: parts[0], + fontStyle: parts[1] || "" + }); + } + events.publish("addFonts", { + fonts: fonts, + dictionary: fontmap + }); + }; - while (e > 16) { - if (e === 99) - return -1; - zip_DUMPBITS(t.b); - e -= 16; - zip_NEEDBITS(e); - t = t.t[zip_GETBITS(e)]; - e = t.e; - } - zip_DUMPBITS(t.b); - zip_NEEDBITS(e); - zip_copy_dist = zip_wp - t.n - zip_GETBITS(e); - zip_DUMPBITS(e); + var SAFE = function __safeCall(fn) { + fn.foo = function __safeCallWrapper() { + try { + return fn.apply(this, arguments); + } catch (e) { + var stack = e.stack || ""; + if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1]; + var m = + "Error in function " + + stack.split("\n")[0].split("<")[0] + + ": " + + e.message; + if (globalObject.console) { + globalObject.console.error(m, e); + if (globalObject.alert) alert(m); + } else { + throw new Error(m); + } + } + }; + fn.foo.bar = fn; + return fn.foo; + }; - // do the copy - while (zip_copy_leng > 0 && n < size) { - --zip_copy_leng; - zip_copy_dist &= zip_WSIZE - 1; - zip_wp &= zip_WSIZE - 1; - buff[off + n++] = zip_slide[zip_wp++] = zip_slide[zip_copy_dist++]; - } + var to8bitStream = function(text, flags) { + /** + * PDF 1.3 spec: + * "For text strings encoded in Unicode, the first two bytes must be 254 followed by + * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts + * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely + * to be a meaningful beginning of a word or phrase.) The remainder of the + * string consists of Unicode character codes, according to the UTF-16 encoding + * specified in the Unicode standard, version 2.0. Commonly used Unicode values + * are represented as 2 bytes per character, with the high-order byte appearing first + * in the string." + * + * In other words, if there are chars in a string with char code above 255, we + * recode the string to UCS2 BE - string doubles in length and BOM is prepended. + * + * HOWEVER! + * Actual *content* (body) text (as opposed to strings used in document properties etc) + * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) + * + * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have + * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could + * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode + * code page. There, however, all characters in the stream are treated as GIDs, + * including BOM, which is the reason we need to skip BOM in content text (i.e. that + * that is tied to a font). + * + * To signal this "special" PDFEscape / to8bitStream handling mode, + * API.text() function sets (unless you overwrite it with manual values + * given to API.text(.., flags) ) + * flags.autoencode = true + * flags.noBOM = true + * + * =================================================================================== + * `flags` properties relied upon: + * .sourceEncoding = string with encoding label. + * "Unicode" by default. = encoding of the incoming text. + * pass some non-existing encoding name + * (ex: 'Do not touch my strings! I know what I am doing.') + * to make encoding code skip the encoding step. + * .outputEncoding = Either valid PDF encoding name + * (must be supported by jsPDF font metrics, otherwise no encoding) + * or a JS object, where key = sourceCharCode, value = outputCharCode + * missing keys will be treated as: sourceCharCode === outputCharCode + * .noBOM + * See comment higher above for explanation for why this is important + * .autoencode + * See comment higher above for explanation for why this is important + */ + + var i, + l, + sourceEncoding, + encodingBlock, + outputEncoding, + newtext, + isUnicode, + ch, + bch; + + flags = flags || {}; + sourceEncoding = flags.sourceEncoding || "Unicode"; + outputEncoding = flags.outputEncoding; + + // This 'encoding' section relies on font metrics format + // attached to font objects by, among others, + // "Willow Systems' standard_font_metrics plugin" + // see jspdf.plugin.standard_font_metrics.js for format + // of the font.metadata.encoding Object. + // It should be something like + // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} + // .widths = {0:width, code:width, ..., 'fof':divisor} + // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} + if ( + (flags.autoencode || outputEncoding) && + fonts[activeFontKey].metadata && + fonts[activeFontKey].metadata[sourceEncoding] && + fonts[activeFontKey].metadata[sourceEncoding].encoding + ) { + encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; + + // each font has default encoding. Some have it clearly defined. + if (!outputEncoding && fonts[activeFontKey].encoding) { + outputEncoding = fonts[activeFontKey].encoding; + } + + // Hmmm, the above did not work? Let's try again, in different place. + if (!outputEncoding && encodingBlock.codePages) { + outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default + } + + if (typeof outputEncoding === "string") { + outputEncoding = encodingBlock[outputEncoding]; + } + // we want output encoding to be a JS Object, where + // key = sourceEncoding's character code and + // value = outputEncoding's character code. + if (outputEncoding) { + isUnicode = false; + newtext = []; + for (i = 0, l = text.length; i < l; i++) { + ch = outputEncoding[text.charCodeAt(i)]; + if (ch) { + newtext.push(String.fromCharCode(ch)); + } else { + newtext.push(text[i]); + } - if (n === size) - return size; + // since we are looping over chars anyway, might as well + // check for residual unicodeness + if (newtext[i].charCodeAt(0) >> 8) { + /* more than 255 */ + isUnicode = true; + } + } + text = newtext.join(""); } + } - zip_method = -1; // done - return n; - } + i = text.length; + // isUnicode may be set to false above. Hence the triple-equal to undefined + while (isUnicode === undefined && i !== 0) { + if (text.charCodeAt(i - 1) >> 8) { + /* more than 255 */ + isUnicode = true; + } + i--; + } + if (!isUnicode) { + return text; + } - function zip_inflate_stored(buff, off, size) { - /* 'decompress' an inflated type 0 (stored) block. */ + newtext = flags.noBOM ? [] : [254, 255]; + for (i = 0, l = text.length; i < l; i++) { + ch = text.charCodeAt(i); + bch = ch >> 8; // divide by 256 + if (bch >> 8) { + /* something left after dividing by 256 second time */ + throw new Error( + "Character at position " + + i + + " of string '" + + text + + "' exceeds 16bits. Cannot be encoded into UCS-2 BE" + ); + } + newtext.push(bch); + newtext.push(ch - (bch << 8)); + } + return String.fromCharCode.apply(undefined, newtext); + }; - // go to byte boundary - let n = zip_bit_len & 7; - zip_DUMPBITS(n); + var pdfEscape = (API.__private__.pdfEscape = API.pdfEscape = function( + text, + flags + ) { + /** + * Replace '/', '(', and ')' with pdf-safe versions + * + * Doing to8bitStream does NOT make this PDF display unicode text. For that + * we also need to reference a unicode font and embed it - royal pain in the rear. + * + * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, + * which JavaScript Strings are happy to provide. So, while we still cannot display + * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) + * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF + * is still parseable. + * This will allow immediate support for unicode in document properties strings. + */ + return to8bitStream(text, flags) + .replace(/\\/g, "\\\\") + .replace(/\(/g, "\\(") + .replace(/\)/g, "\\)"); + }); - // get the length and its complement - zip_NEEDBITS(16); - n = zip_GETBITS(16); - zip_DUMPBITS(16); - zip_NEEDBITS(16); - if (n !== ((~zip_bit_buf) & 0xffff)) - return -1; // error in compressed data - zip_DUMPBITS(16); + var beginPage = (API.__private__.beginPage = function(format) { + pages[++page] = []; + pagesContext[page] = { + objId: 0, + contentsObjId: 0, + userUnit: Number(userUnit), + artBox: null, + bleedBox: null, + cropBox: null, + trimBox: null, + mediaBox: { + bottomLeftX: 0, + bottomLeftY: 0, + topRightX: Number(format[0]), + topRightY: Number(format[1]) + } + }; + _setPage(page); + setOutputDestination(pages[currentPage]); + }); - // read and output the compressed data - zip_copy_leng = n; + var _addPage = function(parmFormat, parmOrientation) { + var dimensions, width, height; - n = 0; - while (zip_copy_leng > 0 && n < size) { - --zip_copy_leng; - zip_wp &= zip_WSIZE - 1; - zip_NEEDBITS(8); - buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8); - zip_DUMPBITS(8); + orientation = parmOrientation || orientation; + + if (typeof parmFormat === "string") { + dimensions = getPageFormat(parmFormat.toLowerCase()); + if (Array.isArray(dimensions)) { + width = dimensions[0]; + height = dimensions[1]; } + } - if (zip_copy_leng === 0) - zip_method = -1; // done - return n; - } + if (Array.isArray(parmFormat)) { + width = parmFormat[0] * scaleFactor; + height = parmFormat[1] * scaleFactor; + } - function zip_inflate_fixed(buff, off, size) { - /* decompress an inflated type 1 (fixed Huffman codes) block. We should - either replace this with a custom decoder, or at least precompute the - Huffman tables. */ + if (isNaN(width)) { + width = format[0]; + height = format[1]; + } - // if first time, set up tables for fixed blocks - if (zip_fixed_tl == null) { - // literal table - const l = Array(288).fill(8, 0, 144).fill(9, 144, 256).fill(7, 256, 280).fill(8, 280, 288); - // make a complete, but wrong code set - zip_fixed_bl = 7; + if (width > 14400 || height > 14400) { + console$1.warn( + "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400" + ); + width = Math.min(14400, width); + height = Math.min(14400, height); + } - let h = zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext, zip_fixed_bl); - if (h.status !== 0) - throw new Error('HufBuild error: ' + h.status); - zip_fixed_tl = h.root; - zip_fixed_bl = h.m; + format = [width, height]; - // distance table - l.fill(5, 0, 30); // make an incomplete code set - zip_fixed_bd = 5; + switch (orientation.substr(0, 1)) { + case "l": + if (height > width) { + format = [height, width]; + } + break; + case "p": + if (width > height) { + format = [height, width]; + } + break; + } - h = zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd); - if (h.status > 1) { - zip_fixed_tl = null; - throw new Error('HufBuild error: '+h.status); - } - zip_fixed_td = h.root; - zip_fixed_bd = h.m; + beginPage(format); + + // Set line width + setLineWidth(lineWidth); + // Set draw color + out(strokeColor); + // resurrecting non-default line caps, joins + if (lineCapID !== 0) { + out(lineCapID + " J"); + } + if (lineJoinID !== 0) { + out(lineJoinID + " j"); + } + events.publish("addPage", { + pageNumber: page + }); + }; + + var _deletePage = function(n) { + if (n > 0 && n <= page) { + pages.splice(n, 1); + pagesContext.splice(n, 1); + page--; + if (currentPage > page) { + currentPage = page; } + this.setPage(currentPage); + } + }; - zip_tl = zip_fixed_tl; - zip_td = zip_fixed_td; - zip_bl = zip_fixed_bl; - zip_bd = zip_fixed_bd; - return zip_inflate_codes(buff, off, size); - } + var _setPage = function(n) { + if (n > 0 && n <= page) { + currentPage = n; + } + }; - function zip_inflate_dynamic(buff, off, size) { - // decompress an inflated type 2 (dynamic Huffman codes) block. - let i, j, // temporary variables - l, // last length - t, // (zip_HuftNode) literal/length code table - h; // (zip_HuftBuild) - const ll = new Array(286+30).fill(0); // literal/length and distance code lengths + var getNumberOfPages = (API.__private__.getNumberOfPages = API.getNumberOfPages = function() { + return pages.length - 1; + }); - // read in table lengths - zip_NEEDBITS(5); - const nl = 257 + zip_GETBITS(5); // number of literal/length codes - zip_DUMPBITS(5); - zip_NEEDBITS(5); - const nd = 1 + zip_GETBITS(5); // number of distance codes - zip_DUMPBITS(5); - zip_NEEDBITS(4); - const nb = 4 + zip_GETBITS(4); // number of bit length codes - zip_DUMPBITS(4); - if (nl > 286 || nd > 30) - return -1; // bad lengths + /** + * Returns a document-specific font key - a label assigned to a + * font name + font type combination at the time the font was added + * to the font inventory. + * + * Font key is used as label for the desired font for a block of text + * to be added to the PDF document stream. + * @private + * @function + * @param fontName {string} can be undefined on "falthy" to indicate "use current" + * @param fontStyle {string} can be undefined on "falthy" to indicate "use current" + * @returns {string} Font key. + * @ignore + */ + var getFont = function(fontName, fontStyle, options) { + var key = undefined, + fontNameLowerCase; + options = options || {}; + + fontName = + fontName !== undefined ? fontName : fonts[activeFontKey].fontName; + fontStyle = + fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; + fontNameLowerCase = fontName.toLowerCase(); + + if ( + fontmap[fontNameLowerCase] !== undefined && + fontmap[fontNameLowerCase][fontStyle] !== undefined + ) { + key = fontmap[fontNameLowerCase][fontStyle]; + } else if ( + fontmap[fontName] !== undefined && + fontmap[fontName][fontStyle] !== undefined + ) { + key = fontmap[fontName][fontStyle]; + } else { + if (options.disableWarning === false) { + console$1.warn( + "Unable to look up font label for font '" + + fontName + + "', '" + + fontStyle + + "'. Refer to getFontList() for available fonts." + ); + } + } - // read in bit-length-code lengths - for (j = 0; j < nb; ++j) { - zip_NEEDBITS(3); - ll[zip_border[j]] = zip_GETBITS(3); - zip_DUMPBITS(3); + if (!key && !options.noFallback) { + key = fontmap["times"][fontStyle]; + if (key == null) { + key = fontmap["times"]["normal"]; } - for (; j < 19; ++j) - ll[zip_border[j]] = 0; + } + return key; + }; - // build decoding table for trees--single level, 7 bit lookup - zip_bl = 7; - h = zip_HuftBuild(ll, 19, 19, null, null, zip_bl); - if (h.status !== 0) - return -1; // incomplete code set + var putInfo = (API.__private__.putInfo = function() { + var objectId = newObject(); + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } + out("<<"); + out("/Producer (" + pdfEscape(encryptor("jsPDF " + jsPDF.version)) + ")"); + for (var key in documentProperties) { + if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { + out( + "/" + + key.substr(0, 1).toUpperCase() + + key.substr(1) + + " (" + + pdfEscape(encryptor(documentProperties[key])) + + ")" + ); + } + } + out("/CreationDate (" + pdfEscape(encryptor(creationDate)) + ")"); + out(">>"); + out("endobj"); + }); - zip_tl = h.root; - zip_bl = h.m; + var putCatalog = (API.__private__.putCatalog = function(options) { + options = options || {}; + var tmpRootDictionaryObjId = + options.rootDictionaryObjId || rootDictionaryObjId; + newObject(); + out("<<"); + out("/Type /Catalog"); + out("/Pages " + tmpRootDictionaryObjId + " 0 R"); + // PDF13ref Section 7.2.1 + if (!zoomMode) zoomMode = "fullwidth"; + switch (zoomMode) { + case "fullwidth": + out("/OpenAction [3 0 R /FitH null]"); + break; + case "fullheight": + out("/OpenAction [3 0 R /FitV null]"); + break; + case "fullpage": + out("/OpenAction [3 0 R /Fit]"); + break; + case "original": + out("/OpenAction [3 0 R /XYZ null null 1]"); + break; + default: + var pcn = "" + zoomMode; + if (pcn.substr(pcn.length - 1) === "%") + zoomMode = parseInt(zoomMode) / 100; + if (typeof zoomMode === "number") { + out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]"); + } + } + if (!layoutMode) layoutMode = "continuous"; + switch (layoutMode) { + case "continuous": + out("/PageLayout /OneColumn"); + break; + case "single": + out("/PageLayout /SinglePage"); + break; + case "two": + case "twoleft": + out("/PageLayout /TwoColumnLeft"); + break; + case "tworight": + out("/PageLayout /TwoColumnRight"); + break; + } + if (pageMode) { + /** + * A name object specifying how the document should be displayed when opened: + * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT + * UseOutlines : Document outline visible + * UseThumbs : Thumbnail images visible + * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible + */ + out("/PageMode /" + pageMode); + } + events.publish("putCatalog"); + out(">>"); + out("endobj"); + }); - // read in literal and distance code lengths - const n = nl + nd; // number of lengths to get - i = l = 0; - while (i < n) { - zip_NEEDBITS(zip_bl); - t = zip_tl.list[zip_GETBITS(zip_bl)]; - j = t.b; - zip_DUMPBITS(j); - j = t.n; - if (j < 16) // length of code in bits (0..15) - ll[i++] = l = j; // save last length in l - else if (j === 16) { // repeat last length 3 to 6 times - zip_NEEDBITS(2); - j = 3 + zip_GETBITS(2); - zip_DUMPBITS(2); - if (i + j > n) - return -1; - while (j-- > 0) - ll[i++] = l; - } else if (j === 17) { // 3 to 10 zero length codes - zip_NEEDBITS(3); - j = 3 + zip_GETBITS(3); - zip_DUMPBITS(3); - if (i + j > n) - return -1; - while (j-- > 0) - ll[i++] = 0; - l = 0; - } else { // j == 18: 11 to 138 zero length codes - zip_NEEDBITS(7); - j = 11 + zip_GETBITS(7); - zip_DUMPBITS(7); - if (i + j > n) - return -1; - while (j-- > 0) - ll[i++] = 0; - l = 0; - } + var putTrailer = (API.__private__.putTrailer = function() { + out("trailer"); + out("<<"); + out("/Size " + (objectNumber + 1)); + // Root and Info must be the last and second last objects written respectively + out("/Root " + objectNumber + " 0 R"); + out("/Info " + (objectNumber - 1) + " 0 R"); + if (encryptionOptions !== null) { + out("/Encrypt " + encryption.oid + " 0 R"); + } + out("/ID [ <" + fileId + "> <" + fileId + "> ]"); + out(">>"); + }); + + var putHeader = (API.__private__.putHeader = function() { + out("%PDF-" + pdfVersion); + out("%\xBA\xDF\xAC\xE0"); + }); + + var putXRef = (API.__private__.putXRef = function() { + var p = "0000000000"; + + out("xref"); + out("0 " + (objectNumber + 1)); + out("0000000000 65535 f "); + for (var i = 1; i <= objectNumber; i++) { + var offset = offsets[i]; + if (typeof offset === "function") { + out((p + offsets[i]()).slice(-10) + " 00000 n "); + } else { + if (typeof offsets[i] !== "undefined") { + out((p + offsets[i]).slice(-10) + " 00000 n "); + } else { + out("0000000000 00000 n "); + } } + } + }); - // build the decoding tables for literal/length and distance codes - zip_bl = 9; // zip_lbits; - h = zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl); - if (zip_bl === 0) // no literals or lengths - h.status = 1; - if (h.status !== 0) - return -1; // incomplete code set - zip_tl = h.root; - zip_bl = h.m; + var buildDocument = (API.__private__.buildDocument = function() { + resetDocument(); + setOutputDestination(content); - for (i = 0; i < nd; ++i) - ll[i] = ll[i + nl]; - zip_bd = 6; // zip_dbits; - h = zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd); - zip_td = h.root; - zip_bd = h.m; + events.publish("buildDocument"); - // incomplete distance tree - if ((zip_bd === 0 && nl > 257) || (h.status !== 0)) // lengths but no distances - return -1; + putHeader(); + putPages(); + putAdditionalObjects(); + putResources(); + if (encryptionOptions !== null) putEncryptionDict(); + putInfo(); + putCatalog(); - // decompress until an end-of-block code - return zip_inflate_codes(buff, off, size); - } + var offsetOfXRef = contentLength; + putXRef(); + putTrailer(); + out("startxref"); + out("" + offsetOfXRef); + out("%%EOF"); - function zip_inflate_internal(buff, off, size) { - // decompress an inflated entry - let n = 0, i; + setOutputDestination(pages[currentPage]); - while (n < size) { - if (zip_eof && zip_method === -1) - return n; + return content.join("\n"); + }); - if (zip_copy_leng > 0) { - if (zip_method !== 0 /* zip_STORED_BLOCK */) { - // STATIC_TREES or DYN_TREES - while (zip_copy_leng > 0 && n < size) { - --zip_copy_leng; - zip_copy_dist &= zip_WSIZE - 1; - zip_wp &= zip_WSIZE - 1; - buff[off + n++] = zip_slide[zip_wp++] = - zip_slide[zip_copy_dist++]; - } - } else { - while (zip_copy_leng > 0 && n < size) { - --zip_copy_leng; - zip_wp &= zip_WSIZE - 1; - zip_NEEDBITS(8); - buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8); - zip_DUMPBITS(8); - } - if (zip_copy_leng === 0) - zip_method = -1; // done - } - if (n === size) - return n; - } + var getBlob = (API.__private__.getBlob = function(data) { + return new Blob([getArrayBuffer(data)], { + type: "application/pdf" + }); + }); - if (zip_method === -1) { - if (zip_eof) - break; + /** + * Generates the PDF document. + * + * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. + * + * @param {string} type A string identifying one of the possible output types.
+ * Possible values are:
+ * 'arraybuffer' -> (ArrayBuffer)
+ * 'blob' -> (Blob)
+ * 'bloburi'/'bloburl' -> (string)
+ * 'datauristring'/'dataurlstring' -> (string)
+ * 'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring
+ * @param {Object|string} options An object providing some additional signalling to PDF generator.
+ * Possible options are 'filename'.
+ * A string can be passed instead of {filename:string} and defaults to 'generated.pdf' + * @function + * @instance + * @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined} + * @memberof jsPDF# + * @name output + */ + var output = (API.output = API.__private__.output = SAFE(function output( + type, + options + ) { + options = options || {}; + + if (typeof options === "string") { + options = { + filename: options + }; + } else { + options.filename = options.filename || "generated.pdf"; + } - // read in last block bit - zip_NEEDBITS(1); - if (zip_GETBITS(1) !== 0) - zip_eof = true; - zip_DUMPBITS(1); + switch (type) { + case undefined: + return buildDocument(); + case "save": + API.save(options.filename); + break; + case "arraybuffer": + return getArrayBuffer(buildDocument()); + case "blob": + return getBlob(buildDocument()); + case "bloburi": + case "bloburl": + // Developer is responsible of calling revokeObjectURL + if ( + typeof globalObject.URL !== "undefined" && + typeof globalObject.URL.createObjectURL === "function" + ) { + return ( + (globalObject.URL && + globalObject.URL.createObjectURL(getBlob(buildDocument()))) || + void 0 + ); + } else { + console$1.warn( + "bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser." + ); + } + break; + case "datauristring": + case "dataurlstring": + var dataURI = ""; + var pdfDocument = buildDocument(); + try { + dataURI = btoa$1(pdfDocument); + } catch (e) { + dataURI = btoa$1(unescape(encodeURIComponent(pdfDocument))); + } + return ( + "data:application/pdf;filename=" + + options.filename + + ";base64," + + dataURI + ); + case "datauri": + case "dataurl": + return (globalObject.document.location.href = this.output( + "datauristring", + options + )); + default: + return null; + } + })); - // read in block type - zip_NEEDBITS(2); - zip_method = zip_GETBITS(2); - zip_DUMPBITS(2); - zip_tl = null; - zip_copy_leng = 0; - } + /** + * Used to see if a supplied hotfix was requested when the pdf instance was created. + * @param {string} hotfixName - The name of the hotfix to check. + * @returns {boolean} + */ + var hasHotfix = function(hotfixName) { + return ( + Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1 + ); + }; - switch (zip_method) { - case 0: // zip_STORED_BLOCK - i = zip_inflate_stored(buff, off + n, size - n); - break; + switch (unit) { + case "pt": + scaleFactor = 1; + break; + case "mm": + scaleFactor = 72 / 25.4; + break; + case "cm": + scaleFactor = 72 / 2.54; + break; + case "in": + scaleFactor = 72; + break; + case "px": + if (hasHotfix("px_scaling") == true) { + scaleFactor = 72 / 96; + } else { + scaleFactor = 96 / 72; + } + break; + case "pc": + scaleFactor = 12; + break; + case "em": + scaleFactor = 12; + break; + case "ex": + scaleFactor = 6; + break; + default: + if (typeof unit === "number") { + scaleFactor = unit; + } else { + throw new Error("Invalid unit: " + unit); + } + } - case 1: // zip_STATIC_TREES - if (zip_tl != null) - i = zip_inflate_codes(buff, off + n, size - n); - else - i = zip_inflate_fixed(buff, off + n, size - n); - break; + var encryption = null; + setCreationDate(); + setFileId(); - case 2: // zip_DYN_TREES - if (zip_tl != null) - i = zip_inflate_codes(buff, off + n, size - n); - else - i = zip_inflate_dynamic(buff, off + n, size - n); - break; + var getEncryptor = function(objectId) { + if (encryptionOptions !== null) { + return encryption.encryptor(objectId, 0); + } + return function(data) { + return data; + }; + }; - default: // error - i = -1; - break; - } + //--------------------------------------- + // Public API - if (i === -1) - return zip_eof ? 0 : -1; - n += i; + var getPageInfo = (API.__private__.getPageInfo = API.getPageInfo = function( + pageNumberOneBased + ) { + if (isNaN(pageNumberOneBased) || pageNumberOneBased % 1 !== 0) { + throw new Error("Invalid argument passed to jsPDF.getPageInfo"); + } + var objId = pagesContext[pageNumberOneBased].objId; + return { + objId: objId, + pageNumber: pageNumberOneBased, + pageContext: pagesContext[pageNumberOneBased] + }; + }); + + var getPageInfoByObjId = (API.__private__.getPageInfoByObjId = function( + objId + ) { + if (isNaN(objId) || objId % 1 !== 0) { + throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId"); + } + for (var pageNumber in pagesContext) { + if (pagesContext[pageNumber].objId === objId) { + break; } - return n; - } + } + return getPageInfo(pageNumber); + }); - let i, cnt = 0; - while ((i = zip_inflate_internal(tgt, cnt, Math.min(1024, tgt.byteLength-cnt))) > 0) - cnt += i; + var getCurrentPageInfo = (API.__private__.getCurrentPageInfo = API.getCurrentPageInfo = function() { + return { + objId: pagesContext[currentPage].objId, + pageNumber: currentPage, + pageContext: pagesContext[currentPage] + }; + }); - return cnt; -} // function ZIP_inflate + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @param format {String/Array} The format of the new page. Can be:
  • a0 - a10
  • b0 - b10
  • c0 - c10
  • dl
  • letter
  • government-letter
  • legal
  • junior-legal
  • ledger
  • tabloid
  • credit-card

+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] + * @param orientation {string} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l"). + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name addPage + */ + API.addPage = function() { + _addPage.apply(this, arguments); + return this; + }; + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name setPage + * @param {number} page Switch the active page to the page number specified (indexed starting at 1). + * @example + * doc = jsPDF() + * doc.addPage() + * doc.addPage() + * doc.text('I am on page 3', 10, 10) + * doc.setPage(1) + * doc.text('I am on page 1', 10, 10) + */ + API.setPage = function() { + _setPage.apply(this, arguments); + setOutputDestination.call(this, pages[currentPage]); + return this; + }; -/** - * https://github.com/pierrec/node-lz4/blob/master/lib/binding.js - * - * LZ4 based compression and decompression - * Copyright (c) 2014 Pierre Curto - * MIT Licensed - */ + /** + * @name insertPage + * @memberof jsPDF# + * + * @function + * @instance + * @param {Object} beforePage + * @returns {jsPDF} + */ + API.insertPage = function(beforePage) { + this.addPage(); + this.movePage(currentPage, beforePage); + return this; + }; -/** - * Decode a block. Assumptions: input contains all sequences of a - * chunk, output is large enough to receive the decoded data. - * If the output buffer is too small, an error will be thrown. - * If the returned value is negative, an error occured at the returned offset. - * - * @param input {Buffer} input data - * @param output {Buffer} output data - * @return {Number} number of decoded bytes - * @private */ -function LZ4_uncompress(input, output, sIdx, eIdx) { - sIdx = sIdx || 0; - eIdx = eIdx || (input.length - sIdx); - // Process each sequence in the incoming data - let j = 0; - for (let i = sIdx, n = eIdx; i < n;) { - const token = input[i++]; + /** + * @name movePage + * @memberof jsPDF# + * @function + * @instance + * @param {number} targetPage + * @param {number} beforePage + * @returns {jsPDF} + */ + API.movePage = function(targetPage, beforePage) { + var tmpPages, tmpPagesContext; + if (targetPage > beforePage) { + tmpPages = pages[targetPage]; + tmpPagesContext = pagesContext[targetPage]; + for (var i = targetPage; i > beforePage; i--) { + pages[i] = pages[i - 1]; + pagesContext[i] = pagesContext[i - 1]; + } + pages[beforePage] = tmpPages; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } else if (targetPage < beforePage) { + tmpPages = pages[targetPage]; + tmpPagesContext = pagesContext[targetPage]; + for (var j = targetPage; j < beforePage; j++) { + pages[j] = pages[j + 1]; + pagesContext[j] = pagesContext[j + 1]; + } + pages[beforePage] = tmpPages; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } + return this; + }; - // Literals - let literals_length = (token >> 4); - if (literals_length > 0) { - // length of literals - let l = literals_length + 240; - while (l === 255) { - l = input[i++]; - literals_length += l; - } + /** + * Deletes a page from the PDF. + * @name deletePage + * @memberof jsPDF# + * @function + * @param {number} targetPage + * @instance + * @returns {jsPDF} + */ + API.deletePage = function() { + _deletePage.apply(this, arguments); + return this; + }; + + /** + * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. + * + * @function + * @instance + * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page. + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page. + * @param {Object} [options] - Collection of settings signaling how the text must be encoded. + * @param {string} [options.align=left] - The alignment of the text, possible values: left, center, right, justify. + * @param {string} [options.baseline=alphabetic] - Sets text baseline used when drawing the text, possible values: alphabetic, ideographic, bottom, top, middle, hanging + * @param {number|Matrix} [options.angle=0] - Rotate the text clockwise or counterclockwise. Expects the angle in degree. + * @param {number} [options.rotationDirection=1] - Direction of the rotation. 0 = clockwise, 1 = counterclockwise. + * @param {number} [options.charSpace=0] - The space between each letter. + * @param {number} [options.horizontalScale=1] - Horizontal scale of the text as a factor of the regular size. + * @param {number} [options.lineHeightFactor=1.15] - The lineheight of each line. + * @param {Object} [options.flags] - Flags for to8bitStream. + * @param {boolean} [options.flags.noBOM=true] - Don't add BOM to Unicode-text. + * @param {boolean} [options.flags.autoencode=true] - Autoencode the Text. + * @param {number} [options.maxWidth=0] - Split the text by given width, 0 = no split. + * @param {string} [options.renderingMode=fill] - Set how the text should be rendered, possible values: fill, stroke, fillThenStroke, invisible, fillAndAddForClipping, strokeAndAddPathForClipping, fillThenStrokeAndAddToPathForClipping, addToPathForClipping. + * @param {boolean} [options.isInputVisual] - Option for the BidiEngine + * @param {boolean} [options.isOutputVisual] - Option for the BidiEngine + * @param {boolean} [options.isInputRtl] - Option for the BidiEngine + * @param {boolean} [options.isOutputRtl] - Option for the BidiEngine + * @param {boolean} [options.isSymmetricSwapping] - Option for the BidiEngine + * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the anchor set by x and y. + * + * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing + * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this + * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a + * DIFFERENT result. A matrix is only allowed in "advanced" API mode. + * @returns {jsPDF} + * @memberof jsPDF# + * @name text + */ + API.__private__.text = API.text = function(text, x, y, options, transform) { + /* + * Inserts something like this into PDF + * BT + * /F1 16 Tf % Font name + size + * 16 TL % How many units down for next line in multiline text + * 0 g % color + * 28.35 813.54 Td % position + * (line one) Tj + * T* (line two) Tj + * T* (line three) Tj + * ET + */ + options = options || {}; + var scope = options.scope || this; + var payload, da, angle, align, charSpace, maxWidth, flags, horizontalScale; + + // Pre-August-2012 the order of arguments was function(x, y, text, flags) + // in effort to make all calls have similar signature like + // function(data, coordinates... , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if ( + typeof text === "number" && + typeof x === "number" && + (typeof y === "string" || Array.isArray(y)) + ) { + var tmp = y; + y = x; + x = text; + text = tmp; + } + + var transformationMatrix; + + if (arguments[3] instanceof Matrix === false) { + flags = arguments[3]; + angle = arguments[4]; + align = arguments[5]; + + if (typeof flags !== "object" || flags === null) { + if (typeof angle === "string") { + align = angle; + angle = null; + } + if (typeof flags === "string") { + align = flags; + flags = null; + } + if (typeof flags === "number") { + angle = flags; + flags = null; + } + options = { + flags: flags, + angle: angle, + align: align + }; + } + } else { + advancedApiModeTrap( + "The transform parameter of text() with a Matrix value" + ); + transformationMatrix = transform; + } + + if (isNaN(x) || isNaN(y) || typeof text === "undefined" || text === null) { + throw new Error("Invalid arguments passed to jsPDF.text"); + } + + if (text.length === 0) { + return scope; + } + + var xtra = ""; + var isHex = false; + var lineHeight = + typeof options.lineHeightFactor === "number" + ? options.lineHeightFactor + : lineHeightFactor; + var scaleFactor = scope.internal.scaleFactor; + + function ESC(s) { + s = s.split("\t").join(Array(options.TabLen || 9).join(" ")); + return pdfEscape(s, flags); + } + + function transformTextToSpecialArray(text) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(curDa); + } else { + if ( + Array.isArray(text) && + (curDa.length === 1 || + (curDa[1] === undefined && curDa[2] === undefined)) + ) { + da.push(curDa[0]); + } else { + da.push([curDa[0], curDa[1], curDa[2]]); + } + } + } + return da; + } + + function processTextByFunction(text, processingFunction) { + var result; + if (typeof text === "string") { + result = processingFunction(text)[0]; + } else if (Array.isArray(text)) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + var tmpResult; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(processingFunction(curDa)[0]); + } else if (Array.isArray(curDa) && typeof curDa[0] === "string") { + tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]); + da.push([tmpResult[0], tmpResult[1], tmpResult[2]]); + } + } + result = da; + } + return result; + } + + //Check if text is of type String + var textIsOfTypeString = false; + var tmpTextIsOfTypeString = true; + + if (typeof text === "string") { + textIsOfTypeString = true; + } else if (Array.isArray(text)) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if ( + typeof curDa !== "string" || + (Array.isArray(curDa) && typeof curDa[0] !== "string") + ) { + tmpTextIsOfTypeString = false; + } + } + textIsOfTypeString = tmpTextIsOfTypeString; + } + if (textIsOfTypeString === false) { + throw new Error( + 'Type of text must be string or Array. "' + + text + + '" is not recognized.' + ); + } - // Copy the literals - const end = i + literals_length; - while (i < end) output[j++] = input[i++]; + //If there are any newlines in text, we assume + //the user wanted to print multiple lines, so break the + //text up into an array. If the text is already an array, + //we assume the user knows what they are doing. + //Convert text into an array anyway to simplify + //later code. - // End of buffer? - if (i === n) return j; + if (typeof text === "string") { + if (text.match(/[\r?\n]/)) { + text = text.split(/\r\n|\r|\n/g); + } else { + text = [text]; } + } - // Match copy - // 2 bytes offset (little endian) - const offset = input[i++] | (input[i++] << 8); + //baseline + var height = activeFontSize / scope.internal.scaleFactor; + var descent = height * (lineHeight - 1); - // 0 is an invalid offset value - if (offset === 0 || offset > j) return -(i-2); + switch (options.baseline) { + case "bottom": + y -= descent; + break; + case "top": + y += height - descent; + break; + case "hanging": + y += height - 2 * descent; + break; + case "middle": + y += height / 2 - descent; + break; + } - // length of match copy - let match_length = (token & 0xf), - l = match_length + 240; - while (l === 255) { - l = input[i++]; - match_length += l; + //multiline + maxWidth = options.maxWidth || 0; + + if (maxWidth > 0) { + if (typeof text === "string") { + text = scope.splitTextToSize(text, maxWidth); + } else if (Object.prototype.toString.call(text) === "[object Array]") { + text = text.reduce(function(acc, textLine) { + return acc.concat(scope.splitTextToSize(textLine, maxWidth)); + }, []); } + } - // Copy the match - let pos = j - offset; // position of the match copy in the current output - const end = j + match_length + 4; // minmatch = 4; - while (j < end) output[j++] = output[pos++]; - } + //creating Payload-Object to make text byRef + payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize + } + }; + events.publish("preProcessText", payload); - return j; -} + text = payload.text; + options = payload.options; + //angle + angle = options.angle; -/** @summary Reads header envelope, determines zipped size and unzip content - * @return {Promise} with unzipped content - * @private */ -async function R__unzip(arr, tgtsize, noalert, src_shift) { - const HDRSIZE = 9, totallen = arr.byteLength, - checkChar = (o, symb) => { return String.fromCharCode(arr.getUint8(o)) === symb; }, - getCode = o => arr.getUint8(o); + if ( + transformationMatrix instanceof Matrix === false && + angle && + typeof angle === "number" + ) { + angle *= Math.PI / 180; - let curr = src_shift || 0, fullres = 0, tgtbuf = null; + if (options.rotationDirection === 0) { + angle = -angle; + } - const nextPortion = () => { - while (fullres < tgtsize) { - let fmt = 'unknown', off = 0, CHKSUM = 0; + if (apiMode === ApiMode.ADVANCED) { + angle = -angle; + } - if (curr + HDRSIZE >= totallen) { - if (!noalert) console.error('Error R__unzip: header size exceeds buffer size'); - return Promise.resolve(null); - } + var c = Math.cos(angle); + var s = Math.sin(angle); + transformationMatrix = new Matrix(c, s, -s, c, 0, 0); + } else if (angle && angle instanceof Matrix) { + transformationMatrix = angle; + } - if (checkChar(curr, 'Z') && checkChar(curr+1, 'L') && getCode(curr + 2) === 8) { fmt = 'new'; off = 2; } else - if (checkChar(curr, 'C') && checkChar(curr+1, 'S') && getCode(curr + 2) === 8) { fmt = 'old'; off = 0; } else - if (checkChar(curr, 'X') && checkChar(curr+1, 'Z') && getCode(curr + 2) === 0) { fmt = 'LZMA'; off = 0; } else - if (checkChar(curr, 'Z') && checkChar(curr+1, 'S') && getCode(curr + 2) === 1) fmt = 'ZSTD'; else - if (checkChar(curr, 'L') && checkChar(curr+1, '4')) { fmt = 'LZ4'; off = 0; CHKSUM = 8; } + if (apiMode === ApiMode.ADVANCED && !transformationMatrix) { + transformationMatrix = identityMatrix; + } - /* C H E C K H E A D E R */ - if ((fmt !== 'new') && (fmt !== 'old') && (fmt !== 'LZ4') && (fmt !== 'ZSTD') && (fmt !== 'LZMA')) { - if (!noalert) console.error(`R__unzip: ${fmt} format is not supported!`); - return Promise.resolve(null); - } + //charSpace - const srcsize = HDRSIZE + ((getCode(curr + 3) & 0xff) | ((getCode(curr + 4) & 0xff) << 8) | ((getCode(curr + 5) & 0xff) << 16)), - uint8arr = new Uint8Array(arr.buffer, arr.byteOffset + curr + HDRSIZE + off + CHKSUM, Math.min(arr.byteLength - curr - HDRSIZE - off - CHKSUM, srcsize - HDRSIZE - CHKSUM)); + charSpace = options.charSpace || activeCharSpace; - if (!tgtbuf) tgtbuf = new ArrayBuffer(tgtsize); - const tgt8arr = new Uint8Array(tgtbuf, fullres); + if (typeof charSpace !== "undefined") { + xtra += hpf(scale(charSpace)) + " Tc\n"; + this.setCharSpace(this.getCharSpace() || 0); + } - if (fmt === 'ZSTD') { - const promise = internals._ZstdStream - ? Promise.resolve(internals._ZstdStream) - : (isNodeJs() ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }) : Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; })) - .then(({ ZstdInit }) => ZstdInit()).then(({ ZstdStream }) => { internals._ZstdStream = ZstdStream; return ZstdStream; }); - return promise.then(ZstdStream => { - const data2 = ZstdStream.decompress(uint8arr), - reslen = data2.length; + horizontalScale = options.horizontalScale; + if (typeof horizontalScale !== "undefined") { + xtra += hpf(horizontalScale * 100) + " Tz\n"; + } - for (let i = 0; i < reslen; ++i) - tgt8arr[i] = data2[i]; + //lang - fullres += reslen; - curr += srcsize; - return nextPortion(); - }); - } else if (fmt === 'LZMA') { - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(lzma => { - const expected_len = (getCode(curr + 6) & 0xff) | ((getCode(curr + 7) & 0xff) << 8) | ((getCode(curr + 8) & 0xff) << 16), - reslen = lzma.decompress(uint8arr, tgt8arr, expected_len); - fullres += reslen; - curr += srcsize; - return nextPortion(); - }); - } + options.lang; - const reslen = (fmt === 'LZ4') ? LZ4_uncompress(uint8arr, tgt8arr) : ZIP_inflate(uint8arr, tgt8arr); + //renderingMode + var renderingMode = -1; + var parmRenderingMode = + typeof options.renderingMode !== "undefined" + ? options.renderingMode + : options.stroke; + var pageContext = scope.internal.getCurrentPageInfo().pageContext; - if (reslen <= 0) break; - fullres += reslen; - curr += srcsize; + switch (parmRenderingMode) { + case 0: + case false: + case "fill": + renderingMode = 0; + break; + case 1: + case true: + case "stroke": + renderingMode = 1; + break; + case 2: + case "fillThenStroke": + renderingMode = 2; + break; + case 3: + case "invisible": + renderingMode = 3; + break; + case 4: + case "fillAndAddForClipping": + renderingMode = 4; + break; + case 5: + case "strokeAndAddPathForClipping": + renderingMode = 5; + break; + case 6: + case "fillThenStrokeAndAddToPathForClipping": + renderingMode = 6; + break; + case 7: + case "addToPathForClipping": + renderingMode = 7; + break; + } + + var usedRenderingMode = + typeof pageContext.usedRenderingMode !== "undefined" + ? pageContext.usedRenderingMode + : -1; + + //if the coder wrote it explicitly to use a specific + //renderingMode, then use it + if (renderingMode !== -1) { + xtra += renderingMode + " Tr\n"; + //otherwise check if we used the rendering Mode already + //if so then set the rendering Mode... + } else if (usedRenderingMode !== -1) { + xtra += "0 Tr\n"; + } + + if (renderingMode !== -1) { + pageContext.usedRenderingMode = renderingMode; + } + + //align + align = options.align || "left"; + var leading = activeFontSize * lineHeight; + var pageWidth = scope.internal.pageSize.getWidth(); + var activeFont = fonts[activeFontKey]; + charSpace = options.charSpace || activeCharSpace; + maxWidth = options.maxWidth || 0; + + var lineWidths; + flags = Object.assign({ autoencode: true, noBOM: true }, options.flags); + + var wordSpacingPerLine = []; + var findWidth = function(v) { + return ( + (scope.getStringUnitWidth(v, { + font: activeFont, + charSpace: charSpace, + fontSize: activeFontSize, + doKerning: false + }) * + activeFontSize) / + scaleFactor + ); + }; + if (Object.prototype.toString.call(text) === "[object Array]") { + da = transformTextToSpecialArray(text); + var newY; + if (align !== "left") { + lineWidths = da.map(findWidth); + } + //The first line uses the "main" Td setting, + //and the subsequent lines are offset by the + //previous line's x coordinate. + var prevWidth = 0; + var newX; + if (align === "right") { + //The passed in x coordinate defines the + //rightmost point of the text. + x -= lineWidths[0]; + text = []; + len = da.length; + for (var i = 0; i < len; i++) { + if (i === 0) { + newX = getHorizontalCoordinate(x); + newY = getVerticalCoordinate(y); + } else { + newX = scale(prevWidth - lineWidths[i]); + newY = -leading; + } + text.push([da[i], newX, newY]); + prevWidth = lineWidths[i]; + } + } else if (align === "center") { + //The passed in x coordinate defines + //the center point. + x -= lineWidths[0] / 2; + text = []; + len = da.length; + for (var j = 0; j < len; j++) { + if (j === 0) { + newX = getHorizontalCoordinate(x); + newY = getVerticalCoordinate(y); + } else { + newX = scale((prevWidth - lineWidths[j]) / 2); + newY = -leading; + } + text.push([da[j], newX, newY]); + prevWidth = lineWidths[j]; + } + } else if (align === "left") { + text = []; + len = da.length; + for (var h = 0; h < len; h++) { + text.push(da[h]); + } + } else if (align === "justify" && activeFont.encoding === "Identity-H") { + // when using unicode fonts, wordSpacePerLine does not apply + text = []; + len = da.length; + maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; + let backToStartX = 0; + for (var l = 0; l < len; l++) { + newY = l === 0 ? getVerticalCoordinate(y) : -leading; + newX = l === 0 ? getHorizontalCoordinate(x) : backToStartX; + if (l < len - 1) { + let spacing = scale( + (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1) + ); + let words = da[l].split(" "); + text.push([words[0] + " ", newX, newY]); + backToStartX = 0; // distance to reset back to the left + for (let i = 1; i < words.length; i++) { + let shiftAmount = + (findWidth(words[i - 1] + " " + words[i]) - + findWidth(words[i])) * + scaleFactor + + spacing; + if (i == words.length - 1) text.push([words[i], shiftAmount, 0]); + else text.push([words[i] + " ", shiftAmount, 0]); + backToStartX -= shiftAmount; + } + } else { + text.push([da[l], newX, newY]); + } + } + text.push(["", backToStartX, 0]); + } else if (align === "justify") { + text = []; + len = da.length; + maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; + for (var l = 0; l < len; l++) { + newY = l === 0 ? getVerticalCoordinate(y) : -leading; + newX = l === 0 ? getHorizontalCoordinate(x) : 0; + + const numSpaces = da[l].split(" ").length - 1; + const spacing = + numSpaces > 0 ? (maxWidth - lineWidths[l]) / numSpaces : 0; + + if (l < len - 1) { + wordSpacingPerLine.push(hpf(scale(spacing))); + } else { + wordSpacingPerLine.push(0); + } + text.push([da[l], newX, newY]); + } + } else { + throw new Error( + 'Unrecognized alignment option, use "left", "center", "right" or "justify".' + ); } + } - if (fullres !== tgtsize) { - if (!noalert) console.error(`R__unzip: fail to unzip data expects ${tgtsize}, got ${fullres}`); - return Promise.resolve(null); + //R2L + var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L; + if (doReversing === true) { + text = processTextByFunction(text, function(text, posX, posY) { + return [ + text + .split("") + .reverse() + .join(""), + posX, + posY + ]; + }); + } + + //creating Payload-Object to make text byRef + payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize } + }; + events.publish("postProcessText", payload); - return Promise.resolve(new DataView(tgtbuf)); - }; + text = payload.text; + isHex = payload.mutex.isHex || false; - return nextPortion(); -} + //Escaping + var activeFontEncoding = fonts[activeFontKey].encoding; + if ( + activeFontEncoding === "WinAnsiEncoding" || + activeFontEncoding === "StandardEncoding" + ) { + text = processTextByFunction(text, function(text, posX, posY) { + return [ESC(text), posX, posY]; + }); + } -/** - * @summary Buffer object to read data from TFile - * - * @private - */ + da = transformTextToSpecialArray(text); + + text = []; + var STRING = 0; + var ARRAY = 1; + var variant = Array.isArray(da[0]) ? ARRAY : STRING; + var posX; + var posY; + var content; + var wordSpacing = ""; + + var generatePosition = function( + parmPosX, + parmPosY, + parmTransformationMatrix + ) { + var position = ""; + if (parmTransformationMatrix instanceof Matrix) { + // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y + // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied + // in the coordinate system established by this matrix + if (typeof options.angle === "number") { + parmTransformationMatrix = matrixMult( + parmTransformationMatrix, + new Matrix(1, 0, 0, 1, parmPosX, parmPosY) + ); + } else { + parmTransformationMatrix = matrixMult( + new Matrix(1, 0, 0, 1, parmPosX, parmPosY), + parmTransformationMatrix + ); + } -class TBuffer { + if (apiMode === ApiMode.ADVANCED) { + parmTransformationMatrix = matrixMult( + new Matrix(1, 0, 0, -1, 0, 0), + parmTransformationMatrix + ); + } - constructor(arr, pos, file, length) { - this._typename = 'TBuffer'; - this.arr = arr; - this.o = pos || 0; - this.fFile = file; - this.length = length || (arr ? arr.byteLength : 0); // use size of array view, blob buffer can be much bigger - this.clearObjectMap(); - this.fTagOffset = 0; - this.last_read_version = 0; - } + position = parmTransformationMatrix.join(" ") + " Tm\n"; + } else { + position = hpf(parmPosX) + " " + hpf(parmPosY) + " Td\n"; + } + return position; + }; - /** @summary locate position in the buffer */ - locate(pos) { this.o = pos; } + for (var lineIndex = 0; lineIndex < da.length; lineIndex++) { + wordSpacing = ""; - /** @summary shift position in the buffer */ - shift(cnt) { this.o += cnt; } + switch (variant) { + case ARRAY: + content = + (isHex ? "<" : "(") + da[lineIndex][0] + (isHex ? ">" : ")"); + posX = parseFloat(da[lineIndex][1]); + posY = parseFloat(da[lineIndex][2]); + break; + case STRING: + content = (isHex ? "<" : "(") + da[lineIndex] + (isHex ? ">" : ")"); + posX = getHorizontalCoordinate(x); + posY = getVerticalCoordinate(y); + break; + } - /** @summary Returns remaining place in the buffer */ - remain() { return this.length - this.o; } + if ( + typeof wordSpacingPerLine !== "undefined" && + typeof wordSpacingPerLine[lineIndex] !== "undefined" + ) { + wordSpacing = wordSpacingPerLine[lineIndex] + " Tw\n"; + } + + if (lineIndex === 0) { + text.push( + wordSpacing + + generatePosition(posX, posY, transformationMatrix) + + content + ); + } else if (variant === STRING) { + text.push(wordSpacing + content); + } else if (variant === ARRAY) { + text.push( + wordSpacing + + generatePosition(posX, posY, transformationMatrix) + + content + ); + } + } - /** @summary Get mapped object with provided tag */ - getMappedObject(tag) { return this.fObjectMap[tag]; } + text = variant === STRING ? text.join(" Tj\nT* ") : text.join(" Tj\n"); + text += " Tj\n"; - /** @summary Map object */ - mapObject(tag, obj) { if (obj !== null) this.fObjectMap[tag] = obj; } + var result = "BT\n/"; + result += activeFontKey + " " + activeFontSize + " Tf\n"; // font face, style, size + result += hpf(activeFontSize * lineHeight) + " TL\n"; // line spacing + result += textColor + "\n"; + result += xtra; + result += text; + result += "ET"; - /** @summary Map class */ - mapClass(tag, classname) { this.fClassMap[tag] = classname; } + out(result); + usedFonts[activeFontKey] = true; + return scope; + }; - /** @summary Get mapped class with provided tag */ - getMappedClass(tag) { return (tag in this.fClassMap) ? this.fClassMap[tag] : -1; } + // PDF supports these path painting and clip path operators: + // + // S - stroke + // s - close/stroke + // f (F) - fill non-zero + // f* - fill evenodd + // B - fill stroke nonzero + // B* - fill stroke evenodd + // b - close fill stroke nonzero + // b* - close fill stroke evenodd + // n - nothing (consume path) + // W - clip nonzero + // W* - clip evenodd + // + // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close() + // method. + /** + * + * @name clip + * @function + * @instance + * @param {string} rule Only possible value is 'evenodd' + * @returns {jsPDF} + * @memberof jsPDF# + * @description All .clip() after calling drawing ops with a style argument of null. + */ + var clip = (API.__private__.clip = API.clip = function(rule) { + // Call .clip() after calling drawing ops with a style argument of null + // W is the PDF clipping op + if ("evenodd" === rule) { + out("W*"); + } else { + out("W"); + } + return this; + }); - /** @summary Clear objects map */ - clearObjectMap() { - this.fObjectMap = {}; - this.fClassMap = {}; - this.fObjectMap[0] = null; - this.fDisplacement = 0; - } + /** + * @name clipEvenOdd + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @description Modify the current clip path by intersecting it with the current path using the even-odd rule. Note + * that this will NOT consume the current path. In order to only use this path for clipping call + * {@link API.discardPath} afterwards. + */ + API.clipEvenOdd = function() { + return clip("evenodd"); + }; - /** @summary read class version from I/O buffer */ - readVersion() { - const ver = {}, bytecnt = this.ntou4(); // byte count + /** + * Consumes the current path without any effect. Mainly used in combination with {@link clip} or + * {@link clipEvenOdd}. The PDF "n" operator. + * @name discardPath + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.__private__.discardPath = API.discardPath = function() { + out("n"); + return this; + }; - if (bytecnt & kByteCountMask) - ver.bytecnt = bytecnt - kByteCountMask - 2; // one can check between Read version and end of streamer - else - this.o -= 4; // rollback read bytes, this is old buffer without byte count + var isValidStyle = (API.__private__.isValidStyle = function(style) { + var validStyleVariants = [ + undefined, + null, + "S", + "D", + "F", + "DF", + "FD", + "f", + "f*", + "B", + "B*", + "n" + ]; + var result = false; + if (validStyleVariants.indexOf(style) !== -1) { + result = true; + } + return result; + }); - this.last_read_version = ver.val = this.ntoi2(); - this.last_read_checksum = 0; - ver.off = this.o; + API.__private__.setDefaultPathOperation = API.setDefaultPathOperation = function( + operator + ) { + if (isValidStyle(operator)) { + defaultPathOperation = operator; + } + return this; + }; - if ((ver.val <= 0) && ver.bytecnt && (ver.bytecnt >= 4)) { - ver.checksum = this.ntou4(); - if (!this.fFile.findStreamerInfo(undefined, undefined, ver.checksum)) { - // console.error(`Fail to find streamer info with check sum ${ver.checksum} version ${ver.val}`); - this.o -= 4; // not found checksum in the list - delete ver.checksum; // remove checksum - } else - this.last_read_checksum = ver.checksum; - } - return ver; - } + var getStyle = (API.__private__.getStyle = API.getStyle = function(style) { + // see path-painting operators in PDF spec + var op = defaultPathOperation; // stroke - /** @summary Check bytecount after object streaming */ - checkByteCount(ver, where) { - if ((ver.bytecnt !== undefined) && (ver.off + ver.bytecnt !== this.o)) { - if (where) - console.log(`Missmatch in ${where} bytecount expected = ${ver.bytecnt} got = ${this.o - ver.off}`); - this.o = ver.off + ver.bytecnt; - return false; - } - return true; - } + switch (style) { + case "D": + case "S": + op = "S"; // stroke + break; + case "F": + op = "f"; // fill + break; + case "FD": + case "DF": + op = "B"; + break; + case "f": + case "f*": + case "B": + case "B*": + /* + Allow direct use of these PDF path-painting operators: + - f fill using nonzero winding number rule + - f* fill using even-odd rule + - B fill then stroke with fill using non-zero winding number rule + - B* fill then stroke with fill using even-odd rule + */ + op = style; + break; + } + return op; + }); - /** @summary Read TString object (or equivalent) - * @desc std::string uses similar binary format */ - readTString() { - let len = this.ntou1(); - // large strings - if (len === 255) len = this.ntou4(); - if (len === 0) return ''; + /** + * Close the current path. The PDF "h" operator. + * @name close + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + var close = (API.close = function() { + out("h"); + return this; + }); - const pos = this.o; - this.o += len; + /** + * Stroke the path. The PDF "S" operator. + * @name stroke + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.stroke = function() { + out("S"); + return this; + }; - return (this.codeAt(pos) === 0) ? '' : this.substring(pos, pos + len); - } + /** + * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled + * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator. + * @name fill + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be filled with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fill = function(pattern) { + fillWithOptionalPattern("f", pattern); + return this; + }; - /** @summary read Char_t array as string - * @desc string either contains all symbols or until 0 symbol */ - readFastString(n) { - let res = '', code, closed = false; - // eslint-disable-next-line no-unmodified-loop-condition - for (let i = 0; (n < 0) || (i < n); ++i) { - code = this.ntou1(); - if (code === 0) { closed = true; if (n < 0) break; } - if (!closed) res += String.fromCharCode(code); - } + /** + * Fill the current path using the even-odd rule. The PDF f* operator. + * @see API.fill + * @name fillEvenOdd + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be filled with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillEvenOdd = function(pattern) { + fillWithOptionalPattern("f*", pattern); + return this; + }; - return res; - } + /** + * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator. + * @see API.fill + * @name fillStroke + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be stroked with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillStroke = function(pattern) { + fillWithOptionalPattern("B", pattern); + return this; + }; - /** @summary read uint8_t */ - ntou1() { return this.arr.getUint8(this.o++); } + /** + * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator. + * @see API.fill + * @name fillStrokeEvenOdd + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be fill-stroked with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillStrokeEvenOdd = function(pattern) { + fillWithOptionalPattern("B*", pattern); + return this; + }; - /** @summary read uint16_t */ - ntou2() { - const o = this.o; this.o += 2; - return this.arr.getUint16(o); - } + var fillWithOptionalPattern = function(style, pattern) { + if (typeof pattern === "object") { + fillWithPattern(pattern, style); + } else { + out(style); + } + }; - /** @summary read uint32_t */ - ntou4() { - const o = this.o; this.o += 4; - return this.arr.getUint32(o); - } + var putStyle = function(style) { + if ( + style === null || + (apiMode === ApiMode.ADVANCED && style === undefined) + ) { + return; + } - /** @summary read uint64_t */ - ntou8() { - const high = this.arr.getUint32(this.o); this.o += 4; - const low = this.arr.getUint32(this.o); this.o += 4; - return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); - } + style = getStyle(style); - /** @summary read int8_t */ - ntoi1() { return this.arr.getInt8(this.o++); } + // stroking / filling / both the path + out(style); + }; - /** @summary read int16_t */ - ntoi2() { - const o = this.o; this.o += 2; - return this.arr.getInt16(o); - } + function cloneTilingPattern(patternKey, boundingBox, xStep, yStep, matrix) { + var clone = new TilingPattern( + boundingBox || this.boundingBox, + xStep || this.xStep, + yStep || this.yStep, + this.gState, + matrix || this.matrix + ); + clone.stream = this.stream; + var key = patternKey + "$$" + this.cloneIndex++ + "$$"; + addPattern(key, clone); + return clone; + } - /** @summary read int32_t */ - ntoi4() { - const o = this.o; this.o += 4; - return this.arr.getInt32(o); - } + var fillWithPattern = function(patternData, style) { + var patternId = patternMap[patternData.key]; + var pattern = patterns[patternId]; - /** @summary read int64_t */ - ntoi8() { - const high = this.arr.getUint32(this.o); this.o += 4; - const low = this.arr.getUint32(this.o); this.o += 4; - if (high < 0x80000000) - return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); - return (~high < 0x200000) ? (-1 - ((~high) * 0x100000000 + ~low)) : (BigInt(-1) - (BigInt(~high) * BigInt(0x100000000) + BigInt(~low))); - } + if (pattern instanceof ShadingPattern) { + out("q"); - /** @summary read float */ - ntof() { - const o = this.o; this.o += 4; - return this.arr.getFloat32(o); - } + out(clipRuleFromStyle(style)); - /** @summary read double */ - ntod() { - const o = this.o; this.o += 8; - return this.arr.getFloat64(o); - } + if (pattern.gState) { + API.setGState(pattern.gState); + } + out(patternData.matrix.toString() + " cm"); + out("/" + patternId + " sh"); + out("Q"); + } else if (pattern instanceof TilingPattern) { + // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation, + // so we must flip them + var matrix = new Matrix(1, 0, 0, -1, 0, getPageHeight()); - /** @summary Reads array of n values from the I/O buffer */ - readFastArray(n, array_type) { - let array, i = 0, o = this.o; - const view = this.arr; - switch (array_type) { - case kDouble: - array = new Float64Array(n); - for (; i < n; ++i, o += 8) - array[i] = view.getFloat64(o); - break; - case kFloat: - array = new Float32Array(n); - for (; i < n; ++i, o += 4) - array[i] = view.getFloat32(o); - break; - case kLong: - case kLong64: - array = new Array(n); - for (; i < n; ++i) - array[i] = this.ntoi8(); - return array; // exit here to avoid conflicts - case kULong: - case kULong64: - array = new Array(n); - for (; i < n; ++i) - array[i] = this.ntou8(); - return array; // exit here to avoid conflicts - case kInt: - case kCounter: - array = new Int32Array(n); - for (; i < n; ++i, o += 4) - array[i] = view.getInt32(o); - break; - case kShort: - array = new Int16Array(n); - for (; i < n; ++i, o += 2) - array[i] = view.getInt16(o); - break; - case kUShort: - array = new Uint16Array(n); - for (; i < n; ++i, o += 2) - array[i] = view.getUint16(o); - break; - case kChar: - array = new Int8Array(n); - for (; i < n; ++i) - array[i] = view.getInt8(o++); - break; - case kBool: - case kUChar: - array = new Uint8Array(n); - for (; i < n; ++i) - array[i] = view.getUint8(o++); - break; - case kTString: - array = new Array(n); - for (; i < n; ++i) - array[i] = this.readTString(); - return array; // exit here to avoid conflicts - case kDouble32: - throw new Error('kDouble32 should not be used in readFastArray'); - case kFloat16: - throw new Error('kFloat16 should not be used in readFastArray'); - // case kBits: - // case kUInt: - default: - array = new Uint32Array(n); - for (; i < n; ++i, o += 4) - array[i] = view.getUint32(o); - break; + if (patternData.matrix) { + matrix = matrix.multiply(patternData.matrix || identityMatrix); + // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances + // for each use + patternId = cloneTilingPattern.call( + pattern, + patternData.key, + patternData.boundingBox, + patternData.xStep, + patternData.yStep, + matrix + ).id; } - this.o = o; - return array; - } + out("q"); + out("/Pattern cs"); + out("/" + patternId + " scn"); - /** @summary Check if provided regions can be extracted from the buffer */ - canExtract(place) { - for (let n = 0; n < place.length; n += 2) - if (place[n] + place[n + 1] > this.length) return false; - return true; - } + if (pattern.gState) { + API.setGState(pattern.gState); + } - /** @summary Extract area */ - extract(place) { - if (!this.arr || !this.arr.buffer || !this.canExtract(place)) return null; - if (place.length === 2) return new DataView(this.arr.buffer, this.arr.byteOffset + place[0], place[1]); + out(style); + out("Q"); + } + }; - const res = new Array(place.length / 2); - for (let n = 0; n < place.length; n += 2) - res[n / 2] = new DataView(this.arr.buffer, this.arr.byteOffset + place[n], place[n + 1]); + var clipRuleFromStyle = function(style) { + switch (style) { + case "f": + case "F": + return "W n"; + case "f*": + return "W* n"; + case "B": + return "W S"; + case "B*": + return "W* S"; + + // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern + // and "n"/"S" as style would still fill/fill and stroke the path) + case "S": + return "W S"; + case "n": + return "W n"; + } + }; - return res; // return array of buffers - } + /** + * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator. + * @param {number} x + * @param {number} y + * @name moveTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var moveTo = (API.moveTo = function(x, y) { + out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " m"); + return this; + }); - /** @summary Get code at buffer position */ - codeAt(pos) { - return this.arr.getUint8(pos); - } + /** + * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator. + * @param {number} x + * @param {number} y + * @memberof jsPDF# + * @name lineTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var lineTo = (API.lineTo = function(x, y) { + out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " l"); + return this; + }); - /** @summary Get part of buffer as string */ - substring(beg, end) { - let res = ''; - for (let n = beg; n < end; ++n) - res += String.fromCharCode(this.arr.getUint8(n)); - return res; - } + /** + * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point + * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3). + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @memberof jsPDF# + * @name curveTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var curveTo = (API.curveTo = function(x1, y1, x2, y2, x3, y3) { + out( + [ + hpf(scale(x1)), + hpf(transformScaleY(y1)), + hpf(scale(x2)), + hpf(transformScaleY(y2)), + hpf(scale(x3)), + hpf(transformScaleY(y3)), + "c" + ].join(" ") + ); + return this; + }); - /** @summary Read buffer as N-dim array */ - readNdimArray(handle, func) { - let ndim = handle.fArrayDim, maxindx = handle.fMaxIndex, res; - if ((ndim < 1) && (handle.fArrayLength > 0)) { ndim = 1; maxindx = [handle.fArrayLength]; } - if (handle.minus1) --ndim; + /** + * Draw a line on the current page. + * + * @name line + * @function + * @instance + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {string} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. default: 'S' + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.__private__.line = API.line = function(x1, y1, x2, y2, style) { + if ( + isNaN(x1) || + isNaN(y1) || + isNaN(x2) || + isNaN(y2) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.line"); + } + if (apiMode === ApiMode.COMPAT) { + return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], style || "S"); + } else { + return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke(); + } + }; - if (ndim < 1) return func(this, handle); + /** + * @typedef {Object} PatternData + * {Matrix|undefined} matrix + * {Number|undefined} xStep + * {Number|undefined} yStep + * {Array.|undefined} boundingBox + */ - if (ndim === 1) { - res = new Array(maxindx[0]); - for (let n = 0; n < maxindx[0]; ++n) - res[n] = func(this, handle); - } else if (ndim === 2) { - res = new Array(maxindx[0]); - for (let n = 0; n < maxindx[0]; ++n) { - const res2 = new Array(maxindx[1]); - for (let k = 0; k < maxindx[1]; ++k) - res2[k] = func(this, handle); - res[n] = res2; - } + /** + * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. + * All data points in `lines` are relative to last line origin. + * `x`, `y` become x1,y1 for first line / curve in the set. + * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. + * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. + * + * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, [1,1], 'F', false) // line, line, bezier curve, line + * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name lines + */ + API.__private__.lines = API.lines = function( + lines, + x, + y, + scale, + style, + closed + ) { + var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4, tmp; + + // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) + // in effort to make all calls have similar signature like + // function(content, coordinateX, coordinateY , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof lines === "number") { + tmp = y; + y = x; + x = lines; + lines = tmp; + } + + scale = scale || [1, 1]; + closed = closed || false; + + if ( + isNaN(x) || + isNaN(y) || + !Array.isArray(lines) || + !Array.isArray(scale) || + !isValidStyle(style) || + typeof closed !== "boolean" + ) { + throw new Error("Invalid arguments passed to jsPDF.lines"); + } + + // starting point + moveTo(x, y); + + scalex = scale[0]; + scaley = scale[1]; + l = lines.length; + //, x2, y2 // bezier only. In page default measurement "units", *after* scaling + //, x3, y3 // bezier only. In page default measurement "units", *after* scaling + // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling + x4 = x; // last / ending point = starting point for first item. + y4 = y; // last / ending point = starting point for first item. + + for (i = 0; i < l; i++) { + leg = lines[i]; + if (leg.length === 2) { + // simple line + x4 = leg[0] * scalex + x4; // here last x4 was prior ending point + y4 = leg[1] * scaley + y4; // here last y4 was prior ending point + lineTo(x4, y4); } else { - const indx = new Array(ndim).fill(0), arr = new Array(ndim); - for (let k = 0; k < ndim; ++k) - arr[k] = []; - res = arr[0]; - while (indx[0] < maxindx[0]) { - let k = ndim - 1; - arr[k].push(func(this, handle)); - ++indx[k]; - while ((indx[k] === maxindx[k]) && (k > 0)) { - indx[k] = 0; - arr[k - 1].push(arr[k]); - arr[k] = []; - ++indx[--k]; - } - } + // bezier curve + x2 = leg[0] * scalex + x4; // here last x4 is prior ending point + y2 = leg[1] * scaley + y4; // here last y4 is prior ending point + x3 = leg[2] * scalex + x4; // here last x4 is prior ending point + y3 = leg[3] * scaley + y4; // here last y4 is prior ending point + x4 = leg[4] * scalex + x4; // here last x4 was prior ending point + y4 = leg[5] * scaley + y4; // here last y4 was prior ending point + curveTo(x2, y2, x3, y3, x4, y4); } + } - return res; - } + if (closed) { + close(); + } - /** @summary read TKey data */ - readTKey(key) { - if (!key) key = {}; - this.classStreamer(key, clTKey); - const name = key.fName.replace(/['"]/g, ''); - if (name !== key.fName) { - key.fRealName = key.fName; - key.fName = name; + putStyle(style); + return this; + }; + + /** + * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative. + * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to) + * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c" + * six and "h" an empty array (or undefined). + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name path + */ + API.path = function(lines) { + for (var i = 0; i < lines.length; i++) { + var leg = lines[i]; + var coords = leg.c; + switch (leg.op) { + case "m": + moveTo(coords[0], coords[1]); + break; + case "l": + lineTo(coords[0], coords[1]); + break; + case "c": + curveTo.apply(this, coords); + break; + case "h": + close(); + break; } - return key; - } + } - /** @summary reading basket data - * @desc this is remaining part of TBasket streamer to decode fEntryOffset - * after unzipping of the TBasket data */ - readBasketEntryOffset(basket, offset) { - this.locate(basket.fLast - offset); + return this; + }; - if (this.remain() <= 0) { - if (!basket.fEntryOffset && (basket.fNevBuf <= 1)) basket.fEntryOffset = [basket.fKeylen]; - if (!basket.fEntryOffset) console.warn(`No fEntryOffset when expected for basket with ${basket.fNevBuf} entries`); - return; - } + /** + * Adds a rectangle to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} w Width (in units declared at inception of PDF document) + * @param {number} h Height (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name rect + */ + API.__private__.rect = API.rect = function(x, y, w, h, style) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || !isValidStyle(style)) { + throw new Error("Invalid arguments passed to jsPDF.rect"); + } + if (apiMode === ApiMode.COMPAT) { + h = -h; + } - const nentries = this.ntoi4(); - // there is error in file=reco_103.root&item=Events;2/PCaloHits_g4SimHits_EcalHitsEE_Sim.&opt=dump;num:10;first:101 - // it is workaround, but normally I/O should fail here - if ((nentries < 0) || (nentries > this.remain() * 4)) { - console.error(`Error when reading entries offset from basket fNevBuf ${basket.fNevBuf} remains ${this.remain()} want to read ${nentries}`); - if (basket.fNevBuf <= 1) basket.fEntryOffset = [basket.fKeylen]; - return; - } + out( + [ + hpf(scale(x)), + hpf(transformScaleY(y)), + hpf(scale(w)), + hpf(scale(h)), + "re" + ].join(" ") + ); - basket.fEntryOffset = this.readFastArray(nentries, kInt); - if (!basket.fEntryOffset) basket.fEntryOffset = [basket.fKeylen]; + putStyle(style); + return this; + }; + + /** + * Adds a triangle to PDF. + * + * @param {number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name triangle + */ + API.__private__.triangle = API.triangle = function( + x1, + y1, + x2, + y2, + x3, + y3, + style + ) { + if ( + isNaN(x1) || + isNaN(y1) || + isNaN(x2) || + isNaN(y2) || + isNaN(x3) || + isNaN(y3) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.triangle"); + } + this.lines( + [ + [x2 - x1, y2 - y1], // vector to point 2 + [x3 - x2, y3 - y2], // vector to point 3 + [x1 - x3, y1 - y3] // closing vector back to point 1 + ], + x1, + y1, // start of path + [1, 1], + style, + true + ); + return this; + }; + + /** + * Adds a rectangle with rounded corners to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} w Width (in units declared at inception of PDF document) + * @param {number} h Height (in units declared at inception of PDF document) + * @param {number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {number} ry Radius along y axis (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name roundedRect + */ + API.__private__.roundedRect = API.roundedRect = function( + x, + y, + w, + h, + rx, + ry, + style + ) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(w) || + isNaN(h) || + isNaN(rx) || + isNaN(ry) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.roundedRect"); + } + var MyArc = (4 / 3) * (Math.SQRT2 - 1); + + rx = Math.min(rx, w * 0.5); + ry = Math.min(ry, h * 0.5); + + this.lines( + [ + [w - 2 * rx, 0], + [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], + [0, h - 2 * ry], + [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], + [-w + 2 * rx, 0], + [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], + [0, -h + 2 * ry], + [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry] + ], + x + rx, + y, // start of path + [1, 1], + style, + true + ); + return this; + }; + + /** + * Adds an ellipse to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {number} ry Radius along y axis (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name ellipse + */ + API.__private__.ellipse = API.ellipse = function(x, y, rx, ry, style) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(rx) || + isNaN(ry) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.ellipse"); + } + var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, + ly = (4 / 3) * (Math.SQRT2 - 1) * ry; - if (this.remain() > 0) - basket.fDisplacement = this.readFastArray(this.ntoi4(), kInt); - else - basket.fDisplacement = undefined; - } + moveTo(x + rx, y); + curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry); + curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y); + curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry); + curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); - /** @summary read class definition from I/O buffer */ - readClass() { - const classInfo = { name: -1 }, bcnt = this.ntou4(), startpos = this.o; - let tag; + putStyle(style); + return this; + }; - if (!(bcnt & kByteCountMask) || (bcnt === kNewClassTag)) - tag = bcnt; - else - tag = this.ntou4(); + /** + * Adds an circle to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} r Radius (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name circle + */ + API.__private__.circle = API.circle = function(x, y, r, style) { + if (isNaN(x) || isNaN(y) || isNaN(r) || !isValidStyle(style)) { + throw new Error("Invalid arguments passed to jsPDF.circle"); + } + return this.ellipse(x, y, r, r, style); + }; - if (!(tag & kClassMask)) { - classInfo.objtag = tag + this.fDisplacement; // indicate that we have deal with objects tag - return classInfo; - } - if (tag === kNewClassTag) { - // got a new class description followed by a new object - classInfo.name = this.readFastString(-1); + /** + * Sets text font face, variant for upcoming text elements. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {string} fontName Font name or family. Example: "times". + * @param {string} fontStyle Font style or variant. Example: "italic". + * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFont + */ + API.setFont = function(fontName, fontStyle, fontWeight) { + if (fontWeight) { + fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); + } + activeFontKey = getFont(fontName, fontStyle, { + disableWarning: false + }); + return this; + }; - if (this.getMappedClass(this.fTagOffset + startpos + kMapOffset) === -1) - this.mapClass(this.fTagOffset + startpos + kMapOffset, classInfo.name); - } else { - // got a tag to an already seen class - const clTag = (tag & ~kClassMask) + this.fDisplacement; - classInfo.name = this.getMappedClass(clTag); + /** + * Gets text font face, variant for upcoming text elements. + * + * @function + * @instance + * @returns {Object} + * @memberof jsPDF# + * @name getFont + */ + var getFontEntry = (API.__private__.getFont = API.getFont = function() { + return fonts[getFont.apply(API, arguments)]; + }); - if (classInfo.name === -1) - console.error(`Did not found class with tag ${clTag}`); + /** + * Returns an object - a tree of fontName to fontStyle relationships available to + * active PDF document. + * + * @public + * @function + * @instance + * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } + * @memberof jsPDF# + * @name getFontList + */ + API.__private__.getFontList = API.getFontList = function() { + var list = {}, + fontName, + fontStyle; + + for (fontName in fontmap) { + if (fontmap.hasOwnProperty(fontName)) { + list[fontName] = []; + for (fontStyle in fontmap[fontName]) { + if (fontmap[fontName].hasOwnProperty(fontStyle)) { + list[fontName].push(fontStyle); + } + } } + } + return list; + }; - return classInfo; - } - - /** @summary Read any object from buffer data */ - readObjectAny() { - const objtag = this.fTagOffset + this.o + kMapOffset, - clRef = this.readClass(); + /** + * Add a custom font to the current instance. + * + * @param {string} postScriptName PDF specification full name for the font. + * @param {string} id PDF-document-instance-specific label assinged to the font. + * @param {string} fontStyle Style of the Font. + * @param {number | string} fontWeight Weight of the Font. + * @param {Object} encoding Encoding_name-to-Font_metrics_object mapping. + * @function + * @instance + * @memberof jsPDF# + * @name addFont + * @returns {string} fontId + */ + API.addFont = function( + postScriptName, + fontName, + fontStyle, + fontWeight, + encoding + ) { + var encodingOptions = [ + "StandardEncoding", + "MacRomanEncoding", + "Identity-H", + "WinAnsiEncoding" + ]; + if (arguments[3] && encodingOptions.indexOf(arguments[3]) !== -1) { + //IE 11 fix + encoding = arguments[3]; + } else if (arguments[3] && encodingOptions.indexOf(arguments[3]) == -1) { + fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); + } + encoding = encoding || "Identity-H"; + return addFont.call(this, postScriptName, fontName, fontStyle, encoding); + }; - // class identified as object and should be handled so - if ('objtag' in clRef) - return this.getMappedObject(clRef.objtag); + var lineWidth = options.lineWidth || 0.200025; // 2mm + /** + * Gets the line width, default: 0.200025. + * + * @function + * @instance + * @returns {number} lineWidth + * @memberof jsPDF# + * @name getLineWidth + */ + var getLineWidth = (API.__private__.getLineWidth = API.getLineWidth = function() { + return lineWidth; + }); - if (clRef.name === -1) return null; + /** + * Sets line width for upcoming lines. + * + * @param {number} width Line width (in units declared at inception of PDF document). + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineWidth + */ + var setLineWidth = (API.__private__.setLineWidth = API.setLineWidth = function( + width + ) { + lineWidth = width; + out(hpf(scale(width)) + " w"); + return this; + }); - const arrkind = getArrayKind(clRef.name); - let obj; + /** + * Sets the dash pattern for upcoming lines. + * + * To reset the settings simply call the method without any parameters. + * @param {Array} dashArray An array containing 0-2 numbers. The first number sets the length of the + * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered + * to be as long as the dashes. An empty array means solid, unbroken lines. + * @param {number} dashPhase The phase lines start with. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineDashPattern + */ + API.__private__.setLineDash = jsPDF.API.setLineDash = jsPDF.API.setLineDashPattern = function( + dashArray, + dashPhase + ) { + dashArray = dashArray || []; + dashPhase = dashPhase || 0; + + if (isNaN(dashPhase) || !Array.isArray(dashArray)) { + throw new Error("Invalid arguments passed to jsPDF.setLineDash"); + } - if (arrkind === 0) - obj = this.readTString(); - else if (arrkind > 0) { - // reading array, can map array only afterwards - obj = this.readFastArray(this.ntou4(), arrkind); - this.mapObject(objtag, obj); - } else { - // reading normal object, should map before to - obj = {}; - this.mapObject(objtag, obj); - this.classStreamer(obj, clRef.name); - } + dashArray = dashArray + .map(function(x) { + return hpf(scale(x)); + }) + .join(" "); + dashPhase = hpf(scale(dashPhase)); - return obj; - } + out("[" + dashArray + "] " + dashPhase + " d"); + return this; + }; - /** @summary Invoke streamer for specified class */ - classStreamer(obj, classname) { - if (obj._typename === undefined) obj._typename = classname; + var lineHeightFactor; - const direct = DirectStreamers[classname]; - if (direct) { - direct(this, obj); - return obj; - } + var getLineHeight = (API.__private__.getLineHeight = API.getLineHeight = function() { + return activeFontSize * lineHeightFactor; + }); - const ver = this.readVersion(), - streamer = this.fFile.getStreamer(classname, ver); + API.__private__.getLineHeight = API.getLineHeight = function() { + return activeFontSize * lineHeightFactor; + }; - if (streamer !== null) { - const len = streamer.length; - for (let n = 0; n < len; ++n) - streamer[n].func(this, obj); - } else { - // just skip bytes belonging to not-recognized object - // console.warn(`skip object ${classname}`); - addMethods(obj); - } + /** + * Sets the LineHeightFactor of proportion. + * + * @param {number} value LineHeightFactor value. Default: 1.15. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineHeightFactor + */ + var setLineHeightFactor = (API.__private__.setLineHeightFactor = API.setLineHeightFactor = function( + value + ) { + value = value || 1.15; + if (typeof value === "number") { + lineHeightFactor = value; + } + return this; + }); - this.checkByteCount(ver, classname); + /** + * Gets the LineHeightFactor, default: 1.15. + * + * @function + * @instance + * @returns {number} lineHeightFactor + * @memberof jsPDF# + * @name getLineHeightFactor + */ + var getLineHeightFactor = (API.__private__.getLineHeightFactor = API.getLineHeightFactor = function() { + return lineHeightFactor; + }); - return obj; - } + setLineHeightFactor(options.lineHeight); -} // class TBuffer + var getHorizontalCoordinate = (API.__private__.getHorizontalCoordinate = function( + value + ) { + return scale(value); + }); -// ============================================================================== + var getVerticalCoordinate = (API.__private__.getVerticalCoordinate = function( + value + ) { + if (apiMode === ApiMode.ADVANCED) { + return value; + } else { + var pageHeight = + pagesContext[currentPage].mediaBox.topRightY - + pagesContext[currentPage].mediaBox.bottomLeftY; + return pageHeight - scale(value); + } + }); -/** - * @summary A class that reads a TDirectory from a buffer. - * - * @private - */ + var getHorizontalCoordinateString = (API.__private__.getHorizontalCoordinateString = API.getHorizontalCoordinateString = function( + value + ) { + return hpf(getHorizontalCoordinate(value)); + }); -class TDirectory { + var getVerticalCoordinateString = (API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function( + value + ) { + return hpf(getVerticalCoordinate(value)); + }); - /** @summary constructor */ - constructor(file, dirname, cycle) { - this.fFile = file; - this._typename = clTDirectory; - this.dir_name = dirname; - this.dir_cycle = cycle; - this.fKeys = []; - } + var strokeColor = options.strokeColor || "0 G"; - /** @summary retrieve a key by its name and cycle in the list of keys */ - getKey(keyname, cycle, only_direct) { - if (typeof cycle !== 'number') cycle = -1; - let bestkey = null; - for (let i = 0; i < this.fKeys.length; ++i) { - const key = this.fKeys[i]; - if (!key || (key.fName !== keyname)) continue; - if (key.fCycle === cycle) { bestkey = key; break; } - if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) bestkey = key; - } - if (bestkey) - return only_direct ? bestkey : Promise.resolve(bestkey); + /** + * Gets the stroke color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getDrawColor + */ + API.__private__.getStrokeColor = API.getDrawColor = function() { + return decodeColorString(strokeColor); + }; - let pos = keyname.lastIndexOf('/'); - // try to handle situation when object name contains slashed (bad practice anyway) - while (pos > 0) { - const dirname = keyname.slice(0, pos), - subname = keyname.slice(pos+1), - dirkey = this.getKey(dirname, undefined, true); + /** + * Sets the stroke color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setDrawColor + */ + API.__private__.setStrokeColor = API.setDrawColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "draw", + precision: 2 + }; - if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) { - return this.fFile.readObject(this.dir_name + '/' + dirname, 1) - .then(newdir => newdir.getKey(subname, cycle)); - } + strokeColor = encodeColorString(options); + out(strokeColor); + return this; + }; - pos = keyname.lastIndexOf('/', pos-1); - } + var fillColor = options.fillColor || "0 g"; - return only_direct ? null : Promise.reject(Error(`Key not found ${keyname}`)); - } + /** + * Gets the fill color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getFillColor + */ + API.__private__.getFillColor = API.getFillColor = function() { + return decodeColorString(fillColor); + }; - /** @summary Read object from the directory - * @param {string} name - object name - * @param {number} [cycle] - cycle number - * @return {Promise} with read object */ - readObject(obj_name, cycle) { - return this.fFile.readObject(this.dir_name + '/' + obj_name, cycle); - } + /** + * Sets the fill color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFillColor + */ + API.__private__.setFillColor = API.setFillColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "fill", + precision: 2 + }; - /** @summary Read list of keys in directory - * @return {Promise} with TDirectory object */ - async readKeys(objbuf) { - objbuf.classStreamer(this, clTDirectory); + fillColor = encodeColorString(options); + out(fillColor); + return this; + }; - if ((this.fSeekKeys <= 0) || (this.fNbytesKeys <= 0)) - return this; + var textColor = options.textColor || "0 g"; + /** + * Gets the text color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getTextColor + */ + var getTextColor = (API.__private__.getTextColor = API.getTextColor = function() { + return decodeColorString(textColor); + }); + /** + * Sets the text color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setTextColor + */ + API.__private__.setTextColor = API.setTextColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "text", + precision: 3 + }; + textColor = encodeColorString(options); - return this.fFile.readBuffer([this.fSeekKeys, this.fNbytesKeys]).then(blob => { - // Read keys of the top directory + return this; + }; - const buf = new TBuffer(blob, 0, this.fFile); + var activeCharSpace = options.charSpace; - buf.readTKey(); - const nkeys = buf.ntoi4(); + /** + * Get global value of CharSpace. + * + * @function + * @instance + * @returns {number} charSpace + * @memberof jsPDF# + * @name getCharSpace + */ + var getCharSpace = (API.__private__.getCharSpace = API.getCharSpace = function() { + return parseFloat(activeCharSpace || 0); + }); - for (let i = 0; i < nkeys; ++i) - this.fKeys.push(buf.readTKey()); + /** + * Set global value of CharSpace. + * + * @param {number} charSpace + * @function + * @instance + * @returns {jsPDF} jsPDF-instance + * @memberof jsPDF# + * @name setCharSpace + */ + API.__private__.setCharSpace = API.setCharSpace = function(charSpace) { + if (isNaN(charSpace)) { + throw new Error("Invalid argument passed to jsPDF.setCharSpace"); + } + activeCharSpace = charSpace; + return this; + }; - this.fFile.fDirectories.push(this); + var lineCapID = 0; + /** + * Is an Object providing a mapping from human-readable to + * integer flag values designating the varieties of line cap + * and join styles. + * + * @memberof jsPDF# + * @name CapJoinStyles + */ + API.CapJoinStyles = { + 0: 0, + butt: 0, + but: 0, + miter: 0, + 1: 1, + round: 1, + rounded: 1, + circle: 1, + 2: 2, + projecting: 2, + project: 2, + square: 2, + bevel: 2 + }; - return this; - }); - } + /** + * Sets the line cap styles. + * See {jsPDF.CapJoinStyles} for variants. + * + * @param {String|Number} style A string or number identifying the type of line cap. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineCap + */ + API.__private__.setLineCap = API.setLineCap = function(style) { + var id = API.CapJoinStyles[style]; + if (id === undefined) { + throw new Error( + "Line cap style of '" + + style + + "' is not recognized. See or extend .CapJoinStyles property for valid styles" + ); + } + lineCapID = id; + out(id + " J"); -} // class TDirectory + return this; + }; -/** - * @summary Interface to read objects from ROOT files - * - * @desc Use {@link openFile} to create instance of the class - */ + var lineJoinID = 0; + /** + * Sets the line join styles. + * See {jsPDF.CapJoinStyles} for variants. + * + * @param {String|Number} style A string or number identifying the type of line join. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineJoin + */ + API.__private__.setLineJoin = API.setLineJoin = function(style) { + var id = API.CapJoinStyles[style]; + if (id === undefined) { + throw new Error( + "Line join style of '" + + style + + "' is not recognized. See or extend .CapJoinStyles property for valid styles" + ); + } + lineJoinID = id; + out(id + " j"); -class TFile { + return this; + }; + /** + * Sets the miterLimit property, which effects the maximum miter length. + * + * @param {number} length The length of the miter + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineMiterLimit + */ + API.__private__.setLineMiterLimit = API.__private__.setMiterLimit = API.setLineMiterLimit = API.setMiterLimit = function( + length + ) { + length = length || 0; + if (isNaN(length)) { + throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit"); + } + out(hpf(scale(length)) + " M"); - constructor(url) { - this._typename = clTFile; - this.fEND = 0; - this.fFullURL = url; - this.fURL = url; - // when disabled ('+' at the end of file name), complete file content read with single operation - this.fAcceptRanges = true; - // use additional time stamp parameter for file name to avoid browser caching problem - this.fUseStampPar = settings.UseStamp ? 'stamp=' + (new Date()).getTime() : false; - this.fFileContent = null; // this can be full or partial content of the file (if ranges are not supported or if 1K header read from file) - // stored as TBuffer instance - this.fMaxRanges = settings.MaxRanges || 200; // maximal number of file ranges requested at once - this.fDirectories = []; - this.fKeys = []; - this.fSeekInfo = 0; - this.fNbytesInfo = 0; - this.fTagOffset = 0; - this.fStreamers = 0; - this.fStreamerInfos = null; - this.fFileName = ''; - this.fStreamers = []; - this.fBasicTypes = {}; // custom basic types, in most case enumerations + return this; + }; - if (!isStr(this.fURL)) return this; + /** + * An object representing a pdf graphics state. + * @class GState + */ - if (this.fURL[this.fURL.length - 1] === '+') { - this.fURL = this.fURL.slice(0, this.fURL.length - 1); - this.fAcceptRanges = false; - } + /** + * + * @param parameters A parameter object that contains all properties this graphics state wants to set. + * Supported are: opacity, stroke-opacity + * @constructor + */ + API.GState = GState; + + /** + * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}. + * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState + * it will be added before use. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name setGState + */ + API.setGState = function(gState) { + if (typeof gState === "string") { + gState = gStates[gStatesMap[gState]]; + } else { + gState = addGState(null, gState); + } - if (this.fURL[this.fURL.length - 1] === '^') { - this.fURL = this.fURL.slice(0, this.fURL.length - 1); - this.fSkipHeadRequest = true; - } + if (!gState.equals(activeGState)) { + out("/" + gState.id + " gs"); + activeGState = gState; + } + }; - if (this.fURL[this.fURL.length - 1] === '-') { - this.fURL = this.fURL.slice(0, this.fURL.length - 1); - this.fUseStampPar = false; + /** + * Adds a new Graphics State. Duplicates are automatically eliminated. + * @param {String} key Might also be null, if no later reference to this gState is needed + * @param {Object} gState The gState object + */ + var addGState = function(key, gState) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (key && gStatesMap[key]) return; + var duplicate = false; + for (var s in gStates) { + if (gStates.hasOwnProperty(s)) { + if (gStates[s].equals(gState)) { + duplicate = true; + break; + } } + } - if (this.fURL.indexOf('file://') === 0) { - this.fUseStampPar = false; - this.fAcceptRanges = false; - } + if (duplicate) { + gState = gStates[s]; + } else { + var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10); + gStates[gStateKey] = gState; + gState.id = gStateKey; + } - const pos = Math.max(this.fURL.lastIndexOf('/'), this.fURL.lastIndexOf('\\')); - this.fFileName = pos >= 0 ? this.fURL.slice(pos + 1) : this.fURL; - } + // several user keys may point to the same GState object + key && (gStatesMap[key] = gState.id); - /** @summary Assign BufferArray with file contentOpen file - * @private */ - assignFileContent(bufArray) { - this.fFileContent = new TBuffer(new DataView(bufArray)); - this.fAcceptRanges = false; - this.fUseStampPar = false; - this.fEND = this.fFileContent.length; - } + events.publish("addGState", gState); - /** @summary Actual file open - * @return {Promise} when file keys are read - * @private */ - async _open() { return this.readKeys(); } + return gState; + }; - /** @summary read buffer(s) from the file - * @return {Promise} with read buffers - * @private */ - async readBuffer(place, filename, progress_callback) { - if ((this.fFileContent !== null) && !filename && (!this.fAcceptRanges || this.fFileContent.canExtract(place))) - return this.fFileContent.extract(place); + /** + * Adds a new {@link GState} for later use. See {@link setGState}. + * @param {String} key + * @param {GState} gState + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name addGState + */ + API.addGState = function(key, gState) { + addGState(key, gState); + return this; + }; - let resolveFunc, rejectFunc; + /** + * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState} + * later. Here, the general pdf graphics state is meant, also including the current transformation matrix, + * fill and stroke colors etc. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name saveGraphicsState + */ + API.saveGraphicsState = function() { + out("q"); + // as we cannot set font key and size independently we must keep track of both + fontStateStack.push({ + key: activeFontKey, + size: activeFontSize, + color: textColor + }); + return this; + }; - const file = this, first_block = (place[0] === 0) && (place.length === 2), - blobs = [], // array of requested segments - promise = new Promise((resolve, reject) => { resolveFunc = resolve; rejectFunc = reject; }); + /** + * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack"). + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name restoreGraphicsState + */ + API.restoreGraphicsState = function() { + out("Q"); - let fileurl = file.fURL, - first = 0, last = 0, - // eslint-disable-next-line prefer-const - read_callback, first_req, - first_block_retry = false; + // restore previous font state + var fontState = fontStateStack.pop(); + activeFontKey = fontState.key; + activeFontSize = fontState.size; + textColor = fontState.color; - if (isStr(filename) && filename) { - const pos = fileurl.lastIndexOf('/'); - fileurl = (pos < 0) ? filename : fileurl.slice(0, pos + 1) + filename; - } + activeGState = null; - function send_new_request(increment) { - if (increment) { - first = last; - last = Math.min(first + file.fMaxRanges * 2, place.length); - if (first >= place.length) return resolveFunc(blobs); - } + return this; + }; - let fullurl = fileurl, ranges = 'bytes', totalsz = 0; - // try to avoid browser caching by adding stamp parameter to URL - if (file.fUseStampPar) - fullurl += ((fullurl.indexOf('?') < 0) ? '?' : '&') + file.fUseStampPar; + /** + * Appends this matrix to the left of all previously applied matrices. + * + * @param {Matrix} matrix + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name setCurrentTransformationMatrix + */ + API.setCurrentTransformationMatrix = function(matrix) { + out(matrix.toString() + " cm"); + return this; + }; - for (let n = first; n < last; n += 2) { - ranges += (n > first ? ',' : '=') + `${place[n]}-${place[n]+place[n+1]-1}`; - totalsz += place[n + 1]; // accumulated total size - } - if (last - first > 2) - totalsz += (last - first) * 60; // for multi-range ~100 bytes/per request + /** + * Inserts a debug comment into the generated pdf. + * @function + * @instance + * @param {String} text + * @returns {jsPDF} + * @memberof jsPDF# + * @name comment + */ + API.comment = function(text) { + out("#" + text); + return this; + }; - // when read first block, allow to read more - maybe ranges are not supported and full file content will be returned - if (file.fAcceptRanges && first_block) - totalsz = Math.max(totalsz, 1e7); + /** + * Point + */ + var Point = function(x, y) { + var _x = x || 0; + Object.defineProperty(this, "x", { + enumerable: true, + get: function() { + return _x; + }, + set: function(value) { + if (!isNaN(value)) { + _x = parseFloat(value); + } + } + }); - return createHttpRequest(fullurl, 'buf', read_callback, undefined, true).then(xhr => { - if (file.fAcceptRanges) { - xhr.setRequestHeader('Range', ranges); - xhr.expected_size = Math.max(Math.round(1.1 * totalsz), totalsz + 200); // 200 if offset for the potential gzip - } + var _y = y || 0; + Object.defineProperty(this, "y", { + enumerable: true, + get: function() { + return _y; + }, + set: function(value) { + if (!isNaN(value)) { + _y = parseFloat(value); + } + } + }); - if (isFunc(progress_callback) && isFunc(xhr.addEventListener)) { - let sum1 = 0, sum2 = 0, sum_total = 0; - for (let n = 1; n < place.length; n += 2) { - sum_total += place[n]; - if (n < first) sum1 += place[n]; - if (n < last) sum2 += place[n]; - } - if (!sum_total) sum_total = 1; + var _type = "pt"; + Object.defineProperty(this, "type", { + enumerable: true, + get: function() { + return _type; + }, + set: function(value) { + _type = value.toString(); + } + }); + return this; + }; - const progress_offest = sum1 / sum_total, progress_this = (sum2 - sum1) / sum_total; - xhr.addEventListener('progress', oEvent => { - if (oEvent.lengthComputable) { - if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') - xhr.abort(); - } - }); - } else if (first_block_retry && isFunc(xhr.addEventListener)) { - xhr.addEventListener('progress', oEvent => { - if (!oEvent.total) - console.warn('Fail to get file size information'); - else if (oEvent.total > 5e7) { - console.error(`Try to load very large file ${oEvent.total} at once - abort`); - xhr.abort(); - } - }); - } + /** + * Rectangle + */ + var Rectangle = function(x, y, w, h) { + Point.call(this, x, y); + this.type = "rect"; + + var _w = w || 0; + Object.defineProperty(this, "w", { + enumerable: true, + get: function() { + return _w; + }, + set: function(value) { + if (!isNaN(value)) { + _w = parseFloat(value); + } + } + }); - first_req = first_block ? xhr : null; - xhr.send(null); - }); + var _h = h || 0; + Object.defineProperty(this, "h", { + enumerable: true, + get: function() { + return _h; + }, + set: function(value) { + if (!isNaN(value)) { + _h = parseFloat(value); + } } + }); - read_callback = function(res) { - if (!res && first_block) { - // if fail to read file with stamp parameter, try once again without it - if (file.fUseStampPar) { - file.fUseStampPar = false; - return send_new_request(); - } - if (file.fAcceptRanges) { - file.fAcceptRanges = false; - first_block_retry = true; - return send_new_request(); - } - } + return this; + }; - if (res && first_req) { - // special workaround for servers like cernbox blocking access to some response headers - // as result, it is not possible to parse multipart responses - if (file.fAcceptRanges && (first_req.status === 206) && (res?.byteLength === place[1]) && !first_req.getResponseHeader('Content-Range') && (file.fMaxRanges > 1)) { - console.warn('Server response with 206 code but browser does not provide access to Content-Range header - setting fMaxRanges = 1, consider to load full file with "filename.root+" argument or adjust server configurations'); - file.fMaxRanges = 1; - } + /** + * FormObject/RenderTarget + */ - // workaround for simpleHTTP - const kind = browser.isFirefox ? first_req.getResponseHeader('Server') : ''; - if (isStr(kind) && kind.indexOf('SimpleHTTP') === 0) { - file.fMaxRanges = 1; - file.fUseStampPar = false; - } - } + var RenderTarget = function() { + this.page = page; + this.currentPage = currentPage; + this.pages = pages.slice(0); + this.pagesContext = pagesContext.slice(0); + this.x = pageX; + this.y = pageY; + this.matrix = pageMatrix; + this.width = getUnscaledPageWidth(currentPage); + this.height = getUnscaledPageHeight(currentPage); + this.outputDestination = outputDestination; + + this.id = ""; // set by endFormObject() + this.objectNumber = -1; // will be set by putXObject() + }; - if (res && first_block && !file.fFileContent) { - // special case - keep content of first request (could be complete file) in memory - file.fFileContent = new TBuffer(isStr(res) ? res : new DataView(res)); + RenderTarget.prototype.restore = function() { + page = this.page; + currentPage = this.currentPage; + pagesContext = this.pagesContext; + pages = this.pages; + pageX = this.x; + pageY = this.y; + pageMatrix = this.matrix; + setPageWidthWithoutScaling(currentPage, this.width); + setPageHeightWithoutScaling(currentPage, this.height); + outputDestination = this.outputDestination; + }; - if (!file.fAcceptRanges) - file.fEND = file.fFileContent.length; + var beginNewRenderTarget = function(x, y, width, height, matrix) { + // save current state + renderTargetStack.push(new RenderTarget()); - return resolveFunc(file.fFileContent.extract(place)); - } + // clear pages + page = currentPage = 0; + pages = []; + pageX = x; + pageY = y; - if (!res) { - if ((first === 0) && (last > 2) && (file.fMaxRanges > 1)) { - // server return no response with multi request - try to decrease ranges count or fail + pageMatrix = matrix; - if (last / 2 > 200) - file.fMaxRanges = 200; - else if (last / 2 > 50) - file.fMaxRanges = 50; - else if (last / 2 > 20) - file.fMaxRanges = 20; - else if (last / 2 > 5) - file.fMaxRanges = 5; - else - file.fMaxRanges = 1; - last = Math.min(last, file.fMaxRanges * 2); - // console.log(`Change maxranges to ${file.fMaxRanges} last ${last}`); - return send_new_request(); - } + beginPage([width, height]); + }; - return rejectFunc(Error('Fail to read with several ranges')); - } + var endFormObject = function(key) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (renderTargetMap[key]) { + renderTargetStack.pop().restore(); + return; + } - // if only single segment requested, return result as is - if (last - first === 2) { - const b = new DataView(res); - if (place.length === 2) return resolveFunc(b); - blobs.push(b); - return send_new_request(true); - } + // save the created xObject + var newXObject = new RenderTarget(); - // object to access response data - const hdr = this.getResponseHeader('Content-Type'), - ismulti = isStr(hdr) && (hdr.indexOf('multipart') >= 0), - view = new DataView(res); - - if (!ismulti) { - // server may returns simple buffer, which combines all segments together - - const hdr_range = this.getResponseHeader('Content-Range'); - let segm_start = 0, segm_last = -1; - - if (isStr(hdr_range) && hdr_range.indexOf('bytes') >= 0) { - const parts = hdr_range.slice(hdr_range.indexOf('bytes') + 6).split(/[\s-/]+/); - if (parts.length === 3) { - segm_start = Number.parseInt(parts[0]); - segm_last = Number.parseInt(parts[1]); - if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) { - segm_start = 0; segm_last = -1; - } - } - } + var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10); + newXObject.id = xObjectId; - let canbe_single_segment = (segm_start <= segm_last); - for (let n = first; n < last; n += 2) { - if ((place[n] < segm_start) || (place[n] + place[n + 1] - 1 > segm_last)) - canbe_single_segment = false; - } + renderTargetMap[key] = xObjectId; + renderTargets[xObjectId] = newXObject; - if (canbe_single_segment) { - for (let n = first; n < last; n += 2) - blobs.push(new DataView(res, place[n] - segm_start, place[n + 1])); - return send_new_request(true); - } + events.publish("addFormObject", newXObject); - if ((file.fMaxRanges === 1) || (first !== 0)) - return rejectFunc(Error('Server returns normal response when multipart was requested, disable multirange support')); + // restore state from stack + renderTargetStack.pop().restore(); + }; - file.fMaxRanges = 1; - last = Math.min(last, file.fMaxRanges * 2); + /** + * Starts a new pdf form object, which means that all consequent draw calls target a new independent object + * until {@link endFormObject} is called. The created object can be referenced and drawn later using + * {@link doFormObject}. Nested form objects are possible. + * x, y, width, height set the bounding box that is used to clip the content. + * + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to + * the parent's. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name beginFormObject + */ + API.beginFormObject = function(x, y, width, height, matrix) { + // The user can set the output target to a new form object. Nested form objects are possible. + // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as + // the PDF-Spec states: + // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which + // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions, + // form XObjects may be independent of the content streams in which they appear, and this is strongly + // recommended although not required" + beginNewRenderTarget(x, y, width, height, matrix); + return this; + }; - return send_new_request(); - } + /** + * Completes and saves the form object. + * @param {String} key The key by which this form object can be referenced. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name endFormObject + */ + API.endFormObject = function(key) { + endFormObject(key); + return this; + }; - // multipart messages requires special handling + /** + * Draws the specified form object by referencing to the respective pdf XObject created with + * {@link API.beginFormObject} and {@link endFormObject}. + * The location is determined by matrix. + * + * @param {String} key The key to the form object. + * @param {Matrix} matrix The matrix applied before drawing the form object. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name doFormObject + */ + API.doFormObject = function(key, matrix) { + var xObject = renderTargets[renderTargetMap[key]]; + out("q"); + out(matrix.toString() + " cm"); + out("/" + xObject.id + " Do"); + out("Q"); + return this; + }; - const indx = hdr.indexOf('boundary='); - let boundary = '', n = first, o = 0; - if (indx > 0) { - boundary = hdr.slice(indx + 9); - if ((boundary[0] === '"') && (boundary[boundary.length - 1] === '"')) - boundary = boundary.slice(1, boundary.length - 1); - boundary = '--' + boundary; - } else - console.error('Did not found boundary id in the response header'); + /** + * Returns the form object specified by key. + * @param key {String} + * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}} + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name getFormObject + */ + API.getFormObject = function(key) { + var xObject = renderTargets[renderTargetMap[key]]; + return { + x: xObject.x, + y: xObject.y, + width: xObject.width, + height: xObject.height, + matrix: xObject.matrix + }; + }; - while (n < last) { - let code1, code2 = view.getUint8(o), nline = 0, line = '', - finish_header = false, segm_start = 0, segm_last = -1; + /** + * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf'). + * Uses FileSaver.js-method saveAs. + * + * @memberof jsPDF# + * @name save + * @function + * @instance + * @param {string} filename The filename including extension. + * @param {Object} options An Object with additional options, possible options: 'returnPromise'. + * @returns {jsPDF|Promise} jsPDF-instance */ + API.save = function(filename, options) { + + options = options || {}; + options.returnPromise = options.returnPromise || false; + + return Promise.reject(Error('save function removed')); + }; - while ((o < view.byteLength - 1) && !finish_header && (nline < 5)) { - code1 = code2; - code2 = view.getUint8(o + 1); + // applying plugins (more methods) ON TOP of built-in API. + // this is intentional as we allow plugins to override + // built-ins + for (var plugin in jsPDF.API) { + if (jsPDF.API.hasOwnProperty(plugin)) { + if (plugin === "events" && jsPDF.API.events.length) { + (function(events, newEvents) { + // jsPDF.API.events is a JS Array of Arrays + // where each Array is a pair of event name, handler + // Events were added by plugins to the jsPDF instantiator. + // These are always added to the new instance and some ran + // during instantiation. + var eventname, handler_and_args, i; + + for (i = newEvents.length - 1; i !== -1; i--) { + // subscribe takes 3 args: 'topic', function, runonce_flag + // if undefined, runonce is false. + // users can attach callback directly, + // or they can attach an array with [callback, runonce_flag] + // that's what the "apply" magic is for below. + eventname = newEvents[i][0]; + handler_and_args = newEvents[i][1]; + events.subscribe.apply( + events, + [eventname].concat( + typeof handler_and_args === "function" + ? [handler_and_args] + : handler_and_args + ) + ); + } + })(events, jsPDF.API.events); + } else { + API[plugin] = jsPDF.API[plugin]; + } + } + } - if (((code1 === 13) && (code2 === 10)) || (code1 === 10)) { - if ((line.length > 2) && (line.slice(0, 2) === '--') && (line !== boundary)) - return rejectFunc(Error(`Decode multipart message, expect boundary ${boundary} got ${line}`)); + function getUnscaledPageWidth(pageNumber) { + return ( + pagesContext[pageNumber].mediaBox.topRightX - + pagesContext[pageNumber].mediaBox.bottomLeftX + ); + } - line = line.toLowerCase(); + function setPageWidthWithoutScaling(pageNumber, value) { + pagesContext[pageNumber].mediaBox.topRightX = + value + pagesContext[pageNumber].mediaBox.bottomLeftX; + } - if ((line.indexOf('content-range') >= 0) && (line.indexOf('bytes') > 0)) { - const parts = line.slice(line.indexOf('bytes') + 6).split(/[\s-/]+/); - if (parts.length === 3) { - segm_start = Number.parseInt(parts[0]); - segm_last = Number.parseInt(parts[1]); - if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) { - segm_start = 0; segm_last = -1; - } - } else - console.error(`Fail to decode content-range ${line} ${parts}`); - } + function getUnscaledPageHeight(pageNumber) { + return ( + pagesContext[pageNumber].mediaBox.topRightY - + pagesContext[pageNumber].mediaBox.bottomLeftY + ); + } - if ((nline > 1) && (line.length === 0)) finish_header = true; + function setPageHeightWithoutScaling(pageNumber, value) { + pagesContext[pageNumber].mediaBox.topRightY = + value + pagesContext[pageNumber].mediaBox.bottomLeftY; + } - nline++; line = ''; - if (code1 !== 10) { - o++; code2 = view.getUint8(o + 1); - } - } else - line += String.fromCharCode(code1); - o++; - } + var getPageWidth = (API.getPageWidth = function(pageNumber) { + pageNumber = pageNumber || currentPage; + return getUnscaledPageWidth(pageNumber) / scaleFactor; + }); - if (!finish_header) - return rejectFunc(Error('Cannot decode header in multipart message')); + var setPageWidth = (API.setPageWidth = function(pageNumber, value) { + setPageWidthWithoutScaling(pageNumber, value * scaleFactor); + }); - if (segm_start > segm_last) { - // fall-back solution, believe that segments same as requested - blobs.push(new DataView(res, o, place[n + 1])); - o += place[n + 1]; - n += 2; - } else { - while ((n < last) && (place[n] >= segm_start) && (place[n] + place[n + 1] - 1 <= segm_last)) { - blobs.push(new DataView(res, o + place[n] - segm_start, place[n + 1])); - n += 2; - } + var getPageHeight = (API.getPageHeight = function(pageNumber) { + pageNumber = pageNumber || currentPage; + return getUnscaledPageHeight(pageNumber) / scaleFactor; + }); - o += (segm_last - segm_start + 1); - } - } + var setPageHeight = (API.setPageHeight = function(pageNumber, value) { + setPageHeightWithoutScaling(pageNumber, value * scaleFactor); + }); + + /** + * Object exposing internal API to plugins + * @public + * @ignore + */ + API.internal = { + pdfEscape: pdfEscape, + getStyle: getStyle, + getFont: getFontEntry, + getFontSize: getFontSize, + getCharSpace: getCharSpace, + getTextColor: getTextColor, + getLineHeight: getLineHeight, + getLineHeightFactor: getLineHeightFactor, + getLineWidth: getLineWidth, + write: write, + getHorizontalCoordinate: getHorizontalCoordinate, + getVerticalCoordinate: getVerticalCoordinate, + getCoordinateString: getHorizontalCoordinateString, + getVerticalCoordinateString: getVerticalCoordinateString, + collections: {}, + newObject: newObject, + newAdditionalObject: newAdditionalObject, + newObjectDeferred: newObjectDeferred, + newObjectDeferredBegin: newObjectDeferredBegin, + getFilters: getFilters, + putStream: putStream, + events: events, + scaleFactor: scaleFactor, + pageSize: { + getWidth: function() { + return getPageWidth(currentPage); + }, + setWidth: function(value) { + setPageWidth(currentPage, value); + }, + getHeight: function() { + return getPageHeight(currentPage); + }, + setHeight: function(value) { + setPageHeight(currentPage, value); + } + }, + encryptionOptions: encryptionOptions, + encryption: encryption, + getEncryptor: getEncryptor, + output: output, + getNumberOfPages: getNumberOfPages, + pages: pages, + out: out, + f2: f2, + f3: f3, + getPageInfo: getPageInfo, + getPageInfoByObjId: getPageInfoByObjId, + getCurrentPageInfo: getCurrentPageInfo, + getPDFVersion: getPdfVersion, + Point: Point, + Rectangle: Rectangle, + Matrix: Matrix, + hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them. + }; - send_new_request(true); - }; + Object.defineProperty(API.internal.pageSize, "width", { + get: function() { + return getPageWidth(currentPage); + }, + set: function(value) { + setPageWidth(currentPage, value); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(API.internal.pageSize, "height", { + get: function() { + return getPageHeight(currentPage); + }, + set: function(value) { + setPageHeight(currentPage, value); + }, + enumerable: true, + configurable: true + }); - return send_new_request(true).then(() => promise); - } + ////////////////////////////////////////////////////// + // continuing initialization of jsPDF Document object + ////////////////////////////////////////////////////// + // Add the first page automatically + addFonts.call(API, standardFonts); + activeFontKey = "F1"; + _addPage(format, orientation); - /** @summary Returns file name */ - getFileName() { return this.fFileName; } + events.publish("initialized"); + return API; +} - /** @summary Get directory with given name and cycle - * @desc Function only can be used for already read directories, which are preserved in the memory - * @private */ - getDir(dirname, cycle) { - if ((cycle === undefined) && isStr(dirname)) { - const pos = dirname.lastIndexOf(';'); - if (pos > 0) { - cycle = Number.parseInt(dirname.slice(pos + 1)); - dirname = dirname.slice(0, pos); - } - } +/** + * jsPDF.API is a STATIC property of jsPDF class. + * jsPDF.API is an object you can add methods and properties to. + * The methods / properties you add will show up in new jsPDF objects. + * + * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, + * callbacks to this object. These will be reassigned to all new instances of jsPDF. + * + * @static + * @public + * @memberof jsPDF# + * @name API + * + * @example + * jsPDF.API.mymethod = function(){ + * // 'this' will be ref to internal API object. see jsPDF source + * // , so you can refer to built-in methods like so: + * // this.line(....) + * // this.text(....) + * } + * var pdfdoc = new jsPDF() + * pdfdoc.mymethod() // <- !!!!!! + */ +jsPDF.API = { + events: [] +}; +/** + * The version of jsPDF. + * @name version + * @type {string} + * @memberof jsPDF# + */ +jsPDF.version = "3.0.3"; - for (let j = 0; j < this.fDirectories.length; ++j) { - const dir = this.fDirectories[j]; - if (dir.dir_name !== dirname) continue; - if ((cycle !== undefined) && (dir.dir_cycle !== cycle)) continue; - return dir; - } - return null; - } +/* global jsPDF */ - /** @summary Retrieve a key by its name and cycle in the list of keys - * @desc If only_direct not specified, returns Promise while key keys must be read first from the directory - * @private */ - getKey(keyname, cycle, only_direct) { - if (typeof cycle !== 'number') cycle = -1; - let bestkey = null; - for (let i = 0; i < this.fKeys.length; ++i) { - const key = this.fKeys[i]; - if (!key || (key.fName !== keyname)) continue; - if (key.fCycle === cycle) { bestkey = key; break; } - if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) bestkey = key; - } - if (bestkey) - return only_direct ? bestkey : Promise.resolve(bestkey); +var jsPDFAPI = jsPDF.API; +var scaleFactor = 1; - let pos = keyname.lastIndexOf('/'); - // try to handle situation when object name contains slashes (bad practice anyway) - while (pos > 0) { - const dirname = keyname.slice(0, pos), - subname = keyname.slice(pos + 1), - dir = this.getDir(dirname); +var pdfEscape = function(value) { + return value + .replace(/\\/g, "\\\\") + .replace(/\(/g, "\\(") + .replace(/\)/g, "\\)"); +}; +var pdfUnescape = function(value) { + return value + .replace(/\\\\/g, "\\") + .replace(/\\\(/g, "(") + .replace(/\\\)/g, ")"); +}; - if (dir) return dir.getKey(subname, cycle, only_direct); +var f2 = function(number) { + return number.toFixed(2); // Ie, %.2f +}; - const dirkey = this.getKey(dirname, undefined, true); - if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) - return this.readObject(dirname).then(newdir => newdir.getKey(subname, cycle)); +var f5 = function(number) { + return number.toFixed(5); // Ie, %.2f +}; - pos = keyname.lastIndexOf('/', pos - 1); - } +jsPDFAPI.__acroform__ = {}; +var inherit = function(child, parent) { + child.prototype = Object.create(parent.prototype); + child.prototype.constructor = child; +}; - return only_direct ? null : Promise.reject(Error(`Key not found ${keyname}`)); - } +var scale = function(x) { + return x * scaleFactor; +}; - /** @summary Read and inflate object buffer described by its key - * @private */ - async readObjBuffer(key) { - return this.readBuffer([key.fSeekKey + key.fKeylen, key.fNbytes - key.fKeylen]).then(blob1 => { - if (key.fObjlen <= key.fNbytes - key.fKeylen) { - const buf = new TBuffer(blob1, 0, this); - buf.fTagOffset = key.fKeylen; - return buf; - } +var createFormXObject = function(formObject) { + var xobj = new AcroFormXObject(); + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + xobj.BBox = [0, 0, Number(f2(width)), Number(f2(height))]; + return xobj; +}; - return R__unzip(blob1, key.fObjlen).then(objbuf => { - if (!objbuf) - return Promise.reject(Error(`Fail to UNZIP buffer for ${key.fName}`)); +/** + * Bit-Operations + */ +var setBit = (jsPDFAPI.__acroform__.setBit = function(number, bitPosition) { + number = number || 0; + bitPosition = bitPosition || 0; - const buf = new TBuffer(objbuf, 0, this); - buf.fTagOffset = key.fKeylen; - return buf; - }); - }); - } + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.setBit" + ); + } + var bitMask = 1 << bitPosition; - /** @summary Read any object from a root file - * @desc One could specify cycle number in the object name or as separate argument - * @param {string} obj_name - name of object, may include cycle number like 'hpxpy;1' - * @param {number} [cycle] - cycle number, also can be included in obj_name - * @return {Promise} promise with object read - * @example - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * let f = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await f.readObject('hpxpy;1'); - * console.log(`Read object of type ${obj._typename}`); */ - async readObject(obj_name, cycle, only_dir) { - const pos = obj_name.lastIndexOf(';'); - if (pos >= 0) { - cycle = Number.parseInt(obj_name.slice(pos + 1)); - obj_name = obj_name.slice(0, pos); - } + number |= bitMask; - if (typeof cycle !== 'number') cycle = -1; - // remove leading slashes - while (obj_name.length && (obj_name[0] === '/')) obj_name = obj_name.slice(1); + return number; +}); - // one uses Promises while in some cases we need to - // read sub-directory to get list of keys - // in such situation calls are asynchrone - return this.getKey(obj_name, cycle).then(key => { - if ((obj_name === nameStreamerInfo) && (key.fClassName === clTList)) - return this.fStreamerInfos; +var clearBit = (jsPDFAPI.__acroform__.clearBit = function(number, bitPosition) { + number = number || 0; + bitPosition = bitPosition || 0; - let isdir = false; + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.clearBit" + ); + } + var bitMask = 1 << bitPosition; - if ((key.fClassName === clTDirectory || key.fClassName === clTDirectoryFile)) { - const dir = this.getDir(obj_name, cycle); - if (dir) return dir; - isdir = true; - } + number &= ~bitMask; - if (!isdir && only_dir) - return Promise.reject(Error(`Key ${obj_name} is not directory}`)); + return number; +}); - return this.readObjBuffer(key).then(buf => { - if (isdir) { - const dir = new TDirectory(this, obj_name, cycle); - dir.fTitle = key.fTitle; - return dir.readKeys(buf); - } +var getBit = (jsPDFAPI.__acroform__.getBit = function(number, bitPosition) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.getBit" + ); + } + return (number & (1 << bitPosition)) === 0 ? 0 : 1; +}); - const obj = {}; - buf.mapObject(1, obj); // tag object itself with id == 1 - buf.classStreamer(obj, key.fClassName); +/* + * Ff starts counting the bit position at 1 and not like javascript at 0 + */ +var getBitForPdf = (jsPDFAPI.__acroform__.getBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.getBitForPdf" + ); + } + return getBit(number, bitPosition - 1); +}); - if ((key.fClassName === clTF1) || (key.fClassName === clTF2)) - return this._readFormulas(obj); +var setBitForPdf = (jsPDFAPI.__acroform__.setBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.setBitForPdf" + ); + } + return setBit(number, bitPosition - 1); +}); - return obj; - }); - }); - } +var clearBitForPdf = (jsPDFAPI.__acroform__.clearBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.clearBitForPdf" + ); + } + return clearBit(number, bitPosition - 1); +}); - /** @summary read formulas from the file and add them to TF1/TF2 objects - * @private */ - async _readFormulas(tf1) { - const arr = []; - for (let indx = 0; indx < this.fKeys.length; ++indx) { - if (this.fKeys[indx].fClassName === 'TFormula') - arr.push(this.readObject(this.fKeys[indx].fName, this.fKeys[indx].fCycle)); - } +var calculateCoordinates = (jsPDFAPI.__acroform__.calculateCoordinates = function( + args, + scope +) { + var getHorizontalCoordinate = scope.internal.getHorizontalCoordinate; + var getVerticalCoordinate = scope.internal.getVerticalCoordinate; + var x = args[0]; + var y = args[1]; + var w = args[2]; + var h = args[3]; - return Promise.all(arr).then(formulas => { - formulas.forEach(obj => tf1.addFormula(obj)); - return tf1; - }); - } + var coordinates = {}; - /** @summary extract streamer infos from the buffer - * @private */ - extractStreamerInfos(buf) { - if (!buf) return; + coordinates.lowerLeft_X = getHorizontalCoordinate(x) || 0; + coordinates.lowerLeft_Y = getVerticalCoordinate(y + h) || 0; + coordinates.upperRight_X = getHorizontalCoordinate(x + w) || 0; + coordinates.upperRight_Y = getVerticalCoordinate(y) || 0; - const lst = {}; - buf.mapObject(1, lst); + return [ + Number(f2(coordinates.lowerLeft_X)), + Number(f2(coordinates.lowerLeft_Y)), + Number(f2(coordinates.upperRight_X)), + Number(f2(coordinates.upperRight_Y)) + ]; +}); - try { - buf.classStreamer(lst, clTList); - } catch (err) { - console.error('Fail extract streamer infos', err); - return; - } +var calculateAppearanceStream = function(formObject) { + if (formObject.appearanceStreamContent) { + return formObject.appearanceStreamContent; + } - lst._typename = clTStreamerInfoList; + if (!formObject.V && !formObject.DV) { + return; + } - this.fStreamerInfos = lst; + // else calculate it + + var stream = []; + var text = formObject._V || formObject.DV; + var calcRes = calculateX(formObject, text); + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + + //PDF 32000-1:2008, page 444 + stream.push("/Tx BMC"); + stream.push("q"); + stream.push("BT"); // Begin Text + stream.push(formObject.scope.__private__.encodeColorString(formObject.color)); + stream.push("/" + fontKey + " " + f2(calcRes.fontSize) + " Tf"); + stream.push("1 0 0 1 0 0 Tm"); // Transformation Matrix + stream.push(calcRes.text); + stream.push("ET"); // End Text + stream.push("Q"); + stream.push("EMC"); + + var appearanceStreamContent = createFormXObject(formObject); + appearanceStreamContent.scope = formObject.scope; + appearanceStreamContent.stream = stream.join("\n"); + return appearanceStreamContent; +}; - if (isFunc(internals.addStreamerInfosForPainter)) - internals.addStreamerInfosForPainter(lst); +var calculateX = function(formObject, text) { + var maxFontSize = + formObject.fontSize === 0 ? formObject.maxFontSize : formObject.fontSize; + var returnValue = { + text: "", + fontSize: "" + }; + // Remove Brackets + text = text.substr(0, 1) == "(" ? text.substr(1) : text; + text = + text.substr(text.length - 1) == ")" + ? text.substr(0, text.length - 1) + : text; + // split into array of words + var textSplit = text.split(" "); + if (formObject.multiline) { + textSplit = textSplit.map(word => word.split("\n")); + } else { + textSplit = textSplit.map(word => [word]); + } - for (let k = 0; k < lst.arr.length; ++k) { - const si = lst.arr[k]; - if (!si.fElements) continue; - for (let l = 0; l < si.fElements.arr.length; ++l) { - const elem = si.fElements.arr[l]; - if (!elem.fTypeName || !elem.fType) continue; + var fontSize = maxFontSize; // The Starting fontSize (The Maximum) + var lineSpacing = 2; + var borderPadding = 2; + + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + height = height < 0 ? -height : height; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + width = width < 0 ? -width : width; + + var isSmallerThanWidth = function(i, lastLine, fontSize) { + if (i + 1 < textSplit.length) { + var tmp = lastLine + " " + textSplit[i + 1][0]; + var TextWidth = calculateFontSpace(tmp, formObject, fontSize).width; + var FieldWidth = width - 2 * borderPadding; + return TextWidth <= FieldWidth; + } else { + return false; + } + }; - let typ = elem.fType, typname = elem.fTypeName; + fontSize++; + FontSize: while (fontSize > 0) { + text = ""; + fontSize--; + var textHeight = calculateFontSpace("3", formObject, fontSize).height; + var startY = formObject.multiline + ? height - fontSize + : (height - textHeight) / 2; + startY += lineSpacing; + var startX; + + var lastY = startY; + var firstWordInLine = 0, + lastWordInLine = 0; + var lastLength; + var currWord = 0; + + if (fontSize <= 0) { + // In case, the Text doesn't fit at all + fontSize = 12; + text = "(...) Tj\n"; + text += + "% Width of Text: " + + calculateFontSpace(text, formObject, fontSize).width + + ", FieldWidth:" + + width + + "\n"; + break; + } - if (typ >= 60) { - if ((typ === kStreamer) && (elem._typename === clTStreamerSTL) && elem.fSTLtype && elem.fCtype && (elem.fCtype < 20)) { - const prefix = (StlNames[elem.fSTLtype] || 'undef') + '<'; - if ((typname.indexOf(prefix) === 0) && (typname[typname.length - 1] === '>')) { - typ = elem.fCtype; - typname = typname.slice(prefix.length, typname.length - 1).trim(); + var lastLine = ""; + var lineCount = 0; + Line: for (var i = 0; i < textSplit.length; i++) { + if (textSplit.hasOwnProperty(i)) { + let isWithNewLine = false; + if (textSplit[i].length !== 1 && currWord !== textSplit[i].length - 1) { + if ( + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + continue FontSize; + } - if ((elem.fSTLtype === kSTLmap) || (elem.fSTLtype === kSTLmultimap)) { - if (typname.indexOf(',') > 0) - typname = typname.slice(0, typname.indexOf(',')).trim(); - else - continue; - } - } - } - if (typ >= 60) continue; + lastLine += textSplit[i][currWord]; + isWithNewLine = true; + lastWordInLine = i; + i--; + } else { + lastLine += textSplit[i][currWord] + " "; + lastLine = + lastLine.substr(lastLine.length - 1) == " " + ? lastLine.substr(0, lastLine.length - 1) + : lastLine; + var key = parseInt(i); + var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize); + var isLastWord = i >= textSplit.length - 1; + + if (nextLineIsSmaller && !isLastWord) { + lastLine += " "; + currWord = 0; + continue; // Line + } else if (!nextLineIsSmaller && !isLastWord) { + if (!formObject.multiline) { + continue FontSize; } else { - if ((typ > 20) && (typname[typname.length - 1] === '*')) typname = typname.slice(0, typname.length - 1); - typ = typ % 20; + if ( + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + // If the Text is higher than the + // FieldObject + continue FontSize; + } + lastWordInLine = key; + // go on } + } else if (isLastWord) { + lastWordInLine = key; + } else { + if ( + formObject.multiline && + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + // If the Text is higher than the FieldObject + continue FontSize; + } + } + } + // Remove last blank - const kind = getTypeId(typname); - if (kind === typ) continue; - - if ((typ === kBits) && (kind === kUInt)) continue; - if ((typ === kCounter) && (kind === kInt)) continue; + var line = ""; - if (typname && typ && (this.fBasicTypes[typname] !== typ)) - this.fBasicTypes[typname] = typ; - } - } - } + for (var x = firstWordInLine; x <= lastWordInLine; x++) { + var currLine = textSplit[x]; + if (formObject.multiline) { + if (x === lastWordInLine) { + line += currLine[currWord] + " "; + currWord = (currWord + 1) % currLine.length; + continue; + } + if (x === firstWordInLine) { + line += currLine[currLine.length - 1] + " "; + continue; + } + } + line += currLine[0] + " "; + } - /** @summary Read file keys - * @private */ - async readKeys() { - // with the first readbuffer we read bigger amount to create header cache - return this.readBuffer([0, 1024]).then(blob => { - const buf = new TBuffer(blob, 0, this); - if (buf.substring(0, 4) !== 'root') - return Promise.reject(Error(`Not a ROOT file ${this.fURL}`)); + // Remove last blank + line = + line.substr(line.length - 1) == " " + ? line.substr(0, line.length - 1) + : line; + // lastLength -= blankSpace.width; + lastLength = calculateFontSpace(line, formObject, fontSize).width; + + // Calculate startX + switch (formObject.textAlign) { + case "right": + startX = width - lastLength - borderPadding; + break; + case "center": + startX = (width - lastLength) / 2; + break; + case "left": + default: + startX = borderPadding; + break; + } + text += f2(startX) + " " + f2(lastY) + " Td\n"; + text += "(" + pdfEscape(line) + ") Tj\n"; + // reset X in PDF + text += -f2(startX) + " 0 Td\n"; - buf.shift(4); + // After a Line, adjust y position + lastY = -(fontSize + lineSpacing); - this.fVersion = buf.ntou4(); - this.fBEGIN = buf.ntou4(); - if (this.fVersion < 1000000) { // small file - this.fEND = buf.ntou4(); - this.fSeekFree = buf.ntou4(); - this.fNbytesFree = buf.ntou4(); - buf.shift(4); // const nfree = buf.ntoi4(); - this.fNbytesName = buf.ntou4(); - this.fUnits = buf.ntou1(); - this.fCompress = buf.ntou4(); - this.fSeekInfo = buf.ntou4(); - this.fNbytesInfo = buf.ntou4(); - } else { // new format to support large files - this.fEND = buf.ntou8(); - this.fSeekFree = buf.ntou8(); - this.fNbytesFree = buf.ntou4(); - buf.shift(4); // const nfree = buf.ntou4(); - this.fNbytesName = buf.ntou4(); - this.fUnits = buf.ntou1(); - this.fCompress = buf.ntou4(); - this.fSeekInfo = buf.ntou8(); - this.fNbytesInfo = buf.ntou4(); - } + // Reset for next iteration step + lastLength = 0; + firstWordInLine = isWithNewLine ? lastWordInLine : lastWordInLine + 1; + lineCount++; - // empty file - if (!this.fSeekInfo || !this.fNbytesInfo) - return Promise.reject(Error(`File ${this.fURL} does not provide streamer infos`)); + lastLine = ""; + continue Line; + } + } + break; + } - // extra check to prevent reading of corrupted data - if (!this.fNbytesName || this.fNbytesName > 100000) - return Promise.reject(Error(`Cannot read directory info of the file ${this.fURL}`)); + returnValue.text = text; + returnValue.fontSize = fontSize; - // *-*-------------Read directory info - let nbytes = this.fNbytesName + 22; - nbytes += 4; // fDatimeC.Sizeof(); - nbytes += 4; // fDatimeM.Sizeof(); - nbytes += 18; // fUUID.Sizeof(); - // assume that the file may be above 2 Gbytes if file version is > 4 - if (this.fVersion >= 40000) nbytes += 12; + return returnValue; +}; - // this part typically read from the header, no need to optimize - return this.readBuffer([this.fBEGIN, Math.max(300, nbytes)]); - }).then(blob3 => { - const buf3 = new TBuffer(blob3, 0, this); +/** + * Small workaround for calculating the TextMetric approximately. + * + * @param text + * @param fontsize + * @returns {TextMetrics} (Has Height and Width) + */ +var calculateFontSpace = function(text, formObject, fontSize) { + var font = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ); + var width = + formObject.scope.getStringUnitWidth(text, { + font: font, + fontSize: parseFloat(fontSize), + charSpace: 0 + }) * parseFloat(fontSize); + var height = + formObject.scope.getStringUnitWidth("3", { + font: font, + fontSize: parseFloat(fontSize), + charSpace: 0 + }) * + parseFloat(fontSize) * + 1.5; + return { height: height, width: width }; +}; - // keep only title from TKey data - this.fTitle = buf3.readTKey().fTitle; +var acroformPluginTemplate = { + fields: [], + xForms: [], + /** + * acroFormDictionaryRoot contains information about the AcroForm + * Dictionary 0: The Event-Token, the AcroFormDictionaryCallback has + * 1: The Object ID of the Root + */ + acroFormDictionaryRoot: null, + /** + * After the PDF gets evaluated, the reference to the root has to be + * reset, this indicates, whether the root has already been printed + * out + */ + printedOut: false, + internal: null, + isInitialized: false +}; - buf3.locate(this.fNbytesName); +var annotReferenceCallback = function(scope) { + //set objId to undefined and force it to get a new objId on buildDocument + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId = undefined; + var fields = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields; + for (var i in fields) { + if (fields.hasOwnProperty(i)) { + var formObject = fields[i]; + //set objId to undefined and force it to get a new objId on buildDocument + formObject.objId = undefined; + // add Annot Reference! + if (formObject.hasAnnotation) { + // If theres an Annotation Widget in the Form Object, put the + // Reference in the /Annot array + createAnnotationReference(formObject, scope); + } + } + } +}; - // we read TDirectory part of TFile - buf3.classStreamer(this, clTDirectory); +var putForm = function(formObject) { + if (formObject.scope.internal.acroformPlugin.printedOut) { + formObject.scope.internal.acroformPlugin.printedOut = false; + formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot = null; + } + formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push( + formObject + ); +}; +/** + * Create the Reference to the widgetAnnotation, so that it gets referenced + * in the Annot[] int the+ (Requires the Annotation Plugin) + */ +var createAnnotationReference = function(object, scope) { + var options = { + type: "reference", + object: object + }; + var findEntry = function(entry) { + return entry.type === options.type && entry.object === options.object; + }; + if ( + scope.internal + .getPageInfo(object.page) + .pageContext.annotations.find(findEntry) === undefined + ) { + scope.internal + .getPageInfo(object.page) + .pageContext.annotations.push(options); + } +}; - if (!this.fSeekKeys) - return Promise.reject(Error(`Empty keys list in ${this.fURL}`)); +// Callbacks + +var putCatalogCallback = function(scope) { + // Put reference to AcroForm to DocumentCatalog + if ( + typeof scope.internal.acroformPlugin.acroFormDictionaryRoot !== "undefined" + ) { + // for safety, shouldn't normally be the case + scope.internal.write( + "/AcroForm " + + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId + + " " + + 0 + + " R" + ); + } else { + throw new Error("putCatalogCallback: Root missing."); + } +}; - // read with same request keys and streamer infos - return this.readBuffer([this.fSeekKeys, this.fNbytesKeys, this.fSeekInfo, this.fNbytesInfo]); - }).then(blobs => { - const buf4 = new TBuffer(blobs[0], 0, this); +/** + * Adds /Acroform X 0 R to Document Catalog, and creates the AcroForm + * Dictionary + */ +var AcroFormDictionaryCallback = function(scope) { + // Remove event + scope.internal.events.unsubscribe( + scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID + ); + delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID; + scope.internal.acroformPlugin.printedOut = true; +}; - buf4.readTKey(); // - const nkeys = buf4.ntoi4(); - for (let i = 0; i < nkeys; ++i) - this.fKeys.push(buf4.readTKey()); +/** + * Creates the single Fields and writes them into the Document + * + * If fieldArray is set, use the fields that are inside it instead of the + * fields from the AcroRoot (for the FormXObjects...) + */ +var createFieldCallback = function(fieldArray, scope) { + var standardFields = !fieldArray; + + if (!fieldArray) { + // in case there is no fieldArray specified, we want to print out + // the Fields of the AcroForm + // Print out Root + scope.internal.newObjectDeferredBegin( + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId, + true + ); + scope.internal.acroformPlugin.acroFormDictionaryRoot.putStream(); + } - const buf5 = new TBuffer(blobs[1], 0, this), - si_key = buf5.readTKey(); - if (!si_key) - return Promise.reject(Error(`Fail to read StreamerInfo data in ${this.fURL}`)); + fieldArray = + fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids; - this.fKeys.push(si_key); - return this.readObjBuffer(si_key); - }).then(blob6 => { - this.extractStreamerInfos(blob6); - return this; - }); - } + for (var i in fieldArray) { + if (fieldArray.hasOwnProperty(i)) { + var fieldObject = fieldArray[i]; + var keyValueList = []; + var oldRect = fieldObject.Rect; - /** @summary Read the directory content from a root file - * @desc If directory was already read - return previously read object - * Same functionality as {@link TFile#readObject} - * @param {string} dir_name - directory name - * @param {number} [cycle] - directory cycle - * @return {Promise} - promise with read directory */ - async readDirectory(dir_name, cycle) { - return this.readObject(dir_name, cycle, true); - } + if (fieldObject.Rect) { + fieldObject.Rect = calculateCoordinates(fieldObject.Rect, scope); + } - /** @summary Search streamer info - * @param {string} clanme - class name - * @param {number} [clversion] - class version - * @param {number} [checksum] - streamer info checksum, have to match when specified - * @private */ - findStreamerInfo(clname, clversion, checksum) { - if (!this.fStreamerInfos) return null; + // Start Writing the Object + scope.internal.newObjectDeferredBegin(fieldObject.objId, true); - const arr = this.fStreamerInfos.arr, len = arr.length; + fieldObject.DA = AcroFormAppearance.createDefaultAppearanceStream( + fieldObject + ); - if (checksum !== undefined) { - let cache = this.fStreamerInfos.cache; - if (!cache) cache = this.fStreamerInfos.cache = {}; - let si = cache[checksum]; - if (si !== undefined) return si; + if ( + typeof fieldObject === "object" && + typeof fieldObject.getKeyValueListForStream === "function" + ) { + keyValueList = fieldObject.getKeyValueListForStream(); + } + + fieldObject.Rect = oldRect; + + if ( + fieldObject.hasAppearanceStream && + !fieldObject.appearanceStreamContent + ) { + // Calculate Appearance + var appearance = calculateAppearanceStream(fieldObject); + keyValueList.push({ key: "AP", value: "<>" }); + + scope.internal.acroformPlugin.xForms.push(appearance); + } + + // Assume AppearanceStreamContent is a Array with N,R,D (at least + // one of them!) + if (fieldObject.appearanceStreamContent) { + var appearanceStreamString = ""; + // Iterate over N,R and D + for (var k in fieldObject.appearanceStreamContent) { + if (fieldObject.appearanceStreamContent.hasOwnProperty(k)) { + var value = fieldObject.appearanceStreamContent[k]; + appearanceStreamString += "/" + k + " "; + appearanceStreamString += "<<"; + if (Object.keys(value).length >= 1 || Array.isArray(value)) { + // appearanceStream is an Array or Object! + for (var i in value) { + if (value.hasOwnProperty(i)) { + var obj = value[i]; + if (typeof obj === "function") { + // if Function is referenced, call it in order + // to get the FormXObject + obj = obj.call(scope, fieldObject); + } + appearanceStreamString += "/" + i + " " + obj + " "; - for (let i = 0; i < len; ++i) { - si = arr[i]; - if (si.fCheckSum === checksum) { - cache[checksum] = si; - return si; + // In case the XForm is already used, e.g. OffState + // of CheckBoxes, don't add it + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) + scope.internal.acroformPlugin.xForms.push(obj); + } + } + } else { + obj = value; + if (typeof obj === "function") { + // if Function is referenced, call it in order to + // get the FormXObject + obj = obj.call(scope, fieldObject); + } + appearanceStreamString += "/" + i + " " + obj; + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) + scope.internal.acroformPlugin.xForms.push(obj); } - } - cache[checksum] = null; // checksum didnot found, do not try again - } else { - for (let i = 0; i < len; ++i) { - const si = arr[i]; - if ((si.fName === clname) && ((si.fClassVersion === clversion) || (clversion === undefined))) return si; - } + appearanceStreamString += ">>"; + } + } + + // appearance stream is a normal Object.. + keyValueList.push({ + key: "AP", + value: "<<\n" + appearanceStreamString + ">>" + }); } - return null; - } + scope.internal.putStream({ + additionalKeyValues: keyValueList, + objectId: fieldObject.objId + }); - /** @summary Returns streamer for the class 'clname', - * @desc From the list of streamers or generate it from the streamer infos and add it to the list - * @private */ - getStreamer(clname, ver, s_i) { - // these are special cases, which are handled separately - if (clname === clTQObject || clname === clTBasket) return null; + scope.internal.out("endobj"); + } + } + if (standardFields) { + createXFormObjectCallback(scope.internal.acroformPlugin.xForms, scope); + } +}; - let streamer, fullname = clname; +var createXFormObjectCallback = function(fieldArray, scope) { + for (var i in fieldArray) { + if (fieldArray.hasOwnProperty(i)) { + var key = i; + var fieldObject = fieldArray[i]; + // Start Writing the Object + scope.internal.newObjectDeferredBegin(fieldObject.objId, true); + + if ( + typeof fieldObject === "object" && + typeof fieldObject.putStream === "function" + ) { + fieldObject.putStream(); + } + delete fieldArray[key]; + } + } +}; - if (ver) { - fullname += (ver.checksum ? `$chksum${ver.checksum}` : `$ver${ver.val}`); - streamer = this.fStreamers[fullname]; - if (streamer !== undefined) return streamer; - } +var initializeAcroForm = function(scope, formObject) { + formObject.scope = scope; + if ( + scope.internal !== undefined && + (scope.internal.acroformPlugin === undefined || + scope.internal.acroformPlugin.isInitialized === false) + ) { + AcroFormField.FieldNum = 0; + scope.internal.acroformPlugin = JSON.parse( + JSON.stringify(acroformPluginTemplate) + ); + if (scope.internal.acroformPlugin.acroFormDictionaryRoot) { + throw new Error("Exception while creating AcroformDictionary"); + } + scaleFactor = scope.internal.scaleFactor; + // The Object Number of the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary(); + scope.internal.acroformPlugin.acroFormDictionaryRoot.scope = scope; - const custom = CustomStreamers[clname]; + // add Callback for creating the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe( + "postPutResources", + function() { + AcroFormDictionaryCallback(scope); + } + ); - // one can define in the user streamers just aliases - if (isStr(custom)) - return this.getStreamer(custom, ver, s_i); + scope.internal.events.subscribe("buildDocument", function() { + annotReferenceCallback(scope); + }); // buildDocument - // streamer is just separate function - if (isFunc(custom)) { - streamer = [{ typename: clname, func: custom }]; - return addClassMethods(clname, streamer); - } + // Register event, that is triggered when the DocumentCatalog is + // written, in order to add /AcroForm - streamer = []; + scope.internal.events.subscribe("putCatalog", function() { + putCatalogCallback(scope); + }); - if (isObject(custom)) { - if (!custom.name && !custom.func) return custom; - streamer.push(custom); // special read entry, add in the beginning of streamer - } + // Register event, that creates all Fields + scope.internal.events.subscribe("postPutPages", function(fieldArray) { + createFieldCallback(fieldArray, scope); + }); - // check element in streamer infos, one can have special cases - if (!s_i) s_i = this.findStreamerInfo(clname, ver.val, ver.checksum); + scope.internal.acroformPlugin.isInitialized = true; + } +}; - if (!s_i) { - delete this.fStreamers[fullname]; - if (!ver.nowarning) - console.warn(`Not found streamer for ${clname} ver ${ver.val} checksum ${ver.checksum} full ${fullname}`); - return null; +//PDF 32000-1:2008, page 26, 7.3.6 +var arrayToPdfArray = (jsPDFAPI.__acroform__.arrayToPdfArray = function( + array, + objId, + scope +) { + var encryptor = function(data) { + return data; + }; + if (Array.isArray(array)) { + var content = "["; + for (var i = 0; i < array.length; i++) { + if (i !== 0) { + content += " "; + } + switch (typeof array[i]) { + case "boolean": + case "number": + case "object": + content += array[i].toString(); + break; + case "string": + if (array[i].substr(0, 1) !== "/") { + if (typeof objId !== "undefined" && scope) + encryptor = scope.internal.getEncryptor(objId); + content += "(" + pdfEscape(encryptor(array[i].toString())) + ")"; + } else { + content += array[i].toString(); + } + break; } + } + content += "]"; + return content; + } + throw new Error( + "Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray" + ); +}); +function getMatches(string, regex, index) { + index || (index = 1); // default to the first capturing group + var matches = []; + var match; + while ((match = regex.exec(string))) { + matches.push(match[index]); + } + return matches; +} +var pdfArrayToStringArray = function(array) { + var result = []; + if (typeof array === "string") { + result = getMatches(array, /\((.*?)\)/g); + } + return result; +}; - // special handling for TStyle which has duplicated member name fLineStyle - if ((s_i.fName === clTStyle) && s_i.fElements) { - s_i.fElements.arr.forEach(elem => { - if (elem.fName === 'fLineStyle') elem.fName = 'fLineStyles'; // like in ROOT JSON now - }); - } +var toPdfString = function(string, objId, scope) { + var encryptor = function(data) { + return data; + }; + if (typeof objId !== "undefined" && scope) + encryptor = scope.internal.getEncryptor(objId); + string = string || ""; + string.toString(); + string = "(" + pdfEscape(encryptor(string)) + ")"; + return string; +}; - // for each entry in streamer info produce member function - if (s_i.fElements) { - for (let j = 0; j < s_i.fElements.arr.length; ++j) - streamer.push(createMemberStreamer(s_i.fElements.arr[j], this)); - } +// ########################## +// Classes +// ########################## - this.fStreamers[fullname] = streamer; +/** + * @class AcroFormPDFObject + * @classdesc A AcroFormPDFObject + */ +var AcroFormPDFObject = function() { + this._objId = undefined; + this._scope = undefined; - return addClassMethods(clname, streamer); - } + /** + * @name AcroFormPDFObject#objId + * @type {any} + */ + Object.defineProperty(this, "objId", { + get: function() { + if (typeof this._objId === "undefined") { + if (typeof this.scope === "undefined") { + return undefined; + } + this._objId = this.scope.internal.newObjectDeferred(); + } + return this._objId; + }, + set: function(value) { + this._objId = value; + } + }); + Object.defineProperty(this, "scope", { + value: this._scope, + writable: true + }); +}; - /** @summary Here we produce list of members, resolving all base classes - * @private */ - getSplittedStreamer(streamer, tgt) { - if (!streamer) return tgt; +/** + * @function AcroFormPDFObject.toString + */ +AcroFormPDFObject.prototype.toString = function() { + return this.objId + " 0 R"; +}; - if (!tgt) tgt = []; +AcroFormPDFObject.prototype.putStream = function() { + var keyValueList = this.getKeyValueListForStream(); + this.scope.internal.putStream({ + data: this.stream, + additionalKeyValues: keyValueList, + objectId: this.objId + }); + this.scope.internal.out("endobj"); +}; - for (let n = 0; n < streamer.length; ++n) { - const elem = streamer[n]; +/** + * Returns an key-value-List of all non-configurable Variables from the Object + * + * @name getKeyValueListForStream + * @returns {string} + */ +AcroFormPDFObject.prototype.getKeyValueListForStream = function() { + var keyValueList = []; + var keys = Object.getOwnPropertyNames(this).filter(function(key) { + return ( + key != "content" && + key != "appearanceStreamContent" && + key != "scope" && + key != "objId" && + key.substring(0, 1) != "_" + ); + }); - if (elem.base === undefined) { - tgt.push(elem); - continue; - } + for (var i in keys) { + if (Object.getOwnPropertyDescriptor(this, keys[i]).configurable === false) { + var key = keys[i]; + var value = this[key]; - if (elem.basename === clTObject) { - tgt.push({ - func(buf, obj) { - buf.ntoi2(); // read version, why it here?? - obj.fUniqueID = buf.ntou4(); - obj.fBits = buf.ntou4(); - if (obj.fBits & kIsReferenced) buf.ntou2(); // skip pid - } - }); - continue; - } + if (value) { + if (Array.isArray(value)) { + keyValueList.push({ + key: key, + value: arrayToPdfArray(value, this.objId, this.scope) + }); + } else if (value instanceof AcroFormPDFObject) { + // In case it is a reference to another PDFObject, + // take the reference number + value.scope = this.scope; + keyValueList.push({ key: key, value: value.objId + " 0 R" }); + } else if (typeof value !== "function") { + keyValueList.push({ key: key, value: value }); + } + } + } + } + return keyValueList; +}; - const ver = { val: elem.base }; +var AcroFormXObject = function() { + AcroFormPDFObject.call(this); - if (ver.val === 4294967295) { - // this is -1 and indicates foreign class, need more workarounds - ver.val = 1; // need to search version 1 - that happens when several versions of foreign class exists ??? - } + Object.defineProperty(this, "Type", { + value: "/XObject", + configurable: false, + writable: true + }); - const parent = this.getStreamer(elem.basename, ver); - if (parent) this.getSplittedStreamer(parent, tgt); - } + Object.defineProperty(this, "Subtype", { + value: "/Form", + configurable: false, + writable: true + }); - return tgt; - } + Object.defineProperty(this, "FormType", { + value: 1, + configurable: false, + writable: true + }); - /** @summary Fully clenaup TFile data - * @private */ - delete() { - this.fDirectories = null; - this.fKeys = null; - this.fStreamers = null; - this.fSeekInfo = 0; - this.fNbytesInfo = 0; - this.fTagOffset = 0; - } + var _BBox = []; + Object.defineProperty(this, "BBox", { + configurable: false, + get: function() { + return _BBox; + }, + set: function(value) { + _BBox = value; + } + }); -} // class TFile + Object.defineProperty(this, "Resources", { + value: "2 0 R", + configurable: false, + writable: true + }); -/** @summary Function to read vector element in the streamer - * @private */ -function readVectorElement(buf) { - if (this.member_wise) { - const n = buf.ntou4(), ver = this.stl_version; - let streamer = null; + var _stream; + Object.defineProperty(this, "stream", { + enumerable: false, + configurable: true, + set: function(value) { + _stream = value.trim(); + }, + get: function() { + if (_stream) { + return _stream; + } else { + return null; + } + } + }); +}; - if (n === 0) return []; // for empty vector no need to search split streamers +inherit(AcroFormXObject, AcroFormPDFObject); - if (n > 1000000) - throw new Error(`member-wise streaming of ${this.conttype} num ${n} member ${this.name}`); +var AcroFormDictionary = function() { + AcroFormPDFObject.call(this); - if ((ver.val === this.member_ver) && (ver.checksum === this.member_checksum)) - streamer = this.member_streamer; - else { - streamer = buf.fFile.getStreamer(this.conttype, ver); + var _Kids = []; - this.member_streamer = streamer = buf.fFile.getSplittedStreamer(streamer); - this.member_ver = ver.val; - this.member_checksum = ver.checksum; + Object.defineProperty(this, "Kids", { + enumerable: false, + configurable: true, + get: function() { + if (_Kids.length > 0) { + return _Kids; + } else { + return undefined; } + } + }); + Object.defineProperty(this, "Fields", { + enumerable: false, + configurable: false, + get: function() { + return _Kids; + } + }); - const res = new Array(n); - let i, k, member; + // Default Appearance + var _DA; + Object.defineProperty(this, "DA", { + enumerable: false, + configurable: false, + get: function() { + if (!_DA) { + return undefined; + } + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + return "(" + pdfEscape(encryptor(_DA)) + ")"; + }, + set: function(value) { + _DA = value; + } + }); +}; - for (i = 0; i < n; ++i) - res[i] = { _typename: this.conttype }; // create objects - if (!streamer) - console.error(`Fail to create split streamer for ${this.conttype} need to read ${n} objects version ${ver}`); - else { - for (k = 0; k < streamer.length; ++k) { - member = streamer[k]; - if (member.split_func) - member.split_func(buf, res, n); - else { - for (i = 0; i < n; ++i) - member.func(buf, res[i]); - } - } - } - return res; - } +inherit(AcroFormDictionary, AcroFormPDFObject); - const n = buf.ntou4(), res = new Array(n); - let i = 0; +/** + * The Field Object contains the Variables, that every Field needs + * + * @class AcroFormField + * @classdesc An AcroForm FieldObject + */ +var AcroFormField = function() { + AcroFormPDFObject.call(this); + + //Annotation-Flag See Table 165 + var _F = 4; + Object.defineProperty(this, "F", { + enumerable: false, + configurable: false, + get: function() { + return _F; + }, + set: function(value) { + if (!isNaN(value)) { + _F = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute F supplied.' + ); + } + } + }); - if (n > 200000) { - console.error(`vector streaming for ${this.conttype} at ${n}`); - return res; - } + /** + * (PDF 1.2) If set, print the annotation when the page is printed. If clear, never print the annotation, regardless of wether is is displayed on the screen. + * NOTE 2 This can be useful for annotations representing interactive pushbuttons, which would serve no meaningful purpose on the printed page. + * + * @name AcroFormField#showWhenPrinted + * @default true + * @type {boolean} + */ + Object.defineProperty(this, "showWhenPrinted", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(_F, 3)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.F = setBitForPdf(_F, 3); + } else { + this.F = clearBitForPdf(_F, 3); + } + } + }); - if (this.arrkind > 0) - while (i < n) res[i++] = buf.readFastArray(buf.ntou4(), this.arrkind); - else if (this.arrkind === 0) - while (i < n) res[i++] = buf.readTString(); - else if (this.isptr) - while (i < n) res[i++] = buf.readObjectAny(); - else if (this.submember) - while (i < n) res[i++] = this.submember.readelem(buf); - else - while (i < n) res[i++] = buf.classStreamer({}, this.conttype); + var _Ff = 0; + Object.defineProperty(this, "Ff", { + enumerable: false, + configurable: false, + get: function() { + return _Ff; + }, + set: function(value) { + if (!isNaN(value)) { + _Ff = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute Ff supplied.' + ); + } + } + }); - return res; -} + var _Rect = []; + Object.defineProperty(this, "Rect", { + enumerable: false, + configurable: false, + get: function() { + if (_Rect.length === 0) { + return undefined; + } + return _Rect; + }, + set: function(value) { + if (typeof value !== "undefined") { + _Rect = value; + } else { + _Rect = []; + } + } + }); + /** + * The x-position of the field. + * + * @name AcroFormField#x + * @default null + * @type {number} + */ + Object.defineProperty(this, "x", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[0])) { + return 0; + } + return _Rect[0]; + }, + set: function(value) { + _Rect[0] = value; + } + }); -/** @summary Function used in streamer to read std::map object - * @private */ -function readMapElement(buf) { - let streamer = this.streamer; + /** + * The y-position of the field. + * + * @name AcroFormField#y + * @default null + * @type {number} + */ + Object.defineProperty(this, "y", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[1])) { + return 0; + } + return _Rect[1]; + }, + set: function(value) { + _Rect[1] = value; + } + }); - if (this.member_wise) { - // when member-wise streaming is used, version is written - const ver = this.stl_version; + /** + * The width of the field. + * + * @name AcroFormField#width + * @default null + * @type {number} + */ + Object.defineProperty(this, "width", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[2])) { + return 0; + } + return _Rect[2]; + }, + set: function(value) { + _Rect[2] = value; + } + }); - if (this.si) { - const si = buf.fFile.findStreamerInfo(this.pairtype, ver.val, ver.checksum); + /** + * The height of the field. + * + * @name AcroFormField#height + * @default null + * @type {number} + */ + Object.defineProperty(this, "height", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[3])) { + return 0; + } + return _Rect[3]; + }, + set: function(value) { + _Rect[3] = value; + } + }); - if (this.si !== si) { - streamer = getPairStreamer(si, this.pairtype, buf.fFile); - if (!streamer || streamer.length !== 2) { - console.log(`Fail to produce streamer for ${this.pairtype}`); - return null; - } - } + var _FT = ""; + Object.defineProperty(this, "FT", { + enumerable: true, + configurable: false, + get: function() { + return _FT; + }, + set: function(value) { + switch (value) { + case "/Btn": + case "/Tx": + case "/Ch": + case "/Sig": + _FT = value; + break; + default: + throw new Error( + 'Invalid value "' + value + '" for attribute FT supplied.' + ); } - } - - const n = buf.ntoi4(), res = new Array(n); - if (this.member_wise && (buf.remain() >= 6)) { - if (buf.ntoi2() === kStreamedMemberWise) - buf.shift(4); // skip checksum - else - buf.shift(-2); // rewind - } + } + }); - for (let i = 0; i < n; ++i) { - res[i] = { _typename: this.pairtype }; - streamer[0].func(buf, res[i]); - if (!this.member_wise) streamer[1].func(buf, res[i]); - } + var _T = null; - // due-to member-wise streaming second element read after first is completed - if (this.member_wise) { - if (buf.remain() >= 6) { - if (buf.ntoi2() === kStreamedMemberWise) - buf.shift(4); // skip checksum - else - buf.shift(-2); // rewind + Object.defineProperty(this, "T", { + enumerable: true, + configurable: false, + get: function() { + if (!_T || _T.length < 1) { + // In case of a Child from a Radio´Group, you don't need a FieldName + if (this instanceof AcroFormChildClass) { + return undefined; + } + _T = "FieldObject" + AcroFormField.FieldNum++; } - for (let i = 0; i < n; ++i) - streamer[1].func(buf, res[i]); - } + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + return "(" + pdfEscape(encryptor(_T)) + ")"; + }, + set: function(value) { + _T = value.toString(); + } + }); - return res; -} + /** + * (Optional) The partial field name (see 12.7.3.2, “Field Names”). + * + * @name AcroFormField#fieldName + * @default null + * @type {string} + */ + Object.defineProperty(this, "fieldName", { + configurable: true, + enumerable: true, + get: function() { + return _T; + }, + set: function(value) { + _T = value; + } + }); -// ============================================================= + var _fontName = "helvetica"; + /** + * The fontName of the font to be used. + * + * @name AcroFormField#fontName + * @default 'helvetica' + * @type {string} + */ + Object.defineProperty(this, "fontName", { + enumerable: true, + configurable: true, + get: function() { + return _fontName; + }, + set: function(value) { + _fontName = value; + } + }); -/** - * @summary Interface to read local file in the browser - * - * @hideconstructor - * @desc Use {@link openFile} to create instance of the class - * @private - */ + var _fontStyle = "normal"; + /** + * The fontStyle of the font to be used. + * + * @name AcroFormField#fontStyle + * @default 'normal' + * @type {string} + */ + Object.defineProperty(this, "fontStyle", { + enumerable: true, + configurable: true, + get: function() { + return _fontStyle; + }, + set: function(value) { + _fontStyle = value; + } + }); -class TLocalFile extends TFile { + var _fontSize = 0; + /** + * The fontSize of the font to be used. + * + * @name AcroFormField#fontSize + * @default 0 (for auto) + * @type {number} + */ + Object.defineProperty(this, "fontSize", { + enumerable: true, + configurable: true, + get: function() { + return _fontSize; + }, + set: function(value) { + _fontSize = value; + } + }); - constructor(file) { - super(null); - this.fUseStampPar = false; - this.fLocalFile = file; - this.fEND = file.size; - this.fFullURL = file.name; - this.fURL = file.name; - this.fFileName = file.name; - } + var _maxFontSize = undefined; + /** + * The maximum fontSize of the font to be used. + * + * @name AcroFormField#maxFontSize + * @default 0 (for auto) + * @type {number} + */ + Object.defineProperty(this, "maxFontSize", { + enumerable: true, + configurable: true, + get: function() { + if (_maxFontSize === undefined) { + // use the old default value here - the value is some kind of random as it depends on the scaleFactor (user unit) + // ("50" is transformed to the "user space" but then used in "pdf space") + return 50 / scaleFactor; + } else { + return _maxFontSize; + } + }, + set: function(value) { + _maxFontSize = value; + } + }); - /** @summary Open local file - * @return {Promise} after file keys are read */ - async _open() { return this.readKeys(); } + var _color = "black"; + /** + * The color of the text + * + * @name AcroFormField#color + * @default 'black' + * @type {string|rgba} + */ + Object.defineProperty(this, "color", { + enumerable: true, + configurable: true, + get: function() { + return _color; + }, + set: function(value) { + _color = value; + } + }); - /** @summary read buffer from local file - * @return {Promise} with read data */ - async readBuffer(place, filename /*, progress_callback */) { - const file = this.fLocalFile; + var _DA = "/F1 0 Tf 0 g"; + // Defines the default appearance (Needed for variable Text) + Object.defineProperty(this, "DA", { + enumerable: true, + configurable: false, + get: function() { + if ( + !_DA || + this instanceof AcroFormChildClass || + this instanceof AcroFormTextField + ) { + return undefined; + } + return toPdfString(_DA, this.objId, this.scope); + }, + set: function(value) { + value = value.toString(); + _DA = value; + } + }); - return new Promise((resolve, reject) => { - if (filename) - return reject(Error(`Cannot access other local file ${filename}`)); + var _DV = null; + Object.defineProperty(this, "DV", { + enumerable: false, + configurable: false, + get: function() { + if (!_DV) { + return undefined; + } + if (this instanceof AcroFormButton === false) { + return toPdfString(_DV, this.objId, this.scope); + } + return _DV; + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === false) { + if (value.substr(0, 1) === "(") { + _DV = pdfUnescape(value.substr(1, value.length - 2)); + } else { + _DV = pdfUnescape(value); + } + } else { + _DV = value; + } + } + }); - const reader = new FileReader(), blobs = []; - let cnt = 0; + /** + * (Optional; inheritable) The default value to which the field reverts when a reset-form action is executed (see 12.7.5.3, “Reset-Form Action”). The format of this value is the same as that of value. + * + * @name AcroFormField#defaultValue + * @default null + * @type {any} + */ + Object.defineProperty(this, "defaultValue", { + enumerable: true, + configurable: true, + get: function() { + if (this instanceof AcroFormButton === true) { + return pdfUnescape(_DV.substr(1, _DV.length - 1)); + } else { + return _DV; + } + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === true) { + _DV = "/" + value; + } else { + _DV = value; + } + } + }); - reader.onload = function(evnt) { - const res = new DataView(evnt.target.result); - if (place.length === 2) return resolve(res); + var _V = null; + Object.defineProperty(this, "_V", { + enumerable: false, + configurable: false, + get: function() { + if (!_V) { + return undefined; + } + return _V; + }, + set: function(value) { + this.V = value; + } + }); + Object.defineProperty(this, "V", { + enumerable: false, + configurable: false, + get: function() { + if (!_V) { + return undefined; + } + if (this instanceof AcroFormButton === false) { + return toPdfString(_V, this.objId, this.scope); + } + return _V; + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === false) { + if (value.substr(0, 1) === "(") { + _V = pdfUnescape(value.substr(1, value.length - 2)); + } else { + _V = pdfUnescape(value); + } + } else { + _V = value; + } + } + }); - blobs.push(res); - cnt += 2; - if (cnt >= place.length) return resolve(blobs); - reader.readAsArrayBuffer(file.slice(place[cnt], place[cnt] + place[cnt + 1])); - }; + /** + * (Optional; inheritable) The field’s value, whose format varies depending on the field type. See the descriptions of individual field types for further information. + * + * @name AcroFormField#value + * @default null + * @type {any} + */ + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + get: function() { + if (this instanceof AcroFormButton === true) { + return pdfUnescape(_V.substr(1, _V.length - 1)); + } else { + return _V; + } + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === true) { + _V = "/" + value; + } else { + _V = value; + } + } + }); - reader.readAsArrayBuffer(file.slice(place[0], place[0] + place[1])); - }); - } + /** + * Check if field has annotations + * + * @name AcroFormField#hasAnnotation + * @readonly + * @type {boolean} + */ + Object.defineProperty(this, "hasAnnotation", { + enumerable: true, + configurable: true, + get: function() { + return this.Rect; + } + }); -} // class TLocalFile + Object.defineProperty(this, "Type", { + enumerable: true, + configurable: false, + get: function() { + return this.hasAnnotation ? "/Annot" : null; + } + }); -/** - * @summary Interface to read file in node.js - * - * @hideconstructor - * @desc Use {@link openFile} to create instance of the class - * @private - */ + Object.defineProperty(this, "Subtype", { + enumerable: true, + configurable: false, + get: function() { + return this.hasAnnotation ? "/Widget" : null; + } + }); -class TNodejsFile extends TFile { + var _hasAppearanceStream = false; + /** + * true if field has an appearanceStream + * + * @name AcroFormField#hasAppearanceStream + * @readonly + * @type {boolean} + */ + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return _hasAppearanceStream; + }, + set: function(value) { + value = Boolean(value); + _hasAppearanceStream = value; + } + }); - constructor(filename) { - super(null); - this.fUseStampPar = false; - this.fEND = 0; - this.fFullURL = filename; - this.fURL = filename; - this.fFileName = filename; - } + /** + * The page on which the AcroFormField is placed + * + * @name AcroFormField#page + * @type {number} + */ + var _page; + Object.defineProperty(this, "page", { + enumerable: true, + configurable: true, + get: function() { + if (!_page) { + return undefined; + } + return _page; + }, + set: function(value) { + _page = value; + } + }); - /** @summary Open file in node.js - * @return {Promise} after file keys are read */ - async _open() { - return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { - this.fs = fs; + /** + * If set, the user may not change the value of the field. Any associated widget annotations will not interact with the user; that is, they will not respond to mouse clicks or change their appearance in response to mouse motions. This flag is useful for fields whose values are computed or imported from a database. + * + * @name AcroFormField#readOnly + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "readOnly", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 1)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 1); + } else { + this.Ff = clearBitForPdf(this.Ff, 1); + } + } + }); - return new Promise((resolve, reject) => + /** + * If set, the field shall have a value at the time it is exported by a submitform action (see 12.7.5.2, “Submit-Form Action”). + * + * @name AcroFormField#required + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "required", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 2)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 2); + } else { + this.Ff = clearBitForPdf(this.Ff, 2); + } + } + }); - this.fs.open(this.fFileName, 'r', (status, fd) => { - if (status) { - console.log(status.message); - return reject(Error(`Not possible to open ${this.fFileName} inside node.js`)); - } - const stats = this.fs.fstatSync(fd); - this.fEND = stats.size; - this.fd = fd; - this.readKeys().then(resolve).catch(reject); - }) - ); - }); - } + /** + * If set, the field shall not be exported by a submit-form action (see 12.7.5.2, “Submit-Form Action”) + * + * @name AcroFormField#noExport + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "noExport", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 3)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 3); + } else { + this.Ff = clearBitForPdf(this.Ff, 3); + } + } + }); - /** @summary Read buffer from node.js file - * @return {Promise} with requested blocks */ - async readBuffer(place, filename /*, progress_callback */) { - return new Promise((resolve, reject) => { - if (filename) - return reject(Error(`Cannot access other local file ${filename}`)); + var _Q = null; + Object.defineProperty(this, "Q", { + enumerable: true, + configurable: false, + get: function() { + if (_Q === null) { + return undefined; + } + return _Q; + }, + set: function(value) { + if ([0, 1, 2].indexOf(value) !== -1) { + _Q = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute Q supplied.' + ); + } + } + }); - if (!this.fs || !this.fd) - return reject(Error(`File is not opened ${this.fFileName}`)); + /** + * (Optional; inheritable) A code specifying the form of quadding (justification) that shall be used in displaying the text: + * 'left', 'center', 'right' + * + * @name AcroFormField#textAlign + * @default 'left' + * @type {string} + */ + Object.defineProperty(this, "textAlign", { + get: function() { + var result; + switch (_Q) { + case 0: + default: + result = "left"; + break; + case 1: + result = "center"; + break; + case 2: + result = "right"; + break; + } + return result; + }, + configurable: true, + enumerable: true, + set: function(value) { + switch (value) { + case "right": + case 2: + _Q = 2; + break; + case "center": + case 1: + _Q = 1; + break; + case "left": + case 0: + default: + _Q = 0; + } + } + }); +}; - const blobs = []; - let cnt = 0; +inherit(AcroFormField, AcroFormPDFObject); - // eslint-disable-next-line n/handle-callback-err - const readfunc = (_err, _bytesRead, buf) => { - const res = new DataView(buf.buffer, buf.byteOffset, place[cnt + 1]); - if (place.length === 2) return resolve(res); - blobs.push(res); - cnt += 2; - if (cnt >= place.length) return resolve(blobs); - this.fs.read(this.fd, Buffer.alloc(place[cnt + 1]), 0, place[cnt + 1], place[cnt], readfunc); - }; +/** + * @class AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormChoiceField = function() { + AcroFormField.call(this); + // Field Type = Choice Field + this.FT = "/Ch"; + // options + this.V = "()"; + + this.fontName = "zapfdingbats"; + // Top Index + var _TI = 0; + + Object.defineProperty(this, "TI", { + enumerable: true, + configurable: false, + get: function() { + return _TI; + }, + set: function(value) { + _TI = value; + } + }); - this.fs.read(this.fd, Buffer.alloc(place[1]), 0, place[1], place[0], readfunc); - }); - } + /** + * (Optional) For scrollable list boxes, the top index (the index in the Opt array of the first option visible in the list). Default value: 0. + * + * @name AcroFormChoiceField#topIndex + * @default 0 + * @type {number} + */ + Object.defineProperty(this, "topIndex", { + enumerable: true, + configurable: true, + get: function() { + return _TI; + }, + set: function(value) { + _TI = value; + } + }); -} // class TNodejsFile + var _Opt = []; + Object.defineProperty(this, "Opt", { + enumerable: true, + configurable: false, + get: function() { + return arrayToPdfArray(_Opt, this.objId, this.scope); + }, + set: function(value) { + _Opt = pdfArrayToStringArray(value); + } + }); -/** - * @summary Proxy to read file contenxt - * - * @desc Should implement following methods: - * - * - openFile() - return Promise with true when file can be open normally - * - getFileName() - returns string with file name - * - getFileSize() - returns size of file - * - readBuffer(pos, len) - return promise with DataView for requested position and length - * - * @private - */ + /** + * @memberof AcroFormChoiceField + * @name getOptions + * @function + * @instance + * @returns {array} array of Options + */ + this.getOptions = function() { + return _Opt; + }; -class FileProxy { + /** + * @memberof AcroFormChoiceField + * @name setOptions + * @function + * @instance + * @param {array} value + */ + this.setOptions = function(value) { + _Opt = value; + if (this.sort) { + _Opt.sort(); + } + }; - async openFile() { return false; } - getFileName() { return ''; } - getFileSize() { return 0; } - async readBuffer(/* pos, sz */) { return null; } + /** + * @memberof AcroFormChoiceField + * @name addOption + * @function + * @instance + * @param {string} value + */ + this.addOption = function(value) { + value = value || ""; + value = value.toString(); + _Opt.push(value); + if (this.sort) { + _Opt.sort(); + } + }; -} // class FileProxy + /** + * @memberof AcroFormChoiceField + * @name removeOption + * @function + * @instance + * @param {string} value + * @param {boolean} allEntries (default: false) + */ + this.removeOption = function(value, allEntries) { + allEntries = allEntries || false; + value = value || ""; + value = value.toString(); + + while (_Opt.indexOf(value) !== -1) { + _Opt.splice(_Opt.indexOf(value), 1); + if (allEntries === false) { + break; + } + } + }; -/** - * @summary File to use file context via FileProxy - * - * @hideconstructor - * @desc Use {@link openFile} to create instance of the class, providing FileProxy as argument - * @private - */ + /** + * If set, the field is a combo box; if clear, the field is a list box. + * + * @name AcroFormChoiceField#combo + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "combo", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 18)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 18); + } else { + this.Ff = clearBitForPdf(this.Ff, 18); + } + } + }); -class TProxyFile extends TFile { + /** + * If set, the combo box shall include an editable text box as well as a drop-down list; if clear, it shall include only a drop-down list. This flag shall be used only if the Combo flag is set. + * + * @name AcroFormChoiceField#edit + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "edit", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 19)); + }, + set: function(value) { + //PDF 32000-1:2008, page 444 + if (this.combo === true) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 19); + } else { + this.Ff = clearBitForPdf(this.Ff, 19); + } + } + } + }); - constructor(proxy) { - super(null); - this.fUseStampPar = false; - this.proxy = proxy; - } + /** + * If set, the field’s option items shall be sorted alphabetically. This flag is intended for use by writers, not by readers. Conforming readers shall display the options in the order in which they occur in the Opt array (see Table 231). + * + * @name AcroFormChoiceField#sort + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "sort", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 20)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 20); + _Opt.sort(); + } else { + this.Ff = clearBitForPdf(this.Ff, 20); + } + } + }); - /** @summary Open file - * @return {Promise} after file keys are read */ - async _open() { - return this.proxy.openFile().then(res => { - if (!res) return false; - this.fEND = this.proxy.getFileSize(); - this.fFullURL = this.fURL = this.fFileName = this.proxy.getFileName(); - if (isStr(this.fFileName)) { - const p = this.fFileName.lastIndexOf('/'); - if ((p > 0) && (p < this.fFileName.length - 4)) - this.fFileName = this.fFileName.slice(p+1); - } - return this.readKeys(); - }); - } + /** + * (PDF 1.4) If set, more than one of the field’s option items may be selected simultaneously; if clear, at most one item shall be selected + * + * @name AcroFormChoiceField#multiSelect + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "multiSelect", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 22)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 22); + } else { + this.Ff = clearBitForPdf(this.Ff, 22); + } + } + }); - /** @summary Read buffer from FileProxy - * @return {Promise} with requested blocks */ - async readBuffer(place, filename /*, progress_callback */) { - if (filename) - return Promise.reject(Error(`Cannot access other file ${filename}`)); + /** + * (PDF 1.4) If set, text entered in the field shall not be spellchecked. This flag shall not be used unless the Combo and Edit flags are both set. + * + * @name AcroFormChoiceField#doNotSpellCheck + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "doNotSpellCheck", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 23)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 23); + } else { + this.Ff = clearBitForPdf(this.Ff, 23); + } + } + }); - if (!this.proxy) - return Promise.reject(Error(`File is not opened ${this.fFileName}`)); + /** + * (PDF 1.5) If set, the new value shall be committed as soon as a selection is made (commonly with the pointing device). In this case, supplying a value for a field involves three actions: selecting the field for fill-in, selecting a choice for the fill-in value, and leaving that field, which finalizes or “commits” the data choice and triggers any actions associated with the entry or changing of this data. If this flag is on, then processing does not wait for leaving the field action to occur, but immediately proceeds to the third step. + * This option enables applications to perform an action once a selection is made, without requiring the user to exit the field. If clear, the new value is not committed until the user exits the field. + * + * @name AcroFormChoiceField#commitOnSelChange + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "commitOnSelChange", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 27)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 27); + } else { + this.Ff = clearBitForPdf(this.Ff, 27); + } + } + }); - if (place.length === 2) - return this.proxy.readBuffer(place[0], place[1]); + this.hasAppearanceStream = false; +}; +inherit(AcroFormChoiceField, AcroFormField); - const arr = []; - for (let k = 0; k < place.length; k+=2) - arr.push(this.proxy.readBuffer(place[k], place[k+1])); - return Promise.all(arr); - } +/** + * @class AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormListBox = function() { + AcroFormChoiceField.call(this); + this.fontName = "helvetica"; -} // class TProxyFile + //PDF 32000-1:2008, page 444 + this.combo = false; +}; +inherit(AcroFormListBox, AcroFormChoiceField); +/** + * @class AcroFormComboBox + * @extends AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormComboBox = function() { + AcroFormListBox.call(this); + this.combo = true; +}; +inherit(AcroFormComboBox, AcroFormListBox); -/** @summary Open ROOT file for reading - * @desc Generic method to open ROOT file for reading - * Following kind of arguments can be provided: - * - string with file URL (see example). In node.js environment local file like 'file://hsimple.root' can be specified - * - [File]{@link https://developer.mozilla.org/en-US/docs/Web/API/File} instance which let read local files from browser - * - [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer} instance with complete file content - * - [FileProxy]{@link FileProxy} let access arbitrary files via tiny proxy API - * @param {string|object} arg - argument for file open like url, see details - * @return {object} - Promise with {@link TFile} instance when file is opened - * @example - * - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * let f = await openFile('https://root.cern/js/files/hsimple.root'); - * console.log(`Open file ${f.getFileName()}`); */ -function openFile(arg) { - let file; +/** + * @class AcroFormEditBox + * @extends AcroFormComboBox + * @extends AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormEditBox = function() { + AcroFormComboBox.call(this); + this.edit = true; +}; +inherit(AcroFormEditBox, AcroFormComboBox); - if (isNodeJs() && isStr(arg)) { - if (arg.indexOf('file://') === 0) - file = new TNodejsFile(arg.slice(7)); - else if (arg.indexOf('http') !== 0) - file = new TNodejsFile(arg); - } +/** + * @class AcroFormButton + * @extends AcroFormField + */ +var AcroFormButton = function() { + AcroFormField.call(this); + this.FT = "/Btn"; + + /** + * (Radio buttons only) If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect. If clear, clicking the selected button deselects it, leaving no button selected. + * + * @name AcroFormButton#noToggleToOff + * @type {boolean} + */ + Object.defineProperty(this, "noToggleToOff", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 15)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 15); + } else { + this.Ff = clearBitForPdf(this.Ff, 15); + } + } + }); + + /** + * If set, the field is a set of radio buttons; if clear, the field is a checkbox. This flag may be set only if the Pushbutton flag is clear. + * + * @name AcroFormButton#radio + * @type {boolean} + */ + Object.defineProperty(this, "radio", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 16)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 16); + } else { + this.Ff = clearBitForPdf(this.Ff, 16); + } + } + }); + + /** + * If set, the field is a pushbutton that does not retain a permanent value. + * + * @name AcroFormButton#pushButton + * @type {boolean} + */ + Object.defineProperty(this, "pushButton", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 17)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 17); + } else { + this.Ff = clearBitForPdf(this.Ff, 17); + } + } + }); - if (!file && isObject(arg) && (arg instanceof FileProxy)) - file = new TProxyFile(arg); + /** + * (PDF 1.5) If set, a group of radio buttons within a radio button field that use the same value for the on state will turn on and off in unison; that is if one is checked, they are all checked. If clear, the buttons are mutually exclusive (the same behavior as HTML radio buttons). + * + * @name AcroFormButton#radioIsUnison + * @type {boolean} + */ + Object.defineProperty(this, "radioIsUnison", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 26)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 26); + } else { + this.Ff = clearBitForPdf(this.Ff, 26); + } + } + }); - if (!file && isObject(arg) && (arg instanceof ArrayBuffer)) { - file = new TFile('localfile.root'); - file.assignFileContent(arg); - } + var _MK = {}; + Object.defineProperty(this, "MK", { + enumerable: false, + configurable: false, + get: function() { + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + if (Object.keys(_MK).length !== 0) { + var result = []; + result.push("<<"); + var key; + for (key in _MK) { + result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); + } + result.push(">>"); + return result.join("\n"); + } + return undefined; + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); - if (!file && isObject(arg) && arg.size && arg.name) - file = new TLocalFile(arg); + /** + * From the PDF reference: + * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. + * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). + * + * - '8' = Cross, + * - 'l' = Circle, + * - '' = nothing + * @name AcroFormButton#caption + * @type {string} + */ + Object.defineProperty(this, "caption", { + enumerable: true, + configurable: true, + get: function() { + return _MK.CA || ""; + }, + set: function(value) { + if (typeof value === "string") { + _MK.CA = value; + } + } + }); - if (!file) - file = new TFile(arg); + var _AS; + Object.defineProperty(this, "AS", { + enumerable: false, + configurable: false, + get: function() { + return _AS; + }, + set: function(value) { + _AS = value; + } + }); - return file._open(); -} + /** + * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") + * + * @name AcroFormButton#appearanceState + * @type {any} + */ + Object.defineProperty(this, "appearanceState", { + enumerable: true, + configurable: true, + get: function() { + return _AS.substr(1, _AS.length - 1); + }, + set: function(value) { + _AS = "/" + value; + } + }); +}; +inherit(AcroFormButton, AcroFormField); -// special way to assign methods when streaming objects -addClassMethods(clTNamed, CustomStreamers[clTNamed]); -addClassMethods(clTObjString, CustomStreamers[clTObjString]); +/** + * @class AcroFormPushButton + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormPushButton = function() { + AcroFormButton.call(this); + this.pushButton = true; +}; +inherit(AcroFormPushButton, AcroFormButton); -// branch types -const kLeafNode = 0, kBaseClassNode = 1, kObjectNode = 2, kClonesNode = 3, - kSTLNode = 4, kClonesMemberNode = 31, kSTLMemberNode = 41, - // branch bits - // kDoNotProcess = BIT(10), // Active bit for branches - // kIsClone = BIT(11), // to indicate a TBranchClones - // kBranchObject = BIT(12), // branch is a TObject* - // kBranchAny = BIT(17), // branch is an object* - // kAutoDelete = BIT(15), - kDoNotUseBufferMap = BIT(22), // If set, at least one of the entry in the branch will use the buffer's map of classname and objects. - clTBranchElement = 'TBranchElement', clTBranchFunc = 'TBranchFunc'; +/** + * @class AcroFormRadioButton + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormRadioButton = function() { + AcroFormButton.call(this); + this.radio = true; + this.pushButton = false; + + var _Kids = []; + Object.defineProperty(this, "Kids", { + enumerable: true, + configurable: false, + get: function() { + return _Kids; + }, + set: function(value) { + if (typeof value !== "undefined") { + _Kids = value; + } else { + _Kids = []; + } + } + }); +}; +inherit(AcroFormRadioButton, AcroFormButton); /** - * @summary Class to read data from TTree + * The Child class of a RadioButton (the radioGroup) -> The single Buttons * - * @desc Instance of TSelector can be used to access TTree data + * @class AcroFormChildClass + * @extends AcroFormField + * @ignore */ +var AcroFormChildClass = function() { + AcroFormField.call(this); + + var _parent; + Object.defineProperty(this, "Parent", { + enumerable: false, + configurable: false, + get: function() { + return _parent; + }, + set: function(value) { + _parent = value; + } + }); -class TSelector { + var _optionName; + Object.defineProperty(this, "optionName", { + enumerable: false, + configurable: true, + get: function() { + return _optionName; + }, + set: function(value) { + _optionName = value; + } + }); - /** @summary constructor */ - constructor() { - this._branches = []; // list of branches to read - this._names = []; // list of member names for each branch in tgtobj - this._directs = []; // indication if only branch without any children should be read - this._break = 0; - this.tgtobj = {}; - } + var _MK = {}; + Object.defineProperty(this, "MK", { + enumerable: false, + configurable: false, + get: function() { + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + var result = []; + result.push("<<"); + var key; + for (key in _MK) { + result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); + } + result.push(">>"); + return result.join("\n"); + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); - /** @summary Add branch to the selector - * @desc Either branch name or branch itself should be specified - * Second parameter defines member name in the tgtobj - * If selector.addBranch('px', 'read_px') is called, - * branch will be read into selector.tgtobj.read_px member - * If second parameter not specified, branch name (here 'px') will be used - * If branch object specified as first parameter and second parameter missing, - * then member like 'br0', 'br1' and so on will be assigned - * @param {string|Object} branch - name of branch (or branch object itself} - * @param {string} [name] - member name in tgtobj where data will be read - * @param {boolean} [direct] - if only branch without any children should be read */ - addBranch(branch, name, direct) { - if (!name) - name = isStr(branch) ? branch : `br${this._branches.length}`; - this._branches.push(branch); - this._names.push(name); - this._directs.push(direct); - return this._branches.length - 1; - } + /** + * From the PDF reference: + * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. + * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). + * + * - '8' = Cross, + * - 'l' = Circle, + * - '' = nothing + * @name AcroFormButton#caption + * @type {string} + */ + Object.defineProperty(this, "caption", { + enumerable: true, + configurable: true, + get: function() { + return _MK.CA || ""; + }, + set: function(value) { + if (typeof value === "string") { + _MK.CA = value; + } + } + }); - /** @summary returns number of branches used in selector */ - numBranches() { return this._branches.length; } + var _AS; + Object.defineProperty(this, "AS", { + enumerable: false, + configurable: false, + get: function() { + return _AS; + }, + set: function(value) { + _AS = value; + } + }); - /** @summary returns branch by index used in selector */ - getBranch(indx) { return this._branches[indx]; } + /** + * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") + * + * @name AcroFormButton#appearanceState + * @type {any} + */ + Object.defineProperty(this, "appearanceState", { + enumerable: true, + configurable: true, + get: function() { + return _AS.substr(1, _AS.length - 1); + }, + set: function(value) { + _AS = "/" + value; + } + }); + this.caption = "l"; + this.appearanceState = "Off"; + // todo: set AppearanceType as variable that can be set from the + // outside... + this._AppearanceType = AcroFormAppearance.RadioButton.Circle; + // The Default appearanceType is the Circle + this.appearanceStreamContent = this._AppearanceType.createAppearanceStream( + this.optionName + ); +}; +inherit(AcroFormChildClass, AcroFormField); - /** @summary returns index of branch - * @private */ - indexOfBranch(branch) { return this._branches.indexOf(branch); } +AcroFormRadioButton.prototype.setAppearance = function(appearance) { + if (!("createAppearanceStream" in appearance && "getCA" in appearance)) { + throw new Error( + "Couldn't assign Appearance to RadioButton. Appearance was Invalid!" + ); + } + for (var objId in this.Kids) { + if (this.Kids.hasOwnProperty(objId)) { + var child = this.Kids[objId]; + child.appearanceStreamContent = appearance.createAppearanceStream( + child.optionName + ); + child.caption = appearance.getCA(); + } + } +}; - /** @summary returns name of branch - * @private */ - nameOfBranch(indx) { return this._names[indx]; } +AcroFormRadioButton.prototype.createOption = function(name) { + // Create new Child for RadioGroup + var child = new AcroFormChildClass(); + child.Parent = this; + child.optionName = name; + // Add to Parent + this.Kids.push(child); - /** @summary function called during TTree processing - * @abstract - * @param {number} progress - current value between 0 and 1 */ - ShowProgress(/* progress */) {} + addField.call(this.scope, child); - /** @summary call this function to abort processing */ - Abort() { this._break = -1111; } + return child; +}; - /** @summary function called before start processing - * @abstract - * @param {object} tree - tree object */ - Begin(/* tree */) {} +/** + * @class AcroFormCheckBox + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormCheckBox = function() { + AcroFormButton.call(this); + + this.fontName = "zapfdingbats"; + this.caption = "3"; + this.appearanceState = "On"; + this.value = "On"; + this.textAlign = "center"; + this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream(); +}; +inherit(AcroFormCheckBox, AcroFormButton); - /** @summary function called when next entry extracted from the tree - * @abstract - * @param {number} entry - read entry number */ - Process(/* entry */) {} +/** + * @class AcroFormTextField + * @extends AcroFormField + */ +var AcroFormTextField = function() { + AcroFormField.call(this); + this.FT = "/Tx"; + + /** + * If set, the field may contain multiple lines of text; if clear, the field’s text shall be restricted to a single line. + * + * @name AcroFormTextField#multiline + * @type {boolean} + */ + Object.defineProperty(this, "multiline", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 13)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 13); + } else { + this.Ff = clearBitForPdf(this.Ff, 13); + } + } + }); - /** @summary function called at the very end of processing - * @abstract - * @param {boolean} res - true if all data were correctly processed */ - Terminate(/* res */) {} + /** + * (PDF 1.4) If set, the text entered in the field represents the pathname of a file whose contents shall be submitted as the value of the field. + * + * @name AcroFormTextField#fileSelect + * @type {boolean} + */ + Object.defineProperty(this, "fileSelect", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 21)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 21); + } else { + this.Ff = clearBitForPdf(this.Ff, 21); + } + } + }); -} // class TSelector + /** + * (PDF 1.4) If set, text entered in the field shall not be spell-checked. + * + * @name AcroFormTextField#doNotSpellCheck + * @type {boolean} + */ + Object.defineProperty(this, "doNotSpellCheck", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 23)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 23); + } else { + this.Ff = clearBitForPdf(this.Ff, 23); + } + } + }); -// ================================================================= + /** + * (PDF 1.4) If set, the field shall not scroll (horizontally for single-line fields, vertically for multiple-line fields) to accommodate more text than fits within its annotation rectangle. Once the field is full, no further text shall be accepted for interactive form filling; for noninteractive form filling, the filler should take care not to add more character than will visibly fit in the defined area. + * + * @name AcroFormTextField#doNotScroll + * @type {boolean} + */ + Object.defineProperty(this, "doNotScroll", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 24)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 24); + } else { + this.Ff = clearBitForPdf(this.Ff, 24); + } + } + }); -/** @summary Checks array kind - * @desc return 0 when not array - * 1 - when arbitrary array - * 2 - when plain (1-dim) array with same-type content - * @private */ -function checkArrayPrototype(arr, check_content) { - if (!isObject(arr)) return 0; + /** + * (PDF 1.5) May be set only if the MaxLen entry is present in the text field dictionary (see Table 229) and if the Multiline, Password, and FileSelect flags are clear. If set, the field shall be automatically divided into as many equally spaced positions, or combs, as the value of MaxLen, and the text is laid out into those combs. + * + * @name AcroFormTextField#comb + * @type {boolean} + */ + Object.defineProperty(this, "comb", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 25)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 25); + } else { + this.Ff = clearBitForPdf(this.Ff, 25); + } + } + }); - const arr_kind = isArrayProto(Object.prototype.toString.apply(arr)); - if (!check_content || (arr_kind !== 1)) return arr_kind; + /** + * (PDF 1.5) If set, the value of this field shall be a rich text string (see 12.7.3.4, “Rich Text Strings”). If the field has a value, the RV entry of the field dictionary (Table 222) shall specify the rich text string. + * + * @name AcroFormTextField#richText + * @type {boolean} + */ + Object.defineProperty(this, "richText", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 26)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 26); + } else { + this.Ff = clearBitForPdf(this.Ff, 26); + } + } + }); - let typ, plain = true; - for (let k = 0; k < arr.length; ++k) { - const sub = typeof arr[k]; - if (!typ) typ = sub; - if (sub !== typ) { plain = false; break; } - if (isObject(sub) && checkArrayPrototype(arr[k])) { plain = false; break; } - } + var _MaxLen = null; + Object.defineProperty(this, "MaxLen", { + enumerable: true, + configurable: false, + get: function() { + return _MaxLen; + }, + set: function(value) { + _MaxLen = value; + } + }); - return plain ? 2 : 1; -} + /** + * (Optional; inheritable) The maximum length of the field’s text, in characters. + * + * @name AcroFormTextField#maxLength + * @type {number} + */ + Object.defineProperty(this, "maxLength", { + enumerable: true, + configurable: true, + get: function() { + return _MaxLen; + }, + set: function(value) { + if (Number.isInteger(value)) { + _MaxLen = value; + } + } + }); + + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return this.V || this.DV; + } + }); +}; +inherit(AcroFormTextField, AcroFormField); /** - * @summary Class to iterate over array elements - * - * @private + * @class AcroFormPasswordField + * @extends AcroFormTextField + * @extends AcroFormField */ +var AcroFormPasswordField = function() { + AcroFormTextField.call(this); + + /** + * If set, the field is intended for entering a secure password that should not be echoed visibly to the screen. Characters typed from the keyboard shall instead be echoed in some unreadable form, such as asterisks or bullet characters. + * NOTE To protect password confidentiality, readers should never store the value of the text field in the PDF file if this flag is set. + * + * @name AcroFormTextField#password + * @type {boolean} + */ + Object.defineProperty(this, "password", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 14)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 14); + } else { + this.Ff = clearBitForPdf(this.Ff, 14); + } + } + }); + this.password = true; +}; +inherit(AcroFormPasswordField, AcroFormTextField); + +// Contains Methods for creating standard appearances +var AcroFormAppearance = { + CheckBox: { + createAppearanceStream: function() { + var appearance = { + N: { + On: AcroFormAppearance.CheckBox.YesNormal + }, + D: { + On: AcroFormAppearance.CheckBox.YesPushDown, + Off: AcroFormAppearance.CheckBox.OffPushDown + } + }; -class ArrayIterator { + return appearance; + }, + /** + * Returns the standard On Appearance for a CheckBox + * + * @returns {AcroFormXObject} + */ + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var calcRes = calculateX(formObject, formObject.caption); + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + stream.push("BMC"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push( + "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor + ); + stream.push("BT"); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + stream.push("EMC"); + xobj.stream = stream.join("\n"); + return xobj; + }, - /** @summary constructor */ - constructor(arr, select, tgtobj) { - this.object = arr; - this.value = 0; // value always used in iterator - this.arr = []; // all arrays - this.indx = []; // all indexes - this.cnt = -1; // current index counter - this.tgtobj = tgtobj; + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var stream = []; + var height = AcroFormAppearance.internal.getHeight(formObject); + var width = AcroFormAppearance.internal.getWidth(formObject); + var calcRes = calculateX(formObject, formObject.caption); + stream.push("1 g"); + stream.push("0 0 " + f2(width) + " " + f2(height) + " re"); + stream.push("f"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push("0 0 " + f2(width - 1) + " " + f2(height - 1) + " re"); + stream.push("W"); + stream.push("n"); + stream.push("0 g"); + stream.push("BT"); + stream.push( + "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor + ); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, - if (isObject(select)) - this.select = select; // remember indexes for selection - else - this.select = []; // empty array, undefined for each dimension means iterate over all indexes - } + /** + * Returns the standard Off Appearance for a CheckBox + * + * @returns {AcroFormXObject} + */ + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, - /** @summary next element */ - next() { - let obj, typ, cnt = this.cnt; + RadioButton: { + Circle: { + createAppearanceStream: function(name) { + var appearanceStreamContent = { + D: { + Off: AcroFormAppearance.RadioButton.Circle.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = + AcroFormAppearance.RadioButton.Circle.YesNormal; + appearanceStreamContent.D[name] = + AcroFormAppearance.RadioButton.Circle.YesPushDown; + return appearanceStreamContent; + }, + getCA: function() { + return "l"; + }, - if (cnt >= 0) { - if (++this.fastindx < this.fastlimit) { - this.value = this.fastarr[this.fastindx]; - return true; - } + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + // Make the Radius of the Circle relative to min(height, width) of formObject + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + var c = AcroFormAppearance.internal.Bezier_C; + var DotRadiusBezier = Number((DotRadius * c).toFixed(5)); + /* + * The Following is a Circle created with Bezier-Curves. + */ + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(DotRadius + " 0 m"); + stream.push( + DotRadius + + " " + + DotRadiusBezier + + " " + + DotRadiusBezier + + " " + + DotRadius + + " 0 " + + DotRadius + + " c" + ); + stream.push( + "-" + + DotRadiusBezier + + " " + + DotRadius + + " -" + + DotRadius + + " " + + DotRadiusBezier + + " -" + + DotRadius + + " 0 c" + ); + stream.push( + "-" + + DotRadius + + " -" + + DotRadiusBezier + + " -" + + DotRadiusBezier + + " -" + + DotRadius + + " 0 -" + + DotRadius + + " c" + ); + stream.push( + DotRadiusBezier + + " -" + + DotRadius + + " " + + DotRadius + + " -" + + DotRadiusBezier + + " " + + DotRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + // Save results for later use; no need to waste + // processor ticks on doing math + var k = Number((DotRadius * 2).toFixed(5)); + var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); + var dc = Number( + (DotRadius * AcroFormAppearance.internal.Bezier_C).toFixed(5) + ); + + stream.push("0.749023 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push( + "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" + ); + stream.push( + "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" + ); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + stream.push("0 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(DotRadius + " 0 m"); + stream.push( + "" + + DotRadius + + " " + + dc + + " " + + dc + + " " + + DotRadius + + " 0 " + + DotRadius + + " c" + ); + stream.push( + "-" + + dc + + " " + + DotRadius + + " -" + + DotRadius + + " " + + dc + + " -" + + DotRadius + + " 0 c" + ); + stream.push( + "-" + + DotRadius + + " -" + + dc + + " -" + + dc + + " -" + + DotRadius + + " 0 -" + + DotRadius + + " c" + ); + stream.push( + dc + + " -" + + DotRadius + + " " + + DotRadius + + " -" + + dc + + " " + + DotRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + // Save results for later use; no need to waste + // processor ticks on doing math + var k = Number((DotRadius * 2).toFixed(5)); + var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); + + stream.push("0.749023 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push( + "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" + ); + stream.push( + "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" + ); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, - while (--cnt >= 0) { - if ((this.select[cnt] === undefined) && (++this.indx[cnt] < this.arr[cnt].length)) - break; - } - if (cnt < 0) return false; + Cross: { + /** + * Creates the Actual AppearanceDictionary-References + * + * @param {string} name + * @returns {Object} + * @ignore + */ + createAppearanceStream: function(name) { + var appearanceStreamContent = { + D: { + Off: AcroFormAppearance.RadioButton.Cross.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = + AcroFormAppearance.RadioButton.Cross.YesNormal; + appearanceStreamContent.D[name] = + AcroFormAppearance.RadioButton.Cross.YesPushDown; + return appearanceStreamContent; + }, + getCA: function() { + return "8"; + }, + + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var cross = AcroFormAppearance.internal.calculateCross(formObject); + stream.push("q"); + stream.push( + "1 1 " + + f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + + " re" + ); + stream.push("W"); + stream.push("n"); + stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); + stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); + stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); + stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var cross = AcroFormAppearance.internal.calculateCross(formObject); + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + stream.push("q"); + stream.push( + "1 1 " + + f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + + " re" + ); + stream.push("W"); + stream.push("n"); + stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); + stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); + stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); + stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; } + } + }, - while (true) { - obj = (cnt < 0) ? this.object : (this.arr[cnt])[this.indx[cnt]]; + /** + * Returns the standard Appearance + * + * @returns {AcroFormXObject} + */ + createDefaultAppearanceStream: function(formObject) { + // Set Helvetica to Standard Font (size: auto) + // Color: Black + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var fontSize = formObject.fontSize; + var result = "/" + fontKey + " " + fontSize + " Tf " + encodedColor; + return result; + } +}; - typ = obj ? typeof obj : 'any'; +AcroFormAppearance.internal = { + Bezier_C: 0.551915024494, - if (typ === 'object') { - if (obj._typename !== undefined) { - if (isRootCollection(obj)) { - obj = obj.arr; - typ = 'array'; - } else - typ = 'any'; - } else if (Number.isInteger(obj.length) && (checkArrayPrototype(obj) > 0)) - typ = 'array'; - else - typ = 'any'; - } + calculateCross: function(formObject) { + var width = AcroFormAppearance.internal.getWidth(formObject); + var height = AcroFormAppearance.internal.getHeight(formObject); + var a = Math.min(width, height); - if (this.select[cnt + 1] === '$self$') { - this.value = obj; - this.fastindx = this.fastlimit = 0; - this.cnt = cnt + 1; - return true; - } + var cross = { + x1: { + // upperLeft + x: (width - a) / 2, + y: (height - a) / 2 + a // height - borderPadding + }, + x2: { + // lowerRight + x: (width - a) / 2 + a, + y: (height - a) / 2 // borderPadding + }, + x3: { + // lowerLeft + x: (width - a) / 2, + y: (height - a) / 2 // borderPadding + }, + x4: { + // upperRight + x: (width - a) / 2 + a, + y: (height - a) / 2 + a // height - borderPadding + } + }; - if ((typ === 'any') && isStr(this.select[cnt + 1])) { - // this is extraction of the member from arbitrary class - this.arr[++cnt] = obj; - this.indx[cnt] = this.select[cnt]; // use member name as index - continue; - } + return cross; + } +}; +AcroFormAppearance.internal.getWidth = function(formObject) { + var result = 0; + if (typeof formObject === "object") { + result = scale(formObject.Rect[2]); + } + return result; +}; +AcroFormAppearance.internal.getHeight = function(formObject) { + var result = 0; + if (typeof formObject === "object") { + result = scale(formObject.Rect[3]); + } + return result; +}; - if ((typ === 'array') && ((obj.length > 0) || (this.select[cnt + 1] === '$size$'))) { - this.arr[++cnt] = obj; - switch (this.select[cnt]) { - case undefined: this.indx[cnt] = 0; break; - case '$last$': this.indx[cnt] = obj.length - 1; break; - case '$size$': - this.value = obj.length; - this.fastindx = this.fastlimit = 0; - this.cnt = cnt; - return true; - default: - if (Number.isInteger(this.select[cnt])) { - this.indx[cnt] = this.select[cnt]; - if (this.indx[cnt] < 0) this.indx[cnt] = obj.length - 1; - } else { - // this is compile variable as array index - can be any expression - this.select[cnt].produce(this.tgtobj); - this.indx[cnt] = Math.round(this.select[cnt].get(0)); - } - } - } else { - if (cnt < 0) return false; +// Public: - this.value = obj; - if (this.select[cnt] === undefined) { - this.fastarr = this.arr[cnt]; - this.fastindx = this.indx[cnt]; - this.fastlimit = this.fastarr.length; - } else - this.fastindx = this.fastlimit = 0; // no any iteration on that level +/** + * Add an AcroForm-Field to the jsPDF-instance + * + * @name addField + * @function + * @instance + * @param {Object} fieldObject + * @returns {jsPDF} + */ +var addField = (jsPDFAPI.addField = function(fieldObject) { + initializeAcroForm(this, fieldObject); - this.cnt = cnt; - return true; - } - } + if (fieldObject instanceof AcroFormField) { + putForm(fieldObject); + } else { + throw new Error("Invalid argument passed to jsPDF.addField."); + } + fieldObject.page = fieldObject.scope.internal.getCurrentPageInfo().pageNumber; + return this; +}); - // unreachable code - // return false; - } +jsPDFAPI.AcroFormChoiceField = AcroFormChoiceField; +jsPDFAPI.AcroFormListBox = AcroFormListBox; +jsPDFAPI.AcroFormComboBox = AcroFormComboBox; +jsPDFAPI.AcroFormEditBox = AcroFormEditBox; +jsPDFAPI.AcroFormButton = AcroFormButton; +jsPDFAPI.AcroFormPushButton = AcroFormPushButton; +jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton; +jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox; +jsPDFAPI.AcroFormTextField = AcroFormTextField; +jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField; +jsPDFAPI.AcroFormAppearance = AcroFormAppearance; + +jsPDFAPI.AcroForm = { + ChoiceField: AcroFormChoiceField, + ListBox: AcroFormListBox, + ComboBox: AcroFormComboBox, + EditBox: AcroFormEditBox, + Button: AcroFormButton, + PushButton: AcroFormPushButton, + RadioButton: AcroFormRadioButton, + CheckBox: AcroFormCheckBox, + TextField: AcroFormTextField, + PasswordField: AcroFormPasswordField, + Appearance: AcroFormAppearance +}; - /** @summary reset iterator */ - reset() { - this.arr = []; - this.indx = []; - delete this.fastarr; - this.cnt = -1; - this.value = 0; - } +jsPDF.AcroForm = { + ChoiceField: AcroFormChoiceField, + ListBox: AcroFormListBox, + ComboBox: AcroFormComboBox, + EditBox: AcroFormEditBox, + Button: AcroFormButton, + PushButton: AcroFormPushButton, + RadioButton: AcroFormRadioButton, + CheckBox: AcroFormCheckBox, + TextField: AcroFormTextField, + PasswordField: AcroFormPasswordField, + Appearance: AcroFormAppearance +}; -} // class ArrayIterator +/** @license + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function(jsPDFAPI) { + + var namespace = "addImage_"; + jsPDFAPI.__addimage__ = {}; + + var UNKNOWN = "UNKNOWN"; + + // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow. + // With too small batch iteration will be slow as more calls are made, + // higher values cause larger and slower garbage collection. + var ARRAY_APPLY_BATCH = 8192; + + var imageFileTypeHeaders = { + PNG: [[0x89, 0x50, 0x4e, 0x47]], + TIFF: [ + [0x4d, 0x4d, 0x00, 0x2a], //Motorola + [0x49, 0x49, 0x2a, 0x00] //Intel + ], + JPEG: [ + [ + 0xff, + 0xd8, + 0xff, + 0xe0, + undefined, + undefined, + 0x4a, + 0x46, + 0x49, + 0x46, + 0x00 + ], //JFIF + [ + 0xff, + 0xd8, + 0xff, + 0xe1, + undefined, + undefined, + 0x45, + 0x78, + 0x69, + 0x66, + 0x00, + 0x00 + ], //Exif + [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW + [0xff, 0xd8, 0xff, 0xee] //EXIF RAW + ], + JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]], + GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]], + GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]], + WEBP: [ + [ + 0x52, + 0x49, + 0x46, + 0x46, + undefined, + undefined, + undefined, + undefined, + 0x57, + 0x45, + 0x42, + 0x50 + ] + ], + BMP: [ + [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc. + [0x42, 0x41], //BA - OS/2 struct bitmap array + [0x43, 0x49], //CI - OS/2 struct color icon + [0x43, 0x50], //CP - OS/2 const color pointer + [0x49, 0x43], //IC - OS/2 struct icon + [0x50, 0x54] //PT - OS/2 pointer + ] + }; + /** + * Recognize filetype of Image by magic-bytes + * + * https://en.wikipedia.org/wiki/List_of_file_signatures + * + * @name getImageFileTypeByImageData + * @public + * @function + * @param {string|arraybuffer} imageData imageData as binary String or arraybuffer + * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG' + * + * @returns {string} filetype of Image + */ + var getImageFileTypeByImageData = (jsPDFAPI.__addimage__.getImageFileTypeByImageData = function( + imageData, + fallbackFormat + ) { + fallbackFormat = fallbackFormat || UNKNOWN; + var i; + var j; + var result = UNKNOWN; + var headerSchemata; + var compareResult; + var fileType; + + if ( + fallbackFormat === "RGBA" || + (imageData.data !== undefined && + imageData.data instanceof Uint8ClampedArray && + "height" in imageData && + "width" in imageData) + ) { + return "RGBA"; + } -/** @summary return class name of the object, stored in the branch - * @private */ -function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = false) { - if (!branch || (branch._typename !== clTBranchElement)) return ''; + if (isArrayBufferView(imageData)) { + for (fileType in imageFileTypeHeaders) { + headerSchemata = imageFileTypeHeaders[fileType]; + for (i = 0; i < headerSchemata.length; i += 1) { + compareResult = true; + for (j = 0; j < headerSchemata[i].length; j += 1) { + if (headerSchemata[i][j] === undefined) { + continue; + } + if (headerSchemata[i][j] !== imageData[j]) { + compareResult = false; + break; + } + } + if (compareResult === true) { + result = fileType; + break; + } + } + } + } else { + for (fileType in imageFileTypeHeaders) { + headerSchemata = imageFileTypeHeaders[fileType]; + for (i = 0; i < headerSchemata.length; i += 1) { + compareResult = true; + for (j = 0; j < headerSchemata[i].length; j += 1) { + if (headerSchemata[i][j] === undefined) { + continue; + } + if (headerSchemata[i][j] !== imageData.charCodeAt(j)) { + compareResult = false; + break; + } + } + if (compareResult === true) { + result = fileType; + break; + } + } + } + } - if ((branch.fType === kLeafNode) && (branch.fID === -2) && (branch.fStreamerType === -1)) { - // object where all sub-branches will be collected - return branch.fClassName; - } + if (result === UNKNOWN && fallbackFormat !== UNKNOWN) { + result = fallbackFormat; + } + return result; + }); - if (with_clones && branch.fClonesName && ((branch.fType === kClonesNode) || (branch.fType === kSTLNode))) - return branch.fClonesName; + // Image functionality ported from pdf.js + var putImage = function(image) { + var out = this.internal.write; + var putStream = this.internal.putStream; + var getFilters = this.internal.getFilters; - const s_elem = findBrachStreamerElement(branch, tree.$file); - if ((branch.fType === kBaseClassNode) && s_elem && (s_elem.fTypeName === kBaseClass)) - return s_elem.fName; + var filter = getFilters(); + while (filter.indexOf("FlateEncode") !== -1) { + filter.splice(filter.indexOf("FlateEncode"), 1); + } - if (branch.fType === kObjectNode) { - if (s_elem && ((s_elem.fType === kObject) || (s_elem.fType === kAny))) - return s_elem.fTypeName; - return clTObject; - } + image.objectId = this.internal.newObject(); + + var additionalKeyValues = []; + additionalKeyValues.push({ key: "Type", value: "/XObject" }); + additionalKeyValues.push({ key: "Subtype", value: "/Image" }); + additionalKeyValues.push({ key: "Width", value: image.width }); + additionalKeyValues.push({ key: "Height", value: image.height }); + + if (image.colorSpace === color_spaces.INDEXED) { + additionalKeyValues.push({ + key: "ColorSpace", + value: + "[/Indexed /DeviceRGB " + + // if an indexed png defines more than one colour with transparency, we've created a sMask + (image.palette.length / 3 - 1) + + " " + + ("sMask" in image && typeof image.sMask !== "undefined" + ? image.objectId + 2 + : image.objectId + 1) + + " 0 R]" + }); + } else { + additionalKeyValues.push({ + key: "ColorSpace", + value: "/" + image.colorSpace + }); + if (image.colorSpace === color_spaces.DEVICE_CMYK) { + additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" }); + } + } + additionalKeyValues.push({ + key: "BitsPerComponent", + value: image.bitsPerComponent + }); + if ( + "decodeParameters" in image && + typeof image.decodeParameters !== "undefined" + ) { + additionalKeyValues.push({ + key: "DecodeParms", + value: "<<" + image.decodeParameters + ">>" + }); + } + if ( + "transparency" in image && + Array.isArray(image.transparency) && + image.transparency.length > 0 + ) { + var transparency = "", + i = 0, + len = image.transparency.length; + for (; i < len; i++) + transparency += + image.transparency[i] + " " + image.transparency[i] + " "; + + additionalKeyValues.push({ + key: "Mask", + value: "[" + transparency + "]" + }); + } + if (typeof image.sMask !== "undefined") { + additionalKeyValues.push({ + key: "SMask", + value: image.objectId + 1 + " 0 R" + }); + } - if ((branch.fType === kLeafNode) && s_elem && with_leafs) { - if ((s_elem.fType === kObject) || (s_elem.fType === kAny)) return s_elem.fTypeName; - if (s_elem.fType === kObjectp) return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1); - } + var alreadyAppliedFilters = + typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined; - return ''; -} + putStream({ + data: image.data, + additionalKeyValues: additionalKeyValues, + alreadyAppliedFilters: alreadyAppliedFilters, + objectId: image.objectId + }); + out("endobj"); + + // Soft mask + if ("sMask" in image && typeof image.sMask !== "undefined") { + const sMaskBitsPerComponent = + image.sMaskBitsPerComponent ?? image.bitsPerComponent; + const sMask = { + width: image.width, + height: image.height, + colorSpace: "DeviceGray", + bitsPerComponent: sMaskBitsPerComponent, + data: image.sMask + }; + if ("filter" in image) { + sMask.decodeParameters = `/Predictor ${image.predictor} /Colors 1 /BitsPerComponent ${sMaskBitsPerComponent} /Columns ${image.width}`; + sMask.filter = image.filter; + } + putImage.call(this, sMask); + } -/** @summary Get branch with specified id - * @desc All sub-branches checked as well - * @return {Object} branch - * @private */ -function getTreeBranch(tree, id) { - if (!Number.isInteger(id)) return; - let res, seq = 0; - function scan(obj) { - obj?.fBranches?.arr.forEach(br => { - if (seq++ === id) res = br; - if (!res) scan(br); + //Palette + if (image.colorSpace === color_spaces.INDEXED) { + var objId = this.internal.newObject(); + //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); + //putStream(zlib.compress(img['pal'])); + putStream({ + data: arrayBufferToBinaryString(new Uint8Array(image.palette)), + objectId: objId }); - } + out("endobj"); + } + }; + var putResourcesCallback = function() { + var images = this.internal.collections[namespace + "images"]; + for (var i in images) { + putImage.call(this, images[i]); + } + }; + var putXObjectsDictCallback = function() { + var images = this.internal.collections[namespace + "images"], + out = this.internal.write, + image; + for (var i in images) { + image = images[i]; + out("/I" + image.index, image.objectId, "0", "R"); + } + }; - scan(tree); - return res; -} + var checkCompressValue = function(value) { + if (value && typeof value === "string") value = value.toUpperCase(); + return value in jsPDFAPI.image_compression ? value : image_compression.NONE; + }; + var initialize = function() { + if (!this.internal.collections[namespace + "images"]) { + this.internal.collections[namespace + "images"] = {}; + this.internal.events.subscribe("putResources", putResourcesCallback); + this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback); + } + }; -/** @summary Special branch search - * @desc Name can include extra part, which will be returned in the result - * @param {string} name - name of the branch - * @return {Object} with 'branch' and 'rest' members - * @private */ -function findBranchComplex(tree, name, lst = undefined, only_search = false) { - let top_search = false, search = name, res = null; + var getImages = function() { + var images = this.internal.collections[namespace + "images"]; + initialize.call(this); + return images; + }; + var getImageIndex = function() { + return Object.keys(this.internal.collections[namespace + "images"]).length; + }; + var notDefined = function(value) { + return typeof value === "undefined" || value === null || value.length === 0; + }; + var generateAliasFromImageData = function(imageData) { + if (typeof imageData === "string" || isArrayBufferView(imageData)) { + return sHashCode(imageData); + } else if (isArrayBufferView(imageData.data)) { + return sHashCode(imageData.data); + } - if (!lst) { - top_search = true; - lst = tree.fBranches; - const pos = search.indexOf('['); - if (pos > 0) search = search.slice(0, pos); - } + return null; + }; - if (!lst || (lst.arr.length === 0)) return null; + var isImageTypeSupported = function(type) { + return typeof jsPDFAPI["process" + type.toUpperCase()] === "function"; + }; - for (let n = 0; n < lst.arr.length; ++n) { - let brname = lst.arr[n].fName; - if (brname[brname.length - 1] === ']') - brname = brname.slice(0, brname.indexOf('[')); + var isDOMElement = function(object) { + return typeof object === "object" && object.nodeType === 1; + }; - // special case when branch name includes STL map name - if ((search.indexOf(brname) !== 0) && (brname.indexOf('<') > 0)) { - const p1 = brname.indexOf('<'), p2 = brname.lastIndexOf('>'); - brname = brname.slice(0, p1) + brname.slice(p2 + 1); - } + var getImageDataFromElement = function(element, format) { + //if element is an image which uses data url definition, just return the dataurl + if (element.nodeName === "IMG" && element.hasAttribute("src")) { + var src = "" + element.getAttribute("src"); - if (brname === search) { res = { branch: lst.arr[n], rest: '' }; break; } + //is base64 encoded dataUrl, directly process it + if (src.indexOf("data:image/") === 0) { + return atob$1( + unescape(src) + .split("base64,") + .pop() + ); + } - if (search.indexOf(brname) !== 0) continue; + //it is probably an url, try to load it + var tmpImageData = jsPDFAPI.loadFile(src, true); + if (tmpImageData !== undefined) { + return tmpImageData; + } + } - // this is a case when branch name is in the begin of the search string + if (element.nodeName === "CANVAS") { + if (element.width === 0 || element.height === 0) { + throw new Error( + "Given canvas must have data. Canvas width: " + + element.width + + ", height: " + + element.height + ); + } + var mimeType; + switch (format) { + case "PNG": + mimeType = "image/png"; + break; + case "WEBP": + mimeType = "image/webp"; + break; + case "JPEG": + case "JPG": + default: + mimeType = "image/jpeg"; + break; + } + return atob$1( + element + .toDataURL(mimeType, 1.0) + .split("base64,") + .pop() + ); + } + }; - // check where point is - let pnt = brname.length; - if (brname[pnt - 1] === '.') pnt--; - if (search[pnt] !== '.') continue; + var checkImagesForAlias = function(alias) { + var images = this.internal.collections[namespace + "images"]; + if (images) { + for (var e in images) { + if (alias === images[e].alias) { + return images[e]; + } + } + } + }; - res = findBranchComplex(tree, search, lst.arr[n].fBranches); - if (!res) res = findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches); + var determineWidthAndHeight = function(width, height, image) { + if (!width && !height) { + width = -96; + height = -96; + } + if (width < 0) { + width = (-1 * image.width * 72) / width / this.internal.scaleFactor; + } + if (height < 0) { + height = (-1 * image.height * 72) / height / this.internal.scaleFactor; + } + if (width === 0) { + width = (height * image.width) / image.height; + } + if (height === 0) { + height = (width * image.height) / image.width; + } - if (!res) res = { branch: lst.arr[n], rest: search.slice(pnt) }; + return [width, height]; + }; - break; - } + var writeImageToPDF = function(x, y, width, height, image, rotation) { + var dims = determineWidthAndHeight.call(this, width, height, image), + coord = this.internal.getCoordinateString, + vcoord = this.internal.getVerticalCoordinateString; - if (top_search && !only_search && !res && (search.indexOf('br_') === 0)) { - let p = 3; - while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) ++p; - const br = (p > 3) ? getTreeBranch(tree, parseInt(search.slice(3, p))) : null; - if (br) res = { branch: br, rest: search.slice(p) }; - } + var images = getImages.call(this); - if (!top_search || !res) return res; + width = dims[0]; + height = dims[1]; + images[image.index] = image; - if (name.length > search.length) res.rest += name.slice(search.length); + if (rotation) { + rotation *= Math.PI / 180; + var c = Math.cos(rotation); + var s = Math.sin(rotation); + //like in pdf Reference do it 4 digits instead of 2 + var f4 = function(number) { + return number.toFixed(4); + }; + var rotationTransformationMatrix = [ + f4(c), + f4(s), + f4(s * -1), + f4(c), + 0, + 0, + "cm" + ]; + } + this.internal.write("q"); //Save graphics state + if (rotation) { + this.internal.write( + [1, "0", "0", 1, coord(x), vcoord(y + height), "cm"].join(" ") + ); //Translate + this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate + this.internal.write( + [coord(width), "0", "0", coord(height), "0", "0", "cm"].join(" ") + ); //Scale + } else { + this.internal.write( + [ + coord(width), + "0", + "0", + coord(height), + coord(x), + vcoord(y + height), + "cm" + ].join(" ") + ); //Translate and Scale + } - return res; -} + if (this.isAdvancedAPI()) { + // draw image bottom up when in "advanced" API mode + this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" ")); + } + this.internal.write("/I" + image.index + " Do"); //Paint Image + this.internal.write("Q"); //Restore graphics state + }; -/** @summary Search branch with specified name - * @param {string} name - name of the branch - * @return {Object} found branch - * @private */ -function findBranch(tree, name) { - const res = findBranchComplex(tree, name, tree.fBranches, true); - return (!res || res.rest) ? null : res.branch; -} + /** + * COLOR SPACES + */ + var color_spaces = (jsPDFAPI.color_spaces = { + DEVICE_RGB: "DeviceRGB", + DEVICE_GRAY: "DeviceGray", + DEVICE_CMYK: "DeviceCMYK", + CAL_GREY: "CalGray", + CAL_RGB: "CalRGB", + LAB: "Lab", + ICC_BASED: "ICCBased", + INDEXED: "Indexed", + PATTERN: "Pattern", + SEPARATION: "Separation", + DEVICE_N: "DeviceN" + }); + /** + * DECODE METHODS + */ + jsPDFAPI.decode = { + DCT_DECODE: "DCTDecode", + FLATE_DECODE: "FlateDecode", + LZW_DECODE: "LZWDecode", + JPX_DECODE: "JPXDecode", + JBIG2_DECODE: "JBIG2Decode", + ASCII85_DECODE: "ASCII85Decode", + ASCII_HEX_DECODE: "ASCIIHexDecode", + RUN_LENGTH_DECODE: "RunLengthDecode", + CCITT_FAX_DECODE: "CCITTFaxDecode" + }; -/** summary Returns number of branches in the TTree - * desc Checks also sub-branches in the branches - * return {number} number of branches - * private -function getNumBranches(tree) { - function count(obj) { - if (!obj?.fBranches) return 0; - let nchld = 0; - obj.fBranches.arr.forEach(sub => { nchld += count(sub); }); - return obj.fBranches.arr.length + nchld; - } + /** + * IMAGE COMPRESSION TYPES + */ + var image_compression = (jsPDFAPI.image_compression = { + NONE: "NONE", + FAST: "FAST", + MEDIUM: "MEDIUM", + SLOW: "SLOW" + }); - return count(tree); -} -*/ + /** + * @name sHashCode + * @function + * @param {string} data + * @returns {string} + */ + var sHashCode = (jsPDFAPI.__addimage__.sHashCode = function(data) { + var hash = 0, + i, + len; + + if (typeof data === "string") { + len = data.length; + for (i = 0; i < len; i++) { + hash = (hash << 5) - hash + data.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + } else if (isArrayBufferView(data)) { + len = data.byteLength / 2; + for (i = 0; i < len; i++) { + hash = (hash << 5) - hash + data[i]; + hash |= 0; // Convert to 32bit integer + } + } + return hash; + }); -/** - * @summary object with single variable in TTree::Draw expression - * - * @private - */ + /** + * Validates if given String is a valid Base64-String + * + * @name validateStringAsBase64 + * @public + * @function + * @param {String} possible Base64-String + * + * @returns {boolean} + */ + var validateStringAsBase64 = (jsPDFAPI.__addimage__.validateStringAsBase64 = function( + possibleBase64String + ) { + possibleBase64String = possibleBase64String || ""; + possibleBase64String.toString().trim(); -class TDrawVariable { + var result = true; - /** @summary constructor */ - constructor(globals) { - this.globals = globals; + if (possibleBase64String.length === 0) { + result = false; + } - this.code = ''; - this.brindex = []; // index of used branches from selector - this.branches = []; // names of branches in target object - this.brarray = []; // array specifier for each branch - this.func = null; // generic function for variable calculation + if (possibleBase64String.length % 4 !== 0) { + result = false; + } - this.kind = undefined; - this.buf = []; // buffer accumulates temporary values - } + if ( + /^[A-Za-z0-9+/]+$/.test( + possibleBase64String.substr(0, possibleBase64String.length - 2) + ) === false + ) { + result = false; + } - /** @summary Parse variable - * @desc when only_branch specified, its placed in the front of the expression */ - parse(tree, selector, code, only_branch, branch_mode) { - const is_start_symbol = symb => { - if ((symb >= 'A') && (symb <= 'Z')) return true; - if ((symb >= 'a') && (symb <= 'z')) return true; - return (symb === '_'); - }, is_next_symbol = symb => { - if (is_start_symbol(symb)) return true; - if ((symb >= '0') && (symb <= '9')) return true; - return false; - }; + if ( + /^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test( + possibleBase64String.substr(-2) + ) === false + ) { + result = false; + } + return result; + }); - if (!code) code = ''; // should be empty string at least + /** + * Strips out and returns info from a valid base64 data URI + * + * @name extractImageFromDataUrl + * @function + * @param {string} dataUrl a valid data URI of format 'data:[][;base64],' + * @returns {string} The raw Base64-encoded data. + */ + var extractImageFromDataUrl = (jsPDFAPI.__addimage__.extractImageFromDataUrl = function( + dataUrl + ) { + if (dataUrl == null) { + return null; + } - this.code = (only_branch?.fName ?? '') + code; + // avoid using a regexp for parsing because it might be vulnerable against ReDoS attacks - let pos = 0, pos2 = 0, br = null; - while ((pos < code.length) || only_branch) { - let arriter = []; + dataUrl = dataUrl.trim(); - if (only_branch) { - br = only_branch; - only_branch = undefined; - } else { - // first try to find branch - pos2 = pos; - while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) pos2++; - if (code[pos2] === '$') { - let repl = ''; - switch (code.slice(pos, pos2)) { - case 'LocalEntry': - case 'Entry': repl = 'arg.$globals.entry'; break; - case 'Entries': repl = 'arg.$globals.entries'; break; - } - if (repl) { - code = code.slice(0, pos) + repl + code.slice(pos2 + 1); - pos = pos + repl.length; - continue; - } - } + if (!dataUrl.startsWith("data:")) { + return null; + } - br = findBranchComplex(tree, code.slice(pos, pos2)); - if (!br) { pos = pos2 + 1; continue; } + const commaIndex = dataUrl.indexOf(","); + if (commaIndex < 0) { + return null; + } - // when full id includes branch name, replace only part of extracted expression - if (br.branch && (br.rest !== undefined)) { - pos2 -= br.rest.length; - branch_mode = undefined; // maybe selection of the sub-object done - br = br.branch; - } + const dataScheme = dataUrl.substring(0, commaIndex).trim(); + if (!dataScheme.endsWith("base64")) { + return null; + } - // when code ends with the point - means object itself will be accessed - // sometime branch name itself ends with the point - if ((pos2 >= code.length - 1) && (code[code.length - 1] === '.')) { - arriter.push('$self$'); - pos2 = code.length; - } - } + return dataUrl.substring(commaIndex + 1); + }); - // now extract all levels of iterators - while (pos2 < code.length) { - if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && (arriter.length === 0)) { - pos2 += 5; - branch_mode = true; - break; - } + /** + * Tests supplied object to determine if ArrayBuffer + * + * @name isArrayBuffer + * @function + * @param {Object} object an Object + * + * @returns {boolean} + */ + jsPDFAPI.__addimage__.isArrayBuffer = function(object) { + return object instanceof ArrayBuffer; + }; - if (code[pos2] === '.') { - // this is object member - const prev = ++pos2; + /** + * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface + * + * @name isArrayBufferView + * @function + * @param {Object} object an Object + * @returns {boolean} + */ + var isArrayBufferView = (jsPDFAPI.__addimage__.isArrayBufferView = function( + object + ) { + return ( + object instanceof Int8Array || + object instanceof Uint8Array || + object instanceof Uint8ClampedArray || + object instanceof Int16Array || + object instanceof Uint16Array || + object instanceof Int32Array || + object instanceof Uint32Array || + object instanceof Float32Array || + object instanceof Float64Array + ); + }); - if ((code[prev] === '@') && (code.slice(prev, prev + 5) === '@size')) { - arriter.push('$size$'); - pos2 += 5; - break; - } + /** + * Convert Binary String to ArrayBuffer + * + * @name binaryStringToUint8Array + * @public + * @function + * @param {string} BinaryString with ImageData + * @returns {Uint8Array} + */ + var binaryStringToUint8Array = (jsPDFAPI.__addimage__.binaryStringToUint8Array = function( + binary_string + ) { + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }); - if (!is_start_symbol(code[prev])) { - arriter.push('$self$'); // last point means extraction of object itself - break; - } + /** + * Convert the Buffer to a Binary String + * + * @name arrayBufferToBinaryString + * @public + * @function + * @param {ArrayBuffer|ArrayBufferView} ArrayBuffer buffer or bufferView with ImageData + * + * @returns {String} + */ + var arrayBufferToBinaryString = (jsPDFAPI.__addimage__.arrayBufferToBinaryString = function( + buffer + ) { + var out = ""; + // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView. + // Do not copy the array if input is already an array. + var buf = isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer); + for (var i = 0; i < buf.length; i += ARRAY_APPLY_BATCH) { + // Limit the amount of characters being parsed to prevent overflow. + // Note that while TextDecoder would be faster, it does not have the same + // functionality as fromCharCode with any provided encodings as of 3/2021. + out += String.fromCharCode.apply( + null, + buf.subarray(i, i + ARRAY_APPLY_BATCH) + ); + } + return out; + }); - while ((pos2 < code.length) && is_next_symbol(code[pos2])) pos2++; + /** + * Possible parameter for addImage, an RGBA buffer with size. + * + * @typedef {Object} RGBAData + * @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData. + * @property {number} width - Image width as the data does not carry this information in itself. + * @property {number} height - Image height as the data does not carry this information in itself. + */ - // this is looks like function call - do not need to extract member with - if (code[pos2] === '(') { pos2 = prev - 1; break; } + /** + * Adds an Image to the PDF. + * + * @name addImage + * @public + * @function + * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData). + * @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP' + * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} width width of the image (in units declared at inception of PDF document) + * @param {number} height height of the Image (in units declared at inception of PDF document) + * @param {string} alias alias of the image (if used multiple times) + * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW' + * @param {number} rotation rotation of the image in degrees (0-359) + * + * @returns jsPDF + */ + jsPDFAPI.addImage = function() { + var imageData, format, x, y, w, h, alias, compression, rotation; + + imageData = arguments[0]; + if (typeof arguments[1] === "number") { + format = UNKNOWN; + x = arguments[1]; + y = arguments[2]; + w = arguments[3]; + h = arguments[4]; + alias = arguments[5]; + compression = arguments[6]; + rotation = arguments[7]; + } else { + format = arguments[1]; + x = arguments[2]; + y = arguments[3]; + w = arguments[4]; + h = arguments[5]; + alias = arguments[6]; + compression = arguments[7]; + rotation = arguments[8]; + } - // this is selection of member, but probably we need to activate iterator for ROOT collection - if (arriter.length === 0) { - // TODO: if selected member is simple data type - no need to make other checks - just break here - if ((br.fType === kClonesNode) || (br.fType === kSTLNode)) - arriter.push(undefined); - else { - const objclass = getBranchObjectClass(br, tree, false, true); - if (objclass && isRootCollection(null, objclass)) - arriter.push(undefined); - } - } - arriter.push(code.slice(prev, pos2)); - continue; - } + if ( + typeof imageData === "object" && + !isDOMElement(imageData) && + "imageData" in imageData + ) { + var options = imageData; + + imageData = options.imageData; + format = options.format || format || UNKNOWN; + x = options.x || x || 0; + y = options.y || y || 0; + w = options.w || options.width || w; + h = options.h || options.height || h; + alias = options.alias || alias; + compression = options.compression || compression; + rotation = options.rotation || options.angle || rotation; + } - if (code[pos2] !== '[') break; + //If compression is not explicitly set, determine if we should use compression + var filter = this.internal.getFilters(); + if (compression === undefined && filter.indexOf("FlateEncode") !== -1) { + compression = "SLOW"; + } - // simple [] - if (code[pos2 + 1] === ']') { arriter.push(undefined); pos2 += 2; continue; } + if (isNaN(x) || isNaN(y)) { + throw new Error("Invalid coordinates passed to jsPDF.addImage"); + } - const prev = pos2++; - let cnt = 0; - while ((pos2 < code.length) && ((code[pos2] !== ']') || (cnt > 0))) { - if (code[pos2] === '[') cnt++; else if (code[pos2] === ']') cnt--; - pos2++; - } - const sub = code.slice(prev + 1, pos2); - switch (sub) { - case '': - case '$all$': arriter.push(undefined); break; - case '$last$': arriter.push('$last$'); break; - case '$size$': arriter.push('$size$'); break; - case '$first$': arriter.push(0); break; - default: - if (Number.isInteger(parseInt(sub))) - arriter.push(parseInt(sub)); - else { - // try to compile code as draw variable - const subvar = new TDrawVariable(this.globals); - if (!subvar.parse(tree, selector, sub)) return false; - arriter.push(subvar); - } - } - pos2++; - } + initialize.call(this); - if (arriter.length === 0) - arriter = undefined; - else if ((arriter.length === 1) && (arriter[0] === undefined)) - arriter = true; + var image = processImageData.call( + this, + imageData, + format, + alias, + compression + ); - let indx = selector.indexOfBranch(br); - if (indx < 0) indx = selector.addBranch(br, undefined, branch_mode); + writeImageToPDF.call(this, x, y, w, h, image, rotation); - branch_mode = undefined; + return this; + }; - this.brindex.push(indx); - this.branches.push(selector.nameOfBranch(indx)); - this.brarray.push(arriter); + var processImageData = function(imageData, format, alias, compression) { + var result, dataAsBinaryString; - // this is simple case of direct usage of the branch - if ((pos === 0) && (pos2 === code.length) && (this.branches.length === 1)) { - this.direct_branch = true; // remember that branch read as is - return true; - } + if ( + typeof imageData === "string" && + getImageFileTypeByImageData(imageData) === UNKNOWN + ) { + imageData = unescape(imageData); + var tmpImageData = convertBase64ToBinaryString(imageData, false); - const replace = 'arg.var' + (this.branches.length - 1); - code = code.slice(0, pos) + replace + code.slice(pos2); - pos = pos + replace.length; + if (tmpImageData !== "") { + imageData = tmpImageData; + } else { + tmpImageData = jsPDFAPI.loadFile(imageData, true); + if (tmpImageData !== undefined) { + imageData = tmpImageData; + } } + } - // support usage of some standard TMath functions - code = code.replace(/TMath::Exp\(/g, 'Math.exp(') - .replace(/TMath::Abs\(/g, 'Math.abs(') - .replace(/TMath::Prob\(/g, 'arg.$math.Prob(') - .replace(/TMath::Gaus\(/g, 'arg.$math.Gaus('); - - this.func = new Function('arg', `return (${code})`); + if (isDOMElement(imageData)) { + imageData = getImageDataFromElement(imageData, format); + } - return true; - } + format = getImageFileTypeByImageData(imageData, format); + if (!isImageTypeSupported(format)) { + throw new Error( + "addImage does not support files of type '" + + format + + "', please ensure that a plugin for '" + + format + + "' support is added." + ); + } - /** @summary Check if it is dummy variable */ - is_dummy() { return (this.branches.length === 0) && !this.func; } + // now do the heavy lifting - /** @summary Produce variable - * @desc after reading tree braches into the object, calculate variable value */ - produce(obj) { - this.length = 1; - this.isarray = false; + if (notDefined(alias)) { + alias = generateAliasFromImageData(imageData); + } + result = checkImagesForAlias.call(this, alias); - if (this.is_dummy()) { - this.value = 1; // used as dummy weight variable - this.kind = 'number'; - return; + if (!result) { + // no need to convert if imageData is already uint8array + if (!(imageData instanceof Uint8Array) && format !== "RGBA") { + dataAsBinaryString = imageData; + imageData = binaryStringToUint8Array(imageData); } - const arg = { $globals: this.globals, $math: jsroot_math }, arrs = []; - let usearrlen = -1; - for (let n = 0; n < this.branches.length; ++n) { - const name = `var${n}`; - arg[name] = obj[this.branches[n]]; + result = this["process" + format.toUpperCase()]( + imageData, + getImageIndex.call(this), + alias, + checkCompressValue(compression), + dataAsBinaryString + ); + } - // try to check if branch is array and need to be iterated - if (this.brarray[n] === undefined) - this.brarray[n] = (checkArrayPrototype(arg[name]) > 0) || isRootCollection(arg[name]); + if (!result) { + throw new Error("An unknown error occurred whilst processing the image."); + } + return result; + }; - // no array - no pain - if (this.brarray[n] === false) continue; + /** + * @name convertBase64ToBinaryString + * @function + * @param {string} stringData + * @returns {string} binary string + */ + var convertBase64ToBinaryString = (jsPDFAPI.__addimage__.convertBase64ToBinaryString = function( + stringData, + throwError + ) { + throwError = typeof throwError === "boolean" ? throwError : true; + var imageData = ""; + var rawData; - // check if array can be used as is - one dimension and normal values - if ((this.brarray[n] === true) && (checkArrayPrototype(arg[name], true) === 2)) { - // plain array, can be used as is - arrs[n] = arg[name]; - } else { - const iter = new ArrayIterator(arg[name], this.brarray[n], obj); - arrs[n] = []; - while (iter.next()) arrs[n].push(iter.value); - } - if ((usearrlen < 0) || (usearrlen < arrs[n].length)) usearrlen = arrs[n].length; - } + if (typeof stringData === "string") { + rawData = extractImageFromDataUrl(stringData) ?? stringData; - if (usearrlen < 0) { - this.value = this.direct_branch ? arg.var0 : this.func(arg); - if (!this.kind) this.kind = typeof this.value; - return; + try { + imageData = atob$1(rawData); + } catch (e) { + if (throwError) { + if (!validateStringAsBase64(rawData)) { + throw new Error( + "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString " + ); + } else { + throw new Error( + "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message + ); + } + } } + } + return imageData; + }); - if (usearrlen === 0) { - // empty array - no any histogram should be filled - this.length = 0; - this.value = 0; - return; - } + /** + * @name getImageProperties + * @function + * @param {Object} imageData + * @returns {Object} + */ + jsPDFAPI.getImageProperties = function(imageData) { + var image; + var tmpImageData = ""; + var format; - this.length = usearrlen; - this.isarray = true; + if (isDOMElement(imageData)) { + imageData = getImageDataFromElement(imageData); + } - if (this.direct_branch) - this.value = arrs[0]; // just use array - else { - this.value = new Array(usearrlen); + if ( + typeof imageData === "string" && + getImageFileTypeByImageData(imageData) === UNKNOWN + ) { + tmpImageData = convertBase64ToBinaryString(imageData, false); - for (let k = 0; k < usearrlen; ++k) { - for (let n = 0; n < this.branches.length; ++n) { - if (arrs[n]) - arg[`var${n}`] = arrs[n][k]; - } - this.value[k] = this.func(arg); - } + if (tmpImageData === "") { + tmpImageData = jsPDFAPI.loadFile(imageData) || ""; } + imageData = tmpImageData; + } - if (!this.kind) this.kind = typeof this.value[0]; - } + format = getImageFileTypeByImageData(imageData); + if (!isImageTypeSupported(format)) { + throw new Error( + "addImage does not support files of type '" + + format + + "', please ensure that a plugin for '" + + format + + "' support is added." + ); + } - /** @summary Get variable */ - get(indx) { return this.isarray ? this.value[indx] : this.value; } + if (!(imageData instanceof Uint8Array)) { + imageData = binaryStringToUint8Array(imageData); + } - /** @summary Append array to the buffer */ - appendArray(tgtarr) { this.buf = this.buf.concat(tgtarr[this.branches[0]]); } + image = this["process" + format.toUpperCase()](imageData); -} // class TDrawVariable + if (!image) { + throw new Error("An unknown error occurred whilst processing the image"); + } + + image.fileType = format; + return image; + }; +})(jsPDF.API); /** - * @summary Selector class for TTree::Draw function + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv * - * @private + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license */ -class TDrawSelector extends TSelector { - - /** @summary constructor */ - constructor() { - super(); - - this.ndim = 0; - this.vars = []; // array of expression variables - this.cut = null; // cut variable - this.hist = null; - this.histo_drawopt = ''; - this.hist_name = '$htemp'; - this.hist_title = 'Result of TTree::Draw'; - this.graph = false; - this.hist_args = []; // arguments for histogram creation - this.arr_limit = 1000; // number of accumulated items before create histogram - this.htype = 'F'; - this.monitoring = 0; - this.globals = {}; // object with global parameters, which could be used in any draw expression - this.last_progress = 0; - this.aver_diff = 0; - } - - /** @summary Set draw selector callbacks */ - setCallback(result_callback, progress_callback) { - this.result_callback = result_callback; - this.progress_callback = progress_callback; - } - - /** @summary Parse parameters */ - parseParameters(tree, args, expr) { - if (!expr || !isStr(expr)) return ''; - - // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value - let pos = expr.lastIndexOf(';'); - while (pos >= 0) { - let parname = expr.slice(pos + 1), parvalue; - expr = expr.slice(0, pos); - pos = expr.lastIndexOf(';'); - - const separ = parname.indexOf(':'); - if (separ > 0) { parvalue = parname.slice(separ + 1); parname = parname.slice(0, separ); } - - let intvalue = parseInt(parvalue); - if (!parvalue || !Number.isInteger(intvalue)) intvalue = undefined; +(function(jsPDFAPI) { - switch (parname) { - case 'num': - case 'entries': - case 'numentries': - if (parvalue === 'all') - args.numentries = tree.fEntries; - else if (parvalue === 'half') - args.numentries = Math.round(tree.fEntries / 2); - else if (intvalue !== undefined) - args.numentries = intvalue; - break; - case 'first': - if (intvalue !== undefined) args.firstentry = intvalue; - break; - case 'mon': - case 'monitor': - args.monitoring = (intvalue !== undefined) ? intvalue : 5000; - break; - case 'player': - args.player = true; - break; - case 'dump': - args.dump = true; - break; - case 'maxseg': - case 'maxrange': - if (intvalue) tree.$file.fMaxRanges = intvalue; - break; - case 'accum': - if (intvalue) this.arr_limit = intvalue; - break; - case 'htype': - if (parvalue && (parvalue.length === 1)) { - this.htype = parvalue.toUpperCase(); - if (['C', 'S', 'I', 'F', 'L', 'D'].indexOf(this.htype) < 0) - this.htype = 'F'; - } - break; - case 'hbins': - this.hist_nbins = parseInt(parvalue); - if (!Number.isInteger(this.hist_nbins) || (this.hist_nbins <= 3)) - delete this.hist_nbins; - else - this.want_hist = true; - break; - case 'drawopt': - args.drawopt = parvalue; - break; - case 'graph': - args.graph = intvalue || true; - break; - } + var notEmpty = function(obj) { + if (typeof obj != "undefined") { + if (obj != "") { + return true; } + } + }; - pos = expr.lastIndexOf('>>'); - if (pos >= 0) { - let harg = expr.slice(pos + 2).trim(); - expr = expr.slice(0, pos).trim(); - pos = harg.indexOf('('); - if (pos > 0) { - this.hist_name = harg.slice(0, pos); - harg = harg.slice(pos); - } - if (harg === 'dump') - args.dump = true; - else if (harg.indexOf('Graph') === 0) - args.graph = true; - else if (pos < 0) { - this.want_hist = true; - this.hist_name = harg; - } else if ((harg[0] === '(') && (harg[harg.length - 1] === ')')) { - this.want_hist = true; - harg = harg.slice(1, harg.length - 1).split(','); - let isok = true; - for (let n = 0; n < harg.length; ++n) { - harg[n] = (n % 3 === 0) ? parseInt(harg[n]) : parseFloat(harg[n]); - if (!Number.isFinite(harg[n])) isok = false; + jsPDF.API.events.push([ + "addPage", + function(addPageData) { + var pageInfo = this.internal.getPageInfo(addPageData.pageNumber); + pageInfo.pageContext.annotations = []; + } + ]); + + jsPDFAPI.events.push([ + "putPage", + function(putPageData) { + var getHorizontalCoordinateString = this.internal.getCoordinateString; + var getVerticalCoordinateString = this.internal + .getVerticalCoordinateString; + var pageInfo = this.internal.getPageInfoByObjId(putPageData.objId); + var pageAnnos = putPageData.pageContext.annotations; + + var anno, rect, line; + var found = false; + for (var a = 0; a < pageAnnos.length && !found; a++) { + anno = pageAnnos[a]; + switch (anno.type) { + case "link": + if ( + notEmpty(anno.options.url) || + notEmpty(anno.options.pageNumber) + ) { + found = true; } - if (isok) this.hist_args = harg; - } + break; + case "reference": + case "text": + case "freetext": + found = true; + break; + } } - - if (args.dump) { - this.dump_values = true; - args.reallocate_objects = true; - if (args.numentries === undefined) args.numentries = 10; + if (found == false) { + return; } - return expr; - } + this.internal.write("/Annots ["); + for (var i = 0; i < pageAnnos.length; i++) { + anno = pageAnnos[i]; + var escape = this.internal.pdfEscape; + var encryptor = this.internal.getEncryptor(putPageData.objId); - /** @summary Parse draw expression */ - parseDrawExpression(tree, args) { - // parse complete expression - let expr = this.parseParameters(tree, args, args.expr), cut = ''; + switch (anno.type) { + case "reference": + // References to Widget Annotations (for AcroForm Fields) + this.internal.write(" " + anno.object.objId + " 0 R "); + break; + case "text": + // Create a an object for both the text and the popup + var objText = this.internal.newAdditionalObject(); + var objPopup = this.internal.newAdditionalObject(); + var encryptorText = this.internal.getEncryptor(objText.objId); + + var title = anno.title || "Note"; + rect = + "/Rect [" + + getHorizontalCoordinateString(anno.bounds.x) + + " " + + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + + " " + + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + + " " + + getVerticalCoordinateString(anno.bounds.y) + + "] "; + + line = + "<>"; + objText.content = line; + + var parent = objText.objId + " 0 R"; + var popoff = 30; + rect = + "/Rect [" + + getHorizontalCoordinateString(anno.bounds.x + popoff) + + " " + + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + + " " + + getHorizontalCoordinateString( + anno.bounds.x + anno.bounds.w + popoff + ) + + " " + + getVerticalCoordinateString(anno.bounds.y) + + "] "; + line = + "< 0) { - cut = expr.slice(pos + 2).trim(); - expr = expr.slice(0, pos).trim(); - } - } + break; + case "freetext": + rect = + "/Rect [" + + getHorizontalCoordinateString(anno.bounds.x) + + " " + + getVerticalCoordinateString(anno.bounds.y) + + " " + + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + + " " + + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + + "] "; + var color = anno.color || "#000000"; + line = + "<>"; + } else if (anno.options.pageNumber) { + // first page is 0 + var info = this.internal.getPageInfo(anno.options.pageNumber); + line = + "< prev)) names.push(expr.slice(prev, pos)); - prev = pos + 1; - break; - } + if (line != "") { + line += " >>"; + this.internal.write(line); + } + break; + } } - if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos)); + this.internal.write("]"); + } + ]); + + /** + * @name createAnnotation + * @function + * @param {Object} options + */ + jsPDFAPI.createAnnotation = function(options) { + var pageInfo = this.internal.getCurrentPageInfo(); + switch (options.type) { + case "link": + this.link( + options.bounds.x, + options.bounds.y, + options.bounds.w, + options.bounds.h, + options + ); + break; + case "text": + case "freetext": + pageInfo.pageContext.annotations.push(options); + break; + } + }; - if ((names.length < 1) || (names.length > 3)) return false; + /** + * Create a link + * + * valid options + *
  • pageNumber or url [required] + *

    If pageNumber is specified, top and zoom may also be specified

    + * @name link + * @function + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + * @param {Object} options + */ + jsPDFAPI.link = function(x, y, w, h, options) { + var pageInfo = this.internal.getCurrentPageInfo(); + var getHorizontalCoordinateString = this.internal.getCoordinateString; + var getVerticalCoordinateString = this.internal.getVerticalCoordinateString; + + pageInfo.pageContext.annotations.push({ + finalBounds: { + x: getHorizontalCoordinateString(x), + y: getVerticalCoordinateString(y), + w: getHorizontalCoordinateString(x + w), + h: getVerticalCoordinateString(y + h) + }, + options: options, + type: "link" + }); + }; - this.ndim = names.length; + /** + * Currently only supports single line text. + * Returns the width of the text/link + * + * @name textWithLink + * @function + * @param {string} text + * @param {number} x + * @param {number} y + * @param {Object} options + * @returns {number} width the width of the text/link + */ + jsPDFAPI.textWithLink = function(text, x, y, options) { + var totalLineWidth = this.getTextWidth(text); + var lineHeight = this.internal.getLineHeight() / this.internal.scaleFactor; + var linkHeight, linkWidth; + + // Checking if maxWidth option is passed to determine lineWidth and number of lines for each line + if (options.maxWidth !== undefined) { + var { maxWidth } = options; + linkWidth = maxWidth; + var numOfLines = this.splitTextToSize(text, linkWidth).length; + linkHeight = Math.ceil(lineHeight * numOfLines); + } else { + linkWidth = totalLineWidth; + linkHeight = lineHeight; + } - let is_direct = !cut; + this.text(text, x, y, options); - for (let n = 0; n < this.ndim; ++n) { - this.vars[n] = new TDrawVariable(this.globals); - if (!this.vars[n].parse(tree, this, names[n])) return false; - if (!this.vars[n].direct_branch) is_direct = false; - } + //TODO We really need the text baseline height to do this correctly. + // Or ability to draw text on top, bottom, center, or baseline. + y += lineHeight * 0.2; + //handle x position based on the align option + if (options.align === "center") { + x = x - totalLineWidth / 2; //since starting from center move the x position by half of text width + } + if (options.align === "right") { + x = x - totalLineWidth; + } + this.link(x, y - lineHeight, linkWidth, linkHeight, options); + return totalLineWidth; + }; - this.cut = new TDrawVariable(this.globals); - if (cut) - if (!this.cut.parse(tree, this, cut)) return false; + //TODO move into external library + /** + * @name getTextWidth + * @function + * @param {string} text + * @returns {number} txtWidth + */ + jsPDFAPI.getTextWidth = function(text) { + var fontSize = this.internal.getFontSize(); + var txtWidth = + (this.getStringUnitWidth(text) * fontSize) / this.internal.scaleFactor; + return txtWidth; + }; - if (!this.numBranches()) { - console.warn('no any branch is selected'); - return false; - } + return this; +})(jsPDF.API); - if (is_direct) this.ProcessArrays = this.ProcessArraysFunc; +/** + * @license + * Copyright (c) 2017 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - this.monitoring = args.monitoring; +/** + * jsPDF arabic parser PlugIn + * + * @name arabic + * @module + */ +(function(jsPDFAPI) { - // force TPolyMarker3D drawing for 3D case - if ((this.ndim === 3) && !this.want_hist && !args.dump) - args.graph = true; + /** + * Arabic shape substitutions: char code => (isolated, final, initial, medial). + * Arabic Substition A + */ + var arabicSubstitionA = { + 0x0621: [0xfe80], // ARABIC LETTER HAMZA + 0x0622: [0xfe81, 0xfe82], // ARABIC LETTER ALEF WITH MADDA ABOVE + 0x0623: [0xfe83, 0xfe84], // ARABIC LETTER ALEF WITH HAMZA ABOVE + 0x0624: [0xfe85, 0xfe86], // ARABIC LETTER WAW WITH HAMZA ABOVE + 0x0625: [0xfe87, 0xfe88], // ARABIC LETTER ALEF WITH HAMZA BELOW + 0x0626: [0xfe89, 0xfe8a, 0xfe8b, 0xfe8c], // ARABIC LETTER YEH WITH HAMZA ABOVE + 0x0627: [0xfe8d, 0xfe8e], // ARABIC LETTER ALEF + 0x0628: [0xfe8f, 0xfe90, 0xfe91, 0xfe92], // ARABIC LETTER BEH + 0x0629: [0xfe93, 0xfe94], // ARABIC LETTER TEH MARBUTA + 0x062a: [0xfe95, 0xfe96, 0xfe97, 0xfe98], // ARABIC LETTER TEH + 0x062b: [0xfe99, 0xfe9a, 0xfe9b, 0xfe9c], // ARABIC LETTER THEH + 0x062c: [0xfe9d, 0xfe9e, 0xfe9f, 0xfea0], // ARABIC LETTER JEEM + 0x062d: [0xfea1, 0xfea2, 0xfea3, 0xfea4], // ARABIC LETTER HAH + 0x062e: [0xfea5, 0xfea6, 0xfea7, 0xfea8], // ARABIC LETTER KHAH + 0x062f: [0xfea9, 0xfeaa], // ARABIC LETTER DAL + 0x0630: [0xfeab, 0xfeac], // ARABIC LETTER THAL + 0x0631: [0xfead, 0xfeae], // ARABIC LETTER REH + 0x0632: [0xfeaf, 0xfeb0], // ARABIC LETTER ZAIN + 0x0633: [0xfeb1, 0xfeb2, 0xfeb3, 0xfeb4], // ARABIC LETTER SEEN + 0x0634: [0xfeb5, 0xfeb6, 0xfeb7, 0xfeb8], // ARABIC LETTER SHEEN + 0x0635: [0xfeb9, 0xfeba, 0xfebb, 0xfebc], // ARABIC LETTER SAD + 0x0636: [0xfebd, 0xfebe, 0xfebf, 0xfec0], // ARABIC LETTER DAD + 0x0637: [0xfec1, 0xfec2, 0xfec3, 0xfec4], // ARABIC LETTER TAH + 0x0638: [0xfec5, 0xfec6, 0xfec7, 0xfec8], // ARABIC LETTER ZAH + 0x0639: [0xfec9, 0xfeca, 0xfecb, 0xfecc], // ARABIC LETTER AIN + 0x063a: [0xfecd, 0xfece, 0xfecf, 0xfed0], // ARABIC LETTER GHAIN + 0x0641: [0xfed1, 0xfed2, 0xfed3, 0xfed4], // ARABIC LETTER FEH + 0x0642: [0xfed5, 0xfed6, 0xfed7, 0xfed8], // ARABIC LETTER QAF + 0x0643: [0xfed9, 0xfeda, 0xfedb, 0xfedc], // ARABIC LETTER KAF + 0x0644: [0xfedd, 0xfede, 0xfedf, 0xfee0], // ARABIC LETTER LAM + 0x0645: [0xfee1, 0xfee2, 0xfee3, 0xfee4], // ARABIC LETTER MEEM + 0x0646: [0xfee5, 0xfee6, 0xfee7, 0xfee8], // ARABIC LETTER NOON + 0x0647: [0xfee9, 0xfeea, 0xfeeb, 0xfeec], // ARABIC LETTER HEH + 0x0648: [0xfeed, 0xfeee], // ARABIC LETTER WAW + 0x0649: [0xfeef, 0xfef0, 64488, 64489], // ARABIC LETTER ALEF MAKSURA + 0x064a: [0xfef1, 0xfef2, 0xfef3, 0xfef4], // ARABIC LETTER YEH + 0x0671: [0xfb50, 0xfb51], // ARABIC LETTER ALEF WASLA + 0x0677: [0xfbdd], // ARABIC LETTER U WITH HAMZA ABOVE + 0x0679: [0xfb66, 0xfb67, 0xfb68, 0xfb69], // ARABIC LETTER TTEH + 0x067a: [0xfb5e, 0xfb5f, 0xfb60, 0xfb61], // ARABIC LETTER TTEHEH + 0x067b: [0xfb52, 0xfb53, 0xfb54, 0xfb55], // ARABIC LETTER BEEH + 0x067e: [0xfb56, 0xfb57, 0xfb58, 0xfb59], // ARABIC LETTER PEH + 0x067f: [0xfb62, 0xfb63, 0xfb64, 0xfb65], // ARABIC LETTER TEHEH + 0x0680: [0xfb5a, 0xfb5b, 0xfb5c, 0xfb5d], // ARABIC LETTER BEHEH + 0x0683: [0xfb76, 0xfb77, 0xfb78, 0xfb79], // ARABIC LETTER NYEH + 0x0684: [0xfb72, 0xfb73, 0xfb74, 0xfb75], // ARABIC LETTER DYEH + 0x0686: [0xfb7a, 0xfb7b, 0xfb7c, 0xfb7d], // ARABIC LETTER TCHEH + 0x0687: [0xfb7e, 0xfb7f, 0xfb80, 0xfb81], // ARABIC LETTER TCHEHEH + 0x0688: [0xfb88, 0xfb89], // ARABIC LETTER DDAL + 0x068c: [0xfb84, 0xfb85], // ARABIC LETTER DAHAL + 0x068d: [0xfb82, 0xfb83], // ARABIC LETTER DDAHAL + 0x068e: [0xfb86, 0xfb87], // ARABIC LETTER DUL + 0x0691: [0xfb8c, 0xfb8d], // ARABIC LETTER RREH + 0x0698: [0xfb8a, 0xfb8b], // ARABIC LETTER JEH + 0x06a4: [0xfb6a, 0xfb6b, 0xfb6c, 0xfb6d], // ARABIC LETTER VEH + 0x06a6: [0xfb6e, 0xfb6f, 0xfb70, 0xfb71], // ARABIC LETTER PEHEH + 0x06a9: [0xfb8e, 0xfb8f, 0xfb90, 0xfb91], // ARABIC LETTER KEHEH + 0x06ad: [0xfbd3, 0xfbd4, 0xfbd5, 0xfbd6], // ARABIC LETTER NG + 0x06af: [0xfb92, 0xfb93, 0xfb94, 0xfb95], // ARABIC LETTER GAF + 0x06b1: [0xfb9a, 0xfb9b, 0xfb9c, 0xfb9d], // ARABIC LETTER NGOEH + 0x06b3: [0xfb96, 0xfb97, 0xfb98, 0xfb99], // ARABIC LETTER GUEH + 0x06ba: [0xfb9e, 0xfb9f], // ARABIC LETTER NOON GHUNNA + 0x06bb: [0xfba0, 0xfba1, 0xfba2, 0xfba3], // ARABIC LETTER RNOON + 0x06be: [0xfbaa, 0xfbab, 0xfbac, 0xfbad], // ARABIC LETTER HEH DOACHASHMEE + 0x06c0: [0xfba4, 0xfba5], // ARABIC LETTER HEH WITH YEH ABOVE + 0x06c1: [0xfba6, 0xfba7, 0xfba8, 0xfba9], // ARABIC LETTER HEH GOAL + 0x06c5: [0xfbe0, 0xfbe1], // ARABIC LETTER KIRGHIZ OE + 0x06c6: [0xfbd9, 0xfbda], // ARABIC LETTER OE + 0x06c7: [0xfbd7, 0xfbd8], // ARABIC LETTER U + 0x06c8: [0xfbdb, 0xfbdc], // ARABIC LETTER YU + 0x06c9: [0xfbe2, 0xfbe3], // ARABIC LETTER KIRGHIZ YU + 0x06cb: [0xfbde, 0xfbdf], // ARABIC LETTER VE + 0x06cc: [0xfbfc, 0xfbfd, 0xfbfe, 0xfbff], // ARABIC LETTER FARSI YEH + 0x06d0: [0xfbe4, 0xfbe5, 0xfbe6, 0xfbe7], //ARABIC LETTER E + 0x06d2: [0xfbae, 0xfbaf], // ARABIC LETTER YEH BARREE + 0x06d3: [0xfbb0, 0xfbb1] // ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + }; - this.graph = args.graph; + /* + var ligaturesSubstitutionA = { + 0xFBEA: []// ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM + }; + */ + + var ligatures = { + 0xfedf: { + 0xfe82: 0xfef5, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + 0xfe84: 0xfef7, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + 0xfe88: 0xfef9, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + 0xfe8e: 0xfefb // ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + }, + 0xfee0: { + 0xfe82: 0xfef6, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM + 0xfe84: 0xfef8, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM + 0xfe88: 0xfefa, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM + 0xfe8e: 0xfefc // ARABIC LIGATURE LAM WITH ALEF FINAL FORM + }, + 0xfe8d: { 0xfedf: { 0xfee0: { 0xfeea: 0xfdf2 } } }, // ALLAH + 0x0651: { + 0x064c: 0xfc5e, // Shadda + Dammatan + 0x064d: 0xfc5f, // Shadda + Kasratan + 0x064e: 0xfc60, // Shadda + Fatha + 0x064f: 0xfc61, // Shadda + Damma + 0x0650: 0xfc62 // Shadda + Kasra + } + }; - if (args.drawopt !== undefined) - this.histo_drawopt = args.drawopt; - else - this.histo_drawopt = (this.ndim === 2) ? 'col' : ''; + var arabic_diacritics = { + 1612: 64606, // Shadda + Dammatan + 1613: 64607, // Shadda + Kasratan + 1614: 64608, // Shadda + Fatha + 1615: 64609, // Shadda + Damma + 1616: 64610 // Shadda + Kasra + }; - return true; - } + var alfletter = [1570, 1571, 1573, 1575]; - /** @summary Draw only specified branch */ - drawOnlyBranch(tree, branch, expr, args) { - this.ndim = 1; + var noChangeInForm = -1; + var isolatedForm = 0; + var finalForm = 1; + var initialForm = 2; + var medialForm = 3; - if (expr.indexOf('dump') === 0) expr = ';' + expr; + jsPDFAPI.__arabicParser__ = {}; - expr = this.parseParameters(tree, args, expr); + //private + var isInArabicSubstitutionA = (jsPDFAPI.__arabicParser__.isInArabicSubstitutionA = function( + letter + ) { + return typeof arabicSubstitionA[letter.charCodeAt(0)] !== "undefined"; + }); - this.monitoring = args.monitoring; + var isArabicLetter = (jsPDFAPI.__arabicParser__.isArabicLetter = function( + letter + ) { + return ( + typeof letter === "string" && + /^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test( + letter + ) + ); + }); - if (args.dump) { - this.dump_values = true; - args.reallocate_objects = true; - } + var isArabicEndLetter = (jsPDFAPI.__arabicParser__.isArabicEndLetter = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length <= 2 + ); + }); - if (this.dump_values) { - this.hist = []; // array of dump objects + var isArabicAlfLetter = (jsPDFAPI.__arabicParser__.isArabicAlfLetter = function( + letter + ) { + return ( + isArabicLetter(letter) && alfletter.indexOf(letter.charCodeAt(0)) >= 0 + ); + }); - this.leaf = args.leaf; + jsPDFAPI.__arabicParser__.arabicLetterHasIsolatedForm = function(letter) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 1 + ); + }; - // branch object remains, therefore we need to copy fields to see them all - this.copy_fields = ((args.branch.fLeaves && (args.branch.fLeaves.arr.length > 1)) || - (args.branch.fBranches && (args.branch.fBranches.arr.length > 0))) && !args.leaf; + var arabicLetterHasFinalForm = (jsPDFAPI.__arabicParser__.arabicLetterHasFinalForm = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 2 + ); + }); - this.addBranch(branch, 'br0', args.direct_branch); // add branch + jsPDFAPI.__arabicParser__.arabicLetterHasInitialForm = function(letter) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 3 + ); + }; - this.Process = this.ProcessDump; + var arabicLetterHasMedialForm = (jsPDFAPI.__arabicParser__.arabicLetterHasMedialForm = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length == 4 + ); + }); - return true; + var resolveLigatures = (jsPDFAPI.__arabicParser__.resolveLigatures = function( + letters + ) { + var i = 0; + var tmpLigatures = ligatures; + var result = ""; + var effectedLetters = 0; + + for (i = 0; i < letters.length; i += 1) { + if (typeof tmpLigatures[letters.charCodeAt(i)] !== "undefined") { + effectedLetters++; + tmpLigatures = tmpLigatures[letters.charCodeAt(i)]; + + if (typeof tmpLigatures === "number") { + result += String.fromCharCode(tmpLigatures); + tmpLigatures = ligatures; + effectedLetters = 0; + } + if (i === letters.length - 1) { + tmpLigatures = ligatures; + result += letters.charAt(i - (effectedLetters - 1)); + i = i - (effectedLetters - 1); + effectedLetters = 0; + } + } else { + tmpLigatures = ligatures; + result += letters.charAt(i - effectedLetters); + i = i - effectedLetters; + effectedLetters = 0; } + } - this.vars[0] = new TDrawVariable(this.globals); - if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch)) return false; - this.hist_title = `drawing branch ${branch.fName} ${expr?' expr:'+expr:''} from ${tree.fName}`; - - this.cut = new TDrawVariable(this.globals); + return result; + }); - if (this.vars[0].direct_branch) this.ProcessArrays = this.ProcessArraysFunc; + jsPDFAPI.__arabicParser__.isArabicDiacritic = function(letter) { + return ( + letter !== undefined && + arabic_diacritics[letter.charCodeAt(0)] !== undefined + ); + }; - return true; - } + var getCorrectForm = (jsPDFAPI.__arabicParser__.getCorrectForm = function( + currentChar, + beforeChar, + nextChar + ) { + if (!isArabicLetter(currentChar)) { + return -1; + } - /** @summary Begin processing */ - Begin(tree) { - this.globals.entries = tree.fEntries; + if (isInArabicSubstitutionA(currentChar) === false) { + return noChangeInForm; + } + if ( + !arabicLetterHasFinalForm(currentChar) || + (!isArabicLetter(beforeChar) && !isArabicLetter(nextChar)) || + (!isArabicLetter(nextChar) && isArabicEndLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && !isArabicLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && isArabicAlfLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && isArabicEndLetter(beforeChar)) + ) { + return isolatedForm; + } - if (this.monitoring) - this.lasttm = new Date().getTime(); - } + if ( + arabicLetterHasMedialForm(currentChar) && + isArabicLetter(beforeChar) && + !isArabicEndLetter(beforeChar) && + isArabicLetter(nextChar) && + arabicLetterHasFinalForm(nextChar) + ) { + return medialForm; + } - /** @summary Show progress */ - ShowProgress(/* value */) {} + if (isArabicEndLetter(currentChar) || !isArabicLetter(nextChar)) { + return finalForm; + } + return initialForm; + }); - /** @summary Get bins for bits histogram */ - getBitsBins(nbits, res) { - res.nbins = res.max = nbits; - res.fLabels = create$1(clTHashList); - for (let k = 0; k < nbits; ++k) { - const s = create$1(clTObjString); - s.fString = k.toString(); - s.fUniqueID = k + 1; - res.fLabels.Add(s); + /** + * @name processArabic + * @function + * @param {string} text + * @returns {string} + */ + var parseArabic = function(text) { + text = text || ""; + + var result = ""; + var i = 0; + var j = 0; + var position = 0; + var currentLetter = ""; + var prevLetter = ""; + var nextLetter = ""; + + var words = text.split("\\s+"); + var newWords = []; + for (i = 0; i < words.length; i += 1) { + newWords.push(""); + for (j = 0; j < words[i].length; j += 1) { + currentLetter = words[i][j]; + prevLetter = words[i][j - 1]; + nextLetter = words[i][j + 1]; + if (isArabicLetter(currentLetter)) { + position = getCorrectForm(currentLetter, prevLetter, nextLetter); + if (position !== -1) { + newWords[i] += String.fromCharCode( + arabicSubstitionA[currentLetter.charCodeAt(0)][position] + ); + } else { + newWords[i] += currentLetter; + } + } else { + newWords[i] += currentLetter; + } } - return res; - } - /** @summary Get min.max bins */ - getMinMaxBins(axisid, nbins) { - const res = { min: 0, max: 0, nbins, k: 1, fLabels: null, title: '' }; - if (axisid >= this.ndim) return res; + newWords[i] = resolveLigatures(newWords[i]); + } + result = newWords.join(" "); - const arr = this.vars[axisid].buf; + return result; + }; - res.title = this.vars[axisid].code || ''; + var processArabic = (jsPDFAPI.__arabicParser__.processArabic = jsPDFAPI.processArabic = function() { + var text = + typeof arguments[0] === "string" ? arguments[0] : arguments[0].text; + var tmpText = []; + var result; + + if (Array.isArray(text)) { + var i = 0; + tmpText = []; + for (i = 0; i < text.length; i += 1) { + if (Array.isArray(text[i])) { + tmpText.push([parseArabic(text[i][0]), text[i][1], text[i][2]]); + } else { + tmpText.push([parseArabic(text[i])]); + } + } + result = tmpText; + } else { + result = parseArabic(text); + } + if (typeof arguments[0] === "string") { + return result; + } else { + arguments[0].text = result; + return arguments[0]; + } + }); - if (this.vars[axisid].kind === 'object') { - // this is any object type - let typename, similar = true, maxbits = 8; - for (let k = 0; k < arr.length; ++k) { - if (!arr[k]) continue; - if (!typename) typename = arr[k]._typename; - if (typename !== arr[k]._typename) similar = false; // check all object types - if (arr[k].fNbits) maxbits = Math.max(maxbits, arr[k].fNbits + 1); - } + jsPDFAPI.events.push(["preProcessText", processArabic]); +})(jsPDF.API); - if (typename && similar) { - if ((typename === 'TBits') && (axisid === 0)) { - this.fill1DHistogram = this.fillTBitsHistogram; - if (maxbits % 8) maxbits = (maxbits & 0xfff0) + 8; +/** @license + * jsPDF Autoprint Plugin + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - if ((this.hist_name === 'bits') && (this.hist_args.length === 1) && this.hist_args[0]) - maxbits = this.hist_args[0]; +/** + * @name autoprint + * @module + */ +(function(jsPDFAPI) { + + /** + * Makes the PDF automatically open the print-Dialog when opened in a PDF-viewer. + * + * @name autoPrint + * @function + * @param {Object} options (optional) Set the attribute variant to 'non-conform' (default) or 'javascript' to activate different methods of automatic printing when opening in a PDF-viewer . + * @returns {jsPDF} + * @example + * var doc = new jsPDF(); + * doc.text(10, 10, 'This is a test'); + * doc.autoPrint({variant: 'non-conform'}); + * doc.save('autoprint.pdf'); + */ + jsPDFAPI.autoPrint = function(options) { + var refAutoPrintTag; + options = options || {}; + options.variant = options.variant || "non-conform"; + + switch (options.variant) { + case "javascript": + //https://github.com/Rob--W/pdf.js/commit/c676ecb5a0f54677b9f3340c3ef2cf42225453bb + this.addJS("print({});"); + break; + case "non-conform": + default: + this.internal.events.subscribe("postPutResources", function() { + refAutoPrintTag = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/S /Named"); + this.internal.out("/Type /Action"); + this.internal.out("/N /Print"); + this.internal.out(">>"); + this.internal.out("endobj"); + }); - return this.getBitsBins(maxbits, res); - } - } - } + this.internal.events.subscribe("putCatalog", function() { + this.internal.out("/OpenAction " + refAutoPrintTag + " 0 R"); + }); + break; + } + return this; + }; +})(jsPDF.API); - if (this.vars[axisid].kind === 'string') { - res.lbls = []; // all labels +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - for (let k = 0; k < arr.length; ++k) { - if (res.lbls.indexOf(arr[k]) < 0) - res.lbls.push(arr[k]); - } +/** + * jsPDF Canvas PlugIn + * This plugin mimics the HTML5 Canvas + * + * The goal is to provide a way for current canvas users to print directly to a PDF. + * @name canvas + * @module + */ +(function(jsPDFAPI) { - res.lbls.sort(); - res.max = res.nbins = res.lbls.length; + /** + * @class Canvas + * @classdesc A Canvas Wrapper for jsPDF + */ + var Canvas = function() { + var jsPdfInstance = undefined; + Object.defineProperty(this, "pdf", { + get: function() { + return jsPdfInstance; + }, + set: function(value) { + jsPdfInstance = value; + } + }); - res.fLabels = create$1(clTHashList); - for (let k = 0; k < res.lbls.length; ++k) { - const s = create$1(clTObjString); - s.fString = res.lbls[k]; - s.fUniqueID = k + 1; - if (s.fString === '') s.fString = ''; - res.fLabels.Add(s); - } - } else if ((axisid === 0) && (this.hist_name === 'bits') && (this.hist_args.length <= 1)) { - this.fill1DHistogram = this.FillBitsHistogram; - return this.getBitsBins(this.hist_args[0] || 32, res); - } else if (axisid * 3 + 2 < this.hist_args.length) { - res.nbins = this.hist_args[axisid * 3]; - res.min = this.hist_args[axisid * 3 + 1]; - res.max = this.hist_args[axisid * 3 + 2]; - } else { - let is_any = false; - for (let i = 1; i < arr.length; ++i) { - const v = arr[i]; - if (!Number.isFinite(v)) continue; - if (is_any) { - res.min = Math.min(res.min, v); - res.max = Math.max(res.max, v); - } else { - res.min = res.max = v; - is_any = true; - } - } - if (!is_any) { res.min = 0; res.max = 1; } + var _width = 150; + /** + * The height property is a positive integer reflecting the height HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 150 is used. + * This is one of the two properties, the other being width, that controls the size of the canvas. + * + * @name width + */ + Object.defineProperty(this, "width", { + get: function() { + return _width; + }, + set: function(value) { + if (isNaN(value) || Number.isInteger(value) === false || value < 0) { + _width = 150; + } else { + _width = value; + } + if (this.getContext("2d").pageWrapXEnabled) { + this.getContext("2d").pageWrapX = _width + 1; + } + } + }); - if (this.hist_nbins) - nbins = res.nbins = this.hist_nbins; + var _height = 300; + /** + * The width property is a positive integer reflecting the width HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 300 is used. + * This is one of the two properties, the other being height, that controls the size of the canvas. + * + * @name height + */ + Object.defineProperty(this, "height", { + get: function() { + return _height; + }, + set: function(value) { + if (isNaN(value) || Number.isInteger(value) === false || value < 0) { + _height = 300; + } else { + _height = value; + } + if (this.getContext("2d").pageWrapYEnabled) { + this.getContext("2d").pageWrapY = _height + 1; + } + } + }); - res.isinteger = (Math.round(res.min) === res.min) && (Math.round(res.max) === res.max); - if (res.isinteger) { - for (let k = 0; k < arr.length; ++k) - if (arr[k] !== Math.round(arr[k])) { res.isinteger = false; break; } - } + var _childNodes = []; + Object.defineProperty(this, "childNodes", { + get: function() { + return _childNodes; + }, + set: function(value) { + _childNodes = value; + } + }); - if (res.isinteger) { - res.min = Math.round(res.min); - res.max = Math.round(res.max); - if (res.max - res.min < nbins * 5) { - res.min -= 1; - res.max += 2; - res.nbins = Math.round(res.max - res.min); - } else { - const range = (res.max - res.min + 2); - let step = Math.floor(range / nbins); - while (step * nbins < range) step++; - res.max = res.min + nbins * step; - } - } else if (res.min >= res.max) { - res.max = res.min; - if (Math.abs(res.min) < 100) { res.min -= 1; res.max += 1; } else - if (res.min > 0) { res.min *= 0.9; res.max *= 1.1; } else { res.min *= 1.1; res.max *= 0.9; } - } else - res.max += (res.max - res.min) / res.nbins; + var _style = {}; + Object.defineProperty(this, "style", { + get: function() { + return _style; + }, + set: function(value) { + _style = value; } + }); - res.k = res.nbins / (res.max - res.min); + Object.defineProperty(this, "parentNode", {}); + }; - res.GetBin = function(value) { - const bin = this.lbls?.indexOf(value) ?? Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1; - return bin < 0 ? 0 : ((bin > this.nbins) ? this.nbins + 1 : bin + 1); - }; + /** + * The getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported. + * + * @name getContext + * @function + * @param {string} contextType Is a String containing the context identifier defining the drawing context associated to the canvas. Possible value is "2d", leading to the creation of a Context2D object representing a two-dimensional rendering context. + * @param {object} contextAttributes + */ + Canvas.prototype.getContext = function(contextType, contextAttributes) { + contextType = contextType || "2d"; + var key; - return res; - } + if (contextType !== "2d") { + return null; + } + for (key in contextAttributes) { + if (this.pdf.context2d.hasOwnProperty(key)) { + this.pdf.context2d[key] = contextAttributes[key]; + } + } + this.pdf.context2d._canvas = this; + return this.pdf.context2d; + }; - /** @summary Create histogram which matches value in dimensions */ - createHistogram(nbins, set_hist = false) { - if (!nbins) nbins = 20; + /** + * The toDataURL() method is just a stub to throw an error if accidently called. + * + * @name toDataURL + * @function + */ + Canvas.prototype.toDataURL = function() { + throw new Error("toDataURL is not implemented."); + }; - const x = this.getMinMaxBins(0, nbins), - y = this.getMinMaxBins(1, nbins), - z = this.getMinMaxBins(2, nbins); - let hist = null; + jsPDFAPI.events.push([ + "initialized", + function() { + this.canvas = new Canvas(); + this.canvas.pdf = this; + } + ]); - switch (this.ndim) { - case 1: hist = createHistogram(clTH1 + this.htype, x.nbins); break; - case 2: hist = createHistogram(clTH2 + this.htype, x.nbins, y.nbins); break; - case 3: hist = createHistogram(clTH3 + this.htype, x.nbins, y.nbins, z.nbins); break; - } + return this; +})(jsPDF.API); - hist.fXaxis.fTitle = x.title; - hist.fXaxis.fXmin = x.min; - hist.fXaxis.fXmax = x.max; - hist.fXaxis.fLabels = x.fLabels; +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - if (this.ndim > 1) hist.fYaxis.fTitle = y.title; - hist.fYaxis.fXmin = y.min; - hist.fYaxis.fXmax = y.max; - hist.fYaxis.fLabels = y.fLabels; +/** + * @name cell + * @module + */ +(function(jsPDFAPI) { - if (this.ndim > 2) hist.fZaxis.fTitle = z.title; - hist.fZaxis.fXmin = z.min; - hist.fZaxis.fXmax = z.max; - hist.fZaxis.fLabels = z.fLabels; + var NO_MARGINS = { left: 0, top: 0, bottom: 0, right: 0 }; - hist.fName = this.hist_name; - hist.fTitle = this.hist_title; - hist.fOption = this.histo_drawopt; - hist.$custom_stat = (this.hist_name === '$htemp') ? 111110 : 111111; + var px2pt = (0.264583 * 72) / 25.4; + var printingHeaderRow = false; - if (set_hist) { - this.hist = hist; - this.x = x; - this.y = y; - this.z = z; - } else - hist.fBits = hist.fBits | kNoStats; + var _initialize = function() { + if (typeof this.internal.__cell__ === "undefined") { + this.internal.__cell__ = {}; + this.internal.__cell__.padding = 3; + this.internal.__cell__.headerFunction = undefined; + this.internal.__cell__.margins = Object.assign({}, NO_MARGINS); + this.internal.__cell__.margins.width = this.getPageWidth(); + _reset.call(this); + } + }; - return hist; - } + var _reset = function() { + this.internal.__cell__.lastCell = new Cell(); + this.internal.__cell__.pages = 1; + }; - /** @summary Create output object - histogram, graph, dump array */ - createOutputObject() { - if (this.hist || !this.vars[0].buf) return; + var Cell = function() { + var _x = arguments[0]; + Object.defineProperty(this, "x", { + enumerable: true, + get: function() { + return _x; + }, + set: function(value) { + _x = value; + } + }); + var _y = arguments[1]; + Object.defineProperty(this, "y", { + enumerable: true, + get: function() { + return _y; + }, + set: function(value) { + _y = value; + } + }); + var _width = arguments[2]; + Object.defineProperty(this, "width", { + enumerable: true, + get: function() { + return _width; + }, + set: function(value) { + _width = value; + } + }); + var _height = arguments[3]; + Object.defineProperty(this, "height", { + enumerable: true, + get: function() { + return _height; + }, + set: function(value) { + _height = value; + } + }); + var _text = arguments[4]; + Object.defineProperty(this, "text", { + enumerable: true, + get: function() { + return _text; + }, + set: function(value) { + _text = value; + } + }); + var _lineNumber = arguments[5]; + Object.defineProperty(this, "lineNumber", { + enumerable: true, + get: function() { + return _lineNumber; + }, + set: function(value) { + _lineNumber = value; + } + }); + var _align = arguments[6]; + Object.defineProperty(this, "align", { + enumerable: true, + get: function() { + return _align; + }, + set: function(value) { + _align = value; + } + }); - if (this.dump_values) { - // just create array where dumped valus will be collected - this.hist = []; + return this; + }; - // reassign fill method - this.fill1DHistogram = this.fill2DHistogram = this.fill3DHistogram = this.dumpValues; - } else if (this.graph) { - const N = this.vars[0].buf.length; - let res = null; + Cell.prototype.clone = function() { + return new Cell( + this.x, + this.y, + this.width, + this.height, + this.text, + this.lineNumber, + this.align + ); + }; - if (this.ndim === 1) { - // A 1-dimensional graph will just have the x axis as an index - res = createTGraph(N, Array.from(Array(N).keys()), this.vars[0].buf); - res.fName = 'Graph'; - res.fTitle = this.hist_title; - } else if (this.ndim === 2) { - res = createTGraph(N, this.vars[0].buf, this.vars[1].buf); - res.fName = 'Graph'; - res.fTitle = this.hist_title; - delete this.vars[1].buf; - } else if (this.ndim === 3) { - res = create$1(clTPolyMarker3D); - res.fN = N; - res.fLastPoint = N - 1; - const arr = new Array(N*3); - for (let k = 0; k< N; ++k) { - arr[k*3] = this.vars[0].buf[k]; - arr[k*3+1] = this.vars[1].buf[k]; - arr[k*3+2] = this.vars[2].buf[k]; - } - res.fP = arr; - res.$hist = this.createHistogram(10); - delete this.vars[1].buf; - delete this.vars[2].buf; - res.fName = 'Points'; - } + Cell.prototype.toArray = function() { + return [ + this.x, + this.y, + this.width, + this.height, + this.text, + this.lineNumber, + this.align + ]; + }; - this.hist = res; + /** + * @name setHeaderFunction + * @function + * @param {function} func + */ + jsPDFAPI.setHeaderFunction = function(func) { + _initialize.call(this); + this.internal.__cell__.headerFunction = + typeof func === "function" ? func : undefined; + return this; + }; + + /** + * @name getTextDimensions + * @function + * @param {string} txt + * @returns {Object} dimensions + */ + jsPDFAPI.getTextDimensions = function(text, options) { + _initialize.call(this); + options = options || {}; + var fontSize = options.fontSize || this.getFontSize(); + var font = options.font || this.getFont(); + var scaleFactor = options.scaleFactor || this.internal.scaleFactor; + var width = 0; + var amountOfLines = 0; + var height = 0; + var tempWidth = 0; + var scope = this; + + if (!Array.isArray(text) && typeof text !== "string") { + if (typeof text === "number") { + text = String(text); } else { - const nbins = [200, 50, 20]; - this.createHistogram(nbins[this.ndim], true); + throw new Error( + "getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings." + ); } + } - const var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length; - - if (!this.graph) { - switch (this.ndim) { - case 1: { - for (let n = 0; n < len; ++n) - this.fill1DHistogram(var0[n], cut ? cut[n] : 1); - break; - } - case 2: { - const var1 = this.vars[1].buf; - for (let n = 0; n < len; ++n) - this.fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1); - delete this.vars[1].buf; - break; - } - case 3: { - const var1 = this.vars[1].buf, var2 = this.vars[2].buf; - for (let n = 0; n < len; ++n) - this.fill3DHistogram(var0[n], var1[n], var2[n], cut ? cut[n] : 1); - delete this.vars[1].buf; - delete this.vars[2].buf; - break; - } - } + const maxWidth = options.maxWidth; + if (maxWidth > 0) { + if (typeof text === "string") { + text = this.splitTextToSize(text, maxWidth); + } else if (Object.prototype.toString.call(text) === "[object Array]") { + text = text.reduce(function(acc, textLine) { + return acc.concat(scope.splitTextToSize(textLine, maxWidth)); + }, []); } + } else { + // Without the else clause, it will not work if you do not pass along maxWidth + text = Array.isArray(text) ? text : [text]; + } - delete this.vars[0].buf; - delete this.cut.buf; - } + for (var i = 0; i < text.length; i++) { + tempWidth = this.getStringUnitWidth(text[i], { font: font }) * fontSize; + if (width < tempWidth) { + width = tempWidth; + } + } - /** @summary Fill TBits histogram */ - fillTBitsHistogram(xvalue, weight) { - if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return; + if (width !== 0) { + amountOfLines = text.length; + } - const sz = Math.min(xvalue.fNbits + 1, xvalue.fNbytes * 8); + width = width / scaleFactor; + height = Math.max( + (amountOfLines * fontSize * this.getLineHeightFactor() - + fontSize * (this.getLineHeightFactor() - 1)) / + scaleFactor, + 0 + ); + return { w: width, h: height }; + }; - for (let bit = 0, mask = 1, b = 0; bit < sz; ++bit) { - if (xvalue.fAllBits[b] && mask) { - if (bit <= this.x.nbins) - this.hist.fArray[bit + 1] += weight; - else - this.hist.fArray[this.x.nbins + 1] += weight; - } + /** + * @name cellAddPage + * @function + */ + jsPDFAPI.cellAddPage = function() { + _initialize.call(this); - mask *= 2; - if (mask >= 0x100) { mask = 1; ++b; } - } - } + this.addPage(); - /** @summary Fill bits histogram */ - FillBitsHistogram(xvalue, weight) { - if (!weight) return; + var margins = this.internal.__cell__.margins || NO_MARGINS; + this.internal.__cell__.lastCell = new Cell( + margins.left, + margins.top, + undefined, + undefined + ); + this.internal.__cell__.pages += 1; - for (let bit = 0, mask = 1; bit < this.x.nbins; ++bit) { - if (xvalue & mask) this.hist.fArray[bit + 1] += weight; - mask *= 2; - } - } + return this; + }; - /** @summary Fill 1D histogram */ - fill1DHistogram(xvalue, weight) { - const bin = this.x.GetBin(xvalue); - this.hist.fArray[bin] += weight; + /** + * @name cell + * @function + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {string} text + * @param {number} lineNumber lineNumber + * @param {string} align + * @return {jsPDF} jsPDF-instance + */ + var cell = (jsPDFAPI.cell = function() { + var currentCell; - if (!this.x.lbls && Number.isFinite(xvalue)) { - this.hist.fTsumw += weight; - this.hist.fTsumwx += weight * xvalue; - this.hist.fTsumwx2 += weight * xvalue * xvalue; + if (arguments[0] instanceof Cell) { + currentCell = arguments[0]; + } else { + currentCell = new Cell( + arguments[0], + arguments[1], + arguments[2], + arguments[3], + arguments[4], + arguments[5] + ); + } + _initialize.call(this); + var lastCell = this.internal.__cell__.lastCell; + var padding = this.internal.__cell__.padding; + var margins = this.internal.__cell__.margins || NO_MARGINS; + var tableHeaderRow = this.internal.__cell__.tableHeaderRow; + var printHeaders = this.internal.__cell__.printHeaders; + // If this is not the first cell, we must change its position + if (typeof lastCell.lineNumber !== "undefined") { + if (lastCell.lineNumber === currentCell.lineNumber) { + //Same line + currentCell.x = (lastCell.x || 0) + (lastCell.width || 0); + currentCell.y = lastCell.y || 0; + } else { + //New line + if ( + lastCell.y + lastCell.height + currentCell.height + margins.bottom > + this.getPageHeight() + ) { + this.cellAddPage(); + currentCell.y = margins.top; + if (printHeaders && tableHeaderRow) { + this.printHeaderRow(currentCell.lineNumber, true); + currentCell.y += tableHeaderRow[0].height; + } + } else { + currentCell.y = lastCell.y + lastCell.height || currentCell.y; + } } - } - - /** @summary Fill 2D histogram */ - fill2DHistogram(xvalue, yvalue, weight) { - const xbin = this.x.GetBin(xvalue), - ybin = this.y.GetBin(yvalue); + } - this.hist.fArray[xbin + (this.x.nbins + 2) * ybin] += weight; - if (!this.x.lbls && !this.y.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue)) { - this.hist.fTsumw += weight; - this.hist.fTsumwx += weight * xvalue; - this.hist.fTsumwy += weight * yvalue; - this.hist.fTsumwx2 += weight * xvalue * xvalue; - this.hist.fTsumwxy += weight * xvalue * yvalue; - this.hist.fTsumwy2 += weight * yvalue * yvalue; + if (typeof currentCell.text[0] !== "undefined") { + this.rect( + currentCell.x, + currentCell.y, + currentCell.width, + currentCell.height, + printingHeaderRow === true ? "FD" : undefined + ); + if (currentCell.align === "right") { + this.text( + currentCell.text, + currentCell.x + currentCell.width - padding, + currentCell.y + padding, + { align: "right", baseline: "top" } + ); + } else if (currentCell.align === "center") { + this.text( + currentCell.text, + currentCell.x + currentCell.width / 2, + currentCell.y + padding, + { + align: "center", + baseline: "top", + maxWidth: currentCell.width - padding - padding + } + ); + } else { + this.text( + currentCell.text, + currentCell.x + padding, + currentCell.y + padding, + { + align: "left", + baseline: "top", + maxWidth: currentCell.width - padding - padding + } + ); } - } - - /** @summary Fill 3D histogram */ - fill3DHistogram(xvalue, yvalue, zvalue, weight) { - const xbin = this.x.GetBin(xvalue), - ybin = this.y.GetBin(yvalue), - zbin = this.z.GetBin(zvalue); + } + this.internal.__cell__.lastCell = currentCell; + return this; + }); - this.hist.fArray[xbin + (this.x.nbins + 2) * (ybin + (this.y.nbins + 2) * zbin)] += weight; - if (!this.x.lbls && !this.y.lbls && !this.z.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue) && Number.isFinite(zvalue)) { - this.hist.fTsumw += weight; - this.hist.fTsumwx += weight * xvalue; - this.hist.fTsumwy += weight * yvalue; - this.hist.fTsumwz += weight * zvalue; - this.hist.fTsumwx2 += weight * xvalue * xvalue; - this.hist.fTsumwy2 += weight * yvalue * yvalue; - this.hist.fTsumwz2 += weight * zvalue * zvalue; - this.hist.fTsumwxy += weight * xvalue * yvalue; - this.hist.fTsumwxz += weight * xvalue * zvalue; - this.hist.fTsumwyz += weight * yvalue * zvalue; - } - } + /** + * Create a table from a set of data. + * @name table + * @function + * @param {Integer} [x] : left-position for top-left corner of table + * @param {Integer} [y] top-position for top-left corner of table + * @param {Object[]} [data] An array of objects containing key-value pairs corresponding to a row of data. + * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost + + * @param {Object} [config.printHeaders] True to print column headers at the top of every page + * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value + * @param {Object} [config.margins] margin values for left, top, bottom, and width + * @param {Object} [config.fontSize] Integer fontSize to use (optional) + * @param {Object} [config.padding] cell-padding in pt to use (optional) + * @param {Object} [config.headerBackgroundColor] default is #c8c8c8 (optional) + * @param {Object} [config.headerTextColor] default is #000 (optional) + * @param {Object} [config.rowStart] callback to handle before print each row (optional) + * @param {Object} [config.cellStart] callback to handle before print each cell (optional) + * @returns {jsPDF} jsPDF-instance + */ + + jsPDFAPI.table = function(x, y, data, headers, config) { + _initialize.call(this); + if (!data) { + throw new Error("No data for PDF table."); + } - /** @summary Dump values */ - dumpValues(v1, v2, v3, v4) { - let obj; - switch (this.ndim) { - case 1: obj = { x: v1, weight: v2 }; break; - case 2: obj = { x: v1, y: v2, weight: v3 }; break; - case 3: obj = { x: v1, y: v2, z: v3, weight: v4 }; break; - } + config = config || {}; + + var headerNames = [], + headerLabels = [], + headerAligns = [], + i, + columnMatrix = {}, + columnWidths = {}, + column, + columnMinWidths = [], + j, + tableHeaderConfigs = [], + //set up defaults. If a value is provided in config, defaults will be overwritten: + autoSize = config.autoSize || false, + printHeaders = config.printHeaders === false ? false : true, + fontSize = + config.css && typeof config.css["font-size"] !== "undefined" + ? config.css["font-size"] * 16 + : config.fontSize || 12, + margins = + config.margins || + Object.assign({ width: this.getPageWidth() }, NO_MARGINS), + padding = typeof config.padding === "number" ? config.padding : 3, + headerBackgroundColor = config.headerBackgroundColor || "#c8c8c8", + headerTextColor = config.headerTextColor || "#000"; + + _reset.call(this); + + this.internal.__cell__.printHeaders = printHeaders; + this.internal.__cell__.margins = margins; + this.internal.__cell__.table_font_size = fontSize; + this.internal.__cell__.padding = padding; + this.internal.__cell__.headerBackgroundColor = headerBackgroundColor; + this.internal.__cell__.headerTextColor = headerTextColor; + this.setFontSize(fontSize); + + // Set header values + if (headers === undefined || headers === null) { + // No headers defined so we derive from data + headerNames = Object.keys(data[0]); + headerLabels = headerNames; + headerAligns = headerNames.map(function() { + return "left"; + }); + } else if (Array.isArray(headers) && typeof headers[0] === "object") { + headerNames = headers.map(function(header) { + return header.name; + }); + headerLabels = headers.map(function(header) { + return header.prompt || header.name || ""; + }); + headerAligns = headers.map(function(header) { + return header.align || "left"; + }); + // Split header configs into names and prompts + for (i = 0; i < headers.length; i += 1) { + columnWidths[headers[i].name] = headers[i].width * px2pt; + } + } else if (Array.isArray(headers) && typeof headers[0] === "string") { + headerNames = headers; + headerLabels = headerNames; + headerAligns = headerNames.map(function() { + return "left"; + }); + } - if (this.cut.is_dummy()) { - if (this.ndim === 1) - obj = v1; - else - delete obj.weight; - } + if ( + autoSize || + (Array.isArray(headers) && typeof headers[0] === "string") + ) { + var headerName; + for (i = 0; i < headerNames.length; i += 1) { + headerName = headerNames[i]; - this.hist.push(obj); - } + // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} - /** @summary function used when all branches can be read as array - * @desc most typical usage - histogramming of single branch */ - ProcessArraysFunc(/* entry */) { - if (this.arr_limit || this.graph) { - const var0 = this.vars[0], - var1 = this.vars[1], - var2 = this.vars[2], - len = this.tgtarr.br0.length; - if ((var0.buf.length === 0) && (len >= this.arr_limit) && !this.graph) { - // special use case - first array large enough to create histogram directly base on it - var0.buf = this.tgtarr.br0; - if (var1) var1.buf = this.tgtarr.br1; - if (var2) var2.buf = this.tgtarr.br2; - } else { - for (let k = 0; k < len; ++k) { - var0.buf.push(this.tgtarr.br0[k]); - if (var1) var1.buf.push(this.tgtarr.br1[k]); - if (var2) var2.buf.push(this.tgtarr.br2[k]); - } - } - var0.kind = 'number'; - if (var1) var1.kind = 'number'; - if (var2) var2.kind = 'number'; - this.cut.buf = null; // do not create buffer for cuts - if (!this.graph && (var0.buf.length >= this.arr_limit)) { - this.createOutputObject(); - this.arr_limit = 0; - } - } else { - const br0 = this.tgtarr.br0, len = br0.length; - switch (this.ndim) { - case 1: { - for (let k = 0; k < len; ++k) - this.fill1DHistogram(br0[k], 1); - break; - } - case 2: { - const br1 = this.tgtarr.br1; - for (let k = 0; k < len; ++k) - this.fill2DHistogram(br0[k], br1[k], 1); - break; - } - case 3: { - const br1 = this.tgtarr.br1, br2 = this.tgtarr.br2; - for (let k = 0; k < len; ++k) - this.fill3DHistogram(br0[k], br1[k], br2[k], 1); - break; - } - } - } - } + columnMatrix[headerName] = data.map(function(rec) { + return rec[headerName]; + }); - /** @summary simple dump of the branch - no need to analyze something */ - ProcessDump(/* entry */) { - const res = this.leaf ? this.tgtobj.br0[this.leaf] : this.tgtobj.br0; + // get header width + this.setFont(undefined, "bold"); + columnMinWidths.push( + this.getTextDimensions(headerLabels[i], { + fontSize: this.internal.__cell__.table_font_size, + scaleFactor: this.internal.scaleFactor + }).w + ); + column = columnMatrix[headerName]; + + // get cell widths + this.setFont(undefined, "normal"); + for (j = 0; j < column.length; j += 1) { + columnMinWidths.push( + this.getTextDimensions(column[j], { + fontSize: this.internal.__cell__.table_font_size, + scaleFactor: this.internal.scaleFactor + }).w + ); + } - if (res && this.copy_fields) { - if (checkArrayPrototype(res) === 0) - this.hist.push(Object.assign({}, res)); - else - this.hist.push(res); - } else - this.hist.push(res); - } + // get final column width + columnWidths[headerName] = + Math.max.apply(null, columnMinWidths) + padding + padding; - /** @summary Normal TSelector Process handler */ - Process(entry) { - this.globals.entry = entry; // can be used in any expression + //have to reset + columnMinWidths = []; + } + } - this.cut.produce(this.tgtobj); - if (!this.dump_values && !this.cut.value) return; + // -- Construct the table + + if (printHeaders) { + var row = {}; + for (i = 0; i < headerNames.length; i += 1) { + row[headerNames[i]] = {}; + row[headerNames[i]].text = headerLabels[i]; + row[headerNames[i]].align = headerAligns[i]; + } + + var rowHeight = calculateLineHeight.call(this, row, columnWidths); + + // Construct the header row + tableHeaderConfigs = headerNames.map(function(value) { + return new Cell( + x, + y, + columnWidths[value], + rowHeight, + row[value].text, + undefined, + row[value].align + ); + }); - for (let n = 0; n < this.ndim; ++n) - this.vars[n].produce(this.tgtobj); + // Store the table header config + this.setTableHeaderRow(tableHeaderConfigs); - const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut; + // Print the header for the start of the table + this.printHeaderRow(1, false); + } - if (this.graph || this.arr_limit) { - switch (this.ndim) { - case 1: - for (let n0 = 0; n0 < var0.length; ++n0) { - var0.buf.push(var0.get(n0)); - cut.buf?.push(cut.value); - } - break; - case 2: - for (let n0 = 0; n0 < var0.length; ++n0) { - for (let n1 = 0; n1 < var1.length; ++n1) { - var0.buf.push(var0.get(n0)); - var1.buf.push(var1.get(n1)); - cut.buf?.push(cut.value); - } - } - break; - case 3: - for (let n0 = 0; n0 < var0.length; ++n0) { - for (let n1 = 0; n1 < var1.length; ++n1) { - for (let n2 = 0; n2 < var2.length; ++n2) { - var0.buf.push(var0.get(n0)); - var1.buf.push(var1.get(n1)); - var2.buf.push(var2.get(n2)); - cut.buf?.push(cut.value); - } - } - } - break; - } - if (!this.graph && (var0.buf.length >= this.arr_limit)) { - this.createOutputObject(); - this.arr_limit = 0; - } - } else if (this.hist) { - switch (this.ndim) { - case 1: - for (let n0 = 0; n0 < var0.length; ++n0) - this.fill1DHistogram(var0.get(n0), cut.value); - break; - case 2: - for (let n0 = 0; n0 < var0.length; ++n0) { - for (let n1 = 0; n1 < var1.length; ++n1) - this.fill2DHistogram(var0.get(n0), var1.get(n1), cut.value); - } - break; - case 3: - for (let n0 = 0; n0 < var0.length; ++n0) { - for (let n1 = 0; n1 < var1.length; ++n1) { - for (let n2 = 0; n2 < var2.length; ++n2) - this.fill3DHistogram(var0.get(n0), var1.get(n1), var2.get(n2), cut.value); - } - } - break; - } + // Construct the data rows + + var align = headers.reduce(function(pv, cv) { + pv[cv.name] = cv.align; + return pv; + }, {}); + for (i = 0; i < data.length; i += 1) { + if ("rowStart" in config && config.rowStart instanceof Function) { + config.rowStart( + { + row: i, + data: data[i] + }, + this + ); } + var lineHeight = calculateLineHeight.call(this, data[i], columnWidths); - if (this.monitoring && this.hist && !this.dump_values) { - const now = new Date().getTime(); - if (now - this.lasttm > this.monitoring) { - this.lasttm = now; - if (isFunc(this.progress_callback)) - this.progress_callback(this.hist); - } + for (j = 0; j < headerNames.length; j += 1) { + var cellData = data[i][headerNames[j]]; + if ("cellStart" in config && config.cellStart instanceof Function) { + config.cellStart( + { + row: i, + col: j, + data: cellData + }, + this + ); + } + cell.call( + this, + new Cell( + x, + y, + columnWidths[headerNames[j]], + lineHeight, + cellData, + i + 2, + align[headerNames[j]] + ) + ); } - } + } + this.internal.__cell__.table_x = x; + this.internal.__cell__.table_y = y; + return this; + }; - /** @summary Normal TSelector Terminate handler */ - Terminate(res) { - if (res && !this.hist) - this.createOutputObject(); + /** + * Calculate the height for containing the highest column + * + * @name calculateLineHeight + * @function + * @param {Object[]} model is the line of data we want to calculate the height of + * @param {Integer[]} columnWidths is size of each column + * @returns {number} lineHeight + * @private + */ + var calculateLineHeight = function calculateLineHeight(model, columnWidths) { + var padding = this.internal.__cell__.padding; + var fontSize = this.internal.__cell__.table_font_size; + var scaleFactor = this.internal.scaleFactor; + + return Object.keys(model) + .map(function(key) { + var value = model[key]; + return this.splitTextToSize( + value.hasOwnProperty("text") ? value.text : value, + columnWidths[key] - padding - padding + ); + }, this) + .map(function(value) { + return ( + (this.getLineHeightFactor() * value.length * fontSize) / scaleFactor + + padding + + padding + ); + }, this) + .reduce(function(pv, cv) { + return Math.max(pv, cv); + }, 0); + }; - this.ShowProgress(); + /** + * Store the config for outputting a table header + * + * @name setTableHeaderRow + * @function + * @param {Object[]} config + * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell + * except the lineNumber parameter is excluded + */ + jsPDFAPI.setTableHeaderRow = function(config) { + _initialize.call(this); + this.internal.__cell__.tableHeaderRow = config; + }; - if (isFunc(this.result_callback)) - this.result_callback(this.hist); - } + /** + * Output the store header row + * + * @name printHeaderRow + * @function + * @param {number} lineNumber The line number to output the header at + * @param {boolean} new_page + */ + jsPDFAPI.printHeaderRow = function(lineNumber, new_page) { + _initialize.call(this); + if (!this.internal.__cell__.tableHeaderRow) { + throw new Error("Property tableHeaderRow does not exist."); + } -} // class TDrawSelector + var tableHeaderCell; + printingHeaderRow = true; + if (typeof this.internal.__cell__.headerFunction === "function") { + var position = this.internal.__cell__.headerFunction( + this, + this.internal.__cell__.pages + ); + this.internal.__cell__.lastCell = new Cell( + position[0], + position[1], + position[2], + position[3], + undefined, + -1 + ); + } + this.setFont(undefined, "bold"); + + var tempHeaderConf = []; + for (var i = 0; i < this.internal.__cell__.tableHeaderRow.length; i += 1) { + tableHeaderCell = this.internal.__cell__.tableHeaderRow[i].clone(); + if (new_page) { + tableHeaderCell.y = this.internal.__cell__.margins.top || 0; + tempHeaderConf.push(tableHeaderCell); + } + tableHeaderCell.lineNumber = lineNumber; + var currentTextColor = this.getTextColor(); + this.setTextColor(this.internal.__cell__.headerTextColor); + this.setFillColor(this.internal.__cell__.headerBackgroundColor); + cell.call(this, tableHeaderCell); + this.setTextColor(currentTextColor); + } + if (tempHeaderConf.length > 0) { + this.setTableHeaderRow(tempHeaderConf); + } + this.setFont(undefined, "normal"); + printingHeaderRow = false; + }; +})(jsPDF.API); -/** @summary return TStreamerElement associated with the branch - if any - * @desc unfortunately, branch.fID is not number of element in streamer info - * @private */ -function findBrachStreamerElement(branch, file) { - if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) return null; +function toLookup(arr) { + return arr.reduce(function(lookup, name, index) { + lookup[name] = index; - const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum), - arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null; - if (!arr) return null; + return lookup; + }, {}); +} - let match_name = branch.fName, - pos = match_name.indexOf('['); - if (pos > 0) match_name = match_name.slice(0, pos); - pos = match_name.lastIndexOf('.'); - if (pos > 0) match_name = match_name.slice(pos + 1); +var fontStyleOrder = { + italic: ["italic", "oblique", "normal"], + oblique: ["oblique", "italic", "normal"], + normal: ["normal", "oblique", "italic"] +}; - function match_elem(elem) { - if (!elem) return false; - if (elem.fName !== match_name) return false; - if (elem.fType === branch.fStreamerType) return true; - if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) return true; - if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) || - (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) && - (elem.fType === kStreamer)) return true; - console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`); - return false; - } +var fontStretchOrder = [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" +]; - // first check branch fID - in many cases gut guess - if (match_elem(arr[branch.fID])) - return arr[branch.fID]; +// For a given font-stretch value, we need to know where to start our search +// from in the fontStretchOrder list. +var fontStretchLookup = toLookup(fontStretchOrder); - for (let k = 0; k < arr.length; ++k) { - if ((k !== branch.fID) && match_elem(arr[k])) - return arr[k]; - } +var fontWeights = [100, 200, 300, 400, 500, 600, 700, 800, 900]; +var fontWeightsLookup = toLookup(fontWeights); - console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`); +function normalizeFontStretch(stretch) { + stretch = stretch || "normal"; - return null; + return typeof fontStretchLookup[stretch] === "number" ? stretch : "normal"; } -/** @summary return type name of given member in the class - * @private */ -function defineMemberTypeName(file, parent_class, member_name) { - const s_i = file.findStreamerInfo(parent_class), - arr = s_i?.fElements?.arr; - if (!arr) return ''; +function normalizeFontStyle(style) { + style = style || "normal"; - let elem = null; - for (let k = 0; k < arr.length; ++k) { - if (arr[k].fTypeName === kBaseClass) { - const res = defineMemberTypeName(file, arr[k].fName, member_name); - if (res) return res; - } else - if (arr[k].fName === member_name) { elem = arr[k]; break; } - } + return fontStyleOrder[style] ? style : "normal"; +} + +function normalizeFontWeight(weight) { + if (!weight) { + return 400; + } - if (!elem) return ''; + if (typeof weight === "number") { + // Ignore values which aren't valid font-weights. + return weight >= 100 && weight <= 900 && weight % 100 === 0 ? weight : 400; + } - let clname = elem.fTypeName; - if (clname[clname.length - 1] === '*') - clname = clname.slice(0, clname.length - 1); + if (/^\d00$/.test(weight)) { + return parseInt(weight); + } - return clname; + switch (weight) { + case "bold": + return 700; + + case "normal": + default: + return 400; + } } -/** @summary create fast list to assign all methods to the object - * @private */ -function makeMethodsList(typename) { - const methods = getMethods(typename), - res = { - names: [], - values: [], - Create() { - const obj = {}; - for (let n = 0; n < this.names.length; ++n) - obj[this.names[n]] = this.values[n]; - return obj; - } - }; +function normalizeFontFace(fontFace) { + var family = fontFace.family.replace(/"|'/g, "").toLowerCase(); - res.names.push('_typename'); - res.values.push(typename); - for (const key in methods) { - res.names.push(key); - res.values.push(methods[key]); - } - return res; + var style = normalizeFontStyle(fontFace.style); + var weight = normalizeFontWeight(fontFace.weight); + var stretch = normalizeFontStretch(fontFace.stretch); + + return { + family: family, + style: style, + weight: weight, + stretch: stretch, + src: fontFace.src || [], + + // The ref property maps this font-face to the font + // added by the .addFont() method. + ref: fontFace.ref || { + name: family, + style: [stretch, style, weight].join(" ") + } + }; } -/** @summary try to define classname for the branch member, scanning list of branches - * @private */ -function detectBranchMemberClass(brlst, prefix, start) { - let clname = ''; - for (let kk = (start || 0); kk < brlst.arr.length; ++kk) { - if ((brlst.arr[kk].fName.indexOf(prefix) === 0) && brlst.arr[kk].fClassName) - clname = brlst.arr[kk].fClassName; - } - return clname; +/** + * Turns a list of font-faces into a map, for easier lookup when resolving + * fonts. + * @private + */ +function buildFontFaceMap(fontFaces) { + var map = {}; + + for (var i = 0; i < fontFaces.length; ++i) { + var normalized = normalizeFontFace(fontFaces[i]); + + var name = normalized.family; + var stretch = normalized.stretch; + var style = normalized.style; + var weight = normalized.weight; + + map[name] = map[name] || {}; + + map[name][stretch] = map[name][stretch] || {}; + map[name][stretch][style] = map[name][stretch][style] || {}; + map[name][stretch][style][weight] = normalized; + } + + return map; } -/** @summary Process selector for the tree - * @desc function similar to the TTree::Process - * @param {object} tree - instance of TTree class - * @param {object} selector - instance of {@link TSelector} class - * @param {object} [args] - different arguments - * @param {number} [args.firstentry] - first entry to process, 0 when not specified - * @param {number} [args.numentries] - number of entries to process, all when not specified - * @return {Promise} with TSelector instance */ -async function treeProcess(tree, selector, args) { - if (!args) args = {}; +/** + * Searches a map of stretches, weights, etc. in the given direction and + * then, if no match has been found, in the opposite directions. + * + * @param {Object.} matchingSet A map of the various font variations. + * @param {any[]} order The order of the different variations + * @param {number} pivot The starting point of the search in the order list. + * @param {number} dir The initial direction of the search (desc = -1, asc = 1) + * @private + */ - if (!selector || !tree.$file || !selector.numBranches()) { - selector?.Terminate(false); - return Promise.reject(Error('required parameter missing for TTree::Process')); - } +function searchFromPivot(matchingSet, order, pivot, dir) { + var i; - // central handle with all information required for reading - const handle = { - tree, // keep tree reference - file: tree.$file, // keep file reference - selector, // reference on selector - arr: [], // list of branches - curr: -1, // current entry ID - current_entry: -1, // current processed entry - simple_read: true, // all baskets in all used branches are in sync, - process_arrays: true // one can process all branches as arrays - }, createLeafElem = (leaf, name) => { - // function creates TStreamerElement which corresponds to the elementary leaf - let datakind = 0; - switch (leaf._typename) { - case 'TLeafF': datakind = kFloat; break; - case 'TLeafD': datakind = kDouble; break; - case 'TLeafO': datakind = kBool; break; - case 'TLeafB': datakind = leaf.fIsUnsigned ? kUChar : kChar; break; - case 'TLeafS': datakind = leaf.fIsUnsigned ? kUShort : kShort; break; - case 'TLeafI': datakind = leaf.fIsUnsigned ? kUInt : kInt; break; - case 'TLeafL': datakind = leaf.fIsUnsigned ? kULong64 : kLong64; break; - case 'TLeafC': datakind = kTString; break; - default: return null; - } - const elem = createStreamerElement(name || leaf.fName, datakind); - if (leaf.fLen > 1) { - elem.fType += kOffsetL; - elem.fArrayLength = leaf.fLen; - } - return elem; - }, findInHandle = branch => { - for (let k = 0; k < handle.arr.length; ++k) { - if (handle.arr[k].branch === branch) - return handle.arr[k]; - } - return null; - }; + for (i = pivot; i >= 0 && i < order.length; i += dir) { + if (matchingSet[order[i]]) { + return matchingSet[order[i]]; + } + } - let namecnt = 0; + for (i = pivot; i >= 0 && i < order.length; i -= dir) { + if (matchingSet[order[i]]) { + return matchingSet[order[i]]; + } + } +} - function AddBranchForReading(branch, target_object, target_name, read_mode) { - // central method to add branch for reading - // read_mode == true - read only this branch - // read_mode == '$child$' is just member of object from for STL or clonesarray - // read_mode == '' is sub-object from STL or clonesarray, happens when such new object need to be created - // read_mode == '.member_name' select only reading of member_name instead of complete object +function resolveFontStretch(stretch, matchingSet) { + if (matchingSet[stretch]) { + return matchingSet[stretch]; + } - if (isStr(branch)) - branch = findBranch(handle.tree, branch); + var pivot = fontStretchLookup[stretch]; - if (!branch) { console.error('Did not found branch'); return null; } + // If the font-stretch value is normal or more condensed, we want to + // start with a descending search, otherwise we should do ascending. + var dir = pivot <= fontStretchLookup["normal"] ? -1 : 1; + var match = searchFromPivot(matchingSet, fontStretchOrder, pivot, dir); - let item = findInHandle(branch); + if (!match) { + // Since a font-family cannot exist without having at least one stretch value + // we should never reach this point. + throw new Error( + "Could not find a matching font-stretch value for " + stretch + ); + } - if (item) { - console.error(`Branch ${branch.fName} already configured for reading`); - if (item.tgt !== target_object) console.error('Target object differs'); - return null; - } + return match; +} - if (!branch.fEntries) { - console.warn(`Branch ${branch.fName} does not have entries`); - return null; - } +function resolveFontStyle(fontStyle, matchingSet) { + if (matchingSet[fontStyle]) { + return matchingSet[fontStyle]; + } - // console.log(`Add branch ${branch.fName}`); + var ordering = fontStyleOrder[fontStyle]; - item = { - branch, - tgt: target_object, // used target object - can be differ for object members - name: target_name, - index: -1, // index in the list of read branches - member: null, // member to read branch - type: 0, // keep type identifier - curr_entry: -1, // last processed entry - raw: null, // raw buffer for reading - basket: null, // current basket object - curr_basket: 0, // number of basket used for processing - read_entry: -1, // last entry which is already read - staged_entry: -1, // entry which is staged for reading - first_readentry: -1, // first entry to read - staged_basket: 0, // last basket staged for reading - numentries: branch.fEntries, - numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file - counters: null, // branch indexes used as counters - ascounter: [], // list of other branches using that branch as counter - baskets: [], // array for read baskets, - staged_prev: 0, // entry limit of previous I/O request - staged_now: 0, // entry limit of current I/O request - progress_showtm: 0, // last time when progress was showed - getBasketEntry(k) { - if (!this.branch || (k > this.branch.fMaxBaskets)) return 0; - const res = (k < this.branch.fMaxBaskets) ? this.branch.fBasketEntry[k] : 0; - if (res) return res; - const bskt = (k > 0) ? this.branch.fBaskets.arr[k - 1] : null; - return bskt ? (this.branch.fBasketEntry[k - 1] + bskt.fNevBuf) : 0; - }, - getTarget(tgtobj) { - // returns target object which should be used for the branch reading - if (!this.tgt) return tgtobj; - for (let k = 0; k < this.tgt.length; ++k) { - const sub = this.tgt[k]; - if (!tgtobj[sub.name]) tgtobj[sub.name] = sub.lst.Create(); - tgtobj = tgtobj[sub.name]; - } - return tgtobj; - }, - getEntry(entry) { - // This should be equivalent to TBranch::GetEntry() method - const shift = entry - this.first_entry; - let off; - if (!this.branch.TestBit(kDoNotUseBufferMap)) - this.raw.clearObjectMap(); - if (this.basket.fEntryOffset) { - off = this.basket.fEntryOffset[shift]; - if (this.basket.fDisplacement) - this.raw.fDisplacement = this.basket.fDisplacement[shift]; - } else - off = this.basket.fKeylen + this.basket.fNevBufSize * shift; - this.raw.locate(off - this.raw.raw_shift); + for (var i = 0; i < ordering.length; ++i) { + if (matchingSet[ordering[i]]) { + return matchingSet[ordering[i]]; + } + } - // this.member.func(this.raw, this.getTarget(tgtobj)); - } - }; + // Since a font-family cannot exist without having at least one style value + // we should never reach this point. + throw new Error("Could not find a matching font-style for " + fontStyle); +} - // last basket can be stored directly with the branch - while (item.getBasketEntry(item.numbaskets + 1)) item.numbaskets++; +function resolveFontWeight(weight, matchingSet) { + if (matchingSet[weight]) { + return matchingSet[weight]; + } - // check all counters if we - const nb_leaves = branch.fLeaves?.arr?.length ?? 0, - leaf = (nb_leaves > 0) ? branch.fLeaves.arr[0] : null, - is_brelem = (branch._typename === clTBranchElement); - let elem = null, // TStreamerElement used to create reader - member = null, // member for actual reading of the branch - child_scan = 0, // scan child branches after main branch is appended - item_cnt = null, item_cnt2 = null, object_class = ''; + if (weight === 400 && matchingSet[500]) { + return matchingSet[500]; + } - if (branch.fBranchCount) { - item_cnt = findInHandle(branch.fBranchCount); + if (weight === 500 && matchingSet[400]) { + return matchingSet[400]; + } - if (!item_cnt) - item_cnt = AddBranchForReading(branch.fBranchCount, target_object, '$counter' + namecnt++, true); + var pivot = fontWeightsLookup[weight]; - if (!item_cnt) { console.error(`Cannot add counter branch ${branch.fBranchCount.fName}`); return null; } + // If the font-stretch value is normal or more condensed, we want to + // start with a descending search, otherwise we should do ascending. + var dir = weight < 400 ? -1 : 1; + var match = searchFromPivot(matchingSet, fontWeights, pivot, dir); - let BranchCount2 = branch.fBranchCount2; + if (!match) { + // Since a font-family cannot exist without having at least one stretch value + // we should never reach this point. + throw new Error( + "Could not find a matching font-weight for value " + weight + ); + } - if (!BranchCount2 && (branch.fBranchCount.fStreamerType === kSTL) && - ((branch.fStreamerType === kStreamLoop) || (branch.fStreamerType === kOffsetL + kStreamLoop))) { - // special case when count member from kStreamLoop not assigned as fBranchCount2 - const elemd = findBrachStreamerElement(branch, handle.file), - arrd = branch.fBranchCount.fBranches.arr; + return match; +} - if (elemd?.fCountName && arrd) { - for (let k = 0; k < arrd.length; ++k) { - if (arrd[k].fName === branch.fBranchCount.fName + '.' + elemd.fCountName) { - BranchCount2 = arrd[k]; - break; - } - } - } +var defaultGenericFontFamilies = { + "sans-serif": "helvetica", + fixed: "courier", + monospace: "courier", + terminal: "courier", + cursive: "times", + fantasy: "times", + serif: "times" +}; - if (!BranchCount2) console.error('Did not found branch for second counter of kStreamLoop element'); - } +var systemFonts = { + caption: "times", + icon: "times", + menu: "times", + "message-box": "times", + "small-caption": "times", + "status-bar": "times" +}; - if (BranchCount2) { - item_cnt2 = findInHandle(BranchCount2); +function ruleToString(rule) { + return [rule.stretch, rule.style, rule.weight, rule.family].join(" "); +} - if (!item_cnt2) item_cnt2 = AddBranchForReading(BranchCount2, target_object, '$counter' + namecnt++, true); +function resolveFontFace(fontFaceMap, rules, opts) { + opts = opts || {}; - if (!item_cnt2) { console.error(`Cannot add counter branch2 ${BranchCount2.fName}`); return null; } - } - } else if (nb_leaves === 1 && leaf && leaf.fLeafCount) { - const br_cnt = findBranch(handle.tree, leaf.fLeafCount.fName); + var defaultFontFamily = opts.defaultFontFamily || "times"; + var genericFontFamilies = Object.assign( + {}, + defaultGenericFontFamilies, + opts.genericFontFamilies || {} + ); - if (br_cnt) { - item_cnt = findInHandle(br_cnt); + var rule = null; + var matches = null; - if (!item_cnt) item_cnt = AddBranchForReading(br_cnt, target_object, '$counter' + namecnt++, true); + for (var i = 0; i < rules.length; ++i) { + rule = normalizeFontFace(rules[i]); - if (!item_cnt) { console.error(`Cannot add counter branch ${br_cnt.fName}`); return null; } - } - } + if (genericFontFamilies[rule.family]) { + rule.family = genericFontFamilies[rule.family]; + } - function ScanBranches(lst, master_target, chld_kind) { - if (!lst || !lst.arr.length) return true; + if (fontFaceMap.hasOwnProperty(rule.family)) { + matches = fontFaceMap[rule.family]; - let match_prefix = branch.fName; - if (match_prefix[match_prefix.length - 1] === '.') match_prefix = match_prefix.slice(0, match_prefix.length - 1); - if (isStr(read_mode) && (read_mode[0] === '.')) match_prefix += read_mode; - match_prefix += '.'; + break; + } + } - for (let k = 0; k < lst.arr.length; ++k) { - const br = lst.arr[k]; - if ((chld_kind > 0) && (br.fType !== chld_kind)) continue; + // Always fallback to a known font family. + matches = matches || fontFaceMap[defaultFontFamily]; + + if (!matches) { + // At this point we should definitiely have a font family, but if we + // don't there is something wrong with our configuration + throw new Error( + "Could not find a font-family for the rule '" + + ruleToString(rule) + + "' and default family '" + + defaultFontFamily + + "'." + ); + } - if (br.fType === kBaseClassNode) { - if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false; - continue; - } + matches = resolveFontStretch(rule.stretch, matches); + matches = resolveFontStyle(rule.style, matches); + matches = resolveFontWeight(rule.weight, matches); - const elem = findBrachStreamerElement(br, handle.file); - if (elem?.fTypeName === kBaseClass) { - // if branch is data of base class, map it to original target - if (br.fTotBytes && !AddBranchForReading(br, target_object, target_name, read_mode)) return false; - if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false; - continue; - } + if (!matches) { + // We should've fount + throw new Error( + "Failed to resolve a font for the rule '" + ruleToString(rule) + "'." + ); + } - let subname = br.fName, chld_direct = 1; + return matches; +} - if (br.fName.indexOf(match_prefix) === 0) - subname = subname.slice(match_prefix.length); - else if (chld_kind > 0) - continue; // for defined children names prefix must be present +function eatWhiteSpace(input) { + return input.trimLeft(); +} - let p = subname.indexOf('['); - if (p > 0) subname = subname.slice(0, p); - p = subname.indexOf('<'); - if (p > 0) subname = subname.slice(0, p); +function parseQuotedFontFamily(input, quote) { + var index = 0; - if (chld_kind > 0) { - chld_direct = '$child$'; - const pp = subname.indexOf('.'); - if (pp > 0) chld_direct = detectBranchMemberClass(lst, branch.fName + '.' + subname.slice(0, pp + 1), k) || clTObject; - } + while (index < input.length) { + var current = input.charAt(index); - if (!AddBranchForReading(br, master_target, subname, chld_direct)) return false; - } + if (current === quote) { + return [input.substring(0, index), input.substring(index + 1)]; + } - return true; + index += 1; + } + + // Unexpected end of input + return null; +} + +function parseNonQuotedFontFamily(input) { + // It implements part of the identifier parser here: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + // + // NOTE: This parser pretty much ignores escaped identifiers and that there is a thing called unicode. + // + // Breakdown of regexp: + // -[a-z_] - when identifier starts with a hyphen, you're not allowed to have another hyphen or a digit + // [a-z_] - allow a-z and underscore at beginning of input + // [a-z0-9_-]* - after that, anything goes + var match = input.match(/^(-[a-z_]|[a-z_])[a-z0-9_-]*/i); + + // non quoted value contains illegal characters + if (match === null) { + return null; + } + + return [match[0], input.substring(match[0].length)]; +} + +var defaultFont = ["times"]; + +function parseFontFamily(input) { + var result = []; + var ch, parsed; + var remaining = input.trim(); + + if (remaining === "") { + return defaultFont; + } + + if (remaining in systemFonts) { + return [systemFonts[remaining]]; + } + + while (remaining !== "") { + parsed = null; + remaining = eatWhiteSpace(remaining); + ch = remaining.charAt(0); + + switch (ch) { + case '"': + case "'": + parsed = parseQuotedFontFamily(remaining.substring(1), ch); + break; + + default: + parsed = parseNonQuotedFontFamily(remaining); + break; + } + + if (parsed === null) { + return defaultFont; + } + + result.push(parsed[0]); + + remaining = eatWhiteSpace(parsed[1]); + + // We expect end of input or a comma separator here + if (remaining !== "" && remaining.charAt(0) !== ",") { + return defaultFont; + } + + remaining = remaining.replace(/^,/, ""); + } + + return result; +} + +/* eslint-disable no-fallthrough */ + +/** + * This plugin mimics the HTML5 CanvasRenderingContext2D. + * + * The goal is to provide a way for current canvas implementations to print directly to a PDF. + * + * @name context2d + * @module + */ +(function(jsPDFAPI) { + var ContextLayer = function(ctx) { + ctx = ctx || {}; + this.isStrokeTransparent = ctx.isStrokeTransparent || false; + this.strokeOpacity = ctx.strokeOpacity || 1; + this.strokeStyle = ctx.strokeStyle || "#000000"; + this.fillStyle = ctx.fillStyle || "#000000"; + this.isFillTransparent = ctx.isFillTransparent || false; + this.fillOpacity = ctx.fillOpacity || 1; + this.font = ctx.font || "10px sans-serif"; + this.textBaseline = ctx.textBaseline || "alphabetic"; + this.textAlign = ctx.textAlign || "left"; + this.lineWidth = ctx.lineWidth || 1; + this.lineJoin = ctx.lineJoin || "miter"; + this.lineCap = ctx.lineCap || "butt"; + this.path = ctx.path || []; + this.transform = + typeof ctx.transform !== "undefined" + ? ctx.transform.clone() + : new Matrix(); + this.globalCompositeOperation = ctx.globalCompositeOperation || "normal"; + this.globalAlpha = ctx.globalAlpha || 1.0; + this.clip_path = ctx.clip_path || []; + this.currentPoint = ctx.currentPoint || new Point(); + this.miterLimit = ctx.miterLimit || 10.0; + this.lastPoint = ctx.lastPoint || new Point(); + this.lineDashOffset = ctx.lineDashOffset || 0.0; + this.lineDash = ctx.lineDash || []; + this.margin = ctx.margin || [0, 0, 0, 0]; + this.prevPageLastElemOffset = ctx.prevPageLastElemOffset || 0; + + this.ignoreClearRect = + typeof ctx.ignoreClearRect === "boolean" ? ctx.ignoreClearRect : true; + return this; + }; + + //stub + var f2, + getHorizontalCoordinateString, + getVerticalCoordinateString, + getHorizontalCoordinate, + getVerticalCoordinate, + Point, + Rectangle, + Matrix, + _ctx; + jsPDFAPI.events.push([ + "initialized", + function() { + this.context2d = new Context2D(this); + + f2 = this.internal.f2; + getHorizontalCoordinateString = this.internal.getCoordinateString; + getVerticalCoordinateString = this.internal.getVerticalCoordinateString; + getHorizontalCoordinate = this.internal.getHorizontalCoordinate; + getVerticalCoordinate = this.internal.getVerticalCoordinate; + Point = this.internal.Point; + Rectangle = this.internal.Rectangle; + Matrix = this.internal.Matrix; + _ctx = new ContextLayer(); + } + ]); + + var Context2D = function(pdf) { + Object.defineProperty(this, "canvas", { + get: function() { + return { parentNode: false, style: false }; } + }); - if (branch._typename === 'TBranchObject') { - member = { - name: target_name, - typename: branch.fClassName, - virtual: leaf.fVirtual, - func(buf, obj) { - let clname = this.typename; - if (this.virtual) clname = buf.readFastString(buf.ntou1() + 1); - obj[this.name] = buf.classStreamer({}, clname); - } - }; - } else if ((branch.fType === kClonesNode) || (branch.fType === kSTLNode)) { - elem = createStreamerElement(target_name, kInt); + var _pdf = pdf; + Object.defineProperty(this, "pdf", { + get: function() { + return _pdf; + } + }); - if (!read_mode || (isStr(read_mode) && (read_mode[0] === '.')) || (read_mode === 1)) { - handle.process_arrays = false; + var _pageWrapXEnabled = false; + /** + * @name pageWrapXEnabled + * @type {boolean} + * @default false + */ + Object.defineProperty(this, "pageWrapXEnabled", { + get: function() { + return _pageWrapXEnabled; + }, + set: function(value) { + _pageWrapXEnabled = Boolean(value); + } + }); - member = { - name: target_name, - conttype: branch.fClonesName || clTObject, - reallocate: args.reallocate_objects, - func(buf, obj) { - const size = buf.ntoi4(); - let n = 0, arr = obj[this.name]; - if (!arr || this.reallocate) - arr = obj[this.name] = new Array(size); - else { - n = arr.length; - arr.length = size; // reallocate array - } + var _pageWrapYEnabled = false; + /** + * @name pageWrapYEnabled + * @type {boolean} + * @default true + */ + Object.defineProperty(this, "pageWrapYEnabled", { + get: function() { + return _pageWrapYEnabled; + }, + set: function(value) { + _pageWrapYEnabled = Boolean(value); + } + }); - while (n < size) arr[n++] = this.methods.Create(); // create new objects - } - }; + var _posX = 0; + /** + * @name posX + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "posX", { + get: function() { + return _posX; + }, + set: function(value) { + if (!isNaN(value)) { + _posX = value; + } + } + }); - if (isStr(read_mode) && (read_mode[0] === '.')) { - member.conttype = detectBranchMemberClass(branch.fBranches, branch.fName + read_mode); - if (!member.conttype) { - console.error(`Cannot select object ${read_mode} in the branch ${branch.fName}`); - return null; - } - } + var _posY = 0; + /** + * @name posY + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "posY", { + get: function() { + return _posY; + }, + set: function(value) { + if (!isNaN(value)) { + _posY = value; + } + } + }); - member.methods = makeMethodsList(member.conttype); + /** + * Gets or sets the page margin when using auto paging. Has no effect when {@link autoPaging} is off. + * @name margin + * @type {number|number[]} + * @default [0, 0, 0, 0] + */ + Object.defineProperty(this, "margin", { + get: function() { + return _ctx.margin; + }, + set: function(value) { + var margin; + if (typeof value === "number") { + margin = [value, value, value, value]; + } else { + margin = new Array(4); + margin[0] = value[0]; + margin[1] = value.length >= 2 ? value[1] : margin[0]; + margin[2] = value.length >= 3 ? value[2] : margin[0]; + margin[3] = value.length >= 4 ? value[3] : margin[1]; + } + _ctx.margin = margin; + } + }); - child_scan = (branch.fType === kClonesNode) ? kClonesMemberNode : kSTLMemberNode; - } - } else if ((object_class = getBranchObjectClass(branch, handle.tree))) { - if (read_mode === true) { - console.warn(`Object branch ${object_class} can not have data to be read directly`); - return null; - } + var _autoPaging = false; + /** + * Gets or sets the auto paging mode. When auto paging is enabled, the context2d will automatically draw on the + * next page if a shape or text chunk doesn't fit entirely on the current page. The context2d will create new + * pages if required. + * + * Context2d supports different modes: + *
      + *
    • + * false: Auto paging is disabled. + *
    • + *
    • + * true or 'slice': Will cut shapes or text chunks across page breaks. Will possibly + * slice text in half, making it difficult to read. + *
    • + *
    • + * 'text': Trys not to cut text in half across page breaks. Works best for documents consisting + * mostly of a single column of text. + *
    • + *
    + * @name Context2D#autoPaging + * @type {boolean|"slice"|"text"} + * @default false + */ + Object.defineProperty(this, "autoPaging", { + get: function() { + return _autoPaging; + }, + set: function(value) { + _autoPaging = value; + } + }); - handle.process_arrays = false; + var lastBreak = 0; + /** + * @name lastBreak + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "lastBreak", { + get: function() { + return lastBreak; + }, + set: function(value) { + lastBreak = value; + } + }); - const newtgt = new Array(target_object ? (target_object.length + 1) : 1); - for (let l = 0; l < newtgt.length - 1; ++l) - newtgt[l] = target_object[l]; - newtgt[newtgt.length - 1] = { name: target_name, lst: makeMethodsList(object_class) }; + var pageBreaks = []; + /** + * Y Position of page breaks. + * @name pageBreaks + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "pageBreaks", { + get: function() { + return pageBreaks; + }, + set: function(value) { + pageBreaks = value; + } + }); - if (!ScanBranches(branch.fBranches, newtgt, 0)) return null; + /** + * @name ctx + * @type {object} + * @default {} + */ + Object.defineProperty(this, "ctx", { + get: function() { + return _ctx; + }, + set: function(value) { + if (value instanceof ContextLayer) { + _ctx = value; + } + } + }); - return item; // this kind of branch does not have baskets and not need to be read - } else if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID === -1)) { - elem = createStreamerElement(target_name, branch.fClassName); + /** + * @name path + * @type {array} + * @default [] + */ + Object.defineProperty(this, "path", { + get: function() { + return _ctx.path; + }, + set: function(value) { + _ctx.path = value; + } + }); - if (elem.fType === kAny) { - const streamer = handle.file.getStreamer(branch.fClassName, { val: branch.fClassVersion, checksum: branch.fCheckSum }); - if (!streamer) { - elem = null; - console.warn('not found streamer!'); - } else { - member = { - name: target_name, - typename: branch.fClassName, - streamer, - func(buf, obj) { - const res = { _typename: this.typename }; - for (let n = 0; n < this.streamer.length; ++n) - this.streamer[n].func(buf, res); - obj[this.name] = res; - } - }; - } - } + /** + * @name ctxStack + * @type {array} + * @default [] + */ + var _ctxStack = []; + Object.defineProperty(this, "ctxStack", { + get: function() { + return _ctxStack; + }, + set: function(value) { + _ctxStack = value; + } + }); - // elem.fType = kAnyP; + /** + * Sets or returns the color, gradient, or pattern used to fill the drawing + * + * @name fillStyle + * @default #000000 + * @property {(color|gradient|pattern)} value The color of the drawing. Default value is #000000
    + * A gradient object (linear or radial) used to fill the drawing (not supported by context2d)
    + * A pattern object to use to fill the drawing (not supported by context2d) + */ + Object.defineProperty(this, "fillStyle", { + get: function() { + return this.ctx.fillStyle; + }, + set: function(value) { + var rgba; + rgba = getRGBA(value); - // only STL containers here - // if (!elem.fSTLtype) elem = null; - } else if (is_brelem && (nb_leaves <= 1)) { - elem = findBrachStreamerElement(branch, handle.file); + this.ctx.fillStyle = rgba.style; + this.ctx.isFillTransparent = rgba.a === 0; + this.ctx.fillOpacity = rgba.a; - // this is basic type - can try to solve problem differently - if (!elem && branch.fStreamerType && (branch.fStreamerType < 20)) - elem = createStreamerElement(target_name, branch.fStreamerType); - } else if (nb_leaves === 1) { - // no special constrains for the leaf names + this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); + this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); + } + }); - elem = createLeafElem(leaf, target_name); - } else if ((branch._typename === 'TBranch') && (nb_leaves > 1)) { - // branch with many elementary leaves + /** + * Sets or returns the color, gradient, or pattern used for strokes + * + * @name strokeStyle + * @default #000000 + * @property {color} color A CSS color value that indicates the stroke color of the drawing. Default value is #000000 (not supported by context2d) + * @property {gradient} gradient A gradient object (linear or radial) used to create a gradient stroke (not supported by context2d) + * @property {pattern} pattern A pattern object used to create a pattern stroke (not supported by context2d) + */ + Object.defineProperty(this, "strokeStyle", { + get: function() { + return this.ctx.strokeStyle; + }, + set: function(value) { + var rgba = getRGBA(value); - const leaves = new Array(nb_leaves); - let isok = true; - for (let l = 0; l < nb_leaves; ++l) { - leaves[l] = createMemberStreamer(createLeafElem(branch.fLeaves.arr[l]), handle.file); - if (!leaves[l]) isok = false; - } + this.ctx.strokeStyle = rgba.style; + this.ctx.isStrokeTransparent = rgba.a === 0; + this.ctx.strokeOpacity = rgba.a; - if (isok) { - member = { - name: target_name, - leaves, - func(buf, obj) { - let tgt = obj[this.name], l = 0; - if (!tgt) obj[this.name] = tgt = {}; - while (l < this.leaves.length) - this.leaves[l++].func(buf, tgt); - } - }; - } + if (rgba.a === 0) { + this.pdf.setDrawColor(255, 255, 255); + } else if (rgba.a === 1) { + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } else { + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } } + }); - if (!elem && !member) { - console.warn(`Not supported branch ${branch.fName} type ${branch._typename}`); - return null; + /** + * Sets or returns the style of the end caps for a line + * + * @name lineCap + * @default butt + * @property {(butt|round|square)} lineCap butt A flat edge is added to each end of the line
    + * round A rounded end cap is added to each end of the line
    + * square A square end cap is added to each end of the line
    + */ + Object.defineProperty(this, "lineCap", { + get: function() { + return this.ctx.lineCap; + }, + set: function(value) { + if (["butt", "round", "square"].indexOf(value) !== -1) { + this.ctx.lineCap = value; + this.pdf.setLineCap(value); + } } + }); - if (!member) { - member = createMemberStreamer(elem, handle.file); + /** + * Sets or returns the current line width + * + * @name lineWidth + * @default 1 + * @property {number} lineWidth The current line width, in pixels + */ + Object.defineProperty(this, "lineWidth", { + get: function() { + return this.ctx.lineWidth; + }, + set: function(value) { + if (!isNaN(value)) { + this.ctx.lineWidth = value; + this.pdf.setLineWidth(value); + } + } + }); - if ((member.base !== undefined) && member.basename) { - // when element represent base class, we need handling which differ from normal IO - member.func = function(buf, obj) { - if (!obj[this.name]) obj[this.name] = { _typename: this.basename }; - buf.classStreamer(obj[this.name], this.basename); - }; - } + /** + * Sets or returns the type of corner created, when two lines meet + */ + Object.defineProperty(this, "lineJoin", { + get: function() { + return this.ctx.lineJoin; + }, + set: function(value) { + if (["bevel", "round", "miter"].indexOf(value) !== -1) { + this.ctx.lineJoin = value; + this.pdf.setLineJoin(value); + } } + }); - if (item_cnt && isStr(read_mode)) { - member.name0 = item_cnt.name; + /** + * A number specifying the miter limit ratio in coordinate space units. Zero, negative, Infinity, and NaN values are ignored. The default value is 10.0. + * + * @name miterLimit + * @default 10 + */ + Object.defineProperty(this, "miterLimit", { + get: function() { + return this.ctx.miterLimit; + }, + set: function(value) { + if (!isNaN(value)) { + this.ctx.miterLimit = value; + this.pdf.setMiterLimit(value); + } + } + }); - const snames = target_name.split('.'); + Object.defineProperty(this, "textBaseline", { + get: function() { + return this.ctx.textBaseline; + }, + set: function(value) { + this.ctx.textBaseline = value; + } + }); - if (snames.length === 1) { - // no point in the name - just plain array of objects - member.get = (arr, n) => arr[n]; - } else if (read_mode === '$child$') { - console.error(`target name ${target_name} contains point, but suppose to be direct child`); - return null; - } else if (snames.length === 2) { - target_name = member.name = snames[1]; - member.name1 = snames[0]; - member.subtype1 = read_mode; - member.methods1 = makeMethodsList(member.subtype1); - member.get = function(arr, n) { - let obj1 = arr[n][this.name1]; - if (!obj1) obj1 = arr[n][this.name1] = this.methods1.Create(); - return obj1; - }; - } else { - // very complex task - we need to reconstruct several embedded members with their types - // try our best - but not all data types can be reconstructed correctly - // while classname is not enough - there can be different versions + Object.defineProperty(this, "textAlign", { + get: function() { + return this.ctx.textAlign; + }, + set: function(value) { + if (["right", "end", "center", "left", "start"].indexOf(value) !== -1) { + this.ctx.textAlign = value; + } + } + }); - if (!branch.fParentName) { - console.error(`Not possible to provide more than 2 parts in the target name ${target_name}`); - return null; - } + var _fontFaceMap = null; - target_name = member.name = snames.pop(); // use last element - member.snames = snames; // remember all sub-names - member.smethods = []; // and special handles to create missing objects + function getFontFaceMap(pdf, fontFaces) { + if (_fontFaceMap === null) { + var fontMap = pdf.getFontList(); - let parent_class = branch.fParentName; // unfortunately, without version + var convertedFontFaces = convertToFontFaces(fontMap); - for (let k = 0; k < snames.length; ++k) { - const chld_class = defineMemberTypeName(handle.file, parent_class, snames[k]); - member.smethods[k] = makeMethodsList(chld_class || 'AbstractClass'); - parent_class = chld_class; - } - member.get = function(arr, n) { - let obj1 = arr[n][this.snames[0]]; - if (!obj1) obj1 = arr[n][this.snames[0]] = this.smethods[0].Create(); - for (let k = 1; k < this.snames.length; ++k) { - let obj2 = obj1[this.snames[k]]; - if (!obj2) obj2 = obj1[this.snames[k]] = this.smethods[k].Create(); - obj1 = obj2; - } - return obj1; - }; - } + _fontFaceMap = buildFontFaceMap(convertedFontFaces.concat(fontFaces)); + } - // case when target is sub-object and need to be created before + return _fontFaceMap; + } - if (member.objs_branch_func) { - // STL branch provides special function for the reading - member.func = member.objs_branch_func; - } else { - member.func0 = member.func; + function convertToFontFaces(fontMap) { + var fontFaces = []; - member.func = function(buf, obj) { - const arr = obj[this.name0]; // objects array where reading is done - let n = 0; - while (n < arr.length) - this.func0(buf, this.get(arr, n++)); // read all individual object with standard functions - }; - } - } else if (item_cnt) { - handle.process_arrays = false; + Object.keys(fontMap).forEach(function(family) { + var styles = fontMap[family]; - if ((elem.fType === kDouble32) || (elem.fType === kFloat16)) { - // special handling for compressed floats + styles.forEach(function(style) { + var fontFace = null; - member.stl_size = item_cnt.name; - member.func = function(buf, obj) { - obj[this.name] = this.readarr(buf, obj[this.stl_size]); - }; - } else if (((elem.fType === kOffsetP + kDouble32) || (elem.fType === kOffsetP + kFloat16)) && branch.fBranchCount2) { - // special handling for variable arrays of compressed floats in branch - not tested + switch (style) { + case "bold": + fontFace = { + family: family, + weight: "bold" + }; + break; - member.stl_size = item_cnt.name; - member.arr_size = item_cnt2.name; - member.func = function(buf, obj) { - const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0); - for (let n = 0; n < sz0; ++n) - arr[n] = (buf.ntou1() === 1) ? this.readarr(buf, sz1[n]) : []; - obj[this.name] = arr; - }; - } else if (((elem.fType > 0) && (elem.fType < kOffsetL)) || (elem.fType === kTString) || - (((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL)) && branch.fBranchCount2)) { - // special handling of simple arrays - member = { - name: target_name, - stl_size: item_cnt.name, - type: elem.fType, - func(buf, obj) { - obj[this.name] = buf.readFastArray(obj[this.stl_size], this.type); - } - }; + case "italic": + fontFace = { + family: family, + style: "italic" + }; + break; - if (branch.fBranchCount2) { - member.type -= kOffsetP; - member.arr_size = item_cnt2.name; - member.func = function(buf, obj) { - const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0); - for (let n = 0; n < sz0; ++n) - arr[n] = (buf.ntou1() === 1) ? buf.readFastArray(sz1[n], this.type) : []; - obj[this.name] = arr; - }; - } - } else if ((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL) && member.cntname) - member.cntname = item_cnt.name; - else if (elem.fType === kStreamer) { - // with streamers one need to extend existing array + case "bolditalic": + fontFace = { + family: family, + weight: "bold", + style: "italic" + }; + break; - if (item_cnt2) - throw new Error('Second branch counter not supported yet with kStreamer'); + case "": + case "normal": + fontFace = { + family: family + }; + break; + } - // function provided by normal I/O - member.func = member.branch_func; - member.stl_size = item_cnt.name; - } else if ((elem.fType === kStreamLoop) || (elem.fType === kOffsetL + kStreamLoop)) { - if (item_cnt2) { - // special solution for kStreamLoop - member.stl_size = item_cnt.name; - member.cntname = item_cnt2.name; - member.func = member.branch_func; // this is special function, provided by base I/O - } else - member.cntname = item_cnt.name; - } else { - member.name = '$stl_member'; + // If font-face is still null here, it is a font with some styling we don't recognize and + // cannot map or it is a font added via the fontFaces option of .html(). + if (fontFace !== null) { + fontFace.ref = { + name: family, + style: style + }; - let loop_size_name; - if (item_cnt2) { - if (member.cntname) { - loop_size_name = item_cnt2.name; - member.cntname = '$loop_size'; - } else - throw new Error('Second branch counter not used - very BAD'); - } + fontFaces.push(fontFace); + } + }); + }); - const stlmember = { - name: target_name, - stl_size: item_cnt.name, - loop_size: loop_size_name, - member0: member, - func(buf, obj) { - const cnt = obj[this.stl_size], arr = new Array(cnt); - for (let n = 0; n < cnt; ++n) { - if (this.loop_size) obj.$loop_size = obj[this.loop_size][n]; - this.member0.func(buf, obj); - arr[n] = obj.$stl_member; - } - delete obj.$stl_member; - delete obj.$loop_size; - obj[this.name] = arr; - } - }; + return fontFaces; + } - member = stlmember; - } - } // if (item_cnt) + var _fontFaces = null; + /** + * A map of available font-faces, as passed in the options of + * .html(). If set a limited implementation of the font style matching + * algorithm defined by https://www.w3.org/TR/css-fonts-3/#font-matching-algorithm + * will be used. If not set it will fallback to previous behavior. + */ + + Object.defineProperty(this, "fontFaces", { + get: function() { + return _fontFaces; + }, + set: function(value) { + _fontFaceMap = null; + _fontFaces = value; + } + }); - // set name used to store result - member.name = target_name; + Object.defineProperty(this, "font", { + get: function() { + return this.ctx.font; + }, + set: function(value) { + this.ctx.font = value; + var rx, matches; + + //source: https://stackoverflow.com/a/10136041 + // eslint-disable-next-line no-useless-escape + rx = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z]+?)\s*$/i; + matches = rx.exec(value); + if (matches !== null) { + var fontStyle = matches[1]; + matches[2]; + var fontWeight = matches[3]; + var fontSize = matches[4]; + matches[5]; + var fontFamily = matches[6]; + } else { + return; + } + var rxFontSize = /^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i; + var fontSizeUnit = rxFontSize.exec(fontSize)[2]; + + if ("px" === fontSizeUnit) { + fontSize = Math.floor( + parseFloat(fontSize) * this.pdf.internal.scaleFactor + ); + } else if ("em" === fontSizeUnit) { + fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); + } else { + fontSize = Math.floor( + parseFloat(fontSize) * this.pdf.internal.scaleFactor + ); + } - item.member = member; // member for reading - if (elem) item.type = elem.fType; - item.index = handle.arr.length; // index in the global list of branches + this.pdf.setFontSize(fontSize); + var parts = parseFontFamily(fontFamily); - if (item_cnt) { - item.counters = [item_cnt.index]; - item_cnt.ascounter.push(item.index); + if (this.fontFaces) { + var fontFaceMap = getFontFaceMap(this.pdf, this.fontFaces); - if (item_cnt2) { - item.counters.push(item_cnt2.index); - item_cnt2.ascounter.push(item.index); - } - } + var rules = parts.map(function(ff) { + return { + family: ff, + stretch: "normal", // TODO: Extract font-stretch from font rule (perhaps write proper parser for it?) + weight: fontWeight, + style: fontStyle + }; + }); - handle.arr.push(item); + var font = resolveFontFace(fontFaceMap, rules); + this.pdf.setFont(font.ref.name, font.ref.style); + return; + } - // now one should add all other child branches - if (child_scan) - if (!ScanBranches(branch.fBranches, target_object, child_scan)) return null; + var style = ""; + if ( + fontWeight === "bold" || + parseInt(fontWeight, 10) >= 700 || + fontStyle === "bold" + ) { + style = "bold"; + } - return item; - } + if (fontStyle === "italic") { + style += "italic"; + } - // main loop to add all branches from selector for reading - for (let nn = 0; nn < selector.numBranches(); ++nn) { - const item = AddBranchForReading(selector.getBranch(nn), undefined, selector.nameOfBranch(nn), selector._directs[nn]); + if (style.length === 0) { + style = "normal"; + } + var jsPdfFontName = ""; + + var fallbackFonts = { + arial: "Helvetica", + Arial: "Helvetica", + verdana: "Helvetica", + Verdana: "Helvetica", + helvetica: "Helvetica", + Helvetica: "Helvetica", + "sans-serif": "Helvetica", + fixed: "Courier", + monospace: "Courier", + terminal: "Courier", + cursive: "Times", + fantasy: "Times", + serif: "Times" + }; - if (!item) { - selector.Terminate(false); - return Promise.reject(Error(`Fail to add branch ${selector.nameOfBranch(nn)}`)); + for (var i = 0; i < parts.length; i++) { + if ( + this.pdf.internal.getFont(parts[i], style, { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + break; + } else if ( + style === "bolditalic" && + this.pdf.internal.getFont(parts[i], "bold", { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + style = "bold"; + } else if ( + this.pdf.internal.getFont(parts[i], "normal", { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + style = "normal"; + break; + } + } + if (jsPdfFontName === "") { + for (var j = 0; j < parts.length; j++) { + if (fallbackFonts[parts[j]]) { + jsPdfFontName = fallbackFonts[parts[j]]; + break; + } + } + } + jsPdfFontName = jsPdfFontName === "" ? "Times" : jsPdfFontName; + this.pdf.setFont(jsPdfFontName, style); } - } + }); - // check if simple reading can be performed and there are direct data in branch + Object.defineProperty(this, "globalCompositeOperation", { + get: function() { + return this.ctx.globalCompositeOperation; + }, + set: function(value) { + this.ctx.globalCompositeOperation = value; + } + }); - for (let h = 1; (h < handle.arr.length) && handle.simple_read; ++h) { - const item = handle.arr[h], item0 = handle.arr[0]; + Object.defineProperty(this, "globalAlpha", { + get: function() { + return this.ctx.globalAlpha; + }, + set: function(value) { + this.ctx.globalAlpha = value; + } + }); - if ((item.numentries !== item0.numentries) || (item.numbaskets !== item0.numbaskets)) handle.simple_read = false; - for (let n = 0; n < item.numbaskets; ++n) { - if (item.getBasketEntry(n) !== item0.getBasketEntry(n)) - handle.simple_read = false; + /** + * A float specifying the amount of the line dash offset. The default value is 0.0. + * + * @name lineDashOffset + * @default 0.0 + */ + Object.defineProperty(this, "lineDashOffset", { + get: function() { + return this.ctx.lineDashOffset; + }, + set: function(value) { + this.ctx.lineDashOffset = value; + setLineDash.call(this); } - } + }); - // now calculate entries range + // Not HTML API + Object.defineProperty(this, "lineDash", { + get: function() { + return this.ctx.lineDash; + }, + set: function(value) { + this.ctx.lineDash = value; + setLineDash.call(this); + } + }); - handle.firstentry = handle.lastentry = 0; - for (let nn = 0; nn < handle.arr.length; ++nn) { - const branch = handle.arr[nn].branch, - e1 = branch.fFirstEntry ?? (branch.fBasketBytes[0] ? branch.fBasketEntry[0] : 0); - handle.firstentry = Math.max(handle.firstentry, e1); - handle.lastentry = (nn === 0) ? (e1 + branch.fEntries) : Math.min(handle.lastentry, e1 + branch.fEntries); - } + // Not HTML API + Object.defineProperty(this, "ignoreClearRect", { + get: function() { + return this.ctx.ignoreClearRect; + }, + set: function(value) { + this.ctx.ignoreClearRect = Boolean(value); + } + }); + }; - if (handle.firstentry >= handle.lastentry) { - selector.Terminate(false); - return Promise.reject(Error('No any common events for selected branches')); - } + /** + * Sets the line dash pattern used when stroking lines. + * @name setLineDash + * @function + * @description It uses an array of values that specify alternating lengths of lines and gaps which describe the pattern. + */ + Context2D.prototype.setLineDash = function(dashArray) { + this.lineDash = dashArray; + }; - handle.process_min = handle.firstentry; - handle.process_max = handle.lastentry; + /** + * gets the current line dash pattern. + * @name getLineDash + * @function + * @returns {Array} An Array of numbers that specify distances to alternately draw a line and a gap (in coordinate space units). If the number, when setting the elements, is odd, the elements of the array get copied and concatenated. For example, setting the line dash to [5, 15, 25] will result in getting back [5, 15, 25, 5, 15, 25]. + */ + Context2D.prototype.getLineDash = function() { + if (this.lineDash.length % 2) { + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getLineDash#return_value + return this.lineDash.concat(this.lineDash); + } else { + // The copied value is returned to prevent contamination from outside. + return this.lineDash.slice(); + } + }; - let resolveFunc, rejectFunc; // Promise methods + Context2D.prototype.fill = function() { + pathPreProcess.call(this, "fill", false); + }; - if (Number.isInteger(args.firstentry) && (args.firstentry > handle.firstentry) && (args.firstentry < handle.lastentry)) - handle.process_min = args.firstentry; + /** + * Actually draws the path you have defined + * + * @name stroke + * @function + * @description The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. The default color is black. + */ + Context2D.prototype.stroke = function() { + pathPreProcess.call(this, "stroke", false); + }; - handle.current_entry = handle.staged_now = handle.process_min; + /** + * Begins a path, or resets the current + * + * @name beginPath + * @function + * @description The beginPath() method begins a path, or resets the current path. + */ + Context2D.prototype.beginPath = function() { + this.path = [ + { + type: "begin" + } + ]; + }; - if (Number.isInteger(args.numentries) && (args.numentries > 0)) { - const max = handle.process_min + args.numentries; - if (max < handle.process_max) handle.process_max = max; - } + /** + * Moves the path to the specified point in the canvas, without creating a line + * + * @name moveTo + * @function + * @param x {Number} The x-coordinate of where to move the path to + * @param y {Number} The y-coordinate of where to move the path to + */ + Context2D.prototype.moveTo = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console$1.error("jsPDF.context2d.moveTo: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.moveTo"); + } - if (isFunc(selector.ProcessArrays) && handle.simple_read) { - // this is indication that selector can process arrays of values - // only strictly-matched tree structure can be used for that + var pt = this.ctx.transform.applyToPoint(new Point(x, y)); - for (let k = 0; k < handle.arr.length; ++k) { - const elem = handle.arr[k]; - if ((elem.type <= 0) || (elem.type >= kOffsetL) || (elem.type === kCharStar)) - handle.process_arrays = false; + this.path.push({ + type: "mt", + x: pt.x, + y: pt.y + }); + this.ctx.lastPoint = new Point(x, y); + }; + + /** + * Creates a path from the current point back to the starting point + * + * @name closePath + * @function + * @description The closePath() method creates a path from the current point back to the starting point. + */ + Context2D.prototype.closePath = function() { + var pathBegin = new Point(0, 0); + var i = 0; + for (i = this.path.length - 1; i !== -1; i--) { + if (this.path[i].type === "begin") { + if ( + typeof this.path[i + 1] === "object" && + typeof this.path[i + 1].x === "number" + ) { + pathBegin = new Point(this.path[i + 1].x, this.path[i + 1].y); + break; + } } + } + this.path.push({ + type: "close" + }); + this.ctx.lastPoint = new Point(pathBegin.x, pathBegin.y); + }; - if (handle.process_arrays) { - // create other members for fast processing + /** + * Adds a new point and creates a line to that point from the last specified point in the canvas + * + * @name lineTo + * @function + * @param x The x-coordinate of where to create the line to + * @param y The y-coordinate of where to create the line to + * @description The lineTo() method adds a new point and creates a line TO that point FROM the last specified point in the canvas (this method does not draw the line). + */ + Context2D.prototype.lineTo = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console$1.error("jsPDF.context2d.lineTo: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.lineTo"); + } - selector.tgtarr = {}; // object with arrays + var pt = this.ctx.transform.applyToPoint(new Point(x, y)); - for (let nn = 0; nn < handle.arr.length; ++nn) { - const item = handle.arr[nn], - elem = createStreamerElement(item.name, item.type); + this.path.push({ + type: "lt", + x: pt.x, + y: pt.y + }); + this.ctx.lastPoint = new Point(pt.x, pt.y); + }; - elem.fType = item.type + kOffsetL; - elem.fArrayLength = 10; - elem.fArrayDim = 1; - elem.fMaxIndex[0] = 10; // 10 if artificial number, will be replaced during reading + /** + * Clips a region of any shape and size from the original canvas + * + * @name clip + * @function + * @description The clip() method clips a region of any shape and size from the original canvas. + */ + Context2D.prototype.clip = function() { + this.ctx.clip_path = JSON.parse(JSON.stringify(this.path)); + pathPreProcess.call(this, null, true); + }; - item.arrmember = createMemberStreamer(elem, handle.file); - } - } - } else - handle.process_arrays = false; + /** + * Creates a cubic Bézier curve + * + * @name quadraticCurveTo + * @function + * @param cpx {Number} The x-coordinate of the Bézier control point + * @param cpy {Number} The y-coordinate of the Bézier control point + * @param x {Number} The x-coordinate of the ending point + * @param y {Number} The y-coordinate of the ending point + * @description The quadraticCurveTo() method adds a point to the current path by using the specified control points that represent a quadratic Bézier curve.

    A quadratic Bézier curve requires two points. The first point is a control point that is used in the quadratic Bézier calculation and the second point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. + */ + Context2D.prototype.quadraticCurveTo = function(cpx, cpy, x, y) { + if (isNaN(x) || isNaN(y) || isNaN(cpx) || isNaN(cpy)) { + console$1.error( + "jsPDF.context2d.quadraticCurveTo: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.quadraticCurveTo" + ); + } - /** read basket with tree data, selecting different files */ - function ReadBaskets(bitems) { - function ExtractPlaces() { - // extract places to read and define file name + var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); + var pt1 = this.ctx.transform.applyToPoint(new Point(cpx, cpy)); - const places = []; - let filename = ''; + this.path.push({ + type: "qct", + x1: pt1.x, + y1: pt1.y, + x: pt0.x, + y: pt0.y + }); + this.ctx.lastPoint = new Point(pt0.x, pt0.y); + }; - for (let n = 0; n < bitems.length; ++n) { - if (bitems[n].done) continue; + /** + * Creates a cubic Bézier curve + * + * @name bezierCurveTo + * @function + * @param cp1x {Number} The x-coordinate of the first Bézier control point + * @param cp1y {Number} The y-coordinate of the first Bézier control point + * @param cp2x {Number} The x-coordinate of the second Bézier control point + * @param cp2y {Number} The y-coordinate of the second Bézier control point + * @param x {Number} The x-coordinate of the ending point + * @param y {Number} The y-coordinate of the ending point + * @description The bezierCurveTo() method adds a point to the current path by using the specified control points that represent a cubic Bézier curve.

    A cubic bezier curve requires three points. The first two points are control points that are used in the cubic Bézier calculation and the last point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. + */ + Context2D.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(cp1x) || + isNaN(cp1y) || + isNaN(cp2x) || + isNaN(cp2y) + ) { + console$1.error( + "jsPDF.context2d.bezierCurveTo: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.bezierCurveTo" + ); + } + var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); + var pt1 = this.ctx.transform.applyToPoint(new Point(cp1x, cp1y)); + var pt2 = this.ctx.transform.applyToPoint(new Point(cp2x, cp2y)); + + this.path.push({ + type: "bct", + x1: pt1.x, + y1: pt1.y, + x2: pt2.x, + y2: pt2.y, + x: pt0.x, + y: pt0.y + }); + this.ctx.lastPoint = new Point(pt0.x, pt0.y); + }; - const branch = bitems[n].branch; + /** + * Creates an arc/curve (used to create circles, or parts of circles) + * + * @name arc + * @function + * @param x {Number} The x-coordinate of the center of the circle + * @param y {Number} The y-coordinate of the center of the circle + * @param radius {Number} The radius of the circle + * @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {Number} The ending angle, in radians + * @param counterclockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @description The arc() method creates an arc/curve (used to create circles, or parts of circles). + */ + Context2D.prototype.arc = function( + x, + y, + radius, + startAngle, + endAngle, + counterclockwise + ) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(radius) || + isNaN(startAngle) || + isNaN(endAngle) + ) { + console$1.error("jsPDF.context2d.arc: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.arc"); + } + counterclockwise = Boolean(counterclockwise); + + if (!this.ctx.transform.isIdentity) { + var xpt = this.ctx.transform.applyToPoint(new Point(x, y)); + x = xpt.x; + y = xpt.y; + + var x_radPt = this.ctx.transform.applyToPoint(new Point(0, radius)); + var x_radPt0 = this.ctx.transform.applyToPoint(new Point(0, 0)); + radius = Math.sqrt( + Math.pow(x_radPt.x - x_radPt0.x, 2) + + Math.pow(x_radPt.y - x_radPt0.y, 2) + ); + } + if (Math.abs(endAngle - startAngle) >= 2 * Math.PI) { + startAngle = 0; + endAngle = 2 * Math.PI; + } - if (places.length === 0) - filename = branch.fFileName; - else if (filename !== branch.fFileName) - continue; + this.path.push({ + type: "arc", + x: x, + y: y, + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + counterclockwise: counterclockwise + }); + // this.ctx.lastPoint(new Point(pt.x,pt.y)); + }; - bitems[n].selected = true; // mark which item was selected for reading + /** + * Creates an arc/curve between two tangents + * + * @name arcTo + * @function + * @param x1 {Number} The x-coordinate of the first tangent + * @param y1 {Number} The y-coordinate of the first tangent + * @param x2 {Number} The x-coordinate of the second tangent + * @param y2 {Number} The y-coordinate of the second tangent + * @param radius The radius of the arc + * @description The arcTo() method creates an arc/curve between two tangents on the canvas. + */ + // eslint-disable-next-line no-unused-vars + Context2D.prototype.arcTo = function(x1, y1, x2, y2, radius) { + throw new Error("arcTo not implemented."); + }; - places.push(branch.fBasketSeek[bitems[n].basket], branch.fBasketBytes[bitems[n].basket]); - } + /** + * Creates a rectangle + * + * @name rect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The rect() method creates a rectangle. + */ + Context2D.prototype.rect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console$1.error("jsPDF.context2d.rect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.rect"); + } + this.moveTo(x, y); + this.lineTo(x + w, y); + this.lineTo(x + w, y + h); + this.lineTo(x, y + h); + this.lineTo(x, y); + this.lineTo(x + w, y); + this.lineTo(x, y); + }; - return places.length > 0 ? { places, filename } : null; - } + /** + * Draws a "filled" rectangle + * + * @name fillRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The fillRect() method draws a "filled" rectangle. The default color of the fill is black. + */ + Context2D.prototype.fillRect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console$1.error("jsPDF.context2d.fillRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.fillRect"); + } + if (isFillTransparent.call(this)) { + return; + } + var tmp = {}; + if (this.lineCap !== "butt") { + tmp.lineCap = this.lineCap; + this.lineCap = "butt"; + } + if (this.lineJoin !== "miter") { + tmp.lineJoin = this.lineJoin; + this.lineJoin = "miter"; + } - function ReadProgress(value) { - if ((handle.staged_prev === handle.staged_now) || - (handle.process_max <= handle.process_min)) return; + this.beginPath(); + this.rect(x, y, w, h); + this.fill(); - const tm = new Date().getTime(); - if (tm - handle.progress_showtm < 500) return; // no need to show very often - handle.progress_showtm = tm; + if (tmp.hasOwnProperty("lineCap")) { + this.lineCap = tmp.lineCap; + } + if (tmp.hasOwnProperty("lineJoin")) { + this.lineJoin = tmp.lineJoin; + } + }; - const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) / - (handle.process_max - handle.process_min); - return handle.selector.ShowProgress(portion); - } + /** + * Draws a rectangle (no fill) + * + * @name strokeRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The strokeRect() method draws a rectangle (no fill). The default color of the stroke is black. + */ + Context2D.prototype.strokeRect = function strokeRect(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console$1.error("jsPDF.context2d.strokeRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.strokeRect"); + } + if (isStrokeTransparent.call(this)) { + return; + } + this.beginPath(); + this.rect(x, y, w, h); + this.stroke(); + }; - function ProcessBlobs(blobs, places) { - if (!blobs || ((places.length > 2) && (blobs.length * 2 !== places.length))) - return Promise.resolve(null); + /** + * Clears the specified pixels within a given rectangle + * + * @name clearRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle to clear, in pixels + * @param h {Number} The height of the rectangle to clear, in pixels + * @description We cannot clear PDF commands that were already written to PDF, so we use white instead.
    + * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set. + * This results in all calls to clearRect() to do nothing, and keep the canvas transparent. + * This flag is stored in the save/restore context and is managed the same way as other drawing states. + * + */ + Context2D.prototype.clearRect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console$1.error("jsPDF.context2d.clearRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.clearRect"); + } + if (this.ignoreClearRect) { + return; + } - if (places.length === 2) blobs = [blobs]; + this.fillStyle = "#ffffff"; + this.fillRect(x, y, w, h); + }; - function DoProcessing(k) { - for (; k < bitems.length; ++k) { - if (!bitems[k].selected) continue; + /** + * Saves the state of the current context + * + * @name save + * @function + */ + Context2D.prototype.save = function(doStackPush) { + doStackPush = typeof doStackPush === "boolean" ? doStackPush : true; + var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; + for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { + this.pdf.setPage(i + 1); + this.pdf.internal.out("q"); + } + this.pdf.setPage(tmpPageNumber); - bitems[k].selected = false; - bitems[k].done = true; + if (doStackPush) { + this.ctx.fontSize = this.pdf.internal.getFontSize(); + var ctx = new ContextLayer(this.ctx); + this.ctxStack.push(this.ctx); + this.ctx = ctx; + } + }; - const blob = blobs.shift(); - let buf = new TBuffer(blob, 0, handle.file); - const basket = buf.classStreamer({}, clTBasket); + /** + * Returns previously saved path state and attributes + * + * @name restore + * @function + */ + Context2D.prototype.restore = function(doStackPop) { + doStackPop = typeof doStackPop === "boolean" ? doStackPop : true; + var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; + for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { + this.pdf.setPage(i + 1); + this.pdf.internal.out("Q"); + } + this.pdf.setPage(tmpPageNumber); + + if (doStackPop && this.ctxStack.length !== 0) { + this.ctx = this.ctxStack.pop(); + this.fillStyle = this.ctx.fillStyle; + this.strokeStyle = this.ctx.strokeStyle; + this.font = this.ctx.font; + this.lineCap = this.ctx.lineCap; + this.lineWidth = this.ctx.lineWidth; + this.lineJoin = this.ctx.lineJoin; + this.lineDash = this.ctx.lineDash; + this.lineDashOffset = this.ctx.lineDashOffset; + } + }; - if (basket.fNbytes !== bitems[k].branch.fBasketBytes[bitems[k].basket]) - console.error(`mismatch in read basket sizes ${basket.fNbytes} != ${bitems[k].branch.fBasketBytes[bitems[k].basket]}`); + /** + * @name toDataURL + * @function + */ + Context2D.prototype.toDataURL = function() { + throw new Error("toDataUrl not implemented."); + }; - // items[k].obj = basket; // keep basket object itself if necessary + //helper functions - bitems[k].bskt_obj = basket; // only number of entries in the basket are relevant for the moment + /** + * Get the decimal values of r, g, b and a + * + * @name getRGBA + * @function + * @private + * @ignore + */ + var getRGBA = function(style) { + var rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; + var rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/; + var rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; - if (basket.fKeylen + basket.fObjlen === basket.fNbytes) { - // use data from original blob - buf.raw_shift = 0; + var r, g, b, a; - bitems[k].raw = buf; // here already unpacked buffer + if (style.isCanvasGradient === true) { + style = style.getColor(); + } - if (bitems[k].branch.fEntryOffsetLen > 0) - buf.readBasketEntryOffset(basket, buf.raw_shift); + if (!style) { + return { r: 0, g: 0, b: 0, a: 0, style: style }; + } - continue; - } + if (rxTransparent.test(style)) { + r = 0; + g = 0; + b = 0; + a = 0; + } else { + var matches = rxRgb.exec(style); + if (matches !== null) { + r = parseInt(matches[1]); + g = parseInt(matches[2]); + b = parseInt(matches[3]); + a = 1; + } else { + matches = rxRgba.exec(style); + if (matches !== null) { + r = parseInt(matches[1]); + g = parseInt(matches[2]); + b = parseInt(matches[3]); + a = parseFloat(matches[4]); + } else { + a = 1; + + if (typeof style === "string" && style.charAt(0) !== "#") { + var rgbColor = new RGBColor$1(style); + if (rgbColor.ok) { + style = rgbColor.toHex(); + } else { + style = "#000000"; + } + } + + if (style.length === 4) { + r = style.substring(1, 2); + r += r; + g = style.substring(2, 3); + g += g; + b = style.substring(3, 4); + b += b; + } else { + r = style.substring(1, 3); + g = style.substring(3, 5); + b = style.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + } + } + return { r: r, g: g, b: b, a: a, style: style }; + }; - // unpack data and create new blob - return R__unzip(blob, basket.fObjlen, false, buf.o).then(objblob => { - if (objblob) { - buf = new TBuffer(objblob, 0, handle.file); - buf.raw_shift = basket.fKeylen; - buf.fTagOffset = basket.fKeylen; - } else - throw new Error('FAIL TO UNPACK'); + /** + * @name isFillTransparent + * @function + * @private + * @ignore + * @returns {Boolean} + */ + var isFillTransparent = function() { + return this.ctx.isFillTransparent || this.globalAlpha == 0; + }; - bitems[k].raw = buf; // here already unpacked buffer + /** + * @name isStrokeTransparent + * @function + * @private + * @ignore + * @returns {Boolean} + */ + var isStrokeTransparent = function() { + return Boolean(this.ctx.isStrokeTransparent || this.globalAlpha == 0); + }; - if (bitems[k].branch.fEntryOffsetLen > 0) - buf.readBasketEntryOffset(basket, buf.raw_shift); + /** + * Draws "filled" text on the canvas + * + * @name fillText + * @function + * @param text {String} Specifies the text that will be written on the canvas + * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) + * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) + * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels + * @description The fillText() method draws filled text on the canvas. The default color of the text is black. + */ + Context2D.prototype.fillText = function(text, x, y, maxWidth) { + if (isNaN(x) || isNaN(y) || typeof text !== "string") { + console$1.error("jsPDF.context2d.fillText: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.fillText"); + } + maxWidth = isNaN(maxWidth) ? undefined : maxWidth; + if (isFillTransparent.call(this)) { + return; + } - return DoProcessing(k+1); // continue processing - }); - } + var degs = rad2deg(this.ctx.transform.rotation); - const req = ExtractPlaces(); - if (req) - return handle.file.readBuffer(req.places, req.filename, ReadProgress).then(blobs => ProcessBlobs(blobs)).catch(() => { return null; }); + // We only use X axis as scale hint + var scale = this.ctx.transform.scaleX; - return Promise.resolve(bitems); - } + putText.call(this, { + text: text, + x: x, + y: y, + scale: scale, + angle: degs, + align: this.textAlign, + maxWidth: maxWidth + }); + }; - return DoProcessing(0); - } + /** + * Draws text on the canvas (no fill) + * + * @name strokeText + * @function + * @param text {String} Specifies the text that will be written on the canvas + * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) + * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) + * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels + * @description The strokeText() method draws text (with no fill) on the canvas. The default color of the text is black. + */ + Context2D.prototype.strokeText = function(text, x, y, maxWidth) { + if (isNaN(x) || isNaN(y) || typeof text !== "string") { + console$1.error("jsPDF.context2d.strokeText: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.strokeText"); + } + if (isStrokeTransparent.call(this)) { + return; + } - const req = ExtractPlaces(); + maxWidth = isNaN(maxWidth) ? undefined : maxWidth; - // extract places where to read - if (req) - return handle.file.readBuffer(req.places, req.filename, ReadProgress).then(blobs => ProcessBlobs(blobs, req.places)).catch(() => { return null; }); + var degs = rad2deg(this.ctx.transform.rotation); + var scale = this.ctx.transform.scaleX; - return Promise.resolve(null); - } + putText.call(this, { + text: text, + x: x, + y: y, + scale: scale, + renderingMode: "stroke", + angle: degs, + align: this.textAlign, + maxWidth: maxWidth + }); + }; - function ReadNextBaskets() { - const bitems = []; - let totalsz = 0, isany = true, is_direct = false, min_staged = handle.process_max; + /** + * Returns an object that contains the width of the specified text + * + * @name measureText + * @function + * @param text {String} The text to be measured + * @description The measureText() method returns an object that contains the width of the specified text, in pixels. + * @returns {Number} + */ + Context2D.prototype.measureText = function(text) { + if (typeof text !== "string") { + console$1.error( + "jsPDF.context2d.measureText: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.measureText" + ); + } + var pdf = this.pdf; + var k = this.pdf.internal.scaleFactor; + + var fontSize = pdf.internal.getFontSize(); + var txtWidth = + (pdf.getStringUnitWidth(text) * fontSize) / pdf.internal.scaleFactor; + txtWidth *= Math.round(((k * 96) / 72) * 10000) / 10000; + + var TextMetrics = function(options) { + options = options || {}; + var _width = options.width || 0; + Object.defineProperty(this, "width", { + get: function() { + return _width; + } + }); + return this; + }; + return new TextMetrics({ width: txtWidth }); + }; - while ((totalsz < 1e6) && isany) { - isany = false; - // very important, loop over branches in reverse order - // let check counter branch after reading of normal branch is prepared - for (let n = handle.arr.length - 1; n >= 0; --n) { - const elem = handle.arr[n]; + //Transformations - while (elem.staged_basket < elem.numbaskets) { - const k = elem.staged_basket++; + /** + * Scales the current drawing bigger or smaller + * + * @name scale + * @function + * @param scalewidth {Number} Scales the width of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) + * @param scaleheight {Number} Scales the height of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) + * @description The scale() method scales the current drawing, bigger or smaller. + */ + Context2D.prototype.scale = function(scalewidth, scaleheight) { + if (isNaN(scalewidth) || isNaN(scaleheight)) { + console$1.error("jsPDF.context2d.scale: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.scale"); + } + var matrix = new Matrix(scalewidth, 0.0, 0.0, scaleheight, 0.0, 0.0); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; - // no need to read more baskets, process_max is not included - if (elem.getBasketEntry(k) >= handle.process_max) break; + /** + * Rotates the current drawing + * + * @name rotate + * @function + * @param angle {Number} The rotation angle, in radians. + * @description To calculate from degrees to radians: degrees*Math.PI/180.
    + * Example: to rotate 5 degrees, specify the following: 5*Math.PI/180 + */ + Context2D.prototype.rotate = function(angle) { + if (isNaN(angle)) { + console$1.error("jsPDF.context2d.rotate: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.rotate"); + } + var matrix = new Matrix( + Math.cos(angle), + Math.sin(angle), + -Math.sin(angle), + Math.cos(angle), + 0.0, + 0.0 + ); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; - // check which baskets need to be read - if (elem.first_readentry < 0) { - const lmt = elem.getBasketEntry(k + 1), - not_needed = (lmt <= handle.process_min); + /** + * Remaps the (0,0) position on the canvas + * + * @name translate + * @function + * @param x {Number} The value to add to horizontal (x) coordinates + * @param y {Number} The value to add to vertical (y) coordinates + * @description The translate() method remaps the (0,0) position on the canvas. + */ + Context2D.prototype.translate = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console$1.error("jsPDF.context2d.translate: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.translate"); + } + var matrix = new Matrix(1.0, 0.0, 0.0, 1.0, x, y); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; - // for (let d=0;d
    The transform() method replaces the current transformation matrix. It multiplies the current transformation matrix with the matrix described by:



    a c e

    b d f

    0 0 1

    In other words, the transform() method lets you scale, rotate, move, and skew the current context. + */ + Context2D.prototype.transform = function(a, b, c, d, e, f) { + if (isNaN(a) || isNaN(b) || isNaN(c) || isNaN(d) || isNaN(e) || isNaN(f)) { + console$1.error("jsPDF.context2d.transform: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.transform"); + } + var matrix = new Matrix(a, b, c, d, e, f); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; - if (not_needed) continue; // if that basket not required, check next + /** + * Resets the current transform to the identity matrix. Then runs transform() + * + * @name setTransform + * @function + * @param a {Number} Horizontal scaling + * @param b {Number} Horizontal skewing + * @param c {Number} Vertical skewing + * @param d {Number} Vertical scaling + * @param e {Number} Horizontal moving + * @param f {Number} Vertical moving + * @description Each object on the canvas has a current transformation matrix.

    The setTransform() method resets the current transform to the identity matrix, and then runs transform() with the same arguments.

    In other words, the setTransform() method lets you scale, rotate, move, and skew the current context. + */ + Context2D.prototype.setTransform = function(a, b, c, d, e, f) { + a = isNaN(a) ? 1 : a; + b = isNaN(b) ? 0 : b; + c = isNaN(c) ? 0 : c; + d = isNaN(d) ? 1 : d; + e = isNaN(e) ? 0 : e; + f = isNaN(f) ? 0 : f; + this.ctx.transform = new Matrix(a, b, c, d, e, f); + }; - elem.curr_basket = k; // basket where reading will start + var hasMargins = function() { + return ( + this.margin[0] > 0 || + this.margin[1] > 0 || + this.margin[2] > 0 || + this.margin[3] > 0 + ); + }; - elem.first_readentry = elem.getBasketEntry(k); // remember which entry will be read first - } + /** + * Draws an image, canvas, or video onto the canvas + * + * @function + * @param img {} Specifies the image, canvas, or video element to use + * @param sx {Number} Optional. The x coordinate where to start clipping + * @param sy {Number} Optional. The y coordinate where to start clipping + * @param swidth {Number} Optional. The width of the clipped image + * @param sheight {Number} Optional. The height of the clipped image + * @param x {Number} The x coordinate where to place the image on the canvas + * @param y {Number} The y coordinate where to place the image on the canvas + * @param width {Number} Optional. The width of the image to use (stretch or reduce the image) + * @param height {Number} Optional. The height of the image to use (stretch or reduce the image) + */ + Context2D.prototype.drawImage = function( + img, + sx, + sy, + swidth, + sheight, + x, + y, + width, + height + ) { + var imageProperties = this.pdf.getImageProperties(img); + var factorX = 1; + var factorY = 1; + + var clipFactorX = 1; + var clipFactorY = 1; + + if (typeof swidth !== "undefined" && typeof width !== "undefined") { + clipFactorX = width / swidth; + clipFactorY = height / sheight; + factorX = ((imageProperties.width / swidth) * width) / swidth; + factorY = ((imageProperties.height / sheight) * height) / sheight; + } - // check if basket already loaded in the branch + //is sx and sy are set and x and y not, set x and y with values of sx and sy + if (typeof x === "undefined") { + x = sx; + y = sy; + sx = 0; + sy = 0; + } - const bitem = { - id: n, // to find which element we are reading - branch: elem.branch, - basket: k, - raw: null // here should be result - }, bskt = elem.branch.fBaskets.arr[k]; - if (bskt) { - bitem.raw = bskt.fBufferRef; - if (bitem.raw) - bitem.raw.locate(0); // reset pointer - same branch may be read several times - else - bitem.raw = new TBuffer(null, 0, handle.file); // create dummy buffer - basket has no data - bitem.raw.raw_shift = bskt.fKeylen; + if (typeof swidth !== "undefined" && typeof width === "undefined") { + width = swidth; + height = sheight; + } + if (typeof swidth === "undefined" && typeof width === "undefined") { + width = imageProperties.width; + height = imageProperties.height; + } - if (bskt.fBufferRef && (elem.branch.fEntryOffsetLen > 0)) - bitem.raw.readBasketEntryOffset(bskt, bitem.raw.raw_shift); + var decomposedTransformationMatrix = this.ctx.transform.decompose(); + var angle = rad2deg(decomposedTransformationMatrix.rotate.shx); + var matrix = new Matrix(); + matrix = matrix.multiply(decomposedTransformationMatrix.translate); + matrix = matrix.multiply(decomposedTransformationMatrix.skew); + matrix = matrix.multiply(decomposedTransformationMatrix.scale); + var xRect = matrix.applyToRectangle( + new Rectangle( + x - sx * clipFactorX, + y - sy * clipFactorY, + swidth * factorX, + sheight * factorY + ) + ); + var pageArray = getPagesByPath.call(this, xRect); + var pages = []; + for (var ii = 0; ii < pageArray.length; ii += 1) { + if (pages.indexOf(pageArray[ii]) === -1) { + pages.push(pageArray[ii]); + } + } - bitem.bskt_obj = bskt; - is_direct = true; - elem.baskets[k] = bitem; - } else { - bitems.push(bitem); - totalsz += elem.branch.fBasketBytes[k]; - isany = true; - } + sortPages(pages); + + var clipPath; + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var i = min; i < max + 1; i++) { + this.pdf.setPage(i); + + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + drawPaths.call(this, "fill", true); + this.path = tmpPaths; + } + var tmpRect = JSON.parse(JSON.stringify(xRect)); + tmpRect = pathPositionRedo( + [tmpRect], + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + )[0]; + + const needsClipping = (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + this.pdf.addImage( + img, + "JPEG", + tmpRect.x, + tmpRect.y, + tmpRect.w, + tmpRect.h, + null, + null, + angle + ); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + } else { + this.pdf.addImage( + img, + "JPEG", + xRect.x, + xRect.y, + xRect.w, + xRect.h, + null, + null, + angle + ); + } + }; - elem.staged_entry = elem.getBasketEntry(k + 1); + var getPagesByPath = function(path, pageWrapX, pageWrapY) { + var result = []; + pageWrapX = pageWrapX || this.pdf.internal.pageSize.width; + pageWrapY = + pageWrapY || + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var yOffset = this.posY + this.ctx.prevPageLastElemOffset; - min_staged = Math.min(min_staged, elem.staged_entry); + switch (path.type) { + default: + case "mt": + case "lt": + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); + break; + case "arc": + result.push( + Math.floor((path.y + yOffset - path.radius) / pageWrapY) + 1 + ); + result.push( + Math.floor((path.y + yOffset + path.radius) / pageWrapY) + 1 + ); + break; + case "qct": + var rectOfQuadraticCurve = getQuadraticCurveBoundary( + this.ctx.lastPoint.x, + this.ctx.lastPoint.y, + path.x1, + path.y1, + path.x, + path.y + ); + result.push( + Math.floor((rectOfQuadraticCurve.y + yOffset) / pageWrapY) + 1 + ); + result.push( + Math.floor( + (rectOfQuadraticCurve.y + rectOfQuadraticCurve.h + yOffset) / + pageWrapY + ) + 1 + ); + break; + case "bct": + var rectOfBezierCurve = getBezierCurveBoundary( + this.ctx.lastPoint.x, + this.ctx.lastPoint.y, + path.x1, + path.y1, + path.x2, + path.y2, + path.x, + path.y + ); + result.push( + Math.floor((rectOfBezierCurve.y + yOffset) / pageWrapY) + 1 + ); + result.push( + Math.floor( + (rectOfBezierCurve.y + rectOfBezierCurve.h + yOffset) / pageWrapY + ) + 1 + ); + break; + case "rect": + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); + result.push(Math.floor((path.y + path.h + yOffset) / pageWrapY) + 1); + } - break; - } - } + for (var i = 0; i < result.length; i += 1) { + while (this.pdf.internal.getNumberOfPages() < result[i]) { + addPage.call(this); } + } + return result; + }; - if ((totalsz === 0) && !is_direct) { - handle.selector.Terminate(true); - return resolveFunc(handle.selector); + var addPage = function() { + var fillStyle = this.fillStyle; + var strokeStyle = this.strokeStyle; + var font = this.font; + var lineCap = this.lineCap; + var lineWidth = this.lineWidth; + var lineJoin = this.lineJoin; + this.pdf.addPage(); + this.fillStyle = fillStyle; + this.strokeStyle = strokeStyle; + this.font = font; + this.lineCap = lineCap; + this.lineWidth = lineWidth; + this.lineJoin = lineJoin; + }; + + var pathPositionRedo = function(paths, x, y) { + for (var i = 0; i < paths.length; i++) { + switch (paths[i].type) { + case "bct": + paths[i].x2 += x; + paths[i].y2 += y; + case "qct": + paths[i].x1 += x; + paths[i].y1 += y; + case "mt": + case "lt": + case "arc": + default: + paths[i].x += x; + paths[i].y += y; } + } + return paths; + }; - handle.staged_prev = handle.staged_now; - handle.staged_now = min_staged; + var sortPages = function(pages) { + return pages.sort(function(a, b) { + return a - b; + }); + }; - let portion = 0; - if (handle.process_max > handle.process_min) - portion = (handle.staged_prev - handle.process_min) / (handle.process_max - handle.process_min); + var pathPreProcess = function(rule, isClip) { + var fillStyle = this.fillStyle; + var strokeStyle = this.strokeStyle; + var lineCap = this.lineCap; + var oldLineWidth = this.lineWidth; + var lineWidth = Math.abs(oldLineWidth * this.ctx.transform.scaleX); + var lineJoin = this.lineJoin; + + var origPath = JSON.parse(JSON.stringify(this.path)); + var xPath = JSON.parse(JSON.stringify(this.path)); + var clipPath; + var tmpPath; + var pages = []; + + for (var i = 0; i < xPath.length; i++) { + if (typeof xPath[i].x !== "undefined") { + var page = getPagesByPath.call(this, xPath[i]); + + for (var ii = 0; ii < page.length; ii += 1) { + if (pages.indexOf(page[ii]) === -1) { + pages.push(page[ii]); + } + } + } + } - if (handle.selector.ShowProgress(portion) === 'break') { - handle.selector.Terminate(true); - return resolveFunc(handle.selector); + for (var j = 0; j < pages.length; j++) { + while (this.pdf.internal.getNumberOfPages() < pages[j]) { + addPage.call(this); + } + } + sortPages(pages); + + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var k = min; k < max + 1; k++) { + this.pdf.setPage(k); + + this.fillStyle = fillStyle; + this.strokeStyle = strokeStyle; + this.lineCap = lineCap; + this.lineWidth = lineWidth; + this.lineJoin = lineJoin; + + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = k === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + k === 1 ? 0 : firstPageHeight + (k - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + drawPaths.call(this, rule, true); + this.path = tmpPaths; + } + tmpPath = JSON.parse(JSON.stringify(origPath)); + this.path = pathPositionRedo( + tmpPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + if (isClip === false || k === 0) { + const needsClipping = (k > min || k < max) && hasMargins.call(this); + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + drawPaths.call(this, rule, isClip); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + this.lineWidth = oldLineWidth; } + } else { + this.lineWidth = lineWidth; + drawPaths.call(this, rule, isClip); + this.lineWidth = oldLineWidth; + } + this.path = origPath; + }; - handle.progress_showtm = new Date().getTime(); + /** + * Processes the paths + * + * @function + * @param rule {String} + * @param isClip {Boolean} + * @private + * @ignore + */ + var drawPaths = function(rule, isClip) { + if (rule === "stroke" && !isClip && isStrokeTransparent.call(this)) { + return; + } - if (totalsz > 0) - return ReadBaskets(bitems).then(ProcessBaskets); + if (rule !== "stroke" && !isClip && isFillTransparent.call(this)) { + return; + } - if (is_direct) return ProcessBaskets([]); // directly process baskets + var moves = []; - throw new Error('No any data is requested - never come here'); - } + //var alpha = (this.ctx.fillOpacity < 1) ? this.ctx.fillOpacity : this.ctx.globalAlpha; + var delta; + var xPath = this.path; + for (var i = 0; i < xPath.length; i++) { + var pt = xPath[i]; - function ProcessBaskets(bitems) { - // this is call-back when next baskets are read + switch (pt.type) { + case "begin": + moves.push({ + begin: true + }); + break; - if ((handle.selector._break !== 0) || (bitems === null)) { - handle.selector.Terminate(false); - return resolveFunc(handle.selector); - } + case "close": + moves.push({ + close: true + }); + break; - // redistribute read baskets over branches - for (let n = 0; n < bitems.length; ++n) - handle.arr[bitems[n].id].baskets[bitems[n].basket] = bitems[n]; + case "mt": + moves.push({ + start: pt, + deltas: [], + abs: [] + }); + break; - // now process baskets + case "lt": + var iii = moves.length; + if (xPath[i - 1] && !isNaN(xPath[i - 1].x)) { + delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + if (iii > 0) { + for (iii; iii >= 0; iii--) { + if ( + moves[iii - 1].close !== true && + moves[iii - 1].begin !== true + ) { + moves[iii - 1].deltas.push(delta); + moves[iii - 1].abs.push(pt); + break; + } + } + } + } + break; - let isanyprocessed = false; + case "bct": + delta = [ + pt.x1 - xPath[i - 1].x, + pt.y1 - xPath[i - 1].y, + pt.x2 - xPath[i - 1].x, + pt.y2 - xPath[i - 1].y, + pt.x - xPath[i - 1].x, + pt.y - xPath[i - 1].y + ]; + moves[moves.length - 1].deltas.push(delta); + break; - while (true) { - let loopentries = 100000000, n, elem; + case "qct": + var x1 = xPath[i - 1].x + (2.0 / 3.0) * (pt.x1 - xPath[i - 1].x); + var y1 = xPath[i - 1].y + (2.0 / 3.0) * (pt.y1 - xPath[i - 1].y); + var x2 = pt.x + (2.0 / 3.0) * (pt.x1 - pt.x); + var y2 = pt.y + (2.0 / 3.0) * (pt.y1 - pt.y); + var x3 = pt.x; + var y3 = pt.y; + delta = [ + x1 - xPath[i - 1].x, + y1 - xPath[i - 1].y, + x2 - xPath[i - 1].x, + y2 - xPath[i - 1].y, + x3 - xPath[i - 1].x, + y3 - xPath[i - 1].y + ]; + moves[moves.length - 1].deltas.push(delta); + break; - // first loop used to check if all required data exists - for (n = 0; n < handle.arr.length; ++n) { - elem = handle.arr[n]; + case "arc": + moves.push({ + deltas: [], + abs: [], + arc: true + }); - if (!elem.raw || !elem.basket || (elem.first_entry + elem.basket.fNevBuf <= handle.current_entry)) { - delete elem.raw; - delete elem.basket; + if (Array.isArray(moves[moves.length - 1].abs)) { + moves[moves.length - 1].abs.push(pt); + } + break; + } + } + var style; + if (!isClip) { + if (rule === "stroke") { + style = "stroke"; + } else { + style = "fill"; + } + } else { + style = null; + } - if ((elem.curr_basket >= elem.numbaskets)) { - if (n === 0) { - handle.selector.Terminate(true); - return resolveFunc(handle.selector); - } - continue; // ignore non-master branch - } + var began = false; + for (var k = 0; k < moves.length; k++) { + if (moves[k].arc) { + var arcs = moves[k].abs; + + for (var ii = 0; ii < arcs.length; ii++) { + var arc = arcs[ii]; + + if (arc.type === "arc") { + drawArc.call( + this, + arc.x, + arc.y, + arc.radius, + arc.startAngle, + arc.endAngle, + arc.counterclockwise, + undefined, + isClip, + !began + ); + } else { + drawLine.call(this, arc.x, arc.y); + } + began = true; + } + } else if (moves[k].close === true) { + this.pdf.internal.out("h"); + began = false; + } else if (moves[k].begin !== true) { + var x = moves[k].start.x; + var y = moves[k].start.y; + drawLines.call(this, moves[k].deltas, x, y); + began = true; + } + } - // this is single response from the tree, includes branch, bakset number, raw data - const bitem = elem.baskets[elem.curr_basket]; + if (style) { + putStyle.call(this, style); + } + if (isClip) { + doClip.call(this); + } + }; - // basket not read - if (!bitem) { - // no data, but no any event processed - problem - if (!isanyprocessed) { - handle.selector.Terminate(false); - return rejectFunc(Error(`no data for ${elem.branch.fName} basket ${elem.curr_basket}`)); - } + var getBaseline = function(y) { + var height = + this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; + var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); + switch (this.ctx.textBaseline) { + case "bottom": + return y - descent; + case "top": + return y + height - descent; + case "hanging": + return y + height - 2 * descent; + case "middle": + return y + height / 2 - descent; + case "ideographic": + // TODO not implemented + return y; + case "alphabetic": + default: + return y; + } + }; - // try to read next portion of tree data - return ReadNextBaskets(); - } + var getTextBottom = function(yBaseLine) { + var height = + this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; + var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); + return yBaseLine + descent; + }; - elem.raw = bitem.raw; - elem.basket = bitem.bskt_obj; - // elem.nev = bitem.fNevBuf; // number of entries in raw buffer - elem.first_entry = elem.getBasketEntry(bitem.basket); + Context2D.prototype.createLinearGradient = function createLinearGradient() { + var canvasGradient = function canvasGradient() {}; - bitem.raw = null; // remove reference on raw buffer - bitem.branch = null; // remove reference on the branch - bitem.bskt_obj = null; // remove reference on the branch - elem.baskets[elem.curr_basket++] = undefined; // remove from array - } + canvasGradient.colorStops = []; + canvasGradient.addColorStop = function(offset, color) { + this.colorStops.push([offset, color]); + }; - // define how much entries can be processed before next raw buffer will be finished - loopentries = Math.min(loopentries, elem.first_entry + elem.basket.fNevBuf - handle.current_entry); - } + canvasGradient.getColor = function() { + if (this.colorStops.length === 0) { + return "#000000"; + } - // second loop extracts all required data + return this.colorStops[0][1]; + }; - // do not read too much - if (handle.current_entry + loopentries > handle.process_max) - loopentries = handle.process_max - handle.current_entry; + canvasGradient.isCanvasGradient = true; + return canvasGradient; + }; + Context2D.prototype.createPattern = function createPattern() { + return this.createLinearGradient(); + }; + Context2D.prototype.createRadialGradient = function createRadialGradient() { + return this.createLinearGradient(); + }; - if (handle.process_arrays && (loopentries > 1)) { - // special case - read all data from baskets as arrays + /** + * + * @param x Edge point X + * @param y Edge point Y + * @param r Radius + * @param a1 start angle + * @param a2 end angle + * @param counterclockwise + * @param style + * @param isClip + */ + var drawArc = function( + x, + y, + r, + a1, + a2, + counterclockwise, + style, + isClip, + includeMove + ) { + // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html + var curves = createArc.call(this, r, a1, a2, counterclockwise); + + for (var i = 0; i < curves.length; i++) { + var curve = curves[i]; + if (i === 0) { + if (includeMove) { + doMove.call(this, curve.x1 + x, curve.y1 + y); + } else { + drawLine.call(this, curve.x1 + x, curve.y1 + y); + } + } + drawCurve.call( + this, + x, + y, + curve.x2, + curve.y2, + curve.x3, + curve.y3, + curve.x4, + curve.y4 + ); + } - for (n = 0; n < handle.arr.length; ++n) { - elem = handle.arr[n]; + if (!isClip) { + putStyle.call(this, style); + } else { + doClip.call(this); + } + }; - elem.getEntry(handle.current_entry); + var putStyle = function(style) { + switch (style) { + case "stroke": + this.pdf.internal.out("S"); + break; + case "fill": + this.pdf.internal.out("f"); + break; + } + }; - elem.arrmember.arrlength = loopentries; - elem.arrmember.func(elem.raw, handle.selector.tgtarr); + var doClip = function() { + this.pdf.clip(); + this.pdf.discardPath(); + }; - elem.raw = null; - } + var doMove = function(x, y) { + this.pdf.internal.out( + getHorizontalCoordinateString(x) + + " " + + getVerticalCoordinateString(y) + + " m" + ); + }; - handle.selector.ProcessArrays(handle.current_entry); + var putText = function(options) { + var textAlign; + switch (options.align) { + case "right": + case "end": + textAlign = "right"; + break; + case "center": + textAlign = "center"; + break; + case "left": + case "start": + default: + textAlign = "left"; + break; + } - handle.current_entry += loopentries; + var textDimensions = this.pdf.getTextDimensions(options.text); + var yBaseLine = getBaseline.call(this, options.y); + var yBottom = getTextBottom.call(this, yBaseLine); + var yTop = yBottom - textDimensions.h; - isanyprocessed = true; - } else { - // main processing loop - while (loopentries--) { - for (n = 0; n < handle.arr.length; ++n) { - elem = handle.arr[n]; + var pt = this.ctx.transform.applyToPoint(new Point(options.x, yBaseLine)); + var decomposedTransformationMatrix = this.ctx.transform.decompose(); + var matrix = new Matrix(); + matrix = matrix.multiply(decomposedTransformationMatrix.translate); + matrix = matrix.multiply(decomposedTransformationMatrix.skew); + matrix = matrix.multiply(decomposedTransformationMatrix.scale); - // locate buffer offset at proper place - elem.getEntry(handle.current_entry); + var baselineRect = this.ctx.transform.applyToRectangle( + new Rectangle(options.x, yBaseLine, textDimensions.w, textDimensions.h) + ); + var textBounds = matrix.applyToRectangle( + new Rectangle(options.x, yTop, textDimensions.w, textDimensions.h) + ); + var pageArray = getPagesByPath.call(this, textBounds); + var pages = []; + for (var ii = 0; ii < pageArray.length; ii += 1) { + if (pages.indexOf(pageArray[ii]) === -1) { + pages.push(pageArray[ii]); + } + } - elem.member.func(elem.raw, elem.getTarget(handle.selector.tgtobj)); - } + sortPages(pages); + + var clipPath, oldSize, oldLineWidth; + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var i = min; i < max + 1; i++) { + this.pdf.setPage(i); + + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusBottomMargin = + this.pdf.internal.pageSize.height - this.margin[2]; + var pageHeightMinusMargins = + pageHeightMinusBottomMargin - this.margin[0]; + var pageWidthMinusRightMargin = + this.pdf.internal.pageSize.width - this.margin[1]; + var pageWidthMinusMargins = pageWidthMinusRightMargin - this.margin[3]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -1 * previousPageHeightSum + topMargin + ); + drawPaths.call(this, "fill", true); + this.path = tmpPaths; + } + var textBoundsOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(textBounds))], + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + )[0]; + + if (options.scale >= 0.01) { + oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * options.scale); + oldLineWidth = this.lineWidth; + this.lineWidth = oldLineWidth * options.scale; + } - handle.selector.Process(handle.current_entry); + var doSlice = this.autoPaging !== "text"; + + if ( + doSlice || + textBoundsOnPage.y + textBoundsOnPage.h <= pageHeightMinusBottomMargin + ) { + if ( + doSlice || + (textBoundsOnPage.y >= topMargin && + textBoundsOnPage.x <= pageWidthMinusRightMargin) + ) { + var croppedText = doSlice + ? options.text + : this.pdf.splitTextToSize( + options.text, + options.maxWidth || + pageWidthMinusRightMargin - textBoundsOnPage.x + )[0]; + var baseLineRectOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(baselineRect))], + this.posX + this.margin[3], + -previousPageHeightSum + + topMargin + + this.ctx.prevPageLastElemOffset + )[0]; + + const needsClipping = + doSlice && (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } - handle.current_entry++; + this.pdf.text( + croppedText, + baseLineRectOnPage.x, + baseLineRectOnPage.y, + { + angle: options.angle, + align: textAlign, + renderingMode: options.renderingMode + } + ); - isanyprocessed = true; + if (needsClipping) { + this.pdf.restoreGraphicsState(); } - } + } + } else { + // This text is the last element of the page, but it got cut off due to the margin + // so we render it in the next page - if (handle.current_entry >= handle.process_max) { - handle.selector.Terminate(true); - return resolveFunc(handle.selector); - } + if (textBoundsOnPage.y < pageHeightMinusBottomMargin) { + // As a result, all other elements have their y offset increased + this.ctx.prevPageLastElemOffset += + pageHeightMinusBottomMargin - textBoundsOnPage.y; + } + } + + if (options.scale >= 0.01) { + this.pdf.setFontSize(oldSize); + this.lineWidth = oldLineWidth; + } } - } + } else { + if (options.scale >= 0.01) { + oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * options.scale); + oldLineWidth = this.lineWidth; + this.lineWidth = oldLineWidth * options.scale; + } + this.pdf.text(options.text, pt.x + this.posX, pt.y + this.posY, { + angle: options.angle, + align: textAlign, + renderingMode: options.renderingMode, + maxWidth: options.maxWidth + }); - return new Promise((resolve, reject) => { - resolveFunc = resolve; - rejectFunc = reject; + if (options.scale >= 0.01) { + this.pdf.setFontSize(oldSize); + this.lineWidth = oldLineWidth; + } + } + }; - // call begin before first entry is read - handle.selector.Begin(tree); + var drawLine = function(x, y, prevX, prevY) { + prevX = prevX || 0; + prevY = prevY || 0; - ReadNextBaskets(); - }); -} + this.pdf.internal.out( + getHorizontalCoordinateString(x + prevX) + + " " + + getVerticalCoordinateString(y + prevY) + + " l" + ); + }; -/** @summary implementation of TTree::Draw - * @param {object|string} args - different setting or simply draw expression - * @param {string} args.expr - draw expression - * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') - * @param {string} [args.drawopt=undefined] - draw options for result histogram - * @param {number} [args.firstentry=0] - first entry to process - * @param {number} [args.numentries=undefined] - number of entries to process, all by default - * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing - * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument - * @return {Promise} with produced object */ -async function treeDraw(tree, args) { - if (isStr(args)) args = { expr: args }; + var drawLines = function(lines, x, y) { + return this.pdf.lines(lines, x, y, null, null); + }; - if (!isStr(args.expr)) args.expr = ''; + var drawCurve = function(x, y, x1, y1, x2, y2, x3, y3) { + this.pdf.internal.out( + [ + f2(getHorizontalCoordinate(x1 + x)), + f2(getVerticalCoordinate(y1 + y)), + f2(getHorizontalCoordinate(x2 + x)), + f2(getVerticalCoordinate(y2 + y)), + f2(getHorizontalCoordinate(x3 + x)), + f2(getVerticalCoordinate(y3 + y)), + "c" + ].join(" ") + ); + }; - const selector = new TDrawSelector(); + /** + * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius. + * + * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + * @function createArc + */ + var createArc = function(radius, startAngle, endAngle, anticlockwise) { + var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below + var twoPi = Math.PI * 2; + var halfPi = Math.PI / 2.0; - if (args.branch) { - if (!selector.drawOnlyBranch(tree, args.branch, args.expr, args)) - return Promise.reject(Error(`Fail to create draw expression ${args.expr} for branch ${args.branch.fName}`)); - } else { - if (!selector.parseDrawExpression(tree, args)) - return Promise.reject(Error(`Fail to create draw expression ${args.expr}`)); - } + while (startAngle > endAngle) { + startAngle = startAngle - twoPi; + } + var totalAngle = Math.abs(endAngle - startAngle); + if (totalAngle < twoPi) { + if (anticlockwise) { + totalAngle = twoPi - totalAngle; + } + } - selector.setCallback(null, args.progress); + // Compute the sequence of arc curves, up to PI/2 at a time. + var curves = []; - return treeProcess(tree, selector, args).then(() => selector.hist); -} + // clockwise or counterclockwise + var sgn = anticlockwise ? -1 : 1; -/** @summary Performs generic I/O test for all branches in the TTree - * @desc Used when 'testio' draw option for TTree is specified - * @private */ -function treeIOTest(tree, args) { - const branches = [], names = [], nchilds = []; + var a1 = startAngle; + for (; totalAngle > EPSILON; ) { + var remain = sgn * Math.min(totalAngle, halfPi); + var a2 = a1 + remain; + curves.push(createSmallArc.call(this, radius, a1, a2)); + totalAngle -= Math.abs(a2 - a1); + a1 = a2; + } - function collectBranches(obj, prntname = '') { - if (!obj?.fBranches) return 0; + return curves; + }; - let cnt = 0; + /** + * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r. + * + * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + * + * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378. + */ + var createSmallArc = function(r, a1, a2) { + var a = (a2 - a1) / 2.0; + + var x4 = r * Math.cos(a); + var y4 = r * Math.sin(a); + var x1 = x4; + var y1 = -y4; + + var q1 = x1 * x1 + y1 * y1; + var q2 = q1 + x1 * x4 + y1 * y4; + var k2 = ((4 / 3) * (Math.sqrt(2 * q1 * q2) - q2)) / (x1 * y4 - y1 * x4); + + var x2 = x1 - k2 * y1; + var y2 = y1 + k2 * x1; + var x3 = x2; + var y3 = -y2; + + var ar = a + a1; + var cos_ar = Math.cos(ar); + var sin_ar = Math.sin(ar); + + return { + x1: r * Math.cos(a1), + y1: r * Math.sin(a1), + x2: x2 * cos_ar - y2 * sin_ar, + y2: x2 * sin_ar + y2 * cos_ar, + x3: x3 * cos_ar - y3 * sin_ar, + y3: x3 * sin_ar + y3 * cos_ar, + x4: r * Math.cos(a2), + y4: r * Math.sin(a2) + }; + }; - for (let n = 0; n < obj.fBranches.arr.length; ++n) { - const br = obj.fBranches.arr[n], - name = (prntname ? prntname + '/' : '') + br.fName; - branches.push(br); - names.push(name); - nchilds.push(0); - const pos = nchilds.length - 1; - cnt += (br.fLeaves?.arr?.length ?? 0); - const nchld = collectBranches(br, name); + var rad2deg = function(value) { + return (value * 180) / Math.PI; + }; - cnt += nchld; - nchilds[pos] = nchld; + var getQuadraticCurveBoundary = function(sx, sy, cpx, cpy, ex, ey) { + var midX1 = sx + (cpx - sx) * 0.5; + var midY1 = sy + (cpy - sy) * 0.5; + var midX2 = ex + (cpx - ex) * 0.5; + var midY2 = ey + (cpy - ey) * 0.5; + var resultX1 = Math.min(sx, ex, midX1, midX2); + var resultX2 = Math.max(sx, ex, midX1, midX2); + var resultY1 = Math.min(sy, ey, midY1, midY2); + var resultY2 = Math.max(sy, ey, midY1, midY2); + return new Rectangle( + resultX1, + resultY1, + resultX2 - resultX1, + resultY2 - resultY1 + ); + }; + + //De Casteljau algorithm + var getBezierCurveBoundary = function(ax, ay, bx, by, cx, cy, dx, dy) { + var tobx = bx - ax; + var toby = by - ay; + var tocx = cx - bx; + var tocy = cy - by; + var todx = dx - cx; + var tody = dy - cy; + var precision = 40; + var d, + i, + px, + py, + qx, + qy, + rx, + ry, + tx, + ty, + sx, + sy, + x, + y, + minx, + miny, + maxx, + maxy, + toqx, + toqy, + torx, + tory, + totx, + toty; + for (i = 0; i < precision + 1; i++) { + d = i / precision; + px = ax + d * tobx; + py = ay + d * toby; + qx = bx + d * tocx; + qy = by + d * tocy; + rx = cx + d * todx; + ry = cy + d * tody; + toqx = qx - px; + toqy = qy - py; + torx = rx - qx; + tory = ry - qy; + + sx = px + d * toqx; + sy = py + d * toqy; + tx = qx + d * torx; + ty = qy + d * tory; + totx = tx - sx; + toty = ty - sy; + + x = sx + d * totx; + y = sy + d * toty; + if (i == 0) { + minx = x; + miny = y; + maxx = x; + maxy = y; + } else { + minx = Math.min(minx, x); + miny = Math.min(miny, y); + maxx = Math.max(maxx, x); + maxy = Math.max(maxy, y); } - return cnt; - } + } + return new Rectangle( + Math.round(minx), + Math.round(miny), + Math.round(maxx - minx), + Math.round(maxy - miny) + ); + }; - const numleaves = collectBranches(tree); - let selector; + var getPrevLineDashValue = function(lineDash, lineDashOffset) { + return JSON.stringify({ + lineDash: lineDash, + lineDashOffset: lineDashOffset + }); + }; - names.push(`Total are ${branches.length} branches with ${numleaves} leaves`); + var setLineDash = function() { + // Avoid unnecessary line dash declarations. + if ( + !this.prevLineDash && + !this.ctx.lineDash.length && + !this.ctx.lineDashOffset + ) { + return; + } - function testBranch(nbr) { - if (nbr >= branches.length) - return Promise.resolve(true); + // Avoid unnecessary line dash declarations. + const nextLineDash = getPrevLineDashValue( + this.ctx.lineDash, + this.ctx.lineDashOffset + ); + if (this.prevLineDash !== nextLineDash) { + this.pdf.setLineDash(this.ctx.lineDash, this.ctx.lineDashOffset); + this.prevLineDash = nextLineDash; + } + }; +})(jsPDF.API); + +// DEFLATE is a complex format; to read this code, you should probably check the RFC first: + +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array; +// fixed length extra bits +var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + // numbers here are at max 18 bits + var r = new i32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; + } + } + return { b: b, r: r }; +}; +var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), revfd = _b.r; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1; +} +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) { + if (cd[i]) + ++l[cd[i] - 1]; + } + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 1; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); + } + } + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5); +// get end of byte +var shft = function (p) { return ((p + 7) / 8) | 0; }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + return new u8(v.subarray(s, e)); +}; +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; +}; +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; + d[o + 2] |= v >> 16; +}; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return { t: et, l: 0 }; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return { t: v, l: 1 }; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; + } + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; + } + dt >>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; + } + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } + } + mbt = mb; + } + return { t: new u8(tr), l: mbt }; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; + } + } + return { c: cl.subarray(0, cli), n: s }; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l; + var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l; + var _c = lc(dlt), lclt = _c.c, nlc = _c.n; + var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + ++lcfreq[lclt[i] & 31]; + for (var i = 0; i < lcdt.length; ++i) + ++lcfreq[lcdt[i] & 31]; + var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; + if (bs >= 0 && flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb), ll = dlt, dm = hMap(ddt, mdb), dl = ddt; + var llm = hMap(lct, mlcb); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12; + } + } + } + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; + } + for (var i = 0; i < li; ++i) { + var sym = syms[i]; + if (sym > 255) { + var len = (sym >> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (sym >> 23) & 31), p += fleb[len]; + var dst = sym & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst]; + } + else { + wbits16(out, p, lm[sym]), p += ll[sym]; + } + } + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, st) { + var s = st.z || dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var lst = st.l; + var pos = (st.r || 0) & 7; + if (lvl) { + if (pos) + w[0] = st.r >> 3; + var opt = deo[lvl - 1]; + var n = opt >> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new i32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx blkpos + var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0; + for (; i + 2 < s; ++i) { + // hash value + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = i - dif + j & 32767; + var pti = prev[ti]; + var cd = ti - pti & 32767; + if (cd > md) + md = cd, pimod = ti; + } + } + } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += imod - pimod & 32767; + } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one int32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + } + } + for (i = Math.max(i, wi); i < s; ++i) { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + if (!lst) { + st.r = (pos & 7) | w[(pos / 8) | 0] << 3; + // shft(pos) now 1 less if pos & 7 != 0 + pos -= 7; + st.h = head, st.p = prev, st.i = i, st.w = wi; + } + } + else { + for (var i = st.w || 0; i < s + lst; i += 65535) { + // end + var e = i + 65535; + if (e >= s) { + // write final block + w[(pos / 8) | 0] = lst; + e = s; + } + pos = wfblk(w, pos + 1, dat.subarray(i, e)); + } + st.i = s; + } + return slc(o, 0, pre + shft(pos) + post); +}; +// Adler32 +var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length | 0; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); + } + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8); + } + }; +}; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + if (!st) { + st = { l: 1 }; + if (opt.dictionary) { + var dict = opt.dictionary.subarray(-32768); + var newDat = new u8(dict.length + dat.length); + newDat.set(dict); + newDat.set(dat, dict.length); + dat = newDat; + st.w = dict.length; + } + } + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, st); +}; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +// zlib header +var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32); + c[1] |= 31 - ((c[0] << 8) | c[1]) % 31; + if (o.dictionary) { + var h = adler(); + h.p(o.dictionary); + wbytes(c, 2, h.d()); + } +}; +/** + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data + */ +function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; +} +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} +catch (e) { } - if (selector?._break || args._break) - return Promise.resolve(true); +/** + * @license + * jsPDF filters PlugIn + * Copyright (c) 2014 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - selector = new TSelector(); +(function(jsPDFAPI) { + + var ASCII85Encode = function(a) { + var b, c, d, e, f, g, h, i, j, k; + // eslint-disable-next-line no-control-regex + for ( + b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), + a += b, + c = [], + d = 0, + e = a.length; + e > d; + d += 4 + ) + (f = + (a.charCodeAt(d) << 24) + + (a.charCodeAt(d + 1) << 16) + + (a.charCodeAt(d + 2) << 8) + + a.charCodeAt(d + 3)), + 0 !== f + ? ((k = f % 85), + (f = (f - k) / 85), + (j = f % 85), + (f = (f - j) / 85), + (i = f % 85), + (f = (f - i) / 85), + (h = f % 85), + (f = (f - h) / 85), + (g = f % 85), + c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) + : c.push(122); + return ( + (function(a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(c, b.length), + String.fromCharCode.apply(String, c) + "~>" + ); + }; - selector.addBranch(branches[nbr], 'br0'); + var ASCII85Decode = function(a) { + var c, + d, + e, + f, + g, + h = String, + l = "length", + w = 255, + x = "charCodeAt", + y = "slice", + z = "replace"; + for ( + "~>" === a[y](-2), + a = a[y](0, -2) + [z](/\s/g, "") + [z]("z", "!!!!!"), + c = "uuuuu"[y](a[l] % 5 || 5), + a += c, + e = [], + f = 0, + g = a[l]; + g > f; + f += 5 + ) + (d = + 52200625 * (a[x](f) - 33) + + 614125 * (a[x](f + 1) - 33) + + 7225 * (a[x](f + 2) - 33) + + 85 * (a[x](f + 3) - 33) + + (a[x](f + 4) - 33)), + e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); + return ( + (function(a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(e, c[l]), + h.fromCharCode.apply(h, e) + ); + }; - selector.Process = function() { - if (this.tgtobj.br0 === undefined) - this.fail = true; - }; + var ASCIIHexEncode = function(value) { + return ( + value + .split("") + .map(function(value) { + return ("0" + value.charCodeAt().toString(16)).slice(-2); + }) + .join("") + ">" + ); + }; - selector.Terminate = function(res) { - if (!isStr(res)) - res = (!res || this.fails) ? 'FAIL' : 'ok'; + var ASCIIHexDecode = function(value) { + var regexCheckIfHex = new RegExp(/^([0-9A-Fa-f]{2})+$/); + value = value.replace(/\s/g, ""); + if (value.indexOf(">") !== -1) { + value = value.substr(0, value.indexOf(">")); + } + if (value.length % 2) { + value += "0"; + } + if (regexCheckIfHex.test(value) === false) { + return ""; + } + var result = ""; + for (var i = 0; i < value.length; i += 2) { + result += String.fromCharCode("0x" + (value[i] + value[i + 1])); + } + return result; + }; + /* + var FlatePredictors = { + None: 1, + TIFF: 2, + PNG_None: 10, + PNG_Sub: 11, + PNG_Up: 12, + PNG_Average: 13, + PNG_Paeth: 14, + PNG_Optimum: 15 + }; + */ - names[nbr] = res + ' ' + names[nbr]; - }; + var FlateEncode = function(data) { + var arr = new Uint8Array(data.length); + var i = data.length; + while (i--) { + arr[i] = data.charCodeAt(i); + } + arr = zlibSync(arr); + data = arr.reduce(function(data, byte) { + return data + String.fromCharCode(byte); + }, ""); + return data; + }; - const br = branches[nbr], - object_class = getBranchObjectClass(br, tree), - num = br.fEntries, - skip_branch = object_class ? (nchilds[nbr] > 100) : !br.fLeaves?.arr?.length; + jsPDFAPI.processDataByFilters = function(origData, filterChain) { + var i = 0; + var data = origData || ""; + var reverseChain = []; + filterChain = filterChain || []; - if (skip_branch || (num <= 0)) - return testBranch(nbr+1); + if (typeof filterChain === "string") { + filterChain = [filterChain]; + } - const drawargs = { numentries: 10 }, - first = br.fFirstEntry || 0, - last = br.fEntryNumber || (first + num); + for (i = 0; i < filterChain.length; i += 1) { + switch (filterChain[i]) { + case "ASCII85Decode": + case "/ASCII85Decode": + data = ASCII85Decode(data); + reverseChain.push("/ASCII85Encode"); + break; + case "ASCII85Encode": + case "/ASCII85Encode": + data = ASCII85Encode(data); + reverseChain.push("/ASCII85Decode"); + break; + case "ASCIIHexDecode": + case "/ASCIIHexDecode": + data = ASCIIHexDecode(data); + reverseChain.push("/ASCIIHexEncode"); + break; + case "ASCIIHexEncode": + case "/ASCIIHexEncode": + data = ASCIIHexEncode(data); + reverseChain.push("/ASCIIHexDecode"); + break; + case "FlateEncode": + case "/FlateEncode": + data = FlateEncode(data); + reverseChain.push("/FlateDecode"); + break; + default: + throw new Error( + 'The filter: "' + filterChain[i] + '" is not implemented' + ); + } + } - if (num < drawargs.numentries) - drawargs.numentries = num; - else - drawargs.firstentry = first + Math.round((last - first - drawargs.numentries) * Math.random()); // select randomly + return { data: data, reverseChain: reverseChain.reverse().join(" ") }; + }; +})(jsPDF.API); - // keep console output for debug purposes - console.log(`test branch ${br.fName} first ${drawargs.firstentry || 0} num ${drawargs.numentries}`); +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - if (isFunc(args.showProgress)) - args.showProgress(`br ${nbr}/${branches.length} ${br.fName}`); +/** + * jsPDF JavaScript plugin + * + * @name javascript + * @module + */ +(function(jsPDFAPI) { + var jsNamesObj, jsJsObj, text; + /** + * @name addJS + * @function + * @param {string} javascript The javascript to be embedded into the PDF-file. + * @returns {jsPDF} + */ + jsPDFAPI.addJS = function(javascript) { + text = javascript; + this.internal.events.subscribe("postPutResources", function() { + jsNamesObj = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/Names [(EmbeddedJS) " + (jsNamesObj + 1) + " 0 R]"); + this.internal.out(">>"); + this.internal.out("endobj"); + + jsJsObj = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/S /JavaScript"); + this.internal.out("/JS (" + text + ")"); + this.internal.out(">>"); + this.internal.out("endobj"); + }); + this.internal.events.subscribe("putCatalog", function() { + if (jsNamesObj !== undefined && jsJsObj !== undefined) { + this.internal.out("/Names <>"); + } + }); + return this; + }; +})(jsPDF.API); - return treeProcess(tree, selector, drawargs).then(() => testBranch(nbr+1)); - } +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - return testBranch(0).then(() => { - if (isFunc(args.showProgress)) - args.showProgress(); +/** + * jsPDF Outline PlugIn + * + * Generates a PDF Outline + * @name outline + * @module + */ +(function(jsPDFAPI) { + + var namesOid; + //var destsGoto = []; + + jsPDFAPI.events.push([ + "postPutResources", + function() { + var pdf = this; + var rx = /^(\d+) 0 obj$/; + + // Write action goto objects for each page + // this.outline.destsGoto = []; + // for (var i = 0; i < totalPages; i++) { + // var id = pdf.internal.newObject(); + // this.outline.destsGoto.push(id); + // pdf.internal.write("<> endobj"); + // } + // + // for (var i = 0; i < dests.length; i++) { + // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 + // R"); + // } + // + if (this.outline.root.children.length > 0) { + var lines = pdf.outline.render().split(/\r\n/); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var m = rx.exec(line); + if (m != null) { + var oid = m[1]; + pdf.internal.newObjectDeferredBegin(oid, false); + } + pdf.internal.write(line); + } + } - return names; - }); -} + // This code will write named destination for each page reference + // (page_1, etc) + if (this.outline.createNamedDestinations) { + var totalPages = this.internal.pages.length; + // WARNING: this assumes jsPDF starts on page 3 and pageIDs + // follow 5, 7, 9, etc + // Write destination objects for each page + var dests = []; + for (var i = 0; i < totalPages; i++) { + var id = pdf.internal.newObject(); + dests.push(id); + var info = pdf.internal.getPageInfo(i + 1); + pdf.internal.write( + "<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj" + ); + } -/** @summary Create hierarchy of TTree object - * @private */ -function treeHierarchy(node, obj) { - function createBranchItem(node, branch, tree, parent_branch) { - if (!node || !branch) return false; + // assign a name for each destination + var names2Oid = pdf.internal.newObject(); + pdf.internal.write("<< /Names [ "); + for (var i = 0; i < dests.length; i++) { + pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R"); + } + pdf.internal.write(" ] >>", "endobj"); - const nb_branches = branch.fBranches?.arr?.length ?? 0, - nb_leaves = branch.fLeaves?.arr?.length ?? 0; + // var kids = pdf.internal.newObject(); + // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R'); + // pdf.internal.write(' ] >>', 'endobj'); - function ClearName(arg) { - const pos = arg.indexOf('['); - if (pos > 0) arg = arg.slice(0, pos); - if (parent_branch && arg.indexOf(parent_branch.fName) === 0) { - arg = arg.slice(parent_branch.fName.length); - if (arg[0] === '.') arg = arg.slice(1); - } - return arg; + namesOid = pdf.internal.newObject(); + pdf.internal.write("<< /Dests " + names2Oid + " 0 R"); + pdf.internal.write(">>", "endobj"); + } + } + ]); + + jsPDFAPI.events.push([ + "putCatalog", + function() { + var pdf = this; + if (pdf.outline.root.children.length > 0) { + pdf.internal.write( + "/Outlines", + this.outline.makeRef(this.outline.root) + ); + if (this.outline.createNamedDestinations) { + pdf.internal.write("/Names " + namesOid + " 0 R"); + } + // Open with Bookmarks showing + // pdf.internal.write("/PageMode /UseOutlines"); } + } + ]); - branch.$tree = tree; // keep tree pointer, later do it more smart + jsPDFAPI.events.push([ + "initialized", + function() { + var pdf = this; - const subitem = { - _name: ClearName(branch.fName), - _kind: prROOT + branch._typename, - _title: branch.fTitle, - _obj: branch + pdf.outline = { + createNamedDestinations: false, + root: { + children: [] + } }; - if (!node._childs) node._childs = []; - - node._childs.push(subitem); + /** + * Options: pageNumber + */ + pdf.outline.add = function(parent, title, options) { + var item = { + title: title, + options: options, + children: [] + }; + if (parent == null) { + parent = this.root; + } + parent.children.push(item); + return item; + }; - if (branch._typename === clTBranchElement) - subitem._title += ` from ${branch.fClassName};${branch.fClassVersion}`; + pdf.outline.render = function() { + this.ctx = {}; + this.ctx.val = ""; + this.ctx.pdf = pdf; - if (nb_branches > 0) { - subitem._more = true; - subitem._expand = function(bnode, bobj) { - // really create all sub-branch items - if (!bobj) return false; + this.genIds_r(this.root); + this.renderRoot(this.root); + this.renderItems(this.root); - if (!bnode._childs) bnode._childs = []; + return this.ctx.val; + }; - if ((bobj.fLeaves?.arr?.length === 1) && - ((bobj.fType === kClonesNode) || (bobj.fType === kSTLNode))) { - bobj.fLeaves.arr[0].$branch = bobj; - bnode._childs.push({ - _name: '@size', - _title: 'container size', - _kind: prROOT + 'TLeafElement', - _icon: 'img_leaf', - _obj: bobj.fLeaves.arr[0], - _more: false - }); - } + pdf.outline.genIds_r = function(node) { + node.id = pdf.internal.newObjectDeferred(); + for (var i = 0; i < node.children.length; i++) { + this.genIds_r(node.children[i]); + } + }; - for (let i = 0; i < bobj.fBranches.arr.length; ++i) - createBranchItem(bnode, bobj.fBranches.arr[i], bobj.$tree, bobj); + pdf.outline.renderRoot = function(node) { + this.objStart(node); + this.line("/Type /Outlines"); + if (node.children.length > 0) { + this.line("/First " + this.makeRef(node.children[0])); + this.line( + "/Last " + this.makeRef(node.children[node.children.length - 1]) + ); + } + this.line( + "/Count " + + this.count_r( + { + count: 0 + }, + node + ) + ); + this.objEnd(); + }; - const object_class = getBranchObjectClass(bobj, bobj.$tree, true), - methods = object_class ? getMethods(object_class) : null; + pdf.outline.renderItems = function(node) { + var getVerticalCoordinateString = this.ctx.pdf.internal + .getVerticalCoordinateString; + for (var i = 0; i < node.children.length; i++) { + var item = node.children[i]; + this.objStart(item); - if (methods && (bobj.fBranches.arr.length > 0)) { - for (const key in methods) { - if (!isFunc(methods[key])) continue; - const s = methods[key].toString(); - if ((s.indexOf('return') > 0) && (s.indexOf('function ()') === 0)) { - bnode._childs.push({ - _name: key+'()', - _title: `function ${key} of class ${object_class}`, - _kind: prROOT + clTBranchFunc, // fictional class, only for drawing - _obj: { _typename: clTBranchFunc, branch: bobj, func: key }, - _more: false - }); - } - } - } + this.line("/Title " + this.makeString(item.title)); - return true; - }; - return true; - } else if (nb_leaves === 1) { - subitem._icon = 'img_leaf'; - subitem._more = false; - } else if (nb_leaves > 1) { - subitem._childs = []; - for (let j = 0; j < nb_leaves; ++j) { - branch.fLeaves.arr[j].$branch = branch; // keep branch pointer for drawing - const leafitem = { - _name: ClearName(branch.fLeaves.arr[j].fName), - _kind: prROOT + branch.fLeaves.arr[j]._typename, - _obj: branch.fLeaves.arr[j] - }; - subitem._childs.push(leafitem); - } - } + this.line("/Parent " + this.makeRef(node)); + if (i > 0) { + this.line("/Prev " + this.makeRef(node.children[i - 1])); + } + if (i < node.children.length - 1) { + this.line("/Next " + this.makeRef(node.children[i + 1])); + } + if (item.children.length > 0) { + this.line("/First " + this.makeRef(item.children[0])); + this.line( + "/Last " + this.makeRef(item.children[item.children.length - 1]) + ); + } - return true; - } + var count = (this.count = this.count_r( + { + count: 0 + }, + item + )); + if (count > 0) { + this.line("/Count " + count); + } - // protect against corrupted TTree objects - if (obj.fBranches === undefined) - return false; + if (item.options) { + if (item.options.pageNumber) { + // Explicit Destination + //WARNING this assumes page ids are 3,5,7, etc. + var info = pdf.internal.getPageInfo(item.options.pageNumber); + this.line( + "/Dest " + + "[" + + info.objId + + " 0 R /XYZ 0 " + + getVerticalCoordinateString(0) + + " 0]" + ); + // this line does not work on all clients (pageNumber instead of page ref) + //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.getHeight() + ' 0]'); + + // Named Destination + // this.line('/Dest (page_' + (item.options.pageNumber) + ')'); + + // Action Destination + // var id = pdf.internal.newObject(); + // pdf.internal.write('<> endobj'); + // this.line('/A ' + id + ' 0 R' ); + } + } + this.objEnd(); + } + for (var z = 0; z < node.children.length; z++) { + this.renderItems(node.children[z]); + } + }; - node._childs = []; - node._tree = obj; // set reference, will be used later by TTree::Draw + pdf.outline.line = function(text) { + this.ctx.val += text + "\r\n"; + }; - for (let i = 0; i < obj.fBranches.arr?.length; ++i) - createBranchItem(node, obj.fBranches.arr[i], obj); + pdf.outline.makeRef = function(node) { + return node.id + " 0 R"; + }; - return true; -} + pdf.outline.makeString = function(val) { + return "(" + pdf.internal.pdfEscape(val) + ")"; + }; -var tree = /*#__PURE__*/Object.freeze({ -__proto__: null, -TDrawSelector: TDrawSelector, -TDrawVariable: TDrawVariable, -TSelector: TSelector, -clTBranchFunc: clTBranchFunc, -kClonesNode: kClonesNode, -kSTLNode: kSTLNode, -treeDraw: treeDraw, -treeHierarchy: treeHierarchy, -treeIOTest: treeIOTest, -treeProcess: treeProcess -}); + pdf.outline.objStart = function(node) { + this.ctx.val += "\r\n" + node.id + " 0 obj" + "\r\n<<\r\n"; + }; -async function import_more() { return Promise.resolve().then(function () { return more; }); } + pdf.outline.objEnd = function() { + this.ctx.val += ">> \r\n" + "endobj" + "\r\n"; + }; -async function import_canvas() { return Promise.resolve().then(function () { return TCanvasPainter$1; }); } + pdf.outline.count_r = function(ctx, node) { + for (var i = 0; i < node.children.length; i++) { + ctx.count++; + this.count_r(ctx, node.children[i]); + } + return ctx.count; + }; + } + ]); -async function import_tree() { return Promise.resolve().then(function () { return TTree; }); } + return this; +})(jsPDF.API); -async function import_h() { return Promise.resolve().then(function () { return HierarchyPainter$1; }); } +function decode(bytes, encoding = 'utf8') { + const decoder = new TextDecoder(encoding); + return decoder.decode(bytes); +} +const encoder = new TextEncoder(); +function encode(str) { + return encoder.encode(str); +} -async function import_geo() { - return Promise.resolve().then(function () { return TGeoPainter$1; }).then(geo => { - const handle = getDrawHandle(prROOT + 'TGeoVolumeAssembly'); - if (handle) handle.icon = 'img_geoassembly'; - return geo; - }); +const defaultByteLength = 1024 * 8; +const hostBigEndian = (() => { + const array = new Uint8Array(4); + const view = new Uint32Array(array.buffer); + return !((view[0] = 1) & array[0]); +})(); +const typedArrays = { + int8: globalThis.Int8Array, + uint8: globalThis.Uint8Array, + int16: globalThis.Int16Array, + uint16: globalThis.Uint16Array, + int32: globalThis.Int32Array, + uint32: globalThis.Uint32Array, + uint64: globalThis.BigUint64Array, + int64: globalThis.BigInt64Array, + float32: globalThis.Float32Array, + float64: globalThis.Float64Array, +}; +class IOBuffer { + /** + * Reference to the internal ArrayBuffer object. + */ + buffer; + /** + * Byte length of the internal ArrayBuffer. + */ + byteLength; + /** + * Byte offset of the internal ArrayBuffer. + */ + byteOffset; + /** + * Byte length of the internal ArrayBuffer. + */ + length; + /** + * The current offset of the buffer's pointer. + */ + offset; + lastWrittenByte; + littleEndian; + _data; + _mark; + _marks; + /** + * Create a new IOBuffer. + * @param data - The data to construct the IOBuffer with. + * If data is a number, it will be the new buffer's length
    + * If data is `undefined`, the buffer will be initialized with a default length of 8Kb
    + * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance, + * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer. + * @param options - An object for the options. + * @returns A new IOBuffer instance. + */ + constructor(data = defaultByteLength, options = {}) { + let dataIsGiven = false; + if (typeof data === 'number') { + data = new ArrayBuffer(data); + } + else { + dataIsGiven = true; + this.lastWrittenByte = data.byteLength; + } + const offset = options.offset ? options.offset >>> 0 : 0; + const byteLength = data.byteLength - offset; + let dvOffset = offset; + if (ArrayBuffer.isView(data) || data instanceof IOBuffer) { + if (data.byteLength !== data.buffer.byteLength) { + dvOffset = data.byteOffset + offset; + } + data = data.buffer; + } + if (dataIsGiven) { + this.lastWrittenByte = byteLength; + } + else { + this.lastWrittenByte = 0; + } + this.buffer = data; + this.length = byteLength; + this.byteLength = byteLength; + this.byteOffset = dvOffset; + this.offset = 0; + this.littleEndian = true; + this._data = new DataView(this.buffer, dvOffset, byteLength); + this._mark = 0; + this._marks = []; + } + /** + * Checks if the memory allocated to the buffer is sufficient to store more + * bytes after the offset. + * @param byteLength - The needed memory in bytes. + * @returns `true` if there is sufficient space and `false` otherwise. + */ + available(byteLength = 1) { + return this.offset + byteLength <= this.length; + } + /** + * Check if little-endian mode is used for reading and writing multi-byte + * values. + * @returns `true` if little-endian mode is used, `false` otherwise. + */ + isLittleEndian() { + return this.littleEndian; + } + /** + * Set little-endian mode for reading and writing multi-byte values. + * @returns This. + */ + setLittleEndian() { + this.littleEndian = true; + return this; + } + /** + * Check if big-endian mode is used for reading and writing multi-byte values. + * @returns `true` if big-endian mode is used, `false` otherwise. + */ + isBigEndian() { + return !this.littleEndian; + } + /** + * Switches to big-endian mode for reading and writing multi-byte values. + * @returns This. + */ + setBigEndian() { + this.littleEndian = false; + return this; + } + /** + * Move the pointer n bytes forward. + * @param n - Number of bytes to skip. + * @returns This. + */ + skip(n = 1) { + this.offset += n; + return this; + } + /** + * Move the pointer n bytes backward. + * @param n - Number of bytes to move back. + * @returns This. + */ + back(n = 1) { + this.offset -= n; + return this; + } + /** + * Move the pointer to the given offset. + * @param offset - The offset to move to. + * @returns This. + */ + seek(offset) { + this.offset = offset; + return this; + } + /** + * Store the current pointer offset. + * @see {@link IOBuffer#reset} + * @returns This. + */ + mark() { + this._mark = this.offset; + return this; + } + /** + * Move the pointer back to the last pointer offset set by mark. + * @see {@link IOBuffer#mark} + * @returns This. + */ + reset() { + this.offset = this._mark; + return this; + } + /** + * Push the current pointer offset to the mark stack. + * @see {@link IOBuffer#popMark} + * @returns This. + */ + pushMark() { + this._marks.push(this.offset); + return this; + } + /** + * Pop the last pointer offset from the mark stack, and set the current + * pointer offset to the popped value. + * @see {@link IOBuffer#pushMark} + * @returns This. + */ + popMark() { + const offset = this._marks.pop(); + if (offset === undefined) { + throw new Error('Mark stack empty'); + } + this.seek(offset); + return this; + } + /** + * Move the pointer offset back to 0. + * @returns This. + */ + rewind() { + this.offset = 0; + return this; + } + /** + * Make sure the buffer has sufficient memory to write a given byteLength at + * the current pointer offset. + * If the buffer's memory is insufficient, this method will create a new + * buffer (a copy) with a length that is twice (byteLength + current offset). + * @param byteLength - The needed memory in bytes. + * @returns This. + */ + ensureAvailable(byteLength = 1) { + if (!this.available(byteLength)) { + const lengthNeeded = this.offset + byteLength; + const newLength = lengthNeeded * 2; + const newArray = new Uint8Array(newLength); + newArray.set(new Uint8Array(this.buffer)); + this.buffer = newArray.buffer; + this.length = newLength; + this.byteLength = newLength; + this._data = new DataView(this.buffer); + } + return this; + } + /** + * Read a byte and return false if the byte's value is 0, or true otherwise. + * Moves pointer forward by one byte. + * @returns The read boolean. + */ + readBoolean() { + return this.readUint8() !== 0; + } + /** + * Read a signed 8-bit integer and move pointer forward by 1 byte. + * @returns The read byte. + */ + readInt8() { + return this._data.getInt8(this.offset++); + } + /** + * Read an unsigned 8-bit integer and move pointer forward by 1 byte. + * @returns The read byte. + */ + readUint8() { + return this._data.getUint8(this.offset++); + } + /** + * Alias for {@link IOBuffer#readUint8}. + * @returns The read byte. + */ + readByte() { + return this.readUint8(); + } + /** + * Read `n` bytes and move pointer forward by `n` bytes. + * @param n - Number of bytes to read. + * @returns The read bytes. + */ + readBytes(n = 1) { + return this.readArray(n, 'uint8'); + } + /** + * Creates an array of corresponding to the type `type` and size `size`. + * For example type `uint8` will create a `Uint8Array`. + * @param size - size of the resulting array + * @param type - number type of elements to read + * @returns The read array. + */ + readArray(size, type) { + const bytes = typedArrays[type].BYTES_PER_ELEMENT * size; + const offset = this.byteOffset + this.offset; + const slice = this.buffer.slice(offset, offset + bytes); + if (this.littleEndian === hostBigEndian && + type !== 'uint8' && + type !== 'int8') { + const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes)); + slice.reverse(); + const returnArray = new typedArrays[type](slice.buffer); + this.offset += bytes; + returnArray.reverse(); + return returnArray; + } + const returnArray = new typedArrays[type](slice); + this.offset += bytes; + return returnArray; + } + /** + * Read a 16-bit signed integer and move pointer forward by 2 bytes. + * @returns The read value. + */ + readInt16() { + const value = this._data.getInt16(this.offset, this.littleEndian); + this.offset += 2; + return value; + } + /** + * Read a 16-bit unsigned integer and move pointer forward by 2 bytes. + * @returns The read value. + */ + readUint16() { + const value = this._data.getUint16(this.offset, this.littleEndian); + this.offset += 2; + return value; + } + /** + * Read a 32-bit signed integer and move pointer forward by 4 bytes. + * @returns The read value. + */ + readInt32() { + const value = this._data.getInt32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 32-bit unsigned integer and move pointer forward by 4 bytes. + * @returns The read value. + */ + readUint32() { + const value = this._data.getUint32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 32-bit floating number and move pointer forward by 4 bytes. + * @returns The read value. + */ + readFloat32() { + const value = this._data.getFloat32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 64-bit floating number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readFloat64() { + const value = this._data.getFloat64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 64-bit signed integer number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readBigInt64() { + const value = this._data.getBigInt64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readBigUint64() { + const value = this._data.getBigUint64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 1-byte ASCII character and move pointer forward by 1 byte. + * @returns The read character. + */ + readChar() { + // eslint-disable-next-line unicorn/prefer-code-point + return String.fromCharCode(this.readInt8()); + } + /** + * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes. + * @param n - Number of characters to read. + * @returns The read characters. + */ + readChars(n = 1) { + let result = ''; + for (let i = 0; i < n; i++) { + result += this.readChar(); + } + return result; + } + /** + * Read the next `n` bytes, return a UTF-8 decoded string and move pointer + * forward by `n` bytes. + * @param n - Number of bytes to read. + * @returns The decoded string. + */ + readUtf8(n = 1) { + return decode(this.readBytes(n)); + } + /** + * Read the next `n` bytes, return a string decoded with `encoding` and move pointer + * forward by `n` bytes. + * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8} + * @param n - Number of bytes to read. + * @param encoding - The encoding to use. Default is 'utf8'. + * @returns The decoded string. + */ + decodeText(n = 1, encoding = 'utf8') { + return decode(this.readBytes(n), encoding); + } + /** + * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer + * forward by 1 byte. + * @param value - The value to write. + * @returns This. + */ + writeBoolean(value) { + this.writeUint8(value ? 0xff : 0x00); + return this; + } + /** + * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte. + * @param value - The value to write. + * @returns This. + */ + writeInt8(value) { + this.ensureAvailable(1); + this._data.setInt8(this.offset++, value); + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as an 8-bit unsigned integer and move pointer forward by 1 + * byte. + * @param value - The value to write. + * @returns This. + */ + writeUint8(value) { + this.ensureAvailable(1); + this._data.setUint8(this.offset++, value); + this._updateLastWrittenByte(); + return this; + } + /** + * An alias for {@link IOBuffer#writeUint8}. + * @param value - The value to write. + * @returns This. + */ + writeByte(value) { + return this.writeUint8(value); + } + /** + * Write all elements of `bytes` as uint8 values and move pointer forward by + * `bytes.length` bytes. + * @param bytes - The array of bytes to write. + * @returns This. + */ + writeBytes(bytes) { + this.ensureAvailable(bytes.length); + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < bytes.length; i++) { + this._data.setUint8(this.offset++, bytes[i]); + } + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 16-bit signed integer and move pointer forward by 2 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeInt16(value) { + this.ensureAvailable(2); + this._data.setInt16(this.offset, value, this.littleEndian); + this.offset += 2; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 16-bit unsigned integer and move pointer forward by 2 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeUint16(value) { + this.ensureAvailable(2); + this._data.setUint16(this.offset, value, this.littleEndian); + this.offset += 2; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit signed integer and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeInt32(value) { + this.ensureAvailable(4); + this._data.setInt32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit unsigned integer and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeUint32(value) { + this.ensureAvailable(4); + this._data.setUint32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit floating number and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeFloat32(value) { + this.ensureAvailable(4); + this._data.setFloat32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit floating number and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeFloat64(value) { + this.ensureAvailable(8); + this._data.setFloat64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit signed bigint and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeBigInt64(value) { + this.ensureAvailable(8); + this._data.setBigInt64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeBigUint64(value) { + this.ensureAvailable(8); + this._data.setBigUint64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write the charCode of `str`'s first character as an 8-bit unsigned integer + * and move pointer forward by 1 byte. + * @param str - The character to write. + * @returns This. + */ + writeChar(str) { + // eslint-disable-next-line unicorn/prefer-code-point + return this.writeUint8(str.charCodeAt(0)); + } + /** + * Write the charCodes of all `str`'s characters as 8-bit unsigned integers + * and move pointer forward by `str.length` bytes. + * @param str - The characters to write. + * @returns This. + */ + writeChars(str) { + for (let i = 0; i < str.length; i++) { + // eslint-disable-next-line unicorn/prefer-code-point + this.writeUint8(str.charCodeAt(i)); + } + return this; + } + /** + * UTF-8 encode and write `str` to the current pointer offset and move pointer + * forward according to the encoded length. + * @param str - The string to write. + * @returns This. + */ + writeUtf8(str) { + return this.writeBytes(encode(str)); + } + /** + * Export a Uint8Array view of the internal buffer. + * The view starts at the byte offset and its length + * is calculated to stop at the last written byte or the original length. + * @returns A new Uint8Array view. + */ + toArray() { + return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte); + } + /** + * Get the total number of bytes written so far, regardless of the current offset. + * @returns - Total number of bytes. + */ + getWrittenByteLength() { + return this.lastWrittenByte - this.byteOffset; + } + /** + * Update the last written byte offset + * @private + */ + _updateLastWrittenByte() { + if (this.offset > this.lastWrittenByte) { + this.lastWrittenByte = this.offset; + } + } } -const clTGraph2D = 'TGraph2D', clTH2Poly = 'TH2Poly', clTEllipse = 'TEllipse', - clTSpline3 = 'TSpline3', clTTree = 'TTree', clTCanvasWebSnapshot = 'TCanvasWebSnapshot', - fPrimitives = 'fPrimitives', fFunctions = 'fFunctions', +/*============================================================================*/ -/** @summary list of registered draw functions - * @private */ -drawFuncs = { lst: [ - { name: clTCanvas, icon: 'img_canvas', class: () => import_canvas().then(h => h.TCanvasPainter), opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, - { name: clTPad, icon: 'img_canvas', func: TPadPainter.draw, opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, - { name: 'TSlider', icon: 'img_canvas', func: TPadPainter.draw }, - { name: clTButton, icon: 'img_canvas', func: TPadPainter.draw }, - { name: clTFrame, icon: 'img_frame', draw: () => import_canvas().then(h => h.drawTFrame) }, - { name: clTPave, icon: 'img_pavetext', class: () => Promise.resolve().then(function () { return TPavePainter$1; }).then(h => h.TPavePainter) }, - { name: clTPaveText, sameas: clTPave }, - { name: clTPavesText, sameas: clTPave }, - { name: clTPaveStats, sameas: clTPave }, - { name: clTPaveLabel, sameas: clTPave }, - { name: clTPaveClass, sameas: clTPave }, - { name: clTDiamond, sameas: clTPave }, - { name: clTLegend, icon: 'img_pavelabel', sameas: clTPave }, - { name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave }, - { name: clTLatex, icon: 'img_text', draw: () => import_more().then(h => h.drawText), direct: true }, - { name: clTMathText, sameas: clTLatex }, - { name: clTText, sameas: clTLatex }, - { name: clTAnnotation, sameas: clTLatex }, - { name: /^TH1/, icon: 'img_histo1d', class: () => Promise.resolve().then(function () { return TH1Painter$1; }).then(h => h.TH1Painter), opt: ';hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;C;B;B1;A;TEXT;LEGO;same', ctrl: 'l', expand_item: fFunctions, for_derived: true }, - { name: clTProfile, icon: 'img_profile', class: () => Promise.resolve().then(function () { return TH1Painter$1; }).then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist', expand_item: fFunctions }, - { name: clTH2Poly, icon: 'img_histo2d', class: () => Promise.resolve().then(function () { return TH2Painter$1; }).then(h => h.TH2Painter), opt: ';COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same', expand_item: 'fBins', theonly: true }, - { name: 'TProfile2Poly', sameas: clTH2Poly }, - { name: 'TH2PolyBin', icon: 'img_histo2d', draw_field: 'fPoly', draw_field_opt: 'L' }, - { name: /^TH2/, icon: 'img_histo2d', class: () => Promise.resolve().then(function () { return TH2Painter$1; }).then(h => h.TH2Painter), dflt: 'col', opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true }, - { name: clTProfile2D, sameas: clTH2 }, - { name: /^TH3/, icon: 'img_histo3d', class: () => Promise.resolve().then(function () { return TH3Painter$1; }).then(h => h.TH3Painter), opt: ';SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL', expand_item: fFunctions, for_derived: true }, - { name: clTProfile3D, sameas: clTH3 }, - { name: clTHStack, icon: 'img_histo1d', class: () => Promise.resolve().then(function () { return THStackPainter$1; }).then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;E;PFC;PLC' }, - { name: clTPolyMarker3D, icon: 'img_histo3d', draw: () => Promise.resolve().then(function () { return draw3d; }).then(h => h.drawPolyMarker3D), direct: true, frame: '3d' }, - { name: clTPolyLine3D, icon: 'img_graph', draw: () => Promise.resolve().then(function () { return draw3d; }).then(h => h.drawPolyLine3D), direct: true, frame: '3d' }, - { name: 'TGraphStruct' }, - { name: 'TGraphNode' }, - { name: 'TGraphEdge' }, - { name: clTGraphTime, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphTimePainter$1; }).then(h => h.TGraphTimePainter), opt: 'once;repeat;first', theonly: true }, - { name: clTGraph2D, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraph2DPainter$1; }).then(h => h.TGraph2DPainter), opt: ';P;PCOL', theonly: true }, - { name: clTGraph2DErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, - { name: clTGraph2DAsymmErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, - { name: clTGraphPolargram, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPolarPainter$1; }).then(h => h.TGraphPolargramPainter), theonly: true }, - { name: clTGraphPolar, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPolarPainter$1; }).then(h => h.TGraphPolarPainter), opt: ';F;L;P;PE', theonly: true }, - { name: /^TGraph/, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPainter$2; }).then(h => h.TGraphPainter), opt: ';L;P' }, - { name: 'TEfficiency', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TEfficiencyPainter$1; }).then(h => h.TEfficiencyPainter), opt: ';AP' }, - { name: clTCutG, sameas: clTGraph }, - { name: /^RooHist/, sameas: clTGraph }, - { name: /^RooCurve/, sameas: clTGraph }, - { name: 'TScatter', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TScatterPainter$1; }).then(h => h.TScatterPainter), opt: ';A' }, - { name: 'RooPlot', icon: 'img_canvas', func: drawRooPlot }, - { name: 'TRatioPlot', icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TRatioPlotPainter$1; }).then(h => h.TRatioPlotPainter), opt: '' }, - { name: clTMultiGraph, icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TMultiGraphPainter$1; }).then(h => h.TMultiGraphPainter), opt: ';l;p;3d', expand_item: 'fGraphs' }, - { name: clTStreamerInfoList, icon: 'img_question', draw: () => import_h().then(h => h.drawStreamerInfo) }, - { name: 'TWebPainting', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TWebPaintingPainter$1; }).then(h => h.TWebPaintingPainter) }, - { name: clTCanvasWebSnapshot, icon: 'img_canvas', draw: () => import_canvas().then(h => h.drawTPadSnapshot) }, - { name: 'TPadWebSnapshot', sameas: clTCanvasWebSnapshot }, - { name: 'kind:Text', icon: 'img_text', func: drawRawText }, - { name: clTObjString, icon: 'img_text', func: drawRawText }, - { name: clTF1, icon: 'img_tf1', class: () => Promise.resolve().then(function () { return TF1Painter$1; }).then(h => h.TF1Painter), opt: ';L;C;FC;FL' }, - { name: clTF2, icon: 'img_tf2', class: () => Promise.resolve().then(function () { return TF2Painter$1; }).then(h => h.TF2Painter), opt: ';BOX;ARR;SURF;SURF1;SURF2;SURF4;SURF6;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same' }, - { name: clTF3, icon: 'img_histo3d', class: () => Promise.resolve().then(function () { return TF3Painter$1; }).then(h => h.TF3Painter), opt: ';SURF' }, - { name: clTSpline3, icon: 'img_tf1', class: () => Promise.resolve().then(function () { return TSplinePainter$1; }).then(h => h.TSplinePainter) }, - { name: 'TSpline5', sameas: clTSpline3 }, - { name: clTEllipse, icon: 'img_graph', draw: () => import_more().then(h => h.drawEllipse), direct: true }, - { name: 'TArc', sameas: clTEllipse }, - { name: 'TCrown', sameas: clTEllipse }, - { name: 'TPie', icon: 'img_graph', draw: () => import_more().then(h => h.drawPie), direct: true }, - { name: 'TPieSlice', icon: 'img_graph', dummy: true }, - { name: 'TExec', icon: 'img_graph', dummy: true }, - { name: clTLine, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TLinePainter$1; }).then(h => h.TLinePainter) }, - { name: 'TArrow', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TArrowPainter$1; }).then(h => h.TArrowPainter) }, - { name: clTPolyLine, icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyLine), direct: true }, - { name: 'TCurlyLine', sameas: clTPolyLine }, - { name: 'TCurlyArc', sameas: clTPolyLine }, - { name: 'TParallelCoord', icon: 'img_graph', dummy: true }, - { name: clTGaxis, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGaxisPainter$1; }).then(h => h.TGaxisPainter) }, - { name: clTBox, icon: 'img_graph', draw: () => import_more().then(h => h.drawBox), direct: true }, - { name: 'TWbox', sameas: clTBox }, - { name: 'TSliderBox', sameas: clTBox }, - { name: 'TMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true }, - { name: 'TPolyMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyMarker), direct: true }, - { name: 'TASImage', icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TASImagePainter$1; }).then(h => h.TASImagePainter), opt: ';z' }, - { name: 'TJSImage', icon: 'img_mgraph', draw: () => import_more().then(h => h.drawJSImage), opt: ';scale;center' }, - { name: clTGeoVolume, icon: 'img_histo3d', class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;count;projx;projz;wire;no_screen;dflt', ctrl: 'dflt' }, - { name: 'TEveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, - { name: nsREX+'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, - { name: 'TGeoOverlap', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt', dflt: 'dflt', ctrl: 'expand' }, - { name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', ctrl: 'dflt', noappend: true, exapnd_after_draw: true }, - { name: 'TGeoVolumeAssembly', sameas: clTGeoVolume, /* icon: 'img_geoassembly', */ opt: ';more;all;count' }, - { name: /^TGeo/, class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;axis;compa;count;projx;projz;wire;no_screen;dflt', dflt: 'dflt', ctrl: 'expand' }, - { name: 'TAxis3D', icon: 'img_graph', draw: () => import_geo().then(h => h.drawAxis3D), direct: true }, - // these are not draw functions, but provide extra info about correspondent classes - { name: 'kind:Command', icon: 'img_execute', execute: true }, - { name: 'TFolder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true, get_expand: () => import_h().then(h => h.folderHierarchy) }, - { name: 'TTask', icon: 'img_task', get_expand: () => import_h().then(h => h.taskHierarchy), for_derived: true }, - { name: clTTree, icon: 'img_tree', get_expand: () => Promise.resolve().then(function () { return tree; }).then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect }, - { name: 'TNtuple', sameas: clTTree }, - { name: 'TNtupleD', sameas: clTTree }, - { name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump', noinspect: true }, - { name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true }, - { name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump', ctrl: 'dump', ignore_online: true, always_draw: true }, - { name: clTList, icon: 'img_list', draw: () => import_h().then(h => h.drawList), get_expand: () => import_h().then(h => h.listHierarchy), dflt: 'expand' }, - { name: clTHashList, sameas: clTList }, - { name: clTObjArray, sameas: clTList }, - { name: clTClonesArray, sameas: clTList }, - { name: clTMap, sameas: clTList }, - { name: clTColor, icon: 'img_color' }, - { name: clTFile, icon: 'img_file', noinspect: true }, - { name: 'TMemFile', icon: 'img_file', noinspect: true }, - { name: clTStyle, icon: 'img_question', noexpand: true }, - { name: 'Session', icon: 'img_globe' }, - { name: 'kind:TopFolder', icon: 'img_base' }, - { name: 'kind:Folder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true }, - { name: nsREX+'RCanvas', icon: 'img_canvas', class: () => init_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, - { name: nsREX+'RCanvasDisplayItem', icon: 'img_canvas', draw: () => init_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, - { name: nsREX+'RHist1Drawable', icon: 'img_histo1d', class: () => init_v7('rh1').then(h => h.RH1Painter), opt: '' }, - { name: nsREX+'RHist2Drawable', icon: 'img_histo2d', class: () => init_v7('rh2').then(h => h.RH2Painter), opt: '' }, - { name: nsREX+'RHist3Drawable', icon: 'img_histo3d', class: () => init_v7('rh3').then(h => h.RH3Painter), opt: '' }, - { name: nsREX+'RHistDisplayItem', icon: 'img_histo1d', draw: () => init_v7('rh3').then(h => h.drawHistDisplayItem), opt: '' }, - { name: nsREX+'RText', icon: 'img_text', draw: () => init_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, - { name: nsREX+'RFrameTitle', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, - { name: nsREX+'RPaletteDrawable', icon: 'img_text', class: () => init_v7('more').then(h => h.RPalettePainter), opt: '' }, - { name: nsREX+'RDisplayHistStat', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RHistStatsPainter), opt: '' }, - { name: nsREX+'RLine', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, - { name: nsREX+'RBox', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, - { name: nsREX+'RMarker', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, - { name: nsREX+'RPave', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPavePainter), opt: '' }, - { name: nsREX+'RLegend', icon: 'img_graph', class: () => init_v7('pave').then(h => h.RLegendPainter), opt: '' }, - { name: nsREX+'RPaveText', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, - { name: nsREX+'RFrame', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRFrame), opt: '' }, - { name: nsREX+'RFont', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, - { name: nsREX+'RAxisDrawable', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRAxis), opt: '' } -], cache: {} }; +function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } +/* The three kinds of block type */ -/** @summary Register draw function for the class - * @desc List of supported draw options could be provided, separated with ';' - * @param {object} args - arguments - * @param {string|regexp} args.name - class name or regexp pattern - * @param {function} [args.func] - draw function - * @param {function} [args.draw] - async function to load draw function - * @param {function} [args.class] - async function to load painter class with static draw function - * @param {boolean} [args.direct] - if true, function is just Redraw() method of ObjectPainter - * @param {string} [args.opt] - list of supported draw options (separated with semicolon) like 'col;scat;' - * @param {string} [args.icon] - icon name shown for the class in hierarchy browser - * @param {string} [args.draw_field] - draw only data member from object, like fHistogram - * @protected */ -function addDrawFunc(args) { - drawFuncs.lst.push(args); - return args; -} +const MIN_MATCH$1 = 3; +const MAX_MATCH$1 = 258; +/* The minimum and maximum match lengths */ -/** @summary return draw handle for specified item kind - * @desc kind could be ROOT.TH1I for ROOT classes or just - * kind string like 'Command' or 'Text' - * selector can be used to search for draw handle with specified option (string) - * or just sequence id - * @private */ -function getDrawHandle(kind, selector) { - if (!isStr(kind)) return null; - if (selector === '') selector = null; +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ - let first = null; +const LENGTH_CODES$1 = 29; +/* number of length codes, not counting the special END_BLOCK code */ - if ((selector === null) && (kind in drawFuncs.cache)) - return drawFuncs.cache[kind]; +const LITERALS$1 = 256; +/* number of literal bytes 0..255 */ - const search = (kind.indexOf(prROOT) === 0) ? kind.slice(5) : `kind:${kind}`; - let counter = 0; - for (let i = 0; i < drawFuncs.lst.length; ++i) { - const h = drawFuncs.lst[i]; - if (isStr(h.name)) { - if (h.name !== search) continue; - } else if (!search.match(h.name)) - continue; +const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; +/* number of Literal or Length codes, including the END_BLOCK code */ - if (h.sameas) { - const hs = getDrawHandle(prROOT + h.sameas, selector); - if (hs) { - for (const key in hs) { - if (h[key] === undefined) - h[key] = hs[key]; - } - delete h.sameas; - } - return h; - } +const D_CODES$1 = 30; +/* eslint-enable comma-spacing,array-bracket-spacing */ - if ((selector === null) || (selector === undefined)) { - // store found handle in cache, can reuse later - if (!(kind in drawFuncs.cache)) drawFuncs.cache[kind] = h; - return h; - } else if (isStr(selector)) { - if (!first) first = h; - // if drawoption specified, check it present in the list +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ - if (selector === '::expand') { - if (('expand' in h) || ('expand_item' in h)) return h; - } else if ('opt' in h) { - const opts = h.opt.split(';'); - for (let j = 0; j < opts.length; ++j) - opts[j] = opts[j].toLowerCase(); - if (opts.indexOf(selector.toLowerCase()) >= 0) return h; - } - } else if (selector === counter) - return h; - ++counter; - } +/* =========================================================================== + * Local data. These are initialized only once. + */ - return first; -} +// We pre-fill arrays with 0 to avoid uninitialized gaps -/** @summary Returns true if handle can be potentially drawn - * @private */ -function canDrawHandle(h) { - if (isStr(h)) - h = getDrawHandle(h); - if (!isObject(h)) return false; - return h.func || h.class || h.draw || h.draw_field; -} +const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ -/** @summary Provide draw settings for specified class or kind - * @private */ -function getDrawSettings(kind, selector) { - const res = { opts: null, inspect: false, expand: false, draw: false, handle: null }; - if (!isStr(kind)) return res; - let isany = false, noinspect = false, canexpand = false; - if (!isStr(selector)) selector = ''; +// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 +const static_ltree = new Array((L_CODES$1 + 2) * 2); +zero$1(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ - for (let cnt = 0; cnt < 1000; ++cnt) { - const h = getDrawHandle(kind, cnt); - if (!h) break; - if (!res.handle) res.handle = h; - if (h.noinspect) noinspect = true; - if (h.noappend) res.noappend = true; - if (h.expand || h.get_expand || h.expand_item || h.can_expand) canexpand = true; - if (!h.func && !h.class && !h.draw) break; - isany = true; - if (!('opt' in h)) continue; - const opts = h.opt.split(';'); - for (let i = 0; i < opts.length; ++i) { - opts[i] = opts[i].toLowerCase(); - if (opts[i].indexOf('same') === 0) { - res.has_same = true; - if (selector.indexOf('nosame') >= 0) continue; - } +const static_dtree = new Array(D_CODES$1 * 2); +zero$1(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ - if (res.opts === null) res.opts = []; - if (res.opts.indexOf(opts[i]) < 0) res.opts.push(opts[i]); - } - if (h.theonly) break; - } +const _dist_code = new Array(DIST_CODE_LEN); +zero$1(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ - if (selector.indexOf('noinspect') >= 0) noinspect = true; +const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); +zero$1(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ - if (isany && (res.opts === null)) res.opts = ['']; +const base_length = new Array(LENGTH_CODES$1); +zero$1(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ - // if no any handle found, let inspect ROOT-based objects - if (!isany && (kind.indexOf(prROOT) === 0) && !noinspect) res.opts = []; +const base_dist = new Array(D_CODES$1); +zero$1(base_dist); - if (!noinspect && res.opts) - res.opts.push(kInspect); +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It isn't worth it to make additional optimizations as in original. +// Small size is preferable. - res.inspect = !noinspect; - res.expand = canexpand; - res.draw = !!res.opts; +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const adler32 = (adler, buf, len, pos) => { + let s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; - return res; -} + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; -/** @summary Set default draw option for provided class - * @example - import { setDefaultDrawOpt } from 'https://root.cern/js/latest/modules/draw.mjs'; - setDefaultDrawOpt('TH1', 'text'); - setDefaultDrawOpt('TH2', 'col'); */ -function setDefaultDrawOpt(classname, opt) { - const handle = getDrawHandle(prROOT + classname, 0); - if (handle) - handle.dflt = opt; -} + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); -/** @summary Draw object in specified HTML element with given draw options. - * @param {string|object} dom - id of div element to draw or directly DOMElement - * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} - * @param {string} opt - draw options separated by space, comma or semicolon - * @return {Promise} with painter object - * @public - * @desc An extensive list of support draw options can be found on [examples page]{@link https://root.cern/js/latest/examples.htm} - * @example - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * import { draw } from 'https://root.cern/js/latest/modules/draw.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * await draw('drawing', obj, 'colz;logx;gridx;gridy'); */ -async function draw(dom, obj, opt) { - if (!isObject(obj)) - return Promise.reject(Error('not an object in draw call')); + s1 %= 65521; + s2 %= 65521; + } - if (isStr(opt) && (opt.indexOf(kInspect) === 0)) - return import_h().then(h => h.drawInspector(dom, obj, opt)); + return (s1 | (s2 << 16)) |0; +}; - let handle, type_info; - if ('_typename' in obj) { - type_info = 'type ' + obj._typename; - handle = getDrawHandle(prROOT + obj._typename, opt); - } else if ('_kind' in obj) { - type_info = 'kind ' + obj._kind; - handle = getDrawHandle(obj._kind, opt); - } else - return import_h().then(h => h.drawInspector(dom, obj, opt)); - // this is case of unsupported class, close it normally - if (!handle) - return Promise.reject(Error(`Object of ${type_info} cannot be shown with draw`)); +var adler32_1 = adler32; - if (handle.dummy) - return null; +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. - if (handle.draw_field && obj[handle.draw_field]) - return draw(dom, obj[handle.draw_field], opt || handle.draw_field_opt); +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// Use ordinary array, since untyped makes no boost here +const makeTable = () => { + let c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } - if (!canDrawHandle(handle)) { - if (opt && (opt.indexOf('same') >= 0)) { - const main_painter = getElementMainPainter(dom); + return table; +}; - if (isFunc(main_painter?.performDrop)) - return main_painter.performDrop(obj, '', null, opt); - } +// Create table on load. Just 255 signed longs. Not a problem. +const crcTable$1 = new Uint32Array(makeTable()); - return Promise.reject(Error(`Function not specified to draw object ${type_info}`)); - } - function performDraw() { - let promise, painter; - if (handle.direct === 'v7') { - promise = Promise.resolve().then(function () { return RCanvasPainter$1; }).then(v7h => { - painter = new v7h.RObjectPainter(dom, obj, opt, handle.csstype); - painter.redraw = handle.func; - return v7h.ensureRCanvas(painter, handle.frame || false); - }).then(() => painter.redraw()); - } else if (handle.direct) { - painter = new ObjectPainter(dom, obj, opt); - painter.redraw = handle.func; - promise = import_canvas().then(v6h => v6h.ensureTCanvas(painter, handle.frame || false)) - .then(() => painter.redraw()); - } else - promise = getPromise(handle.func(dom, obj, opt)); +const crc32 = (crc, buf, len, pos) => { + const t = crcTable$1; + const end = pos + len; - return promise.then(p => { - if (!painter) - painter = p; - if (painter === false) - return null; - if (!painter) - throw Error(`Fail to draw object ${type_info}`); - if (isObject(painter) && !painter.options) - painter.options = { original: opt || '' }; // keep original draw options - return painter; - }); - } + crc ^= -1; - if (isFunc(handle.func)) - return performDraw(); + for (let i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +}; - let promise; - if (isFunc(handle.class)) { - // class coded as async function which returns class handle - // simple extract class and access class.draw method - promise = handle.class().then(cl => { handle.func = cl.draw; }); - } else if (isFunc(handle.draw)) { - // draw function without special class - promise = handle.draw().then(h => { handle.func = h; }); - } else if (!handle.func || !isStr(handle.func)) - return Promise.reject(Error(`Draw function or class not specified to draw ${type_info}`)); - else if (!handle.prereq && !handle.script) - return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); - else { - const init_promise = internals.ignore_v6 - ? Promise.resolve(true) - : _ensureJSROOT().then(v6 => { - const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true); - return pr.then(() => { - if (handle.script) - return loadScript(handle.script); - }).then(() => v6._complete_loading()); - }); +var crc32_1 = crc32; - promise = init_promise.then(() => { - const func = findFunction(handle.func); - if (!isFunc(func)) - return Promise.reject(Error(`Fail to find function ${handle.func} after loading ${handle.prereq || handle.script}`)); +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var messages = { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; - handle.func = func; - }); - } +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var constants$2 = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; - return promise.then(() => performDraw()); -} +const _has = (obj, key) => { + return Object.prototype.hasOwnProperty.call(obj, key); +}; -/** @summary Redraw object in specified HTML element with given draw options. - * @param {string|object} dom - id of div element to draw or directly DOMElement - * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} - * @param {string} opt - draw options - * @return {Promise} with painter object - * @desc If drawing was not done before, it will be performed with {@link draw}. - * Otherwise drawing content will be updated - * @public - * @example - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * import { draw, redraw } from 'https://root.cern/js/latest/modules/draw.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * await draw('drawing', obj, 'colz'); - * let cnt = 0; - * setInterval(() => { - * obj.fTitle = `Next iteration ${cnt++}`; - * redraw('drawing', obj, 'colz'); - * }, 1000); */ -async function redraw(dom, obj, opt) { - if (!isObject(obj)) - return Promise.reject(Error('not an object in redraw')); +var assign = function (obj /*from1, from2, from3, ...*/) { + const sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + const source = sources.shift(); + if (!source) { continue; } - const can_painter = getElementCanvPainter(dom); - let handle, res_painter = null, redraw_res; - if (obj._typename) - handle = getDrawHandle(prROOT + obj._typename); - if (handle?.draw_field && obj[handle.draw_field]) - obj = obj[handle.draw_field]; + if (typeof source !== 'object') { + throw new TypeError(source + 'must be non-object'); + } - if (can_painter) { - if (can_painter.matchObjectType(obj._typename)) { - redraw_res = can_painter.redrawObject(obj, opt); - if (redraw_res) res_painter = can_painter; - } else { - for (let i = 0; i < can_painter.painters.length; ++i) { - const painter = can_painter.painters[i]; - if (painter.matchObjectType(obj._typename)) { - redraw_res = painter.redrawObject(obj, opt); - if (redraw_res) { - res_painter = painter; - break; - } - } - } - } - } else { - const top = new BasePainter(dom).getTopPainter(); - // base painter do not have this method, if it there use it - // it can be object painter here or can be specially introduce method to handling redraw! - if (isFunc(top?.redrawObject)) { - redraw_res = top.redrawObject(obj, opt); - if (redraw_res) res_painter = top; + for (const p in source) { + if (_has(source, p)) { + obj[p] = source[p]; } - } + } + } - if (res_painter) - return getPromise(redraw_res).then(() => res_painter); + return obj; +}; - cleanup(dom); - return draw(dom, obj, opt); -} +// Join array of chunks to single array. +var flattenChunks = (chunks) => { + // calculate data length + let len = 0; -/** @summary Scan streamer infos for derived classes - * @desc Assign draw functions for such derived classes - * @private */ -function addStreamerInfosForPainter(lst) { - if (!lst) return; + for (let i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } - const basics = [clTObject, clTNamed, clTString, 'TCollection', clTAttLine, clTAttFill, clTAttMarker, clTAttText]; + // join chunks + const result = new Uint8Array(len); - function checkBaseClasses(si, lvl) { - const element = si.fElements?.arr[0]; - if ((element?.fTypeName !== kBaseClass) || (lvl > 4)) - return null; - // exclude very basic classes - if (basics.indexOf(element.fName) >= 0) - return null; + for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { + let chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } - let handle = getDrawHandle(prROOT + element.fName); - if (handle && !handle.for_derived) - handle = null; + return result; +}; - // now try find that base class of base in the list - if (handle === null) { - for (let k = 0; k < lst.arr.length; ++k) { - if (lst.arr[k].fName === element.fName) { - handle = checkBaseClasses(lst.arr[k], lvl + 1); - break; - } - } - } +var common = { + assign: assign, + flattenChunks: flattenChunks +}; - return handle?.for_derived ? handle : null; - } +// String encode/decode helpers - lst.arr.forEach(si => { - if (getDrawHandle(prROOT + si.fName) !== null) return; - const handle = checkBaseClasses(si, 0); - if (handle) { - const newhandle = Object.assign({}, handle); - delete newhandle.for_derived; // should we disable? - newhandle.name = si.fName; - addDrawFunc(newhandle); - } - }); +// Quick check if we can use fast array to bin string conversion +// +// - apply(Array) can fail on Android 2.2 +// - apply(Uint8Array) can fail on iOS 5.1 Safari +// +let STR_APPLY_UIA_OK = true; + +try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } + + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +const _utf8len = new Uint8Array(256); +for (let q = 0; q < 256; q++) { + _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); } +_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start -/** @summary Create SVG/PNG/JPEG image for provided object. - * @desc Function especially useful in Node.js environment to generate images for - * supported ROOT classes, but also can be used from web browser - * @param {object} args - function settings - * @param {object} args.object - object for the drawing - * @param {string} [args.format = 'svg'] - image format like 'svg' (default), 'png' or 'jpeg' - * @param {string} [args.option = ''] - draw options - * @param {number} [args.width = 1200] - image width - * @param {number} [args.height = 800] - image height - * @param {boolean} [args.as_buffer = false] - returns image as Buffer instance, can store directly to file - * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object - * @return {Promise} with image code - svg as is, png/jpeg as base64 string or buffer (if as_buffer) specified - * @example - * // how makeImage can be used in node.js - * import { openFile, makeImage } from 'jsroot'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let object = await file.readObject('hpxpy;1'); - * let png64 = await makeImage({ format: 'png', object, option: 'colz', width: 1200, height: 800 }); - * let pngbuf = await makeImage({ format: 'png', as_buffer: true, object, option: 'colz', width: 1200, height: 800 }); */ -async function makeImage(args) { - if (!args) args = {}; - if (!isObject(args.object)) - return Promise.reject(Error('No object specified to generate SVG')); - if (!args.format) - args.format = 'svg'; - if (!args.width) - args.width = 1200; - if (!args.height) - args.height = 800; +// convert string to array (typed, when possible) +var string2buf = (str) => { + if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { + return new TextEncoder().encode(str); + } - if (args.use_canvas_size && (args.object?._typename === clTCanvas) && args.object.fCw && args.object.fCh) { - args.width = args.object.fCw; - args.height = args.object.fCh; - } + let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - async function build(main) { - main.attr('width', args.width).attr('height', args.height) - .style('width', args.width + 'px').style('height', args.height + 'px') - .property('_batch_mode', true) - .property('_batch_format', args.format !== 'svg' ? args.format : null); + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } - function complete(res) { - cleanup(main.node()); - main.remove(); - return res; + // allocate buffer + buf = new Uint8Array(buf_len); + + // convert + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } - return draw(main.node(), args.object, args.option || '').then(() => { - if (args.format !== 'svg') { - const only_img = main.select('svg').selectChild('image'); - if (!only_img.empty()) { - const href = only_img.attr('href'); + return buf; +}; - if (args.as_buffer) { - const p = href.indexOf('base64,'), - str = atob_func(href.slice(p + 7)), - buf = new ArrayBuffer(str.length), - bufView = new Uint8Array(buf); - for (let i = 0; i < str.length; i++) - bufView[i] = str.charCodeAt(i); - return isNodeJs() ? Buffer.from(buf) : buf; - } - return href; - } - } +// Helper +const buf2binstring = (buf, len) => { + // On Chrome, the arguments in a function call that are allowed is `65534`. + // If the length of the buffer is smaller than that, we can use this optimization, + // otherwise we will take a slower path. + if (len < 65534) { + if (buf.subarray && STR_APPLY_UIA_OK) { + return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); + } + } - main.select('svg') - .attr('xmlns', nsSVG) - .attr('width', args.width) - .attr('height', args.height) - .attr('style', null).attr('class', null).attr('x', null).attr('y', null); + let result = ''; + for (let i = 0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; +}; - function clear_element() { - const elem = select(this); - if (elem.style('display') === 'none') elem.remove(); - } - main.selectAll('g.root_frame').each(clear_element); - main.selectAll('svg').each(clear_element); +// convert array to string +var buf2string = (buf, max) => { + const len = max || buf.length; - let svg; - if (args.format === 'pdf') - svg = { node: main.select('svg').node(), width: args.width, height: args.height, can_modify: true }; - else { - svg = compressSVG(main.html()); - if (args.format === 'svg') - return complete(svg); - } + if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { + return new TextDecoder().decode(buf.subarray(0, max)); + } - return svgToImage(svg, args.format, args.as_buffer).then(complete); - }); - } + let i, out; - return isNodeJs() - ? _loadJSDOM().then(handle => build(handle.body.append('div'))) - : build(select('body').append('div').style('display', 'none')); -} + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + const utf16buf = new Array(len * 2); + for (out = 0, i = 0; i < len;) { + let c = buf[i++]; + // quick process ascii + if (c < 0x80) { utf16buf[out++] = c; continue; } -/** @summary Create SVG image for provided object. - * @desc Function especially useful in Node.js environment to generate images for - * supported ROOT classes - * @param {object} args - function settings - * @param {object} args.object - object for the drawing - * @param {string} [args.option] - draw options - * @param {number} [args.width = 1200] - image width - * @param {number} [args.height = 800] - image height - * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object - * @return {Promise} with svg code - * @example - * // how makeSVG can be used in node.js - * import { openFile, makeSVG } from 'jsroot'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let object = await file.readObject('hpxpy;1'); - * let svg = await makeSVG({ object, option: 'lego2,pal50', width: 1200, height: 800 }); */ -async function makeSVG(args) { - if (!args) args = {}; - args.format = 'svg'; - return makeImage(args); -} + let c_len = _utf8len[c]; + // skip 5 & 6 byte codes + if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } -internals.addDrawFunc = addDrawFunc; + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } -function assignPadPainterDraw(PadPainterClass) { - PadPainterClass.prototype.drawObject = (...args) => - draw(...args).catch(err => { console.log(`Error ${err?.message ?? err} at ${err?.stack ?? 'uncknown place'}`); return null; }); - PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings; -} + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } -// only now one can draw primitives in the canvas -assignPadPainterDraw(TPadPainter); + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } -// load v7 only by demand -async function init_v7(arg) { - return Promise.resolve().then(function () { return RCanvasPainter$1; }).then(h => { - // only now one can draw primitives in the canvas - assignPadPainterDraw(h.RPadPainter); - switch (arg) { - case 'more': return Promise.resolve().then(function () { return v7more; }); - case 'pave': return Promise.resolve().then(function () { return RPavePainter$1; }); - case 'rh1': return Promise.resolve().then(function () { return RH1Painter$1; }); - case 'rh2': return Promise.resolve().then(function () { return RH2Painter$1; }); - case 'rh3': return Promise.resolve().then(function () { return RH3Painter$1; }); - } - return h; - }); -} + return buf2binstring(utf16buf, out); +}; -// to avoid cross-dependnecy between io.mjs and draw.mjs -internals.addStreamerInfosForPainter = addStreamerInfosForPainter; +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = (buf, max) => { -/** @summary Draw TRooPlot - * @private */ -async function drawRooPlot(dom, plot) { - return draw(dom, plot._hist, 'hist').then(async hp => { - const arr = []; - for (let i = 0; i < plot._items.arr.length; ++i) - arr.push(draw(dom, plot._items.arr[i], plot._items.opt[i])); - return Promise.all(arr).then(() => hp); - }); -} + max = max || buf.length; + if (max > buf.length) { max = buf.length; } -const kTopFolder = 'TopFolder'; + // go back from last position, until start of sequence found + let pos = max - 1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } -function injectHStyle(node) { - function img(name, sz, fmt, code) { - return `.jsroot .img_${name} { display: inline-block; height: ${sz}px; width: ${sz}px; background-image: url("data:image/${fmt};base64,${code}"); }`; - } + // Very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } - const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', - border_color = settings.DarkMode ? 'green' : 'black', - shadow_color = settings.DarkMode ? '#555' : '#aaa'; + // If we came to start of buffer - that means buffer is too small, + // return max too. + if (pos === 0) { return max; } - injectStyle(` -.jsroot .h_tree { display: block; white-space: nowrap; } -.jsroot .h_tree * { padding: 0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; box-sizing: content-box; line-height: 14px } -.jsroot .h_tree img { border: 0px; vertical-align: middle; } -.jsroot .h_tree a { text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } -.jsroot .h_tree p { font-weight: bold; white-space: nowrap; text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } -.jsroot .h_value_str { color: green; } -.jsroot .h_value_num { color: blue; } -.jsroot .h_line { height: 18px; display: block; } -.jsroot .h_button { cursor: pointer; color: blue; text-decoration: underline; } -.jsroot .h_item { cursor: pointer; } -.jsroot .h_item:hover { text-decoration: underline; } -.jsroot .h_childs { overflow: hidden; display: block; } -.jsroot_fastcmd_btn { height: 32px; width: 32px; display: inline-block; margin: 2px; padding: 2px; background-position: left 2px top 2px; - background-repeat: no-repeat; background-size: 24px 24px; border-color: inherit; } -.jsroot_inspector { border: 1px solid ${border_color}; box-shadow: 1px 1px 2px 2px ${shadow_color}; opacity: 0.95; background-color: ${bkgr_color}; } -.jsroot_drag_area { background-color: #007fff; } -${img('minus', 18, 'gif', 'R0lGODlhEgASAJEDAIKCgoCAgAAAAP///yH5BAEAAAMALAAAAAASABIAAAInnD+By+2rnpyhWvsizE0zf4CIIpRlgiqaiDosa7zZdU22A9y6u98FADs=')} -${img('minusbottom', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAImlC+Ay+2rnpygWvsizE0zf4CIEpRlgiqaiDosa7zZdU32jed6XgAAOw==')} -${img('plus', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAIqlC+Ay+2rnpygWvsizCcczWieAW7BeSaqookfZ4yqU5LZdU06vfe8rysAADs=')} -${img('plusbottom', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAIplC+Ay+2rnpygWvsizCcczWieAW7BeSaqookfZ4yqU5LZdU36zvd+XwAAOw==')} -${img('empty', 18, 'gif', 'R0lGODlhEgASAJEAAAAAAP///4CAgP///yH5BAEAAAMALAAAAAASABIAAAIPnI+py+0Po5y02ouz3pwXADs=')} -${img('line', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIZjB+Ay+2rnpwo0uss3kfz7X1XKE5k+ZxoAQA7')} -${img('join', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIcjB+Ay+2rnpwo0uss3kf5BGocNJZiSZ2opK5BAQA7')} -${img('joinbottom', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIZjB+Ay+2rnpwo0uss3kf5BGrcSJbmiaZGAQA7')} -${img('base', 18, 'gif', 'R0lGODlhEwASAPcAAPv6/Pn4+mnN/4zf/764x2vO//Dv84HZ/5jl/0ZGmfTz9vLy8lHB/+zr70u+/7S03IODtd7d6c/P0ndqiq/w/4Pb/5SKo/Py9fPy9tTU121kjd/f4MzM062tx5+zy5rO67GwxNDM14d8mJzn/7awwry713zX/9bW27u71lFRmW5uoZ+fxjOy/zm1/9HQ2o3g/2xfgZeMplav7sn9/6Cgv37X/6Dp/3jU/2uJ2M7J1JC63vn5+v38/d7e38PD0Z7o/9LR4LS01cPDzPb1+Nzb5IJ2lHCEv5bk/53C3MrJ3X56t+np6YF7o3JsndTU5Wtgh5GHoKaesuLi4mrO/19RdnnV/4WBqF5QdWPK/4+PvW5uu4+PuuHh4q7w/97e68C9z63w/9PT0+zs7FtbmWVXerS0yaqitpuSqWVlpcL6/8jD0H/C9mVajqWu3nFwpYqHtFfE/42DnaWl0bTz/5OPt+7u7tra5Y+Yz+Tk56fM6Gek5pG50LGpvOHh72LJ/9XU5lbD/6GnwHpujfDu8mxpntzb45qav7PH41+n6JeXyUZGopyYsWeGyDu2/6LQ44re/1yV41TD/8LC1zix/sS/zdTU4Y+gsd/c5L7z+a6uzE+3+XG89L6+087O1sTD3K2twoGBtWVbgomo4P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKMALAAAAAATABIAAAjtAEcJFLgDTyE7SVCsAAJgoMNRYTII8fEpkAckOpiEaPhwlARLexxhmpEGzJEmBAJ0HMXhw6MfXeZQsDHADZ8hK13kMTEAwQgEL2oYiaJgJZFDU24cqHCgSgFGFgysBJAJkB8BBQRggQNJxKCVo0rIcMAgEgMHmnBMaADWEyIWLRptEqWETRG2K//ombSmjRZFoaCo4djRyZ0HchIlSECIRNGVXur0WcAlCJoUoOhcAltpyQIxPSRtGQPhjRkMKyN0krLhBCcaKrJoOCO1I48vi0CU6WDIyhNBKcEGyBEDBpUrZOJQugC2ufPnDwMCADs=')} -${img('folder', 18, 'gif', 'R0lGODlhEgASANUAAPv7++/v79u3UsyZNOTk5MHBwaNxC8KPKre3t55sBrqHIpxqBMmWMb2KJbOBG5lnAdu3cbWCHaBuCMuYM///urB+GMWSLad1D8eUL6ampqVzDbeEH6t5E8iVMMCNKMbGxq58FppoAqh2EKx6FP/Ub//4k+vr6///nP/bdf/kf//viba2tv//////mQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAC4ALAAAAAASABIAAAaRQJdwSCwaj8ik0jUYTBidAEA5YFkplANhehxABGAwpKHYRByVwHBibbvbo8+Q0TrZ7/jWBTHEtP6AgX8GK0MWLSWJiostEoVCBy0qk5SVLQmPLh4tKZ2eny0LmQ0tKKanqC0hmQotJK+wsS0PfEIBZxUgHCIaBhIJCw8ZBUMABAUrycrLBQREAAEm0tPUUktKQQA7')} -${img('folderopen', 18, 'gif', 'R0lGODlhEgASANUAAO/v76VzDfv7+8yZNMHBweTk5JpoAqBuCMuYM8mWMZ5sBpxqBPr7/Le3t///pcaaGvDker2KJc+iJqd1D7B+GOKzQ8KPKqJwCrOBG7WCHbeEH9e4QNq/bP/rhJlnAffwiaampuLBUMmgIf3VcKRyDP/XhLqHIqNxC8iVMMbGxqx6FP/kf//bdf/vievr67a2tv/4k8aaGf//nP//mf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAADUALAAAAAASABIAAAaVwJpwSCwaj8ikUjgYIBIogEA5oFkZDEtheqzKvl9axKTJYCiAIYIGblutqtQwQYPZ73jZpCGM+f+AfiEdJy99M21tMxwxJQeGNTGIeHcyHzEjCpAAki2en54OIhULkAKSMiuqqysOGxIGkDWcMyy2t7YQDx58QqcBwMAkFwcKCwYgBEQFBC/Oz9AEBUUALtbX2FJLSUEAOw==')} -${img('page', 18, 'gif', 'R0lGODlhEgASAOYAAPv7++/v7/j7/+32/8HBweTk5P39/djr/8Df//7///P5/8Ph//T09fn5+YGVw2t0pc7n/15hkFWn7ZOq0nqDsMDA/9nh7YSbyoqo2eTx/5G46pK873N+sPX6//f395Cjy83m/7rd/9jl9m13qGVqmoeh0n+OvI+z5Yyu387T//b6/2dtnvz9/32JtpS/8sbGxv7+/tvn92lwom96rHJ8rnSAsoep3NHp/8nk/7e3t+vr67a2tun1/3V4o+Hw/9vt/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEEALAAAAAASABIAAAejgEGCg4SFhoeILjaLjDY1AQCHG0AGAA0eDBY1E5CGGjBAoQkCMTUSHwGGJwaiAh0iNbEvhiihAgIDPDwpFRw5hhgsuLk8Pz8HNL+FJSoKuT4+xzczyoQXzjzQxjcgI9WDDrraPzc4OA/fgibZ0eTmCzLpQS0Z7TflCwgr8hT2EOYIQpCQ16OgwYMRCBgqQGCHw4cOCRQwBCCAjosYL3ZCxNFQIAA7')} -${img('question', 18, 'gif', 'R0lGODlhEgASAPelAOP0//7//9bs//n///j//9Ls/8Pn//r//6rB1t3f5crO2N7g5k1livT4+7PW9dXt/+v4/+Xl5LHW9Ov6/+j1/6CyxrfCz9rd5Nzj6un1/Z6ouwcvj8HBzO7+/+3//+Ln7BUuXNHv/6K4y+/9/wEBZvX08snn/19qhufs8fP7/87n/+/t7czr/5q1yk55q97v/3Cfztnu//z//+X6/ypIdMHY7rPc/7fX9cbl/9/h52WHr2yKrd/0/9fw/4KTs9rm75Svzb2+ya690pu92mWJrcT3//H//+Dv/Xym35S216Ouwsvt/3N/mMnZ5gEBcMnq/wEBXs/o/wEBetzw/zdYpTdZpsvP2ClGml2N3b3H0Nzu/2Z2lF1ricrl/93w/97h6JqluktojM/u/+/z9g8pVff4+ebu9q+1xa6/zzdFaIiXr5Wyz0xslrTK4uL//2uIp11rh8Xj/NXn+Oz2/9bf6bG2xAEBePP//1xwkK/K5Nbr/8fp/2OBtG53kai3ykVCYwEBde/6/7O4xabI+fD//+by/x8+jDhZpM/q/6jK58nO19ny/7jV7ZO42NHr/9H4/2ZwimSV6VBxwMDX7Nvf5hYwX5m20sfb6Ieqyk9Yjr/k/cPM2NDp/+/098Tl9yQ9jLfW+Mne8sjU30JklP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKUALAAAAAASABIAAAjxAEsJHEiwoMEyGMaQWthg0xeDAlGUWKjoz5mFAegY/LBiIalMUK54JCWEoJkIpA6kSDmoAykKgRaqGSiq04A5A5r4AKOEAAAtE2S0USAwSwYIhUb8METiUwAvemLMCMVEoIUjAF5MIYXAThUCDzgVWDQJjkA0cngIEHAHCCAqRqJ0QeQoDxeBFS71KKDCwxonhwiZwPEkzo4+AimJqBFCjBs+UjZ4WmLgxhAQVgb6acGIBShJkbAgMSAhCQ1IBTW8sZRI055HDhoRqXQCYo4tDMJgsqGDTJo6EAlyYFNkVJDgBgXBcJEAucEFeC44n04wIAA7')} -${img('histo1d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEW9vb2np6empqanpqenpqivr6//AAD3+fn09vb19vf3+Pv8+v//+//29/v3+fr19vbZ3Nza3d7X0+Lb3t7b3N3AwMP2+PimpqXe4+Th6uvQ0dTi6uzg5ebFx8nt6vb////r5/T2+fnl4e3a3uDN0NT7/P6lpqX3+vvn9vhcVVHu+//W1uH48//29P///f+mpqelpqb4/v/t/f9oY2H6///59v/x8fXw9fny9/78/v+lpqf7//9iXl12dHPW2t/R1tdtaGbT2dpoZmT6/v9ycnKCgoJpZGJ6dnT3///2///0//95entpa2t+gIKLjI55d3aDgYBvcXL1+/z9/v6lpaWGiIt7fH6Ji42SlJeEhIZubGyMjI17fYD+//+kpKSmpaaRk5WIioyRk5aYmp2OkJJ+f4KTlZilpKWcnqGVl5qcnqCfoaOYmp6PkZOdn6GsrrGoqq6qrK+rrbGpq66lp6uqrbCoqq20tLSsrKzc3NzMzMzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6enrU4/9iYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmLU4/9KSkoAAAAAAAAAAAB6enrU4//m5uZiYmLm5uZiYmLm5uZiYmLm5uZiYmLm5ubU4/9KSkoAAAAAAAAAAAB6enrU4/9KSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkrU4/9KSkoAAAAAAAAAAABubm7U4//U4//U4//U4//U4//U4//U4//U4//U4//U4//U4/9KSkoAAAAAAAAAAABubm5KSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABt6dBwBYjWHVG2AAAAB3RSTlP///////8AGksDRgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOxJREFUeNpjYGBkggBmFmYmRlY2BkZ2DhDg5OLm4eblY2RjYOIXEBQSFhEFkgKCYkxsDOKcEpJS0jKycvJS8gpcIAFFJWUVGFIFCqipa8hrymtpy+sI6crr6bMxGBgayRvLm8iamkmZW1gCBayslWxs7ewd7OwdlZStrYC2ODm7uLrJu3t4usl7mRiwMeh7+/j6+VsHBMr7+wQFhwAFQsPCIyKjomOiIsOiYuPYGOITEpOSU1LTElNTElPlgQLpGZlZ2Tm5eZm5OZm5IAGm/ILCouKS0rKS4oISeaDDypniEICpgo2hsgoZVLMBAHIaNxuoIXy2AAAAAElFTkSuQmCC')} -${img('histo2d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsSAAALEgHS3X78AAABUUlEQVR42o1R0U7CQBDkU/w/v8qk1/OhCS+88miMiQYMBMNRTqiisQiRhF6h13adsuVKDEYvm81kdmdv9q7V7XallP65I30UrpErLGW73SaiFtDF5dXWmNNITJrubJ4RWUI2qU33GTorAdSJMeMwhOxpEE20noRTYISaajBcMrsdOlkgME+/vILtPw6j+BPg5vZuFRuUgZGX71tc2AjALuYrpWcP/WE1+ADAADMAY/OyFghfpJnlSTCAvLb1YDbJmArC5izwQa0K4g5EdgSbTQKTX8keOC8bgXSWAEbqmbs5BmPF3iyR8I+vdNrhIj3ewzdnlaBeWroCDHBZxWtm9DzaEyU2L8pSCNEI+N76+fVs8rE8fbeRUiWR53kHgWgs6cXbD2OOIScQnji7g7OE2UVZNILflnbrulx/XKfTAfL+OugJgqAShGF4/7/T6/Ug+AYZrx7y3UV8agAAAABJRU5ErkJggg==')} -${img('histo3d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX////48OjIgTrHfjjKgTr78+yixH9HiQBHiACiw37jvJXfpVP6wzT7zTn7yj3lp1qOhyJzvgCa3wCa3gB2ugBinQ6Pt2D4+vfOjEr96p3986z83mT99rD99a3WhEvC0kaU3gCV3ADG71zo/KORzw1gowBonS3Z5snHfTb6uyD6tzD+/Nb7z0/70D3KdTXI1l3h+qTi+KXD7luU3ACY3gCc4QCi3g1QjwXHfjr710T6xi/+9sn70UH73E/MdDqhvQCi1BKkug2XxACU1wCS2ADD51rr9aJXkw/MpYDgpkb71U7+9MP7007hnEO3niOj0hGq3SCZtQCbtQCjtwj//+7F4Vui0wBDhgDk5eTMxcGxfi3TfTq+fyPPz4ak3xux5TG87kmZuwCZvACWtgDf8a+c0gCy3yNLiwD7/Ps1iwCiyAPF3F7j7bG67EW77kmq5yWYzwCZwwCTugDc8KTE51ve9YZCigCgwgCVuQDa5p7U9YSq4yWT2gCV2wCT2wCp2h/y+9HC6lW87DlChQBGigCixgCYvgDK3nyXvgC72UjG7mSj3xXL7XDK7W7b9J+36TrG9lBDhQBHigClywCbxQDJ33SXvwCYvQCcwADq+8S77Ei460Hd+KDD9VHU/2VEhgBdlR1rowCXwwDK4W6bxgCaxQCVvQDp/L+/8k7F91fn/6zC9V18tiNbkx/U1dSyv6RglihnoQCYwwChyQDs/7/P/2fE92F5tCBdkib19vXW1taoupVLiwNooQCWwADo/7h5tSBFhgaouZXx8vHOz86ftYVJiQBNjQKetIXt7u3Nzs0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBfAAAAAAAAAA2tmA2tmAAACQAAAAAAAAAAAAAAAAAAAAAATgAABNBfMAAAAAAAAA2tpQ2tpQAACQAAAAAAAAAAAAAAAAAAAAAAdQAABNBfMAAAAAAAAA2tsg2tsgAACQAAAAAAAAAAAAAAAAAAAAAAggAABNBfMCaVmCSAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAQVJREFUeNpjYGBkYmZhZWBj5+BkAAMubh5ePn4BQSFhEVExcaCAhKSUtIysnLyCopKyiqqaOoOGppa2jq6evoGhkbGJqZk5g4WllbWNrZ29g6OTs4urmzuDh6eXt4+vn39AYFBwSGhYOENEZFR0TGxcfEJiUnJKalo6A0NGZlZ2Tm5efkFhUXFJqTnQnrLyisqq6prauvqGxqZmoEBLa1t7R2dXd09vX/+EiUCBSZOnTJ02fcbMWbPnzJ03HyiwYOGixUuWLlu+YuWq1WvWAgXWrd+wcdPmTVu2btu+Y/06kHd27tq9Z+++/QcOHtq1E+JBhsNHjh47fuLIYQYEOHnq1EkwAwCuO1brXBOTOwAAAABJRU5ErkJggg==')} -${img('graph', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEFCgohaz8VogAAAT9JREFUOMulkz1LQlEYx39XmrIhcLa5i4mD4JBQrtHieDbb+gx3dbl9hca7tLi4VOsRMkKQVO7LLAQNNdSQgyJPg903tDT8w4HzPDznd56Xc1BKCVsokzTGjhPBXDcQAAEZDgPZCHDQaESH5/PYXyqZxp8A349vGHkjOXo3uXtp035sy79KABi8DQCwshb7x3U6gIYU6KNej+1kEwUEjbQeWtIb9mTsOCIgN1eXgiYd96OdcKNBOoCuQc47pFgoGmHw7skZTK9X16CUku5zV9BIkhz4vgSuG/nsWzvKIhmXAah+VpfJsxnGZMKkUln05NwykqOORq6UWkn+TRokXFEG/Vx/45c3fbrnFKjpRVkZgHKxbAC8NptrAfm9PAD2l42VtdJjDDwv2CSLpSaGMgsFc91hpdRFKtNtf6OxLeAbVYSb7ipFh3AAAAAASUVORK5CYII=')} -${img('mgraph', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEW9vb2np6empqanpqenpqivr68AAAD3+fn09vb19vf3+Pv8+v//+//29/v3+fr19vbZ3Nza3d6/wcLb3t7b3N3AwMPi4et2oz0yfwDh3+n2+PimpqXe4+Th6uvD0NHi6uzg5ebFx8nt6vY2ggDs/881gQDr5/T2+fnFz9DDZVrAIhDEZVvJ0tTN0NTX0+IvZAA4hAAuYgDT0N77/P6lpqX3+vvn9vi/JRL81cHBJhTu+//W1uEkXgD48//29P8fWwD//f+mpqelpqb4/v/t/f+yCwDBKBi3CgD6//8kYAD59v/x8fXQ0dTw9fny9/78/v+lpqf7//+wAADV5ezZ5e7g6PQjZQDf4+/W2t/R1tfT2drT3+OvAAD9///6/v/////k4vIiXwC1AAD3///2///X6Oz0//9+rUgzfwAwdADa6u6xCwDAJxb5///1+/z9/v6lpaUwfADo/8vl4e3a3uDb6eu+IxL808C+IhDZ5+nW2tr+//+kpKSmpaaArUgvewB1oj39/v/e5ebVd227HgvJa2H8///6/PylpKXn4+ze4eLg5+j9/v20tLSsrKzc3NzMzMzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAPAAAAAAEAAAEAAABzL1z/CSMAAAAAAAAAAAAAAAMAAAAmCTsAAAAAAAAAAAAAAAAAAAQAAQEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7op0gAAAAB3RSTlP///////8AGksDRgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOhJREFUeNpjYGBkggBmFmYmRlY2BkZ2DhDg5OLm4eblY2RjYOIXEBQSFhEVE5cQl5RiYmOQ5pSRlZNXUFRSVlFV4wIJqGtoamnr6OrpGxgaGQMFTEzNzC0sraxtbPXs7B0c2RicnF1c3dw9PL28fXz9/IECAYFBwSGhYeERkVHRMYEBQFti4+ITEuOTklNSg9I8nNgYHOPTMzLjA7Oyc7Jz8/ILQAKFRRnFJaVl5RWVVdU1bAy18XX1DfGNTc0trW3t8UCBjvj4+M746q74+O7qHpAAUzwyADqsl6kGAZj62Bj6JyCDiWwAyPNF46u5fYIAAAAASUVORK5CYII=')} -${img('tree', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAACjklEQVQ4y4WTy49LcRzFP+2tzmVUr9dIDJOWGGVBicgEyTQTCzIetUFssDKJSFhY2SARCYvBbGrj8QcIkYglk8xCmEQ9xqNexbQVY2Zub3un9/W7PwstHZH4Jie/7+Kc8/suzgnwr+kjBqSBbm2lkm6bHyH3XM9SZQ8Z8s3UQJPo0IJVof5EZ7v2faxMrKONlhmQWN5GSFEwLbhybjBPhDwVsmQ4AaA09Mou+k8d702EAzXiS6KEgzahoIthGOi6DtKlN71GS+/cEPs0WewaX2R9ZphssP776UhESY0WSpQNg7Jh4Anx+zgJVKpV3uZyvHjzir27NwGs/XVBH8c7N2nnjx7eSqlYxPM8JCCkxBU+rhA4dVhCYJgmyc4Ej96/7rLi8nNAPc/k2ZNp7cnTpziuiy8lvpSI+tvYhS/xpY8vJXMiEbZv3MzFq3cJqaqiPX72jnKt9kfQRPZ9f5qZ70sMawyAas1GseIy1rNtVXK8Mkm1VsP2PBzhYQuB5Qns+t6AJQSqqlIcrTAy+ONGENBWLF3MN71MxXGo1mE6DqbrYLou8z/a7L3uMKvgUnU8xk2T3u71ADGFDdgvCx/3TwkLEfKxhWDHbY+eYZ+Obz6tJcmRApRsuJ8Ex4Po7Jl8/TDBl7flm4Gm5F1vSZKaFQUh4cB9OLgaDB3UVrjwA+6tBnKAis4El8lwujmJSVQeoKAxFzqDcG0KWhZC6R30tUJRQD3Odxqy4G+DDFks4pisY5RLgRx5pZ5T4cKy95yhSrxZDBCaVqIMOpAd2EIeSEW7wLQh3Ar7RtCHbk0v0vQy1WdgCymgf147Sa0dhAOVMZgoALDu2BDZ/xloQAzQgIOhMCnPYQ+gHRvi4d/8n00kYDRVLifLAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEwLTAyLTExVDE0OjUxOjE3LTA2OjAwHh/NoQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAwNC0wOS0yMFQxNzoxMDoyNi0wNTowMCcJijsAAAAASUVORK5CYII=')} -${img('branch', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX///99plFAfADL27hpmyfP8YxyoilSiRiv0XGGygK02VtRiBmVwjh8xQCcziFZkhLz9+9BfQB2rwaCyACRygFQigXw9Ox0mkpXkQCJzwBblgBmkzP8/fxEgQBCfwBEgQejwITe3t5hkC1CfgBfjynZ2tmSq3eArDu72oNvoDJajyTY2dhFgQDCzLqhvn9EgAazx55XkwCVzC2824GMs1J0oUTY48xajiK72YR9qj2Tq3dhkix+th99xAB3uADA3oQ+fABEgABIgwW82oOUyi5VkgCf0CaEygB+wwCbzjN1mkrA3YZ1tAB7wAB+uB1vl0JdmgCJwwCKzwBoqAB4nVBikiuayzZ8wQCFywCg0Sjd3t1lkjFBfABLgwhKgwlmpgCK0QCJxQBclwDMzMzPz89GggCDpFxDfgCIpmPl5eUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABhAABQEABuZQBjYQBvcgAIZABiYQBlZAAABQDU/wCx/wCO/wBr/wBI/wAl/wAA/wAA3AAAuQAAlgAAcwAAUADU/wCx/wCO/wBr/wBI/wAl/wAA/gAA3AAAuQAAlgAAcwAAUADj/wDH/wCr/wCP/wBz/wBX/wBV/wBJ3AA9uQAxlgAlcwAZUADw/wDi/wDU/wDG/wC4/wCq/wCq/wCS3AB6uQBilgBKcwAyUAD//wD//wD//wD//wD//wD//wD+/gDc3AC5uQCWlgBzcwBQUAD/8AD/4gD/1AD/xgD/uAD/qgD/qgDckgC5egCWYgBzSgBQMgD/4wD/xwD/qwD/jwD/cwD/VwD/VQDcSQC5PQCWMQBzJQBQGQD/1AD/sQD/jgD/awD/SAD/JQD+AADcAAC5AACWAABzAABQAAD/1AD/sQD/jgD/awD/SAD/JQD/AADcAACwULzWAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAALZJREFUeNpjYAADRiZGBmTAzMLKxowswM7BycWNLMDEw8vHL4AkICgkLCIqhiQgLiEpJS0D5cjKySsoKimrqMJk1dQ1NLW0dXQZ9PTlZEECBoZGxiamOmbmmhaWViABaxtbO3sHRycTZxdXA7ANbu4enkxeDt4+vn7WIAH/gMCg4JBQprDwiEhBkEBUtGBMrI5OXHxCYpI/2BrV5OSU5NS09BjB6CiE01JTM5KTVZHcmpycCWEAANfrHJleKislAAAAAElFTkSuQmCC')} -${img('leaf', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX////M27mQs2tilDA9eQA7egBbkhVTjAxJgwWBqVdGgQBrnySdxViu0WrE4oaYv2PC35NtoCqxvaSevX5FgAB7qje73nDK6neu109vpyVupCGo2kJ9xwBQhBtilC9pnx7G63PM6olgnAB/vQBDigCVv0yb1CaDzAB8uBJwmkNnnBnB52ui2Ca94WZopAE/hgCtz2ue2CmDywCByACKujtdjyqdvHpdlhLV9YdkowCFxwCw1lFXmAJvpC5jng1coABlpwBprAB8sitAfABDfgKx31Gr3TuCsi5sqABtqgBUkxTV85zL7I213mef0j+OxyKk00k/ewCp3TCSyhCw0mRRjQC23HmU0h55wQB5vQB4uQB1tgCIwBeJxgCBvQDC3ndCjACYx1204Fx6wwB7vQB1tABzsQBBfQBpkzdtpQB9tQA/iQCMu1SMukNUlQBYmQBsqAd4rh11rwZyrQBvqgBDfwCqvZVWkQBUnACp0Hq/43K733C+4X+w12eZyT2IvSN5sgpZkwBxmUSDqFlbnACJzQy742p/wwB2ugBysgBwrwBvqwBwqQBhmgBCfwDV2NN8pk1foACO1QBZmABRkABpqwB3uQB0sgB0rgBnogBUjgC7w7NymkFdnQBUhxmis41okjdCfgBGgQWHpWPMzMzb3NtumD5NhQzT09Pv8O/a2trOz87l5eXc3NzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtHAA4HXQAAEgAAB9CTigAAABCfCQ4HTxy6Kw4HXRy+8xy+8wAAMwAAAAAAAAAAAAAAAAAAAAAAAgAAAgAABgYAAG7AAAAACgAAAgAAAgYAAEAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Hnw4HnwAAFRpRiYmO2V0aWRtSSY7ZWdsZVNpdGNBO251amRGO3R0bCYmO3J3ZWlvVCY7c2xuaVc7d28ABCwBG8q3AAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOtJREFUeNpjYIACRiZmFlY2dg4ol5OLm4eXj19AUAjMFRYRFROXkJSSlpEF8+XkFRSVlFVU1dQ1NMF8LW0dXT19A0MjYxNTIN/M3MLSytrG1s7ewdHJGSjg4urm7uHp5e3j6+cfABIIDAoOCVUJC4+IjIqOAQk4x8bFJyQmJadEpaalpQMFMjKzsnNy8/ILCouKS0qBAmXlFZVV1TW1dfUNJY1NQIHmlta29o7ozq7unt6+fgaGCRMnTZ4ydVrU9BkzZ5XOBiqYM3HuvPkL0tPTFy5avATkzqXLlq9YuWoJEKxeA/Ho2nUMyAAA9OtDOfv2TiUAAAAASUVORK5CYII=')} -${img('leaf_method', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAKlBMVEUAAAAAgADzExMAgIAAAADAwMCAgADxGRnuFxLnHhHuIyPKJQ/rLi7////aW8ZOAAAAAXRSTlMAQObYZgAAAAFiS0dEDfa0YfUAAAAHdElNRQfgCxIPFR/msbP7AAAAaUlEQVQI12NggANBBiYFMMNQxAjCYA4UUoZIBRpBGMyiQorGIIaxWRCEwSYo3igiCNJlaLkwGSwkJn1QGMhgNDQ0TDU2dACqERYTDksGG5SkmGoApBnFhBRTBUAiaYJpDIJgs10cGBgdACxbDamu76Z5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTAxLTE3VDA5OjMwOjM1KzAxOjAwyGHxKQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0xMS0xOFQxNToyMTozMSswMTowMJgvuUkAAAAASUVORK5CYII=')} -${img('globe', 18, 'gif', 'R0lGODlhEwASAPcAAPr7/AFyAQFCpwGD6jy3/wE9on7N0AE+pAFjyMLI0AE2mwF94wGP9QFpzgU3nISSopWgrmJsfTNLfgFHqAFuBilNiTp4sLnGzwWb/0xYb/P09mRygGl0hRlnMgR12V2Pr6e4xF9peS2Cyh5FpBdSfgF84YmisdPa30hjvw+foQFYvlWj4HWIlkWb5gk5n/b4+gw+kgFMscXb6ylmieDj5ju2pylTsniElgqd/u/x8wGW/O7v8SVMsUq+JSSJXQFiwfv+/AFqvB9ntobZeKbc/9vt+B+YmW2rvKruzQGPkm3PPrjmxQFIklrFLVbD4QGMYaXkoIPD13LC+nGw5AGFQHG66gF2eBaJxket9sLf84HI+wF7axBdbg2c0CR+1QFsEIfJ7yqoUIbH41tldgF+KzVTjn3QfitZgTJZkaDR8gKDsXeWrE+zogE3nCeKzQFtJ0tknjdnbQGB6EJgxQFqAcLJ0WC//yKm/wE+o7vI0ARozEOz/4/g/4KToyaX4/D09pCpuNHV24HA6gw7oAF/AXWKnEVSb5TI6VzDTrPprxBQts7e6FNdcBA9oySd9RRjPAhnD2NvgIydrF+6wdLo9v7//2K+twKSdDmKyeD56wGCyHq12VnF+ZXXsARdTjZWthShoo7gtilDlAFw1RCXvF+z6p/R8kqZzAF0Oj5jjFuJqgFoAkRgxtzr9YmcrJKsugFlylfBgxJGhjJIeFnFuhmi/+bo65ipt8Hn+UhVco7B5SZowAGBKoaZqAGGAVHBUwF8Qq7Y819qe4DEoVyYwrnb8QGN9GCy6QFTuHB9jgGY/gFRtuTu9ZOhr150iwFbwTFiwFus4h9mYt/y+kWZ35vM7hGfccz43Xy/6m3BuS1GiYveqDRfwnbUV4rdu////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAN8ALAAAAAATABIAAAj/AL8JHEiwVTVspar8ITiwiJhswyaBibJJUq9Trxh+S2OAVihvSzqRcoTpmy5ADIPFqrHtGpBETbrIuXJEBgiGbHoogTItExJOoAbw8rHmAkFTC8KYwTWkGx8COp4AozAjD8Epo4wQQfTLCQEcxqigoiONBUFqerRYspYCgzIGmgi98cRlA8EVLaR4UJPk0oASVgKs6kAiBMFDdrzAarDFF5kgCJA9ilNBGMFjWAQse/YjwBcVMfCcgTMr2UBKe0QIaHNgAiQmBRS4+CSKEYSBWe44E6JoEAxZDhrxmDPCEAcaA4vVinTCwi5uKFhBs6EtQ4QEOQYy8+NGUDRiqdCUJJGQa8yNQDsADHyxSNUHE4Vc3erzoFkdWxoAVNLIv7///98EBAA7')} -${img('canvas', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX/AAC1t7etsLCsrq6rrq6rrq2tr6+0tratsK/////p6enIysrl5OTn5uXo5+ajpaXo5+dhhKdliKlmialgg6elp6f6+/vIycnr7Ozw7u7x7u7x7u3t6+vLzMvp7vbs7/bz8PD17+3z7u2rrq/6xS76xy13zv9+z/+EwLF4zP/38/NfgqWAoL36uCj6vCmR2f+TxamSrBmNvoj++fz8+Pf69/WZ3f+g4P+n4/+Cnw2Dox16nQ3//f9hg6eBob6x5/+46f+77P+p2NKSZhOi1s////7//fusrq98sB6CsyWDtSmFuC9+dBl/tilfgqasr6+sr7DbAADcAABcgqWAoLyusLC4urqssLCssLGrsLCrr7Ctr67c3NzMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAKAgJldmV0dU8GB3JvTnZDBWVyb2xsYwdjYWxhUBB0bmVrY2F1b3IICGRPYmFyZWQAAAXj1P/Hsf+rjv+Pa/9zSP9XJf9VAP9JANw9ALkxAJYlAHMZAFDU1P+xsf+Ojv9ra/9ISP8lJf8AAP4AANwAALkAAJYAAHMAAFDU4/+xx/+Oq/9rj/9Ic/8lV/8AVf8ASdwAPbkAMZYAJXMAGVDU8P+x4v+O1P9rxv9IuP8lqv8Aqv8AktwAerkAYpYASnMAMlDU//+x//+O//9r//9I//8l//8A/v4A3NwAubkAlpYAc3MAUFDU//Cx/+KO/9Rr/8ZI/7gl/6oA/6oA3JIAuXoAlmIAc0oAUDLU/+Ox/8eO/6tr/49I/3Ml/1cA/1UA3EkAuT0AljEAcyUAUBnU/9Sx/7GO/45r/2tI/0gl/yUA/gAA3AAAuQAAlgAAcwAAUADj/9TH/7Gr/46P/2tz/0hX/yVV/wBJ3AAQ+AFLAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAALpJREFUeNpjYGBkYmZhYWFlYWNngAAOTijg4oYIMHPy8PLx8nDycwpwQwUEhYSFRDhFxTi5xCECEpJS0jKcsqL8nGwgARZOOXkFRSWwMcwgAWVOFVU1dQ1NLW0dmICunr6BoZGxiSlEgJnTzNzC0sraxtYOJmDv4Ojk7MLp6gYRcOf08PTy9vHl9IOa4c+JAGCBAM7AoEDOwEDO4BCIABOSilCQQBhTeERkVGS4f3R0aBhIICYWAWIYGAClIBsa7hXG7gAAAABJRU5ErkJggg==')} -${img('profile', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsSAAALEgHS3X78AAABZElEQVR42o1R22rCQBD1U/p//apCNtsHwRdfBaFIKbRoUVKMMTWBWIxVCq2b+07POrn4UKjDMpw9O2fm7G5vNBpJKe2/Qto4uEc2WMrBYEBEPaAky36UulwnlSRpUeZEBSGrpEiyHJVGAPVJqZvbO3ftv83Dle+vvPV4/LD0PGYAcKrSFJUsEOgHKoj3s9dFGH9uou3k8ekQKxyDQcYpBnYC7Hm9zBZmlL8BiIJDC0AWpa4FwhZJXoDCBgYAjgU5ToBt+k1tL14ssFNNvIEBAFwVljJlSDBfpwyg1ISnYoEsiHju5XLcd+T50q0tEQm7eaWKKNfUWgKApUsbPFY0lzY6DraEZm585Do/CLMzqLQWQnSC9k34lVa7PTsBs/zYOa4LB5ZlnQXCbif40Ra50jUwE6JtCcMlUiMQlugEQYisG8CWtGlRdQL+jmui/rjhcAhk/Reo6ff7RuB53vN1MZ1OIfgFQC1cuR3Y6lIAAAAASUVORK5CYII=')} -${img('execute', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEXAwMAAxwCvbOAvAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAACBJREFUCFtjYIABHgYGfiA6wMD/gYH/B5g8ABLhYUAGAHniBNrUPuoHAAAAAElFTkSuQmCC')} -${img('file', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAA2klEQVRIx61VURbDIAgTX+9ljg4n2z5sNouj1ml+LE9rQkSU5PA6kTZBToTznj5aqKqq+py4lFJKScnMzCwlAAB6IbnNuyXycd1g3oHrf32CmR9mZqpVOdDHs2DmI+c+AiJixu1RAN9xFUcdWCjVIr8xCX8Jubc8Ao9CJF8nRFgNJBxZSCEkjmrIxxSS0yIAoBU4OkpfU8sCPEbEvqaOXcR31zWORbYJ8EI8rsK+DWm7gMVb8F/GK7eg6818jNjJZjMn0agY7x6oxqL5sWbIbhLHoQN78PQ5F3kDgX8u9tphBfoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMDItMDZUMTA6Mjc6MzErMDE6MDChLu/mAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjIwKzAxOjAwIGvf8wAAAABJRU5ErkJggg==')} -${img('text', 16, 'gif', 'R0lGODlhEgASALMAAP/////MzP+Zmf9mZv8zM/8AAMzM/8zMzJmZ/5mZmWZm/2ZmZjMz/zMzMwAA/////yH5BAUUAA8ALAAAAAASABIAAARo8MlJq73SKGSwdSDjUQoIjhNYOujDnGAnFXRBZKoBIpMw1ICHaaigBAq/AUK1CVEIhcfPNFlRbAEBEvWr0VDYQLYgkCQWh8XiAfgRymPyoTFRa2uPO009maP8ZmsjAHxnBygLDQ1zihEAOw==')} -${img('task', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAAAAACMfPpKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAATklEQVQY05XQUQoAIAgD0N3JY3fIChWttKR9xYvBCj0J0FsI3VVKQflwV22J0oyo3LOCc6pHW4dqi56v2CebbpMLtcmr+uTizz6UYpBnADSS8gvhaL5WAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA0LTA3VDA5OjQyOjQ4KzAyOjAwMgzRmQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOCswMTowMJ0LlncAAAAASUVORK5CYII=')} -${img('pavetext', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAsSURBVBjTY2CgCuBAAt1gASS5KKgARBpJACSEooIsARRbkABYoDsKCRDhEQBA2Am/6OrPewAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0wNFQxMDoxODoyNyswMTowMHsz6UQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MjArMDE6MDAga9/zAAAAAElFTkSuQmCC')} -${img('pavelabel', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAApSURBVBjTY2CgCuBAAt1gASS5KJgABzUEgABFANUWJAAWYIhCAkR4BAAHoAkEyi2U3wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0wNFQxMDoxODoyNyswMTowMHsz6UQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MjArMDE6MDAga9/zAAAAAElFTkSuQmCC')} -${img('list', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEECTc01vBgywAAAE9JREFUOMu1k8ERwDAMwqRc9l/Z/eeRpKZlABkOLFD0JQGgAAah5kp8Y30F2HEwDhGTCG6tX5yqtAV/acEdwHQHl0Y8RbA7pLIxRPziGyM9xLEOKSpp/5AAAAAASUVORK5CYII=')} -${img('color', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAAAA4xcavGZGS1xZT79lW+9wdvFz/3N6fo3RISTZwXbyniXz80v/AAD/zAD/66v//6vGWiYeAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADswAAA7MAbGhBn4AAAAHdElNRQfgAQQLLBhOmhPcAAAAIklEQVQY02NgRgEMDAzMnLzcfDwC7IxMbKwsQ10A3XMEAQA3JQVNowlkTAAAAABJRU5ErkJggg==')} -${img('colz', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEV6fo0A4xcavGZGS1xZT79lW+9wdvFz/3PRISTZwXbyniXz80v/AAD/zAD/66v//6t1AkcGAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADswAAA7MAbGhBn4AAAAHdElNRQfgAQQLNwdqpNWzAAAAT0lEQVQI12NgYGAwNjZmAAOLjmY0hs2ZwxCG1arFEIbt3csQhvXuzRCG/f/PEIZ5eTGEYSgoDGEYKSlDGGZpyRCGaWgwhGHi4gxhwG0HAwCr3BFWzqCkcAAAAABJRU5ErkJggg==')} -${img('frame', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAQQLOwq4oOYCAAAAcUlEQVQoz7WQMQqAMAxFX0Uk4OLgIbp4oZ7BA/cOXR0KDnGpRbGayT+EQF74nw+GHIBo+5hdWdqAaFDoLIsegCSeWE0VcMxXYM6xvmiZSYDTooSR4WlxzzBZwGYBuwWs4mWUpVHJe1H9F1J7yC4ov+kAkTYXFCNzDrEAAAAASUVORK5CYII=')} -${img('class', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAvQC9AL1pQtWoAAAAjUlEQVR42p2T2wnAIAxFM0g/O6jDdBBHcAyHKKQYjfiI0UY4P8I9BG4CID8smB4+8SUsohpO3CFzKqmBFrhCO4kqQnCR6MJF4BEJTVQFhBAmASNIZkH6a0OMc8oUDAu8z7RhTTBVyIIEhxeCdYWjQApvK2TBrgGpwpP1livsBXC0ROMO/LqDKjKEzaf8AZWbJP6pTT9BAAAATHpUWHRTb2Z0d2FyZQAAeNpz0FDW9MxNTE/1TUzPTM5WMNEz0jNQsLTUNzDWNzBUSC7KLC6pdMitLC7JTNZLLdZLKS3IzyvRS87PBQDzvxJ8u4pLSgAAADN6VFh0U2lnbmF0dXJlAAB42ktKs0hLMkk2MzJKNEuzMLKwtEizSElMMbNITUw0NUtNAQCc7Qma0Goe1QAAAABJRU5ErkJggg==')} -${img('member', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAvQC9AL1pQtWoAAAAX0lEQVR42mNgAAIVBob/+DADPgBS8GCPBV6M1xCKDcDnBRcoZhgW4D8DBV75v2bLATAmxyC4ZmRMrCFYNfeU9BBvwJwpS8AYWTNZBoAwTDPFBpAciDCDyNFMtXSAFwAAUyq0GRPbbz4AAABMelRYdFNvZnR3YXJlAAB42nPQUNb0zE1MT/VNTM9MzlYw0TPSM1CwtNQ3MNY3MFRILsosLql0yK0sLslM1kst1kspLcjPK9FLzs8FAPO/Eny7iktKAAAAM3pUWHRTaWduYXR1cmUAAHjaS01JNrE0S00zSbU0NEsxMbMwM0xOSjYwNzY3NLRIMjUCAJcdCJ2BHe6SAAAAAElFTkSuQmCC')} -${img('tf1', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX/////AP8/SMz///+Cf5VqAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfgCw4QHgSCla+2AAAAL0lEQVQI12MQYAACrAQXiFBoABINCgwMQgwcDAwSDEwMDKmhodMYJjAwaKDrAAEAoRAEjHDJ/uQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMTEtMTRUMTc6Mjk6MjErMDE6MDDxcSccAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTExLTE0VDE3OjI5OjA1KzAxOjAwNka8zgAAAABJRU5ErkJggg==')} -${img('tf2', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX/////AP8A/wD////pL6WoAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfgCw4PNgzGaW1jAAAARUlEQVQI12NgEGDQZAASKkBigQKQ6GhgYBDiYgASIiAigIGBS8iBgUFhEpCnoAEkUkNDQxkagUIMrUDMMAVETAARQI0MAD5GCJ7tAr1aAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTExLTE0VDE2OjUxOjUzKzAxOjAwi1Gz3gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0xMS0xNFQxNjo1MTozNiswMTowMG5bLUIAAAAASUVORK5CYII=')} -`, node, 'jsroot_hstyle'); -} + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; -/** @summary Return size as string with suffix like MB or KB - * @private */ -function getSizeStr(sz) { - if (sz < 10000) - return sz.toFixed(0) + 'B'; - if (sz < 1e6) - return (sz/1e3).toFixed(2) + 'KiB'; - if (sz < 1e9) - return (sz/1e6).toFixed(2) + 'MiB'; - return (sz/1e9).toFixed(2) + 'GiB'; -} +var strings = { + string2buf: string2buf, + buf2string: buf2string, + utf8border: utf8border +}; -/** @summary draw list content - * @desc used to draw all items from TList or TObjArray inserted into the TCanvas list of primitives - * @private */ -async function drawList(dom, lst, opt) { - if (!lst || !lst.arr) - return null; +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +var zstream = ZStream; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// See state defs from inflate.js +const BAD$1 = 16209; /* got a data error -- remain here until reset */ +const TYPE$1 = 16191; /* i: waiting for type bits, including last-flag bit */ - const handle = { - dom, lst, opt, - indx: -1, painter: null, - draw_next() { - while (++this.indx < this.lst.arr.length) { - const item = this.lst.arr[this.indx], - opt = (this.lst.opt && this.lst.opt[this.indx]) ? this.lst.opt[this.indx] : this.opt; - if (!item) continue; - return draw(this.dom, item, opt).then(p => { - if (p && !this.painter) this.painter = p; - return this.draw_next(); // reenter loop - }); +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +var inffast = function inflate_fast(strm, start) { + let _in; /* local strm.input */ + let last; /* have enough input while in < last */ + let _out; /* local strm.output */ + let beg; /* inflate()'s initial strm.output */ + let end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + let dmax; /* maximum distance from zlib header */ +//#endif + let wsize; /* window size or zero if not using window */ + let whave; /* valid bytes in the window */ + let wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + let s_window; /* allocated sliding window, if wsize != 0 */ + let hold; /* local strm.hold */ + let bits; /* local strm.bits */ + let lcode; /* local strm.lencode */ + let dcode; /* local strm.distcode */ + let lmask; /* mask for first level of length codes */ + let dmask; /* mask for first level of distance codes */ + let here; /* retrieved table entry */ + let op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + let len; /* match length, unused bytes */ + let dist; /* match distance */ + let from; /* where to copy match from */ + let from_source; + + + let input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + const state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; } - return this.painter; - } - }; + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD$1; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD$1; + break top; + } - return handle.draw_next(); -} +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD$1; + break top; + } -// ===================== hierarchy scanning functions ================================== + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE$1; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD$1; + break top; + } -/** @summary Create hierarchy elements for TFolder object - * @private */ -function folderHierarchy(item, obj) { - if (!obj?.fFolders) - return false; + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; - if (obj.fFolders.arr.length === 0) { - item._more = false; - return true; - } +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const MAXBITS = 15; +const ENOUGH_LENS$1 = 852; +const ENOUGH_DISTS$1 = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const CODES$1 = 0; +const LENS$1 = 1; +const DISTS$1 = 2; + +const lbase = new Uint16Array([ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]); + +const lext = new Uint8Array([ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]); + +const dbase = new Uint16Array([ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]); + +const dext = new Uint8Array([ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]); + +const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => +{ + const bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + let len = 0; /* a code's length in bits */ + let sym = 0; /* index of code symbols */ + let min = 0, max = 0; /* minimum and maximum code lengths */ + let root = 0; /* number of index bits for root table */ + let curr = 0; /* number of index bits for current table */ + let drop = 0; /* code bits to drop for sub-table */ + let left = 0; /* number of prefix codes available */ + let used = 0; /* code entries in table used */ + let huff = 0; /* Huffman code */ + let incr; /* for incrementing code, index */ + let fill; /* index for replicating entries */ + let low; /* low bits for current root entry */ + let mask; /* mask for low root bits */ + let next; /* next available space in table */ + let base = null; /* base value table to use */ +// let shoextra; /* extra bits table to use */ + let match; /* use base and extra for symbol >= match */ + const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + let extra = null; + + let here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ - item._childs = []; + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } - for (let i = 0; i < obj.fFolders.arr.length; ++i) { - const chld = obj.fFolders.arr[i]; - item._childs.push({ - _name: chld.fName, - _kind: prROOT + chld._typename, - _obj: chld - }); - } - return true; -} + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; -/** @summary Create hierarchy elements for TTask object - * @private */ -function taskHierarchy(item, obj) { - // function can be used for different derived classes - // we show not only child tasks, but all complex data members - if (!obj?.fTasks) return false; + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; - objectHierarchy(item, obj, { exclude: ['fTasks', 'fName'] }); + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } - if ((obj.fTasks.arr.length === 0) && (item._childs.length === 0)) { - item._more = false; - return true; - } + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES$1 || max !== 1)) { + return -1; /* incomplete set */ + } - // item._childs = []; + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } - for (let i = 0; i < obj.fTasks.arr.length; ++i) { - const chld = obj.fTasks.arr[i]; - item._childs.push({ - _name: chld.fName, - _kind: prROOT + chld._typename, - _obj: chld - }); - } - return true; -} + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } -/** @summary Create hierarchy elements for TList object - * @private */ -function listHierarchy(folder, lst) { - if (!isRootCollection(lst)) return false; + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ - if ((lst.arr === undefined) || (lst.arr.length === 0)) { - folder._more = false; - return true; - } + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES$1) { + base = extra = work; /* dummy value--not used */ + match = 20; + + } else if (type === LENS$1) { + base = lbase; + extra = lext; + match = 257; + + } else { /* DISTS */ + base = dbase; + extra = dext; + match = 0; + } - let do_context = false, prnt = folder; - while (prnt) { - if (prnt._do_context) do_context = true; - prnt = prnt._parent; - } + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1)) { + return 1; + } - // if list has objects with similar names, create cycle number for them - const ismap = (lst._typename === clTMap), names = [], cnt = [], cycle = []; + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] + 1 < match) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] >= match) { + here_op = extra[work[sym] - match]; + here_val = base[work[sym] - match]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } - for (let i = 0; i < lst.arr.length; ++i) { - const obj = ismap ? lst.arr[i].first : lst.arr[i]; - if (!obj) continue; // for such objects index will be used as name - const objname = obj.fName || obj.name; - if (!objname) continue; - const indx = names.indexOf(objname); - if (indx >= 0) - cnt[indx]++; - else { - cnt[names.length] = cycle[names.length] = 1; - names.push(objname); - } - } + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } - folder._childs = []; - for (let i = 0; i < lst.arr.length; ++i) { - const obj = ismap ? lst.arr[i].first : lst.arr[i]; - let item; - if (!obj?._typename) { - item = { - _name: i.toString(), - _kind: prROOT + 'NULL', - _title: 'NULL', - _value: 'null', - _obj: null - }; - } else { - item = { - _name: obj.fName || obj.name, - _kind: prROOT + obj._typename, - _title: `${obj.fTitle || ''} type:${obj._typename}`, - _obj: obj - }; + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } - switch (obj._typename) { - case clTColor: item._value = getRGBfromTColor(obj); break; - case clTText: - case clTLatex: item._value = obj.fTitle; break; - case clTObjString: item._value = obj.fString; break; - default: if (lst.opt && lst.opt[i] && lst.opt[i].length) item._value = lst.opt[i]; - } + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } - if (do_context && canDrawHandle(obj._typename)) item._direct_context = true; + /* increment past last table */ + next += min; /* here min is 1 << curr */ - // if name is integer value, it should match array index - if (!item._name || (Number.isInteger(parseInt(item._name)) && (parseInt(item._name) !== i)) || (lst.arr.indexOf(obj) < i)) - item._name = i.toString(); - else { - // if there are several such names, add cycle number to the item name - const indx = names.indexOf(obj.fName); - if ((indx >= 0) && (cnt[indx] > 1)) { - item._cycle = cycle[indx]++; - item._keyname = item._name; - item._name = item._keyname + ';' + item._cycle; - } - } + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1)) { + return 1; } - folder._childs.push(item); - } - return true; -} + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + + +var inftrees = inflate_table; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + -/** @summary Create hierarchy of TKey lists in file or sub-directory - * @private */ -function keysHierarchy(folder, keys, file, dirname) { - if (keys === undefined) return false; - folder._childs = []; - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - if (settings.OnlyLastCycle && (i > 0) && (key.fName === keys[i-1].fName) && (key.fCycle < keys[i-1].fCycle)) continue; - const item = { - _name: key.fName + ';' + key.fCycle, - _cycle: key.fCycle, - _kind: prROOT + key.fClassName, - _title: key.fTitle + ` (size: ${getSizeStr(key.fObjlen)})`, - _keyname: key.fName, - _readobj: null, - _parent: folder - }; +const CODES = 0; +const LENS = 1; +const DISTS = 2; - if (key.fRealName) - item._realname = key.fRealName + ';' + key.fCycle; +/* Public constants ==========================================================*/ +/* ===========================================================================*/ - if (key.fClassName === clTDirectory || key.fClassName === clTDirectoryFile) { - const dir = (dirname && file) ? file.getDir(dirname + key.fName) : null; - if (dir) { - // remove cycle number - we have already directory - item._name = key.fName; - keysHierarchy(item, dir.fKeys, file, dirname + key.fName + '/'); - } else { - item._more = true; - item._expand = function(node, obj) { - // one can get expand call from child objects - ignore them - return keysHierarchy(node, obj.fKeys); - }; - } - } else if ((key.fClassName === clTList) && (key.fName === nameStreamerInfo)) { - if (settings.SkipStreamerInfos) continue; - item._name = nameStreamerInfo; - item._kind = prROOT + clTStreamerInfoList; - item._title = 'List of streamer infos for binary I/O'; - item._readobj = file.fStreamerInfos; - } +const { + Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES, + Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR, + Z_DEFLATED +} = constants$2; - folder._childs.push(item); - } - return true; -} +/* STATES ====================================================================*/ +/* ===========================================================================*/ -/** @summary Create hierarchy for arbitrary object - * @private */ -function objectHierarchy(top, obj, args = undefined) { - if (!top || (obj === null)) return false; - top._childs = []; +const HEAD = 16180; /* i: waiting for magic header */ +const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ +const TIME = 16182; /* i: waiting for modification time (gzip) */ +const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ +const EXLEN = 16184; /* i: waiting for extra length (gzip) */ +const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ +const NAME = 16186; /* i: waiting for end of file name (gzip) */ +const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ +const HCRC = 16188; /* i: waiting for header crc (gzip) */ +const DICTID = 16189; /* i: waiting for dictionary check value */ +const DICT = 16190; /* waiting for inflateSetDictionary() call */ +const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ +const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ +const STORED = 16193; /* i: waiting for stored size (length and complement) */ +const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ +const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ +const TABLE = 16196; /* i: waiting for dynamic block table lengths */ +const LENLENS = 16197; /* i: waiting for code length code lengths */ +const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ +const LEN_ = 16199; /* i: same as LEN below, but only first time in */ +const LEN = 16200; /* i: waiting for length/lit/eob code */ +const LENEXT = 16201; /* i: waiting for length extra bits */ +const DIST = 16202; /* i: waiting for distance code */ +const DISTEXT = 16203; /* i: waiting for distance extra bits */ +const MATCH = 16204; /* o: waiting for output space to copy string */ +const LIT = 16205; /* o: waiting for output space to write literal */ +const CHECK = 16206; /* i: waiting for 32-bit check value */ +const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ +const DONE = 16208; /* finished check, done -- remain here until reset */ +const BAD = 16209; /* got a data error -- remain here until reset */ +const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ +const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ - let proto = Object.prototype.toString.apply(obj); +/* ===========================================================================*/ - if (proto === '[object DataView]') { - let item = { - _parent: top, - _name: 'size', - _value: obj.byteLength.toString(), - _vclass: 'h_value_num' - }; - top._childs.push(item); - const namelen = (obj.byteLength < 10) ? 1 : Math.log10(obj.byteLength); - for (let k = 0; k < obj.byteLength; ++k) { - if (k % 16 === 0) { - item = { - _parent: top, - _name: k.toString(), - _value: '', - _vclass: 'h_value_num' - }; - while (item._name.length < namelen) - item._name = '0' + item._name; - top._childs.push(item); - } +const ENOUGH_LENS = 852; +const ENOUGH_DISTS = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - let val = obj.getUint8(k).toString(16); - while (val.length < 2) val = '0'+val; - if (item._value) - item._value += (k % 4 === 0) ? ' | ' : ' '; +const MAX_WBITS = 15; +/* 32K LZ77 window */ +const DEF_WBITS = MAX_WBITS; - item._value += val; - } - return true; - } - // check nosimple property in all parents - let nosimple = true, do_context = false, prnt = top; - while (prnt) { - if (prnt._do_context) do_context = true; - if ('_nosimple' in prnt) { nosimple = prnt._nosimple; break; } - prnt = prnt._parent; - } +const zswap32 = (q) => { - const isarray = (isArrayProto(proto) > 0) && obj.length, - compress = isarray && (obj.length > settings.HierarchyLimit); - let arrcompress = false; + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +}; - if (isarray && (top._name === 'Object') && !top._parent) top._name = 'Array'; - if (compress) { - arrcompress = true; - for (let k = 0; k < obj.length; ++k) { - const typ = typeof obj[k]; - if ((typ === 'number') || (typ === 'boolean') || ((typ === 'string') && (obj[k].length < 16))) continue; - arrcompress = false; break; - } - } +function InflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, + bit 2 true to validate check value */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib), or + -1 if raw or no header yet */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new Uint16Array(320); /* temporary storage for code lengths */ + this.work = new Uint16Array(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new Int32Array(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} - if (!('_obj' in top)) - top._obj = obj; - else if (top._obj !== obj) - alert('object missmatch'); - if (!top._title) { - if (obj._typename) - top._title = prROOT + obj._typename; - else if (isarray) - top._title = 'Array len: ' + obj.length; - } +const inflateStateCheck = (strm) => { - if (arrcompress) { - for (let k = 0; k < obj.length;) { - let nextk = Math.min(k+10, obj.length), allsame = true, prevk = k; + if (!strm) { + return 1; + } + const state = strm.state; + if (!state || state.strm !== strm || + state.mode < HEAD || state.mode > SYNC) { + return 1; + } + return 0; +}; - while (allsame) { - allsame = true; - for (let d=prevk; d { - const item = { _parent: top, _name: k+'..'+(nextk-1), _vclass: 'h_value_num' }; + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.flags = -1; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); + state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK$1; +}; - if (allsame) - item._value = obj[k].toString(); - else { - item._value = ''; - for (let d = k; d < nextk; ++d) - item._value += ((d===k) ? '[ ' : ', ') + obj[d].toString(); - item._value += ' ]'; - } - top._childs.push(item); +const inflateReset = (strm) => { - k = nextk; - } - return true; - } + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); - let lastitem, lastkey, lastfield, cnt; +}; - for (const key in obj) { - if ((key === '_typename') || (key[0] === '$')) continue; - const fld = obj[key]; - if (isFunc(fld)) continue; - if (args?.exclude && (args.exclude.indexOf(key) >= 0)) continue; - if (compress && lastitem) { - if (lastfield===fld) { ++cnt; lastkey = key; continue; } - if (cnt > 0) lastitem._name += '..' + lastkey; - } +const inflateReset2 = (strm, windowBits) => { + let wrap; - const item = { _parent: top, _name: key }; + /* get the state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; - if (compress) { lastitem = item; lastkey = key; lastfield = fld; cnt = 0; } + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 5; + if (windowBits < 48) { + windowBits &= 15; + } + } - if (fld === null) { - item._value = item._title = 'null'; - if (!nosimple) top._childs.push(item); - continue; - } + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR$1; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } - let simple = false; + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +}; - if (isObject(fld)) { - proto = Object.prototype.toString.apply(fld); - if (isArrayProto(proto) > 0) { - item._title = 'array len=' + fld.length; - simple = (proto !== '[object Array]'); - if (fld.length === 0) { - item._value = '[ ]'; - item._more = false; // hpainter will not try to expand again - } else { - item._value = '[...]'; - item._more = true; - item._expand = objectHierarchy; - item._obj = fld; - } - } else if (proto === '[object DataView]') { - item._title = 'DataView len=' + fld.byteLength; - item._value = '[...]'; - item._more = true; - item._expand = objectHierarchy; - item._obj = fld; - } else if (proto === '[object Date]') { - item._more = false; - item._title = 'Date'; - item._value = fld.toString(); - item._vclass = 'h_value_num'; - } else { - if (fld.$kind || fld._typename) - item._kind = item._title = prROOT + (fld.$kind || fld._typename); +const inflateInit2 = (strm, windowBits) => { - if (fld._typename) { - item._title = fld._typename; - if (do_context && canDrawHandle(fld._typename)) item._direct_context = true; - } + if (!strm) { return Z_STREAM_ERROR$1; } + //strm.msg = Z_NULL; /* in case we return an error */ - // check if object already shown in hierarchy (circular dependency) - let curr = top, inparent = false; - while (curr && !inparent) { - inparent = (curr._obj === fld); - curr = curr._parent; - } + const state = new InflateState(); - if (inparent) { - item._value = '{ prnt }'; - item._vclass = 'h_value_num'; - item._more = false; - simple = true; - } else { - item._obj = fld; - item._more = false; + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.strm = strm; + state.window = null/*Z_NULL*/; + state.mode = HEAD; /* to pass state test in inflateReset2() */ + const ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK$1) { + strm.state = null/*Z_NULL*/; + } + return ret; +}; - switch (fld._typename) { - case clTColor: item._value = getRGBfromTColor(fld); break; - case clTText: - case clTLatex: item._value = fld.fTitle; break; - case clTObjString: item._value = fld.fString; break; - default: - if (isRootCollection(fld) && isObject(fld.arr)) { - item._value = fld.arr.length ? '[...]' : '[]'; - item._title += ', size:' + fld.arr.length; - if (fld.arr.length > 0) item._more = true; - } else { - item._more = true; - item._value = '{ }'; - } - } - } - } - } else if ((typeof fld === 'number') || (typeof fld === 'boolean')) { - simple = true; - if (key === 'fBits') - item._value = '0x' + fld.toString(16); - else - item._value = fld.toString(); - item._vclass = 'h_value_num'; - } else if (isStr(fld)) { - simple = true; - item._value = '"' + fld.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>') + '"'; - item._vclass = 'h_value_str'; - } else if (typeof fld === 'undefined') { - simple = true; - item._value = 'undefined'; - item._vclass = 'h_value_num'; - } else { - simple = true; - alert(`miss ${key} type ${typeof fld}`); - } - if (!simple || !nosimple) - top._childs.push(item); - } +const inflateInit = (strm) => { - if (compress && lastitem && (cnt > 0)) - lastitem._name += '..' + lastkey; + return inflateInit2(strm, DEF_WBITS); +}; - return true; -} -/** @summary Create hierarchy for streamer info object - * @private */ -function createStreamerInfoContent(lst) { - const h = { _name: nameStreamerInfo, _childs: [] }; +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +let virgin = true; - for (let i = 0; i < lst.arr.length; ++i) { - const entry = lst.arr[i]; +let lenfix, distfix; // We have no pointers in JS, so keep tables separate - if (entry._typename === clTList) continue; - if (typeof entry.fName === 'undefined') { - console.warn(`strange element in StreamerInfo with type ${entry._typename}`); - continue; - } +const fixedtables = (state) => { - const item = { - _name: `${entry.fName};${entry.fClassVersion}`, - _kind: `class ${entry.fName}`, - _title: `class:${entry.fName} version:${entry.fClassVersion} checksum:${entry.fCheckSum}`, - _icon: 'img_class', - _childs: [] - }; + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + lenfix = new Int32Array(512); + distfix = new Int32Array(32); - if (entry.fTitle) - item._title += ' ' + entry.fTitle; + /* literal/length table */ + let sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } - h._childs.push(item); + inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); - if (typeof entry.fElements === 'undefined') - continue; - for (let l = 0; l < entry.fElements.arr.length; ++l) { - const elem = entry.fElements.arr[l]; - if (!elem?.fName) continue; - let _name = `${elem.fTypeName} ${elem.fName}`; - const _title = `${elem.fTypeName} type:${elem.fType}`; - if (elem.fArrayDim === 1) - _name += `[${elem.fArrayLength}]`; - else { - for (let dim = 0; dim < elem.fArrayDim; ++dim) - _name += `[${elem.fMaxIndex[dim]}]`; - } - if (elem.fBaseVersion === 4294967295) - _name += ':-1'; - else if (elem.fBaseVersion !== undefined) - _name += `:${elem.fBaseVersion}`; - _name += ';'; - if (elem.fTitle) - _name += ` // ${elem.fTitle}`; + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } - item._childs.push({ _name, _title, _kind: elem.fTypeName, _icon: (elem.fTypeName === kBaseClass) ? 'img_class' : 'img_member' }); - } - if (!item._childs.length) - delete item._childs; - } + inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); - return h; -} + /* do this just once */ + virgin = false; + } -/** @summary tag item in hierarchy painter as streamer info - * @desc this function used on THttpServer to mark streamer infos list - * as fictional TStreamerInfoList class, which has special draw function - * @private */ -function markAsStreamerInfo(h, item, obj) { - if (obj?._typename === clTList) - obj._typename = clTStreamerInfoList; -} + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +}; -/** @summary Create hierarchy for object inspector - * @private */ -function createInspectorContent(obj) { - const h = { _name: 'Object', _title: '', _click_action: 'expand', _nosimple: false, _do_context: true }; +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +const updatewindow = (strm, src, end, copy) => { - if (isStr(obj.fName) && obj.fName) - h._name = obj.fName; + let dist; + const state = strm.state; - if (isStr(obj.fTitle) && obj.fTitle) - h._title = obj.fTitle; + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; - if (obj._typename) - h._title += ` type:${obj._typename}`; + state.window = new Uint8Array(state.wsize); + } - if (isRootCollection(obj)) { - h._name = obj.name || obj._typename; - listHierarchy(h, obj); - } else - objectHierarchy(h, obj); + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + state.window.set(src.subarray(end - state.wsize, end), 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + state.window.set(src.subarray(end - copy, end), 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +}; - return h; -} +const inflate$2 = (strm, flush) => { + + let state; + let input, output; // input/output buffers + let next; /* next input INDEX */ + let put; /* next output INDEX */ + let have, left; /* available input and output */ + let hold; /* bit buffer */ + let bits; /* bits in bit buffer */ + let _in, _out; /* save starting available input and output */ + let copy; /* number of stored or match bytes to copy */ + let from; /* where to copy match bytes from */ + let from_source; + let here = 0; /* current decoding table entry */ + let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //let last; /* parent table entry */ + let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + let len; /* length to copy for repeats, bits to drop */ + let ret; /* return code */ + const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ + let opts; + + let n; // temporary variable for NEED_BITS + + const order = /* permutation of code lengths */ + new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); + + + if (inflateStateCheck(strm) || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR$1; + } -/** @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) { - const res = []; + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK$1; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + if (state.wbits === 0) { + state.wbits = 15; + } + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + if (len > 15 || len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } - if (!isStr(val)) return res; + // !!! pako patch. Force use `options.windowBits` if passed. + // Required to always use max window size by default. + state.dmax = 1 << state.wbits; + //state.dmax = 1 << len; + + state.flags = 0; /* indicate zlib header */ + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32_1(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more convenient processing later + state.head.extra = new Uint8Array(state.head.extra_len); + } + state.head.extra.set( + input.subarray( + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + next + copy + ), + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); - val = val.trim(); - if (!val) return res; + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT$1; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + output.set(input.subarray(next, next + copy), put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } - // return as array with single element - if ((val.length < 2) || (val[0] !== '[') || (val[val.length-1] !== ']')) { - res.push(val); - return res; - } + /* handle error breaks in while */ + if (state.mode === BAD) { break; } - // try to split ourself, checking quotes and brackets - let nbr = 0, nquotes = 0, ndouble = 0, last = 1; + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } - for (let 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; } - // eslint-disable-next-line no-fallthrough - case ',': - if (nbr === 0) { - let sub = val.substring(last, indx).trim(); - if ((sub.length > 1) && (sub[0] === sub[sub.length-1]) && ((sub[0] === '"') || (sub[0] === '\''))) - sub = sub.slice(1, sub.length-1); - res.push(sub); - last = indx+1; + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inffast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; } +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' instead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = + /*UPDATE_CHECK(state.check, put - _out, _out);*/ + (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; break; - } - } + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END$1; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR$1; + break inf_leave; + case MEM: + return Z_MEM_ERROR$1; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR$1; + } + } - if (res.length === 0) - res.push(val.slice(1, val.length-1).trim()); + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - return res; -} + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH$1))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { + ret = Z_BUF_ERROR; + } + return ret; +}; -/** @summary central function for expand of all online items - * @private */ -function onlineHierarchy(node, obj) { - if (obj && node && ('_childs' in obj)) { - for (let n = 0; n < obj._childs.length; ++n) { - if (obj._childs[n]._more || obj._childs[n]._childs) - obj._childs[n]._expand = onlineHierarchy; - } - node._childs = obj._childs; - obj._childs = null; - return true; - } +const inflateEnd = (strm) => { + + if (inflateStateCheck(strm)) { + return Z_STREAM_ERROR$1; + } + + let state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK$1; +}; + + +const inflateGetHeader = (strm, head) => { + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK$1; +}; + + +const inflateSetDictionary = (strm, dictionary) => { + const dictLength = dictionary.length; + + let state; + let dictid; + let ret; + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR$1; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32_1(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR$1; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR$1; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK$1; +}; + - return false; -} +var inflateReset_1 = inflateReset; +var inflateReset2_1 = inflateReset2; +var inflateResetKeep_1 = inflateResetKeep; +var inflateInit_1 = inflateInit; +var inflateInit2_1 = inflateInit2; +var inflate_2$1 = inflate$2; +var inflateEnd_1 = inflateEnd; +var inflateGetHeader_1 = inflateGetHeader; +var inflateSetDictionary_1 = inflateSetDictionary; +var inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +module.exports.inflateCodesUsed = inflateCodesUsed; +module.exports.inflateCopy = inflateCopy; +module.exports.inflateGetDictionary = inflateGetDictionary; +module.exports.inflateMark = inflateMark; +module.exports.inflatePrime = inflatePrime; +module.exports.inflateSync = inflateSync; +module.exports.inflateSyncPoint = inflateSyncPoint; +module.exports.inflateUndermine = inflateUndermine; +module.exports.inflateValidate = inflateValidate; +*/ -/** @summary Check if draw handle for specified object can do expand - * @private */ -function canExpandHandle(handle) { - return handle?.expand || handle?.get_expand || handle?.expand_item; -} +var inflate_1$2 = { + inflateReset: inflateReset_1, + inflateReset2: inflateReset2_1, + inflateResetKeep: inflateResetKeep_1, + inflateInit: inflateInit_1, + inflateInit2: inflateInit2_1, + inflate: inflate_2$1, + inflateEnd: inflateEnd_1, + inflateGetHeader: inflateGetHeader_1, + inflateSetDictionary: inflateSetDictionary_1, + inflateInfo: inflateInfo +}; -const kindTFile = prROOT + clTFile; +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} + +var gzheader = GZheader; + +const toString = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, Z_FINISH, + Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR +} = constants$2; + +/* ===========================================================================*/ -/** - * @summary Painter of hierarchical structures - * - * @example - * import { HierarchyPainter } from 'https://root.cern/js/latest/modules/gui/HierarchyPainter.mjs'; - * // create hierarchy painter in 'myTreeDiv' - * let h = new HierarchyPainter('example', 'myTreeDiv'); - * // configure 'simple' layout in 'myMainDiv' - * // one also can specify 'grid2x2' or 'flex' or 'tabs' - * h.setDisplay('simple', 'myMainDiv'); - * // open file and display element - * h.openRootFile('https://root.cern/js/files/hsimple.root').then(() => h.display('hpxpy;1','colz')); */ -class HierarchyPainter extends BasePainter { +/** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + **/ - /** @summary Create painter - * @param {string} name - symbolic name - * @param {string} frameid - element id where hierarchy is drawn - * @param {string} [backgr] - background color */ - constructor(name, frameid, backgr) { - super(frameid); - this.name = name; - this.h = null; // hierarchy - this.with_icons = true; +/* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overridden. + **/ - if (backgr === '__as_dark_mode__') - this.setBasicColors(); - else - this.background = backgr; - this.files_monitoring = !frameid; // by default files monitored when nobrowser option specified - this.nobrowser = (frameid === null); +/** + * Inflate.result -> Uint8Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + **/ - // remember only very first instance - if (!getHPainter()) - setHPainter(this); - } +/** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + **/ - /** @summary Set basic colors - * @private */ - setBasicColors() { - this.background = settings.DarkMode ? 'black' : 'white'; - this.textcolor = settings.DarkMode ? '#eee' : '#111'; - } +/** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + **/ - /** @summary Cleanup hierarchy painter - * @desc clear drawing and browser */ - cleanup() { - this.clearHierarchy(true); - super.cleanup(); +/** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + **/ +function Inflate$1(options) { + this.options = common.assign({ + chunkSize: 1024 * 64, + windowBits: 15, + to: '' + }, options || {}); + + const opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) { opt.windowBits = -15; } + } - if (getHPainter() === this) - setHPainter(null); - } + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ((opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits)) { + opt.windowBits += 32; + } - /** @summary Create file hierarchy - * @private */ - fileHierarchy(file, folder) { - const painter = this; - if (!folder) folder = {}; + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } - folder._name = file.fFileName; - folder._title = (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}, modified: ${convertDate(getTDatime(file.fDatimeM))}`; - folder._kind = kindTFile; - folder._file = file; - folder._fullurl = file.fFullURL; - folder._localfile = file.fLocalFile; - folder._had_direct_read = false; - // this is central get method, item or itemname can be used, returns promise - folder._get = function(item, itemname) { - if (item?._readobj) - return Promise.resolve(item._readobj); - - if (item) itemname = painter.itemFullName(item, this); - - const readFileObject = file => { - if (!this._file) this._file = file; - - if (!file) return Promise.resolve(null); - - return file.readObject(itemname).then(obj => { - // if object was read even when item did not exist try to reconstruct new hierarchy - if (!item && obj) { - // first try to found last read directory - const d = painter.findItem({ name: itemname, top: this, last_exists: true, check_keys: true }); - if ((d?.last !== undefined) && (d.last !== this)) { - // reconstruct only subdir hierarchy - const dir = file.getDir(painter.itemFullName(d.last, this)); - if (dir) { - d.last._name = d.last._keyname; - const dirname = painter.itemFullName(d.last, this); - keysHierarchy(d.last, dir.fKeys, file, dirname + '/'); - } - } else { - // reconstruct full file hierarchy - keysHierarchy(this, file.fKeys, file, ''); - } - item = painter.findItem({ name: itemname, top: this }); - } + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data - if (item) { - item._readobj = obj; - // remove cycle number for objects supporting expand - if ('_expand' in item) item._name = item._keyname; - } + this.strm = new zstream(); + this.strm.avail_out = 0; - return obj; - }); - }; + let status = inflate_1$2.inflateInit2( + this.strm, + opt.windowBits + ); - if (this._file) return readFileObject(this._file); - if (this._localfile) return openFile(this._localfile).then(f => readFileObject(f)); - if (this._fullurl) return openFile(this._fullurl).then(f => readFileObject(f)); - return Promise.resolve(null); - }; + if (status !== Z_OK) { + throw new Error(messages[status]); + } - keysHierarchy(folder, file.fKeys, file, ''); + this.header = new gzheader(); - return folder; - } + inflate_1$2.inflateGetHeader(this.strm, this.header); - /** @summary Iterate over all items in hierarchy - * @param {function} func - function called for every item - * @param {object} [top] - top item to start from - * @private */ - forEachItem(func, top) { - function each_item(item, prnt) { - if (!item) return; - if (prnt) item._parent = prnt; - func(item); - if ('_childs' in item) { - for (let n = 0; n < item._childs.length; ++n) - each_item(item._childs[n], item); - } + // Setup dictionary + if (opt.dictionary) { + // Convert data if needed + if (typeof opt.dictionary === 'string') { + opt.dictionary = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { + opt.dictionary = new Uint8Array(opt.dictionary); + } + if (opt.raw) { //In raw mode we need to set the dictionary early + status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); + if (status !== Z_OK) { + throw new Error(messages[status]); } + } + } +} - if (isFunc(func)) - each_item(top || this.h); - } +/** + * Inflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer): input data + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE + * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, + * `true` means Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. If end of stream detected, + * [[Inflate#onEnd]] will be called. + * + * `flush_mode` is not needed for normal operation, because end of stream + * detected automatically. You may try to use it for advanced things, but + * this functionality was not tested. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Inflate$1.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + const dictionary = this.options.dictionary; + let status, _flush_mode, last_avail_out; - /** @summary Search item in the hierarchy - * @param {object|string} arg - item name or object with arguments - * @param {string} arg.name - item to search - * @param {boolean} [arg.force] - specified elements will be created when not exists - * @param {boolean} [arg.last_exists] - when specified last parent element will be returned - * @param {boolean} [arg.check_keys] - check TFile keys with cycle suffix - * @param {boolean} [arg.allow_index] - let use sub-item indexes instead of name - * @param {object} [arg.top] - element to start search from - * @private */ - findItem(arg) { - function find_in_hierarchy(top, fullname) { - if (!fullname || !top) return top; + if (this.ended) return false; - let pos = fullname.length; + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; - if (!top._parent && (top._kind !== kTopFolder) && (fullname.indexOf(top._name) === 0)) { - // it is allowed to provide item name, which includes top-parent like file.root/folder/item - // but one could skip top-item name, if there are no other items - if (fullname === top._name) return top; + // Convert data if needed + if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } - const len = top._name.length; - if (fullname[len] === '/') { - fullname = fullname.slice(len+1); - pos = fullname.length; - } - } + strm.next_in = 0; + strm.avail_in = strm.input.length; - function process_child(child, ignore_prnt) { - // set parent pointer when searching child - if (!ignore_prnt) child._parent = top; + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } - if ((pos >= fullname.length-1) || (pos < 0)) return child; + status = inflate_1$2.inflate(strm, _flush_mode); - return find_in_hierarchy(child, fullname.slice(pos + 1)); - } + if (status === Z_NEED_DICT && dictionary) { + status = inflate_1$2.inflateSetDictionary(strm, dictionary); - while (pos > 0) { - // we try to find element with slashes inside - start from full name - let localname = (pos >= fullname.length) ? fullname : fullname.slice(0, pos); + if (status === Z_OK) { + status = inflate_1$2.inflate(strm, _flush_mode); + } else if (status === Z_DATA_ERROR) { + // Replace code with more verbose + status = Z_NEED_DICT; + } + } - if (top._childs) { - // first try to find direct matched item - for (let i = 0; i < top._childs.length; ++i) { - if (top._childs[i]._name === localname) - return process_child(top._childs[i]); - } + // Skip snyc markers if more data follows and not raw mode + while (strm.avail_in > 0 && + status === Z_STREAM_END && + strm.state.wrap > 0 && + data[strm.next_in] !== 0) + { + inflate_1$2.inflateReset(strm); + status = inflate_1$2.inflate(strm, _flush_mode); + } - // if first child online, check its elements - if ((top._kind === kTopFolder) && (top._childs[0]._online !== undefined)) { - for (let i = 0; i < top._childs[0]._childs.length; ++i) { - if (top._childs[0]._childs[i]._name === localname) - return process_child(top._childs[0]._childs[i], true); - } - } + switch (status) { + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + this.onEnd(status); + this.ended = true; + return false; + } - // if allowed, try to found item with key - if (arg.check_keys) { - let newest = null; - for (let i = 0; i < top._childs.length; ++i) { - if (top._childs[i]._keyname === localname) { - if (!newest || (newest._cycle < top._childs[i]._cycle)) - newest = top._childs[i]; - } - } - if (newest) return process_child(newest); - } + // Remember real `avail_out` value, because we may patch out buffer content + // to align utf8 strings boundaries. + last_avail_out = strm.avail_out; - let allow_index = arg.allow_index; - if ((localname[0] === '[') && (localname[localname.length-1] === ']') && - /^\d+$/.test(localname.slice(1, localname.length-1))) { - allow_index = true; - localname = localname.slice(1, localname.length-1); - } + if (strm.next_out) { + if (strm.avail_out === 0 || status === Z_STREAM_END) { - // when search for the elements it could be allowed to check index - if (allow_index && /^\d+$/.test(localname)) { - const indx = parseInt(localname); - if (Number.isInteger(indx) && (indx >= 0) && (indx < top._childs.length)) - return process_child(top._childs[indx]); - } - } + if (this.options.to === 'string') { - pos = fullname.lastIndexOf('/', pos - 1); - } + let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); - if (arg.force) { - // if did not found element with given name we just generate it - if (top._childs === undefined) top._childs = []; - pos = fullname.indexOf('/'); - const child = { _name: ((pos < 0) ? fullname : fullname.slice(0, pos)) }; - top._childs.push(child); - return process_child(child); - } + let tail = strm.next_out - next_out_utf8; + let utf8str = strings.buf2string(strm.output, next_out_utf8); - return arg.last_exists ? { last: top, rest: fullname } : null; - } + // move tail & realign counters + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); - let top = this.h, itemname = ''; + this.onData(utf8str); - if (isStr(arg)) { - itemname = arg; - arg = {}; - } else if (isObject(arg)) { - itemname = arg.name; - if ('top' in arg) - top = arg.top; - } else - return null; + } else { + this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); + } + } + } - if (itemname === '__top_folder__') - return top; + // Must repeat iteration if out buffer is full + if (status === Z_OK && last_avail_out === 0) continue; - if (isStr(itemname) && (itemname.indexOf('img:') === 0)) - return null; + // Finalize if end of stream reached. + if (status === Z_STREAM_END) { + status = inflate_1$2.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return true; + } - return find_in_hierarchy(top, itemname); - } + if (strm.avail_in === 0) break; + } - /** @summary Produce full string name for item - * @param {Object} node - item element - * @param {Object} [uptoparent] - up to which parent to continue - * @param {boolean} [compact] - if specified, top parent is not included - * @return {string} produced name - * @private */ - itemFullName(node, uptoparent, compact) { - if (node?._kind === kTopFolder) - return '__top_folder__'; + return true; +}; - let res = ''; - while (node) { - // online items never includes top-level folder - if ((node._online !== undefined) && !uptoparent) return res; +/** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|String): output data. When string output requested, + * each chunk will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Inflate$1.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; - if ((node === uptoparent) || (node._kind === kTopFolder)) break; - if (compact && !node._parent) break; // in compact form top-parent is not included - if (res) res = '/' + res; - res = node._name + res; - node = node._parent; - } - return res; - } +/** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Inflate$1.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK) { + if (this.options.to === 'string') { + this.result = this.chunks.join(''); + } else { + this.result = common.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; - /** @summary Executes item marked as 'Command' - * @desc If command requires additional arguments, they could be specified as extra arguments arg1, arg2, ... - * @param {String} itemname - name of command item - * @param {Object} [elem] - HTML element for command execution - * @param [arg1] - first optional argument - * @param [arg2] - second optional argument and so on - * @return {Promise} with command result */ - async executeCommand(itemname, elem) { - const hitem = this.findItem(itemname), - url = this.getOnlineItemUrl(hitem) + '/cmd.json', - d3node = select(elem), - cmdargs = []; - if ('_numargs' in hitem) { - for (let n = 0; n < hitem._numargs; ++n) - cmdargs.push((n+2 < arguments.length) ? arguments[n+2] : ''); - } +/** + * inflate(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * + * ##### Example: + * + * ```javascript + * const pako = require('pako'); + * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); + * let output; + * + * try { + * output = pako.inflate(input); + * } catch (err) { + * console.log(err); + * } + * ``` + **/ +function inflate$1(input, options) { + const inflator = new Inflate$1(options); - const promise = (cmdargs.length === 0) || !elem - ? Promise.resolve(cmdargs) - : createMenu().then(menu => menu.showCommandArgsDialog(hitem._name, cmdargs)); + inflator.push(input); - return promise.then(args => { - if (args === null) return false; + // That will never happens, if you don't cheat with options :) + if (inflator.err) throw inflator.msg || messages[inflator.err]; - let urlargs = ''; - for (let k = 0; k < args.length; ++k) - urlargs += `${k>0?'&':'?'}arg${k+1}=${args[k]}`; + return inflator.result; +} - if (!d3node.empty()) { - d3node.style('background', 'yellow'); - if (hitem._title) - d3node.attr('title', 'Executing ' + hitem._title); - } - return httpRequest(url + urlargs, 'text').then(res => { - if (d3node.empty()) return res; - const col = (res && (res !== 'false')) ? 'green' : 'red'; - d3node.style('background', col); - if (hitem._title) - d3node.attr('title', hitem._title + ' lastres=' + res); - setTimeout(() => { - d3node.style('background', null); - if (hitem._icon && d3node.classed('jsroot_fastcmd_btn')) - d3node.style('background-image', `url('${hitem._icon}')`); - }, 2000); - if ((col === 'green') && ('_hreload' in hitem)) - this.reload(); - if ((col === 'green') && ('_update_item' in hitem)) - this.updateItems(hitem._update_item.split(';')); - return res; - }); - }); - } +/** + * ungzip(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + **/ - /** @summary Get object item with specified name - * @desc depending from provided option, same item can generate different object types - * @param {Object} arg - item name or config object - * @param {string} arg.name - item name - * @param {Object} arg.item - or item itself - * @param {string} options - supposed draw options - * @return {Promise} with object like { item, obj, itemname } - * @private */ - async getObject(arg, options) { - const result = { item: null, obj: null }; - let itemname, item; - if (arg === null) - return result; +var Inflate_1$1 = Inflate$1; +var inflate_2 = inflate$1; - if (isStr(arg)) - itemname = arg; - else if (isObject(arg)) { - if ((arg._parent !== undefined) && (arg._name !== undefined) && (arg._kind !== undefined)) - item = arg; - else if (arg.name !== undefined) - itemname = arg.name; - else if (arg.arg !== undefined) - itemname = arg.arg; - else if (arg.item !== undefined) - item = arg.item; - } +var inflate_1$1 = { + Inflate: Inflate_1$1, + inflate: inflate_2}; - if (isStr(itemname) && (itemname.indexOf('img:') === 0)) { - // artificial class, can be created by users - result.obj = { _typename: 'TJSImage', fName: itemname.slice(4) }; - return result; - } +const { Inflate, inflate} = inflate_1$1; +var Inflate_1 = Inflate; +var inflate_1 = inflate; - if (item) itemname = this.itemFullName(item); - else item = this.findItem({ name: itemname, allow_index: true, check_keys: true }); +const crcTable = []; +for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + if (c & 1) { + c = 0xedb88320 ^ (c >>> 1); + } + else { + c = c >>> 1; + } + } + crcTable[n] = c; +} +const initialCrc = 0xffffffff; +function updateCrc(currentCrc, data, length) { + let c = currentCrc; + for (let n = 0; n < length; n++) { + c = crcTable[(c ^ data[n]) & 0xff] ^ (c >>> 8); + } + return c; +} +function crc(data, length) { + return (updateCrc(initialCrc, data, length) ^ initialCrc) >>> 0; +} +function checkCrc(buffer, crcLength, chunkName) { + const expectedCrc = buffer.readUint32(); + const actualCrc = crc(new Uint8Array(buffer.buffer, buffer.byteOffset + buffer.offset - crcLength - 4, crcLength), crcLength); // "- 4" because we already advanced by reading the CRC + if (actualCrc !== expectedCrc) { + throw new Error(`CRC mismatch for chunk ${chunkName}. Expected ${expectedCrc}, found ${actualCrc}`); + } +} - // if item not found, try to find nearest parent which could allow us to get inside +function unfilterNone(currentLine, newLine, bytesPerLine) { + for (let i = 0; i < bytesPerLine; i++) { + newLine[i] = currentLine[i]; + } +} +function unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel) { + let i = 0; + for (; i < bytesPerPixel; i++) { + // just copy first bytes + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; + } +} +function unfilterUp(currentLine, newLine, prevLine, bytesPerLine) { + let i = 0; + if (prevLine.length === 0) { + // just copy bytes for first line + for (; i < bytesPerLine; i++) { + newLine[i] = currentLine[i]; + } + } + else { + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; + } + } +} +function unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { + let i = 0; + if (prevLine.length === 0) { + for (; i < bytesPerPixel; i++) { + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + (newLine[i - bytesPerPixel] >> 1)) & 0xff; + } + } + else { + for (; i < bytesPerPixel; i++) { + newLine[i] = (currentLine[i] + (prevLine[i] >> 1)) & 0xff; + } + for (; i < bytesPerLine; i++) { + newLine[i] = + (currentLine[i] + ((newLine[i - bytesPerPixel] + prevLine[i]) >> 1)) & + 0xff; + } + } +} +function unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { + let i = 0; + if (prevLine.length === 0) { + for (; i < bytesPerPixel; i++) { + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; + } + } + else { + for (; i < bytesPerPixel; i++) { + newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; + } + for (; i < bytesPerLine; i++) { + newLine[i] = + (currentLine[i] + + paethPredictor$1(newLine[i - bytesPerPixel], prevLine[i], prevLine[i - bytesPerPixel])) & + 0xff; + } + } +} +function paethPredictor$1(a, b, c) { + const p = a + b - c; + const pa = Math.abs(p - a); + const pb = Math.abs(p - b); + const pc = Math.abs(p - c); + if (pa <= pb && pa <= pc) + return a; + else if (pb <= pc) + return b; + else + return c; +} - const d = item ? null : this.findItem({ name: itemname, last_exists: true, check_keys: true, allow_index: true }); +/** + * Apllies filter on scanline based on the filter type. + * @param filterType - The filter type to apply. + * @param currentLine - The current line of pixel data. + * @param newLine - The new line of pixel data. + * @param prevLine - The previous line of pixel data. + * @param passLineBytes - The number of bytes in the pass line. + * @param bytesPerPixel - The number of bytes per pixel. + */ +function applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel) { + switch (filterType) { + case 0: + unfilterNone(currentLine, newLine, passLineBytes); + break; + case 1: + unfilterSub(currentLine, newLine, passLineBytes, bytesPerPixel); + break; + case 2: + unfilterUp(currentLine, newLine, prevLine, passLineBytes); + break; + case 3: + unfilterAverage(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + break; + case 4: + unfilterPaeth(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + break; + default: + throw new Error(`Unsupported filter: ${filterType}`); + } +} - // if item not found, try to expand hierarchy central function - // implements not process get in central method of hierarchy item (if exists) - // if last_parent found, try to expand it - if ((d !== null) && ('last' in d) && (d.last !== null)) { - const parentname = this.itemFullName(d.last); +const uint16$1 = new Uint16Array([0x00ff]); +const uint8$1 = new Uint8Array(uint16$1.buffer); +const osIsLittleEndian$1 = uint8$1[0] === 0xff; +/** + * Decodes the Adam7 interlaced PNG data. + * + * @param params - DecodeInterlaceNullParams + * @returns - array of pixel data. + */ +function decodeInterlaceAdam7(params) { + const { data, width, height, channels, depth } = params; + // Adam7 interlacing pattern + const passes = [ + { x: 0, y: 0, xStep: 8, yStep: 8 }, // Pass 1 + { x: 4, y: 0, xStep: 8, yStep: 8 }, // Pass 2 + { x: 0, y: 4, xStep: 4, yStep: 8 }, // Pass 3 + { x: 2, y: 0, xStep: 4, yStep: 4 }, // Pass 4 + { x: 0, y: 2, xStep: 2, yStep: 4 }, // Pass 5 + { x: 1, y: 0, xStep: 2, yStep: 2 }, // Pass 6 + { x: 0, y: 1, xStep: 1, yStep: 2 }, // Pass 7 + ]; + const bytesPerPixel = Math.ceil(depth / 8) * channels; + const resultData = new Uint8Array(height * width * bytesPerPixel); + let offset = 0; + // Process each pass + for (let passIndex = 0; passIndex < 7; passIndex++) { + const pass = passes[passIndex]; + // Calculate pass dimensions + const passWidth = Math.ceil((width - pass.x) / pass.xStep); + const passHeight = Math.ceil((height - pass.y) / pass.yStep); + if (passWidth <= 0 || passHeight <= 0) + continue; + const passLineBytes = passWidth * bytesPerPixel; + const prevLine = new Uint8Array(passLineBytes); + // Process each scanline in this pass + for (let y = 0; y < passHeight; y++) { + // First byte is the filter type + const filterType = data[offset++]; + const currentLine = data.subarray(offset, offset + passLineBytes); + offset += passLineBytes; + // Create a new line for the unfiltered data + const newLine = new Uint8Array(passLineBytes); + // Apply the appropriate unfilter + applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + prevLine.set(newLine); + for (let x = 0; x < passWidth; x++) { + const outputX = pass.x + x * pass.xStep; + const outputY = pass.y + y * pass.yStep; + if (outputX >= width || outputY >= height) + continue; + for (let i = 0; i < bytesPerPixel; i++) { + resultData[(outputY * width + outputX) * bytesPerPixel + i] = + newLine[x * bytesPerPixel + i]; + } + } + } + } + if (depth === 16) { + const uint16Data = new Uint16Array(resultData.buffer); + if (osIsLittleEndian$1) { + for (let k = 0; k < uint16Data.length; k++) { + // PNG is always big endian. Swap the bytes. + uint16Data[k] = swap16$1(uint16Data[k]); + } + } + return uint16Data; + } + else { + return resultData; + } +} +function swap16$1(val) { + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); +} + +const uint16 = new Uint16Array([0x00ff]); +const uint8 = new Uint8Array(uint16.buffer); +const osIsLittleEndian = uint8[0] === 0xff; +const empty = new Uint8Array(0); +function decodeInterlaceNull(params) { + const { data, width, height, channels, depth } = params; + const bytesPerPixel = Math.ceil(depth / 8) * channels; + const bytesPerLine = Math.ceil((depth / 8) * channels * width); + const newData = new Uint8Array(height * bytesPerLine); + let prevLine = empty; + let offset = 0; + let currentLine; + let newLine; + for (let i = 0; i < height; i++) { + currentLine = data.subarray(offset + 1, offset + 1 + bytesPerLine); + newLine = newData.subarray(i * bytesPerLine, (i + 1) * bytesPerLine); + switch (data[offset]) { + case 0: + unfilterNone(currentLine, newLine, bytesPerLine); + break; + case 1: + unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel); + break; + case 2: + unfilterUp(currentLine, newLine, prevLine, bytesPerLine); + break; + case 3: + unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); + break; + case 4: + unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); + break; + default: + throw new Error(`Unsupported filter: ${data[offset]}`); + } + prevLine = newLine; + offset += bytesPerLine + 1; + } + if (depth === 16) { + const uint16Data = new Uint16Array(newData.buffer); + if (osIsLittleEndian) { + for (let k = 0; k < uint16Data.length; k++) { + // PNG is always big endian. Swap the bytes. + uint16Data[k] = swap16(uint16Data[k]); + } + } + return uint16Data; + } + else { + return newData; + } +} +function swap16(val) { + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); +} - // this is indication that expand does not give us better path to searched item - if (isObject(arg) && ('rest' in arg)) { - if ((arg.rest === d.rest) || (arg.rest.length <= d.rest.length)) - return result; - } +// https://www.w3.org/TR/PNG/#5PNG-file-signature +const pngSignature = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10); +function checkSignature(buffer) { + if (!hasPngSignature(buffer.readBytes(pngSignature.length))) { + throw new Error('wrong PNG signature'); + } +} +function hasPngSignature(array) { + if (array.length < pngSignature.length) { + return false; + } + for (let i = 0; i < pngSignature.length; i++) { + if (array[i] !== pngSignature[i]) { + return false; + } + } + return true; +} - return this.expandItem(parentname, undefined, options !== 'hierarchy_expand_verbose').then(res => { - if (!res) return result; - let newparentname = this.itemFullName(d.last); - if (newparentname) newparentname += '/'; - return this.getObject({ name: newparentname + d.rest, rest: d.rest }, options); - }); - } +// https://www.w3.org/TR/png/#11tEXt +const textChunkName = 'tEXt'; +const NULL = 0; +const latin1Decoder = new TextDecoder('latin1'); +function validateKeyword(keyword) { + validateLatin1(keyword); + if (keyword.length === 0 || keyword.length > 79) { + throw new Error('keyword length must be between 1 and 79'); + } +} +// eslint-disable-next-line no-control-regex +const latin1Regex = /^[\u0000-\u00FF]*$/; +function validateLatin1(text) { + if (!latin1Regex.test(text)) { + throw new Error('invalid latin1 text'); + } +} +function decodetEXt(text, buffer, length) { + const keyword = readKeyword(buffer); + text[keyword] = readLatin1(buffer, length - keyword.length - 1); +} +// https://www.w3.org/TR/png/#11keywords +function readKeyword(buffer) { + buffer.mark(); + while (buffer.readByte() !== NULL) { + /* advance */ + } + const end = buffer.offset; + buffer.reset(); + const keyword = latin1Decoder.decode(buffer.readBytes(end - buffer.offset - 1)); + // NULL + buffer.skip(1); + validateKeyword(keyword); + return keyword; +} +function readLatin1(buffer, length) { + return latin1Decoder.decode(buffer.readBytes(length)); +} + +const ColorType = { + UNKNOWN: -1, + GREYSCALE: 0, + TRUECOLOUR: 2, + INDEXED_COLOUR: 3, + GREYSCALE_ALPHA: 4, + TRUECOLOUR_ALPHA: 6, +}; +const CompressionMethod = { + UNKNOWN: -1, + DEFLATE: 0, +}; +const FilterMethod = { + UNKNOWN: -1, + ADAPTIVE: 0, +}; +const InterlaceMethod = { + UNKNOWN: -1, + NO_INTERLACE: 0, + ADAM7: 1, +}; +const DisposeOpType = { + NONE: 0, + BACKGROUND: 1, + PREVIOUS: 2, +}; +const BlendOpType = { + SOURCE: 0, + OVER: 1, +}; - result.item = item; +class PngDecoder extends IOBuffer { + _checkCrc; + _inflator; + _png; + _apng; + _end; + _hasPalette; + _palette; + _hasTransparency; + _transparency; + _compressionMethod; + _filterMethod; + _interlaceMethod; + _colorType; + _isAnimated; + _numberOfFrames; + _numberOfPlays; + _frames; + _writingDataChunks; + constructor(data, options = {}) { + super(data); + const { checkCrc = false } = options; + this._checkCrc = checkCrc; + this._inflator = new Inflate_1(); + this._png = { + width: -1, + height: -1, + channels: -1, + data: new Uint8Array(0), + depth: 1, + text: {}, + }; + this._apng = { + width: -1, + height: -1, + channels: -1, + depth: 1, + numberOfFrames: 1, + numberOfPlays: 0, + text: {}, + frames: [], + }; + this._end = false; + this._hasPalette = false; + this._palette = []; + this._hasTransparency = false; + this._transparency = new Uint16Array(0); + this._compressionMethod = CompressionMethod.UNKNOWN; + this._filterMethod = FilterMethod.UNKNOWN; + this._interlaceMethod = InterlaceMethod.UNKNOWN; + this._colorType = ColorType.UNKNOWN; + this._isAnimated = false; + this._numberOfFrames = 1; + this._numberOfPlays = 0; + this._frames = []; + this._writingDataChunks = false; + // PNG is always big endian + // https://www.w3.org/TR/PNG/#7Integers-and-byte-order + this.setBigEndian(); + } + decode() { + checkSignature(this); + while (!this._end) { + const length = this.readUint32(); + const type = this.readChars(4); + this.decodeChunk(length, type); + } + this.decodeImage(); + return this._png; + } + decodeApng() { + checkSignature(this); + while (!this._end) { + const length = this.readUint32(); + const type = this.readChars(4); + this.decodeApngChunk(length, type); + } + this.decodeApngImage(); + return this._apng; + } + // https://www.w3.org/TR/PNG/#5Chunk-layout + decodeChunk(length, type) { + const offset = this.offset; + switch (type) { + // 11.2 Critical chunks + case 'IHDR': // 11.2.2 IHDR Image header + this.decodeIHDR(); + break; + case 'PLTE': // 11.2.3 PLTE Palette + this.decodePLTE(length); + break; + case 'IDAT': // 11.2.4 IDAT Image data + this.decodeIDAT(length); + break; + case 'IEND': // 11.2.5 IEND Image trailer + this._end = true; + break; + // 11.3 Ancillary chunks + case 'tRNS': // 11.3.2.1 tRNS Transparency + this.decodetRNS(length); + break; + case 'iCCP': // 11.3.3.3 iCCP Embedded ICC profile + this.decodeiCCP(length); + break; + case textChunkName: // 11.3.4.3 tEXt Textual data + decodetEXt(this._png.text, this, length); + break; + case 'pHYs': // 11.3.5.3 pHYs Physical pixel dimensions + this.decodepHYs(); + break; + default: + this.skip(length); + break; + } + if (this.offset - offset !== length) { + throw new Error(`Length mismatch while decoding chunk ${type}`); + } + if (this._checkCrc) { + checkCrc(this, length + 4, type); + } + else { + this.skip(4); + } + } + decodeApngChunk(length, type) { + const offset = this.offset; + if (type !== 'fdAT' && type !== 'IDAT' && this._writingDataChunks) { + this.pushDataToFrame(); + } + switch (type) { + case 'acTL': + this.decodeACTL(); + break; + case 'fcTL': + this.decodeFCTL(); + break; + case 'fdAT': + this.decodeFDAT(length); + break; + default: + this.decodeChunk(length, type); + this.offset = offset + length; + break; + } + if (this.offset - offset !== length) { + throw new Error(`Length mismatch while decoding chunk ${type}`); + } + if (this._checkCrc) { + checkCrc(this, length + 4, type); + } + else { + this.skip(4); + } + } + // https://www.w3.org/TR/PNG/#11IHDR + decodeIHDR() { + const image = this._png; + image.width = this.readUint32(); + image.height = this.readUint32(); + image.depth = checkBitDepth(this.readUint8()); + const colorType = this.readUint8(); + this._colorType = colorType; + let channels; + switch (colorType) { + case ColorType.GREYSCALE: + channels = 1; + break; + case ColorType.TRUECOLOUR: + channels = 3; + break; + case ColorType.INDEXED_COLOUR: + channels = 1; + break; + case ColorType.GREYSCALE_ALPHA: + channels = 2; + break; + case ColorType.TRUECOLOUR_ALPHA: + channels = 4; + break; + // Kept for exhaustiveness. + // eslint-disable-next-line unicorn/no-useless-switch-case + case ColorType.UNKNOWN: + default: + throw new Error(`Unknown color type: ${colorType}`); + } + this._png.channels = channels; + this._compressionMethod = this.readUint8(); + if (this._compressionMethod !== CompressionMethod.DEFLATE) { + throw new Error(`Unsupported compression method: ${this._compressionMethod}`); + } + this._filterMethod = this.readUint8(); + this._interlaceMethod = this.readUint8(); + } + decodeACTL() { + this._numberOfFrames = this.readUint32(); + this._numberOfPlays = this.readUint32(); + this._isAnimated = true; + } + decodeFCTL() { + const image = { + sequenceNumber: this.readUint32(), + width: this.readUint32(), + height: this.readUint32(), + xOffset: this.readUint32(), + yOffset: this.readUint32(), + delayNumber: this.readUint16(), + delayDenominator: this.readUint16(), + disposeOp: this.readUint8(), + blendOp: this.readUint8(), + data: new Uint8Array(0), + }; + this._frames.push(image); + } + // https://www.w3.org/TR/PNG/#11PLTE + decodePLTE(length) { + if (length % 3 !== 0) { + throw new RangeError(`PLTE field length must be a multiple of 3. Got ${length}`); + } + const l = length / 3; + this._hasPalette = true; + const palette = []; + this._palette = palette; + for (let i = 0; i < l; i++) { + palette.push([this.readUint8(), this.readUint8(), this.readUint8()]); + } + } + // https://www.w3.org/TR/PNG/#11IDAT + decodeIDAT(length) { + this._writingDataChunks = true; + const dataLength = length; + const dataOffset = this.offset + this.byteOffset; + this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + this.skip(length); + } + decodeFDAT(length) { + this._writingDataChunks = true; + let dataLength = length; + let dataOffset = this.offset + this.byteOffset; + dataOffset += 4; + dataLength -= 4; + this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + this.skip(length); + } + // https://www.w3.org/TR/PNG/#11tRNS + decodetRNS(length) { + switch (this._colorType) { + case ColorType.GREYSCALE: + case ColorType.TRUECOLOUR: { + if (length % 2 !== 0) { + throw new RangeError(`tRNS chunk length must be a multiple of 2. Got ${length}`); + } + if (length / 2 > this._png.width * this._png.height) { + throw new Error(`tRNS chunk contains more alpha values than there are pixels (${length / 2} vs ${this._png.width * this._png.height})`); + } + this._hasTransparency = true; + this._transparency = new Uint16Array(length / 2); + for (let i = 0; i < length / 2; i++) { + this._transparency[i] = this.readUint16(); + } + break; + } + case ColorType.INDEXED_COLOUR: { + if (length > this._palette.length) { + throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${length} vs ${this._palette.length})`); + } + let i = 0; + for (; i < length; i++) { + const alpha = this.readByte(); + this._palette[i].push(alpha); + } + for (; i < this._palette.length; i++) { + this._palette[i].push(255); + } + break; + } + // Kept for exhaustiveness. + /* eslint-disable unicorn/no-useless-switch-case */ + case ColorType.UNKNOWN: + case ColorType.GREYSCALE_ALPHA: + case ColorType.TRUECOLOUR_ALPHA: + default: { + throw new Error(`tRNS chunk is not supported for color type ${this._colorType}`); + } + /* eslint-enable unicorn/no-useless-switch-case */ + } + } + // https://www.w3.org/TR/PNG/#11iCCP + decodeiCCP(length) { + const name = readKeyword(this); + const compressionMethod = this.readUint8(); + if (compressionMethod !== CompressionMethod.DEFLATE) { + throw new Error(`Unsupported iCCP compression method: ${compressionMethod}`); + } + const compressedProfile = this.readBytes(length - name.length - 2); + this._png.iccEmbeddedProfile = { + name, + profile: inflate_1(compressedProfile), + }; + } + // https://www.w3.org/TR/PNG/#11pHYs + decodepHYs() { + const ppuX = this.readUint32(); + const ppuY = this.readUint32(); + const unitSpecifier = this.readByte(); + this._png.resolution = { x: ppuX, y: ppuY, unit: unitSpecifier }; + } + decodeApngImage() { + this._apng.width = this._png.width; + this._apng.height = this._png.height; + this._apng.channels = this._png.channels; + this._apng.depth = this._png.depth; + this._apng.numberOfFrames = this._numberOfFrames; + this._apng.numberOfPlays = this._numberOfPlays; + this._apng.text = this._png.text; + this._apng.resolution = this._png.resolution; + for (let i = 0; i < this._numberOfFrames; i++) { + const newFrame = { + sequenceNumber: this._frames[i].sequenceNumber, + delayNumber: this._frames[i].delayNumber, + delayDenominator: this._frames[i].delayDenominator, + data: this._apng.depth === 8 + ? new Uint8Array(this._apng.width * this._apng.height * this._apng.channels) + : new Uint16Array(this._apng.width * this._apng.height * this._apng.channels), + }; + const frame = this._frames.at(i); + if (frame) { + frame.data = decodeInterlaceNull({ + data: frame.data, + width: frame.width, + height: frame.height, + channels: this._apng.channels, + depth: this._apng.depth, + }); + if (this._hasPalette) { + this._apng.palette = this._palette; + } + if (this._hasTransparency) { + this._apng.transparency = this._transparency; + } + if (i === 0 || + (frame.xOffset === 0 && + frame.yOffset === 0 && + frame.width === this._png.width && + frame.height === this._png.height)) { + newFrame.data = frame.data; + } + else { + const prevFrame = this._apng.frames.at(i - 1); + this.disposeFrame(frame, prevFrame, newFrame); + this.addFrameDataToCanvas(newFrame, frame); + } + this._apng.frames.push(newFrame); + } + } + return this._apng; + } + disposeFrame(frame, prevFrame, imageFrame) { + switch (frame.disposeOp) { + case DisposeOpType.NONE: + break; + case DisposeOpType.BACKGROUND: + for (let row = 0; row < this._png.height; row++) { + for (let col = 0; col < this._png.width; col++) { + const index = (row * frame.width + col) * this._png.channels; + for (let channel = 0; channel < this._png.channels; channel++) { + imageFrame.data[index + channel] = 0; + } + } + } + break; + case DisposeOpType.PREVIOUS: + imageFrame.data.set(prevFrame.data); + break; + default: + throw new Error('Unknown disposeOp'); + } + } + addFrameDataToCanvas(imageFrame, frame) { + const maxValue = 1 << this._png.depth; + const calculatePixelIndices = (row, col) => { + const index = ((row + frame.yOffset) * this._png.width + frame.xOffset + col) * + this._png.channels; + const frameIndex = (row * frame.width + col) * this._png.channels; + return { index, frameIndex }; + }; + switch (frame.blendOp) { + case BlendOpType.SOURCE: + for (let row = 0; row < frame.height; row++) { + for (let col = 0; col < frame.width; col++) { + const { index, frameIndex } = calculatePixelIndices(row, col); + for (let channel = 0; channel < this._png.channels; channel++) { + imageFrame.data[index + channel] = + frame.data[frameIndex + channel]; + } + } + } + break; + // https://www.w3.org/TR/png-3/#13Alpha-channel-processing + case BlendOpType.OVER: + for (let row = 0; row < frame.height; row++) { + for (let col = 0; col < frame.width; col++) { + const { index, frameIndex } = calculatePixelIndices(row, col); + for (let channel = 0; channel < this._png.channels; channel++) { + const sourceAlpha = frame.data[frameIndex + this._png.channels - 1] / maxValue; + const foregroundValue = channel % (this._png.channels - 1) === 0 + ? 1 + : frame.data[frameIndex + channel]; + const value = Math.floor(sourceAlpha * foregroundValue + + (1 - sourceAlpha) * imageFrame.data[index + channel]); + imageFrame.data[index + channel] += value; + } + } + } + break; + default: + throw new Error('Unknown blendOp'); + } + } + decodeImage() { + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + const data = this._isAnimated + ? (this._frames?.at(0)).data + : this._inflator.result; + if (this._filterMethod !== FilterMethod.ADAPTIVE) { + throw new Error(`Filter method ${this._filterMethod} not supported`); + } + if (this._interlaceMethod === InterlaceMethod.NO_INTERLACE) { + this._png.data = decodeInterlaceNull({ + data: data, + width: this._png.width, + height: this._png.height, + channels: this._png.channels, + depth: this._png.depth, + }); + } + else if (this._interlaceMethod === InterlaceMethod.ADAM7) { + this._png.data = decodeInterlaceAdam7({ + data: data, + width: this._png.width, + height: this._png.height, + channels: this._png.channels, + depth: this._png.depth, + }); + } + else { + throw new Error(`Interlace method ${this._interlaceMethod} not supported`); + } + if (this._hasPalette) { + this._png.palette = this._palette; + } + if (this._hasTransparency) { + this._png.transparency = this._transparency; + } + } + pushDataToFrame() { + const result = this._inflator.result; + const lastFrame = this._frames.at(-1); + if (lastFrame) { + lastFrame.data = result; + } + else { + this._frames.push({ + sequenceNumber: 0, + width: this._png.width, + height: this._png.height, + xOffset: 0, + yOffset: 0, + delayNumber: 0, + delayDenominator: 0, + disposeOp: DisposeOpType.NONE, + blendOp: BlendOpType.SOURCE, + data: result, + }); + } + this._inflator = new Inflate_1(); + this._writingDataChunks = false; + } +} +function checkBitDepth(value) { + if (value !== 1 && + value !== 2 && + value !== 4 && + value !== 8 && + value !== 16) { + throw new Error(`invalid bit depth: ${value}`); + } + return value; +} - if ((item !== null) && isObject(item._obj)) { - result.obj = item._obj; - return result; - } +var ResolutionUnitSpecifier; +(function (ResolutionUnitSpecifier) { + /** + * Unit is unknown + */ + ResolutionUnitSpecifier[ResolutionUnitSpecifier["UNKNOWN"] = 0] = "UNKNOWN"; + /** + * Unit is the metre + */ + ResolutionUnitSpecifier[ResolutionUnitSpecifier["METRE"] = 1] = "METRE"; +})(ResolutionUnitSpecifier || (ResolutionUnitSpecifier = {})); - // normally search _get method in the parent items - let curr = item; - while (curr) { - if (isFunc(curr._get)) - return curr._get(item, null, options).then(obj => { result.obj = obj; return result; }); - curr = ('_parent' in curr) ? curr._parent : null; - } +function decodePng(data, options) { + const decoder = new PngDecoder(data, options); + return decoder.decode(); +} - return result; - } +/** + * @license + * + * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - /** @summary returns true if item is last in parent childs list - * @private */ - isLastSibling(hitem) { - if (!hitem || !hitem._parent || !hitem._parent._childs) return false; - const chlds = hitem._parent._childs; - let indx = chlds.indexOf(hitem); - if (indx < 0) return false; - while (++indx < chlds.length) - if (!('_hidden' in chlds[indx])) return false; - return true; - } +/* + * @see http://www.w3.org/TR/PNG-Chunks.html + * + Color Allowed Interpretation + Type Bit Depths - /** @summary Create item html code - * @private */ - addItemHtml(hitem, d3prnt, arg) { - if (!hitem || ('_hidden' in hitem)) return true; + 0 1,2,4,8,16 Each pixel is a grayscale sample. - const isroot = (hitem === this.h), - has_childs = ('_childs' in hitem), - handle = getDrawHandle(hitem._kind), - itemname = this.itemFullName(hitem); - let img1 = '', img2 = '', can_click = false, break_list = false, d3cont; + 2 8,16 Each pixel is an R,G,B triple. - if (handle) { - if ('icon' in handle) img1 = handle.icon; - if ('icon2' in handle) img2 = handle.icon2; - if (!img1 && isFunc(handle.icon_get)) - img1 = handle.icon_get(hitem, this); - if (canDrawHandle(handle) || ('execute' in handle) || ('aslink' in handle) || - (canExpandHandle(handle) && (hitem._more !== false))) can_click = true; - } + 3 1,2,4,8 Each pixel is a palette index; + a PLTE chunk must appear. - if ('_icon' in hitem) img1 = hitem._icon; - if ('_icon2' in hitem) img2 = hitem._icon2; - if (!img1 && ('_online' in hitem)) - hitem._icon = img1 = 'img_globe'; - if (!img1 && isroot) - hitem._icon = img1 = 'img_base'; + 4 8,16 Each pixel is a grayscale sample, + followed by an alpha sample. - if (hitem._more || hitem._expand || hitem._player || hitem._can_draw) - can_click = true; + 6 8,16 Each pixel is an R,G,B triple, + followed by an alpha sample. +*/ - let can_menu = can_click; - if (!can_menu && isStr(hitem._kind) && (hitem._kind.indexOf(prROOT) === 0)) - can_menu = can_click = true; +/* + * @name processPNG + * Entry point: process a PNG and return image dict and metadata for jsPDF + */ +jsPDF.API.processPNG = function(imageData, index, alias, compression) { + if (this.__addimage__.isArrayBuffer(imageData)) { + imageData = new Uint8Array(imageData); + } + if (!this.__addimage__.isArrayBufferView(imageData)) { + return; + } - if (!img2) img2 = img1; - if (!img1) img1 = (has_childs || hitem._more) ? 'img_folder' : 'img_page'; - if (!img2) img2 = (has_childs || hitem._more) ? 'img_folderopen' : 'img_page'; + const decodedPng = decodePng(imageData, { checkCrc: true }); + const { + width, + height, + channels, + palette: decodedPalette, + depth: bitsPerComponent + } = decodedPng; + + let result; + if (decodedPalette && channels === 1) { + result = processIndexedPNG(decodedPng); + } else if (channels === 2 || channels === 4) { + result = processAlphaPNG(decodedPng); + } else { + result = processOpaquePNG(decodedPng); + } - if (arg === 'update') { - d3prnt.selectAll('*').remove(); - d3cont = d3prnt; - } else { - d3cont = d3prnt.append('div'); - if (arg && (arg >= (hitem._parent._show_limit || settings.HierarchyLimit))) break_list = true; - } + const { + colorSpace, + colorsPerPixel, + sMaskBitsPerComponent, + colorBytes, + alphaBytes, + needSMask, + palette, + mask + } = result; + + let predictor = null; + + let filter, decodeParameters, sMask; + if (canCompress(compression)) { + predictor = getPredictorFromCompression(compression); + filter = this.decode.FLATE_DECODE; + decodeParameters = `/Predictor ${predictor} /Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; + + const rowByteLength = Math.ceil( + (width * colorsPerPixel * bitsPerComponent) / 8 + ); - hitem._d3cont = d3cont.node(); // set for direct referencing - d3cont.attr('item', itemname); + imageData = compressBytes( + colorBytes, + rowByteLength, + colorsPerPixel, + bitsPerComponent, + compression + ); + if (needSMask) { + const sMaskRowByteLength = Math.ceil((width * sMaskBitsPerComponent) / 8); + sMask = compressBytes( + alphaBytes, + sMaskRowByteLength, + 1, + sMaskBitsPerComponent, + compression + ); + } + } else { + filter = undefined; + decodeParameters = undefined; + imageData = colorBytes; + if (needSMask) sMask = alphaBytes; + } - // line with all html elements for this item (excluding childs) - const d3line = d3cont.append('div').attr('class', 'h_line'); + if ( + this.__addimage__.isArrayBuffer(imageData) || + this.__addimage__.isArrayBufferView(imageData) + ) { + imageData = this.__addimage__.arrayBufferToBinaryString(imageData); + } - // build indent - let prnt = isroot ? null : hitem._parent; - while (prnt && (prnt !== this.h)) { - d3line.insert('div', ':first-child') - .attr('class', this.isLastSibling(prnt) ? 'img_empty' : 'img_line'); - prnt = prnt._parent; - } + if ( + (sMask && this.__addimage__.isArrayBuffer(sMask)) || + this.__addimage__.isArrayBufferView(sMask) + ) { + sMask = this.__addimage__.arrayBufferToBinaryString(sMask); + } - let icon_class = '', plusminus = false; + return { + alias, + data: imageData, + index, + filter, + decodeParameters, + transparency: mask, + palette, + sMask, + predictor, + width, + height, + bitsPerComponent, + sMaskBitsPerComponent, + colorSpace + }; +}; - if (isroot) ; else if (has_childs && !break_list) { - icon_class = hitem._isopen ? 'img_minus' : 'img_plus'; - plusminus = true; - } else /* if (hitem._more) { - icon_class = 'img_plus'; // should be special plus ??? - plusminus = true; - } else */ - icon_class = 'img_join'; +/* + * PNG filter method types + * + * @see http://www.w3.org/TR/PNG-Filters.html + * @see http://www.libpng.org/pub/png/book/chapter09.html + * + * This is what the value 'Predictor' in decode params relates to + * + * 15 is "optimal prediction", which means the prediction algorithm can change from line to line. + * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte. + * + 0 None + 1 Sub + 2 Up + 3 Average + 4 Paeth + */ - const h = this; +function canCompress(value) { + return value !== jsPDF.API.image_compression.NONE && hasCompressionJS(); +} - if (icon_class) { - if (break_list || this.isLastSibling(hitem)) icon_class += 'bottom'; - const d3icon = d3line.append('div').attr('class', icon_class); - if (plusminus) - d3icon.style('cursor', 'pointer').on('click', function(evnt) { h.tree_click(evnt, this, 'plusminus'); }); - } +function hasCompressionJS() { + return typeof zlibSync === "function"; +} +function compressBytes( + bytes, + lineByteLength, + channels, + bitsPerComponent, + compression +) { + let level = 4; + let filter_method = filterUp; - // make node icons + switch (compression) { + case jsPDF.API.image_compression.FAST: + level = 1; + filter_method = filterSub; + break; - if (this.with_icons && !break_list) { - const icon_name = hitem._isopen ? img2 : img1, - d3img = (icon_name.indexOf('img_') === 0) - ? d3line.append('div') - .attr('class', icon_name) - .attr('title', hitem._kind) - : d3line.append('img') - .attr('src', icon_name) - .attr('alt', '') - .attr('title', hitem._kind) - .style('vertical-align', 'top') - .style('width', '18px') - .style('height', '18px'); + case jsPDF.API.image_compression.MEDIUM: + level = 6; + filter_method = filterAverage; + break; - if (('_icon_click' in hitem) || (handle && ('icon_click' in handle))) - d3img.on('click', function(evnt) { h.tree_click(evnt, this, 'icon'); }); - } + case jsPDF.API.image_compression.SLOW: + level = 9; + filter_method = filterPaeth; + break; + } - const d3a = d3line.append('a'); - if (can_click || has_childs || break_list) - d3a.attr('class', 'h_item').on('click', function(evnt) { h.tree_click(evnt, this); }); + const bytesPerPixel = Math.ceil((channels * bitsPerComponent) / 8); + bytes = applyPngFilterMethod( + bytes, + lineByteLength, + bytesPerPixel, + filter_method + ); + const dat = zlibSync(bytes, { level: level }); + return jsPDF.API.__addimage__.arrayBufferToBinaryString(dat); +} + +function applyPngFilterMethod( + bytes, + lineByteLength, + bytesPerPixel, + filter_method +) { + const lines = bytes.length / lineByteLength; + const result = new Uint8Array(bytes.length + lines); + const filter_methods = getFilterMethods(); + let prevLine; + + for (let i = 0; i < lines; i += 1) { + const offset = i * lineByteLength; + const line = bytes.subarray(offset, offset + lineByteLength); + + if (filter_method) { + result.set(filter_method(line, bytesPerPixel, prevLine), offset + i); + } else { + const len = filter_methods.length; + const results = []; - if (break_list) { - hitem._break_point = true; // indicate that list was broken here - d3a.attr('title', 'there are ' + (hitem._parent._childs.length-arg) + ' more items') - .text('...more...'); - return false; + for (let j = 0; j < len; j += 1) { + results[j] = filter_methods[j](line, bytesPerPixel, prevLine); } - if ('disp_kind' in h) { - if (settings.DragAndDrop && can_click) - this.enableDrag(d3a, itemname); - - if (settings.ContextMenu && can_menu) - d3a.on('contextmenu', function(evnt) { h.tree_contextmenu(evnt, this); }); - - d3a.on('mouseover', function() { h.tree_mouseover(true, this); }) - .on('mouseleave', function() { h.tree_mouseover(false, this); }); - } else if (hitem._direct_context && settings.ContextMenu) - d3a.on('contextmenu', function(evnt) { h.direct_contextmenu(evnt, this); }); - - let element_name = hitem._name, element_title = ''; + const ind = getIndexOfSmallestSum(results.concat()); - if ('_realname' in hitem) - element_name = hitem._realname; + result.set(results[ind], offset + i); + } - if ('_title' in hitem) - element_title = hitem._title; + prevLine = line; + } - if ('_fullname' in hitem) - element_title += ' fullname: ' + hitem._fullname; + return result; +} - if (!element_title) - element_title = element_name; +function filterNone(line) { + /*const result = new Uint8Array(line.length + 1); + result[0] = 0; + result.set(line, 1);*/ - d3a.attr('title', element_title) - .text(element_name + ('_value' in hitem ? ':' : '')) - .style('background', hitem._background ? hitem._background : null); + const result = Array.apply([], line); + result.unshift(0); - if ('_value' in hitem) { - const d3p = d3line.append('p'); - if ('_vclass' in hitem) d3p.attr('class', hitem._vclass); - if (!hitem._isopen) d3p.html(hitem._value); - } + return result; +} - if (has_childs && (isroot || hitem._isopen)) { - const d3chlds = d3cont.append('div').attr('class', 'h_childs'); - if (this.show_overflow) d3chlds.style('overflow', 'initial'); - for (let i = 0; i < hitem._childs.length; ++i) { - const chld = hitem._childs[i]; - chld._parent = hitem; - if (!this.addItemHtml(chld, d3chlds, i)) break; // if too many items, skip rest - } - } +function filterSub(line, colorsPerPixel) { + const len = line.length; + const result = []; - return true; - } + result[0] = 1; - /** @summary Toggle open state of the item - * @desc Used with 'expand all' / 'collapse all' buttons in normal GUI - * @param {boolean} isopen - if items should be expand or closed - * @return {boolean} true when any item was changed */ - toggleOpenState(isopen, h, promises) { - const hitem = h || this.h; + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + result[i + 1] = (line[i] - left + 0x0100) & 0xff; + } - if (hitem._childs === undefined) { - if (!isopen) return false; + return result; +} - if (this.with_icons) { - // in normal hierarchy check precisely if item can be expand - if (!hitem._more && !hitem._expand && !this.canExpandItem(hitem)) return false; - } +function filterUp(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; - const pr = this.expandItem(this.itemFullName(hitem)); - if (isPromise(pr) && isObject(promises)) - promises.push(pr); - if (hitem._childs !== undefined) hitem._isopen = true; - return hitem._isopen; - } + result[0] = 2; - if ((hitem !== this.h) && isopen && !hitem._isopen) { - // when there are childs and they are not see, simply show them - hitem._isopen = true; - return true; - } + for (let i = 0; i < len; i += 1) { + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] - up + 0x0100) & 0xff; + } - let change_child = false; - for (let i = 0; i < hitem._childs.length; ++i) { - if (this.toggleOpenState(isopen, hitem._childs[i], promises)) - change_child = true; - } + return result; +} - if ((hitem !== this.h) && !isopen && hitem._isopen && !change_child) { - // if none of the childs can be closed, than just close that item - delete hitem._isopen; - return true; - } +function filterAverage(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; - if (!h) this.refreshHtml(); - return false; - } + result[0] = 3; - /** @summary Exapnd to specified level - * @protected */ - async exapndToLevel(level) { - if (!level || !Number.isFinite(level) || (level < 0)) return this; + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff; + } - const promises = []; - this.toggleOpenState(true, this.h, promises); - return Promise.all(promises).then(() => this.exapndToLevel(level - 1)); - } + return result; +} - /** @summary Refresh HTML code of hierarchy painter - * @return {Promise} when done */ - async refreshHtml() { - const d3elem = this.selectDom(); - if (d3elem.empty()) - return this; +function filterPaeth(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; - d3elem.html('') // clear html - most simple way - .style('overflow', this.show_overflow ? 'auto' : 'hidden') - .style('display', 'flex') - .style('flex-direction', 'column'); + result[0] = 4; - injectHStyle(d3elem.node()); + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + const upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0; + const paeth = paethPredictor(left, up, upLeft); + result[i + 1] = (line[i] - paeth + 0x0100) & 0xff; + } - const h = this, factcmds = []; - let status_item = null; - this.forEachItem(item => { - delete item._d3cont; // remove html container - if (('_fastcmd' in item) && (item._kind === 'Command')) factcmds.push(item); - if (('_status' in item) && !status_item) status_item = item; - }); + return result; +} - if (!this.h || d3elem.empty()) - return this; +function paethPredictor(left, up, upLeft) { + if (left === up && up === upLeft) { + return left; + } + const pLeft = Math.abs(up - upLeft), + pUp = Math.abs(left - upLeft), + pUpLeft = Math.abs(left + up - upLeft - upLeft); + return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft; +} - if (factcmds.length) { - const fastbtns = d3elem.append('div').attr('style', 'display: inline; vertical-align: middle; white-space: nowrap;'); - for (let n = 0; n < factcmds.length; ++n) { - const btn = fastbtns.append('button') - .text('') - .attr('class', 'jsroot_fastcmd_btn') - .attr('item', this.itemFullName(factcmds[n])) - .attr('title', factcmds[n]._title) - .on('click', function() { h.executeCommand(select(this).attr('item'), this); }); +function getFilterMethods() { + return [filterNone, filterSub, filterUp, filterAverage, filterPaeth]; +} - if (factcmds[n]._icon) - btn.style('background-image', `url('${factcmds[n]._icon}')`); - } - } +function getIndexOfSmallestSum(arrays) { + const sum = arrays.map(function(value) { + return value.reduce(function(pv, cv) { + return pv + Math.abs(cv); + }, 0); + }); + return sum.indexOf(Math.min.apply(null, sum)); +} - const d3btns = d3elem.append('p').attr('class', 'jsroot').style('margin-bottom', '3px').style('margin-top', 0); - d3btns.append('a').attr('class', 'h_button').text('expand all') - .attr('title', 'expand all items in the browser').on('click', () => this.toggleOpenState(true)); - d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('collapse all') - .attr('title', 'collapse all items in the browser').on('click', () => this.toggleOpenState(false)); +function getPredictorFromCompression(compression) { + let predictor; + switch (compression) { + case jsPDF.API.image_compression.FAST: + predictor = 11; + break; - if (isFunc(this.removeInspector)) { - d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('remove') - .attr('title', 'remove inspector').on('click', () => this.removeInspector()); - } + case jsPDF.API.image_compression.MEDIUM: + predictor = 13; + break; - if ('_online' in this.h) { - d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('reload') - .attr('title', 'reload object list from the server').on('click', () => this.reload()); - } + case jsPDF.API.image_compression.SLOW: + predictor = 14; + break; - if ('disp_kind' in this) { - d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('clear') - .attr('title', 'clear all drawn objects').on('click', () => this.clearHierarchy(false)); + default: + predictor = 12; + break; + } + return predictor; +} + +// Extracted helper for Indexed PNGs (palette-based) +function processIndexedPNG(decodedPng) { + const { width, height, data, palette: decodedPalette, depth } = decodedPng; + let needSMask = false; + let palette = []; + let mask = []; + let alphaBytes = undefined; + let hasSemiTransparency = false; + + const maxMaskLength = 1; + let maskLength = 0; + + for (let i = 0; i < decodedPalette.length; i++) { + const [r, g, b, a] = decodedPalette[i]; + palette.push(r, g, b); + if (a != null) { + if (a === 0) { + maskLength++; + if (mask.length < maxMaskLength) { + mask.push(i); + } + } else if (a < 255) { + hasSemiTransparency = true; } + } + } - const maindiv = - d3elem.append('div') - .attr('class', 'jsroot') - .style('font-size', this.with_icons ? '12px' : '15px') - .style('flex', '1'); + if (hasSemiTransparency || maskLength > maxMaskLength) { + needSMask = true; + mask = undefined; + + const totalPixels = width * height; + // per PNG spec, palettes always use 8 bits per component + alphaBytes = new Uint8Array(totalPixels); + const dataView = new DataView(data.buffer); + for (let p = 0; p < totalPixels; p++) { + const paletteIndex = readSample(dataView, p, depth); + const [, , , alpha] = decodedPalette[paletteIndex]; + alphaBytes[p] = alpha; + } + } else if (maskLength === 0) { + mask = undefined; + } - if (!this.show_overflow) - maindiv.style('overflow', 'auto'); + return { + colorSpace: "Indexed", + colorsPerPixel: 1, + sMaskBitsPerComponent: needSMask ? 8 : undefined, + colorBytes: data, + alphaBytes, + needSMask, + palette, + mask + }; +} - if (this.background) { - // case of object inspector and streamer infos display - maindiv.style('background-color', this.background) - .style('margin', '2px').style('padding', '2px'); - } - if (this.textcolor) - maindiv.style('color', this.textcolor); +/* + * Splits color and alpha values into separate buffers + */ +function processAlphaPNG(decodedPng) { + const { data, width, height, channels, depth } = decodedPng; + + const colorSpace = channels === 2 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = channels - 1; + + const totalPixels = width * height; + const colorChannels = colorsPerPixel; // 1 for Gray, 3 for RGB + const alphaChannels = 1; + const totalColorSamples = totalPixels * colorChannels; + const totalAlphaSamples = totalPixels * alphaChannels; + + const colorByteLen = Math.ceil((totalColorSamples * depth) / 8); + const alphaByteLen = Math.ceil((totalAlphaSamples * depth) / 8); + const colorBytes = new Uint8Array(colorByteLen); + const alphaBytes = new Uint8Array(alphaByteLen); + + const dataView = new DataView(data.buffer); + const colorView = new DataView(colorBytes.buffer); + const alphaView = new DataView(alphaBytes.buffer); + + let needSMask = false; + for (let p = 0; p < totalPixels; p++) { + const pixelStartIndex = p * channels; + for (let s = 0; s < colorChannels; s++) { + const sampleIndex = pixelStartIndex + s; + const colorValue = readSample(dataView, sampleIndex, depth); + writeSample(colorView, colorValue, p * colorChannels + s, depth); + } + const sampleIndex = pixelStartIndex + colorChannels; + const alphaValue = readSample(dataView, sampleIndex, depth); + if (alphaValue < (1 << depth) - 1) { + needSMask = true; + } + writeSample(alphaView, alphaValue, p * alphaChannels, depth); + } - this.addItemHtml(this.h, maindiv.append('div').attr('class', 'h_tree')); + return { + colorSpace, + colorsPerPixel, + sMaskBitsPerComponent: needSMask ? depth : undefined, + colorBytes, + alphaBytes, + needSMask + }; +} - this.setTopPainter(); // assign hpainter as top painter +function processOpaquePNG(decodedPng) { + const { data, channels } = decodedPng; + const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3; - if (status_item && !this.status_disabled && !decodeUrl().has('nostatus')) { - const func = findFunction(status_item._status); - if (isFunc(func)) { - return this.createStatusLine().then(sdiv => { - if (sdiv) func(sdiv, this.itemFullName(status_item)); - }); - } - } + let colorBytes; + if (data instanceof Uint16Array) { + colorBytes = convertUint16ArrayToUint8Array(data); + } else { + colorBytes = data; + } - return this; - } + return { colorSpace, colorsPerPixel, colorBytes, needSMask: false }; +} - /** @summary Update item node - * @private */ - updateTreeNode(hitem, d3cont) { - if ((d3cont === undefined) || d3cont.empty()) { - d3cont = select(hitem._d3cont ? hitem._d3cont : null); - const name = this.itemFullName(hitem); - if (d3cont.empty()) - d3cont = this.selectDom().select(`[item='${name}']`); - if (d3cont.empty() && ('_cycle' in hitem)) - d3cont = this.selectDom().select(`[item='${name};${hitem._cycle}']`); - if (d3cont.empty()) return; - } +function convertUint16ArrayToUint8Array(data) { + // PNG/PDF expect MSB-first byte order. Since EcmaScript does not specify + // the byte order of Uint16Array, we need to use a DataView to ensure the + // correct byte order. + const sampleCount = data.length; + const out = new Uint8Array(sampleCount * 2); + const outView = new DataView(out.buffer, out.byteOffset, out.byteLength); - this.addItemHtml(hitem, d3cont, 'update'); + for (let i = 0; i < sampleCount; i++) { + outView.setUint16(i * 2, data[i], false); + } + return out; +} - this.brlayout?.adjustBrowserSize(true); - } +function readSample(view, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const word = safeGetUint16(view, byteIndex); + return (word >> bitOffset) & bitMask; +} - /** @summary Update item background - * @private */ - updateBackground(hitem, scroll_into_view) { - if (!hitem || !hitem._d3cont) return; +function writeSample(view, value, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const writeValue = (value & bitMask) << bitOffset; + const word = + safeGetUint16(view, byteIndex) & ~(bitMask << bitOffset) & 0xffff; + safeSetUint16(view, byteIndex, word | writeValue); +} - const d3cont = select(hitem._d3cont); +function safeGetUint16(view, byteIndex) { + if (byteIndex + 1 < view.byteLength) { + return view.getUint16(byteIndex, false); + } + const b0 = view.getUint8(byteIndex); + return b0 << 8; +} - if (d3cont.empty()) return; +function safeSetUint16(view, byteIndex, value) { + if (byteIndex + 1 < view.byteLength) { + view.setUint16(byteIndex, value, false); + return; + } + const byteToWrite = (value >> 8) & 0xff; + view.setUint8(byteIndex, byteToWrite); +} - const d3a = d3cont.select('.h_item'); +/** + * @license + * + * Copyright (c) 2021 Antti Palola, https://github.com/Pantura + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - d3a.style('background', hitem._background ? hitem._background : null); +/** + * jsPDF RGBA array PlugIn + * @name rgba_support + * @module + */ +(function(jsPDFAPI) { + + /** + * @name processRGBA + * @function + * + * Process RGBA Array. This is a one-dimension array with pixel data [red, green, blue, alpha, red, green, ...]. + * RGBA array data can be obtained from DOM canvas getImageData. + * @ignore + */ + jsPDFAPI.processRGBA = function(imageData, index, alias) { + + var imagePixels = imageData.data; + var length = imagePixels.length; + // jsPDF takes alpha data separately so extract that. + var rgbOut = new Uint8Array((length / 4) * 3); + var alphaOut = new Uint8Array(length / 4); + var outIndex = 0; + var alphaIndex = 0; + + for (var i = 0; i < length; i += 4) { + var r = imagePixels[i]; + var g = imagePixels[i + 1]; + var b = imagePixels[i + 2]; + var alpha = imagePixels[i + 3]; + rgbOut[outIndex++] = r; + rgbOut[outIndex++] = g; + rgbOut[outIndex++] = b; + alphaOut[alphaIndex++] = alpha; + } - if (scroll_into_view && hitem._background) - d3a.node().scrollIntoView(false); - } + var rgbData = this.__addimage__.arrayBufferToBinaryString(rgbOut); + var alphaData = this.__addimage__.arrayBufferToBinaryString(alphaOut); + + return { + alpha: alphaData, + data: rgbData, + index: index, + alias: alias, + colorSpace: "DeviceRGB", + bitsPerComponent: 8, + width: imageData.width, + height: imageData.height + }; + }; +})(jsPDF.API); - /** @summary Focus on hierarchy item - * @param {Object|string} hitem - item to open or its name - * @desc all parents to the otem will be opened first - * @return {Promise} when done - * @private */ - async focusOnItem(hitem) { - if (isStr(hitem)) - hitem = this.findItem(hitem); +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - const name = hitem ? this.itemFullName(hitem) : ''; - if (!name) return false; +/** + * jsPDF setLanguage Plugin + * + * @name setLanguage + * @module + */ +(function(jsPDFAPI) { + + /** + * Add Language Tag to the generated PDF + * + * @name setLanguage + * @function + * @param {string} langCode The Language code as ISO-639-1 (e.g. 'en') or as country language code (e.g. 'en-GB'). + * @returns {jsPDF} + * @example + * var doc = new jsPDF() + * doc.text(10, 10, 'This is a test') + * doc.setLanguage("en-US") + * doc.save('english.pdf') + */ + jsPDFAPI.setLanguage = function(langCode) { + + var langCodes = { + af: "Afrikaans", + sq: "Albanian", + ar: "Arabic (Standard)", + "ar-DZ": "Arabic (Algeria)", + "ar-BH": "Arabic (Bahrain)", + "ar-EG": "Arabic (Egypt)", + "ar-IQ": "Arabic (Iraq)", + "ar-JO": "Arabic (Jordan)", + "ar-KW": "Arabic (Kuwait)", + "ar-LB": "Arabic (Lebanon)", + "ar-LY": "Arabic (Libya)", + "ar-MA": "Arabic (Morocco)", + "ar-OM": "Arabic (Oman)", + "ar-QA": "Arabic (Qatar)", + "ar-SA": "Arabic (Saudi Arabia)", + "ar-SY": "Arabic (Syria)", + "ar-TN": "Arabic (Tunisia)", + "ar-AE": "Arabic (U.A.E.)", + "ar-YE": "Arabic (Yemen)", + an: "Aragonese", + hy: "Armenian", + as: "Assamese", + ast: "Asturian", + az: "Azerbaijani", + eu: "Basque", + be: "Belarusian", + bn: "Bengali", + bs: "Bosnian", + br: "Breton", + bg: "Bulgarian", + my: "Burmese", + ca: "Catalan", + ch: "Chamorro", + ce: "Chechen", + zh: "Chinese", + "zh-HK": "Chinese (Hong Kong)", + "zh-CN": "Chinese (PRC)", + "zh-SG": "Chinese (Singapore)", + "zh-TW": "Chinese (Taiwan)", + cv: "Chuvash", + co: "Corsican", + cr: "Cree", + hr: "Croatian", + cs: "Czech", + da: "Danish", + nl: "Dutch (Standard)", + "nl-BE": "Dutch (Belgian)", + en: "English", + "en-AU": "English (Australia)", + "en-BZ": "English (Belize)", + "en-CA": "English (Canada)", + "en-IE": "English (Ireland)", + "en-JM": "English (Jamaica)", + "en-NZ": "English (New Zealand)", + "en-PH": "English (Philippines)", + "en-ZA": "English (South Africa)", + "en-TT": "English (Trinidad & Tobago)", + "en-GB": "English (United Kingdom)", + "en-US": "English (United States)", + "en-ZW": "English (Zimbabwe)", + eo: "Esperanto", + et: "Estonian", + fo: "Faeroese", + fj: "Fijian", + fi: "Finnish", + fr: "French (Standard)", + "fr-BE": "French (Belgium)", + "fr-CA": "French (Canada)", + "fr-FR": "French (France)", + "fr-LU": "French (Luxembourg)", + "fr-MC": "French (Monaco)", + "fr-CH": "French (Switzerland)", + fy: "Frisian", + fur: "Friulian", + gd: "Gaelic (Scots)", + "gd-IE": "Gaelic (Irish)", + gl: "Galacian", + ka: "Georgian", + de: "German (Standard)", + "de-AT": "German (Austria)", + "de-DE": "German (Germany)", + "de-LI": "German (Liechtenstein)", + "de-LU": "German (Luxembourg)", + "de-CH": "German (Switzerland)", + el: "Greek", + gu: "Gujurati", + ht: "Haitian", + he: "Hebrew", + hi: "Hindi", + hu: "Hungarian", + is: "Icelandic", + id: "Indonesian", + iu: "Inuktitut", + ga: "Irish", + it: "Italian (Standard)", + "it-CH": "Italian (Switzerland)", + ja: "Japanese", + kn: "Kannada", + ks: "Kashmiri", + kk: "Kazakh", + km: "Khmer", + ky: "Kirghiz", + tlh: "Klingon", + ko: "Korean", + "ko-KP": "Korean (North Korea)", + "ko-KR": "Korean (South Korea)", + la: "Latin", + lv: "Latvian", + lt: "Lithuanian", + lb: "Luxembourgish", + mk: "North Macedonia", + ms: "Malay", + ml: "Malayalam", + mt: "Maltese", + mi: "Maori", + mr: "Marathi", + mo: "Moldavian", + nv: "Navajo", + ng: "Ndonga", + ne: "Nepali", + no: "Norwegian", + nb: "Norwegian (Bokmal)", + nn: "Norwegian (Nynorsk)", + oc: "Occitan", + or: "Oriya", + om: "Oromo", + fa: "Persian", + "fa-IR": "Persian/Iran", + pl: "Polish", + pt: "Portuguese", + "pt-BR": "Portuguese (Brazil)", + pa: "Punjabi", + "pa-IN": "Punjabi (India)", + "pa-PK": "Punjabi (Pakistan)", + qu: "Quechua", + rm: "Rhaeto-Romanic", + ro: "Romanian", + "ro-MO": "Romanian (Moldavia)", + ru: "Russian", + "ru-MO": "Russian (Moldavia)", + sz: "Sami (Lappish)", + sg: "Sango", + sa: "Sanskrit", + sc: "Sardinian", + sd: "Sindhi", + si: "Singhalese", + sr: "Serbian", + sk: "Slovak", + sl: "Slovenian", + so: "Somani", + sb: "Sorbian", + es: "Spanish", + "es-AR": "Spanish (Argentina)", + "es-BO": "Spanish (Bolivia)", + "es-CL": "Spanish (Chile)", + "es-CO": "Spanish (Colombia)", + "es-CR": "Spanish (Costa Rica)", + "es-DO": "Spanish (Dominican Republic)", + "es-EC": "Spanish (Ecuador)", + "es-SV": "Spanish (El Salvador)", + "es-GT": "Spanish (Guatemala)", + "es-HN": "Spanish (Honduras)", + "es-MX": "Spanish (Mexico)", + "es-NI": "Spanish (Nicaragua)", + "es-PA": "Spanish (Panama)", + "es-PY": "Spanish (Paraguay)", + "es-PE": "Spanish (Peru)", + "es-PR": "Spanish (Puerto Rico)", + "es-ES": "Spanish (Spain)", + "es-UY": "Spanish (Uruguay)", + "es-VE": "Spanish (Venezuela)", + sx: "Sutu", + sw: "Swahili", + sv: "Swedish", + "sv-FI": "Swedish (Finland)", + "sv-SV": "Swedish (Sweden)", + ta: "Tamil", + tt: "Tatar", + te: "Teluga", + th: "Thai", + tig: "Tigre", + ts: "Tsonga", + tn: "Tswana", + tr: "Turkish", + tk: "Turkmen", + uk: "Ukrainian", + hsb: "Upper Sorbian", + ur: "Urdu", + ve: "Venda", + vi: "Vietnamese", + vo: "Volapuk", + wa: "Walloon", + cy: "Welsh", + xh: "Xhosa", + ji: "Yiddish", + zu: "Zulu" + }; - let itm = hitem, need_refresh = false; + if (this.internal.languageSettings === undefined) { + this.internal.languageSettings = {}; + this.internal.languageSettings.isSubscribed = false; + } - while (itm) { - if ((itm._childs !== undefined) && !itm._isopen) { - itm._isopen = true; - need_refresh = true; - } - itm = itm._parent; + if (langCodes[langCode] !== undefined) { + this.internal.languageSettings.languageCode = langCode; + if (this.internal.languageSettings.isSubscribed === false) { + this.internal.events.subscribe("putCatalog", function() { + this.internal.write( + "/Lang (" + this.internal.languageSettings.languageCode + ")" + ); + }); + this.internal.languageSettings.isSubscribed = true; } + } + return this; + }; +})(jsPDF.API); - const promise = need_refresh ? this.refreshHtml() : Promise.resolve(true); +/** @license + * MIT license. + * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - return promise.then(() => { - const d3cont = this.selectDom().select(`[item='${name}']`); - if (d3cont.empty()) return false; - d3cont.node().scrollIntoView(); - return true; - }); - } +/** + * jsPDF split_text_to_size plugin + * + * @name split_text_to_size + * @module + */ +(function(API) { + /** + * Returns an array of length matching length of the 'word' string, with each + * cell occupied by the width of the char in that position. + * + * @name getCharWidthsArray + * @function + * @param {string} text + * @param {Object} options + * @returns {Array} + */ + var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { + options = options || {}; + + var activeFont = options.font || this.internal.getFont(); + var fontSize = options.fontSize || this.internal.getFontSize(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + + var widths = options.widths + ? options.widths + : activeFont.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning + ? options.kerning + : activeFont.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + var doKerning = options.doKerning === false ? false : true; + var kerningValue = 0; + + var i; + var length = text.length; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0; i < length; i++) { + char_code = text.charCodeAt(i); + + if (typeof activeFont.metadata.widthOfString === "function") { + output.push( + (activeFont.metadata.widthOfGlyph( + activeFont.metadata.characterToGlyph(char_code) + ) + + charSpace * (1000 / fontSize) || 0) / 1000 + ); + } else { + if ( + doKerning && + typeof kerning[char_code] === "object" && + !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) + ) { + kerningValue = + kerning[char_code][prior_char_code] / kerningFractionOf; + } else { + kerningValue = 0; + } + output.push( + (widths[char_code] || default_char_width) / widthsFractionOf + + kerningValue + ); + } + prior_char_code = char_code; + } - /** @summary Handler for click event of item in the hierarchy - * @private */ - tree_click(evnt, node, place) { - if (!node) return; + return output; + }); - const d3cont = select(node.parentNode.parentNode), - itemname = d3cont.attr('item'), - hitem = itemname ? this.findItem(itemname) : null; + /** + * Returns a widths of string in a given font, if the font size is set as 1 point. + * + * In other words, this is "proportional" value. For 1 unit of font size, the length + * of the string will be that much. + * + * Multiply by font size to get actual width in *points* + * Then divide by 72 to get inches or divide by (72/25.4) to get 'mm' etc. + * + * @name getStringUnitWidth + * @public + * @function + * @param {string} text + * @param {string} options + * @returns {number} result + */ + var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { + options = options || {}; - if (!hitem) return; + var fontSize = options.fontSize || this.internal.getFontSize(); + var font = options.font || this.internal.getFont(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + var result = 0; - if (hitem._break_point) { - // special case of more item + if (API.processArabic) { + text = API.processArabic(text); + } - delete hitem._break_point; + if (typeof font.metadata.widthOfString === "function") { + result = + font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; + } else { + result = getCharWidthsArray + .apply(this, arguments) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } + return result; + }); - // update item itself - this.addItemHtml(hitem, d3cont, 'update'); + /** + returns array of lines + */ + var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { + var answer = []; - const prnt = hitem._parent, indx = prnt._childs.indexOf(hitem), - d3chlds = select(d3cont.node().parentNode); + // 1st, chop off the piece that can fit on the hanging line. + var i = 0, + l = word.length, + workingLen = 0; + while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { + workingLen += widths_array[i]; + i++; + } + // this is first line. + answer.push(word.slice(0, i)); + + // 2nd. Split the rest into maxLen pieces. + var startOfLine = i; + workingLen = 0; + while (i !== l) { + if (workingLen + widths_array[i] > maxLen) { + answer.push(word.slice(startOfLine, i)); + workingLen = 0; + startOfLine = i; + } + workingLen += widths_array[i]; + i++; + } + if (startOfLine !== i) { + answer.push(word.slice(startOfLine, i)); + } - if (indx < 0) return console.error('internal error'); + return answer; + }; - prnt._show_limit = (prnt._show_limit || settings.HierarchyLimit) * 2; + // Note, all sizing inputs for this function must be in "font measurement units" + // By default, for PDF, it's "point". + var splitParagraphIntoLines = function(text, maxlen, options) { + // at this time works only on Western scripts, ones with space char + // separating the words. Feel free to expand. - for (let n = indx+1; n < prnt._childs.length; ++n) { - const chld = prnt._childs[n]; - chld._parent = prnt; - if (!this.addItemHtml(chld, d3chlds, n)) break; // if too many items, skip rest - } + if (!options) { + options = {}; + } - return; - } + var line = [], + lines = [line], + line_length = options.textIndent || 0, + separator_length = 0, + current_word_length = 0, + word, + widths_array, + words = text.split(" "), + spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], + i, + l, + tmp, + lineIndent; + + if (options.lineIndent === -1) { + lineIndent = words[0].length + 2; + } else { + lineIndent = options.lineIndent || 0; + } + if (lineIndent) { + var pad = Array(lineIndent).join(" "), + wrds = []; + words.map(function(wrd) { + wrd = wrd.split(/\s*\n/); + if (wrd.length > 1) { + wrds = wrds.concat( + wrd.map(function(wrd, idx) { + return (idx && wrd.length ? "\n" : "") + wrd; + }) + ); + } else { + wrds.push(wrd[0]); + } + }); + words = wrds; + lineIndent = getStringUnitWidth.apply(this, [pad, options]); + } - let prnt = hitem, dflt; - while (prnt) { - if ((dflt = prnt._click_action) !== undefined) break; - prnt = prnt._parent; - } + for (i = 0, l = words.length; i < l; i++) { + var force = 0; + + word = words[i]; + if (lineIndent && word[0] == "\n") { + word = word.substr(1); + force = 1; + } + widths_array = getCharWidthsArray.apply(this, [word, options]); + current_word_length = widths_array.reduce(function(pv, cv) { + return pv + cv; + }, 0); + + if ( + line_length + separator_length + current_word_length > maxlen || + force + ) { + if (current_word_length > maxlen) { + // this happens when you have space-less long URLs for example. + // we just chop these to size. We do NOT insert hiphens + tmp = splitLongWord.apply(this, [ + word, + widths_array, + maxlen - (line_length + separator_length), + maxlen + ]); + // first line we add to existing line object + line.push(tmp.shift()); // it's ok to have extra space indicator there + // last line we make into new line object + line = [tmp.pop()]; + // lines in the middle we apped to lines object as whole lines + while (tmp.length) { + lines.push([tmp.shift()]); // single fragment occupies whole line + } + current_word_length = widths_array + .slice(word.length - (line[0] ? line[0].length : 0)) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } else { + // just put it on a new line + line = [word]; + } - if (!place) place = 'item'; - const selector = (hitem._kind === prROOT + clTKey && hitem._more) ? 'noinspect' : '', - sett = getDrawSettings(hitem._kind, selector), handle = sett.handle; + // now we attach new line to lines + lines.push(line); + line_length = current_word_length + lineIndent; + separator_length = spaceCharWidth; + } else { + line.push(word); - if (place === 'icon') { - let func = null; - if (isFunc(hitem._icon_click)) - func = hitem._icon_click; - else if (isFunc(handle?.icon_click)) - func = handle.icon_click; - if (func && func(hitem, this)) - this.updateTreeNode(hitem, d3cont); - return; + line_length += separator_length + current_word_length; + separator_length = spaceCharWidth; } + } - // special feature - all items with '_expand' function are not drawn by click - if ((place === 'item') && ('_expand' in hitem) && !evnt.ctrlKey && !evnt.shiftKey) place = 'plusminus'; - - // special case - one should expand item - if (((place === 'plusminus') && !('_childs' in hitem) && hitem._more) || - ((place === 'item') && (dflt === 'expand'))) - return this.expandItem(itemname, d3cont); - - - if (place === 'item') { - if (hitem._player) - return this.player(itemname); - - if (handle?.aslink) - return window.open(itemname + '/'); - - if (handle?.execute) - return this.executeCommand(itemname, node.parentNode); + var postProcess; + if (lineIndent) { + postProcess = function(ln, idx) { + return (idx ? pad : "") + ln.join(" "); + }; + } else { + postProcess = function(ln) { + return ln.join(" "); + }; + } - if (handle?.ignore_online && this.isOnlineItem(hitem)) return; + return lines.map(postProcess); + }; - const dflt_expand = (this.default_by_click === 'expand'); - let can_draw = hitem._can_draw, - can_expand = hitem._more, - drawopt = ''; + /** + * Splits a given string into an array of strings. Uses 'size' value + * (in measurement units declared as default for the jsPDF instance) + * and the font's "widths" and "Kerning" tables, where available, to + * determine display length of a given string for a given font. + * + * We use character's 100% of unit size (height) as width when Width + * table or other default width is not available. + * + * @name splitTextToSize + * @public + * @function + * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. + * @param {number} size Nominal number, measured in units default to this instance of jsPDF. + * @param {Object} options Optional flags needed for chopper to do the right thing. + * @returns {Array} array Array with strings chopped to size. + */ + API.splitTextToSize = function(text, maxlen, options) { - if (evnt.shiftKey) { - drawopt = handle?.shift || kInspect; - if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0) && handle?.noinspect) drawopt = ''; - } - if (evnt.ctrlKey && handle?.ctrl) - drawopt = handle.ctrl; + options = options || {}; - if (!drawopt && !handle?.always_draw) { - for (let pitem = hitem._parent; pitem; pitem = pitem._parent) { - if (pitem._painter) { - can_draw = false; - if (can_expand === undefined) - can_expand = false; - break; - } - } - } + var fsize = options.fontSize || this.internal.getFontSize(), + newOptions = function(options) { + var widths = { + 0: 1 + }, + kerning = {}; - if (hitem._childs) can_expand = false; + if (!options.widths || !options.kerning) { + var f = this.internal.getFont(options.fontName, options.fontStyle), + encoding = "Unicode"; + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE + // Actual JavaScript-native String's 16bit char codes used. + // no multi-byte logic here - if (can_draw === undefined) can_draw = sett.draw; - if (can_expand === undefined) can_expand = sett.expand || sett.get_expand; + if (f.metadata[encoding]) { + return { + widths: f.metadata[encoding].widths || widths, + kerning: f.metadata[encoding].kerning || kerning + }; + } else { + return { + font: f.metadata, + fontSize: this.internal.getFontSize(), + charSpace: this.internal.getCharSpace() + }; + } + } else { + return { + widths: options.widths, + kerning: options.kerning + }; + } + }.call(this, options); - if (can_draw && can_expand && !drawopt) { - // if default action specified as expand, disable drawing - // if already displayed, try to expand - if (dflt_expand || (handle?.dflt === 'expand') || (handle?.exapnd_after_draw && this.isItemDisplayed(itemname))) can_draw = false; - } + // first we split on end-of-line chars + var paragraphs; + if (Array.isArray(text)) { + paragraphs = text; + } else { + paragraphs = String(text).split(/\r?\n/); + } - if (can_draw && !drawopt) - drawopt = '__default_draw_option__'; + // now we convert size (max length of line) into "font size units" + // at present time, the "font size unit" is always 'point' + // 'proportional' means, "in proportion to font size" + var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; + // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. + // this may change in the future? + // until then, proportional_maxlen is likely to be in 'points' + + // If first line is to be indented (shorter or longer) than maxLen + // we indicate that by using CSS-style "text-indent" option. + // here it's in font units too (which is likely 'points') + // it can be negative (which makes the first line longer than maxLen) + newOptions.textIndent = options.textIndent + ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize + : 0; + newOptions.lineIndent = options.lineIndent; + + var i, + l, + output = []; + for (i = 0, l = paragraphs.length; i < l; i++) { + output = output.concat( + splitParagraphIntoLines.apply(this, [ + paragraphs[i], + fontUnit_maxLen, + newOptions + ]) + ); + } - if (can_draw) - return this.display(itemname, drawopt, true); + return output; + }; +})(jsPDF.API); + +/** @license + jsPDF standard_fonts_metrics plugin + * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems + * MIT license. + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - if (can_expand || dflt_expand) - return this.expandItem(itemname, d3cont); +/** + * This file adds the standard font metrics to jsPDF. + * + * Font metrics data is reprocessed derivative of contents of + * "Font Metrics for PDF Core 14 Fonts" package, which exhibits the following copyright and license: + * + * Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved. + * + * This file and the 14 PostScript(R) AFM files it accompanies may be used, + * copied, and distributed for any purpose and without charge, with or without + * modification, provided that all copyright notices are retained; that the AFM + * files are not distributed without this file; that all modifications to this + * file or any of the AFM files are prominently noted in the modified file(s); + * and that this paragraph is not modified. Adobe Systems has no responsibility + * or obligation to support the use of the AFM files. + * + * @name standard_fonts_metrics + * @module + */ - // cannot draw, but can inspect ROOT objects - if (isStr(hitem._kind) && (hitem._kind.indexOf(prROOT) === 0) && sett.inspect && (can_draw !== false)) - return this.display(itemname, kInspect, true); +(function(API) { + API.__fontmetrics__ = API.__fontmetrics__ || {}; - if (!hitem._childs || (hitem === this.h)) return; - } + var decoded = "0123456789abcdef", + encoded = "klmnopqrstuvwxyz", + mappingUncompress = {}, + mappingCompress = {}; - if (hitem._isopen) - delete hitem._isopen; - else - hitem._isopen = true; + for (var i = 0; i < encoded.length; i++) { + mappingUncompress[encoded[i]] = decoded[i]; + mappingCompress[decoded[i]] = encoded[i]; + } - this.updateTreeNode(hitem, d3cont); - } + var hex = function(value) { + return "0x" + parseInt(value, 10).toString(16); + }; - /** @summary Handler for mouse-over event - * @private */ - tree_mouseover(on, elem) { - const itemname = select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); + var compress = (API.__fontmetrics__.compress = function(data) { + var vals = ["{"]; + var value, keystring, valuestring, numberprefix; - if (!hitem) return; + for (var key in data) { + value = data[key]; - let painter, prnt = hitem; - while (prnt && !painter) { - painter = prnt._painter; - prnt = prnt._parent; + if (!isNaN(parseInt(key, 10))) { + key = parseInt(key, 10); + keystring = hex(key).slice(2); + keystring = + keystring.slice(0, -1) + mappingCompress[keystring.slice(-1)]; + } else { + keystring = "'" + key + "'"; } - if (isFunc(painter?.mouseOverHierarchy)) - painter.mouseOverHierarchy(on, itemname, hitem); - } + if (typeof value == "number") { + if (value < 0) { + valuestring = hex(value).slice(3); + numberprefix = "-"; + } else { + valuestring = hex(value).slice(2); + numberprefix = ""; + } + valuestring = + numberprefix + + valuestring.slice(0, -1) + + mappingCompress[valuestring.slice(-1)]; + } else { + if (typeof value === "object") { + valuestring = compress(value); + } else { + throw new Error( + "Don't know what to do with value type " + typeof value + "." + ); + } + } + vals.push(keystring + valuestring); + } + vals.push("}"); + return vals.join(""); + }); - /** @summary alternative context menu, used in the object inspector - * @private */ - direct_contextmenu(evnt, elem) { - evnt.preventDefault(); - const itemname = select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); - if (!hitem) return; + /** + * Uncompresses data compressed into custom, base16-like format. + * + * @public + * @function + * @param + * @returns {Type} + */ + var uncompress = (API.__fontmetrics__.uncompress = function(data) { + if (typeof data !== "string") { + throw new Error("Invalid argument passed to uncompress."); + } - if (isFunc(this.fill_context)) { - createMenu(evnt, this).then(menu => { - this.fill_context(menu, hitem); - if (menu.size() > 0) { - menu.tree_node = elem.parentNode; - menu.show(); - } - }); + var output = {}, + sign = 1, + stringparts, // undef. will be [] in string mode + activeobject = output, + parentchain = [], + parent_key_pair, + keyparts = "", + valueparts = "", + key, // undef. will be Truthy when Key is resolved. + datalen = data.length - 1, // stripping ending } + ch; + + for (var i = 1; i < datalen; i += 1) { + // - { } ' are special. + + ch = data[i]; + + if (ch == "'") { + if (stringparts) { + // end of string mode + key = stringparts.join(""); + stringparts = undefined; + } else { + // start of string mode + stringparts = []; + } + } else if (stringparts) { + stringparts.push(ch); + } else if (ch == "{") { + // start of object + parentchain.push([activeobject, key]); + activeobject = {}; + key = undefined; + } else if (ch == "}") { + // end of object + parent_key_pair = parentchain.pop(); + parent_key_pair[0][parent_key_pair[1]] = activeobject; + key = undefined; + activeobject = parent_key_pair[0]; + } else if (ch == "-") { + sign = -1; + } else { + // must be number + if (key === undefined) { + if (mappingUncompress.hasOwnProperty(ch)) { + keyparts += mappingUncompress[ch]; + key = parseInt(keyparts, 16) * sign; + sign = 1; + keyparts = ""; + } else { + keyparts += ch; + } + } else { + if (mappingUncompress.hasOwnProperty(ch)) { + valueparts += mappingUncompress[ch]; + activeobject[key] = parseInt(valueparts, 16) * sign; + sign = 1; + key = undefined; + valueparts = ""; + } else { + valueparts += ch; + } + } } - } + } + return output; + }); - /** @summary Fills settings menu items - * @private */ - fillSettingsMenu(menu, alone) { - menu.addSettingsMenu(true, alone, arg => { - if (arg === 'refresh') { - this.forEachRootFile(folder => keysHierarchy(folder, folder._file.fKeys, folder._file, '')); - this.refreshHtml(); - } else if (arg === 'dark') - this.changeDarkMode(); - else if (arg === 'width') - this.brlayout?.adjustSeparators(settings.BrowserWidth, null); - }); - } + // encoding = 'Unicode' + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE. NO clever BOM behavior + // Actual 16bit char codes used. + // no multi-byte logic here + + // Unicode characters to WinAnsiEncoding: + // {402: 131, 8211: 150, 8212: 151, 8216: 145, 8217: 146, 8218: 130, 8220: 147, 8221: 148, 8222: 132, 8224: 134, 8225: 135, 8226: 149, 8230: 133, 8364: 128, 8240:137, 8249: 139, 8250: 155, 710: 136, 8482: 153, 338: 140, 339: 156, 732: 152, 352: 138, 353: 154, 376: 159, 381: 142, 382: 158} + // as you can see, all Unicode chars are outside of 0-255 range. No char code conflicts. + // this means that you can give Win cp1252 encoded strings to jsPDF for rendering directly + // as well as give strings with some (supported by these fonts) Unicode characters and + // these will be mapped to win cp1252 + // for example, you can send char code (cp1252) 0x80 or (unicode) 0x20AC, getting "Euro" glyph displayed in both cases. + + var encodingBlock = { + codePages: ["WinAnsiEncoding"], + WinAnsiEncoding: uncompress( + "{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}" + ) + }; + var encodings = { + Unicode: { + Courier: encodingBlock, + "Courier-Bold": encodingBlock, + "Courier-BoldOblique": encodingBlock, + "Courier-Oblique": encodingBlock, + Helvetica: encodingBlock, + "Helvetica-Bold": encodingBlock, + "Helvetica-BoldOblique": encodingBlock, + "Helvetica-Oblique": encodingBlock, + "Times-Roman": encodingBlock, + "Times-Bold": encodingBlock, + "Times-BoldItalic": encodingBlock, + "Times-Italic": encodingBlock + // , 'Symbol' + // , 'ZapfDingbats' + } + }; - /** @summary Handle changes of dark mode - * @private */ - changeDarkMode() { - if (this.textcolor) { - this.setBasicColors(); - this.refreshHtml(); + var fontMetrics = { + Unicode: { + // all sizing numbers are n/fontMetricsFractionOf = one font size unit + // this means that if fontMetricsFractionOf = 1000, and letter A's width is 476, it's + // width is 476/1000 or 47.6% of its height (regardless of font size) + // At this time this value applies to "widths" and "kerning" numbers. + + // char code 0 represents "default" (average) width - use it for chars missing in this table. + // key 'fof' represents the "fontMetricsFractionOf" value + + "Courier-Oblique": uncompress( + "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" + ), + "Times-BoldItalic": uncompress( + "{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}" + ), + "Helvetica-Bold": uncompress( + "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" + ), + Courier: uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), + "Courier-BoldOblique": uncompress( + "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" + ), + "Times-Bold": uncompress( + "{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}" + ), + Symbol: uncompress( + "{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}" + ), + Helvetica: uncompress( + "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" + ), + "Helvetica-BoldOblique": uncompress( + "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" + ), + ZapfDingbats: uncompress("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"), + "Courier-Bold": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), + "Times-Italic": uncompress( + "{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}" + ), + "Times-Roman": uncompress( + "{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}" + ), + "Helvetica-Oblique": uncompress( + "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" + ) + } + }; + + /* + This event handler is fired when a new jsPDF object is initialized + This event handler appends metrics data to standard fonts within + that jsPDF instance. The metrics are mapped over Unicode character + codes, NOT CIDs or other codes matching the StandardEncoding table of the + standard PDF fonts. + Future: + Also included is the encoding maping table, converting Unicode (UCS-2, UTF-16) + char codes to StandardEncoding character codes. The encoding table is to be used + somewhere around "pdfEscape" call. + */ + API.events.push([ + "addFont", + function(data) { + var font = data.font; + + var metrics = fontMetrics["Unicode"][font.postScriptName]; + if (metrics) { + font.metadata["Unicode"] = {}; + font.metadata["Unicode"].widths = metrics.widths; + font.metadata["Unicode"].kerning = metrics.kerning; + } + + var encodingBlock = encodings["Unicode"][font.postScriptName]; + if (encodingBlock) { + font.metadata["Unicode"].encoding = encodingBlock; + font.encoding = encodingBlock.codePages[0]; } + } + ]); // end of adding event handler +})(jsPDF.API); - this.brlayout?.createStyle(); - this.createButtons(); // recreate buttons - if (isFunc(this.disp?.changeDarkMode)) - this.disp.changeDarkMode(); - this.disp?.forEachFrame(frame => { - let p = getElementCanvPainter(frame); - if (!p) p = getElementMainPainter(frame); - if (isFunc(p?.changeDarkMode) && (p !== this)) - p.changeDarkMode(); - }); - } +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - /** @summary Toggle dark mode - * @private */ - toggleDarkMode() { - settings.DarkMode = !settings.DarkMode; - this.changeDarkMode(); - } +/** + * @name ttfsupport + * @module + */ +(function(jsPDF) { - /** @summary Handle context menu in the hieararchy - * @private */ - tree_contextmenu(evnt, elem) { - evnt.preventDefault(); - const itemname = select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); - if (!hitem) return; + var binaryStringToUint8Array = function(binary_string) { + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }; - const onlineprop = this.getOnlineProp(itemname), - fileprop = this.getFileProp(itemname); + var addFont = function(font, file) { + // eslint-disable-next-line no-control-regex + if (/^\x00\x01\x00\x00/.test(file)) { + file = binaryStringToUint8Array(file); + } else { + file = binaryStringToUint8Array(atob$1(file)); + } + font.metadata = jsPDF.API.TTFFont.open(file); + font.metadata.Unicode = font.metadata.Unicode || { + encoding: {}, + kerning: {}, + widths: [] + }; + font.metadata.glyIdsUsed = [0]; + }; - function qualifyURL(url) { - const escapeHTML = s => s.split('&').join('&').split('<').join('<').split('"').join('"'), - el = document.createElement('div'); - el.innerHTML = `x`; - return el.firstChild.href; + jsPDF.API.events.push([ + "addFont", + function(data) { + var file = undefined; + var font = data.font; + var instance = data.instance; + if (font.isStandardFont) { + return; + } + if (typeof instance !== "undefined") { + if (instance.existsFileInVFS(font.postScriptName) === false) { + file = instance.loadFile(font.postScriptName); + } else { + file = instance.getFileFromVFS(font.postScriptName); + } + if (typeof file !== "string") { + throw new Error( + "Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('" + + font.postScriptName + + "')." + ); + } + addFont(font, file); + } else { + throw new Error( + "Font does not exist in vFS, import fonts or remove declaration doc.addFont('" + + font.postScriptName + + "')." + ); } + } + ]); // end of adding event handler +})(jsPDF); - createMenu(evnt, this).then(menu => { - if ((!itemname || !hitem._parent) && !('_jsonfile' in hitem)) { - let addr = '', cnt = 0; - const files = [], separ = () => (cnt++ > 0) ? '&' : '?'; +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - this.forEachRootFile(item => files.push(item._file.fFullURL)); +/** + * jsPDF total_pages plugin + * @name total_pages + * @module + */ +(function(jsPDFAPI) { + /** + * @name putTotalPages + * @function + * @param {string} pageExpression Regular Expression + * @returns {jsPDF} jsPDF-instance + */ - if (!this.getTopOnlineItem()) - addr = exports.source_dir + 'index.htm'; + jsPDFAPI.putTotalPages = function(pageExpression) { - if (this.isMonitoring()) - addr += separ() + 'monitoring=' + this.getMonitoringInterval(); + var replaceExpression; + var totalNumberOfPages = 0; + if (parseInt(this.internal.getFont().id.substr(1), 10) < 15) { + replaceExpression = new RegExp(pageExpression, "g"); + totalNumberOfPages = this.internal.getNumberOfPages(); + } else { + replaceExpression = new RegExp( + this.pdfEscape16(pageExpression, this.internal.getFont()), + "g" + ); + totalNumberOfPages = this.pdfEscape16( + this.internal.getNumberOfPages() + "", + this.internal.getFont() + ); + } - if (files.length === 1) - addr += `${separ()}file=${files[0]}`; - else if (files.length > 1) - addr += `${separ()}files=${JSON.stringify(files)}`; + for (var n = 1; n <= this.internal.getNumberOfPages(); n++) { + for (var i = 0; i < this.internal.pages[n].length; i++) { + this.internal.pages[n][i] = this.internal.pages[n][i].replace( + replaceExpression, + totalNumberOfPages + ); + } + } - if (this.disp_kind) - addr += separ() + 'layout=' + this.disp_kind.replace(/ /g, ''); + return this; + }; +})(jsPDF.API); - const items = [], opts = []; +/** + * @license + * jsPDF viewerPreferences Plugin + * @author Aras Abbasi (github.com/arasabbasi) + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - this.disp?.forEachFrame(frame => { - const dummy = new ObjectPainter(frame); - let top = dummy.getTopPainter(), - item = top ? top.getItemName() : null, opt; +/** + * Adds the ability to set ViewerPreferences and by thus + * controlling the way the document is to be presented on the + * screen or in print. + * @name viewerpreferences + * @module + */ +(function(jsPDFAPI) { + /** + * Set the ViewerPreferences of the generated PDF + * + * @name viewerPreferences + * @function + * @public + * @param {Object} options Array with the ViewerPreferences
    + * Example: doc.viewerPreferences({"FitWindow":true});
    + *
    + * You can set following preferences:
    + *
    + * HideToolbar (boolean)
    + * Default value: false
    + *
    + * HideMenubar (boolean)
    + * Default value: false.
    + *
    + * HideWindowUI (boolean)
    + * Default value: false.
    + *
    + * FitWindow (boolean)
    + * Default value: false.
    + *
    + * CenterWindow (boolean)
    + * Default value: false
    + *
    + * DisplayDocTitle (boolean)
    + * Default value: false.
    + *
    + * NonFullScreenPageMode (string)
    + * Possible values: UseNone, UseOutlines, UseThumbs, UseOC
    + * Default value: UseNone
    + *
    + * Direction (string)
    + * Possible values: L2R, R2L
    + * Default value: L2R.
    + *
    + * ViewArea (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox.
    + *
    + * ViewClip (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox
    + *
    + * PrintArea (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox
    + *
    + * PrintClip (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox.
    + *
    + * PrintScaling (string)
    + * Possible values: AppDefault, None
    + * Default value: AppDefault.
    + *
    + * Duplex (string)
    + * Possible values: Simplex, DuplexFlipLongEdge, DuplexFlipShortEdge + * Default value: none
    + *
    + * PickTrayByPDFSize (boolean)
    + * Default value: false
    + *
    + * PrintPageRange (Array)
    + * Example: [[1,5], [7,9]]
    + * Default value: as defined by PDF viewer application
    + *
    + * NumCopies (Number)
    + * Possible values: 1, 2, 3, 4, 5
    + * Default value: 1
    + *
    + * For more information see the PDF Reference, sixth edition on Page 577 + * @param {boolean} doReset True to reset the settings + * @function + * @returns jsPDF jsPDF-instance + * @example + * var doc = new jsPDF() + * doc.text('This is a test', 10, 10) + * doc.viewerPreferences({'FitWindow': true}, true) + * doc.save("viewerPreferences.pdf") + * + * // Example printing 10 copies, using cropbox, and hiding UI. + * doc.viewerPreferences({ + * 'HideWindowUI': true, + * 'PrintArea': 'CropBox', + * 'NumCopies': 10 + * }) + */ + jsPDFAPI.viewerPreferences = function(options, doReset) { + options = options || {}; + doReset = doReset || false; + + var configuration; + var configurationTemplate = { + HideToolbar: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + HideMenubar: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + HideWindowUI: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + FitWindow: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + CenterWindow: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + DisplayDocTitle: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.4 + }, + NonFullScreenPageMode: { + defaultValue: "UseNone", + value: "UseNone", + type: "name", + explicitSet: false, + valueSet: ["UseNone", "UseOutlines", "UseThumbs", "UseOC"], + pdfVersion: 1.3 + }, + Direction: { + defaultValue: "L2R", + value: "L2R", + type: "name", + explicitSet: false, + valueSet: ["L2R", "R2L"], + pdfVersion: 1.3 + }, + ViewArea: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + ViewClip: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintArea: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintClip: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintScaling: { + defaultValue: "AppDefault", + value: "AppDefault", + type: "name", + explicitSet: false, + valueSet: ["AppDefault", "None"], + pdfVersion: 1.6 + }, + Duplex: { + defaultValue: "", + value: "none", + type: "name", + explicitSet: false, + valueSet: [ + "Simplex", + "DuplexFlipShortEdge", + "DuplexFlipLongEdge", + "none" + ], + pdfVersion: 1.7 + }, + PickTrayByPDFSize: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.7 + }, + PrintPageRange: { + defaultValue: "", + value: "", + type: "array", + explicitSet: false, + valueSet: null, + pdfVersion: 1.7 + }, + NumCopies: { + defaultValue: 1, + value: 1, + type: "integer", + explicitSet: false, + valueSet: null, + pdfVersion: 1.7 + } + }; - if (item) - opt = top.getDrawOpt() || top.getItemDrawOpt(); - else { - top = null; - dummy.forEachPainter(p => { - const _item = p.getItemName(); - if (!_item) return; - let _opt = p.getDrawOpt() || p.getItemDrawOpt() || ''; - if (!top) { - top = p; - item = _item; - opt = _opt; - } else if (top.getPadPainter() === p.getPadPainter()) { - if (_opt.indexOf('same ') === 0) - _opt = _opt.slice(5); - item += '+' + _item; - opt += '+' + _opt; - } - }); - } + var configurationKeys = Object.keys(configurationTemplate); - if (item) { - items.push(item); - opts.push(opt || ''); - } - }); + var rangeArray = []; + var i = 0; + var j = 0; + var k = 0; + var isValid; - if (items.length === 1) - addr += separ() + 'item=' + items[0] + separ() + 'opt=' + opts[0]; - else if (items.length > 1) - addr += separ() + 'items=' + JSON.stringify(items) + separ() + 'opts=' + JSON.stringify(opts); + var method; + var value; + function arrayContainsElement(array, element) { + var iterator; + var result = false; - menu.add('Direct link', () => window.open(addr)); - menu.add('Only items', () => window.open(addr + '&nobrowser')); - this.fillSettingsMenu(menu); - } else if (onlineprop) - this.fillOnlineMenu(menu, onlineprop, itemname); - else { - const sett = getDrawSettings(hitem._kind, 'nosame'); + for (iterator = 0; iterator < array.length; iterator += 1) { + if (array[iterator] === element) { + result = true; + } + } + return result; + } - // allow to draw item even if draw function is not defined - if (hitem._can_draw) { - if (!sett.opts) sett.opts = ['']; - if (sett.opts.indexOf('') < 0) - sett.opts.unshift(''); - } + if (this.internal.viewerpreferences === undefined) { + this.internal.viewerpreferences = {}; + this.internal.viewerpreferences.configuration = JSON.parse( + JSON.stringify(configurationTemplate) + ); + this.internal.viewerpreferences.isSubscribed = false; + } + configuration = this.internal.viewerpreferences.configuration; - if (sett.opts) { - menu.addDrawMenu('Draw', sett.opts, arg => this.display(itemname, arg), - 'Draw item in the new frame'); + if (options === "reset" || doReset === true) { + var len = configurationKeys.length; - const active_frame = this.disp?.getActiveFrame(); + for (k = 0; k < len; k += 1) { + configuration[configurationKeys[k]].value = + configuration[configurationKeys[k]].defaultValue; + configuration[configurationKeys[k]].explicitSet = false; + } + } - if (!sett.noappend && active_frame && (getElementCanvPainter(active_frame) || getElementMainPainter(active_frame))) { - menu.addDrawMenu('Superimpose', sett.opts, arg => this.dropItem(itemname, active_frame, arg), - 'Superimpose item with drawing on active frame'); - } + if (typeof options === "object") { + for (method in options) { + value = options[method]; + if ( + arrayContainsElement(configurationKeys, method) && + value !== undefined + ) { + if ( + configuration[method].type === "boolean" && + typeof value === "boolean" + ) { + configuration[method].value = value; + } else if ( + configuration[method].type === "name" && + arrayContainsElement(configuration[method].valueSet, value) + ) { + configuration[method].value = value; + } else if ( + configuration[method].type === "integer" && + Number.isInteger(value) + ) { + configuration[method].value = value; + } else if (configuration[method].type === "array") { + for (i = 0; i < value.length; i += 1) { + isValid = true; + if (value[i].length === 1 && typeof value[i][0] === "number") { + rangeArray.push(String(value[i] - 1)); + } else if (value[i].length > 1) { + for (j = 0; j < value[i].length; j += 1) { + if (typeof value[i][j] !== "number") { + isValid = false; + } + } + if (isValid === true) { + rangeArray.push([value[i][0] - 1, value[i][1] - 1].join(" ")); + } + } } + configuration[method].value = "[" + rangeArray.join(" ") + "]"; + } else { + configuration[method].value = configuration[method].defaultValue; + } - if (fileprop && sett.opts && !fileprop.localfile) { - const url = settings.NewTabUrl || exports.source_dir; - let filepath = qualifyURL(fileprop.fileurl); - if (filepath.indexOf(url) === 0) - filepath = filepath.slice(url.length); - filepath = `${fileprop.kind}=${filepath}`; - if (fileprop.itemname) { - let name = fileprop.itemname; - if (name.search(/\+| |,/) >= 0) name = `'${name}'`; - filepath += `&item=${name}`; - } - - let arg0 = 'nobrowser'; - if (settings.WithCredentials) - arg0 += '&with_credentials'; - if (settings.NewTabUrlPars) - arg0 += '&' + settings.NewTabUrlPars; - if (settings.NewTabUrlExportSettings) { - if (gStyle.fOptStat !== 1111) - arg0 += `&optstat=${gStyle.fOptStat}`; - if (gStyle.fOptFit !== 0) - arg0 += `&optfit=${gStyle.fOptFit}`; - if (gStyle.fOptDate !== 0) - arg0 += `&optdate=${gStyle.fOptDate}`; - if (gStyle.fOptFile !== 0) - arg0 += `&optfile=${gStyle.fOptFile}`; - if (gStyle.fOptTitle !== 1) - arg0 += `&opttitle=${gStyle.fOptTitle}`; - if (settings.TimeZone === 'UTC') - arg0 += '&utc'; - else if (settings.TimeZone === 'Europe/Berlin') - arg0 += '&cet'; - else if (settings.TimeZone) - arg0 += `&timezone='${settings.TimeZone}'`; - if (Math.abs(gStyle.fDateX - 0.01) > 1e-3) - arg0 += `&datex=${gStyle.fDateX.toFixed(3)}`; - if (Math.abs(gStyle.fDateY - 0.01) > 1e-3) - arg0 += `&datey=${gStyle.fDateY.toFixed(3)}`; - if (gStyle.fHistMinimumZero) - arg0 += '&histzero'; - if (settings.DarkMode) - arg0 += '&dark=on'; - if (!settings.UseStamp) - arg0 += '&usestamp=off'; - if (settings.OnlyLastCycle) - arg0 += '&lastcycle'; - if (settings.OptimizeDraw !== 1) - arg0 += `&optimize=${settings.OptimizeDraw}`; - if (settings.MaxRanges !== 200) - arg0 += `&maxranges=${settings.MaxRanges}`; - if (settings.FuncAsCurve) - arg0 += '&tf1=curve'; - if (!settings.ToolBar && !settings.Tooltip && !settings.ContextMenu && !settings.Zooming && !settings.MoveResize && !settings.DragAndDrop) - arg0 += '&interactive=0'; - else if (!settings.ContextMenu) - arg0 += '&nomenu'; - } + configuration[method].explicitSet = true; + } + } + } - menu.addDrawMenu('Draw in new tab', sett.opts, - arg => window.open(`${url}?${arg0}&${filepath}&opt=${arg}`), - 'Draw item in the new browser tab or window'); + if (this.internal.viewerpreferences.isSubscribed === false) { + this.internal.events.subscribe("putCatalog", function() { + var pdfDict = []; + var vPref; + for (vPref in configuration) { + if (configuration[vPref].explicitSet === true) { + if (configuration[vPref].type === "name") { + pdfDict.push("/" + vPref + " /" + configuration[vPref].value); + } else { + pdfDict.push("/" + vPref + " " + configuration[vPref].value); } + } + } + if (pdfDict.length !== 0) { + this.internal.write( + "/ViewerPreferences\n<<\n" + pdfDict.join("\n") + "\n>>" + ); + } + }); + this.internal.viewerpreferences.isSubscribed = true; + } - if ((sett.expand || sett.get_expand) && (hitem._more || hitem._more === undefined)) { - if (hitem._childs === undefined) - menu.add('Expand', () => this.expandItem(itemname), 'Exapnd content of object'); - else { - menu.add('Unexpand', () => { - hitem._more = true; - delete hitem._childs; - delete hitem._isopen; - if (hitem.expand_item) - delete hitem._expand; - this.updateTreeNode(hitem); - }, 'Remove all childs from hierarchy'); - } - } + this.internal.viewerpreferences.configuration = configuration; + return this; + }; +})(jsPDF.API); - if (hitem._kind === prROOT + clTStyle) - menu.add('Apply', () => this.applyStyle(itemname)); - } +/** ==================================================================== + * @license + * jsPDF XMP metadata plugin + * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ - if (isFunc(hitem._menu)) - hitem._menu(menu, hitem, this); +/** + * @name xmp_metadata + * @module + */ +(function(jsPDFAPI) { + + var postPutResources = function() { + var xmpmeta_beginning = ''; + var rdf_beginning = + ''; + var rdf_ending = ""; + var xmpmeta_ending = ""; + var utf8_xmpmeta_beginning = unescape( + encodeURIComponent(xmpmeta_beginning) + ); + var utf8_rdf_beginning = unescape(encodeURIComponent(rdf_beginning)); + var utf8_metadata = unescape( + encodeURIComponent(this.internal.__metadata__.metadata) + ); + var utf8_rdf_ending = unescape(encodeURIComponent(rdf_ending)); + var utf8_xmpmeta_ending = unescape(encodeURIComponent(xmpmeta_ending)); + + var total_len = + utf8_rdf_beginning.length + + utf8_metadata.length + + utf8_rdf_ending.length + + utf8_xmpmeta_beginning.length + + utf8_xmpmeta_ending.length; + + this.internal.__metadata__.metadata_object_number = this.internal.newObject(); + this.internal.write( + "<< /Type /Metadata /Subtype /XML /Length " + total_len + " >>" + ); + this.internal.write("stream"); + this.internal.write( + utf8_xmpmeta_beginning + + utf8_rdf_beginning + + utf8_metadata + + utf8_rdf_ending + + utf8_xmpmeta_ending + ); + this.internal.write("endstream"); + this.internal.write("endobj"); + }; - if (menu.size() > 0) { - menu.tree_node = elem.parentNode; - if (menu.separ) menu.add('separator'); // add separator at the end - menu.add('Close'); - menu.show(); - } - }); // end menu creation + var putCatalog = function() { + if (this.internal.__metadata__.metadata_object_number) { + this.internal.write( + "/Metadata " + + this.internal.__metadata__.metadata_object_number + + " 0 R" + ); + } + }; - return false; - } + /** + * Adds XMP formatted metadata to PDF + * + * @name addMetadata + * @function + * @param {String} metadata The actual metadata to be added. The metadata shall be stored as XMP simple value. Note that if the metadata string contains XML markup characters "<", ">" or "&", those characters should be written using XML entities. + * @param {String} namespaceuri Sets the namespace URI for the metadata. Last character should be slash or hash. + * @returns {jsPDF} jsPDF-instance + */ + jsPDFAPI.addMetadata = function(metadata, namespaceuri) { + if (typeof this.internal.__metadata__ === "undefined") { + this.internal.__metadata__ = { + metadata: metadata, + namespaceuri: namespaceuri || "http://jspdf.default.namespaceuri/" + }; + this.internal.events.subscribe("putCatalog", putCatalog); - /** @summary Starts player for specified item - * @desc Same as 'Player' context menu - * @param {string} itemname - item name for which player should be started - * @param {string} [option] - extra options for the player - * @return {Promise} when ready */ - async player(itemname, option) { - const item = this.findItem(itemname); + this.internal.events.subscribe("postPutResources", postPutResources); + } + return this; + }; +})(jsPDF.API); - if (!item || !item._player || !isStr(item._player)) - return null; +/** + * @name utf8 + * @module + */ +(function(jsPDF) { + var jsPDFAPI = jsPDF.API; + + /***************************************************************************************************/ + /* function : pdfEscape16 */ + /* comment : The character id of a 2-byte string is converted to a hexadecimal number by obtaining */ + /* the corresponding glyph id and width, and then adding padding to the string. */ + /***************************************************************************************************/ + var pdfEscape16 = (jsPDFAPI.pdfEscape16 = function(text, font) { + var widths = font.metadata.Unicode.widths; + var padz = ["", "0", "00", "000", "0000"]; + var ar = [""]; + for (var i = 0, l = text.length, t; i < l; ++i) { + t = font.metadata.characterToGlyph(text.charCodeAt(i)); + font.metadata.glyIdsUsed.push(t); + font.metadata.toUnicode[t] = text.charCodeAt(i); + if (widths.indexOf(t) == -1) { + widths.push(t); + widths.push([parseInt(font.metadata.widthOfGlyph(t), 10)]); + } + if (t == "0") { + //Spaces are not allowed in cmap. + return ar.join(""); + } else { + t = t.toString(16); + ar.push(padz[4 - t.length], t); + } + } + return ar.join(""); + }); - let player_func = null; + var toUnicodeCmap = function(map) { + var code, codes, range, unicode, unicodeMap, _i, _len; + unicodeMap = + "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000>\nendcodespacerange"; + codes = Object.keys(map).sort(function(a, b) { + return a - b; + }); - if (item._module) { - const hh = await this.importModule(item._module); - player_func = hh ? hh[item._player] : null; - } else { - if (item._prereq || (item._player.indexOf('JSROOT.') >= 0)) - await this.loadScripts('', item._prereq); - player_func = findFunction(item._player); + range = []; + for (_i = 0, _len = codes.length; _i < _len; _i++) { + code = codes[_i]; + if (range.length >= 100) { + unicodeMap += + "\n" + + range.length + + " beginbfchar\n" + + range.join("\n") + + "\nendbfchar"; + range = []; + } + + if ( + map[code] !== undefined && + map[code] !== null && + typeof map[code].toString === "function" + ) { + unicode = ("0000" + map[code].toString(16)).slice(-4); + code = ("0000" + (+code).toString(16)).slice(-4); + range.push("<" + code + "><" + unicode + ">"); } + } - if (!isFunc(player_func)) - return null; + if (range.length) { + unicodeMap += + "\n" + + range.length + + " beginbfchar\n" + + range.join("\n") + + "\nendbfchar\n"; + } + unicodeMap += + "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"; + return unicodeMap; + }; - await this.createDisplay(); - return player_func(this, itemname, option); - } + var identityHFunction = function(options) { + var font = options.font; + var out = options.out; + var newObject = options.newObject; + var putStream = options.putStream; + + if ( + font.metadata instanceof jsPDF.API.TTFFont && + font.encoding === "Identity-H" + ) { + //Tag with Identity-H + var widths = font.metadata.Unicode.widths; + var data = font.metadata.subset.encode(font.metadata.glyIdsUsed, 1); + var pdfOutput = data; + var pdfOutput2 = ""; + for (var i = 0; i < pdfOutput.length; i++) { + pdfOutput2 += String.fromCharCode(pdfOutput[i]); + } + var fontTable = newObject(); + putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); + out("endobj"); + + var cmap = newObject(); + var cmapData = toUnicodeCmap(font.metadata.toUnicode); + putStream({ data: cmapData, addLength1: true, objectId: cmap }); + out("endobj"); + + var fontDescriptor = newObject(); + out("<<"); + out("/Type /FontDescriptor"); + out("/FontName /" + toPDFName(font.fontName)); + out("/FontFile2 " + fontTable + " 0 R"); + out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); + out("/Flags " + font.metadata.flags); + out("/StemV " + font.metadata.stemV); + out("/ItalicAngle " + font.metadata.italicAngle); + out("/Ascent " + font.metadata.ascender); + out("/Descent " + font.metadata.decender); + out("/CapHeight " + font.metadata.capHeight); + out(">>"); + out("endobj"); + + var DescendantFont = newObject(); + out("<<"); + out("/Type /Font"); + out("/BaseFont /" + toPDFName(font.fontName)); + out("/FontDescriptor " + fontDescriptor + " 0 R"); + out("/W " + jsPDF.API.PDFObject.convert(widths)); + out("/CIDToGIDMap /Identity"); + out("/DW 1000"); + out("/Subtype /CIDFontType2"); + out("/CIDSystemInfo"); + out("<<"); + out("/Supplement 0"); + out("/Registry (Adobe)"); + out("/Ordering (" + font.encoding + ")"); + out(">>"); + out(">>"); + out("endobj"); + + font.objectNumber = newObject(); + out("<<"); + out("/Type /Font"); + out("/Subtype /Type0"); + out("/ToUnicode " + cmap + " 0 R"); + out("/BaseFont /" + toPDFName(font.fontName)); + out("/Encoding /" + font.encoding); + out("/DescendantFonts [" + DescendantFont + " 0 R]"); + out(">>"); + out("endobj"); + + font.isAlreadyPutted = true; + } + }; - /** @summary Checks if item can be displayed with given draw option - * @private */ - canDisplay(item, drawopt) { - if (!item) return false; - if (item._player) return true; - if (item._can_draw !== undefined) return item._can_draw; - if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0)) return true; - const handle = getDrawHandle(item._kind, drawopt); - return canDrawHandle(handle); - } + jsPDFAPI.events.push([ + "putFont", + function(args) { + identityHFunction(args); + } + ]); + + var winAnsiEncodingFunction = function(options) { + var font = options.font; + var out = options.out; + var newObject = options.newObject; + var putStream = options.putStream; + + if ( + font.metadata instanceof jsPDF.API.TTFFont && + font.encoding === "WinAnsiEncoding" + ) { + //Tag with WinAnsi encoding + var data = font.metadata.rawData; + var pdfOutput = data; + var pdfOutput2 = ""; + for (var i = 0; i < pdfOutput.length; i++) { + pdfOutput2 += String.fromCharCode(pdfOutput[i]); + } + var fontTable = newObject(); + putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); + out("endobj"); + + var cmap = newObject(); + var cmapData = toUnicodeCmap(font.metadata.toUnicode); + putStream({ data: cmapData, addLength1: true, objectId: cmap }); + out("endobj"); + + var fontDescriptor = newObject(); + out("<<"); + out("/Descent " + font.metadata.decender); + out("/CapHeight " + font.metadata.capHeight); + out("/StemV " + font.metadata.stemV); + out("/Type /FontDescriptor"); + out("/FontFile2 " + fontTable + " 0 R"); + out("/Flags 96"); + out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); + out("/FontName /" + toPDFName(font.fontName)); + out("/ItalicAngle " + font.metadata.italicAngle); + out("/Ascent " + font.metadata.ascender); + out(">>"); + out("endobj"); + font.objectNumber = newObject(); + for (var j = 0; j < font.metadata.hmtx.widths.length; j++) { + font.metadata.hmtx.widths[j] = parseInt( + font.metadata.hmtx.widths[j] * (1000 / font.metadata.head.unitsPerEm) + ); //Change the width of Em units to Point units. + } + out( + "<>" + ); + out("endobj"); + font.isAlreadyPutted = true; + } + }; - /** @summary Returns true if given item displayed - * @param {string} itemname - item name */ - isItemDisplayed(itemname) { - const mdi = this.getDisplay(); - return mdi ? mdi.findFrame(itemname) !== null : false; - } + jsPDFAPI.events.push([ + "putFont", + function(args) { + winAnsiEncodingFunction(args); + } + ]); + + var utf8TextFunction = function(args) { + var text = args.text || ""; + var x = args.x; + var y = args.y; + var options = args.options || {}; + var mutex = args.mutex || {}; + + var pdfEscape = mutex.pdfEscape; + var activeFontKey = mutex.activeFontKey; + var fonts = mutex.fonts; + var key = activeFontKey; + + var str = "", + s = 0, + cmapConfirm; + var strText = ""; + var encoding = fonts[key].encoding; + + if (fonts[key].encoding !== "Identity-H") { + return { + text: text, + x: x, + y: y, + options: options, + mutex: mutex + }; + } + strText = text; - /** @summary Display specified item - * @param {string} itemname - item name - * @param {string} [drawopt] - draw option for the item - * @param {boolean} [interactive] - if display was called in interactive mode, will activate selected drawing - * @return {Promise} with created painter object */ - async display(itemname, drawopt, interactive) { - const display_itemname = itemname, - marker = '::_display_on_frame_::'; - let painter = null, - updating = false, - item = null, - frame_name = itemname; + key = activeFontKey; + if (Array.isArray(text)) { + strText = text[0]; + } + for (s = 0; s < strText.length; s += 1) { + if (fonts[key].metadata.hasOwnProperty("cmap")) { + cmapConfirm = + fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; + /* + if (Object.prototype.toString.call(text) === '[object Array]') { + var i = 0; + // for (i = 0; i < text.length; i += 1) { + if (Object.prototype.toString.call(text[s]) === '[object Array]') { + cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s][0].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id + } else { + + } + //} - const p = drawopt?.indexOf(marker) ?? -1; - if (p >= 0) { - frame_name = drawopt.slice(p + marker.length); - drawopt = drawopt.slice(0, p); + } else { + cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id + }*/ + } + if (!cmapConfirm) { + if ( + strText[s].charCodeAt(0) < 256 && + fonts[key].metadata.hasOwnProperty("Unicode") + ) { + str += strText[s]; + } else { + str += ""; + } + } else { + str += strText[s]; } + } + var result = ""; + if (parseInt(key.slice(1)) < 14 || encoding === "WinAnsiEncoding") { + //For the default 13 font + result = pdfEscape(str, key) + .split("") + .map(function(cv) { + return cv.charCodeAt(0).toString(16); + }) + .join(""); + } else if (encoding === "Identity-H") { + result = pdfEscape16(str, fonts[key]); + } + mutex.isHex = true; + + return { + text: result, + x: x, + y: y, + options: options, + mutex: mutex + }; + }; - const complete = (respainter, err) => { - if (err) console.log('When display ', itemname, 'got', err); + var utf8EscapeFunction = function(parms) { + var text = parms.text || "", + x = parms.x, + y = parms.y, + options = parms.options, + mutex = parms.mutex; + var tmpText = []; + var args = { + text: text, + x: x, + y: y, + options: options, + mutex: mutex + }; - if (updating && item) delete item._doing_update; - if (!updating) showProgress(); - if (isFunc(respainter?.setItemName)) { - respainter.setItemName(display_itemname, updating ? null : drawopt, this); // mark painter as created from hierarchy - if (item && !item._painter) item._painter = respainter; - } + if (Array.isArray(text)) { + var i = 0; + for (i = 0; i < text.length; i += 1) { + if (Array.isArray(text[i])) { + if (text[i].length === 3) { + tmpText.push([ + utf8TextFunction(Object.assign({}, args, { text: text[i][0] })) + .text, + text[i][1], + text[i][2] + ]); + } else { + tmpText.push( + utf8TextFunction(Object.assign({}, args, { text: text[i] })).text + ); + } + } else { + tmpText.push( + utf8TextFunction(Object.assign({}, args, { text: text[i] })).text + ); + } + } + parms.text = tmpText; + } else { + parms.text = utf8TextFunction( + Object.assign({}, args, { text: text }) + ).text; + } + }; - return respainter || painter; - }; + jsPDFAPI.events.push(["postProcessText", utf8EscapeFunction]); +})(jsPDF); - return this.createDisplay().then(mdi => { - if (!mdi) return complete(); +/** + * @license + * jsPDF virtual FileSystem functionality + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ - item = this.findItem(display_itemname); +/** + * Use the vFS to handle files + * + * @name vFS + * @module + */ +(function(jsPDFAPI) { - if (item && ('_player' in item)) - return this.player(display_itemname, drawopt).then(res => complete(res)); + var _initializeVFS = function() { + if (typeof this.internal.vFS === "undefined") { + this.internal.vFS = {}; + } + return true; + }; + + /** + * Check if the file exists in the vFS + * + * @name existsFileInVFS + * @function + * @param {string} Possible filename in the vFS. + * @returns {boolean} + * @example + * doc.existsFileInVFS("someFile.txt"); + */ + jsPDFAPI.existsFileInVFS = function(filename) { + _initializeVFS.call(this); + return typeof this.internal.vFS[filename] !== "undefined"; + }; - updating = isStr(drawopt) && (drawopt.indexOf('update:') === 0); + /** + * Add a file to the vFS + * + * @name addFileToVFS + * @function + * @param {string} filename The name of the file which should be added. + * @param {string} filecontent The content of the file. + * @returns {jsPDF} + * @example + * doc.addFileToVFS("someFile.txt", "BADFACE1"); + */ + jsPDFAPI.addFileToVFS = function(filename, filecontent) { + _initializeVFS.call(this); + this.internal.vFS[filename] = filecontent; + return this; + }; - if (updating) { - drawopt = drawopt.slice(7); - if (!item || item._doing_update) return complete(); - item._doing_update = true; - } + /** + * Get the file from the vFS + * + * @name getFileFromVFS + * @function + * @param {string} The name of the file which gets requested. + * @returns {string} + * @example + * doc.getFileFromVFS("someFile.txt"); + */ + jsPDFAPI.getFileFromVFS = function(filename) { + _initializeVFS.call(this); - if (item && !this.canDisplay(item, drawopt)) return complete(); + if (typeof this.internal.vFS[filename] !== "undefined") { + return this.internal.vFS[filename]; + } + return null; + }; +})(jsPDF.API); - let divid = '', use_dflt_opt = false; - if (isStr(drawopt) && (drawopt.indexOf('divid:') >= 0)) { - const pos = drawopt.indexOf('divid:'); - divid = drawopt.slice(pos+6); - drawopt = drawopt.slice(0, pos); - } +/** + * @license + * Unicode Bidi Engine based on the work of Alex Shensis (@asthensis) + * MIT License + */ - if (drawopt === '__default_draw_option__') { - use_dflt_opt = true; - drawopt = ''; - } +(function(jsPDF) { + /** + * Table of Unicode types. + * + * Generated by: + * + * var bidi = require("./bidi/index"); + * var bidi_accumulate = bidi.slice(0, 256).concat(bidi.slice(0x0500, 0x0500 + 256 * 3)). + * concat(bidi.slice(0x2000, 0x2000 + 256)).concat(bidi.slice(0xFB00, 0xFB00 + 256)). + * concat(bidi.slice(0xFE00, 0xFE00 + 2 * 256)); + * + * for( var i = 0; i < bidi_accumulate.length; i++) { + * if(bidi_accumulate[i] === undefined || bidi_accumulate[i] === 'ON') + * bidi_accumulate[i] = 'N'; //mark as neutral to conserve space and substitute undefined + * } + * var bidiAccumulateStr = 'return [ "' + bidi_accumulate.toString().replace(/,/g, '", "') + '" ];'; + * require("fs").writeFile('unicode-types.js', bidiAccumulateStr); + * + * Based on: + * https://github.com/mathiasbynens/unicode-8.0.0 + */ + var bidiUnicodeTypes = [ + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "S", + "B", + "S", + "WS", + "B", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "B", + "B", + "B", + "S", + "WS", + "N", + "N", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "ES", + "CS", + "ES", + "CS", + "CS", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "B", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "CS", + "N", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "L", + "N", + "N", + "BN", + "N", + "N", + "ET", + "ET", + "EN", + "EN", + "N", + "L", + "N", + "N", + "N", + "EN", + "L", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "N", + "N", + "N", + "N", + "N", + "ET", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "R", + "NSM", + "R", + "NSM", + "NSM", + "R", + "NSM", + "NSM", + "R", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "N", + "N", + "AL", + "ET", + "ET", + "AL", + "CS", + "AL", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "ET", + "AN", + "AN", + "AL", + "AL", + "AL", + "NSM", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AN", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "NSM", + "NSM", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "AL", + "AL", + "NSM", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "R", + "R", + "N", + "N", + "N", + "N", + "R", + "N", + "N", + "N", + "N", + "N", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "BN", + "BN", + "BN", + "L", + "R", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "WS", + "B", + "LRE", + "RLE", + "PDF", + "LRO", + "RLO", + "CS", + "ET", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "WS", + "BN", + "BN", + "BN", + "BN", + "BN", + "N", + "LRI", + "RLI", + "FSI", + "PDI", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "EN", + "L", + "N", + "N", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "ES", + "ES", + "N", + "N", + "N", + "L", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "ES", + "ES", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "R", + "NSM", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "ES", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "N", + "R", + "R", + "R", + "R", + "R", + "N", + "R", + "N", + "R", + "R", + "N", + "R", + "R", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "CS", + "N", + "CS", + "N", + "N", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "ET", + "N", + "N", + "ES", + "ES", + "N", + "N", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "N", + "BN", + "N", + "N", + "N", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "ES", + "CS", + "ES", + "CS", + "CS", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N" + ]; - if (!updating) showProgress(`Loading ${display_itemname} ...`); + /** + * Unicode Bidi algorithm compliant Bidi engine. + * For reference see http://unicode.org/reports/tr9/ + */ - return this.getObject(display_itemname, drawopt).then(result => { - if (!updating) showProgress(); + /** + * constructor ( options ) + * + * Initializes Bidi engine + * + * @param {Object} See 'setOptions' below for detailed description. + * options are cashed between invocation of 'doBidiReorder' method + * + * sample usage pattern of BidiEngine: + * var opt = { + * isInputVisual: true, + * isInputRtl: false, + * isOutputVisual: false, + * isOutputRtl: false, + * isSymmetricSwapping: true + * } + * var sourceToTarget = [], levels = []; + * var bidiEng = Globalize.bidiEngine(opt); + * var src = "text string to be reordered"; + * var ret = bidiEng.doBidiReorder(src, sourceToTarget, levels); + */ - if (!item) item = result.item; - let obj = result.obj; + jsPDF.__bidiEngine__ = jsPDF.prototype.__bidiEngine__ = function(options) { + var _UNICODE_TYPES = _bidiUnicodeTypes; - if (!obj) return complete(); + var _STATE_TABLE_LTR = [ + [0, 3, 0, 1, 0, 0, 0], + [0, 3, 0, 1, 2, 2, 0], + [0, 3, 0, 0x11, 2, 0, 1], + [0, 3, 5, 5, 4, 1, 0], + [0, 3, 0x15, 0x15, 4, 0, 1], + [0, 3, 5, 5, 4, 2, 0] + ]; - if (!updating) showProgress(`Drawing ${display_itemname} ...`); + var _STATE_TABLE_RTL = [ + [2, 0, 1, 1, 0, 1, 0], + [2, 0, 1, 1, 0, 2, 0], + [2, 0, 2, 1, 3, 2, 0], + [2, 0, 2, 0x21, 3, 1, 1] + ]; - let handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; + var _TYPE_NAMES_MAP = { L: 0, R: 1, EN: 2, AN: 3, N: 4, B: 5, S: 6 }; + + var _UNICODE_RANGES_MAP = { + 0: 0, + 5: 1, + 6: 2, + 7: 3, + 0x20: 4, + 0xfb: 5, + 0xfe: 6, + 0xff: 7 + }; - if (handle?.draw_field && obj[handle.draw_field]) { - obj = obj[handle.draw_field]; - if (!drawopt) drawopt = handle.draw_field_opt || ''; - handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; - } + var _SWAP_TABLE = [ + "\u0028", + "\u0029", + "\u0028", + "\u003C", + "\u003E", + "\u003C", + "\u005B", + "\u005D", + "\u005B", + "\u007B", + "\u007D", + "\u007B", + "\u00AB", + "\u00BB", + "\u00AB", + "\u2039", + "\u203A", + "\u2039", + "\u2045", + "\u2046", + "\u2045", + "\u207D", + "\u207E", + "\u207D", + "\u208D", + "\u208E", + "\u208D", + "\u2264", + "\u2265", + "\u2264", + "\u2329", + "\u232A", + "\u2329", + "\uFE59", + "\uFE5A", + "\uFE59", + "\uFE5B", + "\uFE5C", + "\uFE5B", + "\uFE5D", + "\uFE5E", + "\uFE5D", + "\uFE64", + "\uFE65", + "\uFE64" + ]; - if (use_dflt_opt && !drawopt && handle?.dflt && (handle.dflt !== 'expand')) - drawopt = handle.dflt; + var _LTR_RANGES_REG_EXPR = new RegExp( + /^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/ + ); - if (divid) { - const func = updating ? redraw : draw; - return func(divid, obj, drawopt).then(p => complete(p)).catch(err => complete(null, err)); - } + var _lastArabic = false, + _hasUbatB, + _hasUbatS, + DIR_LTR = 0, + DIR_RTL = 1, + _isInVisual, + _isInRtl, + _isOutVisual, + _isOutRtl, + _isSymmetricSwapping, + _dir = DIR_LTR; + + this.__bidiEngine__ = {}; + + var _init = function(text, sourceToTargetMap) { + if (sourceToTargetMap) { + for (var i = 0; i < text.length; i++) { + sourceToTargetMap[i] = i; + } + } + if (_isInRtl === undefined) { + _isInRtl = _isContextualDirRtl(text); + } + if (_isOutRtl === undefined) { + _isOutRtl = _isContextualDirRtl(text); + } + }; - let did_actiavte = false; + // for reference see 3.2 in http://unicode.org/reports/tr9/ + // + var _getCharType = function(ch) { + var charCode = ch.charCodeAt(), + range = charCode >> 8, + rangeIdx = _UNICODE_RANGES_MAP[range]; + + if (rangeIdx !== undefined) { + return _UNICODE_TYPES[rangeIdx * 256 + (charCode & 0xff)]; + } else if (range === 0xfc || range === 0xfd) { + return "AL"; + } else if (_LTR_RANGES_REG_EXPR.test(range)) { + //unlikely case + return "L"; + } else if (range === 8) { + // even less likely + return "R"; + } + return "N"; //undefined type, mark as neutral + }; - mdi.forEachPainter((p, frame) => { - if (p.getItemName() !== display_itemname) return; + var _isContextualDirRtl = function(text) { + for (var i = 0, charType; i < text.length; i++) { + charType = _getCharType(text.charAt(i)); + if (charType === "L") { + return false; + } else if (charType === "R") { + return true; + } + } + return false; + }; - const itemopt = p.getItemDrawOpt(); - if (use_dflt_opt && interactive) drawopt = itemopt; + // for reference see 3.3.4 & 3.3.5 in http://unicode.org/reports/tr9/ + // + var _resolveCharType = function(chars, types, resolvedTypes, index) { + var cType = types[index], + wType, + nType, + i, + len; + switch (cType) { + case "L": + case "R": + _lastArabic = false; + break; + case "N": + case "AN": + break; - // verify that object was drawn with same option as specified now (if any) - if (!updating && drawopt && (itemopt !== drawopt)) return; + case "EN": + if (_lastArabic) { + cType = "AN"; + } + break; - if (interactive && !did_actiavte) { - did_actiavte = true; - mdi.activateFrame(frame); - } + case "AL": + _lastArabic = true; + cType = "R"; + break; - if (isFunc(p.redrawObject) && p.redrawObject(obj, drawopt)) painter = p; - }); + case "WS": + cType = "N"; + break; - if (painter) return complete(); + case "CS": + if ( + index < 1 || + index + 1 >= types.length || + ((wType = resolvedTypes[index - 1]) !== "EN" && wType !== "AN") || + ((nType = types[index + 1]) !== "EN" && nType !== "AN") + ) { + cType = "N"; + } else if (_lastArabic) { + nType = "AN"; + } + cType = nType === wType ? nType : "N"; + break; - if (updating) { - console.warn(`something went wrong - did not found painter when doing update of ${display_itemname}`); - return complete(); - } + case "ES": + wType = index > 0 ? resolvedTypes[index - 1] : "B"; + cType = + wType === "EN" && + index + 1 < types.length && + types[index + 1] === "EN" + ? "EN" + : "N"; + break; - const frame = mdi.findFrame(frame_name, true); - cleanup(frame); - mdi.activateFrame(frame); + case "ET": + if (index > 0 && resolvedTypes[index - 1] === "EN") { + cType = "EN"; + break; + } else if (_lastArabic) { + cType = "N"; + break; + } + i = index + 1; + len = types.length; + while (i < len && types[i] === "ET") { + i++; + } + if (i < len && types[i] === "EN") { + cType = "EN"; + } else { + cType = "N"; + } + break; - return draw(frame, obj, drawopt) - .then(p => complete(p)) - .catch(err => complete(null, err)); - }); - }); - } + case "NSM": + if (_isInVisual && !_isInRtl) { + //V->L + len = types.length; + i = index + 1; + while (i < len && types[i] === "NSM") { + i++; + } + if (i < len) { + var c = chars[index]; + var rtlCandidate = (c >= 0x0591 && c <= 0x08ff) || c === 0xfb1e; + wType = types[i]; + if (rtlCandidate && (wType === "R" || wType === "AL")) { + cType = "R"; + break; + } + } + } + if (index < 1 || (wType = types[index - 1]) === "B") { + cType = "N"; + } else { + cType = resolvedTypes[index - 1]; + } + break; - /** @summary Enable drag of the element - * @private */ - enableDrag(d3elem /*, itemname */) { - d3elem.attr('draggable', 'true').on('dragstart', function(ev) { - const itemname = this.parentNode.parentNode.getAttribute('item'); - ev.dataTransfer.setData('item', itemname); - }); - } + case "B": + _lastArabic = false; + _hasUbatB = true; + cType = _dir; + break; - /** @summary Enable drop on the frame - * @private */ - enableDrop(frame) { - const h = this; - select(frame).on('dragover', function(ev) { - const itemname = ev.dataTransfer.getData('item'), - ditem = h.findItem(itemname); - if (isStr(ditem?._kind) && (ditem._kind.indexOf(prROOT) === 0)) - ev.preventDefault(); // let accept drop, otherwise it will be refuced - }).on('dragenter', function() { - select(this).classed('jsroot_drag_area', true); - }).on('dragleave', function() { - select(this).classed('jsroot_drag_area', false); - }).on('drop', function(ev) { - select(this).classed('jsroot_drag_area', false); - const itemname = ev.dataTransfer.getData('item'); - if (itemname) h.dropItem(itemname, this); - }); - } + case "S": + _hasUbatS = true; + cType = "N"; + break; - /** @summary Remove all drop handlers on the frame - * @private */ - clearDrop(frame) { - select(frame).on('dragover', null).on('dragenter', null).on('dragleave', null).on('drop', null); - } + case "LRE": + case "RLE": + case "LRO": + case "RLO": + case "PDF": + _lastArabic = false; + break; + case "BN": + cType = "N"; + break; + } + return cType; + }; - /** @summary Drop item on specified element for drawing - * @return {Promise} when completed - * @private */ - async dropItem(itemname, divid, opt) { - if (!opt || !isStr(opt)) opt = ''; + var _handleUbatS = function(types, levels, length) { + for (var i = 0; i < length; i++) { + if (types[i] === "S") { + levels[i] = _dir; + for (var j = i - 1; j >= 0; j--) { + if (types[j] === "WS") { + levels[j] = _dir; + } else { + break; + } + } + } + } + }; - const drop_complete = (drop_painter, is_main_painter) => { - if (!is_main_painter && isFunc(drop_painter?.setItemName)) - drop_painter.setItemName(itemname, null, this); - return drop_painter; - }; + var _invertString = function(text, sourceToTargetMap, levels) { + var charArray = text.split(""); + if (levels) { + _computeLevels(charArray, levels, { hiLevel: _dir }); + } + charArray.reverse(); + sourceToTargetMap && sourceToTargetMap.reverse(); + return charArray.join(""); + }; - if (itemname === '$legend') { - const cp = getElementCanvPainter(divid); - if (isFunc(cp?.buildLegend)) - return cp.buildLegend(0, 0, 0, 0, '', opt).then(lp => drop_complete(lp)); - console.error('Not possible to build legend'); - return drop_complete(null); + // For reference see 3.3 in http://unicode.org/reports/tr9/ + // + var _computeLevels = function(chars, levels, params) { + var action, + condition, + i, + index, + newLevel, + prevState, + condPos = -1, + len = chars.length, + newState = 0, + resolvedTypes = [], + stateTable = _dir ? _STATE_TABLE_RTL : _STATE_TABLE_LTR, + types = []; + + _lastArabic = false; + _hasUbatB = false; + _hasUbatS = false; + for (i = 0; i < len; i++) { + types[i] = _getCharType(chars[i]); + } + for (index = 0; index < len; index++) { + prevState = newState; + resolvedTypes[index] = _resolveCharType( + chars, + types, + resolvedTypes, + index + ); + newState = stateTable[prevState][_TYPE_NAMES_MAP[resolvedTypes[index]]]; + action = newState & 0xf0; + newState &= 0x0f; + levels[index] = newLevel = stateTable[newState][5]; + if (action > 0) { + if (action === 0x10) { + for (i = condPos; i < index; i++) { + levels[i] = 1; + } + condPos = -1; + } else { + condPos = -1; + } + } + condition = stateTable[newState][6]; + if (condition) { + if (condPos === -1) { + condPos = index; + } + } else { + if (condPos > -1) { + for (i = condPos; i < index; i++) { + levels[i] = newLevel; + } + condPos = -1; + } + } + if (types[index] === "B") { + levels[index] = 0; + } + params.hiLevel |= newLevel; } + if (_hasUbatS) { + _handleUbatS(types, levels, len); + } + }; - return this.getObject(itemname).then(res => { - if (!res.obj) return null; + // for reference see 3.4 in http://unicode.org/reports/tr9/ + // + var _invertByLevel = function( + level, + charArray, + sourceToTargetMap, + levels, + params + ) { + if (params.hiLevel < level) { + return; + } + if (level === 1 && _dir === DIR_RTL && !_hasUbatB) { + charArray.reverse(); + sourceToTargetMap && sourceToTargetMap.reverse(); + return; + } + var ch, + high, + end, + low, + len = charArray.length, + start = 0; + + while (start < len) { + if (levels[start] >= level) { + end = start + 1; + while (end < len && levels[end] >= level) { + end++; + } + for (low = start, high = end - 1; low < high; low++, high--) { + ch = charArray[low]; + charArray[low] = charArray[high]; + charArray[high] = ch; + if (sourceToTargetMap) { + ch = sourceToTargetMap[low]; + sourceToTargetMap[low] = sourceToTargetMap[high]; + sourceToTargetMap[high] = ch; + } + } + start = end; + } + start++; + } + }; - const main_painter = getElementMainPainter(divid); + // for reference see 7 & BD16 in http://unicode.org/reports/tr9/ + // + var _symmetricSwap = function(charArray, levels, params) { + if (params.hiLevel !== 0 && _isSymmetricSwapping) { + for (var i = 0, index; i < charArray.length; i++) { + if (levels[i] === 1) { + index = _SWAP_TABLE.indexOf(charArray[i]); + if (index >= 0) { + charArray[i] = _SWAP_TABLE[index + 1]; + } + } + } + } + }; - if (isFunc(main_painter?.performDrop)) - return main_painter.performDrop(res.obj, itemname, res.item, opt).then(p => drop_complete(p, main_painter === p)); + var _reorder = function(text, sourceToTargetMap, levels) { + var charArray = text.split(""), + params = { hiLevel: _dir }; - if (main_painter?.accept_drops) - return draw(divid, res.obj, 'same ' + opt).then(p => drop_complete(p, main_painter === p)); + if (!levels) { + levels = []; + } + _computeLevels(charArray, levels, params); + _symmetricSwap(charArray, levels, params); + _invertByLevel(DIR_RTL + 1, charArray, sourceToTargetMap, levels, params); + _invertByLevel(DIR_RTL, charArray, sourceToTargetMap, levels, params); + return charArray.join(""); + }; - this.cleanupFrame(divid); - return draw(divid, res.obj, opt).then(p => drop_complete(p)); - }); - } + // doBidiReorder( text, sourceToTargetMap, levels ) + // Performs Bidi reordering by implementing Unicode Bidi algorithm. + // Returns reordered string + // @text [String]: + // - input string to be reordered, this is input parameter + // $sourceToTargetMap [Array] (optional) + // - resultant mapping between input and output strings, this is output parameter + // $levels [Array] (optional) + // - array of calculated Bidi levels, , this is output parameter + this.__bidiEngine__.doBidiReorder = function( + text, + sourceToTargetMap, + levels + ) { + _init(text, sourceToTargetMap); + if (!_isInVisual && _isOutVisual && !_isOutRtl) { + // LLTR->VLTR, LRTL->VLTR + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else if (_isInVisual && _isOutVisual && _isInRtl ^ _isOutRtl) { + // VRTL->VLTR, VLTR->VRTL + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _invertString(text, sourceToTargetMap, levels); + } else if (!_isInVisual && _isOutVisual && _isOutRtl) { + // LLTR->VRTL, LRTL->VRTL + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } else if (_isInVisual && !_isInRtl && !_isOutVisual && !_isOutRtl) { + // VLTR->LLTR + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else if (_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { + // VLTR->LRTL, VRTL->LLTR + text = _invertString(text, sourceToTargetMap); + if (_isInRtl) { + //LLTR -> VLTR + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else { + //LRTL -> VRTL + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } + } else if (_isInVisual && _isInRtl && !_isOutVisual && _isOutRtl) { + // VRTL->LRTL + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } else if (!_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { + // LRTL->LLTR, LLTR->LRTL + var isSymmetricSwappingOrig = _isSymmetricSwapping; + if (_isInRtl) { + //LRTL->LLTR + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + _dir = DIR_LTR; + _isSymmetricSwapping = false; + text = _reorder(text, sourceToTargetMap, levels); + _isSymmetricSwapping = isSymmetricSwappingOrig; + } else { + //LLTR->LRTL + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + _dir = DIR_RTL; + _isSymmetricSwapping = false; + text = _reorder(text, sourceToTargetMap, levels); + _isSymmetricSwapping = isSymmetricSwappingOrig; + text = _invertString(text, sourceToTargetMap); + } + } + return text; + }; - /** @summary Update specified items - * @desc Method can be used to fetch new objects and update all existing drawings - * @param {string|array|boolean} arg - either item name or array of items names to update or true if only automatic items will be updated - * @return {Promise} when ready */ - async updateItems(arg) { - if (!this.disp) - return false; + /** + * @name setOptions( options ) + * @function + * Sets options for Bidi conversion + * @param {Object}: + * - isInputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) + * - isInputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong character of input string) + * - isOutputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) + * - isOutputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong characterof input string) + * - isSymmetricSwapping {boolean} (defaults to false): allowed values true(needs symmetric swapping), false (no need in symmetric swapping), + */ + this.__bidiEngine__.setOptions = function(options) { + if (options) { + _isInVisual = options.isInputVisual; + _isOutVisual = options.isOutputVisual; + _isInRtl = options.isInputRtl; + _isOutRtl = options.isOutputRtl; + _isSymmetricSwapping = options.isSymmetricSwapping; + } + }; - const allitems = [], options = []; - let only_auto_items = false, want_update_all = false; + this.__bidiEngine__.setOptions(options); + return this.__bidiEngine__; + }; - if (isStr(arg)) - arg = [arg]; - else if (!isObject(arg)) { - if (arg === undefined) - arg = !this.isMonitoring(); - want_update_all = true; - only_auto_items = !!arg; + var _bidiUnicodeTypes = bidiUnicodeTypes; + + var bidiEngine = new jsPDF.__bidiEngine__({ isInputVisual: true }); + + var bidiEngineFunction = function(args) { + var text = args.text; + args.x; + args.y; + var options = args.options || {}; + args.mutex || {}; + options.lang; + var tmpText = []; + + options.isInputVisual = + typeof options.isInputVisual === "boolean" ? options.isInputVisual : true; + bidiEngine.setOptions(options); + + if (Object.prototype.toString.call(text) === "[object Array]") { + var i = 0; + tmpText = []; + for (i = 0; i < text.length; i += 1) { + if (Object.prototype.toString.call(text[i]) === "[object Array]") { + tmpText.push([ + bidiEngine.doBidiReorder(text[i][0]), + text[i][1], + text[i][2] + ]); + } else { + tmpText.push([bidiEngine.doBidiReorder(text[i])]); + } } + args.text = tmpText; + } else { + args.text = bidiEngine.doBidiReorder(text); + } + bidiEngine.setOptions({ isInputVisual: true }); + }; - // first collect items - this.disp.forEachPainter(p => { - const itemname = p.getItemName(); - - if (!isStr(itemname) || (allitems.indexOf(itemname) >= 0)) return; + jsPDF.API.events.push(["postProcessText", bidiEngineFunction]); +})(jsPDF); - if (want_update_all) { - const item = this.findItem(itemname); - if (!item || ('_not_monitor' in item) || ('_player' in item)) return; - if (!('_always_monitor' in item)) { - const handle = getDrawHandle(item._kind); - let forced = false; - if (handle?.monitor !== undefined) { - if ((handle.monitor === false) || (handle.monitor === 'never')) return; - if (handle.monitor === 'always') forced = true; - } - if (!forced && only_auto_items) return; - } - } else - if (arg.indexOf(itemname) < 0) return; +/* eslint-disable no-control-regex */ +jsPDF.API.TTFFont = (function() { + /************************************************************************/ + /* function : open */ + /* comment : Decode the encoded ttf content and create a TTFFont object. */ + /************************************************************************/ + TTFFont.open = function(file) { + return new TTFFont(file); + }; + /***************************************************************/ + /* function : TTFFont gernerator */ + /* comment : Decode TTF contents are parsed, Data, */ + /* Subset object is created, and registerTTF function is called.*/ + /***************************************************************/ + function TTFFont(rawData) { + var data; + this.rawData = rawData; + data = this.contents = new Data(rawData); + this.contents.pos = 4; + if (data.readString(4) === "ttcf") { + throw new Error("TTCF not supported."); + } else { + data.pos = 0; + this.parse(); + this.subset = new Subset(this); + this.registerTTF(); + } + } + /********************************************************/ + /* function : parse */ + /* comment : TTF Parses the file contents by each table.*/ + /********************************************************/ + TTFFont.prototype.parse = function() { + this.directory = new Directory(this.contents); + this.head = new HeadTable(this); + this.name = new NameTable(this); + this.cmap = new CmapTable(this); + this.toUnicode = {}; + this.hhea = new HheaTable(this); + this.maxp = new MaxpTable(this); + this.hmtx = new HmtxTable(this); + this.post = new PostTable(this); + this.os2 = new OS2Table(this); + this.loca = new LocaTable(this); + this.glyf = new GlyfTable(this); + this.ascender = + (this.os2.exists && this.os2.ascender) || this.hhea.ascender; + this.decender = + (this.os2.exists && this.os2.decender) || this.hhea.decender; + this.lineGap = (this.os2.exists && this.os2.lineGap) || this.hhea.lineGap; + return (this.bbox = [ + this.head.xMin, + this.head.yMin, + this.head.xMax, + this.head.yMax + ]); + }; + /***************************************************************/ + /* function : registerTTF */ + /* comment : Get the value to assign pdf font descriptors. */ + /***************************************************************/ + TTFFont.prototype.registerTTF = function() { + var e, hi, low, raw, _ref; + this.scaleFactor = 1000.0 / this.head.unitsPerEm; + this.bbox = function() { + var _i, _len, _ref, _results; + _ref = this.bbox; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + e = _ref[_i]; + _results.push(Math.round(e * this.scaleFactor)); + } + return _results; + }.call(this); + this.stemV = 0; + if (this.post.exists) { + raw = this.post.italic_angle; + hi = raw >> 16; + low = raw & 0xff; + if ((hi & 0x8000) !== 0) { + hi = -((hi ^ 0xffff) + 1); + } + this.italicAngle = +("" + hi + "." + low); + } else { + this.italicAngle = 0; + } + this.ascender = Math.round(this.ascender * this.scaleFactor); + this.decender = Math.round(this.decender * this.scaleFactor); + this.lineGap = Math.round(this.lineGap * this.scaleFactor); + this.capHeight = (this.os2.exists && this.os2.capHeight) || this.ascender; + this.xHeight = (this.os2.exists && this.os2.xHeight) || 0; + this.familyClass = ((this.os2.exists && this.os2.familyClass) || 0) >> 8; + this.isSerif = + (_ref = this.familyClass) === 1 || + _ref === 2 || + _ref === 3 || + _ref === 4 || + _ref === 5 || + _ref === 7; + this.isScript = this.familyClass === 10; + this.flags = 0; + if (this.post.isFixedPitch) { + this.flags |= 1 << 0; + } + if (this.isSerif) { + this.flags |= 1 << 1; + } + if (this.isScript) { + this.flags |= 1 << 3; + } + if (this.italicAngle !== 0) { + this.flags |= 1 << 6; + } + this.flags |= 1 << 5; + if (!this.cmap.unicode) { + throw new Error("No unicode cmap for font"); + } + }; + TTFFont.prototype.characterToGlyph = function(character) { + var _ref; + return ( + ((_ref = this.cmap.unicode) != null ? _ref.codeMap[character] : void 0) || + 0 + ); + }; + TTFFont.prototype.widthOfGlyph = function(glyph) { + var scale; + scale = 1000.0 / this.head.unitsPerEm; + return this.hmtx.forGlyph(glyph).advance * scale; + }; + TTFFont.prototype.widthOfString = function(string, size, charSpace) { + var charCode, i, scale, width, _ref; + string = "" + string; + width = 0; + for ( + i = 0, _ref = string.length; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + charCode = string.charCodeAt(i); + width += + this.widthOfGlyph(this.characterToGlyph(charCode)) + + charSpace * (1000 / size) || 0; + } + scale = size / 1000; + return width * scale; + }; + TTFFont.prototype.lineHeight = function(size, includeGap) { + var gap; + if (includeGap == null) { + includeGap = false; + } + gap = includeGap ? this.lineGap : 0; + return ((this.ascender + gap - this.decender) / 1000) * size; + }; + return TTFFont; +})(); - allitems.push(itemname); - options.push('update:' + p.getItemDrawOpt()); - }, true); // only visible panels are considered +/************************************************************************************************/ +/* function : Data */ +/* comment : The ttf data decoded and stored in an array is read and written to the Data object.*/ +/************************************************************************************************/ +var Data = (function() { + function Data(data) { + this.data = data != null ? data : []; + this.pos = 0; + this.length = this.data.length; + } + Data.prototype.readByte = function() { + return this.data[this.pos++]; + }; + Data.prototype.writeByte = function(byte) { + return (this.data[this.pos++] = byte); + }; + Data.prototype.readUInt32 = function() { + var b1, b2, b3, b4; + b1 = this.readByte() * 0x1000000; + b2 = this.readByte() << 16; + b3 = this.readByte() << 8; + b4 = this.readByte(); + return b1 + b2 + b3 + b4; + }; + Data.prototype.writeUInt32 = function(val) { + this.writeByte((val >>> 24) & 0xff); + this.writeByte((val >> 16) & 0xff); + this.writeByte((val >> 8) & 0xff); + return this.writeByte(val & 0xff); + }; + Data.prototype.readInt32 = function() { + var int; + int = this.readUInt32(); + if (int >= 0x80000000) { + return int - 0x100000000; + } else { + return int; + } + }; + Data.prototype.writeInt32 = function(val) { + if (val < 0) { + val += 0x100000000; + } + return this.writeUInt32(val); + }; + Data.prototype.readUInt16 = function() { + var b1, b2; + b1 = this.readByte() << 8; + b2 = this.readByte(); + return b1 | b2; + }; + Data.prototype.writeUInt16 = function(val) { + this.writeByte((val >> 8) & 0xff); + return this.writeByte(val & 0xff); + }; + Data.prototype.readInt16 = function() { + var int; + int = this.readUInt16(); + if (int >= 0x8000) { + return int - 0x10000; + } else { + return int; + } + }; + Data.prototype.writeInt16 = function(val) { + if (val < 0) { + val += 0x10000; + } + return this.writeUInt16(val); + }; + Data.prototype.readString = function(length) { + var i, ret; + ret = []; + for ( + i = 0; + 0 <= length ? i < length : i > length; + i = 0 <= length ? ++i : --i + ) { + ret[i] = String.fromCharCode(this.readByte()); + } + return ret.join(""); + }; + Data.prototype.writeString = function(val) { + var i, _ref, _results; + _results = []; + for ( + i = 0, _ref = val.length; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + _results.push(this.writeByte(val.charCodeAt(i))); + } + return _results; + }; + /*Data.prototype.stringAt = function (pos, length) { + this.pos = pos; + return this.readString(length); + };*/ + Data.prototype.readShort = function() { + return this.readInt16(); + }; + Data.prototype.writeShort = function(val) { + return this.writeInt16(val); + }; + Data.prototype.readLongLong = function() { + var b1, b2, b3, b4, b5, b6, b7, b8; + b1 = this.readByte(); + b2 = this.readByte(); + b3 = this.readByte(); + b4 = this.readByte(); + b5 = this.readByte(); + b6 = this.readByte(); + b7 = this.readByte(); + b8 = this.readByte(); + if (b1 & 0x80) { + return ( + ((b1 ^ 0xff) * 0x100000000000000 + + (b2 ^ 0xff) * 0x1000000000000 + + (b3 ^ 0xff) * 0x10000000000 + + (b4 ^ 0xff) * 0x100000000 + + (b5 ^ 0xff) * 0x1000000 + + (b6 ^ 0xff) * 0x10000 + + (b7 ^ 0xff) * 0x100 + + (b8 ^ 0xff) + + 1) * + -1 + ); + } + return ( + b1 * 0x100000000000000 + + b2 * 0x1000000000000 + + b3 * 0x10000000000 + + b4 * 0x100000000 + + b5 * 0x1000000 + + b6 * 0x10000 + + b7 * 0x100 + + b8 + ); + }; + Data.prototype.writeLongLong = function(val) { + var high, low; + high = Math.floor(val / 0x100000000); + low = val & 0xffffffff; + this.writeByte((high >> 24) & 0xff); + this.writeByte((high >> 16) & 0xff); + this.writeByte((high >> 8) & 0xff); + this.writeByte(high & 0xff); + this.writeByte((low >> 24) & 0xff); + this.writeByte((low >> 16) & 0xff); + this.writeByte((low >> 8) & 0xff); + return this.writeByte(low & 0xff); + }; + Data.prototype.readInt = function() { + return this.readInt32(); + }; + Data.prototype.writeInt = function(val) { + return this.writeInt32(val); + }; + /*Data.prototype.slice = function (start, end) { + return this.data.slice(start, end); + };*/ + Data.prototype.read = function(bytes) { + var buf, i; + buf = []; + for ( + i = 0; + 0 <= bytes ? i < bytes : i > bytes; + i = 0 <= bytes ? ++i : --i + ) { + buf.push(this.readByte()); + } + return buf; + }; + Data.prototype.write = function(bytes) { + var byte, i, _len, _results; + _results = []; + for (i = 0, _len = bytes.length; i < _len; i++) { + byte = bytes[i]; + _results.push(this.writeByte(byte)); + } + return _results; + }; + return Data; +})(); - // force all files to read again (normally in non-browser mode) - if (this.files_monitoring && !only_auto_items && want_update_all) { - this.forEachRootFile(item => { - this.forEachItem(fitem => { delete fitem._readobj; }, item); - delete item._file; - }); +var Directory = (function() { + var checksum; + + /*****************************************************************************************************/ + /* function : Directory generator */ + /* comment : Initialize the offset, tag, length, and checksum for each table for the font to be used.*/ + /*****************************************************************************************************/ + function Directory(data) { + var entry, i, _ref; + this.scalarType = data.readInt(); + this.tableCount = data.readShort(); + this.searchRange = data.readShort(); + this.entrySelector = data.readShort(); + this.rangeShift = data.readShort(); + this.tables = {}; + for ( + i = 0, _ref = this.tableCount; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + entry = { + tag: data.readString(4), + checksum: data.readInt(), + offset: data.readInt(), + length: data.readInt() + }; + this.tables[entry.tag] = entry; + } + } + /********************************************************************************************************/ + /* function : encode */ + /* comment : It encodes and stores the font table object and information used for the directory object. */ + /********************************************************************************************************/ + Directory.prototype.encode = function(tables) { + var adjustment, + directory, + directoryLength, + entrySelector, + headOffset, + log2, + offset, + rangeShift, + searchRange, + sum, + table, + tableCount, + tableData, + tag; + tableCount = Object.keys(tables).length; + log2 = Math.log(2); + searchRange = Math.floor(Math.log(tableCount) / log2) * 16; + entrySelector = Math.floor(searchRange / log2); + rangeShift = tableCount * 16 - searchRange; + directory = new Data(); + directory.writeInt(this.scalarType); + directory.writeShort(tableCount); + directory.writeShort(searchRange); + directory.writeShort(entrySelector); + directory.writeShort(rangeShift); + directoryLength = tableCount * 16; + offset = directory.pos + directoryLength; + headOffset = null; + tableData = []; + for (tag in tables) { + table = tables[tag]; + directory.writeString(tag); + directory.writeInt(checksum(table)); + directory.writeInt(offset); + directory.writeInt(table.length); + tableData = tableData.concat(table); + if (tag === "head") { + headOffset = offset; + } + offset += table.length; + while (offset % 4) { + tableData.push(0); + offset++; } + } + directory.write(tableData); + sum = checksum(directory.data); + adjustment = 0xb1b0afba - sum; + directory.pos = headOffset + 8; + directory.writeUInt32(adjustment); + return directory.data; + }; + /***************************************************************/ + /* function : checksum */ + /* comment : Duplicate the table for the tag. */ + /***************************************************************/ + checksum = function(data) { + var i, sum, tmp, _ref; + data = __slice.call(data); + while (data.length % 4) { + data.push(0); + } + tmp = new Data(data); + sum = 0; + for (i = 0, _ref = data.length; i < _ref; i = i += 4) { + sum += tmp.readUInt32(); + } + return sum & 0xffffffff; + }; + return Directory; +})(); - return this.displayItems(allitems, options); - } +var Table, + __hasProp = {}.hasOwnProperty, + __extends$1 = function(child, parent) { + for (var key in parent) { + if (__hasProp.call(parent, key)) child[key] = parent[key]; + } - /** @summary Display all provided elements - * @return {Promise} when drawing finished - * @private */ - async displayItems(items, options) { - if (!items || (items.length === 0)) - return true; + function ctor() { + this.constructor = child; + } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + child.__super__ = parent.prototype; + return child; + }; - const h = this; +/***************************************************************/ +/* function : Table */ +/* comment : Save info for each table, and parse the table. */ +/***************************************************************/ +Table = (function() { + function Table(file) { + var info; + this.file = file; + info = this.file.directory.tables[this.tag]; + this.exists = !!info; + if (info) { + (this.offset = info.offset), (this.length = info.length); + this.parse(this.file.contents); + } + } + Table.prototype.parse = function() {}; + Table.prototype.encode = function() {}; + Table.prototype.raw = function() { + if (!this.exists) { + return null; + } + this.file.contents.pos = this.offset; + return this.file.contents.read(this.length); + }; + return Table; +})(); - if (!options) options = []; - while (options.length < items.length) - options.push('__default_draw_option__'); +var HeadTable = (function(_super) { + __extends$1(HeadTable, _super); - if ((options.length === 1) && (options[0] === 'iotest')) { - this.clearHierarchy(); - select('#' + this.disp_frameid).html('

    Start I/O test

    '); + function HeadTable() { + return HeadTable.__super__.constructor.apply(this, arguments); + } + HeadTable.prototype.tag = "head"; + HeadTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.revision = data.readInt(); + this.checkSumAdjustment = data.readInt(); + this.magicNumber = data.readInt(); + this.flags = data.readShort(); + this.unitsPerEm = data.readShort(); + this.created = data.readLongLong(); + this.modified = data.readLongLong(); + this.xMin = data.readShort(); + this.yMin = data.readShort(); + this.xMax = data.readShort(); + this.yMax = data.readShort(); + this.macStyle = data.readShort(); + this.lowestRecPPEM = data.readShort(); + this.fontDirectionHint = data.readShort(); + this.indexToLocFormat = data.readShort(); + return (this.glyphDataFormat = data.readShort()); + }; + HeadTable.prototype.encode = function(indexToLocFormat) { + var table; + table = new Data(); + table.writeInt(this.version); + table.writeInt(this.revision); + table.writeInt(this.checkSumAdjustment); + table.writeInt(this.magicNumber); + table.writeShort(this.flags); + table.writeShort(this.unitsPerEm); + table.writeLongLong(this.created); + table.writeLongLong(this.modified); + table.writeShort(this.xMin); + table.writeShort(this.yMin); + table.writeShort(this.xMax); + table.writeShort(this.yMax); + table.writeShort(this.macStyle); + table.writeShort(this.lowestRecPPEM); + table.writeShort(this.fontDirectionHint); + table.writeShort(indexToLocFormat); + table.writeShort(this.glyphDataFormat); + return table.data; + }; + return HeadTable; +})(Table); + +/************************************************************************************/ +/* function : CmapEntry */ +/* comment : Cmap Initializes and encodes object information (required by pdf spec).*/ +/************************************************************************************/ +var CmapEntry = (function() { + function CmapEntry(data, offset) { + var code, + count, + endCode, + glyphId, + glyphIds, + i, + idDelta, + idRangeOffset, + index, + saveOffset, + segCount, + segCountX2, + start, + startCode, + tail, + _j, + _k, + _len; + this.platformID = data.readUInt16(); + this.encodingID = data.readShort(); + this.offset = offset + data.readInt(); + saveOffset = data.pos; + data.pos = this.offset; + this.format = data.readUInt16(); + this.length = data.readUInt16(); + this.language = data.readUInt16(); + this.isUnicode = + (this.platformID === 3 && this.encodingID === 1 && this.format === 4) || + (this.platformID === 0 && this.format === 4) || + (this.platformID === 1 && this.encodingID === 0 && this.format === 0); + this.codeMap = {}; + switch (this.format) { + case 0: + for (i = 0; i < 256; ++i) { + this.codeMap[i] = data.readByte(); + } + break; + case 4: + segCountX2 = data.readUInt16(); + segCount = segCountX2 / 2; + data.pos += 6; + endCode = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + data.pos += 2; + startCode = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + idDelta = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + idRangeOffset = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + count = (this.length - data.pos + this.offset) / 2; + glyphIds = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= count ? _j < count : _j > count; + i = 0 <= count ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + for (i = _j = 0, _len = endCode.length; _j < _len; i = ++_j) { + tail = endCode[i]; + start = startCode[i]; + for ( + code = _k = start; + start <= tail ? _k <= tail : _k >= tail; + code = start <= tail ? ++_k : --_k + ) { + if (idRangeOffset[i] === 0) { + glyphId = code + idDelta[i]; + } else { + index = idRangeOffset[i] / 2 + (code - start) - (segCount - i); + glyphId = glyphIds[index] || 0; + if (glyphId !== 0) { + glyphId += idDelta[i]; + } + } + this.codeMap[code] = glyphId & 0xffff; + } + } + } + data.pos = saveOffset; + } + CmapEntry.encode = function(charmap, encoding) { + var charMap, + code, + codeMap, + codes, + delta, + deltas, + diff, + endCode, + endCodes, + entrySelector, + glyphIDs, + i, + id, + indexes, + last, + map, + nextID, + offset, + old, + rangeOffsets, + rangeShift, + searchRange, + segCount, + segCountX2, + startCode, + startCodes, + startGlyph, + subtable, + _i, + _j, + _k, + _l, + _len, + _len1, + _len2, + _len3, + _len4, + _len5, + _len6, + _len7, + _m, + _n, + _name, + _o, + _p, + _q; + subtable = new Data(); + codes = Object.keys(charmap).sort(function(a, b) { + return a - b; + }); + switch (encoding) { + case "macroman": + id = 0; + indexes = (function() { + var _results = []; + for (i = 0; i < 256; ++i) { + _results.push(0); + } + return _results; + })(); + map = { + 0: 0 + }; + codeMap = {}; + for (_i = 0, _len = codes.length; _i < _len; _i++) { + code = codes[_i]; + if (map[(_name = charmap[code])] == null) { + map[_name] = ++id; + } + codeMap[code] = { + old: charmap[code], + new: map[charmap[code]] + }; + indexes[code] = map[charmap[code]]; + } + subtable.writeUInt16(1); + subtable.writeUInt16(0); + subtable.writeUInt32(12); + subtable.writeUInt16(0); + subtable.writeUInt16(262); + subtable.writeUInt16(0); + subtable.write(indexes); + return { + charMap: codeMap, + subtable: subtable.data, + maxGlyphID: id + 1 + }; + case "unicode": + startCodes = []; + endCodes = []; + nextID = 0; + map = {}; + charMap = {}; + last = diff = null; + for (_j = 0, _len1 = codes.length; _j < _len1; _j++) { + code = codes[_j]; + old = charmap[code]; + if (map[old] == null) { + map[old] = ++nextID; + } + charMap[code] = { + old: old, + new: map[old] + }; + delta = map[old] - code; + if (last == null || delta !== diff) { + if (last) { + endCodes.push(last); + } + startCodes.push(code); + diff = delta; + } + last = code; + } + if (last) { + endCodes.push(last); + } + endCodes.push(0xffff); + startCodes.push(0xffff); + segCount = startCodes.length; + segCountX2 = segCount * 2; + searchRange = 2 * Math.pow(Math.log(segCount) / Math.LN2, 2); + entrySelector = Math.log(searchRange / 2) / Math.LN2; + rangeShift = 2 * segCount - searchRange; + deltas = []; + rangeOffsets = []; + glyphIDs = []; + for (i = _k = 0, _len2 = startCodes.length; _k < _len2; i = ++_k) { + startCode = startCodes[i]; + endCode = endCodes[i]; + if (startCode === 0xffff) { + deltas.push(0); + rangeOffsets.push(0); + break; + } + startGlyph = charMap[startCode]["new"]; + if (startCode - startGlyph >= 0x8000) { + deltas.push(0); + rangeOffsets.push(2 * (glyphIDs.length + segCount - i)); + for ( + code = _l = startCode; + startCode <= endCode ? _l <= endCode : _l >= endCode; + code = startCode <= endCode ? ++_l : --_l + ) { + glyphIDs.push(charMap[code]["new"]); + } + } else { + deltas.push(startGlyph - startCode); + rangeOffsets.push(0); + } + } + subtable.writeUInt16(3); + subtable.writeUInt16(1); + subtable.writeUInt32(12); + subtable.writeUInt16(4); + subtable.writeUInt16(16 + segCount * 8 + glyphIDs.length * 2); + subtable.writeUInt16(0); + subtable.writeUInt16(segCountX2); + subtable.writeUInt16(searchRange); + subtable.writeUInt16(entrySelector); + subtable.writeUInt16(rangeShift); + for (_m = 0, _len3 = endCodes.length; _m < _len3; _m++) { + code = endCodes[_m]; + subtable.writeUInt16(code); + } + subtable.writeUInt16(0); + for (_n = 0, _len4 = startCodes.length; _n < _len4; _n++) { + code = startCodes[_n]; + subtable.writeUInt16(code); + } + for (_o = 0, _len5 = deltas.length; _o < _len5; _o++) { + delta = deltas[_o]; + subtable.writeUInt16(delta); + } + for (_p = 0, _len6 = rangeOffsets.length; _p < _len6; _p++) { + offset = rangeOffsets[_p]; + subtable.writeUInt16(offset); + } + for (_q = 0, _len7 = glyphIDs.length; _q < _len7; _q++) { + id = glyphIDs[_q]; + subtable.writeUInt16(id); + } + return { + charMap: charMap, + subtable: subtable.data, + maxGlyphID: nextID + 1 + }; + } + }; + return CmapEntry; +})(); - const tm0 = new Date(); - return this.getObject(items[0]).then(() => { - const tm1 = new Date(); - select('#' + this.disp_frameid).append('h2').html('Item ' + items[0] + ' reading time = ' + (tm1.getTime() - tm0.getTime()) + 'ms'); - return true; - }); - } +var CmapTable = (function(_super) { + __extends$1(CmapTable, _super); - const dropitems = new Array(items.length), - dropopts = new Array(items.length), - images = new Array(items.length); + function CmapTable() { + return CmapTable.__super__.constructor.apply(this, arguments); + } + CmapTable.prototype.tag = "cmap"; + CmapTable.prototype.parse = function(data) { + var entry, i, tableCount; + data.pos = this.offset; + this.version = data.readUInt16(); + tableCount = data.readUInt16(); + this.tables = []; + this.unicode = null; + for ( + i = 0; + 0 <= tableCount ? i < tableCount : i > tableCount; + i = 0 <= tableCount ? ++i : --i + ) { + entry = new CmapEntry(data, this.offset); + this.tables.push(entry); + if (entry.isUnicode) { + if (this.unicode == null) { + this.unicode = entry; + } + } + } + return true; + }; + /*************************************************************************/ + /* function : encode */ + /* comment : Encode the cmap table corresponding to the input character. */ + /*************************************************************************/ + CmapTable.encode = function(charmap, encoding) { + var result, table; + if (encoding == null) { + encoding = "macroman"; + } + result = CmapEntry.encode(charmap, encoding); + table = new Data(); + table.writeUInt16(0); + table.writeUInt16(1); + result.table = table.data.concat(result.subtable); + return result; + }; + return CmapTable; +})(Table); - // First of all check that items are exists, look for cycle extension and plus sign - for (let i = 0; i < items.length; ++i) { - dropitems[i] = dropopts[i] = null; +var HheaTable = (function(_super) { + __extends$1(HheaTable, _super); - const item = items[i]; - let can_split = true; + function HheaTable() { + return HheaTable.__super__.constructor.apply(this, arguments); + } + HheaTable.prototype.tag = "hhea"; + HheaTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.ascender = data.readShort(); + this.decender = data.readShort(); + this.lineGap = data.readShort(); + this.advanceWidthMax = data.readShort(); + this.minLeftSideBearing = data.readShort(); + this.minRightSideBearing = data.readShort(); + this.xMaxExtent = data.readShort(); + this.caretSlopeRise = data.readShort(); + this.caretSlopeRun = data.readShort(); + this.caretOffset = data.readShort(); + data.pos += 4 * 2; + this.metricDataFormat = data.readShort(); + return (this.numberOfMetrics = data.readUInt16()); + }; + /*HheaTable.prototype.encode = function (ids) { + var i, table, _i, _ref; + table = new Data; + table.writeInt(this.version); + table.writeShort(this.ascender); + table.writeShort(this.decender); + table.writeShort(this.lineGap); + table.writeShort(this.advanceWidthMax); + table.writeShort(this.minLeftSideBearing); + table.writeShort(this.minRightSideBearing); + table.writeShort(this.xMaxExtent); + table.writeShort(this.caretSlopeRise); + table.writeShort(this.caretSlopeRun); + table.writeShort(this.caretOffset); + for (i = _i = 0, _ref = 4 * 2; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + table.writeByte(0); + } + table.writeShort(this.metricDataFormat); + table.writeUInt16(ids.length); + return table.data; + };*/ + return HheaTable; +})(Table); + +var OS2Table = (function(_super) { + __extends$1(OS2Table, _super); + + function OS2Table() { + return OS2Table.__super__.constructor.apply(this, arguments); + } + OS2Table.prototype.tag = "OS/2"; + OS2Table.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readUInt16(); + this.averageCharWidth = data.readShort(); + this.weightClass = data.readUInt16(); + this.widthClass = data.readUInt16(); + this.type = data.readShort(); + this.ySubscriptXSize = data.readShort(); + this.ySubscriptYSize = data.readShort(); + this.ySubscriptXOffset = data.readShort(); + this.ySubscriptYOffset = data.readShort(); + this.ySuperscriptXSize = data.readShort(); + this.ySuperscriptYSize = data.readShort(); + this.ySuperscriptXOffset = data.readShort(); + this.ySuperscriptYOffset = data.readShort(); + this.yStrikeoutSize = data.readShort(); + this.yStrikeoutPosition = data.readShort(); + this.familyClass = data.readShort(); + this.panose = (function() { + var i, _results; + _results = []; + for (i = 0; i < 10; ++i) { + _results.push(data.readByte()); + } + return _results; + })(); + this.charRange = (function() { + var i, _results; + _results = []; + for (i = 0; i < 4; ++i) { + _results.push(data.readInt()); + } + return _results; + })(); + this.vendorID = data.readString(4); + this.selection = data.readShort(); + this.firstCharIndex = data.readShort(); + this.lastCharIndex = data.readShort(); + if (this.version > 0) { + this.ascent = data.readShort(); + this.descent = data.readShort(); + this.lineGap = data.readShort(); + this.winAscent = data.readShort(); + this.winDescent = data.readShort(); + this.codePageRange = (function() { + var i, _results; + _results = []; + for (i = 0; i < 2; i = ++i) { + _results.push(data.readInt()); + } + return _results; + })(); + if (this.version > 1) { + this.xHeight = data.readShort(); + this.capHeight = data.readShort(); + this.defaultChar = data.readShort(); + this.breakChar = data.readShort(); + return (this.maxContext = data.readShort()); + } + } + }; + /*OS2Table.prototype.encode = function () { + return this.raw(); + };*/ + return OS2Table; +})(Table); - if (item?.indexOf('img:') === 0) { images[i] = true; continue; } +var PostTable = (function(_super) { + __extends$1(PostTable, _super); - if ((item?.length > 1) && (item[0] === '\'') && (item[item.length - 1] === '\'')) { - items[i] = item.slice(1, item.length-1); - can_split = false; - } + function PostTable() { + return PostTable.__super__.constructor.apply(this, arguments); + } + PostTable.prototype.tag = "post"; + PostTable.prototype.parse = function(data) { + var length, numberOfGlyphs, _results; + data.pos = this.offset; + this.format = data.readInt(); + this.italicAngle = data.readInt(); + this.underlinePosition = data.readShort(); + this.underlineThickness = data.readShort(); + this.isFixedPitch = data.readInt(); + this.minMemType42 = data.readInt(); + this.maxMemType42 = data.readInt(); + this.minMemType1 = data.readInt(); + this.maxMemType1 = data.readInt(); + switch (this.format) { + case 0x00010000: + break; + case 0x00020000: + numberOfGlyphs = data.readUInt16(); + this.glyphNameIndex = []; + var i; + for ( + i = 0; + 0 <= numberOfGlyphs ? i < numberOfGlyphs : i > numberOfGlyphs; + i = 0 <= numberOfGlyphs ? ++i : --i + ) { + this.glyphNameIndex.push(data.readUInt16()); + } + this.names = []; + _results = []; + while (data.pos < this.offset + this.length) { + length = data.readByte(); + _results.push(this.names.push(data.readString(length))); + } + return _results; + case 0x00025000: + numberOfGlyphs = data.readUInt16(); + return (this.offsets = data.read(numberOfGlyphs)); + case 0x00030000: + break; + case 0x00040000: + return (this.map = function() { + var _j, _ref, _results1; + _results1 = []; + for ( + i = _j = 0, _ref = this.file.maxp.numGlyphs; + 0 <= _ref ? _j < _ref : _j > _ref; + i = 0 <= _ref ? ++_j : --_j + ) { + _results1.push(data.readUInt32()); + } + return _results1; + }.call(this)); + } + }; + return PostTable; +})(Table); + +/*********************************************************************************************************/ +/* function : NameEntry */ +/* comment : Store copyright information, platformID, encodingID, and languageID in the NameEntry object.*/ +/*********************************************************************************************************/ +var NameEntry = (function() { + function NameEntry(raw, entry) { + this.raw = raw; + this.length = raw.length; + this.platformID = entry.platformID; + this.encodingID = entry.encodingID; + this.languageID = entry.languageID; + } + return NameEntry; +})(); - let elem = h.findItem({ name: items[i], check_keys: true }); - if (elem) { items[i] = h.itemFullName(elem); continue; } +var NameTable = (function(_super) { + __extends$1(NameTable, _super); - if (can_split && (items[i][0] === '[') && (items[i][items[i].length - 1] === ']')) { - dropitems[i] = parseAsArray(items[i]); - items[i] = dropitems[i].shift(); - } else if (can_split && (items[i].indexOf('+') > 0)) { - dropitems[i] = items[i].split('+'); - items[i] = dropitems[i].shift(); - } + function NameTable() { + return NameTable.__super__.constructor.apply(this, arguments); + } + NameTable.prototype.tag = "name"; + NameTable.prototype.parse = function(data) { + var count, + entries, + entry, + i, + name, + stringOffset, + strings, + text, + _j, + _len, + _name; + data.pos = this.offset; + data.readShort(); //format + count = data.readShort(); + stringOffset = data.readShort(); + entries = []; + for ( + i = 0; + 0 <= count ? i < count : i > count; + i = 0 <= count ? ++i : --i + ) { + entries.push({ + platformID: data.readShort(), + encodingID: data.readShort(), + languageID: data.readShort(), + nameID: data.readShort(), + length: data.readShort(), + offset: this.offset + stringOffset + data.readShort() + }); + } + strings = {}; + for (i = _j = 0, _len = entries.length; _j < _len; i = ++_j) { + entry = entries[i]; + data.pos = entry.offset; + text = data.readString(entry.length); + name = new NameEntry(text, entry); + if (strings[(_name = entry.nameID)] == null) { + strings[_name] = []; + } + strings[entry.nameID].push(name); + } + this.strings = strings; + this.copyright = strings[0]; + this.fontFamily = strings[1]; + this.fontSubfamily = strings[2]; + this.uniqueSubfamily = strings[3]; + this.fontName = strings[4]; + this.version = strings[5]; + try { + this.postscriptName = strings[6][0].raw.replace( + /[\x00-\x19\x80-\xff]/g, + "" + ); + } catch (e) { + this.postscriptName = strings[4][0].raw.replace( + /[\x00-\x19\x80-\xff]/g, + "" + ); + } + this.trademark = strings[7]; + this.manufacturer = strings[8]; + this.designer = strings[9]; + this.description = strings[10]; + this.vendorUrl = strings[11]; + this.designerUrl = strings[12]; + this.license = strings[13]; + this.licenseUrl = strings[14]; + this.preferredFamily = strings[15]; + this.preferredSubfamily = strings[17]; + this.compatibleFull = strings[18]; + return (this.sampleText = strings[19]); + }; + /*NameTable.prototype.encode = function () { + var id, list, nameID, nameTable, postscriptName, strCount, strTable, string, strings, table, val, _i, _len, _ref; + strings = {}; + _ref = this.strings; + for (id in _ref) { + val = _ref[id]; + strings[id] = val; + } + postscriptName = new NameEntry("" + subsetTag + "+" + this.postscriptName, { + platformID: 1 + , encodingID: 0 + , languageID: 0 + }); + strings[6] = [postscriptName]; + subsetTag = successorOf(subsetTag); + strCount = 0; + for (id in strings) { + list = strings[id]; + if (list != null) { + strCount += list.length; + } + } + table = new Data; + strTable = new Data; + table.writeShort(0); + table.writeShort(strCount); + table.writeShort(6 + 12 * strCount); + for (nameID in strings) { + list = strings[nameID]; + if (list != null) { + for (_i = 0, _len = list.length; _i < _len; _i++) { + string = list[_i]; + table.writeShort(string.platformID); + table.writeShort(string.encodingID); + table.writeShort(string.languageID); + table.writeShort(nameID); + table.writeShort(string.length); + table.writeShort(strTable.pos); + strTable.writeString(string.raw); + } + } + } + return nameTable = { + postscriptName: postscriptName.raw + , table: table.data.concat(strTable.data) + }; + };*/ + return NameTable; +})(Table); - if (dropitems[i] && dropitems[i].length > 0) { - // allow to specify _same_ item in different file - for (let j = 0; j < dropitems[i].length; ++j) { - const pos = dropitems[i][j].indexOf('_same_'); - if ((pos > 0) && (h.findItem(dropitems[i][j]) === null)) - dropitems[i][j] = dropitems[i][j].slice(0, pos) + items[i].slice(pos); +var MaxpTable = (function(_super) { + __extends$1(MaxpTable, _super); - elem = h.findItem({ name: dropitems[i][j], check_keys: true }); - if (elem) dropitems[i][j] = h.itemFullName(elem); + function MaxpTable() { + return MaxpTable.__super__.constructor.apply(this, arguments); + } + MaxpTable.prototype.tag = "maxp"; + MaxpTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.numGlyphs = data.readUInt16(); + this.maxPoints = data.readUInt16(); + this.maxContours = data.readUInt16(); + this.maxCompositePoints = data.readUInt16(); + this.maxComponentContours = data.readUInt16(); + this.maxZones = data.readUInt16(); + this.maxTwilightPoints = data.readUInt16(); + this.maxStorage = data.readUInt16(); + this.maxFunctionDefs = data.readUInt16(); + this.maxInstructionDefs = data.readUInt16(); + this.maxStackElements = data.readUInt16(); + this.maxSizeOfInstructions = data.readUInt16(); + this.maxComponentElements = data.readUInt16(); + return (this.maxComponentDepth = data.readUInt16()); + }; + /*MaxpTable.prototype.encode = function (ids) { + var table; + table = new Data; + table.writeInt(this.version); + table.writeUInt16(ids.length); + table.writeUInt16(this.maxPoints); + table.writeUInt16(this.maxContours); + table.writeUInt16(this.maxCompositePoints); + table.writeUInt16(this.maxComponentContours); + table.writeUInt16(this.maxZones); + table.writeUInt16(this.maxTwilightPoints); + table.writeUInt16(this.maxStorage); + table.writeUInt16(this.maxFunctionDefs); + table.writeUInt16(this.maxInstructionDefs); + table.writeUInt16(this.maxStackElements); + table.writeUInt16(this.maxSizeOfInstructions); + table.writeUInt16(this.maxComponentElements); + table.writeUInt16(this.maxComponentDepth); + return table.data; + };*/ + return MaxpTable; +})(Table); + +var HmtxTable = (function(_super) { + __extends$1(HmtxTable, _super); + + function HmtxTable() { + return HmtxTable.__super__.constructor.apply(this, arguments); + } + HmtxTable.prototype.tag = "hmtx"; + HmtxTable.prototype.parse = function(data) { + var i, last, lsbCount, m, _j, _ref, _results; + data.pos = this.offset; + this.metrics = []; + for ( + i = 0, _ref = this.file.hhea.numberOfMetrics; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + this.metrics.push({ + advance: data.readUInt16(), + lsb: data.readInt16() + }); + } + lsbCount = this.file.maxp.numGlyphs - this.file.hhea.numberOfMetrics; + this.leftSideBearings = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; + i = 0 <= lsbCount ? ++_j : --_j + ) { + _results.push(data.readInt16()); + } + return _results; + })(); + this.widths = function() { + var _j, _len, _ref1, _results; + _ref1 = this.metrics; + _results = []; + for (_j = 0, _len = _ref1.length; _j < _len; _j++) { + m = _ref1[_j]; + _results.push(m.advance); + } + return _results; + }.call(this); + last = this.widths[this.widths.length - 1]; + _results = []; + for ( + i = _j = 0; + 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; + i = 0 <= lsbCount ? ++_j : --_j + ) { + _results.push(this.widths.push(last)); + } + return _results; + }; + /***************************************************************/ + /* function : forGlyph */ + /* comment : Returns the advance width and lsb for this glyph. */ + /***************************************************************/ + HmtxTable.prototype.forGlyph = function(id) { + if (id in this.metrics) { + return this.metrics[id]; + } + return { + advance: this.metrics[this.metrics.length - 1].advance, + lsb: this.leftSideBearings[id - this.metrics.length] + }; + }; + /*HmtxTable.prototype.encode = function (mapping) { + var id, metric, table, _i, _len; + table = new Data; + for (_i = 0, _len = mapping.length; _i < _len; _i++) { + id = mapping[_i]; + metric = this.forGlyph(id); + table.writeUInt16(metric.advance); + table.writeUInt16(metric.lsb); } + return table.data; + };*/ + return HmtxTable; +})(Table); - if ((options[i][0] === '[') && (options[i][options[i].length-1] === ']')) { - dropopts[i] = parseAsArray(options[i]); - options[i] = dropopts[i].shift(); - } else if (options[i].indexOf('+') > 0) { - dropopts[i] = options[i].split('+'); - options[i] = dropopts[i].shift(); - } else - dropopts[i] = []; +var __slice = [].slice; +var GlyfTable = (function(_super) { + __extends$1(GlyfTable, _super); - while (dropopts[i].length < dropitems[i].length) - dropopts[i].push(''); - } + function GlyfTable() { + return GlyfTable.__super__.constructor.apply(this, arguments); + } + GlyfTable.prototype.tag = "glyf"; + GlyfTable.prototype.parse = function() { + return (this.cache = {}); + }; + GlyfTable.prototype.glyphFor = function(id) { + var data, + index, + length, + loca, + numberOfContours, + raw, + xMax, + xMin, + yMax, + yMin; + if (id in this.cache) { + return this.cache[id]; + } + loca = this.file.loca; + data = this.file.contents; + index = loca.indexOf(id); + length = loca.lengthOf(id); + if (length === 0) { + return (this.cache[id] = null); + } + data.pos = this.offset + index; + raw = new Data(data.read(length)); + numberOfContours = raw.readShort(); + xMin = raw.readShort(); + yMin = raw.readShort(); + xMax = raw.readShort(); + yMax = raw.readShort(); + if (numberOfContours === -1) { + this.cache[id] = new CompoundGlyph(raw, xMin, yMin, xMax, yMax); + } else { + this.cache[id] = new SimpleGlyph( + raw, + numberOfContours, + xMin, + yMin, + xMax, + yMax + ); + } + return this.cache[id]; + }; + GlyfTable.prototype.encode = function(glyphs, mapping, old2new) { + var glyph, id, offsets, table, _i, _len; + table = []; + offsets = []; + for (_i = 0, _len = mapping.length; _i < _len; _i++) { + id = mapping[_i]; + glyph = glyphs[id]; + offsets.push(table.length); + if (glyph) { + table = table.concat(glyph.encode(old2new)); + } + } + offsets.push(table.length); + return { + table: table, + offsets: offsets + }; + }; + return GlyfTable; +})(Table); + +var SimpleGlyph = (function() { + /**************************************************************************/ + /* function : SimpleGlyph */ + /* comment : Stores raw, xMin, yMin, xMax, and yMax values for this glyph.*/ + /**************************************************************************/ + function SimpleGlyph(raw, numberOfContours, xMin, yMin, xMax, yMax) { + this.raw = raw; + this.numberOfContours = numberOfContours; + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + this.compound = false; + } + SimpleGlyph.prototype.encode = function() { + return this.raw.data; + }; + return SimpleGlyph; +})(); - // also check if subsequent items has _same_, than use name from first item - const pos = items[i].indexOf('_same_'); - if ((pos > 0) && !h.findItem(items[i]) && (i > 0)) - items[i] = items[i].slice(0, pos) + items[0].slice(pos); +var CompoundGlyph = (function() { + var ARG_1_AND_2_ARE_WORDS, + MORE_COMPONENTS, + WE_HAVE_AN_X_AND_Y_SCALE, + WE_HAVE_A_SCALE, + WE_HAVE_A_TWO_BY_TWO; + ARG_1_AND_2_ARE_WORDS = 0x0001; + WE_HAVE_A_SCALE = 0x0008; + MORE_COMPONENTS = 0x0020; + WE_HAVE_AN_X_AND_Y_SCALE = 0x0040; + WE_HAVE_A_TWO_BY_TWO = 0x0080; + + /********************************************************************************************************************/ + /* function : CompoundGlypg generator */ + /* comment : It stores raw, xMin, yMin, xMax, yMax, glyph id, and glyph offset for the corresponding compound glyph.*/ + /********************************************************************************************************************/ + function CompoundGlyph(raw, xMin, yMin, xMax, yMax) { + var data, flags; + this.raw = raw; + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + this.compound = true; + this.glyphIDs = []; + this.glyphOffsets = []; + data = this.raw; + while (true) { + flags = data.readShort(); + this.glyphOffsets.push(data.pos); + this.glyphIDs.push(data.readUInt16()); + if (!(flags & MORE_COMPONENTS)) { + break; + } + if (flags & ARG_1_AND_2_ARE_WORDS) { + data.pos += 4; + } else { + data.pos += 2; + } + if (flags & WE_HAVE_A_TWO_BY_TWO) { + data.pos += 8; + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + data.pos += 4; + } else if (flags & WE_HAVE_A_SCALE) { + data.pos += 2; + } + } + } + /****************************************************************************************************************/ + /* function : CompoundGlypg encode */ + /* comment : After creating a table for the characters you typed, you call directory.encode to encode the table.*/ + /****************************************************************************************************************/ + CompoundGlyph.prototype.encode = function() { + var i, result, _len, _ref; + result = new Data(__slice.call(this.raw.data)); + _ref = this.glyphIDs; + for (i = 0, _len = _ref.length; i < _len; ++i) { + result.pos = this.glyphOffsets[i]; + } + return result.data; + }; + return CompoundGlyph; +})(); - elem = h.findItem({ name: items[i], check_keys: true }); - if (elem) items[i] = h.itemFullName(elem); +var LocaTable = (function(_super) { + __extends$1(LocaTable, _super); + + function LocaTable() { + return LocaTable.__super__.constructor.apply(this, arguments); + } + LocaTable.prototype.tag = "loca"; + LocaTable.prototype.parse = function(data) { + var format, i; + data.pos = this.offset; + format = this.file.head.indexToLocFormat; + if (format === 0) { + return (this.offsets = function() { + var _ref, _results; + _results = []; + for (i = 0, _ref = this.length; i < _ref; i += 2) { + _results.push(data.readUInt16() * 2); + } + return _results; + }.call(this)); + } else { + return (this.offsets = function() { + var _ref, _results; + _results = []; + for (i = 0, _ref = this.length; i < _ref; i += 4) { + _results.push(data.readUInt32()); + } + return _results; + }.call(this)); + } + }; + LocaTable.prototype.indexOf = function(id) { + return this.offsets[id]; + }; + LocaTable.prototype.lengthOf = function(id) { + return this.offsets[id + 1] - this.offsets[id]; + }; + LocaTable.prototype.encode = function(offsets, activeGlyphs) { + var LocaTable = new Uint32Array(this.offsets.length); + var glyfPtr = 0; + var listGlyf = 0; + for (var k = 0; k < LocaTable.length; ++k) { + LocaTable[k] = glyfPtr; + if (listGlyf < activeGlyphs.length && activeGlyphs[listGlyf] == k) { + ++listGlyf; + LocaTable[k] = glyfPtr; + var start = this.offsets[k]; + var len = this.offsets[k + 1] - start; + if (len > 0) { + glyfPtr += len; + } + } + } + var newLocaTable = new Array(LocaTable.length * 4); + for (var j = 0; j < LocaTable.length; ++j) { + newLocaTable[4 * j + 3] = LocaTable[j] & 0x000000ff; + newLocaTable[4 * j + 2] = (LocaTable[j] & 0x0000ff00) >> 8; + newLocaTable[4 * j + 1] = (LocaTable[j] & 0x00ff0000) >> 16; + newLocaTable[4 * j] = (LocaTable[j] & 0xff000000) >> 24; + } + return newLocaTable; + }; + return LocaTable; +})(Table); + +/************************************************************************************/ +/* function : invert */ +/* comment : Change the object's (key: value) to create an object with (value: key).*/ +/************************************************************************************/ +var invert = function(object) { + var key, ret, val; + ret = {}; + for (key in object) { + val = object[key]; + ret[val] = key; + } + return ret; +}; + +/*var successorOf = function (input) { + var added, alphabet, carry, i, index, isUpperCase, last, length, next, result; + alphabet = 'abcdefghijklmnopqrstuvwxyz'; + length = alphabet.length; + result = input; + i = input.length; + while (i >= 0) { + last = input.charAt(--i); + if (isNaN(last)) { + index = alphabet.indexOf(last.toLowerCase()); + if (index === -1) { + next = last; + carry = true; + } + else { + next = alphabet.charAt((index + 1) % length); + isUpperCase = last === last.toUpperCase(); + if (isUpperCase) { + next = next.toUpperCase(); + } + carry = index + 1 >= length; + if (carry && i === 0) { + added = isUpperCase ? 'A' : 'a'; + result = added + next + result.slice(1); + break; + } + } + } + else { + next = +last + 1; + carry = next > 9; + if (carry) { + next = 0; + } + if (carry && i === 0) { + result = '1' + next + result.slice(1); + break; + } + } + result = result.slice(0, i) + next + result.slice(i + 1); + if (!carry) { + break; + } + } + return result; + };*/ + +var Subset = (function() { + function Subset(font) { + this.font = font; + this.subset = {}; + this.unicodes = {}; + this.next = 33; + } + /*Subset.prototype.use = function (character) { + var i, _i, _ref; + if (typeof character === 'string') { + for (i = _i = 0, _ref = character.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + this.use(character.charCodeAt(i)); + } + return; + } + if (!this.unicodes[character]) { + this.subset[this.next] = character; + return this.unicodes[character] = this.next++; + } + };*/ + /*Subset.prototype.encodeText = function (text) { + var char, i, string, _i, _ref; + string = ''; + for (i = _i = 0, _ref = text.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + char = this.unicodes[text.charCodeAt(i)]; + string += String.fromCharCode(char); + } + return string; + };*/ + /***************************************************************/ + /* function : generateCmap */ + /* comment : Returns the unicode cmap for this font. */ + /***************************************************************/ + Subset.prototype.generateCmap = function() { + var mapping, roman, unicode, unicodeCmap, _ref; + unicodeCmap = this.font.cmap.tables[0].codeMap; + mapping = {}; + _ref = this.subset; + for (roman in _ref) { + unicode = _ref[roman]; + mapping[roman] = unicodeCmap[unicode]; + } + return mapping; + }; + /*Subset.prototype.glyphIDs = function () { + var ret, roman, unicode, unicodeCmap, val, _ref; + unicodeCmap = this.font.cmap.tables[0].codeMap; + ret = [0]; + _ref = this.subset; + for (roman in _ref) { + unicode = _ref[roman]; + val = unicodeCmap[unicode]; + if ((val != null) && __indexOf.call(ret, val) < 0) { + ret.push(val); + } + } + return ret.sort(); + };*/ + /******************************************************************/ + /* function : glyphsFor */ + /* comment : Returns simple glyph objects for the input character.*/ + /******************************************************************/ + Subset.prototype.glyphsFor = function(glyphIDs) { + var additionalIDs, glyph, glyphs, id, _i, _len, _ref; + glyphs = {}; + for (_i = 0, _len = glyphIDs.length; _i < _len; _i++) { + id = glyphIDs[_i]; + glyphs[id] = this.font.glyf.glyphFor(id); + } + additionalIDs = []; + for (id in glyphs) { + glyph = glyphs[id]; + if (glyph != null ? glyph.compound : void 0) { + additionalIDs.push.apply(additionalIDs, glyph.glyphIDs); } - - // now check that items can be displayed - for (let n = items.length - 1; n >= 0; --n) { - if (images[n]) continue; - const hitem = h.findItem(items[n]); - if (!hitem || h.canDisplay(hitem, options[n])) continue; - // try to expand specified item - h.expandItem(items[n], null, true); - items.splice(n, 1); - options.splice(n, 1); - dropitems.splice(n, 1); + } + if (additionalIDs.length > 0) { + _ref = this.glyphsFor(additionalIDs); + for (id in _ref) { + glyph = _ref[id]; + glyphs[id] = glyph; + } + } + return glyphs; + }; + /***************************************************************/ + /* function : encode */ + /* comment : Encode various tables for the characters you use. */ + /***************************************************************/ + Subset.prototype.encode = function(glyID, indexToLocFormat) { + var cmap, + code, + glyf, + glyphs, + id, + ids, + loca, + new2old, + newIDs, + nextGlyphID, + old2new, + oldID, + oldIDs, + tables, + _ref; + cmap = CmapTable.encode(this.generateCmap(), "unicode"); + glyphs = this.glyphsFor(glyID); + old2new = { + 0: 0 + }; + _ref = cmap.charMap; + for (code in _ref) { + ids = _ref[code]; + old2new[ids.old] = ids["new"]; + } + nextGlyphID = cmap.maxGlyphID; + for (oldID in glyphs) { + if (!(oldID in old2new)) { + old2new[oldID] = nextGlyphID++; } + } + new2old = invert(old2new); + newIDs = Object.keys(new2old).sort(function(a, b) { + return a - b; + }); + oldIDs = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = newIDs.length; _i < _len; _i++) { + id = newIDs[_i]; + _results.push(new2old[id]); + } + return _results; + })(); + glyf = this.font.glyf.encode(glyphs, oldIDs, old2new); + loca = this.font.loca.encode(glyf.offsets, oldIDs); + tables = { + cmap: this.font.cmap.raw(), + glyf: glyf.table, + loca: loca, + hmtx: this.font.hmtx.raw(), + hhea: this.font.hhea.raw(), + maxp: this.font.maxp.raw(), + post: this.font.post.raw(), + name: this.font.name.raw(), + head: this.font.head.encode(indexToLocFormat) + }; + if (this.font.os2.exists) { + tables["OS/2"] = this.font.os2.raw(); + } + return this.font.directory.encode(tables); + }; + return Subset; +})(); - if (items.length === 0) - return true; +jsPDF.API.PDFObject = (function() { + var pad; - const frame_names = new Array(items.length), items_wait = new Array(items.length); - for (let n = 0; n < items.length; ++n) { - items_wait[n] = 0; - let fname = items[n], k = 0; - if (items.indexOf(fname) < n) items_wait[n] = true; // if same item specified, one should wait first drawing before start next - const p = options[n].indexOf('frameid:'); - if (p >= 0) { - fname = options[n].slice(p+8); - options[n] = options[n].slice(0, p); - } else { - while (frame_names.indexOf(fname) >= 0) - fname = items[n] + '_' + k++; - } - frame_names[n] = fname; - } + function PDFObject() {} + pad = function(str, length) { + return (Array(length + 1).join("0") + str).slice(-length); + }; + /*****************************************************************************/ + /* function : convert */ + /* comment :Converts pdf tag's / FontBBox and array values in / W to strings */ + /*****************************************************************************/ + PDFObject.convert = function(object) { + var e, items, key, out, val; + if (Array.isArray(object)) { + items = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = object.length; _i < _len; _i++) { + e = object[_i]; + _results.push(PDFObject.convert(e)); + } + return _results; + })().join(" "); + return "[" + items + "]"; + } else if (typeof object === "string") { + return "/" + object; + } else if (object != null ? object.isString : void 0) { + return "(" + object + ")"; + } else if (object instanceof Date) { + return ( + "(D:" + + pad(object.getUTCFullYear(), 4) + + pad(object.getUTCMonth(), 2) + + pad(object.getUTCDate(), 2) + + pad(object.getUTCHours(), 2) + + pad(object.getUTCMinutes(), 2) + + pad(object.getUTCSeconds(), 2) + + "Z)" + ); + } else if ({}.toString.call(object) === "[object Object]") { + out = ["<<"]; + for (key in object) { + val = object[key]; + out.push("/" + key + " " + PDFObject.convert(val)); + } + out.push(">>"); + return out.join("\n"); + } else { + return "" + object; + } + }; + return PDFObject; +})(); - // now check if several same items present - select only one for the drawing - // if draw option includes 'main', such item will be drawn first - for (let n = 0; n < items.length; ++n) { - if (items_wait[n] !== 0) continue; - let found_main = n; - for (let k = 0; k < items.length; ++k) { - if ((items[n]===items[k]) && (options[k].indexOf('main') >= 0)) - found_main = k; - } - for (let k = 0; k < items.length; ++k) { - if (items[n] === items[k]) - items_wait[k] = (found_main !== k); - } - } +/** + * The MIT License (MIT) + * + * Copyright (c) 2015-2023 yWorks GmbH + * Copyright (c) 2013-2015 by Vitaly Puzrin + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ - return this.createDisplay().then(mdi => { - if (!mdi) return false; - // Than create empty frames for each item - for (let i = 0; i < items.length; ++i) { - if (options[i].indexOf('update:') !== 0) { - mdi.createFrame(frame_names[i]); - options[i] += '::_display_on_frame_::'+frame_names[i]; +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types */ +var RGBColor = /** @class */ (function () { + function RGBColor(colorString) { + this.a = undefined; + this.r = 0; + this.g = 0; + this.b = 0; + this.simpleColors = {}; + // eslint-disable-next-line @typescript-eslint/ban-types + this.colorDefs = []; + this.ok = false; + if (!colorString) { + return; + } + // strip any leading # + if (colorString.charAt(0) == '#') { + // remove # if any + colorString = colorString.substr(1, 6); + } + colorString = colorString.replace(/ /g, ''); + colorString = colorString.toLowerCase(); + // before getting into regexps, try simple matches + // and overwrite the input + this.simpleColors = { + aliceblue: 'f0f8ff', + antiquewhite: 'faebd7', + aqua: '00ffff', + aquamarine: '7fffd4', + azure: 'f0ffff', + beige: 'f5f5dc', + bisque: 'ffe4c4', + black: '000000', + blanchedalmond: 'ffebcd', + blue: '0000ff', + blueviolet: '8a2be2', + brown: 'a52a2a', + burlywood: 'deb887', + cadetblue: '5f9ea0', + chartreuse: '7fff00', + chocolate: 'd2691e', + coral: 'ff7f50', + cornflowerblue: '6495ed', + cornsilk: 'fff8dc', + crimson: 'dc143c', + cyan: '00ffff', + darkblue: '00008b', + darkcyan: '008b8b', + darkgoldenrod: 'b8860b', + darkgray: 'a9a9a9', + darkgrey: 'a9a9a9', + darkgreen: '006400', + darkkhaki: 'bdb76b', + darkmagenta: '8b008b', + darkolivegreen: '556b2f', + darkorange: 'ff8c00', + darkorchid: '9932cc', + darkred: '8b0000', + darksalmon: 'e9967a', + darkseagreen: '8fbc8f', + darkslateblue: '483d8b', + darkslategray: '2f4f4f', + darkslategrey: '2f4f4f', + darkturquoise: '00ced1', + darkviolet: '9400d3', + deeppink: 'ff1493', + deepskyblue: '00bfff', + dimgray: '696969', + dimgrey: '696969', + dodgerblue: '1e90ff', + feldspar: 'd19275', + firebrick: 'b22222', + floralwhite: 'fffaf0', + forestgreen: '228b22', + fuchsia: 'ff00ff', + gainsboro: 'dcdcdc', + ghostwhite: 'f8f8ff', + gold: 'ffd700', + goldenrod: 'daa520', + gray: '808080', + grey: '808080', + green: '008000', + greenyellow: 'adff2f', + honeydew: 'f0fff0', + hotpink: 'ff69b4', + indianred: 'cd5c5c', + indigo: '4b0082', + ivory: 'fffff0', + khaki: 'f0e68c', + lavender: 'e6e6fa', + lavenderblush: 'fff0f5', + lawngreen: '7cfc00', + lemonchiffon: 'fffacd', + lightblue: 'add8e6', + lightcoral: 'f08080', + lightcyan: 'e0ffff', + lightgoldenrodyellow: 'fafad2', + lightgray: 'd3d3d3', + lightgrey: 'd3d3d3', + lightgreen: '90ee90', + lightpink: 'ffb6c1', + lightsalmon: 'ffa07a', + lightseagreen: '20b2aa', + lightskyblue: '87cefa', + lightslateblue: '8470ff', + lightslategray: '778899', + lightslategrey: '778899', + lightsteelblue: 'b0c4de', + lightyellow: 'ffffe0', + lime: '00ff00', + limegreen: '32cd32', + linen: 'faf0e6', + magenta: 'ff00ff', + maroon: '800000', + mediumaquamarine: '66cdaa', + mediumblue: '0000cd', + mediumorchid: 'ba55d3', + mediumpurple: '9370d8', + mediumseagreen: '3cb371', + mediumslateblue: '7b68ee', + mediumspringgreen: '00fa9a', + mediumturquoise: '48d1cc', + mediumvioletred: 'c71585', + midnightblue: '191970', + mintcream: 'f5fffa', + mistyrose: 'ffe4e1', + moccasin: 'ffe4b5', + navajowhite: 'ffdead', + navy: '000080', + oldlace: 'fdf5e6', + olive: '808000', + olivedrab: '6b8e23', + orange: 'ffa500', + orangered: 'ff4500', + orchid: 'da70d6', + palegoldenrod: 'eee8aa', + palegreen: '98fb98', + paleturquoise: 'afeeee', + palevioletred: 'd87093', + papayawhip: 'ffefd5', + peachpuff: 'ffdab9', + peru: 'cd853f', + pink: 'ffc0cb', + plum: 'dda0dd', + powderblue: 'b0e0e6', + purple: '800080', + red: 'ff0000', + rosybrown: 'bc8f8f', + royalblue: '4169e1', + saddlebrown: '8b4513', + salmon: 'fa8072', + sandybrown: 'f4a460', + seagreen: '2e8b57', + seashell: 'fff5ee', + sienna: 'a0522d', + silver: 'c0c0c0', + skyblue: '87ceeb', + slateblue: '6a5acd', + slategray: '708090', + slategrey: '708090', + snow: 'fffafa', + springgreen: '00ff7f', + steelblue: '4682b4', + tan: 'd2b48c', + teal: '008080', + thistle: 'd8bfd8', + tomato: 'ff6347', + turquoise: '40e0d0', + violet: 'ee82ee', + violetred: 'd02090', + wheat: 'f5deb3', + white: 'ffffff', + whitesmoke: 'f5f5f5', + yellow: 'ffff00', + yellowgreen: '9acd32' + }; + for (var key in this.simpleColors) { + if (colorString == key) { + colorString = this.simpleColors[key]; } - } - - function dropNextItem(indx, painter) { - if (painter && dropitems[indx] && (dropitems[indx].length > 0)) - return h.dropItem(dropitems[indx].shift(), painter.getDom(), dropopts[indx].shift()).then(() => dropNextItem(indx, painter)); + } + // emd of simple type-in colors + // array of color definition objects + this.colorDefs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], + process: function (bits) { + return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; + } + }, + { + re: /^rgb\(([0-9.]+)%,\s*([0-9.]+)%,\s*([0-9.]+)%\)$/, + example: ['rgb(50.5%, 25.75%, 75.5%)', 'rgb(100%,0%,0%)'], + process: function (bits) { + return [ + Math.round(parseFloat(bits[1]) * 2.55), + Math.round(parseFloat(bits[2]) * 2.55), + Math.round(parseFloat(bits[3]) * 2.55) + ]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ['#00ff00', '336699'], + process: function (bits) { + return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16)]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ['#fb0', 'f0f'], + process: function (bits) { + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; + // search through the definitions to find a match + for (var i = 0; i < this.colorDefs.length; i++) { + var re = this.colorDefs[i].re; + var processor = this.colorDefs[i].process; + var bits = re.exec(colorString); + if (bits) { + var channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + } + // validate/cleanup values + this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r; + this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g; + this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; + } + RGBColor.prototype.toRGB = function () { + return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; + }; + RGBColor.prototype.toRGBA = function () { + return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + (this.a || '1') + ')'; + }; + RGBColor.prototype.toHex = function () { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) + r = '0' + r; + if (g.length == 1) + g = '0' + g; + if (b.length == 1) + b = '0' + b; + return '#' + r + g + b; + }; + // help + RGBColor.prototype.getHelpXML = function () { + var examples = []; + // add regexps + for (var i = 0; i < this.colorDefs.length; i++) { + var example = this.colorDefs[i].example; + for (var j = 0; j < example.length; j++) { + examples[examples.length] = example[j]; + } + } + // add type-in colors + for (var sc in this.simpleColors) { + examples[examples.length] = sc; + } + var xml = document.createElement('ul'); + xml.setAttribute('id', 'rgbcolor-examples'); + for (var i = 0; i < examples.length; i++) { + try { + var listItem = document.createElement('li'); + var listColor = new RGBColor(examples[i]); + var exampleDiv = document.createElement('div'); + exampleDiv.style.cssText = + 'margin: 3px; ' + + 'border: 1px solid black; ' + + 'background:' + + listColor.toHex() + + '; ' + + 'color:' + + listColor.toHex(); + exampleDiv.appendChild(document.createTextNode('test')); + var listItemValue = document.createTextNode(' ' + examples[i] + ' -> ' + listColor.toRGB() + ' -> ' + listColor.toHex()); + listItem.appendChild(exampleDiv); + listItem.appendChild(listItemValue); + xml.appendChild(listItem); + } + catch (e) { } + } + return xml; + }; + return RGBColor; +}()); - dropitems[indx] = null; // mark that all drop items are processed - items[indx] = null; // mark item as ready +var ColorFill = /** @class */ (function () { + function ColorFill(color) { + this.color = color; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ColorFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, undefined]; + }); + }); + }; + return ColorFill; +}()); + +var AttributeState = /** @class */ (function () { + function AttributeState() { + this.xmlSpace = ''; + this.whiteSpace = ''; + this.fill = null; + this.fillOpacity = 1.0; + // public fillRule: string = null + this.fontFamily = ''; + this.fontSize = 16; + this.fontStyle = ''; + // public fontVariant: string + this.fontWeight = ''; + this.opacity = 1.0; + this.stroke = null; + this.strokeDasharray = null; + this.strokeDashoffset = 0; + this.strokeLinecap = ''; + this.strokeLinejoin = ''; + this.strokeMiterlimit = 4.0; + this.strokeOpacity = 1.0; + this.strokeWidth = 1.0; + // public textAlign: string + this.alignmentBaseline = ''; + this.textAnchor = ''; + this.visibility = ''; + this.color = null; + this.contextFill = null; + this.contextStroke = null; + this.fillRule = null; + } + AttributeState.prototype.clone = function () { + var clone = new AttributeState(); + clone.xmlSpace = this.xmlSpace; + clone.whiteSpace = this.whiteSpace; + clone.fill = this.fill; + clone.fillOpacity = this.fillOpacity; + // clone.fillRule = this.fillRule; + clone.fontFamily = this.fontFamily; + clone.fontSize = this.fontSize; + clone.fontStyle = this.fontStyle; + // clone.fontVariant = this.fontVariant; + clone.fontWeight = this.fontWeight; + clone.opacity = this.opacity; + clone.stroke = this.stroke; + clone.strokeDasharray = this.strokeDasharray; + clone.strokeDashoffset = this.strokeDashoffset; + clone.strokeLinecap = this.strokeLinecap; + clone.strokeLinejoin = this.strokeLinejoin; + clone.strokeMiterlimit = this.strokeMiterlimit; + clone.strokeOpacity = this.strokeOpacity; + clone.strokeWidth = this.strokeWidth; + // clone.textAlign = this.textAlign; + clone.textAnchor = this.textAnchor; + clone.alignmentBaseline = this.alignmentBaseline; + clone.visibility = this.visibility; + clone.color = this.color; + clone.fillRule = this.fillRule; + clone.contextFill = this.contextFill; + clone.contextStroke = this.contextStroke; + return clone; + }; + AttributeState.default = function () { + var attributeState = new AttributeState(); + attributeState.xmlSpace = 'default'; + attributeState.whiteSpace = 'normal'; + attributeState.fill = new ColorFill(new RGBColor('rgb(0, 0, 0)')); + attributeState.fillOpacity = 1.0; + // attributeState.fillRule = "nonzero"; + attributeState.fontFamily = 'times'; + attributeState.fontSize = 16; + attributeState.fontStyle = 'normal'; + // attributeState.fontVariant = "normal"; + attributeState.fontWeight = 'normal'; + attributeState.opacity = 1.0; + attributeState.stroke = null; + attributeState.strokeDasharray = null; + attributeState.strokeDashoffset = 0; + attributeState.strokeLinecap = 'butt'; + attributeState.strokeLinejoin = 'miter'; + attributeState.strokeMiterlimit = 4.0; + attributeState.strokeOpacity = 1.0; + attributeState.strokeWidth = 1.0; + // attributeState.textAlign = "start"; + attributeState.alignmentBaseline = 'baseline'; + attributeState.textAnchor = 'start'; + attributeState.visibility = 'visible'; + attributeState.color = new RGBColor('rgb(0, 0, 0)'); + attributeState.fillRule = 'nonzero'; + attributeState.contextFill = null; + attributeState.contextStroke = null; + return attributeState; + }; + AttributeState.getContextColors = function (context, includeCurrentColor) { + if (includeCurrentColor === void 0) { includeCurrentColor = false; } + var colors = {}; + if (context.attributeState.contextFill) { + colors['contextFill'] = context.attributeState.contextFill; + } + if (context.attributeState.contextStroke) { + colors['contextStroke'] = context.attributeState.contextStroke; + } + if (includeCurrentColor && context.attributeState.color) { + colors['color'] = context.attributeState.color; + } + return colors; + }; + return AttributeState; +}()); - for (let cnt = 0; cnt < items.length; ++cnt) { - if (items[cnt] === null) continue; // ignore completed item - if (items_wait[cnt] && items.indexOf(items[cnt]) === cnt) { - items_wait[cnt] = false; - return h.display(items[cnt], options[cnt]).then(painter => dropNextItem(cnt, painter)); - } - } - } +/** + * + * @package + * @param values + * @constructor + * @property pdf + * @property attributeState Keeps track of parent attributes that are inherited automatically + * @property refsHandler The handler that will render references on demand + * @property styleSheets + * @property textMeasure + * @property transform The current transformation matrix + * @property withinClipPath + */ +var Context = /** @class */ (function () { + function Context(pdf, values) { + var _a, _b, _c; + this.pdf = pdf; + this.svg2pdfParameters = values.svg2pdfParameters; + this.attributeState = values.attributeState + ? values.attributeState.clone() + : AttributeState.default(); + this.viewport = values.viewport; + this.refsHandler = values.refsHandler; + this.styleSheets = values.styleSheets; + this.textMeasure = values.textMeasure; + this.transform = (_a = values.transform) !== null && _a !== void 0 ? _a : this.pdf.unitMatrix; + this.withinClipPath = (_b = values.withinClipPath) !== null && _b !== void 0 ? _b : false; + this.withinUse = (_c = values.withinUse) !== null && _c !== void 0 ? _c : false; + } + Context.prototype.clone = function (values) { + var _a, _b, _c, _d; + if (values === void 0) { values = {}; } + return new Context(this.pdf, { + svg2pdfParameters: this.svg2pdfParameters, + attributeState: values.attributeState + ? values.attributeState.clone() + : this.attributeState.clone(), + viewport: (_a = values.viewport) !== null && _a !== void 0 ? _a : this.viewport, + refsHandler: this.refsHandler, + styleSheets: this.styleSheets, + textMeasure: this.textMeasure, + transform: (_b = values.transform) !== null && _b !== void 0 ? _b : this.transform, + withinClipPath: (_c = values.withinClipPath) !== null && _c !== void 0 ? _c : this.withinClipPath, + withinUse: (_d = values.withinUse) !== null && _d !== void 0 ? _d : this.withinUse + }); + }; + return Context; +}()); + +var ReferencesHandler = /** @class */ (function () { + function ReferencesHandler(idMap) { + this.renderedElements = {}; + this.idMap = idMap; + this.idPrefix = String(ReferencesHandler.instanceCounter++); + } + ReferencesHandler.prototype.getRendered = function (id, contextColors, renderCallback) { + return __awaiter(this, void 0, void 0, function () { + var key, svgNode; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + key = this.generateKey(id, contextColors); + if (this.renderedElements.hasOwnProperty(key)) { + return [2 /*return*/, this.renderedElements[id]]; + } + svgNode = this.get(id); + this.renderedElements[key] = svgNode; + return [4 /*yield*/, renderCallback(svgNode)]; + case 1: + _a.sent(); + return [2 /*return*/, svgNode]; + } + }); + }); + }; + ReferencesHandler.prototype.get = function (id) { + // return this.idMap[cssEsc(id, { isIdentifier: true })] + return this.idMap[id]; // jsroot uses plain ids + }; + ReferencesHandler.prototype.generateKey = function (id, contextColors) { + var colorHash = ''; + var keys = ['color', 'contextFill', 'contextStroke']; + if (contextColors) { + colorHash = keys.map(function (key) { var _a, _b; return (_b = (_a = contextColors[key]) === null || _a === void 0 ? void 0 : _a.toRGBA()) !== null && _b !== void 0 ? _b : ''; }).join('|'); + } + return this.idPrefix + '|' + id + '|' + colorHash; + }; + ReferencesHandler.instanceCounter = 0; + return ReferencesHandler; +}()); - const promises = []; +function getAngle(from, to) { + return Math.atan2(to[1] - from[1], to[0] - from[0]); +} +var cToQ = 2 / 3; // ratio to convert quadratic bezier curves to cubic ones +// transforms a cubic bezier control point to a quadratic one: returns from + (2/3) * (to - from) +function toCubic(from, to) { + return [cToQ * (to[0] - from[0]) + from[0], cToQ * (to[1] - from[1]) + from[1]]; +} +function normalize(v) { + var length = Math.sqrt(v[0] * v[0] + v[1] * v[1]); + return [v[0] / length, v[1] / length]; +} +function getDirectionVector(from, to) { + var v = [to[0] - from[0], to[1] - from[1]]; + return normalize(v); +} +function addVectors(v1, v2) { + return [v1[0] + v2[0], v1[1] + v2[1]]; +} +// multiplies a vector with a matrix: vec' = vec * matrix +function multVecMatrix(vec, matrix) { + var x = vec[0]; + var y = vec[1]; + return [matrix.a * x + matrix.c * y + matrix.e, matrix.b * x + matrix.d * y + matrix.f]; +} - if (this._one_by_one) { - function processNext(indx) { - if (indx >= items.length) - return true; - if (items_wait[indx]) - return processNext(indx + 1); - return h.display(items[indx], options[indx]) - .then(painter => dropNextItem(indx, painter)) - .then(() => processNext(indx + 1)); +var Path = /** @class */ (function () { + function Path() { + this.segments = []; + } + Path.prototype.moveTo = function (x, y) { + this.segments.push(new MoveTo(x, y)); + return this; + }; + Path.prototype.lineTo = function (x, y) { + this.segments.push(new LineTo(x, y)); + return this; + }; + Path.prototype.curveTo = function (x1, y1, x2, y2, x, y) { + this.segments.push(new CurveTo(x1, y1, x2, y2, x, y)); + return this; + }; + Path.prototype.close = function () { + this.segments.push(new Close()); + return this; + }; + /** + * Transforms the path in place + */ + Path.prototype.transform = function (matrix) { + this.segments.forEach(function (seg) { + if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { + var p = multVecMatrix([seg.x, seg.y], matrix); + seg.x = p[0]; + seg.y = p[1]; } - promises.push(processNext(0)); - } else { - // We start display of all items parallel, but only if they are not the same - for (let i = 0; i < items.length; ++i) { - if (!items_wait[i]) - promises.push(h.display(items[i], options[i]).then(painter => dropNextItem(i, painter))); + if (seg instanceof CurveTo) { + var p1 = multVecMatrix([seg.x1, seg.y1], matrix); + var p2 = multVecMatrix([seg.x2, seg.y2], matrix); + seg.x1 = p1[0]; + seg.y1 = p1[1]; + seg.x2 = p2[0]; + seg.y2 = p2[1]; } - } - - return Promise.all(promises); - }); - } - - /** @summary Reload hierarchy and refresh html code - * @return {Promise} when completed */ - async reload() { - if ('_online' in this.h) - return this.openOnline(this.h._online).then(() => this.refreshHtml()); - return false; - } + }); + }; + Path.prototype.draw = function (context) { + var p = context.pdf; + this.segments.forEach(function (s) { + if (s instanceof MoveTo) { + p.moveTo(s.x, s.y); + } + else if (s instanceof LineTo) { + p.lineTo(s.x, s.y); + } + else if (s instanceof CurveTo) { + p.curveTo(s.x1, s.y1, s.x2, s.y2, s.x, s.y); + } + else { + p.close(); + } + }); + }; + return Path; +}()); +var MoveTo = /** @class */ (function () { + function MoveTo(x, y) { + this.x = x; + this.y = y; + } + return MoveTo; +}()); +var LineTo = /** @class */ (function () { + function LineTo(x, y) { + this.x = x; + this.y = y; + } + return LineTo; +}()); +var CurveTo = /** @class */ (function () { + function CurveTo(x1, y1, x2, y2, x, y) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.x = x; + this.y = y; + } + return CurveTo; +}()); +var Close = /** @class */ (function () { + function Close() { + } + return Close; +}()); + +function nodeIs(node, tagsString) { + return tagsString.split(',').indexOf((node.nodeName || node.tagName).toLowerCase()) >= 0; +} +function forEachChild(node, fn) { + // copy list of children, as the original might be modified + var children = []; + for (var i = 0; i < node.childNodes.length; i++) { + var childNode = node.childNodes[i]; + if (childNode.nodeName.charAt(0) !== '#') + children.push(childNode); + } + for (var i = 0; i < children.length; i++) { + fn(i, children[i]); + } +} +// returns an attribute of a node, either from the node directly or from css +function getAttribute(node, styleSheets, propertyNode, propertyCss) { + var _a; + if (propertyCss === void 0) { propertyCss = propertyNode; } + var attribute = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propertyCss); + if (attribute) { + return attribute; + } + else { + var propertyValue = styleSheets.getPropertyValue(node, propertyCss); + if (propertyValue) { + return propertyValue; + } + else if (node.hasAttribute(propertyNode)) { + return node.getAttribute(propertyNode) || undefined; + } + else { + return undefined; + } + } +} +function svgNodeIsVisible(svgNode, parentVisible, context) { + if (getAttribute(svgNode.element, context.styleSheets, 'display') === 'none') { + return false; + } + var visible = parentVisible; + var visibility = getAttribute(svgNode.element, context.styleSheets, 'visibility'); + if (visibility) { + visible = visibility !== 'hidden'; + } + return visible; +} +function svgNodeAndChildrenVisible(svgNode, parentVisible, context) { + var visible = svgNodeIsVisible(svgNode, parentVisible, context); + if (svgNode.element.childNodes.length === 0) { + return false; + } + svgNode.children.forEach(function (child) { + if (child.isVisible(visible, context)) { + visible = true; + } + }); + return visible; +} - /** @summary activate (select) specified item - * @param {Array} items - array of items names - * @param {boolean} [force] - if specified, all required sub-levels will be opened - * @private */ - activateItems(items, force) { - if (isStr(items)) items = [items]; +/** + * @constructor + * @property {Marker[]} markers + */ +var MarkerList = /** @class */ (function () { + function MarkerList() { + this.markers = []; + } + MarkerList.prototype.addMarker = function (markers) { + this.markers.push(markers); + }; + MarkerList.prototype.draw = function (context) { + return __awaiter(this, void 0, void 0, function () { + var i, marker, tf, angle, anchor, cos, sin, contextColors; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + i = 0; + _a.label = 1; + case 1: + if (!(i < this.markers.length)) return [3 /*break*/, 4]; + marker = this.markers[i]; + tf = void 0; + angle = marker.angle, anchor = marker.anchor; + cos = Math.cos(angle); + sin = Math.sin(angle); + // position at and rotate around anchor + tf = context.pdf.Matrix(cos, sin, -sin, cos, anchor[0], anchor[1]); + // scale with stroke-width + tf = context.pdf.matrixMult(context.pdf.Matrix(context.attributeState.strokeWidth, 0, 0, context.attributeState.strokeWidth, 0, 0), tf); + tf = context.pdf.matrixMult(tf, context.transform); + // as the marker is already scaled by the current line width we must not apply the line width twice! + context.pdf.saveGraphicsState(); + contextColors = AttributeState.getContextColors(context); + return [4 /*yield*/, context.refsHandler.getRendered(marker.id, contextColors, function (node) { + return node.apply(context); + })]; + case 2: + _a.sent(); + context.pdf.doFormObject(context.refsHandler.generateKey(marker.id, contextColors), tf); + context.pdf.restoreGraphicsState(); + _a.label = 3; + case 3: + i++; + return [3 /*break*/, 1]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + return MarkerList; +}()); +/** + * @param {string} id + * @param {[number,number]} anchor + * @param {number} angle + */ +var Marker = /** @class */ (function () { + function Marker(id, anchor, angle, isStartMarker) { + if (isStartMarker === void 0) { isStartMarker = false; } + this.id = id; + this.anchor = anchor; + this.angle = angle; + this.isStartMarker = isStartMarker; + } + return Marker; +}()); + +var iriReference = /url\(["']?#([^"']+)["']?\)/; +var alignmentBaselineMap = { + bottom: 'bottom', + 'text-bottom': 'bottom', + top: 'top', + 'text-top': 'top', + hanging: 'hanging', + middle: 'middle', + central: 'middle', + center: 'middle', + mathematical: 'middle', + ideographic: 'ideographic', + alphabetic: 'alphabetic', + baseline: 'alphabetic' +}; +var svgNamespaceURI = 'http://www.w3.org/2000/svg'; - const active = [], // array of elements to activate - update = []; // array of elements to update - this.forEachItem(item => { if (item._background) { active.push(item); delete item._background; } }); +/** + * Convert em, px and bare number attributes to pixel values + * @param {string} value + * @param {number} pdfFontSize + */ +function toPixels(value, pdfFontSize) { + var match; + // em + match = value && value.toString().match(/^([\-0-9.]+)em$/); + if (match) { + return parseFloat(match[1]) * pdfFontSize; + } + // pixels + match = value && value.toString().match(/^([\-0-9.]+)(px|)$/); + if (match) { + return parseFloat(match[1]); + } + return 0; +} +function mapAlignmentBaseline(value) { + return alignmentBaselineMap[value] || 'alphabetic'; +} - const mark_active = () => { - for (let n = update.length-1; n >= 0; --n) - this.updateTreeNode(update[n]); +function parseFloats(str) { + var floats = []; + var regex = /[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.?\d+))(?:[eE][+-]?\d+)?/g; + var match; + while ((match = regex.exec(str))) { + floats.push(parseFloat(match[0])); + } + return floats; +} +/** + * extends RGBColor by rgba colors as RGBColor is not capable of it + * currentcolor: the color to return if colorString === 'currentcolor' + */ +function parseColor(colorString, contextColors) { + if (colorString === 'transparent') { + var transparent = new RGBColor('rgb(0,0,0)'); + transparent.a = 0; + return transparent; + } + if (contextColors && colorString.toLowerCase() === 'currentcolor') { + return contextColors.color || new RGBColor('rgb(0,0,0)'); + } + if (contextColors && colorString.toLowerCase() === 'context-stroke') { + return contextColors.contextStroke || new RGBColor('rgb(0,0,0)'); + } + if (contextColors && colorString.toLowerCase() === 'context-fill') { + return contextColors.contextFill || new RGBColor('rgb(0,0,0)'); + } + var match = /\s*rgba\(((?:[^,\)]*,){3}[^,\)]*)\)\s*/.exec(colorString); + if (match) { + var floats = parseFloats(match[1]); + var color = new RGBColor('rgb(' + floats.slice(0, 3).join(',') + ')'); + color.a = floats[3]; + return color; + } + else { + return new RGBColor(colorString); + } +} - for (let n = 0; n < active.length; ++n) - this.updateBackground(active[n], force); - }, +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} - find_next = (itemname, prev_found) => { - if (itemname === undefined) { - // extract next element - if (items.length === 0) return mark_active(); - itemname = items.shift(); - } +var fontFamilyPapandreou; +var hasRequiredFontFamilyPapandreou; - let hitem = this.findItem(itemname); +function requireFontFamilyPapandreou () { + if (hasRequiredFontFamilyPapandreou) return fontFamilyPapandreou; + hasRequiredFontFamilyPapandreou = 1; + // parse + // ===== - if (!hitem) { - const d = this.findItem({ name: itemname, last_exists: true, check_keys: true, allow_index: true }); - if (!d || !d.last) return find_next(); - d.now_found = this.itemFullName(d.last); + // states + // ------ - if (force) { - // if after last expand no better solution found - skip it - if ((prev_found !== undefined) && (d.now_found === prev_found)) return find_next(); + var PLAIN = 0; + var STRINGS = 1; + var ESCAPING = 2; + var IDENTIFIER = 3; + var SEPARATING = 4; + var SPACEAFTERIDENTIFIER = 5; - return this.expandItem(d.now_found).then(res => { - if (!res) return find_next(); - let newname = this.itemFullName(d.last); - if (newname) newname += '/'; - find_next(newname + d.rest, d.now_found); - }); - } - hitem = d.last; - } + // patterns + // -------- - if (hitem) { // deepscan-disable-line - // check that item is visible (opened), otherwise should enable parent + var identifierPattern = /[a-z0-9_-]/i; + var spacePattern = /[\s\t]/; - let prnt = hitem._parent; - while (prnt) { - if (!prnt._isopen) { - if (force) { - prnt._isopen = true; - if (update.indexOf(prnt) < 0) update.push(prnt); - } else { - hitem = prnt; break; - } - } - prnt = prnt._parent; - } + // --- - hitem._background = 'LightSteelBlue'; - if (active.indexOf(hitem) < 0) active.push(hitem); - } + var parse = function(str) { - find_next(); - }; + // vars + // ---- - if (force && this.brlayout) { - if (!this.brlayout.browser_kind) - return this.createBrowser('float', true).then(() => find_next()); - if (!this.brlayout.browser_visible) - this.brlayout.toggleBrowserVisisbility(); - } + var starting = true; + var state = PLAIN; + var buffer = ''; + var i = 0; + var quote; + var c; - // use recursion - find_next(); - } + // result + // ------ - /** @summary Check if item can be (potentially) expand - * @private */ - canExpandItem(item) { - if (!item) return false; - if (item._expand) return true; - const handle = getDrawHandle(item._kind, '::expand'); - return handle && canExpandHandle(handle); - } + var names = []; - /** @summary expand specified item - * @param {String} itemname - item name - * @return {Promise} when ready */ - async expandItem(itemname, d3cont, silent) { - const hitem = this.findItem(itemname), hpainter = this; + // parse + // ----- - if (!hitem && d3cont) - return; + while (true) { - async function doExpandItem(_item, _obj) { - if (isStr(_item._expand)) - _item._expand = findFunction(_item._expand); + c = str[i]; - if (!isFunc(_item._expand)) { - let handle = getDrawHandle(_item._kind, '::expand'); + if (state === PLAIN) { - // in inspector show all memebers - if (handle?.expand_item && !hpainter._inspector) { - _obj = _obj[handle.expand_item]; - _item.expand_item = handle.expand_item; // remember that was exapnd item - handle = _obj?._typename ? getDrawHandle(prROOT + _obj._typename, '::expand') : null; - } + if (!c && starting) { - if (handle?.expand || handle?.get_expand) { - if (isFunc(handle.expand)) - _item._expand = handle.expand; - else if (isStr(handle.expand)) { - if (!internals.ignore_v6) { - const v6 = await _ensureJSROOT(); - await v6.require(handle.prereq); - await v6._complete_loading(); - } - _item._expand = handle.expand = findFunction(handle.expand); - } else if (isFunc(handle.get_expand)) - _item._expand = handle.expand = await handle.get_expand(); - } - } + break; - // try to use expand function - if (_obj && isFunc(_item._expand)) { - if (_item._expand(_item, _obj)) { - _item._isopen = true; - if (_item._parent && !_item._parent._isopen) { - _item._parent._isopen = true; // also show parent - if (!silent) - hpainter.updateTreeNode(_item._parent); - } else { - if (!silent) - hpainter.updateTreeNode(_item, d3cont); - } - return _item; - } - } + } else if (!c && !starting) { - if (_obj && objectHierarchy(_item, _obj)) { - _item._isopen = true; - if (_item._parent && !_item._parent._isopen) { - _item._parent._isopen = true; // also show parent - if (!silent) hpainter.updateTreeNode(_item._parent); - } else { - if (!silent) - hpainter.updateTreeNode(_item, d3cont); - } - return _item; - } + throw new Error('Parse error'); - return -1; - } + } else if (c === '"' || c === "'") { - let promise = Promise.resolve(-1); + quote = c; + state = STRINGS; + starting = false; - if (hitem) { - // item marked as it cannot be expanded, also top item cannot be changed - if ((hitem._more === false) || (!hitem._parent && hitem._childs)) - return; + } else if (spacePattern.test(c)) ; else if (identifierPattern.test(c)) { - if (hitem._childs && hitem._isopen) { - hitem._isopen = false; - if (!silent) this.updateTreeNode(hitem, d3cont); - return; - } + state = IDENTIFIER; + starting = false; + i--; - if (hitem._obj) promise = doExpandItem(hitem, hitem._obj); - } + } else { - return promise.then(res => { - if (res !== -1) return res; // done + throw new Error('Parse error'); - showProgress('Loading ' + itemname); + } - return this.getObject(itemname, silent ? 'hierarchy_expand' : 'hierarchy_expand_verbose').then(res => { - showProgress(); - if (res.obj) return doExpandItem(res.item, res.obj).then(res => { return (res !== -1) ? res : undefined; }); - }); - }); - } + } else if (state === STRINGS) { - /** @summary Return main online item - * @private */ - getTopOnlineItem(item) { - if (item) { - while (item && (!('_online' in item))) item = item._parent; - return item; - } + if (!c) { - if (!this.h) return null; - if ('_online' in this.h) return this.h; - if (this.h._childs && ('_online' in this.h._childs[0])) return this.h._childs[0]; - return null; - } + throw new Error('Parse Error'); - /** @summary Call function for each item which corresponds to JSON file - * @private */ - forEachJsonFile(func) { - if (!this.h) return; - if ('_jsonfile' in this.h) - return func(this.h); + } else if (c === "\\") { - if (this.h._childs) { - for (let n = 0; n < this.h._childs.length; ++n) { - const item = this.h._childs[n]; - if ('_jsonfile' in item) func(item); - } - } - } + state = ESCAPING; - /** @summary Open JSON file - * @param {string} filepath - URL to JSON file - * @return {Promise} when object ready */ - async openJsonFile(filepath) { - let isfileopened = false; - this.forEachJsonFile(item => { if (item._jsonfile === filepath) isfileopened = true; }); - if (isfileopened) return; + } else if (c === quote) { - return httpRequest(filepath, 'object').then(res => { - if (!res) return; - const h1 = { _jsonfile: filepath, _kind: prROOT + res._typename, _jsontmp: res, _name: filepath.split('/').pop() }; - if (res.fTitle) h1._title = res.fTitle; - h1._get = function(item /* ,itemname */) { - if (item._jsontmp) - return Promise.resolve(item._jsontmp); - return httpRequest(item._jsonfile, 'object') - .then(res => { - item._jsontmp = res; - return res; - }); - }; - if (!this.h) - this.h = h1; - else if (this.h._kind === kTopFolder) - this.h._childs.push(h1); - else { - const h0 = this.h, topname = ('_jsonfile' in h0) ? 'Files' : 'Items'; - this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1] }; - } + names.push(buffer); + buffer = ''; + state = SEPARATING; - return this.refreshHtml(); - }); - } + } else { - /** @summary Call function for each item which corresponds to ROOT file - * @private */ - forEachRootFile(func) { - if (!this.h) return; - if ((this.h._kind === kindTFile) && this.h._file) - return func(this.h); + buffer += c; - if (this.h._childs) { - for (let n = 0; n < this.h._childs.length; ++n) { - const item = this.h._childs[n]; - if ((item._kind === kindTFile) && ('_fullurl' in item)) - func(item); - } - } - } + } - /** @summary Find ROOT file which corresponds to provided item name - * @private */ - findRootFileForItem(itemname) { - let item = this.findItem(itemname); - while (item) { - if ((item._kind === kindTFile) && item._fullurl && item._file) - return item; - item = item?._parent; - } - return null; - } + } else if (state === ESCAPING) { - /** @summary Open ROOT file - * @param {string} filepath - URL to ROOT file, argument for openFile - * @return {Promise} when file is opened */ - async openRootFile(filepath) { - let isfileopened = false; - this.forEachRootFile(item => { if (item._fullurl === filepath) isfileopened = true; }); - if (isfileopened) return; + if (c === quote || c === "\\") { - const msg = isStr(filepath) ? filepath : 'file'; + buffer += c; + state = STRINGS; - showProgress(`Opening ${msg} ...`); + } else { - return openFile(filepath).then(file => { - const h1 = this.fileHierarchy(file); - h1._isopen = true; - if (!this.h) { - this.h = h1; - if (this._topname) h1._name = this._topname; - } else if (this.h._kind === kTopFolder) - this.h._childs.push(h1); - else { - const h0 = this.h, topname = (h0._kind === kindTFile) ? 'Files' : 'Items'; - this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1], _isopen: true }; - } + throw new Error('Parse error'); - return this.refreshHtml(); - }).catch(() => { - // make CORS warning - if (isBatchMode()) - console.error(`Fail to open ${msg} - check CORS headers`); - else if (!select('#gui_fileCORS').style('background', 'red').empty()) - setTimeout(() => select('#gui_fileCORS').style('background', ''), 5000); - return false; - }).finally(() => showProgress()); - } + } - /** @summary Create list of files for specified directory */ - async listServerDir(dirname) { - return httpRequest(dirname, 'text').then(res => { - if (!res) return false; - const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }; - let p = 0; - while (p < res.length) { - p = res.indexOf('a href="', p+1); - if (p < 0) break; - p += 8; - const p2 = res.indexOf('"', p+1); - if (p2 < 0) break; + } else if (state === IDENTIFIER) { - const fname = res.slice(p, p2); - p = p2 + 1; - if ((fname.lastIndexOf('.root') === fname.length - 5) && (fname.length > 5)) { - h._childs.push({ - _name: fname, _title: dirname + fname, _url: dirname + fname, _kind: kindTFile, - _click_action: 'expand', _more: true, _obj: {}, - _expand: item => { - return openFile(item._url).then(file => { - if (!file) return false; - delete item._exapnd; - delete item._more; - delete item._click_action; - delete item._obj; - item._isopen = true; - this.fileHierarchy(file, item); - this.updateTreeNode(item); - }); - } - }); - } else if (((fname.lastIndexOf('.json.gz') === fname.length - 8) && (fname.length > 8)) || - ((fname.lastIndexOf('.json') === fname.length - 5) && (fname.length > 5))) { - h._childs.push({ - _name: fname, _title: dirname + fname, _jsonfile: dirname + fname, _can_draw: true, - _get: item => { - return httpRequest(item._jsonfile, 'object').then(res => { - if (res) { - item._kind = prROOT + res._typename; - item._jsontmp = res; - this.updateTreeNode(item); - } - return res; - }); - } - }); - } - } - if (h._childs.length > 0) - this.h = h; - return true; - }); - } + if (!c) { - /** @summary Apply loaded TStyle object - * @desc One also can specify item name of JSON file name where style is loaded - * @param {object|string} style - either TStyle object of item name where object can be load */ - async applyStyle(style) { - if (!style) - return true; + names.push(buffer); + break; - let pr = Promise.resolve(style); + } else if (identifierPattern.test(c)) { - if (isStr(style)) { - const item = this.findItem({ name: style, allow_index: true, check_keys: true }); - if (item !== null) - pr = this.getObject(item).then(res => res.obj); - else if (style.indexOf('.json') > 0) - pr = httpRequest(style, 'object'); - } + buffer += c; - return pr.then(st => { - if (st?._typename === clTStyle) - Object.assign(gStyle, st); - }); - } + } else if (c === ',') { - /** @summary Provides information abouf file item - * @private */ - getFileProp(itemname) { - let item = this.findItem(itemname); - if (!item) return null; + names.push(buffer); + buffer = ''; + state = PLAIN; - let subname = item._name; - while (item._parent) { - item = item._parent; - if ('_file' in item) - return { kind: 'file', fileurl: item._file.fURL, itemname: subname, localfile: !!item._file.fLocalFile }; + } else if (spacePattern.test(c)) { - if ('_jsonfile' in item) - return { kind: 'json', fileurl: item._jsonfile, itemname: subname }; + state = SPACEAFTERIDENTIFIER; + } else { - subname = item._name + '/' + subname; - } + throw new Error('Parse error'); - return null; - } + } + } else if (state === SPACEAFTERIDENTIFIER) { - /** @summary Provides URL for online item - * @desc Such URL can be used to request data from the server - * @return string or null if item is not online - * @private */ - getOnlineItemUrl(item) { - if (isStr(item)) item = this.findItem(item); - let prnt = item; - while (prnt && (prnt._online === undefined)) prnt = prnt._parent; - return prnt ? (prnt._online + this.itemFullName(item, prnt)) : null; - } + if (!c) { - /** @summary Returns true if item is online - * @private */ - isOnlineItem(item) { - return this.getOnlineItemUrl(item) !== null; - } + names.push(buffer); + break; - /** @summary Dynamic module import, supports special shorcuts from core or draw_tree - * @return {Promise} with module - * @private */ - async importModule(module) { - switch (module) { - case 'core': return Promise.resolve().then(function () { return core; }); - case 'draw_tree': return Promise.resolve().then(function () { return TTree; }); - case 'hierarchy': return { HierarchyPainter, markAsStreamerInfo }; - } - return import(/* webpackIgnore: true */ module); - } + } else if (identifierPattern.test(c)) { - /** @summary method used to request object from the http server - * @return {Promise} with requested object - * @private */ - async getOnlineItem(item, itemname, option) { - let url = itemname, h_get = false, req = '', req_kind = 'object', draw_handle = null; + buffer += ' ' + c; + state = IDENTIFIER; - if (isStr(option) && (option.indexOf('hierarchy_expand') === 0)) { - h_get = true; - option = undefined; - } + } else if (c === ',') { - if (item) { - url = this.getOnlineItemUrl(item); - let func = null; - if ('_kind' in item) draw_handle = getDrawHandle(item._kind); + names.push(buffer); + buffer = ''; + state = PLAIN; - if (h_get) { - req = 'h.json?compact=3'; - item._expand = onlineHierarchy; // use proper expand function - } else if (item._make_request) { - if (item._module) { - const h = await this.importModule(item._module); - func = h[item._make_request]; - } else - func = findFunction(item._make_request); - } else if (draw_handle?.make_request) - func = draw_handle.make_request; + } else if (spacePattern.test(c)) ; else { + throw new Error('Parse error'); - if (isFunc(func)) { - // ask to make request - const dreq = func(this, item, url, option); - // result can be simple string or object with req and kind fields - if (dreq) { - if (isStr(dreq)) - req = dreq; - else { - if ('req' in dreq) req = dreq.req; - if ('kind' in dreq) req_kind = dreq.kind; - } - } - } + } - if (!req && (item._kind.indexOf(prROOT) !== 0)) - req = 'item.json.gz?compact=3'; - } + } else if (state === SEPARATING) { - if (!itemname && item && ('_cached_draw_object' in this) && !req) { - // special handling for online draw when cashed - const obj = this._cached_draw_object; - delete this._cached_draw_object; - return obj; - } + if (!c) { - if (!req) - req = 'root.json.gz?compact=23'; + break; - if (url) url += '/'; - url += req; + } else if (c === ',') { - return new Promise(resolveFunc => { - let itemreq = null; + state = PLAIN; - createHttpRequest(url, req_kind, obj => { - const handleAfterRequest = func => { - if (isFunc(func)) { - const res = func(this, item, obj, option, itemreq); - if (isObject(res)) obj = res; - } - resolveFunc(obj); - }; + } else if (spacePattern.test(c)) ; else { - if (!h_get && item?._after_request) { - if (item._module) - this.importModule(item._module).then(h => handleAfterRequest(h[item._after_request])); - else - handleAfterRequest(findFunction(item._after_request)); // v6 support - } else - handleAfterRequest(draw_handle?.after_request); - }, undefined, true).then(xhr => { itemreq = xhr; xhr.send(null); }); - }); - } + throw new Error('Parse error'); - /** @summary Access THttpServer with provided address - * @param {string} server_address - URL to server like 'http://localhost:8090/' - * @return {Promise} when ready */ - async openOnline(server_address) { - const adoptHierarchy = async result => { - this.h = result; - if (!result) - return Promise.resolve(null); + } - if (this.h?._title && (typeof document !== 'undefined')) - document.title = this.h._title; + } - result._isopen = true; + i++; - // mark top hierarchy as online data and - this.h._online = server_address; + } - this.h._get = (item, itemname, option) => this.getOnlineItem(item, itemname, option); + // result + // ------ - this.h._expand = onlineHierarchy; + return names; - const styles = [], scripts = [], v6_modules = [], v7_imports = []; - this.forEachItem(item => { - if (item._childs !== undefined) - item._expand = onlineHierarchy; + }; - if (item._autoload) { - const arr = item._autoload.split(';'); - arr.forEach(name => { - if ((name.length > 4) && (name.lastIndexOf('.mjs') === name.length-4)) - v7_imports.push(this.importModule(name)); - else if ((name.length > 3) && (name.lastIndexOf('.js') === name.length-3)) { - if (!scripts.find(elem => elem === name)) scripts.push(name); - } else if ((name.length > 4) && (name.lastIndexOf('.css') === name.length-4)) { - if (!styles.find(elem => elem === name)) styles.push(name); - } else if (name && !v6_modules.find(elem => elem === name)) - v6_modules.push(name); - }); - } - }); + // stringify + // ========= - return this.loadScripts(scripts, v6_modules) - .then(() => loadScript(styles)) - .then(() => Promise.all(v7_imports)) - .then(() => { - this.forEachItem(item => { - if (!('_drawfunc' in item) || !('_kind' in item)) return; - let typename = 'kind:' + item._kind; - if (item._kind.indexOf(prROOT) === 0) - typename = item._kind.slice(5); - const drawopt = item._drawopt; - if (!canDrawHandle(typename) || drawopt) - addDrawFunc({ name: typename, func: item._drawfunc, script: item._drawscript, opt: drawopt }); - }); + // pattern + // ------- - return this; - }); - }; + var stringsPattern = /[^a-z0-9_-]/i; - if (!server_address) server_address = ''; + // --- - if (isObject(server_address)) { - const h = server_address; - server_address = ''; - return adoptHierarchy(h); - } + var stringify = function(names, options) { - return httpRequest(server_address + 'h.json?compact=3', 'object').then(hh => adoptHierarchy(hh)); - } + // quote + // ----- - /** @summary Get properties for online item - server name and relative name - * @private */ - getOnlineProp(itemname) { - let item = this.findItem(itemname); - if (!item) return null; + var quote = options && options.quote || '"'; + if (quote !== '"' && quote !== "'") { + throw new Error('Quote must be `\'` or `"`'); + } + var quotePattern = new RegExp(quote, 'g'); - let subname = item._name; - while (item._parent) { - item = item._parent; + // stringify + // --------- - if ('_online' in item) { - return { - server: item._online, - itemname: subname - }; - } - subname = item._name + '/' + subname; - } + var safeNames = []; - return null; - } + for (var i = 0; i < names.length; ++i) { + var name = names[i]; - /** @summary Fill context menu for online item - * @private */ - fillOnlineMenu(menu, onlineprop, itemname) { - const node = this.findItem(itemname), - sett = getDrawSettings(node._kind, 'nosame;noinspect'), - handle = getDrawHandle(node._kind), - root_type = isStr(node._kind) ? node._kind.indexOf(prROOT) === 0 : false; + if (stringsPattern.test(name)) { + name = name + .replace(/\\/g, "\\\\") + .replace(quotePattern, "\\" + quote); + name = quote + name + quote; + } + safeNames.push(name); + } - if (sett.opts && (node._can_draw !== false)) { - sett.opts.push(kInspect); - menu.addDrawMenu('Draw', sett.opts, arg => this.display(itemname, arg)); - } + // result + // ------ - if (!node._childs && (node._more !== false) && (node._more || root_type || sett.expand || sett.get_expand)) - menu.add('Expand', () => this.expandItem(itemname)); + return safeNames.join(', '); + }; - if (handle?.execute) - menu.add('Execute', () => this.executeCommand(itemname, menu.tree_node)); + // export + // ====== - if (sett.opts && (node._can_draw !== false)) { - menu.addDrawMenu('Draw in new window', sett.opts, - arg => window.open(onlineprop.server + `?nobrowser&item=${onlineprop.itemname}` + - (this.isMonitoring() ? `&monitoring=${this.getMonitoringInterval()}` : '') + - (arg ? `&opt=${arg}` : ''))); - } + fontFamilyPapandreou = { + parse: parse, + stringify: stringify, + }; + return fontFamilyPapandreou; +} + +var fontFamilyPapandreouExports = requireFontFamilyPapandreou(); +var FontFamily = /*@__PURE__*/getDefaultExportFromCjs(fontFamilyPapandreouExports); + +var fontAliases = { + 'sans-serif': 'helvetica', + verdana: 'helvetica', + arial: 'helvetica', + fixed: 'courier', + monospace: 'courier', + terminal: 'courier', + serif: 'times', + cursive: 'times', + fantasy: 'times' +}; +function findFirstAvailableFontFamily(attributeState, fontFamilies, context) { + var fontType = combineFontStyleAndFontWeight(attributeState.fontStyle, attributeState.fontWeight); + var availableFonts = context.pdf.getFontList(); + var firstAvailable = ''; + var fontIsAvailable = fontFamilies.some(function (font) { + var availableStyles = availableFonts[font]; + if (availableStyles && availableStyles.indexOf(fontType) >= 0) { + firstAvailable = font; + return true; + } + font = font.toLowerCase(); + if (fontAliases.hasOwnProperty(font)) { + firstAvailable = font; + return true; + } + return false; + }); + if (!fontIsAvailable) { + firstAvailable = 'times'; + } + return firstAvailable; +} +var isJsPDF23 = (function () { + var parts = jsPDF.version.split('.'); + return parseFloat(parts[0]) === 2 && parseFloat(parts[1]) === 3; +})(); +function combineFontStyleAndFontWeight(fontStyle, fontWeight) { + if (isJsPDF23) { + return fontWeight == 400 + ? fontStyle == 'italic' + ? 'italic' + : 'normal' + : fontWeight == 700 && fontStyle !== 'italic' + ? 'bold' + : fontStyle + '' + fontWeight; + } + else { + return fontWeight == 400 || fontWeight === 'normal' + ? fontStyle === 'italic' + ? 'italic' + : 'normal' + : (fontWeight == 700 || fontWeight === 'bold') && fontStyle === 'normal' + ? 'bold' + : (fontWeight == 700 ? 'bold' : fontWeight) + '' + fontStyle; + } +} - if (sett.opts?.length && root_type && (node._can_draw !== false)) { - menu.addDrawMenu('Draw as png', sett.opts, - arg => window.open(onlineprop.server + onlineprop.itemname + '/root.png?w=600&h=400' + (arg ? '&opt=' + arg : '')), - 'Request PNG image from the server'); - } +function getBoundingBoxByChildren(context, svgnode) { + if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') { + return [0, 0, 0, 0]; + } + var boundingBox = []; + svgnode.children.forEach(function (child) { + var nodeBox = child.getBoundingBox(context); + if (nodeBox[0] === 0 && nodeBox[1] === 0 && nodeBox[2] === 0 && nodeBox[3] === 0) + return; + var transform = child.computeNodeTransform(context); + // TODO: take into account rotation matrix + nodeBox[0] = nodeBox[0] * transform.sx + transform.tx; + nodeBox[1] = nodeBox[1] * transform.sy + transform.ty; + nodeBox[2] = nodeBox[2] * transform.sx; + nodeBox[3] = nodeBox[3] * transform.sy; + if (boundingBox.length === 0) + boundingBox = nodeBox; + else + boundingBox = [ + Math.min(boundingBox[0], nodeBox[0]), + Math.min(boundingBox[1], nodeBox[1]), + Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - + Math.min(boundingBox[0], nodeBox[0]), + Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - + Math.min(boundingBox[1], nodeBox[1]) + ]; + }); + return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox; +} +function defaultBoundingBox(element, context) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var pf = parseFloat; + // TODO: check if there are other possible coordinate attributes + var x1 = pf(element.getAttribute('x1')) || + pf(getAttribute(element, context.styleSheets, 'x')) || + pf(getAttribute(element, context.styleSheets, 'cx')) - + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var x2 = pf(element.getAttribute('x2')) || + x1 + pf(getAttribute(element, context.styleSheets, 'width')) || + pf(getAttribute(element, context.styleSheets, 'cx')) + + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var y1 = pf(element.getAttribute('y1')) || + pf(getAttribute(element, context.styleSheets, 'y')) || + pf(getAttribute(element, context.styleSheets, 'cy')) - + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var y2 = pf(element.getAttribute('y2')) || + y1 + pf(getAttribute(element, context.styleSheets, 'height')) || + pf(getAttribute(element, context.styleSheets, 'cy')) + + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + return [ + Math.min(x1, x2), + Math.min(y1, y2), + Math.max(x1, x2) - Math.min(x1, x2), + Math.max(y1, y2) - Math.min(y1, y2) + ]; +} - if ('_player' in node) - menu.add('Player', () => this.player(itemname)); - } +function computeViewBoxTransform(node, viewBox, eX, eY, eWidth, eHeight, context, noTranslate) { + if (noTranslate === void 0) { noTranslate = false; } + var vbX = viewBox[0]; + var vbY = viewBox[1]; + var vbWidth = viewBox[2]; + var vbHeight = viewBox[3]; + var scaleX = eWidth / vbWidth; + var scaleY = eHeight / vbHeight; + var align, meetOrSlice; + var preserveAspectRatio = node.getAttribute('preserveAspectRatio'); + if (preserveAspectRatio) { + var alignAndMeetOrSlice = preserveAspectRatio.split(' '); + if (alignAndMeetOrSlice[0] === 'defer') { + alignAndMeetOrSlice = alignAndMeetOrSlice.slice(1); + } + align = alignAndMeetOrSlice[0]; + meetOrSlice = alignAndMeetOrSlice[1] || 'meet'; + } + else { + align = 'xMidYMid'; + meetOrSlice = 'meet'; + } + if (align !== 'none') { + if (meetOrSlice === 'meet') { + // uniform scaling with min scale + scaleX = scaleY = Math.min(scaleX, scaleY); + } + else if (meetOrSlice === 'slice') { + // uniform scaling with max scale + scaleX = scaleY = Math.max(scaleX, scaleY); + } + } + if (noTranslate) { + return context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); + } + var translateX = eX - vbX * scaleX; + var translateY = eY - vbY * scaleY; + if (align.indexOf('xMid') >= 0) { + translateX += (eWidth - vbWidth * scaleX) / 2; + } + else if (align.indexOf('xMax') >= 0) { + translateX += eWidth - vbWidth * scaleX; + } + if (align.indexOf('YMid') >= 0) { + translateY += (eHeight - vbHeight * scaleY) / 2; + } + else if (align.indexOf('YMax') >= 0) { + translateY += eHeight - vbHeight * scaleY; + } + var translate = context.pdf.Matrix(1, 0, 0, 1, translateX, translateY); + var scale = context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); + return context.pdf.matrixMult(scale, translate); +} +// parses the "transform" string +function parseTransform(transformString, context) { + if (!transformString || transformString === 'none') + return context.pdf.unitMatrix; + var mRegex = /^[\s,]*matrix\(([^)]+)\)\s*/, tRegex = /^[\s,]*translate\(([^)]+)\)\s*/, rRegex = /^[\s,]*rotate\(([^)]+)\)\s*/, sRegex = /^[\s,]*scale\(([^)]+)\)\s*/, sXRegex = /^[\s,]*skewX\(([^)]+)\)\s*/, sYRegex = /^[\s,]*skewY\(([^)]+)\)\s*/; + var resultMatrix = context.pdf.unitMatrix; + var m; + var tSLength; + while (transformString.length > 0 && transformString.length !== tSLength) { + tSLength = transformString.length; + var match = mRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], m[1], m[2], m[3], m[4], m[5]), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = rRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + var a = (Math.PI * m[0]) / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0), resultMatrix); + if (m[1] || m[2]) { + var t1 = context.pdf.Matrix(1, 0, 0, 1, m[1], m[2]); + var t2 = context.pdf.Matrix(1, 0, 0, 1, -m[1], -m[2]); + resultMatrix = context.pdf.matrixMult(t2, context.pdf.matrixMult(resultMatrix, t1)); + } + transformString = transformString.substr(match[0].length); + } + match = tRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, m[0], m[1] || 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + if (!m[1]) + m[1] = m[0]; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], 0, 0, m[1], 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sXRegex.exec(transformString); + if (match) { + m = parseFloat(match[1]); + m *= Math.PI / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, Math.tan(m), 1, 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sYRegex.exec(transformString); + if (match) { + m = parseFloat(match[1]); + m *= Math.PI / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, Math.tan(m), 0, 1, 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + } + return resultMatrix; +} - /** @summary Assign existing hierarchy to the painter and refresh HTML code - * @private */ - setHierarchy(h) { - this.h = h; - this.refreshHtml(); - } +var SvgNode = /** @class */ (function () { + function SvgNode(element, children) { + this.element = element; + this.children = children; + this.parent = null; + } + SvgNode.prototype.setParent = function (parent) { + this.parent = parent; + }; + SvgNode.prototype.getParent = function () { + return this.parent; + }; + SvgNode.prototype.getBoundingBox = function (context) { + if (getAttribute(this.element, context.styleSheets, 'display') === 'none') { + return [0, 0, 0, 0]; + } + return this.getBoundingBoxCore(context); + }; + SvgNode.prototype.computeNodeTransform = function (context) { + var nodeTransform = this.computeNodeTransformCore(context); + var transformString = getAttribute(this.element, context.styleSheets, 'transform'); + if (!transformString) + return nodeTransform; + else + return context.pdf.matrixMult(nodeTransform, parseTransform(transformString, context)); + }; + return SvgNode; +}()); - /** @summary Configures monitoring interval - * @param {number} interval - repetition interval in ms - * @param {boolean} flag - initial monitoring state */ - setMonitoring(interval, monitor_on) { - this._runMonitoring('cleanup'); +var NonRenderedNode = /** @class */ (function (_super) { + __extends(NonRenderedNode, _super); + function NonRenderedNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + NonRenderedNode.prototype.render = function (parentContext) { + return Promise.resolve(); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + NonRenderedNode.prototype.getBoundingBoxCore = function (context) { + return []; + }; + NonRenderedNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return NonRenderedNode; +}(SvgNode)); + +var Gradient = /** @class */ (function (_super) { + __extends(Gradient, _super); + function Gradient(pdfGradientType, element, children) { + var _this = _super.call(this, element, children) || this; + _this.pdfGradientType = pdfGradientType; + _this.contextColor = undefined; + return _this; + } + Gradient.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var id, colors, opacitySum, hasOpacity, gState, pattern; + return __generator(this, function (_a) { + id = this.element.getAttribute('id'); + if (!id) { + return [2 /*return*/]; + } + colors = this.getStops(context.styleSheets); + opacitySum = 0; + hasOpacity = false; + colors.forEach(function (_a) { + var opacity = _a.opacity; + if (opacity && opacity !== 1) { + opacitySum += opacity; + hasOpacity = true; + } + }); + if (hasOpacity) { + gState = new GState({ opacity: opacitySum / colors.length }); + } + pattern = new ShadingPattern(this.pdfGradientType, this.getCoordinates(), colors, gState); + context.pdf.addShadingPattern(id, pattern); + return [2 /*return*/]; + }); + }); + }; + Gradient.prototype.getStops = function (styleSheets) { + var _this = this; + if (this.stops) { + return this.stops; + } + // Only need to calculate contextColor once + if (this.contextColor === undefined) { + this.contextColor = null; + var ancestor = this; + while (ancestor) { + var colorAttr = getAttribute(ancestor.element, styleSheets, 'color'); + if (colorAttr) { + this.contextColor = parseColor(colorAttr, null); + break; + } + ancestor = ancestor.getParent(); + } + } + var stops = []; + this.children.forEach(function (stop) { + if (stop.element.tagName.toLowerCase() === 'stop') { + var colorAttr = getAttribute(stop.element, styleSheets, 'color'); + var color = parseColor(getAttribute(stop.element, styleSheets, 'stop-color') || '', colorAttr + ? { color: parseColor(colorAttr, null) } + : { color: _this.contextColor }); + var opacity = parseFloat(getAttribute(stop.element, styleSheets, 'stop-opacity') || '1'); + stops.push({ + offset: Gradient.parseGradientOffset(stop.element.getAttribute('offset') || '0'), + color: [color.r, color.g, color.b], + opacity: opacity + }); + } + }); + return (this.stops = stops); + }; + Gradient.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Gradient.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Gradient.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + /** + * Convert percentage to decimal + */ + Gradient.parseGradientOffset = function (value) { + var parsedValue = parseFloat(value); + if (!isNaN(parsedValue) && value.indexOf('%') >= 0) { + return parsedValue / 100; + } + return parsedValue; + }; + return Gradient; +}(NonRenderedNode)); - if (interval) { - interval = parseInt(interval); - if (Number.isInteger(interval) && (interval > 0)) { - this._monitoring_interval = Math.max(100, interval); - monitor_on = true; - } else - this._monitoring_interval = 3000; - } +var LinearGradient = /** @class */ (function (_super) { + __extends(LinearGradient, _super); + function LinearGradient(element, children) { + return _super.call(this, 'axial', element, children) || this; + } + LinearGradient.prototype.getCoordinates = function () { + return [ + parseFloat(this.element.getAttribute('x1') || '0'), + parseFloat(this.element.getAttribute('y1') || '0'), + parseFloat(this.element.getAttribute('x2') || '1'), + parseFloat(this.element.getAttribute('y2') || '0') + ]; + }; + return LinearGradient; +}(Gradient)); - this._monitoring_on = monitor_on; +var RadialGradient = /** @class */ (function (_super) { + __extends(RadialGradient, _super); + function RadialGradient(element, children) { + return _super.call(this, 'radial', element, children) || this; + } + RadialGradient.prototype.getCoordinates = function () { + var cx = this.element.getAttribute('cx'); + var cy = this.element.getAttribute('cy'); + var fx = this.element.getAttribute('fx'); + var fy = this.element.getAttribute('fy'); + return [ + parseFloat(fx || cx || '0.5'), + parseFloat(fy || cy || '0.5'), + 0, + parseFloat(cx || '0.5'), + parseFloat(cy || '0.5'), + parseFloat(this.element.getAttribute('r') || '0.5') + ]; + }; + return RadialGradient; +}(Gradient)); - if (this.isMonitoring()) - this._runMonitoring(); - } +var GradientFill = /** @class */ (function () { + function GradientFill(key, gradient) { + this.key = key; + this.gradient = gradient; + } + GradientFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + var gradientUnitsMatrix, bBox, gradientTransform; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { + return node.apply(new Context(context.pdf, { + refsHandler: context.refsHandler, + textMeasure: context.textMeasure, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters + })); + }) + // matrix to convert between gradient space and user space + // for "userSpaceOnUse" this is the current transformation: tfMatrix + // for "objectBoundingBox" or default, the gradient gets scaled and transformed to the bounding box + ]; + case 1: + _a.sent(); + if (!this.gradient.element.hasAttribute('gradientUnits') || + this.gradient.element.getAttribute('gradientUnits').toLowerCase() === 'objectboundingbox') { + bBox = forNode.getBoundingBox(context); + gradientUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]); + } + else { + gradientUnitsMatrix = context.pdf.unitMatrix; + } + gradientTransform = parseTransform(getAttribute(this.gradient.element, context.styleSheets, 'gradientTransform', 'transform'), context); + return [2 /*return*/, { + key: this.key, + matrix: context.pdf.matrixMult(gradientTransform, gradientUnitsMatrix) + }]; + } + }); + }); + }; + return GradientFill; +}()); - /** @summary Runs monitoring event loop - * @private */ - _runMonitoring(arg) { - if ((arg === 'cleanup') || !this.isMonitoring()) { - if (this._monitoring_handle) { - clearTimeout(this._monitoring_handle); - delete this._monitoring_handle; - } +var Pattern = /** @class */ (function (_super) { + __extends(Pattern, _super); + function Pattern() { + return _super !== null && _super.apply(this, arguments) || this; + } + Pattern.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var id, bBox, pattern, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + id = this.element.getAttribute('id'); + if (!id) { + return [2 /*return*/]; + } + bBox = this.getBoundingBox(context); + pattern = new TilingPattern([bBox[0], bBox[1], bBox[0] + bBox[2], bBox[1] + bBox[3]], bBox[2], bBox[3]); + context.pdf.beginTilingPattern(pattern); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(new Context(context.pdf, { + attributeState: context.attributeState, + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + context.pdf.endTilingPattern(id, pattern); + return [2 /*return*/]; + } + }); + }); + }; + Pattern.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Pattern.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Pattern.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + return Pattern; +}(NonRenderedNode)); - if (this._monitoring_frame) { - cancelAnimationFrame(this._monitoring_frame); - delete this._monitoring_frame; - } - return; - } +var PatternFill = /** @class */ (function () { + function PatternFill(key, pattern) { + this.key = key; + this.pattern = pattern; + } + PatternFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + var patternData, bBox, patternUnitsMatrix, fillBBox, x, y, width, height, patternContentUnitsMatrix, fillBBox, x, y, width, height, patternTransformMatrix, patternTransform, matrix; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { + return node.apply(new Context(context.pdf, { + refsHandler: context.refsHandler, + textMeasure: context.textMeasure, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters + })); + })]; + case 1: + _a.sent(); + patternData = { + key: this.key, + boundingBox: undefined, + xStep: 0, + yStep: 0, + matrix: undefined + }; + patternUnitsMatrix = context.pdf.unitMatrix; + if (!this.pattern.element.hasAttribute('patternUnits') || + this.pattern.element.getAttribute('patternUnits').toLowerCase() === 'objectboundingbox') { + bBox = forNode.getBoundingBox(context); + patternUnitsMatrix = context.pdf.Matrix(1, 0, 0, 1, bBox[0], bBox[1]); + fillBBox = this.pattern.getBoundingBox(context); + x = fillBBox[0] * bBox[0] || 0; + y = fillBBox[1] * bBox[1] || 0; + width = fillBBox[2] * bBox[2] || 0; + height = fillBBox[3] * bBox[3] || 0; + patternData.boundingBox = [x, y, x + width, y + height]; + patternData.xStep = width; + patternData.yStep = height; + } + patternContentUnitsMatrix = context.pdf.unitMatrix; + if (this.pattern.element.hasAttribute('patternContentUnits') && + this.pattern.element.getAttribute('patternContentUnits').toLowerCase() === + 'objectboundingbox') { + bBox || (bBox = forNode.getBoundingBox(context)); + patternContentUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], 0, 0); + fillBBox = patternData.boundingBox || this.pattern.getBoundingBox(context); + x = fillBBox[0] / bBox[0] || 0; + y = fillBBox[1] / bBox[1] || 0; + width = fillBBox[2] / bBox[2] || 0; + height = fillBBox[3] / bBox[3] || 0; + patternData.boundingBox = [x, y, x + width, y + height]; + patternData.xStep = width; + patternData.yStep = height; + } + patternTransformMatrix = context.pdf.unitMatrix; + patternTransform = getAttribute(this.pattern.element, context.styleSheets, 'patternTransform', 'transform'); + if (patternTransform) { + patternTransformMatrix = parseTransform(patternTransform, context); + } + matrix = patternContentUnitsMatrix; + matrix = context.pdf.matrixMult(matrix, patternUnitsMatrix); // translate by + matrix = context.pdf.matrixMult(matrix, patternTransformMatrix); + matrix = context.pdf.matrixMult(matrix, context.transform); + patternData.matrix = matrix; + return [2 /*return*/, patternData]; + } + }); + }); + }; + return PatternFill; +}()); + +function parseFill(fill, context) { + var url = iriReference.exec(fill); + if (url) { + var fillUrl = url[1]; + var fillNode = context.refsHandler.get(fillUrl); + if (fillNode && (fillNode instanceof LinearGradient || fillNode instanceof RadialGradient)) { + return getGradientFill(fillUrl, fillNode, context); + } + else if (fillNode && fillNode instanceof Pattern) { + return new PatternFill(fillUrl, fillNode); + } + else { + // unsupported fill argument -> fill black + return new ColorFill(new RGBColor('rgb(0, 0, 0)')); + } + } + else { + // plain color + var fillColor = parseColor(fill, context.attributeState); + if (fillColor.ok) { + return new ColorFill(fillColor); + } + else if (fill === 'none') { + return null; + } + else { + return null; + } + } +} +function getGradientFill(fillUrl, gradient, context) { + // "It is necessary that at least two stops are defined to have a gradient effect. If no stops are + // defined, then painting shall occur as if 'none' were specified as the paint style. If one stop + // is defined, then paint with the solid color fill using the color defined for that gradient + // stop." + var stops = gradient.getStops(context.styleSheets); + if (stops.length === 0) { + return null; + } + if (stops.length === 1) { + var stopColor = stops[0].color; + var rgbColor = new RGBColor(); + rgbColor.ok = true; + rgbColor.r = stopColor[0]; + rgbColor.g = stopColor[1]; + rgbColor.b = stopColor[2]; + rgbColor.a = stops[0].opacity; + return new ColorFill(rgbColor); + } + return new GradientFill(fillUrl, gradient); +} - if (arg === 'frame') { - // process of timeout, request animation frame - delete this._monitoring_handle; - this._monitoring_frame = requestAnimationFrame(this._runMonitoring.bind(this, 'draw')); - return; - } +function parseAttributes(context, svgNode, node) { + var domNode = node || svgNode.element; + // update color first so currentColor becomes available for this node + var color = getAttribute(domNode, context.styleSheets, 'color'); + if (color) { + var fillColor = parseColor(color, context.attributeState); + if (fillColor.ok) { + context.attributeState.color = fillColor; + } + else { + // invalid color passed, reset to black + context.attributeState.color = new RGBColor('rgb(0,0,0)'); + } + } + var visibility = getAttribute(domNode, context.styleSheets, 'visibility'); + if (visibility) { + context.attributeState.visibility = visibility; + } + // fill mode + var fill = getAttribute(domNode, context.styleSheets, 'fill'); + if (fill) { + context.attributeState.fill = parseFill(fill, context); + } + // opacity is realized via a pdf graphics state + var fillOpacity = getAttribute(domNode, context.styleSheets, 'fill-opacity'); + if (fillOpacity) { + context.attributeState.fillOpacity = parseFloat(fillOpacity); + } + var strokeOpacity = getAttribute(domNode, context.styleSheets, 'stroke-opacity'); + if (strokeOpacity) { + context.attributeState.strokeOpacity = parseFloat(strokeOpacity); + } + var opacity = getAttribute(domNode, context.styleSheets, 'opacity'); + if (opacity) { + context.attributeState.opacity = parseFloat(opacity); + } + // stroke mode + var strokeWidth = getAttribute(domNode, context.styleSheets, 'stroke-width'); + if (strokeWidth !== void 0 && strokeWidth !== '') { + context.attributeState.strokeWidth = Math.abs(parseFloat(strokeWidth)); + } + var stroke = getAttribute(domNode, context.styleSheets, 'stroke'); + if (stroke) { + if (stroke === 'none') { + context.attributeState.stroke = null; + } + else { + // gradients, patterns not supported for strokes ... + var strokeRGB = parseColor(stroke, context.attributeState); + if (strokeRGB.ok) { + context.attributeState.stroke = new ColorFill(strokeRGB); + } + } + } + if (stroke && context.attributeState.stroke instanceof ColorFill) { + context.attributeState.contextStroke = context.attributeState.stroke.color; + } + if (fill && context.attributeState.fill instanceof ColorFill) { + context.attributeState.contextFill = context.attributeState.fill.color; + } + var lineCap = getAttribute(domNode, context.styleSheets, 'stroke-linecap'); + if (lineCap) { + context.attributeState.strokeLinecap = lineCap; + } + var lineJoin = getAttribute(domNode, context.styleSheets, 'stroke-linejoin'); + if (lineJoin) { + context.attributeState.strokeLinejoin = lineJoin; + } + var dashArray = getAttribute(domNode, context.styleSheets, 'stroke-dasharray'); + if (dashArray) { + var dashOffset = parseInt(getAttribute(domNode, context.styleSheets, 'stroke-dashoffset') || '0'); + context.attributeState.strokeDasharray = parseFloats(dashArray); + context.attributeState.strokeDashoffset = dashOffset; + } + var miterLimit = getAttribute(domNode, context.styleSheets, 'stroke-miterlimit'); + if (miterLimit !== void 0 && miterLimit !== '') { + context.attributeState.strokeMiterlimit = parseFloat(miterLimit); + } + var xmlSpace = domNode.getAttribute('xml:space'); + if (xmlSpace) { + context.attributeState.xmlSpace = xmlSpace; + } + var whiteSpace = getAttribute(domNode, context.styleSheets, 'white-space'); + if (whiteSpace) { + context.attributeState.whiteSpace = whiteSpace; + } + var fontWeight = getAttribute(domNode, context.styleSheets, 'font-weight'); + if (fontWeight) { + context.attributeState.fontWeight = fontWeight; + } + var fontStyle = getAttribute(domNode, context.styleSheets, 'font-style'); + if (fontStyle) { + context.attributeState.fontStyle = fontStyle; + } + var fontFamily = getAttribute(domNode, context.styleSheets, 'font-family'); + if (fontFamily) { + var fontFamilies = FontFamily.parse(fontFamily); + context.attributeState.fontFamily = findFirstAvailableFontFamily(context.attributeState, fontFamilies, context); + } + var fontSize = getAttribute(domNode, context.styleSheets, 'font-size'); + if (fontSize) { + var pdfFontSize = context.pdf.getFontSize(); + context.attributeState.fontSize = toPixels(fontSize, pdfFontSize); + } + var alignmentBaseline = getAttribute(domNode, context.styleSheets, 'vertical-align') || + getAttribute(domNode, context.styleSheets, 'alignment-baseline'); + if (alignmentBaseline) { + var matchArr = alignmentBaseline.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/); + if (matchArr) { + context.attributeState.alignmentBaseline = matchArr[0]; + } + } + var textAnchor = getAttribute(domNode, context.styleSheets, 'text-anchor'); + if (textAnchor) { + context.attributeState.textAnchor = textAnchor; + } + var fillRule = getAttribute(domNode, context.styleSheets, 'fill-rule'); + if (fillRule) { + context.attributeState.fillRule = fillRule; + } +} +function applyAttributes(childContext, parentContext, node) { + var fillOpacity = 1.0, strokeOpacity = 1.0; + fillOpacity *= childContext.attributeState.fillOpacity; + fillOpacity *= childContext.attributeState.opacity; + if (childContext.attributeState.fill instanceof ColorFill && + typeof childContext.attributeState.fill.color.a !== 'undefined') { + fillOpacity *= childContext.attributeState.fill.color.a; + } + strokeOpacity *= childContext.attributeState.strokeOpacity; + strokeOpacity *= childContext.attributeState.opacity; + if (childContext.attributeState.stroke instanceof ColorFill && + typeof childContext.attributeState.stroke.color.a !== 'undefined') { + strokeOpacity *= childContext.attributeState.stroke.color.a; + } + var hasFillOpacity = fillOpacity < 1.0; + var hasStrokeOpacity = strokeOpacity < 1.0; + // This is a workaround for symbols that are used multiple times with different + // fill/stroke attributes. All paths within symbols are both filled and stroked + // and we set the fill/stroke to transparent if the use element has + // fill/stroke="none". + if (nodeIs(node, 'use')) { + hasFillOpacity = true; + hasStrokeOpacity = true; + fillOpacity *= childContext.attributeState.fill ? 1 : 0; + strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; + } + else if (childContext.withinUse) { + if (childContext.attributeState.fill !== parentContext.attributeState.fill) { + hasFillOpacity = true; + fillOpacity *= childContext.attributeState.fill ? 1 : 0; + } + else if (hasFillOpacity && !childContext.attributeState.fill) { + fillOpacity = 0; + } + if (childContext.attributeState.stroke !== parentContext.attributeState.stroke) { + hasStrokeOpacity = true; + strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; + } + else if (hasStrokeOpacity && !childContext.attributeState.stroke) { + strokeOpacity = 0; + } + } + if (hasFillOpacity || hasStrokeOpacity) { + var gState = {}; + hasFillOpacity && (gState['opacity'] = fillOpacity); + hasStrokeOpacity && (gState['stroke-opacity'] = strokeOpacity); + childContext.pdf.setGState(new GState(gState)); + } + if (childContext.attributeState.fill && + childContext.attributeState.fill !== parentContext.attributeState.fill && + childContext.attributeState.fill instanceof ColorFill && + childContext.attributeState.fill.color.ok && + !nodeIs(node, 'text')) { + // text fill color will be applied through setTextColor() + childContext.pdf.setFillColor(childContext.attributeState.fill.color.r, childContext.attributeState.fill.color.g, childContext.attributeState.fill.color.b); + } + if (childContext.attributeState.strokeWidth !== parentContext.attributeState.strokeWidth) { + childContext.pdf.setLineWidth(childContext.attributeState.strokeWidth); + } + if (childContext.attributeState.stroke !== parentContext.attributeState.stroke && + childContext.attributeState.stroke instanceof ColorFill) { + childContext.pdf.setDrawColor(childContext.attributeState.stroke.color.r, childContext.attributeState.stroke.color.g, childContext.attributeState.stroke.color.b); + } + if (childContext.attributeState.strokeLinecap !== parentContext.attributeState.strokeLinecap) { + childContext.pdf.setLineCap(childContext.attributeState.strokeLinecap); + } + if (childContext.attributeState.strokeLinejoin !== parentContext.attributeState.strokeLinejoin) { + childContext.pdf.setLineJoin(childContext.attributeState.strokeLinejoin); + } + if ((childContext.attributeState.strokeDasharray !== parentContext.attributeState.strokeDasharray || + childContext.attributeState.strokeDashoffset !== + parentContext.attributeState.strokeDashoffset) && + childContext.attributeState.strokeDasharray) { + childContext.pdf.setLineDashPattern(childContext.attributeState.strokeDasharray, childContext.attributeState.strokeDashoffset); + } + if (childContext.attributeState.strokeMiterlimit !== parentContext.attributeState.strokeMiterlimit) { + childContext.pdf.setLineMiterLimit(childContext.attributeState.strokeMiterlimit); + } + var font; + if (childContext.attributeState.fontFamily !== parentContext.attributeState.fontFamily) { + if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { + font = fontAliases[childContext.attributeState.fontFamily]; + } + else { + font = childContext.attributeState.fontFamily; + } + } + if (childContext.attributeState.fill && + childContext.attributeState.fill !== parentContext.attributeState.fill && + childContext.attributeState.fill instanceof ColorFill && + childContext.attributeState.fill.color.ok) { + var fillColor = childContext.attributeState.fill.color; + childContext.pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); + } + var fontStyle; + if (childContext.attributeState.fontWeight !== parentContext.attributeState.fontWeight || + childContext.attributeState.fontStyle !== parentContext.attributeState.fontStyle) { + fontStyle = combineFontStyleAndFontWeight(childContext.attributeState.fontStyle, childContext.attributeState.fontWeight); + } + if (font !== undefined || fontStyle !== undefined) { + if (font === undefined) { + if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { + font = fontAliases[childContext.attributeState.fontFamily]; + } + else { + font = childContext.attributeState.fontFamily; + } + } + childContext.pdf.setFont(font, fontStyle); + } + if (childContext.attributeState.fontSize !== parentContext.attributeState.fontSize) { + // correct for a jsPDF-instance measurement unit that differs from `pt` + childContext.pdf.setFontSize(childContext.attributeState.fontSize * childContext.pdf.internal.scaleFactor); + } +} +function applyContext(context) { + var attributeState = context.attributeState, pdf = context.pdf; + var fillOpacity = 1.0, strokeOpacity = 1.0; + fillOpacity *= attributeState.fillOpacity; + fillOpacity *= attributeState.opacity; + if (attributeState.fill instanceof ColorFill && + typeof attributeState.fill.color.a !== 'undefined') { + fillOpacity *= attributeState.fill.color.a; + } + strokeOpacity *= attributeState.strokeOpacity; + strokeOpacity *= attributeState.opacity; + if (attributeState.stroke instanceof ColorFill && + typeof attributeState.stroke.color.a !== 'undefined') { + strokeOpacity *= attributeState.stroke.color.a; + } + var gState = {}; + gState['opacity'] = fillOpacity; + gState['stroke-opacity'] = strokeOpacity; + pdf.setGState(new GState(gState)); + if (attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok) { + // text fill color will be applied through setTextColor() + pdf.setFillColor(attributeState.fill.color.r, attributeState.fill.color.g, attributeState.fill.color.b); + } + else { + pdf.setFillColor(0, 0, 0); + } + pdf.setLineWidth(attributeState.strokeWidth); + if (attributeState.stroke instanceof ColorFill) { + pdf.setDrawColor(attributeState.stroke.color.r, attributeState.stroke.color.g, attributeState.stroke.color.b); + } + else { + pdf.setDrawColor(0, 0, 0); + } + pdf.setLineCap(attributeState.strokeLinecap); + pdf.setLineJoin(attributeState.strokeLinejoin); + if (attributeState.strokeDasharray) { + pdf.setLineDashPattern(attributeState.strokeDasharray, attributeState.strokeDashoffset); + } + else { + pdf.setLineDashPattern([], 0); + } + pdf.setLineMiterLimit(attributeState.strokeMiterlimit); + var font; + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily]; + } + else { + font = attributeState.fontFamily; + } + if (attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok) { + var fillColor = attributeState.fill.color; + pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); + } + else { + pdf.setTextColor(0, 0, 0); + } + var fontStyle = ''; + if (attributeState.fontWeight === 'bold') { + fontStyle = 'bold'; + } + if (attributeState.fontStyle === 'italic') { + fontStyle += 'italic'; + } + if (fontStyle === '') { + fontStyle = 'normal'; + } + if (font !== undefined || fontStyle !== undefined) { + if (font === undefined) { + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily]; + } + else { + font = attributeState.fontFamily; + } + } + pdf.setFont(font, fontStyle); + } + else { + pdf.setFont('helvetica', fontStyle); + } + // correct for a jsPDF-instance measurement unit that differs from `pt` + pdf.setFontSize(attributeState.fontSize * pdf.internal.scaleFactor); +} - if (arg === 'draw') { - delete this._monitoring_frame; - this.updateItems(); - } +function getClipPathNode(clipPathAttr, targetNode, context) { + var match = iriReference.exec(clipPathAttr); + if (!match) { + return undefined; + } + var clipPathId = match[1]; + var clipNode = context.refsHandler.get(clipPathId); + return clipNode || undefined; +} +function applyClipPath(targetNode, clipPathNode, context) { + return __awaiter(this, void 0, void 0, function () { + var clipContext, bBox; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + clipContext = context.clone(); + if (clipPathNode.element.hasAttribute('clipPathUnits') && + clipPathNode.element.getAttribute('clipPathUnits').toLowerCase() === 'objectboundingbox') { + bBox = targetNode.getBoundingBox(context); + clipContext.transform = context.pdf.matrixMult(context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]), context.transform); + } + return [4 /*yield*/, clipPathNode.apply(clipContext)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} - this._monitoring_handle = setTimeout(this._runMonitoring.bind(this, 'frame'), this.getMonitoringInterval()); - } +var RenderedNode = /** @class */ (function (_super) { + __extends(RenderedNode, _super); + function RenderedNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + RenderedNode.prototype.render = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var context, clipPathAttribute, hasClipPath, clipNode; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { + return [2 /*return*/]; + } + context = parentContext.clone(); + context.transform = context.pdf.matrixMult(this.computeNodeTransform(context), parentContext.transform); + parseAttributes(context, this); + clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); + hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; + if (!hasClipPath) return [3 /*break*/, 5]; + clipNode = getClipPathNode(clipPathAttribute, this, context); + if (!clipNode) return [3 /*break*/, 4]; + if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; + context.pdf.saveGraphicsState(); + return [4 /*yield*/, applyClipPath(this, clipNode, context)]; + case 1: + _a.sent(); + return [3 /*break*/, 3]; + case 2: return [2 /*return*/]; + case 3: return [3 /*break*/, 5]; + case 4: + hasClipPath = false; + _a.label = 5; + case 5: + if (!context.withinClipPath) { + context.pdf.saveGraphicsState(); + } + applyAttributes(context, parentContext, this.element); + return [4 /*yield*/, this.renderCore(context)]; + case 6: + _a.sent(); + if (!context.withinClipPath) { + context.pdf.restoreGraphicsState(); + } + if (hasClipPath) { + context.pdf.restoreGraphicsState(); + } + return [2 /*return*/]; + } + }); + }); + }; + return RenderedNode; +}(SvgNode)); - /** @summary Returns configured monitoring interval in ms */ - getMonitoringInterval() { - return this._monitoring_interval || 3000; - } +var GraphicsNode = /** @class */ (function (_super) { + __extends(GraphicsNode, _super); + function GraphicsNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + return GraphicsNode; +}(RenderedNode)); + +var GeometryNode = /** @class */ (function (_super) { + __extends(GeometryNode, _super); + function GeometryNode(hasMarkers, element, children) { + var _this = _super.call(this, element, children) || this; + _this.cachedPath = null; + _this.hasMarkers = hasMarkers; + return _this; + } + GeometryNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var path; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + path = this.getCachedPath(context); + if (path === null || path.segments.length === 0) { + return [2 /*return*/]; + } + if (context.withinClipPath) { + path.transform(context.transform); + } + else { + context.pdf.setCurrentTransformationMatrix(context.transform); + } + path.draw(context); + return [4 /*yield*/, this.fillOrStroke(context)]; + case 1: + _a.sent(); + if (!this.hasMarkers) return [3 /*break*/, 3]; + return [4 /*yield*/, this.drawMarkers(context, path)]; + case 2: + _a.sent(); + _a.label = 3; + case 3: return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.getCachedPath = function (context) { + return this.cachedPath || (this.cachedPath = this.getPath(context)); + }; + GeometryNode.prototype.drawMarkers = function (context, path) { + return __awaiter(this, void 0, void 0, function () { + var markers; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + markers = this.getMarkers(path, context); + return [4 /*yield*/, markers.draw(context.clone({ transform: context.pdf.unitMatrix }))]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.fillOrStroke = function (context) { + return __awaiter(this, void 0, void 0, function () { + var fill, stroke, fillData, _a, isNodeFillRuleEvenOdd; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (context.withinClipPath) { + return [2 /*return*/]; + } + fill = context.attributeState.fill; + stroke = context.attributeState.stroke && context.attributeState.strokeWidth !== 0; + if (!fill) return [3 /*break*/, 2]; + return [4 /*yield*/, fill.getFillData(this, context)]; + case 1: + _a = _b.sent(); + return [3 /*break*/, 3]; + case 2: + _a = undefined; + _b.label = 3; + case 3: + fillData = _a; + isNodeFillRuleEvenOdd = context.attributeState.fillRule === 'evenodd'; + // This is a workaround for symbols that are used multiple times with different + // fill/stroke attributes. All paths within symbols are both filled and stroked + // and we set the fill/stroke to transparent if the use element has + // fill/stroke="none". + if ((fill && stroke) || context.withinUse) { + if (isNodeFillRuleEvenOdd) { + context.pdf.fillStrokeEvenOdd(fillData); + } + else { + context.pdf.fillStroke(fillData); + } + } + else if (fill) { + if (isNodeFillRuleEvenOdd) { + context.pdf.fillEvenOdd(fillData); + } + else { + context.pdf.fill(fillData); + } + } + else if (stroke) { + context.pdf.stroke(); + } + else { + context.pdf.discardPath(); + } + return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.getBoundingBoxCore = function (context) { + var path = this.getCachedPath(context); + if (!path || !path.segments.length) { + return [0, 0, 0, 0]; + } + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = Number.NEGATIVE_INFINITY; + var maxY = Number.NEGATIVE_INFINITY; + var x = 0, y = 0; + for (var i = 0; i < path.segments.length; i++) { + var seg = path.segments[i]; + if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { + x = seg.x; + y = seg.y; + } + if (seg instanceof CurveTo) { + minX = Math.min(minX, x, seg.x1, seg.x2, seg.x); + maxX = Math.max(maxX, x, seg.x1, seg.x2, seg.x); + minY = Math.min(minY, y, seg.y1, seg.y2, seg.y); + maxY = Math.max(maxY, y, seg.y1, seg.y2, seg.y); + } + else { + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + } + return [minX, minY, maxX - minX, maxY - minY]; + }; + GeometryNode.prototype.getMarkers = function (path, context) { + var markerStart = getAttribute(this.element, context.styleSheets, 'marker-start'); + var markerMid = getAttribute(this.element, context.styleSheets, 'marker-mid'); + var markerEnd = getAttribute(this.element, context.styleSheets, 'marker-end'); + var markers = new MarkerList(); + if (markerStart || markerMid || markerEnd) { + markerEnd && (markerEnd = iri(markerEnd)); + markerStart && (markerStart = iri(markerStart)); + markerMid && (markerMid = iri(markerMid)); + var list_1 = path.segments; + var prevAngle = [1, 0], curAngle = void 0, first = false, firstAngle = [1, 0], last_1 = false; + var _loop_1 = function (i) { + var curr = list_1[i]; + var hasStartMarker = markerStart && + (i === 1 || (!(list_1[i] instanceof MoveTo) && list_1[i - 1] instanceof MoveTo)); + if (hasStartMarker) { + list_1.forEach(function (value, index) { + if (!last_1 && value instanceof Close && index > i) { + var tmp = list_1[index - 1]; + last_1 = + (tmp instanceof MoveTo || tmp instanceof LineTo || tmp instanceof CurveTo) && tmp; + } + }); + } + var hasEndMarker = markerEnd && + (i === list_1.length - 1 || (!(list_1[i] instanceof MoveTo) && list_1[i + 1] instanceof MoveTo)); + var hasMidMarker = markerMid && i > 0 && !(i === 1 && list_1[i - 1] instanceof MoveTo); + var prev = list_1[i - 1] || null; + if (prev instanceof MoveTo || prev instanceof LineTo || prev instanceof CurveTo) { + if (curr instanceof CurveTo) { + hasStartMarker && + markers.addMarker(new Marker(markerStart, [prev.x, prev.y], + // @ts-ignore + getAngle(last_1 ? [last_1.x, last_1.y] : [prev.x, prev.y], [curr.x1, curr.y1]), true)); + hasEndMarker && + markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], getAngle([curr.x2, curr.y2], [curr.x, curr.y]))); + if (hasMidMarker) { + curAngle = getDirectionVector([prev.x, prev.y], [curr.x1, curr.y1]); + curAngle = + prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(curAngle[1], curAngle[0]))); + } + prevAngle = getDirectionVector([curr.x2, curr.y2], [curr.x, curr.y]); + } + else if (curr instanceof MoveTo || curr instanceof LineTo) { + curAngle = getDirectionVector([prev.x, prev.y], [curr.x, curr.y]); + if (hasStartMarker) { + // @ts-ignore + var angle = last_1 ? getDirectionVector([last_1.x, last_1.y], [curr.x, curr.y]) : curAngle; + markers.addMarker(new Marker(markerStart, [prev.x, prev.y], Math.atan2(angle[1], angle[0]), true)); + } + hasEndMarker && + markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], Math.atan2(curAngle[1], curAngle[0]))); + if (hasMidMarker) { + var angle = curr instanceof MoveTo + ? prevAngle + : prev instanceof MoveTo + ? curAngle + : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); + } + prevAngle = curAngle; + } + else if (curr instanceof Close) { + // @ts-ignore + curAngle = getDirectionVector([prev.x, prev.y], [first.x, first.y]); + if (hasMidMarker) { + var angle = prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); + } + if (hasEndMarker) { + var angle = normalize(addVectors(curAngle, firstAngle)); + markers.addMarker( + // @ts-ignore + new Marker(markerEnd, [first.x, first.y], Math.atan2(angle[1], angle[0]))); + } + prevAngle = curAngle; + } + } + else { + first = curr instanceof MoveTo && curr; + var next = list_1[i + 1]; + if (next instanceof MoveTo || next instanceof LineTo || next instanceof CurveTo) { + // @ts-ignore + firstAngle = getDirectionVector([first.x, first.y], [next.x, next.y]); + } + } + }; + for (var i = 0; i < list_1.length; i++) { + _loop_1(i); + } + } + markers.markers.forEach(function (marker) { + var markerNode = context.refsHandler.get(marker.id); + if (!markerNode) + return; + var orient = getAttribute(markerNode.element, context.styleSheets, 'orient'); + if (orient == null) + return; + if (marker.isStartMarker && orient === 'auto-start-reverse') { + marker.angle += Math.PI; + } + if (!isNaN(Number(orient))) { + marker.angle = (parseFloat(orient) / 180) * Math.PI; + } + }); + return markers; + }; + return GeometryNode; +}(GraphicsNode)); +function iri(attribute) { + var match = iriReference.exec(attribute); + return (match && match[1]) || undefined; +} - /** @summary Returns true when monitoring is enabled */ - isMonitoring() { - return this._monitoring_on; - } +var Line = /** @class */ (function (_super) { + __extends(Line, _super); + function Line(node, children) { + return _super.call(this, true, node, children) || this; + } + Line.prototype.getPath = function (context) { + if (context.withinClipPath || context.attributeState.stroke === null) { + return null; + } + var x1 = parseFloat(this.element.getAttribute('x1') || '0'), y1 = parseFloat(this.element.getAttribute('y1') || '0'); + var x2 = parseFloat(this.element.getAttribute('x2') || '0'), y2 = parseFloat(this.element.getAttribute('y2') || '0'); + if (!(x1 || x2 || y1 || y2)) { + return null; + } + return new Path().moveTo(x1, y1).lineTo(x2, y2); + }; + Line.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Line.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Line.prototype.fillOrStroke = function (context) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + context.attributeState.fill = null; + return [4 /*yield*/, _super.prototype.fillOrStroke.call(this, context)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return Line; +}(GeometryNode)); - /** @summary Assign default layout and place where drawing will be performed - * @param {string} layout - layout like 'simple' or 'grid2x2' - * @param {string} frameid - DOM element id where object drawing will be performed */ - setDisplay(layout, frameid) { - if (!frameid && isObject(layout)) { - this.disp = layout; - this.disp_kind = 'custom'; - this.disp_frameid = null; - } else { - this.disp_kind = layout; - this.disp_frameid = frameid; - } +var Symbol$1 = /** @class */ (function (_super) { + __extends(Symbol, _super); + function Symbol() { + return _super !== null && _super.apply(this, arguments) || this; + } + Symbol.prototype.apply = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var context, clipPathAttribute, hasClipPath, clipNode, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { + return [2 /*return*/]; + } + context = parentContext.clone(); + context.transform = context.pdf.unitMatrix; + parseAttributes(context, this); + clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); + hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; + if (!hasClipPath) return [3 /*break*/, 3]; + clipNode = getClipPathNode(clipPathAttribute, this, context); + if (!clipNode) return [3 /*break*/, 3]; + if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; + return [4 /*yield*/, applyClipPath(this, clipNode, context)]; + case 1: + _b.sent(); + return [3 /*break*/, 3]; + case 2: return [2 /*return*/]; + case 3: + applyAttributes(context, parentContext, this.element); + _i = 0, _a = this.children; + _b.label = 4; + case 4: + if (!(_i < _a.length)) return [3 /*break*/, 7]; + child = _a[_i]; + return [4 /*yield*/, child.render(context)]; + case 5: + _b.sent(); + _b.label = 6; + case 6: + _i++; + return [3 /*break*/, 4]; + case 7: return [2 /*return*/]; + } + }); + }); + }; + Symbol.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + Symbol.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Symbol.prototype.computeNodeTransformCore = function (context) { + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); + var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + // TODO: implement refX/refY - this is still to do because common browsers don't seem to support the feature yet + // x += parseFloat(this.element.getAttribute("refX")) || 0; ??? + // y += parseFloat(this.element.getAttribute("refY")) || 0; ??? + var viewBox = this.element.getAttribute('viewBox'); + if (viewBox) { + var box = parseFloats(viewBox); + var width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || + getAttribute(this.element.ownerSVGElement, context.styleSheets, 'width') || + viewBox[2]); + var height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || + getAttribute(this.element.ownerSVGElement, context.styleSheets, 'height') || + viewBox[3]); + return computeViewBoxTransform(this.element, box, x, y, width, height, context); + } + else { + return context.pdf.Matrix(1, 0, 0, 1, x, y); + } + }; + return Symbol; +}(NonRenderedNode)); + +var Viewport = /** @class */ (function () { + function Viewport(width, height) { + this.width = width; + this.height = height; + } + return Viewport; +}()); + +/** + * Draws the element referenced by a use node, makes use of pdf's XObjects/FormObjects so nodes are only written once + * to the pdf document. This highly reduces the file size and computation time. + */ +var Use = /** @class */ (function (_super) { + __extends(Use, _super); + function Use() { + return _super !== null && _super.apply(this, arguments) || this; + } + Use.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var pf, url, id, refNode, refNodeOpensViewport, x, y, width, height, t, viewBox, contextColors, refContext; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + pf = parseFloat; + url = this.element.getAttribute('href') || this.element.getAttribute('xlink:href'); + // just in case someone has the idea to use empty use-tags, wtf??? + if (!url) + return [2 /*return*/]; + id = url.substring(1); + refNode = context.refsHandler.get(id); + refNodeOpensViewport = nodeIs(refNode.element, 'symbol,svg') && refNode.element.hasAttribute('viewBox'); + x = pf(getAttribute(this.element, context.styleSheets, 'x') || '0'); + y = pf(getAttribute(this.element, context.styleSheets, 'y') || '0'); + width = undefined; + height = undefined; + if (refNodeOpensViewport) { + // inherits width/height only to svg/symbol + // if there is no viewBox attribute, width/height don't have an effect + // in theory, the default value for width/height is 100%, but we currently don't support this + width = pf(getAttribute(this.element, context.styleSheets, 'width') || + getAttribute(refNode.element, context.styleSheets, 'width') || + '0'); + height = pf(getAttribute(this.element, context.styleSheets, 'height') || + getAttribute(refNode.element, context.styleSheets, 'height') || + '0'); + // accumulate x/y to calculate the viewBox transform + x += pf(getAttribute(refNode.element, context.styleSheets, 'x') || '0'); + y += pf(getAttribute(refNode.element, context.styleSheets, 'y') || '0'); + viewBox = parseFloats(refNode.element.getAttribute('viewBox')); + t = computeViewBoxTransform(refNode.element, viewBox, x, y, width, height, context); + } + else { + t = context.pdf.Matrix(1, 0, 0, 1, x, y); + } + contextColors = AttributeState.getContextColors(context, true); + refContext = new Context(context.pdf, { + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + withinUse: true, + viewport: refNodeOpensViewport ? new Viewport(width, height) : context.viewport, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure, + attributeState: Object.assign(AttributeState.default(), contextColors) + }); + return [4 /*yield*/, context.refsHandler.getRendered(id, contextColors, function (node) { + return Use.renderReferencedNode(node, id, refContext); + })]; + case 1: + _a.sent(); + context.pdf.saveGraphicsState(); + context.pdf.setCurrentTransformationMatrix(context.transform); + // apply the bbox (i.e. clip) if needed + if (refNodeOpensViewport && + getAttribute(refNode.element, context.styleSheets, 'overflow') !== 'visible') { + context.pdf.rect(x, y, width, height); + context.pdf.clip().discardPath(); + } + context.pdf.doFormObject(context.refsHandler.generateKey(id, contextColors), t); + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + } + }); + }); + }; + Use.renderReferencedNode = function (node, id, refContext) { + return __awaiter(this, void 0, void 0, function () { + var bBox; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + bBox = node.getBoundingBox(refContext); + // The content of a PDF form object is implicitly clipped at its /BBox property. + // SVG, however, applies its clip rect at the attribute, which may modify it. + // So, make the bBox a lot larger than it needs to be and hope any thick strokes are + // still within. + bBox = [bBox[0] - 0.5 * bBox[2], bBox[1] - 0.5 * bBox[3], bBox[2] * 2, bBox[3] * 2]; + refContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], refContext.pdf.unitMatrix); + if (!(node instanceof Symbol$1)) return [3 /*break*/, 2]; + return [4 /*yield*/, node.apply(refContext)]; + case 1: + _a.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, node.render(refContext)]; + case 3: + _a.sent(); + _a.label = 4; + case 4: + refContext.pdf.endFormObject(refContext.refsHandler.generateKey(id, refContext.attributeState)); + return [2 /*return*/]; + } + }); + }); + }; + Use.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Use.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Use.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return Use; +}(GraphicsNode)); + +var Rect = /** @class */ (function (_super) { + __extends(Rect, _super); + function Rect(element, children) { + return _super.call(this, false, element, children) || this; + } + Rect.prototype.getPath = function (context) { + var w = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'); + var h = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'); + if (!isFinite(w) || w <= 0 || !isFinite(h) || h <= 0) { + return null; + } + var rxAttr = getAttribute(this.element, context.styleSheets, 'rx'); + var ryAttr = getAttribute(this.element, context.styleSheets, 'ry'); + var rx = Math.min(parseFloat(rxAttr || ryAttr || '0'), w * 0.5); + var ry = Math.min(parseFloat(ryAttr || rxAttr || '0'), h * 0.5); + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); + var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + var arc = (4 / 3) * (Math.SQRT2 - 1); + if (rx === 0 && ry === 0) { + return new Path() + .moveTo(x, y) + .lineTo(x + w, y) + .lineTo(x + w, y + h) + .lineTo(x, y + h) + .close(); + } + else { + return new Path() + .moveTo((x += rx), y) + .lineTo((x += w - 2 * rx), y) + .curveTo(x + rx * arc, y, x + rx, y + (ry - ry * arc), (x += rx), (y += ry)) + .lineTo(x, (y += h - 2 * ry)) + .curveTo(x, y + ry * arc, x - rx * arc, y + ry, (x -= rx), (y += ry)) + .lineTo((x += -w + 2 * rx), y) + .curveTo(x - rx * arc, y, x - rx, y - ry * arc, (x -= rx), (y -= ry)) + .lineTo(x, (y += -h + 2 * ry)) + .curveTo(x, y - ry * arc, x + rx * arc, y - ry, (x += rx), (y -= ry)) + .close(); + } + }; + Rect.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Rect.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return Rect; +}(GeometryNode)); - if (!this.register_resize && (this.disp_kind !== 'batch')) { - this.register_resize = true; - registerForResize(this); - } - } +var EllipseBase = /** @class */ (function (_super) { + __extends(EllipseBase, _super); + function EllipseBase(element, children) { + return _super.call(this, false, element, children) || this; + } + EllipseBase.prototype.getPath = function (context) { + var rx = this.getRx(context); + var ry = this.getRy(context); + if (!isFinite(rx) || ry <= 0 || !isFinite(ry) || ry <= 0) { + return null; + } + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'cx') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'cy') || '0'); + var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, ly = (4 / 3) * (Math.SQRT2 - 1) * ry; + return new Path() + .moveTo(x + rx, y) + .curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry) + .curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y) + .curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry) + .curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); + }; + EllipseBase.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + EllipseBase.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return EllipseBase; +}(GeometryNode)); - /** @summary Returns configured layout */ - getLayout() { - return this.disp_kind; - } +var Ellipse = /** @class */ (function (_super) { + __extends(Ellipse, _super); + function Ellipse(element, children) { + return _super.call(this, element, children) || this; + } + Ellipse.prototype.getRx = function (context) { + return parseFloat(getAttribute(this.element, context.styleSheets, 'rx') || '0'); + }; + Ellipse.prototype.getRy = function (context) { + return parseFloat(getAttribute(this.element, context.styleSheets, 'ry') || '0'); + }; + return Ellipse; +}(EllipseBase)); + +function getTextRenderingMode(attributeState) { + var renderingMode = 'invisible'; + var doStroke = attributeState.stroke && attributeState.strokeWidth !== 0; + var doFill = attributeState.fill; + if (doFill && doStroke) { + renderingMode = 'fillThenStroke'; + } + else if (doFill) { + renderingMode = 'fill'; + } + else if (doStroke) { + renderingMode = 'stroke'; + } + return renderingMode; +} +function transformXmlSpace(trimmedText, attributeState) { + trimmedText = removeNewlines(trimmedText); + trimmedText = replaceTabsBySpace(trimmedText); + var shouldPreserve = attributeState.xmlSpace === 'preserve' || attributeState.whiteSpace === 'pre'; + if (!shouldPreserve) { + trimmedText = trimmedText.trim(); + trimmedText = consolidateSpaces(trimmedText); + } + return trimmedText; +} +function removeNewlines(str) { + return str.replace(/[\n\r]/g, ''); +} +function replaceTabsBySpace(str) { + return str.replace(/[\t]/g, ' '); +} +function consolidateSpaces(str) { + return str.replace(/ +/g, ' '); +} +// applies text transformations to a text node +function transformText(node, text, context) { + var textTransform = getAttribute(node, context.styleSheets, 'text-transform'); + switch (textTransform) { + case 'uppercase': + return text.toUpperCase(); + case 'lowercase': + return text.toLowerCase(); + default: + return text; + // TODO: capitalize, full-width + } +} +function trimLeft(str) { + return str.replace(/^\s+/, ''); +} +function trimRight(str) { + return str.replace(/\s+$/, ''); +} - /** @summary Remove painter reference from hierarhcy - * @private */ - removePainter(obj_painter) { - this.forEachItem(item => { - if (item._painter === obj_painter) { - // delete painter reference - delete item._painter; - // also clear data which could be associated with item - if (isFunc(item.clear)) item.clear(); - } - }); - } +/** + * @param {string} textAnchor + * @param {number} originX + * @param {number} originY + * @constructor + */ +var TextChunk = /** @class */ (function () { + function TextChunk(parent, textAnchor, originX, originY) { + this.textNode = parent; + this.texts = []; + this.textNodes = []; + this.contexts = []; + this.textAnchor = textAnchor; + this.originX = originX; + this.originY = originY; + this.textMeasures = []; + } + TextChunk.prototype.setX = function (originX) { + this.originX = originX; + }; + TextChunk.prototype.setY = function (originY) { + this.originY = originY; + }; + TextChunk.prototype.add = function (tSpan, text, context) { + this.texts.push(text); + this.textNodes.push(tSpan); + this.contexts.push(context); + }; + TextChunk.prototype.rightTrimText = function () { + for (var r = this.texts.length - 1; r >= 0; r--) { + var shouldPreserve = this.contexts[r].attributeState.xmlSpace === 'preserve' || + this.contexts[r].attributeState.whiteSpace === 'pre'; + if (!shouldPreserve) { + this.texts[r] = trimRight(this.texts[r]); + } + // If find a letter, stop right-trimming + if (this.texts[r].match(/[^\s]/)) { + return false; + } + } + return true; + }; + TextChunk.prototype.measureText = function (context) { + for (var i = 0; i < this.texts.length; i++) { + this.textMeasures.push({ + width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), + length: this.texts[i].length + }); + } + }; + TextChunk.prototype.put = function (context, charSpace) { + var i, textNode, textNodeContext, textMeasure; + var alreadySeen = []; + var xs = [], ys = []; + var currentTextX = this.originX, currentTextY = this.originY; + var minX = currentTextX, maxX = currentTextX; + for (i = 0; i < this.textNodes.length; i++) { + textNode = this.textNodes[i]; + textNodeContext = this.contexts[i]; + textMeasure = this.textMeasures[i] || { + width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), + length: this.texts[i].length + }; + var x = currentTextX; + var y = currentTextY; + if (textNode.nodeName !== '#text') { + if (!alreadySeen.includes(textNode)) { + alreadySeen.push(textNode); + var tSpanDx = TextChunk.resolveRelativePositionAttribute(textNode, 'dx'); + if (tSpanDx !== null) { + x += toPixels(tSpanDx, textNodeContext.attributeState.fontSize); + } + var tSpanDy = TextChunk.resolveRelativePositionAttribute(textNode, 'dy'); + if (tSpanDy !== null) { + y += toPixels(tSpanDy, textNodeContext.attributeState.fontSize); + } + } + } + xs[i] = x; + ys[i] = y; + currentTextX = x + textMeasure.width + textMeasure.length * charSpace; + currentTextY = y; + minX = Math.min(minX, x); + maxX = Math.max(maxX, currentTextX); + } + var textOffset = 0; + switch (this.textAnchor) { + case 'start': + textOffset = 0; + break; + case 'middle': + textOffset = (maxX - minX) / 2; + break; + case 'end': + textOffset = maxX - minX; + break; + } + for (i = 0; i < this.textNodes.length; i++) { + textNode = this.textNodes[i]; + textNodeContext = this.contexts[i]; + if (textNode.nodeName !== '#text') { + if (textNodeContext.attributeState.visibility === 'hidden') { + continue; + } + } + context.pdf.saveGraphicsState(); + applyAttributes(textNodeContext, context, textNode); + var alignmentBaseline = textNodeContext.attributeState.alignmentBaseline; + var textRenderingMode = getTextRenderingMode(textNodeContext.attributeState); + context.pdf.text(this.texts[i], xs[i] - textOffset, ys[i], { + baseline: mapAlignmentBaseline(alignmentBaseline), + angle: context.transform, + renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, + charSpace: charSpace === 0 ? void 0 : charSpace + }); + context.pdf.restoreGraphicsState(); + } + return [currentTextX, currentTextY]; + }; + /** + * Resolves a positional attribute (dx, dy) on a given tSpan, possibly + * inheriting it from the nearest ancestor. Positional attributes + * are only inherited from a parent to its first child. + */ + TextChunk.resolveRelativePositionAttribute = function (element, attributeName) { + var _a; + var currentElement = element; + while (currentElement && nodeIs(currentElement, 'tspan')) { + if (currentElement.hasAttribute(attributeName)) { + return currentElement.getAttribute(attributeName); + } + if (!(((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.firstChild) === element)) { + // positional attributes are only inherited from a parent to its first child + break; + } + currentElement = currentElement.parentElement; + } + return null; + }; + return TextChunk; +}()); + +var TextNode = /** @class */ (function (_super) { + __extends(TextNode, _super); + function TextNode() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.boundingBox = []; + return _this; + } + TextNode.prototype.processTSpans = function (textNode, node, context, textChunks, currentTextSegment, trimInfo) { + var pdfFontSize = context.pdf.getFontSize(); + var shouldPreserve = context.attributeState.xmlSpace === 'preserve' || context.attributeState.whiteSpace === 'pre'; + var firstText = true, initialSpace = false; + for (var i = 0; i < node.childNodes.length; i++) { + var childNode = node.childNodes[i]; + if (!childNode.textContent) { + continue; + } + var textContent = childNode.textContent; + if (childNode.nodeName === '#text') { + var trimmedText = removeNewlines(textContent); + trimmedText = replaceTabsBySpace(trimmedText); + if (!shouldPreserve) { + trimmedText = consolidateSpaces(trimmedText); + // If first text in tspan and starts with a space + if (firstText && trimmedText.match(/^\s/)) { + initialSpace = true; + } + // No longer the first text if we've found a letter + if (trimmedText.match(/[^\s]/)) { + firstText = false; + } + // Consolidate spaces across different children + if (trimInfo.prevText.match(/\s$/)) { + trimmedText = trimLeft(trimmedText); + } + } + var transformedText = transformText(node, trimmedText, context); + currentTextSegment.add(node, transformedText, context); + trimInfo.prevText = textContent; + trimInfo.prevContext = context; + } + else if (nodeIs(childNode, 'title')) ; + else if (nodeIs(childNode, 'tspan')) { + var tSpan = childNode; + var tSpanAbsX = tSpan.getAttribute('x'); + if (tSpanAbsX !== null) { + var x = toPixels(tSpanAbsX, pdfFontSize); + currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || + context.attributeState.textAnchor, x, 0); + textChunks.push({ type: 'y', chunk: currentTextSegment }); + } + var tSpanAbsY = tSpan.getAttribute('y'); + if (tSpanAbsY !== null) { + var y = toPixels(tSpanAbsY, pdfFontSize); + currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || + context.attributeState.textAnchor, 0, y); + textChunks.push({ type: 'x', chunk: currentTextSegment }); + } + var childContext = context.clone(); + parseAttributes(childContext, textNode, tSpan); + this.processTSpans(textNode, tSpan, childContext, textChunks, currentTextSegment, trimInfo); + } + } + return initialSpace; + }; + TextNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var xOffset, charSpace, lengthAdjustment, pdfFontSize, textX, textY, dx, dy, textLength, visibility, tSpanCount, textContent, trimmedText, transformedText, defaultSize, shouldPreserve, alignmentBaseline, textRenderingMode, textChunks, currentTextSegment, initialSpace, trimRight, r, totalDefaultWidth_1, totalLength_1; + return __generator(this, function (_a) { + context.pdf.saveGraphicsState(); + xOffset = 0; + charSpace = 0; + lengthAdjustment = 1; + pdfFontSize = context.pdf.getFontSize(); + textX = toPixels(this.element.getAttribute('x'), pdfFontSize); + textY = toPixels(this.element.getAttribute('y'), pdfFontSize); + dx = toPixels(this.element.getAttribute('dx'), pdfFontSize); + dy = toPixels(this.element.getAttribute('dy'), pdfFontSize); + textLength = parseFloat(this.element.getAttribute('textLength') || '0'); + visibility = context.attributeState.visibility; + tSpanCount = this.element.childElementCount; + if (tSpanCount === 0) { + textContent = this.element.textContent || ''; + trimmedText = transformXmlSpace(textContent, context.attributeState); + transformedText = transformText(this.element, trimmedText, context); + xOffset = context.textMeasure.getTextOffset(transformedText, context.attributeState); + if (textLength > 0) { + defaultSize = context.textMeasure.measureTextWidth(transformedText, context.attributeState); + shouldPreserve = context.attributeState.xmlSpace === 'preserve' || context.attributeState.whiteSpace === 'pre'; + if (!shouldPreserve && textContent.match(/^\s/)) { + lengthAdjustment = 0; + } + charSpace = (textLength - defaultSize) / (transformedText.length - lengthAdjustment) || 0; + } + if (visibility === 'visible') { + alignmentBaseline = context.attributeState.alignmentBaseline; + textRenderingMode = getTextRenderingMode(context.attributeState); + context.pdf.text(transformedText, textX + dx - xOffset, textY + dy, { + baseline: mapAlignmentBaseline(alignmentBaseline), + angle: context.transform, + renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, + charSpace: charSpace === 0 ? void 0 : charSpace + }); + this.boundingBox = [ + textX + dx - xOffset, + textY + dy + 0.1 * pdfFontSize, + context.textMeasure.measureTextWidth(transformedText, context.attributeState), + pdfFontSize + ]; + } + } + else { + textChunks = []; + currentTextSegment = new TextChunk(this, context.attributeState.textAnchor, textX + dx, textY + dy); + textChunks.push({ type: '', chunk: currentTextSegment }); + initialSpace = this.processTSpans(this, this.element, context, textChunks, currentTextSegment, + // Set prevText to ' ' so any spaces on left of are trimmed + { prevText: ' ', prevContext: context }); + lengthAdjustment = initialSpace ? 0 : 1; + trimRight = true; + for (r = textChunks.length - 1; r >= 0; r--) { + if (trimRight) { + trimRight = textChunks[r].chunk.rightTrimText(); + } + } + if (textLength > 0) { + totalDefaultWidth_1 = 0; + totalLength_1 = 0; + textChunks.forEach(function (_a) { + var chunk = _a.chunk; + chunk.measureText(context); + chunk.textMeasures.forEach(function (_a) { + var width = _a.width, length = _a.length; + totalDefaultWidth_1 += width; + totalLength_1 += length; + }); + }); + charSpace = (textLength - totalDefaultWidth_1) / (totalLength_1 - lengthAdjustment); + } + // Put the textchunks + textChunks.reduce(function (lastPositions, _a) { + var type = _a.type, chunk = _a.chunk; + if (type === 'x') { + chunk.setX(lastPositions[0]); + } + else if (type === 'y') { + chunk.setY(lastPositions[1]); + } + return chunk.put(context, charSpace); + }, [0, 0]); + } + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + }); + }); + }; + TextNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + TextNode.prototype.getBoundingBoxCore = function (context) { + return this.boundingBox.length > 0 + ? this.boundingBox + : defaultBoundingBox(this.element, context); + }; + TextNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return TextNode; +}(GraphicsNode)); - /** @summary Cleanup all items in hierarchy - * @private */ - clearHierarchy(withbrowser) { - if (this.disp) { - this.disp.cleanup(); - delete this.disp; - } +var path_parse; +var hasRequiredPath_parse; - const plainarr = []; +function requirePath_parse () { + if (hasRequiredPath_parse) return path_parse; + hasRequiredPath_parse = 1; - this.forEachItem(item => { - delete item._painter; // remove reference on the painter - // when only display cleared, try to clear all browser items - if (!withbrowser && isFunc(item.clear)) item.clear(); - if (withbrowser) plainarr.push(item); - }); - if (withbrowser) { - // cleanup all monitoring loops - this.enableMonitoring(false); - // simplify work for javascript and delete all (ok, most of) cross-references - this.selectDom().html(''); - plainarr.forEach(d => { delete d._parent; delete d._childs; delete d._obj; delete d._d3cont; }); - delete this.h; - } - } + var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 }; - /** @summary Returns actual MDI display object - * @desc It should an instance of {@link MDIDsiplay} class */ - getDisplay() { - return this.disp; - } + var SPECIAL_SPACES = [ + 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, + 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF + ]; - /** @summary method called when MDI element is cleaned up - * @desc hook to perform extra actions when frame is cleaned - * @private */ - cleanupFrame(frame) { - select(frame).attr('frame_title', null); + function isSpace(ch) { + return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators + // White spaces + (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) || + (ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0); + } + + function isCommand(code) { + /*eslint-disable no-bitwise*/ + switch (code | 0x20) { + case 0x6D/* m */: + case 0x7A/* z */: + case 0x6C/* l */: + case 0x68/* h */: + case 0x76/* v */: + case 0x63/* c */: + case 0x73/* s */: + case 0x71/* q */: + case 0x74/* t */: + case 0x61/* a */: + case 0x72/* r */: + return true; + } + return false; + } + + function isArc(code) { + return (code | 0x20) === 0x61; + } + + function isDigit(code) { + return (code >= 48 && code <= 57); // 0..9 + } + + function isDigitStart(code) { + return (code >= 48 && code <= 57) || /* 0..9 */ + code === 0x2B || /* + */ + code === 0x2D || /* - */ + code === 0x2E; /* . */ + } + + + function State(path) { + this.index = 0; + this.path = path; + this.max = path.length; + this.result = []; + this.param = 0.0; + this.err = ''; + this.segmentStart = 0; + this.data = []; + } + + function skipSpaces(state) { + while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) { + state.index++; + } + } + + + function scanFlag(state) { + var ch = state.path.charCodeAt(state.index); + + if (ch === 0x30/* 0 */) { + state.param = 0; + state.index++; + return; + } + + if (ch === 0x31/* 1 */) { + state.param = 1; + state.index++; + return; + } + + state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')'; + } + + + function scanParam(state) { + var start = state.index, + index = start, + max = state.max, + zeroFirst = false, + hasCeiling = false, + hasDecimal = false, + hasDot = false, + ch; + + if (index >= max) { + state.err = 'SvgPath: missed param (at pos ' + index + ')'; + return; + } + ch = state.path.charCodeAt(index); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + index++; + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } + + // This logic is shamelessly borrowed from Esprima + // https://github.com/ariya/esprimas + // + if (!isDigit(ch) && ch !== 0x2E/* . */) { + state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')'; + return; + } + + if (ch !== 0x2E/* . */) { + zeroFirst = (ch === 0x30/* 0 */); + index++; + + ch = (index < max) ? state.path.charCodeAt(index) : 0; + + if (zeroFirst && index < max) { + // decimal number starts with '0' such as '09' is illegal. + if (ch && isDigit(ch)) { + state.err = 'SvgPath: numbers started with `0` such as `09` are illegal (at pos ' + start + ')'; + return; + } + } - this.clearDrop(frame); + while (index < max && isDigit(state.path.charCodeAt(index))) { + index++; + hasCeiling = true; + } + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } - const lst = cleanup(frame); + if (ch === 0x2E/* . */) { + hasDot = true; + index++; + while (isDigit(state.path.charCodeAt(index))) { + index++; + hasDecimal = true; + } + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } + + if (ch === 0x65/* e */ || ch === 0x45/* E */) { + if (hasDot && !hasCeiling && !hasDecimal) { + state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; + return; + } + + index++; + + ch = (index < max) ? state.path.charCodeAt(index) : 0; + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + index++; + } + if (index < max && isDigit(state.path.charCodeAt(index))) { + while (index < max && isDigit(state.path.charCodeAt(index))) { + index++; + } + } else { + state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; + return; + } + } + + state.index = index; + state.param = parseFloat(state.path.slice(start, index)) + 0.0; + } + + + function finalizeSegment(state) { + var cmd, cmdLC; + + // Process duplicated commands (without comand name) + + // This logic is shamelessly borrowed from Raphael + // https://github.com/DmitryBaranovskiy/raphael/ + // + cmd = state.path[state.segmentStart]; + cmdLC = cmd.toLowerCase(); + + var params = state.data; + + if (cmdLC === 'm' && params.length > 2) { + state.result.push([ cmd, params[0], params[1] ]); + params = params.slice(2); + cmdLC = 'l'; + cmd = (cmd === 'm') ? 'l' : 'L'; + } + + if (cmdLC === 'r') { + state.result.push([ cmd ].concat(params)); + } else { + + while (params.length >= paramCounts[cmdLC]) { + state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC]))); + if (!paramCounts[cmdLC]) { + break; + } + } + } + } + + + function scanSegment(state) { + var max = state.max, + cmdCode, is_arc, comma_found, need_params, i; + + state.segmentStart = state.index; + cmdCode = state.path.charCodeAt(state.index); + is_arc = isArc(cmdCode); + + if (!isCommand(cmdCode)) { + state.err = 'SvgPath: bad command ' + state.path[state.index] + ' (at pos ' + state.index + ')'; + return; + } + + need_params = paramCounts[state.path[state.index].toLowerCase()]; + + state.index++; + skipSpaces(state); + + state.data = []; + + if (!need_params) { + // Z + finalizeSegment(state); + return; + } - // we remove all painters references from items - if (lst.length > 0) { - this.forEachItem(item => { - if (item._painter && lst.indexOf(item._painter) >= 0) - delete item._painter; - }); - } - } + comma_found = false; - /** @summary Creates configured MDIDisplay object - * @return {Promise} when ready - * @private */ - async createDisplay() { - if ('disp' in this) { - if ((this.disp.numDraw() > 0) || (this.disp_kind === 'custom')) - return this.disp; - this.disp.cleanup(); - delete this.disp; - } + for (;;) { + for (i = need_params; i > 0; i--) { + if (is_arc && (i === 3 || i === 4)) scanFlag(state); + else scanParam(state); - if (this.disp_kind === 'batch') { - const pr = isNodeJs() ? _loadJSDOM() : Promise.resolve(null); - return pr.then(handle => { - this.disp = new BatchDisplay(1200, 800, handle?.body); - return this.disp; - }); - } + if (state.err.length) { + return; + } + state.data.push(state.param); - // check that we can found frame where drawing should be done - if (!document.getElementById(this.disp_frameid)) - return null; + skipSpaces(state); + comma_found = false; - if ((this.disp_kind.indexOf('flex') === 0) || (this.disp_kind.indexOf('coll') === 0)) - this.disp = new FlexibleDisplay(this.disp_frameid); - else if (this.disp_kind === 'tabs') - this.disp = new TabsDisplay(this.disp_frameid); - else - this.disp = new GridDisplay(this.disp_frameid, this.disp_kind); + if (state.index < max && state.path.charCodeAt(state.index) === 0x2C/* , */) { + state.index++; + skipSpaces(state); + comma_found = true; + } + } - this.disp.cleanupFrame = this.cleanupFrame.bind(this); - if (settings.DragAndDrop) - this.disp.setInitFrame(this.enableDrop.bind(this)); + // after ',' param is mandatory + if (comma_found) { + continue; + } - return this.disp; - } + if (state.index >= state.max) { + break; + } - /** @summary If possible, creates custom MDIDisplay for given item - * @param itemname - name of item, for which drawing is created - * @param custom_kind - display kind - * @return {Promise} with mdi object created - * @private */ - async createCustomDisplay(itemname, custom_kind) { - if (this.disp_kind !== 'simple') - return this.createDisplay(); + // Stop on next segment + if (!isDigitStart(state.path.charCodeAt(state.index))) { + break; + } + } - this.disp_kind = custom_kind; + finalizeSegment(state); + } - // check if display can be erased - if (this.disp) { - const num = this.disp.numDraw(); - if ((num > 1) || ((num === 1) && !this.disp.findFrame(itemname))) - return this.createDisplay(); - this.disp.cleanup(); - delete this.disp; - } - return this.createDisplay(); - } + /* Returns array of segments: + * + * [ + * [ command, coord1, coord2, ... ] + * ] + */ + path_parse = function pathParse(svgPath) { + var state = new State(svgPath); + var max = state.max; - /** @summary function updates object drawings for other painters - * @private */ - updateOnOtherFrames(painter, obj) { - const mdi = this.disp; - if (!mdi) return false; + skipSpaces(state); - const handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; - if (handle?.draw_field && obj[handle?.draw_field]) - obj = obj[handle?.draw_field]; + while (state.index < max && !state.err.length) { + scanSegment(state); + } - let isany = false; - mdi.forEachPainter((p /*, frame */) => { - if ((p === painter) || (p.getItemName() !== painter.getItemName())) return; + if (state.err.length) { + state.result = []; - // do not actiavte frame when doing update - // mdi.activateFrame(frame); - if (isFunc(p.redrawObject) && p.redrawObject(obj)) isany = true; - }); - return isany; - } + } else if (state.result.length) { - /** @summary Process resize event - * @private */ - checkResize(size) { - if (this.disp) this.disp.checkMDIResize(null, size); - } + if ('mM'.indexOf(state.result[0][0]) < 0) { + state.err = 'SvgPath: string should start with `M` or `m`'; + state.result = []; + } else { + state.result[0][0] = 'M'; + } + } - /** @summary Load and execute scripts, kept to support v6 applications - * @private */ - async loadScripts(scripts, modules, use_inject) { - if (!scripts?.length && !modules?.length) - return true; + return { + err: state.err, + segments: state.result + }; + }; + return path_parse; +} - if (use_inject && !globalThis.JSROOT) { - globalThis.JSROOT = { - version, gStyle, create: create$1, httpRequest, loadScript, decodeUrl, - source_dir: exports.source_dir, settings, addUserStreamer, addDrawFunc, - draw, redraw - }; - } +var matrix; +var hasRequiredMatrix; - if (internals.ignore_v6 || use_inject) - return loadScript(scripts); +function requireMatrix () { + if (hasRequiredMatrix) return matrix; + hasRequiredMatrix = 1; - return _ensureJSROOT().then(v6 => { - return v6.require(modules) - .then(() => loadScript(scripts)) - .then(() => v6._complete_loading()); - }); - } + // combine 2 matrixes + // m1, m2 - [a, b, c, d, e, g] + // + function combine(m1, m2) { + return [ + m1[0] * m2[0] + m1[2] * m2[1], + m1[1] * m2[0] + m1[3] * m2[1], + m1[0] * m2[2] + m1[2] * m2[3], + m1[1] * m2[2] + m1[3] * m2[3], + m1[0] * m2[4] + m1[2] * m2[5] + m1[4], + m1[1] * m2[4] + m1[3] * m2[5] + m1[5] + ]; + } - /** @summary Start GUI - * @return {Promise} when ready - * @private */ - async startGUI(gui_div, url) { - const d = decodeUrl(url), - getOption = opt => { - let res = d.get(opt, null); - if ((res === null) && gui_div && !gui_div.empty() && gui_div.node().hasAttribute(opt)) - res = gui_div.attr(opt); - return res; - }, + function Matrix() { + if (!(this instanceof Matrix)) { return new Matrix(); } + this.queue = []; // list of matrixes to apply + this.cache = null; // combined matrix cache + } - getUrlOptionAsArray = opt => { - let res = []; - while (opt) { - const separ = opt.indexOf(';'); - let part = (separ > 0) ? opt.slice(0, separ) : opt; + Matrix.prototype.matrix = function (m) { + if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0) { + return this; + } + this.cache = null; + this.queue.push(m); + return this; + }; - opt = (separ > 0) ? opt.slice(separ+1) : ''; - let canarray = true; - if (part[0] === '#') { part = part.slice(1); canarray = false; } + Matrix.prototype.translate = function (tx, ty) { + if (tx !== 0 || ty !== 0) { + this.cache = null; + this.queue.push([ 1, 0, 0, 1, tx, ty ]); + } + return this; + }; - const val = d.get(part, null); - if (canarray) - res = res.concat(parseAsArray(val)); - else if (val !== null) - res.push(val); - } - return res; - }, + Matrix.prototype.scale = function (sx, sy) { + if (sx !== 1 || sy !== 1) { + this.cache = null; + this.queue.push([ sx, 0, 0, sy, 0, 0 ]); + } + return this; + }; - getOptionAsArray = opt => { - let res = getUrlOptionAsArray(opt); - if (res.length > 0 || !gui_div || gui_div.empty()) return res; - while (opt) { - const separ = opt.indexOf(';'); - let part = separ > 0 ? opt.slice(0, separ) : opt; - if (separ > 0) opt = opt.slice(separ+1); else opt = ''; - let canarray = true; - if (part[0] === '#') { part = part.slice(1); canarray = false; } - if (part === 'files') continue; // special case for normal UI + Matrix.prototype.rotate = function (angle, rx, ry) { + var rad, cos, sin; - if (!gui_div.node().hasAttribute(part)) continue; + if (angle !== 0) { + this.translate(rx, ry); - const val = gui_div.attr(part); + rad = angle * Math.PI / 180; + cos = Math.cos(rad); + sin = Math.sin(rad); - if (canarray) - res = res.concat(parseAsArray(val)); - else if (val !== null) - res.push(val); - } - return res; - }, + this.queue.push([ cos, sin, -sin, cos, 0, 0 ]); + this.cache = null; - filesdir = d.get('path') || '', // path used in normal gui - jsonarr = getOptionAsArray('#json;jsons'), - expanditems = getOptionAsArray('expand'), - focusitem = getOption('focus'), - layout = getOption('layout'), - style = getOptionAsArray('#style'), - title = getOption('title'); + this.translate(-rx, -ry); + } + return this; + }; - this._one_by_one = settings.drop_items_one_by_one ?? (getOption('one_by_one') !== null); - let prereq = getOption('prereq') || '', - load = getOption('load'), - dir = getOption('dir'), - inject = getOption('inject'), - filesarr = getOptionAsArray('#file;files'), - itemsarr = getOptionAsArray('#item;items'), - optionsarr = getOptionAsArray('#opt;opts'), - monitor = getOption('monitoring'), - statush = 0, status = getOption('status'), - browser_kind = getOption('browser'), - browser_configured = !!browser_kind; + Matrix.prototype.skewX = function (angle) { + if (angle !== 0) { + this.cache = null; + this.queue.push([ 1, 0, Math.tan(angle * Math.PI / 180), 1, 0, 0 ]); + } + return this; + }; - if (monitor === null) - monitor = 0; - else if (monitor === '') - monitor = 3000; - else - monitor = parseInt(monitor); + Matrix.prototype.skewY = function (angle) { + if (angle !== 0) { + this.cache = null; + this.queue.push([ 1, Math.tan(angle * Math.PI / 180), 0, 1, 0, 0 ]); + } + return this; + }; - if (getOption('float') !== null) { - browser_kind = 'float'; - browser_configured = true; - } else if (getOption('fix') !== null) { - browser_kind = 'fix'; - browser_configured = true; - } - if (!browser_configured && (browser.screenWidth <= 640)) - browser_kind = 'float'; + // Flatten queue + // + Matrix.prototype.toArray = function () { + if (this.cache) { + return this.cache; + } - this.no_select = getOption('noselect'); + if (!this.queue.length) { + this.cache = [ 1, 0, 0, 1, 0, 0 ]; + return this.cache; + } - if (getOption('files_monitoring') !== null) - this.files_monitoring = true; + this.cache = this.queue[0]; - if (title && (typeof document !== 'undefined')) - document.title = title; + if (this.queue.length === 1) { + return this.cache; + } - if (expanditems.length === 0 && (getOption('expand') === '')) expanditems.push(''); + for (var i = 1; i < this.queue.length; i++) { + this.cache = combine(this.cache, this.queue[i]); + } - if (filesdir) { - for (let i = 0; i < filesarr.length; ++i) filesarr[i] = filesdir + filesarr[i]; - for (let i = 0; i < jsonarr.length; ++i) jsonarr[i] = filesdir + jsonarr[i]; - } + return this.cache; + }; - if ((itemsarr.length === 0) && getOption('item') === '') itemsarr.push(''); - if ((jsonarr.length === 1) && (itemsarr.length === 0) && (expanditems.length === 0)) itemsarr.push(''); + // Apply list of matrixes to (x,y) point. + // If `isRelative` set, `translate` component of matrix will be skipped + // + Matrix.prototype.calc = function (x, y, isRelative) { + var m; - if (!this.disp_kind) { - if (isStr(layout) && layout) - this.disp_kind = layout; - else if (settings.DislpayKind && settings.DislpayKind !== 'simple') - this.disp_kind = settings.DislpayKind; - else { - switch (itemsarr.length) { - case 0: - case 1: this.disp_kind = 'simple'; break; - case 2: this.disp_kind = 'vert2'; break; - case 3: this.disp_kind = 'vert21'; break; - case 4: this.disp_kind = 'vert22'; break; - case 5: this.disp_kind = 'vert32'; break; - case 6: this.disp_kind = 'vert222'; break; - case 7: this.disp_kind = 'vert322'; break; - case 8: this.disp_kind = 'vert332'; break; - case 9: this.disp_kind = 'vert333'; break; - default: this.disp_kind = 'flex'; - } - } - } + // Don't change point on empty transforms queue + if (!this.queue.length) { return [ x, y ]; } - if (status === 'no') - status = null; - else if (status === 'off') { - this.status_disabled = true; - status = null; - } else if (status === 'on') - status = true; - else if (status !== null) { - statush = parseInt(status); - if (!Number.isInteger(statush) || (statush < 5)) statush = 0; - status = true; - } - if (this.no_select === '') this.no_select = true; + // Calculate final matrix, if not exists + // + // NB. if you deside to apply transforms to point one-by-one, + // they should be taken in reverse order - if (!browser_kind) - browser_kind = 'fix'; - else if (browser_kind === 'no') - browser_kind = ''; - else if (browser_kind === 'off') { - browser_kind = ''; - status = null; - this.exclude_browser = true; - } - if (getOption('nofloat') !== null) - this.float_browser_disabled = true; + if (!this.cache) { + this.cache = this.toArray(); + } - if (this.start_without_browser) browser_kind = ''; + m = this.cache; - this._topname = getOption('topname'); + // Apply matrix to point + return [ + x * m[0] + y * m[2] + (isRelative ? 0 : m[4]), + x * m[1] + y * m[3] + (isRelative ? 0 : m[5]) + ]; + }; - const openAllFiles = () => { - let promise; - if (load || prereq) { - promise = this.loadScripts(load, prereq); load = ''; prereq = ''; - } else if (inject) { - promise = this.loadScripts(inject, '', true); inject = ''; - } else if (browser_kind) { - promise = this.createBrowser(browser_kind); browser_kind = ''; - } else if (status !== null) { - promise = this.createStatusLine(statush, status); status = null; - } else if (jsonarr.length > 0) - promise = this.openJsonFile(jsonarr.shift()); - else if (filesarr.length > 0) - promise = this.openRootFile(filesarr.shift()); - else if (dir) { - promise = this.listServerDir(dir); dir = ''; - } else if (expanditems.length > 0) - promise = this.expandItem(expanditems.shift()); - else if (style.length > 0) - promise = this.applyStyle(style.shift()); - else { - return this.refreshHtml() - .then(() => this.displayItems(itemsarr, optionsarr)) - .then(() => focusitem ? this.focusOnItem(focusitem) : this) - .then(() => { - this.setMonitoring(monitor); - return itemsarr ? this.refreshHtml() : this; // this is final return - }); - } + matrix = Matrix; + return matrix; +} - return promise.then(openAllFiles); - }; +var transform_parse; +var hasRequiredTransform_parse; - let h0 = null; - if (this.is_online) { - const func = internals.getCachedHierarchy || findFunction('GetCachedHierarchy'); - if (isFunc(func)) - h0 = func(); - if (!isObject(h0)) - h0 = ''; +function requireTransform_parse () { + if (hasRequiredTransform_parse) return transform_parse; + hasRequiredTransform_parse = 1; - if ((this.is_online === 'draw') && !itemsarr.length) - itemsarr.push(''); - } - if (h0 !== null) { - return this.openOnline(h0).then(() => { - // check if server enables monitoring - if (!this.exclude_browser && !browser_configured && ('_browser' in this.h)) { - browser_kind = this.h._browser; - if (browser_kind === 'no') browser_kind = ''; else - if (browser_kind === 'off') { browser_kind = ''; status = null; this.exclude_browser = true; } - } + var Matrix = requireMatrix(); - if (('_monitoring' in this.h) && !monitor) - monitor = this.h._monitoring; + var operations = { + matrix: true, + scale: true, + rotate: true, + translate: true, + skewX: true, + skewY: true + }; - if (('_loadfile' in this.h) && (filesarr.length === 0)) - filesarr = parseAsArray(this.h._loadfile); + var CMD_SPLIT_RE = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/; + var PARAMS_SPLIT_RE = /[\s,]+/; + + + transform_parse = function transformParse(transformString) { + var matrix = new Matrix(); + var cmd, params; + + // Split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', ''] + transformString.split(CMD_SPLIT_RE).forEach(function (item) { + + // Skip empty elements + if (!item.length) { return; } + + // remember operation + if (typeof operations[item] !== 'undefined') { + cmd = item; + return; + } + + // extract params & att operation to matrix + params = item.split(PARAMS_SPLIT_RE).map(function (i) { + return +i || 0; + }); + + // If params count is not correct - ignore command + switch (cmd) { + case 'matrix': + if (params.length === 6) { + matrix.matrix(params); + } + return; + + case 'scale': + if (params.length === 1) { + matrix.scale(params[0], params[0]); + } else if (params.length === 2) { + matrix.scale(params[0], params[1]); + } + return; + + case 'rotate': + if (params.length === 1) { + matrix.rotate(params[0], 0, 0); + } else if (params.length === 3) { + matrix.rotate(params[0], params[1], params[2]); + } + return; + + case 'translate': + if (params.length === 1) { + matrix.translate(params[0], 0); + } else if (params.length === 2) { + matrix.translate(params[0], params[1]); + } + return; + + case 'skewX': + if (params.length === 1) { + matrix.skewX(params[0]); + } + return; + + case 'skewY': + if (params.length === 1) { + matrix.skewY(params[0]); + } + return; + } + }); + + return matrix; + }; + return transform_parse; +} - if (('_drawitem' in this.h) && (itemsarr.length === 0)) { - itemsarr = parseAsArray(this.h._drawitem); - optionsarr = parseAsArray(this.h._drawopt); - } +var a2c; +var hasRequiredA2c; - if (('_layout' in this.h) && !layout && ((this.is_online !== 'draw') || (itemsarr.length > 1))) - this.disp_kind = this.h._layout; +function requireA2c () { + if (hasRequiredA2c) return a2c; + hasRequiredA2c = 1; - if (('_toptitle' in this.h) && this.exclude_browser && (typeof document !== 'undefined')) - document.title = this.h._toptitle; - if (gui_div) - this.prepareGuiDiv(gui_div.attr('id'), this.disp_kind); + var TAU = Math.PI * 2; - return openAllFiles(); - }); - } - if (gui_div) - this.prepareGuiDiv(gui_div.attr('id'), this.disp_kind); + /* eslint-disable space-infix-ops */ - return openAllFiles(); - } + // Calculate an angle between two unit vectors + // + // Since we measure angle between radii of circular arcs, + // we can use simplified math (without length normalization) + // + function unit_vector_angle(ux, uy, vx, vy) { + var sign = (ux * vy - uy * vx < 0) ? -1 : 1; + var dot = ux * vx + uy * vy; - /** @summary Prepare div element - create layout and buttons - * @private */ - prepareGuiDiv(myDiv, layout) { - this.gui_div = isStr(myDiv) ? myDiv : myDiv.attr('id'); + // Add this to work with arbitrary vectors: + // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); - this.brlayout = new BrowserLayout(this.gui_div, this); + // rounding errors, e.g. -1.0000000000000002 can screw up this + if (dot > 1.0) { dot = 1.0; } + if (dot < -1) { dot = -1; } - this.brlayout.create(!this.exclude_browser); + return sign * Math.acos(dot); + } - this.createButtons(); - this.setDisplay(layout, this.brlayout.drawing_divid()); - } + // Convert from endpoint to center parameterization, + // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // + // Return [cx, cy, theta1, delta_theta] + // + function get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi) { + // Step 1. + // + // Moving an ellipse so origin will be the middlepoint between our two + // points. After that, rotate it to line up ellipse axes with coordinate + // axes. + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + var rx_sq = rx * rx; + var ry_sq = ry * ry; + var x1p_sq = x1p * x1p; + var y1p_sq = y1p * y1p; + + // Step 2. + // + // Compute coordinates of the centre of this ellipse (cx', cy') + // in the new coordinate system. + // + var radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); + + if (radicant < 0) { + // due to rounding errors it might be e.g. -1.3877787807814457e-17 + radicant = 0; + } + + radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); + radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); + + var cxp = radicant * rx/ry * y1p; + var cyp = radicant * -ry/rx * x1p; + + // Step 3. + // + // Transform back to get centre coordinates (cx, cy) in the original + // coordinate system. + // + var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2; + var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2; + + // Step 4. + // + // Compute angles (theta1, delta_theta). + // + var v1x = (x1p - cxp) / rx; + var v1y = (y1p - cyp) / ry; + var v2x = (-x1p - cxp) / rx; + var v2y = (-y1p - cyp) / ry; + + var theta1 = unit_vector_angle(1, 0, v1x, v1y); + var delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); + + if (fs === 0 && delta_theta > 0) { + delta_theta -= TAU; + } + if (fs === 1 && delta_theta < 0) { + delta_theta += TAU; + } + + return [ cx, cy, theta1, delta_theta ]; + } - /** @summary Create shortcut buttons */ - createButtons() { - if (this.exclude_browser) return; + // + // Approximate one unit arc segment with bézier curves, + // see http://math.stackexchange.com/questions/873224 + // + function approximate_unit_arc(theta1, delta_theta) { + var alpha = 4/3 * Math.tan(delta_theta/4); + + var x1 = Math.cos(theta1); + var y1 = Math.sin(theta1); + var x2 = Math.cos(theta1 + delta_theta); + var y2 = Math.sin(theta1 + delta_theta); + + return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ]; + } + + a2c = function a2c(x1, y1, x2, y2, fa, fs, rx, ry, phi) { + var sin_phi = Math.sin(phi * TAU / 360); + var cos_phi = Math.cos(phi * TAU / 360); + + // Make sure radii are valid + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + if (x1p === 0 && y1p === 0) { + // we're asked to draw line to itself + return []; + } + + if (rx === 0 || ry === 0) { + // one of the radii is zero + return []; + } + + + // Compensate out-of-range radii + // + rx = Math.abs(rx); + ry = Math.abs(ry); + + var lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); + if (lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + + // Get center parameters (cx, cy, theta1, delta_theta) + // + var cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); + + var result = []; + var theta1 = cc[2]; + var delta_theta = cc[3]; + + // Split an arc to multiple segments, so each segment + // will be less than τ/4 (= 90°) + // + var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); + delta_theta /= segments; + + for (var i = 0; i < segments; i++) { + result.push(approximate_unit_arc(theta1, delta_theta)); + theta1 += delta_theta; + } + + // We have a bezier approximation of a unit circle, + // now need to transform back to the original ellipse + // + return result.map(function (curve) { + for (var i = 0; i < curve.length; i += 2) { + var x = curve[i + 0]; + var y = curve[i + 1]; + + // scale + x *= rx; + y *= ry; + + // rotate + var xp = cos_phi*x - sin_phi*y; + var yp = sin_phi*x + cos_phi*y; + + // translate + curve[i + 0] = xp + cc[0]; + curve[i + 1] = yp + cc[1]; + } + + return curve; + }); + }; + return a2c; +} - const btns = this.brlayout?.createBrowserBtns(); - if (!btns) return; +var ellipse; +var hasRequiredEllipse; - ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos browser', 'browser') - .style('margin', '3px').on('click', () => this.createBrowser('fix', true)); +function requireEllipse () { + if (hasRequiredEllipse) return ellipse; + hasRequiredEllipse = 1; - if (!this.float_browser_disabled) { - ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float browser', 'browser') - .style('margin', '3px').on('click', () => this.createBrowser('float', true)); - } + /* eslint-disable space-infix-ops */ - if (!this.status_disabled) { - ToolbarIcons.createSVG(btns, ToolbarIcons.three_circles, 15, 'toggle status line', 'browser') - .style('margin', '3px').on('click', () => this.createStatusLine(0, 'toggle')); - } - } + // The precision used to consider an ellipse as a circle + // + var epsilon = 0.0000000001; - /** @summary Returns trus if status is exists */ - hasStatusLine() { - if (this.status_disabled || !this.gui_div || !this.brlayout) - return false; - return this.brlayout.hasStatus(); - } + // To convert degree in radians + // + var torad = Math.PI / 180; - /** @summary Create status line - * @param {number} [height] - size of the status line - * @param [mode] - false / true / 'toggle' - * @return {Promise} when ready */ - async createStatusLine(height, mode) { - if (this.status_disabled || !this.gui_div || !this.brlayout) - return ''; - return this.brlayout.createStatusLine(height, mode); - } + // Class constructor : + // an ellipse centred at 0 with radii rx,ry and x - axis - angle ax. + // + function Ellipse(rx, ry, ax) { + if (!(this instanceof Ellipse)) { return new Ellipse(rx, ry, ax); } + this.rx = rx; + this.ry = ry; + this.ax = ax; + } + + // Apply a linear transform m to the ellipse + // m is an array representing a matrix : + // - - + // | m[0] m[2] | + // | m[1] m[3] | + // - - + // + Ellipse.prototype.transform = function (m) { + // We consider the current ellipse as image of the unit circle + // by first scale(rx,ry) and then rotate(ax) ... + // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle. + var c = Math.cos(this.ax * torad), s = Math.sin(this.ax * torad); + var ma = [ + this.rx * (m[0]*c + m[2]*s), + this.rx * (m[1]*c + m[3]*s), + this.ry * (-m[0]*s + m[2]*c), + this.ry * (-m[1]*s + m[3]*c) + ]; + + // ma * transpose(ma) = [ J L ] + // [ L K ] + // L is calculated later (if the image is not a circle) + var J = ma[0]*ma[0] + ma[2]*ma[2], + K = ma[1]*ma[1] + ma[3]*ma[3]; + + // the discriminant of the characteristic polynomial of ma * transpose(ma) + var D = ((ma[0]-ma[3])*(ma[0]-ma[3]) + (ma[2]+ma[1])*(ma[2]+ma[1])) * + ((ma[0]+ma[3])*(ma[0]+ma[3]) + (ma[2]-ma[1])*(ma[2]-ma[1])); + + // the "mean eigenvalue" + var JK = (J + K) / 2; + + // check if the image is (almost) a circle + if (D < epsilon * JK) { + // if it is + this.rx = this.ry = Math.sqrt(JK); + this.ax = 0; + return this; + } + + // if it is not a circle + var L = ma[0]*ma[1] + ma[2]*ma[3]; + + D = Math.sqrt(D); + + // {l1,l2} = the two eigen values of ma * transpose(ma) + var l1 = JK + D/2, + l2 = JK - D/2; + // the x - axis - rotation angle is the argument of the l1 - eigenvector + /*eslint-disable indent*/ + this.ax = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? + 90 + : + Math.atan(Math.abs(L) > Math.abs(l1 - K) ? + (l1 - J) / L + : + L / (l1 - K) + ) * 180 / Math.PI; + /*eslint-enable indent*/ + + // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90 + if (this.ax >= 0) { + // if ax in [0,90] + this.rx = Math.sqrt(l1); + this.ry = Math.sqrt(l2); + } else { + // if ax in ]-90,0[ => exchange axes + this.ax += 90; + this.rx = Math.sqrt(l2); + this.ry = Math.sqrt(l1); + } + + return this; + }; - /** @summary Redraw hierarchy - * @desc works only when inspector or streamer info is displayed - * @private */ - redrawObject(obj) { - if (!this._inspector && !this._streamer_info) return false; - if (this._streamer_info) - this.h = createStreamerInfoContent(obj); - else - this.h = createInspectorContent(obj); - return this.refreshHtml().then(() => { this.setTopPainter(); }); - } + // Check if the ellipse is (almost) degenerate, i.e. rx = 0 or ry = 0 + // + Ellipse.prototype.isDegenerate = function () { + return (this.rx < epsilon * this.ry || this.ry < epsilon * this.rx); + }; - /** @summary Create browser elements - * @return {Promise} when completed */ - async createBrowser(browser_kind, update_html) { - if (!this.gui_div || this.exclude_browser || !this.brlayout) - return false; + ellipse = Ellipse; + return ellipse; +} - const main = select(`#${this.gui_div} .jsroot_browser`); - // one requires top-level container - if (main.empty()) - return false; +var svgpath$1; +var hasRequiredSvgpath$1; - if ((browser_kind === 'float') && this.float_browser_disabled) - browser_kind = 'fix'; +function requireSvgpath$1 () { + if (hasRequiredSvgpath$1) return svgpath$1; + hasRequiredSvgpath$1 = 1; - if (!main.select('.jsroot_browser_area').empty()) { - // this is case when browser created, - // if update_html specified, hidden state will be toggled - if (update_html) this.brlayout.toggleKind(browser_kind); + var pathParse = requirePath_parse(); + var transformParse = requireTransform_parse(); + var matrix = requireMatrix(); + var a2c = requireA2c(); + var ellipse = requireEllipse(); - return true; - } - let guiCode = `

    JSROOT version ${version}

    `; + // Class constructor + // + function SvgPath(path) { + if (!(this instanceof SvgPath)) { return new SvgPath(path); } - if (this.is_online) { - guiCode += '

    Hierarchy in json and xml format

    ' + - '
    ' + - ''; - } else if (!this.no_select) { - const myDiv = select('#'+this.gui_div), - files = myDiv.attr('files') || '../files/hsimple.root', - path = decodeUrl().get('path') || myDiv.attr('path') || '', - arrFiles = files.split(';'); + var pstate = pathParse(path); - guiCode += '' + - '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '

    Read docu' + - ' how to open files from other servers.

    ' + - '
    ' + - '' + - ''; - } else if (this.no_select === 'file') - guiCode += '
    '; + // Array of path segments. + // Each segment is array [command, param1, param2, ...] + this.segments = pstate.segments; + // Error message on parse error. + this.err = pstate.err; - if (this.is_online || !this.no_select || this.no_select === 'file') { - guiCode += '' + - '
    '; - } + // Transforms stack for lazy evaluation + this.__stack = []; + } - guiCode += `
    `; + SvgPath.from = function (src) { + if (typeof src === 'string') return new SvgPath(src); - this.brlayout.setBrowserContent(guiCode); + if (src instanceof SvgPath) { + // Create empty object + var s = new SvgPath(''); - const title_elem = this.brlayout.setBrowserTitle(this.is_online ? 'ROOT online server' : 'Read a ROOT file'); - title_elem?.on('contextmenu', evnt => { - evnt.preventDefault(); - createMenu(evnt).then(menu => { - this.fillSettingsMenu(menu, true); - menu.show(); - }); - }).on('dblclick', () => { - this.createBrowser(this.brlayout?.browser_kind === 'float' ? 'fix' : 'float', true); - }); + // Clone properies + s.err = src.err; + s.segments = src.segments.map(function (sgm) { return sgm.slice(); }); + s.__stack = src.__stack.map(function (m) { + return matrix().matrix(m.toArray()); + }); - if (!this.is_online && !this.no_select) { - this.readSelectedFile = function() { - const filename = main.select('.gui_urlToLoad').property('value').trim(); - if (!filename) return; + return s; + } - if (filename.toLowerCase().lastIndexOf('.json') === filename.length - 5) - this.openJsonFile(filename); - else - this.openRootFile(filename); - }; + throw new Error('SvgPath.from: invalid param type ' + src); + }; - main.select('.gui_selectFileName').property('value', '') - .on('change', evnt => main.select('.gui_urlToLoad').property('value', evnt.target.value)); - main.select('.gui_fileBtn').on('click', () => main.select('.gui_localFile').node().click()); - main.select('.gui_ReadFileBtn').on('click', () => this.readSelectedFile()); + SvgPath.prototype.__matrix = function (m) { + var self = this, i; + + // Quick leave for empty matrix + if (!m.queue.length) { return; } + + this.iterate(function (s, index, x, y) { + var p, result, name, isRelative; + + switch (s[0]) { + + // Process 'assymetric' commands separately + case 'v': + p = m.calc(0, s[1], true); + result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ]; + break; + + case 'V': + p = m.calc(x, s[1], false); + result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ]; + break; + + case 'h': + p = m.calc(s[1], 0, true); + result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ]; + break; + + case 'H': + p = m.calc(s[1], y, false); + result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ]; + break; + + case 'a': + case 'A': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + + // Drop segment if arc is empty (end point === start point) + /*if ((s[0] === 'A' && s[6] === x && s[7] === y) || + (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { + return []; + }*/ + + // Transform rx, ry and the x-axis-rotation + var ma = m.toArray(); + var e = ellipse(s[1], s[2], s[3]).transform(ma); + + // flip sweep-flag if matrix is not orientation-preserving + if (ma[0] * ma[3] - ma[1] * ma[2] < 0) { + s[5] = s[5] ? '0' : '1'; + } + + // Transform end point as usual (without translation for relative notation) + p = m.calc(s[6], s[7], s[0] === 'a'); + + // Empty arcs can be ignored by renderer, but should not be dropped + // to avoid collisions with `S A S` and so on. Replace with empty line. + if ((s[0] === 'A' && s[6] === x && s[7] === y) || + (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { + result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; + break; + } + + // if the resulting ellipse is (almost) a segment ... + if (e.isDegenerate()) { + // replace the arc by a line + result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; + } else { + // if it is a real ellipse + // s[0], s[4] and s[5] are not modified + result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ]; + } + + break; + + case 'm': + // Edge case. The very first `m` should be processed as absolute, if happens. + // Make sense for coord shift transforms. + isRelative = index > 0; + + p = m.calc(s[1], s[2], isRelative); + result = [ 'm', p[0], p[1] ]; + break; + + default: + name = s[0]; + result = [ name ]; + isRelative = (name.toLowerCase() === name); + + // Apply transformations to the segment + for (i = 1; i < s.length; i += 2) { + p = m.calc(s[i], s[i + 1], isRelative); + result.push(p[0], p[1]); + } + } + + self.segments[index] = result; + }, true); + }; + - main.select('.gui_ResetUIBtn').on('click', () => this.clearHierarchy(true)); + // Apply stacked commands + // + SvgPath.prototype.__evaluateStack = function () { + var m, i; - main.select('.gui_urlToLoad').on('keyup', evnt => { - if (evnt.code === 'Enter') this.readSelectedFile(); - }); + if (!this.__stack.length) { return; } - main.select('.gui_localFile').on('change', evnt => { - const files = evnt.target.files; + if (this.__stack.length === 1) { + this.__matrix(this.__stack[0]); + this.__stack = []; + return; + } - for (let n = 0; n < files.length; ++n) { - const f = files[n]; - main.select('.gui_urlToLoad').property('value', f.name); - this.openRootFile(f); - } - }); - } + m = matrix(); + i = this.__stack.length; - const layout = main.select('.gui_layout'); - if (!layout.empty()) { - ['simple', 'vert2', 'vert3', 'vert231', 'horiz2', 'horiz32', 'flex', 'tabs', - 'grid 2x2', 'grid 1x3', 'grid 2x3', 'grid 3x3', 'grid 4x4'].forEach(kind => layout.append('option').attr('value', kind).html(kind)); + while (--i >= 0) { + m.matrix(this.__stack[i].toArray()); + } - layout.on('change', ev => { - const kind = ev.target.value || 'flex'; - this.setDisplay(kind, this.gui_div + '_drawing'); - settings.DislpayKind = kind; - }); - } + this.__matrix(m); + this.__stack = []; + }; - this.setDom(this.gui_div + '_browser_hierarchy'); - if (update_html) { - this.refreshHtml(); - this.initializeBrowser(); - } + // Convert processed SVG Path back to string + // + SvgPath.prototype.toString = function () { + var result = '', prevCmd = '', cmdSkipped = false; + + this.__evaluateStack(); + + for (var i = 0, len = this.segments.length; i < len; i++) { + var segment = this.segments[i]; + var cmd = segment[0]; + + // Command not repeating => store + if (cmd !== prevCmd || cmd === 'm' || cmd === 'M') { + // workaround for FontForge SVG importing bug, keep space between "z m". + if (cmd === 'm' && prevCmd === 'z') result += ' '; + result += cmd; + + cmdSkipped = false; + } else { + cmdSkipped = true; + } + + // Store segment params + for (var pos = 1; pos < segment.length; pos++) { + var val = segment[pos]; + // Space can be skipped + // 1. After command (always) + // 2. For negative value (with '-' at start) + if (pos === 1) { + if (cmdSkipped && val >= 0) result += ' '; + } else if (val >= 0) result += ' '; + + result += val; + } + + prevCmd = cmd; + } + + return result; + }; - return this.brlayout.toggleBrowserKind(browser_kind || 'fix'); - } - /** @summary Initialize browser elements */ - initializeBrowser() { - const main = select(`#${this.gui_div} .jsroot_browser`); - if (main.empty() || !this.brlayout) return; + // Translate path to (x [, y]) + // + SvgPath.prototype.translate = function (x, y) { + this.__stack.push(matrix().translate(x, y || 0)); + return this; + }; - this.brlayout.adjustBrowserSize(); - const selects = main.select('.gui_layout').node(); + // Scale path to (sx [, sy]) + // sy = sx if not defined + // + SvgPath.prototype.scale = function (sx, sy) { + this.__stack.push(matrix().scale(sx, (!sy && (sy !== 0)) ? sx : sy)); + return this; + }; - if (selects) { - let found = false; - for (const i in selects.options) { - const s = selects.options[i].text; - if (!isStr(s)) continue; - if ((s === this.getLayout()) || (s.replace(/ /g, '') === this.getLayout())) { - selects.selectedIndex = i; found = true; - break; - } - } - if (!found) { - const opt = document.createElement('option'); - opt.innerHTML = opt.value = this.getLayout(); - selects.appendChild(opt); - selects.selectedIndex = selects.options.length - 1; - } - } - if (this.is_online) { - if (this.h?._toptitle) - this.brlayout.setBrowserTitle(this.h._toptitle); - main.select('.gui_monitoring') - .property('checked', this.isMonitoring()) - .on('click', evnt => { - this.enableMonitoring(evnt.target.checked); - this.updateItems(); - }); - } else if (!this.no_select) { - let fname = ''; - this.forEachRootFile(item => { if (!fname) fname = item._fullurl; }); - main.select('.gui_urlToLoad').property('value', fname); - } - } + // Rotate path around point (sx [, sy]) + // sy = sx if not defined + // + SvgPath.prototype.rotate = function (angle, rx, ry) { + this.__stack.push(matrix().rotate(angle, rx || 0, ry || 0)); + return this; + }; - /** @summary Enable monitoring mode */ - enableMonitoring(on) { - this.setMonitoring(undefined, on); - const chkbox = select(`#${this.gui_div} .jsroot_browser .gui_monitoring`); - if (!chkbox.empty() && (chkbox.property('checked') !== on)) - chkbox.property('checked', on); - } + // Skew path along the X axis by `degrees` angle + // + SvgPath.prototype.skewX = function (degrees) { + this.__stack.push(matrix().skewX(degrees)); + return this; + }; -} // class HierarchyPainter + // Skew path along the Y axis by `degrees` angle + // + SvgPath.prototype.skewY = function (degrees) { + this.__stack.push(matrix().skewY(degrees)); + return this; + }; -/** @summary Show object in inspector for provided object - * @protected */ -ObjectPainter.prototype.showInspector = function(opt, obj) { - if (opt === 'check') - return true; + // Apply matrix transform (array of 6 elements) + // + SvgPath.prototype.matrix = function (m) { + this.__stack.push(matrix().matrix(m)); + return this; + }; - const main = this.selectDom(), - rect = getElementRect(main), - w = Math.round(rect.width * 0.05) + 'px', - h = Math.round(rect.height * 0.05) + 'px', - id = 'root_inspector_' + internals.id_counter++; - main.append('div') - .attr('id', id) - .attr('class', 'jsroot_inspector') - .style('position', 'absolute') - .style('top', h) - .style('bottom', h) - .style('left', w) - .style('right', w); + // Transform path according to "transform" attr of SVG spec + // + SvgPath.prototype.transform = function (transformString) { + if (!transformString.trim()) { + return this; + } + this.__stack.push(transformParse(transformString)); + return this; + }; - if (!obj?._typename) - obj = isFunc(this.getPrimaryObject) ? this.getPrimaryObject() : this.getObject(); - return drawInspector(id, obj, opt); -}; + // Round coords with given decimal precition. + // 0 by default (to integers) + // + SvgPath.prototype.round = function (d) { + var contourStartDeltaX = 0, contourStartDeltaY = 0, deltaX = 0, deltaY = 0, l; + + d = d || 0; + + this.__evaluateStack(); + + this.segments.forEach(function (s) { + var isRelative = (s[0].toLowerCase() === s[0]); + + switch (s[0]) { + case 'H': + case 'h': + if (isRelative) { s[1] += deltaX; } + deltaX = s[1] - s[1].toFixed(d); + s[1] = +s[1].toFixed(d); + return; + + case 'V': + case 'v': + if (isRelative) { s[1] += deltaY; } + deltaY = s[1] - s[1].toFixed(d); + s[1] = +s[1].toFixed(d); + return; + + case 'Z': + case 'z': + deltaX = contourStartDeltaX; + deltaY = contourStartDeltaY; + return; + + case 'M': + case 'm': + if (isRelative) { + s[1] += deltaX; + s[2] += deltaY; + } + + deltaX = s[1] - s[1].toFixed(d); + deltaY = s[2] - s[2].toFixed(d); + + contourStartDeltaX = deltaX; + contourStartDeltaY = deltaY; + + s[1] = +s[1].toFixed(d); + s[2] = +s[2].toFixed(d); + return; + + case 'A': + case 'a': + // [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + if (isRelative) { + s[6] += deltaX; + s[7] += deltaY; + } + + deltaX = s[6] - s[6].toFixed(d); + deltaY = s[7] - s[7].toFixed(d); + + s[1] = +s[1].toFixed(d); + s[2] = +s[2].toFixed(d); + s[3] = +s[3].toFixed(d + 2); // better precision for rotation + s[6] = +s[6].toFixed(d); + s[7] = +s[7].toFixed(d); + return; + + default: + // a c l q s t + l = s.length; + + if (isRelative) { + s[l - 2] += deltaX; + s[l - 1] += deltaY; + } + + deltaX = s[l - 2] - s[l - 2].toFixed(d); + deltaY = s[l - 1] - s[l - 1].toFixed(d); + + s.forEach(function (val, i) { + if (!i) { return; } + s[i] = +s[i].toFixed(d); + }); + return; + } + }); + + return this; + }; -/** @summary Display streamer info - * @private */ -async function drawStreamerInfo(dom, lst) { - const painter = new HierarchyPainter('sinfo', dom, '__as_dark_mode__'); + // Apply iterator function to all segments. If function returns result, + // current segment will be replaced to array of returned segments. + // If empty array is returned, current regment will be deleted. + // + SvgPath.prototype.iterate = function (iterator, keepLazyStack) { + var segments = this.segments, + replacements = {}, + needReplace = false, + lastX = 0, + lastY = 0, + countourStartX = 0, + countourStartY = 0; + var i, j, newSegments; + + if (!keepLazyStack) { + this.__evaluateStack(); + } + + segments.forEach(function (s, index) { + + var res = iterator(s, index, lastX, lastY); + + if (Array.isArray(res)) { + replacements[index] = res; + needReplace = true; + } + + var isRelative = (s[0] === s[0].toLowerCase()); + + // calculate absolute X and Y + switch (s[0]) { + case 'm': + case 'M': + lastX = s[1] + (isRelative ? lastX : 0); + lastY = s[2] + (isRelative ? lastY : 0); + countourStartX = lastX; + countourStartY = lastY; + return; + + case 'h': + case 'H': + lastX = s[1] + (isRelative ? lastX : 0); + return; + + case 'v': + case 'V': + lastY = s[1] + (isRelative ? lastY : 0); + return; + + case 'z': + case 'Z': + // That make sence for multiple contours + lastX = countourStartX; + lastY = countourStartY; + return; + + default: + lastX = s[s.length - 2] + (isRelative ? lastX : 0); + lastY = s[s.length - 1] + (isRelative ? lastY : 0); + } + }); + + // Replace segments if iterator return results + + if (!needReplace) { return this; } + + newSegments = []; + + for (i = 0; i < segments.length; i++) { + if (typeof replacements[i] !== 'undefined') { + for (j = 0; j < replacements[i].length; j++) { + newSegments.push(replacements[i][j]); + } + } else { + newSegments.push(segments[i]); + } + } + + this.segments = newSegments; + + return this; + }; - // in batch mode HTML drawing is not possible, just keep object reference for a minute - if (isBatchMode()) { - painter.selectDom().property('_json_object_', lst); - return painter; - } - painter._streamer_info = true; - painter.h = createStreamerInfoContent(lst); + // Converts segments from relative to absolute + // + SvgPath.prototype.abs = function () { + + this.iterate(function (s, index, x, y) { + var name = s[0], + nameUC = name.toUpperCase(), + i; + + // Skip absolute commands + if (name === nameUC) { return; } + + s[0] = nameUC; + + switch (name) { + case 'v': + // v has shifted coords parity + s[1] += y; + return; + + case 'a': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // touch x, y only + s[6] += x; + s[7] += y; + return; + + default: + for (i = 1; i < s.length; i++) { + s[i] += i % 2 ? x : y; // odd values are X, even - Y + } + } + }, true); + + return this; + }; - // painter.selectDom().style('overflow','auto'); - return painter.refreshHtml().then(() => { - painter.setTopPainter(); - return painter; - }); -} + // Converts segments from absolute to relative + // + SvgPath.prototype.rel = function () { + + this.iterate(function (s, index, x, y) { + var name = s[0], + nameLC = name.toLowerCase(), + i; + + // Skip relative commands + if (name === nameLC) { return; } + + // Don't touch the first M to avoid potential confusions. + if (index === 0 && name === 'M') { return; } + + s[0] = nameLC; + + switch (name) { + case 'V': + // V has shifted coords parity + s[1] -= y; + return; + + case 'A': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // touch x, y only + s[6] -= x; + s[7] -= y; + return; + + default: + for (i = 1; i < s.length; i++) { + s[i] -= i % 2 ? x : y; // odd values are X, even - Y + } + } + }, true); + + return this; + }; -// ====================================================================================== -/** @summary Display inspector - * @private */ -async function drawInspector(dom, obj, opt) { - cleanup(dom); - const painter = new HierarchyPainter('inspector', dom, '__as_dark_mode__'); + // Converts arcs to cubic bézier curves + // + SvgPath.prototype.unarc = function () { + this.iterate(function (s, index, x, y) { + var new_segments, nextX, nextY, result = [], name = s[0]; + + // Skip anything except arcs + if (name !== 'A' && name !== 'a') { return null; } + + if (name === 'a') { + // convert relative arc coordinates to absolute + nextX = x + s[6]; + nextY = y + s[7]; + } else { + nextX = s[6]; + nextY = s[7]; + } + + new_segments = a2c(x, y, nextX, nextY, s[4], s[5], s[1], s[2], s[3]); + + // Degenerated arcs can be ignored by renderer, but should not be dropped + // to avoid collisions with `S A S` and so on. Replace with empty line. + if (new_segments.length === 0) { + return [ [ s[0] === 'a' ? 'l' : 'L', s[6], s[7] ] ]; + } + + new_segments.forEach(function (s) { + result.push([ 'C', s[2], s[3], s[4], s[5], s[6], s[7] ]); + }); + + return result; + }); + + return this; + }; - // in batch mode HTML drawing is not possible, just keep object reference for a minute - if (isBatchMode()) { - painter.selectDom().property('_json_object_', obj); - return painter; - } - painter.default_by_click = 'expand'; // default action - painter.with_icons = false; - painter._inspector = true; // keep - let expand_level = 0; + // Converts smooth curves (with missed control point) to generic curves + // + SvgPath.prototype.unshort = function () { + var segments = this.segments; + var prevControlX, prevControlY, prevSegment; + var curControlX, curControlY; + + // TODO: add lazy evaluation flag when relative commands supported + + this.iterate(function (s, idx, x, y) { + var name = s[0], nameUC = name.toUpperCase(), isRelative; + + // First command MUST be M|m, it's safe to skip. + // Protect from access to [-1] for sure. + if (!idx) { return; } + + if (nameUC === 'T') { // quadratic curve + isRelative = (name === 't'); + + prevSegment = segments[idx - 1]; + + if (prevSegment[0] === 'Q') { + prevControlX = prevSegment[1] - x; + prevControlY = prevSegment[2] - y; + } else if (prevSegment[0] === 'q') { + prevControlX = prevSegment[1] - prevSegment[3]; + prevControlY = prevSegment[2] - prevSegment[4]; + } else { + prevControlX = 0; + prevControlY = 0; + } + + curControlX = -prevControlX; + curControlY = -prevControlY; + + if (!isRelative) { + curControlX += x; + curControlY += y; + } + + segments[idx] = [ + isRelative ? 'q' : 'Q', + curControlX, curControlY, + s[1], s[2] + ]; + + } else if (nameUC === 'S') { // cubic curve + isRelative = (name === 's'); + + prevSegment = segments[idx - 1]; + + if (prevSegment[0] === 'C') { + prevControlX = prevSegment[3] - x; + prevControlY = prevSegment[4] - y; + } else if (prevSegment[0] === 'c') { + prevControlX = prevSegment[3] - prevSegment[5]; + prevControlY = prevSegment[4] - prevSegment[6]; + } else { + prevControlX = 0; + prevControlY = 0; + } + + curControlX = -prevControlX; + curControlY = -prevControlY; + + if (!isRelative) { + curControlX += x; + curControlY += y; + } + + segments[idx] = [ + isRelative ? 'c' : 'C', + curControlX, curControlY, + s[1], s[2], s[3], s[4] + ]; + } + }); + + return this; + }; - if (isStr(opt) && opt.indexOf(kInspect) === 0) { - opt = opt.slice(kInspect.length); - if (opt.length > 0) - expand_level = Number.parseInt(opt); - } - if (painter.selectDom().classed('jsroot_inspector')) { - painter.removeInspector = function() { - this.selectDom().remove(); - }; - } + svgpath$1 = SvgPath; + return svgpath$1; +} - painter.fill_context = function(menu, hitem) { - const sett = getDrawSettings(hitem._kind, 'nosame'); - if (sett.opts) { - menu.addDrawMenu('nosub:Draw', sett.opts, arg => { - if (!hitem?._obj) return; - const obj = hitem._obj; - let ddom = this.selectDom().node(); - if (isFunc(this.removeInspector)) { - ddom = ddom.parentNode; - this.removeInspector(); - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg, obj); - } - cleanup(ddom); - draw(ddom, obj, arg); - }); - } - }; +var svgpath; +var hasRequiredSvgpath; - painter.h = createInspectorContent(obj); +function requireSvgpath () { + if (hasRequiredSvgpath) return svgpath; + hasRequiredSvgpath = 1; - return painter.refreshHtml().then(() => { - painter.setTopPainter(); - return painter.exapndToLevel(expand_level); - }); + svgpath = requireSvgpath$1(); + return svgpath; } -internals.drawInspector = drawInspector; +var svgpathExports = requireSvgpath(); +var SvgPath = /*@__PURE__*/getDefaultExportFromCjs(svgpathExports); -var HierarchyPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -HierarchyPainter: HierarchyPainter, -drawInspector: drawInspector, -drawList: drawList, -drawStreamerInfo: drawStreamerInfo, -folderHierarchy: folderHierarchy, -getHPainter: getHPainter, -keysHierarchy: keysHierarchy, -listHierarchy: listHierarchy, -markAsStreamerInfo: markAsStreamerInfo, -objectHierarchy: objectHierarchy, -taskHierarchy: taskHierarchy -}); +var PathNode = /** @class */ (function (_super) { + __extends(PathNode, _super); + function PathNode(node, children) { + return _super.call(this, true, node, children) || this; + } + PathNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + PathNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + PathNode.prototype.getPath = function (context) { + var svgPath = new SvgPath(getAttribute(this.element, context.styleSheets, 'd') || '') + .unshort() + .unarc() + .abs(); + var path = new Path(); + var prevX; + var prevY; + svgPath.iterate(function (seg) { + switch (seg[0]) { + case 'M': + path.moveTo(seg[1], seg[2]); + break; + case 'L': + path.lineTo(seg[1], seg[2]); + break; + case 'H': + path.lineTo(seg[1], prevY); + break; + case 'V': + path.lineTo(prevX, seg[1]); + break; + case 'C': + path.curveTo(seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]); + break; + case 'Q': + var p2 = toCubic([prevX, prevY], [seg[1], seg[2]]); + var p3 = toCubic([seg[3], seg[4]], [seg[1], seg[2]]); + path.curveTo(p2[0], p2[1], p3[0], p3[1], seg[3], seg[4]); + break; + case 'Z': + path.close(); + break; + } + switch (seg[0]) { + case 'M': + case 'L': + prevX = seg[1]; + prevY = seg[2]; + break; + case 'H': + prevX = seg[1]; + break; + case 'V': + prevY = seg[1]; + break; + case 'C': + prevX = seg[5]; + prevY = seg[6]; + break; + case 'Q': + prevX = seg[3]; + prevY = seg[4]; + break; + } + }); + return path; + }; + return PathNode; +}(GeometryNode)); + +// groups: 1: mime-type (+ charset), 2: mime-type (w/o charset), 3: charset, 4: base64?, 5: body +var dataUriRegex = /^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,((?:.|\s)*)$/i; +var ImageNode = /** @class */ (function (_super) { + __extends(ImageNode, _super); + function ImageNode(element, children) { + var _this = _super.call(this, element, children) || this; + _this.imageLoadingPromise = null; + _this.imageUrl = _this.element.getAttribute('xlink:href') || _this.element.getAttribute('href'); + if (_this.imageUrl) { + // start loading the image as early as possible + _this.imageLoadingPromise = ImageNode.fetchImageData(_this.imageUrl); + } + return _this; + } + ImageNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var width, height, x, y, _a, data, format, parser, svgElement, preserveAspectRatio, idMap, svgnode, dataUri, _b, imgWidth, imgHeight, viewBox, transform, e_1; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + if (!this.imageLoadingPromise) { + return [2 /*return*/]; + } + context.pdf.setCurrentTransformationMatrix(context.transform); + width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'), height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'), x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + if (!isFinite(width) || width <= 0 || !isFinite(height) || height <= 0) { + return [2 /*return*/]; + } + return [4 /*yield*/, this.imageLoadingPromise]; + case 1: + _a = _c.sent(), data = _a.data, format = _a.format; + if (!(format.indexOf('svg') === 0)) return [3 /*break*/, 3]; + parser = new DOMParser(); + svgElement = parser.parseFromString(data, 'image/svg+xml').firstElementChild; + preserveAspectRatio = this.element.getAttribute('preserveAspectRatio'); + if (!preserveAspectRatio || + preserveAspectRatio.indexOf('defer') < 0 || + !svgElement.getAttribute('preserveAspectRatio')) { + svgElement.setAttribute('preserveAspectRatio', preserveAspectRatio || ''); + } + svgElement.setAttribute('x', String(x)); + svgElement.setAttribute('y', String(y)); + svgElement.setAttribute('width', String(width)); + svgElement.setAttribute('height', String(height)); + idMap = {}; + svgnode = parse(svgElement, idMap); + return [4 /*yield*/, svgnode.render(new Context(context.pdf, { + refsHandler: new ReferencesHandler(idMap), + styleSheets: context.styleSheets, + viewport: new Viewport(width, height), + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _c.sent(); + return [2 /*return*/]; + case 3: + dataUri = "data:image/".concat(format, ";base64,").concat(btoa(data)); + _c.label = 4; + case 4: + _c.trys.push([4, 6, , 7]); + return [4 /*yield*/, ImageNode.getImageDimensions(dataUri)]; + case 5: + _b = _c.sent(), imgWidth = _b[0], imgHeight = _b[1]; + viewBox = [0, 0, imgWidth, imgHeight]; + transform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); + context.pdf.setCurrentTransformationMatrix(transform); + context.pdf.addImage(dataUri, '', // will be ignored anyways if imageUrl is a data url + 0, 0, imgWidth, imgHeight); + return [3 /*break*/, 7]; + case 6: + e_1 = _c.sent(); + typeof console === 'object' && + console.warn && + console.warn("Could not load image ".concat(this.imageUrl, ". \n").concat(e_1)); + return [3 /*break*/, 7]; + case 7: return [2 /*return*/]; + } + }); + }); + }; + ImageNode.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + ImageNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + ImageNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + ImageNode.fetchImageData = function (imageUrl) { + return __awaiter(this, void 0, void 0, function () { + var data, format, match, mimeType, mimeTypeParts; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + match = imageUrl.match(dataUriRegex); + if (!match) return [3 /*break*/, 1]; + mimeType = match[2]; + mimeTypeParts = mimeType.split('/'); + if (mimeTypeParts[0] !== 'image') { + throw new Error("Unsupported image URL: ".concat(imageUrl)); + } + format = mimeTypeParts[1]; + data = match[5]; + if (match[4] === 'base64') { + data = data.replace(/\s/g, ''); + data = atob(data); + } + else { + data = decodeURIComponent(data); + } + return [3 /*break*/, 3]; + case 1: return [4 /*yield*/, ImageNode.fetchImage(imageUrl)]; + case 2: + data = _a.sent(); + format = imageUrl.substring(imageUrl.lastIndexOf('.') + 1); + _a.label = 3; + case 3: return [2 /*return*/, { + data: data, + format: format + }]; + } + }); + }); + }; + ImageNode.fetchImage = function (imageUrl) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', imageUrl, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function () { + if (xhr.status !== 200) { + throw new Error("Error ".concat(xhr.status, ": Failed to load image '").concat(imageUrl, "'")); + } + var bytes = new Uint8Array(xhr.response); + var data = ''; + for (var i = 0; i < bytes.length; i++) { + data += String.fromCharCode(bytes[i]); + } + resolve(data); + }; + xhr.onerror = reject; + xhr.onabort = reject; + xhr.send(null); + }); + }; + ImageNode.getMimeType = function (format) { + format = format.toLowerCase(); + switch (format) { + case 'jpg': + case 'jpeg': + return 'image/jpeg'; + default: + return "image/".concat(format); + } + }; + ImageNode.getImageDimensions = function (src) { + return new Promise(function (resolve, reject) { + var img = new Image(); + img.onload = function () { + resolve([img.width, img.height]); + }; + img.onerror = reject; + img.src = src; + }); + }; + return ImageNode; +}(GraphicsNode)); + +var Traverse = /** @class */ (function (_super) { + __extends(Traverse, _super); + function Traverse(closed, node, children) { + var _this = _super.call(this, true, node, children) || this; + _this.closed = closed; + return _this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Traverse.prototype.getPath = function (context) { + if (!this.element.hasAttribute('points') || this.element.getAttribute('points') === '') { + return null; + } + // @ts-ignore + var points = Traverse.parsePointsString(this.element.getAttribute('points')); + var path = new Path(); + if (points.length < 1) { + return path; + } + path.moveTo(points[0][0], points[0][1]); + for (var i = 1; i < points.length; i++) { + path.lineTo(points[i][0], points[i][1]); + } + if (this.closed) { + path.close(); + } + return path; + }; + Traverse.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Traverse.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Traverse.parsePointsString = function (string) { + var floats = parseFloats(string); + var result = []; + for (var i = 0; i < floats.length - 1; i += 2) { + var x = floats[i]; + var y = floats[i + 1]; + result.push([x, y]); + } + return result; + }; + return Traverse; +}(GeometryNode)); -/** @summary Read style and settings from URL - * @private */ -function readStyleFromURL(url) { - // first try to read settings from coockies - const d = decodeUrl(url), - prefix = d.get('storage_prefix'); +var Polygon = /** @class */ (function (_super) { + __extends(Polygon, _super); + function Polygon(node, children) { + return _super.call(this, true, node, children) || this; + } + return Polygon; +}(Traverse)); - if (isStr(prefix) && prefix) - setStoragePrefix(prefix); +var VoidNode = /** @class */ (function (_super) { + __extends(VoidNode, _super); + function VoidNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + VoidNode.prototype.render = function (parentContext) { + return Promise.resolve(); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + VoidNode.prototype.getBoundingBoxCore = function (context) { + return [0, 0, 0, 0]; + }; + VoidNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + VoidNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return VoidNode; +}(SvgNode)); - readSettings(); - readStyle(); +var MarkerNode = /** @class */ (function (_super) { + __extends(MarkerNode, _super); + function MarkerNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + MarkerNode.prototype.apply = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var tfMatrix, bBox, contextColors, childContext, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + tfMatrix = this.computeNodeTransform(parentContext); + bBox = this.getBoundingBox(parentContext); + parentContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], tfMatrix); + contextColors = AttributeState.getContextColors(parentContext); + childContext = new Context(parentContext.pdf, { + refsHandler: parentContext.refsHandler, + styleSheets: parentContext.styleSheets, + viewport: parentContext.viewport, + svg2pdfParameters: parentContext.svg2pdfParameters, + textMeasure: parentContext.textMeasure, + attributeState: Object.assign(AttributeState.default(), contextColors) + }); + // "Properties do not inherit from the element referencing the 'marker' into the contents of the + // marker. However, by using the context-stroke value for the fill or stroke on elements in its + // definition, a single marker can be designed to match the style of the element referencing the + // marker." + // -> we need to reset all attributes + applyContext(childContext); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(childContext)]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + parentContext.pdf.endFormObject(childContext.refsHandler.generateKey(this.element.getAttribute('id'), contextColors)); + return [2 /*return*/]; + } + }); + }); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + MarkerNode.prototype.getBoundingBoxCore = function (context) { + var viewBox = this.element.getAttribute('viewBox'); + var vb; + if (viewBox) { + vb = parseFloats(viewBox); + } + return [ + (vb && vb[0]) || 0, + (vb && vb[1]) || 0, + (vb && vb[2]) || parseFloat(this.element.getAttribute('markerWidth') || '3'), + (vb && vb[3]) || parseFloat(this.element.getAttribute('markerHeight') || '3') + ]; + }; + MarkerNode.prototype.computeNodeTransformCore = function (context) { + var refX = parseFloat(this.element.getAttribute('refX') || '0'); + var refY = parseFloat(this.element.getAttribute('refY') || '0'); + var viewBox = this.element.getAttribute('viewBox'); + var nodeTransform; + if (viewBox) { + var bounds = parseFloats(viewBox); + // "Markers are drawn such that their reference point (i.e., attributes ‘refX’ and ‘refY’) + // is positioned at the given vertex." - The "translate" part of the viewBox transform is + // ignored. + nodeTransform = computeViewBoxTransform(this.element, bounds, 0, 0, parseFloat(this.element.getAttribute('markerWidth') || '3'), parseFloat(this.element.getAttribute('markerHeight') || '3'), context, true); + nodeTransform = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, -refX, -refY), nodeTransform); + } + else { + nodeTransform = context.pdf.Matrix(1, 0, 0, 1, -refX, -refY); + } + return nodeTransform; + }; + MarkerNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + return MarkerNode; +}(NonRenderedNode)); - function get_bool(name, field, special) { - if (d.has(name)) { - const val = d.get(name); - if (special && (val === special)) - settings[field] = special; - else - settings[field] = (val !== '0') && (val !== 'false') && (val !== 'off'); - } - } +var Circle = /** @class */ (function (_super) { + __extends(Circle, _super); + function Circle(node, children) { + return _super.call(this, node, children) || this; + } + Circle.prototype.getR = function (context) { + var _a; + return ((_a = this.r) !== null && _a !== void 0 ? _a : (this.r = parseFloat(getAttribute(this.element, context.styleSheets, 'r') || '0'))); + }; + Circle.prototype.getRx = function (context) { + return this.getR(context); + }; + Circle.prototype.getRy = function (context) { + return this.getR(context); + }; + return Circle; +}(EllipseBase)); - if (d.has('optimize')) { - settings.OptimizeDraw = 2; - let optimize = d.get('optimize'); - if (optimize) { - optimize = parseInt(optimize); - if (Number.isInteger(optimize)) - settings.OptimizeDraw = optimize; - } - } +var Polyline = /** @class */ (function (_super) { + __extends(Polyline, _super); + function Polyline(node, children) { + return _super.call(this, false, node, children) || this; + } + return Polyline; +}(Traverse)); - get_bool('lastcycle', 'OnlyLastCycle'); - get_bool('usestamp', 'UseStamp'); - get_bool('dark', 'DarkMode'); +var ContainerNode = /** @class */ (function (_super) { + __extends(ContainerNode, _super); + function ContainerNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + ContainerNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(context)]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + ContainerNode.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + return ContainerNode; +}(RenderedNode)); - let mr = d.get('maxranges'); - if (mr) { - mr = parseInt(mr); - if (Number.isInteger(mr)) settings.MaxRanges = mr; - } +var Svg = /** @class */ (function (_super) { + __extends(Svg, _super); + function Svg() { + return _super !== null && _super.apply(this, arguments) || this; + } + Svg.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Svg.prototype.render = function (context) { + return __awaiter(this, void 0, void 0, function () { + var x, y, width, height, transform; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.isVisible(context.attributeState.visibility !== 'hidden', context)) { + return [2 /*return*/]; + } + x = this.getX(context); + y = this.getY(context); + width = this.getWidth(context); + height = this.getHeight(context); + context.pdf.saveGraphicsState(); + transform = context.transform; + if (this.element.hasAttribute('transform')) { + // SVG 2 allows transforms on SVG elements + // "The transform should be applied as if the ‘svg’ had a parent element with that transform set." + transform = context.pdf.matrixMult( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parseTransform(this.element.getAttribute('transform'), context), transform); + } + context.pdf.setCurrentTransformationMatrix(transform); + if (!context.withinUse && + getAttribute(this.element, context.styleSheets, 'overflow') !== 'visible') { + // establish a new viewport + context.pdf + .rect(x, y, width, height) + .clip() + .discardPath(); + } + return [4 /*yield*/, _super.prototype.render.call(this, context.clone({ + transform: context.pdf.unitMatrix, + viewport: context.withinUse ? context.viewport : new Viewport(width, height) + }))]; + case 1: + _a.sent(); + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + } + }); + }); + }; + Svg.prototype.computeNodeTransform = function (context) { + return this.computeNodeTransformCore(context); + }; + Svg.prototype.computeNodeTransformCore = function (context) { + if (context.withinUse) { + return context.pdf.unitMatrix; + } + var x = this.getX(context); + var y = this.getY(context); + var viewBox = this.getViewBox(); + var nodeTransform; + if (viewBox) { + var width = this.getWidth(context); + var height = this.getHeight(context); + nodeTransform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); + } + else { + nodeTransform = context.pdf.Matrix(1, 0, 0, 1, x, y); + } + return nodeTransform; + }; + Svg.prototype.getWidth = function (context) { + if (this.width !== undefined) { + return this.width; + } + var width; + var parameters = context.svg2pdfParameters; + if (this.isOutermostSvg(context)) { + // special treatment for the outermost SVG element + if (parameters.width != null) { + // if there is a user defined width, use it + width = parameters.width; + } + else { + // otherwise check if the SVG element defines the width itself + var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); + if (widthAttr) { + width = parseFloat(widthAttr); + } + else { + // if not, check if we can figure out the aspect ratio from the viewBox attribute + var viewBox = this.getViewBox(); + if (viewBox && + (parameters.height != null || getAttribute(this.element, context.styleSheets, 'height'))) { + // if there is a viewBox and the height is defined, use the width that matches the height together with the aspect ratio + var aspectRatio = viewBox[2] / viewBox[3]; + width = this.getHeight(context) * aspectRatio; + } + else { + // if there is no viewBox use a default of 300 or the largest size that fits into the outer viewport + // at an aspect ratio of 2:1 + width = Math.min(300, context.viewport.width, context.viewport.height * 2); + } + } + } + } + else { + var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); + width = widthAttr ? parseFloat(widthAttr) : context.viewport.width; + } + return (this.width = width); + }; + Svg.prototype.getHeight = function (context) { + if (this.height !== undefined) { + return this.height; + } + var height; + var parameters = context.svg2pdfParameters; + if (this.isOutermostSvg(context)) { + // special treatment for the outermost SVG element + if (parameters.height != null) { + // if there is a user defined height, use it + height = parameters.height; + } + else { + // otherwise check if the SVG element defines the height itself + var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); + if (heightAttr) { + height = parseFloat(heightAttr); + } + else { + // if not, check if we can figure out the aspect ratio from the viewBox attribute + var viewBox = this.getViewBox(); + if (viewBox) { + // if there is a viewBox, use the height that matches the width together with the aspect ratio + var aspectRatio = viewBox[2] / viewBox[3]; + height = this.getWidth(context) / aspectRatio; + } + else { + // if there is no viewBox use a default of 150 or the largest size that fits into the outer viewport + // at an aspect ratio of 2:1 + height = Math.min(150, context.viewport.width / 2, context.viewport.height); + } + } + } + } + else { + var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); + height = heightAttr ? parseFloat(heightAttr) : context.viewport.height; + } + return (this.height = height); + }; + Svg.prototype.getX = function (context) { + if (this.x !== undefined) { + return this.x; + } + if (this.isOutermostSvg(context)) { + return (this.x = 0); + } + var xAttr = getAttribute(this.element, context.styleSheets, 'x'); + return (this.x = xAttr ? parseFloat(xAttr) : 0); + }; + Svg.prototype.getY = function (context) { + if (this.y !== undefined) { + return this.y; + } + if (this.isOutermostSvg(context)) { + return (this.y = 0); + } + var yAttr = getAttribute(this.element, context.styleSheets, 'y'); + return (this.y = yAttr ? parseFloat(yAttr) : 0); + }; + Svg.prototype.getViewBox = function () { + if (this.viewBox !== undefined) { + return this.viewBox; + } + var viewBox = this.element.getAttribute('viewBox'); + return (this.viewBox = viewBox ? parseFloats(viewBox) : undefined); + }; + Svg.prototype.isOutermostSvg = function (context) { + return context.svg2pdfParameters.element === this.element; + }; + return Svg; +}(ContainerNode)); - if (d.has('wrong_http_response')) - settings.HandleWrongHttpResponse = true; +var Group = /** @class */ (function (_super) { + __extends(Group, _super); + function Group() { + return _super !== null && _super.apply(this, arguments) || this; + } + Group.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Group.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return Group; +}(ContainerNode)); - if (d.has('prefer_saved_points')) - settings.PreferSavedPoints = true; +var Anchor = /** @class */ (function (_super) { + __extends(Anchor, _super); + function Anchor() { + return _super !== null && _super.apply(this, arguments) || this; + } + Anchor.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var href, box, scale, ph; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, _super.prototype.renderCore.call(this, context)]; + case 1: + _a.sent(); + href = getAttribute(this.element, context.styleSheets, 'href'); + if (href) { + box = this.getBoundingBox(context); + scale = context.pdf.internal.scaleFactor; + ph = context.pdf.internal.pageSize.getHeight(); + context.pdf.link(scale * (box[0] * context.transform.sx + context.transform.tx), scale * (ph - box[1] * context.transform.sy - context.transform.ty), scale * context.transform.sx * box[2], scale * context.transform.sy * box[3], { url: href }); + } + return [2 /*return*/]; + } + }); + }); + }; + return Anchor; +}(Group)); - const tf1_style = d.get('tf1'); - if (tf1_style === 'curve') - settings.FuncAsCurve = true; - else if (tf1_style === 'line') - settings.FuncAsCurve = false; +var ClipPath = /** @class */ (function (_super) { + __extends(ClipPath, _super); + function ClipPath() { + return _super !== null && _super.apply(this, arguments) || this; + } + ClipPath.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var clipPathMatrix, _i, _a, child, hasClipRuleFromFirstChild, clipRule; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.isVisible(true, context)) { + return [2 /*return*/]; + } + clipPathMatrix = context.pdf.matrixMult(this.computeNodeTransform(context), context.transform); + context.pdf.setCurrentTransformationMatrix(clipPathMatrix); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(new Context(context.pdf, { + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + viewport: context.viewport, + withinClipPath: true, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + hasClipRuleFromFirstChild = this.children.length > 0 && !!getAttribute(this.children[0].element, context.styleSheets, 'clip-rule'); + clipRule = hasClipRuleFromFirstChild + ? this.getClipRuleAttr(this.children[0].element, context.styleSheets) + : this.getClipRuleAttr(this.element, context.styleSheets); + context.pdf.clip(clipRule).discardPath(); + // as we cannot use restoreGraphicsState() to reset the transform (this would reset the clipping path, as well), + // we must append the inverse instead + context.pdf.setCurrentTransformationMatrix(clipPathMatrix.inversed()); + return [2 /*return*/]; + } + }); + }); + }; + ClipPath.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + ClipPath.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + ClipPath.prototype.getClipRuleAttr = function (element, styleSheets) { + return getAttribute(element, styleSheets, 'clip-rule') === 'evenodd' ? 'evenodd' : undefined; + }; + return ClipPath; +}(NonRenderedNode)); + +function parse(node, idMap) { + var svgnode; + var children = []; + forEachChild(node, function (i, n) { return children.push(parse(n, idMap)); }); + switch (node.tagName.toLowerCase()) { + case 'a': + svgnode = new Anchor(node, children); + break; + case 'g': + svgnode = new Group(node, children); + break; + case 'circle': + svgnode = new Circle(node, children); + break; + case 'clippath': + svgnode = new ClipPath(node, children); + break; + case 'ellipse': + svgnode = new Ellipse(node, children); + break; + case 'lineargradient': + svgnode = new LinearGradient(node, children); + break; + case 'image': + svgnode = new ImageNode(node, children); + break; + case 'line': + svgnode = new Line(node, children); + break; + case 'marker': + svgnode = new MarkerNode(node, children); + break; + case 'path': + svgnode = new PathNode(node, children); + break; + case 'pattern': + svgnode = new Pattern(node, children); + break; + case 'polygon': + svgnode = new Polygon(node, children); + break; + case 'polyline': + svgnode = new Polyline(node, children); + break; + case 'radialgradient': + svgnode = new RadialGradient(node, children); + break; + case 'rect': + svgnode = new Rect(node, children); + break; + case 'svg': + svgnode = new Svg(node, children); + break; + case 'symbol': + svgnode = new Symbol$1(node, children); + break; + case 'text': + svgnode = new TextNode(node, children); + break; + case 'use': + svgnode = new Use(node, children); + break; + default: + svgnode = new VoidNode(node, children); + break; + } + if (idMap != undefined && svgnode.element.hasAttribute('id')) { + // const id = cssesc(svgnode.element.id, { isIdentifier: true }) + var id = svgnode.element.id; // jsroot has plain ids + idMap[id] = idMap[id] || svgnode; + } + svgnode.children.forEach(function (c) { return c.setParent(svgnode); }); + return svgnode; +} - if (d.has('with_credentials')) - settings.WithCredentials = true; +var StyleSheets = /** @class */ (function () { + function StyleSheets(rootSvg, loadExtSheets) { + this.rootSvg = rootSvg; + this.loadExternalSheets = loadExtSheets; + this.styleSheets = []; + } + StyleSheets.prototype.load = function () { + return __awaiter(this, void 0, void 0, function () { + var sheetTexts; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.collectStyleSheetTexts()]; + case 1: + sheetTexts = _a.sent(); + this.parseCssSheets(sheetTexts); + return [2 /*return*/]; + } + }); + }); + }; + StyleSheets.prototype.collectStyleSheetTexts = function () { + return __awaiter(this, void 0, void 0, function () { + var sheetTexts, i, node, styleElements, i, styleElement; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + sheetTexts = []; + if (this.loadExternalSheets && this.rootSvg.ownerDocument) { + for (i = 0; i < this.rootSvg.ownerDocument.childNodes.length; i++) { + node = this.rootSvg.ownerDocument.childNodes[i]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (node.nodeName === 'xml-stylesheet' && typeof node.data === 'string') { + sheetTexts.push(StyleSheets.loadSheet( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + node.data + .match(/href=["'].*?["']/)[0] + .split('=')[1] + .slice(1, -1))); + } + } + } + styleElements = this.rootSvg.querySelectorAll('style,link'); + for (i = 0; i < styleElements.length; i++) { + styleElement = styleElements[i]; + if (nodeIs(styleElement, 'style')) { + sheetTexts.push(styleElement.textContent); + } + else if (this.loadExternalSheets && + nodeIs(styleElement, 'link') && + styleElement.getAttribute('rel') === 'stylesheet' && + styleElement.hasAttribute('href')) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sheetTexts.push(StyleSheets.loadSheet(styleElement.getAttribute('href'))); + } + } + return [4 /*yield*/, Promise.all(sheetTexts)]; + case 1: return [2 /*return*/, (_a.sent()).filter(function (sheet) { return sheet !== null; })]; + } + }); + }); + }; + StyleSheets.prototype.parseCssSheets = function (sheetTexts) { + var styleDoc = document.implementation.createHTMLDocument(''); + for (var _i = 0, sheetTexts_1 = sheetTexts; _i < sheetTexts_1.length; _i++) { + var sheetText = sheetTexts_1[_i]; + var style = styleDoc.createElement('style'); + style.textContent = sheetText; + styleDoc.body.appendChild(style); + var sheet = style.sheet; + if (sheet instanceof CSSStyleSheet) { + for (var i = sheet.cssRules.length - 1; i >= 0; i--) { + var cssRule = sheet.cssRules[i]; + if (!(cssRule instanceof CSSStyleRule)) { + sheet.deleteRule(i); + continue; + } + var cssStyleRule = cssRule; + if (cssStyleRule.selectorText.indexOf(',') >= 0) { + sheet.deleteRule(i); + var body = cssStyleRule.cssText.substring(cssStyleRule.selectorText.length); + var selectors = StyleSheets.splitSelectorAtCommas(cssStyleRule.selectorText); + for (var j = 0; j < selectors.length; j++) { + sheet.insertRule(selectors[j] + body, i + j); + } + } + } + this.styleSheets.push(sheet); + } + } + }; + StyleSheets.splitSelectorAtCommas = function (selectorText) { + var initialRegex = /,|["']/g; + var closingDoubleQuotesRegex = /[^\\]["]/g; + var closingSingleQuotesRegex = /[^\\][']/g; + var parts = []; + var state = 'initial'; + var match; + var lastCommaIndex = -1; + var closingQuotesRegex = closingDoubleQuotesRegex; + for (var i = 0; i < selectorText.length;) { + switch (state) { + case 'initial': + initialRegex.lastIndex = i; + match = initialRegex.exec(selectorText); + if (match) { + if (match[0] === ',') { + parts.push(selectorText.substring(lastCommaIndex + 1, initialRegex.lastIndex - 1).trim()); + lastCommaIndex = initialRegex.lastIndex - 1; + } + else { + state = 'withinQuotes'; + closingQuotesRegex = + match[0] === '"' ? closingDoubleQuotesRegex : closingSingleQuotesRegex; + } + i = initialRegex.lastIndex; + } + else { + parts.push(selectorText.substring(lastCommaIndex + 1).trim()); + i = selectorText.length; + } + break; + case 'withinQuotes': + closingQuotesRegex.lastIndex = i; + match = closingQuotesRegex.exec(selectorText); + if (match) { + i = closingQuotesRegex.lastIndex; + state = 'initial'; + } + // else this is a syntax error - omit the last part... + break; + } + } + return parts; + }; + StyleSheets.loadSheet = function (url) { + return (new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'text'; + xhr.onload = function () { + if (xhr.status !== 200) { + reject(new Error("Error ".concat(xhr.status, ": Failed to load '").concat(url, "'"))); + } + resolve(xhr.responseText); + }; + xhr.onerror = reject; + xhr.onabort = reject; + xhr.send(null); + }) + // ignore the error since some stylesheets may not be accessible + // due to CORS policies + .catch(function () { return null; })); + }; + StyleSheets.prototype.getPropertyValue = function (node, propertyCss) { + var matchingRules = []; + for (var _i = 0, _a = this.styleSheets; _i < _a.length; _i++) { + var sheet = _a[_i]; + for (var i = 0; i < sheet.cssRules.length; i++) { + var rule = sheet.cssRules[i]; + if (rule.style.getPropertyValue(propertyCss) && node.matches(rule.selectorText)) { + matchingRules.push(rule); + } + } + } + if (matchingRules.length === 0) { + return undefined; + } + var compare = function (a, b) { + var priorityA = a.style.getPropertyPriority(propertyCss); + var priorityB = b.style.getPropertyPriority(propertyCss); + if (priorityA !== priorityB) { + return priorityA === 'important' ? 1 : -1; + } + // console.log('removed specificity check ', a.selectorText, b.selectorText); + return 0; + // return compareSpecificity(a.selectorText, b.selectorText) + }; + var mostSpecificRule = matchingRules.reduce(function (previousValue, currentValue) { + return compare(previousValue, currentValue) === 1 ? previousValue : currentValue; + }); + return mostSpecificRule.style.getPropertyValue(propertyCss) || undefined; + }; + return StyleSheets; +}()); - let inter = d.get('interactive'); - if (inter === 'nomenu') - settings.ContextMenu = false; - else if (inter !== undefined) { - if (!inter || (inter === '1')) - inter = '111111'; - else if (inter === '0') - inter = '000000'; - if (inter.length === 6) { - switch (inter[0]) { - case '0': settings.ToolBar = false; break; - case '1': settings.ToolBar = 'popup'; break; - case '2': settings.ToolBar = true; break; - } - inter = inter.slice(1); - } - if (inter.length === 5) { - settings.Tooltip = parseInt(inter[0]); - settings.ContextMenu = (inter[1] !== '0'); - settings.Zooming = (inter[2] !== '0'); - settings.MoveResize = (inter[3] !== '0'); - settings.DragAndDrop = (inter[4] !== '0'); - } - } +var TextMeasure = /** @class */ (function () { + function TextMeasure() { + this.measureMethods = {}; + } + TextMeasure.prototype.getTextOffset = function (text, attributeState) { + var textAnchor = attributeState.textAnchor; + if (textAnchor === 'start') { + return 0; + } + var width = this.measureTextWidth(text, attributeState); + var xOffset = 0; + switch (textAnchor) { + case 'end': + xOffset = width; + break; + case 'middle': + xOffset = width / 2; + break; + } + return xOffset; + }; + TextMeasure.prototype.measureTextWidth = function (text, attributeState) { + if (text.length === 0) { + return 0; + } + var fontFamily = attributeState.fontFamily; + var measure = this.getMeasureFunction(fontFamily); + return measure.call(this, text, attributeState.fontFamily, attributeState.fontSize + 'px', attributeState.fontStyle, attributeState.fontWeight); + }; + TextMeasure.prototype.getMeasurementTextNode = function () { + if (!this.textMeasuringTextElement) { + this.textMeasuringTextElement = document.createElementNS(svgNamespaceURI, 'text'); + var svg = document.createElementNS(svgNamespaceURI, 'svg'); + svg.appendChild(this.textMeasuringTextElement); + svg.style.setProperty('position', 'absolute'); + svg.style.setProperty('visibility', 'hidden'); + document.body.appendChild(svg); + } + return this.textMeasuringTextElement; + }; + TextMeasure.prototype.canvasTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight) { + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + if (context != null) { + context.font = [fontStyle, fontWeight, fontSize, fontFamily].join(' '); + return context.measureText(text).width; + } + return 0; + }; + TextMeasure.prototype.svgTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight, measurementTextNode) { + if (measurementTextNode === void 0) { measurementTextNode = this.getMeasurementTextNode(); } + var textNode = measurementTextNode; + textNode.setAttribute('font-family', fontFamily); + textNode.setAttribute('font-size', fontSize); + textNode.setAttribute('font-style', fontStyle); + textNode.setAttribute('font-weight', fontWeight); + textNode.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + textNode.textContent = text; + return textNode.getBBox().width; + }; + /** + * Canvas text measuring is a lot faster than svg measuring. However, it is inaccurate for some fonts. So test each + * font once and decide if canvas is accurate enough. + */ + TextMeasure.prototype.getMeasureFunction = function (fontFamily) { + var method = this.measureMethods[fontFamily]; + if (!method) { + var fontSize = '16px'; + var fontStyle = 'normal'; + var fontWeight = 'normal'; + var canvasWidth = this.canvasTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); + var svgWidth = this.svgTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); + method = + Math.abs(canvasWidth - svgWidth) < TextMeasure.epsilon + ? this.canvasTextMeasure + : this.svgTextMeasure; + this.measureMethods[fontFamily] = method; + } + return method; + }; + TextMeasure.prototype.cleanupTextMeasuring = function () { + if (this.textMeasuringTextElement) { + var parentNode = this.textMeasuringTextElement.parentNode; + if (parentNode) { + document.body.removeChild(parentNode); + } + this.textMeasuringTextElement = undefined; + } + }; + TextMeasure.testString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!"$%&/()=?\'\\+*-_.:,;^}][{#~|<>'; + TextMeasure.epsilon = 0.1; + return TextMeasure; +}()); + +function svg2pdf(element_1, pdf_1) { + return __awaiter(this, arguments, void 0, function (element, pdf, options) { + var x, y, extCss, idMap, refsHandler, styleSheets, viewport, svg2pdfParameters, textMeasure, context, fill, node; + var _a, _b, _c; + if (options === void 0) { options = {}; } + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + x = (_a = options.x) !== null && _a !== void 0 ? _a : 0.0; + y = (_b = options.y) !== null && _b !== void 0 ? _b : 0.0; + extCss = (_c = options.loadExternalStyleSheets) !== null && _c !== void 0 ? _c : false; + idMap = {}; + refsHandler = new ReferencesHandler(idMap); + styleSheets = new StyleSheets(element, extCss); + return [4 /*yield*/, styleSheets.load() + // start with the entire page size as viewport + ]; + case 1: + _d.sent(); + viewport = new Viewport(pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight()); + svg2pdfParameters = __assign(__assign({}, options), { element: element }); + textMeasure = new TextMeasure(); + context = new Context(pdf, { + refsHandler: refsHandler, + styleSheets: styleSheets, + viewport: viewport, + svg2pdfParameters: svg2pdfParameters, + textMeasure: textMeasure + }); + pdf.advancedAPI(); + pdf.saveGraphicsState(); + // set offsets + pdf.setCurrentTransformationMatrix(pdf.Matrix(1, 0, 0, 1, x, y)); + // set default values that differ from pdf defaults + pdf.setLineWidth(context.attributeState.strokeWidth); + fill = context.attributeState.fill.color; + pdf.setFillColor(fill.r, fill.g, fill.b); + pdf.setFont(context.attributeState.fontFamily); + // correct for a jsPDF-instance measurement unit that differs from `pt` + pdf.setFontSize(context.attributeState.fontSize * pdf.internal.scaleFactor); + node = parse(element, idMap); + return [4 /*yield*/, node.render(context)]; + case 2: + _d.sent(); + pdf.restoreGraphicsState(); + pdf.compatAPI(); + context.textMeasure.cleanupTextMeasuring(); + return [2 /*return*/, pdf]; + } + }); + }); +} +jsPDF.API.svg = function (element, options) { + if (options === void 0) { options = {}; } + return svg2pdf(element, this, options); +}; - get_bool('tooltip', 'Tooltip'); +/** @summary Create pdf for existing SVG element + * @return {Promise} with produced PDF file as url string + * @private */ +async function makePDF(svg, args) { + const nodejs = isNodeJs(); + let need_symbols = false; - const mathjax = d.get('mathjax', null); - let latex = d.get('latex', null); - if ((mathjax !== null) && (mathjax !== '0') && (latex === null)) - latex = 'math'; - if (latex !== null) - settings.Latex = constants$1.Latex.fromString(latex); + const restore_fonts = [], restore_symb = [], restore_wing = [], restore_dominant = [], restore_oblique = [], restore_text = [], + node_transform = svg.node.getAttribute('transform'), custom_fonts = {}; - if (d.has('nomenu')) settings.ContextMenu = false; - if (d.has('noprogress')) - settings.ProgressBox = false; - else - get_bool('progress', 'ProgressBox', 'modal'); + if (svg.reset_tranform) + svg.node.removeAttribute('transform'); + + select(svg.node).selectAll('g').each(function() { + if (this.hasAttribute('font-family')) { + const name = this.getAttribute('font-family'); + if (name === kCourier) { + this.setAttribute('font-family', 'courier'); + if (!svg.can_modify) + restore_fonts.push(this); // keep to restore it + } + if (name === kSymbol) { + this.setAttribute('font-family', 'symbol'); + if (!svg.can_modify) + restore_symb.push(this); // keep to restore it + } + if (name === kWingdings) { + this.setAttribute('font-family', 'zapfdingbats'); + if (!svg.can_modify) + restore_wing.push(this); // keep to restore it + } + + if (((name === kArial) || (name === kCourier)) && (this.getAttribute('font-weight') === 'bold') && (this.getAttribute('font-style') === 'oblique')) { + this.setAttribute('font-style', 'italic'); + if (!svg.can_modify) + restore_oblique.push(this); // keep to restore it + } else if ((name === kCourier) && (this.getAttribute('font-style') === 'oblique')) { + this.setAttribute('font-style', 'italic'); + if (!svg.can_modify) + restore_oblique.push(this); // keep to restore it + } + } + }); - if (d.has('notouch')) browser.touches = false; - if (d.has('adjframe')) settings.CanAdjustFrame = true; + select(svg.node).selectAll('text').each(function() { + if (this.hasAttribute('dominant-baseline')) { + this.setAttribute('dy', '.2em'); // slightly different as in plain text + this.removeAttribute('dominant-baseline'); + if (!svg.can_modify) + restore_dominant.push(this); // keep to restore it + } else if (svg.can_modify && nodejs && this.getAttribute('dy') === '.4em') + this.setAttribute('dy', '.2em'); // better alignment in PDF - const has_toolbar = d.has('toolbar'); - if (has_toolbar) { - const toolbar = d.get('toolbar', ''); - let val = null; - if (toolbar.indexOf('popup') >= 0) val = 'popup'; - if (toolbar.indexOf('left') >= 0) { settings.ToolBarSide = 'left'; val = 'popup'; } - if (toolbar.indexOf('right') >= 0) { settings.ToolBarSide = 'right'; val = 'popup'; } - if (toolbar.indexOf('vert') >= 0) { settings.ToolBarVert = true; val = 'popup'; } - if (toolbar.indexOf('show') >= 0) val = true; - settings.ToolBar = val || ((toolbar.indexOf('0') < 0) && (toolbar.indexOf('false') < 0) && (toolbar.indexOf('off') < 0)); - } + if (replaceSymbolsInTextNode(this)) { + need_symbols = true; + if (!svg.can_modify) + restore_text.push(this); // keep to restore it + } + }); - get_bool('skipsi', 'SkipStreamerInfos'); - get_bool('skipstreamerinfos', 'SkipStreamerInfos'); + let pr = Promise.resolve(); - if (d.has('nodraggraphs')) - settings.DragGraphs = false; + if (nodejs) { + const doc = internals.nodejs_document; + doc.originalCreateElementNS = doc.createElementNS; + globalThis.document = doc; + globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet; + globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule; + doc.createElementNS = function(ns, kind) { + const res = doc.originalCreateElementNS(ns, kind); + res.getBBox = function() { + let width = 50, height = 10; + if (this.tagName === 'text') { + // TODO: use jsDOC fonts for label width estimation + const font = detectPdfFont(this); + width = approximateLabelWidth(this.textContent, font); + height = font.size * 1.2; + } - if (d.has('palette')) { - const palette = parseInt(d.get('palette')); - if (Number.isInteger(palette) && (palette > 0) && (palette < 113)) settings.Palette = palette; + return { x: 0, y: 0, width, height }; + }; + return res; + }; + + pr = Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(handle => { + globalThis.Image = handle.Image; + }); } - const render3d = d.get('render3d'), embed3d = d.get('embed3d'), geosegm = d.get('geosegm'); - if (render3d) settings.Render3D = constants$1.Render3D.fromString(render3d); - if (embed3d) settings.Embed3D = constants$1.Embed3D.fromString(embed3d); - if (geosegm) settings.GeoGradPerSegm = Math.max(2, parseInt(geosegm)); - get_bool('geocomp', 'GeoCompressComp'); + const orientation = (svg.width < svg.height) ? 'portrait' : 'landscape'; - if (d.has('hlimit')) settings.HierarchyLimit = parseInt(d.get('hlimit')); + let doc = args?.as_doc ? args.doc : null; - function get_int_style(name, field, dflt) { - if (!d.has(name)) return; - const val = d.get(name); - if (!val || (val === 'true') || (val === 'on')) - gStyle[field] = dflt; - else if ((val === 'false') || (val === 'off')) - gStyle[field] = 0; - else - gStyle[field] = parseInt(val); - return gStyle[field] !== 0; - } - function get_float_style(name, field) { - if (!d.has(name)) return; - const val = d.get(name), - flt = Number.parseFloat(val); - if (Number.isFinite(flt)) - gStyle[field] = flt; + if (doc) { + doc.addPage({ + orientation, + unit: 'px', + format: [svg.width + 10, svg.height + 10] + }); + } else { + doc = new jsPDF({ + orientation, + unit: 'px', + format: [svg.width + 10, svg.height + 10] + }); + if (args?.as_doc) + args.doc = doc; } - if (d.has('histzero')) gStyle.fHistMinimumZero = true; - if (d.has('histmargin')) gStyle.fHistTopMargin = parseFloat(d.get('histmargin')); - get_int_style('optstat', 'fOptStat', 1111); - get_int_style('optfit', 'fOptFit', 0); - const has_date = get_int_style('optdate', 'fOptDate', 1), - has_file = get_int_style('optfile', 'fOptFile', 1); - if ((has_date || has_file) && !has_toolbar) - settings.ToolBarVert = true; - get_float_style('datex', 'fDateX'); - get_float_style('datey', 'fDateY'); + // add custom fonts to PDF document, only TTF format supported + select(svg.node).selectAll('style').each(function() { + const fcfg = this.$fontcfg; + if (!fcfg?.n || !fcfg?.base64) + return; + const name = fcfg.n; + if ((name === kSymbol) || (name === kWingdings)) + return; + if (custom_fonts[name]) + return; + custom_fonts[name] = true; - get_int_style('opttitle', 'fOptTitle', 1); - if (d.has('utc')) - settings.TimeZone = 'UTC'; - if (d.has('cet')) - settings.TimeZone = 'Europe/Berlin'; - else if (d.has('timezone')) { - settings.TimeZone = d.get('timezone'); - if ((settings.TimeZone === 'default') || (settings.TimeZone === 'dflt')) - settings.TimeZone = 'Europe/Berlin'; - else if (settings.TimeZone === 'local') - settings.TimeZone = ''; + const filename = name.toLowerCase().replace(/\s/g, '') + '.ttf'; + doc.addFileToVFS(filename, fcfg.base64); + doc.addFont(filename, fcfg.n, fcfg.s || 'normal'); + }); + + if (need_symbols && !custom_fonts[kSymbol] && settings.LoadSymbolTtf) { + const handler = new FontHandler(122, 10); + pr = pr.then(() => handler.load()).then(() => { + handler.addCustomFontToSvg(select(svg.node)); + doc.addFileToVFS(kSymbol + '.ttf', handler.base64); + doc.addFont(kSymbol + '.ttf', kSymbol, 'normal'); + }); } - gStyle.fStatFormat = d.get('statfmt', gStyle.fStatFormat); - gStyle.fFitFormat = d.get('fitfmt', gStyle.fFitFormat); -} + return pr.then(() => svg2pdf(svg.node, doc, { x: 5, y: 5, width: svg.width, height: svg.height })).then(() => { + if (svg.reset_tranform && !svg.can_modify && node_transform) + svg.node.setAttribute('transform', node_transform); + restore_fonts.forEach(node => node.setAttribute('font-family', kCourier)); + restore_symb.forEach(node => node.setAttribute('font-family', kSymbol)); + restore_wing.forEach(node => node.setAttribute('font-family', kWingdings)); + restore_oblique.forEach(node => node.setAttribute('font-style', 'oblique')); + restore_dominant.forEach(node => { + node.setAttribute('dominant-baseline', 'middle'); + node.removeAttribute('dy'); + }); -/** @summary Build main GUI - * @desc Used in many HTML files to create JSROOT GUI elements - * @param {String} gui_element - id of the `
    ` element - * @param {String} gui_kind - either 'online', 'nobrowser', 'draw' - * @return {Promise} with {@link HierarchyPainter} instance - * @example - * import { buildGUI } from 'https://root.cern/js/latest/modules/gui.mjs'; - * buildGUI('guiDiv'); */ -async function buildGUI(gui_element, gui_kind = '') { - const myDiv = select(isStr(gui_element) ? `#${gui_element}` : gui_element); - if (myDiv.empty()) - return Promise.reject(Error('no div for gui found')); + restore_text.forEach(node => { + node.innerHTML = node.$originalHTML; + if (node.$originalFont) + node.setAttribute('font-family', node.$originalFont); + else + node.removeAttribute('font-family'); + }); - myDiv.html(''); // clear element + const res = args?.as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); + if (nodejs) { + globalThis.document = undefined; + globalThis.CSSStyleSheet = undefined; + globalThis.CSSStyleRule = undefined; + globalThis.Image = undefined; + internals.nodejs_document.createElementNS = internals.nodejs_document.originalCreateElementNS; + if (args?.as_buffer) + return Buffer.from(res); + } - const d = decodeUrl(); - let online = (gui_kind === 'online'), nobrowser = false, drawing = false; + return res; + }); +} - if (gui_kind === 'draw') - online = drawing = nobrowser = true; - else if ((gui_kind === 'nobrowser') || d.has('nobrowser') || (myDiv.attr('nobrowser') && myDiv.attr('nobrowser') !== 'false')) - nobrowser = true; +async function import_more() { return Promise.resolve().then(function () { return more; }); } - if (myDiv.attr('ignoreurl') === 'true') - settings.IgnoreUrlOptions = true; +async function import_canvas() { return Promise.resolve().then(function () { return TCanvasPainter$1; }); } - readStyleFromURL(); +async function import_tree() { return Promise.resolve().then(function () { return TTree; }); } - if (nobrowser) { - let guisize = d.get('divsize'); - if (guisize) { - guisize = guisize.split('x'); - if (guisize.length !== 2) guisize = null; - } +async function import_h() { return Promise.resolve().then(function () { return HierarchyPainter$1; }); } - if (guisize) - myDiv.style('position', 'relative').style('width', guisize[0] + 'px').style('height', guisize[1] + 'px'); - else { - select('html').style('height', '100%'); - select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden'); - myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px'); - } - } +let import_v7 = null, import_geo = null; - const hpainter = new HierarchyPainter('root', null); - if (online) hpainter.is_online = drawing ? 'draw' : 'online'; - if (drawing) hpainter.exclude_browser = true; - hpainter.start_without_browser = nobrowser; +const clTGraph2D = 'TGraph2D', clTH2Poly = 'TH2Poly', clTEllipse = 'TEllipse', + clTSpline3 = 'TSpline3', clTCanvasWebSnapshot = 'TCanvasWebSnapshot', + fPrimitives = 'fPrimitives', fFunctions = 'fFunctions'; - return hpainter.startGUI(myDiv).then(() => { - if (!nobrowser) - return hpainter.initializeBrowser(); - if (!drawing) - return; - const func = internals.getCachedObject || findFunction('GetCachedObject'), - obj = isFunc(func) ? parse(func()) : undefined; - if (isObject(obj)) - hpainter._cached_draw_object = obj; - let opt = d.get('opt', ''); - if (d.has('websocket')) - opt += ';websocket'; - return hpainter.display('', opt); - }).then(() => hpainter); -} +/** @summary list of registered draw functions + * @private */ +/* eslint-disable-next-line one-var */ +const drawFuncs = { lst: [ + { name: clTCanvas, icon: 'img_canvas', class: () => import_canvas().then(h => h.TCanvasPainter), opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, + { name: clTPad, icon: 'img_canvas', func: TPadPainter.draw, opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, + { name: 'TSlider', icon: 'img_canvas', func: TPadPainter.draw }, + { name: clTButton, icon: 'img_canvas', func: TPadPainter.draw }, + { name: 'TInspectCanvas', icon: 'img_canvas', sameas: clTCanvas }, + { name: clTFrame, icon: 'img_frame', draw: () => import_canvas().then(h => h.drawTFrame) }, + { name: clTPave, icon: 'img_pavetext', class: () => Promise.resolve().then(function () { return TPavePainter$1; }).then(h => h.TPavePainter) }, + { name: clTPaveText, sameas: clTPave }, + { name: clTPavesText, sameas: clTPave }, + { name: clTPaveStats, sameas: clTPave }, + { name: clTPaveLabel, sameas: clTPave }, + { name: clTPaveClass, sameas: clTPave }, + { name: clTDiamond, sameas: clTPave }, + { name: clTLegend, icon: 'img_pavelabel', sameas: clTPave }, + { name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave }, + { name: clTText, icon: 'img_text', class: () => Promise.resolve().then(function () { return TTextPainter$1; }).then(h => h.TTextPainter), build3d: () => Promise.resolve().then(function () { return latex3d; }).then(h => h.build3dlatex) }, + { name: clTMathText, sameas: clTText }, + { name: clTLatex, sameas: clTText }, + { name: clTLink, sameas: clTText }, + { name: clTAnnotation, icon: 'img_text', class: () => Promise.resolve().then(function () { return TAnnotation3DPainter$1; }).then(h => h.TAnnotation3DPainter) }, + { name: /^TH1/, icon: 'img_histo1d', class: () => Promise.resolve().then(function () { return TH1Painter$1; }).then(h => h.TH1Painter), opt: ';hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;C;B;B1;A;TEXT;LEGO;same', ctrl: 'l', expand_item: fFunctions, for_derived: true }, + { name: clTProfile, icon: 'img_profile', class: () => Promise.resolve().then(function () { return TH1Painter$1; }).then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist;projx;projxb;projxc=e;projxw', expand_item: fFunctions }, + { name: clTH2Poly, icon: 'img_histo2d', class: () => Promise.resolve().then(function () { return TH2Painter$1; }).then(h => h.TH2Painter), opt: ';COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same', expand_item: 'fBins', theonly: true }, + { name: 'TProfile2Poly', sameas: clTH2Poly }, + { name: 'TH2PolyBin', icon: 'img_histo2d', draw_field: 'fPoly', draw_field_opt: 'L' }, + { name: /^TH2/, icon: 'img_histo2d', class: () => Promise.resolve().then(function () { return TH2Painter$1; }).then(h => h.TH2Painter), opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;COL_POL;COL_ARR;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;PROJXY1;PROJXY2;PROJXY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;CHORD;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true }, + { name: clTProfile2D, sameas: clTH2, opt2: ';projxyb;projxyc=e;projxyw' }, + { name: /^TH3/, icon: 'img_histo3d', class: () => Promise.resolve().then(function () { return TH3Painter$1; }).then(h => h.TH3Painter), opt: ';SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL', expand_item: fFunctions, for_derived: true }, + { name: clTProfile3D, sameas: clTH3 }, + { name: clTHStack, icon: 'img_histo1d', class: () => Promise.resolve().then(function () { return THStackPainter$1; }).then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;COL;LEGO;E;PFC;PLC;PADS' }, + { name: clTPolyMarker3D, icon: 'img_histo3d', draw: () => Promise.resolve().then(function () { return draw3d; }).then(h => h.drawPolyMarker3D), direct: true, frame: '3d' }, + { name: clTPolyLine3D, icon: 'img_graph', draw: () => Promise.resolve().then(function () { return draw3d; }).then(h => h.drawPolyLine3D), direct: true, frame: '3d' }, + { name: 'TGraphStruct' }, + { name: 'TGraphNode' }, + { name: 'TGraphEdge' }, + { name: clTGraphTime, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphTimePainter$1; }).then(h => h.TGraphTimePainter), opt: 'once;repeat;first', theonly: true }, + { name: clTGraph2D, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraph2DPainter$1; }).then(h => h.TGraph2DPainter), opt: ';P;PCOL', theonly: true }, + { name: clTGraph2DErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, + { name: clTGraph2DAsymmErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, + { name: clTGraphPolargram, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPolarPainter$1; }).then(h => h.TGraphPolargramPainter), theonly: true }, + { name: clTGraphPolar, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPolarPainter$1; }).then(h => h.TGraphPolarPainter), opt: ';F;L;P;PE', theonly: true }, + { name: /^TGraph/, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGraphPainter$2; }).then(h => h.TGraphPainter), opt: ';L;P' }, + { name: 'TEfficiency', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TEfficiencyPainter$1; }).then(h => h.TEfficiencyPainter), opt: ';AP;A4P;B' }, + { name: clTCutG, sameas: clTGraph }, + { name: /^RooHist/, sameas: clTGraph }, + { name: /^RooCurve/, sameas: clTGraph }, + { name: /^RooEllipse/, sameas: clTGraph }, + { name: 'TScatter', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TScatterPainter$1; }).then(h => h.TScatterPainter), opt: ';A' }, + { name: 'RooPlot', icon: 'img_canvas', draw: () => Promise.resolve().then(function () { return TGraphTimePainter$1; }).then(h => h.drawRooPlot) }, + { name: 'TRatioPlot', icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TRatioPlotPainter$1; }).then(h => h.TRatioPlotPainter), opt: '' }, + { name: clTMultiGraph, icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TMultiGraphPainter$1; }).then(h => h.TMultiGraphPainter), opt: ';ac;l;p;3d;pads', expand_item: 'fGraphs' }, + { name: clTStreamerInfoList, icon: 'img_question', draw: () => import_h().then(h => h.drawStreamerInfo) }, + { name: 'TWebPainting', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TWebPaintingPainter$1; }).then(h => h.TWebPaintingPainter) }, + { name: clTCanvasWebSnapshot, icon: 'img_canvas', draw: () => import_canvas().then(h => h.drawTPadSnapshot) }, + { name: 'TPadWebSnapshot', sameas: clTCanvasWebSnapshot }, + { name: 'kind:Text', icon: 'img_text', func: drawRawText }, + { name: clTObjString, icon: 'img_text', func: drawRawText }, + { name: clTF1, icon: 'img_tf1', class: () => Promise.resolve().then(function () { return TF1Painter$1; }).then(h => h.TF1Painter), opt: ';L;C;FC;FL' }, + { name: clTF12, sameas: clTF1 }, + { name: clTF2, icon: 'img_tf2', class: () => Promise.resolve().then(function () { return TF2Painter$1; }).then(h => h.TF2Painter), opt: ';BOX;ARR;SURF;SURF1;SURF2;SURF4;SURF6;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same' }, + { name: clTF3, icon: 'img_histo3d', class: () => Promise.resolve().then(function () { return TF3Painter$1; }).then(h => h.TF3Painter), opt: ';SURF' }, + { name: clTSpline3, icon: 'img_tf1', class: () => Promise.resolve().then(function () { return TSplinePainter$1; }).then(h => h.TSplinePainter) }, + { name: 'TSpline5', sameas: clTSpline3 }, + { name: clTEllipse, icon: 'img_graph', draw: () => import_more().then(h => h.drawEllipse), direct: true }, + { name: 'TArc', sameas: clTEllipse }, + { name: 'TCrown', sameas: clTEllipse }, + { name: 'TPie', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TPiePainter$1; }).then(h => h.TPiePainter), opt: ';3D' }, + { name: 'TPieSlice', icon: 'img_graph', dummy: true }, + { name: 'TExec', icon: 'img_graph', dummy: true }, + { name: clTLine, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TLinePainter$1; }).then(h => h.TLinePainter) }, + { name: 'TArrow', icon: 'img_graph', class: () => Promise.resolve().then(function () { return TArrowPainter$1; }).then(h => h.TArrowPainter) }, + { name: clTPolyLine, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TPolyLinePainter$1; }).then(h => h.TPolyLinePainter), opt: ';F' }, + { name: 'TCurlyLine', sameas: clTPolyLine }, + { name: 'TCurlyArc', sameas: clTPolyLine }, + { name: 'TParallelCoord', icon: 'img_graph', dummy: true }, + { name: clTGaxis, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TGaxisPainter$1; }).then(h => h.TGaxisPainter) }, + { name: clTBox, icon: 'img_graph', class: () => Promise.resolve().then(function () { return TBoxPainter$1; }).then(h => h.TBoxPainter), opt: ';L' }, + { name: 'TWbox', sameas: clTBox }, + { name: 'TSliderBox', sameas: clTBox }, + { name: clTMarker, icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true }, + { name: 'TPolyMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyMarker), direct: true }, + { name: 'TASImage', icon: 'img_mgraph', class: () => Promise.resolve().then(function () { return TASImagePainter$1; }).then(h => h.TASImagePainter), opt: ';z' }, + { name: 'TJSImage', icon: 'img_mgraph', draw: () => import_more().then(h => h.drawJSImage), opt: ';scale;center' }, + { name: clTGeoVolume, icon: 'img_histo3d', class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;count;projx;projz;wire;no_screen;dflt', ctrl: 'dflt' }, + { name: 'TEveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, + { name: nsREX + 'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, + { name: 'TGeoOverlap', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt', dflt: 'dflt', ctrl: 'expand' }, + { name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', pm: true, ctrl: 'dflt', noappend: true, expand_after_draw: true }, + { name: 'TGeoVolumeAssembly', sameas: clTGeoVolume, /* icon: 'img_geoassembly', */ opt: ';more;all;count' }, + { name: /^TGeo/, class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;axis;compa;count;projx;projz;wire;no_screen;dflt', dflt: 'dflt', ctrl: 'expand' }, + { name: 'TAxis3D', icon: 'img_graph', draw: () => import_geo().then(h => h.drawAxis3D), direct: true }, + // these are not draw functions, but provide extra info about correspondent classes + { name: 'kind:Command', icon: 'img_execute', execute: true }, + { name: 'TFolder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true, get_expand: () => import_h().then(h => h.folderHierarchy) }, + { name: 'TTask', icon: 'img_task', get_expand: () => import_h().then(h => h.taskHierarchy), for_derived: true }, + { name: clTTree, icon: 'img_tree', get_expand: () => Promise.resolve().then(function () { return tree; }).then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect, pm: true, transform: true }, + { name: 'TNtuple', sameas: clTTree }, + { name: 'TNtupleD', sameas: clTTree }, + { name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump', noinspect: true, transform: true }, + { name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, + { name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump', ctrl: 'dump', ignore_online: true, always_draw: true, transform: true }, + { name: 'ROOT::RNTuple', icon: 'img_tree', get_expand: () => Promise.resolve().then(function () { return rntuple; }).then(h => h.tupleHierarchy), draw: () => Promise.resolve().then(function () { return RNTuple; }).then(h => h.drawRNTuple), dflt: 'expand', pm: true, transform: true }, + { name: 'ROOT::RNTupleField', icon: 'img_leaf', draw: () => Promise.resolve().then(function () { return RNTuple; }).then(h => h.drawRNTuple), opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, + { name: clTList, icon: 'img_list', draw: () => import_h().then(h => h.drawList), get_expand: () => import_h().then(h => h.listHierarchy), dflt: 'expand' }, + { name: clTHashList, sameas: clTList }, + { name: clTObjArray, sameas: clTList }, + { name: clTClonesArray, sameas: clTList }, + { name: clTMap, sameas: clTList }, + { name: clTColor, icon: 'img_color' }, + { name: clTFile, icon: 'img_file', noinspect: true, pm: true }, + { name: 'TMemFile', icon: 'img_file', noinspect: true, pm: true }, + { name: clTStyle, icon: 'img_question', noexpand: true }, + { name: 'Session', icon: 'img_globe' }, + { name: 'kind:TopFolder', icon: 'img_base' }, + { name: 'kind:Folder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true }, + { name: nsREX + 'RCanvas', icon: 'img_canvas', class: () => import_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, + { name: nsREX + 'RCanvasDisplayItem', icon: 'img_canvas', draw: () => import_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, + { name: nsREX + 'RText', icon: 'img_text', draw: () => import_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, + { name: nsREX + 'RFrameTitle', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, + { name: nsREX + 'RPaletteDrawable', icon: 'img_text', class: () => import_v7('more').then(h => h.RPalettePainter), opt: '' }, + { name: nsREX + 'RLine', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, + { name: nsREX + 'RBox', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, + { name: nsREX + 'RMarker', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, + { name: nsREX + 'RPave', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPavePainter), opt: '' }, + { name: nsREX + 'RLegend', icon: 'img_graph', class: () => import_v7('pave').then(h => h.RLegendPainter), opt: '' }, + { name: nsREX + 'RPaveText', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, + { name: nsREX + 'RFrame', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRFrame), opt: '' }, + { name: nsREX + 'RFont', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, + { name: nsREX + 'RAxisDrawable', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRAxis), opt: '' }, + { name: nsREX + 'RTreeMapPainter', class: () => Promise.resolve().then(function () { return RTreeMapPainter$1; }).then(h => h.RTreeMapPainter), opt: '' } +], cache: {} }; -var _rollup_plugin_ignore_empty_module_placeholder = {}; -var _rollup_plugin_ignore_empty_module_placeholder$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -default: _rollup_plugin_ignore_empty_module_placeholder -}); +/** @summary Register draw function for the class + * @param {object} args - arguments + * @param {string|regexp} args.name - class name or regexp pattern or '*' + * @param {function} [args.func] - draw function + * @param {string} [args.sameas] - let behave same as specified class + * @param {function} [args.draw] - async function to load draw function + * @param {function} [args.class] - async function to load painter class with static draw function + * @param {boolean} [args.direct] - if true, function is just Redraw() method of ObjectPainter + * @param {string} [args.opt] - list of supported draw options (separated with semicolon) like 'col;scat;' + * @param {string} [args.icon] - icon name shown for the class in hierarchy browser + * @param {string} [args.draw_field] - draw only data member from object, like fHistogram + * @param {string} [args.noinspect] - disable inspect + * @param {string} [args.noexpand] - disable expand + * @param {string} [args.pm] - always show plus or minus sign even when no child items exists + * @desc List of supported draw options could be provided, separated with ';' + * If args.name parameter is '*', function will be invoked before object drawing. + * If such function does not return value - normal drawing will be continued. + * @protected */ +function addDrawFunc(args) { + if (args?.name === '*') + internals._alt_draw = isFunc(args.func) ? args.func : null; + else + drawFuncs.lst.push(args); + return args; +} -/** @summary Draw TText +/** @summary return draw handle for specified item kind + * @desc kind could be ROOT.TH1I for ROOT classes or just + * kind string like 'Command' or 'Text' + * selector can be used to search for draw handle with specified option (string) + * or just sequence id * @private */ -async function drawText$1() { - const text = this.getObject(), - pp = this.getPadPainter(), - w = pp.getPadWidth(), - h = pp.getPadHeight(), - fp = this.getFramePainter(); - let pos_x = text.fX, pos_y = text.fY, - fact = 1, - annot = this.matchObjectType(clTAnnotation); - - this.createAttText({ attr: text }); - - if (annot && fp?.mode3d && isFunc(fp?.convert3DtoPadNDC)) { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ); - pos_x = pos.x; - pos_y = pos.y; - this.isndc = true; - annot = '3d'; - } else if (text.TestBit(BIT(14))) { - // NDC coordinates - this.isndc = true; - } else if (pp.getRootPad(true)) ; else { - // place in the middle - this.isndc = true; - pos_x = pos_y = 0.5; - text.fTextAlign = 22; - } +function getDrawHandle(kind, selector) { + if (!isStr(kind)) + return null; + if (selector === '') + selector = null; + + let first = null; - this.createG(); + if ((selector === null) && (kind in drawFuncs.cache)) + return drawFuncs.cache[kind]; - this.draw_g.attr('transform', null); // remove transofrm from interactive changes + const search = getTypeForKind(kind) || `kind:${kind}`; + let counter = 0; + for (let i = 0; i < drawFuncs.lst.length; ++i) { + const h = drawFuncs.lst[i]; + if (isStr(h.name)) { + if (h.name !== search) + continue; + } else if (!search.match(h.name)) + continue; - this.pos_x = this.axisToSvg('x', pos_x, this.isndc); - this.pos_y = this.axisToSvg('y', pos_y, this.isndc); + if (h.sameas) { + const hs = getDrawHandle(getKindForType(h.sameas), selector); + if (hs) { + for (const key in hs) { + if (h[key] === undefined) + h[key] = hs[key]; + } + delete h.sameas; + } + return h; + } - const arg = this.textatt.createArg({ x: this.pos_x, y: this.pos_y, text: text.fTitle, latex: 0 }); + if ((selector === null) || (selector === undefined)) { + // store found handle in cache, can reuse later + if (!(kind in drawFuncs.cache)) + drawFuncs.cache[kind] = h; + return h; + } else if (isStr(selector)) { + if (!first) + first = h; + // if draw option specified, check it present in the list - if ((text._typename === clTLatex) || annot) { - arg.latex = 1; - fact = 0.9; - } else if (text._typename === clTMathText) { - arg.latex = 2; - fact = 0.8; + if (selector === '::expand') { + if (('expand' in h) || ('expand_item' in h)) + return h; + } else if ('opt' in h) { + const opts = h.opt.split(';'); + for (let j = 0; j < opts.length; ++j) { + if (opts[j].toLowerCase() === selector.toLowerCase()) + return h; + } + } + } else if (selector === counter) + return h; + ++counter; } - this.startTextDrawing(this.textatt.font, this.textatt.getSize(w, h, fact, 0.05)); + return first; +} - this.drawText(arg); +/** @summary Returns true if handle can be potentially drawn + * @private */ +function canDrawHandle(h) { + if (isStr(h)) + h = getDrawHandle(h); + if (!isObject(h)) + return false; + return (h.func || h.class || h.draw || h.draw_field || h.opt === 'inspect'); +} - return this.finishTextDrawing().then(() => { - if (this.isBatchMode()) return this; +/** @summary Provide draw settings for specified class or kind + * @private */ +function getDrawSettings(kind, selector) { + const res = { opts: null, inspect: false, expand: false, draw: false, handle: null }; + if (!isStr(kind)) + return res; + let isany = false, noinspect = false, canexpand = false; + if (!isStr(selector)) + selector = ''; - this.pos_dx = this.pos_dy = 0; + for (let cnt = 0; cnt < 1000; ++cnt) { + const h = getDrawHandle(kind, cnt); + if (!h) + break; + if (!res.handle) + res.handle = h; + if (h.noinspect) + noinspect = true; + if (h.noappend) + res.noappend = true; + if (h.expand || h.get_expand || h.expand_item || h.can_expand) + canexpand = true; + if (!h.func && !h.class && !h.draw) + break; + isany = true; + if (h.opt === undefined) + continue; + let opt = h.opt; + if (isStr(h.opt2)) + opt += h.opt2; + const opts = opt.split(';'); + for (let i = 0; i < opts.length; ++i) { + opts[i] = opts[i].toLowerCase(); + if (opts[i].indexOf('same') === 0) { + res.has_same = true; + if (selector.indexOf('nosame') >= 0) + continue; + } - if (!this.moveDrag) { - this.moveDrag = function(dx, dy) { - this.pos_dx += dx; - this.pos_dy += dy; - makeTranslate(this.draw_g, this.pos_dx, this.pos_dy); - }; + if (res.opts === null) + res.opts = []; + if (res.opts.indexOf(opts[i]) < 0) + res.opts.push(opts[i]); } + if (h.theonly) + break; + } - if (!this.moveEnd) { - this.moveEnd = function(not_changed) { - if (not_changed) return; - const text = this.getObject(); - text.fX = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc); - text.fY = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc); - this.submitCanvExec(`SetX(${text.fX});;SetY(${text.fY});;`); - }; - } + if (selector.indexOf('noinspect') >= 0) + noinspect = true; - if (annot !== '3d') - addMoveHandler(this); - else { - fp.processRender3D = true; - this.handleRender3D = () => { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ), - new_x = this.axisToSvg('x', pos.x, true), - new_y = this.axisToSvg('y', pos.y, true); - makeTranslate(this.draw_g, new_x - this.pos_x, new_y - this.pos_y); - }; - } + if (isany && (res.opts === null)) + res.opts = ['']; - assignContextMenu(this, kToFront); + // if no any handle found, let inspect ROOT-based objects + if (!isany && getTypeForKind(kind) && !noinspect) + res.opts = []; - return this; - }); + if (!noinspect && res.opts) + res.opts.push(kInspect); + + res.inspect = !noinspect; + res.expand = canexpand; + res.draw = Boolean(res.opts); + + return res; } +/** @summary Set default draw option for provided class + * @example + import { setDefaultDrawOpt } from 'https://root.cern/js/latest/modules/draw.mjs'; + setDefaultDrawOpt('TH1', 'text'); + setDefaultDrawOpt('TH2', 'col'); */ +function setDefaultDrawOpt(classname, opt) { + if (!classname) + return; + if ((opt === undefined) && isStr(classname) && (classname.indexOf(':') > 0)) { + // special usage to set list of options like TH2:lego2;TH3:glbox2 + opt.split(';').forEach(part => { + const arr = part.split(':'); + if (arr.length >= 1) + setDefaultDrawOpt(arr[0], arr[1] || ''); + }); + } else { + const handle = getDrawHandle(getKindForType(classname), 0); + if (handle) + handle.dflt = opt; + } +} -/** @summary Draw TPolyLine - * @private */ -function drawPolyLine() { - this.createG(); +/** @summary Draw object in specified HTML element with given draw options. + * @param {string|object} dom - id of div element to draw or directly DOMElement + * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} + * @param {string} opt - draw options separated by space, comma or semicolon + * @return {Promise} with painter object + * @public + * @desc An extensive list of support draw options can be found on [examples page]{@link https://root.cern/js/latest/examples.htm} + * @example + * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; + * import { draw } from 'https://root.cern/js/latest/modules/draw.mjs'; + * let file = await openFile('https://root.cern/js/files/hsimple.root'); + * let obj = await file.readObject('hpxpy;1'); + * await draw('drawing', obj, 'colz;logx;gridx;gridy'); */ +async function draw(dom, obj, opt) { + if (!isObject(obj)) + return Promise.reject(Error('not an object in draw call')); - const polyline = this.getObject(), - kPolyLineNDC = BIT(14), - isndc = polyline.TestBit(kPolyLineNDC), - opt = this.getDrawOpt() || polyline.fOption, - dofill = (polyline._typename === clTPolyLine) && ((opt === 'f') || (opt === 'F')), - func = this.getAxisToSvgFunc(isndc); + if (isStr(opt) && (opt.indexOf(kInspect) === 0)) + return import_h().then(h => h.drawInspector(dom, obj, opt)); - this.createAttLine({ attr: polyline }); - this.createAttFill({ attr: polyline }); + let handle, type_info; + if ('_typename' in obj) { + type_info = 'type ' + obj._typename; + handle = getDrawHandle(getKindForType(obj._typename), opt); + } else if ('_kind' in obj) { + type_info = 'kind ' + obj._kind; + handle = getDrawHandle(obj._kind, opt); + } else + return import_h().then(h => h.drawInspector(dom, obj, opt)); - let cmd = ''; - for (let n = 0; n <= polyline.fLastPoint; ++n) - cmd += `${n>0?'L':'M'}${func.x(polyline.fX[n])},${func.y(polyline.fY[n])}`; + // this is case of unsupported class, close it normally + if (!handle) + return Promise.reject(Error(`Object of ${type_info} cannot be shown with draw`)); - if (dofill) - cmd += 'Z'; + if (handle.dummy) + return null; - const elem = this.draw_g.append('svg:path').attr('d', cmd); + if (handle.draw_field && obj[handle.draw_field]) + return draw(dom, obj[handle.draw_field], opt || handle.draw_field_opt); - if (dofill) - elem.call(this.fillatt.func); - else - elem.call(this.lineatt.func).style('fill', 'none'); + if (internals._alt_draw && !handle.transform) { + const v = internals._alt_draw(dom, obj, opt); + if (v) + return v; + } - assignContextMenu(this, kToFront); + if (!canDrawHandle(handle)) { + if (opt && (opt.indexOf('same') >= 0)) { + const main_painter = getElementMainPainter(dom); - addMoveHandler(this); + if (isFunc(main_painter?.performDrop)) + return main_painter.performDrop(obj, '', null, opt); + } - this.dx = this.dy = 0; - this.isndc = isndc; + return Promise.reject(Error(`Function not specified to draw object ${type_info}`)); + } - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); - }; + function performDraw() { + let promise, painter; + if (handle.direct === 'v7') { + promise = Promise.resolve().then(function () { return RCanvasPainter$1; }).then(v7h => { + painter = new v7h.RObjectPainter(dom, obj, opt, handle.csstype); + painter.redraw = handle.func; + return v7h.ensureRCanvas(painter, handle.frame || false); + }).then(() => painter.redraw()); + } else if (handle.direct) { + painter = new ObjectPainter(dom, obj, opt); + painter.redraw = handle.func; + promise = import_canvas().then(v6h => v6h.ensureTCanvas(painter, handle.frame || false)) + .then(() => painter.redraw()); + } else + promise = getPromise(handle.func(dom, obj, opt)); - this.moveEnd = function(not_changed) { - if (not_changed) return; - const polyline = this.getObject(), - func = this.getAxisToSvgFunc(this.isndc); - let exec = ''; + return promise.then(p => { + if (!painter) + painter = p; + if (painter === false) + return null; + if (!painter) + throw Error(`Fail to draw object ${type_info}`); + if (isObject(painter) && !painter.options) + painter.options = { original: opt || '' }; // keep original draw options + return painter; + }); + } - for (let n = 0; n <= polyline.fLastPoint; ++n) { - const x = this.svgToAxis('x', func.x(polyline.fX[n]) + this.dx, this.isndc), - y = this.svgToAxis('y', func.y(polyline.fY[n]) + this.dy, this.isndc); - polyline.fX[n] = x; - polyline.fY[n] = y; - exec += `SetPoint(${n},${x},${y});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - this.redraw(); - }; -} + if (isFunc(handle.func)) + return performDraw(); -/** @summary Draw TEllipse - * @private */ -function drawEllipse() { - const ellipse = this.getObject(), - closed_ellipse = (ellipse.fPhimin === 0) && (ellipse.fPhimax === 360), - is_crown = (ellipse._typename === 'TCrown'); + let promise; - this.createAttLine({ attr: ellipse }); - this.createAttFill({ attr: ellipse }); + if (isFunc(handle.class)) { + // class coded as async function which returns class handle + // simple extract class and access class.draw method + promise = handle.class().then(cl => { handle.func = cl.draw; }); + } else if (isFunc(handle.draw)) { + // draw function without special class + promise = handle.draw().then(h => { handle.func = h; }); + } else if (!handle.func || !isStr(handle.func)) + return Promise.reject(Error(`Draw function or class not specified to draw ${type_info}`)); + else { + let func = findFunction(handle.func); + if (isFunc(func)) { + handle.func = func; + return performDraw(); + } + let modules = null; + if (isStr(handle.script)) { + if (handle.script.indexOf('modules:') === 0) + modules = handle.script.slice(8); + else if (handle.script.indexOf('.mjs') > 0) + modules = handle.script; + } + + if (!modules && !handle.prereq && !handle.script) + return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); + + let init_promise = Promise.resolve(true); + if (modules) + init_promise = loadModules(modules); + else if (!internals.ignore_v6) { + init_promise = exports._ensureJSROOT().then(v6 => { + const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true); + return pr.then(() => { + if (handle.script) + return loadScript(handle.script); + }).then(() => v6._complete_loading()); + }); + } - this.createG(); + promise = init_promise.then(() => { + func = findFunction(handle.func); + if (!isFunc(func)) + return Promise.reject(Error(`Fail to find function ${handle.func} after loading ${handle.prereq || handle.script}`)); - const funcs = this.getAxisToSvgFunc(), - x = funcs.x(ellipse.fX1), - y = funcs.y(ellipse.fY1), - rx = is_crown && (ellipse.fR1 <= 0) ? (funcs.x(ellipse.fX1 + ellipse.fR2) - x) : (funcs.x(ellipse.fX1 + ellipse.fR1) - x), - ry = y - funcs.y(ellipse.fY1 + ellipse.fR2); + handle.func = func; + }); + } - let path = ''; + return promise.then(() => performDraw()); +} - if (is_crown && (ellipse.fR1 > 0)) { - const rx1 = rx, ry2 = ry, - ry1 = y - funcs.y(ellipse.fY1 + ellipse.fR1), - rx2 = funcs.x(ellipse.fX1 + ellipse.fR2) - x; +/** @summary Redraw object in specified HTML element with given draw options. + * @param {string|object} dom - id of div element to draw or directly DOMElement + * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} + * @param {string} opt - draw options + * @return {Promise} with painter object + * @desc If drawing was not done before, it will be performed with {@link draw}. + * Otherwise drawing content will be updated + * @public + * @example + * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; + * import { draw, redraw } from 'https://root.cern/js/latest/modules/draw.mjs'; + * let file = await openFile('https://root.cern/js/files/hsimple.root'); + * let obj = await file.readObject('hpxpy;1'); + * await draw('drawing', obj, 'colz'); + * let cnt = 0; + * setInterval(() => { + * obj.fTitle = `Next iteration ${cnt++}`; + * redraw('drawing', obj, 'colz'); + * }, 1000); */ +async function redraw(dom, obj, opt) { + if (!isObject(obj)) + return Promise.reject(Error('not an object in redraw')); - if (closed_ellipse) { - path = `M${-rx1},0A${rx1},${ry1},0,1,0,${rx1},0A${rx1},${ry1},0,1,0,${-rx1},0` + - `M${-rx2},0A${rx2},${ry2},0,1,0,${rx2},0A${rx2},${ry2},0,1,0,${-rx2},0`; - } else { - const large_arc = (ellipse.fPhimax-ellipse.fPhimin>=180) ? 1 : 0, - a1 = ellipse.fPhimin*Math.PI/180, a2 = ellipse.fPhimax*Math.PI/180, - dx1 = Math.round(rx1*Math.cos(a1)), dy1 = Math.round(ry1*Math.sin(a1)), - dx2 = Math.round(rx1*Math.cos(a2)), dy2 = Math.round(ry1*Math.sin(a2)), - dx3 = Math.round(rx2*Math.cos(a1)), dy3 = Math.round(ry2*Math.sin(a1)), - dx4 = Math.round(rx2*Math.cos(a2)), dy4 = Math.round(ry2*Math.sin(a2)); + const can_painter = getElementCanvPainter(dom); + let handle, res_painter = null, redraw_res; + if (obj._typename) + handle = getDrawHandle(getKindForType(obj._typename)); + if (handle?.draw_field && obj[handle.draw_field]) + obj = obj[handle.draw_field]; - path = `M${dx2},${dy2}A${rx1},${ry1},0,${large_arc},0,${dx1},${dy1}` + - `L${dx3},${dy3}A${rx2},${ry2},0,${large_arc},1,${dx4},${dy4}Z`; - } - } else if (ellipse.fTheta === 0) { - if (closed_ellipse) - path = `M${-rx},0A${rx},${ry},0,1,0,${rx},0A${rx},${ry},0,1,0,${-rx},0Z`; - else { - const x1 = Math.round(rx * Math.cos(ellipse.fPhimin*Math.PI/180)), - y1 = Math.round(ry * Math.sin(ellipse.fPhimin*Math.PI/180)), - x2 = Math.round(rx * Math.cos(ellipse.fPhimax*Math.PI/180)), - y2 = Math.round(ry * Math.sin(ellipse.fPhimax*Math.PI/180)); - path = `M0,0L${x1},${y1}A${rx},${ry},0,1,1,${x2},${y2}Z`; + if (can_painter) { + if (can_painter.matchObjectType(obj._typename)) { + redraw_res = can_painter.redrawObject(obj, opt); + if (redraw_res) + res_painter = can_painter; + } else { + can_painter.forEachPainterInPad(painter => { + if (!res_painter && painter.matchObjectType(obj._typename)) { + redraw_res = painter.redrawObject(obj, opt); + if (redraw_res) + res_painter = painter; + } + }, 'objects'); } } else { - const ct = Math.cos(ellipse.fTheta*Math.PI/180), - st = Math.sin(ellipse.fTheta*Math.PI/180), - phi1 = ellipse.fPhimin*Math.PI/180, - phi2 = ellipse.fPhimax*Math.PI/180, - np = 200, - dphi = (phi2-phi1) / (np - (closed_ellipse ? 0 : 1)); - let lastx = 0, lasty = 0; - if (!closed_ellipse) path = 'M0,0'; - for (let n = 0; n < np; ++n) { - const angle = phi1 + n*dphi, - dx = ellipse.fR1 * Math.cos(angle), - dy = ellipse.fR2 * Math.sin(angle), - px = funcs.x(ellipse.fX1 + dx*ct - dy*st) - x, - py = funcs.y(ellipse.fY1 + dx*st + dy*ct) - y; - if (!path) - path = `M${px},${py}`; - else if (lastx === px) - path += `v${py-lasty}`; - else if (lasty === py) - path += `h${px-lastx}`; - else - path += `l${px-lastx},${py-lasty}`; - lastx = px; lasty = py; - } - path += 'Z'; + const top = new BasePainter(dom).getTopPainter(); + // base painter do not have this method, if it there use it + // it can be object painter here or can be specially introduce method to handling redraw! + if (isFunc(top?.redrawObject)) { + redraw_res = top.redrawObject(obj, opt); + if (redraw_res) + res_painter = top; + } } - this.x = x; - this.y = y; - - makeTranslate(this.draw_g.append('svg:path'), x, y) - .attr('d', path) - .call(this.lineatt.func) - .call(this.fillatt.func); - - assignContextMenu(this, kToFront); - - addMoveHandler(this); + if (res_painter) + return getPromise(redraw_res).then(() => res_painter); - this.moveDrag = function(dx, dy) { - this.x += dx; - this.y += dy; - makeTranslate(this.draw_g.select('path'), this.x, this.y); - }; + cleanup(dom); - this.moveEnd = function(not_changed) { - if (not_changed) return; - const ellipse = this.getObject(); - ellipse.fX1 = this.svgToAxis('x', this.x); - ellipse.fY1 = this.svgToAxis('y', this.y); - this.submitCanvExec(`SetX1(${ellipse.fX1});;SetY1(${ellipse.fY1});;Notify();;`); - }; + return draw(dom, obj, opt); } -/** @summary Draw TPie - * @private */ -function drawPie() { - this.createG(); - - const pie = this.getObject(), - nb = pie.fPieSlices.length, - xc = this.axisToSvg('x', pie.fX), - yc = this.axisToSvg('y', pie.fY), - rx = this.axisToSvg('x', pie.fX + pie.fRadius) - xc, - ry = this.axisToSvg('y', pie.fY + pie.fRadius) - yc; - - makeTranslate(this.draw_g, xc, yc); - - // Draw the slices - let total = 0, - af = (pie.fAngularOffset*Math.PI)/180, - x1 = Math.round(rx*Math.cos(af)), - y1 = Math.round(ry*Math.sin(af)); +/** @summary Create three.js model for object + * @param {object} obj - object + * @param {string} opt - draw options + * @return {Promise} with three.js model */ - for (let n = 0; n < nb; n++) - total += pie.fPieSlices[n].fValue; +async function build3d(obj, opt) { + if (!isObject(obj) || !obj?._typename) + return Promise.reject(Error('not an object in build3d')); - for (let n = 0; n < nb; n++) { - const slice = pie.fPieSlices[n]; + const handle = getDrawHandle(getKindForType(obj._typename)); + if (!handle?.class && !handle.build3d) + return Promise.reject(Error(`not able to create three.js for ${obj._typename}`)); - this.createAttLine({ attr: slice }); - this.createAttFill({ attr: slice }); + if (handle.build3d) + return handle.build3d().then(func => func(obj, opt)); - af += slice.fValue/total*2*Math.PI; - const x2 = Math.round(rx*Math.cos(af)), - y2 = Math.round(ry*Math.sin(af)); + return handle.class().then(cl => { + if (!isFunc(cl?.build3d)) + return Promise.reject(Error(`painter class for ${obj._typename} does not implement build3d method`)); - this.draw_g - .append('svg:path') - .attr('d', `M0,0L${x1},${y1}A${rx},${ry},0,0,0,${x2},${y2}z`) - .call(this.lineatt.func) - .call(this.fillatt.func); - x1 = x2; y1 = y2; - } + return cl.build3d(obj, opt); + }); } -/** @summary Draw TBox +/** @summary Scan streamer infos for derived classes + * @desc Assign draw functions for such derived classes * @private */ -function drawBox$1() { - const box = this.getObject(), - opt = this.getDrawOpt(), - draw_line = (opt.toUpperCase().indexOf('L') >= 0); - - this.createAttLine({ attr: box }); - this.createAttFill({ attr: box }); - - // if box filled, contour line drawn only with 'L' draw option: - if (!this.fillatt.empty() && !draw_line) - this.lineatt.color = 'none'; - - this.createG(); - - this.x1 = this.axisToSvg('x', box.fX1); - this.x2 = this.axisToSvg('x', box.fX2); - this.y1 = this.axisToSvg('y', box.fY1); - this.y2 = this.axisToSvg('y', box.fY2); - this.borderMode = (box.fBorderMode && box.fBorderSize && this.fillatt.hasColor()) ? box.fBorderMode : 0; - this.borderSize = box.fBorderSize; - - this.getPathes = () => { - const xx = Math.min(this.x1, this.x2), yy = Math.min(this.y1, this.y2), - ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2), - path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`; - if (!this.borderMode) - return [path]; - const pww = this.borderSize, phh = this.borderSize, - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; +function addStreamerInfosForPainter(lst) { + if (!lst) + return; - return (this.borderMode > 0) ? [path, side1, side2] : [path, side2, side1]; - }; + const basics = [clTObject, clTNamed, clTString, 'TCollection', clTAttLine, clTAttFill, clTAttMarker, clTAttText]; - const paths = this.getPathes(); + function checkBaseClasses(si, lvl) { + const element = si.fElements?.arr[0]; + if ((element?.fTypeName !== kBaseClass) || (lvl > 4)) + return null; + // exclude very basic classes + if (basics.indexOf(element.fName) >= 0) + return null; - this.draw_g - .append('svg:path') - .attr('d', paths[0]) - .call(this.lineatt.func) - .call(this.fillatt.func); + let handle = getDrawHandle(getKindForType(element.fName)); + if (handle && !handle.for_derived) + handle = null; - if (this.borderMode) { - this.draw_g.append('svg:path') - .attr('d', paths[1]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); + // now try find that base class of base in the list + if (handle === null) { + for (let k = 0; k < lst.arr.length; ++k) { + if (lst.arr[k].fName === element.fName) { + handle = checkBaseClasses(lst.arr[k], lvl + 1); + break; + } + } + } - this.draw_g.append('svg:path') - .attr('d', paths[2]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatHex()); + return handle?.for_derived ? handle : null; } - assignContextMenu(this, kToFront); + lst.arr.forEach(si => { + if (getDrawHandle(getKindForType(si.fName)) !== null) + return; - addMoveHandler(this); + const handle = checkBaseClasses(si, 0); + if (handle) { + const newhandle = Object.assign({}, handle); + delete newhandle.for_derived; // should we disable? + newhandle.name = si.fName; + addDrawFunc(newhandle); + } + }); +} - this.moveStart = function(x, y) { - const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2); +/** @summary Create SVG/PNG/JPEG image for provided object. + * @desc Function especially useful in Node.js environment to generate images for + * supported ROOT classes, but also can be used from web browser + * @param {object} args - function settings + * @param {object} args.object - object for the drawing + * @param {string} [args.format = 'svg'] - image format like 'svg' (default), 'png' or 'jpeg' + * @param {string} [args.option = ''] - draw options + * @param {number} [args.width = 1200] - image width + * @param {number} [args.height = 800] - image height + * @param {boolean} [args.as_buffer = false] - returns image as Buffer instance, can store directly to file + * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object + * @return {Promise} with image code - svg as is, png/jpeg as base64 string or buffer (if as_buffer) specified + * @example + * // how makeImage can be used in node.js + * import { openFile, makeImage } from 'jsroot'; + * let file = await openFile('https://root.cern/js/files/hsimple.root'); + * let object = await file.readObject('hpxpy;1'); + * let png64 = await makeImage({ format: 'png', object, option: 'colz', width: 1200, height: 800 }); + * let pngbuf = await makeImage({ format: 'png', as_buffer: true, object, option: 'colz', width: 1200, height: 800 }); */ +async function makeImage(args) { + if (!args) + args = {}; - this.c_x1 = Math.abs(x - this.x2) > ww*0.1; - this.c_x2 = Math.abs(x - this.x1) > ww*0.1; - this.c_y1 = Math.abs(y - this.y2) > hh*0.1; - this.c_y2 = Math.abs(y - this.y1) > hh*0.1; - if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2) - this.c_y1 = this.c_y2 = false; - if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2) - this.c_x1 = this.c_x2 = false; - }; + if (!isObject(args.object)) + return Promise.reject(Error('No object specified to generate SVG')); + if (!args.format) + args.format = 'svg'; + if (!args.width) + args.width = settings.CanvasWidth; + if (!args.height) + args.height = settings.CanvasHeight; - this.moveDrag = function(dx, dy) { - if (this.c_x1) this.x1 += dx; - if (this.c_x2) this.x2 += dx; - if (this.c_y1) this.y1 += dy; - if (this.c_y2) this.y2 += dy; + async function build(main) { + main.attr('width', args.width).attr('height', args.height) + .style('width', args.width + 'px').style('height', args.height + 'px') + .property('_batch_use_canvsize', args.use_canvas_size ?? false) + .property('_batch_mode', true) + .property('_batch_format', args.format !== 'svg' ? args.format : null); - const nodes = this.draw_g.selectAll('path').nodes(), - pathes = this.getPathes(); + function complete(res) { + cleanup(main.node()); + main.remove(); + return res; + } - pathes.forEach((path, i) => select(nodes[i]).attr('d', path)); - }; + return draw(main.node(), args.object, args.option || '').then(() => { + if (args.format !== 'svg') { + const only_img = main.select('svg').selectChild('image'); + if (!only_img.empty()) { + const href = only_img.attr('href'); - this.moveEnd = function(not_changed) { - if (not_changed) return; - const box = this.getObject(); - let exec = ''; - if (this.c_x1) { box.fX1 = this.svgToAxis('x', this.x1); exec += `SetX1(${box.fX1});;`; } - if (this.c_x2) { box.fX2 = this.svgToAxis('x', this.x2); exec += `SetX2(${box.fX2});;`; } - if (this.c_y1) { box.fY1 = this.svgToAxis('y', this.y1); exec += `SetY1(${box.fY1});;`; } - if (this.c_y2) { box.fY2 = this.svgToAxis('y', this.y2); exec += `SetY2(${box.fY2});;`; } - this.submitCanvExec(exec + 'Notify();;'); - }; -} + if (args.as_buffer) { + const p = href.indexOf('base64,'), + str = atob_func(href.slice(p + 7)), + buf = new ArrayBuffer(str.length), + bufView = new Uint8Array(buf); + for (let i = 0; i < str.length; i++) + bufView[i] = str.charCodeAt(i); + return isNodeJs() ? Buffer.from(buf) : buf; + } + return href; + } + } -/** @summary Draw TMarker - * @private */ -function drawMarker$1() { - const marker = this.getObject(), - kMarkerNDC = BIT(14); + const mainsvg = main.select('svg'), + style_filter = mainsvg.style('filter'); - this.isndc = marker.TestBit(kMarkerNDC); + mainsvg.attr('xmlns', nsSVG) + .attr('style', null).attr('class', null).attr('x', null).attr('y', null); - this.createAttMarker({ attr: marker }); + if (!mainsvg.attr('width') && !mainsvg.attr('height')) + mainsvg.attr('width', args.width).attr('height', args.height); - this.createG(); + if (style_filter) + mainsvg.style('filter', style_filter); - const x = this.axisToSvg('x', marker.fX, this.isndc), - y = this.axisToSvg('y', marker.fY, this.isndc), - path = this.markeratt.create(x, y); + function clear_element() { + const elem = select(this); + if (elem.style('display') === 'none') + elem.remove(); + } - if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } + main.selectAll('g.root_frame').each(clear_element); + main.selectAll('svg').each(clear_element); - assignContextMenu(this, kToFront); + let svg; + if (args.format === 'pdf') + svg = { node: mainsvg.node(), width: args.width, height: args.height, can_modify: true }; + else { + svg = compressSVG(main.html()); + if (args.format === 'svg') + return complete(svg); + } - addMoveHandler(this); + return svgToImage(svg, args.format, args).then(complete); + }); + } - this.dx = this.dy = 0; + return isNodeJs() + ? _loadJSDOM().then(handle => build(handle.body.append('div'))) + : build(select('body').append('div').style('display', 'none')); +} - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); - }; - this.moveEnd = function(not_changed) { - if (not_changed) return; - const marker = this.getObject(); - marker.fX = this.svgToAxis('x', this.axisToSvg('x', marker.fX, this.isndc) + this.dx, this.isndc); - marker.fY = this.svgToAxis('y', this.axisToSvg('y', marker.fY, this.isndc) + this.dy, this.isndc); - this.submitCanvExec(`SetX(${marker.fX});;SetY(${marker.fY});;Notify();;`); - this.redraw(); - }; +/** @summary Create SVG image for provided object. + * @desc Function especially useful in Node.js environment to generate images for + * supported ROOT classes + * @param {object} args - function settings + * @param {object} args.object - object for the drawing + * @param {string} [args.option] - draw options + * @param {number} [args.width = 1200] - image width + * @param {number} [args.height = 800] - image height + * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object + * @return {Promise} with svg code + * @example + * // how makeSVG can be used in node.js + * import { openFile, makeSVG } from 'jsroot'; + * let file = await openFile('https://root.cern/js/files/hsimple.root'); + * let object = await file.readObject('hpxpy;1'); + * let svg = await makeSVG({ object, option: 'lego2,pal50', width: 1200, height: 800 }); */ +async function makeSVG(args) { + if (!args) + args = {}; + args.format = 'svg'; + return makeImage(args); } -/** @summary Draw TPolyMarker - * @private */ -function drawPolyMarker() { - const poly = this.getObject(), - func = this.getAxisToSvgFunc(); - - this.createAttMarker({ attr: poly }); +function assignPadPainterDraw(PadPainterClass) { + PadPainterClass.prototype.drawObject = draw; + PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings; +} - this.createG(); +// only now one can draw primitives in the canvas +assignPadPainterDraw(TPadPainter); - let path = ''; - for (let n = 0; n <= poly.fLastPoint; ++n) - path += this.markeratt.create(func.x(poly.fX[n]), func.y(poly.fY[n])); +import_geo = async function() { + return Promise.resolve().then(function () { return TGeoPainter$1; }).then(geo => { + const handle = getDrawHandle(getKindForType('TGeoVolumeAssembly')); + if (handle) + handle.icon = 'img_geoassembly'; + return geo; + }); +}; - if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } +// load v7 only on demand +import_v7 = async function(arg) { + return Promise.resolve().then(function () { return RCanvasPainter$1; }).then(h => { + // only now one can draw primitives in the canvas + assignPadPainterDraw(h.RPadPainter); + switch (arg) { + case 'more': return Promise.resolve().then(function () { return v7more; }); + case 'pave': return Promise.resolve().then(function () { return RPavePainter$1; }); + } + return h; + }); +}; - assignContextMenu(this, kToFront); +// to avoid cross-dependency between modules +Object.assign(internals, { addStreamerInfosForPainter, addDrawFunc, setDefaultDrawOpt, makePDF }); - addMoveHandler(this); +Object.assign(internals.jsroot, { draw, redraw, makeSVG, makeImage, addDrawFunc }); - this.dx = this.dy = 0; +const kTopFolder = 'TopFolder', kExpand = 'expand', kPM = 'plusminus', kDfltDrawOpt = '__default_draw_option__', + cssValueNum = 'h_value_num', cssButton = 'h_button', cssItem = 'h_item', cssTree = 'h_tree'; - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); - }; +function injectHStyle(node) { + function img(name, sz, fmt, code) { + return `.jsroot .img_${name} { display: inline-block; height: ${sz}px; width: ${sz}px; background-image: url("data:image/${fmt};base64,${code}"); }`; + } - this.moveEnd = function(not_changed) { - if (not_changed) return; - const poly = this.getObject(), - func = this.getAxisToSvgFunc(); + const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', + border_color = settings.DarkMode ? 'green' : 'black', + shadow_color = settings.DarkMode ? '#555' : '#aaa'; - let exec = ''; - for (let n = 0; n <= poly.fLastPoint; ++n) { - const x = this.svgToAxis('x', func.x(poly.fX[n]) + this.dx), - y = this.svgToAxis('y', func.y(poly.fY[n]) + this.dy); - poly.fX[n] = x; - poly.fY[n] = y; - exec += `SetPoint(${n},${x},${y});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - this.redraw(); - }; + injectStyle(` +.jsroot .${cssTree} { display: block; white-space: nowrap; } +.jsroot .${cssTree} * { padding: 0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; box-sizing: content-box; line-height: 14px } +.jsroot .${cssTree} img { border: 0px; vertical-align: middle; } +.jsroot .${cssTree} a { text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } +.jsroot .${cssTree} p { font-weight: bold; white-space: nowrap; text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } +.jsroot .h_value_str { color: green; } +.jsroot .${cssValueNum} { color: blue; } +.jsroot .h_line { height: 18px; display: block; } +.jsroot .${cssButton} { cursor: pointer; color: blue; text-decoration: underline; } +.jsroot .${cssItem} { cursor: pointer; user-select: none; } +.jsroot .${cssItem}:hover { text-decoration: underline; } +.jsroot .h_childs { overflow: hidden; display: block; } +.jsroot_fastcmd_btn { height: 32px; width: 32px; display: inline-block; margin: 2px; padding: 2px; background-position: left 2px top 2px; + background-repeat: no-repeat; background-size: 24px 24px; border-color: inherit; } +.jsroot_inspector { border: 1px solid ${border_color}; box-shadow: 1px 1px 2px 2px ${shadow_color}; opacity: 0.95; background-color: ${bkgr_color}; } +.jsroot_drag_area { background-color: #007fff; } +${img('minus', 18, 'gif', 'R0lGODlhEgASAJEDAIKCgoCAgAAAAP///yH5BAEAAAMALAAAAAASABIAAAInnD+By+2rnpyhWvsizE0zf4CIIpRlgiqaiDosa7zZdU22A9y6u98FADs=')} +${img('minusbottom', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAImlC+Ay+2rnpygWvsizE0zf4CIEpRlgiqaiDosa7zZdU32jed6XgAAOw==')} +${img('plus', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAIqlC+Ay+2rnpygWvsizCcczWieAW7BeSaqookfZ4yqU5LZdU06vfe8rysAADs=')} +${img('plusbottom', 18, 'gif', 'R0lGODlhEgASAJECAICAgAAAAP///wAAACH5BAEAAAIALAAAAAASABIAAAIplC+Ay+2rnpygWvsizCcczWieAW7BeSaqookfZ4yqU5LZdU36zvd+XwAAOw==')} +${img('empty', 18, 'gif', 'R0lGODlhEgASAJEAAAAAAP///4CAgP///yH5BAEAAAMALAAAAAASABIAAAIPnI+py+0Po5y02ouz3pwXADs=')} +${img('line', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIZjB+Ay+2rnpwo0uss3kfz7X1XKE5k+ZxoAQA7')} +${img('join', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIcjB+Ay+2rnpwo0uss3kf5BGocNJZiSZ2opK5BAQA7')} +${img('joinbottom', 18, 'gif', 'R0lGODlhEgASAIABAICAgP///yH5BAEAAAEALAAAAAASABIAAAIZjB+Ay+2rnpwo0uss3kf5BGrcSJbmiaZGAQA7')} +${img('base', 18, 'gif', 'R0lGODlhEwASAPcAAPv6/Pn4+mnN/4zf/764x2vO//Dv84HZ/5jl/0ZGmfTz9vLy8lHB/+zr70u+/7S03IODtd7d6c/P0ndqiq/w/4Pb/5SKo/Py9fPy9tTU121kjd/f4MzM062tx5+zy5rO67GwxNDM14d8mJzn/7awwry713zX/9bW27u71lFRmW5uoZ+fxjOy/zm1/9HQ2o3g/2xfgZeMplav7sn9/6Cgv37X/6Dp/3jU/2uJ2M7J1JC63vn5+v38/d7e38PD0Z7o/9LR4LS01cPDzPb1+Nzb5IJ2lHCEv5bk/53C3MrJ3X56t+np6YF7o3JsndTU5Wtgh5GHoKaesuLi4mrO/19RdnnV/4WBqF5QdWPK/4+PvW5uu4+PuuHh4q7w/97e68C9z63w/9PT0+zs7FtbmWVXerS0yaqitpuSqWVlpcL6/8jD0H/C9mVajqWu3nFwpYqHtFfE/42DnaWl0bTz/5OPt+7u7tra5Y+Yz+Tk56fM6Gek5pG50LGpvOHh72LJ/9XU5lbD/6GnwHpujfDu8mxpntzb45qav7PH41+n6JeXyUZGopyYsWeGyDu2/6LQ44re/1yV41TD/8LC1zix/sS/zdTU4Y+gsd/c5L7z+a6uzE+3+XG89L6+087O1sTD3K2twoGBtWVbgomo4P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKMALAAAAAATABIAAAjtAEcJFLgDTyE7SVCsAAJgoMNRYTII8fEpkAckOpiEaPhwlARLexxhmpEGzJEmBAJ0HMXhw6MfXeZQsDHADZ8hK13kMTEAwQgEL2oYiaJgJZFDU24cqHCgSgFGFgysBJAJkB8BBQRggQNJxKCVo0rIcMAgEgMHmnBMaADWEyIWLRptEqWETRG2K//ombSmjRZFoaCo4djRyZ0HchIlSECIRNGVXur0WcAlCJoUoOhcAltpyQIxPSRtGQPhjRkMKyN0krLhBCcaKrJoOCO1I48vi0CU6WDIyhNBKcEGyBEDBpUrZOJQugC2ufPnDwMCADs=')} +${img('folder', 18, 'gif', 'R0lGODlhEgASANUAAPv7++/v79u3UsyZNOTk5MHBwaNxC8KPKre3t55sBrqHIpxqBMmWMb2KJbOBG5lnAdu3cbWCHaBuCMuYM///urB+GMWSLad1D8eUL6ampqVzDbeEH6t5E8iVMMCNKMbGxq58FppoAqh2EKx6FP/Ub//4k+vr6///nP/bdf/kf//viba2tv//////mQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAC4ALAAAAAASABIAAAaRQJdwSCwaj8ik0jUYTBidAEA5YFkplANhehxABGAwpKHYRByVwHBibbvbo8+Q0TrZ7/jWBTHEtP6AgX8GK0MWLSWJiostEoVCBy0qk5SVLQmPLh4tKZ2eny0LmQ0tKKanqC0hmQotJK+wsS0PfEIBZxUgHCIaBhIJCw8ZBUMABAUrycrLBQREAAEm0tPUUktKQQA7')} +${img('folderopen', 18, 'gif', 'R0lGODlhEgASANUAAO/v76VzDfv7+8yZNMHBweTk5JpoAqBuCMuYM8mWMZ5sBpxqBPr7/Le3t///pcaaGvDker2KJc+iJqd1D7B+GOKzQ8KPKqJwCrOBG7WCHbeEH9e4QNq/bP/rhJlnAffwiaampuLBUMmgIf3VcKRyDP/XhLqHIqNxC8iVMMbGxqx6FP/kf//bdf/vievr67a2tv/4k8aaGf//nP//mf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAADUALAAAAAASABIAAAaVwJpwSCwaj8ikUjgYIBIogEA5oFkZDEtheqzKvl9axKTJYCiAIYIGblutqtQwQYPZ73jZpCGM+f+AfiEdJy99M21tMxwxJQeGNTGIeHcyHzEjCpAAki2en54OIhULkAKSMiuqqysOGxIGkDWcMyy2t7YQDx58QqcBwMAkFwcKCwYgBEQFBC/Oz9AEBUUALtbX2FJLSUEAOw==')} +${img('page', 18, 'gif', 'R0lGODlhEgASAOYAAPv7++/v7/j7/+32/8HBweTk5P39/djr/8Df//7///P5/8Ph//T09fn5+YGVw2t0pc7n/15hkFWn7ZOq0nqDsMDA/9nh7YSbyoqo2eTx/5G46pK873N+sPX6//f395Cjy83m/7rd/9jl9m13qGVqmoeh0n+OvI+z5Yyu387T//b6/2dtnvz9/32JtpS/8sbGxv7+/tvn92lwom96rHJ8rnSAsoep3NHp/8nk/7e3t+vr67a2tun1/3V4o+Hw/9vt/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEEALAAAAAASABIAAAejgEGCg4SFhoeILjaLjDY1AQCHG0AGAA0eDBY1E5CGGjBAoQkCMTUSHwGGJwaiAh0iNbEvhiihAgIDPDwpFRw5hhgsuLk8Pz8HNL+FJSoKuT4+xzczyoQXzjzQxjcgI9WDDrraPzc4OA/fgibZ0eTmCzLpQS0Z7TflCwgr8hT2EOYIQpCQ16OgwYMRCBgqQGCHw4cOCRQwBCCAjosYL3ZCxNFQIAA7')} +${img('question', 18, 'gif', 'R0lGODlhEgASAPelAOP0//7//9bs//n///j//9Ls/8Pn//r//6rB1t3f5crO2N7g5k1livT4+7PW9dXt/+v4/+Xl5LHW9Ov6/+j1/6CyxrfCz9rd5Nzj6un1/Z6ouwcvj8HBzO7+/+3//+Ln7BUuXNHv/6K4y+/9/wEBZvX08snn/19qhufs8fP7/87n/+/t7czr/5q1yk55q97v/3Cfztnu//z//+X6/ypIdMHY7rPc/7fX9cbl/9/h52WHr2yKrd/0/9fw/4KTs9rm75Svzb2+ya690pu92mWJrcT3//H//+Dv/Xym35S216Ouwsvt/3N/mMnZ5gEBcMnq/wEBXs/o/wEBetzw/zdYpTdZpsvP2ClGml2N3b3H0Nzu/2Z2lF1ricrl/93w/97h6JqluktojM/u/+/z9g8pVff4+ebu9q+1xa6/zzdFaIiXr5Wyz0xslrTK4uL//2uIp11rh8Xj/NXn+Oz2/9bf6bG2xAEBePP//1xwkK/K5Nbr/8fp/2OBtG53kai3ykVCYwEBde/6/7O4xabI+fD//+by/x8+jDhZpM/q/6jK58nO19ny/7jV7ZO42NHr/9H4/2ZwimSV6VBxwMDX7Nvf5hYwX5m20sfb6Ieqyk9Yjr/k/cPM2NDp/+/098Tl9yQ9jLfW+Mne8sjU30JklP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKUALAAAAAASABIAAAjxAEsJHEiwoMEyGMaQWthg0xeDAlGUWKjoz5mFAegY/LBiIalMUK54JCWEoJkIpA6kSDmoAykKgRaqGSiq04A5A5r4AKOEAAAtE2S0USAwSwYIhUb8METiUwAvemLMCMVEoIUjAF5MIYXAThUCDzgVWDQJjkA0cngIEHAHCCAqRqJ0QeQoDxeBFS71KKDCwxonhwiZwPEkzo4+AimJqBFCjBs+UjZ4WmLgxhAQVgb6acGIBShJkbAgMSAhCQ1IBTW8sZRI055HDhoRqXQCYo4tDMJgsqGDTJo6EAlyYFNkVJDgBgXBcJEAucEFeC44n04wIAA7')} +${img('histo1d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEW9vb2np6empqanpqenpqivr6//AAD3+fn09vb19vf3+Pv8+v//+//29/v3+fr19vbZ3Nza3d7X0+Lb3t7b3N3AwMP2+PimpqXe4+Th6uvQ0dTi6uzg5ebFx8nt6vb////r5/T2+fnl4e3a3uDN0NT7/P6lpqX3+vvn9vhcVVHu+//W1uH48//29P///f+mpqelpqb4/v/t/f9oY2H6///59v/x8fXw9fny9/78/v+lpqf7//9iXl12dHPW2t/R1tdtaGbT2dpoZmT6/v9ycnKCgoJpZGJ6dnT3///2///0//95entpa2t+gIKLjI55d3aDgYBvcXL1+/z9/v6lpaWGiIt7fH6Ji42SlJeEhIZubGyMjI17fYD+//+kpKSmpaaRk5WIioyRk5aYmp2OkJJ+f4KTlZilpKWcnqGVl5qcnqCfoaOYmp6PkZOdn6GsrrGoqq6qrK+rrbGpq66lp6uqrbCoqq20tLSsrKzc3NzMzMzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6enrU4/9iYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmLU4/9KSkoAAAAAAAAAAAB6enrU4//m5uZiYmLm5uZiYmLm5uZiYmLm5uZiYmLm5ubU4/9KSkoAAAAAAAAAAAB6enrU4/9KSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkrU4/9KSkoAAAAAAAAAAABubm7U4//U4//U4//U4//U4//U4//U4//U4//U4//U4//U4/9KSkoAAAAAAAAAAABubm5KSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABt6dBwBYjWHVG2AAAAB3RSTlP///////8AGksDRgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOxJREFUeNpjYGBkggBmFmYmRlY2BkZ2DhDg5OLm4eblY2RjYOIXEBQSFhEFkgKCYkxsDOKcEpJS0jKycvJS8gpcIAFFJWUVGFIFCqipa8hrymtpy+sI6crr6bMxGBgayRvLm8iamkmZW1gCBayslWxs7ewd7OwdlZStrYC2ODm7uLrJu3t4usl7mRiwMeh7+/j6+VsHBMr7+wQFhwAFQsPCIyKjomOiIsOiYuPYGOITEpOSU1LTElNTElPlgQLpGZlZ2Tm5eZm5OZm5IAGm/ILCouKS0rKS4oISeaDDypniEICpgo2hsgoZVLMBAHIaNxuoIXy2AAAAAElFTkSuQmCC')} +${img('histo2d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsSAAALEgHS3X78AAABUUlEQVR42o1R0U7CQBDkU/w/v8qk1/OhCS+88miMiQYMBMNRTqiisQiRhF6h13adsuVKDEYvm81kdmdv9q7V7XallP65I30UrpErLGW73SaiFtDF5dXWmNNITJrubJ4RWUI2qU33GTorAdSJMeMwhOxpEE20noRTYISaajBcMrsdOlkgME+/vILtPw6j+BPg5vZuFRuUgZGX71tc2AjALuYrpWcP/WE1+ADAADMAY/OyFghfpJnlSTCAvLb1YDbJmArC5izwQa0K4g5EdgSbTQKTX8keOC8bgXSWAEbqmbs5BmPF3iyR8I+vdNrhIj3ewzdnlaBeWroCDHBZxWtm9DzaEyU2L8pSCNEI+N76+fVs8rE8fbeRUiWR53kHgWgs6cXbD2OOIScQnji7g7OE2UVZNILflnbrulx/XKfTAfL+OugJgqAShGF4/7/T6/Ug+AYZrx7y3UV8agAAAABJRU5ErkJggg==')} +${img('histo3d', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX////48OjIgTrHfjjKgTr78+yixH9HiQBHiACiw37jvJXfpVP6wzT7zTn7yj3lp1qOhyJzvgCa3wCa3gB2ugBinQ6Pt2D4+vfOjEr96p3986z83mT99rD99a3WhEvC0kaU3gCV3ADG71zo/KORzw1gowBonS3Z5snHfTb6uyD6tzD+/Nb7z0/70D3KdTXI1l3h+qTi+KXD7luU3ACY3gCc4QCi3g1QjwXHfjr710T6xi/+9sn70UH73E/MdDqhvQCi1BKkug2XxACU1wCS2ADD51rr9aJXkw/MpYDgpkb71U7+9MP7007hnEO3niOj0hGq3SCZtQCbtQCjtwj//+7F4Vui0wBDhgDk5eTMxcGxfi3TfTq+fyPPz4ak3xux5TG87kmZuwCZvACWtgDf8a+c0gCy3yNLiwD7/Ps1iwCiyAPF3F7j7bG67EW77kmq5yWYzwCZwwCTugDc8KTE51ve9YZCigCgwgCVuQDa5p7U9YSq4yWT2gCV2wCT2wCp2h/y+9HC6lW87DlChQBGigCixgCYvgDK3nyXvgC72UjG7mSj3xXL7XDK7W7b9J+36TrG9lBDhQBHigClywCbxQDJ33SXvwCYvQCcwADq+8S77Ei460Hd+KDD9VHU/2VEhgBdlR1rowCXwwDK4W6bxgCaxQCVvQDp/L+/8k7F91fn/6zC9V18tiNbkx/U1dSyv6RglihnoQCYwwChyQDs/7/P/2fE92F5tCBdkib19vXW1taoupVLiwNooQCWwADo/7h5tSBFhgaouZXx8vHOz86ftYVJiQBNjQKetIXt7u3Nzs0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBfAAAAAAAAAA2tmA2tmAAACQAAAAAAAAAAAAAAAAAAAAAATgAABNBfMAAAAAAAAA2tpQ2tpQAACQAAAAAAAAAAAAAAAAAAAAAAdQAABNBfMAAAAAAAAA2tsg2tsgAACQAAAAAAAAAAAAAAAAAAAAAAggAABNBfMCaVmCSAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAQVJREFUeNpjYGBkYmZhZWBj5+BkAAMubh5ePn4BQSFhEVExcaCAhKSUtIysnLyCopKyiqqaOoOGppa2jq6evoGhkbGJqZk5g4WllbWNrZ29g6OTs4urmzuDh6eXt4+vn39AYFBwSGhYOENEZFR0TGxcfEJiUnJKalo6A0NGZlZ2Tm5efkFhUXFJqTnQnrLyisqq6prauvqGxqZmoEBLa1t7R2dXd09vX/+EiUCBSZOnTJ02fcbMWbPnzJ03HyiwYOGixUuWLlu+YuWq1WvWAgXWrd+wcdPmTVu2btu+Y/06kHd27tq9Z+++/QcOHtq1E+JBhsNHjh47fuLIYQYEOHnq1EkwAwCuO1brXBOTOwAAAABJRU5ErkJggg==')} +${img('graph', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEFCgohaz8VogAAAT9JREFUOMulkz1LQlEYx39XmrIhcLa5i4mD4JBQrtHieDbb+gx3dbl9hca7tLi4VOsRMkKQVO7LLAQNNdSQgyJPg903tDT8w4HzPDznd56Xc1BKCVsokzTGjhPBXDcQAAEZDgPZCHDQaESH5/PYXyqZxp8A349vGHkjOXo3uXtp035sy79KABi8DQCwshb7x3U6gIYU6KNej+1kEwUEjbQeWtIb9mTsOCIgN1eXgiYd96OdcKNBOoCuQc47pFgoGmHw7skZTK9X16CUku5zV9BIkhz4vgSuG/nsWzvKIhmXAah+VpfJsxnGZMKkUln05NwykqOORq6UWkn+TRokXFEG/Vx/45c3fbrnFKjpRVkZgHKxbAC8NptrAfm9PAD2l42VtdJjDDwv2CSLpSaGMgsFc91hpdRFKtNtf6OxLeAbVYSb7ipFh3AAAAAASUVORK5CYII=')} +${img('mgraph', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEW9vb2np6empqanpqenpqivr68AAAD3+fn09vb19vf3+Pv8+v//+//29/v3+fr19vbZ3Nza3d6/wcLb3t7b3N3AwMPi4et2oz0yfwDh3+n2+PimpqXe4+Th6uvD0NHi6uzg5ebFx8nt6vY2ggDs/881gQDr5/T2+fnFz9DDZVrAIhDEZVvJ0tTN0NTX0+IvZAA4hAAuYgDT0N77/P6lpqX3+vvn9vi/JRL81cHBJhTu+//W1uEkXgD48//29P8fWwD//f+mpqelpqb4/v/t/f+yCwDBKBi3CgD6//8kYAD59v/x8fXQ0dTw9fny9/78/v+lpqf7//+wAADV5ezZ5e7g6PQjZQDf4+/W2t/R1tfT2drT3+OvAAD9///6/v/////k4vIiXwC1AAD3///2///X6Oz0//9+rUgzfwAwdADa6u6xCwDAJxb5///1+/z9/v6lpaUwfADo/8vl4e3a3uDb6eu+IxL808C+IhDZ5+nW2tr+//+kpKSmpaaArUgvewB1oj39/v/e5ebVd227HgvJa2H8///6/PylpKXn4+ze4eLg5+j9/v20tLSsrKzc3NzMzMzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAPAAAAAAEAAAEAAABzL1z/CSMAAAAAAAAAAAAAAAMAAAAmCTsAAAAAAAAAAAAAAAAAAAQAAQEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7op0gAAAAB3RSTlP///////8AGksDRgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOhJREFUeNpjYGBkggBmFmYmRlY2BkZ2DhDg5OLm4eblY2RjYOIXEBQSFhEVE5cQl5RiYmOQ5pSRlZNXUFRSVlFV4wIJqGtoamnr6OrpGxgaGQMFTEzNzC0sraxtbPXs7B0c2RicnF1c3dw9PL28fXz9/IECAYFBwSGhYeERkVHRMYEBQFti4+ITEuOTklNSg9I8nNgYHOPTMzLjA7Oyc7Jz8/ILQAKFRRnFJaVl5RWVVdU1bAy18XX1DfGNTc0trW3t8UCBjvj4+M746q74+O7qHpAAUzwyADqsl6kGAZj62Bj6JyCDiWwAyPNF46u5fYIAAAAASUVORK5CYII=')} +${img('tree', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAACjklEQVQ4y4WTy49LcRzFP+2tzmVUr9dIDJOWGGVBicgEyTQTCzIetUFssDKJSFhY2SARCYvBbGrj8QcIkYglk8xCmEQ9xqNexbQVY2Zub3un9/W7PwstHZH4Jie/7+Kc8/suzgnwr+kjBqSBbm2lkm6bHyH3XM9SZQ8Z8s3UQJPo0IJVof5EZ7v2faxMrKONlhmQWN5GSFEwLbhybjBPhDwVsmQ4AaA09Mou+k8d702EAzXiS6KEgzahoIthGOi6DtKlN71GS+/cEPs0WewaX2R9ZphssP776UhESY0WSpQNg7Jh4Anx+zgJVKpV3uZyvHjzir27NwGs/XVBH8c7N2nnjx7eSqlYxPM8JCCkxBU+rhA4dVhCYJgmyc4Ej96/7rLi8nNAPc/k2ZNp7cnTpziuiy8lvpSI+tvYhS/xpY8vJXMiEbZv3MzFq3cJqaqiPX72jnKt9kfQRPZ9f5qZ70sMawyAas1GseIy1rNtVXK8Mkm1VsP2PBzhYQuB5Qns+t6AJQSqqlIcrTAy+ONGENBWLF3MN71MxXGo1mE6DqbrYLou8z/a7L3uMKvgUnU8xk2T3u71ADGFDdgvCx/3TwkLEfKxhWDHbY+eYZ+Obz6tJcmRApRsuJ8Ex4Po7Jl8/TDBl7flm4Gm5F1vSZKaFQUh4cB9OLgaDB3UVrjwA+6tBnKAis4El8lwujmJSVQeoKAxFzqDcG0KWhZC6R30tUJRQD3Odxqy4G+DDFks4pisY5RLgRx5pZ5T4cKy95yhSrxZDBCaVqIMOpAd2EIeSEW7wLQh3Ar7RtCHbk0v0vQy1WdgCymgf147Sa0dhAOVMZgoALDu2BDZ/xloQAzQgIOhMCnPYQ+gHRvi4d/8n00kYDRVLifLAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEwLTAyLTExVDE0OjUxOjE3LTA2OjAwHh/NoQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAwNC0wOS0yMFQxNzoxMDoyNi0wNTowMCcJijsAAAAASUVORK5CYII=')} +${img('branch', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX///99plFAfADL27hpmyfP8YxyoilSiRiv0XGGygK02VtRiBmVwjh8xQCcziFZkhLz9+9BfQB2rwaCyACRygFQigXw9Ox0mkpXkQCJzwBblgBmkzP8/fxEgQBCfwBEgQejwITe3t5hkC1CfgBfjynZ2tmSq3eArDu72oNvoDJajyTY2dhFgQDCzLqhvn9EgAazx55XkwCVzC2824GMs1J0oUTY48xajiK72YR9qj2Tq3dhkix+th99xAB3uADA3oQ+fABEgABIgwW82oOUyi5VkgCf0CaEygB+wwCbzjN1mkrA3YZ1tAB7wAB+uB1vl0JdmgCJwwCKzwBoqAB4nVBikiuayzZ8wQCFywCg0Sjd3t1lkjFBfABLgwhKgwlmpgCK0QCJxQBclwDMzMzPz89GggCDpFxDfgCIpmPl5eUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABhAABQEABuZQBjYQBvcgAIZABiYQBlZAAABQDU/wCx/wCO/wBr/wBI/wAl/wAA/wAA3AAAuQAAlgAAcwAAUADU/wCx/wCO/wBr/wBI/wAl/wAA/gAA3AAAuQAAlgAAcwAAUADj/wDH/wCr/wCP/wBz/wBX/wBV/wBJ3AA9uQAxlgAlcwAZUADw/wDi/wDU/wDG/wC4/wCq/wCq/wCS3AB6uQBilgBKcwAyUAD//wD//wD//wD//wD//wD//wD+/gDc3AC5uQCWlgBzcwBQUAD/8AD/4gD/1AD/xgD/uAD/qgD/qgDckgC5egCWYgBzSgBQMgD/4wD/xwD/qwD/jwD/cwD/VwD/VQDcSQC5PQCWMQBzJQBQGQD/1AD/sQD/jgD/awD/SAD/JQD+AADcAAC5AACWAABzAABQAAD/1AD/sQD/jgD/awD/SAD/JQD/AADcAACwULzWAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAALZJREFUeNpjYAADRiZGBmTAzMLKxowswM7BycWNLMDEw8vHL4AkICgkLCIqhiQgLiEpJS0D5cjKySsoKimrqMJk1dQ1NLW0dXQZ9PTlZEECBoZGxiamOmbmmhaWViABaxtbO3sHRycTZxdXA7ANbu4enkxeDt4+vn7WIAH/gMCg4JBQprDwiEhBkEBUtGBMrI5OXHxCYpI/2BrV5OSU5NS09BjB6CiE01JTM5KTVZHcmpycCWEAANfrHJleKislAAAAAElFTkSuQmCC')} +${img('leaf', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX////M27mQs2tilDA9eQA7egBbkhVTjAxJgwWBqVdGgQBrnySdxViu0WrE4oaYv2PC35NtoCqxvaSevX5FgAB7qje73nDK6neu109vpyVupCGo2kJ9xwBQhBtilC9pnx7G63PM6olgnAB/vQBDigCVv0yb1CaDzAB8uBJwmkNnnBnB52ui2Ca94WZopAE/hgCtz2ue2CmDywCByACKujtdjyqdvHpdlhLV9YdkowCFxwCw1lFXmAJvpC5jng1coABlpwBprAB8sitAfABDfgKx31Gr3TuCsi5sqABtqgBUkxTV85zL7I213mef0j+OxyKk00k/ewCp3TCSyhCw0mRRjQC23HmU0h55wQB5vQB4uQB1tgCIwBeJxgCBvQDC3ndCjACYx1204Fx6wwB7vQB1tABzsQBBfQBpkzdtpQB9tQA/iQCMu1SMukNUlQBYmQBsqAd4rh11rwZyrQBvqgBDfwCqvZVWkQBUnACp0Hq/43K733C+4X+w12eZyT2IvSN5sgpZkwBxmUSDqFlbnACJzQy742p/wwB2ugBysgBwrwBvqwBwqQBhmgBCfwDV2NN8pk1foACO1QBZmABRkABpqwB3uQB0sgB0rgBnogBUjgC7w7NymkFdnQBUhxmis41okjdCfgBGgQWHpWPMzMzb3NtumD5NhQzT09Pv8O/a2trOz87l5eXc3NzPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtHAA4HXQAAEgAAB9CTigAAABCfCQ4HTxy6Kw4HXRy+8xy+8wAAMwAAAAAAAAAAAAAAAAAAAAAAAgAAAgAABgYAAG7AAAAACgAAAgAAAgYAAEAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Hnw4HnwAAFRpRiYmO2V0aWRtSSY7ZWdsZVNpdGNBO251amRGO3R0bCYmO3J3ZWlvVCY7c2xuaVc7d28ABCwBG8q3AAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAOtJREFUeNpjYIACRiZmFlY2dg4ol5OLm4eXj19AUAjMFRYRFROXkJSSlpEF8+XkFRSVlFVU1dQ1NMF8LW0dXT19A0MjYxNTIN/M3MLSytrG1s7ewdHJGSjg4urm7uHp5e3j6+cfABIIDAoOCVUJC4+IjIqOAQk4x8bFJyQmJadEpaalpQMFMjKzsnNy8/ILCouKS0qBAmXlFZVV1TW1dfUNJY1NQIHmlta29o7ozq7unt6+fgaGCRMnTZ4ydVrU9BkzZ5XOBiqYM3HuvPkL0tPTFy5avATkzqXLlq9YuWoJEKxeA/Ho2nUMyAAA9OtDOfv2TiUAAAAASUVORK5CYII=')} +${img('leaf_method', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAKlBMVEUAAAAAgADzExMAgIAAAADAwMCAgADxGRnuFxLnHhHuIyPKJQ/rLi7////aW8ZOAAAAAXRSTlMAQObYZgAAAAFiS0dEDfa0YfUAAAAHdElNRQfgCxIPFR/msbP7AAAAaUlEQVQI12NggANBBiYFMMNQxAjCYA4UUoZIBRpBGMyiQorGIIaxWRCEwSYo3igiCNJlaLkwGSwkJn1QGMhgNDQ0TDU2dACqERYTDksGG5SkmGoApBnFhBRTBUAiaYJpDIJgs10cGBgdACxbDamu76Z5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTAxLTE3VDA5OjMwOjM1KzAxOjAwyGHxKQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0xMS0xOFQxNToyMTozMSswMTowMJgvuUkAAAAASUVORK5CYII=')} +${img('globe', 18, 'gif', 'R0lGODlhEwASAPcAAPr7/AFyAQFCpwGD6jy3/wE9on7N0AE+pAFjyMLI0AE2mwF94wGP9QFpzgU3nISSopWgrmJsfTNLfgFHqAFuBilNiTp4sLnGzwWb/0xYb/P09mRygGl0hRlnMgR12V2Pr6e4xF9peS2Cyh5FpBdSfgF84YmisdPa30hjvw+foQFYvlWj4HWIlkWb5gk5n/b4+gw+kgFMscXb6ylmieDj5ju2pylTsniElgqd/u/x8wGW/O7v8SVMsUq+JSSJXQFiwfv+/AFqvB9ntobZeKbc/9vt+B+YmW2rvKruzQGPkm3PPrjmxQFIklrFLVbD4QGMYaXkoIPD13LC+nGw5AGFQHG66gF2eBaJxket9sLf84HI+wF7axBdbg2c0CR+1QFsEIfJ7yqoUIbH41tldgF+KzVTjn3QfitZgTJZkaDR8gKDsXeWrE+zogE3nCeKzQFtJ0tknjdnbQGB6EJgxQFqAcLJ0WC//yKm/wE+o7vI0ARozEOz/4/g/4KToyaX4/D09pCpuNHV24HA6gw7oAF/AXWKnEVSb5TI6VzDTrPprxBQts7e6FNdcBA9oySd9RRjPAhnD2NvgIydrF+6wdLo9v7//2K+twKSdDmKyeD56wGCyHq12VnF+ZXXsARdTjZWthShoo7gtilDlAFw1RCXvF+z6p/R8kqZzAF0Oj5jjFuJqgFoAkRgxtzr9YmcrJKsugFlylfBgxJGhjJIeFnFuhmi/+bo65ipt8Hn+UhVco7B5SZowAGBKoaZqAGGAVHBUwF8Qq7Y819qe4DEoVyYwrnb8QGN9GCy6QFTuHB9jgGY/gFRtuTu9ZOhr150iwFbwTFiwFus4h9mYt/y+kWZ35vM7hGfccz43Xy/6m3BuS1GiYveqDRfwnbUV4rdu////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAN8ALAAAAAATABIAAAj/AL8JHEiwVTVspar8ITiwiJhswyaBibJJUq9Trxh+S2OAVihvSzqRcoTpmy5ADIPFqrHtGpBETbrIuXJEBgiGbHoogTItExJOoAbw8rHmAkFTC8KYwTWkGx8COp4AozAjD8Epo4wQQfTLCQEcxqigoiONBUFqerRYspYCgzIGmgi98cRlA8EVLaR4UJPk0oASVgKs6kAiBMFDdrzAarDFF5kgCJA9ilNBGMFjWAQse/YjwBcVMfCcgTMr2UBKe0QIaHNgAiQmBRS4+CSKEYSBWe44E6JoEAxZDhrxmDPCEAcaA4vVinTCwi5uKFhBs6EtQ4QEOQYy8+NGUDRiqdCUJJGQa8yNQDsADHyxSNUHE4Vc3erzoFkdWxoAVNLIv7///98EBAA7')} +${img('canvas', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEX/AAC1t7etsLCsrq6rrq6rrq2tr6+0tratsK/////p6enIysrl5OTn5uXo5+ajpaXo5+dhhKdliKlmialgg6elp6f6+/vIycnr7Ozw7u7x7u7x7u3t6+vLzMvp7vbs7/bz8PD17+3z7u2rrq/6xS76xy13zv9+z/+EwLF4zP/38/NfgqWAoL36uCj6vCmR2f+TxamSrBmNvoj++fz8+Pf69/WZ3f+g4P+n4/+Cnw2Dox16nQ3//f9hg6eBob6x5/+46f+77P+p2NKSZhOi1s////7//fusrq98sB6CsyWDtSmFuC9+dBl/tilfgqasr6+sr7DbAADcAABcgqWAoLyusLC4urqssLCssLGrsLCrr7Ctr67c3NzMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAKAgJldmV0dU8GB3JvTnZDBWVyb2xsYwdjYWxhUBB0bmVrY2F1b3IICGRPYmFyZWQAAAXj1P/Hsf+rjv+Pa/9zSP9XJf9VAP9JANw9ALkxAJYlAHMZAFDU1P+xsf+Ojv9ra/9ISP8lJf8AAP4AANwAALkAAJYAAHMAAFDU4/+xx/+Oq/9rj/9Ic/8lV/8AVf8ASdwAPbkAMZYAJXMAGVDU8P+x4v+O1P9rxv9IuP8lqv8Aqv8AktwAerkAYpYASnMAMlDU//+x//+O//9r//9I//8l//8A/v4A3NwAubkAlpYAc3MAUFDU//Cx/+KO/9Rr/8ZI/7gl/6oA/6oA3JIAuXoAlmIAc0oAUDLU/+Ox/8eO/6tr/49I/3Ml/1cA/1UA3EkAuT0AljEAcyUAUBnU/9Sx/7GO/45r/2tI/0gl/yUA/gAA3AAAuQAAlgAAcwAAUADj/9TH/7Gr/46P/2tz/0hX/yVV/wBJ3AAQ+AFLAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAALpJREFUeNpjYGBkYmZhYWFlYWNngAAOTijg4oYIMHPy8PLx8nDycwpwQwUEhYSFRDhFxTi5xCECEpJS0jKcsqL8nGwgARZOOXkFRSWwMcwgAWVOFVU1dQ1NLW0dmICunr6BoZGxiSlEgJnTzNzC0sraxtYOJmDv4Ojk7MLp6gYRcOf08PTy9vHl9IOa4c+JAGCBAM7AoEDOwEDO4BCIABOSilCQQBhTeERkVGS4f3R0aBhIICYWAWIYGAClIBsa7hXG7gAAAABJRU5ErkJggg==')} +${img('profile', 16, 'gif', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsSAAALEgHS3X78AAABZElEQVR42o1R22rCQBD1U/p//apCNtsHwRdfBaFIKbRoUVKMMTWBWIxVCq2b+07POrn4UKjDMpw9O2fm7G5vNBpJKe2/Qto4uEc2WMrBYEBEPaAky36UulwnlSRpUeZEBSGrpEiyHJVGAPVJqZvbO3ftv83Dle+vvPV4/LD0PGYAcKrSFJUsEOgHKoj3s9dFGH9uou3k8ekQKxyDQcYpBnYC7Hm9zBZmlL8BiIJDC0AWpa4FwhZJXoDCBgYAjgU5ToBt+k1tL14ssFNNvIEBAFwVljJlSDBfpwyg1ISnYoEsiHju5XLcd+T50q0tEQm7eaWKKNfUWgKApUsbPFY0lzY6DraEZm585Do/CLMzqLQWQnSC9k34lVa7PTsBs/zYOa4LB5ZlnQXCbif40Ra50jUwE6JtCcMlUiMQlugEQYisG8CWtGlRdQL+jmui/rjhcAhk/Reo6ff7RuB53vN1MZ1OIfgFQC1cuR3Y6lIAAAAASUVORK5CYII=')} +${img('execute', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEXAwMAAxwCvbOAvAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAACBJREFUCFtjYIABHgYGfiA6wMD/gYH/B5g8ABLhYUAGAHniBNrUPuoHAAAAAElFTkSuQmCC')} +${img('file', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAA2klEQVRIx61VURbDIAgTX+9ljg4n2z5sNouj1ml+LE9rQkSU5PA6kTZBToTznj5aqKqq+py4lFJKScnMzCwlAAB6IbnNuyXycd1g3oHrf32CmR9mZqpVOdDHs2DmI+c+AiJixu1RAN9xFUcdWCjVIr8xCX8Jubc8Ao9CJF8nRFgNJBxZSCEkjmrIxxSS0yIAoBU4OkpfU8sCPEbEvqaOXcR31zWORbYJ8EI8rsK+DWm7gMVb8F/GK7eg6818jNjJZjMn0agY7x6oxqL5sWbIbhLHoQN78PQ5F3kDgX8u9tphBfoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMDItMDZUMTA6Mjc6MzErMDE6MDChLu/mAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjIwKzAxOjAwIGvf8wAAAABJRU5ErkJggg==')} +${img('text', 16, 'gif', 'R0lGODlhEgASALMAAP/////MzP+Zmf9mZv8zM/8AAMzM/8zMzJmZ/5mZmWZm/2ZmZjMz/zMzMwAA/////yH5BAUUAA8ALAAAAAASABIAAARo8MlJq73SKGSwdSDjUQoIjhNYOujDnGAnFXRBZKoBIpMw1ICHaaigBAq/AUK1CVEIhcfPNFlRbAEBEvWr0VDYQLYgkCQWh8XiAfgRymPyoTFRa2uPO009maP8ZmsjAHxnBygLDQ1zihEAOw==')} +${img('task', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAAAAACMfPpKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAATklEQVQY05XQUQoAIAgD0N3JY3fIChWttKR9xYvBCj0J0FsI3VVKQflwV22J0oyo3LOCc6pHW4dqi56v2CebbpMLtcmr+uTizz6UYpBnADSS8gvhaL5WAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA0LTA3VDA5OjQyOjQ4KzAyOjAwMgzRmQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOCswMTowMJ0LlncAAAAASUVORK5CYII=')} +${img('pavetext', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAsSURBVBjTY2CgCuBAAt1gASS5KKgARBpJACSEooIsARRbkABYoDsKCRDhEQBA2Am/6OrPewAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0wNFQxMDoxODoyNyswMTowMHsz6UQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MjArMDE6MDAga9/zAAAAAElFTkSuQmCC')} +${img('pavelabel', 18, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAApSURBVBjTY2CgCuBAAt1gASS5KJgABzUEgABFANUWJAAWYIhCAkR4BAAHoAkEyi2U3wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0wNFQxMDoxODoyNyswMTowMHsz6UQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MjArMDE6MDAga9/zAAAAAElFTkSuQmCC')} +${img('list', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEECTc01vBgywAAAE9JREFUOMu1k8ERwDAMwqRc9l/Z/eeRpKZlABkOLFD0JQGgAAah5kp8Y30F2HEwDhGTCG6tX5yqtAV/acEdwHQHl0Y8RbA7pLIxRPziGyM9xLEOKSpp/5AAAAAASUVORK5CYII=')} +${img('color', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAAAA4xcavGZGS1xZT79lW+9wdvFz/3N6fo3RISTZwXbyniXz80v/AAD/zAD/66v//6vGWiYeAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADswAAA7MAbGhBn4AAAAHdElNRQfgAQQLLBhOmhPcAAAAIklEQVQY02NgRgEMDAzMnLzcfDwC7IxMbKwsQ10A3XMEAQA3JQVNowlkTAAAAABJRU5ErkJggg==')} +${img('colz', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEV6fo0A4xcavGZGS1xZT79lW+9wdvFz/3PRISTZwXbyniXz80v/AAD/zAD/66v//6t1AkcGAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADswAAA7MAbGhBn4AAAAHdElNRQfgAQQLNwdqpNWzAAAAT0lEQVQI12NgYGAwNjZmAAOLjmY0hs2ZwxCG1arFEIbt3csQhvXuzRCG/f/PEIZ5eTGEYSgoDGEYKSlDGGZpyRCGaWgwhGHi4gxhwG0HAwCr3BFWzqCkcAAAAABJRU5ErkJggg==')} +${img('frame', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAQQLOwq4oOYCAAAAcUlEQVQoz7WQMQqAMAxFX0Uk4OLgIbp4oZ7BA/cOXR0KDnGpRbGayT+EQF74nw+GHIBo+5hdWdqAaFDoLIsegCSeWE0VcMxXYM6xvmiZSYDTooSR4WlxzzBZwGYBuwWs4mWUpVHJe1H9F1J7yC4ov+kAkTYXFCNzDrEAAAAASUVORK5CYII=')} +${img('class', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAvQC9AL1pQtWoAAAAjUlEQVR42p2T2wnAIAxFM0g/O6jDdBBHcAyHKKQYjfiI0UY4P8I9BG4CID8smB4+8SUsohpO3CFzKqmBFrhCO4kqQnCR6MJF4BEJTVQFhBAmASNIZkH6a0OMc8oUDAu8z7RhTTBVyIIEhxeCdYWjQApvK2TBrgGpwpP1livsBXC0ROMO/LqDKjKEzaf8AZWbJP6pTT9BAAAATHpUWHRTb2Z0d2FyZQAAeNpz0FDW9MxNTE/1TUzPTM5WMNEz0jNQsLTUNzDWNzBUSC7KLC6pdMitLC7JTNZLLdZLKS3IzyvRS87PBQDzvxJ8u4pLSgAAADN6VFh0U2lnbmF0dXJlAAB42ktKs0hLMkk2MzJKNEuzMLKwtEizSElMMbNITUw0NUtNAQCc7Qma0Goe1QAAAABJRU5ErkJggg==')} +${img('member', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAvQC9AL1pQtWoAAAAX0lEQVR42mNgAAIVBob/+DADPgBS8GCPBV6M1xCKDcDnBRcoZhgW4D8DBV75v2bLATAmxyC4ZmRMrCFYNfeU9BBvwJwpS8AYWTNZBoAwTDPFBpAciDCDyNFMtXSAFwAAUyq0GRPbbz4AAABMelRYdFNvZnR3YXJlAAB42nPQUNb0zE1MT/VNTM9MzlYw0TPSM1CwtNQ3MNY3MFRILsosLql0yK0sLslM1kst1kspLcjPK9FLzs8FAPO/Eny7iktKAAAAM3pUWHRTaWduYXR1cmUAAHjaS01JNrE0S00zSbU0NEsxMbMwM0xOSjYwNzY3NLRIMjUCAJcdCJ2BHe6SAAAAAElFTkSuQmCC')} +${img('tf1', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX/////AP8/SMz///+Cf5VqAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfgCw4QHgSCla+2AAAAL0lEQVQI12MQYAACrAQXiFBoABINCgwMQgwcDAwSDEwMDKmhodMYJjAwaKDrAAEAoRAEjHDJ/uQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMTEtMTRUMTc6Mjk6MjErMDE6MDDxcSccAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTExLTE0VDE3OjI5OjA1KzAxOjAwNka8zgAAAABJRU5ErkJggg==')} +${img('tf2', 16, 'png', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX/////AP8A/wD////pL6WoAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfgCw4PNgzGaW1jAAAARUlEQVQI12NgEGDQZAASKkBigQKQ6GhgYBDiYgASIiAigIGBS8iBgUFhEpCnoAEkUkNDQxkagUIMrUDMMAVETAARQI0MAD5GCJ7tAr1aAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTExLTE0VDE2OjUxOjUzKzAxOjAwi1Gz3gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0xMS0xNFQxNjo1MTozNiswMTowMG5bLUIAAAAASUVORK5CYII=')} +`, node, 'jsroot_hstyle'); } -/** @summary Draw JS image +/** @summary Return size as string with suffix like MB or KB * @private */ -function drawJSImage(dom, obj, opt) { - const painter = new BasePainter(dom), - main = painter.selectDom(), - img = main.append('img').attr('src', obj.fName).attr('title', obj.fTitle || obj.fName); - - if (opt && opt.indexOf('scale') >= 0) - img.style('width', '100%').style('height', '100%'); - else if (opt && opt.indexOf('center') >= 0) { - main.style('position', 'relative'); - img.attr('style', 'margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'); - } - - painter.setTopPainter(); - - return painter; +function getSizeStr(sz) { + if (sz < 10000) + return sz.toFixed(0) + 'B'; + if (sz < 1e6) + return (sz / 1e3).toFixed(2) + 'KiB'; + if (sz < 1e9) + return (sz / 1e6).toFixed(2) + 'MiB'; + return (sz / 1e9).toFixed(2) + 'GiB'; } -var more = /*#__PURE__*/Object.freeze({ -__proto__: null, -drawBox: drawBox$1, -drawEllipse: drawEllipse, -drawJSImage: drawJSImage, -drawMarker: drawMarker$1, -drawPie: drawPie, -drawPolyLine: drawPolyLine, -drawPolyMarker: drawPolyMarker, -drawText: drawText$1 -}); - -const kNotEditable = BIT(18), // bit set if graph is non editable - clTGraphErrors = 'TGraphErrors', - clTGraphAsymmErrors = 'TGraphAsymmErrors', - clTGraphBentErrors = 'TGraphBentErrors', - clTGraphMultiErrors = 'TGraphMultiErrors'; - -/** - * @summary Painter for TGraph object. - * - * @private - */ +/** @summary Return ROOT version as string + * @private */ +function getVersionStr(v) { + const major = Math.floor(v / 10000); + let minor = Math.floor((v - major * 10000) / 100).toString(), + patch = (v % 100).toString(); + if (minor.length < 2) + minor = '0' + minor; + if (patch.length < 2) + patch = '0' + patch; + return `${major}.${minor}.${patch}`; +} +/** @summary draw list content + * @desc used to draw all items from TList or TObjArray inserted into the TCanvas list of primitives + * @private */ +async function drawList(dom, lst, opt) { + if (!lst?.arr) + return null; -let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { + const handle = { + dom, lst, opt, + indx: -1, painter: null, + draw_next() { + while (++this.indx < this.lst.arr.length) { + const item = this.lst.arr[this.indx], + opt2 = (this.lst.opt && this.lst.opt[this.indx]) ? this.lst.opt[this.indx] : this.opt; + if (!item) + continue; + return draw(this.dom, item, opt2).then(p => { + if (p && !this.painter) + this.painter = p; + return this.draw_next(); // reenter loop + }); + } + return this.painter; + } + }; - constructor(dom, graph) { - super(dom, graph); - this.axes_draw = false; // indicate if graph histogram was drawn for axes - this.bins = null; - this.xmin = this.ymin = this.xmax = this.ymax = 0; - this.wheel_zoomy = true; - this.is_bent = (graph._typename === clTGraphBentErrors); - this.has_errors = (graph._typename === clTGraphErrors) || - (graph._typename === clTGraphMultiErrors) || - (graph._typename === clTGraphAsymmErrors) || - this.is_bent || graph._typename.match(/^RooHist/); - } + return handle.draw_next(); +} - /** @summary Return drawn graph object */ - getGraph() { return this.getObject(); } +// ===================== hierarchy scanning functions ================================== - /** @summary Return histogram object used for axis drawings */ - getHistogram() { return this.getObject()?.fHistogram; } +/** @summary Create hierarchy elements for TFolder object + * @private */ +function folderHierarchy(item, obj) { + if (!obj?.fFolders) + return false; - /** @summary Set histogram object to graph */ - setHistogram(histo) { - const obj = this.getObject(); - if (obj) obj.fHistogram = histo; + if (!obj.fFolders.arr.length) { + item._more = false; + return true; } - /** @summary Redraw graph - * @desc may redraw histogram which was used to draw axes - * @return {Promise} for ready */ - async redraw() { - let promise = Promise.resolve(true); - - if (this.$redraw_hist) { - delete this.$redraw_hist; - const hist_painter = this.getMainPainter(); - if (hist_painter?.isSecondary(this) && this.axes_draw) - promise = hist_painter.redraw(); - } + item._childs = []; - return promise.then(() => this.drawGraph()).then(() => { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; - return res; + for (let i = 0; i < obj.fFolders.arr.length; ++i) { + const chld = obj.fFolders.arr[i]; + item._childs.push({ + _name: chld.fName, + _kind: getKindForType(chld._typename), + _obj: chld }); } + return true; +} - /** @summary Cleanup graph painter */ - cleanup() { - delete this.interactive_bin; // break mouse handling - delete this.bins; - super.cleanup(); - } +/** @summary Create hierarchy elements for TList object + * @private */ +function listHierarchy(folder, lst) { + if (!isRootCollection(lst)) + return false; - /** @summary Returns object if this drawing TGraphMultiErrors object */ - get_gme() { - const graph = this.getGraph(); - return graph?._typename === clTGraphMultiErrors ? graph : null; + if (!lst.arr?.length) { + folder._more = false; + return true; } - /** @summary Decode options */ - decodeOptions(opt, first_time) { - if (isStr(opt) && (opt.indexOf('same ') === 0)) - opt = opt.slice(5); + let do_context = false, prnt = folder; + while (prnt) { + if (prnt._do_context) + do_context = true; + prnt = prnt._parent; + } - const graph = this.getGraph(), - is_gme = !!this.get_gme(), - has_main = first_time ? !!this.getMainPainter() : !this.axes_draw; - let blocks_gme = []; + // if list has objects with similar names, create cycle number for them + const ismap = (lst._typename === clTMap), names = [], cnt = [], cycle = []; - if (!this.options) this.options = {}; + for (let i = 0; i < lst.arr.length; ++i) { + const obj = ismap ? lst.arr[i].first : lst.arr[i]; + if (!obj) + continue; // for such objects index will be used as name + const objname = obj.fName || obj.name; + if (!objname) + continue; + const indx = names.indexOf(objname); + if (indx >= 0) + cnt[indx]++; + else { + cnt[names.length] = cycle[names.length] = 1; + names.push(objname); + } + } - // decode main draw options for the graph - const decodeBlock = (d, res) => { - Object.assign(res, { Line: 0, Curve: 0, Rect: 0, Mark: 0, Bar: 0, OutRange: 0, EF: 0, Fill: 0, MainError: 1, Ends: 1, ScaleErrX: 1 }); + folder._childs = []; + for (let i = 0; i < lst.arr.length; ++i) { + const obj = ismap ? lst.arr[i].first : lst.arr[i]; + let item; + if (!obj?._typename) { + item = { + _name: i.toString(), + _kind: getKindForType('NULL'), + _title: 'NULL', + _value: 'null', + _obj: null + }; + } else { + item = { + _name: obj.fName || obj.name, + _kind: getKindForType(obj._typename), + _title: `${obj.fTitle || ''} type:${obj._typename}`, + _obj: obj + }; - if (is_gme && d.check('S=', true)) res.ScaleErrX = d.partAsFloat(); - - if (d.check('L')) res.Line = 1; - if (d.check('F')) res.Fill = 1; - if (d.check('CC')) res.Curve = 2; // draw all points without reduction - if (d.check('C')) res.Curve = 1; - if (d.check('*')) res.Mark = 103; - if (d.check('P0')) res.Mark = 104; - if (d.check('P')) res.Mark = 1; - if (d.check('B')) { res.Bar = 1; res.Errors = 0; } - if (d.check('Z')) { res.Errors = 1; res.Ends = 0; } - if (d.check('||')) { res.Errors = 1; res.MainError = 0; res.Ends = 1; } - if (d.check('[]')) { res.Errors = 1; res.MainError = 0; res.Ends = 2; } - if (d.check('|>')) { res.Errors = 1; res.Ends = 3; } - if (d.check('>')) { res.Errors = 1; res.Ends = 4; } - if (d.check('0')) { res.Mark = 1; res.Errors = 1; res.OutRange = 1; } - if (d.check('1')) if (res.Bar === 1) res.Bar = 2; - if (d.check('2')) { res.Rect = 1; res.Errors = 0; } - if (d.check('3')) { res.EF = 1; res.Errors = 0; } - if (d.check('4')) { res.EF = 2; res.Errors = 0; } - if (d.check('5')) { res.Rect = 2; res.Errors = 0; } - if (d.check('X')) res.Errors = 0; - }; + switch (obj._typename) { + case clTColor: + item._value = getRGBfromTColor(obj); + break; + case clTText: + case clTLatex: + item._value = obj.fTitle; + break; + case clTObjString: + item._value = obj.fString; + break; + default: + if (lst.opt && lst.opt[i] && lst.opt[i].length) + item._value = lst.opt[i]; + } - Object.assign(this.options, { Axis: '', NoOpt: 0, PadStats: false, PadPalette: false, original: opt, second_x: false, second_y: false, individual_styles: false }); + if (do_context && canDrawHandle(obj._typename)) + item._direct_context = true; - if (is_gme && opt) { - if (opt.indexOf(';') > 0) { - blocks_gme = opt.split(';'); - opt = blocks_gme.shift(); - } else if (opt.indexOf('_') > 0) { - blocks_gme = opt.split('_'); - opt = blocks_gme.shift(); + // if name is integer value, it should match array index + if (!item._name || (Number.isInteger(parseInt(item._name)) && (parseInt(item._name) !== i)) || (lst.arr.indexOf(obj) < i)) + item._name = i.toString(); + else { + // if there are several such names, add cycle number to the item name + const indx = names.indexOf(obj.fName); + if ((indx >= 0) && (cnt[indx] > 1)) { + item._cycle = cycle[indx]++; + item._keyname = item._name; + item._name = item._keyname + ';' + item._cycle; + } } } - const res = this.options; - let d = new DrawOptions(opt), hopt = ''; + folder._childs.push(item); + } + return true; +} - PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); - if (d.check('XAXIS_', true)) hopt += ';XAXIS_' + d.part; - if (d.check('YAXIS_', true)) hopt += ';YAXIS_' + d.part; +/** @summary Create hierarchy of TKey lists in file or sub-directory + * @private */ +function keysHierarchy(folder, keys, file, dirname) { + if (keys === undefined) + return false; - if (d.empty()) { - res.original = has_main ? 'lp' : 'alp'; - d = new DrawOptions(res.original); - } + folder._childs = []; - if (d.check('NOOPT')) res.NoOpt = 1; + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; - if (d.check('POS3D_', true)) res.pos3d = d.partAsInt() - 0.5; + if (settings.OnlyLastCycle && (i > 0) && (key.fName === keys[i - 1].fName) && (key.fCycle < keys[i - 1].fCycle)) + continue; - if (d.check('PFC') && !res._pfc) - res._pfc = 2; - if (d.check('PLC') && !res._plc) - res._plc = 2; - if (d.check('PMC') && !res._pmc) - res._pmc = 2; + const item = { + _name: key.fName + ';' + key.fCycle, + _cycle: key.fCycle, + _kind: getKindForType(key.fClassName), + _title: key.fTitle + ` (size: ${getSizeStr(key.fObjlen)})`, + _keyname: key.fName, + _readobj: null, + _parent: folder + }; - if (d.check('A')) res.Axis = d.check('I') ? 'A;' : ' '; // I means invisible axis - if (d.check('X+')) { res.Axis += 'X+'; res.second_x = has_main; } - if (d.check('Y+')) { res.Axis += 'Y+'; res.second_y = has_main; } - if (d.check('RX')) res.Axis += 'RX'; - if (d.check('RY')) res.Axis += 'RY'; + if (key.fRealName) + item._realname = key.fRealName + ';' + key.fCycle; - if (is_gme) { - res.blocks = []; - res.skip_errors_x0 = res.skip_errors_y0 = false; - if (d.check('X0')) res.skip_errors_x0 = true; - if (d.check('Y0')) res.skip_errors_y0 = true; + if (key.fClassName === clTDirectory || key.fClassName === clTDirectoryFile) { + const dir = (dirname && file) ? file.getDir(dirname + key.fName) : null; + if (dir) { + // remove cycle number - we have already directory + item._name = key.fName; + keysHierarchy(item, dir.fKeys, file, dirname + key.fName + '/'); + } else { + item._more = true; + item._expand = function(node, obj) { + // one can get expand call from child objects - ignore them + return keysHierarchy(node, obj.fKeys); + }; + } + } else if ((key.fClassName === clTList) && (key.fName === nameStreamerInfo)) { + if (settings.SkipStreamerInfos) + continue; + item._name = nameStreamerInfo; + item._kind = getKindForType(clTStreamerInfoList); + item._title = 'List of streamer infos for binary I/O'; + item._readobj = file.fStreamerInfos; } - decodeBlock(d, res); - - if (is_gme) - if (d.check('S')) res.individual_styles = true; + folder._childs.push(item); + } + return true; +} - // if (d.check('E')) res.Errors = 1; // E option only defined for TGraphPolar +/** @summary Create hierarchy for arbitrary object + * @private */ +function objectHierarchy(top, obj, args = undefined) { + if (!top || (obj === null)) + return false; - if (res.Errors === undefined) - res.Errors = this.has_errors && (!is_gme || !blocks_gme.length) ? 1 : 0; + top._childs = []; - // special case - one could use svg:path to draw many pixels ( - if ((res.Mark === 1) && (graph.fMarkerStyle === 1)) res.Mark = 101; + let proto = Object.prototype.toString.apply(obj); - // if no drawing option is selected and if opt === '' nothing is done. - if (res.Line + res.Fill + res.Curve + res.Mark + res.Bar + res.EF + res.Rect + res.Errors === 0) - if (d.empty()) res.Line = 1; + if (proto === '[object DataView]') { + let item = { + _parent: top, + _name: 'size', + _value: obj.byteLength.toString(), + _vclass: cssValueNum + }; + top._childs.push(item); + const namelen = (obj.byteLength < 10) ? 1 : Math.log10(obj.byteLength); - if (this.matchObjectType(clTGraphErrors)) { - const len = graph.fEX.length; - let m = 0; - for (let k = 0; k < len; ++k) - m = Math.max(m, graph.fEX[k], graph.fEY[k]); - if (m < 1e-100) - res.Errors = 0; - } + for (let k = 0; k < obj.byteLength; ++k) { + if (k % 16 === 0) { + item = { + _parent: top, + _name: k.toString(), + _value: '', + _vclass: cssValueNum + }; + while (item._name.length < namelen) + item._name = '0' + item._name; + top._childs.push(item); + } - this._cutg = this.matchObjectType(clTCutG); - this._cutg_lastsame = this._cutg && (graph.fNpoints > 3) && - (graph.fX[0] === graph.fX[graph.fNpoints-1]) && (graph.fY[0] === graph.fY[graph.fNpoints-1]); + let val = obj.getUint8(k).toString(16); + while (val.length < 2) + val = '0' + val; + if (item._value) + item._value += (k % 4 === 0) ? ' | ' : ' '; - if (!res.Axis) { - // check if axis should be drawn - // either graph drawn directly or - // graph is first object in list of primitives - const pad = this.getPadPainter()?.getRootPad(true); - if (!pad || (pad?.fPrimitives?.arr[0] === this.getObject())) res.Axis = ' '; + item._value += val; } + return true; + } - res.Axis += hopt; - - for (let bl = 0; bl < blocks_gme.length; ++bl) { - const subd = new DrawOptions(blocks_gme[bl]), subres = {}; - decodeBlock(subd, subres); - subres.skip_errors_x0 = res.skip_errors_x0; - subres.skip_errors_y0 = res.skip_errors_y0; - res.blocks.push(subres); + // check _nosimple property in all parents + let nosimple = true, do_context = false, prnt = top; + while (prnt) { + if (prnt._do_context) + do_context = true; + if ('_nosimple' in prnt) { + nosimple = prnt._nosimple; + break; } + prnt = prnt._parent; } - /** @summary Extract errors for TGraphMultiErrors */ - extractGmeErrors(nblock) { - if (!this.bins) return; - const gr = this.getGraph(); - this.bins.forEach(bin => { - bin.eylow = gr.fEyL[nblock][bin.indx]; - bin.eyhigh = gr.fEyH[nblock][bin.indx]; - }); + const isarray = (isArrayProto(proto) > 0) && obj.length, + compress = isarray && (obj.length > settings.HierarchyLimit); + let arrcompress = false; + + if (isarray && (top._name === 'Object') && !top._parent) + top._name = 'Array'; + + if (compress) { + arrcompress = true; + for (let k = 0; k < obj.length; ++k) { + const typ = typeof obj[k]; + if ((typ === 'number') || (typ === 'boolean') || ((typ === 'string') && (obj[k].length < 16))) + continue; + arrcompress = false; break; + } } - /** @summary Create bins for TF1 drawing */ - createBins() { - const gr = this.getGraph(); - if (!gr) return; + if (!('_obj' in top)) + top._obj = obj; + else if (top._obj !== obj) + alert('object missmatch'); - let kind = 0, npoints = gr.fNpoints; - if (this._cutg && this._cutg_lastsame) - npoints--; + if (!top._title) { + if (obj._typename) + top._title = getKindForType(obj._typename); + else if (isarray) + top._title = 'Array len: ' + obj.length; + } - if (gr._typename === clTGraphErrors) - kind = 1; - else if (gr._typename === clTGraphMultiErrors) - kind = 2; - else if (gr._typename === clTGraphAsymmErrors || gr._typename === clTGraphBentErrors || gr._typename.match(/^RooHist/)) - kind = 3; + if (arrcompress) { + for (let k = 0; k < obj.length;) { + let nextk = Math.min(k + 10, obj.length), allsame = true, prevk = k; - this.bins = new Array(npoints); + while (allsame) { + allsame = true; + for (let d = prevk; d < nextk; ++d) { + if (obj[k] !== obj[d]) + allsame = false; + } - for (let p = 0; p < npoints; ++p) { - const bin = this.bins[p] = { x: gr.fX[p], y: gr.fY[p], indx: p }; - switch (kind) { - case 1: - bin.exlow = bin.exhigh = gr.fEX[p]; - bin.eylow = bin.eyhigh = gr.fEY[p]; - break; - case 2: - bin.exlow = gr.fExL[p]; - bin.exhigh = gr.fExH[p]; - bin.eylow = gr.fEyL[0][p]; - bin.eyhigh = gr.fEyH[0][p]; - break; - case 3: - bin.exlow = gr.fEXlow[p]; - bin.exhigh = gr.fEXhigh[p]; - bin.eylow = gr.fEYlow[p]; - bin.eyhigh = gr.fEYhigh[p]; + if (allsame) { + if (nextk === obj.length) + break; + prevk = nextk; + nextk = Math.min(nextk + 10, obj.length); + } else if (prevk !== k) { + // last block with similar + nextk = prevk; + allsame = true; break; + } } - if (p === 0) { - this.xmin = this.xmax = bin.x; - this.ymin = this.ymax = bin.y; - } + const item = { _parent: top, _name: k + '..' + (nextk - 1), _vclass: cssValueNum }; - if (kind > 0) { - this.xmin = Math.min(this.xmin, bin.x - bin.exlow, bin.x + bin.exhigh); - this.xmax = Math.max(this.xmax, bin.x - bin.exlow, bin.x + bin.exhigh); - this.ymin = Math.min(this.ymin, bin.y - bin.eylow, bin.y + bin.eyhigh); - this.ymax = Math.max(this.ymax, bin.y - bin.eylow, bin.y + bin.eyhigh); - } else { - this.xmin = Math.min(this.xmin, bin.x); - this.xmax = Math.max(this.xmax, bin.x); - this.ymin = Math.min(this.ymin, bin.y); - this.ymax = Math.max(this.ymax, bin.y); + if (allsame) + item._value = obj[k].toString(); + else { + item._value = ''; + for (let d = k; d < nextk; ++d) + item._value += ((d === k) ? '[ ' : ', ') + obj[d].toString(); + item._value += ' ]'; } + + top._childs.push(item); + + k = nextk; } + return true; } - /** @summary Return margins for histogram ranges */ - getHistRangeMargin() { return 0.1; } + let lastitem, lastkey, lastfield, cnt; - /** @summary Create histogram for graph - * @desc graph bins should be created when calling this function - * @param {boolean} [set_x] - set X axis range - * @param {boolean} [set_y] - set Y axis range */ - createHistogram(set_x, set_y) { - if (!set_x && !set_y) - set_x = set_y = true; + for (const key in obj) { + if ((key === '_typename') || (key[0] === '$')) + continue; + const fld = obj[key]; + if (isFunc(fld)) + continue; + if (args?.exclude && (args.exclude.indexOf(key) >= 0)) + continue; - const graph = this.getGraph(), - xmin = this.xmin, - margin = this.getHistRangeMargin(); - let xmax = this.xmax, ymin = this.ymin, ymax = this.ymax; + if (compress && lastitem) { + if (lastfield === fld) { + ++cnt; + lastkey = key; + continue; + } + if (cnt > 0) + lastitem._name += '..' + lastkey; + } - if (xmin >= xmax) xmax = xmin + 1; - if (ymin >= ymax) ymax = ymin + 1; - const dx = (xmax - xmin) * margin, dy = (ymax - ymin) * margin; - let uxmin = xmin - dx, uxmax = xmax + dx, - minimum = ymin - dy, maximum = ymax + dy; + const item = { _parent: top, _name: key }; - if (!this._not_adjust_hrange) { - const pad_logx = this.getPadPainter()?.getPadLog('x'); + if (compress) { + lastitem = item; + lastkey = key; + lastfield = fld; + cnt = 0; + } - if ((uxmin < 0) && (xmin >= 0)) - uxmin = pad_logx ? xmin * (1 - margin) : 0; - if ((uxmax > 0) && (xmax <= 0)) - uxmax = pad_logx ? (1 + margin) * xmax : 0; + if (fld === null) { + item._value = item._title = 'null'; + if (!nosimple) + top._childs.push(item); + continue; } - const minimum0 = minimum, maximum0 = maximum; - let histo = this.getHistogram(); + let simple = false; - if (!histo) { - histo = this._need_2dhist ? createHistogram(clTH2I, 30, 30) : createHistogram(clTH1I, 100); - histo.fName = graph.fName + '_h'; - histo.fBits |= kNoStats; - this._own_histogram = true; - this.setHistogram(histo); - } else if ((histo.fMaximum !== kNoZoom) && (histo.fMinimum !== kNoZoom)) { - minimum = histo.fMinimum; - maximum = histo.fMaximum; - } + if (isObject(fld)) { + proto = Object.prototype.toString.apply(fld); + + if (isArrayProto(proto) > 0) { + item._title = 'array len=' + fld.length; + simple = (proto !== '[object Array]'); + if (!fld.length) { + item._value = '[ ]'; + item._more = false; // hpainter will not try to expand again + } else { + item._value = '[...]'; + item._more = true; + item._expand = objectHierarchy; + item._obj = fld; + } + } else if (proto === '[object DataView]') { + item._title = 'DataView len=' + fld.byteLength; + item._value = '[...]'; + item._more = true; + item._expand = objectHierarchy; + item._obj = fld; + } else if (proto === '[object Date]') { + item._more = false; + item._title = 'Date'; + item._value = fld.toString(); + item._vclass = cssValueNum; + } else { + if (fld.$kind || fld._typename) + item._kind = item._title = getKindForType(fld.$kind || fld._typename); - if (graph.fMinimum !== kNoZoom) minimum = ymin = graph.fMinimum; - if (graph.fMaximum !== kNoZoom) maximum = graph.fMaximum; - if ((minimum < 0) && (ymin >= 0)) minimum = (1 - margin)*ymin; + if (fld._typename) { + item._title = fld._typename; + if (do_context && canDrawHandle(fld._typename)) + item._direct_context = true; + } - setHistogramTitle(histo, this.getObject().fTitle); + // check if object already shown in hierarchy (circular dependency) + let curr = top, inparent = false; + while (curr && !inparent) { + inparent = (curr._obj === fld); + curr = curr._parent; + } - if (set_x && !histo.fXaxis.fLabels) { - histo.fXaxis.fXmin = uxmin; - histo.fXaxis.fXmax = uxmax; - } + if (inparent) { + item._value = '{ prnt }'; + item._vclass = cssValueNum; + item._more = false; + simple = true; + } else { + item._obj = fld; + item._more = false; - if (set_y && !histo.fYaxis.fLabels) { - histo.fYaxis.fXmin = Math.min(minimum0, minimum); - histo.fYaxis.fXmax = Math.max(maximum0, maximum); - histo.fMinimum = minimum; - histo.fMaximum = maximum; + switch (fld._typename) { + case clTColor: + item._value = getRGBfromTColor(fld); + break; + case clTText: + case clTLatex: + item._value = fld.fTitle; + break; + case clTObjString: + item._value = fld.fString; + break; + default: + if (isRootCollection(fld) && isObject(fld.arr)) { + item._value = fld.arr.length ? '[...]' : '[]'; + item._title += ', size:' + fld.arr.length; + if (fld.arr.length) + item._more = true; + } else { + item._more = true; + item._value = '{ }'; + } + } + } + } + } else if ((typeof fld === 'number') || (typeof fld === 'boolean') || (typeof fld === 'bigint')) { + simple = true; + if (key === 'fBits') + item._value = '0x' + fld.toString(16); + else + item._value = fld.toString(); + item._vclass = cssValueNum; + } else if (isStr(fld)) { + simple = true; + item._value = '"' + fld + '"'; + item._vclass = 'h_value_str'; + } else if (typeof fld === 'undefined') { + simple = true; + item._value = 'undefined'; + item._vclass = cssValueNum; + } else { + simple = true; + alert(`miss ${key} type ${typeof fld}`); } - return histo; + if (!simple || !nosimple) + top._childs.push(item); } - /** @summary Check if user range can be unzommed - * @desc Used when graph points covers larger range than provided histogram */ - unzoomUserRange(dox, doy /*, doz */) { - const graph = this.getGraph(); - if (this._own_histogram || !graph) return false; + if (compress && lastitem && (cnt > 0)) + lastitem._name += '..' + lastkey; - const histo = this.getHistogram(); + return true; +} - dox = dox && histo && ((histo.fXaxis.fXmin > this.xmin) || (histo.fXaxis.fXmax < this.xmax)); - doy = doy && histo && ((histo.fYaxis.fXmin > this.ymin) || (histo.fYaxis.fXmax < this.ymax)); - if (!dox && !doy) return false; +/** @summary Create hierarchy elements for TTask object + * @desc function can be used for different derived classes + * we show not only child tasks, but all complex data members + * @private */ +function taskHierarchy(item, obj) { + if (!obj?.fTasks) + return false; - this.createHistogram(dox, doy); - this.getMainPainter()?.extractAxesProperties(1); // just to enforce ranges extraction + objectHierarchy(item, obj, { exclude: ['fTasks', 'fName'] }); + if (!obj.fTasks.arr.length && !item._childs.length) { + item._more = false; return true; } - /** @summary Returns true if graph drawing can be optimize */ - canOptimize() { - return (settings.OptimizeDraw > 0) && !this.options.NoOpt; + for (let i = 0; i < obj.fTasks.arr.length; ++i) { + const chld = obj.fTasks.arr[i]; + item._childs.push({ + _name: chld.fName, + _kind: getKindForType(chld._typename), + _obj: chld + }); } - /** @summary Returns optimized bins - if optimization enabled */ - optimizeBins(maxpnt, filter_func) { - if ((this.bins.length < 30) && !filter_func) - return this.bins; + return true; +} - let selbins = null; - if (isFunc(filter_func)) { - for (let n = 0; n < this.bins.length; ++n) { - if (filter_func(this.bins[n], n)) { - if (!selbins) selbins = (n === 0) ? [] : this.bins.slice(0, n); - } else - if (selbins) selbins.push(this.bins[n]); - } - } - if (!selbins) selbins = this.bins; +/** @summary Create hierarchy for streamer info object + * @private */ +function createStreamerInfoContent(lst) { + const h = { _name: nameStreamerInfo, _childs: [] }; - if (!maxpnt) maxpnt = 500000; + for (let i = 0; i < lst.arr.length; ++i) { + const entry = lst.arr[i]; - if ((selbins.length < maxpnt) || !this.canOptimize()) return selbins; - let step = Math.floor(selbins.length / maxpnt); - if (step < 2) step = 2; - const optbins = []; - for (let n = 0; n < selbins.length; n+=step) - optbins.push(selbins[n]); + if (entry._typename === clTList) + continue; - return optbins; - } + if (typeof entry.fName === 'undefined') { + console.warn(`strange element in StreamerInfo with type ${entry._typename}`); + continue; + } - /** @summary Check if such function should be drawn directly */ - needDrawFunc(graph, func) { - if (func._typename === clTPaveStats) - return (func.fName !== 'stats') || !graph.TestBit(kNoStats); // kNoStats is same for graph and histogram + const item = { + _name: `${entry.fName};${entry.fClassVersion}`, + _kind: `class ${entry.fName}`, + _title: `class:${entry.fName} version:${entry.fClassVersion} checksum:${entry.fCheckSum}`, + _icon: 'img_class', + _childs: [] + }; + + if (entry.fTitle) + item._title += ' ' + entry.fTitle; + + h._childs.push(item); - if ((func._typename === clTF1) || (func._typename === clTF2)) - return !func.TestBit(BIT(9)); // TF1::kNotDraw + if (typeof entry.fElements === 'undefined') + continue; + for (let l = 0; l < entry.fElements.arr.length; ++l) { + const elem = entry.fElements.arr[l]; + if (!elem?.fName) + continue; + let _name = `${elem.fTypeName} ${elem.fName}`; + const _title = `${elem.fTypeName} type:${elem.fType}`; + if (elem.fArrayDim === 1) + _name += `[${elem.fArrayLength}]`; + else { + for (let dim = 0; dim < elem.fArrayDim; ++dim) + _name += `[${elem.fMaxIndex[dim]}]`; + } + if (elem.fBaseVersion === 4294967295) + _name += ':-1'; + else if (elem.fBaseVersion !== undefined) + _name += `:${elem.fBaseVersion}`; + _name += ';'; + if (elem.fTitle) + _name += ` // ${elem.fTitle}`; - return true; + item._childs.push({ _name, _title, _kind: elem.fTypeName, _icon: (elem.fTypeName === kBaseClass) ? 'img_class' : 'img_member' }); + } + if (!item._childs.length) + delete item._childs; } - /** @summary Returns tooltip for specified bin */ - getTooltips(d) { - const pmain = this.get_main(), lines = [], - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - gme = this.get_gme(); + return h; +} - lines.push(this.getObjectHint()); +/** @summary tag item in hierarchy painter as streamer info + * @desc this function used on THttpServer to mark streamer infos list + * as fictional TStreamerInfoList class, which has special draw function + * @private */ +function markAsStreamerInfo(h, item, obj) { + if (obj?._typename === clTList) + obj._typename = clTStreamerInfoList; +} - if (d && funcs) { - if (d.indx !== undefined) - lines.push('p = ' + d.indx); - lines.push('x = ' + funcs.axisAsText('x', d.x), 'y = ' + funcs.axisAsText('y', d.y)); - if (gme) - lines.push('error x = -' + funcs.axisAsText('x', gme.fExL[d.indx]) + '/+' + funcs.axisAsText('x', gme.fExH[d.indx])); - else if (this.options.Errors && (funcs.x_handle.kind === kAxisNormal) && (d.exlow || d.exhigh)) - lines.push('error x = -' + funcs.axisAsText('x', d.exlow) + '/+' + funcs.axisAsText('x', d.exhigh)); - if (gme) { - for (let ny = 0; ny < gme.fNYErrors; ++ny) - lines.push(`error y${ny} = -${funcs.axisAsText('y', gme.fEyL[ny][d.indx])}/+${funcs.axisAsText('y', gme.fEyH[ny][d.indx])}`); - } else if ((this.options.Errors || (this.options.EF > 0)) && (funcs.y_handle.kind === kAxisNormal) && (d.eylow || d.eyhigh)) - lines.push('error y = -' + funcs.axisAsText('y', d.eylow) + '/+' + funcs.axisAsText('y', d.eyhigh)); - } - return lines; - } +/** @summary Create hierarchy for object inspector + * @private */ +function createInspectorContent(obj) { + const h = { _name: 'Object', _title: '', _click_action: kExpand, _nosimple: false, _do_context: true }; - /** @summary Provide frame painter for graph - * @desc If not exists, emulate its behaviour */ - get_main() { - let pmain = this.getFramePainter(); + if (isStr(obj.fName) && obj.fName) + h._name = obj.fName; - if (pmain?.grx && pmain?.gry) return pmain; + if (isStr(obj.fTitle) && obj.fTitle) + h._title = obj.fTitle; - // FIXME: check if needed, can be removed easily - const pp = this.getPadPainter(), - rect = pp?.getPadRect() || { width: 800, height: 600 }; + if (obj._typename) + h._title += ` type:${obj._typename}`; - pmain = { - pad_layer: true, - pad: pp?.getRootPad(true) ?? create$1(clTPad), - pw: rect.width, - ph: rect.height, - fX1NDC: 0.1, fX2NDC: 0.9, fY1NDC: 0.1, fY2NDC: 0.9, - getFrameWidth() { return this.pw; }, - getFrameHeight() { return this.ph; }, - grx(value) { - if (this.pad.fLogx) - value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; - else - value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); - return value * this.pw; - }, - gry(value) { - if (this.pad.fLogv ?? this.pad.fLogy) - value = (value > 0) ? Math.log10(value) : this.pad.fUymin; - else - value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); - return (1 - value) * this.ph; - }, - revertAxis(name, v) { - if (name === 'x') - return v / this.pw * (this.pad.fX2 - this.pad.fX1) + this.pad.fX1; - if (name === 'y') - return (1 - v / this.ph) * (this.pad.fY2 - this.pad.fY1) + this.pad.fY1; - return v; - }, - getGrFuncs() { return this; } - }; + if (isRootCollection(obj)) { + h._name = obj.name || obj._typename; + listHierarchy(h, obj); + } else + objectHierarchy(h, obj); - return pmain.pad ? pmain : null; - } + return h; +} - /** @summary append exclusion area to created path */ - appendExclusion(is_curve, path, drawbins, excl_width) { - const extrabins = []; - for (let n = drawbins.length-1; n >= 0; --n) { - const bin = drawbins[n], - dlen = Math.sqrt(bin.dgrx**2 + bin.dgry**2); - if (dlen > 1e-10) { - // shift point - bin.grx += excl_width*bin.dgry/dlen; - bin.gry -= excl_width*bin.dgrx/dlen; - } - extrabins.push(bin); - } - const path2 = buildSvgCurve(extrabins, { cmd: 'L', line: !is_curve }); +/** @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) { + const res = []; - this.draw_g.append('svg:path') - .attr('d', path + path2 + 'Z') - .call(this.fillatt.func) - .style('opacity', 0.75); - } + if (!isStr(val)) + return res; - /** @summary draw TGraph bins with specified options - * @desc Can be called several times */ - drawBins(funcs, options, draw_g, w, h, lineatt, fillatt, main_block) { - const graph = this.getGraph(); - if (!graph?.fNpoints) return; + val = val.trim(); + if (!val) + return res; - let excl_width = 0, drawbins = null; + // return as array with single element + if ((val.length < 2) || (val.at(0) !== '[') || (val.at(-1) !== ']')) { + res.push(val); + return res; + } - if (main_block && lineatt.excl_side) { - excl_width = lineatt.excl_width; - if ((lineatt.width > 0) && !options.Line && !options.Curve) options.Line = 1; + // try to split ourself, checking quotes and brackets + let nbr = 0, nquotes = 0, ndouble = 0, last = 1; + + for (let 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; + } + // eslint-disable-next-line no-fallthrough + case ',': + if (nbr === 0) { + let sub = val.substring(last, indx).trim(); + if ((sub.length > 1) && (sub.at(0) === sub.at(-1)) && ((sub[0] === '"') || (sub[0] === '\''))) + sub = sub.slice(1, sub.length - 1); + res.push(sub); + last = indx + 1; + } + break; } + } - if (options.EF) { - drawbins = this.optimizeBins((options.EF > 1) ? 20000 : 0); + if (!res.length) + res.push(val.slice(1, val.length - 1).trim()); - // build lower part - for (let n = 0; n < drawbins.length; ++n) { - const bin = drawbins[n]; - bin.grx = funcs.grx(bin.x); - bin.gry = funcs.gry(bin.y - bin.eylow); - } + return res; +} - const path1 = buildSvgCurve(drawbins, { line: options.EF < 2, qubic: true }), - bins2 = []; - for (let n = drawbins.length-1; n >= 0; --n) { - const bin = drawbins[n]; - bin.gry = funcs.gry(bin.y + bin.eyhigh); - bins2.push(bin); - } +/** @summary central function for expand of all online items + * @private */ +function onlineHierarchy(node, obj) { + if (node && obj?._childs) { + for (let n = 0; n < obj._childs.length; ++n) { + if (obj._childs[n]._more || obj._childs[n]._childs) + obj._childs[n]._expand = onlineHierarchy; + } - // build upper part (in reverse direction) - const path2 = buildSvgCurve(bins2, { line: options.EF < 2, cmd: 'L', qubic: true }), - area = draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(fillatt.func); + node._childs = obj._childs; + obj._childs = null; + return true; + } - // Let behaves as ROOT - see JIRA ROOT-8131 - if (fillatt.empty() && fillatt.colorindx) - area.style('stroke', this.getColor(fillatt.colorindx)); - if (main_block) - this.draw_kind = 'lines'; - } + return false; +} - if (options.Line || options.Fill) { - let close_symbol = ''; - if (this._cutg) { - close_symbol = 'Z'; - if (!options.original) options.Fill = 1; - } +/** @summary Check if draw handle for specified object can do expand + * @private */ +function canExpandHandle(handle) { + return handle?.expand || handle?.get_expand || handle?.expand_item; +} - if (options.Fill) { - close_symbol = 'Z'; // always close area if we want to fill it - excl_width = 0; - } +const kindTFile = getKindForType(clTFile); - if (!drawbins) drawbins = this.optimizeBins(0); +/** + * @summary Painter of hierarchical structures + * + * @example + * import { HierarchyPainter } from 'https://root.cern/js/latest/modules/gui/HierarchyPainter.mjs'; + * // create hierarchy painter in 'myTreeDiv' + * let h = new HierarchyPainter('example', 'myTreeDiv'); + * // configure 'simple' layout in 'myMainDiv' + * // one also can specify 'grid2x2' or 'flex' or 'tabs' + * h.setDisplay('simple', 'myMainDiv'); + * // open file and display element + * h.openRootFile('https://root.cern/js/files/hsimple.root').then(() => h.display('hpxpy;1','colz')); */ - for (let n = 0; n < drawbins.length; ++n) { - const bin = drawbins[n]; - bin.grx = funcs.grx(bin.x); - bin.gry = funcs.gry(bin.y); - } +class HierarchyPainter extends BasePainter { - const path = buildSvgCurve(drawbins, { line: true, calc: excl_width }); + #monitoring_interval; // monitoring time interval + #monitoring_on; // if monitoring enabled + #monitoring_handle; // timer handle for monitoring + #monitoring_frame; // animation frame for monitoring + #one_by_one; // process drop items one by one + #topname; // top item name + #cached_draw_object; // cached object for first draw - if (excl_width) - this.appendExclusion(false, path, drawbins, excl_width); + /** @summary Create painter + * @param {string} name - symbolic name + * @param {string} frameid - element id where hierarchy is drawn + * @param {string} [backgr] - background color */ + constructor(name, frameid, backgr) { + super(frameid); + this.name = name; + this.h = null; // hierarchy + this.with_icons = true; - const elem = draw_g.append('svg:path').attr('d', path + close_symbol); - if (options.Line) - elem.call(lineatt.func); + if (backgr === '__as_dark_mode__') + this.setBasicColors(); + else + this.background = backgr; + this.files_monitoring = !frameid; // by default files monitored when nobrowser option specified + this.nobrowser = (frameid === null); - if (options.Fill) - elem.call(fillatt.func); - else - elem.style('fill', 'none'); + // remember only very first instance + if (!getHPainter()) + setHPainter(this); + } - if (main_block) - this.draw_kind = 'lines'; - } + /** @summary Set basic colors + * @private */ + setBasicColors() { + this.background = settings.DarkMode ? 'black' : 'white'; + this.textcolor = settings.DarkMode ? '#eee' : '#111'; + } - if (options.Curve) { - let curvebins = drawbins; - if ((this.draw_kind !== 'lines') || !curvebins || ((options.Curve === 1) && (curvebins.length > 20000))) { - curvebins = this.optimizeBins((options.Curve === 1) ? 20000 : 0); - for (let n = 0; n < curvebins.length; ++n) { - const bin = curvebins[n]; - bin.grx = funcs.grx(bin.x); - bin.gry = funcs.gry(bin.y); - } - } + /** @summary Cleanup hierarchy painter + * @desc clear drawing and browser */ + cleanup() { + this.clearHierarchy(true); - const path = buildSvgCurve(curvebins, { qubic: !excl_width }); - if (excl_width) - this.appendExclusion(true, path, curvebins, excl_width); + super.cleanup(); - draw_g.append('svg:path') - .attr('d', path) - .call(lineatt.func) - .style('fill', 'none'); - if (main_block) - this.draw_kind = 'lines'; // handled same way as lines - } + if (getHPainter() === this) + setHPainter(null); + } - let nodes = null; + /** @summary Create file hierarchy + * @private */ + fileHierarchy(file, folder) { + const painter = this; + if (!folder) + folder = {}; - if (options.Errors || options.Rect || options.Bar) { - drawbins = this.optimizeBins(5000, (pnt, i) => { - const grx = funcs.grx(pnt.x); + folder._name = file.fFileName; + folder._title = (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}, version: ${getVersionStr(file.fVersion)}, modified: ${convertDate(getTDatime(file.fDatimeM))}`; + folder._kind = kindTFile; + folder._file = file; + folder._fullurl = file.fFullURL; + folder._localfile = file.fLocalFile; + folder._had_direct_read = false; + // this is central get method, item or itemname can be used, returns promise + folder._get = function(item, itemname) { + if (item?._readobj) + return Promise.resolve(item._readobj); + + if (item) + itemname = painter.itemFullName(item, this); + + const readFileObject = file2 => { + if (!this._file) + this._file = file2; + + if (!file2) + return Promise.resolve(null); + + return file2.readObject(itemname).then(obj => { + // if object was read even when item did not exist try to reconstruct new hierarchy + if (!item && obj) { + // first try to found last read directory + const d = painter.findItem({ name: itemname, top: this, last_exists: true, check_keys: true }); + if ((d?.last !== undefined) && (d.last !== this)) { + // reconstruct only sub-directory hierarchy + const dir = file2.getDir(painter.itemFullName(d.last, this)); + if (dir) { + d.last._name = d.last._keyname; + const dirname = painter.itemFullName(d.last, this); + keysHierarchy(d.last, dir.fKeys, file2, dirname + '/'); + } + } else { + // reconstruct full file hierarchy + keysHierarchy(this, file2.fKeys, file2, ''); + } + item = painter.findItem({ name: itemname, top: this }); + } - // when drawing bars, take all points - if (!options.Bar && ((grx < 0) || (grx > w))) return true; + if (item) { + item._readobj = obj; + // remove cycle number for objects supporting expand + if ('_expand' in item) + item._name = item._keyname; + } - const gry = funcs.gry(pnt.y); + return obj; + }); + }; - if (!options.Bar && !options.OutRange && ((gry < 0) || (gry > h))) return true; + if (this._file) + return readFileObject(this._file); + if (this._localfile) + return openFile(this._localfile).then(f => readFileObject(f)); + if (this._fullurl) + return openFile(this._fullurl).then(f => readFileObject(f)); + return Promise.resolve(null); + }; - pnt.grx1 = Math.round(grx); - pnt.gry1 = Math.round(gry); + keysHierarchy(folder, file.fKeys, file, ''); - if (this.has_errors) { - pnt.grx0 = Math.round(funcs.grx(pnt.x - options.ScaleErrX*pnt.exlow) - grx); - pnt.grx2 = Math.round(funcs.grx(pnt.x + options.ScaleErrX*pnt.exhigh) - grx); - pnt.gry0 = Math.round(funcs.gry(pnt.y - pnt.eylow) - gry); - pnt.gry2 = Math.round(funcs.gry(pnt.y + pnt.eyhigh) - gry); + return folder; + } - if (this.is_bent) { - pnt.grdx0 = Math.round(funcs.gry(pnt.y + graph.fEXlowd[i]) - gry); - pnt.grdx2 = Math.round(funcs.gry(pnt.y + graph.fEXhighd[i]) - gry); - pnt.grdy0 = Math.round(funcs.grx(pnt.x + graph.fEYlowd[i]) - grx); - pnt.grdy2 = Math.round(funcs.grx(pnt.x + graph.fEYhighd[i]) - grx); - } else - pnt.grdx0 = pnt.grdx2 = pnt.grdy0 = pnt.grdy2 = 0; - } + /** @summary Iterate over all items in hierarchy + * @param {function} func - function called for every item + * @param {object} [top] - top item to start from + * @private */ + forEachItem(func, top) { + function each_item(item, prnt) { + if (!item) + return; + if (prnt) + item._parent = prnt; + func(item); + const len = item._childs?.length ?? 0; + for (let n = 0; n < len; ++n) + each_item(item._childs[n], item); + } - return false; - }); + if (isFunc(func)) + each_item(top || this.h); + } - if (main_block) - this.draw_kind = 'nodes'; + /** @summary Search item in the hierarchy + * @param {object|string} arg - item name or object with arguments + * @param {string} arg.name - item to search + * @param {boolean} [arg.force] - specified elements will be created when not exists + * @param {boolean} [arg.last_exists] - when specified last parent element will be returned + * @param {boolean} [arg.check_keys] - check TFile keys with cycle suffix + * @param {boolean} [arg.allow_index] - let use sub-item indexes instead of name + * @param {object} [arg.top] - element to start search from + * @private */ + findItem(arg) { + function find_in_hierarchy(top, fullname) { + if (!fullname || !top) + return top; - nodes = draw_g.selectAll('.grpoint') - .data(drawbins) - .enter() - .append('svg:g') - .attr('class', 'grpoint') - .attr('transform', d => makeTranslate(d.grx1, d.gry1)); - } + let pos = fullname.length; - if (options.Bar) { - // calculate bar width + if (!top._parent && (top._kind !== kTopFolder) && (fullname.indexOf(top._name) === 0)) { + // it is allowed to provide item name, which includes top-parent like file.root/folder/item + // but one could skip top-item name, if there are no other items + if (fullname === top._name) + return top; - let xmin = 0, xmax = 0; - for (let i = 0; i < drawbins.length; ++i) { - if (i === 0) - xmin = xmax = drawbins[i].grx1; - else { - xmin = Math.min(xmin, drawbins[i].grx1); - xmax = Math.max(xmax, drawbins[i].grx1); + const len = top._name.length; + if (fullname[len] === '/') { + fullname = fullname.slice(len + 1); + pos = fullname.length; } } - if (drawbins.length === 1) - drawbins[0].width = w/4; // pathologic case of single bin - else { - for (let i = 0; i < drawbins.length; ++i) - drawbins[i].width = (xmax - xmin) / drawbins.length * gStyle.fBarWidth; - } - - const yy0 = Math.round(funcs.gry(0)); - let usefill = fillatt; + function process_child(child, ignore_prnt) { + // set parent pointer when searching child + if (!ignore_prnt) + child._parent = top; - if (main_block) { - const fp = this.getFramePainter(), - fpcol = !fp?.fillatt?.empty() ? fp.fillatt.getFillColor() : -1; + if ((pos >= fullname.length - 1) || (pos < 0)) + return child; - if (fpcol === fillatt.getFillColor()) - usefill = this.createAttFill({ color: fpcol === 'white' ? kBlack : kWhite, pattern: 1001, std: false }); + return find_in_hierarchy(child, fullname.slice(pos + 1)); } - nodes.append('svg:path') - .attr('d', d => { - d.bar = true; // element drawn as bar - const dx = d.width > 1 ? Math.round(-d.width/2) : 0, - dw = d.width > 1 ? Math.round(d.width) : 1, - dy = (options.Bar !== 1) ? 0 : ((d.gry1 > yy0) ? yy0-d.gry1 : 0), - dh = (options.Bar !== 1) ? (h > d.gry1 ? h - d.gry1 : 0) : Math.abs(yy0 - d.gry1); - return `M${dx},${dy}h${dw}v${dh}h${-dw}z`; - }) - .call(usefill.func); - } - - if (options.Rect) { - nodes.filter(d => (d.exlow > 0) && (d.exhigh > 0) && (d.eylow > 0) && (d.eyhigh > 0)) - .append('svg:path') - .attr('d', d => { - d.rect = true; - return `M${d.grx0},${d.gry0}H${d.grx2}V${d.gry2}H${d.grx0}Z`; - }) - .call(fillatt.func) - .call(options.Rect === 2 ? lineatt.func : () => {}); - } - - this.error_size = 0; + while (pos > 0) { + // we try to find element with slashes inside - start from full name + let localname = (pos >= fullname.length) ? fullname : fullname.slice(0, pos); - if (options.Errors) { - // to show end of error markers, use line width attribute - let lw = lineatt.width + gStyle.fEndErrorSize, bb = 0; - const vv = options.Ends ? `m0,${lw}v${-2*lw}` : '', - hh = options.Ends ? `m${lw},0h${-2*lw}` : ''; - let vleft = vv, vright = vv, htop = hh, hbottom = hh; + if (top._childs) { + // first try to find direct matched item + for (let i = 0; i < top._childs.length; ++i) { + if (top._childs[i]._name === localname) + return process_child(top._childs[i]); + } - const mainLine = (dx, dy) => { - if (!options.MainError) return `M${dx},${dy}`; - const res = 'M0,0'; - if (dx) return res + (dy ? `L${dx},${dy}` : `H${dx}`); - return dy ? res + `V${dy}` : res; - }; + // if first child online, check its elements + if ((top._kind === kTopFolder) && (top._childs[0]._online !== undefined)) { + for (let i = 0; i < top._childs[0]._childs.length; ++i) { + if (top._childs[0]._childs[i]._name === localname) + return process_child(top._childs[0]._childs[i], true); + } + } - switch (options.Ends) { - case 2: // option [] - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `m${bb},${lw}h${-bb}v${-2*lw}h${bb}`; - vright = `m${-bb},${lw}h${bb}v${-2*lw}h${-bb}`; - htop = `m${-lw},${bb}v${-bb}h${2*lw}v${bb}`; - hbottom = `m${-lw},${-bb}v${bb}h${2*lw}v${-bb}`; - break; - case 3: // option |> - lw = Math.max(lw, Math.round(graph.fMarkerSize*8*0.66)); - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `l${bb},${lw}v${-2*lw}l${-bb},${lw}`; - vright = `l${-bb},${lw}v${-2*lw}l${bb},${lw}`; - htop = `l${-lw},${bb}h${2*lw}l${-lw},${-bb}`; - hbottom = `l${-lw},${-bb}h${2*lw}l${-lw},${bb}`; - break; - case 4: // option > - lw = Math.max(lw, Math.round(graph.fMarkerSize*8*0.66)); - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `l${bb},${lw}m0,${-2*lw}l${-bb},${lw}`; - vright = `l${-bb},${lw}m0,${-2*lw}l${bb},${lw}`; - htop = `l${-lw},${bb}m${2*lw},0l${-lw},${-bb}`; - hbottom = `l${-lw},${-bb}m${2*lw},0l${-lw},${bb}`; - break; - } + // if allowed, try to found item with key + if (arg.check_keys) { + let newest = null; + for (let i = 0; i < top._childs.length; ++i) { + if (top._childs[i]._keyname === localname) { + if (!newest || (newest._cycle < top._childs[i]._cycle)) + newest = top._childs[i]; + } + } + if (newest) + return process_child(newest); + } - this.error_size = lw; + let allow_index = arg.allow_index; + if ((localname.at(0) === '[') && (localname.at(-1) === ']') && + /^\d+$/.test(localname.slice(1, localname.length - 1))) { + allow_index = true; + localname = localname.slice(1, localname.length - 1); + } - lw = Math.floor((lineatt.width-1)/2); // one should take into account half of end-cup line width + // when search for the elements it could be allowed to check index + if (allow_index && /^\d+$/.test(localname)) { + const indx = parseInt(localname); + if (Number.isInteger(indx) && (indx >= 0) && (indx < top._childs.length)) + return process_child(top._childs[indx]); + } + } - let visible = nodes.filter(d => (d.exlow > 0) || (d.exhigh > 0) || (d.eylow > 0) || (d.eyhigh > 0)); - if (options.skip_errors_x0 || options.skip_errors_y0) - visible = visible.filter(d => ((d.x !== 0) || !options.skip_errors_x0) && ((d.y !== 0) || !options.skip_errors_y0)); + pos = fullname.lastIndexOf('/', pos - 1); + } - if (!this.isBatchMode() && settings.Tooltip && main_block) { - visible.append('svg:path') - .style('fill', 'none') - .style('pointer-events', 'visibleFill') - .attr('d', d => `M${d.grx0},${d.gry0}h${d.grx2-d.grx0}v${d.gry2-d.gry0}h${d.grx0-d.grx2}z`); + if (arg.force) { + // if did not found element with given name we just generate it + if (top._childs === undefined) + top._childs = []; + pos = fullname.indexOf('/'); + const child = { _name: ((pos < 0) ? fullname : fullname.slice(0, pos)) }; + top._childs.push(child); + return process_child(child); } - visible.append('svg:path') - .call(lineatt.func) - .style('fill', 'none') - .attr('d', d => { - d.error = true; - return ((d.exlow > 0) ? mainLine(d.grx0+lw, d.grdx0) + vleft : '') + - ((d.exhigh > 0) ? mainLine(d.grx2-lw, d.grdx2) + vright : '') + - ((d.eylow > 0) ? mainLine(d.grdy0, d.gry0-lw) + hbottom : '') + - ((d.eyhigh > 0) ? mainLine(d.grdy2, d.gry2+lw) + htop : ''); - }); + return arg.last_exists ? { last: top, rest: fullname } : null; } - if (options.Mark) { - // for tooltips use markers only if nodes were not created - this.createAttMarker({ attr: graph, style: options.Mark - 100 }); + let top = this.h, itemname; + + if (isStr(arg)) { + itemname = arg; + arg = {}; + } else if (isObject(arg)) { + itemname = arg.name; + if ('top' in arg) + top = arg.top; + } else + return null; - this.marker_size = this.markeratt.getFullSize(); + if (itemname === '__top_folder__') + return top; - this.markeratt.resetPos(); + if (isStr(itemname) && (itemname.indexOf('img:') === 0)) + return null; - const want_tooltip = !this.isBatchMode() && settings.Tooltip && (!this.markeratt.fill || (this.marker_size < 7)) && !nodes && main_block, - hsz = Math.max(5, Math.round(this.marker_size*0.7)), - maxnummarker = 1000000 / (this.markeratt.getMarkerLength() + 7); // let produce SVG at maximum 1MB + return find_in_hierarchy(top, itemname); + } - let path = '', pnt, grx, gry, - hints_marker = '', step = 1; + /** @summary Produce full string name for item + * @param {Object} node - item element + * @param {Object} [uptoparent] - up to which parent to continue + * @param {boolean} [compact] - if specified, top parent is not included + * @return {string} produced name + * @private */ + itemFullName(node, uptoparent, compact) { + if (node?._kind === kTopFolder) + return '__top_folder__'; - if (!drawbins) - drawbins = this.optimizeBins(maxnummarker); - else if (this.canOptimize() && (drawbins.length > 1.5*maxnummarker)) - step = Math.min(2, Math.round(drawbins.length/maxnummarker)); + let res = ''; - for (let n = 0; n < drawbins.length; n += step) { - pnt = drawbins[n]; - grx = funcs.grx(pnt.x); - if ((grx > -this.marker_size) && (grx < w + this.marker_size)) { - gry = funcs.gry(pnt.y); - if ((gry > -this.marker_size) && (gry < h + this.marker_size)) { - path += this.markeratt.create(grx, gry); - if (want_tooltip) hints_marker += `M${grx-hsz},${gry-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; - } - } - } + while (node) { + // online items never includes top-level folder + if ((node._online !== undefined) && !uptoparent) + return res; - if (path) { - draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - if ((nodes === null) && (this.draw_kind === 'none') && main_block) - this.draw_kind = (options.Mark === 101) ? 'path' : 'mark'; - } - if (want_tooltip && hints_marker) { - draw_g.append('svg:path') - .attr('d', hints_marker) - .style('fill', 'none') - .style('pointer-events', 'visibleFill'); - } + if ((node === uptoparent) || (node._kind === kTopFolder)) + break; + if (compact && !node._parent) + break; // in compact form top-parent is not included + if (res) + res = '/' + res; + res = node._name + res; + node = node._parent; } - } - /** @summary append TGraphQQ part */ - appendQQ(funcs, graph) { - const xqmin = Math.max(funcs.scale_xmin, graph.fXq1), - xqmax = Math.min(funcs.scale_xmax, graph.fXq2), - yqmin = Math.max(funcs.scale_ymin, graph.fYq1), - yqmax = Math.min(funcs.scale_ymax, graph.fYq2), - makeLine = (x1, y1, x2, y2) => `M${funcs.grx(x1)},${funcs.gry(y1)}L${funcs.grx(x2)},${funcs.gry(y2)}`, - yxmin = (graph.fYq2 - graph.fYq1)*(funcs.scale_xmin-graph.fXq1)/(graph.fXq2-graph.fXq1) + graph.fYq1, - yxmax = (graph.fYq2-graph.fYq1)*(funcs.scale_xmax-graph.fXq1)/(graph.fXq2-graph.fXq1) + graph.fYq1; + return res; + } - let path2 = ''; - if (yxmin < funcs.scale_ymin) { - const xymin = (graph.fXq2 - graph.fXq1)*(funcs.scale_ymin-graph.fYq1)/(graph.fYq2-graph.fYq1) + graph.fXq1; - path2 = makeLine(xymin, funcs.scale_ymin, xqmin, yqmin); - } else - path2 = makeLine(funcs.scale_xmin, yxmin, xqmin, yqmin); + /** @summary Executes item marked as 'Command' + * @desc If command requires additional arguments, they could be specified as extra arguments arg1, arg2, ... + * @param {String} itemname - name of command item + * @param {Object} [elem] - HTML element for command execution + * @param [arg1] - first optional argument + * @param [arg2] - second optional argument and so on + * @return {Promise} with command result */ + async executeCommand(itemname, elem, ...userargs) { + const hitem = this.findItem(itemname), + url = this.getOnlineItemUrl(hitem) + '/cmd.json', + d3node = select(elem), + cmdargs = []; + for (let n = 0; n < (hitem._numargs ?? 0); ++n) + cmdargs.push(n < userargs.length ? userargs[n] : ''); - if (yxmax > funcs.scale_ymax) { - const xymax = (graph.fXq2-graph.fXq1)*(funcs.scale_ymax-graph.fYq1)/(graph.fYq2-graph.fYq1) + graph.fXq1; - path2 += makeLine(xqmax, yqmax, xymax, funcs.scale_ymax); - } else - path2 += makeLine(xqmax, yqmax, funcs.scale_xmax, yxmax); + const promise = !cmdargs.length || !elem + ? Promise.resolve(cmdargs) + : createMenu().then(menu => menu.showCommandArgsDialog(hitem._name, cmdargs)); + return promise.then(args => { + if (args === null) + return false; - const latt1 = this.createAttLine({ style: 1, width: 1, color: kBlack, std: false }), - latt2 = this.createAttLine({ style: 2, width: 1, color: kBlack, std: false }); + let urlargs = ''; + for (let k = 0; k < args.length; ++k) + urlargs += `${k > 0 ? '&' : '?'}arg${k + 1}=${args[k]}`; - this.draw_g.append('path') - .attr('d', makeLine(xqmin, yqmin, xqmax, yqmax)) - .call(latt1.func) - .style('fill', 'none'); + if (!d3node.empty()) { + d3node.style('background', 'yellow'); + if (hitem._title) + d3node.attr('title', 'Executing ' + hitem._title); + } - this.draw_g.append('path') - .attr('d', path2) - .call(latt2.func) - .style('fill', 'none'); + return httpRequest(url + urlargs, 'text').then(res => { + if (d3node.empty()) + return res; + const col = (res && (res !== 'false')) ? 'green' : 'red'; + d3node.style('background', col); + if (hitem._title) + d3node.attr('title', hitem._title + ' lastres=' + res); + setTimeout(() => { + d3node.style('background', null); + if (hitem._icon && d3node.classed('jsroot_fastcmd_btn')) + d3node.style('background-image', `url('${hitem._icon}')`); + }, 2000); + if ((col === 'green') && ('_hreload' in hitem)) + this.reload(); + if ((col === 'green') && ('_update_item' in hitem)) + this.updateItems(hitem._update_item.split(';')); + return res; + }); + }); } - drawBins3D(/* fp, graph */) { - console.log('Load ./hist/TGraphPainter.mjs to draw graph in 3D'); - } + /** @summary Get object item with specified name + * @desc depending from provided option, same item can generate different object types + * @param {Object} arg - item name or config object + * @param {string} arg.name - item name + * @param {Object} arg.item - or item itself + * @param {string} options - supposed draw options + * @return {Promise} with object like { item, obj, itemname } + * @private */ + async getObject(arg, options) { + const result = { item: null, obj: null }; + let itemname, item; - /** @summary Create necessary histogram draw attributes */ - createGraphDrawAttributes(only_check_auto) { - const graph = this.getGraph(), o = this.options; - if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { - const pp = this.getPadPainter(); - if (isFunc(pp?.getAutoColor)) { - const icolor = pp.getAutoColor(graph.$num_graphs); - this._auto_exec = ''; // can be reused when sending option back to server - if (o._pfc > 1) { o._pfc = 1; graph.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (o._plc > 1) { o._plc = 1; graph.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (o._pmc > 1) { o._pmc = 1; graph.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } - } - } + if (arg === null) + return result; - if (only_check_auto) - this.deleteAttr(); - else { - this.createAttLine({ attr: graph, can_excl: true }); - this.createAttFill({ attr: graph }); + if (isStr(arg)) + itemname = arg; + else if (isObject(arg)) { + if ((arg._parent !== undefined) && (arg._name !== undefined) && (arg._kind !== undefined)) + item = arg; + else if (arg.name !== undefined) + itemname = arg.name; + else if (arg.arg !== undefined) + itemname = arg.arg; + else if (arg.item !== undefined) + item = arg.item; } - } - - /** @summary draw TGraph */ - drawGraph() { - const pmain = this.get_main(), - graph = this.getGraph(); - if (!pmain) return; - // special mode for TMultiGraph 3d drawing - if (this.options.pos3d) - return this.drawBins3D(pmain, graph); + if (isStr(itemname) && (itemname.indexOf('img:') === 0)) { + // artificial class, can be created by users + result.obj = { _typename: 'TJSImage', fName: itemname.slice(4) }; + return result; + } - const is_gme = !!this.get_gme(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - w = pmain.getFrameWidth(), - h = pmain.getFrameHeight(); + if (item) + itemname = this.itemFullName(item); + else + item = this.findItem({ name: itemname, allow_index: true, check_keys: true }); - this.createG(!pmain.pad_layer); + // if item not found, try to find nearest parent which could allow us to get inside - this.createGraphDrawAttributes(); + const d = item ? null : this.findItem({ name: itemname, last_exists: true, check_keys: true, allow_index: true }); - this.fillatt.used = false; // mark used only when really used + // if item not found, try to expand hierarchy central function + // implements not process get in central method of hierarchy item (if exists) + // if last_parent found, try to expand it + if ((d !== null) && ('last' in d) && (d.last !== null)) { + const parentname = this.itemFullName(d.last); - this.draw_kind = 'none'; // indicate if special svg:g were created for each bin - this.marker_size = 0; // indicate if markers are drawn - const draw_g = is_gme ? this.draw_g.append('svg:g') : this.draw_g; + // this is indication that expand does not give us better path to searched item + if (isObject(arg) && ('rest' in arg)) { + if ((arg.rest === d.rest) || (arg.rest.length <= d.rest.length)) + return result; + } - this.drawBins(funcs, this.options, draw_g, w, h, this.lineatt, this.fillatt, true); + return this.expandItem(parentname, undefined, options !== 'hierarchy_expand_verbose').then(res => { + if (!res) + return result; + let newparentname = this.itemFullName(d.last); + if (newparentname) + newparentname += '/'; + return this.getObject({ name: newparentname + d.rest, rest: d.rest }, options); + }); + } - if (graph._typename === 'TGraphQQ') - this.appendQQ(funcs, graph); + result.item = item; - if (is_gme) { - for (let k = 0; k < graph.fNYErrors; ++k) { - let lineatt = this.lineatt, fillatt = this.fillatt; - if (this.options.individual_styles) { - lineatt = this.createAttLine({ attr: graph.fAttLine[k], std: false }); - fillatt = this.createAttFill({ attr: graph.fAttFill[k], std: false }); - } - const sub_g = this.draw_g.append('svg:g'), - options = (k < this.options.blocks.length) ? this.options.blocks[k] : this.options; - this.extractGmeErrors(k); - this.drawBins(funcs, options, sub_g, w, h, lineatt, fillatt); - } - this.extractGmeErrors(0); // ensure that first block kept at the end + if ((item !== null) && isObject(item._obj)) { + result.obj = item._obj; + return result; } - if (!this.isBatchMode()) { - addMoveHandler(this, this.testEditable()); - assignContextMenu(this); + // normally search _get method in the parent items + let curr = item; + while (curr) { + if (isFunc(curr._get)) { + return curr._get(item, null, options).then(obj => { + result.obj = obj; + return result; + }); + } + curr = curr._parent; } + + return result; } - /** @summary Provide tooltip at specified point */ - extractTooltip(pnt) { - if (!pnt) return null; + /** @summary returns true if item is last in parent childs list + * @private */ + isLastSibling(hitem) { + if (!hitem || !hitem._parent || !hitem._parent._childs) + return false; + const chlds = hitem._parent._childs; + let indx = chlds.indexOf(hitem); + if (indx < 0) + return false; + while (++indx < chlds.length) { + if (!('_hidden' in chlds[indx])) + return false; + } + return true; + } - if ((this.draw_kind === 'lines') || (this.draw_kind === 'path') || (this.draw_kind === 'mark')) - return this.extractTooltipForPath(pnt); + /** @summary Create item html code + * @private */ + addItemHtml(hitem, d3prnt, arg) { + if (!hitem || ('_hidden' in hitem)) + return true; - if (this.draw_kind !== 'nodes') return null; + const isroot = (hitem === this.h), + has_childs = ('_childs' in hitem), + handle = getDrawHandle(hitem._kind), + itemname = this.itemFullName(hitem); + let img1 = '', img2 = '', can_click = false, break_list = false, d3cont; - const pmain = this.get_main(), - height = pmain.getFrameHeight(), - esz = this.error_size, - isbar1 = (this.options.Bar === 1), - funcs = isbar1 ? pmain.getGrFuncs(this.options.second_x, this.options.second_y) : null, - msize = this.marker_size ? Math.round(this.marker_size/2 + 1.5) : 0; - let findbin = null, best_dist2 = 1e10, best = null; + if (handle) { + if ('icon' in handle) + img1 = handle.icon; + if ('icon2' in handle) + img2 = handle.icon2; + if (!img1 && isFunc(handle.icon_get)) + img1 = handle.icon_get(hitem, this); + if (canDrawHandle(handle) || ('execute' in handle) || ('aslink' in handle) || + (canExpandHandle(handle) && (hitem._more !== false))) + can_click = true; + } - this.draw_g.selectAll('.grpoint').each(function() { - const d = select(this).datum(); - if (d === undefined) return; - let dist2 = (pnt.x - d.grx1) ** 2; - if (pnt.nproc === 1) dist2 += (pnt.y - d.gry1) ** 2; - if (dist2 >= best_dist2) return; + if ('_icon' in hitem) + img1 = hitem._icon; + if ('_icon2' in hitem) + img2 = hitem._icon2; + if (!img1 && ('_online' in hitem)) + hitem._icon = img1 = 'img_globe'; + if (!img1 && isroot) + hitem._icon = img1 = 'img_base'; - let rect; + if (hitem._more || hitem._expand || hitem._player || hitem._can_draw) + can_click = true; - if (d.error || d.rect || d.marker) { - rect = { x1: Math.min(-esz, d.grx0, -msize), - x2: Math.max(esz, d.grx2, msize), - y1: Math.min(-esz, d.gry2, -msize), - y2: Math.max(esz, d.gry0, msize) }; - } else if (d.bar) { - rect = { x1: -d.width/2, x2: d.width/2, y1: 0, y2: height - d.gry1 }; - - if (isbar1) { - const yy0 = funcs.gry(0); - rect.y1 = (d.gry1 > yy0) ? yy0-d.gry1 : 0; - rect.y2 = (d.gry1 > yy0) ? 0 : yy0-d.gry1; - } - } else - rect = { x1: -5, x2: 5, y1: -5, y2: 5 }; - - const matchx = (pnt.x >= d.grx1 + rect.x1) && (pnt.x <= d.grx1 + rect.x2), - matchy = (pnt.y >= d.gry1 + rect.y1) && (pnt.y <= d.gry1 + rect.y2); - - if (matchx && (matchy || (pnt.nproc > 1))) { - best_dist2 = dist2; - findbin = this; - best = rect; - best.exact = /* matchx && */ matchy; - } - }); + let can_menu = can_click; + if (!can_menu && getTypeForKind(hitem._kind)) + can_menu = can_click = true; - if (findbin === null) return null; + if (!img2) + img2 = img1; + if (!img1) + img1 = (has_childs || hitem._more) ? 'img_folder' : 'img_page'; + if (!img2) + img2 = (has_childs || hitem._more) ? 'img_folderopen' : 'img_page'; - const d = select(findbin).datum(), - gr = this.getGraph(), - res = { name: gr.fName, title: gr.fTitle, - x: d.grx1, y: d.gry1, - color1: this.lineatt.color, - lines: this.getTooltips(d), - rect: best, d3bin: findbin }; + if (arg === 'update') { + d3prnt.selectAll('*').remove(); + d3cont = d3prnt; + } else { + d3cont = d3prnt.append('div'); + if (arg && (arg >= (hitem._parent._show_limit || settings.HierarchyLimit))) + break_list = true; + } - res.user_info = { obj: gr, name: gr.fName, bin: d.indx, cont: d.y, grx: d.grx1, gry: d.gry1 }; + hitem._d3cont = d3cont.node(); // set for direct referencing + d3cont.attr('item', itemname); - if (this.fillatt?.used && !this.fillatt?.empty()) - res.color2 = this.fillatt.getFillColor(); + // line with all html elements for this item (excluding childs) + const h = this, d3line = d3cont.append('div').attr('class', 'h_line'); - if (best.exact) res.exact = true; - res.menu = res.exact; // activate menu only when exactly locate bin - res.menu_dist = 3; // distance always fixed - res.bin = d; - res.binindx = d.indx; + // build indent + let prnt = isroot ? null : hitem._parent, upcnt = 1; + while (prnt && (prnt !== this.h)) { + const is_last = this.isLastSibling(prnt), + d3icon = d3line.insert('div', ':first-child').attr('class', is_last ? 'img_empty' : 'img_line'); + if (!is_last) + d3icon.style('cursor', 'pointer').property('upcnt', upcnt).on('click', function(evnt) { h.tree_click(evnt, this, 'parentminus'); }); + prnt = prnt._parent; + upcnt++; + } - return res; - } + let icon_class = '', plusminus = false; - /** @summary Show tooltip */ - showTooltip(hint) { - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + if (isroot) ; else if ((has_childs && !break_list) || handle?.pm) { + icon_class = hitem._isopen ? 'img_minus' : 'img_plus'; + plusminus = true; + } else + icon_class = 'img_join'; - if (!hint || !this.draw_g) { - ttrect?.remove(); - return; + if (icon_class) { + if (break_list || this.isLastSibling(hitem)) + icon_class += 'bottom'; + const d3icon = d3line.append('div').attr('class', icon_class); + if (plusminus) + d3icon.style('cursor', 'pointer').on('click', function(evnt) { h.tree_click(evnt, this, kPM); }); } - if (hint.usepath) - return this.showTooltipForPath(hint); + // make node icons - const d = select(hint.d3bin).datum(); + if (this.with_icons && !break_list) { + const icon_name = hitem._isopen ? img2 : img1, + d3img = (icon_name.indexOf('img_') === 0) + ? d3line.append('div') + .attr('class', icon_name) + .attr('title', hitem._kind) + : d3line.append('img') + .attr('src', icon_name) + .attr('alt', '') + .attr('title', hitem._kind) + .style('vertical-align', 'top') + .style('width', '18px') + .style('height', '18px'); - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); + if (hitem._icon_click || handle?.icon_click) + d3img.on('click', function(evnt) { h.tree_click(evnt, this, 'icon'); }); } - hint.changed = ttrect.property('current_bin') !== hint.d3bin; + const d3a = d3line.append('a'); + if (can_click || has_childs || break_list) + d3a.attr('class', cssItem).on('click', function(evnt) { h.tree_click(evnt, this); }); - if (hint.changed) { - ttrect.attr('x', d.grx1 + hint.rect.x1) - .attr('width', hint.rect.x2 - hint.rect.x1) - .attr('y', d.gry1 + hint.rect.y1) - .attr('height', hint.rect.y2 - hint.rect.y1) - .style('opacity', '0.3') - .property('current_bin', hint.d3bin); + if (break_list) { + hitem._break_point = true; // indicate that list was broken here + d3a.attr('title', 'there are ' + (hitem._parent._childs.length - arg) + ' more items') + .text('...more...'); + return false; } - } - - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const hint = this.extractTooltip(pnt); - if (!pnt || !pnt.disabled) this.showTooltip(hint); - return hint; - } - - /** @summary Find best bin index for specified point */ - findBestBin(pnt) { - if (!this.bins) return null; - - const islines = (this.draw_kind === 'lines'), - funcs = this.get_main().getGrFuncs(this.options.second_x, this.options.second_y); - let bestindx = -1, - bestbin = null, - bestdist = 1e10, - dist, grx, gry, n, bin; - for (n = 0; n < this.bins.length; ++n) { - bin = this.bins[n]; + if ('disp_kind' in h) { + if (settings.DragAndDrop && can_click) + this.enableDrag(d3a, itemname); - grx = funcs.grx(bin.x); - gry = funcs.gry(bin.y); + if (settings.ContextMenu && can_menu) + d3a.on('contextmenu', function(evnt) { h.tree_contextmenu(evnt, this); }); - dist = (pnt.x-grx)**2 + (pnt.y-gry)**2; + d3a.on('mouseover', function() { h.tree_mouseover(true, this); }) + .on('mouseleave', function() { h.tree_mouseover(false, this); }); + } else if (hitem._direct_context && settings.ContextMenu) + d3a.on('contextmenu', function(evnt) { h.direct_contextmenu(evnt, this); }); - if (dist < bestdist) { - bestdist = dist; - bestbin = bin; - bestindx = n; - } - } + let element_name = hitem._name, element_title = ''; - // check last point - if ((bestdist > 100) && islines) bestbin = null; + if ('_realname' in hitem) + element_name = hitem._realname; - let radius = Math.max(this.lineatt.width + 3, 4); + if ('_title' in hitem) + element_title = hitem._title; - if (this.marker_size > 0) radius = Math.max(this.marker_size, radius); + if ('_fullname' in hitem) + element_title += ' fullname: ' + hitem._fullname; - if (bestbin) - bestdist = Math.sqrt((pnt.x-funcs.grx(bestbin.x))**2 + (pnt.y-funcs.gry(bestbin.y))**2); + if (!element_title) + element_title = element_name; - if (!islines && (bestdist > radius)) bestbin = null; + if (hitem._filter) + element_name += ' *'; - if (!bestbin) bestindx = -1; + d3a.attr('title', element_title) + .text(element_name + ('_value' in hitem ? ':' : '')) + .style('background', hitem._background ? hitem._background : null); - const res = { bin: bestbin, indx: bestindx, dist: bestdist, radius: Math.round(radius) }; + if ('_value' in hitem) { + const d3p = d3line.append('p'); + if ('_vclass' in hitem) + d3p.attr('class', hitem._vclass); + if (!hitem._isopen) + d3p.text(hitem._value); + } - if (!bestbin && islines) { - bestdist = 1e10; + if (has_childs && (isroot || hitem._isopen)) { + const d3chlds = d3cont.append('div').attr('class', 'h_childs'); + if (this.show_overflow) + d3chlds.style('overflow', 'initial'); + for (let i = 0; i < hitem._childs.length; ++i) { + const chld = hitem._childs[i]; + chld._parent = hitem; + if (hitem._filter && chld._name && chld._name.indexOf(hitem._filter) < 0) + continue; + if (!this.addItemHtml(chld, d3chlds, i)) + break; // if too many items, skip rest + } + } - const IsInside = (x, x1, x2) => ((x1 >= x) && (x >= x2)) || ((x1 <= x) && (x <= x2)); + return true; + } - let bin0 = this.bins[0], grx0 = funcs.grx(bin0.x), gry0, posy = 0; - for (n = 1; n < this.bins.length; ++n) { - bin = this.bins[n]; - grx = funcs.grx(bin.x); + /** @summary Toggle open state of the item + * @desc Used with 'expand all' / 'collapse all' buttons in normal GUI + * @param {boolean} isopen - if items should be expand or closed + * @return {boolean} true when any item was changed */ + toggleOpenState(isopen, h, promises) { + const hitem = h || this.h; - if (IsInside(pnt.x, grx0, grx)) { - // if inside interval, check Y distance - gry0 = funcs.gry(bin0.y); - gry = funcs.gry(bin.y); + if (hitem._childs === undefined) { + if (!isopen) + return false; - if (Math.abs(grx - grx0) < 1) { - // very close x - check only y - posy = pnt.y; - dist = IsInside(pnt.y, gry0, gry) ? 0 : Math.min(Math.abs(pnt.y-gry0), Math.abs(pnt.y-gry)); - } else { - posy = gry0 + (pnt.x - grx0) / (grx - grx0) * (gry - gry0); - dist = Math.abs(posy - pnt.y); - } + if (this.with_icons) { + // in normal hierarchy check precisely if item can be expand + if (!hitem._more && !hitem._expand && !this.canExpandItem(hitem)) + return false; + } - if (dist < bestdist) { - bestdist = dist; - res.linex = pnt.x; - res.liney = posy; - } - } + const pr = this.expandItem(this.itemFullName(hitem)); + if (isPromise(pr) && isObject(promises)) + promises.push(pr); + if (hitem._childs !== undefined) + hitem._isopen = true; + return hitem._isopen; + } - bin0 = bin; - grx0 = grx; - } + if ((hitem !== this.h) && isopen && !hitem._isopen) { + // when there are childs and they are not see, simply show them + hitem._isopen = true; + return true; + } - if (bestdist < radius*0.5) { - res.linedist = bestdist; - res.closeline = true; - } + let change_child = false; + for (let i = 0; i < hitem._childs.length; ++i) { + if (this.toggleOpenState(isopen, hitem._childs[i], promises)) + change_child = true; } - return res; - } + if ((hitem !== this.h) && !isopen && hitem._isopen && !change_child) { + // if none of the childs can be closed, than just close that item + delete hitem._isopen; + return true; + } - /** @summary Check editable flag for TGraph - * @desc if arg specified changes or toggles editable flag */ - testEditable(arg) { - const obj = this.getGraph(); - if (!obj) return false; - if ((arg === 'toggle') || ((arg !== undefined) && (!arg !== obj.TestBit(kNotEditable)))) - obj.InvertBit(kNotEditable); - return !obj.TestBit(kNotEditable); + if (!h) + this.refreshHtml(); + return false; } - /** @summary Provide tooltip at specified point for path-based drawing */ - extractTooltipForPath(pnt) { - if (this.bins === null) return null; - - const best = this.findBestBin(pnt); + /** @summary Expand to specified level + * @protected */ + async expandToLevel(level) { + if (!level || !Number.isFinite(level) || (level < 0)) + return this; - if (!best || (!best.bin && !best.closeline)) return null; + const promises = []; + this.toggleOpenState(true, this.h, promises); + return Promise.all(promises).then(() => this.expandToLevel(level - 1)); + } - const islines = (this.draw_kind === 'lines'), - ismark = (this.draw_kind === 'mark'), - pmain = this.get_main(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - gr = this.getGraph(), - res = { name: gr.fName, title: gr.fTitle, - x: best.bin ? funcs.grx(best.bin.x) : best.linex, - y: best.bin ? funcs.gry(best.bin.y) : best.liney, - color1: this.lineatt.color, - lines: this.getTooltips(best.bin), - usepath: true }; + /** @summary Expand to specified level + * @deprecated will be removed in version 8, kept only for backward compatibility + * @protected */ + async exapndToLevel(level) { return this.expandToLevel(level); } - res.user_info = { obj: gr, name: gr.fName, bin: 0, cont: 0, grx: res.x, gry: res.y }; + /** @summary Refresh HTML code of hierarchy painter + * @return {Promise} when done */ + async refreshHtml() { + const d3elem = this.selectDom(); + if (d3elem.empty()) + return this; - res.ismark = ismark; - res.islines = islines; + d3elem.html('') // clear html - most simple way + .style('overflow', this.show_overflow ? 'auto' : 'hidden') + .style('display', 'flex') + .style('flex-direction', 'column'); - if (best.closeline) { - res.menu = res.exact = true; - res.menu_dist = best.linedist; - } else if (best.bin) { - if (this.options.EF && islines) { - res.gry1 = funcs.gry(best.bin.y - best.bin.eylow); - res.gry2 = funcs.gry(best.bin.y + best.bin.eyhigh); - } else - res.gry1 = res.gry2 = funcs.gry(best.bin.y); + injectHStyle(d3elem.node()); + const h = this, factcmds = []; + let status_item = null; + this.forEachItem(item => { + delete item._d3cont; // remove html container + if (('_fastcmd' in item) && (item._kind === 'Command')) + factcmds.push(item); + if (('_status' in item) && !status_item) + status_item = item; + }); - res.binindx = best.indx; - res.bin = best.bin; - res.radius = best.radius; - res.user_info.bin = best.indx; - res.user_info.cont = best.bin.y; + if (!this.h || d3elem.empty()) + return this; - res.exact = (Math.abs(pnt.x - res.x) <= best.radius) && - ((Math.abs(pnt.y - res.gry1) <= best.radius) || (Math.abs(pnt.y - res.gry2) <= best.radius)); + if (factcmds.length) { + const fastbtns = d3elem.append('div').attr('style', 'display: inline; vertical-align: middle; white-space: nowrap;'); + for (let n = 0; n < factcmds.length; ++n) { + const btn = fastbtns.append('button') + .text('') + .attr('class', 'jsroot_fastcmd_btn') + .attr('item', this.itemFullName(factcmds[n])) + .attr('title', factcmds[n]._title) + .on('click', function() { h.executeCommand(select(this).attr('item'), this); }); - res.menu = res.exact; - res.menu_dist = Math.sqrt((pnt.x-res.x)**2 + Math.min(Math.abs(pnt.y-res.gry1), Math.abs(pnt.y-res.gry2))**2); + if (factcmds[n]._icon) + btn.style('background-image', `url('${factcmds[n]._icon}')`); + } } - if (this.fillatt?.used && !this.fillatt?.empty()) - res.color2 = this.fillatt.getFillColor(); + const d3btns = d3elem.append('p').attr('class', 'jsroot').style('margin-bottom', '3px').style('margin-top', 0); + d3btns.append('a').attr('class', cssButton).text('expand all') + .attr('title', 'expand all items in the browser').on('click', () => this.toggleOpenState(true)); + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('collapse all') + .attr('title', 'collapse all items in the browser').on('click', () => this.toggleOpenState(false)); - if (!islines) { - res.color1 = this.getColor(gr.fMarkerColor); - if (!res.color2) res.color2 = res.color1; + if (isFunc(this.storeAsJson)) { + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('json') + .attr('title', 'dump to json file').on('click', () => this.storeAsJson()); } - return res; - } - - /** @summary Show tooltip for path drawing */ - showTooltipForPath(hint) { - let ttbin = this.draw_g?.selectChild('.tooltip_bin'); - - if (!hint?.bin || !this.draw_g) { - ttbin?.remove(); - return; + if (isFunc(this.removeInspector)) { + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('remove') + .attr('title', 'remove inspector').on('click', () => this.removeInspector()); } - if (ttbin.empty()) - ttbin = this.draw_g.append('svg:g').attr('class', 'tooltip_bin'); - - hint.changed = ttbin.property('current_bin') !== hint.bin; - - if (hint.changed) { - ttbin.selectAll('*').remove(); // first delete all children - ttbin.property('current_bin', hint.bin); - - if (hint.ismark) { - ttbin.append('svg:rect') - .style('pointer-events', 'none') - .call(addHighlightStyle) - .style('opacity', '0.3') - .attr('x', Math.round(hint.x - hint.radius)) - .attr('y', Math.round(hint.y - hint.radius)) - .attr('width', 2*hint.radius) - .attr('height', 2*hint.radius); - } else { - ttbin.append('svg:circle').attr('cy', Math.round(hint.gry1)); - if (Math.abs(hint.gry1-hint.gry2) > 1) - ttbin.append('svg:circle').attr('cy', Math.round(hint.gry2)); - - const elem = ttbin.selectAll('circle') - .attr('r', hint.radius) - .attr('cx', Math.round(hint.x)); - - if (!hint.islines) - elem.style('stroke', hint.color1 === 'black' ? 'green' : 'black').style('fill', 'none'); - else { - if (this.options.Line || this.options.Curve) - elem.call(this.lineatt.func); - else - elem.style('stroke', 'black'); - if (this.options.Fill) - elem.call(this.fillatt.func); - else - elem.style('fill', 'none'); - } - } + if ('_online' in this.h) { + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('reload') + .attr('title', 'reload object list from the server').on('click', () => this.reload()); } - } - /** @summary Check if graph moving is enabled */ - moveEnabled() { - return this.testEditable(); - } + if ('disp_kind' in this) { + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('clear') + .attr('title', 'clear all drawn objects').on('click', () => this.clearHierarchy(false)); + } - /** @summary Start moving of TGraph */ - moveStart(x, y) { - this.pos_dx = this.pos_dy = 0; - this.move_funcs = this.get_main().getGrFuncs(this.options.second_x, this.options.second_y); - const hint = this.extractTooltip({ x, y }); - if (hint && hint.exact && (hint.binindx !== undefined)) { - this.move_binindx = hint.binindx; - this.move_bin = hint.bin; - this.move_x0 = this.move_funcs.grx(this.move_bin.x); - this.move_y0 = this.move_funcs.gry(this.move_bin.y); - } else - delete this.move_binindx; - } + const maindiv = + d3elem.append('div') + .attr('class', 'jsroot') + .style('font-size', this.with_icons ? '12px' : '15px') + .style('flex', '1'); - /** @summary Perform moving */ - moveDrag(dx, dy) { - this.pos_dx += dx; - this.pos_dy += dy; + if (!this.show_overflow) + maindiv.style('overflow', 'auto'); - if (this.move_binindx === undefined) - makeTranslate(this.draw_g, this.pos_dx, this.pos_dy); - else if (this.move_funcs && this.move_bin) { - this.move_bin.x = this.move_funcs.revertAxis('x', this.move_x0 + this.pos_dx); - this.move_bin.y = this.move_funcs.revertAxis('y', this.move_y0 + this.pos_dy); - this.drawGraph(); + if (this.background) { + // case of object inspector and streamer infos display + maindiv.style('background-color', this.background) + .style('margin', '2px').style('padding', '2px'); } - } - - /** @summary Complete moving */ - moveEnd(not_changed) { - const graph = this.getGraph(), last = graph?.fNpoints-1; - let exec = ''; + if (this.textcolor) + maindiv.style('color', this.textcolor); - const changeBin = bin => { - exec += `SetPoint(${bin.indx},${bin.x},${bin.y});;`; - graph.fX[bin.indx] = bin.x; - graph.fY[bin.indx] = bin.y; - if ((bin.indx === 0) && this._cutg_lastsame) { - exec += `SetPoint(${last},${bin.x},${bin.y});;`; - graph.fX[last] = bin.x; - graph.fY[last] = bin.y; - } - }; + this.addItemHtml(this.h, maindiv.append('div').attr('class', cssTree)); - if (this.move_binindx === undefined) { - this.draw_g.attr('transform', null); + this.setTopPainter(); // assign this hierarchy painter as top painter - if (this.move_funcs && this.bins && !not_changed) { - for (let k = 0; k < this.bins.length; ++k) { - const bin = this.bins[k]; - bin.x = this.move_funcs.revertAxis('x', this.move_funcs.grx(bin.x) + this.pos_dx); - bin.y = this.move_funcs.revertAxis('y', this.move_funcs.gry(bin.y) + this.pos_dy); - changeBin(bin); - } - if (graph.$redraw_pad) - this.redrawPad(); - else - this.drawGraph(); + if (status_item && !this.status_disabled && !decodeUrl().has('nostatus')) { + const func = findFunction(status_item._status); + if (isFunc(func)) { + return this.createStatusLine().then(sdiv => { + if (sdiv) + func(sdiv, this.itemFullName(status_item)); + }); } - } else { - changeBin(this.move_bin); - delete this.move_binindx; - if (graph.$redraw_pad) - this.redrawPad(); } - delete this.move_funcs; - - if (exec && !not_changed) - this.submitCanvExec(exec); + return this; } - /** @summary Fill option object used in TWebCanvas */ - fillWebObjectOptions(res) { - if (this._auto_exec && res) { - res.fcust = 'auto_exec:' + this._auto_exec; - delete this._auto_exec; + /** @summary Update item node + * @private */ + updateTreeNode(hitem, d3cont) { + if ((d3cont === undefined) || d3cont.empty()) { + d3cont = select(hitem._d3cont ? hitem._d3cont : null); + const name = this.itemFullName(hitem); + if (d3cont.empty()) + d3cont = this.selectDom().select(`[item='${name}']`); + if (d3cont.empty() && ('_cycle' in hitem)) + d3cont = this.selectDom().select(`[item='${name};${hitem._cycle}']`); + if (d3cont.empty()) + return; } - } - /** @summary Fill context menu */ - fillContextMenuItems(menu) { - if (!this.snapid) - menu.addchk(this.testEditable(), 'Editable', () => { this.testEditable('toggle'); this.drawGraph(); }); + this.addItemHtml(hitem, d3cont, 'update'); + + this.brlayout?.adjustBrowserSize(true); } - /** @summary Execute menu command + /** @summary Update item background * @private */ - executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) return true; + updateBackground(hitem, scroll_into_view) { + if (!hitem || !hitem._d3cont) + return; - const canp = this.getCanvPainter(), pmain = this.get_main(); + const d3cont = select(hitem._d3cont); + if (d3cont.empty()) + return; - if ((method.fName === 'RemovePoint') || (method.fName === 'InsertPoint')) { - if (!canp || canp._readonly) return true; // ignore function + const d3a = d3cont.select(`.${cssItem}`); + d3a.style('background', hitem._background ? hitem._background : null); - const pnt = isFunc(pmain?.getLastEventPos) ? pmain.getLastEventPos() : null, - hint = this.extractTooltip(pnt); + if (scroll_into_view && hitem._background) + d3a.node().scrollIntoView(false); + } - if (method.fName === 'InsertPoint') { - if (pnt) { - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - userx = funcs.revertAxis('x', pnt.x) ?? 0, - usery = funcs.revertAxis('y', pnt.y) ?? 0; - this.submitCanvExec(`AddPoint(${userx.toFixed(3)}, ${usery.toFixed(3)})`, method.$execid); - } - } else if (method.$execid && (hint?.binindx !== undefined)) - this.submitCanvExec(`RemovePoint(${hint.binindx})`, method.$execid); + /** @summary Focus on hierarchy item + * @param {Object|string} hitem - item to open or its name + * @desc all parents to the item will be opened first + * @return {Promise} when done + * @private */ + async focusOnItem(hitem) { + if (isStr(hitem)) + hitem = this.findItem(hitem); + const name = hitem ? this.itemFullName(hitem) : ''; + if (!name) + return false; - return true; // call is processed + let itm = hitem, need_refresh = false; + + while (itm) { + if ((itm._childs !== undefined) && !itm._isopen) { + itm._isopen = true; + need_refresh = true; + } + itm = itm._parent; } - return false; + const promise = need_refresh ? this.refreshHtml() : Promise.resolve(true); + + return promise.then(() => { + const d3cont = this.selectDom().select(`[item='${name}']`); + if (d3cont.empty()) + return false; + d3cont.node().scrollIntoView(); + return true; + }); } - /** @summary Update object members + /** @summary Handler for click event of item in the hierarchy * @private */ - _updateMembers(graph, obj) { - graph.fBits = obj.fBits; - graph.fTitle = obj.fTitle; - graph.fX = obj.fX; - graph.fY = obj.fY; - graph.fNpoints = obj.fNpoints; - graph.fMinimum = obj.fMinimum; - graph.fMaximum = obj.fMaximum; + tree_click(evnt, node, place) { + if (!node) + return; - const o = this.options; + let d3cont = select(node.parentNode.parentNode), + itemname = d3cont.attr('item'), + hitem = itemname ? this.findItem(itemname) : null; + if (!hitem) + return; - if (this.snapid !== undefined) - o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + if (place === 'parentminus') { + let upcnt = select(node).property('upcnt') || 1; + while (upcnt-- > 0) + hitem = hitem?._parent; + if (!hitem) + return; + itemname = this.itemFullName(hitem); + d3cont = select(hitem?._d3cont || null); + place = kPM; + } - if (!o._pfc) - graph.fFillColor = obj.fFillColor; - graph.fFillStyle = obj.fFillStyle; - if (!o._plc) - graph.fLineColor = obj.fLineColor; - graph.fLineStyle = obj.fLineStyle; - graph.fLineWidth = obj.fLineWidth; - if (!o._pmc) - graph.fMarkerColor = obj.fMarkerColor; - graph.fMarkerSize = obj.fMarkerSize; - graph.fMarkerStyle = obj.fMarkerStyle; - } + if (hitem._break_point) { + // special case of more item - /** @summary Update TGraph object */ - updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; + delete hitem._break_point; - if (opt && (opt !== this.options.original)) - this.decodeOptions(opt); + // update item itself + this.addItemHtml(hitem, d3cont, 'update'); - this._updateMembers(this.getObject(), obj); + const prnt = hitem._parent, indx = prnt._childs.indexOf(hitem), + d3chlds = select(d3cont.node().parentNode); - this.createBins(); + if (indx < 0) + return console.error('internal error'); - delete this.$redraw_hist; + prnt._show_limit = (prnt._show_limit || settings.HierarchyLimit) * 2; - // if our own histogram was used as axis drawing, we need update histogram as well - if (this.axes_draw) { - const histo = this.createHistogram(), - hist_painter = this.getMainPainter(); - if (hist_painter?.isSecondary(this)) { - hist_painter.updateObject(histo, this.options.Axis); - this.$redraw_hist = true; + for (let n = indx + 1; n < prnt._childs.length; ++n) { + const chld = prnt._childs[n]; + chld._parent = prnt; + if (!this.addItemHtml(chld, d3chlds, n)) + break; // if too many items, skip rest } + + return; } - this._funcHandler = new FunctionsHandler(this, this.getPadPainter(), obj.fFunctions); + let prnt = hitem, dflt; + while (prnt) { + if ((dflt = prnt._click_action) !== undefined) + break; + prnt = prnt._parent; + } - return true; - } + if (!place) + place = 'item'; + const selector = (hitem._kind === getKindForType(clTKey) && hitem._more) ? 'noinspect' : '', + sett = getDrawSettings(hitem._kind, selector), handle = sett.handle; - /** @summary Checks if it makes sense to zoom inside specified axis range - * @desc allow to zoom TGraph only when at least one point in the range */ - canZoomInside(axis, min, max) { - const gr = this.getGraph(); - if (!gr || (axis !== (this.options.pos3d ? 'y' : 'x'))) return false; + if (place === 'icon') { + let func = null; + if (isFunc(hitem._icon_click)) + func = hitem._icon_click; + else if (isFunc(handle?.icon_click)) + func = handle.icon_click; + if (func && func(hitem, this)) + this.updateTreeNode(hitem, d3cont); + return; + } - for (let n = 0; n < gr.fNpoints; ++n) - if ((min < gr.fX[n]) && (gr.fX[n] < max)) return true; + // special feature - all items with '_expand' function are not drawn by click + if ((place === 'item') && ('_expand' in hitem) && !hitem._expand_miss && !evnt.ctrlKey && !evnt.shiftKey) + place = kPM; - return false; - } + // special case - one should expand item + if (((place === kPM) && !('_childs' in hitem) && hitem._more) || + ((place === kPM) && handle?.pm) || + ((place === 'item') && (dflt === kExpand))) + return this.expandItem(itemname, d3cont); - /** @summary Process click on graph-defined buttons */ - clickButton(funcname) { - if (funcname !== 'ToggleZoom') return false; + if (place === 'item') { + if (hitem._player) + return this.player(itemname); - if ((this.xmin === this.xmax) && (this.ymin === this.ymax)) return false; + if (handle?.aslink) + return window.open(itemname + '/'); - return this.getFramePainter()?.zoom(this.xmin, this.xmax, this.ymin, this.ymax); - } + if (handle?.execute) + return this.executeCommand(itemname, node.parentNode); - /** @summary Find TF1/TF2 in TGraph list of functions */ - findFunc() { - return this.getGraph()?.fFunctions?.arr?.find(func => (func._typename === clTF1) || (func._typename === clTF2)); - } + if (handle?.ignore_online && this.isOnlineItem(hitem)) + return; - /** @summary Find stat box in TGraph list of functions */ - findStat() { - return this.getGraph()?.fFunctions?.arr?.find(func => (func._typename === clTPaveStats) && (func.fName === 'stats')); - } + const dflt_expand = (this.default_by_click === kExpand); + let can_draw = hitem._can_draw, + can_expand = hitem._more, + drawopt = ''; - /** @summary Create stat box */ - createStat() { - const func = this.findFunc(); - if (!func) return null; + if (evnt.shiftKey) { + drawopt = handle?.shift || kInspect; + if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0) && handle?.noinspect) + drawopt = ''; + } + if (evnt.ctrlKey && handle?.ctrl) + drawopt = handle.ctrl; - let stats = this.findStat(); - if (stats) return stats; + if (!drawopt && !handle?.always_draw) { + for (let pitem = hitem._parent; pitem; pitem = pitem._parent) { + if (pitem._painter) { + can_draw = false; + if (can_expand === undefined) + can_expand = false; + break; + } + } + } - // do not create stats box when drawing canvas - if (this.getCanvPainter()?.normal_canvas) return null; + if (hitem._childs) + can_expand = false; - this.create_stats = true; + if (can_draw === undefined) + can_draw = sett.draw; + if (can_expand === undefined) + can_expand = sett.expand || sett.get_expand; - const st = gStyle; + if (can_draw && can_expand && !drawopt) { + // if default action specified as expand, disable drawing + // if already displayed, try to expand + if (dflt_expand || (handle?.dflt === kExpand) || (handle?.expand_after_draw && this.isItemDisplayed(itemname))) + can_draw = false; + } - stats = create$1(clTPaveStats); - Object.assign(stats, { fName: 'stats', fOptStat: 0, fOptFit: st.fOptFit || 111, fBorderSize: 1, - fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, - fFillColor: st.fStatColor, fFillStyle: st.fStatStyle }); + if (can_draw && !drawopt) + drawopt = kDfltDrawOpt; - stats.fTextAngle = 0; - stats.fTextSize = st.fStatFontSize; // 9 ?? - stats.fTextAlign = 12; - stats.fTextColor = st.fStatTextColor; - stats.fTextFont = st.fStatFont; + if (can_draw) + return this.display(itemname, drawopt, null, true); - stats.AddText(func.fName); + if (can_expand || dflt_expand) + return this.expandItem(itemname, d3cont); - // while TF1 was found, one can be sure that stats is existing - this.getGraph().fFunctions.Add(stats); + // cannot draw, but can inspect ROOT objects + if (getTypeForKind(hitem._kind) && sett.inspect && (can_draw !== false)) + return this.display(itemname, kInspect, null, true); - return stats; - } + if (!hitem._childs || (hitem === this.h)) + return; + } - /** @summary Fill statistic */ - fillStatistic(stat, _dostat, dofit) { - const func = this.findFunc(); + if (hitem._isopen) + delete hitem._isopen; + else + hitem._isopen = true; - if (!func || !dofit) return false; + this.updateTreeNode(hitem, d3cont); + } - stat.clearPave(); + /** @summary Handler for mouse-over event + * @private */ + tree_mouseover(on, elem) { + const itemname = select(elem.parentNode.parentNode).attr('item'), + hitem = this.findItem(itemname); + if (!hitem) + return; - stat.fillFunctionStat(func, (dofit === 1) ? 111 : dofit, 1); + let painter, prnt = hitem; + while (prnt && !painter) { + painter = prnt._painter; + prnt = prnt._parent; + } - return true; + if (isFunc(painter?.mouseOverHierarchy)) + painter.mouseOverHierarchy(on, itemname, hitem); } - /** @summary Draw axis histogram + /** @summary alternative context menu, used in the object inspector * @private */ - async drawAxisHisto() { - const histo = this.createHistogram(); - return TH1Painter$2.draw(this.getDom(), histo, this.options.Axis); + direct_contextmenu(evnt, elem) { + evnt.preventDefault(); + const itemname = select(elem.parentNode.parentNode).attr('item'), + hitem = this.findItem(itemname); + if (!hitem) + return; + + if (isFunc(this.fill_context)) { + createMenu(evnt, this).then(menu => { + this.fill_context(menu, hitem); + if (menu.size() > 0) { + menu.tree_node = elem.parentNode; + menu.show(); + } + }); + } } - /** @summary Draw TGraph + /** @summary Fills settings menu items * @private */ - static async _drawGraph(painter, opt) { - painter.decodeOptions(opt, true); - painter.createBins(); - painter.createStat(); - const graph = painter.getGraph(); - if (!settings.DragGraphs && graph && !graph.TestBit(kNotEditable)) - graph.InvertBit(kNotEditable); - - let promise = Promise.resolve(); + fillSettingsMenu(menu, alone) { + menu.addSettingsMenu(true, alone, arg => { + if (arg === 'refresh') { + this.forEachRootFile(folder => keysHierarchy(folder, folder._file.fKeys, folder._file, '')); + this.refreshHtml(); + } else if (arg === 'dark') + this.changeDarkMode(); + else if (arg === 'width') + this.brlayout?.adjustSeparators(settings.BrowserWidth, null); + }); + } - if ((!painter.getMainPainter() || painter.options.second_x || painter.options.second_y) && painter.options.Axis) { - promise = painter.drawAxisHisto().then(hist_painter => { - hist_painter?.setSecondaryId(painter, 'hist'); - painter.axes_draw = !!hist_painter; - }); + /** @summary Handle changes of dark mode + * @private */ + changeDarkMode() { + if (this.textcolor) { + this.setBasicColors(); + this.refreshHtml(); } - return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawGraph(); - }).then(() => { - const handler = new FunctionsHandler(painter, painter.getPadPainter(), graph.fFunctions, true); - return handler.drawNext(0); // returns painter + this.brlayout?.createStyle(); + this.createButtons(); // recreate buttons + if (isFunc(this.disp?.changeDarkMode)) + this.disp.changeDarkMode(); + this.disp?.forEachFrame(frame => { + const p = getElementCanvPainter(frame) || getElementMainPainter(frame); + if (isFunc(p?.changeDarkMode) && (p !== this)) + p.changeDarkMode(); }); } - static async draw(dom, graph, opt) { - return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); + /** @summary Toggle dark mode + * @private */ + toggleDarkMode() { + settings.DarkMode = !settings.DarkMode; + this.changeDarkMode(); } -}; // class TGraphPainter - -var TGraphPainter$2 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TGraphPainter: TGraphPainter$1, -clTGraphAsymmErrors: clTGraphAsymmErrors -}); - -class TGraphPainter extends TGraphPainter$1 { - - /** @summary Draw TGraph points in 3D + /** @summary Handle context menu in the hierarchy * @private */ - drawBins3D(fp, graph) { - if (!fp.mode3d || !fp.grx || !fp.gry || !fp.grz || !fp.toplevel) - return console.log('Frame painter missing base 3d elements'); - - if (fp.zoom_xmin !== fp.zoom_xmax) - if ((this.options.pos3d < fp.zoom_xmin) || (this.options.pos3d > fp.zoom_xmax)) return; - - this.createGraphDrawAttributes(true); - - const drawbins = this.optimizeBins(1000); - let first = 0, last = drawbins.length-1; - - if (fp.zoom_ymin !== fp.zoom_ymax) { - while ((first < last) && (drawbins[first].x < fp.zoom_ymin)) first++; - while ((first < last) && (drawbins[last].x > fp.zoom_ymax)) last--; - } - - if (first === last) return; + tree_contextmenu(evnt, elem) { + evnt.preventDefault(); + const itemname = select(elem.parentNode.parentNode).attr('item'), + hitem = this.findItem(itemname); + if (!hitem) + return; - const pnts = [], grx = fp.grx(this.options.pos3d); - let p0 = drawbins[first]; + const onlineprop = this.getOnlineProp(itemname), + fileprop = this.getFileProp(itemname); - for (let n = first + 1; n <= last; ++n) { - const p1 = drawbins[n]; - pnts.push(grx, fp.gry(p0.x), fp.grz(p0.y), - grx, fp.gry(p1.x), fp.grz(p1.y)); - p0 = p1; + function qualifyURL(url) { + const escapeHTML = s => s.split('&').join('&').split('<').join('<').split('"').join('"'), + el = document.createElement('div'); + el.innerHTML = `x`; + return el.firstChild.href; } - const lines = createLineSegments(pnts, create3DLineMaterial(this, graph)); - - fp.add3DMesh(lines, this, true); - - fp.render3D(100); - } + createMenu(evnt, this).then(menu => { + if ((!itemname || !hitem._parent) && !('_jsonfile' in hitem)) { + let addr = '', cnt = 0; + const files = [], separ = () => { return (cnt++ > 0) ? '&' : '?'; }; - /** @summary Draw axis histogram - * @private */ - async drawAxisHisto() { - return TH1Painter.draw(this.getDom(), this.createHistogram(), this.options.Axis); - } + this.forEachRootFile(item => files.push(item._file.fFullURL)); - static async draw(dom, graph, opt) { - return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); - } + if (!this.getTopOnlineItem()) + addr = exports.source_dir + 'index.htm'; -} // class TGraphPainter + if (this.isMonitoring()) + addr += separ() + 'monitoring=' + this.getMonitoringInterval(); -/** @summary direct draw function for TPolyMarker3D object - * @private */ -async function drawPolyMarker3D$1() { - const fp = this.$fp || this.getFramePainter(); + if (files.length === 1) + addr += `${separ()}file=${files[0]}`; + else if (files.length > 1) + addr += `${separ()}files=${JSON.stringify(files)}`; - delete this.$fp; + if (this.disp_kind) + addr += separ() + 'layout=' + this.disp_kind.replace(/ /g, ''); - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return this; + const items = [], opts = []; - const poly = this.getObject(), sizelimit = 50000, fP = poly.fP; - let step = 1, numselect = 0; + this.disp?.forEachFrame(frame => { + const dummy = new ObjectPainter(frame); + let top = dummy.getTopPainter(), + item = top ? top.getItemName() : null, opt; - for (let i = 0; i < fP.length; i += 3) { - if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i+1] < fp.scale_ymin) || (fP[i+1] > fp.scale_ymax) || - (fP[i+2] < fp.scale_zmin) || (fP[i+2] > fp.scale_zmax)) continue; - ++numselect; - } + if (item) + opt = top.getDrawOpt() || top.getItemDrawOpt(); + else { + top = null; + dummy.forEachPainter(p => { + const _item = p.getItemName(); + if (!_item) + return; + let _opt = p.getDrawOpt() || p.getItemDrawOpt() || ''; + if (!top) { + top = p; + item = _item; + opt = _opt; + } else if (top.getPadPainter() === p.getPadPainter()) { + if (_opt.indexOf('same ') === 0) + _opt = _opt.slice(5); + item += '+' + _item; + opt += '+' + _opt; + } + }); + } - if ((settings.OptimizeDraw > 0) && (numselect > sizelimit)) { - step = Math.floor(numselect/sizelimit); - if (step <= 2) step = 2; - } + if (item) { + items.push(item); + opts.push(opt || ''); + } + }); - const size = Math.floor(numselect/step), - pnts = new PointsCreator(size, fp.webgl, fp.size_x3d/100), - index = new Int32Array(size); - let select = 0, icnt = 0; + if (items.length === 1) + addr += separ() + 'item=' + items[0] + separ() + 'opt=' + opts[0]; + else if (items.length > 1) + addr += separ() + 'items=' + JSON.stringify(items) + separ() + 'opts=' + JSON.stringify(opts); - for (let i = 0; i < fP.length; i += 3) { - if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i+1] < fp.scale_ymin) || (fP[i+1] > fp.scale_ymax) || - (fP[i+2] < fp.scale_zmin) || (fP[i+2] > fp.scale_zmax)) continue; - if (step > 1) { - select = (select+1) % step; - if (select !== 0) continue; - } + menu.add('Direct link', () => window.open(addr)); + menu.add('Only items', () => window.open(addr + '&nobrowser')); + this.fillSettingsMenu(menu); + } else if (onlineprop) + this.fillOnlineMenu(menu, onlineprop, itemname); + else { + const sett = getDrawSettings(hitem._kind, 'nosame'); - index[icnt++] = i; + // allow to draw item even if draw function is not defined + if (hitem._can_draw) { + if (!sett.opts) + sett.opts = ['']; + if (sett.opts.indexOf('') < 0) + sett.opts.unshift(''); + } - pnts.addPoint(fp.grx(fP[i]), fp.gry(fP[i+1]), fp.grz(fP[i+2])); - } + if (sett.opts) { + menu.addDrawMenu('Draw', sett.opts, arg => this.display(itemname, arg), + 'Draw item in the new frame'); - return pnts.createPoints({ color: this.getColor(poly.fMarkerColor), style: poly.fMarkerStyle }).then(mesh => { - mesh.tip_color = (poly.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - mesh.tip_name = poly.fName || 'Poly3D'; - mesh.poly = poly; - mesh.fp = fp; - mesh.scale0 = 0.7*pnts.scale; - mesh.index = index; + const active_frame = this.disp?.getActiveFrame(); - fp.add3DMesh(mesh, this, true); + if (!sett.noappend && active_frame && (getElementCanvPainter(active_frame) || getElementMainPainter(active_frame))) { + menu.addDrawMenu('Superimpose', sett.opts, arg => this.dropItem(itemname, active_frame, arg), + 'Superimpose item with drawing on active frame'); + } + } - mesh.tooltip = function(intersect) { - let indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.index.length)) return null; + if (fileprop && sett.opts && !fileprop.localfile) { + const url = settings.NewTabUrl || exports.source_dir; + let filepath = qualifyURL(fileprop.fileurl); + if (filepath.indexOf(url) === 0) + filepath = filepath.slice(url.length); + filepath = `${fileprop.kind}=${filepath}`; + if (fileprop.itemname) { + let name = fileprop.itemname; + if (name.search(/\+| |,/) >= 0) + name = `'${name}'`; + filepath += `&item=${name}`; + } - indx = this.index[indx]; + let arg0 = 'nobrowser'; + if (settings.WithCredentials) + arg0 += '&with_credentials'; + if (settings.NewTabUrlPars) + arg0 += '&' + settings.NewTabUrlPars; + if (settings.NewTabUrlExportSettings) { + if (gStyle.fOptStat !== 1111) + arg0 += `&optstat=${gStyle.fOptStat}`; + if (gStyle.fOptFit) + arg0 += `&optfit=${gStyle.fOptFit}`; + if (gStyle.fOptDate) + arg0 += `&optdate=${gStyle.fOptDate}`; + if (gStyle.fOptFile) + arg0 += `&optfile=${gStyle.fOptFile}`; + if (gStyle.fOptTitle !== 1) + arg0 += `&opttitle=${gStyle.fOptTitle}`; + if (settings.TimeZone === 'UTC') + arg0 += '&utc'; + else if (settings.TimeZone === 'Europe/Berlin') + arg0 += '&cet'; + else if (settings.TimeZone) + arg0 += `&timezone='${settings.TimeZone}'`; + if (Math.abs(gStyle.fDateX - 0.01) > 1e-3) + arg0 += `&datex=${gStyle.fDateX.toFixed(3)}`; + if (Math.abs(gStyle.fDateY - 0.01) > 1e-3) + arg0 += `&datey=${gStyle.fDateY.toFixed(3)}`; + if (gStyle.fHistMinimumZero) + arg0 += '&histzero'; + if (settings.DarkMode) + arg0 += '&dark=on'; + if (!settings.UseStamp) + arg0 += '&usestamp=off'; + if (settings.OnlyLastCycle) + arg0 += '&lastcycle'; + if (settings.OptimizeDraw !== 1) + arg0 += `&optimize=${settings.OptimizeDraw}`; + if (settings.MaxRanges !== 200) + arg0 += `&maxranges=${settings.MaxRanges}`; + if (settings.FuncAsCurve) + arg0 += '&tf1=curve'; + if (!settings.ToolBar && !settings.Tooltip && !settings.ContextMenu && !settings.Zooming && !settings.MoveResize && !settings.DragAndDrop) + arg0 += '&interactive=0'; + else if (!settings.ContextMenu) + arg0 += '&nomenu'; + } - const fp = this.fp, - grx = fp.grx(this.poly.fP[indx]), - gry = fp.gry(this.poly.fP[indx+1]), - grz = fp.grz(this.poly.fP[indx+2]); + menu.addDrawMenu('Draw in new tab', sett.opts, + arg => window.open(`${url}?${arg0}&${filepath}&opt=${arg}`), + 'Draw item in the new browser tab or window'); + } - return { - x1: grx - this.scale0, - x2: grx + this.scale0, - y1: gry - this.scale0, - y2: gry + this.scale0, - z1: grz - this.scale0, - z2: grz + this.scale0, - color: this.tip_color, - lines: [this.tip_name, - 'pnt: ' + indx/3, - 'x: ' + fp.axisAsText('x', this.poly.fP[indx]), - 'y: ' + fp.axisAsText('y', this.poly.fP[indx+1]), - 'z: ' + fp.axisAsText('z', this.poly.fP[indx+2]) - ] - }; - }; + if ((sett.expand || sett.get_expand) && (hitem._more || hitem._more === undefined)) { + if (hitem._childs === undefined) + menu.add('Expand', () => this.expandItem(itemname), 'Expand content of object'); + else { + if (sett.handle?.pm || (hitem._childs.length > 25)) { + menu.add('Filter...', () => menu.input('Enter items to select', hitem._filter, f => { + const changed = hitem._filter !== f; + hitem._filter = f; + if (changed) + this.updateTreeNode(hitem); + }), 'Filter out items based on input pattern'); + } - fp.render3D(100); // set timeout to be able draw other points + menu.add('Unexpand', () => { + hitem._more = true; + delete hitem._childs; + delete hitem._isopen; + if (hitem.expand_item) + delete hitem._expand; + this.updateTreeNode(hitem); + }, 'Remove all childs from hierarchy'); + } + } - return this; - }); -} + if (hitem._kind === getKindForType(clTStyle)) + menu.add('Apply', () => this.applyStyle(itemname)); + } -/** @summary Show TTree::Draw progress during processing - * @private */ -TDrawSelector.prototype.ShowProgress = function(value) { - let msg, ret; - if ((value === undefined) || !Number.isFinite(value)) - msg = ret = ''; - else if (this._break) { - msg = 'Breaking ... '; - ret = 'break'; - } else { - if (this.last_progress !== value) { - const diff = value - this.last_progress; - if (!this.aver_diff) this.aver_diff = diff; - this.aver_diff = diff * 0.3 + this.aver_diff * 0.7; - } + if (isFunc(hitem._menu)) + hitem._menu(menu, hitem, this); - this.last_progress = value; + if (menu.size() > 0) { + menu.tree_node = elem.parentNode; + if (menu.separ) + menu.separator(); // add separator at the end + menu.add('Close'); + menu.show(); + } + }); // end menu creation - let ndig = 0; - if (this.aver_diff <= 0) - ndig = 0; - else if (this.aver_diff < 0.0001) - ndig = 3; - else if (this.aver_diff < 0.001) - ndig = 2; - else if (this.aver_diff < 0.01) - ndig = 1; - msg = `TTree draw ${(value * 100).toFixed(ndig)} % `; + return false; } - showProgress(msg, -1, () => { this._break = 1; }); - return ret; -}; - -/** @summary Draw result of tree drawing - * @private */ -async function drawTreeDrawResult(dom, obj, opt) { - const typ = obj?._typename; + /** @summary Starts player for specified item + * @desc Same as 'Player' context menu + * @param {string} itemname - item name for which player should be started + * @param {string} [option] - extra options for the player + * @return {Promise} when ready */ + async player(itemname, option) { + const item = this.findItem(itemname); - if (!typ || !isStr(typ)) - return Promise.reject(Error('Object without type cannot be draw with TTree')); + if (!isStr(item?._player)) + return null; - if (typ.indexOf(clTH1) === 0) - return TH1Painter.draw(dom, obj, opt); - if (typ.indexOf(clTH2) === 0) - return TH2Painter.draw(dom, obj, opt); - if (typ.indexOf(clTH3) === 0) - return TH3Painter.draw(dom, obj, opt); - if (typ.indexOf(clTGraph) === 0) - return TGraphPainter.draw(dom, obj, opt); - if ((typ === clTPolyMarker3D) && obj.$hist) { - return TH3Painter.draw(dom, obj.$hist, opt).then(() => { - const p2 = new ObjectPainter(dom, obj, opt); - p2.addToPadPrimitives(); - p2.redraw = drawPolyMarker3D$1; - return p2.redraw(); - }); - } + let player_func; - return Promise.reject(Error(`Object of type ${typ} cannot be draw with TTree`)); -} + if (item._module) { + const hh = await this.importModule(item._module); + player_func = hh ? hh[item._player] : null; + } else { + if (item._prereq || (item._player.indexOf('JSROOT.') >= 0)) + await this.loadScripts('', item._prereq); + player_func = findFunction(item._player); + } + if (!isFunc(player_func)) + return null; -/** @summary Handle callback function with progress of tree draw - * @private */ -async function treeDrawProgress(obj, final) { - // no need to update drawing if previous is not yet completed - if (!final && !this.last_pr) - return; + await this.createDisplay(); + return player_func(this, itemname, option); + } - if (this.dump || this.testio) { - if (!final) return; - if (isBatchMode()) { - const painter = new BasePainter(this.drawid); - painter.selectDom().property('_json_object_', obj); - return painter; - } - if (isFunc(internals.drawInspector)) - return internals.drawInspector(this.drawid, obj); - const str = create$1(clTObjString); - str.fString = toJSON(obj, 2); - return drawRawText(this.drawid, str); + /** @summary Checks if item can be displayed with given draw option + * @private */ + canDisplay(item, drawopt) { + if (!item) + return false; + if (item._player) + return true; + if (item._can_draw !== undefined) + return item._can_draw; + if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0)) + return true; + const handle = getDrawHandle(item._kind, drawopt); + return canDrawHandle(handle); } - // complex logic with intermediate update - // while TTree reading not synchronized with drawing, - // next portion can appear before previous is drawn - // critical is last drawing which should wait for previous one - // therefore last_pr is kept as inidication that promise is not yet processed + /** @summary Returns true if given item displayed + * @param {string} itemname - item name */ + isItemDisplayed(itemname) { + const mdi = this.getDisplay(); + return mdi?.findFrame(itemname) !== null; + } - if (!this.last_pr) this.last_pr = Promise.resolve(true); + /** @summary Display specified item + * @param {string} itemname - item name + * @param {string} [drawopt] - draw option for the item + * @param {string|Object} [dom] - place where to draw item, same as for @ref draw function + * @param {boolean} [interactive] - if display was called in interactive mode, will activate selected drawing + * @return {Promise} with created painter object */ + async display(itemname, drawopt, dom = null, interactive = false) { + const display_itemname = itemname; + let painter = null, + updating = false, + item = null, + frame_name = itemname; - return this.last_pr.then(() => { - if (this.obj_painter) - this.last_pr = this.obj_painter.redrawObject(obj).then(() => this.obj_painter); - else if (!obj) { - if (final) console.log('no result after tree drawing'); - this.last_pr = false; // return false indicating no drawing is done - } else { - this.last_pr = drawTreeDrawResult(this.drawid, obj).then(p => { - this.obj_painter = p; - if (!final) this.last_pr = null; - return p; // return painter for histogram - }); + // only to support old API where dom was not there + if ((dom === true) || (dom === false)) { + interactive = dom; + dom = null; } - return final ? this.last_pr : null; - }); -} + if (isStr(dom) && (dom.indexOf('frame:') === 0)) { + frame_name = dom.slice(6); + dom = null; + } + const complete = (respainter, err) => { + if (err) + console.log('When display ', itemname, 'got', err); -/** @summary Create painter to perform tree drawing on server side - * @private */ -function createTreePlayer(player) { - player.draw_first = true; + if (updating && item) + delete item._doing_update; + if (!updating) + showProgress(); + if (isFunc(respainter?.setItemName)) { + respainter.setItemName(display_itemname, updating ? null : drawopt, this); // mark painter as created from hierarchy - player.configureOnline = function(itemname, url, askey, root_version, expr) { - this.setItemName(itemname, '', this); - this.url = url; - this.root_version = root_version; - this.askey = askey; - this.draw_expr = expr; - }; + if (item && !item._painter) + item._painter = respainter; + } - player.configureTree = function(tree) { - this.local_tree = tree; - }; + return respainter || painter; + }; - player.showExtraButtons = function(args) { - const main = this.selectDom(), - numentries = this.local_tree?.fEntries || 0; + return this.createDisplay().then(mdi => { + if (!mdi) + return complete(); - main.select('.treedraw_more').remove(); // remove more button first + item = this.findItem(display_itemname); - main.select('.treedraw_buttons').node().innerHTML += - 'Cut: '+ - 'Opt: '+ - `Num: `+ - `First: `+ - ''; + if (item?._player) + return this.player(display_itemname, drawopt).then(res => complete(res)); - main.select('.treedraw_exe').on('click', () => this.performDraw()); - main.select('.treedraw_cut').property('value', args?.parse_cut || '').on('change', () => this.performDraw()); - main.select('.treedraw_opt').property('value', args?.drawopt || '').on('change', () => this.performDraw()); - main.select('.treedraw_number').attr('value', args?.numentries || ''); // .on('change', () => this.performDraw()); - main.select('.treedraw_first').attr('value', args?.firstentry || ''); // .on('change', () => this.performDraw()); - main.select('.treedraw_clear').on('click', () => cleanup(this.drawid)); - }; + updating = isStr(drawopt) && (drawopt.indexOf('update:') === 0); - player.showPlayer = function(args) { - const main = this.selectDom(); + if (updating) { + drawopt = drawopt.slice(7); + if (!item || item._doing_update) + return complete(); + item._doing_update = true; + } - this.drawid = 'jsroot_tree_player_' + internals.id_counter++ + '_draw'; + if (item && !this.canDisplay(item, drawopt)) + return complete(); - const show_extra = args?.parse_cut || args?.numentries || args?.firstentry; + const use_dflt_opt = drawopt === kDfltDrawOpt; + if (use_dflt_opt) + drawopt = ''; - main.html('
    '+ - '
    ' + - '' + - 'Expr:'+ - '' + - '' + - '
    ' + - '

    ' + - `
    ` + - '
    '); + if (!updating) + showProgress(`Loading ${display_itemname} ...`); - // only when main html element created, one can set painter - // ObjectPainter allow such usage of methods from BasePainter - this.setTopPainter(); + return this.getObject(display_itemname, drawopt).then(result => { + if (!updating) + showProgress(); - if (this.local_tree) { - main.select('.treedraw_buttons') - .attr('title', 'Tree draw player for: ' + this.local_tree.fName); - } - main.select('.treedraw_exe').on('click', () => this.performDraw()); - main.select('.treedraw_varexp') - .attr('value', args?.parse_expr || this.draw_expr || 'px:py') - .on('change', () => this.performDraw()); - main.select('.treedraw_varexp_info') - .attr('title', 'Example of valid draw expressions:\n' + - ' px - 1-dim draw\n' + - ' px:py - 2-dim draw\n' + - ' px:py:pz - 3-dim draw\n' + - ' px+py:px-py - use any expressions\n' + - ' px:py>>Graph - create and draw TGraph\n' + - ' px:py>>dump - dump extracted variables\n' + - ' px:py>>h(50,-5,5,50,-5,5) - custom histogram\n' + - ' px:py;hbins:100 - custom number of bins'); + if (!item) + item = result.item; + let obj = result.obj; - if (show_extra) - this.showExtraButtons(args); - else - main.select('.treedraw_more').on('click', () => this.showExtraButtons(args)); + if (!obj) + return complete(); - this.checkResize(); + if (!updating) + showProgress(`Drawing ${display_itemname} ...`); - registerForResize(this); - }; + let handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; - player.getValue = function(sel) { - const elem = this.selectDom().select(sel); - if (elem.empty()) return; - const val = elem.property('value'); - if (val !== undefined) return val; - return elem.attr('value'); - }; + if (handle?.draw_field && obj[handle.draw_field]) { + obj = obj[handle.draw_field]; + if (!drawopt) + drawopt = handle.draw_field_opt || ''; + handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; + } - player.performLocalDraw = function() { - if (!this.local_tree) return; + if (use_dflt_opt && !drawopt && handle?.dflt && (handle.dflt !== kExpand)) + drawopt = handle.dflt; - const frame = this.selectDom(), - args = { expr: this.getValue('.treedraw_varexp') }; + if (dom) + return (updating ? redraw : draw)(dom, obj, drawopt).then(p => complete(p)).catch(err => complete(null, err)); - if (frame.select('.treedraw_more').empty()) { - args.cut = this.getValue('.treedraw_cut'); - if (!args.cut) delete args.cut; + let did_activate = false; + const arr = []; - args.drawopt = this.getValue('.treedraw_opt'); - if (args.drawopt === 'dump') { args.dump = true; args.drawopt = ''; } - if (!args.drawopt) delete args.drawopt; + mdi.forEachPainter((p, frame) => { + if (p.getItemName() !== display_itemname) + return; - args.numentries = parseInt(this.getValue('.treedraw_number')); - if (!Number.isInteger(args.numentries)) delete args.numentries; + const itemopt = p.getItemDrawOpt(); + if (use_dflt_opt && interactive) + drawopt = itemopt; - args.firstentry = parseInt(this.getValue('.treedraw_first')); - if (!Number.isInteger(args.firstentry)) delete args.firstentry; - } + // verify that object was drawn with same option as specified now (if any) + if (!updating && drawopt && (itemopt !== drawopt)) + return; - /* if (args.drawopt) */ cleanup(this.drawid); + if (interactive && !did_activate) { + did_activate = true; + mdi.activateFrame(frame); + } - args.drawid = this.drawid; + if (isFunc(p.redrawObject)) { + const pr = p.redrawObject(obj, drawopt); - args.progress = treeDrawProgress.bind(args); + if (pr) { + painter = p; + arr.push(pr); + } + } + }); - treeDraw(this.local_tree, args).then(obj => args.progress(obj, true)); - }; + if (painter) + return Promise.all(arr).then(() => complete()); - player.getDrawOpt = function() { - let res = 'player'; - const expr = this.getValue('.treedraw_varexp'); - if (expr) res += ':' + expr; - return res; - }; + if (updating) { + console.warn(`something went wrong - did not found painter when doing update of ${display_itemname}`); + return complete(); + } - player.performDraw = function() { - if (this.local_tree) - return this.performLocalDraw(); + const frame = mdi.findFrame(frame_name, true); + cleanup(frame); + mdi.activateFrame(frame); - const frame = this.selectDom(); - let url = this.url + '/exe.json.gz?compact=3&method=Draw', - expr = this.getValue('.treedraw_varexp'), - hname = 'h_tree_draw', option = ''; - const pos = expr.indexOf('>>'); + return draw(frame, obj, drawopt) + .then(p => complete(p)) + .catch(err => complete(null, err)); + }); + }); + } - if (pos < 0) - expr += `>>${hname}`; - else { - hname = expr.slice(pos+2); - if (hname[0] === '+') hname = hname.slice(1); - const pos2 = hname.indexOf('('); - if (pos2 > 0) hname = hname.slice(0, pos2); - } + /** @summary Enable drag of the element + * @private */ + enableDrag(d3elem /* , itemname */) { + d3elem.attr('draggable', 'true').on('dragstart', function(ev) { + const itemname = this.parentNode.parentNode.getAttribute('item'); + ev.dataTransfer.setData('item', itemname); + }); + } - if (frame.select('.treedraw_more').empty()) { - const cut = this.getValue('.treedraw_cut'); - let nentries = this.getValue('.treedraw_number'), - firstentry = this.getValue('.treedraw_first'); + /** @summary Enable drop on the frame + * @private */ + enableDrop(frame) { + const h = this; + select(frame).on('dragover', ev => { + const itemname = ev.dataTransfer.getData('item'), + ditem = h.findItem(itemname); + if (getTypeForKind(ditem?._kind)) + ev.preventDefault(); // let accept drop, otherwise it will be refused + }).on('dragenter', function() { + select(this).classed('jsroot_drag_area', true); + }).on('dragleave', function() { + select(this).classed('jsroot_drag_area', false); + }).on('drop', function(ev) { + select(this).classed('jsroot_drag_area', false); + const itemname = ev.dataTransfer.getData('item'); + if (!itemname) + return; + const painters = [], elements = []; + let pad_painter = getElementCanvPainter(this), + target = ev.target; + pad_painter?.forEachPainter(pp => { + painters.push(pp); + elements.push(pp.getPadSvg().node()); + }, 'pads'); + // only if there are sub-pads - try to find them + if (painters.length > 1) { + while (target && (target !== this)) { + const p = elements.indexOf(target); + if (p > 0) { + pad_painter = painters[p]; + break; + } + target = target.parentNode; + } + } + h.dropItem(itemname, pad_painter || this); + }); + } - option = this.getValue('.treedraw_opt'); + /** @summary Remove all drop handlers on the frame + * @private */ + clearDrop(frame) { + select(frame).on('dragover', null).on('dragenter', null).on('dragleave', null).on('drop', null); + } - url += `&prototype="const char*,const char*,Option_t*,Long64_t,Long64_t"&varexp="${expr}"&selection="${cut}"`; + /** @summary Drop item on specified element for drawing + * @return {Promise} when completed + * @private */ + async dropItem(itemname, dom, opt) { + if (!opt || !isStr(opt)) + opt = ''; - // provide all optional arguments - default value kMaxEntries not works properly in ROOT6 - if (!nentries) nentries = 'TTree::kMaxEntries'; // kMaxEntries available since ROOT 6.05/03 - if (!firstentry) firstentry = '0'; - url += `&option="${option}"&nentries=${nentries}&firstentry=${firstentry}`; - } else - url += `&prototype="Option_t*"&opt="${expr}"`; + const drop_complete = (drop_painter, is_main) => { + if (!is_main && isFunc(drop_painter?.setItemName)) + drop_painter.setItemName(itemname, null, this); + return drop_painter; + }; - url += `&_ret_object_=${hname}`; + if (itemname === '$legend') { + const cp = getElementCanvPainter(dom); + if (isFunc(cp?.buildLegend)) + return cp.buildLegend(0, 0, 0, 0, '', opt).then(lp => drop_complete(lp)); + console.error('Not possible to build legend'); + return drop_complete(null); + } - const submitDrawRequest = () => { - httpRequest(url, 'object').then(res => { - cleanup(this.drawid); - drawTreeDrawResult(this.drawid, res, option); - }); - }; + return this.getObject(itemname).then(res => { + if (!res.obj) + return null; - this.draw_expr = expr; + const mp = getElementMainPainter(dom); - if (this.askey) { - // first let read tree from the file - this.askey = false; - httpRequest(this.url + '/root.json.gz?compact=3', 'text').then(submitDrawRequest); - } else - submitDrawRequest(); - }; + if (isFunc(mp?.performDrop)) + return mp.performDrop(res.obj, itemname, res.item, opt).then(p => drop_complete(p, mp === p)); - player.checkResize = function(/* arg */) { - resize(this.drawid); - }; + const sett = res.obj._typename ? getDrawSettings(getKindForType(res.obj._typename)) : null; + if (!sett?.draw) + return null; - return player; -} + const cp = getElementCanvPainter(dom); + if (cp) { + if (sett?.has_same && mp) + opt = 'same ' + opt; + } else + this.cleanupFrame(dom); -/** @summary function used with THttpServer to assign player for the TTree object - * @private */ -function drawTreePlayer(hpainter, itemname, askey, asleaf) { - let item = hpainter.findItem(itemname), - expr = '', leaf_cnt = 0; - const top = hpainter.getTopOnlineItem(item); - if (!item || !top) return null; + // if drop on sub-pad painter - call add pad buttons + if (isFunc(dom?.addPadButtons)) + dom.addPadButtons(); - if (asleaf) { - expr = item._name; - while (item && !item._ttree) item = item._parent; - if (!item) return null; - itemname = hpainter.itemFullName(item); + return draw(dom, res.obj, opt).then(p => drop_complete(p, mp === p)); + }); } - const url = hpainter.getOnlineItemUrl(itemname); - if (!url) return null; + /** @summary Update specified items + * @desc Method can be used to fetch new objects and update all existing drawings + * @param {string|array|boolean} arg - either item name or array of items names to update or true if only automatic items will be updated + * @return {Promise} when ready */ + async updateItems(arg) { + if (!this.disp) + return false; - const root_version = top._root_version || 400129, // by default use version number 6-27-01 + const allitems = [], options = []; + let only_auto_items = false, want_update_all = false; - mdi = hpainter.getDisplay(); - if (!mdi) return null; + if (isStr(arg)) + arg = [arg]; + else if (!isObject(arg)) { + if (arg === undefined) + arg = !this.isMonitoring(); + want_update_all = true; + only_auto_items = Boolean(arg); + } - const frame = mdi.findFrame(itemname, true); - if (!frame) return null; + // first collect items + this.disp.forEachPainter(p => { + const itemname = p.getItemName(); - const divid = select(frame).attr('id'), - player = new BasePainter(divid); + if (!isStr(itemname) || (allitems.indexOf(itemname) >= 0)) + return; - if (item._childs && !asleaf) { - for (let n = 0; n < item._childs.length; ++n) { - const leaf = item._childs[n]; - if (leaf && leaf._kind && (leaf._kind.indexOf(prROOT + 'TLeaf') === 0) && (leaf_cnt < 2)) { - if (leaf_cnt++ > 0) expr += ':'; - expr += leaf._name; - } + if (want_update_all) { + const item = this.findItem(itemname); + if (!item || item._not_monitor || item._player) + return; + if (!('_always_monitor' in item)) { + const handle = getDrawHandle(item._kind); + let forced = false; + if (handle?.monitor !== undefined) { + if ((handle.monitor === false) || (handle.monitor === 'never')) + return; + if (handle.monitor === 'always') + forced = true; + } + if (!forced && only_auto_items) + return; + } + } else if (arg.indexOf(itemname) < 0) + return; + + allitems.push(itemname); + options.push('update:' + p.getItemDrawOpt()); + }, true); // only visible panels are considered + + // force all files to read again (normally in non-browser mode) + if (this.files_monitoring && !only_auto_items && want_update_all) { + this.forEachRootFile(item => { + this.forEachItem(fitem => { delete fitem._readobj; }, item); + delete item._file; + }); } - } - createTreePlayer(player); - player.configureOnline(itemname, url, askey, root_version, expr); - player.showPlayer(); + return this.displayItems(allitems, options); + } - return player; -} + /** @summary Display all provided elements + * @return {Promise} when drawing finished + * @private */ + async displayItems(items, options) { + if (!items?.length) + return true; -/** @summary function used with THttpServer when tree is not yet loaded - * @private */ -function drawTreePlayerKey(hpainter, itemname) { - return drawTreePlayer(hpainter, itemname, true); -} + const h = this; -/** @summary function used with THttpServer when tree is not yet loaded - * @private */ -function drawLeafPlayer(hpainter, itemname) { - return drawTreePlayer(hpainter, itemname, false, true); -} + if (!options) + options = []; + while (options.length < items.length) + options.push(kDfltDrawOpt); -/** @summary function called from draw() - * @desc just envelope for real TTree::Draw method which do the main job - * Can be also used for the branch and leaf object - * @private */ -async function drawTree(dom, obj, opt) { - let tree = obj, args = opt; + if ((options.length === 1) && (options[0] === 'iotest')) { + this.clearHierarchy(); + select('#' + this.disp_frameid).html('').append('h2').text('Start I/O test'); - if (obj._typename === clTBranchFunc) { - // fictional object, created only in browser - args = { expr: `.${obj.func}()`, branch: obj.branch }; - if (opt && opt.indexOf('dump') === 0) - args.expr += '>>' + opt; - else if (opt) - args.expr += opt; - tree = obj.branch.$tree; - } else if (obj.$branch) { - // this is drawing of the single leaf from the branch - args = { expr: `.${obj.fName}${opt || ''}`, branch: obj.$branch }; - if ((args.branch.fType === kClonesNode) || (args.branch.fType === kSTLNode)) { - // special case of size - args.expr = opt; - args.direct_branch = true; + const tm0 = new Date(); + return this.getObject(items[0]).then(res => { + const tm1 = new Date(); + select('#' + this.disp_frameid).append('h2').text(`Item ${items[0]} reading ` + (res?.obj ? `type ${res?.obj._typename} time = ${tm1.getTime() - tm0.getTime()}ms` : 'fail')); + return true; + }); } - tree = obj.$branch.$tree; - } else if (obj.$tree) { - // this is drawing of the branch + const dropitems = new Array(items.length), + dropopts = new Array(items.length), + images = new Array(items.length); - // if generic object tried to be drawn without specifying any options, it will be just dump - if (!opt && obj.fStreamerType && (obj.fStreamerType !== kTString) && - (obj.fStreamerType >= kObject) && (obj.fStreamerType <= kAnyP)) opt = 'dump'; + // First of all check that items are exists, look for cycle extension and plus sign + for (let i = 0; i < items.length; ++i) { + dropitems[i] = dropopts[i] = null; - args = { expr: opt, branch: obj }; - tree = obj.$tree; - } else { - if (!args) args = 'player'; - if (isStr(args)) args = { expr: args }; - } + const item = items[i]; + let can_split = true; - if (!tree) - throw Error('No TTree object available for TTree::Draw'); + if (item?.indexOf('img:') === 0) { + images[i] = true; + continue; + } - if (isStr(args.expr)) { - const p = args.expr.indexOf('player'); - if (p === 0) { - args.player = true; - args.expr = args.expr.slice(6); - if (args.expr[0] === ':') args.expr = args.expr.slice(1); - } else if ((p >= 0) && (p === args.expr.length-6)) { - args.player = true; - args.expr = args.expr.slice(0, p); - if ((p > 0) && (args.expr[p-1] === ';')) args.expr = args.expr.slice(0, p-1); - } - } + if ((item?.length > 1) && (item.at(0) === '\'') && (item.at(-1) === '\'')) { + items[i] = item.slice(1, item.length - 1); + can_split = false; + } - let painter; + let elem = h.findItem({ name: items[i], check_keys: true }); + if (elem) { + items[i] = h.itemFullName(elem); + continue; + } - if (args.player) { - painter = new ObjectPainter(dom, obj, opt); - createTreePlayer(painter); - painter.configureTree(tree); - painter.showPlayer(args); - args.drawid = painter.drawid; - } else - args.drawid = dom; + if (can_split && (items[i].at(0) === '[') && (items[i].at(-1) === ']')) { + dropitems[i] = parseAsArray(items[i]); + items[i] = dropitems[i].shift(); + } else if (can_split && (items[i].indexOf('+') > 0)) { + dropitems[i] = items[i].split('+'); + items[i] = dropitems[i].shift(); + } + if (dropitems[i]?.length) { + // allow to specify _same_ item in different file + for (let j = 0; j < dropitems[i].length; ++j) { + const pos = dropitems[i][j].indexOf('_same_'); + if ((pos > 0) && (h.findItem(dropitems[i][j]) === null)) + dropitems[i][j] = dropitems[i][j].slice(0, pos) + items[i].slice(pos); - // use in result handling same function as for progress handling + elem = h.findItem({ name: dropitems[i][j], check_keys: true }); + if (elem) + dropitems[i][j] = h.itemFullName(elem); + } - args.progress = treeDrawProgress.bind(args); + if ((options[i].at(0) === '[') && (options[i].at(-1) === ']')) { + dropopts[i] = parseAsArray(options[i]); + options[i] = dropopts[i].shift(); + } else if (options[i].indexOf('+') > 0) { + dropopts[i] = options[i].split('+'); + options[i] = dropopts[i].shift(); + } else + dropopts[i] = []; - let pr; - if (args.expr === 'testio') { - args.testio = true; - args.showProgress = msg => showProgress(msg, -1, () => { args._break = 1; }); - pr = treeIOTest(tree, args); - } else if (args.expr || args.branch) - pr = treeDraw(tree, args); - else - return painter; - return pr.then(res => args.progress(res, true)); -} + while (dropopts[i].length < dropitems[i].length) + dropopts[i].push(''); + } -var TTree = /*#__PURE__*/Object.freeze({ -__proto__: null, -drawLeafPlayer: drawLeafPlayer, -drawTree: drawTree, -drawTreePlayer: drawTreePlayer, -drawTreePlayerKey: drawTreePlayerKey -}); + // also check if subsequent items has _same_, than use name from first item + const pos = items[i].indexOf('_same_'); + if ((pos > 0) && !h.findItem(items[i]) && (i > 0)) + items[i] = items[i].slice(0, pos) + items[0].slice(pos); + + elem = h.findItem({ name: items[i], check_keys: true }); + if (elem) + items[i] = h.itemFullName(elem); + } + + // now check that items can be displayed + for (let n = items.length - 1; n >= 0; --n) { + if (images[n]) + continue; + const hitem = h.findItem(items[n]); + if (!hitem || h.canDisplay(hitem, options[n])) + continue; + // try to expand specified item + h.expandItem(items[n], null, true); + items.splice(n, 1); + options.splice(n, 1); + dropitems.splice(n, 1); + } + + if (!items.length) + return true; + + const frame_names = new Array(items.length), items_wait = new Array(items.length); + for (let n = 0; n < items.length; ++n) { + items_wait[n] = 0; + let fname = items[n], k = 0; + if (items.indexOf(fname) < n) + items_wait[n] = true; // if same item specified, one should wait first drawing before start next + const p = options[n].indexOf('frameid:'); + if (p >= 0) { + fname = options[n].slice(p + 8); + options[n] = options[n].slice(0, p); + } else { + while (frame_names.indexOf(fname) >= 0) + fname = items[n] + '_' + k++; + } + frame_names[n] = fname; + } -/** - * @summary Painter class for THStack - * - * @private - */ + // now check if several same items present - select only one for the drawing + // if draw option includes 'main', such item will be drawn first + for (let n = 0; n < items.length; ++n) { + if (items_wait[n] !== 0) + continue; + let found_main = n; + for (let k = 0; k < items.length; ++k) { + if ((items[n] === items[k]) && (options[k].indexOf('main') >= 0)) + found_main = k; + } + for (let k = 0; k < items.length; ++k) { + if (items[n] === items[k]) + items_wait[k] = (found_main !== k); + } + } -class THStackPainter extends ObjectPainter { + return this.createDisplay().then(mdi => { + if (!mdi) + return false; - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} stack - THStack object - * @param {string} [opt] - draw options */ - constructor(dom, stack, opt) { - super(dom, stack, opt); - this.firstpainter = null; - this.painters = []; // keep painters to be able update objects - } + const doms = new Array(items.length); - /** @summary Cleanup THStack painter */ - cleanup() { - this.getPadPainter()?.cleanPrimitives(objp => { return (objp === this.firstpainter) || (this.painters.indexOf(objp) >= 0); }); - delete this.firstpainter; - delete this.painters; - super.cleanup(); - } + // Than create empty frames for each item + for (let i = 0; i < items.length; ++i) { + if (options[i].indexOf('update:')) { + mdi.createFrame(frame_names[i]); + doms[i] = 'frame:' + frame_names[i]; + } + } - /** @summary Build sum of all histograms - * @desc Build a separate list fStack containing the running sum of all histograms */ - buildStack(stack) { - if (!stack.fHists) return false; - const nhists = stack.fHists.arr.length; - if (nhists <= 0) return false; - const lst = create$1(clTList); - lst.Add(clone(stack.fHists.arr[0]), stack.fHists.opt[0]); - for (let i = 1; i < nhists; ++i) { - const hnext = clone(stack.fHists.arr[i]), - hnextopt = stack.fHists.opt[i], - hprev = lst.arr[i-1], - xnext = hnext.fXaxis, xprev = hprev.fXaxis; + function dropNextItem(indx, painter) { + if (painter && dropitems[indx]?.length) + return h.dropItem(dropitems[indx].shift(), painter.getDrawDom(), dropopts[indx].shift()).then(() => dropNextItem(indx, painter)); - let match = (xnext.fNbins === xprev.fNbins) && - (xnext.fXmin === xprev.fXmin) && - (xnext.fXmax === xprev.fXmax); + dropitems[indx] = null; // mark that all drop items are processed + items[indx] = null; // mark item as ready - if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && - (Math.abs((xnext.fXmax - xnext.fXmin)/xnext.fNbins - (xprev.fXmax - xprev.fXmin)/xprev.fNbins) < 0.0001)) { - // simple extension of histogram to make sum - const arr = new Array(hprev.fNcells).fill(0); - for (let n = 1; n <= xnext.fNbins; ++n) - arr[n] = hnext.fArray[n]; - hnext.fNcells = hprev.fNcells; - Object.assign(xnext, xprev); - hnext.fArray = arr; - match = true; + for (let cnt = 0; cnt < items.length; ++cnt) { + if (items[cnt] === null) + continue; // ignore completed item + if (items_wait[cnt] && items.indexOf(items[cnt]) === cnt) { + items_wait[cnt] = false; + return h.display(items[cnt], options[cnt], doms[cnt]).then(drop_painter => dropNextItem(cnt, drop_painter)); + } + } } - if (!match) { - console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); - lst.Clear(); - return false; + + const promises = []; + + if (this.#one_by_one) { + function processNext(indx) { + if (indx >= items.length) + return true; + if (items_wait[indx]) + return processNext(indx + 1); + return h.display(items[indx], options[indx], doms[indx]) + .then(painter => dropNextItem(indx, painter)) + .then(() => processNext(indx + 1)); + } + promises.push(processNext(0)); + } else { + // We start display of all items parallel, but only if they are not the same + for (let i = 0; i < items.length; ++i) { + if (!items_wait[i]) + promises.push(h.display(items[i], options[i], doms[i]).then(painter => dropNextItem(i, painter))); + } } - // trivial sum of histograms - for (let n = 0; n < hnext.fArray.length; ++n) - hnext.fArray[n] += hprev.fArray[n]; + return Promise.all(promises).then(() => { + if (mdi?.createFinalBatchFrame && isBatchMode() && !isNodeJs()) + mdi.createFinalBatchFrame(); + }); + }); + } - lst.Add(hnext, hnextopt); - } - stack.fStack = lst; - return true; + /** @summary Reload hierarchy and refresh html code + * @return {Promise} when completed */ + async reload() { + if ('_online' in this.h) + return this.openOnline(this.h._online).then(() => this.refreshHtml()); + return false; } - /** @summary Returns stack min/max values */ - getMinMax(iserr) { - const stack = this.getObject(), - pad = this.getPadPainter().getRootPad(true); - let min = 0, max = 0; + /** @summary activate (select) specified item + * @param {Array} items - array of items names + * @param {boolean} [force] - if specified, all required sub-levels will be opened + * @private */ + activateItems(items, force) { + if (isStr(items)) + items = [items]; - const getHistMinMax = (hist, witherr) => { - const res = { min: 0, max: 0 }; - let domin = true, domax = true; - if (hist.fMinimum !== kNoZoom) { - res.min = hist.fMinimum; - domin = false; + const active = [], // array of elements to activate + update = []; // array of elements to update + this.forEachItem(item => { + if (item._background) { + active.push(item); + delete item._background; } - if (hist.fMaximum !== kNoZoom) { - res.max = hist.fMaximum; - domax = false; + }); + + const find_next = (itemname, prev_found) => { + if (itemname === undefined) { + // extract next element + if (!items.length) { + update.reverse().forEach(node => this.updateTreeNode(node)); + active.forEach(item => this.updateBackground(item, force)); + return; + } + itemname = items.shift(); } - if (!domin && !domax) return res; + let hitem = this.findItem(itemname); - let i1 = 1, i2 = hist.fXaxis.fNbins, j1 = 1, j2 = 1, first = true; + if (!hitem) { + const d = this.findItem({ name: itemname, last_exists: true, check_keys: true, allow_index: true }); + if (!d || !d.last) + return find_next(); + d.now_found = this.itemFullName(d.last); - if (hist.fXaxis.TestBit(EAxisBits.kAxisRange)) { - i1 = hist.fXaxis.fFirst; - i2 = hist.fXaxis.fLast; - } + if (force) { + // if after last expand no better solution found - skip it + if ((prev_found !== undefined) && (d.now_found === prev_found)) + return find_next(); - if (hist._typename.indexOf(clTH2) === 0) { - j2 = hist.fYaxis.fNbins; - if (hist.fYaxis.TestBit(EAxisBits.kAxisRange)) { - j1 = hist.fYaxis.fFirst; - j2 = hist.fYaxis.fLast; + return this.expandItem(d.now_found).then(res => { + if (!res) + return find_next(); + let newname = this.itemFullName(d.last); + if (newname) + newname += '/'; + find_next(newname + d.rest, d.now_found); + }); } - } - for (let j = j1; j <= j2; ++j) { - for (let i = i1; i <= i2; ++i) { - const val = hist.getBinContent(i, j), - err = witherr ? hist.getBinError(hist.getBin(i, j)) : 0; - if (domin && (first || (val-err < res.min))) res.min = val-err; - if (domax && (first || (val+err > res.max))) res.max = val+err; - first = false; - } + hitem = d.last; } - return res; - }; + if (hitem) { + // check that item is visible (opened), otherwise should enable parent - if (this.options.nostack) { - for (let i = 0; i < stack.fHists.arr.length; ++i) { - const resh = getHistMinMax(stack.fHists.arr[i], iserr); - if (i === 0) { - min = resh.min; max = resh.max; - } else { - min = Math.min(min, resh.min); - max = Math.max(max, resh.max); + let prnt = hitem._parent; + while (prnt) { + if (!prnt._isopen) { + if (force) { + prnt._isopen = true; + if (update.indexOf(prnt) < 0) + update.push(prnt); + } else { + hitem = prnt; break; + } + } + prnt = prnt._parent; } - } - } else { - min = getHistMinMax(stack.fStack.arr[0], iserr).min; - max = getHistMinMax(stack.fStack.arr[stack.fStack.arr.length-1], iserr).max; - } - - const adjustRange = () => { - if (pad && (pad.fLogv ?? (this.options.ndim === 1 ? pad.fLogy : pad.fLogz))) { - if (max <= 0) max = 1; - if (min <= 0) min = 1e-4*max; - const kmin = 1/(1 + 0.5*Math.log10(max / min)), - kmax = 1 + 0.2*Math.log10(max / min); - min *= kmin; - max *= kmax; - } else if ((min > 0) && (min < 0.05*max)) - min = 0; - }; - max *= (1 + gStyle.fHistTopMargin); - - adjustRange(); - - let max0 = max, min0 = min, zoomed = false; + hitem._background = 'LightSteelBlue'; + if (active.indexOf(hitem) < 0) + active.push(hitem); + } - if (stack.fMaximum !== kNoZoom) { - max = stack.fMaximum; - max0 = Math.max(max, max0); - zoomed = true; - } + find_next(); + }; - if (stack.fMinimum !== kNoZoom) { - min = stack.fMinimum; - min0 = Math.min(min, min0); - zoomed = true; + if (force && this.brlayout) { + if (!this.brlayout.browser_kind) + return this.createBrowser('float', true).then(() => find_next()); + if (!this.brlayout.browser_visible) + this.brlayout.toggleBrowserVisisbility(); } - if (zoomed) - adjustRange(); - else - min = max = kNoZoom; - - return { min, max, min0, max0, zoomed, hopt: `hmin:${min0};hmax:${max0};minimum:${min};maximum:${max}` }; + // use recursion + find_next(); } - /** @summary Provide draw options for the histogram */ - getHistDrawOption(hist, opt) { - let hopt = opt || hist.fOption || this.options.hopt; - if (hopt.toUpperCase().indexOf(this.options.hopt) < 0) - hopt += ' ' + this.options.hopt; - if (this.options.draw_errors && !hopt) - hopt = 'E'; - if (!this.options.pads) - hopt += ' same nostat' + this.options.auto; - return hopt; + /** @summary Check if item can be (potentially) expand + * @private */ + canExpandItem(item) { + if (!item) + return false; + if (item._expand) + return true; + const handle = getDrawHandle(item._kind, '::expand'); + return handle && canExpandHandle(handle); } - /** @summary Draw next stack histogram */ - async drawNextHisto(indx, pad_painter) { - const stack = this.getObject(), - hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length || 0; + /** @summary expand specified item + * @param {String} itemname - item name + * @return {Promise} when ready */ + async expandItem(itemname, d3cont, silent) { + const hitem = this.findItem(itemname), hpainter = this; - if (indx >= nhists) - return this; + if (!hitem && d3cont) + return; - const rindx = this.options.horder ? indx : nhists-indx-1, - subid = this.options.nostack ? `hists_${rindx}` : `stack_${rindx}`, - hist = hlst.arr[rindx], - hopt = this.getHistDrawOption(hist, hlst.opt[rindx]); + function doneExpandItem(_item) { + if (_item._childs === undefined) + _item._expand_miss = true; + else { + _item._isopen = true; + if (_item._parent && !_item._parent._isopen) { + _item._parent._isopen = true; // also show parent + if (!silent) + hpainter.updateTreeNode(_item._parent); + } else if (!silent) + hpainter.updateTreeNode(_item, d3cont); + } + return _item; + } - // handling of 'pads' draw option - if (pad_painter) { - const subpad_painter = pad_painter.getSubPadPainter(indx+1); - if (!subpad_painter) - return this; + async function doExpandItem(_item, _obj) { + delete _item._expand_miss; - const prev_name = subpad_painter.selectCurrentPad(subpad_painter.this_pad_name); + if (isStr(_item._expand)) + _item._expand = findFunction(_item._expand); - return this.hdraw_func(subpad_painter.getDom(), hist, hopt).then(subp => { - if (subp) { - subp.setSecondaryId(this, subid); - this.painters.push(subp); + if (!isFunc(_item._expand)) { + let handle = getDrawHandle(_item._kind, '::expand'); + + // in inspector show all members + if (handle?.expand_item && !hpainter._inspector) { + _obj = _obj[handle.expand_item]; + _item.expand_item = handle.expand_item; // remember that was expand item + handle = _obj?._typename ? getDrawHandle(getKindForType(_obj._typename), '::expand') : null; } - subpad_painter.selectCurrentPad(prev_name); - return this.drawNextHisto(indx+1, pad_painter); - }); - } - // special handling of stacked histograms - set $baseh object for correct drawing - // also used to provide tooltips - if ((rindx > 0) && !this.options.nostack) - hist.$baseh = hlst.arr[rindx - 1]; - // this number used for auto colors creation - if (this.options.auto) - hist.$num_histos = nhists; + if (handle?.expand || handle?.get_expand) { + if (isFunc(handle.expand)) + _item._expand = handle.expand; + else if (isStr(handle.expand)) { + if (!internals.ignore_v6) { + const v6 = await exports._ensureJSROOT(); + await v6.require(handle.prereq); + await v6._complete_loading(); + } + _item._expand = handle.expand = findFunction(handle.expand); + } else if (isFunc(handle.get_expand)) + _item._expand = handle.expand = await handle.get_expand(); + } + } - return this.hdraw_func(this.getDom(), hist, hopt).then(subp => { - subp.setSecondaryId(this, subid); - this.painters.push(subp); - return this.drawNextHisto(indx+1, pad_painter); - }); - } + // try to use expand function + if (_obj && isFunc(_item._expand)) { + const res = _item._expand(_item, _obj); + if (res) + return getPromise(res).then(() => doneExpandItem(_item)); + } - /** @summary Decode draw options of THStack painter */ - decodeOptions(opt) { - if (!this.options) this.options = {}; - Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }); + if (_obj && objectHierarchy(_item, _obj)) + return doneExpandItem(_item); - const stack = this.getObject(), - hist = stack.fHistogram || (stack.fHists ? stack.fHists.arr[0] : null) || (stack.fStack ? stack.fStack.arr[0] : null), + // mark as expand miss - behaves as normal object + _item._expand_miss = true; + return -1; + } - hasErrors = hist => { - if (hist.fSumw2 && (hist.fSumw2.length > 0)) { - for (let n = 0; n < hist.fSumw2.length; ++n) - if (hist.fSumw2[n] > 0) return true; - } - return false; - }; + let promise = Promise.resolve(-1); - if (hist && (hist._typename.indexOf(clTH2) === 0)) - this.options.ndim = 2; + if (hitem) { + // item marked as it cannot be expanded, also top item cannot be changed + if ((hitem._more === false) || (!hitem._parent && hitem._childs)) + return; - if ((this.options.ndim === 2) && !opt) - opt = 'lego1'; + if (hitem._childs && hitem._isopen) { + hitem._isopen = false; + if (!silent) + this.updateTreeNode(hitem, d3cont); + return; + } - if (stack.fHists && !this.options.nostack) { - for (let k = 0; k < stack.fHists.arr.length; ++k) - this.options.has_errors = this.options.has_errors || hasErrors(stack.fHists.arr[k]); + if (hitem._obj) + promise = doExpandItem(hitem, hitem._obj); } - this.options.nhist = stack.fHists?.arr?.length ?? 1; + return promise.then(res => { + if (res !== -1) + return res; // done - const d = new DrawOptions(opt); + showProgress('Loading ' + itemname); - this.options.nostack = d.check('NOSTACK'); - if (d.check('STACK')) this.options.nostack = false; - this.options.same = d.check('SAME'); + return this.getObject(itemname, silent ? 'hierarchy_expand' : 'hierarchy_expand_verbose').then(res2 => { + showProgress(); + if (res2.obj) + return doExpandItem(res2.item, res2.obj).then(res3 => { return res3 !== -1 ? res3 : undefined; }); + }); + }); + } - d.check('NOCLEAR'); // ignore noclear option + /** @summary Return main online item + * @private */ + getTopOnlineItem(item) { + if (item) { + while (item && (!('_online' in item))) + item = item._parent; + return item; + } - ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) this.options.auto += ' ' + f; }); + if (!this.h) + return null; + if (this.h._online) + return this.h; + if (this.h._childs && this.h._childs[0]?._online) + return this.h._childs[0]; + return null; + } - this.options.pads = d.check('PADS'); - if (this.options.pads) this.options.nostack = true; + /** @summary Call function for each item which corresponds to JSON file + * @private */ + forEachJsonFile(func) { + if (!this.h) + return; - this.options.hopt = d.remain(); // use remaining draw options for histogram draw + if (this.h._jsonfile) + return func(this.h); - const dolego = d.check('LEGO'); + this.h._childs?.forEach(item => { + if (item._jsonfile) + func(item); + }); + } - this.options.errors = d.check('E'); + /** @summary Open JSON file + * @param {string} filepath - URL to JSON file + * @return {Promise} when object ready */ + async openJsonFile(filepath) { + let isfileopened = false; + this.forEachJsonFile(item => { + if (item._jsonfile === filepath) + isfileopened = true; + }); + if (isfileopened) + return; - // if any histogram appears with pre-calculated errors, use E for all histograms - if (!this.options.nostack && this.options.has_errors && !dolego && !d.check('HIST') && (this.options.hopt.indexOf('E') < 0)) - this.options.draw_errors = true; + return httpRequest(filepath, 'object').then(res2 => { + if (!res2) + return; + const h1 = { _jsonfile: filepath, _kind: getKindForType(res2._typename), _jsontmp: res2, _name: filepath.split('/').pop() }; + if (res2.fTitle) + h1._title = res2.fTitle; + h1._get = function(item /* ,itemname */) { + if (item._jsontmp) + return Promise.resolve(item._jsontmp); + return httpRequest(item._jsonfile, 'object') + .then(res3 => { + item._jsontmp = res3; + return res3; + }); + }; + if (!this.h) + this.h = h1; + else if (this.h._kind === kTopFolder) + this.h._childs.push(h1); + else { + const h0 = this.h, topname = h0?._jsonfile ? 'Files' : 'Items'; + this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1] }; + } - this.options.horder = this.options.nostack || dolego; + return this.refreshHtml(); + }); } - /** @summary Create main histogram for THStack axis drawing */ - createHistogram(stack) { - const histos = stack.fHists, - numhistos = histos ? histos.arr.length : 0; + /** @summary Call function for each item which corresponds to ROOT file + * @private */ + forEachRootFile(func) { + if (!this.h) + return; + if ((this.h._kind === kindTFile) && this.h._file) + return func(this.h); - if (!numhistos) { - const histo = createHistogram(clTH1I, 100); - setHistogramTitle(histo, stack.fTitle); - histo.fBits |= kNoStats; - return histo; + this.h._childs?.forEach(item => { + if ((item._kind === kindTFile) && item._fullurl) + func(item); + }); + } + + /** @summary Find ROOT file which corresponds to provided item name + * @private */ + findRootFileForItem(itemname) { + let item = this.findItem(itemname); + while (item) { + if ((item._kind === kindTFile) && item._fullurl && item._file) + return item; + item = item?._parent; } + return null; + } - const h0 = histos.arr[0], - histo = createHistogram((this.options.ndim === 1) ? clTH1I : clTH2I, h0.fXaxis.fNbins, h0.fYaxis.fNbins); - histo.fName = 'axis_hist'; - histo.fBits |= kNoStats; - Object.assign(histo.fXaxis, h0.fXaxis); - if (this.options.ndim === 2) - Object.assign(histo.fYaxis, h0.fYaxis); + /** @summary Open ROOT file + * @param {string} filepath - URL to ROOT file, argument for openFile + * @return {Promise} when file is opened */ + async openRootFile(filepath) { + let isfileopened = false; + this.forEachRootFile(item => { + if (item._fullurl === filepath) + isfileopened = true; + }); + if (isfileopened) + return; - // this code is not exists in ROOT painter, can be skipped? - for (let n = 1; n < numhistos; ++n) { - const h = histos.arr[n]; + const msg = isStr(filepath) ? filepath : 'file'; - if (!histo.fXaxis.fLabels) { - histo.fXaxis.fXmin = Math.min(histo.fXaxis.fXmin, h.fXaxis.fXmin); - histo.fXaxis.fXmax = Math.max(histo.fXaxis.fXmax, h.fXaxis.fXmax); - } + showProgress(`Opening ${msg} ...`); - if ((this.options.ndim === 2) && !histo.fYaxis.fLabels) { - histo.fYaxis.fXmin = Math.min(histo.fYaxis.fXmin, h.fYaxis.fXmin); - histo.fYaxis.fXmax = Math.max(histo.fYaxis.fXmax, h.fYaxis.fXmax); + return openFile(filepath).then(file => { + const h1 = this.fileHierarchy(file); + h1._isopen = true; + if (!this.h) { + this.h = h1; + if (this.#topname) + h1._name = this.#topname; + } else if (this.h._kind === kTopFolder) + this.h._childs.push(h1); + else { + const h0 = this.h, topname = (h0._kind === kindTFile) ? 'Files' : 'Items'; + this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1], _isopen: true }; } - } - - histo.fTitle = stack.fTitle; - return histo; + return this.refreshHtml(); + }).catch(() => { + // make CORS warning + if (isBatchMode()) + console.error(`Fail to open ${msg} - check CORS headers`); + else if (!select('#gui_fileCORS').style('background', 'red').empty()) + setTimeout(() => select('#gui_fileCORS').style('background', ''), 5000); + return false; + }).finally(() => showProgress()); } - /** @summary Update thstack object */ - updateObject(obj) { - if (!this.matchObjectType(obj)) return false; + /** @summary Create list of files for specified directory */ + async listServerDir(dirname) { + return httpRequest(dirname, 'text').then(res => { + if (!res) + return false; + const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }, fmap = {}; + let p = 0; + while (p < res.length) { + p = res.indexOf('a href="', p + 1); + if (p < 0) + break; + p += 8; + const p2 = res.indexOf('"', p + 1); + if (p2 < 0) + break; - const stack = this.getObject(); + const fname = res.slice(p, p2); + p = p2 + 1; - stack.fHists = obj.fHists; - stack.fStack = obj.fStack; - stack.fTitle = obj.fTitle; - stack.fMinimum = obj.fMinimum; - stack.fMaximum = obj.fMaximum; + if (fmap[fname]) + continue; + fmap[fname] = true; + + if ((fname.lastIndexOf('.root') === fname.length - 5) && (fname.length > 5)) { + h._childs.push({ + _name: fname, _title: dirname + fname, _url: dirname + fname, _kind: kindTFile, + _click_action: kExpand, _more: true, _obj: {}, + _expand: item => { + return openFile(item._url).then(file => { + if (!file) + return false; + delete item._expand; + delete item._more; + delete item._click_action; + delete item._obj; + item._isopen = true; + this.fileHierarchy(file, item); + this.updateTreeNode(item); + }); + } + }); + } else if (((fname.lastIndexOf('.json.gz') === fname.length - 8) && (fname.length > 8)) || + ((fname.lastIndexOf('.json') === fname.length - 5) && (fname.length > 5))) { + h._childs.push({ + _name: fname, _title: dirname + fname, _jsonfile: dirname + fname, _can_draw: true, + _get: item => { + return httpRequest(item._jsonfile, 'object').then(res2 => { + if (res2) { + item._kind = getKindForType(res2._typename); + item._jsontmp = res2; + this.updateTreeNode(item); + } + return res2; + }); + } + }); + } + } + if (h._childs.length) + this.h = h; + return true; + }); + } + + /** @summary Apply loaded TStyle object + * @desc One also can specify item name of JSON file name where style is loaded + * @param {object|string} style - either TStyle object of item name where object can be load */ + async applyStyle(style) { + if (!style) + return true; - if (!this.options.nostack) - this.options.nostack = !this.buildStack(stack); + let pr = Promise.resolve(style); - if (this.firstpainter) { - let src = obj.fHistogram; - if (!src) - src = stack.fHistogram = this.createHistogram(stack); + if (isStr(style)) { + const item = this.findItem({ name: style, allow_index: true, check_keys: true }); + if (item !== null) + pr = this.getObject(item).then(res => res.obj); + else if (style.indexOf('.json') > 0) + pr = httpRequest(style, 'object'); + } + + return pr.then(st => { + if (st?._typename === clTStyle) + Object.assign(gStyle, st); + }); + } - const mm = this.getMinMax(this.options.errors || this.options.draw_errors); + /** @summary Provides information about file item + * @private */ + getFileProp(itemname) { + let item = this.findItem(itemname); + if (!item) + return null; - this.firstpainter.options.minimum = mm.min; - this.firstpainter.options.maximum = mm.max; - this.firstpainter._checked_zooming = false; // force to check 3d zooming + itemname = item._name; + while (item._parent) { + item = item._parent; + if (item._file) + return { kind: 'file', fileurl: item._file.fURL, itemname, localfile: Boolean(item._file.fLocalFile) }; - if (this.options.ndim === 1) { - this.firstpainter.ymin = mm.min0; - this.firstpainter.ymax = mm.max0; - } else { - this.firstpainter.zmin = mm.min0; - this.firstpainter.zmax = mm.max0; - } + if (item._jsonfile) + return { kind: 'json', fileurl: item._jsonfile, itemname }; - this.firstpainter.updateObject(src); + itemname = item._name + '/' + itemname; } - // and now update histograms - const hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length ?? 0; + return null; + } - if (nhists !== this.painters.length) { - this.did_update = 1; - this.getPadPainter()?.cleanPrimitives(objp => this.painters.indexOf(objp) >= 0); - this.painters = []; - } else { - this.did_update = 2; - for (let indx = 0; indx < nhists; ++indx) { - const rindx = this.options.horder ? indx : nhists - indx - 1, - hist = hlst.arr[rindx]; - this.painters[indx].updateObject(hist, this.getHistDrawOption(hist, hlst.opt[rindx])); - } - } + /** @summary Provides URL for online item + * @desc Such URL can be used to request data from the server + * @return string or null if item is not online + * @private */ + getOnlineItemUrl(item) { + if (isStr(item)) + item = this.findItem(item); + let prnt = item; + while (prnt && (prnt._online === undefined)) + prnt = prnt._parent; + return prnt ? (prnt._online + this.itemFullName(item, prnt)) : null; + } - return true; + /** @summary Returns true if item is online + * @private */ + isOnlineItem(item) { + return this.getOnlineItemUrl(item) !== null; } - /** @summary Redraw THStack - * @desc Do something if previous update had changed number of histograms */ - redraw(reason) { - if (this.did_update === 1) { - delete this.did_update; - return this.drawNextHisto(0, this.options.pads ? this.getPadPainter() : null); - } else if (this.did_update === 2) { - delete this.did_update; - const redrawSub = indx => { - if (indx >= this.painters.length) - return Promise.resolve(this); - return this.painters[indx].redraw(reason).then(() => redrawSub(indx+1)); - }; - return redrawSub(0); + /** @summary Dynamic module import, supports special shortcuts from core or draw_tree + * @return {Promise} with module + * @private */ + async importModule(module) { + switch (module) { + case 'core': return Promise.resolve().then(function () { return core; }); + case 'draw_tree': return Promise.resolve().then(function () { return TTree; }); + case 'hierarchy': return { HierarchyPainter, markAsStreamerInfo }; } + return import(/* webpackIgnore: true */ module); } - - /** @summary Fill hstack context menu */ - fillContextMenuItems(menu) { - menu.addchk(this.options.draw_errors, 'Draw errors', flag => { - this.options.draw_errors = flag; - const stack = this.getObject(), - hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length ?? 0; - for (let indx = 0; indx < nhists; ++indx) { - const rindx = this.options.horder ? indx : nhists - indx - 1, - hist = hlst.arr[rindx]; - this.painters[indx].decodeOptions(this.getHistDrawOption(hist, hlst.opt[rindx])); - } - this.redrawPad(); - }, 'Change draw erros in the stack'); + + /** @summary set cached object for gui drawing + * @private */ + setCachedObject(obj) { + this.#cached_draw_object = obj; } - /** @summary draw THStack object */ - static async draw(dom, stack, opt) { - if (!stack.fHists || !stack.fHists.arr) - return null; // drawing not needed + /** @summary method used to request object from the http server + * @return {Promise} with requested object + * @private */ + async getOnlineItem(item, itemname, option) { + let url = itemname, h_get = false, req = '', req_kind = 'object', draw_handle = null; - const painter = new THStackPainter(dom, stack, opt); - let pad_painter = null, skip_drawing = false; + if (isStr(option) && (option.indexOf('hierarchy_expand') === 0)) { + h_get = true; + option = undefined; + } - return ensureTCanvas(painter, false).then(() => { - painter.decodeOptions(opt); + if (item) { + url = this.getOnlineItemUrl(item); + let func = null; + if (item._kind) + draw_handle = getDrawHandle(item._kind); + + if (h_get) { + req = 'h.json?compact=3'; + item._expand = onlineHierarchy; // use proper expand function + } else if (item._make_request) { + if (item._module) { + const h = await this.importModule(item._module); + func = h[item._make_request]; + } else + func = findFunction(item._make_request); + } else if (draw_handle?.make_request) + func = draw_handle.make_request; - painter.hdraw_func = (painter.options.ndim === 1) ? TH1Painter.draw : TH2Painter.draw; - if (painter.options.pads) { - pad_painter = painter.getPadPainter(); - if (pad_painter.doingDraw() && pad_painter.pad?.fPrimitives && - (pad_painter.pad.fPrimitives.arr.length > 1) && (pad_painter.pad.fPrimitives.arr.indexOf(stack) === 0)) { - skip_drawing = true; - console.log('special case with THStack with is already rendered - do nothing'); - return; + if (isFunc(func)) { + // ask to make request + const dreq = func(this, item, url, option); + // result can be simple string or object with req and kind fields + if (dreq) { + if (isStr(dreq)) + req = dreq; + else { + if (dreq.req) + req = dreq.req; + if (dreq.kind) + req_kind = dreq.kind; + } } - - pad_painter.cleanPrimitives(p => p !== painter); - return pad_painter.divide(painter.options.nhist); } - if (!painter.options.nostack) - painter.options.nostack = !painter.buildStack(stack); + if (!req && !getTypeForKind(item._kind)) + req = 'item.json.gz?compact=3'; + } + + if (!itemname && item && this.#cached_draw_object && !req) { + // special handling for online draw when cashed + const obj = this.#cached_draw_object; + this.#cached_draw_object = undefined; + return obj; + } - if (painter.options.same) return; + if (!req) + req = 'root.json.gz?compact=23'; - const no_histogram = !stack.fHistogram; + if (url) + url += '/'; + url += req; - if (no_histogram) - stack.fHistogram = painter.createHistogram(stack); + return new Promise(resolveFunc => { + let itemreq = null; - const mm = painter.getMinMax(painter.options.errors || painter.options.draw_errors), - hopt = painter.options.hopt + ';' + mm.hopt; + createHttpRequest(url, req_kind, obj => { + const handleAfterRequest = func => { + if (isFunc(func)) { + const res = func(this, item, obj, option, itemreq); + if (isObject(res)) + obj = res; + } + resolveFunc(obj); + }; - return painter.hdraw_func(dom, stack.fHistogram, hopt).then(subp => { - painter.addToPadPrimitives(); - painter.firstpainter = subp; - subp.setSecondaryId(painter, 'hist'); // mark hist painter as created by hstack + if (!h_get && item?._after_request) { + if (item._module) + this.importModule(item._module).then(h => handleAfterRequest(h[item._after_request])); + else + handleAfterRequest(findFunction(item._after_request)); // v6 support + } else + handleAfterRequest(draw_handle?.after_request); + }, undefined, true).then(xhr => { + itemreq = xhr; + xhr.send(null); }); - }).then(() => skip_drawing ? painter : painter.drawNextHisto(0, pad_painter)); + }); } -} // class THStackPainter + /** @summary Access THttpServer with provided address + * @param {string} server_address - URL to server like 'http://localhost:8090/' + * @return {Promise} when ready */ + async openOnline(server_address) { + const adoptHierarchy = async result => { + this.h = result; + if (!result) + return Promise.resolve(null); -var THStackPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -THStackPainter: THStackPainter -}); + if (this.h?._title && (typeof document !== 'undefined')) + document.title = this.h._title; -/** @summary Prepare frame painter for 3D drawing - * @private */ -function before3DDraw(painter) { - const fp = painter.getFramePainter(); + result._isopen = true; - if (!fp?.mode3d || !painter.getObject()) - return null; + // mark top hierarchy as online data and + this.h._online = server_address; - if (fp?.toplevel) - return fp; + this.h._get = (item, itemname, option) => this.getOnlineItem(item, itemname, option); - const main = painter.getMainPainter(); + this.h._expand = onlineHierarchy; - if (main && !isFunc(main.drawExtras)) - return null; + const styles = [], scripts = [], v6_modules = [], v7_imports = []; + this.forEachItem(item => { + if (item._childs !== undefined) + item._expand = onlineHierarchy; - const pr = main ? Promise.resolve(main) : drawDummy3DGeom(painter); + if (item._autoload) { + const arr = item._autoload.split(';'); + arr.forEach(name => { + if ((name.length > 4) && (name.lastIndexOf('.mjs') === name.length - 4)) + v7_imports.push(this.importModule(name)); + else if ((name.length > 3) && (name.lastIndexOf('.js') === name.length - 3)) { + if (!scripts.find(elem => elem === name)) + scripts.push(name); + } else if ((name.length > 4) && (name.lastIndexOf('.css') === name.length - 4)) { + if (!styles.find(elem => elem === name)) + styles.push(name); + } else if (name && !v6_modules.find(elem => elem === name)) + v6_modules.push(name); + }); + } + }); - return pr.then(geop => { - const pp = painter.getPadPainter(); - if (pp) pp._disable_dragging = true; + return this.loadScripts(scripts, v6_modules) + .then(() => loadScript(styles)) + .then(() => Promise.all(v7_imports)) + .then(() => { + this.forEachItem(item => { + if (!('_drawfunc' in item) || !('_kind' in item)) + return; + const typename = getTypeForKind(item._kind) || `kind:${item._kind}`, + drawopt = item._drawopt; + if (!canDrawHandle(typename) || drawopt) + addDrawFunc({ name: typename, func: item._drawfunc, script: item._drawscript, opt: drawopt }); + }); - if (geop._dummy && isFunc(painter.get3DBox)) - geop.extendCustomBoundingBox(painter.get3DBox()); - return geop.drawExtras(painter.getObject(), '', true, true); - }); -} + return this; + }); + }; -/** @summary Function to extract 3DBox for poly marker and line - * @private */ -function get3DBox() { - const obj = this.getObject(); - if (!obj?.fP.length) - return null; - const box = { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } }; + if (!server_address) + server_address = ''; - for (let k = 0; k < obj.fP.length; k += 3) { - const x = obj.fP[k], - y = obj.fP[k + 1], - z = obj.fP[k + 2]; - if (k === 0) { - box.min.x = box.max.x = x; - box.min.y = box.max.y = y; - box.min.z = box.max.z = z; - } else { - box.min.x = Math.min(x, box.min.x); - box.max.x = Math.max(x, box.max.x); - box.min.y = Math.min(y, box.min.y); - box.max.y = Math.max(y, box.max.y); - box.min.z = Math.min(z, box.min.z); - box.max.z = Math.max(z, box.max.z); + if (isObject(server_address)) { + const h = server_address; + server_address = ''; + return adoptHierarchy(h); } - } - return box; -} - - -/** @summary direct draw function for TPolyMarker3D object (optionally with geo painter) - * @private */ -async function drawPolyMarker3D() { - this.get3DBox = get3DBox; - - const fp = before3DDraw(this); - - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return fp; - - this.$fp = fp; - - return drawPolyMarker3D$1.bind(this)(); -} - -/** @summary Direct draw function for TPolyLine3D object - * @desc Takes into account dashed properties - * @private */ -async function drawPolyLine3D() { - this.get3DBox = get3DBox; + return httpRequest(server_address + 'h.json?compact=3', 'object').then(hh => adoptHierarchy(hh)); + } - const line = this.getObject(), - fp = before3DDraw(this); + /** @summary Get properties for online item - server name and relative name + * @private */ + getOnlineProp(itemname) { + let item = this.findItem(itemname); + if (!item) + return null; - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return fp; + itemname = item._name; + while (item._parent) { + item = item._parent; - const limit = 3*line.fN, p = line.fP, pnts = []; + if (item._online) + return { server: item._online, itemname }; + itemname = item._name + '/' + itemname; + } - for (let n = 3; n < limit; n += 3) { - pnts.push(fp.grx(p[n-3]), fp.gry(p[n-2]), fp.grz(p[n-1]), - fp.grx(p[n]), fp.gry(p[n+1]), fp.grz(p[n+2])); + return null; } - const lines = createLineSegments(pnts, create3DLineMaterial(this, line)); + /** @summary Fill context menu for online item + * @private */ + fillOnlineMenu(menu, onlineprop, itemname) { + const node = this.findItem(itemname), + sett = getDrawSettings(node._kind, 'nosame;noinspect'), + handle = getDrawHandle(node._kind), + root_type = getTypeForKind(node._kind); - fp.add3DMesh(lines, this, true); + if (sett.opts && (node._can_draw !== false)) { + sett.opts.push(kInspect); + menu.addDrawMenu('Draw', sett.opts, arg => this.display(itemname, arg)); + } - fp.render3D(100); + if (!node._childs && (node._more !== false) && (node._more || root_type || sett.expand || sett.get_expand)) + menu.add('Expand', () => this.expandItem(itemname)); - return true; -} + if (handle?.execute) + menu.add('Execute', () => this.executeCommand(itemname, menu.tree_node)); -var draw3d = /*#__PURE__*/Object.freeze({ -__proto__: null, -drawPolyLine3D: drawPolyLine3D, -drawPolyMarker3D: drawPolyMarker3D -}); + if (sett.opts && (node._can_draw !== false)) { + menu.addDrawMenu('Draw in new window', sett.opts, + arg => window.open(onlineprop.server + `?nobrowser&item=${onlineprop.itemname}` + + (this.isMonitoring() ? `&monitoring=${this.getMonitoringInterval()}` : '') + + (arg ? `&opt=${arg}` : ''))); + } -/** - * @summary Painter for TGraphTime object - * - * @private - */ + if (sett.opts?.length && root_type && (node._can_draw !== false)) { + menu.addDrawMenu('Draw as png', sett.opts, + arg => window.open(onlineprop.server + onlineprop.itemname + '/root.png?w=600&h=400' + (arg ? '&opt=' + arg : '')), + 'Request PNG image from the server'); + } -class TGraphTimePainter extends ObjectPainter { + if (node._player) + menu.add('Player', () => this.player(itemname)); + } - /** @summary Redraw object */ - redraw() { - if (this.step === undefined) this.startDrawing(); + /** @summary Assign existing hierarchy to the painter and refresh HTML code + * @private */ + setHierarchy(h) { + this.h = h; + this.refreshHtml(); } - /** @summary Decode drawing options */ - decodeOptions(opt) { - const d = new DrawOptions(opt || 'REPEAT'); + /** @summary Configures monitoring interval + * @param {number} interval - repetition interval in ms + * @param {boolean} flag - initial monitoring state */ + setMonitoring(interval, monitor_on) { + this.#runMonitoring('cleanup'); - if (!this.options) this.options = {}; + if (interval) { + interval = parseInt(interval); + if (Number.isInteger(interval) && (interval > 0)) { + this.#monitoring_interval = Math.max(100, interval); + monitor_on = true; + } else + this.#monitoring_interval = 3000; + } - Object.assign(this.options, { - once: d.check('ONCE'), - repeat: d.check('REPEAT'), - first: d.check('FIRST') - }); + this.#monitoring_on = monitor_on; - this.storeDrawOpt(opt); + if (this.isMonitoring()) + this.#runMonitoring(); } - /** @summary Draw primitives */ - async drawPrimitives(indx) { - if (!indx) { - indx = 0; - this._doing_primitives = true; - } - - const lst = this.getObject()?.fSteps.arr[this.step]; + /** @summary Runs monitoring event loop + * @private */ + #runMonitoring(arg) { + if ((arg === 'cleanup') || !this.isMonitoring()) { + if (this.#monitoring_handle) { + clearTimeout(this.#monitoring_handle); + this.#monitoring_handle = undefined; + } - if (!lst || (indx >= lst.arr.length)) { - delete this._doing_primitives; + if (this.#monitoring_frame) { + cancelAnimationFrame(this.#monitoring_frame); + this.#monitoring_frame = undefined; + } return; } - return draw(this.getDom(), lst.arr[indx], lst.opt[indx]).then(p => { - if (p) { - p.$grtimeid = this.selfid; // indicator that painter created by ourself - p.$grstep = this.step; // remember step - } - return this.drawPrimitives(indx+1); - }); - } - - /** @summary Continue drawing */ - continueDrawing() { - if (!this.options) return; - - const gr = this.getObject(); - - if (this.options.first) { - // draw only single frame, cancel all others - delete this.step; + if (arg === 'frame') { + // process of timeout, request animation frame + this.#monitoring_handle = undefined; + this.#monitoring_frame = requestAnimationFrame(() => this.#runMonitoring('draw')); return; } - if (this.wait_animation_frame) { - delete this.wait_animation_frame; + if (arg === 'draw') { + this.#monitoring_frame = undefined; + this.updateItems(); + } - // clear pad - const pp = this.getPadPainter(); - if (!pp) { - // most probably, pad is cleared - delete this.step; - return; - } + this.#monitoring_handle = setTimeout(() => this.#runMonitoring('frame'), this.getMonitoringInterval()); + } - // draw ptrimitives again - this.drawPrimitives().then(() => { - // clear primitives produced by previous drawing to avoid flicking - pp.cleanPrimitives(p => { return (p.$grtimeid === this.selfid) && (p.$grstep !== this.step); }); + /** @summary Returns configured monitoring interval in ms */ + getMonitoringInterval() { return this.#monitoring_interval || 3000; } - this.continueDrawing(); - }); - } else if (this.running_timeout) { - clearTimeout(this.running_timeout); - delete this.running_timeout; + /** @summary Returns true when monitoring is enabled */ + isMonitoring() { return this.#monitoring_on; } - this.wait_animation_frame = true; - // use animation frame to disable update in inactive form - requestAnimationFrame(() => this.continueDrawing()); + /** @summary Assign default layout and place where drawing will be performed + * @param {string} layout - layout like 'simple' or 'grid2x2' + * @param {string} frameid - DOM element id where object drawing will be performed */ + setDisplay(layout, frameid) { + if (!frameid && isObject(layout)) { + this.disp = layout; + this.disp_kind = 'custom'; + this.disp_frameid = null; } else { - let sleeptime = Math.max(gr.fSleepTime, 10); - - if (++this.step > gr.fSteps.arr.length) { - if (this.options.repeat) { - this.step = 0; // start again - sleeptime = Math.max(5000, 5*sleeptime); // increase sleep time - } else { - delete this.step; // clear indicator that animation running - return; - } - } + this.disp_kind = layout; + this.disp_frameid = frameid; + } - this.running_timeout = setTimeout(() => this.continueDrawing(), sleeptime); + if (!this.register_resize && (this.disp_kind !== 'batch')) { + this.register_resize = true; + registerForResize(this); } } - /** @ummary Start drawing of graph time */ - startDrawing() { - this.step = 0; + /** @summary Returns configured layout */ + getLayout() { + return this.disp_kind; + } - return this.drawPrimitives().then(() => { - this.continueDrawing(); - return this; + /** @summary Remove painter reference from hierarchy + * @private */ + removePainter(obj_painter) { + this.forEachItem(item => { + if (item._painter === obj_painter) { + // delete painter reference + delete item._painter; + // also clear data which could be associated with item + if (isFunc(item.clear)) + item.clear(); + } }); } - /** @summary Draw TGraphTime object */ - static async draw(dom, gr, opt) { - if (!gr.fFrame) { - console.error('Frame histogram not exists'); - return null; + /** @summary Cleanup all items in hierarchy + * @private */ + clearHierarchy(withbrowser) { + if (this.disp) { + this.disp.cleanup(); + delete this.disp; } - const painter = new TGraphTimePainter(dom, gr); + const plainarr = []; - if (painter.getMainPainter()) { - console.error('Cannot draw graph time on top of other histograms'); - return null; + this.forEachItem(item => { + delete item._painter; // remove reference on the painter + // when only display cleared, try to clear all browser items + if (!withbrowser && isFunc(item.clear)) + item.clear(); + if (withbrowser) + plainarr.push(item); + }); + + if (withbrowser) { + // cleanup all monitoring loops + this.enableMonitoring(false); + // simplify work for javascript and delete all (ok, most of) cross-references + this.selectDom().html(''); + plainarr.forEach(d => { + delete d._parent; + delete d._childs; + delete d._obj; + delete d._d3cont; + }); + delete this.h; } + } - painter.decodeOptions(opt); + /** @summary Returns actual MDI display object + * @desc It should an instance of {@link MDIDisplay} class */ + getDisplay() { + return this.disp; + } - if (!gr.fFrame.fTitle && gr.fTitle) { - const arr = gr.fTitle.split(';'); - gr.fFrame.fTitle = arr[0]; - if (arr[1]) gr.fFrame.fXaxis.fTitle = arr[1]; - if (arr[2]) gr.fFrame.fYaxis.fTitle = arr[2]; - } + /** @summary method called when MDI element is cleaned up + * @desc hook to perform extra actions when frame is cleaned + * @private */ + cleanupFrame(frame) { + select(frame).attr('frame_title', null); + + this.clearDrop(frame); - painter.selfid = 'grtime_' + internals.id_counter++; // use to identify primitives which should be clean + const lst = cleanup(frame); - return TH1Painter$2.draw(dom, gr.fFrame, '').then(() => { - painter.addToPadPrimitives(); - return painter.startDrawing(); - }); + // we remove all painters references from items + if (lst.length) { + this.forEachItem(item => { + if (item._painter && lst.indexOf(item._painter) >= 0) + delete item._painter; + }); + } } -} // class TGraphTimePainter + /** @summary Creates configured MDIDisplay object + * @return {Promise} when ready + * @private */ + async createDisplay() { + if (this.disp) { + if ((this.disp.numDraw() > 0) || (this.disp_kind === 'custom')) + return this.disp; + this.disp.cleanup(); + delete this.disp; + } -var TGraphTimePainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TGraphTimePainter: TGraphTimePainter -}); + if (this.disp_kind === 'batch') { + const pr = isNodeJs() ? _loadJSDOM() : Promise.resolve(null); + return pr.then(handle => { + this.disp = new BatchDisplay(1200, 800, handle?.body); + return this.disp; + }); + } -function getMax(arr) { - let v = arr[0]; - for (let i = 1; i < arr.length; ++i) - v = Math.max(v, arr[i]); - return v; -} + // check that we can found frame where drawing should be done + if (!this.disp_frameid || !document.getElementById(this.disp_frameid)) + return null; -function getMin(arr) { - let v = arr[0]; - for (let i = 1; i < arr.length; ++i) - v = Math.min(v, arr[i]); - return v; -} + if (isBatchMode()) + this.disp = new BatchDisplay(settings.CanvasWidth, settings.CanvasHeight); + else if ((this.disp_kind.indexOf('flex') === 0) || (this.disp_kind.indexOf('coll') === 0)) + this.disp = new FlexibleDisplay(this.disp_frameid); + else if (this.disp_kind === 'tabs') + this.disp = new TabsDisplay(this.disp_frameid); + else + this.disp = new GridDisplay(this.disp_frameid, this.disp_kind); -function TMath_Sort(np, values, indicies /*, down */) { - const arr = new Array(np); - for (let i = 0; i < np; ++i) - arr[i] = { v: values[i], i }; + this.disp.cleanupFrame = this.cleanupFrame.bind(this); + if (settings.DragAndDrop) + this.disp.setInitFrame(this.enableDrop.bind(this)); - arr.sort((a, b) => { return a.v < b.v ? -1 : (a.v > b.v ? 1 : 0); }); + return this.disp; + } - for (let i = 0; i < np; ++i) - indicies[i] = arr[i].i; -} + /** @summary If possible, creates custom MDIDisplay for given item + * @param itemname - name of item, for which drawing is created + * @param custom_kind - display kind + * @return {Promise} with mdi object created + * @private */ + async createCustomDisplay(itemname, custom_kind) { + if (this.disp_kind !== 'simple') + return this.createDisplay(); -class TGraphDelaunay { + this.disp_kind = custom_kind; - constructor(g) { - this.fGraph2D = g; - this.fX = g.fX; - this.fY = g.fY; - this.fZ = g.fZ; - this.fNpoints = g.fNpoints; - this.fZout = 0.0; - this.fNdt = 0; - this.fNhull = 0; - this.fHullPoints = null; - this.fXN = null; - this.fYN = null; - this.fOrder = null; - this.fDist = null; - this.fPTried = null; - this.fNTried = null; - this.fMTried = null; - this.fInit = false; - this.fXNmin = 0.0; - this.fXNmax = 0.0; - this.fYNmin = 0.0; - this.fYNmax = 0.0; - this.fXoffset = 0.0; - this.fYoffset = 0.0; - this.fXScaleFactor = 0.0; - this.fYScaleFactor = 0.0; + // check if display can be erased + if (this.disp) { + const num = this.disp.numDraw(); + if ((num > 1) || ((num === 1) && !this.disp.findFrame(itemname))) + return this.createDisplay(); + this.disp.cleanup(); + delete this.disp; + } - this.SetMaxIter(); + return this.createDisplay(); } + /** @summary function updates object drawings for other painters + * @private */ + updateOnOtherFrames(painter, obj) { + const handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; + if (handle?.draw_field && obj[handle?.draw_field]) + obj = obj[handle?.draw_field]; - Initialize() { - if (!this.fInit) { - this.CreateTrianglesDataStructure(); - this.FindHull(); - this.fInit = true; - } + let isany = false; + this.disp?.forEachPainter((p /* , frame */) => { + if ((p === painter) || (p.getItemName() !== painter.getItemName())) + return; + + // do not activate frame when doing update + // mdi.activateFrame(frame); + if (isFunc(p.redrawObject) && p.redrawObject(obj)) + isany = true; + }); + return isany; } - ComputeZ(x, y) { - // Initialise the Delaunay algorithm if needed. - // CreateTrianglesDataStructure computes fXoffset, fYoffset, - // fXScaleFactor and fYScaleFactor; - // needed in this function. - this.Initialize(); + /** @summary Process resize event + * @private */ + checkResize(size) { + this.disp?.checkMDIResize(null, size); + } + + /** @summary Load and execute scripts, kept to support v6 applications + * @private */ + async loadScripts(scripts, modules, use_inject) { + if (!scripts?.length && !modules?.length) + return true; + + if (use_inject && scripts.indexOf('.mjs') > 0) + return loadModules(scripts.split(';')); - // Find the z value corresponding to the point (x,y). - const xx = (x+this.fXoffset)*this.fXScaleFactor, - yy = (y+this.fYoffset)*this.fYScaleFactor; - let zz = this.Interpolate(xx, yy); + if (use_inject && !globalThis.JSROOT) { + globalThis.JSROOT = { + version, gStyle, create: create$1, httpRequest, loadScript, decodeUrl, + source_dir: exports.source_dir, settings, addUserStreamer, addDrawFunc, + draw, redraw + }; + } - // Wrong zeros may appear when points sit on a regular grid. - // The following line try to avoid this problem. - if (zz === 0) zz = this.Interpolate(xx+0.0001, yy); + if (internals.ignore_v6 || use_inject) + return loadScript(scripts); - return zz; + return exports._ensureJSROOT().then(v6 => { + return v6.require(modules) + .then(() => loadScript(scripts)) + .then(() => v6._complete_loading()); + }); } + /** @summary Start GUI + * @return {Promise} when ready + * @private */ + async startGUI(gui_div, url) { + const d = decodeUrl(url), - CreateTrianglesDataStructure() { - // Offset fX and fY so they average zero, and scale so the average - // of the X and Y ranges is one. The normalized version of fX and fY used - // in Interpolate. - const xmax = getMax(this.fGraph2D.fX), - ymax = getMax(this.fGraph2D.fY), - xmin = getMin(this.fGraph2D.fX), - ymin = getMin(this.fGraph2D.fY); - this.fXoffset = -(xmax+xmin)/2; - this.fYoffset = -(ymax+ymin)/2; - this.fXScaleFactor = 1/(xmax-xmin); - this.fYScaleFactor = 1/(ymax-ymin); - this.fXNmax = (xmax+this.fXoffset)*this.fXScaleFactor; - this.fXNmin = (xmin+this.fXoffset)*this.fXScaleFactor; - this.fYNmax = (ymax+this.fYoffset)*this.fYScaleFactor; - this.fYNmin = (ymin+this.fYoffset)*this.fYScaleFactor; - this.fXN = new Array(this.fNpoints+1); - this.fYN = new Array(this.fNpoints+1); - for (let n = 0; n < this.fNpoints; n++) { - this.fXN[n+1] = (this.fX[n]+this.fXoffset)*this.fXScaleFactor; - this.fYN[n+1] = (this.fY[n]+this.fYoffset)*this.fYScaleFactor; - } + getOption = opt => { + let res = d.get(opt, null); + if ((res === null) && gui_div && !gui_div.empty() && gui_div.node().hasAttribute(opt)) + res = gui_div.attr(opt); + return res; + }, - // If needed, creates the arrays to hold the Delaunay triangles. - // A maximum number of 2*fNpoints is guessed. If more triangles will be - // find, FillIt will automatically enlarge these arrays. - this.fPTried = []; - this.fNTried = []; - this.fMTried = []; - } + getUrlOptionAsArray = opt => { + let res = []; + while (opt) { + const separ = opt.indexOf(';'); + let part = (separ > 0) ? opt.slice(0, separ) : opt; - /// Is point e inside the triangle t1-t2-t3 ? + opt = (separ > 0) ? opt.slice(separ + 1) : ''; - Enclose(t1, t2, t3, e) { - const x = [this.fXN[t1], this.fXN[t2], this.fXN[t3], this.fXN[t1]], - y = [this.fYN[t1], this.fYN[t2], this.fYN[t3], this.fYN[t1]], - xp = this.fXN[e], - yp = this.fYN[e]; - let i = 0, j = x.length - 1, oddNodes = false; + let canarray = true; + if (part[0] === '#') { + part = part.slice(1); + canarray = false; + } - for (; i < x.length; ++i) { - if ((y[i]=yp) || (y[j]=yp)) { - if (x[i]+(yp-y[i])/(y[j]-y[i])*(x[j]-x[i]) { + let res = getUrlOptionAsArray(opt); + if (res.length || !gui_div || gui_div.empty()) + return res; + while (opt) { + const separ = opt.indexOf(';'); + let part = separ > 0 ? opt.slice(0, separ) : opt; + opt = separ > 0 ? opt.slice(separ + 1) : ''; + + let canarray = true; + if (part[0] === '#') { + part = part.slice(1); + canarray = false; + } + if (part === 'files' || !gui_div.node().hasAttribute(part)) + continue; - /// Files the triangle defined by the 3 vertices p, n and m into the - /// fxTried arrays. If these arrays are to small they are automatically - /// expanded. + const val = gui_div.attr(part); - FileIt(p, n, m) { - let swap, tmp, ps = p, ns = n, ms = m; + if (canarray) + res = res.concat(parseAsArray(val)); + else if (val !== null) + res.push(val); + } + return res; + }, - // order the vertices before storing them - do { - swap = false; - if (ns > ps) { tmp = ps; ps = ns; ns = tmp; swap = true; } - if (ms > ns) { tmp = ns; ns = ms; ms = tmp; swap = true; } - } while (swap); + filesdir = d.get('path') || '', // path used in normal gui + jsonarr = getOptionAsArray('#json;jsons'), + expanditems = getOptionAsArray('expand'), + focusitem = getOption('focus'), + layout = getOption('layout'), + style = getOptionAsArray('#style'), + title = getOption('title'); - // store a new Delaunay triangle - this.fNdt++; - this.fPTried.push(ps); - this.fNTried.push(ns); - this.fMTried.push(ms); - } + this.#one_by_one = settings.drop_items_one_by_one ?? (getOption('one_by_one') !== null); + + let prereq = getOption('prereq') || '', + load = getOption('load'), + dir = getOption('dir'), + inject = getOption('inject'), + filesarr = getOptionAsArray('#file;files'), + itemsarr = getOptionAsArray('#item;items'), + optionsarr = getOptionAsArray('#opt;opts'), + monitor = getOption('monitoring'), + statush = 0, status = getOption('status'), + browser_kind = getOption('browser'), + browser_configured = Boolean(browser_kind); + if (monitor === null) + monitor = 0; + else if (monitor === '') + monitor = 3000; + else + monitor = parseInt(monitor); - /// Attempt to find all the Delaunay triangles of the point set. It is not - /// guaranteed that it will fully succeed, and no check is made that it has - /// fully succeeded (such a check would be possible by referencing the points - /// that make up the convex hull). The method is to check if each triangle - /// shares all three of its sides with other triangles. If not, a point is - /// generated just outside the triangle on the side(s) not shared, and a new - /// triangle is found for that point. If this method is not working properly - /// (many triangles are not being found) it's probably because the new points - /// are too far beyond or too close to the non-shared sides. Fiddling with - /// the size of the `alittlebit' parameter may help. + if (getOption('float') !== null) { + browser_kind = 'float'; + browser_configured = true; + } else if (getOption('fix') !== null) { + browser_kind = 'fix'; + browser_configured = true; + } - FindAllTriangles() { - if (this.fAllTri) return; + if (!browser_configured && (browser.screenWidth <= 640)) + browser_kind = 'float'; - this.fAllTri = true; + this.no_select = getOption('noselect'); + this.top_info = getOption('info'); - let xcntr, ycntr, xm, ym, xx, yy, - sx, sy, nx, ny, mx, my, mdotn, nn, a, - t1, t2, pa, na, ma, pb, nb, mb, p1=0, p2=0, m, n, p3=0; - const s = [false, false, false], - alittlebit = 0.0001; + if (getOption('files_monitoring') !== null) + this.files_monitoring = true; - this.Initialize(); + if (title && (typeof document !== 'undefined')) + document.title = title; - // start with a point that is guaranteed to be inside the hull (the - // centre of the hull). The starting point is shifted "a little bit" - // otherwise, in case of triangles aligned on a regular grid, we may - // found none of them. - xcntr = 0; - ycntr = 0; - for (n = 1; n <= this.fNhull; n++) { - xcntr += this.fXN[this.fHullPoints[n-1]]; - ycntr += this.fYN[this.fHullPoints[n-1]]; + if (!expanditems.length && (getOption('expand') === '')) + expanditems.push(''); + + if (filesdir) { + for (let i = 0; i < filesarr.length; ++i) + filesarr[i] = filesdir + filesarr[i]; + for (let i = 0; i < jsonarr.length; ++i) + jsonarr[i] = filesdir + jsonarr[i]; } - xcntr = xcntr/this.fNhull+alittlebit; - ycntr = ycntr/this.fNhull+alittlebit; - // and calculate it's triangle - this.Interpolate(xcntr, ycntr); - // loop over all Delaunay triangles (including those constantly being - // produced within the loop) and check to see if their 3 sides also - // correspond to the sides of other Delaunay triangles, i.e. that they - // have all their neighbours. - t1 = 1; - while (t1 <= this.fNdt) { - // get the three points that make up this triangle - pa = this.fPTried[t1-1]; - na = this.fNTried[t1-1]; - ma = this.fMTried[t1-1]; + if (!itemsarr.length && ((getOption('item') === '') || (jsonarr.length === 1 && !expanditems.length))) + itemsarr.push(''); - // produce three integers which will represent the three sides - s[0] = false; - s[1] = false; - s[2] = false; - // loop over all other Delaunay triangles - for (t2=1; t2<=this.fNdt; t2++) { - if (t2 !== t1) { - // get the points that make up this triangle - pb = this.fPTried[t2-1]; - nb = this.fNTried[t2-1]; - mb = this.fMTried[t2-1]; - // do triangles t1 and t2 share a side? - if ((pa === pb && na === nb) || (pa === pb && na === mb) || (pa === nb && na === mb)) { - // they share side 1 - s[0] = true; - } else if ((pa === pb && ma === nb) || (pa === pb && ma === mb) || (pa === nb && ma === mb)) { - // they share side 2 - s[1] = true; - } else if ((na === pb && ma === nb) || (na === pb && ma === mb) || (na === nb && ma === mb)) { - // they share side 3 - s[2] = true; - } - } - // if t1 shares all its sides with other Delaunay triangles then - // forget about it - if (s[0] && s[1] && s[2]) continue; + if (!this.disp_kind) { + if (isStr(layout) && layout) + this.disp_kind = layout; + else if (settings.DislpayKind && settings.DislpayKind !== 'simple') + this.disp_kind = settings.DislpayKind; + else { + const _kinds = ['simple', 'simple', 'vert2', 'vert21', 'vert22', 'vert32', + 'vert222', 'vert322', 'vert332', 'vert333']; + this.disp_kind = _kinds[itemsarr.length] || 'flex'; } - // Looks like t1 is missing a neighbour on at least one side. - // For each side, take a point a little bit beyond it and calculate - // the Delaunay triangle for that point, this should be the triangle - // which shares the side. - for (m=1; m<=3; m++) { - if (!s[m-1]) { - // get the two points that make up this side - if (m === 1) { - p1 = pa; - p2 = na; - p3 = ma; - } else if (m === 2) { - p1 = pa; - p2 = ma; - p3 = na; - } else if (m === 3) { - p1 = na; - p2 = ma; - p3 = pa; - } - // get the coordinates of the centre of this side - xm = (this.fXN[p1]+this.fXN[p2])/2.0; - ym = (this.fYN[p1]+this.fYN[p2])/2.0; - // we want to add a little to these coordinates to get a point just - // outside the triangle; (sx,sy) will be the vector that represents - // the side - sx = this.fXN[p1]-this.fXN[p2]; - sy = this.fYN[p1]-this.fYN[p2]; - // (nx,ny) will be the normal to the side, but don't know if it's - // pointing in or out yet - nx = sy; - ny = -sx; - nn = Math.sqrt(nx*nx+ny*ny); - nx = nx/nn; - ny = ny/nn; - mx = this.fXN[p3]-xm; - my = this.fYN[p3]-ym; - mdotn = mx*nx+my*ny; - if (mdotn > 0) { - // (nx,ny) is pointing in, we want it pointing out - nx = -nx; - ny = -ny; - } - // increase/decrease xm and ym a little to produce a point - // just outside the triangle (ensuring that the amount added will - // be large enough such that it won't be lost in rounding errors) - a = Math.abs(Math.max(alittlebit*xm, alittlebit*ym)); - xx = xm+nx*a; - yy = ym+ny*a; - // try and find a new Delaunay triangle for this point - this.Interpolate(xx, yy); + } - // this side of t1 should now, hopefully, if it's not part of the - // hull, be shared with a new Delaunay triangle just calculated by Interpolate - } - } - t1++; + if (status === 'no') + status = null; + else if (status === 'off') { + this.status_disabled = true; + status = null; + } else if (status === 'on') + status = true; + else if (status !== null) { + statush = parseInt(status); + if (!Number.isInteger(statush) || (statush < 5)) + statush = 0; + status = true; } - } + if (this.no_select === '') + this.no_select = true; - /// Finds those points which make up the convex hull of the set. If the xy - /// plane were a sheet of wood, and the points were nails hammered into it - /// at the respective coordinates, then if an elastic band were stretched - /// over all the nails it would form the shape of the convex hull. Those - /// nails in contact with it are the points that make up the hull. + if (!browser_kind) + browser_kind = 'fix'; + else if (browser_kind === 'no') + browser_kind = ''; + else if (browser_kind === 'off') { + browser_kind = ''; + status = null; + this.exclude_browser = true; + } + if (getOption('nofloat') !== null) + this.float_browser_disabled = true; - FindHull() { - if (!this.fHullPoints) - this.fHullPoints = new Array(this.fNpoints); + if (this.start_without_browser) + browser_kind = ''; - let nhull_tmp = 0; - for (let n=1; n<=this.fNpoints; n++) { - // if the point is not inside the hull of the set of all points - // bar it, then it is part of the hull of the set of all points - // including it - const is_in = this.InHull(n, n); - if (!is_in) { - // cannot increment fNhull directly - InHull needs to know that - // the hull has not yet been completely found - nhull_tmp++; - this.fHullPoints[nhull_tmp-1] = n; - } - } - this.fNhull = nhull_tmp; - } + this.#topname = getOption('topname'); + const openAllFiles = () => { + let promise; - /// Is point e inside the hull defined by all points apart from x ? + if (load || prereq) { + promise = this.loadScripts(load, prereq); + load = prereq = ''; + } else if (inject) { + promise = this.loadScripts(inject, '', true); + inject = ''; + } else if (browser_kind) { + promise = this.createBrowser(browser_kind); + browser_kind = ''; + } else if (status !== null) { + promise = this.createStatusLine(statush, status); + status = null; + } else if (jsonarr.length) + promise = this.openJsonFile(jsonarr.shift()); + else if (filesarr.length) + promise = this.openRootFile(filesarr.shift()); + else if (dir) { + promise = this.listServerDir(dir); + dir = ''; + } else if (expanditems.length) + promise = this.expandItem(expanditems.shift()); + else if (style.length) + promise = this.applyStyle(style.shift()); + else { + return this.refreshHtml() + .then(() => this.displayItems(itemsarr, optionsarr)) + .then(() => { return focusitem ? this.focusOnItem(focusitem) : this; }) + .then(() => { + this.setMonitoring(monitor); + return itemsarr ? this.refreshHtml() : this; // this is final return + }); + } - InHull(e, x) { - let n1, n2, n, m, ntry, - lastdphi, dd1, dd2, dx1, dx2, dx3, dy1, dy2, dy3, - u, v, vNv1, vNv2, phi1, phi2, dphi, - deTinhull = false; + return promise.then(openAllFiles); + }; - const xx = this.fXN[e], - yy = this.fYN[e]; + let h0 = null; + if (this.is_online) { + const func = internals.getCachedHierarchy || findFunction('GetCachedHierarchy'); + if (isFunc(func)) + h0 = func(); + if (!isObject(h0)) + h0 = ''; - if (this.fNhull > 0) { - // The hull has been found - no need to use any points other than - // those that make up the hull - ntry = this.fNhull; - } else { - // The hull has not yet been found, will have to try every point - ntry = this.fNpoints; + if ((this.is_online === 'draw') && !itemsarr.length) + itemsarr.push(''); } - // n1 and n2 will represent the two points most separated by angle - // from point e. Initially the angle between them will be <180 degs. - // But subsequent points will increase the n1-e-n2 angle. If it - // increases above 180 degrees then point e must be surrounded by - // points - it is not part of the hull. - n1 = 1; - n2 = 2; - if (n1 === x) { - n1 = n2; - n2++; - } else if (n2 === x) - n2++; + if (h0 !== null) { + return this.openOnline(h0).then(() => { + // check if server enables monitoring + if (!this.exclude_browser && !browser_configured && this.h._browser) { + browser_kind = this.h._browser; + if (browser_kind === 'no') + browser_kind = ''; + else if (browser_kind === 'off') { + browser_kind = ''; + status = null; + this.exclude_browser = true; + } + } + if (('_monitoring' in this.h) && !monitor) + monitor = this.h._monitoring; - // Get the angle n1-e-n2 and set it to lastdphi - dx1 = xx-this.fXN[n1]; - dy1 = yy-this.fYN[n1]; - dx2 = xx-this.fXN[n2]; - dy2 = yy-this.fYN[n2]; - phi1 = Math.atan2(dy1, dx1); - phi2 = Math.atan2(dy2, dx2); - dphi = (phi1-phi2)-(Math.floor((phi1-phi2)/(Math.PI*2))*Math.PI*2); - if (dphi < 0) dphi += Math.PI*2; - lastdphi = dphi; - for (n=1; n<=ntry; n++) { - if (this.fNhull > 0) { - // Try hull point n - m = this.fHullPoints[n-1]; - } else - m = n; + if (this.h._loadfile && !filesarr.length) + filesarr = parseAsArray(this.h._loadfile); - if ((m !== n1) && (m !== n2) && (m !== x)) { - // Can the vector e->m be represented as a sum with positive - // coefficients of vectors e->n1 and e->n2? - dx1 = xx-this.fXN[n1]; - dy1 = yy-this.fYN[n1]; - dx2 = xx-this.fXN[n2]; - dy2 = yy-this.fYN[n2]; - dx3 = xx-this.fXN[m]; - dy3 = yy-this.fYN[m]; - - dd1 = (dx2*dy1-dx1*dy2); - dd2 = (dx1*dy2-dx2*dy1); - - if (dd1*dd2 !== 0) { - u = (dx2*dy3-dx3*dy2)/dd1; - v = (dx1*dy3-dx3*dy1)/dd2; - if ((u < 0) || (v < 0)) { - // No, it cannot - point m does not lie in-between n1 and n2 as - // viewed from e. Replace either n1 or n2 to increase the - // n1-e-n2 angle. The one to replace is the one which makes the - // smallest angle with e->m - vNv1 = (dx1*dx3+dy1*dy3)/Math.sqrt(dx1*dx1+dy1*dy1); - vNv2 = (dx2*dx3+dy2*dy3)/Math.sqrt(dx2*dx2+dy2*dy2); - if (vNv1 > vNv2) { - n1 = m; - phi1 = Math.atan2(dy3, dx3); - phi2 = Math.atan2(dy2, dx2); - } else { - n2 = m; - phi1 = Math.atan2(dy1, dx1); - phi2 = Math.atan2(dy3, dx3); - } - dphi = (phi1-phi2)-(Math.floor((phi1-phi2)/(Math.PI*2))*Math.PI*2); - if (dphi < 0) dphi += Math.PI*2; - if ((dphi - Math.PI)*(lastdphi - Math.PI) < 0) { - // The addition of point m means the angle n1-e-n2 has risen - // above 180 degs, the point is in the hull. - deTinhull = true; - return deTinhull; - } - lastdphi = dphi; - } + if (('_drawitem' in this.h) && !itemsarr.length) { + itemsarr = parseAsArray(this.h._drawitem); + optionsarr = parseAsArray(this.h._drawopt); } - } + + if (this.h._layout && !layout && ((this.is_online !== 'draw') || (itemsarr.length > 1))) + this.disp_kind = this.h._layout; + + if (('_toptitle' in this.h) && this.exclude_browser && (typeof document !== 'undefined')) + document.title = this.h._toptitle; + + if (gui_div) + this.prepareGuiDiv(gui_div.attr('id'), this.disp_kind); + + return openAllFiles(); + }); } - // Point e is not surrounded by points - it is not in the hull. - return deTinhull; - } + if (gui_div) + this.prepareGuiDiv(gui_div.attr('id'), this.disp_kind); - /// Finds the z-value at point e given that it lies - /// on the plane defined by t1,t2,t3 + return openAllFiles(); + } - InterpolateOnPlane(TI1, TI2, TI3, e) { - let tmp, swap, t1 = TI1, t2 = TI2, t3 = TI3; + /** @summary Prepare div element - create layout and buttons + * @private */ + prepareGuiDiv(myDiv, layout) { + this.gui_div = isStr(myDiv) ? myDiv : myDiv.attr('id'); - // order the vertices - do { - swap = false; - if (t2 > t1) { tmp = t1; t1 = t2; t2 = tmp; swap = true; } - if (t3 > t2) { tmp = t2; t2 = t3; t3 = tmp; swap = true; } - } while (swap); + this.brlayout = new BrowserLayout(this.gui_div, this); - const x1 = this.fXN[t1], - x2 = this.fXN[t2], - x3 = this.fXN[t3], - y1 = this.fYN[t1], - y2 = this.fYN[t2], - y3 = this.fYN[t3], - f1 = this.fZ[t1-1], - f2 = this.fZ[t2-1], - f3 = this.fZ[t3-1], - u = (f1*(y2-y3)+f2*(y3-y1)+f3*(y1-y2))/(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2)), - v = (f1*(x2-x3)+f2*(x3-x1)+f3*(x1-x2))/(y1*(x2-x3)+y2*(x3-x1)+y3*(x1-x2)), - w = f1-u*x1-v*y1; + this.brlayout.create(!this.exclude_browser); + + this.createButtons(); - return u*this.fXN[e] + v*this.fYN[e] + w; + this.setDisplay(layout, this.brlayout.drawing_divid()); } - /// Finds the Delaunay triangle that the point (xi,yi) sits in (if any) and - /// calculate a z-value for it by linearly interpolating the z-values that - /// make up that triangle. + /** @summary Create shortcut buttons */ + createButtons() { + if (this.exclude_browser) + return; + + const btns = this.brlayout?.createBrowserBtns(); + if (!btns) + return; - Interpolate(xx, yy) { - let thevalue, - it, ntris_tried, p, n, m, - i, j, k, l, z, f, d, o1, o2, a, b, t1, t2, t3, - ndegen = 0, degen = 0, fdegen = 0, o1degen = 0, o2degen = 0, - vxN, vyN, - d1, d2, d3, c1, c2, dko1, dko2, dfo1, - dfo2, sin_sum, cfo1k, co2o1k, co2o1f, - dx1, dx2, dx3, dy1, dy2, dy3, u, v; - const dxz = [0, 0, 0], dyz = [0, 0, 0]; + ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos browser', 'browser') + .style('margin', '3px').on('click', () => this.createBrowser('fix', true)); - // initialise the Delaunay algorithm if needed - this.Initialize(); + if (!this.float_browser_disabled) { + ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float browser', 'browser') + .style('margin', '3px').on('click', () => this.createBrowser('float', true)); + } - // create vectors needed for sorting - if (!this.fOrder) { - this.fOrder = new Array(this.fNpoints); - this.fDist = new Array(this.fNpoints); + if (!this.status_disabled) { + ToolbarIcons.createSVG(btns, ToolbarIcons.three_circles, 15, 'toggle status line', 'browser') + .style('margin', '3px').on('click', () => this.createStatusLine(0, 'toggle')); } + } - // the input point will be point zero. - this.fXN[0] = xx; - this.fYN[0] = yy; + /** @summary Returns true if status is exists */ + hasStatusLine() { + if (this.status_disabled || !this.gui_div || !this.brlayout) + return false; + return this.brlayout.hasStatus(); + } - // set the output value to the default value for now - thevalue = this.fZout; + /** @summary Create status line + * @param {number} [height] - size of the status line + * @param [mode] - false / true / 'toggle' + * @return {Promise} when ready */ + async createStatusLine(height, mode) { + if (this.status_disabled || !this.gui_div || !this.brlayout) + return ''; + return this.brlayout.createStatusLine(height, mode); + } - // some counting - ntris_tried = 0; + /** @summary Redraw hierarchy + * @desc works only when inspector or streamer info is displayed + * @private */ + redrawObject(obj) { + if (!this._inspector && !this._streamer_info) + return false; + if (this._streamer_info) + this.h = createStreamerInfoContent(obj); + else + this.h = createInspectorContent(obj); + return this.refreshHtml().then(() => this.setTopPainter()); + } - // no point in proceeding if xx or yy are silly - if ((xx > this.fXNmax) || (xx < this.fXNmin) || (yy > this.fYNmax) || (yy < this.fYNmin)) - return thevalue; + /** @summary Create browser elements + * @return {Promise} when completed */ + async createBrowser(browser_kind, update_html) { + if (!this.gui_div || this.exclude_browser || !this.brlayout) + return false; - // check existing Delaunay triangles for a good one - for (it=1; it<=this.fNdt; it++) { - p = this.fPTried[it-1]; - n = this.fNTried[it-1]; - m = this.fMTried[it-1]; - // p, n and m form a previously found Delaunay triangle, does it - // enclose the point? - if (this.Enclose(p, n, m, 0)) { - // yes, we have the triangle - thevalue = this.InterpolateOnPlane(p, n, m, 0); - return thevalue; - } - } + const main = select(`#${this.gui_div} .jsroot_browser`); + // one requires top-level container + if (main.empty()) + return false; - // is this point inside the convex hull? - const shouldbein = this.InHull(0, -1); - if (!shouldbein) - return thevalue; + if ((browser_kind === 'float') && this.float_browser_disabled) + browser_kind = 'fix'; - // it must be in a Delaunay triangle - find it... + if (!main.select('.jsroot_browser_area').empty()) { + // this is case when browser created, + // if update_html specified, hidden state will be toggled - // order mass points by distance in mass plane from desired point - for (it=1; it<=this.fNpoints; it++) { - vxN = this.fXN[it]; - vyN = this.fYN[it]; - this.fDist[it-1] = Math.sqrt((xx-vxN)*(xx-vxN)+(yy-vyN)*(yy-vyN)); + if (update_html) + this.brlayout.toggleKind(browser_kind); + + return true; } - // sort array 'fDist' to find closest points - TMath_Sort(this.fNpoints, this.fDist, this.fOrder /*, false */); - for (it=0; itJSROOT version ${version}

    `; - // loop over triplets of close points to try to find a triangle that - // encloses the point. - for (k=3; k<=this.fNpoints; k++) { - m = this.fOrder[k-1]; - for (j=2; j<=k-1; j++) { - n = this.fOrder[j-1]; - for (i=1; i<=j-1; i++) { - let skip_this_triangle = false; // used instead of goto L90 - p = this.fOrder[i-1]; - if (ntris_tried > this.fMaxIter) { - // perhaps this point isn't in the hull after all - /// Warning("Interpolate", - /// "Abandoning the effort to find a Delaunay triangle (and thus interpolated z-value) for point %g %g" - /// ,xx,yy); - return thevalue; - } - ntris_tried++; - // check the points aren't colinear - d1 = Math.sqrt((this.fXN[p]-this.fXN[n])**2+(this.fYN[p]-this.fYN[n])**2); - d2 = Math.sqrt((this.fXN[p]-this.fXN[m])**2+(this.fYN[p]-this.fYN[m])**2); - d3 = Math.sqrt((this.fXN[n]-this.fXN[m])**2+(this.fYN[n]-this.fYN[m])**2); - if ((d1+d2 <= d3) || (d1+d3 <= d2) || (d2+d3 <= d1)) - continue; + if (this.is_online) { + guiCode += '

    Hierarchy in json and xml format

    ' + + '
    ' + + ''; + } else if (!this.no_select) { + const myDiv = select('#' + this.gui_div), + files = myDiv.attr('files') || 'https://root.cern/js/files/hsimple.root', + path = decodeUrl().get('path') || myDiv.attr('path') || '', + arrFiles = files.split(';'); + + guiCode += '' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '

    Read docu' + + ' how to open files from other servers.

    ' + + '
    ' + + '' + + ''; + } else if (this.no_select === 'file') + guiCode += '
    '; - // does the triangle enclose the point? - if (!this.Enclose(p, n, m, 0)) - continue; - // is it a Delaunay triangle? (ie. are there any other points - // inside the circle that is defined by its vertices?) + if (this.is_online || !this.no_select || this.no_select === 'file') { + guiCode += '' + + '
    '; + } - // test the triangle for Delaunay'ness + guiCode += `
    `; - // loop over all other points testing each to see if it's - // inside the triangle's circle - ndegen = 0; - for (z = 1; z <= this.fNpoints; z++) { - if ((z === p) || (z === n) || (z === m)) - continue; // goto L50; - // An easy first check is to see if point z is inside the triangle - // (if it's in the triangle it's also in the circle) + this.brlayout.setBrowserContent(guiCode); - // point z cannot be inside the triangle if it's further from (xx,yy) - // than the furthest pointing making up the triangle - test this - for (l=1; l<=this.fNpoints; l++) { - if (this.fOrder[l-1] === z) { - if ((l { + evnt.preventDefault(); + createMenu(evnt).then(menu => { + this.fillSettingsMenu(menu, true); + menu.show(); + }); + }).on('dblclick', () => { + this.createBrowser(this.brlayout?.browser_kind === 'float' ? 'fix' : 'float', true); + }); - // if it is inside the triangle this can't be a Delaunay triangle - if (this.Enclose(p, n, m, z)) { skip_this_triangle = true; break; } // goto L90; - } else { - // there's no way it could be in the triangle so there's no point - // calling enclose - break; // goto L1; - } - } - } + if (!this.is_online && !this.no_select) { + this.readSelectedFile = function() { + const filename = main.select('.gui_urlToLoad').property('value').trim(); + if (!filename) + return; - if (skip_this_triangle) break; + if (filename.toLowerCase().lastIndexOf('.json') === filename.length - 5) + this.openJsonFile(filename); + else + this.openRootFile(filename); + }; - // is point z colinear with any pair of the triangle points? - // L1: - if (((this.fXN[p]-this.fXN[z])*(this.fYN[p]-this.fYN[n])) === ((this.fYN[p]-this.fYN[z])*(this.fXN[p]-this.fXN[n]))) { - // z is colinear with p and n - a = p; - b = n; - } else if (((this.fXN[p]-this.fXN[z])*(this.fYN[p]-this.fYN[m])) === ((this.fYN[p]-this.fYN[z])*(this.fXN[p]-this.fXN[m]))) { - // z is colinear with p and m - a = p; - b = m; - } else if (((this.fXN[n]-this.fXN[z])*(this.fYN[n]-this.fYN[m])) === ((this.fYN[n]-this.fYN[z])*(this.fXN[n]-this.fXN[m]))) { - // z is colinear with n and m - a = n; - b = m; - } else { - a = 0; - b = 0; - } - if (a !== 0) { - // point z is colinear with 2 of the triangle points, if it lies - // between them it's in the circle otherwise it's outside - if (this.fXN[a] !== this.fXN[b]) { - if (((this.fXN[z]-this.fXN[a])*(this.fXN[z]-this.fXN[b])) < 0) { - skip_this_triangle = true; - break; - // goto L90; - } else if (((this.fXN[z]-this.fXN[a])*(this.fXN[z]-this.fXN[b])) === 0) { - // At least two points are sitting on top of each other, we will - // treat these as one and not consider this a 'multiple points lying - // on a common circle' situation. It is a sign something could be - // wrong though, especially if the two coincident points have - // different fZ's. If they don't then this is harmless. - console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); - } - } else { - if (((this.fYN[z]-this.fYN[a])*(this.fYN[z]-this.fYN[b])) < 0) { - skip_this_triangle = true; - break; - // goto L90; - } else if (((this.fYN[z]-this.fYN[a])*(this.fYN[z]-this.fYN[b])) === 0) { - // At least two points are sitting on top of each other - see above. - console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); - } - } - // point is outside the circle, move to next point - continue; // goto L50; - } + main.select('.gui_selectFileName').property('value', '') + .on('change', evnt => main.select('.gui_urlToLoad').property('value', evnt.target.value)); + main.select('.gui_fileBtn').on('click', () => main.select('.gui_localFile').node().click()); - if (skip_this_triangle) break; // deepscan-disable-line + main.select('.gui_ReadFileBtn').on('click', () => this.readSelectedFile()); - /// Error("Interpolate", "Should not get to here"); - // may as well soldier on - // SL: initialize before try to find better values - f = m; - o1 = p; - o2 = n; + main.select('.gui_ResetUIBtn').on('click', () => this.clearHierarchy(true)); - // if point z were to look at the triangle, which point would it see - // lying between the other two? (we're going to form a quadrilateral - // from the points, and then demand certain properties of that - // quadrilateral) - dxz[0] = this.fXN[p]-this.fXN[z]; - dyz[0] = this.fYN[p]-this.fYN[z]; - dxz[1] = this.fXN[n]-this.fXN[z]; - dyz[1] = this.fYN[n]-this.fYN[z]; - dxz[2] = this.fXN[m]-this.fXN[z]; - dyz[2] = this.fYN[m]-this.fYN[z]; - for (l=1; l<=3; l++) { - dx1 = dxz[l-1]; - dx2 = dxz[l%3]; - dx3 = dxz[(l+1)%3]; - dy1 = dyz[l-1]; - dy2 = dyz[l%3]; - dy3 = dyz[(l+1)%3]; + main.select('.gui_urlToLoad').on('keyup', evnt => { + if (evnt.code === 'Enter') + this.readSelectedFile(); + }); - // u et v are used only to know their sign. The previous - // code computed them with a division which was long and - // might be a division by 0. It is now replaced by a - // multiplication. - u = (dy3*dx2-dx3*dy2)*(dy1*dx2-dx1*dy2); - v = (dy3*dx1-dx3*dy1)*(dy2*dx1-dx2*dy1); + main.select('.gui_localFile').on('change', evnt => { + const files = evnt.target.files; - if ((u >= 0) && (v >= 0)) { - // vector (dx3,dy3) is expressible as a sum of the other two vectors - // with positive coefficients -> i.e. it lies between the other two vectors - if (l === 1) { - f = m; o1 = p; o2 = n; // deepscan-disable-line - } else if (l === 2) { - f = p; o1 = n; o2 = m; - } else { - f = n; o1 = m; o2 = p; - } - break; // goto L2; - } - } - // L2: - // this is not a valid quadrilateral if the diagonals don't cross, - // check that points f and z lie on opposite side of the line o1-o2, - // this is true if the angle f-o1-z is greater than o2-o1-z and o2-o1-f - cfo1k = ((this.fXN[f]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))* - ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))); - co2o1k = ((this.fXN[o2]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[o2]-this.fXN[o1])*(this.fXN[o2]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[o2]-this.fYN[o1]))* - ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1]) + (this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))); - co2o1f = ((this.fXN[o2]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[o2]-this.fXN[o1])*(this.fXN[o2]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[o2]-this.fYN[o1]))* - ((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1]) + (this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))); - if ((cfo1k > co2o1k) || (cfo1k > co2o1f)) { - // not a valid quadrilateral - point z is definitely outside the circle - continue; // goto L50; - } - // calculate the 2 internal angles of the quadrangle formed by joining - // points z and f to points o1 and o2, at z and f. If they sum to less - // than 180 degrees then z lies outside the circle - dko1 = Math.sqrt((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1])); - dko2 = Math.sqrt((this.fXN[z]-this.fXN[o2])*(this.fXN[z]-this.fXN[o2])+(this.fYN[z]-this.fYN[o2])*(this.fYN[z]-this.fYN[o2])); - dfo1 = Math.sqrt((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1])); - dfo2 = Math.sqrt((this.fXN[f]-this.fXN[o2])*(this.fXN[f]-this.fXN[o2])+(this.fYN[f]-this.fYN[o2])*(this.fYN[f]-this.fYN[o2])); - c1 = ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o2])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o2]))/dko1/dko2; - c2 = ((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o2])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o2]))/dfo1/dfo2; - sin_sum = c1*Math.sqrt(1-c2*c2)+c2*Math.sqrt(1-c1*c1); + for (let n = 0; n < files.length; ++n) { + const f = files[n]; + main.select('.gui_urlToLoad').property('value', f.name); + this.openRootFile(f); + } + }); + } - // sin_sum doesn't always come out as zero when it should do. - if (sin_sum < -1.e-6) { - // z is inside the circle, this is not a Delaunay triangle - skip_this_triangle = true; - break; - // goto L90; - } else if (Math.abs(sin_sum) <= 1.e-6) { - // point z lies on the circumference of the circle (within rounding errors) - // defined by the triangle, so there is potential for degeneracy in the - // triangle set (Delaunay triangulation does not give a unique way to split - // a polygon whose points lie on a circle into constituent triangles). Make - // a note of the additional point number. - ndegen++; - degen = z; - fdegen = f; - o1degen = o1; - o2degen = o2; - } + const layout = main.select('.gui_layout'); + if (!layout.empty()) { + ['simple', 'vert2', 'vert3', 'vert231', 'horiz2', 'horiz32', 'flex', 'tabs', + 'grid 2x2', 'grid 1x3', 'grid 2x3', 'grid 3x3', 'grid 4x4'].forEach(kind => layout.append('option').attr('value', kind).text(kind)); - // L50: continue; - } // end of for ( z = 1 ...) loop + layout.on('change', ev => { + const kind = ev.target.value || 'flex'; + this.setDisplay(kind, this.gui_div + '_drawing'); + settings.DislpayKind = kind; + }); + } - if (skip_this_triangle) continue; + this.setDom(this.gui_div + '_browser_hierarchy'); - // This is a good triangle - if (ndegen > 0) { - // but is degenerate with at least one other, - // haven't figured out what to do if more than 4 points are involved - /// if (ndegen > 1) { - /// Error("Interpolate", - /// "More than 4 points lying on a circle. No decision making process formulated for triangulating this region in a non-arbitrary way %d %d %d %d", - /// p,n,m,degen); - /// return thevalue; - /// } + if (update_html) { + this.refreshHtml(); + this.initializeBrowser(); + } - // we have a quadrilateral which can be split down either diagonal - // (d<->f or o1<->o2) to form valid Delaunay triangles. Choose diagonal - // with highest average z-value. Whichever we choose we will have - // verified two triangles as good and two as bad, only note the good ones - d = degen; - f = fdegen; - o1 = o1degen; - o2 = o2degen; - if ((this.fZ[o1-1] + this.fZ[o2-1]) > (this.fZ[d-1] + this.fZ[f-1])) { - // best diagonalisation of quadrilateral is current one, we have - // the triangle - t1 = p; - t2 = n; - t3 = m; - // file the good triangles - this.FileIt(p, n, m); - this.FileIt(d, o1, o2); - } else { - // use other diagonal to split quadrilateral, use triangle formed by - // point f, the degnerate point d and whichever of o1 and o2 create - // an enclosing triangle - t1 = f; - t2 = d; - if (this.Enclose(f, d, o1, 0)) - t3 = o1; - else - t3 = o2; + return this.brlayout.toggleBrowserKind(browser_kind || 'fix'); + } - // file the good triangles - this.FileIt(f, d, o1); - this.FileIt(f, d, o2); - } - } else { - // this is a Delaunay triangle, file it - this.FileIt(p, n, m); - t1 = p; - t2 = n; - t3 = m; - } - // do the interpolation - thevalue = this.InterpolateOnPlane(t1, t2, t3, 0); - return thevalue; + /** @summary Initialize browser elements */ + initializeBrowser() { + const main = select(`#${this.gui_div} .jsroot_browser`); + if (main.empty() || !this.brlayout) + return; + + this.brlayout.adjustBrowserSize(); + + const selects = main.select('.gui_layout').node(); - // L90: continue; + if (selects) { + let found = false; + for (const i in selects.options) { + const s = selects.options[i].text; + if (!isStr(s)) + continue; + if ((s === this.getLayout()) || (s.replace(/ /g, '') === this.getLayout())) { + selects.selectedIndex = i; + found = true; + break; } } + if (!found) { + const opt = document.createElement('option'); + opt.innerText = opt.value = this.getLayout(); + selects.appendChild(opt); + selects.selectedIndex = selects.options.length - 1; + } + } + + if (this.is_online) { + if (this.h?._toptitle) + this.brlayout.setBrowserTitle(this.h._toptitle); + main.select('.gui_monitoring') + .property('checked', this.isMonitoring()) + .on('click', evnt => { + this.enableMonitoring(evnt.target.checked); + this.updateItems(); + }); + } else if (!this.no_select) { + let fname = ''; + this.forEachRootFile(item => { + if (!fname) + fname = item._fullurl; + }); + main.select('.gui_urlToLoad').property('value', fname); } - if (shouldbein) // deepscan-disable-line - console.error(`Interpolate Point outside hull when expected inside: this point could be dodgy ${xx} ${yy} ${ntris_tried}`); - return thevalue; } - /// Defines the number of triangles tested for a Delaunay triangle - /// (number of iterations) before abandoning the search + /** @summary Enable monitoring mode */ + enableMonitoring(on) { + this.setMonitoring(undefined, on); - SetMaxIter(n = 100000) { - this.fAllTri = false; - this.fMaxIter = n; + const chkbox = select(`#${this.gui_div} .jsroot_browser .gui_monitoring`); + if (!chkbox.empty() && (chkbox.property('checked') !== on)) + chkbox.property('checked', on); } - /// Sets the histogram bin height for points lying outside the convex hull ie: - /// the bins in the margin. +} // class HierarchyPainter - SetMarginBinsContent(z) { - this.fZout = z; +// ====================================================================================== + + +/** @summary Display streamer info + * @private */ +async function drawStreamerInfo(dom, lst) { + const painter = new HierarchyPainter('sinfo', dom, '__as_dark_mode__'); + + // in batch mode HTML drawing is not possible, just keep object reference for a minute + if (isBatchMode()) { + painter.selectDom().property('_json_object_', lst); + return painter; } + painter._streamer_info = true; + painter.h = createStreamerInfoContent(lst); + + // painter.selectDom().style('overflow','auto'); + + return painter.refreshHtml().then(() => { + painter.setTopPainter(); + return painter; + }); } - /** @summary Function handles tooltips in the mesh */ -function graph2DTooltip(intersect) { - let indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.index.length)) return null; - const sqr = v => v*v; +/** @summary Display inspector + * @private */ +async function drawInspector(dom, obj, opt) { + cleanup(dom); + const painter = new HierarchyPainter('inspector', dom, '__as_dark_mode__'); - indx = this.index[indx]; + // in batch mode HTML drawing is not possible, just keep object reference for a minute + if (isBatchMode()) { + painter.selectDom().property('_json_object_', obj); + return painter; + } - const fp = this.fp, gr = this.graph; - let grx = fp.grx(gr.fX[indx]), - gry = fp.gry(gr.fY[indx]), - grz = fp.grz(gr.fZ[indx]); + painter.default_by_click = kExpand; // default action + painter.with_icons = false; + painter._inspector = true; // keep + let expand_level = 0; - if (this.check_next && indx+1 { + if (!hitem?._obj) + return; + const obj2 = hitem._obj; + let ddom = this.selectDom().node(); + if (isFunc(this.removeInspector)) { + ddom = ddom.parentNode; + this.removeInspector(); + if (arg.indexOf(kInspect) === 0) + return this.showInspector(arg, obj2); + } + cleanup(ddom); + draw(ddom, obj2, arg); + }); + } }; -} + painter.h = createInspectorContent(obj); + return painter.refreshHtml().then(() => { + painter.setTopPainter(); + return painter.expandToLevel(expand_level); + }); +} -/** - * @summary Painter for TGraph2D classes - * @private - */ -class TGraph2DPainter extends ObjectPainter { +/** @summary Show object in inspector for provided object + * @protected */ +ObjectPainter.prototype.showInspector = function(opt, obj) { + if (opt === 'check') + return true; - /** @summary Decode options string */ - decodeOptions(opt, _gr) { - const d = new DrawOptions(opt); + const main = this.selectDom(), + rect = getElementRect(main), + w = Math.round(rect.width * 0.05) + 'px', + h = Math.round(rect.height * 0.05) + 'px', + id = 'root_inspector_' + internals.id_counter++; - if (!this.options) - this.options = {}; + main.append('div') + .attr('id', id) + .attr('class', 'jsroot_inspector') + .style('position', 'absolute') + .style('top', h) + .style('bottom', h) + .style('left', w) + .style('right', w); - const res = this.options, gr2d = this.getObject(); + if (!obj?._typename) + obj = isFunc(this.getPrimaryObject) ? this.getPrimaryObject() : this.getObject(); - if (d.check('FILL_', 'color') && gr2d) - gr2d.fFillColor = d.color; + return drawInspector(id, obj, opt); +}; - if (d.check('LINE_', 'color') && gr2d) - gr2d.fLineColor = d.color; - d.check('SAME'); - if (d.check('TRI1')) - res.Triangles = 11; // wireframe and colors - else if (d.check('TRI2')) - res.Triangles = 10; // only color triangles - else if (d.check('TRIW')) - res.Triangles = 1; - else if (d.check('TRI')) - res.Triangles = 2; - else - res.Triangles = 0; - res.Line = d.check('LINE'); - res.Error = d.check('ERR') && (this.matchObjectType(clTGraph2DErrors) || this.matchObjectType(clTGraph2DAsymmErrors)); +internals.drawInspector = drawInspector; - if (d.check('P0COL')) - res.Color = res.Circles = res.Markers = true; - else { - res.Color = d.check('COL'); - res.Circles = d.check('P0'); - res.Markers = d.check('P'); - } +var HierarchyPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +HierarchyPainter: HierarchyPainter, +drawInspector: drawInspector, +drawList: drawList, +drawStreamerInfo: drawStreamerInfo, +folderHierarchy: folderHierarchy, +keysHierarchy: keysHierarchy, +listHierarchy: listHierarchy, +markAsStreamerInfo: markAsStreamerInfo, +objectHierarchy: objectHierarchy, +parseAsArray: parseAsArray, +taskHierarchy: taskHierarchy +}); - if (!res.Markers) res.Color = false; +/** @summary Read style and settings from URL + * @private */ +function readStyleFromURL(url) { + // first try to read settings from local storage + const d = decodeUrl(url), + prefix = d.get('storage_prefix'); - if (res.Color || res.Triangles >= 10) - res.Zscale = d.check('Z'); + if (isStr(prefix) && prefix) + setStoragePrefix(prefix); - res.isAny = function() { - return this.Markers || this.Error || this.Circles || this.Line || this.Triangles; - }; + if (readSettings()) + setDefaultDrawOpt(settings._dflt_drawopt); - if (res.isAny()) { - res.Axis = 'lego2'; - if (res.Zscale) res.Axis += 'z'; - } else - res.Axis = opt; + readStyle(); - this.storeDrawOpt(opt); + function get_bool(name, field, special) { + if (d.has(name)) { + const val = d.get(name); + if (special && (val === special)) + settings[field] = special; + else + settings[field] = (val !== '0') && (val !== 'false') && (val !== 'off'); + } } - /** @summary Create histogram for axes drawing */ - createHistogram() { - const gr = this.getObject(), - asymm = this.matchObjectType(clTGraph2DAsymmErrors); - let xmin = gr.fX[0], xmax = xmin, - ymin = gr.fY[0], ymax = ymin, - zmin = gr.fZ[0], zmax = zmin; - - for (let p = 0; p < gr.fNpoints; ++p) { - const x = gr.fX[p], y = gr.fY[p], z = gr.fZ[p]; - - if (this.options.Error) { - xmin = Math.min(xmin, x - (asymm ? gr.fEXlow[p] : gr.fEX[p])); - xmax = Math.max(xmax, x + (asymm ? gr.fEXhigh[p] : gr.fEX[p])); - ymin = Math.min(ymin, y - (asymm ? gr.fEYlow[p] : gr.fEY[p])); - ymax = Math.max(ymax, y + (asymm ? gr.fEYhigh[p] : gr.fEY[p])); - zmin = Math.min(zmin, z - (asymm ? gr.fEZlow[p] : gr.fEZ[p])); - zmax = Math.max(zmax, z + (asymm ? gr.fEZhigh[p] : gr.fEZ[p])); - } else { - xmin = Math.min(xmin, x); - xmax = Math.max(xmax, x); - ymin = Math.min(ymin, y); - ymax = Math.max(ymax, y); - zmin = Math.min(zmin, z); - zmax = Math.max(zmax, z); - } + if (d.has('optimize')) { + settings.OptimizeDraw = 2; + let optimize = d.get('optimize'); + if (optimize) { + optimize = parseInt(optimize); + if (Number.isInteger(optimize)) + settings.OptimizeDraw = optimize; } + } - function calc_delta(min, max, margin) { - if (min < max) return margin * (max - min); - return Math.abs(min) < 1e5 ? 0.02 : 0.02 * Math.abs(min); - } - const dx = calc_delta(xmin, xmax, gr.fMargin), - dy = calc_delta(ymin, ymax, gr.fMargin), - dz = calc_delta(zmin, zmax, 0); - let uxmin = xmin - dx, uxmax = xmax + dx, - uymin = ymin - dy, uymax = ymax + dy, - uzmin = zmin - dz, uzmax = zmax + dz; + if (d.has('scale')) { + const s = parseInt(d.get('scale')); + settings.CanvasScale = Number.isInteger(s) ? s : 2; + } - if ((uxmin < 0) && (xmin >= 0)) uxmin = xmin*0.98; - if ((uxmax > 0) && (xmax <= 0)) uxmax = 0; + const b = d.get('batch'); + if (b !== undefined) { + setBatchMode(d !== 'off'); + if (b === 'png') + internals.batch_png = true; + } - if ((uymin < 0) && (ymin >= 0)) uymin = ymin*0.98; - if ((uymax > 0) && (ymax <= 0)) uymax = 0; + get_bool('lastcycle', 'OnlyLastCycle'); + get_bool('usestamp', 'UseStamp'); + get_bool('dark', 'DarkMode'); + get_bool('approx_text_size', 'ApproxTextSize'); - if ((uzmin < 0) && (zmin >= 0)) uzmin = zmin*0.98; - if ((uzmax > 0) && (zmax <= 0)) uzmax = 0; + let mr = d.get('maxranges'); + if (mr) { + mr = parseInt(mr); + if (Number.isInteger(mr)) + settings.MaxRanges = mr; + } - const graph = this.getObject(); + if (d.has('wrong_http_response')) + settings.HandleWrongHttpResponse = true; - if (graph.fMinimum !== kNoZoom) uzmin = graph.fMinimum; - if (graph.fMaximum !== kNoZoom) uzmax = graph.fMaximum; + if (d.has('prefer_saved_points')) + settings.PreferSavedPoints = true; - this._own_histogram = true; // when histogram created on client side + const tf1_style = d.get('tf1'); + if (tf1_style === 'curve') + settings.FuncAsCurve = true; + else if (tf1_style === 'line') + settings.FuncAsCurve = false; - const histo = createHistogram(clTH2F, graph.fNpx, graph.fNpy); - histo.fName = graph.fName + '_h'; - setHistogramTitle(histo, graph.fTitle); - histo.fXaxis.fXmin = uxmin; - histo.fXaxis.fXmax = uxmax; - histo.fYaxis.fXmin = uymin; - histo.fYaxis.fXmax = uymax; - histo.fZaxis.fXmin = uzmin; - histo.fZaxis.fXmax = uzmax; - histo.fMinimum = uzmin; - histo.fMaximum = uzmax; - histo.fBits |= kNoStats; + if (d.has('with_credentials')) + settings.WithCredentials = true; - if (!this.options.isAny()) { - const dulaunay = this.buildDelaunay(graph); - if (dulaunay) { - for (let i = 0; i < graph.fNpx; ++i) { - const xx = uxmin + (i + 0.5) / graph.fNpx * (uxmax - uxmin); - for (let j = 0; j < graph.fNpy; ++j) { - const yy = uymin + (j + 0.5) / graph.fNpy * (uymax - uymin), - zz = dulaunay.ComputeZ(xx, yy); - histo.fArray[histo.getBin(i+1, j+1)] = zz; - } - } + let inter = d.get('interactive'); + if (inter === 'nomenu') + settings.ContextMenu = false; + else if (inter !== undefined) { + if (!inter || (inter === '1')) + inter = '111111'; + else if (inter === '0') + inter = '000000'; + if (inter.length === 6) { + switch (inter[0]) { + case '0': + settings.ToolBar = false; + break; + case '1': + settings.ToolBar = 'popup'; + break; + case '2': + settings.ToolBar = true; + break; } + inter = inter.slice(1); } - - return histo; - } - - buildDelaunay(graph) { - if (!this._delaunay) { - this._delaunay = new TGraphDelaunay(graph); - this._delaunay.FindAllTriangles(); - if (!this._delaunay.fNdt) - delete this._delaunay; + if (inter.length === 5) { + settings.Tooltip = parseInt(inter[0]); + settings.ContextMenu = (inter[1] !== '0'); + settings.Zooming = (inter[2] !== '0'); + settings.MoveResize = (inter[3] !== '0'); + settings.DragAndDrop = (inter[4] !== '0'); } - return this._delaunay; } - drawTriangles(fp, graph, levels, palette) { - const dulaunay = this.buildDelaunay(graph); - if (!dulaunay) return; + get_bool('tooltip', 'Tooltip'); - const main_grz = !fp.logz ? fp.grz : value => (value < fp.scale_zmin) ? -0.1 : fp.grz(value), - plain_mode = this.options.Triangles === 2, - do_faces = (this.options.Triangles >= 10) || plain_mode, - do_lines = (this.options.Triangles % 10 === 1) || (plain_mode && (graph.fLineColor !== graph.fFillColor)), - triangles = new Triangles3DHandler(levels, main_grz, 0, 2*fp.size_z3d, do_lines); + const mathjax = d.get('mathjax', null); + let latex = d.get('latex', null); + if ((mathjax !== null) && (mathjax !== '0') && (latex === null)) + latex = 'math'; + if (latex !== null) + settings.Latex = constants$1.Latex.fromString(latex); - for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { - triangles.createBuffers(); + if (d.has('nomenu')) + settings.ContextMenu = false; + if (d.has('noprogress')) + settings.ProgressBox = false; + else + get_bool('progress', 'ProgressBox', 'modal'); - for (let t = 0; t < dulaunay.fNdt; ++t) { - const points = [dulaunay.fPTried[t], dulaunay.fNTried[t], dulaunay.fMTried[t]], - coord = []; - let use_triangle = true; - for (let i = 0; i < 3; ++i) { - const pnt = points[i] - 1; - coord.push(fp.grx(graph.fX[pnt]), fp.gry(graph.fY[pnt]), main_grz(graph.fZ[pnt])); + if (d.has('notouch')) + browser.touches = false; + if (d.has('adjframe')) + settings.CanAdjustFrame = true; - if ((graph.fX[pnt] < fp.scale_xmin) || (graph.fX[pnt] > fp.scale_xmax) || - (graph.fY[pnt] < fp.scale_ymin) || (graph.fY[pnt] > fp.scale_ymax)) - use_triangle = false; - } + const has_toolbar = d.has('toolbar'); + if (has_toolbar) { + const toolbar = d.get('toolbar', ''); + let val = null; + if (toolbar.indexOf('popup') >= 0) + val = 'popup'; + if (toolbar.indexOf('left') >= 0) { + settings.ToolBarSide = 'left'; + val = 'popup'; + } + if (toolbar.indexOf('right') >= 0) { + settings.ToolBarSide = 'right'; + val = 'popup'; + } + if (toolbar.indexOf('vert') >= 0) { + settings.ToolBarVert = true; + val = 'popup'; + } + if (toolbar.indexOf('show') >= 0) + val = true; + settings.ToolBar = val || ((toolbar.indexOf('0') < 0) && (toolbar.indexOf('false') < 0) && (toolbar.indexOf('off') < 0)); + } - if (do_faces && use_triangle) - triangles.addMainTriangle(...coord); + get_bool('skipsi', 'SkipStreamerInfos'); + get_bool('skipstreamerinfos', 'SkipStreamerInfos'); - if (do_lines && use_triangle) { - triangles.addLineSegment(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]); + if (d.has('nodraggraphs')) + settings.DragGraphs = false; - triangles.addLineSegment(coord[3], coord[4], coord[5], coord[6], coord[7], coord[8]); + if (d.has('palette')) { + const palette = parseInt(d.get('palette')); + if (Number.isInteger(palette) && (palette > 0) && (palette < 113)) + settings.Palette = palette; + } - triangles.addLineSegment(coord[6], coord[7], coord[8], coord[0], coord[1], coord[2]); - } - } - } + const render3d = d.get('render3d'), embed3d = d.get('embed3d'), geosegm = d.get('geosegm'); + if (render3d) + settings.Render3D = constants$1.Render3D.fromString(render3d); + if (embed3d) + settings.Embed3D = constants$1.Embed3D.fromString(embed3d); + if (geosegm) + settings.GeoGradPerSegm = Math.max(2, parseInt(geosegm)); + get_bool('geocomp', 'GeoCompressComp'); - triangles.callFuncs((lvl, pos) => { - const geometry = createLegoGeom(this.getMainPainter(), pos, null, 100, 100), - color = plain_mode ? this.getColor(graph.fFillColor) : palette.calcColor(lvl, levels.length), - material = new MeshBasicMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })), + if (d.has('hlimit')) + settings.HierarchyLimit = parseInt(d.get('hlimit')); - mesh = new Mesh(geometry, material); + function get_int_style(name, field, dflt) { + if (!d.has(name)) + return; + const val = d.get(name); + if (!val || (val === 'true') || (val === 'on')) + gStyle[field] = dflt; + else if ((val === 'false') || (val === 'off')) + gStyle[field] = 0; + else + gStyle[field] = parseInt(val); + return gStyle[field]; + } + function get_float_style(name, field) { + if (!d.has(name)) + return; + const val = d.get(name), + flt = Number.parseFloat(val); + if (Number.isFinite(flt)) + gStyle[field] = flt; + } - fp.add3DMesh(mesh, this); + if (d.has('histzero')) + gStyle.fHistMinimumZero = true; + if (d.has('histmargin')) + gStyle.fHistTopMargin = parseFloat(d.get('histmargin')); + get_int_style('optstat', 'fOptStat', 1111); + get_int_style('optfit', 'fOptFit', 0); + const has_date = get_int_style('optdate', 'fOptDate', 1), + has_file = get_int_style('optfile', 'fOptFile', 1); + if ((has_date || has_file) && !has_toolbar) + settings.ToolBarVert = true; + get_float_style('datex', 'fDateX'); + get_float_style('datey', 'fDateY'); + get_float_style('barwidth', 'fBarWidth'); + get_float_style('baroffset', 'fBarOffset'); - mesh.painter = this; // to let use it with context menu - }, (_isgrid, lpos) => { - const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), - linemesh = createLineSegments(convertLegoBuf(this.getMainPainter(), lpos, 100, 100), material); - fp.add3DMesh(linemesh, this); - }); + get_int_style('opttitle', 'fOptTitle', 1); + if (d.has('utc')) + settings.TimeZone = 'UTC'; + if (d.has('cet')) + settings.TimeZone = 'Europe/Berlin'; + else if (d.has('timezone')) { + settings.TimeZone = d.get('timezone'); + if ((settings.TimeZone === 'default') || (settings.TimeZone === 'dflt')) + settings.TimeZone = 'Europe/Berlin'; + else if (settings.TimeZone === 'local') + settings.TimeZone = ''; } - /** @summary Update TGraph2D object */ - updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; - - if (opt && (opt !== this.options.original)) - this.decodeOptions(opt, obj); + gStyle.fStatFormat = d.get('statfmt', gStyle.fStatFormat); + gStyle.fFitFormat = d.get('fitfmt', gStyle.fFitFormat); - Object.assign(this.getObject(), obj); + if (d.has('colors')) { + parseAsArray(d.get('colors')).forEach(elem => { + if (!isStr(elem)) + return; + const p = elem.indexOf('_'); + if (p < 0) + return; + const id = parseInt(elem.slice(0, p)), + col = elem.slice(p + 1); + if (col.match(/^[a-fA-F0-9]+/) && ((col.length === 6) || (col.length === 8))) + addColor('#' + col, null, id); + }); + } +} - delete this._delaunay; // rebuild triangles - delete this.$redraw_hist; +/** @summary Build main GUI + * @desc Used in many HTML files to create JSROOT GUI elements + * @param {String} gui_element - id of the `
    ` element + * @param {String} gui_kind - either 'online', 'nobrowser', 'draw' + * @return {Promise} with {@link HierarchyPainter} instance + * @example + * import { buildGUI } from 'https://root.cern/js/latest/modules/gui.mjs'; + * buildGUI('guiDiv'); */ +async function buildGUI(gui_element, gui_kind = '') { + const myDiv = select(isStr(gui_element) ? `#${gui_element}` : gui_element); + if (myDiv.empty()) + return Promise.reject(Error('no div for gui found')); - // if our own histogram was used as axis drawing, we need update histogram as well - if (this.axes_draw) { - const hist_painter = this.getMainPainter(); - hist_painter?.updateObject(this.createHistogram(), this.options.Axis); - this.$redraw_hist = hist_painter; - } + myDiv.html(''); // clear element - return true; - } + const d = decodeUrl(), getSize = name => { + const res = d.has(name) ? d.get(name).split('x') : []; + if (res.length !== 2) + return null; + res[0] = parseInt(res[0]); + res[1] = parseInt(res[1]); + return res[0] > 0 && res[1] > 0 ? res : null; + }; + let online = (gui_kind === 'online'), nobrowser = false, drawing = false; - /** @summary Redraw TGraph2D object - * @desc Update histogram drawing if necessary - * @return {Promise} for drawing ready */ - async redraw() { - let promise = Promise.resolve(true); + if (gui_kind === 'draw') + online = drawing = nobrowser = true; + else if ((gui_kind === 'nobrowser') || d.has('nobrowser') || (myDiv.attr('nobrowser') && myDiv.attr('nobrowser') !== 'false')) + nobrowser = true; - if (this.$redraw_hist) { - promise = this.$redraw_hist.redraw(); - delete this.$redraw_hist; - } + if (myDiv.attr('ignoreurl') === 'true') + settings.IgnoreUrlOptions = true; - return promise.then(() => this.drawGraph2D()); - } + readStyleFromURL(); - /** @summary Actual drawing of TGraph2D object - * @return {Promise} for drawing ready */ - async drawGraph2D() { - const main = this.getMainPainter(), - fp = this.getFramePainter(), - graph = this.getObject(); + if (isBatchMode()) + nobrowser = true; - if (!graph || !main || !fp || !fp.mode3d) - return this; + const divsize = getSize('divsize'), canvsize = getSize('canvsize'), smallpad = getSize('smallpad'); + if (divsize) + myDiv.style('position', 'relative').style('width', divsize[0] + 'px').style('height', divsize[1] + 'px'); + else if (!isBatchMode()) { + select('html').style('height', '100%'); + select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden'); + myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px'); + } + if (canvsize) { + settings.CanvasWidth = canvsize[0]; + settings.CanvasHeight = canvsize[1]; + } + if (smallpad) { + settings.SmallPad.width = smallpad[0]; + settings.SmallPad.height = smallpad[1]; + } - fp.remove3DMeshes(this); + const hpainter = new HierarchyPainter('root', null); + if (online) + hpainter.is_online = drawing ? 'draw' : 'online'; + if (drawing || isBatchMode()) + hpainter.exclude_browser = true; + hpainter.start_without_browser = nobrowser; - if (!this.options.isAny()) { - // no need to draw somthing if histogram content was drawn - if (main.draw_content) - return this; - if ((graph.fMarkerSize === 1) && (graph.fMarkerStyle === 1)) - this.options.Circles = true; - else - this.options.Markers = true; - } + return hpainter.startGUI(myDiv).then(() => { + if (!nobrowser) + return hpainter.initializeBrowser(); + if (!drawing) + return; + const func = internals.getCachedObject || findFunction('GetCachedObject'), + obj = isFunc(func) ? parse$1(func()) : undefined; + if (isObject(obj)) + hpainter.setCachedObject(obj); + let opt = d.get('opt', ''); + if (d.has('websocket')) + opt += ';websocket'; + return hpainter.display('', opt); + }).then(() => hpainter); +} - const countSelected = (zmin, zmax) => { - let cnt = 0; - for (let i = 0; i < graph.fNpoints; ++i) { - if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) || - (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) || - (graph.fZ[i] < zmin) || (graph.fZ[i] >= zmax)) continue; +var _rollup_plugin_ignore_empty_module_placeholder = {}; - ++cnt; - } - return cnt; - }; +var _rollup_plugin_ignore_empty_module_placeholder$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +default: _rollup_plugin_ignore_empty_module_placeholder +}); - // try to define scale-down factor - let step = 1; - if ((settings.OptimizeDraw > 0) && !fp.webgl) { - const numselected = countSelected(fp.scale_zmin, fp.scale_zmax), - sizelimit = 50000; +/** @summary Draw TEllipse + * @private */ +function drawEllipse() { + const ellipse = this.getObject(), + closed_ellipse = (ellipse.fPhimin === 0) && (ellipse.fPhimax === 360), + is_crown = (ellipse._typename === 'TCrown'); - if (numselected > sizelimit) { - step = Math.floor(numselected / sizelimit); - if (step <= 2) step = 2; - } - } + this.createAttLine({ attr: ellipse }); + this.createAttFill({ attr: ellipse }); - const markeratt = this.createAttMarker({ attr: graph, std: false }), - promises = []; - let palette = null, - levels = [fp.scale_zmin, fp.scale_zmax], - scale = fp.size_x3d / 100 * markeratt.getFullSize(); + const g = this.createG(), + funcs = this.getAxisToSvgFunc(), + x = funcs.x(ellipse.fX1), + y = funcs.y(ellipse.fY1), + rx = is_crown && (ellipse.fR1 <= 0) ? (funcs.x(ellipse.fX1 + ellipse.fR2) - x) : (funcs.x(ellipse.fX1 + ellipse.fR1) - x), + ry = y - funcs.y(ellipse.fY1 + ellipse.fR2), + dr = Math.PI / 180; - if (this.options.Circles) - scale = 0.06 * fp.size_x3d; + let path = ''; - if (fp.usesvg) scale *= 0.3; + if (is_crown && (ellipse.fR1 > 0)) { + const ratio = ellipse.fYXRatio ?? 1, + rx1 = rx, ry2 = ratio * ry, + ry1 = ratio * (y - funcs.y(ellipse.fY1 + ellipse.fR1)), + rx2 = funcs.x(ellipse.fX1 + ellipse.fR2) - x; - scale *= 7 * Math.max(fp.size_x3d / fp.getFrameWidth(), fp.size_z3d / fp.getFrameHeight()); + if (closed_ellipse) { + path = `M${-rx1},0A${rx1},${ry1},0,1,0,${rx1},0A${rx1},${ry1},0,1,0,${-rx1},0` + + `M${-rx2},0A${rx2},${ry2},0,1,0,${rx2},0A${rx2},${ry2},0,1,0,${-rx2},0`; + } else { + const large_arc = (ellipse.fPhimax - ellipse.fPhimin >= 180) ? 1 : 0, + a1 = ellipse.fPhimin * dr, a2 = ellipse.fPhimax * dr, + dx1 = Math.round(rx1 * Math.cos(a1)), dy1 = Math.round(ry1 * Math.sin(a1)), + dx2 = Math.round(rx1 * Math.cos(a2)), dy2 = Math.round(ry1 * Math.sin(a2)), + dx3 = Math.round(rx2 * Math.cos(a1)), dy3 = Math.round(ry2 * Math.sin(a1)), + dx4 = Math.round(rx2 * Math.cos(a2)), dy4 = Math.round(ry2 * Math.sin(a2)); - if (this.options.Color || (this.options.Triangles >= 10)) { - levels = main.getContourLevels(true); - palette = main.getHistPalette(); + path = `M${dx2},${dy2}A${rx1},${ry1},0,${large_arc},0,${dx1},${dy1}` + + `L${dx3},${dy3}A${rx2},${ry2},0,${large_arc},1,${dx4},${dy4}Z`; } + } else if (ellipse.fTheta === 0) { + if (closed_ellipse) + path = `M${-rx},0A${rx},${ry},0,1,0,${rx},0A${rx},${ry},0,1,0,${-rx},0Z`; + else { + const x1 = Math.round(rx * Math.cos(ellipse.fPhimin * dr)), + y1 = Math.round(ry * Math.sin(ellipse.fPhimin * dr)), + x2 = Math.round(rx * Math.cos(ellipse.fPhimax * dr)), + y2 = Math.round(ry * Math.sin(ellipse.fPhimax * dr)); + path = `M0,0L${x1},${y1}A${rx},${ry},0,1,1,${x2},${y2}Z`; + } + } else { + const ct = Math.cos(ellipse.fTheta * dr), + st = Math.sin(ellipse.fTheta * dr), + phi1 = ellipse.fPhimin * dr, + phi2 = ellipse.fPhimax * dr, + np = 200, + dphi = (phi2 - phi1) / (np - (closed_ellipse ? 0 : 1)); + let lastx = 0, lasty = 0; + if (!closed_ellipse) + path = 'M0,0'; + for (let n = 0; n < np; ++n) { + const angle = phi1 + n * dphi, + dx = ellipse.fR1 * Math.cos(angle), + dy = ellipse.fR2 * Math.sin(angle), + px = funcs.x(ellipse.fX1 + dx * ct - dy * st) - x, + py = funcs.y(ellipse.fY1 + dx * st + dy * ct) - y; + if (!path) + path = `M${px},${py}`; + else if (lastx === px) + path += `v${py - lasty}`; + else if (lasty === py) + path += `h${px - lastx}`; + else + path += `l${px - lastx},${py - lasty}`; + lastx = px; + lasty = py; + } + path += 'Z'; + } - if (this.options.Triangles) - this.drawTriangles(fp, graph, levels, palette); - - for (let lvl = 0; lvl < levels.length-1; ++lvl) { - const lvl_zmin = Math.max(levels[lvl], fp.scale_zmin), - lvl_zmax = Math.min(levels[lvl+1], fp.scale_zmax); + this.x = x; + this.y = y; - if (lvl_zmin >= lvl_zmax) continue; + makeTranslate(g.append('svg:path'), x, y) + .attr('d', path) + .call(this.lineatt.func) + .call(this.fillatt.func); - const size = Math.floor(countSelected(lvl_zmin, lvl_zmax) / step), - index = new Int32Array(size); - let pnts = null, select = 0, icnt = 0, - err = null, asymm = false, line = null, ierr = 0, iline = 0; + assignContextMenu(this); - if (this.options.Markers || this.options.Circles) - pnts = new PointsCreator(size, fp.webgl, scale/3); + addMoveHandler(this); - if (this.options.Error) { - err = new Float32Array(size*6*3); - asymm = this.matchObjectType(clTGraph2DAsymmErrors); - } + this.moveDrag = function(dx, dy) { + this.x += dx; + this.y += dy; + makeTranslate(this.getG().select('path'), this.x, this.y); + }; - if (this.options.Line) - line = new Float32Array((size-1)*6); + this.moveEnd = function(not_changed) { + if (not_changed) + return; + const ell = this.getObject(); + ell.fX1 = this.svgToAxis('x', this.x); + ell.fY1 = this.svgToAxis('y', this.y); + this.submitCanvExec(`SetX1(${ell.fX1});;SetY1(${ell.fY1});;Notify();;`); + }; +} - for (let i = 0; i < graph.fNpoints; ++i) { - if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) || - (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) || - (graph.fZ[i] < lvl_zmin) || (graph.fZ[i] >= lvl_zmax)) continue; +/** @summary Draw TMarker + * @private */ +function drawMarker$1() { + const marker = this.getObject(), + kMarkerNDC = BIT(14); - if (step > 1) { - select = (select+1) % step; - if (select !== 0) continue; - } + this.isndc = marker.TestBit(kMarkerNDC); - index[icnt++] = i; // remember point index for tooltip + const d = new DrawOptions(this.getDrawOpt()), + use_frame = this.isndc ? false : d.check('FRAME'), + swap_xy = use_frame && this.getFramePainter()?.swap_xy(); - const x = fp.grx(graph.fX[i]), - y = fp.gry(graph.fY[i]), - z = fp.grz(graph.fZ[i]); + this.createAttMarker({ attr: marker }); - if (pnts) pnts.addPoint(x, y, z); + const g = this.createG(use_frame ? 'frame2d' : undefined); - if (err) { - err[ierr] = fp.grx(graph.fX[i] - (asymm ? graph.fEXlow[i] : graph.fEX[i])); - err[ierr+1] = y; - err[ierr+2] = z; - err[ierr+3] = fp.grx(graph.fX[i] + (asymm ? graph.fEXhigh[i] : graph.fEX[i])); - err[ierr+4] = y; - err[ierr+5] = z; - ierr+=6; - err[ierr] = x; - err[ierr+1] = fp.gry(graph.fY[i] - (asymm ? graph.fEYlow[i] : graph.fEY[i])); - err[ierr+2] = z; - err[ierr+3] = x; - err[ierr+4] = fp.gry(graph.fY[i] + (asymm ? graph.fEYhigh[i] : graph.fEY[i])); - err[ierr+5] = z; - ierr+=6; - err[ierr] = x; - err[ierr+1] = y; - err[ierr+2] = fp.grz(graph.fZ[i] - (asymm ? graph.fEZlow[i] : graph.fEZ[i])); - err[ierr+3] = x; - err[ierr+4] = y; - err[ierr+5] = fp.grz(graph.fZ[i] + (asymm ? graph.fEZhigh[i] : graph.fEZ[i])); - ierr+=6; - } + let x = this.axisToSvg('x', marker.fX, this.isndc), + y = this.axisToSvg('y', marker.fY, this.isndc); + if (swap_xy) + [x, y] = [y, x]; - if (line) { - if (iline>=6) { - line[iline] = line[iline-3]; - line[iline+1] = line[iline-2]; - line[iline+2] = line[iline-1]; - iline+=3; - } - line[iline] = x; - line[iline+1] = y; - line[iline+2] = z; - iline+=3; - } - } + const path = this.markeratt.create(x, y); - if (line && (iline > 3) && (line.length === iline)) { - const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), - linemesh = createLineSegments(line, material); - fp.add3DMesh(linemesh, this); + if (path) { + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); + } - linemesh.graph = graph; - linemesh.index = index; - linemesh.fp = fp; - linemesh.scale0 = 0.7*scale; - linemesh.tip_name = this.getObjectHint(); - linemesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - linemesh.nvertex = 2; - linemesh.check_next = true; + if (d.check('NO_INTERACTIVE')) + return; - linemesh.tooltip = graph2DTooltip; - } + assignContextMenu(this); - if (err) { - const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), - errmesh = createLineSegments(err, material); - fp.add3DMesh(errmesh, this); + addMoveHandler(this); - errmesh.graph = graph; - errmesh.index = index; - errmesh.fp = fp; - errmesh.scale0 = 0.7*scale; - errmesh.tip_name = this.getObjectHint(); - errmesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - errmesh.nvertex = 6; + this.dx = this.dy = 0; - errmesh.tooltip = graph2DTooltip; - } + this.moveDrag = function(dx, dy) { + this.dx += dx; + this.dy += dy; + if (this.getG()) + makeTranslate(this.getG().select('path'), this.dx, this.dy); + }; - if (pnts) { - let color = 'blue'; + this.moveEnd = function(not_changed) { + if (not_changed || !this.getG()) + return; + const mrk = this.getObject(); + let fx = this.svgToAxis('x', this.axisToSvg('x', mrk.fX, this.isndc) + this.dx, this.isndc), + fy = this.svgToAxis('y', this.axisToSvg('y', mrk.fY, this.isndc) + this.dy, this.isndc); + if (swap_xy) + [fx, fy] = [fy, fx]; + mrk.fX = fx; + mrk.fY = fy; + this.submitCanvExec(`SetX(${fx});;SetY(${fy});;Notify();;`); + this.redraw(); + }; +} - if (!this.options.Circles || this.options.Color) - color = palette?.calcColor(lvl, levels.length) ?? this.getColor(graph.fMarkerColor); +/** @summary Draw TPolyMarker + * @private */ +function drawPolyMarker() { + const poly = this.getObject(), + func = this.getAxisToSvgFunc(); - const pr = pnts.createPoints({ color, style: this.options.Circles ? 4 : graph.fMarkerStyle }).then(mesh => { - mesh.graph = graph; - mesh.fp = fp; - mesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - mesh.scale0 = 0.3*scale; - mesh.index = index; + this.createAttMarker({ attr: poly }); - mesh.tip_name = this.getObjectHint(); - mesh.tooltip = graph2DTooltip; - fp.add3DMesh(mesh, this); - }); + const g = this.createG(); - promises.push(pr); - } - } + let path = ''; + for (let n = 0; n <= poly.fLastPoint; ++n) + path += this.markeratt.create(func.x(poly.fX[n]), func.y(poly.fY[n])); - return Promise.all(promises).then(() => { - if (this.options.Zscale && this.axes_draw) { - const pal = this.getMainPainter()?.findFunction(clTPaletteAxis), - pal_painter = this.getPadPainter()?.findPainterFor(pal); - return pal_painter?.drawPave(); - } - }).then(() => { - fp.render3D(100); - return this; - }); + if (path) { + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); } - /** @summary draw TGraph2D object */ - static async draw(dom, gr, opt) { - const painter = new TGraph2DPainter(dom, gr); - painter.decodeOptions(opt, gr); + assignContextMenu(this); - let promise = Promise.resolve(null); + addMoveHandler(this); - if (!painter.getMainPainter()) { - // histogram is not preserved in TGraph2D - promise = TH2Painter.draw(dom, painter.createHistogram(), painter.options.Axis); - painter.axes_draw = true; + this.dx = this.dy = 0; + + this.moveDrag = function(dx, dy) { + this.dx += dx; + this.dy += dy; + if (this.getG()) + makeTranslate(this.getG().select('path'), this.dx, this.dy); + }; + + this.moveEnd = function(not_changed) { + if (not_changed || !this.getG()) + return; + const poly2 = this.getObject(), + func2 = this.getAxisToSvgFunc(); + let exec = ''; + for (let n = 0; n <= poly2.fLastPoint; ++n) { + const x = this.svgToAxis('x', func2.x(poly2.fX[n]) + this.dx), + y = this.svgToAxis('y', func2.y(poly2.fY[n]) + this.dy); + poly2.fX[n] = x; + poly2.fY[n] = y; + exec += `SetPoint(${n},${x},${y});;`; } + this.submitCanvExec(exec + 'Notify();;'); + this.redraw(); + }; +} - return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawGraph2D(); - }); +/** @summary Draw JS image + * @private */ +function drawJSImage(dom, obj, opt) { + const painter = new BasePainter(dom), + main = painter.selectDom(), + img = main.append('img').attr('src', obj.fName).attr('title', obj.fTitle || obj.fName); + + if (opt && opt.indexOf('scale') >= 0) + img.style('width', '100%').style('height', '100%'); + else if (opt && opt.indexOf('center') >= 0) { + main.style('position', 'relative'); + img.attr('style', 'margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'); } -} // class TGraph2DPainter + painter.setTopPainter(); -var TGraph2DPainter$1 = /*#__PURE__*/Object.freeze({ + return painter; +} + +var more = /*#__PURE__*/Object.freeze({ __proto__: null, -TGraph2DPainter: TGraph2DPainter +drawEllipse: drawEllipse, +drawJSImage: drawJSImage, +drawMarker: drawMarker$1, +drawPolyMarker: drawPolyMarker }); -/** - * @summary Painter for TGraphPolargram objects. - * - * @private */ +/** @summary direct draw function for TPolyMarker3D object + * @private */ +async function drawPolyMarker3D$1() { + const fp = this.$fp || this.getFramePainter(); -class TGraphPolargramPainter extends ObjectPainter { + delete this.$fp; - /** @summary Create painter - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} polargram - object to draw */ - constructor(dom, polargram) { - super(dom, polargram); - this.$polargram = true; // indicate that this is polargram - this.zoom_rmin = this.zoom_rmax = 0; - } + if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) + return this; - /** @summary Translate coordinates */ - translate(angle, radius, keep_float) { - let rx = this.r(radius), - ry = rx/this.szx*this.szy, - grx = rx * Math.cos(-angle - this.angle), - gry = ry * Math.sin(-angle - this.angle); + const poly = this.getObject(), sizelimit = 50000, fP = poly.fP; + let step = 1, numselect = 0; - if (!keep_float) { - grx = Math.round(grx); - gry = Math.round(gry); - rx = Math.round(rx); - ry = Math.round(ry); - } - return { grx, gry, rx, ry }; + for (let i = 0; i < fP.length; i += 3) { + if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || + (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || + (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) + continue; + ++numselect; } - /** @summary format label for radius ticks */ - format(radius) { - if (radius === Math.round(radius)) return radius.toString(); - if (this.ndig > 10) return radius.toExponential(4); + if ((settings.OptimizeDraw > 0) && (numselect > sizelimit)) + step = Math.max(2, Math.floor(numselect / sizelimit)); - return radius.toFixed((this.ndig > 0) ? this.ndig : 0); - } + const size = Math.floor(numselect / step), + pnts = new PointsCreator(size, fp.webgl, fp.size_x3d / 100), + index = new Int32Array(size); + let select = 0, icnt = 0; - /** @summary Convert axis values to text */ - axisAsText(axis, value) { - if (axis === 'r') { - if (value === Math.round(value)) return value.toString(); - if (this.ndig>10) return value.toExponential(4); - return value.toFixed(this.ndig+2); + for (let i = 0; i < fP.length; i += 3) { + if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || + (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || + (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) + continue; + + if (step > 1) { + select = (select + 1) % step; + if (select) + continue; } - value *= 180/Math.PI; - return (value === Math.round(value)) ? value.toString() : value.toFixed(1); - } + index[icnt++] = i; - /** @summary Returns coordinate of frame - without using frame itself */ - getFrameRect() { - const pp = this.getPadPainter(), - pad = pp.getRootPad(true), - w = pp.getPadWidth(), - h = pp.getPadHeight(), - rect = {}; + pnts.addPoint(fp.grx(fP[i]), fp.gry(fP[i + 1]), fp.grz(fP[i + 2])); + } - if (pad) { - rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin))*w); - rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin))*h); - } else { - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); - } + return pnts.createPoints({ color: this.getColor(poly.fMarkerColor), style: poly.fMarkerStyle }).then(mesh => { + mesh.tip_color = (poly.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; + mesh.tip_name = poly.fName || 'Poly3D'; + mesh.poly = poly; + mesh.fp = fp; + mesh.scale0 = 0.7 * pnts.scale; + mesh.index = index; - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); + fp.add3DMesh(mesh, this, true); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; + mesh.tooltip = function(intersect) { + let indx = Math.floor(intersect.index / this.nvertex); + if ((indx < 0) || (indx >= this.index.length)) + return null; - rect.transform = makeTranslate(rect.x, rect.y) || ''; + indx = this.index[indx]; - return rect; - } + const fp2 = this.fp, + grx = fp2.grx(this.poly.fP[indx]), + gry = fp2.gry(this.poly.fP[indx + 1]), + grz = fp2.grz(this.poly.fP[indx + 2]); - /** @summary Process mouse event */ - mouseEvent(kind, evnt) { - const layer = this.getLayerSvg('primitives_layer'), - interactive = layer.select('.interactive_ellipse'); - if (interactive.empty()) return; + return { + x1: grx - this.scale0, + x2: grx + this.scale0, + y1: gry - this.scale0, + y2: gry + this.scale0, + z1: grz - this.scale0, + z2: grz + this.scale0, + color: this.tip_color, + lines: [ + this.tip_name, + 'pnt: ' + indx / 3, + 'x: ' + fp2.axisAsText('x', this.poly.fP[indx]), + 'y: ' + fp2.axisAsText('y', this.poly.fP[indx + 1]), + 'z: ' + fp2.axisAsText('z', this.poly.fP[indx + 2]) + ] + }; + }; - let pnt = null; + fp.render3D(100); // set timeout to be able draw other points - if (kind !== 'leave') { - const pos = pointer(evnt, interactive.node()); - pnt = { x: pos[0], y: pos[1], touch: false }; + return this; + }); +} + +/** @summary Show TTree::Draw progress during processing + * @private */ +TDrawSelector.prototype.ShowProgress = function(value) { + let msg, ret; + if ((value === undefined) || !Number.isFinite(value)) + msg = ret = ''; + else if (this._break) { + msg = 'Breaking ... '; + ret = 'break'; + } else { + if (this.last_progress !== value) { + const diff = value - this.last_progress; + if (!this.aver_diff) + this.aver_diff = diff; + this.aver_diff = diff * 0.3 + this.aver_diff * 0.7; } - this.processFrameTooltipEvent(pnt); + this.last_progress = value; + + let ndig = 0; + if (this.aver_diff <= 0) + ndig = 0; + else if (this.aver_diff < 0.0001) + ndig = 3; + else if (this.aver_diff < 0.001) + ndig = 2; + else if (this.aver_diff < 0.01) + ndig = 1; + msg = `TTree draw ${(value * 100).toFixed(ndig)} % `; } - /** @summary Process mouse wheel event */ - mouseWheel(evnt) { - evnt.stopPropagation(); - evnt.preventDefault(); + showProgress(msg, 0, () => { this._break = 1; }); + return ret; +}; - this.processFrameTooltipEvent(null); // remove all tooltips +/** @summary Draw result of tree drawing + * @private */ +async function drawTreeDrawResult(dom, obj, opt) { + const typ = obj?._typename; - const polar = this.getObject(); - if (!polar) return; + if (!typ || !isStr(typ)) + return Promise.reject(Error('Object without type cannot be draw with TTree')); - let delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - if (!delta) return; + if (internals._alt_draw) { + const v = internals._alt_draw(dom, obj, opt); + if (v) + return v; + } - delta = (delta < 0) ? -0.2 : 0.2; + if (typ.indexOf(clTH1) === 0) + return TH1Painter.draw(dom, obj, opt); + if (typ.indexOf(clTH2) === 0) + return TH2Painter.draw(dom, obj, opt); + if (typ.indexOf(clTH3) === 0) + return TH3Painter.draw(dom, obj, opt); + if (typ.indexOf(clTGraph) === 0) + return TGraphPainter.draw(dom, obj, opt); + if ((typ === clTPolyMarker3D) && obj.$hist) { + return TH3Painter.draw(dom, obj.$hist, opt).then(() => { + const p2 = new ObjectPainter(dom, obj, opt); + p2.addToPadPrimitives(); + p2.redraw = drawPolyMarker3D$1; + return p2.redraw(); + }); + } - let rmin = this.scale_rmin, rmax = this.scale_rmax; - const range = rmax - rmin; + return Promise.reject(Error(`Object of type ${typ} cannot be draw with TTree`)); +} - // rmin -= delta*range; - rmax += delta*range; - if ((rminpolar.fRwrmax)) rmin = rmax = 0; +/** @summary Handle callback function with progress of tree draw + * @private */ +async function treeDrawProgress(obj, final) { + // no need to update drawing if previous is not yet completed + if (!final && !this.last_pr) + return; - if ((this.zoom_rmin !== rmin) || (this.zoom_rmax !== rmax)) { - this.zoom_rmin = rmin; - this.zoom_rmax = rmax; - this.redrawPad(); + if (this.dump || this.dump_entries || this.testio) { + if (!final) + return; + if (isBatchMode()) { + const painter = new BasePainter(this.drawid); + painter.selectDom().property('_json_object_', obj); + return painter; } + if (isFunc(internals.drawInspector)) + return internals.drawInspector(this.drawid, obj); + const str = create$1(clTObjString); + str.fString = toJSON(obj, 2); + return drawRawText(this.drawid, str); } - /** @summary Redraw polargram */ - redraw() { - if (!this.isMainPainter()) return; - - const polar = this.getObject(), - rect = this.getPadPainter().getFrameRect(); - - this.createG(); + // complex logic with intermediate update + // while TTree reading not synchronized with drawing, + // next portion can appear before previous is drawn + // critical is last drawing which should wait for previous one + // therefore last_pr is kept as indication that promise is not yet processed - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); - this.szx = rect.szx; - this.szy = rect.szy; + if (!this.last_pr) + this.last_pr = Promise.resolve(true); - this.scale_rmin = polar.fRwrmin; - this.scale_rmax = polar.fRwrmax; - if (this.zoom_rmin !== this.zoom_rmax) { - this.scale_rmin = this.zoom_rmin; - this.scale_rmax = this.zoom_rmax; + return this.last_pr.then(() => { + if (this.obj_painter) + this.last_pr = getPromise(this.obj_painter.redrawObject(obj)).then(() => this.obj_painter); + else if (!obj) { + if (final) + console.log('no result after tree drawing'); + this.last_pr = false; // return false indicating no drawing is done + } else { + this.last_pr = drawTreeDrawResult(this.drawid, obj, this.drawopt).then(p => { + this.obj_painter = p; + if (!final) + this.last_pr = null; + return p; // return painter for histogram + }); } - this.r = linear().domain([this.scale_rmin, this.scale_rmax]).range([0, this.szx]); - this.angle = polar.fAxisAngle || 0; + return final ? this.last_pr : null; + }); +} - const ticks = this.r.ticks(5); - let nminor = Math.floor((polar.fNdivRad % 10000) / 100); - this.createAttLine({ attr: polar }); - if (!this.gridatt) this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false }); +/** @summary Create painter to perform tree drawing on server side + * @private */ +function createTreePlayer(player) { + player.draw_first = true; - const range = Math.abs(polar.fRwrmax - polar.fRwrmin); - this.ndig = (range <= 0) ? -3 : Math.round(Math.log10(ticks.length / range)); + player.configureOnline = function(itemname, url, askey, root_version, expr) { + this.setItemName(itemname, '', this); + this.url = url; + this.root_version = root_version; + this.askey = askey; + this.draw_expr = expr; + }; - // verify that all radius labels are unique - let lbls = [], indx = 0; - while (indx= 0) { - if (++this.ndig>10) break; - lbls = []; indx = 0; continue; - } - lbls.push(lbl); - indx++; - } + player.configureTree = function(tree) { + this.local_tree = tree; + }; - let exclude_last = false; + player.showExtraButtons = function(args) { + const main = this.selectDom(), + numentries = this.local_tree?.fEntries || 0; - if ((ticks[ticks.length-1] < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) { - ticks.push(polar.fRwrmax); - exclude_last = true; - } + main.select('.treedraw_more').remove(); // remove more button first - this.startTextDrawing(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)); + main.select('.treedraw_buttons').node().innerHTML += + 'Cut: ' + + 'Opt: ' + + `Num: ` + + `First: ` + + ''; - for (let n = 0; n < ticks.length; ++n) { - let rx = this.r(ticks[n]), ry = rx/this.szx*this.szy; - this.draw_g.append('ellipse') - .attr('cx', 0) - .attr('cy', 0) - .attr('rx', Math.round(rx)) - .attr('ry', Math.round(ry)) - .style('fill', 'none') - .call(this.lineatt.func); + main.select('.treedraw_exe').on('click', () => this.performDraw()); + main.select('.treedraw_cut').property('value', args?.parse_cut || '').on('change', () => this.performDraw()); + main.select('.treedraw_opt').property('value', args?.drawopt || '').on('change', () => this.performDraw()); + main.select('.treedraw_number').attr('value', args?.numentries || ''); // .on('change', () => this.performDraw()); + main.select('.treedraw_first').attr('value', args?.firstentry || ''); // .on('change', () => this.performDraw()); + main.select('.treedraw_clear').on('click', () => cleanup(this.drawid)); + }; - if ((n < ticks.length-1) || !exclude_last) { - this.drawText({ align: 23, x: Math.round(rx), y: Math.round(polar.fRadialTextSize * this.szy * 0.5), - text: this.format(ticks[n]), color: this.getColor(polar.fRadialLabelColor), latex: 0 }); - } + player.showPlayer = function(args) { + const main = this.selectDom(); - if ((nminor>1) && ((n < ticks.length-1) || !exclude_last)) { - const dr = (ticks[1] - ticks[0]) / nminor; - for (let nn = 1; nn < nminor; ++nn) { - const gridr = ticks[n] + dr*nn; - if (gridr > this.scale_rmax) break; - rx = this.r(gridr); ry = rx/this.szx*this.szy; - this.draw_g.append('ellipse') - .attr('cx', 0) - .attr('cy', 0) - .attr('rx', Math.round(rx)) - .attr('ry', Math.round(ry)) - .style('fill', 'none') - .call(this.gridatt.func); - } - } - } + this.drawid = 'jsroot_tree_player_' + internals.id_counter++ + '_draw'; - let nmajor = polar.fNdivPol % 100; - if ((nmajor !== 8) && (nmajor !== 3)) nmajor = 8; + const show_extra = args?.parse_cut || args?.numentries || args?.firstentry; - return this.finishTextDrawing().then(() => { - const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2); - this.startTextDrawing(polar.fPolarLabelFont, fontsize); + main.html('
    ' + + '
    ' + + '' + + 'Expr:' + + '' + + '' + + '
    ' + + '

    ' + + `
    ` + + '
    '); - lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}']; - const aligns = [12, 11, 21, 31, 32, 33, 23, 13]; + // only when main html element created, one can set painter + // ObjectPainter allow such usage of methods from BasePainter + this.setTopPainter(); - for (let n = 0; n < nmajor; ++n) { - const angle = -n*2*Math.PI/nmajor - this.angle; - this.draw_g.append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`) - .call(this.lineatt.func); + if (this.local_tree) { + main.select('.treedraw_buttons') + .attr('title', 'Tree draw player for: ' + this.local_tree.fName); + } + main.select('.treedraw_exe').on('click', () => this.performDraw()); + main.select('.treedraw_varexp') + .attr('value', args?.parse_expr || this.draw_expr || 'px:py') + .on('change', () => this.performDraw()); + main.select('.treedraw_varexp_info') + .attr('title', 'Example of valid draw expressions:\n' + + ' px - 1-dim draw\n' + + ' px:py - 2-dim draw\n' + + ' px:py:pz - 3-dim draw\n' + + ' px+py:px-py - use any expressions\n' + + ' px:py>>Graph - create and draw TGraph\n' + + ' px:py>>dump - dump extracted variables\n' + + ' px:py>>h(50,-5,5,50,-5,5) - custom histogram\n' + + ' px:py;hbins:100 - custom number of bins'); - const aindx = Math.round(16 -angle/Math.PI*4) % 8; // index in align table, here absolute angle is important + if (show_extra) + this.showExtraButtons(args); + else + main.select('.treedraw_more').on('click', () => this.showExtraButtons(args)); - this.drawText({ align: aligns[aindx], - x: Math.round((this.szx+fontsize)*Math.cos(angle)), - y: Math.round((this.szy + fontsize/this.szx*this.szy)*(Math.sin(angle))), - text: lbls[n], - color: this.getColor(polar.fPolarLabelColor), latex: 1 }); - } + this.checkResize(); - return this.finishTextDrawing(); - }).then(() => { - nminor = Math.floor((polar.fNdivPol % 10000) / 100); + registerForResize(this); + }; - if (nminor > 1) { - for (let n = 0; n < nmajor*nminor; ++n) { - if (n % nminor === 0) continue; - const angle = -n*2*Math.PI/nmajor/nminor - this.angle; - this.draw_g.append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`) - .call(this.gridatt.func); - } - } + player.getValue = function(sel) { + const elem = this.selectDom().select(sel); + if (elem.empty()) + return; + return elem.property('value') ?? elem.attr('value'); + }; - if (this.isBatchMode()) return; + player.performLocalDraw = function() { + if (!this.local_tree) + return; - TooltipHandler.assign(this); + const frame = this.selectDom(), + args = { expr: this.getValue('.treedraw_varexp') }; - const layer = this.getLayerSvg('primitives_layer'); - let interactive = layer.select('.interactive_ellipse'); + if (frame.select('.treedraw_more').empty()) { + args.cut = this.getValue('.treedraw_cut'); + if (!args.cut) + delete args.cut; - if (interactive.empty()) { - interactive = layer.append('g') - .classed('most_upper_primitives', true) - .append('ellipse') - .classed('interactive_ellipse', true) - .attr('cx', 0) - .attr('cy', 0) - .style('fill', 'none') - .style('pointer-events', 'visibleFill') - .on('mouseenter', evnt => this.mouseEvent('enter', evnt)) - .on('mousemove', evnt => this.mouseEvent('move', evnt)) - .on('mouseleave', evnt => this.mouseEvent('leave', evnt)); + args.drawopt = this.getValue('.treedraw_opt'); + if (args.drawopt === 'dump') { + args.dump = true; + args.drawopt = ''; } + if (!args.drawopt) + delete args.drawopt; - interactive.attr('rx', this.szx).attr('ry', this.szy); - - select(interactive.node().parentNode).attr('transform', this.draw_g.attr('transform')); - - if (settings.Zooming && settings.ZoomWheel) - interactive.on('wheel', evnt => this.mouseWheel(evnt)); - }); - } + args.numentries = parseInt(this.getValue('.treedraw_number')); + if (!Number.isInteger(args.numentries)) + delete args.numentries; - /** @summary Draw TGraphPolargram */ - static async draw(dom, polargram /* , opt */) { - const main = getElementMainPainter(dom); - if (main) { - if (main.getObject() === polargram) - return main; - throw Error('Cannot superimpose TGraphPolargram with any other drawings'); + args.firstentry = parseInt(this.getValue('.treedraw_first')); + if (!Number.isInteger(args.firstentry)) + delete args.firstentry; } - const painter = new TGraphPolargramPainter(dom, polargram); - return ensureTCanvas(painter, false).then(() => { - painter.setAsMainPainter(); - return painter.redraw(); - }).then(() => painter); - } + cleanup(this.drawid); -} // class TGraphPolargramPainter + args.drawid = this.drawid; + args.progress = treeDrawProgress.bind(args); -/** - * @summary Painter for TGraphPolar objects. - * - * @private - */ + treeDraw(this.local_tree, args).then(obj => args.progress(obj, true)); + }; -class TGraphPolarPainter extends ObjectPainter { + player.getDrawOpt = function() { + let res = 'player'; + const expr = this.getValue('.treedraw_varexp'); + if (expr) + res += ':' + expr; + return res; + }; - /** @summary Redraw TGraphPolar */ - redraw() { - this.drawGraphPolar(); - } + player.performDraw = function() { + if (this.local_tree) + return this.performLocalDraw(); - /** @summary Decode options for drawing TGraphPolar */ - decodeOptions(opt) { - const d = new DrawOptions(opt || 'L'); + const frame = this.selectDom(); + let url = this.url + '/exe.json.gz?compact=3&method=Draw', + expr = this.getValue('.treedraw_varexp'), + hname = 'h_tree_draw', option = ''; + const pos = expr.indexOf('>>'); - if (!this.options) this.options = {}; + if (pos < 0) + expr += `>>${hname}`; + else { + hname = expr.slice(pos + 2); + if (hname[0] === '+') + hname = hname.slice(1); + const pos2 = hname.indexOf('('); + if (pos2 > 0) + hname = hname.slice(0, pos2); + } - Object.assign(this.options, { - mark: d.check('P'), - err: d.check('E'), - fill: d.check('F'), - line: d.check('L'), - curve: d.check('C') - }); + if (frame.select('.treedraw_more').empty()) { + const cut = this.getValue('.treedraw_cut'); + let nentries = this.getValue('.treedraw_number'), + firstentry = this.getValue('.treedraw_first'); - this.storeDrawOpt(opt); - } + option = this.getValue('.treedraw_opt'); - /** @summary Drawing TGraphPolar */ - drawGraphPolar() { - const graph = this.getObject(), - main = this.getMainPainter(); + url += `&prototype="const char*,const char*,Option_t*,Long64_t,Long64_t"&varexp="${expr}"&selection="${cut}"`; - if (!graph || !main?.$polargram) return; + // provide all optional arguments - default value kMaxEntries not works properly in ROOT6 + if (!nentries) + nentries = 'TTree::kMaxEntries'; // kMaxEntries available since ROOT 6.05/03 + if (!firstentry) + firstentry = '0'; + url += `&option="${option}"&nentries=${nentries}&firstentry=${firstentry}`; + } else + url += `&prototype="Option_t*"&opt="${expr}"`; - if (this.options.mark) this.createAttMarker({ attr: graph }); - if (this.options.err || this.options.line || this.options.curve) this.createAttLine({ attr: graph }); - if (this.options.fill) this.createAttFill({ attr: graph }); + url += `&_ret_object_=${hname}`; - this.createG(); + const submitDrawRequest = () => { + httpRequest(url, 'object').then(res => { + cleanup(this.drawid); + drawTreeDrawResult(this.drawid, res, option); + }); + }; - this.draw_g.attr('transform', main.draw_g.attr('transform')); + this.draw_expr = expr; - let mpath = '', epath = ''; - const bins = []; + if (this.askey) { + // first let read tree from the file + this.askey = false; + httpRequest(this.url + '/root.json.gz?compact=3', 'text').then(submitDrawRequest); + } else + submitDrawRequest(); + }; - for (let n = 0; n < graph.fNpoints; ++n) { - if (graph.fY[n] > main.scale_rmax) continue; + player.checkResize = function(/* arg */) { + resize(this.drawid); + }; - if (this.options.err) { - let pos1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]), - pos2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]); - epath += `M${pos1.grx},${pos1.gry}L${pos2.grx},${pos2.gry}`; + return player; +} - pos1 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]); - pos2 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]); - epath += `M${pos1.grx},${pos1.gry}A${pos2.rx},${pos2.ry},0,0,1,${pos2.grx},${pos2.gry}`; - } +/** @summary function used with THttpServer to assign player for the TTree object + * @private */ +function drawTreePlayer(hpainter, itemname, askey, asleaf) { + let item = hpainter.findItem(itemname), + expr = '', leaf_cnt = 0; + const top = hpainter.getTopOnlineItem(item); + if (!item || !top) + return null; - const pos = main.translate(graph.fX[n], graph.fY[n]); + if (asleaf) { + expr = item._name; + while (item && !item._ttree) + item = item._parent; + if (!item) + return null; + itemname = hpainter.itemFullName(item); + } - if (this.options.mark) - mpath += this.markeratt.create(pos.grx, pos.gry); + const url = hpainter.getOnlineItemUrl(itemname); + if (!url) + return null; - if (this.options.curve || this.options.line || this.options.fill) - bins.push(pos); - } + const root_version = top._root_version || 400129, // by default use version number 6-27-01 + mdi = hpainter.getDisplay(); + if (!mdi) + return null; - if ((this.options.fill || this.options.line) && bins.length) { - const lpath = buildSvgCurve(bins, { line: true }); - if (this.options.fill) { - this.draw_g.append('svg:path') - .attr('d', lpath + 'Z') - .call(this.fillatt.func); - } + const frame = mdi.findFrame(itemname, true); + if (!frame) + return null; - if (this.options.line) { - this.draw_g.append('svg:path') - .attr('d', lpath) - .style('fill', 'none') - .call(this.lineatt.func); + const divid = select(frame).attr('id'), + player = new BasePainter(divid); + + if (item._childs && !asleaf) { + for (let n = 0; n < item._childs.length; ++n) { + const leaf = item._childs[n]; + if (isStr(leaf?._kind) && (leaf._kind.indexOf(getKindForType('TLeaf')) === 0) && (leaf_cnt < 2)) { + if (leaf_cnt++ > 0) + expr += ':'; + expr += leaf._name; } } + } - if (this.options.curve && bins.length) { - this.draw_g.append('svg:path') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .call(this.lineatt.func); - } + createTreePlayer(player); + player.configureOnline(itemname, url, askey, root_version, expr); + player.showPlayer(); - if (epath) { - this.draw_g.append('svg:path') - .attr('d', epath) - .style('fill', 'none') - .call(this.lineatt.func); - } + return player; +} - if (mpath) { - this.draw_g.append('svg:path') - .attr('d', mpath) - .call(this.markeratt.func); - } - } +/** @summary function used with THttpServer when tree is not yet loaded + * @private */ +function drawTreePlayerKey(hpainter, itemname) { + return drawTreePlayer(hpainter, itemname, true); +} - /** @summary Create polargram object */ - createPolargram() { - const polargram = create$1('TGraphPolargram'), - gr = this.getObject(); +/** @summary function used with THttpServer when tree is not yet loaded + * @private */ +function drawLeafPlayer(hpainter, itemname) { + return drawTreePlayer(hpainter, itemname, false, true); +} - let rmin = gr.fY[0] || 0, rmax = rmin; - for (let n = 0; n < gr.fNpoints; ++n) { - rmin = Math.min(rmin, gr.fY[n] - gr.fEY[n]); - rmax = Math.max(rmax, gr.fY[n] + gr.fEY[n]); +/** @summary function called from draw() + * @desc just envelope for real TTree::Draw method which do the main job + * Can be also used for the branch and leaf object + * @private */ +async function drawTree(dom, obj, opt) { + let tree = obj, args = opt; + + if (obj._typename === clTBranchFunc) { + // fictional object, created only in browser + args = { expr: `.${obj.func}()`, branch: obj.branch }; + if (opt && opt.indexOf('dump') === 0) + args.expr += '>>' + opt; + else if (opt) + args.expr += opt; + tree = obj.branch.$tree; + } else if (obj.$branch) { + // this is drawing of the single leaf from the branch + args = { expr: `.${obj.fName}${opt || ''}`, branch: obj.$branch }; + if ((args.branch.fType === kClonesNode) || (args.branch.fType === kSTLNode)) { + // special case of size + args.expr = opt; + args.direct_branch = true; } - polargram.fRwrmin = rmin - (rmax-rmin)*0.1; - polargram.fRwrmax = rmax + (rmax-rmin)*0.1; + tree = obj.$branch.$tree; + } else if (obj.$tree) { + // this is drawing of the branch - return polargram; - } + // if generic object tried to be drawn without specifying any options, it will be just dump + if (!opt && obj.fStreamerType && (obj.fStreamerType !== kTString) && + (obj.fStreamerType >= kObject) && (obj.fStreamerType <= kAnyP)) + opt = 'dump'; - /** @summary Provide tooltip at specified point */ - extractTooltip(pnt) { - if (!pnt) return null; + args = { expr: opt, branch: obj }; + tree = obj.$tree; + } else { + if (!args) + args = 'player'; + if (isStr(args)) + args = { expr: args }; + } - const graph = this.getObject(), - main = this.getMainPainter(); - let best_dist2 = 1e10, bestindx = -1, bestpos = null; + if (!tree) + throw Error('No TTree object available for TTree::Draw'); - for (let n = 0; n < graph.fNpoints; ++n) { - const pos = main.translate(graph.fX[n], graph.fY[n]), - dist2 = (pos.x-pnt.x)**2 + (pos.y-pnt.y)**2; - if (dist2 < best_dist2) { best_dist2 = dist2; bestindx = n; bestpos = pos; } + if (isStr(args.expr)) { + const p = args.expr.indexOf('player'); + if (p === 0) { + args.player = true; + args.expr = args.expr.slice(6); + if (args.expr[0] === ':') + args.expr = args.expr.slice(1); + } else if ((p >= 0) && (p === args.expr.length - 6)) { + args.player = true; + args.expr = args.expr.slice(0, p); + if ((p > 0) && (args.expr[p - 1] === ';')) + args.expr = args.expr.slice(0, p - 1); } + } - let match_distance = 5; - if (this.markeratt?.used) match_distance = this.markeratt.getFullSize(); - - if (Math.sqrt(best_dist2) > match_distance) return null; - - const res = { - name: this.getObject().fName, title: this.getObject().fTitle, - x: bestpos.x, y: bestpos.y, - color1: this.markeratt?.used ? this.markeratt.color : this.lineatt.color, - exact: Math.sqrt(best_dist2) < 4, - lines: [this.getObjectHint()], - binindx: bestindx, - menu_dist: match_distance, - radius: match_distance - }; - - res.lines.push(`r = ${main.axisAsText('r', graph.fY[bestindx])}`, - `phi = ${main.axisAsText('phi', graph.fX[bestindx])}`); + let painter; - if (graph.fEY && graph.fEY[bestindx]) - res.lines.push(`error r = ${main.axisAsText('r', graph.fEY[bestindx])}`); + if (args.player) { + painter = new ObjectPainter(dom, obj, opt); + createTreePlayer(painter); + painter.configureTree(tree); + painter.showPlayer(args); + args.drawid = painter.drawid; + } else + args.drawid = dom; - if (graph.fEX && graph.fEX[bestindx]) - res.lines.push(`error phi = ${main.axisAsText('phi', graph.fEX[bestindx])}`); - return res; - } + // use in result handling same function as for progress handling - /** @summary Show tooltip */ - showTooltip(hint) { - let ttcircle = this.draw_g?.selectChild('.tooltip_bin'); + args.progress = treeDrawProgress.bind(args); - if (!hint || !this.draw_g) { - ttcircle?.remove(); - return; - } + let pr; + if (args.expr === 'testio') { + args.testio = true; + args.showProgress = msg => showProgress(msg, -1, () => { args._break = 1; }); + pr = treeIOTest(tree, args); + } else if (args.expr || args.branch) + pr = treeDraw(tree, args); + else + return painter; - if (ttcircle.empty()) { - ttcircle = this.draw_g.append('svg:ellipse') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none'); - } + return pr.then(res => args.progress(res, true)); +} - hint.changed = ttcircle.property('current_bin') !== hint.binindx; +var TTree = /*#__PURE__*/Object.freeze({ +__proto__: null, +drawLeafPlayer: drawLeafPlayer, +drawTree: drawTree, +drawTreePlayer: drawTreePlayer, +drawTreePlayerKey: drawTreePlayerKey, +treeDrawProgress: treeDrawProgress +}); - if (hint.changed) { - ttcircle.attr('cx', hint.x) - .attr('cy', hint.y) - .attr('rx', Math.round(hint.radius)) - .attr('ry', Math.round(hint.radius)) - .style('fill', 'none') - .style('stroke', hint.color1) - .property('current_bin', hint.binindx); - } - } +const kTextNDC = BIT(14); - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const hint = this.extractTooltip(pnt); - if (!pnt || !pnt.disabled) this.showTooltip(hint); - return hint; - } +class TTextPainter extends ObjectPainter { - /** @summary Draw TGraphPolar */ - static async draw(dom, graph, opt) { - const painter = new TGraphPolarPainter(dom, graph); - painter.decodeOptions(opt); + async _redrawText(x, y, annot) { + const text = this.getObject(), + pp = this.getPadPainter(), + fp = this.getFramePainter(), + is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://'); - const main = painter.getMainPainter(); - if (main && !main.$polargram) { - console.error('Cannot superimpose TGraphPolar with plain histograms'); - return null; - } + // special handling of dummy frame painter + if (fp?.getDrawDom() === null) + return this; - let pr = Promise.resolve(null); - if (!main) { - if (!graph.fPolargram) - graph.fPolargram = painter.createPolargram(); - pr = TGraphPolargramPainter.draw(dom, graph.fPolargram); + let fact = 1, use_frame = false; + + this.createAttText({ attr: text }); + + if ((annot === '3d') || text.TestBit(kTextNDC)) + this.isndc = true; + else if (!annot && pp?.getRootPad(true)) { + // force pad coordinates + const d = new DrawOptions(this.getDrawOpt()); + use_frame = d.check('FRAME'); + } else if (!annot) { + // place in the middle + this.isndc = true; + x = y = 0.5; + text.fTextAlign = 22; } - return pr.then(() => { - painter.addToPadPrimitives(); - painter.drawGraphPolar(); - return painter; - }); - } + const g = this.createG(use_frame ? 'frame2d' : undefined, is_url); -} // class TGraphPolarPainter + g.attr('transform', null); // remove transform from interactive changes -var TGraphPolarPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TGraphPolarPainter: TGraphPolarPainter, -TGraphPolargramPainter: TGraphPolargramPainter -}); + x = this.axisToSvg('x', x, this.isndc); + y = this.axisToSvg('y', y, this.isndc); + this.swap_xy = use_frame && fp?.swap_xy(); -/** @summary Create log scale for axis bins - * @private */ -function produceTAxisLogScale(axis, num, min, max) { - let lmin, lmax; + if (this.swap_xy) + [x, y] = [y, x]; - if (max > 0) { - lmax = Math.log(max); - lmin = min > 0 ? Math.log(min) : lmax - 5; - } else { - lmax = -10; - lmin = -15; - } + const arg = this.textatt.createArg({ x, y, text: text.fTitle, latex: 0 }); - axis.fNbins = num; - axis.fXbins = new Array(num + 1); - for (let i = 0; i <= num; ++i) - axis.fXbins[i] = Math.exp(lmin + i / num * (lmax - lmin)); - axis.fXmin = Math.exp(lmin); - axis.fXmax = Math.exp(lmax); -} + if (this.matchObjectType(clTLatex) || annot) + arg.latex = 1; + else if (this.matchObjectType(clTMathText)) { + arg.latex = 2; + fact = 0.8; + } -function scanTF1Options(opt) { - if (!isStr(opt)) opt = ''; - let p = opt.indexOf(';webcanv_hist'), webcanv_hist = false, use_saved = 0; - if (p >= 0) { - webcanv_hist = true; - opt = opt.slice(0, p); - } - p = opt.indexOf(';force_saved'); - if (p >= 0) { - use_saved = 2; - opt = opt.slice(0, p); - } - p = opt.indexOf(';prefer_saved'); - if (p >= 0) { - use_saved = 1; - opt = opt.slice(0, p); - } - return { opt, webcanv_hist, use_saved }; -} + if (is_url) { + g.attr('href', text.fName); + if (!this.isBatchMode()) + g.append('svg:title').text(`link on ${text.fName}`); + } + return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp, fact)) + .then(() => this.drawText(arg)) + .then(() => this.finishTextDrawing()) + .then(() => { + if (this.isBatchMode()) + return this; -/** - * @summary Painter for TF1 object - * - * @private - */ + if (pp.isButton() && !pp.isEditable()) { + g.on('click', () => this.getCanvPainter().selectActivePad(pp)); + return this; + } -class TF1Painter extends TH1Painter$2 { + Object.assign(this, { pos_x: x, pos_y: y, pos_dx: 0, pos_dy: 0 }); - /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + if (annot !== '3d') + addMoveHandler(this, true, is_url); - /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF1; } + assignContextMenu(this); - /** @summary Returns true while function is drawn */ - isTF1() { return true; } + if (this.matchObjectType(clTLink)) + g.style('cursor', 'pointer').on('click', () => this.submitCanvExec('ExecuteEvent(kButton1Up, 0, 0);;')); - /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } + return this; + }); + } - /** @summary Update function */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; - delete obj.evalPar; - const histo = this.getHisto(); + async redraw() { + return this._redrawText(this.getObject().fX, this.getObject().fY); + } - if (this.webcanv_hist) { - const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH1D); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); - } + moveDrag(dx, dy) { + this.pos_dx += dx; + this.pos_dy += dy; + makeTranslate(this.getG(), this.pos_dx, this.pos_dy); + } - this.$func = obj; - this.createTF1Histogram(obj, histo); - this.scanContent(); - return true; + moveEnd(not_changed) { + if (not_changed) + return; + const txt = this.getObject(); + let fx = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc), + fy = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc); + if (this.swap_xy) + [fx, fy] = [fy, fx]; + + txt.fX = fx; + txt.fY = fy; + this.submitCanvExec(`SetX(${fx});;SetY(${fy});;`); } - /** @summary Redraw TF1 - * @private */ - redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'zoom')) { - this.createTF1Histogram(this.$func, this.getHisto()); - this.scanContent(); - } + fillContextMenuItems(menu) { + const text = this.getObject(); + menu.add('Change text', () => menu.input('Enter new text', text.fTitle).then(t => { + text.fTitle = t; + this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); + })); + } - return super.redraw(reason); + /** @summary draw TText-derived object */ + static async draw(dom, obj, opt) { + const painter = new TTextPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); } - /** @summary Create histogram for TF1 drawing - * @private */ - createTF1Histogram(tf1, hist) { - const fp = this.getFramePainter(), - pad = this.getPadPainter()?.getRootPad(true), - logx = pad?.fLogx, - gr = fp?.getGrFuncs(this.second_x, this.second_y); - let xmin = tf1.fXmin, xmax = tf1.fXmax, np = Math.max(tf1.fNpx, 100); +} // class TTextPainter - if (gr?.zoom_xmin !== gr?.zoom_xmax) { - const dx = (xmax - xmin) / np; - if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) - xmin = Math.max(xmin, gr.zoom_xmin - dx); - if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) - xmax = Math.min(xmax, gr.zoom_xmax + dx); - } +var TTextPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TTextPainter: TTextPainter +}); - this._use_saved_points = (tf1.fSave.length > 3) && (settings.PreferSavedPoints || (this.use_saved > 1)); +function getRotation(camera, mesh) { + const dx = camera.position.x - mesh.position.x, + dy = camera.position.y - mesh.position.y; + return Math.atan2(dy, dx) + Math.PI / 2; +} - const ensureBins = num => { - if (hist.fNcells !== num + 2) { - hist.fNcells = num + 2; - hist.fArray = new Float32Array(hist.fNcells); - } - hist.fArray.fill(0); - hist.fXaxis.fNbins = num; - hist.fXaxis.fXbins = []; - }; +class TAnnotation3DPainter extends TTextPainter { - delete this._fail_eval; + /** @summary Redraw annotation + * @desc handle 3d and 2d mode */ - // this._use_saved_points = true; + async redraw() { + const fp = this.getFramePainter(), + text = this.getObject(); - if (!this._use_saved_points) { - let iserror = false; + if (fp?.mode3d && !this.use_2d) { + const mesh = build3dlatex(text, '', this, fp); + mesh.traverse(o => o.geometry?.rotateX(Math.PI / 2)); + mesh.position.set(fp.grx(text.fX), fp.gry(text.fY), fp.grz(text.fZ)); + mesh.rotation.set(0, 0, getRotation(fp.camera, mesh)); + fp.processRender3D = true; + fp.add3DMesh(mesh, this, true); + fp.render3D(100); + return this; + } - if (!tf1.evalPar) { - try { - if (!proivdeEvalPar(tf1)) - iserror = true; - } catch { - iserror = true; - } - } + const mode = fp?.mode3d && isFunc(fp?.convert3DtoPadNDC) ? '3d' : '2d'; + let x = text.fX, y = text.fY; - ensureBins(np); + if (mode === '3d') { + const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ); + x = pos.x; + y = pos.y; + } - if (logx) - produceTAxisLogScale(hist.fXaxis, np, xmin, xmax); - else { - hist.fXaxis.fXmin = xmin; - hist.fXaxis.fXmax = xmax; - } + return this._redrawText(x, y, mode).then(() => { + fp.processRender3D = mode === '3d'; + return this; + }); + } - for (let n = 0; (n < np) && !iserror; n++) { - const x = hist.fXaxis.GetBinCenter(n + 1); - let y = 0; - try { - y = tf1.evalPar(x); - } catch (err) { - iserror = true; - } + /** @summary Extra handling during 3d rendering + * @desc Allows to reposition annotation when rotate/zoom drawing */ + handleRender3D() { + const text = this.getObject(), + fp = this.getFramePainter(); + if (this.use_2d) { + const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ), + new_x = this.axisToSvg('x', pos.x, true), + new_y = this.axisToSvg('y', pos.y, true); + makeTranslate(this.getG(), new_x - this.pos_x, new_y - this.pos_y); + } else + fp.get3DMeshes(this).forEach(mesh => mesh.rotation.set(0, 0, getRotation(fp.camera, mesh))); + } - if (!iserror) - hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); - } + /** @summary draw TAnnotation3D object */ + static async draw(dom, obj, opt) { + const painter = new TAnnotation3DPainter(dom, obj, opt); + painter.use_2d = (opt === '2d') || (opt === '2D'); + return ensureTCanvas(painter, painter.use_2d ? true : '3d').then(() => painter.redraw()); + } - if (iserror) - this._fail_eval = true; +} // class TAnnotation3DPainter - if (iserror && (tf1.fSave.length > 3)) - this._use_saved_points = true; - } +var TAnnotation3DPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TAnnotation3DPainter: TAnnotation3DPainter +}); - // in the case there were points have saved and we cannot calculate function - // if we don't have the user's function - if (this._use_saved_points) { - np = tf1.fSave.length - 3; - let custom_xaxis = null; - xmin = tf1.fSave[np + 1]; - xmax = tf1.fSave[np + 2]; +const kIsZoomed = BIT(16); // bit set when zooming on Y axis - if (xmin === xmax) { - // xmin = tf1.fSave[np]; - const mp = this.getMainPainter(); - if (isFunc(mp?.getHisto)) - custom_xaxis = mp?.getHisto()?.fXaxis; - } +/** + * @summary Painter class for THStack + * + * @private + */ - if (custom_xaxis) { - ensureBins(hist.fXaxis.fNbins); - Object.assign(hist.fXaxis, custom_xaxis); - // TODO: find first bin +let THStackPainter$2 = class THStackPainter extends ObjectPainter { - for (let n = 0; n < np; ++n) { - const y = tf1.fSave[n]; - hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); - } - } else { - ensureBins(tf1.fNpx); - hist.fXaxis.fXmin = tf1.fXmin; - hist.fXaxis.fXmax = tf1.fXmax; + #firstpainter; // first painter on stack + #painters; // array of sub-painters + #stack; // internal stack of histograms + #did_update; // flag used in update - for (let n = 0; n < tf1.fNpx; ++n) { - const y = _getTF1Save(tf1, hist.fXaxis.GetBinCenter(n + 1)); - hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); - } - } - } + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} stack - THStack object + * @param {string} [opt] - draw options */ + constructor(dom, stack, opt) { + super(dom, stack, opt); + this.#firstpainter = null; + this.#painters = []; // keep painters to be able update objects + } - hist.fName = 'Func'; - setHistogramTitle(hist, tf1.fTitle); - hist.fMinimum = tf1.fMinimum; - hist.fMaximum = tf1.fMaximum; - hist.fLineColor = tf1.fLineColor; - hist.fLineStyle = tf1.fLineStyle; - hist.fLineWidth = tf1.fLineWidth; - hist.fFillColor = tf1.fFillColor; - hist.fFillStyle = tf1.fFillStyle; - hist.fMarkerColor = tf1.fMarkerColor; - hist.fMarkerStyle = tf1.fMarkerStyle; - hist.fMarkerSize = tf1.fMarkerSize; - hist.fBits |= kNoStats; + /** @summary Cleanup THStack painter */ + cleanup() { + this.getPadPainter()?.cleanPrimitives(objp => { return (objp === this.#firstpainter) || (this.#painters.indexOf(objp) >= 0); }); + this.#firstpainter = null; + this.#painters = []; + this.#stack = undefined; + super.cleanup(); } - /** @summary Extract function ranges */ - extractAxesProperties(ndim) { - super.extractAxesProperties(ndim); + /** @summary Build sum of all histograms + * @desc Build a separate #stack containing the running sum of all histograms */ + buildStack(stack, pp) { + this.#stack = null; - const func = this.$func, nsave = func?.fSave.length ?? 0; + if (!stack.fHists) + return false; + const nhists = stack.fHists.arr.length; + if (nhists <= 0) + return false; - if (nsave > 3 && this._use_saved_points) { - this.xmin = Math.min(this.xmin, func.fSave[nsave - 2]); - this.xmax = Math.max(this.xmax, func.fSave[nsave - 1]); - } - if (func) { - this.xmin = Math.min(this.xmin, func.fXmin); - this.xmax = Math.max(this.xmax, func.fXmax); + let arr = pp?.findInPrimitives(undefined, clTObjArray); + if ((arr?.arr.length === nhists) && (arr?.name === stack.fName)) { + this.#stack = arr; + return true; } - } - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - const nsave = this.$func?.fSave.length ?? 0; - if ((nsave > 3) && this._use_saved_points && (axis === 'x')) { - // in the case where the points have been saved, useful for example - // if we don't have the user's function - const nb_points = nsave - 2, - xmin = this.$func.fSave[nsave - 2], - xmax = this.$func.fSave[nsave - 1]; + arr = create$1(clTObjArray); + let hprev = clone(stack.fHists.arr[0]); + arr.arr.push(hprev); + for (let i = 1; i < nhists; ++i) { + const hnext = clone(stack.fHists.arr[i]), + xnext = hnext.fXaxis, xprev = hprev.fXaxis; - return Math.abs(xmax - xmin) / nb_points < Math.abs(max - min); - } + let match = (xnext.fNbins === xprev.fNbins) && + (xnext.fXmin === xprev.fXmin) && + (xnext.fXmax === xprev.fXmax); - // if function calculated, one always could zoom inside - return (axis === 'x') || (axis === 'y'); - } + if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && + (Math.abs((xnext.fXmax - xnext.fXmin) / xnext.fNbins - (xprev.fXmax - xprev.fXmin) / xprev.fNbins) < 0.0001)) { + // simple extension of histogram to make sum + const arr2 = new Array(hprev.fNcells).fill(0); + for (let n = 1; n <= xnext.fNbins; ++n) + arr2[n] = hnext.fArray[n]; + hnext.fNcells = hprev.fNcells; + Object.assign(xnext, xprev); + hnext.fArray = arr2; + match = true; + } + if (!match) { + console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); + return false; + } - /** @summary retrurn tooltips for TF2 */ - getTF1Tooltips(pnt) { - delete this.$tmp_tooltip; - const lines = [this.getObjectHint()], - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); + // trivial sum of histograms + for (let n = 0; n < hnext.fArray.length; ++n) + hnext.fArray[n] += hprev.fArray[n]; - if (!funcs || !isFunc(this.$func?.evalPar)) { - lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); - return lines; + arr.arr.push(hnext); + hprev = hnext; } + this.#stack = arr; + return true; + } - const x = funcs.revertAxis('x', pnt.x); - let y = 0, gry = 0, iserror = false; - - try { - y = this.$func.evalPar(x); - gry = Math.round(funcs.gry(y)); - } catch { - iserror = true; - } + /** @summary Returns stack min/max values */ + getMinMax(iserr) { + const stack = this.getObject(), + o = this.getOptions(), + pad = this.getPadPainter()?.getRootPad(true), + logscale = pad?.fLogv ?? (o.ndim === 1 ? pad?.fLogy : pad?.fLogz); + let themin = 0, themax = 0; - lines.push('x = ' + funcs.axisAsText('x', x), - 'value = ' + (iserror ? '' : floatToString(y, gStyle.fStatFormat))); + const getHistMinMax = (hist, witherr) => { + const res = { min: 0, max: 0 }; + let domin = true, domax = true; + if (hist.fMinimum !== kNoZoom) { + res.min = hist.fMinimum; + domin = false; + } + if (hist.fMaximum !== kNoZoom) { + res.max = hist.fMaximum; + domax = false; + } - if (!iserror) - this.$tmp_tooltip = { y, gry }; - return lines; - } + if (!domin && !domax) + return res; - /** @summary process tooltip event for TF1 object */ - processTooltipEvent(pnt) { - if (this._use_saved_points) - return super.processTooltipEvent(pnt); + let i1 = 1, i2 = hist.fXaxis.fNbins, j1 = 1, j2 = 1, first = true; - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + if (hist.fXaxis.TestBit(EAxisBits.kAxisRange)) { + i1 = hist.fXaxis.fFirst; + i2 = hist.fXaxis.fLast; + } - if (!this.draw_g || !pnt) { - ttrect?.remove(); - return null; - } + if (hist._typename.indexOf(clTH2) === 0) { + j2 = hist.fYaxis.fNbins; + if (hist.fYaxis.TestBit(EAxisBits.kAxisRange)) { + j1 = hist.fYaxis.fFirst; + j2 = hist.fYaxis.fLast; + } + } + let err = 0; + for (let j = j1; j <= j2; ++j) { + for (let i = i1; i <= i2; ++i) { + const val = hist.getBinContent(i, j); + if (witherr) + err = hist.getBinError(hist.getBin(i, j)); + if (logscale && (val - err <= 0)) + continue; + if (domin && (first || (val - err < res.min))) + res.min = val - err; + if (domax && (first || (val + err > res.max))) + res.max = val + err; + first = false; + } + } - const res = { name: this.$func?.fName, title: this.$func?.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getTF1Tooltips(pnt), exact: true, menu: true }; + return res; + }; - if (pnt.disabled) - ttrect.remove(); - else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .style('fill', 'none') - .attr('r', (this.lineatt?.width ?? 1) + 4); + if (o.nostack) { + for (let i = 0; i < stack.fHists.arr.length; ++i) { + const resh = getHistMinMax(stack.fHists.arr[i], iserr); + if (i === 0) { + themin = resh.min; + themax = resh.max; + } else { + themin = Math.min(themin, resh.min); + themax = Math.max(themax, resh.max); + } } - - ttrect.attr('cx', pnt.x) - .attr('cy', this.$tmp_tooltip.gry ?? pnt.y) - .call(this.lineatt?.func); + } else { + themin = getHistMinMax(this.#stack.arr.at(0), iserr).min; + themax = getHistMinMax(this.#stack.arr.at(-1), iserr).max; } - return res; - } + if (logscale) + themin = (themin > 0) ? themin * 0.9 : themax * 1e-3; + else if (themin > 0) + themin = 0; - /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed - * @private */ - fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; - } + if (stack.fMaximum !== kNoZoom) + themax = stack.fMaximum; - /** @summary draw TF1 object */ - static async draw(dom, tf1, opt) { - const web = scanTF1Options(opt); - opt = web.opt; - delete web.opt; - let hist; + if (stack.fMinimum !== kNoZoom) + themin = stack.fMinimum; - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH1D); - } + // redo code from THStack::BuildAndPaint - if (!hist) { - hist = createHistogram(clTH1D, 100); - hist.fBits |= kNoStats; + if (!o.nostack || (stack.fMaximum === kNoZoom)) { + if (logscale) { + if (themin > 0) + themax *= (1 + 0.2 * Math.log10(themax / themin)); + } else if (stack.fMaximum === kNoZoom) + themax *= (1 + gStyle.fHistTopMargin); + } + if (!o.nostack || (stack.fMinimum === kNoZoom)) { + if (logscale) + themin = (themin > 0) ? themin / (1 + 0.5 * Math.log10(themax / themin)) : 1e-3 * themax; } - if (!opt && getElementMainPainter(dom)) - opt = 'same'; + const res = { min: themin, max: themax, hopt: `;hmin:${themin};hmax:${themax}` }; + if (stack.fHistogram?.TestBit(kIsZoomed)) + res.hopt += ';zoom_min_max'; - const painter = new TF1Painter(dom, hist); + return res; + } - painter.$func = tf1; - Object.assign(painter, web); + /** @summary Provide draw options for the histogram */ + getHistDrawOption(hist, opt) { + const o = this.getOptions(); + let hopt = opt || hist.fOption || o.hopt; + if (hopt.toUpperCase().indexOf(o.hopt) < 0) + hopt += ' ' + o.hopt; + if (o.draw_errors && !hopt) + hopt = 'E'; + if (o.zscale) { + const p = hopt.toUpperCase().indexOf('COLZ'); + if (p >= 0) + hopt = hopt.slice(0, p + 3) + hopt.slice(p + 4); + } + if (!o.pads) + hopt += ' same nostat' + o.auto; + return hopt; + } - painter.createTF1Histogram(tf1, hist); + /** @summary Draw next stack histogram */ + async drawNextHisto(indx, pad_painter) { + const stack = this.getObject(), + o = this.getOptions(), + hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length || 0; - return THistPainter._drawHist(painter, opt); - } + if (indx >= nhists) + return this; -} // class TF1Painter + const rindx = o.horder ? indx : nhists - indx - 1, + subid = o.nostack ? `hists_${rindx}` : `stack_${rindx}`, + hist = hlst.arr[rindx], + hopt = this.getHistDrawOption(hist, stack.fHists.opt[rindx]); -var TF1Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TF1Painter: TF1Painter, -produceTAxisLogScale: produceTAxisLogScale, -scanTF1Options: scanTF1Options -}); + // handling of 'pads' draw option + if (pad_painter) { + const subpad_painter = pad_painter.getSubPadPainter(indx + 1); + if (!subpad_painter) + return this; -const kIsBayesian = BIT(14), // Bayesian statistics are used - kPosteriorMode = BIT(15), // Use posterior mean for best estimate (Bayesian statistics) - // kShortestInterval = BIT(16), // Use shortest interval, not implemented - too complicated - kUseBinPrior = BIT(17), // Use a different prior for each bin - kUseWeights = BIT(18), // Use weights - getBetaAlpha = (obj, bin) => (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].first : obj.fBeta_alpha, - getBetaBeta = (obj, bin) => (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].second : obj.fBeta_beta; + subpad_painter.cleanPrimitives(true); -/** - * @summary Painter for TEfficiency object - * - * @private - */ + return this.drawHist(subpad_painter, hist, hopt).then(subp => { + if (subp) { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + } + return this.drawNextHisto(indx + 1, pad_painter); + }); + } -class TEfficiencyPainter extends ObjectPainter { + // special handling of stacked histograms + // also used to provide tooltips + if ((rindx > 0) && !o.nostack) + hist.$baseh = hlst.arr[rindx - 1]; + // this number used for auto colors creation + if (o.auto) + hist.$num_histos = nhists; - /** @summary Caluclate efficiency */ - getEfficiency(obj, bin) { - const BetaMean = (a, b) => (a <= 0 || b <= 0) ? 0 : a / (a + b), - BetaMode = (a, b) => { - if (a <= 0 || b <= 0) return 0; - if (a <= 1 || b <= 1) { - if (a < b) return 0; - if (a > b) return 1; - if (a === b) return 0.5; // cannot do otherwise - } - return (a - 1.0) / (a + b -2.0); - }, - total = obj.fTotalHistogram.fArray[bin], // should work for both 1-d and 2-d - passed = obj.fPassedHistogram.fArray[bin]; // should work for both 1-d and 2-d + const dom = this.#firstpainter?.getPadPainter() || this.getDrawDom(); - if (obj.TestBit(kIsBayesian)) { - // parameters for the beta prior distribution - const alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha, - beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; + return this.drawHist(dom, hist, hopt).then(subp => { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + return this.drawNextHisto(indx + 1, pad_painter); + }); + } - let aa, bb; - if (obj.TestBit(kUseWeights)) { - const tw = total, // fTotalHistogram->GetBinContent(bin); - tw2 = obj.fTotalHistogram.fSumw2 ? obj.fTotalHistogram.fSumw2[bin] : Math.abs(total), - pw = passed; // fPassedHistogram->GetBinContent(bin); + /** @summary Decode draw options of THStack painter */ + decodeOptions(opt) { + const o = this.setOptions({ ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }), + stack = this.getObject(), + hist = stack.fHistogram || stack.fHists?.arr[0] || this.#stack?.arr[0]; - if (tw2 <= 0) return pw/tw; + if (hist?._typename.indexOf(clTH2) === 0) + o.ndim = 2; - // tw/tw2 renormalize the weights - const norm = tw/tw2; - aa = pw * norm + alpha; - bb = (tw - pw) * norm + beta; - } else { - aa = passed + alpha; - bb = total - passed + beta; - } + if ((o.ndim === 2) && !opt) + opt = 'lego1'; - if (!obj.TestBit(kPosteriorMode)) - return BetaMean(aa, bb); - else - return BetaMode(aa, bb); + if (!o.nostack) { + stack.fHists?.arr.forEach(h => { + const len = h.fSumw2?.length ?? 0; + for (let n = 0; n < len; ++n) { + if (h.fSumw2[n] > 0) { + o.has_errors = true; + break; + } + } + }); } - return total ? passed/total : 0; - } + o.nhist = stack.fHists?.arr?.length ?? 1; - /** @summary Caluclate efficiency error low */ - getEfficiencyErrorLow(obj, bin, value) { - const total = obj.fTotalHistogram.fArray[bin], - passed = obj.fPassedHistogram.fArray[bin]; - let alpha = 0, beta = 0; - if (obj.TestBit(kIsBayesian)) { - alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; - beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; - } + const d = new DrawOptions(opt); - return value - this.fBoundary(total, passed, obj.fConfLevel, false, alpha, beta); - } + o.nostack = d.check('NOSTACK'); + if (d.check('STACK')) + o.nostack = false; + o.same = d.check('SAME'); - /** @summary Caluclate efficiency error low up */ - getEfficiencyErrorUp(obj, bin, value) { - const total = obj.fTotalHistogram.fArray[bin], - passed = obj.fPassedHistogram.fArray[bin]; - let alpha = 0, beta = 0; - if (obj.TestBit(kIsBayesian)) { - alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; - beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; - } + d.check('NOCLEAR'); // ignore option - return this.fBoundary(total, passed, obj.fConfLevel, true, alpha, beta) - value; - } + ['PFC', 'PLC', 'PMC'].forEach(f => { + if (d.check(f)) + o.auto += ' ' + f; + }); - /** @summary Copy drawning attributes */ - copyAttributes(obj, eff) { - ['fLineColor', 'fLineStyle', 'fLineWidth', 'fFillColor', 'fFillStyle', 'fMarkerColor', 'fMarkerStyle', 'fMarkerSize'].forEach(name => { obj[name] = eff[name]; }); - } + o.pads = d.check('PADS'); + if (o.pads) + o.nostack = true; - /** @summary Create graph for the drawing of 1-dim TEfficiency */ - createGraph(/* eff */) { - const gr = create$1(clTGraphAsymmErrors); - gr.fName = 'eff_graph'; - return gr; - } + o.hopt = d.remain().trim(); // use remaining draw options for histogram draw - /** @summary Create histogram for the drawing of 2-dim TEfficiency */ - createHisto(eff) { - const nbinsx = eff.fTotalHistogram.fXaxis.fNbins, - nbinsy = eff.fTotalHistogram.fYaxis.fNbins, - hist = createHistogram(clTH2F, nbinsx, nbinsy); - Object.assign(hist.fXaxis, eff.fTotalHistogram.fXaxis); - Object.assign(hist.fYaxis, eff.fTotalHistogram.fYaxis); - hist.fName = 'eff_histo'; - return hist; - } + const dolego = d.check('LEGO'); - /** @summary Fill graph with points from efficiency object */ - fillGraph(gr, opt) { - const eff = this.getObject(), - xaxis = eff.fTotalHistogram.fXaxis, - npoints = xaxis.fNbins, - plot0Bins = (opt.indexOf('e0') >= 0); + o.errors = d.check('E'); - for (let n = 0, j = 0; n < npoints; ++n) { - if (!plot0Bins && eff.fTotalHistogram.getBinContent(n+1) === 0) continue; + o.zscale = d.check('COLZ'); - const value = this.getEfficiency(eff, n+1); + // if any histogram appears with pre-calculated errors, use E for all histograms + if (!o.nostack && o.has_errors && !dolego && !d.check('HIST') && (o.hopt.indexOf('E') < 0)) + o.draw_errors = true; - gr.fX[j] = xaxis.GetBinCenter(n+1); - gr.fY[j] = value; - gr.fEXlow[j] = xaxis.GetBinCenter(n+1) - xaxis.GetBinLowEdge(n+1); - gr.fEXhigh[j] = xaxis.GetBinLowEdge(n+2) - xaxis.GetBinCenter(n+1); - gr.fEYlow[j] = this.getEfficiencyErrorLow(eff, n+1, value); - gr.fEYhigh[j] = this.getEfficiencyErrorUp(eff, n+1, value); + o.horder = o.nostack || dolego; + } - gr.fNpoints = ++j; + /** @summary Create main histogram for THStack axis drawing */ + createHistogram(stack) { + const o = this.getOptions(), + histos = stack.fHists, + numhistos = histos?.arr.length ?? 0; + + if (!numhistos) { + const histo = createHistogram(clTH1F, 100); + setHistogramTitle(histo, stack.fTitle); + histo.fBits |= kNoStats; + return histo; } - gr.fTitle = eff.fTitle; - this.copyAttributes(gr, eff); - } + const h0 = histos.arr[0], + histo = createHistogram((o.ndim === 1) ? clTH1F : clTH2F, h0.fXaxis.fNbins, h0.fYaxis.fNbins); + + histo.fName = 'axis_hist'; + histo.fBits |= kNoStats; + Object.assign(histo.fXaxis, h0.fXaxis); + if (o.ndim === 2) + Object.assign(histo.fYaxis, h0.fYaxis); - /** @summary Fill graph with points from efficiency object */ - fillHisto(hist) { - const eff = this.getObject(), - nbinsx = hist.fXaxis.fNbins, - nbinsy = hist.fYaxis.fNbins; + // this code is not exists in ROOT painter, can be skipped? + for (let n = 1; n < numhistos; ++n) { + const h = histos.arr[n]; + + if (!histo.fXaxis.fLabels) { + histo.fXaxis.fXmin = Math.min(histo.fXaxis.fXmin, h.fXaxis.fXmin); + histo.fXaxis.fXmax = Math.max(histo.fXaxis.fXmax, h.fXaxis.fXmax); + } - for (let i = 0; i < nbinsx+2; ++i) { - for (let j = 0; j < nbinsy+2; ++j) { - const bin = hist.getBin(i, j), - value = this.getEfficiency(eff, bin); - hist.fArray[bin] = value; + if ((o.ndim === 2) && !histo.fYaxis.fLabels) { + histo.fYaxis.fXmin = Math.min(histo.fYaxis.fXmin, h.fYaxis.fXmin); + histo.fYaxis.fXmax = Math.max(histo.fYaxis.fXmax, h.fYaxis.fXmax); } } - hist.fTitle = eff.fTitle; - hist.fBits = hist.fBits | kNoStats; - this.copyAttributes(hist, eff); + histo.fTitle = stack.fTitle; + + return histo; } - /** @summary Draw function */ - drawFunction(indx) { - const eff = this.getObject(); + /** @summary Update THStack object */ + updateObject(obj) { + if (!this.matchObjectType(obj)) + return false; - if (!eff?.fFunctions || (indx >= eff.fFunctions.arr.length)) - return this; + const stack = this.getObject(), + pp = this.getPadPainter(), + o = this.getOptions(); - return TF1Painter.draw(this.getDom(), eff.fFunctions.arr[indx], eff.fFunctions.opt[indx]).then(() => this.drawFunction(indx+1)); - } + stack.fHists = obj.fHists; + stack.fTitle = obj.fTitle; + stack.fMinimum = obj.fMinimum; + stack.fMaximum = obj.fMaximum; - /** @summary Draw TEfficiency object */ - static async draw(dom, eff, opt) { - if (!eff || !eff.fTotalHistogram) - return null; + if (!o.nostack) + o.nostack = !this.buildStack(stack, pp); - if (!opt || !isStr(opt)) opt = ''; - opt = opt.toLowerCase(); + if (this.#firstpainter) { + let src = obj.fHistogram; + if (!src) + src = stack.fHistogram = this.createHistogram(stack); - let ndim = 0; - if (eff.fTotalHistogram._typename.indexOf(clTH1) === 0) - ndim = 1; - else if (eff.fTotalHistogram._typename.indexOf(clTH2) === 0) - ndim = 2; - else - return null; + const mm = this.getMinMax(o.errors || o.draw_errors); + this.#firstpainter.options.hmin = mm.min; + this.#firstpainter.options.hmax = mm.max; - const painter = new TEfficiencyPainter(dom, eff); - painter.ndim = ndim; + this.#firstpainter._checked_zooming = false; // force to check 3d zooming - painter.fBoundary = getTEfficiencyBoundaryFunc(eff.fStatisticOption, eff.TestBit(kIsBayesian)); + if (o.ndim === 1) { + this.#firstpainter.ymin = mm.min; + this.#firstpainter.ymax = mm.max; + } else { + this.#firstpainter.zmin = mm.min; + this.#firstpainter.zmax = mm.max; + } - let promise; + this.#firstpainter.updateObject(src); - if (ndim === 1) { - if (!opt) opt = 'ap'; - if ((opt.indexOf('same') < 0) && (opt.indexOf('a') < 0)) opt += 'a'; - if (opt.indexOf('p') < 0) opt += 'p'; + this.#firstpainter.options.zoom_min_max = src.TestBit(kIsZoomed); + } - const gr = painter.createGraph(eff); - painter.fillGraph(gr, opt); - promise = TGraphPainter$1.draw(dom, gr, opt); + // and now update histograms + const hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length ?? 0; + + if (nhists !== this.#painters.length) { + this.#did_update = 1; + pp?.cleanPrimitives(objp => this.#painters.indexOf(objp) >= 0); + this.#painters = []; } else { - if (!opt) opt = 'col'; - const hist = painter.createHisto(eff); - painter.fillHisto(hist, opt); - promise = TH2Painter$2.draw(dom, hist, opt); + this.#did_update = 2; + for (let indx = 0; indx < nhists; ++indx) { + const rindx = o.horder ? indx : nhists - indx - 1, + hist = hlst.arr[rindx]; + this.#painters[indx].updateObject(hist, this.getHistDrawOption(hist, stack.fHists.opt[rindx])); + } } - return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawFunction(0); - }); + return true; } -} // class TEfficiencyPainter + /** @summary Redraw THStack + * @desc Do something if previous update had changed number of histograms */ + redraw(reason) { + if (!this.#did_update) + return; -var TEfficiencyPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TEfficiencyPainter: TEfficiencyPainter -}); + const full_redraw = this.#did_update === 1; + this.#did_update = undefined; -class TScatterPainter extends TGraphPainter$1 { + let pr = Promise.resolve(this); - constructor(dom, obj) { - super(dom, obj); - this._need_2dhist = true; - this._not_adjust_hrange = true; - } + const o = this.getOptions(); - /** @summary Return drawn graph object */ - getGraph() { return this.getObject()?.fGraph; } + if (this.#firstpainter) { + const mm = this.getMinMax(o.errors || o.draw_errors); + this.#firstpainter.decodeOptions(o.hopt + mm.hopt); + pr = this.#firstpainter.redraw(reason); + } - /** @summary Return margins for histogram ranges */ - getHistRangeMargin() { return this.getObject()?.fMargin ?? 0.1; } + return pr.then(() => { + if (full_redraw) + return this.drawNextHisto(0, o.pads ? this.getPadPainter() : null); - /** @summary Draw axis histogram - * @private */ - async drawAxisHisto() { - const histo = this.createHistogram(); - return TH2Painter$2.draw(this.getDom(), histo, this.options.Axis); + const redrawSub = indx => { + if (indx >= this.#painters.length) + return this; + return this.#painters[indx].redraw(reason).then(() => redrawSub(indx + 1)); + }; + return redrawSub(0); + }); } - /** @summary Provide palette, create if necessary - * @private */ - getPalette() { - const gr = this.getGraph(); - let pal = gr?.fFunctions?.arr?.find(func => (func._typename === clTPaletteAxis)); + /** @summary Fill THStack context menu */ + fillContextMenuItems(menu) { + const o = this.getOptions(); + menu.addRedrawMenu(this); + if (!o.pads) { + menu.addchk(o.draw_errors, 'Draw errors', flag => { + o.draw_errors = flag; + const stack = this.getObject(), + hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length ?? 0; + for (let indx = 0; indx < nhists; ++indx) { + const rindx = o.horder ? indx : nhists - indx - 1, + hist = hlst.arr[rindx]; + this.#painters[indx].decodeOptions(this.getHistDrawOption(hist, stack.fHists.opt[rindx])); + } + this.redrawPad(); + }, 'Change draw erros in the stack'); + } + } - if (pal) return pal; + /** @summary Invoke histogram drawing */ + drawHist(dom, hist, hopt) { + const func = (this.getOptions().ndim === 1) ? TH1Painter$2.draw : TH2Painter$2.draw; + return func(dom, hist, hopt); + } - if (gr) { - pal = create$1(clTPaletteAxis); + /** @summary Access or modify histogram min/max + * @private */ + accessMM(ismin, v) { + const name = ismin ? 'fMinimum' : 'fMaximum', + stack = this.getObject(); + if (v === undefined) + return stack[name]; - const fp = this.get_main(); - Object.assign(pal, { fX1NDC: fp.fX2NDC + 0.005, fX2NDC: fp.fX2NDC + 0.05, fY1NDC: fp.fY1NDC, fY2NDC: fp.fY2NDC, fInit: 1, $can_move: true }); - Object.assign(pal.fAxis, { fChopt: '+', fLineColor: 1, fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11, fNdiv: 510 }); - gr.fFunctions.AddFirst(pal, ''); - } + this.#did_update = 2; - return pal; - } + stack[name] = v; - /** @summary Update TScatter members */ - _updateMembers(scatter, obj) { - scatter.fBits = obj.fBits; - scatter.fTitle = obj.fTitle; - scatter.fNpoints = obj.fNpoints; - scatter.fColor = obj.fColor; - scatter.fSize = obj.fSize; - scatter.fMargin = obj.fMargin; - scatter.fMinMarkerSize = obj.fMinMarkerSize; - scatter.fMaxMarkerSize = obj.fMaxMarkerSize; - super._updateMembers(scatter.fGraph, obj.fGraph); + this.interactiveRedraw('pad', ismin ? `exec:SetMinimum(${v})` : `exec:SetMaximum(${v})`); } - /** @summary Actual drawing of TScatter */ - async drawGraph() { - const fpainter = this.get_main(), - hpainter = this.getMainPainter(), - scatter = this.getObject(); - let scale = 1, offset = 0; - if (!fpainter || !hpainter || !scatter) return; + /** @summary Full stack redraw with specified draw option */ + async redrawWith(opt, skip_cleanup) { + const pp = this.getPadPainter(), + o = this.getOptions(); - if (scatter.fColor) { - const pal = this.getPalette(); - if (pal) - pal.$main_painter = this; + if (!skip_cleanup && pp) { + this.#firstpainter = null; + this.#painters = []; + if (o.pads) + pp.divide(0, 0); + pp.removePrimitive(this, true); + } - const pp = this.getPadPainter(); - if (!this._color_palette && isFunc(pp?.getCustomPalette)) - this._color_palette = pp.getCustomPalette(); - if (!this._color_palette) - this._color_palette = getColorPalette(this.options.Palette, pp?.isGrayscale()); + this.decodeOptions(opt); - let minc = scatter.fColor[0], maxc = scatter.fColor[0]; - for (let i = 1; i < scatter.fColor.length; ++i) { - minc = Math.min(minc, scatter.fColor[i]); - maxc = Math.max(maxc, scatter.fColor[i]); - } - if (maxc <= minc) - maxc = minc < 0 ? 0.9*minc : (minc > 0 ? 1.1*minc : 1); - this.fContour = new HistContour(minc, maxc); - this.fContour.createNormal(30); - this.fContour.configIndicies(0, 0); + const stack = this.getObject(); - fpainter.zmin = minc; - fpainter.zmax = maxc; - } + let pr = Promise.resolve(this), pad_painter = null; - if (scatter.fSize) { - let mins = scatter.fSize[0], maxs = scatter.fSize[0]; + if (o.pads) { + pr = ensureTCanvas(this, false).then(() => { + pad_painter = this.getPadPainter(); + return pad_painter.divide(o.nhist, 0, true); + }); + } else { + if (!o.nostack) + o.nostack = !this.buildStack(stack, pp); - for (let i = 1; i < scatter.fSize.length; ++i) { - mins = Math.min(mins, scatter.fSize[i]); - maxs = Math.max(maxs, scatter.fSize[i]); - } + if (!o.same && stack.fHists?.arr.length) { + if (!stack.fHistogram) + stack.fHistogram = this.createHistogram(stack); - if (maxs <= mins) - maxs = mins < 0 ? 0.9*mins : (mins > 0 ? 1.1*mins : 1); + const mm = this.getMinMax(o.errors || o.draw_errors); - scale = (scatter.fMaxMarkerSize - scatter.fMinMarkerSize) / (maxs - mins); - offset = mins; + pr = this.drawHist(this.getDrawDom(), stack.fHistogram, o.hopt + mm.hopt).then(subp => { + this.#firstpainter = subp; + subp.$stack_hist = true; + subp.setSecondaryId(this, 'hist'); // mark hist painter as created by THStack + }); + } } - this.createG(!fpainter.pad_layer); + return pr.then(() => this.drawNextHisto(0, pad_painter)).then(() => { + if (!o.pads) + this.addToPadPrimitives(); + return this; + }); + } - const funcs = fpainter.getGrFuncs(); + /** @summary draw THStack object in 2D only */ + static async draw(dom, stack, opt) { + if (!stack.fHists || !stack.fHists.arr) + return null; // drawing not needed - for (let i = 0; i < this.bins.length; ++i) { - const pnt = this.bins[i], - grx = funcs.grx(pnt.x), - gry = funcs.gry(pnt.y), - size = scatter.fSize ? scatter.fMinMarkerSize + scale * (scatter.fSize[i] - offset) : scatter.fMarkerSize, - color = scatter.fColor ? this.fContour.getPaletteColor(this._color_palette, scatter.fColor[i]) : this.getColor(scatter.fMarkerColor), - handle = new TAttMarkerHandler({ color, size, style: scatter.fMarkerStyle }); + const painter = new THStackPainter(dom, stack, opt); - this.draw_g.append('svg:path') - .attr('d', handle.create(grx, gry)) - .call(handle.func); - } + return painter.redrawWith(opt, true); + } - return this; +}; // class THStackPainter + +class THStackPainter extends THStackPainter$2 { + + /** @summary Invoke histogram drawing */ + drawHist(dom, hist, hopt) { + const func = (this.getOptions().ndim === 1) ? TH1Painter.draw : TH2Painter.draw; + return func(dom, hist, hopt); } - static async draw(dom, obj, opt) { - return TGraphPainter$1._drawGraph(new TScatterPainter(dom, obj), opt); + /** @summary draw THStack object */ + static async draw(dom, stack, opt) { + if (!stack.fHists || !stack.fHists.arr) + return null; // drawing not needed + + const painter = new THStackPainter(dom, stack, opt); + + return painter.redrawWith(opt, true); } -} // class TScatterPainter +} // class THStackPainter -var TScatterPainter$1 = /*#__PURE__*/Object.freeze({ +var THStackPainter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, -TScatterPainter: TScatterPainter +THStackPainter: THStackPainter }); -const kLineNDC = BIT(14); +/** @summary Prepare frame painter for 3D drawing + * @private */ +function before3DDraw(painter) { + const fp = painter.getFramePainter(); -class TLinePainter extends ObjectPainter { + if (!fp?.mode3d || !painter.getObject()) + return null; - /** @summary Start interactive moving */ - moveStart(x, y) { - const fullsize = Math.sqrt((this.x1-this.x2)**2 + (this.y1-this.y2)**2), - sz1 = Math.sqrt((x-this.x1)**2 + (y-this.y1)**2)/fullsize, - sz2 = Math.sqrt((x-this.x2)**2 + (y-this.y2)**2)/fullsize; - if (sz1 > 0.9) - this.side = 1; - else if (sz2 > 0.9) - this.side = -1; - else - this.side = 0; - } + if (fp?.toplevel) + return fp; - /** @summary Continue interactive moving */ - moveDrag(dx, dy) { - if (this.side !== 1) { this.x1 += dx; this.y1 += dy; } - if (this.side !== -1) { this.x2 += dx; this.y2 += dy; } - this.draw_g.select('path').attr('d', this.createPath()); - } + const main = painter.getMainPainter(); - /** @summary Finish interactive moving */ - moveEnd(not_changed) { - if (not_changed) return; - const line = this.getObject(); - let exec = ''; - line.fX1 = this.svgToAxis('x', this.x1, this.isndc); - line.fX2 = this.svgToAxis('x', this.x2, this.isndc); - line.fY1 = this.svgToAxis('y', this.y1, this.isndc); - line.fY2 = this.svgToAxis('y', this.y2, this.isndc); - if (this.side !== 1) exec += `SetX1(${line.fX1});;SetY1(${line.fY1});;`; - if (this.side !== -1) exec += `SetX2(${line.fX2});;SetY2(${line.fY2});;`; - this.submitCanvExec(exec + 'Notify();;'); - } + if (main && !isFunc(main.drawExtras)) + return null; - /** @summary Calculate line coordinates */ - prepareDraw() { - const line = this.getObject(); + const pr = main ? Promise.resolve(main) : drawDummy3DGeom(painter); - this.isndc = line.TestBit(kLineNDC); + return pr.then(geop => { + const pp = painter.getPadPainter(); + if (pp) + pp.options._disable_dragging = true; - const func = this.getAxisToSvgFunc(this.isndc, true, true); + if (geop.options.dummy && isFunc(painter.get3DBox)) + geop.extendCustomBoundingBox(painter.get3DBox()); + return geop.drawExtras(painter.getObject(), '', true, true); + }); +} - this.x1 = func.x(line.fX1); - this.y1 = func.y(line.fY1); - this.x2 = func.x(line.fX2); - this.y2 = func.y(line.fY2); +/** @summary Function to extract 3DBox for poly marker and line + * @private */ +function get3DBox() { + const obj = this.getObject(); + if (!obj?.fP.length) + return null; + const box = { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } }; - this.createAttLine({ attr: line }); + for (let k = 0; k < obj.fP.length; k += 3) { + const x = obj.fP[k], + y = obj.fP[k + 1], + z = obj.fP[k + 2]; + if (k === 0) { + box.min.x = box.max.x = x; + box.min.y = box.max.y = y; + box.min.z = box.max.z = z; + } else { + box.min.x = Math.min(x, box.min.x); + box.max.x = Math.max(x, box.max.x); + box.min.y = Math.min(y, box.min.y); + box.max.y = Math.max(y, box.max.y); + box.min.z = Math.min(z, box.min.z); + box.max.z = Math.max(z, box.max.z); + } } - /** @summary Create path */ - createPath() { - const x1 = Math.round(this.x1), x2 = Math.round(this.x2), y1 = Math.round(this.y1), y2 = Math.round(this.y2); - return `M${x1},${y1}` + (x1 === x2 ? `V${y2}` : (y1 === y2 ? `H${x2}` : `L${x2},${y2}`)); - } + return box; +} - /** @summary Add extras - used for TArrow */ - addExtras() {} - /** @summary Redraw line */ - redraw() { - this.createG(); +/** @summary direct draw function for TPolyMarker3D object (optionally with geo painter) + * @private */ +async function drawPolyMarker3D() { + this.get3DBox = get3DBox; - this.prepareDraw(); + const fp = before3DDraw(this); - const elem = this.draw_g.append('svg:path') - .attr('d', this.createPath()) - .call(this.lineatt.func); + if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) + return fp; - if (this.getObject()?.$do_not_draw) - elem.remove(); - else { - this.addExtras(elem); - addMoveHandler(this); - assignContextMenu(this, kToFront); - } + this.$fp = fp; - return this; - } + return drawPolyMarker3D$1.bind(this)(); +} - /** @summary Draw TLine object */ - static async draw(dom, obj, opt) { - const painter = new TLinePainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); +/** @summary Direct draw function for TPolyLine3D object + * @desc Takes into account dashed properties + * @private */ +async function drawPolyLine3D() { + this.get3DBox = get3DBox; + + const line = this.getObject(), + fp = before3DDraw(this); + + if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) + return fp; + + const limit = 3 * line.fN, p = line.fP, pnts = []; + + for (let n = 3; n < limit; n += 3) { + pnts.push(fp.grx(p[n - 3]), fp.gry(p[n - 2]), fp.grz(p[n - 1]), + fp.grx(p[n]), fp.gry(p[n + 1]), fp.grz(p[n + 2])); } -} // class TLinePainter + const lines = createLineSegments(pnts, create3DLineMaterial(this, line)); -var TLinePainter$1 = /*#__PURE__*/Object.freeze({ + fp.add3DMesh(lines, this, true); + + fp.render3D(100); + + return true; +} + +var draw3d = /*#__PURE__*/Object.freeze({ __proto__: null, -TLinePainter: TLinePainter +drawPolyLine3D: drawPolyLine3D, +drawPolyMarker3D: drawPolyMarker3D }); /** - * @summary Painter class for TRatioPlot + * @summary Painter for TGraphTime object * * @private */ -class TRatioPlotPainter extends ObjectPainter { +class TGraphTimePainter extends ObjectPainter { - /** @summary Set grids range */ - setGridsRange(xmin, xmax, ymin, ymax, low_p) { - const ratio = this.getObject(); - if (xmin === xmax) { - const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad)?.getFramePainter()?.x_handle; - if (!x_handle) return; - if (xmin === 0) { - // in case of unzoom full range should be used - xmin = x_handle.full_min; - xmax = x_handle.full_max; - } else { - // in case of y-scale zooming actual range has to be used - xmin = x_handle.scale_min; - xmax = x_handle.scale_max; - } - } - ratio.fGridlines.forEach(line => { - line.fX1 = xmin; - line.fX2 = xmax; + #step; // step number + #selfid; // use to identify primitives which should be clean + #wait_animation_frame; // animation flag + #running_timeout; // timeout handle + + constructor(dom, gr, opt) { + super(dom, gr, opt); + this.decodeOptions(opt); + this.#selfid = 'grtime_' + internals.id_counter++; + } + + /** @summary Redraw object */ + redraw() { + if (this.#step === undefined) + this.startDrawing(); + } + + /** @summary Decode drawing options */ + decodeOptions(opt) { + const d = new DrawOptions(opt || 'REPEAT'); + + this.setOptions({ + once: d.check('ONCE'), + repeat: d.check('REPEAT'), + first: d.check('FIRST') }); - const nlines = Math.min(ratio.fGridlines.length, ratio.fGridlinePositions.length); - for (let i = 0; i < nlines; ++i) { - const y = ratio.fGridlinePositions[i], - line = ratio.fGridlines[i]; - if (ymin !== 'ignorey') { - line.$do_not_draw = (ymin !== ymax) && ((y < ymin) || (y > ymax)); - line.fY1 = line.fY2 = y; - } - low_p?.findPainterFor(line)?.redraw(); - } + + this.storeDrawOpt(opt); } - /** @summary Configure custom interactive handlers for ratio plot - * @desc Should work for both new and old code */ - configureInteractive() { - const ratio = this.getObject(), - pp = this.getPadPainter(), - up_p = pp.findPainterFor(ratio.fUpperPad, 'upper_pad', clTPad), - up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), - low_fp = low_p?.getFramePainter(); + /** @summary Draw primitives */ + async drawPrimitives(indx) { + const lst = this.getObject()?.fSteps.arr[this.#step]; - if (!up_p || !low_p) + if (!lst || (indx >= lst.arr.length)) return; - low_p.forEachPainterInPad(objp => { - if (isFunc(objp?.testEditable)) - objp.testEditable(false); + const obj = lst.arr[indx], + opt = lst.opt[indx] + (obj._typename === clTMarker ? ';no_interactive' : ''); + + return draw(this.getPadPainter(), obj, opt).then(p => { + if (p) { + p.$grtimeid = this.#selfid; // indicator that painter created by ourself + p.$grstep = this.#step; // remember step + } + return this.drawPrimitives(indx + 1); }); + } - this.setGridsRange(low_fp.scale_xmin, low_fp.scale_xmax, low_fp.scale_ymin, low_fp.scale_ymax, low_p); + /** @summary Continue drawing */ + continueDrawing() { + const gr = this.getObject(), + o = this.getOptions(); - if (up_p._ratio_interactive && low_p._ratio_interactive) + if (o.first) { + // draw only single frame, cancel all others + this.#step = undefined; return; + } - up_p._ratio_interactive = true; - low_p._ratio_interactive = true; + if (this.#wait_animation_frame) { + this.#wait_animation_frame = undefined; - up_fp.o_zoom = up_fp.zoom; - up_fp._ratio_low_fp = low_fp; - up_fp._ratio_painter = this; + // clear pad + const pp = this.getPadPainter(); + if (!pp) { + // most probably, pad is cleared + this.#step = undefined; + return; + } - up_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) { - return this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax).then(res => { - this._ratio_painter.setGridsRange(up_fp.scale_xmin, up_fp.scale_xmax, 'ignory'); - return this._ratio_low_fp.o_zoom(up_fp.scale_xmin, up_fp.scale_xmax).then(() => res); - }); - }; + // draw primitives again + this.drawPrimitives(0).then(() => { + // clear primitives produced by previous drawing to avoid flicking + pp.cleanPrimitives(p => { return (p.$grtimeid === this.#selfid) && (p.$grstep !== this.#step); }); - up_fp.o_sizeChanged = up_fp.sizeChanged; - up_fp.sizeChanged = function() { - this.o_sizeChanged(); - this._ratio_low_fp.fX1NDC = this.fX1NDC; - this._ratio_low_fp.fX2NDC = this.fX2NDC; - this._ratio_low_fp.o_sizeChanged(); - }; + this.continueDrawing(); + }); + } else if (this.#running_timeout) { + clearTimeout(this.#running_timeout); + this.#running_timeout = undefined; - low_fp.o_zoom = low_fp.zoom; - low_fp._ratio_up_fp = up_fp; - low_fp._ratio_painter = this; + this.#wait_animation_frame = true; + // use animation frame to disable update in inactive form + requestAnimationFrame(() => this.continueDrawing()); + } else { + let sleeptime = Math.max(gr.fSleepTime, 10); - low_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) { - if (xmin === xmax) { - xmin = up_fp.xmin; - xmax = up_fp.xmax; - } else { - if (xmin < up_fp.xmin) xmin = up_fp.xmin; - if (xmax > up_fp.xmax) xmax = up_fp.xmax; + if (++this.#step > gr.fSteps.arr.length) { + if (o.repeat) { + this.#step = 0; // start again + sleeptime = Math.max(5000, 5 * sleeptime); // increase sleep time + } else { + this.#step = undefined; // clear indicator that animation running + return; + } } - this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax); - return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax)); - }; - low_fp.o_sizeChanged = low_fp.sizeChanged; - low_fp.sizeChanged = function() { - this.o_sizeChanged(); - this._ratio_up_fp.fX1NDC = this.fX1NDC; - this._ratio_up_fp.fX2NDC = this.fX2NDC; - this._ratio_up_fp.o_sizeChanged(); - }; + this.#running_timeout = setTimeout(() => this.continueDrawing(), sleeptime); + } } - /** @summary Redraw old TRatioPlot where object was in very end of list of primitives */ - async redrawOld() { - const ratio = this.getObject(), - pp = this.getPadPainter(), - top_p = pp.findPainterFor(ratio.fTopPad, 'top_pad', clTPad), - pad = pp.getRootPad(), - mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, - tick_x = pad.fTickx || mirrow_axis, - tick_y = pad.fTicky || mirrow_axis; + /** @summary Start drawing of TGraphTime */ + startDrawing() { + this.#step = 0; - top_p?.disablePadDrawing(); + return this.drawPrimitives(0).then(() => { + this.continueDrawing(); + return this; + }); + } - const up_p = pp.findPainterFor(ratio.fUpperPad, 'upper_pad', clTPad), - up_main = up_p?.getMainPainter(), - up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), - low_main = low_p?.getMainPainter(), - low_fp = low_p?.getFramePainter(); - let promise_up = Promise.resolve(true); + /** @summary Draw TGraphTime object */ + static async draw(dom, gr, opt) { + if (!gr.fFrame) { + console.error('Frame histogram not exists'); + return null; + } - if (up_p && up_main && up_fp && low_fp && !up_p._ratio_configured) { - up_p._ratio_configured = true; + const painter = new TGraphTimePainter(dom, gr, opt); - up_main.options.Axis = 0; // draw both axes + if (painter.getMainPainter()) { + console.error('Cannot draw graph time on top of other histograms'); + return null; + } - const h = up_main.getHisto(); + if (!gr.fFrame.fTitle && gr.fTitle) { + const arr = gr.fTitle.split(';'); + gr.fFrame.fTitle = arr[0]; + if (arr[1]) + gr.fFrame.fXaxis.fTitle = arr[1]; + if (arr[2]) + gr.fFrame.fYaxis.fTitle = arr[2]; + } - h.fYaxis.$use_top_pad = true; // workaround to use same scaling - h.fXaxis.fLabelSize = 0; // do not draw X axis labels - h.fXaxis.fTitle = ''; // do not draw X axis title + return TH1Painter$2.draw(dom, gr.fFrame, '').then(() => { + painter.addToPadPrimitives(); + return painter.startDrawing(); + }); + } + +} // class TGraphTimePainter + + +/** @summary Draw TRooPlot + * @private */ +async function drawRooPlot(dom, plot) { + return draw(dom, plot._hist, 'hist').then(async hp => { + const arr = []; + for (let i = 0; i < plot._items.arr.length; ++i) + arr.push(draw(dom, plot._items.arr[i], plot._items.opt[i])); + return Promise.all(arr).then(() => hp); + }); +} + +var TGraphTimePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TGraphTimePainter: TGraphTimePainter, +drawRooPlot: drawRooPlot +}); + +function getMax(arr) { + let v = arr[0]; + for (let i = 1; i < arr.length; ++i) + v = Math.max(v, arr[i]); + return v; +} + +function getMin(arr) { + let v = arr[0]; + for (let i = 1; i < arr.length; ++i) + v = Math.min(v, arr[i]); + return v; +} - up_p.getRootPad().fTickx = tick_x; - up_p.getRootPad().fTicky = tick_y; +function TMath_Sort(np, values, indicies /* , down */) { + const arr = new Array(np); + for (let i = 0; i < np; ++i) + arr[i] = { v: values[i], i }; - promise_up = up_p.redrawPad(); - } + arr.sort((a, b) => { return a.v < b.v ? -1 : (a.v > b.v ? 1 : 0); }); - return promise_up.then(() => { - if (!low_p || !low_main || !low_fp || !up_fp || low_p._ratio_configured) - return this; + for (let i = 0; i < np; ++i) + indicies[i] = arr[i].i; +} - low_p._ratio_configured = true; - low_main.options.Axis = 0; // draw both axes - const h = low_main.getHisto(); - h.fXaxis.fTitle = 'x'; +class TGraphDelaunay { - h.fXaxis.$use_top_pad = true; - h.fYaxis.$use_top_pad = true; - low_p.getRootPad().fTickx = tick_x; - low_p.getRootPad().fTicky = tick_y; + constructor(g) { + this.fGraph2D = g; + this.fX = g.fX; + this.fY = g.fY; + this.fZ = g.fZ; + this.fNpoints = g.fNpoints; + this.fZout = 0.0; + this.fNdt = 0; + this.fNhull = 0; + this.fHullPoints = null; + this.fXN = null; + this.fYN = null; + this.fOrder = null; + this.fDist = null; + this.fPTried = null; + this.fNTried = null; + this.fMTried = null; + this.fInit = false; + this.fXNmin = 0.0; + this.fXNmax = 0.0; + this.fYNmin = 0.0; + this.fYNmax = 0.0; + this.fXoffset = 0.0; + this.fYoffset = 0.0; + this.fXScaleFactor = 0.0; + this.fYScaleFactor = 0.0; - const arr = []; - let currpad; + this.SetMaxIter(); + } - // add missing lines in old ratio painter - if ((ratio.fGridlinePositions.length > 0) && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) { - ratio.fGridlinePositions.forEach(gridy => { - let found = false; - ratio.fGridlines.forEach(line => { - if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) found = true; - }); - if (!found) { - const line = create$1(clTLine); - line.fX1 = up_fp.scale_xmin; - line.fX2 = up_fp.scale_xmax; - line.fY1 = line.fY2 = gridy; - line.fLineStyle = 2; - ratio.fGridlines.push(line); - if (currpad === undefined) - currpad = this.selectCurrentPad(ratio.fLowerPad.fName); - arr.push(TLinePainter.draw(this.getDom(), line)); - } - }); - } - return Promise.all(arr).then(() => { - if (currpad !== undefined) - this.selectCurrentPad(currpad); - return low_fp.zoom(up_fp.scale_xmin, up_fp.scale_xmax); - }); - }); + Initialize() { + if (!this.fInit) { + this.CreateTrianglesDataStructure(); + this.FindHull(); + this.fInit = true; + } } - /** @summary Redraw TRatioPlot */ - async redraw() { - const ratio = this.getObject(), - pp = this.getPadPainter(); + ComputeZ(x, y) { + // Initialize the Delaunay algorithm if needed. + // CreateTrianglesDataStructure computes fXoffset, fYoffset, + // fXScaleFactor and fYScaleFactor; + // needed in this function. + this.Initialize(); - if (this.$oldratio === undefined) - this.$oldratio = !!pp.findPainterFor(ratio.fTopPad, 'top_pad', clTPad); + // Find the z value corresponding to the point (x,y). + const xx = (x + this.fXoffset) * this.fXScaleFactor, + yy = (y + this.fYoffset) * this.fYScaleFactor; + let zz = this.Interpolate(xx, yy); - // configure ratio interactive at the end - pp.$userInteractive = () => this.configureInteractive(); + // Wrong zeros may appear when points sit on a regular grid. + // The following line try to avoid this problem. + if (zz === 0) + zz = this.Interpolate(xx + 0.0001, yy); - if (this.$oldratio) - return this.redrawOld(); + return zz; + } - const pad = pp.getRootPad(), - mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, - tick_x = pad.fTickx || mirrow_axis, - tick_y = pad.fTicky || mirrow_axis; - // do not draw primitives and pad itself - ratio.fTopPad.$disable_drawing = true; + CreateTrianglesDataStructure() { + // Offset fX and fY so they average zero, and scale so the average + // of the X and Y ranges is one. The normalized version of fX and fY used + // in Interpolate. + const xmax = getMax(this.fGraph2D.fX), + ymax = getMax(this.fGraph2D.fY), + xmin = getMin(this.fGraph2D.fX), + ymin = getMin(this.fGraph2D.fY); + this.fXoffset = -(xmax + xmin) / 2; + this.fYoffset = -(ymax + ymin) / 2; + this.fXScaleFactor = 1 / (xmax - xmin); + this.fYScaleFactor = 1 / (ymax - ymin); + this.fXNmax = (xmax + this.fXoffset) * this.fXScaleFactor; + this.fXNmin = (xmin + this.fXoffset) * this.fXScaleFactor; + this.fYNmax = (ymax + this.fYoffset) * this.fYScaleFactor; + this.fYNmin = (ymin + this.fYoffset) * this.fYScaleFactor; + this.fXN = new Array(this.fNpoints + 1); + this.fYN = new Array(this.fNpoints + 1); + for (let n = 0; n < this.fNpoints; n++) { + this.fXN[n + 1] = (this.fX[n] + this.fXoffset) * this.fXScaleFactor; + this.fYN[n + 1] = (this.fY[n] + this.fYoffset) * this.fYScaleFactor; + } - ratio.fUpperPad.$ratio_pad = 'up'; // indicate drawing of the axes for main painter - ratio.fUpperPad.fTickx = tick_x; - ratio.fUpperPad.fTicky = tick_y; + // If needed, creates the arrays to hold the Delaunay triangles. + // A maximum number of 2*fNpoints is guessed. If more triangles will be + // find, FillIt will automatically enlarge these arrays. + this.fPTried = []; + this.fNTried = []; + this.fMTried = []; + } - ratio.fLowerPad.$ratio_pad = 'low'; // indicate drawing of the axes for main painter - ratio.fLowerPad.fTickx = tick_x; - ratio.fLowerPad.fTicky = tick_y; - return this; - } + // Is point e inside the triangle t1-t2-t3 ? - /** @summary Draw TRatioPlot */ - static async draw(dom, ratio, opt) { - const painter = new TRatioPlotPainter(dom, ratio, opt); + Enclose(t1, t2, t3, e) { + const x = [this.fXN[t1], this.fXN[t2], this.fXN[t3], this.fXN[t1]], + y = [this.fYN[t1], this.fYN[t2], this.fYN[t3], this.fYN[t1]], + xp = this.fXN[e], + yp = this.fYN[e]; + let i = 0, j = x.length - 1, oddNodes = false; - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } + for (; i < x.length; ++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; + } -} // class TRatioPlotPainter + return oddNodes; + } -var TRatioPlotPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TRatioPlotPainter: TRatioPlotPainter -}); -/** - * @summary Painter for TMultiGraph object. - * - * @private - */ + // Files the triangle defined by the 3 vertices p, n and m into the + // fxTried arrays. If these arrays are to small they are automatically + // expanded. -let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { + FileIt(p, n, m) { + let swap, ps = p, ns = n, ms = m; - /** @summary Create painter - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} obj - TMultiGraph object to draw */ - constructor(dom, mgraph) { - super(dom, mgraph); - this.firstpainter = null; - this.autorange = false; - this.painters = []; // keep painters to be able update objects - } + // order the vertices before storing them + do { + swap = false; + if (ns > ps) { + [ns, ps] = [ps, ns]; + swap = true; + } + if (ms > ns) { + [ms, ns] = [ns, ms]; + swap = true; + } + } while (swap); - /** @summary Cleanup multigraph painter */ - cleanup() { - this.painters = []; - super.cleanup(); + // store a new Delaunay triangle + this.fNdt++; + this.fPTried.push(ps); + this.fNTried.push(ns); + this.fMTried.push(ms); } - /** @summary Update multigraph object */ - updateObject(obj) { - if (!this.matchObjectType(obj)) return false; - const mgraph = this.getObject(), - graphs = obj.fGraphs, - pp = this.getPadPainter(); + // Attempt to find all the Delaunay triangles of the point set. It is not + // guaranteed that it will fully succeed, and no check is made that it has + // fully succeeded (such a check would be possible by referencing the points + // that make up the convex hull). The method is to check if each triangle + // shares all three of its sides with other triangles. If not, a point is + // generated just outside the triangle on the side(s) not shared, and a new + // triangle is found for that point. If this method is not working properly + // (many triangles are not being found) it's probably because the new points + // are too far beyond or too close to the non-shared sides. Fiddling with + // the size of the `alittlebit' parameter may help. - mgraph.fTitle = obj.fTitle; + FindAllTriangles() { + if (this.fAllTri) + return; - let isany = false; - if (this.firstpainter) { - let histo = obj.fHistogram; - if (this.autorange && !histo) - histo = this.scanGraphsRange(graphs); + this.fAllTri = true; - if (this.firstpainter.updateObject(histo)) - isany = true; - } + let xcntr, ycntr, xm, ym, xx, yy, + sx, sy, nx, ny, mx, my, mdotn, nn, a, + t1, t2, pa, na, ma, pb, nb, mb, p1 = 0, p2 = 0, m, n, p3 = 0; + const s = [false, false, false], + alittlebit = 0.0001; - const ngr = Math.min(graphs.arr.length, this.painters.length); + this.Initialize(); - for (let i = 0; i < ngr; ++i) { - if (this.painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this._restopt) + this._auto)) - isany = true; + // start with a point that is guaranteed to be inside the hull (the + // centre of the hull). The starting point is shifted "a little bit" + // otherwise, in case of triangles aligned on a regular grid, we may + // found none of them. + xcntr = 0; + ycntr = 0; + for (n = 1; n <= this.fNhull; n++) { + xcntr += this.fXN[this.fHullPoints[n - 1]]; + ycntr += this.fYN[this.fHullPoints[n - 1]]; } + xcntr = xcntr / this.fNhull + alittlebit; + ycntr = ycntr / this.fNhull + alittlebit; + // and calculate it's triangle + this.Interpolate(xcntr, ycntr); - this._funcHandler = new FunctionsHandler(this, pp, obj.fFunctions); + // loop over all Delaunay triangles (including those constantly being + // produced within the loop) and check to see if their 3 sides also + // correspond to the sides of other Delaunay triangles, i.e. that they + // have all their neighbors. + t1 = 1; + while (t1 <= this.fNdt) { + // get the three points that make up this triangle + pa = this.fPTried[t1 - 1]; + na = this.fNTried[t1 - 1]; + ma = this.fMTried[t1 - 1]; - return isany; - } + // produce three integers which will represent the three sides + s[0] = false; + s[1] = false; + s[2] = false; + // loop over all other Delaunay triangles + for (t2 = 1; t2 <= this.fNdt; t2++) { + if (t2 !== t1) { + // get the points that make up this triangle + pb = this.fPTried[t2 - 1]; + nb = this.fNTried[t2 - 1]; + mb = this.fMTried[t2 - 1]; + // do triangles t1 and t2 share a side? + if ((pa === pb && na === nb) || (pa === pb && na === mb) || (pa === nb && na === mb)) { + // they share side 1 + s[0] = true; + } else if ((pa === pb && ma === nb) || (pa === pb && ma === mb) || (pa === nb && ma === mb)) { + // they share side 2 + s[1] = true; + } else if ((na === pb && ma === nb) || (na === pb && ma === mb) || (na === nb && ma === mb)) { + // they share side 3 + s[2] = true; + } + } + // if t1 shares all its sides with other Delaunay triangles then + // forget about it + if (s[0] && s[1] && s[2]) + continue; + } + // Looks like t1 is missing a neighbor on at least one side. + // For each side, take a point a little bit beyond it and calculate + // the Delaunay triangle for that point, this should be the triangle + // which shares the side. + for (m = 1; m <= 3; m++) { + if (!s[m - 1]) { + // get the two points that make up this side + if (m === 1) { + p1 = pa; + p2 = na; + p3 = ma; + } else if (m === 2) { + p1 = pa; + p2 = ma; + p3 = na; + } else if (m === 3) { + p1 = na; + p2 = ma; + p3 = pa; + } + // get the coordinates of the centre of this side + xm = (this.fXN[p1] + this.fXN[p2]) / 2.0; + ym = (this.fYN[p1] + this.fYN[p2]) / 2.0; + // we want to add a little to these coordinates to get a point just + // outside the triangle; (sx,sy) will be the vector that represents + // the side + sx = this.fXN[p1] - this.fXN[p2]; + sy = this.fYN[p1] - this.fYN[p2]; + // (nx,ny) will be the normal to the side, but don't know if it's + // pointing in or out yet + nx = sy; + ny = -sx; + nn = Math.sqrt(nx * nx + ny * ny); + nx /= nn; + ny /= nn; + mx = this.fXN[p3] - xm; + my = this.fYN[p3] - ym; + mdotn = mx * nx + my * ny; + if (mdotn > 0) { + // (nx,ny) is pointing in, we want it pointing out + nx = -nx; + ny = -ny; + } + // increase/decrease xm and ym a little to produce a point + // just outside the triangle (ensuring that the amount added will + // be large enough such that it won't be lost in rounding errors) + a = Math.abs(Math.max(alittlebit * xm, alittlebit * ym)); + xx = xm + nx * a; + yy = ym + ny * a; + // try and find a new Delaunay triangle for this point + this.Interpolate(xx, yy); - /** @summary Redraw multigraph - * @desc may redraw histogram which was used to draw axes - * @return {Promise} for ready */ - async redraw(reason) { - const promise = this.firstpainter?.redraw(reason) ?? Promise.resolve(true), - redrawNext = async indx => { - if (indx >= this.painters.length) - return this; - return this.painters[indx].redraw(reason).then(() => redrawNext(indx + 1)); - }; + // this side of t1 should now, hopefully, if it's not part of the + // hull, be shared with a new Delaunay triangle just calculated by Interpolate + } + } + t1++; + } + } - return promise.then(() => redrawNext(0)).then(() => { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; - return res; - }); - } + // Finds those points which make up the convex hull of the set. If the xy + // plane were a sheet of wood, and the points were nails hammered into it + // at the respective coordinates, then if an elastic band were stretched + // over all the nails it would form the shape of the convex hull. Those + // nails in contact with it are the points that make up the hull. - /** @summary Scan graphs range - * @return {object} histogram for axes drawing */ - scanGraphsRange(graphs, histo, pad) { - const mgraph = this.getObject(), - rw = { xmin: 0, xmax: 0, ymin: 0, ymax: 0, first: true }; - let maximum, minimum, logx = false, logy = false, - time_display = false, time_format = ''; + FindHull() { + if (!this.fHullPoints) + this.fHullPoints = new Array(this.fNpoints); - if (pad) { - logx = pad.fLogx; - logy = pad.fLogv ?? pad.fLogy; - rw.xmin = pad.fUxmin; - rw.xmax = pad.fUxmax; - rw.ymin = pad.fUymin; - rw.ymax = pad.fUymax; - rw.first = false; + let nhull_tmp = 0; + for (let n = 1; n <= this.fNpoints; n++) { + // if the point is not inside the hull of the set of all points + // bar it, then it is part of the hull of the set of all points + // including it + const is_in = this.InHull(n, n); + if (!is_in) { + // cannot increment fNhull directly - InHull needs to know that + // the hull has not yet been completely found + nhull_tmp++; + this.fHullPoints[nhull_tmp - 1] = n; + } } + this.fNhull = nhull_tmp; + } - // ignore existing histo in 3d case - if (this._3d && histo && !histo.fXaxis.fLabels) - histo = null; - if (!histo) { - this.autorange = true; + // Is point e inside the hull defined by all points apart from x ? - if (graphs.arr[0]?.fHistogram?.fXaxis?.fTimeDisplay) { - time_display = true; - time_format = graphs.arr[0].fHistogram.fXaxis.fTimeFormat; - } - } + InHull(e, x) { + let n1, n2, n, m, ntry, + lastdphi, dd1, dd2, dx1, dx2, dx3, dy1, dy2, dy3, + u, v, vNv1, vNv2, phi1, phi2, dphi, + deTinhull = false; - graphs.arr.forEach(gr => { - if (gr.fNpoints === 0) return; - if (rw.first) { - rw.xmin = rw.xmax = gr.fX[0]; - rw.ymin = rw.ymax = gr.fY[0]; - rw.first = false; - } - for (let i = 0; i < gr.fNpoints; ++i) { - rw.xmin = Math.min(rw.xmin, gr.fX[i]); - rw.xmax = Math.max(rw.xmax, gr.fX[i]); - rw.ymin = Math.min(rw.ymin, gr.fY[i]); - rw.ymax = Math.max(rw.ymax, gr.fY[i]); - } - }); + const xx = this.fXN[e], + yy = this.fYN[e]; - if (rw.xmin === rw.xmax) - rw.xmax += 1; - if (rw.ymin === rw.ymax) - rw.ymax += 1; - const dx = 0.05 * (rw.xmax - rw.xmin), - dy = 0.05 * (rw.ymax - rw.ymin); - let uxmin = rw.xmin - dx, - uxmax = rw.xmax + dx; - if (logy) { - if (rw.ymin <= 0) - rw.ymin = 0.001 * rw.ymax; - minimum = rw.ymin / (1 + 0.5 * Math.log10(rw.ymax / rw.ymin)); - maximum = rw.ymax * (1 + 0.2 * Math.log10(rw.ymax / rw.ymin)); + if (this.fNhull > 0) { + // The hull has been found - no need to use any points other than + // those that make up the hull + ntry = this.fNhull; } else { - minimum = rw.ymin - dy; - maximum = rw.ymax + dy; + // The hull has not yet been found, will have to try every point + ntry = this.fNpoints; } - if (minimum < 0 && rw.ymin >= 0) - minimum = 0; - if (maximum > 0 && rw.ymax <= 0) - maximum = 0; - - const glob_minimum = minimum, glob_maximum = maximum; - if (uxmin < 0 && rw.xmin >= 0) - uxmin = logx ? 0.9 * rw.xmin : 0; - if (uxmax > 0 && rw.xmax <= 0) - uxmax = logx? 1.1 * rw.xmax : 0; + // n1 and n2 will represent the two points most separated by angle + // from point e. Initially the angle between them will be <180 degrees. + // But subsequent points will increase the n1-e-n2 angle. If it + // increases above 180 degrees then point e must be surrounded by + // points - it is not part of the hull. + n1 = 1; + n2 = 2; + if (n1 === x) { + n1 = n2; + n2++; + } else if (n2 === x) + n2++; - if (mgraph.fMinimum !== kNoZoom) - rw.ymin = minimum = mgraph.fMinimum; - if (mgraph.fMaximum !== kNoZoom) - rw.ymax = maximum = mgraph.fMaximum; - if (minimum < 0 && rw.ymin >= 0 && logy) - minimum = 0.9 * rw.ymin; - if (maximum > 0 && rw.ymax <= 0 && logy) - maximum = 1.1 * rw.ymax; - if (minimum <= 0 && logy) - minimum = 0.001 * maximum; - if (!logy && minimum > 0 && minimum < 0.05*maximum) - minimum = 0; - if (uxmin <= 0 && logx) - uxmin = (uxmax > 1000) ? 1 : 0.001 * uxmax; + // Get the angle n1-e-n2 and set it to lastdphi + dx1 = xx - this.fXN[n1]; + dy1 = yy - this.fYN[n1]; + dx2 = xx - this.fXN[n2]; + dy2 = yy - this.fYN[n2]; + phi1 = Math.atan2(dy1, dx1); + phi2 = Math.atan2(dy2, dx2); + dphi = (phi1 - phi2) - (Math.floor((phi1 - phi2) / (Math.PI * 2)) * Math.PI * 2); + if (dphi < 0) + dphi += Math.PI * 2; + lastdphi = dphi; + for (n = 1; n <= ntry; n++) { + if (this.fNhull > 0) { + // Try hull point n + m = this.fHullPoints[n - 1]; + } else + m = n; - // Create a temporary histogram to draw the axis (if necessary) - if (!histo) { - let xaxis, yaxis; - if (this._3d) { - histo = createHistogram(clTH2I, graphs.arr.length, 10); - xaxis = histo.fXaxis; - xaxis.fXmin = 0; - xaxis.fXmax = graphs.arr.length; - xaxis.fLabels = create$1(clTHashList); - for (let i = 0; i < graphs.arr.length; i++) { - const lbl = create$1(clTObjString); - lbl.fString = graphs.arr[i].fTitle || `gr${i}`; - lbl.fUniqueID = graphs.arr.length - i; // graphs drawn in reverse order - xaxis.fLabels.Add(lbl, ''); + if ((m !== n1) && (m !== n2) && (m !== x)) { + // Can the vector e->m be represented as a sum with positive + // coefficients of vectors e->n1 and e->n2? + dx1 = xx - this.fXN[n1]; + dy1 = yy - this.fYN[n1]; + dx2 = xx - this.fXN[n2]; + dy2 = yy - this.fYN[n2]; + dx3 = xx - this.fXN[m]; + dy3 = yy - this.fYN[m]; + + dd1 = (dx2 * dy1 - dx1 * dy2); + dd2 = (dx1 * dy2 - dx2 * dy1); + + if (dd1 * dd2) { + u = (dx2 * dy3 - dx3 * dy2) / dd1; + v = (dx1 * dy3 - dx3 * dy1) / dd2; + if ((u < 0) || (v < 0)) { + // No, it cannot - point m does not lie in-between n1 and n2 as + // viewed from e. Replace either n1 or n2 to increase the + // n1-e-n2 angle. The one to replace is the one which makes the + // smallest angle with e->m + vNv1 = (dx1 * dx3 + dy1 * dy3) / Math.sqrt(dx1 * dx1 + dy1 * dy1); + vNv2 = (dx2 * dx3 + dy2 * dy3) / Math.sqrt(dx2 * dx2 + dy2 * dy2); + if (vNv1 > vNv2) { + n1 = m; + phi1 = Math.atan2(dy3, dx3); + phi2 = Math.atan2(dy2, dx2); + } else { + n2 = m; + phi1 = Math.atan2(dy1, dx1); + phi2 = Math.atan2(dy3, dx3); + } + dphi = (phi1 - phi2) - (Math.floor((phi1 - phi2) / (Math.PI * 2)) * Math.PI * 2); + if (dphi < 0) + dphi += Math.PI * 2; + if ((dphi - Math.PI) * (lastdphi - Math.PI) < 0) { + // The addition of point m means the angle n1-e-n2 has risen + // above 180 degrees, the point is in the hull. + deTinhull = true; + return deTinhull; + } + lastdphi = dphi; + } } - xaxis = histo.fYaxis; - yaxis = histo.fZaxis; - } else { - histo = createHistogram(clTH1I, 10); - xaxis = histo.fXaxis; - yaxis = histo.fYaxis; - } - histo.fTitle = mgraph.fTitle; - if (histo.fTitle.indexOf(';') >= 0) { - const t = histo.fTitle.split(';'); - histo.fTitle = t[0]; - if (t[1]) xaxis.fTitle = t[1]; - if (t[2]) yaxis.fTitle = t[2]; } - - xaxis.fXmin = uxmin; - xaxis.fXmax = uxmax; - xaxis.fTimeDisplay = time_display; - if (time_display) xaxis.fTimeFormat = time_format; } + // Point e is not surrounded by points - it is not in the hull. + return deTinhull; + } - const axis = this._3d ? histo.fZaxis : histo.fYaxis; - axis.fXmin = Math.min(minimum, glob_minimum); - axis.fXmax = Math.max(maximum, glob_maximum); - histo.fMinimum = minimum; - histo.fMaximum = maximum; - histo.fBits |= kNoStats; + // Finds the z-value at point e given that it lies + // on the plane defined by t1,t2,t3 - return histo; - } + InterpolateOnPlane(TI1, TI2, TI3, e) { + let swap, t1 = TI1, t2 = TI2, t3 = TI3; - /** @summary draw speical histogram for axis - * @return {Promise} when ready */ - async drawAxisHist(histo, hopt) { - return TH1Painter$2.draw(this.getDom(), histo, hopt); - } + // order the vertices + do { + swap = false; + if (t2 > t1) { + [t1, t2] = [t2, t1]; + swap = true; + } + if (t3 > t2) { + [t2, t3] = [t3, t2]; + swap = true; + } + } while (swap); - /** @summary Draw graph */ - async drawGraph(gr, opt /*, pos3d */) { - return TGraphPainter$1.draw(this.getDom(), gr, opt); + const x1 = this.fXN[t1], + x2 = this.fXN[t2], + x3 = this.fXN[t3], + y1 = this.fYN[t1], + y2 = this.fYN[t2], + y3 = this.fYN[t3], + f1 = this.fZ[t1 - 1], + f2 = this.fZ[t2 - 1], + f3 = this.fZ[t3 - 1], + u = (f1 * (y2 - y3) + f2 * (y3 - y1) + f3 * (y1 - y2)) / (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)), + v = (f1 * (x2 - x3) + f2 * (x3 - x1) + f3 * (x1 - x2)) / (y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2)), + w = f1 - u * x1 - v * y1; + + return u * this.fXN[e] + v * this.fYN[e] + w; } - /** @summary method draws next graph */ - async drawNextGraph(indx) { - const graphs = this.getObject().fGraphs; + // Finds the Delaunay triangle that the point (xi,yi) sits in (if any) and + // calculate a z-value for it by linearly interpolating the z-values that + // make up that triangle. - // at the end of graphs drawing draw functions (if any) - if (indx >= graphs.arr.length) - return this; + Interpolate(xx, yy) { + let thevalue, + it, ntris_tried, p, n, m, + i, j, k, l, z, f, d, o1, o2, a, b, t1, t2, t3, + /* eslint-disable-next-line no-useless-assignment */ + ndegen = 0, degen = 0, fdegen = 0, o1degen = 0, o2degen = 0, + vxN, vyN, + d1, d2, d3, c1, c2, dko1, dko2, dfo1, + dfo2, sin_sum, cfo1k, co2o1k, co2o1f, + dx1, dx2, dx3, dy1, dy2, dy3, u, v; + const dxz = [0, 0, 0], dyz = [0, 0, 0]; - const gr = graphs.arr[indx], - draw_opt = (graphs.opt[indx] || this._restopt) + this._auto; + // initialize the Delaunay algorithm if needed + this.Initialize(); - // used in automatic colors numbering - if (this._auto) - gr.$num_graphs = graphs.arr.length; + // create vectors needed for sorting + if (!this.fOrder) { + this.fOrder = new Array(this.fNpoints); + this.fDist = new Array(this.fNpoints); + } - return this.drawGraph(gr, draw_opt, graphs.arr.length - indx).then(subp => { - if (subp) { - subp.setSecondaryId(this, `graphs_${indx}`); - this.painters.push(subp); - } + // the input point will be point zero. + this.fXN[0] = xx; + this.fYN[0] = yy; - return this.drawNextGraph(indx+1); - }); - } + // set the output value to the default value for now + thevalue = this.fZout; - /** @summary Draw multigraph object using painter instance - * @private */ - static async _drawMG(painter, opt) { - const d = new DrawOptions(opt); + // some counting + ntris_tried = 0; - painter._3d = d.check('3D'); - painter._auto = ''; // extra options for auto colors - ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) painter._auto += ' ' + f; }); + // no point in proceeding if xx or yy are silly + if ((xx > this.fXNmax) || (xx < this.fXNmin) || (yy > this.fYNmax) || (yy < this.fYNmin)) + return thevalue; - let hopt = ''; - if (d.check('FB') && painter._3d) hopt += 'FB'; // will be directly combined with LEGO - PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); + // check existing Delaunay triangles for a good one + for (it = 1; it <= this.fNdt; it++) { + p = this.fPTried[it - 1]; + n = this.fNTried[it - 1]; + m = this.fMTried[it - 1]; + // p, n and m form a previously found Delaunay triangle, does it + // enclose the point? + if (this.Enclose(p, n, m, 0)) { + // yes, we have the triangle + thevalue = this.InterpolateOnPlane(p, n, m, 0); + return thevalue; + } + } - painter._restopt = d.remain(); + // is this point inside the convex hull? + const shouldbein = this.InHull(0, -1); + if (!shouldbein) + return thevalue; - let promise = Promise.resolve(true); - if (d.check('A') || !painter.getMainPainter()) { - const mgraph = painter.getObject(), - histo = painter.scanGraphsRange(mgraph.fGraphs, mgraph.fHistogram, painter.getPadPainter()?.getRootPad(true)); + // it must be in a Delaunay triangle - find it... - promise = painter.drawAxisHist(histo, hopt).then(ap => { - ap.setSecondaryId(painter, 'hist'); // mark that axis painter generated from mg - painter.firstpainter = ap; - }); + // order mass points by distance in mass plane from desired point + for (it = 1; it <= this.fNpoints; it++) { + vxN = this.fXN[it]; + vyN = this.fYN[it]; + this.fDist[it - 1] = Math.sqrt((xx - vxN) * (xx - vxN) + (yy - vyN) * (yy - vyN)); } - return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawNextGraph(0); - }).then(() => { - const handler = new FunctionsHandler(painter, painter.getPadPainter(), painter.getObject().fFunctions, true); - return handler.drawNext(0); // returns painter - }); - } - - /** @summary Draw TMultiGraph object */ - static async draw(dom, mgraph, opt) { - return TMultiGraphPainter._drawMG(new TMultiGraphPainter(dom, mgraph), opt); - } + // sort array 'fDist' to find closest points + TMath_Sort(this.fNpoints, this.fDist, this.fOrder /* , false */); + for (it = 0; it < this.fNpoints; it++) + this.fOrder[it]++; -}; // class TMultiGraphPainter + // loop over triplets of close points to try to find a triangle that + // encloses the point. + for (k = 3; k <= this.fNpoints; k++) { + m = this.fOrder[k - 1]; + for (j = 2; j <= k - 1; j++) { + n = this.fOrder[j - 1]; + for (i = 1; i <= j - 1; i++) { + let skip_this_triangle = false; // used instead of goto L90 + p = this.fOrder[i - 1]; + if (ntris_tried > this.fMaxIter) { + // perhaps this point isn't in the hull after all + /* Warning("Interpolate", + "Abandoning the effort to find a Delaunay triangle (and thus interpolated z-value) for point %g %g" + ,xx,yy); */ + return thevalue; + } + ntris_tried++; + // check the points aren't colinear + d1 = Math.sqrt((this.fXN[p] - this.fXN[n]) ** 2 + (this.fYN[p] - this.fYN[n]) ** 2); + d2 = Math.sqrt((this.fXN[p] - this.fXN[m]) ** 2 + (this.fYN[p] - this.fYN[m]) ** 2); + d3 = Math.sqrt((this.fXN[n] - this.fXN[m]) ** 2 + (this.fYN[n] - this.fYN[m]) ** 2); + if ((d1 + d2 <= d3) || (d1 + d3 <= d2) || (d2 + d3 <= d1)) + continue; -class TMultiGraphPainter extends TMultiGraphPainter$2 { + // does the triangle enclose the point? + if (!this.Enclose(p, n, m, 0)) + continue; - /** @summary draw speical histogram for axis - * @return {Promise} when ready */ - async drawAxisHist(histo, hopt) { - return this._3d - ? TH2Painter.draw(this.getDom(), histo, 'LEGO' + hopt) - : TH1Painter$2.draw(this.getDom(), histo, hopt); - } + // is it a Delaunay triangle? (ie. are there any other points + // inside the circle that is defined by its vertices?) - /** @summary draw multigraph in 3D */ - async drawGraph(gr, opt, pos3d) { - if (this._3d) opt += 'pos3d_'+pos3d; - return TGraphPainter.draw(this.getDom(), gr, opt); - } + // test the triangle for Delaunay'ness - /** @summary Draw TMultiGraph object */ - static async draw(dom, mgraph, opt) { - return TMultiGraphPainter._drawMG(new TMultiGraphPainter(dom, mgraph), opt); - } + // loop over all other points testing each to see if it's + // inside the triangle's circle + ndegen = 0; + for (z = 1; z <= this.fNpoints; z++) { + if ((z === p) || (z === n) || (z === m)) + continue; // goto L50; + // An easy first check is to see if point z is inside the triangle + // (if it's in the triangle it's also in the circle) -} // class TMultiGraphPainter + // point z cannot be inside the triangle if it's further from (xx,yy) + // than the furthest pointing making up the triangle - test this + for (l = 1; l <= this.fNpoints; l++) { + if (this.fOrder[l - 1] === z) { + if ((l < i) || (l < j) || (l < k)) { + // point z is nearer to (xx,yy) than m, n or p - it could be in the + // triangle so call enclose to find out -var TMultiGraphPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TMultiGraphPainter: TMultiGraphPainter -}); + // if it is inside the triangle this can't be a Delaunay triangle + if (this.Enclose(p, n, m, z)) { + skip_this_triangle = true; + break; + } // goto L90; + } else { + // there's no way it could be in the triangle so there's no point + // calling enclose + break; // goto L1; + } + } + } -/** @summary Draw direct TVirtualX commands into SVG - * @private */ + if (skip_this_triangle) + break; -class TWebPaintingPainter extends ObjectPainter { + // is point z colinear with any pair of the triangle points? + // L1: + if (((this.fXN[p] - this.fXN[z]) * (this.fYN[p] - this.fYN[n])) === ((this.fYN[p] - this.fYN[z]) * (this.fXN[p] - this.fXN[n]))) { + // z is colinear with p and n + a = p; + b = n; + } else if (((this.fXN[p] - this.fXN[z]) * (this.fYN[p] - this.fYN[m])) === ((this.fYN[p] - this.fYN[z]) * (this.fXN[p] - this.fXN[m]))) { + // z is colinear with p and m + a = p; + b = m; + } else if (((this.fXN[n] - this.fXN[z]) * (this.fYN[n] - this.fYN[m])) === ((this.fYN[n] - this.fYN[z]) * (this.fXN[n] - this.fXN[m]))) { + // z is colinear with n and m + a = n; + b = m; + } else { + a = 0; + b = 0; + } + if (a) { + // point z is colinear with 2 of the triangle points, if it lies + // between them it's in the circle otherwise it's outside + if (this.fXN[a] !== this.fXN[b]) { + if (((this.fXN[z] - this.fXN[a]) * (this.fXN[z] - this.fXN[b])) < 0) { + skip_this_triangle = true; + break; + // goto L90; + } else if (((this.fXN[z] - this.fXN[a]) * (this.fXN[z] - this.fXN[b])) === 0) { + // At least two points are sitting on top of each other, we will + // treat these as one and not consider this a 'multiple points lying + // on a common circle' situation. It is a sign something could be + // wrong though, especially if the two coincident points have + // different fZ's. If they don't then this is harmless. + console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); + } + } else if (((this.fYN[z] - this.fYN[a]) * (this.fYN[z] - this.fYN[b])) < 0) { + skip_this_triangle = true; + break; + // goto L90; + } else if (((this.fYN[z] - this.fYN[a]) * (this.fYN[z] - this.fYN[b])) === 0) { + // At least two points are sitting on top of each other - see above. + console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); + } + // point is outside the circle, move to next point + continue; // goto L50; + } - /** @summary Update TWebPainting object */ - updateObject(obj) { - if (!this.matchObjectType(obj)) return false; - this.assignObject(obj); - return true; - } + if (skip_this_triangle) + break; - /** @summary draw TWebPainting object */ - async redraw() { - const obj = this.getObject(), func = this.getAxisToSvgFunc(); + /* Error("Interpolate", "Should not get to here"); */ + // may as well soldier on + // SL: initialize before try to find better values + f = m; + o1 = p; + o2 = n; - if (!obj?.fOper || !func) return; + // if point z were to look at the triangle, which point would it see + // lying between the other two? (we're going to form a quadrilateral + // from the points, and then demand certain properties of that + // quadrilateral) + dxz[0] = this.fXN[p] - this.fXN[z]; + dyz[0] = this.fYN[p] - this.fYN[z]; + dxz[1] = this.fXN[n] - this.fXN[z]; + dyz[1] = this.fYN[n] - this.fYN[z]; + dxz[2] = this.fXN[m] - this.fXN[z]; + dyz[2] = this.fYN[m] - this.fYN[z]; + for (l = 1; l <= 3; l++) { + dx1 = dxz[l - 1]; + dx2 = dxz[l % 3]; + dx3 = dxz[(l + 1) % 3]; + dy1 = dyz[l - 1]; + dy2 = dyz[l % 3]; + dy3 = dyz[(l + 1) % 3]; - let indx = 0, attr = {}, lastpath = null, lastkind = 'none', d = '', - oper, npoints, n; + // u et v are used only to know their sign. The previous + // code computed them with a division which was long and + // might be a division by 0. It is now replaced by a + // multiplication. + u = (dy3 * dx2 - dx3 * dy2) * (dy1 * dx2 - dx1 * dy2); + v = (dy3 * dx1 - dx3 * dy1) * (dy2 * dx1 - dx2 * dy1); - const arr = obj.fOper.split(';'), - check_attributes = kind => { - if (kind === lastkind) return; + if ((u >= 0) && (v >= 0)) { + // vector (dx3,dy3) is expressible as a sum of the other two vectors + // with positive coefficients -> i.e. it lies between the other two vectors + if (l === 1) { + f = m; + o1 = p; + o2 = n; + } else if (l === 2) { + f = p; + o1 = n; + o2 = m; + } else { + f = n; + o1 = m; + o2 = p; + } + break; // goto L2; + } + } + // L2: + // this is not a valid quadrilateral if the diagonals don't cross, + // check that points f and z lie on opposite side of the line o1-o2, + // this is true if the angle f-o1-z is greater than o2-o1-z and o2-o1-f + cfo1k = ((this.fXN[f] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])) / + Math.sqrt(((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])) * + ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1]))); + co2o1k = ((this.fXN[o2] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])) / + Math.sqrt(((this.fXN[o2] - this.fXN[o1]) * (this.fXN[o2] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[o2] - this.fYN[o1])) * + ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1]))); + co2o1f = ((this.fXN[o2] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])) / + Math.sqrt(((this.fXN[o2] - this.fXN[o1]) * (this.fXN[o2] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[o2] - this.fYN[o1])) * + ((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1]))); + if ((cfo1k > co2o1k) || (cfo1k > co2o1f)) { + // not a valid quadrilateral - point z is definitely outside the circle + continue; // goto L50; + } + // calculate the 2 internal angles of the quadrangle formed by joining + // points z and f to points o1 and o2, at z and f. If they sum to less + // than 180 degrees then z lies outside the circle + dko1 = Math.sqrt((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])); + dko2 = Math.sqrt((this.fXN[z] - this.fXN[o2]) * (this.fXN[z] - this.fXN[o2]) + (this.fYN[z] - this.fYN[o2]) * (this.fYN[z] - this.fYN[o2])); + dfo1 = Math.sqrt((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])); + dfo2 = Math.sqrt((this.fXN[f] - this.fXN[o2]) * (this.fXN[f] - this.fXN[o2]) + (this.fYN[f] - this.fYN[o2]) * (this.fYN[f] - this.fYN[o2])); + c1 = ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o2]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o2])) / dko1 / dko2; + c2 = ((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o2]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o2])) / dfo1 / dfo2; + sin_sum = c1 * Math.sqrt(1 - c2 * c2) + c2 * Math.sqrt(1 - c1 * c1); - if (lastpath) { - lastpath.attr('d', d); // flush previous - d = ''; lastpath = null; lastkind = 'none'; - } + // sin_sum doesn't always come out as zero when it should do. + if (sin_sum < -1e-6) { + // z is inside the circle, this is not a Delaunay triangle + skip_this_triangle = true; + break; + // goto L90; + } else if (Math.abs(sin_sum) <= 1.e-6) { + // point z lies on the circumference of the circle (within rounding errors) + // defined by the triangle, so there is potential for degeneracy in the + // triangle set (Delaunay triangulation does not give a unique way to split + // a polygon whose points lie on a circle into constituent triangles). Make + // a note of the additional point number. + ndegen++; + degen = z; + fdegen = f; + o1degen = o1; + o2degen = o2; + } - if (!kind) return; + // L50: continue; + } // end of for ( z = 1 ...) loop - lastkind = kind; - lastpath = this.draw_g.append('svg:path'); - switch (kind) { - case 'f': lastpath.call(this.fillatt.func); break; - case 'l': lastpath.call(this.lineatt.func).style('fill', 'none'); break; - case 'm': lastpath.call(this.markeratt.func); break; - } - }, read_attr = (str, names) => { - let lastp = 0; - const obj = { _typename: 'any' }; - for (let k = 0; k < names.length; ++k) { - const p = str.indexOf(':', lastp+1); - obj[names[k]] = parseInt(str.slice(lastp+1, (p > lastp) ? p : undefined)); - lastp = p; - } - return obj; - }, process = k => { - while (++k < arr.length) { - oper = arr[k][0]; - switch (oper) { - case 'z': - this.createAttLine({ attr: read_attr(arr[k], ['fLineColor', 'fLineStyle', 'fLineWidth']), force: true }); - check_attributes(); - continue; - case 'y': - this.createAttFill({ attr: read_attr(arr[k], ['fFillColor', 'fFillStyle']), force: true }); - check_attributes(); - continue; - case 'x': - this.createAttMarker({ attr: read_attr(arr[k], ['fMarkerColor', 'fMarkerStyle', 'fMarkerSize']), force: true }); - check_attributes(); - continue; - case 'o': - attr = read_attr(arr[k], ['fTextColor', 'fTextFont', 'fTextSize', 'fTextAlign', 'fTextAngle']); - if (attr.fTextSize < 0) attr.fTextSize *= -0.001; - check_attributes(); + if (skip_this_triangle) continue; - case 'r': - case 'b': { - check_attributes((oper === 'b') ? 'f' : 'l'); - const x1 = func.x(obj.fBuf[indx++]), - y1 = func.y(obj.fBuf[indx++]), - x2 = func.x(obj.fBuf[indx++]), - y2 = func.y(obj.fBuf[indx++]); + // This is a good triangle + if (ndegen > 0) { + // but is degenerate with at least one other, + // haven't figured out what to do if more than 4 points are involved + /* if (ndegen > 1) { + Error("Interpolate", + "More than 4 points lying on a circle. No decision making process formulated for triangulating this region in a non-arbitrary way %d %d %d %d", + p,n,m,degen); + return thevalue; + } */ - d += `M${x1},${y1}h${x2-x1}v${y2-y1}h${x1-x2}z`; + // we have a quadrilateral which can be split down either diagonal + // (d<->f or o1<->o2) to form valid Delaunay triangles. Choose diagonal + // with highest average z-value. Whichever we choose we will have + // verified two triangles as good and two as bad, only note the good ones + d = degen; + f = fdegen; + o1 = o1degen; + o2 = o2degen; + if ((this.fZ[o1 - 1] + this.fZ[o2 - 1]) > (this.fZ[d - 1] + this.fZ[f - 1])) { + // best diagonalisation of quadrilateral is current one, we have + // the triangle + t1 = p; + t2 = n; + t3 = m; + // file the good triangles + this.FileIt(p, n, m); + this.FileIt(d, o1, o2); + } else { + // use other diagonal to split quadrilateral, use triangle formed by + // point f, the degnerate point d and whichever of o1 and o2 create + // an enclosing triangle + t1 = f; + t2 = d; + if (this.Enclose(f, d, o1, 0)) + t3 = o1; + else + t3 = o2; - continue; + // file the good triangles + this.FileIt(f, d, o1); + this.FileIt(f, d, o2); + } + } else { + // this is a Delaunay triangle, file it + this.FileIt(p, n, m); + t1 = p; + t2 = n; + t3 = m; } - case 'l': - case 'f': { - check_attributes(oper); - - npoints = parseInt(arr[k].slice(1)); + // do the interpolation + thevalue = this.InterpolateOnPlane(t1, t2, t3, 0); + return thevalue; - for (n = 0; n < npoints; ++n) - d += `${(n>0)?'L':'M'}${func.x(obj.fBuf[indx++])},${func.y(obj.fBuf[indx++])}`; + // L90: continue; + } + } + } + if (shouldbein) + console.error(`Interpolate Point outside hull when expected inside: this point could be dodgy ${xx} ${yy} ${ntris_tried}`); + return thevalue; + } - if (oper === 'f') d += 'Z'; + /** @summary Defines the number of triangles tested for a Delaunay triangle + * @desc (number of iterations) before abandoning the search */ + SetMaxIter(n = 100000) { + this.fAllTri = false; + this.fMaxIter = n; + } - continue; - } + /** @summary Sets the histogram bin height for points lying outside the convex hull ie: + * @desc the bins in the margin. */ + SetMarginBinsContent(z) { + this.fZout = z; + } - case 'm': { - check_attributes(oper); + /** @summary Returns the X and Y graphs building a contour. + * @desc A contour level may consist in several parts not connected to each other. + * This function finds them and returns them in a graphs' list. */ + GetContourList(contour) { + if (!this.fNdt) + return null; - npoints = parseInt(arr[k].slice(1)); + let graph, // current graph + // Find all the segments making the contour + r21, r20, r10, p0, p1, p2, x0, y0, z0, x1, y1, z1, x2, y2, z2, + it, i0, i1, i2, nbSeg = 0, + // Allocate space to store the segments. They cannot be more than the + // number of triangles. + xs0c, ys0c, xs1c, ys1c; + + const t = [0, 0, 0], + xs0 = new Array(this.fNdt).fill(0), + ys0 = new Array(this.fNdt).fill(0), + xs1 = new Array(this.fNdt).fill(0), + ys1 = new Array(this.fNdt).fill(0); + + // Loop over all the triangles in order to find all the line segments + // making the contour. + + // old implementation + for (it = 0; it < this.fNdt; it++) { + t[0] = this.fPTried[it]; + t[1] = this.fNTried[it]; + t[2] = this.fMTried[it]; + p0 = t[0] - 1; + p1 = t[1] - 1; + p2 = t[2] - 1; + x0 = this.fX[p0]; + x2 = this.fX[p0]; + y0 = this.fY[p0]; + y2 = this.fY[p0]; + z0 = this.fZ[p0]; + z2 = this.fZ[p0]; + + // Order along Z axis the points (xi,yi,zi) where "i" belongs to {0,1,2} + // After this z0 < z1 < z2 + /* eslint-disable-next-line no-useless-assignment */ + i0 = i1 = i2 = 0; + if (this.fZ[p1] <= z0) { + z0 = this.fZ[p1]; + x0 = this.fX[p1]; + y0 = this.fY[p1]; + i0 = 1; + } + if (this.fZ[p1] > z2) { + z2 = this.fZ[p1]; + x2 = this.fX[p1]; + y2 = this.fY[p1]; + i2 = 1; + } + if (this.fZ[p2] <= z0) { + z0 = this.fZ[p2]; + x0 = this.fX[p2]; + y0 = this.fY[p2]; + i0 = 2; + } + if (this.fZ[p2] > z2) { + z2 = this.fZ[p2]; + x2 = this.fX[p2]; + y2 = this.fY[p2]; + i2 = 2; + } + if (i0 === 0 && i2 === 0) { + console.error('GetContourList: wrong vertices ordering'); + return null; + } - this.markeratt.resetPos(); - for (n = 0; n < npoints; ++n) - d += this.markeratt.create(func.x(obj.fBuf[indx++]), func.y(obj.fBuf[indx++])); + i1 = 3 - i2 - i0; - continue; - } + x1 = this.fX[t[i1] - 1]; + y1 = this.fY[t[i1] - 1]; + z1 = this.fZ[t[i1] - 1]; - case 'h': - case 't': { - if (attr.fTextSize) { - check_attributes(); + if (contour >= z0 && contour <= z2) { + r20 = (contour - z0) / (z2 - z0); + xs0c = r20 * (x2 - x0) + x0; + ys0c = r20 * (y2 - y0) + y0; + if (contour >= z1 && contour <= z2) { + r21 = (contour - z1) / (z2 - z1); + xs1c = r21 * (x2 - x1) + x1; + ys1c = r21 * (y2 - y1) + y1; + } else { + r10 = (contour - z0) / (z1 - z0); + xs1c = r10 * (x1 - x0) + x0; + ys1c = r10 * (y1 - y0) + y0; + } + // do not take the segments equal to a point + if (xs0c !== xs1c || ys0c !== ys1c) { + nbSeg++; + xs0[nbSeg - 1] = xs0c; + ys0[nbSeg - 1] = ys0c; + xs1[nbSeg - 1] = xs1c; + ys1[nbSeg - 1] = ys1c; + } + } + } - const height = (attr.fTextSize > 1) ? attr.fTextSize : this.getPadPainter().getPadHeight() * attr.fTextSize, - group = this.draw_g.append('svg:g'); - let angle = attr.fTextAngle, - txt = arr[k].slice(1); + const list = [], // list holding all the graphs + segUsed = new Array(this.fNdt).fill(false); - if (angle >= 360) angle -= Math.floor(angle/360) * 360; + // Find all the graphs making the contour. There is two kind of graphs, + // either they are "opened" or they are "closed" - this.startTextDrawing(attr.fTextFont, height, group); + // Find the opened graphs + let xc = 0, yc = 0, xnc = 0, ync = 0, + findNew, s0, s1, is, js; - if (oper === 'h') { - let res = ''; - for (n = 0; n < txt.length; n += 2) - res += String.fromCharCode(parseInt(txt.slice(n, n+2), 16)); - txt = res; - } + for (is = 0; is < nbSeg; is++) { + if (segUsed[is]) + continue; + s0 = s1 = false; - // todo - correct support of angle - this.drawText({ align: attr.fTextAlign, - x: func.x(obj.fBuf[indx++]), - y: func.y(obj.fBuf[indx++]), - rotate: -angle, - text: txt, - color: getColor(attr.fTextColor), - latex: 0, draw_g: group }); + // Find to which segment is is connected. It can be connected + // via 0, 1 or 2 vertices. + for (js = 0; js < nbSeg; js++) { + if (is === js) + continue; + if (xs0[is] === xs0[js] && ys0[is] === ys0[js]) + s0 = true; + if (xs0[is] === xs1[js] && ys0[is] === ys1[js]) + s0 = true; + if (xs1[is] === xs0[js] && ys1[is] === ys0[js]) + s1 = true; + if (xs1[is] === xs1[js] && ys1[is] === ys1[js]) + s1 = true; + } + + // Segment is is alone, not connected. It is stored in the + // list and the next segment is examined. + if (!s0 && !s1) { + graph = []; + graph.push(xs0[is], ys0[is]); + graph.push(xs1[is], ys1[is]); + segUsed[is] = true; + list.push(graph); + continue; + } - return this.finishTextDrawing(group).then(() => process(k)); - } - continue; + // Segment is is connected via 1 vertex only and can be considered + // as the starting point of an opened contour. + if (!s0 || !s1) { + // Find all the segments connected to segment is + graph = []; + if (s0) { + xc = xs0[is]; + yc = ys0[is]; + xnc = xs1[is]; + ync = ys1[is]; + } + if (s1) { + xc = xs1[is]; + yc = ys1[is]; + xnc = xs0[is]; + ync = ys0[is]; + } + graph.push(xnc, ync); + segUsed[is] = true; + js = 0; + + while (true) { + findNew = false; + while (js < nbSeg && segUsed[js]) + js++; + + if (xc === xs0[js] && yc === ys0[js]) { + xc = xs1[js]; + yc = ys1[js]; + findNew = true; + } else if (xc === xs1[js] && yc === ys1[js]) { + xc = xs0[js]; + yc = ys0[js]; + findNew = true; } - - default: - console.log(`unsupported operation ${oper}`); + if (findNew) { + segUsed[js] = true; + graph.push(xc, yc); + js = 0; + } else if (++js >= nbSeg) + break; } + list.push(graph); } + } - return Promise.resolve(true); - }; - this.createG(); + // Find the closed graphs. At this point all the remaining graphs + // are closed. Any segment can be used to start the search. + for (is = 0; is < nbSeg; is++) { + if (segUsed[is]) + continue; - return process(-1).then(() => { check_attributes(); return this; }); - } + // Find all the segments connected to segment is + graph = []; + segUsed[is] = true; + xc = xs0[is]; + yc = ys0[is]; + js = 0; + graph.push(xc, yc); + while (true) { + while (js < nbSeg && segUsed[js]) + js++; + findNew = false; + if (xc === xs0[js] && yc === ys0[js]) { + xc = xs1[js]; + yc = ys1[js]; + findNew = true; + } else if (xc === xs1[js] && yc === ys1[js]) { + xc = xs0[js]; + yc = ys0[js]; + findNew = true; + } + if (findNew) { + segUsed[js] = true; + graph.push(xc, yc); + js = 0; + } else if (++js >= nbSeg) + break; + } + graph.push(xs0[is], ys0[is]); + list.push(graph); + } - static async draw(dom, obj) { - const painter = new TWebPaintingPainter(dom, obj); - painter.addToPadPrimitives(); - return painter.redraw(); + return list; } -} // class TWebPaintingPainter +} // class TGraphDelaunay -var TWebPaintingPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TWebPaintingPainter: TWebPaintingPainter -}); +/** @summary Function handles tooltips in the mesh */ +function _graph2DTooltip(intersect) { + let indx = Math.floor(intersect.index / this.nvertex); + if ((indx < 0) || (indx >= this.index.length)) + return null; + const sqr = v => v * v; -/** - * @summary Painter for TF2 object - * - * @private - */ + indx = this.index[indx]; -class TF2Painter extends TH2Painter { + const fp = this.fp, gr = this.graph; + let grx = fp.grx(gr.fX[indx]), + gry = fp.gry(gr.fY[indx]), + grz = fp.grz(gr.fZ[indx]); - /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + if (this.check_next && indx + 1 < gr.fX.length) { + const d = intersect.point, + grx1 = fp.grx(gr.fX[indx + 1]), + gry1 = fp.gry(gr.fY[indx + 1]), + grz1 = fp.grz(gr.fZ[indx + 1]); + if (sqr(d.x - grx1) + sqr(d.y - gry1) + sqr(d.z - grz1) < sqr(d.x - grx) + sqr(d.y - gry) + sqr(d.z - grz)) { + grx = grx1; + gry = gry1; + grz = grz1; + indx++; + } + } - /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF2; } + return { + x1: grx - this.scale0, + x2: grx + this.scale0, + y1: gry - this.scale0, + y2: gry + this.scale0, + z1: grz - this.scale0, + z2: grz + this.scale0, + color: this.tip_color, + lines: [this.tip_name, + 'pnt: ' + indx, + 'x: ' + fp.axisAsText('x', gr.fX[indx]), + 'y: ' + fp.axisAsText('y', gr.fY[indx]), + 'z: ' + fp.axisAsText('z', gr.fZ[indx])] + }; +} - /** @summary Returns true while function is drawn */ - isTF1() { return true; } - /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } +/** + * @summary Painter for TGraph2D classes + * @private + */ - /** @summary Update histogram */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; - delete obj.evalPar; - const histo = this.getHisto(); +class TGraph2DPainter extends ObjectPainter { - if (this.webcanv_hist) { - const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); - } + #redraw_hist; // painter to redraw histogram + #delaunay; // used delaunay instance - this.$func = obj; - this.createTF2Histogram(obj, histo); - this.scanContent(); - return true; - } + /** @summary Decode options string */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + gr2d = this.getObject(); - /** @summary Redraw TF2 - * @private */ - redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'zoom')) { - this.createTF2Histogram(this.$func, this.getHisto()); - this.scanContent(); - } + if (d.check('FILL_', 'color') && gr2d) + gr2d.fFillColor = d.color; - return super.redraw(reason); - } + if (d.check('LINE_', 'color') && gr2d) + gr2d.fLineColor = d.color; - /** @summary Create histogram for TF2 drawing - * @private */ - createTF2Histogram(func, hist) { - let nsave = func.fSave.length - 6; - if ((nsave > 0) && (nsave !== (func.fSave[nsave+4]+1) * (func.fSave[nsave+5]+1))) - nsave = 0; + d.check('SAME'); - this._use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this.use_saved > 1)); + let Triangles = 0, Contour = 0; - const fp = this.getFramePainter(), - pad = this.getPadPainter()?.getRootPad(true), - logx = pad?.fLogx, logy = pad?.fLogy, - gr = fp?.getGrFuncs(this.second_x, this.second_y); - let xmin = func.fXmin, xmax = func.fXmax, - ymin = func.fYmin, ymax = func.fYmax, - npx = Math.max(func.fNpx, 20), - npy = Math.max(func.fNpy, 20); + if (d.check('CONT5')) + Contour = 15; + else if (d.check('TRI1')) + Triangles = 11; // wire-frame and colors + else if (d.check('TRI2')) + Triangles = 10; // only color triangles + else if (d.check('TRIW')) + Triangles = 1; + else if (d.check('TRI')) + Triangles = 2; - if (gr?.zoom_xmin !== gr?.zoom_xmax) { - const dx = (xmax - xmin) / npx; - if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) - xmin = Math.max(xmin, gr.zoom_xmin - dx); - if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) - xmax = Math.min(xmax, gr.zoom_xmax + dx); - } + const res = this.setOptions({ + Triangles, + Contour, + Line: d.check('LINE'), + Error: d.check('ERR') && (this.matchObjectType(clTGraph2DErrors) || this.matchObjectType(clTGraph2DAsymmErrors)) + }); - if (gr?.zoom_ymin !== gr?.zoom_ymax) { - const dy = (ymax - ymin) / npy; - if ((ymin < gr.zoom_ymin) && (gr.zoom_ymin < ymax)) - ymin = Math.max(ymin, gr.zoom_ymin - dy); - if ((ymin < gr.zoom_ymax) && (gr.zoom_ymax < ymax)) - ymax = Math.min(ymax, gr.zoom_ymax + dy); + if (d.check('P0COL')) + res.Color = res.Circles = res.Markers = true; + else { + res.Color = d.check('COL'); + res.Circles = d.check('P0'); + res.Markers = d.check('P'); } - const ensureBins = (nx, ny) => { - if (hist.fNcells !== (nx + 2) * (ny + 2)) { - hist.fNcells = (nx + 2) * (ny + 2); - hist.fArray = new Float32Array(hist.fNcells); - } - hist.fArray.fill(0); - hist.fXaxis.fNbins = nx; - hist.fXaxis.fXbins = []; - hist.fYaxis.fNbins = ny; - hist.fYaxis.fXbins = []; + if (!res.Markers) + res.Color = false; + + if (res.Color || res.Triangles >= 10 || res.Contour) + res.Zscale = d.check('Z'); + + res.isAny = function() { + return this.Markers || this.Error || this.Circles || this.Line || this.Triangles || res.Contour; }; - delete this._fail_eval; + if (res.Contour) + res.Axis = ''; + else if (res.isAny()) { + res.Axis = 'lego2'; + if (res.Zscale) + res.Axis += 'z'; + } else + res.Axis = opt; - if (!this._use_saved_points) { - let iserror = false; + this.storeDrawOpt(opt); + } - if (!func.evalPar && !proivdeEvalPar(func)) - iserror = true; + /** @summary Create histogram for axes drawing */ + createHistogram() { + const gr = this.getObject(), + o = this.getOptions(), + asymm = this.matchObjectType(clTGraph2DAsymmErrors); + let xmin = gr.fX[0], xmax = xmin, + ymin = gr.fY[0], ymax = ymin, + zmin = gr.fZ[0], zmax = zmin; - ensureBins(npx, npy); - hist.fXaxis.fXmin = xmin; - hist.fXaxis.fXmax = xmax; - hist.fYaxis.fXmin = ymin; - hist.fYaxis.fXmax = ymax; + for (let p = 0; p < gr.fNpoints; ++p) { + const x = gr.fX[p], y = gr.fY[p], z = gr.fZ[p]; - if (logx) - produceTAxisLogScale(hist.fXaxis, npx, xmin, xmax); - if (logy) - produceTAxisLogScale(hist.fYaxis, npy, ymin, ymax); + if (o.Error) { + xmin = Math.min(xmin, x - (asymm ? gr.fEXlow[p] : gr.fEX[p])); + xmax = Math.max(xmax, x + (asymm ? gr.fEXhigh[p] : gr.fEX[p])); + ymin = Math.min(ymin, y - (asymm ? gr.fEYlow[p] : gr.fEY[p])); + ymax = Math.max(ymax, y + (asymm ? gr.fEYhigh[p] : gr.fEY[p])); + zmin = Math.min(zmin, z - (asymm ? gr.fEZlow[p] : gr.fEZ[p])); + zmax = Math.max(zmax, z + (asymm ? gr.fEZhigh[p] : gr.fEZ[p])); + } else { + xmin = Math.min(xmin, x); + xmax = Math.max(xmax, x); + ymin = Math.min(ymin, y); + ymax = Math.max(ymax, y); + zmin = Math.min(zmin, z); + zmax = Math.max(zmax, z); + } + } - for (let j = 0; (j < npy) && !iserror; ++j) { - for (let i = 0; (i < npx) && !iserror; ++i) { - const x = hist.fXaxis.GetBinCenter(i+1), - y = hist.fYaxis.GetBinCenter(j+1); - let z = 0; + function calc_delta(min, max, margin) { + if (min < max) + return margin * (max - min); + return Math.abs(min) < 1e5 ? 0.02 : 0.02 * Math.abs(min); + } + const dx = calc_delta(xmin, xmax, gr.fMargin), + dy = calc_delta(ymin, ymax, gr.fMargin), + dz = calc_delta(zmin, zmax, 0); + let uxmin = xmin - dx, uxmax = xmax + dx, + uymin = ymin - dy, uymax = ymax + dy, + uzmin = zmin - dz, uzmax = zmax + dz; - try { - z = func.evalPar(x, y); - } catch { - iserror = true; - } + if ((uxmin < 0) && (xmin >= 0)) + uxmin = xmin * 0.98; + if ((uxmax > 0) && (xmax <= 0)) + uxmax = 0; - if (!iserror) - hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); - } - } + if ((uymin < 0) && (ymin >= 0)) + uymin = ymin * 0.98; + if ((uymax > 0) && (ymax <= 0)) + uymax = 0; - if (iserror) - this._fail_eval = true; + if ((uzmin < 0) && (zmin >= 0)) + uzmin = zmin * 0.98; + if ((uzmax > 0) && (zmax <= 0)) + uzmax = 0; - if (iserror && (nsave > 6)) - this._use_saved_points = true; - } - - if (this._use_saved_points) { - npx = Math.round(func.fSave[nsave+4]); - npy = Math.round(func.fSave[nsave+5]); - const xmin = func.fSave[nsave], xmax = func.fSave[nsave+1], - ymin = func.fSave[nsave+2], ymax = func.fSave[nsave+3], - dx = (xmax - xmin) / npx, - dy = (ymax - ymin) / npy; - function getSave(x, y) { - if (x < xmin || x > xmax) return 0; - if (dx <= 0) return 0; - if (y < ymin || y > ymax) return 0; - if (dy <= 0) return 0; - const ibin = Math.min(npx-1, Math.floor((x-xmin)/dx)), - jbin = Math.min(npy-1, Math.floor((y-ymin)/dy)), - xlow = xmin + ibin*dx, - ylow = ymin + jbin*dy, - t = (x-xlow)/dx, - u = (y-ylow)/dy, - k1 = jbin*(npx+1) + ibin, - k2 = jbin*(npx+1) + ibin +1, - k3 = (jbin+1)*(npx+1) + ibin +1, - k4 = (jbin+1)*(npx+1) + ibin; - return (1-t)*(1-u)*func.fSave[k1] +t*(1-u)*func.fSave[k2] +t*u*func.fSave[k3] + (1-t)*u*func.fSave[k4]; - } + const graph = this.getObject(); - ensureBins(func.fNpx, func.fNpy); - hist.fXaxis.fXmin = func.fXmin; - hist.fXaxis.fXmax = func.fXmax; - hist.fYaxis.fXmin = func.fYmin; - hist.fYaxis.fXmax = func.fYmax; + if (graph.fMinimum !== kNoZoom) + uzmin = graph.fMinimum; + if (graph.fMaximum !== kNoZoom) + uzmax = graph.fMaximum; - for (let j = 0; j < func.fNpy; ++j) { - const y = hist.fYaxis.GetBinCenter(j + 1); - for (let i = 0; i < func.fNpx; ++i) { - const x = hist.fXaxis.GetBinCenter(i + 1), - z = getSave(x, y); - hist.setBinContent(hist.getBin(i+1, j+1), Number.isFinite(z) ? z : 0); + const histo = createHistogram(clTH2D, graph.fNpx, graph.fNpy); + histo.fName = graph.fName + '_h'; + setHistogramTitle(histo, graph.fTitle); + histo.fXaxis.fXmin = uxmin; + histo.fXaxis.fXmax = uxmax; + histo.fYaxis.fXmin = uymin; + histo.fYaxis.fXmax = uymax; + histo.fZaxis.fXmin = uzmin; + histo.fZaxis.fXmax = uzmax; + histo.fMinimum = uzmin; + histo.fMaximum = uzmax; + histo.fBits |= kNoStats; + + if (!o.isAny()) { + const dulaunay = this.buildDelaunay(graph); + if (dulaunay) { + for (let i = 0; i < graph.fNpx; ++i) { + const xx = uxmin + (i + 0.5) / graph.fNpx * (uxmax - uxmin); + for (let j = 0; j < graph.fNpy; ++j) { + const yy = uymin + (j + 0.5) / graph.fNpy * (uymax - uymin), + zz = dulaunay.ComputeZ(xx, yy); + histo.fArray[histo.getBin(i + 1, j + 1)] = zz; + } } } } - hist.fName = 'Func'; - setHistogramTitle(hist, func.fTitle); - hist.fMinimum = func.fMinimum; - hist.fMaximum = func.fMaximum; - // fHistogram->SetContour(fContour.fN, levels); - hist.fLineColor = func.fLineColor; - hist.fLineStyle = func.fLineStyle; - hist.fLineWidth = func.fLineWidth; - hist.fFillColor = func.fFillColor; - hist.fFillStyle = func.fFillStyle; - hist.fMarkerColor = func.fMarkerColor; - hist.fMarkerStyle = func.fMarkerStyle; - hist.fMarkerSize = func.fMarkerSize; - hist.fBits |= kNoStats; - - return hist; + return histo; } - /** @summary Extract function ranges */ - extractAxesProperties(ndim) { - super.extractAxesProperties(ndim); - - const func = this.$func, nsave = func?.fSave.length ?? 0; - - if (nsave > 6 && this._use_saved_points) { - this.xmin = Math.min(this.xmin, func.fSave[nsave-6]); - this.xmax = Math.max(this.xmax, func.fSave[nsave-5]); - this.ymin = Math.min(this.ymin, func.fSave[nsave-4]); - this.ymax = Math.max(this.ymax, func.fSave[nsave-3]); - } - if (func) { - this.xmin = Math.min(this.xmin, func.fXmin); - this.xmax = Math.max(this.xmax, func.fXmax); - this.ymin = Math.min(this.ymin, func.fYmin); - this.ymax = Math.max(this.ymax, func.fYmax); + buildDelaunay(graph) { + if (!this.#delaunay) { + this.#delaunay = new TGraphDelaunay(graph); + this.#delaunay.FindAllTriangles(); + if (!this.#delaunay.fNdt) + this.#delaunay = undefined; } + return this.#delaunay; } - /** @summary retrurn tooltips for TF2 */ - getTF2Tooltips(pnt) { - const lines = [this.getObjectHint()], - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); - - if (!funcs || !isFunc(this.$func?.evalPar)) { - lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); - return lines; - } + drawTriangles(fp, graph, levels, palette) { + const dulaunay = this.buildDelaunay(graph); + if (!dulaunay) + return; - const x = funcs.revertAxis('x', pnt.x), - y = funcs.revertAxis('y', pnt.y); - let z = 0, iserror = false; + const main_grz = !fp.logz ? fp.grz : value => { return (value < fp.scale_zmin) ? -0.1 : fp.grz(value); }, + o = this.getOptions(), + plain_mode = o.Triangles === 2, + do_faces = (o.Triangles >= 10) || plain_mode, + do_lines = (o.Triangles % 10 === 1) || (plain_mode && (graph.fLineColor !== graph.fFillColor)), + triangles = new Triangles3DHandler(levels, main_grz, 0, 2 * fp.size_z3d, do_lines); - try { - z = this.$func.evalPar(x, y); - } catch { - iserror = true; - } + for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { + triangles.createBuffers(); - lines.push('x = ' + funcs.axisAsText('x', x), - 'y = ' + funcs.axisAsText('y', y), - 'value = ' + (iserror ? '' : floatToString(z, gStyle.fStatFormat))); - return lines; - } + for (let t = 0; t < dulaunay.fNdt; ++t) { + const points = [dulaunay.fPTried[t], dulaunay.fNTried[t], dulaunay.fMTried[t]], + coord = []; + let use_triangle = true; + for (let i = 0; i < 3; ++i) { + const pnt = points[i] - 1; + coord.push(fp.grx(graph.fX[pnt]), fp.gry(graph.fY[pnt]), main_grz(graph.fZ[pnt])); - /** @summary process tooltip event for TF2 object */ - processTooltipEvent(pnt) { - if (this._use_saved_points) - return super.processTooltipEvent(pnt); + if ((graph.fX[pnt] < fp.scale_xmin) || (graph.fX[pnt] > fp.scale_xmax) || + (graph.fY[pnt] < fp.scale_ymin) || (graph.fY[pnt] > fp.scale_ymax)) + use_triangle = false; + } - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + if (do_faces && use_triangle) + triangles.addMainTriangle(...coord); - if (!this.draw_g || !pnt) { - ttrect?.remove(); - return null; + if (do_lines && use_triangle) { + triangles.addLineSegment(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]); + triangles.addLineSegment(coord[3], coord[4], coord[5], coord[6], coord[7], coord[8]); + triangles.addLineSegment(coord[6], coord[7], coord[8], coord[0], coord[1], coord[2]); + } + } } - const res = { name: this.$func?.fName, title: this.$func?.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getTF2Tooltips(pnt), exact: true, menu: true }; - - if (pnt.disabled) - ttrect.remove(); - else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .style('fill', 'none') - .attr('r', (this.lineatt?.width ?? 1) + 4); - } + triangles.callFuncs((lvl, pos) => { + const geometry = createLegoGeom(this.getMainPainter(), pos, null, 100, 100), + color = plain_mode ? this.getColor(graph.fFillColor) : palette.calcColor(lvl, levels.length), + material = new THREE.MeshBasicMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); - ttrect.attr('cx', pnt.x) - .attr('cy', pnt.y) - .call(this.lineatt?.func); - } + fp.add3DMesh(mesh, this); - return res; + mesh.painter = this; // to let use it with context menu + }, (_isgrid, lpos) => { + const lcolor = this.getColor(graph.fLineColor), + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), + linemesh = createLineSegments(convertLegoBuf(this.getMainPainter(), lpos, 100, 100), material); + fp.add3DMesh(linemesh, this); + }); } - /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed - * @private */ - fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; - } + /** @summary Update TGraph2D object */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; - /** @summary draw TF2 object */ - static async draw(dom, tf2, opt) { - const web = scanTF1Options(opt); - opt = web.opt; - delete web.opt; + const o = this.getOptions(); - const d = new DrawOptions(opt); - if (d.empty()) - opt = 'cont3'; - else if (d.opt === 'SAME') - opt = 'cont2 same'; + if (opt && (opt !== o.original)) + this.decodeOptions(opt, obj); - // workaround for old waves.C - const o2 = isStr(opt) ? opt.toUpperCase() : ''; - if (o2 === 'SAMECOLORZ' || o2 === 'SAMECOLOR' || o2 === 'SAMECOLZ') - opt = 'samecol'; + Object.assign(this.getObject(), obj); - if ((opt.indexOf('same') === 0) || (opt.indexOf('SAME') === 0)) { - if (!getElementMainPainter(dom)) - opt = 'A_ADJUST_FRAME_' + opt.slice(4); - } + this.#delaunay = undefined; // rebuild triangles - let hist; + this.#redraw_hist = undefined; - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH2F); + // if our own histogram was used as axis drawing, we need update histogram as well + if (this.axes_draw) { + const hist_painter = this.getMainPainter(); + hist_painter?.updateObject(this.createHistogram(), o.Axis); + this.#redraw_hist = hist_painter; } - if (!hist) { - hist = createHistogram(clTH2F, 20, 20); - hist.fBits |= kNoStats; - } + return true; + } - const painter = new TF2Painter(dom, hist); + /** @summary Redraw TGraph2D object + * @desc Update histogram drawing if necessary + * @return {Promise} for drawing ready */ + async redraw() { + const promise = getPromise(this.#redraw_hist?.redraw()); - painter.$func = tf2; - Object.assign(painter, web); - painter.createTF2Histogram(tf2, hist); - return THistPainter._drawHist(painter, opt); + this.#redraw_hist = undefined; + + return promise.then(() => this.drawGraph2D()); } -} // class TF2Painter + async drawContour(fp, main, graph) { + const dulaunay = this.buildDelaunay(graph); + if (!dulaunay) + return this; -var TF2Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TF2Painter: TF2Painter -}); + const cntr = main.getContour(), + palette = main.getHistPalette(), + levels = cntr.getLevels(), + funcs = fp.getGrFuncs(), + g = this.createG(true); -function findZValue(arrz, arrv, cross = 0) { - for (let i = arrz.length - 2; i >= 0; --i) { - const v1 = arrv[i], v2 = arrv[i + 1], - z1 = arrz[i], z2 = arrz[i + 1]; - if (v1 === cross) return z1; - if (v2 === cross) return z2; - if ((v1 < cross) !== (v2 < cross)) - return z1 + (cross - v1) / (v2 - v1) * (z2 - z1); - } + this.createAttLine({ attr: graph, nocolor: true }); - return arrz[0] - 1; -} + for (let k = 0; k < levels.length; ++k) { + const lst = dulaunay.GetContourList(levels[k]), + color = cntr.getPaletteColor(palette, levels[k]); + let path = ''; + for (let i = 0; i < lst.length; ++i) { + const gr = lst[i], arr = []; + for (let n = 0; n < gr.length; n += 2) + arr.push({ grx: funcs.grx(gr[n]), gry: funcs.gry(gr[n + 1]) }); + path += buildSvgCurve(arr, { cmd: 'M', line: true }); + } + this.lineatt.color = color; -/** - * @summary Painter for TF3 object - * - * @private - */ + g.append('svg:path') + .attr('d', path) + .style('fill', 'none') + .call(this.lineatt.func); + } -class TF3Painter extends TH2Painter { + return this; + } - /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + /** @summary Actual drawing of TGraph2D object + * @return {Promise} for drawing ready */ + async drawGraph2D() { + const fp = this.getFramePainter(), + main = this.getMainPainter(), + graph = this.getObject(), + o = this.getOptions(); - /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF3; } + if (!graph || !main || !fp) + return this; - /** @summary Returns true while function is drawn */ - isTF1() { return true; } + if (o.Contour) + return this.drawContour(fp, main, graph); - /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } + if (!fp.mode3d) + return this; - /** @summary Update histogram */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; - delete obj.evalPar; - const histo = this.getHisto(); + fp.remove3DMeshes(this); - if (this.webcanv_hist) { - const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); + if (!o.isAny()) { + // no need to draw smoothing if histogram content was drawn + if (main.draw_content) + return this; + if ((graph.fMarkerSize === 1) && (graph.fMarkerStyle === 1)) + o.Circles = true; + else + o.Markers = true; } - this.$func = obj; - this.createTF3Histogram(obj, histo); - this.scanContent(); - return true; - } + const countSelected = (zmin, zmax) => { + let cnt = 0; + for (let i = 0; i < graph.fNpoints; ++i) { + if ((graph.fX[i] >= fp.scale_xmin) && (graph.fX[i] <= fp.scale_xmax) && + (graph.fY[i] >= fp.scale_ymin) && (graph.fY[i] <= fp.scale_ymax) && + (graph.fZ[i] >= zmin) && (graph.fZ[i] < zmax)) ++cnt; + } + return cnt; + }; - /** @summary Redraw TF2 - * @private */ - redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'logy' || reason === 'zoom')) { - this.createTF3Histogram(this.$func, this.getHisto()); - this.scanContent(); + // try to define scale-down factor + let step = 1; + if ((settings.OptimizeDraw > 0) && !fp.webgl) { + const numselected = countSelected(fp.scale_zmin, fp.scale_zmax), + sizelimit = 50000; + + if (numselected > sizelimit) + step = Math.max(2, Math.floor(numselected / sizelimit)); } - return super.redraw(reason); - } + const markeratt = this.createAttMarker({ attr: graph, std: false }), + promises = []; + let palette = null, + levels = [fp.scale_zmin, fp.scale_zmax], + scale = fp.size_x3d / 100 * markeratt.getFullSize(); - /** @summary Create histogram for TF3 drawing - * @private */ - createTF3Histogram(func, hist) { - const nsave = func.fSave.length - 9; + if (o.Circles) + scale = 0.06 * fp.size_x3d; - this._use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this.use_saved > 1)); + if (fp.usesvg) + scale *= 0.3; - const fp = this.getFramePainter(), - pad = this.getPadPainter()?.getRootPad(true), - logx = pad?.fLogx, logy = pad?.fLogy, - gr = fp?.getGrFuncs(this.second_x, this.second_y); - let xmin = func.fXmin, xmax = func.fXmax, - ymin = func.fYmin, ymax = func.fYmax, - zmin = func.fZmin, zmax = func.fZmax, - npx = Math.max(func.fNpx, 20), - npy = Math.max(func.fNpy, 20), - npz = Math.max(func.fNpz, 20); + const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(); - if (gr?.zoom_xmin !== gr?.zoom_xmax) { - const dx = (xmax - xmin) / npx; - if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) - xmin = Math.max(xmin, gr.zoom_xmin - dx); - if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) - xmax = Math.min(xmax, gr.zoom_xmax + dx); - } + if ((fw > 10) && (fh > 10)) + scale *= 7 * Math.max(fp.size_x3d / fw, fp.size_z3d / fh); - if (gr?.zoom_ymin !== gr?.zoom_ymax) { - const dy = (ymax - ymin) / npy; - if ((ymin < gr.zoom_ymin) && (gr.zoom_ymin < ymax)) - ymin = Math.max(ymin, gr.zoom_ymin - dy); - if ((ymin < gr.zoom_ymax) && (gr.zoom_ymax < ymax)) - ymax = Math.min(ymax, gr.zoom_ymax + dy); + if (o.Color || (o.Triangles >= 10)) { + levels = main.getContourLevels(true); + palette = main.getHistPalette(); } - if (gr?.zoom_zmin !== gr?.zoom_zmax) { - // no need for dz here - TH2 is not binned over Z axis - if ((zmin < gr.zoom_zmin) && (gr.zoom_zmin < zmax)) - zmin = gr.zoom_zmin; - if ((zmin < gr.zoom_zmax) && (gr.zoom_zmax < zmax)) - zmax = gr.zoom_zmax; - } + if (o.Triangles) + this.drawTriangles(fp, graph, levels, palette); - const ensureBins = (nx, ny) => { - if (hist.fNcells !== (nx + 2) * (ny + 2)) { - hist.fNcells = (nx + 2) * (ny + 2); - hist.fArray = new Float32Array(hist.fNcells); - } - hist.fArray.fill(0); - hist.fXaxis.fNbins = nx; - hist.fXaxis.fXbins = []; - hist.fYaxis.fNbins = ny; - hist.fYaxis.fXbins = []; - hist.fXaxis.fXmin = xmin; - hist.fXaxis.fXmax = xmax; - hist.fYaxis.fXmin = ymin; - hist.fYaxis.fXmax = ymax; - hist.fMinimum = zmin; - hist.fMaximum = zmax; - }; + for (let lvl = 0; lvl < levels.length - 1; ++lvl) { + const lvl_zmin = Math.max(levels[lvl], fp.scale_zmin), + lvl_zmax = Math.min(levels[lvl + 1], fp.scale_zmax); - delete this._fail_eval; + if (lvl_zmin >= lvl_zmax) + continue; - if (!this._use_saved_points) { - let iserror = false; + const size = Math.floor(countSelected(lvl_zmin, lvl_zmax) / step), + index = new Int32Array(size), + pnts = o.Markers || o.Circles ? new PointsCreator(size, fp.webgl, scale / 3) : null, + err = o.Error ? new Float32Array(size * 6 * 3) : null, + asymm = err && this.matchObjectType(clTGraph2DAsymmErrors), + line = o.Line ? new Float32Array((size - 1) * 6) : null; - if (!func.evalPar && !proivdeEvalPar(func)) - iserror = true; + let select = 0, icnt = 0, ierr = 0, iline = 0; - ensureBins(npx, npy); + for (let i = 0; i < graph.fNpoints; ++i) { + if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) || + (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) || + (graph.fZ[i] < lvl_zmin) || (graph.fZ[i] >= lvl_zmax)) + continue; - if (logx) - produceTAxisLogScale(hist.fXaxis, npx, xmin, xmax); - if (logy) - produceTAxisLogScale(hist.fYaxis, npy, ymin, ymax); + if (step > 1) { + select = (select + 1) % step; + if (select) + continue; + } - const arrv = new Array(npz), arrz = new Array(npz); - for (let k = 0; k < npz; ++k) - arrz[k] = zmin + k / (npz - 1) * (zmax - zmin); + index[icnt++] = i; // remember point index for tooltip - for (let j = 0; (j < npy) && !iserror; ++j) { - for (let i = 0; (i < npx) && !iserror; ++i) { - const x = hist.fXaxis.GetBinCenter(i+1), - y = hist.fYaxis.GetBinCenter(j+1); - let z = 0; + const x = fp.grx(graph.fX[i]), y = fp.gry(graph.fY[i]), z = fp.grz(graph.fZ[i]); - try { - for (let k = 0; k < npz; ++k) - arrv[k] = func.evalPar(x, y, arrz[k]); + pnts?.addPoint(x, y, z); - z = findZValue(arrz, arrv); - } catch { - iserror = true; - } + if (err) { + err[ierr] = fp.grx(graph.fX[i] - (asymm ? graph.fEXlow[i] : graph.fEX[i])); + err[ierr + 1] = y; + err[ierr + 2] = z; + err[ierr + 3] = fp.grx(graph.fX[i] + (asymm ? graph.fEXhigh[i] : graph.fEX[i])); + err[ierr + 4] = y; + err[ierr + 5] = z; + ierr += 6; + err[ierr] = x; + err[ierr + 1] = fp.gry(graph.fY[i] - (asymm ? graph.fEYlow[i] : graph.fEY[i])); + err[ierr + 2] = z; + err[ierr + 3] = x; + err[ierr + 4] = fp.gry(graph.fY[i] + (asymm ? graph.fEYhigh[i] : graph.fEY[i])); + err[ierr + 5] = z; + ierr += 6; + err[ierr] = x; + err[ierr + 1] = y; + err[ierr + 2] = fp.grz(graph.fZ[i] - (asymm ? graph.fEZlow[i] : graph.fEZ[i])); + err[ierr + 3] = x; + err[ierr + 4] = y; + err[ierr + 5] = fp.grz(graph.fZ[i] + (asymm ? graph.fEZhigh[i] : graph.fEZ[i])); + ierr += 6; + } - if (!iserror) - hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); + if (line) { + if (iline >= 6) { + line[iline] = line[iline - 3]; + line[iline + 1] = line[iline - 2]; + line[iline + 2] = line[iline - 1]; + iline += 3; + } + line[iline] = x; + line[iline + 1] = y; + line[iline + 2] = z; + iline += 3; } } - if (iserror) - this._fail_eval = true; + if (line && (iline > 3) && (line.length === iline)) { + const lcolor = this.getColor(graph.fLineColor), + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), + linemesh = createLineSegments(line, material); + fp.add3DMesh(linemesh, this); - if (iserror && (nsave > 0)) - this._use_saved_points = true; - } - - if (this._use_saved_points) { - xmin = func.fSave[nsave]; xmax = func.fSave[nsave+1]; - ymin = func.fSave[nsave+2]; ymax = func.fSave[nsave+3]; - zmin = func.fSave[nsave+4]; zmax = func.fSave[nsave+5]; - npx = Math.round(func.fSave[nsave+6]); - npy = Math.round(func.fSave[nsave+7]); - npz = Math.round(func.fSave[nsave+8]); - // dx = (xmax - xmin) / npx, - // dy = (ymax - ymin) / npy, - const dz = (zmax - zmin) / npz; + linemesh.graph = graph; + linemesh.index = index; + linemesh.fp = fp; + linemesh.scale0 = 0.7 * scale; + linemesh.tip_name = this.getObjectHint(); + linemesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; + linemesh.nvertex = 2; + linemesh.check_next = true; + linemesh.tooltip = _graph2DTooltip; + } - ensureBins(npx + 1, npy + 1); + if (err) { + const lcolor = this.getColor(graph.fLineColor), + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), + errmesh = createLineSegments(err, material); + fp.add3DMesh(errmesh, this); - const arrv = new Array(npz + 1), arrz = new Array(npz + 1); - for (let k = 0; k <= npz; k++) - arrz[k] = zmin + k*dz; + errmesh.graph = graph; + errmesh.index = index; + errmesh.fp = fp; + errmesh.scale0 = 0.7 * scale; + errmesh.tip_name = this.getObjectHint(); + errmesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; + errmesh.nvertex = 6; + errmesh.tooltip = _graph2DTooltip; + } - for (let i = 0; i <= npx; ++i) { - for (let j = 0; j <= npy; ++j) { - for (let k = 0; k <= npz; k++) - arrv[k] = func.fSave[i + (npx + 1)*(j + (npy + 1)*k)]; - const z = findZValue(arrz, arrv); - hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); - } + if (pnts) { + let color = 'blue'; + + if (!o.Circles || o.Color) + color = palette?.calcColor(lvl, levels.length) ?? this.getColor(graph.fMarkerColor); + + const pr = pnts.createPoints({ color, fill: o.Circles ? 'white' : undefined, style: o.Circles ? 4 : graph.fMarkerStyle }).then(mesh => { + mesh.graph = graph; + mesh.fp = fp; + mesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; + mesh.scale0 = 0.3 * scale; + mesh.index = index; + mesh.tip_name = this.getObjectHint(); + mesh.tooltip = _graph2DTooltip; + fp.add3DMesh(mesh, this); + }); + + promises.push(pr); } } - hist.fName = 'Func'; - setHistogramTitle(hist, func.fTitle); + return Promise.all(promises).then(() => { + const main2 = this.getMainPainter(), + handle_palette = this.axes_draw || (main2?.draw_content === false); + if (!handle_palette) + return; + const pal = main2?.findFunction(clTPaletteAxis), + pal_painter = this.getPadPainter()?.findPainterFor(pal); + if (!pal_painter) + return; - // hist.fMinimum = func.fMinimum; - // hist.fMaximum = func.fMaximum; - // fHistogram->SetContour(fContour.fN, levels); - hist.fLineColor = func.fLineColor; - hist.fLineStyle = func.fLineStyle; - hist.fLineWidth = func.fLineWidth; - hist.fFillColor = func.fFillColor; - hist.fFillStyle = func.fFillStyle; - hist.fMarkerColor = func.fMarkerColor; - hist.fMarkerStyle = func.fMarkerStyle; - hist.fMarkerSize = func.fMarkerSize; - hist.fBits |= kNoStats; + pal_painter.Enabled = o.Zscale; - return hist; - } + if (o.Zscale) + return pal_painter.drawPave(); - /** @summary Extract function ranges */ - extractAxesProperties(ndim) { - super.extractAxesProperties(ndim); + pal_painter.removeG(); // completely remove drawing without need to redraw complete pad + }).then(() => { + fp.render3D(100); + return this; + }); + } - const func = this.$func, nsave = func?.fSave.length ?? 0; + /** @summary Build three.js of TGraph2D object */ + static async build3d(gr, opt) { + const painter = new TGraph2DPainter(null, gr); + painter.decodeOptions(opt, gr); - if (nsave > 9 && this._use_saved_points) { - this.xmin = Math.min(this.xmin, func.fSave[nsave-9]); - this.xmax = Math.max(this.xmax, func.fSave[nsave-8]); - this.ymin = Math.min(this.ymin, func.fSave[nsave-7]); - this.ymax = Math.max(this.ymax, func.fSave[nsave-6]); - this.zmin = Math.min(this.zmin, func.fSave[nsave-5]); - this.zmax = Math.max(this.zmax, func.fSave[nsave-4]); - } - if (func) { - this.xmin = Math.min(this.xmin, func.fXmin); - this.xmax = Math.max(this.xmax, func.fXmax); - this.ymin = Math.min(this.ymin, func.fYmin); - this.ymax = Math.max(this.ymax, func.fYmax); - this.zmin = Math.min(this.zmin, func.fZmin); - this.zmax = Math.max(this.zmax, func.fZmax); + if (painter.options.Contour) { + console.error('Contour plot is not 3d'); + return null; } - } - - /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed - * @private */ - fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; - } - /** @summary draw TF3 object */ - static async draw(dom, tf3, opt) { - const web = scanTF1Options(opt); - opt = web.opt; - delete web.opt; + return TH2Painter.build3d(painter.createHistogram(), painter.options.Axis, true).then(hist_painter => { + painter.axes_draw = true; + const fp = hist_painter.getFramePainter(); - const d = new DrawOptions(opt); - if (d.empty() || (opt === 'gl')) - opt = 'surf1'; - else if (d.opt === 'SAME') - opt = 'surf1 same'; + painter.getFramePainter = () => fp; + painter.getMainPainter = () => hist_painter; - if ((opt.indexOf('same') === 0) || (opt.indexOf('SAME') === 0)) { - if (!getElementMainPainter(dom)) - opt = 'A_ADJUST_FRAME_' + opt.slice(4); - } + return painter.drawGraph2D().then(() => fp.create3DScene(-1, true)); + }); + } - let hist; + /** @summary draw TGraph2D object */ + static async draw(dom, gr, opt) { + const painter = new TGraph2DPainter(dom, gr); + painter.decodeOptions(opt, gr); - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH2F); - } + let promise = Promise.resolve(null); - if (!hist) { - hist = createHistogram(clTH2F, 20, 20); - hist.fBits |= kNoStats; + if (!painter.getMainPainter()) { + // histogram is not preserved in TGraph2D + promise = TH2Painter.draw(dom, painter.createHistogram(), painter.options.Axis); + painter.axes_draw = true; } - const painter = new TF3Painter(dom, hist); - - painter.$func = tf3; - Object.assign(painter, web); - painter.createTF3Histogram(tf3, hist); - return THistPainter._drawHist(painter, opt); + return promise.then(() => { + painter.addToPadPrimitives(); + return painter.drawGraph2D(); + }); } -} // class TF3Painter +} // class TGraph2DPainter -var TF3Painter$1 = /*#__PURE__*/Object.freeze({ +var TGraph2DPainter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, -TF3Painter: TF3Painter +TGraph2DPainter: TGraph2DPainter }); +const kNoTitle = BIT(17); + /** - * @summary Painter for TSpline objects. + * @summary Painter for TGraphPolargram objects. * - * @private - */ - -class TSplinePainter extends ObjectPainter { - - /** @summary Update TSpline object - * @private */ - updateObject(obj, opt) { - const spline = this.getObject(); - - if (spline._typename !== obj._typename) return false; - - if (spline !== obj) Object.assign(spline, obj); + * @private */ - if (opt !== undefined) this.decodeOptions(opt); +class TGraphPolargramPainter extends TooltipHandler { - return true; + /** @summary Create painter + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} polargram - object to draw */ + constructor(dom, polargram, opt) { + super(dom, polargram, opt); + this.$polargram = true; // indicate that this is polargram + this.zoom_rmin = this.zoom_rmax = 0; + this.t0 = 0; + this.mult = 1; + this.decodeOptions(opt); + this.setTooltipEnabled(true); } - /** @summary Evaluate spline at given position - * @private */ - eval(knot, x) { - const dx = x - knot.fX; + /** @summary Returns true if fixed coordinates are configured */ + isNormalAngles() { + const polar = this.getObject(); + return polar?.fRadian || polar?.fGrad || polar?.fDegree; + } - if (knot._typename === 'TSplinePoly3') - return knot.fY + dx*(knot.fB + dx*(knot.fC + dx*knot.fD)); + /** @summary Decode draw options */ + decodeOptions(opt) { + const d = new DrawOptions(opt); - if (knot._typename === 'TSplinePoly5') - return knot.fY + dx*(knot.fB + dx*(knot.fC + dx*(knot.fD + dx*(knot.fE + dx*knot.fF)))); + this.setOptions({ + rdot: d.check('RDOT'), + rangle: d.check('RANGLE', true) ? d.partAsInt() : 0, + NoLabels: d.check('N'), + OrthoLabels: d.check('O') + }); - return knot.fY + dx; + this.storeDrawOpt(opt); } - /** @summary Find idex for x value - * @private */ - findX(x) { - const spline = this.getObject(); - let klow = 0, khig = spline.fNp - 1; + /** @summary Set angles range displayed by the polargram */ + setAnglesRange(tmin, tmax, set_obj) { + if (tmin >= tmax) + tmax = tmin + 1; + if (set_obj) { + const polar = this.getObject(); + polar.fRwtmin = tmin; + polar.fRwtmax = tmax; + } + this.t0 = tmin; + this.mult = 2 * Math.PI / (tmax - tmin); + } - if (x <= spline.fXmin) return 0; - if (x >= spline.fXmax) return khig; + /** @summary Translate coordinates */ + translate(input_angle, radius, keep_float) { + // recalculate angle + const angle = (input_angle - this.t0) * this.mult; + let rx = this.r(radius), + ry = rx / this.szx * this.szy, + grx = rx * Math.cos(-angle), + gry = ry * Math.sin(-angle); - if (spline.fKstep) { - // Equidistant knots, use histogramming - klow = Math.round((x - spline.fXmin)/spline.fDelta); - // Correction for rounding errors - if (x < spline.fPoly[klow].fX) - klow = Math.max(klow-1, 0); - else if (klow < khig) - if (x > spline.fPoly[klow+1].fX) ++klow; - } else { - // Non equidistant knots, binary search - while (khig - klow > 1) { - const khalf = Math.round((klow + khig)/2); - if (x > spline.fPoly[khalf].fX) klow = khalf; - else khig = khalf; - } + if (!keep_float) { + grx = Math.round(grx); + gry = Math.round(gry); + rx = Math.round(rx); + ry = Math.round(ry); } - return klow; + return { grx, gry, rx, ry }; } - /** @summary Create histogram for axes drawing - * @private */ - createDummyHisto() { - const spline = this.getObject(); - let xmin = 0, xmax = 1, ymin = 0, ymax = 1; + /** @summary format label for radius ticks */ + format(radius) { + if (radius === Math.round(radius)) + return radius.toString(); + if (this.ndig > 10) + return radius.toExponential(4); + return radius.toFixed((this.ndig > 0) ? this.ndig : 0); + } + + /** @summary Convert axis values to text */ + axisAsText(axis, value) { + if (axis === 'r') { + if (value === Math.round(value)) + return value.toString(); + if (this.ndig > 10) + return value.toExponential(4); + return value.toFixed(this.ndig + 2); + } - if (spline.fPoly) { - xmin = xmax = spline.fPoly[0].fX; - ymin = ymax = spline.fPoly[0].fY; + value *= 180 / Math.PI; + return (value === Math.round(value)) ? value.toString() : value.toFixed(1); + } - spline.fPoly.forEach(knot => { - xmin = Math.min(knot.fX, xmin); - xmax = Math.max(knot.fX, xmax); - ymin = Math.min(knot.fY, ymin); - ymax = Math.max(knot.fY, ymax); - }); + /** @summary Returns coordinate of frame - without using frame itself */ + getFrameRect() { + const pp = this.getPadPainter(), + pad = pp.getRootPad(true), + w = pp.getPadWidth(), + h = pp.getPadHeight(), + rect = {}; - if (ymax > 0) ymax *= (1 + gStyle.fHistTopMargin); - if (ymin < 0) ymin *= (1 + gStyle.fHistTopMargin); + if (pad) { + rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin)) * w); + rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin)) * h); + } else { + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); } - const histo = createHistogram(clTH1I, 10); + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); - histo.fName = spline.fName + '_hist'; - histo.fTitle = spline.fTitle; - histo.fBits |= kNoStats; + rect.hint_delta_x = rect.szx; + rect.hint_delta_y = rect.szy; - histo.fXaxis.fXmin = xmin; - histo.fXaxis.fXmax = xmax; - histo.fYaxis.fXmin = ymin; - histo.fYaxis.fXmax = ymax; + rect.transform = makeTranslate(rect.x, rect.y) || ''; - return histo; + return rect; } - /** @summary Process tooltip event - * @private */ - processTooltipEvent(pnt) { - const spline = this.getObject(), - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); - let cleanup = false, xx, yy, knot = null, indx = 0; + /** @summary Process mouse event */ + mouseEvent(kind, evnt) { + let pnt = null; + if (kind !== 'leave') { + const pos = pointer(evnt, this.getG()?.node()); + pnt = { x: pos[0], y: pos[1], touch: false }; + } + this.processFrameTooltipEvent(pnt); + } - if ((pnt === null) || !spline || !funcs) - cleanup = true; - else { - xx = funcs.revertAxis('x', pnt.x); - indx = this.findX(xx); - knot = spline.fPoly[indx]; - yy = this.eval(knot, xx); + /** @summary Process mouse wheel event */ + mouseWheel(evnt) { + evnt.stopPropagation(); + evnt.preventDefault(); - if ((indx < spline.fN-1) && (Math.abs(spline.fPoly[indx+1].fX-xx) < Math.abs(xx-knot.fX))) knot = spline.fPoly[++indx]; + this.processFrameTooltipEvent(null); // remove all tooltips - if (Math.abs(funcs.grx(knot.fX) - pnt.x) < 0.5*this.knot_size) { - xx = knot.fX; yy = knot.fY; - } else { - knot = null; - if ((xx < spline.fXmin) || (xx > spline.fXmax)) cleanup = true; - } - } + const polar = this.getObject(); + if (!polar) + return; - let gbin = this.draw_g?.selectChild('.tooltip_bin'); - const radius = this.lineatt.width + 3; + let delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); + if (!delta) + return; - if (cleanup || !this.draw_g) { - gbin?.remove(); - return null; - } + delta = (delta < 0) ? -0.2 : 0.2; - if (gbin.empty()) { - gbin = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .attr('r', radius) - .style('fill', 'none') - .call(this.lineatt.func); - } + let rmin = this.scale_rmin, rmax = this.scale_rmax; + const range = rmax - rmin; - const res = { name: this.getObject().fName, - title: this.getObject().fTitle, - x: funcs.grx(xx), - y: funcs.gry(yy), - color1: this.lineatt.color, - lines: [], - exact: (knot !== null) || (Math.abs(funcs.gry(yy) - pnt.y) < radius) }; + // rmin -= delta*range; + rmax += delta * range; - res.changed = gbin.property('current_xx') !== xx; - res.menu = res.exact; - res.menu_dist = Math.sqrt((res.x-pnt.x)**2 + (res.y-pnt.y)**2); + if ((rmin < polar.fRwrmin) || (rmax > polar.fRwrmax)) + rmin = rmax = 0; - if (res.changed) { - gbin.attr('cx', Math.round(res.x)) - .attr('cy', Math.round(res.y)) - .property('current_xx', xx); + if ((this.zoom_rmin !== rmin) || (this.zoom_rmax !== rmax)) { + this.zoom_rmin = rmin; + this.zoom_rmax = rmax; + this.redrawPad(); } + } - const name = this.getObjectHint(); - if (name) res.lines.push(name); - res.lines.push(`x = ${funcs.axisAsText('x', xx)}`, - `y = ${funcs.axisAsText('y', yy)}`); - if (knot !== null) { - res.lines.push(`knot = ${indx}`, - `B = ${floatToString(knot.fB, gStyle.fStatFormat)}`, - `C = ${floatToString(knot.fC, gStyle.fStatFormat)}`, - `D = ${floatToString(knot.fD, gStyle.fStatFormat)}`); - if ((knot.fE !== undefined) && (knot.fF !== undefined)) { - res.lines.push(`E = ${floatToString(knot.fE, gStyle.fStatFormat)}`, - `F = ${floatToString(knot.fF, gStyle.fStatFormat)}`); - } + /** @summary Process mouse double click event */ + mouseDoubleClick() { + if (this.zoom_rmin || this.zoom_rmax) { + this.zoom_rmin = this.zoom_rmax = 0; + this.redrawPad(); } - - return res; } - /** @summary Redraw object - * @private */ - redraw() { - const spline = this.getObject(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - w = pmain.getFrameWidth(), - h = pmain.getFrameHeight(); + /** @summary Draw polargram polar labels */ + async drawPolarLabels(polar, nmajor) { + const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2), + o = this.getOptions(); - this.createG(true); + return this.startTextDrawingAsync(polar.fPolarLabelFont, fontsize).then(() => { + const lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}'], + aligns = [12, 11, 21, 31, 32, 33, 23, 13]; - this.knot_size = 5; // used in tooltip handling + for (let n = 0; n < nmajor; ++n) { + const angle = -n * 2 * Math.PI / nmajor; + this.getG().append('svg:path') + .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) + .call(this.lineatt.func); - this.createAttLine({ attr: spline }); + let align = 12, rotate = 0; - if (this.options.Line || this.options.Curve) { - const npx = Math.max(10, spline.fNpx), bins = []; // index of current knot - let xmin = Math.max(pmain.scale_xmin, spline.fXmin), - xmax = Math.min(pmain.scale_xmax, spline.fXmax), - indx = this.findX(xmin); + if (o.OrthoLabels) { + rotate = -n / nmajor * 360; + if ((rotate > -271) && (rotate < -91)) { + align = 32; + rotate += 180; + } + } else { + const aindx = Math.round(16 - angle / Math.PI * 4) % 8; // index in align table, here absolute angle is important + align = aligns[aindx]; + } - if (pmain.logx) { - xmin = Math.log(xmin); - xmax = Math.log(xmax); + this.drawText({ + align, rotate, + x: Math.round((this.szx + fontsize) * Math.cos(angle)), + y: Math.round((this.szy + fontsize / this.szx * this.szy) * (Math.sin(angle))), + text: lbls[n], + color: this.getColor(polar.fPolarLabelColor), latex: 1 + }); } - for (let n = 0; n < npx; ++n) { - let x = xmin + (xmax-xmin)/npx*(n-1); - if (pmain.logx) x = Math.exp(x); + return this.finishTextDrawing(); + }); + } - while ((indx < spline.fNp-1) && (x > spline.fPoly[indx+1].fX)) ++indx; + /** @summary Redraw polargram */ + async redraw() { + if (!this.isMainPainter()) + return; - const y = this.eval(spline.fPoly[indx], x); + const polar = this.getObject(), + o = this.getOptions(), + rect = this.getPadPainter().getFrameRect(), + g = this.createG(); - bins.push({ x, y, grx: funcs.grx(x), gry: funcs.gry(y) }); - } + makeTranslate(g, Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); + this.szx = rect.szx; + this.szy = rect.szy; - this.draw_g.append('svg:path') - .attr('class', 'line') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .call(this.lineatt.func); + this.scale_rmin = polar.fRwrmin; + this.scale_rmax = polar.fRwrmax; + if (this.zoom_rmin !== this.zoom_rmax) { + this.scale_rmin = this.zoom_rmin; + this.scale_rmax = this.zoom_rmax; } - if (this.options.Mark) { - // for tooltips use markers only if nodes where not created - let path = ''; + this.r = linear().domain([this.scale_rmin, this.scale_rmax]).range([0, this.szx]); - this.createAttMarker({ attr: spline }); + if (polar.fRadian) { + polar.fRwtmin = 0; + polar.fRwtmax = 2 * Math.PI; + } else if (polar.fDegree) { + polar.fRwtmin = 0; + polar.fRwtmax = 360; + } else if (polar.fGrad) { + polar.fRwtmin = 0; + polar.fRwtmax = 200; + } - this.markeratt.resetPos(); + this.setAnglesRange(polar.fRwtmin, polar.fRwtmax); - this.knot_size = this.markeratt.getFullSize(); + const ticks = this.r.ticks(5); + let nminor = Math.floor((polar.fNdivRad % 10000) / 100), + nmajor = polar.fNdivPol % 100; + if (nmajor !== 3) + nmajor = 8; - for (let n = 0; n < spline.fPoly.length; n++) { - const knot = spline.fPoly[n], - grx = funcs.grx(knot.fX); - if ((grx > -this.knot_size) && (grx < w + this.knot_size)) { - const gry = funcs.gry(knot.fY); - if ((gry > -this.knot_size) && (gry < h + this.knot_size)) - path += this.markeratt.create(grx, gry); - } - } + this.createAttLine({ attr: polar }); + if (!this.gridatt) + this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false }); - if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + const range = Math.abs(polar.fRwrmax - polar.fRwrmin); + this.ndig = (range <= 0) ? -3 : Math.round(Math.log10(ticks.length / range)); + + // verify that all radius labels are unique + let lbls = [], indx = 0; + while (indx < ticks.length) { + const lbl = this.format(ticks[indx]); + if (lbls.indexOf(lbl) >= 0) { + if (++this.ndig > 10) + break; + lbls = []; + indx = 0; + continue; } + lbls.push(lbl); + indx++; } - } - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis /* , min, max */) { - if (axis !== 'x') return false; + let exclude_last = false; + const pointer_events = this.isBatchMode() ? null : 'visibleFill'; - // spline can always be calculated and therefore one can zoom inside - return !!this.getObject(); - } + if ((ticks.at(-1) < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) { + ticks.push(polar.fRwrmax); + exclude_last = true; + } - /** @summary Decode options for TSpline drawing */ - decodeOptions(opt) { - const d = new DrawOptions(opt); + return this.startTextDrawingAsync(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)).then(() => { + const axis_angle = - (o.rangle || polar.fAxisAngle) / 180 * Math.PI, + ca = Math.cos(axis_angle), + sa = Math.sin(axis_angle); + for (let n = 0; n < ticks.length; ++n) { + let rx = this.r(ticks[n]), + ry = rx / this.szx * this.szy; + g.append('ellipse') + .attr('cx', 0) + .attr('cy', 0) + .attr('rx', Math.round(rx)) + .attr('ry', Math.round(ry)) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); - if (!this.options) this.options = {}; + if ((n < ticks.length - 1) || !exclude_last) { + const halign = ca > 0.7 ? 1 : (ca > 0 ? 3 : (ca > -0.7 ? 1 : 3)), + valign = Math.abs(ca) < 0.7 ? 1 : 3; + this.drawText({ + align: 10 * halign + valign, + x: Math.round(rx * ca), + y: Math.round(ry * sa), + text: this.format(ticks[n]), + color: this.getColor(polar.fRadialLabelColor), latex: 0 + }); + if (o.rdot) { + g.append('ellipse') + .attr('cx', Math.round(rx * ca)) + .attr('cy', Math.round(ry * sa)) + .attr('rx', 3) + .attr('ry', 3) + .style('fill', 'red'); + } + } - const has_main = !!this.getMainPainter(); + if ((nminor > 1) && ((n < ticks.length - 1) || !exclude_last)) { + const dr = (ticks[1] - ticks[0]) / nminor; + for (let nn = 1; nn < nminor; ++nn) { + const gridr = ticks[n] + dr * nn; + if (gridr > this.scale_rmax) + break; + rx = this.r(gridr); + ry = rx / this.szx * this.szy; + g.append('ellipse') + .attr('cx', 0) + .attr('cy', 0) + .attr('rx', Math.round(rx)) + .attr('ry', Math.round(ry)) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.gridatt.func); + } + } + } - Object.assign(this.options, { - Same: d.check('SAME'), - Line: d.check('L'), - Curve: d.check('C'), - Mark: d.check('P'), - Hopt: '', - second_x: false, - second_y: false - }); + if (ca < 0.999) { + g.append('path') + .attr('d', `M0,0L${Math.round(this.szx * ca)},${Math.round(this.szy * sa)}`) + .style('pointer-events', pointer_events) + .call(this.lineatt.func); + } - if (!this.options.Line && !this.options.Curve && !this.options.Mark) - this.options.Curve = true; + return this.finishTextDrawing(); + }).then(() => { + return o.NoLabels ? true : this.drawPolarLabels(polar, nmajor); + }).then(() => { + nminor = Math.floor((polar.fNdivPol % 10000) / 100); - if (d.check('X+')) { this.options.Hopt += 'X+'; this.options.second_x = has_main; } - if (d.check('Y+')) { this.options.Hopt += 'Y+'; this.options.second_y = has_main; } + if (nminor > 1) { + for (let n = 0; n < nmajor * nminor; ++n) { + if (n % nminor === 0) + continue; + const angle = -n * 2 * Math.PI / nmajor / nminor; + g.append('svg:path') + .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) + .call(this.gridatt.func); + } + } - this.storeDrawOpt(opt); - } + if (this.isBatchMode()) + return; - /** @summary Draw TSpline */ - static async draw(dom, spline, opt) { - const painter = new TSplinePainter(dom, spline); - painter.decodeOptions(opt); + assignContextMenu(this, kNoReorder); - const no_main = !painter.getMainPainter(); - let promise = Promise.resolve(); - if (no_main || painter.options.second_x || painter.options.second_y) { - if (painter.options.Same && no_main) { - console.warn('TSpline painter requires histogram to be drawn'); - return null; - } - const histo = painter.createDummyHisto(); - promise = TH1Painter.draw(dom, histo, painter.options.Hopt); - } + this.assignZoomHandler(g); + }); + } - return promise.then(() => { - painter.addToPadPrimitives(); - painter.redraw(); - return painter; + /** @summary Fill TGraphPolargram context menu */ + fillContextMenuItems(menu) { + const pp = this.getObject(), o = this.getOptions(); + menu.sub('Axis range'); + menu.addchk(pp.fRadian, 'Radian', flag => { + pp.fRadian = flag; + pp.fDegree = pp.fGrad = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToRadian()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as radian range 0..2*Pi'); + menu.addchk(pp.fDegree, 'Degree', flag => { + pp.fDegree = flag; + pp.fRadian = pp.fGrad = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToDegree()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as degree range 0..360'); + menu.addchk(pp.fGrad, 'Grad', flag => { + pp.fGrad = flag; + pp.fRadian = pp.fDegree = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToGrad()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as grad range 0..200'); + menu.endsub(); + menu.addSizeMenu('Axis angle', 0, 315, 45, o.rangle || pp.fAxisAngle, v => { + o.rangle = pp.fAxisAngle = v; + this.interactiveRedraw('pad', `exec:SetAxisAngle(${v})`); }); } -} // class TSplinePainter + /** @summary Assign zoom handler to element + * @private */ + assignZoomHandler(elem) { + elem.on('mouseenter', evnt => this.mouseEvent('enter', evnt)) + .on('mousemove', evnt => this.mouseEvent('move', evnt)) + .on('mouseleave', evnt => this.mouseEvent('leave', evnt)); -var TSplinePainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TSplinePainter: TSplinePainter -}); + if (settings.Zooming) + elem.on('dblclick', evnt => this.mouseDoubleClick(evnt)); -/** @summary Drawing TArrow - * @private */ -class TArrowPainter extends TLinePainter { + if (settings.Zooming && settings.ZoomWheel) + elem.on('wheel', evnt => this.mouseWheel(evnt)); + } - /** @summary Create line segment with rotation */ - rotate(angle, x0, y0) { - let dx = this.wsize * Math.cos(angle), dy = this.wsize * Math.sin(angle), res = ''; - if ((x0 !== undefined) && (y0 !== undefined)) - res = `M${Math.round(x0-dx)},${Math.round(y0-dy)}`; - else { - dx = -dx; dy = -dy; + /** @summary Draw TGraphPolargram */ + static async draw(dom, polargram, opt) { + const main = getElementMainPainter(dom); + if (main) { + if (main.getObject() === polargram) + return main; + throw Error('Cannot superimpose TGraphPolargram with any other drawings'); } - res += `l${Math.round(dx)},${Math.round(dy)}`; - if (x0 && (y0 === undefined)) res += 'z'; - return res; + + const painter = new TGraphPolargramPainter(dom, polargram, opt); + return ensureTCanvas(painter, false).then(() => { + painter.setAsMainPainter(); + return painter.redraw(); + }).then(() => painter); } - /** @summary Create SVG path for the arrow */ - createPath() { - const angle = Math.atan2(this.y2 - this.y1, this.x2 - this.x1), - dlen = this.wsize * Math.cos(this.angle2), - dx = dlen*Math.cos(angle), dy = dlen*Math.sin(angle); +} // class TGraphPolargramPainter - let path = ''; - if (this.beg) { - path += this.rotate(angle - Math.PI - this.angle2, this.x1, this.y1) + - this.rotate(angle - Math.PI + this.angle2, this.beg > 10); - } - if (this.mid % 10 === 2) { - path += this.rotate(angle - Math.PI - this.angle2, (this.x1+this.x2-dx)/2, (this.y1+this.y2-dy)/2) + - this.rotate(angle - Math.PI + this.angle2, this.mid > 10); - } +/** + * @summary Painter for TGraphPolar objects. + * + * @private + */ - if (this.mid % 10 === 1) { - path += this.rotate(angle - this.angle2, (this.x1+this.x2+dx)/2, (this.y1+this.y2+dy)/2) + - this.rotate(angle + this.angle2, this.mid > 10); - } +class TGraphPolarPainter extends ObjectPainter { - if (this.end) { - path += this.rotate(angle - this.angle2, this.x2, this.y2) + - this.rotate(angle + this.angle2, this.end > 10); - } + /** @summary Decode options for drawing TGraphPolar */ + decodeOptions(opt) { + const d = new DrawOptions(opt || 'L'), + rdot = d.check('RDOT'), + rangle = d.check('RANGLE', true) ? d.partAsInt() : 0, + o = this.setOptions({ + mark: d.check('P'), + err: d.check('E'), + fill: d.check('F'), + line: d.check('L'), + curve: d.check('C'), + radian: d.check('R'), + degree: d.check('D'), + grad: d.check('G'), + Axis: d.check('N') ? 'N' : '' + }, opt); + + if (d.check('O')) + o.Axis += 'O'; + if (rdot) + o.Axis += '_rdot'; + if (rangle) + o.Axis += `_rangle${rangle}`; - return `M${Math.round(this.x1 + (this.beg > 10 ? dx : 0))},${Math.round(this.y1 + (this.beg > 10 ? dy : 0))}` + - `L${Math.round(this.x2 - (this.end > 10 ? dx : 0))},${Math.round(this.y2 - (this.end > 10 ? dy : 0))}` + - path; + this.storeDrawOpt(opt); } - /** @summary calculate all TArrow coordinates */ - prepareDraw() { - super.prepareDraw(); - - const arrow = this.getObject(), - oo = arrow.fOption, - rect = this.getPadPainter().getPadRect(); - - this.wsize = Math.max(3, Math.round(Math.max(rect.width, rect.height) * arrow.fArrowSize * 0.8)); - this.angle2 = arrow.fAngle/2/180 * Math.PI; - this.beg = this.mid = this.end = 0; + /** @summary Update TGraphPolar with polargram */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; - if (oo.indexOf('<') === 0) - this.beg = (oo.indexOf('<|') === 0) ? 12 : 2; - if (oo.indexOf('->-') >= 0) - this.mid = 1; - else if (oo.indexOf('-|>-') >= 0) - this.mid = 11; - else if (oo.indexOf('-<-') >= 0) - this.mid = 2; - else if (oo.indexOf('-<|-') >= 0) - this.mid = 12; + if (opt && (opt !== this.getOptions().original)) + this.decodeOptions(opt); - const p1 = oo.lastIndexOf('>'), p2 = oo.lastIndexOf('|>'), len = oo.length; - if ((p1 >= 0) && (p1 === len-1)) - this.end = ((p2 >= 0) && (p2 === len-2)) ? 11 : 1; + if (this._draw_axis && obj.fPolargram) + this.getMainPainter().updateObject(obj.fPolargram); - this.createAttFill({ attr: arrow }); + delete obj.fPolargram; + // copy all properties but not polargram + Object.assign(this.getObject(), obj); + return true; } - /** @summary Add extras to path for TArrow */ - addExtras(elem) { - if ((this.beg > 10) || (this.end > 10)) - elem.call(this.fillatt.func); - else - elem.style('fill', 'none'); + /** @summary Draw TGraphPolar title */ + async drawTitle(first_time) { + return drawObjectTitle(this, first_time, this._draw_axis, !this.getObject()?.TestBit(kNoTitle)); } - /** @summary Draw TArrow object */ - static async draw(dom, obj, opt) { - const painter = new TArrowPainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); + /** @summary Redraw TGraphPolar */ + async redraw() { + return this.drawGraphPolar() + .then(() => this.drawTitle()); } -} // class TArrowPainter + /** @summary Drawing TGraphPolar */ + async drawGraphPolar() { + const graph = this.getObject(), + o = this.getOptions(), + main = this.getMainPainter(); -var TArrowPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TArrowPainter: TArrowPainter -}); + if (!graph || !main?.$polargram) + return; -/** @summary Drawing TGaxis - * @private */ -class TGaxisPainter extends TAxisPainter { + if (o.mark) + this.createAttMarker({ attr: graph }); + if (o.err || o.line || o.curve) + this.createAttLine({ attr: graph }); + if (o.fill) + this.createAttFill({ attr: graph }); - /** @summary Convert TGaxis position into NDC to fix it when frame zoomed */ - convertTo(opt) { - const gaxis = this.getObject(), - x1 = this.axisToSvg('x', gaxis.fX1), - y1 = this.axisToSvg('y', gaxis.fY1), - x2 = this.axisToSvg('x', gaxis.fX2), - y2 = this.axisToSvg('y', gaxis.fY2); + const g = this.createG(); - if (opt === 'ndc') { - const pw = this.getPadPainter().getPadWidth(), - ph = this.getPadPainter().getPadHeight(); - - gaxis.fX1 = x1 / pw; - gaxis.fX2 = x2 / pw; - gaxis.fY1 = (ph - y1) / ph; - gaxis.fY2 = (ph - y2)/ ph; - this.use_ndc = true; - } else if (opt === 'frame') { - const rect = this.getFramePainter().getFrameRect(); - gaxis.fX1 = (x1 - rect.x) / rect.width; - gaxis.fX2 = (x2 - rect.x) / rect.width; - gaxis.fY1 = (y1 - rect.y) / rect.height; - gaxis.fY2 = (y2 - rect.y) / rect.height; - this.bind_frame = true; + if (this._draw_axis && !main.isNormalAngles()) { + const has_err = graph.fEX?.length; + let rwtmin = graph.fX[0], + rwtmax = graph.fX[0]; + for (let n = 0; n < graph.fNpoints; ++n) { + rwtmin = Math.min(rwtmin, graph.fX[n] - (has_err ? graph.fEX[n] : 0)); + rwtmax = Math.max(rwtmax, graph.fX[n] + (has_err ? graph.fEX[n] : 0)); + } + rwtmax += (rwtmax - rwtmin) / graph.fNpoints; + main.setAnglesRange(rwtmin, rwtmax, true); } - } - /** @summary Drag moving handle */ - moveDrag(dx, dy) { - this.gaxis_x += dx; - this.gaxis_y += dy; - makeTranslate(this.getG(), this.gaxis_x, this.gaxis_y); - } + g.attr('transform', main.getG().attr('transform')); - /** @summary Drag end handle */ - moveEnd(not_changed) { - if (not_changed) return; + let mpath = '', epath = ''; + const bins = [], pointer_events = this.isBatchMode() ? null : 'visibleFill'; - const gaxis = this.getObject(); + for (let n = 0; n < graph.fNpoints; ++n) { + if (graph.fY[n] > main.scale_rmax) + continue; - let fx, fy; - if (this.bind_frame) { - const rect = this.getFramePainter().getFrameRect(); - fx = (this.gaxis_x - rect.x) / rect.width; - fy = (this.gaxis_y - rect.y) / rect.height; - } else { - fx = this.svgToAxis('x', this.gaxis_x, this.use_ndc); - fy = this.svgToAxis('y', this.gaxis_y, this.use_ndc); - } + if (o.err) { + const p1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]), + p2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]), + p3 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]), + p4 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]); - if (this.vertical) { - gaxis.fX1 = gaxis.fX2 = fx; - if (this.reverse) { - gaxis.fY2 = fy + (gaxis.fY2 - gaxis.fY1); - gaxis.fY1 = fy; - } else { - gaxis.fY1 = fy + (gaxis.fY1 - gaxis.fY2); - gaxis.fY2 = fy; - } - } else { - if (this.reverse) { - gaxis.fX1 = fx + (gaxis.fX1 - gaxis.fX2); - gaxis.fX2 = fx; - } else { - gaxis.fX2 = fx + (gaxis.fX2 - gaxis.fX1); - gaxis.fX1 = fx; + epath += `M${p1.grx},${p1.gry}L${p2.grx},${p2.gry}` + + `M${p3.grx},${p3.gry}A${p4.rx},${p4.ry},0,0,1,${p4.grx},${p4.gry}`; } - gaxis.fY1 = gaxis.fY2 = fy; - } - this.submitAxisExec(`SetX1(${gaxis.fX1});;SetX2(${gaxis.fX2});;SetY1(${gaxis.fY1});;SetY2(${gaxis.fY2})`, true); - } + const pos = main.translate(graph.fX[n], graph.fY[n]); - /** @summary Redraw axis, used in standalone mode for TGaxis */ - redraw() { - const gaxis = this.getObject(), - min = gaxis.fWmin, - max = gaxis.fWmax; - let x1, y1, x2, y2; + if (o.mark) + mpath += this.markeratt.create(pos.grx, pos.gry); - if (this.bind_frame) { - const rect = this.getFramePainter().getFrameRect(); - x1 = Math.round(rect.x + gaxis.fX1 * rect.width); - x2 = Math.round(rect.x + gaxis.fX2 * rect.width); - y1 = Math.round(rect.y + gaxis.fY1 * rect.height); - y2 = Math.round(rect.y + gaxis.fY2 * rect.height); - } else { - x1 = this.axisToSvg('x', gaxis.fX1, this.use_ndc); - y1 = this.axisToSvg('y', gaxis.fY1, this.use_ndc); - x2 = this.axisToSvg('x', gaxis.fX2, this.use_ndc); - y2 = this.axisToSvg('y', gaxis.fY2, this.use_ndc); + if (o.curve || o.line || o.fill) + bins.push(pos); } - const w = x2 - x1, h = y1 - y2, - vertical = Math.abs(w) < Math.abs(h); - let sz = vertical ? h : w, reverse = false; + if ((o.fill || o.line) && bins.length) { + const lpath = buildSvgCurve(bins, { line: true }); + if (o.fill) { + g.append('svg:path') + .attr('d', lpath + 'Z') + .style('pointer-events', pointer_events) + .call(this.fillatt.func); + } - if (sz < 0) { - reverse = true; - sz = -sz; - if (vertical) - y2 = y1; - else - x1 = x2; + if (o.line) { + g.append('svg:path') + .attr('d', lpath) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); + } } - this.configureAxis(vertical ? 'yaxis' : 'xaxis', min, max, min, max, vertical, [0, sz], { - time_scale: gaxis.fChopt.indexOf('t') >= 0, - log: (gaxis.fChopt.indexOf('G') >= 0) ? 1 : 0, - reverse, - swap_side: reverse, - axis_func: this.axis_func - }); - - this.createG(); - - this.gaxis_x = x1; - this.gaxis_y = y2; - - return this.drawAxis(this.getG(), Math.abs(w), Math.abs(h), makeTranslate(this.gaxis_x, this.gaxis_y) || '').then(() => { - addMoveHandler(this); - assignContextMenu(this); - return this; - }); - } - - /** @summary Fill TGaxis context */ - fillContextMenu(menu) { - menu.addTAxisMenu(EAxisBits, this, this.getObject(), ''); - } + if (o.curve && bins.length) { + g.append('svg:path') + .attr('d', buildSvgCurve(bins)) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); + } - /** @summary Check if there is function for TGaxis can be found */ - async checkFuncion() { - const gaxis = this.getObject(); - if (!gaxis.fFunctionName) { - this.axis_func = null; - return; + if (epath) { + g.append('svg:path') + .attr('d', epath) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); } - const func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); - let promise = Promise.resolve(func); - if (!func) { - const h = getHPainter(), - item = h?.findItem({ name: gaxis.fFunctionName, check_keys: true }); - if (item) { - promise = h.getObject({ item }).then(res => { - return res?.obj?._typename === clTF1 ? res.obj : null; - }); - } + if (mpath) { + g.append('svg:path') + .attr('d', mpath) + .style('pointer-events', pointer_events) + .call(this.markeratt.func); } - return promise.then(f => { - this.axis_func = f; - if (f) - proivdeEvalPar(f); - }); + if (!this.isBatchMode()) { + assignContextMenu(this, kNoReorder); + main.assignZoomHandler(g); + } } - /** @summary Create handle for custom function in the axis */ - createFuncHandle(func, logbase, smin, smax) { - const res = function(v) { return res.toGraph(v); }; - res._func = func; - res._domain = [smin, smax]; - res._scale = logbase ? log().base(logbase) : linear(); - res._scale.domain(res._domain).range([0, 100]); - res.eval = function(v) { - try { - v = res._func.evalPar(v); - } catch (err) { - v = 0; - } - return Number.isFinite(v) ? v : 0; - }; - - const vmin = res.eval(smin), vmax = res.eval(smax); - if ((vmin < vmax) === (smin < smax)) { - res._vmin = vmin; - res._vk = 1/(vmax - vmin); - } else if (vmin === vmax) { - res._vmin = 0; - res._vk = 1; - } else { - res._vmin = vmax; - res._vk = 1/(vmin - vmax); + /** @summary Create polargram object */ + createPolargram(gr) { + const o = this.getOptions(); + if (!gr.fPolargram) { + gr.fPolargram = create$1('TGraphPolargram'); + if (o.radian) + gr.fPolargram.fRadian = true; + else if (o.degree) + gr.fPolargram.fDegree = true; + else if (o.grad) + gr.fPolargram.fGrad = true; } - res._range = [0, 100]; - res.range = function(arr) { - if (arr) { - res._range = arr; - return res; - } - return res._range; - }; - - res.domain = function() { return res._domain; }; - res.toGraph = function(v) { - const rel = (res.eval(v) - res._vmin) * res._vk; - return res._range[0] * (1 - rel) + res._range[1] * rel; - }; + let rmin = gr.fY[0] || 0, rmax = rmin; + const has_err = gr.fEY?.length; + for (let n = 0; n < gr.fNpoints; ++n) { + rmin = Math.min(rmin, gr.fY[n] - (has_err ? gr.fEY[n] : 0)); + rmax = Math.max(rmax, gr.fY[n] + (has_err ? gr.fEY[n] : 0)); + } - res.ticks = function(arg) { return res._scale.ticks(arg); }; + gr.fPolargram.fRwrmin = rmin - (rmax - rmin) * 0.1; + gr.fPolargram.fRwrmax = rmax + (rmax - rmin) * 0.1; - return res; + return gr.fPolargram; } - /** @summary Draw TGaxis object */ - static async draw(dom, obj, opt) { - const painter = new TGaxisPainter(dom, obj, false); + /** @summary Provide tooltip at specified point */ + extractTooltip(pnt) { + if (!pnt) + return null; - return ensureTCanvas(painter, false).then(() => { - if (opt) painter.convertTo(opt); - return painter.checkFuncion(); - }).then(() => painter.redraw()); - } + const graph = this.getObject(), + main = this.getMainPainter(); + let best_dist2 = 1e10, bestindx = -1, bestpos = null; -} // class TGaxisPainter + for (let n = 0; n < graph.fNpoints; ++n) { + const pos = main.translate(graph.fX[n], graph.fY[n]), + dist2 = (pos.grx - pnt.x) ** 2 + (pos.gry - pnt.y) ** 2; + if (dist2 < best_dist2) { + best_dist2 = dist2; + bestindx = n; + bestpos = pos; + } + } -var TGaxisPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -TGaxisPainter: TGaxisPainter -}); + let match_distance = 5; + if (this.markeratt?.used) + match_distance = this.markeratt.getFullSize(); -/** - * @summary Painter for TASImage object. - * - * @private - */ + if (Math.sqrt(best_dist2) > match_distance) + return null; -class TASImagePainter extends ObjectPainter { + const res = { + name: this.getObject().fName, title: this.getObject().fTitle, + x: bestpos.grx, y: bestpos.gry, + color1: (this.markeratt?.used ? this.markeratt.color : undefined) ?? (this.fillatt?.used ? this.fillatt.color : undefined) ?? this.lineatt?.color, + exact: Math.sqrt(best_dist2) < 4, + lines: [this.getObjectHint()], + binindx: bestindx, + menu_dist: match_distance, + radius: match_distance + }; - /** @summary Decode options string */ - decodeOptions(opt) { - const d = new DrawOptions(opt); + res.lines.push(`r = ${main.axisAsText('r', graph.fY[bestindx])}`, + `phi = ${main.axisAsText('phi', graph.fX[bestindx])}`); - this.options = { Zscale: false }; + if (graph.fEY && graph.fEY[bestindx]) + res.lines.push(`error r = ${main.axisAsText('r', graph.fEY[bestindx])}`); - const obj = this.getObject(); + if (graph.fEX && graph.fEX[bestindx]) + res.lines.push(`error phi = ${main.axisAsText('phi', graph.fEX[bestindx])}`); - if (d.check('CONST')) { - this.options.constRatio = true; - if (obj) obj.fConstRatio = true; - console.log('use const'); - } - if (d.check('Z')) this.options.Zscale = true; + return res; } - /** @summary Create RGBA buffers */ - createRGBA(nlevels) { - const obj = this.getObject(), - pal = obj?.fPalette; - if (!pal) return null; + /** @summary Show tooltip */ + showTooltip(hint) { + let ttcircle = this.getG()?.selectChild('.tooltip_bin'); - const rgba = new Array((nlevels+1) * 4).fill(0); // precaclucated colors + if (!hint || !this.getG()) { + ttcircle?.remove(); + return; + } - for (let lvl = 0, indx = 1; lvl <= nlevels; ++lvl) { - const l = lvl/nlevels; - while ((pal.fPoints[indx] < l) && (indx < pal.fPoints.length-1)) indx++; + if (ttcircle.empty()) { + ttcircle = this.getG().append('svg:ellipse') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none'); + } - const r1 = (pal.fPoints[indx] - l) / (pal.fPoints[indx] - pal.fPoints[indx-1]), - r2 = (l - pal.fPoints[indx-1]) / (pal.fPoints[indx] - pal.fPoints[indx-1]); + hint.changed = ttcircle.property('current_bin') !== hint.binindx; - rgba[lvl*4] = Math.min(255, Math.round((pal.fColorRed[indx-1] * r1 + pal.fColorRed[indx] * r2) / 256)); - rgba[lvl*4+1] = Math.min(255, Math.round((pal.fColorGreen[indx-1] * r1 + pal.fColorGreen[indx] * r2) / 256)); - rgba[lvl*4+2] = Math.min(255, Math.round((pal.fColorBlue[indx-1] * r1 + pal.fColorBlue[indx] * r2) / 256)); - rgba[lvl*4+3] = Math.min(255, Math.round((pal.fColorAlpha[indx-1] * r1 + pal.fColorAlpha[indx] * r2) / 256)); + if (hint.changed) { + ttcircle.attr('cx', hint.x) + .attr('cy', hint.y) + .attr('rx', Math.round(hint.radius)) + .attr('ry', Math.round(hint.radius)) + .style('fill', 'none') + .style('stroke', hint.color1) + .property('current_bin', hint.binindx); } + } - return rgba; + /** @summary Process tooltip event */ + processTooltipEvent(pnt) { + const hint = this.extractTooltip(pnt); + if (!pnt || !pnt.disabled) + this.showTooltip(hint); + return hint; } - /** @summary Create url using image buffer - * @private */ - async makeUrlFromImageBuf(obj, fp) { - const nlevels = 1000; - this.rgba = this.createRGBA(nlevels); // precaclucated colors + /** @summary Draw TGraphPolar */ + static async draw(dom, graph, opt) { + const painter = new TGraphPolarPainter(dom, graph, opt); + painter.decodeOptions(opt); - let min = obj.fImgBuf[0], max = obj.fImgBuf[0]; - for (let k = 1; k < obj.fImgBuf.length; ++k) { - const v = obj.fImgBuf[k]; - min = Math.min(v, min); - max = Math.max(v, max); + const main = painter.getMainPainter(); + if (main && !main.$polargram) { + console.error('Cannot superimpose TGraphPolar with plain histograms'); + return null; } - // does not work properly in Node.js, causes 'Maximum call stack size exceeded' error - // min = Math.min.apply(null, obj.fImgBuf), - // max = Math.max.apply(null, obj.fImgBuf); + let pr = Promise.resolve(null); + if (!main) { + // indicate that axis defined by this graph + painter._draw_axis = true; + pr = TGraphPolargramPainter.draw(dom, painter.createPolargram(graph), painter.options.Axis); + } - // create countor like in hist painter to allow palette drawing - this.fContour = { - arr: new Array(200), - rgba: this.rgba, - getLevels() { return this.arr; }, - getPaletteColor(pal, zval) { - if (!this.arr || !this.rgba) return 'white'; - const indx = Math.round((zval - this.arr[0]) / (this.arr[this.arr.length-1] - this.arr[0]) * (this.rgba.length-4)/4) * 4; - return '#' + toHex(this.rgba[indx], 1) + toHex(this.rgba[indx+1], 1) + toHex(this.rgba[indx+2], 1) + toHex(this.rgba[indx+3], 1); - } - }; - for (let k = 0; k < 200; k++) - this.fContour.arr[k] = min + (max-min)/(200-1)*k; + return pr.then(gram_painter => { + gram_painter?.setSecondaryId(painter, 'polargram'); + painter.addToPadPrimitives(); + return painter.drawGraphPolar(); + }).then(() => painter.drawTitle(true)); + } - if (min >= max) max = min + 1; +} // class TGraphPolarPainter - const z = this.getImageZoomRange(fp, obj.fConstRatio, obj.fWidth, obj.fHeight), - pr = isNodeJs() - ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h => h.default.createCanvas(z.xmax - z.xmin, z.ymax - z.ymin)) - : new Promise(resolveFunc => { - const c = document.createElement('canvas'); - c.width = z.xmax - z.xmin; - c.height = z.ymax - z.ymin; - resolveFunc(c); - }); +var TGraphPolarPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TGraphPolarPainter: TGraphPolarPainter, +TGraphPolargramPainter: TGraphPolargramPainter +}); - return pr.then(canvas => { - const context = canvas.getContext('2d'), - imageData = context.getImageData(0, 0, canvas.width, canvas.height), - arr = imageData.data; +/** @summary Create log scale for axis bins + * @private */ +function produceTAxisLogScale(axis, num, min, max) { + let lmin, lmax; - for (let i = z.ymin; i < z.ymax; ++i) { - let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4; - const row = i * obj.fWidth; - for (let j = z.xmin; j < z.xmax; ++j) { - let iii = Math.round((obj.fImgBuf[row + j] - min) / (max - min) * nlevels) * 4; - // copy rgba value for specified point - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; - } - } + if (max > 0) { + lmax = Math.log(max); + lmin = min > 0 ? Math.log(min) : lmax - 5; + } else { + lmax = -10; + lmin = -15; + } - context.putImageData(imageData, 0, 0); + axis.fNbins = num; + axis.fXbins = new Array(num + 1); + for (let i = 0; i <= num; ++i) + axis.fXbins[i] = Math.exp(lmin + i / num * (lmax - lmin)); + axis.fXmin = Math.exp(lmin); + axis.fXmax = Math.exp(lmax); +} - return { url: canvas.toDataURL(), constRatio: obj.fConstRatio, can_zoom: true }; - }); +function scanTF1Options(opt) { + if (!isStr(opt)) + opt = ''; + let p = opt.indexOf(';webcanv_hist'), _webcanv_hist = false, _use_saved = 0; + if (p >= 0) { + _webcanv_hist = true; + opt = opt.slice(0, p); + } + p = opt.indexOf(';force_saved'); + if (p >= 0) { + _use_saved = 2; + opt = opt.slice(0, p); + } + p = opt.indexOf(';prefer_saved'); + if (p >= 0) { + _use_saved = 1; + opt = opt.slice(0, p); } + return { opt, _webcanv_hist, _use_saved }; +} - getImageZoomRange(fp, constRatio, width, height) { - const res = { xmin: 0, xmax: width, ymin: 0, ymax: height }; - if (!fp) return res; - let offx = 0, offy = 0, sizex = width, sizey = height; +/** + * @summary Painter for TF1 object + * + * @private + */ - if (constRatio) { - const image_ratio = height/width, - frame_ratio = fp.getFrameHeight() / fp.getFrameWidth(); +class TF1Painter extends TH1Painter$2 { - if (image_ratio > frame_ratio) { - const w2 = height / frame_ratio; - offx = Math.round((w2 - width)/2); - sizex = Math.round(w2); - } else { - const h2 = frame_ratio * width; - offy = Math.round((h2 - height)/2); - sizey = Math.round(h2); - } - } + #use_saved_points; // use saved points for drawing + #func; // func object + #tmp_tooltip; // temporary tooltip + #fail_eval; // fail evaluation of function - if (fp.zoom_xmin !== fp.zoom_xmax) { - res.xmin = Math.min(width, Math.max(0, Math.round(fp.zoom_xmin * sizex) - offx)); - res.xmax = Math.min(width, Math.max(0, Math.round(fp.zoom_xmax * sizex) - offx)); - } - if (fp.zoom_ymin !== fp.zoom_ymax) { - res.ymin = Math.min(height, Math.max(0, Math.round(fp.zoom_ymin * sizey) - offy)); - res.ymax = Math.min(height, Math.max(0, Math.round(fp.zoom_ymax * sizey) - offy)); - } - return res; - } + /** @summary Assign function */ + setFunc(f) { this.#func = f; } - /** @summary Produce data url from png buffer */ - async makeUrlFromPngBuf(obj, fp) { - const buf = obj.fPngBuf; - let pngbuf = ''; + /** @summary Returns drawn object name */ + getObjectName() { return this.#func?.fName ?? 'func'; } - if (isStr(buf)) - pngbuf = buf; - else { - for (let k = 0; k < buf.length; ++k) - pngbuf += String.fromCharCode(buf[k] < 0 ? 256 + buf[k] : buf[k]); - } + /** @summary Returns drawn object class name */ + getClassName() { return this.#func?._typename ?? clTF1; } - const res = { url: 'data:image/png;base64,' + btoa_func(pngbuf), constRatio: obj.fConstRatio, can_zoom: fp && !isNodeJs() }, - doc = getDocument(); + /** @summary Returns true while function is drawn */ + isTF1() { return true; } - if (!res.can_zoom || ((fp?.zoom_xmin === fp?.zoom_xmax) && (fp?.zoom_ymin === fp?.zoom_ymax))) - return res; + isTF12() { return this.getClassName() === clTF12; } - return new Promise(resolveFunc => { - const image = doc.createElement('img'); + /** @summary Returns primary function which was then drawn as histogram */ + getPrimaryObject() { return this.#func; } - image.onload = () => { - const canvas = doc.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; + /** @summary Update function */ + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; + delete obj.evalPar; + const histo = this.getHisto(); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0); + if (this._webcanv_hist) { + const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH1D); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); + } - const arr = context.getImageData(0, 0, image.width, image.height).data, - z = this.getImageZoomRange(fp, res.constRatio, image.width, image.height), - canvas2 = doc.createElement('canvas'); - canvas2.width = z.xmax - z.xmin; - canvas2.height = z.ymax - z.ymin; + this.setFunc(obj); + this.createTF1Histogram(obj, histo); + this.scanContent(); + return true; + } - const context2 = canvas2.getContext('2d'), - imageData2 = context2.getImageData(0, 0, canvas2.width, canvas2.height), - arr2 = imageData2.data; + /** @summary Redraw TF1 + * @private */ + redraw(reason) { + if (!this.#use_saved_points && (reason === 'logx' || reason === 'zoom')) { + this.createTF1Histogram(this.#func, this.getHisto()); + this.scanContent(); + } - for (let i = z.ymin; i < z.ymax; ++i) { - let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4, - src = ((image.height - i - 1) * image.width + z.xmin) * 4; - for (let j = z.xmin; j < z.xmax; ++j) { - // copy rgba value for specified point - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - } - } + return super.redraw(reason); + } - context2.putImageData(imageData2, 0, 0); + /** @summary Create histogram for TF1 drawing + * @private */ + createTF1Histogram(tf1, hist) { + const fp = this.getFramePainter(), + pad = this.getPadPainter()?.getRootPad(true), + logx = pad?.fLogx, + gr = fp?.getGrFuncs(this.second_x, this.second_y); + let xmin = tf1.fXmin, xmax = tf1.fXmax, np = Math.max(tf1.fNpx, 100); - res.url = canvas2.toDataURL(); + if (gr?.zoom_xmin !== gr?.zoom_xmax) { + const dx = (xmax - xmin) / np; + if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) + xmin = Math.max(xmin, gr.zoom_xmin - dx); + if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) + xmax = Math.min(xmax, gr.zoom_xmax + dx); + } - resolveFunc(res); - }; + this.#use_saved_points = (tf1.fSave.length > 3) && (settings.PreferSavedPoints || (this._use_saved > 1)); - image.onerror = () => resolveFunc(res); + const ensureBins = num => { + if (hist.fNcells !== num + 2) { + hist.fNcells = num + 2; + hist.fArray = new Float32Array(hist.fNcells); + } + hist.fArray.fill(0); + hist.fXaxis.fNbins = num; + hist.fXaxis.fXbins = []; + }; - image.src = res.url; - }); - } + this.#fail_eval = undefined; - /** @summary Draw image */ - async drawImage() { - const obj = this.getObject(), - fp = this.getFramePainter(), - rect = fp?.getFrameRect() ?? this.getPadPainter().getPadRect(); + // this.#use_saved_points = true; - this.wheel_zoomy = true; + if (!this.#use_saved_points) { + let iserror = false; - if (obj._blob) { - // try to process blob data due to custom streamer - if ((obj._blob.length === 15) && !obj._blob[0]) { - obj.fImageQuality = obj._blob[1]; - obj.fImageCompression = obj._blob[2]; - obj.fConstRatio = obj._blob[3]; - obj.fPalette = { - _typename: clTImagePalette, - fUniqueID: obj._blob[4], - fBits: obj._blob[5], - fNumPoints: obj._blob[6], - fPoints: obj._blob[7], - fColorRed: obj._blob[8], - fColorGreen: obj._blob[9], - fColorBlue: obj._blob[10], - fColorAlpha: obj._blob[11] - }; + if (!tf1.evalPar) { + try { + if (this.isTF12()) { + if (proivdeEvalPar(tf1.fF2)) { + tf1.evalPar = function(x) { + return this.fCase ? this.fF2.evalPar(x, this.fXY) : this.fF2.evalPar(this.fXY, x); + }; + } else + iserror = true; + } else if (!proivdeEvalPar(tf1)) + iserror = true; + } catch { + iserror = true; + } + } - obj.fWidth = obj._blob[12]; - obj.fHeight = obj._blob[13]; - obj.fImgBuf = obj._blob[14]; + ensureBins(np); - if ((obj.fWidth * obj.fHeight !== obj.fImgBuf.length) || - (obj.fPalette.fNumPoints !== obj.fPalette.fPoints.length)) { - console.error(`TASImage _blob decoding error ${obj.fWidth * obj.fHeight} != ${obj.fImgBuf.length} ${obj.fPalette.fNumPoints} != ${obj.fPalette.fPoints.length}`); - delete obj.fImgBuf; - delete obj.fPalette; - } - } else if ((obj._blob.length === 3) && obj._blob[0]) { - obj.fPngBuf = obj._blob[2]; - if (obj.fPngBuf?.length !== obj._blob[1]) { - console.error(`TASImage with png buffer _blob error ${obj._blob[1]} != ${obj.fPngBuf?.length}`); - delete obj.fPngBuf; + if (logx) + produceTAxisLogScale(hist.fXaxis, np, xmin, xmax); + else { + hist.fXaxis.fXmin = xmin; + hist.fXaxis.fXmax = xmax; + } + + for (let n = 0; (n < np) && !iserror; n++) { + const x = hist.fXaxis.GetBinCenter(n + 1); + let y = 0; + try { + y = tf1.evalPar(x); + } catch { + iserror = true; } - } else - console.error(`TASImage _blob len ${obj._blob.length} not recognized`); - delete obj._blob; - } + if (!iserror) + hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); + } - let promise; + if (iserror) + this.#fail_eval = true; - if (obj.fImgBuf && obj.fPalette) - promise = this.makeUrlFromImageBuf(obj, fp); - else if (obj.fPngBuf) - promise = this.makeUrlFromPngBuf(obj, fp); - else - promise = Promise.resolve(null); + if (iserror && (tf1.fSave.length > 3)) + this.#use_saved_points = true; + } - return promise.then(res => { - if (!res?.url) - return this; + // in the case there were points have saved and we cannot calculate function + // if we don't have the user's function + if (this.#use_saved_points) { + np = tf1.fSave.length - 3; + let custom_xaxis = null; + xmin = tf1.fSave[np + 1]; + xmax = tf1.fSave[np + 2]; - const img = this.createG(!!fp) - .append('image') - .attr('href', res.url) - .attr('width', rect.width) - .attr('height', rect.height) - .attr('preserveAspectRatio', res.constRatio ? null : 'none'); + if (xmin === xmax) { + const mp = this.getMainPainter(); + if (isFunc(mp?.getHisto)) + custom_xaxis = mp?.getHisto()?.fXaxis; + } - if (!this.isBatchMode()) { - if (settings.MoveResize || settings.ContextMenu) - img.style('pointer-events', 'visibleFill'); + if (custom_xaxis) { + ensureBins(hist.fXaxis.fNbins); + Object.assign(hist.fXaxis, custom_xaxis); + // TODO: find first bin - if (res.can_zoom) - img.style('cursor', 'pointer'); + for (let n = 0; n < np; ++n) { + const y = tf1.fSave[n]; + hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); + } + } else { + ensureBins(tf1.fNpx); + hist.fXaxis.fXmin = tf1.fXmin; + hist.fXaxis.fXmax = tf1.fXmax; + + for (let n = 0; n < tf1.fNpx; ++n) { + const y = _getTF1Save(tf1, hist.fXaxis.GetBinCenter(n + 1)); + hist.setBinContent(n + 1, Number.isFinite(y) ? y : 0); + } } + } - assignContextMenu(this); + hist.fName = 'Func'; + setHistogramTitle(hist, tf1.fTitle); + hist.fMinimum = tf1.fMinimum; + hist.fMaximum = tf1.fMaximum; + hist.fLineColor = tf1.fLineColor; + hist.fLineStyle = tf1.fLineStyle; + hist.fLineWidth = tf1.fLineWidth; + hist.fFillColor = tf1.fFillColor; + hist.fFillStyle = tf1.fFillStyle; + hist.fMarkerColor = tf1.fMarkerColor; + hist.fMarkerStyle = tf1.fMarkerStyle; + hist.fMarkerSize = tf1.fMarkerSize; + hist.fBits |= kNoStats; + } - if (!fp || !res.can_zoom) - return this; + /** @summary Extract function ranges */ + extractAxesProperties(ndim) { + super.extractAxesProperties(ndim); - return this.drawColorPalette(this.options.Zscale, true).then(() => { - fp.setAxesRanges(create$1(clTAxis), 0, 1, create$1(clTAxis), 0, 1, null, 0, 0); - fp.createXY({ ndim: 2, check_pad_range: false }); - return fp.addInteractivity(); - }); - }); - } + const func = this.#func, nsave = func?.fSave.length ?? 0; - /** @summary Fill TASImage context */ - fillContextMenuItems(menu) { - const obj = this.getObject(); - if (obj) { - menu.addchk(obj.fConstRatio, 'Const ratio', flag => { - obj.fConstRatio = flag; - this.interactiveRedraw('pad', `exec:SetConstRatio(${flag})`); - }, 'Change const ratio flag of image'); + if (nsave > 3 && this.#use_saved_points) { + this.xmin = Math.min(this.xmin, func.fSave[nsave - 2]); + this.xmax = Math.max(this.xmax, func.fSave[nsave - 1]); } - if (obj?.fPalette) { - menu.addchk(this.options.Zscale, 'Color palette', flag => { - this.options.Zscale = flag; - this.drawColorPalette(flag, true); - }, 'Toggle color palette'); + if (func) { + this.xmin = Math.min(this.xmin, func.fXmin); + this.xmax = Math.max(this.xmax, func.fXmax); } } /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis, min, max) { - const obj = this.getObject(); - - if (!obj) - return false; + const nsave = this.#func?.fSave.length ?? 0; + if ((nsave > 3) && this.#use_saved_points && (axis === 'x')) { + // in the case where the points have been saved, useful for example + // if we don't have the user's function + const nb_points = nsave - 2, + xmin = this.#func.fSave[nsave - 2], + xmax = this.#func.fSave[nsave - 1]; - if (((axis === 'x') || (axis === 'y')) && (max - min > 0.01)) return true; + return Math.abs(xmax - xmin) / nb_points < Math.abs(max - min); + } - return false; + // if function calculated, one always could zoom inside + return (axis === 'x') || (axis === 'y'); } - /** @summary Draw color palette - * @private */ - async drawColorPalette(enabled, can_move) { - if (!this.isMainPainter()) - return null; + /** @summary return tooltips for TF2 */ + getTF1Tooltips(pnt) { + this.#tmp_tooltip = undefined; + const lines = [this.getObjectHint()], + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); - if (!this.draw_palette) { - const pal = create$1(clTPaletteAxis); - Object.assign(pal, { fX1NDC: 0.91, fX2NDC: 0.95, fY1NDC: 0.1, fY2NDC: 0.9, fInit: 1 }); - pal.fAxis.fChopt = '+'; - this.draw_palette = pal; - this._color_palette = true; // to emulate behaviour of hist painter + if (!funcs || !isFunc(this.#func?.evalPar)) { + lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); + return lines; } - let pal_painter = this.getPadPainter().findPainterFor(this.draw_palette); + const x = funcs.revertAxis('x', pnt.x); + let y = 0, gry = 0, iserror = false; - if (!enabled) { - if (pal_painter) { - pal_painter.Enabled = false; - pal_painter.removeG(); // completely remove drawing without need to redraw complete pad - } - return null; + try { + y = this.#func.evalPar(x); + gry = Math.round(funcs.gry(y)); + } catch { + iserror = true; } - const fp = this.getFramePainter(); + lines.push('x = ' + funcs.axisAsText('x', x), + 'value = ' + (iserror ? '' : floatToString(y, gStyle.fStatFormat))); - // keep palette width - if (can_move && fp) { - const pal = this.draw_palette; - pal.fX2NDC = fp.fX2NDC + 0.01 + (pal.fX2NDC - pal.fX1NDC); - pal.fX1NDC = fp.fX2NDC + 0.01; - pal.fY1NDC = fp.fY1NDC; - pal.fY2NDC = fp.fY2NDC; + if (!iserror) + this.#tmp_tooltip = { y, gry }; + return lines; + } + + /** @summary process tooltip event for TF1 object */ + processTooltipEvent(pnt) { + if (this.#use_saved_points) + return super.processTooltipEvent(pnt); + + let ttrect = this.getG()?.selectChild('.tooltip_bin'); + + if (!this.getG() || !pnt) { + ttrect?.remove(); + return null; } - if (pal_painter) { - pal_painter.Enabled = true; - return pal_painter.drawPave(''); + const res = { + name: this.#func?.fName, title: this.#func?.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getTF1Tooltips(pnt), exact: true, menu: true + }; + + if (pnt.disabled) + ttrect.remove(); + else { + if (ttrect.empty()) { + ttrect = this.getG().append('svg:circle') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .style('fill', 'none') + .attr('r', (this.lineatt?.width ?? 1) + 4); + } + + ttrect.attr('cx', pnt.x) + .attr('cy', this.#tmp_tooltip?.gry ?? pnt.y); + if (this.lineatt) + ttrect.call(this.lineatt.func); } - const prev_name = this.selectCurrentPad(this.getPadName()); + return res; + } - return TPavePainter.draw(this.getDom(), this.draw_palette).then(p => { - pal_painter = p; + /** @summary fill information for TWebCanvas + * @desc Used to inform web canvas when evaluation failed + * @private */ + fillWebObjectOptions(opt) { + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; + } - this.selectCurrentPad(prev_name); - // mark painter as secondary - not in list of TCanvas primitives - pal_painter.setSecondary(this); + /** @summary draw TF1 object */ + static async draw(dom, tf1, opt) { + const web = scanTF1Options(opt); + opt = web.opt; + delete web.opt; + let hist; - // make dummy redraw, palette will be updated only from histogram painter - pal_painter.redraw = function() {}; - }); - } + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH1D); - /** @summary Toggle colz draw option - * @private */ - toggleColz() { - if (this.getObject()?.fPalette) { - this.options.Zscale = !this.options.Zscale; - return this.drawColorPalette(this.options.Zscale, true); + if (!hist) { + hist = createHistogram(clTH1D, 100); + hist.fBits |= kNoStats; } - } - /** @summary Redraw image */ - redraw() { - return this.drawImage(); - } + if (!opt && getElementMainPainter(dom)) + opt = 'same'; - /** @summary Process click on TASImage-defined buttons - * @desc may return promise or simply false */ - clickButton(funcname) { - if (this.isMainPainter() && funcname === 'ToggleColorZ') - return this.toggleColz(); + const painter = new TF1Painter(dom, hist); - return false; - } + painter.setFunc(tf1); + Object.assign(painter, web); - /** @summary Fill pad toolbar for TASImage */ - fillToolbar() { - const pp = this.getPadPainter(); - if (pp && this.getObject()?.fPalette) { - pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); - pp.showPadButtons(); - } - } + painter.createTF1Histogram(tf1, hist); - /** @summary Draw TASImage object */ - static async draw(dom, obj, opt) { - const painter = new TASImagePainter(dom, obj, opt); - painter.setAsMainPainter(); - painter.decodeOptions(opt); - return ensureTCanvas(painter, false) - .then(() => painter.drawImage()) - .then(() => { - painter.fillToolbar(); - return painter; - }); + return THistPainter._drawHist(painter, opt); } -} // class TASImagePainter +} // class TF1Painter -var TASImagePainter$1 = /*#__PURE__*/Object.freeze({ +var TF1Painter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, -TASImagePainter: TASImagePainter +TF1Painter: TF1Painter, +produceTAxisLogScale: produceTAxisLogScale, +scanTF1Options: scanTF1Options }); -const kNormal = 1, /* kLessTraffic = 2, */ kOffline = 3; +const kIsBayesian = BIT(14), // Bayesian statistics are used + kPosteriorMode = BIT(15), // Use posterior mean for best estimate (Bayesian statistics) + // kShortestInterval = BIT(16), // Use shortest interval, not implemented - too complicated + kUseBinPrior = BIT(17), // Use a different prior for each bin + kUseWeights = BIT(18), // Use weights + getBetaAlpha = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].first : obj.fBeta_alpha; }, + getBetaBeta = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].second : obj.fBeta_beta; }; -class RObjectPainter extends ObjectPainter { +/** + * @summary Painter for TEfficiency object + * + * @private + */ - constructor(dom, obj, opt, csstype) { - super(dom, obj, opt); - this.csstype = csstype; - } +class TEfficiencyPainter extends ObjectPainter { - /** @summary Evaluate v7 attributes using fAttr storage and configured RStyle */ - v7EvalAttr(name, dflt) { - const obj = this.getObject(); - if (!obj) return dflt; - if (this.cssprefix) name = this.cssprefix + name; + /** @summary Calculate efficiency */ + getEfficiency(obj, bin) { + const BetaMean = (a, b) => { return (a <= 0 || b <= 0) ? 0 : a / (a + b); }, + BetaMode = (a, b) => { + if (a <= 0 || b <= 0) + return 0; + if (a <= 1 || b <= 1) { + if (a < b) + return 0; + if (a > b) + return 1; + if (a === b) + return 0.5; // cannot do otherwise + } + return (a - 1.0) / (a + b - 2.0); + }, + total = obj.fTotalHistogram.fArray[bin], // should work for both 1-d and 2-d + passed = obj.fPassedHistogram.fArray[bin]; // should work for both 1-d and 2-d - const type_check = res => { - if (dflt === undefined) return res; - const typ1 = typeof dflt, typ2 = typeof res; - if (typ1 === typ2) return res; - if (typ1 === 'boolean') { - if (typ2 === 'string') return (res !== '') && (res !== '0') && (res !== 'no') && (res !== 'off'); - return !!res; - } - if ((typ1 === 'number') && (typ2 === 'string')) - return parseFloat(res); - return res; - }; + if (obj.TestBit(kIsBayesian)) { + // parameters for the beta prior distribution + const alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha, + beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; - if (obj.fAttr?.m) { - const value = obj.fAttr.m[name]; - if (value) return type_check(value.v); // found value direct in attributes - } + let aa, bb; + if (obj.TestBit(kUseWeights)) { + const tw = total, // fTotalHistogram->GetBinContent(bin); + tw2 = obj.fTotalHistogram.fSumw2 ? obj.fTotalHistogram.fSumw2[bin] : Math.abs(total), + pw = passed; // fPassedHistogram->GetBinContent(bin); - if (this.rstyle?.fBlocks) { - const blks = this.rstyle.fBlocks; - for (let k = 0; k < blks.length; ++k) { - const block = blks[k], - match = (this.csstype && (block.selector === this.csstype)) || - (obj.fId && (block.selector === ('#' + obj.fId))) || - (obj.fCssClass && (block.selector === ('.' + obj.fCssClass))); + if (tw2 <= 0) + return pw / tw; - if (match && block.map?.m) { - const value = block.map.m[name.toLowerCase()]; - if (value) return type_check(value.v); - } + // tw/tw2 re-normalize the weights + const norm = tw / tw2; + aa = pw * norm + alpha; + bb = (tw - pw) * norm + beta; + } else { + aa = passed + alpha; + bb = total - passed + beta; } + + return !obj.TestBit(kPosteriorMode) ? BetaMean(aa, bb) : BetaMode(aa, bb); } - return dflt; + return total ? passed / total : 0; } - /** @summary Set v7 attributes value */ - v7SetAttr(name, value) { - const obj = this.getObject(); - if (this.cssprefix) name = this.cssprefix + name; + /** @summary Calculate efficiency error low */ + getEfficiencyErrorLow(obj, bin, value) { + const total = obj.fTotalHistogram.fArray[bin], + passed = obj.fPassedHistogram.fArray[bin]; + let alpha = 0, beta = 0; + if (obj.TestBit(kIsBayesian)) { + alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; + beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; + } - if (obj?.fAttr?.m) - obj.fAttr.m[name] = { v: value }; + return value - this.fBoundary(total, passed, obj.fConfLevel, false, alpha, beta); } - /** @summary Decode pad length from string, return pixel value */ - v7EvalLength(name, sizepx, dflt) { - if (sizepx <= 0) sizepx = 1; - - const value = this.v7EvalAttr(name); - - if (value === undefined) - return Math.round(dflt*sizepx); + /** @summary Calculate efficiency error low up */ + getEfficiencyErrorUp(obj, bin, value) { + const total = obj.fTotalHistogram.fArray[bin], + passed = obj.fPassedHistogram.fArray[bin]; + let alpha = 0, beta = 0; + if (obj.TestBit(kIsBayesian)) { + alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; + beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; + } - if (typeof value === 'number') - return Math.round(value*sizepx); + return this.fBoundary(total, passed, obj.fConfLevel, true, alpha, beta) - value; + } - if (value === null) - return 0; + /** @summary Copy drawing attributes */ + copyAttributes(obj, eff) { + ['fLineColor', 'fLineStyle', 'fLineWidth', 'fFillColor', 'fFillStyle', 'fMarkerColor', 'fMarkerStyle', 'fMarkerSize'].forEach(name => { obj[name] = eff[name]; }); + } - let norm = 0, px = 0, val = value, operand = 0, pos = 0; + /** @summary Create graph for the drawing of 1-dim TEfficiency */ + createGraph(/* eff */) { + const gr = create$1(clTGraphAsymmErrors); + gr.fName = 'eff_graph'; + return gr; + } - while (val) { - // skip empty spaces - while ((pos < val.length) && ((val[pos] === ' ') || (val[pos] === '\t'))) - ++pos; + /** @summary Create histogram for the drawing of 2-dim TEfficiency */ + createHisto(eff) { + const nbinsx = eff.fTotalHistogram.fXaxis.fNbins, + nbinsy = eff.fTotalHistogram.fYaxis.fNbins, + hist = createHistogram(clTH2F, nbinsx, nbinsy); + Object.assign(hist.fXaxis, eff.fTotalHistogram.fXaxis); + Object.assign(hist.fYaxis, eff.fTotalHistogram.fYaxis); + hist.fName = 'eff_histo'; + return hist; + } - if (pos >= val.length) - break; + /** @summary Fill graph with points from efficiency object */ + fillGraph(gr, opt) { + const eff = this.getObject(), + xaxis = eff.fTotalHistogram.fXaxis, + npoints = xaxis.fNbins, + plot0Bins = (opt.indexOf('e0') >= 0); - if ((val[pos] === '-') || (val[pos] === '+')) { - if (operand) { - console.log('Fail to parse RPadLength ' + value); - return dflt; - } - operand = (val[pos] === '-') ? -1 : 1; - pos++; + for (let n = 0, j = 0; n < npoints; ++n) { + if (!plot0Bins && eff.fTotalHistogram.getBinContent(n + 1) === 0) continue; - } - - if (pos > 0) { val = val.slice(pos); pos = 0; } - while ((pos < val.length) && (((val[pos] >= '0') && (val[pos] <= '9')) || (val[pos] === '.'))) pos++; - - const v = parseFloat(val.slice(0, pos)); - if (!Number.isFinite(v)) { - console.log(`Fail to parse RPadLength ${value}`); - return Math.round(dflt*sizepx); - } + const value = this.getEfficiency(eff, n + 1); - val = val.slice(pos); - pos = 0; - if (!operand) operand = 1; - if (val && (val[0] === '%')) { - val = val.slice(1); - norm += operand*v*0.01; - } else if ((val.length > 1) && (val[0] === 'p') && (val[1] === 'x')) { - val = val.slice(2); - px += operand*v; - } else - norm += operand*v; + gr.fX[j] = xaxis.GetBinCenter(n + 1); + gr.fY[j] = value; + gr.fEXlow[j] = xaxis.GetBinCenter(n + 1) - xaxis.GetBinLowEdge(n + 1); + gr.fEXhigh[j] = xaxis.GetBinLowEdge(n + 2) - xaxis.GetBinCenter(n + 1); + gr.fEYlow[j] = this.getEfficiencyErrorLow(eff, n + 1, value); + gr.fEYhigh[j] = this.getEfficiencyErrorUp(eff, n + 1, value); - operand = 0; + gr.fNpoints = ++j; } - return Math.round(norm*sizepx + px); + gr.fTitle = eff.fTitle; + this.copyAttributes(gr, eff); } - /** @summary Evaluate RColor using attribute storage and configured RStyle */ - v7EvalColor(name, dflt) { - let val = this.v7EvalAttr(name, ''); - if (!val || !isStr(val)) return dflt; + /** @summary Fill graph with points from efficiency object */ + fillHisto(hist) { + const eff = this.getObject(), + nbinsx = hist.fXaxis.fNbins, + nbinsy = hist.fYaxis.fNbins; - if (val === 'auto') { - const pp = this.getPadPainter(); - if (pp?._auto_color_cnt !== undefined) { - const pal = pp.getHistPalette(), - cnt = pp._auto_color_cnt++; - let num = pp._num_primitives - 1; - if (num < 2) num = 2; - val = pal ? pal.getColorOrdinal((cnt % num) / num) : 'blue'; - if (!this._auto_colors) this._auto_colors = {}; - this._auto_colors[name] = val; - } else if (this._auto_colors && this._auto_colors[name]) - val = this._auto_colors[name]; - else { - console.error(`Autocolor ${name} not defined yet - please check code`); - val = ''; - } - } else if (val[0] === '[') { - const ordinal = parseFloat(val.slice(1, val.length-1)); - val = 'black'; - if (Number.isFinite(ordinal)) { - const pal = this.getPadPainter()?.getHistPalette(); - if (pal) val = pal.getColorOrdinal(ordinal); + for (let i = 0; i < nbinsx + 2; ++i) { + for (let j = 0; j < nbinsy + 2; ++j) { + const bin = hist.getBin(i, j); + hist.fArray[bin] = this.getEfficiency(eff, bin); } } - return val; + + hist.fTitle = eff.fTitle; + hist.fBits |= kNoStats; + this.copyAttributes(hist, eff); } - /** @summary Evaluate RAttrText properties - * @return {Object} FontHandler, can be used directly for the text drawing */ - v7EvalFont(name, dflts, fontScale) { - if (!dflts) - dflts = {}; - else if (typeof dflts === 'number') - dflts = { size: dflts }; + /** @summary Draw function */ + drawFunction(indx) { + const eff = this.getObject(); - const pp = this.getPadPainter(), - rfont = pp?._dfltRFont || { fFamily: 'Arial', fStyle: '', fWeight: '' }, - text_angle = this.v7EvalAttr(name + '_angle', 0), - text_align = this.v7EvalAttr(name + '_align', dflts.align || 'none'), - text_color = this.v7EvalColor(name + '_color', dflts.color || 'none'), - font_family = this.v7EvalAttr(name + '_font_family', rfont.fFamily || 'Arial'), - font_style = this.v7EvalAttr(name + '_font_style', rfont.fStyle || ''), - font_weight = this.v7EvalAttr(name + '_font_weight', rfont.fWeight || ''); - let text_size = this.v7EvalAttr(name + '_size', dflts.size || 12); + if (!eff?.fFunctions || (indx >= eff.fFunctions.arr.length)) + return this; - if (isStr(text_size)) text_size = parseFloat(text_size); - if (!Number.isFinite(text_size) || (text_size <= 0)) text_size = 12; - if (!fontScale) fontScale = pp?.getPadHeight() || 100; + return TF1Painter.draw(this.getPadPainter(), eff.fFunctions.arr[indx], eff.fFunctions.opt[indx]) + .then(funcp => { + funcp?.setSecondaryId(this, `func_${indx}`); + return this.drawFunction(indx + 1); + }); + } - const handler = new FontHandler(null, text_size, fontScale); - handler.setNameStyleWeight(font_family, font_style, font_weight); + /** @summary Fill context menu */ + fillContextMenuItems(menu) { + menu.addRedrawMenu(this); + } - if (text_angle) handler.setAngle(360 - text_angle); - if (text_align !== 'none') handler.setAlign(text_align); - if (text_color !== 'none') handler.setColor(text_color); + /** @summary Fully redraw efficiency with new draw options */ + async redrawWith(opt, skip_cleanup) { + if (!skip_cleanup) + this.getPadPainter()?.removePrimitive(this, true); - return handler; - } + if (!opt || !isStr(opt)) + opt = ''; + opt = opt.toLowerCase(); - /** @summary Create this.fillatt object based on v7 fill attributes */ - createv7AttFill(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'fill_'; + let promise, draw_total = false; - const color = this.v7EvalColor(prefix + 'color', ''), - pattern = this.v7EvalAttr(prefix + 'style', 0); + const eff = this.getObject(), + dom = this.getDrawDom(); + + if (opt[0] === 'b') { + draw_total = true; + promise = (this.ndim === 1 ? TH1Painter : TH2Painter).draw(dom, eff.fTotalHistogram, opt.slice(1)); + } else if (this.ndim === 1) { + if (!opt) + opt = 'ap'; + if ((opt.indexOf('same') < 0) && (opt.indexOf('a') < 0)) + opt += 'a'; + if (opt.indexOf('p') < 0) + opt += 'p'; + + const gr = this.createGraph(eff); + this.fillGraph(gr, opt); + promise = TGraphPainter$1.draw(dom, gr, opt); + } else { + if (!opt) + opt = 'col'; + const hist = this.createHisto(eff); + this.fillHisto(hist, opt); + promise = TH2Painter.draw(dom, hist, opt); + } - this.createAttFill({ pattern, color, color_as_svg: true }); + return promise.then(subp => { + subp?.setSecondaryId(this, 'eff'); + this.addToPadPrimitives(); + return draw_total ? this : this.drawFunction(0); + }); } - /** @summary Create this.lineatt object based on v7 line attributes */ - createv7AttLine(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'line_'; + /** @summary Draw TEfficiency object */ + static async draw(dom, eff, opt) { + if (!eff || !eff.fTotalHistogram) + return null; - const color = this.v7EvalColor(prefix + 'color', 'black'), - width = this.v7EvalAttr(prefix + 'width', 1), - style = this.v7EvalAttr(prefix + 'style', 1), - pattern = this.v7EvalAttr(prefix + 'pattern'); + const painter = new TEfficiencyPainter(dom, eff); - this.createAttLine({ color, width, style, pattern }); + if (eff.fTotalHistogram._typename.indexOf(clTH1) === 0) + painter.ndim = 1; + else if (eff.fTotalHistogram._typename.indexOf(clTH2) === 0) + painter.ndim = 2; + else + return null; - if (prefix === 'border_') - this.lineatt.setBorder(this.v7EvalAttr(prefix + 'rx', 0), this.v7EvalAttr(prefix + 'ry', 0)); + painter.fBoundary = getTEfficiencyBoundaryFunc(eff.fStatisticOption, eff.TestBit(kIsBayesian)); + + return painter.redrawWith(opt, true); } - /** @summary Create this.markeratt object based on v7 attributes */ - createv7AttMarker(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'marker_'; +} // class TEfficiencyPainter - const color = this.v7EvalColor(prefix + 'color', 'black'), - size = this.v7EvalAttr(prefix + 'size', 0.01), - style = this.v7EvalAttr(prefix + 'style', 1), - refsize = (size >= 1) ? 1 : (this.getPadPainter()?.getPadHeight() || 100); +var TEfficiencyPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TEfficiencyPainter: TEfficiencyPainter +}); - this.createAttMarker({ color, size, style, refsize }); - } +/** + * @summary Painter for TScatter object. + * + * @private + */ - /** @summary Create RChangeAttr, which can be applied on the server side - * @private */ - v7AttrChange(req, name, value, kind) { - if (!this.snapid) - return false; +class TScatterPainter extends TGraphPainter$1 { - if (!req._typename) { - req._typename = `${nsREX}RChangeAttrRequest`; - req.ids = []; - req.names = []; - req.values = []; - req.update = true; - } + #color_palette; // color palette + #contour; // colors contour - if (this.cssprefix) name = this.cssprefix + name; - req.ids.push(this.snapid); - req.names.push(name); - let obj = null; + /** @summary Cleanup painter */ + cleanup() { + this.clearHistPalette(); + this.#contour = undefined; + super.cleanup(); + } - if ((value === null) || (value === undefined)) { - if (!kind) kind = 'none'; - if (kind !== 'none') console.error(`Trying to set ${kind} for none value`); - } + /** @summary Return drawn graph object */ + getGraph() { return this.getObject()?.fGraph; } - if (!kind) { - switch (typeof value) { - case 'number': kind = 'double'; break; - case 'boolean': kind = 'boolean'; break; - } - } + /** @summary Return colors contour */ + getContour() { return this.#contour; } - obj = { _typename: `${nsREX}RAttrMap::` }; - switch (kind) { - case 'none': obj._typename += 'NoValue_t'; break; - case 'boolean': obj._typename += 'BoolValue_t'; obj.v = !!value; break; - case 'int': obj._typename += 'IntValue_t'; obj.v = parseInt(value); break; - case 'double': obj._typename += 'DoubleValue_t'; obj.v = parseFloat(value); break; - default: obj._typename += 'StringValue_t'; obj.v = isStr(value) ? value : JSON.stringify(value); break; - } + /** @summary Is TScatter object */ + isScatter() { return true; } - req.values.push(obj); - return true; - } + /** @summary Return margins for histogram ranges */ + getHistRangeMargin() { return this.getObject()?.fMargin ?? 0.1; } - /** @summary Sends accumulated attribute changes to server */ - v7SendAttrChanges(req, do_update) { - const canp = this.getCanvPainter(); - if (canp && req?._typename) { - if (do_update !== undefined) - req.update = !!do_update; - canp.v7SubmitRequest('', req); - } + /** @summary Draw axis histogram + * @private */ + async drawAxisHisto() { + const set_x = this.isDummyHistogram('x'), + set_y = this.isDummyHistogram('y'), + histo = this.createHistogram(set_x, set_y); + return TH2Painter$2.draw(this.getDrawDom(), histo, this.getOptions().Axis + ';IGNORE_PALETTE'); } - /** @summary Submit request to server-side drawable - * @param kind defines request kind, only single request a time can be submitted - * @param req is object derived from DrawableRequest, including correct _typename - * @param method is method of painter object which will be called when getting reply */ - v7SubmitRequest(kind, req, method) { - const canp = this.getCanvPainter(); - if (!isFunc(canp?.submitDrawableRequest)) return null; + /** @summary Provide palette, create if necessary + * @private */ + getPalette() { + const gr = this.getGraph(); + let pal = gr?.fFunctions?.arr?.find(func => (func._typename === clTPaletteAxis)); - // special situation when snapid not yet assigned - just keep ref until snapid is there - // maybe keep full list - for now not clear if really needed - if (!this.snapid) { - this._pending_request = { kind, req, method }; - return req; + if (!pal && gr) { + pal = create$1(clTPaletteAxis); + + const fp = this.get_fp(); + Object.assign(pal, { fX1NDC: fp.fX2NDC + 0.005, fX2NDC: fp.fX2NDC + 0.05, fY1NDC: fp.fY1NDC, fY2NDC: fp.fY2NDC, fInit: 1, $can_move: true }); + Object.assign(pal.fAxis, { fChopt: '+', fLineColor: 1, fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11, fNdiv: 510 }); + gr.fFunctions.AddFirst(pal, ''); } - return canp.submitDrawableRequest(kind, req, this, method); + return pal; } - /** @summary Assign snapid to the painter - * @desc Overwrite default method */ - assignSnapId(id) { - this.snapid = id; - if (this.snapid && this._pending_request) { - const p = this._pending_request; - this.v7SubmitRequest(p.kind, p.req, p.method); - delete this._pending_request; - } + /** @summary Update TScatter members + * @private */ + _updateMembers(scatter, obj) { + scatter.fBits = obj.fBits; + scatter.fTitle = obj.fTitle; + scatter.fNpoints = obj.fNpoints; + scatter.fColor = obj.fColor; + scatter.fSize = obj.fSize; + scatter.fMargin = obj.fMargin; + scatter.fMinMarkerSize = obj.fMinMarkerSize; + scatter.fMaxMarkerSize = obj.fMaxMarkerSize; + return super._updateMembers(scatter.fGraph, obj.fGraph); } - /** @summary Return communication mode with the server - * @desc - * kOffline means no server there, - * kLessTraffic advise not to send commands if offline functionality available - * kNormal is standard functionality with RCanvas on server side */ - v7CommMode() { - const canp = this.getCanvPainter(); - if (!canp || !canp.submitDrawableRequest || !canp._websocket) - return kOffline; - - return kNormal; + /** @summary Return Z axis used for palette drawing + * @private */ + getZaxis() { + return this.getHistogram()?.fZaxis; } - v7NormalMode() { return this.v7CommMode() === kNormal; } - - v7OfflineMode() { return this.v7CommMode() === kOffline; } - -} // class RObjectPainter - -/** - * @summary Axis painter for v7 - * - * @private - */ - -class RAxisPainter extends RObjectPainter { - - /** @summary constructor */ - constructor(dom, arg1, axis, cssprefix) { - const drawable = cssprefix ? arg1.getObject() : arg1; - super(dom, drawable, '', cssprefix ? arg1.csstype : 'axis'); - Object.assign(this, AxisPainterMethods); - this.initAxisPainter(); + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + if (axis !== 'z') + return super.canZoomInside(axis, min, max); - this.axis = axis; - if (cssprefix) { // drawing from the frame - this.embedded = true; // indicate that painter embedded into the histo painter - // this.csstype = arg1.csstype; // for the moment only via frame one can set axis attributes - this.cssprefix = cssprefix; - this.rstyle = arg1.rstyle; - } else { - // this.csstype = 'axis'; - this.cssprefix = 'axis_'; + const levels = this.#contour?.getLevels(); + if (!levels) + return false; + // match at least full color level inside + for (let i = 0; i < levels.length - 1; ++i) { + if ((min <= levels[i]) && (max >= levels[i + 1])) + return true; } + return false; } - /** @summary cleanup painter */ - cleanup() { - delete this.axis; - delete this.axis_g; - this.cleanupAxisPainter(); - super.cleanup(); + /** @summary Returns color palette associated with histogram + * @desc Create if required, checks pad and canvas for custom palette */ + getHistPalette(force) { + let pal = force ? null : this.#color_palette; + if (pal) + return pal; + const pp = this.getPadPainter(); + if (isFunc(pp?.getCustomPalette)) + pal = pp.getCustomPalette(); + if (!pal) + pal = getColorPalette(this.getOptions().Palette, pp?.isGrayscale()); + this.#color_palette = pal; + return pal; } - /** @summary Use in GED to identify kind of axis */ - getAxisType() { return 'RAttrAxis'; } - - /** @summary Configure only base parameters, later same handle will be used for drawing */ - configureZAxis(name, fp) { - this.name = name; - this.kind = kAxisNormal; - this.log = false; - const _log = this.v7EvalAttr('log', 0); - if (_log) { - this.log = true; - this.logbase = 10; - if (Math.abs(_log - Math.exp(1)) < 0.1) - this.logbase = Math.exp(1); - else if (_log > 1.9) - this.logbase = Math.round(_log); - } - fp.logz = this.log; + /** @summary Remove palette */ + clearHistPalette() { + this.#color_palette = undefined; } - /** @summary Configure axis painter - * @desc Axis can be drawn inside frame group with offset to 0 point for the frame - * Therefore one should distinguish when caclulated coordinates used for axis drawing itself or for calculation of frame coordinates - * @private */ - configureAxis(name, min, max, smin, smax, vertical, frame_range, axis_range, opts) { - if (!opts) opts = {}; - this.name = name; - this.full_min = min; - this.full_max = max; - this.kind = kAxisNormal; - this.vertical = vertical; - this.log = false; - const _log = this.v7EvalAttr('log', 0), - _symlog = this.v7EvalAttr('symlog', 0); - this.reverse = opts.reverse || false; - - if (this.v7EvalAttr('time')) { - this.kind = kAxisTime; - this.timeoffset = 0; - let toffset = this.v7EvalAttr('timeOffset'); - if (toffset !== undefined) { - toffset = parseFloat(toffset); - if (Number.isFinite(toffset)) this.timeoffset = toffset*1000; - } - } else if (this.axis?.fLabelsIndex) { - this.kind = kAxisLabels; - delete this.own_labels; - } else if (opts.labels) - this.kind = kAxisLabels; - else - this.kind = kAxisNormal; - + /** @summary Actual drawing of TScatter */ + async drawGraph() { + const fp = this.get_fp(), + hpainter = this.getMainPainter(), + scatter = this.getObject(), + hist = this.getHistogram(); - if (this.kind === kAxisTime) - this.func = time().domain([this.convertDate(smin), this.convertDate(smax)]); - else if (_symlog && (_symlog > 0)) { - this.symlog = _symlog; - this.func = symlog().constant(_symlog).domain([smin, smax]); - } else if (_log) { - if (smax <= 0) smax = 1; - if ((smin <= 0) || (smin >= smax)) - smin = smax * 0.0001; - this.log = true; - this.logbase = 10; - if (Math.abs(_log - Math.exp(1)) < 0.1) - this.logbase = Math.exp(1); - else if (_log > 1.9) - this.logbase = Math.round(_log); - this.func = log().base(this.logbase).domain([smin, smax]); - } else - this.func = linear().domain([smin, smax]); + let scale = 1, offset = 0, palette; + if (!fp || !hpainter || !scatter) + return; - this.scale_min = smin; - this.scale_max = smax; + if (scatter.fColor) { + const pal = this.getPalette(); + if (pal) + pal.$main_painter = this; - this.gr_range = axis_range || 1000; // when not specified, one can ignore it + palette = this.getHistPalette(); - const range = frame_range ?? [0, this.gr_range]; + let minc = scatter.fColor[0], maxc = scatter.fColor[0]; + for (let i = 1; i < scatter.fColor.length; ++i) { + minc = Math.min(minc, scatter.fColor[i]); + maxc = Math.max(maxc, scatter.fColor[i]); + } + if (maxc <= minc) + maxc = minc < 0 ? 0.9 * minc : (minc > 0 ? 1.1 * minc : 1); + else if ((minc > 0) && (minc < 0.3 * maxc)) + minc = 0; + this.#contour = new HistContour(minc, maxc); + this.#contour.createNormal(30); + this.#contour.configIndicies(0, 0); - this.axis_shift = range[1] - this.gr_range; + fp.zmin = minc; + fp.zmax = maxc; - if (this.reverse) - this.func.range([range[1], range[0]]); - else - this.func.range(range); + if (!fp.zoomChangedInteractive('z') && hist && hist.fMinimum !== kNoZoom && hist.fMaximum !== kNoZoom) { + fp.zoom_zmin = hist.fMinimum; + fp.zoom_zmax = hist.fMaximum; + } + } - if (this.kind === kAxisTime) - this.gr = val => this.func(this.convertDate(val)); - else if (this.log) - this.gr = val => (val < this.scale_min) ? (this.vertical ? this.func.range()[0]+5 : -5) : this.func(val); - else - this.gr = this.func; + if (scatter.fSize) { + let mins = scatter.fSize[0], maxs = scatter.fSize[0]; - delete this.format;// remove formatting func + for (let i = 1; i < scatter.fSize.length; ++i) { + mins = Math.min(mins, scatter.fSize[i]); + maxs = Math.max(maxs, scatter.fSize[i]); + } - const ndiv = this.v7EvalAttr('ndiv', 508); + if (maxs <= mins) + maxs = mins < 0 ? 0.9 * mins : (mins > 0 ? 1.1 * mins : 1); - this.nticks = ndiv % 100; - this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv/10000); + scale = (scatter.fMaxMarkerSize - scatter.fMinMarkerSize) / (maxs - mins); + offset = mins; + } - if (this.nticks > 20) this.nticks = 20; + const g = this.createG(!fp.pad_layer), + funcs = fp.getGrFuncs(), + is_zoom = (fp.zoom_zmin !== fp.zoom_zmax) && scatter.fColor, + bins = this._getBins(); - const gr_range = Math.abs(this.gr_range) || 100; + for (let i = 0; i < bins.length; ++i) { + if (is_zoom && ((scatter.fColor[i] < fp.zoom_zmin) || (scatter.fColor[i] > fp.zoom_zmax))) + continue; - if (this.kind === kAxisTime) { - if (this.nticks > 8) this.nticks = 8; + const pnt = bins[i], + grx = funcs.grx(pnt.x), + gry = funcs.gry(pnt.y), + size = scatter.fSize ? scatter.fMinMarkerSize + scale * (scatter.fSize[i] - offset) : scatter.fMarkerSize, + color = scatter.fColor ? this.#contour.getPaletteColor(palette, scatter.fColor[i]) : this.getColor(scatter.fMarkerColor), + handle = new TAttMarkerHandler({ color, size, style: scatter.fMarkerStyle }); - const scale_range = this.scale_max - this.scale_min, - tf2 = chooseTimeFormat(scale_range / gr_range, false); - let tf1 = this.v7EvalAttr('timeFormat', ''); + g.append('svg:path') + .attr('d', handle.create(grx, gry)) + .call(handle.func); + } - if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) - tf1 = chooseTimeFormat(scale_range / this.nticks, true); + return this; + } - this.tfunc1 = this.tfunc2 = timeFormat(tf1); - if (tf2 !== tf1) - this.tfunc2 = timeFormat(tf2); + /** @summary Draw TScatter object */ + static async draw(dom, obj, opt) { + return TGraphPainter$1._drawGraph(new TScatterPainter(dom, obj), opt); + } - this.format = this.formatTime; - } else if (this.log) { - if (this.nticks2 > 1) { - this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally - this.nticks2 = 1; - } - this.noexp = this.v7EvalAttr('noexp', false); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && (this.logbase === 10)) this.noexp = true; - this.moreloglabels = this.v7EvalAttr('moreloglbls', false); +} // class TScatterPainter - this.format = this.formatLog; - } else if (this.kind === kAxisLabels) { - this.nticks = 50; // for text output allow max 50 names - const scale_range = this.scale_max - this.scale_min; - if (this.nticks > scale_range) - this.nticks = Math.round(scale_range); - this.nticks2 = 1; +var TScatterPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TScatterPainter: TScatterPainter +}); - this.format = this.formatLabels; - } else { - this.order = 0; - this.ndig = 0; - this.format = this.formatNormal; - } +const kLineNDC = BIT(14); + +/** + * @summary Painter for TLine class + * @private + */ + +class TLinePainter extends ObjectPainter { + + #side; // side which is interactively moved + + /** @summary Start interactive moving */ + moveStart(x, y) { + const fullsize = Math.max(1, Math.sqrt((this.x1 - this.x2) ** 2 + (this.y1 - this.y2) ** 2)), + sz1 = Math.sqrt((x - this.x1) ** 2 + (y - this.y1) ** 2) / fullsize, + sz2 = Math.sqrt((x - this.x2) ** 2 + (y - this.y2) ** 2) / fullsize; + if (sz1 > 0.9) + this.#side = 1; + else if (sz2 > 0.9) + this.#side = -1; + else + this.#side = 0; } - /** @summary Return scale min */ - getScaleMin() { - return this.func ? this.func.domain()[0] : 0; + /** @summary Continue interactive moving */ + moveDrag(dx, dy) { + if (this.#side !== 1) { + this.x1 += dx; + this.y1 += dy; + } + if (this.#side !== -1) { + this.x2 += dx; + this.y2 += dy; + } + this.getG().select('path').attr('d', this.createPath()); } - /** @summary Return scale max */ - getScaleMax() { - return this.func ? this.func.domain()[1] : 0; + /** @summary Finish interactive moving */ + moveEnd(not_changed) { + if (not_changed) + return; + const line = this.getObject(); + let exec = '', + fx1 = this.svgToAxis('x', this.x1, this.isndc), + fx2 = this.svgToAxis('x', this.x2, this.isndc), + fy1 = this.svgToAxis('y', this.y1, this.isndc), + fy2 = this.svgToAxis('y', this.y2, this.isndc); + if (this.swap_xy) + [fx1, fy1, fx2, fy2] = [fy1, fx1, fy2, fx2]; + line.fX1 = fx1; + line.fX2 = fx2; + line.fY1 = fy1; + line.fY2 = fy2; + if (this.#side !== 1) + exec += `SetX1(${fx1});;SetY1(${fy1});;`; + if (this.#side !== -1) + exec += `SetX2(${fx2});;SetY2(${fy2});;`; + this.submitCanvExec(exec + 'Notify();;'); } - /** @summary Provide label for axis value */ - formatLabels(d) { - const indx = Math.round(d); - if (this.axis?.fLabelsIndex) { - if ((indx < 0) || (indx >= this.axis.fNBinsNoOver)) return null; - for (let i = 0; i < this.axis.fLabelsIndex.length; ++i) { - const pair = this.axis.fLabelsIndex[i]; - if (pair.second === indx) return pair.first; - } - } else { - const labels = this.getObject().fLabels; - if (labels && (indx >= 0) && (indx < labels.length)) - return labels[indx]; - } - return null; + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const line = this.getObject(), + isndc = line.TestBit(kLineNDC); + if (isndc) + return null; + const minx = Math.min(line.fX1, line.fX2), + maxx = Math.max(line.fX1, line.fX2), + miny = Math.min(line.fY1, line.fY2), + maxy = Math.max(line.fY1, line.fY2); + return { minx, miny, maxx, maxy }; } - /** @summary Creates array with minor/middle/major ticks */ - createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) this.noticksopt = true; + /** @summary Calculate line coordinates */ + prepareDraw() { + const line = this.getObject(); - const ticks = this.produceTicks(this.nticks), - handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: ticks, middle: ticks, major: ticks }; + this.isndc = line.TestBit(kLineNDC); - if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min)*1e-5; - if (res[0] > this.scale_min + delta) res.unshift(this.scale_min); - if (res[res.length-1] < this.scale_max - delta) res.push(this.scale_max); - return res; - } + const use_frame = this.isndc ? false : new DrawOptions(this.getDrawOpt()).check('FRAME'); - if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10))) { - handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); + this.createG(use_frame ? 'frame2d' : undefined); - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); + this.swap_xy = use_frame && this.getFramePainter()?.swap_xy(); - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) - handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { - handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) handle.minor = handle.middle; - } - } + const func = this.getAxisToSvgFunc(this.isndc, true); - handle.reset = function() { - this.nminor = this.nmiddle = this.nmajor = 0; - }; + this.x1 = func.x(line.fX1); + this.y1 = func.y(line.fY1); + this.x2 = func.x(line.fX2); + this.y2 = func.y(line.fY2); - handle.next = function(doround) { - if (this.nminor >= this.minor.length) return false; + if (this.swap_xy) + [this.x1, this.y1, this.x2, this.y2] = [this.y1, this.x1, this.y2, this.x2]; - this.tick = this.minor[this.nminor++]; - this.grpos = this.func(this.tick); - if (doround) this.grpos = Math.round(this.grpos); - this.kind = 3; + this.createAttLine({ attr: line }); + } - if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { - this.nmiddle++; - this.kind = 2; - } + /** @summary Create path */ + createPath() { + const x1 = Math.round(this.x1), x2 = Math.round(this.x2), y1 = Math.round(this.y1), y2 = Math.round(this.y2); + return `M${x1},${y1}` + (x1 === x2 ? `V${y2}` : (y1 === y2 ? `H${x2}` : `L${x2},${y2}`)); + } - if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { - this.nmajor++; - this.kind = 1; - } - return true; - }; + /** @summary Add extras - used for TArrow */ + addExtras() {} - handle.last_major = function() { - return (this.kind !== 1) ? false : this.nmajor === this.major.length; - }; + /** @summary Redraw line */ + redraw() { + this.prepareDraw(); - handle.next_major_grpos = function() { - if (this.nmajor >= this.major.length) return null; - return this.func(this.major[this.nmajor]); - }; + const elem = this.appendPath(this.createPath()) + .call(this.lineatt.func); - handle.get_modifier = function() { return null; }; + if (this.getObject()?.$do_not_draw) + elem.remove(); + else { + this.addExtras(elem); + addMoveHandler(this); + assignContextMenu(this); + } - this.order = 0; - this.ndig = 0; + return this; + } - // at the moment when drawing labels, we can try to find most optimal text representation for them + /** @summary Draw TLine object */ + static async draw(dom, obj, opt) { + const painter = new TLinePainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } - if ((this.kind === kAxisNormal) && !this.log && (handle.major.length > 0)) { - let maxorder = 0, minorder = 0, exclorder3 = false; +} // class TLinePainter - if (!optionNoexp) { - const maxtick = Math.max(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - mintick = Math.min(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick)/3)*3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick)/3)*3 : 0; +var TLinePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TLinePainter: TLinePainter +}); + +const k_upper_pad = 'upper_pad', k_lower_pad = 'lower_pad', k_top_pad = 'top_pad'; + + +/** + * @summary Painter class for TRatioPlot + * + * @private + */ + +class TRatioPlotPainter extends ObjectPainter { - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 + #oldratio; // old ratio plot - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } + /** @summary Set grids range */ + setGridsRange(xmin, xmax, ymin, ymax, low_p) { + const ratio = this.getObject(); + if (xmin === xmax) { + const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad)?.getFramePainter()?.x_handle; + if (!x_handle) + return; + if (xmin === 0) { + // in case of unzoom full range should be used + xmin = x_handle.full_min; + xmax = x_handle.full_max; + } else { + // in case of y-scale zooming actual range has to be used + xmin = x_handle.scale_min; + xmax = x_handle.scale_max; + } + } + ratio.fGridlines.forEach(line => { + line.fX1 = xmin; + line.fX2 = xmax; + }); + const nlines = Math.min(ratio.fGridlines.length, ratio.fGridlinePositions.length); + for (let i = 0; i < nlines; ++i) { + const y = ratio.fGridlinePositions[i], + line = ratio.fGridlines[i]; + if (ymin !== 'ignorey') { + line.$do_not_draw = (ymin !== ymax) && ((y < ymin) || (y > ymax)); + line.fY1 = line.fY2 = y; } + low_p?.findPainterFor(line)?.redraw(); + } + } - // now try to find best combination of order and ndig for labels + /** @summary Configure custom interactive handlers for ratio plot + * @desc Should work for both new and old code */ + configureInteractive() { + const ratio = this.getObject(), + pp = this.getPadPainter(), + up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), + up_fp = up_p?.getFramePainter(), + low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), + low_fp = low_p?.getFramePainter(); - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; + if (!up_p || !low_p) + return; - for (let order = minorder; order <= maxorder; order+=3) { - if (exclorder3 && (order===3)) continue; - this.order = order; - this.ndig = 0; - let lbls = [], indx = 0, totallen = 0; - while (indx 11) break; // not too many digits, anyway it will be exponential - lbls = []; indx = 0; totallen = 0; - } + low_p.forEachPainterInPad(objp => { + if (isFunc(objp?.testEditable)) + objp.testEditable(false); + }); - // for order === 0 we should virually remove '0.' and extra label on top - if (!order && (this.ndig < 4)) - totallen -= (handle.major.length * 2 + 3); + this.setGridsRange(low_fp.scale_xmin, low_fp.scale_xmax, low_fp.scale_ymin, low_fp.scale_ymax, low_p); - if (totallen < bestlen) { - bestlen = totallen; - bestorder = this.order; - bestndig = this.ndig; - } - } + if (up_p._ratio_interactive && low_p._ratio_interactive) + return; - this.order = bestorder; - this.ndig = bestndig; + up_p._ratio_interactive = true; + low_p._ratio_interactive = true; - if (optionInt) { - if (this.order) console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); - this.ndig = 0; - this.order = 0; + up_fp.o_zoom = up_fp.zoom; + up_fp._ratio_low_fp = low_fp; + up_fp._ratio_painter = this; + + up_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) { + return this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax).then(res => { + this._ratio_painter.setGridsRange(up_fp.scale_xmin, up_fp.scale_xmax, 'ignory'); + return this._ratio_low_fp.o_zoom(up_fp.scale_xmin, up_fp.scale_xmax).then(() => res); + }); + }; + + up_fp.o_sizeChanged = up_fp.sizeChanged; + up_fp.sizeChanged = function() { + this.o_sizeChanged(); + this._ratio_low_fp.fX1NDC = this.fX1NDC; + this._ratio_low_fp.fX2NDC = this.fX2NDC; + this._ratio_low_fp.o_sizeChanged(); + }; + + low_fp.o_zoom = low_fp.zoom; + low_fp._ratio_up_fp = up_fp; + low_fp._ratio_painter = this; + + low_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) { + if (xmin === xmax) { + xmin = up_fp.xmin; + xmax = up_fp.xmax; + } else { + xmin = Math.min(xmin, up_fp.xmin); + xmax = Math.max(xmax, up_fp.xmax); } - } + this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax); + return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax)); + }; - return handle; + low_fp.o_sizeChanged = low_fp.sizeChanged; + low_fp.sizeChanged = function() { + this.o_sizeChanged(); + this._ratio_up_fp.fX1NDC = this.fX1NDC; + this._ratio_up_fp.fX2NDC = this.fX2NDC; + this._ratio_up_fp.o_sizeChanged(); + }; } - /** @summary Is labels should be centered */ - isCenteredLabels() { - if (this.kind === kAxisLabels) return true; - if (this.kind === 'log') return false; - return this.v7EvalAttr('labels_center', false); - } + /** @summary Redraw old TRatioPlot where object was in very end of list of primitives */ + async redrawOld() { + const ratio = this.getObject(), + pp = this.getPadPainter(), + top_p = pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad), + pad = pp.getRootPad(), + mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, + tick_x = pad.fTickx || mirrow_axis, + tick_y = pad.fTicky || mirrow_axis; - /** @summary Used to move axis labels instead of zooming - * @private */ - processLabelsMove(arg, pos) { - if (this.optionUnlab || !this.axis_g) return false; + top_p?.disablePadDrawing(); - const label_g = this.axis_g.select('.axis_labels'); - if (!label_g || (label_g.size() !== 1)) return false; + const up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), + up_main = up_p?.getMainPainter(), + up_fp = up_p?.getFramePainter(), + low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), + low_main = low_p?.getMainPainter(), + low_fp = low_p?.getFramePainter(); + let promise_up = Promise.resolve(true); - if (arg === 'start') { - // no moving without labels - const box = label_g.node().getBBox(); + if (up_p && up_main && up_fp && low_fp && !up_p._ratio_configured) { + up_p._ratio_configured = true; - label_g.append('rect') - .classed('drag', true) - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - if (this.vertical) - this.drag_pos0 = pos[0]; - else - this.drag_pos0 = pos[1]; + up_main.options.Axis = 0; // draw both axes + const h = up_main.getHisto(); - return true; - } + h.fYaxis.$use_top_pad = true; // workaround to use same scaling + h.fXaxis.fLabelSize = 0; // do not draw X axis labels + h.fXaxis.fTitle = ''; // do not draw X axis title - let offset = label_g.property('fix_offset'); + up_p.getRootPad().fTickx = tick_x; + up_p.getRootPad().fTicky = tick_y; - if (this.vertical) { - offset += Math.round(pos[0] - this.drag_pos0); - label_g.attr('transform', `translate(${offset})`); - } else { - offset += Math.round(pos[1] - this.drag_pos0); - label_g.attr('transform', `translate(0,${offset})`); + promise_up = up_p.redrawPad(); } - if (!offset) label_g.attr('transform', null); - if (arg === 'stop') { - label_g.select('rect.drag').remove(); - delete this.drag_pos0; - if (offset !== label_g.property('fix_offset')) { - label_g.property('fix_offset', offset); - const side = label_g.property('side') || 1; - this.labelsOffset = offset / (this.vertical ? -side : side); - this.changeAxisAttr(1, 'labels_offset', this.labelsOffset / this.scalingSize); + return promise_up.then(() => { + if (!low_p || !low_main || !low_fp || !up_fp || low_p._ratio_configured) + return this; + + low_p._ratio_configured = true; + low_main.options.Axis = 0; // draw both axes + const h = low_main.getHisto(); + h.fXaxis.fTitle = 'x'; + + h.fXaxis.$use_top_pad = true; + h.fYaxis.$use_top_pad = true; + low_p.getRootPad().fTickx = tick_x; + low_p.getRootPad().fTicky = tick_y; + + const arr = []; + + // add missing lines in old ratio painter + if (ratio.fGridlinePositions.length && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) { + ratio.fGridlinePositions.forEach(gridy => { + let found = false; + ratio.fGridlines.forEach(line => { + if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) + found = true; + }); + if (!found) { + const line = create$1(clTLine); + line.fX1 = up_fp.scale_xmin; + line.fX2 = up_fp.scale_xmax; + line.fY1 = line.fY2 = gridy; + line.fLineStyle = 2; + ratio.fGridlines.push(line); + arr.push(TLinePainter.draw(low_p, line)); + } + }); } - } - return true; + return Promise.all(arr) + .then(() => low_fp.zoomSingle('x', up_fp.scale_xmin, up_fp.scale_xmax)) + .then(changed => { return changed ? true : low_p.redrawPad(); }) + .then(() => this); + }); } - /** @summary Add interactive elements to draw axes title */ - addTitleDrag(title_g, side) { - if (!settings.MoveResize || this.isBatchMode()) return; + /** @summary Redraw TRatioPlot */ + async redraw() { + const ratio = this.getObject(), + pp = this.getPadPainter(); - let drag_rect = null, - acc_x, acc_y, new_x, new_y, alt_pos, curr_indx; + if (this.#oldratio === undefined) + this.#oldratio = Boolean(pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad)); - const drag_move = drag().subject(Object); + // configure ratio interactive at the end + pp.$userInteractive = () => this.configureInteractive(); - drag_move - .on('start', evnt => { - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + if (this.#oldratio) + return this.redrawOld(); - const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = this.vertical ? box.height : box.width; + const pad = pp.getRootPad(), + mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, + tick_x = pad.fTickx || mirrow_axis, + tick_y = pad.fTicky || mirrow_axis; - new_x = acc_x = title_g.property('shift_x'); - new_y = acc_y = title_g.property('shift_y'); + // do not draw primitives and pad itself + ratio.fTopPad.$disable_drawing = true; - if (this.titlePos === 'center') - curr_indx = 1; - else - curr_indx = (this.titlePos === 'left') ? 0 : 2; + ratio.fUpperPad.$ratio_pad = 'up'; // indicate drawing of the axes for main painter + ratio.fUpperPad.fTickx = tick_x; + ratio.fUpperPad.fTicky = tick_y; - // let d = ((this.gr_range > 0) && this.vertical) ? title_length : 0; - alt_pos = [0, this.gr_range/2, this.gr_range]; // possible positions - const off = this.vertical ? -title_length : title_length, - swap = this.isReverseAxis() ? 2 : 0; - if (this.title_align === 'middle') { - alt_pos[swap] += off/2; - alt_pos[2-swap] -= off/2; - } else if ((this.title_align === 'begin') ^ this.isTitleRotated()) { - alt_pos[1] -= off/2; - alt_pos[2-swap] -= off; - } else { // end - alt_pos[swap] += off; - alt_pos[1] += off/2; - } + ratio.fLowerPad.$ratio_pad = 'low'; // indicate drawing of the axes for main painter + ratio.fLowerPad.fTickx = tick_x; + ratio.fLowerPad.fTicky = tick_y; - alt_pos[curr_indx] = this.vertical ? acc_y : acc_x; + return this; + } - drag_rect = title_g.append('rect') - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) return; + /** @summary Draw TRatioPlot */ + static async draw(dom, ratio, opt) { + const painter = new TRatioPlotPainter(dom, ratio, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); +} // class TRatioPlotPainter - acc_x += evnt.dx; - acc_y += evnt.dy; +var TRatioPlotPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TRatioPlotPainter: TRatioPlotPainter +}); - const p = this.vertical ? acc_y : acc_x; - let set_x, set_y, besti = 0; +const kResetHisto = BIT(17); - for (let i = 1; i < 3; ++i) - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) besti = i; +/** + * @summary Painter for TMultiGraph object. + * + * @private + */ - if (this.vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_x = alt_pos[besti]; - set_y = acc_y; - } +let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { - new_x = set_x; new_y = set_y; curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - }).on('end', evnt => { - if (!drag_rect) return; + #firstpainter; // first painter + #painters; // array of sub-painters + #funcs_handler; // special instance for functions drawing + #restopt; // remaining part of draw options + #auto; // extra options for auto colors + #is3d; // if 3d drawing + #pads; // pads draw option - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + /** @summary Create painter + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} obj - TMultiGraph object to draw */ + constructor(dom, mgraph) { + super(dom, mgraph); + this.#firstpainter = null; + this.#painters = []; // keep painters to be able update objects + } + + /** @summary Cleanup TMultiGraph painter */ + cleanup() { + this.#painters = []; + this.#is3d = undefined; + this.#pads = undefined; + this.#auto = undefined; + this.#restopt = undefined; + super.cleanup(); + } - const basepos = title_g.property('basepos') || 0; + /** @summary Return true if 3D drawing is used */ + is3d() { return this.#is3d; } - title_g.property('shift_x', new_x) - .property('shift_y', new_y); + /** @summary Update TMultiGraph object */ + updateObject(obj) { + if (!this.matchObjectType(obj)) + return false; + + const mgraph = this.getObject(), + graphs = obj.fGraphs, + pp = this.getPadPainter(); - this.titleOffset = (this.vertical ? basepos - new_x : new_y - basepos) * side; + mgraph.fTitle = obj.fTitle; - if (curr_indx === 1) - this.titlePos = 'center'; - else if (curr_indx === 0) - this.titlePos = 'left'; - else - this.titlePos = 'right'; + let isany = false; + if (this.#firstpainter) { + const histo = this.scanGraphsRange(graphs, obj.fHistogram, pp?.getRootPad(true), true); + if (this.#firstpainter.updateObject(histo)) + isany = true; + } - this.changeAxisAttr(0, 'title_position', this.titlePos, 'title_offset', this.titleOffset / this.scalingSize); + const ngr = Math.min(graphs.arr.length, this.#painters.length); - drag_rect.remove(); - drag_rect = null; - }); + // TODO: handle changing number of graphs + for (let i = 0; i < ngr; ++i) { + if (this.#painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this.#restopt) + this.#auto)) + isany = true; + } - title_g.style('cursor', 'move').call(drag_move); - } + this.#funcs_handler = new FunctionsHandler(this, pp, obj.fFunctions); - /** @summary checks if value inside graphical range, taking into account delta */ - isInsideGrRange(pos, delta1, delta2) { - if (!delta1) delta1 = 0; - if (delta2 === undefined) delta2 = delta1; - if (this.gr_range < 0) - return (pos >= this.gr_range - delta2) && (pos <= delta1); - return (pos >= -delta1) && (pos <= this.gr_range + delta2); + return isany; } - /** @summary returns graphical range */ - getGrRange(delta) { - if (!delta) delta = 0; - if (this.gr_range < 0) - return this.gr_range - delta; - return this.gr_range + delta; - } + /** @summary Redraw TMultiGraph + * @desc may redraw histogram which was used to draw axes + * @return {Promise} for ready */ + async redraw(reason) { + const promise = this.#firstpainter?.redraw(reason) ?? Promise.resolve(true), + redrawNext = async indx => { + if (indx >= this.#painters.length) + return this; + return this.#painters[indx].redraw(reason).then(() => redrawNext(indx + 1)); + }; - /** @summary If axis direction is negative coordinates direction */ - isReverseAxis() { - return !this.vertical !== (this.getGrRange() > 0); + return promise.then(() => redrawNext(0)).then(() => { + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; + return res; + }); } - /** @summary Draw axis ticks - * @private */ - drawMainLine(axis_g) { - let ending = ''; + /** @summary Scan graphs range + * @return {object} histogram for axes drawing */ + scanGraphsRange(graphs, histo, pad, reset_histo) { + const mgraph = this.getObject(), + rw = { xmin: 0, xmax: 0, ymin: 0, ymax: 0, first: true }, + test = (v1, v2) => { return Math.abs(v2 - v1) < 1e-6; }; + let maximum, minimum, logx = false, logy = false, + src_hist, dummy_histo = false; - if (this.endingSize && this.endingStyle) { - let sz = (this.gr_range > 0) ? -this.endingSize : this.endingSize; - const sz7 = Math.round(sz*0.7); - sz = Math.round(sz); - if (this.vertical) - ending = `l${sz7},${sz}M0,${this.gr_range}l${-sz7},${sz}`; - else - ending = `l${sz},${sz7}M${this.gr_range},0l${sz},${-sz7}`; + if (pad) { + logx = pad.fLogx; + logy = pad.fLogv ?? pad.fLogy; } - axis_g.append('svg:path') - .attr('d', 'M0,0' + (this.vertical ? 'v' : 'h') + this.gr_range + ending) - .call(this.lineatt.func) - .style('fill', ending ? 'none' : null); - } + // ignore existing histogram in 3d case + if (this.is3d() && histo && !histo.fXaxis.fLabels) + histo = null; - /** @summary Draw axis ticks - * @return {Object} with gaps on left and right side - * @private */ - drawTicks(axis_g, side, main_draw) { - if (main_draw) this.ticks = []; + if (!histo) + src_hist = graphs.arr[0]?.fHistogram; + else { + dummy_histo = test(histo.fMinimum, -0.05) && test(histo.fMaximum, 1.05) && + test(histo.fXaxis.fXmin, -0.05) && test(histo.fXaxis.fXmax, 1.05); + src_hist = histo; + } - this.handle.reset(); + graphs.arr.forEach(gr => { + if (gr.fNpoints === 0) + return; + if (gr.TestBit(kResetHisto)) + reset_histo = true; + if (rw.first) { + rw.xmin = rw.xmax = gr.fX[0]; + rw.ymin = rw.ymax = gr.fY[0]; + rw.first = false; + } + for (let i = 0; i < gr.fNpoints; ++i) { + rw.xmin = Math.min(rw.xmin, gr.fX[i]); + rw.xmax = Math.max(rw.xmax, gr.fX[i]); + rw.ymin = Math.min(rw.ymin, gr.fY[i]); + rw.ymax = Math.max(rw.ymax, gr.fY[i]); + } + }); - let res = '', ticks_plusminus = 0; - if (this.ticksSide === 'both') { - side = 1; - ticks_plusminus = 1; - } + if (rw.xmin === rw.xmax) + rw.xmax += 1; + if (rw.ymin === rw.ymax) + rw.ymax += 1; + const dx = 0.05 * (rw.xmax - rw.xmin), + dy = 0.05 * (rw.ymax - rw.ymin); - while (this.handle.next(true)) { - let h1 = Math.round(this.ticksSize/4), h2 = 0; + let uxmin = rw.xmin - dx, + uxmax = rw.xmax + dx; + if (logy) { + if (rw.ymin <= 0) + rw.ymin = 0.001 * rw.ymax; + minimum = rw.ymin / (1 + 0.5 * Math.log10(rw.ymax / rw.ymin)); + maximum = rw.ymax * (1 + 0.2 * Math.log10(rw.ymax / rw.ymin)); + } else { + minimum = rw.ymin - dy; + maximum = rw.ymax + dy; + } + if (minimum < 0 && rw.ymin >= 0) + minimum = 0; + if (maximum > 0 && rw.ymax <= 0) + maximum = 0; - if (this.handle.kind < 3) - h1 = Math.round(this.ticksSize/2); + const glob_minimum = minimum, glob_maximum = maximum; - const grpos = this.handle.grpos - this.axis_shift; + if (uxmin < 0 && rw.xmin >= 0) + uxmin = logx ? 0.9 * rw.xmin : 0; + if (uxmax > 0 && rw.xmax <= 0) + uxmax = logx ? 1.1 * rw.xmax : 0; - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(grpos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) continue; + if (mgraph.fMinimum !== kNoZoom) + rw.ymin = minimum = mgraph.fMinimum; + if (mgraph.fMaximum !== kNoZoom) + rw.ymax = maximum = mgraph.fMaximum; - if (this.handle.kind === 1) { - // if not showing labels, not show large tick - if ((this.kind === kAxisLabels) || (this.format(this.handle.tick, true) !== null)) h1 = this.ticksSize; + if (minimum < 0 && rw.ymin >= 0 && logy) + minimum = 0.9 * rw.ymin; + if (maximum > 0 && rw.ymax <= 0 && logy) + maximum = 1.1 * rw.ymax; + if (minimum <= 0 && logy) + minimum = 0.001 * maximum; + if (!logy && minimum > 0 && minimum < 0.05 * maximum) + minimum = 0; + if (uxmin <= 0 && logx) + uxmin = (uxmax > 1000) ? 1 : 0.001 * uxmax; - if (main_draw) this.ticks.push(grpos); // keep graphical positions of major ticks + // Create a temporary histogram to draw the axis (if necessary) + if (!histo || reset_histo || dummy_histo) { + let xaxis, yaxis; + if (this.is3d()) { + histo = createHistogram(clTH2F, graphs.arr.length, 10); + xaxis = histo.fXaxis; + xaxis.fXmin = 0; + xaxis.fXmax = graphs.arr.length; + xaxis.fLabels = create$1(clTHashList); + for (let i = 0; i < graphs.arr.length; i++) { + const lbl = create$1(clTObjString); + lbl.fString = graphs.arr[i].fTitle || `gr${i}`; + lbl.fUniqueID = graphs.arr.length - i; // graphs drawn in reverse order + xaxis.fLabels.Add(lbl, ''); + } + xaxis = histo.fYaxis; + yaxis = histo.fZaxis; + } else { + histo = createHistogram(src_hist?._typename ?? clTH1F, src_hist?.fXaxis.fNbins ?? 10); + xaxis = histo.fXaxis; + yaxis = histo.fYaxis; } - if (ticks_plusminus > 0) - h2 = -h1; - else if (side < 0) { - h2 = -h1; h1 = 0; - } else - h2 = 0; + if (src_hist) { + Object.assign(xaxis, src_hist.fXaxis); + yaxis.fTitle = src_hist.fYaxis.fTitle; + } - res += this.vertical ? `M${h1},${grpos}H${h2}` : `M${grpos},${-h1}V${-h2}`; + histo.fTitle = mgraph.fTitle; + if (histo.fTitle.indexOf(';') >= 0) { + const t = histo.fTitle.split(';'); + histo.fTitle = t[0]; + if (t[1]) + xaxis.fTitle = t[1]; + if (t[2]) + yaxis.fTitle = t[2]; + } + if (!xaxis.fLabels) { + xaxis.fXmin = uxmin; + xaxis.fXmax = uxmax; + } } - if (res) { - axis_g.append('svg:path') - .attr('d', res) - .style('stroke', this.ticksColor || this.lineatt.color) - .style('stroke-width', !this.ticksWidth || (this.ticksWidth === 1) ? null : this.ticksWidth); - } + const axis = this.is3d() ? histo.fZaxis : histo.fYaxis; + axis.fXmin = Math.min(minimum, glob_minimum); + axis.fXmax = Math.max(maximum, glob_maximum); + if (histo.fMinimum === kNoZoom) + histo.fMinimum = minimum; + if (histo.fMaximum === kNoZoom) + histo.fMaximum = maximum; + histo.fBits |= kNoStats; - const gap0 = Math.round(0.25*this.ticksSize), gap = Math.round(1.25*this.ticksSize); - return { '-1': (side > 0) || ticks_plusminus ? gap : gap0, - 1: (side < 0) || ticks_plusminus ? gap : gap0 }; + return histo; } - /** @summary Performs labels drawing - * @return {Promise} with gaps in both direction */ - async drawLabels(axis_g, side, gaps) { - const center_lbls = this.isCenteredLabels(), - rotate_lbls = this.labelsFont.angle !== 0, - label_g = axis_g.append('svg:g').attr('class', 'axis_labels').property('side', side), - lbl_pos = this.handle.lbl_pos || this.handle.major; - let textscale = 1, maxtextlen = 0, lbls_tilt = false, - max_lbl_width = 0, max_lbl_height = 0; - - // function called when text is drawn to analyze width, required to correctly scale all labels - function process_drawtext_ready(painter) { - max_lbl_width = Math.max(max_lbl_width, this.result_width); - max_lbl_height = Math.max(max_lbl_height, this.result_height); - - const textwidth = this.result_width; - - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { - let maxwidth = this.gap_before*0.45 + this.gap_after*0.45; - if (!this.gap_before) maxwidth = 0.9*this.gap_after; else - if (!this.gap_after) maxwidth = 0.9*this.gap_before; - textscale = Math.min(textscale, maxwidth / textwidth); - } - - if ((textscale > 0.0001) && (textscale < 0.8) && !painter.vertical && !rotate_lbls && (maxtextlen > 5) && (side > 0)) - lbls_tilt = true; + /** @summary draw special histogram for axis + * @return {Promise} when ready */ + async drawAxisHist(histo, hopt) { + return TH1Painter$2.draw(this.getDrawDom(), histo, hopt); + } - const scale = textscale * (lbls_tilt ? 3 : 1); - if ((scale > 0.0001) && (scale < 1)) - painter.scaleTextDrawing(1/scale, label_g); - } + /** @summary Draw graph */ + async drawGraph(dom, gr, opt /* , pos3d */) { + return TGraphPainter$1.draw(dom, gr, opt); + } - const fix_offset = Math.round((this.vertical ? -side : side) * this.labelsOffset), - fix_coord = Math.round((this.vertical ? -side : side) * gaps[side]); - let lastpos = 0; + /** @summary method draws next graph */ + async drawNextGraph(indx, pad_painter) { + const graphs = this.getObject().fGraphs; - if (fix_offset) - label_g.attr('transform', this.vertical ? `translate(${fix_offset})` : `translate(0,${fix_offset})`); + // at the end of graphs drawing draw functions (if any) + if (indx >= graphs.arr.length) + return this; - label_g.property('fix_offset', fix_offset); + const gr = graphs.arr[indx], + draw_opt = (graphs.opt[indx] || this.#restopt) + this.#auto, + pos3d = graphs.arr.length - indx, + subid = `graphs_${indx}`; - this.startTextDrawing(this.labelsFont, 'font', label_g); + // handling of 'pads' draw option + if (pad_painter) { + const subpad_painter = pad_painter.getSubPadPainter(indx + 1); + if (!subpad_painter) + return this; - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - const lbl = this.format(lbl_pos[nmajor], true); - if (lbl === null) continue; + subpad_painter.cleanPrimitives(true); - const arg = { text: lbl, latex: 1, draw_g: label_g }; - let pos = Math.round(this.func(lbl_pos[nmajor])); + return this.drawGraph(subpad_painter, gr, draw_opt, pos3d).then(subp => { + if (subp) { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + } + return this.drawNextGraph(indx + 1, pad_painter); + }); + } - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor-1]))) : 0; - arg.gap_after = (nmajor < lbl_pos.length-1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor+1])-pos)) : 0; + // used in automatic colors numbering + if (this.#auto) + gr.$num_graphs = graphs.arr.length; - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - (this.vertical ? 0.5*gap : -0.5*gap)); - if (!this.isInsideGrRange(pos, 5)) continue; + return this.drawGraph(this.getPadPainter(), gr, draw_opt, pos3d).then(subp => { + if (subp) { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); } + return this.drawNextGraph(indx + 1); + }); + } - maxtextlen = Math.max(maxtextlen, lbl.length); - - pos -= this.axis_shift; + /** @summary Fill TMultiGraph context menu */ + fillContextMenuItems(menu) { + menu.addRedrawMenu(this); + } - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(pos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) continue; + /** @summary Redraw TMultiGraph object using provided option + * @private */ + async redrawWith(opt, skip_cleanup) { + if (!skip_cleanup) { + this.#firstpainter = null; + this.#painters = []; + const pp = this.getPadPainter(); + pp?.removePrimitive(this, true); + if (this.#pads) + pp?.divide(0, 0); + } - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += this.labelsFont.size; - } - } + const d = new DrawOptions(opt), + mgraph = this.getObject(); - arg.post_process = process_drawtext_ready; + this.#is3d = d.check('3D'); + this.#auto = ''; + this.#pads = d.check('PADS'); + ['PFC', 'PLC', 'PMC'].forEach(f => { + if (d.check(f)) + this.#auto += ' ' + f; + }); - this.drawText(arg); + let hopt = '', pad_painter = null; + if (d.check('FB') && this.is3d()) + hopt += 'FB'; // will be directly combined with LEGO + PadDrawOptions.forEach(name => { + if (d.check(name)) + hopt += ';' + name; + }); - if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos-lastpos); - textscale = Math.min(textscale, 0.9*axis_step/this.labelsFont.size); - } + this.#restopt = d.remain(); - lastpos = pos; + let promise = Promise.resolve(true); + if (this.#pads) { + promise = ensureTCanvas(this, false).then(() => { + pad_painter = this.getPadPainter(); + return pad_painter.divide(mgraph.fGraphs.arr.length, 0, true); + }); + } else if (d.check('A') || !this.getMainPainter()) { + const histo = this.scanGraphsRange(mgraph.fGraphs, mgraph.fHistogram, this.getPadPainter()?.getRootPad(true)); + promise = this.drawAxisHist(histo, hopt).then(ap => { + ap.setSecondaryId(this, 'hist'); // mark that axis painter generated from mg + this.#firstpainter = ap; + }); } - if (this.order) { - this.drawText({ x: this.vertical ? side*5 : this.getGrRange(5), - y: this.has_obstacle ? fix_coord : (this.vertical ? this.getGrRange(3) : -3*side), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g }); - } + return promise.then(() => { + this.addToPadPrimitives(); + return this.drawNextGraph(0, pad_painter); + }).then(() => { + if (this.#pads) + return this; + const handler = new FunctionsHandler(this, this.getPadPainter(), this.getObject().fFunctions, true); + return handler.drawNext(0); // returns painter + }); + } - return this.finishTextDrawing(label_g).then(() => { - if (lbls_tilt) { - label_g.selectAll('text').each(function() { - const txt = select(this), tr = txt.attr('transform'); - txt.attr('transform', tr + ' rotate(25)').style('text-anchor', 'start'); - }); - } + /** @summary Draw TMultiGraph object in 2D only */ + static async draw(dom, mgraph, opt) { + const painter = new TMultiGraphPainter(dom, mgraph, opt); + return painter.redrawWith(opt, true); + } - if (this.vertical) - gaps[side] += Math.round(rotate_lbls ? 1.2*max_lbl_height : max_lbl_width + 0.4*this.labelsFont.size) - side*fix_offset; - else { - const tilt_height = lbls_tilt ? max_lbl_width * Math.sin(25/180*Math.PI) + max_lbl_height * (Math.cos(25/180*Math.PI) + 0.2) : 0; +}; // class TMultiGraphPainter - gaps[side] += Math.round(Math.max(rotate_lbls ? max_lbl_width + 0.4*this.labelsFont.size : 1.2*max_lbl_height, 1.2*this.labelsFont.size, tilt_height)) + fix_offset; - } +class TMultiGraphPainter extends TMultiGraphPainter$2 { - return gaps; - }); + /** @summary draw special histogram for axis + * @return {Promise} when ready */ + async drawAxisHist(histo, hopt) { + const dom = this.getDrawDom(); + return this.is3d() ? TH2Painter.draw(dom, histo, 'LEGO' + hopt) + : TH1Painter$2.draw(dom, histo, hopt); } - /** @summary Add zomming rect to axis drawing */ - addZoomingRect(axis_g, side, lgaps) { - if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const sz = Math.max(lgaps[side], 10), - d = this.vertical ? `v${this.gr_range}h${-side*sz}v${-this.gr_range}` : `h${this.gr_range}v${side*sz}h${-this.gr_range}`; - axis_g.append('svg:path') - .attr('d', `M0,0${d}z`) - .attr('class', 'axis_zoom') - .style('opacity', '0') - .style('cursor', 'crosshair'); - } + /** @summary draw multi graph in 3D */ + async drawGraph(dom, gr, opt, pos3d) { + if (this.is3d()) + opt += `pos3d_${pos3d}`; + return TGraphPainter.draw(dom, gr, opt); } - /** @summary Returns true if axis title is rotated */ - isTitleRotated() { - return this.titleFont && (this.titleFont.angle !== (this.vertical ? 270 : 0)); + /** @summary Draw TMultiGraph object */ + static async draw(dom, mgraph, opt) { + const painter = new TMultiGraphPainter(dom, mgraph, opt); + return painter.redrawWith(opt, true); } - /** @summary Draw axis title */ - async drawTitle(axis_g, side, lgaps) { - if (!this.fTitle) - return this; - - const title_g = axis_g.append('svg:g').attr('class', 'axis_title'), - rotated = this.isTitleRotated(); - let title_shift_x = 0, title_shift_y = 0, title_basepos = 0; +} // class TMultiGraphPainter - this.startTextDrawing(this.titleFont, 'font', title_g); +var TMultiGraphPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TMultiGraphPainter: TMultiGraphPainter +}); - this.title_align = this.titleCenter ? 'middle' : (this.titleOpposite ^ (this.isReverseAxis() || rotated) ? 'begin' : 'end'); +/** + * @summary Painter for TWebPainting classes. + * + * @private + */ - if (this.vertical) { - title_basepos = Math.round(-side*(lgaps[side])); - title_shift_x = title_basepos + Math.round(-side*this.titleOffset); - title_shift_y = Math.round(this.titleCenter ? this.gr_range/2 : (this.titleOpposite ? 0 : this.gr_range)); - this.drawText({ align: [this.title_align, ((side < 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g }); - } else { - title_shift_x = Math.round(this.titleCenter ? this.gr_range/2 : (this.titleOpposite ? 0 : this.gr_range)); - title_basepos = Math.round(side*lgaps[side]); - title_shift_y = title_basepos + Math.round(side*this.titleOffset); - this.drawText({ align: [this.title_align, ((side > 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g }); - } +class TWebPaintingPainter extends ObjectPainter { - makeTranslate(title_g, title_shift_x, title_shift_y) - .property('basepos', title_basepos) - .property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); + /** @summary Update TWebPainting object */ + updateObject(obj) { + if (!this.matchObjectType(obj)) + return false; + this.assignObject(obj); + return true; + } - this.addTitleDrag(title_g, side); + /** @summary Provides menu header */ + getMenuHeader() { + return this.getObject()?.fClassName || 'TWebPainting'; + } - return this.finishTextDrawing(title_g); + /** @summary Fill context menu + * @desc Create only header, items will be requested from server */ + fillContextMenu(menu) { + const cl = this.getMenuHeader(); + menu.header(cl, `${urlClassPrefix}${cl}.html`); + return true; } - /** @summary Extract major draw attributes, which are also used in interactive operations - * @private */ - extractDrawAttributes(scalingSize) { + /** @summary Mouse click handler + * @desc Redirect mouse click events to the ROOT application + * @private */ + handleMouseClick(evnt) { const pp = this.getPadPainter(), - rect = pp?.getPadRect() || { width: 10, height: 10 }; + rect = pp?.getPadRect(); - this.scalingSize = scalingSize || (this.vertical ? rect.width : rect.height); + if (pp && rect && this.getSnapId()) { + const pos = pointer(evnt, this.getG().node()); + pp.selectObjectPainter(this, { x: pos[0] + rect.x, y: pos[1] + rect.y }); + } + } - this.createv7AttLine('line_'); + /** @summary draw TWebPainting object */ + async redraw() { + const obj = this.getObject(), func = this.getAxisToSvgFunc(); - this.optionUnlab = this.v7EvalAttr('labels_hide', false); + if (!obj?.fOper || !func) + return this; - this.endingStyle = this.v7EvalAttr('ending_style', ''); - this.endingSize = Math.round(this.v7EvalLength('ending_size', this.scalingSize, this.endingStyle ? 0.02 : 0)); - this.startingSize = Math.round(this.v7EvalLength('starting_size', this.scalingSize, 0)); - this.ticksSize = this.v7EvalLength('ticks_size', this.scalingSize, 0.02); - this.ticksSide = this.v7EvalAttr('ticks_side', 'normal'); - this.ticksColor = this.v7EvalColor('ticks_color', ''); - this.ticksWidth = this.v7EvalAttr('ticks_width', 1); - if (scalingSize && (this.ticksSize < 0)) - this.ticksSize = -this.ticksSize; + let indx = 0, attr = {}, lastpath = null, lastkind = 'none', d = '', + oper, npoints, n; - this.fTitle = this.v7EvalAttr('title_value', ''); + /* eslint-disable one-var */ + const g = this.createG(), + arr = obj.fOper.split(';'); + const check_attributes = kind => { + if (kind === lastkind) + return; - if (this.fTitle) { - this.titleFont = this.v7EvalFont('title', { size: 0.03 }, scalingSize || pp?.getPadHeight() || 10); - this.titleFont.roundAngle(180, this.vertical ? 270 : 0); + if (lastpath) { + lastpath.attr('d', d); // flush previous + d = ''; + lastpath = null; + lastkind = 'none'; + } - this.titleOffset = this.v7EvalLength('title_offset', this.scalingSize, 0); - this.titlePos = this.v7EvalAttr('title_position', 'right'); - this.titleCenter = (this.titlePos === 'center'); - this.titleOpposite = (this.titlePos === 'left'); - } else { - delete this.titleFont; - delete this.titleOffset; - delete this.titlePos; - } + if (!kind) + return; - // TODO: remove old scaling factors for labels and ticks - this.labelsFont = this.v7EvalFont('labels', { size: scalingSize ? 0.05 : 0.03 }); - this.labelsFont.roundAngle(180); - if (this.labelsFont.angle) this.labelsFont.angle = 270; - this.labelsOffset = this.v7EvalLength('labels_offset', this.scalingSize, 0); + lastkind = kind; + lastpath = g.append('svg:path').attr('d', ''); // placeholder for 'd' to have it always in front + switch (kind) { + case 'f': + lastpath.call(this.fillatt.func); + break; + case 'l': + lastpath.call(this.lineatt.func).style('fill', 'none'); + break; + case 'm': + lastpath.call(this.markeratt.func); + break; + } + }; + const read_attr = (str, names) => { + let lastp = 0; + const obj2 = { _typename: 'any' }; + for (let k = 0; k < names.length; ++k) { + const p = str.indexOf(':', lastp + 1); + obj2[names[k]] = parseInt(str.slice(lastp + 1, p > lastp ? p : undefined)); + lastp = p; + } + return obj2; + }; + const process = k => { + while (++k < arr.length) { + oper = arr[k][0]; + switch (oper) { + case 'z': + this.createAttLine({ attr: read_attr(arr[k], ['fLineColor', 'fLineStyle', 'fLineWidth']), force: true }); + check_attributes(); + continue; + case 'y': + this.createAttFill({ attr: read_attr(arr[k], ['fFillColor', 'fFillStyle']), force: true }); + check_attributes(); + continue; + case 'x': + this.createAttMarker({ attr: read_attr(arr[k], ['fMarkerColor', 'fMarkerStyle', 'fMarkerSize']), force: true }); + check_attributes(); + continue; + case 'o': + attr = read_attr(arr[k], ['fTextColor', 'fTextFont', 'fTextSize', 'fTextAlign', 'fTextAngle']); + if (attr.fTextSize < 0) + attr.fTextSize *= -1e-3; + check_attributes(); + continue; + case 'r': + case 'b': { + check_attributes((oper === 'b') ? 'f' : 'l'); + + const x1 = func.x(obj.fBuf[indx++]), + y1 = func.y(obj.fBuf[indx++]), + x2 = func.x(obj.fBuf[indx++]), + y2 = func.y(obj.fBuf[indx++]); - if (scalingSize) this.ticksSize = this.labelsFont.size*0.5; // old lego scaling factor + d += `M${x1},${y1}h${x2 - x1}v${y2 - y1}h${x1 - x2}z`; + continue; + } + case 'l': + case 'f': { + check_attributes(oper); - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) - this.ticksSize = this.maxTickSize; - } + npoints = parseInt(arr[k].slice(1)); - /** @summary Performs axis drawing - * @return {Promise} which resolved when drawing is completed */ - async drawAxis(layer, transform, side) { - let axis_g = layer; + for (n = 0; n < npoints; ++n) + d += `${(n > 0) ? 'L' : 'M'}${func.x(obj.fBuf[indx++])},${func.y(obj.fBuf[indx++])}`; - if (side === undefined) side = 1; + if (oper === 'f') + d += 'Z'; - if (!this.standalone) { - axis_g = layer.selectChild(`.${this.name}_container`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); - else - axis_g.selectAll('*').remove(); - } + continue; + } - axis_g.attr('transform', transform); + case 'm': { + check_attributes(oper); - this.extractDrawAttributes(); - this.axis_g = axis_g; - this.side = side; + npoints = parseInt(arr[k].slice(1)); + + this.markeratt.resetPos(); + for (n = 0; n < npoints; ++n) + d += this.markeratt.create(func.x(obj.fBuf[indx++]), func.y(obj.fBuf[indx++])); - if (this.ticksSide === 'invert') side = -side; + continue; + } - if (this.standalone) - this.drawMainLine(axis_g); + case 'h': + case 't': { + if (attr.fTextSize) { + check_attributes(); - const optionNoopt = false, // no ticks position optimization - optionInt = false, // integer labels - optionNoexp = false; // do not create exp + const height = (attr.fTextSize > 1) ? attr.fTextSize : this.getPadPainter().getPadHeight() * attr.fTextSize, + group = g.append('svg:g'); + + return this.startTextDrawingAsync(attr.fTextFont, height, group).then(() => { + let text = arr[k].slice(1), + angle = attr.fTextAngle; + if (angle >= 360) + angle -= Math.floor(angle / 360) * 360; + + if (oper === 'h') { + let res = ''; + for (n = 0; n < text.length; n += 2) + res += String.fromCharCode(parseInt(text.slice(n, n + 2), 16)); + text = res; + } - this.handle = this.createTicks(false, optionNoexp, optionNoopt, optionInt); + // todo - correct support of angle + this.drawText({ + align: attr.fTextAlign, + x: func.x(obj.fBuf[indx++]), + y: func.y(obj.fBuf[indx++]), + rotate: -angle, + text, + color: getColor(attr.fTextColor), + latex: 0, draw_g: group + }); + + return this.finishTextDrawing(group); + }).then(() => process(k)); + } + continue; + } - // first draw ticks - const tgaps = this.drawTicks(axis_g, side, true), - // draw labels - labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); + default: + console.log(`unsupported operation ${oper}`); + } + } - return labelsPromise.then(lgaps => { - // when drawing axis on frame, zoom rect should be always outside - this.addZoomingRect(axis_g, this.standalone ? side : this.side, lgaps); + return Promise.resolve(true); + }; - return this.drawTitle(axis_g, side, lgaps); + + return process(-1).then(() => { + check_attributes(); + assignContextMenu(this); + if (!this.isBatchMode()) + g.on('click', evnt => this.handleMouseClick(evnt)); + return this; }); } - /** @summary Assign handler, which is called when axis redraw by interactive changes - * @desc Used by palette painter to reassign iteractive handlers - * @private */ - setAfterDrawHandler(handler) { - this._afterDrawAgain = handler; + static async draw(dom, obj) { + const painter = new TWebPaintingPainter(dom, obj); + painter.addToPadPrimitives(); + return painter.redraw(); } - /** @summary Draw axis with the same settings, used by interactive changes */ - drawAxisAgain() { - if (!this.axis_g || !this.side) return; - - this.axis_g.selectAll('*').remove(); +} // class TWebPaintingPainter - this.extractDrawAttributes(); +var TWebPaintingPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TWebPaintingPainter: TWebPaintingPainter +}); - let side = this.side; - if (this.ticksSide === 'invert') side = -side; +/** + * @summary Painter for TF2 object + * + * @private + */ - if (this.standalone) - this.drawMainLine(this.axis_g); +class TF2Painter extends TH2Painter { - // first draw ticks - const tgaps = this.drawTicks(this.axis_g, side, false), - labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(this.axis_g, side, tgaps); + #use_saved_points; // use saved points for drawing + #func; // func object + #fail_eval; // fail evaluation of function - return labelsPromise.then(lgaps => { - // when drawing axis on frame, zoom rect should be always outside - this.addZoomingRect(this.axis_g, this.standalone ? side : this.side, lgaps); + /** @summary Assign function */ + setFunc(f) { this.#func = f; } - return this.drawTitle(this.axis_g, side, lgaps); - }).then(() => { - if (isFunc(this._afterDrawAgain)) - this._afterDrawAgain(); - }); - } + /** @summary Returns drawn object name */ + getObjectName() { return this.#func?.fName ?? 'func'; } - /** @summary Draw axis again on opposite frame size */ - drawAxisOtherPlace(layer, transform, side, only_ticks) { - let axis_g = layer.selectChild(`.${this.name}_container2`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container2`); - else - axis_g.selectAll('*').remove(); + /** @summary Returns drawn object class name */ + getClassName() { return this.#func?._typename ?? clTF2; } - axis_g.attr('transform', transform); + /** @summary Returns true while function is drawn */ + isTF1() { return true; } - if (this.ticksSide === 'invert') side = -side; + /** @summary Returns primary function which was then drawn as histogram */ + getPrimaryObject() { return this.#func; } - // draw ticks again - const tgaps = this.drawTicks(axis_g, side, false), + /** @summary Update histogram */ + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; + delete obj.evalPar; + const histo = this.getHisto(); - // draw labels again - promise = this.optionUnlab || only_ticks ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); + if (this._webcanv_hist) { + const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); + } - return promise.then(lgaps => { - this.addZoomingRect(axis_g, side, lgaps); - return true; - }); + this.setFunc(obj); + this.createTF2Histogram(obj, histo); + this.scanContent(); + return true; } - /** @summary Change zooming in standalone mode */ - zoomStandalone(min, max) { - this.changeAxisAttr(1, 'zoomMin', min, 'zoomMax', max); + /** @summary Redraw TF2 + * @private */ + redraw(reason) { + if (!this.#use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'zoom')) { + this.createTF2Histogram(this.#func, this.getHisto()); + this.scanContent(); + } + + return super.redraw(reason); } - /** @summary Redraw axis, used in standalone mode for RAxisDrawable */ - redraw() { - const drawable = this.getObject(), - pp = this.getPadPainter(), - pos = pp.getCoordinate(drawable.fPos), - reverse = this.v7EvalAttr('reverse', false), - labels_len = drawable.fLabels.length, - min = (labels_len > 0) ? 0 : this.v7EvalAttr('min', 0), - max = (labels_len > 0) ? labels_len : this.v7EvalAttr('max', 100); - let len = pp.getPadLength(drawable.fVertical, drawable.fLength); + /** @summary Create histogram for TF2 drawing + * @private */ + createTF2Histogram(func, hist) { + let nsave = func.fSave.length - 6; + if ((nsave > 0) && (nsave !== (func.fSave[nsave + 4] + 1) * (func.fSave[nsave + 5] + 1))) + nsave = 0; - // in vertical direction axis drawn in negative direction - if (drawable.fVertical) len -= pp.getPadHeight(); + this.#use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this._use_saved > 1)); - let smin = this.v7EvalAttr('zoomMin'), - smax = this.v7EvalAttr('zoomMax'); - if (smin === smax) { - smin = min; smax = max; - } + const fp = this.getFramePainter(), + pad = this.getPadPainter()?.getRootPad(true), + logx = pad?.fLogx, logy = pad?.fLogy, + gr = fp?.getGrFuncs(this.second_x, this.second_y); + let xmin = func.fXmin, xmax = func.fXmax, + ymin = func.fYmin, ymax = func.fYmax, + npx = Math.max(func.fNpx, 20), + npy = Math.max(func.fNpy, 20); - this.configureAxis('axis', min, max, smin, smax, drawable.fVertical, undefined, len, { reverse, labels: labels_len > 0 }); + if (gr?.zoom_xmin !== gr?.zoom_xmax) { + const dx = (xmax - xmin) / npx; + if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) + xmin = Math.max(xmin, gr.zoom_xmin - dx); + if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) + xmax = Math.min(xmax, gr.zoom_xmax + dx); + } - this.createG(); + if (gr?.zoom_ymin !== gr?.zoom_ymax) { + const dy = (ymax - ymin) / npy; + if ((ymin < gr.zoom_ymin) && (gr.zoom_ymin < ymax)) + ymin = Math.max(ymin, gr.zoom_ymin - dy); + if ((ymin < gr.zoom_ymax) && (gr.zoom_ymax < ymax)) + ymax = Math.min(ymax, gr.zoom_ymax + dy); + } - this.standalone = true; // no need to clean axis container + const ensureBins = (nx, ny) => { + if (hist.fNcells !== (nx + 2) * (ny + 2)) { + hist.fNcells = (nx + 2) * (ny + 2); + hist.fArray = new Float32Array(hist.fNcells); + } + hist.fArray.fill(0); + hist.fXaxis.fNbins = nx; + hist.fXaxis.fXbins = []; + hist.fYaxis.fNbins = ny; + hist.fYaxis.fXbins = []; + }; - const promise = this.drawAxis(this.draw_g, makeTranslate(pos.x, pos.y)); + this.#fail_eval = undefined; - if (this.isBatchMode()) return promise; + if (!this.#use_saved_points) { + let iserror = false; - return promise.then(() => { - if (settings.ContextMenu) { - this.draw_g.on('contextmenu', evnt => { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - createMenu(evnt, this).then(menu => { - menu.add('header:RAxisDrawable'); - menu.add('Unzoom', () => this.zoomStandalone()); - this.fillAxisContextMenu(menu, ''); - menu.show(); - }); - }); - } + if (!func.evalPar && !proivdeEvalPar(func)) + iserror = true; - addDragHandler(this, { x: pos.x, y: pos.y, width: this.vertical ? 10 : len, height: this.vertical ? len : 10, - only_move: true, redraw: d => this.positionChanged(d) }); + ensureBins(npx, npy); + hist.fXaxis.fXmin = xmin; + hist.fXaxis.fXmax = xmax; + hist.fYaxis.fXmin = ymin; + hist.fYaxis.fXmax = ymax; - this.draw_g.on('dblclick', () => this.zoomStandalone()); + if (logx) + produceTAxisLogScale(hist.fXaxis, npx, xmin, xmax); + if (logy) + produceTAxisLogScale(hist.fYaxis, npy, ymin, ymax); - if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { - evnt.stopPropagation(); - evnt.preventDefault(); + for (let j = 0; (j < npy) && !iserror; ++j) { + for (let i = 0; (i < npx) && !iserror; ++i) { + const x = hist.fXaxis.GetBinCenter(i + 1), + y = hist.fYaxis.GetBinCenter(j + 1); + let z = 0; - const pos = pointer(evnt, this.draw_g.node()), - coord = this.vertical ? (1 - pos[1] / len) : pos[0] / len, - item = this.analyzeWheelEvent(evnt, coord); + try { + z = func.evalPar(x, y); + } catch { + iserror = true; + } - if (item.changed) this.zoomStandalone(item.min, item.max); - }); + if (!iserror) + hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); + } } - }); - } - /** @summary Process interactive moving of the axis drawing */ - positionChanged(drag) { - const drawable = this.getObject(), - rect = this.getPadPainter().getPadRect(), - xn = drag.x / rect.width, - yn = 1 - drag.y / rect.height; + if (iserror) + this.#fail_eval = true; - drawable.fPos.fHoriz.fArr = [xn]; - drawable.fPos.fVert.fArr = [yn]; + if (iserror && (nsave > 6)) + this.#use_saved_points = true; + } + + if (this.#use_saved_points) { + npx = Math.round(func.fSave[nsave + 4]); + npy = Math.round(func.fSave[nsave + 5]); + xmin = func.fSave[nsave]; + xmax = func.fSave[nsave + 1]; + ymin = func.fSave[nsave + 2]; + ymax = func.fSave[nsave + 3]; + const dx = (xmax - xmin) / npx, dy = (ymax - ymin) / npy, getSave = (x, y) => { + if (x < xmin || x > xmax || dx <= 0) + return 0; + if (y < ymin || y > ymax || dy <= 0) + return 0; + const ibin = Math.min(npx - 1, Math.floor((x - xmin) / dx)), + jbin = Math.min(npy - 1, Math.floor((y - ymin) / dy)), + xlow = xmin + ibin * dx, + ylow = ymin + jbin * dy, + t = (x - xlow) / dx, + u = (y - ylow) / dy, + k1 = jbin * (npx + 1) + ibin, + k2 = jbin * (npx + 1) + ibin + 1, + k3 = (jbin + 1) * (npx + 1) + ibin + 1, + k4 = (jbin + 1) * (npx + 1) + ibin; + return (1 - t) * (1 - u) * func.fSave[k1] + t * (1 - u) * func.fSave[k2] + t * u * func.fSave[k3] + (1 - t) * u * func.fSave[k4]; + }; - this.submitCanvExec(`SetPos({${xn.toFixed(4)},${yn.toFixed(4)}})`); - } + ensureBins(func.fNpx, func.fNpy); + hist.fXaxis.fXmin = func.fXmin; + hist.fXaxis.fXmax = func.fXmax; + hist.fYaxis.fXmin = func.fYmin; + hist.fYaxis.fXmax = func.fYmax; - /** @summary Change axis attribute, submit changes to server and redraw axis when specified - * @desc Arguments as redraw_mode, name1, value1, name2, value2, ... */ - changeAxisAttr(redraw_mode) { - const changes = {}; - let indx = 1; - while (indx < arguments.length - 1) { - this.v7AttrChange(changes, arguments[indx], arguments[indx+1]); - this.v7SetAttr(arguments[indx], arguments[indx+1]); - indx += 2; + for (let j = 0; j < func.fNpy; ++j) { + const y = hist.fYaxis.GetBinCenter(j + 1); + for (let i = 0; i < func.fNpx; ++i) { + const x = hist.fXaxis.GetBinCenter(i + 1), + z = getSave(x, y); + hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); + } + } } - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - if (redraw_mode === 1) { - if (this.standalone) - this.redraw(); - else - this.drawAxisAgain(); - } else if (redraw_mode) - this.redrawPad(); - } - /** @summary Change axis log scale kind */ - changeAxisLog(arg) { - if ((this.kind === kAxisLabels) || (this.kind === kAxisTime)) return; - if (arg === 'toggle') arg = this.log ? 0 : 10; + hist.fName = 'Func'; + setHistogramTitle(hist, func.fTitle); + hist.fMinimum = func.fMinimum; + hist.fMaximum = func.fMaximum; + // fHistogram->SetContour(fContour.fN, levels); + hist.fLineColor = func.fLineColor; + hist.fLineStyle = func.fLineStyle; + hist.fLineWidth = func.fLineWidth; + hist.fFillColor = func.fFillColor; + hist.fFillStyle = func.fFillStyle; + hist.fMarkerColor = func.fMarkerColor; + hist.fMarkerStyle = func.fMarkerStyle; + hist.fMarkerSize = func.fMarkerSize; + hist.fBits |= kNoStats; - arg = parseFloat(arg); - if (Number.isFinite(arg)) this.changeAxisAttr(2, 'log', arg, 'symlog', 0); + return hist; } - /** @summary Provide context menu for axis */ - fillAxisContextMenu(menu, kind) { - if (kind) menu.add('Unzoom', () => this.getFramePainter().unzoom(kind)); - - menu.add('sub:Log scale', () => this.changeAxisLog('toggle')); - menu.addchk(!this.log && !this.symlog, 'linear', 0, arg => this.changeAxisLog(arg)); - menu.addchk(this.log && !this.symlog && (this.logbase === 10), 'log10', () => this.changeAxisLog(10)); - menu.addchk(this.log && !this.symlog && (this.logbase === 2), 'log2', () => this.changeAxisLog(2)); - menu.addchk(this.log && !this.symlog && Math.abs(this.logbase - Math.exp(1)) < 0.1, 'ln', () => this.changeAxisLog(Math.exp(1))); - menu.addchk(!this.log && this.symlog, 'symlog', 0, () => - menu.input('set symlog constant', this.symlog || 10, 'float').then(v => this.changeAxisAttr(2, 'symlog', v))); - menu.add('endsub:'); - - menu.add('Divisions', () => menu.input('Set axis devisions', this.v7EvalAttr('ndiv', 508), 'int').then(val => this.changeAxisAttr(2, 'ndiv', val))); + /** @summary Extract function ranges */ + extractAxesProperties(ndim) { + super.extractAxesProperties(ndim); - menu.add('sub:Ticks'); - menu.addRColorMenu('color', this.ticksColor, col => this.changeAxisAttr(1, 'ticks_color', col)); - menu.addSizeMenu('size', 0, 0.05, 0.01, this.ticksSize/this.scalingSize, sz => this.changeAxisAttr(1, 'ticks_size', sz)); - menu.addSelectMenu('side', ['normal', 'invert', 'both'], this.ticksSide, side => this.changeAxisAttr(1, 'ticks_side', side)); - menu.add('endsub:'); + const func = this.#func, nsave = func?.fSave.length ?? 0; - if (!this.optionUnlab && this.labelsFont) { - menu.add('sub:Labels'); - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.labelsOffset/this.scalingSize, - offset => this.changeAxisAttr(1, 'labels_offset', offset)); - menu.addRAttrTextItems(this.labelsFont, { noangle: 1, noalign: 1 }, - change => this.changeAxisAttr(1, 'labels_' + change.name, change.value)); - menu.addchk(this.labelsFont.angle, 'rotate', res => this.changeAxisAttr(1, 'labels_angle', res ? 180 : 0)); - menu.add('endsub:'); + if (nsave > 6 && this.#use_saved_points) { + this.xmin = Math.min(this.xmin, func.fSave[nsave - 6]); + this.xmax = Math.max(this.xmax, func.fSave[nsave - 5]); + this.ymin = Math.min(this.ymin, func.fSave[nsave - 4]); + this.ymax = Math.max(this.ymax, func.fSave[nsave - 3]); } + if (func) { + this.xmin = Math.min(this.xmin, func.fXmin); + this.xmax = Math.max(this.xmax, func.fXmax); + this.ymin = Math.min(this.ymin, func.fYmin); + this.ymax = Math.max(this.ymax, func.fYmax); + } + } - menu.add('sub:Title', () => menu.input('Enter axis title', this.fTitle).then(t => this.changeAxisAttr(1, 'title_value', t))); - - if (this.fTitle) { - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.titleOffset/this.scalingSize, - offset => this.changeAxisAttr(1, 'title_offset', offset)); + /** @summary return tooltips for TF2 */ + getTF2Tooltips(pnt) { + const lines = [this.getObjectHint()], + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); - menu.addSelectMenu('position', ['left', 'center', 'right'], this.titlePos, - pos => this.changeAxisAttr(1, 'title_position', pos)); + if (!funcs || !isFunc(this.#func?.evalPar)) { + lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); + return lines; + } - menu.addchk(this.isTitleRotated(), 'rotate', flag => this.changeAxisAttr(1, 'title_angle', flag ? 180 : 0)); + const x = funcs.revertAxis('x', pnt.x), + y = funcs.revertAxis('y', pnt.y); + let z = 0, iserror = false; - menu.addRAttrTextItems(this.titleFont, { noangle: 1, noalign: 1 }, change => this.changeAxisAttr(1, 'title_' + change.name, change.value)); + try { + z = this.#func.evalPar(x, y); + } catch { + iserror = true; } - menu.add('endsub:'); - return true; + lines.push('x = ' + funcs.axisAsText('x', x), + 'y = ' + funcs.axisAsText('y', y), + 'value = ' + (iserror ? '' : floatToString(z, gStyle.fStatFormat))); + return lines; } -} // class RAxisPainter - -/** - * @summary Painter class for RFrame, main handler for interactivity - * - * @private - */ + /** @summary process tooltip event for TF2 object */ + processTooltipEvent(pnt) { + if (this.#use_saved_points) + return super.processTooltipEvent(pnt); -class RFramePainter extends RObjectPainter { + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} tframe - RFrame object */ - constructor(dom, tframe) { - super(dom, tframe, '', 'frame'); - this.mode3d = false; - this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing - this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.axes_drawn = false; - this.keys_handler = null; - this.projection = 0; // different projections - this.v7_frame = true; // indicator of v7, used in interactive part - } + if (!this.getG() || !pnt) { + ttrect?.remove(); + return null; + } - /** @summary Returns frame painter - object itself */ - getFramePainter() { return this; } + const res = { + name: this.#func?.fName, title: this.#func?.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getTF2Tooltips(pnt), exact: true, menu: true + }; - /** @summary Returns true if it is ROOT6 frame - * @private */ - is_root6() { return false; } + if (pnt.disabled) + ttrect.remove(); + else { + if (ttrect.empty()) { + ttrect = this.getG().append('svg:circle') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .style('fill', 'none') + .attr('r', (this.lineatt?.width ?? 1) + 4); + } - /** @summary Set active flag for frame - can block some events - * @private */ - setFrameActive(on) { - this.enabledKeys = on && settings.HandleKeys; - // used only in 3D mode - if (this.control) - this.control.enableKeys = this.enabledKeys; - } + ttrect.attr('cx', pnt.x) + .attr('cy', pnt.y); + if (this.lineatt) + ttrect.call(this.lineatt.func); + } - setLastEventPos(pnt) { - // set position of last context menu event, can be - this.fLastEventPnt = pnt; + return res; } - getLastEventPos() { - // return position of last event - return this.fLastEventPnt; + /** @summary fill information for TWebCanvas + * @desc Used to inform web canvas when evaluation failed + * @private */ + fillWebObjectOptions(opt) { + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } - /** @summary Update graphical attributes */ - updateAttributes(force) { - if ((this.fX1NDC === undefined) || (force && !this.modified_NDC)) { - const rect = this.getPadPainter().getPadRect(); - this.fX1NDC = this.v7EvalLength('margins_left', rect.width, gStyle.fPadLeftMargin) / rect.width; - this.fY1NDC = this.v7EvalLength('margins_bottom', rect.height, gStyle.fPadBottomMargin) / rect.height; - this.fX2NDC = 1 - this.v7EvalLength('margins_right', rect.width, gStyle.fPadRightMargin) / rect.width; - this.fY2NDC = 1 - this.v7EvalLength('margins_top', rect.height, gStyle.fPadTopMargin) / rect.height; - } - - if (!this.fillatt) - this.createv7AttFill(); + /** @summary draw TF2 object */ + static async draw(dom, tf2, opt) { + const web = scanTF1Options(opt); + opt = web.opt; + delete web.opt; - this.createv7AttLine('border_'); - } + const d = new DrawOptions(opt); + if (d.empty()) + opt = 'cont3'; + else if (d.opt === 'SAME') + opt = 'cont2 same'; - /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.projection); } + let hist; - /** @summary Rcalculate frame ranges using specified projection functions - * @desc Not yet used in v7 */ - recalculateRange(Proj) { - this.projection = Proj || 0; + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH2F); - if ((this.projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >=90))) { - console.warn(`Mercator Projection: latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.projection = 0; + if (!hist) { + hist = createHistogram(clTH2F, 20, 20); + hist.fBits |= kNoStats; } - const func = this.getProjectionFunc(); - if (!func) return; + const painter = new TF2Painter(dom, hist); - const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; - if (this.scale_xmin < 0 && this.scale_xmax > 0) { - pnts.push(func(0, this.scale_ymin)); - pnts.push(func(0, this.scale_ymax)); - } - if (this.scale_ymin < 0 && this.scale_ymax > 0) { - pnts.push(func(this.scale_xmin, 0)); - pnts.push(func(this.scale_xmax, 0)); - } + painter.setFunc(tf2); + Object.assign(painter, web); + painter.createTF2Histogram(tf2, hist); - this.original_xmin = this.scale_xmin; - this.original_xmax = this.scale_xmax; - this.original_ymin = this.scale_ymin; - this.original_ymax = this.scale_ymax; + return THistPainter._drawHist(painter, opt); + } - this.scale_xmin = this.scale_xmax = pnts[0].x; - this.scale_ymin = this.scale_ymax = pnts[0].y; +} // class TF2Painter - for (let n = 1; n < pnts.length; ++n) { - this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); - this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); - this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); - this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); - } +var TF2Painter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TF2Painter: TF2Painter +}); + +function findZValue(arrz, arrv, cross = 0) { + for (let i = arrz.length - 2; i >= 0; --i) { + const v1 = arrv[i], v2 = arrv[i + 1], + z1 = arrz[i], z2 = arrz[i + 1]; + if (v1 === cross) + return z1; + if (v2 === cross) + return z2; + if ((v1 < cross) !== (v2 < cross)) + return z1 + (cross - v1) / (v2 - v1) * (z2 - z1); } + return arrz[0] - 1; +} - /** @summary Draw axes grids - * @desc Called immediately after axes drawing */ - drawGrids() { - const layer = this.getFrameSvg().selectChild('.axis_layer'); - layer.selectAll('.xgrid').remove(); - layer.selectAll('.ygrid').remove(); +/** + * @summary Painter for TF3 object + * + * @private + */ - const h = this.getFrameHeight(), - w = this.getFrameWidth(), - gridx = this.v7EvalAttr('gridX', false), - gridy = this.v7EvalAttr('gridY', false), - grid_style = getSvgLineStyle(gStyle.fGridStyle), - grid_color = (gStyle.fGridColor > 0) ? this.getColor(gStyle.fGridColor) : 'black'; +class TF3Painter extends TH2Painter { - if (this.x_handle) - this.x_handle.draw_grid = gridx; + #use_saved_points; // use saved points for drawing + #func; // func object + #fail_eval; // fail evaluation of function - // add a grid on x axis, if the option is set - if (this.x_handle?.draw_grid) { - let grid = ''; - for (let n = 0; n < this.x_handle.ticks.length; ++n) { - grid += this.swap_xy - ? `M0,${h+this.x_handle.ticks[n]}h${w}` - : `M${this.x_handle.ticks[n]},0v${h}`; - } + /** @summary Assign function */ + setFunc(f) { this.#func = f; } - if (grid) { - layer.append('svg:path') - .attr('class', 'xgrid') - .attr('d', grid) - .style('stroke', grid_color) - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', grid_style); - } - } + /** @summary Returns drawn object name */ + getObjectName() { return this.#func?.fName ?? 'func'; } - if (this.y_handle) - this.y_handle.draw_grid = gridy; + /** @summary Returns drawn object class name */ + getClassName() { return this.#func?._typename ?? clTF3; } - // add a grid on y axis, if the option is set - if (this.y_handle?.draw_grid) { - let grid = ''; - for (let n = 0; n < this.y_handle.ticks.length; ++n) { - grid += this.swap_xy - ? `M${this.y_handle.ticks[n]},0v${h}` - : `M0,${h+this.y_handle.ticks[n]}h${w}`; - } + /** @summary Returns true while function is drawn */ + isTF1() { return true; } - if (grid) { - layer.append('svg:path') - .attr('class', 'ygrid') - .attr('d', grid) - .style('stroke', grid_color) - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', grid_style); - } + /** @summary Returns primary function which was then drawn as histogram */ + getPrimaryObject() { return this.#func; } + + /** @summary Update histogram */ + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; + delete obj.evalPar; + const histo = this.getHisto(); + + if (this._webcanv_hist) { + const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); } + + this.setFunc(obj); + this.createTF3Histogram(obj, histo); + this.scanContent(); + return true; } - /** @summary Converts 'raw' axis value into text */ - axisAsText(axis, value) { - const handle = this[`${axis}_handle`]; + /** @summary Redraw TF2 + * @private */ + redraw(reason) { + if (!this.#use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'logy' || reason === 'zoom')) { + this.createTF3Histogram(this.#func, this.getHisto()); + this.scanContent(); + } - return handle ? handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']) : value.toPrecision(4); + return super.redraw(reason); } - /** @summary Set axix range */ - _setAxisRange(prefix, vmin, vmax) { - const nmin = `${prefix}min`, nmax = `${prefix}max`; - if (this[nmin] !== this[nmax]) return; - let min = this.v7EvalAttr(`${prefix}_min`), - max = this.v7EvalAttr(`${prefix}_max`); + /** @summary Create histogram for TF3 drawing + * @private */ + createTF3Histogram(func, hist) { + const nsave = func.fSave.length - 9; - if (min !== undefined) vmin = min; - if (max !== undefined) vmax = max; + this.#use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this._use_saved > 1)); - if (vmin < vmax) { - this[nmin] = vmin; - this[nmax] = vmax; + const fp = this.getFramePainter(), + pad = this.getPadPainter()?.getRootPad(true), + logx = pad?.fLogx, logy = pad?.fLogy, + gr = fp?.getGrFuncs(this.second_x, this.second_y); + let xmin = func.fXmin, xmax = func.fXmax, + ymin = func.fYmin, ymax = func.fYmax, + zmin = func.fZmin, zmax = func.fZmax, + npx = Math.max(func.fNpx, 20), + npy = Math.max(func.fNpy, 20), + npz = Math.max(func.fNpz, 20); + + if (gr?.zoom_xmin !== gr?.zoom_xmax) { + const dx = (xmax - xmin) / npx; + if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) + xmin = Math.max(xmin, gr.zoom_xmin - dx); + if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) + xmax = Math.min(xmax, gr.zoom_xmax + dx); } - const nzmin = `zoom_${prefix}min`, nzmax = `zoom_${prefix}max`; + if (gr?.zoom_ymin !== gr?.zoom_ymax) { + const dy = (ymax - ymin) / npy; + if ((ymin < gr.zoom_ymin) && (gr.zoom_ymin < ymax)) + ymin = Math.max(ymin, gr.zoom_ymin - dy); + if ((ymin < gr.zoom_ymax) && (gr.zoom_ymax < ymax)) + ymax = Math.min(ymax, gr.zoom_ymax + dy); + } - if ((this[nzmin] === this[nzmax]) && !this.zoomChangedInteractive(prefix)) { - min = this.v7EvalAttr(`${prefix}_zoomMin`); - max = this.v7EvalAttr(`${prefix}_zoomMax`); + if (gr?.zoom_zmin !== gr?.zoom_zmax) { + // no need for dz here - TH2 is not binned over Z axis + if ((zmin < gr.zoom_zmin) && (gr.zoom_zmin < zmax)) + zmin = gr.zoom_zmin; + if ((zmin < gr.zoom_zmax) && (gr.zoom_zmax < zmax)) + zmax = gr.zoom_zmax; + } - if ((min !== undefined) || (max !== undefined)) { - this[nzmin] = (min === undefined) ? this[nmin] : min; - this[nzmax] = (max === undefined) ? this[nmax] : max; + const ensureBins = (nx, ny) => { + if (hist.fNcells !== (nx + 2) * (ny + 2)) { + hist.fNcells = (nx + 2) * (ny + 2); + hist.fArray = new Float32Array(hist.fNcells); } - } - } + hist.fArray.fill(0); + hist.fXaxis.fNbins = nx; + hist.fXaxis.fXbins = []; + hist.fYaxis.fNbins = ny; + hist.fYaxis.fXbins = []; + hist.fXaxis.fXmin = xmin; + hist.fXaxis.fXmax = xmax; + hist.fYaxis.fXmin = ymin; + hist.fYaxis.fXmax = ymax; + hist.fMinimum = zmin; + hist.fMaximum = zmax; + }; - /** @summary Set axes ranges for drawing, check configured attributes if range already specified */ - setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax) { - if (this.axes_drawn) return; - this.xaxis = xaxis; - this._setAxisRange('x', xmin, xmax); - this.yaxis = yaxis; - this._setAxisRange('y', ymin, ymax); - this.zaxis = zaxis; - this._setAxisRange('z', zmin, zmax); - } + this.#fail_eval = undefined; - /** @summary Set secondary axes ranges */ - setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { - if (second_x) { - this.x2axis = xaxis; - this._setAxisRange('x2', xmin, xmax); - } - if (second_y) { - this.y2axis = yaxis; - this._setAxisRange('y2', ymin, ymax); - } - } + if (!this.#use_saved_points) { + let iserror = false; - /** @summary Create x,y objects which maps user coordinates into pixels - * @desc Must be used only for v6 objects, see TFramePainter for more details - * @private */ - createXY(opts) { - if (this.self_drawaxes) return; + if (!func.evalPar && !proivdeEvalPar(func)) + iserror = true; - this.cleanXY(); // remove all previous configurations + ensureBins(npx, npy); - if (!opts) opts = { ndim: 1 }; + if (logx) + produceTAxisLogScale(hist.fXaxis, npx, xmin, xmax); + if (logy) + produceTAxisLogScale(hist.fYaxis, npy, ymin, ymax); - this.v6axes = true; - this.swap_xy = opts.swap_xy || false; - this.reverse_x = opts.reverse_x || false; - this.reverse_y = opts.reverse_y || false; + const arrv = new Array(npz), arrz = new Array(npz); + for (let k = 0; k < npz; ++k) + arrz[k] = zmin + k / (npz - 1) * (zmax - zmin); - this.logx = this.v7EvalAttr('x_log', 0); - this.logy = this.v7EvalAttr('y_log', 0); + for (let j = 0; (j < npy) && !iserror; ++j) { + for (let i = 0; (i < npx) && !iserror; ++i) { + const x = hist.fXaxis.GetBinCenter(i + 1), + y = hist.fYaxis.GetBinCenter(j + 1); + let z = 0; - const w = this.getFrameWidth(), h = this.getFrameHeight(); + try { + for (let k = 0; k < npz; ++k) + arrv[k] = func.evalPar(x, y, arrz[k]); - this.scales_ndim = opts.ndim; + z = findZValue(arrz, arrv); + } catch { + iserror = true; + } - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; + if (!iserror) + hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); + } + } - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; + if (iserror) + this.#fail_eval = true; - if (opts.extra_y_space) { - const log_scale = this.swap_xy ? this.logx : this.logy; - if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax)*1.1); - else - this.scale_ymax += (this.scale_ymax - this.scale_ymin)*0.1; + if (iserror && (nsave > 0)) + this.#use_saved_points = true; } - if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { - this.zoom_xmin = opts.zoom_xmin; - this.zoom_xmax = opts.zoom_xmax; - } + if (this.#use_saved_points) { + xmin = func.fSave[nsave]; + xmax = func.fSave[nsave + 1]; + ymin = func.fSave[nsave + 2]; + ymax = func.fSave[nsave + 3]; + zmin = func.fSave[nsave + 4]; + zmax = func.fSave[nsave + 5]; + npx = Math.round(func.fSave[nsave + 6]); + npy = Math.round(func.fSave[nsave + 7]); + npz = Math.round(func.fSave[nsave + 8]); - if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { - this.zoom_ymin = opts.zoom_ymin; - this.zoom_ymax = opts.zoom_ymax; - } + const dz = (zmax - zmin) / npz; - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - } + ensureBins(npx + 1, npy + 1); - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; + const arrv = new Array(npz + 1), + arrz = new Array(npz + 1); + for (let k = 0; k <= npz; k++) + arrz[k] = zmin + k * dz; + + for (let i = 0; i <= npx; ++i) { + for (let j = 0; j <= npy; ++j) { + for (let k = 0; k <= npz; k++) + arrv[k] = func.fSave[i + (npx + 1) * (j + (npy + 1) * k)]; + const z = findZValue(arrz, arrv); + hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); + } + } } - let xaxis = this.xaxis, yaxis = this.yaxis; - if (xaxis?._typename !== clTAxis) xaxis = create$1(clTAxis); - if (yaxis?._typename !== clTAxis) yaxis = create$1(clTAxis); + hist.fName = 'Func'; + setHistogramTitle(hist, func.fTitle); - this.x_handle = new TAxisPainter(this.getDom(), xaxis, true); - this.x_handle.setPadName(this.getPadName()); - this.x_handle.optionUnlab = this.v7EvalAttr('x_labels_hide', false); - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x, - log: this.swap_xy ? this.logy : this.logx, - symlog: this.swap_xy ? opts.symlog_y : opts.symlog_x, - logcheckmin: this.swap_xy, - logminfactor: 0.0001 }); + // hist.fMinimum = func.fMinimum; + // hist.fMaximum = func.fMaximum; + // fHistogram->SetContour(fContour.fN, levels); + hist.fLineColor = func.fLineColor; + hist.fLineStyle = func.fLineStyle; + hist.fLineWidth = func.fLineWidth; + hist.fFillColor = func.fFillColor; + hist.fFillStyle = func.fFillStyle; + hist.fMarkerColor = func.fMarkerColor; + hist.fMarkerStyle = func.fMarkerStyle; + hist.fMarkerSize = func.fMarkerSize; + hist.fBits |= kNoStats; - this.x_handle.assignFrameMembers(this, 'x'); + return hist; + } - this.y_handle = new TAxisPainter(this.getDom(), yaxis, true); - this.y_handle.setPadName(this.getPadName()); - this.y_handle.optionUnlab = this.v7EvalAttr('y_labels_hide', false); + /** @summary Extract function ranges */ + extractAxesProperties(ndim) { + super.extractAxesProperties(ndim); - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y, - log: this.swap_xy ? this.logx : this.logy, - symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.ymax) ? 0.5 * opts.ymin_nz : 0, - logminfactor: 3e-4 }); + const func = this.#func, nsave = func?.fSave.length ?? 0; - this.y_handle.assignFrameMembers(this, 'y'); + if (nsave > 9 && this.#use_saved_points) { + this.xmin = Math.min(this.xmin, func.fSave[nsave - 9]); + this.xmax = Math.max(this.xmax, func.fSave[nsave - 8]); + this.ymin = Math.min(this.ymin, func.fSave[nsave - 7]); + this.ymax = Math.max(this.ymax, func.fSave[nsave - 6]); + this.zmin = Math.min(this.zmin, func.fSave[nsave - 5]); + this.zmax = Math.max(this.zmax, func.fSave[nsave - 4]); + } + if (func) { + this.xmin = Math.min(this.xmin, func.fXmin); + this.xmax = Math.max(this.xmax, func.fXmax); + this.ymin = Math.min(this.ymin, func.fYmin); + this.ymax = Math.max(this.ymax, func.fYmax); + this.zmin = Math.min(this.zmin, func.fZmin); + this.zmax = Math.max(this.zmax, func.fZmax); + } } - /** @summary Identify if requested axes are drawn - * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { - return !second_x && !second_y ? this.axes_drawn : false; + /** @summary fill information for TWebCanvas + * @desc Used to inform web canvas when evaluation failed + * @private */ + fillWebObjectOptions(opt) { + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } - /** @summary Draw configured axes on the frame - * @desc axes can be drawn only for main histogram */ - async drawAxes() { - if (this.axes_drawn || (this.xmin === this.xmax) || (this.ymin === this.ymax)) - return this.axes_drawn; - - const ticksx = this.v7EvalAttr('ticksX', 1), - ticksy = this.v7EvalAttr('ticksY', 1); - let sidex = 1, sidey = 1; + /** @summary draw TF3 object */ + static async draw(dom, tf3, opt) { + const web = scanTF1Options(opt); + opt = web.opt; + delete web.opt; - if (this.v7EvalAttr('swapX', false)) sidex = -1; - if (this.v7EvalAttr('swapY', false)) sidey = -1; + const d = new DrawOptions(opt); + if (d.empty() || (opt === 'gl')) + opt = 'surf1'; + else if (d.opt === 'SAME') + opt = 'surf1 same'; - const w = this.getFrameWidth(), h = this.getFrameHeight(); + let hist; - if (!this.v6axes) { - // this is partially same as v6 createXY method + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH2F); - this.cleanupAxes(); + if (!hist) { + hist = createHistogram(clTH2F, 20, 20); + hist.fBits |= kNoStats; + } - this.swap_xy = false; + const painter = new TF3Painter(dom, hist); - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - } else { - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; - } + painter.setFunc(tf3, clTF3); + Object.assign(painter, web); + painter.createTF3Histogram(tf3, hist); + return THistPainter._drawHist(painter, opt); + } - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - } else { - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; - } +} // class TF3Painter - this.recalculateRange(0); +var TF3Painter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TF3Painter: TF3Painter +}); - this.x_handle = new RAxisPainter(this.getDom(), this, this.xaxis, 'x_'); - this.x_handle.setPadName(this.getPadName()); - this.x_handle.snapid = this.snapid; - this.x_handle.draw_swapside = (sidex < 0); - this.x_handle.draw_ticks = ticksx; +/** + * @summary Painter for TSpline classes. + * + * @private + */ - this.y_handle = new RAxisPainter(this.getDom(), this, this.yaxis, 'y_'); - this.y_handle.setPadName(this.getPadName()); - this.y_handle.snapid = this.snapid; - this.y_handle.draw_swapside = (sidey < 0); - this.y_handle.draw_ticks = ticksy; +class TSplinePainter extends ObjectPainter { - this.z_handle = new RAxisPainter(this.getDom(), this, this.zaxis, 'z_'); - this.z_handle.setPadName(this.getPadName()); - this.z_handle.snapid = this.snapid; + #knot_size; // graphical size of each knot - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, false, [0, w], w, { reverse: false }); - this.x_handle.assignFrameMembers(this, 'x'); + /** @summary Update TSpline object + * @private */ + updateObject(obj, opt) { + const spline = this.getObject(); - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, true, [h, 0], -h, { reverse: false }); - this.y_handle.assignFrameMembers(this, 'y'); + if (spline._typename !== obj._typename) + return false; - // only get basic properties like log scale - this.z_handle.configureZAxis('zaxis', this); - } + if (spline !== obj) + Object.assign(spline, obj); - const layer = this.getFrameSvg().selectChild('.axis_layer'); + if (opt !== undefined) + this.decodeOptions(opt); - this.x_handle.has_obstacle = false; + return true; + } - const draw_horiz = this.swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.swap_xy ? this.x_handle : this.y_handle; - let pr; + /** @summary Evaluate spline at given position + * @private */ + eval(knot, x) { + const dx = x - knot.fX; - if (this.getPadPainter()?._fast_drawing) - pr = Promise.resolve(true); // do nothing - else if (this.v6axes) { - // in v7 ticksx/y values shifted by 1 relative to v6 - // In v7 ticksx === 0 means no ticks, ticksx === 1 equivalent to === 0 in v6 + if (knot._typename === 'TSplinePoly3') + return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * knot.fD)); - const can_adjust_frame = false, disable_x_draw = false, disable_y_draw = false; + if (knot._typename === 'TSplinePoly5') + return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * (knot.fD + dx * (knot.fE + dx * knot.fF)))); - draw_horiz.disable_ticks = (ticksx <= 0); - draw_vertical.disable_ticks = (ticksy <= 0); + return knot.fY + dx; + } - const pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - (ticksx > 1) ? -h : 0, disable_x_draw, - undefined, false, this.getPadPainter().getPadHeight() - h - this.getFrameY()), + /** @summary Find idex for x value + * @private */ + findX(x) { + const spline = this.getObject(); + let klow = 0, khig = spline.fNp - 1; - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - (ticksy > 1) ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this._frame_x, can_adjust_frame); + if (x <= spline.fXmin) + return klow; + if (x >= spline.fXmax) + return khig; - pr = Promise.all([pr1, pr2]).then(() => this.drawGrids()); + if (spline.fKstep) { + // Equidistant knots, use histogram + klow = Math.round((x - spline.fXmin) / spline.fDelta); + // Correction for rounding errors + if (x < spline.fPoly[klow].fX) + klow = Math.max(klow - 1, 0); + else if ((klow < khig) && (x > spline.fPoly[klow + 1].fX)) + ++klow; } else { - let arr = []; + // Non equidistant knots, binary search + while (khig - klow > 1) { + const khalf = Math.round((klow + khig) / 2); + if (x > spline.fPoly[khalf].fX) + klow = khalf; + else + khig = khalf; + } + } + return klow; + } - if (ticksx > 0) - arr.push(draw_horiz.drawAxis(layer, makeTranslate(0, sidex > 0 ? h : 0), sidex)); + /** @summary Create histogram for axes drawing + * @private */ + createDummyHisto() { + const spline = this.getObject(); + let xmin = 0, xmax = 1, ymin = 0, ymax = 1; - if (ticksy > 0) - arr.push(draw_vertical.drawAxis(layer, makeTranslate(sidey > 0 ? 0 : w, h), sidey)); + if (spline.fPoly) { + xmin = xmax = spline.fPoly[0].fX; + ymin = ymax = spline.fPoly[0].fY; - pr = Promise.all(arr).then(() => { - arr = []; - if (ticksx > 1) - arr.push(draw_horiz.drawAxisOtherPlace(layer, makeTranslate(0, sidex < 0 ? h : 0), -sidex, ticksx === 2)); + spline.fPoly.forEach(knot => { + xmin = Math.min(knot.fX, xmin); + xmax = Math.max(knot.fX, xmax); + ymin = Math.min(knot.fY, ymin); + ymax = Math.max(knot.fY, ymax); + }); - if (ticksy > 1) - arr.push(draw_vertical.drawAxisOtherPlace(layer, makeTranslate(sidey < 0 ? 0 : w, h), -sidey, ticksy === 2)); - return Promise.all(arr); - }).then(() => this.drawGrids()); + if (ymax > 0) + ymax *= (1 + gStyle.fHistTopMargin); + if (ymin < 0) + ymin *= (1 + gStyle.fHistTopMargin); } - return pr.then(() => { - this.axes_drawn = true; - return true; - }); + const histo = createHistogram(clTH1I, 10); + + histo.fName = spline.fName + '_hist'; + histo.fTitle = spline.fTitle; + histo.fBits |= kNoStats; + + histo.fXaxis.fXmin = xmin; + histo.fXaxis.fXmax = xmax; + histo.fYaxis.fXmin = ymin; + histo.fYaxis.fXmax = ymax; + histo.fMinimum = ymin; + histo.fMaximum = ymax; + + return histo; } - /** @summary Draw secondary configuread axes */ - drawAxes2(second_x, second_y) { - const w = this.getFrameWidth(), h = this.getFrameHeight(), - layer = this.getFrameSvg().selectChild('.axis_layer'); - let pr1, pr2; + /** @summary Process tooltip event + * @private */ + processTooltipEvent(pnt) { + const spline = this.getObject(), + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); + let cleanup = false, xx, yy, knot = null, indx = 0; - if (second_x) { - if (this.zoom_x2min !== this.zoom_x2max) { - this.scale_x2min = this.zoom_x2min; - this.scale_x2max = this.zoom_x2max; + if ((pnt === null) || !spline || !funcs) + cleanup = true; + else { + xx = funcs.revertAxis('x', pnt.x); + indx = this.findX(xx); + knot = spline.fPoly[indx]; + yy = this.eval(knot, xx); + + if ((indx < spline.fN - 1) && (Math.abs(spline.fPoly[indx + 1].fX - xx) < Math.abs(xx - knot.fX))) + knot = spline.fPoly[++indx]; + + if (Math.abs(funcs.grx(knot.fX) - pnt.x) < 0.5 * this.#knot_size) { + xx = knot.fX; + yy = knot.fY; } else { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; + knot = null; + if ((xx < spline.fXmin) || (xx > spline.fXmax)) + cleanup = true; } - this.x2_handle = new RAxisPainter(this.getDom(), this, this.x2axis, 'x2_'); - this.x2_handle.setPadName(this.getPadName()); - this.x2_handle.snapid = this.snapid; + } - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, false, [0, w], w, { reverse: false }); - this.x2_handle.assignFrameMembers(this, 'x2'); + let gbin = this.getG()?.selectChild('.tooltip_bin'); + const radius = this.lineatt.width + 3; - pr1 = this.x2_handle.drawAxis(layer, null, -1); + if (cleanup || !this.getG()) { + gbin?.remove(); + return null; } - if (second_y) { - if (this.zoom_y2min !== this.zoom_y2max) { - this.scale_y2min = this.zoom_y2min; - this.scale_y2max = this.zoom_y2max; - } else { - this.scale_y2min = this.y2min; - this.scale_y2max = this.y2max; - } + if (gbin.empty()) { + gbin = this.getG().append('svg:circle') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .attr('r', radius) + .style('fill', 'none') + .call(this.lineatt.func); + } - this.y2_handle = new RAxisPainter(this.getDom(), this, this.y2axis, 'y2_'); - this.y2_handle.setPadName(this.getPadName()); - this.y2_handle.snapid = this.snapid; + const res = { + name: this.getObject().fName, + title: this.getObject().fTitle, + x: funcs.grx(xx), + y: funcs.gry(yy), + color1: this.lineatt.color, + lines: [], + exact: (knot !== null) || (Math.abs(funcs.gry(yy) - pnt.y) < radius) + }; - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, true, [h, 0], -h, { reverse: false }); - this.y2_handle.assignFrameMembers(this, 'y2'); + res.changed = gbin.property('current_xx') !== xx; + res.menu = res.exact; + res.menu_dist = Math.sqrt((res.x - pnt.x) ** 2 + (res.y - pnt.y) ** 2); - pr2 = this.y2_handle.drawAxis(layer, makeTranslate(w, h), -1); + if (res.changed) { + gbin.attr('cx', Math.round(res.x)) + .attr('cy', Math.round(res.y)) + .property('current_xx', xx); } - return Promise.all([pr1, pr2]); + const name = this.getObjectHint(); + if (name) + res.lines.push(name); + res.lines.push(`x = ${funcs.axisAsText('x', xx)}`, + `y = ${funcs.axisAsText('y', yy)}`); + if (knot !== null) { + res.lines.push(`knot = ${indx}`, + `B = ${floatToString(knot.fB, gStyle.fStatFormat)}`, + `C = ${floatToString(knot.fC, gStyle.fStatFormat)}`, + `D = ${floatToString(knot.fD, gStyle.fStatFormat)}`); + if ((knot.fE !== undefined) && (knot.fF !== undefined)) { + res.lines.push(`E = ${floatToString(knot.fE, gStyle.fStatFormat)}`, + `F = ${floatToString(knot.fF, gStyle.fStatFormat)}`); + } + } + + return res; } - /** @summary Return functions to create x/y points based on coordinates - * @desc In default case returns frame painter itself + /** @summary Redraw object * @private */ - getGrFuncs(second_x, second_y) { - const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) return this; + redraw() { + const spline = this.getObject(), + o = this.getOptions(), + funcs = this.getFramePainter().getGrFuncs(o.second_x, o.second_y), + w = funcs.getFrameWidth(), + h = funcs.getFrameHeight(), + g = this.createG(true); - return { - use_x2, - grx: use_x2 ? this.grx2 : this.grx, - x_handle: use_x2 ? this.x2_handle : this.x_handle, - logx: use_x2 ? this.x2_handle.log : this.x_handle.log, - scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, - scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, - use_y2, - gry: use_y2 ? this.gry2 : this.gry, - y_handle: use_y2 ? this.y2_handle : this.y_handle, - logy: use_y2 ? this.y2_handle.log : this.y_handle.log, - scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, - scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - swap_xy: this.swap_xy, - fp: this, - revertAxis(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.revertAxis(name, v); - }, - axisAsText(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.axisAsText(name, v); - } - }; - } + this.#knot_size = 5; // used in tooltip handling - /** @summary function called at the end of resize of frame - * @desc Used to update attributes on the server - * @private */ - sizeChanged() { - const changes = {}; - this.v7AttrChange(changes, 'margins_left', this.fX1NDC); - this.v7AttrChange(changes, 'margins_bottom', this.fY1NDC); - this.v7AttrChange(changes, 'margins_right', 1 - this.fX2NDC); - this.v7AttrChange(changes, 'margins_top', 1 - this.fY2NDC); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server + this.createAttLine({ attr: spline }); - this.redrawPad(); - } + if (o.Line || o.Curve) { + const npx = Math.max(10, spline.fNpx), bins = []; // index of current knot + let xmin = Math.max(funcs.scale_xmin, spline.fXmin), + xmax = Math.min(funcs.scale_xmax, spline.fXmax), + indx = this.findX(xmin); - /** @summary Remove all x/y functions - * @private */ - cleanXY() { - // remove all axes drawings - const clean = (name, grname) => { - this[name]?.cleanup(); - delete this[name]; - delete this[grname]; - }; + if (funcs.logx) { + xmin = Math.log(xmin); + xmax = Math.log(xmax); + } - clean('x_handle', 'grx'); - clean('y_handle', 'gry'); - clean('z_handle', 'grz'); - clean('x2_handle', 'grx2'); - clean('y2_handle', 'gry2'); + for (let n = 0; n < npx; ++n) { + let x = xmin + (xmax - xmin) / npx * (n - 1); + if (funcs.logx) + x = Math.exp(x); - delete this.v6axes; // marker that v6 axes are used - } + while ((indx < spline.fNp - 1) && (x > spline.fPoly[indx + 1].fX)) + ++indx; - /** @summary Remove all axes drawings - * @private */ - cleanupAxes() { - this.cleanXY(); + const y = this.eval(spline.fPoly[indx], x); - this.draw_g?.selectChild('.axis_layer').selectAll('*').remove(); - this.axes_drawn = false; - } + bins.push({ x, y, grx: funcs.grx(x), gry: funcs.gry(y) }); + } - /** @summary Removes all drawn elements of the frame - * @private */ - cleanFrameDrawings() { - // cleanup all 3D drawings if any - if (isFunc(this.create3DScene)) - this.create3DScene(-1); + g.append('svg:path') + .attr('class', 'line') + .attr('d', buildSvgCurve(bins)) + .style('fill', 'none') + .call(this.lineatt.func); + } - this.cleanupAxes(); + if (o.Mark) { + // for tooltips use markers only if nodes where not created - const clean = (name) => { - this[name+'min'] = this[name+'max'] = 0; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - this[`scale_${name}min`] = this[`scale_${name}max`] = 0; - }; + this.createAttMarker({ attr: spline }); - clean('x'); - clean('y'); - clean('z'); - clean('x2'); - clean('y2'); + this.markeratt.resetPos(); - this.draw_g?.selectChild('.main_layer').selectAll('*').remove(); - this.draw_g?.selectChild('.upper_layer').selectAll('*').remove(); - } + this.#knot_size = this.markeratt.getFullSize(); - /** @summary Fully cleanup frame - * @private */ - cleanup() { - this.cleanFrameDrawings(); + let path = ''; - if (this.draw_g) { - this.draw_g.selectAll('*').remove(); - this.draw_g.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null); - } + for (let n = 0; n < spline.fPoly.length; n++) { + const knot = spline.fPoly[n], + grx = funcs.grx(knot.fX); + if ((grx > -this.#knot_size) && (grx < w + this.#knot_size)) { + const gry = funcs.gry(knot.fY); + if ((gry > -this.#knot_size) && (gry < h + this.#knot_size)) + path += this.markeratt.create(grx, gry); + } + } - if (this.keys_handler) { - window.removeEventListener('keydown', this.keys_handler, false); - this.keys_handler = null; + if (path) { + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); + } } - delete this.enabledKeys; - delete this.self_drawaxes; + } - delete this.xaxis; - delete this.yaxis; - delete this.zaxis; - delete this.x2axis; - delete this.y2axis; + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis /* , min, max */) { + // spline can always be calculated and therefore one can zoom inside + return (axis !== 'x') ? false : Boolean(this.getObject()); + } - delete this.draw_g; // frame element managet by the pad + /** @summary Decode options for TSpline drawing */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + o = this.setOptions({ + Same: d.check('SAME'), + Line: d.check('L'), + Curve: d.check('C'), + Mark: d.check('P'), + Hopt: '', + second_x: false, + second_y: false + }); - delete this._click_handler; - delete this._dblclick_handler; + if (!o.Line && !o.Curve && !o.Mark) + o.Curve = true; - const pp = this.getPadPainter(); - if (pp?.frame_painter_ref === this) - delete pp.frame_painter_ref; + if (d.check('X+')) { + o.Hopt += 'X+'; + o.second_x = Boolean(this.getMainPainter()); + } + if (d.check('Y+')) { + o.Hopt += 'Y+'; + o.second_y = Boolean(this.getMainPainter()); + } - super.cleanup(); + this.storeDrawOpt(opt); } - /** @summary Redraw frame - * @private */ - redraw() { - const pp = this.getPadPainter(); - if (pp) pp.frame_painter_ref = this; - - // first update all attributes from objects - this.updateAttributes(); - - const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, - lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)); - let w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)), - rotate = false, fixpos = false, trans; + /** @summary Draw TSpline */ + static async draw(dom, spline, opt) { + const painter = new TSplinePainter(dom, spline); + painter.decodeOptions(opt); - if (pp?.options) { - if (pp.options.RotateFrame) rotate = true; - if (pp.options.FixFrame) fixpos = true; + const no_main = !painter.getMainPainter(); + let promise = Promise.resolve(); + if (no_main || painter.options.second_x || painter.options.second_y) { + if (painter.options.Same && no_main) { + console.warn('TSpline painter requires histogram to be drawn'); + return null; + } + const histo = painter.createDummyHisto(); + promise = TH1Painter.draw(dom, histo, painter.options.Hopt); } - if (rotate) { - trans = `rotate(-90,${lm},${tm}) translate(${lm-h},${tm})`; - [w, h] = [h, w]; - } else - trans = makeTranslate(lm, tm); + return promise.then(() => { + painter.addToPadPrimitives(); + painter.redraw(); + return painter; + }); + } +} // class TSplinePainter - // update values here to let access even when frame is not really updated - this._frame_x = lm; - this._frame_y = tm; - this._frame_width = w; - this._frame_height = h; - this._frame_rotate = rotate; - this._frame_fixpos = fixpos; +var TSplinePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TSplinePainter: TSplinePainter +}); + +/** + * @summary Painter for TBox class + * @private + */ - if (this.mode3d) return this; // no need for real draw in mode3d +class TPiePainter extends ObjectPainter { + + #cx; // recent cx + #cy; // recent cy + #rx; // recent rx + #ry; // recent ry + #slices; // recent slices + #movex; // moving X coordinate + #movey; // moving Y coordinate + #angle0; // initial angle + #offset0; // initial offset + #slice; // moving slice + #mode; // moving mode + #padh; // pad height - // this is svg:g object - container for every other items belonging to frame - this.draw_g = this.getFrameSvg(); + /** @summary Decode options */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + o = this.getOptions(); + o.is3d = d.check('3D'); + o.lblor = 0; + o.sort = 0; + o.samecolor = false; + o.same = d.check('SAME'); + if (d.check('SC')) + o.samecolor = true; // around + if (d.check('T')) + o.lblor = 2; // around + if (d.check('R')) + o.lblor = 1; // along the radius + if (d.check('>')) + o.sort = 1; + if (d.check('<')) + o.sort = -1; + } + + #findDrawnSlice(x, y) { + if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) + return null; + let angle = Math.atan2(y / this.#ry, x / this.#rx); - let top_rect, main_svg; + while (angle < 0.5 * Math.PI) + angle += 2 * Math.PI; - if (this.draw_g.empty()) { - this.draw_g = this.getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame'); + return this.#slices.find(elem => { + return ((elem.a1 < angle) && (angle < elem.a2)) || + ((elem.a1 < angle + 2 * Math.PI) && (angle + 2 * Math.PI < elem.a2)); + }); + } - if (!this.isBatchMode()) - this.draw_g.append('svg:title').text(''); + /** @summary start of drag handler + * @private */ + moveStart(x, y) { + if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) + return; + let angle = Math.atan2(y / this.#ry, x / this.#rx); - top_rect = this.draw_g.append('svg:rect'); + while (angle < 0.5 * Math.PI) + angle += 2 * Math.PI; - main_svg = this.draw_g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); + const pie = this.getObject(), + len = Math.sqrt((x / this.#rx) ** 2 + (y / this.#ry) ** 2), + slice = this.#findDrawnSlice(x, y); - this.draw_g.append('svg:g').attr('class', 'axis_layer'); - this.draw_g.append('svg:g').attr('class', 'upper_layer'); - } else { - top_rect = this.draw_g.selectChild('rect'); - main_svg = this.draw_g.selectChild('.main_layer'); - } + // kind of cursor shown + this.#mode = ((len > 0.95) && (x > this.#rx * 0.95) && this.options.is3d) ? 'n-resize' : ((slice && len - slice.offset < 0.7) ? 'grab' : 'w-resize'); - this.axes_drawn = false; + this.#movex = x; + this.#movey = y; - this.draw_g.attr('transform', trans); + this.getG().style('cursor', this.#mode); - top_rect.attr('x', 0) - .attr('y', 0) - .attr('width', w) - .attr('height', h) - .attr('rx', this.lineatt.rx || null) - .attr('ry', this.lineatt.ry || null) - .call(this.fillatt.func) - .call(this.lineatt.func); + if (this.#mode === 'grab') { + this.#slice = slice.n; + this.#angle0 = len; + this.#offset0 = pie.fPieSlices[this.#slice].fRadiusOffset; + } else if (this.#mode === 'n-resize') { + this.#padh = this.getPadPainter().getPadHeight(); + this.#angle0 = pie.fAngle3D; + this.#offset0 = y; + } else { + this.#angle0 = angle; + this.#offset0 = pie.fAngularOffset; + } + } - main_svg.attr('width', w) - .attr('height', h) - .attr('viewBox', `0 0 ${w} ${h}`); + /** @summary drag handler + * @private */ + moveDrag(dx, dy) { + this.#movex += dx; + this.#movey += dy; - let pr = Promise.resolve(true); + const pie = this.getObject(); - if (this.v7EvalAttr('drawAxes')) { - this.self_drawaxes = true; - this.setAxesRanges(); - pr = this.drawAxes().then(() => this.addInteractivity()); + if (this.#mode === 'grab') { + const len = Math.sqrt((this.#movex / this.#rx) ** 2 + (this.#movey / this.#ry) ** 2); + pie.fPieSlices[this.#slice].fRadiusOffset = Math.max(0, this.#offset0 + 0.25 * (len - this.#angle0)); + } else if (this.#mode === 'n-resize') + pie.fAngle3D = Math.max(5, Math.min(85, this.#angle0 + (this.#movey - this.#offset0) / this.#padh * 180)); + else { + const angle = Math.atan2(this.#movey / this.#ry, this.#movex / this.#rx); + pie.fAngularOffset = this.#offset0 - (angle - this.#angle0) / Math.PI * 180; } - return pr.then(() => { return this; }); + this.drawPie(); } - /** @summary Returns frame X position */ - getFrameX() { return this._frame_x || 0; } + /** @summary end of drag handler + * @private */ + moveEnd(not_changed) { + if (not_changed) + return; - /** @summary Returns frame Y position */ - getFrameY() { return this._frame_y || 0; } + const pie = this.getObject(); - /** @summary Returns frame width */ - getFrameWidth() { return this._frame_width || 0; } + let exec; - /** @summary Returns frame height */ - getFrameHeight() { return this._frame_height || 0; } + if (this.#mode === 'grab') + exec = `SetEntryRadiusOffset(${this.#slice},${pie.fPieSlices[this.#slice].fRadiusOffset})`; + else if (this.#mode === 'n-resize') + exec = `SetAngle3D(${pie.fAngle3D})`; + else + exec = `SetAngularOffset(${pie.fAngularOffset})`; - /** @summary Returns frame rectangle plus extra info for hint display */ - getFrameRect() { - return { - x: this._frame_x || 0, - y: this._frame_y || 0, - width: this.getFrameWidth(), - height: this.getFrameHeight(), - transform: this.draw_g?.attr('transform') || '', - hint_delta_x: 0, - hint_delta_y: 0 - }; - } + if (exec) + this.submitCanvExec(exec + ';;Notify();;'); - /** @summary Returns palette associated with frame */ - getHistPalette() { - return this.getPadPainter().getHistPalette(); - } + this.#mode = null; - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled */ - configureUserClickHandler(handler) { - this._click_handler = isFunc(handler) ? handler : null; + this.getG().style('cursor', null); } - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ - configureUserDblclickHandler(handler) { - this._dblclick_handler = isFunc(handler) ? handler : null; - } + /** @summary Update TPie object */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; - /** @summary function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed - * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax) { - // disable zooming when axis conversion is enabled - if (this.projection) return false; + this.decodeOptions(opt); - if (xmin === 'x') { xmin = xmax; xmax = ymin; ymin = undefined; } else - if (xmin === 'y') { ymax = ymin; ymin = xmax; xmin = xmax = undefined; } else - if (xmin === 'z') { zmin = xmax; zmax = ymin; xmin = xmax = ymin = undefined; } + Object.assign(this.getObject(), obj); - let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), - unzoom_x = false, unzoom_y = false, unzoom_z = false; + return true; + } - if (zoom_x) { - let cnt = 0; - if (xmin <= this.xmin) { xmin = this.xmin; cnt++; } - if (xmax >= this.xmax) { xmax = this.xmax; cnt++; } - if (cnt === 2) { zoom_x = false; unzoom_x = true; } - } else - unzoom_x = (xmin === xmax) && (xmin === 0); + /** @summary Redraw pie */ + async drawPie() { + const maing = this.createG(), + pie = this.getObject(), + o = this.getOptions(), + pp = this.getPadPainter(), + radX = pie.fRadius; + this.#cx = this.axisToSvg('x', pie.fX); + this.#cy = this.axisToSvg('y', pie.fY); - if (zoom_y) { - let cnt = 0; - if (ymin <= this.ymin) { ymin = this.ymin; cnt++; } - if (ymax >= this.ymax) { ymax = this.ymax; cnt++; } - if (cnt === 2) { zoom_y = false; unzoom_y = true; } - } else - unzoom_y = (ymin === ymax) && (ymin === 0); + let radY = radX, pixelHeight = 1; + if (o.is3d) { + radY *= Math.sin(pie.fAngle3D / 180 * Math.PI); + pixelHeight = this.axisToSvg('y', pie.fY - pie.fHeight) - this.#cy; + } - if (zoom_z) { - let cnt = 0; - // if (this.logz && this.ymin_nz && this.getDimension()===2) main_zmin = 0.3*this.ymin_nz; - if (zmin <= this.zmin) { zmin = this.zmin; cnt++; } - if (zmax >= this.zmax) { zmax = this.zmax; cnt++; } - if (cnt === 2) { zoom_z = false; unzoom_z = true; } - } else - unzoom_z = (zmin === zmax) && (zmin === 0); + maing.style('cursor', this.#mode || null); + this.createAttText({ attr: pie }); - let changed = false, r_x = '', r_y = '', r_z = '', is_any_check = false; - const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false] - }, + const rx = this.axisToSvg('x', pie.fX + radX) - this.#cx, + ry = this.axisToSvg('y', pie.fY - radY) - this.#cy, + dist_to_15pi = a => { + while (a < 0.5 * Math.PI) + a += 2 * Math.PI; + while (a >= 2.5 * Math.PI) + a -= 2 * Math.PI; + return Math.abs(a - 1.5 * Math.PI); + }; - checkZooming = (painter, force) => { - if (!force && !isFunc(painter.canZoomInside)) return; + makeTranslate(maing, this.#cx, this.#cy); - is_any_check = true; + // pie.fPieSlices[4].fValue = 100; - if (zoom_x && (force || painter.canZoomInside('x', xmin, xmax))) { - this.zoom_xmin = xmin; - this.zoom_xmax = xmax; - changed = true; r_x = '0'; - zoom_x = false; - req.values[0] = xmin; req.values[1] = xmax; - req.flags[0] = req.flags[1] = true; - } - if (zoom_y && (force || painter.canZoomInside('y', ymin, ymax))) { - this.zoom_ymin = ymin; - this.zoom_ymax = ymax; - changed = true; r_y = '1'; - zoom_y = false; - req.values[2] = ymin; req.values[3] = ymax; - req.flags[2] = req.flags[3] = true; - } - if (zoom_z && (force || painter.canZoomInside('z', zmin, zmax))) { - this.zoom_zmin = zmin; - this.zoom_zmax = zmax; - changed = true; r_z = '2'; - zoom_z = false; - req.values[4] = zmin; req.values[5] = zmax; - req.flags[4] = req.flags[5] = true; - } - }; + const arr = []; + let total = 0, af = -pie.fAngularOffset / 180 * Math.PI; + // ensure all angles are positive + while (af <= 2 * Math.PI) + af += 2 * Math.PI; + + for (let n = 0; n < pie.fPieSlices.length; n++) { + const slice = pie.fPieSlices[n], + value = slice.fValue; + total += value; + arr.push({ + n, value, slice, + offset: slice.fRadiusOffset, + attline: this.createAttLine(slice), + attfill: this.createAttFill(slice) + }); + } - // first process zooming (if any) - if (zoom_x || zoom_y || zoom_z) - this.forEachPainter(painter => checkZooming(painter)); + // sort in increase/decrease order + if (o.sort !== 0) + arr.sort((v1, v2) => { return o.sort * (v1.value - v2.value); }); - // force zooming when no any other painter can verify zoom range - if (!is_any_check && this.self_drawaxes) - checkZooming(null, true); + // now assign angles for each slice - // and process unzoom, if any - if (unzoom_x || unzoom_y || unzoom_z) { - if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) { changed = true; r_x = '0'; } - this.zoom_xmin = this.zoom_xmax = 0; - req.values[0] = req.values[1] = -1; - } - if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { changed = true; r_y = '1'; } - this.zoom_ymin = this.zoom_ymax = 0; - req.values[2] = req.values[3] = -1; - } - if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) { changed = true; r_z = '2'; } - this.zoom_zmin = this.zoom_zmax = 0; - req.values[4] = req.values[5] = -1; + for (let n = 0; n < arr.length; n++) { + const entry = arr[n]; + entry.seq = n; + entry.a2 = af; + af -= entry.value / total * 2 * Math.PI; + entry.a1 = af; + + entry.x1 = Math.round(rx * Math.cos(entry.a1)); + entry.y1 = Math.round(ry * Math.sin(entry.a1)); + entry.x2 = Math.round(rx * Math.cos(entry.a2)); + entry.y2 = Math.round(ry * Math.sin(entry.a2)); + + if (entry.offset) { + const coef = radX > 0 ? entry.offset / radX : 0.1, + mid_angle = (entry.a1 + entry.a2) / 2; + entry.dx = Math.round(rx * coef * Math.cos(mid_angle)); + entry.dy = Math.round(ry * coef * Math.sin(mid_angle)); + } else + entry.dx = entry.dy = 0; + } + + const add_path = (entry, path) => { + const elem = maing.append('svg:path') + .attr('d', path) + .call(entry.attline.func) + .call(entry.attfill.func); + if (entry.offset) + makeTranslate(elem, entry.dx, entry.dy); + }, build_pie = (entry, func) => { + // use same segments for side and top/bottom curves + let a = entry.a1, border = 0; + while (border <= entry.a1) + border += Math.PI; + while (a < entry.a2) { + if (border >= entry.a2) { + func(a, entry.a2, entry); + a = entry.a2; + } else { + func(a, border, entry); + a = border; + border += Math.PI; + } } + }, add_curved_side = (aa1, aa2, entry) => { + if (dist_to_15pi((aa1 + aa2) / 2) < 0.5 * Math.PI) + return; + const xx1 = Math.round(rx * Math.cos(aa1)), + yy1 = Math.round(ry * Math.sin(aa1)), + xx2 = Math.round(rx * Math.cos(aa2)), + yy2 = Math.round(ry * Math.sin(aa2)); + add_path(entry, `M${xx1},${yy1}a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}v${pixelHeight}a${rx},${ry},0,0,0,${xx1 - xx2},${yy1 - yy2}z`); + }, add_planar_side = (x, y, entry) => { + add_path(entry, `M0,0v${pixelHeight}l${x},${y}v${-pixelHeight}z`); + }; + + // build main paths for each slice + + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; + if (o.is3d) { + entry.pie_path = ''; + build_pie(entry, (aa1, aa2) => { + const xx1 = Math.round(rx * Math.cos(aa1)), + yy1 = Math.round(ry * Math.sin(aa1)), + xx2 = Math.round(rx * Math.cos(aa2)), + yy2 = Math.round(ry * Math.sin(aa2)); + entry.pie_path += `a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}`; + }); + } else + entry.pie_path = `a${rx},${ry},0,0,1,${entry.x2 - entry.x1},${entry.y2 - entry.y1}`; } - if (!changed) return false; + // code to create 3d effect - if (this.v7NormalMode()) - this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); + if (o.is3d) { + let start_indx = -1, border = Math.PI / 2; + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; - return this.interactiveRedraw('pad', 'zoom' + r_x + r_y + r_z).then(() => true); - } + // first add bottom + add_path(entry, `M0,${pixelHeight}l${entry.x1},${entry.y1}${entry.pie_path}z`); - /** @summary Provide zooming of single axis - * @desc One can specify names like x/y/z but also second axis x2 or y2 */ - async zoomSingle(name, vmin, vmax) { - const names = ['x', 'y', 'z', 'x2', 'y2'], indx = names.indexOf(name); + if ((entry.a1 <= 1.5 * Math.PI) && (entry.a2 >= 1.5 * Math.PI)) + start_indx = indx; + else if ((entry.a1 <= 3.5 * Math.PI) && (entry.a2 >= 3.5 * Math.PI)) { + start_indx = indx; + border = 2.5 * Math.PI; + } + } - // disable zooming when axis conversion is enabled - if (this.projection || !this[name+'_handle'] || (indx < 0)) - return false; + if (start_indx < 0) { + console.error('fail to find start index, use default'); + start_indx = 0; + } - let zoom_v = (vmin !== vmax), unzoom_v = false; + let indx = start_indx, cnt = arr.length; - if (zoom_v) { - let cnt = 0; - if (vmin <= this[name+'min']) { vmin = this[name+'min']; cnt++; } - if (vmax >= this[name+'max']) { vmax = this[name+'max']; cnt++; } - if (cnt === 2) { zoom_v = false; unzoom_v = true; } - } else - unzoom_v = (vmin === vmax) && (vmin === 0); + while ((arr[indx].a1 > border) && (cnt-- > 0)) { + const entry1 = arr[indx]; + indx++; + if (indx === arr.length) { + indx = 0; + border += 2 * Math.PI; + } + const entry2 = arr[indx]; + if (entry1.offset || entry2.offset) { + add_planar_side(entry1.x1, entry1.y1, entry1); + add_planar_side(entry2.x2, entry2.y2, entry2); + } + // curved + build_pie(entry1, add_curved_side); + } - let changed = false, is_any_check = false; - const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false, false, false, false, false] - }, + indx = start_indx; - checkZooming = (painter, force) => { - if (!force && !isFunc(painter?.canZoomInside)) return; + while (cnt-- > 0) { + const entry1 = arr[indx]; + indx = (indx === 0) ? arr.length - 1 : indx - 1; + const entry2 = arr[indx]; - is_any_check = true; + if (entry1.offset || entry2.offset) { + add_planar_side(entry1.x2, entry1.y2, entry1); + add_planar_side(entry2.x1, entry2.y1, entry2); + } - if (zoom_v && (force || painter.canZoomInside(name[0], vmin, vmax))) { - this['zoom_' + name + 'min'] = vmin; - this['zoom_' + name + 'max'] = vmax; - changed = true; - zoom_v = false; - req.values[indx*2] = vmin; req.values[indx*2+1] = vmax; - req.flags[indx*2] = req.flags[indx*2+1] = true; + build_pie(entry2, add_curved_side); } - }; - - // first process zooming (if any) - if (zoom_v) - this.forEachPainter(painter => checkZooming(painter)); - - // force zooming when no any other painter can verify zoom range - if (!is_any_check && this.self_drawaxes) - checkZooming(null, true); + } - if (unzoom_v) { - if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) changed = true; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - req.values[indx*2] = req.values[indx*2+1] = -1; + // add main path + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; + add_path(entry, `M0,0l${entry.x1},${entry.y1}${entry.pie_path}z`); } - if (!changed) return false; + // at the end draw text - if (this.v7NormalMode()) - this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); + return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp), maing).then(() => { + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx], + slice = entry.slice, + mid_angle = (entry.a1 + entry.a2) / 2, + frac = total ? slice.fValue / total : 0; - return this.interactiveRedraw('pad', `zoom${indx}`).then(() => true); - } + let tmptxt = pie.fLabelFormat; + tmptxt = tmptxt.replaceAll('%txt', slice.fTitle); + tmptxt = tmptxt.replaceAll('%val', floatToString(slice.fValue, pie.fValueFormat)); + tmptxt = tmptxt.replaceAll('%frac', floatToString(frac, pie.fFractionFormat)); + tmptxt = tmptxt.replaceAll('%perc', floatToString(frac * 100, pie.fPercentFormat) + '%'); - /** @summary Checks if specified axis zoomed */ - isAxisZoomed(axis) { - return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; - } + const arg = { + draw_g: maing, + x: entry.dx + rx * (1 + pie.fLabelsOffset) * Math.cos(mid_angle), + y: entry.dy + ry * (1 + pie.fLabelsOffset) * Math.sin(mid_angle), + latex: 1, + align: 22, + text: tmptxt + }; - /** @summary Unzoom specified axes - * @return {Promise} with boolean flag if zoom is changed */ - async unzoom(dox, doy, doz) { - if (dox === 'all') - return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); + if (o.samecolor) + arg.color = this.getColor(slice.fFillColor); - if ((dox === 'x2') || (dox === 'y2')) { - return this.zoomSingle(dox, 0, 0).then(changed => { - if (changed) this.zoomChangedInteractive(dox, 'unzoom'); - return changed; - }); - } + if (o.lblor === 1) { + // radial positioning of the labels + arg.rotate = Math.atan2(arg.y, arg.x) / Math.PI * 180; + if (arg.x > 0) + arg.align = 12; + else { + arg.align = 32; + arg.rotate += 180; + } + } else if (o.lblor === 2) { + // in the slice + arg.rotate = Math.atan2(entry.y2 - entry.y1, entry.x2 - entry.x1) / Math.PI * 180; + if ((arg.rotate > 90) || (arg.rotate < -90)) { + arg.rotate += 180; + arg.align = 21; + } else + arg.align = 23; + } else if ((arg.x >= 0) && (arg.y >= 0)) { + arg.align = 13; + if (o.is3d) + arg.y += pixelHeight; + } else if ((arg.x > 0) && (arg.y < 0)) + arg.align = 11; + else if ((arg.x < 0) && (arg.y >= 0)) { + arg.align = 33; + if (o.is3d) + arg.y += pixelHeight; + } else if ((arg.x < 0) && (arg.y < 0)) + arg.align = 31; - if (typeof dox === 'undefined') dox = doy = doz = true; else - if (isStr(dox)) { doz = dox.indexOf('z') >= 0; doy = dox.indexOf('y') >= 0; dox = dox.indexOf('x') >= 0; } + this.drawText(arg); + } + return this.finishTextDrawing(maing); + }).then(() => { + this.#rx = rx; + this.#ry = ry; + this.#slices = arr; + return this; + }); + } - return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, - doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined).then(changed => { - if (changed && dox) this.zoomChangedInteractive('x', 'unzoom'); - if (changed && doy) this.zoomChangedInteractive('y', 'unzoom'); - if (changed && doz) this.zoomChangedInteractive('z', 'unzoom'); + /** @summary Draw TPie title */ + async drawTitle(first_time) { + return drawObjectTitle(this, first_time, !this.options.same, true); + } - return changed; + /** @summary Redraw TPie object */ + async redraw() { + return this.drawPie().then(() => this.drawTitle()).then(() => { + assignContextMenu(this); + addMoveHandler(this); + return this; }); } - /** @summary Mark/check if zoom for specific axis was changed interactively - * @private */ - zoomChangedInteractive(axis, value) { - if (axis === 'reset') { - this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; - return; + /** @summary Fill specific items */ + fillContextMenuItems(menu) { + const pie = this.getObject(); + menu.add('Change title', () => menu.input('Enter new title', pie.fTitle).then(t => { + pie.fTitle = t; + this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); + })); + menu.add('Angular offset', () => menu.input('Enter new angular offset', pie.fAngularOffset, 'float').then(v => { + pie.fAngularOffset = v; + this.interactiveRedraw('pad', `exec:SetAngularOffset(${v})`); + })); + if (this.options.is3d) { + menu.add('Angle 3D', () => menu.input('Enter new angle 3D', pie.fAngle3D, 'float', 0, 90).then(v => { + pie.fAngle3D = v; + this.interactiveRedraw('pad', `exec:SetAngle3D(${v})`); + })); } - if (!axis || axis === 'any') - return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) return; - - const fld = 'zoom_changed_' + axis; - if (value === undefined) return this[fld]; - if (value === 'unzoom') { - // special handling of unzoom, only if was never changed before flag set to true - this[fld] = (this[fld] === undefined); + if (!menu.getEventPosition()) return; - } - if (value) this[fld] = true; - } + const svg = this.getPadPainter()?.getPadSvg(), + rect = svg.node().getBoundingClientRect(), + x = menu.getEventPosition().clientX - rect.left - svg.node().clientLeft, + y = menu.getEventPosition().clientY - rect.top - svg.node().clientTop, + elem = this.#findDrawnSlice(x - this.#cx, y - this.#cy); + if (!elem) + return; - /** @summary Fill menu for frame when server is not there */ - fillObjectOfflineMenu(menu, kind) { - if ((kind !== 'x') && (kind !== 'y')) return; + menu.sub(`Slice${elem.n}`); - menu.add('Unzoom', () => this.unzoom(kind)); + menu.add('Title', () => menu.input('Enter new title', elem.slice.fTitle).then(t => { + elem.slice.fTitle = t; + this.interactiveRedraw('pad', `exec:SetEntryLabel(${elem.n},"${t}")`); + })); + menu.add('Offset', () => menu.input('Enter new slice offset', elem.slice.fRadiusOffset, 'float', 0, 1).then(v => { + elem.slice.fRadiusOffset = v; + this.interactiveRedraw('pad', `exec:SetEntryRadiusOffset(${elem.n},${v})`); + })); - // if (this[kind+'_kind'] === kAxisNormal) - // menu.addchk(this['log'+kind], 'SetLog'+kind, this.toggleAxisLog.bind(this, kind)); + menu.sub('Line att'); + menu.addSizeMenu('width', 1, 10, 1, elem.attline.width, arg => { + elem.slice.fLineWidth = arg; + this.interactiveRedraw('pad', `exec:SetEntryLineWidth(${elem.n},${arg})`); + }); + if (!elem.attline.nocolor) { + menu.addColorMenu('color', elem.attline.color, arg => { + elem.slice.fLineColor = getColorId(arg).id; + this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryLineColor', elem.n)); + }); + } + menu.addLineStyleMenu('style', elem.attline.style, id => { + elem.slice.fLineStyle = id; + this.interactiveRedraw('pad', `exec:SetEntryLineStyle(${elem.n},${id})`); + }); + menu.endsub(); + + menu.sub('Fill att'); + menu.addColorMenu('color', elem.attfill.colorindx, arg => { + elem.slice.fFillColor = getColorId(arg).id; + this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryFillColor', elem.n)); + }, elem.attfill.kind); + menu.addFillStyleMenu('style', elem.attfill.pattern, elem.attfill.colorindx, id => { + elem.slice.fFillStyle = id; + this.interactiveRedraw('pad', `exec:SetEntryFillStyle(${elem.n},${id})`); + }); + menu.endsub(); - // here should be all axes attributes in offline + menu.endsub(); } - /** @summary Set grid drawing for specified axis */ - changeFrameAttr(attr, value) { - const changes = {}; - this.v7AttrChange(changes, attr, value); - this.v7SetAttr(attr, value); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - this.redrawPad(); + /** @summary Draw TPie object */ + static async draw(dom, obj, opt) { + const painter = new TPiePainter(dom, obj, opt); + painter.decodeOptions(opt); + return ensureTCanvas(painter, false) + .then(() => painter.drawPie()) + .then(() => painter.drawTitle(true)) + .then(() => { + assignContextMenu(painter); + addMoveHandler(painter); + return painter; + }); } - /** @summary Fill context menu */ - fillContextMenu(menu, kind /* , obj */) { - // when fill and show context menu, remove all zooming +} // class TPiePainter - if (kind === 'pal') kind = 'z'; +var TPiePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TPiePainter: TPiePainter +}); - if ((kind === 'x') || (kind === 'y') || (kind === 'x2') || (kind === 'y2')) { - const handle = this[kind+'_handle']; - if (!handle) return false; - menu.add('header: ' + kind.toUpperCase() + ' axis'); - return handle.fillAxisContextMenu(menu, kind); - } +/** + * @summary Painter for TArrow class + * @private + */ - const alone = menu.size() === 0; +class TArrowPainter extends TLinePainter { - if (alone) - menu.add('header:Frame'); - else - menu.add('separator'); + #beg; + #mid; + #end; + #angle2; // half of angle in rad + #wsize; // arrow size - if (this.zoom_xmin !== this.zoom_xmax) - menu.add('Unzoom X', () => this.unzoom('x')); - if (this.zoom_ymin !== this.zoom_ymax) - menu.add('Unzoom Y', () => this.unzoom('y')); - if (this.zoom_zmin !== this.zoom_zmax) - menu.add('Unzoom Z', () => this.unzoom('z')); - if (this.zoom_x2min !== this.zoom_x2max) - menu.add('Unzoom X2', () => this.unzoom('x2')); - if (this.zoom_y2min !== this.zoom_y2max) - menu.add('Unzoom Y2', () => this.unzoom('y2')); - menu.add('Unzoom all', () => this.unzoom('all')); + /** @summary Create line segment with rotation */ + rotate(angle, x0, y0) { + let dx = this.#wsize * Math.cos(angle), dy = this.#wsize * Math.sin(angle), res = ''; + if ((x0 !== undefined) && (y0 !== undefined)) + res = `M${Math.round(x0 - dx)},${Math.round(y0 - dy)}`; + else { + dx = -dx; + dy = -dy; + } + res += `l${Math.round(dx)},${Math.round(dy)}`; + if (x0 && (y0 === undefined)) + res += 'z'; + return res; + } - menu.add('separator'); + /** @summary Create SVG path for the arrow */ + createPath() { + const angle = Math.atan2(this.y2 - this.y1, this.x2 - this.x1), + dlen = this.#wsize * Math.cos(this.#angle2), + dx = dlen * Math.cos(angle), dy = dlen * Math.sin(angle); - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + let path = ''; + if (this.#beg) { + path += this.rotate(angle - Math.PI - this.#angle2, this.x1, this.y1) + + this.rotate(angle - Math.PI + this.#angle2, this.#beg > 10); + } - if (this.x_handle) - menu.addchk(this.x_handle.draw_grid, 'Grid x', flag => this.changeFrameAttr('gridX', flag)); - if (this.y_handle) - menu.addchk(this.y_handle.draw_grid, 'Grid y', flag => this.changeFrameAttr('gridY', flag)); - if (this.x_handle && !this.x2_handle) - menu.addchk(this.x_handle.draw_swapside, 'Swap x', flag => this.changeFrameAttr('swapX', flag)); - if (this.y_handle && !this.y2_handle) - menu.addchk(this.y_handle.draw_swapside, 'Swap y', flag => this.changeFrameAttr('swapY', flag)); - if (this.x_handle && !this.x2_handle) { - menu.add('sub:Ticks x'); - menu.addchk(this.x_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksX', 0)); - menu.addchk(this.x_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksX', 1)); - menu.addchk(this.x_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksX', 2)); - menu.addchk(this.x_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksX', 3)); - menu.add('endsub:'); - } - if (this.y_handle && !this.y2_handle) { - menu.add('sub:Ticks y'); - menu.addchk(this.y_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksY', 0)); - menu.addchk(this.y_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksY', 1)); - menu.addchk(this.y_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksY', 2)); - menu.addchk(this.y_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksY', 3)); - menu.add('endsub:'); - } + if (this.#mid % 10 === 2) { + path += this.rotate(angle - Math.PI - this.#angle2, (this.x1 + this.x2 - dx) / 2, (this.y1 + this.y2 - dy) / 2) + + this.rotate(angle - Math.PI + this.#angle2, this.#mid > 10); + } - menu.addAttributesMenu(this, alone ? '' : 'Frame '); - menu.add('separator'); + if (this.#mid % 10 === 1) { + path += this.rotate(angle - this.#angle2, (this.x1 + this.x2 + dx) / 2, (this.y1 + this.y2 + dy) / 2) + + this.rotate(angle + this.#angle2, this.#mid > 10); + } - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`frame.${fmt}`, () => this.getPadPainter().saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.add('endsub:'); + if (this.#end) { + path += this.rotate(angle - this.#angle2, this.x2, this.y2) + + this.rotate(angle + this.#angle2, this.#end > 10); + } - return true; + return `M${Math.round(this.x1 + (this.#beg > 10 ? dx : 0))},${Math.round(this.y1 + (this.#beg > 10 ? dy : 0))}` + + `L${Math.round(this.x2 - (this.#end > 10 ? dx : 0))},${Math.round(this.y2 - (this.#end > 10 ? dy : 0))}` + + path; } - /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; } + /** @summary calculate all TArrow coordinates */ + prepareDraw() { + super.prepareDraw(); - /** @summary Show axis status message - * @desc method called normally when mouse enter main object element - * @private */ - showAxisStatus(axis_name, evnt) { - const hint_name = axis_name, hint_title = 'axis', - m = pointer(evnt, this.getFrameSvg().node()); - let id = (axis_name === 'x') ? 0 : 1; + const arrow = this.getObject(), + oo = arrow.fOption, + rect = this.getPadPainter().getPadRect(); - if (this.swap_xy) id = 1 - id; + this.#wsize = Math.max(3, Math.round(Math.max(rect.width, rect.height) * arrow.fArrowSize * 0.8)); + this.#angle2 = arrow.fAngle / 2 / 180 * Math.PI; + this.#beg = this.#mid = this.#end = 0; - const axis_value = this.revertAxis(axis_name, m[id]); + if (oo.indexOf('<') === 0) + this.#beg = (oo.indexOf('<|') === 0) ? 12 : 2; + if (oo.indexOf('->-') >= 0) + this.#mid = 1; + else if (oo.indexOf('-|>-') >= 0) + this.#mid = 11; + else if (oo.indexOf('-<-') >= 0) + this.#mid = 2; + else if (oo.indexOf('-<|-') >= 0) + this.#mid = 12; - this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${Math.round(m[0])},${Math.round(m[1])}`); - } + const p1 = oo.lastIndexOf('>'), p2 = oo.lastIndexOf('|>'), len = oo.length; + if ((p1 >= 0) && (p1 === len - 1)) + this.#end = ((p2 >= 0) && (p2 === len - 2)) ? 11 : 1; - /** @summary Add interactive keys handlers - * @private */ - addKeysHandler() { - if (this.isBatchMode()) return; - FrameInteractive.assign(this); - this.addFrameKeysHandler(); + this.createAttFill({ attr: arrow, enable: (this.#beg > 10) || (this.#end > 10) }); } - /** @summary Add interactive functionality to the frame - * @private */ - addInteractivity(for_second_axes) { - if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) - return true; - - FrameInteractive.assign(this); - if (!for_second_axes) - this.addBasicInteractivity(); - return this.addFrameInteractivity(for_second_axes); + /** @summary Add extras to path for TArrow */ + addExtras(elem) { + elem.call(this.fillatt.func); } - /** @summary Set selected range back to pad object - to be implemented - * @private */ - setRootPadRange(/* pad, is3d */) { - // TODO: change of pad range and send back to root application + /** @summary Draw TArrow object */ + static async draw(dom, obj, opt) { + const painter = new TArrowPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); } - /** @summary Toggle log scale on the specified axes */ - toggleAxisLog(axis) { - const handle = this[axis+'_handle']; - return handle?.changeAxisLog('toggle'); - } +} // class TArrowPainter -} // class RFramePainter +var TArrowPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TArrowPainter: TArrowPainter +}); + +const kPolyLineNDC = BIT(14); /** - * @summary Painter class for RPad - * + * @summary Painter for TPolyLine class * @private */ -class RPadPainter extends RObjectPainter { +class TPolyLinePainter extends ObjectPainter { - /** @summary constructor */ - constructor(dom, pad, iscan) { - super(dom, pad, '', 'pad'); - this.pad = pad; - this.iscan = iscan; // indicate if working with canvas - this.this_pad_name = ''; - if (!this.iscan && (pad !== null)) { - if (pad.fObjectID) - this.this_pad_name = 'pad' + pad.fObjectID; // use objectid as padname - else - this.this_pad_name = 'ppp' + internals.id_counter++; // artificical name + #dx; // interactive change + #dy; // interactive change + #isndc; // if NDC coordinates used + + /** @summary Dragging object + * @private */ + moveDrag(dx, dy) { + this.#dx += dx; + this.#dy += dy; + makeTranslate(this.getG().select('path'), this.#dx, this.#dy); + } + + /** @summary End dragging object + * @private */ + moveEnd(not_changed) { + if (not_changed) + return; + const polyline = this.getObject(), + func = this.getAxisToSvgFunc(this.#isndc); + let exec = ''; + + for (let n = 0; n <= polyline.fLastPoint; ++n) { + const x = this.svgToAxis('x', func.x(polyline.fX[n]) + this.#dx, this.#isndc), + y = this.svgToAxis('y', func.y(polyline.fY[n]) + this.#dy, this.#isndc); + polyline.fX[n] = x; + polyline.fY[n] = y; + exec += `SetPoint(${n},${x},${y});;`; } - this.painters = []; // complete list of all painters in the pad - this.has_canvas = true; - this.forEachPainter = this.forEachPainterInPad; + this.submitCanvExec(exec + 'Notify();;'); + this.redraw(); + } - const d = this.selectDom(); - if (!d.empty() && d.property('_batch_mode')) - this.batch_mode = true; + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const polyline = this.getObject(), + isndc = polyline.TestBit(kPolyLineNDC); + if (isndc || !polyline.fLastPoint) + return null; + let minx = polyline.fX[0], maxx = minx, + miny = polyline.fY[0], maxy = miny; + for (let n = 1; n <= polyline.fLastPoint; ++n) { + minx = Math.min(minx, polyline.fX[n]); + maxx = Math.max(maxx, polyline.fX[n]); + miny = Math.min(miny, polyline.fY[n]); + maxy = Math.max(maxy, polyline.fY[n]); + } + return { minx, miny, maxx, maxy }; } - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { - if (this.batch_mode !== undefined) - return this.batch_mode; + /** @summary Redraw poly line */ + redraw() { + const g = this.createG(), + polyline = this.getObject(), + isndc = polyline.TestBit(kPolyLineNDC), + opt = this.getDrawOpt() || polyline.fOption, + dofill = (polyline._typename === clTPolyLine) && (isStr(opt) && opt.toLowerCase().indexOf('f') >= 0), + func = this.getAxisToSvgFunc(isndc); - if (isBatchMode()) - return true; + this.createAttLine({ attr: polyline }); + this.createAttFill({ attr: polyline, enable: dofill }); - if (!this.iscan && this.has_canvas) - return this.getCanvPainter()?.isBatchMode(); + let cmd = ''; + for (let n = 0; n <= polyline.fLastPoint; ++n) + cmd += `${n > 0 ? 'L' : 'M'}${func.x(polyline.fX[n])},${func.y(polyline.fY[n])}`; - return false; - } + g.append('svg:path') + .attr('d', cmd + (dofill ? 'Z' : '')) + .call(dofill ? () => {} : this.lineatt.func) + .call(this.fillatt.func); - /** @summary Indicates that is not Root6 pad painter - * @private */ - isRoot6() { return false; } + assignContextMenu(this); - /** @summary Returns true if pad is editable */ - isEditable() { - return true; - } + addMoveHandler(this); - /** @summary Returns SVG element for the pad itself - * @private */ - svg_this_pad() { - return this.getPadSvg(this.this_pad_name); - } + this.#dx = this.#dy = 0; + this.#isndc = isndc; - /** @summary Returns main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { - return this.main_painter_ref || null; + return this; } - /** @summary Assign main painter on the pad - * @private */ - setMainPainter(painter, force) { - if (!this.main_painter_ref || force) - this.main_painter_ref = painter; + /** @summary Draw TPolyLine object */ + static async draw(dom, obj, opt) { + const painter = new TPolyLinePainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); } - /** @summary cleanup pad and all primitives inside */ - cleanup() { - if (this._doing_draw) - console.error('pad drawing is not completed when cleanup is called'); - - this.painters.forEach(p => p.cleanup()); +} // class TPolyLinePainter - const svg_p = this.svg_this_pad(); - if (!svg_p.empty()) { - svg_p.property('pad_painter', null); - if (!this.iscan) svg_p.remove(); - } +var TPolyLinePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TPolyLinePainter: TPolyLinePainter +}); - delete this.main_painter_ref; - delete this.frame_painter_ref; - delete this.pads_cache; - delete this._pad_x; - delete this._pad_y; - delete this._pad_width; - delete this._pad_height; - delete this._doing_draw; - delete this._dfltRFont; +/** @summary Drawing TGaxis + * @private */ +class TGaxisPainter extends TAxisPainter { - this.painters = []; - this.pad = null; - this.draw_object = null; - this.pad_frame = null; - this.this_pad_name = undefined; - this.has_canvas = false; + /** @summary Convert TGaxis position into NDC to fix it when frame zoomed */ + convertTo(opt) { + const gaxis = this.getObject(), + x1 = this.axisToSvg('x', gaxis.fX1), + y1 = this.axisToSvg('y', gaxis.fY1), + x2 = this.axisToSvg('x', gaxis.fX2), + y2 = this.axisToSvg('y', gaxis.fY2); - selectActivePad({ pp: this, active: false }); + if (opt === 'ndc') { + const pw = this.getPadPainter().getPadWidth(), + ph = this.getPadPainter().getPadHeight(); + + gaxis.fX1 = x1 / pw; + gaxis.fX2 = x2 / pw; + gaxis.fY1 = (ph - y1) / ph; + gaxis.fY2 = (ph - y2) / ph; + this.use_ndc = true; + } else if (opt === 'frame') { + const rect = this.getFramePainter().getFrameRect(); + gaxis.fX1 = (x1 - rect.x) / rect.width; + gaxis.fX2 = (x2 - rect.x) / rect.width; + gaxis.fY1 = (y1 - rect.y) / rect.height; + gaxis.fY2 = (y2 - rect.y) / rect.height; + this.bind_frame = true; + } + } - super.cleanup(); + /** @summary Drag moving handle */ + moveDrag(dx, dy) { + this.gaxis_x += dx; + this.gaxis_y += dy; + makeTranslate(this.getG(), this.gaxis_x, this.gaxis_y); } - /** @summary Returns frame painter inside the pad - * @private */ - getFramePainter() { return this.frame_painter_ref; } + /** @summary Drag end handle */ + moveEnd(not_changed) { + if (not_changed) + return; - /** @summary get pad width */ - getPadWidth() { return this._pad_width || 0; } + const gaxis = this.getObject(); - /** @summary get pad height */ - getPadHeight() { return this._pad_height || 0; } + let fx, fy; + if (this.bind_frame) { + const rect = this.getFramePainter().getFrameRect(); + fx = (this.gaxis_x - rect.x) / rect.width; + fy = (this.gaxis_y - rect.y) / rect.height; + } else { + fx = this.svgToAxis('x', this.gaxis_x, this.use_ndc); + fy = this.svgToAxis('y', this.gaxis_y, this.use_ndc); + } - /** @summary return pad log state x or y are allowed */ - getPadLog(name) { return false; } + if (this.vertical) { + gaxis.fX1 = gaxis.fX2 = fx; + if (this.reverse) { + gaxis.fY2 = fy + (gaxis.fY2 - gaxis.fY1); + gaxis.fY1 = fy; + } else { + gaxis.fY1 = fy + (gaxis.fY1 - gaxis.fY2); + gaxis.fY2 = fy; + } + } else { + if (this.reverse) { + gaxis.fX1 = fx + (gaxis.fX1 - gaxis.fX2); + gaxis.fX2 = fx; + } else { + gaxis.fX2 = fx + (gaxis.fX2 - gaxis.fX1); + gaxis.fX1 = fx; + } + gaxis.fY1 = gaxis.fY2 = fy; + } - /** @summary get pad rect */ - getPadRect() { - return { - x: this._pad_x || 0, - y: this._pad_y || 0, - width: this.getPadWidth(), - height: this.getPadHeight() - }; + this.submitAxisExec(`SetX1(${gaxis.fX1});;SetX2(${gaxis.fX2});;SetY1(${gaxis.fY1});;SetY2(${gaxis.fY2})`, true); } - /** @summary Returns frame coordiantes - also when frame is not drawn */ - getFrameRect() { - const fp = this.getFramePainter(); - if (fp) return fp.getFrameRect(); + /** @summary Redraw axis, used in standalone mode for TGaxis */ + redraw() { + const gaxis = this.getObject(), + min = gaxis.fWmin, + max = gaxis.fWmax; + let x1, y1, x2, y2; + + if (this.bind_frame) { + const rect = this.getFramePainter().getFrameRect(); + x1 = Math.round(rect.x + gaxis.fX1 * rect.width); + x2 = Math.round(rect.x + gaxis.fX2 * rect.width); + y1 = Math.round(rect.y + gaxis.fY1 * rect.height); + y2 = Math.round(rect.y + gaxis.fY2 * rect.height); + } else { + x1 = this.axisToSvg('x', gaxis.fX1, this.use_ndc); + y1 = this.axisToSvg('y', gaxis.fY1, this.use_ndc); + x2 = this.axisToSvg('x', gaxis.fX2, this.use_ndc); + y2 = this.axisToSvg('y', gaxis.fY2, this.use_ndc); + } + + const w = x2 - x1, h = y1 - y2, + vertical = Math.abs(w) < Math.abs(h); + let sz = vertical ? h : w, reverse = false; - const w = this.getPadWidth(), - h = this.getPadHeight(), - rect = {}; + if (sz < 0) { + reverse = true; + sz = -sz; + if (vertical) + y2 = y1; + else + x1 = x2; + } - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - rect.transform = makeTranslate(rect.x, rect.y) || ''; - return rect; + this.configureAxis(vertical ? 'yaxis' : 'xaxis', min, max, min, max, vertical, [0, sz], { + time_scale: gaxis.fChopt.indexOf('t') >= 0, + log: (gaxis.fChopt.indexOf('G') >= 0) ? 1 : 0, + reverse, + swap_side: reverse, + axis_func: this.axis_func + }); + + this.gaxis_x = x1; + this.gaxis_y = y2; + + return this.drawAxis(this.createG(), Math.abs(w), Math.abs(h), makeTranslate(this.gaxis_x, this.gaxis_y) || '').then(() => { + addMoveHandler(this); + assignContextMenu(this, kNoReorder); + return this; + }); } - /** @summary return RPad object */ - getRootPad(is_root6) { - return (is_root6 === undefined) || !is_root6 ? this.pad : null; + /** @summary Fill TGaxis context menu items */ + fillContextMenuItems(menu) { + menu.addTAxisMenu(EAxisBits, this, this.getObject(), ''); } - /** @summary Cleanup primitives from pad - selector lets define which painters to remove - * @private */ - cleanPrimitives(selector) { - if (!isFunc(selector)) return; + /** @summary Check if there is function for TGaxis can be found */ + async checkFuncion() { + const gaxis = this.getObject(); + if (!gaxis.fFunctionName) { + this.axis_func = null; + return; + } + const func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); - for (let k = this.painters.length-1; k >= 0; --k) { - if (selector(this.painters[k])) { - this.painters[k].cleanup(); - this.painters.splice(k, 1); + let promise = Promise.resolve(func); + if (!func) { + const h = getHPainter(), + item = h?.findItem({ name: gaxis.fFunctionName, check_keys: true }); + if (item) { + promise = h.getObject({ item }).then(res => { + return res?.obj?._typename === clTF1 ? res.obj : null; + }); } } + + return promise.then(f => { + this.axis_func = f; + if (f) + proivdeEvalPar(f); + }); } - /** @summary Removes and cleanup specified primitive - * @desc also secondary primitives will be removed - * @return new index to continue loop or -111 if main painter removed - * @private */ - removePrimitive(indx) { - const prim = this.painters[indx], arr = []; - let resindx = indx; - for (let k = this.painters.length-1; k >= 0; --k) { - if ((k === indx) || this.painters[k].isSecondary(prim)) { - arr.push(this.painters[k]); - this.painters.splice(k, 1); - if (k <= indx) resindx--; + /** @summary Create handle for custom function in the axis */ + createFuncHandle(func, logbase, smin, smax) { + const res = function(v) { return res.toGraph(v); }; + res._func = func; + res._domain = [smin, smax]; + res._scale = logbase ? log().base(logbase) : linear(); + res._scale.domain(res._domain).range([0, 100]); + res.eval = function(v) { + try { + v = res._func.evalPar(v); + } catch { + v = 0; } - } + return Number.isFinite(v) ? v : 0; + }; - arr.forEach(painter => { - painter.cleanup(); - if (this.main_painter_ref === painter) { - delete this.main_painter_ref; - resindx = -111; + const vmin = res.eval(smin), vmax = res.eval(smax); + if ((vmin < vmax) === (smin < smax)) { + res._vmin = vmin; + res._vk = 1 / (vmax - vmin); + } else if (vmin === vmax) { + res._vmin = 0; + res._vk = 1; + } else { + res._vmin = vmax; + res._vk = 1 / (vmin - vmax); + } + res._range = [0, 100]; + res.range = function(arr) { + if (arr) { + res._range = arr; + return res; } - }); + return res._range; + }; - return resindx; - } + res.domain = function() { return res._domain; }; - /** @summary try to find object by name in list of pad primitives - * @desc used to find title drawing - * @private */ - findInPrimitives(objname, objtype) { - console.warn('findInPrimitives not implemented for RPad'); - return null; - } + res.toGraph = function(v) { + const rel = (res.eval(v) - res._vmin) * res._vk; + return res._range[0] * (1 - rel) + res._range[1] * rel; + }; - /** @summary Try to find painter for specified object - * @desc can be used to find painter for some special objects, registered as - * histogram functions - * @private */ - findPainterFor(selobj, selname, seltype) { - return this.painters.find(p => { - const pobj = p.getObject(); - if (!pobj) return false; + res.ticks = function(arg) { return res._scale.ticks(arg); }; - if (selobj && (pobj === selobj)) return true; - if (!selname && !seltype) return false; - if (selname && (pobj.fName !== selname)) return false; - if (seltype && (pobj._typename !== seltype)) return false; - return true; - }); + return res; } - /** @summary Returns palette associated with pad. - * @desc Either from existing palette painter or just default palette */ - getHistPalette() { - const pp = this.findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`); + /** @summary Draw TGaxis object */ + static async draw(dom, obj, opt) { + const painter = new TGaxisPainter(dom, obj, false); - if (pp) return pp.getHistPalette(); + return ensureTCanvas(painter, false).then(() => { + if (opt) + painter.convertTo(opt); + return painter.checkFuncion(); + }).then(() => painter.redraw()); + } - if (!this.fDfltPalette) { - this.fDfltPalette = { - _typename: `${nsREX}RPalette`, - fColors: [{ fOrdinal: 0, fColor: { fColor: 'rgb(53, 42, 135)' } }, - { fOrdinal: 0.125, fColor: { fColor: 'rgb(15, 92, 221)' } }, - { fOrdinal: 0.25, fColor: { fColor: 'rgb(20, 129, 214)' } }, - { fOrdinal: 0.375, fColor: { fColor: 'rgb(6, 164, 202)' } }, - { fOrdinal: 0.5, fColor: { fColor: 'rgb(46, 183, 164)' } }, - { fOrdinal: 0.625, fColor: { fColor: 'rgb(135, 191, 119)' } }, - { fOrdinal: 0.75, fColor: { fColor: 'rgb(209, 187, 89)' } }, - { fOrdinal: 0.875, fColor: { fColor: 'rgb(254, 200, 50)' } }, - { fOrdinal: 1, fColor: { fColor: 'rgb(249, 251, 14)' } }], - fInterpolate: true, - fNormalized: true - }; - addMethods(this.fDfltPalette, `${nsREX}RPalette`); - } +} // class TGaxisPainter - return this.fDfltPalette; - } +var TGaxisPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TGaxisPainter: TGaxisPainter +}); - /** @summary Returns number of painters - * @private */ - getNumPainters() { return this.painters.length; } +/** + * @summary Painter for TBox class + * @private + */ - /** @summary Call function for each painter in pad - * @param {function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and subpads, 'objects' only for object in current pad - * @private */ - forEachPainterInPad(userfunc, kind) { - if (!kind) kind = 'all'; - if (kind !== 'objects') userfunc(this); - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') userfunc(sub); - } - } +class TBoxPainter extends ObjectPainter { - /** @summary register for pad events receiver - * @desc in pad painter, while pad may be drawn without canvas - * @private */ - registerForPadEvents(receiver) { - this.pad_events_receiver = receiver; - } + #border_mode; + #border_size; - /** @summary Generate pad events, normally handled by GED - * @desc in pad painter, while pad may be drawn without canvas + /** @summary start of drag handler * @private */ - producePadEvent(what, padpainter, painter, position, place) { - if ((what === 'select') && isFunc(this.selectActivePad)) - this.selectActivePad(padpainter, painter, position); + moveStart(x, y) { + const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2); - if (this.pad_events_receiver) - this.pad_events_receiver({ what, padpainter, painter, position, place }); + this.c_x1 = Math.abs(x - this.x2) > ww * 0.1; + this.c_x2 = Math.abs(x - this.x1) > ww * 0.1; + this.c_y1 = Math.abs(y - this.y2) > hh * 0.1; + this.c_y2 = Math.abs(y - this.y1) > hh * 0.1; + if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2) + this.c_y1 = this.c_y2 = false; + if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2) + this.c_x1 = this.c_x2 = false; } - /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos, place) { - const istoppad = (this.iscan || !this.has_canvas), - canp = istoppad ? this : this.getCanvPainter(); - - if (painter === undefined) painter = this; - - if (pos && !istoppad) - pos = getAbsPosInCanvas(this.svg_this_pad(), pos); + /** @summary drag handler + * @private */ + moveDrag(dx, dy) { + this.x1 += this.c_x1 ? dx : 0; + this.x2 += this.c_x2 ? dx : 0; + this.y1 += this.c_y1 ? dy : 0; + this.y2 += this.c_y2 ? dy : 0; - selectActivePad({ pp: this, active: true }); + const nodes = this.getG().selectAll('path').nodes(), + pathes = this.getPathes(); - canp.producePadEvent('select', this, painter, pos, place); + pathes.forEach((path, i) => select(nodes[i]).attr('d', path)); } - /** @summary Set fast drawing property depending on the size + /** @summary end of drag handler * @private */ - setFastDrawing(w, h) { - const was_fast = this._fast_drawing; - this._fast_drawing = settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this._fast_drawing) - this.showPadButtons(); + moveEnd(not_changed) { + if (not_changed) + return; + const box = this.getObject(), X = this.swap_xy ? 'Y' : 'X', Y = this.swap_xy ? 'X' : 'Y'; + let exec = ''; + if (this.c_x1) { + const v = this.svgToAxis('x', this.x1); + box[`f${X}1`] = v; + exec += `Set${X}1(${v});;`; + } + if (this.c_x2) { + const v = this.svgToAxis('x', this.x2); + box[`f${X}2`] = v; + exec += `Set${X}2(${v});;`; + } + if (this.c_y1) { + const v = this.svgToAxis('y', this.y1); + box[`f${Y}1`] = v; + exec += `Set${Y}1(${v});;`; + } + if (this.c_y2) { + const v = this.svgToAxis('y', this.y2); + box[`f${Y}2`] = v; + exec += `Set${Y}2(${v});;`; + } + this.submitCanvExec(exec + 'Notify();;'); } - /** @summary Returns true if canvas configured with grayscale - * @private */ - isGrayscale() { - return false; + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const box = this.getObject(), + minx = Math.min(box.fX1, box.fX2), + maxx = Math.max(box.fX1, box.fX2), + miny = Math.min(box.fY1, box.fY2), + maxy = Math.max(box.fY1, box.fY2); + return { minx, miny, maxx, maxy }; } - /** @summary Set grayscale mode for the canvas - * @private */ - setGrayscale(/* flag */) { - console.error('grayscale mode not implemented for RCanvas'); + /** @summary Create path */ + getPathes() { + const xx = Math.round(Math.min(this.x1, this.x2)), + yy = Math.round(Math.min(this.y1, this.y2)), + ww = Math.round(Math.abs(this.x2 - this.x1)), + hh = Math.round(Math.abs(this.y1 - this.y2)), + path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`; + if (!this.#border_mode) + return [path]; + return [path].concat(getBoxDecorations(xx, yy, ww, hh, this.#border_mode, this.#border_size, this.#border_size)); } - /** @summary Create SVG element for the canvas */ - createCanvasSvg(check_resize, new_size) { - const lmt = 5; - let factor = null, svg = null, rect = null, btns, frect; - - if (check_resize > 0) { - if (this._fixed_size) - return check_resize > 1; // flag used to force re-drawing of all subpads - - svg = this.getCanvSvg(); - if (svg.empty()) - return false; + /** @summary Redraw box */ + redraw() { + const box = this.getObject(), + d = new DrawOptions(this.getDrawOpt()), + fp = d.check('FRAME') ? this.getFramePainter() : null, + draw_line = d.check('L'); - factor = svg.property('height_factor'); + this.createAttLine({ attr: box }); + this.createAttFill({ attr: box }); - rect = this.testMainResize(check_resize, null, factor); + this.swap_xy = fp?.swap_xy(); - if (!rect.changed && (check_resize === 1)) - return false; + // if box filled, contour line drawn only with 'L' draw option: + if (!this.fillatt.empty() && !draw_line) + this.lineatt.color = 'none'; - if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + const g = this.createG(fp); - frect = svg.selectChild('.canvas_fillrect'); - } else { - const render_to = this.selectDom(); + this.x1 = this.axisToSvg('x', box.fX1); + this.x2 = this.axisToSvg('x', box.fX2); + this.y1 = this.axisToSvg('y', box.fY1); + this.y2 = this.axisToSvg('y', box.fY2); - if (render_to.style('position') === 'static') - render_to.style('position', 'relative'); + if (this.swap_xy) + [this.x1, this.x2, this.y1, this.y2] = [this.y1, this.y2, this.x1, this.x2]; - svg = render_to.append('svg') - .attr('class', 'jsroot root_canvas') - .property('pad_painter', this) // this is custom property - .property('current_pad', '') // this is custom property - .property('redraw_by_resize', false); // could be enabled to force redraw by each resize + this.#border_mode = (box.fBorderMode && this.fillatt.hasColor()) ? box.fBorderMode : 0; + this.#border_size = box.fBorderSize || 2; - this.setTopPainter(); // assign canvas as top painter of that element + const paths = this.getPathes(); - if (!this.isBatchMode() && !this.online_canvas) - svg.append('svg:title').text('ROOT canvas'); + g.append('svg:path') + .attr('d', paths[0]) + .call(this.lineatt.func) + .call(this.fillatt.func); - frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); - if (!this.isBatchMode()) { - frect.style('pointer-events', 'visibleFill') - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter(this, null)) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } + if (this.#border_mode) { + g.append('svg:path') + .attr('d', paths[1]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); - svg.append('svg:g').attr('class', 'primitives_layer'); - svg.append('svg:g').attr('class', 'info_layer'); - if (!this.isBatchMode()) { - btns = svg.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide === 'left') - .property('vertical', settings.ToolBarVert); - } + g.append('svg:path') + .attr('d', paths[2]) + .call(this.fillatt.func) + .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); + } - factor = 0.66; - if (this.pad && this.pad.fWinSize[0] && this.pad.fWinSize[1]) { - factor = this.pad.fWinSize[1] / this.pad.fWinSize[0]; - if ((factor < 0.1) || (factor > 10)) factor = 0.66; - } + assignContextMenu(this); - if (this._fixed_size) { - render_to.style('overflow', 'auto'); - rect = { width: this.pad.fWinSize[0], height: this.pad.fWinSize[1] }; - if (!rect.width || !rect.height) - rect = getElementRect(render_to); - } else - rect = this.testMainResize(2, new_size, factor); - } + addMoveHandler(this); - this.createAttFill({ pattern: 1001, color: 0 }); + return this; + } - if ((rect.width <= lmt) || (rect.height <= lmt)) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - rect.width = 200; rect.height = 100; // just to complete drawing - } else - svg.style('display', null); + /** @summary Draw TBox object */ + static async draw(dom, obj, opt) { + const painter = new TBoxPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } - if (this._fixed_size) { - svg.attr('x', 0) - .attr('y', 0) - .attr('width', rect.width) - .attr('height', rect.height) - .style('position', 'absolute'); - } else { - svg.attr('x', 0) - .attr('y', 0) - .style('width', '100%') - .style('height', '100%') - .style('position', 'absolute') - .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - } +} // class TBoxPainter - svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); +var TBoxPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TBoxPainter: TBoxPainter +}); - svg.attr('viewBox', `0 0 ${rect.width} ${rect.height}`) - .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio - .property('height_factor', factor) - .property('draw_x', 0) - .property('draw_y', 0) - .property('draw_width', rect.width) - .property('draw_height', rect.height); +/** + * @summary Painter for TASImage object. + * + * @private + */ - this._pad_x = 0; - this._pad_y = 0; - this._pad_width = rect.width; - this._pad_height = rect.height; +class TASImagePainter extends ObjectPainter { - frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) - .call(this.fillatt.func); + #contour; - this.setFastDrawing(rect.width, rect.height); + /** @summary Decode options string */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + obj = this.getObject(); - if (this.alignButtons && btns) - this.alignButtons(btns, rect.width, rect.height); + if (d.check('CONST') && obj) + obj.fConstRatio = true; - return true; + this.setOptions({ Zscale: d.check('Z') }); } - /** @summary Draw item name on canvas, dummy for RPad - * @private */ - drawItemNameOnCanvas() { - } + /** @summary Create RGBA buffers */ + createRGBA(nlevels) { + const obj = this.getObject(), + pal = obj?.fPalette; + if (!pal) + return null; - /** @summary Enlarge pad draw element when possible */ - enlargePad(evnt, is_dblclick, is_escape) { - evnt?.preventDefault(); - evnt?.stopPropagation(); + const rgba = new Array((nlevels + 1) * 4).fill(0); // precalculated colors - // ignore double click on canvas itself for enlarge - if (is_dblclick && this._websocket && (this.enlargeMain('state') === 'off')) - return; + for (let lvl = 0, indx = 1; lvl <= nlevels; ++lvl) { + const l = lvl / nlevels; + while ((pal.fPoints[indx] < l) && (indx < pal.fPoints.length - 1)) + indx++; - const svg_can = this.getCanvSvg(), - pad_enlarged = svg_can.property('pad_enlarged'); + const r1 = (pal.fPoints[indx] - l) / (pal.fPoints[indx] - pal.fPoints[indx - 1]), + r2 = (l - pal.fPoints[indx - 1]) / (pal.fPoints[indx] - pal.fPoints[indx - 1]); - if (this.iscan || !this.has_canvas || (!pad_enlarged && !this.hasObjectsToDraw() && !this.painters)) { - if (this._fixed_size) return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) return; - if (this.enlargeMain('state') === 'off') - svg_can.property('pad_enlarged', null); - else - selectActivePad({ pp: this, active: true }); - } else if (!pad_enlarged && !is_escape) { - this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.pad); - selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.pad) { - this.enlargeMain(false); - svg_can.property('pad_enlarged', null); - } else if (!is_escape && is_dblclick) - console.error('missmatch with pad double click events'); + rgba[lvl * 4] = Math.min(255, Math.round((pal.fColorRed[indx - 1] * r1 + pal.fColorRed[indx] * r2) / 256)); + rgba[lvl * 4 + 1] = Math.min(255, Math.round((pal.fColorGreen[indx - 1] * r1 + pal.fColorGreen[indx] * r2) / 256)); + rgba[lvl * 4 + 2] = Math.min(255, Math.round((pal.fColorBlue[indx - 1] * r1 + pal.fColorBlue[indx] * r2) / 256)); + rgba[lvl * 4 + 3] = Math.min(255, Math.round((pal.fColorAlpha[indx - 1] * r1 + pal.fColorAlpha[indx] * r2) / 256)); + } - return this.checkResize(true); + return rgba; } - /** @summary Create SVG element for the pad - * @return true when pad is displayed and all its items should be redrawn */ - createPadSvg(only_resize) { - if (!this.has_canvas) { - this.createCanvasSvg(only_resize ? 2 : 0); - return true; - } - - const svg_parent = this.getPadSvg(this.pad_name), // this.pad_name MUST be here to select parent pad - svg_can = this.getCanvSvg(), - width = svg_parent.property('draw_width'), - height = svg_parent.property('draw_height'), - pad_enlarged = svg_can.property('pad_enlarged'); - let pad_visible = true, - w = width, h = height, x = 0, y = 0, - svg_pad = null, svg_rect = null, btns = null; + /** @summary Cleanup painter + * @private */ + cleanup() { + this.#contour = undefined; + super.cleanup(); + } - if (this.pad?.fPos && this.pad?.fSize) { - x = Math.round(width * this.pad.fPos.fHoriz.fArr[0]); - y = Math.round(height * this.pad.fPos.fVert.fArr[0]); - w = Math.round(width * this.pad.fSize.fHoriz.fArr[0]); - h = Math.round(height * this.pad.fSize.fVert.fArr[0]); - } + /** @summary Return colors contour + * @private */ + getContour() { return this.#contour; } - if (pad_enlarged) { - pad_visible = false; - if (pad_enlarged === this.pad) - pad_visible = true; - else - this.forEachPainterInPad(pp => { if (pp.getObject() === pad_enlarged) pad_visible = true; }, 'pads'); + /** @summary Create url using image buffer + * @private */ + async makeUrlFromImageBuf(obj, fp) { + const nlevels = 1000; + this.rgba = this.createRGBA(nlevels); // precalculated colors - if (pad_visible) { w = width; h = height; x = y = 0; } + let min = obj.fImgBuf[0], max = obj.fImgBuf[0]; + for (let k = 1; k < obj.fImgBuf.length; ++k) { + const v = obj.fImgBuf[k]; + min = Math.min(v, min); + max = Math.max(v, max); } - if (only_resize) { - svg_pad = this.svg_this_pad(); - svg_rect = svg_pad.selectChild('.root_pad_border'); - if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); - this.addPadInteractive(true); - } else { - svg_pad = svg_parent.selectChild('.primitives_layer') - .append('svg:svg') // here was g before, svg used to blend all drawin outside - .classed('__root_pad_' + this.this_pad_name, true) - .attr('pad', this.this_pad_name) // set extra attribute to mark pad name - .property('pad_painter', this); // this is custom property + // does not work properly in Node.js, causes 'Maximum call stack size exceeded' error + // min = Math.min.apply(null, obj.fImgBuf), + // max = Math.max.apply(null, obj.fImgBuf); - if (!this.isBatchMode()) - svg_pad.append('svg:title').text('ROOT subpad'); + // create contour like in hist painter to allow palette drawing + this.#contour = { + arr: new Array(200), + rgba: this.rgba, + getLevels() { return this.arr; }, + getPaletteColor(pal, zval) { + if (!this.arr || !this.rgba) + return 'white'; + const indx = Math.round((zval - this.arr[0]) / (this.arr.at(-1) - this.arr.at(0)) * (this.rgba.length - 4) / 4) * 4; + return toColor(this.rgba[indx] / 255, this.rgba[indx + 1] / 255, this.rgba[indx + 2] / 255, this.rgba[indx + 3] / 255); + } + }; + for (let k = 0; k < 200; k++) + this.#contour.arr[k] = min + (max - min) / (200 - 1) * k; - svg_rect = svg_pad.append('svg:path').attr('class', 'root_pad_border'); + if (min >= max) + max = min + 1; - svg_pad.append('svg:g').attr('class', 'primitives_layer'); - if (!this.isBatchMode()) { - btns = svg_pad.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide !== 'left') - .property('vertical', settings.ToolBarVert); - } + const z = this.getImageZoomRange(fp, obj.fConstRatio, obj.fWidth, obj.fHeight), + pr = isNodeJs() + ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h => h.default.createCanvas(z.xmax - z.xmin, z.ymax - z.ymin)) + : new Promise(resolveFunc => { + const c = document.createElement('canvas'); + c.width = z.xmax - z.xmin; + c.height = z.ymax - z.ymin; + resolveFunc(c); + }); - if (settings.ContextMenu) - svg_rect.on('contextmenu', evnt => this.padContextMenu(evnt)); + return pr.then(canvas => { + const context = canvas.getContext('2d'), + imageData = context.getImageData(0, 0, canvas.width, canvas.height), + arr = imageData.data; - if (!this.isBatchMode()) { - svg_rect.style('pointer-events', 'visibleFill') // get events also for not visible rect - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter(this, null)) - .on('mouseenter', () => this.showObjectStatus()); + for (let i = z.ymin; i < z.ymax; ++i) { + let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4; + const row = i * obj.fWidth; + for (let j = z.xmin; j < z.xmax; ++j) { + let iii = Math.round((obj.fImgBuf[row + j] - min) / (max - min) * nlevels) * 4; + // copy rgba value for specified point + arr[dst++] = this.rgba[iii++]; + arr[dst++] = this.rgba[iii++]; + arr[dst++] = this.rgba[iii++]; + arr[dst++] = this.rgba[iii]; + } } - } - this.createAttFill({ attr: this.pad }); - - this.createAttLine({ attr: this.pad, color0: this.pad.fBorderMode === 0 ? 'none' : '' }); + context.putImageData(imageData, 0, 0); - svg_pad.style('display', pad_visible ? null : 'none') - .attr('viewBox', `0 0 ${w} ${h}`) // due to svg - .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio - .attr('x', x) // due to svg - .attr('y', y) // due to svg - .attr('width', w) // due to svg - .attr('height', h) // due to svg - .property('draw_x', x) // this is to make similar with canvas - .property('draw_y', y) - .property('draw_width', w) - .property('draw_height', h); + return { url: canvas.toDataURL(), constRatio: obj.fConstRatio, can_zoom: true }; + }); + } - this._pad_x = x; - this._pad_y = y; - this._pad_width = w; - this._pad_height = h; + getImageZoomRange(fp, constRatio, width, height) { + const res = { xmin: 0, xmax: width, ymin: 0, ymax: height }; + if (!fp) + return res; - svg_rect.attr('d', `M0,0H${w}V${h}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); + let offx = 0, offy = 0, sizex = width, sizey = height; - this.setFastDrawing(w, h); + if (constRatio) { + const image_ratio = height / width, + frame_ratio = fp.getFrameHeight() / fp.getFrameWidth(); - // special case of 3D canvas overlay - if (svg_pad.property('can3d') === constants$1.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.this_pad_name) - .style('display', pad_visible ? '' : 'none'); + if (image_ratio > frame_ratio) { + const w2 = height / frame_ratio; + offx = Math.round((w2 - width) / 2); + sizex = Math.round(w2); + } else { + const h2 = frame_ratio * width; + offy = Math.round((h2 - height) / 2); + sizey = Math.round(h2); + } } - if (this.alignButtons && btns) this.alignButtons(btns, w, h); - - return pad_visible; - } - - /** @summary Add pad interactive features like dragging and resize - * @private */ - addPadInteractive(cleanup = false) { - if (isFunc(this.$userInteractive)) { - this.$userInteractive(); - delete this.$userInteractive; + if (fp.zoom_xmin !== fp.zoom_xmax) { + res.xmin = Math.min(width, Math.max(0, Math.round(fp.zoom_xmin * sizex) - offx)); + res.xmax = Math.min(width, Math.max(0, Math.round(fp.zoom_xmax * sizex) - offx)); } - // if (this.isBatchMode()) - // return; - } - - /** @summary returns true if any objects beside sub-pads exists in the pad */ - hasObjectsToDraw() { - return this.pad?.fPrimitives?.find(obj => obj._typename !== `${nsREX}RPadDisplayItem`); - } - - /** @summary sync drawing/redrawing/resize of the pad - * @param {string} kind - kind of draw operation, if true - always queued - * @return {Promise} when pad is ready for draw operation or false if operation already queued - * @private */ - syncDraw(kind) { - const entry = { kind: kind || 'redraw' }; - if (this._doing_draw === undefined) { - this._doing_draw = [entry]; - return Promise.resolve(true); + if (fp.zoom_ymin !== fp.zoom_ymax) { + res.ymin = Math.min(height, Math.max(0, Math.round(fp.zoom_ymin * sizey) - offy)); + res.ymax = Math.min(height, Math.max(0, Math.round(fp.zoom_ymax * sizey) - offy)); } - // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this._doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) - return false; - this._doing_draw.push(entry); - return new Promise(resolveFunc => { - entry.func = resolveFunc; - }); + return res; } - /** @summary confirms that drawing is completed, may trigger next drawing immediately - * @private */ - confirmDraw() { - if (this._doing_draw === undefined) - return console.warn('failure, should not happen'); - this._doing_draw.shift(); - if (this._doing_draw.length === 0) - delete this._doing_draw; + /** @summary Produce data url from png buffer */ + async makeUrlFromPngBuf(obj, fp) { + const buf = obj.fPngBuf; + let pngbuf = ''; + + if (isStr(buf)) + pngbuf = buf; else { - const entry = this._doing_draw[0]; - if (entry.func) { entry.func(); delete entry.func; } + for (let k = 0; k < buf.length; ++k) + pngbuf += String.fromCharCode(buf[k] < 0 ? 256 + buf[k] : buf[k]); } - } - /** @summary Draw single primitive */ - async drawObject(/* dom, obj, opt */) { - console.log('Not possible to draw object without loading of draw.mjs'); - return null; - } + const res = { url: 'data:image/png;base64,' + btoa_func(pngbuf), constRatio: obj.fConstRatio, can_zoom: fp && !isNodeJs() }, + doc = getDocument(); - /** @summary Draw pad primitives - * @private */ - async drawPrimitives(indx) { - if (indx === undefined) { - if (this.iscan) - this._start_tm = new Date().getTime(); + if (!res.can_zoom || ((fp?.zoom_xmin === fp?.zoom_xmax) && (fp?.zoom_ymin === fp?.zoom_ymax))) + return res; - // set number of primitves - this._num_primitives = this.pad?.fPrimitives?.length ?? 0; + return new Promise(resolveFunc => { + const image = doc.createElement('img'); - return this.syncDraw(true).then(() => this.drawPrimitives(0)); - } + image.onload = () => { + const canvas = doc.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; - if (!this.pad || (indx >= this._num_primitives)) { - this.confirmDraw(); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); - if (this._start_tm) { - const spenttm = new Date().getTime() - this._start_tm; - if (spenttm > 3000) console.log(`Canvas drawing took ${(spenttm*1e-3).toFixed(2)}s`); - delete this._start_tm; - } + const arr = context.getImageData(0, 0, image.width, image.height).data, + z = this.getImageZoomRange(fp, res.constRatio, image.width, image.height), + canvas2 = doc.createElement('canvas'); + canvas2.width = z.xmax - z.xmin; + canvas2.height = z.ymax - z.ymin; - return; - } + const context2 = canvas2.getContext('2d'), + imageData2 = context2.getImageData(0, 0, canvas2.width, canvas2.height), + arr2 = imageData2.data; - // handle used to invoke callback only when necessary - return this.drawObject(this.getDom(), this.pad.fPrimitives[indx], '').then(op => { - // mark painter as belonging to primitives - if (isObject(op)) - op._primitive = true; + for (let i = z.ymin; i < z.ymax; ++i) { + let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4, + src = ((image.height - i - 1) * image.width + z.xmin) * 4; + for (let j = z.xmin; j < z.xmax; ++j) { + // copy rgba value for specified point + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + } + } - return this.drawPrimitives(indx+1); - }); - } + context2.putImageData(imageData2, 0, 0); - /** @summary Process tooltip event in the pad - * @private */ - processPadTooltipEvent(pnt) { - const painters = [], hints = []; + res.url = canvas2.toDataURL(); - // first count - how many processors are there - this.painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) painters.push(obj); - }); + resolveFunc(res); + }; - if (pnt) pnt.nproc = painters.length; + image.onerror = () => resolveFunc(res); - painters.forEach(obj => { - const hint = obj.processTooltipEvent(pnt) || { user_info: null }; - hints.push(hint); - if (pnt?.painters) hint.painter = obj; + image.src = res.url; }); - - return hints; } - /** @summary Changes canvas dark mode - * @private */ - changeDarkMode(mode) { - this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); - } - - /** @summary Fill pad context menu - * @private */ - fillContextMenu(menu) { - if (this.iscan) - menu.add('header: RCanvas'); - else - menu.add('header: RPad'); + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + /** @summary Draw image */ + async drawImage() { + const obj = this.getObject(), + fp = this.getFramePainter(), + rect = fp?.getFrameRect() ?? this.getPadPainter().getPadRect(); - if (!this._websocket) { - menu.addAttributesMenu(this); - if (this.iscan) { - menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') this.changeDarkMode(); - }); - } - } + if (obj._blob) { + // try to process blob data due to custom streamer + if ((obj._blob.length === 15) && !obj._blob[0]) { + obj.fImageQuality = obj._blob[1]; + obj.fImageCompression = obj._blob[2]; + obj.fConstRatio = obj._blob[3]; + obj.fPalette = { + _typename: clTImagePalette, + fUniqueID: obj._blob[4], + fBits: obj._blob[5], + fNumPoints: obj._blob[6], + fPoints: obj._blob[7], + fColorRed: obj._blob[8], + fColorGreen: obj._blob[9], + fColorBlue: obj._blob[10], + fColorAlpha: obj._blob[11] + }; - menu.add('separator'); + obj.fWidth = obj._blob[12]; + obj.fHeight = obj._blob[13]; + obj.fImgBuf = obj._blob[14]; - if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) - menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); + if ((obj.fWidth * obj.fHeight !== obj.fImgBuf.length) || + (obj.fPalette.fNumPoints !== obj.fPalette.fPoints.length)) { + console.error(`TASImage _blob decoding error ${obj.fWidth * obj.fHeight} != ${obj.fImgBuf.length} ${obj.fPalette.fNumPoints} != ${obj.fPalette.fPoints.length}`); + delete obj.fImgBuf; + delete obj.fPalette; + } + } else if ((obj._blob.length === 3) && obj._blob[0]) { + obj.fPngBuf = obj._blob[2]; + if (obj.fPngBuf?.length !== obj._blob[1]) { + console.error(`TASImage with png buffer _blob error ${obj._blob[1]} != ${obj.fPngBuf?.length}`); + delete obj.fPngBuf; + } + } else + console.error(`TASImage _blob len ${obj._blob.length} not recognized`); - if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { - if (this.canStatusBar()) - menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); + delete obj._blob; } - if (this.enlargeMain() || (this.has_canvas && this.hasObjectsToDraw())) - menu.addchk((this.enlargeMain('state') === 'on'), 'Enlarge ' + (this.iscan ? 'canvas' : 'pad'), () => this.enlargePad()); - - const fname = this.this_pad_name || (this.iscan ? 'canvas' : 'pad'); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.iscan, `${fname}.${fmt}`))); - menu.add('endsub:'); - - return true; - } - - /** @summary Show pad context menu - * @private */ - padContextMenu(evnt) { - if (evnt.stopPropagation) { - // this is normal event processing and not emulated jsroot event - - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu + let promise; - this.getFramePainter()?.setLastEventPos(); - } + if (obj.fImgBuf && obj.fPalette) + promise = this.makeUrlFromImageBuf(obj, fp); + else if (obj.fPngBuf) + promise = this.makeUrlFromPngBuf(obj, fp); + else + promise = Promise.resolve(null); - createMenu(evnt, this).then(menu => { - this.fillContextMenu(menu); - return this.fillObjectExecMenu(menu); - }).then(menu => menu.show()); - } + return promise.then(res => { + if (!res?.url) + return this; - /** @summary Redraw pad means redraw ourself - * @return {Promise} when redrawing ready */ - async redrawPad(reason) { - const sync_promise = this.syncDraw(reason); - if (sync_promise === false) { - console.log('Prevent RPad redrawing'); - return false; - } + const img = this.createG(fp) + .append('image') + .attr('href', res.url) + .attr('width', rect.width) + .attr('height', rect.height) + .attr('preserveAspectRatio', res.constRatio ? null : 'none'); - let showsubitems = true; - const redrawNext = indx => { - while (indx < this.painters.length) { - const sub = this.painters[indx++]; - let res = 0; - if (showsubitems || sub.this_pad_name) - res = sub.redraw(reason); + if (!this.isBatchMode()) { + if (settings.MoveResize || settings.ContextMenu) + img.style('pointer-events', 'visibleFill'); - if (isPromise(res)) - return res.then(() => redrawNext(indx)); + if (res.can_zoom) + img.style('cursor', 'pointer'); } - return true; - }; - return sync_promise.then(() => { - if (this.iscan) - this.createCanvasSvg(2); - else - showsubitems = this.createPadSvg(true); + assignContextMenu(this, kNoReorder); - return redrawNext(0); - }).then(() => { - this.addPadInteractive(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - this.confirmDraw(); - return true; + if (!fp || !res.can_zoom) + return this; + + return this.drawColorPalette(this.getOptions().Zscale, true).then(() => { + fp.setAxesRanges(create$1(clTAxis), 0, 1, create$1(clTAxis), 0, 1, null, 0, 0); + fp.createXY({ ndim: 2, check_pad_range: false }); + return fp.addInteractivity(); + }); }); } - /** @summary redraw pad */ - redraw(reason) { - return this.redrawPad(reason); + /** @summary Fill TASImage context menu */ + fillContextMenuItems(menu) { + const obj = this.getObject(), o = this.getOptions(); + if (obj) { + menu.addchk(obj.fConstRatio, 'Const ratio', flag => { + obj.fConstRatio = flag; + this.interactiveRedraw('pad', `exec:SetConstRatio(${flag})`); + }, 'Change const ratio flag of image'); + } + if (obj?.fPalette) { + menu.addchk(o.Zscale, 'Color palette', flag => { + o.Zscale = flag; + this.drawColorPalette(flag, true); + }, 'Toggle color palette'); + } } + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + const obj = this.getObject(); - /** @summary Checks if pad should be redrawn by resize - * @private */ - needRedrawByResize() { - const elem = this.svg_this_pad(); - if (!elem.empty() && elem.property('can3d') === constants$1.Embed3D.Overlay) return true; + if (!obj) + return false; - for (let i = 0; i < this.painters.length; ++i) { - if (isFunc(this.painters[i].needRedrawByResize)) - if (this.painters[i].needRedrawByResize()) return true; - } + if (((axis === 'x') || (axis === 'y')) && (max - min > 0.01)) + return true; return false; } - /** @summary Check resize of canvas */ - checkCanvasResize(size, force) { - if (this._ignore_resize) - return false; - - if (!this.iscan && this.has_canvas) return false; - - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) return false; + /** @summary Return palette - dummy here + * @private */ + getHistPalette() { return true; } - if ((size === true) || (size === false)) { force = size; size = null; } + /** @summary Draw color palette + * @private */ + async drawColorPalette(enabled, can_move) { + if (!this.isMainPainter()) + return null; - if (isObject(size) && size.force) force = true; + if (!this.draw_palette) { + const pal = create$1(clTPaletteAxis); + Object.assign(pal, { fX1NDC: 0.91, fX2NDC: 0.95, fY1NDC: 0.1, fY2NDC: 0.9, fInit: 1 }); + pal.fAxis.fChopt = '+'; + this.draw_palette = pal; + } - if (!force) force = this.needRedrawByResize(); + let pal_painter = this.getPadPainter().findPainterFor(this.draw_palette); - let changed = false; - const redrawNext = indx => { - if (!changed || (indx >= this.painters.length)) { - this.confirmDraw(); - return changed; + if (!enabled) { + if (pal_painter) { + pal_painter.Enabled = false; + pal_painter.removeG(); // completely remove drawing without need to redraw complete pad } + return null; + } - return getPromise(this.painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx+1)); - }; + const fp = this.getFramePainter(); + // keep palette width + if (can_move && fp) { + const pal = this.draw_palette; + pal.fX2NDC = fp.fX2NDC + 0.01 + (pal.fX2NDC - pal.fX1NDC); + pal.fX1NDC = fp.fX2NDC + 0.01; + pal.fY1NDC = fp.fY1NDC; + pal.fY2NDC = fp.fY2NDC; + } - return sync_promise.then(() => { - changed = this.createCanvasSvg(force ? 2 : 1, size); + if (pal_painter) { + pal_painter.Enabled = true; + return pal_painter.drawPave(''); + } - if (changed && this.iscan && this.pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this._resize_tmout) - clearTimeout(this._resize_tmout); - this._resize_tmout = setTimeout(() => { - delete this._resize_tmout; - if (!this.pad?.fWinSize) return; - const cw = this.getPadWidth(), ch = this.getPadHeight(); - if ((cw > 0) && (ch > 0) && ((this.pad.fWinSize[0] !== cw) || (this.pad.fWinSize[1] !== ch))) { - this.pad.fWinSize[0] = cw; - this.pad.fWinSize[1] = ch; - this.sendWebsocket(`RESIZED:[${cw},${ch}]`); - } - }, 1000); // long enough delay to prevent multiple occurence - } + return TPavePainter.draw(this.getPadPainter(), this.draw_palette).then(p => { + pal_painter = p; - // if canvas changed, redraw all its subitems. - // If redrawing was forced for canvas, same applied for sub-elements - return redrawNext(0); + // mark painter as secondary - not in list of TCanvas primitives + pal_painter.setSecondaryId(this); + + // make dummy redraw, palette will be updated only from histogram painter + pal_painter.redraw = function() {}; }); } - /** @summary update RPad object + /** @summary Toggle colz draw option * @private */ - updateObject(obj) { - if (!obj) return false; - - this.pad.fStyle = obj.fStyle; - this.pad.fAttr = obj.fAttr; - - if (this.iscan) { - this.pad.fTitle = obj.fTitle; - this.pad.fWinSize = obj.fWinSize; - } else { - this.pad.fPos = obj.fPos; - this.pad.fSize = obj.fSize; + toggleColz() { + if (this.getObject()?.fPalette) { + const o = this.getOptions(); + o.Zscale = !o.Zscale; + return this.drawColorPalette(o.Zscale, true); } + } - return true; + /** @summary Redraw image */ + redraw() { + return this.drawImage(); } + /** @summary Process click on TASImage-defined buttons + * @desc may return promise or simply false */ + clickButton(funcname) { + if (this.isMainPainter() && funcname === 'ToggleColorZ') + return this.toggleColz(); + + return false; + } - /** @summary Add object painter to list of primitives - * @private */ - addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && (objpainter.snapid === undefined)) { - // keep snap id in painter, will be used for the - if (this.painters.indexOf(objpainter) < 0) - this.painters.push(objpainter); - objpainter.assignSnapId(lst[indx].fObjectID); - if (!objpainter.rstyle) objpainter.rstyle = lst[indx].fStyle || this.rstyle; + /** @summary Fill pad toolbar for TASImage */ + fillToolbar() { + const pp = this.getPadPainter(); + if (pp && this.getObject()?.fPalette) { + pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); + pp.showPadButtons(); } } - /** @summary Extract properties from TObjectDisplayItem */ - extractTObjectProp(snap) { - if (snap.fColIndex && snap.fColValue) { - const colors = this.root_colors || getRootColors(); - for (let k = 0; k < snap.fColIndex.length; ++k) - colors[snap.fColIndex[k]] = snap.fColValue[k]; - } + /** @summary Draw TASImage object */ + static async draw(dom, obj, opt) { + const painter = new TASImagePainter(dom, obj, opt); + painter.setAsMainPainter(); + painter.decodeOptions(opt); + return ensureTCanvas(painter, false) + .then(() => painter.drawImage()) + .then(() => { + painter.fillToolbar(); + return painter; + }); + } - // painter used only for evaluation of attributes - const pattr = new RObjectPainter(), obj = snap.fObject; - pattr.assignObject(snap); - pattr.csstype = snap.fCssType; - pattr.rstyle = snap.fStyle; +} // class TASImagePainter - snap.fOption = pattr.v7EvalAttr('options', ''); +var TASImagePainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +TASImagePainter: TASImagePainter +}); - const extract_color = (member_name, attr_name) => { - const col = pattr.v7EvalColor(attr_name, ''); - if (col) obj[member_name] = addColor(col, this.root_colors); - }; +const LITTLE_ENDIAN = true; +class RBufferReader { + + constructor(buffer) { + if (buffer instanceof ArrayBuffer) { + this.buffer = buffer; + this.byteOffset = 0; + this.byteLength = buffer.byteLength; + } else if (ArrayBuffer.isView(buffer)) { + this.buffer = buffer.buffer; + this.byteOffset = buffer.byteOffset; + this.byteLength = buffer.byteLength; + } else + throw new TypeError('Invalid buffer type'); - // handle TAttLine - if ((obj.fLineColor !== undefined) && (obj.fLineWidth !== undefined) && (obj.fLineStyle !== undefined)) { - extract_color('fLineColor', 'line_color'); - obj.fLineWidth = pattr.v7EvalAttr('line_width', obj.fLineWidth); - obj.fLineStyle = pattr.v7EvalAttr('line_style', obj.fLineStyle); - } + this.view = new DataView(this.buffer); + // important - offset should start from actual place in the buffer + this.offset = this.byteOffset; + } - // handle TAttFill - if ((obj.fFillColor !== undefined) && (obj.fFillStyle !== undefined)) { - extract_color('fFillColor', 'fill_color'); - obj.fFillStyle = pattr.v7EvalAttr('fill_style', obj.fFillStyle); - } + // Move to a specific position in the buffer + seek(position) { + if (typeof position === 'bigint') { + if (position > BigInt(Number.MAX_SAFE_INTEGER)) + throw new Error(`Offset too large to seek safely: ${position}`); + this.offset = Number(position); + } else + this.offset = position; + } - // handle TAttMarker - if ((obj.fMarkerColor !== undefined) && (obj.fMarkerStyle !== undefined) && (obj.fMarkerSize !== undefined)) { - extract_color('fMarkerColor', 'marker_color'); - obj.fMarkerStyle = pattr.v7EvalAttr('marker_style', obj.fMarkerStyle); - obj.fMarkerSize = pattr.v7EvalAttr('marker_size', obj.fMarkerSize); - } - // handle TAttText - if ((obj.fTextColor !== undefined) && (obj.fTextAlign !== undefined) && (obj.fTextAngle !== undefined) && (obj.fTextSize !== undefined)) { - extract_color('fTextColor', 'text_color'); - obj.fTextAlign = pattr.v7EvalAttr('text_align', obj.fTextAlign); - obj.fTextAngle = pattr.v7EvalAttr('text_angle', obj.fTextAngle); - obj.fTextSize = pattr.v7EvalAttr('text_size', obj.fTextSize); - // TODO: v7 font handling differs much from v6, ignore for the moment - } + // Read unsigned 8-bit integer (1 BYTE) + readU8() { + const val = this.view.getUint8(this.offset); + this.offset += 1; + return val; } - /** @summary Function called when drawing next snapshot from the list - * @return {Promise} with pad painter when ready - * @private */ - async drawNextSnap(lst, indx) { - if (indx === undefined) { - indx = -1; - // flag used to prevent immediate pad redraw during first draw - this._snaps_map = {}; // to control how much snaps are drawn - this._num_primitives = lst ? lst.length : 0; - this._auto_color_cnt = 0; - } + // Read unsigned 16-bit integer (2 BYTES) + readU16() { + const val = this.view.getUint16(this.offset, LITTLE_ENDIAN); + this.offset += 2; + return val; + } - delete this.next_rstyle; + // Read unsigned 32-bit integer (4 BYTES) + readU32() { + const val = this.view.getUint32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } - ++indx; // change to the next snap + // Read signed 8-bit integer (1 BYTE) + readS8() { + const val = this.view.getInt8(this.offset); + this.offset += 1; + return val; + } - if (!lst || indx >= lst.length) { - delete this._snaps_map; - delete this._auto_color_cnt; - return this; - } + // Read signed 16-bit integer (2 BYTES) + readS16() { + const val = this.view.getInt16(this.offset, LITTLE_ENDIAN); + this.offset += 2; + return val; + } + + // Read signed 32-bit integer (4 BYTES) + readS32() { + const val = this.view.getInt32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } + + // Read 32-bit float (4 BYTES) + readF32() { + const val = this.view.getFloat32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } - const snap = lst[indx], - snapid = snap.fObjectID; - let cnt = this._snaps_map[snapid], - objpainter = null; + // Read 64-bit float (8 BYTES) + readF64() { + const val = this.view.getFloat64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } - if (cnt) cnt++; else cnt=1; - this._snaps_map[snapid] = cnt; // check how many objects with same snapid drawn, use them again + // Read a string with 32-bit length prefix + readString() { + const length = this.readU32(); + let str = ''; + for (let i = 0; i < length; i++) + str += String.fromCharCode(this.readU8()); + return str; + } - // empty object, no need to do something, take next - if (snap.fDummy) return this.drawNextSnap(lst, indx); + // Read unsigned 64-bit integer (8 BYTES) + readU64() { + const val = this.view.getBigUint64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } - // first appropriate painter for the object - // if same object drawn twice, two painters will exists - for (let k = 0; k < this.painters.length; ++k) { - if (this.painters[k].snapid === snapid) - if (--cnt === 0) { objpainter = this.painters[k]; break; } - } + // Read signed 64-bit integer (8 BYTES) + readS64() { + const val = this.view.getBigInt64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } - if (objpainter) { - if (snap._typename === `${nsREX}RPadDisplayItem`) { - // subpad - return objpainter.redrawPadSnap(snap).then(ppainter => { - this.addObjectPainter(ppainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); - } +} - if (snap._typename === `${nsREX}TObjectDisplayItem`) - this.extractTObjectProp(snap); +const ENTupleColumnType = { + kBit: 0x00, + kByte: 0x01, + kChar: 0x02, + kInt8: 0x03, + kUInt8: 0x04, + kInt16: 0x05, + kUInt16: 0x06, + kInt32: 0x07, + kUInt32: 0x08, + kInt64: 0x09, + kUInt64: 0x0A, + kReal16: 0x0B, + kReal32: 0x0C, + kReal64: 0x0D, + kIndex32: 0x0E, + kIndex64: 0x0F, + kSplitInt16: 0x11, + kSplitUInt16: 0x12, + kSplitInt32: 0x13, + kSplitUInt32: 0x14, + kSplitInt64: 0x15, + kSplitUInt64: 0x16, + kSplitReal16: 0x17, + kSplitReal32: 0x18, + kSplitReal64: 0x19, + kSplitIndex32: 0x1A, + kSplitIndex64: 0x1B}; - let promise; - if (objpainter.updateObject(snap.fDrawable || snap.fObject || snap, snap.fOption || '', true)) - promise = objpainter.redraw(); +/** + * @summary Rearrange bytes from split format to normal format (row-wise) for decoding + */ +function recontructUnsplitBuffer(blob, columnDescriptor) { + const { coltype } = columnDescriptor; + + if ( + coltype === ENTupleColumnType.kSplitUInt16 || + coltype === ENTupleColumnType.kSplitUInt32 || + coltype === ENTupleColumnType.kSplitUInt64 || + coltype === ENTupleColumnType.kSplitReal16 || + coltype === ENTupleColumnType.kSplitReal32 || + coltype === ENTupleColumnType.kSplitReal64 || + coltype === ENTupleColumnType.kSplitIndex32 || + coltype === ENTupleColumnType.kSplitIndex64 || + coltype === ENTupleColumnType.kSplitInt16 || + coltype === ENTupleColumnType.kSplitInt32 || + coltype === ENTupleColumnType.kSplitInt64 + ) { + // Determine byte size based on column type + let byteSize; + switch (coltype) { + case ENTupleColumnType.kSplitReal64: + case ENTupleColumnType.kSplitInt64: + case ENTupleColumnType.kSplitUInt64: + case ENTupleColumnType.kSplitIndex64: + byteSize = 8; + break; + case ENTupleColumnType.kSplitReal32: + case ENTupleColumnType.kSplitInt32: + case ENTupleColumnType.kSplitIndex32: + case ENTupleColumnType.kSplitUInt32: + byteSize = 4; + break; + case ENTupleColumnType.kSplitInt16: + case ENTupleColumnType.kSplitUInt16: + case ENTupleColumnType.kSplitReal16: + byteSize = 2; + break; + default: + throw new Error(`Unsupported split coltype: ${coltype} (0x${coltype.toString(16).padStart(2, '0')})`); + } - return getPromise(promise).then(() => this.drawNextSnap(lst, indx)); // call next + const splitView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), + count = blob.byteLength / byteSize, + outBuffer = new ArrayBuffer(blob.byteLength), + outBytes = new Uint8Array(outBuffer); + + for (let i = 0; i < count; ++i) { + for (let b = 0; b < byteSize; ++b) { + const splitIndex = b * count + i, + byte = splitView.getUint8(splitIndex), + writeIndex = i * byteSize + b; + outBytes[writeIndex] = byte; + } } - if (snap._typename === `${nsREX}RPadDisplayItem`) { // subpad - const subpad = snap, // not subpad, but just attributes + // Return updated blob and remapped coltype + const newBlob = outBuffer; + let newColtype; + switch (coltype) { + case ENTupleColumnType.kSplitUInt16: + newColtype = ENTupleColumnType.kUInt16; + break; + case ENTupleColumnType.kSplitUInt32: + newColtype = ENTupleColumnType.kUInt32; + break; + case ENTupleColumnType.kSplitUInt64: + newColtype = ENTupleColumnType.kUInt64; + break; + case ENTupleColumnType.kSplitIndex32: + newColtype = ENTupleColumnType.kIndex32; + break; + case ENTupleColumnType.kSplitIndex64: + newColtype = ENTupleColumnType.kIndex64; + break; + case ENTupleColumnType.kSplitReal16: + newColtype = ENTupleColumnType.kReal16; + break; + case ENTupleColumnType.kSplitReal32: + newColtype = ENTupleColumnType.kReal32; + break; + case ENTupleColumnType.kSplitReal64: + newColtype = ENTupleColumnType.kReal64; + break; + case ENTupleColumnType.kSplitInt16: + newColtype = ENTupleColumnType.kInt16; + break; + case ENTupleColumnType.kSplitInt32: + newColtype = ENTupleColumnType.kInt32; + break; + case ENTupleColumnType.kSplitInt64: + newColtype = ENTupleColumnType.kInt64; + break; + default: + throw new Error(`Unsupported split coltype for reassembly: ${coltype}`); + } - padpainter = new RPadPainter(this.getDom(), subpad, false); - padpainter.decodeOptions(''); - padpainter.addToPadPrimitives(this.this_pad_name); // only set parent pad name - padpainter.assignSnapId(snap.fObjectID); - padpainter.rstyle = snap.fStyle; + return { blob: newBlob, coltype: newColtype }; + } - padpainter.createPadSvg(); + // If no split type, return original blob and coltype + return { blob, coltype }; +} - if (snap.fPrimitives && snap.fPrimitives.length > 0) - padpainter.addPadButtons(); - // we select current pad, where all drawing is performed - const prev_name = padpainter.selectCurrentPad(padpainter.this_pad_name); +/** + * @summary Decode a reconstructed index buffer (32- or 64-bit deltas to absolute indices) + */ +function DecodeDeltaIndex(blob, coltype) { + let deltas, result; + + if (coltype === ENTupleColumnType.kIndex32) { + deltas = new Int32Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 4); + result = new Int32Array(deltas.length); + } else if (coltype === ENTupleColumnType.kIndex64) { + deltas = new BigInt64Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 8); + result = new BigInt64Array(deltas.length); + } else + throw new Error(`DecodeDeltaIndex: unsupported column type ${coltype}`); - return padpainter.drawNextSnap(snap.fPrimitives).then(() => { - padpainter.addPadInteractive(); - padpainter.selectCurrentPad(prev_name); - return this.drawNextSnap(lst, indx); - }); - } + if (deltas.length > 0) + result[0] = deltas[0]; + for (let i = 1; i < deltas.length; ++i) + result[i] = result[i - 1] + deltas[i]; - // will be used in addToPadPrimitives to assign style to sub-painters - this.next_rstyle = lst[indx].fStyle || this.rstyle; + return { blob: result, coltype }; +} - if (snap._typename === `${nsREX}TObjectDisplayItem`) { - // identifier used in RObjectDrawable - const webSnapIds = { kNone: 0, kObject: 1, kColors: 4, kStyle: 5, kPalette: 6 }; +/** + * @summary Decode a reconstructed signed integer buffer using ZigZag encoding + */ +function decodeZigzag(blob, coltype) { + let zigzag, result; + + if (coltype === ENTupleColumnType.kInt16) { + zigzag = new Uint16Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 2); + result = new Int16Array(zigzag.length); + } else if (coltype === ENTupleColumnType.kInt32) { + zigzag = new Uint32Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 4); + result = new Int32Array(zigzag.length); + } else if (coltype === ENTupleColumnType.kInt64) { + zigzag = new BigUint64Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 8); + result = new BigInt64Array(zigzag.length); + } else + throw new Error(`decodeZigzag: unsupported column type ${coltype}`); - if (snap.fKind === webSnapIds.kStyle) { - Object.assign(gStyle, snap.fObject); - return this.drawNextSnap(lst, indx); - } + for (let i = 0; i < zigzag.length; ++i) { + // ZigZag decode: (x >>> 1) ^ (-(x & 1)) + const x = zigzag[i]; + result[i] = (x >>> 1) ^ (-(x & 1)); + } - if (snap.fKind === webSnapIds.kColors) { - const ListOfColors = [], arr = snap.fObject.arr; - for (let n = 0; n < arr.length; ++n) { - const name = arr[n].fString, p = name.indexOf('='); - if (p > 0) - ListOfColors[parseInt(name.slice(0, p))] = name.slice(p+1); - } + return { blob: result, coltype }; +} - this.root_colors = ListOfColors; - // set global list of colors - // adoptRootColors(ListOfColors); - return this.drawNextSnap(lst, indx); - } +// Envelope Types +// TODO: Define usage logic for envelope types in future +// const kEnvelopeTypeHeader = 0x01, +// kEnvelopeTypeFooter = 0x02, +// kEnvelopeTypePageList = 0x03, - if (snap.fKind === webSnapIds.kPalette) { - const arr = snap.fObject.arr, palette = []; - for (let n = 0; n < arr.length; ++n) - palette[n] = arr[n].fString; - this.custom_palette = new ColorPalette(palette); - return this.drawNextSnap(lst, indx); - } +// Field Flags +const kFlagRepetitiveField = 0x01, + kFlagProjectedField = 0x02, + kFlagHasTypeChecksum = 0x04, - if (!this.getFramePainter()) { - return this.drawObject(this.getDom(), { _typename: clTFrame, $dummy: true }, '') - .then(() => this.drawNextSnap(lst, indx-1)); - } // call same object again + // Column Flags + kFlagDeferredColumn = 0x01, + kFlagHasValueRange = 0x02; - this.extractTObjectProp(snap); - } +class RNTupleDescriptorBuilder { - // TODO - fDrawable is v7, fObject from v6, maybe use same data member? - return this.drawObject(this.getDom(), snap.fDrawable || snap.fObject || snap, snap.fOption || '').then(objpainter => { - this.addObjectPainter(objpainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); - } + deserializeHeader(header_blob) { + if (!header_blob) + return; - /** @summary Search painter with specified snapid, also sub-pads are checked - * @private */ - findSnap(snapid, onlyid) { - function check(checkid) { - if (!checkid || !isStr(checkid)) return false; - if (checkid === snapid) return true; - return onlyid && (checkid.length > snapid.length) && - (checkid.indexOf(snapid) === (checkid.length - snapid.length)); - } + const reader = new RBufferReader(header_blob), - if (check(this.snapid)) return this; + payloadStart = reader.offset, + // Read the envelope metadata + { + envelopeLength + } = this._readEnvelopeMetadata(reader), - if (!this.painters) return null; + // Seek to end of envelope to get checksum + checksumPos = payloadStart + envelopeLength - 8, + currentPos = reader.offset; - for (let k=0; k> 16n) & 0xFFFFFFFFFFFFn); - // update only pad/canvas attributes - this.updateObject(snap); + return { + envelopeType, + envelopeLength + }; + } - // apply all changes in the object (pad or canvas) - if (this.iscan) - this.createCanvasSvg(2); - else - this.createPadSvg(true); + _readSchemaDescription(reader) { + // Reading new descriptor arrays from the input + const newFields = this._readFieldDescriptors(reader), + newColumns = this._readColumnDescriptors(reader), + newAliases = this._readAliasColumn(reader), + newExtra = this._readExtraTypeInformation(reader); + // Merging these new arrays into existing arrays + this.fieldDescriptors = (this.fieldDescriptors || []).concat(newFields); + this.columnDescriptors = (this.columnDescriptors || []).concat(newColumns); + this.aliasColumns = (this.aliasColumns || []).concat(newAliases); + this.extraTypeInfo = (this.extraTypeInfo || []).concat(newExtra); + } - let isanyfound = false, isanyremove = false; - // find and remove painters which no longer exists in the list - for (let k = 0; k < this.painters.length; ++k) { - let sub = this.painters[k]; - if (sub.snapid === undefined) continue; // look only for painters with snapid + _readFeatureFlags(reader) { + this.featureFlags = []; + while (true) { + const val = reader.readU64(); + this.featureFlags.push(val); + if ((val & 0x8000000000000000n) === 0n) + break; // MSB not set: end of list + } + + // verify all feature flags are zero + if (this.featureFlags.some(v => v !== 0n)) + throw new Error('Unexpected non-zero feature flags: ' + this.featureFlags); + } + + _readFieldDescriptors(reader) { + const startOffset = BigInt(reader.offset), + fieldListSize = reader.readS64(), // signed 64-bit + fieldListIsList = fieldListSize < 0; + + + if (!fieldListIsList) + throw new Error('Field list frame is not a list frame, which is required.'); + + const fieldListCount = reader.readU32(), // number of field entries + // List frame: list of field record frames + + fieldDescriptors = []; + for (let i = 0; i < fieldListCount; ++i) { + const recordStart = BigInt(reader.offset), + fieldRecordSize = reader.readS64(), + fieldVersion = reader.readU32(), + typeVersion = reader.readU32(), + parentFieldId = reader.readU32(), + structRole = reader.readU16(), + flags = reader.readU16(), + + fieldName = reader.readString(), + typeName = reader.readString(), + typeAlias = reader.readString(), + description = reader.readString(); + let arraySize = null, + sourceFieldId = null, + checksum = null; + + if (flags & kFlagRepetitiveField) + arraySize = reader.readU64(); + + if (flags & kFlagProjectedField) + sourceFieldId = reader.readU32(); + + if (flags & kFlagHasTypeChecksum) + checksum = reader.readU32(); + + + fieldDescriptors.push({ + fieldVersion, + typeVersion, + parentFieldId, + structRole, + flags, + fieldName, + typeName, + typeAlias, + description, + arraySize, + sourceFieldId, + checksum + }); + reader.seek(Number(recordStart + fieldRecordSize)); + } + reader.seek(Number(startOffset - fieldListSize)); + return fieldDescriptors; + } + + _readColumnDescriptors(reader) { + const startOffset = BigInt(reader.offset), + columnListSize = reader.readS64(), + columnListIsList = columnListSize < 0; + if (!columnListIsList) + throw new Error('Column list frame is not a list frame, which is required.'); + const columnListCount = reader.readU32(), // number of column entries + columnDescriptors = []; + for (let i = 0; i < columnListCount; ++i) { + const recordStart = BigInt(reader.offset), + columnRecordSize = reader.readS64(), + coltype = reader.readU16(), + bitsOnStorage = reader.readU16(), + fieldId = reader.readU32(), + flags = reader.readU16(), + representationIndex = reader.readU16(); + let firstElementIndex = null, + minValue = null, + maxValue = null; + + if (flags & kFlagDeferredColumn) + firstElementIndex = reader.readU64(); + + if (flags & kFlagHasValueRange) { + minValue = reader.readF64(); + maxValue = reader.readF64(); + } + + + const column = { + coltype, + bitsOnStorage, + fieldId, + flags, + representationIndex, + firstElementIndex, + minValue, + maxValue, + index: i + }; + column.isDeferred = function() { + return (this.flags & RNTupleDescriptorBuilder.kFlagDeferredColumn) !== 0; + }; + column.isSuppressed = function() { + return this.firstElementIndex !== null && this.firstElementIndex < 0; + }; - snap.fPrimitives.forEach(prim => { - if (sub && (prim.fObjectID === sub.snapid)) { - sub = null; isanyfound = true; - } + columnDescriptors.push(column); + reader.seek(Number(recordStart + columnRecordSize)); + } + reader.seek(Number(startOffset - columnListSize)); + return columnDescriptors; + } + _readAliasColumn(reader) { + const startOffset = BigInt(reader.offset), + aliasColumnListSize = reader.readS64(), + aliasListisList = aliasColumnListSize < 0; + if (!aliasListisList) + throw new Error('Alias column list frame is not a list frame, which is required.'); + const aliasColumnCount = reader.readU32(), // number of alias column entries + aliasColumns = []; + for (let i = 0; i < aliasColumnCount; ++i) { + const recordStart = BigInt(reader.offset), + aliasColumnRecordSize = reader.readS64(), + physicalColumnId = reader.readU32(), + fieldId = reader.readU32(); + aliasColumns.push({ + physicalColumnId, + fieldId }); - - if (sub) { - // remove painter which does not found in the list of snaps - this.painters.splice(k--, 1); - sub.cleanup(); // cleanup such painter - isanyremove = true; - if (this.main_painter_ref === sub) - delete this.main_painter_ref; - } + reader.seek(Number(recordStart + aliasColumnRecordSize)); + } + reader.seek(Number(startOffset - aliasColumnListSize)); + return aliasColumns; + } + _readExtraTypeInformation(reader) { + const startOffset = BigInt(reader.offset), + extraTypeInfoListSize = reader.readS64(), + isList = extraTypeInfoListSize < 0; + + if (!isList) + throw new Error('Extra type info frame is not a list frame, which is required.'); + + const entryCount = reader.readU32(), + + extraTypeInfo = []; + for (let i = 0; i < entryCount; ++i) { + const recordStart = BigInt(reader.offset), + extraTypeInfoRecordSize = reader.readS64(), + contentId = reader.readU32(), + typeVersion = reader.readU32(); + extraTypeInfo.push({ + contentId, + typeVersion + }); + reader.seek(Number(recordStart + extraTypeInfoRecordSize)); } + reader.seek(Number(startOffset - extraTypeInfoListSize)); + return extraTypeInfo; + } + _readClusterGroups(reader) { + const startOffset = BigInt(reader.offset), + clusterGroupListSize = reader.readS64(), + isList = clusterGroupListSize < 0; + if (!isList) + throw new Error('Cluster group frame is not a list frame'); - if (isanyremove) - delete this.pads_cache; + const groupCount = reader.readU32(), - if (!isanyfound) { - let fp = this.getFramePainter(); - // cannot preserve ROOT6 frame - it must be recreated - if (fp?.is_root6()) fp = null; - for (let k = 0; k < this.painters.length; ++k) { - if (fp !== this.painters[k]) - this.painters[k].cleanup(); - } - this.painters = []; - delete this.main_painter_ref; - if (fp) { - this.painters.push(fp); - fp.cleanFrameDrawings(); - fp.redraw(); // need to create all layers again - } - if (isFunc(this.removePadButtons)) - this.removePadButtons(); - this.addPadButtons(true); - } + clusterGroups = []; - const prev_name = this.selectCurrentPad(this.this_pad_name); + for (let i = 0; i < groupCount; ++i) { + const recordStart = BigInt(reader.offset), + clusterRecordSize = reader.readS64(), + minEntry = reader.readU64(), + entrySpan = reader.readU64(), + numClusters = reader.readU32(), + pageListLength = reader.readU64(), - return this.drawNextSnap(snap.fPrimitives).then(() => { - this.addPadInteractive(); - this.selectCurrentPad(prev_name); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - return this; - }); + // Locator method to get the page list locator offset + pageListLocator = this._readLocator(reader), + + + group = { + minEntry, + entrySpan, + numClusters, + pageListLocator, + pageListLength + }; + clusterGroups.push(group); + reader.seek(Number(recordStart + clusterRecordSize)); + } + reader.seek(Number(startOffset - clusterGroupListSize)); + this.clusterGroups = clusterGroups; } - /** @summary Create image for the pad - * @desc Used with web-based canvas to create images for server side - * @return {Promise} with image data, coded with btoa() function - * @private */ - async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'pdf')) { - return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg')) return res; - const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ+7) : ''; + _readLocator(reader) { + const sizeAndType = reader.readU32(); // 4 bytes: size + T bit + if ((sizeAndType | 0) < 0) // | makes the sizeAndType as signed + throw new Error('Non-standard locators (T=1) not supported yet'); + const size = sizeAndType, + offset = reader.readU64(); // 8 bytes: offset + return { + size, + offset + }; + } + deserializePageList(page_list_blob) { + if (!page_list_blob) + throw new Error('deserializePageList: received an invalid or empty page list blob'); + + const reader = new RBufferReader(page_list_blob); + this._readEnvelopeMetadata(reader); + // Page list checksum (64-bit xxhash3) + const pageListHeaderChecksum = reader.readU64(); + if (pageListHeaderChecksum !== this.headerEnvelopeChecksum) + throw new Error('RNTuple corrupted: header checksum does not match Page List Header checksum.'); + + const listStartOffset = BigInt(reader.offset), + // Read cluster summaries list frame + clusterSummaryListSize = reader.readS64(); + if (clusterSummaryListSize >= 0) + throw new Error('Expected a list frame for cluster summaries'); + const clusterSummaryCount = reader.readU32(), + + clusterSummaries = []; + + for (let i = 0; i < clusterSummaryCount; ++i) { + const recordStart = BigInt(reader.offset), + clusterSummaryRecordSize = reader.readS64(), + firstEntry = reader.readU64(), + combined = reader.readU64(), + flags = combined >> 56n; + if (flags & 0x01n) + throw new Error('Cluster summary uses unsupported sharded flag (0x01)'); + const numEntries = Number(combined & 0x00FFFFFFFFFFFFFFn); + clusterSummaries.push({ + firstEntry, + numEntries, + flags }); + reader.seek(Number(recordStart + clusterSummaryRecordSize)); } + reader.seek(Number(listStartOffset - clusterSummaryListSize)); + this.clusterSummaries = clusterSummaries; + this._readNestedFrames(reader); - return ''; + /* const checksumPagelist = */ reader.readU64(); } - /** @summary Show context menu for specified item - * @private */ - itemContextMenu(name) { - const rrr = this.svg_this_pad().node().getBoundingClientRect(), - evnt = { clientX: rrr.left+10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getMainPainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) selp = this.painters[indx]; - } - } + _readNestedFrames(reader) { + const clusterPageLocations = [], + numListClusters = reader.readS64(); + if (numListClusters >= 0) + throw new Error('Expected list frame for clusters'); + const numRecordCluster = reader.readU32(); - if (!isFunc(selp?.fillContextMenu)) return; + for (let i = 0; i < numRecordCluster; ++i) { + const outerListSize = reader.readS64(); + if (outerListSize >= 0) + throw new Error('Expected outer list frame for columns'); - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.snapid) - selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); - } + const numColumns = reader.readU32(), + columns = []; - /** @summary Save pad in specified format - * @desc Used from context menu */ - saveAs(kind, full_canvas, filename) { - if (!filename) - filename = (this.this_pad_name || (this.iscan ? 'canvas' : 'pad')) + '.' + kind; + for (let c = 0; c < numColumns; ++c) { + const innerListSize = reader.readS64(); + if (innerListSize >= 0) + throw new Error('Expected inner list frame for pages'); - this.produceImage(full_canvas, kind).then(imgdata => { - if (!imgdata) - return console.error(`Fail to produce image ${filename}`); + const numPages = reader.readU32(), + pages = []; - saveFile(filename, (kind !== 'svg') ? imgdata : 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(imgdata)); - }); - } + for (let p = 0; p < numPages; ++p) { + const numElementsWithBit = reader.readS32(), + hasChecksum = numElementsWithBit < 0, + numElements = BigInt(Math.abs(Number(numElementsWithBit))), - /** @summary Search active pad - * @return {Object} pad painter for active pad */ - findActivePad() { - return null; - } + locator = this._readLocator(reader); + pages.push({ + numElements, + hasChecksum, + locator + }); + } - /** @summary Prodce image for the pad - * @return {Promise} with created image */ - async produceImage(full_canvas, file_format) { - const use_frame = (full_canvas === 'frame'), - elem = use_frame ? this.getFrameSvg(this.this_pad_name) : (full_canvas ? this.getCanvSvg() : this.svg_this_pad()), - painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, - items = []; // keep list of replaced elements, which should be moved back at the end + const elementOffset = reader.readS64(), + isSuppressed = elementOffset < 0; - if (elem.empty()) - return ''; + let compression = null; + if (!isSuppressed) + compression = reader.readU32(); - if (use_frame || !full_canvas) { - const defs = this.getCanvSvg().selectChild('.canvas_defs'); - if (!defs.empty()) { - items.push({ prnt: this.getCanvSvg(), defs }); - elem.node().insertBefore(defs.node(), elem.node().firstChild); + columns.push({ + pages, + elementOffset, + isSuppressed, + compression + }); } + + clusterPageLocations.push(columns); } - if (!use_frame) { - // do not make transformations for the frame - painter.forEachPainterInPad(pp => { - const item = { prnt: pp.svg_this_pad() }; - items.push(item); + this.pageLocations = clusterPageLocations; + } - // remove buttons from each subpad - const btns = pp.getLayerSvg('btns_layer', this.this_pad_name); - item.btns_node = btns.node(); - if (item.btns_node) { - item.btns_prnt = item.btns_node.parentNode; - item.btns_next = item.btns_node.nextSibling; - btns.remove(); + // Example Of Deserializing Page Content + deserializePage(blob, columnDescriptor, pageInfo) { + const originalColtype = columnDescriptor.coltype, + { + coltype + } = recontructUnsplitBuffer(blob, columnDescriptor); + let { + blob: processedBlob + } = recontructUnsplitBuffer(blob, columnDescriptor); + + + // Handle split index types + if (originalColtype === ENTupleColumnType.kSplitIndex32 || originalColtype === ENTupleColumnType.kSplitIndex64) { + const { + blob: decodedArray + } = DecodeDeltaIndex(processedBlob, coltype); + processedBlob = decodedArray; + } + + // Handle Split Signed Int types + if (originalColtype === ENTupleColumnType.kSplitInt16 || originalColtype === ENTupleColumnType.kSplitInt32 || originalColtype === ENTupleColumnType.kSplitInt64) { + const { + blob: decodedArray + } = decodeZigzag(processedBlob, coltype); + processedBlob = decodedArray; + } + + const reader = new RBufferReader(processedBlob), + values = [], + + // Use numElements from pageInfo parameter + numValues = Number(pageInfo.numElements), + // Helper for all simple types + extractValues = (readFunc) => { + for (let i = 0; i < numValues; ++i) + values.push(readFunc()); + }; + switch (coltype) { + case ENTupleColumnType.kBit: { + let bitCount = 0; + const totalBitsInBuffer = processedBlob.byteLength * 8; + if (totalBitsInBuffer < numValues) + throw new Error(`kBit: Not enough bits in buffer (${totalBitsInBuffer}) for numValues (${numValues})`); + + for (let byteIndex = 0; byteIndex < processedBlob.byteLength; ++byteIndex) { + const byte = reader.readU8(); + + // Extract 8 bits from this byte + for (let bitPos = 0; bitPos < 8 && bitCount < numValues; ++bitPos, ++bitCount) { + const bitValue = (byte >>> bitPos) & 1, + boolValue = bitValue === 1; + values.push(boolValue); + } } + break; + } + + case ENTupleColumnType.kReal64: + extractValues(reader.readF64.bind(reader)); + break; + case ENTupleColumnType.kReal32: + extractValues(reader.readF32.bind(reader)); + break; + case ENTupleColumnType.kInt64: + extractValues(reader.readS64.bind(reader)); + break; + case ENTupleColumnType.kUInt64: + extractValues(reader.readU64.bind(reader)); + break; + case ENTupleColumnType.kInt32: + extractValues(reader.readS32.bind(reader)); + break; + case ENTupleColumnType.kUInt32: + extractValues(reader.readU32.bind(reader)); + break; + case ENTupleColumnType.kInt16: + extractValues(reader.readS16.bind(reader)); + break; + case ENTupleColumnType.kUInt16: + extractValues(reader.readU16.bind(reader)); + break; + case ENTupleColumnType.kInt8: + extractValues(reader.readS8.bind(reader)); + break; + case ENTupleColumnType.kUInt8: + case ENTupleColumnType.kByte: + extractValues(reader.readU8.bind(reader)); + break; + case ENTupleColumnType.kChar: + extractValues(() => String.fromCharCode(reader.readS8())); + break; + case ENTupleColumnType.kIndex32: + extractValues(reader.readS32.bind(reader)); + break; + case ENTupleColumnType.kIndex64: + extractValues(reader.readS64.bind(reader)); + break; + default: + throw new Error(`Unsupported column type: ${columnDescriptor.coltype}`); + } + return values; + } - const main = pp.getFramePainter(); - if (!isFunc(main?.render3D) || !isFunc(main?.access3dKind)) return; +} // class RNTupleDescriptorBuilder - const can3d = main.access3dKind(); - if ((can3d !== constants$1.Embed3D.Overlay) && (can3d !== constants$1.Embed3D.Embed)) return; +/** @summary Very preliminary function to read header/footer from RNTuple + * @private */ +async function readHeaderFooter(tuple) { + // if already read - return immediately, make possible to call several times + if (tuple?.builder) + return true; - const sz2 = main.getSizeFor3d(constants$1.Embed3D.Embed), // get size and position of DOM element as it will be embed - canvas = main.renderer.domElement; + if (!tuple.$file) + return false; - main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately + // request header and footer buffers from the file + return tuple.$file.readBuffer([tuple.fSeekHeader, tuple.fNBytesHeader, tuple.fSeekFooter, tuple.fNBytesFooter]).then(blobs => { + if (blobs?.length !== 2) + return false; - const dataUrl = canvas.toDataURL('image/png'); + // Handle both compressed and uncompressed cases + const processBlob = (blob, uncompressedSize) => { + // If uncompressedSize matches blob size, it's uncompressed + if (blob.byteLength === uncompressedSize) + return Promise.resolve(blob); + return R__unzip(blob, uncompressedSize); + }; - // remove 3D drawings - if (can3d === constants$1.Embed3D.Embed) { - item.foreign = item.prnt.select('.' + sz2.clname); - item.foreign.remove(); - } + return Promise.all([ + processBlob(blobs[0], tuple.fLenHeader), + processBlob(blobs[1], tuple.fLenFooter) + ]).then(unzip_blobs => { + const [header_blob, footer_blob] = unzip_blobs; + if (!header_blob || !footer_blob) + return false; - const svg_frame = main.getFrameSvg(); - item.frame_node = svg_frame.node(); - if (item.frame_node) { - item.frame_next = item.frame_node.nextSibling; - svg_frame.remove(); + tuple.builder = new RNTupleDescriptorBuilder; + tuple.builder.deserializeHeader(header_blob); + tuple.builder.deserializeFooter(footer_blob); + + // Build fieldToColumns mapping + tuple.fieldToColumns = {}; + for (const colDesc of tuple.builder.columnDescriptors) { + const fieldDesc = tuple.builder.fieldDescriptors[colDesc.fieldId], + fieldName = fieldDesc.fieldName; + if (!tuple.fieldToColumns[fieldName]) + tuple.fieldToColumns[fieldName] = []; + tuple.fieldToColumns[fieldName].push(colDesc); + } + + // Deserialize Page List + const group = tuple.builder.clusterGroups?.[0]; + if (!group || !group.pageListLocator) + throw new Error('No valid cluster group or page list locator found'); + + const offset = Number(group.pageListLocator.offset), + size = Number(group.pageListLocator.size), + uncompressedSize = Number(group.pageListLength); + + return tuple.$file.readBuffer([offset, size]).then(page_list_blob => { + if (!(page_list_blob instanceof DataView)) + throw new Error(`Expected DataView from readBuffer, got ${Object.prototype.toString.call(page_list_blob)}`); + + // Check if page list data is uncompressed + if (page_list_blob.byteLength === uncompressedSize) { + // Data is uncompressed, use directly + tuple.builder.deserializePageList(page_list_blob); + return true; } + // Attempt to decompress the page list + return R__unzip(page_list_blob, uncompressedSize).then(unzipped_blob => { + if (!(unzipped_blob instanceof DataView)) + throw new Error(`Unzipped page list is not a DataView, got ${Object.prototype.toString.call(unzipped_blob)}`); - // add svg image - item.img = item.prnt.insert('image', '.primitives_layer') // create image object - .attr('x', sz2.x) - .attr('y', sz2.y) - .attr('width', canvas.width) - .attr('height', canvas.height) - .attr('href', dataUrl); - }, 'pads'); - } - - let width = elem.property('draw_width'), height = elem.property('draw_height'); - if (use_frame) { - const fp = this.getFramePainter(); - width = fp.getFrameWidth(); - height = fp.getFrameHeight(); - } + tuple.builder.deserializePageList(unzipped_blob); + return true; + }); + }); + }); + }).catch(err => { + console.error('Error during readHeaderFooter execution:', err); + throw err; + }); +} - const arg = (file_format === 'pdf') - ? { node: elem.node(), width, height, reset_tranform: use_frame } - : compressSVG(`${elem.node().innerHTML}`); +function readEntry(rntuple, fieldName, entryIndex) { + const builder = rntuple.builder, + field = builder.fieldDescriptors.find(f => f.fieldName === fieldName), + fieldData = rntuple._clusterData[fieldName]; - return svgToImage(arg, file_format).then(res => { - for (let k = 0; k < items.length; ++k) { - const item = items[k]; + if (!field) + throw new Error(`No descriptor for field ${fieldName}`); + if (!fieldData) + throw new Error(`No data for field ${fieldName}`); - item.img?.remove(); // delete embed image + // Detect and decode string fields + if (Array.isArray(fieldData) && fieldData.length === 2) { + const [offsets, payload] = fieldData, + start = entryIndex === 0 ? 0 : Number(offsets[entryIndex - 1]), + end = Number(offsets[entryIndex]), + decoded = payload.slice(start, end).join(''); // Convert to string + return decoded; + } - const prim = item.prnt.selectChild('.primitives_layer'); + // Fallback: primitive type (e.g. int, float) + return fieldData[0][entryIndex]; +} - if (item.foreign) // reinsert foreign object - item.prnt.node().insertBefore(item.foreign.node(), prim.node()); +/** @summary Return field name for specified branch index + * @desc API let use field name in selector or field object itself */ +function getSelectorFieldName(selector, i) { + const br = selector.getBranch(i); + return isStr(br) ? br : br?.fieldName; +} - if (item.frame_node) // reinsert frame as first in list of primitives - prim.node().insertBefore(item.frame_node, item.frame_next); +// Read and process the next data cluster from the RNTuple +function readNextCluster(rntuple, selector) { + const builder = rntuple.builder; - if (item.btns_node) // reinsert buttons - item.btns_prnt.insertBefore(item.btns_node, item.btns_next); + // Add validation + if (!builder.clusterSummaries || builder.clusterSummaries.length === 0) + throw new Error('No cluster summaries available - possibly incomplete file reading'); - if (item.defs) // reinsert defs - item.prnt.node().insertBefore(item.defs.node(), item.prnt.node().firstChild); - } - return res; - }); - } + const clusterIndex = selector.currentCluster, + clusterSummary = builder.clusterSummaries[clusterIndex], + // Gather all pages for this cluster from selected fields only + pages = [], + // Collect only selected field names from selector + selectedFields = []; - /** @summary Process pad button click */ - clickPadButton(funcname, evnt) { - if (funcname === 'CanvasSnapShot') - return this.saveAs('png', true); + for (let i = 0; i < selector.numBranches(); ++i) + selectedFields.push(getSelectorFieldName(selector, i)); - if (funcname === 'enlargePad') - return this.enlargePad(); + // For each selected field, collect its columns' pages + for (const fieldName of selectedFields) { + const columns = rntuple.fieldToColumns[fieldName]; + if (!columns) + throw new Error(`Selected field '${fieldName}' not found in RNTuple`); - if (funcname === 'PadSnapShot') - return this.saveAs('png', false); + for (const colDesc of columns) { + const colEntry = builder.pageLocations[clusterIndex]?.[colDesc.index]; - if (funcname === 'PadContextMenus') { - evnt?.preventDefault(); - evnt?.stopPropagation(); - if (closeMenu()) return; + // When the data is missing or broken + if (!colEntry || !colEntry.pages) + throw new Error(`No pages for column ${colDesc.index} in cluster ${clusterIndex}`); - return createMenu(evnt, this).then(menu => { - menu.add('header:Menus'); + for (const page of colEntry.pages) + pages.push({ page, colDesc, fieldName }); + } + } - if (this.iscan) - menu.add('Canvas', 'pad', this.itemContextMenu); - else - menu.add('Pad', 'pad', this.itemContextMenu); + selector.currentCluster++; - if (this.getFramePainter()) - menu.add('Frame', 'frame', this.itemContextMenu); + // Early exit if no pages to read (i.e., no selected fields matched) + if (pages.length === 0) { + selector.Terminate(false); + return Promise.resolve(); + } - const main = this.getMainPainter(); // here hist painter methods + // Build flat array of [offset, size, offset, size, ...] to read pages + const dataToRead = pages.flatMap(p => + [Number(p.page.locator.offset), Number(p.page.locator.size)] + ); - if (main) { - menu.add('X axis', 'xaxis', this.itemContextMenu); - menu.add('Y axis', 'yaxis', this.itemContextMenu); - if (isFunc(main.getDimension) && (main.getDimension() > 1)) - menu.add('Z axis', 'zaxis', this.itemContextMenu); - } + return rntuple.$file.readBuffer(dataToRead).then(blobsRaw => { + const blobs = Array.isArray(blobsRaw) ? blobsRaw : [blobsRaw], + unzipPromises = blobs.map((blob, idx) => { + const { page, colDesc } = pages[idx], + colEntry = builder.pageLocations[clusterIndex][colDesc.index], // Access column entry + numElements = Number(page.numElements), + elementSize = colDesc.bitsOnStorage / 8; + + // Check if data is compressed + if (colEntry.compression === 0) + return Promise.resolve(blob); // Uncompressed: use blob directly + const expectedSize = numElements * elementSize; + + // Special handling for boolean fields + if (colDesc.coltype === ENTupleColumnType.kBit) { + const expectedBoolSize = Math.ceil(numElements / 8); + if (blob.byteLength === expectedBoolSize) + return Promise.resolve(blob); + // Try decompression but catch errors for boolean fields + return R__unzip(blob, expectedBoolSize).catch(err => { + throw new Error(`Failed to unzip boolean page ${idx}: ${err.message}`); + }); + } - if (this.painters?.length) { - menu.add('separator'); - const shown = []; - this.painters.forEach((pp, indx) => { - const obj = pp?.getObject(); - if (!obj || (shown.indexOf(obj) >= 0) || pp.isSecondary()) return; - let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); - if (name) name += '::'; - name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); - menu.add(name, indx, this.itemContextMenu); - shown.push(obj); + // If the blob is already the expected size, treat as uncompressed + if (blob.byteLength === expectedSize) + return Promise.resolve(blob); + + // Try decompression + return R__unzip(blob, expectedSize).then(result => { + if (!result) + return blob; // Fallback to original blob + return result; + }).catch(err => { + throw new Error(`Failed to unzip page ${idx}: ${err.message}`); }); + }); + + return Promise.all(unzipPromises).then(unzipBlobs => { + rntuple._clusterData = {}; // store deserialized data per field + + for (let i = 0; i < unzipBlobs.length; ++i) { + const blob = unzipBlobs[i]; + // Ensure blob is a DataView + if (!(blob instanceof DataView)) + throw new Error(`Invalid blob type for page ${i}: ${Object.prototype.toString.call(blob)}`); + const { + page, + colDesc + } = pages[i], + field = builder.fieldDescriptors[colDesc.fieldId], + values = builder.deserializePage(blob, colDesc, page); + + // Support multiple representations (e.g., string fields with offsets + payload) + if (!rntuple._clusterData[field.fieldName]) + rntuple._clusterData[field.fieldName] = []; + + // splitting string fields into offset and payload components + if (field.typeName === 'std::string') { + if ( + colDesc.coltype === ENTupleColumnType.kIndex64 || + colDesc.coltype === ENTupleColumnType.kIndex32 || + colDesc.coltype === ENTupleColumnType.kSplitIndex64 || + colDesc.coltype === ENTupleColumnType.kSplitIndex32 + ) // Index64/Index32 + rntuple._clusterData[field.fieldName][0] = values; // Offsets + else if (colDesc.coltype === ENTupleColumnType.kChar) + rntuple._clusterData[field.fieldName][1] = values; // Payload + else + throw new Error(`Unsupported column type for string field: ${colDesc.coltype}`); + } else + rntuple._clusterData[field.fieldName][0] = values; + } + + // Ensure string fields have ending offset for proper reconstruction of the last entry + for (const fieldName of selectedFields) { + const field = builder.fieldDescriptors.find(f => f.fieldName === fieldName), + colData = rntuple._clusterData[fieldName]; + if (field.typeName === 'std::string') { + if (!Array.isArray(colData) || colData.length !== 2) + throw new Error(`String field '${fieldName}' must have 2 columns`); + if (colData[0].length !== builder.clusterSummaries[clusterIndex].numEntries) + throw new Error(`Malformed string field '${fieldName}': missing final offset`); } + } - menu.show(); - }); - } + const numEntries = clusterSummary.numEntries; + for (let i = 0; i < numEntries; ++i) { + for (let b = 0; b < selector.numBranches(); ++b) { + const fieldName = getSelectorFieldName(selector, b), + tgtName = selector.nameOfBranch(b), + values = rntuple._clusterData[fieldName]; - // click automatically goes to all sub-pads - // if any painter indicates that processing completed, it returns true - let done = false; - const prs = []; + if (!values) + throw new Error(`Missing values for selected field: ${fieldName}`); + selector.tgtobj[tgtName] = readEntry(rntuple, fieldName, i); + } + selector.Process(); + } - for (let i = 0; i < this.painters.length; ++i) { - const pp = this.painters[i]; + selector.Terminate(true); + }); + }); +} - if (isFunc(pp.clickPadButton)) - prs.push(pp.clickPadButton(funcname, evnt)); +// TODO args can later be used to filter fields, limit entries, etc. +// Create reader and deserialize doubles from the buffer +function rntupleProcess(rntuple, selector, args) { + return readHeaderFooter(rntuple).then(() => { + selector.Begin(); + selector.currentCluster = 0; + return readNextCluster(rntuple, selector); + }).then(() => selector); +} - if (!done && isFunc(pp.clickButton)) { - done = pp.clickButton(funcname); - if (isPromise(done)) prs.push(done); - } - } +class TDrawSelectorTuple extends TDrawSelector { - return Promise.all(prs); + /** @summary Return total number of entries + * @desc TODO: check implementation details ! */ + getNumEntries(tuple) { + let cnt = 0; + tuple?.builder.clusterSummaries.forEach(summary => { cnt += summary.numEntries; }); + return cnt; } - /** @summary Add button to the pad - * @private */ - addPadButton(btn, tooltip, funcname, keyname) { - if (!settings.ToolBar || this.isBatchMode()) return; + /** @summary Search for field in tuple + * @desc TODO: Can be more complex when name includes extra parts referencing member or collection size or more */ + findBranch(tuple, name) { + return tuple.builder?.fieldDescriptors.find(field => { + return field.fieldName === name; + }); + } - if (!this._buttons) this._buttons = []; - // check if there are duplications + /** @summary Returns true if field can be used as array */ + isArrayBranch(/* tuple, br */) { return false; } - for (let k = 0; k < this._buttons.length; ++k) - if (this._buttons[k].funcname === funcname) return; +} // class TDrawSelectorTuple - this._buttons.push({ btn, tooltip, funcname, keyname }); - const iscan = this.iscan || !this.has_canvas; - if (!iscan && (funcname.indexOf('Pad') !== 0) && (funcname !== 'enlargePad')) { - const cp = this.getCanvPainter(); - if (cp && (cp !== this)) cp.addPadButton(btn, tooltip, funcname); - } - } +/** @summary implementation of drawing for RNTuple + * @param {object|string} args - different setting or simply draw expression + * @param {string} args.expr - draw expression + * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') + * @param {string} [args.drawopt=undefined] - draw options for result histogram + * @param {number} [args.firstentry=0] - first entry to process + * @param {number} [args.numentries=undefined] - number of entries to process, all by default + * @param {Array} [args.elist=undefined] - array of entries id to process, all by default + * @param {boolean} [args.staged] - staged processing, first apply cut to select entries and then perform drawing for selected entries + * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing + * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument + * @return {Promise} with produced object */ - /** @summary Add buttons for pad or canvas - * @private */ - addPadButtons(is_online) { - this.addPadButton('camera', 'Create PNG', this.iscan ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); +async function rntupleDraw(rntuple, args) { + if (isStr(args)) + args = { expr: args }; + else if (!isObject(args)) + args = {}; - if (settings.ContextMenu) - this.addPadButton('question', 'Access context menus', 'PadContextMenus'); + args.SelectorClass = TDrawSelectorTuple; + args.processFunction = rntupleProcess; - const add_enlarge = !this.iscan && this.has_canvas && this.hasObjectsToDraw(); + return readHeaderFooter(rntuple).then(res_header_footer => { + return res_header_footer ? treeDraw(rntuple, args) : null; + }); +} - if (add_enlarge || this.enlargeMain('verify')) - this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); - if (is_online && this.brlayout) { - this.addPadButton('diamand', 'Toggle Ged', 'ToggleGed'); - this.addPadButton('three_circles', 'Toggle Status', 'ToggleStatus'); - } - } +/** @summary Create hierarchy of ROOT::RNTuple object + * @desc Used by hierarchy painter to explore sub-elements + * @private */ +async function tupleHierarchy(tuple_node, tuple) { + tuple_node._childs = []; + // tuple_node._tuple = tuple; // set reference, will be used later by RNTuple::Draw - /** @summary Show pad buttons - * @private */ - showPadButtons() { - if (!this._buttons) return; + return readHeaderFooter(tuple).then(res => { + if (!res) + return res; - PadButtonsHandler.assign(this); - this.showPadButtons(); - } + tuple.builder?.fieldDescriptors.forEach(field => { + const item = { + _name: field.fieldName, + _typename: 'ROOT::RNTupleField', // pseudo class name, used in draw.mjs + _kind: 'ROOT::RNTupleField', + _title: `Filed of type ${field.typeName}`, + $tuple: tuple, // reference on tuple, need for drawing + $field: field + }; - /** @summary Calculates RPadLength value */ - getPadLength(vertical, len, frame_painter) { - let rect, res; - const sign = vertical ? -1 : 1, - getV = (indx, dflt) => (indx < len.fArr.length) ? len.fArr[indx] : dflt, - getRect = () => { - if (!rect) - rect = frame_painter ? frame_painter.getFrameRect() : this.getPadRect(); - return rect; - }; + item._obj = item; - if (frame_painter) { - const user = getV(2), func = vertical ? 'gry' : 'grx'; - if ((user !== undefined) && frame_painter[func]) - res = frame_painter[func](user); - } + tuple_node._childs.push(item); + }); - if (res === undefined) - res = vertical ? getRect().height : 0; + return true; + }); +} - const norm = getV(0, 0), pixel = getV(1, 0); +var rntuple = /*#__PURE__*/Object.freeze({ +__proto__: null, +RBufferReader: RBufferReader, +readEntry: readEntry, +readHeaderFooter: readHeaderFooter, +rntupleDraw: rntupleDraw, +rntupleProcess: rntupleProcess, +tupleHierarchy: tupleHierarchy +}); + +/** @summary function called from draw() + * @desc just envelope for real TTree::Draw method which do the main job + * Can be also used for the branch and leaf object + * @private */ +async function drawRNTuple(dom, obj, opt) { + const args = {}; + let tuple; + + if (obj?.$tuple) { + // case of fictional ROOT::RNTupleField + tuple = obj.$tuple; + args.expr = obj._name; + if (isStr(opt) && opt.indexOf('dump') === 0) + args.expr += '>>' + opt; + else if (opt) + args.expr += opt; + } else { + tuple = obj; + args.expr = opt; + } - res += sign*pixel; + if (!tuple) + throw Error('No RNTuple object available for drawing'); - if (norm) - res += sign * (vertical ? getRect().height : getRect().width) * norm; + args.drawid = dom; - return Math.round(res); - } + args.progress = treeDrawProgress.bind(args); + return rntupleDraw(tuple, args).then(res => args.progress(res, true)); +} - /** @summary Calculates pad position for RPadPos values - * @param {object} pos - instance of RPadPos - * @param {object} frame_painter - if drawing will be performed inside frame, frame painter */ - getCoordinate(pos, frame_painter) { - return { - x: this.getPadLength(false, pos.fHoriz, frame_painter), - y: this.getPadLength(true, pos.fVert, frame_painter) - }; - } +var RNTuple = /*#__PURE__*/Object.freeze({ +__proto__: null, +drawRNTuple: drawRNTuple +}); - /** @summary Decode pad draw options */ - decodeOptions(opt) { - const pad = this.getObject(); - if (!pad) return; +const kNormal = 1, /* kLessTraffic = 2, */ kOffline = 3; - const d = new DrawOptions(opt); +class RObjectPainter extends ObjectPainter { - if (!this.options) this.options = {}; + #pending_request; + #auto_colors; // handle for auto colors - Object.assign(this.options, { GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false }); + constructor(dom, obj, opt, csstype) { + super(dom, obj, opt); + this.csstype = csstype; + } - if (d.check('NOCOLORS') || d.check('NOCOL')) this.options.GlobalColors = this.options.LocalColors = false; - if (d.check('LCOLORS') || d.check('LCOL')) { this.options.GlobalColors = false; this.options.LocalColors = true; } - if (d.check('NOPALETTE') || d.check('NOPAL')) this.options.IgnorePalette = true; - if (d.check('ROTATE')) this.options.RotateFrame = true; - if (d.check('FIXFRAME')) this.options.FixFrame = true; + /** @summary Add painter to pad list of painters + * @desc For RCanvas also handles common style + * @protected */ + addToPadPrimitives(pad_painter) { + const pp = super.addToPadPrimitives(pad_painter); + + if (pp && !this.rstyle && pp.next_rstyle) + this.rstyle = pp.next_rstyle; - if (d.check('WHITE')) pad.fFillColor = 0; - if (d.check('LOGX')) pad.fLogx = 1; - if (d.check('LOGY')) pad.fLogy = 1; - if (d.check('LOGZ')) pad.fLogz = 1; - if (d.check('LOG')) pad.fLogx = pad.fLogy = pad.fLogz = 1; - if (d.check('GRIDX')) pad.fGridx = 1; - if (d.check('GRIDY')) pad.fGridy = 1; - if (d.check('GRID')) pad.fGridx = pad.fGridy = 1; - if (d.check('TICKX')) pad.fTickx = 1; - if (d.check('TICKY')) pad.fTicky = 1; - if (d.check('TICK')) pad.fTickx = pad.fTicky = 1; + return pp; } - /** @summary draw RPad object */ - static async draw(dom, pad, opt) { - const painter = new RPadPainter(dom, pad, false); - painter.decodeOptions(opt); + /** @summary Evaluate v7 attributes using fAttr storage and configured RStyle */ + v7EvalAttr(name, dflt) { + const obj = this.getObject(); + if (!obj) + return dflt; + if (this.cssprefix) + name = this.cssprefix + name; - if (painter.getCanvSvg().empty()) { - painter.has_canvas = false; - painter.this_pad_name = ''; - painter.setTopPainter(); - } else - painter.addToPadPrimitives(painter.pad_name); // must be here due to pad painter + const type_check = res => { + if (dflt === undefined) + return res; + const typ1 = typeof dflt, typ2 = typeof res; + if (typ1 === typ2) + return res; + if (typ1 === 'boolean') { + if (typ2 === 'string') + return (res !== '') && (res !== '0') && (res !== 'no') && (res !== 'off'); + return Boolean(res); + } + if ((typ1 === 'number') && (typ2 === 'string')) + return parseFloat(res); + return res; + }; + if (obj.fAttr?.m) { + const value = obj.fAttr.m[name]; + if (value) + return type_check(value.v); // found value direct in attributes + } - painter.createPadSvg(); + if (this.rstyle?.fBlocks) { + const blks = this.rstyle.fBlocks; + for (let k = 0; k < blks.length; ++k) { + const block = blks[k], + match = (this.csstype && (block.selector === this.csstype)) || + (obj.fId && (block.selector === ('#' + obj.fId))) || + (obj.fCssClass && (block.selector === ('.' + obj.fCssClass))); - if (painter.matchObjectType(clTPad) && (!painter.has_canvas || painter.hasObjectsToDraw())) - painter.addPadButtons(); + if (match && block.map?.m) { + const value = block.map.m[name.toLowerCase()]; + if (value) + return type_check(value.v); + } + } + } - // we select current pad, where all drawing is performed - const prev_name = painter.has_canvas ? painter.selectCurrentPad(painter.this_pad_name) : undefined; + return dflt; + } - selectActivePad({ pp: painter, active: false }); + /** @summary Set v7 attributes value */ + v7SetAttr(name, value) { + const obj = this.getObject(); + if (this.cssprefix) + name = this.cssprefix + name; - // flag used to prevent immediate pad redraw during first draw - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.showPadButtons(); - // we restore previous pad name - painter.selectCurrentPad(prev_name); - return painter; - }); + if (obj?.fAttr?.m) + obj.fAttr.m[name] = { v: value }; } -} // class RPadPainter + /** @summary Decode pad length from string, return pixel value */ + v7EvalLength(name, sizepx, dflt) { + if (sizepx <= 0) + sizepx = 1; -/** - * [js-sha256]{@link https://github.com/emn178/js-sha256} - * - * @version 0.10.1 - * @author Chen, Yi-Cyuan [emn178@gmail.com] - * @copyright Chen, Yi-Cyuan 2014-2023 - * @license MIT - */ + const value = this.v7EvalAttr(name); -const HEX_CHARS = '0123456789abcdef'.split(''), - EXTRA = [-2147483648, 8388608, 32768, 128], - SHIFT = [24, 16, 8, 0], - K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; - - -class Sha256 { - - constructor(is224) { - this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - if (is224) { - this.h0 = 0xc1059ed8; - this.h1 = 0x367cd507; - this.h2 = 0x3070dd17; - this.h3 = 0xf70e5939; - this.h4 = 0xffc00b31; - this.h5 = 0x68581511; - this.h6 = 0x64f98fa7; - this.h7 = 0xbefa4fa4; - } else { // 256 - this.h0 = 0x6a09e667; - this.h1 = 0xbb67ae85; - this.h2 = 0x3c6ef372; - this.h3 = 0xa54ff53a; - this.h4 = 0x510e527f; - this.h5 = 0x9b05688c; - this.h6 = 0x1f83d9ab; - this.h7 = 0x5be0cd19; - } - - this.block = this.start = this.bytes = this.hBytes = 0; - this.finalized = this.hashed = false; - this.first = true; - this.is224 = is224; - } + if (value === undefined) + return Math.round(dflt * sizepx); - /** One can use only string or Uint8Array */ - update(message) { - if (this.finalized) - return; + if (typeof value === 'number') + return Math.round(value * sizepx); - const notString = (typeof message !== 'string'), - length = message.length, blocks = this.blocks; + if (value === null) + return 0; - let code, index = 0, i; + let norm = 0, px = 0, val = value, operand = 0, pos = 0; - while (index < length) { - if (this.hashed) { - this.hashed = false; - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } + while (val) { + // skip empty spaces + while ((pos < val.length) && ((val[pos] === ' ') || (val[pos] === '\t'))) + ++pos; - if (notString) { - for (i = this.start; index < length && i < 64; ++index) - blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; - } else { - for (i = this.start; index < length && i < 64; ++index) { - code = message.charCodeAt(index); - if (code < 0x80) - blocks[i >> 2] |= code << SHIFT[i++ & 3]; - else if (code < 0x800) { - blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else if (code < 0xd800 || code >= 0xe000) { - blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); - blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } - } - } + if (pos >= val.length) + break; - this.lastByteIndex = i; - this.bytes += i - this.start; - if (i >= 64) { - this.block = blocks[16]; - this.start = i - 64; - this.hash(); - this.hashed = true; - } else - this.start = i; - } - if (this.bytes > 4294967295) { - this.hBytes += this.bytes / 4294967296 << 0; - this.bytes = this.bytes % 4294967296; - } - return this; - } + if ((val[pos] === '-') || (val[pos] === '+')) { + if (operand) { + console.log(`Fail to parse RPadLength ${value}`); + return dflt; + } + operand = (val[pos] === '-') ? -1 : 1; + pos++; + continue; + } - finalize() { - if (this.finalized) - return; - this.finalized = true; - const blocks = this.blocks, - i = this.lastByteIndex; - blocks[16] = this.block; - blocks[i >> 2] |= EXTRA[i & 3]; - this.block = blocks[16]; - if (i >= 56) { - if (!this.hashed) - this.hash(); - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - blocks[14] = this.hBytes << 3 | this.bytes >>> 29; - blocks[15] = this.bytes << 3; - this.hash(); - } + if (pos > 0) { + val = val.slice(pos); + pos = 0; + } - hash() { - const blocks = this.blocks; - let a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, - h = this.h7, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; - - for (j = 16; j < 64; ++j) { - // rightrotate - t1 = blocks[j - 15]; - s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); - t1 = blocks[j - 2]; - s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); - blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; - } - - bc = b & c; - for (j = 0; j < 64; j += 4) { - if (this.first) { - if (this.is224) { - ab = 300032; - t1 = blocks[0] - 1413257819; - h = t1 - 150054599 << 0; - d = t1 + 24177077 << 0; - } else { - ab = 704751109; - t1 = blocks[0] - 210244248; - h = t1 - 1521486534 << 0; - d = t1 + 143694565 << 0; - } - this.first = false; - } else { - s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); - s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); - ab = a & b; - maj = ab ^ (a & c) ^ bc; - ch = (e & f) ^ (~e & g); - t1 = h + s1 + ch + K[j] + blocks[j]; - t2 = s0 + maj; - h = d + t1 << 0; - d = t1 + t2 << 0; - } - s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); - s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); - da = d & a; - maj = da ^ (d & b) ^ ab; - ch = (h & e) ^ (~h & f); - t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; - t2 = s0 + maj; - g = c + t1 << 0; - c = t1 + t2 << 0; - s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); - s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); - cd = c & d; - maj = cd ^ (c & a) ^ da; - ch = (g & h) ^ (~g & e); - t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; - t2 = s0 + maj; - f = b + t1 << 0; - b = t1 + t2 << 0; - s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); - s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); - bc = b & c; - maj = bc ^ (b & d) ^ cd; - ch = (f & g) ^ (~f & h); - t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; - t2 = s0 + maj; - e = a + t1 << 0; - a = t1 + t2 << 0; - this.chromeBugWorkAround = true; - } - - this.h0 = this.h0 + a << 0; - this.h1 = this.h1 + b << 0; - this.h2 = this.h2 + c << 0; - this.h3 = this.h3 + d << 0; - this.h4 = this.h4 + e << 0; - this.h5 = this.h5 + f << 0; - this.h6 = this.h6 + g << 0; - this.h7 = this.h7 + h << 0; - } + while ((pos < val.length) && (((val[pos] >= '0') && (val[pos] <= '9')) || (val[pos] === '.'))) + pos++; - digest() { - this.finalize(); - - const h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7, - arr = [ - (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, - (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, - (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, - (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, - (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, - (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, - (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF - ]; - if (!this.is224) - arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); - return arr; - } + const v = parseFloat(val.slice(0, pos)); + if (!Number.isFinite(v)) { + console.log(`Fail to parse RPadLength ${value}`); + return Math.round(dflt * sizepx); + } - hex() { - const d = this.digest(); - let res = ''; - for (let i = 0; i < d.length; ++i) - res += HEX_CHARS[(d[i] >> 4) & 0xF] + HEX_CHARS[d[i] & 0xF]; - return res; - } + val = val.slice(pos); + pos = 0; + if (!operand) + operand = 1; + if (val && (val[0] === '%')) { + val = val.slice(1); + norm += operand * v * 0.01; + } else if ((val.length > 1) && (val[0] === 'p') && (val[1] === 'x')) { + val = val.slice(2); + px += operand * v; + } else + norm += operand * v; - toString() { - return this.hex(); - } + operand = 0; + } -} // class Sha256 + return Math.round(norm * sizepx + px); + } -function sha256(message, as_hex) { - const m = new Sha256(false); - m.update(message); - return as_hex ? m.hex() : m.digest(); -} + /** @summary Evaluate RColor using attribute storage and configured RStyle */ + v7EvalColor(name, dflt) { + let val = this.v7EvalAttr(name, ''); + if (!val || !isStr(val)) + return dflt; -function sha256_2(message, arr, as_hex) { - const m = new Sha256(false); - m.update(message); - m.update(arr); - return as_hex ? m.hex() : m.digest(); -} + if (val === 'auto') { + const pp = this.getPadPainter(); + if (pp) { + val = pp.getAutoColor(); + if (!this.#auto_colors) + this.#auto_colors = {}; + this.#auto_colors[name] = val; + } else if (this.#auto_colors && this.#auto_colors[name]) + val = this.#auto_colors[name]; + else { + console.error(`Autocolor ${name} not defined yet - please check code`); + val = ''; + } + } else if (val[0] === '[') { + const ordinal = parseFloat(val.slice(1, val.length - 1)); + val = 'black'; + if (Number.isFinite(ordinal)) { + const pal = this.getPadPainter()?.getHistPalette(); + if (pal) + val = pal.getColorOrdinal(ordinal); + } + } -// secret session key used for hashing connections keys -// only if set, all messages from and to server signed with HMAC hash -let sessionKey = ''; + // to make colors similar in node and in pupperteer + if ((val[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) { + const col = color(val); + if (col.opacity !== 1) + col.opacity = col.opacity.toFixed(2); + return col.formatRgb(); + } -/** @summary HMAC implementation - * @desc see https://en.wikipedia.org/wiki/HMAC for more details - * @private */ -function HMAC(key, m, o) { - const kbis = sha256(sessionKey + key), - block_size = 64, - opad = 0x5c, ipad = 0x36, - ko = [], ki = []; - while (kbis.length < block_size) - kbis.push(0); - for (let i = 0; i < kbis.length; ++i) { - const code = kbis[i]; - ko.push(code ^ opad); - ki.push(code ^ ipad); + return val; } - const hash = sha256_2(ki, (o === undefined) ? m : new Uint8Array(m, o)); + /** @summary Evaluate RAttrText properties + * @return {Object} FontHandler, can be used directly for the text drawing */ + v7EvalFont(name, dflts, fontScale) { + if (!dflts) + dflts = {}; + else if (typeof dflts === 'number') + dflts = { size: dflts }; - return sha256_2(ko, hash, true); -} + const pp = this.getPadPainter(), + rfont = pp?._dfltRFont || { fFamily: 'Arial', fStyle: '', fWeight: '' }, + text_angle = this.v7EvalAttr(name + '_angle', 0), + text_align = this.v7EvalAttr(name + '_align', dflts.align || 'none'), + text_color = this.v7EvalColor(name + '_color', dflts.color || 'none'), + font_family = this.v7EvalAttr(name + '_font_family', rfont.fFamily || 'Arial'), + font_style = this.v7EvalAttr(name + '_font_style', rfont.fStyle || ''), + font_weight = this.v7EvalAttr(name + '_font_weight', rfont.fWeight || ''); + let text_size = this.v7EvalAttr(name + '_size', dflts.size || 12); -/** - * @summary Class emulating web socket with long-poll http requests - * - * @private - */ + if (isStr(text_size)) + text_size = parseFloat(text_size); + if (!Number.isFinite(text_size) || (text_size <= 0)) + text_size = 12; + if (!fontScale) + fontScale = pp?.getPadHeight() || 100; -class LongPollSocket { - - constructor(addr, _raw, _handle, _counter) { - this.path = addr; - this.connid = null; - this.req = null; - this.raw = _raw; - this.handle = _handle; - this.counter = _counter; - - this.nextRequest('', 'connect'); - } - - /** @summary Submit next request */ - nextRequest(data, kind) { - let url = this.path, reqmode = 'buf', post = null; - if (kind === 'connect') { - url += this.raw ? '?raw_connect' : '?txt_connect'; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); - console.log(`longpoll connect ${url} raw = ${this.raw}`); - this.connid = 'connect'; - } else if (kind === 'close') { - if ((this.connid === null) || (this.connid === 'close')) return; - url += `?connection=${this.connid}&close`; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); - this.connid = 'close'; - reqmode = 'text;sync'; // use sync mode to close connection before browser window closed - } else if ((this.connid === null) || (typeof this.connid !== 'number')) { - if (!browser.qt5) console.error('No connection'); - } else { - url += '?connection=' + this.connid; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); - if (kind === 'dummy') url += '&dummy'; - } + const handler = new FontHandler(null, text_size, fontScale); + handler.setNameStyleWeight(font_family, font_style, font_weight); - if (data) { - if (this.raw) { - // special workaround to avoid POST request, use base64 coding - url += '&post=' + btoa_func(data); - } else { - // send data with post request - most efficient way - reqmode = 'postbuf'; - post = data; - } - } + if (text_angle) + handler.setAngle(360 - text_angle); + if (text_align !== 'none') + handler.setAlign(text_align); + if (text_color !== 'none') + handler.setColor(text_color); - createHttpRequest(url, reqmode, function(res) { - // this set to the request itself, res is response + return handler; + } + + /** @summary Create this.fillatt object based on v7 fill attributes */ + createv7AttFill(prefix) { + if (!prefix || !isStr(prefix)) + prefix = 'fill_'; - if (this.handle.req === this) - this.handle.req = null; // get response for existing dummy request + const color = this.v7EvalColor(prefix + 'color', ''), + pattern = this.v7EvalAttr(prefix + 'style', 0); - if (res === null) - return this.handle.processRequest(null); + this.createAttFill({ pattern, color, color_as_svg: true }); + } - if (this.handle.raw) { - // raw mode - all kind of reply data packed into binary buffer - // first 4 bytes header 'txt:' or 'bin:' - // after the 'bin:' there is length of optional text argument like 'bin:14 :optional_text' - // and immedaitely after text binary data. Server sends binary data so, that offset should be multiple of 8 + /** @summary Create this.lineatt object based on v7 line attributes */ + createv7AttLine(prefix) { + if (!prefix || !isStr(prefix)) + prefix = 'line_'; - const u8Arr = new Uint8Array(res); - let str = '', i = 0, offset = u8Arr.length; - if (offset < 4) { - if (!browser.qt5) console.error(`longpoll got short message in raw mode ${offset}`); - return this.handle.processRequest(null); - } + const color = this.v7EvalColor(prefix + 'color', 'black'), + width = this.v7EvalAttr(prefix + 'width', 1), + style = this.v7EvalAttr(prefix + 'style', 1); + let pattern = this.v7EvalAttr(prefix + 'pattern'); + if (pattern && isNodeJs()) + pattern = pattern.split(',').join(', '); - while (i < 4) str += String.fromCharCode(u8Arr[i++]); - if (str !== 'txt:') { - str = ''; - while ((i < offset) && (String.fromCharCode(u8Arr[i]) !== ':')) - str += String.fromCharCode(u8Arr[i++]); - ++i; - offset = i + parseInt(str.trim()); - } + this.createAttLine({ color, width, style, pattern }); - str = ''; - while (i < offset) str += String.fromCharCode(u8Arr[i++]); + if (prefix === 'border_') + this.lineatt.setBorder(this.v7EvalAttr(prefix + 'rx', 0), this.v7EvalAttr(prefix + 'ry', 0)); + } - if (str) { - if (str === '<>') - this.handle.processRequest(-1111); - else - this.handle.processRequest(str); - } - if (offset < u8Arr.length) - this.handle.processRequest(res, offset); - } else if (this.getResponseHeader('Content-Type') === 'application/x-binary') { - // binary reply with optional header - const extra_hdr = this.getResponseHeader('LongpollHeader'); - if (extra_hdr) this.handle.processRequest(extra_hdr); - this.handle.processRequest(res, 0); - } else { - // text reply - if (res && !isStr(res)) { - let str = ''; - const u8Arr = new Uint8Array(res); - for (let i = 0; i < u8Arr.length; ++i) - str += String.fromCharCode(u8Arr[i]); - res = str; - } - if (res === '<>') - this.handle.processRequest(-1111); - else - this.handle.processRequest(res); - } - }, function(/* err, status */) { - this.handle.processRequest(null, 'error'); - }, true).then(req => { - req.handle = this; - if (!this.req) - this.req = req; // any request can be used for response, do not submit dummy until req is there - req.send(post); - }); + /** @summary Create this.markeratt object based on v7 attributes */ + createv7AttMarker(prefix) { + if (!prefix || !isStr(prefix)) + prefix = 'marker_'; + + const color = this.v7EvalColor(prefix + 'color', 'black'), + size = this.v7EvalAttr(prefix + 'size', 0.01), + style = this.v7EvalAttr(prefix + 'style', 1), + refsize = (size >= 1) ? 1 : (this.getPadPainter()?.getPadHeight() || 100); + + this.createAttMarker({ color, size, style, refsize }); } - /** @summary Process request */ - processRequest(res, _offset) { - if (res === null) { - if (isFunc(this.onerror)) - this.onerror('receive data with connid ' + (this.connid || '---')); - if ((_offset === 'error') && isFunc(this.onclose)) - this.onclose('force_close'); - this.connid = null; - return; - } else if (res === -1111) - res = ''; + /** @summary Create RChangeAttr, which can be applied on the server side + * @private */ + v7AttrChange(req, name, value, kind) { + if (!this.getSnapId()) + return false; - let dummy_tmout = 5; + if (!req._typename) { + req._typename = `${nsREX}RChangeAttrRequest`; + req.ids = []; + req.names = []; + req.values = []; + req.update = true; + } - if (this.connid === 'connect') { - if (!res) { - this.connid = null; - if (isFunc(this.onerror)) - this.onerror('connection rejected'); - return; + if (this.cssprefix) + name = this.cssprefix + name; + req.ids.push(this.getSnapId()); + req.names.push(name); + + if ((value === null) || (value === undefined)) { + if (!kind) + kind = 'none'; + if (kind !== 'none') + console.error(`Trying to set ${kind} for none value`); + } + + if (!kind) { + switch (typeof value) { + case 'number': kind = 'double'; break; + case 'boolean': kind = 'boolean'; break; } + } - this.connid = parseInt(res); - dummy_tmout = 100; // when establishing connection, wait a bit longer to submit dummy package - console.log(`Get new longpoll connection with id ${this.connid}`); - if (isFunc(this.onopen)) - this.onopen(); - } else if (this.connid === 'close') { - if (isFunc(this.onclose)) - this.onclose(); - return; - } else { - if (isFunc(this.onmessage) && res) - this.onmessage({ data: res, offset: _offset }); + const obj = { _typename: `${nsREX}RAttrMap::` }; + switch (kind) { + case 'none': + obj._typename += 'NoValue_t'; + break; + case 'boolean': + obj._typename += 'BoolValue_t'; + obj.v = Boolean(value); + break; + case 'int': + obj._typename += 'IntValue_t'; + obj.v = parseInt(value); + break; + case 'double': + obj._typename += 'DoubleValue_t'; + obj.v = parseFloat(value); + break; + default: + obj._typename += 'StringValue_t'; + obj.v = isStr(value) ? value : JSON.stringify(value); + break; } - // minimal timeout to reduce load, generate dummy only if client not submit new request immediately - if (!this.req) - setTimeout(() => { if (!this.req) this.nextRequest('', 'dummy'); }, dummy_tmout); + req.values.push(obj); + return true; } - /** @summary Send data */ - send(str) { this.nextRequest(str); } + /** @summary Sends accumulated attribute changes to server */ + v7SendAttrChanges(req, do_update) { + const canp = this.getCanvPainter(); + if (canp && req?._typename) { + if (do_update !== undefined) + req.update = Boolean(do_update); + canp.v7SubmitRequest('', req); + } + } - /** @summary Close connection */ - close() { this.nextRequest('', 'close'); } + /** @summary Submit request to server-side drawable + * @param kind defines request kind, only single request a time can be submitted + * @param req is object derived from DrawableRequest, including correct _typename + * @param method is method of painter object which will be called when getting reply */ + v7SubmitRequest(kind, req, method) { + const canp = this.getCanvPainter(); + if (!isFunc(canp?.submitDrawableRequest)) + return null; -} // class LongPollSocket + // special situation when snapid not yet assigned - just keep ref until snapid is there + // maybe keep full list - for now not clear if really needed + if (!this.getSnapId()) { + this.#pending_request = { kind, req, method }; + return req; + } -// ======================================================================================== + return canp.submitDrawableRequest(kind, req, this, method); + } -/** - * @summary Class re-playing socket data from stored protocol - * - * @private - */ + /** @summary Assign snapid to the painter + * @desc Overwrite default method */ + assignSnapId(id) { + super.assignSnapId(id); + if (this.getSnapId() && this.#pending_request) { + const p = this.#pending_request; + this.#pending_request = undefined; + this.v7SubmitRequest(p.kind, p.req, p.method); + } + } -class FileDumpSocket { + /** @summary Return communication mode with the server + * @desc + * kOffline means no server there, + * kLessTraffic advise not to send commands if offline functionality available + * kNormal is standard functionality with RCanvas on server side */ + v7CommMode() { + const canp = this.getCanvPainter(); + if (!canp || !canp.submitDrawableRequest || !canp.getWebsocket()) + return kOffline; - constructor(receiver) { - this.receiver = receiver; - this.protocol = []; - this.cnt = 0; - httpRequest('protocol.json', 'text').then(res => this.getProtocol(res)); - } - - /** @summary Get stored protocol */ - getProtocol(res) { - if (!res) return; - this.protocol = JSON.parse(res); - if (isFunc(this.onopen)) this.onopen(); - this.nextOperation(); - } - - /** @summary Emulate send - just cound operation */ - send(/* str */) { - if (this.protocol[this.cnt] === 'send') { - this.cnt++; - setTimeout(() => this.nextOperation(), 10); - } - } - - /** @summary Emulate close */ - close() {} - - /** @summary Read data for next operation */ - nextOperation() { - // when file request running - just ignore - if (this.wait_for_file) return; - const fname = this.protocol[this.cnt]; - - if (!fname) return; - if (fname === 'send') return; // waiting for send - this.wait_for_file = true; - this.cnt++; - httpRequest(fname, (fname.indexOf('.bin') > 0 ? 'buf' : 'text')).then(res => { - this.wait_for_file = false; - if (!res) return; - const p = fname.indexOf('_ch'), - chid = (p > 0) ? Number.parseInt(fname.slice(p+3, fname.indexOf('.', p))) : 1; - if (isFunc(this.receiver.provideData)) - this.receiver.provideData(chid, res, 0); - setTimeout(() => this.nextOperation(), 10); - }); + return kNormal; } -} // class FileDumpSocket + v7NormalMode() { return this.v7CommMode() === kNormal; } + + v7OfflineMode() { return this.v7CommMode() === kOffline; } +} // class RObjectPainter /** - * @summary Client communication handle for RWebWindow. + * @summary Axis painter for v7 * - * @desc Should be created with {@link connectWebWindow} function + * @private */ -class WebWindowHandle { - - constructor(socket_kind, credits) { - this.kind = socket_kind; - this.state = 0; - this.credits = credits || 10; - this.cansend = this.credits; - this.ackn = this.credits; - this.send_seq = 1; // sequence counter of send messages - this.recv_seq = 0; // sequence counter of received messages - } +class RAxisPainter extends RObjectPainter { - /** @summary Returns arguments specified in the RWebWindow::SetUserArgs() method - * @desc Can be any valid JSON expression. Undefined by default. - * @param {string} [field] - if specified and user args is object, returns correspondent object member - * @return user arguments object */ - getUserArgs(field) { - if (field && isStr(field)) - return isObject(this.user_args) ? this.user_args[field] : undefined; + /** @summary constructor */ + constructor(dom, arg1, axis, cssprefix) { + const drawable = cssprefix ? arg1.getObject() : arg1; + super(dom, drawable, '', cssprefix ? arg1.csstype : 'axis'); + Object.assign(this, AxisPainterMethods); + this.initAxisPainter(); - return this.user_args; + this.axis = axis; + if (cssprefix) { // drawing from the frame + this.embedded = true; // indicate that painter embedded into the histo painter + // this.csstype = arg1.csstype; // for the moment only via frame one can set axis attributes + this.cssprefix = cssprefix; + this.rstyle = arg1.rstyle; + } else { + // this.csstype = 'axis'; + this.cssprefix = 'axis_'; + } } - /** @summary Set user args - * @desc Normally set via RWebWindow::SetUserArgs() method */ - setUserArgs(args) { this.user_args = args; } - - /** @summary Set callbacks receiver. - * @param {object} obj - object with receiver functions - * @param {function} obj.onWebsocketMsg - called when new data receieved from RWebWindow - * @param {function} obj.onWebsocketOpened - called when connection established - * @param {function} obj.onWebsocketClosed - called when connection closed - * @param {function} obj.onWebsocketError - called when get error via the connection */ - setReceiver(obj) { this.receiver = obj; } - - /** @summary Cleanup and close connection. */ + /** @summary cleanup painter */ cleanup() { - delete this.receiver; - this.close(true); + delete this.axis; + delete this.axis_g; + this.cleanupAxisPainter(); + super.cleanup(); } - /** @summary Invoke method in the receiver. - * @private */ - invokeReceiver(brdcst, method, arg, arg2) { - if (this.receiver && isFunc(this.receiver[method])) - this.receiver[method](this, arg, arg2); + /** @summary Use in GED to identify kind of axis */ + getAxisType() { return 'RAttrAxis'; } - if (brdcst && this.channels) { - const ks = Object.keys(this.channels); - for (let n = 0; n < ks.length; ++n) - this.channels[ks[n]].invokeReceiver(false, method, arg, arg2); + /** @summary Configure only base parameters, later same handle will be used for drawing */ + configureZAxis(name, fp) { + this.name = name; + this.kind = kAxisNormal; + this.log = false; + const _log = this.v7EvalAttr('log', 0); + if (_log) { + this.log = true; + this.logbase = 10; + if (Math.abs(_log - Math.exp(1)) < 0.1) + this.logbase = Math.exp(1); + else if (_log > 1.9) + this.logbase = Math.round(_log); } + fp.logz = this.log; } - /** @summary Provide data for receiver. When no queue - do it directly. - * @private */ - provideData(chid, msg, len) { - if (this.wait_first_recv) { - // here dummy first recv like EMBED_DONE is handled - delete this.wait_first_recv; - this.state = 1; - return this.invokeReceiver(false, 'onWebsocketOpened'); - } - - if ((chid > 1) && this.channels) { - const channel = this.channels[chid]; - if (channel) - return channel.provideData(1, msg, len); - } - - const force_queue = len && (len < 0); - if (!force_queue && (!this.msgqueue || !this.msgqueue.length)) - return this.invokeReceiver(false, 'onWebsocketMsg', msg, len); - - if (!this.msgqueue) this.msgqueue = []; - if (force_queue) len = undefined; - - this.msgqueue.push({ ready: true, msg, len }); - } + /** @summary Configure axis painter + * @desc Axis can be drawn inside frame group with offset to 0 point for the frame + * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates + * @private */ + configureAxis(name, min, max, smin, smax, vertical, frame_range, axis_range, opts) { + if (!opts) + opts = {}; + this.name = name; + this.full_min = min; + this.full_max = max; + this.kind = kAxisNormal; + this.vertical = vertical; + this.log = false; + const _log = this.v7EvalAttr('log', 0), + _symlog = this.v7EvalAttr('symlog', 0); + this.reverse = opts.reverse || false; - /** @summary Reserve entry in queue for data, which is not yet decoded. - * @private */ - reserveQueueItem() { - if (!this.msgqueue) this.msgqueue = []; - const item = { ready: false, msg: null, len: 0 }; - this.msgqueue.push(item); - return item; - } + if (this.v7EvalAttr('time')) { + this.kind = kAxisTime; + this.timeoffset = 0; + let toffset = this.v7EvalAttr('timeOffset'); + if (toffset !== undefined) { + toffset = parseFloat(toffset); + if (Number.isFinite(toffset)) + this.timeoffset = toffset * 1000; + } + } else if (this.axis?.fLabelsIndex) { + this.kind = kAxisLabels; + delete this.own_labels; + } else if (opts.labels) + this.kind = kAxisLabels; + else + this.kind = kAxisNormal; - /** @summary Provide data for item which was reserved before. - * @private */ - markQueueItemDone(item, _msg, _len) { - item.ready = true; - item.msg = _msg; - item.len = _len; - this.processQueue(); - } + if (this.kind === kAxisTime) + this.func = time().domain([this.convertDate(smin), this.convertDate(smax)]); + else if (_symlog && (_symlog > 0)) { + this.symlog = _symlog; + this.func = symlog().constant(_symlog).domain([smin, smax]); + } else if (_log) { + if (smax <= 0) + smax = 1; + if ((smin <= 0) || (smin >= smax)) + smin = smax * 0.0001; + this.log = true; + this.logbase = 10; + if (Math.abs(_log - Math.exp(1)) < 0.1) + this.logbase = Math.exp(1); + else if (_log > 1.9) + this.logbase = Math.round(_log); + this.func = log().base(this.logbase).domain([smin, smax]); + } else + this.func = linear().domain([smin, smax]); - /** @summary Process completed messages in the queue - * @private */ - processQueue() { - if (this._loop_msgqueue || !this.msgqueue) return; - this._loop_msgqueue = true; - while ((this.msgqueue.length > 0) && this.msgqueue[0].ready) { - const front = this.msgqueue.shift(); - this.invokeReceiver(false, 'onWebsocketMsg', front.msg, front.len); - } - if (this.msgqueue.length === 0) - delete this.msgqueue; - delete this._loop_msgqueue; - } - - /** @summary Close connection */ - close(force) { - if (this.master) { - this.master.send(`CLOSECH=${this.channelid}`, 0); - delete this.master.channels[this.channelid]; - delete this.master; - return; - } + this.scale_min = smin; + this.scale_max = smax; - if (this.timerid) { - clearTimeout(this.timerid); - delete this.timerid; - } + this.gr_range = axis_range || 1000; // when not specified, one can ignore it - if (this._websocket && (this.state > 0)) { - this.state = force ? -1 : 0; // -1 prevent socket from reopening - this._websocket.onclose = null; // hide normal handler - this._websocket.close(); - delete this._websocket; - } - } + const range = frame_range ?? [0, this.gr_range]; - /** @summary Checks number of credits for send operation - * @param {number} [numsend = 1] - number of required send operations - * @return true if one allow to send specified number of text message to server */ - canSend(numsend) { return this.cansend >= (numsend || 1); } + this.axis_shift = range[1] - this.gr_range; - /** @summary Returns number of possible send operations relative to number of credits */ - getRelCanSend() { return !this.credits ? 1 : this.cansend / this.credits; } + if (this.reverse) + this.func.range([range[1], range[0]]); + else + this.func.range(range); - /** @summary Send text message via the connection. - * @param {string} msg - text message to send - * @param {number} [chid] - channel id, 1 by default, 0 used only for internal communication */ - send(msg, chid) { - if (this.master) - return this.master.send(msg, this.channelid); + if (this.kind === kAxisTime) + this.gr = val => this.func(this.convertDate(val)); + else if (this.log) + this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; + else + this.gr = this.func; - if (!this._websocket || (this.state <= 0)) return false; + delete this.format;// remove formatting func - if (!Number.isInteger(chid)) chid = 1; // when not configured, channel 1 is used - main widget + const ndiv = this.v7EvalAttr('ndiv', 508); - if (this.cansend <= 0) console.error(`should be queued before sending cansend: ${this.cansend}`); + this.nticks = ndiv % 100; + this.nticks2 = (ndiv % 10000 - this.nticks) / 100; + this.nticks3 = Math.floor(ndiv / 10000); - const prefix = `${this.send_seq++}:${this.ackn}:${this.cansend}:${chid}:`; - this.ackn = 0; - this.cansend--; // decrease number of allowed send packets + this.nticks = Math.min(this.nticks, 20); - let hash = 'none'; - if (this.key && sessionKey) - hash = HMAC(this.key, `${prefix}${msg}`); + const gr_range = Math.abs(this.gr_range) || 100; - this._websocket.send(`${hash}:${prefix}${msg}`); + if (this.kind === kAxisTime) { + this.nticks = Math.min(this.nticks, 8); - if ((this.kind === 'websocket') || (this.kind === 'longpoll')) { - if (this.timerid) clearTimeout(this.timerid); - this.timerid = setTimeout(() => this.keepAlive(), 10000); - } + const scale_range = this.scale_max - this.scale_min, + tf2 = chooseTimeFormat(scale_range / gr_range, false); + let tf1 = this.v7EvalAttr('timeFormat', ''); - return true; - } + if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) + tf1 = chooseTimeFormat(scale_range / this.nticks, true); - /** @summary Send only last message of specified kind during defined time interval. - * @desc Idea is to prvent sending multiple messages of similar kind and overload connection - * Instead timeout is started after which only last specified message will be send - * @private */ - sendLast(kind, tmout, msg) { - let d = this._delayed; - if (!d) d = this._delayed = {}; - d[kind] = msg; - if (!d[`${kind}_handler`]) - d[`${kind}_handler`] = setTimeout(() => { delete d[`${kind}_handler`]; this.send(d[kind]); }, tmout); - } + this.tfunc1 = this.tfunc2 = timeFormat(tf1); + if (tf2 !== tf1) + this.tfunc2 = timeFormat(tf2); - /** @summary Inject message(s) into input queue, for debug purposes only - * @private */ - inject(msg, chid, immediate) { - // use timeout to avoid too deep call stack - if (!immediate) - return setTimeout(this.inject.bind(this, msg, chid, true), 0); + this.format = this.formatTime; + } else if (this.log) { + if (this.nticks2 > 1) { + this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally + this.nticks2 = 1; + } + this.noexp = this.v7EvalAttr('noexp', false); + if ((this.scale_max < 300) && (this.scale_min > 0.3) && (this.logbase === 10)) + this.noexp = true; + this.moreloglabels = this.v7EvalAttr('moreloglbls', false); - if (chid === undefined) chid = 1; + this.format = this.formatLog; + } else if (this.kind === kAxisLabels) { + this.nticks = 50; // for text output allow max 50 names + const scale_range = this.scale_max - this.scale_min; + if (this.nticks > scale_range) + this.nticks = Math.round(scale_range); + this.nticks2 = 1; - if (Array.isArray(msg)) { - for (let k = 0; k < msg.length; ++k) - this.provideData(chid, isStr(msg[k]) ? msg[k] : JSON.stringify(msg[k]), -1); - this.processQueue(); - } else if (msg) - this.provideData(chid, isStr(msg) ? msg : JSON.stringify(msg)); + this.format = this.formatLabels; + } else { + this.order = 0; + this.ndig = 0; + this.format = this.formatNormal; + } } - /** @summary Send keep-alive message. - * @desc Only for internal use, only when used with websockets - * @private */ - keepAlive() { - delete this.timerid; - this.send('KEEPALIVE', 0); + /** @summary Return scale min */ + getScaleMin() { + return this.func ? this.func.domain()[0] : 0; } - /** @summary Request server to resize window - * @desc For local displays like CEF or qt5 only server can do this */ - resizeWindow(w, h) { - if (browser.qt5 || browser.cef3) - this.send(`RESIZE=${w},${h}`, 0); - else if ((typeof window !== 'undefined') && isFunc(window?.resizeTo)) - window.resizeTo(w, h); + /** @summary Return scale max */ + getScaleMax() { + return this.func ? this.func.domain()[1] : 0; } - /** @summary Method open channel, which will share same connection, but can be used independently from main - * @private */ - createChannel() { - if (this.master) - return this.master.createChannel(); - - const channel = new WebWindowHandle('channel', this.credits); - channel.wait_first_recv = true; // first received message via the channel is confirmation of established connection - - if (!this.channels) { - this.channels = {}; - this.freechannelid = 2; + /** @summary Provide label for axis value */ + formatLabels(d) { + const indx = Math.round(d); + if (this.axis?.fLabelsIndex) { + if ((indx < 0) || (indx >= this.axis.fNBinsNoOver)) + return null; + for (let i = 0; i < this.axis.fLabelsIndex.length; ++i) { + const pair = this.axis.fLabelsIndex[i]; + if (pair.second === indx) + return pair.first; + } + } else { + const labels = this.getObject().fLabels; + if (labels && (indx >= 0) && (indx < labels.length)) + return labels[indx]; } - - channel.master = this; - channel.channelid = this.freechannelid++; - - // register - this.channels[channel.channelid] = channel; - - // now server-side entity should be initialized and init message send from server side! - return channel; + return null; } - /** @summary Returns true if socket connected */ - isConnected() { return this.state > 0; } + /** @summary Creates array with minor/middle/major ticks */ + createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { + if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) + this.noticksopt = true; - /** @summary Returns used channel ID, 1 by default */ - getChannelId() { return this.channelid && this.master ? this.channelid : 1; } + const ticks = this.produceTicks(this.nticks), + handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: ticks, middle: ticks, major: ticks }; - /** @summary Assign href parameter - * @param {string} [path] - absolute path, when not specified window.location.url will be used - * @private */ - setHRef(path) { - if (isStr(path) && (path.indexOf('?') > 0)) { - this.href = path.slice(0, path.indexOf('?')); - const d = decodeUrl(path); - this.key = d.get('key'); - this.token = d.get('token'); - } else { - this.href = path; - delete this.key; - delete this.token; + if (only_major_as_array) { + const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; + if (res.at(0) > this.scale_min + delta) + res.unshift(this.scale_min); + if (res.at(-1) < this.scale_max - delta) + res.push(this.scale_max); + return res; } - } - /** @summary Return href part - * @param {string} [relative_path] - relative path to the handle - * @private */ - getHRef(relative_path) { - if (!relative_path || !this.kind || !this.href) - return this.href; - let addr = this.href; - if (relative_path.indexOf('../') === 0) { - const ddd = addr.lastIndexOf('/', addr.length-2); - addr = addr.slice(0, ddd) + relative_path.slice(2); - } else - addr += relative_path; + if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10))) { + handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); - return addr; - } + const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - /** @summary provide connection args for the web socket - * @private */ - getConnArgs(ntry) { - let args = ''; - if (this.key) { - const k = HMAC(this.key, `attempt_${ntry}`); - args += `key=${k}&ntry=${ntry}`; - } - if (this.token) { - if (args) args += '&'; - args += `token=${this.token}`; + // avoid black filling by middle-size + if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) + handle.minor = handle.middle = handle.major; + else if ((this.nticks3 > 1) && !this.log) { + handle.minor = this.produceTicks(handle.middle.length, this.nticks3); + if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) + handle.minor = handle.middle; + } } - return args; - } - - /** @summary Create configured socket for current object. - * @private */ - connect(href) { - this.close(); - if (!href && this.href) href = this.href; - let ntry = 0; + handle.reset = function() { + this.nminor = this.nmiddle = this.nmajor = 0; + }; - const retry_open = first_time => { - if (this.state !== 0) return; + handle.next = function(doround) { + if (this.nminor >= this.minor.length) + return false; - if (!first_time) console.log(`try connect window again ${new Date().toString()}`); + this.tick = this.minor[this.nminor++]; + this.grpos = this.func(this.tick); + if (doround) + this.grpos = Math.round(this.grpos); + this.kind = 3; - if (this._websocket) { - this._websocket.close(); - delete this._websocket; + if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { + this.nmiddle++; + this.kind = 2; } - if (!href) { - href = window.location.href; - if (href && href.indexOf('#') > 0) href = href.slice(0, href.indexOf('#')); - if (href && href.lastIndexOf('/') > 0) href = href.slice(0, href.lastIndexOf('/') + 1); + if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { + this.nmajor++; + this.kind = 1; } - this.href = href; - ntry++; - - if (first_time) console.log(`Opening web socket at ${href}`); + return true; + }; - if (ntry > 2) showProgress(`Trying to connect ${href}`); + handle.last_major = function() { + return (this.kind !== 1) ? false : this.nmajor === this.major.length; + }; - let path = href; + handle.next_major_grpos = function() { + return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); + }; - if (this.kind === 'file') { - path += 'root.filedump'; - this._websocket = new FileDumpSocket(this); - console.log(`configure protocol log ${path}`); - } else if ((this.kind === 'websocket') && first_time) { - path = path.replace('http://', 'ws://').replace('https://', 'wss://') + 'root.websocket'; - path += '?' + this.getConnArgs(ntry); - console.log(`configure websocket ${path}`); - this._websocket = new WebSocket(path); - } else { - path += 'root.longpoll'; - console.log(`configure longpoll ${path}`); - this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), this, ntry); - } + handle.get_modifier = function() { return null; }; - if (!this._websocket) return; + this.order = 0; + this.ndig = 0; - this._websocket.onopen = () => { - if (ntry > 2) showProgress(); - this.state = 1; + // at the moment when drawing labels, we can try to find most optimal text representation for them - const key = this.key || ''; - this.send(`READY=${key}`, 0); // need to confirm connection - this.invokeReceiver(false, 'onWebsocketOpened'); - }; + if ((this.kind === kAxisNormal) && !this.log && handle.major.length) { + let maxorder = 0, minorder = 0, exclorder3 = false; - this._websocket.onmessage = e => { - let msg = e.data; - - if (this.next_binary) { - const binchid = this.next_binary, - server_hash = this.next_binary_hash; - delete this.next_binary; - delete this.next_binary_hash; - - if (msg instanceof Blob) { - // convert Blob object to BufferArray - const reader = new FileReader(), qitem = this.reserveQueueItem(); - // The file's text will be printed here - reader.onload = event => { - let result = event.target.result; - if (this.key && sessionKey) { - const hash = HMAC(this.key, result, 0); - if (hash !== server_hash) { - console.log('Discard binary buffer because of HMAC mismatch'); - result = new ArrayBuffer(0); - } - } + if (!optionNoexp) { + const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, + ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - this.markQueueItemDone(qitem, result, 0); - }; - reader.readAsArrayBuffer(msg, e.offset || 0); - } else { - // this is from CEF or LongPoll handler - let result = msg; - if (this.key && sessionKey) { - const hash = HMAC(this.key, result, e.offset || 0); - if (hash !== server_hash) { - console.log('Discard binary buffer because of HMAC mismatch'); - result = new ArrayBuffer(0); - } - } - this.provideData(binchid, result, e.offset || 0); - } + exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - return; + if (maxtick || mintick) { + maxorder = Math.max(ord1, ord2) + 3; + minorder = Math.min(ord1, ord2) - 3; } + } - if (!isStr(msg)) - return console.log(`unsupported message kind: ${typeof msg}`); - - const i0 = msg.indexOf(':'), - server_hash = msg.slice(0, i0), - i1 = msg.indexOf(':', i0 + 1), - seq_id = Number.parseInt(msg.slice(i0 + 1, i1)), - i2 = msg.indexOf(':', i1 + 1), - credit = Number.parseInt(msg.slice(i1 + 1, i2)), - i3 = msg.indexOf(':', i2 + 1), - // cansend = parseInt(msg.slice(i2 + 1, i3)), // TODO: take into account when sending messages - i4 = msg.indexOf(':', i3 + 1), - chid = Number.parseInt(msg.slice(i3 + 1, i4)); - - // for authentication HMAC checksum and sequence id is important - // HMAC used to authenticate server - // sequence id is necessary to exclude submission of same packet again - if (this.key && sessionKey) { - const client_hash = HMAC(this.key, msg.slice(i0+1)); - if (server_hash !== client_hash) - return console.log(`Failure checking server md5 sum ${server_hash}`); - } - - if (seq_id <= this.recv_seq) - return console.log(`Failure with packet sequence ${seq_id} <= ${this.recv_seq}`); - - this.recv_seq = seq_id; // sequence id of received packet - this.ackn++; // count number of received packets, - this.cansend += credit; // how many packets client can send - - msg = msg.slice(i4 + 1); - - if (chid === 0) { - console.log(`GET chid=0 message ${msg}`); - if (msg === 'CLOSE') { - this.close(true); // force closing of socket - this.invokeReceiver(true, 'onWebsocketClosed'); - } else if (msg.indexOf('NEW_KEY=') === 0) { - const newkey = msg.slice(8); - this.close(true); - let href = (typeof document !== 'undefined') ? document.URL : null; - if (isStr(href) && (typeof window !== 'undefined') && window?.history) { - const p = href.indexOf('?key='); - if (p > 0) href = href.slice(0, p); - window.history.replaceState(window.history.state, undefined, `${href}?key=${newkey}`); - } else if (typeof sessionStorage !== 'undefined') - sessionStorage.setItem('RWebWindow_Key', newkey); - location.reload(true); - } - } else if (msg.slice(0, 10) === '$$binary$$') { - this.next_binary = chid; - this.next_binary_hash = msg.slice(10); - } else if (msg === '$$nullbinary$$') - this.provideData(chid, new ArrayBuffer(0), 0); - else - this.provideData(chid, msg); + // now try to find best combination of order and ndig for labels - if (this.ackn > 7) - this.send('READY', 0); // send dummy message to server - }; + let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - this._websocket.onclose = arg => { - delete this._websocket; - if ((this.state > 0) || (arg === 'force_close')) { - console.log('websocket closed'); - this.state = 0; - this.invokeReceiver(true, 'onWebsocketClosed'); + for (let order = minorder; order <= maxorder; order += 3) { + if (exclorder3 && (order === 3)) + continue; + this.order = order; + this.ndig = 0; + let lbls = [], indx = 0, totallen = 0; + while (indx < handle.major.length) { + const v0 = handle.major[indx], + lbl = this.format(v0, true); + + let bad_value = lbls.indexOf(lbl) >= 0; + if (!bad_value) { + try { + const v1 = parseFloat(lbl) * Math.pow(10, order); + bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); + } catch { + console.warn('Failure by parsing of', lbl); + bad_value = true; + } + } + if (bad_value) { + if (++this.ndig > 15) { + totallen += 1e10; + break; // not too many digits, anyway it will be exponential + } + lbls = []; + indx = totallen = 0; + } else { + lbls.push(lbl); + totallen += lbl.length; + indx++; + } } - }; - this._websocket.onerror = err => { - console.log(`websocket error ${err} state ${this.state}`); - if (this.state > 0) { - this.invokeReceiver(true, 'onWebsocketError', err); - this.state = 0; + // for order === 0 we should virtually remove '0.' and extra label on top + if (!order && (this.ndig < 4)) + totallen -= (handle.major.length * 2 + 3); + + if (totallen < bestlen) { + bestlen = totallen; + bestorder = this.order; + bestndig = this.ndig; } - }; + } + + this.order = bestorder; + this.ndig = bestndig; - // only in interactive mode try to reconnect - if (!isBatchMode()) - setTimeout(retry_open, 3000); // after 3 seconds try again - }; // retry_open + if (optionInt) { + if (this.order) + console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); + if (this.ndig) + console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); + this.ndig = 0; + this.order = 0; + } + } - retry_open(true); // call for the first time + return handle; } - /** @summary Send newkey request to application - * @desc If server creates newkey and response - webpage will be reaload - * After key generation done, connection will not be working any longer - * WARNING - only call when you know that you are doing - * @private */ - askReload() { - this.send('GENERATE_KEY', 0); + /** @summary Is labels should be centered */ + isCenteredLabels() { + if (this.kind === kAxisLabels) + return true; + if (this.kind === 'log') + return false; + return this.v7EvalAttr('labels_center', false); } - /** @summary Instal Ctrl-R handler to realod web window - * @desc Instead of default window reload invokes {@link askReload} method - * WARNING - only call when you know that you are doing + /** @summary Is labels should be rotated */ + isRotateLabels() { return false; } + + /** @summary Used to move axis labels instead of zooming * @private */ - addReloadKeyHandler() { - if (this.kind === 'file') return; + processLabelsMove(arg, pos) { + if (this.optionUnlab || !this.axis_g) + return false; - window.addEventListener('keydown', evnt => { - if (((evnt.key === 'R') || (evnt.key === 'r')) && evnt.ctrlKey) { - evnt.stopPropagation(); - evnt.preventDefault(); - console.log('Prevent Ctrl-R propogation - ask reload RWebWindow!'); - this.askReload(); - } - }); - } + const label_g = this.axis_g.select('.axis_labels'); + if (!label_g || (label_g.size() !== 1)) + return false; -} // class WebWindowHandle + if (arg === 'start') { + // no moving without labels + const box = label_g.node().getBBox(); -/** - * @summary Painter class for RCanvas - * - * @private - */ + label_g.append('rect') + .classed('drag', true) + .attr('x', box.x) + .attr('y', box.y) + .attr('width', box.width) + .attr('height', box.height) + .style('cursor', 'move') + .call(addHighlightStyle, true); + if (this.vertical) + this.drag_pos0 = pos[0]; + else + this.drag_pos0 = pos[1]; -class RCanvasPainter extends RPadPainter { - /** @summary constructor */ - constructor(dom, canvas) { - super(dom, canvas, true); - this._websocket = null; - this.tooltip_allowed = settings.Tooltip; - this.v7canvas = true; - if ((dom === null) && (canvas === null)) { - // for web canvas details are important - settings.SmallPad.width = 20; - settings.SmallPad.height = 10; + return true; } - } - /** @summary Cleanup canvas painter */ - cleanup() { - delete this._websocket; - delete this._submreq; + let offset = label_g.property('fix_offset'); - if (this._changed_layout) - this.setLayoutKind('simple'); - delete this._changed_layout; + if (this.vertical) { + offset += Math.round(pos[0] - this.drag_pos0); + makeTranslate(label_g, offset); + } else { + offset += Math.round(pos[1] - this.drag_pos0); + makeTranslate(label_g, 0, offset); + } + if (!offset) + makeTranslate(label_g); - super.cleanup(); - } + if (arg === 'stop') { + label_g.select('rect.drag').remove(); + delete this.drag_pos0; + if (offset !== label_g.property('fix_offset')) { + label_g.property('fix_offset', offset); + const side = label_g.property('side') || 1; + this.labelsOffset = offset / (this.vertical ? -side : side); + this.changeAxisAttr(1, 'labels_offset', this.labelsOffset / this.scalingSize); + } + } - /** @summary Returns layout kind */ - getLayoutKind() { - const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); - return layout || 'simple'; + return true; } - /** @summary Set canvas layout kind */ - setLayoutKind(kind, main_selector) { - const origin = this.selectDom('origin'); - if (!origin.empty()) { - if (!kind) kind = 'simple'; - origin.property('layout', kind); - origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this._changed_layout = (kind !== 'simple'); // use in cleanup - } - } + /** @summary Add interactive elements to draw axes title */ + addTitleDrag(title_g, side) { + if (!settings.MoveResize || this.isBatchMode()) + return; - /** @summary Changes layout - * @return {Promise} indicating when finished */ - async changeLayout(layout_kind, mainid) { - const current = this.getLayoutKind(); - if (current === layout_kind) - return true; + let drag_rect = null, + acc_x, acc_y, new_x, new_y, alt_pos, curr_indx; - const origin = this.selectDom('origin'), - sidebar2 = origin.select('.side_panel2'), - lst = []; - let sidebar = origin.select('.side_panel'), - main = this.selectDom(), force; + const drag_move = drag().subject(Object); - while (main.node().firstChild) - lst.push(main.node().removeChild(main.node().firstChild)); + drag_move + .on('start', evnt => { + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - if (!sidebar.empty()) - cleanup(sidebar.node()); - if (!sidebar2.empty()) - cleanup(sidebar2.node()); + const box = title_g.node().getBBox(), // check that elements visible, request precise value + title_length = this.vertical ? box.height : box.width; - this.setLayoutKind('simple'); // restore defaults - origin.html(''); // cleanup origin + new_x = acc_x = title_g.property('shift_x'); + new_y = acc_y = title_g.property('shift_y'); - if (layout_kind === 'simple') { - main = origin; - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - this.setLayoutKind(layout_kind); - force = true; - } else { - const grid = new GridDisplay(origin.node(), layout_kind); + if (this.titlePos === 'center') + curr_indx = 1; + else + curr_indx = (this.titlePos === 'left') ? 0 : 2; - if (mainid === undefined) - mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; + // let d = ((this.gr_range > 0) && this.vertical) ? title_length : 0; + alt_pos = [0, this.gr_range / 2, this.gr_range]; // possible positions + const off = this.vertical ? -title_length : title_length, + swap = this.isReverseAxis() ? 2 : 0; + if (this.title_align === 'middle') { + alt_pos[swap] += off / 2; + alt_pos[2 - swap] -= off / 2; + } else if ((this.title_align === 'begin') ^ this.isTitleRotated()) { + alt_pos[1] -= off / 2; + alt_pos[2 - swap] -= off; + } else { // end + alt_pos[swap] += off; + alt_pos[1] += off / 2; + } + + alt_pos[curr_indx] = this.vertical ? acc_y : acc_x; + + drag_rect = title_g.append('rect') + .attr('x', box.x) + .attr('y', box.y) + .attr('width', box.width) + .attr('height', box.height) + .style('cursor', 'move') + .call(addHighlightStyle, true); + // .style('pointer-events','none'); // let forward double click to underlying elements + }).on('drag', evnt => { + if (!drag_rect) + return; - main = select(grid.getGridFrame(mainid)); - main.classed('central_panel', true).style('position', 'relative'); + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - if (mainid === 2) { - // left panel for Y - sidebar = select(grid.getGridFrame(0)); - sidebar.classed('side_panel2', true).style('position', 'relative'); - // bottom panel for X - sidebar = select(grid.getGridFrame(3)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } else { - sidebar = select(grid.getGridFrame(1 - mainid)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } + acc_x += evnt.dx; + acc_y += evnt.dy; - // now append all childs to the new main - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); + const p = this.vertical ? acc_y : acc_x; + let set_x, set_y, besti = 0; - this.setLayoutKind(layout_kind, '.central_panel'); + for (let i = 1; i < 3; ++i) { + if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) + besti = i; + } - // remove reference to MDIDisplay, solves resize problem - origin.property('mdi', null); - } + if (this.vertical) { + set_x = acc_x; + set_y = alt_pos[besti]; + } else { + set_x = alt_pos[besti]; + set_y = acc_y; + } - // resize main drawing and let draw extras - resize(main.node(), force); - return true; - } + new_x = set_x; + new_y = set_y; + curr_indx = besti; + makeTranslate(title_g, new_x, new_y); + }).on('end', evnt => { + if (!drag_rect) + return; - /** @summary Toggle projection - * @return {Promise} indicating when ready - * @private */ - async toggleProjection(kind) { - delete this.proj_painter; + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - if (kind) this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed + const basepos = title_g.property('basepos') || 0; - if (isFunc(this.showUI5ProjectionArea)) - return this.showUI5ProjectionArea(kind); + title_g.property('shift_x', new_x) + .property('shift_y', new_y); - let layout = 'simple', mainid; + this.titleOffset = (this.vertical ? basepos - new_x : new_y - basepos) * side; - switch (kind) { - case 'XY': layout = 'projxy'; mainid = 2; break; - case 'X': - case 'bottom': layout = 'vert2_31'; mainid = 0; break; - case 'Y': - case 'left': layout = 'horiz2_13'; mainid = 1; break; - case 'top': layout = 'vert2_13'; mainid = 1; break; - case 'right': layout = 'horiz2_31'; mainid = 0; break; - } + if (curr_indx === 1) + this.titlePos = 'center'; + else if (curr_indx === 0) + this.titlePos = 'left'; + else + this.titlePos = 'right'; - return this.changeLayout(layout, mainid); - } + this.changeAxisAttr(0, 'title_position', this.titlePos, 'title_offset', this.titleOffset / this.scalingSize); - /** @summary Draw projection for specified histogram - * @private */ - async drawProjection(/* kind,hist,hopt */) { - // dummy for the moment - return false; - } + drag_rect.remove(); + drag_rect = null; + }); - /** @summary Draw in side panel - * @private */ - async drawInSidePanel(canv, opt, kind) { - const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', - side = this.selectDom('origin').select(sel); - return side.empty() ? null : this.drawObject(side.node(), canv, opt); + title_g.style('cursor', 'move').call(drag_move); } - /** @summary Checks if canvas shown inside ui5 widget - * @desc Function should be used only from the func which supposed to be replaced by ui5 - * @private */ - testUI5() { - if (!this.use_openui) return false; - console.warn('full ui5 should be used - not loaded yet? Please check!!'); - return true; + /** @summary checks if value inside graphical range, taking into account delta */ + isInsideGrRange(pos, delta1, delta2) { + if (!delta1) + delta1 = 0; + if (delta2 === undefined) + delta2 = delta1; + if (this.gr_range < 0) + return (pos >= this.gr_range - delta2) && (pos <= delta1); + return (pos >= -delta1) && (pos <= this.gr_range + delta2); } - /** @summary Show message - * @desc Used normally with web-based canvas and handled in ui5 - * @private */ - showMessage(msg) { - if (!this.testUI5()) - showProgress(msg, 7000); + /** @summary returns graphical range */ + getGrRange(delta) { + if (!delta) + delta = 0; + if (this.gr_range < 0) + return this.gr_range - delta; + return this.gr_range + delta; } - /** @summary Function called when canvas menu item Save is called */ - saveCanvasAsFile(fname) { - const pnt = fname.indexOf('.'); - this.createImage(fname.slice(pnt+1)) - .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); + /** @summary If axis direction is negative coordinates direction */ + isReverseAxis() { + return !this.vertical !== (this.getGrRange() > 0); } - /** @summary Send command to server to save canvas with specified name - * @desc Should be only used in web-based canvas + /** @summary Draw axis ticks * @private */ - sendSaveCommand(fname) { - this.sendWebsocket('PRODUCE:' + fname); - } + drawMainLine(axis_g) { + let ending = ''; - /** @summary Send message via web socket - * @private */ - sendWebsocket(msg) { - if (this._websocket?.canSend()) { - this._websocket.send(msg); - return true; + if (this.endingSize && this.endingStyle) { + let sz = (this.gr_range > 0) ? -this.endingSize : this.endingSize; + const sz7 = Math.round(sz * 0.7); + sz = Math.round(sz); + if (this.vertical) + ending = `l${sz7},${sz}M0,${this.gr_range}l${-sz7},${sz}`; + else + ending = `l${sz},${sz7}M${this.gr_range},0l${sz},${-sz7}`; } - return false; - } - - /** @summary Close websocket connection to canvas - * @private */ - closeWebsocket(force) { - if (this._websocket) { - this._websocket.close(force); - this._websocket.cleanup(); - delete this._websocket; - } + axis_g.append('svg:path') + .attr('d', 'M0,0' + (this.vertical ? 'v' : 'h') + this.gr_range + ending) + .call(this.lineatt.func) + .style('fill', ending ? 'none' : null); } - /** @summary Use provided connection for the web canvas + /** @summary Draw axis ticks + * @return {Object} with gaps on left and right side * @private */ - useWebsocket(handle) { - this.closeWebsocket(); - - this._websocket = handle; - this._websocket.setReceiver(this); - this._websocket.connect(); - } - - /** @summary set, test or reset timeout of specified name - * @desc Used to prevent overloading of websocket for specific function */ - websocketTimeout(name, tm) { - if (!this._websocket) - return; - if (!this._websocket._tmouts) - this._websocket._tmouts = {}; - - const handle = this._websocket._tmouts[name]; - if (tm === undefined) - return handle !== undefined; - - if (tm === 'reset') { - if (handle) { clearTimeout(handle); delete this._websocket._tmouts[name]; } - } else if (!handle && Number.isInteger(tm)) - this._websocket._tmouts[name] = setTimeout(() => { delete this._websocket._tmouts[name]; }, tm); - } + drawTicks(axis_g, side, main_draw) { + if (main_draw) + this.ticks = []; - /** @summary Hanler for websocket open event - * @private */ - onWebsocketOpened(/* handle */) { - } + this.handle.reset(); - /** @summary Hanler for websocket close event - * @private */ - onWebsocketClosed(/* handle */) { - if (!this.embed_canvas) - closeCurrentWindow(); - } + let res = '', ticks_plusminus = 0; + if (this.ticksSide === 'both') { + side = 1; + ticks_plusminus = 1; + } - /** @summary Hanler for websocket message - * @private */ - onWebsocketMsg(handle, msg) { - // console.log('GET_MSG ' + msg.slice(0,30)); + while (this.handle.next(true)) { + let h1 = Math.round(this.ticksSize / 4), h2; - if (msg === 'CLOSE') { - this.onWebsocketClosed(); - this.closeWebsocket(true); - } else if (msg.slice(0, 5) === 'SNAP:') { - msg = msg.slice(5); - const p1 = msg.indexOf(':'), - snapid = msg.slice(0, p1), - snap = parse(msg.slice(p1+1)); - this.syncDraw(true) - .then(() => { - if (!this.snapid && snap?.fWinSize) - this.resizeBrowser(snap.fWinSize[0], snap.fWinSize[1]); - }).then(() => this.redrawPadSnap(snap)) - .then(() => { - this.addPadInteractive(); - handle.send(`SNAPDONE:${snapid}`); // send ready message back when drawing completed - this.confirmDraw(); - }); - } else if (msg.slice(0, 4) === 'JSON') { - const obj = parse(msg.slice(4)); - // console.log('get JSON ', msg.length-4, obj._typename); - this.redrawObject(obj); - } else if (msg.slice(0, 9) === 'REPL_REQ:') - this.processDrawableReply(msg.slice(9)); - else if (msg.slice(0, 4) === 'CMD:') { - msg = msg.slice(4); - const p1 = msg.indexOf(':'), - cmdid = msg.slice(0, p1), - cmd = msg.slice(p1+1), - reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG')) { - this.createImage(cmd.toLowerCase()) - .then(res => handle.send(reply + res)); - } else if (cmd.indexOf('ADDPANEL:') === 0) { - const relative_path = cmd.slice(9); - if (!isFunc(this.showUI5Panel)) - handle.send(reply + 'false'); - else { - const conn = new WebWindowHandle(handle.kind); + if (this.handle.kind < 3) + h1 = Math.round(this.ticksSize / 2); - // set interim receiver until first message arrives - conn.setReceiver({ - cpainter: this, + const grpos = this.handle.grpos - this.axis_shift; - onWebsocketOpened() { - }, + if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(grpos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) + continue; - onWebsocketMsg(panel_handle, msg) { - const panel_name = (msg.indexOf('SHOWPANEL:') === 0) ? msg.slice(10) : ''; - this.cpainter.showUI5Panel(panel_name, panel_handle) - .then(res => handle.send(reply + (res ? 'true' : 'false'))); - }, + if (this.handle.kind === 1) { + // if not showing labels, not show large tick + if ((this.kind === kAxisLabels) || (this.format(this.handle.tick, true) !== null)) + h1 = this.ticksSize; - onWebsocketClosed() { - // if connection failed, - handle.send(reply + 'false'); - }, + if (main_draw) + this.ticks.push(grpos); // keep graphical positions of major ticks + } - onWebsocketError() { - // if connection failed, - handle.send(reply + 'false'); - } + if (ticks_plusminus > 0) + h2 = -h1; + else if (side < 0) { + h2 = -h1; + h1 = 0; + } else + h2 = 0; - }); + res += this.vertical ? `M${h1},${grpos}H${h2}` : `M${grpos},${-h1}V${-h2}`; + } - let addr = handle.href; - if (relative_path.indexOf('../') === 0) { - const ddd = addr.lastIndexOf('/', addr.length-2); - addr = addr.slice(0, ddd) + relative_path.slice(2); - } else - addr += relative_path; + if (res) { + axis_g.append('svg:path') + .attr('d', res) + .style('stroke', this.ticksColor || this.lineatt.color) + .style('stroke-width', !this.ticksWidth || (this.ticksWidth === 1) ? null : this.ticksWidth); + } - // only when connection established, panel will be activated - conn.connect(addr); - } - } else { - console.log('Unrecognized command ' + cmd); - handle.send(reply); - } - } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { - const kind = msg[1], - hist = parse(msg.slice(7)); - this.drawProjection(kind, hist); - } else if (msg.slice(0, 5) === 'SHOW:') { - const that = msg.slice(5), - on = that[that.length-1] === '1'; - this.showSection(that.slice(0, that.length-2), on); - } else - console.log(`unrecognized msg len: ${msg.length} msg: ${msg.slice(0, 30)}`); + const gap0 = Math.round(0.25 * this.ticksSize), gap = Math.round(1.25 * this.ticksSize); + return { + '-1': (side > 0) || ticks_plusminus ? gap : gap0, + 1: (side < 0) || ticks_plusminus ? gap : gap0 + }; } - /** @summary Submit request to RDrawable object on server side */ - submitDrawableRequest(kind, req, painter, method) { - if (!this._websocket || !req || !req._typename || - !painter.snapid || !isStr(painter.snapid)) return null; + /** @summary Performs labels drawing + * @return {Promise} with gaps in both direction */ + async drawLabels(axis_g, side, gaps) { + const center_lbls = this.isCenteredLabels(), + rotate_lbls = Boolean(this.labelsFont.angle), + label_g = axis_g.append('svg:g').attr('class', 'axis_labels').property('side', side), + lbl_pos = this.handle.lbl_pos || this.handle.major; + let textscale = 1, maxtextlen = 0, lbls_tilt = false, + max_lbl_width = 0, max_lbl_height = 0; - if (kind && method) { - // if kind specified - check if such request already was submitted - if (!painter._requests) painter._requests = {}; + // function called when text is drawn to analyze width, required to correctly scale all labels + function process_drawtext_ready(painter) { + max_lbl_width = Math.max(max_lbl_width, this.result_width); + max_lbl_height = Math.max(max_lbl_height, this.result_height); - const prevreq = painter._requests[kind]; + const textwidth = this.result_width; - if (prevreq) { - const tm = new Date().getTime(); - if (!prevreq._tm || (tm - prevreq._tm < 5000)) { - prevreq._nextreq = req; // submit when got reply - return false; - } - delete painter._requests[kind]; // let submit new request after timeout + if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { + const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); + textscale = Math.min(textscale, maxwidth / textwidth); } - painter._requests[kind] = req; // keep reference on the request + if ((textscale > 0.0001) && (textscale < 0.8) && !painter.vertical && !rotate_lbls && (maxtextlen > 5) && (side > 0)) + lbls_tilt = true; + + const scale = textscale * (lbls_tilt ? 3 : 1); + if ((scale > 0.0001) && (scale < 1)) + painter.scaleTextDrawing(1 / scale, label_g); } - req.id = painter.snapid; + const fix_offset = Math.round((this.vertical ? -side : side) * this.labelsOffset), + fix_coord = Math.round((this.vertical ? -side : side) * gaps[side]); + let lastpos = 0; - if (method) { - if (!this._nextreqid) this._nextreqid = 1; - req.reqid = this._nextreqid++; - } else - req.reqid = 0; // request will not be replied + if (fix_offset) + makeTranslate(label_g, this.vertical ? fix_offset : 0, this.vertical ? 0 : fix_offset); + label_g.property('fix_offset', fix_offset); - const msg = JSON.stringify(req); + return this.startTextDrawingAsync(this.labelsFont, 'font', label_g).then(() => { + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + const lbl = this.format(lbl_pos[nmajor], true); + if (lbl === null) + continue; - if (req.reqid) { - req._kind = kind; - req._painter = painter; - req._method = method; - req._tm = new Date().getTime(); + const arg = { text: lbl, latex: 1, draw_g: label_g }; + let pos = Math.round(this.func(lbl_pos[nmajor])); - if (!this._submreq) this._submreq = {}; - this._submreq[req.reqid] = req; // fast access to submitted requests - } + arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; + arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - // console.log('Sending request ', msg.slice(0,60)); + if (center_lbls) { + const gap = arg.gap_after || arg.gap_before; + pos = Math.round(pos - (this.vertical ? 0.5 * gap : -0.5 * gap)); + if (!this.isInsideGrRange(pos, 5)) + continue; + } - this.sendWebsocket('REQ:' + msg); - return req; - } + maxtextlen = Math.max(maxtextlen, lbl.length); - /** @summary Submit menu request - * @private */ - async submitMenuRequest(painter, menukind, reqid) { - return new Promise(resolveFunc => { - this.submitDrawableRequest('', { - _typename: `${nsREX}RDrawableMenuRequest`, - menukind: menukind || '', - menureqid: reqid // used to identify menu request - }, painter, resolveFunc); - }); - } + pos -= this.axis_shift; - /** @summary Submit executable command for given painter */ - submitExec(painter, exec, subelem) { - // snapid is intentionally ignored - only painter.snapid has to be used - if (!this._websocket) return; + if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(pos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) + continue; - if (subelem && isStr(subelem)) { - const len = subelem.length; - if ((len > 2) && (subelem.indexOf('#x') === len - 2)) subelem = 'x'; else - if ((len > 2) && (subelem.indexOf('#y') === len - 2)) subelem = 'y'; else - if ((len > 2) && (subelem.indexOf('#z') === len - 2)) subelem = 'z'; + if (this.vertical) { + arg.x = fix_coord; + arg.y = pos; + arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); + } else { + arg.x = pos; + arg.y = fix_coord; + arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); + if (this.log && !this.noexp && !this.vertical && arg.align === 23) { + arg.align = 21; + arg.y += this.labelsFont.size; + } + } - if ((subelem === 'x') || (subelem === 'y') || (subelem === 'z')) - exec = subelem + 'axis#' + exec; - else - return console.log(`not recoginzed subelem ${subelem} in SubmitExec`); - } + arg.post_process = process_drawtext_ready; - this.submitDrawableRequest('', { _typename: `${nsREX}RDrawableExecRequest`, exec }, painter); - } + this.drawText(arg); - /** @summary Process reply from request to RDrawable */ - processDrawableReply(msg) { - const reply = parse(msg); - if (!reply || !reply.reqid || !this._submreq) return false; + if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + const axis_step = Math.abs(pos - lastpos); + textscale = Math.min(textscale, 0.9 * axis_step / this.labelsFont.size); + } - const req = this._submreq[reply.reqid]; - if (!req) return false; + lastpos = pos; + } - // remove reference first - delete this._submreq[reply.reqid]; + if (this.order) { + this.drawText({ x: this.vertical ? side * 5 : this.getGrRange(5), + y: this.has_obstacle ? fix_coord : (this.vertical ? this.getGrRange(3) : -3 * side), + align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), + latex: 1, + text: '#times' + this.formatExp(10, this.order), + draw_g: label_g }); + } - // remove blocking reference for that kind - if (req._kind && req._painter?._requests) { - if (req._painter._requests[req._kind] === req) - delete req._painter._requests[req._kind]; - } + return this.finishTextDrawing(label_g); + }).then(() => { + if (lbls_tilt) { + label_g.selectAll('text').each(function() { + const txt = select(this), tr = txt.attr('transform'); + txt.attr('transform', tr + ' rotate(25)').style('text-anchor', 'start'); + }); + } - if (req._method) - req._method(reply, req); + if (this.vertical) + gaps[side] += Math.round(rotate_lbls ? 1.2 * max_lbl_height : max_lbl_width + 0.4 * this.labelsFont.size) - side * fix_offset; + else { + const tilt_height = lbls_tilt ? max_lbl_width * Math.sin(25 / 180 * Math.PI) + max_lbl_height * (Math.cos(25 / 180 * Math.PI) + 0.2) : 0; - // resubmit last request of that kind - if (req._nextreq && !req._painter._requests[req._kind]) - this.submitDrawableRequest(req._kind, req._nextreq, req._painter, req._method); - } + gaps[side] += Math.round(Math.max(rotate_lbls ? max_lbl_width + 0.4 * this.labelsFont.size : 1.2 * max_lbl_height, 1.2 * this.labelsFont.size, tilt_height)) + fix_offset; + } - /** @summary Show specified section in canvas */ - async showSection(that, on) { - switch (that) { - case 'Menu': break; - case 'StatusBar': break; - case 'Editor': break; - case 'ToolBar': break; - case 'ToolTips': this.setTooltipAllowed(on); break; - } - return true; + return gaps; + }); } - /** @summary Method informs that something was changed in the canvas - * @desc used to update information on the server (when used with web6gui) - * @private */ - processChanges(kind, painter, subelem) { - // check if we could send at least one message more - for some meaningful actions - if (!this._websocket || !this._websocket.canSend(2) || !isStr(kind)) return; - if (!painter) painter = this; - switch (kind) { - case 'sbits': - console.log('Status bits in RCanvas are changed - that to do?'); - break; - case 'frame': // when moving frame - case 'zoom': // when changing zoom inside frame - console.log('Frame moved or zoom is changed - that to do?'); - break; - case 'pave_moved': - console.log('TPave is moved inside RCanvas - that to do?'); - break; - default: - if ((kind.slice(0, 5) === 'exec:') && painter?.snapid) - this.submitExec(painter, kind.slice(5), subelem); - else - console.log('UNPROCESSED CHANGES', kind); + /** @summary Add zooming rect to axis drawing */ + addZoomingRect(axis_g, side, lgaps) { + if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { + const sz = Math.max(lgaps[side], 10), + d = this.vertical ? `v${this.gr_range}h${-side * sz}v${-this.gr_range}` : `h${this.gr_range}v${side * sz}h${-this.gr_range}`; + axis_g.append('svg:path') + .attr('d', `M0,0${d}z`) + .attr('class', 'axis_zoom') + .style('opacity', '0') + .style('cursor', 'crosshair'); } } - /** @summary Handle pad button click event - * @private */ - clickPadButton(funcname, evnt) { - if (funcname === 'ToggleGed') - return this.activateGed(this, null, 'toggle'); - if (funcname === 'ToggleStatus') - return this.activateStatusBar('toggle'); - return super.clickPadButton(funcname, evnt); + /** @summary Returns true if axis title is rotated */ + isTitleRotated() { + return this.titleFont && (this.titleFont.angle !== (this.vertical ? 270 : 0)); } - /** @summary returns true when event status area exist for the canvas */ - hasEventStatus() { - if (this.testUI5()) return false; - if (this.brlayout) - return this.brlayout.hasStatus(); - const hp = getHPainter(); - return hp ? hp.hasStatusLine() : false; - } + /** @summary Draw axis title */ + async drawTitle(axis_g, side, lgaps) { + if (!this.fTitle) + return this; - /** @summary Check if status bar can be toggled - * @private */ - canStatusBar() { - return this.testUI5() || this.brlayout || getHPainter(); - } + const title_g = axis_g.append('svg:g').attr('class', 'axis_title'), + rotated = this.isTitleRotated(); - /** @summary Show/toggle event status bar - * @private */ - activateStatusBar(state) { - if (this.testUI5()) - return; - if (this.brlayout) - this.brlayout.createStatusLine(23, state); - else - getHPainter()?.createStatusLine(23, state); + return this.startTextDrawingAsync(this.titleFont, 'font', title_g).then(() => { + let title_shift_x, title_shift_y, title_basepos; - this.processChanges('sbits', this); - } + this.title_align = this.titleCenter ? 'middle' : (this.titleOpposite ^ (this.isReverseAxis() || rotated) ? 'begin' : 'end'); - /** @summary Show online canvas status - * @private */ - showCanvasStatus(...msgs) { - if (this.testUI5()) return; + if (this.vertical) { + title_basepos = Math.round(-side * (lgaps[side])); + title_shift_x = title_basepos + Math.round(-side * this.titleOffset); + title_shift_y = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); + this.drawText({ + align: [this.title_align, ((side < 0) ^ rotated ? 'top' : 'bottom')], + text: this.fTitle, draw_g: title_g + }); + } else { + title_shift_x = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); + title_basepos = Math.round(side * lgaps[side]); + title_shift_y = title_basepos + Math.round(side * this.titleOffset); + this.drawText({ + align: [this.title_align, ((side > 0) ^ rotated ? 'top' : 'bottom')], + text: this.fTitle, draw_g: title_g + }); + } - const br = this.brlayout || getHPainter()?.brlayout; + makeTranslate(title_g, title_shift_x, title_shift_y) + .property('basepos', title_basepos) + .property('shift_x', title_shift_x) + .property('shift_y', title_shift_y); - br?.showStatus(...msgs); - } + this.addTitleDrag(title_g, side); - /** @summary Returns true if GED is present on the canvas */ - hasGed() { - if (this.testUI5()) return false; - return this.brlayout?.hasContent() ?? false; + return this.finishTextDrawing(title_g); + }); } - /** @summary Function used to de-activate GED - * @private */ - removeGed() { - if (this.testUI5()) return; + /** @summary Extract major draw attributes, which are also used in interactive operations + * @private */ + extractDrawAttributes(scalingSize) { + const pp = this.getPadPainter(), + rect = pp?.getPadRect() || { width: 10, height: 10 }; - this.registerForPadEvents(null); + this.scalingSize = scalingSize || (this.vertical ? rect.width : rect.height); - if (this.ged_view) { - this.ged_view.getController().cleanupGed(); - this.ged_view.destroy(); - delete this.ged_view; - } - this.brlayout?.deleteContent(true); - this.processChanges('sbits', this); - } + this.createv7AttLine('line_'); - /** @summary Get view data for ui5 panel - * @private */ - getUi5PanelData(/* panel_name */) { - return { jsroot: { settings, create: create$1, parse, toJSON, loadScript, EAxisBits, getColorExec } }; - } + this.optionUnlab = this.v7EvalAttr('labels_hide', false); - /** @summary Function used to activate GED - * @return {Promise} when GED is there - * @private */ - async activateGed(objpainter, kind, mode) { - if (this.testUI5() || !this.brlayout) - return false; + this.endingStyle = this.v7EvalAttr('ending_style', ''); + this.endingSize = Math.round(this.v7EvalLength('ending_size', this.scalingSize, this.endingStyle ? 0.02 : 0)); + this.startingSize = Math.round(this.v7EvalLength('starting_size', this.scalingSize, 0)); + this.ticksSize = this.v7EvalLength('ticks_size', this.scalingSize, 0.02); + this.ticksSide = this.v7EvalAttr('ticks_side', 'normal'); + this.ticksColor = this.v7EvalColor('ticks_color', ''); + this.ticksWidth = this.v7EvalAttr('ticks_width', 1); + if (scalingSize && (this.ticksSize < 0)) + this.ticksSize = -this.ticksSize; - if (this.brlayout.hasContent()) { - if ((mode === 'toggle') || (mode === false)) - this.removeGed(); - else - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); + this.fTitle = this.v7EvalAttr('title_value', ''); - return true; + if (this.fTitle) { + this.titleFont = this.v7EvalFont('title', { size: 0.03 }, scalingSize || pp?.getPadHeight() || 10); + this.titleFont.roundAngle(180, this.vertical ? 270 : 0); + + this.titleOffset = this.v7EvalLength('title_offset', this.scalingSize, 0); + this.titlePos = this.v7EvalAttr('title_position', 'right'); + this.titleCenter = (this.titlePos === 'center'); + this.titleOpposite = (this.titlePos === 'left'); + } else { + delete this.titleFont; + delete this.titleOffset; + delete this.titlePos; } - if (mode === false) - return false; + // TODO: remove old scaling factors for labels and ticks + this.labelsFont = this.v7EvalFont('labels', { size: scalingSize ? 0.05 : 0.03 }); + this.labelsFont.roundAngle(180); + if (this.labelsFont.angle) + this.labelsFont.angle = 270; + this.labelsOffset = this.v7EvalLength('labels_offset', this.scalingSize, 0); - const btns = this.brlayout.createBrowserBtns(); + if (scalingSize) + this.ticksSize = this.labelsFont.size * 0.5; // old lego scaling factor - ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); + if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) + this.ticksSize = this.maxTickSize; + } - ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); + /** @summary Performs axis drawing + * @return {Promise} which resolved when drawing is completed */ + async drawAxis(layer, transform, side) { + let axis_g = layer; - ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') - .style('margin', '3px').on('click', () => this.removeGed()); + if (side === undefined) + side = 1; - // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area - this.brlayout.setBrowserContent('
    Loading GED ...
    '); - this.brlayout.setBrowserTitle('GED'); - this.brlayout.toggleBrowserKind(kind || 'float'); + if (!this.standalone) { + axis_g = layer.selectChild(`.${this.name}_container`); + if (axis_g.empty()) + axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); + else + axis_g.selectAll('*').remove(); + } - return new Promise(resolveFunc => { - loadOpenui5.then(sap => { - select('#ged_placeholder').text(''); + axis_g.attr('transform', transform); - sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { - const oModel = new JSONModel({ handle: null }); + this.extractDrawAttributes(); + this.axis_g = axis_g; + this.side = side; - XMLView.create({ - viewName: 'rootui5.canv.view.Ged', - viewData: this.getUi5PanelData('Ged') - }).then(oGed => { - oGed.setModel(oModel); + if (this.ticksSide === 'invert') + side = -side; - oGed.placeAt('ged_placeholder'); + if (this.standalone) + this.drawMainLine(axis_g); - this.ged_view = oGed; + const optionNoopt = false, // no ticks position optimization + optionInt = false, // integer labels + optionNoexp = false; // do not create exp - // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself - this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); + this.handle = this.createTicks(false, optionNoexp, optionNoopt, optionInt); - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); + // first draw ticks + const tgaps = this.drawTicks(axis_g, side, true), + labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); // draw labels - this.processChanges('sbits', this); + return labelsPromise.then(lgaps => { + // when drawing axis on frame, zoom rect should be always outside + this.addZoomingRect(axis_g, this.standalone ? side : this.side, lgaps); - resolveFunc(true); - }); - }); - }); + return this.drawTitle(axis_g, side, lgaps); }); } - /** @summary produce JSON for RCanvas, which can be used to display canvas once again + /** @summary Assign handler, which is called when axis redraw by interactive changes + * @desc Used by palette painter to reassign interactive handlers * @private */ - produceJSON() { - console.error('RCanvasPainter.produceJSON not yet implemented'); - return ''; + setAfterDrawHandler(handler) { + this._afterDrawAgain = handler; } - /** @summary resize browser window to get requested canvas sizes */ - resizeBrowser(fullW, fullH) { - if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) + /** @summary Draw axis with the same settings, used by interactive changes */ + drawAxisAgain() { + if (!this.axis_g || !this.side) return; - this._websocket?.resizeWindow(fullW, fullH); - } - /** @summary draw RCanvas object */ - static async draw(dom, can /*, opt */) { - const nocanvas = !can; - if (nocanvas) - can = create$1(`${nsREX}RCanvas`); + this.axis_g.selectAll('*').remove(); - const painter = new RCanvasPainter(dom, can); - painter.normal_canvas = !nocanvas; - painter.createCanvasSvg(0); + this.extractDrawAttributes(); - selectActivePad({ pp: painter, active: false }); + let side = this.side; + if (this.ticksSide === 'invert') + side = -side; - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.addPadButtons(); - painter.showPadButtons(); - return painter; + if (this.standalone) + this.drawMainLine(this.axis_g); + + // first draw ticks + const tgaps = this.drawTicks(this.axis_g, side, false), + labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(this.axis_g, side, tgaps); + + return labelsPromise.then(lgaps => { + // when drawing axis on frame, zoom rect should be always outside + this.addZoomingRect(this.axis_g, this.standalone ? side : this.side, lgaps); + + return this.drawTitle(this.axis_g, side, lgaps); + }).then(() => { + if (isFunc(this._afterDrawAgain)) + this._afterDrawAgain(); }); } -} // class RCanvasPainter + /** @summary Draw axis again on opposite frame size */ + drawAxisOtherPlace(layer, transform, side, only_ticks) { + let axis_g = layer.selectChild(`.${this.name}_container2`); + if (axis_g.empty()) + axis_g = layer.append('svg:g').attr('class', `${this.name}_container2`); + else + axis_g.selectAll('*').remove(); + axis_g.attr('transform', transform); -/** @summary draw RPadSnapshot object - * @private */ -function drawRPadSnapshot(dom, snap /*, opt */) { - const painter = new RCanvasPainter(dom, null); - painter.normal_canvas = false; - painter.batch_mode = isBatchMode(); - return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { - painter.confirmDraw(); - painter.showPadButtons(); - return painter; - }); -} + if (this.ticksSide === 'invert') + side = -side; -/** @summary Ensure RCanvas and RFrame for the painter object - * @param {Object} painter - painter object to process - * @param {string|boolean} frame_kind - false for no frame or '3d' for special 3D mode - * @desc Assigns DOM, creates and draw RCanvas and RFrame if necessary, add painter to pad list of painters - * @return {Promise} for ready - * @private */ -async function ensureRCanvas(painter, frame_kind) { - if (!painter) - return Promise.reject(Error('Painter not provided in ensureRCanvas')); + // draw ticks and labels again + const tgaps = this.drawTicks(axis_g, side, false), + promise = this.optionUnlab || only_ticks ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); - // simple check - if canvas there, can use painter - const pr = painter.getCanvSvg().empty() ? RCanvasPainter.draw(painter.getDom(), null /* noframe */) : Promise.resolve(true); + return promise.then(lgaps => { + this.addZoomingRect(axis_g, side, lgaps); + return true; + }); + } - return pr.then(() => { - if ((frame_kind !== false) && painter.getFrameSvg().selectChild('.main_layer').empty()) - return RFramePainter.draw(painter.getDom(), null, isStr(frame_kind) ? frame_kind : ''); - }).then(() => { - painter.addToPadPrimitives(); - return painter; - }); -} + /** @summary Change zooming in standalone mode */ + zoomStandalone(min, max) { + return this.changeAxisAttr(1, 'zoomMin', min, 'zoomMax', max); + } + /** @summary Redraw axis, used in standalone mode for RAxisDrawable */ + redraw() { + const drawable = this.getObject(), + pp = this.getPadPainter(), + pos = pp.getCoordinate(drawable.fPos), + reverse = this.v7EvalAttr('reverse', false), + labels_len = drawable.fLabels.length, + min = (labels_len > 0) ? 0 : this.v7EvalAttr('min', 0), + max = (labels_len > 0) ? labels_len : this.v7EvalAttr('max', 100); + let len = pp.getPadLength(drawable.fVertical, drawable.fLength), + smin = this.v7EvalAttr('zoomMin'), + smax = this.v7EvalAttr('zoomMax'); -/** @summary Function used for direct draw of RFrameTitle - * @private */ -function drawRFrameTitle(reason, drag) { - const fp = this.getFramePainter(); - if (!fp) - return console.log('no frame painter - no title'); + // in vertical direction axis drawn in negative direction + if (drawable.fVertical) + len -= pp.getPadHeight(); - const rect = fp.getFrameRect(), - fx = rect.x, - fy = rect.y, - fw = rect.width, - // fh = rect.height, - ph = this.getPadPainter().getPadHeight(), - title = this.getObject(), - title_width = fw, - textFont = this.v7EvalFont('text', { size: 0.07, color: 'black', align: 22 }); - let title_margin = this.v7EvalLength('margin', ph, 0.02), - title_height = this.v7EvalLength('height', ph, 0.05); + if (smin === smax) { + smin = min; + smax = max; + } - if (reason === 'drag') { - title_height = drag.height; - title_margin = fy - drag.y - drag.height; - const changes = {}; - this.v7AttrChange(changes, 'margin', title_margin / ph); - this.v7AttrChange(changes, 'height', title_height / ph); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - } + this.configureAxis('axis', min, max, smin, smax, drawable.fVertical, undefined, len, { reverse, labels: labels_len > 0 }); - this.createG(); + const g = this.createG(); - makeTranslate(this.draw_g, fx, Math.round(fy-title_margin-title_height)); + this.standalone = true; // no need to clean axis container - const arg = { x: title_width/2, y: title_height/2, text: title.fText, latex: 1 }; + const promise = this.drawAxis(g, makeTranslate(pos.x, pos.y)); - this.startTextDrawing(textFont, 'font'); + if (this.isBatchMode()) + return promise; - this.drawText(arg); + return promise.then(() => { + if (settings.ContextMenu) { + g.on('contextmenu', evnt => { + evnt.stopPropagation(); // disable main context menu + evnt.preventDefault(); // disable browser context menu + createMenu(evnt, this).then(menu => { + menu.header('RAxisDrawable', `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); + menu.add('Unzoom', () => this.zoomStandalone()); + this.fillAxisContextMenu(menu, ''); + menu.show(); + }); + }); + } - return this.finishTextDrawing().then(() => - addDragHandler(this, { x: fx, y: Math.round(fy-title_margin-title_height), width: title_width, height: title_height, - minwidth: 20, minheight: 20, no_change_x: true, redraw: d => this.redraw('drag', d) }) - ); -} + addDragHandler(this, { + x: pos.x, y: pos.y, width: this.vertical ? 10 : len, height: this.vertical ? len : 10, + only_move: true, redraw: d => this.positionChanged(d) + }); -/// ///////////////////////////////////////////////////////////////////////////////////////// + g.on('dblclick', () => this.zoomStandalone()); -registerMethods(`${nsREX}RPalette`, { + if (settings.ZoomWheel) { + g.on('wheel', evnt => { + evnt.stopPropagation(); + evnt.preventDefault(); - extractRColor(rcolor) { - return rcolor.fColor || 'black'; - }, + const pos2 = pointer(evnt, this.getG().node()), + coord = this.vertical ? (1 - pos2[1] / len) : pos2[0] / len, + item = this.analyzeWheelEvent(evnt, coord); - getColor(indx) { - return this.palette[indx]; - }, + if (item.changed) + this.zoomStandalone(item.min, item.max); + }); + } + }); + } - getContourIndex(zc) { - const cntr = this.fContour; - let l = 0, r = cntr.length-1, mid; + /** @summary Process interactive moving of the axis drawing */ + positionChanged(drag) { + const drawable = this.getObject(), + rect = this.getPadPainter().getPadRect(), + xn = drag.x / rect.width, + yn = 1 - drag.y / rect.height; - if (zc < cntr[0]) return -1; - if (zc >= cntr[r]) return r-1; + drawable.fPos.fHoriz.fArr = [xn]; + drawable.fPos.fVert.fArr = [yn]; - if (this.fCustomContour) { - while (l < r-1) { - mid = Math.round((l+r)/2); - if (cntr[mid] > zc) r = mid; else l = mid; - } - return l; - } + this.submitCanvExec(`SetPos({${xn.toFixed(4)},${yn.toFixed(4)}})`); + } - // last color in palette starts from level cntr[r-1] - return Math.floor((zc-cntr[0]) / (cntr[r-1] - cntr[0]) * (r-1)); - }, + /** @summary Change axis attribute, submit changes to server and redraw axis when specified + * @desc Arguments as redraw_mode, name1, value1, name2, value2, ... */ + changeAxisAttr(redraw_mode, ...args) { + const changes = {}; + let indx = 0; + while (indx < args.length) { + this.v7AttrChange(changes, args[indx], args[indx + 1]); + this.v7SetAttr(args[indx], args[indx + 1]); + indx += 2; + } + this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server + if (redraw_mode === 1) { + if (this.standalone) + this.redraw(); + else + this.drawAxisAgain(); + } else if (redraw_mode) + this.redrawPad(); + } - getContourColor(zc) { - const zindx = this.getContourIndex(zc); - return (zindx < 0) ? '' : this.getColor(zindx); - }, + /** @summary Change axis log scale kind */ + changeAxisLog(arg) { + if ((this.kind === kAxisLabels) || (this.kind === kAxisTime)) + return; + if (arg === 'toggle') + arg = this.log ? 0 : 10; - getContour() { - return this.fContour && (this.fContour.length > 1) ? this.fContour : null; - }, + arg = parseFloat(arg); + if (Number.isFinite(arg)) + this.changeAxisAttr(2, 'log', arg, 'symlog', 0); + } - deleteContour() { - delete this.fContour; - }, + /** @summary Provide context menu for axis */ + fillAxisContextMenu(menu, kind) { + if (kind) + menu.add('Unzoom', () => this.getFramePainter().unzoom(kind)); - calcColor(value, entry1, entry2) { - const dist = entry2.fOrdinal - entry1.fOrdinal, - r1 = entry2.fOrdinal - value, - r2 = value - entry1.fOrdinal; + menu.sub('Log scale', () => this.changeAxisLog('toggle')); + menu.addchk(!this.log && !this.symlog, 'linear', 0, arg => this.changeAxisLog(arg)); + menu.addchk(this.log && !this.symlog && (this.logbase === 10), 'log10', () => this.changeAxisLog(10)); + menu.addchk(this.log && !this.symlog && (this.logbase === 2), 'log2', () => this.changeAxisLog(2)); + menu.addchk(this.log && !this.symlog && Math.abs(this.logbase - Math.exp(1)) < 0.1, 'ln', () => this.changeAxisLog(Math.exp(1))); + menu.addchk(!this.log && this.symlog, 'symlog', 0, () => + menu.input('set symlog constant', this.symlog || 10, 'float').then(v => this.changeAxisAttr(2, 'symlog', v))); + menu.endsub(); - if (!this.fInterpolate || (dist <= 0)) - return (r1 < r2) ? entry2.fColor : entry1.fColor; + menu.add('Divisions', () => menu.input('Set axis devisions', this.v7EvalAttr('ndiv', 508), 'int').then(val => this.changeAxisAttr(2, 'ndiv', val))); - // interpolate - const col1 = rgb(this.extractRColor(entry1.fColor)), - col2 = rgb(this.extractRColor(entry2.fColor)), - color = rgb(Math.round((col1.r*r1 + col2.r*r2)/dist), - Math.round((col1.g*r1 + col2.g*r2)/dist), - Math.round((col1.b*r1 + col2.b*r2)/dist)); + menu.sub('Ticks'); + menu.addRColorMenu('color', this.ticksColor, col => this.changeAxisAttr(1, 'ticks_color', col)); + menu.addSizeMenu('size', 0, 0.05, 0.01, this.ticksSize / this.scalingSize, sz => this.changeAxisAttr(1, 'ticks_size', sz)); + menu.addSelectMenu('side', ['normal', 'invert', 'both'], this.ticksSide, side => this.changeAxisAttr(1, 'ticks_side', side)); + menu.endsub(); - return color.toString(); - }, + if (!this.optionUnlab && this.labelsFont) { + menu.sub('Labels'); + menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.labelsOffset / this.scalingSize, + offset => this.changeAxisAttr(1, 'labels_offset', offset)); + menu.addRAttrTextItems(this.labelsFont, { noangle: 1, noalign: 1 }, + change => this.changeAxisAttr(1, 'labels_' + change.name, change.value)); + menu.addchk(this.labelsFont.angle, 'rotate', res => this.changeAxisAttr(1, 'labels_angle', res ? 180 : 0)); + menu.endsub(); + } - createPaletteColors(len) { - const arr = []; - let indx = 0; + menu.sub('Title', () => menu.input('Enter axis title', this.fTitle).then(t => this.changeAxisAttr(1, 'title_value', t))); - while (arr.length < len) { - const value = arr.length / (len-1), + if (this.fTitle) { + menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.titleOffset / this.scalingSize, + offset => this.changeAxisAttr(1, 'title_offset', offset)); - entry = this.fColors[indx]; + menu.addSelectMenu('position', ['left', 'center', 'right'], this.titlePos, + pos => this.changeAxisAttr(1, 'title_position', pos)); - if ((Math.abs(entry.fOrdinal - value) < 0.0001) || (indx === this.fColors.length - 1)) { - arr.push(this.extractRColor(entry.fColor)); - continue; - } + menu.addchk(this.isTitleRotated(), 'rotate', flag => this.changeAxisAttr(1, 'title_angle', flag ? 180 : 0)); - const next = this.fColors[indx+1]; - if (next.fOrdinal <= value) - indx++; - else - arr.push(this.calcColor(value, entry, next)); + menu.addRAttrTextItems(this.titleFont, { noangle: 1, noalign: 1 }, change => this.changeAxisAttr(1, 'title_' + change.name, change.value)); } - return arr; - }, + menu.endsub(); + return true; + } - getColorOrdinal(value) { - // extract color with ordinal value between 0 and 1 - if (!this.fColors) - return 'black'; - if ((typeof value !== 'number') || (value < 0)) - value = 0; - else if (value > 1) - value = 1; +} // class RAxisPainter - // TODO: implement better way to find index +/** + * @summary Painter class for RFrame, main handler for interactivity + * + * @private + */ - let entry, next = this.fColors[0]; - for (let indx = 0; indx < this.fColors.length-1; ++indx) { - entry = next; +class RFramePainter extends RObjectPainter { - if (Math.abs(entry.fOrdinal - value) < 0.0001) - return this.extractRColor(entry.fColor); + #frame_x; // frame X coordinate + #frame_y; // frame Y coordinate + #frame_width; // frame width + #frame_height; // frame height + #frame_trans; // transform of frame element + #swap_xy; // swap X/Y axis on the frame + #reverse_x; // reverse X axis + #reverse_y; // reverse Y axis + #axes_drawn; // when axes are drawn + #projection; // id of projection function + #click_handler; // handle for click events + #dblclick_handler; // handle for double click events + #keys_handler; // assigned handler for keyboard events + #enabled_keys; // when keyboard press handling enabled + #last_event_pos; // position of last event - next = this.fColors[indx+1]; - if (next.fOrdinal > value) - return this.calcColor(value, entry, next); - } + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} frame - RFrame object */ + constructor(dom, frame) { + super(dom, frame, '', 'frame'); + this.mode3d = false; + this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing + this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing + this.#axes_drawn = false; + this.#projection = 0; // different projections + this.v7_frame = true; // indicator of v7, used in interactive part + } - return this.extractRColor(next.fColor); - }, + /** @summary Returns frame painter - object itself */ + getFramePainter() { return this; } - setFullRange(min, max) { - // set full z scale range, used in zooming - this.full_min = min; - this.full_max = max; - }, + /** @summary Returns true if it is ROOT6 frame + * @private */ + is_root6() { return false; } - createContour(logz, nlevels, zmin, zmax, zminpositive) { - this.fContour = []; - delete this.fCustomContour; - this.colzmin = zmin; - this.colzmax = zmax; + /** @summary Set active flag for frame - can block some events + * @private */ + setFrameActive(on) { + this.#enabled_keys = on && settings.HandleKeys; + // used only in 3D mode + if (this.control) + this.control.enableKeys = this.#enabled_keys; + } - if (logz) { - if (this.colzmax <= 0) this.colzmax = 1.0; - if (this.colzmin <= 0) { - if ((zminpositive === undefined) || (zminpositive <= 0)) - this.colzmin = 0.0001*this.colzmax; - else - this.colzmin = ((zminpositive < 3) || (zminpositive>100)) ? 0.3*zminpositive : 1; - } - if (this.colzmin >= this.colzmax) - this.colzmin = 0.0001*this.colzmax; + /** @summary Returns true if keys handling enabled + * @private */ + isEnabledKeys() { return this.#enabled_keys; } - const logmin = Math.log(this.colzmin)/Math.log(10), - logmax = Math.log(this.colzmax)/Math.log(10), - dz = (logmax-logmin)/nlevels; - this.fContour.push(this.colzmin); - for (let level=1; level 0 && p2 > p1) { - const base64 = font.fSrc.slice(p1 + 7, p2 - 2), - is_ttf = font.fSrc.indexOf('data:application/font-ttf') > 0; - // TODO: for the moment only ttf format supported by jsPDF - if (is_ttf) - entry.property('$fonthandler', { name: font.fFamily, format: 'ttf', base64 }); + /** @summary Update graphical attributes */ + updateAttributes(force) { + if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { + const rect = this.getPadPainter().getPadRect(); + this.fX1NDC = this.v7EvalLength('margins_left', rect.width, gStyle.fPadLeftMargin) / rect.width; + this.fY1NDC = this.v7EvalLength('margins_bottom', rect.height, gStyle.fPadBottomMargin) / rect.height; + this.fX2NDC = 1 - this.v7EvalLength('margins_right', rect.width, gStyle.fPadRightMargin) / rect.width; + this.fY2NDC = 1 - this.v7EvalLength('margins_top', rect.height, gStyle.fPadTopMargin) / rect.height; } - } - if (font.fDefault) - this.getPadPainter()._dfltRFont = font; + if (!this.fillatt) + this.createv7AttFill(); - return true; -} + this.createv7AttLine('border_'); + } -/** @summary draw RAxis object - * @private */ -function drawRAxis(dom, obj, opt) { - const painter = new RAxisPainter(dom, obj, opt); - painter.disable_zooming = true; - return ensureRCanvas(painter, false) - .then(() => painter.redraw()) - .then(() => painter); -} + /** @summary Returns coordinates transformation func */ + getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } -/** @summary draw RFrame object - * @private */ -function drawRFrame(dom, obj, opt) { - const p = new RFramePainter(dom, obj); - if (opt === '3d') p.mode3d = true; - return ensureRCanvas(p, false).then(() => p.redraw()); -} + /** @summary Recalculate frame ranges using specified projection functions + * @desc Not yet used in v7 */ + recalculateRange(Proj) { + this.#projection = Proj || 0; -var RCanvasPainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -RCanvasPainter: RCanvasPainter, -RObjectPainter: RObjectPainter, -RPadPainter: RPadPainter, -drawRAxis: drawRAxis, -drawRFont: drawRFont, -drawRFrame: drawRFrame, -drawRFrameTitle: drawRFrameTitle, -drawRPadSnapshot: drawRPadSnapshot, -ensureRCanvas: ensureRCanvas -}); + if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { + console.warn(`Mercator Projection: latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); + this.#projection = 0; + } -/** @summary draw RText object - * @private */ -function drawText() { - const text = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(text.fPos, onframe), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }); + const func = this.getProjectionFunc(); + if (!func) + return; - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + const pnts = [func(this.scale_xmin, this.scale_ymin), + func(this.scale_xmin, this.scale_ymax), + func(this.scale_xmax, this.scale_ymax), + func(this.scale_xmax, this.scale_ymin)]; + if (this.scale_xmin < 0 && this.scale_xmax > 0) { + pnts.push(func(0, this.scale_ymin)); + pnts.push(func(0, this.scale_ymax)); + } + if (this.scale_ymin < 0 && this.scale_ymax > 0) { + pnts.push(func(this.scale_xmin, 0)); + pnts.push(func(this.scale_xmax, 0)); + } - this.startTextDrawing(textFont, 'font'); + this.original_xmin = this.scale_xmin; + this.original_xmax = this.scale_xmax; + this.original_ymin = this.scale_ymin; + this.original_ymax = this.scale_ymax; - this.drawText({ x: p.x, y: p.y, text: text.fText, latex: 1 }); + this.scale_xmin = this.scale_xmax = pnts[0].x; + this.scale_ymin = this.scale_ymax = pnts[0].y; - return this.finishTextDrawing(); -} + for (let n = 1; n < pnts.length; ++n) { + this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); + this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); + this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); + this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); + } + } -/** @summary draw RLine object - * @private */ -function drawLine() { - const line = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(line.fP1, onframe), - p2 = pp.getCoordinate(line.fP2, onframe); + getFrameSvg() { return this.getPadPainter().getFrameSvg(); } - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - this.createv7AttLine(); + /** @summary Draw axes grids + * @desc Called immediately after axes drawing */ + drawGrids() { + const layer = this.getFrameSvg().selectChild('.axis_layer'); - this.draw_g - .append('svg:path') - .attr('d', `M${p1.x},${p1.y}L${p2.x},${p2.y}`) - .call(this.lineatt.func); -} + layer.selectAll('.xgrid').remove(); + layer.selectAll('.ygrid').remove(); -/** @summary draw RBox object - * @private */ -function drawBox() { - const box = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(box.fP1, onframe), - p2 = pp.getCoordinate(box.fP2, onframe); + const h = this.getFrameHeight(), + w = this.getFrameWidth(), + gridx = this.v7EvalAttr('gridX', false), + gridy = this.v7EvalAttr('gridY', false), + grid_style = getSvgLineStyle(gStyle.fGridStyle), + grid_color = (gStyle.fGridColor > 0) ? this.getColor(gStyle.fGridColor) : 'black'; - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + if (this.x_handle) + this.x_handle.draw_grid = gridx; - this.createv7AttLine('border_'); + // add a grid on x axis, if the option is set + if (this.x_handle?.draw_grid) { + let grid = ''; + for (let n = 0; n < this.x_handle.ticks.length; ++n) { + grid += this.#swap_xy + ? `M0,${h + this.x_handle.ticks[n]}h${w}` + : `M${this.x_handle.ticks[n]},0v${h}`; + } - this.createv7AttFill(); + if (grid) { + layer.append('svg:path') + .attr('class', 'xgrid') + .attr('d', grid) + .style('stroke', grid_color) + .style('stroke-width', gStyle.fGridWidth) + .style('stroke-dasharray', grid_style); + } + } - this.draw_g - .append('svg:path') - .attr('d', `M${p1.x},${p1.y}H${p2.x}V${p2.y}H${p1.x}Z`) - .call(this.lineatt.func) - .call(this.fillatt.func); -} + if (this.y_handle) + this.y_handle.draw_grid = gridy; -/** @summary draw RMarker object - * @private */ -function drawMarker() { - const marker = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(marker.fP, onframe); + // add a grid on y axis, if the option is set + if (this.y_handle?.draw_grid) { + let grid = ''; + for (let n = 0; n < this.y_handle.ticks.length; ++n) { + grid += this.#swap_xy + ? `M${this.y_handle.ticks[n]},0v${h}` + : `M0,${h + this.y_handle.ticks[n]}h${w}`; + } - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + if (grid) { + layer.append('svg:path') + .attr('class', 'ygrid') + .attr('d', grid) + .style('stroke', grid_color) + .style('stroke-width', gStyle.fGridWidth) + .style('stroke-dasharray', grid_style); + } + } + } - this.createv7AttMarker(); + /** @summary Converts 'raw' axis value into text */ + axisAsText(axis, value) { + const handle = this[`${axis}_handle`]; - const path = this.markeratt.create(p.x, p.y); + return handle ? handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']) : value.toPrecision(4); + } - if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } -} + /** @summary Set axis range */ + _setAxisRange(prefix, vmin, vmax) { + const nmin = `${prefix}min`, nmax = `${prefix}max`; + if (this[nmin] !== this[nmax]) + return; + let min = this.v7EvalAttr(`${prefix}_min`), + max = this.v7EvalAttr(`${prefix}_max`); -/** @summary painter for RPalette - * - * @private - */ + if (min !== undefined) + vmin = min; + if (max !== undefined) + vmax = max; -class RPalettePainter extends RObjectPainter { + if (vmin < vmax) { + this[nmin] = vmin; + this[nmax] = vmax; + } - /** @summary get palette */ - getHistPalette() { - const pal = this.getObject()?.fPalette; + const nzmin = `zoom_${prefix}min`, nzmax = `zoom_${prefix}max`; - if (pal && !isFunc(pal.getColor)) - addMethods(pal, `${nsREX}RPalette`); + if ((this[nzmin] === this[nzmax]) && !this.zoomChangedInteractive(prefix)) { + min = this.v7EvalAttr(`${prefix}_zoomMin`); + max = this.v7EvalAttr(`${prefix}_zoomMax`); - return pal; + if ((min !== undefined) || (max !== undefined)) { + this[nzmin] = (min === undefined) ? this[nmin] : min; + this[nzmax] = (max === undefined) ? this[nmax] : max; + } + } } - /** @summary Draw palette */ - drawPalette(drag) { - const palette = this.getHistPalette(), - contour = palette.getContour(), - framep = this.getFramePainter(); - - if (!contour) - return console.log('no contour - no palette'); + /** @summary Set axes ranges for drawing, check configured attributes if range already specified */ + setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax) { + if (this.#axes_drawn) + return; + this.xaxis = xaxis; + this._setAxisRange('x', xmin, xmax); + this.yaxis = yaxis; + this._setAxisRange('y', ymin, ymax); + this.zaxis = zaxis; + this._setAxisRange('z', zmin, zmax); + } - // frame painter must be there - if (!framep) - return console.log('no frame painter - no palette'); + /** @summary Set secondary axes ranges */ + setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { + if (second_x) { + this.x2axis = xaxis; + this._setAxisRange('x2', xmin, xmax); + } + if (second_y) { + this.y2axis = yaxis; + this._setAxisRange('y2', ymin, ymax); + } + } - const zmin = contour[0], - zmax = contour[contour.length-1], - rect = framep.getFrameRect(), - pad_width = this.getPadPainter().getPadWidth(), - pad_height = this.getPadPainter().getPadHeight(), - visible = this.v7EvalAttr('visible', true), - vertical = this.v7EvalAttr('vertical', true); - let gmin = palette.full_min, - gmax = palette.full_max, - palette_x, palette_y, palette_width, palette_height; + /** @summary Create x,y objects which maps user coordinates into pixels + * @desc Must be used only for v6 objects, see TFramePainter for more details + * @private */ + createXY(opts) { + if (this.self_drawaxes) + return; - if (drag) { - palette_width = drag.width; - palette_height = drag.height; + this.cleanXY(); // remove all previous configurations - const changes = {}; - if (vertical) { - this.v7AttrChange(changes, 'margin', (drag.x - rect.x - rect.width) / pad_width); - this.v7AttrChange(changes, 'width', palette_width / pad_width); - } else { - this.v7AttrChange(changes, 'margin', (drag.y - rect.y - rect.height) / pad_width); - this.v7AttrChange(changes, 'width', palette_height / pad_height); - } - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - } else { - if (vertical) { - const margin = this.v7EvalLength('margin', pad_width, 0.02); - palette_x = Math.round(rect.x + rect.width + margin); - palette_width = this.v7EvalLength('width', pad_width, 0.05); - palette_y = rect.y; - palette_height = rect.height; - } else { - const margin = this.v7EvalLength('margin', pad_height, 0.02); - palette_x = rect.x; - palette_width = rect.width; - palette_y = Math.round(rect.y + rect.height + margin); - palette_height = this.v7EvalLength('width', pad_height, 0.05); - } + if (!opts) + opts = { ndim: 1 }; - // x,y,width,height attributes used for drag functionality - makeTranslate(this.draw_g, palette_x, palette_y); - } + this.v6axes = true; + this.#swap_xy = opts.swap_xy || false; + this.#reverse_x = opts.reverse_x || false; + this.#reverse_y = opts.reverse_y || false; - let g_btns = this.draw_g.selectChild('.colbtns'); - if (g_btns.empty()) - g_btns = this.draw_g.append('svg:g').attr('class', 'colbtns'); - else - g_btns.selectAll('*').remove(); + this.logx = this.v7EvalAttr('x_log', 0); + this.logy = this.v7EvalAttr('y_log', 0); - if (!visible) return; + const w = this.getFrameWidth(), h = this.getFrameHeight(), pp = this.getPadPainter(); - g_btns.append('svg:path') - .attr('d', `M0,0H${palette_width}V${palette_height}H0Z`) - .style('stroke', 'black') - .style('fill', 'none'); + this.scales_ndim = opts.ndim; - if ((gmin === undefined) || (gmax === undefined)) { gmin = zmin; gmax = zmax; } + this.scale_xmin = this.xmin; + this.scale_xmax = this.xmax; - if (vertical) - framep.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, true, [palette_height, 0], -palette_height, { reverse: false }); - else - framep.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, false, [0, palette_width], palette_width, { reverse: false }); + this.scale_ymin = this.ymin; + this.scale_ymax = this.ymax; - for (let i = 0; i < contour.length-1; ++i) { - const z0 = Math.round(framep.z_handle.gr(contour[i])), - z1 = Math.round(framep.z_handle.gr(contour[i+1])), - col = palette.getContourColor((contour[i]+contour[i+1])/2), + if (opts.extra_y_space) { + const log_scale = this.#swap_xy ? this.logx : this.logy; + if (log_scale && (this.scale_ymax > 0)) + this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); + else + this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; + } - r = g_btns.append('svg:path') - .attr('d', vertical ? `M0,${z1}H${palette_width}V${z0}H0Z` : `M${z0},0V${palette_height}H${z1}V0Z`) - .style('fill', col) - .style('stroke', col) - .property('fill0', col) - .property('fill1', rgb(col).darker(0.5).toString()); + if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { + this.zoom_xmin = opts.zoom_xmin; + this.zoom_xmax = opts.zoom_xmax; + } - if (this.isTooltipAllowed()) { - r.on('mouseover', function() { - select(this).transition().duration(100).style('fill', select(this).property('fill1')); - }).on('mouseout', function() { - select(this).transition().duration(100).style('fill', select(this).property('fill0')); - }).append('svg:title').text(contour[i].toFixed(2) + ' - ' + contour[i+1].toFixed(2)); - } + if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { + this.zoom_ymin = opts.zoom_ymin; + this.zoom_ymax = opts.zoom_ymax; + } - if (settings.Zooming) - r.on('dblclick', () => framep.unzoom('z')); + if (this.zoom_xmin !== this.zoom_xmax) { + this.scale_xmin = this.zoom_xmin; + this.scale_xmax = this.zoom_xmax; } - framep.z_handle.maxTickSize = Math.round(palette_width*0.3); + if (this.zoom_ymin !== this.zoom_ymax) { + this.scale_ymin = this.zoom_ymin; + this.scale_ymax = this.zoom_ymax; + } - const promise = framep.z_handle.drawAxis(this.draw_g, makeTranslate(vertical ? palette_width : 0, palette_height), vertical ? -1 : 1); + let xaxis = this.xaxis, yaxis = this.yaxis; + if (xaxis?._typename !== clTAxis) + xaxis = create$1(clTAxis); + if (yaxis?._typename !== clTAxis) + yaxis = create$1(clTAxis); - if (this.isBatchMode() || drag) - return promise; + this.x_handle = new TAxisPainter(pp, xaxis, true); + this.x_handle.optionUnlab = this.v7EvalAttr('x_labels_hide', false); - return promise.then(() => { - if (settings.ContextMenu) { - this.draw_g.on('contextmenu', evnt => { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - createMenu(evnt, this).then(menu => { - menu.add('header:Palette'); - menu.addchk(vertical, 'Vertical', flag => { this.v7SetAttr('vertical', flag); this.redrawPad(); }); - framep.z_handle.fillAxisContextMenu(menu, 'z'); - menu.show(); - }); - }); - } + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x, + log: this.#swap_xy ? this.logy : this.logx, + symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: 0.0001 + }); - addDragHandler(this, { x: palette_x, y: palette_y, width: palette_width, height: palette_height, - minwidth: 20, minheight: 20, no_change_x: !vertical, no_change_y: vertical, redraw: d => this.drawPalette(d) }); + this.x_handle.assignFrameMembers(this, 'x'); - if (!settings.Zooming) return; + this.y_handle = new TAxisPainter(pp, yaxis, true); + this.y_handle.optionUnlab = this.v7EvalAttr('y_labels_hide', false); - let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect, zoom_rect_visible, moving_labels, last_pos; + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + reverse: this.#reverse_y, + log: this.#swap_xy ? this.logx : this.logy, + symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.ymax) ? 0.5 * opts.ymin_nz : 0, + logminfactor: 3e-4 + }); - const moveRectSel = evnt => { - if (!doing_zoom) return; - evnt.preventDefault(); + this.y_handle.assignFrameMembers(this, 'y'); + } - last_pos = pointer(evnt, this.draw_g.node()); + /** @summary Identify if requested axes are drawn + * @desc Checks if x/y axes are drawn. Also if second side is already there */ + hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : false; } - if (moving_labels) - return framep.z_handle.processLabelsMove('move', last_pos); + /** @summary Draw configured axes on the frame + * @desc axes can be drawn only for main histogram */ + async drawAxes() { + if (this.#axes_drawn || (this.xmin === this.xmax) || (this.ymin === this.ymax)) + return this.#axes_drawn; - if (vertical) - sel2 = Math.min(Math.max(last_pos[1], 0), palette_height); - else - sel2 = Math.min(Math.max(last_pos[0], 0), palette_width); + const ticksx = this.v7EvalAttr('ticksX', 1), + ticksy = this.v7EvalAttr('ticksY', 1), + sidex = this.v7EvalAttr('swapX', false) ? -1 : 1, + sidey = this.v7EvalAttr('swapY', false) ? -1 : 1, + w = this.getFrameWidth(), + h = this.getFrameHeight(), + pp = this.getPadPainter(); - const sz = Math.abs(sel2-sel1); + if (!this.v6axes) { + // this is partially same as v6 createXY method - if (!zoom_rect_visible && (sz > 1)) { - zoom_rect.style('display', null); - zoom_rect_visible = true; - } + this.cleanupAxes(); - if (vertical) - zoom_rect.attr('y', Math.min(sel1, sel2)).attr('height', sz); - else - zoom_rect.attr('x', Math.min(sel1, sel2)).attr('width', sz); - }, endRectSel = evnt => { - if (!doing_zoom) return; + this.#swap_xy = false; - evnt.preventDefault(); - select(window).on('mousemove.colzoomRect', null) - .on('mouseup.colzoomRect', null); - zoom_rect.remove(); - zoom_rect = null; - doing_zoom = false; + if (this.zoom_xmin !== this.zoom_xmax) { + this.scale_xmin = this.zoom_xmin; + this.scale_xmax = this.zoom_xmax; + } else { + this.scale_xmin = this.xmin; + this.scale_xmax = this.xmax; + } - if (moving_labels) - framep.z_handle.processLabelsMove('stop', last_pos); - else { - const z = framep.z_handle.func, z1 = z.invert(sel1), z2 = z.invert(sel2); - this.getFramePainter().zoom('z', Math.min(z1, z2), Math.max(z1, z2)); - } - }, startRectSel = evnt => { - // ignore when touch selection is activated - if (doing_zoom) return; - doing_zoom = true; + if (this.zoom_ymin !== this.zoom_ymax) { + this.scale_ymin = this.zoom_ymin; + this.scale_ymax = this.zoom_ymax; + } else { + this.scale_ymin = this.ymin; + this.scale_ymax = this.ymax; + } - evnt.preventDefault(); - evnt.stopPropagation(); + this.recalculateRange(0); - last_pos = pointer(evnt, this.draw_g.node()); - sel1 = sel2 = last_pos[vertical ? 1 : 0]; - zoom_rect_visible = false; - moving_labels = false; - zoom_rect = g_btns - .append('svg:rect') - .attr('class', 'zoom') - .attr('id', 'colzoomRect') - .style('display', 'none'); - if (vertical) - zoom_rect.attr('x', 0).attr('width', palette_width).attr('y', sel1).attr('height', 1); - else - zoom_rect.attr('x', sel1).attr('width', 1).attr('y', 0).attr('height', palette_height); + this.x_handle = new RAxisPainter(pp, this, this.xaxis, 'x_'); + this.x_handle.assignSnapId(this.getSnapId()); + this.x_handle.draw_swapside = (sidex < 0); + this.x_handle.draw_ticks = ticksx; - select(window).on('mousemove.colzoomRect', moveRectSel) - .on('mouseup.colzoomRect', endRectSel, true); + this.y_handle = new RAxisPainter(pp, this, this.yaxis, 'y_'); + this.y_handle.assignSnapId(this.getSnapId()); + this.y_handle.draw_swapside = (sidey < 0); + this.y_handle.draw_ticks = ticksy; - setTimeout(() => { - if (!zoom_rect_visible && doing_zoom) - moving_labels = framep.z_handle.processLabelsMove('start', last_pos); - }, 500); - }, assignHandlers = () => { - this.draw_g.selectAll('.axis_zoom, .axis_labels') - .on('mousedown', startRectSel) - .on('dblclick', () => framep.unzoom('z')); + this.z_handle = new RAxisPainter(pp, this, this.zaxis, 'z_'); + this.z_handle.assignSnapId(this.getSnapId()); - if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { - evnt.stopPropagation(); - evnt.preventDefault(); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, false, [0, w], w, { reverse: false }); + this.x_handle.assignFrameMembers(this, 'x'); - const pos = pointer(evnt, this.draw_g.node()), - coord = vertical ? (1 - pos[1] / palette_height) : pos[0] / palette_width, + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, true, [h, 0], -h, { reverse: false }); + this.y_handle.assignFrameMembers(this, 'y'); - item = framep.z_handle.analyzeWheelEvent(evnt, coord); - if (item.changed) - framep.zoom('z', item.min, item.max); - }); - } - }; + // only get basic properties like log scale + this.z_handle.configureZAxis('zaxis', this); + } - framep.z_handle.setAfterDrawHandler(assignHandlers); + const layer = this.getFrameSvg().selectChild('.axis_layer'); - assignHandlers(); - }); - } + this.x_handle.has_obstacle = false; - /** @summary draw RPalette object */ - static async draw(dom, palette, opt) { - const painter = new RPalettePainter(dom, palette, opt, 'palette'); - return ensureRCanvas(painter, false).then(() => { - painter.createG(); // just create container, real drawing will be done by histogram - return painter; - }); - } + const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, + draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; + let pr; -} // class RPalettePainter + if (this.getPadPainter()?.isFastDrawing()) + pr = Promise.resolve(true); // do nothing + else if (this.v6axes) { + // in v7 ticksx/y values shifted by 1 relative to v6 + // In v7 ticksx === 0 means no ticks, ticksx === 1 equivalent to === 0 in v6 -var v7more = /*#__PURE__*/Object.freeze({ -__proto__: null, -RPalettePainter: RPalettePainter, -drawBox: drawBox, -drawLine: drawLine, -drawMarker: drawMarker, -drawText: drawText -}); + const can_adjust_frame = false, disable_x_draw = false, disable_y_draw = false; -const ECorner = { kTopLeft: 1, kTopRight: 2, kBottomLeft: 3, kBottomRight: 4 }; + draw_horiz.disable_ticks = (ticksx <= 0); + draw_vertical.disable_ticks = (ticksy <= 0); -/** - * @summary Painter for RPave class - * - * @private - */ + const pr1 = draw_horiz.drawAxis(layer, w, h, + draw_horiz.invert_side ? null : `translate(0,${h})`, + (ticksx > 1) ? -h : 0, disable_x_draw, + undefined, false, this.getPadPainter().getPadHeight() - h - this.getFrameY()), -class RPavePainter extends RObjectPainter { + pr2 = draw_vertical.drawAxis(layer, w, h, + draw_vertical.invert_side ? `translate(${w})` : null, + (ticksy > 1) ? w : 0, disable_y_draw, + draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); - /** @summary Draw pave content - * @desc assigned depending on pave class */ - async drawContent() { return this; } + pr = Promise.all([pr1, pr2]).then(() => this.drawGrids()); + } else { + let arr = []; - /** @summary Draw pave */ - async drawPave() { - const rect = this.getPadPainter().getPadRect(), - fp = this.getFramePainter(); + if (ticksx > 0) + arr.push(draw_horiz.drawAxis(layer, makeTranslate(0, sidex > 0 ? h : 0), sidex)); - this.onFrame = fp && this.v7EvalAttr('onFrame', true); - this.corner = this.v7EvalAttr('corner', ECorner.kTopRight); + if (ticksy > 0) + arr.push(draw_vertical.drawAxis(layer, makeTranslate(sidey > 0 ? 0 : w, h), sidey)); - const visible = this.v7EvalAttr('visible', true), - offsetx = this.v7EvalLength('offsetX', rect.width, 0.02), - offsety = this.v7EvalLength('offsetY', rect.height, 0.02), - pave_width = this.v7EvalLength('width', rect.width, 0.3), - pave_height = this.v7EvalLength('height', rect.height, 0.3); + pr = Promise.all(arr).then(() => { + arr = []; + if (ticksx > 1) + arr.push(draw_horiz.drawAxisOtherPlace(layer, makeTranslate(0, sidex < 0 ? h : 0), -sidex, ticksx === 2)); - this.createG(); + if (ticksy > 1) + arr.push(draw_vertical.drawAxisOtherPlace(layer, makeTranslate(sidey < 0 ? 0 : w, h), -sidey, ticksy === 2)); + return Promise.all(arr); + }).then(() => this.drawGrids()); + } - this.draw_g.classed('most_upper_primitives', true); // this primitive will remain on top of list + return pr.then(() => { + this.#axes_drawn = true; + return true; + }); + } - if (!visible) - return this; + /** @summary Draw secondary configured axes */ + drawAxes2(second_x, second_y) { + const w = this.getFrameWidth(), h = this.getFrameHeight(), + pp = this.getPadPainter(), + layer = this.getFrameSvg().selectChild('.axis_layer'); + let pr1, pr2; - this.createv7AttLine('border_'); + if (second_x) { + if (this.zoom_x2min !== this.zoom_x2max) { + this.scale_x2min = this.zoom_x2min; + this.scale_x2max = this.zoom_x2max; + } else { + this.scale_x2min = this.x2min; + this.scale_x2max = this.x2max; + } + this.x2_handle = new RAxisPainter(pp, this, this.x2axis, 'x2_'); + this.x2_handle.assignSnapId(this.getSnapId()); - this.createv7AttFill(); + this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, false, [0, w], w, { reverse: false }); + this.x2_handle.assignFrameMembers(this, 'x2'); - const fr = this.onFrame ? fp.getFrameRect() : rect; - let pave_x = 0, pave_y = 0; - switch (this.corner) { - case ECorner.kTopLeft: - pave_x = fr.x + offsetx; - pave_y = fr.y + offsety; - break; - case ECorner.kBottomLeft: - pave_x = fr.x + offsetx; - pave_y = fr.y + fr.height - offsety - pave_height; - break; - case ECorner.kBottomRight: - pave_x = fr.x + fr.width - offsetx - pave_width; - pave_y = fr.y + fr.height - offsety - pave_height; - break; - case ECorner.kTopRight: - default: - pave_x = fr.x + fr.width - offsetx - pave_width; - pave_y = fr.y + offsety; + pr1 = this.x2_handle.drawAxis(layer, null, -1); } - makeTranslate(this.draw_g, pave_x, pave_y); - - this.draw_g.append('svg:rect') - .attr('x', 0) - .attr('width', pave_width) - .attr('y', 0) - .attr('height', pave_height) - .call(this.lineatt.func) - .call(this.fillatt.func); + if (second_y) { + if (this.zoom_y2min !== this.zoom_y2max) { + this.scale_y2min = this.zoom_y2min; + this.scale_y2max = this.zoom_y2max; + } else { + this.scale_y2min = this.y2min; + this.scale_y2max = this.y2max; + } - this.pave_width = pave_width; - this.pave_height = pave_height; + this.y2_handle = new RAxisPainter(pp, this, this.y2axis, 'y2_'); + this.y2_handle.assignSnapId(this.getSnapId()); - // here should be fill and draw of text + this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, true, [h, 0], -h, { reverse: false }); + this.y2_handle.assignFrameMembers(this, 'y2'); - return this.drawContent().then(() => { - if (!this.isBatchMode()) { - // TODO: provide pave context menu as in v6 - if (settings.ContextMenu && this.paveContextMenu) - this.draw_g.on('contextmenu', evnt => this.paveContextMenu(evnt)); + pr2 = this.y2_handle.drawAxis(layer, makeTranslate(w, h), -1); + } - addDragHandler(this, { x: pave_x, y: pave_y, width: pave_width, height: pave_height, - minwidth: 20, minheight: 20, redraw: d => this.sizeChanged(d) }); - } + return Promise.all([pr1, pr2]); + } + /** @summary Return functions to create x/y points based on coordinates + * @desc In default case returns frame painter itself + * @private */ + getGrFuncs(second_x, second_y) { + const use_x2 = second_x && this.grx2, + use_y2 = second_y && this.gry2; + if (!use_x2 && !use_y2) return this; - }); - } - /** @summary Process interactive moving of the stats box */ - sizeChanged(drag) { - this.pave_width = drag.width; - this.pave_height = drag.height; + return { + use_x2, + grx: use_x2 ? this.grx2 : this.grx, + x_handle: use_x2 ? this.x2_handle : this.x_handle, + logx: use_x2 ? this.x2_handle.log : this.x_handle.log, + scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, + scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, + use_y2, + gry: use_y2 ? this.gry2 : this.gry, + y_handle: use_y2 ? this.y2_handle : this.y_handle, + logy: use_y2 ? this.y2_handle.log : this.y_handle.log, + scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, + scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, + fp: this, + swap_xy() { return this.fp.swap_xy(); }, + revertAxis(name, v) { + if ((name === 'x') && this.use_x2) + name = 'x2'; + if ((name === 'y') && this.use_y2) + name = 'y2'; + return this.fp.revertAxis(name, v); + }, + axisAsText(name, v) { + if ((name === 'x') && this.use_x2) + name = 'x2'; + if ((name === 'y') && this.use_y2) + name = 'y2'; + return this.fp.axisAsText(name, v); + } + }; + } - const pave_x = drag.x, - pave_y = drag.y, - rect = this.getPadPainter().getPadRect(), - fr = this.onFrame ? this.getFramePainter().getFrameRect() : rect, - changes = {}; - let offsetx = 0, offsety = 0; + /** @summary function called at the end of resize of frame + * @desc Used to update attributes on the server + * @private */ + sizeChanged() { + const changes = {}; + this.v7AttrChange(changes, 'margins_left', this.fX1NDC); + this.v7AttrChange(changes, 'margins_bottom', this.fY1NDC); + this.v7AttrChange(changes, 'margins_right', 1 - this.fX2NDC); + this.v7AttrChange(changes, 'margins_top', 1 - this.fY2NDC); + this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - switch (this.corner) { - case ECorner.kTopLeft: - offsetx = pave_x - fr.x; - offsety = pave_y - fr.y; - break; - case ECorner.kBottomLeft: - offsetx = pave_x - fr.x; - offsety = fr.y + fr.height - pave_y - this.pave_height; - break; - case ECorner.kBottomRight: - offsetx = fr.x + fr.width - pave_x - this.pave_width; - offsety = fr.y + fr.height - pave_y - this.pave_height; - break; - case ECorner.kTopRight: - default: - offsetx = fr.x + fr.width - pave_x - this.pave_width; - offsety = pave_y - fr.y; - } + this.redrawPad(); + } - this.v7AttrChange(changes, 'offsetX', offsetx / rect.width); - this.v7AttrChange(changes, 'offsetY', offsety / rect.height); - this.v7AttrChange(changes, 'width', this.pave_width / rect.width); - this.v7AttrChange(changes, 'height', this.pave_height / rect.height); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server + /** @summary Remove all x/y functions + * @private */ + cleanXY() { + // remove all axes drawings + const clean = (name, grname) => { + this[name]?.cleanup(); + delete this[name]; + delete this[grname]; + }; - this.draw_g.selectChild('rect') - .attr('width', this.pave_width) - .attr('height', this.pave_height); + clean('x_handle', 'grx'); + clean('y_handle', 'gry'); + clean('z_handle', 'grz'); + clean('x2_handle', 'grx2'); + clean('y2_handle', 'gry2'); - this.drawContent(); + delete this.v6axes; // marker that v6 axes are used } - /** @summary Redraw RPave object */ - async redraw(/* reason */) { - return this.drawPave(); - } + /** @summary Remove all axes drawings + * @private */ + cleanupAxes() { + this.cleanXY(); - /** @summary draw RPave object */ - static async draw(dom, pave, opt) { - const painter = new RPavePainter(dom, pave, opt, 'pave'); - return ensureRCanvas(painter, false).then(() => painter.drawPave()); + this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); + this.#axes_drawn = false; } -} - - -/** - * @summary Painter for RLegend class - * - * @private - */ + /** @summary Removes all drawn elements of the frame + * @private */ + cleanFrameDrawings() { + // cleanup all 3D drawings if any + if (isFunc(this.create3DScene)) + this.create3DScene(-1); -class RLegendPainter extends RPavePainter { + this.cleanupAxes(); - /** @summary draw RLegend content */ - async drawContent() { - const legend = this.getObject(), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), - width = this.pave_width, - height = this.pave_height, - pp = this.getPadPainter(); + const clean = (name) => { + this[name + 'min'] = this[name + 'max'] = 0; + this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; + this[`scale_${name}min`] = this[`scale_${name}max`] = 0; + }; - let nlines = legend.fEntries.length; - if (legend.fTitle) nlines++; + clean('x'); + clean('y'); + clean('z'); + clean('x2'); + clean('y2'); - if (!nlines || !pp) return this; + this.getG()?.selectChild('.main_layer').selectAll('*').remove(); + this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); + } - const stepy = height / nlines, margin_x = 0.02 * width; - let posy = 0; + /** @summary Fully cleanup frame + * @private */ + cleanup() { + this.cleanFrameDrawings(); - textFont.setSize(height/(nlines * 1.2)); - this.startTextDrawing(textFont, 'font'); + this.getG()?.selectAll('*').remove(); + this.getG()?.on('mousedown', null) + .on('dblclick', null) + .on('wheel', null) + .on('contextmenu', null) + .property('interactive_set', null); - if (legend.fTitle) { - this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: legend.fTitle }); - posy += stepy; + if (this.#keys_handler) { + window.removeEventListener('keydown', this.#keys_handler, false); + this.#keys_handler = undefined; } + this.#enabled_keys = undefined; + delete this.self_drawaxes; - for (let i = 0; i < legend.fEntries.length; ++i) { - const entry = legend.fEntries[i], w4 = Math.round(width/4); - let objp = null; + delete this.xaxis; + delete this.yaxis; + delete this.zaxis; + delete this.x2axis; + delete this.y2axis; - this.drawText({ latex: 1, width: 0.75*width - 3*margin_x, height: stepy, x: 2*margin_x + w4, y: posy, text: entry.fLabel }); + this.setG(undefined); // frame element managed by the pad - if (entry.fDrawableId !== 'custom') - objp = pp.findSnap(entry.fDrawableId, true); - else if (entry.fDrawable.fIO) { - objp = new RObjectPainter(this.getDom(), entry.fDrawable.fIO); - if (entry.fLine) objp.createv7AttLine(); - if (entry.fFill) objp.createv7AttFill(); - if (entry.fMarker) objp.createv7AttMarker(); - } + this.#click_handler = undefined; + this.#dblclick_handler = undefined; - if (entry.fFill && objp?.fillatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy*0.1)}h${w4}v${Math.round(stepy*0.8)}h${-w4}z`) - .call(objp.fillatt.func); - } + this.getPadPainter()?.setFramePainter(this, false); - if (entry.fLine && objp?.lineatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy/2)}h${w4}`) - .call(objp.lineatt.func); - } + super.cleanup(); + } - if (entry.fError && objp?.lineatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x + width/8)},${Math.round(posy + stepy*0.2)}v${Math.round(stepy*0.6)}`) - .call(objp.lineatt.func); - } + /** @summary Redraw frame + * @private */ + redraw() { + const pp = this.getPadPainter(); + pp?.setFramePainter(this, true); - if (entry.fMarker && objp?.markeratt) { - this.draw_g.append('svg:path') - .attr('d', objp.markeratt.create(margin_x + width/8, posy + stepy/2)) - .call(objp.markeratt.func); - } + // first update all attributes from objects + this.updateAttributes(); - posy += stepy; - } + const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, + lm = Math.round(rect.width * this.fX1NDC), + tm = Math.round(rect.height * (1 - this.fY2NDC)), + rotate = pp?.options?.RotateFrame, + w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), + h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); - return this.finishTextDrawing(); - } + // update values here to let access even when frame is not really updated + this.#frame_x = lm; + this.#frame_y = tm; + this.#frame_width = rotate ? h : w; + this.#frame_height = rotate ? w : h; + this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); + this.$can_drag = !rotate && !pp?.options?.FixFrame; - /** @summary draw RLegend object */ - static async draw(dom, legend, opt) { - const painter = new RLegendPainter(dom, legend, opt, 'legend'); - return ensureRCanvas(painter, false).then(() => painter.drawPave()); + return this.mode3d ? this : this.createFrameG(); } -} // class RLegendPainter + /** @summary Create frame element and update all attributes + * @private */ + createFrameG() { + // this is svg:g object - container for every other items belonging to frame + let g = this.setG(this.getFrameSvg()), + top_rect, main_svg; + if (g.empty()) { + g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); -/** - * @summary Painter for RPaveText class - * - * @private - */ + if (!this.isBatchMode()) + g.append('svg:title').text(''); -class RPaveTextPainter extends RPavePainter { + top_rect = g.append('svg:rect'); - /** @summary draw RPaveText content */ - drawContent() { - const pavetext = this.getObject(), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), - width = this.pave_width, - height = this.pave_height, - nlines = pavetext.fText.length; + main_svg = g.append('svg:svg') + .attr('class', 'main_layer') + .attr('x', 0) + .attr('y', 0) + .attr('overflow', 'hidden'); - if (!nlines) return; + g.append('svg:g').attr('class', 'axis_layer'); + g.append('svg:g').attr('class', 'upper_layer'); + } else { + top_rect = g.selectChild('rect'); + main_svg = g.selectChild('.main_layer'); + } - const stepy = height / nlines, margin_x = 0.02 * width; - let posy = 0; + this.#axes_drawn = false; - textFont.setSize(height/(nlines * 1.2)); + g.attr('transform', this.#frame_trans); - this.startTextDrawing(textFont, 'font'); + top_rect.attr('x', 0) + .attr('y', 0) + .attr('width', this.#frame_width) + .attr('height', this.#frame_height) + .attr('rx', this.lineatt.rx || null) + .attr('ry', this.lineatt.ry || null) + .call(this.fillatt.func) + .call(this.lineatt.func); - for (let i = 0; i < pavetext.fText.length; ++i) { - const line = pavetext.fText[i]; + main_svg.attr('width', this.#frame_width) + .attr('height', this.#frame_height) + .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); - this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: line }); - posy += stepy; - } + let pr = Promise.resolve(true); - return this.finishTextDrawing(undefined, true); - } + if (this.v7EvalAttr('drawAxes')) { + this.self_drawaxes = true; + this.setAxesRanges(); + pr = this.drawAxes().then(() => this.addInteractivity()); + } - /** @summary draw RPaveText object */ - static async draw(dom, pave, opt) { - const painter = new RPaveTextPainter(dom, pave, opt, 'pavetext'); - return ensureRCanvas(painter, false).then(() => painter.drawPave()); + return pr.then(() => { return this; }); } -} // class RPaveTextPainter + /** @summary Returns frame X position */ + getFrameX() { return this.#frame_x || 0; } -/** - * @summary Painter for RHistStats class - * - * @private - */ + /** @summary Returns frame Y position */ + getFrameY() { return this.#frame_y || 0; } -class RHistStatsPainter extends RPavePainter { + /** @summary Returns frame width */ + getFrameWidth() { return this.#frame_width || 0; } - /** @summary clear entries from stat box */ - clearStat() { - this.stats_lines = []; - } + /** @summary Returns frame height */ + getFrameHeight() { return this.#frame_height || 0; } - /** @summary add text entry to stat box */ - addText(line) { - this.stats_lines.push(line); + /** @summary Returns frame rectangle plus extra info for hint display */ + getFrameRect() { + return { + x: this.#frame_x || 0, + y: this.#frame_y || 0, + width: this.getFrameWidth(), + height: this.getFrameHeight(), + transform: this.getG()?.attr('transform') || '', + hint_delta_x: 0, + hint_delta_y: 0 + }; } - /** @summary update statistic from the server */ - updateStatistic(reply) { - this.stats_lines = reply.lines; - this.drawStatistic(this.stats_lines); + /** @summary Returns palette associated with frame */ + getHistPalette() { + return this.getPadPainter().getHistPalette(); } - /** @summary fill statistic */ - fillStatistic() { - const pp = this.getPadPainter(); - if (pp?._fast_drawing) return false; - - const obj = this.getObject(); - if (obj.fLines !== undefined) { - this.stats_lines = obj.fLines; - delete obj.fLines; - return true; - } - - if (this.v7OfflineMode()) { - const main = this.getMainPainter(); - if (!isFunc(main?.fillStatistic)) return false; - // we take statistic from main painter - return main.fillStatistic(this, gStyle.fOptStat, gStyle.fOptFit); - } - - // show lines which are exists, maybe server request will be recieved later - return (this.stats_lines !== undefined); + /** @summary Configure user-defined click handler + * @desc Function will be called every time when frame click was performed + * As argument, tooltip object with selected bins will be provided + * If handler function returns true, default handling of click will be disabled */ + configureUserClickHandler(handler) { + this.#click_handler = isFunc(handler) ? handler : null; } - /** @summary format float value as string - * @private */ - format(value, fmt) { - if (!fmt) fmt = 'stat'; - - switch (fmt) { - case 'stat' : fmt = gStyle.fStatFormat; break; - case 'fit': fmt = gStyle.fFitFormat; break; - case 'entries': if ((Math.abs(value) < 1e9) && (Math.round(value) === value)) return value.toFixed(0); fmt = '14.7g'; break; - case 'last': fmt = this.lastformat; break; - } - - const res = floatToString(value, fmt || '6.4g', true); - - this.lastformat = res[1]; + /** @summary Returns actual click handler */ + getClickHandler() { return this.#click_handler; } - return res[0]; + /** @summary Configure user-defined dblclick handler + * @desc Function will be called every time when double click was called + * As argument, tooltip object with selected bins will be provided + * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ + configureUserDblclickHandler(handler) { + this.#dblclick_handler = isFunc(handler) ? handler : null; } - /** @summary Draw content */ - async drawContent() { - if (this.fillStatistic()) - return this.drawStatistic(this.stats_lines); + /** @summary Returns actual double-click handler */ + getDblclickHandler() { return this.#dblclick_handler; } - return this; - } + /** @summary function can be used for zooming into specified range + * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed + * @return {Promise} with boolean flag if zoom operation was performed */ + async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { + // disable zooming when axis conversion is enabled + if (this.#projection) + return false; - /** @summary Change mask */ - changeMask(nbit) { - const obj = this.getObject(), mask = (1<= this.xmax) { + xmax = this.xmax; + cnt++; + } + if (cnt === 2) { + zoom_x = false; + unzoom_x = true; + } + } else + unzoom_x = (xmin === xmax) && (xmin === 0); - createMenu(evnt, this).then(menu => { - const obj = this.getObject(), - action = this.changeMask.bind(this); + if (zoom_y) { + let cnt = 0; + if (ymin <= this.ymin) { + ymin = this.ymin; + cnt++; + } + if (ymax >= this.ymax) { + ymax = this.ymax; + cnt++; + } + if (cnt === 2) { + zoom_y = false; + unzoom_y = true; + } + } else + unzoom_y = (ymin === ymax) && (ymin === 0); - menu.add('header: StatBox'); + if (zoom_z) { + let cnt = 0; + if (zmin <= this.zmin) { + zmin = this.zmin; + cnt++; + } + if (zmax >= this.zmax) { + zmax = this.zmax; + cnt++; + } + if (cnt === 2) { + zoom_z = false; + unzoom_z = true; + } + } else + unzoom_z = (zmin === zmax) && (zmin === 0); - for (let n=0; n { + if (!force && !isFunc(painter.canZoomInside)) + return; - return this.fillObjectExecMenu(menu); - }).then(menu => menu.show()); - } + is_any_check = true; - /** @summary Draw statistic */ - async drawStatistic(lines) { - if (!lines) return this; - const textFont = this.v7EvalFont('stats_text', { size: 12, color: 'black', align: 22 }), - width = this.pave_width, - height = this.pave_height, - nlines = lines.length; - let first_stat = 0, num_cols = 0, maxlen = 0; + if (zoom_x && (force || painter.canZoomInside('x', xmin, xmax))) { + this.zoom_xmin = xmin; + this.zoom_xmax = xmax; + changed = true; + r_x = '0'; + zoom_x = false; + req.values[0] = xmin; + req.values[1] = xmax; + req.flags[0] = req.flags[1] = true; + if (interactive) + this.zoomChangedInteractive('x', interactive); + } + if (zoom_y && (force || painter.canZoomInside('y', ymin, ymax))) { + this.zoom_ymin = ymin; + this.zoom_ymax = ymax; + changed = true; + r_y = '1'; + zoom_y = false; + req.values[2] = ymin; + req.values[3] = ymax; + req.flags[2] = req.flags[3] = true; + if (interactive) + this.zoomChangedInteractive('y', interactive); + } + if (zoom_z && (force || painter.canZoomInside('z', zmin, zmax))) { + this.zoom_zmin = zmin; + this.zoom_zmax = zmax; + changed = true; + r_z = '2'; + zoom_z = false; + req.values[4] = zmin; + req.values[5] = zmax; + req.flags[4] = req.flags[5] = true; + if (interactive) + this.zoomChangedInteractive('z', interactive); + } + }; - // adjust font size - for (let j = 0; j < nlines; ++j) { - const line = lines[j]; - if (j > 0) maxlen = Math.max(maxlen, line.length); - if ((j === 0) || (line.indexOf('|') < 0)) continue; - if (first_stat === 0) first_stat = j; - const parts = line.split('|'); - if (parts.length > num_cols) - num_cols = parts.length; - } + // first process zooming (if any) + if (zoom_x || zoom_y || zoom_z) + this.forEachPainter(painter => checkZooming(painter)); - // for characters like 'p' or 'y' several more pixels required to stay in the box when drawn in last line - const stepy = height / nlines, margin_x = 0.02 * width; - let has_head = false, - text_g = this.draw_g.selectChild('.statlines'); - if (text_g.empty()) - text_g = this.draw_g.append('svg:g').attr('class', 'statlines'); - else - text_g.selectAll('*').remove(); - - textFont.setSize(height/(nlines * 1.2)); - this.startTextDrawing(textFont, 'font', text_g); - - if (nlines === 1) - this.drawText({ width, height, text: lines[0], latex: 1, draw_g: text_g }); - else { - for (let j = 0; j < nlines; ++j) { - const posy = j*stepy; - - if (first_stat && (j >= first_stat)) { - const parts = lines[j].split('|'); - for (let n = 0; n < parts.length; ++n) { - this.drawText({ align: 'middle', x: width * n / num_cols, y: posy, latex: 0, - width: width/num_cols, height: stepy, text: parts[n], draw_g: text_g }); - } - } else if (lines[j].indexOf('=') < 0) { - if (j === 0) { - has_head = true; - const max_hlen = Math.max(maxlen, Math.round((width-2*margin_x)/stepy/0.65)); - if (lines[j].length > max_hlen + 5) - lines[j] = lines[j].slice(0, max_hlen+2) + '...'; - } - this.drawText({ align: (j === 0) ? 'middle' : 'start', x: margin_x, y: posy, - width: width - 2*margin_x, height: stepy, text: lines[j], draw_g: text_g }); - } else { - const parts = lines[j].split('='), args = []; - - for (let n = 0; n < 2; ++n) { - const arg = { - align: (n === 0) ? 'start' : 'end', x: margin_x, y: posy, - width: width-2*margin_x, height: stepy, text: parts[n], draw_g: text_g, - _expected_width: width-2*margin_x, _args: args, - post_process(painter) { - if (this._args[0].ready && this._args[1].ready) - painter.scaleTextDrawing(1.05*(this._args[0].result_width && this._args[1].result_width)/this.__expected_width, this.draw_g); - } - }; - args.push(arg); - } + // force zooming when no any other painter can verify zoom range + if (!is_any_check && this.self_drawaxes) + checkZooming(null, true); - for (let n = 0; n < 2; ++n) - this.drawText(args[n]); + // and process unzoom, if any + if (unzoom_x || unzoom_y || unzoom_z) { + if (unzoom_x) { + if (this.zoom_xmin !== this.zoom_xmax) { + changed = true; + r_x = '0'; + } + this.zoom_xmin = this.zoom_xmax = 0; + req.values[0] = req.values[1] = -1; + if (interactive) + this.zoomChangedInteractive('x', interactive); + } + if (unzoom_y) { + if (this.zoom_ymin !== this.zoom_ymax) { + changed = true; + r_y = '1'; + } + this.zoom_ymin = this.zoom_ymax = 0; + req.values[2] = req.values[3] = -1; + if (interactive) + this.zoomChangedInteractive('y', interactive); + } + if (unzoom_z) { + if (this.zoom_zmin !== this.zoom_zmax) { + changed = true; + r_z = '2'; } + this.zoom_zmin = this.zoom_zmax = 0; + req.values[4] = req.values[5] = -1; + if (interactive) + this.zoomChangedInteractive('z', interactive); } } - let lpath = ''; - - if (has_head) - lpath += 'M0,' + Math.round(stepy) + 'h' + width; - - if ((first_stat > 0) && (num_cols > 1)) { - for (let nrow = first_stat; nrow < nlines; ++nrow) - lpath += 'M0,' + Math.round(nrow * stepy) + 'h' + width; - for (let ncol = 0; ncol < num_cols - 1; ++ncol) - lpath += 'M' + Math.round(width / num_cols * (ncol + 1)) + ',' + Math.round(first_stat * stepy) + 'V' + height; - } + if (!changed) + return false; - if (lpath) this.draw_g.append('svg:path').attr('d', lpath); + if (this.v7NormalMode()) + this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); - return this.finishTextDrawing(text_g); + return this.interactiveRedraw('pad', 'zoom' + r_x + r_y + r_z).then(() => true); } - /** @summary Redraw stats box */ - async redraw(reason) { - if (reason && isStr(reason) && (reason.indexOf('zoom') === 0) && this.v7NormalMode()) { - const req = { - _typename: `${nsREX}RHistStatBoxBase::RRequest`, - mask: this.getObject().fShowMask // lines to show in stat box - }; - - this.v7SubmitRequest('stat', req, reply => this.updateStatistic(reply)); - } + /** @summary Zooming of single axis + * @param {String} name - axis name like x/y/z but also second axis x2 or y2 + * @param {Number} vmin - axis minimal value, 0 for unzoom + * @param {Number} vmax - axis maximal value, 0 for unzoom + * @param {Boolean} [interactive] - if change was performed interactively + * @protected */ + async zoomSingle(name, vmin, vmax, interactive) { + const names = ['x', 'y', 'z', 'x2', 'y2'], indx = names.indexOf(name); - return this.drawPave(); - } + // disable zooming when axis conversion is enabled + if (this.#projection || (!this[`${name}_handle`] && (name !== 'z')) || (indx < 0)) + return false; - /** @summary draw RHistStats object */ - static async draw(dom, stats, opt) { - const painter = new RHistStatsPainter(dom, stats, opt, stats); - return ensureRCanvas(painter, false).then(() => painter.drawPave()); - } + let zoom_v = (vmin !== vmax), unzoom_v = false; -} // class RHistStatsPainter + if (zoom_v) { + let cnt = 0; + if (vmin <= this[name + 'min']) { + vmin = this[name + 'min']; + cnt++; + } + if (vmax >= this[name + 'max']) { + vmax = this[name + 'max']; + cnt++; + } + if (cnt === 2) { + zoom_v = false; + unzoom_v = true; + } + } else + unzoom_v = (vmin === vmax) && (vmin === 0); -var RPavePainter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -RHistStatsPainter: RHistStatsPainter, -RLegendPainter: RLegendPainter, -RPavePainter: RPavePainter, -RPaveTextPainter: RPaveTextPainter -}); -/** @summary assign methods for the RAxis objects - * @private */ -function assignRAxisMethods(axis) { - if ((axis._typename === `${nsREX}RAxisEquidistant`) || (axis._typename === `${nsREX}RAxisLabels`)) { - if (axis.fInvBinWidth === 0) { - axis.$dummy = true; - axis.fInvBinWidth = 1; - axis.fNBinsNoOver = 0; - axis.fLow = 0; - } - - axis.min = axis.fLow; - axis.max = axis.fLow + axis.fNBinsNoOver/axis.fInvBinWidth; - axis.GetNumBins = function() { return this.fNBinsNoOver; }; - axis.GetBinCoord = function(bin) { return this.fLow + bin/this.fInvBinWidth; }; - axis.FindBin = function(x, add) { return Math.floor((x - this.fLow)*this.fInvBinWidth + add); }; - } else if (axis._typename === `${nsREX}RAxisIrregular`) { - axis.min = axis.fBinBorders[0]; - axis.max = axis.fBinBorders[axis.fBinBorders.length - 1]; - axis.GetNumBins = function() { return this.fBinBorders.length; }; - axis.GetBinCoord = function(bin) { - const indx = Math.round(bin); - if (indx <= 0) return this.fBinBorders[0]; - if (indx >= this.fBinBorders.length) return this.fBinBorders[this.fBinBorders.length - 1]; - if (indx === bin) return this.fBinBorders[indx]; - const indx2 = (bin < indx) ? indx - 1 : indx + 1; - return this.fBinBorders[indx] * Math.abs(bin-indx2) + this.fBinBorders[indx2] * Math.abs(bin-indx); - }; - axis.FindBin = function(x, add) { - for (let k = 1; k < this.fBinBorders.length; ++k) - if (x < this.fBinBorders[k]) return Math.floor(k-1+add); - return this.fBinBorders.length - 1; + let changed = false, is_any_check = false; + const req = { + _typename: `${nsREX}RFrame::RUserRanges`, + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + flags: [false, false, false, false, false, false, false, false, false, false] }; - } - - // to support some code from ROOT6 drawing - - axis.GetBinCenter = function(bin) { return this.GetBinCoord(bin-0.5); }; - axis.GetBinLowEdge = function(bin) { return this.GetBinCoord(bin-1); }; -} - -/** @summary Returns real histogram impl - * @private */ -function getHImpl(obj) { - return obj?.fHistImpl?.fIO || null; -} - - -/** @summary Base painter class for RHist objects - * - * @private - */ - -class RHistPainter extends RObjectPainter { - - /** @summary Constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} histo - RHist object */ - constructor(dom, histo) { - super(dom, histo); - this.csstype = 'hist'; - this.draw_content = true; - this.nbinsx = 0; - this.nbinsy = 0; - this.accept_drops = true; // indicate that one can drop other objects like doing Draw('same') - this.mode3d = false; - // initialize histogram methods - this.getHisto(true); - } + // eslint-disable-next-line one-var + const checkZooming = (painter, force) => { + if (!force && !isFunc(painter?.canZoomInside)) + return; - /** @summary Returns true if RHistDisplayItem is used */ - isDisplayItem() { - return this.getObject()?.fAxes; - } + is_any_check = true; - /** @summary get histogram */ - getHisto(force) { - const obj = this.getObject(); - let histo = getHImpl(obj); - - if (histo && (!histo.getBinContent || force)) { - if (histo.fAxes._2) { - assignRAxisMethods(histo.fAxes._0); - assignRAxisMethods(histo.fAxes._1); - assignRAxisMethods(histo.fAxes._2); - histo.getBin = function(x, y, z) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1) + this.fAxes._0.GetNumBins()*this.fAxes._1.GetNumBins()*(z-1); }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x, y, z) { return this.fStatistics.fBinContent[this.getBin(x, y, z)]; }; - histo.getBinError = function(x, y, z) { - const bin = this.getBin(x, y, z); - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); - }; - } else if (histo.fAxes._1) { - assignRAxisMethods(histo.fAxes._0); - assignRAxisMethods(histo.fAxes._1); - histo.getBin = function(x, y) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1); }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x, y) { return this.fStatistics.fBinContent[this.getBin(x, y)]; }; - histo.getBinError = function(x, y) { - const bin = this.getBin(x, y); - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); - }; - } else { - assignRAxisMethods(histo.fAxes._0); - histo.getBin = function(x) { return x-1; }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x) { return this.fStatistics.fBinContent[x-1]; }; - histo.getBinError = function(x) { - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[x-1]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[x-1])); - }; + if (zoom_v && (force || painter.canZoomInside(name[0], vmin, vmax))) { + this[`zoom_${name}min`] = vmin; + this[`zoom_${name}max`] = vmax; + changed = true; + zoom_v = false; + req.values[indx * 2] = vmin; + req.values[indx * 2 + 1] = vmax; + req.flags[indx * 2] = req.flags[indx * 2 + 1] = true; } - } else if (!histo && obj?.fAxes) { - // case of RHistDisplayItem - - histo = obj; - - if (!histo.getBinContent || force) { - if (histo.fAxes.length === 3) { - assignRAxisMethods(histo.fAxes[0]); - assignRAxisMethods(histo.fAxes[1]); - assignRAxisMethods(histo.fAxes[2]); - - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; - - histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; - histo.dy = histo.fIndicies[3] + 1; - histo.stepy = histo.fIndicies[5]; - - histo.nz = histo.fIndicies[7] - histo.fIndicies[6]; - histo.dz = histo.fIndicies[6] + 1; - histo.stepz = histo.fIndicies[8]; - - // this is index in original histogram - histo.getBin = function(x, y, z) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1) + this.fAxes[0].GetNumBins()*this.fAxes[1].GetNumBins()*(z-1); }; - - // this is index in current available data - if ((histo.stepx > 1) || (histo.stepy > 1) || (histo.stepz > 1)) - histo.getBin0 = function(x, y, z) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy) + this.nx/this.stepx*this.ny/this.stepy*Math.floor((z-this.dz)/this.stepz); }; - else - histo.getBin0 = function(x, y, z) { return (x-this.dx) + this.nx*(y-this.dy) + this.nx*this.ny*(z-this.dz); }; + }; - histo.getBinContent = function(x, y, z) { return this.fBinContent[this.getBin0(x, y, z)]; }; - histo.getBinError = function(x, y, z) { return Math.sqrt(Math.abs(this.getBinContent(x, y, z))); }; - } else if (histo.fAxes.length === 2) { - assignRAxisMethods(histo.fAxes[0]); - assignRAxisMethods(histo.fAxes[1]); + // first process zooming (if any) + if (zoom_v) + this.forEachPainter(painter => checkZooming(painter)); - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; + // force zooming when no any other painter can verify zoom range + if (!is_any_check && this.self_drawaxes) + checkZooming(null, true); - histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; - histo.dy = histo.fIndicies[3] + 1; - histo.stepy = histo.fIndicies[5]; + if (unzoom_v) { + if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) + changed = true; + this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; + req.values[indx * 2] = req.values[indx * 2 + 1] = -1; + } - // this is index in original histogram - histo.getBin = function(x, y) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1); }; + if (!changed) + return false; - // this is index in current available data - if ((histo.stepx > 1) || (histo.stepy > 1)) - histo.getBin0 = function(x, y) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy); }; - else - histo.getBin0 = function(x, y) { return (x-this.dx) + this.nx*(y-this.dy); }; + if (interactive) + this.zoomChangedInteractive(name, interactive); - histo.getBinContent = function(x, y) { return this.fBinContent[this.getBin0(x, y)]; }; - histo.getBinError = function(x, y) { return Math.sqrt(Math.abs(this.getBinContent(x, y))); }; - } else { - assignRAxisMethods(histo.fAxes[0]); - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; - - histo.getBin = function(x) { return x-1; }; - if (histo.stepx > 1) - histo.getBin0 = function(x) { return Math.floor((x-this.dx)/this.stepx); }; - else - histo.getBin0 = function(x) { return x-this.dx; }; - histo.getBinContent = function(x) { return this.fBinContent[this.getBin0(x)]; }; - histo.getBinError = function(x) { return Math.sqrt(Math.abs(this.getBinContent(x))); }; - } - } - } - return histo; - } + if (this.v7NormalMode()) + this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); - /** @summary Decode options */ - decodeOptions(/* opt */) { - if (!this.options) this.options = { Hist: 1, System: 1 }; + return this.interactiveRedraw('pad', `zoom${indx}`).then(() => true); } - /** @summary Copy draw options from other painter */ - copyOptionsFrom(src) { - if (src === this) return; - const o = this.options, o0 = src.options; - o.Mode3D = o0.Mode3D; + /** @summary Unzoom single axis */ + async unzoomSingle(name, interactive) { + return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); } - /** @summary copy draw options to all other histograms in the pad */ - copyOptionsToOthers() { - this.forEachPainter(painter => { - if ((painter !== this) && isFunc(painter.copyOptionsFrom)) - painter.copyOptionsFrom(this); - }, 'objects'); + /** @summary Checks if specified axis zoomed */ + isAxisZoomed(axis) { + return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; } - /** @summary Clear 3d drawings - if any */ - clear3DScene() { - const fp = this.getFramePainter(); - if (isFunc(fp?.create3DScene)) - fp.create3DScene(-1); - this.mode3d = false; - } + /** @summary Unzoom specified axes + * @return {Promise} with boolean flag if zoom is changed */ + async unzoom(dox, doy, doz) { + if (dox === 'all') + return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); - /** @summary Cleanup hist painter */ - cleanup() { - this.clear3DScene(); + if ((dox === 'x2') || (dox === 'y2')) + return this.unzoomSingle(dox); - delete this.options; + if (typeof dox === 'undefined') + dox = doy = doz = true; + else if (isStr(dox)) { + doz = dox.indexOf('z') >= 0; + doy = dox.indexOf('y') >= 0; + dox = dox.indexOf('x') >= 0; + } - super.cleanup(); + return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, + doy ? 0 : undefined, doy ? 0 : undefined, + doz ? 0 : undefined, doz ? 0 : undefined, + 'unzoom'); } - /** @summary Returns histogram dimension */ - getDimension() { return 1; } - - /** @summary Scan histogram content - * @abstract */ - scanContent(/* when_axis_changed */) { - // function will be called once new histogram or - // new histogram content is assigned - // one should find min,max,nbins, maxcontent values - // if when_axis_changed === true specified, content will be scanned after axis zoom changed + /** @summary Reset all zoom attributes + * @private */ + resetZoom() { + ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { + this[`zoom_${n}min`] = undefined; + this[`zoom_${n}max`] = undefined; + this[`zoom_changed_${n}`] = undefined; + }); } - /** @summary Draw axes */ - async drawFrameAxes() { - // return true when axes was drawn - const main = this.getFramePainter(); - if (!main) - return false; + /** @summary Mark/check if zoom for specific axis was changed interactively + * @private */ + zoomChangedInteractive(axis, value) { + if (axis === 'reset') { + this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; + return; + } + if (!axis || axis === 'any') + return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - if (!this.draw_content) - return true; + if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) + return; - if (!this.isMainPainter()) { - if (!this.options.second_x && !this.options.second_y) - return true; + const fld = 'zoom_changed_' + axis; + if (value === undefined) + return this[fld]; - main.setAxes2Ranges(this.options.second_x, this.getAxis('x'), this.xmin, this.xmax, this.options.second_y, this.getAxis('y'), this.ymin, this.ymax); - return main.drawAxes2(this.options.second_x, this.options.second_y); + if (value === 'unzoom') { + // special handling of unzoom, only if was never changed before flag set to true + this[fld] = (this[fld] === undefined); + return; } - main.cleanupAxes(); - main.xmin = main.xmax = 0; - main.ymin = main.ymax = 0; - main.zmin = main.zmax = 0; - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, this.getAxis('z'), this.zmin, this.zmax); - return main.drawAxes(); - } - - /** @summary create attributes */ - createHistDrawAttributes() { - this.createv7AttFill(); - this.createv7AttLine(); + if (value) + this[fld] = true; } - /** @summary update display item */ - updateDisplayItem(obj, src) { - if (!obj || !src) return false; - - obj.fAxes = src.fAxes; - obj.fIndicies = src.fIndicies; - obj.fBinContent = src.fBinContent; - obj.fContMin = src.fContMin; - obj.fContMinPos = src.fContMinPos; - obj.fContMax = src.fContMax; + /** @summary Fill menu for frame when server is not there */ + fillObjectOfflineMenu(menu, kind) { + if ((kind !== 'x') && (kind !== 'y')) + return; - // update histogram attributes - this.getHisto(true); + menu.add('Unzoom', () => this.unzoom(kind)); - return true; + // here should be all axes attributes in offline } - /** @summary update histogram object */ - updateObject(obj /* , opt */) { - const origin = this.getObject(); - - if (obj !== origin) { - if (!this.matchObjectType(obj)) return false; - - if (this.isDisplayItem()) - - this.updateDisplayItem(origin, obj); - - else { - const horigin = getHImpl(origin), - hobj = getHImpl(obj); + /** @summary Set grid drawing for specified axis */ + changeFrameAttr(attr, value) { + const changes = {}; + this.v7AttrChange(changes, attr, value); + this.v7SetAttr(attr, value); + this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server + this.redrawPad(); + } - if (!horigin || !hobj) return false; + /** @summary Fill context menu */ + fillContextMenu(menu, kind, obj) { + if (kind === 'pal') + kind = 'z'; - // make it easy - copy statistics without axes - horigin.fStatistics = hobj.fStatistics; + if ((kind === 'x') || (kind === 'y') || (kind === 'x2') || (kind === 'y2')) { + const handle = this[kind + '_handle'], + faxis = obj || this[kind + 'axis']; + if (!handle) + return false; + menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); - origin.fTitle = obj.fTitle; + if (isFunc(faxis?.TestBit)) { + const main = this.getMainPainter(true); + menu.addTAxisMenu(EAxisBits, main || this, faxis, kind); + return true; } + + return handle.fillAxisContextMenu(menu, kind); } - this.scanContent(); + const alone = menu.size() === 0; - this.histogram_updated = true; // indicate that object updated + if (alone) + menu.header('Frame', `${urlClassPrefix}ROOT_1_1Experimental_1_1RFrame.html`); + else + menu.separator(); - return true; - } + if (this.zoom_xmin !== this.zoom_xmax) + menu.add('Unzoom X', () => this.unzoom('x')); + if (this.zoom_ymin !== this.zoom_ymax) + menu.add('Unzoom Y', () => this.unzoom('y')); + if (this.zoom_zmin !== this.zoom_zmax) + menu.add('Unzoom Z', () => this.unzoom('z')); + if (this.zoom_x2min !== this.zoom_x2max) + menu.add('Unzoom X2', () => this.unzoom('x2')); + if (this.zoom_y2min !== this.zoom_y2max) + menu.add('Unzoom Y2', () => this.unzoom('y2')); + menu.add('Unzoom all', () => this.unzoom('all')); - /** @summary Get axis object */ - getAxis(name) { - const histo = this.getHisto(), obj = this.getObject(); - let axis; + menu.separator(); - if (obj?.fAxes) { - switch (name) { - case 'x': axis = obj.fAxes[0]; break; - case 'y': axis = obj.fAxes[1]; break; - case 'z': axis = obj.fAxes[2]; break; - default: axis = obj.fAxes[0]; break; - } - } else if (histo?.fAxes) { - switch (name) { - case 'x': axis = histo.fAxes._0; break; - case 'y': axis = histo.fAxes._1; break; - case 'z': axis = histo.fAxes._2; break; - default: axis = histo.fAxes._0; break; - } + menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); + + if (this.x_handle) + menu.addchk(this.x_handle.draw_grid, 'Grid x', flag => this.changeFrameAttr('gridX', flag)); + if (this.y_handle) + menu.addchk(this.y_handle.draw_grid, 'Grid y', flag => this.changeFrameAttr('gridY', flag)); + if (this.x_handle && !this.x2_handle) + menu.addchk(this.x_handle.draw_swapside, 'Swap x', flag => this.changeFrameAttr('swapX', flag)); + if (this.y_handle && !this.y2_handle) + menu.addchk(this.y_handle.draw_swapside, 'Swap y', flag => this.changeFrameAttr('swapY', flag)); + if (this.x_handle && !this.x2_handle) { + menu.sub('Ticks x'); + menu.addchk(this.x_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksX', 0)); + menu.addchk(this.x_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksX', 1)); + menu.addchk(this.x_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksX', 2)); + menu.addchk(this.x_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksX', 3)); + menu.endsub(); + } + if (this.y_handle && !this.y2_handle) { + menu.sub('Ticks y'); + menu.addchk(this.y_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksY', 0)); + menu.addchk(this.y_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksY', 1)); + menu.addchk(this.y_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksY', 2)); + menu.addchk(this.y_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksY', 3)); + menu.endsub(); } - if (axis && !axis.GetBinCoord) - assignRAxisMethods(axis); + menu.addAttributesMenu(this, alone ? '' : 'Frame '); + menu.separator(); + + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => this.getPadPainter().saveAs(fmt, 'frame', `frame.${fmt}`))); + menu.endsub(); - return axis; + return true; } - /** @summary Get tip text for axis bin */ - getAxisBinTip(name, bin, step) { - const pmain = this.getFramePainter(), - handle = pmain[`${name}_handle`], - axis = this.getAxis(name), - x1 = axis.GetBinCoord(bin); + /** @summary Convert graphical coordinate into axis value */ + revertAxis(axis, pnt) { return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; } - if (handle.kind === kAxisLabels) - return pmain.axisAsText(name, x1); + /** @summary Show axis status message + * @desc method called normally when mouse enter main object element + * @private */ + showAxisStatus(axis_name, evnt) { + const hint_name = axis_name, hint_title = 'axis', + m = pointer(evnt, this.getFrameSvg().node()); + let id = (axis_name === 'x') ? 0 : 1; - const x2 = axis.GetBinCoord(bin+(step || 1)); + if (this.#swap_xy) + id = 1 - id; - if (handle.kind === kAxisTime) - return pmain.axisAsText(name, (x1+x2)/2); + const axis_value = this.revertAxis(axis_name, m[id]); - return `[${pmain.axisAsText(name, x1)}, ${pmain.axisAsText(name, x2)})`; + this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${Math.round(m[0])},${Math.round(m[1])}`); } - /** @summary Extract axes ranges and bins numbers - * @desc Also here ensured that all axes objects got their necessary methods */ - extractAxesProperties(ndim) { - const histo = this.getHisto(); - if (!histo) return; - - this.nbinsx = this.nbinsy = this.nbinsz = 0; + /** @summary Add interactive keys handlers + * @private */ + addKeysHandler() { + if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) + return; - let axis = this.getAxis('x'); - this.nbinsx = axis.GetNumBins(); - this.xmin = axis.min; - this.xmax = axis.max; + FrameInteractive.assign(this); - if (ndim < 2) return; - axis = this.getAxis('y'); - this.nbinsy = axis.GetNumBins(); - this.ymin = axis.min; - this.ymax = axis.max; + this.#keys_handler = evnt => this.processKeyPress(evnt); - if (ndim < 3) return; - axis = this.getAxis('z'); - this.nbinsz = axis.GetNumBins(); - this.zmin = axis.min; - this.zmax = axis.max; + window.addEventListener('keydown', this.#keys_handler, false); } - /** @summary Add interactive features, only main painter does it */ - addInteractivity() { - // only first painter in list allowed to add interactive functionality to the frame + /** @summary Add interactive functionality to the frame + * @private */ + addInteractivity(for_second_axes) { + if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) + return true; - const ismain = this.isMainPainter(), - second_axis = this.options.second_x || this.options.second_y, - fp = ismain || second_axis ? this.getFramePainter() : null; - return fp?.addInteractivity(!ismain && second_axis) ?? true; + FrameInteractive.assign(this); + if (!for_second_axes) + this.addBasicInteractivity(); + return this.addFrameInteractivity(for_second_axes); } - /** @summary Process item reply */ - processItemReply(reply, req) { - if (!this.isDisplayItem()) - return console.error('Get item when display normal histogram'); - - if (req.reqid === this.current_item_reqid) { - if (reply !== null) - this.updateDisplayItem(this.getObject(), reply.item); - - req.resolveFunc(true); - } + /** @summary Set selected range back to pad object - to be implemented + * @private */ + setRootPadRange(/* pad, is3d */) { + // TODO: change of pad range and send back to root application } - /** @summary Special method to request bins from server if existing data insufficient - * @return {Promise} when ready */ - async drawingBins(reason) { - let is_axes_zoomed = false; - if (reason && isStr(reason) && (reason.indexOf('zoom') === 0)) { - if (reason.indexOf('0') > 0) is_axes_zoomed = true; - if ((this.getDimension() > 1) && (reason.indexOf('1') > 0)) is_axes_zoomed = true; - if ((this.getDimension() > 2) && (reason.indexOf('2') > 0)) is_axes_zoomed = true; - } - - if (this.isDisplayItem() && is_axes_zoomed && this.v7NormalMode()) { - const handle = this.prepareDraw({ only_indexes: true }); - - // submit request if histogram data not enough for display - if (handle.incomplete) { - return new Promise(resolveFunc => { - // use empty kind to always submit request - const req = this.v7SubmitRequest('', { _typename: `${nsREX}RHistDrawableBase::RRequest` }, - this.processItemReply.bind(this)); - if (req) { - this.current_item_reqid = req.reqid; // ignore all previous requests, only this one will be processed - req.resolveFunc = resolveFunc; - setTimeout(this.processItemReply.bind(this, null, req), 1000); // after 1 s draw something that we can - } else - resolveFunc(true); - }); - } - } - - return true; + /** @summary Toggle log scale on the specified axes */ + toggleAxisLog(axis) { + const handle = this[axis + '_handle']; + return handle?.changeAxisLog('toggle'); } - /** @summary Toggle statbox drawing - * @desc Not yet implemented */ - toggleStat(/* arg */) {} +} // class RFramePainter - /** @summary get selected index for axis */ - getSelectIndex(axis, size, add) { - // be aware - here indexes starts from 0 - const taxis = this.getAxis(axis), - nbins = this['nbins'+axis] || 0; - let indx = 0; +/** + * @summary Painter class for RPad + * + * @private + */ - if (this.options.second_x && axis === 'x') axis = 'x2'; - if (this.options.second_y && axis === 'y') axis = 'y2'; +class RPadPainter extends RObjectPainter { - const main = this.getFramePainter(), - min = main ? main[`zoom_${axis}min`] : 0, - max = main ? main[`zoom_${axis}max`] : 0; + #iscan; // is canvas flag + #pad_name; // name of the pad + #pad; // RPad object + #painters; // painters in the pad + #pad_scale; // scaling factor of the pad + #pad_x; // pad x coordinate + #pad_y; // pad y coordinate + #pad_width; // pad width + #pad_height; // pad height + #doing_draw; // drawing handles + #custom_palette; // custom palette + #frame_painter_ref; // frame painter + #main_painter_ref; // main painter on the pad + #num_primitives; // number of primitives + #auto_color_cnt; // counter for auto colors + #fixed_size; // fixed size flag + #has_canvas; // when top-level canvas exists + #fast_drawing; // fast drawing mode + #resize_tmout; // timeout handle for resize + #start_draw_tm; // time when start drawing primitives - if ((min !== max) && taxis) { - if (size === 'left') - indx = taxis.FindBin(min, add || 0); + /** @summary constructor */ + constructor(dom, pad, opt, iscan, add_to_primitives) { + super(dom, pad, '', 'pad'); + this.#pad = pad; + this.#iscan = iscan; // indicate if working with canvas + this.#pad_name = ''; + if (!iscan && pad) { + if (pad.fObjectID) + this.#pad_name = 'pad' + pad.fObjectID; // use objectid as pad name else - indx = taxis.FindBin(max, (add || 0) + 0.5); - if (indx < 0) - indx = 0; - else if (indx > nbins) - indx = nbins; - } else - indx = (size === 'left') ? 0 : nbins; + this.#pad_name = 'ppp' + internals.id_counter++; // artificial name + } + this.#painters = []; // complete list of all painters in the pad + this.#has_canvas = true; + this.forEachPainter = this.forEachPainterInPad; + + const d = this.selectDom(); + if (!d.empty() && d.property('_batch_mode')) + this.batch_mode = true; + if (opt !== undefined) + this.decodeOptions(opt); - return indx; + if (add_to_primitives) { + if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { + this.#has_canvas = false; + this.#pad_name = ''; + this.setTopPainter(); + } else + this.addToPadPrimitives(); // must be here due to pad painter + } } - /** @summary Auto zoom into histogram non-empty range - * @abstract */ - autoZoom() {} + /** @summary Returns pad name + * @protected */ + getPadName() { return this.#pad_name; } - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const fp = this.getFramePainter(); - if (!fp) return false; + /** @summary Indicates that drawing runs in batch mode + * @private */ + isBatchMode() { + if (this.batch_mode !== undefined) + return this.batch_mode; - switch (funcname) { - case 'ToggleZoom': - if ((this.zoom_xmin !== this.zoom_xmax) || (this.zoom_ymin !== this.zoom_ymax) || (this.zoom_zmin !== this.zoom_zmax)) { - const res = this.unzoom(); - fp.zoomChangedInteractive('reset'); - return res; - } - if (this.draw_content) - return this.autoZoom(); - break; - case 'ToggleLogX': return fp.toggleAxisLog('x'); - case 'ToggleLogY': return fp.toggleAxisLog('y'); - case 'ToggleLogZ': return fp.toggleAxisLog('z'); - case 'ToggleStatBox': return getPromise(this.toggleStat()); - } - return false; + if (isBatchMode()) + return true; + + return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); } - /** @summary Fill pad toolbar with hist-related functions */ - fillToolbar(not_shown) { - const pp = this.getPadPainter(); - if (!pp) return; + /** @summary Indicates that is not Root6 pad painter + * @private */ + isRoot6() { return false; } - pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); - pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); - pp.addPadButton('arrow_up', 'Toggle log y', 'ToggleLogY', 'PageUp'); - if (this.getDimension() > 1) - pp.addPadButton('arrow_diag', 'Toggle log z', 'ToggleLogZ'); - if (this.draw_content) - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - if (!not_shown) pp.showPadButtons(); - } + /** @summary Returns true if pad is editable */ + isEditable() { return true; } - /** @summary get tool tips used in 3d mode */ - get3DToolTip(indx) { - const histo = this.getHisto(), - tip = { bin: indx, name: histo.fName || 'histo', title: histo.fTitle }; - switch (this.getDimension()) { - case 1: - tip.ix = indx + 1; tip.iy = 1; - tip.value = histo.getBinContent(tip.ix); - tip.error = histo.getBinError(tip.ix); - tip.lines = this.getBinTooltips(indx-1); - break; - case 2: - tip.ix = (indx % this.nbinsx) + 1; - tip.iy = (indx - (tip.ix - 1)) / this.nbinsx + 1; - tip.value = histo.getBinContent(tip.ix, tip.iy); - tip.error = histo.getBinError(tip.ix, tip.iy); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1); - break; - case 3: - tip.ix = indx % this.nbinsx + 1; - tip.iy = ((indx - (tip.ix - 1)) / this.nbinsx) % this.nbinsy + 1; - tip.iz = (indx - (tip.ix - 1) - (tip.iy - 1) * this.nbinsx) / this.nbinsx / this.nbinsy + 1; - tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); - tip.error = histo.getBinError(tip.ix, tip.iy, tip.iz); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1, tip.iz-1); - break; - } + /** @summary Returns true if button */ + isButton() { return false; } - return tip; + /** @summary Returns true if it is canvas + * @param {Boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ + isCanvas(is_online = false) { + if (!this.#iscan) + return false; + if (is_online === true) + return isFunc(this.getWebsocket) && this.getWebsocket(); + return isStr(is_online) ? this.#iscan === is_online : true; } - /** @summary Create contour levels for currently selected Z range */ - createContour(main, palette, args) { - if (!main || !palette) return; + /** @summary Returns true if it is canvas or top pad without canvas */ + isTopPad() { return this.isCanvas() || !this.#has_canvas; } - if (!args) args = {}; + /** @summary returns pad painter + * @protected */ + getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } - let nlevels = gStyle.fNumberContours, - zmin = this.minbin, zmax = this.maxbin, zminpos = this.minposbin; + /** @summary returns canvas painter + * @protected */ + getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } - if (args.scatter_plot) { - if (nlevels > 50) nlevels = 50; - zmin = this.minposbin; - } + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg + * @protected */ + getCanvSvg() { return this.selectDom().select('.root_canvas'); } - if (zmin === zmax) { zmin = this.gminbin; zmax = this.gmaxbin; zminpos = this.gminposbin; } + /** @summary Pad svg element + * @return {object} d3 selection with pad svg + * @protected */ + getPadSvg() { + const c = this.getCanvSvg(); + if (!this.#pad_name || c.empty()) + return c; - if (this.getDimension() < 3) { - if (main.zoom_zmin !== main.zoom_zmax) { - zmin = main.zoom_zmin; - zmax = main.zoom_zmax; - } else if (args.full_z_range) { - zmin = main.zmin; - zmax = main.zmax; - } - } + return c.select('.primitives_layer .__root_pad_' + this.#pad_name); + } - palette.setFullRange(main.zmin, main.zmax); - palette.createContour(main.logz, nlevels, zmin, zmax, zminpos); + /** @summary Method selects immediate layer under canvas/pad main element + * @param {string} name - layer name like 'primitives_layer', 'btns_layer', 'info_layer' + * @protected */ + getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } - if (this.getDimension() < 3) { - main.scale_zmin = palette.colzmin; - main.scale_zmax = palette.colzmax; + /** @summary Returns svg element for the frame in current pad + * @protected */ + getFrameSvg() { + const layer = this.getLayerSvg('primitives_layer'); + if (layer.empty()) + return layer; + let node = layer.node().firstChild; + while (node) { + const elem = select(node); + if (elem.classed('root_frame')) + return elem; + node = node.nextSibling; } + return select(null); } - /** @summary Start dialog to modify range of axis where histogram values are displayed */ - changeValuesRange(menu, arg) { - const pmain = this.getFramePainter(); - if (!pmain) return; - const prefix = pmain.isAxisZoomed(arg) ? 'zoom_' + arg : arg, - curr = '[' + pmain[`${prefix}min`] + ',' + pmain[`${prefix}max`] + ']'; - menu.input('Enter values range for axis ' + arg + ' like [0,100] or empty string to unzoom', curr).then(res => { - res = res ? JSON.parse(res) : []; - if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) - pmain.unzoom(arg); - else - pmain.zoom(arg, res[0], res[1]); - }); + /** @summary Returns main painter on the pad + * @desc Typically main painter is TH1/TH2 object which is drawing axes + * @private */ + getMainPainter() { return this.#main_painter_ref || null; } + + /** @summary Assign main painter on the pad + * @private */ + setMainPainter(painter, force) { + if (!this.#main_painter_ref || force) + this.#main_painter_ref = painter; } - /** @summary Fill histogram context menu */ - fillContextMenuItems(menu) { - if (this.draw_content) { - menu.addchk(this.toggleStat('only-check'), 'Show statbox', () => this.toggleStat()); + /** @summary cleanup pad and all primitives inside */ + cleanup() { + if (this.#doing_draw) + console.error('pad drawing is not completed when cleanup is called'); - if (this.getDimension() === 2) - menu.add('Values range', () => this.changeValuesRange(menu, 'z')); + this.#painters.forEach(p => p.cleanup()); - if (isFunc(this.fillHistContextMenu)) - this.fillHistContextMenu(menu); + const svg_p = this.getPadSvg(); + if (!svg_p.empty()) { + svg_p.property('pad_painter', null); + if (!this.isCanvas()) + svg_p.remove(); } - const fp = this.getFramePainter(); + this.#main_painter_ref = undefined; + this.#frame_painter_ref = undefined; + this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; + this.#doing_draw = undefined; + delete this._dfltRFont; - if (this.options.Mode3D) { - // menu for 3D drawings + this.#painters = []; + this.#pad = undefined; + this.assignObject(null); + this.#pad_name = undefined; + this.#has_canvas = false; - if (menu.size() > 0) - menu.add('separator'); + selectActivePad({ pp: this, active: false }); - const main = this.getMainPainter() || this; + super.cleanup(); + } - menu.addchk(main.isTooltipAllowed(), 'Show tooltips', () => main.setTooltipAllowed('toggle')); + /** @summary Returns frame painter inside the pad + * @private */ + getFramePainter() { return this.#frame_painter_ref; } - menu.addchk(fp?.enable_highlight, 'Highlight bins', () => { - fp.enable_highlight = !fp.enable_highlight; - if (!fp.enable_highlight && main.mode3d && isFunc(main.highlightBin3D)) - main.highlightBin3D(null); - }); + /** @summary Assign actual frame painter + * @private */ + setFramePainter(fp, on) { + if (on) + this.#frame_painter_ref = fp; + else if (this.#frame_painter_ref === fp) + this.#frame_painter_ref = undefined; + } - if (isFunc(fp?.render3D)) { - menu.addchk(main.options.FrontBox, 'Front box', () => { - main.options.FrontBox = !main.options.FrontBox; - fp.render3D(); - }); - menu.addchk(main.options.BackBox, 'Back box', () => { - main.options.BackBox = !main.options.BackBox; - fp.render3D(); - }); - } + /** @summary get pad width */ + getPadWidth() { return this.#pad_width || 0; } - if (this.draw_content) { - menu.addchk(!this.options.Zero, 'Suppress zeros', () => { - this.options.Zero = !this.options.Zero; - this.redrawPad(); - }); + /** @summary get pad height */ + getPadHeight() { return this.#pad_height || 0; } - if ((this.options.Lego === 12) || (this.options.Lego === 14)) - this.fillPaletteMenu(menu); - } + /** @summary get pad height */ + getPadScale() { return this.#pad_scale || 1; } - if (isFunc(main.control?.reset)) - menu.add('Reset camera', () => main.control.reset()); - } + /** @summary return pad log state x or y are allowed */ + getPadLog(/* name */) { return false; } - if (this.histogram_updated && fp.zoomChangedInteractive()) - menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); + /** @summary get pad rect */ + getPadRect() { + return { + x: this.#pad_x || 0, + y: this.#pad_y || 0, + width: this.getPadWidth(), + height: this.getPadHeight() + }; } - /** @summary Update palette drawing */ - updatePaletteDraw() { - if (this.isMainPainter()) - this.getPadPainter().findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`)?.drawPalette(); + /** @summary Returns frame coordinates - also when frame is not drawn */ + getFrameRect() { + const fp = this.getFramePainter(); + if (fp) + return fp.getFrameRect(); + + const w = this.getPadWidth(), + h = this.getPadHeight(), + rect = {}; + + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); + rect.hint_delta_x = rect.szx; + rect.hint_delta_y = rect.szy; + rect.transform = makeTranslate(rect.x, rect.y) || ''; + return rect; } - /** @summary Fill menu entries for palette */ - fillPaletteMenu(menu) { - menu.addPaletteMenu(this.options.Palette || settings.Palette, arg => { - // TODO: rewrite for RPalette functionality - this.options.Palette = parseInt(arg); - this.redraw(); // redraw histogram - }); + /** @summary return RPad object */ + getRootPad(is_root6) { + return (is_root6 === undefined) || !is_root6 ? this.#pad : null; } - /** @summary Toggle 3D drawing mode */ - toggleMode3D() { - this.options.Mode3D = !this.options.Mode3D; + /** @summary Cleanup primitives from pad - selector lets define which painters to remove + * @private */ + cleanPrimitives(selector) { + // remove all primitives + if (selector === true) + selector = () => true; - if (this.options.Mode3D) { - if (!this.options.Surf && !this.options.Lego && !this.options.Error) { - if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) - this.options.Lego = this.options.Color ? 14 : 13; - else - this.options.Lego = this.options.Color ? 12 : 1; + if (!isFunc(selector)) + return false; - this.options.Zero = false; // do not show zeros by default + let is_any = false; + + for (let k = this.#painters.length - 1; k >= 0; --k) { + const subp = this.#painters[k]; + if (selector(subp)) { + subp.cleanup(); + this.#painters.splice(k, 1); + is_any = true; } } - this.copyOptionsToOthers(); - return this.interactiveRedraw('pad', 'drawopt'); + return is_any; } - /** @summary Calculate histogram inidicies and axes values for each visible bin */ - prepareDraw(args) { - if (!args) args = { rounding: true, extra: 0, middle: 0 }; - - if (args.extra === undefined) args.extra = 0; - if (args.right_extra === undefined) args.right_extra = args.extra; - if (args.middle === undefined) args.middle = 0; + /** @summary Divide pad on sub-pads */ + async divide(/* nx, ny, use_existing */) { + if (settings.Debug) + console.warn('RPadPainter.divide not implemented'); + return this; + } - const histo = this.getHisto(), xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), - pmain = this.getFramePainter(), - hdim = this.getDimension(), - res = { - i1: this.getSelectIndex('x', 'left', 0 - args.extra), - i2: this.getSelectIndex('x', 'right', 1 + args.right_extra), - j1: (hdim < 2) ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra), - j2: (hdim < 2) ? 1 : this.getSelectIndex('y', 'right', 1 + args.right_extra), - k1: (hdim < 3) ? 0 : this.getSelectIndex('z', 'left', 0 - args.extra), - k2: (hdim < 3) ? 1 : this.getSelectIndex('z', 'right', 1 + args.right_extra), - stepi: 1, stepj: 1, stepk: 1, - min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1 - }; - let i, j, x, y, binz, binarea; + /** @summary Removes and cleanup specified primitive + * @desc also secondary primitives will be removed + * @return new index to continue loop or -111 if main painter removed + * @private */ + removePrimitive(arg, clean_only_secondary) { + let indx, prim = null; + if (Number.isInteger(arg)) { + indx = arg; + prim = this.#painters[indx]; + } else { + indx = this.#painters.indexOf(arg); + prim = arg; + } + if (indx < 0) + return indx; - if (this.isDisplayItem() && histo.fIndicies) { - if (res.i1 < histo.fIndicies[0]) { res.i1 = histo.fIndicies[0]; res.incomplete = true; } - if (res.i2 > histo.fIndicies[1]) { res.i2 = histo.fIndicies[1]; res.incomplete = true; } - res.stepi = histo.fIndicies[2]; - if (res.stepi > 1) res.incomplete = true; - if ((hdim > 1) && (histo.fIndicies.length > 5)) { - if (res.j1 < histo.fIndicies[3]) { res.j1 = histo.fIndicies[3]; res.incomplete = true; } - if (res.j2 > histo.fIndicies[4]) { res.j2 = histo.fIndicies[4]; res.incomplete = true; } - res.stepj = histo.fIndicies[5]; - if (res.stepj > 1) res.incomplete = true; - } - if ((hdim > 2) && (histo.fIndicies.length > 8)) { - if (res.k1 < histo.fIndicies[6]) { res.k1 = histo.fIndicies[6]; res.incomplete = true; } - if (res.k2 > histo.fIndicies[7]) { res.k2 = histo.fIndicies[7]; res.incomplete = true; } - res.stepk = histo.fIndicies[8]; - if (res.stepk > 1) res.incomplete = true; + const arr = []; + let resindx = indx - 1; // object removed itself + arr.push(prim); + this.#painters.splice(indx, 1); + + let len0 = 0; + while (len0 < arr.length) { + for (let k = this.#painters.length - 1; k >= 0; --k) { + if (this.#painters[k].isSecondary(arr[len0])) { + arr.push(this.#painters[k]); + this.#painters.splice(k, 1); + if (k <= indx) + resindx--; + } } + len0++; } - if (args.only_indexes) return res; + arr.forEach(painter => { + if ((painter !== prim) || !clean_only_secondary) + painter.cleanup(); + if (this.getMainPainter() === painter) { + delete this.setMainPainter(undefined, true); + resindx = -111; + } + }); - // no need for Float32Array, plain Array is 10% faster - // reserve more places to avoid complex boundary checks + return resindx; + } - res.grx = new Array(res.i2+res.stepi+1); - res.gry = new Array(res.j2+res.stepj+1); + /** @summary try to find object by name in list of pad primitives + * @desc used to find title drawing + * @private */ + findInPrimitives(/* objname, objtype */) { + if (settings.Debug) + console.warn('findInPrimitives not implemented for RPad'); + return null; + } - if (args.original) { - res.original = true; - res.origx = new Array(res.i2+1); - res.origy = new Array(res.j2+1); - } + /** @summary Try to find painter for specified object + * @desc can be used to find painter for some special objects, registered as + * histogram functions + * @private */ + findPainterFor(selobj, selname, seltype) { + return this.#painters.find(p => { + const pobj = p.getObject(); + if (!pobj) + return false; - if (args.pixel_density) args.rounding = true; + if (selobj && (pobj === selobj)) + return true; + if (!selname && !seltype) + return false; + if (selname && (pobj.fName !== selname)) + return false; + if (seltype && (pobj._typename !== seltype)) + return false; + return true; + }); + } - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); + /** @summary Returns palette associated with pad. + * @desc Either from existing palette painter or just default palette */ + getHistPalette() { + const pp = this.findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`); - // calculate graphical coordinates in advance - for (i = res.i1; i <= res.i2; ++i) { - x = xaxis.GetBinCoord(i + args.middle); - if (funcs.logx && (x <= 0)) { res.i1 = i+1; continue; } - if (res.origx) res.origx[i] = x; - res.grx[i] = funcs.grx(x); - if (args.rounding) res.grx[i] = Math.round(res.grx[i]); + if (pp) + return pp.getHistPalette(); - if (args.use3d) { - if (res.grx[i] < -pmain.size_x3d) { res.i1 = i; res.grx[i] = -pmain.size_x3d; } - if (res.grx[i] > pmain.size_x3d) { res.i2 = i; res.grx[i] = pmain.size_x3d; } - } + if (!this.fDfltPalette) { + this.fDfltPalette = { + _typename: `${nsREX}RPalette`, + fColors: [{ fOrdinal: 0, fColor: { fColor: 'rgb(53, 42, 135)' } }, + { fOrdinal: 0.125, fColor: { fColor: 'rgb(15, 92, 221)' } }, + { fOrdinal: 0.25, fColor: { fColor: 'rgb(20, 129, 214)' } }, + { fOrdinal: 0.375, fColor: { fColor: 'rgb(6, 164, 202)' } }, + { fOrdinal: 0.5, fColor: { fColor: 'rgb(46, 183, 164)' } }, + { fOrdinal: 0.625, fColor: { fColor: 'rgb(135, 191, 119)' } }, + { fOrdinal: 0.75, fColor: { fColor: 'rgb(209, 187, 89)' } }, + { fOrdinal: 0.875, fColor: { fColor: 'rgb(254, 200, 50)' } }, + { fOrdinal: 1, fColor: { fColor: 'rgb(249, 251, 14)' } }], + fInterpolate: true, + fNormalized: true + }; + exports.addMethods(this.fDfltPalette, `${nsREX}RPalette`); } - if (args.use3d) { - if ((res.i1 < res.i2-2) && (res.grx[res.i1] === res.grx[res.i1+1])) res.i1++; - if ((res.i1 < res.i2-2) && (res.grx[res.i2-1] === res.grx[res.i2])) res.i2--; - } + return this.fDfltPalette; + } - // copy last valid value to higher indicies - while (i < res.i2 + res.stepi + 1) - res.grx[i++] = res.grx[res.i2]; + /** @summary Returns custom palette + * @private */ + getCustomPalette(no_recursion) { + return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); + } - if (hdim === 1) { - res.gry[0] = funcs.gry(0); - res.gry[1] = funcs.gry(1); - } else { - for (j = res.j1; j <= res.j2; ++j) { - y = yaxis.GetBinCoord(j + args.middle); - if (funcs.logy && (y <= 0)) { res.j1 = j+1; continue; } - if (res.origy) res.origy[j] = y; - res.gry[j] = funcs.gry(y); - if (args.rounding) res.gry[j] = Math.round(res.gry[j]); + /** @summary Returns number of painters + * @protected */ + getNumPainters() { return this.#painters.length; } - if (args.use3d) { - if (res.gry[j] < -pmain.size_y3d) { res.j1 = j; res.gry[j] = -pmain.size_y3d; } - if (res.gry[j] > pmain.size_y3d) { res.j2 = j; res.gry[j] = pmain.size_y3d; } - } - } - } + /** @summary Add painter to pad list of painters + * @protected */ + addToPrimitives(painter) { + if (this.#painters.indexOf(painter) < 0) + this.#painters.push(painter); + return this; + } - if (args.use3d && (hdim > 1)) { - if ((res.j1 < res.j2-2) && (res.gry[res.j1] === res.gry[res.j1+1])) res.j1++; - if ((res.j1 < res.j2-2) && (res.gry[res.j2-1] === res.gry[res.j2])) res.j2--; + /** @summary Call function for each painter in pad + * @param {function} userfunc - function to call + * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad + * @private */ + forEachPainterInPad(userfunc, kind) { + if (!kind) + kind = 'all'; + if (kind !== 'objects') + userfunc(this); + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (isFunc(sub.forEachPainterInPad)) { + if (kind !== 'objects') + sub.forEachPainterInPad(userfunc, kind); + } else if (kind !== 'pads') + userfunc(sub); } + } - // copy last valid value to higher indicies - if (hdim > 1) { - while (j < res.j2 + res.stepj + 1) - res.gry[j++] = res.gry[res.j2]; - } + /** @summary register for pad events receiver + * @desc in pad painter, while pad may be drawn without canvas + * @private */ + registerForPadEvents(receiver) { + this.pad_events_receiver = receiver; + } - // find min/max values in selected range - this.maxbin = this.minbin = this.minposbin = null; + /** @summary Generate pad events, normally handled by GED + * @desc in pad painter, while pad may be drawn without canvas + * @private */ + producePadEvent(what, padpainter, painter, position) { + if ((what === 'select') && isFunc(this.selectActivePad)) + this.selectActivePad(padpainter, painter, position); - for (i = res.i1; i < res.i2; i += res.stepi) { - for (j = res.j1; j < res.j2; j += res.stepj) { - binz = histo.getBinContent(i + 1, j + 1); - if (!Number.isFinite(binz)) continue; - res.sumz += binz; - if (args.pixel_density) { - binarea = (res.grx[i+res.stepi]-res.grx[i])*(res.gry[j]-res.gry[j+res.stepj]); - if (binarea <= 0) continue; - res.max = Math.max(res.max, binz); - if ((binz > 0) && ((binz < res.min) || (res.min === 0))) res.min = binz; - binz = binz/binarea; - } - if (this.maxbin === null) - this.maxbin = this.minbin = binz; - else { - this.maxbin = Math.max(this.maxbin, binz); - this.minbin = Math.min(this.minbin, binz); - } - if (binz > 0) - if ((this.minposbin === null) || (binz < this.minposbin)) this.minposbin = binz; - } - } + if (isFunc(this.pad_events_receiver)) + this.pad_events_receiver({ what, padpainter, painter, position }); + } - res.palette = pmain.getHistPalette(); + /** @summary method redirect call to pad events receiver */ + selectObjectPainter(painter, pos) { + const canp = this.isTopPad() ? this : this.getCanvPainter(); - if (res.palette) - this.createContour(pmain, res.palette, args); + if (painter === undefined) + painter = this; - return res; + if (pos && !this.isTopPad()) + pos = getAbsPosInCanvas(this.getPadSvg(), pos); + + selectActivePad({ pp: this, active: true }); + + canp.producePadEvent('select', this, painter, pos); } -} // class RHistPainter + /** @summary Set fast drawing property depending on the size + * @private */ + setFastDrawing(w, h) { + const was_fast = this.#fast_drawing; + this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); + if (was_fast !== this.#fast_drawing) + this.showPadButtons(); + } -/** - * @summary Painter for RH1 classes - * - * @private - */ + /** @summary Return fast drawing flag + * @private */ + isFastDrawing() { return this.#fast_drawing; } -let RH1Painter$2 = class RH1Painter extends RHistPainter { + /** @summary Returns true if canvas configured with grayscale + * @private */ + isGrayscale() { return false; } - /** @summary Constructor - * @param {object|string} dom - DOM element or id - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = false; + /** @summary Set grayscale mode for the canvas + * @private */ + setGrayscale(/* flag */) { + console.error('grayscale mode not implemented for RCanvas'); } - /** @summary Scan content */ - scanContent(when_axis_changed) { - // if when_axis_changed === true specified, content will be scanned after axis zoom changed + /** @summary Returns true if default pad range is configured + * @private */ + isDefaultPadRange() { + return true; + } - const histo = this.getHisto(); - if (!histo) return; + /** @summary Create SVG element for the canvas */ + createCanvasSvg(check_resize, new_size) { + const lmt = 5; + let factor, svg, rect, btns, frect; - if (!this.nbinsx && when_axis_changed) when_axis_changed = false; + if (check_resize > 0) { + if (this.#fixed_size) + return check_resize > 1; // flag used to force re-drawing of all sub-pads - if (!when_axis_changed) - this.extractAxesProperties(1); + svg = this.getCanvSvg(); + if (svg.empty()) + return false; - let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0; + factor = svg.property('height_factor'); - if (this.isDisplayItem()) { - // take min/max values from the display item - hmin = histo.fContMin; - hmin_nz = histo.fContMinPos; - hmax = histo.fContMax; - hsum = hmax; - } else { - const left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'); + rect = this.testMainResize(check_resize, null, factor); - if (when_axis_changed) - if ((left === this.scan_xleft) && (right === this.scan_xright)) return; + if (!rect.changed && (check_resize === 1)) + return false; + if (!this.isBatchMode()) + btns = this.getLayerSvg('btns_layer'); - this.scan_xleft = left; - this.scan_xright = right; + frect = svg.selectChild('.canvas_fillrect'); + } else { + const render_to = this.selectDom(); - let first = true, value, err; + if (render_to.style('position') === 'static') + render_to.style('position', 'relative'); - for (let i = 0; i < this.nbinsx; ++i) { - value = histo.getBinContent(i+1); - hsum += value; + svg = render_to.append('svg') + .attr('class', 'jsroot root_canvas') + .property('pad_painter', this) // this is custom property + .property('redraw_by_resize', false); // could be enabled to force redraw by each resize - if ((i=right)) continue; + this.setTopPainter(); // assign canvas as top painter of that element - if (value > 0) - if ((hmin_nz === 0) || (value this.enlargePad(evnt, true)) + .on('click', () => this.selectObjectPainter(this, null)) + .on('mouseenter', () => this.showObjectStatus()) + .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); } - } - this.stat_entries = hsum; + svg.append('svg:g').attr('class', 'primitives_layer'); + svg.append('svg:g').attr('class', 'info_layer'); + if (!this.isBatchMode()) { + btns = svg.append('svg:g') + .attr('class', 'btns_layer') + .property('leftside', settings.ToolBarSide === 'left') + .property('vertical', settings.ToolBarVert); + } - this.hmin = hmin; - this.hmax = hmax; + factor = 0.66; + if (this.#pad?.fWinSize[0] && this.#pad.fWinSize[1]) { + factor = this.#pad.fWinSize[1] / this.#pad.fWinSize[0]; + if ((factor < 0.1) || (factor > 10)) + factor = 0.66; + } - this.ymin_nz = hmin_nz; // value can be used to show optimal log scale + if (this.#fixed_size) { + render_to.style('overflow', 'auto'); + rect = { width: this.#pad.fWinSize[0], height: this.#pad.fWinSize[1] }; + if (!rect.width || !rect.height) + rect = getElementRect(render_to); + } else + rect = this.testMainResize(2, new_size, factor); + } - if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) - this.draw_content = false; - else - this.draw_content = true; + this.createAttFill({ pattern: 1001, color: 0 }); - if (this.draw_content) { - if (hmin >= hmax) { - if (hmin === 0) { - this.ymin = 0; - this.ymax = 1; - } else if (hmin < 0) { - this.ymin = 2 * hmin; - this.ymax = 0; - } else { - this.ymin = 0; - this.ymax = hmin * 2; - } + if ((rect.width <= lmt) || (rect.height <= lmt)) { + if (!this.hasSnapId()) { + svg.style('display', 'none'); + console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); + } + if (this.#pad_width && this.#pad_height) { + // use last valid dimensions + rect.width = this.#pad_width; + rect.height = this.#pad_height; } else { - const dy = (hmax - hmin) * 0.05; - this.ymin = hmin - dy; - if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; - this.ymax = hmax + dy; + // just to complete drawing. + rect.width = 800; + rect.height = 600; } + } else + svg.style('display', null); + + if (this.#fixed_size) { + svg.attr('x', 0) + .attr('y', 0) + .attr('width', rect.width) + .attr('height', rect.height) + .style('position', 'absolute'); + } else { + svg.attr('x', 0) + .attr('y', 0) + .style('width', '100%') + .style('height', '100%') + .style('position', 'absolute') + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); } - } - - /** @summary Count statistic */ - countStat(cond) { - const histo = this.getHisto(), xaxis = this.getAxis('x'), - left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'), - stat_sumwy = 0, stat_sumwy2 = 0, - res = { name: 'histo', meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, entries: this.stat_entries, xmax: 0, wmax: 0 }; - let stat_sumw = 0, stat_sumwx = 0, stat_sumwx2 = 0, - i, xx = 0, w = 0, xmax = null, wmax = null; - - for (i = left; i < right; ++i) { - xx = xaxis.GetBinCoord(i+0.5); - if (cond && !cond(xx)) continue; + svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); - w = histo.getBinContent(i + 1); + svg.attr('viewBox', `0 0 ${rect.width} ${rect.height}`) + .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio + .property('height_factor', factor) + .property('draw_x', 0) + .property('draw_y', 0) + .property('draw_width', rect.width) + .property('draw_height', rect.height); - if ((xmax === null) || (w > wmax)) { xmax = xx; wmax = w; } + this.#pad_x = 0; + this.#pad_y = 0; + this.#pad_width = rect.width; + this.#pad_height = rect.height; - stat_sumw += w; - stat_sumwx += w * xx; - stat_sumwx2 += w * xx**2; - } + frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) + .call(this.fillatt.func); - res.integral = stat_sumw; + this.setFastDrawing(rect.width, rect.height); - if (Math.abs(stat_sumw) > 1e-300) { - res.meanx = stat_sumwx / stat_sumw; - res.meany = stat_sumwy / stat_sumw; - res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany**2)); - } + if (isFunc(this.alignButtons) && btns) + this.alignButtons(btns, rect.width, rect.height); - if (xmax !== null) { - res.xmax = xmax; - res.wmax = wmax; - } + return true; + } - return res; + /** @summary Draw item name on canvas, dummy for RPad + * @private */ + drawItemNameOnCanvas() { } - /** @summary Fill statistic */ - fillStatistic(stat, dostat/*, dofit */) { - const histo = this.getHisto(), - data = this.countStat(), - print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10; + /** @summary Enlarge pad draw element when possible */ + enlargePad(evnt, is_dblclick, is_escape) { + evnt?.preventDefault(); + evnt?.stopPropagation(); - // make empty at the beginning - stat.clearStat(); + // ignore double click on online canvas itself for enlarge + if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) + return; - if (print_name > 0) - stat.addText(data.name); + const svg_can = this.getCanvSvg(), + pad_enlarged = svg_can.property('pad_enlarged'); - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { + if (this.#fixed_size) + return; // canvas cannot be enlarged in such mode + if (!this.enlargeMain(is_escape ? false : 'toggle')) + return; + if (this.enlargeMain('state') === 'off') + svg_can.property('pad_enlarged', null); + else + selectActivePad({ pp: this, active: true }); + } else if (!pad_enlarged && !is_escape) { + this.enlargeMain(true, true); + svg_can.property('pad_enlarged', this.#pad); + selectActivePad({ pp: this, active: true }); + } else if (pad_enlarged === this.#pad) { + this.enlargeMain(false); + svg_can.property('pad_enlarged', null); + } else if (!is_escape && is_dblclick) + console.error('missmatch with pad double click events'); + + return this.checkResize(true); + } - if (print_mean > 0) - stat.addText('Mean = ' + stat.format(data.meanx)); + /** @summary Create SVG element for the pad + * @return true when pad is displayed and all its items should be redrawn */ + createPadSvg(only_resize) { + if (this.isTopPad()) { + this.createCanvasSvg(only_resize ? 2 : 0); + return true; + } - if (print_rms > 0) - stat.addText('Std Dev = ' + stat.format(data.rmsx)); + const svg_parent = this.getPadPainter()?.getPadSvg(), + svg_can = this.getCanvSvg(), + width = svg_parent.property('draw_width'), + height = svg_parent.property('draw_height'), + pad_enlarged = svg_can.property('pad_enlarged'); + let pad_visible = true, + w = width, h = height, x = 0, y = 0, + svg_pad, svg_rect, btns = null; - if (print_under > 0) - stat.addText('Underflow = ' + stat.format(histo.getBinContent(0), 'entries')); + if (this.#pad?.fPos && this.#pad?.fSize) { + x = Math.round(width * this.#pad.fPos.fHoriz.fArr[0]); + y = Math.round(height * this.#pad.fPos.fVert.fArr[0]); + w = Math.round(width * this.#pad.fSize.fHoriz.fArr[0]); + h = Math.round(height * this.#pad.fSize.fVert.fArr[0]); + } - if (print_over > 0) - stat.addText('Overflow = ' + stat.format(histo.getBinContent(this.nbinsx+1), 'entries')); + if (pad_enlarged) { + pad_visible = false; + if (pad_enlarged === this.#pad) + pad_visible = true; + else { + this.forEachPainterInPad(pp => { + if (pp.getObject() === pad_enlarged) + pad_visible = true; + }, 'pads'); + } - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); + if (pad_visible) { + w = width; + h = height; + x = y = 0; + } + } - if (print_skew > 0) - stat.addText('Skew = '); + if (only_resize) { + svg_pad = this.getPadSvg(); + svg_rect = svg_pad.selectChild('.root_pad_border'); + if (!this.isBatchMode()) + btns = this.getLayerSvg('btns_layer'); + this.addPadInteractive(true); + } else { + svg_pad = svg_parent.selectChild('.primitives_layer') + .append('svg:svg') // here was g before, svg used to blend all drawings outside + .classed('__root_pad_' + this.#pad_name, true) + .attr('pad', this.#pad_name) // set extra attribute to mark pad name + .property('pad_painter', this); // this is custom property - if (print_kurt > 0) - stat.addText('Kurt = '); + if (!this.isBatchMode()) + svg_pad.append('svg:title').text('ROOT subpad'); - return true; - } + svg_rect = svg_pad.append('svg:path').attr('class', 'root_pad_border'); - /** @summary Draw histogram as bars */ - async drawBars(handle, funcs, width, height) { - this.createG(true); + svg_pad.append('svg:g').attr('class', 'primitives_layer'); + if (!this.isBatchMode()) { + btns = svg_pad.append('svg:g') + .attr('class', 'btns_layer') + .property('leftside', settings.ToolBarSide !== 'left') + .property('vertical', settings.ToolBarVert); + } - const left = handle.i1, right = handle.i2, di = handle.stepi, - pmain = this.getFramePainter(), - histo = this.getHisto(), xaxis = this.getAxis('x'); - let i, x1, x2, grx1, grx2, y, gry1, gry2, w, - bars = '', barsl = '', barsr = ''; + if (settings.ContextMenu) + svg_rect.on('contextmenu', evnt => this.padContextMenu(evnt)); - gry2 = pmain.swap_xy ? 0 : height; - if (Number.isFinite(this.options.BaseLine)) { - if (this.options.BaseLine >= funcs.scale_ymin) - gry2 = Math.round(funcs.gry(this.options.BaseLine)); + if (!this.isBatchMode()) { + svg_rect.style('pointer-events', 'visibleFill') // get events also for not visible rect + .on('dblclick', evnt => this.enlargePad(evnt, true)) + .on('click', () => this.selectObjectPainter(this, null)) + .on('mouseenter', () => this.showObjectStatus()); + } } - for (i = left; i < right; i += di) { - x1 = xaxis.GetBinCoord(i); - x2 = xaxis.GetBinCoord(i+di); + this.createAttFill({ attr: this.#pad }); + + this.createAttLine({ attr: this.#pad, color0: this.#pad.fBorderMode === 0 ? 'none' : '' }); - if (funcs.logx && (x2 <= 0)) continue; + svg_pad.style('display', pad_visible ? null : 'none') + .attr('viewBox', `0 0 ${w} ${h}`) // due to svg + .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio + .attr('x', x) // due to svg + .attr('y', y) // due to svg + .attr('width', w) // due to svg + .attr('height', h) // due to svg + .property('draw_x', x) // this is to make similar with canvas + .property('draw_y', y) + .property('draw_width', w) + .property('draw_height', h); - grx1 = Math.round(funcs.grx(x1)); - grx2 = Math.round(funcs.grx(x2)); + this.#pad_x = x; + this.#pad_y = y; + this.#pad_width = w; + this.#pad_height = h; - y = histo.getBinContent(i+1); - if (funcs.logy && (y < funcs.scale_ymin)) continue; - gry1 = Math.round(funcs.gry(y)); + svg_rect.attr('d', `M0,0H${w}V${h}H0Z`) + .call(this.fillatt.func) + .call(this.lineatt.func); - w = grx2 - grx1; - grx1 += Math.round(this.options.BarOffset*w); - w = Math.round(this.options.BarWidth*w); + this.setFastDrawing(w, h); - if (pmain.swap_xy) - bars += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - else - bars += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - - if (this.options.BarStyle > 0) { - grx2 = grx1 + w; - w = Math.round(w / 10); - if (pmain.swap_xy) { - barsl += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - barsr += `M${gry2},${grx2}h${gry1-gry2}v${-w}h${gry2-gry1}z`; - } else { - barsl += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - barsr += `M${grx2},${gry1}h${-w}v${gry2-gry1}h${w}z`; - } - } + // special case of 3D canvas overlay + if (svg_pad.property('can3d') === constants$1.Embed3D.Overlay) { + this.selectDom().select('.draw3d_' + this.#pad_name) + .style('display', pad_visible ? '' : 'none'); } - if (this.fillatt.empty()) this.fillatt.setSolidColor('blue'); + if (this.alignButtons && btns) + this.alignButtons(btns, w, h); - if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) - .call(this.fillatt.func); - } + return pad_visible; + } - if (barsl) { - this.draw_g.append('svg:path') - .attr('d', barsl) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); + /** @summary Add pad interactive features like dragging and resize + * @private */ + addPadInteractive(/* cleanup = false */) { + if (isFunc(this.$userInteractive)) { + this.$userInteractive(); + delete this.$userInteractive; } + // if (this.isBatchMode()) + // return; + } - if (barsr) { - this.draw_g.append('svg:path') - .attr('d', barsr) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatHex()); - } - - return true; + /** @summary returns true if any objects beside sub-pads exists in the pad */ + hasObjectsToDraw() { + return this.#pad?.fPrimitives?.find(obj => obj._typename !== `${nsREX}RPadDisplayItem`); } - /** @summary Draw histogram as filled errors */ - async drawFilledErrors(handle, funcs /*, width, height */) { - this.createG(true); + /** @summary sync drawing/redrawing/resize of the pad + * @param {string} kind - kind of draw operation, if true - always queued + * @return {Promise} when pad is ready for draw operation or false if operation already queued + * @private */ + syncDraw(kind) { + const entry = { kind: kind || 'redraw' }; + if (this.#doing_draw === undefined) { + this.#doing_draw = [entry]; + return Promise.resolve(true); + } + // if queued operation registered, ignore next calls, indx === 0 is running operation + if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) + return false; + this.#doing_draw.push(entry); + return new Promise(resolveFunc => { + entry.func = resolveFunc; + }); + } - const left = handle.i1, right = handle.i2, di = handle.stepi, - histo = this.getHisto(), xaxis = this.getAxis('x'), - bins1 = [], bins2 = []; - let i, x, grx, y, yerr, gry; + /** @summary confirms that drawing is completed, may trigger next drawing immediately + * @private */ + confirmDraw() { + if (this.#doing_draw === undefined) + return console.warn('failure, should not happen'); + this.#doing_draw.shift(); + if (!this.#doing_draw.length) + this.#doing_draw = undefined; + else { + const entry = this.#doing_draw[0]; + if (entry.func) { + entry.func(); + delete entry.func; + } + } + } - for (i = left; i < right; i += di) { - x = xaxis.GetBinCoord(i+0.5); - if (funcs.logx && (x <= 0)) continue; - grx = Math.round(funcs.grx(x)); + /** @summary Draw single primitive */ + async drawObject(/* dom, obj, opt */) { + console.log('Not possible to draw object without loading of draw.mjs'); + return null; + } - y = histo.getBinContent(i+1); - yerr = histo.getBinError(i+1); - if (funcs.logy && (y-yerr < funcs.scale_ymin)) continue; + /** @summary Draw pad primitives + * @private */ + async drawPrimitives(indx) { + if (indx === undefined) { + if (this.isCanvas()) + this.#start_draw_tm = new Date().getTime(); - gry = Math.round(funcs.gry(y + yerr)); - bins1.push({ grx, gry }); + // set number of primitives + this.#num_primitives = this.#pad?.fPrimitives?.length ?? 0; - gry = Math.round(funcs.gry(y - yerr)); - bins2.unshift({ grx, gry }); + return this.syncDraw(true).then(() => this.drawPrimitives(0)); } - const path1 = buildSvgCurve(bins1, { line: this.options.ErrorKind !== 4 }), - path2 = buildSvgCurve(bins2, { line: this.options.ErrorKind !== 4, cmd: 'L' }); + if (!this.#pad || (indx >= this.#num_primitives)) { + this.confirmDraw(); + + if (this.#start_draw_tm) { + const spenttm = new Date().getTime() - this.#start_draw_tm; + if (spenttm > 3000) + console.log(`Canvas drawing took ${(spenttm * 1e-3).toFixed(2)}s`); + this.#start_draw_tm = undefined; + } - if (this.fillatt.empty()) this.fillatt.setSolidColor('blue'); + return; + } - this.draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(this.fillatt.func); + // handle used to invoke callback only when necessary + return this.drawObject(this, this.#pad.fPrimitives[indx], '').then(op => { + // mark painter as belonging to primitives + if (isObject(op)) + op._primitive = true; - return true; + return this.drawPrimitives(indx + 1); + }); } - /** @summary Draw 1D histogram as SVG */ - async draw1DBins() { - const pmain = this.getFramePainter(), - rect = pmain.getFrameRect(); - - if (!this.draw_content || (rect.width <= 0) || (rect.height <= 0)) { - this.removeG(); - return false; - } + /** @summary Provide autocolor + * @private */ + getAutoColor() { + const pal = this.getHistPalette(), + cnt = this.#auto_color_cnt++, + num = Math.max(this.#num_primitives - 1, 2); + return pal?.getColorOrdinal((cnt % num) / num) ?? 'blue'; + } - this.createHistDrawAttributes(); + /** @summary Process tooltip event in the pad + * @private */ + processPadTooltipEvent(pnt) { + const painters = [], hints = []; - const handle = this.prepareDraw({ extra: 1, only_indexes: true }), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); + // first count - how many processors are there + this.#painters?.forEach(obj => { + if (isFunc(obj.processTooltipEvent)) + painters.push(obj); + }); - if (this.options.Bar) - return this.drawBars(handle, funcs, rect.width, rect.height); + if (pnt) + pnt.nproc = painters.length; - if ((this.options.ErrorKind === 3) || (this.options.ErrorKind === 4)) - return this.drawFilledErrors(handle, funcs, rect.width, rect.height); + painters.forEach(obj => { + const hint = obj.processTooltipEvent(pnt) || { user_info: null }; + hints.push(hint); + if (pnt?.painters) + hint.painter = obj; + }); - return this.drawHistBins(handle, funcs, rect.width, rect.height); + return hints; } - /** @summary Draw histogram bins */ - async drawHistBins(handle, funcs, width, height) { - this.createG(true); + /** @summary Changes canvas dark mode + * @private */ + changeDarkMode(mode) { + this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); + } - const options = this.options, - left = handle.i1, - right = handle.i2, - di = handle.stepi, - histo = this.getHisto(), - want_tooltip = !this.isBatchMode() && settings.Tooltip, - xaxis = this.getAxis('x'), - exclude_zero = !options.Zero, - show_errors = options.Error, - show_line = options.Line, - show_text = options.Text; - let show_markers = options.Mark, - res = '', lastbin = false, - startx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, - path_fill = null, path_err = null, path_marker = null, path_line = null, - hints_err = null, - endx = '', endy = '', dend = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx, - text_font; - - if (show_errors && !show_markers && (this.v7EvalAttr('marker_style', 1) > 1)) - show_markers = true; + /** @summary Fill pad context menu + * @private */ + fillContextMenu(menu) { + const clname = this.isCanvas() ? 'RCanvas' : 'RPad'; - if (options.ErrorKind === 2) { - if (this.fillatt.empty()) show_markers = true; - else path_fill = ''; - } else if (options.Error) { - path_err = ''; - hints_err = want_tooltip ? '' : null; - } + menu.header(clname, `${urlClassPrefix}ROOT_1_1Experimental_1_1${clname}.html`); - if (show_line) path_line = ''; + menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - if (show_markers) { - // draw markers also when e2 option was specified - this.createv7AttMarker(); - if (this.markeratt.size > 0) { - // simply use relative move from point, can optimize in the future - path_marker = ''; - this.markeratt.resetPos(); - } else - show_markers = false; + if (!this.isCanvas(true)) { + // if not online canvas + menu.addAttributesMenu(this); + if (this.isCanvas()) { + menu.addSettingsMenu(false, false, arg => { + if (arg === 'dark') + this.changeDarkMode(); + }); + } } - if (show_text) { - text_font = this.v7EvalFont('text', { size: 20, color: 'black', align: 22 }); + menu.separator(); - if (!text_font.angle && !options.TextKind) { - const space = width / (right - left + 1); - if (space < 3 * text_font.size) { - text_font.setAngle(270); - text_font.setSize(Math.round(space*0.7)); - } - } + if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) + menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); - this.startTextDrawing(text_font, 'font'); + if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { + if (this.canStatusBar()) + menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); } - // if there are too many points, exclude many vertical drawings at the same X position - // instead define min and max value and made min-max drawing - let use_minmax = ((right-left) > 3*width); + if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) + menu.addchk((this.enlargeMain('state') === 'on'), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - if (options.ErrorKind === 1) { - const lw = this.lineatt.width + gStyle.fEndErrorSize; - endx = `m0,${lw}v${-2*lw}m0,${lw}`; - endy = `m${lw},0h${-2*lw}m${lw},0`; - dend = Math.floor((this.lineatt.width-1)/2); - } + const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); + menu.sub('Save as'); + ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`))); + menu.endsub(); - const draw_markers = show_errors || show_markers; + return true; + } - if (draw_markers || show_text || show_line) use_minmax = true; + /** @summary Show pad context menu + * @private */ + padContextMenu(evnt) { + if (evnt.stopPropagation) { + // this is normal event processing and not emulated jsroot event - const draw_bin = besti => { - bincont = histo.getBinContent(besti+1); - if (!exclude_zero || (bincont !== 0)) { - mx1 = Math.round(funcs.grx(xaxis.GetBinCoord(besti))); - mx2 = Math.round(funcs.grx(xaxis.GetBinCoord(besti+di))); - midx = Math.round((mx1+mx2)/2); - my = Math.round(funcs.gry(bincont)); - yerr1 = yerr2 = 20; - if (show_errors) { - binerr = histo.getBinError(besti+1); - yerr1 = Math.round(my - funcs.gry(bincont + binerr)); // up - yerr2 = Math.round(funcs.gry(bincont - binerr) - my); // down - } + evnt.stopPropagation(); // disable main context menu + evnt.preventDefault(); // disable browser context menu - if (show_text && (bincont !== 0)) { - const lbl = (bincont === Math.round(bincont)) ? bincont.toString() : floatToString(bincont, gStyle.fPaintTextFormat); + this.getFramePainter()?.setLastEventPos(); + } - if (text_font.angle) - this.drawText({ align: 12, x: midx, y: Math.round(my - 2 - text_font.size / 5), text: lbl, latex: 0 }); - else - this.drawText({ x: Math.round(mx1 + (mx2 - mx1) * 0.1), y: Math.round(my - 2 - text_font.size), width: Math.round((mx2 - mx1) * 0.8), height: text_font.size, text: lbl, latex: 0 }); - } - - if (show_line && (path_line !== null)) - path_line += ((path_line.length === 0) ? 'M' : 'L') + midx + ',' + my; - - if (draw_markers) { - if ((my >= -yerr1) && (my <= height + yerr2)) { - if (path_fill !== null) - path_fill += `M${mx1},${my-yerr1}h${mx2-mx1}v${yerr1+yerr2+1}h${mx1-mx2}z`; - if (path_marker !== null) - path_marker += this.markeratt.create(midx, my); - if (path_err !== null) { - let edx = 5; - if (this.options.errorX > 0) { - edx = Math.round((mx2-mx1)*this.options.errorX); - const mmx1 = midx - edx, mmx2 = midx + edx; - path_err += `M${mmx1+dend},${my}${endx}h${mmx2-mmx1-2*dend}${endx}`; - } - path_err += `M${midx},${my-yerr1+dend}${endy}v${yerr1+yerr2-2*dend}${endy}`; - if (hints_err !== null) - hints_err += `M${midx-edx},${my-yerr1}h${2*edx}v${yerr1+yerr2}h${-2*edx}z`; - } - } - } - } - }; + createMenu(evnt, this).then(menu => { + this.fillContextMenu(menu); + return this.fillObjectExecMenu(menu); + }).then(menu => menu.show()); + } - for (i = left; i <= right; i += di) { - x = xaxis.GetBinCoord(i); + /** @summary Redraw legend object + * @desc Used when object attributes are changed to ensure that legend is up to date + * @private */ + async redrawLegend() {} - if (funcs.logx && (x <= 0)) continue; + /** @summary Deliver mouse move or click event to the web canvas + * @private */ + deliverWebCanvasEvent() {} - grx = Math.round(funcs.grx(x)); + /** @summary Redraw pad means redraw ourself + * @return {Promise} when redrawing ready */ + async redrawPad(reason) { + const sync_promise = this.syncDraw(reason); + if (sync_promise === false) { + console.log('Prevent RPad redrawing'); + return false; + } - lastbin = (i > right - di); + let showsubitems = true; + const redrawNext = indx => { + while (indx < this.#painters.length) { + const sub = this.#painters[indx++]; + let res = 0; + if (showsubitems || isPadPainter(sub)) + res = sub.redraw(reason); - if (lastbin && (left < right)) - gry = curry; - else { - y = histo.getBinContent(i+1); - gry = Math.round(funcs.gry(y)); + if (isPromise(res)) + return res.then(() => redrawNext(indx)); } + return true; + }; - if (res.length === 0) { - bestimin = bestimax = i; - prevx = startx = currx = grx; - prevy = curry_min = curry_max = curry = gry; - res = 'M'+currx+','+curry; - } else - if (use_minmax) { - if ((grx === currx) && !lastbin) { - if (gry < curry_min) bestimax = i; else - if (gry > curry_max) bestimin = i; - curry_min = Math.min(curry_min, gry); - curry_max = Math.max(curry_max, gry); - curry = gry; - } else { - if (draw_markers || show_text || show_line) { - if (bestimin === bestimax) draw_bin(bestimin); else - if (bestimin < bestimax) { draw_bin(bestimin); draw_bin(bestimax); } else { - draw_bin(bestimax); draw_bin(bestimin); - } - } + return sync_promise.then(() => { + if (this.isCanvas()) + this.createCanvasSvg(2); + else + showsubitems = this.createPadSvg(true); - // when several points as same X differs, need complete logic - if (!draw_markers && ((curry_min !== curry_max) || (prevy !== curry_min))) { - if (prevx !== currx) - res += 'h'+(currx-prevx); + return redrawNext(0); + }).then(() => { + this.addPadInteractive(); + if (getActivePad() === this) + this.getCanvPainter()?.producePadEvent('padredraw', this); + this.confirmDraw(); + return true; + }); + } - if (curry === curry_min) { - if (curry_max !== prevy) - res += 'v' + (curry_max - prevy); - if (curry_min !== curry_max) - res += 'v' + (curry_min - curry_max); - } else { - if (curry_min !== prevy) - res += 'v' + (curry_min - prevy); - if (curry_max !== curry_min) - res += 'v' + (curry_max - curry_min); - if (curry !== curry_max) - res += 'v' + (curry - curry_max); - } + /** @summary redraw pad */ + redraw(reason) { + return this.redrawPad(reason); + } - prevx = currx; - prevy = curry; - } - if (lastbin && (prevx !== grx)) - res += 'h'+(grx-prevx); + /** @summary Checks if pad should be redrawn by resize + * @private */ + needRedrawByResize() { + const elem = this.getPadSvg(); + if (!elem.empty() && elem.property('can3d') === constants$1.Embed3D.Overlay) + return true; - bestimin = bestimax = i; - curry_min = curry_max = curry = gry; - currx = grx; - } - } else - if ((gry !== curry) || lastbin) { - if (grx !== currx) res += 'h'+(grx-currx); - if (gry !== curry) res += 'v'+(gry-curry); - curry = gry; - currx = grx; + for (let i = 0; i < this.#painters.length; ++i) { + if (isFunc(this.#painters[i].needRedrawByResize)) { + if (this.#painters[i].needRedrawByResize()) + return true; } } - const fill_for_interactive = !this.isBatchMode() && this.fillatt.empty() && options.Hist && settings.Tooltip && !draw_markers && !show_line; - let h0 = height + 3; - if (!fill_for_interactive) { - const gry0 = Math.round(funcs.gry(0)); - if (gry0 <= 0) - h0 = -3; - else if (gry0 < height) - h0 = gry0; - } - const close_path = `L${currx},${h0}H${startx}Z`; + return false; + } - if (draw_markers || show_line) { - if (path_fill) { - this.draw_g.append('svg:path') - .attr('d', path_fill) - .call(this.fillatt.func); - } + /** @summary Check resize of canvas */ + checkCanvasResize(size, force) { + if (this._ignore_resize || !this.isTopPad()) + return false; - if (path_err) { - this.draw_g.append('svg:path') - .attr('d', path_err) - .call(this.lineatt.func); - } + const sync_promise = this.syncDraw('canvas_resize'); + if (sync_promise === false) + return false; - if (hints_err) { - this.draw_g.append('svg:path') - .attr('d', hints_err) - .style('fill', 'none') - .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); - } + if ((size === true) || (size === false)) { + force = size; + size = null; + } - if (path_line) { - if (!this.fillatt.empty() && !options.Hist) { - this.draw_g.append('svg:path') - .attr('d', path_line + close_path) - .call(this.fillatt.func); - } + if (isObject(size) && size.force) + force = true; - this.draw_g.append('svg:path') - .attr('d', path_line) - .style('fill', 'none') - .call(this.lineatt.func); - } + if (!force) + force = this.needRedrawByResize(); - if (path_marker) { - this.draw_g.append('svg:path') - .attr('d', path_marker) - .call(this.markeratt.func); + let changed = false; + const redrawNext = indx => { + if (!changed || (indx >= this.#painters.length)) { + this.confirmDraw(); + return changed; } - } else if (res && options.Hist) { - this.draw_g.append('svg:path') - .attr('d', res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) - .style('stroke-linejoin', 'miter') - .call(this.lineatt.func) - .call(this.fillatt.func); - } - - return show_text ? this.finishTextDrawing() : true; - } - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(bin) { - const tips = [], - name = this.getObjectHint(), - pmain = this.getFramePainter(), - histo = this.getHisto(), - xaxis = this.getAxis('x'), - di = this.isDisplayItem() ? histo.stepx : 1, - x1 = xaxis.GetBinCoord(bin), - x2 = xaxis.GetBinCoord(bin+di), - xlbl = this.getAxisBinTip('x', bin, di); + return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); + }; - let cont = histo.getBinContent(bin+1); - if (name) tips.push(name); + return sync_promise.then(() => { + changed = this.createCanvasSvg(force ? 2 : 1, size); - if (this.options.Error || this.options.Mark) { - tips.push(`x = ${xlbl}`, `y = ${pmain.axisAsText('y', cont)}`); - if (this.options.Error) { - if (xlbl[0] === '[') tips.push('error x = ' + ((x2 - x1) / 2).toPrecision(4)); - tips.push('error y = ' + histo.getBinError(bin + 1).toPrecision(4)); + if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { + if (this.#resize_tmout) + clearTimeout(this.#resize_tmout); + this.#resize_tmout = setTimeout(() => { + this.#resize_tmout = undefined; + if (!this.#pad?.fWinSize) + return; + const cw = this.getPadWidth(), ch = this.getPadHeight(); + if ((cw > 0) && (ch > 0) && ((this.#pad.fWinSize[0] !== cw) || (this.#pad.fWinSize[1] !== ch))) { + this.#pad.fWinSize[0] = cw; + this.#pad.fWinSize[1] = ch; + this.sendWebsocket(`RESIZED:[${cw},${ch}]`); + } + }, 1000); // long enough delay to prevent multiple occurrence } - } else { - tips.push(`bin = ${bin+1}`, `x = ${xlbl}`); - if (histo.$baseh) cont -= histo.$baseh.getBinContent(bin+1); - const lbl = 'entries = ' + (di > 1 ? '~' : ''); - if (cont === Math.round(cont)) - tips.push(lbl + cont); - else - tips.push(lbl + floatToString(cont, gStyle.fStatFormat)); - } - return tips; + // if canvas changed, redraw all its subitems. + // If redrawing was forced for canvas, same applied for sub-elements + return redrawNext(0); + }); } - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + /** @summary update RPad object + * @private */ + updateObject(obj) { + if (!obj) + return false; - if (!pnt || !this.draw_content || this.options.Mode3D || !this.draw_g) { - ttrect?.remove(); - return null; - } + this.#pad.fStyle = obj.fStyle; + this.#pad.fAttr = obj.fAttr; - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - width = pmain.getFrameWidth(), - height = pmain.getFrameHeight(), - histo = this.getHisto(), xaxis = this.getAxis('x'), - left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 2); + if (this.isCanvas()) { + this.#pad.fTitle = obj.fTitle; + this.#pad.fWinSize = obj.fWinSize; + } else { + this.#pad.fPos = obj.fPos; + this.#pad.fSize = obj.fSize; + } - let findbin = null, show_rect, - grx1, grx2, gry1, gry2, gapx = 2, - l = left, r = right; + return true; + } - function GetBinGrX(i) { - const xx = xaxis.GetBinCoord(i); - return (funcs.logx && (xx <= 0)) ? null : funcs.grx(xx); + /** @summary Add object painter to list of primitives + * @private */ + addObjectPainter(objpainter, lst, indx) { + if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { + // keep snap id in painter, will be used for the + if (this.#painters.indexOf(objpainter) < 0) + this.#painters.push(objpainter); + objpainter.assignSnapId(lst[indx].fObjectID); + if (!objpainter.rstyle) + objpainter.rstyle = lst[indx].fStyle || this.rstyle; } + } - function GetBinGrY(i) { - const yy = histo.getBinContent(i + 1); - if (funcs.logy && (yy < funcs.scale_ymin)) - return funcs.swap_xy ? -1000 : 10*height; - return Math.round(funcs.gry(yy)); + /** @summary Extract properties from TObjectDisplayItem */ + extractTObjectProp(snap) { + if (snap.fColIndex && snap.fColValue) { + const colors = this.getColors() || getRootColors(); + for (let k = 0; k < snap.fColIndex.length; ++k) + colors[snap.fColIndex[k]] = convertColor(snap.fColValue[k]); } - const pnt_x = funcs.swap_xy ? pnt.y : pnt.x, - pnt_y = funcs.swap_xy ? pnt.x : pnt.y; + // painter used only for evaluation of attributes + const pattr = new RObjectPainter(), obj = snap.fObject; + pattr.assignObject(snap); + pattr.csstype = snap.fCssType; + pattr.rstyle = snap.fStyle; - while (l < r-1) { - const m = Math.round((l+r)*0.5), - xx = GetBinGrX(m); - if ((xx === null) || (xx < pnt_x - 0.5)) - if (funcs.swap_xy) r = m; else l = m; - else if (xx > pnt_x + 0.5) - if (funcs.swap_xy) l = m; else r = m; - else { l++; r--; } - } + snap.fOption = pattr.v7EvalAttr('options', ''); - findbin = r = l; - grx1 = GetBinGrX(findbin); + const extract_color = (member_name, attr_name) => { + const col = pattr.v7EvalColor(attr_name, ''); + if (col) + obj[member_name] = addColor(col, this.getColors()); + }; - if (funcs.swap_xy) { - while ((l > left) && (GetBinGrX(l-1) < grx1 + 2)) --l; - while ((r < right) && (GetBinGrX(r+1) > grx1 - 2)) ++r; - } else { - while ((l > left) && (GetBinGrX(l-1) > grx1 - 2)) --l; - while ((r < right) && (GetBinGrX(r+1) < grx1 + 2)) ++r; + // handle TAttLine + if ((obj.fLineColor !== undefined) && (obj.fLineWidth !== undefined) && (obj.fLineStyle !== undefined)) { + extract_color('fLineColor', 'line_color'); + obj.fLineWidth = pattr.v7EvalAttr('line_width', obj.fLineWidth); + obj.fLineStyle = pattr.v7EvalAttr('line_style', obj.fLineStyle); } - if (l < r) { - // many points can be assigned with the same cursor position - // first try point around mouse y - let best = height; - for (let m = l; m <= r; m++) { - const dist = Math.abs(GetBinGrY(m) - pnt_y); - if (dist < best) { best = dist; findbin = m; } - } - - // if best distance still too far from mouse position, just take from between - if (best > height/10) - findbin = Math.round(l + (r-l) / height * pnt_y); - - grx1 = GetBinGrX(findbin); + // handle TAttFill + if ((obj.fFillColor !== undefined) && (obj.fFillStyle !== undefined)) { + extract_color('fFillColor', 'fill_color'); + obj.fFillStyle = pattr.v7EvalAttr('fill_style', obj.fFillStyle); } - grx1 = Math.round(grx1); - grx2 = Math.round(GetBinGrX(findbin+1)); - - if (this.options.Bar) { - const w = grx2 - grx1; - grx1 += Math.round(this.options.BarOffset*w); - grx2 = grx1 + Math.round(this.options.BarWidth*w); + // handle TAttMarker + if ((obj.fMarkerColor !== undefined) && (obj.fMarkerStyle !== undefined) && (obj.fMarkerSize !== undefined)) { + extract_color('fMarkerColor', 'marker_color'); + obj.fMarkerStyle = pattr.v7EvalAttr('marker_style', obj.fMarkerStyle); + obj.fMarkerSize = pattr.v7EvalAttr('marker_size', obj.fMarkerSize); } - if (grx1 > grx2) - [grx1, grx2] = [grx2, grx1]; - - if (this.isDisplayItem() && ((findbin <= histo.dx) || (findbin >= histo.dx + histo.nx))) { - // special case when zoomed out of scale and bin is not available - ttrect.remove(); - return null; + // handle TAttText + if ((obj.fTextColor !== undefined) && (obj.fTextAlign !== undefined) && (obj.fTextAngle !== undefined) && (obj.fTextSize !== undefined)) { + extract_color('fTextColor', 'text_color'); + obj.fTextAlign = pattr.v7EvalAttr('text_align', obj.fTextAlign); + obj.fTextAngle = pattr.v7EvalAttr('text_angle', obj.fTextAngle); + obj.fTextSize = pattr.v7EvalAttr('text_size', obj.fTextSize); + // TODO: v7 font handling differs much from v6, ignore for the moment } + } - const midx = Math.round((grx1 + grx2)/2), - midy = gry1 = gry2 = GetBinGrY(findbin); - - if (this.options.Bar) { - show_rect = true; - - gapx = 0; - - gry1 = Math.round(funcs.gry(((this.options.BaseLine!==false) && (this.options.BaseLine > funcs.scale_ymin)) ? this.options.BaseLine : funcs.scale_ymin)); - - if (gry1 > gry2) - [gry1, gry2] = [gry2, gry1]; - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if (this.options.Error || this.options.Mark) { - show_rect = true; - - let msize = 3; - if (this.markeratt) msize = Math.max(msize, this.markeratt.getFullSize()); - - if (this.options.Error) { - const cont = histo.getBinContent(findbin+1), - binerr = histo.getBinError(findbin+1); - - gry1 = Math.round(funcs.gry(cont + binerr)); // up - gry2 = Math.round(funcs.gry(cont - binerr)); // down - - const dx = (grx2-grx1)*this.options.errorX; - grx1 = Math.round(midx - dx); - grx2 = Math.round(midx + dx); - } - - // show at least 6 pixels as tooltip rect - if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; } - - gry1 = Math.min(gry1, midy - msize); - gry2 = Math.max(gry2, midy + msize); - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if (this.options.Line) - - show_rect = false; - - else { - // if histogram alone, use old-style with rects - // if there are too many points at pixel, use circle - show_rect = (pnt.nproc === 1) && (right-left < width); - - if (show_rect) { - gry2 = height; - - if (!this.fillatt.empty()) { - gry2 = Math.min(height, Math.max(0, Math.round(funcs.gry(0)))); - if (gry2 < gry1) - [gry1, gry2] = [gry2, gry1]; - } - - // for mouse events pointer should be between y1 and y2 - if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) findbin = null; - } + /** @summary Function called when drawing next snapshot from the list + * @return {Promise} with pad painter when ready + * @private */ + async drawNextSnap(lst, pindx, indx) { + if (indx === undefined) { + indx = -1; + // flag used to prevent immediate pad redraw during first draw + this.#num_primitives = lst?.length ?? 0; + this.#auto_color_cnt = 0; } - if (findbin !== null) { - // if bin on boundary found, check that x position is ok - if ((findbin === left) && (grx1 > pnt_x + gapx)) findbin = null; else - if ((findbin === right-1) && (grx2 < pnt_x - gapx)) findbin = null; else - // if bars option used check that bar is not match - if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) findbin = null; else - // exclude empty bin if empty bins suppressed - if (!this.options.Zero && (histo.getBinContent(findbin+1) === 0)) findbin = null; - } + delete this.next_rstyle; - if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) { - ttrect.remove(); - return null; + ++indx; // change to the next snap + + if (!lst || indx >= lst.length) { + this.#auto_color_cnt = undefined; + return this; } - const res = { name: 'histo', title: histo.fTitle, - x: midx, y: midy, exact: true, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(findbin) }; + const snap = lst[indx], is_subpad = snap._typename === `${nsREX}RPadDisplayItem`; - if (pnt.disabled) { - // case when tooltip should not highlight bin + // empty object, no need to do something, take next + if (snap.fDummy) + return this.drawNextSnap(lst, pindx + 1, indx); - ttrect.remove(); - res.changed = true; - } else if (show_rect) { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); + if (snap._typename === `${nsREX}TObjectDisplayItem`) { + // identifier used in TObjectDrawable + + if (snap.fKind === webSnapIds.kStyle) { + Object.assign(gStyle, snap.fObject); + return this.drawNextSnap(lst, pindx, indx); } - res.changed = ttrect.property('current_bin') !== findbin; + if (snap.fKind === webSnapIds.kColors) { + const colors = [], arr = snap.fObject.arr; + for (let n = 0; n < arr.length; ++n) { + const name = arr[n].fString, p = name.indexOf('='); + if (p > 0) + colors[parseInt(name.slice(0, p))] = convertColor(name.slice(p + 1)); + } - if (res.changed) { - ttrect.attr('x', pmain.swap_xy ? gry1 : grx1) - .attr('width', pmain.swap_xy ? gry2-gry1 : grx2-grx1) - .attr('y', pmain.swap_xy ? grx1 : gry1) - .attr('height', pmain.swap_xy ? grx2-grx1 : gry2-gry1) - .style('opacity', '0.3') - .property('current_bin', findbin); + this.setColors(colors); + // set global list of colors + // adoptRootColors(ListOfColors); + return this.drawNextSnap(lst, pindx, indx); } - res.exact = (Math.abs(midy - pnt_y) <= 5) || ((pnt_y>=gry1) && (pnt_y<=gry2)); + if (snap.fKind === webSnapIds.kPalette) { + const arr = snap.fObject.arr, palette = []; + for (let n = 0; n < arr.length; ++n) + palette[n] = arr[n].fString; + this.#custom_palette = new ColorPalette(palette); + return this.drawNextSnap(lst, pindx, indx); + } - res.menu = res.exact; // one could show context menu - // distance to middle point, use to decide which menu to activate - res.menu_dist = Math.sqrt((midx-pnt_x)**2 + (midy-pnt_y)**2); - } else { - const radius = this.lineatt.width + 3; + if (snap.fKind === webSnapIds.kFont) + return this.drawNextSnap(lst, pindx, indx); - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .attr('r', radius) - .call(this.lineatt.func) - .call(this.fillatt.func); + if (!this.getFramePainter()) { + // draw dummy frame which is not provided by RCanvas + return this.drawObject(this, { _typename: clTFrame, $dummy: true }, '') + .then(() => this.drawNextSnap(lst, pindx, indx - 1)); } - res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius); + this.extractTObjectProp(snap); + } - res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram - res.menu_dist = Math.sqrt((midx-pnt.x)**2 + (midy-pnt.y)**2); + // try to locate existing object painter, only allowed when redrawing pad snap + let objpainter, promise; - res.changed = ttrect.property('current_bin') !== findbin; + while ((pindx !== undefined) && (pindx < this.#painters.length)) { + const subp = this.#painters[pindx++]; - if (res.changed) { - ttrect.attr('cx', midx) - .attr('cy', midy) - .property('current_bin', findbin); + if (subp.getSnapId() === snap.fObjectID) { + objpainter = subp; + break; + } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { + console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fDrawable?._typename}`); + break; } } - if (res.changed) { - res.user_info = { obj: histo, name: 'histo', - bin: findbin, cont: histo.getBinContent(findbin+1), - grx: midx, gry: midy }; - } - - return res; - } - - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - menu.add('Auto zoom-in', () => this.autoZoom()); + if (objpainter) { + if (is_subpad) + promise = objpainter.redrawPadSnap(snap); + else if (objpainter.updateObject(snap.fDrawable || snap.fObject || snap, snap.fOption || '', true)) + promise = objpainter.redraw(); + } else if (is_subpad) { + const padpainter = new RPadPainter(this, snap, '', false, 'webpad'); + padpainter.assignSnapId(snap.fObjectID); + padpainter.rstyle = snap.fStyle; - const opts = this.getSupportedDrawOptions(); + padpainter.createPadSvg(); - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); + if (snap.fPrimitives?.length) + padpainter.addPadButtons(); - this.decodeOptions(arg); // obsolete, should be implemented differently + pindx++; // new painter will be add + promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); + } else { + // will be used in addToPadPrimitives to assign style to sub-painters + this.next_rstyle = snap.fStyle || this.rstyle; + pindx++; // new painter will be add - if (this.options.need_fillcol && this.fillatt?.empty()) - this.fillatt.change(5, 1001); + // TODO - fDrawable is v7, fObject from v6, maybe use same data member? + promise = this.drawObject(this, snap.fDrawable || snap.fObject || snap, snap.fOption || '') + .then(objp => this.addObjectPainter(objp, lst, indx)); + } - // redraw all objects - this.interactiveRedraw('pad', 'drawopt'); - }); + return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next } - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - let left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 1); - const dist = right - left, histo = this.getHisto(), xaxis = this.getAxis('x'); - - if (dist === 0) return; - - // first find minimum - let min = histo.getBinContent(left + 1); - for (let indx = left; indx < right; ++indx) - min = Math.min(min, histo.getBinContent(indx+1)); - if (min > 0) return; // if all points positive, no chance for autoscale - - while ((left < right) && (histo.getBinContent(left+1) <= min)) ++left; - while ((left < right) && (histo.getBinContent(right) <= min)) --right; - - // if singular bin - if ((left === right-1) && (left > 2) && (right < this.nbinsx-2)) { - --left; ++right; + /** @summary Search painter with specified snapid, also sub-pads are checked + * @private */ + findSnap(snapid, onlyid) { + function check(checkid) { + if (!checkid || !isStr(checkid)) + return false; + if (checkid === snapid) + return true; + return onlyid && (checkid.length > snapid.length) && + (checkid.indexOf(snapid) === (checkid.length - snapid.length)); } - if ((right - left < dist) && (left < right)) - return this.getFramePainter().zoom(xaxis.GetBinCoord(left), xaxis.GetBinCoord(right)); - } + if (check(this.getSnapId())) + return this; - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - const xaxis = this.getAxis('x'); + if (!this.#painters) + return null; - if ((axis === 'x') && (xaxis.FindBin(max, 0.5) - xaxis.FindBin(min, 0) > 1)) return true; + for (let k = 0; k < this.#painters.length; ++k) { + let sub = this.#painters[k]; - if ((axis === 'y') && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true; + if (!onlyid && isFunc(sub.findSnap)) + sub = sub.findSnap(snapid); + else if (!check(sub.getSnapId())) + sub = null; - return false; - } + if (sub) + return sub; + } - /** @summary Call appropriate draw function */ - async callDrawFunc(reason) { - const main = this.getFramePainter(); + return null; + } - if (main && (main.mode3d !== this.options.Mode3D) && !this.isMainPainter()) - this.options.Mode3D = main.mode3d; + /** @summary Redraw pad snap + * @desc Online version of drawing pad primitives + * @return {Promise} with pad painter */ + async redrawPadSnap(snap) { + // for the pad/canvas display item contains list of primitives plus pad attributes - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } + if (!snap || !snap.fPrimitives) + return this; - /** @summary Draw in 2d */ - async draw2D(reason) { - this.clear3DScene(); + if (this.isCanvas(true) && snap.fTitle && !this.embed_canvas && (typeof document !== 'undefined')) + document.title = snap.fTitle; - return this.drawFrameAxes().then(res => { - return res ? this.drawingBins(reason) : false; - }).then(res => { - if (res) - return this.draw1DBins().then(() => this.addInteractivity()); - }).then(() => this); - } + if (!this.hasSnapId()) { + // first time getting snap, create all gui elements first - /** @summary Draw in 3d */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/RH1Painter.mjs'); - return this.draw2D(reason); - } + this.assignSnapId(snap.fObjectID); - /** @summary Readraw histogram */ - async redraw(reason) { - return this.callDrawFunc(reason); - } + this.assignObject(snap); + this.#pad = snap; - static async _draw(painter, opt) { - return ensureRCanvas(painter).then(() => { - painter.setAsMainPainter(); + if (this.isBatchMode() && this.isCanvas()) + this.#fixed_size = true; - painter.options = { Hist: false, Bar: false, BarStyle: 0, - Error: false, ErrorKind: -1, errorX: gStyle.fErrorX, - Zero: false, Mark: false, - Line: false, Fill: false, Lego: 0, Surf: 0, - Text: false, TextAngle: 0, TextKind: '', AutoColor: 0, - BarOffset: 0, BarWidth: 1, BaseLine: false, - Mode3D: false, FrontBox: false, BackBox: false }; - - const d = new DrawOptions(opt); - if (d.check('R3D_', true)) - painter.options.Render3D = constants$1.Render3D.fromString(d.part.toLowerCase()); - - const kind = painter.v7EvalAttr('kind', 'hist'), - sub = painter.v7EvalAttr('sub', 0), - has_main = !!painter.getMainPainter(), - o = painter.options; - - o.Text = painter.v7EvalAttr('drawtext', false); - o.BarOffset = painter.v7EvalAttr('baroffset', 0.0); - o.BarWidth = painter.v7EvalAttr('barwidth', 1.0); - o.second_x = has_main && painter.v7EvalAttr('secondx', false); - o.second_y = has_main && painter.v7EvalAttr('secondy', false); + const mainid = this.selectDom().attr('id'); - switch (kind) { - case 'bar': o.Bar = true; o.BarStyle = sub; break; - case 'err': o.Error = true; o.ErrorKind = sub; break; - case 'p': o.Mark = true; break; - case 'l': o.Line = true; break; - case 'lego': o.Lego = sub > 0 ? 10+sub : 12; o.Mode3D = true; break; - default: o.Hist = true; + if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { + this.brlayout = new BrowserLayout(mainid, null, this); + this.brlayout.create(mainid, true); + this.setDom(this.brlayout.drawing_divid()); // need to create canvas + registerForResize(this.brlayout); } - painter.scanContent(); + this.createCanvasSvg(0); + this.addPadButtons(true); - return painter.callDrawFunc(); - }); - } + return this.drawNextSnap(snap.fPrimitives).then(() => { + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); + return this; + }); + } - /** @summary draw RH1 object */ - static async draw(dom, histo, opt) { - return RH1Painter._draw(new RH1Painter(dom, histo), opt); - } + // update only pad/canvas attributes + this.updateObject(snap); -}; // class RH1Painter + // apply all changes in the object (pad or canvas) + if (this.isCanvas()) + this.createCanvasSvg(2); + else + this.createPadSvg(true); -class RH1Painter extends RH1Painter$2 { + let missmatch = false, i = 0, k = 0; - /** @summary Draw 1-D histogram in 3D mode */ - draw3D(reason) { - this.mode3d = true; + // match painters with new list of primitives + while (k < this.#painters.length) { + const sub = this.#painters[k]; - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(), // is main histogram - zmult = 1 + 2*gStyle.fHistTopMargin; - let pr = Promise.resolve(this); + // skip check secondary painters or painters without snapid + // also frame painter will be excluded here + if (!sub.hasSnapId() || sub.isSecondary()) { + k++; + continue; // look only for painters with snapid + } - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - return pr; - } + if (i >= snap.fPrimitives.length) + break; - this.deleteAttr(); - this.scanContent(true); // may be required for axis drawings + const prim = snap.fPrimitives[i]; - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, null, this.ymin, this.ymax, null, 0, 0); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { use_y_for_z: true, zmult, zoom: settings.Zooming, ndim: 1, draw: true, v7: true }); - }); + if (prim.fObjectID === sub.getSnapId()) { + i++; + k++; + } else if (prim.fDummy || !prim.fObjectID || ((prim._typename === `${nsREX}TObjectDisplayItem`) && ((prim.fKind === webSnapIds.kStyle) || (prim.fKind === webSnapIds.kColors) || (prim.fKind === webSnapIds.kPalette) || (prim.fKind === webSnapIds.kFont)))) { + // ignore primitives without snapid or which are not produce drawings + i++; + } else { + missmatch = true; + break; + } } - if (!main.mode3d) - return pr; + let cnt = 1000; + // remove painters without primitives, limit number of checks + while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { + if (this.removePrimitive(k) === -111) + missmatch = true; + } + if (cnt < 0) + missmatch = true; - return pr.then(() => this.drawingBins(reason)).then(() => { - // called when bins received from server, must be reentrant - const main = this.getFramePainter(); + if (missmatch) { + const old_painters = this.#painters; + this.#painters = []; + old_painters.forEach(objp => objp.cleanup()); + this.setMainPainter(undefined, true); + if (isFunc(this.removePadButtons)) + this.removePadButtons(); + this.addPadButtons(true); + } - drawBinsLego(this, true); - this.updatePaletteDraw(); - main.render3D(); - main.addKeysHandler(); + return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { + this.addPadInteractive(); + if (getActivePad() === this) + this.getCanvPainter()?.producePadEvent('padredraw', this); + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); return this; }); } - /** @summary draw RH1 object */ - static async draw(dom, histo, opt) { - return RH1Painter._draw(new RH1Painter(dom, histo), opt); + /** @summary Create image for the pad + * @desc Used with web-based canvas to create images for server side + * @return {Promise} with image data, coded with btoa() function + * @private */ + async createImage(format) { + if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { + return this.produceImage(true, format).then(res => { + if (!res || (format === 'svg')) + return res; + const separ = res.indexOf('base64,'); + return (separ > 0) ? res.slice(separ + 7) : ''; + }); + } + return ''; } -} // class RH1Painter + /** @summary Show context menu for specified item + * @private */ + itemContextMenu(name) { + const rrr = this.getPadSvg().node().getBoundingClientRect(), + evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; -var RH1Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -RH1Painter: RH1Painter -}); + // use timeout to avoid conflict with mouse click and automatic menu close + if (name === 'pad') + return postponePromise(() => this.padContextMenu(evnt), 50); -/** - * @summary Painter for RH2 classes - * - * @private - */ + let selp = null, selkind; -let RH2Painter$2 = class RH2Painter extends RHistPainter { + switch (name) { + case 'xaxis': + case 'yaxis': + case 'zaxis': + selp = this.getMainPainter(); + selkind = name[0]; + break; + case 'frame': + selp = this.getFramePainter(); + break; + default: { + const indx = parseInt(name); + if (Number.isInteger(indx)) + selp = this.#painters[indx]; + } + } - /** @summary constructor - * @param {object|string} dom - DOM element or id - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = true; - } + if (!isFunc(selp?.fillContextMenu)) + return; - /** @summary Cleanup painter */ - cleanup() { - delete this.tt_handle; - super.cleanup(); + return createMenu(evnt, selp).then(menu => { + const offline_menu = selp.fillContextMenu(menu, selkind); + if (offline_menu || selp.getSnapId()) + selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); + }); } - /** @summary Returns histogram dimension */ - getDimension() { return 2; } + /** @summary Save pad in specified format + * @desc Used from context menu */ + saveAs(kind, full_canvas, filename) { + if (!filename) + filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; - /** @summary Toggle projection */ - toggleProjection(kind, width) { - if ((kind === 'Projections') || (kind === 'Off')) - kind = ''; + this.produceImage(full_canvas, kind).then(imgdata => { + if (!imgdata) + return console.error(`Fail to produce image ${filename}`); - let widthX = width, widthY = width; + if ((browser.qt6 || browser.cef3) && this.getSnapId()) { + console.warn(`sending file ${filename} to server`); + let res = imgdata; + if (kind !== 'svg') { + const separ = res.indexOf('base64,'); + res = (separ > 0) ? res.slice(separ + 7) : ''; + } + if (res) + this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); + } else + saveFile(filename, (kind !== 'svg') ? imgdata : prSVG + encodeURIComponent(imgdata)); + }); + } - if (isStr(kind) && (kind.indexOf('XY') === 0)) { - const ws = kind.length > 2 ? kind.slice(2) : ''; - kind = 'XY'; - widthX = widthY = parseInt(ws); - } else if (isStr(kind) && (kind.length > 1)) { - const ps = kind.indexOf('_'); - if ((ps > 0) && (kind[0] === 'X') && (kind[ps+1] === 'Y')) { - widthX = parseInt(kind.slice(1, ps)) || 1; - widthY = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps+1] === 'X')) { - widthY = parseInt(kind.slice(1, ps)) || 1; - widthX = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else { - widthX = widthY = parseInt(kind.slice(1)) || 1; - kind = kind[0]; - } - } + /** @summary Search active pad + * @return {Object} pad painter for active pad */ + findActivePad() { return null; } - if (!widthX && !widthY) - widthX = widthY = 1; + /** @summary Produce image for the pad + * @return {Promise} with created image */ + async produceImage(full_canvas, file_format, args) { + const use_frame = (full_canvas === 'frame'), + elem = use_frame ? this.getFrameSvg() : (full_canvas ? this.getCanvSvg() : this.getPadSvg()), + painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, + items = []; // keep list of replaced elements, which should be moved back at the end - if (kind && (this.is_projection === kind)) { - if ((this.projection_widthX === widthX) && (this.projection_widthY === widthY)) - kind = ''; - else { - this.projection_widthX = widthX; - this.projection_widthY = widthY; - return; + if (elem.empty()) + return ''; + + if (use_frame || !full_canvas) { + const defs = this.getCanvSvg().selectChild('.canvas_defs'); + if (!defs.empty()) { + items.push({ prnt: this.getCanvSvg(), defs }); + elem.node().insertBefore(defs.node(), elem.node().firstChild); } } - delete this.proj_hist; + if (!use_frame) { + // do not make transformations for the frame + painter.forEachPainterInPad(pp => { + const item = { prnt: pp.getPadSvg() }; + items.push(item); - const new_proj = (this.is_projection === kind) ? '' : kind; - this.projection_widthX = widthX; - this.projection_widthY = widthY; - this.is_projection = ''; // avoid projection handling until area is created + // remove buttons from each sub-pad + const btns = pp.getLayerSvg('btns_layer'); + item.btns_node = btns.node(); + if (item.btns_node) { + item.btns_prnt = item.btns_node.parentNode; + item.btns_next = item.btns_node.nextSibling; + btns.remove(); + } - this.provideSpecialDrawArea(new_proj).then(() => { this.is_projection = new_proj; return this.redrawProjection(); }); - } + const fp = pp.getFramePainter(); + if (!isFunc(fp?.access3dKind)) + return; - /** @summary Readraw projections */ - redrawProjection(/* ii1, ii2, jj1, jj2 */) { - // do nothing for the moment - // if (!this.is_projection) return; - } + const can3d = fp.access3dKind(); + if ((can3d !== constants$1.Embed3D.Overlay) && (can3d !== constants$1.Embed3D.Embed)) + return; - /** @summary Execute menu command */ - executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) return true; + const main = isFunc(fp.getRenderer) ? fp : fp.getMainPainter(), + canvas = isFunc(main.getRenderer) ? main.getRenderer()?.domElement : null; + if (!isFunc(main?.render3D) || !isObject(canvas)) + return; - if ((method.fName === 'SetShowProjectionX') || (method.fName === 'SetShowProjectionY')) { - this.toggleProjection(method.fName[17], args && parseInt(args) ? parseInt(args) : 1); - return true; - } + const sz2 = fp.getSizeFor3d(constants$1.Embed3D.Embed); // get size and position of DOM element as it will be embed + main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately + const dataUrl = canvas.toDataURL('image/png'); - return false; - } + // remove 3D drawings + if (can3d === constants$1.Embed3D.Embed) { + item.foreign = item.prnt.select('.' + sz2.clname); + item.foreign.remove(); + } - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - if (this.getPadPainter()?.iscan) { - let kind = this.is_projection || ''; - if (kind) kind += this.projection_widthX; - if ((this.projection_widthX !== this.projection_widthY) && (this.is_projection === 'XY')) - kind = `X${this.projection_widthX}_Y${this.projection_widthY}`; - const kinds = ['X1', 'X2', 'X3', 'X5', 'X10', 'Y1', 'Y2', 'Y3', 'Y5', 'Y10', 'XY1', 'XY2', 'XY3', 'XY5', 'XY10']; - if (kind) kinds.unshift('Off'); + const svg_frame = fp.getFrameSvg(); + item.frame_node = svg_frame.node(); + if (item.frame_node) { + item.frame_next = item.frame_node.nextSibling; + svg_frame.remove(); + } - menu.add('sub:Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); - for (let k = 0; k < kinds.length; ++k) - menu.addchk(kind === kinds[k], kinds[k], kinds[k], arg => this.toggleProjection(arg)); - menu.add('endsub:'); + // add svg image + item.img = item.prnt.insert('image', '.primitives_layer') // create image object + .attr('x', sz2.x) + .attr('y', sz2.y) + .attr('width', canvas.width) + .attr('height', canvas.height) + .attr('href', dataUrl); + }, 'pads'); } - menu.add('Auto zoom-in', () => this.autoZoom()); - - const opts = this.getSupportedDrawOptions(); + let width = elem.property('draw_width'), height = elem.property('draw_height'); + if (use_frame) { + const fp = this.getFramePainter(); + width = fp.getFrameWidth(); + height = fp.getFrameHeight(); + } - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); - this.decodeOptions(arg); - this.interactiveRedraw('pad', 'drawopt'); - }); + const arg = (file_format === 'pdf') + ? { node: elem.node(), width, height, reset_tranform: use_frame } + : compressSVG(`${elem.node().innerHTML}`); - if (this.options.Color) - this.fillPaletteMenu(menu); - } + return svgToImage(arg, file_format, args).then(res => { + for (let k = 0; k < items.length; ++k) { + const item = items[k]; - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const res = super.clickButton(funcname); - if (res) return res; + item.img?.remove(); // delete embed image - switch (funcname) { - case 'ToggleColor': return this.toggleColor(); - case 'Toggle3D': return this.toggleMode3D(); - } + const prim = item.prnt.selectChild('.primitives_layer'); - // all methods here should not be processed further - return false; - } + if (item.foreign) // reinsert foreign object + item.prnt.node().insertBefore(item.foreign.node(), prim.node()); - /** @summary Fill pad toolbar with RH2-related functions */ - fillToolbar() { - super.fillToolbar(true); + if (item.frame_node) // reinsert frame as first in list of primitives + prim.node().insertBefore(item.frame_node, item.frame_next); - const pp = this.getPadPainter(); - if (!pp) return; + if (item.btns_node) // reinsert buttons + item.btns_prnt.insertBefore(item.btns_node, item.btns_next); - pp.addPadButton('th2color', 'Toggle color', 'ToggleColor'); - pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); - pp.addPadButton('th2draw3d', 'Toggle 3D mode', 'Toggle3D'); - pp.showPadButtons(); + if (item.defs) // reinsert defs + item.prnt.node().insertBefore(item.defs.node(), item.prnt.node().firstChild); + } + return res; + }); } - /** @summary Toggle color drawing mode */ - toggleColor() { - if (this.options.Mode3D) { - this.options.Mode3D = false; - this.options.Color = true; - } else - this.options.Color = !this.options.Color; + /** @summary Process pad button click */ + clickPadButton(funcname, evnt) { + if (funcname === 'CanvasSnapShot') + return this.saveAs('png', true); - return this.redraw(); - } + if (funcname === 'enlargePad') + return this.enlargePad(); - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - const i1 = this.getSelectIndex('x', 'left', -1), - i2 = this.getSelectIndex('x', 'right', 1), - j1 = this.getSelectIndex('y', 'left', -1), - j2 = this.getSelectIndex('y', 'right', 1), - histo = this.getHisto(), xaxis = this.getAxis('x'), yaxis = this.getAxis('y'); + if (funcname === 'PadSnapShot') + return this.saveAs('png', false); - if ((i1 === i2) || (j1 === j2)) return; + if (funcname === 'PadContextMenus') { + evnt?.preventDefault(); + evnt?.stopPropagation(); + if (closeMenu()) + return; - // first find minimum - let min = histo.getBinContent(i1 + 1, j1 + 1); - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) - min = Math.min(min, histo.getBinContent(i+1, j+1)); - } - if (min > 0) return; // if all points positive, no chance for autoscale + return createMenu(evnt, this).then(menu => { + menu.header('Menus'); - let ileft = i2, iright = i1, jleft = j2, jright = j1; + menu.add(this.isCanvas() ? 'Canvas' : 'Pad', 'pad', this.itemContextMenu); - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) { - if (histo.getBinContent(i + 1, j + 1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - } - } - } + if (this.getFramePainter()) + menu.add('Frame', 'frame', this.itemContextMenu); - let xmin, xmax, ymin, ymax, isany = false; + const main = this.getMainPainter(); // here hist painter methods - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } + if (main) { + menu.add('X axis', 'xaxis', this.itemContextMenu); + menu.add('Y axis', 'yaxis', this.itemContextMenu); + if (isFunc(main.getDimension) && (main.getDimension() > 1)) + menu.add('Z axis', 'zaxis', this.itemContextMenu); + } - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = xaxis.GetBinCoord(ileft); - xmax = xaxis.GetBinCoord(iright); - isany = true; - } + if (this.#painters?.length) { + menu.separator(); + const shown = []; + this.#painters.forEach((pp, indx) => { + const obj = pp?.getObject(); + if (!obj || (shown.indexOf(obj) >= 0) || pp.isSecondary()) + return; + let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); + if (name) + name += '::'; + name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); + menu.add(name, indx, this.itemContextMenu); + shown.push(obj); + }); + } - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = yaxis.GetBinCoord(jleft); - ymax = yaxis.GetBinCoord(jright); - isany = true; + menu.show(); + }); } - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax); - } - - /** @summary Scan content of 2-dim histogram */ - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy) return; + // click automatically goes to all sub-pads + // if any painter indicates that processing completed, it returns true + let done = false; + const prs = []; - const histo = this.getHisto(); + for (let i = 0; i < this.#painters.length; ++i) { + const pp = this.#painters[i]; - this.extractAxesProperties(2); + if (isFunc(pp.clickPadButton)) + prs.push(pp.clickPadButton(funcname, evnt)); - if (this.isDisplayItem()) { - // take min/max values from the display item - this.gminbin = histo.fContMin; - this.gminposbin = histo.fContMinPos > 0 ? histo.fContMinPos : null; - this.gmaxbin = histo.fContMax; - } else { - // global min/max, used at the moment in 3D drawing - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1); - this.gminposbin = null; - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) { - const bin_content = histo.getBinContent(i+1, j+1); - if (bin_content < this.gminbin) this.gminbin = bin_content; else - if (bin_content > this.gmaxbin) this.gmaxbin = bin_content; - if (bin_content > 0) - if ((this.gminposbin === null) || (this.gminposbin > bin_content)) this.gminposbin = bin_content; - } + if (!done && isFunc(pp.clickButton)) { + done = pp.clickButton(funcname); + if (isPromise(done)) + prs.push(done); } } - this.zmin = this.gminbin; - this.zmax = this.gmaxbin; - - // this value used for logz scale drawing - if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; - - if (this.options.Axis > 0) // Paint histogram axis only - this.draw_content = false; - else - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); + return Promise.all(prs); } - /** @summary Count statistic */ - countStat(cond) { - const histo = this.getHisto(), - res = { name: 'histo', entries: 0, integral: 0, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], xmax: 0, ymax: 0, wmax: null }, - xleft = this.getSelectIndex('x', 'left'), - xright = this.getSelectIndex('x', 'right'), - yleft = this.getSelectIndex('y', 'left'), - yright = this.getSelectIndex('y', 'right'), - xaxis = this.getAxis('x'), yaxis = this.getAxis('y'); - let stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumx2 = 0, stat_sumy2 = 0, - xside, yside, xx, yy, zz, - xi, yi; - - // TODO: account underflow/overflow bins, now stored in different array and only by histogram itself - for (xi = 1; xi <= this.nbinsx; ++xi) { - xside = (xi <= xleft+1) ? 0 : (xi > xright+1 ? 2 : 1); - xx = xaxis.GetBinCoord(xi - 0.5); + /** @summary Add button to the pad + * @private */ + addPadButton(btn, tooltip, funcname, keyname) { + if (!settings.ToolBar || this.isBatchMode()) + return; - for (yi = 1; yi <= this.nbinsy; ++yi) { - yside = (yi <= yleft+1) ? 0 : (yi > yright+1 ? 2 : 1); - yy = yaxis.GetBinCoord(yi - 0.5); + if (!this._buttons) + this._buttons = []; + // check if there are duplications - zz = histo.getBinContent(xi, yi); + for (let k = 0; k < this._buttons.length; ++k) { + if (this._buttons[k].funcname === funcname) + return; + } - res.entries += zz; + this._buttons.push({ btn, tooltip, funcname, keyname }); - res.matrix[yside * 3 + xside] += zz; + if (!this.isTopPad() && funcname.indexOf('Pad') && (funcname !== 'enlargePad')) { + const cp = this.getCanvPainter(); + if (cp && (cp !== this)) + cp.addPadButton(btn, tooltip, funcname); + } + } - if ((xside !== 1) || (yside !== 1)) continue; + /** @summary Add buttons for pad or canvas + * @private */ + addPadButtons(is_online) { + this.addPadButton('camera', 'Create PNG', this.isCanvas() ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); - if (cond && !cond(xx, yy)) continue; + if (settings.ContextMenu) + this.addPadButton('question', 'Access context menus', 'PadContextMenus'); - if ((res.wmax === null) || (zz > res.wmax)) { res.wmax = zz; res.xmax = xx; res.ymax = yy; } + const add_enlarge = !this.isTopPad() && this.hasObjectsToDraw(); - stat_sum0 += zz; - stat_sumx1 += xx * zz; - stat_sumy1 += yy * zz; - stat_sumx2 += xx**2 * zz; - stat_sumy2 += yy**2 * zz; - } - } + if (add_enlarge || this.enlargeMain('verify')) + this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); + if (is_online && this.brlayout) { + this.addPadButton('diamand', 'Toggle Ged', 'ToggleGed'); + this.addPadButton('three_circles', 'Toggle Status', 'ToggleStatus'); } - - if (res.wmax === null) res.wmax = 0; - res.integral = stat_sum0; - return res; } - /** @summary Fill statistic into statbox */ - fillStatistic(stat, dostat /*, dofit */) { - const data = this.countStat(), - print_name = Math.floor(dostat % 10), - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10; - - stat.clearStat(); - - if (print_name > 0) - stat.addText(data.name); + /** @summary Show pad buttons + * @private */ + showPadButtons() { + if (!this._buttons) + return; - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + PadButtonsHandler.assign(this); + this.showPadButtons(); + } - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); - } + /** @summary Calculates RPadLength value */ + getPadLength(vertical, len, frame_painter) { + let rect, res; + const sign = vertical ? -1 : 1, + getV = (indx, dflt) => { return (indx < len.fArr.length) ? len.fArr[indx] : dflt; }, + getRect = () => { + if (!rect) + rect = frame_painter ? frame_painter.getFrameRect() : this.getPadRect(); + return rect; + }; - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); + if (frame_painter) { + const user = getV(2), func = vertical ? 'gry' : 'grx'; + if ((user !== undefined) && frame_painter[func]) + res = frame_painter[func](user); } - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.matrix[4], 'entries')); - - if (print_skew > 0) { - stat.addText('Skewness x = '); - stat.addText('Skewness y = '); - } + if (res === undefined) + res = vertical ? getRect().height : 0; - if (print_kurt > 0) - stat.addText('Kurt = '); + const norm = getV(0, 0), pixel = getV(1, 0); - if ((print_under > 0) || (print_over > 0)) { - const m = data.matrix; + res += sign * pixel; - stat.addText('' + m[6].toFixed(0) + ' | ' + m[7].toFixed(0) + ' | ' + m[7].toFixed(0)); - stat.addText('' + m[3].toFixed(0) + ' | ' + m[4].toFixed(0) + ' | ' + m[5].toFixed(0)); - stat.addText('' + m[0].toFixed(0) + ' | ' + m[1].toFixed(0) + ' | ' + m[2].toFixed(0)); - } + if (norm) + res += sign * (vertical ? getRect().height : getRect().width) * norm; - return true; + return Math.round(res); } - /** @summary Draw histogram bins as color */ - drawBinsColor() { - const histo = this.getHisto(), - handle = this.prepareDraw(), - di = handle.stepi, dj = handle.stepj, - entries = []; - let colindx, cmd1, cmd2, i, j, binz, dx, dy, entry, last_entry; - const flush_last_entry = () => { - last_entry.path += `h${dx}v${last_entry.y2-last_entry.y}h${-dx}z`; - last_entry.dy = 0; - last_entry = null; + /** @summary Calculates pad position for RPadPos values + * @param {object} pos - instance of RPadPos + * @param {object} frame_painter - if drawing will be performed inside frame, frame painter */ + getCoordinate(pos, frame_painter) { + return { + x: this.getPadLength(false, pos.fHoriz, frame_painter), + y: this.getPadLength(true, pos.fVert, frame_painter) }; + } - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - dx = (handle.grx[i+di] - handle.grx[i]) || 1; - - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i+1, j+1); - colindx = handle.palette.getContourIndex(binz); - if (binz === 0) { - if (!this.options.Zero) - colindx = null; - else if ((colindx === null) && this._show_empty_bins) - colindx = 0; - } - if (colindx === null) { - if (last_entry) flush_last_entry(); - continue; - } - - cmd1 = `M${handle.grx[i]},${handle.gry[j]}`; + /** @summary Decode pad draw options */ + decodeOptions(opt) { + const pad = this.getObject(); + if (!pad) + return; - dy = (handle.gry[j+dj] - handle.gry[j]) || -1; + const d = new DrawOptions(opt), + o = this.setOptions({ GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false }); + + if (d.check('NOCOLORS') || d.check('NOCOL')) + o.GlobalColors = o.LocalColors = false; + if (d.check('LCOLORS') || d.check('LCOL')) { + o.GlobalColors = false; + o.LocalColors = true; + } + if (d.check('NOPALETTE') || d.check('NOPAL')) + o.IgnorePalette = true; + if (d.check('ROTATE')) + o.RotateFrame = true; + if (d.check('FIXFRAME')) + o.FixFrame = true; + + if (d.check('WHITE')) + pad.fFillColor = 0; + if (d.check('LOGX')) + pad.fLogx = 1; + if (d.check('LOGY')) + pad.fLogy = 1; + if (d.check('LOGZ')) + pad.fLogz = 1; + if (d.check('LOG')) + pad.fLogx = pad.fLogy = pad.fLogz = 1; + if (d.check('GRIDX')) + pad.fGridx = 1; + if (d.check('GRIDY')) + pad.fGridy = 1; + if (d.check('GRID')) + pad.fGridx = pad.fGridy = 1; + if (d.check('TICKX2')) + pad.fTickx = 2; + if (d.check('TICKY2')) + pad.fTicky = 2; + if (d.check('TICK2')) + pad.fTickx = pad.fTicky = 2; + if (d.check('TICKX')) + pad.fTickx = 1; + if (d.check('TICKY')) + pad.fTicky = 1; + if (d.check('TICK')) + pad.fTickx = pad.fTicky = 1; + } - entry = entries[colindx]; + /** @summary draw RPad object */ + static async draw(dom, pad, opt) { + const painter = new RPadPainter(dom, pad, opt, false, true); - if (entry === undefined) - entry = entries[colindx] = { path: cmd1 }; - else if ((entry === last_entry)) { - entry.y2 = handle.gry[j] + dy; - continue; - } else { - cmd2 = `m${handle.grx[i]-entry.x},${handle.gry[j]-entry.y}`; - entry.path += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - } - if (last_entry) flush_last_entry(); - entry.x = handle.grx[i]; - entry.y = handle.gry[j]; - { - entry.y2 = handle.gry[j] + dy; - last_entry = entry; - } - } - if (last_entry) flush_last_entry(); - } + painter.createPadSvg(); - entries.forEach((entry, colindx) => { - if (entry) { - this.draw_g - .append('svg:path') - .style('fill', handle.palette.getColor(colindx)) - .attr('d', entry.path); - } - }); + if (painter.matchObjectType(nsREX + 'RPad') && (painter.isTopPad() || painter.hasObjectsToDraw())) + painter.addPadButtons(); - this.updatePaletteDraw(); + selectActivePad({ pp: painter, active: false }); - return handle; + // flag used to prevent immediate pad redraw during first draw + return painter.drawPrimitives().then(() => { + painter.addPadInteractive(); + painter.showPadButtons(); + return painter; + }); } - /** @summary Draw histogram bins as contour */ - drawBinsContour(funcs, frame_w, frame_h) { - const handle = this.prepareDraw({ rounding: false, extra: 100 }), - main = this.getFramePainter(), - palette = main.getHistPalette(), - levels = palette.getContour(), - func = main.getProjectionFunc(), +} // class RPadPainter - BuildPath = (xp, yp, iminus, iplus, do_close) => { - let cmd = '', last, pnt, first, isany; - for (let i = iminus; i <= iplus; ++i) { - if (func) { - pnt = func(xp[i], yp[i]); - pnt.x = Math.round(funcs.grx(pnt.x)); - pnt.y = Math.round(funcs.gry(pnt.y)); - } else - pnt = { x: Math.round(xp[i]), y: Math.round(yp[i]) }; +/** + * @summary Painter class for RCanvas + * + * @private + */ - if (!cmd) { - cmd = `M${pnt.x},${pnt.y}`; first = pnt; - } else if ((i === iplus) && first && (pnt.x === first.x) && (pnt.y === first.y)) { - if (!isany) return ''; // all same points - cmd += 'z'; do_close = false; - } else if ((pnt.x !== last.x) && (pnt.y !== last.y)) { - cmd += `l${pnt.x - last.x},${pnt.y - last.y}`; isany = true; - } else if (pnt.x !== last.x) { - cmd += `h${pnt.x - last.x}`; isany = true; - } else if (pnt.y !== last.y) { - cmd += `v${pnt.y - last.y}`; isany = true; - } - last = pnt; - } - if (do_close) cmd += 'z'; - return cmd; - }; +class RCanvasPainter extends RPadPainter { - if (this.options.Contour === 14) { - this.draw_g - .append('svg:path') - .attr('d', `M0,0h${frame_w}v${frame_h}h${-frame_w}z`) - .style('fill', palette.getColor(0)); - } + #websocket; // WebWindow handle used for communication with server + #changed_layout; // modified layout + #submreq; // submitted requests + #nextreqid; // id of next request - buildHist2dContour(this.getHisto(), handle, levels, palette, - (colindx, xp, yp, iminus, iplus) => { - const icol = palette.getColor(colindx); - let fillcolor = icol, lineatt; + /** @summary constructor */ + constructor(dom, canvas, opt) { + super(dom, canvas, opt, true); + this.#websocket = null; + this.#submreq = {}; + this.tooltip_allowed = settings.Tooltip; + this.v7canvas = true; + } - switch (this.options.Contour) { - case 1: break; - case 11: fillcolor = 'none'; lineatt = this.createAttLine({ color: icol, std: false }); break; - case 12: fillcolor = 'none'; lineatt = this.createAttLine({ color: 1, style: (colindx%5 + 1), width: 1, std: false }); break; - case 13: fillcolor = 'none'; lineatt = this.lineatt; break; - } + /** @summary Cleanup canvas painter */ + cleanup() { + this.#websocket = undefined; + this.#submreq = {}; - const dd = BuildPath(xp, yp, iminus, iplus, fillcolor !== 'none'); - if (!dd) return; + if (this.#changed_layout) + this.setLayoutKind('simple'); + this.#changed_layout = undefined; - const elem = this.draw_g - .append('svg:path') - .attr('d', dd) - .style('fill', fillcolor); + super.cleanup(); + } - if (lineatt) - elem.call(lineatt.func); - } - ); + /** @summary Returns readonly flag */ + isReadonly() { return false; } - handle.hide_only_zeros = true; // text drawing suppress only zeros + /** @summary Returns canvas name */ + getCanvasName() { + const title = this.getRootPad()?.fTitle; + return (!title || !isStr(title)) ? 'rcanvas' : title.replace(/ /g, '_'); + } - return handle; + /** @summary Returns layout kind */ + getLayoutKind() { + const origin = this.selectDom('origin'), + layout = origin.empty() ? '' : origin.property('layout'); + return layout || 'simple'; } - /** @summary Create polybin */ - createPolyBin() { - // see how TH2Painter is implemented - return ''; + /** @summary Set canvas layout kind */ + setLayoutKind(kind, main_selector) { + const origin = this.selectDom('origin'); + if (!origin.empty()) { + if (!kind) + kind = 'simple'; + origin.property('layout', kind); + origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); + this.#changed_layout = (kind !== 'simple'); // use in cleanup + } } - /** @summary Draw RH2 bins as text */ - drawBinsText(handle) { - if (handle === null) handle = this.prepareDraw({ rounding: false }); + /** @summary Changes layout + * @return {Promise} indicating when finished */ + async changeLayout(layout_kind, mainid) { + const current = this.getLayoutKind(); + if (current === layout_kind) + return true; - const histo = this.getHisto(), - textFont = this.v7EvalFont('text', { size: 20, color: 'black', align: 22 }), - text_offset = this.options.BarOffset || 0, - text_g = this.draw_g.append('svg:g').attr('class', 'th2_text'), - di = handle.stepi, dj = handle.stepj; - let i, j, binz, binw, binh, text, x, y, width, height; + const origin = this.selectDom('origin'), + sidebar2 = origin.select('.side_panel2'), + lst = []; + let sidebar = origin.select('.side_panel'), + main = this.selectDom(), force; + + while (main.node().firstChild) + lst.push(main.node().removeChild(main.node().firstChild)); - this.startTextDrawing(textFont, 'font', text_g); + if (!sidebar.empty()) + cleanup(sidebar.node()); + if (!sidebar2.empty()) + cleanup(sidebar2.node()); - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i+1, j+1); - if ((binz === 0) && !this._show_empty_bins) continue; + this.setLayoutKind('simple'); // restore defaults + origin.html(''); // cleanup origin - binw = handle.grx[i+di] - handle.grx[i]; - binh = handle.gry[j] - handle.gry[j+dj]; + if (layout_kind === 'simple') { + main = origin; + for (let k = 0; k < lst.length; ++k) + main.node().appendChild(lst[k]); + this.setLayoutKind(layout_kind); + force = true; + } else { + const grid = new GridDisplay(origin.node(), layout_kind); - text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); + if (mainid === undefined) + mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; - if (textFont.angle) { - x = Math.round(handle.grx[i] + binw*0.5); - y = Math.round(handle.gry[j+dj] + binh*(0.5 + text_offset)); - width = height = 0; - } else { - x = Math.round(handle.grx[i] + binw*0.1); - y = Math.round(handle.gry[j+dj] + binh*(0.1 + text_offset)); - width = Math.round(binw*0.8); - height = Math.round(binh*0.8); - } + main = select(grid.getGridFrame(mainid)); + main.classed('central_panel', true).style('position', 'relative'); - this.drawText({ align: 22, x, y, width, height, text, latex: 0, draw_g: text_g }); + if (mainid === 2) { + // left panel for Y + sidebar = select(grid.getGridFrame(0)); + sidebar.classed('side_panel2', true).style('position', 'relative'); + // bottom panel for X + sidebar = select(grid.getGridFrame(3)); + sidebar.classed('side_panel', true).style('position', 'relative'); + } else { + sidebar = select(grid.getGridFrame(1 - mainid)); + sidebar.classed('side_panel', true).style('position', 'relative'); } - } - return this.finishTextDrawing(text_g, true).then(() => { - handle.hide_only_zeros = true; // text drawing suppress only zeros - return handle; - }); - } + // now append all childs to the new main + for (let k = 0; k < lst.length; ++k) + main.node().appendChild(lst[k]); - /** @summary Draw RH2 bins as arrows */ - drawBinsArrow() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: false }), - scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1])/(handle.i2 - handle.i1 + 1-0.03)/2, - scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1])/(handle.j2 - handle.j1 + 1-0.03)/2, - di = handle.stepi, dj = handle.stepj, - makeLine = (dx, dy) => dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); - let cmd = '', i, j, dn = 1e-30, dx, dy, xc, yc, - dxn, dyn, x1, x2, y1, y2, anr, si, co; + this.setLayoutKind(layout_kind, '.central_panel'); - for (let loop = 0; loop < 2; ++loop) { - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - if (i === handle.i1) - dx = histo.getBinContent(i+1+di, j+1) - histo.getBinContent(i+1, j+1); - else if (i >= handle.i2-di) - dx = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1-di, j+1); - else - dx = 0.5*(histo.getBinContent(i+1+di, j+1) - histo.getBinContent(i+1-di, j+1)); + // remove reference to MDIDisplay, solves resize problem + origin.property('mdi', null); + } - if (j === handle.j1) - dy = histo.getBinContent(i+1, j+1+dj) - histo.getBinContent(i+1, j+1); - else if (j >= handle.j2-dj) - dy = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1, j+1-dj); - else - dy = 0.5*(histo.getBinContent(i+1, j+1+dj) - histo.getBinContent(i+1, j+1-dj)); + // resize main drawing and let draw extras + resize(main.node(), force); + return true; + } + /** @summary Toggle projection + * @return {Promise} indicating when ready + * @private */ + async toggleProjection(kind) { + delete this.proj_painter; - if (loop === 0) - dn = Math.max(dn, Math.abs(dx), Math.abs(dy)); - else { - xc = (handle.grx[i] + handle.grx[i+di])/2; - yc = (handle.gry[j] + handle.gry[j+dj])/2; - dxn = scale_x*dx/dn; - dyn = scale_y*dy/dn; - x1 = xc - dxn; - x2 = xc + dxn; - y1 = yc - dyn; - y2 = yc + dyn; - dx = Math.round(x2-x1); - dy = Math.round(y2-y1); + if (kind) + this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed - if ((dx !== 0) || (dy !== 0)) { - cmd += 'M'+Math.round(x1)+','+Math.round(y1) + makeLine(dx, dy); + if (isFunc(this.showUI5ProjectionArea)) + return this.showUI5ProjectionArea(kind); - if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { - anr = Math.sqrt(2/(dx**2 + dy**2)); - si = Math.round(anr*(dx + dy)); - co = Math.round(anr*(dx - dy)); - if (si || co) - cmd += `m${-si},${co}` + makeLine(si, -co) + makeLine(-co, -si); - } - } - } - } - } + let layout = 'simple', mainid; + + switch (kind) { + case 'XY': + layout = 'projxy'; + mainid = 2; + break; + case 'X': + case 'bottom': + layout = 'vert2_31'; + mainid = 0; + break; + case 'Y': + case 'left': + layout = 'horiz2_13'; + mainid = 1; + break; + case 'top': + layout = 'vert2_13'; + mainid = 1; + break; + case 'right': + layout = 'horiz2_31'; + mainid = 0; + break; } - this.draw_g - .append('svg:path') - .attr('d', cmd) - .style('fill', 'none') - .call(this.lineatt.func); + return this.changeLayout(layout, mainid); + } - return handle; + /** @summary Draw projection for specified histogram + * @private */ + async drawProjection(/* kind, hist, hopt */) { + // dummy for the moment + return false; } - /** @summary Draw RH2 bins as boxes */ - drawBinsBox() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: false }), - main = this.getFramePainter(); + /** @summary Draw in side panel + * @private */ + async drawInSidePanel(canv, opt, kind) { + const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', + side = this.selectDom('origin').select(sel); + return side.empty() ? null : this.drawObject(side.node(), canv, opt); + } - if (main.maxbin === main.minbin) { - main.maxbin = this.gmaxbin; - main.minbin = this.gminbin; - main.minposbin = this.gminposbin; - } - if (main.maxbin === main.minbin) - main.minbin = Math.min(0, main.maxbin-1); + /** @summary Checks if canvas shown inside ui5 widget + * @desc Function should be used only from the func which supposed to be replaced by ui5 + * @private */ + testUI5() { + return this.use_openui ?? false; + } - const absmax = Math.max(Math.abs(main.maxbin), Math.abs(main.minbin)), - absmin = Math.max(0, main.minbin), - di = handle.stepi, dj = handle.stepj; - let i, j, binz, absz, res = '', cross = '', btn1 = '', btn2 = '', - zdiff, dgrx, dgry, xx, yy, ww, hh, - xyfactor, uselogz = false, logmin = 0; + /** @summary Show message + * @desc Used normally with web-based canvas and handled in ui5 + * @private */ + showMessage(msg) { + if (!this.testUI5()) + showProgress(msg, 7000); + } - if (main.logz && (absmax > 0)) { - uselogz = true; - const logmax = Math.log(absmax); - if (absmin > 0) - logmin = Math.log(absmin); - else if ((main.minposbin >= 1) && (main.minposbin < 100)) - logmin = Math.log(0.7); - else - logmin = (main.minposbin > 0) ? Math.log(0.7*main.minposbin) : logmax - 10; - if (logmin >= logmax) logmin = logmax - 10; - xyfactor = 1.0 / (logmax - logmin); - } else - xyfactor = 1.0 / (absmax - absmin); + /** @summary Function called when canvas menu item Save is called */ + saveCanvasAsFile(fname) { + const pnt = fname.indexOf('.'); + this.createImage(fname.slice(pnt + 1)) + .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); + } + /** @summary Send command to server to save canvas with specified name + * @desc Should be only used in web-based canvas + * @private */ + sendSaveCommand(fname) { this.sendWebsocket('PRODUCE:' + fname); } - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i + 1, j + 1); - absz = Math.abs(binz); - if ((absz === 0) || (absz < absmin)) continue; + /** @summary Return assigned web socket + * @private */ + getWebsocket() { return this.#websocket; } - zdiff = uselogz ? ((absz > 0) ? Math.log(absz) - logmin : 0) : (absz - absmin); - // area of the box should be proportional to absolute bin content - zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor))); - // avoid oversized bins - if (zdiff < 0) zdiff = 0; + /** @summary Return true if message can be send via web socket + * @private */ + canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } + + /** @summary Send message via web socket + * @private */ + sendWebsocket(msg) { + if (this.#websocket?.canSend()) { + this.#websocket.send(msg); + return true; + } - ww = handle.grx[i+di] - handle.grx[i]; - hh = handle.gry[j] - handle.gry[j+dj]; + return false; + } - dgrx = zdiff * ww; - dgry = zdiff * hh; + /** @summary Close websocket connection to canvas + * @private */ + closeWebsocket(force) { + if (this.#websocket) { + this.#websocket.close(force); + this.#websocket.cleanup(); + this.#websocket = undefined; + } + } - xx = Math.round(handle.grx[i] + dgrx); - yy = Math.round(handle.gry[j+dj] + dgry); + /** @summary Use provided connection for the web canvas + * @private */ + useWebsocket(handle) { + this.closeWebsocket(); - ww = Math.max(Math.round(ww - 2*dgrx), 1); - hh = Math.max(Math.round(hh - 2*dgry), 1); + this.#websocket = handle; + this.#websocket.setReceiver(this); + this.#websocket.connect(); + } - res += `M${xx},${yy}v${hh}h${ww}v${-hh}z`; + /** @summary set, test or reset timeout of specified name + * @desc Used to prevent overloading of websocket for specific function */ + websocketTimeout(name, tm) { + if (!this.#websocket) + return; + if (!this.#websocket._tmouts) + this.#websocket._tmouts = {}; - if ((binz < 0) && (this.options.BoxStyle === 10)) - cross += `M${xx},${yy}l${ww},${hh}M${xx+ww},${yy}l${-ww},${hh}`; + const handle = this.#websocket._tmouts[name]; + if (tm === undefined) + return handle !== undefined; - if ((this.options.BoxStyle === 11) && (ww>5) && (hh>5)) { - const pww = Math.round(ww*0.1), - phh = Math.round(hh*0.1), - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; - btn2 += (binz < 0) ? side1 : side2; - btn1 += (binz < 0) ? side2 : side1; - } + if (tm === 'reset') { + if (handle) { + clearTimeout(handle); + delete this.#websocket._tmouts[name]; } - } - - if (res) { - const elem = this.draw_g - .append('svg:path') - .attr('d', res) - .call(this.fillatt.func); - if ((this.options.BoxStyle !== 11) && this.fillatt.empty()) - elem.call(this.lineatt.func); - } + } else if (!handle && Number.isInteger(tm)) + this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); + } - if (btn1 && this.fillatt.hasColor()) { - this.draw_g.append('svg:path') - .attr('d', btn1) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatHex()); - } + /** @summary Handler for websocket open event + * @private */ + onWebsocketOpened(/* handle */) { + } - if (btn2) { - this.draw_g.append('svg:path') - .attr('d', btn2) - .call(this.fillatt.func) - .style('fill', !this.fillatt.hasColor() ? 'red' : rgb(this.fillatt.color).darker(0.5).formatHex()); - } + /** @summary Handler for websocket close event + * @private */ + onWebsocketClosed(/* handle */) { + if (!this.embed_canvas) + closeCurrentWindow(); + } - if (cross) { - const elem = this.draw_g.append('svg:path') - .attr('d', cross) - .style('fill', 'none'); - if (!this.lineatt.empty()) - elem.call(this.lineatt.func); - } + /** @summary Handler for websocket message + * @private */ + onWebsocketMsg(handle, msg) { + // console.log('GET_MSG ' + msg.slice(0,30)); - return handle; - } + if (msg === 'CLOSE') { + this.onWebsocketClosed(); + this.closeWebsocket(true); + } else if (msg.slice(0, 5) === 'SNAP:') { + msg = msg.slice(5); + const p1 = msg.indexOf(':'), + snapid = msg.slice(0, p1), + snap = parse$1(msg.slice(p1 + 1)); + this.syncDraw(true) + .then(() => { + if (!this.getSnapId() && snap?.fWinSize) + this.resizeBrowser(snap.fWinSize[0], snap.fWinSize[1]); + }).then(() => this.redrawPadSnap(snap)) + .then(() => { + this.addPadInteractive(); + handle.send(`SNAPDONE:${snapid}`); // send ready message back when drawing completed + this.confirmDraw(); + }).catch(err => { + if (isFunc(this.showConsoleError)) + this.showConsoleError(err); + else + console.log(err); + }); + } else if (msg.slice(0, 4) === 'JSON') { + const obj = parse$1(msg.slice(4)); + this.redrawObject(obj); + } else if (msg.slice(0, 9) === 'REPL_REQ:') + this.processDrawableReply(msg.slice(9)); + else if (msg.slice(0, 4) === 'CMD:') { + msg = msg.slice(4); + const p1 = msg.indexOf(':'), + cmdid = msg.slice(0, p1), + cmd = msg.slice(p1 + 1), + reply = `REPLY:${cmdid}:`; + if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { + this.createImage(cmd.toLowerCase()) + .then(res => handle.send(reply + res)); + } else if (cmd.indexOf('ADDPANEL:') === 0) { + if (!isFunc(this.showUI5Panel)) + handle.send(reply + 'false'); + else { + const window_path = cmd.slice(9), + conn = handle.createNewInstance(window_path); - /** @summary Draw RH2 bins as scatter plot */ - drawBinsScatter() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: true, pixel_density: true, scatter_plot: true }), - colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [], - scale = this.options.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), - di = handle.stepi, dj = handle.stepj, - rnd = new TRandom(handle.sumz); - let colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1; + // set interim receiver until first message arrives + conn.setReceiver({ + cpainter: this, - if (scale*handle.sumz < 1e5) { - // one can use direct drawing of scatter plot without any patterns + onWebsocketOpened() { + }, - this.createv7AttMarker(); + onWebsocketMsg(panel_handle, msg2) { + const panel_name = (msg2.indexOf('SHOWPANEL:') === 0) ? msg2.slice(10) : ''; + this.cpainter.showUI5Panel(panel_name, panel_handle) + .then(res => handle.send(reply + (res ? 'true' : 'false'))); + }, - this.markeratt.resetPos(); + onWebsocketClosed() { + // if connection failed, + handle.send(reply + 'false'); + }, - let path = '', k, npix; - for (i = handle.i1; i < handle.i2; i += di) { - cw = handle.grx[i+di] - handle.grx[i]; - for (j = handle.j1; j < handle.j2; j += dj) { - ch = handle.gry[j] - handle.gry[j+dj]; - binz = histo.getBinContent(i + 1, j + 1); + onWebsocketError() { + // if connection failed, + handle.send(reply + 'false'); + } - npix = Math.round(scale*binz); - if (npix <= 0) continue; + }); - for (k = 0; k < npix; ++k) { - path += this.markeratt.create( - Math.round(handle.grx[i] + cw * rnd.random()), - Math.round(handle.gry[j+1] + ch * rnd.random())); - } + // only when connection established, panel will be activated + conn.connect(); } + } else { + console.log('Unrecognized command ' + cmd); + handle.send(reply); } + } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { + const kind = msg[1], + hist = parse$1(msg.slice(7)); + this.drawProjection(kind, hist); + } else if (msg.slice(0, 5) === 'SHOW:') { + const that = msg.slice(5), + on = that.at(-1) === '1'; + this.showSection(that.slice(0, that.length - 2), on); + } else + console.log(`unrecognized msg len: ${msg.length} msg: ${msg.slice(0, 30)}`); + } - this.draw_g - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + /** @summary Submit request to RDrawable object on server side */ + submitDrawableRequest(kind, req, painter, method) { + if (!this.getWebsocket() || !req?._typename || !painter.getSnapId()) + return null; - return handle; - } + if (kind && method) { + // if kind specified - check if such request already was submitted + if (!painter._requests) + painter._requests = {}; - // limit filling factor, do not try to produce as many points as filled area; - if (this.maxbin > 0.7) factor = 0.7/this.maxbin; + const prevreq = painter._requests[kind]; - // let nlevels = Math.round(handle.max - handle.min); + if (prevreq) { + const tm = new Date().getTime(); + if (!prevreq._tm || (tm - prevreq._tm < 5000)) { + prevreq._nextreq = req; // submit when got reply + return false; + } + delete painter._requests[kind]; // let submit new request after timeout + } - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i + 1, j + 1); - if ((binz <= 0) || (binz < this.minbin)) continue; + painter._requests[kind] = req; // keep reference on the request + } - cw = handle.grx[i+di] - handle.grx[i]; - ch = handle.gry[j] - handle.gry[j+dj]; - if (cw*ch <= 0) continue; + req.id = painter.getSnapId(); - colindx = handle.palette.getContourIndex(binz/cw/ch); - if (colindx < 0) continue; + if (method) { + if (!this.#nextreqid) + this.#nextreqid = 1; + req.reqid = this.#nextreqid++; + } else + req.reqid = 0; // request will not be replied - cmd1 = `M${handle.grx[i]},${handle.gry[j+dj]}`; - if (colPaths[colindx] === undefined) { - colPaths[colindx] = cmd1; - cell_w[colindx] = cw; - cell_h[colindx] = ch; - } else { - cmd2 = `m${handle.grx[i]-currx[colindx]},${handle.gry[j+dj]-curry[colindx]}`; - colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - cell_w[colindx] = Math.max(cell_w[colindx], cw); - cell_h[colindx] = Math.max(cell_h[colindx], ch); - } - currx[colindx] = handle.grx[i]; - curry[colindx] = handle.gry[j+dj]; + const msg = JSON.stringify(req); - colPaths[colindx] += `v${ch}h${cw}v${-ch}z`; - } + if (req.reqid) { + req._kind = kind; + req._painter = painter; + req._method = method; + req._tm = new Date().getTime(); + + this.#submreq[req.reqid] = req; // fast access to submitted requests } - const layer = this.getFrameSvg().selectChild('.main_layer'); - let defs = layer.selectChild('def'); - if (defs.empty() && (colPaths.length > 0)) - defs = layer.insert('svg:defs', ':first-child'); + this.sendWebsocket('REQ:' + msg); + return req; + } + + /** @summary Submit menu request + * @private */ + async submitMenuRequest(painter, menukind, reqid) { + return new Promise(resolveFunc => { + this.submitDrawableRequest('', { + _typename: `${nsREX}RDrawableMenuRequest`, + menukind: menukind || '', + menureqid: reqid // used to identify menu request + }, painter, resolveFunc); + }); + } - this.createv7AttMarker(); + /** @summary Submit executable command for given painter */ + submitExec(painter, exec, subelem) { + if (subelem && isStr(subelem)) { + const len = subelem.length; + if ((len > 2) && (subelem.indexOf('#x') === len - 2)) + subelem = 'x'; + else if ((len > 2) && (subelem.indexOf('#y') === len - 2)) + subelem = 'y'; + else if ((len > 2) && (subelem.indexOf('#z') === len - 2)) + subelem = 'z'; - const cntr = handle.palette.getContour(); + if ((subelem === 'x') || (subelem === 'y') || (subelem === 'z')) + exec = subelem + 'axis#' + exec; + else + return console.log(`not recoginzed subelem ${subelem} in submitExec`); + } - for (colindx = 0; colindx < colPaths.length; ++colindx) { - if ((colPaths[colindx] !== undefined) && (colindx 0) - handle = this.drawBinsContour(funcs, rect.width, rect.height); - - if (this.options.Text) - pr = this.drawBinsText(handle); + if (this.brlayout) + return this.brlayout.hasStatus(); + const hp = getHPainter(); + return hp ? hp.hasStatusLine() : false; + } - if (!handle && !pr) - handle = this.drawBinsColor(); + /** @summary Check if status bar can be toggled + * @private */ + canStatusBar() { + return this.testUI5() || this.brlayout || getHPainter(); + } - if (!pr) pr = Promise.resolve(handle); + /** @summary Show/toggle event status bar + * @private */ + activateStatusBar(state) { + if (this.testUI5()) + return; + if (this.brlayout) + this.brlayout.createStatusLine(23, state); + else + getHPainter()?.createStatusLine(23, state); - return pr.then(h => { - this.tt_handle = h; - return this; - }); + this.processChanges('sbits', this); } - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(i, j) { - const lines = [], - histo = this.getHisto(); - let binz = histo.getBinContent(i+1, j+1), - di = 1, dj = 1; - - if (this.isDisplayItem()) { - di = histo.stepx || 1; - dj = histo.stepy || 1; - } + /** @summary Show online canvas status + * @private */ + showCanvasStatus(...msgs) { + if (this.testUI5()) + return; - lines.push(this.getObjectHint() || 'histo<2>', - 'x = ' + this.getAxisBinTip('x', i, di), - 'y = ' + this.getAxisBinTip('y', j, dj), - `bin = ${i+1}, ${j+1}`); + const br = this.brlayout || getHPainter()?.brlayout; + br?.showStatus(...msgs); + } - if (histo.$baseh) binz -= histo.$baseh.getBinContent(i+1, j+1); + /** @summary Returns true if GED is present on the canvas */ + hasGed() { + if (this.testUI5()) + return false; + return this.brlayout?.hasContent() ?? false; + } - const lbl = 'entries = ' + ((di > 1) || (dj > 1) ? '~' : ''); + /** @summary Function used to de-activate GED + * @private */ + removeGed() { + if (this.testUI5()) + return; - if (binz === Math.round(binz)) - lines.push(lbl + binz); - else - lines.push(lbl + floatToString(binz, gStyle.fStatFormat)); + this.registerForPadEvents(null); - return lines; + if (this.ged_view) { + this.ged_view.getController().cleanupGed(); + this.ged_view.destroy(); + delete this.ged_view; + } + this.brlayout?.deleteContent(true); + this.processChanges('sbits', this); } - /** @summary Provide text information (tooltips) for poly bin */ - getPolyBinTooltips() { - // see how TH2Painter is implemented - return []; + /** @summary Get view data for ui5 panel + * @private */ + getUi5PanelData(/* panel_name */) { + return { jsroot: { settings, create: create$1, parse: parse$1, toJSON, loadScript, EAxisBits, getColorExec } }; } - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const histo = this.getHisto(), - h = this.tt_handle; - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + /** @summary Function used to activate GED + * @return {Promise} when GED is there + * @private */ + async activateGed(objpainter, kind, mode) { + if (this.testUI5() || !this.brlayout) + return false; - if (!pnt || !this.draw_content || !this.draw_g || !h || this.options.Proj) { - ttrect?.remove(); - return null; - } + if (this.brlayout.hasContent()) { + if ((mode === 'toggle') || (mode === false)) + this.removeGed(); + else + objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - if (h.poly) { - // process tooltips from TH2Poly - see TH2Painter - return null; + return true; } - let i, j, binz = 0, colindx = null; - - // search bins position - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x>=h.grx[i]) && (pnt.x<=h.grx[i+1])) break; - - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y>=h.gry[j+1]) && (pnt.y<=h.gry[j])) break; + if (mode === false) + return false; - if ((i < h.i2) && (j < h.j2)) { - binz = histo.getBinContent(i+1, j+1); - if (this.is_projection) - colindx = 0; // just to avoid hide - else if (h.hide_only_zeros) - colindx = (binz === 0) && !this._show_empty_bins ? null : 0; - else { - colindx = h.palette.getContourIndex(binz); - if ((colindx === null) && (binz === 0) && this._show_empty_bins) colindx = 0; - } - } + const btns = this.brlayout.createBrowserBtns(); - if (colindx === null) { - ttrect.remove(); - return null; - } + ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') + .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); - const res = { name: 'histo', title: histo.fTitle || 'title', - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(i, j), exact: true, menu: true }; + ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') + .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); - if (this.options.Color) - res.color2 = h.palette.getColor(colindx); + ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') + .style('margin', '3px').on('click', () => this.removeGed()); - if (pnt.disabled && !this.is_projection) { - ttrect.remove(); - res.changed = true; - } else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } + // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area + this.brlayout.setBrowserContent('
    Loading GED ...
    '); + this.brlayout.setBrowserTitle('GED'); + this.brlayout.toggleBrowserKind(kind || 'float'); - const pmain = this.getFramePainter(); - let i1 = i, i2 = i+1, - j1 = j, j2 = j+1, - x1 = h.grx[i1], x2 = h.grx[i2], - y1 = h.gry[j2], y2 = h.gry[j1], - binid = i*10000 + j, path; + return new Promise(resolveFunc => { + loadOpenui5.then(sap => { + select('#ged_placeholder').text(''); - if (this.is_projection) { - const pwx = this.projection_widthX || 1, ddx = (pwx - 1) / 2; - if ((this.is_projection.indexOf('X')) >= 0 && (pwx > 1)) { - if (j2+ddx >= h.j2) { - j2 = Math.min(Math.round(j2+ddx), h.j2); - j1 = Math.max(j2-pwx, h.j1); - } else { - j1 = Math.max(Math.round(j1-ddx), h.j1); - j2 = Math.min(j1+pwx, h.j2); - } - } - const pwy = this.projection_widthY || 1, ddy = (pwy - 1) / 2; - if ((this.is_projection.indexOf('Y')) >= 0 && (pwy > 1)) { - if (i2+ddy >= h.i2) { - i2 = Math.min(Math.round(i2+ddy), h.i2); - i1 = Math.max(i2-pwy, h.i1); - } else { - i1 = Math.max(Math.round(i1-ddy), h.i1); - i2 = Math.min(i1+pwy, h.i2); - } - } - } + sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { + const oModel = new JSONModel({ handle: null }); - if (this.is_projection === 'X') { - x1 = 0; x2 = pmain.getFrameWidth(); - y1 = h.gry[j2]; y2 = h.gry[j1]; - binid = j1*777 + j2*333; - } else if (this.is_projection === 'Y') { - y1 = 0; y2 = pmain.getFrameHeight(); - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*777 + i2*333; - } else if (this.is_projection === 'XY') { - y1 = h.gry[j2]; y2 = h.gry[j1]; - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*789 + i2*653 + j1*12345 + j2*654321; - path = `M${x1},0H${x2}V${y1}H${pmain.getFrameWidth()}V${y2}H${x2}V${pmain.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; - } + XMLView.create({ + viewName: 'rootui5.canv.view.Ged', + viewData: this.getUi5PanelData('Ged') + }).then(oGed => { + oGed.setModel(oModel); - res.changed = ttrect.property('current_bin') !== binid; + oGed.placeAt('ged_placeholder'); - if (res.changed) { - ttrect.attr('d', path || `M${x1},${y1}H${x2}V${y2}H${x1}Z`) - .style('opacity', '0.7') - .property('current_bin', binid); - } + this.ged_view = oGed; - if (this.is_projection && res.changed) - this.redrawProjection(i1, i2, j1, j2); - } + // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself + this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); - if (res.changed) { - res.user_info = { obj: histo, name: 'histo', - bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1, - grx: pnt.x, gry: pnt.y }; - } + objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - return res; - } + this.processChanges('sbits', this); - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - if (axis === 'z') return true; - const obj = this.getAxis(axis); - return obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1; + resolveFunc(true); + }); + }); + }); + }); } - /** @summary Performs 2D drawing of histogram - * @return {Promise} when ready */ - async draw2D(reason) { - this.clear3DScene(); - - return this.drawFrameAxes().then(res => { - return res ? this.drawingBins(reason) : false; - }).then(res => { - if (res) return this.draw2DBins().then(() => this.addInteractivity()); - }).then(() => this); + /** @summary produce JSON for RCanvas, which can be used to display canvas once again + * @private */ + produceJSON() { + console.error('RCanvasPainter.produceJSON not yet implemented'); + return ''; } - /** @summary Performs 3D drawing of histogram - * @return {Promise} when ready */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/RH1Painter.mjs'); - return this.draw2D(reason); + /** @summary resize browser window to get requested canvas sizes */ + resizeBrowser(fullW, fullH) { + if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) + return; + this.getWebsocket()?.resizeWindow(fullW, fullH); } - /** @summary Call drawing function depending from 3D mode */ - async callDrawFunc(reason) { - const main = this.getFramePainter(); + /** @summary draw RCanvas object */ + static async draw(dom, can, opt) { + const nocanvas = !can; + if (nocanvas) + can = create$1(`${nsREX}RCanvas`); - if (main && (main.mode3d !== this.options.Mode3D) && !this.isMainPainter()) - this.options.Mode3D = main.mode3d; + const painter = new RCanvasPainter(dom, can, opt); + painter.createCanvasSvg(0); - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } + selectActivePad({ pp: painter, active: false }); - /** @summary Redraw histogram */ - async redraw(reason) { - return this.callDrawFunc(reason); + return painter.drawPrimitives().then(() => { + painter.addPadInteractive(); + painter.addPadButtons(); + painter.showPadButtons(); + return painter; + }); } - /** @summary Draw histogram using painter instance - * @private */ - static async _draw(painter /* , opt */) { - return ensureRCanvas(painter).then(() => { - painter.setAsMainPainter(); - - painter.options = { Hist: false, Error: false, Zero: false, Mark: false, - Line: false, Fill: false, Lego: 0, Surf: 0, - Text: true, TextAngle: 0, TextKind: '', - BaseLine: false, Mode3D: false, AutoColor: 0, - Color: false, Scat: false, ScatCoef: 1, Box: false, BoxStyle: 0, Arrow: false, Contour: 0, Proj: 0, - BarOffset: 0, BarWidth: 1, minimum: kNoZoom, maximum: kNoZoom, - FrontBox: false, BackBox: false }; +} // class RCanvasPainter - const kind = painter.v7EvalAttr('kind', ''), - sub = painter.v7EvalAttr('sub', 0), - o = painter.options; - o.Text = painter.v7EvalAttr('drawtext', false); +/** @summary draw RPadSnapshot object + * @private */ +function drawRPadSnapshot(dom, snap, opt) { + const painter = new RCanvasPainter(dom, null, opt); + painter.batch_mode = isBatchMode(); + return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { + painter.confirmDraw(); + painter.showPadButtons(); + return painter; + }); +} - switch (kind) { - case 'lego': o.Lego = sub > 0 ? 10+sub : 12; o.Mode3D = true; break; - case 'surf': o.Surf = sub > 0 ? 10+sub : 1; o.Mode3D = true; break; - case 'box': o.Box = true; o.BoxStyle = 10 + sub; break; - case 'err': o.Error = true; o.Mode3D = true; break; - case 'cont': o.Contour = sub > 0 ? 10+sub : 1; break; - case 'arr': o.Arrow = true; break; - case 'scat': o.Scat = true; break; - case 'col': o.Color = true; break; - default: if (!o.Text) o.Color = true; - } +/** @summary Ensure RCanvas and RFrame for the painter object + * @param {Object} painter - painter object to process + * @param {string|boolean} frame_kind - false for no frame or '3d' for special 3D mode + * @desc Assigns DOM, creates and draw RCanvas and RFrame if necessary, add painter to pad list of painters + * @return {Promise} for ready + * @private */ +async function ensureRCanvas(painter /* , frame_kind */) { + if (!painter) + return Promise.reject(Error('Painter not provided in ensureRCanvas')); - // here we deciding how histogram will look like and how will be shown - // painter.decodeOptions(opt); + // simple check - if canvas there, can use painter + const pad_painter = painter.getPadPainter(), + pr = pad_painter ? Promise.resolve(pad_painter) : + RCanvasPainter.draw(painter.getDom(), null /* noframe */); + + return pr.then(pp => { + // if ((frame_kind !== false) && pp.getFrameSvg().selectChild('.main_layer').empty()) + // return RFramePainter.draw(painter.getDom(), null, isStr(frame_kind) ? frame_kind : ''); + painter.addToPadPrimitives(pp); + return painter; + }); +} - painter._show_empty_bins = false; - painter.scanContent(); +/** @summary Function used for direct draw of RFrameTitle + * @private */ +function drawRFrameTitle(reason, drag) { + const fp = this.getFramePainter(); + if (!fp) + return console.log('no frame painter - no title'); - return painter.callDrawFunc(); - }); - } + const rect = fp.getFrameRect(), + fx = rect.x, + fy = rect.y, + fw = rect.width, + // fh = rect.height, + ph = this.getPadPainter().getPadHeight(), + title = this.getObject(), + title_width = fw, + textFont = this.v7EvalFont('text', { size: 0.07, color: 'black', align: 22 }); + let title_margin = this.v7EvalLength('margin', ph, 0.02), + title_height = this.v7EvalLength('height', ph, 0.05); - /** @summary draw RH2 object */ - static async draw(dom, obj, opt) { - // create painter and add it to canvas - return RH2Painter._draw(new RH2Painter(dom, obj), opt); + if (reason === 'drag') { + title_height = drag.height; + title_margin = fy - drag.y - drag.height; + const changes = {}; + this.v7AttrChange(changes, 'margin', title_margin / ph); + this.v7AttrChange(changes, 'height', title_height / ph); + this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server } -}; // class RH2Painter + const g = this.createG(); -class RH2Painter extends RH2Painter$2 { + makeTranslate(g, fx, Math.round(fy - title_margin - title_height)); - /** Draw histogram bins in 3D, using provided draw options */ - draw3DBins() { - if (!this.draw_content) return; + return this.startTextDrawingAsync(textFont, 'font').then(() => { + this.drawText({ x: title_width / 2, y: title_height / 2, text: title.fText, latex: 1 }); + return this.finishTextDrawing(); + }).then(() => { + addDragHandler(this, { + x: fx, y: Math.round(fy - title_margin - title_height), width: title_width, height: title_height, + minwidth: 20, minheight: 20, no_change_x: true, redraw: d => this.redraw('drag', d) + }); + }); +} - if (this.options.Surf) - return drawBinsSurf3D(this, true); +// ========================================================== - if (this.options.Error) - return drawBinsError3D(this, true); +registerMethods(`${nsREX}RPalette`, { - if (this.options.Contour) - return drawBinsContour3D(this, true, true); + extractRColor(rcolor) { + const col = rcolor.fColor || 'black'; + return convertColor(col); + }, - drawBinsLego(this, true); - this.updatePaletteDraw(); - } + getColor(indx) { + return this.palette[indx]; + }, - draw3D(reason) { - this.mode3d = true; + getContourIndex(zc) { + const cntr = this.fContour; + let l = 0, r = cntr.length - 1; - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(); // is main histogram - let pr = Promise.resolve(this); + if (zc < cntr[0]) + return -1; + if (zc >= cntr[r]) + return r - 1; - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - return pr; + if (this.fCustomContour) { + while (l < r - 1) { + const mid = Math.round((l + r) / 2); + if (cntr[mid] > zc) + r = mid; + else + l = mid; + } + return l; } - let zmult = 1 + 2*gStyle.fHistTopMargin; - - this.zmin = main.logz ? this.gminposbin * 0.3 : this.gminbin; - this.zmax = this.gmaxbin; - if (this.options.minimum !== kNoZoom) this.zmin = this.options.minimum; - if (this.options.maximum !== kNoZoom) { this.zmax = this.options.maximum; zmult = 1; } - if (main.logz && (this.zmin <= 0)) this.zmin = this.zmax * 1e-5; - - this.deleteAttr(); + // last color in palette starts from level cntr[r-1] + return Math.floor((zc - cntr[0]) / (cntr[r - 1] - cntr[0]) * (r - 1)); + }, - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, null, this.zmin, this.zmax); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { zmult, zoom: settings.Zooming, ndim: 2, draw: true, v7: true }); - }); - } + getContourColor(zc) { + const zindx = this.getContourIndex(zc); + return (zindx < 0) ? '' : this.getColor(zindx); + }, - if (!main.mode3d) - return pr; + getContour() { + return this.fContour && (this.fContour.length > 1) ? this.fContour : null; + }, - return pr.then(() => this.drawingBins(reason)).then(() => { - // called when bins received from server, must be reentrant - const main = this.getFramePainter(); + deleteContour() { + this.fContour = undefined; + }, - this.draw3DBins(); - main.render3D(); - main.addKeysHandler(); + calcColor(value, entry1, entry2) { + const dist = entry2.fOrdinal - entry1.fOrdinal, + r1 = entry2.fOrdinal - value, + r2 = value - entry1.fOrdinal; - return this; - }); - } + if (!this.fInterpolate || (dist <= 0)) + return convertColor((r1 < r2) ? entry2.fColor : entry1.fColor); - /** @summary draw RH2 object */ - static async draw(dom, obj, opt) { - // create painter and add it to canvas - return RH2Painter._draw(new RH2Painter(dom, obj), opt); - } + // interpolate + const col1 = rgb(this.extractRColor(entry1.fColor)), + col2 = rgb(this.extractRColor(entry2.fColor)), + color = rgb(Math.round((col1.r * r1 + col2.r * r2) / dist), + Math.round((col1.g * r1 + col2.g * r2) / dist), + Math.round((col1.b * r1 + col2.b * r2) / dist)); -} // class RH2Painter + return color.formatRgb(); + }, -var RH2Painter$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -RH2Painter: RH2Painter -}); + createPaletteColors(len) { + const arr = []; + let indx = 0; -/** - * @summary Painter for RH3 classes - * - * @private - */ + while (arr.length < len) { + const value = arr.length / (len - 1), + entry = this.fColors[indx]; -class RH3Painter extends RHistPainter { + if ((Math.abs(entry.fOrdinal - value) < 0.0001) || (indx === this.fColors.length - 1)) { + arr.push(this.extractRColor(entry.fColor)); + continue; + } - /** @summary Returns histogram dimension */ - getDimension() { return 3; } + const next = this.fColors[indx + 1]; + if (next.fOrdinal <= value) + indx++; + else + arr.push(this.calcColor(value, entry, next)); + } - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) return; + return arr; + }, - const histo = this.getHisto(); - if (!histo) return; + getColorOrdinal(value) { + // extract color with ordinal value between 0 and 1 + if (!this.fColors) + return 'black'; + if ((typeof value !== 'number') || (value < 0)) + value = 0; + else if (value > 1) + value = 1; - this.extractAxesProperties(3); + // TODO: implement better way to find index - // global min/max, used at the moment in 3D drawing + let entry, next = this.fColors[0]; + for (let indx = 0; indx < this.fColors.length - 1; ++indx) { + entry = next; - if (this.isDisplayItem()) { - // take min/max values from the display item - this.gminbin = histo.fContMin; - this.gminposbin = histo.fContMinPos > 0 ? histo.fContMinPos : null; - this.gmaxbin = histo.fContMax; - } else { - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1, 1); + if (Math.abs(entry.fOrdinal - value) < 0.0001) + return this.extractRColor(entry.fColor); - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) { - for (let k = 0; k < this.nbinsz; ++k) { - const bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content < this.gminbin) this.gminbin = bin_content; else - if (bin_content > this.gmaxbin) this.gmaxbin = bin_content; - } - } - } + next = this.fColors[indx + 1]; + if (next.fOrdinal > value) + return this.calcColor(value, entry, next); } - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); - } - - /** @summary Count histogram statistic */ - countStat() { - const histo = this.getHisto(), - xaxis = this.getAxis('x'), - yaxis = this.getAxis('y'), - zaxis = this.getAxis('z'), - i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - res = { name: histo.fName, entries: 0, integral: 0, meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0 }; - let stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0, - xi, yi, zi, xx, xside, yy, yside, zz, zside, cont; - - for (xi = 1; xi <= this.nbinsx; ++xi) { - xx = xaxis.GetBinCoord(xi - 0.5); - xside = (xi <= i1+1) ? 0 : (xi > i2+1 ? 2 : 1); - - for (yi = 1; yi <= this.nbinsy; ++yi) { - yy = yaxis.GetBinCoord(yi - 0.5); - yside = (yi <= j1+1) ? 0 : (yi > j2+1 ? 2 : 1); + return this.extractRColor(next.fColor); + }, - for (zi = 1; zi <= this.nbinsz; ++zi) { - zz = zaxis.GetBinCoord(zi - 0.5); - zside = (zi <= k1+1) ? 0 : (zi > k2+1 ? 2 : 1); + setFullRange(min, max) { + // set full z scale range, used in zooming + this.full_min = min; + this.full_max = max; + }, - cont = histo.getBinContent(xi, yi, zi); - res.entries += cont; + createContour(logz, nlevels, zmin, zmax, zminpositive) { + this.fContour = []; + delete this.fCustomContour; + this.colzmin = zmin; + this.colzmax = zmax; - if ((xside === 1) && (yside === 1) && (zside === 1)) { - stat_sum0 += cont; - stat_sumx1 += xx * cont; - stat_sumy1 += yy * cont; - stat_sumz1 += zz * cont; - stat_sumx2 += xx**2 * cont; - stat_sumy2 += yy**2 * cont; - stat_sumz2 += zz**2 * cont; - } - } + if (logz) { + if (this.colzmax <= 0) + this.colzmax = 1.0; + if (this.colzmin <= 0) { + if ((zminpositive === undefined) || (zminpositive <= 0)) + this.colzmin = 0.0001 * this.colzmax; + else + this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3 * zminpositive : 1; } - } + if (this.colzmin >= this.colzmax) + this.colzmin = 0.0001 * this.colzmax; - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.meanz = stat_sumz1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); - res.rmsz = Math.sqrt(Math.abs(stat_sumz2 / stat_sum0 - res.meanz**2)); + const logmin = Math.log(this.colzmin) / Math.log(10), + logmax = Math.log(this.colzmax) / Math.log(10), + dz = (logmax - logmin) / nlevels; + this.fContour.push(this.colzmin); + for (let level = 1; level < nlevels; level++) + this.fContour.push(Math.exp((logmin + dz * level) * Math.log(10))); + this.fContour.push(this.colzmax); + this.fCustomContour = true; + } else { + if ((this.colzmin === this.colzmax) && this.colzmin) { + this.colzmax += 0.01 * Math.abs(this.colzmax); + this.colzmin -= 0.01 * Math.abs(this.colzmin); + } + const dz = (this.colzmax - this.colzmin) / nlevels; + for (let level = 0; level <= nlevels; level++) + this.fContour.push(this.colzmin + dz * level); } - res.integral = stat_sum0; - - if (histo.fEntries > 1) - res.entries = histo.fEntries; - - return res; + if (!this.palette || (this.palette.length !== nlevels)) + this.palette = this.createPaletteColors(nlevels); } - /** @summary Fill statistic */ - fillStatistic(stat, dostat /*, dofit */) { - const data = this.countStat(), - print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - // print_under = Math.floor(dostat / 10000) % 10, - // print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10; - // print_skew = Math.floor(dostat / 10000000) % 10; - // print_kurt = Math.floor(dostat / 100000000) % 10; - - stat.clearStat(); +}); - if (print_name > 0) - stat.addText(data.name); +/** @summary draw RFont object + * @private */ +function drawRFont() { + const font = this.getObject(), + svg = this.getCanvSvg(), + clname = 'custom_font_' + font.fFamily + font.fWeight + font.fStyle; + let defs = svg.selectChild('.canvas_defs'); - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); + if (defs.empty()) + defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); - stat.addText('Mean z = ' + stat.format(data.meanz)); + let entry = defs.selectChild('.' + clname); + if (entry.empty()) { + entry = defs.append('style') + .attr('type', 'text/css') + .attr('class', clname) + .text(`@font-face { font-family: "${font.fFamily}"; font-weight: ${font.fWeight ? font.fWeight : 'normal'}; font-style: ${font.fStyle ? font.fStyle : 'normal'}; src: ${font.fSrc}; }`); + const p1 = font.fSrc.indexOf('base64,'), + p2 = font.fSrc.lastIndexOf(' format('); + if (p1 > 0 && p2 > p1) { + const base64 = font.fSrc.slice(p1 + 7, p2 - 2), + is_ttf = font.fSrc.indexOf('data:application/font-ttf') > 0; + // TODO: for the moment only ttf format supported by jsPDF + if (is_ttf) + entry.property('$fontcfg', { n: font.fFamily, base64 }); } + } - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - stat.addText('Std Dev z = ' + stat.format(data.rmsz)); - } + if (font.fDefault) + this.getPadPainter()._dfltRFont = font; - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); + return true; +} +/** @summary draw RAxis object + * @private */ +function drawRAxis(dom, obj, opt) { + const painter = new RAxisPainter(dom, obj, opt); + painter.disable_zooming = true; + return ensureRCanvas(painter) + .then(() => painter.redraw()) + .then(() => painter); +} - return true; - } +/** @summary draw RFrame object + * @private */ +function drawRFrame(dom, obj, opt) { + const p = new RFramePainter(dom, obj); + if (opt === '3d') + p.mode3d = true; + return ensureRCanvas(p).then(() => p.redraw()); +} - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(ix, iy, iz) { - const lines = [], histo = this.getHisto(); - let dx = 1, dy = 1, dz = 1; +var RCanvasPainter$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +RCanvasPainter: RCanvasPainter, +RObjectPainter: RObjectPainter, +RPadPainter: RPadPainter, +drawRAxis: drawRAxis, +drawRFont: drawRFont, +drawRFrame: drawRFrame, +drawRFrameTitle: drawRFrameTitle, +drawRPadSnapshot: drawRPadSnapshot, +ensureRCanvas: ensureRCanvas +}); - if (this.isDisplayItem()) { - dx = histo.stepx || 1; - dy = histo.stepy || 1; - dz = histo.stepz || 1; - } +/** @summary draw RText object + * @private */ +function drawText() { + const text = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p = pp.getCoordinate(text.fPos, onframe), + textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }); - lines.push(this.getObjectHint(), - `x = ${this.getAxisBinTip('x', ix, dx)} xbin=${ix+1}`, - `y = ${this.getAxisBinTip('y', iy, dy)} ybin=${iy+1}`, - `z = ${this.getAxisBinTip('z', iz, dz)} zbin=${iz+1}`); + this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - const binz = histo.getBinContent(ix+1, iy+1, iz+1), - lbl = 'entries = '+ ((dx > 1) || (dy > 1) || (dz > 1) ? '~' : ''); - if (binz === Math.round(binz)) - lines.push(lbl + binz); - else - lines.push(lbl + floatToString(binz, gStyle.fStatFormat)); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + this.drawText({ x: p.x, y: p.y, text: text.fText, latex: 1 }); + return this.finishTextDrawing(); + }); +} - return lines; - } +/** @summary draw RLine object + * @private */ +function drawLine() { + const line = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p1 = pp.getCoordinate(line.fP1, onframe), + p2 = pp.getCoordinate(line.fP2, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - /** @summary Try to draw 3D histogram as scatter plot - * @desc If there are too many points, returns promise with false */ - async draw3DScatter(handle) { - const histo = this.getHisto(), - main = this.getFramePainter(), - i1 = handle.i1, i2 = handle.i2, di = handle.stepi, - j1 = handle.j1, j2 = handle.j2, dj = handle.stepj, - k1 = handle.k1, k2 = handle.k2, dk = handle.stepk; + this.createv7AttLine(); - if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) - return true; + g.append('svg:path') + .attr('d', `M${p1.x},${p1.y}L${p2.x},${p2.y}`) + .call(this.lineatt.func); +} - // scale down factor if too large values - const coef = (this.gmaxbin > 1000) ? 1000/this.gmaxbin : 1, - content_lmt = Math.max(0, this.gminbin); - let i, j, k, bin_content, numpixels = 0, sumz = 0; +/** @summary draw RBox object + * @private */ +function drawBox() { + const box = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p1 = pp.getCoordinate(box.fP1, onframe), + p2 = pp.getCoordinate(box.fP2, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - sumz += bin_content; - if (bin_content <= content_lmt) continue; - numpixels += Math.round(bin_content*coef); - } - } - } + this.createv7AttLine('border_'); - // too many pixels - use box drawing - if (numpixels > (main.webgl ? 100000 : 30000)) - return false; + this.createv7AttFill(); - const pnts = new PointsCreator(numpixels, main.webgl, main.size_x3d/200), - bins = new Int32Array(numpixels), - xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), zaxis = this.getAxis('z'), - rnd = new TRandom(sumz); - let nbin = 0; + g.append('svg:path') + .attr('d', `M${p1.x},${p1.y}H${p2.x}V${p2.y}H${p1.x}Z`) + .call(this.lineatt.func) + .call(this.fillatt.func); +} - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content <= content_lmt) continue; - const num = Math.round(bin_content*coef); +/** @summary draw RMarker object + * @private */ +function drawMarker() { + const marker = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p = pp.getCoordinate(marker.fP, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - for (let n=0; n { - main.add3DMesh(mesh); +/** @summary painter for RPalette + * + * @private + */ - mesh.bins = bins; - mesh.painter = this; - mesh.tip_color = 0x00FF00; +class RPalettePainter extends RObjectPainter { - mesh.tooltip = function(intersect) { - const indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]); - - tip.x1 = main.grx(p.getAxis('x').GetBinLowEdge(tip.ix)); - tip.x2 = main.grx(p.getAxis('x').GetBinLowEdge(tip.ix+di)); - tip.y1 = main.gry(p.getAxis('y').GetBinLowEdge(tip.iy)); - tip.y2 = main.gry(p.getAxis('y').GetBinLowEdge(tip.iy+dj)); - tip.z1 = main.grz(p.getAxis('z').GetBinLowEdge(tip.iz)); - tip.z2 = main.grz(p.getAxis('z').GetBinLowEdge(tip.iz+dk)); - tip.color = this.tip_color; - tip.opacity = 0.3; + /** @summary get palette */ + getHistPalette() { + const pal = this.getObject()?.fPalette; - return tip; - }; + if (pal && !isFunc(pal.getColor)) + exports.addMethods(pal, `${nsREX}RPalette`); - return true; - }); + return pal; } - /** @summary Drawing of 3D histogram */ - draw3DBins(handle) { - const main = this.getFramePainter(); - let fillcolor = this.v7EvalColor('fill_color', 'red'), - buffer_size = 0, use_lambert = false, - use_helper = false, use_colors = false, use_opacity = 1, use_scale = true, - single_bin_verts, single_bin_norms, - tipscale = 0.5; - - if (this.options.Sphere) { - // drawing spheres - tipscale = 0.4; - use_lambert = true; - if (this.options.Sphere === 11) use_colors = true; + /** @summary Draw palette */ + drawPalette(drag) { + const palette = this.getHistPalette(), + contour = palette.getContour(), + fp = this.getFramePainter(); + + if (!contour) + return console.log('no contour - no palette'); - const geom = main.webgl ? new SphereGeometry(0.5, 16, 12) : new SphereGeometry(0.5, 8, 6); - geom.applyMatrix4(new Matrix4().makeRotationX(Math.PI/2)); - geom.computeVertexNormals(); + // frame painter must be there + if (!fp) + return console.log('no frame painter - no palette'); - const indx = geom.getIndex().array, - pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; + const zmin = contour.at(0), + zmax = contour.at(-1), + rect = fp.getFrameRect(), + pad_width = this.getPadPainter().getPadWidth(), + pad_height = this.getPadPainter().getPadHeight(), + visible = this.v7EvalAttr('visible', true), + vertical = this.v7EvalAttr('vertical', true), + g = this.getG(); + let gmin = palette.full_min, + gmax = palette.full_max, + palette_x, palette_y, palette_width, palette_height; - buffer_size = indx.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); + if (drag) { + palette_width = drag.width; + palette_height = drag.height; - for (let k=0; k fp.unzoom('z')); + } - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.Color && ((bin_content === 0) || (bin_content < this.gminbin))) continue; - wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1; - if (wei < 1e-3) continue; // do not draw empty or very small bins + fp.z_handle.maxTickSize = Math.round(palette_width * 0.3); - nbins++; + const promise = fp.z_handle.drawAxis(g, makeTranslate(vertical ? palette_width : 0, palette_height), vertical ? -1 : 1); - if (!use_colors) continue; + if (this.isBatchMode() || drag) + return promise; - const colindx = palette.getContourIndex(bin_content); - if (colindx >= 0) { - if (cols_size[colindx] === undefined) { - cols_size[colindx] = 0; - cols_sequence[colindx] = num_colors++; - } - cols_size[colindx]+=1; - } else - console.error(`not found color for value = ${bin_content}`); - } + return promise.then(() => { + if (settings.ContextMenu) { + g.on('contextmenu', evnt => { + evnt.stopPropagation(); // disable main context menu + evnt.preventDefault(); // disable browser context menu + createMenu(evnt, this).then(menu => { + menu.header('Palette'); + menu.addchk(vertical, 'Vertical', flag => { + this.v7SetAttr('vertical', flag); + this.redrawPad(); + }); + fp.z_handle.fillAxisContextMenu(menu, 'z'); + menu.show(); + }); + }); } - } - if (!use_colors) { - cols_size.push(nbins); - num_colors = 1; - cols_sequence = [0]; - } + addDragHandler(this, { + x: palette_x, y: palette_y, width: palette_width, height: palette_height, + minwidth: 20, minheight: 20, no_change_x: !vertical, no_change_y: vertical, redraw: d => this.drawPalette(d) + }); - const cols_nbins = new Array(num_colors), - bin_verts = new Array(num_colors), - bin_norms = new Array(num_colors), - bin_tooltips = new Array(num_colors), - helper_kind = new Array(num_colors), - helper_indexes = new Array(num_colors), // helper_kind === 1, use original vertices - helper_positions = new Array(num_colors); // helper_kind === 2, all vertices copied into separate buffer + if (!settings.Zooming) + return; - for (let ncol = 0; ncol < cols_size.length; ++ncol) { - if (!cols_size[ncol]) continue; // ignore dummy colors + let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect, zoom_rect_visible, moving_labels, last_pos; - nbins = cols_size[ncol]; // how many bins with specified color - const nseq = cols_sequence[ncol]; + const moveRectSel = evnt => { + if (!doing_zoom) + return; + evnt.preventDefault(); - cols_nbins[nseq] = 0; // counter for the filled bins + last_pos = pointer(evnt, this.getG().node()); - helper_kind[nseq] = 0; + if (moving_labels) + return fp.z_handle.processLabelsMove('move', last_pos); - // 1 - use same vertices to create helper, one can use maximal 64K vertices - // 2 - all vertices copied into separate buffer - if (use_helper) - helper_kind[nseq] = (nbins * buffer_size / 3 > 0xFFF0) ? 2 : 1; + if (vertical) + sel2 = Math.min(Math.max(last_pos[1], 0), palette_height); + else + sel2 = Math.min(Math.max(last_pos[0], 0), palette_width); - bin_verts[nseq] = new Float32Array(nbins * buffer_size); - bin_norms[nseq] = new Float32Array(nbins * buffer_size); - bin_tooltips[nseq] = new Int32Array(nbins); + const sz = Math.abs(sel2 - sel1); - if (helper_kind[nseq] === 1) - helper_indexes[nseq] = new Uint16Array(nbins * Box3D.MeshSegments.length); + if (!zoom_rect_visible && (sz > 1)) { + zoom_rect.style('display', null); + zoom_rect_visible = true; + } - if (helper_kind[nseq] === 2) - helper_positions[nseq] = new Float32Array(nbins * Box3D.Segments.length * 3); - } + if (vertical) + zoom_rect.attr('y', Math.min(sel1, sel2)).attr('height', sz); + else + zoom_rect.attr('x', Math.min(sel1, sel2)).attr('width', sz); + }, endRectSel = evnt => { + if (!doing_zoom) + return; - const xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), zaxis = this.getAxis('z'); - let grx1, grx2, gry1, gry2, grz1, grz2; + evnt.preventDefault(); + select(window).on('mousemove.colzoomRect', null) + .on('mouseup.colzoomRect', null); + zoom_rect.remove(); + zoom_rect = null; + doing_zoom = false; - for (i = i1; i < i2; i += di) { - grx1 = main.grx(xaxis.GetBinLowEdge(i+1)); - grx2 = main.grx(xaxis.GetBinLowEdge(i+2)); - for (j = j1; j < j2; j += dj) { - gry1 = main.gry(yaxis.GetBinLowEdge(j+1)); - gry2 = main.gry(yaxis.GetBinLowEdge(j+2)); - for (k = k1; k < k2; k +=dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.Color && ((bin_content === 0) || (bin_content < this.gminbin))) continue; + if (moving_labels) + fp.z_handle.processLabelsMove('stop', last_pos); + else { + const z = fp.z_handle.func, z1 = z.invert(sel1), z2 = z.invert(sel2); + fp.zoomSingle('z', Math.min(z1, z2), Math.max(z1, z2)); + } + }, startRectSel = evnt => { + // ignore when touch selection is activated + if (doing_zoom) + return; + doing_zoom = true; - wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1; - if (wei < 1e-3) continue; // do not show very small bins + evnt.preventDefault(); + evnt.stopPropagation(); - let nseq = 0; - if (use_colors) { - const colindx = palette.getContourIndex(bin_content); - if (colindx < 0) continue; - nseq = cols_sequence[colindx]; - } + last_pos = pointer(evnt, this.getG().node()); + sel1 = sel2 = last_pos[vertical ? 1 : 0]; + zoom_rect_visible = false; + moving_labels = false; + zoom_rect = g_btns + .append('svg:rect') + .attr('class', 'zoom') + .attr('id', 'colzoomRect') + .style('display', 'none'); + if (vertical) + zoom_rect.attr('x', 0).attr('width', palette_width).attr('y', sel1).attr('height', 1); + else + zoom_rect.attr('x', sel1).attr('width', 1).attr('y', 0).attr('height', palette_height); - nbins = cols_nbins[nseq]; + select(window).on('mousemove.colzoomRect', moveRectSel) + .on('mouseup.colzoomRect', endRectSel, true); - grz1 = main.grz(zaxis.GetBinLowEdge(k+1)); - grz2 = main.grz(zaxis.GetBinLowEdge(k+2)); + setTimeout(() => { + if (!zoom_rect_visible && doing_zoom) + moving_labels = fp.z_handle.processLabelsMove('start', last_pos); + }, 500); + }, assignHandlers = () => { + this.getG().selectAll('.axis_zoom, .axis_labels') + .on('mousedown', startRectSel) + .on('dblclick', () => fp.unzoom('z')); - // remember bin index for tooltip - bin_tooltips[nseq][nbins] = histo.getBin(i+1, j+1, k+1); + if (settings.ZoomWheel) { + this.getG().on('wheel', evnt => { + evnt.stopPropagation(); + evnt.preventDefault(); - let vvv = nbins * buffer_size; - const bin_v = bin_verts[nseq], bin_n = bin_norms[nseq]; + const pos = pointer(evnt, this.getG().node()), + coord = vertical ? (1 - pos[1] / palette_height) : pos[0] / palette_width, - // Grab the coordinates and scale that are being assigned to each bin - for (let vi = 0; vi < buffer_size; vi += 3, vvv += 3) { - bin_v[vvv] = (grx2 + grx1) / 2 + single_bin_verts[vi] * (grx2 - grx1) * wei; - bin_v[vvv+1] = (gry2 + gry1) / 2 + single_bin_verts[vi+1] * (gry2 - gry1) * wei; - bin_v[vvv+2] = (grz2 + grz1) / 2 + single_bin_verts[vi+2] * (grz2 - grz1) * wei; + item = fp.z_handle.analyzeWheelEvent(evnt, coord); + if (item.changed) + fp.zoomSingle('z', item.min, item.max); + }); + } + }; - bin_n[vvv] = single_bin_norms[vi]; - bin_n[vvv+1] = single_bin_norms[vi+1]; - bin_n[vvv+2] = single_bin_norms[vi+2]; - } + fp.z_handle.setAfterDrawHandler(assignHandlers); - if (helper_kind[nseq] === 1) { - // reuse vertices created for the mesh - const helper_segments = Box3D.MeshSegments; - vvv = nbins * helper_segments.length; - const shift = Math.round(nbins * buffer_size/3), - helper_i = helper_indexes[nseq]; - for (let n = 0; n < helper_segments.length; ++n) - helper_i[vvv+n] = shift + helper_segments[n]; - } + assignHandlers(); + }); + } - if (helper_kind[nseq] === 2) { - const helper_segments = Box3D.Segments, - helper_p = helper_positions[nseq]; - vvv = nbins * helper_segments.length * 3; - for (let n = 0; n < helper_segments.length; ++n, vvv += 3) { - const vert = Box3D.Vertices[helper_segments[n]]; - helper_p[vvv] = (grx2 + grx1) / 2 + (vert.x - 0.5) * (grx2 - grx1) * wei; - helper_p[vvv+1] = (gry2 + gry1) / 2 + (vert.y - 0.5) * (gry2 - gry1) * wei; - helper_p[vvv+2] = (grz2 + grz1) / 2 + (vert.z - 0.5) * (grz2 - grz1) * wei; - } - } + /** @summary draw RPalette object */ + static async draw(dom, palette, opt) { + const painter = new RPalettePainter(dom, palette, opt, 'palette'); + return ensureRCanvas(painter).then(() => { + painter.createG(); // just create container, real drawing will be done by histogram + return painter; + }); + } - cols_nbins[nseq] = nbins+1; - } - } - } +} // class RPalettePainter - for (let ncol = 0; ncol < cols_size.length; ++ncol) { - if (!cols_size[ncol]) continue; // ignore dummy colors +var v7more = /*#__PURE__*/Object.freeze({ +__proto__: null, +RPalettePainter: RPalettePainter, +drawBox: drawBox, +drawLine: drawLine, +drawMarker: drawMarker, +drawText: drawText +}); - const nseq = cols_sequence[ncol], - // BufferGeometries that store geometry of all bins - all_bins_buffgeom = new BufferGeometry(); +const ECorner = { kTopLeft: 1, kTopRight: 2, kBottomLeft: 3, kBottomRight: 4 }; - // Create mesh from bin buffergeometry - all_bins_buffgeom.setAttribute('position', new BufferAttribute(bin_verts[nseq], 3)); - all_bins_buffgeom.setAttribute('normal', new BufferAttribute(bin_norms[nseq], 3)); +/** + * @summary Painter for RPave class + * + * @private + */ - if (use_colors) fillcolor = palette.getColor(ncol); +class RPavePainter extends RObjectPainter { - const material = use_lambert - ? new MeshLambertMaterial({ color: fillcolor, opacity: use_opacity, transparent: use_opacity < 1, vertexColors: false }) - : new MeshBasicMaterial({ color: fillcolor, opacity: use_opacity, transparent: use_opacity < 1, vertexColors: false }), - combined_bins = new Mesh(all_bins_buffgeom, material); + /** @summary Draw pave content + * @desc assigned depending on pave class */ + async drawContent() { return this; } - combined_bins.bins = bin_tooltips[nseq]; - combined_bins.bins_faces = buffer_size/9; - combined_bins.painter = this; - combined_bins.tipscale = tipscale; - combined_bins.tip_color = 0x00FF00; - combined_bins.use_scale = use_scale; + /** @summary Draw pave */ + async drawPave() { + const rect = this.getPadPainter().getPadRect(), + fp = this.getFramePainter(); - combined_bins.tooltip = function(intersect) { - const indx = Math.floor(intersect.faceIndex / this.bins_faces); - if ((indx < 0) || (indx >= this.bins.length)) return null; + this.onFrame = fp && this.v7EvalAttr('onFrame', true); + this.corner = this.v7EvalAttr('corner', ECorner.kTopRight); - const p = this.painter, - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]), - grx1 = main.grx(xaxis.GetBinCoord(tip.ix-1)), - grx2 = main.grx(xaxis.GetBinCoord(tip.ix)), - gry1 = main.gry(yaxis.GetBinCoord(tip.iy-1)), - gry2 = main.gry(yaxis.GetBinCoord(tip.iy)), - grz1 = main.grz(zaxis.GetBinCoord(tip.iz-1)), - grz2 = main.grz(zaxis.GetBinCoord(tip.iz)), - wei2 = (this.use_scale ? Math.pow(Math.abs(tip.value*this.use_scale), 0.3333) : 1) * this.tipscale; + const visible = this.v7EvalAttr('visible', true), + offsetx = this.v7EvalLength('offsetX', rect.width, 0.02), + offsety = this.v7EvalLength('offsetY', rect.height, 0.02), + pave_width = this.v7EvalLength('width', rect.width, 0.3), + pave_height = this.v7EvalLength('height', rect.height, 0.3), + g = this.createG(); - tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; - tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; - tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; - tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; - tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; - tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; - tip.color = this.tip_color; + g.classed('most_upper_primitives', true); // this primitive will remain on top of list - return tip; - }; + if (!visible) + return this; - main.add3DMesh(combined_bins); + this.createv7AttLine('border_'); - if (helper_kind[nseq] > 0) { - const lcolor = this.v7EvalColor('line_color', 'lightblue'), - helper_material = new LineBasicMaterial({ color: lcolor }), - lines = (helper_kind[nseq] === 1) - // reuse positions from the mesh - only special index was created - ? createLineSegments(bin_verts[nseq], helper_material, helper_indexes[nseq]) - : createLineSegments(helper_positions[nseq], helper_material); + this.createv7AttFill(); - main.add3DMesh(lines); - } + const fr = this.onFrame ? fp.getFrameRect() : rect; + let pave_x = 0, pave_y = 0; + switch (this.corner) { + case ECorner.kTopLeft: + pave_x = fr.x + offsetx; + pave_y = fr.y + offsety; + break; + case ECorner.kBottomLeft: + pave_x = fr.x + offsetx; + pave_y = fr.y + fr.height - offsety - pave_height; + break; + case ECorner.kBottomRight: + pave_x = fr.x + fr.width - offsetx - pave_width; + pave_y = fr.y + fr.height - offsety - pave_height; + break; + case ECorner.kTopRight: + default: + pave_x = fr.x + fr.width - offsetx - pave_width; + pave_y = fr.y + offsety; } - if (use_colors) - this.updatePaletteDraw(); - } + makeTranslate(g, pave_x, pave_y); - draw3D() { - if (!this.draw_content) - return false; + g.append('svg:rect') + .attr('x', 0) + .attr('width', pave_width) + .attr('y', 0) + .attr('height', pave_height) + .call(this.lineatt.func) + .call(this.fillatt.func); + + this.pave_width = pave_width; + this.pave_height = pave_height; - // this.options.Scatter = false; - // this.options.Box = true; + // here should be fill and draw of text + + return this.drawContent().then(() => { + if (!this.isBatchMode()) { + // TODO: provide pave context menu as in v6 + if (settings.ContextMenu && this.paveContextMenu) + g.on('contextmenu', evnt => this.paveContextMenu(evnt)); - const handle = this.prepareDraw({ only_indexes: true, extra: -0.5, right_extra: -1 }), - pr = this.options.Scatter ? this.draw3DScatter(handle) : Promise.resolve(false); + addDragHandler(this, { + x: pave_x, y: pave_y, width: pave_width, height: pave_height, + minwidth: 20, minheight: 20, redraw: d => this.sizeChanged(d) + }); + } - return pr.then(res => { - return res || this.draw3DBins(handle); + return this; }); } - /** @summary Redraw histogram */ - redraw(reason) { - const main = this.getFramePainter(); // who makes axis and 3D drawing + /** @summary Process interactive moving of the stats box */ + sizeChanged(drag) { + this.pave_width = drag.width; + this.pave_height = drag.height; - if (reason === 'resize') { - if (main.resize3D()) main.render3D(); - return this; + const pave_x = drag.x, + pave_y = drag.y, + rect = this.getPadPainter().getPadRect(), + fr = this.onFrame ? this.getFramePainter().getFrameRect() : rect, + changes = {}; + let offsetx, offsety; + + switch (this.corner) { + case ECorner.kTopLeft: + offsetx = pave_x - fr.x; + offsety = pave_y - fr.y; + break; + case ECorner.kBottomLeft: + offsetx = pave_x - fr.x; + offsety = fr.y + fr.height - pave_y - this.pave_height; + break; + case ECorner.kBottomRight: + offsetx = fr.x + fr.width - pave_x - this.pave_width; + offsety = fr.y + fr.height - pave_y - this.pave_height; + break; + case ECorner.kTopRight: + default: + offsetx = fr.x + fr.width - pave_x - this.pave_width; + offsety = pave_y - fr.y; } - assignFrame3DMethods(main); - return main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, this.getAxis('z'), this.zmin, this.zmax); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { zoom: settings.Zooming, ndim: 3, draw: true, v7: true }); - return this.drawingBins(reason); - }).then(() => this.draw3D()).then(() => { - main.render3D(); - main.addKeysHandler(); - return this; - }); - } + this.v7AttrChange(changes, 'offsetX', offsetx / rect.width); + this.v7AttrChange(changes, 'offsetY', offsety / rect.height); + this.v7AttrChange(changes, 'width', this.pave_width / rect.width); + this.v7AttrChange(changes, 'height', this.pave_height / rect.height); + this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - /** @summary Fill pad toolbar with RH3-related functions */ - fillToolbar() { - const pp = this.getPadPainter(); - if (!pp) return; + this.getG().selectChild('rect') + .attr('width', this.pave_width) + .attr('height', this.pave_height); - pp.addPadButton('auto_zoom', 'Unzoom all axes', 'ToggleZoom', 'Ctrl *'); - if (this.draw_content) - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - pp.showPadButtons(); + this.drawContent(); } - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - let obj = this.getHisto(); - if (obj) obj = obj['f'+axis.toUpperCase()+'axis']; - return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); + /** @summary Redraw RPave object */ + async redraw(/* reason */) { + return this.drawPave(); } - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - const i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - histo = this.getHisto(); - let i, j, k; + /** @summary draw RPave object */ + static async draw(dom, pave, opt) { + const painter = new RPavePainter(dom, pave, opt, 'pave'); + return ensureRCanvas(painter).then(() => painter.drawPave()); + } - if ((i1 === i2) || (j1 === j2) || (k1 === k2)) return; +} - // first find minimum - let min = histo.getBinContent(i1 + 1, j1 + 1, k1+1); - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) - min = Math.min(min, histo.getBinContent(i+1, j+1, k+1)); - } - } - if (min > 0) return; // if all points positive, no chance for autoscale +/** + * @summary Painter for RLegend class + * + * @private + */ - let ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1; +class RLegendPainter extends RPavePainter { - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - if (histo.getBinContent(i+1, j+1, k+1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - if (k < kleft) kleft = k; - if (k >= kright) kright = k + 1; - } - } - } - } + /** @summary draw RLegend content */ + async drawContent() { + const legend = this.getObject(), + textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), + width = this.pave_width, + height = this.pave_height, + pp = this.getPadPainter(); - let xmin, xmax, ymin, ymax, zmin, zmax, isany = false; + let nlines = legend.fEntries.length; + if (legend.fTitle) + nlines++; - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } - if ((kleft === kright-1) && (kleft > k1+1) && (kright < k2-1)) { kleft--; kright++; } + if (!nlines || !pp) + return this; - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = this.getAxis('x').GetBinLowEdge(ileft+1); - xmax = this.getAxis('x').GetBinLowEdge(iright+1); - isany = true; - } + const stepy = height / nlines, margin_x = 0.02 * width; - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = this.getAxis('y').GetBinLowEdge(jleft+1); - ymax = this.getAxis('y').GetBinLowEdge(jright+1); - isany = true; - } + textFont.setSize(height / (nlines * 1.2)); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + let posy = 0; - if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) { - zmin = this.getAxis('z').GetBinLowEdge(kleft+1); - zmax = this.getAxis('z').GetBinLowEdge(kright+1); - isany = true; - } + if (legend.fTitle) { + this.drawText({ latex: 1, width: width - 2 * margin_x, height: stepy, x: margin_x, y: posy, text: legend.fTitle }); + posy += stepy; + } - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax, zmin, zmax); - } + for (let i = 0; i < legend.fEntries.length; ++i) { + const entry = legend.fEntries[i], w4 = Math.round(width / 4); + let objp = null; - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - const opts = this.getSupportedDrawOptions(); + this.drawText({ latex: 1, width: 0.75 * width - 3 * margin_x, height: stepy, x: 2 * margin_x + w4, y: posy, text: entry.fLabel }); - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); + if (entry.fDrawableId !== 'custom') + objp = pp.findSnap(entry.fDrawableId, true); + else if (entry.fDrawable.fIO) { + objp = new RObjectPainter(this.getPadPainter(), entry.fDrawable.fIO); + if (entry.fLine) + objp.createv7AttLine(); + if (entry.fFill) + objp.createv7AttFill(); + if (entry.fMarker) + objp.createv7AttMarker(); + } - this.decodeOptions(arg); + if (entry.fFill && objp?.fillatt) { + this.appendPath(`M${Math.round(margin_x)},${Math.round(posy + stepy * 0.1)}h${w4}v${Math.round(stepy * 0.8)}h${-w4}z`) + .call(objp.fillatt.func); + } - this.interactiveRedraw(true, 'drawopt'); + if (entry.fLine && objp?.lineatt) { + this.appendPath(`M${Math.round(margin_x)},${Math.round(posy + stepy / 2)}h${w4}`) + .call(objp.lineatt.func); + } + + if (entry.fError && objp?.lineatt) { + this.appendPath(`M${Math.round(margin_x + width / 8)},${Math.round(posy + stepy * 0.2)}v${Math.round(stepy * 0.6)}`) + .call(objp.lineatt.func); + } + + if (entry.fMarker && objp?.markeratt) { + this.appendPath(objp.markeratt.create(margin_x + width / 8, posy + stepy / 2)) + .call(objp.markeratt.func); + } + + posy += stepy; + } + + return this.finishTextDrawing(); }); } - /** @summary draw RH3 object */ - static async draw(dom, histo /* ,opt */) { - const painter = new RH3Painter(dom, histo); - painter.mode3d = true; + /** @summary draw RLegend object */ + static async draw(dom, legend, opt) { + const painter = new RLegendPainter(dom, legend, opt, 'legend'); + return ensureRCanvas(painter).then(() => painter.drawPave()); + } + +} // class RLegendPainter - return ensureRCanvas(painter, '3d').then(() => { - painter.setAsMainPainter(); - painter.options = { Box: 0, Scatter: false, Sphere: 0, Color: false, minimum: kNoZoom, maximum: kNoZoom, FrontBox: false, BackBox: false }; +/** + * @summary Painter for RPaveText class + * + * @private + */ - const kind = painter.v7EvalAttr('kind', ''), - sub = painter.v7EvalAttr('sub', 0), - o = painter.options; +class RPaveTextPainter extends RPavePainter { - switch (kind) { - case 'box': o.Box = 10 + sub; break; - case 'sphere': o.Sphere = 10 + sub; break; - case 'col': o.Color = true; break; - case 'scat': o.Scatter = true; break; - default: o.Box = 10; - } + /** @summary draw RPaveText content */ + async drawContent() { + const pavetext = this.getObject(), + nlines = pavetext?.fText.length; - painter.scanContent(); - return painter.redraw(); - }); - } + if (!nlines) + return; -} // class RH3Painter + const textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), + width = this.pave_width, + height = this.pave_height, + stepy = height / nlines, margin_x = 0.02 * width; -/** @summary draw RHistDisplayItem object - * @private */ -function drawHistDisplayItem(dom, obj, opt) { - if (!obj) - return null; + textFont.setSize(height / (nlines * 1.2)); - if (obj.fAxes.length === 1) - return RH1Painter.draw(dom, obj, opt); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + for (let i = 0, posy = 0; i < pavetext.fText.length; ++i, posy += stepy) + this.drawText({ latex: 1, width: width - 2 * margin_x, height: stepy, x: margin_x, y: posy, text: pavetext.fText[i] }); - if (obj.fAxes.length === 2) - return RH2Painter.draw(dom, obj, opt); + return this.finishTextDrawing(undefined, true); + }); + } - if (obj.fAxes.length === 3) - return RH3Painter.draw(dom, obj, opt); + /** @summary draw RPaveText object */ + static async draw(dom, pave, opt) { + const painter = new RPaveTextPainter(dom, pave, opt, 'pavetext'); + return ensureRCanvas(painter).then(() => painter.drawPave()); + } - return null; -} +} // class RPaveTextPainter -var RH3Painter$1 = /*#__PURE__*/Object.freeze({ +var RPavePainter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, -RH3Painter: RH3Painter, -drawHistDisplayItem: drawHistDisplayItem +RLegendPainter: RLegendPainter, +RPavePainter: RPavePainter, +RPaveTextPainter: RPaveTextPainter }); exports.BIT = BIT; @@ -127322,21 +184901,28 @@ exports.GridDisplay = GridDisplay; exports.HierarchyPainter = HierarchyPainter; exports.MDIDisplay = MDIDisplay; exports.ObjectPainter = ObjectPainter; +exports.RTreeMapPainter = RTreeMapPainter; +exports.TCanvasPainter = TCanvasPainter; exports.TGeoPainter = TGeoPainter; +exports.TGraphPainter = TGraphPainter; exports.TH1Painter = TH1Painter; exports.TH2Painter = TH2Painter; exports.TH3Painter = TH3Painter; +exports.THREE = THREE; +exports.TPadPainter = TPadPainter; exports.TRandom = TRandom; exports.TSelector = TSelector; exports.TabsDisplay = TabsDisplay; -exports._ensureJSROOT = _ensureJSROOT; exports._loadJSDOM = _loadJSDOM; exports.addDrawFunc = addDrawFunc; exports.addHighlightStyle = addHighlightStyle; -exports.addMethods = addMethods; +exports.addMoveHandler = addMoveHandler; +exports.addUserStreamer = addUserStreamer; +exports.assignContextMenu = assignContextMenu; exports.atob_func = atob_func; exports.browser = browser; exports.btoa_func = btoa_func; +exports.build3d = build3d; exports.buildGUI = buildGUI; exports.buildSvgCurve = buildSvgCurve; exports.clTAnnotation = clTAnnotation; @@ -127353,6 +184939,7 @@ exports.clTColor = clTColor; exports.clTCutG = clTCutG; exports.clTDiamond = clTDiamond; exports.clTF1 = clTF1; +exports.clTF12 = clTF12; exports.clTF2 = clTF2; exports.clTF3 = clTF3; exports.clTFile = clTFile; @@ -127369,8 +184956,10 @@ exports.clTGraphPolargram = clTGraphPolargram; exports.clTGraphTime = clTGraphTime; exports.clTH1 = clTH1; exports.clTH1D = clTH1D; +exports.clTH1F = clTH1F; exports.clTH1I = clTH1I; exports.clTH2 = clTH2; +exports.clTH2D = clTH2D; exports.clTH2F = clTH2F; exports.clTH2I = clTH2I; exports.clTH3 = clTH3; @@ -127382,8 +184971,10 @@ exports.clTLatex = clTLatex; exports.clTLegend = clTLegend; exports.clTLegendEntry = clTLegendEntry; exports.clTLine = clTLine; +exports.clTLink = clTLink; exports.clTList = clTList; exports.clTMap = clTMap; +exports.clTMarker = clTMarker; exports.clTMathText = clTMathText; exports.clTMultiGraph = clTMultiGraph; exports.clTNamed = clTNamed; @@ -127407,8 +184998,10 @@ exports.clTProfile3D = clTProfile3D; exports.clTString = clTString; exports.clTStyle = clTStyle; exports.clTText = clTText; +exports.clTTree = clTTree; exports.cleanup = cleanup; exports.clone = clone; +exports.closeMenu = closeMenu; exports.compressSVG = compressSVG; exports.constants = constants$1; exports.convertDate = convertDate; @@ -127416,6 +185009,8 @@ exports.create = create$1; exports.createGeoPainter = createGeoPainter; exports.createHistogram = createHistogram; exports.createHttpRequest = createHttpRequest; +exports.createMenu = createMenu; +exports.createRootColors = createRootColors; exports.createTGraph = createTGraph; exports.createTHStack = createTHStack; exports.createTMultiGraph = createTMultiGraph; @@ -127424,21 +185019,32 @@ exports.d3_select = select; exports.decodeUrl = decodeUrl; exports.draw = draw; exports.drawRawText = drawRawText; +exports.drawTFrame = drawTFrame; +exports.drawTPadSnapshot = drawTPadSnapshot; exports.drawingJSON = drawingJSON; +exports.ensureTCanvas = ensureTCanvas; +exports.extendRootColors = extendRootColors; exports.findFunction = findFunction; exports.floatToString = floatToString; exports.gStyle = gStyle; exports.geoCfg = geoCfg; exports.getAbsPosInCanvas = getAbsPosInCanvas; exports.getActivePad = getActivePad; +exports.getBoxDecorations = getBoxDecorations; +exports.getColor = getColor; exports.getDocument = getDocument; +exports.getDomCanvasPainter = getDomCanvasPainter; exports.getElementCanvPainter = getElementCanvPainter; exports.getElementMainPainter = getElementMainPainter; +exports.getElementPadPainter = getElementPadPainter; exports.getElementRect = getElementRect; exports.getHPainter = getHPainter; +exports.getKindForType = getKindForType; exports.getMethods = getMethods; exports.getPromise = getPromise; exports.getTDatime = getTDatime; +exports.getTypeForKind = getTypeForKind; +exports.hasMenu = hasMenu; exports.httpRequest = httpRequest; exports.injectCode = injectCode; exports.internals = internals; @@ -127447,6 +185053,7 @@ exports.isBatchMode = isBatchMode; exports.isFunc = isFunc; exports.isNodeJs = isNodeJs; exports.isObject = isObject; +exports.isPadPainter = isPadPainter; exports.isPromise = isPromise; exports.isRootCollection = isRootCollection; exports.isStr = isStr; @@ -127455,21 +185062,28 @@ exports.kAxisLabels = kAxisLabels; exports.kAxisNormal = kAxisNormal; exports.kAxisTime = kAxisTime; exports.kInspect = kInspect; +exports.kNoReorder = kNoReorder; exports.kNoStats = kNoStats; exports.kNoZoom = kNoZoom; exports.kTitle = kTitle; +exports.kToFront = kToFront; +exports.loadMathjax = loadMathjax; +exports.loadModules = loadModules; exports.loadOpenui5 = loadOpenui5; exports.loadScript = loadScript; exports.makeImage = makeImage; exports.makeSVG = makeSVG; exports.makeTranslate = makeTranslate; exports.nsREX = nsREX; +exports.nsROOT = nsROOT; exports.nsSVG = nsSVG; exports.openFile = openFile; -exports.parse = parse; +exports.parse = parse$1; exports.parseMulti = parseMulti; exports.postponePromise = postponePromise; +exports.prJSON = prJSON; exports.prROOT = prROOT; +exports.prSVG = prSVG; exports.readStyleFromURL = readStyleFromURL; exports.redraw = redraw; exports.registerForResize = registerForResize; @@ -127482,9 +185096,13 @@ exports.setHPainter = setHPainter; exports.setHistogramTitle = setHistogramTitle; exports.setSaveFile = setSaveFile; exports.settings = settings; +exports.showPainterMenu = showPainterMenu; exports.svgToImage = svgToImage; exports.toJSON = toJSON; exports.treeDraw = treeDraw; +exports.treeProcess = treeProcess; +exports.unzipJSON = unzipJSON; +exports.urlClassPrefix = urlClassPrefix; exports.version = version; exports.version_date = version_date; exports.version_id = version_id; diff --git a/build/rollup.config.js b/build/rollup.config.js index e1c3221dc..6ba08b1e0 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -1,17 +1,17 @@ import json from '@rollup/plugin-json'; import nodeResolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; -import modify from 'rollup-plugin-modify'; +import replace from '@rollup/plugin-replace'; import ascii from 'rollup-plugin-ascii'; import ignore from 'rollup-plugin-ignore'; -import meta from '../package.json' assert {type: 'json'}; +import meta from '../package.json' with { type: 'json' }; -const ignore_modules = ['fs', 'zlib', 'gl', './base/lzma.mjs', './base/zstd.mjs', '../../scripts/jspdf.es.min.js', '../../scripts/svg2pdf.es.min.js']; +const ignore_jsroot_modules = [ './base/lzma.mjs', './base/zstd.mjs' ]; -const importMetaUrlPolyfill = `(typeof document === 'undefined' && typeof location === 'undefined' ? undefined : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('jsroot.js', document.baseURI).href));`; +const external_node_modules = ['mathjax', 'jsdom', 'fs', 'canvas', 'tmp', 'zlib', 'xhr2', 'node:worker_threads', '@oneidentity/zstd-js', 'gl', 'three', 'three/addons']; -for(let key in meta.dependencies) - ignore_modules.push(key); +// TODO: maybe keep node modules as external to be able use produced builds as well? +const ignore_modules = ignore_jsroot_modules.concat(external_node_modules); const config = { input: "modules/main.mjs", @@ -25,49 +25,47 @@ const config = { banner: `// ${meta.homepage} v${meta.version}` }, plugins: [ - modify({ - 'import.meta?.url': importMetaUrlPolyfill - }), ignore(ignore_modules), nodeResolve(), json(), ascii() ], onwarn(message, warn) { - if (message.code === "CIRCULAR_DEPENDENCY") return; + if (message.code === "CIRCULAR_DEPENDENCY") + return; warn(message); } }; const config_hist = { - ...config, - input: "modules/hist/bundle.mjs", - output: { - ...config.output, - file: "build/hist.js", - inlineDynamicImports: true - } + ...config, + input: "modules/hist/bundle.mjs", + output: { + ...config.output, + file: "build/hist.js", + inlineDynamicImports: true + } } const config_2d = { - ...config, - input: "modules/hist2d/bundle.mjs", - output: { - ...config.output, - file: "build/hist2d.js", - inlineDynamicImports: true - } + ...config, + input: "modules/hist2d/bundle.mjs", + output: { + ...config.output, + file: "build/hist2d.js", + inlineDynamicImports: true + } } const config_geom = { - ...config, - input: "modules/geom/bundle.mjs", - output: { - ...config.output, - format: 'es', - file: 'build/geom.mjs', - inlineDynamicImports: true - } + ...config, + input: "modules/geom/bundle.mjs", + output: { + ...config.output, + format: 'es', + file: 'build/geom.mjs', + inlineDynamicImports: true + } } const config_geom_nothreejs = { @@ -75,65 +73,66 @@ const config_geom_nothreejs = { input: "modules/geom/bundle.mjs", external: ['three', 'three/addons'], output: { - ...config.output, - format: 'es', - file: 'build/geom_nothreejs.mjs', - inlineDynamicImports: true + ...config.output, + format: 'es', + file: 'build/geom_nothreejs.mjs', + inlineDynamicImports: true }, plugins: [ - modify({ + replace({ + delimiters: ['', ''], + preventAssignment: true, "from '../three.mjs'": "from 'three'", "from '../three_addons.mjs'": "from 'three/addons'", - 'import.meta?.url': importMetaUrlPolyfill - }), - ignore(ignore_modules), - nodeResolve(), - json(), - ascii() + }), + ignore(ignore_modules), + nodeResolve(), + json(), + ascii() ], } const config_minified = { - ...config, - output: { - ...config.output, - file: "build/jsroot.min.js", - inlineDynamicImports: true - }, - plugins: [ - ...config.plugins, - terser({ - output: { - preamble: config.output.banner - }, - mangle: { - reserved: [ - "InternMap", - "InternSet" - ] - } - }) - ] + ...config, + output: { + ...config.output, + file: "build/jsroot.min.js", + inlineDynamicImports: true + }, + plugins: [ + ...config.plugins, + terser({ + output: { + preamble: config.output.banner + }, + mangle: { + reserved: [ + "InternMap", + "InternSet" + ] + } + }) + ] } const config_hist_minified = { - ...config_minified, - input: "modules/hist/bundle.mjs", - output: { - ...config.output, - file: "build/hist.min.js", - inlineDynamicImports: true - } + ...config_minified, + input: "modules/hist/bundle.mjs", + output: { + ...config.output, + file: "build/hist.min.js", + inlineDynamicImports: true + } } const config_2d_minified = { - ...config_minified, - input: "modules/hist2d/bundle.mjs", - output: { - ...config.output, - file: "build/hist2d.min.js", - inlineDynamicImports: true - } + ...config_minified, + input: "modules/hist2d/bundle.mjs", + output: { + ...config.output, + file: "build/hist2d.min.js", + inlineDynamicImports: true + } } export default [ diff --git a/changes.md b/changes.md index 5ddd3cecb..b97276f2b 100644 --- a/changes.md +++ b/changes.md @@ -1,10 +1,226 @@ # JSROOT changelog + ## Changes in dev +1. Resort order of ranges in http request, fixing several long-standing problems #374 +1. Implement for `TPie` 3d, text, title drawing including interactivity +1. Implement `TCanvas` support in `build3d` function #373 +1. Implements `TTree` branches filtering via context menu #364 +1. Let define alternative draw function #378 +1. Remove support for deprectaed `TH1K` class +1. Fix - paint frame border mode/size from TCanvas + + +## Changes in 7.10.1 +1. Fix - proper paint axis labels on both sides when pad.fTickx/y = 2 +2. Fix - recover io after bad http response + + +## Changes in 7.10.0 +1. `RNtuple` support, thanks to Kriti Mahajan https://github.com/Krmjn09 +2. Implement `RTreeMapPainter` to display `RNTuple` structure, thanks to Patryk Pilichowski https://github.com/magnustymoteus +3. Implement `build3d` function for building three.js objects for `TH1/2/3`, `TLatex` `TGeo`, `TGraph2D` classes #368 +4. Draw `TAnnotation3D` in real 3D with handling scene rotation +5. Let use hex colors in histogram draw options like "fill_00ff00" or "line_77aa1166" +6. Let configure exact axis ticks position via draw option like "xticks:[-3,-1,1,3]" +7. Support gStyle.fBarOffset for `TGraph` bar drawing +8. Support "fill_" and "line_" draw options for `TGraph` +9. Support dark mode when store images +10. With 'Shift' key pressed whole graph is moved by dragging action +11. Support `Xall` and `Yall` as projections width #340 +12. Implement `unzipJSON()` function for data embeding in jupyter +13. Support reading `TBranch` from very old ROOT files with custom streamers +14. Upgrade three.js r174 -> r180 +15. Upgrade lil-gui.mjs 0.19.2 -> 0.20.0 +16. Upgrade svg2pdf.js 2.3.0 -> 2.6.0 +17. Upgrade jsPDF 2.5.2 -> 3.0.3, exclude gif, bmp, jpeg support +18. Use ES6 modules to implement geoworker, enable node.js usage +19. Remove countGeometryFaces function - use numGeometryFaces instead +20. Remove experimental RHist classes, deprecated in ROOT 6.38 +21. Internal - ws members are private, new methods has to be used +22. Fix - ticks size and labels with kMoreLogLabels axis bit +23. Fix - first color in palette drawing #365 +24. Fix - latex parsing error of `#delta_{0}_suffix` string +25. Fix - reduce plain HTML usage to minimize danger of JS code injection + + +## Changes in 7.9.3 +1. Fix - store large PDF with 3D drawing inside +2. Fix - prevent JS code injection via `TObjString` drawing +3. Fix - reduce use of HTML in hpainter, display and menu components + + +## Changes in 7.9.2 +1. Fix - reading `TLeafC` leafs +2. Fix - support BigInt in object inspector +3. Fix - svg2pdf.js URL bounding box +4. Fix - `TTree::Draw` with strings +5. Fix - toggle vertical/horizontal palette via context menu +6. Fix - detect HTML element size from style attribute +7. Fix - typo in `expandToLevel` method +8. Fix - handle missed expand in hierarchy painter + + +## Changes in 7.9.1 +1. Fix - colz handling on `THStack`, avoid multiple palette drawings +2. Fix - bug in pad.Divide context menu command +3. Fix - drag and drop of histograms on empty sub-pads +4. Fix - add missing colors 100 - 127 +5. Fix - correct online context menu for histogram title +6. Fix - copy all X axis attributes in multi-graph painter +7. Fix - if histogram WebGL drawing fails, fallback to default 2D + + +## Changes in 7.9.0 +1. New draw options: + - 'pol' and 'arr_colz' draw option for `TH2` + - 'col7' uses bar offset and width for `TH2` + - 'cont5' for `TGraph2D` using Delaunay algorithm + - 'chord' drawing of `TH2` implements zooming + - 'box1' for `TH3` with negative bins + - 'same' option for first histogram on pad, draw without creating `TFrame` + - 'rangleNN' for `TGraphPolargram`, also support fAxisAngle member + - 'N' and 'O' for `TGraphPolargram` for angle coordinate systems + - 'arc' for `TPave` and derived classes + - 'allbins' for histograms to display underflow/overflow bins + - Poisson errors for `TH1`/`TH2`, https://root-forum.cern.ch/t/62335/ + - test fSumw2 when detect empty `TH2` bin, sync with https://github.com/root-project/root/pull/17948 +2. New supported classes: + - `TF12` - projection of `TF2` + - `TLink` and `TButton`, used in `TInspectCanvas` +3. New partameters in `TTree::Draw`: + - '>>elist' to request entries matching cut conditions + - 'elist' to specify entries for processing + - 'nmatch' to process exactly the specified number of entries, break processing afterwards + - 'staged' algorithm to first select entries and then process only these entries +4. New settings parameters: + - `settings.FilesTimeout` global timeout for file reading operations + - `settings.FilesRemap` fallback address for http server, used for `root.cern` + - `settings.TreeReadBunchSize` bunch read size for `TTree` processing + - `settings.UserSelect` to set 'user-select: none' style in drawings to exclude text selection +5. Context menus: + - all `TPave`-derived classes + - in 'chord' drawings of `TH2` + - editing histogram and graph title +6. Fixes: + - match histogram title drawing with native ROOT implementation + - float to string conversion when 'g' is specified + - handle `TPave` NDC position also when fInit is not set + - properly handle image sizes in svg2pdf + - drawing `TPaveText` with zero text size + - correct axis range in `TScatter` drawing + - use draw option also for graph drawing in `TTree::Draw` +7. Internals: + - upgrade three.js r168 -> r174 + - use private members and methods + - use `WeakRef` class for cross-referencing of painters + - use negative indexes in arrays and Strings + - remove support of qt5 webengine, only qt6web can be used + + +## Changes in 7.8.2 +1. Fix - hidden canvas in Jupyter Lab, https://root-forum.cern.ch/t/63097/ +2. Fix - repair small bug in `TF3` painting + + +## Changes in 7.8.1 +1. Fix - correctly position title according to gStyle->GetTitleAlign() +2. Fix - tooltips on TGraphPolar +3. Fix - use 'portrait' orientation for PDF pages where width smaller than height +4. Fix - font corruption after PDF generation +5. Fix - support drawing of `RooEllipse` class + + +## Changes in 7.8.0 1. Let use custom time zone for time display, support '&utc' and '&cet' in URL parameters -2. Fix - properly select TF1 range after zooming -3. Fix - TH1 y-range selection -4. Fix - add 'gl' and svg2pdf-related packages to dependencies in package.json +2. Support gStyle.fLegendFillStyle +3. Let change histogram min/max values via context menu +4. Support Z-scale zooming with `TScatter` +5. Implement "haxis" draw option for histogram to draw only axes for hbar +6. Implement "axisg" and "haxisg" to draw axes with grids +7. Support `TH1` marker, text and line drawing superimposed with "haxis" +8. Support `TBox`, `TLatex`, `TLine`, `TMarker` drawing on "frame", support drawing on swapped axes +9. `TProfile` and `TProfile2D` projections https://github.com/root-project/root/issues/15851 +10. Draw total histogram from `TEfficiency` when draw option starts with 'b' +11. Let redraw `TEfficiency`, `THStack` and `TMultiGraph` with different draw options via hist context menu +12. Support 'pads' draw options for `TMultiGraph`, support context menu for it +13. Let drop objects on sub-pads +14. Properly loads ES6 modules for web canvas +15. Improve performance of `TH3`/`RH3` drawing by using `THREE.InstancedMesh` +16. Implement batch mode with '&batch' URL parameter to create SVG/PNG images with default GUI +17. Adjust node.js implementation to produce identical output with normal browser +18. Create necessary infrastructure for testing with 'puppeteer' +19. Support injection of ES6 modules via '&inject=path.mjs' +20. Using importmap for 'jsroot' in all major HTML files and in demos +21. Implement `settings.CutAxisLabels` flag to remove labels which may exceed graphical range +22. Let disable usage of `TAxis` custom labels via context menu +23. Let configure default draw options via context menu, preserved in the local storage +24. Let save canvas as JSON file from context menu, object as JSON from inspector +25. Upgrade three.js r162 -> r168, use r162 only in node.js because of "gl" module +26. Create unified svg2pdf/jspdf ES6 modules, integrate in jsroot builds +27. Let create multi-page PDF document - in `TWebCanvas` batch mode +28. Let add in latex external links via `#url[link]{label}` syntax - including jsPDF support +29. Support `TAttMarker` style with line width bigger than 1 +30. Provide link to ROOT class documentation from context menus +31. Implement axis labels and title rotations on lego plots +32. Internals - upgrade to eslint 9 +33. Internals - do not select pad (aka gPad) for objects drawing, always use assigned pad painter +34. Fix - properly save zoomed ranges in drawingJSON() +35. Fix - properly redraw `TMultiGraph` +36. Fix - show empty bin in `TProfile2D` if it has entries #316 +37. Fix - unzooming on log scale was extending range forever +38. Fix - display empty hist bin if fSumw2 not zero +39. Fix - geometry display on android devices + + +## Changes in 7.7.6 +1. Fix - latex super-script without leading symbol, https://root-forum.cern.ch/t/63114/ +2. Fix - correctly read std::pair<> without dictionary, https://root-forum.cern.ch/t/63114/ +3. Fix - chromium in mobile device emulation mode, https://root-forum.cern.ch/t/63201/ +4. Fix - files remap for root.cern site using fallback URL + + +## Changes in 7.7.5 +1. Fix - can enable exponent only for log10 axis scale +2. Fix - proper set custom font size in latex +3. Fix - do not force style 8 for hist markers +4. Fix - ensure minimal hist title height +5. Fix - disable Bloom effect on Android +6. Fix - handle reordering of fragments in multipart reply #319 +7. Fix - properly show non-zero entries #320 + + +## Changes in 7.7.4 +1. Fix - TGraph Y range selection, do not cross 0 +2. Fix - correctly handle `#font[id]` in latex +3. Fix - store canvas with embed geometry drawing +4. Fix - upgrade rollup and import.meta polyfill + + +## Changes in 7.7.3 +1. Fix - correctly handle in I/O empty std::map +2. Fix - reading of small (<1KB) ROOT files +3. Fix - race condition in zstd initialization #318 +4. Fix - deployment with zstd #317 + + +## Changes in 7.7.2 +1. Fix - hide empty title on the canvas +2. Fix - properly handle zooming in THStack histogram +3. Fix - always use 0 as minimum in THStack drawings +4. Fix - always show all ticks for labeled axis +5. Fix - draw TProfile2D bins content as text, not entries +6. Fix - interactive zooming on log color palette +7. Fix - keyboard handling while input dialog active +8. Fix - legend entry with not configured fill attributes +9. Fix - prevent that color palette exceed graphical range +10. Fix - exponential log axis labels with kMoreLogLabels bit set + + +## Changes in 7.7.1 +1. Fix - properly select TF1 range after zooming +2. Fix - TH1 y-range selection +3. Fix - add 'gl' and svg2pdf-related packages to dependencies in package.json ## Changes in 7.7.0 @@ -1261,11 +1477,11 @@ 8. Fix several problems with markers drawing; implement plus, asterisk, mult symbols. 9. Implement custom layout, which allows to configure user-defined layout for displayed objects 10. Fix errors with scaling of axis labels. -11. Support also Y axis with custom labels like: http://jsroot.gsi.de/dev/?nobrowser&file=../files/atlas.root&item=LEDShapeHeightCorr_Gain0;1&opt=col +11. Support also Y axis with custom labels like: https://jsroot.gsi.de/dev/?nobrowser&file=https://jsroot.gsi.de/files/atlas.root&item=LEDShapeHeightCorr_Gain0;1&opt=col ## Changes in 3.7 -1. Support of X axis with custom labels like: http://jsroot.gsi.de/dev/?nobrowser&json=../files/hist_xlabels.json +1. Support of X axis with custom labels like: https://jsroot.gsi.de/dev/?nobrowser&json=https://jsroot.gsi.de/files/hist_xlabels.json 2. Extend functionality of JSROOT.addDrawFunc() function. One could register type-specific `make_request` and `after_request` functions; `icon`, `prereq`, `script`, `monitor` properties. This let add more custom elements to the generic gui, implemented with JSROOT.HierarchyPainter @@ -1452,7 +1668,7 @@ 13. Provide example fileitem.htm how read and display item from ROOT file. 14. In default index.htm page one could specify 'file', 'layout', 'item' and 'items' parameters like: - + 15. Support direct reading of objects from sub-sub-directories. 16. Introduce demo.htm, which demonstrates online usage of JSROOT. 17. One could use demo.htm directly with THttpServer providing address like: diff --git a/demo/adopt_colors.htm b/demo/adopt_colors.htm index 565aa54f6..ac5e41e03 100644 --- a/demo/adopt_colors.htm +++ b/demo/adopt_colors.htm @@ -4,22 +4,26 @@ Reading object from the ROOT file + + -
    +
    diff --git a/demo/alice_esd.js b/demo/alice_esd.mjs similarity index 59% rename from demo/alice_esd.js rename to demo/alice_esd.mjs index 3a7bb0618..7ca42a814 100644 --- a/demo/alice_esd.js +++ b/demo/alice_esd.mjs @@ -3,18 +3,20 @@ // geometry in https://root.cern/files/alice_ESDgeometry.root // Have to return Promise with list of objects which can be drawn on geometry -async function extract_geo_tracks(tree, opt) { - // as first argument, tree should be provided +import { create, version, TSelector, treeProcess } from 'jsroot'; - console.log('CALL async function extract_geo_tracks'); +console.log(`LOAD alice_esd.mjs with JSROOT ${version}`); - let handle = await import(JSROOT.source_dir + 'modules/tree.mjs'); +/** Name of the function 'extract_tracks' is specified as drop parameter in the URL string + * As first argument of the function TTree instance is provided */ +globalThis.extract_tracks = async function(tree, opt) { + console.log('CALL extract_tracks'); - const selector = new handle.TSelector(); + const selector = new TSelector(); selector.addBranch('ESDfriend.fTracks.fPoints', 'pnts'); - let lst = JSROOT.create('TList'), numentry = 0, numtracks = 0; + let lst = create('TList'), numentry = 0, numtracks = 0; selector.Process = function() { // function called for every entry @@ -27,8 +29,9 @@ async function extract_geo_tracks(tree, opt) { for (let p = 0; p < pnts.length; ++p) { numtracks++; const arr = pnts[p]; - if (!arr.fNPoints) continue; - const track = JSROOT.create('TGeoTrack'); + if (!arr.fNPoints) + continue; + const track = create('TGeoTrack'); track.fNpoints = arr.fNPoints*4; track.fPoints = new Float32Array(track.fNpoints*4); for (let k = 0; k < arr.fNPoints; ++k) { @@ -39,7 +42,8 @@ async function extract_geo_tracks(tree, opt) { track.fLineWidth = 2; track.fLineColor = 3; lst.Add(track); - if (numtracks > 100) return this.Abort(); // do not accumulate too many tracks + if (numtracks > 100) + return this.Abort(); // do not accumulate too many tracks } } @@ -48,11 +52,8 @@ async function extract_geo_tracks(tree, opt) { console.log(`Read done num entries ${numentry} tracks ${numtracks}`); } - await handle.treeProcess(tree, selector); - - console.log('FINISH extract_geo_tracks'); - - return lst; + return treeProcess(tree, selector).then( () => { + console.log('FINISH extract_tracks'); + return lst; + }); } - -console.log(`LOAD alice_esd.js with JSROOT ${JSROOT.version}`); diff --git a/demo/amore.js b/demo/amore.js deleted file mode 100644 index 171a99572..000000000 --- a/demo/amore.js +++ /dev/null @@ -1,6 +0,0 @@ -// special handling for the amore::core::String_t which is redifinition of TString -JSROOT.addUserStreamer('amore::core::String_t', 'TString'); - -// register class and identify, that 'fVal' field should be used for drawing -// one could specify explicit draw function, but it is not required in such simple case -JSROOT.addDrawFunc({ name: 'amore::core::MonitorObjectHisto', icon: 'img_histo1d', draw_field: 'fVal' }); diff --git a/demo/amore.mjs b/demo/amore.mjs new file mode 100644 index 000000000..d24aea00c --- /dev/null +++ b/demo/amore.mjs @@ -0,0 +1,8 @@ +import { addUserStreamer, addDrawFunc } from 'jsroot'; + +// special handling for the amore::core::String_t which is redefinition of TString +addUserStreamer('amore::core::String_t', 'TString'); + +// register class and identify, that 'fVal' field should be used for drawing +// one could specify explicit draw function, but it is not required in such simple case +addDrawFunc({ name: 'amore::core::MonitorObjectHisto', icon: 'img_histo1d', draw_field: 'fVal' }); diff --git a/demo/build3d.htm b/demo/build3d.htm new file mode 100644 index 000000000..595ccfd53 --- /dev/null +++ b/demo/build3d.htm @@ -0,0 +1,157 @@ + + + + Building three.js model for supported classes + + + + + + + + + + + + + diff --git a/demo/context_menu.htm b/demo/context_menu.htm index 1c0218a9e..3c54bec20 100644 --- a/demo/context_menu.htm +++ b/demo/context_menu.htm @@ -4,10 +4,12 @@ Custom context menu example + - -
    +
    loading ...
    @@ -15,33 +17,35 @@ - -
    +
    -
    +
    @@ -11,9 +14,9 @@ @@ -14,25 +17,23 @@ - diff --git a/demo/gaudi.js b/demo/gaudi.mjs similarity index 94% rename from demo/gaudi.js rename to demo/gaudi.mjs index 38194fc34..8f5469456 100644 --- a/demo/gaudi.js +++ b/demo/gaudi.mjs @@ -9,7 +9,9 @@ // JSROOT searches for checksum and rall-back when streamer info not found // This is a case for pool::Token class, therefore checksum should be skipped here -JSROOT.addUserStreamer( +import { addUserStreamer } from 'jsroot'; + +addUserStreamer( 'pool::Token', function(buf, obj) { obj._typename = 'pool::Token'; diff --git a/demo/make_image.htm b/demo/make_image.htm index 90200a9ae..67db4ea1d 100644 --- a/demo/make_image.htm +++ b/demo/make_image.htm @@ -14,7 +14,6 @@ padding: 2px; height: 100%; } - .grid-container > div { background-color: rgba(255, 255, 255, 0.8); text-align: center; @@ -22,8 +21,11 @@ font-size: 16px; } - + +

    Example usage of makeImage function

    @@ -54,7 +56,7 @@

    Example usage of makeImage function

    + -
    +
    + diff --git a/demo/multigraph.htm b/demo/multigraph.htm index f366a16e7..2e96d44db 100644 --- a/demo/multigraph.htm +++ b/demo/multigraph.htm @@ -4,45 +4,52 @@ TMultiGraph creation and update + -
    +
    - diff --git a/demo/multigraph_legend.htm b/demo/multigraph_legend.htm index 041e64423..134313a5a 100644 --- a/demo/multigraph_legend.htm +++ b/demo/multigraph_legend.htm @@ -4,19 +4,22 @@ TMultiGraph with TLegend and custom labels + -
    +
    diff --git a/demo/node/Readme.md b/demo/node/Readme.md index ff11673dc..11bd04bec 100644 --- a/demo/node/Readme.md +++ b/demo/node/Readme.md @@ -29,6 +29,6 @@ JSROOT also implements extensive tree-draw functionality, shown in example: +Possibility to read TTree data, using `TSelector` class shown in example: - node selector.js + node tree_selector.js diff --git a/demo/node/buffer_test.js b/demo/node/buffer_test.js new file mode 100644 index 000000000..60eac3aef --- /dev/null +++ b/demo/node/buffer_test.js @@ -0,0 +1,64 @@ +import { RBufferReader } from 'jsroot/rntuple'; + +// This is a test script +//TEST 1 +{ + console.log('Perform basic RBufferReader test'); + // creating a buffer with one 32-bit unsigned integer + let buffer = new ArrayBuffer(4); + let view = new DataView(buffer); + + view.setUint32(0, 0x12345678, true) + + // read the buffer + let reader = new RBufferReader(buffer); + let value = reader.readU32(); + + if (value === 0x12345678) + console.log('test 1 passed'); + else + console.error('FAILURE: test 1 does not match') +} + +// TEST 2: Read 16-bit and 8-bit integers +{ + const buffer = new ArrayBuffer(3); + const view = new DataView(buffer); + view.setUint16(0, 0xABCD, true); // First 2 bytes + view.setUint8(2, 0xEF); // Last byte + const reader = new RBufferReader(buffer); + const u16 = reader.readU16(); + const u8 = reader.readU8(); + + if (u16 === 0xABCD) + console.log('test - 2 - readU16 passed'); + else + console.error('FAILURE: test - 2 - readU16 does not match') + + if (u8 === 0xEF) + console.log('test- 2 - readU8 passed'); + else + console.error('FAILURE: test - 2 - readU8 does not match') + } + +//Test 3: Read a string +{ + const text = 'HELLO'; + const buffer = new ArrayBuffer(4 + text.length) // 4 bytes for length and 5 for hello + + const view = new DataView(buffer) + + // set the 32 bit length of the string + view.setUint32(0,text.length,true); + //set each char's ASCII code + for (let i = 0; i < text.length; i++) + view.setUint8(4+i,text.charCodeAt(i)); + + const reader = new RBufferReader(buffer); + const result = reader.readString(); + + if (result === text) + console.log('test 3 - readString passed'); + else + console.error('FAILURE: test 3 - readString does not match'); +} diff --git a/demo/node/build3d.js b/demo/node/build3d.js new file mode 100644 index 000000000..f129cdebe --- /dev/null +++ b/demo/node/build3d.js @@ -0,0 +1,65 @@ +import { httpRequest, create, openFile, treeDraw, build3d } from 'jsroot'; + +async function test3d(obj, opt, title, json_reflength = -1) { + if (!obj) { + console.error(`No object provided for ${title}`); + return null; + } + + let obj3d = await build3d(obj, opt); + + if (!obj3d) { + console.error(`Fail to create three.js model for ${obj._typename}`); + return null; + } + + if (json_reflength <= 0) + console.log(`${title} Ok`); + else { + let json = JSON.stringify(obj3d.toJSON()); + if (Math.abs(json.length - json_reflength) / json_reflength > 0.01) + console.error(`${title} FAILURE json ${json.length} too much differ from refernce ${json_reflength}`); + else + console.log(`${title} OK json ${json.length}`); + } + + return obj3d; +} + +let server = 'https://root.cern/js/files/', + filename = server + 'hsimple.root', + filename2 = server + 'graph2d.root', + filename3 = server + 'geom/simple_alice.json.gz'; + +let file = await openFile(filename); +let hist2 = await file.readObject('hpxpy'); + +await test3d(hist2, 'lego2', 'TH2 lego plot', 6916869); + +let tuple = await file.readObject('ntuple'); +let hist3 = await treeDraw(tuple, 'px:py:pz;hbins:15'); + +await test3d(hist3, 'box3', 'TH3 box plot', 4223457); + +let hist1 = await file.readObject('hpx'); + +await test3d(hist1, 'lego2', 'TH1 lego plot', 3332953); + +let geom = await httpRequest(filename3, 'object'); +await test3d(geom, '', 'Geometry build', 1811230); + +let file2 = await openFile(filename2); +let gr2 = await file2.readObject('Graph2D'); + +await test3d(gr2, 'p', 'TGraph2D drawing with p'); + +let latex = create('TLatex'); +latex.fTitle = 'C(x) = d #sqrt{#frac{2}{#lambdaD}} #int^{x}_{0}cos(#frac{#pi}{2}t^{2})dt'; +latex.fTextAlign = 22; +latex.fTextColor = 3; +latex.fTextSize = 10; + +await test3d(latex, '', 'TLatex integral and sqrt drawing', 1746096); + +latex.fTitle = 'F(t) = #sum_{i=-#infty}^{#infty}A(i)cos#[]{#frac{i}{t+i}}'; +await test3d(latex, '', 'TLatex infinity drawing', 975849); diff --git a/demo/node/file_proxy.js b/demo/node/file_proxy.js index 0db47826e..7721dc916 100644 --- a/demo/node/file_proxy.js +++ b/demo/node/file_proxy.js @@ -4,12 +4,14 @@ import { version, FileProxy, openFile, makeSVG, treeDraw } from 'jsroot'; -import { writeFileSync, openSync, readSync, statSync } from 'fs'; +import { writeFileSync, readFileSync, openSync, readSync, statSync } from 'fs'; import { open, stat } from 'node:fs/promises'; console.log(`JSROOT version ${version}`); +/** Class using sync file I/O */ + class FileProxySync extends FileProxy { constructor(filename) { @@ -20,7 +22,8 @@ class FileProxySync extends FileProxy { openFile() { this.fd = openSync(this.filename); - if (!this.fd) return Promise.resolve(false); + if (!this.fd) + return Promise.resolve(false); let stats = statSync(this.filename); this.size = stats.size; return Promise.resolve(true); @@ -30,28 +33,22 @@ class FileProxySync extends FileProxy { getFileSize() { return this.size; } - readBuffer(pos, sz) - { + readBuffer(pos, sz) { if (!this.fd) return Promise.resolve(null); - let buffer = new ArrayBuffer(sz); - - let view = new DataView(buffer, 0, sz); - - let bytesRead = readSync(this.fd, view, 0, sz, pos); - - // console.log(`Read at ${pos} size ${bytesRead}`); + const buffer = new ArrayBuffer(sz), + view = new DataView(buffer, 0, sz), + bytesRead = readSync(this.fd, view, 0, sz, pos); - if (bytesRead == sz) - return Promise.resolve(view); - - return Promise.resolve(null); + return Promise.resolve(bytesRead == sz ? view : null); } } // class FileProxySync +/** Class using async node.js file I/O */ + class FileProxyPromise extends FileProxy { constructor(filename) { @@ -60,10 +57,10 @@ class FileProxyPromise extends FileProxy { this.size = 0; } - openFile() { - + async openFile() { return open(this.filename).then(fd => { - if (!fd) return false; + if (!fd) + return false; this.fd = fd; return stat(this.filename); @@ -78,18 +75,14 @@ class FileProxyPromise extends FileProxy { getFileSize() { return this.size; } - readBuffer(pos, sz) - { + async readBuffer(pos, sz) { if (!this.fd) return Promise.resolve(null); - let buffer = new ArrayBuffer(sz); - - let view = new DataView(buffer, 0, sz); + const buffer = new ArrayBuffer(sz), + view = new DataView(buffer, 0, sz); return this.fd.read(view, 0, sz, pos).then(res => { - // console.log(`Read ${pos} size ${res.bytesRead}`); - // console.trace(); return res.bytesRead > 0 ? res.buffer : null; }); @@ -97,45 +90,80 @@ class FileProxyPromise extends FileProxy { } // class FileProxyPromise -let proxy = null, fname = '../../../files/hsimple.root'; + +/** Class supporting multi-range requests */ +class FileProxyMultiple extends FileProxyPromise { + + /** example of reading several segments at once, always return array */ + async readBuffers(places) { + if (!this.fd) + return Promise.resolve([]); + + const promises = []; + for (let k = 0; k < places.length; k += 2) { + const pos = places[k], sz = places[k + 1], + buffer = new ArrayBuffer(sz), + view = new DataView(buffer, 0, sz); + + promises.push(this.fd.read(view, 0, sz, pos)); + } + + return Promise.all(promises).then(arr => { + const res = []; + for (let k = 0; k < arr.length; ++k) + res.push(arr[k].bytesRead > 0 ? arr[k].buffer : null); + return res; + }); + } + +} // class FileProxyMultiple + +let filearg = null, fname = './hsimple.root'; if (process.argv && process.argv[3] && typeof process.argv[3] == 'string') fname = process.argv[3]; if (fname.indexOf('http') == 0) { console.log('Using normal file API'); - proxy = fname; + filearg = fname; +} else if (process.argv && process.argv[2] == 'buffer') { + const nodeBuffer = readFileSync(fname); + filearg = new Uint8Array(nodeBuffer).buffer; + console.log('Using BufferArray', filearg.byteLength); } else if (process.argv && process.argv[2] == 'sync') { console.log('Using FileProxySync'); - proxy = new FileProxySync(fname); + filearg = new FileProxySync(fname); +} else if (process.argv && process.argv[2] == 'multi') { + console.log('Using FileProxyMultiple'); + filearg = new FileProxyMultiple(fname); } else { console.log('Using FileProxyPromise'); - proxy = new FileProxyPromise(fname); + filearg = new FileProxyPromise(fname); } if (process.argv && process.argv[2] == 'sync') { console.log('Using sync API'); - let file = await openFile(proxy); - if (!file) { + const file = await openFile(filearg); + if (!file) console.error('Fail to open file'); - } // now read ntuple, perform Draw operation, create SVG file and sve to the disk - let ntuple = await file.readObject('ntuple'); - let hist = await treeDraw(ntuple, 'px:py::pz>5'); - let svg = await makeSVG({ object: hist, width: 1200, height: 800 }); + const ntuple = await file.readObject('ntuple'); + const hist = await treeDraw(ntuple, 'px:py::pz>5'); + const svg = await makeSVG({ object: hist, width: 1200, height: 800 }); writeFileSync('draw_proxy.svg', svg); console.log(`Create draw_proxy.svg size ${svg.length}`); } else { console.log('Using promise API'); - openFile(proxy).then(file => file.readObject('ntuple')) - .then(ntuple => treeDraw(ntuple, 'px:py::pz>5')) - .then(hist => makeSVG({ object: hist, width: 1200, height: 800 })) - .then(svg => { - writeFileSync('draw_proxy.svg', svg); - console.log(`Create draw_proxy.svg size ${svg.length}`); - }); + openFile(filearg) + .then(file => file.readObject('ntuple')) + .then(ntuple => treeDraw(ntuple, 'px:py::pz>5')) + .then(hist => makeSVG({ object: hist, width: 1200, height: 800 })) + .then(svg => { + writeFileSync('draw_proxy.svg', svg); + console.log(`Create draw_proxy.svg size ${svg.length}`); + }); } diff --git a/demo/node/geomsvg.js b/demo/node/geomsvg.js index d0c2fd243..23daa6ea8 100644 --- a/demo/node/geomsvg.js +++ b/demo/node/geomsvg.js @@ -1,7 +1,6 @@ -import { httpRequest, version } from 'jsroot'; -import { makeSVG } from 'jsroot/draw'; - import { writeFileSync } from 'fs'; +import { version, httpRequest, makeSVG } from 'jsroot'; + console.log(`JSROOT version ${version}`); @@ -9,9 +8,9 @@ console.log(`JSROOT version ${version}`); // r3d_img is normal webgl, create svg:image (default) // r3d_svg uses SVGRenderer, can produce large output -let obj = await httpRequest('https://root.cern/js/files/geom/simple_alice.json.gz', 'object'); +const obj = await httpRequest('https://root.cern/js/files/geom/simple_alice.json.gz', 'object'); -let svg = await makeSVG({ object: obj, width: 1200, height: 800 /*, option: "r3d_svg" */ }); +const svg = await makeSVG({ object: obj, width: 1200, height: 800 /* , option: "r3d_svg" */ }); writeFileSync('alice_geom.svg', svg); console.log(`Create alice_geom.svg size ${svg.length}`); diff --git a/demo/node/hpainter.js b/demo/node/hpainter.js new file mode 100644 index 000000000..bbcf3b97e --- /dev/null +++ b/demo/node/hpainter.js @@ -0,0 +1,49 @@ +// demo how HierarchyPainter can be used without direct display +// in batch display one just able to create images + + +import { version, HierarchyPainter, draw, addDrawFunc } from 'jsroot'; +// import { writeFileSync } from 'fs'; + +console.log(`JSROOT version ${version}`); + + +const hp = new HierarchyPainter('hpainter'); + +// configure batch display to properly handle DOM in the node.js +hp.setDisplay('batch'); + +// catch draw function calls +addDrawFunc({ + name: '*', + func: (dom, obj, opt) => { + console.log(`Actual draw of ${obj._typename}`); + // if function return true no normal drawing will be performed + // do not try to call `draw` function from here !!! + // return true; + } +}); + +await hp.openRootFile('https://root.cern/js/files/hsimple.root'); + +// display of TH2 histogram +console.log('Invoke histogram drawing'); +await hp.display('hpxpy'); + +await hp.expandItem('ntuple'); + +// invoking TTree::Draw +console.log('Invoke TBranch drawing'); +await hp.display('ntuple/pz'); + +// should be BatchDisplay +const disp = hp.getDisplay(); + +for (let id = 0; id < disp.numFrames(); ++id) { + const svg = await disp.makeSVG(id); + + console.log(`Frame ${id} create svg size ${svg.length}`); + + // one can save svg plain file + // writeFileSync(`frame${id}.svg`, svg); +} diff --git a/demo/node/make_image.js b/demo/node/make_image.js index 6a2bee437..b94fa3319 100644 --- a/demo/node/make_image.js +++ b/demo/node/make_image.js @@ -1,35 +1,38 @@ // Example for makeImage // Create svg, pdf, png, jpeg images for different kinds of data +import { writeFileSync } from 'fs'; import { version, openFile, makeSVG, makeImage } from 'jsroot'; -import { writeFileSync } from 'fs'; const width = 1200, height = 800; console.log(`JSROOT version ${version}`); +let outdir = ''; +if (process?.argv && process.argv[2]) + outdir = process.argv[2]; function processResults(name, title, svg, pdf, png, jpeg) { console.log(`${title} ${name}.svg ${svg.length} ${name}.pdf ${pdf.byteLength} ${name}.png ${png.byteLength} ${name}.jpeg ${jpeg.byteLength}`); if (svg.length) - writeFileSync(`${name}.svg`, svg); + writeFileSync(`${outdir+name}.svg`, svg); else console.error(`Fail to create SVG for ${title}`); if (pdf.byteLength) - writeFileSync(`${name}.pdf`, pdf); + writeFileSync(`${outdir+name}.pdf`, pdf); else console.error(`Fail to create PDF for ${title}`); if (png.byteLength) - writeFileSync(`${name}.png`, png); + writeFileSync(`${outdir+name}.png`, png); else console.error(`Fail to create PNG for ${title}`); if (jpeg.byteLength) - writeFileSync(`${name}.jpeg`, jpeg); + writeFileSync(`${outdir+name}.jpeg`, jpeg); else console.error(`Fail to create JPEG for ${title}`); } diff --git a/demo/node/makesvg.js b/demo/node/makesvg.js index fec562609..ac2b88406 100644 --- a/demo/node/makesvg.js +++ b/demo/node/makesvg.js @@ -1,6 +1,6 @@ +import { writeFileSync } from 'fs'; import { version, openFile, makeSVG } from 'jsroot'; -import { writeFileSync } from 'fs'; console.log(`JSROOT version ${version}`); diff --git a/demo/node/package.json b/demo/node/package.json index 7116b1af5..a31d46bad 100644 --- a/demo/node/package.json +++ b/demo/node/package.json @@ -1,6 +1,6 @@ { "name": "jsroot-demos", - "version": "7.7.1", + "version": "7.10.99", "engines": { "node": ">=0.18" }, diff --git a/demo/node/rntuple_selector.js b/demo/node/rntuple_selector.js new file mode 100644 index 000000000..38f358f16 --- /dev/null +++ b/demo/node/rntuple_selector.js @@ -0,0 +1,58 @@ +import { rntupleProcess } from '../../modules/rntuple.mjs'; +import { openFile } from '../../modules/io.mjs'; +import { TSelector } from '../../modules/tree.mjs'; + +const selector = new TSelector(); +selector.sum = 0; +selector.count = 0; +selector.addBranch('Category'); +selector.addBranch('Flag'); +selector.addBranch('Age'); +selector.addBranch('Service'); +selector.addBranch('Children'); +selector.addBranch('Grade'); +selector.addBranch('Step'); +selector.addBranch('Hrweek'); +selector.addBranch('Cost'); +selector.addBranch('Division'); +selector.addBranch('Nation'); + +selector.Begin = function() { + console.log('Begin processing'); +}; + +selector.Process = function() { + console.log('Entry : ', this.tgtobj); + this.count++; +}; + + +selector.Terminate = function() { + if (this.count === 0) + console.error('No entries processed'); +}; + +if (typeof window === 'undefined') { + openFile('https://jsroot.gsi.de/files/tmp/ntpl001_staff.root') + .then(file => file.readObject('Staff')) + .then(rntuple => { + if (!rntuple) + throw new Error('myNtuple not found'); + return rntupleProcess(rntuple, selector); + }) + .then(() => console.log('RNTuple::Process finished')) + .catch(err => console.error(err)); +} + + +// if (typeof window === 'undefined') { +// openFile('./simple.root') +// .then(file => file.readObject('myNtuple')) +// .then(rntuple => { +// if (!rntuple) +// throw new Error('myNtuple not found'); +// return rntupleProcess(rntuple, selector); +// }) +// .then(() => console.log('RNTuple::Process finished')) +// .catch(err => console.error(err)); +// } diff --git a/demo/node/rntuple_test.js b/demo/node/rntuple_test.js new file mode 100644 index 000000000..25c766f85 --- /dev/null +++ b/demo/node/rntuple_test.js @@ -0,0 +1,126 @@ +import { version, openFile, TSelector } from 'jsroot'; + +import { readHeaderFooter, readEntry, rntupleProcess } from 'jsroot/rntuple'; + +console.log(`JSROOT version ${version}`); + + +let filename = './rntuple_test.root'; +if (process?.argv && process.argv[2]) + filename = process.argv[2]; + +const file = await openFile(filename), + rntuple = await file.readObject('Data'); + +await readHeaderFooter(rntuple); + +console.log('Performing Validations and debugging info'); + +if (rntuple.builder?.name !== 'Data') + console.error('FAILURE: name differs from expected'); +else + console.log('OK: name is', rntuple.builder?.name); + + +if (rntuple.builder?.description !== '') + console.error('FAILURE: description should be the empty string'); +else + console.log('OK: description is empty string'); + +if (rntuple.builder?.xxhash3 === undefined || rntuple.builder.xxhash3 === null) + console.warn('WARNING: xxhash3 is missing'); +else + console.log('OK: xxhash3 is', '0x' + rntuple.builder.xxhash3.toString(16).padStart(16, '0')); + +// Fields Check + +if (!rntuple.builder?.fieldDescriptors?.length) + console.error('FAILURE: No fields deserialized'); +else { + console.log(`OK: ${rntuple.builder.fieldDescriptors.length} field(s) deserialized`); + for (let i = 0; i < rntuple.builder.fieldDescriptors.length; ++i) { + const field = rntuple.builder.fieldDescriptors[i]; + if (!field.fieldName || !field.typeName) + console.error(`FAILURE: Field ${i} is missing name or type`); + else + console.log(`OK: Field ${i}: ${field.fieldName} (${field.typeName})`); + if (i === 0) { + if (field.fieldName !== 'IntField' || field.typeName !== 'std::int32_t') + console.error(`FAILURE: First field should be 'IntField (std::int32_t)' but got '${field.fieldName} (${field.typeName})'`); + } else if (i === rntuple.builder.fieldDescriptors.length - 1){ + if (field.fieldName !== 'StringField' || field.typeName !== 'std::string') + console.error(`FAILURE: Last field should be 'StringField (std::string)' but got '${field.fieldName} (${field.typeName})'`); + } + } +} + +// Column Check + +if (!rntuple.builder?.columnDescriptors?.length) + console.error('FAILURE: No columns deserialized'); +else { + console.log(`OK: ${rntuple.builder.columnDescriptors.length} column(s) deserialized`); + for (let i = 0; i < rntuple.builder.columnDescriptors.length; ++i) { + const column = rntuple.builder.columnDescriptors[i]; + if (column.fieldId === undefined || column.fieldId === null) + console.error(`FAILURE: Column ${i} is missing fieldId`); + else + console.log(`OK: Column ${i} fieldId: ${column.fieldId} `); + if (i === 0) { + if (column.fieldId !== 0) + console.error('FAILURE: First column should be for fieldId 0 (IntField)'); + } else if (i === rntuple.builder.columnDescriptors.length - 1){ + if (column.fieldId !== 3) + console.error('FAILURE: Last column should be for fieldId 3 (StringField)'); + } + } +} + +// Setup selector to process all fields (so cluster gets loaded) +const selector = new TSelector(), + fields = ['IntField', 'FloatField', 'DoubleField', 'StringField']; +for (const f of fields) + selector.addBranch(f); + +selector.Begin = () => { + console.log('\nBegin processing to load cluster data...'); +}; +selector.Process = function() {}; +selector.Terminate = () => { + console.log('Finished dummy processing'); +}; + +// Run rntupleProcess to ensure cluster is loaded +await rntupleProcess(rntuple, selector); + +// Now validate entry data +const EPSILON = 1e-10; + +for (let entryIndex = 0; entryIndex < 10; ++entryIndex) { + console.log(`\nChecking entry ${entryIndex}:`); + + const expected = { + IntField: entryIndex, + FloatField: entryIndex * entryIndex, + DoubleField: entryIndex * 0.5, + StringField: `entry_${entryIndex}` + }; + + for (const field of fields) { + try { + const value = readEntry(rntuple, field, entryIndex), + expectedValue = expected[field], + + pass = typeof value === 'number' + ? Math.abs(value - expectedValue) < EPSILON + : value === expectedValue; + + if (!pass) + console.error(`FAILURE: ${field} at entry ${entryIndex} expected ${expectedValue}, got ${value}`); + else + console.log(`OK: ${field} at entry ${entryIndex} = ${value}`); + } catch (err) { + console.error(`ERROR: Failed to read ${field} at entry ${entryIndex}: ${err.message}`); + } + } +} \ No newline at end of file diff --git a/demo/node/rntuple_test.root b/demo/node/rntuple_test.root new file mode 100644 index 000000000..28830eb60 Binary files /dev/null and b/demo/node/rntuple_test.root differ diff --git a/demo/node/simple.root b/demo/node/simple.root new file mode 100644 index 000000000..3f90a9da1 Binary files /dev/null and b/demo/node/simple.root differ diff --git a/demo/node/tree_draw.js b/demo/node/tree_draw.js index 5c867a66e..a7a782fbb 100644 --- a/demo/node/tree_draw.js +++ b/demo/node/tree_draw.js @@ -10,11 +10,40 @@ let arg = 'https://root.cern/js/files/hsimple.root'; if (process.argv && (process.argv[2] == 'buf')) arg = await httpRequest(arg, 'buf'); -let file = await openFile(arg); - -// now read ntuple, perform Draw operation, create SVG file and sve to the disk -let ntuple = await file.readObject('ntuple'); -let hist = await treeDraw(ntuple, 'px:py::pz>5'); -let svg = await makeSVG({ object: hist, width: 1200, height: 800 }); -writeFileSync('tree_draw.svg', svg); -console.log(`Create tree_draw.svg size ${svg.length}`); +const file = await openFile(arg); + +// now read ntuple, perform Draw operation, create SVG file and save to the disk +const ntuple = await file.readObject('ntuple'); +const hist = await treeDraw(ntuple, 'px:py::pz>5'); +hist.fTitle = 'Example of TTree::Draw'; +const svg = await makeSVG({ object: hist, width: 1200, height: 800 }); +writeFileSync('tree_draw1.svg', svg); +console.log(`Create tree_draw1.svg size ${svg.length}`); + +// extract entries list which corresponds to cut expression +const elist = await treeDraw(ntuple, '::pz>5>>elist'); +// apply entries list for draw expression +const hist2 = await treeDraw(ntuple, { expr: 'px:py', elist }); +hist2.fTitle = 'Example of TTree::Draw'; +const svg2 = await makeSVG({ object: hist2, width: 1200, height: 800 }); +writeFileSync('tree_draw2.svg', svg2); +console.log(`Create tree_draw2.svg size ${svg2.length}`); + +// check if produced SVG files are the same +if (svg !== svg2) + console.error('FAILURE: svg and svg2 do not match'); + + +// check that staged drawing also produce same results +const hist3 = await treeDraw(ntuple, 'px:py::pz>5;staged'); +hist3.fTitle = 'Example of TTree::Draw'; +const svg3 = await makeSVG({ object: hist3, width: 1200, height: 800 }); +writeFileSync('tree_draw3.svg', svg3); +console.log(`Create tree_draw3.svg size ${svg3.length}`); + +// check if produced SVG files are the same +if (svg !== svg3) + console.error('FAILURE: svg and svg3 do not match'); + +if ((svg === svg2) && (svg2 === svg3)) + console.log('OK: all svg files match!') diff --git a/demo/node/selector.js b/demo/node/tree_selector.js similarity index 100% rename from demo/node/selector.js rename to demo/node/tree_selector.js diff --git a/demo/node/tree_staged.js b/demo/node/tree_staged.js new file mode 100644 index 000000000..41c9d4039 --- /dev/null +++ b/demo/node/tree_staged.js @@ -0,0 +1,84 @@ +import { version, openFile, treeDraw, treeProcess, TSelector } from 'jsroot'; + +console.log(`JSROOT version ${version}`); + +// Macro demonstrates three different methods to produce entries list which match +// specific cut condition. It is: +// 1. Use of string draw expression for treeDraw function +// 2. Use object as option for treeDraw operation +// 3. Direct use of TSelector to process tree and select entries + +function compareArrays(arr1, arr2) { + if (arr1.length !== arr2.length) + return false; + for (let i = 0; i < arr1.length; ++i) { + if (arr1[i] !== arr2[i]) + return false; + } + return true; +} + +// open file +const file = await openFile('https://root.cern/js/files/hsimple.root'); + +// read ntuple +const ntuple = await file.readObject('ntuple'); + +// use treeDraw to extract array of entries which match condition 'pz>5' +const entries1 = await treeDraw(ntuple, '::pz>5>>elist'); +console.log('entries1', entries1.length); + +// same can be achieved when specify draw expression as args +const entries2 = await treeDraw(ntuple, { cut: 'pz>5', dump_entries: true }); +console.log('entries2', entries2.length); + +const selector = new TSelector, entries3 = []; +selector.addBranch('pz'); +selector.Process = function(entry) { + if (this.tgtobj.pz > 5) + entries3.push(entry); +} +await treeProcess(ntuple, selector); +console.log('entries3', entries3.length); + +if (!compareArrays(entries1, entries2)) + console.error('Entries 1 and 2 differs'); + +if (!compareArrays(entries1, entries3)) + console.error('Entries 1 and 3 differs'); + +if (!compareArrays(entries2, entries3)) + console.error('Entries 2 and 3 differs'); + + +// And in the second stage extract values of 'px' branch only for +// selected entries. Again three different approaches are used: +// 1. Use string expression for treeDraw +// 2. Use object as argument for treeDraw +// 3. Use selector with treeProcess and elist options + +// now use these selected entries to dump values of px branch +const pxarr1 = await treeDraw(ntuple, `px;dump;elist:[${entries1}]`); +console.log('pxarr1', pxarr1.length); + +// same expression, but provided as object +const pxarr2 = await treeDraw(ntuple, { expr: 'px', dump: true, elist: entries2}); +console.log('pxarr2', pxarr2.length); + +// and use entries list as argument for treeProcess +const selector2 = new TSelector, pxarr3 = []; +selector2.addBranch('px'); +selector2.Process = function() { + pxarr3.push(this.tgtobj.px); +} +await treeProcess(ntuple, selector2, { elist: entries3 }); +console.log('pxarr3', pxarr3.length); + +if (!compareArrays(pxarr1, pxarr2)) + console.error('px arrays 1 and 2 differs'); + +if (!compareArrays(pxarr1, pxarr3)) + console.error('px arrays 1 and 3 differs'); + +if (!compareArrays(pxarr2, pxarr3)) + console.error('px arrays 2 and 3 differs'); diff --git a/demo/preload.htm b/demo/preload.htm index fcaaf550c..9f1438d1b 100644 --- a/demo/preload.htm +++ b/demo/preload.htm @@ -9,7 +9,7 @@

    Example of loading build/jsroot.js bundle with all common functions

    -
    +
    - - - - -

    Example of using old JSRoot.core.js functionality

    - -
    - - - - - diff --git a/demo/preload_v6_require.htm b/demo/preload_v6_require.htm deleted file mode 100644 index afed95b06..000000000 --- a/demo/preload_v6_require.htm +++ /dev/null @@ -1,375 +0,0 @@ - - - - - Load all scripts and data in advance - - - - - -

    Example of loading old JSRoot.core.js functionality with require.js

    - -
    - - - - - diff --git a/demo/read_file.htm b/demo/read_file.htm index bd5fe674b..3e5a1ca4f 100644 --- a/demo/read_file.htm +++ b/demo/read_file.htm @@ -4,14 +4,17 @@ Reading object from the ROOT file + -
    +
    diff --git a/demo/read_geometry_tracks.htm b/demo/read_geometry_tracks.htm index 9d572c548..1d5cee480 100644 --- a/demo/read_geometry_tracks.htm +++ b/demo/read_geometry_tracks.htm @@ -4,29 +4,30 @@ Reading geometry and tracks from the ROOT file + -
    +
    - diff --git a/demo/read_json.htm b/demo/read_json.htm index 6b0e97efa..734c26f79 100644 --- a/demo/read_json.htm +++ b/demo/read_json.htm @@ -4,13 +4,16 @@ Reading object from the JSON file + -
    +
    diff --git a/demo/read_tree.htm b/demo/read_tree.htm index 2570ede7f..17a12c00a 100644 --- a/demo/read_tree.htm +++ b/demo/read_tree.htm @@ -4,6 +4,9 @@ Reading object from the ROOT file + @@ -14,8 +17,8 @@ diff --git a/demo/store_json.htm b/demo/store_json.htm index ad9cf3d81..374e12d2c 100644 --- a/demo/store_json.htm +++ b/demo/store_json.htm @@ -4,22 +4,25 @@ Store canvas in JSON to replicate it + -
    +

    Example show replication of TCanvas with all primitives, using drawingJSON() function

    -
    +
    - diff --git a/demo/testing.htm b/demo/testing.htm index fd69ec50f..b8976472a 100644 --- a/demo/testing.htm +++ b/demo/testing.htm @@ -4,16 +4,19 @@ Run interactivity testings + -
    +
    diff --git a/demo/tgeo_build.htm b/demo/tgeo_build.htm index c61cbebb8..edb6d7e20 100644 --- a/demo/tgeo_build.htm +++ b/demo/tgeo_build.htm @@ -14,6 +14,9 @@ overflow: hidden; } + @@ -21,12 +24,12 @@ + + + + + + + + + diff --git a/demo/th2.htm b/demo/th2.htm index fbbb75135..a598b542c 100644 --- a/demo/th2.htm +++ b/demo/th2.htm @@ -4,13 +4,17 @@ Create, draw and update TH2 object + + -
    +
    -
    +
    - - diff --git a/demo/thstack.htm b/demo/thstack.htm index 857135c0b..999263f93 100644 --- a/demo/thstack.htm +++ b/demo/thstack.htm @@ -4,33 +4,36 @@ Create and update THStack object + +

    Text Before

    -
    +

    Text After

    - diff --git a/demo/tooltip.htm b/demo/tooltip.htm index eed1a2b8c..be004c795 100644 --- a/demo/tooltip.htm +++ b/demo/tooltip.htm @@ -4,6 +4,9 @@ Use of tooltip callback in JSROOT + @@ -16,20 +19,20 @@ All -
    +
    -
    +
    - diff --git a/demo/tooltip_canvas.htm b/demo/tooltip_canvas.htm index 7b0a15530..f3d75dc6e 100644 --- a/demo/tooltip_canvas.htm +++ b/demo/tooltip_canvas.htm @@ -4,34 +4,32 @@ Use of tooltip callback in JSROOT + -
    Place for info
    -
    Tooltip Click Doubleclk - All + All + 3D mode
    - -
    - -
    - +
    +
    - diff --git a/demo/tooltip_lines.htm b/demo/tooltip_lines.htm new file mode 100644 index 000000000..294ad9062 --- /dev/null +++ b/demo/tooltip_lines.htm @@ -0,0 +1,67 @@ + + + + + Replace tooltip lines + + + + + +
    +
    +
    +
    + + + + diff --git a/demo/update_draw.htm b/demo/update_draw.htm index 40730b952..32314f5fe 100644 --- a/demo/update_draw.htm +++ b/demo/update_draw.htm @@ -4,7 +4,6 @@ Demonstrator of online usage of JSROOT - + + @@ -22,31 +24,34 @@ - diff --git a/demo/without_id.htm b/demo/without_id.htm index 0d418b677..3bda4f231 100644 --- a/demo/without_id.htm +++ b/demo/without_id.htm @@ -4,16 +4,24 @@ Example how element pointer can be used + -
    +
    -
    -
    +
    +
    - diff --git a/docs/HttpServer.md b/docs/HttpServer.md index 6eabac6ec..17f93b61d 100644 --- a/docs/HttpServer.md +++ b/docs/HttpServer.md @@ -8,7 +8,7 @@ The idea of THttpServer is to provide remote http access to running ROOT applica ## Starting the HTTP server -To start the http server, at any time, create an instance of the [THttpServer](https://root.cern/root/html/THttpServer.html) class like: +To start the http server, at any time, create an instance of the [THttpServer](https://root.cern/doc/master/classTHttpServer.html) class like: ```cpp auto serv = new THttpServer("http:8080"); @@ -582,7 +582,7 @@ import { httpRequest, draw } from './jsrootsys/modules/core.mjs'; let res = await httpRequest("your_server/multi.json?number=3", "multi", "Files/job1.root/hpx/root.json\nFiles/job1.root/hpxpy/root.json\nFiles/job1.root/hprof/root.json\n"); for (let n = 0; n < res.length; ++n) { - console.log('Requested element of type', ${res[n]._typename}); + console.log('Requested element of type', res[n]._typename); // draw('drawid', res[n], 'hist'); } ``` @@ -592,7 +592,7 @@ Here argument "multi" identifies, that server response should be parsed with `pa ## Using unix sockets -Starting from ROOT version 6.28, one can start server with unix socket. Just do: +Starting from ROOT version 6.28, one can start server bind to the unix socket. Just do: Just call: ```cpp @@ -679,3 +679,26 @@ serv->Register(handler); After that web socket connection can be established with the address `ws://host_name:8080/name1/root.websocket` Example client code can be found in `$ROOTSYS/tutorials/http/ws.htm` file. Custom HTML page for websocket handler is specified with `TUserHandler::GetDefaultPageContent()` method returning `"file:ws.htm"`. + + +## Processing of custom http requests + +To process custom http requests, one can derive from THttpServer class and +reimplement `THttpServer::MissedRequest()` method. Like: + +```cpp +class TCustomHttpServer : public THttpServer { + + TCustomHttpServer(const char *engine) : THttpServer(engine) {} + + void MissedRequest(THttpCallArg *arg) override + { + if (!strcmp(arg->GetPathName(), "custom_path") && + !strcmp(arg->GetFileName(), "empty_object.json")) { + arg->SetJsonContent("{}"); + } else { + THttpServer::MissedRequest(arg); + } + } +}; +``` \ No newline at end of file diff --git a/docs/JSROOT.md b/docs/JSROOT.md index 9fa08066c..e1b784487 100644 --- a/docs/JSROOT.md +++ b/docs/JSROOT.md @@ -17,6 +17,13 @@ When required, there are following alternatives to install JSROOT on other web s - use [npm](https://npmjs.com/package/jsroot) package manager and invoke `npm install jsroot` - clone master branch from [repository](https://github.com/root-project/jsroot/) +When Apache server will be used one need to add following entry to `.htaccess` file: +``` + + ForceType text/javascript + +``` + ## Drawing objects in JSROOT @@ -45,11 +52,13 @@ To automate files loading and objects drawing, one can provide number of URL par - palette - id of default color palette, 51..121 - new ROOT6 palette (default 57) - interactive - enable/disable interactive functions 0 - disable all, 1 - enable all - noselect - hide file-selection part in the browser (only when file name is specified) +- info - information text displayed on the top of hierarchy browser - mathjax - use MathJax for latex output - latex - 'off', 'symbols', 'normal', 'mathjax', 'alwaysmath' control of TLatex processor - style - name of TStyle object to define global JSROOT style - toolbar - show canvas tool buttons 'off', 'on' and 'popup', 'left' or 'right' for position, 'vert' for vertical -- divsize - fixed size in pixels for main div element like &dvisize=700x400 +- divsize - fixed size in pixels for main div element like &dvisize=1500x800 +- canvsize - default canvas size in pixels like &canvsize=1200x800 - optstat - settings for stat box, default 1111 (see TStyle::SetOptStat) - optfit - fit parameters settings for stat box, default 0 (see TStyle::SetOptFit) - statfmt - formatting for float values in stat box, default 6.4g (see TStyle::SetStatFormat) @@ -67,9 +76,9 @@ To automate files loading and objects drawing, one can provide number of URL par For instance: -- -- -- +- +- +- Following layouts are supported: @@ -116,7 +125,11 @@ List of supported classes and draw options: [e4](https://root.cern/js/latest/examples.htm#th1_e4), [lego](https://root.cern/js/latest/examples.htm#th1_lego), [text](https://root.cern/js/latest/examples.htm#th1_text), -[X+Y+](https://root.cern/js/latest/examples.htm#th1_x+y+) +[X+Y+](https://root.cern/js/latest/examples.htm#th1_x+y+), +[tickxy](https://root.cern/js/latest/examples.htm#th1_tickxy), +[xticks](https://root.cern/js/latest/examples.htm#th1_xticks), +[logy](https://root.cern/js/latest/examples.htm#th1_logy), +[mly](https://root.cern/js/latest/examples.htm#th1_mly) - TH2 : [scat](https://root.cern/js/latest/examples.htm#th2), [col](https://root.cern/js/latest/examples.htm#th2_col), [colz](https://root.cern/js/latest/examples.htm#th2_colz), @@ -142,7 +155,10 @@ List of supported classes and draw options: [lego1](https://root.cern/js/latest/examples.htm#th2_lego1), [lego2](https://root.cern/js/latest/examples.htm#th2_lego2), [lego3](https://root.cern/js/latest/examples.htm#th2_lego3), -[lego4](https://root.cern/js/latest/examples.htm#th2_lego4) +[lego4](https://root.cern/js/latest/examples.htm#th2_lego4), +[circ](https://root.cern/js/latest/examples.htm#th2_circ), +[chord](https://root.cern/js/latest/examples.htm#th2_chord), +[xyticks](https://root.cern/js/latest/examples.htm#th2_xyticks) - TH2Poly : [col](https://root.cern/js/latest/examples.htm#th2poly_honeycomb), [lego](https://root.cern/js/latest/examples.htm#th2poly_lego), [europe](https://root.cern/js/latest/examples.htm#th2poly_europe), @@ -204,6 +220,34 @@ List of supported classes and draw options: More examples of supported classes can be found on: +One can change some histograms colors using draw options: + +- `line_N` [line color](https://root.cern/js/latest/examples.htm#th1_line_n) +- `fill_N` [fill color](https://root.cern/js/latest/examples.htm#th1_fill_n) +- `htitle:value` [histogram title](https://root.cern/js/latest/examples.htm#th1_htitle) +- `xaxis_N` [X axis color](https://root.cern/js/latest/examples.htm#th1_xaxis_n) +- `yaxis_N` [Y axis color](https://root.cern/js/latest/examples.htm#th1_yaxis_n) + +One also can modify histogram axes attributes by following option: +- `ctx` center title of X axis +- `cty` center title of Y axis +- `ctz` center title of Z axis +- `otx` place X axis title in opposite to normal corner +- `oty` place Y axis title in opposite to normal corner +- `otz` place Z axis title in opposite to normal corner +- `noex` no exponent for X axis labels +- `noey` no exponent for Y axis labels +- `noez` no exponent for Z axis labels +- `mlx` more log labels on X axis +- `mly` more log labels on Y axis +- `mlz` more log labels on Y axis +- `xtitle:value` set title for X axis +- `ytitle:value` set title for Y axis +- `ztitle:value` set title for Z axis + +Here N can be existing ROOT color index or hex6/hex8 values like [line_ff00ff](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js//files/hsimple.root&item=hpx;1&opt=line_ff00ff) or [fill_7733ff34](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js//files/hsimple.root&item=hpx;1&opt=fill_7733ff34). + + There are special JSROOT draw options which only can be used with for `TCanvas` or `TPad` objects: - logx - enable log10 scale for X axis @@ -237,39 +281,39 @@ There are special JSROOT draw options which only can be used with for `TCanvas` In the URL string one could use "+" sign to specify objects superposition: - - [item=hpx+hprof](https://root.cern/js/latest/?file=../files/hsimple.root&item=hpx+hprof) + - [item=hpx+hprof](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=hpx+hprof) With similar syntax one could specify individual draw options for superimposed objects - - [item=hpx+hprof&opt=logy+hist](https://root.cern/js/latest/?file=../files/hsimple.root&item=hpx+hprof&opt=logy+hist) + - [item=hpx+hprof&opt=logy+hist](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=hpx+hprof&opt=logy+hist) Here "logy" option will be used for "hpx1" item and "hist" option for "hprof;1" item. While draw option can include "+" sign itself, for superposition one could specify arrays of items and draw options like: - - [item=[hpx;1,hprof;1]&opt=[logy,hist]](https://root.cern/js/latest/?file=../files/hsimple.root&item=[hpx;1,hprof;1]&opt=[logy,hist]) + - [item=[hpx;1,hprof;1]&opt=[logy,hist]](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=[hpx;1,hprof;1]&opt=[logy,hist]) ## TTree draw JSROOT provides possibility to display TTree data, using [TTree::Draw](https://root.cern/doc/master/classTTree.html) syntax: - - [opt=px](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple;1&opt=px) - - [opt=px:py](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple;1&opt=px:py) - - [opt=px:py:pz](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple;1&opt=px:py:pz) + - [opt=px](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple;1&opt=px) + - [opt=px:py](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple;1&opt=px:py) + - [opt=px:py:pz](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple;1&opt=px:py:pz) It is also possible to use branch by id number specifying name like "br_0", "br_1" and so on: - - [opt=br_0:br_1](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=br_0:br_1) + - [opt=br_0:br_1](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=br_0:br_1) Histogram ranges and binning defined after reading first 1000 entries from the tree. Like in ROOT, one could configure histogram binning and range directly: - - [opt=px:py>>h(50,-5,5,50,-5,5)](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px:py>>h%2850,-5,5,50,-5,5%29) + - [opt=px:py>>h(50,-5,5,50,-5,5)](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py>>h%2850,-5,5,50,-5,5%29) One and two dimensional draw expressions can be resulted into TGraph object, using ">>Graph" as output: - - [opt=px:py>>Graph](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px:py>>Graph) + - [opt=px:py>>Graph](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py>>Graph) For any integer value one can accumulate histogram with value bits distribution, specifying as output ">>bits(16)" or ">>bits": @@ -281,29 +325,34 @@ There is special handling of TBits objects: It is allowed to use different expressions with branch values: - - [opt=px+py:px-py](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px+py:px-py) + - [opt=px+py:px-py](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px+py:px-py) Such expression can include arithmetical operations and all methods, provided in JavaScript [Math](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) class: - - [opt=Math.abs(px+py)](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=Math.abs%28px+py%29) + - [opt=Math.abs(px+py)](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=Math.abs%28px+py%29) In the expression one could use "Entry$" and "Entries$" variables. One also could specify cut condition, separating it with "::" from the rest draw expression like: - - [opt=px:py::pz>5](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px:py::pz>5) + - [opt=px:py::pz>5](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py::pz>5) Contrary to the normal ROOT, JSROOT allows to use "(expr?res1:res2)" operator (placed into brackets): - - [opt=px:py::(pz>5?2:1)](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px:py::%28pz>5?2:1%29) + - [opt=px:py::(pz>5?2:1)](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py::%28pz>5?2:1%29) It is possible to "dump" content of any branch (by default - first 10 entries): - - [item=ntuple/px&opt=dump](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple/px&opt=dump) + - [item=ntuple/px&opt=dump](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple/px&opt=dump) Or one could dump values produced with draw expression (also first 10 entries by default): - - [opt=px:py::pz>>dump](https://root.cern/js/latest/?file=../files/hsimple.root&item=ntuple&opt=px:py::pz>>dump) + - [opt=px:py::pz>>dump](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py::pz>>dump) + +One also can dump list of entries which match cut expression and use these entries ids to perform other draw operations: + + - [opt=::pz>5>>elist](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=::pz>5>>elist) + - [opt=px:py;elist:[7..12,20,35..49]](https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&item=ntuple&opt=px:py;elist:[7..12,20,35..49]) Working with array indexes is supported. By default, all elements in array are used for the drawing. One could specify index for any array dimension (-1 means last element in the array). For instance, dump last element from `event.fTracks` array: @@ -321,6 +370,9 @@ At the end of expression one can add several parameters with the syntax: Following parameters are supported: - "first" - id of the first entry to process - "entries" - number of entries to process + - "elist" - array of selected entries like `[7,12..25,40]` + - "nmatch" - abort processing after accumulated number of matched entries + - "staged" - first search entries with cut selection and then performed TTree::Draw - "monitor" - periodically show intermediate draw results (interval in milliseconds) - "maxrange" - maximal number of ranges in single HTTP request - "accum" - number of accumulated values before creating histogram @@ -336,8 +388,8 @@ Example - [opt=event.fTracks[].fTriggerBits;entries:1000;first:200;maxrange:25]( JSROOT implements display of TGeo objects like: - - [file=rootgeom.root&item=simple1](https://root.cern/js/latest/?file=../files/geom/rootgeom.root&item=simple1) - - [file=building.root&item=geom&opt=z](https://root.cern/js/latest/?nobrowser&file=../files/geom/building.root&item=geom;1&opt=z) + - [file=rootgeom.root&item=simple1](https://root.cern/js/latest/?file=https://root.cern/js/files/geom/rootgeom.root&item=simple1) + - [file=building.root&item=geom&opt=z](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js/files/geom/building.root&item=geom;1&opt=z) Following classes are supported by geometry viewer: - TGeoVolume @@ -392,11 +444,11 @@ In the URL string several global settings can be changed: It is possible to display only part of geometry model. For instance, one could select sub-item like: -- [file=rootgeom.root&item=simple1/TOP/REPLICA_1](https://root.cern/js/latest/?file=../files/geom/rootgeom.root&item=simple1/TOP/REPLICA_1) +- [file=rootgeom.root&item=simple1/TOP/REPLICA_1](https://root.cern/js/latest/?file=https://root.cern/js/files/geom/rootgeom.root&item=simple1/TOP/REPLICA_1) Or one can use simple selection syntax (work only with first-level volumes): -- [item=simple1&opt=-bar1-bar2](https://root.cern/js/latest/?file=../files/geom/rootgeom.root&item=simple1;1&opt=-bar1-bar2) +- [item=simple1&opt=-bar1-bar2](https://root.cern/js/latest/?file=https://root.cern/js/files/geom/rootgeom.root&item=simple1;1&opt=-bar1-bar2) Syntax uses '+' sign to enable visibility flag of specified volume and '-' sign to disable visibility. One could use wildcard symbol like '+TUBE1*'. @@ -437,7 +489,7 @@ Example of major LHC detectors: * LHCb: [full](https://root.cern/js/latest/?file=https://root.cern/files/lhcbfull.root&item=Geometry;1&opt=all;dflt) Other detectors examples: - * HADES: [full](https://root.cern/js/latest/?file=https://root.cern/files/hades2.root&item=CBMGeom;1&opt=all;dflt), [preselected](https://root.cern/js/latest/?json=../files/geom/hades.json.gz) + * HADES: [full](https://root.cern/js/latest/?file=https://root.cern/files/hades2.root&item=CBMGeom;1&opt=all;dflt), [preselected](https://root.cern/js/latest/?json=https://root.cern/js/files/geom/hades.json.gz) * BABAR: [full](https://root.cern/js/latest/?file=https://root.cern/files/babar.root&item=babar;1&opt=macro:https://root.cern/files/babar_all.C), [emca](https://root.cern/js/latest/?file=https://root.cern/files/babar.root&item=babar;1&opt=macro:https://root.cern/files/babar_emca.C) * STAR: [full](https://root.cern/js/latest/?file=https://root.cern/files/star.root&item=star;1&opt=macro:https://root.cern/files/star_all.C;clipxyz), [svtt](https://root.cern/js/latest/?file=https://root.cern/files/star.root&item=star;1&opt=macro:https://root.cern/files/star_svtt.C) * D0: [full](https://root.cern/js/latest/?file=https://root.cern/files/d0.root&item=d0;1&opt=clipxyz) @@ -448,7 +500,7 @@ Other detectors examples: Together with geometry one could display tracks (TEveTrack) and hits (TEvePointSet, TPolyMarker3D) objects. Either one do it interactively by drag and drop, or superimpose drawing with `+` sign like: -- [item=simple_alice.json.gz+tracks_hits.root/tracks+tracks_hits.root/hits](https://root.cern/js/latest/?nobrowser&json=../files/geom/simple_alice.json.gz&file=../files/geom/tracks_hits.root&item=simple_alice.json.gz+tracks_hits.root/tracks+tracks_hits.root/hits) +- [item=simple_alice.json.gz+tracks_hits.root/tracks+tracks_hits.root/hits](https://root.cern/js/latest/?nobrowser&json=https://root.cern/js/files/geom/simple_alice.json.gz&file=https://root.cern/js/files/geom/tracks_hits.root&item=simple_alice.json.gz+tracks_hits.root/tracks+tracks_hits.root/hits) There is a problem of correct rendering of transparent volumes. To solve problem in general is very expensive (in terms of computing power), therefore several approximation solution can be applied: @@ -571,7 +623,7 @@ therefore there is no guarantee that the file content is not changed/replaced be If somebody still wants to use monitoring of data from ROOT files, could try link like: -- +- In this particular case, the histogram is not changing. @@ -604,7 +656,7 @@ When JSROOT is used with THttpServer, the address looks like: ``` @@ -622,8 +674,8 @@ One also can load some special components directly like: h.setDisplay("simple", "myMainDiv"); // open file and display element - await h.openRootFile("../../files/hsimple.root"); - await h.display("hpxpy;1","colz"); + await h.openRootFile('https://root.cern/js/files/hsimple.root'); + await h.display('hpxpy;1","colz'); ``` @@ -633,7 +685,7 @@ to change stat format using to display value in stats box: ```javascript import { gStyle } from 'https://root.cern/js/latest/modules/main.mjs'; -gStyle.fStatFormat = "7.5g"; +gStyle.fStatFormat = '7.5g'; ``` There is also `settings` object which contains all other JSROOT settings. For instance, @@ -641,8 +693,8 @@ one can configure custom format for different axes: ```javascript import { settings } from 'https://root.cern/js/latest/modules/main.mjs'; -settings.XValuesFormat = "4.2g"; -settings.YValuesFormat = "6.1f"; +settings.XValuesFormat = '4.2g'; +settings.YValuesFormat = '6.1f'; ``` One also can use `build/jsroot.js` bundle to load all functionality at one and access it via `JSROOT` global handle: @@ -652,7 +704,7 @@ One also can use `build/jsroot.js` bundle to load all functionality at one and a ``` @@ -811,7 +863,8 @@ selector.Process = function() { } selector.Terminate = function(res) { - if (!res || (cnt === 0)) return; + if (!res || (cnt === 0)) + return; let meanpx = sumpx/cnt, meanpy = sumpy/cnt; console.log(`Results meanpx = ${meanpx} meanpy = ${meanpy}`); } @@ -833,6 +886,32 @@ let args = { numentries: 1000, firstentry: 500 }; treeProcess(tree, selector, args); ``` +In some applications access to TTree can be optimized using 'staged' approach. +It means that on the first stage interesting entries identified in the TTree and +on the second stage data only for these entries are read. This can boost performance a lot. + +To get list of entries which match to some condition, one can use `>>elist` redirection in draw expression. + +```javascript +const entries = await treeDraw(tree, '::pz>5>>elist'); +``` + +Here only cut condition `pz>5` is specified - no any normal draw expression is configured. +On the second stage one simply use entries for drawing. Like: + +```javascript +const hist = await treeDraw(tree, `px:py;elist:[${entries}]`); +``` + +Such 'staged' approach directly implemented in the tree drawing: + +```javascript +const hist2 = await treeDraw(tree, 'px:py::pz>5;staged'); +``` + +In the [tree_staged.js](https://github.com/root-project/jsroot/blob/master/demo/node/tree_staged.js) macro +one can see different possibilities to use staged approach for TTree processing + ### TGeo API @@ -948,7 +1027,7 @@ JSROOT provides `loadOpenui5` function to load supported OpenUI5: ``` -JSROOT uses when no other source is specified. +JSROOT uses when no other source is specified. There are small details when using OpenUI5 with THttpServer. First of all, location of JSROOT modules should be specified as `/jsrootsys/modules/main.mjs`. And then trying to access files from local disk, one should specify `/currentdir/` folder: @@ -966,7 +1045,7 @@ JSROOT provides [example](https://root.cern/js/latest/demo/openui5/) showing usa * Core functionality should be imported from `main.mjs` module like: ```javascript -import { create, parse, createHistogram, redraw } from 'https://root.cern/js/7.0.0/modules/main.mjs'; +import { create, parse, createHistogram, redraw } from 'https://root.cern/js/7.9.0/modules/main.mjs'; ``` * It is still possible to use `JSRoot.core.js` script, which provides very similar (but not identical!) functionality as with `v6` via global `JSROOT` object @@ -978,20 +1057,20 @@ import { create, parse, createHistogram, redraw } from 'https://root.cern/js/7.0 * Global hierarchy painter `JSROOT.hpainter` no longer existing, one can use `getHPainter` function: ```javascript -import { getHPainter } from 'https://root.cern/js/7.0.0/modules/main.mjs'; +import { getHPainter } from 'https://root.cern/js/7.9.0/modules/main.mjs'; let hpainter = getHPainter(); ``` * All math functions previously available via `JSROOT.Math` should be imported from `base/math.mjs` module: ```javascript -import * as math from 'https://root.cern/js/7.0.0/modules/base/math.mjs'; +import * as math from 'https://root.cern/js/7.9.0/modules/base/math.mjs'; ``` * Indication of batch mode `JSROOT.batch_mode` should be accessed via functions: ```javascript -import { isBatchMode, setBatchMode } from 'https://root.cern/js/7.0.0/modules/main.mjs'; +import { isBatchMode, setBatchMode } from 'https://root.cern/js/7.9.0/modules/main.mjs'; let was_batch = isBatchMode(); if (!was_batch) setBatchMode(true); ``` diff --git a/docs/jsdoc.json b/docs/jsdoc.json index e97d42cb8..ac04bf504 100644 --- a/docs/jsdoc.json +++ b/docs/jsdoc.json @@ -2,7 +2,7 @@ "source": { "include": ["modules"], "includePattern": ".mjs$", - "excludePattern": "(node_modules|main.mjs|three.mjs|d3.mjs|lil-gui.mjs)" + "excludePattern": "(node_modules|main.mjs|three.mjs|three_addons.mjs|d3.mjs|lil-gui.mjs|jspdf.mjs|svg2pdf.mjs|lzma.mjs|zstd.mjs|sha256.mjs|testing.mjs)" }, "sourceType": "module", "recurseDepth": 10, diff --git a/docs/main.md b/docs/main.md index 872bde683..a13c6b6ec 100644 --- a/docs/main.md +++ b/docs/main.md @@ -4,9 +4,9 @@ JavaScript ROOT provides interactive ROOT-like graphics in the web browsers. Data can be read and displayed from binary and JSON ROOT files. JSROOT implements user interface for THttpServer class. -TTree::Draw() -THStack -Geometry +TTree::Draw() +THStack +Geometry


    diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..272df1564 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,143 @@ +import stylisticJs from '@stylistic/eslint-plugin'; + +import globals from 'globals'; + +import js from "@eslint/js" + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + MathJax: 'readonly' + } + }, + plugins: { + '@stylistic/js': stylisticJs + }, + rules: { + 'array-callback-return': 'error', + 'consistent-return': 'off', + camelcase: 'off', + 'default-case-last': 'error', + eqeqeq: 'warn', + curly: ['warn', 'multi-or-nest'], + 'one-var': ['warn', 'consecutive'], + 'no-new-func': 'off', + 'no-unused-vars': 'error', + 'no-use-before-define': ['error', { + functions: true, + classes: true, + variables: true, + allowNamedExports: false + }], + 'no-unreachable-loop': 'error', + 'no-unreachable': 'error', + 'no-constant-condition': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-else-return': 'warn', + 'no-extra-bind': 'error', + 'no-extra-boolean-cast': 'warn', + 'no-implicit-coercion': 'warn', + 'for-direction': 'error', + 'no-undef': 'warn', + 'no-await-in-loop': 'warn', + 'no-class-assign': 'error', + 'no-cond-assign': 'warn', + 'no-constructor-return': 'error', + 'no-duplicate-imports': 'error', + 'no-promise-executor-return': 'error', + 'no-self-compare': 'error', + 'no-template-curly-in-string': 'warn', + 'no-useless-assignment': 'warn', + 'no-implicit-globals': 'warn', + 'no-invalid-this': 'off', + 'no-labels': 'error', + 'no-lonely-if': 'warn', + 'no-return-assign': 'warn', + 'no-shadow': 'warn', + 'no-throw-literal': 'warn', + 'no-unneeded-ternary': 'warn', + 'no-unused-expressions': 'warn', + 'no-useless-call': 'warn', + 'no-useless-computed-key': 'warn', + 'no-useless-constructor': 'warn', + 'no-useless-rename': 'warn', + 'no-useless-return': 'warn', + 'no-var': 'error', + 'no-void': 'error', + 'object-shorthand': 'warn', + 'operator-assignment': ['warn', 'always'], + 'prefer-arrow-callback': 'warn', + 'prefer-const': 'warn', + 'prefer-numeric-literals': 'warn', + 'prefer-object-has-own': 'warn', + 'prefer-promise-reject-errors': 'warn', + 'prefer-rest-params': 'warn', + 'prefer-spread': 'warn', + 'prefer-template': 'off', + + // moved to @stylistic/js + '@stylistic/js/semi': 'warn', + '@stylistic/js/quotes': ['warn', 'single'], + '@stylistic/js/indent': ['warn', 3, { + ignoredNodes: ['ConditionalExpression', 'ExportNamedDeclaration'], + ImportDeclaration: 'off', + VariableDeclarator: 'first', + ArrayExpression: 'first', + MemberExpression: 'off', + FunctionDeclaration: { parameters: 'first' }, + CallExpression: { arguments: 'first' }, + SwitchCase: 1 + }], + '@stylistic/js/eol-last': ['warn', 'always' ], + '@stylistic/js/array-bracket-spacing': 'warn', + '@stylistic/js/comma-spacing': 'warn', + '@stylistic/js/max-len': ['warn', { + code: 200, + ignoreStrings: true, + ignoreTemplateLiterals: true, + tabWidth: 3 + }], + '@stylistic/js/max-statements-per-line' : ['warn', { + max: 2, + ignoredNodes: ['BreakStatement'] + }], + '@stylistic/js/keyword-spacing': 'warn', + '@stylistic/js/semi-spacing': 'warn', + '@stylistic/js/semi-style': ['warn', 'last'], + '@stylistic/js/no-floating-decimal': 'warn', + '@stylistic/js/no-confusing-arrow': 'warn', + '@stylistic/js/no-extra-semi': 'warn', + '@stylistic/js/no-mixed-spaces-and-tabs': 'warn', + '@stylistic/js/no-multi-spaces': ['warn', { ignoreEOLComments: true }], + '@stylistic/js/no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 0 }], + '@stylistic/js/no-trailing-spaces': 'warn', + '@stylistic/js/no-whitespace-before-property': 'warn', + '@stylistic/js/object-curly-spacing': ['warn', 'always'], + '@stylistic/js/key-spacing': 'warn', + '@stylistic/js/object-property-newline': 'off', + '@stylistic/js/spaced-comment': 'warn', + '@stylistic/js/computed-property-spacing': 'warn', + '@stylistic/js/object-curly-newline': ['warn', { + ObjectExpression: { 'consistent': true }, + ObjectPattern: { 'consistent': true }, + ImportDeclaration: 'never', + ExportDeclaration: 'never' + }], + '@stylistic/js/quote-props': ['warn', 'as-needed'], + '@stylistic/js/padded-blocks': ['warn', { blocks: 'never', classes: 'always', switches: 'never' }], + '@stylistic/js/space-infix-ops': 'warn', + '@stylistic/js/space-in-parens': 'warn', + '@stylistic/js/space-before-blocks': 'warn', + '@stylistic/js/space-before-function-paren': ['warn', 'never'], + '@stylistic/js/switch-colon-spacing': 'warn', + '@stylistic/js/template-curly-spacing': 'error' + } + } +]; + diff --git a/examples.htm b/examples.htm index b11da54e1..f27ba4d3b 100644 --- a/examples.htm +++ b/examples.htm @@ -322,6 +322,15 @@ } + @@ -345,18 +354,13 @@

    - import { settings, constants, httpRequest, decodeUrl, version, version_id, source_dir, gStyle } from './modules/core.mjs'; - import { cleanup } from './modules/base/ObjectPainter.mjs'; - import { registerForResize } from './modules/gui/utils.mjs'; - import { createRootColors } from './modules/base/colors.mjs'; - import { draw, makeImage } from './modules/draw.mjs'; - import { readStyleFromURL } from './modules/gui.mjs'; - import { openFile } from './modules/io.mjs'; - import { setHPainter, getHPainter } from './modules/gui/display.mjs'; - import { HierarchyPainter } from './modules/gui/HierarchyPainter.mjs'; - import { testInteractivity } from './modules/testing.mjs'; - - document.getElementById('version').innerHTML = (version_id != 'dev') ? version_id : version; + import { settings, constants, httpRequest, decodeUrl, version, version_id, source_dir, gStyle, + cleanup, draw, makeImage, openFile, setHPainter, getHPainter, HierarchyPainter, + registerForResize, readStyleFromURL } from 'jsroot'; + import { createRootColors } from 'jsroot/colors'; + import { testInteractivity } from 'jsroot/testing'; + + document.getElementById('version').innerText = (version_id != 'dev') ? version_id : version; document.getElementById('version').title = `${version}, ${source_dir}`; document.getElementById('expandButton').addEventListener('click', event => { @@ -371,7 +375,7 @@

    JSROOT

    JSROOT
    JSROOT
    JSROOT
    JSROOT
    JSROOT
    JSROOT
    JSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOTJSROOT Draw of single element + -
    loading ...
    - - - diff --git a/files/online.htm b/files/online.htm index 35da1dc63..9b026a4dd 100644 --- a/files/online.htm +++ b/files/online.htm @@ -5,21 +5,16 @@ Online server + -
    loading ...
    - - - diff --git a/fonts/wingding.ttf b/fonts/wingding.ttf new file mode 100644 index 000000000..47bd09584 Binary files /dev/null and b/fonts/wingding.ttf differ diff --git a/index.htm b/index.htm index 0cad76756..fb99f5640 100644 --- a/index.htm +++ b/index.htm @@ -4,13 +4,21 @@ Read a ROOT file + + -
    - loading scripts ... +
    + loading modules ...
    @@ -42,7 +50,7 @@ Example: - https://root.cern/js/latest/?file=../files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz] + https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz] Page can be used to open files from other web servers like: diff --git a/libs/d3.mjs b/libs/d3.mjs index 78bf8744a..48209dd53 100644 --- a/libs/d3.mjs +++ b/libs/d3.mjs @@ -1,2 +1,2 @@ // https://d3js.org v7.9.0 Copyright 2010-2021 Mike Bostock -var t="7.9.0";function n(t,n,e){t.prototype=n.prototype=e,e.constructor=t}function e(t,n){var e=Object.create(t.prototype);for(var r in n)e[r]=n[r];return e}function r(){}var i=.7,o=1/i,u="\\s*([+-]?\\d+)\\s*",a="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*",l="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*",c=/^#([0-9a-f]{3,8})$/,s=new RegExp(`^rgb\\(${u},${u},${u}\\)$`),f=new RegExp(`^rgb\\(${l},${l},${l}\\)$`),h=new RegExp(`^rgba\\(${u},${u},${u},${a}\\)$`),g=new RegExp(`^rgba\\(${l},${l},${l},${a}\\)$`),p=new RegExp(`^hsl\\(${a},${l},${l}\\)$`),d=new RegExp(`^hsla\\(${a},${l},${l},${a}\\)$`),y={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function v(){return this.rgb().formatHex()}function m(){return this.rgb().formatRgb()}function w(t){var n,e;return t=(t+"").trim().toLowerCase(),(n=c.exec(t))?(e=n[1].length,n=parseInt(n[1],16),6===e?_(n):3===e?new T(n>>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?M(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?M(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=s.exec(t))?new T(n[1],n[2],n[3],1):(n=f.exec(t))?new T(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=h.exec(t))?M(n[1],n[2],n[3],n[4]):(n=g.exec(t))?M(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=p.exec(t))?S(n[1],n[2]/100,n[3]/100,1):(n=d.exec(t))?S(n[1],n[2]/100,n[3]/100,n[4]):y.hasOwnProperty(t)?_(y[t]):"transparent"===t?new T(NaN,NaN,NaN,0):null}function _(t){return new T(t>>16&255,t>>8&255,255&t,1)}function M(t,n,e,r){return r<=0&&(t=n=e=NaN),new T(t,n,e,r)}function x(t){return t instanceof r||(t=w(t)),t?new T((t=t.rgb()).r,t.g,t.b,t.opacity):new T}function b(t,n,e,r){return 1===arguments.length?x(t):new T(t,n,e,null==r?1:r)}function T(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function A(){return`#${k(this.r)}${k(this.g)}${k(this.b)}`}function N(){const t=C(this.opacity);return`${1===t?"rgb(":"rgba("}${$(this.r)}, ${$(this.g)}, ${$(this.b)}${1===t?")":`, ${t})`}`}function C(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function $(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function k(t){return((t=$(t))<16?"0":"")+t.toString(16)}function S(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new E(t,n,e,r)}function D(t){if(t instanceof E)return new E(t.h,t.s,t.l,t.opacity);if(t instanceof r||(t=w(t)),!t)return new E;if(t instanceof E)return t;var n=(t=t.rgb()).r/255,e=t.g/255,i=t.b/255,o=Math.min(n,e,i),u=Math.max(n,e,i),a=NaN,l=u-o,c=(u+o)/2;return l?(a=n===u?(e-i)/l+6*(e0&&c<1?0:a,new E(a,l,c,t.opacity)}function U(t,n,e,r){return 1===arguments.length?D(t):new E(t,n,e,null==r?1:r)}function E(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function F(t){return(t=(t||0)%360)<0?t+360:t}function q(t){return Math.max(0,Math.min(1,t||0))}function Y(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}n(r,w,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:v,formatHex:v,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return D(this).formatHsl()},formatRgb:m,toString:m}),n(T,b,e(r,{brighter(t){return t=null==t?o:Math.pow(o,t),new T(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=null==t?i:Math.pow(i,t),new T(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new T($(this.r),$(this.g),$(this.b),C(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:A,formatHex:A,formatHex8:function(){return`#${k(this.r)}${k(this.g)}${k(this.b)}${k(255*(isNaN(this.opacity)?1:this.opacity))}`},formatRgb:N,toString:N})),n(E,U,e(r,{brighter(t){return t=null==t?o:Math.pow(o,t),new E(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=null==t?i:Math.pow(i,t),new E(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new T(Y(t>=240?t-240:t+120,i,r),Y(t,i,r),Y(t<120?t+240:t-120,i,r),this.opacity)},clamp(){return new E(F(this.h),q(this.s),q(this.l),C(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=C(this.opacity);return`${1===t?"hsl(":"hsla("}${F(this.h)}, ${100*q(this.s)}%, ${100*q(this.l)}%${1===t?")":`, ${t})`}`}}));const H=Math.PI/180,P=180/Math.PI,L=.96422,O=1,j=.82521,I=4/29,R=6/29,X=3*R*R,z=R*R*R;function V(t){if(t instanceof W)return new W(t.l,t.a,t.b,t.opacity);if(t instanceof rt)return it(t);t instanceof T||(t=x(t));var n,e,r=K(t.r),i=K(t.g),o=K(t.b),u=Q((.2225045*r+.7168786*i+.0606169*o)/O);return r===i&&i===o?n=e=u:(n=Q((.4360747*r+.3850649*i+.1430804*o)/L),e=Q((.0139322*r+.0971045*i+.7141733*o)/j)),new W(116*u-16,500*(n-u),200*(u-e),t.opacity)}function Z(t,n){return new W(t,0,0,null==n?1:n)}function B(t,n,e,r){return 1===arguments.length?V(t):new W(t,n,e,null==r?1:r)}function W(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Q(t){return t>z?Math.pow(t,1/3):t/X+I}function G(t){return t>R?t*t*t:X*(t-I)}function J(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function K(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function tt(t){if(t instanceof rt)return new rt(t.h,t.c,t.l,t.opacity);if(t instanceof W||(t=V(t)),0===t.a&&0===t.b)return new rt(NaN,0t+e))}function Tt(){return Ct(!1,!1)}function At(){return Ct(!1,!0)}function Nt(){return Ct(!0,!1)}function Ct(t,n){var e=0,r=null,i=null,o=null;function u(u){var a,l=u.length,c=new Array(l),s=bt(0,l),f=new Array(l*l),h=new Array(l),g=0;u=Float64Array.from({length:l*l},n?(t,n)=>u[n%l][n/l|0]:(t,n)=>u[n/l|0][n%l]);for(let n=0;nr(c[t],c[n])));for(const e of s){const r=n;if(t){const t=bt(1+~l,l).filter((t=>t<0?u[~t*l+e]:u[e*l+t]));i&&t.sort(((t,n)=>i(t<0?-u[~t*l+e]:u[e*l+t],n<0?-u[~n*l+e]:u[e*l+n])));for(const r of t)if(r<0){(f[~r*l+e]||(f[~r*l+e]={source:null,target:null})).target={index:e,startAngle:n,endAngle:n+=u[~r*l+e]*g,value:u[~r*l+e]}}else{(f[e*l+r]||(f[e*l+r]={source:null,target:null})).source={index:e,startAngle:n,endAngle:n+=u[e*l+r]*g,value:u[e*l+r]}}h[e]={index:e,startAngle:r,endAngle:n,value:c[e]}}else{const t=bt(0,l).filter((t=>u[e*l+t]||u[t*l+e]));i&&t.sort(((t,n)=>i(u[e*l+t],u[e*l+n])));for(const r of t){let t;if(e=0))throw new Error(`invalid digits: ${t}`);if(n>15)return Ut;const e=10**n;return function(t){this._+=t[0];for(let n=1,r=t.length;nSt)if(Math.abs(s*a-l*c)>St&&i){let h=e-o,g=r-u,p=a*a+l*l,d=h*h+g*g,y=Math.sqrt(p),v=Math.sqrt(f),m=i*Math.tan(($t-Math.acos((p+f-d)/(2*y*v)))/2),w=m/v,_=m/y;Math.abs(w-1)>St&&this._append`L${t+w*c},${n+w*s}`,this._append`A${i},${i},0,0,${+(s*h>c*g)},${this._x1=t+_*a},${this._y1=n+_*l}`}else this._append`L${this._x1=t},${this._y1=n}`;else;}arc(t,n,e,r,i,o){if(t=+t,n=+n,o=!!o,(e=+e)<0)throw new Error(`negative radius: ${e}`);let u=e*Math.cos(r),a=e*Math.sin(r),l=t+u,c=n+a,s=1^o,f=o?r-i:i-r;null===this._x1?this._append`M${l},${c}`:(Math.abs(this._x1-l)>St||Math.abs(this._y1-c)>St)&&this._append`L${l},${c}`,e&&(f<0&&(f=f%kt+kt),f>Dt?this._append`A${e},${e},0,1,${s},${t-u},${n-a}A${e},${e},0,1,${s},${this._x1=l},${this._y1=c}`:f>St&&this._append`A${e},${e},0,${+(f>=$t)},${s},${this._x1=t+e*Math.cos(i)},${this._y1=n+e*Math.sin(i)}`)}rect(t,n,e,r){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${e=+e}v${+r}h${-e}Z`}toString(){return this._}}function Ft(){return new Et}Ft.prototype=Et.prototype;var qt=Array.prototype.slice;function Yt(t){return function(){return t}}function Ht(t){return t.source}function Pt(t){return t.target}function Lt(t){return t.radius}function Ot(t){return t.startAngle}function jt(t){return t.endAngle}function It(){return 0}function Rt(){return 10}function Xt(t){var n=Ht,e=Pt,r=Lt,i=Lt,o=Ot,u=jt,a=It,l=null;function c(){var c,s=n.apply(this,arguments),f=e.apply(this,arguments),h=a.apply(this,arguments)/2,g=qt.call(arguments),p=+r.apply(this,(g[0]=s,g)),d=o.apply(this,g)-wt,y=u.apply(this,g)-wt,v=+i.apply(this,(g[0]=f,g)),m=o.apply(this,g)-wt,w=u.apply(this,g)-wt;if(l||(l=c=Ft()),h>xt&&(dt(y-d)>2*h+xt?y>d?(d+=h,y-=h):(d-=h,y+=h):d=y=(d+y)/2,dt(w-m)>2*h+xt?w>m?(m+=h,w-=h):(m-=h,w+=h):m=w=(m+w)/2),l.moveTo(p*yt(d),p*vt(d)),l.arc(0,0,p,d,y),d!==m||y!==w)if(t){var _=v-+t.apply(this,arguments),M=(m+w)/2;l.quadraticCurveTo(0,0,_*yt(m),_*vt(m)),l.lineTo(v*yt(M),v*vt(M)),l.lineTo(_*yt(w),_*vt(w))}else l.quadraticCurveTo(0,0,v*yt(m),v*vt(m)),l.arc(0,0,v,m,w);if(l.quadraticCurveTo(0,0,p*yt(d),p*vt(d)),l.closePath(),c)return l=null,c+""||null}return t&&(c.headRadius=function(n){return arguments.length?(t="function"==typeof n?n:Yt(+n),c):t}),c.radius=function(t){return arguments.length?(r=i="function"==typeof t?t:Yt(+t),c):r},c.sourceRadius=function(t){return arguments.length?(r="function"==typeof t?t:Yt(+t),c):r},c.targetRadius=function(t){return arguments.length?(i="function"==typeof t?t:Yt(+t),c):i},c.startAngle=function(t){return arguments.length?(o="function"==typeof t?t:Yt(+t),c):o},c.endAngle=function(t){return arguments.length?(u="function"==typeof t?t:Yt(+t),c):u},c.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:Yt(+t),c):a},c.source=function(t){return arguments.length?(n=t,c):n},c.target=function(t){return arguments.length?(e=t,c):e},c.context=function(t){return arguments.length?(l=null==t?null:t,c):l},c}function zt(){return Xt()}function Vt(){return Xt(Rt)}var Zt={value:()=>{}};function Bt(){for(var t,n=0,e=arguments.length,r={};n=0&&(n=t.slice(e+1),t=t.slice(0,e)),t&&!r.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))),u=-1,a=o.length;if(!(arguments.length<2)){if(null!=n&&"function"!=typeof n)throw new Error("invalid callback: "+n);for(;++u0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Kt.hasOwnProperty(n)?{space:Kt[n],local:t}:t}function nn(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===Jt&&n.documentElement.namespaceURI===Jt?n.createElement(t):n.createElementNS(e,t)}}function en(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function rn(t){var n=tn(t);return(n.local?en:nn)(n)}function on(){}function un(t){return null==t?on:function(){return this.querySelector(t)}}function an(t){return null==t?[]:Array.isArray(t)?t:Array.from(t)}function ln(){return[]}function cn(t){return null==t?ln:function(){return this.querySelectorAll(t)}}function sn(t){return function(){return this.matches(t)}}function fn(t){return function(n){return n.matches(t)}}var hn=Array.prototype.find;function gn(){return this.firstElementChild}var pn=Array.prototype.filter;function dn(){return Array.from(this.children)}function yn(t){return new Array(t.length)}function vn(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function mn(t,n,e,r,i,o){for(var u,a=0,l=n.length,c=o.length;an?1:t>=n?0:NaN}function bn(t){return function(){this.removeAttribute(t)}}function Tn(t){return function(){this.removeAttributeNS(t.space,t.local)}}function An(t,n){return function(){this.setAttribute(t,n)}}function Nn(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function Cn(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function $n(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function kn(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Sn(t){return function(){this.style.removeProperty(t)}}function Dn(t,n,e){return function(){this.style.setProperty(t,n,e)}}function Un(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function En(t,n){return t.style.getPropertyValue(n)||kn(t).getComputedStyle(t,null).getPropertyValue(n)}function Fn(t){return function(){delete this[t]}}function qn(t,n){return function(){this[t]=n}}function Yn(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function Hn(t){return t.trim().split(/^|\s+/)}function Pn(t){return t.classList||new Ln(t)}function Ln(t){this._node=t,this._names=Hn(t.getAttribute("class")||"")}function On(t,n){for(var e=Pn(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var le=[null];function ce(t,n){this._groups=t,this._parents=n}function se(){return new ce([[document.documentElement]],le)}function fe(t){return"string"==typeof t?new ce([[document.querySelector(t)]],[document.documentElement]):new ce([[t]],le)}function he(t){return fe(rn(t).call(document.documentElement))}ce.prototype=se.prototype={constructor:ce,select:function(t){"function"!=typeof t&&(t=un(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=M&&(M=_+1);!(w=v[M])&&++M=0;)(r=i[o])&&(u&&4^r.compareDocumentPosition(u)&&u.parentNode.insertBefore(r,u),u=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=xn);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?Sn:"function"==typeof n?Un:Dn)(t,n,null==e?"":e)):En(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?Fn:"function"==typeof n?Yn:qn)(t,n)):this.node()[t]},classed:function(t,n){var e=Hn(t+"");if(arguments.length<2){for(var r=Pn(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}(t+""),u=o.length;if(!(arguments.length<2)){for(a=n?ie:re,r=0;rve(t,n)))}function we(t){return"string"==typeof t?new ce([document.querySelectorAll(t)],[document.documentElement]):new ce([an(t)],le)}de.prototype=pe.prototype={constructor:de,get:function(t){for(var n=this._;!(n in t);)if(!(t=t.parentNode))return;return t[n]},set:function(t,n){return t[this._]=n},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};const _e={passive:!1},Me={capture:!0,passive:!1};function xe(t){t.stopImmediatePropagation()}function be(t){t.preventDefault(),t.stopImmediatePropagation()}function Te(t){var n=t.document.documentElement,e=fe(t).on("dragstart.drag",be,Me);"onselectstart"in n?e.on("selectstart.drag",be,Me):(n.__noselect=n.style.MozUserSelect,n.style.MozUserSelect="none")}function Ae(t,n){var e=t.document.documentElement,r=fe(t).on("dragstart.drag",null);n&&(r.on("click.drag",be,Me),setTimeout((function(){r.on("click.drag",null)}),0)),"onselectstart"in e?r.on("selectstart.drag",null):(e.style.MozUserSelect=e.__noselect,delete e.__noselect)}var Ne=t=>()=>t;function Ce(t,{sourceEvent:n,subject:e,target:r,identifier:i,active:o,x:u,y:a,dx:l,dy:c,dispatch:s}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:u,enumerable:!0,configurable:!0},y:{value:a,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:c,enumerable:!0,configurable:!0},_:{value:s}})}function $e(t){return!t.ctrlKey&&!t.button}function ke(){return this.parentNode}function Se(t,n){return null==n?{x:t.x,y:t.y}:n}function De(){return navigator.maxTouchPoints||"ontouchstart"in this}function Ue(){var t,n,e,r,i=$e,o=ke,u=Se,a=De,l={},c=Bt("start","drag","end"),s=0,f=0;function h(t){t.on("mousedown.drag",g).filter(a).on("touchstart.drag",y).on("touchmove.drag",v,_e).on("touchend.drag touchcancel.drag",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function g(u,a){if(!r&&i.call(this,u,a)){var l=w(this,o.call(this,u,a),u,a,"mouse");l&&(fe(u.view).on("mousemove.drag",p,Me).on("mouseup.drag",d,Me),Te(u.view),xe(u),e=!1,t=u.clientX,n=u.clientY,l("start",u))}}function p(r){if(be(r),!e){var i=r.clientX-t,o=r.clientY-n;e=i*i+o*o>f}l.mouse("drag",r)}function d(t){fe(t.view).on("mousemove.drag mouseup.drag",null),Ae(t.view,e),be(t),l.mouse("end",t)}function y(t,n){if(i.call(this,t,n)){var e,r,u=t.changedTouches,a=o.call(this,t,n),l=u.length;for(e=0;en?1:t>=n?0:NaN}function Fe(t,n){return null==t||null==n?NaN:nt?1:n>=t?0:NaN}function qe(t){let n,e,r;function i(t,r,i=0,o=t.length){if(i>>1;e(t[n],r)<0?i=n+1:o=n}while(iEe(t(n),e),r=(n,e)=>t(n)-e):(n=t===Ee||t===Fe?t:Ye,e=t,r=t),{left:i,center:function(t,n,e=0,o=t.length){const u=i(t,n,e,o-1);return u>e&&r(t[u-1],n)>-r(t[u],n)?u-1:u},right:function(t,r,i=0,o=t.length){if(i>>1;e(t[n],r)<=0?i=n+1:o=n}while(i=t))-(null==n||!(n>=n))||(tn?1:0)}const Re=Math.sqrt(50),Xe=Math.sqrt(10),ze=Math.sqrt(2);function Ve(t,n,e){const r=(n-t)/Math.max(0,e),i=Math.floor(Math.log10(r)),o=r/Math.pow(10,i),u=o>=Re?10:o>=Xe?5:o>=ze?2:1;let a,l,c;return i<0?(c=Math.pow(10,-i)/u,a=Math.round(t*c),l=Math.round(n*c),a/cn&&--l,c=-c):(c=Math.pow(10,i)*u,a=Math.round(t/c),l=Math.round(n/c),a*cn&&--l),l0))return[];if((t=+t)===(n=+n))return[t];const r=n=i))return[];const a=o-i+1,l=new Array(a);if(r)if(u<0)for(let t=0;t=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e=i)&&(e=i)}return e}function Ge(t,n){let e;if(void 0===n)for(const n of t)null!=n&&(e>n||void 0===e&&n>=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e>i||void 0===e&&i>=i)&&(e=i)}return e}function Je(t,n,e=0,r=1/0,i){if(n=Math.floor(n),e=Math.floor(Math.max(0,e)),r=Math.floor(Math.min(t.length-1,r)),!(e<=n&&n<=r))return t;for(i=void 0===i?Ie:function(t=Ee){if(t===Ee)return Ie;if("function"!=typeof t)throw new TypeError("compare is not a function");return(n,e)=>{const r=t(n,e);return r||0===r?r:(0===t(e,e))-(0===t(n,n))}}(i);r>e;){if(r-e>600){const o=r-e+1,u=n-e+1,a=Math.log(o),l=.5*Math.exp(2*a/3),c=.5*Math.sqrt(a*l*(o-l)/o)*(u-o/2<0?-1:1);Je(t,n,Math.max(e,Math.floor(n-u*l/o+c)),Math.min(r,Math.floor(n+(o-u)*l/o+c)),i)}const o=t[n];let u=e,a=r;for(Ke(t,e,n),i(t[r],o)>0&&Ke(t,e,r);u0;)--a}0===i(t[e],o)?Ke(t,e,a):(++a,Ke(t,a,r)),a<=n&&(e=a+1),n<=a&&(r=a-1)}return t}function Ke(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function tr(t,n,e=He){if((r=t.length)&&!isNaN(n=+n)){if(n<=0||r<2)return+e(t[0],0,t);if(n>=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),u=+e(t[o],o,t);return u+(+e(t[o+1],o+1,t)-u)*(i-o)}}function nr(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}function er(t,n){switch(arguments.length){case 0:break;case 1:"function"==typeof t?this.interpolator(t):this.range(t);break;default:this.domain(t),"function"==typeof n?this.interpolator(n):this.range(n)}return this}const rr=Symbol("implicit");function ir(){var t=new InternMap,n=[],e=[],r=rr;function i(i){let o=t.get(i);if(void 0===o){if(r!==rr)return r;t.set(i,o=n.push(i)-1)}return e[o%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=new InternMap;for(const r of e)t.has(r)||t.set(r,n.push(r)-1);return i},i.range=function(t){return arguments.length?(e=Array.from(t),i):e.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return ir(n,e).unknown(r)},nr.apply(i,arguments),i}function or(){var t,n,e=ir().unknown(void 0),r=e.domain,i=e.range,o=0,u=1,a=!1,l=0,c=0,s=.5;function f(){var e=r().length,f=u()=>t;function cr(t){return 1==(t=+t)?sr:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):lr(isNaN(n)?e:n)}}function sr(t,n){var e=n-t;return e?function(t,n){return function(e){return t+e*n}}(t,e):lr(isNaN(t)?n:t)}var fr=function t(n){var e=cr(n);function r(t,n){var r=e((t=b(t)).r,(n=b(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),u=sr(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=u(n),t+""}}return r.gamma=t,r}(1);function hr(t,n){n||(n=[]);var e,r=t?Math.min(n.length,t.length):0,i=n.slice();return function(o){for(e=0;eo&&(i=n.slice(o,i),a[u]?a[u]+=i:a[++u]=i),(e=e[0])===(r=r[0])?a[u]?a[u]+=r:a[++u]=r:(a[++u]=null,l.push({i:u,x:dr(e,r)})),o=mr.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:dr(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,u.rotate,a,l),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:dr(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,u.skewX,a,l),function(t,n,e,r,o,u){if(t!==e||n!==r){var a=o.push(i(o)+"scale(",null,",",null,")");u.push({i:a-4,x:dr(t,e)},{i:a-2,x:dr(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,u.scaleX,u.scaleY,a,l),o=u=null,function(t){for(var n,e=-1,r=l.length;++en&&(e=t,t=n,n=e),c=function(e){return Math.max(t,Math.min(n,e))}),r=l>2?Fr:Er,i=o=null,f}function f(n){return null==n||isNaN(n=+n)?e:(i||(i=r(u.map(t),a,l)))(t(c(n)))}return f.invert=function(e){return c(n((o||(o=r(a,u.map(t),dr)))(e)))},f.domain=function(t){return arguments.length?(u=Array.from(t,kr),s()):u.slice()},f.range=function(t){return arguments.length?(a=Array.from(t),s()):a.slice()},f.rangeRound=function(t){return a=Array.from(t),l=Mr,s()},f.clamp=function(t){return arguments.length?(c=!!t||Dr,s()):c!==Dr},f.interpolate=function(t){return arguments.length?(l=t,s()):l},f.unknown=function(t){return arguments.length?(e=t,f):e},function(e,r){return t=e,n=r,s()}}function Hr(){return Yr()(Dr,Dr)}function Pr(t,n){if((e=(t=n?t.toExponential(n-1):t.toExponential()).indexOf("e"))<0)return null;var e,r=t.slice(0,e);return[r.length>1?r[0]+r.slice(2):r,+t.slice(e+1)]}function Lr(t){return(t=Pr(Math.abs(t)))?t[1]:NaN}var Or,jr=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Ir(t){if(!(n=jr.exec(t)))throw new Error("invalid format: "+t);var n;return new Rr({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function Rr(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function Xr(t,n){var e=Pr(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Ir.prototype=Rr.prototype,Rr.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var zr={"%":(t,n)=>(100*t).toFixed(n),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,n)=>t.toExponential(n),f:(t,n)=>t.toFixed(n),g:(t,n)=>t.toPrecision(n),o:t=>Math.round(t).toString(8),p:(t,n)=>Xr(100*t,n),r:Xr,s:function(t,n){var e=Pr(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(Or=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=r.length;return o===u?r:o>u?r+new Array(o-u+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Pr(t,Math.max(0,n+o-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Vr(t){return t}var Zr,Br,Wr,Qr=Array.prototype.map,Gr=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function Jr(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?Vr:(n=Qr.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],u=0,a=n[0],l=0;i>0&&a>0&&(l+a+1>r&&(a=Math.max(1,r-l)),o.push(t.substring(i-=a,i+a)),!((l+=a+1)>r));)a=n[u=(u+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",u=void 0===t.decimal?".":t.decimal+"",a=void 0===t.numerals?Vr:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(Qr.call(t.numerals,String)),l=void 0===t.percent?"%":t.percent+"",c=void 0===t.minus?"−":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function f(t){var n=(t=Ir(t)).fill,e=t.align,f=t.sign,h=t.symbol,g=t.zero,p=t.width,d=t.comma,y=t.precision,v=t.trim,m=t.type;"n"===m?(d=!0,m="g"):zr[m]||(void 0===y&&(y=12),v=!0,m="g"),(g||"0"===n&&"="===e)&&(g=!0,n="0",e="=");var w="$"===h?i:"#"===h&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",_="$"===h?o:/[%p]/.test(m)?l:"",M=zr[m],x=/[defgprs%]/.test(m);function b(t){var i,o,l,h=w,b=_;if("c"===m)b=M(t)+b,t="";else{var T=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:M(Math.abs(t),y),v&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),T&&0==+t&&"+"!==f&&(T=!1),h=(T?"("===f?f:c:"-"===f||"("===f?"":f)+h,b=("s"===m?Gr[8+Or/3]:"")+b+(T&&"("===f?")":""),x)for(i=-1,o=t.length;++i(l=t.charCodeAt(i))||l>57){b=(46===l?u+t.slice(i+1):t.slice(i))+b,t=t.slice(0,i);break}}d&&!g&&(t=r(t,1/0));var A=h.length+t.length+b.length,N=A>1)+h+t+b+N.slice(A);break;default:t=N+h+t+b}return a(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),b.toString=function(){return t+""},b}return{format:f,formatPrefix:function(t,n){var e=f(((t=Ir(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Lr(n)/3))),i=Math.pow(10,-r),o=Gr[8+r/3];return function(t){return e(i*t)+o}}}}function Kr(t,n,e,r){var i,o=We(t,n,e);switch((r=Ir(null==r?",f":r)).type){case"s":var u=Math.max(Math.abs(t),Math.abs(n));return null!=r.precision||isNaN(i=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Lr(n)/3)))-Lr(Math.abs(t)))}(o,u))||(r.precision=i),Wr(r,u);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Lr(n)-Lr(t))+1}(o,Math.max(Math.abs(t),Math.abs(n))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=function(t){return Math.max(0,-Lr(Math.abs(t)))}(o))||(r.precision=i-2*("%"===r.type))}return Br(r)}function ti(t){var n=t.domain;return t.ticks=function(t){var e=n();return Ze(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return Kr(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i,o=n(),u=0,a=o.length-1,l=o[u],c=o[a],s=10;for(c0;){if((i=Be(l,c,e))===r)return o[u]=l,o[a]=c,n(o);if(i>0)l=Math.floor(l/i)*i,c=Math.ceil(c/i)*i;else{if(!(i<0))break;l=Math.ceil(l*i)/i,c=Math.floor(c*i)/i}r=i}return t},t}function ni(){var t=Hr();return t.copy=function(){return qr(t,ni())},nr.apply(t,arguments),ti(t)}function ei(t){var n;function e(t){return null==t||isNaN(t=+t)?n:t}return e.invert=e,e.domain=e.range=function(n){return arguments.length?(t=Array.from(n,kr),e):t.slice()},e.unknown=function(t){return arguments.length?(n=t,e):n},e.copy=function(){return ei(t).unknown(n)},t=arguments.length?Array.from(t,kr):[0,1],ti(e)}function ri(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],u=t[i];return u-t(-n,e)}function si(t){const n=t(ii,oi),e=n.domain;let r,i,o=10;function u(){return r=function(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),n=>Math.log(n)/t)}(o),i=function(t){return 10===t?li:t===Math.E?Math.exp:n=>Math.pow(t,n)}(o),e()[0]<0?(r=ci(r),i=ci(i),t(ui,ai)):t(ii,oi),n}return n.base=function(t){return arguments.length?(o=+t,u()):o},n.domain=function(t){return arguments.length?(e(t),u()):e()},n.ticks=t=>{const n=e();let u=n[0],a=n[n.length-1];const l=a0){for(;f<=h;++f)for(c=1;ca)break;p.push(s)}}else for(;f<=h;++f)for(c=o-1;c>=1;--c)if(s=f>0?c/i(-f):c*i(f),!(sa)break;p.push(s)}2*p.length{if(null==t&&(t=10),null==e&&(e=10===o?"s":","),"function"!=typeof e&&(o%1||null!=(e=Ir(e)).precision||(e.trim=!0),e=Br(e)),t===1/0)return e;const u=Math.max(1,o*t/n.ticks().length);return t=>{let n=t/i(Math.round(r(t)));return n*oe(ri(e(),{floor:t=>i(Math.floor(r(t))),ceil:t=>i(Math.ceil(r(t)))})),n}function fi(){const t=si(Yr()).domain([1,10]);return t.copy=()=>qr(t,fi()).base(t.base()),nr.apply(t,arguments),t}function hi(t){return function(n){return Math.sign(n)*Math.log1p(Math.abs(n/t))}}function gi(t){return function(n){return Math.sign(n)*Math.expm1(Math.abs(n))*t}}function pi(t){var n=1,e=t(hi(n),gi(n));return e.constant=function(e){return arguments.length?t(hi(n=+e),gi(n)):n},ti(e)}function di(){var t=pi(Yr());return t.copy=function(){return qr(t,di()).constant(t.constant())},nr.apply(t,arguments)}function yi(t){return function(n){return n<0?-Math.pow(-n,t):Math.pow(n,t)}}function vi(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function mi(t){return t<0?-t*t:t*t}function wi(t){var n=t(Dr,Dr),e=1;return n.exponent=function(n){return arguments.length?1===(e=+n)?t(Dr,Dr):.5===e?t(vi,mi):t(yi(e),yi(1/e)):e},ti(n)}function _i(){var t=wi(Yr());return t.copy=function(){return qr(t,_i()).exponent(t.exponent())},nr.apply(t,arguments),t}function Mi(){return _i.apply(null,arguments).exponent(.5)}function xi(t){return Math.sign(t)*t*t}function bi(){var t,n=Hr(),e=[0,1],r=!1;function i(e){var i=function(t){return Math.sign(t)*Math.sqrt(Math.abs(t))}(n(e));return isNaN(i)?t:r?Math.round(i):i}return i.invert=function(t){return n.invert(xi(t))},i.domain=function(t){return arguments.length?(n.domain(t),i):n.domain()},i.range=function(t){return arguments.length?(n.range((e=Array.from(t,kr)).map(xi)),i):e.slice()},i.rangeRound=function(t){return i.range(t).round(!0)},i.round=function(t){return arguments.length?(r=!!t,i):r},i.clamp=function(t){return arguments.length?(n.clamp(t),i):n.clamp()},i.unknown=function(n){return arguments.length?(t=n,i):t},i.copy=function(){return bi(n.domain(),e).round(r).clamp(n.clamp()).unknown(t)},nr.apply(i,arguments),ti(i)}function Ti(){var t,n=[],e=[],r=[];function i(){var t=0,i=Math.max(1,e.length);for(r=new Array(i-1);++t0?r[i-1]:n[0],i=r?[i[r-1],e]:[i[u-1],i[u]]},u.unknown=function(n){return arguments.length?(t=n,u):u},u.thresholds=function(){return i.slice()},u.copy=function(){return Ai().domain([n,e]).range(o).unknown(t)},nr.apply(ti(u),arguments)}function Ni(){var t,n=[.5],e=[0,1],r=1;function i(i){return null!=i&&i<=i?e[Le(n,i,0,r)]:t}return i.domain=function(t){return arguments.length?(n=Array.from(t),r=Math.min(n.length,e.length-1),i):n.slice()},i.range=function(t){return arguments.length?(e=Array.from(t),r=Math.min(n.length,e.length-1),i):e.slice()},i.invertExtent=function(t){var r=e.indexOf(t);return[n[r-1],n[r]]},i.unknown=function(n){return arguments.length?(t=n,i):t},i.copy=function(){return Ni().domain(n).range(e).unknown(t)},nr.apply(i,arguments)}Zr=Jr({thousands:",",grouping:[3],currency:["$",""]}),Br=Zr.format,Wr=Zr.formatPrefix;const Ci=new Date,$i=new Date;function ki(t,n,e,r){function i(n){return t(n=0===arguments.length?new Date:new Date(+n)),n}return i.floor=n=>(t(n=new Date(+n)),n),i.ceil=e=>(t(e=new Date(e-1)),n(e,1),t(e),e),i.round=t=>{const n=i(t),e=i.ceil(t);return t-n(n(t=new Date(+t),null==e?1:Math.floor(e)),t),i.range=(e,r,o)=>{const u=[];if(e=i.ceil(e),o=null==o?1:Math.floor(o),!(e0))return u;let a;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(aki((n=>{if(n>=n)for(;t(n),!e(n);)n.setTime(n-1)}),((t,r)=>{if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})),e&&(i.count=(n,r)=>(Ci.setTime(+n),$i.setTime(+r),t(Ci),t($i),Math.floor(e(Ci,$i))),i.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?n=>r(n)%t==0:n=>i.count(0,n)%t==0):i:null)),i}const Si=ki((()=>{}),((t,n)=>{t.setTime(+t+n)}),((t,n)=>n-t));Si.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?ki((n=>{n.setTime(Math.floor(n/t)*t)}),((n,e)=>{n.setTime(+n+e*t)}),((n,e)=>(e-n)/t)):Si:null),Si.range;const Di=1e3,Ui=6e4,Ei=36e5,Fi=864e5,qi=6048e5,Yi=2592e6,Hi=31536e6,Pi=ki((t=>{t.setTime(t-t.getMilliseconds())}),((t,n)=>{t.setTime(+t+n*Di)}),((t,n)=>(n-t)/Di),(t=>t.getUTCSeconds()));Pi.range;const Li=ki((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Di)}),((t,n)=>{t.setTime(+t+n*Ui)}),((t,n)=>(n-t)/Ui),(t=>t.getMinutes()));Li.range;const Oi=ki((t=>{t.setUTCSeconds(0,0)}),((t,n)=>{t.setTime(+t+n*Ui)}),((t,n)=>(n-t)/Ui),(t=>t.getUTCMinutes()));Oi.range;const ji=ki((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Di-t.getMinutes()*Ui)}),((t,n)=>{t.setTime(+t+n*Ei)}),((t,n)=>(n-t)/Ei),(t=>t.getHours()));ji.range;const Ii=ki((t=>{t.setUTCMinutes(0,0,0)}),((t,n)=>{t.setTime(+t+n*Ei)}),((t,n)=>(n-t)/Ei),(t=>t.getUTCHours()));Ii.range;const Ri=ki((t=>t.setHours(0,0,0,0)),((t,n)=>t.setDate(t.getDate()+n)),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Ui)/Fi),(t=>t.getDate()-1));Ri.range;const Xi=ki((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/Fi),(t=>t.getUTCDate()-1));Xi.range;const zi=ki((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/Fi),(t=>Math.floor(t/Fi)));function Vi(t){return ki((n=>{n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)}),((t,n)=>{t.setDate(t.getDate()+7*n)}),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Ui)/qi))}zi.range;const Zi=Vi(0),Bi=Vi(1),Wi=Vi(2),Qi=Vi(3),Gi=Vi(4),Ji=Vi(5),Ki=Vi(6);function to(t){return ki((n=>{n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+7*n)}),((t,n)=>(n-t)/qi))}Zi.range,Bi.range,Wi.range,Qi.range,Gi.range,Ji.range,Ki.range;const no=to(0),eo=to(1),ro=to(2),io=to(3),oo=to(4),uo=to(5),ao=to(6);no.range,eo.range,ro.range,io.range,oo.range,uo.range,ao.range;const lo=ki((t=>{t.setDate(1),t.setHours(0,0,0,0)}),((t,n)=>{t.setMonth(t.getMonth()+n)}),((t,n)=>n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())),(t=>t.getMonth()));lo.range;const co=ki((t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCMonth(t.getUTCMonth()+n)}),((t,n)=>n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())),(t=>t.getUTCMonth()));co.range;const so=ki((t=>{t.setMonth(0,1),t.setHours(0,0,0,0)}),((t,n)=>{t.setFullYear(t.getFullYear()+n)}),((t,n)=>n.getFullYear()-t.getFullYear()),(t=>t.getFullYear()));so.every=t=>isFinite(t=Math.floor(t))&&t>0?ki((n=>{n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)}),((n,e)=>{n.setFullYear(n.getFullYear()+e*t)})):null,so.range;const fo=ki((t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCFullYear(t.getUTCFullYear()+n)}),((t,n)=>n.getUTCFullYear()-t.getUTCFullYear()),(t=>t.getUTCFullYear()));function ho(t,n,e,r,i,o){const u=[[Pi,1,Di],[Pi,5,5e3],[Pi,15,15e3],[Pi,30,3e4],[o,1,Ui],[o,5,3e5],[o,15,9e5],[o,30,18e5],[i,1,Ei],[i,3,108e5],[i,6,216e5],[i,12,432e5],[r,1,Fi],[r,2,1728e5],[e,1,qi],[n,1,Yi],[n,3,7776e6],[t,1,Hi]];function a(n,e,r){const i=Math.abs(e-n)/r,o=qe((([,,t])=>t)).right(u,i);if(o===u.length)return t.every(We(n/Hi,e/Hi,r));if(0===o)return Si.every(Math.max(We(n,e,r),1));const[a,l]=u[i/u[o-1][2]isFinite(t=Math.floor(t))&&t>0?ki((n=>{n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)}),((n,e)=>{n.setUTCFullYear(n.getUTCFullYear()+e*t)})):null,fo.range;const[go,po]=ho(fo,co,no,zi,Ii,Oi),[yo,vo]=ho(so,lo,Zi,Ri,ji,Li);function mo(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function wo(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function _o(t,n,e){return{y:t,m:n,d:e,H:0,M:0,S:0,L:0}}function Mo(t){var n=t.dateTime,e=t.date,r=t.time,i=t.periods,o=t.days,u=t.shortDays,a=t.months,l=t.shortMonths,c=Eo(i),s=Fo(i),f=Eo(o),h=Fo(o),g=Eo(u),p=Fo(u),d=Eo(a),y=Fo(a),v=Eo(l),m=Fo(l),w={a:function(t){return u[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return l[t.getMonth()]},B:function(t){return a[t.getMonth()]},c:null,d:nu,e:nu,f:uu,g:vu,G:wu,H:eu,I:ru,j:iu,L:ou,m:au,M:lu,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:Iu,s:Ru,S:cu,u:su,U:fu,V:gu,w:pu,W:du,x:null,X:null,y:yu,Y:mu,Z:_u,"%":ju},_={a:function(t){return u[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return l[t.getUTCMonth()]},B:function(t){return a[t.getUTCMonth()]},c:null,d:Mu,e:Mu,f:Nu,g:Hu,G:Lu,H:xu,I:bu,j:Tu,L:Au,m:Cu,M:$u,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:Iu,s:Ru,S:ku,u:Su,U:Du,V:Eu,w:Fu,W:qu,x:null,X:null,y:Yu,Y:Pu,Z:Ou,"%":ju},M={a:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.w=p.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.w=h.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=m.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.m=y.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(t,e,r){return T(t,n,e,r)},d:zo,e:zo,f:Go,g:jo,G:Oo,H:Zo,I:Zo,j:Vo,L:Qo,m:Xo,M:Bo,p:function(t,n,e){var r=c.exec(n.slice(e));return r?(t.p=s.get(r[0].toLowerCase()),e+r[0].length):-1},q:Ro,Q:Ko,s:tu,S:Wo,u:Yo,U:Ho,V:Po,w:qo,W:Lo,x:function(t,n,r){return T(t,e,n,r)},X:function(t,n,e){return T(t,r,n,e)},y:jo,Y:Oo,Z:Io,"%":Jo};function x(t,n){return function(e){var r,i,o,u=[],a=-1,l=0,c=t.length;for(e instanceof Date||(e=new Date(+e));++a53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=wo(_o(o.y,0,1))).getUTCDay(),r=i>4||0===i?eo.ceil(r):eo(r),r=Xi.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=mo(_o(o.y,0,1))).getDay(),r=i>4||0===i?Bi.ceil(r):Bi(r),r=Ri.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?wo(_o(o.y,0,1)).getUTCDay():mo(_o(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,wo(o)):mo(o)}}function T(t,n,e,r){for(var i,o,u=0,a=n.length,l=e.length;u=l)return-1;if(37===(i=n.charCodeAt(u++))){if(i=n.charAt(u++),!(o=M[i in Co?n.charAt(u++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return w.x=x(e,w),w.X=x(r,w),w.c=x(n,w),_.x=x(e,_),_.X=x(r,_),_.c=x(n,_),{format:function(t){var n=x(t+="",w);return n.toString=function(){return t},n},parse:function(t){var n=b(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=x(t+="",_);return n.toString=function(){return t},n},utcParse:function(t){var n=b(t+="",!0);return n.toString=function(){return t},n}}}var xo,bo,To,Ao,No,Co={"-":"",_:" ",0:"0"},$o=/^\s*\d+/,ko=/^%/,So=/[\\^$*+?|[\]().{}]/g;function Do(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o[t.toLowerCase(),n])))}function qo(t,n,e){var r=$o.exec(n.slice(e,e+1));return r?(t.w=+r[0],e+r[0].length):-1}function Yo(t,n,e){var r=$o.exec(n.slice(e,e+1));return r?(t.u=+r[0],e+r[0].length):-1}function Ho(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.U=+r[0],e+r[0].length):-1}function Po(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.V=+r[0],e+r[0].length):-1}function Lo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.W=+r[0],e+r[0].length):-1}function Oo(t,n,e){var r=$o.exec(n.slice(e,e+4));return r?(t.y=+r[0],e+r[0].length):-1}function jo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),e+r[0].length):-1}function Io(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function Ro(t,n,e){var r=$o.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function Xo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function zo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function Vo(t,n,e){var r=$o.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function Zo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Bo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Wo(t,n,e){var r=$o.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Qo(t,n,e){var r=$o.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Go(t,n,e){var r=$o.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Jo(t,n,e){var r=ko.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Ko(t,n,e){var r=$o.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function tu(t,n,e){var r=$o.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function nu(t,n){return Do(t.getDate(),n,2)}function eu(t,n){return Do(t.getHours(),n,2)}function ru(t,n){return Do(t.getHours()%12||12,n,2)}function iu(t,n){return Do(1+Ri.count(so(t),t),n,3)}function ou(t,n){return Do(t.getMilliseconds(),n,3)}function uu(t,n){return ou(t,n)+"000"}function au(t,n){return Do(t.getMonth()+1,n,2)}function lu(t,n){return Do(t.getMinutes(),n,2)}function cu(t,n){return Do(t.getSeconds(),n,2)}function su(t){var n=t.getDay();return 0===n?7:n}function fu(t,n){return Do(Zi.count(so(t)-1,t),n,2)}function hu(t){var n=t.getDay();return n>=4||0===n?Gi(t):Gi.ceil(t)}function gu(t,n){return t=hu(t),Do(Gi.count(so(t),t)+(4===so(t).getDay()),n,2)}function pu(t){return t.getDay()}function du(t,n){return Do(Bi.count(so(t)-1,t),n,2)}function yu(t,n){return Do(t.getFullYear()%100,n,2)}function vu(t,n){return Do((t=hu(t)).getFullYear()%100,n,2)}function mu(t,n){return Do(t.getFullYear()%1e4,n,4)}function wu(t,n){var e=t.getDay();return Do((t=e>=4||0===e?Gi(t):Gi.ceil(t)).getFullYear()%1e4,n,4)}function _u(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+Do(n/60|0,"0",2)+Do(n%60,"0",2)}function Mu(t,n){return Do(t.getUTCDate(),n,2)}function xu(t,n){return Do(t.getUTCHours(),n,2)}function bu(t,n){return Do(t.getUTCHours()%12||12,n,2)}function Tu(t,n){return Do(1+Xi.count(fo(t),t),n,3)}function Au(t,n){return Do(t.getUTCMilliseconds(),n,3)}function Nu(t,n){return Au(t,n)+"000"}function Cu(t,n){return Do(t.getUTCMonth()+1,n,2)}function $u(t,n){return Do(t.getUTCMinutes(),n,2)}function ku(t,n){return Do(t.getUTCSeconds(),n,2)}function Su(t){var n=t.getUTCDay();return 0===n?7:n}function Du(t,n){return Do(no.count(fo(t)-1,t),n,2)}function Uu(t){var n=t.getUTCDay();return n>=4||0===n?oo(t):oo.ceil(t)}function Eu(t,n){return t=Uu(t),Do(oo.count(fo(t),t)+(4===fo(t).getUTCDay()),n,2)}function Fu(t){return t.getUTCDay()}function qu(t,n){return Do(eo.count(fo(t)-1,t),n,2)}function Yu(t,n){return Do(t.getUTCFullYear()%100,n,2)}function Hu(t,n){return Do((t=Uu(t)).getUTCFullYear()%100,n,2)}function Pu(t,n){return Do(t.getUTCFullYear()%1e4,n,4)}function Lu(t,n){var e=t.getUTCDay();return Do((t=e>=4||0===e?oo(t):oo.ceil(t)).getUTCFullYear()%1e4,n,4)}function Ou(){return"+0000"}function ju(){return"%"}function Iu(t){return+t}function Ru(t){return Math.floor(+t/1e3)}function Xu(t){return xo=Mo(t),bo=xo.format,To=xo.parse,Ao=xo.utcFormat,No=xo.utcParse,xo}Xu({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var zu="%Y-%m-%dT%H:%M:%S.%LZ";var Vu=Date.prototype.toISOString?function(t){return t.toISOString()}:Ao(zu),Zu=Vu;var Bu=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:No(zu),Wu=Bu;function Qu(t){return new Date(t)}function Gu(t){return t instanceof Date?+t:+new Date(+t)}function Ju(t,n,e,r,i,o,u,a,l,c){var s=Hr(),f=s.invert,h=s.domain,g=c(".%L"),p=c(":%S"),d=c("%I:%M"),y=c("%I %p"),v=c("%a %d"),m=c("%b %d"),w=c("%B"),_=c("%Y");function M(t){return(l(t)n(r/(t.length-1))))},e.quantiles=function(n){return Array.from({length:n+1},((e,r)=>function(t,n,e){if(t=Float64Array.from(function*(t,n){if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(yield n);else{let e=-1;for(let r of t)null!=(r=n(r,++e,t))&&(r=+r)>=r&&(yield r)}}(t,e)),(r=t.length)&&!isNaN(n=+n)){if(n<=0||r<2)return Ge(t);if(n>=1)return Qe(t);var r,i=(r-1)*n,o=Math.floor(i),u=Qe(Je(t,o).subarray(0,o+1));return u+(Ge(t.subarray(o+1))-u)*(i-o)}}(t,r/n)))},e.copy=function(){return la(n).domain(t)},er.apply(e,arguments)}function ca(){var t,n,e,r,i,o,u,a=0,l=.5,c=1,s=1,f=Dr,h=!1;function g(t){return isNaN(t=+t)?u:(t=.5+((t=+o(t))-n)*(s*t=1?Aa:t<=-1?-Aa:Math.asin(t)}function $a(t){return t.innerRadius}function ka(t){return t.outerRadius}function Sa(t){return t.startAngle}function Da(t){return t.endAngle}function Ua(t){return t&&t.padAngle}function Ea(t,n,e,r,i,o,u){var a=t-e,l=n-r,c=(u?o:-o)/xa(a*a+l*l),s=c*l,f=-c*a,h=t+s,g=n+f,p=e+s,d=r+f,y=(h+p)/2,v=(g+d)/2,m=p-h,w=d-g,_=m*m+w*w,M=i-o,x=h*d-p*g,b=(w<0?-1:1)*xa(wa(0,M*M*_-x*x)),T=(x*w-m*b)/_,A=(-x*m-w*b)/_,N=(x*w+m*b)/_,C=(-x*m+w*b)/_,$=T-y,k=A-v,S=N-y,D=C-v;return $*$+k*k>S*S+D*D&&(T=N,A=C),{cx:T,cy:A,x01:-s,y01:-f,x11:T*(i/M-1),y11:A*(i/M-1)}}function Fa(){var t=$a,n=ka,e=da(0),r=null,i=Sa,o=Da,u=Ua,a=null,l=function(t){let n=3;return t.digits=function(e){if(!arguments.length)return n;if(null==e)n=null;else{const t=Math.floor(e);if(!(t>=0))throw new RangeError(`invalid digits: ${e}`);n=t}return t},()=>new Et(n)}(c);function c(){var c,s,f,h=+t.apply(this,arguments),g=+n.apply(this,arguments),p=i.apply(this,arguments)-Aa,d=o.apply(this,arguments)-Aa,y=ya(d-p),v=d>p;if(a||(a=c=l()),gba)if(y>Na-ba)a.moveTo(g*ma(p),g*Ma(p)),a.arc(0,0,g,p,d,!v),h>ba&&(a.moveTo(h*ma(d),h*Ma(d)),a.arc(0,0,h,d,p,v));else{var m,w,_=p,M=d,x=p,b=d,T=y,A=y,N=u.apply(this,arguments)/2,C=N>ba&&(r?+r.apply(this,arguments):xa(h*h+g*g)),$=_a(ya(g-h)/2,+e.apply(this,arguments)),k=$,S=$;if(C>ba){var D=Ca(C/h*Ma(N)),U=Ca(C/g*Ma(N));(T-=2*D)>ba?(x+=D*=v?1:-1,b-=D):(T=0,x=b=(p+d)/2),(A-=2*U)>ba?(_+=U*=v?1:-1,M-=U):(A=0,_=M=(p+d)/2)}var E=g*ma(_),F=g*Ma(_),q=h*ma(b),Y=h*Ma(b);if($>ba){var H,P=g*ma(M),L=g*Ma(M),O=h*ma(x),j=h*Ma(x);if(y1?0:f<-1?Ta:Math.acos(f))/2),Z=xa(H[0]*H[0]+H[1]*H[1]);k=_a($,(h-Z)/(V-1)),S=_a($,(g-Z)/(V+1))}else k=S=0}A>ba?S>ba?(m=Ea(O,j,E,F,g,S,v),w=Ea(P,L,q,Y,g,S,v),a.moveTo(m.cx+m.x01,m.cy+m.y01),S<$?a.arc(m.cx,m.cy,S,va(m.y01,m.x01),va(w.y01,w.x01),!v):(a.arc(m.cx,m.cy,S,va(m.y01,m.x01),va(m.y11,m.x11),!v),a.arc(0,0,g,va(m.cy+m.y11,m.cx+m.x11),va(w.cy+w.y11,w.cx+w.x11),!v),a.arc(w.cx,w.cy,S,va(w.y11,w.x11),va(w.y01,w.x01),!v))):(a.moveTo(E,F),a.arc(0,0,g,_,M,!v)):a.moveTo(E,F),h>ba&&T>ba?k>ba?(m=Ea(q,Y,P,L,h,-k,v),w=Ea(E,F,O,j,h,-k,v),a.lineTo(m.cx+m.x01,m.cy+m.y01),k<$?a.arc(m.cx,m.cy,k,va(m.y01,m.x01),va(w.y01,w.x01),!v):(a.arc(m.cx,m.cy,k,va(m.y01,m.x01),va(m.y11,m.x11),!v),a.arc(0,0,h,va(m.cy+m.y11,m.cx+m.x11),va(w.cy+w.y11,w.cx+w.x11),v),a.arc(w.cx,w.cy,k,va(w.y11,w.x11),va(w.y01,w.x01),!v))):a.arc(0,0,h,b,x,v):a.lineTo(q,Y)}else a.moveTo(0,0);if(a.closePath(),c)return a=null,c+""||null}return c.centroid=function(){var e=(+t.apply(this,arguments)+ +n.apply(this,arguments))/2,r=(+i.apply(this,arguments)+ +o.apply(this,arguments))/2-Ta/2;return[ma(r)*e,Ma(r)*e]},c.innerRadius=function(n){return arguments.length?(t="function"==typeof n?n:da(+n),c):t},c.outerRadius=function(t){return arguments.length?(n="function"==typeof t?t:da(+t),c):n},c.cornerRadius=function(t){return arguments.length?(e="function"==typeof t?t:da(+t),c):e},c.padRadius=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:da(+t),c):r},c.startAngle=function(t){return arguments.length?(i="function"==typeof t?t:da(+t),c):i},c.endAngle=function(t){return arguments.length?(o="function"==typeof t?t:da(+t),c):o},c.padAngle=function(t){return arguments.length?(u="function"==typeof t?t:da(+t),c):u},c.context=function(t){return arguments.length?(a=null==t?null:t,c):a},c}var qa,Ya,Ha=0,Pa=0,La=0,Oa=1e3,ja=0,Ia=0,Ra=0,Xa="object"==typeof performance&&performance.now?performance:Date,za="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function Va(){return Ia||(za(Za),Ia=Xa.now()+Ra)}function Za(){Ia=0}function Ba(){this._call=this._time=this._next=null}function Wa(t,n,e){var r=new Ba;return r.restart(t,n,e),r}function Qa(){Ia=(ja=Xa.now())+Ra,Ha=Pa=0;try{!function(){Va(),++Ha;for(var t,n=qa;n;)(t=Ia-n._time)>=0&&n._call.call(void 0,t),n=n._next;--Ha}()}finally{Ha=0,function(){var t,n,e=qa,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:qa=n);Ya=t,Ja(r)}(),Ia=0}}function Ga(){var t=Xa.now(),n=t-ja;n>Oa&&(Ra-=n,ja=t)}function Ja(t){Ha||(Pa&&(Pa=clearTimeout(Pa)),t-Ia>24?(t<1/0&&(Pa=setTimeout(Qa,t-Xa.now()-Ra)),La&&(La=clearInterval(La))):(La||(ja=Xa.now(),La=setInterval(Ga,Oa)),Ha=1,za(Qa)))}function Ka(t,n,e){var r=new Ba;return n=null==n?0:+n,r.restart((e=>{r.stop(),t(e+n)}),n,e),r}Ba.prototype=Wa.prototype={constructor:Ba,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?Va():+e)+(null==n?0:+n),this._next||Ya===this||(Ya?Ya._next=this:qa=this,Ya=this),this._call=t,this._time=e,Ja()},stop:function(){this._call&&(this._call=null,this._time=1/0,Ja())}};var tl=Bt("start","end","cancel","interrupt"),nl=[],el=0,rl=1,il=2,ol=3,ul=4,al=5,ll=6;function cl(t,n,e,r,i,o){var u=t.__transition;if(u){if(e in u)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=rl,e.timer.restart(u,e.delay,e.time),e.delay<=t&&u(t-e.delay)}function u(o){var c,s,f,h;if(e.state!==rl)return l();for(c in i)if((h=i[c]).name===e.name){if(h.state===ol)return Ka(u);h.state===ul?(h.state=ll,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[c]):+cel)throw new Error("too late; already scheduled");return e}function fl(t,n){var e=hl(t,n);if(e.state>ol)throw new Error("too late; already running");return e}function hl(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function gl(t,n){var e,r,i,o=t.__transition,u=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>il&&e.state=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?sl:fl;return function(){var u=o(this,t),a=u.on;a!==r&&(i=(r=a).copy()).on(n,e),u.on=i}}(e,t,n))},attr:function(t,n){var e=tn(t),r="transform"===e?$r:vl;return this.attrTween(t,"function"==typeof n?(e.local?bl:xl)(e,r,yl(this,"attr."+t,n)):null==n?(e.local?wl:ml)(e):(e.local?Ml:_l)(e,r,n))},attrTween:function(t,n){var e="attr."+t;if(arguments.length<2)return(e=this.tween(e))&&e._value;if(null==n)return this.tween(e,null);if("function"!=typeof n)throw new Error;var r=tn(t);return this.tween(e,(r.local?Tl:Al)(r,n))},style:function(t,n,e){var r="transform"==(t+="")?Cr:vl;return null==n?this.styleTween(t,function(t,n){var e,r,i;return function(){var o=En(this,t),u=(this.style.removeProperty(t),En(this,t));return o===u?null:o===e&&u===r?i:i=n(e=o,r=u)}}(t,r)).on("end.style."+t,Dl(t)):"function"==typeof n?this.styleTween(t,function(t,n,e){var r,i,o;return function(){var u=En(this,t),a=e(this),l=a+"";return null==a&&(this.style.removeProperty(t),l=a=En(this,t)),u===l?null:u===r&&l===i?o:(i=l,o=n(r=u,a))}}(t,r,yl(this,"style."+t,n))).each(function(t,n){var e,r,i,o,u="style."+n,a="end."+u;return function(){var l=fl(this,t),c=l.on,s=null==l.value[u]?o||(o=Dl(n)):void 0;c===e&&i===s||(r=(e=c).copy()).on(a,i=s),l.on=r}}(this._id,t)):this.styleTween(t,function(t,n,e){var r,i,o=e+"";return function(){var u=En(this,t);return u===o?null:u===r?i:i=n(r=u,e)}}(t,r,n),e).on("end.style."+t,null)},styleTween:function(t,n,e){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==n)return this.tween(r,null);if("function"!=typeof n)throw new Error;return this.tween(r,function(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}(t,o,e)),r}return o._value=n,o}(t,n,null==e?"":e))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var n=t(this);this.textContent=null==n?"":n}}(yl(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var n="text";if(arguments.length<1)return(n=this.tween(n))&&n._value;if(null==t)return this.tween(n,null);if("function"!=typeof t)throw new Error;return this.tween(n,function(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&function(t){return function(n){this.textContent=t.call(this,n)}}(r)),n}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}(this._id))},tween:function(t,n){var e=this._id;if(t+="",arguments.length<2){for(var r,i=hl(this.node(),e).tween,o=0,u=i.length;orl&&e.name===n)return new El([[t]],Ll,n,+r);return null}export{Ol as active,Fa as arc,Tt as chord,Nt as chordDirected,At as chordTranspose,w as color,he as create,rn as creator,gt as cubehelix,Ue as drag,Te as dragDisable,Ae as dragEnable,Z as gray,et as hcl,U as hsl,gl as interrupt,Zu as isoFormat,Wu as isoParse,B as lab,nt as lch,pe as local,sn as matcher,tn as namespace,Kt as namespaces,ve as pointer,me as pointers,b as rgb,zt as ribbon,Vt as ribbonArrow,or as scaleBand,sa as scaleDiverging,fa as scaleDivergingLog,ga as scaleDivergingPow,pa as scaleDivergingSqrt,ha as scaleDivergingSymlog,ei as scaleIdentity,rr as scaleImplicit,ni as scaleLinear,fi as scaleLog,ir as scaleOrdinal,ar as scalePoint,_i as scalePow,Ti as scaleQuantile,Ai as scaleQuantize,bi as scaleRadial,ra as scaleSequential,ia as scaleSequentialLog,ua as scaleSequentialPow,la as scaleSequentialQuantile,aa as scaleSequentialSqrt,oa as scaleSequentialSymlog,Mi as scaleSqrt,di as scaleSymlog,Ni as scaleThreshold,Ku as scaleTime,ta as scaleUtc,fe as select,we as selectAll,se as selection,un as selector,cn as selectorAll,En as style,Kr as tickFormat,bo as timeFormat,Xu as timeFormatDefaultLocale,Mo as timeFormatLocale,To as timeParse,Fl as transition,Ao as utcFormat,No as utcParse,t as version,kn as window}; +var t="7.9.0";function n(t,n,e){t.prototype=n.prototype=e,e.constructor=t}function e(t,n){var e=Object.create(t.prototype);for(var r in n)e[r]=n[r];return e}function r(){}var i=.7,o=1/i,u="\\s*([+-]?\\d+)\\s*",a="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*",l="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*",c=/^#([0-9a-f]{3,8})$/,s=new RegExp(`^rgb\\(${u},${u},${u}\\)$`),f=new RegExp(`^rgb\\(${l},${l},${l}\\)$`),h=new RegExp(`^rgba\\(${u},${u},${u},${a}\\)$`),g=new RegExp(`^rgba\\(${l},${l},${l},${a}\\)$`),p=new RegExp(`^hsl\\(${a},${l},${l}\\)$`),d=new RegExp(`^hsla\\(${a},${l},${l},${a}\\)$`),y={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function v(){return this.rgb().formatHex()}function m(){return this.rgb().formatRgb()}function w(t){var n,e;return t=(t+"").trim().toLowerCase(),(n=c.exec(t))?(e=n[1].length,n=parseInt(n[1],16),6===e?_(n):3===e?new T(n>>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?M(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?M(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=s.exec(t))?new T(n[1],n[2],n[3],1):(n=f.exec(t))?new T(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=h.exec(t))?M(n[1],n[2],n[3],n[4]):(n=g.exec(t))?M(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=p.exec(t))?S(n[1],n[2]/100,n[3]/100,1):(n=d.exec(t))?S(n[1],n[2]/100,n[3]/100,n[4]):y.hasOwnProperty(t)?_(y[t]):"transparent"===t?new T(NaN,NaN,NaN,0):null}function _(t){return new T(t>>16&255,t>>8&255,255&t,1)}function M(t,n,e,r){return r<=0&&(t=n=e=NaN),new T(t,n,e,r)}function x(t){return t instanceof r||(t=w(t)),t?new T((t=t.rgb()).r,t.g,t.b,t.opacity):new T}function b(t,n,e,r){return 1===arguments.length?x(t):new T(t,n,e,null==r?1:r)}function T(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function A(){return`#${k(this.r)}${k(this.g)}${k(this.b)}`}function N(){const t=C(this.opacity);return`${1===t?"rgb(":"rgba("}${$(this.r)}, ${$(this.g)}, ${$(this.b)}${1===t?")":`, ${t})`}`}function C(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function $(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function k(t){return((t=$(t))<16?"0":"")+t.toString(16)}function S(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new E(t,n,e,r)}function D(t){if(t instanceof E)return new E(t.h,t.s,t.l,t.opacity);if(t instanceof r||(t=w(t)),!t)return new E;if(t instanceof E)return t;var n=(t=t.rgb()).r/255,e=t.g/255,i=t.b/255,o=Math.min(n,e,i),u=Math.max(n,e,i),a=NaN,l=u-o,c=(u+o)/2;return l?(a=n===u?(e-i)/l+6*(e0&&c<1?0:a,new E(a,l,c,t.opacity)}function U(t,n,e,r){return 1===arguments.length?D(t):new E(t,n,e,null==r?1:r)}function E(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function F(t){return(t=(t||0)%360)<0?t+360:t}function q(t){return Math.max(0,Math.min(1,t||0))}function Y(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}n(r,w,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:v,formatHex:v,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return D(this).formatHsl()},formatRgb:m,toString:m}),n(T,b,e(r,{brighter(t){return t=null==t?o:Math.pow(o,t),new T(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=null==t?i:Math.pow(i,t),new T(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new T($(this.r),$(this.g),$(this.b),C(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:A,formatHex:A,formatHex8:function(){return`#${k(this.r)}${k(this.g)}${k(this.b)}${k(255*(isNaN(this.opacity)?1:this.opacity))}`},formatRgb:N,toString:N})),n(E,U,e(r,{brighter(t){return t=null==t?o:Math.pow(o,t),new E(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=null==t?i:Math.pow(i,t),new E(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new T(Y(t>=240?t-240:t+120,i,r),Y(t,i,r),Y(t<120?t+240:t-120,i,r),this.opacity)},clamp(){return new E(F(this.h),q(this.s),q(this.l),C(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=C(this.opacity);return`${1===t?"hsl(":"hsla("}${F(this.h)}, ${100*q(this.s)}%, ${100*q(this.l)}%${1===t?")":`, ${t})`}`}}));const H=Math.PI/180,P=180/Math.PI,L=.96422,O=.82521,j=4/29,I=6/29,R=3*I*I,X=I*I*I;function z(t){if(t instanceof B)return new B(t.l,t.a,t.b,t.opacity);if(t instanceof et)return rt(t);t instanceof T||(t=x(t));var n,e,r=J(t.r),i=J(t.g),o=J(t.b),u=W((.2225045*r+.7168786*i+.0606169*o)/1);return r===i&&i===o?n=e=u:(n=W((.4360747*r+.3850649*i+.1430804*o)/L),e=W((.0139322*r+.0971045*i+.7141733*o)/O)),new B(116*u-16,500*(n-u),200*(u-e),t.opacity)}function V(t,n){return new B(t,0,0,null==n?1:n)}function Z(t,n,e,r){return 1===arguments.length?z(t):new B(t,n,e,null==r?1:r)}function B(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function W(t){return t>X?Math.pow(t,1/3):t/R+j}function Q(t){return t>I?t*t*t:R*(t-j)}function G(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function J(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function K(t){if(t instanceof et)return new et(t.h,t.c,t.l,t.opacity);if(t instanceof B||(t=z(t)),0===t.a&&0===t.b)return new et(NaN,0t+e))}function bt(){return Nt(!1,!1)}function Tt(){return Nt(!1,!0)}function At(){return Nt(!0,!1)}function Nt(t,n){var e=0,r=null,i=null,o=null;function u(u){var a,l=u.length,c=new Array(l),s=xt(0,l),f=new Array(l*l),h=new Array(l),g=0;u=Float64Array.from({length:l*l},n?(t,n)=>u[n%l][n/l|0]:(t,n)=>u[n/l|0][n%l]);for(let n=0;nr(c[t],c[n])));for(const e of s){const r=n;if(t){const t=xt(1+~l,l).filter((t=>t<0?u[~t*l+e]:u[e*l+t]));i&&t.sort(((t,n)=>i(t<0?-u[~t*l+e]:u[e*l+t],n<0?-u[~n*l+e]:u[e*l+n])));for(const r of t)if(r<0){(f[~r*l+e]||(f[~r*l+e]={source:null,target:null})).target={index:e,startAngle:n,endAngle:n+=u[~r*l+e]*g,value:u[~r*l+e]}}else{(f[e*l+r]||(f[e*l+r]={source:null,target:null})).source={index:e,startAngle:n,endAngle:n+=u[e*l+r]*g,value:u[e*l+r]}}h[e]={index:e,startAngle:r,endAngle:n,value:c[e]}}else{const t=xt(0,l).filter((t=>u[e*l+t]||u[t*l+e]));i&&t.sort(((t,n)=>i(u[e*l+t],u[e*l+n])));for(const r of t){let t;if(e=0))throw new Error(`invalid digits: ${t}`);if(n>15)return Dt;const e=10**n;return function(t){this._+=t[0];for(let n=1,r=t.length;nkt)if(Math.abs(s*a-l*c)>kt&&i){let h=e-o,g=r-u,p=a*a+l*l,d=h*h+g*g,y=Math.sqrt(p),v=Math.sqrt(f),m=i*Math.tan((Ct-Math.acos((p+f-d)/(2*y*v)))/2),w=m/v,_=m/y;Math.abs(w-1)>kt&&this._append`L${t+w*c},${n+w*s}`,this._append`A${i},${i},0,0,${+(s*h>c*g)},${this._x1=t+_*a},${this._y1=n+_*l}`}else this._append`L${this._x1=t},${this._y1=n}`;else;}arc(t,n,e,r,i,o){if(t=+t,n=+n,o=!!o,(e=+e)<0)throw new Error(`negative radius: ${e}`);let u=e*Math.cos(r),a=e*Math.sin(r),l=t+u,c=n+a,s=1^o,f=o?r-i:i-r;null===this._x1?this._append`M${l},${c}`:(Math.abs(this._x1-l)>kt||Math.abs(this._y1-c)>kt)&&this._append`L${l},${c}`,e&&(f<0&&(f=f%$t+$t),f>St?this._append`A${e},${e},0,1,${s},${t-u},${n-a}A${e},${e},0,1,${s},${this._x1=l},${this._y1=c}`:f>kt&&this._append`A${e},${e},0,${+(f>=Ct)},${s},${this._x1=t+e*Math.cos(i)},${this._y1=n+e*Math.sin(i)}`)}rect(t,n,e,r){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${e=+e}v${+r}h${-e}Z`}toString(){return this._}}function Et(){return new Ut}Et.prototype=Ut.prototype;var Ft=Array.prototype.slice;function qt(t){return function(){return t}}function Yt(t){return t.source}function Ht(t){return t.target}function Pt(t){return t.radius}function Lt(t){return t.startAngle}function Ot(t){return t.endAngle}function jt(){return 0}function It(){return 10}function Rt(t){var n=Yt,e=Ht,r=Pt,i=Pt,o=Lt,u=Ot,a=jt,l=null;function c(){var c,s=n.apply(this,arguments),f=e.apply(this,arguments),h=a.apply(this,arguments)/2,g=Ft.call(arguments),p=+r.apply(this,(g[0]=s,g)),d=o.apply(this,g)-mt,y=u.apply(this,g)-mt,v=+i.apply(this,(g[0]=f,g)),m=o.apply(this,g)-mt,w=u.apply(this,g)-mt;if(l||(l=c=Et()),h>Mt&&(pt(y-d)>2*h+Mt?y>d?(d+=h,y-=h):(d-=h,y+=h):d=y=(d+y)/2,pt(w-m)>2*h+Mt?w>m?(m+=h,w-=h):(m-=h,w+=h):m=w=(m+w)/2),l.moveTo(p*dt(d),p*yt(d)),l.arc(0,0,p,d,y),d!==m||y!==w)if(t){var _=v-+t.apply(this,arguments),M=(m+w)/2;l.quadraticCurveTo(0,0,_*dt(m),_*yt(m)),l.lineTo(v*dt(M),v*yt(M)),l.lineTo(_*dt(w),_*yt(w))}else l.quadraticCurveTo(0,0,v*dt(m),v*yt(m)),l.arc(0,0,v,m,w);if(l.quadraticCurveTo(0,0,p*dt(d),p*yt(d)),l.closePath(),c)return l=null,c+""||null}return t&&(c.headRadius=function(n){return arguments.length?(t="function"==typeof n?n:qt(+n),c):t}),c.radius=function(t){return arguments.length?(r=i="function"==typeof t?t:qt(+t),c):r},c.sourceRadius=function(t){return arguments.length?(r="function"==typeof t?t:qt(+t),c):r},c.targetRadius=function(t){return arguments.length?(i="function"==typeof t?t:qt(+t),c):i},c.startAngle=function(t){return arguments.length?(o="function"==typeof t?t:qt(+t),c):o},c.endAngle=function(t){return arguments.length?(u="function"==typeof t?t:qt(+t),c):u},c.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:qt(+t),c):a},c.source=function(t){return arguments.length?(n=t,c):n},c.target=function(t){return arguments.length?(e=t,c):e},c.context=function(t){return arguments.length?(l=null==t?null:t,c):l},c}function Xt(){return Rt()}function zt(){return Rt(It)}var Vt={value:()=>{}};function Zt(){for(var t,n=0,e=arguments.length,r={};n=0&&(n=t.slice(e+1),t=t.slice(0,e)),t&&!r.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))),u=-1,a=o.length;if(!(arguments.length<2)){if(null!=n&&"function"!=typeof n)throw new Error("invalid callback: "+n);for(;++u0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Jt.hasOwnProperty(n)?{space:Jt[n],local:t}:t}function tn(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===Gt&&n.documentElement.namespaceURI===Gt?n.createElement(t):n.createElementNS(e,t)}}function nn(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function en(t){var n=Kt(t);return(n.local?nn:tn)(n)}function rn(){}function on(t){return null==t?rn:function(){return this.querySelector(t)}}function un(t){return null==t?[]:Array.isArray(t)?t:Array.from(t)}function an(){return[]}function ln(t){return null==t?an:function(){return this.querySelectorAll(t)}}function cn(t){return function(){return this.matches(t)}}function sn(t){return function(n){return n.matches(t)}}var fn=Array.prototype.find;function hn(){return this.firstElementChild}var gn=Array.prototype.filter;function pn(){return Array.from(this.children)}function dn(t){return new Array(t.length)}function yn(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function vn(t,n,e,r,i,o){for(var u,a=0,l=n.length,c=o.length;an?1:t>=n?0:NaN}function xn(t){return function(){this.removeAttribute(t)}}function bn(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Tn(t,n){return function(){this.setAttribute(t,n)}}function An(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function Nn(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function Cn(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function $n(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function kn(t){return function(){this.style.removeProperty(t)}}function Sn(t,n,e){return function(){this.style.setProperty(t,n,e)}}function Dn(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function Un(t,n){return t.style.getPropertyValue(n)||$n(t).getComputedStyle(t,null).getPropertyValue(n)}function En(t){return function(){delete this[t]}}function Fn(t,n){return function(){this[t]=n}}function qn(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function Yn(t){return t.trim().split(/^|\s+/)}function Hn(t){return t.classList||new Pn(t)}function Pn(t){this._node=t,this._names=Yn(t.getAttribute("class")||"")}function Ln(t,n){for(var e=Hn(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var ae=[null];function le(t,n){this._groups=t,this._parents=n}function ce(){return new le([[document.documentElement]],ae)}function se(t){return"string"==typeof t?new le([[document.querySelector(t)]],[document.documentElement]):new le([[t]],ae)}function fe(t){return se(en(t).call(document.documentElement))}le.prototype=ce.prototype={constructor:le,select:function(t){"function"!=typeof t&&(t=on(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=M&&(M=_+1);!(w=v[M])&&++M=0;)(r=i[o])&&(u&&4^r.compareDocumentPosition(u)&&u.parentNode.insertBefore(r,u),u=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=Mn);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?kn:"function"==typeof n?Dn:Sn)(t,n,null==e?"":e)):Un(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?En:"function"==typeof n?qn:Fn)(t,n)):this.node()[t]},classed:function(t,n){var e=Yn(t+"");if(arguments.length<2){for(var r=Hn(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}(t+""),u=o.length;if(!(arguments.length<2)){for(a=n?re:ee,r=0;rye(t,n)))}function me(t){return"string"==typeof t?new le([document.querySelectorAll(t)],[document.documentElement]):new le([un(t)],ae)}pe.prototype=ge.prototype={constructor:pe,get:function(t){for(var n=this._;!(n in t);)if(!(t=t.parentNode))return;return t[n]},set:function(t,n){return t[this._]=n},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};const we={passive:!1},_e={capture:!0,passive:!1};function Me(t){t.stopImmediatePropagation()}function xe(t){t.preventDefault(),t.stopImmediatePropagation()}function be(t){var n=t.document.documentElement,e=se(t).on("dragstart.drag",xe,_e);"onselectstart"in n?e.on("selectstart.drag",xe,_e):(n.__noselect=n.style.MozUserSelect,n.style.MozUserSelect="none")}function Te(t,n){var e=t.document.documentElement,r=se(t).on("dragstart.drag",null);n&&(r.on("click.drag",xe,_e),setTimeout((function(){r.on("click.drag",null)}),0)),"onselectstart"in e?r.on("selectstart.drag",null):(e.style.MozUserSelect=e.__noselect,delete e.__noselect)}var Ae=t=>()=>t;function Ne(t,{sourceEvent:n,subject:e,target:r,identifier:i,active:o,x:u,y:a,dx:l,dy:c,dispatch:s}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:u,enumerable:!0,configurable:!0},y:{value:a,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:c,enumerable:!0,configurable:!0},_:{value:s}})}function Ce(t){return!t.ctrlKey&&!t.button}function $e(){return this.parentNode}function ke(t,n){return null==n?{x:t.x,y:t.y}:n}function Se(){return navigator.maxTouchPoints||"ontouchstart"in this}function De(){var t,n,e,r,i=Ce,o=$e,u=ke,a=Se,l={},c=Zt("start","drag","end"),s=0,f=0;function h(t){t.on("mousedown.drag",g).filter(a).on("touchstart.drag",y).on("touchmove.drag",v,we).on("touchend.drag touchcancel.drag",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function g(u,a){if(!r&&i.call(this,u,a)){var l=w(this,o.call(this,u,a),u,a,"mouse");l&&(se(u.view).on("mousemove.drag",p,_e).on("mouseup.drag",d,_e),be(u.view),Me(u),e=!1,t=u.clientX,n=u.clientY,l("start",u))}}function p(r){if(xe(r),!e){var i=r.clientX-t,o=r.clientY-n;e=i*i+o*o>f}l.mouse("drag",r)}function d(t){se(t.view).on("mousemove.drag mouseup.drag",null),Te(t.view,e),xe(t),l.mouse("end",t)}function y(t,n){if(i.call(this,t,n)){var e,r,u=t.changedTouches,a=o.call(this,t,n),l=u.length;for(e=0;en?1:t>=n?0:NaN}function Ee(t,n){return null==t||null==n?NaN:nt?1:n>=t?0:NaN}function Fe(t){let n,e,r;function i(t,r,i=0,o=t.length){if(i>>1;e(t[n],r)<0?i=n+1:o=n}while(iUe(t(n),e),r=(n,e)=>t(n)-e):(n=t===Ue||t===Ee?t:qe,e=t,r=t),{left:i,center:function(t,n,e=0,o=t.length){const u=i(t,n,e,o-1);return u>e&&r(t[u-1],n)>-r(t[u],n)?u-1:u},right:function(t,r,i=0,o=t.length){if(i>>1;e(t[n],r)<=0?i=n+1:o=n}while(i=t))-(null==n||!(n>=n))||(tn?1:0)}const je=Math.sqrt(50),Ie=Math.sqrt(10),Re=Math.sqrt(2);function Xe(t,n,e){const r=(n-t)/Math.max(0,e),i=Math.floor(Math.log10(r)),o=r/Math.pow(10,i),u=o>=je?10:o>=Ie?5:o>=Re?2:1;let a,l,c;return i<0?(c=Math.pow(10,-i)/u,a=Math.round(t*c),l=Math.round(n*c),a/cn&&--l,c=-c):(c=Math.pow(10,i)*u,a=Math.round(t/c),l=Math.round(n/c),a*cn&&--l),l0))return[];if((t=+t)===(n=+n))return[t];const r=n=i))return[];const a=o-i+1,l=new Array(a);if(r)if(u<0)for(let t=0;t=n)&&(e=n);return e}function We(t,n){let e;for(const n of t)null!=n&&(e>n||void 0===e&&n>=n)&&(e=n);return e}function Qe(t,n,e=0,r=1/0,i){if(n=Math.floor(n),e=Math.floor(Math.max(0,e)),r=Math.floor(Math.min(t.length-1,r)),!(e<=n&&n<=r))return t;for(i=void 0===i?Oe:function(t=Ue){if(t===Ue)return Oe;if("function"!=typeof t)throw new TypeError("compare is not a function");return(n,e)=>{const r=t(n,e);return r||0===r?r:(0===t(e,e))-(0===t(n,n))}}(i);r>e;){if(r-e>600){const o=r-e+1,u=n-e+1,a=Math.log(o),l=.5*Math.exp(2*a/3),c=.5*Math.sqrt(a*l*(o-l)/o)*(u-o/2<0?-1:1);Qe(t,n,Math.max(e,Math.floor(n-u*l/o+c)),Math.min(r,Math.floor(n+(o-u)*l/o+c)),i)}const o=t[n];let u=e,a=r;for(Ge(t,e,n),i(t[r],o)>0&&Ge(t,e,r);u0;)--a}0===i(t[e],o)?Ge(t,e,a):(++a,Ge(t,a,r)),a<=n&&(e=a+1),n<=a&&(r=a-1)}return t}function Ge(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function Je(t,n,e=Ye){if((r=t.length)&&!isNaN(n=+n)){if(n<=0||r<2)return+e(t[0],0,t);if(n>=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),u=+e(t[o],o,t);return u+(+e(t[o+1],o+1,t)-u)*(i-o)}}function Ke(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}function tr(t,n){switch(arguments.length){case 0:break;case 1:"function"==typeof t?this.interpolator(t):this.range(t);break;default:this.domain(t),"function"==typeof n?this.interpolator(n):this.range(n)}return this}const nr=Symbol("implicit");function er(){var t=new InternMap,n=[],e=[],r=nr;function i(i){let o=t.get(i);if(void 0===o){if(r!==nr)return r;t.set(i,o=n.push(i)-1)}return e[o%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=new InternMap;for(const r of e)t.has(r)||t.set(r,n.push(r)-1);return i},i.range=function(t){return arguments.length?(e=Array.from(t),i):e.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return er(n,e).unknown(r)},Ke.apply(i,arguments),i}function rr(){var t,n,e=er().unknown(void 0),r=e.domain,i=e.range,o=0,u=1,a=!1,l=0,c=0,s=.5;function f(){var e=r().length,f=u()=>t;function ar(t){return 1==(t=+t)?lr:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):ur(isNaN(n)?e:n)}}function lr(t,n){var e=n-t;return e?function(t,n){return function(e){return t+e*n}}(t,e):ur(isNaN(t)?n:t)}var cr=function t(n){var e=ar(n);function r(t,n){var r=e((t=b(t)).r,(n=b(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),u=lr(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=u(n),t+""}}return r.gamma=t,r}(1);function sr(t,n){n||(n=[]);var e,r=t?Math.min(n.length,t.length):0,i=n.slice();return function(o){for(e=0;eo&&(i=n.slice(o,i),a[u]?a[u]+=i:a[++u]=i),(e=e[0])===(r=r[0])?a[u]?a[u]+=r:a[++u]=r:(a[++u]=null,l.push({i:u,x:gr(e,r)})),o=yr.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:gr(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,u.rotate,a,l),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:gr(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,u.skewX,a,l),function(t,n,e,r,o,u){if(t!==e||n!==r){var a=o.push(i(o)+"scale(",null,",",null,")");u.push({i:a-4,x:gr(t,e)},{i:a-2,x:gr(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,u.scaleX,u.scaleY,a,l),o=u=null,function(t){for(var n,e=-1,r=l.length;++en&&(e=t,t=n,n=e),c=function(e){return Math.max(t,Math.min(n,e))}),r=l>2?Ur:Dr,i=o=null,f}function f(n){return null==n||isNaN(n=+n)?e:(i||(i=r(u.map(t),a,l)))(t(c(n)))}return f.invert=function(e){return c(n((o||(o=r(a,u.map(t),gr)))(e)))},f.domain=function(t){return arguments.length?(u=Array.from(t,Cr),s()):u.slice()},f.range=function(t){return arguments.length?(a=Array.from(t),s()):a.slice()},f.rangeRound=function(t){return a=Array.from(t),l=wr,s()},f.clamp=function(t){return arguments.length?(c=!!t||kr,s()):c!==kr},f.interpolate=function(t){return arguments.length?(l=t,s()):l},f.unknown=function(t){return arguments.length?(e=t,f):e},function(e,r){return t=e,n=r,s()}}function qr(){return Fr()(kr,kr)}function Yr(t,n){if((e=(t=n?t.toExponential(n-1):t.toExponential()).indexOf("e"))<0)return null;var e,r=t.slice(0,e);return[r.length>1?r[0]+r.slice(2):r,+t.slice(e+1)]}function Hr(t){return(t=Yr(Math.abs(t)))?t[1]:NaN}var Pr,Lr=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Or(t){if(!(n=Lr.exec(t)))throw new Error("invalid format: "+t);var n;return new jr({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function jr(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function Ir(t,n){var e=Yr(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Or.prototype=jr.prototype,jr.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Rr={"%":(t,n)=>(100*t).toFixed(n),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,n)=>t.toExponential(n),f:(t,n)=>t.toFixed(n),g:(t,n)=>t.toPrecision(n),o:t=>Math.round(t).toString(8),p:(t,n)=>Ir(100*t,n),r:Ir,s:function(t,n){var e=Yr(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(Pr=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=r.length;return o===u?r:o>u?r+new Array(o-u+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Yr(t,Math.max(0,n+o-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Xr(t){return t}var zr,Vr,Zr,Br=Array.prototype.map,Wr=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function Qr(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?Xr:(n=Br.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],u=0,a=n[0],l=0;i>0&&a>0&&(l+a+1>r&&(a=Math.max(1,r-l)),o.push(t.substring(i-=a,i+a)),!((l+=a+1)>r));)a=n[u=(u+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",u=void 0===t.decimal?".":t.decimal+"",a=void 0===t.numerals?Xr:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(Br.call(t.numerals,String)),l=void 0===t.percent?"%":t.percent+"",c=void 0===t.minus?"−":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function f(t){var n=(t=Or(t)).fill,e=t.align,f=t.sign,h=t.symbol,g=t.zero,p=t.width,d=t.comma,y=t.precision,v=t.trim,m=t.type;"n"===m?(d=!0,m="g"):Rr[m]||(void 0===y&&(y=12),v=!0,m="g"),(g||"0"===n&&"="===e)&&(g=!0,n="0",e="=");var w="$"===h?i:"#"===h&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",_="$"===h?o:/[%p]/.test(m)?l:"",M=Rr[m],x=/[defgprs%]/.test(m);function b(t){var i,o,l,h=w,b=_;if("c"===m)b=M(t)+b,t="";else{var T=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:M(Math.abs(t),y),v&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),T&&0==+t&&"+"!==f&&(T=!1),h=(T?"("===f?f:c:"-"===f||"("===f?"":f)+h,b=("s"===m?Wr[8+Pr/3]:"")+b+(T&&"("===f?")":""),x)for(i=-1,o=t.length;++i(l=t.charCodeAt(i))||l>57){b=(46===l?u+t.slice(i+1):t.slice(i))+b,t=t.slice(0,i);break}}d&&!g&&(t=r(t,1/0));var A=h.length+t.length+b.length,N=A>1)+h+t+b+N.slice(A);break;default:t=N+h+t+b}return a(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),b.toString=function(){return t+""},b}return{format:f,formatPrefix:function(t,n){var e=f(((t=Or(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Hr(n)/3))),i=Math.pow(10,-r),o=Wr[8+r/3];return function(t){return e(i*t)+o}}}}function Gr(t,n,e,r){var i,o=Ze(t,n,e);switch((r=Or(null==r?",f":r)).type){case"s":var u=Math.max(Math.abs(t),Math.abs(n));return null!=r.precision||isNaN(i=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Hr(n)/3)))-Hr(Math.abs(t)))}(o,u))||(r.precision=i),Zr(r,u);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Hr(n)-Hr(t))+1}(o,Math.max(Math.abs(t),Math.abs(n))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=function(t){return Math.max(0,-Hr(Math.abs(t)))}(o))||(r.precision=i-2*("%"===r.type))}return Vr(r)}function Jr(t){var n=t.domain;return t.ticks=function(t){var e=n();return ze(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return Gr(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i,o=n(),u=0,a=o.length-1,l=o[u],c=o[a],s=10;for(c0;){if((i=Ve(l,c,e))===r)return o[u]=l,o[a]=c,n(o);if(i>0)l=Math.floor(l/i)*i,c=Math.ceil(c/i)*i;else{if(!(i<0))break;l=Math.ceil(l*i)/i,c=Math.floor(c*i)/i}r=i}return t},t}function Kr(){var t=qr();return t.copy=function(){return Er(t,Kr())},Ke.apply(t,arguments),Jr(t)}function ti(t){var n;function e(t){return null==t||isNaN(t=+t)?n:t}return e.invert=e,e.domain=e.range=function(n){return arguments.length?(t=Array.from(n,Cr),e):t.slice()},e.unknown=function(t){return arguments.length?(n=t,e):n},e.copy=function(){return ti(t).unknown(n)},t=arguments.length?Array.from(t,Cr):[0,1],Jr(e)}function ni(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],u=t[i];return u-t(-n,e)}function li(t){const n=t(ei,ri),e=n.domain;let r,i,o=10;function u(){return r=function(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),n=>Math.log(n)/t)}(o),i=function(t){return 10===t?ui:t===Math.E?Math.exp:n=>Math.pow(t,n)}(o),e()[0]<0?(r=ai(r),i=ai(i),t(ii,oi)):t(ei,ri),n}return n.base=function(t){return arguments.length?(o=+t,u()):o},n.domain=function(t){return arguments.length?(e(t),u()):e()},n.ticks=t=>{const n=e();let u=n[0],a=n[n.length-1];const l=a0){for(;f<=h;++f)for(c=1;ca)break;p.push(s)}}else for(;f<=h;++f)for(c=o-1;c>=1;--c)if(s=f>0?c/i(-f):c*i(f),!(sa)break;p.push(s)}2*p.length{if(null==t&&(t=10),null==e&&(e=10===o?"s":","),"function"!=typeof e&&(o%1||null!=(e=Or(e)).precision||(e.trim=!0),e=Vr(e)),t===1/0)return e;const u=Math.max(1,o*t/n.ticks().length);return t=>{let n=t/i(Math.round(r(t)));return n*oe(ni(e(),{floor:t=>i(Math.floor(r(t))),ceil:t=>i(Math.ceil(r(t)))})),n}function ci(){const t=li(Fr()).domain([1,10]);return t.copy=()=>Er(t,ci()).base(t.base()),Ke.apply(t,arguments),t}function si(t){return function(n){return Math.sign(n)*Math.log1p(Math.abs(n/t))}}function fi(t){return function(n){return Math.sign(n)*Math.expm1(Math.abs(n))*t}}function hi(t){var n=1,e=t(si(n),fi(n));return e.constant=function(e){return arguments.length?t(si(n=+e),fi(n)):n},Jr(e)}function gi(){var t=hi(Fr());return t.copy=function(){return Er(t,gi()).constant(t.constant())},Ke.apply(t,arguments)}function pi(t){return function(n){return n<0?-Math.pow(-n,t):Math.pow(n,t)}}function di(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function yi(t){return t<0?-t*t:t*t}function vi(t){var n=t(kr,kr),e=1;return n.exponent=function(n){return arguments.length?1===(e=+n)?t(kr,kr):.5===e?t(di,yi):t(pi(e),pi(1/e)):e},Jr(n)}function mi(){var t=vi(Fr());return t.copy=function(){return Er(t,mi()).exponent(t.exponent())},Ke.apply(t,arguments),t}function wi(){return mi.apply(null,arguments).exponent(.5)}function _i(t){return Math.sign(t)*t*t}function Mi(){var t,n=qr(),e=[0,1],r=!1;function i(e){var i=function(t){return Math.sign(t)*Math.sqrt(Math.abs(t))}(n(e));return isNaN(i)?t:r?Math.round(i):i}return i.invert=function(t){return n.invert(_i(t))},i.domain=function(t){return arguments.length?(n.domain(t),i):n.domain()},i.range=function(t){return arguments.length?(n.range((e=Array.from(t,Cr)).map(_i)),i):e.slice()},i.rangeRound=function(t){return i.range(t).round(!0)},i.round=function(t){return arguments.length?(r=!!t,i):r},i.clamp=function(t){return arguments.length?(n.clamp(t),i):n.clamp()},i.unknown=function(n){return arguments.length?(t=n,i):t},i.copy=function(){return Mi(n.domain(),e).round(r).clamp(n.clamp()).unknown(t)},Ke.apply(i,arguments),Jr(i)}function xi(){var t,n=[],e=[],r=[];function i(){var t=0,i=Math.max(1,e.length);for(r=new Array(i-1);++t0?r[i-1]:n[0],i=r?[i[r-1],e]:[i[u-1],i[u]]},u.unknown=function(n){return arguments.length?(t=n,u):u},u.thresholds=function(){return i.slice()},u.copy=function(){return bi().domain([n,e]).range(o).unknown(t)},Ke.apply(Jr(u),arguments)}function Ti(){var t,n=[.5],e=[0,1],r=1;function i(i){return null!=i&&i<=i?e[He(n,i,0,r)]:t}return i.domain=function(t){return arguments.length?(n=Array.from(t),r=Math.min(n.length,e.length-1),i):n.slice()},i.range=function(t){return arguments.length?(e=Array.from(t),r=Math.min(n.length,e.length-1),i):e.slice()},i.invertExtent=function(t){var r=e.indexOf(t);return[n[r-1],n[r]]},i.unknown=function(n){return arguments.length?(t=n,i):t},i.copy=function(){return Ti().domain(n).range(e).unknown(t)},Ke.apply(i,arguments)}zr=Qr({thousands:",",grouping:[3],currency:["$",""]}),Vr=zr.format,Zr=zr.formatPrefix;const Ai=new Date,Ni=new Date;function Ci(t,n,e,r){function i(n){return t(n=0===arguments.length?new Date:new Date(+n)),n}return i.floor=n=>(t(n=new Date(+n)),n),i.ceil=e=>(t(e=new Date(e-1)),n(e,1),t(e),e),i.round=t=>{const n=i(t),e=i.ceil(t);return t-n(n(t=new Date(+t),null==e?1:Math.floor(e)),t),i.range=(e,r,o)=>{const u=[];if(e=i.ceil(e),o=null==o?1:Math.floor(o),!(e0))return u;let a;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(aCi((n=>{if(n>=n)for(;t(n),!e(n);)n.setTime(n-1)}),((t,r)=>{if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})),e&&(i.count=(n,r)=>(Ai.setTime(+n),Ni.setTime(+r),t(Ai),t(Ni),Math.floor(e(Ai,Ni))),i.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?n=>r(n)%t==0:n=>i.count(0,n)%t==0):i:null)),i}const $i=Ci((()=>{}),((t,n)=>{t.setTime(+t+n)}),((t,n)=>n-t));$i.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?Ci((n=>{n.setTime(Math.floor(n/t)*t)}),((n,e)=>{n.setTime(+n+e*t)}),((n,e)=>(e-n)/t)):$i:null),$i.range;const ki=1e3,Si=6e4,Di=36e5,Ui=864e5,Ei=6048e5,Fi=2592e6,qi=31536e6,Yi=Ci((t=>{t.setTime(t-t.getMilliseconds())}),((t,n)=>{t.setTime(+t+n*ki)}),((t,n)=>(n-t)/ki),(t=>t.getUTCSeconds()));Yi.range;const Hi=Ci((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*ki)}),((t,n)=>{t.setTime(+t+n*Si)}),((t,n)=>(n-t)/Si),(t=>t.getMinutes()));Hi.range;const Pi=Ci((t=>{t.setUTCSeconds(0,0)}),((t,n)=>{t.setTime(+t+n*Si)}),((t,n)=>(n-t)/Si),(t=>t.getUTCMinutes()));Pi.range;const Li=Ci((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*ki-t.getMinutes()*Si)}),((t,n)=>{t.setTime(+t+n*Di)}),((t,n)=>(n-t)/Di),(t=>t.getHours()));Li.range;const Oi=Ci((t=>{t.setUTCMinutes(0,0,0)}),((t,n)=>{t.setTime(+t+n*Di)}),((t,n)=>(n-t)/Di),(t=>t.getUTCHours()));Oi.range;const ji=Ci((t=>t.setHours(0,0,0,0)),((t,n)=>t.setDate(t.getDate()+n)),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Si)/Ui),(t=>t.getDate()-1));ji.range;const Ii=Ci((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/Ui),(t=>t.getUTCDate()-1));Ii.range;const Ri=Ci((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/Ui),(t=>Math.floor(t/Ui)));function Xi(t){return Ci((n=>{n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)}),((t,n)=>{t.setDate(t.getDate()+7*n)}),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Si)/Ei))}Ri.range;const zi=Xi(0),Vi=Xi(1),Zi=Xi(2),Bi=Xi(3),Wi=Xi(4),Qi=Xi(5),Gi=Xi(6);function Ji(t){return Ci((n=>{n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+7*n)}),((t,n)=>(n-t)/Ei))}zi.range,Vi.range,Zi.range,Bi.range,Wi.range,Qi.range,Gi.range;const Ki=Ji(0),to=Ji(1),no=Ji(2),eo=Ji(3),ro=Ji(4),io=Ji(5),oo=Ji(6);Ki.range,to.range,no.range,eo.range,ro.range,io.range,oo.range;const uo=Ci((t=>{t.setDate(1),t.setHours(0,0,0,0)}),((t,n)=>{t.setMonth(t.getMonth()+n)}),((t,n)=>n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())),(t=>t.getMonth()));uo.range;const ao=Ci((t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCMonth(t.getUTCMonth()+n)}),((t,n)=>n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())),(t=>t.getUTCMonth()));ao.range;const lo=Ci((t=>{t.setMonth(0,1),t.setHours(0,0,0,0)}),((t,n)=>{t.setFullYear(t.getFullYear()+n)}),((t,n)=>n.getFullYear()-t.getFullYear()),(t=>t.getFullYear()));lo.every=t=>isFinite(t=Math.floor(t))&&t>0?Ci((n=>{n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)}),((n,e)=>{n.setFullYear(n.getFullYear()+e*t)})):null,lo.range;const co=Ci((t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCFullYear(t.getUTCFullYear()+n)}),((t,n)=>n.getUTCFullYear()-t.getUTCFullYear()),(t=>t.getUTCFullYear()));function so(t,n,e,r,i,o){const u=[[Yi,1,ki],[Yi,5,5e3],[Yi,15,15e3],[Yi,30,3e4],[o,1,Si],[o,5,3e5],[o,15,9e5],[o,30,18e5],[i,1,Di],[i,3,108e5],[i,6,216e5],[i,12,432e5],[r,1,Ui],[r,2,1728e5],[e,1,Ei],[n,1,Fi],[n,3,7776e6],[t,1,qi]];function a(n,e,r){const i=Math.abs(e-n)/r,o=Fe((([,,t])=>t)).right(u,i);if(o===u.length)return t.every(Ze(n/qi,e/qi,r));if(0===o)return $i.every(Math.max(Ze(n,e,r),1));const[a,l]=u[i/u[o-1][2]isFinite(t=Math.floor(t))&&t>0?Ci((n=>{n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)}),((n,e)=>{n.setUTCFullYear(n.getUTCFullYear()+e*t)})):null,co.range;const[fo,ho]=so(co,ao,Ki,Ri,Oi,Pi),[go,po]=so(lo,uo,zi,ji,Li,Hi);function yo(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function vo(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function mo(t,n,e){return{y:t,m:n,d:e,H:0,M:0,S:0,L:0}}function wo(t){var n=t.dateTime,e=t.date,r=t.time,i=t.periods,o=t.days,u=t.shortDays,a=t.months,l=t.shortMonths,c=Do(i),s=Uo(i),f=Do(o),h=Uo(o),g=Do(u),p=Uo(u),d=Do(a),y=Uo(a),v=Do(l),m=Uo(l),w={a:function(t){return u[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return l[t.getMonth()]},B:function(t){return a[t.getMonth()]},c:null,d:Ko,e:Ko,f:iu,g:du,G:vu,H:tu,I:nu,j:eu,L:ru,m:ou,M:uu,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:Ou,s:ju,S:au,u:lu,U:cu,V:fu,w:hu,W:gu,x:null,X:null,y:pu,Y:yu,Z:mu,"%":Lu},_={a:function(t){return u[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return l[t.getUTCMonth()]},B:function(t){return a[t.getUTCMonth()]},c:null,d:wu,e:wu,f:Tu,g:qu,G:Hu,H:_u,I:Mu,j:xu,L:bu,m:Au,M:Nu,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:Ou,s:ju,S:Cu,u:$u,U:ku,V:Du,w:Uu,W:Eu,x:null,X:null,y:Fu,Y:Yu,Z:Pu,"%":Lu},M={a:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.w=p.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.w=h.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=m.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.m=y.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(t,e,r){return T(t,n,e,r)},d:Ro,e:Ro,f:Wo,g:Lo,G:Po,H:zo,I:zo,j:Xo,L:Bo,m:Io,M:Vo,p:function(t,n,e){var r=c.exec(n.slice(e));return r?(t.p=s.get(r[0].toLowerCase()),e+r[0].length):-1},q:jo,Q:Go,s:Jo,S:Zo,u:Fo,U:qo,V:Yo,w:Eo,W:Ho,x:function(t,n,r){return T(t,e,n,r)},X:function(t,n,e){return T(t,r,n,e)},y:Lo,Y:Po,Z:Oo,"%":Qo};function x(t,n){return function(e){var r,i,o,u=[],a=-1,l=0,c=t.length;for(e instanceof Date||(e=new Date(+e));++a53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=vo(mo(o.y,0,1))).getUTCDay(),r=i>4||0===i?to.ceil(r):to(r),r=Ii.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=yo(mo(o.y,0,1))).getDay(),r=i>4||0===i?Vi.ceil(r):Vi(r),r=ji.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?vo(mo(o.y,0,1)).getUTCDay():yo(mo(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,vo(o)):yo(o)}}function T(t,n,e,r){for(var i,o,u=0,a=n.length,l=e.length;u=l)return-1;if(37===(i=n.charCodeAt(u++))){if(i=n.charAt(u++),!(o=M[i in Ao?n.charAt(u++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return w.x=x(e,w),w.X=x(r,w),w.c=x(n,w),_.x=x(e,_),_.X=x(r,_),_.c=x(n,_),{format:function(t){var n=x(t+="",w);return n.toString=function(){return t},n},parse:function(t){var n=b(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=x(t+="",_);return n.toString=function(){return t},n},utcParse:function(t){var n=b(t+="",!0);return n.toString=function(){return t},n}}}var _o,Mo,xo,bo,To,Ao={"-":"",_:" ",0:"0"},No=/^\s*\d+/,Co=/^%/,$o=/[\\^$*+?|[\]().{}]/g;function ko(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o[t.toLowerCase(),n])))}function Eo(t,n,e){var r=No.exec(n.slice(e,e+1));return r?(t.w=+r[0],e+r[0].length):-1}function Fo(t,n,e){var r=No.exec(n.slice(e,e+1));return r?(t.u=+r[0],e+r[0].length):-1}function qo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.U=+r[0],e+r[0].length):-1}function Yo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.V=+r[0],e+r[0].length):-1}function Ho(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.W=+r[0],e+r[0].length):-1}function Po(t,n,e){var r=No.exec(n.slice(e,e+4));return r?(t.y=+r[0],e+r[0].length):-1}function Lo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),e+r[0].length):-1}function Oo(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function jo(t,n,e){var r=No.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function Io(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function Ro(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function Xo(t,n,e){var r=No.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function zo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Vo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Zo(t,n,e){var r=No.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Bo(t,n,e){var r=No.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Wo(t,n,e){var r=No.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Qo(t,n,e){var r=Co.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Go(t,n,e){var r=No.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function Jo(t,n,e){var r=No.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function Ko(t,n){return ko(t.getDate(),n,2)}function tu(t,n){return ko(t.getHours(),n,2)}function nu(t,n){return ko(t.getHours()%12||12,n,2)}function eu(t,n){return ko(1+ji.count(lo(t),t),n,3)}function ru(t,n){return ko(t.getMilliseconds(),n,3)}function iu(t,n){return ru(t,n)+"000"}function ou(t,n){return ko(t.getMonth()+1,n,2)}function uu(t,n){return ko(t.getMinutes(),n,2)}function au(t,n){return ko(t.getSeconds(),n,2)}function lu(t){var n=t.getDay();return 0===n?7:n}function cu(t,n){return ko(zi.count(lo(t)-1,t),n,2)}function su(t){var n=t.getDay();return n>=4||0===n?Wi(t):Wi.ceil(t)}function fu(t,n){return t=su(t),ko(Wi.count(lo(t),t)+(4===lo(t).getDay()),n,2)}function hu(t){return t.getDay()}function gu(t,n){return ko(Vi.count(lo(t)-1,t),n,2)}function pu(t,n){return ko(t.getFullYear()%100,n,2)}function du(t,n){return ko((t=su(t)).getFullYear()%100,n,2)}function yu(t,n){return ko(t.getFullYear()%1e4,n,4)}function vu(t,n){var e=t.getDay();return ko((t=e>=4||0===e?Wi(t):Wi.ceil(t)).getFullYear()%1e4,n,4)}function mu(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+ko(n/60|0,"0",2)+ko(n%60,"0",2)}function wu(t,n){return ko(t.getUTCDate(),n,2)}function _u(t,n){return ko(t.getUTCHours(),n,2)}function Mu(t,n){return ko(t.getUTCHours()%12||12,n,2)}function xu(t,n){return ko(1+Ii.count(co(t),t),n,3)}function bu(t,n){return ko(t.getUTCMilliseconds(),n,3)}function Tu(t,n){return bu(t,n)+"000"}function Au(t,n){return ko(t.getUTCMonth()+1,n,2)}function Nu(t,n){return ko(t.getUTCMinutes(),n,2)}function Cu(t,n){return ko(t.getUTCSeconds(),n,2)}function $u(t){var n=t.getUTCDay();return 0===n?7:n}function ku(t,n){return ko(Ki.count(co(t)-1,t),n,2)}function Su(t){var n=t.getUTCDay();return n>=4||0===n?ro(t):ro.ceil(t)}function Du(t,n){return t=Su(t),ko(ro.count(co(t),t)+(4===co(t).getUTCDay()),n,2)}function Uu(t){return t.getUTCDay()}function Eu(t,n){return ko(to.count(co(t)-1,t),n,2)}function Fu(t,n){return ko(t.getUTCFullYear()%100,n,2)}function qu(t,n){return ko((t=Su(t)).getUTCFullYear()%100,n,2)}function Yu(t,n){return ko(t.getUTCFullYear()%1e4,n,4)}function Hu(t,n){var e=t.getUTCDay();return ko((t=e>=4||0===e?ro(t):ro.ceil(t)).getUTCFullYear()%1e4,n,4)}function Pu(){return"+0000"}function Lu(){return"%"}function Ou(t){return+t}function ju(t){return Math.floor(+t/1e3)}function Iu(t){return _o=wo(t),Mo=_o.format,xo=_o.parse,bo=_o.utcFormat,To=_o.utcParse,_o}Iu({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ru="%Y-%m-%dT%H:%M:%S.%LZ";var Xu=Date.prototype.toISOString?function(t){return t.toISOString()}:bo(Ru);var zu=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:To(Ru);function Vu(t){return new Date(t)}function Zu(t){return t instanceof Date?+t:+new Date(+t)}function Bu(t,n,e,r,i,o,u,a,l,c){var s=qr(),f=s.invert,h=s.domain,g=c(".%L"),p=c(":%S"),d=c("%I:%M"),y=c("%I %p"),v=c("%a %d"),m=c("%b %d"),w=c("%B"),_=c("%Y");function M(t){return(l(t)n(r/(t.length-1))))},e.quantiles=function(n){return Array.from({length:n+1},((e,r)=>function(t,n){if((e=(t=Float64Array.from(function*(t){for(let n of t)null!=n&&(n=+n)>=n&&(yield n)}(t))).length)&&!isNaN(n=+n)){if(n<=0||e<2)return We(t);if(n>=1)return Be(t);var e,r=(e-1)*n,i=Math.floor(r),o=Be(Qe(t,i).subarray(0,i+1));return o+(We(t.subarray(i+1))-o)*(r-i)}}(t,r/n)))},e.copy=function(){return ia(n).domain(t)},tr.apply(e,arguments)}function oa(){var t,n,e,r,i,o,u,a=0,l=.5,c=1,s=1,f=kr,h=!1;function g(t){return isNaN(t=+t)?u:(t=.5+((t=+o(t))-n)*(s*t=1?Ma:t<=-1?-Ma:Math.asin(t)}function Ta(t){return t.innerRadius}function Aa(t){return t.outerRadius}function Na(t){return t.startAngle}function Ca(t){return t.endAngle}function $a(t){return t&&t.padAngle}function ka(t,n,e,r,i,o,u){var a=t-e,l=n-r,c=(u?o:-o)/ma(a*a+l*l),s=c*l,f=-c*a,h=t+s,g=n+f,p=e+s,d=r+f,y=(h+p)/2,v=(g+d)/2,m=p-h,w=d-g,_=m*m+w*w,M=i-o,x=h*d-p*g,b=(w<0?-1:1)*ma(da(0,M*M*_-x*x)),T=(x*w-m*b)/_,A=(-x*m-w*b)/_,N=(x*w+m*b)/_,C=(-x*m+w*b)/_,$=T-y,k=A-v,S=N-y,D=C-v;return $*$+k*k>S*S+D*D&&(T=N,A=C),{cx:T,cy:A,x01:-s,y01:-f,x11:T*(i/M-1),y11:A*(i/M-1)}}function Sa(){var t=Ta,n=Aa,e=fa(0),r=null,i=Na,o=Ca,u=$a,a=null,l=function(t){let n=3;return t.digits=function(e){if(!arguments.length)return n;if(null==e)n=null;else{const t=Math.floor(e);if(!(t>=0))throw new RangeError(`invalid digits: ${e}`);n=t}return t},()=>new Ut(n)}(c);function c(){var c,s,f,h=+t.apply(this,arguments),g=+n.apply(this,arguments),p=i.apply(this,arguments)-Ma,d=o.apply(this,arguments)-Ma,y=ha(d-p),v=d>p;if(a||(a=c=l()),gwa)if(y>xa-wa)a.moveTo(g*pa(p),g*va(p)),a.arc(0,0,g,p,d,!v),h>wa&&(a.moveTo(h*pa(d),h*va(d)),a.arc(0,0,h,d,p,v));else{var m,w,_=p,M=d,x=p,b=d,T=y,A=y,N=u.apply(this,arguments)/2,C=N>wa&&(r?+r.apply(this,arguments):ma(h*h+g*g)),$=ya(ha(g-h)/2,+e.apply(this,arguments)),k=$,S=$;if(C>wa){var D=ba(C/h*va(N)),U=ba(C/g*va(N));(T-=2*D)>wa?(x+=D*=v?1:-1,b-=D):(T=0,x=b=(p+d)/2),(A-=2*U)>wa?(_+=U*=v?1:-1,M-=U):(A=0,_=M=(p+d)/2)}var E=g*pa(_),F=g*va(_),q=h*pa(b),Y=h*va(b);if($>wa){var H,P=g*pa(M),L=g*va(M),O=h*pa(x),j=h*va(x);if(y<_a)if(H=function(t,n,e,r,i,o,u,a){var l=e-t,c=r-n,s=u-i,f=a-o,h=f*l-s*c;if(!(h*h1?0:f<-1?_a:Math.acos(f))/2),Z=ma(H[0]*H[0]+H[1]*H[1]);k=ya($,(h-Z)/(V-1)),S=ya($,(g-Z)/(V+1))}else k=S=0}A>wa?S>wa?(m=ka(O,j,E,F,g,S,v),w=ka(P,L,q,Y,g,S,v),a.moveTo(m.cx+m.x01,m.cy+m.y01),S<$?a.arc(m.cx,m.cy,S,ga(m.y01,m.x01),ga(w.y01,w.x01),!v):(a.arc(m.cx,m.cy,S,ga(m.y01,m.x01),ga(m.y11,m.x11),!v),a.arc(0,0,g,ga(m.cy+m.y11,m.cx+m.x11),ga(w.cy+w.y11,w.cx+w.x11),!v),a.arc(w.cx,w.cy,S,ga(w.y11,w.x11),ga(w.y01,w.x01),!v))):(a.moveTo(E,F),a.arc(0,0,g,_,M,!v)):a.moveTo(E,F),h>wa&&T>wa?k>wa?(m=ka(q,Y,P,L,h,-k,v),w=ka(E,F,O,j,h,-k,v),a.lineTo(m.cx+m.x01,m.cy+m.y01),k<$?a.arc(m.cx,m.cy,k,ga(m.y01,m.x01),ga(w.y01,w.x01),!v):(a.arc(m.cx,m.cy,k,ga(m.y01,m.x01),ga(m.y11,m.x11),!v),a.arc(0,0,h,ga(m.cy+m.y11,m.cx+m.x11),ga(w.cy+w.y11,w.cx+w.x11),v),a.arc(w.cx,w.cy,k,ga(w.y11,w.x11),ga(w.y01,w.x01),!v))):a.arc(0,0,h,b,x,v):a.lineTo(q,Y)}else a.moveTo(0,0);if(a.closePath(),c)return a=null,c+""||null}return c.centroid=function(){var e=(+t.apply(this,arguments)+ +n.apply(this,arguments))/2,r=(+i.apply(this,arguments)+ +o.apply(this,arguments))/2-_a/2;return[pa(r)*e,va(r)*e]},c.innerRadius=function(n){return arguments.length?(t="function"==typeof n?n:fa(+n),c):t},c.outerRadius=function(t){return arguments.length?(n="function"==typeof t?t:fa(+t),c):n},c.cornerRadius=function(t){return arguments.length?(e="function"==typeof t?t:fa(+t),c):e},c.padRadius=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:fa(+t),c):r},c.startAngle=function(t){return arguments.length?(i="function"==typeof t?t:fa(+t),c):i},c.endAngle=function(t){return arguments.length?(o="function"==typeof t?t:fa(+t),c):o},c.padAngle=function(t){return arguments.length?(u="function"==typeof t?t:fa(+t),c):u},c.context=function(t){return arguments.length?(a=null==t?null:t,c):a},c}var Da,Ua,Ea=0,Fa=0,qa=0,Ya=0,Ha=0,Pa=0,La="object"==typeof performance&&performance.now?performance:Date,Oa="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function ja(){return Ha||(Oa(Ia),Ha=La.now()+Pa)}function Ia(){Ha=0}function Ra(){this._call=this._time=this._next=null}function Xa(t,n,e){var r=new Ra;return r.restart(t,n,e),r}function za(){Ha=(Ya=La.now())+Pa,Ea=Fa=0;try{!function(){ja(),++Ea;for(var t,n=Da;n;)(t=Ha-n._time)>=0&&n._call.call(void 0,t),n=n._next;--Ea}()}finally{Ea=0,function(){var t,n,e=Da,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Da=n);Ua=t,Za(r)}(),Ha=0}}function Va(){var t=La.now(),n=t-Ya;n>1e3&&(Pa-=n,Ya=t)}function Za(t){Ea||(Fa&&(Fa=clearTimeout(Fa)),t-Ha>24?(t<1/0&&(Fa=setTimeout(za,t-La.now()-Pa)),qa&&(qa=clearInterval(qa))):(qa||(Ya=La.now(),qa=setInterval(Va,1e3)),Ea=1,Oa(za)))}function Ba(t,n,e){var r=new Ra;return n=null==n?0:+n,r.restart((e=>{r.stop(),t(e+n)}),n,e),r}Ra.prototype=Xa.prototype={constructor:Ra,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?ja():+e)+(null==n?0:+n),this._next||Ua===this||(Ua?Ua._next=this:Da=this,Ua=this),this._call=t,this._time=e,Za()},stop:function(){this._call&&(this._call=null,this._time=1/0,Za())}};var Wa=Zt("start","end","cancel","interrupt"),Qa=[];function Ga(t,n,e,r,i,o){var u=t.__transition;if(u){if(e in u)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=1,e.timer.restart(u,e.delay,e.time),e.delay<=t&&u(t-e.delay)}function u(o){var c,s,f,h;if(1!==e.state)return l();for(c in i)if((h=i[c]).name===e.name){if(3===h.state)return Ba(u);4===h.state?(h.state=6,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[c]):+c0)throw new Error("too late; already scheduled");return e}function Ka(t,n){var e=tl(t,n);if(e.state>3)throw new Error("too late; already running");return e}function tl(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function nl(t,n){var e,r,i,o=t.__transition,u=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>2&&e.state<5,e.state=6,e.timer.stop(),e.on.call(r?"interrupt":"cancel",t,t.__data__,e.index,e.group),delete o[i]):u=!1;u&&delete t.__transition}}function el(t,n){var e,r;return function(){var i=Ka(this,t),o=i.tween;if(o!==e)for(var u=0,a=(r=e=o).length;u=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?Ja:Ka;return function(){var u=o(this,t),a=u.on;a!==r&&(i=(r=a).copy()).on(n,e),u.on=i}}(e,t,n))},attr:function(t,n){var e=Kt(t),r="transform"===e?Nr:ol;return this.attrTween(t,"function"==typeof n?(e.local?fl:sl)(e,r,il(this,"attr."+t,n)):null==n?(e.local?al:ul)(e):(e.local?cl:ll)(e,r,n))},attrTween:function(t,n){var e="attr."+t;if(arguments.length<2)return(e=this.tween(e))&&e._value;if(null==n)return this.tween(e,null);if("function"!=typeof n)throw new Error;var r=Kt(t);return this.tween(e,(r.local?hl:gl)(r,n))},style:function(t,n,e){var r="transform"==(t+="")?Ar:ol;return null==n?this.styleTween(t,function(t,n){var e,r,i;return function(){var o=Un(this,t),u=(this.style.removeProperty(t),Un(this,t));return o===u?null:o===e&&u===r?i:i=n(e=o,r=u)}}(t,r)).on("end.style."+t,wl(t)):"function"==typeof n?this.styleTween(t,function(t,n,e){var r,i,o;return function(){var u=Un(this,t),a=e(this),l=a+"";return null==a&&(this.style.removeProperty(t),l=a=Un(this,t)),u===l?null:u===r&&l===i?o:(i=l,o=n(r=u,a))}}(t,r,il(this,"style."+t,n))).each(function(t,n){var e,r,i,o,u="style."+n,a="end."+u;return function(){var l=Ka(this,t),c=l.on,s=null==l.value[u]?o||(o=wl(n)):void 0;c===e&&i===s||(r=(e=c).copy()).on(a,i=s),l.on=r}}(this._id,t)):this.styleTween(t,function(t,n,e){var r,i,o=e+"";return function(){var u=Un(this,t);return u===o?null:u===r?i:i=n(r=u,e)}}(t,r,n),e).on("end.style."+t,null)},styleTween:function(t,n,e){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==n)return this.tween(r,null);if("function"!=typeof n)throw new Error;return this.tween(r,function(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}(t,o,e)),r}return o._value=n,o}(t,n,null==e?"":e))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var n=t(this);this.textContent=null==n?"":n}}(il(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var n="text";if(arguments.length<1)return(n=this.tween(n))&&n._value;if(null==t)return this.tween(n,null);if("function"!=typeof t)throw new Error;return this.tween(n,function(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&function(t){return function(n){this.textContent=t.call(this,n)}}(r)),n}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}(this._id))},tween:function(t,n){var e=this._id;if(t+="",arguments.length<2){for(var r,i=tl(this.node(),e).tween,o=0,u=i.length;o1&&e.name===n)return new Ml([[t]],Cl,n,+r);return null}export{$l as active,Sa as arc,bt as chord,At as chordDirected,Tt as chordTranspose,w as color,fe as create,en as creator,ht as cubehelix,De as drag,be as dragDisable,Te as dragEnable,V as gray,nt as hcl,U as hsl,nl as interrupt,Xu as isoFormat,zu as isoParse,Z as lab,tt as lch,ge as local,cn as matcher,Kt as namespace,Jt as namespaces,ye as pointer,ve as pointers,b as rgb,Xt as ribbon,zt as ribbonArrow,rr as scaleBand,ua as scaleDiverging,aa as scaleDivergingLog,ca as scaleDivergingPow,sa as scaleDivergingSqrt,la as scaleDivergingSymlog,ti as scaleIdentity,nr as scaleImplicit,Kr as scaleLinear,ci as scaleLog,er as scaleOrdinal,or as scalePoint,mi as scalePow,xi as scaleQuantile,bi as scaleQuantize,Mi as scaleRadial,Ku as scaleSequential,ta as scaleSequentialLog,ea as scaleSequentialPow,ia as scaleSequentialQuantile,ra as scaleSequentialSqrt,na as scaleSequentialSymlog,wi as scaleSqrt,gi as scaleSymlog,Ti as scaleThreshold,Wu as scaleTime,Qu as scaleUtc,se as select,me as selectAll,ce as selection,on as selector,ln as selectorAll,Un as style,Gr as tickFormat,Mo as timeFormat,Iu as timeFormatDefaultLocale,wo as timeFormatLocale,xo as timeParse,xl as transition,bo as utcFormat,To as utcParse,t as version,$n as window}; diff --git a/libs/d3/README.md b/libs/d3/README.md index f7c449bb4..646e073a0 100644 --- a/libs/d3/README.md +++ b/libs/d3/README.md @@ -1,4 +1,5 @@ # Building d3.js for jsroot npm install - npm run-script build + npm run build + rm -rf node_modules package-lock.json diff --git a/libs/d3/package.json b/libs/d3/package.json index b9d304836..73f04b578 100644 --- a/libs/d3/package.json +++ b/libs/d3/package.json @@ -34,13 +34,13 @@ }, "dependencies": { "d3-color": ">=3.1.0", - "d3-chord": "3", - "d3-drag": "3", - "d3-scale": "4", - "d3-selection": "3", - "d3-shape": "3", - "d3-transition": "3", - "d3-time-format": "4" + "d3-chord": "^3.0.1", + "d3-drag": "^3.0.0", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "d3-transition": "^3.0.1", + "d3-time-format": "^4.1.0" }, "devDependencies": { "@rollup/plugin-json": "6", @@ -48,10 +48,10 @@ "@rollup/plugin-terser": "^0.4.0", "eslint": "8", "mocha": "10", - "rollup": "3" + "rollup": ">=3.29.5" }, "scripts": { - "build": "rollup -c" + "build": "rollup -c rollup_d3.config.js" }, "engines": { "node": ">=12" diff --git a/libs/d3/rollup.config.js b/libs/d3/rollup_d3.config.js similarity index 87% rename from libs/d3/rollup.config.js rename to libs/d3/rollup_d3.config.js index 51287de77..b8afd881b 100644 --- a/libs/d3/rollup.config.js +++ b/libs/d3/rollup_d3.config.js @@ -1,8 +1,8 @@ -import {readFileSync} from "fs"; +import { readFileSync } from "fs"; import json from "@rollup/plugin-json"; import nodeResolve from "@rollup/plugin-node-resolve"; import terser from "@rollup/plugin-terser"; -import meta from "./package.json" assert {type: "json"}; +import meta from "./package.json" with { type: 'json' }; // Extract copyrights from the LICENSE. const copyright = readFileSync("./node_modules/d3-selection/LICENSE", "utf-8") @@ -26,7 +26,8 @@ const config = { json() ], onwarn(message, warn) { - if (message.code === "CIRCULAR_DEPENDENCY") return; + if (message.code === "CIRCULAR_DEPENDENCY") + return; warn(message); } }; diff --git a/libs/jsPDF/Readme.md b/libs/jsPDF/Readme.md new file mode 100644 index 000000000..780552259 --- /dev/null +++ b/libs/jsPDF/Readme.md @@ -0,0 +1,27 @@ +# jsPDF + +## Original code + +https://github.com/parallax/jsPDF + +## Modification in original version + +https://github.com/linev/jsPDF/commits/jsroot/ + +1. Do not create exception when unicode map missing in font - used for symbols +2. Provide directly atob/btoa for node and browser +3. Do not expose build date +4. Comment out svg.js, html.js, png_support.js, fileloading.js - not used with svg2pdf +5. Remove API.save +6. Remove 'fflate' from externals - include into build +7. Remove several outpus - code with confuses browser when loaded directly + +## How to build + + npm install + npm run build + sed '$ d' ./dist/jspdf.es.min.js > ~/git/jsroot/libs/jspdf.mjs + sed '$ d' ./dist/jspdf.es.js > ~/git/jsroot/modules/base/jspdf.mjs + +Command `sed '$ d'` removes last line in the script which referencing map + diff --git a/libs/jspdf.LICENSE b/libs/jspdf.LICENSE new file mode 100644 index 000000000..4c0e11169 --- /dev/null +++ b/libs/jspdf.LICENSE @@ -0,0 +1,22 @@ +Copyright +(c) 2010-2021 James Hall, https://github.com/MrRio/jsPDF +(c) 2015-2021 yWorks GmbH, https://www.yworks.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libs/jspdf.mjs b/libs/jspdf.mjs new file mode 100644 index 000000000..d0c9c7ddd --- /dev/null +++ b/libs/jspdf.mjs @@ -0,0 +1,333 @@ +/** @license + * + * jsPDF - PDF Document creation from JavaScript + * Version 3.0.3 + * + * Copyright (c) 2010-2025 James Hall , https://github.com/MrRio/jsPDF + * 2015-2025 yWorks GmbH, http://www.yworks.com + * 2015-2025 Lukas Holländer , https://github.com/HackbrettXXX + * 2016-2018 Aras Abbasi + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Steven Spungin, https://github.com/Flamenco + * 2014 Kenneth Glassey, https://github.com/Gavvers + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, Flamenco + */ + +const t=globalThis; +/** + * A class to parse color values + * @author Stoyan Stefanov + * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} + * @license Use it if you like it + */function e(t){var e;t=t||"",this.ok=!1,"#"==t.charAt(0)&&(t=t.substr(1,6)),t={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"}[t=(t=t.replace(/ /g,"")).toLowerCase()]||t;for(var n=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}],r=0;r255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),n=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==n.length&&(n="0"+n),"#"+t+e+n}}let n,r;function i(){t.console&&"function"==typeof t.console.log&&t.console.log.apply(t.console,arguments)}"object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&process.versions.v8?(n=t=>Buffer.from(t,"base64").toString("latin1"),r=t=>Buffer.from(t,"latin1").toString("base64")):(n=globalThis.atob,r=globalThis.btoa);var s=function(e){t.console&&("function"==typeof t.console.warn?t.console.warn.apply(t.console,arguments):i.call(null,arguments))},a=function(e){t.console&&("function"==typeof t.console.error?t.console.error.apply(t.console,arguments):i(e))}; +/** + * @license + * Joseph Myers does not specify a particular license for his work. + * + * Author: Joseph Myers + * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js + * + * Modified by: Owen Leong + */function o(t,e){var n=t[0],r=t[1],i=t[2],s=t[3];n=h(n,r,i,s,e[0],7,-680876936),s=h(s,n,r,i,e[1],12,-389564586),i=h(i,s,n,r,e[2],17,606105819),r=h(r,i,s,n,e[3],22,-1044525330),n=h(n,r,i,s,e[4],7,-176418897),s=h(s,n,r,i,e[5],12,1200080426),i=h(i,s,n,r,e[6],17,-1473231341),r=h(r,i,s,n,e[7],22,-45705983),n=h(n,r,i,s,e[8],7,1770035416),s=h(s,n,r,i,e[9],12,-1958414417),i=h(i,s,n,r,e[10],17,-42063),r=h(r,i,s,n,e[11],22,-1990404162),n=h(n,r,i,s,e[12],7,1804603682),s=h(s,n,r,i,e[13],12,-40341101),i=h(i,s,n,r,e[14],17,-1502002290),n=c(n,r=h(r,i,s,n,e[15],22,1236535329),i,s,e[1],5,-165796510),s=c(s,n,r,i,e[6],9,-1069501632),i=c(i,s,n,r,e[11],14,643717713),r=c(r,i,s,n,e[0],20,-373897302),n=c(n,r,i,s,e[5],5,-701558691),s=c(s,n,r,i,e[10],9,38016083),i=c(i,s,n,r,e[15],14,-660478335),r=c(r,i,s,n,e[4],20,-405537848),n=c(n,r,i,s,e[9],5,568446438),s=c(s,n,r,i,e[14],9,-1019803690),i=c(i,s,n,r,e[3],14,-187363961),r=c(r,i,s,n,e[8],20,1163531501),n=c(n,r,i,s,e[13],5,-1444681467),s=c(s,n,r,i,e[2],9,-51403784),i=c(i,s,n,r,e[7],14,1735328473),n=u(n,r=c(r,i,s,n,e[12],20,-1926607734),i,s,e[5],4,-378558),s=u(s,n,r,i,e[8],11,-2022574463),i=u(i,s,n,r,e[11],16,1839030562),r=u(r,i,s,n,e[14],23,-35309556),n=u(n,r,i,s,e[1],4,-1530992060),s=u(s,n,r,i,e[4],11,1272893353),i=u(i,s,n,r,e[7],16,-155497632),r=u(r,i,s,n,e[10],23,-1094730640),n=u(n,r,i,s,e[13],4,681279174),s=u(s,n,r,i,e[0],11,-358537222),i=u(i,s,n,r,e[3],16,-722521979),r=u(r,i,s,n,e[6],23,76029189),n=u(n,r,i,s,e[9],4,-640364487),s=u(s,n,r,i,e[12],11,-421815835),i=u(i,s,n,r,e[15],16,530742520),n=f(n,r=u(r,i,s,n,e[2],23,-995338651),i,s,e[0],6,-198630844),s=f(s,n,r,i,e[7],10,1126891415),i=f(i,s,n,r,e[14],15,-1416354905),r=f(r,i,s,n,e[5],21,-57434055),n=f(n,r,i,s,e[12],6,1700485571),s=f(s,n,r,i,e[3],10,-1894986606),i=f(i,s,n,r,e[10],15,-1051523),r=f(r,i,s,n,e[1],21,-2054922799),n=f(n,r,i,s,e[8],6,1873313359),s=f(s,n,r,i,e[15],10,-30611744),i=f(i,s,n,r,e[6],15,-1560198380),r=f(r,i,s,n,e[13],21,1309151649),n=f(n,r,i,s,e[4],6,-145523070),s=f(s,n,r,i,e[11],10,-1120210379),i=f(i,s,n,r,e[2],15,718787259),r=f(r,i,s,n,e[9],21,-343485551),t[0]=_(n,t[0]),t[1]=_(r,t[1]),t[2]=_(i,t[2]),t[3]=_(s,t[3])}function l(t,e,n,r,i,s){return e=_(_(e,t),_(r,s)),_(e<>>32-i,n)}function h(t,e,n,r,i,s,a){return l(e&n|~e&r,t,e,i,s,a)}function c(t,e,n,r,i,s,a){return l(e&r|n&~r,t,e,i,s,a)}function u(t,e,n,r,i,s,a){return l(e^n^r,t,e,i,s,a)}function f(t,e,n,r,i,s,a){return l(n^(e|~r),t,e,i,s,a)}function d(t){var e,n=t.length,r=[1732584193,-271733879,-1732584194,271733878];for(e=64;e<=t.length;e+=64)o(r,p(t.substring(e-64,e)));t=t.substring(e-64);var i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=t.charCodeAt(e)<<(e%4<<3);if(i[e>>2]|=128<<(e%4<<3),e>55)for(o(r,i),e=0;e<16;e++)i[e]=0;return i[14]=8*n,o(r,i),r}function p(t){var e,n=[];for(e=0;e<64;e+=4)n[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return n}var g="0123456789abcdef".split("");function m(t){for(var e="",n=0;n<4;n++)e+=g[t>>8*n+4&15]+g[t>>8*n&15];return e}function w(t){return String.fromCharCode(255&t,(65280&t)>>8,(16711680&t)>>16,(4278190080&t)>>24)}function y(t){return function(t){return t.map(w).join("")}(d(t))}var b="5d41402abc4b2a76b9719d911017c592"!=function(t){for(var e=0;e>16)+(e>>16)+(n>>16)<<16|65535&n}return t+e&4294967295} +/** + * @license + * FPDF is released under a permissive license: there is no usage restriction. + * You may embed it freely in your application (commercial or not), with or + * without modifications. + * + * Reference: http://www.fpdf.org/en/script/script37.php + */function v(t,e){var n,r,i,s;if(t!==n){for(var a=(i=t,s=1+(256/t.length|0),new Array(s+1).join(i)),o=[],l=0;l<256;l++)o[l]=l;var h=0;for(l=0;l<256;l++){var c=o[l];h=(h+c+a.charCodeAt(l))%256,o[l]=o[h],o[h]=c}n=t,r=o}else o=r;var u=e.length,f=0,d=0,p="";for(l=0;l€/\f©þdSiz";let s=(e+this.padding).substr(0,32),a=(n+this.padding).substr(0,32);this.O=this.processOwnerPassword(s,a),this.P=-(1+(255^i)),this.encryptionKey=y(s+this.O+this.lsbFirstWord(this.P)+this.hexToBytes(r)).substr(0,5),this.U=v(this.encryptionKey,this.padding)}function L(t){if(/[^\u0000-\u00ff]/.test(t))throw new Error("Invalid PDF Name Object: "+t+", Only accept ASCII characters.");for(var e="",n=t.length,r=0;r126?"#"+("0"+i.toString(16)).slice(-2):t[r]}return e}function A(e){if("object"!=typeof e)throw new Error("Invalid Context passed to initialize PubSub (jsPDF-module)");var n={};this.subscribe=function(t,e,r){if(r=r||!1,"string"!=typeof t||"function"!=typeof e||"boolean"!=typeof r)throw new Error("Invalid arguments passed to PubSub.subscribe (jsPDF-module)");n.hasOwnProperty(t)||(n[t]={});var i=Math.random().toString(35);return n[t][i]=[e,!!r],i},this.unsubscribe=function(t){for(var e in n)if(n[e][t])return delete n[e][t],0===Object.keys(n[e]).length&&delete n[e],!0;return!1},this.publish=function(r){if(n.hasOwnProperty(r)){var i=Array.prototype.slice.call(arguments,1),s=[];for(var o in n[r]){var l=n[r][o];try{l[0].apply(e,i)}catch(h){t.console&&a("jsPDF PubSub Error",h.message,h)}l[1]&&s.push(o)}s.length&&s.forEach(this.unsubscribe)}},this.getTopics=function(){return n}}function S(t){if(!(this instanceof S))return new S(t);var e="opacity,stroke-opacity".split(",");for(var n in t)t.hasOwnProperty(n)&&e.indexOf(n)>=0&&(this[n]=t[n]);this.id="",this.objectNumber=-1}function k(t,e){this.gState=t,this.matrix=e,this.id="",this.objectNumber=-1}function P(t,e,n,r,i){if(!(this instanceof P))return new P(t,e,n,r,i);this.type="axial"===t?2:3,this.coords=e,this.colors=n,k.call(this,r,i)}function F(t,e,n,r,i){if(!(this instanceof F))return new F(t,e,n,r,i);this.boundingBox=t,this.xStep=e,this.yStep=n,this.stream="",this.cloneIndex=0,k.call(this,r,i)}function I(n){var i,a="string"==typeof arguments[0]?arguments[0]:"p",o=arguments[1],l=arguments[2],h=arguments[3],c=[],u=1,f=16,d="S",p=null;"object"==typeof(n=n||{})&&(a=n.orientation,o=n.unit||o,l=n.format||l,h=n.compress||n.compressPdf||h,null!==(p=n.encryption||null)&&(p.userPassword=p.userPassword||"",p.ownerPassword=p.ownerPassword||"",p.userPermissions=p.userPermissions||[]),u="number"==typeof n.userUnit?Math.abs(n.userUnit):1,void 0!==n.precision&&(i=n.precision),void 0!==n.floatPrecision&&(f=n.floatPrecision),d=n.defaultPathOperation||"S"),c=n.filters||(!0===h?["FlateEncode"]:c),o=o||"mm",a=(""+(a||"P")).toLowerCase();var g=n.putOnlyUsedFonts||!1,m={},w={internal:{},__private__:{}};w.__private__.PubSub=A;var y="1.3",b=w.__private__.getPdfVersion=function(){return y};w.__private__.setPdfVersion=function(t){y=t};var _={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};w.__private__.getPageFormats=function(){return _};var v=w.__private__.getPageFormat=function(t){return _[t]};l=l||"a4";var x="compat",k="advanced",C=x;function E(){this.saveGraphicsState(),lt(new Ut(Nt,0,0,-Nt,0,Ln()*Nt).toString()+" cm"),this.setFontSize(this.getFontSize()/Nt),d="n",C=k}function O(){this.restoreGraphicsState(),d="S",C=x}var j=w.__private__.combineFontStyleAndFontWeight=function(t,e){if("bold"==t&&"normal"==e||"bold"==t&&400==e||"normal"==t&&"italic"==e||"bold"==t&&"italic"==e)throw new Error("Invalid Combination of fontweight and fontstyle");return e&&(t=400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"),t};w.advancedAPI=function(t){var e=C===x;return e&&E.call(this),"function"!=typeof t||(t(this),e&&O.call(this)),this},w.compatAPI=function(t){var e=C===k;return e&&O.call(this),"function"!=typeof t||(t(this),e&&E.call(this)),this},w.isAdvancedAPI=function(){return C===k};var B,M=function(t){if(C!==k)throw new Error(t+" is only available in 'advanced' API mode. You need to call advancedAPI() first.")},q=w.roundToPrecision=w.__private__.roundToPrecision=function(t,e){var n=i||e;if(isNaN(t)||isNaN(n))throw new Error("Invalid argument passed to jsPDF.roundToPrecision");return t.toFixed(n).replace(/0+$/,"")};B=w.hpf=w.__private__.hpf="number"==typeof f?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return q(t,f)}:"smart"===f?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return q(t,t>-1&&t<1?16:5)}:function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return q(t,16)};var R=w.f2=w.__private__.f2=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f2");return q(t,2)},T=w.__private__.f3=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f3");return q(t,3)},D=w.scale=w.__private__.scale=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.scale");return C===x?t*Nt:C===k?t:void 0},z=function(t){return D(function(t){return C===x?Ln()-t:C===k?t:void 0}(t))};w.__private__.setPrecision=w.setPrecision=function(t){"number"==typeof parseInt(t,10)&&(i=parseInt(t,10))};var U,W="00000000000000000000000000000000",H=w.__private__.getFileId=function(){return W},V=w.__private__.setFileId=function(t){return W=void 0!==t&&/^[a-fA-F0-9]{32}$/.test(t)?t.toUpperCase():W.split("").map(function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))}).join(""),null!==p&&(Fe=new N(p.userPermissions,p.userPassword,p.ownerPassword,W)),W};w.setFileId=function(t){return V(t),this},w.getFileId=function(){return H()};var G=w.__private__.convertDateToPDFDate=function(t){var e=t.getTimezoneOffset(),n=e<0?"+":"-",r=Math.floor(Math.abs(e/60)),i=Math.abs(e%60),s=[n,K(r),"'",K(i),"'"].join("");return["D:",t.getFullYear(),K(t.getMonth()+1),K(t.getDate()),K(t.getHours()),K(t.getMinutes()),K(t.getSeconds()),s].join("")},Z=w.__private__.convertPDFDateToDate=function(t){var e=parseInt(t.substr(2,4),10),n=parseInt(t.substr(6,2),10)-1,r=parseInt(t.substr(8,2),10),i=parseInt(t.substr(10,2),10),s=parseInt(t.substr(12,2),10),a=parseInt(t.substr(14,2),10);return new Date(e,n,r,i,s,a,0)},Y=w.__private__.setCreationDate=function(t){var e;if(void 0===t&&(t=new Date),t instanceof Date)e=G(t);else{if(!/^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/.test(t))throw new Error("Invalid argument passed to jsPDF.setCreationDate");e=t}return U=e},J=w.__private__.getCreationDate=function(t){var e=U;return"jsDate"===t&&(e=Z(U)),e};w.setCreationDate=function(t){return Y(t),this},w.getCreationDate=function(t){return J(t)};var X,K=w.__private__.padd2=function(t){return("0"+parseInt(t)).slice(-2)},$=w.__private__.padd2Hex=function(t){return("00"+(t=t.toString())).substr(t.length)},Q=0,tt=[],et=[],nt=0,rt=[],it=[],st=!1,at=et;w.__private__.setCustomOutputDestination=function(t){st=!0,at=t};var ot=function(t){st||(at=t)};w.__private__.resetCustomOutputDestination=function(){st=!1,at=et};var lt=w.__private__.out=function(t){return t=t.toString(),nt+=t.length+1,at.push(t),at},ht=w.__private__.write=function(t){return lt(1===arguments.length?t.toString():Array.prototype.join.call(arguments," "))},ct=w.__private__.getArrayBuffer=function(t){for(var e=t.length,n=new ArrayBuffer(e),r=new Uint8Array(n);e--;)r[e]=t.charCodeAt(e);return n},ut=[["Helvetica","helvetica","normal","WinAnsiEncoding"],["Helvetica-Bold","helvetica","bold","WinAnsiEncoding"],["Helvetica-Oblique","helvetica","italic","WinAnsiEncoding"],["Helvetica-BoldOblique","helvetica","bolditalic","WinAnsiEncoding"],["Courier","courier","normal","WinAnsiEncoding"],["Courier-Bold","courier","bold","WinAnsiEncoding"],["Courier-Oblique","courier","italic","WinAnsiEncoding"],["Courier-BoldOblique","courier","bolditalic","WinAnsiEncoding"],["Times-Roman","times","normal","WinAnsiEncoding"],["Times-Bold","times","bold","WinAnsiEncoding"],["Times-Italic","times","italic","WinAnsiEncoding"],["Times-BoldItalic","times","bolditalic","WinAnsiEncoding"],["ZapfDingbats","zapfdingbats","normal",null],["Symbol","symbol","normal",null]];w.__private__.getStandardFonts=function(){return ut};var ft=n.fontSize||16;w.__private__.setFontSize=w.setFontSize=function(t){return ft=C===k?t/Nt:t,this};var dt,pt=w.__private__.getFontSize=w.getFontSize=function(){return C===x?ft:ft*Nt},gt=n.R2L||!1;w.__private__.setR2L=w.setR2L=function(t){return gt=t,this},w.__private__.getR2L=w.getR2L=function(){return gt};var mt,wt=w.__private__.setZoomMode=function(t){if(/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(t))dt=t;else if(isNaN(t)){if(-1===[void 0,null,"fullwidth","fullheight","fullpage","original"].indexOf(t))throw new Error('zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "'+t+'" is not recognized.');dt=t}else dt=parseInt(t,10)};w.__private__.getZoomMode=function(){return dt};var yt,bt=w.__private__.setPageMode=function(t){if(-1==[void 0,null,"UseNone","UseOutlines","UseThumbs","FullScreen"].indexOf(t))throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "'+t+'" is not recognized.');mt=t};w.__private__.getPageMode=function(){return mt};var _t=w.__private__.setLayoutMode=function(t){if(-1==[void 0,null,"continuous","single","twoleft","tworight","two"].indexOf(t))throw new Error('Layout mode must be one of continuous, single, twoleft, tworight. "'+t+'" is not recognized.');yt=t};w.__private__.getLayoutMode=function(){return yt},w.__private__.setDisplayMode=w.setDisplayMode=function(t,e,n){return wt(t),_t(e),bt(n),this};var vt={title:"",subject:"",author:"",keywords:"",creator:""};w.__private__.getDocumentProperty=function(t){if(-1===Object.keys(vt).indexOf(t))throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");return vt[t]},w.__private__.getDocumentProperties=function(){return vt},w.__private__.setDocumentProperties=w.setProperties=w.setDocumentProperties=function(t){for(var e in vt)vt.hasOwnProperty(e)&&t[e]&&(vt[e]=t[e]);return this},w.__private__.setDocumentProperty=function(t,e){if(-1===Object.keys(vt).indexOf(t))throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");return vt[t]=e};var xt,Nt,Lt,At,St,kt={},Pt={},Ft=[],It={},Ct={},Et={},Ot={},jt=null,Bt=0,Mt=[],qt=new A(w),Rt=n.hotfixes||[],Tt={},Dt={},zt=[],Ut=function(t,e,n,r,i,s){if(!(this instanceof Ut))return new Ut(t,e,n,r,i,s);isNaN(t)&&(t=1),isNaN(e)&&(e=0),isNaN(n)&&(n=0),isNaN(r)&&(r=1),isNaN(i)&&(i=0),isNaN(s)&&(s=0),this._matrix=[t,e,n,r,i,s]};Object.defineProperty(Ut.prototype,"sx",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Ut.prototype,"shy",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Ut.prototype,"shx",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Ut.prototype,"sy",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Ut.prototype,"tx",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Ut.prototype,"ty",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Ut.prototype,"a",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Ut.prototype,"b",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Ut.prototype,"c",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Ut.prototype,"d",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Ut.prototype,"e",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Ut.prototype,"f",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Ut.prototype,"rotation",{get:function(){return Math.atan2(this.shx,this.sx)}}),Object.defineProperty(Ut.prototype,"scaleX",{get:function(){return this.decompose().scale.sx}}),Object.defineProperty(Ut.prototype,"scaleY",{get:function(){return this.decompose().scale.sy}}),Object.defineProperty(Ut.prototype,"isIdentity",{get:function(){return 1===this.sx&&0===this.shy&&0===this.shx&&1===this.sy&&0===this.tx&&0===this.ty}}),Ut.prototype.join=function(t){return[this.sx,this.shy,this.shx,this.sy,this.tx,this.ty].map(B).join(t)},Ut.prototype.multiply=function(t){var e=t.sx*this.sx+t.shy*this.shx,n=t.sx*this.shy+t.shy*this.sy,r=t.shx*this.sx+t.sy*this.shx,i=t.shx*this.shy+t.sy*this.sy,s=t.tx*this.sx+t.ty*this.shx+this.tx,a=t.tx*this.shy+t.ty*this.sy+this.ty;return new Ut(e,n,r,i,s,a)},Ut.prototype.decompose=function(){var t=this.sx,e=this.shy,n=this.shx,r=this.sy,i=this.tx,s=this.ty,a=Math.sqrt(t*t+e*e),o=(t/=a)*n+(e/=a)*r;n-=t*o,r-=e*o;var l=Math.sqrt(n*n+r*r);return o/=l,t*(r/=l)>16&255,i=h>>8&255,s=255&h}if(void 0===i||void 0===a&&r===i&&i===s)n="string"==typeof r?r+" "+o[0]:2===t.precision?R(r/255)+" "+o[0]:T(r/255)+" "+o[0];else if(void 0===a||"object"==typeof a){if(a&&!isNaN(a.a)&&0===a.a)return["1.","1.","1.",o[1]].join(" ");n="string"==typeof r?[r,i,s,o[1]].join(" "):2===t.precision?[R(r/255),R(i/255),R(s/255),o[1]].join(" "):[T(r/255),T(i/255),T(s/255),o[1]].join(" ")}else n="string"==typeof r?[r,i,s,a,o[2]].join(" "):2===t.precision?[R(r),R(i),R(s),R(a),o[2]].join(" "):[T(r),T(i),T(s),T(a),o[2]].join(" ");return n},ee=w.__private__.getFilters=function(){return c},ne=w.__private__.putStream=function(t){var e=(t=t||{}).data||"",n=t.filters||ee(),r=t.alreadyAppliedFilters||[],i=t.addLength1||!1,s=e.length,a=t.objectId,o=function(t){return t};if(null!==p&&void 0===a)throw new Error("ObjectId must be passed to putStream for file encryption");null!==p&&(o=Fe.encryptor(a,0));var l={};!0===n&&(n=["FlateEncode"]);var h=t.additionalKeyValues||[],c=(l=void 0!==I.API.processDataByFilters?I.API.processDataByFilters(e,n):{data:e,reverseChain:[]}).reverseChain+(Array.isArray(r)?r.join(" "):r.toString());if(0!==l.data.length&&(h.push({key:"Length",value:l.data.length}),!0===i&&h.push({key:"Length1",value:s})),0!=c.length)if(c.split("/").length-1==1)h.push({key:"Filter",value:c});else{h.push({key:"Filter",value:"["+c+"]"});for(var u=0;u>"),0!==l.data.length&&(lt("stream"),lt(o(l.data)),lt("endstream"))},re=w.__private__.putPage=function(t){var e=t.number,n=t.data,r=t.objId,i=t.contentsObjId;Jt(r,!0),lt("<>"),lt("endobj");var s=n.join("\n");return C===k&&(s+="\nQ"),Jt(i,!0),ne({data:s,filters:ee(),objectId:i}),lt("endobj"),r},ie=w.__private__.putPages=function(){var t,e,n=[];for(t=1;t<=Bt;t++)Mt[t].objId=Yt(),Mt[t].contentsObjId=Yt();for(t=1;t<=Bt;t++)n.push(re({number:t,data:it[t],objId:Mt[t].objId,contentsObjId:Mt[t].contentsObjId,mediaBox:Mt[t].mediaBox,cropBox:Mt[t].cropBox,bleedBox:Mt[t].bleedBox,trimBox:Mt[t].trimBox,artBox:Mt[t].artBox,userUnit:Mt[t].userUnit,rootDictionaryObjId:Kt,resourceDictionaryObjId:$t}));Jt(Kt,!0),lt("<>"),lt("endobj"),qt.publish("postPutPages")},se=function(t){qt.publish("putFont",{font:t,out:lt,newObject:Zt,putStream:ne}),!0!==t.isAlreadyPutted&&(t.objectNumber=Zt(),lt("<<"),lt("/Type /Font"),lt("/BaseFont /"+L(t.postScriptName)),lt("/Subtype /Type1"),"string"==typeof t.encoding&<("/Encoding /"+t.encoding),lt("/FirstChar 32"),lt("/LastChar 255"),lt(">>"),lt("endobj"))},ae=function(t){t.objectNumber=Zt();var e=[];e.push({key:"Type",value:"/XObject"}),e.push({key:"Subtype",value:"/Form"}),e.push({key:"BBox",value:"["+[B(t.x),B(t.y),B(t.x+t.width),B(t.y+t.height)].join(" ")+"]"}),e.push({key:"Matrix",value:"["+t.matrix.toString()+"]"});var n=t.pages[1].join("\n");ne({data:n,additionalKeyValues:e,objectId:t.objectNumber}),lt("endobj")},oe=function(t,e){e||(e=21);var n=Zt(),r=function(t,e){var n,r=[],i=1/(e-1);for(n=0;n<1;n+=i)r.push(n);if(r.push(1),0!=t[0].offset){var s={offset:0,color:t[0].color};t.unshift(s)}if(1!=t[t.length-1].offset){var a={offset:1,color:t[t.length-1].color};t.push(a)}for(var o="",l=0,h=0;ht[l+1].offset;)l++;var c=t[l].offset,u=(n-c)/(t[l+1].offset-c),f=t[l].color,d=t[l+1].color;o+=$(Math.round((1-u)*f[0]+u*d[0]).toString(16))+$(Math.round((1-u)*f[1]+u*d[1]).toString(16))+$(Math.round((1-u)*f[2]+u*d[2]).toString(16))}return o.trim()}(t.colors,e),i=[];i.push({key:"FunctionType",value:"0"}),i.push({key:"Domain",value:"[0.0 1.0]"}),i.push({key:"Size",value:"["+e+"]"}),i.push({key:"BitsPerSample",value:"8"}),i.push({key:"Range",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),i.push({key:"Decode",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),ne({data:r,additionalKeyValues:i,alreadyAppliedFilters:["/ASCIIHexDecode"],objectId:n}),lt("endobj"),t.objectNumber=Zt(),lt("<< /ShadingType "+t.type),lt("/ColorSpace /DeviceRGB");var s="/Coords ["+B(parseFloat(t.coords[0]))+" "+B(parseFloat(t.coords[1]))+" ";2===t.type?s+=B(parseFloat(t.coords[2]))+" "+B(parseFloat(t.coords[3])):s+=B(parseFloat(t.coords[2]))+" "+B(parseFloat(t.coords[3]))+" "+B(parseFloat(t.coords[4]))+" "+B(parseFloat(t.coords[5])),lt(s+="]"),t.matrix&<("/Matrix ["+t.matrix.toString()+"]"),lt("/Function "+n+" 0 R"),lt("/Extend [true true]"),lt(">>"),lt("endobj")},le=function(t,e){var n=Yt(),r=Zt();e.push({resourcesOid:n,objectOid:r}),t.objectNumber=r;var i=[];i.push({key:"Type",value:"/Pattern"}),i.push({key:"PatternType",value:"1"}),i.push({key:"PaintType",value:"1"}),i.push({key:"TilingType",value:"1"}),i.push({key:"BBox",value:"["+t.boundingBox.map(B).join(" ")+"]"}),i.push({key:"XStep",value:B(t.xStep)}),i.push({key:"YStep",value:B(t.yStep)}),i.push({key:"Resources",value:n+" 0 R"}),t.matrix&&i.push({key:"Matrix",value:"["+t.matrix.toString()+"]"}),ne({data:t.stream,additionalKeyValues:i,objectId:t.objectNumber}),lt("endobj")},he=function(t){for(var e in t.objectNumber=Zt(),lt("<<"),t)switch(e){case"opacity":lt("/ca "+R(t[e]));break;case"stroke-opacity":lt("/CA "+R(t[e]))}lt(">>"),lt("endobj")},ce=function(t){Jt(t.resourcesOid,!0),lt("<<"),lt("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),function(){for(var t in lt("/Font <<"),kt)kt.hasOwnProperty(t)&&(!1===g||!0===g&&m.hasOwnProperty(t))&<("/"+t+" "+kt[t].objectNumber+" 0 R");lt(">>")}(),function(){if(Object.keys(It).length>0){for(var t in lt("/Shading <<"),It)It.hasOwnProperty(t)&&It[t]instanceof P&&It[t].objectNumber>=0&<("/"+t+" "+It[t].objectNumber+" 0 R");qt.publish("putShadingPatternDict"),lt(">>")}}(),function(t){if(Object.keys(It).length>0){for(var e in lt("/Pattern <<"),It)It.hasOwnProperty(e)&&It[e]instanceof w.TilingPattern&&It[e].objectNumber>=0&&It[e].objectNumber>")}}(t.objectOid),function(){if(Object.keys(Et).length>0){var t;for(t in lt("/ExtGState <<"),Et)Et.hasOwnProperty(t)&&Et[t].objectNumber>=0&<("/"+t+" "+Et[t].objectNumber+" 0 R");qt.publish("putGStateDict"),lt(">>")}}(),function(){for(var t in lt("/XObject <<"),Tt)Tt.hasOwnProperty(t)&&Tt[t].objectNumber>=0&<("/"+t+" "+Tt[t].objectNumber+" 0 R");qt.publish("putXobjectDict"),lt(">>")}(),lt(">>"),lt("endobj")},ue=function(t){Pt[t.fontName]=Pt[t.fontName]||{},Pt[t.fontName][t.fontStyle]=t.id},fe=function(t,e,n,r,i){var s={id:"F"+(Object.keys(kt).length+1).toString(10),postScriptName:t,fontName:e,fontStyle:n,encoding:r,isStandardFont:i||!1,metadata:{}};return qt.publish("addFont",{font:s,instance:this}),kt[s.id]=s,ue(s),s.id},de=w.__private__.pdfEscape=w.pdfEscape=function(t,e){return function(t,e){var n,r,i,s,a,o,l,h,c;if(i=(e=e||{}).sourceEncoding||"Unicode",a=e.outputEncoding,(e.autoencode||a)&&kt[xt].metadata&&kt[xt].metadata[i]&&kt[xt].metadata[i].encoding&&(s=kt[xt].metadata[i].encoding,!a&&kt[xt].encoding&&(a=kt[xt].encoding),!a&&s.codePages&&(a=s.codePages[0]),"string"==typeof a&&(a=s[a]),a)){for(l=!1,o=[],n=0,r=t.length;n>8&&(l=!0);t=o.join("")}for(n=t.length;void 0===l&&0!==n;)t.charCodeAt(n-1)>>8&&(l=!0),n--;if(!l)return t;for(o=e.noBOM?[]:[254,255],n=0,r=t.length;n>8)>>8)throw new Error("Character at position "+n+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");o.push(c),o.push(h-(c<<8))}return String.fromCharCode.apply(void 0,o)}(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},pe=w.__private__.beginPage=function(t){it[++Bt]=[],Mt[Bt]={objId:0,contentsObjId:0,userUnit:Number(u),artBox:null,bleedBox:null,cropBox:null,trimBox:null,mediaBox:{bottomLeftX:0,bottomLeftY:0,topRightX:Number(t[0]),topRightY:Number(t[1])}},we(Bt),ot(it[X])},ge=function(t,e){var n,r,i;switch(a=e||a,"string"==typeof t&&(n=v(t.toLowerCase()),Array.isArray(n)&&(r=n[0],i=n[1])),Array.isArray(t)&&(r=t[0]*Nt,i=t[1]*Nt),isNaN(r)&&(r=l[0],i=l[1]),(r>14400||i>14400)&&(s("A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"),r=Math.min(14400,r),i=Math.min(14400,i)),l=[r,i],a.substr(0,1)){case"l":i>r&&(l=[i,r]);break;case"p":r>i&&(l=[i,r])}pe(l),Je(Ze),lt(rn),0!==cn&<(cn+" J"),0!==un&<(un+" j"),qt.publish("addPage",{pageNumber:Bt})},me=function(t){t>0&&t<=Bt&&(it.splice(t,1),Mt.splice(t,1),Bt--,X>Bt&&(X=Bt),this.setPage(X))},we=function(t){t>0&&t<=Bt&&(X=t)},ye=w.__private__.getNumberOfPages=w.getNumberOfPages=function(){return it.length-1},be=function(t,e,n){var r,i=void 0;return n=n||{},t=void 0!==t?t:kt[xt].fontName,e=void 0!==e?e:kt[xt].fontStyle,r=t.toLowerCase(),void 0!==Pt[r]&&void 0!==Pt[r][e]?i=Pt[r][e]:void 0!==Pt[t]&&void 0!==Pt[t][e]?i=Pt[t][e]:!1===n.disableWarning&&s("Unable to look up font label for font '"+t+"', '"+e+"'. Refer to getFontList() for available fonts."),i||n.noFallback||null==(i=Pt.times[e])&&(i=Pt.times.normal),i},_e=w.__private__.putInfo=function(){var t=Zt(),e=function(t){return t};for(var n in null!==p&&(e=Fe.encryptor(t,0)),lt("<<"),lt("/Producer ("+de(e("jsPDF "+I.version))+")"),vt)vt.hasOwnProperty(n)&&vt[n]&<("/"+n.substr(0,1).toUpperCase()+n.substr(1)+" ("+de(e(vt[n]))+")");lt("/CreationDate ("+de(e(U))+")"),lt(">>"),lt("endobj")},ve=w.__private__.putCatalog=function(t){var e=(t=t||{}).rootDictionaryObjId||Kt;switch(Zt(),lt("<<"),lt("/Type /Catalog"),lt("/Pages "+e+" 0 R"),dt||(dt="fullwidth"),dt){case"fullwidth":lt("/OpenAction [3 0 R /FitH null]");break;case"fullheight":lt("/OpenAction [3 0 R /FitV null]");break;case"fullpage":lt("/OpenAction [3 0 R /Fit]");break;case"original":lt("/OpenAction [3 0 R /XYZ null null 1]");break;default:var n=""+dt;"%"===n.substr(n.length-1)&&(dt=parseInt(dt)/100),"number"==typeof dt&<("/OpenAction [3 0 R /XYZ null null "+R(dt)+"]")}switch(yt||(yt="continuous"),yt){case"continuous":lt("/PageLayout /OneColumn");break;case"single":lt("/PageLayout /SinglePage");break;case"two":case"twoleft":lt("/PageLayout /TwoColumnLeft");break;case"tworight":lt("/PageLayout /TwoColumnRight")}mt&<("/PageMode /"+mt),qt.publish("putCatalog"),lt(">>"),lt("endobj")},xe=w.__private__.putTrailer=function(){lt("trailer"),lt("<<"),lt("/Size "+(Q+1)),lt("/Root "+Q+" 0 R"),lt("/Info "+(Q-1)+" 0 R"),null!==p&<("/Encrypt "+Fe.oid+" 0 R"),lt("/ID [ <"+W+"> <"+W+"> ]"),lt(">>")},Ne=w.__private__.putHeader=function(){lt("%PDF-"+y),lt("%ºß¬à")},Le=w.__private__.putXRef=function(){var t="0000000000";lt("xref"),lt("0 "+(Q+1)),lt("0000000000 65535 f ");for(var e=1;e<=Q;e++)"function"==typeof tt[e]?lt((t+tt[e]()).slice(-10)+" 00000 n "):void 0!==tt[e]?lt((t+tt[e]).slice(-10)+" 00000 n "):lt("0000000000 00000 n ")},Ae=w.__private__.buildDocument=function(){var t;Q=0,nt=0,et=[],tt=[],rt=[],Kt=Yt(),$t=Yt(),ot(et),qt.publish("buildDocument"),Ne(),ie(),function(){qt.publish("putAdditionalObjects");for(var t=0;t"),lt("/O <"+Fe.toHexString(Fe.O)+">"),lt("/P "+Fe.P),lt(">>"),lt("endobj")),_e(),ve();var e=nt;return Le(),xe(),lt("startxref"),lt(""+e),lt("%%EOF"),ot(it[X]),et.join("\n")},Se=w.__private__.getBlob=function(t){return new Blob([ct(t)],{type:"application/pdf"})},ke=w.output=w.__private__.output=(Gt=function(e,n){switch("string"==typeof(n=n||{})?n={filename:n}:n.filename=n.filename||"generated.pdf",e){case void 0:return Ae();case"save":w.save(n.filename);break;case"arraybuffer":return ct(Ae());case"blob":return Se(Ae());case"bloburi":case"bloburl":if(void 0!==t.URL&&"function"==typeof t.URL.createObjectURL)return t.URL&&t.URL.createObjectURL(Se(Ae()))||void 0;s("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");break;case"datauristring":case"dataurlstring":var i="",a=Ae();try{i=r(a)}catch(o){i=r(unescape(encodeURIComponent(a)))}return"data:application/pdf;filename="+n.filename+";base64,"+i;case"datauri":case"dataurl":return t.document.location.href=this.output("datauristring",n);default:return null}},Gt.foo=function(){try{return Gt.apply(this,arguments)}catch(r){var e=r.stack||"";~e.indexOf(" at ")&&(e=e.split(" at ")[1]);var n="Error in function "+e.split("\n")[0].split("<")[0]+": "+r.message;if(!t.console)throw new Error(n);t.console.error(n,r),t.alert&&alert(n)}},Gt.foo.bar=Gt,Gt.foo),Pe=function(t){return!0===Array.isArray(Rt)&&Rt.indexOf(t)>-1};switch(o){case"pt":Nt=1;break;case"mm":Nt=72/25.4;break;case"cm":Nt=72/2.54;break;case"in":Nt=72;break;case"px":Nt=1==Pe("px_scaling")?.75:96/72;break;case"pc":case"em":Nt=12;break;case"ex":Nt=6;break;default:if("number"!=typeof o)throw new Error("Invalid unit: "+o);Nt=o}var Fe=null;Y(),V();var Ie=w.__private__.getPageInfo=w.getPageInfo=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfo");return{objId:Mt[t].objId,pageNumber:t,pageContext:Mt[t]}},Ce=w.__private__.getPageInfoByObjId=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId");for(var e in Mt)if(Mt[e].objId===t)break;return Ie(e)},Ee=w.__private__.getCurrentPageInfo=w.getCurrentPageInfo=function(){return{objId:Mt[X].objId,pageNumber:X,pageContext:Mt[X]}};w.addPage=function(){return ge.apply(this,arguments),this},w.setPage=function(){return we.apply(this,arguments),ot.call(this,it[X]),this},w.insertPage=function(t){return this.addPage(),this.movePage(X,t),this},w.movePage=function(t,e){var n,r;if(t>e){n=it[t],r=Mt[t];for(var i=t;i>e;i--)it[i]=it[i-1],Mt[i]=Mt[i-1];it[e]=n,Mt[e]=r,this.setPage(e)}else if(t0&&("string"==typeof t?t=p.splitTextToSize(t,c):"[object Array]"===Object.prototype.toString.call(t)&&(t=t.reduce(function(t,e){return t.concat(p.splitTextToSize(e,c))},[]))),s={text:t,x:e,y:n,options:r,mutex:{pdfEscape:de,activeFontKey:xt,fonts:kt,activeFontSize:ft}},qt.publish("preProcessText",s),t=s.text,o=(r=s.options).angle,d instanceof Ut==0&&o&&"number"==typeof o){o*=Math.PI/180,0===r.rotationDirection&&(o=-o),C===k&&(o=-o);var O=Math.cos(o),j=Math.sin(o);d=new Ut(O,j,-j,O,0,0)}else o&&o instanceof Ut&&(d=o);C!==k||d||(d=Ht),void 0!==(h=r.charSpace||ln)&&(y+=B(D(h))+" Tc\n",this.setCharSpace(this.getCharSpace()||0)),void 0!==(f=r.horizontalScale)&&(y+=B(100*f)+" Tz\n"),r.lang;var q=-1,R=void 0!==r.renderingMode?r.renderingMode:r.stroke,T=p.internal.getCurrentPageInfo().pageContext;switch(R){case 0:case!1:case"fill":q=0;break;case 1:case!0:case"stroke":q=1;break;case 2:case"fillThenStroke":q=2;break;case 3:case"invisible":q=3;break;case 4:case"fillAndAddForClipping":q=4;break;case 5:case"strokeAndAddPathForClipping":q=5;break;case 6:case"fillThenStrokeAndAddToPathForClipping":q=6;break;case 7:case"addToPathForClipping":q=7}var z=void 0!==T.usedRenderingMode?T.usedRenderingMode:-1;-1!==q?y+=q+" Tr\n":-1!==z&&(y+="0 Tr\n"),-1!==q&&(T.usedRenderingMode=q),l=r.align||"left";var U,W=ft*b,H=p.internal.pageSize.getWidth(),V=kt[xt];h=r.charSpace||ln,c=r.maxWidth||0,u=Object.assign({autoencode:!0,noBOM:!0},r.flags);var G=[],Z=function(t){return p.getStringUnitWidth(t,{font:V,charSpace:h,fontSize:ft,doKerning:!1})*ft/_};if("[object Array]"===Object.prototype.toString.call(t)){var Y;a=x(t),"left"!==l&&(U=a.map(Z));var J,X=0;if("right"===l){e-=U[0],t=[],F=a.length;for(var K=0;K0?(c-U[tt])/r:0;tt":")"),nt=parseFloat(a[ht][1]),rt=parseFloat(a[ht][2]);break;case 0:it=(w?"<":"(")+a[ht]+(w?">":")"),nt=Qe(e),rt=tn(n)}void 0!==G&&void 0!==G[ht]&&(at=G[ht]+" Tw\n"),0===ht?t.push(at+ot(nt,rt,d)+it):0===st?t.push(at+it):1===st&&t.push(at+ot(nt,rt,d)+it)}t=0===st?t.join(" Tj\nT* "):t.join(" Tj\n"),t+=" Tj\n";var ct="BT\n/";return ct+=xt+" "+ft+" Tf\n",ct+=B(ft*b)+" TL\n",ct+=an+"\n",ct+=y,ct+=t,lt(ct+="ET"),m[xt]=!0,p};var Oe=w.__private__.clip=w.clip=function(t){return lt("evenodd"===t?"W*":"W"),this};w.clipEvenOdd=function(){return Oe("evenodd")},w.__private__.discardPath=w.discardPath=function(){return lt("n"),this};var je=w.__private__.isValidStyle=function(t){var e=!1;return-1!==[void 0,null,"S","D","F","DF","FD","f","f*","B","B*","n"].indexOf(t)&&(e=!0),e};w.__private__.setDefaultPathOperation=w.setDefaultPathOperation=function(t){return je(t)&&(d=t),this};var Be=w.__private__.getStyle=w.getStyle=function(t){var e=d;switch(t){case"D":case"S":e="S";break;case"F":e="f";break;case"FD":case"DF":e="B";break;case"f":case"f*":case"B":case"B*":e=t}return e},Me=w.close=function(){return lt("h"),this};w.stroke=function(){return lt("S"),this},w.fill=function(t){return qe("f",t),this},w.fillEvenOdd=function(t){return qe("f*",t),this},w.fillStroke=function(t){return qe("B",t),this},w.fillStrokeEvenOdd=function(t){return qe("B*",t),this};var qe=function(t,e){"object"==typeof e?De(e,t):lt(t)},Re=function(t){null===t||C===k&&void 0===t||(t=Be(t),lt(t))};function Te(t,e,n,r,i){var s=new F(e||this.boundingBox,n||this.xStep,r||this.yStep,this.gState,i||this.matrix);s.stream=this.stream;var a=t+"$$"+this.cloneIndex+++"$$";return Vt(a,s),s}var De=function(t,e){var n=Ct[t.key],r=It[n];if(r instanceof P)lt("q"),lt(ze(e)),r.gState&&w.setGState(r.gState),lt(t.matrix.toString()+" cm"),lt("/"+n+" sh"),lt("Q");else if(r instanceof F){var i=new Ut(1,0,0,-1,0,Ln());t.matrix&&(i=i.multiply(t.matrix||Ht),n=Te.call(r,t.key,t.boundingBox,t.xStep,t.yStep,i).id),lt("q"),lt("/Pattern cs"),lt("/"+n+" scn"),r.gState&&w.setGState(r.gState),lt(e),lt("Q")}},ze=function(t){switch(t){case"f":case"F":case"n":return"W n";case"f*":return"W* n";case"B":case"S":return"W S";case"B*":return"W* S"}},Ue=w.moveTo=function(t,e){return lt(B(D(t))+" "+B(z(e))+" m"),this},We=w.lineTo=function(t,e){return lt(B(D(t))+" "+B(z(e))+" l"),this},He=w.curveTo=function(t,e,n,r,i,s){return lt([B(D(t)),B(z(e)),B(D(n)),B(z(r)),B(D(i)),B(z(s)),"c"].join(" ")),this};w.__private__.line=w.line=function(t,e,n,r,i){if(isNaN(t)||isNaN(e)||isNaN(n)||isNaN(r)||!je(i))throw new Error("Invalid arguments passed to jsPDF.line");return C===x?this.lines([[n-t,r-e]],t,e,[1,1],i||"S"):this.lines([[n-t,r-e]],t,e,[1,1]).stroke()},w.__private__.lines=w.lines=function(t,e,n,r,i,s){var a,o,l,h,c,u,f,d,p,g,m,w;if("number"==typeof t&&(w=n,n=e,e=t,t=w),r=r||[1,1],s=s||!1,isNaN(e)||isNaN(n)||!Array.isArray(t)||!Array.isArray(r)||!je(i)||"boolean"!=typeof s)throw new Error("Invalid arguments passed to jsPDF.lines");for(Ue(e,n),a=r[0],o=r[1],h=t.length,g=e,m=n,l=0;l>8&255,t>>16&255,t>>24&255)},N.prototype.toHexString=function(t){return t.split("").map(function(t){return("0"+(255&t.charCodeAt(0)).toString(16)).slice(-2)}).join("")},N.prototype.hexToBytes=function(t){for(var e=[],n=0;n>8&255,t>>16&255,255&e,e>>8&255)).substr(0,10);return function(t){return v(n,t)}},S.prototype.equals=function(t){var e,n="id,objectNumber,equals";if(!t||typeof t!=typeof this)return!1;var r=0;for(e in this)if(!(n.indexOf(e)>=0)){if(this.hasOwnProperty(e)&&!t.hasOwnProperty(e))return!1;if(this[e]!==t[e])return!1;r++}for(e in t)t.hasOwnProperty(e)&&n.indexOf(e)<0&&r--;return 0===r},I.API={events:[]},I.version="3.0.3";var C=I.API,E=1,O=function(t){return t.replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},j=function(t){return t.replace(/\\\\/g,"\\").replace(/\\\(/g,"(").replace(/\\\)/g,")")},B=function(t){return t.toFixed(2)},M=function(t){return t.toFixed(5)};C.__acroform__={};var q=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t},R=function(t){return t*E},T=function(t){var e=new et,n=gt.internal.getHeight(t)||0,r=gt.internal.getWidth(t)||0;return e.BBox=[0,0,Number(B(r)),Number(B(n))],e},D=C.__acroform__.setBit=function(t,e){if(t=t||0,e=e||0,isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.setBit");return t|1<t.split("\n")):i.map(t=>[t]);var s=n,a=gt.internal.getHeight(t)||0;a=a<0?-a:a;var o=gt.internal.getWidth(t)||0;o=o<0?-o:o;var l=function(e,n,r){if(e+10;){e="",s--;var h,c,u=J("3",t,s).height,f=t.multiline?a-s:(a-u)/2,d=f+=2,p=0,g=0,m=0;if(s<=0){e="(...) Tj\n",e+="% Width of Text: "+J(e,t,s=12).width+", FieldWidth:"+o+"\n";break}var w="",y=0;e:for(var b=0;ba)continue t;w+=i[b][m],n=!0,g=b,b--}else{w=" "==(w+=i[b][m]+" ").substr(w.length-1)?w.substr(0,w.length-1):w;var _=parseInt(b),v=l(_,w,s),x=b>=i.length-1;if(v&&!x){w+=" ",m=0;continue}if(v||x){if(x)g=_;else if(t.multiline&&(u+2)*(y+2)+2>a)continue t}else{if(!t.multiline)continue t;if((u+2)*(y+2)+2>a)continue t;g=_}}for(var N="",L=p;L<=g;L++){var A=i[L];if(t.multiline){if(L===g){N+=A[m]+" ",m=(m+1)%A.length;continue}if(L===p){N+=A[A.length-1]+" ";continue}}N+=A[0]+" "}switch(N=" "==N.substr(N.length-1)?N.substr(0,N.length-1):N,c=J(N,t,s).width,t.textAlign){case"right":h=o-c-2;break;case"center":h=(o-c)/2;break;default:h=2}e+=B(h)+" "+B(d)+" Td\n",e+="("+O(N)+") Tj\n",e+=-B(h)+" 0 Td\n",d=-(s+2),c=0,p=n?g:g+1,y++,w="";continue e}break}return r.text=e,r.fontSize=s,r},J=function(t,e,n){var r=e.scope.internal.getFont(e.fontName,e.fontStyle),i=e.scope.getStringUnitWidth(t,{font:r,fontSize:parseFloat(n),charSpace:0})*parseFloat(n);return{height:e.scope.getStringUnitWidth("3",{font:r,fontSize:parseFloat(n),charSpace:0})*parseFloat(n)*1.5,width:i}},X={fields:[],xForms:[],acroFormDictionaryRoot:null,printedOut:!1,internal:null,isInitialized:!1},K=function(t,e){var n={type:"reference",object:t};void 0===e.internal.getPageInfo(t.page).pageContext.annotations.find(function(t){return t.type===n.type&&t.object===n.object})&&e.internal.getPageInfo(t.page).pageContext.annotations.push(n)},$=C.__acroform__.arrayToPdfArray=function(t,e,n){var r=function(t){return t};if(Array.isArray(t)){for(var i="[",s=0;s0?e:void 0}}),Object.defineProperty(this,"Fields",{enumerable:!1,configurable:!1,get:function(){return e}}),Object.defineProperty(this,"DA",{enumerable:!1,configurable:!1,get:function(){if(t){var e=function(t){return t};return this.scope&&(e=this.scope.internal.getEncryptor(this.objId)),"("+O(e(t))+")"}},set:function(e){t=e}})};q(nt,tt);var rt=function(){tt.call(this);var t=4;Object.defineProperty(this,"F",{enumerable:!1,configurable:!1,get:function(){return t},set:function(e){if(isNaN(e))throw new Error('Invalid value "'+e+'" for attribute F supplied.');t=e}}),Object.defineProperty(this,"showWhenPrinted",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(t,3))},set:function(e){!0===Boolean(e)?this.F=H(t,3):this.F=V(t,3)}});var e=0;Object.defineProperty(this,"Ff",{enumerable:!1,configurable:!1,get:function(){return e},set:function(t){if(isNaN(t))throw new Error('Invalid value "'+t+'" for attribute Ff supplied.');e=t}});var n=[];Object.defineProperty(this,"Rect",{enumerable:!1,configurable:!1,get:function(){if(0!==n.length)return n},set:function(t){n=void 0!==t?t:[]}}),Object.defineProperty(this,"x",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[0])?0:n[0]},set:function(t){n[0]=t}}),Object.defineProperty(this,"y",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[1])?0:n[1]},set:function(t){n[1]=t}}),Object.defineProperty(this,"width",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[2])?0:n[2]},set:function(t){n[2]=t}}),Object.defineProperty(this,"height",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[3])?0:n[3]},set:function(t){n[3]=t}});var r="";Object.defineProperty(this,"FT",{enumerable:!0,configurable:!1,get:function(){return r},set:function(t){switch(t){case"/Btn":case"/Tx":case"/Ch":case"/Sig":r=t;break;default:throw new Error('Invalid value "'+t+'" for attribute FT supplied.')}}});var i=null;Object.defineProperty(this,"T",{enumerable:!0,configurable:!1,get:function(){if(!i||i.length<1){if(this instanceof ut)return;i="FieldObject"+rt.FieldNum++}var t=function(t){return t};return this.scope&&(t=this.scope.internal.getEncryptor(this.objId)),"("+O(t(i))+")"},set:function(t){i=t.toString()}}),Object.defineProperty(this,"fieldName",{configurable:!0,enumerable:!0,get:function(){return i},set:function(t){i=t}});var s="helvetica";Object.defineProperty(this,"fontName",{enumerable:!0,configurable:!0,get:function(){return s},set:function(t){s=t}});var a="normal";Object.defineProperty(this,"fontStyle",{enumerable:!0,configurable:!0,get:function(){return a},set:function(t){a=t}});var o=0;Object.defineProperty(this,"fontSize",{enumerable:!0,configurable:!0,get:function(){return o},set:function(t){o=t}});var l=void 0;Object.defineProperty(this,"maxFontSize",{enumerable:!0,configurable:!0,get:function(){return void 0===l?50/E:l},set:function(t){l=t}});var h="black";Object.defineProperty(this,"color",{enumerable:!0,configurable:!0,get:function(){return h},set:function(t){h=t}});var c="/F1 0 Tf 0 g";Object.defineProperty(this,"DA",{enumerable:!0,configurable:!1,get:function(){if(!(!c||this instanceof ut||this instanceof dt))return Q(c,this.objId,this.scope)},set:function(t){t=t.toString(),c=t}});var u=null;Object.defineProperty(this,"DV",{enumerable:!1,configurable:!1,get:function(){if(u)return this instanceof lt==0?Q(u,this.objId,this.scope):u},set:function(t){t=t.toString(),u=this instanceof lt==0?"("===t.substr(0,1)?j(t.substr(1,t.length-2)):j(t):t}}),Object.defineProperty(this,"defaultValue",{enumerable:!0,configurable:!0,get:function(){return this instanceof lt==1?j(u.substr(1,u.length-1)):u},set:function(t){t=t.toString(),u=this instanceof lt==1?"/"+t:t}});var f=null;Object.defineProperty(this,"_V",{enumerable:!1,configurable:!1,get:function(){if(f)return f},set:function(t){this.V=t}}),Object.defineProperty(this,"V",{enumerable:!1,configurable:!1,get:function(){if(f)return this instanceof lt==0?Q(f,this.objId,this.scope):f},set:function(t){t=t.toString(),f=this instanceof lt==0?"("===t.substr(0,1)?j(t.substr(1,t.length-2)):j(t):t}}),Object.defineProperty(this,"value",{enumerable:!0,configurable:!0,get:function(){return this instanceof lt==1?j(f.substr(1,f.length-1)):f},set:function(t){t=t.toString(),f=this instanceof lt==1?"/"+t:t}}),Object.defineProperty(this,"hasAnnotation",{enumerable:!0,configurable:!0,get:function(){return this.Rect}}),Object.defineProperty(this,"Type",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Annot":null}}),Object.defineProperty(this,"Subtype",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Widget":null}});var d,p=!1;Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return p},set:function(t){t=Boolean(t),p=t}}),Object.defineProperty(this,"page",{enumerable:!0,configurable:!0,get:function(){if(d)return d},set:function(t){d=t}}),Object.defineProperty(this,"readOnly",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,1))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,1):this.Ff=V(this.Ff,1)}}),Object.defineProperty(this,"required",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,2))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,2):this.Ff=V(this.Ff,2)}}),Object.defineProperty(this,"noExport",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,3))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,3):this.Ff=V(this.Ff,3)}});var g=null;Object.defineProperty(this,"Q",{enumerable:!0,configurable:!1,get:function(){if(null!==g)return g},set:function(t){if(-1===[0,1,2].indexOf(t))throw new Error('Invalid value "'+t+'" for attribute Q supplied.');g=t}}),Object.defineProperty(this,"textAlign",{get:function(){var t;switch(g){case 0:default:t="left";break;case 1:t="center";break;case 2:t="right"}return t},configurable:!0,enumerable:!0,set:function(t){switch(t){case"right":case 2:g=2;break;case"center":case 1:g=1;break;default:g=0}}})};q(rt,tt);var it=function(){rt.call(this),this.FT="/Ch",this.V="()",this.fontName="zapfdingbats";var t=0;Object.defineProperty(this,"TI",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"topIndex",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){t=e}});var e=[];Object.defineProperty(this,"Opt",{enumerable:!0,configurable:!1,get:function(){return $(e,this.objId,this.scope)},set:function(t){var n,r;r=[],"string"==typeof(n=t)&&(r=function(t,e,n){n||(n=1);for(var r,i=[];r=e.exec(t);)i.push(r[n]);return i}(n,/\((.*?)\)/g)),e=r}}),this.getOptions=function(){return e},this.setOptions=function(t){e=t,this.sort&&e.sort()},this.addOption=function(t){t=(t=t||"").toString(),e.push(t),this.sort&&e.sort()},this.removeOption=function(t,n){for(n=n||!1,t=(t=t||"").toString();-1!==e.indexOf(t)&&(e.splice(e.indexOf(t),1),!1!==n););},Object.defineProperty(this,"combo",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,18))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,18):this.Ff=V(this.Ff,18)}}),Object.defineProperty(this,"edit",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,19))},set:function(t){!0===this.combo&&(!0===Boolean(t)?this.Ff=H(this.Ff,19):this.Ff=V(this.Ff,19))}}),Object.defineProperty(this,"sort",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,20))},set:function(t){!0===Boolean(t)?(this.Ff=H(this.Ff,20),e.sort()):this.Ff=V(this.Ff,20)}}),Object.defineProperty(this,"multiSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,22))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,22):this.Ff=V(this.Ff,22)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,23):this.Ff=V(this.Ff,23)}}),Object.defineProperty(this,"commitOnSelChange",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,27))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,27):this.Ff=V(this.Ff,27)}}),this.hasAppearanceStream=!1};q(it,rt);var st=function(){it.call(this),this.fontName="helvetica",this.combo=!1};q(st,it);var at=function(){st.call(this),this.combo=!0};q(at,st);var ot=function(){at.call(this),this.edit=!0};q(ot,at);var lt=function(){rt.call(this),this.FT="/Btn",Object.defineProperty(this,"noToggleToOff",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,15))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,15):this.Ff=V(this.Ff,15)}}),Object.defineProperty(this,"radio",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,16))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,16):this.Ff=V(this.Ff,16)}}),Object.defineProperty(this,"pushButton",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,17))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,17):this.Ff=V(this.Ff,17)}}),Object.defineProperty(this,"radioIsUnison",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,26):this.Ff=V(this.Ff,26)}});var t,e={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};if(this.scope&&(t=this.scope.internal.getEncryptor(this.objId)),0!==Object.keys(e).length){var n,r=[];for(n in r.push("<<"),e)r.push("/"+n+" ("+O(t(e[n]))+")");return r.push(">>"),r.join("\n")}},set:function(t){"object"==typeof t&&(e=t)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return e.CA||""},set:function(t){"string"==typeof t&&(e.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return t.substr(1,t.length-1)},set:function(e){t="/"+e}})};q(lt,rt);var ht=function(){lt.call(this),this.pushButton=!0};q(ht,lt);var ct=function(){lt.call(this),this.radio=!0,this.pushButton=!1;var t=[];Object.defineProperty(this,"Kids",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=void 0!==e?e:[]}})};q(ct,lt);var ut=function(){var t,e;rt.call(this),Object.defineProperty(this,"Parent",{enumerable:!1,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"optionName",{enumerable:!1,configurable:!0,get:function(){return e},set:function(t){e=t}});var n,r={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};this.scope&&(t=this.scope.internal.getEncryptor(this.objId));var e,n=[];for(e in n.push("<<"),r)n.push("/"+e+" ("+O(t(r[e]))+")");return n.push(">>"),n.join("\n")},set:function(t){"object"==typeof t&&(r=t)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return r.CA||""},set:function(t){"string"==typeof t&&(r.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return n},set:function(t){n=t}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return n.substr(1,n.length-1)},set:function(t){n="/"+t}}),this.caption="l",this.appearanceState="Off",this._AppearanceType=gt.RadioButton.Circle,this.appearanceStreamContent=this._AppearanceType.createAppearanceStream(this.optionName)};q(ut,rt),ct.prototype.setAppearance=function(t){if(!("createAppearanceStream"in t)||!("getCA"in t))throw new Error("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");for(var e in this.Kids)if(this.Kids.hasOwnProperty(e)){var n=this.Kids[e];n.appearanceStreamContent=t.createAppearanceStream(n.optionName),n.caption=t.getCA()}},ct.prototype.createOption=function(t){var e=new ut;return e.Parent=this,e.optionName=t,this.Kids.push(e),mt.call(this.scope,e),e};var ft=function(){lt.call(this),this.fontName="zapfdingbats",this.caption="3",this.appearanceState="On",this.value="On",this.textAlign="center",this.appearanceStreamContent=gt.CheckBox.createAppearanceStream()};q(ft,lt);var dt=function(){rt.call(this),this.FT="/Tx",Object.defineProperty(this,"multiline",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,13))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,13):this.Ff=V(this.Ff,13)}}),Object.defineProperty(this,"fileSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,21))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,21):this.Ff=V(this.Ff,21)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,23):this.Ff=V(this.Ff,23)}}),Object.defineProperty(this,"doNotScroll",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,24))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,24):this.Ff=V(this.Ff,24)}}),Object.defineProperty(this,"comb",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,25))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,25):this.Ff=V(this.Ff,25)}}),Object.defineProperty(this,"richText",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,26):this.Ff=V(this.Ff,26)}});var t=null;Object.defineProperty(this,"MaxLen",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"maxLength",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){Number.isInteger(e)&&(t=e)}}),Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return this.V||this.DV}})};q(dt,rt);var pt=function(){dt.call(this),Object.defineProperty(this,"password",{enumerable:!0,configurable:!0,get:function(){return Boolean(W(this.Ff,14))},set:function(t){!0===Boolean(t)?this.Ff=H(this.Ff,14):this.Ff=V(this.Ff,14)}}),this.password=!0};q(pt,dt);var gt={CheckBox:{createAppearanceStream:function(){return{N:{On:gt.CheckBox.YesNormal},D:{On:gt.CheckBox.YesPushDown,Off:gt.CheckBox.OffPushDown}}},YesPushDown:function(t){var e=T(t);e.scope=t.scope;var n=[],r=t.scope.internal.getFont(t.fontName,t.fontStyle).id,i=t.scope.__private__.encodeColorString(t.color),s=Y(t,t.caption);return n.push("0.749023 g"),n.push("0 0 "+B(gt.internal.getWidth(t))+" "+B(gt.internal.getHeight(t))+" re"),n.push("f"),n.push("BMC"),n.push("q"),n.push("0 0 1 rg"),n.push("/"+r+" "+B(s.fontSize)+" Tf "+i),n.push("BT"),n.push(s.text),n.push("ET"),n.push("Q"),n.push("EMC"),e.stream=n.join("\n"),e},YesNormal:function(t){var e=T(t);e.scope=t.scope;var n=t.scope.internal.getFont(t.fontName,t.fontStyle).id,r=t.scope.__private__.encodeColorString(t.color),i=[],s=gt.internal.getHeight(t),a=gt.internal.getWidth(t),o=Y(t,t.caption);return i.push("1 g"),i.push("0 0 "+B(a)+" "+B(s)+" re"),i.push("f"),i.push("q"),i.push("0 0 1 rg"),i.push("0 0 "+B(a-1)+" "+B(s-1)+" re"),i.push("W"),i.push("n"),i.push("0 g"),i.push("BT"),i.push("/"+n+" "+B(o.fontSize)+" Tf "+r),i.push(o.text),i.push("ET"),i.push("Q"),e.stream=i.join("\n"),e},OffPushDown:function(t){var e=T(t);e.scope=t.scope;var n=[];return n.push("0.749023 g"),n.push("0 0 "+B(gt.internal.getWidth(t))+" "+B(gt.internal.getHeight(t))+" re"),n.push("f"),e.stream=n.join("\n"),e}},RadioButton:{Circle:{createAppearanceStream:function(t){var e={D:{Off:gt.RadioButton.Circle.OffPushDown},N:{}};return e.N[t]=gt.RadioButton.Circle.YesNormal,e.D[t]=gt.RadioButton.Circle.YesPushDown,e},getCA:function(){return"l"},YesNormal:function(t){var e=T(t);e.scope=t.scope;var n=[],r=gt.internal.getWidth(t)<=gt.internal.getHeight(t)?gt.internal.getWidth(t)/4:gt.internal.getHeight(t)/4;r=Number((.9*r).toFixed(5));var i=gt.internal.Bezier_C,s=Number((r*i).toFixed(5));return n.push("q"),n.push("1 0 0 1 "+M(gt.internal.getWidth(t)/2)+" "+M(gt.internal.getHeight(t)/2)+" cm"),n.push(r+" 0 m"),n.push(r+" "+s+" "+s+" "+r+" 0 "+r+" c"),n.push("-"+s+" "+r+" -"+r+" "+s+" -"+r+" 0 c"),n.push("-"+r+" -"+s+" -"+s+" -"+r+" 0 -"+r+" c"),n.push(s+" -"+r+" "+r+" -"+s+" "+r+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e},YesPushDown:function(t){var e=T(t);e.scope=t.scope;var n=[],r=gt.internal.getWidth(t)<=gt.internal.getHeight(t)?gt.internal.getWidth(t)/4:gt.internal.getHeight(t)/4;r=Number((.9*r).toFixed(5));var i=Number((2*r).toFixed(5)),s=Number((i*gt.internal.Bezier_C).toFixed(5)),a=Number((r*gt.internal.Bezier_C).toFixed(5));return n.push("0.749023 g"),n.push("q"),n.push("1 0 0 1 "+M(gt.internal.getWidth(t)/2)+" "+M(gt.internal.getHeight(t)/2)+" cm"),n.push(i+" 0 m"),n.push(i+" "+s+" "+s+" "+i+" 0 "+i+" c"),n.push("-"+s+" "+i+" -"+i+" "+s+" -"+i+" 0 c"),n.push("-"+i+" -"+s+" -"+s+" -"+i+" 0 -"+i+" c"),n.push(s+" -"+i+" "+i+" -"+s+" "+i+" 0 c"),n.push("f"),n.push("Q"),n.push("0 g"),n.push("q"),n.push("1 0 0 1 "+M(gt.internal.getWidth(t)/2)+" "+M(gt.internal.getHeight(t)/2)+" cm"),n.push(r+" 0 m"),n.push(r+" "+a+" "+a+" "+r+" 0 "+r+" c"),n.push("-"+a+" "+r+" -"+r+" "+a+" -"+r+" 0 c"),n.push("-"+r+" -"+a+" -"+a+" -"+r+" 0 -"+r+" c"),n.push(a+" -"+r+" "+r+" -"+a+" "+r+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e},OffPushDown:function(t){var e=T(t);e.scope=t.scope;var n=[],r=gt.internal.getWidth(t)<=gt.internal.getHeight(t)?gt.internal.getWidth(t)/4:gt.internal.getHeight(t)/4;r=Number((.9*r).toFixed(5));var i=Number((2*r).toFixed(5)),s=Number((i*gt.internal.Bezier_C).toFixed(5));return n.push("0.749023 g"),n.push("q"),n.push("1 0 0 1 "+M(gt.internal.getWidth(t)/2)+" "+M(gt.internal.getHeight(t)/2)+" cm"),n.push(i+" 0 m"),n.push(i+" "+s+" "+s+" "+i+" 0 "+i+" c"),n.push("-"+s+" "+i+" -"+i+" "+s+" -"+i+" 0 c"),n.push("-"+i+" -"+s+" -"+s+" -"+i+" 0 -"+i+" c"),n.push(s+" -"+i+" "+i+" -"+s+" "+i+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e}},Cross:{createAppearanceStream:function(t){var e={D:{Off:gt.RadioButton.Cross.OffPushDown},N:{}};return e.N[t]=gt.RadioButton.Cross.YesNormal,e.D[t]=gt.RadioButton.Cross.YesPushDown,e},getCA:function(){return"8"},YesNormal:function(t){var e=T(t);e.scope=t.scope;var n=[],r=gt.internal.calculateCross(t);return n.push("q"),n.push("1 1 "+B(gt.internal.getWidth(t)-2)+" "+B(gt.internal.getHeight(t)-2)+" re"),n.push("W"),n.push("n"),n.push(B(r.x1.x)+" "+B(r.x1.y)+" m"),n.push(B(r.x2.x)+" "+B(r.x2.y)+" l"),n.push(B(r.x4.x)+" "+B(r.x4.y)+" m"),n.push(B(r.x3.x)+" "+B(r.x3.y)+" l"),n.push("s"),n.push("Q"),e.stream=n.join("\n"),e},YesPushDown:function(t){var e=T(t);e.scope=t.scope;var n=gt.internal.calculateCross(t),r=[];return r.push("0.749023 g"),r.push("0 0 "+B(gt.internal.getWidth(t))+" "+B(gt.internal.getHeight(t))+" re"),r.push("f"),r.push("q"),r.push("1 1 "+B(gt.internal.getWidth(t)-2)+" "+B(gt.internal.getHeight(t)-2)+" re"),r.push("W"),r.push("n"),r.push(B(n.x1.x)+" "+B(n.x1.y)+" m"),r.push(B(n.x2.x)+" "+B(n.x2.y)+" l"),r.push(B(n.x4.x)+" "+B(n.x4.y)+" m"),r.push(B(n.x3.x)+" "+B(n.x3.y)+" l"),r.push("s"),r.push("Q"),e.stream=r.join("\n"),e},OffPushDown:function(t){var e=T(t);e.scope=t.scope;var n=[];return n.push("0.749023 g"),n.push("0 0 "+B(gt.internal.getWidth(t))+" "+B(gt.internal.getHeight(t))+" re"),n.push("f"),e.stream=n.join("\n"),e}}},createDefaultAppearanceStream:function(t){var e=t.scope.internal.getFont(t.fontName,t.fontStyle).id,n=t.scope.__private__.encodeColorString(t.color);return"/"+e+" "+t.fontSize+" Tf "+n}};gt.internal={Bezier_C:.551915024494,calculateCross:function(t){var e=gt.internal.getWidth(t),n=gt.internal.getHeight(t),r=Math.min(e,n);return{x1:{x:(e-r)/2,y:(n-r)/2+r},x2:{x:(e-r)/2+r,y:(n-r)/2},x3:{x:(e-r)/2,y:(n-r)/2},x4:{x:(e-r)/2+r,y:(n-r)/2+r}}}},gt.internal.getWidth=function(t){var e=0;return"object"==typeof t&&(e=R(t.Rect[2])),e},gt.internal.getHeight=function(t){var e=0;return"object"==typeof t&&(e=R(t.Rect[3])),e};var mt=C.addField=function(t){if(function(t,e){if(e.scope=t,void 0!==t.internal&&(void 0===t.internal.acroformPlugin||!1===t.internal.acroformPlugin.isInitialized)){if(rt.FieldNum=0,t.internal.acroformPlugin=JSON.parse(JSON.stringify(X)),t.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("Exception while creating AcroformDictionary");E=t.internal.scaleFactor,t.internal.acroformPlugin.acroFormDictionaryRoot=new nt,t.internal.acroformPlugin.acroFormDictionaryRoot.scope=t,t.internal.acroformPlugin.acroFormDictionaryRoot._eventID=t.internal.events.subscribe("postPutResources",function(){!function(t){t.internal.events.unsubscribe(t.internal.acroformPlugin.acroFormDictionaryRoot._eventID),delete t.internal.acroformPlugin.acroFormDictionaryRoot._eventID,t.internal.acroformPlugin.printedOut=!0}(t)}),t.internal.events.subscribe("buildDocument",function(){!function(t){t.internal.acroformPlugin.acroFormDictionaryRoot.objId=void 0;var e=t.internal.acroformPlugin.acroFormDictionaryRoot.Fields;for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];r.objId=void 0,r.hasAnnotation&&K(r,t)}}(t)}),t.internal.events.subscribe("putCatalog",function(){!function(t){if(void 0===t.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("putCatalogCallback: Root missing.");t.internal.write("/AcroForm "+t.internal.acroformPlugin.acroFormDictionaryRoot.objId+" 0 R")}(t)}),t.internal.events.subscribe("postPutPages",function(e){!function(t,e){var n=!t;for(var r in t||(e.internal.newObjectDeferredBegin(e.internal.acroformPlugin.acroFormDictionaryRoot.objId,!0),e.internal.acroformPlugin.acroFormDictionaryRoot.putStream()),t=t||e.internal.acroformPlugin.acroFormDictionaryRoot.Kids)if(t.hasOwnProperty(r)){var i=t[r],s=[],a=i.Rect;if(i.Rect&&(i.Rect=G(i.Rect,e)),e.internal.newObjectDeferredBegin(i.objId,!0),i.DA=gt.createDefaultAppearanceStream(i),"object"==typeof i&&"function"==typeof i.getKeyValueListForStream&&(s=i.getKeyValueListForStream()),i.Rect=a,i.hasAppearanceStream&&!i.appearanceStreamContent){var o=Z(i);s.push({key:"AP",value:"<>"}),e.internal.acroformPlugin.xForms.push(o)}if(i.appearanceStreamContent){var l="";for(var h in i.appearanceStreamContent)if(i.appearanceStreamContent.hasOwnProperty(h)){var c=i.appearanceStreamContent[h];if(l+="/"+h+" ",l+="<<",Object.keys(c).length>=1||Array.isArray(c)){for(var r in c)if(c.hasOwnProperty(r)){var u=c[r];"function"==typeof u&&(u=u.call(e,i)),l+="/"+r+" "+u+" ",e.internal.acroformPlugin.xForms.indexOf(u)>=0||e.internal.acroformPlugin.xForms.push(u)}}else"function"==typeof(u=c)&&(u=u.call(e,i)),l+="/"+r+" "+u,e.internal.acroformPlugin.xForms.indexOf(u)>=0||e.internal.acroformPlugin.xForms.push(u);l+=">>"}s.push({key:"AP",value:"<<\n"+l+">>"})}e.internal.putStream({additionalKeyValues:s,objectId:i.objId}),e.internal.out("endobj")}n&&function(t,e){for(var n in t)if(t.hasOwnProperty(n)){var r=n,i=t[n];e.internal.newObjectDeferredBegin(i.objId,!0),"object"==typeof i&&"function"==typeof i.putStream&&i.putStream(),delete t[r]}}(e.internal.acroformPlugin.xForms,e)}(e,t)}),t.internal.acroformPlugin.isInitialized=!0}}(this,t),!(t instanceof rt))throw new Error("Invalid argument passed to jsPDF.addField.");var e;return(e=t).scope.internal.acroformPlugin.printedOut&&(e.scope.internal.acroformPlugin.printedOut=!1,e.scope.internal.acroformPlugin.acroFormDictionaryRoot=null),e.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(e),t.page=t.scope.internal.getCurrentPageInfo().pageNumber,this};C.AcroFormChoiceField=it,C.AcroFormListBox=st,C.AcroFormComboBox=at,C.AcroFormEditBox=ot,C.AcroFormButton=lt,C.AcroFormPushButton=ht,C.AcroFormRadioButton=ct,C.AcroFormCheckBox=ft,C.AcroFormTextField=dt,C.AcroFormPasswordField=pt,C.AcroFormAppearance=gt,C.AcroForm={ChoiceField:it,ListBox:st,ComboBox:at,EditBox:ot,Button:lt,PushButton:ht,RadioButton:ct,CheckBox:ft,TextField:dt,PasswordField:pt,Appearance:gt},I.AcroForm={ChoiceField:it,ListBox:st,ComboBox:at,EditBox:ot,Button:lt,PushButton:ht,RadioButton:ct,CheckBox:ft,TextField:dt,PasswordField:pt,Appearance:gt};var wt=I.AcroForm; +/** @license + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */function yt(t){return t.reduce(function(t,e,n){return t[e]=n,t},{})}!function(t){var e="addImage_";t.__addimage__={};var r="UNKNOWN",i={PNG:[[137,80,78,71]],TIFF:[[77,77,0,42],[73,73,42,0]],JPEG:[[255,216,255,224,void 0,void 0,74,70,73,70,0],[255,216,255,225,void 0,void 0,69,120,105,102,0,0],[255,216,255,219],[255,216,255,238]],JPEG2000:[[0,0,0,12,106,80,32,32]],GIF87a:[[71,73,70,56,55,97]],GIF89a:[[71,73,70,56,57,97]],WEBP:[[82,73,70,70,void 0,void 0,void 0,void 0,87,69,66,80]],BMP:[[66,77],[66,65],[67,73],[67,80],[73,67],[80,84]]},s=t.__addimage__.getImageFileTypeByImageData=function(t,e){var n,s,a,o,l,h=r;if("RGBA"===(e=e||r)||void 0!==t.data&&t.data instanceof Uint8ClampedArray&&"height"in t&&"width"in t)return"RGBA";if(N(t))for(l in i)for(a=i[l],n=0;n>"}),"transparency"in t&&Array.isArray(t.transparency)&&t.transparency.length>0){for(var s="",o=0,l=t.transparency.length;o>",p.content=i;var y=p.objId+" 0 R";i="<>";else if(n.options.pageNumber)switch(i="<>",this.internal.write(i))}}this.internal.write("]")}}]),t.createAnnotation=function(t){var e=this.internal.getCurrentPageInfo();switch(t.type){case"link":this.link(t.bounds.x,t.bounds.y,t.bounds.w,t.bounds.h,t);break;case"text":case"freetext":e.pageContext.annotations.push(t)}},t.link=function(t,e,n,r,i){var s=this.internal.getCurrentPageInfo(),a=this.internal.getCoordinateString,o=this.internal.getVerticalCoordinateString;s.pageContext.annotations.push({finalBounds:{x:a(t),y:o(e),w:a(t+n),h:o(e+r)},options:i,type:"link"})},t.textWithLink=function(t,e,n,r){var i,s,a=this.getTextWidth(t),o=this.internal.getLineHeight()/this.internal.scaleFactor;if(void 0!==r.maxWidth){var{maxWidth:l}=r;s=l;var h=this.splitTextToSize(t,s).length;i=Math.ceil(o*h)}else s=a,i=o;return this.text(t,e,n,r),n+=.2*o,"center"===r.align&&(e-=a/2),"right"===r.align&&(e-=a),this.link(e,n-o,s,i,r),a},t.getTextWidth=function(t){var e=this.internal.getFontSize();return this.getStringUnitWidth(t)*e/this.internal.scaleFactor}}(I.API), +/** + * @license + * Copyright (c) 2017 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +function(t){var e={1569:[65152],1570:[65153,65154],1571:[65155,65156],1572:[65157,65158],1573:[65159,65160],1574:[65161,65162,65163,65164],1575:[65165,65166],1576:[65167,65168,65169,65170],1577:[65171,65172],1578:[65173,65174,65175,65176],1579:[65177,65178,65179,65180],1580:[65181,65182,65183,65184],1581:[65185,65186,65187,65188],1582:[65189,65190,65191,65192],1583:[65193,65194],1584:[65195,65196],1585:[65197,65198],1586:[65199,65200],1587:[65201,65202,65203,65204],1588:[65205,65206,65207,65208],1589:[65209,65210,65211,65212],1590:[65213,65214,65215,65216],1591:[65217,65218,65219,65220],1592:[65221,65222,65223,65224],1593:[65225,65226,65227,65228],1594:[65229,65230,65231,65232],1601:[65233,65234,65235,65236],1602:[65237,65238,65239,65240],1603:[65241,65242,65243,65244],1604:[65245,65246,65247,65248],1605:[65249,65250,65251,65252],1606:[65253,65254,65255,65256],1607:[65257,65258,65259,65260],1608:[65261,65262],1609:[65263,65264,64488,64489],1610:[65265,65266,65267,65268],1649:[64336,64337],1655:[64477],1657:[64358,64359,64360,64361],1658:[64350,64351,64352,64353],1659:[64338,64339,64340,64341],1662:[64342,64343,64344,64345],1663:[64354,64355,64356,64357],1664:[64346,64347,64348,64349],1667:[64374,64375,64376,64377],1668:[64370,64371,64372,64373],1670:[64378,64379,64380,64381],1671:[64382,64383,64384,64385],1672:[64392,64393],1676:[64388,64389],1677:[64386,64387],1678:[64390,64391],1681:[64396,64397],1688:[64394,64395],1700:[64362,64363,64364,64365],1702:[64366,64367,64368,64369],1705:[64398,64399,64400,64401],1709:[64467,64468,64469,64470],1711:[64402,64403,64404,64405],1713:[64410,64411,64412,64413],1715:[64406,64407,64408,64409],1722:[64414,64415],1723:[64416,64417,64418,64419],1726:[64426,64427,64428,64429],1728:[64420,64421],1729:[64422,64423,64424,64425],1733:[64480,64481],1734:[64473,64474],1735:[64471,64472],1736:[64475,64476],1737:[64482,64483],1739:[64478,64479],1740:[64508,64509,64510,64511],1744:[64484,64485,64486,64487],1746:[64430,64431],1747:[64432,64433]},n={65247:{65154:65269,65156:65271,65160:65273,65166:65275},65248:{65154:65270,65156:65272,65160:65274,65166:65276},65165:{65247:{65248:{65258:65010}}},1617:{1612:64606,1613:64607,1614:64608,1615:64609,1616:64610}},r={1612:64606,1613:64607,1614:64608,1615:64609,1616:64610},i=[1570,1571,1573,1575];t.__arabicParser__={};var s=t.__arabicParser__.isInArabicSubstitutionA=function(t){return void 0!==e[t.charCodeAt(0)]},a=t.__arabicParser__.isArabicLetter=function(t){return"string"==typeof t&&/^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test(t)},o=t.__arabicParser__.isArabicEndLetter=function(t){return a(t)&&s(t)&&e[t.charCodeAt(0)].length<=2},l=t.__arabicParser__.isArabicAlfLetter=function(t){return a(t)&&i.indexOf(t.charCodeAt(0))>=0};t.__arabicParser__.arabicLetterHasIsolatedForm=function(t){return a(t)&&s(t)&&e[t.charCodeAt(0)].length>=1};var h=t.__arabicParser__.arabicLetterHasFinalForm=function(t){return a(t)&&s(t)&&e[t.charCodeAt(0)].length>=2};t.__arabicParser__.arabicLetterHasInitialForm=function(t){return a(t)&&s(t)&&e[t.charCodeAt(0)].length>=3};var c=t.__arabicParser__.arabicLetterHasMedialForm=function(t){return a(t)&&s(t)&&4==e[t.charCodeAt(0)].length},u=t.__arabicParser__.resolveLigatures=function(t){var e=0,r=n,i="",s=0;for(e=0;e>"),this.internal.out("endobj")}),this.internal.events.subscribe("putCatalog",function(){this.internal.out("/OpenAction "+e+" 0 R")})),this}, +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +function(t){var e=function(){var t=void 0;Object.defineProperty(this,"pdf",{get:function(){return t},set:function(e){t=e}});var e=150;Object.defineProperty(this,"width",{get:function(){return e},set:function(t){e=isNaN(t)||!1===Number.isInteger(t)||t<0?150:t,this.getContext("2d").pageWrapXEnabled&&(this.getContext("2d").pageWrapX=e+1)}});var n=300;Object.defineProperty(this,"height",{get:function(){return n},set:function(t){n=isNaN(t)||!1===Number.isInteger(t)||t<0?300:t,this.getContext("2d").pageWrapYEnabled&&(this.getContext("2d").pageWrapY=n+1)}});var r=[];Object.defineProperty(this,"childNodes",{get:function(){return r},set:function(t){r=t}});var i={};Object.defineProperty(this,"style",{get:function(){return i},set:function(t){i=t}}),Object.defineProperty(this,"parentNode",{})};e.prototype.getContext=function(t,e){var n;if("2d"!==(t=t||"2d"))return null;for(n in e)this.pdf.context2d.hasOwnProperty(n)&&(this.pdf.context2d[n]=e[n]);return this.pdf.context2d._canvas=this,this.pdf.context2d},e.prototype.toDataURL=function(){throw new Error("toDataURL is not implemented.")},t.events.push(["initialized",function(){this.canvas=new e,this.canvas.pdf=this}])}(I.API), +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ +function(t){var e={left:0,top:0,bottom:0,right:0},n=!1,r=function(){void 0===this.internal.__cell__&&(this.internal.__cell__={},this.internal.__cell__.padding=3,this.internal.__cell__.headerFunction=void 0,this.internal.__cell__.margins=Object.assign({},e),this.internal.__cell__.margins.width=this.getPageWidth(),i.call(this))},i=function(){this.internal.__cell__.lastCell=new s,this.internal.__cell__.pages=1},s=function(){var t=arguments[0];Object.defineProperty(this,"x",{enumerable:!0,get:function(){return t},set:function(e){t=e}});var e=arguments[1];Object.defineProperty(this,"y",{enumerable:!0,get:function(){return e},set:function(t){e=t}});var n=arguments[2];Object.defineProperty(this,"width",{enumerable:!0,get:function(){return n},set:function(t){n=t}});var r=arguments[3];Object.defineProperty(this,"height",{enumerable:!0,get:function(){return r},set:function(t){r=t}});var i=arguments[4];Object.defineProperty(this,"text",{enumerable:!0,get:function(){return i},set:function(t){i=t}});var s=arguments[5];Object.defineProperty(this,"lineNumber",{enumerable:!0,get:function(){return s},set:function(t){s=t}});var a=arguments[6];return Object.defineProperty(this,"align",{enumerable:!0,get:function(){return a},set:function(t){a=t}}),this};s.prototype.clone=function(){return new s(this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align)},s.prototype.toArray=function(){return[this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align]},t.setHeaderFunction=function(t){return r.call(this),this.internal.__cell__.headerFunction="function"==typeof t?t:void 0,this},t.getTextDimensions=function(t,e){r.call(this);var n=(e=e||{}).fontSize||this.getFontSize(),i=e.font||this.getFont(),s=e.scaleFactor||this.internal.scaleFactor,a=0,o=0,l=0,h=this;if(!Array.isArray(t)&&"string"!=typeof t){if("number"!=typeof t)throw new Error("getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings.");t=String(t)}const c=e.maxWidth;c>0?"string"==typeof t?t=this.splitTextToSize(t,c):"[object Array]"===Object.prototype.toString.call(t)&&(t=t.reduce(function(t,e){return t.concat(h.splitTextToSize(e,c))},[])):t=Array.isArray(t)?t:[t];for(var u=0;uthis.getPageHeight()?(this.cellAddPage(),t.y=o.top,h&&l&&(this.printHeaderRow(t.lineNumber,!0),t.y+=l[0].height)):t.y=i.y+i.height||t.y),void 0!==t.text[0]&&(this.rect(t.x,t.y,t.width,t.height,!0===n?"FD":void 0),"right"===t.align?this.text(t.text,t.x+t.width-a,t.y+a,{align:"right",baseline:"top"}):"center"===t.align?this.text(t.text,t.x+t.width/2,t.y+a,{align:"center",baseline:"top",maxWidth:t.width-a-a}):this.text(t.text,t.x+a,t.y+a,{align:"left",baseline:"top",maxWidth:t.width-a-a})),this.internal.__cell__.lastCell=t,this};t.table=function(t,n,l,h,c){if(r.call(this),!l)throw new Error("No data for PDF table.");var u,f,d,p,g=[],m=[],w=[],y={},b={},_=[],v=[],x=(c=c||{}).autoSize||!1,N=!1!==c.printHeaders,L=c.css&&void 0!==c.css["font-size"]?16*c.css["font-size"]:c.fontSize||12,A=c.margins||Object.assign({width:this.getPageWidth()},e),S="number"==typeof c.padding?c.padding:3,k=c.headerBackgroundColor||"#c8c8c8",P=c.headerTextColor||"#000";if(i.call(this),this.internal.__cell__.printHeaders=N,this.internal.__cell__.margins=A,this.internal.__cell__.table_font_size=L,this.internal.__cell__.padding=S,this.internal.__cell__.headerBackgroundColor=k,this.internal.__cell__.headerTextColor=P,this.setFontSize(L),null==h)m=g=Object.keys(l[0]),w=g.map(function(){return"left"});else if(Array.isArray(h)&&"object"==typeof h[0])for(g=h.map(function(t){return t.name}),m=h.map(function(t){return t.prompt||t.name||""}),w=h.map(function(t){return t.align||"left"}),u=0;u0&&this.setTableHeaderRow(l),this.setFont(void 0,"normal"),n=!1}}(I.API);var bt={italic:["italic","oblique","normal"],oblique:["oblique","italic","normal"],normal:["normal","oblique","italic"]},_t=["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded"],vt=yt(_t),xt=[100,200,300,400,500,600,700,800,900],Nt=yt(xt);function Lt(t){var e=t.family.replace(/"|'/g,"").toLowerCase(),n=function(t){return bt[t=t||"normal"]?t:"normal"}(t.style),r=function(t){return t?"number"==typeof t?t>=100&&t<=900&&t%100==0?t:400:/^\d00$/.test(t)?parseInt(t):"bold"===t?700:400:400}(t.weight),i=function(t){return"number"==typeof vt[t=t||"normal"]?t:"normal"}(t.stretch);return{family:e,style:n,weight:r,stretch:i,src:t.src||[],ref:t.ref||{name:e,style:[i,n,r].join(" ")}}}function At(t,e,n,r){var i;for(i=n;i>=0&&i=0&&i=2?t[1]:e[0],e[2]=t.length>=3?t[2]:e[0],e[3]=t.length>=4?t[3]:e[1]),u.margin=e}});var a=!1;Object.defineProperty(this,"autoPaging",{get:function(){return a},set:function(t){a=t}});var o=0;Object.defineProperty(this,"lastBreak",{get:function(){return o},set:function(t){o=t}});var l=[];Object.defineProperty(this,"pageBreaks",{get:function(){return l},set:function(t){l=t}}),Object.defineProperty(this,"ctx",{get:function(){return u},set:function(t){t instanceof f&&(u=t)}}),Object.defineProperty(this,"path",{get:function(){return u.path},set:function(t){u.path=t}});var h=[];Object.defineProperty(this,"ctxStack",{get:function(){return h},set:function(t){h=t}}),Object.defineProperty(this,"fillStyle",{get:function(){return this.ctx.fillStyle},set:function(t){var e;e=p(t),this.ctx.fillStyle=e.style,this.ctx.isFillTransparent=0===e.a,this.ctx.fillOpacity=e.a,this.pdf.setFillColor(e.r,e.g,e.b,{a:e.a}),this.pdf.setTextColor(e.r,e.g,e.b,{a:e.a})}}),Object.defineProperty(this,"strokeStyle",{get:function(){return this.ctx.strokeStyle},set:function(t){var e=p(t);this.ctx.strokeStyle=e.style,this.ctx.isStrokeTransparent=0===e.a,this.ctx.strokeOpacity=e.a,0===e.a?this.pdf.setDrawColor(255,255,255):(e.a,this.pdf.setDrawColor(e.r,e.g,e.b))}}),Object.defineProperty(this,"lineCap",{get:function(){return this.ctx.lineCap},set:function(t){-1!==["butt","round","square"].indexOf(t)&&(this.ctx.lineCap=t,this.pdf.setLineCap(t))}}),Object.defineProperty(this,"lineWidth",{get:function(){return this.ctx.lineWidth},set:function(t){isNaN(t)||(this.ctx.lineWidth=t,this.pdf.setLineWidth(t))}}),Object.defineProperty(this,"lineJoin",{get:function(){return this.ctx.lineJoin},set:function(t){-1!==["bevel","round","miter"].indexOf(t)&&(this.ctx.lineJoin=t,this.pdf.setLineJoin(t))}}),Object.defineProperty(this,"miterLimit",{get:function(){return this.ctx.miterLimit},set:function(t){isNaN(t)||(this.ctx.miterLimit=t,this.pdf.setMiterLimit(t))}}),Object.defineProperty(this,"textBaseline",{get:function(){return this.ctx.textBaseline},set:function(t){this.ctx.textBaseline=t}}),Object.defineProperty(this,"textAlign",{get:function(){return this.ctx.textAlign},set:function(t){-1!==["right","end","center","left","start"].indexOf(t)&&(this.ctx.textAlign=t)}});var c=null;var d=null;Object.defineProperty(this,"fontFaces",{get:function(){return d},set:function(t){c=null,d=t}}),Object.defineProperty(this,"font",{get:function(){return this.ctx.font},set:function(t){var e;if(this.ctx.font=t,null!==(e=/^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z]+?)\s*$/i.exec(t))){var n=e[1];e[2];var r=e[3],i=e[4];e[5];var s=e[6],a=/^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i.exec(i)[2];i="px"===a?Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor):"em"===a?Math.floor(parseFloat(i)*this.pdf.getFontSize()):Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor),this.pdf.setFontSize(i);var o=function(t){var e,n,r=[],i=t.trim();if(""===i)return Et;if(i in kt)return[kt[i]];for(;""!==i;){switch(n=null,e=(i=Ft(i)).charAt(0)){case'"':case"'":n=It(i.substring(1),e);break;default:n=Ct(i)}if(null===n)return Et;if(r.push(n[0]),""!==(i=Ft(n[1]))&&","!==i.charAt(0))return Et;i=i.replace(/^,/,"")}return r}(s);if(this.fontFaces){var l=function(t,e){if(null===c){var n=function(t){var e=[];return Object.keys(t).forEach(function(n){t[n].forEach(function(t){var r=null;switch(t){case"bold":r={family:n,weight:"bold"};break;case"italic":r={family:n,style:"italic"};break;case"bolditalic":r={family:n,weight:"bold",style:"italic"};break;case"":case"normal":r={family:n}}null!==r&&(r.ref={name:n,style:t},e.push(r))})}),e}(t.getFontList());c=function(t){for(var e={},n=0;n=700||"bold"===n)&&(f="bold"),"italic"===n&&(f+="italic"),0===f.length&&(f="normal");for(var d="",p={arial:"Helvetica",Arial:"Helvetica",verdana:"Helvetica",Verdana:"Helvetica",helvetica:"Helvetica",Helvetica:"Helvetica","sans-serif":"Helvetica",fixed:"Courier",monospace:"Courier",terminal:"Courier",cursive:"Times",fantasy:"Times",serif:"Times"},g=0;g=2*Math.PI&&(r=0,i=2*Math.PI),this.path.push({type:"arc",x:t,y:e,radius:n,startAngle:r,endAngle:i,counterclockwise:s})},d.prototype.arcTo=function(t,e,n,r,i){throw new Error("arcTo not implemented.")},d.prototype.rect=function(t,e,n,r){if(isNaN(t)||isNaN(e)||isNaN(n)||isNaN(r))throw a("jsPDF.context2d.rect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.rect");this.moveTo(t,e),this.lineTo(t+n,e),this.lineTo(t+n,e+r),this.lineTo(t,e+r),this.lineTo(t,e),this.lineTo(t+n,e),this.lineTo(t,e)},d.prototype.fillRect=function(t,e,n,r){if(isNaN(t)||isNaN(e)||isNaN(n)||isNaN(r))throw a("jsPDF.context2d.fillRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.fillRect");if(!g.call(this)){var i={};"butt"!==this.lineCap&&(i.lineCap=this.lineCap,this.lineCap="butt"),"miter"!==this.lineJoin&&(i.lineJoin=this.lineJoin,this.lineJoin="miter"),this.beginPath(),this.rect(t,e,n,r),this.fill(),i.hasOwnProperty("lineCap")&&(this.lineCap=i.lineCap),i.hasOwnProperty("lineJoin")&&(this.lineJoin=i.lineJoin)}},d.prototype.strokeRect=function(t,e,n,r){if(isNaN(t)||isNaN(e)||isNaN(n)||isNaN(r))throw a("jsPDF.context2d.strokeRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.strokeRect");m.call(this)||(this.beginPath(),this.rect(t,e,n,r),this.stroke())},d.prototype.clearRect=function(t,e,n,r){if(isNaN(t)||isNaN(e)||isNaN(n)||isNaN(r))throw a("jsPDF.context2d.clearRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.clearRect");this.ignoreClearRect||(this.fillStyle="#ffffff",this.fillRect(t,e,n,r))},d.prototype.save=function(t){t="boolean"!=typeof t||t;for(var e=this.pdf.internal.getCurrentPageInfo().pageNumber,n=0;n0||this.margin[1]>0||this.margin[2]>0||this.margin[3]>0};d.prototype.drawImage=function(t,e,n,r,i,s,a,o,l){var u=this.pdf.getImageProperties(t),f=1,d=1,p=1,g=1;void 0!==r&&void 0!==o&&(p=o/r,g=l/i,f=u.width/r*o/r,d=u.height/i*l/i),void 0===s&&(s=e,a=n,e=0,n=0),void 0!==r&&void 0===o&&(o=r,l=i),void 0===r&&void 0===o&&(o=u.width,l=u.height);for(var m,b=this.ctx.transform.decompose(),x=M(b.rotate.shx),L=new c,A=(L=(L=(L=L.multiply(b.translate)).multiply(b.skew)).multiply(b.scale)).applyToRectangle(new h(s-e*p,a-n*g,r*f,i*d)),S=y.call(this,A),k=[],P=0;PF||Cx||A0))for(;l>=0;l--)if(!0!==i[l-1].close&&!0!==i[l-1].begin){i[l-1].deltas.push(n),i[l-1].abs.push(o);break}break;case"bct":n=[o.x1-s[a-1].x,o.y1-s[a-1].y,o.x2-s[a-1].x,o.y2-s[a-1].y,o.x-s[a-1].x,o.y-s[a-1].y],i[i.length-1].deltas.push(n);break;case"qct":var h=s[a-1].x+2/3*(o.x1-s[a-1].x),c=s[a-1].y+2/3*(o.y1-s[a-1].y),u=o.x+2/3*(o.x1-o.x),f=o.y+2/3*(o.y1-o.y),d=o.x,p=o.y;n=[h-s[a-1].x,c-s[a-1].y,u-s[a-1].x,f-s[a-1].y,d-s[a-1].x,p-s[a-1].y],i[i.length-1].deltas.push(n);break;case"arc":i.push({deltas:[],abs:[],arc:!0}),Array.isArray(i[i.length-1].abs)&&i[i.length-1].abs.push(o)}}r=e?null:"stroke"===t?"stroke":"fill";for(var w=!1,y=0;y=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale);var R="text"!==this.autoPaging;if(R||q.y+q.h<=C){if(R||q.y>=F&&q.x<=O){var T=R?t.text:this.pdf.splitTextToSize(t.text,t.maxWidth||O-q.x)[0],D=_([JSON.parse(JSON.stringify(p))],this.posX+this.margin[3],-B+F+this.ctx.prevPageLastElemOffset)[0];const n=R&&(P>S||P=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)}else t.scale>=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale),this.pdf.text(t.text,s.x+this.posX,s.y+this.posY,{angle:t.angle,align:e,renderingMode:t.renderingMode,maxWidth:t.maxWidth}),t.scale>=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)},C=function(t,e,n,s){n=n||0,s=s||0,this.pdf.internal.out(r(t+n)+" "+i(e+s)+" l")},E=function(t,e,n){return this.pdf.lines(t,e,n,null,null)},O=function(t,e,r,i,a,l,h,c){this.pdf.internal.out([n(s(r+t)),n(o(i+e)),n(s(a+t)),n(o(l+e)),n(s(h+t)),n(o(c+e)),"c"].join(" "))},j=function(t,e,n,r){for(var i=2*Math.PI,s=Math.PI/2;e>n;)e-=i;var a=Math.abs(n-e);a1e-5;){var c=h+l*Math.min(a,s);o.push(B.call(this,t,h,c)),a-=Math.abs(c-h),h=c}return o},B=function(t,e,n){var r=(n-e)/2,i=t*Math.cos(r),s=t*Math.sin(r),a=i,o=-s,l=a*a+o*o,h=l+a*i+o*s,c=4/3*(Math.sqrt(2*l*h)-h)/(a*s-o*i),u=a-c*o,f=o+c*a,d=u,p=-f,g=r+e,m=Math.cos(g),w=Math.sin(g);return{x1:t*Math.cos(e),y1:t*Math.sin(e),x2:u*m-f*w,y2:u*w+f*m,x3:d*m-p*w,y3:d*w+p*m,x4:t*Math.cos(n),y4:t*Math.sin(n)}},M=function(t){return 180*t/Math.PI},q=function(t,e,n,r,i,s){var a=t+.5*(n-t),o=e+.5*(r-e),l=i+.5*(n-i),c=s+.5*(r-s),u=Math.min(t,i,a,l),f=Math.max(t,i,a,l),d=Math.min(e,s,o,c),p=Math.max(e,s,o,c);return new h(u,d,f-u,p-d)},R=function(t,e,n,r,i,s,a,o){var l,c,u,f,d,p,g,m,w,y,b,_,v,x,N=n-t,L=r-e,A=i-n,S=s-r,k=a-i,P=o-s;for(c=0;c<41;c++)w=(g=(u=t+(l=c/40)*N)+l*((d=n+l*A)-u))+l*(d+l*(i+l*k-d)-g),y=(m=(f=e+l*L)+l*((p=r+l*S)-f))+l*(p+l*(s+l*P-p)-m),0==c?(b=w,_=y,v=w,x=y):(b=Math.min(b,w),_=Math.min(_,y),v=Math.max(v,w),x=Math.max(x,y));return new h(Math.round(b),Math.round(_),Math.round(v-b),Math.round(x-_))},T=function(){if(!this.prevLineDash&&!this.ctx.lineDash.length&&!this.ctx.lineDashOffset)return;const t=(e=this.ctx.lineDash,n=this.ctx.lineDashOffset,JSON.stringify({lineDash:e,lineDashOffset:n}));var e,n;this.prevLineDash!==t&&(this.pdf.setLineDash(this.ctx.lineDash,this.ctx.lineDashOffset),this.prevLineDash=t)}}(I.API);var Ot=Uint8Array,jt=Uint16Array,Bt=Int32Array,Mt=new Ot([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),qt=new Ot([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Rt=new Ot([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Tt=function(t,e){for(var n=new jt(31),r=0;r<31;++r)n[r]=e+=1<>1|(21845&Vt)<<1;Gt=(61680&(Gt=(52428&Gt)>>2|(13107&Gt)<<2))>>4|(3855&Gt)<<4,Ht[Vt]=((65280&Gt)>>8|(255&Gt)<<8)>>1}var Zt=function(t,e,n){for(var r=t.length,i=0,s=new jt(e);i>l]=h}else for(a=new jt(r),i=0;i>15-t[i]);return a},Yt=new Ot(288);for(Vt=0;Vt<144;++Vt)Yt[Vt]=8;for(Vt=144;Vt<256;++Vt)Yt[Vt]=9;for(Vt=256;Vt<280;++Vt)Yt[Vt]=7;for(Vt=280;Vt<288;++Vt)Yt[Vt]=8;var Jt=new Ot(32);for(Vt=0;Vt<32;++Vt)Jt[Vt]=5;var Xt=Zt(Yt,9,0),Kt=Zt(Jt,5,0),$t=function(t){return(t+7)/8|0},Qt=function(t,e,n){n<<=7&e;var r=e/8|0;t[r]|=n,t[r+1]|=n>>8},te=function(t,e,n){n<<=7&e;var r=e/8|0;t[r]|=n,t[r+1]|=n>>8,t[r+2]|=n>>16},ee=function(t,e){for(var n=[],r=0;rf&&(f=s[r].s);var d=new jt(f+1),p=ne(n[c-1],d,0);if(p>e){r=0;var g=0,m=p-e,w=1<e))break;g+=w-(1<>=m;g>0;){var b=s[r].s;d[b]=0&&g;--r){var _=s[r].s;d[_]==e&&(--d[_],++g)}p=e}return{t:new Ot(d),l:p}},ne=function(t,e,n){return-1==t.s?Math.max(ne(t.l,e,n+1),ne(t.r,e,n+1)):e[t.s]=n},re=function(t){for(var e=t.length;e&&!t[--e];);for(var n=new jt(++e),r=0,i=t[0],s=1,a=function(t){n[r++]=t},o=1;o<=e;++o)if(t[o]==i&&o!=e)++s;else{if(!i&&s>2){for(;s>138;s-=138)a(32754);s>2&&(a(s>10?s-11<<5|28690:s-3<<5|12305),s=0)}else if(s>3){for(a(i),--s;s>6;s-=6)a(8304);s>2&&(a(s-3<<5|8208),s=0)}for(;s--;)a(i);s=1,i=t[o]}return{c:n.subarray(0,r),n:e}},ie=function(t,e){for(var n=0,r=0;r>8,t[i+2]=255^t[i],t[i+3]=255^t[i+1];for(var s=0;s4&&!S[Rt[P-1]];--P);var F,I,C,E,O=h+5<<3,j=ie(i,Yt)+ie(s,Jt)+a,B=ie(i,f)+ie(s,g)+a+14+3*P+ie(N,S)+2*N[16]+3*N[17]+7*N[18];if(l>=0&&O<=j&&O<=B)return se(e,c,t.subarray(l,l+h));if(Qt(e,c,1+(B15&&(Qt(e,c,T[L]>>5&127),c+=T[L]>>12)}}}else F=Xt,I=Yt,C=Kt,E=Jt;for(L=0;L255){te(e,c,F[257+(D=z>>18&31)]),c+=I[D+257],D>7&&(Qt(e,c,z>>23&31),c+=Mt[D]);var U=31&z;te(e,c,C[U]),c+=E[U],U>3&&(te(e,c,z>>5&8191),c+=qt[U])}else te(e,c,F[z]),c+=I[z]}return te(e,c,F[256]),c+I[256]},oe=new Bt([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),le=new Ot(0),he=function(){var t=1,e=0;return{p:function(n){for(var r=t,i=e,s=0|n.length,a=0;a!=s;){for(var o=Math.min(a+2655,s);a>16),i=(65535&i)+15*(i>>16)}t=r,e=i},d:function(){return(255&(t%=65521))<<24|(65280&t)<<8|(255&(e%=65521))<<8|e>>8}}},ce=function(t,e,n){for(;n;++e)t[e]=n,n>>>=8};function ue(t,e){e||(e={});var n=he();n.p(t);var r=function(t,e,n,r,i){if(!i&&(i={l:1},e.dictionary)){var s=e.dictionary.subarray(-32768),a=new Ot(s.length+t.length);a.set(s),a.set(t,s.length),t=a,i.w=s.length}return function(t,e,n,r,i,s){var a=s.z||t.length,o=new Ot(r+a+5*(1+Math.ceil(a/7e3))+i),l=o.subarray(r,o.length-i),h=s.l,c=7&(s.r||0);if(e){c&&(l[0]=s.r>>3);for(var u=oe[e-1],f=u>>13,d=8191&u,p=(1<7e3||S>24576)&&(E>423||!h)){c=ae(t,l,0,_,v,x,L,S,P,A-P,c),S=N=L=0,P=A;for(var O=0;O<286;++O)v[O]=0;for(O=0;O<30;++O)x[O]=0}var j=2,B=0,M=d,q=I-C&32767;if(E>2&&F==b(A-q))for(var R=Math.min(f,E)-1,T=Math.min(32767,A),D=Math.min(258,E);q<=T&&--M&&I!=C;){if(t[A+j]==t[A+j-q]){for(var z=0;zj){if(j=z,B=q,z>R)break;var U=Math.min(q,z-2),W=0;for(O=0;OW&&(W=V,C=H)}}}q+=(I=C)-(C=g[I])&32767}if(B){_[S++]=268435456|Ut[j]<<18|Wt[B];var G=31&Ut[j],Z=31&Wt[B];L+=Mt[G]+qt[Z],++v[257+G],++x[Z],k=A+j,++N}else _[S++]=t[A],++v[t[A]]}}for(A=Math.max(A,k);A=a&&(l[c/8|0]=h,Y=a),c=se(l,c+1,t.subarray(A,Y))}s.i=a}return function(t,e,n){return(null==e||e<0)&&(e=0),(null==n||n>t.length)&&(n=t.length),new Ot(t.subarray(e,n))}(o,0,r+$t(c)+i)}(t,null==e.level?6:e.level,null==e.mem?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(t.length)))):12+e.mem,n,4,i)}(t,e,e.dictionary?6:2);return function(t,e){var n=e.level,r=0==n?0:n<6?1:9==n?3:2;if(t[0]=120,t[1]=r<<6|(e.dictionary&&32),t[1]|=31-(t[0]<<8|t[1])%31,e.dictionary){var i=he();i.p(e.dictionary),ce(t,2,i.d())}}(r,e),ce(r,r.length-4,n.d()),r}var fe,de,pe,ge="undefined"!=typeof TextDecoder&&new TextDecoder;try{ge.decode(le,{stream:!0})}catch(Vs){} +/** + * @license + * jsPDF filters PlugIn + * Copyright (c) 2014 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */function me(t,e="utf8"){return new TextDecoder(e).decode(t)}!function(t){var e=function(t){var e,n,r,i,s,a,o,l,h,c;for(/[^\x00-\xFF]/.test(t),n=[],r=0,i=(t+=e="\0\0\0\0".slice(t.length%4||4)).length;i>r;r+=4)0!==(s=(t.charCodeAt(r)<<24)+(t.charCodeAt(r+1)<<16)+(t.charCodeAt(r+2)<<8)+t.charCodeAt(r+3))?(a=(s=((s=((s=((s=(s-(c=s%85))/85)-(h=s%85))/85)-(l=s%85))/85)-(o=s%85))/85)%85,n.push(a+33,o+33,l+33,h+33,c+33)):n.push(122);return function(t,e){for(var n=e;n>0;n--)t.pop()}(n,e.length),String.fromCharCode.apply(String,n)+"~>"},n=function(t){var e,n,r,i,s,a=String,o="length",l=255,h="charCodeAt",c="slice",u="replace";for(t[c](-2),t=t[c](0,-2)[u](/\s/g,"")[u]("z","!!!!!"),r=[],i=0,s=(t+=e="uuuuu"[c](t[o]%5||5))[o];s>i;i+=5)n=52200625*(t[h](i)-33)+614125*(t[h](i+1)-33)+7225*(t[h](i+2)-33)+85*(t[h](i+3)-33)+(t[h](i+4)-33),r.push(l&n>>24,l&n>>16,l&n>>8,l&n);return function(t,e){for(var n=e;n>0;n--)t.pop()}(r,e[o]),a.fromCharCode.apply(a,r)},r=function(t){return t.split("").map(function(t){return("0"+t.charCodeAt().toString(16)).slice(-2)}).join("")+">"},i=function(t){var e=new RegExp(/^([0-9A-Fa-f]{2})+$/);if(-1!==(t=t.replace(/\s/g,"")).indexOf(">")&&(t=t.substr(0,t.indexOf(">"))),t.length%2&&(t+="0"),!1===e.test(t))return"";for(var n="",r=0;r>"),this.internal.out("endobj"),de=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/S /JavaScript"),this.internal.out("/JS ("+pe+")"),this.internal.out(">>"),this.internal.out("endobj")}),this.internal.events.subscribe("putCatalog",function(){void 0!==fe&&void 0!==de&&this.internal.out("/Names <>")}),this}, +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +function(t){var e;t.events.push(["postPutResources",function(){var t=this,n=/^(\d+) 0 obj$/;if(this.outline.root.children.length>0)for(var r=t.outline.render().split(/\r\n/),i=0;i> endobj")}var f=t.internal.newObject();for(t.internal.write("<< /Names [ "),i=0;i>","endobj"),e=t.internal.newObject(),t.internal.write("<< /Dests "+f+" 0 R"),t.internal.write(">>","endobj")}}]),t.events.push(["putCatalog",function(){var t=this;t.outline.root.children.length>0&&(t.internal.write("/Outlines",this.outline.makeRef(this.outline.root)),this.outline.createNamedDestinations&&t.internal.write("/Names "+e+" 0 R"))}]),t.events.push(["initialized",function(){var t=this;t.outline={createNamedDestinations:!1,root:{children:[]}},t.outline.add=function(t,e,n){var r={title:e,options:n,children:[]};return null==t&&(t=this.root),t.children.push(r),r},t.outline.render=function(){return this.ctx={},this.ctx.val="",this.ctx.pdf=t,this.genIds_r(this.root),this.renderRoot(this.root),this.renderItems(this.root),this.ctx.val},t.outline.genIds_r=function(e){e.id=t.internal.newObjectDeferred();for(var n=0;n0&&(this.line("/First "+this.makeRef(t.children[0])),this.line("/Last "+this.makeRef(t.children[t.children.length-1]))),this.line("/Count "+this.count_r({count:0},t)),this.objEnd()},t.outline.renderItems=function(e){for(var n=this.ctx.pdf.internal.getVerticalCoordinateString,r=0;r0&&this.line("/Prev "+this.makeRef(e.children[r-1])),r0&&(this.line("/First "+this.makeRef(i.children[0])),this.line("/Last "+this.makeRef(i.children[i.children.length-1])));var s=this.count=this.count_r({count:0},i);if(s>0&&this.line("/Count "+s),i.options&&i.options.pageNumber){var a=t.internal.getPageInfo(i.options.pageNumber);this.line("/Dest ["+a.objId+" 0 R /XYZ 0 "+n(0)+" 0]")}this.objEnd()}for(var o=0;o> \r\nendobj\r\n"},t.outline.count_r=function(t,e){for(var n=0;n{const t=new Uint8Array(4);return!((new Uint32Array(t.buffer)[0]=1)&t[0])})(),be={int8:globalThis.Int8Array,uint8:globalThis.Uint8Array,int16:globalThis.Int16Array,uint16:globalThis.Uint16Array,int32:globalThis.Int32Array,uint32:globalThis.Uint32Array,uint64:globalThis.BigUint64Array,int64:globalThis.BigInt64Array,float32:globalThis.Float32Array,float64:globalThis.Float64Array};class _e{buffer;byteLength;byteOffset;length;offset;lastWrittenByte;littleEndian;_data;_mark;_marks;constructor(t=8192,e={}){let n=!1;"number"==typeof t?t=new ArrayBuffer(t):(n=!0,this.lastWrittenByte=t.byteLength);const r=e.offset?e.offset>>>0:0,i=t.byteLength-r;let s=r;(ArrayBuffer.isView(t)||t instanceof _e)&&(t.byteLength!==t.buffer.byteLength&&(s=t.byteOffset+r),t=t.buffer),this.lastWrittenByte=n?i:0,this.buffer=t,this.length=i,this.byteLength=i,this.byteOffset=s,this.offset=0,this.littleEndian=!0,this._data=new DataView(this.buffer,s,i),this._mark=0,this._marks=[]}available(t=1){return this.offset+t<=this.length}isLittleEndian(){return this.littleEndian}setLittleEndian(){return this.littleEndian=!0,this}isBigEndian(){return!this.littleEndian}setBigEndian(){return this.littleEndian=!1,this}skip(t=1){return this.offset+=t,this}back(t=1){return this.offset-=t,this}seek(t){return this.offset=t,this}mark(){return this._mark=this.offset,this}reset(){return this.offset=this._mark,this}pushMark(){return this._marks.push(this.offset),this}popMark(){const t=this._marks.pop();if(void 0===t)throw new Error("Mark stack empty");return this.seek(t),this}rewind(){return this.offset=0,this}ensureAvailable(t=1){if(!this.available(t)){const e=2*(this.offset+t),n=new Uint8Array(e);n.set(new Uint8Array(this.buffer)),this.buffer=n.buffer,this.length=e,this.byteLength=e,this._data=new DataView(this.buffer)}return this}readBoolean(){return 0!==this.readUint8()}readInt8(){return this._data.getInt8(this.offset++)}readUint8(){return this._data.getUint8(this.offset++)}readByte(){return this.readUint8()}readBytes(t=1){return this.readArray(t,"uint8")}readArray(t,e){const n=be[e].BYTES_PER_ELEMENT*t,r=this.byteOffset+this.offset,i=this.buffer.slice(r,r+n);if(this.littleEndian===ye&&"uint8"!==e&&"int8"!==e){const t=new Uint8Array(this.buffer.slice(r,r+n));t.reverse();const i=new be[e](t.buffer);return this.offset+=n,i.reverse(),i}const s=new be[e](i);return this.offset+=n,s}readInt16(){const t=this._data.getInt16(this.offset,this.littleEndian);return this.offset+=2,t}readUint16(){const t=this._data.getUint16(this.offset,this.littleEndian);return this.offset+=2,t}readInt32(){const t=this._data.getInt32(this.offset,this.littleEndian);return this.offset+=4,t}readUint32(){const t=this._data.getUint32(this.offset,this.littleEndian);return this.offset+=4,t}readFloat32(){const t=this._data.getFloat32(this.offset,this.littleEndian);return this.offset+=4,t}readFloat64(){const t=this._data.getFloat64(this.offset,this.littleEndian);return this.offset+=8,t}readBigInt64(){const t=this._data.getBigInt64(this.offset,this.littleEndian);return this.offset+=8,t}readBigUint64(){const t=this._data.getBigUint64(this.offset,this.littleEndian);return this.offset+=8,t}readChar(){return String.fromCharCode(this.readInt8())}readChars(t=1){let e="";for(let n=0;nthis.lastWrittenByte&&(this.lastWrittenByte=this.offset)}} +/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */function ve(t){let e=t.length;for(;--e>=0;)t[e]=0}const xe=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),Ne=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),Le=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),Ae=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Se=new Array(576);ve(Se);const ke=new Array(60);ve(ke);const Pe=new Array(512);ve(Pe);const Fe=new Array(256);ve(Fe);const Ie=new Array(29);ve(Ie);const Ce=new Array(30);function Ee(t,e,n,r,i){this.static_tree=t,this.extra_bits=e,this.extra_base=n,this.elems=r,this.max_length=i,this.has_stree=t&&t.length}let Oe,je,Be;function Me(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}ve(Ce);const qe=t=>t<256?Pe[t]:Pe[256+(t>>>7)],Re=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},Te=(t,e,n)=>{t.bi_valid>16-n?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=n-16):(t.bi_buf|=e<{Te(t,n[2*e],n[2*e+1])},ze=(t,e)=>{let n=0;do{n|=1&t,t>>>=1,n<<=1}while(--e>0);return n>>>1},Ue=(t,e,n)=>{const r=new Array(16);let i,s,a=0;for(i=1;i<=15;i++)a=a+n[i-1]<<1,r[i]=a;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=ze(r[e]++,e))}},We=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},He=t=>{t.bi_valid>8?Re(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},Ve=(t,e,n,r)=>{const i=2*e,s=2*n;return t[i]{const r=t.heap[n];let i=n<<1;for(;i<=t.heap_len&&(i{let r,i,s,a,o=0;if(0!==t.sym_next)do{r=255&t.pending_buf[t.sym_buf+o++],r+=(255&t.pending_buf[t.sym_buf+o++])<<8,i=t.pending_buf[t.sym_buf+o++],0===r?De(t,i,e):(s=Fe[i],De(t,s+256+1,e),a=xe[s],0!==a&&(i-=Ie[s],Te(t,i,a)),r--,s=qe(r),De(t,s,n),a=Ne[s],0!==a&&(r-=Ce[s],Te(t,r,a)))}while(o{const n=e.dyn_tree,r=e.stat_desc.static_tree,i=e.stat_desc.has_stree,s=e.stat_desc.elems;let a,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,a=0;a>1;a>=1;a--)Ge(t,n,a);l=s;do{a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],Ge(t,n,1),o=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=o,n[2*l]=n[2*a]+n[2*o],t.depth[l]=(t.depth[a]>=t.depth[o]?t.depth[a]:t.depth[o])+1,n[2*a+1]=n[2*o+1]=l,t.heap[1]=l++,Ge(t,n,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const n=e.dyn_tree,r=e.max_code,i=e.stat_desc.static_tree,s=e.stat_desc.has_stree,a=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,c,u,f,d,p,g=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(n[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)c=t.heap[h],f=n[2*n[2*c+1]+1]+1,f>l&&(f=l,g++),n[2*c+1]=f,c>r||(t.bl_count[f]++,d=0,c>=o&&(d=a[c-o]),p=n[2*c],t.opt_len+=p*(f+d),s&&(t.static_len+=p*(i[2*c+1]+d)));if(0!==g){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,g-=2}while(g>0);for(f=l;0!==f;f--)for(c=t.bl_count[f];0!==c;)u=t.heap[--h],u>r||(n[2*u+1]!==f&&(t.opt_len+=(f-n[2*u+1])*n[2*u],n[2*u+1]=f),c--)}})(t,e),Ue(n,h,t.bl_count)},Je=(t,e,n)=>{let r,i,s=-1,a=e[1],o=0,l=7,h=4;for(0===a&&(l=138,h=3),e[2*(n+1)+1]=65535,r=0;r<=n;r++)i=a,a=e[2*(r+1)+1],++o{let r,i,s=-1,a=e[1],o=0,l=7,h=4;for(0===a&&(l=138,h=3),r=0;r<=n;r++)if(i=a,a=e[2*(r+1)+1],!(++o{Te(t,0+(r?1:0),3),He(t),Re(t,n),Re(t,~n),n&&t.pending_buf.set(t.window.subarray(e,e+n),t.pending),t.pending+=n};var Qe={_tr_init:t=>{Ke||((()=>{let t,e,n,r,i;const s=new Array(16);for(n=0,r=0;r<28;r++)for(Ie[r]=n,t=0;t<1<>=7;r<30;r++)for(Ce[r]=i<<7,t=0;t<1<{let i,s,a=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,n=4093624447;for(e=0;e<=31;e++,n>>>=1)if(1&n&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),Ye(t,t.l_desc),Ye(t,t.d_desc),a=(t=>{let e;for(Je(t,t.dyn_ltree,t.l_desc.max_code),Je(t,t.dyn_dtree,t.d_desc.max_code),Ye(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*Ae[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),i=t.opt_len+3+7>>>3,s=t.static_len+3+7>>>3,s<=i&&(i=s)):i=s=n+5,n+4<=i&&-1!==e?$e(t,e,n,r):4===t.strategy||s===i?(Te(t,2+(r?1:0),3),Ze(t,Se,ke)):(Te(t,4+(r?1:0),3),((t,e,n,r)=>{let i;for(Te(t,e-257,5),Te(t,n-1,5),Te(t,r-4,4),i=0;i(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=n,0===e?t.dyn_ltree[2*n]++:(t.matches++,e--,t.dyn_ltree[2*(Fe[n]+256+1)]++,t.dyn_dtree[2*qe(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{Te(t,2,3),De(t,256,Se),(t=>{16===t.bi_valid?(Re(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}},tn=(t,e,n,r)=>{let i=65535&t,s=t>>>16&65535,a=0;for(;0!==n;){a=n>2e3?2e3:n,n-=a;do{i=i+e[r++]|0,s=s+i|0}while(--a);i%=65521,s%=65521}return i|s<<16};const en=new Uint32Array((()=>{let t,e=[];for(var n=0;n<256;n++){t=n;for(var r=0;r<8;r++)t=1&t?3988292384^t>>>1:t>>>1;e[n]=t}return e})());var nn=(t,e,n,r)=>{const i=en,s=r+n;t^=-1;for(let a=r;a>>8^i[255&(t^e[a])];return-1^t},rn={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},sn={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:an,_tr_stored_block:on,_tr_flush_block:ln,_tr_tally:hn,_tr_align:cn}=Qe,{Z_NO_FLUSH:un,Z_PARTIAL_FLUSH:fn,Z_FULL_FLUSH:dn,Z_FINISH:pn,Z_BLOCK:gn,Z_OK:mn,Z_STREAM_END:wn,Z_STREAM_ERROR:yn,Z_DATA_ERROR:bn,Z_BUF_ERROR:_n,Z_DEFAULT_COMPRESSION:vn,Z_FILTERED:xn,Z_HUFFMAN_ONLY:Nn,Z_RLE:Ln,Z_FIXED:An,Z_DEFAULT_STRATEGY:Sn,Z_UNKNOWN:kn,Z_DEFLATED:Pn}=sn,Fn=258,In=262,Cn=42,En=113,On=666,jn=(t,e)=>(t.msg=rn[e],e),Bn=t=>2*t-(t>4?9:0),Mn=t=>{let e=t.length;for(;--e>=0;)t[e]=0},qn=t=>{let e,n,r,i=t.w_size;e=t.hash_size,r=e;do{n=t.head[--r],t.head[r]=n>=i?n-i:0}while(--e);e=i,r=e;do{n=t.prev[--r],t.prev[r]=n>=i?n-i:0}while(--e)};let Rn=(t,e,n)=>(e<{const e=t.state;let n=e.pending;n>t.avail_out&&(n=t.avail_out),0!==n&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+n),t.next_out),t.next_out+=n,e.pending_out+=n,t.total_out+=n,t.avail_out-=n,e.pending-=n,0===e.pending&&(e.pending_out=0))},Dn=(t,e)=>{ln(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,Tn(t.strm)},zn=(t,e)=>{t.pending_buf[t.pending++]=e},Un=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},Wn=(t,e,n,r)=>{let i=t.avail_in;return i>r&&(i=r),0===i?0:(t.avail_in-=i,e.set(t.input.subarray(t.next_in,t.next_in+i),n),1===t.state.wrap?t.adler=tn(t.adler,e,i,n):2===t.state.wrap&&(t.adler=nn(t.adler,e,i,n)),t.next_in+=i,t.total_in+=i,i)},Hn=(t,e)=>{let n,r,i=t.max_chain_length,s=t.strstart,a=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-In?t.strstart-(t.w_size-In):0,h=t.window,c=t.w_mask,u=t.prev,f=t.strstart+Fn;let d=h[s+a-1],p=h[s+a];t.prev_length>=t.good_match&&(i>>=2),o>t.lookahead&&(o=t.lookahead);do{if(n=e,h[n+a]===p&&h[n+a-1]===d&&h[n]===h[s]&&h[++n]===h[s+1]){s+=2,n++;do{}while(h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&h[++s]===h[++n]&&sa){if(t.match_start=e,a=r,r>=o)break;d=h[s+a-1],p=h[s+a]}}}while((e=u[e&c])>l&&0!==--i);return a<=t.lookahead?a:t.lookahead},Vn=t=>{const e=t.w_size;let n,r,i;do{if(r=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-In)&&(t.window.set(t.window.subarray(e,e+e-r),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),qn(t),r+=e),0===t.strm.avail_in)break;if(n=Wn(t.strm,t.window,t.strstart+t.lookahead,r),t.lookahead+=n,t.lookahead+t.insert>=3)for(i=t.strstart-t.insert,t.ins_h=t.window[i],t.ins_h=Rn(t,t.ins_h,t.window[i+1]);t.insert&&(t.ins_h=Rn(t,t.ins_h,t.window[i+3-1]),t.prev[i&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=i,i++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead{let n,r,i,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,a=0,o=t.strm.avail_in;do{if(n=65535,i=t.bi_valid+42>>3,t.strm.avail_outr+t.strm.avail_in&&(n=r+t.strm.avail_in),n>i&&(n=i),n>8,t.pending_buf[t.pending-2]=~n,t.pending_buf[t.pending-1]=~n>>8,Tn(t.strm),r&&(r>n&&(r=n),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+r),t.strm.next_out),t.strm.next_out+=r,t.strm.avail_out-=r,t.strm.total_out+=r,t.block_start+=r,n-=r),n&&(Wn(t.strm,t.strm.output,t.strm.next_out,n),t.strm.next_out+=n,t.strm.avail_out-=n,t.strm.total_out+=n)}while(0===a);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_wateri&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,i+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),i>t.strm.avail_in&&(i=t.strm.avail_in),i&&(Wn(t.strm,t.window,t.strstart,i),t.strstart+=i,t.insert+=i>t.w_size-t.insert?t.w_size-t.insert:i),t.high_water>3,i=t.pending_buf_size-i>65535?65535:t.pending_buf_size-i,s=i>t.w_size?t.w_size:i,r=t.strstart-t.block_start,(r>=s||(r||e===pn)&&e!==un&&0===t.strm.avail_in&&r<=i)&&(n=r>i?i:r,a=e===pn&&0===t.strm.avail_in&&n===r?1:0,on(t,t.block_start,n,a),t.block_start+=n,Tn(t.strm)),a?3:1)},Zn=(t,e)=>{let n,r;for(;;){if(t.lookahead=3&&(t.ins_h=Rn(t,t.ins_h,t.window[t.strstart+3-1]),n=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==n&&t.strstart-n<=t.w_size-In&&(t.match_length=Hn(t,n)),t.match_length>=3)if(r=hn(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=Rn(t,t.ins_h,t.window[t.strstart+3-1]),n=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!==--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=Rn(t,t.ins_h,t.window[t.strstart+1]);else r=hn(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(r&&(Dn(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===pn?(Dn(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(Dn(t,!1),0===t.strm.avail_out)?1:2},Yn=(t,e)=>{let n,r,i;for(;;){if(t.lookahead=3&&(t.ins_h=Rn(t,t.ins_h,t.window[t.strstart+3-1]),n=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==n&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){i=t.strstart+t.lookahead-3,r=hn(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=i&&(t.ins_h=Rn(t,t.ins_h,t.window[t.strstart+3-1]),n=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!==--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,r&&(Dn(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(r=hn(t,0,t.window[t.strstart-1]),r&&Dn(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(r=hn(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===pn?(Dn(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(Dn(t,!1),0===t.strm.avail_out)?1:2};function Jn(t,e,n,r,i){this.good_length=t,this.max_lazy=e,this.nice_length=n,this.max_chain=r,this.func=i}const Xn=[new Jn(0,0,0,0,Gn),new Jn(4,4,8,4,Zn),new Jn(4,5,16,8,Zn),new Jn(4,6,32,32,Zn),new Jn(4,4,16,16,Yn),new Jn(8,16,32,32,Yn),new Jn(8,16,128,128,Yn),new Jn(8,32,128,256,Yn),new Jn(32,128,258,1024,Yn),new Jn(32,258,258,4096,Yn)];function Kn(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=Pn,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),Mn(this.dyn_ltree),Mn(this.dyn_dtree),Mn(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),Mn(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),Mn(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const $n=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.status!==Cn&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&e.status!==En&&e.status!==On?1:0},Qn=t=>{if($n(t))return jn(t,yn);t.total_in=t.total_out=0,t.data_type=kn;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?Cn:En,t.adler=2===e.wrap?0:1,e.last_flush=-2,an(e),mn},tr=t=>{const e=Qn(t);return e===mn&&((n=t.state).window_size=2*n.w_size,Mn(n.head),n.max_lazy_match=Xn[n.level].max_lazy,n.good_match=Xn[n.level].good_length,n.nice_match=Xn[n.level].nice_length,n.max_chain_length=Xn[n.level].max_chain,n.strstart=0,n.block_start=0,n.lookahead=0,n.insert=0,n.match_length=n.prev_length=2,n.match_available=0,n.ins_h=0),e;var n},er=(t,e,n,r,i,s)=>{if(!t)return yn;let a=1;if(e===vn&&(e=6),r<0?(a=0,r=-r):r>15&&(a=2,r-=16),i<1||i>9||n!==Pn||r<8||r>15||e<0||e>9||s<0||s>An||8===r&&1!==a)return jn(t,yn);8===r&&(r=9);const o=new Kn;return t.state=o,o.strm=t,o.status=Cn,o.wrap=a,o.gzhead=null,o.w_bits=r,o.w_size=1<$n(t)||2!==t.state.wrap?yn:(t.state.gzhead=e,mn),ir=(t,e)=>{if($n(t)||e>gn||e<0)return t?jn(t,yn):yn;const n=t.state;if(!t.output||0!==t.avail_in&&!t.input||n.status===On&&e!==pn)return jn(t,0===t.avail_out?_n:yn);const r=n.last_flush;if(n.last_flush=e,0!==n.pending){if(Tn(t),0===t.avail_out)return n.last_flush=-1,mn}else if(0===t.avail_in&&Bn(e)<=Bn(r)&&e!==pn)return jn(t,_n);if(n.status===On&&0!==t.avail_in)return jn(t,_n);if(n.status===Cn&&0===n.wrap&&(n.status=En),n.status===Cn){let e=Pn+(n.w_bits-8<<4)<<8,r=-1;if(r=n.strategy>=Nn||n.level<2?0:n.level<6?1:6===n.level?2:3,e|=r<<6,0!==n.strstart&&(e|=32),e+=31-e%31,Un(n,e),0!==n.strstart&&(Un(n,t.adler>>>16),Un(n,65535&t.adler)),t.adler=1,n.status=En,Tn(t),0!==n.pending)return n.last_flush=-1,mn}if(57===n.status)if(t.adler=0,zn(n,31),zn(n,139),zn(n,8),n.gzhead)zn(n,(n.gzhead.text?1:0)+(n.gzhead.hcrc?2:0)+(n.gzhead.extra?4:0)+(n.gzhead.name?8:0)+(n.gzhead.comment?16:0)),zn(n,255&n.gzhead.time),zn(n,n.gzhead.time>>8&255),zn(n,n.gzhead.time>>16&255),zn(n,n.gzhead.time>>24&255),zn(n,9===n.level?2:n.strategy>=Nn||n.level<2?4:0),zn(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(zn(n,255&n.gzhead.extra.length),zn(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(t.adler=nn(t.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69;else if(zn(n,0),zn(n,0),zn(n,0),zn(n,0),zn(n,0),zn(n,9===n.level?2:n.strategy>=Nn||n.level<2?4:0),zn(n,3),n.status=En,Tn(t),0!==n.pending)return n.last_flush=-1,mn;if(69===n.status){if(n.gzhead.extra){let e=n.pending,r=(65535&n.gzhead.extra.length)-n.gzindex;for(;n.pending+r>n.pending_buf_size;){let i=n.pending_buf_size-n.pending;if(n.pending_buf.set(n.gzhead.extra.subarray(n.gzindex,n.gzindex+i),n.pending),n.pending=n.pending_buf_size,n.gzhead.hcrc&&n.pending>e&&(t.adler=nn(t.adler,n.pending_buf,n.pending-e,e)),n.gzindex+=i,Tn(t),0!==n.pending)return n.last_flush=-1,mn;e=0,r-=i}let i=new Uint8Array(n.gzhead.extra);n.pending_buf.set(i.subarray(n.gzindex,n.gzindex+r),n.pending),n.pending+=r,n.gzhead.hcrc&&n.pending>e&&(t.adler=nn(t.adler,n.pending_buf,n.pending-e,e)),n.gzindex=0}n.status=73}if(73===n.status){if(n.gzhead.name){let e,r=n.pending;do{if(n.pending===n.pending_buf_size){if(n.gzhead.hcrc&&n.pending>r&&(t.adler=nn(t.adler,n.pending_buf,n.pending-r,r)),Tn(t),0!==n.pending)return n.last_flush=-1,mn;r=0}e=n.gzindexr&&(t.adler=nn(t.adler,n.pending_buf,n.pending-r,r)),n.gzindex=0}n.status=91}if(91===n.status){if(n.gzhead.comment){let e,r=n.pending;do{if(n.pending===n.pending_buf_size){if(n.gzhead.hcrc&&n.pending>r&&(t.adler=nn(t.adler,n.pending_buf,n.pending-r,r)),Tn(t),0!==n.pending)return n.last_flush=-1,mn;r=0}e=n.gzindexr&&(t.adler=nn(t.adler,n.pending_buf,n.pending-r,r))}n.status=103}if(103===n.status){if(n.gzhead.hcrc){if(n.pending+2>n.pending_buf_size&&(Tn(t),0!==n.pending))return n.last_flush=-1,mn;zn(n,255&t.adler),zn(n,t.adler>>8&255),t.adler=0}if(n.status=En,Tn(t),0!==n.pending)return n.last_flush=-1,mn}if(0!==t.avail_in||0!==n.lookahead||e!==un&&n.status!==On){let r=0===n.level?Gn(n,e):n.strategy===Nn?((t,e)=>{let n;for(;;){if(0===t.lookahead&&(Vn(t),0===t.lookahead)){if(e===un)return 1;break}if(t.match_length=0,n=hn(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,n&&(Dn(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===pn?(Dn(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(Dn(t,!1),0===t.strm.avail_out)?1:2})(n,e):n.strategy===Ln?((t,e)=>{let n,r,i,s;const a=t.window;for(;;){if(t.lookahead<=Fn){if(Vn(t),t.lookahead<=Fn&&e===un)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(i=t.strstart-1,r=a[i],r===a[++i]&&r===a[++i]&&r===a[++i])){s=t.strstart+Fn;do{}while(r===a[++i]&&r===a[++i]&&r===a[++i]&&r===a[++i]&&r===a[++i]&&r===a[++i]&&r===a[++i]&&r===a[++i]&&it.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(n=hn(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(n=hn(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),n&&(Dn(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===pn?(Dn(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(Dn(t,!1),0===t.strm.avail_out)?1:2})(n,e):Xn[n.level].func(n,e);if(3!==r&&4!==r||(n.status=On),1===r||3===r)return 0===t.avail_out&&(n.last_flush=-1),mn;if(2===r&&(e===fn?cn(n):e!==gn&&(on(n,0,0,!1),e===dn&&(Mn(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),Tn(t),0===t.avail_out))return n.last_flush=-1,mn}return e!==pn?mn:n.wrap<=0?wn:(2===n.wrap?(zn(n,255&t.adler),zn(n,t.adler>>8&255),zn(n,t.adler>>16&255),zn(n,t.adler>>24&255),zn(n,255&t.total_in),zn(n,t.total_in>>8&255),zn(n,t.total_in>>16&255),zn(n,t.total_in>>24&255)):(Un(n,t.adler>>>16),Un(n,65535&t.adler)),Tn(t),n.wrap>0&&(n.wrap=-n.wrap),0!==n.pending?mn:wn)},sr=t=>{if($n(t))return yn;const e=t.state.status;return t.state=null,e===En?jn(t,bn):mn},ar=(t,e)=>{let n=e.length;if($n(t))return yn;const r=t.state,i=r.wrap;if(2===i||1===i&&r.status!==Cn||r.lookahead)return yn;if(1===i&&(t.adler=tn(t.adler,e,n,0)),r.wrap=0,n>=r.w_size){0===i&&(Mn(r.head),r.strstart=0,r.block_start=0,r.insert=0);let t=new Uint8Array(r.w_size);t.set(e.subarray(n-r.w_size,n),0),e=t,n=r.w_size}const s=t.avail_in,a=t.next_in,o=t.input;for(t.avail_in=n,t.next_in=0,t.input=e,Vn(r);r.lookahead>=3;){let t=r.strstart,e=r.lookahead-2;do{r.ins_h=Rn(r,r.ins_h,r.window[t+3-1]),r.prev[t&r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=t,t++}while(--e);r.strstart=t,r.lookahead=2,Vn(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=2,r.match_available=0,t.next_in=a,t.input=o,t.avail_in=s,r.wrap=i,mn};const or=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var lr=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const n=e.shift();if(n){if("object"!=typeof n)throw new TypeError(n+"must be non-object");for(const e in n)or(n,e)&&(t[e]=n[e])}}return t},hr=t=>{let e=0;for(let r=0,i=t.length;r=252?6:Zs>=248?5:Zs>=240?4:Zs>=224?3:Zs>=192?2:1;ur[254]=ur[254]=1;var fr=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,n,r,i,s,a=t.length,o=0;for(i=0;i>>6,e[s++]=128|63&n):n<65536?(e[s++]=224|n>>>12,e[s++]=128|n>>>6&63,e[s++]=128|63&n):(e[s++]=240|n>>>18,e[s++]=128|n>>>12&63,e[s++]=128|n>>>6&63,e[s++]=128|63&n);return e},dr=(t,e)=>{const n=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let r,i;const s=new Array(2*n);for(i=0,r=0;r4)s[i++]=65533,r+=a-1;else{for(e&=2===a?31:3===a?15:7;a>1&&r1?s[i++]=65533:e<65536?s[i++]=e:(e-=65536,s[i++]=55296|e>>10&1023,s[i++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&cr)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let n="";for(let r=0;r{(e=e||t.length)>t.length&&(e=t.length);let n=e-1;for(;n>=0&&128==(192&t[n]);)n--;return n<0||0===n?e:n+ur[t[n]]>e?n:e},gr=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const mr=Object.prototype.toString,{Z_NO_FLUSH:wr,Z_SYNC_FLUSH:yr,Z_FULL_FLUSH:br,Z_FINISH:_r,Z_OK:vr,Z_STREAM_END:xr,Z_DEFAULT_COMPRESSION:Nr,Z_DEFAULT_STRATEGY:Lr,Z_DEFLATED:Ar}=sn;function Sr(t){this.options=lr({level:Nr,method:Ar,chunkSize:16384,windowBits:15,memLevel:8,strategy:Lr},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new gr,this.strm.avail_out=0;let n=nr(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(n!==vr)throw new Error(rn[n]);if(e.header&&rr(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?fr(e.dictionary):"[object ArrayBuffer]"===mr.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,n=ar(this.strm,t),n!==vr)throw new Error(rn[n]);this._dict_set=!0}}Sr.prototype.push=function(t,e){const n=this.strm,r=this.options.chunkSize;let i,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?_r:wr,"string"==typeof t?n.input=fr(t):"[object ArrayBuffer]"===mr.call(t)?n.input=new Uint8Array(t):n.input=t,n.next_in=0,n.avail_in=n.input.length;;)if(0===n.avail_out&&(n.output=new Uint8Array(r),n.next_out=0,n.avail_out=r),(s===yr||s===br)&&n.avail_out<=6)this.onData(n.output.subarray(0,n.next_out)),n.avail_out=0;else{if(i=ir(n,s),i===xr)return n.next_out>0&&this.onData(n.output.subarray(0,n.next_out)),i=sr(this.strm),this.onEnd(i),this.ended=!0,i===vr;if(0!==n.avail_out){if(s>0&&n.next_out>0)this.onData(n.output.subarray(0,n.next_out)),n.avail_out=0;else if(0===n.avail_in)break}else this.onData(n.output)}return!0},Sr.prototype.onData=function(t){this.chunks.push(t)},Sr.prototype.onEnd=function(t){t===vr&&(this.result=hr(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const kr=16209;var Pr=function(t,e){let n,r,i,s,a,o,l,h,c,u,f,d,p,g,m,w,y,b,_,v,x,N,L,A;const S=t.state;n=t.next_in,L=t.input,r=n+(t.avail_in-5),i=t.next_out,A=t.output,s=i-(e-t.avail_out),a=i+(t.avail_out-257),o=S.dmax,l=S.wsize,h=S.whave,c=S.wnext,u=S.window,f=S.hold,d=S.bits,p=S.lencode,g=S.distcode,m=(1<>>24,f>>>=b,d-=b,b=y>>>16&255,0===b)A[i++]=65535&y;else{if(!(16&b)){if(64&b){if(32&b){S.mode=16191;break t}t.msg="invalid literal/length code",S.mode=kr;break t}y=p[(65535&y)+(f&(1<>>=b,d-=b),d<15&&(f+=L[n++]<>>24,f>>>=b,d-=b,b=y>>>16&255,16&b){if(v=65535&y,b&=15,do){t.msg="invalid distance too far back",S.mode=kr;break t}if(f>>>=b,d-=b,b=i-s,v>b){if(b=v-b,b>h&&S.sane){t.msg="invalid distance too far back",S.mode=kr;break t}if(x=0,N=u,0===c){if(x+=l-b,b<_){_-=b;do{A[i++]=u[x++]}while(--b);x=i-v,N=A}}else if(c2;)A[i++]=N[x++],A[i++]=N[x++],A[i++]=N[x++],_-=3;_&&(A[i++]=N[x++],_>1&&(A[i++]=N[x++]))}else{x=i-v;do{A[i++]=A[x++],A[i++]=A[x++],A[i++]=A[x++],_-=3}while(_>2);_&&(A[i++]=A[x++],_>1&&(A[i++]=A[x++]))}break}if(64&b){t.msg="invalid distance code",S.mode=kr;break t}y=g[(65535&y)+(f&(1<>3,n-=_,d-=_<<3,f&=(1<{const l=o.bits;let h,c,u,f,d,p,g=0,m=0,w=0,y=0,b=0,_=0,v=0,x=0,N=0,L=0,A=null;const S=new Uint16Array(16),k=new Uint16Array(16);let P,F,I,C=null;for(g=0;g<=15;g++)S[g]=0;for(m=0;m=1&&0===S[y];y--);if(b>y&&(b=y),0===y)return i[s++]=20971520,i[s++]=20971520,o.bits=1,0;for(w=1;w0&&(0===t||1!==y))return-1;for(k[1]=0,g=1;g<15;g++)k[g+1]=k[g]+S[g];for(m=0;m852||2===t&&N>592)return 1;for(;;){P=g-v,a[m]+1=p?(F=C[a[m]-p],I=A[a[m]-p]):(F=96,I=0),h=1<>v)+c]=P<<24|F<<16|I}while(0!==c);for(h=1<>=1;if(0!==h?(L&=h-1,L+=h):L=0,m++,0===--S[g]){if(g===y)break;g=e[n+a[m]]}if(g>b&&(L&f)!==u){for(0===v&&(v=b),d+=w,_=g-v,x=1<<_;_+v852||2===t&&N>592)return 1;u=L&f,i[u]=b<<24|_<<16|d-s}}return 0!==L&&(i[d+L]=g-v<<24|64<<16),o.bits=b,0};const{Z_FINISH:jr,Z_BLOCK:Br,Z_TREES:Mr,Z_OK:qr,Z_STREAM_END:Rr,Z_NEED_DICT:Tr,Z_STREAM_ERROR:Dr,Z_DATA_ERROR:zr,Z_MEM_ERROR:Ur,Z_BUF_ERROR:Wr,Z_DEFLATED:Hr}=sn,Vr=16180,Gr=16190,Zr=16191,Yr=16192,Jr=16194,Xr=16199,Kr=16200,$r=16206,Qr=16209,ti=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ei(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const ni=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode16211?1:0},ri=t=>{if(ni(t))return Dr;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=Vr,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,qr},ii=t=>{if(ni(t))return Dr;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ri(t)},si=(t,e)=>{let n;if(ni(t))return Dr;const r=t.state;return e<0?(n=0,e=-e):(n=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?Dr:(null!==r.window&&r.wbits!==e&&(r.window=null),r.wrap=n,r.wbits=e,ii(t))},ai=(t,e)=>{if(!t)return Dr;const n=new ei;t.state=n,n.strm=t,n.window=null,n.mode=Vr;const r=si(t,e);return r!==qr&&(t.state=null),r};let oi,li,hi=!0;const ci=t=>{if(hi){oi=new Int32Array(512),li=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(Or(1,t.lens,0,288,oi,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;Or(2,t.lens,0,32,li,0,t.work,{bits:5}),hi=!1}t.lencode=oi,t.lenbits=9,t.distcode=li,t.distbits=5},ui=(t,e,n,r)=>{let i;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(n-s.wsize,n),0),s.wnext=0,s.whave=s.wsize):(i=s.wsize-s.wnext,i>r&&(i=r),s.window.set(e.subarray(n-r,n-r+i),s.wnext),(r-=i)?(s.window.set(e.subarray(n-r,n),0),s.wnext=r,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave{let n,r,i,s,a,o,l,h,c,u,f,d,p,g,m,w,y,b,_,v,x,N,L=0;const A=new Uint8Array(4);let S,k;const P=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(ni(t)||!t.output||!t.input&&0!==t.avail_in)return Dr;n=t.state,n.mode===Zr&&(n.mode=Yr),a=t.next_out,i=t.output,l=t.avail_out,s=t.next_in,r=t.input,o=t.avail_in,h=n.hold,c=n.bits,u=o,f=l,N=qr;t:for(;;)switch(n.mode){case Vr:if(0===n.wrap){n.mode=Yr;break}for(;c<16;){if(0===o)break t;o--,h+=r[s++]<>>8&255,n.check=nn(n.check,A,2,0),h=0,c=0,n.mode=16181;break}if(n.head&&(n.head.done=!1),!(1&n.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",n.mode=Qr;break}if((15&h)!==Hr){t.msg="unknown compression method",n.mode=Qr;break}if(h>>>=4,c-=4,x=8+(15&h),0===n.wbits&&(n.wbits=x),x>15||x>n.wbits){t.msg="invalid window size",n.mode=Qr;break}n.dmax=1<>8&1),512&n.flags&&4&n.wrap&&(A[0]=255&h,A[1]=h>>>8&255,n.check=nn(n.check,A,2,0)),h=0,c=0,n.mode=16182;case 16182:for(;c<32;){if(0===o)break t;o--,h+=r[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,n.check=nn(n.check,A,4,0)),h=0,c=0,n.mode=16183;case 16183:for(;c<16;){if(0===o)break t;o--,h+=r[s++]<>8),512&n.flags&&4&n.wrap&&(A[0]=255&h,A[1]=h>>>8&255,n.check=nn(n.check,A,2,0)),h=0,c=0,n.mode=16184;case 16184:if(1024&n.flags){for(;c<16;){if(0===o)break t;o--,h+=r[s++]<>>8&255,n.check=nn(n.check,A,2,0)),h=0,c=0}else n.head&&(n.head.extra=null);n.mode=16185;case 16185:if(1024&n.flags&&(d=n.length,d>o&&(d=o),d&&(n.head&&(x=n.head.extra_len-n.length,n.head.extra||(n.head.extra=new Uint8Array(n.head.extra_len)),n.head.extra.set(r.subarray(s,s+d),x)),512&n.flags&&4&n.wrap&&(n.check=nn(n.check,r,d,s)),o-=d,s+=d,n.length-=d),n.length))break t;n.length=0,n.mode=16186;case 16186:if(2048&n.flags){if(0===o)break t;d=0;do{x=r[s+d++],n.head&&x&&n.length<65536&&(n.head.name+=String.fromCharCode(x))}while(x&&d>9&1,n.head.done=!0),t.adler=n.check=0,n.mode=Zr;break;case 16189:for(;c<32;){if(0===o)break t;o--,h+=r[s++]<>>=7&c,c-=7&c,n.mode=$r;break}for(;c<3;){if(0===o)break t;o--,h+=r[s++]<>>=1,c-=1,3&h){case 0:n.mode=16193;break;case 1:if(ci(n),n.mode=Xr,e===Mr){h>>>=2,c-=2;break t}break;case 2:n.mode=16196;break;case 3:t.msg="invalid block type",n.mode=Qr}h>>>=2,c-=2;break;case 16193:for(h>>>=7&c,c-=7&c;c<32;){if(0===o)break t;o--,h+=r[s++]<>>16^65535)){t.msg="invalid stored block lengths",n.mode=Qr;break}if(n.length=65535&h,h=0,c=0,n.mode=Jr,e===Mr)break t;case Jr:n.mode=16195;case 16195:if(d=n.length,d){if(d>o&&(d=o),d>l&&(d=l),0===d)break t;i.set(r.subarray(s,s+d),a),o-=d,s+=d,l-=d,a+=d,n.length-=d;break}n.mode=Zr;break;case 16196:for(;c<14;){if(0===o)break t;o--,h+=r[s++]<>>=5,c-=5,n.ndist=1+(31&h),h>>>=5,c-=5,n.ncode=4+(15&h),h>>>=4,c-=4,n.nlen>286||n.ndist>30){t.msg="too many length or distance symbols",n.mode=Qr;break}n.have=0,n.mode=16197;case 16197:for(;n.have>>=3,c-=3}for(;n.have<19;)n.lens[P[n.have++]]=0;if(n.lencode=n.lendyn,n.lenbits=7,S={bits:n.lenbits},N=Or(0,n.lens,0,19,n.lencode,0,n.work,S),n.lenbits=S.bits,N){t.msg="invalid code lengths set",n.mode=Qr;break}n.have=0,n.mode=16198;case 16198:for(;n.have>>24,w=L>>>16&255,y=65535&L,!(m<=c);){if(0===o)break t;o--,h+=r[s++]<>>=m,c-=m,n.lens[n.have++]=y;else{if(16===y){for(k=m+2;c>>=m,c-=m,0===n.have){t.msg="invalid bit length repeat",n.mode=Qr;break}x=n.lens[n.have-1],d=3+(3&h),h>>>=2,c-=2}else if(17===y){for(k=m+3;c>>=m,c-=m,x=0,d=3+(7&h),h>>>=3,c-=3}else{for(k=m+7;c>>=m,c-=m,x=0,d=11+(127&h),h>>>=7,c-=7}if(n.have+d>n.nlen+n.ndist){t.msg="invalid bit length repeat",n.mode=Qr;break}for(;d--;)n.lens[n.have++]=x}}if(n.mode===Qr)break;if(0===n.lens[256]){t.msg="invalid code -- missing end-of-block",n.mode=Qr;break}if(n.lenbits=9,S={bits:n.lenbits},N=Or(1,n.lens,0,n.nlen,n.lencode,0,n.work,S),n.lenbits=S.bits,N){t.msg="invalid literal/lengths set",n.mode=Qr;break}if(n.distbits=6,n.distcode=n.distdyn,S={bits:n.distbits},N=Or(2,n.lens,n.nlen,n.ndist,n.distcode,0,n.work,S),n.distbits=S.bits,N){t.msg="invalid distances set",n.mode=Qr;break}if(n.mode=Xr,e===Mr)break t;case Xr:n.mode=Kr;case Kr:if(o>=6&&l>=258){t.next_out=a,t.avail_out=l,t.next_in=s,t.avail_in=o,n.hold=h,n.bits=c,Pr(t,f),a=t.next_out,i=t.output,l=t.avail_out,s=t.next_in,r=t.input,o=t.avail_in,h=n.hold,c=n.bits,n.mode===Zr&&(n.back=-1);break}for(n.back=0;L=n.lencode[h&(1<>>24,w=L>>>16&255,y=65535&L,!(m<=c);){if(0===o)break t;o--,h+=r[s++]<>b)],m=L>>>24,w=L>>>16&255,y=65535&L,!(b+m<=c);){if(0===o)break t;o--,h+=r[s++]<>>=b,c-=b,n.back+=b}if(h>>>=m,c-=m,n.back+=m,n.length=y,0===w){n.mode=16205;break}if(32&w){n.back=-1,n.mode=Zr;break}if(64&w){t.msg="invalid literal/length code",n.mode=Qr;break}n.extra=15&w,n.mode=16201;case 16201:if(n.extra){for(k=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}n.was=n.length,n.mode=16202;case 16202:for(;L=n.distcode[h&(1<>>24,w=L>>>16&255,y=65535&L,!(m<=c);){if(0===o)break t;o--,h+=r[s++]<>b)],m=L>>>24,w=L>>>16&255,y=65535&L,!(b+m<=c);){if(0===o)break t;o--,h+=r[s++]<>>=b,c-=b,n.back+=b}if(h>>>=m,c-=m,n.back+=m,64&w){t.msg="invalid distance code",n.mode=Qr;break}n.offset=y,n.extra=15&w,n.mode=16203;case 16203:if(n.extra){for(k=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}if(n.offset>n.dmax){t.msg="invalid distance too far back",n.mode=Qr;break}n.mode=16204;case 16204:if(0===l)break t;if(d=f-l,n.offset>d){if(d=n.offset-d,d>n.whave&&n.sane){t.msg="invalid distance too far back",n.mode=Qr;break}d>n.wnext?(d-=n.wnext,p=n.wsize-d):p=n.wnext-d,d>n.length&&(d=n.length),g=n.window}else g=i,p=a-n.offset,d=n.length;d>l&&(d=l),l-=d,n.length-=d;do{i[a++]=g[p++]}while(--d);0===n.length&&(n.mode=Kr);break;case 16205:if(0===l)break t;i[a++]=n.length,l--,n.mode=Kr;break;case $r:if(n.wrap){for(;c<32;){if(0===o)break t;o--,h|=r[s++]<{if(ni(t))return Dr;let e=t.state;return e.window&&(e.window=null),t.state=null,qr},mi=(t,e)=>{if(ni(t))return Dr;const n=t.state;return 2&n.wrap?(n.head=e,e.done=!1,qr):Dr},wi=(t,e)=>{const n=e.length;let r,i,s;return ni(t)?Dr:(r=t.state,0!==r.wrap&&r.mode!==Gr?Dr:r.mode===Gr&&(i=1,i=tn(i,e,n,0),i!==r.check)?zr:(s=ui(t,e,n,n),s?(r.mode=16210,Ur):(r.havedict=1,qr)))},yi=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const bi=Object.prototype.toString,{Z_NO_FLUSH:_i,Z_FINISH:vi,Z_OK:xi,Z_STREAM_END:Ni,Z_NEED_DICT:Li,Z_STREAM_ERROR:Ai,Z_DATA_ERROR:Si,Z_MEM_ERROR:ki}=sn;function Pi(t){this.options=lr({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&(15&e.windowBits||(e.windowBits|=15)),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new gr,this.strm.avail_out=0;let n=di(this.strm,e.windowBits);if(n!==xi)throw new Error(rn[n]);if(this.header=new yi,mi(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=fr(e.dictionary):"[object ArrayBuffer]"===bi.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(n=wi(this.strm,e.dictionary),n!==xi)))throw new Error(rn[n])}function Fi(t,e){const n=new Pi(e);if(n.push(t),n.err)throw n.msg||rn[n.err];return n.result}Pi.prototype.push=function(t,e){const n=this.strm,r=this.options.chunkSize,i=this.options.dictionary;let s,a,o;if(this.ended)return!1;for(a=e===~~e?e:!0===e?vi:_i,"[object ArrayBuffer]"===bi.call(t)?n.input=new Uint8Array(t):n.input=t,n.next_in=0,n.avail_in=n.input.length;;){for(0===n.avail_out&&(n.output=new Uint8Array(r),n.next_out=0,n.avail_out=r),s=pi(n,a),s===Li&&i&&(s=wi(n,i),s===xi?s=pi(n,a):s===Si&&(s=Li));n.avail_in>0&&s===Ni&&n.state.wrap>0&&0!==t[n.next_in];)fi(n),s=pi(n,a);switch(s){case Ai:case Si:case Li:case ki:return this.onEnd(s),this.ended=!0,!1}if(o=n.avail_out,n.next_out&&(0===n.avail_out||s===Ni))if("string"===this.options.to){let t=pr(n.output,n.next_out),e=n.next_out-t,i=dr(n.output,t);n.next_out=e,n.avail_out=r-e,e&&n.output.set(n.output.subarray(t,t+e),0),this.onData(i)}else this.onData(n.output.length===n.next_out?n.output:n.output.subarray(0,n.next_out));if(s!==xi||0!==o){if(s===Ni)return s=gi(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===n.avail_in)break}}return!0},Pi.prototype.onData=function(t){this.chunks.push(t)},Pi.prototype.onEnd=function(t){t===xi&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=hr(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Ii={Inflate:Pi,inflate:Fi,inflateRaw:function(t,e){return(e=e||{}).raw=!0,Fi(t,e)},ungzip:Fi,constants:sn};const{Inflate:Ci,inflate:Ei,inflateRaw:Oi,ungzip:ji}=Ii;var Bi=Ci,Mi=Ei;const qi=[];for(let Zs=0;Zs<256;Zs++){let t=Zs;for(let e=0;e<8;e++)1&t?t=3988292384^t>>>1:t>>>=1;qi[Zs]=t}const Ri=4294967295;function Ti(t,e,n){const r=t.readUint32(),i=(function(t,e,n){let r=Ri;for(let i=0;i>>8;return r}(0,new Uint8Array(t.buffer,t.byteOffset+t.offset-e-4,e),e)^Ri)>>>0;if(i!==r)throw new Error(`CRC mismatch for chunk ${n}. Expected ${r}, found ${i}`)}function Di(t,e,n){for(let r=0;r>1)&255}else{for(;s>1)&255;for(;s>1)&255}}function Hi(t,e,n,r,i){let s=0;if(0===n.length){for(;s>8&255}const Xi=new Uint16Array([255]),Ki=255===new Uint8Array(Xi.buffer)[0],$i=new Uint8Array(0);function Qi(t){const{data:e,width:n,height:r,channels:i,depth:s}=t,a=Math.ceil(s/8)*i,o=Math.ceil(s/8*i*n),l=new Uint8Array(r*o);let h,c,u=$i,f=0;for(let d=0;d>8&255}const es=Uint8Array.of(137,80,78,71,13,10,26,10);function ns(t){if(!function(t){if(t.length79)throw new Error("keyword length must be between 1 and 79")}(n),n}class as extends _e{_checkCrc;_inflator;_png;_apng;_end;_hasPalette;_palette;_hasTransparency;_transparency;_compressionMethod;_filterMethod;_interlaceMethod;_colorType;_isAnimated;_numberOfFrames;_numberOfPlays;_frames;_writingDataChunks;constructor(t,e={}){super(t);const{checkCrc:n=!1}=e;this._checkCrc=n,this._inflator=new Bi,this._png={width:-1,height:-1,channels:-1,data:new Uint8Array(0),depth:1,text:{}},this._apng={width:-1,height:-1,channels:-1,depth:1,numberOfFrames:1,numberOfPlays:0,text:{},frames:[]},this._end=!1,this._hasPalette=!1,this._palette=[],this._hasTransparency=!1,this._transparency=new Uint16Array(0),this._compressionMethod=-1,this._filterMethod=-1,this._interlaceMethod=-1,this._colorType=-1,this._isAnimated=!1,this._numberOfFrames=1,this._numberOfPlays=0,this._frames=[],this._writingDataChunks=!1,this.setBigEndian()}decode(){for(ns(this);!this._end;){const t=this.readUint32(),e=this.readChars(4);this.decodeChunk(t,e)}return this.decodeImage(),this._png}decodeApng(){for(ns(this);!this._end;){const t=this.readUint32(),e=this.readChars(4);this.decodeApngChunk(t,e)}return this.decodeApngImage(),this._apng}decodeChunk(t,e){const n=this.offset;switch(e){case"IHDR":this.decodeIHDR();break;case"PLTE":this.decodePLTE(t);break;case"IDAT":this.decodeIDAT(t);break;case"IEND":this._end=!0;break;case"tRNS":this.decodetRNS(t);break;case"iCCP":this.decodeiCCP(t);break;case"tEXt":!function(t,e,n){const r=ss(e);t[r]=function(t,e){return rs.decode(t.readBytes(e))}(e,n-r.length-1)}(this._png.text,this,t);break;case"pHYs":this.decodepHYs();break;default:this.skip(t)}if(this.offset-n!==t)throw new Error(`Length mismatch while decoding chunk ${e}`);this._checkCrc?Ti(this,t+4,e):this.skip(4)}decodeApngChunk(t,e){const n=this.offset;switch("fdAT"!==e&&"IDAT"!==e&&this._writingDataChunks&&this.pushDataToFrame(),e){case"acTL":this.decodeACTL();break;case"fcTL":this.decodeFCTL();break;case"fdAT":this.decodeFDAT(t);break;default:this.decodeChunk(t,e),this.offset=n+t}if(this.offset-n!==t)throw new Error(`Length mismatch while decoding chunk ${e}`);this._checkCrc?Ti(this,t+4,e):this.skip(4)}decodeIHDR(){const t=this._png;t.width=this.readUint32(),t.height=this.readUint32(),t.depth=function(t){if(1!==t&&2!==t&&4!==t&&8!==t&&16!==t)throw new Error(`invalid bit depth: ${t}`);return t}(this.readUint8());const e=this.readUint8();let n;switch(this._colorType=e,e){case 0:case 3:n=1;break;case 2:n=3;break;case 4:n=2;break;case 6:n=4;break;default:throw new Error(`Unknown color type: ${e}`)}if(this._png.channels=n,this._compressionMethod=this.readUint8(),0!==this._compressionMethod)throw new Error(`Unsupported compression method: ${this._compressionMethod}`);this._filterMethod=this.readUint8(),this._interlaceMethod=this.readUint8()}decodeACTL(){this._numberOfFrames=this.readUint32(),this._numberOfPlays=this.readUint32(),this._isAnimated=!0}decodeFCTL(){const t={sequenceNumber:this.readUint32(),width:this.readUint32(),height:this.readUint32(),xOffset:this.readUint32(),yOffset:this.readUint32(),delayNumber:this.readUint16(),delayDenominator:this.readUint16(),disposeOp:this.readUint8(),blendOp:this.readUint8(),data:new Uint8Array(0)};this._frames.push(t)}decodePLTE(t){if(t%3!=0)throw new RangeError(`PLTE field length must be a multiple of 3. Got ${t}`);const e=t/3;this._hasPalette=!0;const n=[];this._palette=n;for(let r=0;rthis._png.width*this._png.height)throw new Error(`tRNS chunk contains more alpha values than there are pixels (${t/2} vs ${this._png.width*this._png.height})`);this._hasTransparency=!0,this._transparency=new Uint16Array(t/2);for(let e=0;ethis._palette.length)throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${t} vs ${this._palette.length})`);let e=0;for(;e({index:((t+e.yOffset)*this._png.width+e.xOffset+n)*this._png.channels,frameIndex:(t*e.width+n)*this._png.channels});switch(e.blendOp){case 0:for(let n=0;n=n||s>=r))for(let t=0;t>>1)&255}return i}function ys(t,e,n){const r=t.length,i=[];i[0]=4;for(let s=0;s>s&a}function xs(t,e,n,r){const i=n*r,s=Math.floor(i/8),a=16-(i-8*s+r),o=(1<>8&255;t.setUint8(e,r)} +/** + * @license + * + * Copyright (c) 2021 Antti Palola, https://github.com/Pantura + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */(t,s,Ns(t,s)&~(o<1){o=!0,h=void 0;const t=e*n;a=new Uint8Array(t);const l=new DataView(r.buffer);for(let e=0;er&&(i.push(t.slice(l,s)),o=0,l=s),o+=e[s],s++;return l!==s&&i.push(t.slice(l,s)),i},fs=function(t,e,n){n||(n={});var r,i,s,a,o,l,h,c=[],u=[c],f=n.textIndent||0,d=0,p=0,g=t.split(" "),m=hs.apply(this,[" ",n])[0];if(l=-1===n.lineIndent?g[0].length+2:n.lineIndent||0){var w=Array(l).join(" "),y=[];g.map(function(t){(t=t.split(/\s*\n/)).length>1?y=y.concat(t.map(function(t,e){return(e&&t.length?"\n":"")+t})):y.push(t[0])}),g=y,l=cs.apply(this,[w,n])}for(s=0,a=g.length;se||b){if(p>e){for(o=us.apply(this,[r,i,e-(f+d),e]),c.push(o.shift()),c=[o.pop()];o.length;)u.push([o.shift()]);p=i.slice(r.length-(c[0]?c[0].length:0)).reduce(function(t,e){return t+e},0)}else c=[r];u.push(c),f=p+l,d=m}else c.push(r),f+=d+p,d=m}return h=l?function(t,e){return(e?w:"")+t.join(" ")}:function(t){return t.join(" ")},u.map(h)},ls.splitTextToSize=function(t,e,n){var r,i=(n=n||{}).fontSize||this.internal.getFontSize(),s=function(t){if(t.widths&&t.kerning)return{widths:t.widths,kerning:t.kerning};var e=this.internal.getFont(t.fontName,t.fontStyle),n="Unicode";return e.metadata[n]?{widths:e.metadata[n].widths||{0:1},kerning:e.metadata[n].kerning||{}}:{font:e.metadata,fontSize:this.internal.getFontSize(),charSpace:this.internal.getCharSpace()}}.call(this,n);r=Array.isArray(t)?t:String(t).split(/\r?\n/);var a=1*this.internal.scaleFactor*e/i;s.textIndent=n.textIndent?1*n.textIndent*this.internal.scaleFactor/i:0,s.lineIndent=n.lineIndent;var o,l,h=[];for(o=0,l=r.length;o1){for(c=0;c>")}),this.internal.viewerpreferences.isSubscribed=!0),this.internal.viewerpreferences.configuration=n,this}, +/** ==================================================================== + * @license + * jsPDF XMP metadata plugin + * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ +function(t){var e=function(){var t='',e=unescape(encodeURIComponent('')),n=unescape(encodeURIComponent(t)),r=unescape(encodeURIComponent(this.internal.__metadata__.metadata)),i=unescape(encodeURIComponent("")),s=unescape(encodeURIComponent("")),a=n.length+r.length+i.length+e.length+s.length;this.internal.__metadata__.metadata_object_number=this.internal.newObject(),this.internal.write("<< /Type /Metadata /Subtype /XML /Length "+a+" >>"),this.internal.write("stream"),this.internal.write(e+n+r+i+s),this.internal.write("endstream"),this.internal.write("endobj")},n=function(){this.internal.__metadata__.metadata_object_number&&this.internal.write("/Metadata "+this.internal.__metadata__.metadata_object_number+" 0 R")};t.addMetadata=function(t,r){return void 0===this.internal.__metadata__&&(this.internal.__metadata__={metadata:t,namespaceuri:r||"http://jspdf.default.namespaceuri/"},this.internal.events.subscribe("putCatalog",n),this.internal.events.subscribe("postPutResources",e)),this}}(I.API),function(t){var e=t.API,n=e.pdfEscape16=function(t,e){for(var n,r=e.metadata.Unicode.widths,i=["","0","00","000","0000"],s=[""],a=0,o=t.length;a=100&&(s+="\n"+r.length+" beginbfchar\n"+r.join("\n")+"\nendbfchar",r=[]),void 0!==t[e]&&null!==t[e]&&"function"==typeof t[e].toString&&(i=("0000"+t[e].toString(16)).slice(-4),e=("0000"+(+e).toString(16)).slice(-4),r.push("<"+e+"><"+i+">"));return r.length&&(s+="\n"+r.length+" beginbfchar\n"+r.join("\n")+"\nendbfchar\n"),s+"endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"};e.events.push(["putFont",function(e){!function(e){var n=e.font,i=e.out,s=e.newObject,a=e.putStream;if(n.metadata instanceof t.API.TTFFont&&"Identity-H"===n.encoding){for(var o=n.metadata.Unicode.widths,l=n.metadata.subset.encode(n.metadata.glyIdsUsed,1),h="",c=0;c>"),i("endobj");var p=s();i("<<"),i("/Type /Font"),i("/BaseFont /"+L(n.fontName)),i("/FontDescriptor "+d+" 0 R"),i("/W "+t.API.PDFObject.convert(o)),i("/CIDToGIDMap /Identity"),i("/DW 1000"),i("/Subtype /CIDFontType2"),i("/CIDSystemInfo"),i("<<"),i("/Supplement 0"),i("/Registry (Adobe)"),i("/Ordering ("+n.encoding+")"),i(">>"),i(">>"),i("endobj"),n.objectNumber=s(),i("<<"),i("/Type /Font"),i("/Subtype /Type0"),i("/ToUnicode "+f+" 0 R"),i("/BaseFont /"+L(n.fontName)),i("/Encoding /"+n.encoding),i("/DescendantFonts ["+p+" 0 R]"),i(">>"),i("endobj"),n.isAlreadyPutted=!0}}(e)}]),e.events.push(["putFont",function(e){!function(e){var n=e.font,i=e.out,s=e.newObject,a=e.putStream;if(n.metadata instanceof t.API.TTFFont&&"WinAnsiEncoding"===n.encoding){for(var o=n.metadata.rawData,l="",h=0;h>"),i("endobj"),n.objectNumber=s();for(var d=0;d>"),i("endobj"),n.isAlreadyPutted=!0}}(e)}]);var i=function(t){var e,r=t.text||"",i=t.x,s=t.y,a=t.options||{},o=t.mutex||{},l=o.pdfEscape,h=o.activeFontKey,c=o.fonts,u=h,f="",d=0,p="",g=c[u].encoding;if("Identity-H"!==c[u].encoding)return{text:r,x:i,y:s,options:a,mutex:o};for(p=r,u=h,Array.isArray(r)&&(p=r[0]),d=0;d","<","[","]","[","{","}","{","«","»","«","‹","›","‹","⁅","⁆","⁅","⁽","⁾","⁽","₍","₎","₍","≤","≥","≤","〈","〉","〈","﹙","﹚","﹙","﹛","﹜","﹛","﹝","﹞","﹝","﹤","﹥","﹤"],g=new RegExp(/^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/),m=!1,w=0;this.__bidiEngine__={};var y=function(t){var e=t.charCodeAt(),n=e>>8,r=d[n];return void 0!==r?h[256*r+(255&e)]:252===n||253===n?"AL":g.test(n)?"L":8===n?"R":"N"},b=function(t){for(var e,n=0;n=e.length||"EN"!==(l=a[o-1])&&"AN"!==l||"EN"!==(h=e[o+1])&&"AN"!==h?f="N":m&&(h="AN"),f=h===l?h:"N";break;case"ES":f="EN"===(l=o>0?a[o-1]:"B")&&o+10&&"EN"===a[o-1]){f="EN";break}if(m){f="N";break}for(c=o+1,u=e.length;c=1425&&d<=2303||64286===d;if(l=e[c],p&&("R"===l||"AL"===l)){f="R";break}}}f=o<1||"B"===(l=e[o-1])?"N":a[o-1];break;case"B":m=!1,n=!0,f=w;break;case"S":r=!0,f="N"}return f},v=function(t,e,n){var r=t.split("");return n&&x(r,n,{hiLevel:w}),r.reverse(),e&&e.reverse(),r.join("")},x=function(t,e,i){var s,a,o,l,h,d=-1,p=t.length,g=0,b=[],v=w?u:c,x=[];for(m=!1,n=!1,r=!1,a=0;a0)if(16===s){for(a=d;a-1){for(a=d;a=0&&"WS"===t[i];i--)e[i]=w}}(x,e,p)},N=function(t,e,r,i,s){if(!(s.hiLevel=t){for(l=u+1;l=t;)l++;for(h=u,o=l-1;h=0&&(t[i]=p[r+1])}(r,n,i),N(2,r,e,n,i),N(1,r,e,n,i),r.join("")};return this.__bidiEngine__.doBidiReorder=function(t,e,n){if(function(t,e){if(e)for(var n=0;n>16)&&(e=-(1+(65535^e))),this.italicAngle=+(e+"."+n)):this.italicAngle=0,this.ascender=Math.round(this.ascender*this.scaleFactor),this.decender=Math.round(this.decender*this.scaleFactor),this.lineGap=Math.round(this.lineGap*this.scaleFactor),this.capHeight=this.os2.exists&&this.os2.capHeight||this.ascender,this.xHeight=this.os2.exists&&this.os2.xHeight||0,this.familyClass=(this.os2.exists&&this.os2.familyClass||0)>>8,this.isSerif=1===(i=this.familyClass)||2===i||3===i||4===i||5===i||7===i,this.isScript=10===this.familyClass,this.flags=0,this.post.isFixedPitch&&(this.flags|=1),this.isSerif&&(this.flags|=2),this.isScript&&(this.flags|=8),0!==this.italicAngle&&(this.flags|=64),this.flags|=32,!this.cmap.unicode)throw new Error("No unicode cmap for font")},t.prototype.characterToGlyph=function(t){var e;return(null!=(e=this.cmap.unicode)?e.codeMap[t]:void 0)||0},t.prototype.widthOfGlyph=function(t){var e;return e=1e3/this.head.unitsPerEm,this.hmtx.forGlyph(t).advance*e},t.prototype.widthOfString=function(t,e,n){var r,i,s,a;for(s=0,i=0,a=(t=""+t).length;0<=a?ia;i=0<=a?++i:--i)r=t.charCodeAt(i),s+=this.widthOfGlyph(this.characterToGlyph(r))+n*(1e3/e)||0;return s*(e/1e3)},t.prototype.lineHeight=function(t,e){var n;return null==e&&(e=!1),n=e?this.lineGap:0,(this.ascender+n-this.decender)/1e3*t},t}();var Ls,As=function(){function t(t){this.data=null!=t?t:[],this.pos=0,this.length=this.data.length}return t.prototype.readByte=function(){return this.data[this.pos++]},t.prototype.writeByte=function(t){return this.data[this.pos++]=t},t.prototype.readUInt32=function(){return 16777216*this.readByte()+(this.readByte()<<16)+(this.readByte()<<8)+this.readByte()},t.prototype.writeUInt32=function(t){return this.writeByte(t>>>24&255),this.writeByte(t>>16&255),this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt32=function(){var t;return(t=this.readUInt32())>=2147483648?t-4294967296:t},t.prototype.writeInt32=function(t){return t<0&&(t+=4294967296),this.writeUInt32(t)},t.prototype.readUInt16=function(){return this.readByte()<<8|this.readByte()},t.prototype.writeUInt16=function(t){return this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt16=function(){var t;return(t=this.readUInt16())>=32768?t-65536:t},t.prototype.writeInt16=function(t){return t<0&&(t+=65536),this.writeUInt16(t)},t.prototype.readString=function(t){var e,n;for(n=[],e=0;0<=t?et;e=0<=t?++e:--e)n[e]=String.fromCharCode(this.readByte());return n.join("")},t.prototype.writeString=function(t){var e,n,r;for(r=[],e=0,n=t.length;0<=n?en;e=0<=n?++e:--e)r.push(this.writeByte(t.charCodeAt(e)));return r},t.prototype.readShort=function(){return this.readInt16()},t.prototype.writeShort=function(t){return this.writeInt16(t)},t.prototype.readLongLong=function(){var t,e,n,r,i,s,a,o;return t=this.readByte(),e=this.readByte(),n=this.readByte(),r=this.readByte(),i=this.readByte(),s=this.readByte(),a=this.readByte(),o=this.readByte(),128&t?-1*(72057594037927940*(255^t)+281474976710656*(255^e)+1099511627776*(255^n)+4294967296*(255^r)+16777216*(255^i)+65536*(255^s)+256*(255^a)+(255^o)+1):72057594037927940*t+281474976710656*e+1099511627776*n+4294967296*r+16777216*i+65536*s+256*a+o},t.prototype.writeLongLong=function(t){var e,n;return e=Math.floor(t/4294967296),n=4294967295&t,this.writeByte(e>>24&255),this.writeByte(e>>16&255),this.writeByte(e>>8&255),this.writeByte(255&e),this.writeByte(n>>24&255),this.writeByte(n>>16&255),this.writeByte(n>>8&255),this.writeByte(255&n)},t.prototype.readInt=function(){return this.readInt32()},t.prototype.writeInt=function(t){return this.writeInt32(t)},t.prototype.read=function(t){var e,n;for(e=[],n=0;0<=t?nt;n=0<=t?++n:--n)e.push(this.readByte());return e},t.prototype.write=function(t){var e,n,r,i;for(i=[],n=0,r=t.length;nr;n=0<=r?++n:--n)e={tag:t.readString(4),checksum:t.readInt(),offset:t.readInt(),length:t.readInt()},this.tables[e.tag]=e}return e.prototype.encode=function(e){var n,r,i,s,a,o,l,h,c,u,f,d,p;for(p in f=Object.keys(e).length,o=Math.log(2),c=16*Math.floor(Math.log(f)/o),s=Math.floor(c/o),h=16*f-c,(r=new As).writeInt(this.scalarType),r.writeShort(f),r.writeShort(c),r.writeShort(s),r.writeShort(h),i=16*f,l=r.pos+i,a=null,d=[],e)for(u=e[p],r.writeString(p),r.writeInt(t(u)),r.writeInt(l),r.writeInt(u.length),d=d.concat(u),"head"===p&&(a=l),l+=u.length;l%4;)d.push(0),l++;return r.write(d),n=2981146554-t(r.data),r.pos=a+8,r.writeUInt32(n),r.data},t=function(t){var e,n,r,i;for(t=Ts.call(t);t.length%4;)t.push(0);for(r=new As(t),n=0,e=0,i=t.length;eu;o=0<=u?++e:--e)n.push(t.readUInt16());return n}(),t.pos+=2,p=function(){var e,n;for(n=[],o=e=0;0<=u?eu;o=0<=u?++e:--e)n.push(t.readUInt16());return n}(),l=function(){var e,n;for(n=[],o=e=0;0<=u?eu;o=0<=u?++e:--e)n.push(t.readUInt16());return n}(),h=function(){var e,n;for(n=[],o=e=0;0<=u?eu;o=0<=u?++e:--e)n.push(t.readUInt16());return n}(),r=(this.length-t.pos+this.offset)/2,a=function(){var e,n;for(n=[],o=e=0;0<=r?er;o=0<=r?++e:--e)n.push(t.readUInt16());return n}(),o=m=0,y=i.length;m=g;n=d<=g?++w:--w)0===h[o]?s=n+l[o]:0!==(s=a[h[o]/2+(n-d)-(u-o)]||0)&&(s+=l[o]),this.codeMap[n]=65535&s}t.pos=c}return t.encode=function(t,e){var n,r,i,s,a,o,l,h,c,u,f,d,p,g,m,w,y,b,_,v,x,N,L,A,S,k,P,F,I,C,E,O,j,B,M,q,R,T,D,z,U,W,H,V,G,Z;switch(F=new As,s=Object.keys(t).sort(function(t,e){return t-e}),e){case"macroman":for(p=0,g=function(){var t=[];for(d=0;d<256;++d)t.push(0);return t}(),w={0:0},i={},I=0,j=s.length;I=32768)for(o.push(0),v.push(2*(f.length+L-d)),r=O=S;S<=h?O<=h:O>=h;r=S<=h?++O:--O)f.push(n[r].new);else o.push(P-S),v.push(0)}for(F.writeUInt16(3),F.writeUInt16(1),F.writeUInt32(12),F.writeUInt16(4),F.writeUInt16(16+8*L+2*f.length),F.writeUInt16(0),F.writeUInt16(A),F.writeUInt16(N),F.writeUInt16(u),F.writeUInt16(x),U=0,q=c.length;Ur;n=0<=r?++n:--n)e=new Is(t,this.offset),this.tables.push(e),e.isUnicode&&null==this.unicode&&(this.unicode=e);return!0},t.encode=function(t,e){var n,r;return null==e&&(e="macroman"),n=Is.encode(t,e),(r=new As).writeUInt16(0),r.writeUInt16(1),n.table=r.data.concat(n.subtable),n},t}(),Es=function(){function t(){return t.__super__.constructor.apply(this,arguments)}return Ps(t,Ls),t.prototype.tag="hhea",t.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.ascender=t.readShort(),this.decender=t.readShort(),this.lineGap=t.readShort(),this.advanceWidthMax=t.readShort(),this.minLeftSideBearing=t.readShort(),this.minRightSideBearing=t.readShort(),this.xMaxExtent=t.readShort(),this.caretSlopeRise=t.readShort(),this.caretSlopeRun=t.readShort(),this.caretOffset=t.readShort(),t.pos+=8,this.metricDataFormat=t.readShort(),this.numberOfMetrics=t.readUInt16()},t}(),Os=function(){function t(){return t.__super__.constructor.apply(this,arguments)}return Ps(t,Ls),t.prototype.tag="OS/2",t.prototype.parse=function(t){if(t.pos=this.offset,this.version=t.readUInt16(),this.averageCharWidth=t.readShort(),this.weightClass=t.readUInt16(),this.widthClass=t.readUInt16(),this.type=t.readShort(),this.ySubscriptXSize=t.readShort(),this.ySubscriptYSize=t.readShort(),this.ySubscriptXOffset=t.readShort(),this.ySubscriptYOffset=t.readShort(),this.ySuperscriptXSize=t.readShort(),this.ySuperscriptYSize=t.readShort(),this.ySuperscriptXOffset=t.readShort(),this.ySuperscriptYOffset=t.readShort(),this.yStrikeoutSize=t.readShort(),this.yStrikeoutPosition=t.readShort(),this.familyClass=t.readShort(),this.panose=function(){var e,n;for(n=[],e=0;e<10;++e)n.push(t.readByte());return n}(),this.charRange=function(){var e,n;for(n=[],e=0;e<4;++e)n.push(t.readInt());return n}(),this.vendorID=t.readString(4),this.selection=t.readShort(),this.firstCharIndex=t.readShort(),this.lastCharIndex=t.readShort(),this.version>0&&(this.ascent=t.readShort(),this.descent=t.readShort(),this.lineGap=t.readShort(),this.winAscent=t.readShort(),this.winDescent=t.readShort(),this.codePageRange=function(){var e,n;for(n=[],e=0;e<2;e=++e)n.push(t.readInt());return n}(),this.version>1))return this.xHeight=t.readShort(),this.capHeight=t.readShort(),this.defaultChar=t.readShort(),this.breakChar=t.readShort(),this.maxContext=t.readShort()},t}(),js=function(){function t(){return t.__super__.constructor.apply(this,arguments)}return Ps(t,Ls),t.prototype.tag="post",t.prototype.parse=function(t){var e,n,r;switch(t.pos=this.offset,this.format=t.readInt(),this.italicAngle=t.readInt(),this.underlinePosition=t.readShort(),this.underlineThickness=t.readShort(),this.isFixedPitch=t.readInt(),this.minMemType42=t.readInt(),this.maxMemType42=t.readInt(),this.minMemType1=t.readInt(),this.maxMemType1=t.readInt(),this.format){case 65536:case 196608:break;case 131072:var i;for(n=t.readUInt16(),this.glyphNameIndex=[],i=0;0<=n?in;i=0<=n?++i:--i)this.glyphNameIndex.push(t.readUInt16());for(this.names=[],r=[];t.posn;i=0<=n?++e:--e)r.push(t.readUInt32());return r}.call(this)}},t}(),Bs=function(t,e){this.raw=t,this.length=t.length,this.platformID=e.platformID,this.encodingID=e.encodingID,this.languageID=e.languageID},Ms=function(){function t(){return t.__super__.constructor.apply(this,arguments)}return Ps(t,Ls),t.prototype.tag="name",t.prototype.parse=function(t){var e,n,r,i,s,a,o,l,h,c,u;for(t.pos=this.offset,t.readShort(),e=t.readShort(),a=t.readShort(),n=[],i=0;0<=e?ie;i=0<=e?++i:--i)n.push({platformID:t.readShort(),encodingID:t.readShort(),languageID:t.readShort(),nameID:t.readShort(),length:t.readShort(),offset:this.offset+a+t.readShort()});for(o={},i=h=0,c=n.length;ha;e=0<=a?++e:--e)this.metrics.push({advance:t.readUInt16(),lsb:t.readInt16()});for(r=this.file.maxp.numGlyphs-this.file.hhea.numberOfMetrics,this.leftSideBearings=function(){var n,i;for(i=[],e=n=0;0<=r?nr;e=0<=r?++n:--n)i.push(t.readInt16());return i}(),this.widths=function(){var t,e,n,r;for(r=[],t=0,e=(n=this.metrics).length;tr;e=0<=r?++s:--s)o.push(this.widths.push(n));return o},t.prototype.forGlyph=function(t){return t in this.metrics?this.metrics[t]:{advance:this.metrics[this.metrics.length-1].advance,lsb:this.leftSideBearings[t-this.metrics.length]}},t}(),Ts=[].slice,Ds=function(){function t(){return t.__super__.constructor.apply(this,arguments)}return Ps(t,Ls),t.prototype.tag="glyf",t.prototype.parse=function(){return this.cache={}},t.prototype.glyphFor=function(t){var e,n,r,i,s,a,o,l,h,c;return t in this.cache?this.cache[t]:(i=this.file.loca,e=this.file.contents,n=i.indexOf(t),0===(r=i.lengthOf(t))?this.cache[t]=null:(e.pos=this.offset+n,s=(a=new As(e.read(r))).readShort(),l=a.readShort(),c=a.readShort(),o=a.readShort(),h=a.readShort(),this.cache[t]=-1===s?new Us(a,l,c,o,h):new zs(a,s,l,c,o,h),this.cache[t]))},t.prototype.encode=function(t,e,n){var r,i,s,a,o;for(s=[],i=[],a=0,o=e.length;a0&&(r+=o)}for(var l=new Array(4*n.length),h=0;h>8,l[4*h+1]=(16711680&n[h])>>16,l[4*h]=(4278190080&n[h])>>24;return l},t}(),Hs=function(){function t(t){this.font=t,this.subset={},this.unicodes={},this.next=33}return t.prototype.generateCmap=function(){var t,e,n,r,i;for(e in r=this.font.cmap.tables[0].codeMap,t={},i=this.subset)n=i[e],t[e]=r[n];return t},t.prototype.glyphsFor=function(t){var e,n,r,i,s,a,o;for(r={},s=0,a=t.length;s0)for(i in o=this.glyphsFor(e))n=o[i],r[i]=n;return r},t.prototype.encode=function(t,e){var n,r,i,s,a,o,l,h,c,u,f,d,p,g,m;for(r in n=Cs.encode(this.generateCmap(),"unicode"),s=this.glyphsFor(t),f={0:0},m=n.charMap)f[(o=m[r]).old]=o.new;for(d in u=n.maxGlyphID,s)d in f||(f[d]=u++);return h=function(t){var e,n;for(e in n={},t)n[t[e]]=e;return n}(f),c=Object.keys(h).sort(function(t,e){return t-e}),p=function(){var t,e,n;for(n=[],t=0,e=c.length;t>"),s.join("\n")}return""+n},e}();export{wt as AcroForm,gt as AcroFormAppearance,lt as AcroFormButton,ft as AcroFormCheckBox,it as AcroFormChoiceField,at as AcroFormComboBox,ot as AcroFormEditBox,st as AcroFormListBox,pt as AcroFormPasswordField,ht as AcroFormPushButton,ct as AcroFormRadioButton,dt as AcroFormTextField,S as GState,P as ShadingPattern,F as TilingPattern,I as default,I as jsPDF}; diff --git a/libs/svg2pdf.LICENSE b/libs/svg2pdf.LICENSE new file mode 100644 index 000000000..750648691 --- /dev/null +++ b/libs/svg2pdf.LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2015-2023 yWorks GmbH +Copyright (c) 2013-2015 by Vitaly Puzrin + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/libs/svg2pdf.mjs b/libs/svg2pdf.mjs new file mode 100644 index 000000000..78debc341 --- /dev/null +++ b/libs/svg2pdf.mjs @@ -0,0 +1 @@ +import{jsPDF as t,GState as e,ShadingPattern as r,TilingPattern as i}from"./jspdf.mjs";var n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)};function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}var s=function(){return s=Object.assign||function(t){for(var e,r=1,i=arguments.length;r0&&n[n.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!n||a[1]>n[0]&&a[1]255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b}}return t.prototype.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},t.prototype.toRGBA=function(){return"rgba("+this.r+", "+this.g+", "+this.b+", "+(this.a||"1")+")"},t.prototype.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r},t.prototype.getHelpXML=function(){for(var e=[],r=0;r "+l.toRGB()+" -> "+l.toHex());o.appendChild(u),o.appendChild(h),s.appendChild(o)}catch(t){}return s},t}(),h=function(){function t(t){this.color=t}return t.prototype.getFillData=function(t,e){return o(this,void 0,void 0,(function(){return l(this,(function(t){return[2,void 0]}))}))},t}(),c=function(){function t(){this.xmlSpace="",this.whiteSpace="",this.fill=null,this.fillOpacity=1,this.fontFamily="",this.fontSize=16,this.fontStyle="",this.fontWeight="",this.opacity=1,this.stroke=null,this.strokeDasharray=null,this.strokeDashoffset=0,this.strokeLinecap="",this.strokeLinejoin="",this.strokeMiterlimit=4,this.strokeOpacity=1,this.strokeWidth=1,this.alignmentBaseline="",this.textAnchor="",this.visibility="",this.color=null,this.contextFill=null,this.contextStroke=null,this.fillRule=null}return t.prototype.clone=function(){var e=new t;return e.xmlSpace=this.xmlSpace,e.whiteSpace=this.whiteSpace,e.fill=this.fill,e.fillOpacity=this.fillOpacity,e.fontFamily=this.fontFamily,e.fontSize=this.fontSize,e.fontStyle=this.fontStyle,e.fontWeight=this.fontWeight,e.opacity=this.opacity,e.stroke=this.stroke,e.strokeDasharray=this.strokeDasharray,e.strokeDashoffset=this.strokeDashoffset,e.strokeLinecap=this.strokeLinecap,e.strokeLinejoin=this.strokeLinejoin,e.strokeMiterlimit=this.strokeMiterlimit,e.strokeOpacity=this.strokeOpacity,e.strokeWidth=this.strokeWidth,e.textAnchor=this.textAnchor,e.alignmentBaseline=this.alignmentBaseline,e.visibility=this.visibility,e.color=this.color,e.fillRule=this.fillRule,e.contextFill=this.contextFill,e.contextStroke=this.contextStroke,e},t.default=function(){var e=new t;return e.xmlSpace="default",e.whiteSpace="normal",e.fill=new h(new u("rgb(0, 0, 0)")),e.fillOpacity=1,e.fontFamily="times",e.fontSize=16,e.fontStyle="normal",e.fontWeight="normal",e.opacity=1,e.stroke=null,e.strokeDasharray=null,e.strokeDashoffset=0,e.strokeLinecap="butt",e.strokeLinejoin="miter",e.strokeMiterlimit=4,e.strokeOpacity=1,e.strokeWidth=1,e.alignmentBaseline="baseline",e.textAnchor="start",e.visibility="visible",e.color=new u("rgb(0, 0, 0)"),e.fillRule="nonzero",e.contextFill=null,e.contextStroke=null,e},t.getContextColors=function(t,e){void 0===e&&(e=!1);var r={};return t.attributeState.contextFill&&(r.contextFill=t.attributeState.contextFill),t.attributeState.contextStroke&&(r.contextStroke=t.attributeState.contextStroke),e&&t.attributeState.color&&(r.color=t.attributeState.color),r},t}(),f=function(){function t(t,e){var r,i,n;this.pdf=t,this.svg2pdfParameters=e.svg2pdfParameters,this.attributeState=e.attributeState?e.attributeState.clone():c.default(),this.viewport=e.viewport,this.refsHandler=e.refsHandler,this.styleSheets=e.styleSheets,this.textMeasure=e.textMeasure,this.transform=null!==(r=e.transform)&&void 0!==r?r:this.pdf.unitMatrix,this.withinClipPath=null!==(i=e.withinClipPath)&&void 0!==i&&i,this.withinUse=null!==(n=e.withinUse)&&void 0!==n&&n}return t.prototype.clone=function(e){var r,i,n,a;return void 0===e&&(e={}),new t(this.pdf,{svg2pdfParameters:this.svg2pdfParameters,attributeState:e.attributeState?e.attributeState.clone():this.attributeState.clone(),viewport:null!==(r=e.viewport)&&void 0!==r?r:this.viewport,refsHandler:this.refsHandler,styleSheets:this.styleSheets,textMeasure:this.textMeasure,transform:null!==(i=e.transform)&&void 0!==i?i:this.transform,withinClipPath:null!==(n=e.withinClipPath)&&void 0!==n?n:this.withinClipPath,withinUse:null!==(a=e.withinUse)&&void 0!==a?a:this.withinUse})},t}(),p=function(){function t(e){this.renderedElements={},this.idMap=e,this.idPrefix=String(t.instanceCounter++)}return t.prototype.getRendered=function(t,e,r){return o(this,void 0,void 0,(function(){var i,n;return l(this,(function(a){switch(a.label){case 0:return i=this.generateKey(t,e),this.renderedElements.hasOwnProperty(i)?[2,this.renderedElements[t]]:(n=this.get(t),this.renderedElements[i]=n,[4,r(n)]);case 1:return a.sent(),[2,n]}}))}))},t.prototype.get=function(t){return this.idMap[t]},t.prototype.generateKey=function(t,e){var r="";return e&&(r=["color","contextFill","contextStroke"].map((function(t){var r,i;return null!==(i=null===(r=e[t])||void 0===r?void 0:r.toRGBA())&&void 0!==i?i:""})).join("|")),this.idPrefix+"|"+t+"|"+r},t.instanceCounter=0,t}();function d(t,e){return Math.atan2(e[1]-t[1],e[0]-t[0])}var m=2/3;function g(t,e){return[m*(e[0]-t[0])+t[0],m*(e[1]-t[1])+t[1]]}function v(t){var e=Math.sqrt(t[0]*t[0]+t[1]*t[1]);return[t[0]/e,t[1]/e]}function y(t,e){return v([e[0]-t[0],e[1]-t[1]])}function b(t,e){return[t[0]+e[0],t[1]+e[1]]}function x(t,e){var r=t[0],i=t[1];return[e.a*r+e.c*i+e.e,e.b*r+e.d*i+e.f]}var S=function(){function t(){this.segments=[]}return t.prototype.moveTo=function(t,e){return this.segments.push(new w(t,e)),this},t.prototype.lineTo=function(t,e){return this.segments.push(new k(t,e)),this},t.prototype.curveTo=function(t,e,r,i,n,a){return this.segments.push(new M(t,e,r,i,n,a)),this},t.prototype.close=function(){return this.segments.push(new C),this},t.prototype.transform=function(t){this.segments.forEach((function(e){if(e instanceof w||e instanceof k||e instanceof M){var r=x([e.x,e.y],t);e.x=r[0],e.y=r[1]}if(e instanceof M){var i=x([e.x1,e.y1],t),n=x([e.x2,e.y2],t);e.x1=i[0],e.y1=i[1],e.x2=n[0],e.y2=n[1]}}))},t.prototype.draw=function(t){var e=t.pdf;this.segments.forEach((function(t){t instanceof w?e.moveTo(t.x,t.y):t instanceof k?e.lineTo(t.x,t.y):t instanceof M?e.curveTo(t.x1,t.y1,t.x2,t.y2,t.x,t.y):e.close()}))},t}(),w=function(t,e){this.x=t,this.y=e},k=function(t,e){this.x=t,this.y=e},M=function(t,e,r,i,n,a){this.x1=t,this.y1=e,this.x2=r,this.y2=i,this.x=n,this.y=a},C=function(){};function F(t,e){return e.split(",").indexOf((t.nodeName||t.tagName).toLowerCase())>=0}function T(t,e,r,i){var n;void 0===i&&(i=r);var a=null===(n=t.style)||void 0===n?void 0:n.getPropertyValue(i);if(a)return a;var s=e.getPropertyValue(t,i);return s||(t.hasAttribute(r)&&t.getAttribute(r)||void 0)}function A(t,e,r){if("none"===T(t.element,r.styleSheets,"display"))return!1;var i=e,n=T(t.element,r.styleSheets,"visibility");return n&&(i="hidden"!==n),i}function P(t,e,r){var i=A(t,e,r);return 0!==t.element.childNodes.length&&(t.children.forEach((function(t){t.isVisible(i,r)&&(i=!0)})),i)}var B,N,E=function(){function t(){this.markers=[]}return t.prototype.addMarker=function(t){this.markers.push(t)},t.prototype.draw=function(t){return o(this,void 0,void 0,(function(){var e,r,i,n,a,s,o,u;return l(this,(function(l){switch(l.label){case 0:e=0,l.label=1;case 1:return e=0?y+=(n-f*d)/2:l.indexOf("xMax")>=0&&(y+=n-f*d),l.indexOf("YMid")>=0?b+=(a-p*m)/2:l.indexOf("YMax")>=0&&(b+=a-p*m);var x=s.pdf.Matrix(1,0,0,1,y,b),S=s.pdf.Matrix(d,0,0,m,0,0);return s.pdf.matrixMult(S,x)}function $(t,e){if(!t||"none"===t)return e.pdf.unitMatrix;for(var r,i,n=/^[\s,]*matrix\(([^)]+)\)\s*/,a=/^[\s,]*translate\(([^)]+)\)\s*/,s=/^[\s,]*rotate\(([^)]+)\)\s*/,o=/^[\s,]*scale\(([^)]+)\)\s*/,l=/^[\s,]*skewX\(([^)]+)\)\s*/,u=/^[\s,]*skewY\(([^)]+)\)\s*/,h=e.pdf.unitMatrix;t.length>0&&t.length!==i;){i=t.length;var c=n.exec(t);if(c&&(r=D(c[1]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],r[1],r[2],r[3],r[4],r[5]),h),t=t.substr(c[0].length)),c=s.exec(t)){r=D(c[1]);var f=Math.PI*r[0]/180;if(h=e.pdf.matrixMult(e.pdf.Matrix(Math.cos(f),Math.sin(f),-Math.sin(f),Math.cos(f),0,0),h),r[1]||r[2]){var p=e.pdf.Matrix(1,0,0,1,r[1],r[2]),d=e.pdf.Matrix(1,0,0,1,-r[1],-r[2]);h=e.pdf.matrixMult(d,e.pdf.matrixMult(h,p))}t=t.substr(c[0].length)}(c=a.exec(t))&&(r=D(c[1]),h=e.pdf.matrixMult(e.pdf.Matrix(1,0,0,1,r[0],r[1]||0),h),t=t.substr(c[0].length)),(c=o.exec(t))&&((r=D(c[1]))[1]||(r[1]=r[0]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],0,0,r[1],0,0),h),t=t.substr(c[0].length)),(c=l.exec(t))&&(r=parseFloat(c[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,0,Math.tan(r),1,0,0),h),t=t.substr(c[0].length)),(c=u.exec(t))&&(r=parseFloat(c[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,Math.tan(r),0,1,0,0),h),t=t.substr(c[0].length))}return h}var K=function(){function t(t,e){this.element=t,this.children=e,this.parent=null}return t.prototype.setParent=function(t){this.parent=t},t.prototype.getParent=function(){return this.parent},t.prototype.getBoundingBox=function(t){return"none"===T(this.element,t.styleSheets,"display")?[0,0,0,0]:this.getBoundingBoxCore(t)},t.prototype.computeNodeTransform=function(t){var e=this.computeNodeTransformCore(t),r=T(this.element,t.styleSheets,"transform");return r?t.pdf.matrixMult(e,$(r,t)):e},t}(),Z=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return a(e,t),e.prototype.render=function(t){return Promise.resolve()},e.prototype.getBoundingBoxCore=function(t){return[]},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(K),J=function(t){function i(e,r,i){var n=t.call(this,r,i)||this;return n.pdfGradientType=e,n.contextColor=void 0,n}return a(i,t),i.prototype.apply=function(t){return o(this,void 0,void 0,(function(){var i,n,a,s,o,u;return l(this,(function(l){return(i=this.element.getAttribute("id"))?(n=this.getStops(t.styleSheets),a=0,s=!1,n.forEach((function(t){var e=t.opacity;e&&1!==e&&(a+=e,s=!0)})),s&&(o=new e({opacity:a/n.length})),u=new r(this.pdfGradientType,this.getCoordinates(),n,o),t.pdf.addShadingPattern(i,u),[2]):[2]}))}))},i.prototype.getStops=function(t){var e=this;if(this.stops)return this.stops;if(void 0===this.contextColor){this.contextColor=null;for(var r=this;r;){var n=T(r.element,t,"color");if(n){this.contextColor=V(n,null);break}r=r.getParent()}}var a=[];return this.children.forEach((function(r){if("stop"===r.element.tagName.toLowerCase()){var n=T(r.element,t,"color"),s=V(T(r.element,t,"stop-color")||"",n?{color:V(n,null)}:{color:e.contextColor}),o=parseFloat(T(r.element,t,"stop-opacity")||"1");a.push({offset:i.parseGradientOffset(r.element.getAttribute("offset")||"0"),color:[s.r,s.g,s.b],opacity:o})}})),this.stops=a},i.prototype.getBoundingBoxCore=function(t){return X(this.element,t)},i.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},i.prototype.isVisible=function(t,e){return P(this,t,e)},i.parseGradientOffset=function(t){var e=parseFloat(t);return!isNaN(e)&&t.indexOf("%")>=0?e/100:e},i}(Z),tt=function(t){function e(e,r){return t.call(this,"axial",e,r)||this}return a(e,t),e.prototype.getCoordinates=function(){return[parseFloat(this.element.getAttribute("x1")||"0"),parseFloat(this.element.getAttribute("y1")||"0"),parseFloat(this.element.getAttribute("x2")||"1"),parseFloat(this.element.getAttribute("y2")||"0")]},e}(J),et=function(t){function e(e,r){return t.call(this,"radial",e,r)||this}return a(e,t),e.prototype.getCoordinates=function(){var t=this.element.getAttribute("cx"),e=this.element.getAttribute("cy"),r=this.element.getAttribute("fx"),i=this.element.getAttribute("fy");return[parseFloat(r||t||"0.5"),parseFloat(i||e||"0.5"),0,parseFloat(t||"0.5"),parseFloat(e||"0.5"),parseFloat(this.element.getAttribute("r")||"0.5")]},e}(J),rt=function(){function t(t,e){this.key=t,this.gradient=e}return t.prototype.getFillData=function(t,e){return o(this,void 0,void 0,(function(){var r,i,n;return l(this,(function(a){switch(a.label){case 0:return[4,e.refsHandler.getRendered(this.key,null,(function(t){return t.apply(new f(e.pdf,{refsHandler:e.refsHandler,textMeasure:e.textMeasure,styleSheets:e.styleSheets,viewport:e.viewport,svg2pdfParameters:e.svg2pdfParameters}))}))];case 1:return a.sent(),this.gradient.element.hasAttribute("gradientUnits")&&"objectboundingbox"!==this.gradient.element.getAttribute("gradientUnits").toLowerCase()?r=e.pdf.unitMatrix:(i=t.getBoundingBox(e),r=e.pdf.Matrix(i[2],0,0,i[3],i[0],i[1])),n=$(T(this.gradient.element,e.styleSheets,"gradientTransform","transform"),e),[2,{key:this.key,matrix:e.pdf.matrixMult(n,r)}]}}))}))},t}(),it=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return a(e,t),e.prototype.apply=function(t){return o(this,void 0,void 0,(function(){var e,r,n,a,s;return l(this,(function(o){switch(o.label){case 0:if(!(e=this.element.getAttribute("id")))return[2];r=this.getBoundingBox(t),n=new i([r[0],r[1],r[0]+r[2],r[1]+r[3]],r[2],r[3]),t.pdf.beginTilingPattern(n),a=0,s=this.children,o.label=1;case 1:return a=0?(a=t,!0):(t=t.toLowerCase(),!!j.hasOwnProperty(t)&&(a=t,!0))}))||(a="times"),a}(t.attributeState,F,t)}var A=T(i,t.styleSheets,"font-size");if(A){var P=t.pdf.getFontSize();t.attributeState.fontSize=H(A,P)}var B=T(i,t.styleSheets,"vertical-align")||T(i,t.styleSheets,"alignment-baseline");if(B){var N=B.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/);N&&(t.attributeState.alignmentBaseline=N[0])}var E=T(i,t.styleSheets,"text-anchor");E&&(t.attributeState.textAnchor=E);var L=T(i,t.styleSheets,"fill-rule");L&&(t.attributeState.fillRule=L)}function ot(t,r,i){var n=1,a=1;n*=t.attributeState.fillOpacity,n*=t.attributeState.opacity,t.attributeState.fill instanceof h&&void 0!==t.attributeState.fill.color.a&&(n*=t.attributeState.fill.color.a),a*=t.attributeState.strokeOpacity,a*=t.attributeState.opacity,t.attributeState.stroke instanceof h&&void 0!==t.attributeState.stroke.color.a&&(a*=t.attributeState.stroke.color.a);var s,o,l=n<1,u=a<1;if(F(i,"use")?(l=!0,u=!0,n*=t.attributeState.fill?1:0,a*=t.attributeState.stroke?1:0):t.withinUse&&(t.attributeState.fill!==r.attributeState.fill?(l=!0,n*=t.attributeState.fill?1:0):l&&!t.attributeState.fill&&(n=0),t.attributeState.stroke!==r.attributeState.stroke?(u=!0,a*=t.attributeState.stroke?1:0):u&&!t.attributeState.stroke&&(a=0)),l||u){var c={};l&&(c.opacity=n),u&&(c["stroke-opacity"]=a),t.pdf.setGState(new e(c))}if(t.attributeState.fill&&t.attributeState.fill!==r.attributeState.fill&&t.attributeState.fill instanceof h&&t.attributeState.fill.color.ok&&!F(i,"text")&&t.pdf.setFillColor(t.attributeState.fill.color.r,t.attributeState.fill.color.g,t.attributeState.fill.color.b),t.attributeState.strokeWidth!==r.attributeState.strokeWidth&&t.pdf.setLineWidth(t.attributeState.strokeWidth),t.attributeState.stroke!==r.attributeState.stroke&&t.attributeState.stroke instanceof h&&t.pdf.setDrawColor(t.attributeState.stroke.color.r,t.attributeState.stroke.color.g,t.attributeState.stroke.color.b),t.attributeState.strokeLinecap!==r.attributeState.strokeLinecap&&t.pdf.setLineCap(t.attributeState.strokeLinecap),t.attributeState.strokeLinejoin!==r.attributeState.strokeLinejoin&&t.pdf.setLineJoin(t.attributeState.strokeLinejoin),t.attributeState.strokeDasharray===r.attributeState.strokeDasharray&&t.attributeState.strokeDashoffset===r.attributeState.strokeDashoffset||!t.attributeState.strokeDasharray||t.pdf.setLineDashPattern(t.attributeState.strokeDasharray,t.attributeState.strokeDashoffset),t.attributeState.strokeMiterlimit!==r.attributeState.strokeMiterlimit&&t.pdf.setLineMiterLimit(t.attributeState.strokeMiterlimit),t.attributeState.fontFamily!==r.attributeState.fontFamily&&(s=j.hasOwnProperty(t.attributeState.fontFamily)?j[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.attributeState.fill&&t.attributeState.fill!==r.attributeState.fill&&t.attributeState.fill instanceof h&&t.attributeState.fill.color.ok){var f=t.attributeState.fill.color;t.pdf.setTextColor(f.r,f.g,f.b)}t.attributeState.fontWeight===r.attributeState.fontWeight&&t.attributeState.fontStyle===r.attributeState.fontStyle||(o=U(t.attributeState.fontStyle,t.attributeState.fontWeight)),void 0===s&&void 0===o||(void 0===s&&(s=j.hasOwnProperty(t.attributeState.fontFamily)?j[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.pdf.setFont(s,o)),t.attributeState.fontSize!==r.attributeState.fontSize&&t.pdf.setFontSize(t.attributeState.fontSize*t.pdf.internal.scaleFactor)}function lt(t,e,r){var i=O.exec(t);if(i){var n=i[1];return r.refsHandler.get(n)||void 0}}function ut(t,e,r){return o(this,void 0,void 0,(function(){var i,n;return l(this,(function(a){switch(a.label){case 0:return i=r.clone(),e.element.hasAttribute("clipPathUnits")&&"objectboundingbox"===e.element.getAttribute("clipPathUnits").toLowerCase()&&(n=t.getBoundingBox(r),i.transform=r.pdf.matrixMult(r.pdf.Matrix(n[2],0,0,n[3],n[0],n[1]),r.transform)),[4,e.apply(i)];case 1:return a.sent(),[2]}}))}))}var ht=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return a(e,t),e.prototype.render=function(t){return o(this,void 0,void 0,(function(){var e,r,i,n;return l(this,(function(a){switch(a.label){case 0:return this.isVisible("hidden"!==t.attributeState.visibility,t)?((e=t.clone()).transform=e.pdf.matrixMult(this.computeNodeTransform(e),t.transform),st(e,this),r=T(this.element,e.styleSheets,"clip-path"),(i=r&&"none"!==r)?(n=lt(r,0,e))?n.isVisible(!0,e)?(e.pdf.saveGraphicsState(),[4,ut(this,n,e)]):[3,2]:[3,4]:[3,5]):[2];case 1:return a.sent(),[3,3];case 2:return[2];case 3:return[3,5];case 4:i=!1,a.label=5;case 5:return e.withinClipPath||e.pdf.saveGraphicsState(),ot(e,t,this.element),[4,this.renderCore(e)];case 6:return a.sent(),e.withinClipPath||e.pdf.restoreGraphicsState(),i&&e.pdf.restoreGraphicsState(),[2]}}))}))},e}(K),ct=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return a(e,t),e}(ht),ft=function(t){function e(e,r,i){var n=t.call(this,r,i)||this;return n.cachedPath=null,n.hasMarkers=e,n}return a(e,t),e.prototype.renderCore=function(t){return o(this,void 0,void 0,(function(){var e;return l(this,(function(r){switch(r.label){case 0:return null===(e=this.getCachedPath(t))||0===e.segments.length?[2]:(t.withinClipPath?e.transform(t.transform):t.pdf.setCurrentTransformationMatrix(t.transform),e.draw(t),[4,this.fillOrStroke(t)]);case 1:return r.sent(),this.hasMarkers?[4,this.drawMarkers(t,e)]:[3,3];case 2:r.sent(),r.label=3;case 3:return[2]}}))}))},e.prototype.getCachedPath=function(t){return this.cachedPath||(this.cachedPath=this.getPath(t))},e.prototype.drawMarkers=function(t,e){return o(this,void 0,void 0,(function(){return l(this,(function(r){switch(r.label){case 0:return[4,this.getMarkers(e,t).draw(t.clone({transform:t.pdf.unitMatrix}))];case 1:return r.sent(),[2]}}))}))},e.prototype.fillOrStroke=function(t){return o(this,void 0,void 0,(function(){var e,r,i,n,a;return l(this,(function(s){switch(s.label){case 0:return t.withinClipPath?[2]:(e=t.attributeState.fill,r=t.attributeState.stroke&&0!==t.attributeState.strokeWidth,e?[4,e.getFillData(this,t)]:[3,2]);case 1:return n=s.sent(),[3,3];case 2:n=void 0,s.label=3;case 3:return i=n,a="evenodd"===t.attributeState.fillRule,e&&r||t.withinUse?a?t.pdf.fillStrokeEvenOdd(i):t.pdf.fillStroke(i):e?a?t.pdf.fillEvenOdd(i):t.pdf.fill(i):r?t.pdf.stroke():t.pdf.discardPath(),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){var e=this.getCachedPath(t);if(!e||!e.segments.length)return[0,0,0,0];for(var r=Number.POSITIVE_INFINITY,i=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,a=Number.NEGATIVE_INFINITY,s=0,o=0,l=0;lt){var i=s[r-1];c=(i instanceof w||i instanceof k||i instanceof M)&&i}}));var p=n&&(t===s.length-1||!(s[t]instanceof w)&&s[t+1]instanceof w),m=i&&t>0&&!(1===t&&s[t-1]instanceof w),g=s[t-1]||null;if(g instanceof w||g instanceof k||g instanceof M){if(e instanceof M)f&&a.addMarker(new L(r,[g.x,g.y],d(c?[c.x,c.y]:[g.x,g.y],[e.x1,e.y1]),!0)),p&&a.addMarker(new L(n,[e.x,e.y],d([e.x2,e.y2],[e.x,e.y]))),m&&(l=y([g.x,g.y],[e.x1,e.y1]),l=g instanceof w?l:v(b(o,l)),a.addMarker(new L(i,[g.x,g.y],Math.atan2(l[1],l[0])))),o=y([e.x2,e.y2],[e.x,e.y]);else if(e instanceof w||e instanceof k){if(l=y([g.x,g.y],[e.x,e.y]),f){var x=c?y([c.x,c.y],[e.x,e.y]):l;a.addMarker(new L(r,[g.x,g.y],Math.atan2(x[1],x[0]),!0))}if(p&&a.addMarker(new L(n,[e.x,e.y],Math.atan2(l[1],l[0]))),m){x=e instanceof w?o:g instanceof w?l:v(b(o,l));a.addMarker(new L(i,[g.x,g.y],Math.atan2(x[1],x[0])))}o=l}else if(e instanceof C){if(l=y([g.x,g.y],[u.x,u.y]),m){x=g instanceof w?l:v(b(o,l));a.addMarker(new L(i,[g.x,g.y],Math.atan2(x[1],x[0])))}if(p){x=v(b(l,h));a.addMarker(new L(n,[u.x,u.y],Math.atan2(x[1],x[0])))}o=l}}else{u=e instanceof w&&e;var S=s[t+1];(S instanceof w||S instanceof k||S instanceof M)&&(h=y([u.x,u.y],[S.x,S.y]))}},p=0;p=0;t--){if("preserve"===this.contexts[t].attributeState.xmlSpace||"pre"===this.contexts[t].attributeState.whiteSpace||(this.texts[t]=this.texts[t].replace(/\s+$/,"")),this.texts[t].match(/[^\s]/))return!1}return!0},t.prototype.measureText=function(t){for(var e=0;e0&&(m=t.textMeasure.measureTextWidth(d,t.attributeState),!("preserve"===t.attributeState.xmlSpace||"pre"===t.attributeState.whiteSpace)&&f.match(/^\s/)&&(i=0),r=(h-m)/(d.length-i)||0),"visible"===c&&(g=t.attributeState.alignmentBaseline,v=St(t.attributeState),t.pdf.text(d,a+o-e,s+u,{baseline:R(g),angle:t.transform,renderingMode:"fill"===v?void 0:v,charSpace:0===r?void 0:r}),this.boundingBox=[a+o-e,s+u+.1*n,t.textMeasure.measureTextWidth(d,t.attributeState),n]);else{for(y=[],b=new Vt(this,t.attributeState.textAnchor,a+o,s+u),y.push({type:"",chunk:b}),x=this.processTSpans(this,this.element,t,y,b,{prevText:" ",prevContext:t}),i=x?0:1,S=!0,w=y.length-1;w>=0;w--)S&&(S=y[w].chunk.rightTrimText());h>0&&(k=0,M=0,y.forEach((function(e){var r=e.chunk;r.measureText(t),r.textMeasures.forEach((function(t){var e=t.width,r=t.length;k+=e,M+=r}))})),r=(h-k)/(M-i)),y.reduce((function(e,i){var n=i.type,a=i.chunk;return"x"===n?a.setX(e[0]):"y"===n&&a.setY(e[1]),a.put(t,r)}),[0,0])}return t.pdf.restoreGraphicsState(),[2]}))}))},e.prototype.isVisible=function(t,e){return P(this,t,e)},e.prototype.getBoundingBoxCore=function(t){return this.boundingBox.length>0?this.boundingBox:X(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(ct);function qt(){if(Pt)return At;function t(){if(!(this instanceof t))return new t;this.queue=[],this.cache=null}return Pt=1,t.prototype.matrix=function(t){return 1===t[0]&&0===t[1]&&0===t[2]&&1===t[3]&&0===t[4]&&0===t[5]||(this.cache=null,this.queue.push(t)),this},t.prototype.translate=function(t,e){return 0===t&&0===e||(this.cache=null,this.queue.push([1,0,0,1,t,e])),this},t.prototype.scale=function(t,e){return 1===t&&1===e||(this.cache=null,this.queue.push([t,0,0,e,0,0])),this},t.prototype.rotate=function(t,e,r){var i,n,a;return 0!==t&&(this.translate(e,r),i=t*Math.PI/180,n=Math.cos(i),a=Math.sin(i),this.queue.push([n,a,-a,n,0,0]),this.cache=null,this.translate(-e,-r)),this},t.prototype.skewX=function(t){return 0!==t&&(this.cache=null,this.queue.push([1,0,Math.tan(t*Math.PI/180),1,0,0])),this},t.prototype.skewY=function(t){return 0!==t&&(this.cache=null,this.queue.push([1,Math.tan(t*Math.PI/180),0,1,0,0])),this},t.prototype.toArray=function(){if(this.cache)return this.cache;if(!this.queue.length)return this.cache=[1,0,0,1,0,0],this.cache;if(this.cache=this.queue[0],1===this.queue.length)return this.cache;for(var t=1;t1&&(n=1),n<-1&&(n=-1),(t*i-e*r<0?-1:1)*Math.acos(n)}function r(t,e){var r=4/3*Math.tan(e/4),i=Math.cos(t),n=Math.sin(t),a=Math.cos(t+e),s=Math.sin(t+e);return[i,n,i-n*r,n+i*r,a+s*r,s-a*r,a,s]}return Et=function(i,n,a,s,o,l,u,h,c){var f=Math.sin(c*t/360),p=Math.cos(c*t/360),d=p*(i-a)/2+f*(n-s)/2,m=-f*(i-a)/2+p*(n-s)/2;if(0===d&&0===m)return[];if(0===u||0===h)return[];u=Math.abs(u),h=Math.abs(h);var g=d*d/(u*u)+m*m/(h*h);g>1&&(u*=Math.sqrt(g),h*=Math.sqrt(g));var v=function(r,i,n,a,s,o,l,u,h,c){var f=c*(r-n)/2+h*(i-a)/2,p=-h*(r-n)/2+c*(i-a)/2,d=l*l,m=u*u,g=f*f,v=p*p,y=d*m-d*v-m*g;y<0&&(y=0),y/=d*v+m*g;var b=(y=Math.sqrt(y)*(s===o?-1:1))*l/u*p,x=y*-u/l*f,S=c*b-h*x+(r+n)/2,w=h*b+c*x+(i+a)/2,k=(f-b)/l,M=(p-x)/u,C=(-f-b)/l,F=(-p-x)/u,T=e(1,0,k,M),A=e(k,M,C,F);return 0===o&&A>0&&(A-=t),1===o&&A<0&&(A+=t),[S,w,T,A]}(i,n,a,s,o,l,u,h,f,p),y=[],b=v[2],x=v[3],S=Math.max(Math.ceil(Math.abs(x)/(t/4)),1);x/=S;for(var w=0;wMath.abs(c-o)?(c-s)/h:h/(c-o))/Math.PI,this.ax>=0?(this.rx=Math.sqrt(c),this.ry=Math.sqrt(f)):(this.ax+=90,this.rx=Math.sqrt(f),this.ry=Math.sqrt(c)),this},r.prototype.isDegenerate=function(){return this.rx=48&&t<=57}function i(t){return t>=48&&t<=57||43===t||45===t||46===t}function n(t){this.index=0,this.path=t,this.max=t.length,this.result=[],this.param=0,this.err="",this.segmentStart=0,this.data=[]}function a(t){for(;t.index=5760&&e.indexOf(r)>=0);)t.index++;var r}function s(t){var e=t.path.charCodeAt(t.index);return 48===e?(t.param=0,void t.index++):49===e?(t.param=1,void t.index++):void(t.err="SvgPath: arc flag can be 0 or 1 only (at pos "+t.index+")")}function o(t){var e,i=t.index,n=i,a=t.max,s=!1,o=!1,l=!1,u=!1;if(n>=a)t.err="SvgPath: missed param (at pos "+n+")";else if(43!==(e=t.path.charCodeAt(n))&&45!==e||(e=++n2&&(e.result.push([r,n[0],n[1]]),n=n.slice(2),i="l",r="m"===r?"l":"L"),"r"===i)e.result.push([r].concat(n));else for(;n.length>=t[i]&&(e.result.push([r].concat(n.splice(0,t[i]))),t[i]););}function u(e){var r,n,u,h,c,f=e.max;if(e.segmentStart=e.index,n=97==(32|(r=e.path.charCodeAt(e.index))),function(t){switch(32|t){case 109:case 122:case 108:case 104:case 118:case 99:case 115:case 113:case 116:case 97:case 114:return!0}return!1}(r))if(h=t[e.path[e.index].toLowerCase()],e.index++,a(e),e.data=[],h){for(u=!1;;){for(c=h;c>0;c--){if(!n||3!==c&&4!==c?o(e):s(e),e.err.length)return;e.data.push(e.param),a(e),u=!1,e.index=e.max)break;if(!i(e.path.charCodeAt(e.index)))break}}l(e)}else l(e);else e.err="SvgPath: bad command "+e.path[e.index]+" (at pos "+e.index+")"}return Ft=function(t){var e=new n(t),r=e.max;for(a(e);e.index0,u=["m",(l=t.calc(i[1],i[2],c))[0],l[1]];break;default:for(u=[h=i[0]],c=h.toLowerCase()===h,e=1;e=0;)t.matrix(this.__stack[e].toArray());this.__matrix(t),this.__stack=[]}},a.prototype.toString=function(){var t="",e="",r=!1;this.__evaluateStack();for(var i=0,n=this.segments.length;i=0&&(t+=" "):l>=0&&(t+=" "),t+=l}e=s}return t},a.prototype.translate=function(t,e){return this.__stack.push(r().translate(t,e||0)),this},a.prototype.scale=function(t,e){return this.__stack.push(r().scale(t,e||0===e?e:t)),this},a.prototype.rotate=function(t,e,i){return this.__stack.push(r().rotate(t,e||0,i||0)),this},a.prototype.skewX=function(t){return this.__stack.push(r().skewX(t)),this},a.prototype.skewY=function(t){return this.__stack.push(r().skewY(t)),this},a.prototype.matrix=function(t){return this.__stack.push(r().matrix(t)),this},a.prototype.transform=function(t){return t.trim()?(this.__stack.push(e(t)),this):this},a.prototype.round=function(t){var e,r=0,i=0,n=0,a=0;return t=t||0,this.__evaluateStack(),this.segments.forEach((function(s){var o=s[0].toLowerCase()===s[0];switch(s[0]){case"H":case"h":return o&&(s[1]+=n),n=s[1]-s[1].toFixed(t),void(s[1]=+s[1].toFixed(t));case"V":case"v":return o&&(s[1]+=a),a=s[1]-s[1].toFixed(t),void(s[1]=+s[1].toFixed(t));case"Z":case"z":return n=r,void(a=i);case"M":case"m":return o&&(s[1]+=n,s[2]+=a),n=s[1]-s[1].toFixed(t),a=s[2]-s[2].toFixed(t),r=n,i=a,s[1]=+s[1].toFixed(t),void(s[2]=+s[2].toFixed(t));case"A":case"a":return o&&(s[6]+=n,s[7]+=a),n=s[6]-s[6].toFixed(t),a=s[7]-s[7].toFixed(t),s[1]=+s[1].toFixed(t),s[2]=+s[2].toFixed(t),s[3]=+s[3].toFixed(t+2),s[6]=+s[6].toFixed(t),void(s[7]=+s[7].toFixed(t));default:return e=s.length,o&&(s[e-2]+=n,s[e-1]+=a),n=s[e-2]-s[e-2].toFixed(t),a=s[e-1]-s[e-1].toFixed(t),void s.forEach((function(e,r){r&&(s[r]=+s[r].toFixed(t))}))}})),this},a.prototype.iterate=function(t,e){var r,i,n,a=this.segments,s={},o=!1,l=0,u=0,h=0,c=0;if(e||this.__evaluateStack(),a.forEach((function(e,r){var i=t(e,r,l,u);Array.isArray(i)&&(s[r]=i,o=!0);var n=e[0]===e[0].toLowerCase();switch(e[0]){case"m":case"M":return l=e[1]+(n?l:0),u=e[2]+(n?u:0),h=l,void(c=u);case"h":case"H":return void(l=e[1]+(n?l:0));case"v":case"V":return void(u=e[1]+(n?u:0));case"z":case"Z":return l=h,void(u=c);default:l=e[e.length-2]+(n?l:0),u=e[e.length-1]+(n?u:0)}})),!o)return this;for(n=[],r=0;r0&&!!T(this.children[0].element,t.styleSheets,"clip-rule"),a=n?this.getClipRuleAttr(this.children[0].element,t.styleSheets):this.getClipRuleAttr(this.element,t.styleSheets),t.pdf.clip(a).discardPath(),t.pdf.setCurrentTransformationMatrix(e.inversed()),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return Y(t,this)},e.prototype.isVisible=function(t,e){return P(this,t,e)},e.prototype.getClipRuleAttr=function(t,e){return"evenodd"===T(t,e,"clip-rule")?"evenodd":void 0},e}(Z);function oe(t,e){var r,i=[];switch(function(t,e){for(var r=[],i=0;i=0;l--){var u=o.cssRules[l];if(u instanceof CSSStyleRule){var h=u;if(h.selectorText.indexOf(",")>=0){o.deleteRule(l);for(var c=h.cssText.substring(h.selectorText.length),f=t.splitSelectorAtCommas(h.selectorText),p=0;p",t.epsilon=.1,t}();function he(t,e){return o(this,arguments,void 0,(function(t,e,r){var i,n,a,o,u,h,c,d,m,g,v,y,b,x;return void 0===r&&(r={}),l(this,(function(l){switch(l.label){case 0:return i=null!==(y=r.x)&&void 0!==y?y:0,n=null!==(b=r.y)&&void 0!==b?b:0,a=null!==(x=r.loadExternalStyleSheets)&&void 0!==x&&x,u=new p(o={}),[4,(h=new le(t,a)).load()];case 1:return l.sent(),c=new gt(e.internal.pageSize.getWidth(),e.internal.pageSize.getHeight()),d=s(s({},r),{element:t}),m=new ue,g=new f(e,{refsHandler:u,styleSheets:h,viewport:c,svg2pdfParameters:d,textMeasure:m}),e.advancedAPI(),e.saveGraphicsState(),e.setCurrentTransformationMatrix(e.Matrix(1,0,0,1,i,n)),e.setLineWidth(g.attributeState.strokeWidth),v=g.attributeState.fill.color,e.setFillColor(v.r,v.g,v.b),e.setFont(g.attributeState.fontFamily),e.setFontSize(g.attributeState.fontSize*e.internal.scaleFactor),[4,oe(t,o).render(g)];case 2:return l.sent(),e.restoreGraphicsState(),e.compatAPI(),g.textMeasure.cleanupTextMeasuring(),[2,e]}}))}))}t.API.svg=function(t,e){return void 0===e&&(e={}),he(t,this,e)};export{he as svg2pdf}; diff --git a/libs/svg2pdf/Readme.md b/libs/svg2pdf/Readme.md new file mode 100644 index 000000000..43334fe38 --- /dev/null +++ b/libs/svg2pdf/Readme.md @@ -0,0 +1,22 @@ +# svg2pdf + +## Original code + +https://github.com/yWorks/svg2pdf.js + +## Modification in original version + +https://github.com/linev/svg2pdf.js/commits/jsroot/ + +1. Do not use 'specifity' and 'cssesc' - not required for JSROOT +2. Keep only 'jspdf' as external - rest is included in build + +## How to build + + npm install + npm run build + sed '$ d' ./dist/svg2pdf.es.js | sed "s/from 'jspdf'/from '.\/jspdf.mjs'/g" > ~/git/jsroot/modules/base/svg2pdf.mjs + sed '$ d' ./dist/svg2pdf.es.min.js | sed 's/from"jspdf"/from".\/jspdf.mjs"/g' > ~/git/jsroot/libs/svg2pdf.mjs + +Command sed '$ d' removew reference on map and modifies import + diff --git a/libs/three.mjs b/libs/three.mjs index bfe2e8ceb..a7017ec4a 100644 --- a/libs/three.mjs +++ b/libs/three.mjs @@ -1,6 +1,6 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -const t="162",e={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},n={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},i=0,r=1,s=2,a=3,o=0,l=1,c=2,h=3,u=0,d=1,p=2,m=0,f=1,g=2,v=3,_=4,x=5,y=100,M=101,S=102,b=103,E=104,T=200,w=201,A=202,R=203,C=204,L=205,P=206,I=207,U=208,D=209,N=210,O=211,F=212,z=213,B=214,H=0,G=1,k=2,V=3,W=4,X=5,j=6,q=7,Y=0,J=1,Z=2,K=0,$=1,Q=2,tt=3,et=4,nt=5,it=6,rt=7,st="attached",at="detached",ot=300,lt=301,ct=302,ht=303,ut=304,dt=306,pt=1e3,mt=1001,ft=1002,gt=1003,vt=1004,_t=1004,xt=1005,yt=1005,Mt=1006,St=1007,bt=1007,Et=1008,Tt=1008,wt=1009,At=1010,Rt=1011,Ct=1012,Lt=1013,Pt=1014,It=1015,Ut=1016,Dt=1017,Nt=1018,Ot=1020,Ft=1021,zt=1023,Bt=1024,Ht=1025,Gt=1026,kt=1027,Vt=1028,Wt=1029,Xt=1030,jt=1031,qt=1033,Yt=33776,Jt=33777,Zt=33778,Kt=33779,$t=35840,Qt=35841,te=35842,ee=35843,ne=36196,ie=37492,re=37496,se=37808,ae=37809,oe=37810,le=37811,ce=37812,he=37813,ue=37814,de=37815,pe=37816,me=37817,fe=37818,ge=37819,ve=37820,_e=37821,xe=36492,ye=36494,Me=36495,Se=36283,be=36284,Ee=36285,Te=36286,we=2200,Ae=2201,Re=2202,Ce=2300,Le=2301,Pe=2302,Ie=2400,Ue=2401,De=2402,Ne=2500,Oe=2501,Fe=0,ze=1,Be=2,He=3200,Ge=3201,ke=0,Ve=1,We="",Xe="srgb",je="srgb-linear",qe="display-p3",Ye="display-p3-linear",Je="linear",Ze="srgb",Ke="rec709",$e="p3",Qe=0,tn=7680,en=7681,nn=7682,rn=7683,sn=34055,an=34056,on=5386,ln=512,cn=513,hn=514,un=515,dn=516,pn=517,mn=518,fn=519,gn=512,vn=513,_n=514,xn=515,yn=516,Mn=517,Sn=518,bn=519,En=35044,Tn=35048,wn=35040,An=35045,Rn=35049,Cn=35041,Ln=35046,Pn=35050,In=35042,Un="100",Dn="300 es",Nn=1035,On=2e3,Fn=2001;class zn{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+Bn[t>>16&255]+Bn[t>>24&255]+"-"+Bn[255&e]+Bn[e>>8&255]+"-"+Bn[e>>16&15|64]+Bn[e>>24&255]+"-"+Bn[63&n|128]+Bn[n>>8&255]+"-"+Bn[n>>16&255]+Bn[n>>24&255]+Bn[255&i]+Bn[i>>8&255]+Bn[i>>16&255]+Bn[i>>24&255]).toLowerCase()}function Wn(t,e,n){return Math.max(e,Math.min(n,t))}function Xn(t,e){return(t%e+e)%e}function jn(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)}function qn(t,e,n){return t!==e?(n-t)/(e-t):0}function Yn(t,e,n){return(1-n)*t+n*e}function Jn(t,e,n,i){return Yn(t,e,1-Math.exp(-n*i))}function Zn(t,e=1){return e-Math.abs(Xn(t,2*e)-e)}function Kn(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)}function $n(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)}function Qn(t,e){return t+Math.floor(Math.random()*(e-t+1))}function ti(t,e){return t+Math.random()*(e-t)}function ei(t){return t*(.5-Math.random())}function ni(t){void 0!==t&&(Hn=t);let e=Hn+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296}function ii(t){return t*Gn}function ri(t){return t*kn}function si(t){return!(t&t-1)&&0!==t}function ai(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function oi(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function li(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}function ci(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function hi(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const ui={DEG2RAD:Gn,RAD2DEG:kn,generateUUID:Vn,clamp:Wn,euclideanModulo:Xn,mapLinear:jn,inverseLerp:qn,lerp:Yn,damp:Jn,pingpong:Zn,smoothstep:Kn,smootherstep:$n,randInt:Qn,randFloat:ti,randFloatSpread:ei,seededRandom:ni,degToRad:ii,radToDeg:ri,isPowerOfTwo:si,ceilPowerOfTwo:ai,floorPowerOfTwo:oi,setQuaternionFromProperEuler:li,normalize:hi,denormalize:ci};var di=Object.freeze({__proto__:null,DEG2RAD:Gn,MathUtils:ui,RAD2DEG:kn,ceilPowerOfTwo:ai,clamp:Wn,damp:Jn,degToRad:ii,denormalize:ci,euclideanModulo:Xn,floorPowerOfTwo:oi,generateUUID:Vn,inverseLerp:qn,isPowerOfTwo:si,lerp:Yn,mapLinear:jn,normalize:hi,pingpong:Zn,radToDeg:ri,randFloat:ti,randFloatSpread:ei,randInt:Qn,seededRandom:ni,setQuaternionFromProperEuler:li,smootherstep:$n,smoothstep:Kn});class pi{constructor(t=0,e=0){pi.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Wn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class mi{constructor(t,e,n,i,r,s,a,o,l){mi.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l)}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],_=i[4],x=i[7],y=i[2],M=i[5],S=i[8];return r[0]=s*m+a*v+o*y,r[3]=s*f+a*_+o*M,r[6]=s*g+a*x+o*S,r[1]=l*m+c*v+h*y,r[4]=l*f+c*_+h*M,r[7]=l*g+c*x+h*S,r[2]=u*m+d*v+p*y,r[5]=u*f+d*_+p*M,r[8]=u*g+d*x+p*S,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(fi.makeScale(t,e)),this}rotate(t){return this.premultiply(fi.makeRotation(-t)),this}translate(t,e){return this.premultiply(fi.makeTranslation(t,e)),this}makeTranslation(t,e){return t.isVector2?this.set(1,0,t.x,0,1,t.y,0,0,1):this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const fi=new mi;function gi(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const vi={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function _i(t,e){return new vi[t](e)}function xi(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function yi(){const t=xi("canvas");return t.style.display="block",t}const Mi={};function Si(t){t in Mi||(Mi[t]=!0,console.warn(t))}const bi=(new mi).set(.8224621,.177538,0,.0331941,.9668058,0,.0170827,.0723974,.9105199),Ei=(new mi).set(1.2249401,-.2249404,0,-.0420569,1.0420571,0,-.0196376,-.0786361,1.0982735),Ti={[je]:{transfer:Je,primaries:Ke,toReference:t=>t,fromReference:t=>t},[Xe]:{transfer:Ze,primaries:Ke,toReference:t=>t.convertSRGBToLinear(),fromReference:t=>t.convertLinearToSRGB()},[Ye]:{transfer:Je,primaries:$e,toReference:t=>t.applyMatrix3(Ei),fromReference:t=>t.applyMatrix3(bi)},[qe]:{transfer:Ze,primaries:$e,toReference:t=>t.convertSRGBToLinear().applyMatrix3(Ei),fromReference:t=>t.applyMatrix3(bi).convertLinearToSRGB()}},wi=new Set([je,Ye]),Ai={enabled:!0,_workingColorSpace:je,get workingColorSpace(){return this._workingColorSpace},set workingColorSpace(t){if(!wi.has(t))throw new Error(`Unsupported working color space, "${t}".`);this._workingColorSpace=t},convert:function(t,e,n){if(!1===this.enabled||e===n||!e||!n)return t;const i=Ti[e].toReference;return(0,Ti[n].fromReference)(i(t))},fromWorkingColorSpace:function(t,e){return this.convert(t,this._workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this._workingColorSpace)},getPrimaries:function(t){return Ti[t].primaries},getTransfer:function(t){return t===We?Je:Ti[t].transfer}};function Ri(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Ci(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}let Li;class Pi{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===Li&&(Li=xi("canvas")),Li.width=t.width,Li.height=t.height;const n=Li.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=Li}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=xi("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==ot)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case pt:t.x=t.x-Math.floor(t.x);break;case mt:t.x=t.x<0?0:1;break;case ft:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case pt:t.y=t.y-Math.floor(t.y);break;case mt:t.y=t.y<0?0:1;break;case ft:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}}Oi.DEFAULT_IMAGE=null,Oi.DEFAULT_MAPPING=ot,Oi.DEFAULT_ANISOTROPY=1;class Fi{constructor(t=0,e=0,n=0,i=1){Fi.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}class Wi{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,n)}set(t,e,n){if(void 0===e&&void 0===n){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,n);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Xe){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,Ai.toWorkingColorSpace(this,e),this}setRGB(t,e,n,i=Ai.workingColorSpace){return this.r=t,this.g=e,this.b=n,Ai.toWorkingColorSpace(this,i),this}setHSL(t,e,n,i=Ai.workingColorSpace){if(t=Xn(t,1),e=Wn(e,0,1),n=Wn(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Vi(r,i,t+1/3),this.g=Vi(r,i,t),this.b=Vi(r,i,t-1/3)}return Ai.toWorkingColorSpace(this,i),this}setStyle(t,e=Xe){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const s=i[1],a=i[2];switch(s){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const n=i[1],r=n.length;if(3===r)return this.setRGB(parseInt(n.charAt(0),16)/15,parseInt(n.charAt(1),16)/15,parseInt(n.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(n,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Xe){const n=Hi[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=Ri(t.r),this.g=Ri(t.g),this.b=Ri(t.b),this}copyLinearToSRGB(t){return this.r=Ci(t.r),this.g=Ci(t.g),this.b=Ci(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Xe){return Ai.fromWorkingColorSpace(Xi.copy(this),t),65536*Math.round(Wn(255*Xi.r,0,255))+256*Math.round(Wn(255*Xi.g,0,255))+Math.round(Wn(255*Xi.b,0,255))}getHexString(t=Xe){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=Ai.workingColorSpace){Ai.fromWorkingColorSpace(Xi.copy(this),e);const n=Xi.r,i=Xi.g,r=Xi.b,s=Math.max(n,i,r),a=Math.min(n,i,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case n:o=(i-r)/t+(i=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e=!0){const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!0===e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(Wn(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=2*Math.PI*Math.random(),e=2*Math.PI*Math.random(),n=Math.random(),i=Math.sqrt(1-n),r=Math.sqrt(n);return this.set(i*Math.sin(t),i*Math.cos(t),r*Math.sin(e),r*Math.cos(e))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class qi{constructor(t=0,e=0,n=0){qi.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(Ji.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(Ji.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=2*(s*i-a*n),c=2*(a*e-r*i),h=2*(r*n-s*e);return this.x=e+o*l+s*h-a*c,this.y=n+o*c+a*l-r*h,this.z=i+o*h+r*c-s*l,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return Yi.copy(this).projectOnVector(t),this.sub(Yi)}reflect(t){return this.sub(Yi.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Wn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=Math.random()*Math.PI*2,e=2*Math.random()-1,n=Math.sqrt(1-e*e);return this.x=n*Math.cos(t),this.y=e,this.z=n*Math.sin(t),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const Yi=new qi,Ji=new ji;class Zi{constructor(t=new qi(1/0,1/0,1/0),e=new qi(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,$i),$i.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(ar),or.subVectors(this.max,ar),tr.subVectors(t.a,ar),er.subVectors(t.b,ar),nr.subVectors(t.c,ar),ir.subVectors(er,tr),rr.subVectors(nr,er),sr.subVectors(tr,nr);let e=[0,-ir.z,ir.y,0,-rr.z,rr.y,0,-sr.z,sr.y,ir.z,0,-ir.x,rr.z,0,-rr.x,sr.z,0,-sr.x,-ir.y,ir.x,0,-rr.y,rr.x,0,-sr.y,sr.x,0];return!!hr(e,tr,er,nr,or)&&(e=[1,0,0,0,1,0,0,0,1],!!hr(e,tr,er,nr,or)&&(lr.crossVectors(ir,rr),e=[lr.x,lr.y,lr.z],hr(e,tr,er,nr,or)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,$i).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize($i).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Ki[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Ki[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Ki[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Ki[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Ki[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Ki[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Ki[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Ki[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Ki)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Ki=[new qi,new qi,new qi,new qi,new qi,new qi,new qi,new qi],$i=new qi,Qi=new Zi,tr=new qi,er=new qi,nr=new qi,ir=new qi,rr=new qi,sr=new qi,ar=new qi,or=new qi,lr=new qi,cr=new qi;function hr(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){cr.fromArray(t,s);const a=r.x*Math.abs(cr.x)+r.y*Math.abs(cr.y)+r.z*Math.abs(cr.z),o=e.dot(cr),l=n.dot(cr),c=i.dot(cr);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const ur=new Zi,dr=new qi,pr=new qi;class mr{constructor(t=new qi,e=-1){this.isSphere=!0,this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):ur.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;dr.subVectors(t,this.center);const e=dr.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(dr,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(pr.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(dr.copy(t.center).add(pr)),this.expandByPoint(dr.copy(t.center).sub(pr))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const fr=new qi,gr=new qi,vr=new mi;class _r{constructor(t=new qi(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=fr.subVectors(n,e).cross(gr.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(t).addScaledVector(this.normal,-this.distanceToPoint(t))}intersectLine(t,e){const n=t.delta(fr),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(t.start).addScaledVector(n,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||vr.getNormalMatrix(t),i=this.coplanarPoint(fr).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const xr=new mr,yr=new qi;class Mr{constructor(t=new _r,e=new _r,n=new _r,i=new _r,r=new _r,s=new _r){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t,e=2e3){const n=this.planes,i=t.elements,r=i[0],s=i[1],a=i[2],o=i[3],l=i[4],c=i[5],h=i[6],u=i[7],d=i[8],p=i[9],m=i[10],f=i[11],g=i[12],v=i[13],_=i[14],x=i[15];if(n[0].setComponents(o-r,u-l,f-d,x-g).normalize(),n[1].setComponents(o+r,u+l,f+d,x+g).normalize(),n[2].setComponents(o+s,u+c,f+p,x+v).normalize(),n[3].setComponents(o-s,u-c,f-p,x-v).normalize(),n[4].setComponents(o-a,u-h,f-m,x-_).normalize(),e===On)n[5].setComponents(o+a,u+h,f+m,x+_).normalize();else{if(e!==Fn)throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+e);n[5].setComponents(a,h,m,_).normalize()}return this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),xr.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),xr.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(xr)}intersectsSprite(t){return xr.center.set(0,0,0),xr.radius=.7071067811865476,xr.applyMatrix4(t.matrixWorld),this.intersectsSphere(xr)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,yr.y=i.normal.y>0?t.max.y:t.min.y,yr.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(yr)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}class Sr{constructor(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){Sr.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f)}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new Sr).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/br.setFromMatrixColumn(t,0).length(),r=1/br.setFromMatrixColumn(t,1).length(),s=1/br.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(Tr,t,wr)}lookAt(t,e,n){const i=this.elements;return Cr.subVectors(t,e),0===Cr.lengthSq()&&(Cr.z=1),Cr.normalize(),Ar.crossVectors(n,Cr),0===Ar.lengthSq()&&(1===Math.abs(n.z)?Cr.x+=1e-4:Cr.z+=1e-4,Cr.normalize(),Ar.crossVectors(n,Cr)),Ar.normalize(),Rr.crossVectors(Cr,Ar),i[0]=Ar.x,i[4]=Rr.x,i[8]=Cr.x,i[1]=Ar.y,i[5]=Rr.y,i[9]=Cr.y,i[2]=Ar.z,i[6]=Rr.z,i[10]=Cr.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],_=n[7],x=n[11],y=n[15],M=i[0],S=i[4],b=i[8],E=i[12],T=i[1],w=i[5],A=i[9],R=i[13],C=i[2],L=i[6],P=i[10],I=i[14],U=i[3],D=i[7],N=i[11],O=i[15];return r[0]=s*M+a*T+o*C+l*U,r[4]=s*S+a*w+o*L+l*D,r[8]=s*b+a*A+o*P+l*N,r[12]=s*E+a*R+o*I+l*O,r[1]=c*M+h*T+u*C+d*U,r[5]=c*S+h*w+u*L+d*D,r[9]=c*b+h*A+u*P+d*N,r[13]=c*E+h*R+u*I+d*O,r[2]=p*M+m*T+f*C+g*U,r[6]=p*S+m*w+f*L+g*D,r[10]=p*b+m*A+f*P+g*N,r[14]=p*E+m*R+f*I+g*O,r[3]=v*M+_*T+x*C+y*U,r[7]=v*S+_*w+x*L+y*D,r[11]=v*b+_*A+x*P+y*N,r[15]=v*E+_*R+x*I+y*O,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,_=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,x=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,y=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+n*_+i*x+r*y;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const S=1/M;return t[0]=v*S,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*S,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*S,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*S,t[4]=_*S,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*S,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*S,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*S,t[8]=x*S,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*S,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*S,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*S,t[12]=y*S,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*S,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*S,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*S,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return t.isVector3?this.set(1,0,0,t.x,0,1,0,t.y,0,0,1,t.z,0,0,0,1):this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,_=o*c,x=o*h,y=n.x,M=n.y,S=n.z;return i[0]=(1-(m+g))*y,i[1]=(d+x)*y,i[2]=(p-_)*y,i[3]=0,i[4]=(d-x)*M,i[5]=(1-(u+g))*M,i[6]=(f+v)*M,i[7]=0,i[8]=(p+_)*S,i[9]=(f-v)*S,i[10]=(1-(u+m))*S,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=br.set(i[0],i[1],i[2]).length();const s=br.set(i[4],i[5],i[6]).length(),a=br.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],Er.copy(this);const o=1/r,l=1/s,c=1/a;return Er.elements[0]*=o,Er.elements[1]*=o,Er.elements[2]*=o,Er.elements[4]*=l,Er.elements[5]*=l,Er.elements[6]*=l,Er.elements[8]*=c,Er.elements[9]*=c,Er.elements[10]*=c,e.setFromRotationMatrix(Er),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s,a=2e3){const o=this.elements,l=2*r/(e-t),c=2*r/(n-i),h=(e+t)/(e-t),u=(n+i)/(n-i);let d,p;if(a===On)d=-(s+r)/(s-r),p=-2*s*r/(s-r);else{if(a!==Fn)throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+a);d=-s/(s-r),p=-s*r/(s-r)}return o[0]=l,o[4]=0,o[8]=h,o[12]=0,o[1]=0,o[5]=c,o[9]=u,o[13]=0,o[2]=0,o[6]=0,o[10]=d,o[14]=p,o[3]=0,o[7]=0,o[11]=-1,o[15]=0,this}makeOrthographic(t,e,n,i,r,s,a=2e3){const o=this.elements,l=1/(e-t),c=1/(n-i),h=1/(s-r),u=(e+t)*l,d=(n+i)*c;let p,m;if(a===On)p=(s+r)*h,m=-2*h;else{if(a!==Fn)throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+a);p=r*h,m=-1*h}return o[0]=2*l,o[4]=0,o[8]=0,o[12]=-u,o[1]=0,o[5]=2*c,o[9]=0,o[13]=-d,o[2]=0,o[6]=0,o[10]=m,o[14]=-p,o[3]=0,o[7]=0,o[11]=0,o[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const br=new qi,Er=new Sr,Tr=new qi(0,0,0),wr=new qi(1,1,1),Ar=new qi,Rr=new qi,Cr=new qi;function Lr(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function Pr(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,n=0;for(;!(8388608&e);)e<<=1,n-=8388608;e&=-8388609,n+=947912704,s[t]=e|n}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:n,baseTable:i,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}function Dr(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=Wn(t,-65504,65504),Ir.floatView[0]=t;const e=Ir.uint32View[0],n=e>>23&511;return Ir.baseTable[n]+((8388607&e)>>Ir.shiftTable[n])}function Nr(t){const e=t>>10;return Ir.uint32View[0]=Ir.mantissaTable[Ir.offsetTable[e]+(1023&t)]+Ir.exponentTable[e],Ir.floatView[0]}const Or={toHalfFloat:Dr,fromHalfFloat:Nr};var Fr=Object.freeze({__proto__:null,DataUtils:Or,fromHalfFloat:Nr,toHalfFloat:Dr});const zr=new qi,Br=new pi;class Hr{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=En,this._updateRange={offset:0,count:-1},this.updateRanges=[],this.gpuType=It,this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}get updateRange(){return Si("THREE.BufferAttribute: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead."),this._updateRange}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this.gpuType=t.gpuType,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(i.type="BatchedMesh",i.perObjectFrustumCulled=this.perObjectFrustumCulled,i.sortObjects=this.sortObjects,i.drawRanges=this._drawRanges,i.reservedRanges=this._reservedRanges,i.visibility=this._visibility,i.active=this._active,i.bounds=this._bounds.map((t=>({boxInitialized:t.boxInitialized,boxMin:t.box.min.toArray(),boxMax:t.box.max.toArray(),sphereInitialized:t.sphereInitialized,sphereRadius:t.sphere.radius,sphereCenter:t.sphere.center.toArray()}))),i.maxGeometryCount=this._maxGeometryCount,i.maxVertexCount=this._maxVertexCount,i.maxIndexCount=this._maxIndexCount,i.geometryInitialized=this._geometryInitialized,i.geometryCount=this._geometryCount,i.matricesTexture=this._matricesTexture.toJSON(t),null!==this.boundingSphere&&(i.boundingSphere={center:i.boundingSphere.center.toArray(),radius:i.boundingSphere.radius}),null!==this.boundingBox&&(i.boundingBox={min:i.boundingBox.min.toArray(),max:i.boundingBox.max.toArray()})),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),h.length>0&&(n.nodes=h)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations.slice(),this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.6,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),void 0!==this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(n.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapRotation&&(n.envMapRotation=this.envMapRotation.toArray()),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),this.side!==u&&(n.side=this.side),!0===this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=!0),this.blendSrc!==C&&(n.blendSrc=this.blendSrc),this.blendDst!==L&&(n.blendDst=this.blendDst),this.blendEquation!==y&&(n.blendEquation=this.blendEquation),null!==this.blendSrcAlpha&&(n.blendSrcAlpha=this.blendSrcAlpha),null!==this.blendDstAlpha&&(n.blendDstAlpha=this.blendDstAlpha),null!==this.blendEquationAlpha&&(n.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(n.blendColor=this.blendColor.getHex()),0!==this.blendAlpha&&(n.blendAlpha=this.blendAlpha),3!==this.depthFunc&&(n.depthFunc=this.depthFunc),!1===this.depthTest&&(n.depthTest=this.depthTest),!1===this.depthWrite&&(n.depthWrite=this.depthWrite),!1===this.colorWrite&&(n.colorWrite=this.colorWrite),255!==this.stencilWriteMask&&(n.stencilWriteMask=this.stencilWriteMask),519!==this.stencilFunc&&(n.stencilFunc=this.stencilFunc),0!==this.stencilRef&&(n.stencilRef=this.stencilRef),255!==this.stencilFuncMask&&(n.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==tn&&(n.stencilFail=this.stencilFail),this.stencilZFail!==tn&&(n.stencilZFail=this.stencilZFail),this.stencilZPass!==tn&&(n.stencilZPass=this.stencilZPass),!0===this.stencilWrite&&(n.stencilWrite=this.stencilWrite),void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaHash&&(n.alphaHash=!0),!0===this.alphaToCoverage&&(n.alphaToCoverage=!0),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=!0),!0===this.forceSinglePass&&(n.forceSinglePass=!0),!0===this.wireframe&&(n.wireframe=!0),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=!0),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.blendColor.copy(t.blendColor),this.blendAlpha=t.blendAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaHash=t.alphaHash,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}function Rs(t){const e={};for(const n in t){e[n]={};for(const i in t[n]){const r=t[n][i];r&&(r.isColor||r.isMatrix3||r.isMatrix4||r.isVector2||r.isVector3||r.isVector4||r.isTexture||r.isQuaternion)?r.isRenderTargetTexture?(console.warn("UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms()."),e[n][i]=null):e[n][i]=r.clone():Array.isArray(r)?e[n][i]=r.slice():e[n][i]=r}}return e}function Cs(t){const e={};for(let n=0;n0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}const Us=new qi,Ds=new qi,Ns=new qi,Os=new qi,Fs=new qi,zs=new qi,Bs=new qi;class Hs{constructor(t=new qi,e=new qi(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Us)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Us.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Us.copy(this.origin).addScaledVector(this.direction,e),Us.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){Ds.copy(t).add(e).multiplyScalar(.5),Ns.copy(e).sub(t).normalize(),Os.copy(this.origin).sub(Ds);const r=.5*t.distanceTo(e),s=-this.direction.dot(Ns),a=Os.dot(this.direction),o=-Os.dot(Ns),l=Os.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.origin).addScaledVector(this.direction,h),i&&i.copy(Ds).addScaledVector(Ns,u),d}intersectSphere(t,e){Us.subVectors(t.center,this.origin);const n=Us.dot(this.direction),i=Us.dot(Us)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||isNaN(n))&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Us)}intersectTriangle(t,e,n,i,r){Fs.subVectors(e,t),zs.subVectors(n,t),Bs.crossVectors(Fs,zs);let s,a=this.direction.dot(Bs);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}Os.subVectors(this.origin,t);const o=s*this.direction.dot(zs.crossVectors(Os,zs));if(o<0)return null;const l=s*this.direction.dot(Fs.cross(Os));if(l<0)return null;if(o+l>a)return null;const c=-s*Os.dot(Bs);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}const Gs=new qi,ks=new qi,Vs=new qi,Ws=new qi,Xs=new qi,js=new qi,qs=new qi,Ys=new qi,Js=new qi,Zs=new qi;class Ks{constructor(t=new qi,e=new qi,n=new qi){this.a=t,this.b=e,this.c=n}static getNormal(t,e,n,i){i.subVectors(n,e),Gs.subVectors(t,e),i.cross(Gs);const r=i.lengthSq();return r>0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Gs.subVectors(i,e),ks.subVectors(n,e),Vs.subVectors(t,e);const s=Gs.dot(Gs),a=Gs.dot(ks),o=Gs.dot(Vs),l=ks.dot(ks),c=ks.dot(Vs),h=s*l-a*a;if(0===h)return r.set(0,0,0),null;const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return null!==this.getBarycoord(t,e,n,i,Ws)&&(Ws.x>=0&&Ws.y>=0&&Ws.x+Ws.y<=1)}static getInterpolation(t,e,n,i,r,s,a,o){return null===this.getBarycoord(t,e,n,i,Ws)?(o.x=0,o.y=0,"z"in o&&(o.z=0),"w"in o&&(o.w=0),null):(o.setScalar(0),o.addScaledVector(r,Ws.x),o.addScaledVector(s,Ws.y),o.addScaledVector(a,Ws.z),o)}static isFrontFacing(t,e,n,i){return Gs.subVectors(n,e),ks.subVectors(t,e),Gs.cross(ks).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Gs.subVectors(this.c,this.b),ks.subVectors(this.a,this.b),.5*Gs.cross(ks).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Ks.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Ks.getBarycoord(t,this.a,this.b,this.c,e)}getInterpolation(t,e,n,i,r){return Ks.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return Ks.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Ks.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;Xs.subVectors(i,n),js.subVectors(r,n),Ys.subVectors(t,n);const o=Xs.dot(Ys),l=js.dot(Ys);if(o<=0&&l<=0)return e.copy(n);Js.subVectors(t,i);const c=Xs.dot(Js),h=js.dot(Js);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(Xs,s);Zs.subVectors(t,r);const d=Xs.dot(Zs),p=js.dot(Zs);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(js,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return qs.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(qs,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(Xs,s).addScaledVector(js,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}class $s extends As{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new Wi(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new $r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const Qs=new Sr,ta=new Hs,ea=new mr,na=new qi,ia=new qi,ra=new qi,sa=new qi,aa=new qi,oa=new qi,la=new pi,ca=new pi,ha=new pi,ua=new qi,da=new qi,pa=new qi,ma=new qi,fa=new qi;class ga extends fs{constructor(t=new bs,e=new $s){super(),this.isMesh=!0,this.type="Mesh",this.geometry=t,this.material=e,this.updateMorphTargets()}copy(t,e){return super.copy(t,e),void 0!==t.morphTargetInfluences&&(this.morphTargetInfluences=t.morphTargetInfluences.slice()),void 0!==t.morphTargetDictionary&&(this.morphTargetDictionary=Object.assign({},t.morphTargetDictionary)),this.material=Array.isArray(t.material)?t.material.slice():t.material,this.geometry=t.geometry,this}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t(t.far-t.near)**2)return}Qs.copy(r).invert(),ta.copy(t.ray).applyMatrix4(Qs),null!==n.boundingBox&&!1===ta.intersectsBox(n.boundingBox)||this._computeIntersections(t,e,ta)}}_computeIntersections(t,e,n){let i;const r=this.geometry,s=this.material,a=r.index,o=r.attributes.position,l=r.attributes.uv,c=r.attributes.uv1,h=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(s))for(let r=0,o=u.length;rn.far?null:{distance:c,point:fa.clone(),object:t}}(t,e,n,i,ia,ra,sa,ma);if(h){r&&(la.fromBufferAttribute(r,o),ca.fromBufferAttribute(r,l),ha.fromBufferAttribute(r,c),h.uv=Ks.getInterpolation(ma,ia,ra,sa,la,ca,ha,new pi)),s&&(la.fromBufferAttribute(s,o),ca.fromBufferAttribute(s,l),ha.fromBufferAttribute(s,c),h.uv1=Ks.getInterpolation(ma,ia,ra,sa,la,ca,ha,new pi)),a&&(ua.fromBufferAttribute(a,o),da.fromBufferAttribute(a,l),pa.fromBufferAttribute(a,c),h.normal=Ks.getInterpolation(ma,ia,ra,sa,ua,da,pa,new qi),h.normal.dot(i.direction)>0&&h.normal.multiplyScalar(-1));const t={a:o,b:l,c:c,normal:new qi,materialIndex:0};Ks.getNormal(ia,ra,sa,t.normal),h.face=t}return h}const _a={alphahash_fragment:"#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif",alphahash_pars_fragment:"#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif",alphamap_fragment:"#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif",alphamap_pars_fragment:"#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",alphatest_fragment:"#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif",alphatest_pars_fragment:"#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif",aomap_fragment:"#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif",aomap_pars_fragment:"#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif",batching_pars_vertex:"#ifdef USE_BATCHING\n\tattribute float batchId;\n\tuniform highp sampler2D batchingTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",batching_vertex:"#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( batchId );\n#endif",begin_vertex:"vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif",beginnormal_vertex:"vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif",bsdfs:"float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated",iridescence_fragment:"#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",colorspace_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",colorspace_pars_fragment:"\nconst mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3(\n\tvec3( 0.8224621, 0.177538, 0.0 ),\n\tvec3( 0.0331941, 0.9668058, 0.0 ),\n\tvec3( 0.0170827, 0.0723974, 0.9105199 )\n);\nconst mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.2249401, - 0.2249404, 0.0 ),\n\tvec3( - 0.0420569, 1.0420571, 0.0 ),\n\tvec3( - 0.0196376, - 0.0786361, 1.0982735 )\n);\nvec4 LinearSRGBToLinearDisplayP3( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a );\n}\nvec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a );\n}\nvec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn sRGBTransferOETF( value );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphinstance_vertex:"#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[MORPHTARGETS_COUNT];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t#endif\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\t#ifndef USE_INSTANCING_MORPH\n\t\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\t#endif\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",opaque_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tfloat startCompression = 0.8 - 0.04;\n\tfloat desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min(color.r, min(color.g, color.b));\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max(color.r, max(color.g, color.b));\n\tif (peak < startCompression) return color;\n\tfloat d = 1. - startCompression;\n\tfloat newPeak = 1. - d * d / (peak + d - startCompression);\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);\n\treturn mix(color, vec3(1, 1, 1), g);\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},xa={common:{diffuse:{value:new Wi(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new mi},alphaMap:{value:null},alphaMapTransform:{value:new mi},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new mi}},envmap:{envMap:{value:null},envMapRotation:{value:new mi},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new mi}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new mi}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new mi},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new mi},normalScale:{value:new pi(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new mi},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new mi}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new mi}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new mi}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Wi(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Wi(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new mi},alphaTest:{value:0},uvTransform:{value:new mi}},sprite:{diffuse:{value:new Wi(16777215)},opacity:{value:1},center:{value:new pi(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new mi},alphaMap:{value:null},alphaMapTransform:{value:new mi},alphaTest:{value:0}}},ya={basic:{uniforms:Cs([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.fog]),vertexShader:_a.meshbasic_vert,fragmentShader:_a.meshbasic_frag},lambert:{uniforms:Cs([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,xa.lights,{emissive:{value:new Wi(0)}}]),vertexShader:_a.meshlambert_vert,fragmentShader:_a.meshlambert_frag},phong:{uniforms:Cs([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,xa.lights,{emissive:{value:new Wi(0)},specular:{value:new Wi(1118481)},shininess:{value:30}}]),vertexShader:_a.meshphong_vert,fragmentShader:_a.meshphong_frag},standard:{uniforms:Cs([xa.common,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.roughnessmap,xa.metalnessmap,xa.fog,xa.lights,{emissive:{value:new Wi(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:_a.meshphysical_vert,fragmentShader:_a.meshphysical_frag},toon:{uniforms:Cs([xa.common,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.gradientmap,xa.fog,xa.lights,{emissive:{value:new Wi(0)}}]),vertexShader:_a.meshtoon_vert,fragmentShader:_a.meshtoon_frag},matcap:{uniforms:Cs([xa.common,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,{matcap:{value:null}}]),vertexShader:_a.meshmatcap_vert,fragmentShader:_a.meshmatcap_frag},points:{uniforms:Cs([xa.points,xa.fog]),vertexShader:_a.points_vert,fragmentShader:_a.points_frag},dashed:{uniforms:Cs([xa.common,xa.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:_a.linedashed_vert,fragmentShader:_a.linedashed_frag},depth:{uniforms:Cs([xa.common,xa.displacementmap]),vertexShader:_a.depth_vert,fragmentShader:_a.depth_frag},normal:{uniforms:Cs([xa.common,xa.bumpmap,xa.normalmap,xa.displacementmap,{opacity:{value:1}}]),vertexShader:_a.meshnormal_vert,fragmentShader:_a.meshnormal_frag},sprite:{uniforms:Cs([xa.sprite,xa.fog]),vertexShader:_a.sprite_vert,fragmentShader:_a.sprite_frag},background:{uniforms:{uvTransform:{value:new mi},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:_a.background_vert,fragmentShader:_a.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new mi}},vertexShader:_a.backgroundCube_vert,fragmentShader:_a.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:_a.cube_vert,fragmentShader:_a.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:_a.equirect_vert,fragmentShader:_a.equirect_frag},distanceRGBA:{uniforms:Cs([xa.common,xa.displacementmap,{referencePosition:{value:new qi},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:_a.distanceRGBA_vert,fragmentShader:_a.distanceRGBA_frag},shadow:{uniforms:Cs([xa.lights,xa.fog,{color:{value:new Wi(0)},opacity:{value:1}}]),vertexShader:_a.shadow_vert,fragmentShader:_a.shadow_frag}};ya.physical={uniforms:Cs([ya.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new mi},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new mi},clearcoatNormalScale:{value:new pi(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new mi},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new mi},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new mi},sheen:{value:0},sheenColor:{value:new Wi(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new mi},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new mi},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new mi},transmissionSamplerSize:{value:new pi},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new mi},attenuationDistance:{value:0},attenuationColor:{value:new Wi(0)},specularColor:{value:new Wi(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new mi},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new mi},anisotropyVector:{value:new pi},anisotropyMap:{value:null},anisotropyMapTransform:{value:new mi}}]),vertexShader:_a.meshphysical_vert,fragmentShader:_a.meshphysical_frag};const Ma={r:0,b:0,g:0},Sa=new $r,ba=new Sr;function Ea(t,e,n,i,r,s,a){const o=new Wi(0);let l,c,h=!0===s?0:1,p=null,m=0,f=null;function g(e,n){e.getRGB(Ma,Ls(t)),i.buffers.color.setClear(Ma.r,Ma.g,Ma.b,n,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),h=e,g(o,h)},getClearAlpha:function(){return h},setClearAlpha:function(t){h=t,g(o,h)},render:function(s,v){let _=!1,x=!0===v.isScene?v.background:null;if(x&&x.isTexture){x=(v.backgroundBlurriness>0?n:e).get(x)}null===x?g(o,h):x&&x.isColor&&(g(x,1),_=!0);const y=t.xr.getEnvironmentBlendMode();"additive"===y?i.buffers.color.setClear(0,0,0,1,a):"alpha-blend"===y&&i.buffers.color.setClear(0,0,0,0,a),(t.autoClear||_)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),x&&(x.isCubeTexture||x.mapping===dt)?(void 0===c&&(c=new ga(new Es(1,1,1),new Is({name:"BackgroundCubeMaterial",uniforms:Rs(ya.backgroundCube.uniforms),vertexShader:ya.backgroundCube.vertexShader,fragmentShader:ya.backgroundCube.fragmentShader,side:d,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),c.geometry.deleteAttribute("uv"),c.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(c.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(c)),Sa.copy(v.backgroundRotation),Sa.x*=-1,Sa.y*=-1,Sa.z*=-1,x.isCubeTexture&&!1===x.isRenderTargetTexture&&(Sa.y*=-1,Sa.z*=-1),c.material.uniforms.envMap.value=x,c.material.uniforms.flipEnvMap.value=x.isCubeTexture&&!1===x.isRenderTargetTexture?-1:1,c.material.uniforms.backgroundBlurriness.value=v.backgroundBlurriness,c.material.uniforms.backgroundIntensity.value=v.backgroundIntensity,c.material.uniforms.backgroundRotation.value.setFromMatrix4(ba.makeRotationFromEuler(Sa)),c.material.toneMapped=Ai.getTransfer(x.colorSpace)!==Ze,p===x&&m===x.version&&f===t.toneMapping||(c.material.needsUpdate=!0,p=x,m=x.version,f=t.toneMapping),c.layers.enableAll(),s.unshift(c,c.geometry,c.material,0,0,null)):x&&x.isTexture&&(void 0===l&&(l=new ga(new Ts(2,2),new Is({name:"BackgroundMaterial",uniforms:Rs(ya.background.uniforms),vertexShader:ya.background.vertexShader,fragmentShader:ya.background.fragmentShader,side:u,depthTest:!1,depthWrite:!1,fog:!1})),l.geometry.deleteAttribute("normal"),Object.defineProperty(l.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(l)),l.material.uniforms.t2D.value=x,l.material.uniforms.backgroundIntensity.value=v.backgroundIntensity,l.material.toneMapped=Ai.getTransfer(x.colorSpace)!==Ze,!0===x.matrixAutoUpdate&&x.updateMatrix(),l.material.uniforms.uvTransform.value.copy(x.matrix),p===x&&m===x.version&&f===t.toneMapping||(l.material.needsUpdate=!0,p=x,m=x.version,f=t.toneMapping),l.layers.enableAll(),s.unshift(l,l.geometry,l.material,0,0,null))}}}function Ta(t,e,n,i){const r=t.getParameter(t.MAX_VERTEX_ATTRIBS),s=i.isWebGL2?null:e.get("OES_vertex_array_object"),a=i.isWebGL2||null!==s,o={},l=p(null);let c=l,h=!1;function u(e){return i.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function d(e){return i.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function p(t){const e=[],n=[],i=[];for(let t=0;t=0){const n=r[e];let i=s[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;a++}}return c.attributesNum!==a||c.index!==i}(r,x,d,y),M&&function(t,e,n,i){const r={},s=e.attributes;let a=0;const o=n.getAttributes();for(const e in o){if(o[e].location>=0){let n=s[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,a++}}c.attributes=r,c.attributesNum=a,c.index=i}(r,x,d,y)}else{const t=!0===l.wireframe;c.geometry===x.id&&c.program===d.id&&c.wireframe===t||(c.geometry=x.id,c.program=d.id,c.wireframe=t,M=!0)}null!==y&&n.update(y,t.ELEMENT_ARRAY_BUFFER),(M||h)&&(h=!1,function(r,s,a,o){if(!1===i.isWebGL2&&(r.isInstancedMesh||o.isInstancedBufferGeometry)&&null===e.get("ANGLE_instanced_arrays"))return;m();const l=o.attributes,c=a.getAttributes(),h=s.defaultAttributeValues;for(const e in c){const s=c[e];if(s.location>=0){let a=l[e];if(void 0===a&&("instanceMatrix"===e&&r.instanceMatrix&&(a=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(a=r.instanceColor)),void 0!==a){const e=a.normalized,l=a.itemSize,c=n.get(a);if(void 0===c)continue;const h=c.buffer,u=c.type,d=c.bytesPerElement,p=!0===i.isWebGL2&&(u===t.INT||u===t.UNSIGNED_INT||a.gpuType===Lt);if(a.isInterleavedBufferAttribute){const n=a.data,i=n.stride,c=a.offset;if(n.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(t.VERTEX_SHADER,t.MEDIUM_FLOAT).precision>0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&"WebGL2RenderingContext"===t.constructor.name;let a=void 0!==n.precision?n.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=s||e.has("WEBGL_draw_buffers"),c=!0===n.logarithmicDepthBuffer,h=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),u=t.getParameter(t.MAX_VERTEX_TEXTURE_IMAGE_UNITS),d=t.getParameter(t.MAX_TEXTURE_SIZE),p=t.getParameter(t.MAX_CUBE_MAP_TEXTURE_SIZE),m=t.getParameter(t.MAX_VERTEX_ATTRIBS),f=t.getParameter(t.MAX_VERTEX_UNIFORM_VECTORS),g=t.getParameter(t.MAX_VARYING_VECTORS),v=t.getParameter(t.MAX_FRAGMENT_UNIFORM_VECTORS),_=u>0,x=s||e.has("OES_texture_float");return{isWebGL2:s,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:d,maxCubemapSize:p,maxAttributes:m,maxVertexUniforms:f,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:_,floatFragmentTextures:x,floatVertexTextures:_&&x,maxSamples:s?t.getParameter(t.MAX_SAMPLES):0}}function Ra(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new _r,o=new mi,l={value:null,needsUpdate:!1};function c(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0);e.numPlanes=i,e.numIntersection=0}();else{const t=s?0:i,e=4*t;let r=m.clippingState||null;l.value=r,r=c(u,o,e,h);for(let t=0;t!==e;++t)r[t]=n[t];m.clippingState=r,this.numIntersection=d?this.numPlanes:0,this.numPlanes+=t}}}class Ca extends fs{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new Sr,this.projectionMatrix=new Sr,this.projectionMatrixInverse=new Sr,this.coordinateSystem=On}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this.coordinateSystem=t.coordinateSystem,this}getWorldDirection(t){return super.getWorldDirection(t).negate()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}const La=new qi,Pa=new pi,Ia=new pi;class Ua extends Ca{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*kn*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Gn*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*kn*Math.atan(Math.tan(.5*Gn*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(t,e,n){La.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),e.set(La.x,La.y).multiplyScalar(-t/La.z),La.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),n.set(La.x,La.y).multiplyScalar(-t/La.z)}getViewSize(t,e){return this.getViewBounds(t,Pa,Ia),e.subVectors(Ia,Pa)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Gn*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const Da=-90;class Na extends fs{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n,this.coordinateSystem=null,this.activeMipmapLevel=0;const i=new Ua(Da,1,t,e);i.layers=this.layers,this.add(i);const r=new Ua(Da,1,t,e);r.layers=this.layers,this.add(r);const s=new Ua(Da,1,t,e);s.layers=this.layers,this.add(s);const a=new Ua(Da,1,t,e);a.layers=this.layers,this.add(a);const o=new Ua(Da,1,t,e);o.layers=this.layers,this.add(o);const l=new Ua(Da,1,t,e);l.layers=this.layers,this.add(l)}updateCoordinateSystem(){const t=this.coordinateSystem,e=this.children.concat(),[n,i,r,s,a,o]=e;for(const t of e)this.remove(t);if(t===On)n.up.set(0,1,0),n.lookAt(1,0,0),i.up.set(0,1,0),i.lookAt(-1,0,0),r.up.set(0,0,-1),r.lookAt(0,1,0),s.up.set(0,0,1),s.lookAt(0,-1,0),a.up.set(0,1,0),a.lookAt(0,0,1),o.up.set(0,1,0),o.lookAt(0,0,-1);else{if(t!==Fn)throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+t);n.up.set(0,-1,0),n.lookAt(-1,0,0),i.up.set(0,-1,0),i.lookAt(1,0,0),r.up.set(0,0,1),r.lookAt(0,1,0),s.up.set(0,0,-1),s.lookAt(0,-1,0),a.up.set(0,-1,0),a.lookAt(0,0,1),o.up.set(0,-1,0),o.lookAt(0,0,-1)}for(const t of e)this.add(t),t.updateMatrixWorld()}update(t,e){null===this.parent&&this.updateMatrixWorld();const{renderTarget:n,activeMipmapLevel:i}=this;this.coordinateSystem!==t.coordinateSystem&&(this.coordinateSystem=t.coordinateSystem,this.updateCoordinateSystem());const[r,s,a,o,l,c]=this.children,h=t.getRenderTarget(),u=t.getActiveCubeFace(),d=t.getActiveMipmapLevel(),p=t.xr.enabled;t.xr.enabled=!1;const m=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0,i),t.render(e,r),t.setRenderTarget(n,1,i),t.render(e,s),t.setRenderTarget(n,2,i),t.render(e,a),t.setRenderTarget(n,3,i),t.render(e,o),t.setRenderTarget(n,4,i),t.render(e,l),n.texture.generateMipmaps=m,t.setRenderTarget(n,5,i),t.render(e,c),t.setRenderTarget(h,u,d),t.xr.enabled=p,n.texture.needsPMREMUpdate=!0}}class Oa extends Oi{constructor(t,e,n,i,r,s,a,o,l,c){super(t=void 0!==t?t:[],e=void 0!==e?e:lt,n,i,r,s,a,o,l,c),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class Fa extends Bi{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];this.texture=new Oa(i,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:Mt}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Es(5,5,5),r=new Is({name:"CubemapFromEquirect",uniforms:Rs(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:d,blending:0});r.uniforms.tEquirect.value=e;const s=new ga(i,r),a=e.minFilter;e.minFilter===Et&&(e.minFilter=Mt);return new Na(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}function za(t){let e=new WeakMap;function n(t,e){return e===ht?t.mapping=lt:e===ut&&(t.mapping=ct),t}function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping;if(s===ht||s===ut){if(e.has(r)){return n(e.get(r).texture,r.mapping)}{const s=r.image;if(s&&s.height>0){const a=new Fa(s.height);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",i),n(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class Ba extends Ca{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const Ha=[.125,.215,.35,.446,.526,.582],Ga=20,ka=new Ba,Va=new Wi;let Wa=null,Xa=0,ja=0;const qa=(1+Math.sqrt(5))/2,Ya=1/qa,Ja=[new qi(1,1,1),new qi(-1,1,1),new qi(1,1,-1),new qi(-1,1,-1),new qi(0,qa,Ya),new qi(0,qa,-Ya),new qi(Ya,0,qa),new qi(-Ya,0,qa),new qi(qa,Ya,0),new qi(-qa,Ya,0)];class Za{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){Wa=this._renderer.getRenderTarget(),Xa=this._renderer.getActiveCubeFace(),ja=this._renderer.getActiveMipmapLevel(),this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=to(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=Qa(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=Ha[a-t+4-1]:0===a&&(o=0),i.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,m=3,f=2,g=1,v=new Float32Array(m*p*d),_=new Float32Array(f*p*d),x=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,m*p*t),_.set(u,f*p*t);const r=[t,t,t,t,t,t];x.set(r,g*p*t)}const y=new bs;y.setAttribute("position",new Hr(v,m)),y.setAttribute("uv",new Hr(_,f)),y.setAttribute("faceIndex",new Hr(x,g)),e.push(y),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(Ga),r=new qi(0,1,0),s=new Is({name:"SphericalGaussianBlur",defines:{n:Ga,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:eo(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(i,t,e)}return i}_compileMaterial(t){const e=new ga(this._lodPlanes[0],t);this._renderer.compile(e,ka)}_sceneToCubeUV(t,e,n,i){const r=new Ua(90,1,e,n),s=[1,-1,1,1,1,1],a=[1,1,1,-1,-1,-1],o=this._renderer,l=o.autoClear,c=o.toneMapping;o.getClearColor(Va),o.toneMapping=K,o.autoClear=!1;const h=new $s({name:"PMREM.Background",side:d,depthWrite:!1,depthTest:!1}),u=new ga(new Es,h);let p=!1;const m=t.background;m?m.isColor&&(h.color.copy(m),t.background=null,p=!0):(h.color.copy(Va),p=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(r.up.set(0,s[e],0),r.lookAt(a[e],0,0)):1===n?(r.up.set(0,0,s[e]),r.lookAt(0,a[e],0)):(r.up.set(0,s[e],0),r.lookAt(0,0,a[e]));const l=this._cubeSize;$a(i,n*l,e>2?l:0,l,l),o.setRenderTarget(i),p&&o.render(u,r),o.render(t,r)}u.geometry.dispose(),u.material.dispose(),o.toneMapping=c,o.autoClear=l,t.background=m}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===lt||t.mapping===ct;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=to()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=Qa());const r=i?this._cubemapMaterial:this._equirectMaterial,s=new ga(this._lodPlanes[0],r);r.uniforms.envMap.value=t;const a=this._cubeSize;$a(e,0,0,3*a,2*a),n.setRenderTarget(e),n.render(s,ka)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;eGa&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),o.setRenderTarget(e),o.render(c,ka)}}function Ka(t,e,n){const i=new Bi(t,e,n);return i.texture.mapping=dt,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function $a(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function Qa(){return new Is({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:eo(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function to(){return new Is({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:eo(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function eo(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function no(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping,a=s===ht||s===ut,o=s===lt||s===ct;if(a||o){if(r.isRenderTargetTexture&&!0===r.needsPMREMUpdate){r.needsPMREMUpdate=!1;let i=e.get(r);return null===n&&(n=new Za(t)),i=a?n.fromEquirectangular(r,i):n.fromCubemap(r,i),e.set(r,i),i.texture}if(e.has(r))return e.get(r).texture;{const s=r.image;if(a&&s&&s.height>0||o&&s&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(b=Math.ceil(S/e.maxTextureSize),S=e.maxTextureSize);const E=new Float32Array(S*b*4*p),T=new oo(E,S,b,p);T.type=It,T.needsUpdate=!0;const w=4*M;for(let R=0;R0)return t;const r=e*n;let s=yo[r];if(void 0===s&&(s=new Float32Array(r),yo[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function wo(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function El(t,e){const n=function(t){const e=Ai.getPrimaries(Ai.workingColorSpace),n=Ai.getPrimaries(t);let i;switch(e===n?i="":e===$e&&n===Ke?i="LinearDisplayP3ToLinearSRGB":e===Ke&&n===$e&&(i="LinearSRGBToLinearDisplayP3"),t){case je:case Ye:return[i,"LinearTransferOETF"];case Xe:case qe:return[i,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space:",t),[i,"LinearTransferOETF"]}}(e);return`vec4 ${t}( vec4 value ) { return ${n[0]}( ${n[1]}( value ) ); }`}function Tl(t,e){let n;switch(e){case $:n="Linear";break;case Q:n="Reinhard";break;case tt:n="OptimizedCineon";break;case et:n="ACESFilmic";break;case it:n="AgX";break;case rt:n="Neutral";break;case nt:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}function wl(t){return""!==t}function Al(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function Rl(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const Cl=/^[ \t]*#include +<([\w\d./]+)>/gm;function Ll(t){return t.replace(Cl,Il)}const Pl=new Map([["encodings_fragment","colorspace_fragment"],["encodings_pars_fragment","colorspace_pars_fragment"],["output_fragment","opaque_fragment"]]);function Il(t,e){let n=_a[e];if(void 0===n){const t=Pl.get(e);if(void 0===t)throw new Error("Can not resolve #include <"+e+">");n=_a[t],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',e,t)}return Ll(n)}const Ul=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Dl(t){return t.replace(Ul,Nl)}function Nl(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(y+="\n"),M=[g,"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,_].filter(wl).join("\n"),M.length>0&&(M+="\n")):(y=[Ol(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,_,n.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",n.batching?"#define USE_BATCHING":"",n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.instancingMorph?"#define USE_INSTANCING_MORPH":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+p:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors&&n.isWebGL2?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.useLegacyLights?"#define LEGACY_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH","\tuniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(wl).join("\n"),M=[g,Ol(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,_,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+d:"",n.envMap?"#define "+p:"",n.envMap?"#define "+m:"",f?"#define CUBEUV_TEXEL_WIDTH "+f.texelWidth:"",f?"#define CUBEUV_TEXEL_HEIGHT "+f.texelHeight:"",f?"#define CUBEUV_MAX_MIP "+f.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.useLegacyLights?"#define LEGACY_LIGHTS":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",n.toneMapping!==K?"#define TONE_MAPPING":"",n.toneMapping!==K?_a.tonemapping_pars_fragment:"",n.toneMapping!==K?Tl("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",_a.colorspace_pars_fragment,El("linearToOutputTexel",n.outputColorSpace),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(wl).join("\n")),a=Ll(a),a=Al(a,n),a=Rl(a,n),o=Ll(o),o=Al(o,n),o=Rl(o,n),a=Dl(a),o=Dl(o),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(S="#version 300 es\n",y=[v,"precision mediump sampler2DArray;","#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+y,M=["precision mediump sampler2DArray;","#define varying in",n.glslVersion===Dn?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===Dn?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+M);const b=S+y+a,E=S+M+o,T=yl(r,r.VERTEX_SHADER,b),w=yl(r,r.FRAGMENT_SHADER,E);function A(e){if(t.debug.checkShaderErrors){const n=r.getProgramInfoLog(x).trim(),i=r.getShaderInfoLog(T).trim(),s=r.getShaderInfoLog(w).trim();let a=!0,o=!0;if(!1===r.getProgramParameter(x,r.LINK_STATUS))if(a=!1,"function"==typeof t.debug.onShaderError)t.debug.onShaderError(r,x,T,w);else{const t=bl(r,T,"vertex"),i=bl(r,w,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(x,r.VALIDATE_STATUS)+"\n\nMaterial Name: "+e.name+"\nMaterial Type: "+e.type+"\n\nProgram Info Log: "+n+"\n"+t+"\n"+i)}else""!==n?console.warn("THREE.WebGLProgram: Program Info Log:",n):""!==i&&""!==s||(o=!1);o&&(e.diagnostics={runnable:a,programLog:n,vertexShader:{log:i,prefix:y},fragmentShader:{log:s,prefix:M}})}r.deleteShader(T),r.deleteShader(w),R=new xl(r,x),C=function(t,e){const n={},i=t.getProgramParameter(e,t.ACTIVE_ATTRIBUTES);for(let r=0;r0,Y=s.clearcoat>0,J=s.iridescence>0,Z=s.sheen>0,$=s.transmission>0,Q=q&&!!s.anisotropyMap,tt=Y&&!!s.clearcoatMap,et=Y&&!!s.clearcoatNormalMap,nt=Y&&!!s.clearcoatRoughnessMap,it=J&&!!s.iridescenceMap,rt=J&&!!s.iridescenceThicknessMap,st=Z&&!!s.sheenColorMap,at=Z&&!!s.sheenRoughnessMap,ot=!!s.specularMap,lt=!!s.specularColorMap,ct=!!s.specularIntensityMap,ht=$&&!!s.transmissionMap,ut=$&&!!s.thicknessMap,pt=!!s.gradientMap,mt=!!s.alphaMap,ft=s.alphaTest>0,gt=!!s.alphaHash,vt=!!s.extensions;let _t=K;s.toneMapped&&(null!==U&&!0!==U.isXRRenderTarget||(_t=t.toneMapping));const xt={isWebGL2:u,shaderID:T,shaderType:s.type,shaderName:s.name,vertexShader:R,fragmentShader:C,defines:s.defines,customVertexShaderID:L,customFragmentShaderID:P,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:f,batching:N,instancing:D,instancingColor:D&&null!==x.instanceColor,instancingMorph:D&&null!==x.morphTexture,supportsVertexTextures:m,outputColorSpace:null===U?t.outputColorSpace:!0===U.isXRRenderTarget?U.texture.colorSpace:je,alphaToCoverage:!!s.alphaToCoverage,map:O,matcap:F,envMap:z,envMapMode:z&&b.mapping,envMapCubeUVHeight:E,aoMap:B,lightMap:H,bumpMap:G,normalMap:k,displacementMap:m&&V,emissiveMap:W,normalMapObjectSpace:k&&1===s.normalMapType,normalMapTangentSpace:k&&0===s.normalMapType,metalnessMap:X,roughnessMap:j,anisotropy:q,anisotropyMap:Q,clearcoat:Y,clearcoatMap:tt,clearcoatNormalMap:et,clearcoatRoughnessMap:nt,iridescence:J,iridescenceMap:it,iridescenceThicknessMap:rt,sheen:Z,sheenColorMap:st,sheenRoughnessMap:at,specularMap:ot,specularColorMap:lt,specularIntensityMap:ct,transmission:$,transmissionMap:ht,thicknessMap:ut,gradientMap:pt,opaque:!1===s.transparent&&1===s.blending&&!1===s.alphaToCoverage,alphaMap:mt,alphaTest:ft,alphaHash:gt,combine:s.combine,mapUv:O&&v(s.map.channel),aoMapUv:B&&v(s.aoMap.channel),lightMapUv:H&&v(s.lightMap.channel),bumpMapUv:G&&v(s.bumpMap.channel),normalMapUv:k&&v(s.normalMap.channel),displacementMapUv:V&&v(s.displacementMap.channel),emissiveMapUv:W&&v(s.emissiveMap.channel),metalnessMapUv:X&&v(s.metalnessMap.channel),roughnessMapUv:j&&v(s.roughnessMap.channel),anisotropyMapUv:Q&&v(s.anisotropyMap.channel),clearcoatMapUv:tt&&v(s.clearcoatMap.channel),clearcoatNormalMapUv:et&&v(s.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:nt&&v(s.clearcoatRoughnessMap.channel),iridescenceMapUv:it&&v(s.iridescenceMap.channel),iridescenceThicknessMapUv:rt&&v(s.iridescenceThicknessMap.channel),sheenColorMapUv:st&&v(s.sheenColorMap.channel),sheenRoughnessMapUv:at&&v(s.sheenRoughnessMap.channel),specularMapUv:ot&&v(s.specularMap.channel),specularColorMapUv:lt&&v(s.specularColorMap.channel),specularIntensityMapUv:ct&&v(s.specularIntensityMap.channel),transmissionMapUv:ht&&v(s.transmissionMap.channel),thicknessMapUv:ut&&v(s.thicknessMap.channel),alphaMapUv:mt&&v(s.alphaMap.channel),vertexTangents:!!M.attributes.tangent&&(k||q),vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!M.attributes.color&&4===M.attributes.color.itemSize,pointsUvs:!0===x.isPoints&&!!M.attributes.uv&&(O||mt),fog:!!y,useFog:!0===s.fog,fogExp2:!!y&&y.isFogExp2,flatShading:!0===s.flatShading,sizeAttenuation:!0===s.sizeAttenuation,logarithmicDepthBuffer:p,skinning:!0===x.isSkinnedMesh,morphTargets:void 0!==M.morphAttributes.position,morphNormals:void 0!==M.morphAttributes.normal,morphColors:void 0!==M.morphAttributes.color,morphTargetsCount:A,morphTextureStride:I,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numLightProbes:o.numLightProbes,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&h.length>0,shadowMapType:t.shadowMap.type,toneMapping:_t,useLegacyLights:t._useLegacyLights,decodeVideoTexture:O&&!0===s.map.isVideoTexture&&Ai.getTransfer(s.map.colorSpace)===Ze,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:s.side===d,useDepthPacking:s.depthPacking>=0,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionDerivatives:vt&&!0===s.extensions.derivatives,extensionFragDepth:vt&&!0===s.extensions.fragDepth,extensionDrawBuffers:vt&&!0===s.extensions.drawBuffers,extensionShaderTextureLOD:vt&&!0===s.extensions.shaderTextureLOD,extensionClipCullDistance:vt&&!0===s.extensions.clipCullDistance&&i.has("WEBGL_clip_cull_distance"),extensionMultiDraw:vt&&!0===s.extensions.multiDraw&&i.has("WEBGL_multi_draw"),rendererExtensionFragDepth:u||i.has("EXT_frag_depth"),rendererExtensionDrawBuffers:u||i.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:u||i.has("EXT_shader_texture_lod"),rendererExtensionParallelShaderCompile:i.has("KHR_parallel_shader_compile"),customProgramCacheKey:s.customProgramCacheKey()};return xt.vertexUv1s=c.has(1),xt.vertexUv2s=c.has(2),xt.vertexUv3s=c.has(3),c.clear(),xt},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputColorSpace),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.mapUv),t.push(e.alphaMapUv),t.push(e.lightMapUv),t.push(e.aoMapUv),t.push(e.bumpMapUv),t.push(e.normalMapUv),t.push(e.displacementMapUv),t.push(e.emissiveMapUv),t.push(e.metalnessMapUv),t.push(e.roughnessMapUv),t.push(e.anisotropyMapUv),t.push(e.clearcoatMapUv),t.push(e.clearcoatNormalMapUv),t.push(e.clearcoatRoughnessMapUv),t.push(e.iridescenceMapUv),t.push(e.iridescenceThicknessMapUv),t.push(e.sheenColorMapUv),t.push(e.sheenRoughnessMapUv),t.push(e.specularMapUv),t.push(e.specularColorMapUv),t.push(e.specularIntensityMapUv),t.push(e.transmissionMapUv),t.push(e.thicknessMapUv),t.push(e.combine),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.numLightProbes),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){o.disableAll(),e.isWebGL2&&o.enable(0);e.supportsVertexTextures&&o.enable(1);e.instancing&&o.enable(2);e.instancingColor&&o.enable(3);e.instancingMorph&&o.enable(4);e.matcap&&o.enable(5);e.envMap&&o.enable(6);e.normalMapObjectSpace&&o.enable(7);e.normalMapTangentSpace&&o.enable(8);e.clearcoat&&o.enable(9);e.iridescence&&o.enable(10);e.alphaTest&&o.enable(11);e.vertexColors&&o.enable(12);e.vertexAlphas&&o.enable(13);e.vertexUv1s&&o.enable(14);e.vertexUv2s&&o.enable(15);e.vertexUv3s&&o.enable(16);e.vertexTangents&&o.enable(17);e.anisotropy&&o.enable(18);e.alphaHash&&o.enable(19);e.batching&&o.enable(20);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.skinning&&o.enable(4);e.morphTargets&&o.enable(5);e.morphNormals&&o.enable(6);e.morphColors&&o.enable(7);e.premultipliedAlpha&&o.enable(8);e.shadowMapEnabled&&o.enable(9);e.useLegacyLights&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.transmission&&o.enable(15);e.sheen&&o.enable(16);e.opaque&&o.enable(17);e.pointsUvs&&o.enable(18);e.decodeVideoTexture&&o.enable(19);e.alphaToCoverage&&o.enable(20);t.push(o.mask)}(n,e),n.push(t.outputColorSpace)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=g[t.type];let n;if(e){const t=ya[e];n=Ps.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=h.length;t0?i.push(h):!0===a.transparent?r.push(h):n.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?i.unshift(h):!0===a.transparent?r.unshift(h):n.unshift(h)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||Vl),i.length>1&&i.sort(e||Wl),r.length>1&&r.sort(e||Wl)}}}function jl(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new Xl,t.set(e,[r])):n>=i.length?(r=new Xl,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function ql(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new qi,color:new Wi};break;case"SpotLight":n={position:new qi,direction:new qi,color:new Wi,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new qi,color:new Wi,distance:0,decay:0};break;case"HemisphereLight":n={direction:new qi,skyColor:new Wi,groundColor:new Wi};break;case"RectAreaLight":n={color:new Wi,position:new qi,halfWidth:new qi,halfHeight:new qi}}return t[e.id]=n,n}}}let Yl=0;function Jl(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function Zl(t,e){const n=new ql,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new pi};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new pi,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let t=0;t<9;t++)r.probe.push(new qi);const s=new qi,a=new Sr,o=new Sr;return{setup:function(s,a){let o=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0,_=0,x=0,y=0;s.sort(Jl);const M=!0===a?Math.PI:1;for(let t=0,e=s.length;t0&&(e.isWebGL2?!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=xa.LTC_FLOAT_1,r.rectAreaLTC2=xa.LTC_FLOAT_2):(r.rectAreaLTC1=xa.LTC_HALF_1,r.rectAreaLTC2=xa.LTC_HALF_2):!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=xa.LTC_FLOAT_1,r.rectAreaLTC2=xa.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=xa.LTC_HALF_1,r.rectAreaLTC2=xa.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=o,r.ambient[1]=l,r.ambient[2]=c;const S=r.hash;S.directionalLength===h&&S.pointLength===u&&S.spotLength===d&&S.rectAreaLength===p&&S.hemiLength===m&&S.numDirectionalShadows===f&&S.numPointShadows===g&&S.numSpotShadows===v&&S.numSpotMaps===_&&S.numLightProbes===y||(r.directional.length=h,r.spot.length=d,r.rectArea.length=p,r.point.length=u,r.hemi.length=m,r.directionalShadow.length=f,r.directionalShadowMap.length=f,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=f,r.pointShadowMatrix.length=g,r.spotLightMatrix.length=v+_-x,r.spotLightMap.length=_,r.numSpotLightShadowsWithMaps=x,r.numLightProbes=y,S.directionalLength=h,S.pointLength=u,S.spotLength=d,S.rectAreaLength=p,S.hemiLength=m,S.numDirectionalShadows=f,S.numPointShadows=g,S.numSpotShadows=v,S.numSpotMaps=_,S.numLightProbes=y,r.version=Yl++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=s.length?(a=new Kl(t,e),s.push(a)):a=s[r],a},dispose:function(){n=new WeakMap}}}class Ql extends As{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class tc extends As{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function ec(t,e,n){let i=new Mr;const r=new pi,s=new pi,a=new Fi,o=new Ql({depthPacking:3201}),c=new tc,m={},f=n.maxTextureSize,g={[u]:d,[d]:u,[p]:2},v=new Is({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new pi},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),_=v.clone();_.defines.HORIZONTAL_PASS=1;const x=new bs;x.setAttribute("position",new Hr(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const y=new ga(x,v),M=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=l;let S=this.type;function b(n,i){const s=e.update(y);v.defines.VSM_SAMPLES!==n.blurSamples&&(v.defines.VSM_SAMPLES=n.blurSamples,_.defines.VSM_SAMPLES=n.blurSamples,v.needsUpdate=!0,_.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new Bi(r.x,r.y)),v.uniforms.shadow_pass.value=n.map.texture,v.uniforms.resolution.value=n.mapSize,v.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,s,v,y,null),_.uniforms.shadow_pass.value=n.mapPass.texture,_.uniforms.resolution.value=n.mapSize,_.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,s,_,y,null)}function E(e,n,i,r){let s=null;const a=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==a)s=a;else if(s=!0===i.isPointLight?c:o,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0){const t=s.uuid,e=n.uuid;let i=m[t];void 0===i&&(i={},m[t]=i);let r=i[e];void 0===r&&(r=s.clone(),i[e]=r,n.addEventListener("dispose",w)),s=r}if(s.visible=n.visible,s.wireframe=n.wireframe,s.side=r===h?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:g[n.side],s.alphaMap=n.alphaMap,s.alphaTest=n.alphaTest,s.map=n.map,s.clipShadows=n.clipShadows,s.clippingPlanes=n.clippingPlanes,s.clipIntersection=n.clipIntersection,s.displacementMap=n.displacementMap,s.displacementScale=n.displacementScale,s.displacementBias=n.displacementBias,s.wireframeLinewidth=n.wireframeLinewidth,s.linewidth=n.linewidth,!0===i.isPointLight&&!0===s.isMeshDistanceMaterial){t.properties.get(s).light=i}return s}function T(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&o===h)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),l=n.material;if(Array.isArray(l)){const e=i.groups;for(let c=0,h=e.length;cf||r.y>f)&&(r.x>f&&(s.x=Math.floor(f/g.x),r.x=s.x*g.x,u.mapSize.x=s.x),r.y>f&&(s.y=Math.floor(f/g.y),r.y=s.y*g.y,u.mapSize.y=s.y)),null===u.map||!0===p||!0===m){const t=this.type!==h?{minFilter:gt,magFilter:gt}:{};null!==u.map&&u.map.dispose(),u.map=new Bi(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const v=u.getViewportCount();for(let t=0;t=1):-1!==$.indexOf("OpenGL ES")&&(K=parseFloat(/^OpenGL ES (\d)/.exec($)[1]),Z=K>=2);let Q=null,tt={};const et=t.getParameter(t.SCISSOR_BOX),nt=t.getParameter(t.VIEWPORT),it=(new Fi).fromArray(et),rt=(new Fi).fromArray(nt);function st(e,n,r,s){const a=new Uint8Array(4),o=t.createTexture();t.bindTexture(e,o),t.texParameteri(e,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(e,t.TEXTURE_MAG_FILTER,t.NEAREST);for(let o=0;oi||s.height>i)&&(r=i/Math.max(s.width,s.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap||"undefined"!=typeof VideoFrame&&t instanceof VideoFrame){const i=e?oi:Math.floor,a=i(r*s.width),o=i(r*s.height);void 0===d&&(d=f(a,o));const l=n?f(a,o):d;l.width=a,l.height=o;return l.getContext("2d").drawImage(t,0,0,a,o),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+s.width+"x"+s.height+") to ("+a+"x"+o+")."),l}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+s.width+"x"+s.height+")."),t}return t}function v(t){const e=H(t);return si(e.width)&&si(e.height)}function _(t,e){return t.generateMipmaps&&e&&t.minFilter!==gt&&t.minFilter!==Mt}function x(e){t.generateMipmap(e)}function y(n,i,r,s,a=!1){if(!1===o)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let l=i;if(i===t.RED&&(r===t.FLOAT&&(l=t.R32F),r===t.HALF_FLOAT&&(l=t.R16F),r===t.UNSIGNED_BYTE&&(l=t.R8)),i===t.RED_INTEGER&&(r===t.UNSIGNED_BYTE&&(l=t.R8UI),r===t.UNSIGNED_SHORT&&(l=t.R16UI),r===t.UNSIGNED_INT&&(l=t.R32UI),r===t.BYTE&&(l=t.R8I),r===t.SHORT&&(l=t.R16I),r===t.INT&&(l=t.R32I)),i===t.RG&&(r===t.FLOAT&&(l=t.RG32F),r===t.HALF_FLOAT&&(l=t.RG16F),r===t.UNSIGNED_BYTE&&(l=t.RG8)),i===t.RG_INTEGER&&(r===t.UNSIGNED_BYTE&&(l=t.RG8UI),r===t.UNSIGNED_SHORT&&(l=t.RG16UI),r===t.UNSIGNED_INT&&(l=t.RG32UI),r===t.BYTE&&(l=t.RG8I),r===t.SHORT&&(l=t.RG16I),r===t.INT&&(l=t.RG32I)),i===t.RGBA){const e=a?Je:Ai.getTransfer(s);r===t.FLOAT&&(l=t.RGBA32F),r===t.HALF_FLOAT&&(l=t.RGBA16F),r===t.UNSIGNED_BYTE&&(l=e===Ze?t.SRGB8_ALPHA8:t.RGBA8),r===t.UNSIGNED_SHORT_4_4_4_4&&(l=t.RGBA4),r===t.UNSIGNED_SHORT_5_5_5_1&&(l=t.RGB5_A1)}return l!==t.R16F&&l!==t.R32F&&l!==t.RG16F&&l!==t.RG32F&&l!==t.RGBA16F&&l!==t.RGBA32F||e.get("EXT_color_buffer_float"),l}function M(t,e,n){return!0===_(t,n)||t.isFramebufferTexture&&t.minFilter!==gt&&t.minFilter!==Mt?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function S(e){return e===gt||e===vt||e===xt?t.NEAREST:t.LINEAR}function b(t){const e=t.target;e.removeEventListener("dispose",b),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=p.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&T(t),0===Object.keys(r).length&&p.delete(n)}i.remove(t)}(e),e.isVideoTexture&&u.delete(e)}function E(e){const n=e.target;n.removeEventListener("dispose",E),function(e){const n=i.get(e);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++){if(Array.isArray(n.__webglFramebuffer[e]))for(let i=0;i0&&s.__version!==e.version){const t=e.image;if(null===t)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==t.complete)return void U(s,e,r);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.bindTexture(t.TEXTURE_2D,s.__webglTexture,t.TEXTURE0+r)}const R={[pt]:t.REPEAT,[mt]:t.CLAMP_TO_EDGE,[ft]:t.MIRRORED_REPEAT},C={[gt]:t.NEAREST,[vt]:t.NEAREST_MIPMAP_NEAREST,[xt]:t.NEAREST_MIPMAP_LINEAR,[Mt]:t.LINEAR,[St]:t.LINEAR_MIPMAP_NEAREST,[Et]:t.LINEAR_MIPMAP_LINEAR},L={[gn]:t.NEVER,[bn]:t.ALWAYS,[vn]:t.LESS,[xn]:t.LEQUAL,[_n]:t.EQUAL,[Sn]:t.GEQUAL,[yn]:t.GREATER,[Mn]:t.NOTEQUAL};function P(n,s,a){if(s.type!==It||!1!==e.has("OES_texture_float_linear")||s.magFilter!==Mt&&s.magFilter!==St&&s.magFilter!==xt&&s.magFilter!==Et&&s.minFilter!==Mt&&s.minFilter!==St&&s.minFilter!==xt&&s.minFilter!==Et||console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),a?(t.texParameteri(n,t.TEXTURE_WRAP_S,R[s.wrapS]),t.texParameteri(n,t.TEXTURE_WRAP_T,R[s.wrapT]),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,R[s.wrapR]),t.texParameteri(n,t.TEXTURE_MAG_FILTER,C[s.magFilter]),t.texParameteri(n,t.TEXTURE_MIN_FILTER,C[s.minFilter])):(t.texParameteri(n,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(n,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,t.CLAMP_TO_EDGE),s.wrapS===mt&&s.wrapT===mt||console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),t.texParameteri(n,t.TEXTURE_MAG_FILTER,S(s.magFilter)),t.texParameteri(n,t.TEXTURE_MIN_FILTER,S(s.minFilter)),s.minFilter!==gt&&s.minFilter!==Mt&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),s.compareFunction&&(t.texParameteri(n,t.TEXTURE_COMPARE_MODE,t.COMPARE_REF_TO_TEXTURE),t.texParameteri(n,t.TEXTURE_COMPARE_FUNC,L[s.compareFunction])),!0===e.has("EXT_texture_filter_anisotropic")){if(s.magFilter===gt)return;if(s.minFilter!==xt&&s.minFilter!==Et)return;if(s.type===It&&!1===e.has("OES_texture_float_linear"))return;if(!1===o&&s.type===Ut&&!1===e.has("OES_texture_half_float_linear"))return;if(s.anisotropy>1||i.get(s).__currentAnisotropy){const a=e.get("EXT_texture_filter_anisotropic");t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy}}}function I(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",b));const r=n.source;let s=p.get(r);void 0===s&&(s={},p.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.colorSpace),e.join()}(n);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,i=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&T(n)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return i}function U(e,a,l){let c=t.TEXTURE_2D;(a.isDataArrayTexture||a.isCompressedArrayTexture)&&(c=t.TEXTURE_2D_ARRAY),a.isData3DTexture&&(c=t.TEXTURE_3D);const h=I(e,a),u=a.source;n.bindTexture(c,e.__webglTexture,t.TEXTURE0+l);const d=i.get(u);if(u.version!==d.__version||!0===h){n.activeTexture(t.TEXTURE0+l);const e=Ai.getPrimaries(Ai.workingColorSpace),i=a.colorSpace===We?null:Ai.getPrimaries(a.colorSpace),p=a.colorSpace===We||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,p);const m=function(t){return!o&&(t.wrapS!==mt||t.wrapT!==mt||t.minFilter!==gt&&t.minFilter!==Mt)}(a)&&!1===v(a.image);let f=g(a.image,m,!1,r.maxTextureSize);f=B(a,f);const S=v(f)||o,b=s.convert(a.format,a.colorSpace);let E,T=s.convert(a.type),w=y(a.internalFormat,b,T,a.colorSpace,a.isVideoTexture);P(c,a,S);const A=a.mipmaps,R=o&&!0!==a.isVideoTexture&&w!==ne,C=void 0===d.__version||!0===h,L=u.dataReady,I=M(a,f,S);if(a.isDepthTexture)w=t.DEPTH_COMPONENT,o?w=a.type===It?t.DEPTH_COMPONENT32F:a.type===Pt?t.DEPTH_COMPONENT24:a.type===Ot?t.DEPTH24_STENCIL8:t.DEPTH_COMPONENT16:a.type===It&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),a.format===Gt&&w===t.DEPTH_COMPONENT&&a.type!==Ct&&a.type!==Pt&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),a.type=Pt,T=s.convert(a.type)),a.format===kt&&w===t.DEPTH_COMPONENT&&(w=t.DEPTH_STENCIL,a.type!==Ot&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),a.type=Ot,T=s.convert(a.type))),C&&(R?n.texStorage2D(t.TEXTURE_2D,1,w,f.width,f.height):n.texImage2D(t.TEXTURE_2D,0,w,f.width,f.height,0,b,T,null));else if(a.isDataTexture)if(A.length>0&&S){R&&C&&n.texStorage2D(t.TEXTURE_2D,I,w,A[0].width,A[0].height);for(let e=0,i=A.length;e>=1,i>>=1}}else if(A.length>0&&S){if(R&&C){const e=H(A[0]);n.texStorage2D(t.TEXTURE_2D,I,w,e.width,e.height)}for(let e=0,i=A.length;e>h),i=Math.max(1,r.height>>h);c===t.TEXTURE_3D||c===t.TEXTURE_2D_ARRAY?n.texImage3D(c,h,p,e,i,r.depth,0,u,d,null):n.texImage2D(c,h,p,e,i,0,u,d,null)}n.bindFramebuffer(t.FRAMEBUFFER,e),z(r)?l.framebufferTexture2DMultisampleEXT(t.FRAMEBUFFER,o,c,i.get(a).__webglTexture,0,F(r)):(c===t.TEXTURE_2D||c>=t.TEXTURE_CUBE_MAP_POSITIVE_X&&c<=t.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&t.framebufferTexture2D(t.FRAMEBUFFER,o,c,i.get(a).__webglTexture,h),n.bindFramebuffer(t.FRAMEBUFFER,null)}function N(e,n,i){if(t.bindRenderbuffer(t.RENDERBUFFER,e),n.depthBuffer&&!n.stencilBuffer){let r=!0===o?t.DEPTH_COMPONENT24:t.DEPTH_COMPONENT16;if(i||z(n)){const e=n.depthTexture;e&&e.isDepthTexture&&(e.type===It?r=t.DEPTH_COMPONENT32F:e.type===Pt&&(r=t.DEPTH_COMPONENT24));const i=F(n);z(n)?l.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,i,r,n.width,n.height):t.renderbufferStorageMultisample(t.RENDERBUFFER,i,r,n.width,n.height)}else t.renderbufferStorage(t.RENDERBUFFER,r,n.width,n.height);t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_ATTACHMENT,t.RENDERBUFFER,e)}else if(n.depthBuffer&&n.stencilBuffer){const r=F(n);i&&!1===z(n)?t.renderbufferStorageMultisample(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):z(n)?l.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):t.renderbufferStorage(t.RENDERBUFFER,t.DEPTH_STENCIL,n.width,n.height),t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_STENCIL_ATTACHMENT,t.RENDERBUFFER,e)}else{const e=n.textures;for(let r=0;r0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function B(t,n){const i=t.colorSpace,r=t.format,s=t.type;return!0===t.isCompressedTexture||!0===t.isVideoTexture||t.format===Nn||i!==je&&i!==We&&(Ai.getTransfer(i)===Ze?!1===o?!0===e.has("EXT_sRGB")&&r===zt?(t.format=Nn,t.minFilter=Mt,t.generateMipmaps=!1):n=Pi.sRGBToLinear(n):r===zt&&s===wt||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",i)),n}function H(t){return"undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement?(h.width=t.naturalWidth||t.width,h.height=t.naturalHeight||t.height):"undefined"!=typeof VideoFrame&&t instanceof VideoFrame?(h.width=t.displayWidth,h.height=t.displayHeight):(h.width=t.width,h.height=t.height),h}this.allocateTextureUnit=function(){const t=w;return t>=r.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+r.maxTextures),w+=1,t},this.resetTextureUnits=function(){w=0},this.setTexture2D=A,this.setTexture2DArray=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?U(s,e,r):n.bindTexture(t.TEXTURE_2D_ARRAY,s.__webglTexture,t.TEXTURE0+r)},this.setTexture3D=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?U(s,e,r):n.bindTexture(t.TEXTURE_3D,s.__webglTexture,t.TEXTURE0+r)},this.setTextureCube=function(e,a){const l=i.get(e);e.version>0&&l.__version!==e.version?function(e,a,l){if(6!==a.image.length)return;const c=I(e,a),h=a.source;n.bindTexture(t.TEXTURE_CUBE_MAP,e.__webglTexture,t.TEXTURE0+l);const u=i.get(h);if(h.version!==u.__version||!0===c){n.activeTexture(t.TEXTURE0+l);const e=Ai.getPrimaries(Ai.workingColorSpace),i=a.colorSpace===We?null:Ai.getPrimaries(a.colorSpace),d=a.colorSpace===We||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,d);const p=a.isCompressedTexture||a.image[0].isCompressedTexture,m=a.image[0]&&a.image[0].isDataTexture,f=[];for(let t=0;t<6;t++)f[t]=p||m?m?a.image[t].image:a.image[t]:g(a.image[t],!1,!0,r.maxCubemapSize),f[t]=B(a,f[t]);const S=f[0],b=v(S)||o,E=s.convert(a.format,a.colorSpace),T=s.convert(a.type),w=y(a.internalFormat,E,T,a.colorSpace),A=o&&!0!==a.isVideoTexture,R=void 0===u.__version||!0===c,C=h.dataReady;let L,I=M(a,S,b);if(P(t.TEXTURE_CUBE_MAP,a,b),p){A&&R&&n.texStorage2D(t.TEXTURE_CUBE_MAP,I,w,S.width,S.height);for(let e=0;e<6;e++){L=f[e].mipmaps;for(let i=0;i0&&I++;const e=H(f[0]);n.texStorage2D(t.TEXTURE_CUBE_MAP,I,w,e.width,e.height)}for(let e=0;e<6;e++)if(m){A?C&&n.texSubImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,0,0,f[e].width,f[e].height,E,T,f[e].data):n.texImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,w,f[e].width,f[e].height,0,E,T,f[e].data);for(let i=0;i1,m=v(e)||o;if(p||(void 0===h.__webglTexture&&(h.__webglTexture=t.createTexture()),h.__version=l.version,a.memory.textures++),d){c.__webglFramebuffer=[];for(let e=0;e<6;e++)if(o&&l.mipmaps&&l.mipmaps.length>0){c.__webglFramebuffer[e]=[];for(let n=0;n0){c.__webglFramebuffer=[];for(let e=0;e0&&!1===z(e)){c.__webglMultisampledFramebuffer=t.createFramebuffer(),c.__webglColorRenderbuffer=[],n.bindFramebuffer(t.FRAMEBUFFER,c.__webglMultisampledFramebuffer);for(let n=0;n0)for(let i=0;i0)for(let n=0;n0&&!1===z(e)){const r=e.textures,s=e.width,a=e.height;let o=t.COLOR_BUFFER_BIT;const l=[],h=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,u=i.get(e),d=r.length>1;if(d)for(let e=0;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(oc)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new ac;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class cc{constructor(){this.texture=null,this.mesh=null,this.depthNear=0,this.depthFar=0}init(t,e,n){if(null===this.texture){const i=new Oi;t.properties.get(i).__webglTexture=e.texture,e.depthNear==n.depthNear&&e.depthFar==n.depthFar||(this.depthNear=e.depthNear,this.depthFar=e.depthFar),this.texture=i}}render(t,e){if(null!==this.texture){if(null===this.mesh){const t=e.cameras[0].viewport,n=new Is({extensions:{fragDepth:!0},vertexShader:"\nvoid main() {\n\n\tgl_Position = vec4( position, 1.0 );\n\n}",fragmentShader:"\nuniform sampler2DArray depthColor;\nuniform float depthWidth;\nuniform float depthHeight;\n\nvoid main() {\n\n\tvec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight );\n\n\tif ( coord.x >= 1.0 ) {\n\n\t\tgl_FragDepthEXT = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r;\n\n\t} else {\n\n\t\tgl_FragDepthEXT = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r;\n\n\t}\n\n}",uniforms:{depthColor:{value:this.texture},depthWidth:{value:t.z},depthHeight:{value:t.w}}});this.mesh=new ga(new Ts(20,20),n)}t.render(this.mesh,e)}}reset(){this.texture=null,this.mesh=null}}class hc extends zn{constructor(t,e){super();const n=this;let i=null,r=1,s=null,a="local-floor",o=1,l=null,c=null,h=null,u=null,d=null,p=null;const m=new cc,f=e.getContextAttributes();let g=null,v=null;const _=[],x=[],y=new pi;let M=null;const S=new Ua;S.layers.enable(1),S.viewport=new Fi;const b=new Ua;b.layers.enable(2),b.viewport=new Fi;const E=[S,b],T=new sc;T.layers.enable(1),T.layers.enable(2);let w=null,A=null;function R(t){const e=x.indexOf(t.inputSource);if(-1===e)return;const n=_[e];void 0!==n&&(n.update(t.inputSource,t.frame,l||s),n.dispatchEvent({type:t.type,data:t.inputSource}))}function C(){i.removeEventListener("select",R),i.removeEventListener("selectstart",R),i.removeEventListener("selectend",R),i.removeEventListener("squeeze",R),i.removeEventListener("squeezestart",R),i.removeEventListener("squeezeend",R),i.removeEventListener("end",C),i.removeEventListener("inputsourceschange",L);for(let t=0;t<_.length;t++){const e=x[t];null!==e&&(x[t]=null,_[t].disconnect(e))}w=null,A=null,m.reset(),t.setRenderTarget(g),d=null,u=null,h=null,i=null,v=null,N.stop(),n.isPresenting=!1,t.setPixelRatio(M),t.setSize(y.width,y.height,!1),n.dispatchEvent({type:"sessionend"})}function L(t){for(let e=0;e=0&&(x[i]=null,_[i].disconnect(n))}for(let e=0;e=x.length){x.push(n),i=t;break}if(null===x[t]){x[t]=n,i=t;break}}if(-1===i)break}const r=_[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(t){let e=_[t];return void 0===e&&(e=new lc,_[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=_[t];return void 0===e&&(e=new lc,_[t]=e),e.getGripSpace()},this.getHand=function(t){let e=_[t];return void 0===e&&(e=new lc,_[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return l||s},this.setReferenceSpace=function(t){l=t},this.getBaseLayer=function(){return null!==u?u:d},this.getBinding=function(){return h},this.getFrame=function(){return p},this.getSession=function(){return i},this.setSession=async function(c){if(i=c,null!==i){if(g=t.getRenderTarget(),i.addEventListener("select",R),i.addEventListener("selectstart",R),i.addEventListener("selectend",R),i.addEventListener("squeeze",R),i.addEventListener("squeezestart",R),i.addEventListener("squeezeend",R),i.addEventListener("end",C),i.addEventListener("inputsourceschange",L),!0!==f.xrCompatible&&await e.makeXRCompatible(),M=t.getPixelRatio(),t.getSize(y),void 0===i.renderState.layers||!1===t.capabilities.isWebGL2){const n={antialias:void 0!==i.renderState.layers||f.antialias,alpha:!0,depth:f.depth,stencil:f.stencil,framebufferScaleFactor:r};d=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:d}),t.setPixelRatio(1),t.setSize(d.framebufferWidth,d.framebufferHeight,!1),v=new Bi(d.framebufferWidth,d.framebufferHeight,{format:zt,type:wt,colorSpace:t.outputColorSpace,stencilBuffer:f.stencil})}else{let n=null,s=null,a=null;f.depth&&(a=f.stencil?e.DEPTH24_STENCIL8:e.DEPTH_COMPONENT24,n=f.stencil?kt:Gt,s=f.stencil?Ot:Pt);const o={colorFormat:e.RGBA8,depthFormat:a,scaleFactor:r};h=new XRWebGLBinding(i,e),u=h.createProjectionLayer(o),i.updateRenderState({layers:[u]}),t.setPixelRatio(1),t.setSize(u.textureWidth,u.textureHeight,!1),v=new Bi(u.textureWidth,u.textureHeight,{format:zt,type:wt,depthTexture:new mo(u.textureWidth,u.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:f.stencil,colorSpace:t.outputColorSpace,samples:f.antialias?4:0});t.properties.get(v).__ignoreDepthValues=u.ignoreDepthValues}v.isXRRenderTarget=!0,this.setFoveation(o),l=null,s=await i.requestReferenceSpace(a),N.setContext(i),N.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==i)return i.environmentBlendMode};const P=new qi,I=new qi;function U(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCamera=function(t){if(null===i)return;null!==m.texture&&(t.near=m.depthNear,t.far=m.depthFar),T.near=b.near=S.near=t.near,T.far=b.far=S.far=t.far,w===T.near&&A===T.far||(i.updateRenderState({depthNear:T.near,depthFar:T.far}),w=T.near,A=T.far,S.near=w,S.far=A,b.near=w,b.far=A,S.updateProjectionMatrix(),b.updateProjectionMatrix(),t.updateProjectionMatrix());const e=t.parent,n=T.cameras;U(T,e);for(let t=0;t0&&(i.alphaTest.value=r.alphaTest);const s=e.get(r),a=s.envMap,o=s.envMapRotation;if(a&&(i.envMap.value=a,uc.copy(o),uc.x*=-1,uc.y*=-1,uc.z*=-1,a.isCubeTexture&&!1===a.isRenderTargetTexture&&(uc.y*=-1,uc.z*=-1),i.envMapRotation.value.setFromMatrix4(dc.makeRotationFromEuler(uc)),i.flipEnvMap.value=a.isCubeTexture&&!1===a.isRenderTargetTexture?-1:1,i.reflectivity.value=r.reflectivity,i.ior.value=r.ior,i.refractionRatio.value=r.refractionRatio),r.lightMap){i.lightMap.value=r.lightMap;const e=!0===t._useLegacyLights?Math.PI:1;i.lightMapIntensity.value=r.lightMapIntensity*e,n(r.lightMap,i.lightMapTransform)}r.aoMap&&(i.aoMap.value=r.aoMap,i.aoMapIntensity.value=r.aoMapIntensity,n(r.aoMap,i.aoMapTransform))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,Ls(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,r,s,a,o){r.isMeshBasicMaterial||r.isMeshLambertMaterial?i(t,r):r.isMeshToonMaterial?(i(t,r),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,r)):r.isMeshPhongMaterial?(i(t,r),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,r)):r.isMeshStandardMaterial?(i(t,r),function(t,i){t.metalness.value=i.metalness,i.metalnessMap&&(t.metalnessMap.value=i.metalnessMap,n(i.metalnessMap,t.metalnessMapTransform));t.roughness.value=i.roughness,i.roughnessMap&&(t.roughnessMap.value=i.roughnessMap,n(i.roughnessMap,t.roughnessMapTransform));const r=e.get(i).envMap;r&&(t.envMapIntensity.value=i.envMapIntensity)}(t,r),r.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap,n(e.sheenColorMap,t.sheenColorMapTransform)),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap,n(e.sheenRoughnessMap,t.sheenRoughnessMapTransform)));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap,n(e.clearcoatMap,t.clearcoatMapTransform)),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap,n(e.clearcoatRoughnessMap,t.clearcoatRoughnessMapTransform)),e.clearcoatNormalMap&&(t.clearcoatNormalMap.value=e.clearcoatNormalMap,n(e.clearcoatNormalMap,t.clearcoatNormalMapTransform),t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),e.side===d&&t.clearcoatNormalScale.value.negate()));e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap,n(e.iridescenceMap,t.iridescenceMapTransform)),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap,n(e.iridescenceThicknessMap,t.iridescenceThicknessMapTransform)));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap,n(e.transmissionMap,t.transmissionMapTransform)),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap,n(e.thicknessMap,t.thicknessMapTransform)),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));e.anisotropy>0&&(t.anisotropyVector.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation)),e.anisotropyMap&&(t.anisotropyMap.value=e.anisotropyMap,n(e.anisotropyMap,t.anisotropyMapTransform)));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap,n(e.specularColorMap,t.specularColorMapTransform));e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap,n(e.specularIntensityMap,t.specularIntensityMapTransform))}(t,r,o)):r.isMeshMatcapMaterial?(i(t,r),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,r)):r.isMeshDepthMaterial?i(t,r):r.isMeshDistanceMaterial?(i(t,r),function(t,n){const i=e.get(n).light;t.referencePosition.value.setFromMatrixPosition(i.matrixWorld),t.nearDistance.value=i.shadow.camera.near,t.farDistance.value=i.shadow.camera.far}(t,r)):r.isMeshNormalMaterial?i(t,r):r.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform))}(t,r),r.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,r)):r.isPointsMaterial?function(t,e,i,r){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*r,e.map&&(t.map.value=e.map,n(e.map,t.uvTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r,s,a):r.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r):r.isShadowMaterial?(t.color.value.copy(r.color),t.opacity.value=r.opacity):r.isShaderMaterial&&(r.uniformsNeedUpdate=!1)}}}function mc(t,e,n,i){let r={},s={},a=[];const o=n.isWebGL2?t.getParameter(t.MAX_UNIFORM_BUFFER_BINDINGS):0;function l(t,e,n,i){const r=t.value,s=e+"_"+n;if(void 0===i[s])return i[s]="number"==typeof r||"boolean"==typeof r?r:r.clone(),!0;{const t=i[s];if("number"==typeof r||"boolean"==typeof r){if(t!==r)return i[s]=r,!0}else if(!1===t.equals(r))return t.copy(r),!0}return!1}function c(t){const e={boundary:0,storage:0};return"number"==typeof t||"boolean"==typeof t?(e.boundary=4,e.storage=4):t.isVector2?(e.boundary=8,e.storage=8):t.isVector3||t.isColor?(e.boundary=16,e.storage=12):t.isVector4?(e.boundary=16,e.storage=16):t.isMatrix3?(e.boundary=48,e.storage=48):t.isMatrix4?(e.boundary=64,e.storage=64):t.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",t),e}function h(e){const n=e.target;n.removeEventListener("dispose",h);const i=a.indexOf(n.__bindingPointIndex);a.splice(i,1),t.deleteBuffer(r[n.id]),delete r[n.id],delete s[n.id]}return{bind:function(t,e){const n=e.program;i.uniformBlockBinding(t,n)},update:function(n,u){let d=r[n.id];void 0===d&&(!function(t){const e=t.uniforms;let n=0;const i=16;for(let t=0,r=e.length;t0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0),u=!!n.morphAttributes.position,d=!!n.morphAttributes.normal,p=!!n.morphAttributes.color;let m=K;i.toneMapped&&(null!==T&&!0!==T.isXRRenderTarget||(m=M.toneMapping));const f=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,g=void 0!==f?f.length:0,v=et.get(i),x=_.state.lights;if(!0===k&&(!0===V||t!==A)){const e=t===A&&i.id===w;dt.setState(i,t,e)}let y=!1;i.version===v.__version?v.needsLights&&v.lightsStateVersion!==x.state.version||v.outputColorSpace!==o||r.isBatchedMesh&&!1===v.batching?y=!0:r.isBatchedMesh||!0!==v.batching?r.isInstancedMesh&&!1===v.instancing?y=!0:r.isInstancedMesh||!0!==v.instancing?r.isSkinnedMesh&&!1===v.skinning?y=!0:r.isSkinnedMesh||!0!==v.skinning?r.isInstancedMesh&&!0===v.instancingColor&&null===r.instanceColor||r.isInstancedMesh&&!1===v.instancingColor&&null!==r.instanceColor||r.isInstancedMesh&&!0===v.instancingMorph&&null===r.morphTexture||r.isInstancedMesh&&!1===v.instancingMorph&&null!==r.morphTexture||v.envMap!==l||!0===i.fog&&v.fog!==s?y=!0:void 0===v.numClippingPlanes||v.numClippingPlanes===dt.numPlanes&&v.numIntersection===dt.numIntersection?(v.vertexAlphas!==c||v.vertexTangents!==h||v.morphTargets!==u||v.morphNormals!==d||v.morphColors!==p||v.toneMapping!==m||!0===$.isWebGL2&&v.morphTargetsCount!==g)&&(y=!0):y=!0:y=!0:y=!0:y=!0:(y=!0,v.__version=i.version);let S=v.currentProgram;!0===y&&(S=Kt(i,e,r));let b=!1,E=!1,R=!1;const C=S.getUniforms(),L=v.uniforms;Q.useProgram(S.program)&&(b=!0,E=!0,R=!0);i.id!==w&&(w=i.id,E=!0);if(b||A!==t){C.setValue(Mt,"projectionMatrix",t.projectionMatrix),C.setValue(Mt,"viewMatrix",t.matrixWorldInverse);const e=C.map.cameraPosition;void 0!==e&&e.setValue(Mt,q.setFromMatrixPosition(t.matrixWorld)),$.logarithmicDepthBuffer&&C.setValue(Mt,"logDepthBufFC",2/(Math.log(t.far+1)/Math.LN2)),(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial)&&C.setValue(Mt,"isOrthographic",!0===t.isOrthographicCamera),A!==t&&(A=t,E=!0,R=!0)}if(r.isSkinnedMesh){C.setOptional(Mt,r,"bindMatrix"),C.setOptional(Mt,r,"bindMatrixInverse");const t=r.skeleton;t&&($.floatVertexTextures?(null===t.boneTexture&&t.computeBoneTexture(),C.setValue(Mt,"boneTexture",t.boneTexture,nt)):console.warn("THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required."))}r.isBatchedMesh&&(C.setOptional(Mt,r,"batchingTexture"),C.setValue(Mt,"batchingTexture",r._matricesTexture,nt));const P=n.morphAttributes;(void 0!==P.position||void 0!==P.normal||void 0!==P.color&&!0===$.isWebGL2)&&ft.update(r,n,S);(E||v.receiveShadow!==r.receiveShadow)&&(v.receiveShadow=r.receiveShadow,C.setValue(Mt,"receiveShadow",r.receiveShadow));i.isMeshGouraudMaterial&&null!==i.envMap&&(L.envMap.value=l,L.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);E&&(C.setValue(Mt,"toneMappingExposure",M.toneMappingExposure),v.needsLights&&(U=R,(I=L).ambientLightColor.needsUpdate=U,I.lightProbe.needsUpdate=U,I.directionalLights.needsUpdate=U,I.directionalLightShadows.needsUpdate=U,I.pointLights.needsUpdate=U,I.pointLightShadows.needsUpdate=U,I.spotLights.needsUpdate=U,I.spotLightShadows.needsUpdate=U,I.rectAreaLights.needsUpdate=U,I.hemisphereLights.needsUpdate=U),s&&!0===i.fog&&ct.refreshFogUniforms(L,s),ct.refreshMaterialUniforms(L,i,N,D,W),xl.upload(Mt,$t(v),L,nt));var I,U;i.isShaderMaterial&&!0===i.uniformsNeedUpdate&&(xl.upload(Mt,$t(v),L,nt),i.uniformsNeedUpdate=!1);i.isSpriteMaterial&&C.setValue(Mt,"center",r.center);if(C.setValue(Mt,"modelViewMatrix",r.modelViewMatrix),C.setValue(Mt,"normalMatrix",r.normalMatrix),C.setValue(Mt,"modelMatrix",r.matrixWorld),i.isShaderMaterial||i.isRawShaderMaterial){const t=i.uniformsGroups;for(let e=0,n=t.length;e{function n(){i.forEach((function(t){et.get(t).currentProgram.isReady()&&i.delete(t)})),0!==i.size?setTimeout(n,10):e(t)}null!==Z.get("KHR_parallel_shader_compile")?n():setTimeout(n,10)}))};let Ht=null;function Gt(){Vt.stop()}function kt(){Vt.start()}const Vt=new Lr;function Xt(t,e,n,i){if(!1===t.visible)return;if(t.layers.test(e.layers))if(t.isGroup)n=t.renderOrder;else if(t.isLOD)!0===t.autoUpdate&&t.update(e);else if(t.isLight)_.pushLight(t),t.castShadow&&_.pushShadow(t);else if(t.isSprite){if(!t.frustumCulled||G.intersectsSprite(t)){i&&q.setFromMatrixPosition(t.matrixWorld).applyMatrix4(X);const e=ot.update(t),r=t.material;r.visible&&v.push(t,e,r,n,q.z,null)}}else if((t.isMesh||t.isLine||t.isPoints)&&(!t.frustumCulled||G.intersectsObject(t))){const e=ot.update(t),r=t.material;if(i&&(void 0!==t.boundingSphere?(null===t.boundingSphere&&t.computeBoundingSphere(),q.copy(t.boundingSphere.center)):(null===e.boundingSphere&&e.computeBoundingSphere(),q.copy(e.boundingSphere.center)),q.applyMatrix4(t.matrixWorld).applyMatrix4(X)),Array.isArray(r)){const i=e.groups;for(let s=0,a=i.length;s0&&function(t,e,n,i){const r=!0===n.isScene?n.overrideMaterial:null;if(null!==r)return;const s=$.isWebGL2;null===W&&(W=new Bi(1,1,{generateMipmaps:!0,type:Z.has("EXT_color_buffer_half_float")?Ut:wt,minFilter:Et,samples:s?4:0}));M.getDrawingBufferSize(j),s?W.setSize(j.x,j.y):W.setSize(oi(j.x),oi(j.y));const a=M.getRenderTarget();M.setRenderTarget(W),M.getClearColor(P),I=M.getClearAlpha(),I<1&&M.setClearColor(16777215,.5);M.clear();const o=M.toneMapping;M.toneMapping=K,Jt(t,n,i),nt.updateMultisampleRenderTarget(W),nt.updateRenderTargetMipmap(W);let l=!1;for(let t=0,r=e.length;t0&&Jt(r,e,n),s.length>0&&Jt(s,e,n),a.length>0&&Jt(a,e,n),Q.buffers.depth.setTest(!0),Q.buffers.depth.setMask(!0),Q.buffers.color.setMask(!0),Q.setPolygonOffset(!1)}function Jt(t,e,n){const i=!0===e.isScene?e.overrideMaterial:null;for(let r=0,s=t.length;r0?y[y.length-1]:null,x.pop(),v=x.length>0?x[x.length-1]:null},this.getActiveCubeFace=function(){return b},this.getActiveMipmapLevel=function(){return E},this.getRenderTarget=function(){return T},this.setRenderTargetTextures=function(t,e,n){et.get(t.texture).__webglTexture=e,et.get(t.depthTexture).__webglTexture=n;const i=et.get(t);i.__hasExternalTextures=!0,i.__autoAllocateDepthBuffer=void 0===n,i.__autoAllocateDepthBuffer||!0===Z.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),i.__useRenderToTexture=!1)},this.setRenderTargetFramebuffer=function(t,e){const n=et.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,n=0){T=t,b=e,E=n;let i=!0,r=null,s=!1,a=!1;if(t){const o=et.get(t);void 0!==o.__useDefaultFramebuffer?(Q.bindFramebuffer(Mt.FRAMEBUFFER,null),i=!1):void 0===o.__webglFramebuffer?nt.setupRenderTarget(t):o.__hasExternalTextures&&nt.rebindTextures(t,et.get(t.texture).__webglTexture,et.get(t.depthTexture).__webglTexture);const l=t.texture;(l.isData3DTexture||l.isDataArrayTexture||l.isCompressedArrayTexture)&&(a=!0);const c=et.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=Array.isArray(c[e])?c[e][n]:c[e],s=!0):r=$.isWebGL2&&t.samples>0&&!1===nt.useMultisampledRTT(t)?et.get(t).__webglMultisampledFramebuffer:Array.isArray(c)?c[n]:c,R.copy(t.viewport),C.copy(t.scissor),L=t.scissorTest}else R.copy(z).multiplyScalar(N).floor(),C.copy(B).multiplyScalar(N).floor(),L=H;if(Q.bindFramebuffer(Mt.FRAMEBUFFER,r)&&$.drawBuffers&&i&&Q.drawBuffers(t,r),Q.viewport(R),Q.scissor(C),Q.setScissorTest(L),s){const i=et.get(t.texture);Mt.framebufferTexture2D(Mt.FRAMEBUFFER,Mt.COLOR_ATTACHMENT0,Mt.TEXTURE_CUBE_MAP_POSITIVE_X+e,i.__webglTexture,n)}else if(a){const i=et.get(t.texture),r=e||0;Mt.framebufferTextureLayer(Mt.FRAMEBUFFER,Mt.COLOR_ATTACHMENT0,i.__webglTexture,n||0,r)}w=-1},this.readRenderTargetPixels=function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=et.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){Q.bindFramebuffer(Mt.FRAMEBUFFER,o);try{const a=t.texture,o=a.format,l=a.type;if(o!==zt&&_t.convert(o)!==Mt.getParameter(Mt.IMPLEMENTATION_COLOR_READ_FORMAT))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===Ut&&(Z.has("EXT_color_buffer_half_float")||$.isWebGL2&&Z.has("EXT_color_buffer_float"));if(!(l===wt||_t.convert(l)===Mt.getParameter(Mt.IMPLEMENTATION_COLOR_READ_TYPE)||l===It&&($.isWebGL2||Z.has("OES_texture_float")||Z.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&Mt.readPixels(e,n,i,r,_t.convert(o),_t.convert(l),s)}finally{const t=null!==T?et.get(T).__webglFramebuffer:null;Q.bindFramebuffer(Mt.FRAMEBUFFER,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),s=Math.floor(e.image.height*i);nt.setTexture2D(e,0),Mt.copyTexSubImage2D(Mt.TEXTURE_2D,n,0,0,t.x,t.y,r,s),Q.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,s=e.image.height,a=_t.convert(n.format),o=_t.convert(n.type);nt.setTexture2D(n,0),Mt.pixelStorei(Mt.UNPACK_FLIP_Y_WEBGL,n.flipY),Mt.pixelStorei(Mt.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),Mt.pixelStorei(Mt.UNPACK_ALIGNMENT,n.unpackAlignment),e.isDataTexture?Mt.texSubImage2D(Mt.TEXTURE_2D,i,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?Mt.compressedTexSubImage2D(Mt.TEXTURE_2D,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):Mt.texSubImage2D(Mt.TEXTURE_2D,i,t.x,t.y,a,o,e.image),0===i&&n.generateMipmaps&&Mt.generateMipmap(Mt.TEXTURE_2D),Q.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(M.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const s=Math.round(t.max.x-t.min.x),a=Math.round(t.max.y-t.min.y),o=t.max.z-t.min.z+1,l=_t.convert(i.format),c=_t.convert(i.type);let h;if(i.isData3DTexture)nt.setTexture3D(i,0),h=Mt.TEXTURE_3D;else{if(!i.isDataArrayTexture&&!i.isCompressedArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");nt.setTexture2DArray(i,0),h=Mt.TEXTURE_2D_ARRAY}Mt.pixelStorei(Mt.UNPACK_FLIP_Y_WEBGL,i.flipY),Mt.pixelStorei(Mt.UNPACK_PREMULTIPLY_ALPHA_WEBGL,i.premultiplyAlpha),Mt.pixelStorei(Mt.UNPACK_ALIGNMENT,i.unpackAlignment);const u=Mt.getParameter(Mt.UNPACK_ROW_LENGTH),d=Mt.getParameter(Mt.UNPACK_IMAGE_HEIGHT),p=Mt.getParameter(Mt.UNPACK_SKIP_PIXELS),m=Mt.getParameter(Mt.UNPACK_SKIP_ROWS),f=Mt.getParameter(Mt.UNPACK_SKIP_IMAGES),g=n.isCompressedTexture?n.mipmaps[r]:n.image;Mt.pixelStorei(Mt.UNPACK_ROW_LENGTH,g.width),Mt.pixelStorei(Mt.UNPACK_IMAGE_HEIGHT,g.height),Mt.pixelStorei(Mt.UNPACK_SKIP_PIXELS,t.min.x),Mt.pixelStorei(Mt.UNPACK_SKIP_ROWS,t.min.y),Mt.pixelStorei(Mt.UNPACK_SKIP_IMAGES,t.min.z),n.isDataTexture||n.isData3DTexture?Mt.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g.data):i.isCompressedArrayTexture?Mt.compressedTexSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,g.data):Mt.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g),Mt.pixelStorei(Mt.UNPACK_ROW_LENGTH,u),Mt.pixelStorei(Mt.UNPACK_IMAGE_HEIGHT,d),Mt.pixelStorei(Mt.UNPACK_SKIP_PIXELS,p),Mt.pixelStorei(Mt.UNPACK_SKIP_ROWS,m),Mt.pixelStorei(Mt.UNPACK_SKIP_IMAGES,f),0===r&&i.generateMipmaps&&Mt.generateMipmap(h),Q.unbindTexture()},this.initTexture=function(t){t.isCubeTexture?nt.setTextureCube(t,0):t.isData3DTexture?nt.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?nt.setTexture2DArray(t,0):nt.setTexture2D(t,0),Q.unbindTexture()},this.resetState=function(){b=0,E=0,T=null,Q.reset(),xt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get coordinateSystem(){return On}get outputColorSpace(){return this._outputColorSpace}set outputColorSpace(t){this._outputColorSpace=t;const e=this.getContext();e.drawingBufferColorSpace=t===qe?"display-p3":"srgb",e.unpackColorSpace=Ai.workingColorSpace===Ye?"display-p3":"srgb"}get useLegacyLights(){return console.warn("THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733."),this._useLegacyLights}set useLegacyLights(t){console.warn("THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733."),this._useLegacyLights=t}}class gc{constructor(t,e=1,n=1e3){this.isFog=!0,this.name="",this.color=new Wi(t),this.near=e,this.far=n}clone(){return new gc(this.color,this.near,this.far)}toJSON(){return{type:"Fog",name:this.name,color:this.color.getHex(),near:this.near,far:this.far}}}class vc extends fs{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.backgroundRotation=new $r,this.environmentRotation=new $r,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,this.backgroundRotation.copy(t.backgroundRotation),this.environmentRotation.copy(t.environmentRotation),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e.object.backgroundRotation=this.backgroundRotation.toArray(),e.object.environmentRotation=this.environmentRotation.toArray(),e}}class _c extends Hr{constructor(t,e,n,i=1){super(t,e,n),this.isInstancedBufferAttribute=!0,this.meshPerAttribute=i}copy(t){return super.copy(t),this.meshPerAttribute=t.meshPerAttribute,this}toJSON(){const t=super.toJSON();return t.meshPerAttribute=this.meshPerAttribute,t.isInstancedBufferAttribute=!0,t}}class xc extends Oi{constructor(t=null,e=1,n=1,i,r,s,a,o,l=1003,c=1003,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isDataTexture=!0,this.image={data:t,width:e,height:n},this.generateMipmaps=!1,this.flipY=!1,this.unpackAlignment=1}}const yc=new Sr,Mc=new Sr,Sc=[],bc=new Zi,Ec=new Sr,Tc=new ga,wc=new mr;class Ac extends ga{constructor(t,e,n){super(t,e),this.isInstancedMesh=!0,this.instanceMatrix=new _c(new Float32Array(16*n),16),this.instanceColor=null,this.morphTexture=null,this.count=n,this.boundingBox=null,this.boundingSphere=null;for(let t=0;to)continue;u.applyMatrix4(this.matrixWorld);const s=t.ray.origin.distanceTo(u);st.far||e.push({distance:s,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,s.start),i=Math.min(m.count,s.start+s.count)-1;no)continue;u.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(u);it.far||e.push({distance:i,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,object:a})}}class jc extends Oi{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}}class qc{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)n=this.getPoint(s/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let s;s=e||t*n[r-1];let a,o=0,l=r-1;for(;o<=l;)if(i=Math.floor(o+(l-o)/2),a=n[i]-s,a<0)o=i+1;else{if(!(a>0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new pi:new qi);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new qi,i=[],r=[],s=[],a=new qi,o=new Sr;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new qi)}r[0]=new qi,s[0]=new qi;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(Wn(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(Wn(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.6,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class Yc extends qc{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e=new pi){const n=e,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(Kc.subVectors(i[0],i[1]).add(i[0]),a=Kc);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(nh(a,o.x,l.x,c.x,h.x),nh(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=n){const t=i[r]-n,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class fh extends bs{constructor(t=[new pi(0,-.5),new pi(.5,0),new pi(0,.5)],e=12,n=0,i=2*Math.PI){super(),this.type="LatheGeometry",this.parameters={points:t,segments:e,phiStart:n,phiLength:i},e=Math.floor(e),i=Wn(i,0,2*Math.PI);const r=[],s=[],a=[],o=[],l=[],c=1/e,h=new qi,u=new pi,d=new qi,p=new qi,m=new qi;let f=0,g=0;for(let e=0;e<=t.length-1;e++)switch(e){case 0:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,m.copy(d),d.normalize(),o.push(d.x,d.y,d.z);break;case t.length-1:o.push(m.x,m.y,m.z);break;default:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,p.copy(d),d.x+=m.x,d.y+=m.y,d.z+=m.z,d.normalize(),o.push(d.x,d.y,d.z),m.copy(p)}for(let r=0;r<=e;r++){const d=n+r*c*i,p=Math.sin(d),m=Math.cos(d);for(let n=0;n<=t.length-1;n++){h.x=t[n].x*p,h.y=t[n].y,h.z=t[n].x*m,s.push(h.x,h.y,h.z),u.x=r/e,u.y=n/(t.length-1),a.push(u.x,u.y);const i=o[3*n+0]*p,c=o[3*n+1],d=o[3*n+0]*m;l.push(i,c,d)}}for(let n=0;n0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new Jr(h,3)),this.setAttribute("normal",new Jr(u,3)),this.setAttribute("uv",new Jr(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new _h(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class xh extends _h{constructor(t=1,e=1,n=32,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new xh(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class yh extends bs{constructor(t=[],e=[],n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new Jr(r,3)),this.setAttribute("normal",new Jr(r.slice(),3)),this.setAttribute("uv",new Jr(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new yh(t.vertices,t.indices,t.radius,t.details)}}class Mh extends yh{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-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,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new Mh(t.radius,t.detail)}}const Sh=new qi,bh=new qi,Eh=new qi,Th=new Ks;class wh extends bs{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const n=4,i=Math.pow(10,n),r=Math.cos(Gn*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?32767/p:0}return Ph(s,a,n,o,l,p,0),a};function Ch(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=Zh(s,t[s],t[s+1],a);return a&&Wh(a,a.next)&&(Kh(a),a=a.next),a}function Lh(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!Wh(i,i.next)&&0!==Vh(i.prev,i,i.next))i=i.next;else{if(Kh(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Ph(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{0===r.z&&(r.z=Bh(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?Uh(t,i,r,s):Ih(t))e.push(o.i/n|0),e.push(t.i/n|0),e.push(l.i/n|0),Kh(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Ph(t=Dh(Lh(t),e,n),e,n,i,r,s,2):2===a&&Nh(t,e,n,i,r,s):Ph(Lh(t),e,n,i,r,s,1);break}}function Ih(t){const e=t.prev,n=t,i=t.next;if(Vh(e,n,i)>=0)return!1;const r=e.x,s=n.x,a=i.x,o=e.y,l=n.y,c=i.y,h=rs?r>a?r:a:s>a?s:a,p=o>l?o>c?o:c:l>c?l:c;let m=i.next;for(;m!==e;){if(m.x>=h&&m.x<=d&&m.y>=u&&m.y<=p&&Gh(r,o,s,l,a,c,m.x,m.y)&&Vh(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Uh(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Vh(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=ol?o>c?o:c:l>c?l:c,g=h>u?h>d?h:d:u>d?u:d,v=Bh(p,m,e,n,i),_=Bh(f,g,e,n,i);let x=t.prevZ,y=t.nextZ;for(;x&&x.z>=v&&y&&y.z<=_;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Gh(o,h,l,u,c,d,x.x,x.y)&&Vh(x.prev,x,x.next)>=0)return!1;if(x=x.prevZ,y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Gh(o,h,l,u,c,d,y.x,y.y)&&Vh(y.prev,y,y.next)>=0)return!1;y=y.nextZ}for(;x&&x.z>=v;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Gh(o,h,l,u,c,d,x.x,x.y)&&Vh(x.prev,x,x.next)>=0)return!1;x=x.prevZ}for(;y&&y.z<=_;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Gh(o,h,l,u,c,d,y.x,y.y)&&Vh(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Dh(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!Wh(r,s)&&Xh(r,i,i.next,s)&&Yh(r,s)&&Yh(s,r)&&(e.push(r.i/n|0),e.push(i.i/n|0),e.push(s.i/n|0),Kh(i),Kh(i.next),i=t=s),i=i.next}while(i!==t);return Lh(i)}function Nh(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&kh(a,t)){let o=Jh(a,t);return a=Lh(a,a.next),o=Lh(o,o.next),Ph(a,e,n,i,r,s,0),void Ph(o,e,n,i,r,s,0)}t=t.next}a=a.next}while(a!==t)}function Oh(t,e){return t.x-e.x}function Fh(t,e){const n=function(t,e){let n,i=e,r=-1/0;const s=t.x,a=t.y;do{if(a<=i.y&&a>=i.next.y&&i.next.y!==i.y){const t=i.x+(a-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=s&&t>r&&(r=t,n=i.x=i.x&&i.x>=l&&s!==i.x&&Gh(an.x||i.x===n.x&&zh(n,i)))&&(n=i,u=h)),i=i.next}while(i!==o);return n}(t,e);if(!n)return e;const i=Jh(n,t);return Lh(i,i.next),Lh(n,n.next)}function zh(t,e){return Vh(t.prev,t,e.prev)<0&&Vh(e.next,t,t.next)<0}function Bh(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function Hh(t){let e=t,n=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(i-o)>=(n-a)*(e-o)&&(n-a)*(s-o)>=(r-a)*(i-o)}function kh(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&Xh(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(Yh(t,e)&&Yh(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Vh(t.prev,t,e.prev)||Vh(t,e.prev,e))||Wh(t,e)&&Vh(t.prev,t,t.next)>0&&Vh(e.prev,e,e.next)>0)}function Vh(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function Wh(t,e){return t.x===e.x&&t.y===e.y}function Xh(t,e,n,i){const r=qh(Vh(t,e,n)),s=qh(Vh(t,e,i)),a=qh(Vh(n,i,t)),o=qh(Vh(n,i,e));return r!==s&&a!==o||(!(0!==r||!jh(t,n,e))||(!(0!==s||!jh(t,i,e))||(!(0!==a||!jh(n,t,i))||!(0!==o||!jh(n,e,i)))))}function jh(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function qh(t){return t>0?1:t<0?-1:0}function Yh(t,e){return Vh(t.prev,t,t.next)<0?Vh(t,e,t.next)>=0&&Vh(t,t.prev,e)>=0:Vh(t,e,t.prev)<0||Vh(t,t.next,e)<0}function Jh(t,e){const n=new $h(t.i,t.x,t.y),i=new $h(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function Zh(t,e,n,i){const r=new $h(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function Kh(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function $h(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}class Qh{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function eu(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new pi(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new pi(i/s,r/s)}const P=[];for(let t=0,e=w.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=w.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Mu extends As{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Wi(16777215),this.specular=new Wi(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Wi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new pi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new $r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Su extends As{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Wi(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Wi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new pi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class bu extends As{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new pi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Eu extends As{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Wi(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Wi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new pi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new $r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Tu extends As{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Wi(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new pi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class wu extends Rc{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}const Au={enabled:!1,files:{},add:function(t,e){!1!==this.enabled&&(this.files[t]=e)},get:function(t){if(!1!==this.enabled)return this.files[t]},remove:function(t){delete this.files[t]},clear:function(){this.files={}}};class Ru{constructor(t,e,n){const i=this;let r,s=!1,a=0,o=0;const l=[];this.onStart=void 0,this.onLoad=t,this.onProgress=e,this.onError=n,this.itemStart=function(t){o++,!1===s&&void 0!==i.onStart&&i.onStart(t,a,o),s=!0},this.itemEnd=function(t){a++,void 0!==i.onProgress&&i.onProgress(t,a,o),a===o&&(s=!1,void 0!==i.onLoad&&i.onLoad())},this.itemError=function(t){void 0!==i.onError&&i.onError(t)},this.resolveURL=function(t){return r?r(t):t},this.setURLModifier=function(t){return r=t,this},this.addHandler=function(t,e){return l.push(t,e),this},this.removeHandler=function(t){const e=l.indexOf(t);return-1!==e&&l.splice(e,2),this},this.getHandler=function(t){for(let e=0,n=l.length;e{e&&e(r),this.manager.itemEnd(t)}),0),r;if(void 0!==Pu[t])return void Pu[t].push({onLoad:e,onProgress:n,onError:i});Pu[t]=[],Pu[t].push({onLoad:e,onProgress:n,onError:i});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(s).then((e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const n=Pu[t],i=e.body.getReader(),r=e.headers.get("Content-Length")||e.headers.get("X-File-Size"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){i.read().then((({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=n.length;t{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then((t=>(new DOMParser).parseFromString(t,a)));case"json":return t.json();default:if(void 0===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),n=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(n);return t.arrayBuffer().then((t=>i.decode(t)))}}})).then((e=>{Au.add(t,e);const n=Pu[t];delete Pu[t];for(let t=0,i=n.length;t{const n=Pu[t];if(void 0===n)throw this.manager.itemError(t),e;delete Pu[t];for(let t=0,i=n.length;t{this.manager.itemEnd(t)})),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class Du extends Oi{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class Nu extends Lu{constructor(t){super(t)}load(t,e,n,i){const r=this,s=[],a=new Du,o=new Uu(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],(function(t){const n=r.parse(t,!0);s[c]={width:n.width,height:n.height,format:n.format,mipmaps:n.mipmaps},l+=1,6===l&&(1===n.mipmapCount&&(a.minFilter=Mt),a.image=s,a.format=n.format,a.needsUpdate=!0,e&&e(a))}),n,i)}if(Array.isArray(t))for(let e=0,n=t.length;e=n.length&&n.push({start:-1,count:-1,z:-1});const r=n[this.index];i.push(r),this.index++,r.start=t.start,r.count=t.count,r.z=e}reset(){this.list.length=0,this.index=0}}const Vu="batchId",Wu=new Sr,Xu=new Sr,ju=new Sr,qu=new Sr,Yu=new Mr,Ju=new Zi,Zu=new mr,Ku=new qi,$u=new ku,Qu=new ga,td=[];function ed(t,e,n=0){const i=e.itemSize;if(t.isInterleavedBufferAttribute||t.array.constructor!==e.array.constructor){const r=t.count;for(let s=0;s65536?new Uint32Array(r):new Uint16Array(r);e.setIndex(new Hr(t,1))}const s=i>65536?new Uint32Array(n):new Uint16Array(n);e.setAttribute(Vu,new Hr(s,1)),this._geometryInitialized=!0}}_validateGeometry(t){if(t.getAttribute(Vu))throw new Error(`BatchedMesh: Geometry cannot use attribute "${Vu}"`);const e=this.geometry;if(Boolean(t.getIndex())!==Boolean(e.getIndex()))throw new Error('BatchedMesh: All geometries must consistently have "index".');for(const n in e.attributes){if(n===Vu)continue;if(!t.hasAttribute(n))throw new Error(`BatchedMesh: Added geometry missing "${n}". All geometries must have consistent attributes.`);const i=t.getAttribute(n),r=e.getAttribute(n);if(i.itemSize!==r.itemSize||i.normalized!==r.normalized)throw new Error("BatchedMesh: All attributes must have a consistent itemSize and normalized value.")}}setCustomSort(t){return this.customSort=t,this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Zi);const t=this._geometryCount,e=this.boundingBox,n=this._active;e.makeEmpty();for(let i=0;i=this._maxGeometryCount)throw new Error("BatchedMesh: Maximum geometry count reached.");const i={vertexStart:-1,vertexCount:-1,indexStart:-1,indexCount:-1};let r=null;const s=this._reservedRanges,a=this._drawRanges,o=this._bounds;0!==this._geometryCount&&(r=s[s.length-1]),i.vertexCount=-1===e?t.getAttribute("position").count:e,i.vertexStart=null===r?0:r.vertexStart+r.vertexCount;const l=t.getIndex(),c=null!==l;if(c&&(i.indexCount=-1===n?l.count:n,i.indexStart=null===r?0:r.indexStart+r.indexCount),-1!==i.indexStart&&i.indexStart+i.indexCount>this._maxIndexCount||i.vertexStart+i.vertexCount>this._maxVertexCount)throw new Error("BatchedMesh: Reserved space request exceeds the maximum buffer size.");const h=this._visibility,u=this._active,d=this._matricesTexture,p=this._matricesTexture.image.data;h.push(!0),u.push(!0);const m=this._geometryCount;this._geometryCount++,ju.toArray(p,16*m),d.needsUpdate=!0,s.push(i),a.push({start:c?i.indexStart:i.vertexStart,count:-1}),o.push({boxInitialized:!1,box:new Zi,sphereInitialized:!1,sphere:new mr});const f=this.geometry.getAttribute(Vu);for(let t=0;t=this._geometryCount)throw new Error("BatchedMesh: Maximum geometry count reached.");this._validateGeometry(e);const n=this.geometry,i=null!==n.getIndex(),r=n.getIndex(),s=e.getIndex(),a=this._reservedRanges[t];if(i&&s.count>a.indexCount||e.attributes.position.count>a.vertexCount)throw new Error("BatchedMesh: Reserved space not large enough for provided geometry.");const o=a.vertexStart,l=a.vertexCount;for(const t in n.attributes){if(t===Vu)continue;const i=e.getAttribute(t),r=n.getAttribute(t);ed(i,r,o);const s=i.itemSize;for(let t=i.count,e=l;t=e.length||!1===e[t]||(e[t]=!1,this._visibilityChanged=!0),this}getBoundingBoxAt(t,e){if(!1===this._active[t])return null;const n=this._bounds[t],i=n.box,r=this.geometry;if(!1===n.boxInitialized){i.makeEmpty();const e=r.index,s=r.attributes.position,a=this._drawRanges[t];for(let t=a.start,n=a.start+a.count;t=this._geometryCount||!1===n[t]||(e.toArray(r,16*t),i.needsUpdate=!0),this}getMatrixAt(t,e){const n=this._active,i=this._matricesTexture.image.data;return t>=this._geometryCount||!1===n[t]?null:e.fromArray(i,16*t)}setVisibleAt(t,e){const n=this._visibility,i=this._active;return t>=this._geometryCount||!1===i[t]||n[t]===e||(n[t]=e,this._visibilityChanged=!0),this}getVisibleAt(t){const e=this._visibility,n=this._active;return!(t>=this._geometryCount||!1===n[t])&&e[t]}raycast(t,e){const n=this._visibility,i=this._active,r=this._drawRanges,s=this._geometryCount,a=this.matrixWorld,o=this.geometry;Qu.material=this.material,Qu.geometry.index=o.index,Qu.geometry.attributes=o.attributes,null===Qu.geometry.boundingBox&&(Qu.geometry.boundingBox=new Zi),null===Qu.geometry.boundingSphere&&(Qu.geometry.boundingSphere=new mr);for(let o=0;o({...t}))),this._reservedRanges=t._reservedRanges.map((t=>({...t}))),this._visibility=t._visibility.slice(),this._active=t._active.slice(),this._bounds=t._bounds.map((t=>({boxInitialized:t.boxInitialized,box:t.box.clone(),sphereInitialized:t.sphereInitialized,sphere:t.sphere.clone()}))),this._maxGeometryCount=t._maxGeometryCount,this._maxVertexCount=t._maxVertexCount,this._maxIndexCount=t._maxIndexCount,this._geometryInitialized=t._geometryInitialized,this._geometryCount=t._geometryCount,this._multiDrawCounts=t._multiDrawCounts.slice(),this._multiDrawStarts=t._multiDrawStarts.slice(),this._matricesTexture=t._matricesTexture.clone(),this._matricesTexture.image.data=this._matricesTexture.image.slice(),this}dispose(){return this.geometry.dispose(),this._matricesTexture.dispose(),this._matricesTexture=null,this}onBeforeRender(t,e,n,i,r){if(!this._visibilityChanged&&!this.perObjectFrustumCulled&&!this.sortObjects)return;const s=i.getIndex(),a=null===s?1:s.array.BYTES_PER_ELEMENT,o=this._active,l=this._visibility,c=this._multiDrawStarts,h=this._multiDrawCounts,u=this._drawRanges,d=this.perObjectFrustumCulled;d&&(qu.multiplyMatrices(n.projectionMatrix,n.matrixWorldInverse).multiply(this.matrixWorld),Yu.setFromProjectionMatrix(qu,t.coordinateSystem));let p=0;if(this.sortObjects){Xu.copy(this.matrixWorld).invert(),Ku.setFromMatrixPosition(n.matrixWorld).applyMatrix4(Xu);for(let t=0,e=l.length;tt.far||e.push({distance:o,point:od.clone(),uv:Ks.getInterpolation(od,pd,md,fd,gd,vd,_d,new pi),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function yd(t,e,n,i,r,s){hd.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(ud.x=s*hd.x-r*hd.y,ud.y=r*hd.x+s*hd.y):ud.copy(hd),t.copy(e),t.x+=ud.x,t.y+=ud.y,t.applyMatrix4(dd)}const Md=new qi,Sd=new qi;class bd extends fs{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){Md.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(Md);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Md.setFromMatrixPosition(t.matrixWorld),Sd.setFromMatrixPosition(this.matrixWorld);const n=Md.distanceTo(Sd)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;i=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=n.slice(r,s),this.values=this.values.slice(r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&(a=i,ArrayBuffer.isView(a)&&!(a instanceof DataView)))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}var a;return t}optimize(){const t=this.times.slice(),e=this.values.slice(),n=this.getValueSize(),i=this.getInterpolation()===Pe,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=t.slice(0,s),this.values=e.slice(0,s*n)):(this.times=t,this.values=e),this}clone(){const t=this.times.slice(),e=this.values.slice(),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}dp.prototype.TimeBufferType=Float32Array,dp.prototype.ValueBufferType=Float32Array,dp.prototype.DefaultInterpolation=Le;class pp extends dp{}pp.prototype.ValueTypeName="bool",pp.prototype.ValueBufferType=Array,pp.prototype.DefaultInterpolation=Ce,pp.prototype.InterpolantFactoryMethodLinear=void 0,pp.prototype.InterpolantFactoryMethodSmooth=void 0;class mp extends dp{}mp.prototype.ValueTypeName="color";class fp extends dp{}fp.prototype.ValueTypeName="number";class gp extends lp{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)ji.slerpFlat(r,0,s,l-a,s,l,o);return r}}class vp extends dp{InterpolantFactoryMethodLinear(t){return new gp(this.times,this.values,this.getValueSize(),t)}}vp.prototype.ValueTypeName="quaternion",vp.prototype.DefaultInterpolation=Le,vp.prototype.InterpolantFactoryMethodSmooth=void 0;class _p extends dp{}_p.prototype.ValueTypeName="string",_p.prototype.ValueBufferType=Array,_p.prototype.DefaultInterpolation=Ce,_p.prototype.InterpolantFactoryMethodLinear=void 0,_p.prototype.InterpolantFactoryMethodSmooth=void 0;class xp extends dp{}xp.prototype.ValueTypeName="vector";class yp{constructor(t,e=-1,n,i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=Vn(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Mp(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(dp.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];op(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new Wi).setHex(r.value);break;case"v2":i.uniforms[e].value=(new pi).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new qi).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new Fi).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new mi).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new Sr).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new pi).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=n(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapRotation&&i.envMapRotation.fromArray(t.envMapRotation),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new pi).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=n(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=n(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=n(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=n(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=n(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}static createMaterialFromType(t){return new{ShadowMaterial:gu,SpriteMaterial:vu,RawShaderMaterial:_u,ShaderMaterial:Is,PointsMaterial:Bc,MeshPhysicalMaterial:yu,MeshStandardMaterial:xu,MeshPhongMaterial:Mu,MeshToonMaterial:Su,MeshNormalMaterial:bu,MeshLambertMaterial:Eu,MeshDepthMaterial:Ql,MeshDistanceMaterial:tc,MeshBasicMaterial:$s,MeshMatcapMaterial:Tu,LineDashedMaterial:wu,LineBasicMaterial:Rc,Material:As}[t]}}class bp{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;n0){const n=new Ru(e);r=new Ou(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new Ou(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e{const e=new Zi;e.min.fromArray(t.boxMin),e.max.fromArray(t.boxMax);const n=new mr;return n.radius=t.sphereRadius,n.center.fromArray(t.sphereCenter),{boxInitialized:t.boxInitialized,box:e,sphereInitialized:t.sphereInitialized,sphere:n}})),s._maxGeometryCount=t.maxGeometryCount,s._maxVertexCount=t.maxVertexCount,s._maxIndexCount=t.maxIndexCount,s._geometryInitialized=t.geometryInitialized,s._geometryCount=t.geometryCount,s._matricesTexture=h(t.matricesTexture.uuid);break;case"LOD":s=new bd;break;case"Line":s=new Dc(l(t.geometry),c(t.material));break;case"LineLoop":s=new zc(l(t.geometry),c(t.material));break;case"LineSegments":s=new Fc(l(t.geometry),c(t.material));break;case"PointCloud":case"Points":s=new Wc(l(t.geometry),c(t.material));break;case"Sprite":s=new xd(c(t.material));break;case"Group":s=new ac;break;case"Bone":s=new Dd;break;default:s=new fs}if(s.uuid=t.uuid,void 0!==t.name&&(s.name=t.name),void 0!==t.matrix?(s.matrix.fromArray(t.matrix),void 0!==t.matrixAutoUpdate&&(s.matrixAutoUpdate=t.matrixAutoUpdate),s.matrixAutoUpdate&&s.matrix.decompose(s.position,s.quaternion,s.scale)):(void 0!==t.position&&s.position.fromArray(t.position),void 0!==t.rotation&&s.rotation.fromArray(t.rotation),void 0!==t.quaternion&&s.quaternion.fromArray(t.quaternion),void 0!==t.scale&&s.scale.fromArray(t.scale)),void 0!==t.up&&s.up.fromArray(t.up),void 0!==t.castShadow&&(s.castShadow=t.castShadow),void 0!==t.receiveShadow&&(s.receiveShadow=t.receiveShadow),t.shadow&&(void 0!==t.shadow.bias&&(s.shadow.bias=t.shadow.bias),void 0!==t.shadow.normalBias&&(s.shadow.normalBias=t.shadow.normalBias),void 0!==t.shadow.radius&&(s.shadow.radius=t.shadow.radius),void 0!==t.shadow.mapSize&&s.shadow.mapSize.fromArray(t.shadow.mapSize),void 0!==t.shadow.camera&&(s.shadow.camera=this.parseObject(t.shadow.camera))),void 0!==t.visible&&(s.visible=t.visible),void 0!==t.frustumCulled&&(s.frustumCulled=t.frustumCulled),void 0!==t.renderOrder&&(s.renderOrder=t.renderOrder),void 0!==t.userData&&(s.userData=t.userData),void 0!==t.layers&&(s.layers.mask=t.layers),void 0!==t.children){const a=t.children;for(let t=0;t{e&&e(n),r.manager.itemEnd(t)})).catch((t=>{i&&i(t)})):(setTimeout((function(){e&&e(s),r.manager.itemEnd(t)}),0),s);const a={};a.credentials="anonymous"===this.crossOrigin?"same-origin":"include",a.headers=this.requestHeader;const o=fetch(t,a).then((function(t){return t.blob()})).then((function(t){return createImageBitmap(t,Object.assign(r.options,{colorSpaceConversion:"none"}))})).then((function(n){return Au.add(t,n),e&&e(n),r.manager.itemEnd(t),n})).catch((function(e){i&&i(e),Au.remove(t),r.manager.itemError(t),r.manager.itemEnd(t)}));Au.add(t,o),r.manager.itemStart(t)}}let Pp;class Ip{static getContext(){return void 0===Pp&&(Pp=new(window.AudioContext||window.webkitAudioContext)),Pp}static setContext(t){Pp=t}}class Up extends Lu{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new Uu(this.manager);function a(e){i?i(e):console.error(e),r.manager.itemError(t)}s.setResponseType("arraybuffer"),s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(t){try{const n=t.slice(0);Ip.getContext().decodeAudioData(n,(function(t){e(t)})).catch(a)}catch(t){a(t)}}),n,i)}}const Dp=new Sr,Np=new Sr,Op=new Sr;class Fp{constructor(){this.type="StereoCamera",this.aspect=1,this.eyeSep=.064,this.cameraL=new Ua,this.cameraL.layers.enable(1),this.cameraL.matrixAutoUpdate=!1,this.cameraR=new Ua,this.cameraR.layers.enable(2),this.cameraR.matrixAutoUpdate=!1,this._cache={focus:null,fov:null,aspect:null,near:null,far:null,zoom:null,eyeSep:null}}update(t){const e=this._cache;if(e.focus!==t.focus||e.fov!==t.fov||e.aspect!==t.aspect*this.aspect||e.near!==t.near||e.far!==t.far||e.zoom!==t.zoom||e.eyeSep!==this.eyeSep){e.focus=t.focus,e.fov=t.fov,e.aspect=t.aspect*this.aspect,e.near=t.near,e.far=t.far,e.zoom=t.zoom,e.eyeSep=this.eyeSep,Op.copy(t.projectionMatrix);const n=e.eyeSep/2,i=n*e.near/e.focus,r=e.near*Math.tan(Gn*e.fov*.5)/e.zoom;let s,a;Np.elements[12]=-n,Dp.elements[12]=n,s=-r*e.aspect+i,a=r*e.aspect+i,Op.elements[0]=2*e.near/(a-s),Op.elements[8]=(a+s)/(a-s),this.cameraL.projectionMatrix.copy(Op),s=-r*e.aspect-i,a=r*e.aspect-i,Op.elements[0]=2*e.near/(a-s),Op.elements[8]=(a+s)/(a-s),this.cameraR.projectionMatrix.copy(Op)}this.cameraL.matrixWorld.copy(t.matrixWorld).multiply(Np),this.cameraR.matrixWorld.copy(t.matrixWorld).multiply(Dp)}}class zp{constructor(t){this.value=t}clone(){return new zp(void 0===this.value.clone?this.value:this.value.clone())}}class Bp extends id{constructor(t,e,n=1){super(t,e),this.isInstancedInterleavedBuffer=!0,this.meshPerAttribute=n}copy(t){return super.copy(t),this.meshPerAttribute=t.meshPerAttribute,this}clone(t){const e=super.clone(t);return e.meshPerAttribute=this.meshPerAttribute,e}toJSON(t){const e=super.toJSON(t);return e.isInstancedInterleavedBuffer=!0,e.meshPerAttribute=this.meshPerAttribute,e}}class Hp{constructor(t,e,n,i,r){this.isGLBufferAttribute=!0,this.name="",this.buffer=t,this.type=e,this.itemSize=n,this.elementSize=i,this.count=r,this.version=0}set needsUpdate(t){!0===t&&this.version++}setBuffer(t){return this.buffer=t,this}setType(t,e){return this.type=t,this.elementSize=e,this}setItemSize(t){return this.itemSize=t,this}setCount(t){return this.count=t,this}}const Gp=new Sr;class kp{constructor(t,e,n=0,i=1/0){this.ray=new Hs(t,e),this.near=n,this.far=i,this.camera=null,this.layers=new Qr,this.params={Mesh:{},Line:{threshold:1},LOD:{},Points:{threshold:1},Sprite:{}}}set(t,e){this.ray.set(t,e)}setFromCamera(t,e){e.isPerspectiveCamera?(this.ray.origin.setFromMatrixPosition(e.matrixWorld),this.ray.direction.set(t.x,t.y,.5).unproject(e).sub(this.ray.origin).normalize(),this.camera=e):e.isOrthographicCamera?(this.ray.origin.set(t.x,t.y,(e.near+e.far)/(e.near-e.far)).unproject(e),this.ray.direction.set(0,0,-1).transformDirection(e.matrixWorld),this.camera=e):console.error("THREE.Raycaster: Unsupported camera type: "+e.type)}setFromXRController(t){return Gp.identity().extractRotation(t.matrixWorld),this.ray.origin.setFromMatrixPosition(t.matrixWorld),this.ray.direction.set(0,0,-1).applyMatrix4(Gp),this}intersectObject(t,e=!0,n=[]){return Wp(t,this,n,e),n.sort(Vp),n}intersectObjects(t,e=!0,n=[]){for(let i=0,r=t.length;ithis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Jp).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Kp=new qi,$p=new qi;class Qp{constructor(t=new qi,e=new qi){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){Kp.subVectors(t,this.start),$p.subVectors(this.end,this.start);const n=$p.dot($p);let i=$p.dot(Kp)/n;return e&&(i=Wn(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return this.delta(n).multiplyScalar(i).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}class tm extends Dc{constructor(t,e=1,n=16776960){const i=n,r=new bs;r.setAttribute("position",new Jr([1,-1,0,-1,1,0,-1,-1,0,1,1,0,-1,1,0,-1,-1,0,1,-1,0,1,1,0],3)),r.computeBoundingSphere(),super(r,new Rc({color:i,toneMapped:!1})),this.type="PlaneHelper",this.plane=t,this.size=e;const s=new bs;s.setAttribute("position",new Jr([1,1,0,-1,1,0,-1,-1,0,1,1,0,-1,-1,0,1,-1,0],3)),s.computeBoundingSphere(),this.add(new ga(s,new $s({color:i,opacity:.2,transparent:!0,depthWrite:!1,toneMapped:!1})))}updateMatrixWorld(t){this.position.set(0,0,0),this.scale.set(.5*this.size,.5*this.size,1),this.lookAt(this.plane.normal),this.translateZ(-this.plane.constant),super.updateMatrixWorld(t)}dispose(){this.geometry.dispose(),this.material.dispose(),this.children[0].geometry.dispose(),this.children[0].material.dispose()}}class em{constructor(){this.type="ShapePath",this.color=new Wi,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new mh,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,n,i){return this.currentPath.quadraticCurveTo(t,e,n,i),this}bezierCurveTo(t,e,n,i,r,s){return this.currentPath.bezierCurveTo(t,e,n,i,r,s),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const n=e.length;let i=!1;for(let r=n-1,s=0;sNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const n=Qh.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,s,a;const o=[];if(1===i.length)return s=i[0],a=new Ah,a.curves=s.curves,o.push(a),o;let l=!n(i[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],m=0;h[m]=void 0,p[m]=[];for(let e=0,a=i.length;e1){let t=!1,n=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;t>8&255]+Vn[t>>16&255]+Vn[t>>24&255]+"-"+Vn[255&e]+Vn[e>>8&255]+"-"+Vn[e>>16&15|64]+Vn[e>>24&255]+"-"+Vn[63&n|128]+Vn[n>>8&255]+"-"+Vn[n>>16&255]+Vn[n>>24&255]+Vn[255&i]+Vn[i>>8&255]+Vn[i>>16&255]+Vn[i>>24&255]).toLowerCase()}function jn(t,e,n){return Math.max(e,Math.min(n,t))}function qn(t,e){return(t%e+e)%e}function Yn(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)}function Jn(t,e,n){return t!==e?(n-t)/(e-t):0}function Zn(t,e,n){return(1-n)*t+n*e}function Kn(t,e,n,i){return Zn(t,e,1-Math.exp(-n*i))}function $n(t,e=1){return e-Math.abs(qn(t,2*e)-e)}function Qn(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)}function ti(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)}function ei(t,e){return t+Math.floor(Math.random()*(e-t+1))}function ni(t,e){return t+Math.random()*(e-t)}function ii(t){return t*(.5-Math.random())}function ri(t){void 0!==t&&(kn=t);let e=kn+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296}function si(t){return t*Gn}function ai(t){return t*Wn}function oi(t){return!(t&t-1)&&0!==t}function li(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ci(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function hi(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),f=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*f,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*f,o*c);break;case"ZYZ":t.set(l*f,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}function ui(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function di(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const pi={DEG2RAD:Gn,RAD2DEG:Wn,generateUUID:Xn,clamp:jn,euclideanModulo:qn,mapLinear:Yn,inverseLerp:Jn,lerp:Zn,damp:Kn,pingpong:$n,smoothstep:Qn,smootherstep:ti,randInt:ei,randFloat:ni,randFloatSpread:ii,seededRandom:ri,degToRad:si,radToDeg:ai,isPowerOfTwo:oi,ceilPowerOfTwo:li,floorPowerOfTwo:ci,setQuaternionFromProperEuler:hi,normalize:di,denormalize:ui};var fi=Object.freeze({__proto__:null,DEG2RAD:Gn,MathUtils:pi,RAD2DEG:Wn,ceilPowerOfTwo:li,clamp:jn,damp:Kn,degToRad:si,denormalize:ui,euclideanModulo:qn,floorPowerOfTwo:ci,generateUUID:Xn,inverseLerp:Jn,isPowerOfTwo:oi,lerp:Zn,mapLinear:Yn,normalize:di,pingpong:$n,radToDeg:ai,randFloat:ni,randFloatSpread:ii,randInt:ei,seededRandom:ri,setQuaternionFromProperEuler:hi,smootherstep:ti,smoothstep:Qn});class mi{constructor(t=0,e=0){mi.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=jn(this.x,t.x,e.x),this.y=jn(this.y,t.y,e.y),this}clampScalar(t,e){return this.x=jn(this.x,t,e),this.y=jn(this.y,t,e),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(jn(n,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(jn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class gi{constructor(t=0,e=0,n=0,i=1){this.isQuaternion=!0,this._x=t,this._y=e,this._z=n,this._w=i}static slerpFlat(t,e,n,i,r,s,a){let o=n[i+0],l=n[i+1],c=n[i+2],h=n[i+3];const u=r[s+0],d=r[s+1],p=r[s+2],f=r[s+3];if(0===a)return t[e+0]=o,t[e+1]=l,t[e+2]=c,void(t[e+3]=h);if(1===a)return t[e+0]=u,t[e+1]=d,t[e+2]=p,void(t[e+3]=f);if(h!==f||o!==u||l!==d||c!==p){let t=1-a;const e=o*u+l*d+c*p+h*f,n=e>=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+f*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e=!0){const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!0===e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return n<1e-8?(n=0,Math.abs(t.x)>Math.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(jn(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=2*Math.PI*Math.random(),e=2*Math.PI*Math.random(),n=Math.random(),i=Math.sqrt(1-n),r=Math.sqrt(n);return this.set(i*Math.sin(t),i*Math.cos(t),r*Math.sin(e),r*Math.cos(e))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class vi{constructor(t=0,e=0,n=0){vi.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(xi.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(xi.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=2*(s*i-a*n),c=2*(a*e-r*i),h=2*(r*n-s*e);return this.x=e+o*l+s*h-a*c,this.y=n+o*c+a*l-r*h,this.z=i+o*h+r*c-s*l,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=jn(this.x,t.x,e.x),this.y=jn(this.y,t.y,e.y),this.z=jn(this.z,t.z,e.z),this}clampScalar(t,e){return this.x=jn(this.x,t,e),this.y=jn(this.y,t,e),this.z=jn(this.z,t,e),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(jn(n,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return _i.copy(this).projectOnVector(t),this.sub(_i)}reflect(t){return this.sub(_i.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(jn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=Math.random()*Math.PI*2,e=2*Math.random()-1,n=Math.sqrt(1-e*e);return this.x=n*Math.cos(t),this.y=e,this.z=n*Math.sin(t),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const _i=new vi,xi=new gi;class yi{constructor(t,e,n,i,r,s,a,o,l){yi.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l)}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],f=i[0],m=i[3],g=i[6],v=i[1],_=i[4],x=i[7],y=i[2],M=i[5],S=i[8];return r[0]=s*f+a*v+o*y,r[3]=s*m+a*_+o*M,r[6]=s*g+a*x+o*S,r[1]=l*f+c*v+h*y,r[4]=l*m+c*_+h*M,r[7]=l*g+c*x+h*S,r[2]=u*f+d*v+p*y,r[5]=u*m+d*_+p*M,r[8]=u*g+d*x+p*S,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const f=1/p;return t[0]=h*f,t[1]=(i*l-c*n)*f,t[2]=(a*n-i*s)*f,t[3]=u*f,t[4]=(c*e-i*o)*f,t[5]=(i*r-a*e)*f,t[6]=d*f,t[7]=(n*o-l*e)*f,t[8]=(s*e-n*r)*f,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(Mi.makeScale(t,e)),this}rotate(t){return this.premultiply(Mi.makeRotation(-t)),this}translate(t,e){return this.premultiply(Mi.makeTranslation(t,e)),this}makeTranslation(t,e){return t.isVector2?this.set(1,0,t.x,0,1,t.y,0,0,1):this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const Mi=new yi;function Si(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const bi={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function Ti(t,e){return new bi[t](e)}function Ei(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function wi(){const t=Ei("canvas");return t.style.display="block",t}const Ai={};function Ri(t){t in Ai||(Ai[t]=!0,console.warn(t))}const Ci=(new yi).set(.4123908,.3575843,.1804808,.212639,.7151687,.0721923,.0193308,.1191948,.9505322),Pi=(new yi).set(3.2409699,-1.5373832,-.4986108,-.9692436,1.8759675,.0415551,.0556301,-.203977,1.0569715);function Ii(){const t={enabled:!0,workingColorSpace:Ze,spaces:{},convert:function(t,e,n){return!1!==this.enabled&&e!==n&&e&&n?(this.spaces[e].transfer===$e&&(t.r=Ui(t.r),t.g=Ui(t.g),t.b=Ui(t.b)),this.spaces[e].primaries!==this.spaces[n].primaries&&(t.applyMatrix3(this.spaces[e].toXYZ),t.applyMatrix3(this.spaces[n].fromXYZ)),this.spaces[n].transfer===$e&&(t.r=Di(t.r),t.g=Di(t.g),t.b=Di(t.b)),t):t},workingToColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},colorSpaceToWorking:function(t,e){return this.convert(t,e,this.workingColorSpace)},getPrimaries:function(t){return this.spaces[t].primaries},getTransfer:function(t){return t===Ye?Ke:this.spaces[t].transfer},getToneMappingMode:function(t){return this.spaces[t].outputColorSpaceConfig.toneMappingMode||"standard"},getLuminanceCoefficients:function(t,e=this.workingColorSpace){return t.fromArray(this.spaces[e].luminanceCoefficients)},define:function(t){Object.assign(this.spaces,t)},_getMatrix:function(t,e,n){return t.copy(this.spaces[e].toXYZ).multiply(this.spaces[n].fromXYZ)},_getDrawingBufferColorSpace:function(t){return this.spaces[t].outputColorSpaceConfig.drawingBufferColorSpace},_getUnpackColorSpace:function(t=this.workingColorSpace){return this.spaces[t].workingColorSpaceConfig.unpackColorSpace},fromWorkingColorSpace:function(e,n){return Ri("THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace()."),t.workingToColorSpace(e,n)},toWorkingColorSpace:function(e,n){return Ri("THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking()."),t.colorSpaceToWorking(e,n)}},e=[.64,.33,.3,.6,.15,.06],n=[.2126,.7152,.0722],i=[.3127,.329];return t.define({[Ze]:{primaries:e,whitePoint:i,transfer:Ke,toXYZ:Ci,fromXYZ:Pi,luminanceCoefficients:n,workingColorSpaceConfig:{unpackColorSpace:Je},outputColorSpaceConfig:{drawingBufferColorSpace:Je}},[Je]:{primaries:e,whitePoint:i,transfer:$e,toXYZ:Ci,fromXYZ:Pi,luminanceCoefficients:n,outputColorSpaceConfig:{drawingBufferColorSpace:Je}}}),t}const Li=Ii();function Ui(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Di(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}let Ni;class Oi{static getDataURL(t,e="image/png"){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let n;if(t instanceof HTMLCanvasElement)n=t;else{void 0===Ni&&(Ni=Ei("canvas")),Ni.width=t.width,Ni.height=t.height;const e=Ni.getContext("2d");t instanceof ImageData?e.putImageData(t,0,0):e.drawImage(t,0,0,t.width,t.height),n=Ni}return n.toDataURL(e)}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=Ei("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t1),this.pmremVersion=0}get width(){return this.source.getSize(Vi).x}get height(){return this.source.getSize(Vi).y}get depth(){return this.source.getSize(Vi).z}get image(){return this.source.data}set image(t=null){this.source.data=t}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}clone(){return(new this.constructor).copy(this)}copy(t){return this.name=t.name,this.source=t.source,this.mipmaps=t.mipmaps.slice(0),this.mapping=t.mapping,this.channel=t.channel,this.wrapS=t.wrapS,this.wrapT=t.wrapT,this.magFilter=t.magFilter,this.minFilter=t.minFilter,this.anisotropy=t.anisotropy,this.format=t.format,this.internalFormat=t.internalFormat,this.type=t.type,this.offset.copy(t.offset),this.repeat.copy(t.repeat),this.center.copy(t.center),this.rotation=t.rotation,this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrix.copy(t.matrix),this.generateMipmaps=t.generateMipmaps,this.premultiplyAlpha=t.premultiplyAlpha,this.flipY=t.flipY,this.unpackAlignment=t.unpackAlignment,this.colorSpace=t.colorSpace,this.renderTarget=t.renderTarget,this.isRenderTargetTexture=t.isRenderTargetTexture,this.isArrayTexture=t.isArrayTexture,this.userData=JSON.parse(JSON.stringify(t.userData)),this.needsUpdate=!0,this}setValues(t){for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Texture.setValues(): parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&n&&i.isVector2&&n.isVector2||i&&n&&i.isVector3&&n.isVector3||i&&n&&i.isMatrix3&&n.isMatrix3?i.copy(n):this[e]=n:console.warn(`THREE.Texture.setValues(): property '${e}' does not exist.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;if(!e&&void 0!==t.textures[this.uuid])return t.textures[this.uuid];const n={metadata:{version:4.7,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,image:this.source.toJSON(t).uuid,mapping:this.mapping,channel:this.channel,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,internalFormat:this.internalFormat,type:this.type,colorSpace:this.colorSpace,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,generateMipmaps:this.generateMipmaps,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};return Object.keys(this.userData).length>0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==ot)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case pt:t.x=t.x-Math.floor(t.x);break;case ft:t.x=t.x<0?0:1;break;case mt:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case pt:t.y=t.y-Math.floor(t.y);break;case ft:t.y=t.y<0?0:1;break;case mt:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}set needsPMREMUpdate(t){!0===t&&this.pmremVersion++}}ki.DEFAULT_IMAGE=null,ki.DEFAULT_MAPPING=ot,ki.DEFAULT_ANISOTROPY=1;class Gi{constructor(t=0,e=0,n=0,i=1){Gi.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this.w/=t.w,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],f=o[2],m=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o1;this.dispose()}this.viewport.set(0,0,t,e),this.scissor.set(0,0,t,e)}clone(){return(new this.constructor).copy(this)}copy(t){this.width=t.width,this.height=t.height,this.depth=t.depth,this.scissor.copy(t.scissor),this.scissorTest=t.scissorTest,this.viewport.copy(t.viewport),this.textures.length=0;for(let e=0,n=t.textures.length;e1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}class Zi{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,n)}set(t,e,n){if(void 0===e&&void 0===n){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,n);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Je){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,Li.colorSpaceToWorking(this,e),this}setRGB(t,e,n,i=Li.workingColorSpace){return this.r=t,this.g=e,this.b=n,Li.colorSpaceToWorking(this,i),this}setHSL(t,e,n,i=Li.workingColorSpace){if(t=qn(t,1),e=jn(e,0,1),n=jn(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Ji(r,i,t+1/3),this.g=Ji(r,i,t),this.b=Ji(r,i,t-1/3)}return Li.colorSpaceToWorking(this,i),this}setStyle(t,e=Je){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const s=i[1],a=i[2];switch(s){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const n=i[1],r=n.length;if(3===r)return this.setRGB(parseInt(n.charAt(0),16)/15,parseInt(n.charAt(1),16)/15,parseInt(n.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(n,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Je){const n=ji[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=Ui(t.r),this.g=Ui(t.g),this.b=Ui(t.b),this}copyLinearToSRGB(t){return this.r=Di(t.r),this.g=Di(t.g),this.b=Di(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Je){return Li.workingToColorSpace(Ki.copy(this),t),65536*Math.round(jn(255*Ki.r,0,255))+256*Math.round(jn(255*Ki.g,0,255))+Math.round(jn(255*Ki.b,0,255))}getHexString(t=Je){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=Li.workingColorSpace){Li.workingToColorSpace(Ki.copy(this),e);const n=Ki.r,i=Ki.g,r=Ki.b,s=Math.max(n,i,r),a=Math.min(n,i,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case n:o=(i-r)/t+(i=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y&&t.z>=this.min.z&&t.z<=this.max.z}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y&&t.max.z>=this.min.z&&t.min.z<=this.max.z}intersectsSphere(t){return this.clampPoint(t.center,tr),tr.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(lr),cr.subVectors(this.max,lr),nr.subVectors(t.a,lr),ir.subVectors(t.b,lr),rr.subVectors(t.c,lr),sr.subVectors(ir,nr),ar.subVectors(rr,ir),or.subVectors(nr,rr);let e=[0,-sr.z,sr.y,0,-ar.z,ar.y,0,-or.z,or.y,sr.z,0,-sr.x,ar.z,0,-ar.x,or.z,0,-or.x,-sr.y,sr.x,0,-ar.y,ar.x,0,-or.y,or.x,0];return!!dr(e,nr,ir,rr,cr)&&(e=[1,0,0,0,1,0,0,0,1],!!dr(e,nr,ir,rr,cr)&&(hr.crossVectors(sr,ar),e=[hr.x,hr.y,hr.z],dr(e,nr,ir,rr,cr)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,tr).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(tr).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Qi[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Qi[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Qi[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Qi[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Qi[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Qi[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Qi[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Qi[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Qi)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}toJSON(){return{min:this.min.toArray(),max:this.max.toArray()}}fromJSON(t){return this.min.fromArray(t.min),this.max.fromArray(t.max),this}}const Qi=[new vi,new vi,new vi,new vi,new vi,new vi,new vi,new vi],tr=new vi,er=new $i,nr=new vi,ir=new vi,rr=new vi,sr=new vi,ar=new vi,or=new vi,lr=new vi,cr=new vi,hr=new vi,ur=new vi;function dr(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){ur.fromArray(t,s);const a=r.x*Math.abs(ur.x)+r.y*Math.abs(ur.y)+r.z*Math.abs(ur.z),o=e.dot(ur),l=n.dot(ur),c=i.dot(ur);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const pr=new $i,fr=new vi,mr=new vi;class gr{constructor(t=new vi,e=-1){this.isSphere=!0,this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):pr.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;fr.subVectors(t,this.center);const e=fr.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(fr,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(mr.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(fr.copy(t.center).add(mr)),this.expandByPoint(fr.copy(t.center).sub(mr))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}toJSON(){return{radius:this.radius,center:this.center.toArray()}}fromJSON(t){return this.radius=t.radius,this.center.fromArray(t.center),this}}const vr=new vi,_r=new vi,xr=new yi;class yr{constructor(t=new vi(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=vr.subVectors(n,e).cross(_r.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(t).addScaledVector(this.normal,-this.distanceToPoint(t))}intersectLine(t,e){const n=t.delta(vr),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(t.start).addScaledVector(n,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||xr.getNormalMatrix(t),i=this.coplanarPoint(vr).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const Mr=new gr,Sr=new mi(.5,.5),br=new vi;class Tr{constructor(t=new yr,e=new yr,n=new yr,i=new yr,r=new yr,s=new yr){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t,e=2e3,n=!1){const i=this.planes,r=t.elements,s=r[0],a=r[1],o=r[2],l=r[3],c=r[4],h=r[5],u=r[6],d=r[7],p=r[8],f=r[9],m=r[10],g=r[11],v=r[12],_=r[13],x=r[14],y=r[15];if(i[0].setComponents(l-s,d-c,g-p,y-v).normalize(),i[1].setComponents(l+s,d+c,g+p,y+v).normalize(),i[2].setComponents(l+a,d+h,g+f,y+_).normalize(),i[3].setComponents(l-a,d-h,g-f,y-_).normalize(),n)i[4].setComponents(o,u,m,x).normalize(),i[5].setComponents(l-o,d-u,g-m,y-x).normalize();else if(i[4].setComponents(l-o,d-u,g-m,y-x).normalize(),e===Nn)i[5].setComponents(l+o,d+u,g+m,y+x).normalize();else{if(e!==On)throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+e);i[5].setComponents(o,u,m,x).normalize()}return this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),Mr.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),Mr.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(Mr)}intersectsSprite(t){Mr.center.set(0,0,0);const e=Sr.distanceTo(t.center);return Mr.radius=.7071067811865476+e,Mr.applyMatrix4(t.matrixWorld),this.intersectsSphere(Mr)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,br.y=i.normal.y>0?t.max.y:t.min.y,br.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(br)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}class Er{constructor(t,e,n,i,r,s,a,o,l,c,h,u,d,p,f,m){Er.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,f,m)}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,f,m){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=f,g[15]=m,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new Er).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/wr.setFromMatrixColumn(t,0).length(),r=1/wr.setFromMatrixColumn(t,1).length(),s=1/wr.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(Rr,t,Cr)}lookAt(t,e,n){const i=this.elements;return Lr.subVectors(t,e),0===Lr.lengthSq()&&(Lr.z=1),Lr.normalize(),Pr.crossVectors(n,Lr),0===Pr.lengthSq()&&(1===Math.abs(n.z)?Lr.x+=1e-4:Lr.z+=1e-4,Lr.normalize(),Pr.crossVectors(n,Lr)),Pr.normalize(),Ir.crossVectors(Lr,Pr),i[0]=Pr.x,i[4]=Ir.x,i[8]=Lr.x,i[1]=Pr.y,i[5]=Ir.y,i[9]=Lr.y,i[2]=Pr.z,i[6]=Ir.z,i[10]=Lr.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],f=n[6],m=n[10],g=n[14],v=n[3],_=n[7],x=n[11],y=n[15],M=i[0],S=i[4],b=i[8],T=i[12],E=i[1],w=i[5],A=i[9],R=i[13],C=i[2],P=i[6],I=i[10],L=i[14],U=i[3],D=i[7],N=i[11],O=i[15];return r[0]=s*M+a*E+o*C+l*U,r[4]=s*S+a*w+o*P+l*D,r[8]=s*b+a*A+o*I+l*N,r[12]=s*T+a*R+o*L+l*O,r[1]=c*M+h*E+u*C+d*U,r[5]=c*S+h*w+u*P+d*D,r[9]=c*b+h*A+u*I+d*N,r[13]=c*T+h*R+u*L+d*O,r[2]=p*M+f*E+m*C+g*U,r[6]=p*S+f*w+m*P+g*D,r[10]=p*b+f*A+m*I+g*N,r[14]=p*T+f*R+m*L+g*O,r[3]=v*M+_*E+x*C+y*U,r[7]=v*S+_*w+x*P+y*D,r[11]=v*b+_*A+x*I+y*N,r[15]=v*T+_*R+x*L+y*O,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],f=t[13],m=t[14],g=t[15],v=h*m*l-f*u*l+f*o*d-a*m*d-h*o*g+a*u*g,_=p*u*l-c*m*l-p*o*d+s*m*d+c*o*g-s*u*g,x=c*f*l-p*h*l+p*a*d-s*f*d-c*a*g+s*h*g,y=p*h*o-c*f*o-p*a*u+s*f*u+c*a*m-s*h*m,M=e*v+n*_+i*x+r*y;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const S=1/M;return t[0]=v*S,t[1]=(f*u*r-h*m*r-f*i*d+n*m*d+h*i*g-n*u*g)*S,t[2]=(a*m*r-f*o*r+f*i*l-n*m*l-a*i*g+n*o*g)*S,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*S,t[4]=_*S,t[5]=(c*m*r-p*u*r+p*i*d-e*m*d-c*i*g+e*u*g)*S,t[6]=(p*o*r-s*m*r-p*i*l+e*m*l+s*i*g-e*o*g)*S,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*S,t[8]=x*S,t[9]=(p*h*r-c*f*r-p*n*d+e*f*d+c*n*g-e*h*g)*S,t[10]=(s*f*r-p*a*r+p*n*l-e*f*l-s*n*g+e*a*g)*S,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*S,t[12]=y*S,t[13]=(c*f*i-p*h*i+p*n*u-e*f*u-c*n*m+e*h*m)*S,t[14]=(p*a*i-s*f*i-p*n*o+e*f*o+s*n*m-e*a*m)*S,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*S,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return t.isVector3?this.set(1,0,0,t.x,0,1,0,t.y,0,0,1,t.z,0,0,0,1):this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,f=s*c,m=s*h,g=a*h,v=o*l,_=o*c,x=o*h,y=n.x,M=n.y,S=n.z;return i[0]=(1-(f+g))*y,i[1]=(d+x)*y,i[2]=(p-_)*y,i[3]=0,i[4]=(d-x)*M,i[5]=(1-(u+g))*M,i[6]=(m+v)*M,i[7]=0,i[8]=(p+_)*S,i[9]=(m-v)*S,i[10]=(1-(u+f))*S,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=wr.set(i[0],i[1],i[2]).length();const s=wr.set(i[4],i[5],i[6]).length(),a=wr.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],Ar.copy(this);const o=1/r,l=1/s,c=1/a;return Ar.elements[0]*=o,Ar.elements[1]*=o,Ar.elements[2]*=o,Ar.elements[4]*=l,Ar.elements[5]*=l,Ar.elements[6]*=l,Ar.elements[8]*=c,Ar.elements[9]*=c,Ar.elements[10]*=c,e.setFromRotationMatrix(Ar),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s,a=2e3,o=!1){const l=this.elements,c=2*r/(e-t),h=2*r/(n-i),u=(e+t)/(e-t),d=(n+i)/(n-i);let p,f;if(o)p=r/(s-r),f=s*r/(s-r);else if(a===Nn)p=-(s+r)/(s-r),f=-2*s*r/(s-r);else{if(a!==On)throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+a);p=-s/(s-r),f=-s*r/(s-r)}return l[0]=c,l[4]=0,l[8]=u,l[12]=0,l[1]=0,l[5]=h,l[9]=d,l[13]=0,l[2]=0,l[6]=0,l[10]=p,l[14]=f,l[3]=0,l[7]=0,l[11]=-1,l[15]=0,this}makeOrthographic(t,e,n,i,r,s,a=2e3,o=!1){const l=this.elements,c=2/(e-t),h=2/(n-i),u=-(e+t)/(e-t),d=-(n+i)/(n-i);let p,f;if(o)p=1/(s-r),f=s/(s-r);else if(a===Nn)p=-2/(s-r),f=-(s+r)/(s-r);else{if(a!==On)throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+a);p=-1/(s-r),f=-r/(s-r)}return l[0]=c,l[4]=0,l[8]=0,l[12]=u,l[1]=0,l[5]=h,l[9]=0,l[13]=d,l[2]=0,l[6]=0,l[10]=p,l[14]=f,l[3]=0,l[7]=0,l[11]=0,l[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const wr=new vi,Ar=new Er,Rr=new vi(0,0,0),Cr=new vi(1,1,1),Pr=new vi,Ir=new vi,Lr=new vi;function Ur(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function Dr(t){const e=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),e.get(t)},remove:function(n){n.isInterleavedBufferAttribute&&(n=n.data);const i=e.get(n);i&&(t.deleteBuffer(i.buffer),e.delete(n))},update:function(n,i){if(n.isInterleavedBufferAttribute&&(n=n.data),n.isGLBufferAttribute){const t=e.get(n);return void((!t||t.versiont.start-e.start);let e=0;for(let t=1;t>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,n=0;for(;!(8388608&e);)e<<=1,n-=8388608;e&=-8388609,n+=947912704,s[t]=e|n}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:n,baseTable:i,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}function Fr(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=jn(t,-65504,65504),Nr.floatView[0]=t;const e=Nr.uint32View[0],n=e>>23&511;return Nr.baseTable[n]+((8388607&e)>>Nr.shiftTable[n])}function Br(t){const e=t>>10;return Nr.uint32View[0]=Nr.mantissaTable[Nr.offsetTable[e]+(1023&t)]+Nr.exponentTable[e],Nr.floatView[0]}var zr=Object.freeze({__proto__:null,DataUtils:class{static toHalfFloat(t){return Fr(t)}static fromHalfFloat(t){return Br(t)}},fromHalfFloat:Br,toHalfFloat:Fr});const Hr=new vi,Vr=new mi;let kr=0;class Gr{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,Object.defineProperty(this,"id",{value:kr++}),this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=Tn,this.updateRanges=[],this.gpuType=Lt,this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this.gpuType=t.gpuType,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(i.type="BatchedMesh",i.perObjectFrustumCulled=this.perObjectFrustumCulled,i.sortObjects=this.sortObjects,i.drawRanges=this._drawRanges,i.reservedRanges=this._reservedRanges,i.geometryInfo=this._geometryInfo.map(t=>({...t,boundingBox:t.boundingBox?t.boundingBox.toJSON():void 0,boundingSphere:t.boundingSphere?t.boundingSphere.toJSON():void 0})),i.instanceInfo=this._instanceInfo.map(t=>({...t})),i.availableInstanceIds=this._availableInstanceIds.slice(),i.availableGeometryIds=this._availableGeometryIds.slice(),i.nextIndexStart=this._nextIndexStart,i.nextVertexStart=this._nextVertexStart,i.geometryCount=this._geometryCount,i.maxInstanceCount=this._maxInstanceCount,i.maxVertexCount=this._maxVertexCount,i.maxIndexCount=this._maxIndexCount,i.geometryInitialized=this._geometryInitialized,i.matricesTexture=this._matricesTexture.toJSON(t),i.indirectTexture=this._indirectTexture.toJSON(t),null!==this._colorsTexture&&(i.colorsTexture=this._colorsTexture.toJSON(t)),null!==this.boundingSphere&&(i.boundingSphere=this.boundingSphere.toJSON()),null!==this.boundingBox&&(i.boundingBox=this.boundingBox.toJSON())),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),h.length>0&&(n.nodes=h)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations.slice(),this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;ee.count&&console.warn("THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry."),e.needsUpdate=!0}return this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new $i);const t=this.attributes.position,e=this.morphAttributes.position;if(t&&t.isGLBufferAttribute)return console.error("THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.",this),void this.boundingBox.set(new vi(-1/0,-1/0,-1/0),new vi(1/0,1/0,1/0));if(void 0!==t){if(this.boundingBox.setFromBufferAttribute(t),e)for(let t=0,n=e.length;t0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere=a.toJSON()),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone());const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0?1:-1,c.push(A.x,A.y,A.z),h.push(o/m),h.push(1-s/g),E+=1}}for(let t=0;t0!=t>0&&this.version++,this._alphaTest=t}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.7,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),void 0!==this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.sheenColorMap&&this.sheenColorMap.isTexture&&(n.sheenColorMap=this.sheenColorMap.toJSON(t).uuid),this.sheenRoughnessMap&&this.sheenRoughnessMap.isTexture&&(n.sheenRoughnessMap=this.sheenRoughnessMap.toJSON(t).uuid),void 0!==this.dispersion&&(n.dispersion=this.dispersion),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(n.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapRotation&&(n.envMapRotation=this.envMapRotation.toArray()),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),!0===this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=!0),this.blendSrc!==C&&(n.blendSrc=this.blendSrc),this.blendDst!==P&&(n.blendDst=this.blendDst),this.blendEquation!==y&&(n.blendEquation=this.blendEquation),null!==this.blendSrcAlpha&&(n.blendSrcAlpha=this.blendSrcAlpha),null!==this.blendDstAlpha&&(n.blendDstAlpha=this.blendDstAlpha),null!==this.blendEquationAlpha&&(n.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(n.blendColor=this.blendColor.getHex()),0!==this.blendAlpha&&(n.blendAlpha=this.blendAlpha),3!==this.depthFunc&&(n.depthFunc=this.depthFunc),!1===this.depthTest&&(n.depthTest=this.depthTest),!1===this.depthWrite&&(n.depthWrite=this.depthWrite),!1===this.colorWrite&&(n.colorWrite=this.colorWrite),255!==this.stencilWriteMask&&(n.stencilWriteMask=this.stencilWriteMask),519!==this.stencilFunc&&(n.stencilFunc=this.stencilFunc),0!==this.stencilRef&&(n.stencilRef=this.stencilRef),255!==this.stencilFuncMask&&(n.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==tn&&(n.stencilFail=this.stencilFail),this.stencilZFail!==tn&&(n.stencilZFail=this.stencilZFail),this.stencilZPass!==tn&&(n.stencilZPass=this.stencilZPass),!0===this.stencilWrite&&(n.stencilWrite=this.stencilWrite),void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaHash&&(n.alphaHash=!0),!0===this.alphaToCoverage&&(n.alphaToCoverage=!0),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=!0),!0===this.forceSinglePass&&(n.forceSinglePass=!0),!0===this.wireframe&&(n.wireframe=!0),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=!0),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.blendColor.copy(t.blendColor),this.blendAlpha=t.blendAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaHash=t.alphaHash,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}function Is(t){const e={};for(const n in t){e[n]={};for(const i in t[n]){const r=t[n][i];r&&(r.isColor||r.isMatrix3||r.isMatrix4||r.isVector2||r.isVector3||r.isVector4||r.isTexture||r.isQuaternion)?r.isRenderTargetTexture?(console.warn("UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms()."),e[n][i]=null):e[n][i]=r.clone():Array.isArray(r)?e[n][i]=r.slice():e[n][i]=r}}return e}function Ls(t){const e={};for(let n=0;n0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}const Os=new vi,Fs=new vi,Bs=new vi,zs=new vi,Hs=new vi,Vs=new vi,ks=new vi;class Gs{constructor(t=new vi,e=new vi(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Os)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Os.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Os.copy(this.origin).addScaledVector(this.direction,e),Os.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){Fs.copy(t).add(e).multiplyScalar(.5),Bs.copy(e).sub(t).normalize(),zs.copy(this.origin).sub(Fs);const r=.5*t.distanceTo(e),s=-this.direction.dot(Bs),a=zs.dot(this.direction),o=-zs.dot(Bs),l=zs.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.origin).addScaledVector(this.direction,h),i&&i.copy(Fs).addScaledVector(Bs,u),d}intersectSphere(t,e){Os.subVectors(t.center,this.origin);const n=Os.dot(this.direction),i=Os.dot(Os)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return!(t.radius<0)&&this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||isNaN(n))&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Os)}intersectTriangle(t,e,n,i,r){Hs.subVectors(e,t),Vs.subVectors(n,t),ks.crossVectors(Hs,Vs);let s,a=this.direction.dot(ks);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}zs.subVectors(this.origin,t);const o=s*this.direction.dot(Vs.crossVectors(zs,Vs));if(o<0)return null;const l=s*this.direction.dot(Hs.cross(zs));if(l<0)return null;if(o+l>a)return null;const c=-s*zs.dot(ks);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}const Ws=new vi,Xs=new vi,js=new vi,qs=new vi,Ys=new vi,Js=new vi,Zs=new vi,Ks=new vi,$s=new vi,Qs=new vi,ta=new Gi,ea=new Gi,na=new Gi;class ia{constructor(t=new vi,e=new vi,n=new vi){this.a=t,this.b=e,this.c=n}static getNormal(t,e,n,i){i.subVectors(n,e),Ws.subVectors(t,e),i.cross(Ws);const r=i.lengthSq();return r>0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Ws.subVectors(i,e),Xs.subVectors(n,e),js.subVectors(t,e);const s=Ws.dot(Ws),a=Ws.dot(Xs),o=Ws.dot(js),l=Xs.dot(Xs),c=Xs.dot(js),h=s*l-a*a;if(0===h)return r.set(0,0,0),null;const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return null!==this.getBarycoord(t,e,n,i,qs)&&(qs.x>=0&&qs.y>=0&&qs.x+qs.y<=1)}static getInterpolation(t,e,n,i,r,s,a,o){return null===this.getBarycoord(t,e,n,i,qs)?(o.x=0,o.y=0,"z"in o&&(o.z=0),"w"in o&&(o.w=0),null):(o.setScalar(0),o.addScaledVector(r,qs.x),o.addScaledVector(s,qs.y),o.addScaledVector(a,qs.z),o)}static getInterpolatedAttribute(t,e,n,i,r,s){return ta.setScalar(0),ea.setScalar(0),na.setScalar(0),ta.fromBufferAttribute(t,e),ea.fromBufferAttribute(t,n),na.fromBufferAttribute(t,i),s.setScalar(0),s.addScaledVector(ta,r.x),s.addScaledVector(ea,r.y),s.addScaledVector(na,r.z),s}static isFrontFacing(t,e,n,i){return Ws.subVectors(n,e),Xs.subVectors(t,e),Ws.cross(Xs).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Ws.subVectors(this.c,this.b),Xs.subVectors(this.a,this.b),.5*Ws.cross(Xs).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return ia.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return ia.getBarycoord(t,this.a,this.b,this.c,e)}getInterpolation(t,e,n,i,r){return ia.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return ia.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return ia.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;Ys.subVectors(i,n),Js.subVectors(r,n),Ks.subVectors(t,n);const o=Ys.dot(Ks),l=Js.dot(Ks);if(o<=0&&l<=0)return e.copy(n);$s.subVectors(t,i);const c=Ys.dot($s),h=Js.dot($s);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(Ys,s);Qs.subVectors(t,r);const d=Ys.dot(Qs),p=Js.dot(Qs);if(p>=0&&d<=p)return e.copy(r);const f=d*l-o*p;if(f<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(Js,a);const m=c*p-d*h;if(m<=0&&h-c>=0&&d-p>=0)return Zs.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(Zs,a);const g=1/(m+f+u);return s=f*g,a=u*g,e.copy(n).addScaledVector(Ys,s).addScaledVector(Js,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}class ra extends Ps{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new Zi(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new es,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const sa=new Er,aa=new Gs,oa=new gr,la=new vi,ca=new vi,ha=new vi,ua=new vi,da=new vi,pa=new vi,fa=new vi,ma=new vi;class ga extends _s{constructor(t=new ws,e=new ra){super(),this.isMesh=!0,this.type="Mesh",this.geometry=t,this.material=e,this.morphTargetDictionary=void 0,this.morphTargetInfluences=void 0,this.count=1,this.updateMorphTargets()}copy(t,e){return super.copy(t,e),void 0!==t.morphTargetInfluences&&(this.morphTargetInfluences=t.morphTargetInfluences.slice()),void 0!==t.morphTargetDictionary&&(this.morphTargetDictionary=Object.assign({},t.morphTargetDictionary)),this.material=Array.isArray(t.material)?t.material.slice():t.material,this.geometry=t.geometry,this}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t(t.far-t.near)**2)return}sa.copy(r).invert(),aa.copy(t.ray).applyMatrix4(sa),null!==n.boundingBox&&!1===aa.intersectsBox(n.boundingBox)||this._computeIntersections(t,e,aa)}}_computeIntersections(t,e,n){let i;const r=this.geometry,s=this.material,a=r.index,o=r.attributes.position,l=r.attributes.uv,c=r.attributes.uv1,h=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(s))for(let r=0,o=u.length;rn.far?null:{distance:c,point:ma.clone(),object:t}}(t,e,n,i,ca,ha,ua,fa);if(h){const t=new vi;ia.getBarycoord(fa,ca,ha,ua,t),r&&(h.uv=ia.getInterpolatedAttribute(r,o,l,c,t,new mi)),s&&(h.uv1=ia.getInterpolatedAttribute(s,o,l,c,t,new mi)),a&&(h.normal=ia.getInterpolatedAttribute(a,o,l,c,t,new vi),h.normal.dot(i.direction)>0&&h.normal.multiplyScalar(-1));const e={a:o,b:l,c:c,normal:new vi,materialIndex:0};ia.getNormal(ca,ha,ua,e.normal),h.face=e,h.barycoord=t}return h}const _a={alphahash_fragment:"#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif",alphahash_pars_fragment:"#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif",alphamap_fragment:"#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif",alphamap_pars_fragment:"#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",alphatest_fragment:"#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif",alphatest_pars_fragment:"#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif",aomap_fragment:"#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif",aomap_pars_fragment:"#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif",batching_pars_vertex:"#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec3 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 ).rgb;\n\t}\n#endif",batching_vertex:"#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif",begin_vertex:"vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif",beginnormal_vertex:"vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif",bsdfs:"float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated",iridescence_fragment:"#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",colorspace_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",colorspace_pars_fragment:"vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphinstance_vertex:"#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",opaque_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\tfloat depth = unpackRGBAToDepth( texture2D( depths, uv ) );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\treturn step( depth, compare );\n\t\t#else\n\t\t\treturn step( compare, depth );\n\t\t#endif\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow( sampler2D shadow, vec2 uv, float compare ) {\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\tfloat hard_shadow = step( distribution.x, compare );\n\t\t#else\n\t\t\tfloat hard_shadow = step( compare, distribution.x );\n\t\t#endif\n\t\tif ( hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\tfloat fragCoordZ = vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ];\n\t#else\n\t\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ] + 0.5;\n\t#endif\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},xa={common:{diffuse:{value:new Zi(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new yi},alphaMap:{value:null},alphaMapTransform:{value:new yi},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new yi}},envmap:{envMap:{value:null},envMapRotation:{value:new yi},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new yi}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new yi}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new yi},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new yi},normalScale:{value:new mi(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new yi},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new yi}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new yi}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new yi}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Zi(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Zi(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new yi},alphaTest:{value:0},uvTransform:{value:new yi}},sprite:{diffuse:{value:new Zi(16777215)},opacity:{value:1},center:{value:new mi(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new yi},alphaMap:{value:null},alphaMapTransform:{value:new yi},alphaTest:{value:0}}},ya={basic:{uniforms:Ls([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.fog]),vertexShader:_a.meshbasic_vert,fragmentShader:_a.meshbasic_frag},lambert:{uniforms:Ls([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,xa.lights,{emissive:{value:new Zi(0)}}]),vertexShader:_a.meshlambert_vert,fragmentShader:_a.meshlambert_frag},phong:{uniforms:Ls([xa.common,xa.specularmap,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,xa.lights,{emissive:{value:new Zi(0)},specular:{value:new Zi(1118481)},shininess:{value:30}}]),vertexShader:_a.meshphong_vert,fragmentShader:_a.meshphong_frag},standard:{uniforms:Ls([xa.common,xa.envmap,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.roughnessmap,xa.metalnessmap,xa.fog,xa.lights,{emissive:{value:new Zi(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:_a.meshphysical_vert,fragmentShader:_a.meshphysical_frag},toon:{uniforms:Ls([xa.common,xa.aomap,xa.lightmap,xa.emissivemap,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.gradientmap,xa.fog,xa.lights,{emissive:{value:new Zi(0)}}]),vertexShader:_a.meshtoon_vert,fragmentShader:_a.meshtoon_frag},matcap:{uniforms:Ls([xa.common,xa.bumpmap,xa.normalmap,xa.displacementmap,xa.fog,{matcap:{value:null}}]),vertexShader:_a.meshmatcap_vert,fragmentShader:_a.meshmatcap_frag},points:{uniforms:Ls([xa.points,xa.fog]),vertexShader:_a.points_vert,fragmentShader:_a.points_frag},dashed:{uniforms:Ls([xa.common,xa.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:_a.linedashed_vert,fragmentShader:_a.linedashed_frag},depth:{uniforms:Ls([xa.common,xa.displacementmap]),vertexShader:_a.depth_vert,fragmentShader:_a.depth_frag},normal:{uniforms:Ls([xa.common,xa.bumpmap,xa.normalmap,xa.displacementmap,{opacity:{value:1}}]),vertexShader:_a.meshnormal_vert,fragmentShader:_a.meshnormal_frag},sprite:{uniforms:Ls([xa.sprite,xa.fog]),vertexShader:_a.sprite_vert,fragmentShader:_a.sprite_frag},background:{uniforms:{uvTransform:{value:new yi},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:_a.background_vert,fragmentShader:_a.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new yi}},vertexShader:_a.backgroundCube_vert,fragmentShader:_a.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:_a.cube_vert,fragmentShader:_a.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:_a.equirect_vert,fragmentShader:_a.equirect_frag},distanceRGBA:{uniforms:Ls([xa.common,xa.displacementmap,{referencePosition:{value:new vi},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:_a.distanceRGBA_vert,fragmentShader:_a.distanceRGBA_frag},shadow:{uniforms:Ls([xa.lights,xa.fog,{color:{value:new Zi(0)},opacity:{value:1}}]),vertexShader:_a.shadow_vert,fragmentShader:_a.shadow_frag}};ya.physical={uniforms:Ls([ya.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new yi},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new yi},clearcoatNormalScale:{value:new mi(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new yi},dispersion:{value:0},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new yi},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new yi},sheen:{value:0},sheenColor:{value:new Zi(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new yi},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new yi},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new yi},transmissionSamplerSize:{value:new mi},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new yi},attenuationDistance:{value:0},attenuationColor:{value:new Zi(0)},specularColor:{value:new Zi(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new yi},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new yi},anisotropyVector:{value:new mi},anisotropyMap:{value:null},anisotropyMapTransform:{value:new yi}}]),vertexShader:_a.meshphysical_vert,fragmentShader:_a.meshphysical_frag};const Ma={r:0,b:0,g:0},Sa=new es,ba=new Er;function Ta(t,e,n,i,r,s,a){const o=new Zi(0);let l,c,h=!0===s?0:1,u=null,d=0,p=null;function f(t){let i=!0===t.isScene?t.background:null;if(i&&i.isTexture){i=(t.backgroundBlurriness>0?n:e).get(i)}return i}function m(e,n){e.getRGB(Ma,Us(t)),i.buffers.color.setClear(Ma.r,Ma.g,Ma.b,n,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),h=e,m(o,h)},getClearAlpha:function(){return h},setClearAlpha:function(t){h=t,m(o,h)},render:function(e){let n=!1;const r=f(e);null===r?m(o,h):r&&r.isColor&&(m(r,1),n=!0);const s=t.xr.getEnvironmentBlendMode();"additive"===s?i.buffers.color.setClear(0,0,0,1,a):"alpha-blend"===s&&i.buffers.color.setClear(0,0,0,0,a),(t.autoClear||n)&&(i.buffers.depth.setTest(!0),i.buffers.depth.setMask(!0),i.buffers.color.setMask(!0),t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil))},addToRenderList:function(e,n){const i=f(n);i&&(i.isCubeTexture||i.mapping===dt)?(void 0===c&&(c=new ga(new As(1,1,1),new Ns({name:"BackgroundCubeMaterial",uniforms:Is(ya.backgroundCube.uniforms),vertexShader:ya.backgroundCube.vertexShader,fragmentShader:ya.backgroundCube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),c.geometry.deleteAttribute("normal"),c.geometry.deleteAttribute("uv"),c.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(c.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(c)),Sa.copy(n.backgroundRotation),Sa.x*=-1,Sa.y*=-1,Sa.z*=-1,i.isCubeTexture&&!1===i.isRenderTargetTexture&&(Sa.y*=-1,Sa.z*=-1),c.material.uniforms.envMap.value=i,c.material.uniforms.flipEnvMap.value=i.isCubeTexture&&!1===i.isRenderTargetTexture?-1:1,c.material.uniforms.backgroundBlurriness.value=n.backgroundBlurriness,c.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,c.material.uniforms.backgroundRotation.value.setFromMatrix4(ba.makeRotationFromEuler(Sa)),c.material.toneMapped=Li.getTransfer(i.colorSpace)!==$e,u===i&&d===i.version&&p===t.toneMapping||(c.material.needsUpdate=!0,u=i,d=i.version,p=t.toneMapping),c.layers.enableAll(),e.unshift(c,c.geometry,c.material,0,0,null)):i&&i.isTexture&&(void 0===l&&(l=new ga(new Rs(2,2),new Ns({name:"BackgroundMaterial",uniforms:Is(ya.background.uniforms),vertexShader:ya.background.vertexShader,fragmentShader:ya.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),l.geometry.deleteAttribute("normal"),Object.defineProperty(l.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(l)),l.material.uniforms.t2D.value=i,l.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,l.material.toneMapped=Li.getTransfer(i.colorSpace)!==$e,!0===i.matrixAutoUpdate&&i.updateMatrix(),l.material.uniforms.uvTransform.value.copy(i.matrix),u===i&&d===i.version&&p===t.toneMapping||(l.material.needsUpdate=!0,u=i,d=i.version,p=t.toneMapping),l.layers.enableAll(),e.unshift(l,l.geometry,l.material,0,0,null))},dispose:function(){void 0!==c&&(c.geometry.dispose(),c.material.dispose(),c=void 0),void 0!==l&&(l.geometry.dispose(),l.material.dispose(),l=void 0)}}}function Ea(t,e){const n=t.getParameter(t.MAX_VERTEX_ATTRIBS),i={},r=c(null);let s=r,a=!1;function o(e){return t.bindVertexArray(e)}function l(e){return t.deleteVertexArray(e)}function c(t){const e=[],i=[],r=[];for(let t=0;t=0){const n=r[e];let i=a[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;o++}}return s.attributesNum!==o||s.index!==i}(n,m,l,g),v&&function(t,e,n,i){const r={},a=e.attributes;let o=0;const l=n.getAttributes();for(const e in l){if(l[e].location>=0){let n=a[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,o++}}s.attributes=r,s.attributesNum=o,s.index=i}(n,m,l,g),null!==g&&e.update(g,t.ELEMENT_ARRAY_BUFFER),(v||a)&&(a=!1,function(n,i,r,s){h();const a=s.attributes,o=r.getAttributes(),l=i.defaultAttributeValues;for(const i in o){const r=o[i];if(r.location>=0){let o=a[i];if(void 0===o&&("instanceMatrix"===i&&n.instanceMatrix&&(o=n.instanceMatrix),"instanceColor"===i&&n.instanceColor&&(o=n.instanceColor)),void 0!==o){const i=o.normalized,a=o.itemSize,l=e.get(o);if(void 0===l)continue;const c=l.buffer,h=l.type,p=l.bytesPerElement,m=h===t.INT||h===t.UNSIGNED_INT||o.gpuType===Pt;if(o.isInterleavedBufferAttribute){const e=o.data,l=e.stride,g=o.offset;if(e.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(t.VERTEX_SHADER,t.MEDIUM_FLOAT).precision>0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}let a=void 0!==n.precision?n.precision:"highp";const o=s(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=!0===n.logarithmicDepthBuffer,c=!0===n.reversedDepthBuffer&&e.has("EXT_clip_control"),h=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),u=t.getParameter(t.MAX_VERTEX_TEXTURE_IMAGE_UNITS);return{isWebGL2:!0,getMaxAnisotropy:function(){if(void 0!==r)return r;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");r=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else r=0;return r},getMaxPrecision:s,textureFormatReadable:function(e){return e===Vt||i.convert(e)===t.getParameter(t.IMPLEMENTATION_COLOR_READ_FORMAT)},textureTypeReadable:function(n){const r=n===Ut&&(e.has("EXT_color_buffer_half_float")||e.has("EXT_color_buffer_float"));return!(n!==wt&&i.convert(n)!==t.getParameter(t.IMPLEMENTATION_COLOR_READ_TYPE)&&n!==Lt&&!r)},precision:a,logarithmicDepthBuffer:l,reversedDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:t.getParameter(t.MAX_TEXTURE_SIZE),maxCubemapSize:t.getParameter(t.MAX_CUBE_MAP_TEXTURE_SIZE),maxAttributes:t.getParameter(t.MAX_VERTEX_ATTRIBS),maxVertexUniforms:t.getParameter(t.MAX_VERTEX_UNIFORM_VECTORS),maxVaryings:t.getParameter(t.MAX_VARYING_VECTORS),maxFragmentUniforms:t.getParameter(t.MAX_FRAGMENT_UNIFORM_VECTORS),vertexTextures:u>0,maxSamples:t.getParameter(t.MAX_SAMPLES)}}function Ra(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new yr,o=new yi,l={value:null,needsUpdate:!1};function c(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0);e.numPlanes=i,e.numIntersection=0}();else{const t=s?0:i,e=4*t;let r=f.clippingState||null;l.value=r,r=c(u,o,e,h);for(let t=0;t!==e;++t)r[t]=n[t];f.clippingState=r,this.numIntersection=d?this.numPlanes:0,this.numPlanes+=t}}}class Ca extends _s{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new Er,this.projectionMatrix=new Er,this.projectionMatrixInverse=new Er,this.coordinateSystem=Nn,this._reversedDepth=!1}get reversedDepth(){return this._reversedDepth}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this.coordinateSystem=t.coordinateSystem,this}getWorldDirection(t){return super.getWorldDirection(t).negate()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}const Pa=new vi,Ia=new mi,La=new mi;class Ua extends Ca{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*Wn*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Gn*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*Wn*Math.atan(Math.tan(.5*Gn*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(t,e,n){Pa.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),e.set(Pa.x,Pa.y).multiplyScalar(-t/Pa.z),Pa.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),n.set(Pa.x,Pa.y).multiplyScalar(-t/Pa.z)}getViewSize(t,e){return this.getViewBounds(t,Ia,La),e.subVectors(La,Ia)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Gn*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far,this.coordinateSystem,this.reversedDepth),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const Da=-90;class Na extends _s{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n,this.coordinateSystem=null,this.activeMipmapLevel=0;const i=new Ua(Da,1,t,e);i.layers=this.layers,this.add(i);const r=new Ua(Da,1,t,e);r.layers=this.layers,this.add(r);const s=new Ua(Da,1,t,e);s.layers=this.layers,this.add(s);const a=new Ua(Da,1,t,e);a.layers=this.layers,this.add(a);const o=new Ua(Da,1,t,e);o.layers=this.layers,this.add(o);const l=new Ua(Da,1,t,e);l.layers=this.layers,this.add(l)}updateCoordinateSystem(){const t=this.coordinateSystem,e=this.children.concat(),[n,i,r,s,a,o]=e;for(const t of e)this.remove(t);if(t===Nn)n.up.set(0,1,0),n.lookAt(1,0,0),i.up.set(0,1,0),i.lookAt(-1,0,0),r.up.set(0,0,-1),r.lookAt(0,1,0),s.up.set(0,0,1),s.lookAt(0,-1,0),a.up.set(0,1,0),a.lookAt(0,0,1),o.up.set(0,1,0),o.lookAt(0,0,-1);else{if(t!==On)throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+t);n.up.set(0,-1,0),n.lookAt(-1,0,0),i.up.set(0,-1,0),i.lookAt(1,0,0),r.up.set(0,0,1),r.lookAt(0,1,0),s.up.set(0,0,-1),s.lookAt(0,-1,0),a.up.set(0,-1,0),a.lookAt(0,0,1),o.up.set(0,-1,0),o.lookAt(0,0,-1)}for(const t of e)this.add(t),t.updateMatrixWorld()}update(t,e){null===this.parent&&this.updateMatrixWorld();const{renderTarget:n,activeMipmapLevel:i}=this;this.coordinateSystem!==t.coordinateSystem&&(this.coordinateSystem=t.coordinateSystem,this.updateCoordinateSystem());const[r,s,a,o,l,c]=this.children,h=t.getRenderTarget(),u=t.getActiveCubeFace(),d=t.getActiveMipmapLevel(),p=t.xr.enabled;t.xr.enabled=!1;const f=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0,i),t.render(e,r),t.setRenderTarget(n,1,i),t.render(e,s),t.setRenderTarget(n,2,i),t.render(e,a),t.setRenderTarget(n,3,i),t.render(e,o),t.setRenderTarget(n,4,i),t.render(e,l),n.texture.generateMipmaps=f,t.setRenderTarget(n,5,i),t.render(e,c),t.setRenderTarget(h,u,d),t.xr.enabled=p,n.texture.needsPMREMUpdate=!0}}class Oa extends ki{constructor(t=[],e=301,n,i,r,s,a,o,l,c){super(t,e,n,i,r,s,a,o,l,c),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class Fa extends Xi{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];this.texture=new Oa(i),this._setTextureOptions(e),this.texture.isRenderTargetTexture=!0}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new As(5,5,5),r=new Ns({name:"CubemapFromEquirect",uniforms:Is(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new ga(i,r),a=e.minFilter;e.minFilter===Tt&&(e.minFilter=Mt);return new Na(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e=!0,n=!0,i=!0){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}function Ba(t){let e=new WeakMap;function n(t,e){return e===ht?t.mapping=lt:e===ut&&(t.mapping=ct),t}function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping;if(s===ht||s===ut){if(e.has(r)){return n(e.get(r).texture,r.mapping)}{const s=r.image;if(s&&s.height>0){const a=new Fa(s.height);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",i),n(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class za extends Ca{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far,this.coordinateSystem,this.reversedDepth),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const Ha=[.125,.215,.35,.446,.526,.582],Va=20,ka=new za,Ga=new Zi;let Wa=null,Xa=0,ja=0,qa=!1;const Ya=(1+Math.sqrt(5))/2,Ja=1/Ya,Za=[new vi(-Ya,Ja,0),new vi(Ya,Ja,0),new vi(-Ja,0,Ya),new vi(Ja,0,Ya),new vi(0,Ya,-Ja),new vi(0,Ya,Ja),new vi(-1,1,-1),new vi(1,1,-1),new vi(-1,1,1),new vi(1,1,1)],Ka=new vi;class $a{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100,r={}){const{size:s=256,position:a=Ka}=r;Wa=this._renderer.getRenderTarget(),Xa=this._renderer.getActiveCubeFace(),ja=this._renderer.getActiveMipmapLevel(),qa=this._renderer.xr.enabled,this._renderer.xr.enabled=!1,this._setSize(s);const o=this._allocateTargets();return o.depthBuffer=!0,this._sceneToCubeUV(t,n,i,o,a),e>0&&this._blur(o,0,0,e),this._applyPMREM(o),this._cleanup(o),o}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=no(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=eo(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=Ha[a-t+4-1]:0===a&&(o=0),i.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,f=3,m=2,g=1,v=new Float32Array(f*p*d),_=new Float32Array(m*p*d),x=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,f*p*t),_.set(u,m*p*t);const r=[t,t,t,t,t,t];x.set(r,g*p*t)}const y=new ws;y.setAttribute("position",new Gr(v,f)),y.setAttribute("uv",new Gr(_,m)),y.setAttribute("faceIndex",new Gr(x,g)),e.push(y),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(Va),r=new vi(0,1,0),s=new Ns({name:"SphericalGaussianBlur",defines:{n:Va,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:io(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(i,t,e)}return i}_compileMaterial(t){const e=new ga(this._lodPlanes[0],t);this._renderer.compile(e,ka)}_sceneToCubeUV(t,e,n,i,r){const s=new Ua(90,1,e,n),a=[1,-1,1,1,1,1],o=[1,1,1,-1,-1,-1],l=this._renderer,c=l.autoClear,h=l.toneMapping;l.getClearColor(Ga),l.toneMapping=0,l.autoClear=!1;l.state.buffers.depth.getReversed()&&(l.setRenderTarget(i),l.clearDepth(),l.setRenderTarget(null));const u=new ra({name:"PMREM.Background",side:1,depthWrite:!1,depthTest:!1}),d=new ga(new As,u);let p=!1;const f=t.background;f?f.isColor&&(u.color.copy(f),t.background=null,p=!0):(u.color.copy(Ga),p=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(s.up.set(0,a[e],0),s.position.set(r.x,r.y,r.z),s.lookAt(r.x+o[e],r.y,r.z)):1===n?(s.up.set(0,0,a[e]),s.position.set(r.x,r.y,r.z),s.lookAt(r.x,r.y+o[e],r.z)):(s.up.set(0,a[e],0),s.position.set(r.x,r.y,r.z),s.lookAt(r.x,r.y,r.z+o[e]));const c=this._cubeSize;to(i,n*c,e>2?c:0,c,c),l.setRenderTarget(i),p&&l.render(d,s),l.render(t,s)}d.geometry.dispose(),d.material.dispose(),l.toneMapping=h,l.autoClear=c,t.background=f}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===lt||t.mapping===ct;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=no()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=eo());const r=i?this._cubemapMaterial:this._equirectMaterial,s=new ga(this._lodPlanes[0],r);r.uniforms.envMap.value=t;const a=this._cubeSize;to(e,0,0,3*a,2*a),n.setRenderTarget(e),n.render(s,ka)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;const i=this._lodPlanes.length;for(let e=1;eVa&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${f} samples when the maximum is set to 20`);const m=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),o.setRenderTarget(e),o.render(c,ka)}}function Qa(t,e,n){const i=new Xi(t,e,n);return i.texture.mapping=dt,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function to(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function eo(){return new Ns({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:io(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function no(){return new Ns({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:io(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function io(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function ro(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping,a=s===ht||s===ut,o=s===lt||s===ct;if(a||o){let s=e.get(r);const l=void 0!==s?s.texture.pmremVersion:0;if(r.isRenderTargetTexture&&r.pmremVersion!==l)return null===n&&(n=new $a(t)),s=a?n.fromEquirectangular(r,s):n.fromCubemap(r,s),s.texture.pmremVersion=r.pmremVersion,e.set(r,s),s.texture;if(void 0!==s)return s.texture;{const l=r.image;return a&&l&&l.height>0||o&&l&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(y=Math.ceil(x/e.maxTextureSize),x=e.maxTextureSize);const M=new Float32Array(x*y*4*h),S=new co(M,x,y,h);S.type=Lt,S.needsUpdate=!0;const b=4*_;for(let E=0;E0)return t;const r=e*n;let s=yo[r];if(void 0===s&&(s=new Float32Array(r),yo[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function wo(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function Tl(t,e){const n=function(t){Li._getMatrix(Sl,Li.workingColorSpace,t);const e=`mat3( ${Sl.elements.map(t=>t.toFixed(4))} )`;switch(Li.getTransfer(t)){case Ke:return[e,"LinearTransferOETF"];case $e:return[e,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space: ",t),[e,"LinearTransferOETF"]}}(e);return[`vec4 ${t}( vec4 value ) {`,`\treturn ${n[1]}( vec4( value.rgb * ${n[0]}, value.a ) );`,"}"].join("\n")}function El(t,e){let n;switch(e){case 1:n="Linear";break;case 2:n="Reinhard";break;case 3:n="Cineon";break;case 4:n="ACESFilmic";break;case 6:n="AgX";break;case 7:n="Neutral";break;case 5:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}const wl=new vi;function Al(){Li.getLuminanceCoefficients(wl);return["float luminance( const in vec3 rgb ) {",`\tconst vec3 weights = vec3( ${wl.x.toFixed(4)}, ${wl.y.toFixed(4)}, ${wl.z.toFixed(4)} );`,"\treturn dot( weights, rgb );","}"].join("\n")}function Rl(t){return""!==t}function Cl(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function Pl(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const Il=/^[ \t]*#include +<([\w\d./]+)>/gm;function Ll(t){return t.replace(Il,Dl)}const Ul=new Map;function Dl(t,e){let n=_a[e];if(void 0===n){const t=Ul.get(e);if(void 0===t)throw new Error("Can not resolve #include <"+e+">");n=_a[t],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',e,t)}return Ll(n)}const Nl=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Ol(t){return t.replace(Nl,Fl)}function Fl(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(g+="\n"),v=["#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,f].filter(Rl).join("\n"),v.length>0&&(v+="\n")):(g=[Bl(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,f,n.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",n.batching?"#define USE_BATCHING":"",n.batchingColor?"#define USE_BATCHING_COLOR":"",n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.instancingMorph?"#define USE_INSTANCING_MORPH":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+h:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.logarithmicDepthBuffer?"#define USE_LOGARITHMIC_DEPTH_BUFFER":"",n.reversedDepthBuffer?"#define USE_REVERSED_DEPTH_BUFFER":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH","\tuniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Rl).join("\n"),v=[Bl(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,f,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+c:"",n.envMap?"#define "+h:"",n.envMap?"#define "+u:"",d?"#define CUBEUV_TEXEL_WIDTH "+d.texelWidth:"",d?"#define CUBEUV_TEXEL_HEIGHT "+d.texelHeight:"",d?"#define CUBEUV_MAX_MIP "+d.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.dispersion?"#define USE_DISPERSION":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor||n.batchingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.decodeVideoTextureEmissive?"#define DECODE_VIDEO_TEXTURE_EMISSIVE":"",n.logarithmicDepthBuffer?"#define USE_LOGARITHMIC_DEPTH_BUFFER":"",n.reversedDepthBuffer?"#define USE_REVERSED_DEPTH_BUFFER":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?_a.tonemapping_pars_fragment:"",0!==n.toneMapping?El("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",_a.colorspace_pars_fragment,Tl("linearToOutputTexel",n.outputColorSpace),Al(),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Rl).join("\n")),a=Ll(a),a=Cl(a,n),a=Pl(a,n),o=Ll(o),o=Cl(o,n),o=Pl(o,n),a=Ol(a),o=Ol(o),!0!==n.isRawShaderMaterial&&(_="#version 300 es\n",g=[p,"#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+g,v=["#define varying in",n.glslVersion===Dn?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===Dn?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+v);const x=_+g+a,y=_+v+o,M=yl(r,r.VERTEX_SHADER,x),S=yl(r,r.FRAGMENT_SHADER,y);function b(e){if(t.debug.checkShaderErrors){const n=r.getProgramInfoLog(m)||"",i=r.getShaderInfoLog(M)||"",s=r.getShaderInfoLog(S)||"",a=n.trim(),o=i.trim(),l=s.trim();let c=!0,h=!0;if(!1===r.getProgramParameter(m,r.LINK_STATUS))if(c=!1,"function"==typeof t.debug.onShaderError)t.debug.onShaderError(r,m,M,S);else{const t=bl(r,M,"vertex"),n=bl(r,S,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(m,r.VALIDATE_STATUS)+"\n\nMaterial Name: "+e.name+"\nMaterial Type: "+e.type+"\n\nProgram Info Log: "+a+"\n"+t+"\n"+n)}else""!==a?console.warn("THREE.WebGLProgram: Program Info Log:",a):""!==o&&""!==l||(h=!1);h&&(e.diagnostics={runnable:c,programLog:a,vertexShader:{log:o,prefix:g},fragmentShader:{log:l,prefix:v}})}r.deleteShader(M),r.deleteShader(S),T=new xl(r,m),E=function(t,e){const n={},i=t.getProgramParameter(e,t.ACTIVE_ATTRIBUTES);for(let r=0;r0,q=s.clearcoat>0,Y=s.dispersion>0,J=s.iridescence>0,Z=s.sheen>0,K=s.transmission>0,$=j&&!!s.anisotropyMap,Q=q&&!!s.clearcoatMap,tt=q&&!!s.clearcoatNormalMap,et=q&&!!s.clearcoatRoughnessMap,nt=J&&!!s.iridescenceMap,it=J&&!!s.iridescenceThicknessMap,rt=Z&&!!s.sheenColorMap,st=Z&&!!s.sheenRoughnessMap,at=!!s.specularMap,ot=!!s.specularColorMap,lt=!!s.specularIntensityMap,ct=K&&!!s.transmissionMap,ht=K&&!!s.thicknessMap,ut=!!s.gradientMap,pt=!!s.alphaMap,ft=s.alphaTest>0,mt=!!s.alphaHash,gt=!!s.extensions;let vt=0;s.toneMapped&&(null!==I&&!0!==I.isXRRenderTarget||(vt=t.toneMapping));const _t={shaderID:b,shaderType:s.type,shaderName:s.name,vertexShader:w,fragmentShader:A,defines:s.defines,customVertexShaderID:R,customFragmentShaderID:C,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:p,batching:D,batchingColor:D&&null!==v._colorsTexture,instancing:U,instancingColor:U&&null!==v.instanceColor,instancingMorph:U&&null!==v.morphTexture,supportsVertexTextures:d,outputColorSpace:null===I?t.outputColorSpace:!0===I.isXRRenderTarget?I.texture.colorSpace:Ze,alphaToCoverage:!!s.alphaToCoverage,map:N,matcap:O,envMap:F,envMapMode:F&&M.mapping,envMapCubeUVHeight:S,aoMap:B,lightMap:z,bumpMap:H,normalMap:V,displacementMap:d&&k,emissiveMap:G,normalMapObjectSpace:V&&1===s.normalMapType,normalMapTangentSpace:V&&0===s.normalMapType,metalnessMap:W,roughnessMap:X,anisotropy:j,anisotropyMap:$,clearcoat:q,clearcoatMap:Q,clearcoatNormalMap:tt,clearcoatRoughnessMap:et,dispersion:Y,iridescence:J,iridescenceMap:nt,iridescenceThicknessMap:it,sheen:Z,sheenColorMap:rt,sheenRoughnessMap:st,specularMap:at,specularColorMap:ot,specularIntensityMap:lt,transmission:K,transmissionMap:ct,thicknessMap:ht,gradientMap:ut,opaque:!1===s.transparent&&1===s.blending&&!1===s.alphaToCoverage,alphaMap:pt,alphaTest:ft,alphaHash:mt,combine:s.combine,mapUv:N&&m(s.map.channel),aoMapUv:B&&m(s.aoMap.channel),lightMapUv:z&&m(s.lightMap.channel),bumpMapUv:H&&m(s.bumpMap.channel),normalMapUv:V&&m(s.normalMap.channel),displacementMapUv:k&&m(s.displacementMap.channel),emissiveMapUv:G&&m(s.emissiveMap.channel),metalnessMapUv:W&&m(s.metalnessMap.channel),roughnessMapUv:X&&m(s.roughnessMap.channel),anisotropyMapUv:$&&m(s.anisotropyMap.channel),clearcoatMapUv:Q&&m(s.clearcoatMap.channel),clearcoatNormalMapUv:tt&&m(s.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:et&&m(s.clearcoatRoughnessMap.channel),iridescenceMapUv:nt&&m(s.iridescenceMap.channel),iridescenceThicknessMapUv:it&&m(s.iridescenceThicknessMap.channel),sheenColorMapUv:rt&&m(s.sheenColorMap.channel),sheenRoughnessMapUv:st&&m(s.sheenRoughnessMap.channel),specularMapUv:at&&m(s.specularMap.channel),specularColorMapUv:ot&&m(s.specularColorMap.channel),specularIntensityMapUv:lt&&m(s.specularIntensityMap.channel),transmissionMapUv:ct&&m(s.transmissionMap.channel),thicknessMapUv:ht&&m(s.thicknessMap.channel),alphaMapUv:pt&&m(s.alphaMap.channel),vertexTangents:!!x.attributes.tangent&&(V||j),vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!x.attributes.color&&4===x.attributes.color.itemSize,pointsUvs:!0===v.isPoints&&!!x.attributes.uv&&(N||pt),fog:!!_,useFog:!0===s.fog,fogExp2:!!_&&_.isFogExp2,flatShading:!0===s.flatShading&&!1===s.wireframe,sizeAttenuation:!0===s.sizeAttenuation,logarithmicDepthBuffer:u,reversedDepthBuffer:L,skinning:!0===v.isSkinnedMesh,morphTargets:void 0!==x.morphAttributes.position,morphNormals:void 0!==x.morphAttributes.normal,morphColors:void 0!==x.morphAttributes.color,morphTargetsCount:E,morphTextureStride:P,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numLightProbes:o.numLightProbes,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&h.length>0,shadowMapType:t.shadowMap.type,toneMapping:vt,decodeVideoTexture:N&&!0===s.map.isVideoTexture&&Li.getTransfer(s.map.colorSpace)===$e,decodeVideoTextureEmissive:G&&!0===s.emissiveMap.isVideoTexture&&Li.getTransfer(s.emissiveMap.colorSpace)===$e,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:1===s.side,useDepthPacking:s.depthPacking>=0,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionClipCullDistance:gt&&!0===s.extensions.clipCullDistance&&i.has("WEBGL_clip_cull_distance"),extensionMultiDraw:(gt&&!0===s.extensions.multiDraw||D)&&i.has("WEBGL_multi_draw"),rendererExtensionParallelShaderCompile:i.has("KHR_parallel_shader_compile"),customProgramCacheKey:s.customProgramCacheKey()};return _t.vertexUv1s=c.has(1),_t.vertexUv2s=c.has(2),_t.vertexUv3s=c.has(3),c.clear(),_t},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputColorSpace),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.mapUv),t.push(e.alphaMapUv),t.push(e.lightMapUv),t.push(e.aoMapUv),t.push(e.bumpMapUv),t.push(e.normalMapUv),t.push(e.displacementMapUv),t.push(e.emissiveMapUv),t.push(e.metalnessMapUv),t.push(e.roughnessMapUv),t.push(e.anisotropyMapUv),t.push(e.clearcoatMapUv),t.push(e.clearcoatNormalMapUv),t.push(e.clearcoatRoughnessMapUv),t.push(e.iridescenceMapUv),t.push(e.iridescenceThicknessMapUv),t.push(e.sheenColorMapUv),t.push(e.sheenRoughnessMapUv),t.push(e.specularMapUv),t.push(e.specularColorMapUv),t.push(e.specularIntensityMapUv),t.push(e.transmissionMapUv),t.push(e.thicknessMapUv),t.push(e.combine),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.numLightProbes),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){o.disableAll(),e.supportsVertexTextures&&o.enable(0);e.instancing&&o.enable(1);e.instancingColor&&o.enable(2);e.instancingMorph&&o.enable(3);e.matcap&&o.enable(4);e.envMap&&o.enable(5);e.normalMapObjectSpace&&o.enable(6);e.normalMapTangentSpace&&o.enable(7);e.clearcoat&&o.enable(8);e.iridescence&&o.enable(9);e.alphaTest&&o.enable(10);e.vertexColors&&o.enable(11);e.vertexAlphas&&o.enable(12);e.vertexUv1s&&o.enable(13);e.vertexUv2s&&o.enable(14);e.vertexUv3s&&o.enable(15);e.vertexTangents&&o.enable(16);e.anisotropy&&o.enable(17);e.alphaHash&&o.enable(18);e.batching&&o.enable(19);e.dispersion&&o.enable(20);e.batchingColor&&o.enable(21);e.gradientMap&&o.enable(22);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.reversedDepthBuffer&&o.enable(4);e.skinning&&o.enable(5);e.morphTargets&&o.enable(6);e.morphNormals&&o.enable(7);e.morphColors&&o.enable(8);e.premultipliedAlpha&&o.enable(9);e.shadowMapEnabled&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.transmission&&o.enable(15);e.sheen&&o.enable(16);e.opaque&&o.enable(17);e.pointsUvs&&o.enable(18);e.decodeVideoTexture&&o.enable(19);e.decodeVideoTextureEmissive&&o.enable(20);e.alphaToCoverage&&o.enable(21);t.push(o.mask)}(n,e),n.push(t.outputColorSpace)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=f[t.type];let n;if(e){const t=ya[e];n=Ds.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=h.length;t0?i.push(h):!0===a.transparent?r.push(h):n.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?i.unshift(h):!0===a.transparent?r.unshift(h):n.unshift(h)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||Xl),i.length>1&&i.sort(e||jl),r.length>1&&r.sort(e||jl)}}}function Yl(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new ql,t.set(e,[r])):n>=i.length?(r=new ql,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function Jl(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new vi,color:new Zi};break;case"SpotLight":n={position:new vi,direction:new vi,color:new Zi,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new vi,color:new Zi,distance:0,decay:0};break;case"HemisphereLight":n={direction:new vi,skyColor:new Zi,groundColor:new Zi};break;case"RectAreaLight":n={color:new Zi,position:new vi,halfWidth:new vi,halfHeight:new vi}}return t[e.id]=n,n}}}let Zl=0;function Kl(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function $l(t){const e=new Jl,n=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new mi};break;case"PointLight":n={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new mi,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),i={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let t=0;t<9;t++)i.probe.push(new vi);const r=new vi,s=new Er,a=new Er;return{setup:function(r){let s=0,a=0,o=0;for(let t=0;t<9;t++)i.probe[t].set(0,0,0);let l=0,c=0,h=0,u=0,d=0,p=0,f=0,m=0,g=0,v=0,_=0;r.sort(Kl);for(let t=0,x=r.length;t0&&(!0===t.has("OES_texture_float_linear")?(i.rectAreaLTC1=xa.LTC_FLOAT_1,i.rectAreaLTC2=xa.LTC_FLOAT_2):(i.rectAreaLTC1=xa.LTC_HALF_1,i.rectAreaLTC2=xa.LTC_HALF_2)),i.ambient[0]=s,i.ambient[1]=a,i.ambient[2]=o;const x=i.hash;x.directionalLength===l&&x.pointLength===c&&x.spotLength===h&&x.rectAreaLength===u&&x.hemiLength===d&&x.numDirectionalShadows===p&&x.numPointShadows===f&&x.numSpotShadows===m&&x.numSpotMaps===g&&x.numLightProbes===_||(i.directional.length=l,i.spot.length=h,i.rectArea.length=u,i.point.length=c,i.hemi.length=d,i.directionalShadow.length=p,i.directionalShadowMap.length=p,i.pointShadow.length=f,i.pointShadowMap.length=f,i.spotShadow.length=m,i.spotShadowMap.length=m,i.directionalShadowMatrix.length=p,i.pointShadowMatrix.length=f,i.spotLightMatrix.length=m+g-v,i.spotLightMap.length=g,i.numSpotLightShadowsWithMaps=v,i.numLightProbes=_,x.directionalLength=l,x.pointLength=c,x.spotLength=h,x.rectAreaLength=u,x.hemiLength=d,x.numDirectionalShadows=p,x.numPointShadows=f,x.numSpotShadows=m,x.numSpotMaps=g,x.numLightProbes=_,i.version=Zl++)},setupView:function(t,e){let n=0,o=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=r.length?(s=new Ql(t),r.push(s)):s=r[i],s},dispose:function(){e=new WeakMap}}}class ec extends Ps{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class nc extends Ps{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function ic(t,e,n){let i=new Tr;const r=new mi,s=new mi,a=new Gi,o=new ec({depthPacking:3201}),l=new nc,c={},h=n.maxTextureSize,f={[u]:1,[d]:0,[p]:2},m=new Ns({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new mi},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),g=m.clone();g.defines.HORIZONTAL_PASS=1;const v=new ws;v.setAttribute("position",new Gr(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const _=new ga(v,m),x=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=1;let y=this.type;function M(n,i){const s=e.update(_);m.defines.VSM_SAMPLES!==n.blurSamples&&(m.defines.VSM_SAMPLES=n.blurSamples,g.defines.VSM_SAMPLES=n.blurSamples,m.needsUpdate=!0,g.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new Xi(r.x,r.y)),m.uniforms.shadow_pass.value=n.map.texture,m.uniforms.resolution.value=n.mapSize,m.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,s,m,_,null),g.uniforms.shadow_pass.value=n.mapPass.texture,g.uniforms.resolution.value=n.mapSize,g.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,s,g,_,null)}function S(e,n,i,r){let s=null;const a=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==a)s=a;else if(s=!0===i.isPointLight?l:o,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0||!0===n.alphaToCoverage){const t=s.uuid,e=n.uuid;let i=c[t];void 0===i&&(i={},c[t]=i);let r=i[e];void 0===r&&(r=s.clone(),i[e]=r,n.addEventListener("dispose",T)),s=r}if(s.visible=n.visible,s.wireframe=n.wireframe,s.side=3===r?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:f[n.side],s.alphaMap=n.alphaMap,s.alphaTest=!0===n.alphaToCoverage?.5:n.alphaTest,s.map=n.map,s.clipShadows=n.clipShadows,s.clippingPlanes=n.clippingPlanes,s.clipIntersection=n.clipIntersection,s.displacementMap=n.displacementMap,s.displacementScale=n.displacementScale,s.displacementBias=n.displacementBias,s.wireframeLinewidth=n.wireframeLinewidth,s.linewidth=n.linewidth,!0===i.isPointLight&&!0===s.isMeshDistanceMaterial){t.properties.get(s).light=i}return s}function b(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===o)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),l=n.material;if(Array.isArray(l)){const e=i.groups;for(let c=0,h=e.length;ch||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/m.x),r.x=s.x*m.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/m.y),r.y=s.y*m.y,u.mapSize.y=s.y)),null===u.map||!0===p||!0===f){const t=3!==this.type?{minFilter:gt,magFilter:gt}:{};null!==u.map&&u.map.dispose(),u.map=new Xi(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const g=u.getViewportCount();for(let t=0;t=1):-1!==J.indexOf("OpenGL ES")&&(Y=parseFloat(/^OpenGL ES (\d)/.exec(J)[1]),q=Y>=2);let Z=null,K={};const $=t.getParameter(t.SCISSOR_BOX),Q=t.getParameter(t.VIEWPORT),tt=(new Gi).fromArray($),et=(new Gi).fromArray(Q);function nt(e,n,i,r){const s=new Uint8Array(4),a=t.createTexture();t.bindTexture(e,a),t.texParameteri(e,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(e,t.TEXTURE_MAG_FILTER,t.NEAREST);for(let a=0;an||r.height>n)&&(i=n/Math.max(r.width,r.height)),i<1){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap||"undefined"!=typeof VideoFrame&&t instanceof VideoFrame){const n=Math.floor(i*r.width),s=Math.floor(i*r.height);void 0===u&&(u=f(n,s));const a=e?f(n,s):u;a.width=n,a.height=s;return a.getContext("2d").drawImage(t,0,0,n,s),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+r.width+"x"+r.height+") to ("+n+"x"+s+")."),a}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+r.width+"x"+r.height+")."),t}return t}function g(t){return t.generateMipmaps}function v(e){t.generateMipmap(e)}function _(e){return e.isWebGLCubeRenderTarget?t.TEXTURE_CUBE_MAP:e.isWebGL3DRenderTarget?t.TEXTURE_3D:e.isWebGLArrayRenderTarget||e.isCompressedArrayTexture?t.TEXTURE_2D_ARRAY:t.TEXTURE_2D}function x(n,i,r,s,a=!1){if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let o=i;if(i===t.RED&&(r===t.FLOAT&&(o=t.R32F),r===t.HALF_FLOAT&&(o=t.R16F),r===t.UNSIGNED_BYTE&&(o=t.R8)),i===t.RED_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.R8UI),r===t.UNSIGNED_SHORT&&(o=t.R16UI),r===t.UNSIGNED_INT&&(o=t.R32UI),r===t.BYTE&&(o=t.R8I),r===t.SHORT&&(o=t.R16I),r===t.INT&&(o=t.R32I)),i===t.RG&&(r===t.FLOAT&&(o=t.RG32F),r===t.HALF_FLOAT&&(o=t.RG16F),r===t.UNSIGNED_BYTE&&(o=t.RG8)),i===t.RG_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RG8UI),r===t.UNSIGNED_SHORT&&(o=t.RG16UI),r===t.UNSIGNED_INT&&(o=t.RG32UI),r===t.BYTE&&(o=t.RG8I),r===t.SHORT&&(o=t.RG16I),r===t.INT&&(o=t.RG32I)),i===t.RGB_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RGB8UI),r===t.UNSIGNED_SHORT&&(o=t.RGB16UI),r===t.UNSIGNED_INT&&(o=t.RGB32UI),r===t.BYTE&&(o=t.RGB8I),r===t.SHORT&&(o=t.RGB16I),r===t.INT&&(o=t.RGB32I)),i===t.RGBA_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RGBA8UI),r===t.UNSIGNED_SHORT&&(o=t.RGBA16UI),r===t.UNSIGNED_INT&&(o=t.RGBA32UI),r===t.BYTE&&(o=t.RGBA8I),r===t.SHORT&&(o=t.RGBA16I),r===t.INT&&(o=t.RGBA32I)),i===t.RGB&&(r===t.UNSIGNED_INT_5_9_9_9_REV&&(o=t.RGB9_E5),r===t.UNSIGNED_INT_10F_11F_11F_REV&&(o=t.R11F_G11F_B10F)),i===t.RGBA){const e=a?Ke:Li.getTransfer(s);r===t.FLOAT&&(o=t.RGBA32F),r===t.HALF_FLOAT&&(o=t.RGBA16F),r===t.UNSIGNED_BYTE&&(o=e===$e?t.SRGB8_ALPHA8:t.RGBA8),r===t.UNSIGNED_SHORT_4_4_4_4&&(o=t.RGBA4),r===t.UNSIGNED_SHORT_5_5_5_1&&(o=t.RGB5_A1)}return o!==t.R16F&&o!==t.R32F&&o!==t.RG16F&&o!==t.RG32F&&o!==t.RGBA16F&&o!==t.RGBA32F||e.get("EXT_color_buffer_float"),o}function y(e,n){let i;return e?null===n||n===It||n===Ot?i=t.DEPTH24_STENCIL8:n===Lt?i=t.DEPTH32F_STENCIL8:n===Ct&&(i=t.DEPTH24_STENCIL8,console.warn("DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.")):null===n||n===It||n===Ot?i=t.DEPTH_COMPONENT24:n===Lt?i=t.DEPTH_COMPONENT32F:n===Ct&&(i=t.DEPTH_COMPONENT16),i}function M(t,e){return!0===g(t)||t.isFramebufferTexture&&t.minFilter!==gt&&t.minFilter!==Mt?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function S(t){const e=t.target;e.removeEventListener("dispose",S),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=d.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&T(t),0===Object.keys(r).length&&d.delete(n)}i.remove(t)}(e),e.isVideoTexture&&h.delete(e)}function b(e){const n=e.target;n.removeEventListener("dispose",b),function(e){const n=i.get(e);e.depthTexture&&(e.depthTexture.dispose(),i.remove(e.depthTexture));if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++){if(Array.isArray(n.__webglFramebuffer[e]))for(let i=0;i0&&s.__version!==e.version){const t=e.image;if(null===t)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==t.complete)return void U(s,e,r);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}else e.isExternalTexture&&(s.__webglTexture=e.sourceTexture?e.sourceTexture:null);n.bindTexture(t.TEXTURE_2D,s.__webglTexture,t.TEXTURE0+r)}const A={[pt]:t.REPEAT,[ft]:t.CLAMP_TO_EDGE,[mt]:t.MIRRORED_REPEAT},R={[gt]:t.NEAREST,[vt]:t.NEAREST_MIPMAP_NEAREST,[xt]:t.NEAREST_MIPMAP_LINEAR,[Mt]:t.LINEAR,[St]:t.LINEAR_MIPMAP_NEAREST,[Tt]:t.LINEAR_MIPMAP_LINEAR},C={[gn]:t.NEVER,[bn]:t.ALWAYS,[vn]:t.LESS,[xn]:t.LEQUAL,[_n]:t.EQUAL,[Sn]:t.GEQUAL,[yn]:t.GREATER,[Mn]:t.NOTEQUAL};function P(n,s){if(s.type!==Lt||!1!==e.has("OES_texture_float_linear")||s.magFilter!==Mt&&s.magFilter!==St&&s.magFilter!==xt&&s.magFilter!==Tt&&s.minFilter!==Mt&&s.minFilter!==St&&s.minFilter!==xt&&s.minFilter!==Tt||console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),t.texParameteri(n,t.TEXTURE_WRAP_S,A[s.wrapS]),t.texParameteri(n,t.TEXTURE_WRAP_T,A[s.wrapT]),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,A[s.wrapR]),t.texParameteri(n,t.TEXTURE_MAG_FILTER,R[s.magFilter]),t.texParameteri(n,t.TEXTURE_MIN_FILTER,R[s.minFilter]),s.compareFunction&&(t.texParameteri(n,t.TEXTURE_COMPARE_MODE,t.COMPARE_REF_TO_TEXTURE),t.texParameteri(n,t.TEXTURE_COMPARE_FUNC,C[s.compareFunction])),!0===e.has("EXT_texture_filter_anisotropic")){if(s.magFilter===gt)return;if(s.minFilter!==xt&&s.minFilter!==Tt)return;if(s.type===Lt&&!1===e.has("OES_texture_float_linear"))return;if(s.anisotropy>1||i.get(s).__currentAnisotropy){const a=e.get("EXT_texture_filter_anisotropic");t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy}}}function I(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",S));const r=n.source;let s=d.get(r);void 0===s&&(s={},d.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.colorSpace),e.join()}(n);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,i=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&T(n)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return i}function L(t,e,n){return Math.floor(Math.floor(t/n)/e)}function U(e,a,o){let l=t.TEXTURE_2D;(a.isDataArrayTexture||a.isCompressedArrayTexture)&&(l=t.TEXTURE_2D_ARRAY),a.isData3DTexture&&(l=t.TEXTURE_3D);const c=I(e,a),h=a.source;n.bindTexture(l,e.__webglTexture,t.TEXTURE0+o);const u=i.get(h);if(h.version!==u.__version||!0===c){n.activeTexture(t.TEXTURE0+o);const e=Li.getPrimaries(Li.workingColorSpace),i=a.colorSpace===Ye?null:Li.getPrimaries(a.colorSpace),d=a.colorSpace===Ye||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,d);let p=m(a.image,!1,r.maxTextureSize);p=k(a,p);const f=s.convert(a.format,a.colorSpace),_=s.convert(a.type);let S,b=x(a.internalFormat,f,_,a.colorSpace,a.isVideoTexture);P(l,a);const T=a.mipmaps,E=!0!==a.isVideoTexture,w=void 0===u.__version||!0===c,A=h.dataReady,R=M(a,p);if(a.isDepthTexture)b=y(a.format===Gt,a.type),w&&(E?n.texStorage2D(t.TEXTURE_2D,1,b,p.width,p.height):n.texImage2D(t.TEXTURE_2D,0,b,p.width,p.height,0,f,_,null));else if(a.isDataTexture)if(T.length>0){E&&w&&n.texStorage2D(t.TEXTURE_2D,R,b,T[0].width,T[0].height);for(let e=0,i=T.length;et.start-e.start);let o=0;for(let t=1;t0){const i=ac(S.width,S.height,a.format,a.type);for(const r of a.layerUpdates){const s=S.data.subarray(r*i/S.data.BYTES_PER_ELEMENT,(r+1)*i/S.data.BYTES_PER_ELEMENT);n.compressedTexSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,r,S.width,S.height,1,f,s)}a.clearLayerUpdates()}else n.compressedTexSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,0,S.width,S.height,p.depth,f,S.data)}else n.compressedTexImage3D(t.TEXTURE_2D_ARRAY,e,b,S.width,S.height,p.depth,0,S.data,0,0);else console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");else E?A&&n.texSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,0,S.width,S.height,p.depth,f,_,S.data):n.texImage3D(t.TEXTURE_2D_ARRAY,e,b,S.width,S.height,p.depth,0,f,_,S.data)}else{E&&w&&n.texStorage2D(t.TEXTURE_2D,R,b,T[0].width,T[0].height);for(let e=0,i=T.length;e0){const e=ac(p.width,p.height,a.format,a.type);for(const i of a.layerUpdates){const r=p.data.subarray(i*e/p.data.BYTES_PER_ELEMENT,(i+1)*e/p.data.BYTES_PER_ELEMENT);n.texSubImage3D(t.TEXTURE_2D_ARRAY,0,0,0,i,p.width,p.height,1,f,_,r)}a.clearLayerUpdates()}else n.texSubImage3D(t.TEXTURE_2D_ARRAY,0,0,0,0,p.width,p.height,p.depth,f,_,p.data)}else n.texImage3D(t.TEXTURE_2D_ARRAY,0,b,p.width,p.height,p.depth,0,f,_,p.data);else if(a.isData3DTexture)E?(w&&n.texStorage3D(t.TEXTURE_3D,R,b,p.width,p.height,p.depth),A&&n.texSubImage3D(t.TEXTURE_3D,0,0,0,0,p.width,p.height,p.depth,f,_,p.data)):n.texImage3D(t.TEXTURE_3D,0,b,p.width,p.height,p.depth,0,f,_,p.data);else if(a.isFramebufferTexture){if(w)if(E)n.texStorage2D(t.TEXTURE_2D,R,b,p.width,p.height);else{let e=p.width,i=p.height;for(let r=0;r>=1,i>>=1}}else if(T.length>0){if(E&&w){const e=G(T[0]);n.texStorage2D(t.TEXTURE_2D,R,b,e.width,e.height)}for(let e=0,i=T.length;e>h),i=Math.max(1,r.height>>h);c===t.TEXTURE_3D||c===t.TEXTURE_2D_ARRAY?n.texImage3D(c,h,p,e,i,r.depth,0,u,d,null):n.texImage2D(c,h,p,e,i,0,u,d,null)}n.bindFramebuffer(t.FRAMEBUFFER,e),V(r)?o.framebufferTexture2DMultisampleEXT(t.FRAMEBUFFER,l,c,m.__webglTexture,0,H(r)):(c===t.TEXTURE_2D||c>=t.TEXTURE_CUBE_MAP_POSITIVE_X&&c<=t.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&t.framebufferTexture2D(t.FRAMEBUFFER,l,c,m.__webglTexture,h),n.bindFramebuffer(t.FRAMEBUFFER,null)}function N(e,n,i){if(t.bindRenderbuffer(t.RENDERBUFFER,e),n.depthBuffer){const r=n.depthTexture,s=r&&r.isDepthTexture?r.type:null,a=y(n.stencilBuffer,s),l=n.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,c=H(n);V(n)?o.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,c,a,n.width,n.height):i?t.renderbufferStorageMultisample(t.RENDERBUFFER,c,a,n.width,n.height):t.renderbufferStorage(t.RENDERBUFFER,a,n.width,n.height),t.framebufferRenderbuffer(t.FRAMEBUFFER,l,t.RENDERBUFFER,e)}else{const e=n.textures;for(let r=0;r{delete r.__boundDepthTexture,delete r.__depthDisposeCallback,t.removeEventListener("dispose",e)};t.addEventListener("dispose",e),r.__depthDisposeCallback=e}r.__boundDepthTexture=t}if(e.depthTexture&&!r.__autoAllocateDepthBuffer){if(s)throw new Error("target.depthTexture not supported in Cube render targets");const t=e.texture.mipmaps;t&&t.length>0?O(r.__webglFramebuffer[0],e):O(r.__webglFramebuffer,e)}else if(s){r.__webglDepthbuffer=[];for(let i=0;i<6;i++)if(n.bindFramebuffer(t.FRAMEBUFFER,r.__webglFramebuffer[i]),void 0===r.__webglDepthbuffer[i])r.__webglDepthbuffer[i]=t.createRenderbuffer(),N(r.__webglDepthbuffer[i],e,!1);else{const n=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,s=r.__webglDepthbuffer[i];t.bindRenderbuffer(t.RENDERBUFFER,s),t.framebufferRenderbuffer(t.FRAMEBUFFER,n,t.RENDERBUFFER,s)}}else{const i=e.texture.mipmaps;if(i&&i.length>0?n.bindFramebuffer(t.FRAMEBUFFER,r.__webglFramebuffer[0]):n.bindFramebuffer(t.FRAMEBUFFER,r.__webglFramebuffer),void 0===r.__webglDepthbuffer)r.__webglDepthbuffer=t.createRenderbuffer(),N(r.__webglDepthbuffer,e,!1);else{const n=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,i=r.__webglDepthbuffer;t.bindRenderbuffer(t.RENDERBUFFER,i),t.framebufferRenderbuffer(t.FRAMEBUFFER,n,t.RENDERBUFFER,i)}}n.bindFramebuffer(t.FRAMEBUFFER,null)}const B=[],z=[];function H(t){return Math.min(r.maxSamples,t.samples)}function V(t){const n=i.get(t);return t.samples>0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function k(t,e){const n=t.colorSpace,i=t.format,r=t.type;return!0===t.isCompressedTexture||!0===t.isVideoTexture||n!==Ze&&n!==Ye&&(Li.getTransfer(n)===$e?i===Vt&&r===wt||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",n)),e}function G(t){return"undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement?(c.width=t.naturalWidth||t.width,c.height=t.naturalHeight||t.height):"undefined"!=typeof VideoFrame&&t instanceof VideoFrame?(c.width=t.displayWidth,c.height=t.displayHeight):(c.width=t.width,c.height=t.height),c}this.allocateTextureUnit=function(){const t=E;return t>=r.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+r.maxTextures),E+=1,t},this.resetTextureUnits=function(){E=0},this.setTexture2D=w,this.setTexture2DArray=function(e,r){const s=i.get(e);!1===e.isRenderTargetTexture&&e.version>0&&s.__version!==e.version?U(s,e,r):n.bindTexture(t.TEXTURE_2D_ARRAY,s.__webglTexture,t.TEXTURE0+r)},this.setTexture3D=function(e,r){const s=i.get(e);!1===e.isRenderTargetTexture&&e.version>0&&s.__version!==e.version?U(s,e,r):n.bindTexture(t.TEXTURE_3D,s.__webglTexture,t.TEXTURE0+r)},this.setTextureCube=function(e,a){const o=i.get(e);e.version>0&&o.__version!==e.version?function(e,a,o){if(6!==a.image.length)return;const l=I(e,a),c=a.source;n.bindTexture(t.TEXTURE_CUBE_MAP,e.__webglTexture,t.TEXTURE0+o);const h=i.get(c);if(c.version!==h.__version||!0===l){n.activeTexture(t.TEXTURE0+o);const e=Li.getPrimaries(Li.workingColorSpace),i=a.colorSpace===Ye?null:Li.getPrimaries(a.colorSpace),u=a.colorSpace===Ye||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,u);const d=a.isCompressedTexture||a.image[0].isCompressedTexture,p=a.image[0]&&a.image[0].isDataTexture,f=[];for(let t=0;t<6;t++)f[t]=d||p?p?a.image[t].image:a.image[t]:m(a.image[t],!0,r.maxCubemapSize),f[t]=k(a,f[t]);const _=f[0],y=s.convert(a.format,a.colorSpace),S=s.convert(a.type),b=x(a.internalFormat,y,S,a.colorSpace),T=!0!==a.isVideoTexture,E=void 0===h.__version||!0===l,w=c.dataReady;let A,R=M(a,_);if(P(t.TEXTURE_CUBE_MAP,a),d){T&&E&&n.texStorage2D(t.TEXTURE_CUBE_MAP,R,b,_.width,_.height);for(let e=0;e<6;e++){A=f[e].mipmaps;for(let i=0;i0&&R++;const e=G(f[0]);n.texStorage2D(t.TEXTURE_CUBE_MAP,R,b,e.width,e.height)}for(let e=0;e<6;e++)if(p){T?w&&n.texSubImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,0,0,f[e].width,f[e].height,y,S,f[e].data):n.texImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,b,f[e].width,f[e].height,0,y,S,f[e].data);for(let i=0;i1;if(u||(void 0===l.__webglTexture&&(l.__webglTexture=t.createTexture()),l.__version=r.version,a.memory.textures++),h){o.__webglFramebuffer=[];for(let e=0;e<6;e++)if(r.mipmaps&&r.mipmaps.length>0){o.__webglFramebuffer[e]=[];for(let n=0;n0){o.__webglFramebuffer=[];for(let e=0;e0&&!1===V(e)){o.__webglMultisampledFramebuffer=t.createFramebuffer(),o.__webglColorRenderbuffer=[],n.bindFramebuffer(t.FRAMEBUFFER,o.__webglMultisampledFramebuffer);for(let n=0;n0)for(let i=0;i0)for(let n=0;n0)if(!1===V(e)){const r=e.textures,s=e.width,a=e.height;let o=t.COLOR_BUFFER_BIT;const c=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,h=i.get(e),u=r.length>1;if(u)for(let e=0;e0?n.bindFramebuffer(t.DRAW_FRAMEBUFFER,h.__webglFramebuffer[0]):n.bindFramebuffer(t.DRAW_FRAMEBUFFER,h.__webglFramebuffer);for(let n=0;no+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(uc)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new hc;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class pc extends ki{constructor(t=null){super(),this.sourceTexture=t,this.isExternalTexture=!0}copy(t){return super.copy(t),this.sourceTexture=t.sourceTexture,this}}class fc{constructor(){this.texture=null,this.mesh=null,this.depthNear=0,this.depthFar=0}init(t,e){if(null===this.texture){const n=new pc(t.texture);t.depthNear===e.depthNear&&t.depthFar===e.depthFar||(this.depthNear=t.depthNear,this.depthFar=t.depthFar),this.texture=n}}getMesh(t){if(null!==this.texture&&null===this.mesh){const e=t.cameras[0].viewport,n=new Ns({vertexShader:"\nvoid main() {\n\n\tgl_Position = vec4( position, 1.0 );\n\n}",fragmentShader:"\nuniform sampler2DArray depthColor;\nuniform float depthWidth;\nuniform float depthHeight;\n\nvoid main() {\n\n\tvec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight );\n\n\tif ( coord.x >= 1.0 ) {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r;\n\n\t} else {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r;\n\n\t}\n\n}",uniforms:{depthColor:{value:this.texture},depthWidth:{value:e.z},depthHeight:{value:e.w}}});this.mesh=new ga(new Rs(20,20),n)}return this.mesh}reset(){this.texture=null,this.mesh=null}getDepthTexture(){return this.texture}}class mc extends Hn{constructor(t,e){super();const n=this;let i=null,r=1,s=null,a="local-floor",o=1,l=null,c=null,h=null,u=null,d=null,p=null;const f="undefined"!=typeof XRWebGLBinding,m=new fc,g={},v=e.getContextAttributes();let _=null,x=null;const y=[],M=[],S=new mi;let b=null;const T=new Ua;T.viewport=new Gi;const E=new Ua;E.viewport=new Gi;const w=[T,E],A=new cc;let R=null,C=null;function P(t){const e=M.indexOf(t.inputSource);if(-1===e)return;const n=y[e];void 0!==n&&(n.update(t.inputSource,t.frame,l||s),n.dispatchEvent({type:t.type,data:t.inputSource}))}function I(){i.removeEventListener("select",P),i.removeEventListener("selectstart",P),i.removeEventListener("selectend",P),i.removeEventListener("squeeze",P),i.removeEventListener("squeezestart",P),i.removeEventListener("squeezeend",P),i.removeEventListener("end",I),i.removeEventListener("inputsourceschange",L);for(let t=0;t=0&&(M[i]=null,y[i].disconnect(n))}for(let e=0;e=M.length){M.push(n),i=t;break}if(null===M[t]){M[t]=n,i=t;break}}if(-1===i)break}const r=y[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(t){let e=y[t];return void 0===e&&(e=new dc,y[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=y[t];return void 0===e&&(e=new dc,y[t]=e),e.getGripSpace()},this.getHand=function(t){let e=y[t];return void 0===e&&(e=new dc,y[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return l||s},this.setReferenceSpace=function(t){l=t},this.getBaseLayer=function(){return null!==u?u:d},this.getBinding=function(){return null===h&&f&&(h=new XRWebGLBinding(i,e)),h},this.getFrame=function(){return p},this.getSession=function(){return i},this.setSession=async function(c){if(i=c,null!==i){_=t.getRenderTarget(),i.addEventListener("select",P),i.addEventListener("selectstart",P),i.addEventListener("selectend",P),i.addEventListener("squeeze",P),i.addEventListener("squeezestart",P),i.addEventListener("squeezeend",P),i.addEventListener("end",I),i.addEventListener("inputsourceschange",L),!0!==v.xrCompatible&&await e.makeXRCompatible(),b=t.getPixelRatio(),t.getSize(S);if(f&&"createProjectionLayer"in XRWebGLBinding.prototype){let n=null,s=null,a=null;v.depth&&(a=v.stencil?e.DEPTH24_STENCIL8:e.DEPTH_COMPONENT24,n=v.stencil?Gt:kt,s=v.stencil?Ot:It);const o={colorFormat:e.RGBA8,depthFormat:a,scaleFactor:r};h=this.getBinding(),u=h.createProjectionLayer(o),i.updateRenderState({layers:[u]}),t.setPixelRatio(1),t.setSize(u.textureWidth,u.textureHeight,!1),x=new Xi(u.textureWidth,u.textureHeight,{format:Vt,type:wt,depthTexture:new fo(u.textureWidth,u.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:v.stencil,colorSpace:t.outputColorSpace,samples:v.antialias?4:0,resolveDepthBuffer:!1===u.ignoreDepthValues,resolveStencilBuffer:!1===u.ignoreDepthValues})}else{const n={antialias:v.antialias,alpha:!0,depth:v.depth,stencil:v.stencil,framebufferScaleFactor:r};d=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:d}),t.setPixelRatio(1),t.setSize(d.framebufferWidth,d.framebufferHeight,!1),x=new Xi(d.framebufferWidth,d.framebufferHeight,{format:Vt,type:wt,colorSpace:t.outputColorSpace,stencilBuffer:v.stencil,resolveDepthBuffer:!1===d.ignoreDepthValues,resolveStencilBuffer:!1===d.ignoreDepthValues})}x.isXRRenderTarget=!0,this.setFoveation(o),l=null,s=await i.requestReferenceSpace(a),F.setContext(i),F.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==i)return i.environmentBlendMode},this.getDepthTexture=function(){return m.getDepthTexture()};const U=new vi,D=new vi;function N(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCamera=function(t){if(null===i)return;let e=t.near,n=t.far;null!==m.texture&&(m.depthNear>0&&(e=m.depthNear),m.depthFar>0&&(n=m.depthFar)),A.near=E.near=T.near=e,A.far=E.far=T.far=n,R===A.near&&C===A.far||(i.updateRenderState({depthNear:A.near,depthFar:A.far}),R=A.near,C=A.far),A.layers.mask=6|t.layers.mask,T.layers.mask=3&A.layers.mask,E.layers.mask=5&A.layers.mask;const r=t.parent,s=A.cameras;N(A,r);for(let t=0;t0&&(t.alphaTest.value=i.alphaTest);const r=e.get(i),s=r.envMap,a=r.envMapRotation;s&&(t.envMap.value=s,gc.copy(a),gc.x*=-1,gc.y*=-1,gc.z*=-1,s.isCubeTexture&&!1===s.isRenderTargetTexture&&(gc.y*=-1,gc.z*=-1),t.envMapRotation.value.setFromMatrix4(vc.makeRotationFromEuler(gc)),t.flipEnvMap.value=s.isCubeTexture&&!1===s.isRenderTargetTexture?-1:1,t.reflectivity.value=i.reflectivity,t.ior.value=i.ior,t.refractionRatio.value=i.refractionRatio),i.lightMap&&(t.lightMap.value=i.lightMap,t.lightMapIntensity.value=i.lightMapIntensity,n(i.lightMap,t.lightMapTransform)),i.aoMap&&(t.aoMap.value=i.aoMap,t.aoMapIntensity.value=i.aoMapIntensity,n(i.aoMap,t.aoMapTransform))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,Us(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,r,s,a,o){r.isMeshBasicMaterial||r.isMeshLambertMaterial?i(t,r):r.isMeshToonMaterial?(i(t,r),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,r)):r.isMeshPhongMaterial?(i(t,r),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,r)):r.isMeshStandardMaterial?(i(t,r),function(t,e){t.metalness.value=e.metalness,e.metalnessMap&&(t.metalnessMap.value=e.metalnessMap,n(e.metalnessMap,t.metalnessMapTransform));t.roughness.value=e.roughness,e.roughnessMap&&(t.roughnessMap.value=e.roughnessMap,n(e.roughnessMap,t.roughnessMapTransform));e.envMap&&(t.envMapIntensity.value=e.envMapIntensity)}(t,r),r.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap,n(e.sheenColorMap,t.sheenColorMapTransform)),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap,n(e.sheenRoughnessMap,t.sheenRoughnessMapTransform)));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap,n(e.clearcoatMap,t.clearcoatMapTransform)),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap,n(e.clearcoatRoughnessMap,t.clearcoatRoughnessMapTransform)),e.clearcoatNormalMap&&(t.clearcoatNormalMap.value=e.clearcoatNormalMap,n(e.clearcoatNormalMap,t.clearcoatNormalMapTransform),t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),1===e.side&&t.clearcoatNormalScale.value.negate()));e.dispersion>0&&(t.dispersion.value=e.dispersion);e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap,n(e.iridescenceMap,t.iridescenceMapTransform)),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap,n(e.iridescenceThicknessMap,t.iridescenceThicknessMapTransform)));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap,n(e.transmissionMap,t.transmissionMapTransform)),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap,n(e.thicknessMap,t.thicknessMapTransform)),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));e.anisotropy>0&&(t.anisotropyVector.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation)),e.anisotropyMap&&(t.anisotropyMap.value=e.anisotropyMap,n(e.anisotropyMap,t.anisotropyMapTransform)));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap,n(e.specularColorMap,t.specularColorMapTransform));e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap,n(e.specularIntensityMap,t.specularIntensityMapTransform))}(t,r,o)):r.isMeshMatcapMaterial?(i(t,r),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,r)):r.isMeshDepthMaterial?i(t,r):r.isMeshDistanceMaterial?(i(t,r),function(t,n){const i=e.get(n).light;t.referencePosition.value.setFromMatrixPosition(i.matrixWorld),t.nearDistance.value=i.shadow.camera.near,t.farDistance.value=i.shadow.camera.far}(t,r)):r.isMeshNormalMaterial?i(t,r):r.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform))}(t,r),r.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,r)):r.isPointsMaterial?function(t,e,i,r){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*r,e.map&&(t.map.value=e.map,n(e.map,t.uvTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r,s,a):r.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r):r.isShadowMaterial?(t.color.value.copy(r.color),t.opacity.value=r.opacity):r.isShaderMaterial&&(r.uniformsNeedUpdate=!1)}}}function xc(t,e,n,i){let r={},s={},a=[];const o=t.getParameter(t.MAX_UNIFORM_BUFFER_BINDINGS);function l(t,e,n,i){const r=t.value,s=e+"_"+n;if(void 0===i[s])return i[s]="number"==typeof r||"boolean"==typeof r?r:r.clone(),!0;{const t=i[s];if("number"==typeof r||"boolean"==typeof r){if(t!==r)return i[s]=r,!0}else if(!1===t.equals(r))return t.copy(r),!0}return!1}function c(t){const e={boundary:0,storage:0};return"number"==typeof t||"boolean"==typeof t?(e.boundary=4,e.storage=4):t.isVector2?(e.boundary=8,e.storage=8):t.isVector3||t.isColor?(e.boundary=16,e.storage=12):t.isVector4?(e.boundary=16,e.storage=16):t.isMatrix3?(e.boundary=48,e.storage=48):t.isMatrix4?(e.boundary=64,e.storage=64):t.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",t),e}function h(e){const n=e.target;n.removeEventListener("dispose",h);const i=a.indexOf(n.__bindingPointIndex);a.splice(i,1),t.deleteBuffer(r[n.id]),delete r[n.id],delete s[n.id]}return{bind:function(t,e){const n=e.program;i.uniformBlockBinding(t,n)},update:function(n,u){let d=r[n.id];void 0===d&&(!function(t){const e=t.uniforms;let n=0;const i=16;for(let t=0,r=e.length;t0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0),u=!!n.morphAttributes.position,d=!!n.morphAttributes.normal,p=!!n.morphAttributes.color;let f=0;i.toneMapped&&(null!==T&&!0!==T.isXRRenderTarget||(f=y.toneMapping));const m=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,g=void 0!==m?m.length:0,_=Q.get(i),x=v.state.lights;if(!0===V&&(!0===k||t!==w)){const e=t===w&&i.id===E;ht.setState(i,t,e)}let M=!1;i.version===_.__version?_.needsLights&&_.lightsStateVersion!==x.state.version||_.outputColorSpace!==o||r.isBatchedMesh&&!1===_.batching?M=!0:r.isBatchedMesh||!0!==_.batching?r.isBatchedMesh&&!0===_.batchingColor&&null===r.colorTexture||r.isBatchedMesh&&!1===_.batchingColor&&null!==r.colorTexture||r.isInstancedMesh&&!1===_.instancing?M=!0:r.isInstancedMesh||!0!==_.instancing?r.isSkinnedMesh&&!1===_.skinning?M=!0:r.isSkinnedMesh||!0!==_.skinning?r.isInstancedMesh&&!0===_.instancingColor&&null===r.instanceColor||r.isInstancedMesh&&!1===_.instancingColor&&null!==r.instanceColor||r.isInstancedMesh&&!0===_.instancingMorph&&null===r.morphTexture||r.isInstancedMesh&&!1===_.instancingMorph&&null!==r.morphTexture||_.envMap!==l||!0===i.fog&&_.fog!==s?M=!0:void 0===_.numClippingPlanes||_.numClippingPlanes===ht.numPlanes&&_.numIntersection===ht.numIntersection?(_.vertexAlphas!==c||_.vertexTangents!==h||_.morphTargets!==u||_.morphNormals!==d||_.morphColors!==p||_.toneMapping!==f||_.morphTargetsCount!==g)&&(M=!0):M=!0:M=!0:M=!0:M=!0:(M=!0,_.__version=i.version);let S=_.currentProgram;!0===M&&(S=jt(i,e,r));let b=!1,A=!1,R=!1;const C=S.getUniforms(),P=_.uniforms;K.useProgram(S.program)&&(b=!0,A=!0,R=!0);i.id!==E&&(E=i.id,A=!0);if(b||w!==t){K.buffers.depth.getReversed()&&!0!==t.reversedDepth&&(t._reversedDepth=!0,t.updateProjectionMatrix()),C.setValue(xt,"projectionMatrix",t.projectionMatrix),C.setValue(xt,"viewMatrix",t.matrixWorldInverse);const e=C.map.cameraPosition;void 0!==e&&e.setValue(xt,W.setFromMatrixPosition(t.matrixWorld)),Z.logarithmicDepthBuffer&&C.setValue(xt,"logDepthBufFC",2/(Math.log(t.far+1)/Math.LN2)),(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial)&&C.setValue(xt,"isOrthographic",!0===t.isOrthographicCamera),w!==t&&(w=t,A=!0,R=!0)}if(r.isSkinnedMesh){C.setOptional(xt,r,"bindMatrix"),C.setOptional(xt,r,"bindMatrixInverse");const t=r.skeleton;t&&(null===t.boneTexture&&t.computeBoneTexture(),C.setValue(xt,"boneTexture",t.boneTexture,tt))}r.isBatchedMesh&&(C.setOptional(xt,r,"batchingTexture"),C.setValue(xt,"batchingTexture",r._matricesTexture,tt),C.setOptional(xt,r,"batchingIdTexture"),C.setValue(xt,"batchingIdTexture",r._indirectTexture,tt),C.setOptional(xt,r,"batchingColorTexture"),null!==r._colorsTexture&&C.setValue(xt,"batchingColorTexture",r._colorsTexture,tt));const I=n.morphAttributes;void 0===I.position&&void 0===I.normal&&void 0===I.color||pt.update(r,n,S);(A||_.receiveShadow!==r.receiveShadow)&&(_.receiveShadow=r.receiveShadow,C.setValue(xt,"receiveShadow",r.receiveShadow));i.isMeshGouraudMaterial&&null!==i.envMap&&(P.envMap.value=l,P.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);i.isMeshStandardMaterial&&null===i.envMap&&null!==e.environment&&(P.envMapIntensity.value=e.environmentIntensity);A&&(C.setValue(xt,"toneMappingExposure",y.toneMappingExposure),_.needsLights&&(N=R,(L=P).ambientLightColor.needsUpdate=N,L.lightProbe.needsUpdate=N,L.directionalLights.needsUpdate=N,L.directionalLightShadows.needsUpdate=N,L.pointLights.needsUpdate=N,L.pointLightShadows.needsUpdate=N,L.spotLights.needsUpdate=N,L.spotLightShadows.needsUpdate=N,L.rectAreaLights.needsUpdate=N,L.hemisphereLights.needsUpdate=N),s&&!0===i.fog&&ot.refreshFogUniforms(P,s),ot.refreshMaterialUniforms(P,i,D,U,v.state.transmissionRenderTarget[t.id]),xl.upload(xt,Yt(_),P,tt));var L,N;i.isShaderMaterial&&!0===i.uniformsNeedUpdate&&(xl.upload(xt,Yt(_),P,tt),i.uniformsNeedUpdate=!1);i.isSpriteMaterial&&C.setValue(xt,"center",r.center);if(C.setValue(xt,"modelViewMatrix",r.modelViewMatrix),C.setValue(xt,"normalMatrix",r.normalMatrix),C.setValue(xt,"modelMatrix",r.matrixWorld),i.isShaderMaterial||i.isRawShaderMaterial){const t=i.uniformsGroups;for(let e=0,n=t.length;e{function n(){i.forEach(function(t){Q.get(t).currentProgram.isReady()&&i.delete(t)}),0!==i.size?setTimeout(n,10):e(t)}null!==J.get("KHR_parallel_shader_compile")?n():setTimeout(n,10)})};let Lt=null;function Ft(){zt.stop()}function Bt(){zt.start()}const zt=new Ur;function Ht(t,e,n,i){if(!1===t.visible)return;if(t.layers.test(e.layers))if(t.isGroup)n=t.renderOrder;else if(t.isLOD)!0===t.autoUpdate&&t.update(e);else if(t.isLight)v.pushLight(t),t.castShadow&&v.pushShadow(t);else if(t.isSprite){if(!t.frustumCulled||H.intersectsSprite(t)){i&&X.setFromMatrixPosition(t.matrixWorld).applyMatrix4(G);const e=st.update(t),r=t.material;r.visible&&g.push(t,e,r,n,X.z,null)}}else if((t.isMesh||t.isLine||t.isPoints)&&(!t.frustumCulled||H.intersectsObject(t))){const e=st.update(t),r=t.material;if(i&&(void 0!==t.boundingSphere?(null===t.boundingSphere&&t.computeBoundingSphere(),X.copy(t.boundingSphere.center)):(null===e.boundingSphere&&e.computeBoundingSphere(),X.copy(e.boundingSphere.center)),X.applyMatrix4(t.matrixWorld).applyMatrix4(G)),Array.isArray(r)){const i=e.groups;for(let s=0,a=i.length;s0&&Gt(r,e,n),s.length>0&&Gt(s,e,n),a.length>0&&Gt(a,e,n),K.buffers.depth.setTest(!0),K.buffers.depth.setMask(!0),K.buffers.color.setMask(!0),K.setPolygonOffset(!1)}function kt(t,e,n,i){if(null!==(!0===n.isScene?n.overrideMaterial:null))return;void 0===v.state.transmissionRenderTarget[i.id]&&(v.state.transmissionRenderTarget[i.id]=new Xi(1,1,{generateMipmaps:!0,type:J.has("EXT_color_buffer_half_float")||J.has("EXT_color_buffer_float")?Ut:wt,minFilter:Tt,samples:4,stencilBuffer:s,resolveDepthBuffer:!1,resolveStencilBuffer:!1,colorSpace:Li.workingColorSpace}));const r=v.state.transmissionRenderTarget[i.id],a=i.viewport||A;r.setSize(a.z*y.transmissionResolutionScale,a.w*y.transmissionResolutionScale);const o=y.getRenderTarget(),l=y.getActiveCubeFace(),c=y.getActiveMipmapLevel();y.setRenderTarget(r),y.getClearColor(P),I=y.getClearAlpha(),I<1&&y.setClearColor(16777215,.5),y.clear(),q&&dt.render(n);const h=y.toneMapping;y.toneMapping=0;const u=i.viewport;if(void 0!==i.viewport&&(i.viewport=void 0),v.setupLightsView(i),!0===V&&ht.setGlobalState(y.clippingPlanes,i),Gt(t,n,i),tt.updateMultisampleRenderTarget(r),tt.updateRenderTargetMipmap(r),!1===J.has("WEBGL_multisampled_render_to_texture")){let t=!1;for(let r=0,s=e.length;r0)for(let e=0,s=n.length;e0&&kt(i,r,t,e),q&&dt.render(t),Vt(g,t,e);null!==T&&0===b&&(tt.updateMultisampleRenderTarget(T),tt.updateRenderTargetMipmap(T)),!0===t.isScene&&t.onAfterRender(y,t,e),vt.resetDefaultState(),E=-1,w=null,x.pop(),x.length>0?(v=x[x.length-1],!0===V&&ht.setGlobalState(y.clippingPlanes,v.state.camera)):v=null,_.pop(),g=_.length>0?_[_.length-1]:null},this.getActiveCubeFace=function(){return S},this.getActiveMipmapLevel=function(){return b},this.getRenderTarget=function(){return T},this.setRenderTargetTextures=function(t,e,n){const i=Q.get(t);i.__autoAllocateDepthBuffer=!1===t.resolveDepthBuffer,!1===i.__autoAllocateDepthBuffer&&(i.__useRenderToTexture=!1),Q.get(t.texture).__webglTexture=e,Q.get(t.depthTexture).__webglTexture=i.__autoAllocateDepthBuffer?void 0:n,i.__hasExternalTextures=!0},this.setRenderTargetFramebuffer=function(t,e){const n=Q.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e};const Kt=xt.createFramebuffer();this.setRenderTarget=function(t,e=0,n=0){T=t,S=e,b=n;let i=!0,r=null,s=!1,a=!1;if(t){const o=Q.get(t);if(void 0!==o.__useDefaultFramebuffer)K.bindFramebuffer(xt.FRAMEBUFFER,null),i=!1;else if(void 0===o.__webglFramebuffer)tt.setupRenderTarget(t);else if(o.__hasExternalTextures)tt.rebindTextures(t,Q.get(t.texture).__webglTexture,Q.get(t.depthTexture).__webglTexture);else if(t.depthBuffer){const e=t.depthTexture;if(o.__boundDepthTexture!==e){if(null!==e&&Q.has(e)&&(t.width!==e.image.width||t.height!==e.image.height))throw new Error("WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.");tt.setupDepthRenderbuffer(t)}}const l=t.texture;(l.isData3DTexture||l.isDataArrayTexture||l.isCompressedArrayTexture)&&(a=!0);const c=Q.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=Array.isArray(c[e])?c[e][n]:c[e],s=!0):r=t.samples>0&&!1===tt.useMultisampledRTT(t)?Q.get(t).__webglMultisampledFramebuffer:Array.isArray(c)?c[n]:c,A.copy(t.viewport),R.copy(t.scissor),C=t.scissorTest}else A.copy(F).multiplyScalar(D).floor(),R.copy(B).multiplyScalar(D).floor(),C=z;0!==n&&(r=Kt);if(K.bindFramebuffer(xt.FRAMEBUFFER,r)&&i&&K.drawBuffers(t,r),K.viewport(A),K.scissor(R),K.setScissorTest(C),s){const i=Q.get(t.texture);xt.framebufferTexture2D(xt.FRAMEBUFFER,xt.COLOR_ATTACHMENT0,xt.TEXTURE_CUBE_MAP_POSITIVE_X+e,i.__webglTexture,n)}else if(a){const i=e;for(let e=0;e=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&(t.textures.length>1&&xt.readBuffer(xt.COLOR_ATTACHMENT0+o),xt.readPixels(e,n,i,r,gt.convert(l),gt.convert(c),s))}finally{const t=null!==T?Q.get(T).__webglFramebuffer:null;K.bindFramebuffer(xt.FRAMEBUFFER,t)}}},this.readRenderTargetPixelsAsync=async function(t,e,n,i,r,s,a,o=0){if(!t||!t.isWebGLRenderTarget)throw new Error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let l=Q.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(l=l[a]),l){if(e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r){K.bindFramebuffer(xt.FRAMEBUFFER,l);const a=t.textures[o],c=a.format,h=a.type;if(!Z.textureFormatReadable(c))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.");if(!Z.textureTypeReadable(h))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.");const u=xt.createBuffer();xt.bindBuffer(xt.PIXEL_PACK_BUFFER,u),xt.bufferData(xt.PIXEL_PACK_BUFFER,s.byteLength,xt.STREAM_READ),t.textures.length>1&&xt.readBuffer(xt.COLOR_ATTACHMENT0+o),xt.readPixels(e,n,i,r,gt.convert(c),gt.convert(h),0);const d=null!==T?Q.get(T).__webglFramebuffer:null;K.bindFramebuffer(xt.FRAMEBUFFER,d);const p=xt.fenceSync(xt.SYNC_GPU_COMMANDS_COMPLETE,0);return xt.flush(),await function(t,e,n){return new Promise(function(i,r){setTimeout(function s(){switch(t.clientWaitSync(e,t.SYNC_FLUSH_COMMANDS_BIT,0)){case t.WAIT_FAILED:r();break;case t.TIMEOUT_EXPIRED:setTimeout(s,n);break;default:i()}},n)})}(xt,p,4),xt.bindBuffer(xt.PIXEL_PACK_BUFFER,u),xt.getBufferSubData(xt.PIXEL_PACK_BUFFER,0,s),xt.deleteBuffer(u),xt.deleteSync(p),s}throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.")}},this.copyFramebufferToTexture=function(t,e=null,n=0){const i=Math.pow(2,-n),r=Math.floor(t.image.width*i),s=Math.floor(t.image.height*i),a=null!==e?e.x:0,o=null!==e?e.y:0;tt.setTexture2D(t,0),xt.copyTexSubImage2D(xt.TEXTURE_2D,n,0,0,a,o,r,s),K.unbindTexture()};const $t=xt.createFramebuffer(),Qt=xt.createFramebuffer();this.copyTextureToTexture=function(t,e,n=null,i=null,r=0,s=null){let a,o,l,c,h,u,d,p,f;null===s&&(0!==r?(Ri("WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels."),s=r,r=0):s=0);const m=t.isCompressedTexture?t.mipmaps[s]:t.image;if(null!==n)a=n.max.x-n.min.x,o=n.max.y-n.min.y,l=n.isBox3?n.max.z-n.min.z:1,c=n.min.x,h=n.min.y,u=n.isBox3?n.min.z:0;else{const e=Math.pow(2,-r);a=Math.floor(m.width*e),o=Math.floor(m.height*e),l=t.isDataArrayTexture?m.depth:t.isData3DTexture?Math.floor(m.depth*e):1,c=0,h=0,u=0}null!==i?(d=i.x,p=i.y,f=i.z):(d=0,p=0,f=0);const g=gt.convert(e.format),v=gt.convert(e.type);let _;e.isData3DTexture?(tt.setTexture3D(e,0),_=xt.TEXTURE_3D):e.isDataArrayTexture||e.isCompressedArrayTexture?(tt.setTexture2DArray(e,0),_=xt.TEXTURE_2D_ARRAY):(tt.setTexture2D(e,0),_=xt.TEXTURE_2D),xt.pixelStorei(xt.UNPACK_FLIP_Y_WEBGL,e.flipY),xt.pixelStorei(xt.UNPACK_PREMULTIPLY_ALPHA_WEBGL,e.premultiplyAlpha),xt.pixelStorei(xt.UNPACK_ALIGNMENT,e.unpackAlignment);const x=xt.getParameter(xt.UNPACK_ROW_LENGTH),y=xt.getParameter(xt.UNPACK_IMAGE_HEIGHT),M=xt.getParameter(xt.UNPACK_SKIP_PIXELS),S=xt.getParameter(xt.UNPACK_SKIP_ROWS),b=xt.getParameter(xt.UNPACK_SKIP_IMAGES);xt.pixelStorei(xt.UNPACK_ROW_LENGTH,m.width),xt.pixelStorei(xt.UNPACK_IMAGE_HEIGHT,m.height),xt.pixelStorei(xt.UNPACK_SKIP_PIXELS,c),xt.pixelStorei(xt.UNPACK_SKIP_ROWS,h),xt.pixelStorei(xt.UNPACK_SKIP_IMAGES,u);const T=t.isDataArrayTexture||t.isData3DTexture,E=e.isDataArrayTexture||e.isData3DTexture;if(t.isDepthTexture){const n=Q.get(t),i=Q.get(e),m=Q.get(n.__renderTarget),g=Q.get(i.__renderTarget);K.bindFramebuffer(xt.READ_FRAMEBUFFER,m.__webglFramebuffer),K.bindFramebuffer(xt.DRAW_FRAMEBUFFER,g.__webglFramebuffer);for(let n=0;n0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e.object.backgroundRotation=this.backgroundRotation.toArray(),1!==this.environmentIntensity&&(e.object.environmentIntensity=this.environmentIntensity),e.object.environmentRotation=this.environmentRotation.toArray(),e}}class bc extends Gr{constructor(t,e,n,i=1){super(t,e,n),this.isInstancedBufferAttribute=!0,this.meshPerAttribute=i}copy(t){return super.copy(t),this.meshPerAttribute=t.meshPerAttribute,this}toJSON(){const t=super.toJSON();return t.meshPerAttribute=this.meshPerAttribute,t.isInstancedBufferAttribute=!0,t}}class Tc extends ki{constructor(t=null,e=1,n=1,i,r,s,a,o,l=1003,c=1003,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isDataTexture=!0,this.image={data:t,width:e,height:n},this.generateMipmaps=!1,this.flipY=!1,this.unpackAlignment=1}}const Ec=new Er,wc=new Er,Ac=[],Rc=new $i,Cc=new Er,Pc=new ga,Ic=new gr;class Lc extends ga{constructor(t,e,n){super(t,e),this.isInstancedMesh=!0,this.instanceMatrix=new bc(new Float32Array(16*n),16),this.instanceColor=null,this.morphTexture=null,this.count=n,this.boundingBox=null,this.boundingSphere=null;for(let t=0;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;ti)return;zc.applyMatrix4(t.matrixWorld);const l=e.ray.origin.distanceTo(zc);return le.far?void 0:{distance:l,point:Hc.clone().applyMatrix4(t.matrixWorld),index:a,face:null,faceIndex:null,barycoord:null,object:t}}const Gc=new vi,Wc=new vi;class Xc extends Vc{constructor(t,e){super(t,e),this.isLineSegments=!0,this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(null===t.index){const e=t.attributes.position,n=[];for(let t=0,i=e.count;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,faceIndex:null,barycoord:null,object:a})}}class th extends ki{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}}class eh extends ws{constructor(t=1,e=1,n=4,i=8,r=1){super(),this.type="CapsuleGeometry",this.parameters={radius:t,height:e,capSegments:n,radialSegments:i,heightSegments:r},e=Math.max(0,e),n=Math.max(1,Math.floor(n)),i=Math.max(3,Math.floor(i)),r=Math.max(1,Math.floor(r));const s=[],a=[],o=[],l=[],c=e/2,h=Math.PI/2*t,u=e,d=2*h+u,p=2*n+r,f=i+1,m=new vi,g=new vi;for(let v=0;v<=p;v++){let _=0,x=0,y=0,M=0;if(v<=n){const e=v/n,i=e*Math.PI/2;x=-c-t*Math.cos(i),y=t*Math.sin(i),M=-t*Math.cos(i),_=e*h}else if(v<=n+r){const i=(v-n)/r;x=i*e-c,y=t,M=0,_=h+i*u}else{const e=(v-n-r)/n,i=e*Math.PI/2;x=c+t*Math.sin(i),y=t*Math.cos(i),M=t*Math.sin(i),_=h+u+e*h}const S=Math.max(0,Math.min(1,_/d));let b=0;0===v?b=.5/i:v===p&&(b=-.5/i);for(let t=0;t<=i;t++){const e=t/i,n=e*Math.PI*2,r=Math.sin(n),s=Math.cos(n);g.x=-y*s,g.y=x,g.z=y*r,a.push(g.x,g.y,g.z),m.set(-y*s,M,y*r),m.normalize(),o.push(m.x,m.y,m.z),l.push(e+b,S)}if(v>0){const t=(v-1)*f;for(let e=0;e0||0!==i)&&(c.push(s,a,l),_+=3),(e>0||i!==r-1)&&(c.push(a,o,l),_+=3)}l.addGroup(g,_,0),g+=_}(),!1===s&&(t>0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new $r(h,3)),this.setAttribute("normal",new $r(u,3)),this.setAttribute("uv",new $r(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new ih(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class rh extends ih{constructor(t=1,e=1,n=32,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new rh(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class sh extends ws{constructor(t=[],e=[],n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new $r(r,3)),this.setAttribute("normal",new $r(r.slice(),3)),this.setAttribute("uv",new $r(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new sh(t.vertices,t.indices,t.radius,t.details)}}class ah extends sh{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-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,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new ah(t.radius,t.detail)}}const oh=new vi,lh=new vi,ch=new vi,hh=new ia;class uh extends ws{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const n=4,i=Math.pow(10,n),r=Math.cos(Gn*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new mi:new vi);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e=!1){const n=new vi,i=[],r=[],s=[],a=new vi,o=new Er;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new vi)}r[0]=new vi,s[0]=new vi;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(jn(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(jn(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.7,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class ph extends dh{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e=new mi){const n=e,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(gh.subVectors(i[0],i[1]).add(i[0]),a=gh);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(Mh(a,o.x,l.x,c.x,h.x),Mh(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=n){const t=i[r]-n,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class Dh extends Uh{constructor(t){super(t),this.uuid=Xn(),this.type="Shape",this.holes=[]}getPointsHoles(t){const e=[];for(let n=0,i=this.holes.length;n80*n){o=1/0,l=1/0;let e=-1/0,i=-1/0;for(let s=n;se&&(e=n),r>i&&(i=r)}c=Math.max(e-o,i-l),c=0!==c?32767/c:0}return Bh(s,a,n,o,l,c,0),a}function Oh(t,e,n,i,r){let s;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(let r=e;r=e;r-=i)s=ru(r/i|0,t[r],t[r+1],s);return s&&$h(s,s.next)&&(su(s),s=s.next),s}function Fh(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!$h(i,i.next)&&0!==Kh(i.prev,i,i.next))i=i.next;else{if(su(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Bh(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{0===r.z&&(r.z=jh(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n=1;do{let i,r=t;t=null;let s=null;for(e=0;r;){e++;let a=r,o=0;for(let t=0;t0||l>0&&a;)0!==o&&(0===l||!a||r.z<=a.z)?(i=r,r=r.nextZ,o--):(i=a,a=a.nextZ,l--),s?s.nextZ=i:t=i,i.prevZ=s,s=i;r=a}s.nextZ=null,n*=2}while(e>1)}(r)}(t,i,r,s);let o=t;for(;t.prev!==t.next;){const l=t.prev,c=t.next;if(s?Hh(t,i,r,s):zh(t))e.push(l.i,t.i,c.i),su(t),t=c.next,o=c.next;else if((t=c)===o){a?1===a?Bh(t=Vh(Fh(t),e),e,n,i,r,s,2):2===a&&kh(t,e,n,i,r,s):Bh(Fh(t),e,n,i,r,s,1);break}}}function zh(t){const e=t.prev,n=t,i=t.next;if(Kh(e,n,i)>=0)return!1;const r=e.x,s=n.x,a=i.x,o=e.y,l=n.y,c=i.y,h=Math.min(r,s,a),u=Math.min(o,l,c),d=Math.max(r,s,a),p=Math.max(o,l,c);let f=i.next;for(;f!==e;){if(f.x>=h&&f.x<=d&&f.y>=u&&f.y<=p&&Jh(r,o,s,l,a,c,f.x,f.y)&&Kh(f.prev,f,f.next)>=0)return!1;f=f.next}return!0}function Hh(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Kh(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=Math.min(o,l,c),f=Math.min(h,u,d),m=Math.max(o,l,c),g=Math.max(h,u,d),v=jh(p,f,e,n,i),_=jh(m,g,e,n,i);let x=t.prevZ,y=t.nextZ;for(;x&&x.z>=v&&y&&y.z<=_;){if(x.x>=p&&x.x<=m&&x.y>=f&&x.y<=g&&x!==r&&x!==a&&Jh(o,h,l,u,c,d,x.x,x.y)&&Kh(x.prev,x,x.next)>=0)return!1;if(x=x.prevZ,y.x>=p&&y.x<=m&&y.y>=f&&y.y<=g&&y!==r&&y!==a&&Jh(o,h,l,u,c,d,y.x,y.y)&&Kh(y.prev,y,y.next)>=0)return!1;y=y.nextZ}for(;x&&x.z>=v;){if(x.x>=p&&x.x<=m&&x.y>=f&&x.y<=g&&x!==r&&x!==a&&Jh(o,h,l,u,c,d,x.x,x.y)&&Kh(x.prev,x,x.next)>=0)return!1;x=x.prevZ}for(;y&&y.z<=_;){if(y.x>=p&&y.x<=m&&y.y>=f&&y.y<=g&&y!==r&&y!==a&&Jh(o,h,l,u,c,d,y.x,y.y)&&Kh(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Vh(t,e){let n=t;do{const i=n.prev,r=n.next.next;!$h(i,r)&&Qh(i,n,n.next,r)&&nu(i,r)&&nu(r,i)&&(e.push(i.i,n.i,r.i),su(n),su(n.next),n=t=r),n=n.next}while(n!==t);return Fh(n)}function kh(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Zh(a,t)){let o=iu(a,t);return a=Fh(a,a.next),o=Fh(o,o.next),Bh(a,e,n,i,r,s,0),void Bh(o,e,n,i,r,s,0)}t=t.next}a=a.next}while(a!==t)}function Gh(t,e){let n=t.x-e.x;if(0===n&&(n=t.y-e.y,0===n)){n=(t.next.y-t.y)/(t.next.x-t.x)-(e.next.y-e.y)/(e.next.x-e.x)}return n}function Wh(t,e){const n=function(t,e){let n=e;const i=t.x,r=t.y;let s,a=-1/0;if($h(t,n))return n;do{if($h(t,n.next))return n.next;if(r<=n.y&&r>=n.next.y&&n.next.y!==n.y){const t=n.x+(r-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(t<=i&&t>a&&(a=t,s=n.x=n.x&&n.x>=l&&i!==n.x&&Yh(rs.x||n.x===s.x&&Xh(s,n)))&&(s=n,h=e)}n=n.next}while(n!==o);return s}(t,e);if(!n)return e;const i=iu(n,t);return Fh(i,i.next),Fh(n,n.next)}function Xh(t,e){return Kh(t.prev,t,e.prev)<0&&Kh(e.next,t,t.next)<0}function jh(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function qh(t){let e=t,n=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(i-o)>=(n-a)*(e-o)&&(n-a)*(s-o)>=(r-a)*(i-o)}function Jh(t,e,n,i,r,s,a,o){return!(t===a&&e===o)&&Yh(t,e,n,i,r,s,a,o)}function Zh(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&Qh(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(nu(t,e)&&nu(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Kh(t.prev,t,e.prev)||Kh(t,e.prev,e))||$h(t,e)&&Kh(t.prev,t,t.next)>0&&Kh(e.prev,e,e.next)>0)}function Kh(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function $h(t,e){return t.x===e.x&&t.y===e.y}function Qh(t,e,n,i){const r=eu(Kh(t,e,n)),s=eu(Kh(t,e,i)),a=eu(Kh(n,i,t)),o=eu(Kh(n,i,e));return r!==s&&a!==o||(!(0!==r||!tu(t,n,e))||(!(0!==s||!tu(t,i,e))||(!(0!==a||!tu(n,t,i))||!(0!==o||!tu(n,e,i)))))}function tu(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function eu(t){return t>0?1:t<0?-1:0}function nu(t,e){return Kh(t.prev,t,t.next)<0?Kh(t,e,t.next)>=0&&Kh(t,t.prev,e)>=0:Kh(t,e,t.prev)<0||Kh(t,t.next,e)<0}function iu(t,e){const n=au(t.i,t.x,t.y),i=au(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function ru(t,e,n,i){const r=au(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function su(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function au(t,e,n){return{i:t,x:e,y:n,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}class ou{static triangulate(t,e,n=2){return Nh(t,e,n)}}class lu{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function hu(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,f=e.y+a/u,m=((n.x-c/d-p)*c-(n.y+l/d-f)*l)/(a*c-o*l);i=p+a*m-t.x,r=f+o*m-t.y;const g=i*i+r*r;if(g<=2)return new mi(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new mi(i/s,r/s)}const I=[];for(let t=0,e=A.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=A.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get dispersion(){return this._dispersion}set dispersion(t){this._dispersion>0!=t>0&&this.version++,this._dispersion=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.dispersion=t.dispersion,this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Iu extends Ps{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Zi(16777215),this.specular=new Zi(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Zi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new mi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new es,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Lu extends Ps{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Zi(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Zi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new mi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Uu extends Ps{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new mi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Du extends Ps{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Zi(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Zi(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new mi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new es,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Nu extends Ps{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Zi(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new mi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Ou extends Uc{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}const Fu={enabled:!1,files:{},add:function(t,e){!1!==this.enabled&&(this.files[t]=e)},get:function(t){if(!1!==this.enabled)return this.files[t]},remove:function(t){delete this.files[t]},clear:function(){this.files={}}};class Bu{constructor(t,e,n){const i=this;let r,s=!1,a=0,o=0;const l=[];this.onStart=void 0,this.onLoad=t,this.onProgress=e,this.onError=n,this.abortController=new AbortController,this.itemStart=function(t){o++,!1===s&&void 0!==i.onStart&&i.onStart(t,a,o),s=!0},this.itemEnd=function(t){a++,void 0!==i.onProgress&&i.onProgress(t,a,o),a===o&&(s=!1,void 0!==i.onLoad&&i.onLoad())},this.itemError=function(t){void 0!==i.onError&&i.onError(t)},this.resolveURL=function(t){return r?r(t):t},this.setURLModifier=function(t){return r=t,this},this.addHandler=function(t,e){return l.push(t,e),this},this.removeHandler=function(t){const e=l.indexOf(t);return-1!==e&&l.splice(e,2),this},this.getHandler=function(t){for(let e=0,n=l.length;e{e&&e(r),this.manager.itemEnd(t)},0),r;if(void 0!==Vu[t])return void Vu[t].push({onLoad:e,onProgress:n,onError:i});Vu[t]=[],Vu[t].push({onLoad:e,onProgress:n,onError:i});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin",signal:"function"==typeof AbortSignal.any?AbortSignal.any([this._abortController.signal,this.manager.abortController.signal]):this._abortController.signal}),a=this.mimeType,o=this.responseType;fetch(s).then(e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const n=Vu[t],i=e.body.getReader(),r=e.headers.get("X-File-Size")||e.headers.get("Content-Length"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){i.read().then(({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=n.length;t{t.error(e)})}()}});return new Response(l)}throw new ku(`fetch for "${e.url}" responded with ${e.status}: ${e.statusText}`,e)}).then(t=>{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then(t=>(new DOMParser).parseFromString(t,a));case"json":return t.json();default:if(""===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),n=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(n);return t.arrayBuffer().then(t=>i.decode(t))}}}).then(e=>{Fu.add(`file:${t}`,e);const n=Vu[t];delete Vu[t];for(let t=0,i=n.length;t{const n=Vu[t];if(void 0===n)throw this.manager.itemError(t),e;delete Vu[t];for(let t=0,i=n.length;t{this.manager.itemEnd(t)}),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}abort(){return this._abortController.abort(),this._abortController=new AbortController,this}}class Wu extends ki{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class Xu extends Hu{constructor(t){super(t)}load(t,e,n,i){const r=this,s=[],a=new Wu,o=new Gu(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],function(t){const n=r.parse(t,!0);s[c]={width:n.width,height:n.height,format:n.format,mipmaps:n.mipmaps},l+=1,6===l&&(1===n.mipmapCount&&(a.minFilter=Mt),a.image=s,a.format=n.format,a.needsUpdate=!0,e&&e(a))},n,i)}if(Array.isArray(t))for(let e=0,n=t.length;e=r.length&&r.push({start:-1,count:-1,z:-1,index:-1});const a=r[this.index];s.push(a),this.index++,a.start=t,a.count=e,a.z=n,a.index=i}reset(){this.list.length=0,this.index=0}}const rd=new Er,sd=new Zi(1,1,1),ad=new Tr,od=new Qu,ld=new $i,cd=new gr,hd=new vi,ud=new vi,dd=new vi,pd=new id,fd=new ga,md=[];function gd(t,e,n=0){const i=e.itemSize;if(t.isInterleavedBufferAttribute||t.array.constructor!==e.array.constructor){const r=t.count;for(let s=0;s65535?new Uint32Array(i):new Uint16Array(i);e.setIndex(new Gr(t,1))}this._geometryInitialized=!0}}_validateGeometry(t){const e=this.geometry;if(Boolean(t.getIndex())!==Boolean(e.getIndex()))throw new Error('THREE.BatchedMesh: All geometries must consistently have "index".');for(const n in e.attributes){if(!t.hasAttribute(n))throw new Error(`THREE.BatchedMesh: Added geometry missing "${n}". All geometries must have consistent attributes.`);const i=t.getAttribute(n),r=e.getAttribute(n);if(i.itemSize!==r.itemSize||i.normalized!==r.normalized)throw new Error("THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.")}}validateInstanceId(t){const e=this._instanceInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid instanceId ${t}. Instance is either out of range or has been deleted.`)}validateGeometryId(t){const e=this._geometryInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid geometryId ${t}. Geometry is either out of range or has been deleted.`)}setCustomSort(t){return this.customSort=t,this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new $i);const t=this.boundingBox,e=this._instanceInfo;t.makeEmpty();for(let n=0,i=e.length;n=this.maxInstanceCount&&0===this._availableInstanceIds.length)throw new Error("THREE.BatchedMesh: Maximum item count reached.");const e={visible:!0,active:!0,geometryIndex:t};let n=null;this._availableInstanceIds.length>0?(this._availableInstanceIds.sort(td),n=this._availableInstanceIds.shift(),this._instanceInfo[n]=e):(n=this._instanceInfo.length,this._instanceInfo.push(e));const i=this._matricesTexture;rd.identity().toArray(i.image.data,16*n),i.needsUpdate=!0;const r=this._colorsTexture;return r&&(sd.toArray(r.image.data,4*n),r.needsUpdate=!0),this._visibilityChanged=!0,n}addGeometry(t,e=-1,n=-1){this._initializeGeometry(t),this._validateGeometry(t);const i={vertexStart:-1,vertexCount:-1,reservedVertexCount:-1,indexStart:-1,indexCount:-1,reservedIndexCount:-1,start:-1,count:-1,boundingBox:null,boundingSphere:null,active:!0},r=this._geometryInfo;i.vertexStart=this._nextVertexStart,i.reservedVertexCount=-1===e?t.getAttribute("position").count:e;const s=t.getIndex();if(null!==s&&(i.indexStart=this._nextIndexStart,i.reservedIndexCount=-1===n?s.count:n),-1!==i.indexStart&&i.indexStart+i.reservedIndexCount>this._maxIndexCount||i.vertexStart+i.reservedVertexCount>this._maxVertexCount)throw new Error("THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.");let a;return this._availableGeometryIds.length>0?(this._availableGeometryIds.sort(td),a=this._availableGeometryIds.shift(),r[a]=i):(a=this._geometryCount,this._geometryCount++,r.push(i)),this.setGeometryAt(a,t),this._nextIndexStart=i.indexStart+i.reservedIndexCount,this._nextVertexStart=i.vertexStart+i.reservedVertexCount,a}setGeometryAt(t,e){if(t>=this._geometryCount)throw new Error("THREE.BatchedMesh: Maximum geometry count reached.");this._validateGeometry(e);const n=this.geometry,i=null!==n.getIndex(),r=n.getIndex(),s=e.getIndex(),a=this._geometryInfo[t];if(i&&s.count>a.reservedIndexCount||e.attributes.position.count>a.reservedVertexCount)throw new Error("THREE.BatchedMesh: Reserved space not large enough for provided geometry.");const o=a.vertexStart,l=a.reservedVertexCount;a.vertexCount=e.getAttribute("position").count;for(const t in n.attributes){const i=e.getAttribute(t),r=n.getAttribute(t);gd(i,r,o);const s=i.itemSize;for(let t=i.count,e=l;t=e.length||!1===e[t].active)return this;const n=this._instanceInfo;for(let e=0,i=n.length;ee).sort((t,e)=>n[t].vertexStart-n[e].vertexStart),r=this.geometry;for(let s=0,a=n.length;s=this._geometryCount)return null;const n=this.geometry,i=this._geometryInfo[t];if(null===i.boundingBox){const t=new $i,e=n.index,r=n.attributes.position;for(let n=i.start,s=i.start+i.count;n=this._geometryCount)return null;const n=this.geometry,i=this._geometryInfo[t];if(null===i.boundingSphere){const e=new gr;this.getBoundingBoxAt(t,ld),ld.getCenter(e.center);const r=n.index,s=n.attributes.position;let a=0;for(let t=i.start,n=i.start+i.count;tt.active);if(Math.max(...n.map(t=>t.vertexStart+t.reservedVertexCount))>t)throw new Error(`BatchedMesh: Geometry vertex values are being used outside the range ${e}. Cannot shrink further.`);if(this.geometry.index){if(Math.max(...n.map(t=>t.indexStart+t.reservedIndexCount))>e)throw new Error(`BatchedMesh: Geometry index values are being used outside the range ${e}. Cannot shrink further.`)}const i=this.geometry;i.dispose(),this._maxVertexCount=t,this._maxIndexCount=e,this._geometryInitialized&&(this._geometryInitialized=!1,this.geometry=new ws,this._initializeGeometry(i));const r=this.geometry;i.index&&vd(i.index.array,r.index.array);for(const t in i.attributes)vd(i.attributes[t].array,r.attributes[t].array)}raycast(t,e){const n=this._instanceInfo,i=this._geometryInfo,r=this.matrixWorld,s=this.geometry;fd.material=this.material,fd.geometry.index=s.index,fd.geometry.attributes=s.attributes,null===fd.geometry.boundingBox&&(fd.geometry.boundingBox=new $i),null===fd.geometry.boundingSphere&&(fd.geometry.boundingSphere=new gr);for(let s=0,a=n.length;s({...t,boundingBox:null!==t.boundingBox?t.boundingBox.clone():null,boundingSphere:null!==t.boundingSphere?t.boundingSphere.clone():null})),this._instanceInfo=t._instanceInfo.map(t=>({...t})),this._availableInstanceIds=t._availableInstanceIds.slice(),this._availableGeometryIds=t._availableGeometryIds.slice(),this._nextIndexStart=t._nextIndexStart,this._nextVertexStart=t._nextVertexStart,this._geometryCount=t._geometryCount,this._maxInstanceCount=t._maxInstanceCount,this._maxVertexCount=t._maxVertexCount,this._maxIndexCount=t._maxIndexCount,this._geometryInitialized=t._geometryInitialized,this._multiDrawCounts=t._multiDrawCounts.slice(),this._multiDrawStarts=t._multiDrawStarts.slice(),this._indirectTexture=t._indirectTexture.clone(),this._indirectTexture.image.data=this._indirectTexture.image.data.slice(),this._matricesTexture=t._matricesTexture.clone(),this._matricesTexture.image.data=this._matricesTexture.image.data.slice(),null!==this._colorsTexture&&(this._colorsTexture=t._colorsTexture.clone(),this._colorsTexture.image.data=this._colorsTexture.image.data.slice()),this}dispose(){this.geometry.dispose(),this._matricesTexture.dispose(),this._matricesTexture=null,this._indirectTexture.dispose(),this._indirectTexture=null,null!==this._colorsTexture&&(this._colorsTexture.dispose(),this._colorsTexture=null)}onBeforeRender(t,e,n,i,r){if(!this._visibilityChanged&&!this.perObjectFrustumCulled&&!this.sortObjects)return;const s=i.getIndex(),a=null===s?1:s.array.BYTES_PER_ELEMENT,o=this._instanceInfo,l=this._multiDrawStarts,c=this._multiDrawCounts,h=this._geometryInfo,u=this.perObjectFrustumCulled,d=this._indirectTexture,p=d.image.data,f=n.isArrayCamera?od:ad;u&&!n.isArrayCamera&&(rd.multiplyMatrices(n.projectionMatrix,n.matrixWorldInverse).multiply(this.matrixWorld),ad.setFromProjectionMatrix(rd,n.coordinateSystem,n.reversedDepth));let m=0;if(this.sortObjects){rd.copy(this.matrixWorld).invert(),hd.setFromMatrixPosition(n.matrixWorld).applyMatrix4(rd),ud.set(0,0,-1).transformDirection(n.matrixWorld).transformDirection(rd);for(let t=0,e=o.length;tt.far||e.push({distance:o,point:bd.clone(),uv:ia.getInterpolation(bd,Cd,Pd,Id,Ld,Ud,Dd,new mi),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Od(t,e,n,i,r,s){wd.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(Ad.x=s*wd.x-r*wd.y,Ad.y=r*wd.x+s*wd.y):Ad.copy(wd),t.copy(e),t.x+=Ad.x,t.y+=Ad.y,t.applyMatrix4(Rd)}const Fd=new vi,Bd=new vi;class zd extends _s{constructor(){super(),this.isLOD=!0,this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){Fd.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(Fd);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Fd.setFromMatrixPosition(t.matrixWorld),Bd.setFromMatrixPosition(this.matrixWorld);const n=Fd.distanceTo(Bd)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;i=r)){const a=e[1];t=r)break e}s=n,n=0;break n}break t}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=n.slice(r,s),this.values=this.values.slice(r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!==0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&(a=i,ArrayBuffer.isView(a)&&!(a instanceof DataView)))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}var a;return t}optimize(){const t=this.times.slice(),e=this.values.slice(),n=this.getValueSize(),i=this.getInterpolation()===Ue,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=t.slice(0,s),this.values=e.slice(0,s*n)):(this.times=t,this.values=e),this}clone(){const t=this.times.slice(),e=this.values.slice(),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}Rp.prototype.ValueTypeName="",Rp.prototype.TimeBufferType=Float32Array,Rp.prototype.ValueBufferType=Float32Array,Rp.prototype.DefaultInterpolation=Le;class Cp extends Rp{constructor(t,e,n){super(t,e,n)}}Cp.prototype.ValueTypeName="bool",Cp.prototype.ValueBufferType=Array,Cp.prototype.DefaultInterpolation=Ie,Cp.prototype.InterpolantFactoryMethodLinear=void 0,Cp.prototype.InterpolantFactoryMethodSmooth=void 0;class Pp extends Rp{constructor(t,e,n,i){super(t,e,n,i)}}Pp.prototype.ValueTypeName="color";class Ip extends Rp{constructor(t,e,n,i){super(t,e,n,i)}}Ip.prototype.ValueTypeName="number";class Lp extends Tp{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)gi.slerpFlat(r,0,s,l-a,s,l,o);return r}}class Up extends Rp{constructor(t,e,n,i){super(t,e,n,i)}InterpolantFactoryMethodLinear(t){return new Lp(this.times,this.values,this.getValueSize(),t)}}Up.prototype.ValueTypeName="quaternion",Up.prototype.InterpolantFactoryMethodSmooth=void 0;class Dp extends Rp{constructor(t,e,n){super(t,e,n)}}Dp.prototype.ValueTypeName="string",Dp.prototype.ValueBufferType=Array,Dp.prototype.DefaultInterpolation=Ie,Dp.prototype.InterpolantFactoryMethodLinear=void 0,Dp.prototype.InterpolantFactoryMethodSmooth=void 0;class Np extends Rp{constructor(t,e,n,i){super(t,e,n,i)}}Np.prototype.ValueTypeName="vector";class Op{constructor(t="",e=-1,n=[],i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=Xn(),this.userData={},this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Fp(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r.userData=JSON.parse(t.userData||"{}"),r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode,userData:JSON.stringify(t.userData)};for(let t=0,i=n.length;t!==i;++t)e.push(Rp.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(console.warn("THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185"),!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];bp(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new Zi).setHex(r.value);break;case"v2":i.uniforms[e].value=(new mi).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new vi).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new Gi).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new yi).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new Er).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new mi).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=n(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapRotation&&i.envMapRotation.fromArray(t.envMapRotation),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new mi).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=n(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=n(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=n(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=n(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=n(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}createMaterialFromType(t){return Bp.createMaterialFromType(t)}static createMaterialFromType(t){return new{ShadowMaterial:wu,SpriteMaterial:Au,RawShaderMaterial:Ru,ShaderMaterial:Ns,PointsMaterial:qc,MeshPhysicalMaterial:Pu,MeshStandardMaterial:Cu,MeshPhongMaterial:Iu,MeshToonMaterial:Lu,MeshNormalMaterial:Uu,MeshLambertMaterial:Du,MeshDepthMaterial:ec,MeshDistanceMaterial:nc,MeshBasicMaterial:ra,MeshMatcapMaterial:Nu,LineDashedMaterial:Ou,LineBasicMaterial:Uc,Material:Ps}[t]}}class zp{static extractUrlBase(t){const e=t.lastIndexOf("/");return-1===e?"./":t.slice(0,e+1)}static resolveURL(t,e){return"string"!=typeof t||""===t?"":(/^https?:\/\//i.test(e)&&/^\//.test(t)&&(e=e.replace(/(^https?:\/\/[^\/]+).*/i,"$1")),/^(https?:)?\/\//i.test(t)||/^data:.*,.*$/i.test(t)||/^blob:.*$/i.test(t)?t:e+t)}}class Hp extends ws{constructor(){super(),this.isInstancedBufferGeometry=!0,this.type="InstancedBufferGeometry",this.instanceCount=1/0}copy(t){return super.copy(t),this.instanceCount=t.instanceCount,this}toJSON(){const t=super.toJSON();return t.instanceCount=this.instanceCount,t.isInstancedBufferGeometry=!0,t}}class Vp extends Hu{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new Gu(r.manager);s.setPath(r.path),s.setRequestHeader(r.requestHeader),s.setWithCredentials(r.withCredentials),s.load(t,function(n){try{e(r.parse(JSON.parse(n)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}},n,i)}parse(t){const e={},n={};function i(t,i){if(void 0!==e[i])return e[i];const r=t.interleavedBuffers[i],s=function(t,e){if(void 0!==n[e])return n[e];const i=t.arrayBuffers,r=i[e],s=new Uint32Array(r).buffer;return n[e]=s,s}(t,r.buffer),a=Ti(r.type,s),o=new xd(a,r.stride);return o.uuid=r.uuid,e[i]=o,o}const r=t.isInstancedBufferGeometry?new Hp:new ws,s=t.data.index;if(void 0!==s){const t=Ti(s.type,s.array);r.setIndex(new Gr(t,1))}const a=t.data.attributes;for(const e in a){const n=a[e];let s;if(n.isInterleavedBufferAttribute){const e=i(t.data,n.data);s=new Md(e,n.itemSize,n.offset,n.normalized)}else{const t=Ti(n.type,n.array);s=new(n.isInstancedBufferAttribute?bc:Gr)(t,n.itemSize,n.normalized)}void 0!==n.name&&(s.name=n.name),void 0!==n.usage&&s.setUsage(n.usage),r.setAttribute(e,s)}const o=t.data.morphAttributes;if(o)for(const e in o){const n=o[e],s=[];for(let e=0,r=n.length;e0){const n=new Bu(e);r=new qu(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new qu(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e{let e=null,n=null;return void 0!==t.boundingBox&&(e=(new $i).fromJSON(t.boundingBox)),void 0!==t.boundingSphere&&(n=(new gr).fromJSON(t.boundingSphere)),{...t,boundingBox:e,boundingSphere:n}}),s._instanceInfo=t.instanceInfo,s._availableInstanceIds=t._availableInstanceIds,s._availableGeometryIds=t._availableGeometryIds,s._nextIndexStart=t.nextIndexStart,s._nextVertexStart=t.nextVertexStart,s._geometryCount=t.geometryCount,s._maxInstanceCount=t.maxInstanceCount,s._maxVertexCount=t.maxVertexCount,s._maxIndexCount=t.maxIndexCount,s._geometryInitialized=t.geometryInitialized,s._matricesTexture=h(t.matricesTexture.uuid),s._indirectTexture=h(t.indirectTexture.uuid),void 0!==t.colorsTexture&&(s._colorsTexture=h(t.colorsTexture.uuid)),void 0!==t.boundingSphere&&(s.boundingSphere=(new gr).fromJSON(t.boundingSphere)),void 0!==t.boundingBox&&(s.boundingBox=(new $i).fromJSON(t.boundingBox));break;case"LOD":s=new zd;break;case"Line":s=new Vc(l(t.geometry),c(t.material));break;case"LineLoop":s=new jc(l(t.geometry),c(t.material));break;case"LineSegments":s=new Xc(l(t.geometry),c(t.material));break;case"PointCloud":case"Points":s=new $c(l(t.geometry),c(t.material));break;case"Sprite":s=new Nd(c(t.material));break;case"Group":s=new hc;break;case"Bone":s=new Zd;break;default:s=new _s}if(s.uuid=t.uuid,void 0!==t.name&&(s.name=t.name),void 0!==t.matrix?(s.matrix.fromArray(t.matrix),void 0!==t.matrixAutoUpdate&&(s.matrixAutoUpdate=t.matrixAutoUpdate),s.matrixAutoUpdate&&s.matrix.decompose(s.position,s.quaternion,s.scale)):(void 0!==t.position&&s.position.fromArray(t.position),void 0!==t.rotation&&s.rotation.fromArray(t.rotation),void 0!==t.quaternion&&s.quaternion.fromArray(t.quaternion),void 0!==t.scale&&s.scale.fromArray(t.scale)),void 0!==t.up&&s.up.fromArray(t.up),void 0!==t.castShadow&&(s.castShadow=t.castShadow),void 0!==t.receiveShadow&&(s.receiveShadow=t.receiveShadow),t.shadow&&(void 0!==t.shadow.intensity&&(s.shadow.intensity=t.shadow.intensity),void 0!==t.shadow.bias&&(s.shadow.bias=t.shadow.bias),void 0!==t.shadow.normalBias&&(s.shadow.normalBias=t.shadow.normalBias),void 0!==t.shadow.radius&&(s.shadow.radius=t.shadow.radius),void 0!==t.shadow.mapSize&&s.shadow.mapSize.fromArray(t.shadow.mapSize),void 0!==t.shadow.camera&&(s.shadow.camera=this.parseObject(t.shadow.camera))),void 0!==t.visible&&(s.visible=t.visible),void 0!==t.frustumCulled&&(s.frustumCulled=t.frustumCulled),void 0!==t.renderOrder&&(s.renderOrder=t.renderOrder),void 0!==t.userData&&(s.userData=t.userData),void 0!==t.layers&&(s.layers.mask=t.layers),void 0!==t.children){const a=t.children;for(let t=0;t{if(!0!==jp.has(s))return e&&e(n),r.manager.itemEnd(t),n;i&&i(jp.get(s)),r.manager.itemError(t),r.manager.itemEnd(t)}):(setTimeout(function(){e&&e(s),r.manager.itemEnd(t)},0),s);const a={};a.credentials="anonymous"===this.crossOrigin?"same-origin":"include",a.headers=this.requestHeader,a.signal="function"==typeof AbortSignal.any?AbortSignal.any([this._abortController.signal,this.manager.abortController.signal]):this._abortController.signal;const o=fetch(t,a).then(function(t){return t.blob()}).then(function(t){return createImageBitmap(t,Object.assign(r.options,{colorSpaceConversion:"none"}))}).then(function(n){return Fu.add(`image-bitmap:${t}`,n),e&&e(n),r.manager.itemEnd(t),n}).catch(function(e){i&&i(e),jp.set(o,e),Fu.remove(`image-bitmap:${t}`),r.manager.itemError(t),r.manager.itemEnd(t)});Fu.add(`image-bitmap:${t}`,o),r.manager.itemStart(t)}abort(){return this._abortController.abort(),this._abortController=new AbortController,this}}let Yp;class Jp{static getContext(){return void 0===Yp&&(Yp=new(window.AudioContext||window.webkitAudioContext)),Yp}static setContext(t){Yp=t}}class Zp extends Hu{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new Gu(this.manager);function a(e){i?i(e):console.error(e),r.manager.itemError(t)}s.setResponseType("arraybuffer"),s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,function(t){try{const n=t.slice(0);Jp.getContext().decodeAudioData(n,function(t){e(t)}).catch(a)}catch(t){a(t)}},n,i)}}const Kp=new Er,$p=new Er,Qp=new Er;class tf{constructor(){this.type="StereoCamera",this.aspect=1,this.eyeSep=.064,this.cameraL=new Ua,this.cameraL.layers.enable(1),this.cameraL.matrixAutoUpdate=!1,this.cameraR=new Ua,this.cameraR.layers.enable(2),this.cameraR.matrixAutoUpdate=!1,this._cache={focus:null,fov:null,aspect:null,near:null,far:null,zoom:null,eyeSep:null}}update(t){const e=this._cache;if(e.focus!==t.focus||e.fov!==t.fov||e.aspect!==t.aspect*this.aspect||e.near!==t.near||e.far!==t.far||e.zoom!==t.zoom||e.eyeSep!==this.eyeSep){e.focus=t.focus,e.fov=t.fov,e.aspect=t.aspect*this.aspect,e.near=t.near,e.far=t.far,e.zoom=t.zoom,e.eyeSep=this.eyeSep,Qp.copy(t.projectionMatrix);const n=e.eyeSep/2,i=n*e.near/e.focus,r=e.near*Math.tan(Gn*e.fov*.5)/e.zoom;let s,a;$p.elements[12]=-n,Kp.elements[12]=n,s=-r*e.aspect+i,a=r*e.aspect+i,Qp.elements[0]=2*e.near/(a-s),Qp.elements[8]=(a+s)/(a-s),this.cameraL.projectionMatrix.copy(Qp),s=-r*e.aspect-i,a=r*e.aspect-i,Qp.elements[0]=2*e.near/(a-s),Qp.elements[8]=(a+s)/(a-s),this.cameraR.projectionMatrix.copy(Qp)}this.cameraL.matrixWorld.copy(t.matrixWorld).multiply($p),this.cameraR.matrixWorld.copy(t.matrixWorld).multiply(Kp)}}class ef{constructor(t){this.value=t}clone(){return new ef(void 0===this.value.clone?this.value:this.value.clone())}}class nf extends xd{constructor(t,e,n=1){super(t,e),this.isInstancedInterleavedBuffer=!0,this.meshPerAttribute=n}copy(t){return super.copy(t),this.meshPerAttribute=t.meshPerAttribute,this}clone(t){const e=super.clone(t);return e.meshPerAttribute=this.meshPerAttribute,e}toJSON(t){const e=super.toJSON(t);return e.isInstancedInterleavedBuffer=!0,e.meshPerAttribute=this.meshPerAttribute,e}}class rf{constructor(t,e,n,i,r,s=!1){this.isGLBufferAttribute=!0,this.name="",this.buffer=t,this.type=e,this.itemSize=n,this.elementSize=i,this.count=r,this.normalized=s,this.version=0}set needsUpdate(t){!0===t&&this.version++}setBuffer(t){return this.buffer=t,this}setType(t,e){return this.type=t,this.elementSize=e,this}setItemSize(t){return this.itemSize=t,this}setCount(t){return this.count=t,this}}const sf=new Er;class af{constructor(t,e,n=0,i=1/0){this.ray=new Gs(t,e),this.near=n,this.far=i,this.camera=null,this.layers=new ns,this.params={Mesh:{},Line:{threshold:1},LOD:{},Points:{threshold:1},Sprite:{}}}set(t,e){this.ray.set(t,e)}setFromCamera(t,e){e.isPerspectiveCamera?(this.ray.origin.setFromMatrixPosition(e.matrixWorld),this.ray.direction.set(t.x,t.y,.5).unproject(e).sub(this.ray.origin).normalize(),this.camera=e):e.isOrthographicCamera?(this.ray.origin.set(t.x,t.y,(e.near+e.far)/(e.near-e.far)).unproject(e),this.ray.direction.set(0,0,-1).transformDirection(e.matrixWorld),this.camera=e):console.error("THREE.Raycaster: Unsupported camera type: "+e.type)}setFromXRController(t){return sf.identity().extractRotation(t.matrixWorld),this.ray.origin.setFromMatrixPosition(t.matrixWorld),this.ray.direction.set(0,0,-1).applyMatrix4(sf),this}intersectObject(t,e=!0,n=[]){return lf(t,this,n,e),n.sort(of),n}intersectObjects(t,e=!0,n=[]){for(let i=0,r=t.length;i=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,df).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const ff=new vi,mf=new vi,gf=new vi,vf=new vi,_f=new vi,xf=new vi,yf=new vi;class Mf{constructor(t=new vi,e=new vi){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){ff.subVectors(t,this.start),mf.subVectors(this.end,this.start);const n=mf.dot(mf);let i=mf.dot(ff)/n;return e&&(i=jn(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return this.delta(n).multiplyScalar(i).add(this.start)}distanceSqToLine3(t,e=xf,n=yf){const i=1e-8*1e-8;let r,s;const a=this.start,o=t.start,l=this.end,c=t.end;gf.subVectors(l,a),vf.subVectors(c,o),_f.subVectors(a,o);const h=gf.dot(gf),u=vf.dot(vf),d=vf.dot(_f);if(h<=i&&u<=i)return e.copy(a),n.copy(o),e.sub(n),e.dot(e);if(h<=i)r=0,s=d/u,s=jn(s,0,1);else{const t=gf.dot(_f);if(u<=i)s=0,r=jn(-t/h,0,1);else{const e=gf.dot(vf),n=h*u-e*e;r=0!==n?jn((e*d-t*u)/n,0,1):0,s=(e*r+d)/u,s<0?(s=0,r=jn(-t/h,0,1)):s>1&&(s=1,r=jn((e-t)/h,0,1))}}return e.copy(a).add(gf.multiplyScalar(r)),n.copy(o).add(vf.multiplyScalar(s)),e.sub(n),e.dot(e)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}class Sf extends Vc{constructor(t,e=1,n=16776960){const i=n,r=new ws;r.setAttribute("position",new $r([1,-1,0,-1,1,0,-1,-1,0,1,1,0,-1,1,0,-1,-1,0,1,-1,0,1,1,0],3)),r.computeBoundingSphere(),super(r,new Uc({color:i,toneMapped:!1})),this.type="PlaneHelper",this.plane=t,this.size=e;const s=new ws;s.setAttribute("position",new $r([1,1,0,-1,1,0,-1,-1,0,1,1,0,-1,-1,0,1,-1,0],3)),s.computeBoundingSphere(),this.add(new ga(s,new ra({color:i,opacity:.2,transparent:!0,depthWrite:!1,toneMapped:!1})))}updateMatrixWorld(t){this.position.set(0,0,0),this.scale.set(.5*this.size,.5*this.size,1),this.lookAt(this.plane.normal),this.translateZ(-this.plane.constant),super.updateMatrixWorld(t)}dispose(){this.geometry.dispose(),this.material.dispose(),this.children[0].geometry.dispose(),this.children[0].material.dispose()}}class bf{constructor(){this.type="ShapePath",this.color=new Zi,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new Uh,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,n,i){return this.currentPath.quadraticCurveTo(t,e,n,i),this}bezierCurveTo(t,e,n,i,r,s){return this.currentPath.bezierCurveTo(t,e,n,i,r,s),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const n=e.length;let i=!1;for(let r=n-1,s=0;sNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const n=lu.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,s,a;const o=[];if(1===i.length)return s=i[0],a=new Dh,a.curves=s.curves,o.push(a),o;let l=!n(i[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],f=0;h[f]=void 0,p[f]=[];for(let e=0,a=i.length;e1){let t=!1,n=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;t} + */ +const builds = [ + { + input: { + 'three.mjs': '../../../threejs/src/Three_jsroot.js' + }, + plugins: [ + glsl(), + header() + ], + preserveEntrySignatures: 'allow-extension', + output: [ + { + format: 'esm', + dir: '../../modules', + minifyInternalExports: false, + entryFileNames: '[name]', + } + ] + }, + { + input: { + 'three.mjs': '../../../threejs/src/Three_jsroot.js' + }, + plugins: [ + glsl(), + header(), + terser() + ], + preserveEntrySignatures: 'allow-extension', + output: [ + { + format: 'esm', + dir: '..', + minifyInternalExports: false, + entryFileNames: '[name]', + } + ] + }, + { + input: { + 'three_addons.mjs': '../../../threejs/src/Three_addons.js' + }, + external: ['three'], + plugins: [ + glsl(), + import_three(), + header() + ], + preserveEntrySignatures: 'allow-extension', + output: [ + { + format: 'esm', + dir: '../../modules', + minifyInternalExports: false, + entryFileNames: '[name]', + } + ] + }, + { + input: { + 'three_addons.mjs': '../../../threejs/src/Three_addons.js' + }, + external: ['three'], + plugins: [ + glsl(), + import_three(), + header(), + terser() + ], + preserveEntrySignatures: 'allow-extension', + output: [ + { + format: 'esm', + dir: '..', + minifyInternalExports: false, + entryFileNames: '[name]', + } + ] + }, + +]; + +export default builds; diff --git a/libs/three_addons.mjs b/libs/three_addons.mjs index d3a760dad..ce2b88c28 100644 --- a/libs/three_addons.mjs +++ b/libs/three_addons.mjs @@ -1,6 +1,6 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -import{ExtrudeGeometry as e,ShapePath as t,Ray as i,Plane as r,MathUtils as o,EventDispatcher as n,Vector3 as s,MOUSE as a,TOUCH as l,Quaternion as c,Spherical as h,Vector2 as u,OrthographicCamera as d,BufferGeometry as p,Float32BufferAttribute as m,Mesh as f,ShaderMaterial as g,UniformsUtils as v,WebGLRenderTarget as b,HalfFloatType as x,NoBlending as y,Clock as w,Color as S,AdditiveBlending as M,MeshBasicMaterial as T,Vector4 as C,Box3 as E,Matrix4 as P,Frustum as z,Matrix3 as j,DoubleSide as O,Box2 as R,SRGBColorSpace as A,Camera as L}from"./three.mjs";class _ extends e{constructor(e,t={}){const i=t.font;if(void 0===i)super();else{const r=i.generateShapes(e,t.size);t.depth=void 0!==t.height?t.height:50,void 0===t.bevelThickness&&(t.bevelThickness=10),void 0===t.bevelSize&&(t.bevelSize=8),void 0===t.bevelEnabled&&(t.bevelEnabled=!1),super(r,t)}this.type="TextGeometry"}}class k{constructor(e){this.isFont=!0,this.type="Font",this.data=e}generateShapes(e,t=100){const i=[],r=function(e,t,i){const r=Array.from(e),o=t/i.resolution,n=(i.boundingBox.yMax-i.boundingBox.yMin+i.underlineThickness)*o,s=[];let a=0,l=0;for(let e=0;eMath.PI&&(x-=v),y<-Math.PI?y+=v:y>Math.PI&&(y-=v),d.theta=x<=y?Math.max(x,Math.min(y,d.theta)):d.theta>(x+y)/2?Math.max(x,d.theta):Math.min(y,d.theta)),d.phi=Math.max(i.minPolarAngle,Math.min(i.maxPolarAngle,d.phi)),d.makeSafe(),!0===i.enableDamping?i.target.addScaledVector(f,i.dampingFactor):i.target.add(f),i.target.sub(i.cursor),i.target.clampLength(i.minTargetRadius,i.maxTargetRadius),i.target.add(i.cursor);let w=!1;if(i.zoomToCursor&&P||i.object.isOrthographicCamera)d.radius=K(d.radius);else{const e=d.radius;d.radius=K(d.radius*m),w=e!=d.radius}if(t.setFromSpherical(d),t.applyQuaternion(l),b.copy(i.target).add(t),i.object.lookAt(i.target),!0===i.enableDamping?(p.theta*=1-i.dampingFactor,p.phi*=1-i.dampingFactor,f.multiplyScalar(1-i.dampingFactor)):(p.set(0,0,0),f.set(0,0,0)),i.zoomToCursor&&P){let r=null;if(i.object.isPerspectiveCamera){const e=t.length();r=K(e*m);const o=e-r;i.object.position.addScaledVector(C,o),i.object.updateMatrixWorld(),w=!!o}else if(i.object.isOrthographicCamera){const e=new s(E.x,E.y,0);e.unproject(i.object);const o=i.object.zoom;i.object.zoom=Math.max(i.minZoom,Math.min(i.maxZoom,i.object.zoom/m)),i.object.updateProjectionMatrix(),w=o!==i.object.zoom;const n=new s(E.x,E.y,0);n.unproject(i.object),i.object.position.sub(n).add(e),i.object.updateMatrixWorld(),r=t.length()}else console.warn("WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled."),i.zoomToCursor=!1;null!==r&&(this.screenSpacePanning?i.target.set(0,0,-1).transformDirection(i.object.matrix).multiplyScalar(r).add(i.object.position):(F.origin.copy(i.object.position),F.direction.set(0,0,-1).transformDirection(i.object.matrix),Math.abs(i.object.up.dot(F.direction))n||8*(1-u.dot(i.object.quaternion))>n||g.distanceToSquared(i.target)>n)&&(i.dispatchEvent(N),h.copy(i.object.position),u.copy(i.object.quaternion),g.copy(i.target),!0)}}(),this.dispose=function(){i.domElement.removeEventListener("contextmenu",ce),i.domElement.removeEventListener("pointerdown",te),i.domElement.removeEventListener("pointercancel",re),i.domElement.removeEventListener("wheel",oe),i.domElement.removeEventListener("pointermove",ie),i.domElement.removeEventListener("pointerup",re);i.domElement.getRootNode().removeEventListener("keydown",ne,{capture:!0}),null!==i._domElementKeyEvents&&(i._domElementKeyEvents.removeEventListener("keydown",ae),i._domElementKeyEvents=null)};const i=this,r={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6};let o=r.NONE;const n=1e-6,d=new h,p=new h;let m=1;const f=new s,g=new u,v=new u,b=new u,x=new u,y=new u,w=new u,S=new u,M=new u,T=new u,C=new s,E=new u;let P=!1;const z=[],j={};let O=!1;function R(e){const t=Math.abs(.01*e);return Math.pow(.95,i.zoomSpeed*t)}function A(e){p.theta-=e}function L(e){p.phi-=e}const _=function(){const e=new s;return function(t,i){e.setFromMatrixColumn(i,0),e.multiplyScalar(-t),f.add(e)}}(),k=function(){const e=new s;return function(t,r){!0===i.screenSpacePanning?e.setFromMatrixColumn(r,1):(e.setFromMatrixColumn(r,0),e.crossVectors(i.object.up,e)),e.multiplyScalar(t),f.add(e)}}(),D=function(){const e=new s;return function(t,r){const o=i.domElement;if(i.object.isPerspectiveCamera){const n=i.object.position;e.copy(n).sub(i.target);let s=e.length();s*=Math.tan(i.object.fov/2*Math.PI/180),_(2*t*s/o.clientHeight,i.object.matrix),k(2*r*s/o.clientHeight,i.object.matrix)}else i.object.isOrthographicCamera?(_(t*(i.object.right-i.object.left)/i.object.zoom/o.clientWidth,i.object.matrix),k(r*(i.object.top-i.object.bottom)/i.object.zoom/o.clientHeight,i.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),i.enablePan=!1)}}();function H(e){i.object.isPerspectiveCamera||i.object.isOrthographicCamera?m/=e:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),i.enableZoom=!1)}function V(e){i.object.isPerspectiveCamera||i.object.isOrthographicCamera?m*=e:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),i.enableZoom=!1)}function W(e,t){if(!i.zoomToCursor)return;P=!0;const r=i.domElement.getBoundingClientRect(),o=e-r.left,n=t-r.top,s=r.width,a=r.height;E.x=o/s*2-1,E.y=-n/a*2+1,C.set(E.x,E.y,1).unproject(i.object).sub(i.object.position).normalize()}function K(e){return Math.max(i.minDistance,Math.min(i.maxDistance,e))}function X(e){g.set(e.clientX,e.clientY)}function Q(e){x.set(e.clientX,e.clientY)}function Z(e){if(1===z.length)g.set(e.pageX,e.pageY);else{const t=ue(e),i=.5*(e.pageX+t.x),r=.5*(e.pageY+t.y);g.set(i,r)}}function G(e){if(1===z.length)x.set(e.pageX,e.pageY);else{const t=ue(e),i=.5*(e.pageX+t.x),r=.5*(e.pageY+t.y);x.set(i,r)}}function q(e){const t=ue(e),i=e.pageX-t.x,r=e.pageY-t.y,o=Math.sqrt(i*i+r*r);S.set(0,o)}function J(e){if(1==z.length)v.set(e.pageX,e.pageY);else{const t=ue(e),i=.5*(e.pageX+t.x),r=.5*(e.pageY+t.y);v.set(i,r)}b.subVectors(v,g).multiplyScalar(i.rotateSpeed);const t=i.domElement;A(2*Math.PI*b.x/t.clientHeight),L(2*Math.PI*b.y/t.clientHeight),g.copy(v)}function $(e){if(1===z.length)y.set(e.pageX,e.pageY);else{const t=ue(e),i=.5*(e.pageX+t.x),r=.5*(e.pageY+t.y);y.set(i,r)}w.subVectors(y,x).multiplyScalar(i.panSpeed),D(w.x,w.y),x.copy(y)}function ee(e){const t=ue(e),r=e.pageX-t.x,o=e.pageY-t.y,n=Math.sqrt(r*r+o*o);M.set(0,n),T.set(0,Math.pow(M.y/S.y,i.zoomSpeed)),H(T.y),S.copy(M);W(.5*(e.pageX+t.x),.5*(e.pageY+t.y))}function te(e){!1!==i.enabled&&(0===z.length&&(i.domElement.setPointerCapture(e.pointerId),i.domElement.addEventListener("pointermove",ie),i.domElement.addEventListener("pointerup",re)),function(e){for(let t=0;t0?H(R(T.y)):T.y<0&&V(R(T.y)),S.copy(M),i.update()}(e);break;case r.PAN:if(!1===i.enablePan)return;!function(e){y.set(e.clientX,e.clientY),w.subVectors(y,x).multiplyScalar(i.panSpeed),D(w.x,w.y),x.copy(y),i.update()}(e)}}(e))}function re(e){switch(function(e){delete j[e.pointerId];for(let t=0;t0&&H(R(e.deltaY)),i.update()}(function(e){const t=e.deltaMode,i={clientX:e.clientX,clientY:e.clientY,deltaY:e.deltaY};switch(t){case 1:i.deltaY*=16;break;case 2:i.deltaY*=100}e.ctrlKey&&!O&&(i.deltaY*=10);return i}(e)),i.dispatchEvent(U))}function ne(e){if("Control"===e.key){O=!0;i.domElement.getRootNode().addEventListener("keyup",se,{passive:!0,capture:!0})}}function se(e){if("Control"===e.key){O=!1;i.domElement.getRootNode().removeEventListener("keyup",se,{passive:!0,capture:!0})}}function ae(e){!1!==i.enabled&&!1!==i.enablePan&&function(e){let t=!1;switch(e.code){case i.keys.UP:e.ctrlKey||e.metaKey||e.shiftKey?L(2*Math.PI*i.rotateSpeed/i.domElement.clientHeight):D(0,i.keyPanSpeed),t=!0;break;case i.keys.BOTTOM:e.ctrlKey||e.metaKey||e.shiftKey?L(-2*Math.PI*i.rotateSpeed/i.domElement.clientHeight):D(0,-i.keyPanSpeed),t=!0;break;case i.keys.LEFT:e.ctrlKey||e.metaKey||e.shiftKey?A(2*Math.PI*i.rotateSpeed/i.domElement.clientHeight):D(i.keyPanSpeed,0),t=!0;break;case i.keys.RIGHT:e.ctrlKey||e.metaKey||e.shiftKey?A(-2*Math.PI*i.rotateSpeed/i.domElement.clientHeight):D(-i.keyPanSpeed,0),t=!0}t&&(e.preventDefault(),i.update())}(e)}function le(e){switch(he(e),z.length){case 1:switch(i.touches.ONE){case l.ROTATE:if(!1===i.enableRotate)return;Z(e),o=r.TOUCH_ROTATE;break;case l.PAN:if(!1===i.enablePan)return;G(e),o=r.TOUCH_PAN;break;default:o=r.NONE}break;case 2:switch(i.touches.TWO){case l.DOLLY_PAN:if(!1===i.enableZoom&&!1===i.enablePan)return;!function(e){i.enableZoom&&q(e),i.enablePan&&G(e)}(e),o=r.TOUCH_DOLLY_PAN;break;case l.DOLLY_ROTATE:if(!1===i.enableZoom&&!1===i.enableRotate)return;!function(e){i.enableZoom&&q(e),i.enableRotate&&Z(e)}(e),o=r.TOUCH_DOLLY_ROTATE;break;default:o=r.NONE}break;default:o=r.NONE}o!==r.NONE&&i.dispatchEvent(B)}function ce(e){!1!==i.enabled&&e.preventDefault()}function he(e){let t=j[e.pointerId];void 0===t&&(t=new u,j[e.pointerId]=t),t.set(e.pageX,e.pageY)}function ue(e){const t=e.pointerId===z[0]?z[1]:z[0];return j[t]}i.domElement.addEventListener("contextmenu",ce),i.domElement.addEventListener("pointerdown",te),i.domElement.addEventListener("pointercancel",re),i.domElement.addEventListener("wheel",oe,{passive:!1});i.domElement.getRootNode().addEventListener("keydown",ne,{passive:!0,capture:!0}),this.update()}}const V={name:"CopyShader",uniforms:{tDiffuse:{value:null},opacity:{value:1}},vertexShader:"\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}",fragmentShader:"\n\n\t\tuniform float opacity;\n\n\t\tuniform sampler2D tDiffuse;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvec4 texel = texture2D( tDiffuse, vUv );\n\t\t\tgl_FragColor = opacity * texel;\n\n\n\t\t}"};class W{constructor(){this.isPass=!0,this.enabled=!0,this.needsSwap=!0,this.clear=!1,this.renderToScreen=!1}setSize(){}render(){console.error("THREE.Pass: .render() must be implemented in derived pass.")}dispose(){}}const K=new d(-1,1,1,-1,0,1);const X=new class extends p{constructor(){super(),this.setAttribute("position",new m([-1,3,0,-1,-1,0,3,-1,0],3)),this.setAttribute("uv",new m([0,2,0,0,2,0],2))}};class Q{constructor(e){this._mesh=new f(X,e)}dispose(){this._mesh.geometry.dispose()}render(e){e.render(this._mesh,K)}get material(){return this._mesh.material}set material(e){this._mesh.material=e}}class Z extends W{constructor(e,t){super(),this.textureID=void 0!==t?t:"tDiffuse",e instanceof g?(this.uniforms=e.uniforms,this.material=e):e&&(this.uniforms=v.clone(e.uniforms),this.material=new g({name:void 0!==e.name?e.name:"unspecified",defines:Object.assign({},e.defines),uniforms:this.uniforms,vertexShader:e.vertexShader,fragmentShader:e.fragmentShader})),this.fsQuad=new Q(this.material)}render(e,t,i){this.uniforms[this.textureID]&&(this.uniforms[this.textureID].value=i.texture),this.fsQuad.material=this.material,this.renderToScreen?(e.setRenderTarget(null),this.fsQuad.render(e)):(e.setRenderTarget(t),this.clear&&e.clear(e.autoClearColor,e.autoClearDepth,e.autoClearStencil),this.fsQuad.render(e))}dispose(){this.material.dispose(),this.fsQuad.dispose()}}class G extends W{constructor(e,t){super(),this.scene=e,this.camera=t,this.clear=!0,this.needsSwap=!1,this.inverse=!1}render(e,t,i){const r=e.getContext(),o=e.state;let n,s;o.buffers.color.setMask(!1),o.buffers.depth.setMask(!1),o.buffers.color.setLocked(!0),o.buffers.depth.setLocked(!0),this.inverse?(n=0,s=1):(n=1,s=0),o.buffers.stencil.setTest(!0),o.buffers.stencil.setOp(r.REPLACE,r.REPLACE,r.REPLACE),o.buffers.stencil.setFunc(r.ALWAYS,n,4294967295),o.buffers.stencil.setClear(s),o.buffers.stencil.setLocked(!0),e.setRenderTarget(i),this.clear&&e.clear(),e.render(this.scene,this.camera),e.setRenderTarget(t),this.clear&&e.clear(),e.render(this.scene,this.camera),o.buffers.color.setLocked(!1),o.buffers.depth.setLocked(!1),o.buffers.color.setMask(!0),o.buffers.depth.setMask(!0),o.buffers.stencil.setLocked(!1),o.buffers.stencil.setFunc(r.EQUAL,1,4294967295),o.buffers.stencil.setOp(r.KEEP,r.KEEP,r.KEEP),o.buffers.stencil.setLocked(!0)}}class q extends W{constructor(){super(),this.needsSwap=!1}render(e){e.state.buffers.stencil.setLocked(!1),e.state.buffers.stencil.setTest(!1)}}class J{constructor(e,t){if(this.renderer=e,this._pixelRatio=e.getPixelRatio(),void 0===t){const i=e.getSize(new u);this._width=i.width,this._height=i.height,(t=new b(this._width*this._pixelRatio,this._height*this._pixelRatio,{type:x})).texture.name="EffectComposer.rt1"}else this._width=t.width,this._height=t.height;this.renderTarget1=t,this.renderTarget2=t.clone(),this.renderTarget2.texture.name="EffectComposer.rt2",this.writeBuffer=this.renderTarget1,this.readBuffer=this.renderTarget2,this.renderToScreen=!0,this.passes=[],this.copyPass=new Z(V),this.copyPass.material.blending=y,this.clock=new w}swapBuffers(){const e=this.readBuffer;this.readBuffer=this.writeBuffer,this.writeBuffer=e}addPass(e){this.passes.push(e),e.setSize(this._width*this._pixelRatio,this._height*this._pixelRatio)}insertPass(e,t){this.passes.splice(t,0,e),e.setSize(this._width*this._pixelRatio,this._height*this._pixelRatio)}removePass(e){const t=this.passes.indexOf(e);-1!==t&&this.passes.splice(t,1)}isLastEnabledPass(e){for(let t=e+1;tu?(d=1,p=0):(d=0,p=1);const m=h-d+l,f=u-p+l,g=h-1+2*l,v=u-1+2*l,b=255&s,x=255&a,y=this.perm[b+this.perm[x]]%12,w=this.perm[b+d+this.perm[x+p]]%12,S=this.perm[b+1+this.perm[x+1]]%12;let M=.5-h*h-u*u;M<0?i=0:(M*=M,i=M*M*this.dot(this.grad3[y],h,u));let T=.5-m*m-f*f;T<0?r=0:(T*=T,r=T*T*this.dot(this.grad3[w],m,f));let C=.5-g*g-v*v;return C<0?o=0:(C*=C,o=C*C*this.dot(this.grad3[S],g,v)),70*(i+r+o)}noise3d(e,t,i){let r,o,n,s;const a=(e+t+i)*(1/3),l=Math.floor(e+a),c=Math.floor(t+a),h=Math.floor(i+a),u=1/6,d=(l+c+h)*u,p=e-(l-d),m=t-(c-d),f=i-(h-d);let g,v,b,x,y,w;p>=m?m>=f?(g=1,v=0,b=0,x=1,y=1,w=0):p>=f?(g=1,v=0,b=0,x=1,y=0,w=1):(g=0,v=0,b=1,x=1,y=0,w=1):mw?32:0)+(y>S?16:0)+(w>S?8:0)+(y>M?4:0)+(w>M?2:0)+(S>M?1:0),C=n[T][0]>=3?1:0,E=n[T][1]>=3?1:0,P=n[T][2]>=3?1:0,z=n[T][3]>=3?1:0,j=n[T][0]>=2?1:0,O=n[T][1]>=2?1:0,R=n[T][2]>=2?1:0,A=n[T][3]>=2?1:0,L=n[T][0]>=1?1:0,_=n[T][1]>=1?1:0,k=n[T][2]>=1?1:0,D=n[T][3]>=1?1:0,N=y-C+l,B=w-E+l,U=S-P+l,F=M-z+l,I=y-j+2*l,Y=w-O+2*l,H=S-R+2*l,V=M-A+2*l,W=y-L+3*l,K=w-_+3*l,X=S-k+3*l,Q=M-D+3*l,Z=y-1+4*l,G=w-1+4*l,q=S-1+4*l,J=M-1+4*l,$=255&f,ee=255&g,te=255&v,ie=255&b,re=s[$+s[ee+s[te+s[ie]]]]%32,oe=s[$+C+s[ee+E+s[te+P+s[ie+z]]]]%32,ne=s[$+j+s[ee+O+s[te+R+s[ie+A]]]]%32,se=s[$+L+s[ee+_+s[te+k+s[ie+D]]]]%32,ae=s[$+1+s[ee+1+s[te+1+s[ie+1]]]]%32;let le=.6-y*y-w*w-S*S-M*M;le<0?c=0:(le*=le,c=le*le*this.dot4(o[re],y,w,S,M));let ce=.6-N*N-B*B-U*U-F*F;ce<0?h=0:(ce*=ce,h=ce*ce*this.dot4(o[oe],N,B,U,F));let he=.6-I*I-Y*Y-H*H-V*V;he<0?u=0:(he*=he,u=he*he*this.dot4(o[ne],I,Y,H,V));let ue=.6-W*W-K*K-X*X-Q*Q;ue<0?d=0:(ue*=ue,d=ue*ue*this.dot4(o[se],W,K,X,Q));let de=.6-Z*Z-G*G-q*q-J*J;return de<0?p=0:(de*=de,p=de*de*this.dot4(o[ae],Z,G,q,J)),27*(c+h+u+d+p)}}const te={name:"LuminosityHighPassShader",shaderID:"luminosityHighPass",uniforms:{tDiffuse:{value:null},luminosityThreshold:{value:1},smoothWidth:{value:1},defaultColor:{value:new S(0)},defaultOpacity:{value:0}},vertexShader:"\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}",fragmentShader:"\n\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform vec3 defaultColor;\n\t\tuniform float defaultOpacity;\n\t\tuniform float luminosityThreshold;\n\t\tuniform float smoothWidth;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvec4 texel = texture2D( tDiffuse, vUv );\n\n\t\t\tvec3 luma = vec3( 0.299, 0.587, 0.114 );\n\n\t\t\tfloat v = dot( texel.xyz, luma );\n\n\t\t\tvec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );\n\n\t\t\tfloat alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );\n\n\t\t\tgl_FragColor = mix( outputColor, texel, alpha );\n\n\t\t}"};class ie extends W{constructor(e,t,i,r){super(),this.strength=void 0!==t?t:1,this.radius=i,this.threshold=r,this.resolution=void 0!==e?new u(e.x,e.y):new u(256,256),this.clearColor=new S(0,0,0),this.renderTargetsHorizontal=[],this.renderTargetsVertical=[],this.nMips=5;let o=Math.round(this.resolution.x/2),n=Math.round(this.resolution.y/2);this.renderTargetBright=new b(o,n,{type:x}),this.renderTargetBright.texture.name="UnrealBloomPass.bright",this.renderTargetBright.texture.generateMipmaps=!1;for(let e=0;e\n\t\t\t\tvarying vec2 vUv;\n\t\t\t\tuniform sampler2D colorTexture;\n\t\t\t\tuniform vec2 invSize;\n\t\t\t\tuniform vec2 direction;\n\t\t\t\tuniform float gaussianCoefficients[KERNEL_RADIUS];\n\n\t\t\t\tvoid main() {\n\t\t\t\t\tfloat weightSum = gaussianCoefficients[0];\n\t\t\t\t\tvec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum;\n\t\t\t\t\tfor( int i = 1; i < KERNEL_RADIUS; i ++ ) {\n\t\t\t\t\t\tfloat x = float(i);\n\t\t\t\t\t\tfloat w = gaussianCoefficients[i];\n\t\t\t\t\t\tvec2 uvOffset = direction * invSize * x;\n\t\t\t\t\t\tvec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb;\n\t\t\t\t\t\tvec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb;\n\t\t\t\t\t\tdiffuseSum += (sample1 + sample2) * w;\n\t\t\t\t\t\tweightSum += 2.0 * w;\n\t\t\t\t\t}\n\t\t\t\t\tgl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n\t\t\t\t}"})}getCompositeMaterial(e){return new g({defines:{NUM_MIPS:e},uniforms:{blurTexture1:{value:null},blurTexture2:{value:null},blurTexture3:{value:null},blurTexture4:{value:null},blurTexture5:{value:null},bloomStrength:{value:1},bloomFactors:{value:null},bloomTintColors:{value:null},bloomRadius:{value:0}},vertexShader:"varying vec2 vUv;\n\t\t\t\tvoid main() {\n\t\t\t\t\tvUv = uv;\n\t\t\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\t\t\t\t}",fragmentShader:"varying vec2 vUv;\n\t\t\t\tuniform sampler2D blurTexture1;\n\t\t\t\tuniform sampler2D blurTexture2;\n\t\t\t\tuniform sampler2D blurTexture3;\n\t\t\t\tuniform sampler2D blurTexture4;\n\t\t\t\tuniform sampler2D blurTexture5;\n\t\t\t\tuniform float bloomStrength;\n\t\t\t\tuniform float bloomRadius;\n\t\t\t\tuniform float bloomFactors[NUM_MIPS];\n\t\t\t\tuniform vec3 bloomTintColors[NUM_MIPS];\n\n\t\t\t\tfloat lerpBloomFactor(const in float factor) {\n\t\t\t\t\tfloat mirrorFactor = 1.2 - factor;\n\t\t\t\t\treturn mix(factor, mirrorFactor, bloomRadius);\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\t\t\t\t\tgl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\n\t\t\t\t}"})}}ie.BlurDirectionX=new u(1,0),ie.BlurDirectionY=new u(0,1);class re{constructor(){this.id=0,this.object=null,this.z=0,this.renderOrder=0}}class oe{constructor(){this.id=0,this.v1=new ne,this.v2=new ne,this.v3=new ne,this.normalModel=new s,this.vertexNormalsModel=[new s,new s,new s],this.vertexNormalsLength=0,this.color=new S,this.material=null,this.uvs=[new u,new u,new u],this.z=0,this.renderOrder=0}}class ne{constructor(){this.position=new s,this.positionWorld=new s,this.positionScreen=new C,this.visible=!0}copy(e){this.positionWorld.copy(e.positionWorld),this.positionScreen.copy(e.positionScreen)}}class se{constructor(){this.id=0,this.v1=new ne,this.v2=new ne,this.vertexColors=[new S,new S],this.material=null,this.z=0,this.renderOrder=0}}class ae{constructor(){this.id=0,this.object=null,this.x=0,this.y=0,this.z=0,this.rotation=0,this.scale=new u,this.material=null,this.renderOrder=0}}class le{constructor(){let e,t,i,r,o,n,a,l,c,h,u,d=0,p=0,m=0,f=0,g=0;const v={objects:[],lights:[],elements:[]},b=new s,x=new C,y=new E(new s(-1,-1,-1),new s(1,1,1)),w=new E,S=new Array(3),M=new P,T=new P,R=new P,A=new z,L=[],_=[],k=[],D=[],N=[];const B=new function(){const e=[],t=[],s=[];let c=null;const h=new j;function d(e){const t=e.position,i=e.positionWorld,r=e.positionScreen;i.copy(t).applyMatrix4(u),r.copy(i).applyMatrix4(T);const o=1/r.w;r.x*=o,r.y*=o,r.z*=o,e.visible=r.x>=-1&&r.x<=1&&r.y>=-1&&r.y<=1&&r.z>=-1&&r.z<=1}function g(e,t,i){return!0===e.visible||!0===t.visible||!0===i.visible||(S[0]=e.positionScreen,S[1]=t.positionScreen,S[2]=i.positionScreen,y.intersectsBox(w.setFromPoints(S)))}function M(e,t,i){return(i.positionScreen.x-e.positionScreen.x)*(t.positionScreen.y-e.positionScreen.y)-(i.positionScreen.y-e.positionScreen.y)*(t.positionScreen.x-e.positionScreen.x)<0}return{setObject:function(i){c=i,h.getNormalMatrix(c.matrixWorld),e.length=0,t.length=0,s.length=0},projectVertex:d,checkTriangleVisibility:g,checkBackfaceCulling:M,pushVertex:function(e,t,o){i=function(){if(r===p){const e=new ne;return _.push(e),p++,r++,e}return _[r++]}(),i.position.set(e,t,o),d(i)},pushNormal:function(t,i,r){e.push(t,i,r)},pushColor:function(e,i,r){t.push(e,i,r)},pushUv:function(e,t){s.push(e,t)},pushLine:function(e,i){const r=_[e],o=_[i];r.positionScreen.copy(r.position).applyMatrix4(R),o.positionScreen.copy(o.position).applyMatrix4(R),!0===function(e,t){let i=0,r=1;const o=e.z+e.w,n=t.z+t.w,s=-e.z+e.w,a=-t.z+t.w;return o>=0&&n>=0&&s>=0&&a>=0||!(o<0&&n<0||s<0&&a<0)&&(o<0?i=Math.max(i,o/(o-n)):n<0&&(r=Math.min(r,o/(o-n))),s<0?i=Math.max(i,s/(s-a)):a<0&&(r=Math.min(r,s/(s-a))),!(r=-1&&e.z<=1&&(c=function(){if(h===g){const e=new ae;return N.push(e),g++,h++,e}return N[h++]}(),c.id=t.id,c.x=e.x*r,c.y=e.y*r,c.z=e.z,c.renderOrder=t.renderOrder,c.object=t,c.rotation=t.rotation,c.scale.x=t.scale.x*Math.abs(c.x-(e.x+i.projectionMatrix.elements[0])/(e.w+i.projectionMatrix.elements[12])),c.scale.y=t.scale.y*Math.abs(c.y-(e.y+i.projectionMatrix.elements[5])/(e.w+i.projectionMatrix.elements[13])),c.material=t.material,v.elements.push(c))}function Y(e,t){return e.renderOrder!==t.renderOrder?e.renderOrder-t.renderOrder:e.z!==t.z?t.z-e.z:e.id!==t.id?e.id-t.id:0}this.projectScene=function(e,i,o,s){n=0,l=0,h=0,v.elements.length=0,!0===e.matrixWorldAutoUpdate&&e.updateMatrixWorld(),null===i.parent&&!0===i.matrixWorldAutoUpdate&&i.updateMatrixWorld(),M.copy(i.matrixWorldInverse),T.multiplyMatrices(i.projectionMatrix,M),A.setFromProjectionMatrix(T),t=0,v.objects.length=0,v.lights.length=0,U(e),!0===o&&v.objects.sort(Y);const a=v.objects;for(let e=0,t=a.length;e0)for(let o=0;o0)for(let r=0;r0;)F.removeChild(F.childNodes[0])}function Y(e){return null!==f?e.toFixed(f):e}function H(e,t,i){let r=t.scale.x*n,o=t.scale.y*a;i.isPointsMaterial&&(r*=i.size,o*=i.size);const s="M"+Y(e.x-.5*r)+","+Y(e.y-.5*o)+"h"+Y(r)+"v"+Y(o)+"h"+Y(-r)+"z";let l="";(i.isSpriteMaterial||i.isPointsMaterial)&&(l="fill:"+i.color.getStyle(v.outputColorSpace)+";fill-opacity:"+i.opacity),X(l,s)}function V(e,t,i){const r="M"+Y(e.positionScreen.x)+","+Y(e.positionScreen.y)+"L"+Y(t.positionScreen.x)+","+Y(t.positionScreen.y);if(i.isLineBasicMaterial){let e="fill:none;stroke:"+i.color.getStyle(v.outputColorSpace)+";stroke-opacity:"+i.opacity+";stroke-width:"+i.linewidth+";stroke-linecap:"+i.linecap;i.isLineDashedMaterial&&(e=e+";stroke-dasharray:"+i.dashSize+","+i.gapSize),X(e,r)}}function W(e,t,r,o,n){v.info.render.vertices+=3,v.info.render.faces++;const s="M"+Y(e.positionScreen.x)+","+Y(e.positionScreen.y)+"L"+Y(t.positionScreen.x)+","+Y(t.positionScreen.y)+"L"+Y(r.positionScreen.x)+","+Y(r.positionScreen.y)+"z";let a="";n.isMeshBasicMaterial?(y.copy(n.color),n.vertexColors&&y.multiply(o.color)):n.isMeshLambertMaterial||n.isMeshPhongMaterial||n.isMeshStandardMaterial?(w.copy(n.color),n.vertexColors&&w.multiply(o.color),y.copy(M),O.copy(e.positionWorld).add(t.positionWorld).add(r.positionWorld).divideScalar(3),function(e,t,i,r){for(let o=0,n=e.length;o1)continue;if(c.positionScreen.z<-1||c.positionScreen.z>1)continue;if(h.positionScreen.z<-1||h.positionScreen.z>1)continue;l.positionScreen.x*=n,l.positionScreen.y*=-a,c.positionScreen.x*=n,c.positionScreen.y*=-a,h.positionScreen.x*=n,h.positionScreen.y*=-a,this.overdraw>0&&(K(l.positionScreen,c.positionScreen,this.overdraw),K(c.positionScreen,h.positionScreen,this.overdraw),K(h.positionScreen,l.positionScreen,this.overdraw)),x.setFromPoints([l.positionScreen,c.positionScreen,h.positionScreen]),!0===b.intersectsBox(x)&&W(l,c,h,i,r)}}Q(),r.traverseVisible((function(e){if(e.isSVGObject){if(z.setFromMatrixPosition(e.matrixWorld),z.applyMatrix4(N),z.z<-1||z.z>1)return;const t=z.x*n,i=-z.y*a,r=e.node;r.setAttribute("transform","translate("+t+","+i+")"),F.appendChild(r)}}))}}}export{V as CopyShader,J as EffectComposer,k as Font,te as LuminosityHighPassShader,G as MaskPass,H as OrbitControls,W as Pass,$ as RenderPass,ce as SVGRenderer,Z as ShaderPass,ee as SimplexNoise,_ as TextGeometry,ie as UnrealBloomPass}; +import{ExtrudeGeometry as t,ShapePath as e,Ray as i,Plane as s,MathUtils as o,Vector3 as r,Controls as n,MOUSE as a,TOUCH as h,Quaternion as l,Spherical as c,Vector2 as u,OrthographicCamera as d,BufferGeometry as p,Float32BufferAttribute as m,Mesh as f,ShaderMaterial as _,UniformsUtils as g,WebGLRenderTarget as v,HalfFloatType as b,NoBlending as y,Clock as x,Color as S,AdditiveBlending as M,MeshBasicMaterial as w,Vector4 as T,Box3 as P,Matrix4 as C,Frustum as E,Matrix3 as D,DoubleSide as R,Box2 as z,SRGBColorSpace as j,Camera as k}from"./three.mjs";class L extends t{constructor(t,e={}){const i=e.font;if(void 0===i)super();else{const s=i.generateShapes(t,e.size);void 0===e.depth&&(e.depth=50),void 0===e.bevelThickness&&(e.bevelThickness=10),void 0===e.bevelSize&&(e.bevelSize=8),void 0===e.bevelEnabled&&(e.bevelEnabled=!1),super(s,e)}this.type="TextGeometry"}}class A{constructor(t){this.isFont=!0,this.type="Font",this.data=t}generateShapes(t,e=100){const i=[],s=function(t,e,i){const s=Array.from(t),o=e/i.resolution,r=(i.boundingBox.yMax-i.boundingBox.yMin+i.underlineThickness)*o,n=[];let a=0,h=0;for(let t=0;tMath.PI&&(i-=K),s<-Math.PI?s+=K:s>Math.PI&&(s-=K),this._spherical.theta=i<=s?Math.max(i,Math.min(s,this._spherical.theta)):this._spherical.theta>(i+s)/2?Math.max(i,this._spherical.theta):Math.min(s,this._spherical.theta)),this._spherical.phi=Math.max(this.minPolarAngle,Math.min(this.maxPolarAngle,this._spherical.phi)),this._spherical.makeSafe(),!0===this.enableDamping?this.target.addScaledVector(this._panOffset,this.dampingFactor):this.target.add(this._panOffset),this.target.sub(this.cursor),this.target.clampLength(this.minTargetRadius,this.maxTargetRadius),this.target.add(this.cursor);let o=!1;if(this.zoomToCursor&&this._performCursorZoom||this.object.isOrthographicCamera)this._spherical.radius=this._clampDistance(this._spherical.radius);else{const t=this._spherical.radius;this._spherical.radius=this._clampDistance(this._spherical.radius*this._scale),o=t!=this._spherical.radius}if(Y.setFromSpherical(this._spherical),Y.applyQuaternion(this._quatInverse),e.copy(this.target).add(Y),this.object.lookAt(this.target),!0===this.enableDamping?(this._sphericalDelta.theta*=1-this.dampingFactor,this._sphericalDelta.phi*=1-this.dampingFactor,this._panOffset.multiplyScalar(1-this.dampingFactor)):(this._sphericalDelta.set(0,0,0),this._panOffset.set(0,0,0)),this.zoomToCursor&&this._performCursorZoom){let t=null;if(this.object.isPerspectiveCamera){const e=Y.length();t=this._clampDistance(e*this._scale);const i=e-t;this.object.position.addScaledVector(this._dollyDirection,i),this.object.updateMatrixWorld(),o=!!i}else if(this.object.isOrthographicCamera){const e=new r(this._mouse.x,this._mouse.y,0);e.unproject(this.object);const i=this.object.zoom;this.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom/this._scale)),this.object.updateProjectionMatrix(),o=i!==this.object.zoom;const s=new r(this._mouse.x,this._mouse.y,0);s.unproject(this.object),this.object.position.sub(s).add(e),this.object.updateMatrixWorld(),t=Y.length()}else console.warn("WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled."),this.zoomToCursor=!1;null!==t&&(this.screenSpacePanning?this.target.set(0,0,-1).transformDirection(this.object.matrix).multiplyScalar(t).add(this.object.position):(I.origin.copy(this.object.position),I.direction.set(0,0,-1).transformDirection(this.object.matrix),Math.abs(this.object.up.dot(I.direction))$||8*(1-this._lastQuaternion.dot(this.object.quaternion))>$||this._lastTargetPosition.distanceToSquared(this.target)>$)&&(this.dispatchEvent(U),this._lastPosition.copy(this.object.position),this._lastQuaternion.copy(this.object.quaternion),this._lastTargetPosition.copy(this.target),!0)}_getAutoRotationAngle(t){return null!==t?K/60*this.autoRotateSpeed*t:K/60/60*this.autoRotateSpeed}_getZoomScale(t){const e=Math.abs(.01*t);return Math.pow(.95,this.zoomSpeed*e)}_rotateLeft(t){this._sphericalDelta.theta-=t}_rotateUp(t){this._sphericalDelta.phi-=t}_panLeft(t,e){Y.setFromMatrixColumn(e,0),Y.multiplyScalar(-t),this._panOffset.add(Y)}_panUp(t,e){!0===this.screenSpacePanning?Y.setFromMatrixColumn(e,1):(Y.setFromMatrixColumn(e,0),Y.crossVectors(this.object.up,Y)),Y.multiplyScalar(t),this._panOffset.add(Y)}_pan(t,e){const i=this.domElement;if(this.object.isPerspectiveCamera){const s=this.object.position;Y.copy(s).sub(this.target);let o=Y.length();o*=Math.tan(this.object.fov/2*Math.PI/180),this._panLeft(2*t*o/i.clientHeight,this.object.matrix),this._panUp(2*e*o/i.clientHeight,this.object.matrix)}else this.object.isOrthographicCamera?(this._panLeft(t*(this.object.right-this.object.left)/this.object.zoom/i.clientWidth,this.object.matrix),this._panUp(e*(this.object.top-this.object.bottom)/this.object.zoom/i.clientHeight,this.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),this.enablePan=!1)}_dollyOut(t){this.object.isPerspectiveCamera||this.object.isOrthographicCamera?this._scale/=t:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_dollyIn(t){this.object.isPerspectiveCamera||this.object.isOrthographicCamera?this._scale*=t:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_updateZoomParameters(t,e){if(!this.zoomToCursor)return;this._performCursorZoom=!0;const i=this.domElement.getBoundingClientRect(),s=t-i.left,o=e-i.top,r=i.width,n=i.height;this._mouse.x=s/r*2-1,this._mouse.y=-o/n*2+1,this._dollyDirection.set(this._mouse.x,this._mouse.y,1).unproject(this.object).sub(this.object.position).normalize()}_clampDistance(t){return Math.max(this.minDistance,Math.min(this.maxDistance,t))}_handleMouseDownRotate(t){this._rotateStart.set(t.clientX,t.clientY)}_handleMouseDownDolly(t){this._updateZoomParameters(t.clientX,t.clientX),this._dollyStart.set(t.clientX,t.clientY)}_handleMouseDownPan(t){this._panStart.set(t.clientX,t.clientY)}_handleMouseMoveRotate(t){this._rotateEnd.set(t.clientX,t.clientY),this._rotateDelta.subVectors(this._rotateEnd,this._rotateStart).multiplyScalar(this.rotateSpeed);const e=this.domElement;this._rotateLeft(K*this._rotateDelta.x/e.clientHeight),this._rotateUp(K*this._rotateDelta.y/e.clientHeight),this._rotateStart.copy(this._rotateEnd),this.update()}_handleMouseMoveDolly(t){this._dollyEnd.set(t.clientX,t.clientY),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyOut(this._getZoomScale(this._dollyDelta.y)):this._dollyDelta.y<0&&this._dollyIn(this._getZoomScale(this._dollyDelta.y)),this._dollyStart.copy(this._dollyEnd),this.update()}_handleMouseMovePan(t){this._panEnd.set(t.clientX,t.clientY),this._panDelta.subVectors(this._panEnd,this._panStart).multiplyScalar(this.panSpeed),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd),this.update()}_handleMouseWheel(t){this._updateZoomParameters(t.clientX,t.clientY),t.deltaY<0?this._dollyIn(this._getZoomScale(t.deltaY)):t.deltaY>0&&this._dollyOut(this._getZoomScale(t.deltaY)),this.update()}_handleKeyDown(t){let e=!1;switch(t.code){case this.keys.UP:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateUp(K*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(0,this.keyPanSpeed),e=!0;break;case this.keys.BOTTOM:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateUp(-K*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(0,-this.keyPanSpeed),e=!0;break;case this.keys.LEFT:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateLeft(K*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(this.keyPanSpeed,0),e=!0;break;case this.keys.RIGHT:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateLeft(-K*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(-this.keyPanSpeed,0),e=!0}e&&(t.preventDefault(),this.update())}_handleTouchStartRotate(t){if(1===this._pointers.length)this._rotateStart.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._rotateStart.set(i,s)}}_handleTouchStartPan(t){if(1===this._pointers.length)this._panStart.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._panStart.set(i,s)}}_handleTouchStartDolly(t){const e=this._getSecondPointerPosition(t),i=t.pageX-e.x,s=t.pageY-e.y,o=Math.sqrt(i*i+s*s);this._dollyStart.set(0,o)}_handleTouchStartDollyPan(t){this.enableZoom&&this._handleTouchStartDolly(t),this.enablePan&&this._handleTouchStartPan(t)}_handleTouchStartDollyRotate(t){this.enableZoom&&this._handleTouchStartDolly(t),this.enableRotate&&this._handleTouchStartRotate(t)}_handleTouchMoveRotate(t){if(1==this._pointers.length)this._rotateEnd.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._rotateEnd.set(i,s)}this._rotateDelta.subVectors(this._rotateEnd,this._rotateStart).multiplyScalar(this.rotateSpeed);const e=this.domElement;this._rotateLeft(K*this._rotateDelta.x/e.clientHeight),this._rotateUp(K*this._rotateDelta.y/e.clientHeight),this._rotateStart.copy(this._rotateEnd)}_handleTouchMovePan(t){if(1===this._pointers.length)this._panEnd.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._panEnd.set(i,s)}this._panDelta.subVectors(this._panEnd,this._panStart).multiplyScalar(this.panSpeed),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd)}_handleTouchMoveDolly(t){const e=this._getSecondPointerPosition(t),i=t.pageX-e.x,s=t.pageY-e.y,o=Math.sqrt(i*i+s*s);this._dollyEnd.set(0,o),this._dollyDelta.set(0,Math.pow(this._dollyEnd.y/this._dollyStart.y,this.zoomSpeed)),this._dollyOut(this._dollyDelta.y),this._dollyStart.copy(this._dollyEnd);const r=.5*(t.pageX+e.x),n=.5*(t.pageY+e.y);this._updateZoomParameters(r,n)}_handleTouchMoveDollyPan(t){this.enableZoom&&this._handleTouchMoveDolly(t),this.enablePan&&this._handleTouchMovePan(t)}_handleTouchMoveDollyRotate(t){this.enableZoom&&this._handleTouchMoveDolly(t),this.enableRotate&&this._handleTouchMoveRotate(t)}_addPointer(t){this._pointers.push(t.pointerId)}_removePointer(t){delete this._pointerPositions[t.pointerId];for(let e=0;eu?(d=1,p=0):(d=0,p=1);const m=c-d+h,f=u-p+h,_=c-1+2*h,g=u-1+2*h,v=255&n,b=255&a,y=this.perm[v+this.perm[b]]%12,x=this.perm[v+d+this.perm[b+p]]%12,S=this.perm[v+1+this.perm[b+1]]%12;let M=.5-c*c-u*u;M<0?i=0:(M*=M,i=M*M*this._dot(this.grad3[y],c,u));let w=.5-m*m-f*f;w<0?s=0:(w*=w,s=w*w*this._dot(this.grad3[x],m,f));let T=.5-_*_-g*g;return T<0?o=0:(T*=T,o=T*T*this._dot(this.grad3[S],_,g)),70*(i+s+o)}noise3d(t,e,i){let s,o,r,n;const a=(t+e+i)*(1/3),h=Math.floor(t+a),l=Math.floor(e+a),c=Math.floor(i+a),u=1/6,d=(h+l+c)*u,p=t-(h-d),m=e-(l-d),f=i-(c-d);let _,g,v,b,y,x;p>=m?m>=f?(_=1,g=0,v=0,b=1,y=1,x=0):p>=f?(_=1,g=0,v=0,b=1,y=0,x=1):(_=0,g=0,v=1,b=1,y=0,x=1):mx?32:0)+(y>S?16:0)+(x>S?8:0)+(y>M?4:0)+(x>M?2:0)+(S>M?1:0),T=r[w][0]>=3?1:0,P=r[w][1]>=3?1:0,C=r[w][2]>=3?1:0,E=r[w][3]>=3?1:0,D=r[w][0]>=2?1:0,R=r[w][1]>=2?1:0,z=r[w][2]>=2?1:0,j=r[w][3]>=2?1:0,k=r[w][0]>=1?1:0,L=r[w][1]>=1?1:0,A=r[w][2]>=1?1:0,O=r[w][3]>=1?1:0,U=y-T+h,B=x-P+h,F=S-C+h,I=M-E+h,W=y-D+2*h,N=x-R+2*h,Y=S-z+2*h,K=M-j+2*h,V=y-k+3*h,Z=x-L+3*h,H=S-A+3*h,X=M-O+3*h,Q=y-1+4*h,q=x-1+4*h,G=S-1+4*h,J=M-1+4*h,$=255&f,tt=255&_,et=255&g,it=255&v,st=n[$+n[tt+n[et+n[it]]]]%32,ot=n[$+T+n[tt+P+n[et+C+n[it+E]]]]%32,rt=n[$+D+n[tt+R+n[et+z+n[it+j]]]]%32,nt=n[$+k+n[tt+L+n[et+A+n[it+O]]]]%32,at=n[$+1+n[tt+1+n[et+1+n[it+1]]]]%32;let ht=.6-y*y-x*x-S*S-M*M;ht<0?l=0:(ht*=ht,l=ht*ht*this._dot4(o[st],y,x,S,M));let lt=.6-U*U-B*B-F*F-I*I;lt<0?c=0:(lt*=lt,c=lt*lt*this._dot4(o[ot],U,B,F,I));let ct=.6-W*W-N*N-Y*Y-K*K;ct<0?u=0:(ct*=ct,u=ct*ct*this._dot4(o[rt],W,N,Y,K));let ut=.6-V*V-Z*Z-H*H-X*X;ut<0?d=0:(ut*=ut,d=ut*ut*this._dot4(o[nt],V,Z,H,X));let dt=.6-Q*Q-q*q-G*G-J*J;return dt<0?p=0:(dt*=dt,p=dt*dt*this._dot4(o[at],Q,q,G,J)),27*(l+c+u+d+p)}_dot(t,e,i){return t[0]*e+t[1]*i}_dot3(t,e,i,s){return t[0]*e+t[1]*i+t[2]*s}_dot4(t,e,i,s,o){return t[0]*e+t[1]*i+t[2]*s+t[3]*o}}const wt={name:"LuminosityHighPassShader",uniforms:{tDiffuse:{value:null},luminosityThreshold:{value:1},smoothWidth:{value:1},defaultColor:{value:new S(0)},defaultOpacity:{value:0}},vertexShader:"\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}",fragmentShader:"\n\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform vec3 defaultColor;\n\t\tuniform float defaultOpacity;\n\t\tuniform float luminosityThreshold;\n\t\tuniform float smoothWidth;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvec4 texel = texture2D( tDiffuse, vUv );\n\n\t\t\tfloat v = luminance( texel.xyz );\n\n\t\t\tvec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );\n\n\t\t\tfloat alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );\n\n\t\t\tgl_FragColor = mix( outputColor, texel, alpha );\n\n\t\t}"};class Tt extends mt{constructor(t,e=1,i,s){super(),this.strength=e,this.radius=i,this.threshold=s,this.resolution=void 0!==t?new u(t.x,t.y):new u(256,256),this.clearColor=new S(0,0,0),this.needsSwap=!1,this.renderTargetsHorizontal=[],this.renderTargetsVertical=[],this.nMips=5;let o=Math.round(this.resolution.x/2),n=Math.round(this.resolution.y/2);this.renderTargetBright=new v(o,n,{type:b}),this.renderTargetBright.texture.name="UnrealBloomPass.bright",this.renderTargetBright.texture.generateMipmaps=!1;for(let t=0;t\n\t\t\t\tvarying vec2 vUv;\n\t\t\t\tuniform sampler2D colorTexture;\n\t\t\t\tuniform vec2 invSize;\n\t\t\t\tuniform vec2 direction;\n\t\t\t\tuniform float gaussianCoefficients[KERNEL_RADIUS];\n\n\t\t\t\tvoid main() {\n\t\t\t\t\tfloat weightSum = gaussianCoefficients[0];\n\t\t\t\t\tvec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum;\n\t\t\t\t\tfor( int i = 1; i < KERNEL_RADIUS; i ++ ) {\n\t\t\t\t\t\tfloat x = float(i);\n\t\t\t\t\t\tfloat w = gaussianCoefficients[i];\n\t\t\t\t\t\tvec2 uvOffset = direction * invSize * x;\n\t\t\t\t\t\tvec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb;\n\t\t\t\t\t\tvec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb;\n\t\t\t\t\t\tdiffuseSum += (sample1 + sample2) * w;\n\t\t\t\t\t\tweightSum += 2.0 * w;\n\t\t\t\t\t}\n\t\t\t\t\tgl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n\t\t\t\t}"})}_getCompositeMaterial(t){return new _({defines:{NUM_MIPS:t},uniforms:{blurTexture1:{value:null},blurTexture2:{value:null},blurTexture3:{value:null},blurTexture4:{value:null},blurTexture5:{value:null},bloomStrength:{value:1},bloomFactors:{value:null},bloomTintColors:{value:null},bloomRadius:{value:0}},vertexShader:"varying vec2 vUv;\n\t\t\t\tvoid main() {\n\t\t\t\t\tvUv = uv;\n\t\t\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\t\t\t\t}",fragmentShader:"varying vec2 vUv;\n\t\t\t\tuniform sampler2D blurTexture1;\n\t\t\t\tuniform sampler2D blurTexture2;\n\t\t\t\tuniform sampler2D blurTexture3;\n\t\t\t\tuniform sampler2D blurTexture4;\n\t\t\t\tuniform sampler2D blurTexture5;\n\t\t\t\tuniform float bloomStrength;\n\t\t\t\tuniform float bloomRadius;\n\t\t\t\tuniform float bloomFactors[NUM_MIPS];\n\t\t\t\tuniform vec3 bloomTintColors[NUM_MIPS];\n\n\t\t\t\tfloat lerpBloomFactor(const in float factor) {\n\t\t\t\t\tfloat mirrorFactor = 1.2 - factor;\n\t\t\t\t\treturn mix(factor, mirrorFactor, bloomRadius);\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\t\t\t\t\tgl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +\n\t\t\t\t\t\tlerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\n\t\t\t\t}"})}}Tt.BlurDirectionX=new u(1,0),Tt.BlurDirectionY=new u(0,1);class Pt{constructor(){this.id=0,this.object=null,this.z=0,this.renderOrder=0}}class Ct{constructor(){this.id=0,this.v1=new Et,this.v2=new Et,this.v3=new Et,this.normalModel=new r,this.vertexNormalsModel=[new r,new r,new r],this.vertexNormalsLength=0,this.color=new S,this.material=null,this.uvs=[new u,new u,new u],this.z=0,this.renderOrder=0}}class Et{constructor(){this.position=new r,this.positionWorld=new r,this.positionScreen=new T,this.visible=!0}copy(t){this.positionWorld.copy(t.positionWorld),this.positionScreen.copy(t.positionScreen)}}class Dt{constructor(){this.id=0,this.v1=new Et,this.v2=new Et,this.vertexColors=[new S,new S],this.material=null,this.z=0,this.renderOrder=0}}class Rt{constructor(){this.id=0,this.object=null,this.x=0,this.y=0,this.z=0,this.rotation=0,this.scale=new u,this.material=null,this.renderOrder=0}}class zt{constructor(){let t,e,i,s,o,n,a,h,l,c,u,d=0,p=0,m=0,f=0,_=0;const g={objects:[],lights:[],elements:[]},v=new r,b=new T,y=new P(new r(-1,-1,-1),new r(1,1,1)),x=new P,S=new Array(3),M=new C,w=new C,z=new C,j=new E,k=[],L=[],A=[],O=[],U=[];const B=new function(){const t=[],e=[],r=[];let l=null;const c=new D;function d(t){const e=t.position,i=t.positionWorld,s=t.positionScreen;i.copy(e).applyMatrix4(u),s.copy(i).applyMatrix4(w);const o=1/s.w;s.x*=o,s.y*=o,s.z*=o,t.visible=s.x>=-1&&s.x<=1&&s.y>=-1&&s.y<=1&&s.z>=-1&&s.z<=1}function _(t,e,i){return!0===t.visible||!0===e.visible||!0===i.visible||(S[0]=t.positionScreen,S[1]=e.positionScreen,S[2]=i.positionScreen,y.intersectsBox(x.setFromPoints(S)))}function M(t,e,i){return(i.positionScreen.x-t.positionScreen.x)*(e.positionScreen.y-t.positionScreen.y)-(i.positionScreen.y-t.positionScreen.y)*(e.positionScreen.x-t.positionScreen.x)<0}return{setObject:function(i){l=i,c.getNormalMatrix(l.matrixWorld),t.length=0,e.length=0,r.length=0},projectVertex:d,checkTriangleVisibility:_,checkBackfaceCulling:M,pushVertex:function(t,e,o){i=function(){if(s===p){const t=new Et;return L.push(t),p++,s++,t}return L[s++]}(),i.position.set(t,e,o),d(i)},pushNormal:function(e,i,s){t.push(e,i,s)},pushColor:function(t,i,s){e.push(t,i,s)},pushUv:function(t,e){r.push(t,e)},pushLine:function(t,i){const s=L[t],o=L[i];s.positionScreen.copy(s.position).applyMatrix4(z),o.positionScreen.copy(o.position).applyMatrix4(z),!0===function(t,e){let i=0,s=1;const o=t.z+t.w,r=e.z+e.w,n=-t.z+t.w,a=-e.z+e.w;return o>=0&&r>=0&&n>=0&&a>=0||!(o<0&&r<0||n<0&&a<0)&&(o<0?i=Math.max(i,o/(o-r)):r<0&&(s=Math.min(s,o/(o-r))),n<0?i=Math.max(i,n/(n-a)):a<0&&(s=Math.min(s,n/(n-a))),!(s=-1&&t.z<=1&&(l=function(){if(c===_){const t=new Rt;return U.push(t),_++,c++,t}return U[c++]}(),l.id=e.id,l.x=t.x*s,l.y=t.y*s,l.z=t.z,l.renderOrder=e.renderOrder,l.object=e,l.rotation=e.rotation,l.scale.x=e.scale.x*Math.abs(l.x-(t.x+i.projectionMatrix.elements[0])/(t.w+i.projectionMatrix.elements[12])),l.scale.y=e.scale.y*Math.abs(l.y-(t.y+i.projectionMatrix.elements[5])/(t.w+i.projectionMatrix.elements[13])),l.material=e.material,g.elements.push(l))}function N(t,e){return t.renderOrder!==e.renderOrder?t.renderOrder-e.renderOrder:t.z!==e.z?e.z-t.z:t.id!==e.id?t.id-e.id:0}this.projectScene=function(t,i,o,r){n=0,h=0,c=0,g.elements.length=0,!0===t.matrixWorldAutoUpdate&&t.updateMatrixWorld(),null===i.parent&&!0===i.matrixWorldAutoUpdate&&i.updateMatrixWorld(),M.copy(i.matrixWorldInverse),w.multiplyMatrices(i.projectionMatrix,M),j.setFromProjectionMatrix(w),e=0,g.objects.length=0,g.lights.length=0,F(t),!0===o&&g.objects.sort(N);const a=g.objects;for(let t=0,e=a.length;t0)for(let o=0;o0)for(let s=0;s0;)I.removeChild(I.childNodes[0])}function N(t){return null!==f?t.toFixed(f):t}function Y(t,e,i){let s=e.scale.x*n,o=e.scale.y*a;i.isPointsMaterial&&(s*=i.size,o*=i.size);const r="M"+N(t.x-.5*s)+","+N(t.y-.5*o)+"h"+N(s)+"v"+N(o)+"h"+N(-s)+"z";let h="";(i.isSpriteMaterial||i.isPointsMaterial)&&(h="fill:"+i.color.getStyle(g.outputColorSpace)+";fill-opacity:"+i.opacity),H(h,r)}function K(t,e,i){const s="M"+N(t.positionScreen.x)+","+N(t.positionScreen.y)+"L"+N(e.positionScreen.x)+","+N(e.positionScreen.y);if(i.isLineBasicMaterial){let t="fill:none;stroke:"+i.color.getStyle(g.outputColorSpace)+";stroke-opacity:"+i.opacity+";stroke-width:"+i.linewidth+";stroke-linecap:"+i.linecap;i.isLineDashedMaterial&&(t=t+";stroke-dasharray:"+i.dashSize+","+i.gapSize),H(t,s)}}function V(t,e,s,o,r){g.info.render.vertices+=3,g.info.render.faces++;const n="M"+N(t.positionScreen.x)+","+N(t.positionScreen.y)+"L"+N(e.positionScreen.x)+","+N(e.positionScreen.y)+"L"+N(s.positionScreen.x)+","+N(s.positionScreen.y)+"z";let a="";r.isMeshBasicMaterial?(y.copy(r.color),r.vertexColors&&y.multiply(o.color)):r.isMeshLambertMaterial||r.isMeshPhongMaterial||r.isMeshStandardMaterial?(x.copy(r.color),r.vertexColors&&x.multiply(o.color),y.copy(M),R.copy(t.positionWorld).add(e.positionWorld).add(s.positionWorld).divideScalar(3),function(t,e,i,s){for(let o=0,r=t.length;o1)continue;if(l.positionScreen.z<-1||l.positionScreen.z>1)continue;if(c.positionScreen.z<-1||c.positionScreen.z>1)continue;h.positionScreen.x*=n,h.positionScreen.y*=-a,l.positionScreen.x*=n,l.positionScreen.y*=-a,c.positionScreen.x*=n,c.positionScreen.y*=-a,this.overdraw>0&&(Z(h.positionScreen,l.positionScreen,this.overdraw),Z(l.positionScreen,c.positionScreen,this.overdraw),Z(c.positionScreen,h.positionScreen,this.overdraw)),b.setFromPoints([h.positionScreen,l.positionScreen,c.positionScreen]),!0===v.intersectsBox(b)&&V(h,l,c,i,s)}}X(),s.traverseVisible(function(t){if(t.isSVGObject){if(E.setFromMatrixPosition(t.matrixWorld),E.applyMatrix4(U),E.z<-1||E.z>1)return;const e=E.x*n,i=-E.y*a,s=t.node;s.setAttribute("transform","translate("+e+","+i+")"),I.appendChild(s)}})}}}export{pt as CopyShader,xt as EffectComposer,A as Font,wt as LuminosityHighPassShader,bt as MaskPass,tt as OrbitControls,mt as Pass,St as RenderPass,jt as SVGRenderer,vt as ShaderPass,Mt as SimplexNoise,L as TextGeometry,Tt as UnrealBloomPass}; diff --git a/modules/base/BasePainter.mjs b/modules/base/BasePainter.mjs index 4552059d9..f64398ca0 100644 --- a/modules/base/BasePainter.mjs +++ b/modules/base/BasePainter.mjs @@ -1,8 +1,13 @@ import { select as d3_select } from '../d3.mjs'; -import { settings, internals, isNodeJs, isFunc, isStr, isObject, btoa_func, getDocument, source_dir, loadScript, httpRequest } from '../core.mjs'; -import { detectFont, addCustomFont, getCustomFont, FontHandler } from './FontHandler.mjs'; -import { approximateLabelWidth, replaceSymbolsInTextNode } from './latex.mjs'; -import { getColor } from './colors.mjs'; +import { settings, internals, isNodeJs, isFunc, isStr, isObject, btoa_func, getDocument } from '../core.mjs'; +import { getColor, addColor } from './colors.mjs'; + +/** @summary Standard prefix for SVG file context as data url + * @private */ +const prSVG = 'data:image/svg+xml;charset=utf-8,', +/** @summary Standard prefix for JSON file context as data url + * @private */ + prJSON = 'data:application/json;charset=utf-8,'; /** @summary Returns visible rect of element @@ -22,7 +27,8 @@ function getElementRect(elem, sizearg) { const styleValue = name => { let value = elem.style(name); - if (!value || !isStr(value)) return 0; + if (!value || !isStr(value)) + return 0; value = parseFloat(value.replace('px', '')); return !Number.isFinite(value) ? 0 : Math.round(value); }; @@ -53,7 +59,8 @@ function getElementRect(elem, sizearg) { /** @summary Calculate absolute position of provided element in canvas * @private */ function getAbsPosInCanvas(sel, pos) { - if (!pos) return pos; + if (!pos) + return pos; while (!sel.empty() && !sel.classed('root_canvas')) { const cl = sel.attr('class'); @@ -74,57 +81,76 @@ function getAbsPosInCanvas(sel, pos) { * @return {string|Array} - converted value or array with value and actual format * @private */ function floatToString(value, fmt, ret_fmt) { - if (!fmt) fmt = '6.4g'; + if (!fmt) + fmt = '6.4g'; + else if (fmt === 'g') + fmt = '7.5g'; fmt = fmt.trim(); const len = fmt.length; if (len < 2) return ret_fmt ? [value.toFixed(4), '6.4f'] : value.toFixed(4); - const last = fmt[len-1]; - fmt = fmt.slice(0, len-1); + + const kind = fmt[len - 1].toLowerCase(), + compact = (len > 1) && (fmt[len - 2] === 'c') ? 'c' : ''; + fmt = fmt.slice(0, len - (compact ? 2 : 1)); + + if (kind === 'g') { + const se = floatToString(value, fmt + 'ce', true), + sg = floatToString(value, fmt + 'cf', true), + res = se[0].length < sg[0].length || ((sg[0] === '0') && value) ? se : sg; + return ret_fmt ? res : res[0]; + } + let isexp, prec = fmt.indexOf('.'); - prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec+1)); - if (!Number.isInteger(prec) || (prec <= 0)) prec = 4; - - let 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')) { - const se = floatToString(value, fmt+'Q', true); - let sg = floatToString(value, fmt+'W', true); - if (se[0].length < sg[0].length) sg = se; - return ret_fmt ? sg : sg[0]; - } else { - isexp = false; + prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec + 1)); + if (!Number.isInteger(prec) || (prec <= 0)) prec = 4; + + switch (kind) { + case 'e': + isexp = true; + break; + case 'f': + isexp = false; + break; + default: + isexp = false; + prec = 4; } if (isexp) { - // for exponential representation only one significant digit befor point - if (significance) prec--; - if (prec < 0) prec = 0; + let se = value.toExponential(prec); + + if (compact) { + const pnt = se.indexOf('.'), + pe = se.toLowerCase().indexOf('e'); + if ((pnt > 0) && (pe > pnt)) { + let p = pe; + while ((p > pnt) && (se[p - 1] === '0')) + p--; + if (p === pnt + 1) + p--; + if (p !== pe) + se = se.slice(0, p) + se.slice(pe); + } + } - const se = value.toExponential(prec); - return ret_fmt ? [se, `5.${prec}e`] : se; + return ret_fmt ? [se, `${prec + 2}.${prec}${compact}e`] : se; } let sg = value.toFixed(prec); - if (significance) { - // when using fixed representation, one could get 0 - if ((value !== 0) && (Number(sg) === 0) && (prec > 0)) { - prec = 20; sg = value.toFixed(prec); - } - + if (compact) { let l = 0; - while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) l++; + while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) + l++; let diff = sg.length - l - prec; - if (sg.indexOf('.') > l) diff--; + if (sg.indexOf('.') > l) + diff--; - if (diff !== 0) { + if (diff) { prec -= diff; if (prec < 0) prec = 0; @@ -132,9 +158,22 @@ function floatToString(value, fmt, ret_fmt) { prec = 20; sg = value.toFixed(prec); } + + const pnt = sg.indexOf('.'); + if (pnt > 0) { + let p = sg.length; + while ((p > pnt) && (sg[p - 1] === '0')) + p--; + if (p === pnt + 1) + p--; + sg = sg.slice(0, p); + } + + if (sg === '-0') + sg = '0'; } - return ret_fmt ? [sg, '5.'+prec+'f'] : sg; + return ret_fmt ? [sg, `${prec + 2}.${prec}${compact}f`] : sg; } @@ -143,39 +182,75 @@ function floatToString(value, fmt, ret_fmt) { class DrawOptions { constructor(opt) { - this.opt = isStr(opt) ? opt.toUpperCase().trim() : ''; - this.part = ''; + if (isStr(opt)) { + this.origin = opt.trim(); + this.opt = this.origin.toUpperCase(); + } else + this.opt = this.origin = ''; + this.part = this.partO = ''; } - /** @summary Returns true if remaining options are empty or contain only seperators symbols. */ - empty() { - if (this.opt.length === 0) return true; - return this.opt.replace(/[ ;_,]/g, '').length === 0; - } + /** @summary Returns true if remaining options are empty or contain only separators symbols. */ + empty() { return !this.opt ? true : !this.opt.replace(/[ ;_,]/g, ''); } /** @summary Returns remaining part of the draw options. */ remain() { return this.opt; } + /** @summary Remove [pos, pos2) part from the string */ + #cut(pos, pos2) { + this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); + this.origin = this.origin.slice(0, pos) + this.origin.slice(pos2); + } + /** @summary Checks if given option exists */ check(name, postpart) { const pos = this.opt.indexOf(name); - if (pos < 0) return false; - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos + name.length); + if (pos < 0) + return false; + this.#cut(pos, pos + name.length); this.part = ''; - if (!postpart) return true; + if (!postpart) + return true; let pos2 = pos; - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) pos2++; + const is_array = postpart === 'array'; + if (is_array) { + if (this.opt[pos2] !== '[') + return false; + while ((pos2 < this.opt.length) && (this.opt[pos2] !== ']')) + pos2++; + if (++pos2 > this.opt.length) + return false; + } else { + while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) + pos2++; + } if (pos2 > pos) { this.part = this.opt.slice(pos, pos2); - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); + this.partO = this.origin.slice(pos, pos2); + this.#cut(pos, pos2); + } + + if (is_array) { + try { + this.array = JSON.parse(this.part); + } catch { + this.array = undefined; + } + return this.array?.length !== undefined; } if (postpart !== 'color') return true; + if (((this.part.length === 6) || (this.part.length === 8)) && this.part.match(/^[a-fA-F0-9]+/)) { + this.color = addColor('#' + this.part); + return true; + } + this.color = this.partAsInt(1) - 1; - if (this.color >= 0) return true; + if (this.color >= 0) + return true; for (let col = 0; col < 8; ++col) { if (getColor(col).toUpperCase() === this.part) { this.color = col; @@ -185,10 +260,13 @@ class DrawOptions { return false; } + /** @summary Returns (original) part after found options. */ + getPart(origin) { return origin ? this.partO : this.part; } + /** @summary Returns remaining part of found option as integer. */ partAsInt(offset, dflt) { let mult = 1; - const last = this.part ? this.part[this.part.length - 1] : ''; + const last = this.part ? this.part.at(-1) : ''; if (last === 'K') mult = 1e3; else if (last === 'M') @@ -197,7 +275,7 @@ class DrawOptions { mult = 1e9; let val = this.part.replace(/^\D+/g, ''); val = val ? parseInt(val, 10) : Number.NaN; - return !Number.isInteger(val) ? (dflt || 0) : mult*val + (offset || 0); + return !Number.isInteger(val) ? (dflt || 0) : mult * val + (offset || 0); } /** @summary Returns remaining part of found option as float. */ @@ -215,7 +293,8 @@ class DrawOptions { class TRandom { constructor(i) { - if (i !== undefined) this.seed(i); + if (i !== undefined) + this.seed(i); } /** @summary Seed simple random generator */ @@ -231,7 +310,8 @@ class TRandom { /** @summary Produce random value between 0 and 1 */ random() { - if (this.m_z === undefined) return Math.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; let result = ((this.m_z << 16) + this.m_w) & 0xffffffff; @@ -242,7 +322,7 @@ class TRandom { } // class TRandom -/** @summary Build smooth SVG curve uzing Bezier +/** @summary Build smooth SVG curve using Bezier * @desc Reuse code from https://stackoverflow.com/questions/62855310 * @private */ function buildSvgCurve(p, args) { @@ -254,7 +334,8 @@ function buildSvgCurve(p, args) { args.ndig = 0; let npnts = p.length; - if (npnts < 3) args.line = true; + if (npnts < 3) + args.line = true; args.t = args.t ?? 0.2; @@ -263,33 +344,35 @@ function buildSvgCurve(p, args) { args.mindiff = 100; for (let i = 1; i < npnts; i++) { args.maxy = Math.max(args.maxy, p[i].gry); - args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i-1].grx), Math.abs(p[i].gry - p[i-1].gry)); + args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i - 1].grx), Math.abs(p[i].gry - p[i - 1].gry)); } if (args.ndig === undefined) args.ndig = args.mindiff > 20 ? 0 : (args.mindiff > 5 ? 1 : 2); } const end_point = (pnt1, pnt2, sign) => { - const len = Math.sqrt((pnt2.gry - pnt1.gry)**2 + (pnt2.grx - pnt1.grx)**2) * args.t, + const len = Math.sqrt((pnt2.gry - pnt1.gry) ** 2 + (pnt2.grx - pnt1.grx) ** 2) * args.t, a2 = Math.atan2(pnt2.dgry, pnt2.dgrx), - a1 = Math.atan2(sign*(pnt2.gry - pnt1.gry), sign*(pnt2.grx - pnt1.grx)); + a1 = Math.atan2(sign * (pnt2.gry - pnt1.gry), sign * (pnt2.grx - pnt1.grx)); - pnt1.dgrx = len * Math.cos(2*a1 - a2); - pnt1.dgry = len * Math.sin(2*a1 - a2); + pnt1.dgrx = len * Math.cos(2 * a1 - a2); + pnt1.dgry = len * Math.sin(2 * a1 - a2); }, conv = val => { if (!args.ndig || (Math.round(val) === val)) return val.toFixed(0); - let s = val.toFixed(args.ndig), p = s.length-1; - while (s[p] === '0') p--; - if (s[p] === '.') p--; - s = s.slice(0, p+1); + let s = val.toFixed(args.ndig), p1 = s.length - 1; + while (s[p1] === '0') + p1--; + if (s[p1] === '.') + p1--; + s = s.slice(0, p1 + 1); return (s === '-0') ? '0' : s; }; if (args.calc) { for (let i = 1; i < npnts - 1; i++) { - p[i].dgrx = (p[i+1].grx - p[i-1].grx) * args.t; - p[i].dgry = (p[i+1].gry - p[i-1].gry) * args.t; + p[i].dgrx = (p[i + 1].grx - p[i - 1].grx) * args.t; + p[i].dgry = (p[i + 1].gry - p[i - 1].gry) * args.t; } if (npnts > 2) { @@ -309,24 +392,30 @@ function buildSvgCurve(p, args) { let i0 = 1; if (args.qubic) { npnts--; i0++; - path += `Q${conv(p[1].grx-p[1].dgrx)},${conv(p[1].gry-p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; + path += `Q${conv(p[1].grx - p[1].dgrx)},${conv(p[1].gry - p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; } - path += `C${conv(p[i0-1].grx+p[i0-1].dgrx)},${conv(p[i0-1].gry+p[i0-1].dgry)},${conv(p[i0].grx-p[i0].dgrx)},${conv(p[i0].gry-p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; + path += `C${conv(p[i0 - 1].grx + p[i0 - 1].dgrx)},${conv(p[i0 - 1].gry + p[i0 - 1].dgry)},${conv(p[i0].grx - p[i0].dgrx)},${conv(p[i0].gry - p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; // continue with simpler points for (let i = i0 + 1; i < npnts; i++) - path += `S${conv(p[i].grx-p[i].dgrx)},${conv(p[i].gry-p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; + path += `S${conv(p[i].grx - p[i].dgrx)},${conv(p[i].gry - p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; if (args.qubic) - path += `Q${conv(p[npnts].grx-p[npnts].dgrx)},${conv(p[npnts].gry-p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; + path += `Q${conv(p[npnts].grx - p[npnts].dgrx)},${conv(p[npnts].gry - p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; } else if (npnts < 10000) { // build simple curve let acc_x = 0, acc_y = 0, currx = Math.round(p[0].grx), curry = Math.round(p[0].gry); const flush = () => { - if (acc_x) { path += 'h' + acc_x; acc_x = 0; } - if (acc_y) { path += 'v' + acc_y; acc_y = 0; } + if (acc_x) { + path += 'h' + acc_x; + acc_x = 0; + } + if (acc_y) { + path += 'v' + acc_y; + acc_y = 0; + } }; for (let n = 1; n < npnts; ++n) { @@ -337,13 +426,16 @@ function buildSvgCurve(p, args) { flush(); path += `l${dx},${dy}`; } else if (!dx && dy) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) flush(); + if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) + flush(); acc_y += dy; } else if (dx && !dy) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) flush(); + if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) + flush(); acc_x += dx; } - currx += dx; curry += dy; + currx += dx; + curry += dy; } flush(); @@ -367,10 +459,10 @@ function buildSvgCurve(p, args) { if (cminy !== cmaxy) { if (cminy !== curry) - path += `v${cminy-curry}`; - path += `v${cmaxy-cminy}`; + path += `v${cminy - curry}`; + path += `v${cmaxy - cminy}`; if (cmaxy !== prevy) - path += `v${prevy-cmaxy}`; + path += `v${prevy - cmaxy}`; curry = prevy; } const dy = lasty - curry; @@ -378,21 +470,22 @@ function buildSvgCurve(p, args) { path += `l${dx},${dy}`; else path += `h${dx}`; - currx = lastx; curry = lasty; + currx = lastx; + curry = lasty; prevy = cminy = cmaxy = lasty; } if (cminy !== cmaxy) { if (cminy !== curry) - path += `v${cminy-curry}`; - path += `v${cmaxy-cminy}`; + path += `v${cminy - curry}`; + path += `v${cmaxy - cminy}`; if (cmaxy !== prevy) - path += `v${prevy-cmaxy}`; + path += `v${prevy - cmaxy}`; } } if (args.height) - args.close = `L${conv(p[p.length-1].grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; + args.close = `L${conv(p.at(-1).grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; return path; } @@ -405,11 +498,13 @@ function compressSVG(svg) { .replace(/ class="\w*"/g, '') // remove all classes .replace(/ pad="\w*"/g, '') // remove all pad ids .replace(/ title=""/g, '') // remove all empty titles + .replace(/ style=""/g, '') // remove all empty styles .replace(/<\/g>/g, '') // remove all empty groups with transform + .replace(/<\/g>/g, '') // remove all empty groups with transform + .replace(/<\/g>/g, '') // remove hidden title .replace(/<\/g>/g, ''); // remove all empty groups - // remove all empty frame svgs, typically appears in 3D drawings, maybe should be improved in frame painter itself + // remove all empty frame svg, typically appears in 3D drawings, maybe should be improved in frame painter itself svg = svg.replace(/<\/svg>/g, ''); return svg; @@ -423,11 +518,18 @@ function compressSVG(svg) { class BasePainter { + #divid; // either id of DOM element or element itself + #selected_main; // d3.select for dom elements + #hitemname; // item name in the hpainter + #hdrawopt; // draw option in the hpainter + #hpainter; // assigned hpainter + /** @summary constructor * @param {object|string} [dom] - dom element or id of dom element */ constructor(dom) { - this.divid = null; // either id of DOM element or element itself - if (dom) this.setDom(dom); + this.#divid = null; // either id of DOM element or element itself + if (dom) + this.setDom(dom); } /** @summary Assign painter to specified DOM element @@ -436,36 +538,41 @@ class BasePainter { * @protected */ setDom(elem) { if (elem !== undefined) { - this.divid = elem; - delete this._selected_main; + this.#divid = elem; + this.#selected_main = null; } } /** @summary Returns assigned dom element */ - getDom() { - return this.divid; - } + getDom() { return this.#divid; } + + /** @summary Returns argument for draw function */ + getDrawDom() { return this.#divid; } /** @summary Selects main HTML element assigned for drawing - * @desc if main element was layouted, returns main element inside layout + * @desc if main element was layout, returns main element inside layout * @param {string} [is_direct] - if 'origin' specified, returns original element even if actual drawing moved to some other place * @return {object} d3.select object for main element for drawing */ selectDom(is_direct) { - if (!this.divid) return d3_select(null); + if (!this.#divid) + return d3_select(null); - let res = this._selected_main; + let res = this.#selected_main; if (!res) { - if (isStr(this.divid)) { - let id = this.divid; - if (id[0] !== '#') id = '#' + id; + if (isStr(this.#divid)) { + let id = this.#divid; + if (id[0] !== '#') + id = '#' + id; res = d3_select(id); - if (!res.empty()) this.divid = res.node(); + if (!res.empty()) + this.#divid = res.node(); } else - res = d3_select(this.divid); - this._selected_main = res; + res = d3_select(this.#divid); + this.#selected_main = res; } - if (!res || res.empty() || (is_direct === 'origin')) return res; + if (!res || res.empty() || (is_direct === 'origin')) + return res; const use_enlarge = res.property('use_enlarge'), layout = res.property('layout') || 'simple', @@ -483,12 +590,13 @@ class BasePainter { /** @summary Access/change top painter * @private */ - _accessTopPainter(on) { + #accessTopPainter(on) { const chld = this.selectDom().node()?.firstChild; - if (!chld) return null; + if (!chld) + return null; if (on === true) chld.painter = this; - else if (on === false) + else if ((on === false) && (chld.painter === this)) delete chld.painter; return chld.painter; } @@ -496,37 +604,32 @@ class BasePainter { /** @summary Set painter, stored in first child element * @desc Only make sense after first drawing is completed and any child element add to configured DOM * @protected */ - setTopPainter() { - this._accessTopPainter(true); - } + setTopPainter() { this.#accessTopPainter(true); } /** @summary Return top painter set for the selected dom element * @protected */ - getTopPainter() { - return this._accessTopPainter(); - } + getTopPainter() { return this.#accessTopPainter(); } /** @summary Clear reference on top painter * @protected */ - clearTopPainter() { - this._accessTopPainter(false); - } + clearTopPainter() { this.#accessTopPainter(false); } /** @summary Generic method to cleanup painter * @desc Removes all visible elements and all internal data */ cleanup(keep_origin) { this.clearTopPainter(); const origin = this.selectDom('origin'); - if (!origin.empty() && !keep_origin) origin.html(''); - this.divid = null; - delete this._selected_main; + if (!origin.empty() && !keep_origin) + origin.html(''); + this.#divid = null; + this.#selected_main = undefined; - if (isFunc(this._hpainter?.removePainter)) - this._hpainter.removePainter(this); + if (isFunc(this.#hpainter?.removePainter)) + this.#hpainter.removePainter(this); - delete this._hitemname; - delete this._hdrawopt; - delete this._hpainter; + this.#hitemname = undefined; + this.#hdrawopt = undefined; + this.#hpainter = undefined; } /** @summary Checks if draw elements were resized and drawing should be updated @@ -554,11 +657,11 @@ class BasePainter { can_resize = origin.attr('can_resize'); let do_resize = false; - if (can_resize === 'height') - if (height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) do_resize = true; + if ((can_resize === 'height') && height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) + do_resize = true; - if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && - can_resize && can_resize !== 'false') do_resize = true; + if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && can_resize && (can_resize !== 'false')) + do_resize = true; if (do_resize && (enlarge !== 'on')) { // if zero size and can_resize attribute set, change container size @@ -576,6 +679,14 @@ class BasePainter { rect.changed = false; + if (!rect.width && !rect.height && !main.empty() && main.attr('style')) { + const ws = main.style('width'), hs = main.style('height'); + if (isStr(ws) && isStr(hs) && ws.match(/^\d+px$/) && hs.match(/^\d+px$/)) { + rect.width = parseInt(ws.slice(0, ws.length - 2)); + rect.height = parseInt(hs.slice(0, hs.length - 2)); + } + } + if (old_h && old_w && (old_h > 0) && (old_w > 0)) { if ((old_h !== rect.height) || (old_w !== rect.width)) rect.changed = (check_level > 1) || (rect.width / old_w < 0.99) || (rect.width / old_w > 1.01) || (rect.height / old_h < 0.99) || (rect.height / old_h > 1.01); @@ -609,20 +720,25 @@ class BasePainter { origin = this.selectDom('origin'), doc = getDocument(); - if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) return false; + if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) + return false; - if ((action === undefined) || (action === 'verify')) return true; + if ((action === undefined) || (action === 'verify')) + return true; const state = origin.property('use_enlarge') ? 'on' : 'off'; - if (action === 'state') return state; + if (action === 'state') + return state; - if (action === 'toggle') action = (state === 'off'); + if (action === 'toggle') + action = (state === 'off'); let enlarge = d3_select(doc.getElementById('jsroot_enlarge_div')); if ((action === true) && (state !== 'on')) { - if (!enlarge.empty()) return false; + if (!enlarge.empty()) + return false; enlarge = d3_select(doc.body) .append('div') @@ -642,7 +758,7 @@ class BasePainter { } } - while (main.node().childNodes.length > 0) + while (main.node().childNodes.length) enlarge.node().appendChild(main.node().firstChild); origin.property('use_enlarge', true); @@ -650,7 +766,7 @@ class BasePainter { return true; } if ((action === false) && (state !== 'off')) { - while (enlarge.node() && enlarge.node().childNodes.length > 0) + while (enlarge.node()?.childNodes.length) main.node().appendChild(enlarge.node().firstChild); enlarge.remove(); @@ -666,24 +782,24 @@ class BasePainter { * @desc Used by {@link HierarchyPainter} * @private */ setItemName(name, opt, hpainter) { - if (isStr(name)) - this._hitemname = name; - else - delete this._hitemname; - // only upate draw option, never delete. + this.#hitemname = isStr(name) ? name : undefined; + // only update draw option, never delete. if (isStr(opt)) - this._hdrawopt = opt; + this.#hdrawopt = opt; - this._hpainter = hpainter; + this.#hpainter = hpainter; } + /** @summary Returns assigned histogram painter */ + getHPainter() { return this.#hpainter; } + /** @summary Returns assigned item name * @desc Used with {@link HierarchyPainter} to identify drawn item name */ - getItemName() { return this._hitemname ?? null; } + getItemName() { return this.#hitemname ?? null; } /** @summary Returns assigned item draw option * @desc Used with {@link HierarchyPainter} to identify drawn item option */ - getItemDrawOpt() { return this._hdrawopt ?? ''; } + getItemDrawOpt() { return this.#hdrawopt ?? ''; } } // class BasePainter @@ -705,11 +821,22 @@ async function _loadJSDOM() { /** @summary Return translate string for transform attribute of some svg element * @return string or null if x and y are zeros * @private */ -function makeTranslate(g, x, y) { +function makeTranslate(g, x, y, scale = 1) { if (!isObject(g)) { - y = x; x = g; g = null; + scale = y; + y = x; + x = g; + g = null; + } + let res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); + if (scale && scale !== 1) { + if (res) + res += ' '; + else + res = ''; + res += `scale(${scale.toFixed(3)})`; } - const res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); + return g ? g.attr('transform', res) : res; } @@ -727,200 +854,75 @@ function addHighlightStyle(elem, drag) { } } -/** @summary Create pdf for existing SVG element - * @return {Promise} with produced PDF file as url string - * @private */ -async function svgToPDF(args, as_buffer) { - const nodejs = isNodeJs(); - let _jspdf, _svg2pdf, need_symbols = false; - - const pr = nodejs - ? import('../../scripts/jspdf.es.min.js').then(h1 => { _jspdf = h1; return import('../../scripts/svg2pdf.es.min.js'); }).then(h2 => { _svg2pdf = h2; }) - : loadScript(source_dir + 'scripts/jspdf.umd.min.js').then(() => loadScript(source_dir + 'scripts/svg2pdf.umd.min.js')).then(() => { _jspdf = globalThis.jspdf; _svg2pdf = globalThis.svg2pdf; }), - restore_fonts = [], restore_dominant = [], restore_text = [], - node_transform = args.node.getAttribute('transform'), custom_fonts = {}; - - if (args.reset_tranform) - args.node.removeAttribute('transform'); - - return pr.then(() => { - d3_select(args.node).selectAll('g').each(function() { - if (this.hasAttribute('font-family')) { - const name = this.getAttribute('font-family'); - if (name === 'Courier New') { - this.setAttribute('font-family', 'courier'); - if (!args.can_modify) restore_fonts.push(this); // keep to restore it - } - } - }); - - d3_select(args.node).selectAll('text').each(function() { - if (this.hasAttribute('dominant-baseline')) { - this.setAttribute('dy', '.2em'); // slightly different as in plain text - this.removeAttribute('dominant-baseline'); - if (!args.can_modify) restore_dominant.push(this); // keep to restore it - } else if (args.can_modify && nodejs && this.getAttribute('dy') === '.4em') - this.setAttribute('dy', '.2em'); // better allignment in PDF - - if (replaceSymbolsInTextNode(this)) { - need_symbols = true; - if (!args.can_modify) restore_text.push(this); // keep to restore it - } - }); - - if (nodejs) { - const doc = internals.nodejs_document; - doc.oldFunc = doc.createElementNS; - globalThis.document = doc; - globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet; - globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule; - doc.createElementNS = function(ns, kind) { - const res = doc.oldFunc(ns, kind); - res.getBBox = function() { - let width = 50, height = 10; - if (this.tagName === 'text') { - // TODO: use jsDOC fonts for label width estimation - const font = detectFont(this); - width = approximateLabelWidth(this.textContent, font); - height = font.size; - } - - return { x: 0, y: 0, width, height }; - }; - return res; - }; - } - - // eslint-disable-next-line new-cap - const doc = new _jspdf.jsPDF({ - orientation: 'landscape', - unit: 'px', - format: [args.width + 10, args.height + 10] - }); - - // add custom fonts to PDF document, only TTF format supported - d3_select(args.node).selectAll('style').each(function() { - const fh = this.$fonthandler; - if (!fh || custom_fonts[fh.name] || (fh.format !== 'ttf')) return; - const filename = fh.name.toLowerCase().replace(/\s/g, '') + '.ttf'; - doc.addFileToVFS(filename, fh.base64); - doc.addFont(filename, fh.name, 'normal', 'normal', (fh.name === 'symbol') ? 'StandardEncoding' : 'Identity-H'); - custom_fonts[fh.name] = true; - }); - - let pr2 = Promise.resolve(true); - - if (need_symbols && !custom_fonts.symbol) { - if (!getCustomFont('symbol')) { - pr2 = nodejs - ? import('fs').then(fs => { - const base64 = fs.readFileSync('../../fonts/symbol.ttf').toString('base64'); - console.log('reading symbol.ttf', base64.length); - addCustomFont(25, 'symbol', 'ttf', base64); - }) - : httpRequest(source_dir+'fonts/symbol.ttf', 'bin').then(buf => { - const base64 = btoa_func(buf); - addCustomFont(25, 'symbol', 'ttf', base64); - }); - } - - pr2 = pr2.then(() => { - const fh = getCustomFont('symbol'), - handler = new FontHandler(1242, 10); - handler.name = 'symbol'; - handler.base64 = fh.base64; - handler.addCustomFontToSvg(d3_select(args.node)); - doc.addFileToVFS('symbol.ttf', fh.base64); - doc.addFont('symbol.ttf', 'symbol', 'normal', 'normal', 'StandardEncoding' /* 'WinAnsiEncoding' */); - }); - } - - return pr2.then(() => _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height })) - .then(() => { - if (args.reset_tranform && !args.can_modify && node_transform) - args.node.setAttribute('transform', node_transform); - - restore_fonts.forEach(node => node.setAttribute('font-family', 'Courier New')); - restore_dominant.forEach(node => { - node.setAttribute('dominant-baseline', 'middle'); - node.removeAttribute('dy'); - }); - - restore_text.forEach(node => { node.innerHTML = node.$originalHTML; }); - - const res = as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); - if (nodejs) { - globalThis.document = undefined; - globalThis.CSSStyleSheet = undefined; - globalThis.CSSStyleRule = undefined; - internals.nodejs_document.createElementNS = internals.nodejs_document.oldFunc; - if (as_buffer) return Buffer.from(res); - } - return res; - }); - }); -} - - /** @summary Create image based on SVG * @param {string} svg - svg code of the image * @param {string} [image_format] - image format like 'png', 'jpeg' or 'webp' - * @param {boolean} [as_buffer] - return Buffer object for image + * @param {Objects} [args] - optional arguments + * @param {boolean} [args.as_buffer] - return image as buffer * @return {Promise} with produced image in base64 form or as Buffer (or canvas when no image_format specified) * @private */ -async function svgToImage(svg, image_format, as_buffer) { +async function svgToImage(svg, image_format, args) { + if ((args === true) || (args === false)) + args = { as_buffer: args }; + if (image_format === 'svg') return svg; if (image_format === 'pdf') - return svgToPDF(svg, as_buffer); + return internals.makePDF ? internals.makePDF(svg, args) : null; // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex const doctype = ''; - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x'+p1); - return c === '%' ? '%25' : c; - }); - svg = decodeURIComponent(svg); - - const img_src = 'data:image/svg+xml;base64,' + btoa_func(svg); if (isNodeJs()) { + svg = encodeURIComponent(doctype + svg); + svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { + const c = String.fromCharCode('0x' + p1); + return c === '%' ? '%25' : c; + }); + + const img_src = 'data:image/svg+xml;base64,' + btoa_func(decodeURIComponent(svg)); + return import('canvas').then(async handle => { return handle.default.loadImage(img_src).then(img => { const canvas = handle.default.createCanvas(img.width, img.height); canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); - if (as_buffer) return canvas.toBuffer('image/' + image_format); + if (args?.as_buffer) + return canvas.toBuffer('image/' + image_format); return image_format ? canvas.toDataURL('image/' + image_format) : canvas; }); }); } + const img_src = URL.createObjectURL(new Blob([doctype + svg], { type: 'image/svg+xml;charset=utf-8' })); + return new Promise(resolveFunc => { const image = document.createElement('img'); image.onload = function() { + URL.revokeObjectURL(img_src); + const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); - if (as_buffer && image_format) + if (args?.as_buffer && image_format) canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format); else resolveFunc(image_format ? canvas.toDataURL('image/' + image_format) : canvas); }; image.onerror = function(arg) { + URL.revokeObjectURL(img_src); console.log(`IMAGE ERROR ${arg}`); resolveFunc(null); }; - image.src = img_src; + image.setAttribute('src', img_src); }); } @@ -936,21 +938,30 @@ function getTDatime(dt) { return new Date(Date.UTC(y, m, d, h, min, s)); } -/** @summary Convert Date object into string used preconfigured time zone +/** @summary Convert Date object into string used configured time zone * @desc Time zone stored in settings.TimeZone */ function convertDate(dt) { let res = ''; if (settings.TimeZone && isStr(settings.TimeZone)) { - try { - res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); - } catch (err) { - res = ''; - } + try { + res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); + } catch { + res = ''; + } } return res || dt.toLocaleString('en-GB'); } -export { getElementRect, getAbsPosInCanvas, getTDatime, convertDate, - DrawOptions, TRandom, floatToString, buildSvgCurve, compressSVG, +/** @summary Box decorations + * @private */ +function getBoxDecorations(xx, yy, ww, hh, bmode, pww, phh) { + const side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2 * pww - ww}v${hh - 2 * phh}l${-pww},${phh}z`, + side2 = `M${xx + ww},${yy + hh}v${-hh}l${-pww},${phh}v${hh - 2 * phh}h${2 * pww - ww}l${-pww},${phh}z`; + return bmode > 0 ? [side1, side2] : [side2, side1]; +} + + +export { prSVG, prJSON, getElementRect, getAbsPosInCanvas, getTDatime, convertDate, + DrawOptions, TRandom, floatToString, buildSvgCurve, compressSVG, getBoxDecorations, BasePainter, _loadJSDOM, makeTranslate, addHighlightStyle, svgToImage }; diff --git a/modules/base/FontHandler.mjs b/modules/base/FontHandler.mjs index 558e695bd..6222e8290 100644 --- a/modules/base/FontHandler.mjs +++ b/modules/base/FontHandler.mjs @@ -1,25 +1,88 @@ -const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'Symbol', kWingdings = 'Wingdings', -// average width taken from symbols.html, counted only for letters and digits -root_fonts = [null, // index 0 not exists - { n: kTimes, s: 'italic', aw: 0.5314 }, - { n: kTimes, w: 'bold', aw: 0.5809 }, - { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, - { n: kArial, aw: 0.5778 }, - { n: kArial, s: 'oblique', aw: 0.5783 }, - { n: kArial, w: 'bold', aw: 0.6034 }, - { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, - { n: kCourier, aw: 0.6003 }, - { n: kCourier, s: 'oblique', aw: 0.6004 }, - { n: kCourier, w: 'bold', aw: 0.6003 }, - { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, - { n: kSymbol, aw: 0.5521 }, - { n: kTimes, aw: 0.5521 }, - { n: kWingdings, aw: 0.5664 }, - { n: kSymbol, s: 'italic', aw: 0.5314 }, - { n: kVerdana, aw: 0.5664 }, - { n: kVerdana, s: 'italic', aw: 0.5495 }, - { n: kVerdana, w: 'bold', aw: 0.5748 }, - { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }]; +import { isNodeJs, httpRequest, btoa_func, source_dir, settings, isObject } from '../core.mjs'; + + +const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'RootSymbol', kWingdings = 'Wingdings', + // average width taken from symbols.html, counted only for letters and digits + root_fonts = [null, // index 0 not exists + { n: kTimes, s: 'italic', aw: 0.5314 }, + { n: kTimes, w: 'bold', aw: 0.5809 }, + { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, + { n: kArial, aw: 0.5778 }, + { n: kArial, s: 'oblique', aw: 0.5783 }, + { n: kArial, w: 'bold', aw: 0.6034 }, + { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, + { n: kCourier, aw: 0.6003 }, + { n: kCourier, s: 'oblique', aw: 0.6004 }, + { n: kCourier, w: 'bold', aw: 0.6003 }, + { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, + { n: kSymbol, aw: 0.5521, file: 'symbol.ttf' }, + { n: kTimes, aw: 0.5521 }, + { n: kWingdings, aw: 0.5664, file: 'wingding.ttf' }, + { n: kSymbol, s: 'oblique', aw: 0.5314, file: 'symbol.ttf' }, + { n: kVerdana, aw: 0.5664 }, + { n: kVerdana, s: 'italic', aw: 0.5495 }, + { n: kVerdana, w: 'bold', aw: 0.5748 }, + { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }], + // list of loaded fonts including handling of multiple simultaneous requests + gFontFiles = {}; + +/** @summary Read font file from some pre-configured locations + * @return {Promise} with base64 code of the font + * @private */ +async function loadFontFile(fname) { + let entry = gFontFiles[fname]; + if (entry?.base64) + return entry?.base64; + + if (entry?.promises !== undefined) { + return new Promise(resolveFunc => { + entry.promises.push(resolveFunc); + }); + } + + entry = gFontFiles[fname] = { promises: [] }; + + const locations = []; + if (fname.indexOf('/') >= 0) + locations.push(''); // just use file name as is + else { + locations.push(source_dir + 'fonts/'); + if (isNodeJs()) + locations.push('../../fonts/'); + else if (source_dir.indexOf('jsrootsys/') >= 0) { + locations.unshift(source_dir.replace(/jsrootsys/g, 'rootsys_fonts')); + locations.unshift(source_dir.replace(/jsrootsys/g, 'rootsys/fonts')); + } + } + + function completeReading(base64) { + entry.base64 = base64; + const arr = entry.promises; + delete entry.promises; + arr.forEach(func => func(base64)); + return base64; + } + + async function tryNext() { + if (!locations.length) { + completeReading(null); + throw new Error(`Fail to load ${fname} font`); + } + let path = locations.shift() + fname; + console.log('loading font', path); + const pr = isNodeJs() ? import('fs').then(fs => { + const prefix = 'file://' + (process?.platform === 'win32' ? '/' : ''); + if (path.indexOf(prefix) === 0) + path = path.slice(prefix.length); + return fs.readFileSync(path).toString('base64'); + }) : httpRequest(path, 'bin').then(buf => btoa_func(buf)); + + return pr.then(res => { return res ? completeReading(res) : tryNext();}).catch(() => tryNext()); + } + + return tryNext(); +} + /** * @summary Helper class for font handling @@ -35,20 +98,45 @@ class FontHandler { this.scaled = true; } - this.size = Math.round(size || 11); + this.size = Math.round(size); this.scale = scale; + this.index = 0; this.func = this.setFont.bind(this); - const indx = (fontIndex && Number.isInteger(fontIndex)) ? Math.floor(fontIndex / 10) : 0, - cfg = root_fonts[indx]; + let cfg; - if (cfg) + if (fontIndex && isObject(fontIndex)) + cfg = fontIndex; + else { + if (fontIndex && Number.isInteger(fontIndex)) + this.index = Math.floor(fontIndex / 10); + cfg = root_fonts[this.index]; + } + + if (cfg) { + this.cfg = cfg; this.setNameStyleWeight(cfg.n, cfg.s, cfg.w, cfg.aw, cfg.format, cfg.base64); - else + } else this.setNameStyleWeight(kArial); } + /** @summary Should returns true if font has to be loaded before + * @private */ + needLoad() { return this.cfg?.file && !this.isSymbol && !this.base64; } + + /** @summary Async function to load font + * @private */ + async load() { + if (!this.needLoad()) + return true; + return loadFontFile(this.cfg.file).then(base64 => { + this.cfg.base64 = this.base64 = base64; + this.format = 'ttf'; + return Boolean(base64); + }); + } + /** @summary Directly set name, style and weight for the font * @private */ setNameStyleWeight(name, style, weight, aver_width, format, base64) { @@ -58,7 +146,7 @@ class FontHandler { this.aver_width = aver_width || (weight ? 0.58 : 0.55); this.format = format; // format of custom font, ttf by default this.base64 = base64; // indication of custom font - if ((this.name === kSymbol) || (this.name === kWingdings)) { + if (!settings.LoadSymbolTtf && ((this.name === kSymbol) || (this.name === kWingdings))) { this.isSymbol = this.name; this.name = kTimes; } else @@ -70,6 +158,11 @@ class FontHandler { this.painter = painter; } + /** @summary Force setting of style and weight, used in latex */ + setUseFullStyle(flag) { + this.full_style = flag; + } + /** @summary Assigns font-related attributes */ addCustomFontToSvg(svg) { if (!this.base64 || !this.name) @@ -80,10 +173,9 @@ class FontHandler { defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); const entry = defs.selectChild('.' + clname); if (entry.empty()) { - console.log('Adding style entry for class', clname); defs.append('style') .attr('class', clname) - .property('$fonthandler', this) + .property('$fontcfg', this.cfg || null) .text(`@font-face { font-family: "${this.name}"; font-weight: normal; font-style: normal; src: url(data:application/font-${fmt};charset=utf-8;base64,${this.base64}); }`); } } @@ -95,9 +187,14 @@ class FontHandler { selection.attr('font-family', this.name) .attr('font-size', this.size) - .attr('xml:space', 'preserve') - .attr('font-weight', this.weight || null) - .attr('font-style', this.style || null); + .attr(':xml:space', 'preserve'); + this.setFontStyle(selection); + } + + /** @summary Assigns only font style attributes */ + setFontStyle(selection) { + selection.attr('font-weight', this.weight || (this.full_style ? 'normal' : null)) + .attr('font-style', this.style || (this.full_style ? 'normal' : null)); } /** @summary Set font size (optional) */ @@ -112,11 +209,12 @@ class FontHandler { /** @summary Set text angle (optional) */ setAngle(angle) { this.angle = angle; } - /** @summary Allign angle to step raster, add optional offset */ + /** @summary Align angle to step raster, add optional offset */ roundAngle(step, offset) { this.angle = parseInt(this.angle || 0); - if (!Number.isInteger(this.angle)) this.angle = 0; - this.angle = Math.round(this.angle/step) * step + (offset || 0); + if (!Number.isInteger(this.angle)) + this.angle = 0; + this.angle = Math.round(this.angle / step) * step + (offset || 0); if (this.angle < 0) this.angle += 360; else if (this.angle >= 360) @@ -127,7 +225,7 @@ class FontHandler { clearFont(selection) { selection.attr('font-family', null) .attr('font-size', null) - .attr('xml:space', null) + .attr(':xml:space', null) .attr('font-weight', null) .attr('font-style', null); } @@ -143,8 +241,10 @@ class FontHandler { * @private */ getFontHtml() { let res = Math.round(this.size) + 'pt ' + this.name; - if (this.weight) res += ' ' + this.weight; - if (this.style) res += ' ' + this.style; + if (this.weight) + res += ' ' + this.weight; + if (this.style) + res += ' ' + this.style; return res; } @@ -172,43 +272,37 @@ function getCustomFont(name) { /** @summary Try to detect and create font handler for SVG text node * @private */ -function detectFont(node) { +function detectPdfFont(node) { const sz = node.getAttribute('font-size'), - family = node.getAttribute('font-family'), p = sz.indexOf('px'), sz_pixels = p > 0 ? Number.parseInt(sz.slice(0, p)) : 12; - let style = node.getAttribute('font-style'), - weight = node.getAttribute('font-weight'), - fontIndx = null, name = ''; + + let family = node.getAttribute('font-family'), + style = node.getAttribute('font-style'), + weight = node.getAttribute('font-weight'); + + if (family === 'times') + family = kTimes; + else if (family === 'symbol') + family = kSymbol; + else if (family === 'arial') + family = kArial; + else if (family === 'verdana') + family = kVerdana; if (weight === 'normal') weight = ''; - else if (weight === 'bold') - name += 'b'; if (style === 'normal') style = ''; - else if (style === 'italic') - name += 'i'; - else if (style === 'oblique') - name += 'o'; - - if (family === 'arial') - name += 'Arial'; - else if (family === 'times') - name += 'Times New Roman'; - else if (family === 'verdana') - name += 'Verdana'; - for (let n = 1; n < root_fonts.length; ++n) { - if (name === root_fonts[n]) { - fontIndx = n*10 + 2; - break; - } - } + const fcfg = root_fonts.find(elem => { + return (elem?.n === family) && + ((!weight && !elem.w) || (elem.w === weight)) && + ((!style && !elem.s) || (elem.s === style)); + }); - const handler = new FontHandler(fontIndx, sz_pixels); - if (!fontIndx) - handler.setNameStyleWeight(family, style, weight); - return handler; + return new FontHandler(fcfg || root_fonts[13], sz_pixels); } -export { FontHandler, addCustomFont, getCustomFont, detectFont }; + +export { kArial, kCourier, kSymbol, kWingdings, kTimes, + FontHandler, addCustomFont, getCustomFont, detectPdfFont }; diff --git a/modules/base/ObjectPainter.mjs b/modules/base/ObjectPainter.mjs index 5adcc5552..329862e20 100644 --- a/modules/base/ObjectPainter.mjs +++ b/modules/base/ObjectPainter.mjs @@ -1,7 +1,7 @@ -import { select as d3_select, pointer as d3_pointer } from '../d3.mjs'; +import { pointer as d3_pointer } from '../d3.mjs'; import { settings, constants, internals, isNodeJs, isBatchMode, getPromise, BIT, - prROOT, clTObjString, clTAxis, isObject, isFunc, isStr, getDocument } from '../core.mjs'; -import { isPlainText, producePlainText, produceLatex, produceMathjax, typesetMathjax } from './latex.mjs'; + getKindForType, clTObjString, clTAxis, isObject, isFunc, isStr, getDocument, urlClassPrefix } from '../core.mjs'; +import { isPlainText, producePlainText, produceLatex, produceMathjax, typesetMathjax, approximateLabelWidth } from './latex.mjs'; import { getElementRect, BasePainter, makeTranslate } from './BasePainter.mjs'; import { TAttMarkerHandler } from './TAttMarkerHandler.mjs'; import { TAttFillHandler } from './TAttFillHandler.mjs'; @@ -11,6 +11,17 @@ import { FontHandler } from './FontHandler.mjs'; import { getRootColors } from './colors.mjs'; +/** @summary returns true if pad painter @private */ +function isPadPainter(p) { + return isFunc(p?.getRootPad) && isFunc(p?.forEachPainterInPad); +} + +/** @summary returns canvas painter from DOM element @private */ +function getDomCanvasPainter(dom) { + const elem = dom?.select('.root_canvas'); + return !elem || elem.empty() ? null : elem.property('pad_painter'); +} + /** * @summary Painter class for ROOT objects * @@ -18,39 +29,67 @@ import { getRootColors } from './colors.mjs'; class ObjectPainter extends BasePainter { + #draw_object; // drawn object + #draw_g; // element for object drawing + #pad_painter_ref; // reference of pad painter + #main_painter; // WeakRef to main painter in the pad + #primary_ref; // reference of primary painter - if any + #snapid; // assigned online identifier + #is_primary; // if primary painter + #secondary_id; // id of this painter in relation to primary painter + #options; // current options object + #options_store; // stored draw options used to check changes + #user_tooltip_handler; // configured user tooltip handler + #user_tooltip_timeout; // timeout configured with tooltip handler + #user_toottip_handle; // timeout handle processing user tooltip + #user_context_menu; // function for user context menu + #special_draw_area; // current special draw area like projection + #root_colors; // custom colors list + #fillatt; // fill attribute + #lineatt; // line attribute + #markeratt; // marker attribute + #textatt; // text attribute + /** @summary constructor - * @param {object|string} dom - dom element or identifier + * @param {object|string} dom - dom element or identifier or pad painter * @param {object} obj - object to draw * @param {string} [opt] - object draw options */ constructor(dom, obj, opt) { - super(dom); - // this.draw_g = undefined; // container for all drawn objects - // this._main_painter = undefined; // main painter in the correspondent pad - this.pad_name = dom ? this.selectCurrentPad() : ''; // name of pad where object is drawn + const pp = isPadPainter(dom) ? dom : null; + + super(pp?.getDom() ?? dom); + + this.setPadPainter(pp); + + this.#draw_g = undefined; // container for all drawn objects this.assignObject(obj); if (isStr(opt)) - this.options = { original: opt }; + this.#options = { original: opt }; } /** @summary Assign object to the painter * @protected */ - assignObject(obj) { - if (isObject(obj)) - this.draw_object = obj; - else - delete this.draw_object; - } + assignObject(obj) { this.#draw_object = isObject(obj) ? obj : null; } - /** @summary Assigns pad name where element will be drawn - * @desc Should happend before first draw of element is performed, only for special use case - * @param {string} [pad_name] - on which subpad element should be draw, if not specified - use current + /** @summary Returns drawn object */ + getObject() { return this.#draw_object; } + + /** @summary Assign new pad painter * @protected */ - setPadName(pad_name) { - this.pad_name = isStr(pad_name) ? pad_name : this.selectCurrentPad(); - } + setPadPainter(pp) { this.#pad_painter_ref = pp ? new WeakRef(pp) : undefined; } - /** @summary Returns pad name where object is drawn */ - getPadName() { return this.pad_name || ''; } + /** @summary returns pad painter where object is drawn + * @protected */ + getPadPainter() { return this.#pad_painter_ref?.deref(); } + + /** @summary returns canvas painter + * @protected */ + getCanvPainter() { + let pp = this.getPadPainter(); + while (pp && !pp.isCanvas()) + pp = pp.getPadPainter(); + return pp; + } /** @summary Indicates that drawing runs in batch mode * @private */ @@ -59,7 +98,13 @@ class ObjectPainter extends BasePainter { /** @summary Assign snapid to the painter * @desc Identifier used to communicate with server side and identifies object on the server * @private */ - assignSnapId(id) { this.snapid = id; } + assignSnapId(id) { this.#snapid = id; } + + /** @summary Provides identifier on server for requested sub-element */ + getSnapId(subelem) { return !this.#snapid ? '' : (this.#snapid + (subelem ? '#' + subelem : '')); } + + /** @summary Returns true if snapid was assigned */ + hasSnapId() { return this.#snapid !== undefined; } /** @summary Generic method to cleanup painter. * @desc Remove object drawing and (in case of main painter) also main HTML components @@ -71,24 +116,24 @@ class ObjectPainter extends BasePainter { if (this.isMainPainter()) { const pp = this.getPadPainter(); - if (!pp || (pp.normal_canvas === false)) + if (!pp || pp.isCanvas('auto')) keep_origin = false; } // cleanup all existing references - delete this.pad_name; - delete this._main_painter; - this.draw_object = null; - delete this.snapid; + this.#pad_painter_ref = undefined; + this.#main_painter = null; + this.#draw_object = null; + this.#snapid = undefined; + this.#is_primary = undefined; + this.#primary_ref = undefined; + this.#secondary_id = undefined; // remove attributes objects (if any) - delete this.fillatt; - delete this.lineatt; - delete this.markeratt; - delete this.bins; - delete this.root_colors; - delete this.options; - delete this.options_store; + this.deleteAttr(); + this.#root_colors = undefined; + this.#options = undefined; + this.#options_store = undefined; // remove extra fields from v7 painters delete this.rstyle; @@ -97,9 +142,6 @@ class ObjectPainter extends BasePainter { super.cleanup(keep_origin); } - /** @summary Returns drawn object */ - getObject() { return this.draw_object; } - /** @summary Returns drawn object name */ getObjectName() { return this.getObject()?.fName ?? ''; } @@ -111,59 +153,101 @@ class ObjectPainter extends BasePainter { * @protected */ matchObjectType(arg) { const clname = this.getClassName(); - if (!arg || !clname) return false; - if (isStr(arg)) return arg === clname; - if (isStr(arg._typename)) return arg._typename === clname; - return clname.match(arg); + if (!arg || !clname) + return false; + if (isStr(arg)) + return arg === clname; + if (isStr(arg._typename)) + return arg._typename === clname; + return Boolean(clname.match(arg)); } /** @summary Change item name - * @desc When available, used for svg:title proprty + * @desc When available, used for svg:title property * @private */ setItemName(name, opt, hpainter) { super.setItemName(name, opt, hpainter); - if (this.no_default_title || !name) return; + if (this._no_default_title || !name) + return; const can = this.getCanvSvg(); - if (!can.empty()) can.select('title').text(name); - else this.selectDom().attr('title', name); + if (!can.empty()) + can.select('title').text(name); + else + this.selectDom().attr('title', name); const cp = this.getCanvPainter(); if (cp && ((cp === this) || (this.isMainPainter() && (cp === this.getPadPainter())))) cp.drawItemNameOnCanvas(name); } - /** @summary Store actual this.options together with original string + /** @summary Create options and copy new args + * @return options + * @private */ + setOptions(new_options, as_is) { + if (as_is) + this.#options = new_options; + else { + if (!this.#options) + this.#options = {}; + Object.assign(this.#options, new_options); + } + return this.#options; + } + + /** @summary Return actual options */ + getOptions(as_is) { + if (!as_is && !this.#options) + this.#options = {}; + return this.#options; + } + + /** @summary Emulate old options property */ + get options() { return this.getOptions(); } + + /** @summary Store actual options together with original string * @private */ storeDrawOpt(original) { - if (!this.options) return; - if (!original) original = ''; + if (!this.#options) + return; + if (!original) + original = ''; const pp = original.indexOf(';;'); - if (pp >= 0) original = original.slice(0, pp); - this.options.original = original; - this.options_store = Object.assign({}, this.options); + if (pp >= 0) + original = original.slice(0, pp); + this.#options.original = original; + this.#options_store = Object.assign({}, this.#options); } + /** @summary Return dom argument for object drawing + * @desc Can be used to draw other objects on same pad / same dom element + * @protected */ + getDrawDom() { return this.getPadPainter() || this.getDom(); } + /** @summary Return actual draw options as string * @param ignore_pad - do not include pad settings into histogram draw options * @desc if options are not modified - returns original string which was specified for object draw */ getDrawOpt(ignore_pad) { - if (!this.options) return ''; + if (!this.#options) + return ''; - if (isFunc(this.options.asString)) { + if (isFunc(this.#options.asString)) { let changed = false; const pp = this.getPadPainter(); - if (!this.options_store || pp?._interactively_changed) + if (!this.#options_store || pp?.options._interactively_changed) changed = true; else { - for (const k in this.options) { - if (this.options[k] !== this.options_store[k]) - changed = true; + for (const k in this.#options_store) { + if (this.#options[k] !== this.#options_store[k]) { + if ((k[0] !== '_') && (k[0] !== '$') && (k[0].toLowerCase() !== k[0])) + changed = true; } + } } - if (changed && isFunc(this.options.asString)) - return this.options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); + + if (changed && isFunc(this.#options.asString)) + return this.#options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); } - return this.options.original || ''; // nothing better, return original draw option + return this.#options.original || ''; // nothing better, return original draw option } /** @summary Returns array with supported draw options as configured in draw.mjs @@ -175,7 +259,7 @@ class ObjectPainter extends BasePainter { if (!cl || !isFunc(pp?.getObjectDrawSettings)) return []; - return pp.getObjectDrawSettings(prROOT + cl, 'nosame')?.opts; + return pp.getObjectDrawSettings(getKindForType(cl), 'nosame')?.opts; } /** @summary Central place to update objects drawing @@ -188,13 +272,17 @@ class ObjectPainter extends BasePainter { * only way to control how object can be update while requested from the server * @protected */ redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) return false; - const doc = getDocument(), - current = doc.body.style.cursor; - document.body.style.cursor = 'wait'; - const res = this.redrawPad(); - doc.body.style.cursor = current; - return res; + if (!this.updateObject(obj, opt)) + return false; + const doc = this.isBatchMode() ? null : getDocument(), + current = doc?.body.style.cursor; + if (doc) + doc.body.style.cursor = 'wait'; + return this.redrawPad().then(res => { + if (doc) + doc.body.style.cursor = current; + return res; + }); } /** @summary Generic method to update object content. @@ -202,8 +290,9 @@ class ObjectPainter extends BasePainter { * @param {object} obj - object with new data * @param {string} [opt] - option which will be used for redrawing * @protected */ - updateObject(obj /*, opt */) { - if (!this.matchObjectType(obj)) return false; + updateObject(obj /* , opt */) { + if (!this.matchObjectType(obj)) + return false; Object.assign(this.getObject(), obj); return true; } @@ -211,7 +300,7 @@ class ObjectPainter extends BasePainter { /** @summary Returns string with object hint * @desc It is either item name or object name or class name. * Such string typically used as object tooltip. - * If result string larger than 20 symbols, it will be cutted. */ + * If result string larger than 20 symbols, it will be shorten. */ getObjectHint() { const iname = this.getItemName(); if (iname) @@ -219,29 +308,36 @@ class ObjectPainter extends BasePainter { return this.getObjectName() || this.getClassName() || ''; } + /** @summary Set colors list + * @protected */ + setColors(lst) { this.#root_colors = lst; } + + /** @summary Return colors list + * @protected */ + getColors(force) { + if (!this.#root_colors && force) + this.setColors(this.getCanvPainter()?.getColors() || getRootColors()); + return this.#root_colors; + } + /** @summary returns color from current list of colors * @desc First checks canvas painter and then just access global list of colors * @param {number} indx - color index * @return {string} with SVG color name or rgb() * @protected */ - getColor(indx) { - if (!this.root_colors) - this.root_colors = this.getCanvPainter()?.root_colors || getRootColors(); - - return this.root_colors[indx]; - } + getColor(indx) { return this.getColors(true)[indx]; } /** @summary Add color to list of colors * @desc Returned color index can be used as color number in all other draw functions * @return {number} new color index * @protected */ addColor(color) { - if (!this.root_colors) - this.root_colors = this.getCanvPainter()?.root_colors || getRootColors(); - const indx = this.root_colors.indexOf(color); - if (indx >= 0) return indx; - this.root_colors.push(color); - return this.root_colors.length - 1; + const lst = this.getColors(true), + indx = lst.indexOf(color); + if (indx >= 0) + return indx; + lst.push(color); + return lst.length - 1; } /** @summary returns tooltip allowed flag @@ -255,8 +351,7 @@ class ObjectPainter extends BasePainter { /** @summary change tooltip allowed flag * @param {boolean|string} [on = true] set tooltip allowed state or 'toggle' * @private */ - setTooltipAllowed(on) { - if (on === undefined) on = true; + setTooltipAllowed(on = true) { const src = this.getCanvPainter() || this; src.tooltip_allowed = (on === 'toggle') ? !src.tooltip_allowed : on; } @@ -272,44 +367,66 @@ class ObjectPainter extends BasePainter { * @desc generic method to delete all graphical elements, associated with the painter * @protected */ removeG() { - this.draw_g?.remove(); - delete this.draw_g; + this.#draw_g?.remove(); + this.#draw_g = undefined; } /** @summary Returns created element used for object drawing * @desc Element should be created by {@link ObjectPainter#createG} * @protected */ - getG() { return this.draw_g; } + getG() { return this.#draw_g; } + + /** @summary introduce property for backward compatibility */ + get draw_g() { return this.#draw_g; } + + /** @summary Assign G element used for object drawing + * @protected */ + setG(g) { + this.#draw_g = g; + return g; + } + + /** @summary Append svg::path to G + * @protected */ + appendPath(d) { return this.#draw_g.append('svg:path').attr('d', d); } /** @summary (re)creates svg:g element for object drawings * @desc either one attach svg:g to pad primitives (default) * or svg:g element created in specified frame layer ('main_layer' will be used when true specified) * @param {boolean|string} [frame_layer] - when specified, element will be created inside frame layer, otherwise in the pad * @protected */ - createG(frame_layer) { + createG(frame_layer, use_a = false) { let layer; + const pp = this.getPadPainter(); + + if (frame_layer === 'frame2d') { + const fp = this.getFramePainter(); + frame_layer = fp && !fp.mode3d; + } + if (frame_layer) { - const frame = this.getFrameSvg(); + const frame = pp.getFrameSvg(); if (frame.empty()) { console.error('Not found frame to create g element inside'); return frame; } - if (!isStr(frame_layer)) frame_layer = 'main_layer'; + if (!isStr(frame_layer)) + frame_layer = 'main_layer'; layer = frame.selectChild('.' + frame_layer); } else - layer = this.getLayerSvg('primitives_layer'); + layer = pp.getLayerSvg('primitives_layer'); - if (this.draw_g && this.draw_g.node().parentNode !== layer.node()) { - console.log('g element changes its layer!!'); + if (this.#draw_g && this.#draw_g.node().parentNode !== layer.node()) { + console.log('g element changes its layer!'); this.removeG(); } - if (this.draw_g) { + if (this.#draw_g) { // clear all elements, keep g element on its place - this.draw_g.selectAll('*').remove(); + this.#draw_g.selectAll('*').remove(); } else { - this.draw_g = layer.append('svg:g'); + this.#draw_g = layer.append(use_a ? 'svg:a' : 'svg:g'); if (!frame_layer) layer.selectChildren('.most_upper_primitives').raise(); @@ -318,129 +435,75 @@ class ObjectPainter extends BasePainter { // set attributes for debugging, both should be there for opt out them later const clname = this.getClassName(), objname = this.getObjectName(); if (objname || clname) { - this.draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) - .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); + this.#draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) + .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); } - this.draw_g.property('in_frame', !!frame_layer); // indicates coordinate system + this.#draw_g.property('in_frame', Boolean(frame_layer)); // indicates coordinate system - return this.draw_g; + return this.#draw_g; } /** @summary Bring draw element to the front */ bringToFront(check_online) { - if (!this.draw_g) return; - const prnt = this.draw_g.node().parentNode; - prnt?.appendChild(this.draw_g.node()); - - if (!check_online || !this.snapid) return; - const pp = this.getPadPainter(); - if (!pp?.snapid) return; - - this.getCanvPainter()?.sendWebsocket('POPOBJ:'+JSON.stringify([pp.snapid.toString(), this.snapid.toString()])); - } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Pad svg element - * @param {string} [pad_name] - pad name to select, if not specified - pad where object is drawn - * @return {object} d3 selection with pad svg - * @protected */ - getPadSvg(pad_name) { - if (pad_name === undefined) - pad_name = this.pad_name; - - let c = this.getCanvSvg(); - if (!pad_name || c.empty()) return c; - - const cp = c.property('pad_painter'); - if (cp?.pads_cache && cp.pads_cache[pad_name]) - return d3_select(cp.pads_cache[pad_name]); + if (!this.#draw_g) + return; + const prnt = this.#draw_g.node().parentNode; + prnt?.appendChild(this.#draw_g.node()); - c = c.select('.primitives_layer .__root_pad_' + pad_name); - if (cp) { - if (!cp.pads_cache) cp.pads_cache = {}; - cp.pads_cache[pad_name] = c.node(); + if (check_online && this.getSnapId()) { + const pp = this.getPadPainter(); + if (pp?.getSnapId()) + this.getCanvPainter()?.sendWebsocket('POPOBJ:' + JSON.stringify([pp.getSnapId(), this.getSnapId()])); } - return c; } - /** @summary Assign unique identifier for the painter + /** @summary Assign is_primary flag * @private */ - getUniqueId(only_read = false) { - if (!only_read && (this._unique_painter_id === undefined)) - this._unique_painter_id = internals.id_counter++; // assign unique identifier - return this._unique_painter_id; - } + setPrimary(flag = true) { this.#is_primary = flag; } + + /** @summary Return is_primary flag + * @private */ + isPrimary() { return this.#is_primary; } /** @summary Assign secondary id * @private */ - setSecondaryId(main, name) { - this._main_painter_id = main.getUniqueId(); - this._secondary_id = name; + setSecondaryId(primary, name) { + primary.setPrimary(true); // mark as primary, used later + this.#primary_ref = new WeakRef(primary); + this.#secondary_id = name; } + /** @summary Returns secondary id + * @private */ + getSecondaryId() { return this.#secondary_id; } + /** @summary Check if this is secondary painter - * @desc if main painter provided - check if this really main for this + * @desc if primary painter provided - check if this really main for this * @private */ - isSecondary(main) { - if (this._main_painter_id === undefined) + isSecondary(primary) { + if (!this.#primary_ref) return false; - return !isObject(main) ? true : this._main_painter_id === main.getUniqueId(true); + return !isObject(primary) ? true : this.#primary_ref.deref() === primary; } - /** @summary Provides identifier on server for requested sublement */ - getSnapId(subelem) { - if (!this.snapid) - return ''; - - return this.snapid.toString() + (subelem ? '#'+subelem : ''); - } + /** @summary Return primary object + * @private */ + getPrimary() { return this.#primary_ref?.deref(); } - /** @summary Method selects immediate layer under canvas/pad main element - * @param {string} name - layer name, exits 'primitives_layer', 'btns_layer', 'info_layer' - * @param {string} [pad_name] - pad name; current pad name used by default + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg * @protected */ - getLayerSvg(name, pad_name) { - let svg = this.getPadSvg(pad_name); - if (svg.empty()) return svg; - - if (name.indexOf('prim#') === 0) { - svg = svg.selectChild('.primitives_layer'); - name = name.slice(5); - } - - return svg.selectChild('.' + name); - } + getCanvSvg() { return this.selectDom().select('.root_canvas'); } /** @summary Method selects current pad name * @param {string} [new_name] - when specified, new current pad name will be configured * @return {string} previous selected pad or actual pad when new_name not specified - * @private */ - selectCurrentPad(new_name) { - const svg = this.getCanvSvg(); - if (svg.empty()) return ''; - const curr = svg.property('current_pad'); - if (new_name !== undefined) svg.property('current_pad', new_name); - return curr; - } - - /** @summary returns pad painter - * @param {string} [pad_name] pad name or use current pad by default - * @protected */ - getPadPainter(pad_name) { - const elem = this.getPadSvg(isStr(pad_name) ? pad_name : undefined); - return elem.empty() ? null : elem.property('pad_painter'); - } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter() { - const elem = this.getCanvSvg(); - return elem.empty() ? null : elem.property('pad_painter'); + * @private + * @deprecated to be removed in v8 */ + selectCurrentPad() { + console.warn('selectCurrentPad is deprecated, will be removed in v8'); + return ''; } /** @summary Return functor, which can convert x and y coordinates into pixels, used for drawing in the pad @@ -452,22 +515,22 @@ class ObjectPainter extends BasePainter { * @protected */ getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { const func = { isndc, nornd }, - use_frame = this.draw_g?.property('in_frame'); + use_frame = this.getG()?.property('in_frame'); if (use_frame || (use_frame_coordinates && !isndc)) - func.main = this.getFramePainter(); - if (func.main?.grx && func.main?.gry) { - func.x0 = (use_frame_coordinates && !isndc) ? func.main.getFrameX() : 0; - func.y0 = (use_frame_coordinates && !isndc) ? func.main.getFrameY() : 0; + func.fp = this.getFramePainter(); + if (func.fp?.grx && func.fp?.gry) { + func.x0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameX() : 0; + func.y0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameY() : 0; if (nornd) { - func.x = function(x) { return this.x0 + this.main.grx(x); }; - func.y = function(y) { return this.y0 + this.main.gry(y); }; + func.x = function(x) { return this.x0 + this.fp.grx(x); }; + func.y = function(y) { return this.y0 + this.fp.gry(y); }; } else { - func.x = function(x) { return this.x0 + Math.round(this.main.grx(x)); }; - func.y = function(y) { return this.y0 + Math.round(this.main.gry(y)); }; + func.x = function(x) { return this.x0 + Math.round(this.fp.grx(x)); }; + func.y = function(y) { return this.y0 + Math.round(this.fp.gry(y)); }; } } else if (!use_frame) { const pp = this.getPadPainter(); - if (!isndc) func.pad = pp?.getRootPad(true); // need for NDC conversion + func.pad = isndc ? null : pp?.getRootPad(true); // need for NDC conversion func.padw = pp?.getPadWidth() ?? 10; func.x = function(value) { if (this.pad) { @@ -502,7 +565,7 @@ class ObjectPainter extends BasePainter { * @param {number} value - axis value to convert. * @param {boolean} ndc - is value in NDC coordinates * @param {boolean} [noround] - skip rounding - * @return {number} value of requested coordiantes + * @return {number} value of requested coordinates * @protected */ axisToSvg(axis, value, ndc, noround) { const func = this.getAxisToSvgFunc(ndc, noround); @@ -512,12 +575,12 @@ class ObjectPainter extends BasePainter { /** @summary Converts pad SVG x or y coordinates into axis values. * @desc Reverse transformation for {@link ObjectPainter#axisToSvg} * @param {string} axis - name like 'x' or 'y' - * @param {number} coord - graphics coordiante. + * @param {number} coord - graphics coordinate. * @param {boolean} ndc - kind of return value - * @return {number} value of requested coordiantes + * @return {number} value of requested coordinates * @protected */ svgToAxis(axis, coord, ndc) { - const use_frame = this.draw_g?.property('in_frame'); + const use_frame = this.getG()?.property('in_frame'); if (use_frame) return this.getFramePainter()?.revertAxis(axis, coord) ?? 0; @@ -529,30 +592,18 @@ class ObjectPainter extends BasePainter { if (pad) { if (axis === 'y') { value = pad.fY1 + value * (pad.fY2 - pad.fY1); - if (pad.fLogy) value = Math.pow(10, value); + if (pad.fLogy) + value = Math.pow(10, value); } else { value = pad.fX1 + value * (pad.fX2 - pad.fX1); - if (pad.fLogx) value = Math.pow(10, value); + if (pad.fLogx) + value = Math.pow(10, value); } } return value; } - /** @summary Returns svg element for the frame in current pad - * @protected */ - getFrameSvg(pad_name) { - const layer = this.getLayerSvg('primitives_layer', pad_name); - if (layer.empty()) return layer; - let node = layer.node().firstChild; - while (node) { - const elem = d3_select(node); - if (elem.classed('root_frame')) return elem; - node = node.nextSibling; - } - return d3_select(null); - } - /** @summary Returns frame painter for current pad * @desc Pad has direct reference on frame if any * @protected */ @@ -562,19 +613,17 @@ class ObjectPainter extends BasePainter { /** @summary Returns painter for main object on the pad. * @desc Typically it is first histogram drawn on the pad and which draws frame axes - * But it also can be special usecase as TASImage or TGraphPolargram + * But it also can be special use-case as TASImage or TGraphPolargram * @param {boolean} [not_store] - if true, prevent temporary storage of main painter reference * @protected */ getMainPainter(not_store) { - let res = this._main_painter; + let res = this.#main_painter?.deref(); if (!res) { const pp = this.getPadPainter(); res = pp ? pp.getMainPainter() : this.getTopPainter(); - if (!res) res = null; - if (!not_store) - this._main_painter = res; + this.#main_painter = not_store || !res ? null : new WeakRef(res); } - return res; + return res || null; } /** @summary Returns true if this is main painter @@ -594,34 +643,28 @@ class ObjectPainter extends BasePainter { } /** @summary Add painter to pad list of painters - * @param {string} [pad_name] - optional pad name where painter should be add - * @desc Normally one should use {@link ensureTCanvas} to add painter to pad list of primitives + * @desc Normally called from {@link ensureTCanvas} function when new painter is created * @protected */ - addToPadPrimitives(pad_name) { - if (pad_name !== undefined) this.setPadName(pad_name); - const pp = this.getPadPainter(pad_name); // important - pad_name must be here, otherwise PadPainter class confuses itself - - if (!pp || (pp === this)) return false; - - if (pp.painters.indexOf(this) < 0) - pp.painters.push(this); + addToPadPrimitives(pad_painter) { + if (this.#pad_painter_ref) + pad_painter = this.#pad_painter_ref.deref(); + else { + if (!pad_painter) + pad_painter = getDomCanvasPainter(this.selectDom()); // try to detect in DOM + if (pad_painter) + this.#pad_painter_ref = new WeakRef(pad_painter); + } - if (!this.rstyle && pp.next_rstyle) - this.rstyle = pp.next_rstyle; + if (!pad_painter || (pad_painter === this)) + return null; - return true; + return pad_painter.addToPrimitives(this); } /** @summary Remove painter from pad list of painters + * @desc Can be used from external frameworks to add/remove painters * @protected */ - removeFromPadPrimitives() { - const pp = this.getPadPainter(); - if (!pp || (pp === this)) return false; - - const k = pp.painters.indexOf(this); - if (k >= 0) pp.painters.splice(k, 1); - return true; - } + removeFromPadPrimitives() { this.getPadPainter()?.removePrimitive(this); } /** @summary Creates marker attributes object * @desc Can be used to produce markers in painter. @@ -631,22 +674,27 @@ class ObjectPainter extends BasePainter { * @return {object} created handler * @protected */ createAttMarker(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fMarkerColor !== undefined && args.fMarkerStyle !== undefined && args.fMarkerSize !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.markeratt : null; + let handler = args.std ? this.#markeratt : null; if (!handler) handler = new TAttMarkerHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.markeratt = handler; + if (args.std) + this.#markeratt = handler; return handler; } @@ -657,22 +705,27 @@ class ObjectPainter extends BasePainter { * @param {object} args - either TAttLine or see constructor arguments of {@link TAttLineHandler} * @protected */ createAttLine(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fLineColor !== undefined && args.fLineStyle !== undefined && args.fLineWidth !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.lineatt : null; + let handler = args.std ? this.#lineatt : null; if (!handler) handler = new TAttLineHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.lineatt = handler; + if (args.std) + this.#lineatt = handler; return handler; } @@ -680,22 +733,27 @@ class ObjectPainter extends BasePainter { * @param {object} args - either TAttText or see constructor arguments of {@link TAttTextHandler} * @protected */ createAttText(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args.fTextFont !== undefined && args.fTextSize !== undefined && args.fTextColor !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; - if (args.painter === undefined) args.painter = this; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.textatt : null; + let handler = args.std ? this.#textatt : null; if (!handler) handler = new TAttTextHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.textatt = handler; + if (args.std) + this.#textatt = handler; return handler; } @@ -704,7 +762,7 @@ class ObjectPainter extends BasePainter { * otherwise newly created patters will not be usable in the canvas * See {@link TAttFillHandler} for more info. * Instance assigned as this.fillatt data member, recognized by GED editors - * @param {object} args - for special cases one can specify TAttFill as args or number of parameters + * @param {object} [args] - for special cases one can specify TAttFill as args or number of parameters * @param {boolean} [args.std = true] - this is standard fill attribute for object and should be used as this.fillatt * @param {object} [args.attr = null] - object, derived from TAttFill * @param {number} [args.pattern = undefined] - integer index of fill pattern @@ -714,39 +772,53 @@ class ObjectPainter extends BasePainter { * @return created handle * @protected */ createAttFill(args) { - if (!isObject(args)) + if (args === undefined) + args = { attr: this.getObject() }; + else if (!isObject(args)) args = { std: true }; else if (args._typename && args.fFillColor !== undefined && args.fFillStyle !== undefined) args = { attr: args, std: false }; - if (args.std === undefined) args.std = true; + if (args.std === undefined) + args.std = true; + if (args.painter === undefined) + args.painter = this; - let handler = args.std ? this.fillatt : null; + let handler = args.std ? this.#fillatt : null; - if (!args.svg) args.svg = this.getCanvSvg(); - if (args.painter === undefined) args.painter = this; + if (!args.svg) + args.svg = this.getCanvSvg(); if (!handler) handler = new TAttFillHandler(args); else if (!handler.changed || args.force) handler.setArgs(args); - if (args.std) this.fillatt = handler; + if (args.std) + this.#fillatt = handler; return handler; } + get fillatt() { return this.#fillatt; } + get lineatt() { return this.#lineatt; } + get markeratt() { return this.#markeratt; } + get textatt() { return this.#textatt; } + /** @summary call function for each painter in the pad * @desc Iterate over all known painters * @private */ forEachPainter(userfunc, kind) { // iterate over all painters from pad list - const pp = this.getPadPainter(); + let pp = this.getPadPainter(), top = null; + if (!pp) { + top = this.getTopPainter(); + if (isPadPainter(top)) + pp = top; + } if (pp) pp.forEachPainterInPad(userfunc, kind); - else { - const painter = this.getTopPainter(); - if (painter && (kind !== 'pads')) userfunc(painter); - } + else if (top && (kind !== 'pads')) + userfunc(top); } /** @summary indicate that redraw was invoked via interactive action (like context menu or zooming) @@ -755,7 +827,7 @@ class ObjectPainter extends BasePainter { * @private */ async interactiveRedraw(arg, info, subelem) { let reason, res; - if (isStr(info) && (info.indexOf('exec:') !== 0)) + if (isStr(info) && info.indexOf('exec:')) reason = info; if (arg === 'pad') @@ -764,13 +836,16 @@ class ObjectPainter extends BasePainter { res = this.redraw(reason); return getPromise(res).then(() => { + if (arg === 'attribute') + return this.getPadPainter()?.redrawLegend(); + }).then(() => { // inform GED that something changes const canp = this.getCanvPainter(); if (isFunc(canp?.producePadEvent)) canp.producePadEvent('redraw', this.getPadPainter(), this, null, subelem); - // inform server that drawopt changes + // inform server that draw options changes if (isFunc(canp?.processChanges)) canp.processChanges(info, this, subelem); @@ -790,20 +865,21 @@ class ObjectPainter extends BasePainter { * @private */ executeMenuCommand(method) { if (method.fName === 'Inspect') - // primitve inspector, keep it here + // primitive inspector, keep it here return this.showInspector(); return false; } /** @summary Invoke method for object via WebCanvas functionality - * @desc Requires that painter marked with object identifier (this.snapid) or identifier provided as second argument + * @desc Requires that painter marked with object identifier (this.#snapid) or identifier provided as second argument * Canvas painter should exists and in non-readonly mode * Execution string can look like 'Print()'. * Many methods call can be chained with 'Print();;Update();;Clear()' * @private */ submitCanvExec(exec, snapid) { - if (!exec || !isStr(exec)) return; + if (!exec || !isStr(exec)) + return; const canp = this.getCanvPainter(); if (isFunc(canp?.submitExec)) @@ -812,10 +888,15 @@ class ObjectPainter extends BasePainter { /** @summary remove all created draw attributes * @protected */ - deleteAttr() { - delete this.lineatt; - delete this.fillatt; - delete this.markeratt; + deleteAttr(name) { + if (!name || name === 'line') + this.#lineatt = undefined; + if (!name || name === 'fill') + this.#fillatt = undefined; + if (!name || name === 'marker') + this.#markeratt = undefined; + if (!name || name === 'text') + this.#textatt = undefined; } /** @summary Show object in inspector for provided object @@ -827,13 +908,14 @@ class ObjectPainter extends BasePainter { /** @summary Fill context menu for the object * @private */ fillContextMenu(menu) { - const name = this.getObjectName(); - let cl = this.getClassName(); - const p = cl.lastIndexOf('::'); - if (p > 0) cl = cl.slice(p+2); - const title = (cl && name) ? `${cl}:${name}` : (cl || name || 'object'); + const cl = this.getClassName(), + name = this.getObjectName(), + p = cl.lastIndexOf('::'), + cl0 = (p > 0) ? cl.slice(p + 2) : cl, + hdr = (cl0 && name) ? `${cl0}:${name}` : (cl0 || name || 'object'), + url = cl ? `${urlClassPrefix}${cl.replaceAll('::', '_1_1')}.html` : ''; - menu.add(`header:${title}`); + menu.header(hdr, url); const size0 = menu.size(); @@ -849,21 +931,25 @@ class ObjectPainter extends BasePainter { } /** @summary shows objects status - * @desc Either used canvas painter method or globaly assigned + * @desc Either used canvas painter method or globally assigned * When no parameters are specified, just basic object properties are shown * @private */ showObjectStatus(name, title, info, info2) { let cp = this.getCanvPainter(); - if (cp && !isFunc(cp.showCanvasStatus)) cp = null; + if (!isFunc(cp?.showCanvasStatus)) + cp = null; - if (!cp && !isFunc(internals.showStatus)) return false; + if (!cp && !isFunc(internals.showStatus)) + return false; - if (this.enlargeMain('state') === 'on') return false; + if (this.enlargeMain('state') === 'on') + return false; if ((name === undefined) && (title === undefined)) { const obj = this.getObject(); - if (!obj) return; + if (!obj) + return; name = this.getItemName() || obj.fName; title = obj.fTitle || obj._typename; info = obj._typename; @@ -886,14 +972,18 @@ class ObjectPainter extends BasePainter { * @desc required before any text can be drawn * @param {number} font_face - font id as used in ROOT font attributes * @param {number} font_size - font size as used in ROOT font attributes - * @param {object} [draw_g] - element where text drawm, by default using main object element + * @param {object} [draw_g] - element where text drawn, by default using main object element * @param {number} [max_font_size] - maximal font size, used when text can be scaled * @protected */ - startTextDrawing(font_face, font_size, draw_g, max_font_size) { - if (!draw_g) draw_g = this.draw_g; - if (!draw_g || draw_g.empty()) return; + startTextDrawing(font_face, font_size, draw_g, max_font_size, can_async) { + if (!draw_g) + draw_g = this.getG(); + if (!draw_g || draw_g.empty()) + return false; const font = (font_size === 'font') ? font_face : new FontHandler(font_face, font_size); + if (can_async && font.needLoad()) + return font; font.setPainter(this); // may be required when custom font is used @@ -905,10 +995,27 @@ class ObjectPainter extends BasePainter { .property('text_factor', 0) .property('max_text_width', 0) // keep maximal text width, use it later .property('max_font_size', max_font_size) - .property('_fast_drawing', this.getPadPainter()?._fast_drawing ?? false); + .property('_fast_drawing', this.getPadPainter()?.isFastDrawing() ?? false); if (draw_g.property('_fast_drawing')) draw_g.property('_font_too_small', (max_font_size && (max_font_size < 5)) || (font.size < 4)); + + return true; + } + + /** @summary Start async text drawing + * @return {Promise} for loading of font if necessary + * @private */ + async startTextDrawingAsync(font_face, font_size, draw_g, max_font_size) { + const font = this.startTextDrawing(font_face, font_size, draw_g, max_font_size, true); + if ((font === true) || (font === false)) + return font; + return font.load().then(res => { + if (!res) + return false; + + return this.startTextDrawing(font, 'font', draw_g, max_font_size); + }); } /** @summary Apply scaling factor to all drawn text in the element @@ -917,22 +1024,24 @@ class ObjectPainter extends BasePainter { * @param {object} [draw_g] - drawing element for the text * @protected */ scaleTextDrawing(factor, draw_g) { - if (!draw_g) draw_g = this.draw_g; - if (!draw_g || draw_g.empty()) return; + if (!draw_g) + draw_g = this.getG(); + if (!draw_g || draw_g.empty()) + return; if (factor && (factor > draw_g.property('text_factor'))) draw_g.property('text_factor', factor); } /** @summary Analyze if all text draw operations are completed * @private */ - _checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { - let all_args = draw_g.property('all_args'), missing = 0; - if (!all_args) { - console.log('Text drawing is finished - why calling _checkAllTextDrawing?????'); - all_args = []; - } + #checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { + const all_args = draw_g.property('all_args') || []; + let missing = 0; - all_args.forEach(arg => { if (!arg.ready) missing++; }); + all_args.forEach(arg => { + if (!arg.ready) + missing++; + }); if (missing > 0) { if (isFunc(resolveFunc)) { @@ -950,7 +1059,7 @@ class ObjectPainter extends BasePainter { max_sz = draw_g.property('max_font_size'); let font_size = font.size, any_text = false, only_text = true; - if ((f > 0) && ((f < 0.9) || (f > 1))) + if ((f > 0) && ((f < 0.95) || (f > 1.05))) font.size = Math.max(1, Math.floor(font.size / f)); if (max_sz && (font.size > max_sz)) @@ -962,9 +1071,9 @@ class ObjectPainter extends BasePainter { } all_args.forEach(arg => { - if (arg.mj_node && arg.applyAttributesToMathJax) { + if (arg.mj_node && arg.mj_func) { const svg = arg.mj_node.select('svg'); // MathJax svg - arg.applyAttributesToMathJax(this, arg.mj_node, svg, arg, font_size, f); + arg.mj_func(this, arg.mj_node, svg, arg, font_size, f); delete arg.mj_node; // remove reference only_text = false; } else if (arg.txt_g) @@ -987,7 +1096,8 @@ class ObjectPainter extends BasePainter { txt = arg.txt_node; delete arg.txt_node; is_txt = true; - if (optimize_arr !== null) optimize_arr.push(txt); + if (optimize_arr !== null) + optimize_arr.push(txt); } else if (arg.txt_g) { txt = arg.txt_g; delete arg.txt_g; @@ -1003,8 +1113,8 @@ class ObjectPainter extends BasePainter { // adjust x position when scale into specified rectangle if (arg.align[0] === 'middle') arg.x += arg.width / 2; - else if (arg.align[0] === 'end') - arg.x += arg.width; + else if (arg.align[0] === 'end') + arg.x += arg.width; } if (arg.height) { @@ -1020,7 +1130,10 @@ class ObjectPainter extends BasePainter { // handle simple text drawing if (isNodeJs()) { - if (arg.scale && (f > 0)) { arg.box.width *= 1/f; arg.box.height *= 1/f; } + if (arg.scale && (f > 0)) { + arg.box.width *= 1 / f; + arg.box.height *= 1 / f; + } } else if (!arg.plain && !arg.fast) { // exact box dimension only required when complex text was build arg.box = getElementRect(txt, 'bbox'); @@ -1031,8 +1144,8 @@ class ObjectPainter extends BasePainter { if (arg.align[1] === 'top') txt.attr('dy', '.8em'); else if (arg.align[1] === 'middle') { - if (isNodeJs()) txt.attr('dy', '.4em'); - else txt.attr('dominant-baseline', 'middle'); + // if (isNodeJs()) txt.attr('dy', '.4em'); else // old workaround for node.js + txt.attr('dominant-baseline', 'middle'); } } else { txt.attr('text-anchor', 'start'); @@ -1040,28 +1153,36 @@ class ObjectPainter extends BasePainter { dy = ((arg.align[1] === 'top') ? (arg.top_shift || 1) : (arg.align[1] === 'middle') ? (arg.mid_shift || 0.5) : 0) * arg.box.height; } } else if (arg.text_rect) { - // handle latext drawing + // handle latex drawing const box = arg.text_rect; - scale = (f > 0) && (Math.abs(1-f) > 0.01) ? 1/f : 1; + scale = (f > 0) && (Math.abs(1 - f) > 0.01) ? 1 / f : 1; dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * box.width * scale; if (arg.align[1] === 'top') - dy = -box.y1*scale; + dy = -box.y1 * scale; else if (arg.align[1] === 'bottom') - dy = -box.y2*scale; + dy = -box.y2 * scale; else if (arg.align[1] === 'middle') - dy = -0.5*(box.y1 + box.y2)*scale; + dy = -0.5 * (box.y1 + box.y2) * scale; } else console.error('text rect not calcualted - please check code'); - if (!arg.rotate) { arg.x += dx; arg.y += dy; dx = dy = 0; } + if (!arg.rotate) { + arg.x += dx; + arg.y += dy; + dx = dy = 0; + } // use translate and then rotate to avoid complex sign calculations let trans = makeTranslate(Math.round(arg.x), Math.round(arg.y)) || ''; const dtrans = makeTranslate(Math.round(dx), Math.round(dy)), - append = arg => { if (trans) trans += ' '; trans += arg; }; + append = aaa => { + if (trans) + trans += ' '; + trans += aaa; + }; if (arg.rotate) append(`rotate(${Math.round(arg.rotate)})`); @@ -1069,7 +1190,8 @@ class ObjectPainter extends BasePainter { append(`scale(${scale.toFixed(3)})`); if (dtrans) append(dtrans); - if (trans) txt.attr('transform', trans); + if (trans) + txt.attr('transform', trans); }); @@ -1082,7 +1204,8 @@ class ObjectPainter extends BasePainter { let first = optimize_arr[0].attr(name); optimize_arr.forEach(txt_node => { const value = txt_node.attr(name); - if (!value || (value !== first)) first = undefined; + if (!value || (value !== first)) + first = undefined; }); if (first) { draw_g.attr(name, first); @@ -1092,16 +1215,17 @@ class ObjectPainter extends BasePainter { } // if specified, call resolve function - if (resolveFunc) resolveFunc(this); // IMPORTANT - return painter, may use in draw methods + if (resolveFunc) + resolveFunc(this); // IMPORTANT - return painter, may use in draw methods } /** @summary Post-process plain text drawing * @private */ - _postprocessDrawText(arg, txt_node) { - // complete rectangle with very rougth size estimations + #postprocessDrawText(arg, txt_node) { + // complete rectangle with very rough size estimations arg.box = !isNodeJs() && !settings.ApproxTextSize && !arg.fast ? getElementRect(txt_node, 'bbox') - : (arg.text_rect || { height: arg.font_size * 1.2, width: arg.text.length * arg.font_size * arg.font.aver_width }); + : (arg.text_rect || { height: Math.round(1.15 * arg.font_size), width: approximateLabelWidth(arg.text, arg.font, arg.font_size) }); txt_node.attr('visibility', 'hidden'); // hide elements until text drawing is finished @@ -1132,7 +1256,7 @@ class ObjectPainter extends BasePainter { * @param {boolean} [arg.scale = true] - scale into draw box when width and height parameters are specified * @param {number} [arg.latex] - 0 - plain text, 1 - normal TLatex, 2 - math * @param {string} [arg.color=black] - text color - * @param {number} [arg.rotate] - rotaion angle + * @param {number} [arg.rotate] - rotation angle * @param {number} [arg.font_size] - fixed font size * @param {object} [arg.draw_g] - element where to place text, if not specified central draw_g container is used * @param {function} [arg.post_process] - optional function called when specified text is drawn @@ -1141,23 +1265,25 @@ class ObjectPainter extends BasePainter { if (!arg.text) arg.text = ''; - arg.draw_g = arg.draw_g || this.draw_g; - if (!arg.draw_g || arg.draw_g.empty()) return; + arg.draw_g = arg.draw_g || this.getG(); + if (!arg.draw_g || arg.draw_g.empty()) + return; const font = arg.draw_g.property('text_font'); arg.font = font; // use in latex conversion if (font) { - if (font.color && !arg.color) arg.color = font.color; - if (font.align && !arg.align) arg.align = font.align; - if (font.angle && !arg.rotate) arg.rotate = font.angle; + arg.color = arg.color || font.color; + arg.align = arg.align || font.align; + arg.rotate = arg.rotate || font.angle; } let align = ['start', 'middle']; if (isStr(arg.align)) { align = arg.align.split(';'); - if (align.length === 1) align.push('middle'); + if (align.length === 1) + align.push('middle'); } else if (typeof arg.align === 'number') { if ((arg.align / 10) >= 3) align[0] = 'end'; @@ -1172,7 +1298,8 @@ class ObjectPainter extends BasePainter { } else if (isObject(arg.align) && (arg.align.length === 2)) align = arg.align; - if (arg.latex === undefined) arg.latex = 1; // latex 0-text, 1-latex, 2-math + if (arg.latex === undefined) + arg.latex = 1; // 0: text, 1: latex, 2: math arg.align = align; arg.x = arg.x || 0; arg.y = arg.y || 0; @@ -1184,10 +1311,12 @@ class ObjectPainter extends BasePainter { if (arg.draw_g.property('_fast_drawing')) { if (arg.scale) { // area too small - ignore such drawing - if (arg.height < 4) return 0; + if (arg.height < 4) + return 0; } else if (arg.font_size) { // font size too small - if (arg.font_size < 4) return 0; + if (arg.font_size < 4) + return 0; } else if (arg.draw_g.property('_font_too_small')) { // configure font is too small - ignore drawing return 0; @@ -1210,30 +1339,33 @@ class ObjectPainter extends BasePainter { if (!use_mathjax || arg.nomathjax) { arg.txt_node = arg.draw_g.append('svg:text'); - if (arg.color) arg.txt_node.attr('fill', arg.color); + if (arg.color) + arg.txt_node.attr('fill', arg.color); - if (arg.font_size) arg.txt_node.attr('font-size', arg.font_size); - else arg.font_size = font.size; + if (arg.font_size) + arg.txt_node.attr('font-size', arg.font_size); + else + arg.font_size = font.size; arg.plain = !arg.latex || (settings.Latex === cl.Off) || (settings.Latex === cl.Symbols); arg.simple_latex = arg.latex && (settings.Latex === cl.Symbols); - if (!arg.plain || arg.simple_latex || (arg.font && arg.font.isSymbol)) { + if (!arg.plain || arg.simple_latex || arg.font?.isSymbol) { if (arg.simple_latex || isPlainText(arg.text) || arg.plain) { arg.simple_latex = true; producePlainText(this, arg.txt_node, arg); } else { - arg.txt_node.remove(); // just remove text node, + arg.txt_node.remove(); // just remove text node delete arg.txt_node; arg.txt_g = arg.draw_g.append('svg:g'); produceLatex(this, arg.txt_g, arg); } arg.ready = true; - this._postprocessDrawText(arg, arg.txt_g || arg.txt_node); + this.#postprocessDrawText(arg, arg.txt_g || arg.txt_node); if (arg.draw_g.property('draw_text_completed')) - this._checkAllTextDrawing(arg.draw_g); // check if all other elements are completed + this.#checkAllTextDrawing(arg.draw_g); // check if all other elements are completed return 0; } @@ -1241,7 +1373,7 @@ class ObjectPainter extends BasePainter { arg.txt_node.text(arg.text); arg.ready = true; - return this._postprocessDrawText(arg, arg.txt_node); + return this.#postprocessDrawText(arg, arg.txt_node); } arg.mj_node = arg.draw_g.append('svg:g').attr('visibility', 'hidden'); // hide text until drawing is finished @@ -1249,7 +1381,7 @@ class ObjectPainter extends BasePainter { produceMathjax(this, arg.mj_node, arg).then(() => { arg.ready = true; if (arg.draw_g.property('draw_text_completed')) - this._checkAllTextDrawing(arg.draw_g); + this.#checkAllTextDrawing(arg.draw_g); }); return 0; @@ -1257,51 +1389,50 @@ class ObjectPainter extends BasePainter { /** @summary Finish text drawing * @desc Should be called to complete all text drawing operations - * @param {function} [draw_g] - element for text drawing, this.draw_g used when not specified + * @param {function} [draw_g] - element for text drawing, default is getG() * @return {Promise} when text drawing completed * @protected */ async finishTextDrawing(draw_g, try_optimize) { - if (!draw_g) draw_g = this.draw_g; + if (!draw_g) + draw_g = this.getG(); if (!draw_g || draw_g.empty()) return false; draw_g.property('draw_text_completed', true); // mark that text drawing is completed return new Promise(resolveFunc => { - this._checkAllTextDrawing(draw_g, resolveFunc, try_optimize); + this.#checkAllTextDrawing(draw_g, resolveFunc, try_optimize); }); } /** @summary Configure user-defined context menu for the object - * @desc fillmenu_func will be called when context menu is actiavted + * @desc fillmenu_func will be called when context menu is activated * Arguments fillmenu_func are (menu,kind) - * First is menu object, second is object subelement like axis 'x' or 'y' + * First is menu object, second is object sub-element like axis 'x' or 'y' * Function should return promise with menu when items are filled - * @param {function} fillmenu_func - function to fill custom context menu for oabject */ + * @param {function} fillmenu_func - function to fill custom context menu for object */ configureUserContextMenu(fillmenu_func) { - if (!fillmenu_func || !isFunc(fillmenu_func)) - delete this._userContextMenuFunc; - else - this._userContextMenuFunc = fillmenu_func; + this.#user_context_menu = isFunc(fillmenu_func) ? fillmenu_func : undefined; } /** @summary Fill object menu in web canvas * @private */ async fillObjectExecMenu(menu, kind) { - if (isFunc(this._userContextMenuFunc)) - return this._userContextMenuFunc(menu, kind); + if (isFunc(this.#user_context_menu)) + return this.#user_context_menu(menu, kind); const canvp = this.getCanvPainter(); - if (!this.snapid || !canvp || canvp?._readonly || !canvp?._websocket) + if (!this.getSnapId() || !canvp || canvp?.isReadonly() || !canvp?.getWebsocket()) return menu; - function DoExecMenu(arg) { + function doExecMenu(arg) { const execp = menu.exec_painter || this, cp = execp.getCanvPainter(), item = menu.exec_items[parseInt(arg)]; - if (!item?.fName) return; + if (!item?.fName) + return; // this is special entry, produced by TWebMenuItem, which recognizes editor entries itself if (item.fExec === 'Show:Editor') { @@ -1310,29 +1441,29 @@ class ObjectPainter extends BasePainter { return; } - if (isFunc(cp?.executeObjectMethod)) - if (cp.executeObjectMethod(execp, item, item.$execid)) return; + if (isFunc(cp?.executeObjectMethod) && cp.executeObjectMethod(execp, item, item.$execid)) + return; item.fClassName = execp.getClassName(); if ((item.$execid.indexOf('#x') > 0) || (item.$execid.indexOf('#y') > 0) || (item.$execid.indexOf('#z') > 0)) item.fClassName = clTAxis; - if (execp.executeMenuCommand(item)) return; + if (execp.executeMenuCommand(item)) + return; - if (!item.$execid) return; + if (!item.$execid) + return; if (!item.fArgs) { - if (cp?.v7canvas) - return cp.submitExec(execp, item.fExec, kind); - else - return execp.submitCanvExec(item.fExec, item.$execid); + return cp?.v7canvas ? cp.submitExec(execp, item.fExec, kind) + : execp.submitCanvExec(item.fExec, item.$execid); } menu.showMethodArgsDialog(item).then(args => { - if (!args) return; - if (execp.executeMenuCommand(item, args)) return; + if (!args || execp.executeMenuCommand(item, args)) + return; - const exec = item.fExec.slice(0, item.fExec.length-1) + args + ')'; + const exec = item.fExec.slice(0, item.fExec.length - 1) + args + ')'; if (cp?.v7canvas) cp.submitExec(execp, exec, kind); else @@ -1340,9 +1471,10 @@ class ObjectPainter extends BasePainter { }); } - const DoFillMenu = (_menu, _reqid, _resolveFunc, reply) => { + const doFillMenu = (_menu, _reqid, _resolveFunc, reply) => { // avoid multiple call of the callback after timeout - if (menu._got_menu) return; + if (menu._got_menu) + return; menu._got_menu = true; if (reply && (_reqid !== reply.fId)) @@ -1352,7 +1484,7 @@ class ObjectPainter extends BasePainter { if (menu.exec_items?.length) { if (_menu.size() > 0) - _menu.add('separator'); + _menu.separator(); let lastclname; @@ -1362,29 +1494,30 @@ class ObjectPainter extends BasePainter { item.$menu = menu; if (item.fClassName && lastclname && (lastclname !== item.fClassName)) { - _menu.add('endsub:'); + _menu.endsub(); lastclname = ''; } if (lastclname !== item.fClassName) { lastclname = item.fClassName; const p = lastclname.lastIndexOf('::'), - shortname = (p > 0) ? lastclname.slice(p+2) : lastclname; + shortname = (p > 0) ? lastclname.slice(p + 2) : lastclname; - _menu.add('sub:' + shortname.replace(/[<>]/g, '_')); + _menu.sub(shortname.replace(/[<>]/g, '_')); } if ((item.fChecked === undefined) || (item.fChecked < 0)) - _menu.add(item.fName, n, DoExecMenu); + _menu.add(item.fName, n, doExecMenu); else - _menu.addchk(item.fChecked, item.fName, n, DoExecMenu); + _menu.addchk(item.fChecked, item.fName, n, doExecMenu); } - if (lastclname) _menu.add('endsub:'); + if (lastclname) + _menu.endsub(); } _resolveFunc(_menu); }, - reqid = this.getSnapId(kind); + reqid = this.getSnapId(kind); menu._got_menu = false; @@ -1396,36 +1529,37 @@ class ObjectPainter extends BasePainter { let did_resolve = false; function handleResolve(res) { - if (did_resolve) return; + if (did_resolve) + return; did_resolve = true; resolveFunc(res); } // set timeout to avoid menu hanging - setTimeout(() => DoFillMenu(menu, reqid, handleResolve), 2000); + setTimeout(() => doFillMenu(menu, reqid, handleResolve), 2000); - canvp.submitMenuRequest(this, kind, reqid).then(lst => DoFillMenu(menu, reqid, handleResolve, lst)); + canvp.submitMenuRequest(this, kind, reqid).then(lst => doFillMenu(menu, reqid, handleResolve, lst)); }); } /** @summary Configure user-defined tooltip handler * @desc Hook for the users to get tooltip information when mouse cursor moves over frame area - * Hanlder function will be called every time when new data is selected + * Handler function will be called every time when new data is selected * when mouse leave frame area, handler(null) will be called * @param {function} handler - function called when tooltip is produced * @param {number} [tmout = 100] - delay in ms before tooltip delivered */ - configureUserTooltipHandler(handler, tmout) { + configureUserTooltipHandler(handler, tmout = 100) { if (!handler || !isFunc(handler)) { - delete this._user_tooltip_handler; - delete this._user_tooltip_timeout; + this.#user_tooltip_handler = undefined; + this.#user_tooltip_timeout = undefined; } else { - this._user_tooltip_handler = handler; - this._user_tooltip_timeout = tmout || 100; + this.#user_tooltip_handler = handler; + this.#user_tooltip_timeout = tmout; } } - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed + /** @summary Configure user-defined click handler + * @desc Function will be called every time when frame click was performed * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of click will be disabled * @param {function} handler - function called when mouse click is done */ @@ -1449,42 +1583,44 @@ class ObjectPainter extends BasePainter { /** @summary Check if user-defined tooltip function was configured * @return {boolean} flag is user tooltip handler was configured */ hasUserTooltip() { - return isFunc(this._user_tooltip_handler); + return isFunc(this.#user_tooltip_handler); } /** @summary Provide tooltips data to user-defined function * @param {object} data - tooltip data * @private */ provideUserTooltip(data) { - if (!this.hasUserTooltip()) return; + if (!this.hasUserTooltip()) + return; - if (this._user_tooltip_timeout <= 0) - return this._user_tooltip_handler(data); + if (this.#user_tooltip_timeout <= 0) + return this.#user_tooltip_handler(data); - if (this._user_tooltip_handle) { - clearTimeout(this._user_tooltip_handle); - delete this._user_tooltip_handle; + if (this.#user_toottip_handle) { + clearTimeout(this.#user_toottip_handle); + this.#user_toottip_handle = undefined; } if (!data) - return this._user_tooltip_handler(data); + return this.#user_tooltip_handler(data); // only after timeout user function will be called - this._user_tooltip_handle = setTimeout(() => { - delete this._user_tooltip_handle; - if (this._user_tooltip_handler) this._user_tooltip_handler(data); - }, this._user_tooltip_timeout); + this.#user_toottip_handle = setTimeout(() => { + this.#user_toottip_handle = undefined; + if (this.#user_tooltip_handler) + this.#user_tooltip_handler(data); + }, this.#user_tooltip_timeout); } /** @summary Provide projection areas * @param kind - 'X', 'Y', 'XY' or '' * @private */ async provideSpecialDrawArea(kind) { - if (kind === this._special_draw_area) + if (kind === this.#special_draw_area) return true; return this.getCanvPainter().toggleProjection(kind).then(() => { - this._special_draw_area = kind; + this.#special_draw_area = kind; return true; }); } @@ -1496,22 +1632,25 @@ class ObjectPainter extends BasePainter { * @private */ async drawInSpecialArea(obj, opt, kind) { const canp = this.getCanvPainter(); - if (this._special_draw_area && isFunc(canp?.drawProjection)) - return canp.drawProjection(kind || this._special_draw_area, obj, opt); + if (this.#special_draw_area && isFunc(canp?.drawProjection)) + return canp.drawProjection(kind || this.#special_draw_area, obj, opt); return false; } /** @summary Get tooltip for painter and specified event position - * @param {Object} evnt - object wiith clientX and clientY positions + * @param {Object} evnt - object with clientX and clientY positions * @private */ getToolTip(evnt) { - if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) return null; + if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) + return null; - const frame = this.getFrameSvg(); - if (frame.empty()) return null; + const frame = this.getPadPainter()?.getFrameSvg(); + if (!frame || frame.empty()) + return null; const layer = frame.selectChild('.main_layer'); - if (layer.empty()) return null; + if (layer.empty()) + return null; const pos = d3_pointer(evnt, layer.node()), pnt = { touch: false, x: pos[0], y: pos[1] }; @@ -1535,33 +1674,34 @@ function drawRawText(dom, txt /* , opt */) { const painter = new BasePainter(dom); painter.txt = txt; - painter.redrawObject = function(obj) { + painter.redrawObject = async function(obj) { this.txt = obj; - this.drawText(); - return true; + return this.drawText(); }; painter.drawText = async function() { - let txt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; - if (!isStr(txt)) txt = ''; - - const mathjax = this.txt.mathjax || (settings.Latex === constants.Latex.AlwaysMathJax); + let stxt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; + if (!isStr(stxt)) + stxt = ''; - if (!mathjax && !('as_is' in this.txt)) { - const arr = txt.split('\n'); txt = ''; - for (let i = 0; i < arr.length; ++i) - txt += `
    ${arr[i]}
    `; - } - - const frame = this.selectDom(); + const mathjax = this.txt.mathjax || (settings.Latex === constants.Latex.AlwaysMathJax), + frame = this.selectDom(); let main = frame.select('div'); if (main.empty()) main = frame.append('div').attr('style', 'max-width:100%;max-height:100%;overflow:auto'); - main.html(txt); + else + main.html(''); // (re) set painter to first child element, base painter not requires canvas this.setTopPainter(); + if (!mathjax && !('as_is' in this.txt)) { + const arr = stxt.split('\n'); + for (let i = 0; i < arr.length; ++i) + main.append('pre').style('margin', '0').text(arr[i]); + } else + main.text(stxt); + if (mathjax) typesetMathjax(frame.node()); @@ -1571,18 +1711,25 @@ function drawRawText(dom, txt /* , opt */) { return painter.drawText(); } -/** @summary Returns canvas painter (if any) for specified HTML element - * @param {string|object} dom - id or DOM element +/** @summary Returns canvas painter (if any) for specified DOM element + * @param {string|object} dom - id or DOM element or pad painter * @private */ function getElementCanvPainter(dom) { - return new ObjectPainter(dom).getCanvPainter(); + return isPadPainter(dom) ? dom.getCanvPainter() : getDomCanvasPainter(new ObjectPainter(dom).selectDom()); +} + +/** @summary Returns pad painter (if any) for specified DOM element + * @param {string|object} dom - id or DOM element or pad painter + * @private */ +function getElementPadPainter(dom) { + return isPadPainter(dom) ? dom : new ObjectPainter(dom).getPadPainter(); } /** @summary Returns main painter (if any) for specified HTML element - typically histogram painter - * @param {string|object} dom - id or DOM element + * @param {string|object} dom - id or DOM element or pad painter * @private */ function getElementMainPainter(dom) { - return new ObjectPainter(dom).getMainPainter(true); + return isPadPainter(dom) ? dom.getMainPainter() : new ObjectPainter(dom).getMainPainter(true); } /** @summary Save object, drawn in specified element, as JSON. @@ -1651,7 +1798,10 @@ function resize(dom, arg) { * cleanup(document.querySelector('#drawing')); */ function cleanup(dom) { const dummy = new ObjectPainter(dom), lst = []; - dummy.forEachPainter(p => { if (lst.indexOf(p) < 0) lst.push(p); }); + dummy.forEachPainter(p => { + if (lst.indexOf(p) < 0) + lst.push(p); + }); lst.forEach(p => p.cleanup()); dummy.selectDom().html(''); return lst; @@ -1673,10 +1823,11 @@ const EAxisBits = { kLabelsUp: BIT(21), kIsInteger: BIT(22), kMoreLogLabels: BIT(23), - kOppositeTitle: BIT(32) // atrificial bit, not possible to set in ROOT + kOppositeTitle: BIT(32) // artificial bit, not possible to set in ROOT }, kAxisLabels = 'labels', kAxisNormal = 'normal', kAxisFunc = 'func', kAxisTime = 'time'; +Object.assign(internals.jsroot, { ObjectPainter, cleanup, resize }); -export { getElementCanvPainter, getElementMainPainter, drawingJSON, - selectActivePad, getActivePad, cleanup, resize, - ObjectPainter, drawRawText, EAxisBits, kAxisLabels, kAxisNormal, kAxisFunc, kAxisTime }; +export { isPadPainter, getDomCanvasPainter, getElementPadPainter, getElementCanvPainter, getElementMainPainter, drawingJSON, + selectActivePad, getActivePad, cleanup, resize, drawRawText, + ObjectPainter, EAxisBits, kAxisLabels, kAxisNormal, kAxisFunc, kAxisTime }; diff --git a/modules/base/RObjectPainter.mjs b/modules/base/RObjectPainter.mjs index e88291af7..ec47735f3 100644 --- a/modules/base/RObjectPainter.mjs +++ b/modules/base/RObjectPainter.mjs @@ -1,30 +1,51 @@ -import { isStr, isFunc, nsREX } from '../core.mjs'; +import { isStr, isFunc, nsREX, settings, isNodeJs, isBatchMode } from '../core.mjs'; import { FontHandler } from './FontHandler.mjs'; import { ObjectPainter } from './ObjectPainter.mjs'; +import { color as d3_color } from '../d3.mjs'; const kNormal = 1, /* kLessTraffic = 2, */ kOffline = 3; class RObjectPainter extends ObjectPainter { + #pending_request; + #auto_colors; // handle for auto colors + constructor(dom, obj, opt, csstype) { super(dom, obj, opt); this.csstype = csstype; } + /** @summary Add painter to pad list of painters + * @desc For RCanvas also handles common style + * @protected */ + addToPadPrimitives(pad_painter) { + const pp = super.addToPadPrimitives(pad_painter); + + if (pp && !this.rstyle && pp.next_rstyle) + this.rstyle = pp.next_rstyle; + + return pp; + } + /** @summary Evaluate v7 attributes using fAttr storage and configured RStyle */ v7EvalAttr(name, dflt) { const obj = this.getObject(); - if (!obj) return dflt; - if (this.cssprefix) name = this.cssprefix + name; + if (!obj) + return dflt; + if (this.cssprefix) + name = this.cssprefix + name; const type_check = res => { - if (dflt === undefined) return res; + if (dflt === undefined) + return res; const typ1 = typeof dflt, typ2 = typeof res; - if (typ1 === typ2) return res; + if (typ1 === typ2) + return res; if (typ1 === 'boolean') { - if (typ2 === 'string') return (res !== '') && (res !== '0') && (res !== 'no') && (res !== 'off'); - return !!res; + if (typ2 === 'string') + return (res !== '') && (res !== '0') && (res !== 'no') && (res !== 'off'); + return Boolean(res); } if ((typ1 === 'number') && (typ2 === 'string')) return parseFloat(res); @@ -33,7 +54,8 @@ class RObjectPainter extends ObjectPainter { if (obj.fAttr?.m) { const value = obj.fAttr.m[name]; - if (value) return type_check(value.v); // found value direct in attributes + if (value) + return type_check(value.v); // found value direct in attributes } if (this.rstyle?.fBlocks) { @@ -46,7 +68,8 @@ class RObjectPainter extends ObjectPainter { if (match && block.map?.m) { const value = block.map.m[name.toLowerCase()]; - if (value) return type_check(value.v); + if (value) + return type_check(value.v); } } } @@ -57,7 +80,8 @@ class RObjectPainter extends ObjectPainter { /** @summary Set v7 attributes value */ v7SetAttr(name, value) { const obj = this.getObject(); - if (this.cssprefix) name = this.cssprefix + name; + if (this.cssprefix) + name = this.cssprefix + name; if (obj?.fAttr?.m) obj.fAttr.m[name] = { v: value }; @@ -65,15 +89,16 @@ class RObjectPainter extends ObjectPainter { /** @summary Decode pad length from string, return pixel value */ v7EvalLength(name, sizepx, dflt) { - if (sizepx <= 0) sizepx = 1; + if (sizepx <= 0) + sizepx = 1; const value = this.v7EvalAttr(name); if (value === undefined) - return Math.round(dflt*sizepx); + return Math.round(dflt * sizepx); if (typeof value === 'number') - return Math.round(value*sizepx); + return Math.round(value * sizepx); if (value === null) return 0; @@ -90,7 +115,7 @@ class RObjectPainter extends ObjectPainter { if ((val[pos] === '-') || (val[pos] === '+')) { if (operand) { - console.log('Fail to parse RPadLength ' + value); + console.log(`Fail to parse RPadLength ${value}`); return dflt; } operand = (val[pos] === '-') ? -1 : 1; @@ -98,63 +123,76 @@ class RObjectPainter extends ObjectPainter { continue; } - if (pos > 0) { val = val.slice(pos); pos = 0; } + if (pos > 0) { + val = val.slice(pos); + pos = 0; + } - while ((pos < val.length) && (((val[pos] >= '0') && (val[pos] <= '9')) || (val[pos] === '.'))) pos++; + while ((pos < val.length) && (((val[pos] >= '0') && (val[pos] <= '9')) || (val[pos] === '.'))) + pos++; const v = parseFloat(val.slice(0, pos)); if (!Number.isFinite(v)) { console.log(`Fail to parse RPadLength ${value}`); - return Math.round(dflt*sizepx); + return Math.round(dflt * sizepx); } val = val.slice(pos); pos = 0; - if (!operand) operand = 1; + if (!operand) + operand = 1; if (val && (val[0] === '%')) { val = val.slice(1); - norm += operand*v*0.01; + norm += operand * v * 0.01; } else if ((val.length > 1) && (val[0] === 'p') && (val[1] === 'x')) { val = val.slice(2); - px += operand*v; + px += operand * v; } else - norm += operand*v; + norm += operand * v; operand = 0; } - return Math.round(norm*sizepx + px); + return Math.round(norm * sizepx + px); } /** @summary Evaluate RColor using attribute storage and configured RStyle */ v7EvalColor(name, dflt) { let val = this.v7EvalAttr(name, ''); - if (!val || !isStr(val)) return dflt; + if (!val || !isStr(val)) + return dflt; if (val === 'auto') { const pp = this.getPadPainter(); - if (pp?._auto_color_cnt !== undefined) { - const pal = pp.getHistPalette(), - cnt = pp._auto_color_cnt++; - let num = pp._num_primitives - 1; - if (num < 2) num = 2; - val = pal ? pal.getColorOrdinal((cnt % num) / num) : 'blue'; - if (!this._auto_colors) this._auto_colors = {}; - this._auto_colors[name] = val; - } else if (this._auto_colors && this._auto_colors[name]) - val = this._auto_colors[name]; + if (pp) { + val = pp.getAutoColor(); + if (!this.#auto_colors) + this.#auto_colors = {}; + this.#auto_colors[name] = val; + } else if (this.#auto_colors && this.#auto_colors[name]) + val = this.#auto_colors[name]; else { console.error(`Autocolor ${name} not defined yet - please check code`); val = ''; } } else if (val[0] === '[') { - const ordinal = parseFloat(val.slice(1, val.length-1)); + const ordinal = parseFloat(val.slice(1, val.length - 1)); val = 'black'; if (Number.isFinite(ordinal)) { - const pal = this.getPadPainter()?.getHistPalette(); - if (pal) val = pal.getColorOrdinal(ordinal); + const pal = this.getPadPainter()?.getHistPalette(); + if (pal) + val = pal.getColorOrdinal(ordinal); } } + + // to make colors similar in node and in pupperteer + if ((val[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) { + const col = d3_color(val); + if (col.opacity !== 1) + col.opacity = col.opacity.toFixed(2); + return col.formatRgb(); + } + return val; } @@ -174,25 +212,32 @@ class RObjectPainter extends ObjectPainter { font_family = this.v7EvalAttr(name + '_font_family', rfont.fFamily || 'Arial'), font_style = this.v7EvalAttr(name + '_font_style', rfont.fStyle || ''), font_weight = this.v7EvalAttr(name + '_font_weight', rfont.fWeight || ''); - let text_size = this.v7EvalAttr(name + '_size', dflts.size || 12); - - if (isStr(text_size)) text_size = parseFloat(text_size); - if (!Number.isFinite(text_size) || (text_size <= 0)) text_size = 12; - if (!fontScale) fontScale = pp?.getPadHeight() || 100; - - const handler = new FontHandler(null, text_size, fontScale); - handler.setNameStyleWeight(font_family, font_style, font_weight); - - if (text_angle) handler.setAngle(360 - text_angle); - if (text_align !== 'none') handler.setAlign(text_align); - if (text_color !== 'none') handler.setColor(text_color); - - return handler; - } + let text_size = this.v7EvalAttr(name + '_size', dflts.size || 12); + + if (isStr(text_size)) + text_size = parseFloat(text_size); + if (!Number.isFinite(text_size) || (text_size <= 0)) + text_size = 12; + if (!fontScale) + fontScale = pp?.getPadHeight() || 100; + + const handler = new FontHandler(null, text_size, fontScale); + handler.setNameStyleWeight(font_family, font_style, font_weight); + + if (text_angle) + handler.setAngle(360 - text_angle); + if (text_align !== 'none') + handler.setAlign(text_align); + if (text_color !== 'none') + handler.setColor(text_color); + + return handler; + } /** @summary Create this.fillatt object based on v7 fill attributes */ createv7AttFill(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'fill_'; + if (!prefix || !isStr(prefix)) + prefix = 'fill_'; const color = this.v7EvalColor(prefix + 'color', ''), pattern = this.v7EvalAttr(prefix + 'style', 0); @@ -202,12 +247,15 @@ class RObjectPainter extends ObjectPainter { /** @summary Create this.lineatt object based on v7 line attributes */ createv7AttLine(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'line_'; + if (!prefix || !isStr(prefix)) + prefix = 'line_'; const color = this.v7EvalColor(prefix + 'color', 'black'), width = this.v7EvalAttr(prefix + 'width', 1), - style = this.v7EvalAttr(prefix + 'style', 1), - pattern = this.v7EvalAttr(prefix + 'pattern'); + style = this.v7EvalAttr(prefix + 'style', 1); + let pattern = this.v7EvalAttr(prefix + 'pattern'); + if (pattern && isNodeJs()) + pattern = pattern.split(',').join(', '); this.createAttLine({ color, width, style, pattern }); @@ -215,9 +263,10 @@ class RObjectPainter extends ObjectPainter { this.lineatt.setBorder(this.v7EvalAttr(prefix + 'rx', 0), this.v7EvalAttr(prefix + 'ry', 0)); } - /** @summary Create this.markeratt object based on v7 attributes */ + /** @summary Create this.markeratt object based on v7 attributes */ createv7AttMarker(prefix) { - if (!prefix || !isStr(prefix)) prefix = 'marker_'; + if (!prefix || !isStr(prefix)) + prefix = 'marker_'; const color = this.v7EvalColor(prefix + 'color', 'black'), size = this.v7EvalAttr(prefix + 'size', 0.01), @@ -230,7 +279,7 @@ class RObjectPainter extends ObjectPainter { /** @summary Create RChangeAttr, which can be applied on the server side * @private */ v7AttrChange(req, name, value, kind) { - if (!this.snapid) + if (!this.getSnapId()) return false; if (!req._typename) { @@ -241,14 +290,16 @@ class RObjectPainter extends ObjectPainter { req.update = true; } - if (this.cssprefix) name = this.cssprefix + name; - req.ids.push(this.snapid); + if (this.cssprefix) + name = this.cssprefix + name; + req.ids.push(this.getSnapId()); req.names.push(name); - let obj = null; if ((value === null) || (value === undefined)) { - if (!kind) kind = 'none'; - if (kind !== 'none') console.error(`Trying to set ${kind} for none value`); + if (!kind) + kind = 'none'; + if (kind !== 'none') + console.error(`Trying to set ${kind} for none value`); } if (!kind) { @@ -258,13 +309,27 @@ class RObjectPainter extends ObjectPainter { } } - obj = { _typename: `${nsREX}RAttrMap::` }; + const obj = { _typename: `${nsREX}RAttrMap::` }; switch (kind) { - case 'none': obj._typename += 'NoValue_t'; break; - case 'boolean': obj._typename += 'BoolValue_t'; obj.v = !!value; break; - case 'int': obj._typename += 'IntValue_t'; obj.v = parseInt(value); break; - case 'double': obj._typename += 'DoubleValue_t'; obj.v = parseFloat(value); break; - default: obj._typename += 'StringValue_t'; obj.v = isStr(value) ? value : JSON.stringify(value); break; + case 'none': + obj._typename += 'NoValue_t'; + break; + case 'boolean': + obj._typename += 'BoolValue_t'; + obj.v = Boolean(value); + break; + case 'int': + obj._typename += 'IntValue_t'; + obj.v = parseInt(value); + break; + case 'double': + obj._typename += 'DoubleValue_t'; + obj.v = parseFloat(value); + break; + default: + obj._typename += 'StringValue_t'; + obj.v = isStr(value) ? value : JSON.stringify(value); + break; } req.values.push(obj); @@ -276,7 +341,7 @@ class RObjectPainter extends ObjectPainter { const canp = this.getCanvPainter(); if (canp && req?._typename) { if (do_update !== undefined) - req.update = !!do_update; + req.update = Boolean(do_update); canp.v7SubmitRequest('', req); } } @@ -287,12 +352,13 @@ class RObjectPainter extends ObjectPainter { * @param method is method of painter object which will be called when getting reply */ v7SubmitRequest(kind, req, method) { const canp = this.getCanvPainter(); - if (!isFunc(canp?.submitDrawableRequest)) return null; + if (!isFunc(canp?.submitDrawableRequest)) + return null; // special situation when snapid not yet assigned - just keep ref until snapid is there // maybe keep full list - for now not clear if really needed - if (!this.snapid) { - this._pending_request = { kind, req, method }; + if (!this.getSnapId()) { + this.#pending_request = { kind, req, method }; return req; } @@ -302,11 +368,11 @@ class RObjectPainter extends ObjectPainter { /** @summary Assign snapid to the painter * @desc Overwrite default method */ assignSnapId(id) { - this.snapid = id; - if (this.snapid && this._pending_request) { - const p = this._pending_request; + super.assignSnapId(id); + if (this.getSnapId() && this.#pending_request) { + const p = this.#pending_request; + this.#pending_request = undefined; this.v7SubmitRequest(p.kind, p.req, p.method); - delete this._pending_request; } } @@ -317,7 +383,7 @@ class RObjectPainter extends ObjectPainter { * kNormal is standard functionality with RCanvas on server side */ v7CommMode() { const canp = this.getCanvPainter(); - if (!canp || !canp.submitDrawableRequest || !canp._websocket) + if (!canp || !canp.submitDrawableRequest || !canp.getWebsocket()) return kOffline; return kNormal; diff --git a/modules/base/TAttFillHandler.mjs b/modules/base/TAttFillHandler.mjs index f6e17caf1..96a906168 100644 --- a/modules/base/TAttFillHandler.mjs +++ b/modules/base/TAttFillHandler.mjs @@ -1,6 +1,6 @@ import { gStyle, isObject, isStr } from '../core.mjs'; import { color as d3_color, rgb as d3_rgb, select as d3_select } from '../d3.mjs'; -import { getColor, findColor, clTLinearGradient, clTRadialGradient, toHex } from './colors.mjs'; +import { getColor, findColor, clTLinearGradient, clTRadialGradient, toColor } from './colors.mjs'; /** @@ -10,9 +10,12 @@ import { getColor, findColor, clTLinearGradient, clTRadialGradient, toHex } from class TAttFillHandler { + #disabled; // if fill disabled + /** @summary constructor * @param {object} args - arguments see {@link TAttFillHandler#setArgs} for more info - * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, 2 means all other objects where such combination is white-color filling */ + * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, + * 2 means all other objects where such combination is white-color filling */ constructor(args) { this.color = 'none'; this.colorindx = 0; @@ -29,15 +32,18 @@ class TAttFillHandler { * @param {object} args - different arguments to set fill attributes * @param {object} [args.attr] - TAttFill object * @param {number} [args.color] - color id - * @param {number} [args.pattern] - filll pattern id + * @param {number} [args.pattern] - fill pattern id * @param {object} [args.svg] - SVG element to store newly created patterns * @param {string} [args.color_as_svg] - color in SVG format */ setArgs(args) { if (isObject(args.attr)) { - if ((args.pattern === undefined) && (args.attr.fFillStyle !== undefined)) args.pattern = args.attr.fFillStyle; - if ((args.color === undefined) && (args.attr.fFillColor !== undefined)) args.color = args.attr.fFillColor; + args.pattern ??= args.attr.fFillStyle; + args.color ??= args.attr.fFillColor; } + if (args.enable !== undefined) + this.enable(args.enable); + const was_changed = this.changed; // preserve changed state this.change(args.color, args.pattern, args.svg, args.color_as_svg, args.painter); this.changed = was_changed; @@ -45,6 +51,11 @@ class TAttFillHandler { /** @summary Apply fill style to selection */ apply(selection) { + if (this.#disabled) { + selection.style('fill', 'none'); + return; + } + this.used = true; selection.style('fill', this.getFillColor()); @@ -61,9 +72,9 @@ class TAttFillHandler { /** @summary Returns fill color without pattern url. * @desc If empty, alternative color will be provided - * @param {string} [altern] - alternative color which returned when fill color not exists + * @param {string} [alt] - alternative color which returned when fill color not exists * @private */ - getFillColorAlt(altern) { return this.color && (this.color !== 'none') ? this.color : altern; } + getFillColorAlt(alt) { return this.color && (this.color !== 'none') ? this.color : alt; } /** @summary Returns true if color not specified or fill style not specified */ empty() { @@ -71,6 +82,11 @@ class TAttFillHandler { return !fill || (fill === 'none'); } + /** @summary Enable or disable fill usage - if disabled only 'fill: none' will be applied */ + enable(on) { + this.#disabled = ((on === undefined) || on) ? undefined : true; + } + /** @summary Set usage flag of attribute */ setUsed(flag) { this.used = flag; @@ -97,7 +113,8 @@ class TAttFillHandler { /** @summary Check if solid fill is used, also color can be checked * @param {string} [solid_color] - when specified, checks if fill color matches */ isSolid(solid_color) { - if ((this.pattern !== 1001) || this.gradient) return false; + if ((this.pattern !== 1001) || this.gradient) + return false; return !solid_color || (solid_color === this.color); } @@ -109,7 +126,7 @@ class TAttFillHandler { if (!Number.isInteger(this.pattern)) this.pattern = 0; - this.change(this.color, this.pattern, painter ? painter.getCanvSvg() : null, true, painter); + this.change(this.color, this.pattern, painter?.getCanvSvg(), true, painter); } /** @summary Method to change fill attributes. @@ -154,7 +171,8 @@ class TAttFillHandler { if (color_as_svg) { this.color = color; - if (color !== 'none') indx = d3_color(color).hex().slice(1); // fictional index produced from color code + if (color !== 'none') + indx = d3_color(color).hex().slice(1); // fictional index produced from color code } else this.color = painter ? painter.getColor(indx) : getColor(indx); @@ -164,11 +182,12 @@ class TAttFillHandler { this.color = 'none'; } - if (this.isSolid()) return true; + if (this.isSolid()) + return true; if (!this.gradient) { if ((this.pattern >= 4000) && (this.pattern <= 4100)) { - // special transparent colors (use for subpads) + // special transparent colors (use for sub-pads) this.opacity = (this.pattern - 4000) / 100; return true; } @@ -176,9 +195,10 @@ class TAttFillHandler { return false; } - if (!svg || svg.empty()) return false; + if (!svg || svg.empty()) + return false; - let id = '', lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; + let id, lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; if (this.gradient) id = `grad_${this.gradient.fNumber}`; @@ -186,50 +206,141 @@ class TAttFillHandler { id = `pat_${this.pattern}_${indx}`; switch (this.pattern) { - case 3001: w = h = 2; fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; break; - case 3002: w = 4; h = 2; fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; break; - case 3003: w = h = 4; fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; break; - case 3004: w = h = 8; lines = 'M8,0L0,8'; break; - case 3005: w = h = 8; lines = 'M0,0L8,8'; break; - case 3006: w = h = 4; lines = 'M1,0v4'; break; - case 3007: w = h = 4; lines = 'M0,1h4'; break; + case 3001: + w = h = 2; + fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; + break; + case 3002: + w = 4; + h = 2; + fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; + break; + case 3003: + w = h = 4; + fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; + break; + case 3004: + w = h = 8; + lines = 'M8,0L0,8'; + break; + case 3005: + w = h = 8; + lines = 'M0,0L8,8'; + break; + case 3006: + w = h = 4; + lines = 'M1,0v4'; + break; + case 3007: + w = h = 4; + lines = 'M0,1h4'; + break; case 3008: w = h = 10; fills = 'M0,3v-3h3ZM7,0h3v3ZM0,7v3h3ZM7,10h3v-3ZM5,2l3,3l-3,3l-3,-3Z'; lines = 'M0,3l5,5M3,10l5,-5M10,7l-5,-5M7,0l-5,5'; break; - case 3009: w = 12; h = 12; lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; lfill = 'none'; break; - case 3010: w = h = 10; lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; break; // bricks - case 3011: w = 9; h = 18; lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; lfill = 'none'; break; - case 3012: w = 10; h = 20; lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; lfill = 'none'; break; - case 3013: w = h = 7; lines = 'M0,0L7,7M7,0L0,7'; lfill = 'none'; break; - case 3014: w = h = 16; lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; lfill = 'none'; break; - case 3015: w = 6; h = 12; lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; lfill = 'none'; break; - case 3016: w = 12; h = 7; lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; lfill = 'none'; break; - case 3017: w = h = 4; lines = 'M3,1l-2,2'; break; - case 3018: w = h = 4; lines = 'M1,1l2,2'; break; + case 3009: + w = h = 12; + lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; + lfill = 'none'; + break; + case 3010: // bricks + w = h = 10; + lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; + break; + case 3011: + w = 9; + h = 18; + lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; + lfill = 'none'; + break; + case 3012: + w = 10; + h = 20; + lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; + lfill = 'none'; + break; + case 3013: + w = h = 7; + lines = 'M0,0L7,7M7,0L0,7'; + lfill = 'none'; + break; + case 3014: + w = h = 16; + lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; + lfill = 'none'; + break; + case 3015: + w = 6; + h = 12; + lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; + lfill = 'none'; + break; + case 3016: + w = 12; + h = 7; + lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; + lfill = 'none'; + break; + case 3017: + w = h = 4; + lines = 'M3,1l-2,2'; + break; + case 3018: + w = h = 4; + lines = 'M1,1l2,2'; + break; case 3019: w = h = 12; lines = 'M1,6A5,5,0,0,0,11,6A5,5,0,0,0,1,6h-1h1A5,5,0,0,1,6,11v1v-1A5,5,0,0,1,11,6h1h-1A5,5,0,0,1,6,1v-1v1A5,5,0,0,1,1,6'; lfill = 'none'; break; - case 3020: w = 7; h = 12; lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; lfill = 'none'; break; - case 3021: w = h = 8; lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; lfill = 'none'; break; // left stairs - case 3022: w = h = 8; lines = 'M0,2h2v4h4v2M6,0v2h2'; lfill = 'none'; break; // right stairs - case 3023: w = h = 8; fills = 'M4,0h4v4zM8,4v4h-4z'; fills2 = 'M4,0L0,4L4,8L8,4Z'; break; - case 3024: w = h = 16; fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; break; - case 3025: w = h = 18; fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; break; + case 3020: + w = 7; + h = 12; + lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; + lfill = 'none'; + break; + case 3021: // left stairs + w = h = 8; + lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; + lfill = 'none'; + break; + case 3022: // right stairs + w = h = 8; + lines = 'M0,2h2v4h4v2M6,0v2h2'; + lfill = 'none'; + break; + case 3023: + w = h = 8; + fills = 'M4,0h4v4zM8,4v4h-4z'; + fills2 = 'M4,0L0,4L4,8L8,4Z'; + break; + case 3024: + w = h = 16; + fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; + fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; + break; + case 3025: + w = h = 18; + fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; + break; default: { if ((this.pattern > 3025) && (this.pattern < 3100)) { // same as 3002, see TGX11.cxx, line 2234 - w = 4; h = 2; fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; break; + w = 4; + h = 2; + fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; + break; } const code = this.pattern % 1000, k = code % 10, j = ((code - k) % 100) / 10, i = (code - j * 10 - k) / 100; - if (!i) break; + if (!i) + break; // use flexible hatches only possible when single pattern is used, // otherwise it is not possible to adjust pattern dimension that both hatches match with each other @@ -240,7 +351,7 @@ class TAttFillHandler { hatches_spacing = Math.max(1, Math.round(spacing_original)) * 6, sz = i * hatches_spacing; // axis distance between lines - id += use_new ? `_hn${Math.round(spacing_original*100)}` : `_ho${hatches_spacing}`; + id += use_new ? `_hn${Math.round(spacing_original * 100)}` : `_ho${hatches_spacing}`; w = h = 6 * sz; // we use at least 6 steps @@ -273,34 +384,35 @@ class TAttFillHandler { pos.push(0, y1, w, y2); y1 += step; } - for (let k = 0; k < pos.length; k += 4) { + for (let b = 0; b < pos.length; b += 4) { if (swap) { - x1 = pos[k+1]; - y1 = pos[k]; - x2 = pos[k+3]; - y2 = pos[k+2]; + x1 = pos[b + 1]; + y1 = pos[b]; + x2 = pos[b + 3]; + y2 = pos[b + 2]; } else { - x1 = pos[k]; - y1 = pos[k+1]; - x2 = pos[k+2]; - y2 = pos[k+3]; + x1 = pos[b]; + y1 = pos[b + 1]; + x2 = pos[b + 2]; + y2 = pos[b + 3]; } lines += `M${x1},${y1}`; if (y2 === y1) - lines += `h${x2-x1}`; + lines += `h${x2 - x1}`; else if (x2 === x1) - lines += `v${y2-y1}`; + lines += `v${y2 - y1}`; else lines += `L${x2},${y2}`; } - }, + }; - produce_new = (_aa, _bb, angle, swapx) => { + /* eslint-disable-next-line one-var */ + const produce_new = (_aa, _bb, angle, swapx) => { if ((angle === 0) || (angle === 90)) { - const dy = i*spacing_original*3, + const dy = i * spacing_original * 3, nsteps = Math.round(h / dy), dyreal = h / nsteps; - let yy = dyreal/2; + let yy = dyreal / 2; while (yy < h) { if (angle === 0) @@ -313,8 +425,8 @@ class TAttFillHandler { return; } - const a = angle/180*Math.PI, - dy = i*spacing_original*3/Math.cos(a), + const a = angle / 180 * Math.PI, + dy = i * spacing_original * 3 / Math.cos(a), hside = Math.tan(a) * w, hside_steps = Math.round(hside / dy), dyreal = hside / hside_steps, @@ -324,7 +436,8 @@ class TAttFillHandler { let yy = nsteps * dyreal; - while (Math.abs(yy-h) < 0.1) yy -= dyreal; + while (Math.abs(yy - h) < 0.1) + yy -= dyreal; while (yy + hside > 0) { let x1 = 0, y1 = yy, x2 = w, y2 = yy + hside; @@ -347,9 +460,10 @@ class TAttFillHandler { lines += `M${Math.round(x1)},${Math.round(y1)}L${Math.round(x2)},${Math.round(y2)}`; yy -= dyreal; } - }, + }; - func = use_new ? produce_new : produce_old; + /* eslint-disable-next-line one-var */ + const func = use_new ? produce_new : produce_old; let horiz = false, vertical = false; @@ -377,14 +491,17 @@ class TAttFillHandler { case 9: vertical = true; break; } - if (horiz) func(0, false, 0); - if (vertical) func(0, true, 90); + if (horiz) + func(0, false, 0); + if (vertical) + func(0, true, 90); break; } } - if (!fills && !lines) return false; + if (!fills && !lines) + return false; } this.pattern_url = `url(#${id})`; @@ -412,10 +529,10 @@ class TAttFillHandler { } for (let n = 0; n < this.gradient.fColorPositions.length; ++n) { const pos = this.gradient.fColorPositions[n], - col = '#' + toHex(this.gradient.fColors[n*4]) + toHex(this.gradient.fColors[n*4+1]) + toHex(this.gradient.fColors[n*4+2]); - grad.append('svg:stop').attr('offset', `${Math.round(pos*100)}%`) + col = toColor(this.gradient.fColors[n * 4], this.gradient.fColors[n * 4 + 1], this.gradient.fColors[n * 4 + 2]); + grad.append('svg:stop').attr('offset', `${Math.round(pos * 100)}%`) .attr('stop-color', col) - .attr('stop-opacity', `${Math.round(this.gradient.fColors[n*4+3]*100)}%`); + .attr('stop-opacity', `${Math.round(this.gradient.fColors[n * 4 + 3] * 100)}%`); } } else { const patt = defs.append('svg:pattern') @@ -424,11 +541,15 @@ class TAttFillHandler { if (fills2) { const col = d3_rgb(this.color); - col.r = Math.round((col.r + 255) / 2); col.g = Math.round((col.g + 255) / 2); col.b = Math.round((col.b + 255) / 2); + col.r = Math.round((col.r + 255) / 2); + col.g = Math.round((col.g + 255) / 2); + col.b = Math.round((col.b + 255) / 2); patt.append('svg:path').attr('d', fills2).style('fill', col); } - if (fills) patt.append('svg:path').attr('d', fills).style('fill', this.color); - if (lines) patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); + if (fills) + patt.append('svg:path').attr('d', fills).style('fill', this.color); + if (lines) + patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); } } @@ -439,7 +560,8 @@ class TAttFillHandler { * @private */ createSample(svg, width, height, plain) { // we need to create extra handle to change - if (plain) svg = d3_select(svg); + if (plain) + svg = d3_select(svg); const sample = new TAttFillHandler({ svg, pattern: this.pattern, color: this.color, color_as_svg: true }); @@ -453,7 +575,8 @@ class TAttFillHandler { saveToStyle(name_color, name_pattern) { if (name_color) { const indx = this.colorindx ?? findColor(this.color); - if (indx >= 0) gStyle[name_color] = indx; + if (indx >= 0) + gStyle[name_color] = indx; } if (name_pattern) gStyle[name_pattern] = this.pattern; diff --git a/modules/base/TAttLineHandler.mjs b/modules/base/TAttLineHandler.mjs index 4f9588ea7..160bcf545 100644 --- a/modules/base/TAttLineHandler.mjs +++ b/modules/base/TAttLineHandler.mjs @@ -4,9 +4,9 @@ import { getColor, findColor } from './colors.mjs'; const root_line_styles = [ - '', '', '3,3', '1,2', - '3,4,1,4', '5,3,1,3', '5,3,1,3,1,3,1,3', '5,5', - '5,3,1,3,1,3', '20,5', '20,10,1,10', '1,3']; + '', '', '3, 3', '1, 2', + '3, 4, 1, 4', '5, 3, 1, 3', '5, 3, 1, 3, 1, 3, 1, 3', '5, 5', + '5, 3, 1, 3, 1, 3', '20, 5', '20, 10, 1, 10', '1, 3']; /** * @summary Handle for line attributes @@ -20,7 +20,8 @@ class TAttLineHandler { constructor(args) { this.func = this.apply.bind(this); this.used = true; - if (args._typename && (args.fLineStyle !== undefined)) args = { attr: args }; + if (args._typename && (args.fLineStyle !== undefined)) + args = { attr: args }; this.setArgs(args); } @@ -34,10 +35,11 @@ class TAttLineHandler { if (args.attr) { this.color_index = args.attr.fLineColor; args.color = args.color0 || (args.painter?.getColor(this.color_index) ?? getColor(this.color_index)); - if (args.width === undefined) args.width = args.attr.fLineWidth; - if (args.style === undefined) args.style = args.attr.fLineStyle; + args.width ??= args.attr.fLineWidth; + args.style ??= args.attr.fLineStyle; } else if (isStr(args.color)) { - if ((args.color !== 'none') && !args.width) args.width = 1; + if ((args.color !== 'none') && !args.width) + args.width = 1; } else if (typeof args.color === 'number') { this.color_index = args.color; args.color = args.painter?.getColor(args.color) ?? getColor(args.color); @@ -46,7 +48,8 @@ class TAttLineHandler { if (args.width === undefined) args.width = (args.color && args.color !== 'none') ? 1 : 0; - this.color = (args.width === 0) ? 'none' : args.color; + this.nocolor = args.nocolor; + this.color = (args.width === 0) || this.nocolor ? 'none' : args.color; this.width = args.width; this.style = args.style; this.pattern = args.pattern || root_line_styles[this.style] || null; @@ -72,7 +75,8 @@ class TAttLineHandler { this.excl_width = width; if (side !== undefined) { this.excl_side = side; - if ((this.excl_width === 0) && (this.excl_side !== 0)) this.excl_width = 20; + if ((this.excl_width === 0) && this.excl_side) + this.excl_width = 20; } this.changed = true; } @@ -112,15 +116,17 @@ class TAttLineHandler { applyBorder(selection) { this.used = true; if (this.empty()) { - selection.style('stroke', null) + selection.attr('rx', null) + .attr('ry', null) + .style('stroke', null) .style('stroke-width', null) - .style('stroke-dasharray', null) - .attr('rx', null).attr('ry', null); + .style('stroke-dasharray', null); } else { - selection.style('stroke', this.color) + selection.attr('rx', this.rx || null) + .attr('ry', this.ry || null) + .style('stroke', this.color) .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern) - .attr('rx', this.rx || null).attr('ry', this.ry || null); + .style('stroke-dasharray', this.pattern); } } @@ -148,9 +154,10 @@ class TAttLineHandler { /** @summary Create sample element inside primitive SVG - used in context menu */ createSample(svg, width, height, plain) { - if (plain) svg = d3_select(svg); + if (plain) + svg = d3_select(svg); svg.append('path') - .attr('d', `M0,${height/2}h${width}`) + .attr('d', `M0,${height / 2}h${width}`) .call(this.func); } @@ -172,7 +179,8 @@ class TAttLineHandler { /** @summary Get svg string for specified line style * @private */ function getSvgLineStyle(indx) { - if ((indx < 0) || (indx >= root_line_styles.length)) indx = 11; + if ((indx < 0) || (indx >= root_line_styles.length)) + indx = 11; return root_line_styles[indx]; } diff --git a/modules/base/TAttMarkerHandler.mjs b/modules/base/TAttMarkerHandler.mjs index 3e02efcd3..57e78bc4e 100644 --- a/modules/base/TAttMarkerHandler.mjs +++ b/modules/base/TAttMarkerHandler.mjs @@ -3,17 +3,20 @@ import { select as d3_select } from '../d3.mjs'; import { getColor } from './colors.mjs'; -const root_markers = [ - 0, 1, 2, 3, 4, // 0..4 - 5, 106, 107, 104, 1, // 5..9 - 1, 1, 1, 1, 1, // 10..14 - 1, 1, 1, 1, 1, // 15..19 - 104, 125, 126, 132, 4, // 20..24 - 25, 26, 27, 28, 130, // 25..29 - 30, 3, 32, 127, 128, // 30..34 - 35, 36, 37, 38, 137, // 35..39 - 40, 140, 42, 142, 44, // 40..44 - 144, 46, 146, 148, 149]; // 45..49 +// list of marker types which can have line widths +const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46], + // internal recoding of root markers + root_markers = [ + 0, 1, 2, 3, 4, // 0..4 + 5, 106, 107, 104, 1, // 5..9 + 1, 1, 1, 1, 1, // 10..14 + 1, 1, 1, 1, 1, // 15..19 + 104, 125, 126, 132, 4, // 20..24 + 25, 26, 27, 28, 130, // 25..29 + 30, 3, 32, 127, 128, // 30..34 + 35, 36, 37, 38, 137, // 35..39 + 40, 140, 42, 142, 44, // 40..44 + 144, 46, 146, 148, 149]; // 45..49 /** @@ -50,13 +53,14 @@ class TAttMarkerHandler { * @param {number} args.size - marker size * @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */ setArgs(args) { - if (isObject(args) && (typeof args.fMarkerStyle === 'number')) args = { attr: args }; + if (isObject(args) && (typeof args.fMarkerStyle === 'number')) + args = { attr: args }; if (args.attr) { - if (args.color === undefined) - args.color = args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); - if (!args.style || (args.style < 0)) args.style = args.attr.fMarkerStyle; - if (!args.size) args.size = args.attr.fMarkerSize; + args.color ??= args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); + if (!args.style || (args.style < 0)) + args.style = args.attr.fMarkerStyle; + args.size ??= args.attr.fMarkerSize; } this.color = args.color; @@ -64,7 +68,7 @@ class TAttMarkerHandler { this.size = args.size; this.refsize = args.refsize; - this._configure(); + this.#configure(); } /** @summary Set usage flag of attribute */ @@ -92,11 +96,13 @@ class TAttMarkerHandler { if ((xx === this.lastx) && (yy === this.lasty)) mv = ''; // pathological case, but let exclude it else { - const m2 = `m${xx-this.lastx},${yy - this.lasty}`; - if (m2.length < mv.length) mv = m2; + const m2 = `m${xx - this.lastx},${yy - this.lasty}`; + if (m2.length < mv.length) + mv = m2; } } - this.lastx = xx + 1; this.lasty = yy; + this.lastx = xx + 1; + this.lasty = yy; return mv + 'h1'; } @@ -113,16 +119,19 @@ class TAttMarkerHandler { change(color, style, size) { this.changed = true; - if (color !== undefined) this.color = color; - if ((style !== undefined) && (style >= 0)) this.style = style; - if (size !== undefined) this.size = size; + if (color !== undefined) + this.color = color; + if ((style !== undefined) && (style >= 0)) + this.style = style; + if (size !== undefined) + this.size = size; - this._configure(); + this.#configure(); } /** @summary Prepare object to create marker * @private */ - _configure() { + #configure() { this.x0 = this.y0 = 0; if ((this.style === 1) || (this.style === 777)) { @@ -135,8 +144,15 @@ class TAttMarkerHandler { } this.optimized = false; + this.lwidth = 1; - const marker_kind = root_markers[this.style] ?? 104, + let style = this.style; + if (style >= 50) { + this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length); + style = root_50_67[(style - 50) % root_50_67.length]; + } + + const marker_kind = root_markers[style] ?? 104, shape = marker_kind % 100; this.fill = (marker_kind >= 100); @@ -146,13 +162,15 @@ class TAttMarkerHandler { const size = this.getFullSize(); this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2); - if (shape === 30) this.ndig++; // increase precision for star + if (shape === 30) + this.ndig++; // increase precision for star let s1 = size.toFixed(this.ndig); - const s2 = (size/2).toFixed(this.ndig), - s3 = (size/3).toFixed(this.ndig), - s4 = (size/4).toFixed(this.ndig), - s8 = (size/8).toFixed(this.ndig), - s38 = (size*3/8).toFixed(this.ndig); + const s2 = (size / 2).toFixed(this.ndig), + s3 = (size / 3).toFixed(this.ndig), + s4 = (size / 4).toFixed(this.ndig), + s8 = (size / 8).toFixed(this.ndig), + s38 = (size * 3 / 8).toFixed(this.ndig), + s34 = (size * 3 / 4).toFixed(this.ndig); switch (shape) { case 1: // dot @@ -163,17 +181,17 @@ class TAttMarkerHandler { this.marker = `v${s1}m-${s2},-${s2}h${s1}`; break; case 3: // asterisk - this.x0 = this.y0 = -size / 2; - this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}m0,-${s2}h${s1}m-${s2},-${s2}v${s1}`; + this.y0 = -size / 2; + this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`; break; case 4: // circle this.x0 = -parseFloat(s2); s1 = (parseFloat(s2) * 2).toFixed(this.ndig); this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`; break; - case 5: // mult - this.x0 = this.y0 = -size / 2; - this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}`; + case 5: // multiply + this.x0 = this.y0 = -3 / 8 * size; + this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`; break; case 6: // small dot this.x0 = -1; @@ -191,7 +209,7 @@ class TAttMarkerHandler { this.y0 = -size / 2; this.marker = `l-${s2},${s1}h${s1}z`; break; - case 27: // diamand + case 27: // diamond this.y0 = -size / 2; this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; break; @@ -201,7 +219,7 @@ class TAttMarkerHandler { break; case 30: { // star this.y0 = -size / 2; - const s56 = (size*5/6).toFixed(this.ndig), s58 = (size*5/8).toFixed(this.ndig); + const s56 = (size * 5 / 6).toFixed(this.ndig), s58 = (size * 5 / 8).toFixed(this.ndig); this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`; break; } @@ -218,38 +236,44 @@ class TAttMarkerHandler { this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`; break; case 37: - this.x0 = -size/2; + this.x0 = -size / 2; this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`; break; case 38: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`; break; case 40: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`; break; case 42: - this.y0 = -size/2; + this.y0 = -size / 2; this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`; break; case 44: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`; break; case 46: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`; break; case 48: - this.x0 = -size/4; this.y0 = -size/2; + this.x0 = -size / 4; + this.y0 = -size / 2; this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`; break; case 49: - this.x0 = -size/6; this.y0 = -size/2; + this.x0 = -size / 6; + this.y0 = -size / 2; this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`; break; - default: // diamand + default: // diamond this.y0 = -size / 2; this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; break; @@ -271,6 +295,7 @@ class TAttMarkerHandler { apply(selection) { this.used = true; selection.style('stroke', this.stroke ? this.color : 'none') + .style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null) .style('fill', this.fill ? this.color : 'none'); } @@ -286,7 +311,8 @@ class TAttMarkerHandler { * @param {number} height - height of sample SVG * @private */ createSample(svg, width, height, plain) { - if (plain) svg = d3_select(svg); + if (plain) + svg = d3_select(svg); this.resetPos(); svg.append('path') .attr('d', this.create(width / 2, height / 2)) diff --git a/modules/base/TAttTextHandler.mjs b/modules/base/TAttTextHandler.mjs index fef20c947..7a92f47f8 100644 --- a/modules/base/TAttTextHandler.mjs +++ b/modules/base/TAttTextHandler.mjs @@ -1,6 +1,16 @@ import { getColor } from './colors.mjs'; +function calcTextSize(sz, sz0, fact, pp) { + if (!sz) + sz = sz0 || 0; + + if (sz >= 1) + return Math.round(sz * (pp?.getPadScale() || 1)); + + return Math.round(sz * Math.min(pp?.getPadWidth() ?? 1000, pp?.getPadHeight() ?? 1000) * (fact || 1)); +} + /** * @summary Handle for text attributes * @private @@ -12,7 +22,8 @@ class TAttTextHandler { * @param {object} attr - attributes, see {@link TAttTextHandler#setArgs} */ constructor(args) { this.used = true; - if (args._typename && (args.fTextFont !== undefined)) args = { attr: args }; + if (args._typename && (args.fTextFont !== undefined)) + args = { attr: args }; this.setArgs(args); } @@ -78,7 +89,8 @@ class TAttTextHandler { /** @summary Create argument for drawText method */ createArg(arg) { - if (!arg) arg = {}; + if (!arg) + arg = {}; this.align_used = !arg.noalign && !arg.align; if (this.align_used) arg.align = this.align; @@ -90,30 +102,19 @@ class TAttTextHandler { } /** @summary Provides pixel size */ - getSize(w, h, fact, zero_size) { - if (this.size >= 1) - return Math.round(this.size); - if (!w) w = 1000; - if (!h) h = w; - if (!fact) fact = 1; - - return Math.round((this.size || zero_size || 0) * Math.min(w, h) * fact); - } + getSize(pp, fact, zero_size) { return calcTextSize(this.size, zero_size, fact, pp); } /** @summary Returns alternating size - which defined by sz1 variable */ - getAltSize(sz1, h) { - if (!sz1) sz1 = this.size; - return Math.round(sz1 >= 1 ? sz1 : sz1 * h); - } + getAltSize(sz1, pp) { return calcTextSize(sz1, this.size, 1, pp); } /** @summary Get font index - without precision */ - getGedFont() { return Math.floor(this.font/10); } + getGedFont() { return Math.floor(this.font / 10); } /** @summary Change text font from GED */ setGedFont(value) { const v = parseInt(value); if ((v > 0) && (v < 17)) - this.change(v*10 + (this.font % 10)); + this.change(v * 10 + (this.font % 10)); return this.font; } diff --git a/modules/base/base3d.mjs b/modules/base/base3d.mjs index c7a891341..0616f41cc 100644 --- a/modules/base/base3d.mjs +++ b/modules/base/base3d.mjs @@ -1,44 +1,98 @@ import { select as d3_select, color as d3_color } from '../d3.mjs'; +import { browser, settings, internals, constants, isBatchMode, nsSVG, + isNodeJs, isObject, isFunc, isStr, getDocument } from '../core.mjs'; import { WebGLRenderer, WebGLRenderTarget, CanvasTexture, TextureLoader, Raycaster, BufferGeometry, BufferAttribute, Float32BufferAttribute, Vector2, Vector3, Color, Points, PointsMaterial, - LineSegments, LineDashedMaterial, LineBasicMaterial } from '../three.mjs'; -import { Font, OrbitControls, SVGRenderer } from '../three_addons.mjs'; -import { browser, settings, constants, isBatchMode, nsSVG, isNodeJs, isObject, isFunc, isStr, getDocument } from '../core.mjs'; -import { getElementRect, getAbsPosInCanvas, makeTranslate } from './BasePainter.mjs'; + LineSegments, LineDashedMaterial, LineBasicMaterial, + REVISION, DoubleSide, Object3D, Matrix4, Line3, Mesh, MeshBasicMaterial, MeshLambertMaterial, + Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, + FrontSide, Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, + MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, + Group, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, + AmbientLight, HemisphereLight, DirectionalLight } from '../three.mjs'; +import { Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass } from '../three_addons.mjs'; +import { prSVG, getElementRect, getAbsPosInCanvas, makeTranslate } from './BasePainter.mjs'; import { TAttMarkerHandler } from './TAttMarkerHandler.mjs'; import { getSvgLineStyle } from './TAttLineHandler.mjs'; -/** @ummary Create three.js Font with Helvetica +const originalTHREE = { + REVISION, DoubleSide, FrontSide, Object3D, Color, Vector2, Vector3, Matrix4, Line3, Raycaster, + WebGLRenderer, WebGLRenderTarget, + BufferGeometry, BufferAttribute, Float32BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, + LineSegments, LineDashedMaterial, LineBasicMaterial, Points, PointsMaterial, + Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, + Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, + MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, + Group, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, + AmbientLight, HemisphereLight, DirectionalLight, + CanvasTexture, TextureLoader, + + Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass +}, THREE = Object.assign({}, originalTHREE); + + +/** @summary Import proper three.js version + * @desc in node.js only r162 supports WebGL1 which can be emulated with "gl" package. + * Therefore only this version can be used for working in node.js + * @private */ +async function importThreeJs(original) { + if (original) { + Object.keys(THREE).forEach(key => delete THREE[key]); + Object.assign(THREE, originalTHREE); + return THREE; + } + + if (!isNodeJs() || (THREE.REVISION <= 162)) + return THREE; + return import('three').then(h1 => { + Object.assign(THREE, h1); + return import('three/addons'); + }).then(h2 => { + Object.assign(THREE, h2); + return THREE; + }); +} + +let _hfont; + +/** @summary Create three.js Helvetica Regular Font instance * @private */ -function createHelveticaFont() { -// eslint-disable-next-line -const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; -// eslint-disable-next-line -const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; +function getHelveticaFont() { + if (_hfont && _hfont instanceof THREE.Font) + return _hfont; - return new Font({ + // eslint-disable-next-line + const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; + // eslint-disable-next-line + const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; + + // segment in clockwise - holes, otherwise - solid + // provide infinity symbol + glyphs['\u221E'] = { x_min: 100, x_max: 700, ha: 800, o: 'm 150 200 l 350 300 l 150 400 l 150 200 m 450 300 l 650 200 l 650 400 l 450 300 m 100 100 l 100 500 l 400 350 l 700 500 l 700 100 l 400 250 l 100 100 ' }; + + _hfont = new THREE.Font({ ascender, boundingBox, cssFontStyle, cssFontWeight, default: helvetiker_regular_typeface, descender, familyName, glyphs, lineHeight, original_font_information, resolution, underlinePosition, underlineThickness }); -} -// eslint-disable-next-line -const HelveticerRegularFont = new createHelveticaFont(); + return _hfont; +} -/** @ummary Create three.js Color instance, handles optional opacity +/** @summary Create three.js Color instance, handles optional opacity * @private */ function getMaterialArgs(color, args) { - if (!args || !isObject(args)) args = {}; + if (!args || !isObject(args)) + args = {}; if (isStr(color) && (((color[0] === '#') && (color.length === 9)) || (color.indexOf('rgba') >= 0))) { const col = d3_color(color); - args.color = new Color(col.r, col.g, col.b); + args.color = new THREE.Color(col.r, col.g, col.b); args.opacity = col.opacity ?? 1; args.transparent = args.opacity < 1; } else - args.color = new Color(color); + args.color = new THREE.Color(color); return args; } @@ -46,58 +100,59 @@ function createSVGRenderer(as_is, precision, doc) { if (as_is) { if (doc !== undefined) globalThis.docuemnt = doc; - const rndr = new SVGRenderer(); + const rndr = new THREE.SVGRenderer(); rndr.setPrecision(precision); return rndr; } const excl_style1 = ';stroke-opacity:1;stroke-width:1;stroke-linecap:round', - excl_style2 = ';fill-opacity:1', - doc_wrapper = { - svg_attr: {}, - svg_style: {}, - path_attr: {}, - accPath: '', - createElementNS(ns, kind) { - if (kind === 'path') { - return { - _wrapper: this, - setAttribute(name, value) { - // cut useless fill-opacity:1 at the end of many SVG attributes - if ((name === 'style') && value) { - const pos1 = value.indexOf(excl_style1); - if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) - value = value.slice(0, value.length - excl_style1.length); - const pos2 = value.indexOf(excl_style2); - if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) - value = value.slice(0, value.length - excl_style2.length); - } - this._wrapper.path_attr[name] = value; - } - }; - } - - if (kind !== 'svg') { - console.error(`not supported element for SVGRenderer ${kind}`); - return null; - } - - return { - _wrapper: this, - childNodes: [], // may be accessed - make dummy - style: this.svg_style, // for background color - setAttribute(name, value) { - this._wrapper.svg_attr[name] = value; - }, - appendChild(_node) { - this._wrapper.accPath += ``; - this._wrapper.path_attr = {}; - }, - removeChild(_node) { - this.childNodes = []; - } - }; - } + excl_style2 = ';fill-opacity:1'; + /* eslint-disable-next-line one-var */ + const doc_wrapper = { + svg_attr: {}, + svg_style: {}, + path_attr: {}, + accPath: '', + createElementNS(ns, kind) { + if (kind === 'path') { + return { + _wrapper: this, + setAttribute(name, value) { + // cut useless fill-opacity:1 at the end of many SVG attributes + if ((name === 'style') && value) { + const pos1 = value.indexOf(excl_style1); + if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) + value = value.slice(0, value.length - excl_style1.length); + const pos2 = value.indexOf(excl_style2); + if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) + value = value.slice(0, value.length - excl_style2.length); + } + this._wrapper.path_attr[name] = value; + } + }; + } + + if (kind !== 'svg') { + console.error(`not supported element for SVGRenderer ${kind}`); + return null; + } + + return { + _wrapper: this, + childNodes: [], // may be accessed - make dummy + style: this.svg_style, // for background color + setAttribute(name, value) { + this._wrapper.svg_attr[name] = value; + }, + appendChild(/* node */) { + this._wrapper.accPath += ``; + this._wrapper.path_attr = {}; + }, + removeChild(/* node */) { + this.childNodes = []; + } + }; + } }; let originalDocument; @@ -107,7 +162,7 @@ function createSVGRenderer(as_is, precision, doc) { globalThis.document = doc_wrapper; } - const rndr = new SVGRenderer(); + const rndr = new THREE.SVGRenderer(); if (isNodeJs()) globalThis.document = originalDocument; @@ -117,14 +172,14 @@ function createSVGRenderer(as_is, precision, doc) { rndr.originalRender = rndr.render; rndr.render = function(scene, camera) { - const originalDocument = globalThis.document; + const _doc = globalThis.document; if (isNodeJs()) globalThis.document = this.doc_wrapper; this.originalRender(scene, camera); if (isNodeJs()) - globalThis.document = originalDocument; + globalThis.document = _doc; }; rndr.clearHTML = function() { @@ -133,10 +188,10 @@ function createSVGRenderer(as_is, precision, doc) { rndr.makeOuterHTML = function() { const wrap = this.doc_wrapper, - _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, - _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; + _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, + _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; - return `${wrap.accPath}`; + return `${wrap.accPath}`; }; rndr.fillTargetSVG = function(svg) { @@ -171,8 +226,8 @@ function createSVGRenderer(as_is, precision, doc) { } -/** @ummary Define rendering kind which will be used for rendering of 3D elements - * @param {value} [render3d] - preconfigured value, will be used if applicable +/** @summary Define rendering kind which will be used for rendering of 3D elements + * @param {value} [render3d] - pre-configured value, will be used if applicable * @param {value} [is_batch] - is batch mode is configured * @return {value} - rendering kind, see constants.Render3D * @private */ @@ -180,11 +235,14 @@ function getRender3DKind(render3d, is_batch) { if (is_batch === undefined) is_batch = isBatchMode(); - if (!render3d) render3d = is_batch ? settings.Render3DBatch : settings.Render3D; + if (!render3d) + render3d = is_batch ? settings.Render3DBatch : settings.Render3D; const rc = constants.Render3D; - if (render3d === rc.Default) render3d = is_batch ? rc.WebGLImage : rc.WebGL; - if (is_batch && (render3d === rc.WebGL)) render3d = rc.WebGLImage; + if (render3d === rc.Default) + render3d = is_batch ? rc.WebGLImage : rc.WebGL; + if (is_batch && (render3d === rc.WebGL)) + render3d = rc.WebGLImage; return render3d; } @@ -196,19 +254,21 @@ const Handling3DDrawings = { * @return current value * @private */ access3dKind(new_value) { - const svg = this.getPadSvg(); - if (svg.empty()) return -1; + const svg = this.getPadPainter()?.getPadSvg(); + if (!svg || svg.empty()) + return -1; // returns kind of currently created 3d canvas const kind = svg.property('can3d'); - if (new_value !== undefined) svg.property('can3d', new_value); + if (new_value !== undefined) + svg.property('can3d', new_value); return ((kind === null) || (kind === undefined)) ? -1 : kind; }, - /** @summary Returns size which availble for 3D drawing. + /** @summary Returns size which available for 3D drawing. * @desc One uses frame sizes for the 3D drawing - like TH2/TH3 objects * @private */ - getSizeFor3d(can3d /*, render3d */) { + getSizeFor3d(can3d /* , render3d */) { if (can3d === undefined) { // analyze which render/embed mode can be used can3d = getRender3DKind(); @@ -220,16 +280,17 @@ const Handling3DDrawings = { else if (browser.isFirefox) can3d = constants.Embed3D.Embed; else if (browser.chromeVersion > 95) - // version 96 works partially, 97 works fine + // version 96 works partially, 97 works fine can3d = constants.Embed3D.Embed; else can3d = constants.Embed3D.Overlay; } - const pad = this.getPadSvg(), - clname = 'draw3d_' + (this.getPadName() || 'canvas'); + const pp = this.getPadPainter(), + pad = pp?.getPadSvg(), + clname = 'draw3d_' + (pp?.getPadName() || 'canvas'); - if (pad.empty()) { + if (!pad || pad.empty()) { // this is a case when object drawn without canvas const rect = getElementRect(this.selectDom()); @@ -237,11 +298,13 @@ const Handling3DDrawings = { rect.height = Math.round(0.66 * rect.width); this.selectDom().style('height', rect.height + 'px'); } - rect.x = 0; rect.y = 0; rect.clname = clname; rect.can3d = -1; + rect.x = rect.y = 0; + rect.clname = clname; + rect.can3d = -1; return rect; } - const fp = this.getFramePainter(), pp = this.getPadPainter(); + const fp = this.getFramePainter(); let size; if (fp?.mode3d && (can3d > 0)) @@ -253,7 +316,7 @@ const Handling3DDrawings = { size.width = pp.getPadWidth(); size.height = pp.getPadHeight(); } else if (fp && !fp.mode3d) { - elem = this.getFrameSvg(); + elem = pp.getFrameSvg(); size.x = elem.property('draw_x'); size.y = elem.property('draw_y'); } @@ -265,16 +328,24 @@ const Handling3DDrawings = { const rect = pp?.getPadRect(); if (rect) { // while 3D canvas uses area also for the axis labels, extend area relative to normal frame - const dx = Math.round(size.width*0.07), dy = Math.round(size.height*0.05); + const dx = Math.round(size.width * 0.07), dy = Math.round(size.height * 0.05); - size.x = Math.max(0, size.x-dx); - size.y = Math.max(0, size.y-dy); - size.width = Math.min(size.width + 2*dx, rect.width - size.x); - size.height = Math.min(size.height + 2*dy, rect.height - size.y); + size.x = Math.max(0, size.x - dx); + size.y = Math.max(0, size.y - dy); + size.width = Math.min(size.width + 2 * dx, rect.width - size.x); + size.height = Math.min(size.height + 2 * dy, rect.height - size.y); } - if (can3d === 1) - size = getAbsPosInCanvas(this.getPadSvg(), size); + if (can3d === constants.Embed3D.Overlay) { + size = getAbsPosInCanvas(pad, size); + const scale = this.getCanvPainter().getPadScale(); + if (scale && scale !== 1) { + size.x /= scale; + size.y /= scale; + size.width /= scale; + size.height /= scale; + } + } return size; }, @@ -289,10 +360,10 @@ const Handling3DDrawings = { const main = this.selectDom().node(); let chld = main?.firstChild; - if (chld && !chld.$jsroot) + while (chld && !chld.$jsroot) chld = chld.nextSibling; - if (chld?.$jsroot) { + if (chld?.$jsroot === '3d') { delete chld.painter; main.removeChild(chld); } @@ -304,11 +375,13 @@ const Handling3DDrawings = { d3_select(this.getCanvSvg().node().nextSibling).remove(); // remove html5 canvas this.getCanvSvg().style('display', null); // show SVG canvas } else { - if (this.getPadSvg().empty()) return; + const pp = this.getPadPainter(); + if (!pp || pp.getPadSvg().empty()) + return; this.apply3dSize(size).remove(); - this.getFrameSvg().style('display', null); // clear display property + pp.getFrameSvg().style('display', null); // clear display property } return can3d; }, @@ -316,16 +389,17 @@ const Handling3DDrawings = { /** @summary Add 3D canvas * @private */ add3dCanvas(size, canv, webgl) { - if (!canv || (size.can3d < -1)) return; + if (!canv || (size.can3d < -1)) + return; if (size.can3d === -1) { // case when 3D object drawn without canvas const main = this.selectDom().node(); - if (main !== null) { + if (main) { main.appendChild(canv); canv.painter = this; - canv.$jsroot = true; // mark canvas as added by jsroot + canv.$jsroot = '3d'; // mark canvas as added by jsroot } return; @@ -341,10 +415,12 @@ const Handling3DDrawings = { this.getCanvSvg().node().parentNode.appendChild(canv); // add directly } else { - if (this.getPadSvg().empty()) return; + const pp = this.getPadPainter(); + if (!pp || pp.getPadSvg().empty()) + return; // first hide normal frame - this.getFrameSvg().style('display', 'none'); + pp.getFrameSvg().style('display', 'none'); const elem = this.apply3dSize(size); elem.attr('title', '').node().appendChild(canv); @@ -360,11 +436,13 @@ const Handling3DDrawings = { let elem; if (size.can3d > 1) { - elem = this.getLayerSvg(size.clname); + const pp = this.getPadPainter(); + + elem = pp.getLayerSvg(size.clname); if (onlyget) return elem; - const svg = this.getPadSvg(); + const svg = pp.getPadSvg(); if (size.can3d === constants.Embed3D.EmbedSVG) { // this is SVG mode or image mode - just create group to hold element @@ -406,10 +484,14 @@ const Handling3DDrawings = { const pos0 = prnt.getBoundingClientRect(), doc = getDocument(); while (prnt) { - if (prnt === doc) { prnt = null; break; } + if (prnt === doc) { + prnt = null; + break; + } try { - if (getComputedStyle(prnt).position !== 'static') break; - } catch (err) { + if (getComputedStyle(prnt).position !== 'static') + break; + } catch { break; } prnt = prnt.parentNode; @@ -447,11 +529,14 @@ async function createRender3D(width, height, render3d, args) { render3d = getRender3DKind(render3d); - if (!args) args = { antialias: true, alpha: true }; + if (!args) + args = { antialias: true, alpha: true }; let promise; - if (render3d === rc.SVG) { + if (render3d === rc.None) + promise = Promise.resolve(null); + else if (render3d === rc.SVG) { // SVG rendering const r = createSVGRenderer(false, 0, doc); r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); @@ -463,30 +548,41 @@ async function createRender3D(width, height, render3d, args) { args.canvas.addEventListener = () => {}; // dummy args.canvas.removeEventListener = () => {}; // dummy args.canvas.style = {}; - return import('gl'); + return internals._node_gl || import('gl'); }).then(node_gl => { - const gl = node_gl.default(width, height, { preserveDrawingBuffer: true }); - if (!gl) throw Error('Fail to create headless-gl'); + internals._node_gl = node_gl; + const gl = node_gl?.default(width, height, { preserveDrawingBuffer: true }); + if (!gl) + throw Error('Fail to create headless-gl'); args.context = gl; gl.canvas = args.canvas; - const r = new WebGLRenderer(args); - r.jsroot_output = new WebGLRenderTarget(width, height); + const r = new THREE.WebGLRenderer(args); + r.jsroot_output = new THREE.WebGLRenderTarget(width, height); r.setRenderTarget(r.jsroot_output); r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); return r; + }).catch(() => { + console.log('gl module is not available - fallback to SVGRenderer'); + render3d = rc.SVG; + const r = createSVGRenderer(false, 0, doc); + r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); + return r; }); } else if (render3d === rc.WebGL) { // interactive WebGL Rendering - promise = Promise.resolve(new WebGLRenderer(args)); + promise = Promise.resolve(new THREE.WebGLRenderer(args)); } else { // rendering with WebGL directly into svg image - const r = new WebGLRenderer(args); + const r = new THREE.WebGLRenderer(args); r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); promise = Promise.resolve(r); } return promise.then(renderer => { + if (!renderer) + return renderer; + if (!renderer.jsroot_dom) renderer.jsroot_dom = renderer.domElement; else @@ -502,13 +598,13 @@ async function createRender3D(width, height, render3d, args) { renderer.originalSetSize = renderer.setSize; // apply size to dom element - renderer.setSize = function(width, height, updateStyle) { + renderer.setSize = function(w, h, updateStyle) { if (this.jsroot_custom_dom) { - this.jsroot_dom.setAttribute('width', width); - this.jsroot_dom.setAttribute('height', height); + this.jsroot_dom.setAttribute('width', w); + this.jsroot_dom.setAttribute('height', h); } - this.originalSetSize(width, height, updateStyle); + this.originalSetSize(w, h, updateStyle); }; renderer.setSize(width, height); @@ -521,13 +617,14 @@ async function createRender3D(width, height, render3d, args) { /** @summary Cleanup created renderer object * @private */ function cleanupRender3D(renderer) { - if (!renderer) return; + if (!renderer) + return; if (isNodeJs()) { const ctxt = isFunc(renderer.getContext) ? renderer.getContext() : null, ext = ctxt?.getExtension('STACKGL_destroy_context'); if (isFunc(ext?.destroy)) - ext.destroy(); + ext.destroy(); } else { // suppress warnings in Chrome about lost webgl context, not required in firefox if (browser.isChrome && isFunc(renderer.forceContextLoss)) @@ -570,7 +667,9 @@ function afterRender3D(renderer) { let indx1 = 0, indx2 = (canvas.height - 1) * 4 * canvas.width, k, d; while (indx1 < indx2) { for (k = 0; k < 4 * canvas.width; ++k) { - d = pixels[indx1 + k]; pixels[indx1 + k] = pixels[indx2 + k]; pixels[indx2 + k] = d; + d = pixels[indx1 + k]; + pixels[indx1 + k] = pixels[indx2 + k]; + pixels[indx2 + k] = d; } indx1 += 4 * canvas.width; indx2 -= 4 * canvas.width; @@ -610,6 +709,7 @@ class TooltipFor3D { this.parent = prnt || getDocument().body; this.canvas = canvas; // we need canvas to recalculate mouse events this.abspos = !prnt; + this.scale = 1; } /** @summary check parent */ @@ -620,10 +720,16 @@ class TooltipFor3D { } } + /** @summary set scaling factor */ + setScale(v) { + this.scale = v; + } + /** @summary extract position from event * @desc can be used to process it later when event is gone */ extract_pos(e) { - if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) return e; + if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) + return e; const res = { u: 0, l: 0 }; if (this.abspos) { res.l = e.pageX; @@ -632,6 +738,8 @@ class TooltipFor3D { res.l = e.offsetX; res.u = e.offsetY; } + res.l /= this.scale; + res.u /= this.scale; return res; } @@ -639,18 +747,19 @@ class TooltipFor3D { * @desc event is delivered from canvas, * but position should be calculated relative to the element where tooltip is placed */ pos(e) { - if (!this.tt) return; + if (!this.tt) + return; const pos = this.extract_pos(e); if (!this.abspos) { const rect1 = this.parent.getBoundingClientRect(), rect2 = this.canvas.getBoundingClientRect(); - if ((rect1.left !== undefined) && (rect2.left!== undefined)) - pos.l += (rect2.left-rect1.left); + if ((rect1.left !== undefined) && (rect2.left !== undefined)) + pos.l += (rect2.left - rect1.left); - if ((rect1.top !== undefined) && (rect2.top!== undefined)) - pos.u += rect2.top-rect1.top; + if ((rect1.top !== undefined) && (rect2.top !== undefined)) + pos.u += rect2.top - rect1.top; if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth) pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3; @@ -663,8 +772,10 @@ class TooltipFor3D { let abs_parent = this.parent; while (abs_parent) { const style = getComputedStyle(abs_parent); - if (!style || (style.position !== 'static')) break; - if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) break; + if (!style || (style.position !== 'static')) + break; + if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) + break; abs_parent = abs_parent.parentNode; } @@ -675,51 +786,48 @@ class TooltipFor3D { } } - this.tt.style.top = `${pos.u+15}px`; - this.tt.style.left = `${pos.l+3}px`; + this.tt.style.top = `${pos.u + 15}px`; + this.tt.style.left = `${pos.l + 3}px`; } /** @summary Show tooltip */ show(v /* , mouse_pos, status_func */) { - if (!v) return this.hide(); + let lines; + if (v && isObject(v) && (v.lines || v.line)) { + if (!v.only_status) + lines = v.line ? [v.line] : v.lines; + } else if (isStr(v)) + lines = [v]; - if (isObject(v) && (v.lines || v.line)) { - if (v.only_status) return this.hide(); + const doc = this.parent.ownerDocument; - if (v.line) - v = v.line; - else { - let res = v.lines[0]; - for (let n = 1; n < v.lines.length; ++n) - res += '
    ' + v.lines[n]; - v = res; - } - } + if (!lines || !doc) + return this.hide(); - if (this.tt === null) { - const doc = getDocument(); + if (!this.tt) { this.tt = doc.createElement('div'); - this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; overflow: hidden; z-index: 101;'); + this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; width: auto; overflow: hidden; z-index: 101;'); this.cont = doc.createElement('div'); - this.cont.setAttribute('style', 'display: block; padding: 2px 12px 3px 7px; margin-left: 5px; font-size: 11px; background: #777; color: #fff;'); + this.cont.setAttribute('style', 'display: block; padding: 5px; margin-left: 5px; font-size: 11px; line-height: 18px; background: #777; color: #fff;'); this.tt.appendChild(this.cont); this.parent.appendChild(this.tt); } - if (this.lastlbl !== v) { - this.cont.innerHTML = v; - this.lastlbl = v; - this.tt.style.width = 'auto'; // let it be automatically resizing... - } + this.cont.innerText = ''; + lines.forEach(lbl => { + const p = doc.createElement('p'); + p.innerText = lbl; + p.setAttribute('style', 'padding: 0px; margin: 1px;'); + this.cont.appendChild(p); + }); } /** @summary Hide tooltip */ hide() { - if (this.tt !== null) + if (this.tt) this.parent.removeChild(this.tt); - this.tt = null; - this.lastlbl = ''; + this.tt = this.cont = null; } } // class TooltipFor3D @@ -733,7 +841,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { let control = null; function control_mousedown(evnt) { - if (!control) return; + if (!control) + return; // function used to hide some events from orbit control and redirect them to zooming rect if (control.mouse_zoom_mesh) { @@ -743,8 +852,10 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } // only left-button is considered - if ((evnt.button!==undefined) && (evnt.button !== 0)) return; - if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return; + if ((evnt.button !== undefined) && (evnt.button !== 0)) + return; + if ((evnt.buttons !== undefined) && (evnt.buttons !== 1)) + return; if (control.enable_zoom) { control.mouse_zoom_mesh = control.detectZoomMesh(evnt); @@ -761,7 +872,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } function control_mouseup(evnt) { - if (!control) return; + if (!control) + return; if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.get3dZoomCoord) { let kind = control.mouse_zoom_mesh.object.zoom, @@ -771,7 +883,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { if (pos1 > pos2) [pos1, pos2] = [pos2, pos1]; - if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) kind = 'y'; + if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) + kind = 'y'; // try to zoom if ((pos1 < pos2) && control.painter.zoom(kind, pos1, pos2)) @@ -782,10 +895,6 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { if (control.enable_zoom) control.removeZoomMesh(); - // only left-button is considered - // if ((evnt.button!==undefined) && (evnt.button !== 0)) return; - // if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return; - if (control.enable_select && control.mouse_select_pnt) { const pnt = control.getMousePos(evnt, {}), same_pnt = (pnt.x === control.mouse_select_pnt.x) && (pnt.y === control.mouse_select_pnt.y); @@ -798,13 +907,16 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } } - function render3DFired(painter) { - if (!painter || painter.renderer === undefined) return false; - return painter.render_tmout !== undefined; // when timeout configured, object is prepared for rendering + function render3DFired(_painter) { + if (_painter?.renderer === undefined) + return false; + // when timeout configured, object is prepared for rendering + return _painter.render_tmout !== undefined; } function control_mousewheel(evnt) { - if (!control) return; + if (!control) + return; // try to handle zoom extra if (render3DFired(control.painter) || control.mouse_zoom_mesh) { @@ -815,7 +927,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } const intersect = control.detectZoomMesh(evnt); - if (!intersect) return; + if (!intersect) + return; evnt.preventDefault(); evnt.stopPropagation(); @@ -828,14 +941,21 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { // z changes from 0..2*size_z3d, others -size_x3d..+size_x3d switch (kind) { - case 'x': position = (position + control.painter.size_x3d)/2/control.painter.size_x3d; break; - case 'y': position = (position + control.painter.size_y3d)/2/control.painter.size_y3d; break; - case 'z': position = position/2/control.painter.size_z3d; break; + case 'x': + position = (position + control.painter.size_x3d) / 2 / control.painter.size_x3d; + break; + case 'y': + position = (position + control.painter.size_y3d) / 2 / control.painter.size_y3d; + break; + case 'z': + position = position / 2 / control.painter.size_z3d; + break; } control.painter.analyzeMouseWheelEvent(evnt, item, position, false); - if ((kind === 'z') && intersect.object.use_y_for_z) kind = 'y'; + if ((kind === 'z') && intersect.object.use_y_for_z) + kind = 'y'; control.painter.zoom(kind, item.min, item.max); } @@ -851,7 +971,7 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { renderer.domElement.addEventListener('pointerup', control_mouseup); } - control = new OrbitControls(camera, renderer.domElement); + control = new THREE.OrbitControls(camera, renderer.domElement); control.enableDamping = false; control.dampingFactor = 1.0; @@ -870,7 +990,7 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { control.camera = camera; control.scene = scene; control.renderer = renderer; - control.raycaster = new Raycaster(); + control.raycaster = new THREE.Raycaster(); control.raycaster.params.Line.threshold = 10; control.raycaster.params.Points.threshold = 5; control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming @@ -909,7 +1029,7 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { delete this.mouse_zoom_mesh; }; - control.HideTooltip = function() { + control.hideTooltip = function() { this.tooltip.hide(); }; @@ -932,9 +1052,10 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { control.getMouseIntersects = function(mouse) { // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer - if (!this.renderer) return []; + if (!this.renderer) + return []; - const sz = (this.renderer instanceof SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new Vector2()), + const sz = (this.renderer instanceof THREE.SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new THREE.Vector2()), pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 }; this.camera.updateMatrix(); @@ -964,21 +1085,25 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { control.getInfoAtMousePosition = function(mouse_pos) { const intersects = this.getMouseIntersects(mouse_pos); - let tip = null, painter = null; + let tip = null, p = null; for (let i = 0; i < intersects.length; ++i) { - if (intersects[i].object.tooltip) { - tip = intersects[i].object.tooltip(intersects[i]); - painter = intersects[i].object.painter; + const obj3d = intersects[i].object; + if (isFunc(obj3d?.tooltip)) { + tip = obj3d.tooltip(intersects[i]); + p = obj3d.tip_painter || obj3d.painter || tip?.$painter; break; } } - if (tip && painter) { - return { obj: painter.getObject(), name: painter.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1+tip.x2)/2, gry: (tip.y1+tip.y2)/2, grz: (tip.z1+tip.z2)/2 }; + if (tip && p) { + return { + obj: p.getObject(), + name: p.getObject().fName, + bin: tip.bin, cont: tip.value, + binx: tip.ix, biny: tip.iy, binz: tip.iz, + grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 + }; } }; @@ -991,16 +1116,17 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } // then check if double-click handler assigned - const fp = this.painter?.getFramePainter(); - if (isFunc(fp?._dblclick_handler)) { + const handler = this.painter?.getFramePainter()?.getDblclickHandler(); + + if (isFunc(handler)) { const info = this.getInfoAtMousePosition(this.getMousePos(evnt, {})); if (info) { - fp._dblclick_handler(info); + handler(info); return; } - } + } - this.reset(); + this.reset(); }; control.changeEvent = function() { @@ -1062,12 +1188,14 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { }; control.mainProcessMouseMove = function(evnt) { - if (!this.painter) return; // protect when cleanup + if (!this.painter) + return; // protect when cleanup if (this.control_active && evnt.buttons && (evnt.buttons & 2)) this.block_ctxt = true; // if right button in control was active, block next context menu - if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) return; + if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) + return; if (this.mouse_zoom_mesh) { // when working with zoom mesh, need special handling @@ -1075,7 +1203,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { const zoom2 = this.detectZoomMesh(evnt), pnt2 = (zoom2?.object === this.mouse_zoom_mesh.object) ? zoom2.point : this.mouse_zoom_mesh.object.globalIntersect(this.raycaster); - if (pnt2) this.mouse_zoom_mesh.point2 = pnt2; + if (pnt2) + this.mouse_zoom_mesh.point2 = pnt2; if (pnt2 && this.painter.enable_highlight) { if (this.mouse_zoom_mesh.object.showSelection(this.mouse_zoom_mesh.point, pnt2)) @@ -1106,21 +1235,27 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { control.delayedProcessMouseMove = function() { // remove handle - allow to trigger new timeout delete this.tmout_handle; - if (!this.painter) return; // protect when cleanup + if (!this.painter) + return; // protect when cleanup const mouse = this.tmout_mouse, intersects = this.getMouseIntersects(mouse), tip = this.processMouseMove(intersects); if (tip) { - let name = '', title = '', coord = '', info = ''; - if (mouse) coord = mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0); + let name = '', title = '', info = ''; + const coord = mouse ? mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0) : ''; if (isStr(tip)) info = tip; else { - name = tip.name; title = tip.title; - if (tip.line) info = tip.line; else - if (tip.lines) { info = tip.lines.slice(1).join(' '); name = tip.lines[0]; } + name = tip.name; + title = tip.title; + if (tip.line) + info = tip.line; + else if (tip.lines) { + info = tip.lines.slice(1).join(' '); + name = tip.lines[0]; + } } this.painter.showObjectStatus(name, title, info, coord); } @@ -1128,7 +1263,6 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { this.cursor_changed = false; if (tip && this.painter?.isTooltipAllowed()) { this.tooltip.checkParent(this.painter.selectDom().node()); - this.tooltip.show(tip, mouse); this.tooltip.pos(this.tmout_ttpos); } else { @@ -1145,7 +1279,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { }; control.mainProcessMouseLeave = function() { - if (!this.painter) return; // protect when cleanup + if (!this.painter) + return; // protect when cleanup // do not enter main event at all if (this.tmout_handle) { @@ -1174,11 +1309,11 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { delete this.single_click_tm; if (kind === 1) { - const fp = this.painter?.getFramePainter(); - if (isFunc(fp?._click_handler)) { + const handler = this.painter?.getFramePainter()?.getClickHandler(); + if (isFunc(handler)) { const info = this.getInfoAtMousePosition(mouse_pos); if (info) { - fp._click_handler(info); + handler(info); return; } } @@ -1189,11 +1324,27 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { const intersects = this.getMouseIntersects(mouse_pos); this.processSingleClick(intersects); } + + if (kind === 3) { + const intersects = this.getMouseIntersects(mouse_pos); + let objpainter = null; + for (let i = 0; !objpainter && (i < intersects.length); ++i) { + const obj3d = intersects[i].object; + objpainter = obj3d.painter || obj3d.parent?.painter; // check one top level + } + if (objpainter) { + // while axis painter not directly appears in the list of primitives, pad and canvas take from frame + const padp = this.painter?.getPadPainter(), + canvp = this.painter?.getCanvPainter(); + canvp?.producePadEvent('select', padp, objpainter); + } + } }; control.lstn_click = function(evnt) { // ignore right-mouse click - if (evnt.detail === 2) return; + if (evnt.detail === 2) + return; if (this.single_click_tm) { clearTimeout(this.single_click_tm); @@ -1201,10 +1352,12 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { } let kind = 0; - if (isFunc(this.painter?.getFramePainter()?._click_handler)) - kind = 1; // user click handler + if (this.painter?.getFramePainter()?.getClickHandler()) + kind = 1; // user click handler else if (this.processSingleClick && this.painter?.options?.mouse_click) kind = 2; // eve7 click handler + else if (this.painter?.getCanvPainter()) + kind = 3; // select event for GED // if normal event, set longer timeout waiting if double click not detected if (kind) @@ -1233,7 +1386,8 @@ function createOrbitControl(painter, camera, scene, renderer, lookat) { * @desc Simplify JS engine to remove it from memory * @private */ function disposeThreejsObject(obj, only_childs) { - if (!obj) return; + if (!obj) + return; if (obj.children) { for (let i = 0; i < obj.children.length; i++) @@ -1266,8 +1420,10 @@ function disposeThreejsObject(obj, only_childs) { delete obj.tooltip; delete obj.stack; // used in geom painter delete obj.drawn_highlight; // special highlight object - - obj = undefined; + // used in lego tooltips + delete obj.face_to_bins_index; + delete obj.tip_painter; + delete obj.handle; } @@ -1275,54 +1431,56 @@ function disposeThreejsObject(obj, only_childs) { * @desc If required, calculates lineDistance attribute for dashed geometries * @private */ function createLineSegments(arr, material, index = undefined, only_geometry = false) { - const geom = new BufferGeometry(); + const geom = new THREE.BufferGeometry(); - geom.setAttribute('position', arr instanceof Float32Array ? new BufferAttribute(arr, 3) : new Float32BufferAttribute(arr, 3)); - if (index) geom.setIndex(new BufferAttribute(index, 1)); + geom.setAttribute('position', arr instanceof Float32Array ? new THREE.BufferAttribute(arr, 3) : new THREE.Float32BufferAttribute(arr, 3)); + if (index) + geom.setIndex(new THREE.BufferAttribute(index, 1)); if (material.isLineDashedMaterial) { - const v1 = new Vector3(), - v2 = new Vector3(); - let d = 0, distances = null; + const v1 = new THREE.Vector3(), + v2 = new THREE.Vector3(); + let d = 0, distances; if (index) { distances = new Float32Array(index.length); for (let n = 0; n < index.length; n += 2) { - const i1 = index[n], i2 = index[n+1]; - v1.set(arr[i1], arr[i1+1], arr[i1+2]); - v2.set(arr[i2], arr[i2+1], arr[i2+2]); + const i1 = index[n], i2 = index[n + 1]; + v1.set(arr[i1], arr[i1 + 1], arr[i1 + 2]); + v2.set(arr[i2], arr[i2 + 1], arr[i2 + 2]); distances[n] = d; d += v2.distanceTo(v1); - distances[n+1] = d; + distances[n + 1] = d; } } else { - distances = new Float32Array(arr.length/3); + distances = new Float32Array(arr.length / 3); for (let n = 0; n < arr.length; n += 6) { - v1.set(arr[n], arr[n+1], arr[n+2]); - v2.set(arr[n+3], arr[n+4], arr[n+5]); - distances[n/3] = d; + v1.set(arr[n], arr[n + 1], arr[n + 2]); + v2.set(arr[n + 3], arr[n + 4], arr[n + 5]); + distances[n / 3] = d; d += v2.distanceTo(v1); - distances[n/3+1] = d; + distances[n / 3 + 1] = d; } } - geom.setAttribute('lineDistance', new BufferAttribute(distances, 1)); + geom.setAttribute('lineDistance', new THREE.BufferAttribute(distances, 1)); } - return only_geometry ? geom : new LineSegments(geom, material); + return only_geometry ? geom : new THREE.LineSegments(geom, material); } /** @summary Help structures for calculating Box mesh * @private */ const Box3D = { - Vertices: [new Vector3(1, 1, 1), new Vector3(1, 1, 0), - new Vector3(1, 0, 1), new Vector3(1, 0, 0), - new Vector3(0, 1, 0), new Vector3(0, 1, 1), - new Vector3(0, 0, 0), new Vector3(0, 0, 1)], - Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, - 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], - Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], - Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices - MeshSegments: undefined + Vertices: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 0), + new THREE.Vector3(1, 0, 1), new THREE.Vector3(1, 0, 0), + new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 1, 1), + new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)], + Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, + 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], + Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], + Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices + Crosses: [0, 7, 2, 5, 0, 3, 1, 2, 7, 3, 2, 6, 5, 6, 4, 7, 5, 1, 0, 4, 3, 4, 1, 6], // addresses Vertices + MeshSegments: undefined }; // these segments address vertices from the mesh, we can use positions from box mesh @@ -1376,22 +1534,22 @@ class PointsControl extends InteractiveControl { /** @summary cleanup object */ cleanup() { - if (!this.mesh) return; + if (!this.mesh) + return; delete this.mesh.is_selected; this.createSpecial(null); delete this.mesh; } /** @summary extract intersect index */ - extractIndex(intersect) { - return intersect && intersect.index!==undefined ? intersect.index : undefined; - } + extractIndex(intersect) { return intersect?.index; } /** @summary set selection */ setSelected(col, indx) { const m = this.mesh; if ((m.select_col === col) && (m.select_indx === indx)) { - col = null; indx = undefined; + col = null; + indx = undefined; } m.select_col = col; m.select_indx = indx; @@ -1423,18 +1581,19 @@ class PointsControl extends InteractiveControl { } if (!m.js_special) { - const geom = new BufferGeometry(); + const geom = new THREE.BufferGeometry(); geom.setAttribute('position', m.geometry.getAttribute('position')); - const material = new PointsMaterial({ size: m.material.size*2, color }); + const material = new THREE.PointsMaterial({ size: m.material.size * 2, color }); material.sizeAttenuation = m.material.sizeAttenuation; - m.js_special = new Points(geom, material); + m.js_special = new THREE.Points(geom, material); m.js_special.jsroot_special = true; // special object, exclude from intersections m.add(m.js_special); } - m.js_special.material.color = new Color(color); - if (index !== undefined) m.js_special.geometry.setDrawRange(index, 1); + m.js_special.material.color = new THREE.Color(color); + if (index !== undefined) + m.js_special.geometry.setDrawRange(index, 1); } } // class PointsControl @@ -1456,17 +1615,17 @@ class PointsCreator { this.webgl = iswebgl; this.scale = scale || 1; - this.pos = new Float32Array(number*3); - this.geom = new BufferGeometry(); - this.geom.setAttribute('position', new BufferAttribute(this.pos, 3)); + this.pos = new Float32Array(number * 3); + this.geom = new THREE.BufferGeometry(); + this.geom.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); this.indx = 0; } /** @summary Add point */ addPoint(x, y, z) { this.pos[this.indx] = x; - this.pos[this.indx+1] = y; - this.pos[this.indx+2] = z; + this.pos[this.indx + 1] = y; + this.pos[this.indx + 2] = z; this.indx += 3; } @@ -1480,20 +1639,24 @@ class PointsCreator { let k = 1; // special dots - if (!args.style) k = 1.1; else - if (args.style === 1) k = 0.3; else - if (args.style === 6) k = 0.5; else - if (args.style === 7) k = 0.7; + if (!args.style) + k = 1.1; + else if (args.style === 1) + k = 0.3; + else if (args.style === 6) + k = 0.5; + else if (args.style === 7) + k = 0.7; const makePoints = texture => { - const material_args = { size: 3*this.scale*k }; + const material_args = { size: 3 * this.scale * k }; if (texture) { material_args.map = texture; material_args.transparent = true; } else material_args.color = args.color || 'black'; - const pnts = new Points(this.geom, new PointsMaterial(material_args)); + const pnts = new THREE.Points(this.geom, new THREE.PointsMaterial(material_args)); pnts.nvertex = 1; return pnts; }; @@ -1507,27 +1670,26 @@ class PointsCreator { const handler = new TAttMarkerHandler({ style: args.style, color: args.color, size: 7 }), w = handler.fill ? 1 : 7, - imgdata = '' + - ``+ + imgdata = `` + + `` + '', - dataUrl = 'data:image/svg+xml;charset=utf8,' + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); + dataUrl = prSVG + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); let promise; if (isNodeJs()) { promise = import('canvas').then(handle => handle.default.loadImage(dataUrl).then(img => { - const canvas = handle.default.createCanvas(64, 64), - ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, 64, 64); - return new CanvasTexture(canvas); - })); + const canvas = handle.default.createCanvas(64, 64), + ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, 64, 64); + return new THREE.CanvasTexture(canvas); + })); } else if (this.noPromise) { // only for v6 support - return makePoints(new TextureLoader().load(dataUrl)); + return makePoints(new THREE.TextureLoader().load(dataUrl)); } else { promise = new Promise((resolveFunc, rejectFunc) => { - const loader = new TextureLoader(); - // eslint-disable-next-line prefer-promise-reject-errors - loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc()); + const loader = new THREE.TextureLoader(); + loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc(Error(`Fail to load ${dataUrl}`))); }); } @@ -1541,13 +1703,14 @@ class PointsCreator { * @desc Takes into account dashed properties * @private */ function create3DLineMaterial(painter, arg, is_v7 = false) { - if (!painter || !arg) return null; + if (!painter || !arg) + return null; let color, lstyle, lwidth; if (isStr(arg) || is_v7) { - color = painter.v7EvalColor(arg+'color', 'black'); - lstyle = parseInt(painter.v7EvalAttr(arg+'style', 0)); - lwidth = parseInt(painter.v7EvalAttr(arg+'width', 1)); + color = painter.v7EvalColor(arg + 'color', 'black'); + lstyle = parseInt(painter.v7EvalAttr(arg + 'style', 0)); + lwidth = parseInt(painter.v7EvalAttr(arg + 'width', 1)); } else { color = painter.getColor(arg.fLineColor); lstyle = arg.fLineStyle; @@ -1557,15 +1720,29 @@ function create3DLineMaterial(painter, arg, is_v7 = false) { const style = lstyle ? getSvgLineStyle(lstyle) : '', dash = style ? style.split(',') : [], material = (dash && dash.length >= 2) - ? new LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) - : new LineBasicMaterial({ color }); + ? new THREE.LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) + : new THREE.LineBasicMaterial({ color }); - if (lwidth && (lwidth > 1)) material.linewidth = lwidth; + if (lwidth && (lwidth > 1)) + material.linewidth = lwidth; return material; } -export { assign3DHandler, disposeThreejsObject, createOrbitControl, +/** @summary Create plain text geometry + * @private */ +function createTextGeometry(lbl, size) { + const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }; + if (THREE.REVISION > 162) + geom_args.depth = 0; + else + geom_args.height = 0; + + return new THREE.TextGeometry(lbl, geom_args); +} + + +export { THREE, importThreeJs, assign3DHandler, disposeThreejsObject, createOrbitControl, createLineSegments, create3DLineMaterial, Box3D, getMaterialArgs, createRender3D, beforeRender3D, afterRender3D, getRender3DKind, cleanupRender3D, - HelveticerRegularFont, InteractiveControl, PointsControl, PointsCreator, createSVGRenderer }; + getHelveticaFont, createTextGeometry, InteractiveControl, PointsControl, PointsCreator, createSVGRenderer }; diff --git a/modules/base/colors.mjs b/modules/base/colors.mjs index 338c7ea18..f8c286c5b 100644 --- a/modules/base/colors.mjs +++ b/modules/base/colors.mjs @@ -1,14 +1,36 @@ -import { clTColor, settings } from '../core.mjs'; +import { clTColor, isBatchMode, isNodeJs, settings } from '../core.mjs'; import { color as d3_color } from '../d3.mjs'; const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', kWhite = 0, kBlack = 1, kRed = 2, kGreen = 3, kBlue = 4, kYellow = 5, kMagenta = 6, kCyan = 7; -/** @summary Covert value between 0 and 1 into hex, used for colors coding +/** @summary Covert value between 0 and 1 into decimal string using scale factor, used for colors coding * @private */ -function toHex(num, scale = 255) { - const s = Math.round(num * scale).toString(16); - return s.length === 1 ? '0'+s : s; +function toDec(num, scale = 255) { + return Math.round(num * scale).toString(10); +} + +/** @summary Convert alfa value from rgba to string + * @private */ +function toAlfa(a) { + const res = a.toFixed(2); + if ((res.length === 4) && (res[3] === '0')) + return res.slice(0, 3); + return res; +} + +/** @summary Convert r,g,b,a values to string + * @private */ +function toColor(r, g, b, a = 1) { + return (a !== undefined) && (a !== 1) + ? `rgba(${toDec(r)}, ${toDec(g)}, ${toDec(b)}, ${toAlfa(a)})` + : `rgb(${toDec(r)}, ${toDec(g)}, ${toDec(b)})`; +} + +/** @summary Convert color string to unify node.js and browser + * @private */ +function convertColor(col) { + return (isNodeJs() || (isBatchMode() && settings.ApproxTextSize)) && (col[0] === '#' || col[0] === 'r') ? d3_color(col).formatRgb() : col; } /** @summary list of global root colors @@ -18,11 +40,18 @@ let gbl_colors_list = []; /** @summary Generates all root colors, used also in jstests to reset colors * @private */ function createRootColors() { - const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', '#59d454', '#5954d9', 'white']; + function conv(arg) { + const r = Number.parseInt(arg.slice(0, 2), 16), + g = Number.parseInt(arg.slice(2, 4), 16), + b = Number.parseInt(arg.slice(4, 6), 16); + return `rgb(${r}, ${g}, ${b})`; + } + + const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', conv('59d454'), conv('5954d9'), 'white']; colorMap[110] = 'white'; const moreCol = [ - { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000' }, + { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff18006f2da8a52a2ab2beb55790fcf89c20e42536964a8b9c9ca17a21dd1845fbff5e02c91f16c849a9adad7d86c8dd578dff6563643f90daffa90ebd1f0194a4a2832db6a96b59e76300b9ac7071758192daddb2b2b2' }, { n: 201, s: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' }, { n: 390, s: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300' }, { n: 406, s: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300' }, @@ -37,7 +66,7 @@ function createRootColors() { const s = entry.s; for (let n = 0; n < s.length; n += 6) { const num = entry.n + n / 6; - colorMap[num] = '#' + s.slice(n, n+6); + colorMap[num] = conv(s.slice(n, n + 6)); } }); @@ -53,38 +82,39 @@ function getRootColors() { /** @summary Produces rgb code for TColor object * @private */ function getRGBfromTColor(col) { - if (col?._typename !== clTColor) return null; + if (col?._typename !== clTColor) + return null; - let rgb = '#' + toHex(col.fRed) + toHex(col.fGreen) + toHex(col.fBlue); - if ((col.fAlpha !== undefined) && (col.fAlpha !== 1)) - rgb += toHex(col.fAlpha); + const rgb = toColor(col.fRed, col.fGreen, col.fBlue, col.fAlpha); switch (rgb) { - case '#ffffff': return 'white'; - case '#000000': return 'black'; - case '#ff0000': return 'red'; - case '#00ff00': return 'green'; - case '#0000ff': return 'blue'; - case '#ffff00': return 'yellow'; - case '#ff00ff': return 'magenta'; - case '#00ffff': return 'cyan'; + case 'rgb(255, 255, 255)': return 'white'; + case 'rgb(0, 0, 0)': return 'black'; + case 'rgb(255, 0, 0)': return 'red'; + case 'rgb(0, 255, 0)': return 'green'; + case 'rgb(0, 0, 255)': return 'blue'; + case 'rgb(255, 255, 0)': return 'yellow'; + case 'rgb(255, 0, 255)': return 'magenta'; + case 'rgb(0, 255, 255)': return 'cyan'; } return rgb; } -/** @ummary Return list of grey colors for the original array +/** @summary Return list of grey colors for the original array * @private */ function getGrayColors(rgb_array) { const gray_colors = []; - if (!rgb_array) rgb_array = getRootColors(); + if (!rgb_array) + rgb_array = getRootColors(); for (let n = 0; n < rgb_array.length; ++n) { - if (!rgb_array[n]) continue; + if (!rgb_array[n]) + continue; const rgb = d3_color(rgb_array[n]), - gray = 0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b; + gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; rgb.r = rgb.g = rgb.b = gray; - gray_colors[n] = rgb.hex(); + gray_colors[n] = rgb.formatRgb(); } return gray_colors; @@ -99,7 +129,8 @@ function extendRootColors(jsarr, objarr, grayscale) { jsarr[n] = gbl_colors_list[n]; } - if (!objarr) return jsarr; + if (!objarr) + return jsarr; let rgb_array = objarr; if (objarr._typename && objarr.arr) { @@ -128,7 +159,7 @@ function extendRootColors(jsarr, objarr, grayscale) { return grayscale ? getGrayColors(jsarr) : jsarr; } -/** @ummary Set global list of colors. +/** @summary Set global list of colors. * @desc Either TObjArray of TColor instances or just plain array with rgb() code. * List of colors typically stored together with TCanvas primitives * @private */ @@ -148,7 +179,8 @@ function getColor(indx) { * @return Color index or -1 if fails * @private */ function findColor(name) { - if (!name) return -1; + if (!name) + return -1; for (let indx = 0; indx < gbl_colors_list.length; ++indx) { if (gbl_colors_list[indx] === name) return indx; @@ -159,14 +191,27 @@ function findColor(name) { /** @summary Add new color * @param {string} rgb - color name or just string with rgb value * @param {array} [lst] - optional colors list, to which add colors + * @param {array} [lst] - optional colors list, to which add colors * @return {number} index of new color * @private */ -function addColor(rgb, lst) { - if (!lst) lst = gbl_colors_list; - const indx = lst.indexOf(rgb); - if (indx >= 0) return indx; +function addColor(rgb, lst, indx) { + if (!lst) + lst = gbl_colors_list; + + if ((rgb[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) + rgb = d3_color(rgb).formatRgb(); + + if (indx !== undefined) { + if (Number.isInteger(indx) && (indx > 0)) + lst[indx] = rgb; + return indx; + } + + indx = lst.indexOf(rgb); + if (indx >= 0) + return indx; lst.push(rgb); - return lst.length-1; + return lst.length - 1; } /** @@ -186,7 +231,7 @@ class ColorPalette { calcColorIndex(i, len) { const plen = this.palette.length, theColor = Math.floor((i + 0.99) * plen / (len - 1)); return (theColor > plen - 1) ? plen - 1 : theColor; - } + } /** @summary Returns color with provided index */ getColor(indx) { return this.palette[indx]; } @@ -201,19 +246,24 @@ class ColorPalette { function createDefaultPalette(grayscale) { const hue2rgb = (p, q, t) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2/3 - t) * 6; + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t < 1 / 6) + return p + (q - p) * 6 * t; + if (t < 1 / 2) + return q; + if (t < 2 / 3) + return p + (q - p) * (2 / 3 - t) * 6; return p; }, HLStoRGB = (h, l, s) => { const q = (l < 0.5) ? l * (1 + s) : l + s - l * s, p = 2 * l - q, - r = hue2rgb(p, q, h + 1/3), + r = hue2rgb(p, q, h + 1 / 3), g = hue2rgb(p, q, h), - b = hue2rgb(p, q, h - 1/3); - return '#' + toHex(r) + toHex(g) + toHex(b); + b = hue2rgb(p, q, h - 1 / 3); + return toColor(r, g, b); }, minHue = 0, maxHue = 280, maxPretty = 50, palette = []; for (let i = 0; i < maxPretty; ++i) { const hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360; @@ -225,24 +275,25 @@ function createDefaultPalette(grayscale) { function createGrayPalette() { const palette = []; for (let i = 0; i < 50; ++i) { - const code = toHex((i+2)/60); - palette.push('#'+code+code+code); + const code = toDec((i + 2) / 60); + palette.push(`rgb(${code}, ${code}, ${code})`); } return new ColorPalette(palette); } -/* eslint-disable comma-spacing */ - - /** @summary Create color palette * @private */ function getColorPalette(id, grayscale) { id = id || settings.Palette; - if ((id > 0) && (id < 10)) return createGrayPalette(); - if (id < 51) return createDefaultPalette(grayscale); - if (id > 113) id = 57; - const stops = [0,0.125,0.25,0.375,0.5,0.625,0.75,0.875,1]; + if ((id > 0) && (id < 10)) + return createGrayPalette(); + if (id < 51) + return createDefaultPalette(grayscale); + if (id > 113) + id = 57; + const stops = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; let rgb; + /* eslint-disable @stylistic/js/comma-spacing */ switch (id) { // Deep Sea case 51: rgb = [[0,9,13,17,24,32,27,25,29],[0,0,0,2,37,74,113,160,221],[28,42,59,78,98,129,154,184,221]]; break; @@ -257,7 +308,11 @@ function getColorPalette(id, grayscale) { // Inverted Dark Body Radiator case 56: rgb = [[242,234,237,230,212,156,99,45,0],[243,238,238,168,101,45,0,0,0],[230,95,11,8,9,3,1,1,0]]; break; // Bird (default, keep float for backward compatibility) - case 57: rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982],[42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716],[134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; break; + case 57: + rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982], + [42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716], + [134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; + break; // Cubehelix case 58: rgb = [[0,24,2,54,176,236,202,194,255],[0,29,92,129,117,120,176,236,255],[0,68,80,34,57,172,252,245,255]]; break; // Green Red Violet @@ -372,58 +427,70 @@ function getColorPalette(id, grayscale) { case 113: rgb = [[0,5,65,97,124,156,189,224,255],[32,54,77,100,123,148,175,203,234],[77,110,107,111,120,119,111,94,70]]; break; default: return createDefaultPalette(); } + /* eslint-enable @stylistic/js/comma-spacing */ const NColors = 255, Red = rgb[0], Green = rgb[1], Blue = rgb[2], palette = []; for (let g = 1; g < stops.length; g++) { - // create the colors... - const nColorsGradient = Math.round(Math.floor(NColors*stops[g]) - Math.floor(NColors*stops[g-1])); - for (let c = 0; c < nColorsGradient; c++) { - const col = '#' + toHex(Red[g-1] + c * (Red[g] - Red[g-1]) / nColorsGradient, 1) + - toHex(Green[g-1] + c * (Green[g] - Green[g-1]) / nColorsGradient, 1) + - toHex(Blue[g-1] + c * (Blue[g] - Blue[g-1]) / nColorsGradient, 1); - palette.push(col); - } - } - - return new ColorPalette(palette, grayscale); + // create the colors... + const nColorsGradient = Math.round(Math.floor(NColors * stops[g]) - Math.floor(NColors * stops[g - 1])); + for (let c = 0; c < nColorsGradient; c++) { + const col = 'rgb(' + toDec(Red[g - 1] + c * (Red[g] - Red[g - 1]) / nColorsGradient, 1) + ', ' + + toDec(Green[g - 1] + c * (Green[g] - Green[g - 1]) / nColorsGradient, 1) + ', ' + + toDec(Blue[g - 1] + c * (Blue[g] - Blue[g - 1]) / nColorsGradient, 1) + ')'; + palette.push(col); + } + } + + return new ColorPalette(palette, grayscale); } /** @summary Decode list of ROOT colors coded by TWebCanvas * @private */ function decodeWebCanvasColors(oper) { - const colors = [], arr = oper.split(';'); + const colors = [], arr = oper.split(';'), + convert_rgb = isNodeJs() || (isBatchMode() && settings.ApproxTextSize); for (let n = 0; n < arr.length; ++n) { const name = arr[n]; let p = name.indexOf(':'); if (p > 0) { - colors[parseInt(name.slice(0, p))] = d3_color(`rgb(${name.slice(p+1)})`).formatHex(); + const col = `rgb(${name.slice(p + 1)})`; + colors[parseInt(name.slice(0, p))] = convert_rgb ? d3_color(col).formatRgb() : col; continue; } p = name.indexOf('='); if (p > 0) { - colors[parseInt(name.slice(0, p))] = d3_color(`rgba(${name.slice(p+1)})`).formatHex8(); + let col = `rgba(${name.slice(p + 1)})`; + if (convert_rgb) { + col = d3_color(col); + col.opacity = (Math.round(col.opacity * 255) / 255).toFixed(2); + col = col.formatRgb(); + } + colors[parseInt(name.slice(0, p))] = col; continue; } p = name.indexOf('#'); - if (p < 0) continue; + if (p < 0) + continue; const colindx = parseInt(name.slice(0, p)), - data = JSON.parse(name.slice(p+1)), + data = JSON.parse(name.slice(p + 1)), grad = { _typename: data[0] === 10 ? clTLinearGradient : clTRadialGradient, fNumber: colindx, fType: data[0] }; let cnt = 1; grad.fCoordinateMode = Math.round(data[cnt++]); const nsteps = Math.round(data[cnt++]); - grad.fColorPositions = data.slice(cnt, cnt + nsteps); cnt += nsteps; - grad.fColors = data.slice(cnt, cnt + 4*nsteps); cnt += 4*nsteps; + grad.fColorPositions = data.slice(cnt, cnt + nsteps); + cnt += nsteps; + grad.fColors = data.slice(cnt, cnt + 4 * nsteps); + cnt += 4 * nsteps; grad.fStart = { fX: data[cnt++], fY: data[cnt++] }; grad.fEnd = { fX: data[cnt++], fY: data[cnt++] }; if (grad._typename === clTRadialGradient && cnt < data.length) { grad.fR1 = data[cnt++]; - grad.fR2 = data[cnt++]; + grad.fR2 = data[cnt]; } colors[colindx] = grad; @@ -435,8 +502,8 @@ function decodeWebCanvasColors(oper) { createRootColors(); -export { getColor, findColor, addColor, adoptRootColors, +export { getColor, findColor, addColor, adoptRootColors, convertColor, getRootColors, getGrayColors, - extendRootColors, getRGBfromTColor, createRootColors, toHex, + extendRootColors, getRGBfromTColor, createRootColors, toColor, kWhite, kBlack, kRed, kGreen, kBlue, kYellow, kMagenta, kCyan, ColorPalette, getColorPalette, clTLinearGradient, clTRadialGradient, decodeWebCanvasColors }; diff --git a/modules/base/func.mjs b/modules/base/func.mjs index 78edd981b..1437d102b 100644 --- a/modules/base/func.mjs +++ b/modules/base/func.mjs @@ -8,22 +8,24 @@ function proivdeEvalPar(obj, check_save) { obj.$math = jsroot_math; let _func = obj.fTitle, isformula = false, pprefix = '['; - if (_func === 'gaus') _func = 'gaus(0)'; + if (_func === 'gaus') + _func = 'gaus(0)'; if (isStr(obj.fFormula?.fFormula)) { - if (obj.fFormula.fFormula.indexOf('[](double*x,double*p)') === 0) { - isformula = true; pprefix = 'p['; - _func = obj.fFormula.fFormula.slice(21); - } else { - _func = obj.fFormula.fFormula; - pprefix = '[p'; - } - - if (obj.fFormula.fClingParameters && obj.fFormula.fParams) { - obj.fFormula.fParams.forEach(pair => { - const regex = new RegExp(`(\\[${pair.first}\\])`, 'g'), - parvalue = obj.fFormula.fClingParameters[pair.second]; - _func = _func.replace(regex, (parvalue < 0) ? `(${parvalue})` : parvalue); - }); + if (obj.fFormula.fFormula.indexOf('[](double*x,double*p)') === 0) { + isformula = true; + pprefix = 'p['; + _func = obj.fFormula.fFormula.slice(21); + } else { + _func = obj.fFormula.fFormula; + pprefix = '[p'; + } + + if (obj.fFormula.fClingParameters && obj.fFormula.fParams) { + obj.fFormula.fParams.forEach(pair => { + const regex = new RegExp(`(\\[${pair.first}\\])`, 'g'), + parvalue = obj.fFormula.fClingParameters[pair.second]; + _func = _func.replace(regex, (parvalue < 0) ? `(${parvalue})` : parvalue); + }); } } @@ -65,11 +67,11 @@ function proivdeEvalPar(obj, check_save) { if (_func.match(/^pol[0-9]$/) && (parseInt(_func[3]) === obj.fNpar - 1)) { _func = '[0]'; for (let k = 1; k < obj.fNpar; ++k) - _func += ` + [${k}] * `+ ((k === 1) ? 'x' : `Math.pow(x,${k})`); + _func += ` + [${k}] * ` + ((k === 1) ? 'x' : `Math.pow(x,${k})`); } if (_func.match(/^chebyshev[0-9]$/) && (parseInt(_func[9]) === obj.fNpar - 1)) { - _func = `this.$math.ChebyshevN(${obj.fNpar-1}, x, `; + _func = `this.$math.ChebyshevN(${obj.fNpar - 1}, x, `; for (let k = 0; k < obj.fNpar; ++k) _func += (k === 0 ? '[' : ', ') + `[${k}]`; _func += '])'; @@ -109,38 +111,43 @@ function proivdeEvalPar(obj, check_save) { function _getTF1Save(func, x) { const np = func.fSave.length - 3, xmin = func.fSave[np + 1], - xmax = func.fSave[np + 2], - dx = (xmax - xmin) / np; - if (x < xmin) - return func.fSave[0]; - if (x > xmax) - return func.fSave[np]; - - const bin = Math.min(np - 1, Math.floor((x - xmin) / dx)); - let xlow = xmin + bin * dx, - xup = xlow + dx, - ylow = func.fSave[bin], - yup = func.fSave[bin + 1]; - - if (!Number.isFinite(ylow) && (bin < np - 1)) { - xlow += dx; xup += dx; - ylow = yup; yup = func.fSave[bin + 2]; - } else if (!Number.isFinite(yup) && (bin > 0)) { - xup -= dx; xlow -= dx; - yup = ylow; ylow = func.fSave[bin - 1]; - } - - return ((xup * ylow - xlow * yup) + x * (yup - ylow)) / dx; + xmax = func.fSave[np + 2], + dx = (xmax - xmin) / np; + if (x < xmin) + return func.fSave[0]; + if (x > xmax) + return func.fSave[np]; + + const bin = Math.min(np - 1, Math.floor((x - xmin) / dx)); + let xlow = xmin + bin * dx, + xup = xlow + dx, + ylow = func.fSave[bin], + yup = func.fSave[bin + 1]; + + if (!Number.isFinite(ylow) && (bin < np - 1)) { + xlow += dx; + xup += dx; + ylow = yup; + yup = func.fSave[bin + 2]; + } else if (!Number.isFinite(yup) && (bin > 0)) { + xup -= dx; + xlow -= dx; + yup = ylow; + ylow = func.fSave[bin - 1]; + } + + return ((xup * ylow - xlow * yup) + x * (yup - ylow)) / dx; } /** @summary Provide TF1 value * @desc First try evaluate, if not possible - check saved buffer * @private */ function getTF1Value(func, x, skip_eval = undefined) { - let y = 0, iserr = false; if (!func) return 0; + let iserr = false; + if (!skip_eval && !func.evalPar) { try { if (!proivdeEvalPar(func)) @@ -152,16 +159,15 @@ function getTF1Value(func, x, skip_eval = undefined) { if (func.evalPar && !iserr) { try { - y = func.evalPar(x); - return y; + return func.evalPar(x); } catch { - y = 0; + /* eslint-disable-next-line no-useless-assignment */ + iserr = true; } } const np = func.fSave.length - 3; - if ((np < 2) || (func.fSave[np + 1] === func.fSave[np + 2])) return 0; - return _getTF1Save(func, x); + return (np < 2) || (func.fSave[np + 1] === func.fSave[np + 2]) ? 0 : _getTF1Save(func, x); } export { proivdeEvalPar, getTF1Value, _getTF1Save }; diff --git a/modules/base/jspdf.mjs b/modules/base/jspdf.mjs new file mode 100644 index 000000000..616b9c67d --- /dev/null +++ b/modules/base/jspdf.mjs @@ -0,0 +1,32088 @@ +/** @license + * + * jsPDF - PDF Document creation from JavaScript + * Version 3.0.3 + * + * Copyright (c) 2010-2025 James Hall , https://github.com/MrRio/jsPDF + * 2015-2025 yWorks GmbH, http://www.yworks.com + * 2015-2025 Lukas Holländer , https://github.com/HackbrettXXX + * 2016-2018 Aras Abbasi + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Steven Spungin, https://github.com/Flamenco + * 2014 Kenneth Glassey, https://github.com/Gavvers + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, Flamenco + */ + +const globalObject = globalThis; + +/** + * A class to parse color values + * @author Stoyan Stefanov + * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} + * @license Use it if you like it + */ + +function RGBColor(color_string) { + color_string = color_string || ""; + this.ok = false; + + // strip any leading # + if (color_string.charAt(0) == "#") { + // remove # if any + color_string = color_string.substr(1, 6); + } + + color_string = color_string.replace(/ /g, ""); + color_string = color_string.toLowerCase(); + + var channels; + + // before getting into regexps, try simple matches + // and overwrite the input + var simple_colors = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "00ffff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000000", + blanchedalmond: "ffebcd", + blue: "0000ff", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "00ffff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dodgerblue: "1e90ff", + feldspar: "d19275", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "ff00ff", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgrey: "d3d3d3", + lightgreen: "90ee90", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslateblue: "8470ff", + lightslategray: "778899", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "00ff00", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "ff00ff", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370d8", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "d87093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + red: "ff0000", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + violetred: "d02090", + wheat: "f5deb3", + white: "ffffff", + whitesmoke: "f5f5f5", + yellow: "ffff00", + yellowgreen: "9acd32" + }; + color_string = simple_colors[color_string] || color_string; + + // array of color definition objects + var color_defs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ["rgb(123, 234, 45)", "rgb(255,234,245)"], + process: function(bits) { + return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ["#00ff00", "336699"], + process: function(bits) { + return [ + parseInt(bits[1], 16), + parseInt(bits[2], 16), + parseInt(bits[3], 16) + ]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ["#fb0", "f0f"], + process: function(bits) { + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; + + // search through the definitions to find a match + for (var i = 0; i < color_defs.length; i++) { + var re = color_defs[i].re; + var processor = color_defs[i].process; + var bits = re.exec(color_string); + if (bits) { + channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + } + + // validate/cleanup values + this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r; + this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g; + this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; + + // some getters + this.toRGB = function() { + return "rgb(" + this.r + ", " + this.g + ", " + this.b + ")"; + }; + this.toHex = function() { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) r = "0" + r; + if (g.length == 1) g = "0" + g; + if (b.length == 1) b = "0" + b; + return "#" + r + g + b; + }; +} + +let atob, btoa; + +if ((typeof process === 'object') && (typeof process.versions === 'object') && process.versions.node && process.versions.v8) { + atob = str => Buffer.from(str, 'base64').toString('latin1'); + btoa = str => Buffer.from(str, 'latin1').toString('base64'); +} else { + atob = globalThis.atob; + btoa = globalThis.btoa; +} + +function consoleLog() { + if (globalObject.console && typeof globalObject.console.log === "function") { + globalObject.console.log.apply(globalObject.console, arguments); + } +} + +function consoleWarn(str) { + if (globalObject.console) { + if (typeof globalObject.console.warn === "function") { + globalObject.console.warn.apply(globalObject.console, arguments); + } else { + consoleLog.call(null, arguments); + } + } +} + +function consoleError(str) { + if (globalObject.console) { + if (typeof globalObject.console.error === "function") { + globalObject.console.error.apply(globalObject.console, arguments); + } else { + consoleLog(str); + } + } +} +var console = { + log: consoleLog, + warn: consoleWarn, + error: consoleError +}; + +/** + * @license + * Joseph Myers does not specify a particular license for his work. + * + * Author: Joseph Myers + * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js + * + * Modified by: Owen Leong + */ + +function md5cycle(x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); +} + +function cmn(q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); +} + +function ff(a, b, c, d, x, s, t) { + return cmn((b & c) | (~b & d), a, b, x, s, t); +} + +function gg(a, b, c, d, x, s, t) { + return cmn((b & d) | (c & ~d), a, b, x, s, t); +} + +function hh(a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); +} + +function ii(a, b, c, d, x, s, t) { + return cmn(c ^ (b | ~d), a, b, x, s, t); +} + +function md51(s) { + // txt = ''; + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i; + for (i = 64; i <= s.length; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < s.length; i++) + tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + tail[i >> 2] |= 0x80 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i++) tail[i] = 0; + } + tail[14] = n * 8; + md5cycle(state, tail); + return state; +} + +/* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ +function md5blk(s) { + /* I figured global was faster. */ + var md5blks = [], + i; /* Andy King said do it this way. */ + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = + s.charCodeAt(i) + + (s.charCodeAt(i + 1) << 8) + + (s.charCodeAt(i + 2) << 16) + + (s.charCodeAt(i + 3) << 24); + } + return md5blks; +} + +var hex_chr = "0123456789abcdef".split(""); + +function rhex(n) { + var s = "", + j = 0; + for (; j < 4; j++) + s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f]; + return s; +} + +function hex(x) { + for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); + return x.join(""); +} + +// Converts a 4-byte number to byte string +function singleToByteString(n) { + return String.fromCharCode( + (n & 0xff) >> 0, + (n & 0xff00) >> 8, + (n & 0xff0000) >> 16, + (n & 0xff000000) >> 24 + ); +} + +// Converts an array of numbers to a byte string +function toByteString(x) { + return x.map(singleToByteString).join(""); +} + +// Returns the MD5 hash as a byte string +function md5Bin(s) { + return toByteString(md51(s)); +} + +// Returns MD5 hash as a hex string +function md5(s) { + return hex(md51(s)); +} + +var md5Check = md5("hello") != "5d41402abc4b2a76b9719d911017c592"; + +function add32(a, b) { + if (md5Check) { + /* if the md5Check does not match + the expected value, we're dealing + with an old browser and need + this function. */ + var lsw = (a & 0xffff) + (b & 0xffff), + msw = (a >> 16) + (b >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); + } else { + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + return (a + b) & 0xffffffff; + } +} + +/** + * @license + * FPDF is released under a permissive license: there is no usage restriction. + * You may embed it freely in your application (commercial or not), with or + * without modifications. + * + * Reference: http://www.fpdf.org/en/script/script37.php + */ + +function repeat(str, num) { + return new Array(num + 1).join(str); +} + +/** + * Converts a byte string to a hex string + * + * @name rc4 + * @function + * @param {string} key Byte string of encryption key + * @param {string} data Byte string of data to be encrypted + * @returns {string} Encrypted string + */ +function rc4(key, data) { + var lastKey, lastState; + if (key !== lastKey) { + var k = repeat(key, ((256 / key.length) >> 0) + 1); + var state = []; + for (var i = 0; i < 256; i++) { + state[i] = i; + } + var j = 0; + for (var i = 0; i < 256; i++) { + var t = state[i]; + j = (j + t + k.charCodeAt(i)) % 256; + state[i] = state[j]; + state[j] = t; + } + lastKey = key; + lastState = state; + } else { + state = lastState; + } + var length = data.length; + var a = 0; + var b = 0; + var out = ""; + for (var i = 0; i < length; i++) { + a = (a + 1) % 256; + t = state[a]; + b = (b + t) % 256; + state[a] = state[b]; + state[b] = t; + k = state[(state[a] + state[b]) % 256]; + out += String.fromCharCode(data.charCodeAt(i) ^ k); + } + return out; +} + +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + * Author: Owen Leong (@owenl131) + * Date: 15 Oct 2020 + * References: + * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt + * https://github.com/foliojs/pdfkit/blob/master/lib/security.js + * http://www.fpdf.org/en/script/script37.php + */ + +var permissionOptions = { + print: 4, + modify: 8, + copy: 16, + "annot-forms": 32 +}; + +/** + * Initializes encryption settings + * + * @name constructor + * @function + * @param {Array} permissions Permissions allowed for user, "print", "modify", "copy" and "annot-forms". + * @param {String} userPassword Permissions apply to this user. Leaving this empty means the document + * is not password protected but viewer has the above permissions. + * @param {String} ownerPassword Owner has full functionalities to the file. + * @param {String} fileId As hex string, should be same as the file ID in the trailer. + * @example + * var security = new PDFSecurity(["print"]) + */ +function PDFSecurity(permissions, userPassword, ownerPassword, fileId) { + this.v = 1; // algorithm 1, future work can add in more recent encryption schemes + this.r = 2; // revision 2 + + // set flags for what functionalities the user can access + let protection = 192; + permissions.forEach(function(perm) { + if (typeof permissionOptions.perm !== "undefined") { + throw new Error("Invalid permission: " + perm); + } + protection += permissionOptions[perm]; + }); + + // padding is used to pad the passwords to 32 bytes, also is hashed and stored in the final PDF + this.padding = + "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" + + "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; + let paddedUserPassword = (userPassword + this.padding).substr(0, 32); + let paddedOwnerPassword = (ownerPassword + this.padding).substr(0, 32); + + this.O = this.processOwnerPassword(paddedUserPassword, paddedOwnerPassword); + this.P = -((protection ^ 255) + 1); + this.encryptionKey = md5Bin( + paddedUserPassword + + this.O + + this.lsbFirstWord(this.P) + + this.hexToBytes(fileId) + ).substr(0, 5); + this.U = rc4(this.encryptionKey, this.padding); +} + +/** + * Breaks down a 4-byte number into its individual bytes, with the least significant bit first + * + * @name lsbFirstWord + * @function + * @param {number} data 32-bit number + * @returns {Array} + */ +PDFSecurity.prototype.lsbFirstWord = function(data) { + return String.fromCharCode( + (data >> 0) & 0xff, + (data >> 8) & 0xff, + (data >> 16) & 0xff, + (data >> 24) & 0xff + ); +}; + +/** + * Converts a byte string to a hex string + * + * @name toHexString + * @function + * @param {String} byteString Byte string + * @returns {String} + */ +PDFSecurity.prototype.toHexString = function(byteString) { + return byteString + .split("") + .map(function(byte) { + return ("0" + (byte.charCodeAt(0) & 0xff).toString(16)).slice(-2); + }) + .join(""); +}; + +/** + * Converts a hex string to a byte string + * + * @name hexToBytes + * @function + * @param {String} hex Hex string + * @returns {String} + */ +PDFSecurity.prototype.hexToBytes = function(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) + bytes.push(String.fromCharCode(parseInt(hex.substr(c, 2), 16))); + return bytes.join(""); +}; + +/** + * Computes the 'O' field in the encryption dictionary + * + * @name processOwnerPassword + * @function + * @param {String} paddedUserPassword Byte string of padded user password + * @param {String} paddedOwnerPassword Byte string of padded owner password + * @returns {String} + */ +PDFSecurity.prototype.processOwnerPassword = function( + paddedUserPassword, + paddedOwnerPassword +) { + let key = md5Bin(paddedOwnerPassword).substr(0, 5); + return rc4(key, paddedUserPassword); +}; + +/** + * Returns an encryptor function which can take in a byte string and returns the encrypted version + * + * @name encryptor + * @function + * @param {number} objectId + * @param {number} generation Not sure what this is for, you can set it to 0 + * @returns {Function} + * @example + * out("stream"); + * encryptor = security.encryptor(object.id, 0); + * out(encryptor(data)); + * out("endstream"); + */ +PDFSecurity.prototype.encryptor = function(objectId, generation) { + let key = md5Bin( + this.encryptionKey + + String.fromCharCode( + objectId & 0xff, + (objectId >> 8) & 0xff, + (objectId >> 16) & 0xff, + generation & 0xff, + (generation >> 8) & 0xff + ) + ).substr(0, 10); + return function(data) { + return rc4(key, data); + }; +}; + +/** + * Convert string to `PDF Name Object`. + * Detail: PDF Reference 1.3 - Chapter 3.2.4 Name Object + * @param str + */ +function toPDFName(str) { + // eslint-disable-next-line no-control-regex + if (/[^\u0000-\u00ff]/.test(str)) { + // non ascii string + throw new Error( + "Invalid PDF Name Object: " + str + ", Only accept ASCII characters." + ); + } + var result = "", + strLength = str.length; + for (var i = 0; i < strLength; i++) { + var charCode = str.charCodeAt(i); + if ( + charCode < 0x21 || + charCode === 0x23 /* # */ || + charCode === 0x25 /* % */ || + charCode === 0x28 /* ( */ || + charCode === 0x29 /* ) */ || + charCode === 0x2f /* / */ || + charCode === 0x3c /* < */ || + charCode === 0x3e /* > */ || + charCode === 0x5b /* [ */ || + charCode === 0x5d /* ] */ || + charCode === 0x7b /* { */ || + charCode === 0x7d /* } */ || + charCode > 0x7e + ) { + // Char CharCode hexStr paddingHexStr Result + // "\t" 9 9 09 #09 + // " " 32 20 20 #20 + // "©" 169 a9 a9 #a9 + var hexStr = charCode.toString(16), + paddingHexStr = ("0" + hexStr).slice(-2); + + result += "#" + paddingHexStr; + } else { + // Other ASCII printable characters between 0x21 <= X <= 0x7e + result += str[i]; + } + } + return result; +} + +/* eslint-disable no-console */ +/** + * jsPDF's Internal PubSub Implementation. + * Backward compatible rewritten on 2014 by + * Diego Casorran, https://github.com/diegocr + * + * @class + * @name PubSub + * @ignore + */ +function PubSub(context) { + if (typeof context !== "object") { + throw new Error( + "Invalid Context passed to initialize PubSub (jsPDF-module)" + ); + } + var topics = {}; + + this.subscribe = function(topic, callback, once) { + once = once || false; + if ( + typeof topic !== "string" || + typeof callback !== "function" || + typeof once !== "boolean" + ) { + throw new Error( + "Invalid arguments passed to PubSub.subscribe (jsPDF-module)" + ); + } + + if (!topics.hasOwnProperty(topic)) { + topics[topic] = {}; + } + + var token = Math.random().toString(35); + topics[topic][token] = [callback, !!once]; + + return token; + }; + + this.unsubscribe = function(token) { + for (var topic in topics) { + if (topics[topic][token]) { + delete topics[topic][token]; + if (Object.keys(topics[topic]).length === 0) { + delete topics[topic]; + } + return true; + } + } + return false; + }; + + this.publish = function(topic) { + if (topics.hasOwnProperty(topic)) { + var args = Array.prototype.slice.call(arguments, 1), + tokens = []; + + for (var token in topics[topic]) { + var sub = topics[topic][token]; + try { + sub[0].apply(context, args); + } catch (ex) { + if (globalObject.console) { + console.error("jsPDF PubSub Error", ex.message, ex); + } + } + if (sub[1]) tokens.push(token); + } + if (tokens.length) tokens.forEach(this.unsubscribe); + } + }; + + this.getTopics = function() { + return topics; + }; +} + +function GState(parameters) { + if (!(this instanceof GState)) { + return new GState(parameters); + } + + /** + * @name GState#opacity + * @type {any} + */ + /** + * @name GState#stroke-opacity + * @type {any} + */ + var supported = "opacity,stroke-opacity".split(","); + for (var p in parameters) { + if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) { + this[p] = parameters[p]; + } + } + /** + * @name GState#id + * @type {string} + */ + this.id = ""; // set by addGState() + /** + * @name GState#objectNumber + * @type {number} + */ + this.objectNumber = -1; // will be set by putGState() +} + +GState.prototype.equals = function equals(other) { + var ignore = "id,objectNumber,equals"; + var p; + if (!other || typeof other !== typeof this) return false; + var count = 0; + for (p in this) { + if (ignore.indexOf(p) >= 0) continue; + if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false; + if (this[p] !== other[p]) return false; + count++; + } + for (p in other) { + if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--; + } + return count === 0; +}; + +function Pattern(gState, matrix) { + this.gState = gState; + this.matrix = matrix; + + this.id = ""; // set by addPattern() + this.objectNumber = -1; // will be set by putPattern() +} + +function ShadingPattern(type, coords, colors, gState, matrix) { + if (!(this instanceof ShadingPattern)) { + return new ShadingPattern(type, coords, colors, gState, matrix); + } + + // see putPattern() for information how they are realized + this.type = type === "axial" ? 2 : 3; + this.coords = coords; + this.colors = colors; + + Pattern.call(this, gState, matrix); +} + +function TilingPattern(boundingBox, xStep, yStep, gState, matrix) { + if (!(this instanceof TilingPattern)) { + return new TilingPattern(boundingBox, xStep, yStep, gState, matrix); + } + + this.boundingBox = boundingBox; + this.xStep = xStep; + this.yStep = yStep; + + this.stream = ""; // set by endTilingPattern(); + + this.cloneIndex = 0; + + Pattern.call(this, gState, matrix); +} + +/** + * Creates new jsPDF document object instance. + * @name jsPDF + * @class + * @param {Object} [options] - Collection of settings initializing the jsPDF-instance + * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").
    + * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.
    + * Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" + * units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]. + * @param {string/Array} [options.format=a4] The format of the first page. Can be:
    • a0 - a10
    • b0 - b10
    • c0 - c10
    • dl
    • letter
    • government-letter
    • legal
    • junior-legal
    • ledger
    • tabloid
    • credit-card

    + * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] + * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used. + * @param {boolean} [options.compress=false] Compress the generated PDF. + * @param {number} [options.precision=16] Precision of the element-positions. + * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it. + * @param {string[]} [options.hotfixes] An array of strings to enable hotfixes such as correct pixel scaling. + * @param {Object} [options.encryption] + * @param {string} [options.encryption.userPassword] Password for the user bound by the given permissions list. + * @param {string} [options.encryption.ownerPassword] Both userPassword and ownerPassword should be set for proper authentication. + * @param {string[]} [options.encryption.userPermissions] Array of permissions "print", "modify", "copy", "annot-forms", accessible by the user. + * @param {number|"smart"} [options.floatPrecision=16] + * @returns {jsPDF} jsPDF-instance + * @description + * ``` + * { + * orientation: 'p', + * unit: 'mm', + * format: 'a4', + * putOnlyUsedFonts:true, + * floatPrecision: 16 // or "smart", default is 16 + * } + * ``` + * + * @constructor + */ +function jsPDF(options) { + var orientation = typeof arguments[0] === "string" ? arguments[0] : "p"; + var unit = arguments[1]; + var format = arguments[2]; + var compressPdf = arguments[3]; + var filters = []; + var userUnit = 1.0; + var precision; + var floatPrecision = 16; + var defaultPathOperation = "S"; + var encryptionOptions = null; + + options = options || {}; + + if (typeof options === "object") { + orientation = options.orientation; + unit = options.unit || unit; + format = options.format || format; + compressPdf = options.compress || options.compressPdf || compressPdf; + encryptionOptions = options.encryption || null; + if (encryptionOptions !== null) { + encryptionOptions.userPassword = encryptionOptions.userPassword || ""; + encryptionOptions.ownerPassword = encryptionOptions.ownerPassword || ""; + encryptionOptions.userPermissions = + encryptionOptions.userPermissions || []; + } + userUnit = + typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0; + if (typeof options.precision !== "undefined") { + precision = options.precision; + } + if (typeof options.floatPrecision !== "undefined") { + floatPrecision = options.floatPrecision; + } + defaultPathOperation = options.defaultPathOperation || "S"; + } + + filters = + options.filters || (compressPdf === true ? ["FlateEncode"] : filters); + + unit = unit || "mm"; + orientation = ("" + (orientation || "P")).toLowerCase(); + var putOnlyUsedFonts = options.putOnlyUsedFonts || false; + var usedFonts = {}; + + var API = { + internal: {}, + __private__: {} + }; + + API.__private__.PubSub = PubSub; + + var pdfVersion = "1.3"; + var getPdfVersion = (API.__private__.getPdfVersion = function() { + return pdfVersion; + }); + + API.__private__.setPdfVersion = function(value) { + pdfVersion = value; + }; + + // Size in pt of various paper formats + var pageFormats = { + a0: [2383.94, 3370.39], + a1: [1683.78, 2383.94], + a2: [1190.55, 1683.78], + a3: [841.89, 1190.55], + a4: [595.28, 841.89], + a5: [419.53, 595.28], + a6: [297.64, 419.53], + a7: [209.76, 297.64], + a8: [147.4, 209.76], + a9: [104.88, 147.4], + a10: [73.7, 104.88], + b0: [2834.65, 4008.19], + b1: [2004.09, 2834.65], + b2: [1417.32, 2004.09], + b3: [1000.63, 1417.32], + b4: [708.66, 1000.63], + b5: [498.9, 708.66], + b6: [354.33, 498.9], + b7: [249.45, 354.33], + b8: [175.75, 249.45], + b9: [124.72, 175.75], + b10: [87.87, 124.72], + c0: [2599.37, 3676.54], + c1: [1836.85, 2599.37], + c2: [1298.27, 1836.85], + c3: [918.43, 1298.27], + c4: [649.13, 918.43], + c5: [459.21, 649.13], + c6: [323.15, 459.21], + c7: [229.61, 323.15], + c8: [161.57, 229.61], + c9: [113.39, 161.57], + c10: [79.37, 113.39], + dl: [311.81, 623.62], + letter: [612, 792], + "government-letter": [576, 756], + legal: [612, 1008], + "junior-legal": [576, 360], + ledger: [1224, 792], + tabloid: [792, 1224], + "credit-card": [153, 243] + }; + + API.__private__.getPageFormats = function() { + return pageFormats; + }; + + var getPageFormat = (API.__private__.getPageFormat = function(value) { + return pageFormats[value]; + }); + + format = format || "a4"; + + var ApiMode = { + COMPAT: "compat", + ADVANCED: "advanced" + }; + var apiMode = ApiMode.COMPAT; + + function advancedAPI() { + // prepend global change of basis matrix + // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix + // that does this job for us (however, texts, images and similar objects must be drawn bottom up)) + this.saveGraphicsState(); + out( + new Matrix( + scaleFactor, + 0, + 0, + -scaleFactor, + 0, + getPageHeight() * scaleFactor + ).toString() + " cm" + ); + this.setFontSize(this.getFontSize() / scaleFactor); + + // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation + // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here. + defaultPathOperation = "n"; + + apiMode = ApiMode.ADVANCED; + } + + function compatAPI() { + this.restoreGraphicsState(); + defaultPathOperation = "S"; + apiMode = ApiMode.COMPAT; + } + + /** + * @function combineFontStyleAndFontWeight + * @param {string} fontStyle Fontstyle or variant. Example: "italic". + * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 + * @returns {string} + * @private + */ + var combineFontStyleAndFontWeight = (API.__private__.combineFontStyleAndFontWeight = function( + fontStyle, + fontWeight + ) { + if ( + (fontStyle == "bold" && fontWeight == "normal") || + (fontStyle == "bold" && fontWeight == 400) || + (fontStyle == "normal" && fontWeight == "italic") || + (fontStyle == "bold" && fontWeight == "italic") + ) { + throw new Error("Invalid Combination of fontweight and fontstyle"); + } + if (fontWeight) { + fontStyle = + fontWeight == 400 || fontWeight === "normal" + ? fontStyle === "italic" + ? "italic" + : "normal" + : (fontWeight == 700 || fontWeight === "bold") && + fontStyle === "normal" + ? "bold" + : (fontWeight == 700 ? "bold" : fontWeight) + "" + fontStyle; + } + return fontStyle; + }); + + /** + * @callback ApiSwitchBody + * @param {jsPDF} pdf + */ + + /** + * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual + * screen coordinates and the PDF coordinate system. + * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms + * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might + * not support this mode, though. + * Initial mode is "compat". + * + * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to + * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}. + * + * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the + * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject} + * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode. + * + * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. + * The API mode will be switched back automatically afterwards. + * @returns {jsPDF} + * @memberof jsPDF# + * @name advancedAPI + */ + API.advancedAPI = function(body) { + var doSwitch = apiMode === ApiMode.COMPAT; + + if (doSwitch) { + advancedAPI.call(this); + } + + if (typeof body !== "function") { + return this; + } + + body(this); + + if (doSwitch) { + compatAPI.call(this); + } + + return this; + }; + + /** + * Switches to "compat" API mode. See {@link advancedAPI} for more details. + * + * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. + * The API mode will be switched back automatically afterwards. + * @return {jsPDF} + * @memberof jsPDF# + * @name compatApi + */ + API.compatAPI = function(body) { + var doSwitch = apiMode === ApiMode.ADVANCED; + + if (doSwitch) { + compatAPI.call(this); + } + + if (typeof body !== "function") { + return this; + } + + body(this); + + if (doSwitch) { + advancedAPI.call(this); + } + + return this; + }; + + /** + * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}. + * @memberof jsPDF# + * @name isAdvancedAPI + */ + API.isAdvancedAPI = function() { + return apiMode === ApiMode.ADVANCED; + }; + + var advancedApiModeTrap = function(methodName) { + if (apiMode !== ApiMode.ADVANCED) { + throw new Error( + methodName + + " is only available in 'advanced' API mode. " + + "You need to call advancedAPI() first." + ); + } + }; + + var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function( + number, + parmPrecision + ) { + var tmpPrecision = precision || parmPrecision; + if (isNaN(number) || isNaN(tmpPrecision)) { + throw new Error("Invalid argument passed to jsPDF.roundToPrecision"); + } + return number.toFixed(tmpPrecision).replace(/0+$/, ""); + }); + + // high precision float + var hpf; + if (typeof floatPrecision === "number") { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + return roundToPrecision(number, floatPrecision); + }; + } else if (floatPrecision === "smart") { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + if (number > -1 && number < 1) { + return roundToPrecision(number, 16); + } else { + return roundToPrecision(number, 5); + } + }; + } else { + hpf = API.hpf = API.__private__.hpf = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.hpf"); + } + return roundToPrecision(number, 16); + }; + } + var f2 = (API.f2 = API.__private__.f2 = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.f2"); + } + return roundToPrecision(number, 2); + }); + + var f3 = (API.__private__.f3 = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.f3"); + } + return roundToPrecision(number, 3); + }); + + var scale = (API.scale = API.__private__.scale = function(number) { + if (isNaN(number)) { + throw new Error("Invalid argument passed to jsPDF.scale"); + } + if (apiMode === ApiMode.COMPAT) { + return number * scaleFactor; + } else if (apiMode === ApiMode.ADVANCED) { + return number; + } + }); + + var transformY = function(y) { + if (apiMode === ApiMode.COMPAT) { + return getPageHeight() - y; + } else if (apiMode === ApiMode.ADVANCED) { + return y; + } + }; + + var transformScaleY = function(y) { + return scale(transformY(y)); + }; + + /** + * @name setPrecision + * @memberof jsPDF# + * @function + * @instance + * @param {string} precision + * @returns {jsPDF} + */ + API.__private__.setPrecision = API.setPrecision = function(value) { + if (typeof parseInt(value, 10) === "number") { + precision = parseInt(value, 10); + } + }; + + var fileId = "00000000000000000000000000000000"; + + var getFileId = (API.__private__.getFileId = function() { + return fileId; + }); + + var setFileId = (API.__private__.setFileId = function(value) { + if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) { + fileId = value.toUpperCase(); + } else { + fileId = fileId + .split("") + .map(function() { + return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16)); + }) + .join(""); + } + + if (encryptionOptions !== null) { + encryption = new PDFSecurity( + encryptionOptions.userPermissions, + encryptionOptions.userPassword, + encryptionOptions.ownerPassword, + fileId + ); + } + return fileId; + }); + + /** + * @name setFileId + * @memberof jsPDF# + * @function + * @instance + * @param {string} value GUID. + * @returns {jsPDF} + */ + API.setFileId = function(value) { + setFileId(value); + return this; + }; + + /** + * @name getFileId + * @memberof jsPDF# + * @function + * @instance + * + * @returns {string} GUID. + */ + API.getFileId = function() { + return getFileId(); + }; + + var creationDate; + + var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function( + parmDate + ) { + var result = ""; + var tzoffset = parmDate.getTimezoneOffset(), + tzsign = tzoffset < 0 ? "+" : "-", + tzhour = Math.floor(Math.abs(tzoffset / 60)), + tzmin = Math.abs(tzoffset % 60), + timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(""); + + result = [ + "D:", + parmDate.getFullYear(), + padd2(parmDate.getMonth() + 1), + padd2(parmDate.getDate()), + padd2(parmDate.getHours()), + padd2(parmDate.getMinutes()), + padd2(parmDate.getSeconds()), + timeZoneString + ].join(""); + return result; + }); + + var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function( + parmPDFDate + ) { + var year = parseInt(parmPDFDate.substr(2, 4), 10); + var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1; + var date = parseInt(parmPDFDate.substr(8, 2), 10); + var hour = parseInt(parmPDFDate.substr(10, 2), 10); + var minutes = parseInt(parmPDFDate.substr(12, 2), 10); + var seconds = parseInt(parmPDFDate.substr(14, 2), 10); + // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10); + // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10); + + var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0); + return resultingDate; + }); + + var setCreationDate = (API.__private__.setCreationDate = function(date) { + var tmpCreationDateString; + var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/; + if (typeof date === "undefined") { + date = new Date(); + } + + if (date instanceof Date) { + tmpCreationDateString = convertDateToPDFDate(date); + } else if (regexPDFCreationDate.test(date)) { + tmpCreationDateString = date; + } else { + throw new Error("Invalid argument passed to jsPDF.setCreationDate"); + } + creationDate = tmpCreationDateString; + return creationDate; + }); + + var getCreationDate = (API.__private__.getCreationDate = function(type) { + var result = creationDate; + if (type === "jsDate") { + result = convertPDFDateToDate(creationDate); + } + return result; + }); + + /** + * @name setCreationDate + * @memberof jsPDF# + * @function + * @instance + * @param {Object} date + * @returns {jsPDF} + */ + API.setCreationDate = function(date) { + setCreationDate(date); + return this; + }; + + /** + * @name getCreationDate + * @memberof jsPDF# + * @function + * @instance + * @param {Object} type + * @returns {Object} + */ + API.getCreationDate = function(type) { + return getCreationDate(type); + }; + + var padd2 = (API.__private__.padd2 = function(number) { + return ("0" + parseInt(number)).slice(-2); + }); + + var padd2Hex = (API.__private__.padd2Hex = function(hexString) { + hexString = hexString.toString(); + return ("00" + hexString).substr(hexString.length); + }); + + var objectNumber = 0; // 'n' Current object number + var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. + var content = []; + var contentLength = 0; + var additionalObjects = []; + + var pages = []; + var currentPage; + var hasCustomDestination = false; + var outputDestination = content; + + var resetDocument = function() { + //reset fields relevant for objectNumber generation and xref. + objectNumber = 0; + contentLength = 0; + content = []; + offsets = []; + additionalObjects = []; + + rootDictionaryObjId = newObjectDeferred(); + resourceDictionaryObjId = newObjectDeferred(); + }; + + API.__private__.setCustomOutputDestination = function(destination) { + hasCustomDestination = true; + outputDestination = destination; + }; + var setOutputDestination = function(destination) { + if (!hasCustomDestination) { + outputDestination = destination; + } + }; + + API.__private__.resetCustomOutputDestination = function() { + hasCustomDestination = false; + outputDestination = content; + }; + + var out = (API.__private__.out = function(string) { + string = string.toString(); + contentLength += string.length + 1; + outputDestination.push(string); + + return outputDestination; + }); + + var write = (API.__private__.write = function(value) { + return out( + arguments.length === 1 + ? value.toString() + : Array.prototype.join.call(arguments, " ") + ); + }); + + var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) { + var len = data.length, + ab = new ArrayBuffer(len), + u8 = new Uint8Array(ab); + + while (len--) u8[len] = data.charCodeAt(len); + return ab; + }); + + var standardFonts = [ + ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"], + ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"], + ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"], + ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"], + ["Courier", "courier", "normal", "WinAnsiEncoding"], + ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"], + ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"], + ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"], + ["Times-Roman", "times", "normal", "WinAnsiEncoding"], + ["Times-Bold", "times", "bold", "WinAnsiEncoding"], + ["Times-Italic", "times", "italic", "WinAnsiEncoding"], + ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"], + ["ZapfDingbats", "zapfdingbats", "normal", null], + ["Symbol", "symbol", "normal", null] + ]; + + API.__private__.getStandardFonts = function() { + return standardFonts; + }; + + var activeFontSize = options.fontSize || 16; + + /** + * Sets font size for upcoming text elements. + * + * @param {number} size Font size in points. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFontSize + */ + API.__private__.setFontSize = API.setFontSize = function(size) { + if (apiMode === ApiMode.ADVANCED) { + activeFontSize = size / scaleFactor; + } else { + activeFontSize = size; + } + return this; + }; + + /** + * Gets the fontsize for upcoming text elements. + * + * @function + * @instance + * @returns {number} + * @memberof jsPDF# + * @name getFontSize + */ + var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() { + if (apiMode === ApiMode.COMPAT) { + return activeFontSize; + } else { + return activeFontSize * scaleFactor; + } + }); + + var R2L = options.R2L || false; + + /** + * Set value of R2L functionality. + * + * @param {boolean} value + * @function + * @instance + * @returns {jsPDF} jsPDF-instance + * @memberof jsPDF# + * @name setR2L + */ + API.__private__.setR2L = API.setR2L = function(value) { + R2L = value; + return this; + }; + + /** + * Get value of R2L functionality. + * + * @function + * @instance + * @returns {boolean} jsPDF-instance + * @memberof jsPDF# + * @name getR2L + */ + API.__private__.getR2L = API.getR2L = function() { + return R2L; + }; + + var zoomMode; // default: 1; + + var setZoomMode = (API.__private__.setZoomMode = function(zoom) { + var validZoomModes = [ + undefined, + null, + "fullwidth", + "fullheight", + "fullpage", + "original" + ]; + + if (/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(zoom)) { + zoomMode = zoom; + } else if (!isNaN(zoom)) { + zoomMode = parseInt(zoom, 10); + } else if (validZoomModes.indexOf(zoom) !== -1) { + zoomMode = zoom; + } else { + throw new Error( + 'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' + + zoom + + '" is not recognized.' + ); + } + }); + + API.__private__.getZoomMode = function() { + return zoomMode; + }; + + var pageMode; // default: 'UseOutlines'; + var setPageMode = (API.__private__.setPageMode = function(pmode) { + var validPageModes = [ + undefined, + null, + "UseNone", + "UseOutlines", + "UseThumbs", + "FullScreen" + ]; + + if (validPageModes.indexOf(pmode) == -1) { + throw new Error( + 'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + + pmode + + '" is not recognized.' + ); + } + pageMode = pmode; + }); + + API.__private__.getPageMode = function() { + return pageMode; + }; + + var layoutMode; // default: 'continuous'; + var setLayoutMode = (API.__private__.setLayoutMode = function(layout) { + var validLayoutModes = [ + undefined, + null, + "continuous", + "single", + "twoleft", + "tworight", + "two" + ]; + + if (validLayoutModes.indexOf(layout) == -1) { + throw new Error( + 'Layout mode must be one of continuous, single, twoleft, tworight. "' + + layout + + '" is not recognized.' + ); + } + layoutMode = layout; + }); + + API.__private__.getLayoutMode = function() { + return layoutMode; + }; + + /** + * Set the display mode options of the page like zoom and layout. + * + * @name setDisplayMode + * @memberof jsPDF# + * @function + * @instance + * @param {integer|String} zoom You can pass an integer or percentage as + * a string. 2 will scale the document up 2x, '200%' will scale up by the + * same amount. You can also set it to 'fullwidth', 'fullheight', + * 'fullpage', or 'original'. + * + * Only certain PDF readers support this, such as Adobe Acrobat. + * + * @param {string} layout Layout mode can be: 'continuous' - this is the + * default continuous scroll. 'single' - the single page mode only shows one + * page at a time. 'twoleft' - two column left mode, first page starts on + * the left, and 'tworight' - pages are laid out in two columns, with the + * first page on the right. This would be used for books. + * @param {string} pmode 'UseOutlines' - it shows the + * outline of the document on the left. 'UseThumbs' - shows thumbnails along + * the left. 'FullScreen' - prompts the user to enter fullscreen mode. + * + * @returns {jsPDF} + */ + API.__private__.setDisplayMode = API.setDisplayMode = function( + zoom, + layout, + pmode + ) { + setZoomMode(zoom); + setLayoutMode(layout); + setPageMode(pmode); + return this; + }; + + var documentProperties = { + title: "", + subject: "", + author: "", + keywords: "", + creator: "" + }; + + API.__private__.getDocumentProperty = function(key) { + if (Object.keys(documentProperties).indexOf(key) === -1) { + throw new Error("Invalid argument passed to jsPDF.getDocumentProperty"); + } + return documentProperties[key]; + }; + + API.__private__.getDocumentProperties = function() { + return documentProperties; + }; + + /** + * Adds a properties to the PDF document. + * + * @param {Object} A property_name-to-property_value object structure. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setDocumentProperties + */ + API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function( + properties + ) { + // copying only those properties we can render. + for (var property in documentProperties) { + if (documentProperties.hasOwnProperty(property) && properties[property]) { + documentProperties[property] = properties[property]; + } + } + return this; + }; + + API.__private__.setDocumentProperty = function(key, value) { + if (Object.keys(documentProperties).indexOf(key) === -1) { + throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty"); + } + return (documentProperties[key] = value); + }; + + var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font. + var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont() + var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle + var fontStateStack = []; // + var patterns = {}; // collection of pattern objects + var patternMap = {}; // see fonts + var gStates = {}; // collection of graphic state objects + var gStatesMap = {}; // see fonts + var activeGState = null; + var scaleFactor; // Scale factor + var page = 0; + var pagesContext = []; + var events = new PubSub(API); + var hotfixes = options.hotfixes || []; + + var renderTargets = {}; + var renderTargetMap = {}; + var renderTargetStack = []; + var pageX; + var pageY; + var pageMatrix; // only used for FormObjects + + /** + * A matrix object for 2D homogenous transformations:
    + * | a b 0 |
    + * | c d 0 |
    + * | e f 1 |
    + * pdf multiplies matrices righthand: v' = v x m1 x m2 x ... + * + * @class + * @name Matrix + * @param {number} sx + * @param {number} shy + * @param {number} shx + * @param {number} sy + * @param {number} tx + * @param {number} ty + * @constructor + */ + var Matrix = function(sx, shy, shx, sy, tx, ty) { + if (!(this instanceof Matrix)) { + return new Matrix(sx, shy, shx, sy, tx, ty); + } + + if (isNaN(sx)) sx = 1; + if (isNaN(shy)) shy = 0; + if (isNaN(shx)) shx = 0; + if (isNaN(sy)) sy = 1; + if (isNaN(tx)) tx = 0; + if (isNaN(ty)) ty = 0; + + this._matrix = [sx, shy, shx, sy, tx, ty]; + }; + + /** + * @name sx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "sx", { + get: function() { + return this._matrix[0]; + }, + set: function(value) { + this._matrix[0] = value; + } + }); + + /** + * @name shy + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "shy", { + get: function() { + return this._matrix[1]; + }, + set: function(value) { + this._matrix[1] = value; + } + }); + + /** + * @name shx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "shx", { + get: function() { + return this._matrix[2]; + }, + set: function(value) { + this._matrix[2] = value; + } + }); + + /** + * @name sy + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "sy", { + get: function() { + return this._matrix[3]; + }, + set: function(value) { + this._matrix[3] = value; + } + }); + + /** + * @name tx + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "tx", { + get: function() { + return this._matrix[4]; + }, + set: function(value) { + this._matrix[4] = value; + } + }); + + /** + * @name ty + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "ty", { + get: function() { + return this._matrix[5]; + }, + set: function(value) { + this._matrix[5] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "a", { + get: function() { + return this._matrix[0]; + }, + set: function(value) { + this._matrix[0] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "b", { + get: function() { + return this._matrix[1]; + }, + set: function(value) { + this._matrix[1] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "c", { + get: function() { + return this._matrix[2]; + }, + set: function(value) { + this._matrix[2] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "d", { + get: function() { + return this._matrix[3]; + }, + set: function(value) { + this._matrix[3] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "e", { + get: function() { + return this._matrix[4]; + }, + set: function(value) { + this._matrix[4] = value; + } + }); + + Object.defineProperty(Matrix.prototype, "f", { + get: function() { + return this._matrix[5]; + }, + set: function(value) { + this._matrix[5] = value; + } + }); + + /** + * @name rotation + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "rotation", { + get: function() { + return Math.atan2(this.shx, this.sx); + } + }); + + /** + * @name scaleX + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "scaleX", { + get: function() { + return this.decompose().scale.sx; + } + }); + + /** + * @name scaleY + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "scaleY", { + get: function() { + return this.decompose().scale.sy; + } + }); + + /** + * @name isIdentity + * @memberof Matrix# + */ + Object.defineProperty(Matrix.prototype, "isIdentity", { + get: function() { + if (this.sx !== 1) { + return false; + } + if (this.shy !== 0) { + return false; + } + if (this.shx !== 0) { + return false; + } + if (this.sy !== 1) { + return false; + } + if (this.tx !== 0) { + return false; + } + if (this.ty !== 0) { + return false; + } + return true; + } + }); + + /** + * Join the Matrix Values to a String + * + * @function join + * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them. + * @returns {string} A string with all array elements joined. + * @memberof Matrix# + */ + Matrix.prototype.join = function(separator) { + return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty] + .map(hpf) + .join(separator); + }; + + /** + * Multiply the matrix with given Matrix + * + * @function multiply + * @param matrix + * @returns {Matrix} + * @memberof Matrix# + */ + Matrix.prototype.multiply = function(matrix) { + var sx = matrix.sx * this.sx + matrix.shy * this.shx; + var shy = matrix.sx * this.shy + matrix.shy * this.sy; + var shx = matrix.shx * this.sx + matrix.sy * this.shx; + var sy = matrix.shx * this.shy + matrix.sy * this.sy; + var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx; + var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty; + + return new Matrix(sx, shy, shx, sy, tx, ty); + }; + + /** + * @function decompose + * @memberof Matrix# + */ + Matrix.prototype.decompose = function() { + var a = this.sx; + var b = this.shy; + var c = this.shx; + var d = this.sy; + var e = this.tx; + var f = this.ty; + + var scaleX = Math.sqrt(a * a + b * b); + a /= scaleX; + b /= scaleX; + + var shear = a * c + b * d; + c -= a * shear; + d -= b * shear; + + var scaleY = Math.sqrt(c * c + d * d); + c /= scaleY; + d /= scaleY; + shear /= scaleY; + + if (a * d < b * c) { + a = -a; + b = -b; + shear = -shear; + scaleX = -scaleX; + } + + return { + scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0), + translate: new Matrix(1, 0, 0, 1, e, f), + rotate: new Matrix(a, b, -b, a, 0, 0), + skew: new Matrix(1, 0, shear, 1, 0, 0) + }; + }; + + /** + * @function toString + * @memberof Matrix# + */ + Matrix.prototype.toString = function(parmPrecision) { + return this.join(" "); + }; + + /** + * @function inversed + * @memberof Matrix# + */ + Matrix.prototype.inversed = function() { + var a = this.sx, + b = this.shy, + c = this.shx, + d = this.sy, + e = this.tx, + f = this.ty; + + var quot = 1 / (a * d - b * c); + + var aInv = d * quot; + var bInv = -b * quot; + var cInv = -c * quot; + var dInv = a * quot; + var eInv = -aInv * e - cInv * f; + var fInv = -bInv * e - dInv * f; + + return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv); + }; + + /** + * @function applyToPoint + * @memberof Matrix# + */ + Matrix.prototype.applyToPoint = function(pt) { + var x = pt.x * this.sx + pt.y * this.shx + this.tx; + var y = pt.x * this.shy + pt.y * this.sy + this.ty; + return new Point(x, y); + }; + + /** + * @function applyToRectangle + * @memberof Matrix# + */ + Matrix.prototype.applyToRectangle = function(rect) { + var pt1 = this.applyToPoint(rect); + var pt2 = this.applyToPoint(new Point(rect.x + rect.w, rect.y + rect.h)); + return new Rectangle(pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y); + }; + + /** + * Clone the Matrix + * + * @function clone + * @memberof Matrix# + * @name clone + * @instance + */ + Matrix.prototype.clone = function() { + var sx = this.sx; + var shy = this.shy; + var shx = this.shx; + var sy = this.sy; + var tx = this.tx; + var ty = this.ty; + + return new Matrix(sx, shy, shx, sy, tx, ty); + }; + + API.Matrix = Matrix; + + /** + * Multiplies two matrices. (see {@link Matrix}) + * @param {Matrix} m1 + * @param {Matrix} m2 + * @memberof jsPDF# + * @name matrixMult + */ + var matrixMult = (API.matrixMult = function(m1, m2) { + return m2.multiply(m1); + }); + + /** + * The identity matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)). + * @type {Matrix} + * @memberof! jsPDF# + * @name identityMatrix + */ + var identityMatrix = new Matrix(1, 0, 0, 1, 0, 0); + API.unitMatrix = API.identityMatrix = identityMatrix; + + /** + * Adds a new pattern for later use. + * @param {String} key The key by it can be referenced later. The keys must be unique! + * @param {API.Pattern} pattern The pattern + */ + var addPattern = function(key, pattern) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (patternMap[key]) return; + + var prefix = pattern instanceof ShadingPattern ? "Sh" : "P"; + var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10); + pattern.id = patternKey; + + patternMap[key] = patternKey; + patterns[patternKey] = pattern; + + events.publish("addPattern", pattern); + }; + + /** + * A pattern describing a shading pattern. + * + * Only available in "advanced" API mode. + * + * @param {String} type One of "axial" or "radial" + * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points + * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle. + * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes + * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255]. + * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional). + * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system + * and the use coordinate system (optional). + * @constructor + * @extends API.Pattern + */ + API.ShadingPattern = ShadingPattern; + + /** + * A PDF Tiling pattern. + * + * Only available in "advanced" API mode. + * + * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped. + * @param {Number} xStep Horizontal spacing between pattern cells. + * @param {Number} yStep Vertical spacing between pattern cells. + * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional). + * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system + * and the use coordinate system (optional). + * @constructor + * @extends API.Pattern + */ + API.TilingPattern = TilingPattern; + + /** + * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode. + * @param {String} key + * @param {Pattern} pattern + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name addPattern + */ + API.addShadingPattern = function(key, pattern) { + advancedApiModeTrap("addShadingPattern()"); + + addPattern(key, pattern); + return this; + }; + + /** + * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern} + * gets called. Only available in "advanced" API mode. + * @param {API.Pattern} pattern + * @memberof jsPDF# + * @name beginTilingPattern + */ + API.beginTilingPattern = function(pattern) { + advancedApiModeTrap("beginTilingPattern()"); + + beginNewRenderTarget( + pattern.boundingBox[0], + pattern.boundingBox[1], + pattern.boundingBox[2] - pattern.boundingBox[0], + pattern.boundingBox[3] - pattern.boundingBox[1], + pattern.matrix + ); + }; + + /** + * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called. + * + * Only available in "advanced" API mode. + * + * @param {string} key A unique key that is used to reference this pattern at later use. + * @param {API.Pattern} pattern The pattern to end. + * @memberof jsPDF# + * @name endTilingPattern + */ + API.endTilingPattern = function(key, pattern) { + advancedApiModeTrap("endTilingPattern()"); + + // retrieve the stream + pattern.stream = pages[currentPage].join("\n"); + + addPattern(key, pattern); + + events.publish("endTilingPattern", pattern); + + // restore state from stack + renderTargetStack.pop().restore(); + }; + + var newObject = (API.__private__.newObject = function() { + var oid = newObjectDeferred(); + newObjectDeferredBegin(oid, true); + return oid; + }); + + // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data + var newObjectDeferred = (API.__private__.newObjectDeferred = function() { + objectNumber++; + offsets[objectNumber] = function() { + return contentLength; + }; + return objectNumber; + }); + + var newObjectDeferredBegin = function(oid, doOutput) { + doOutput = typeof doOutput === "boolean" ? doOutput : false; + offsets[oid] = contentLength; + if (doOutput) { + out(oid + " 0 obj"); + } + return oid; + }; + // Does not output the object until after the pages have been output. + // Returns an object containing the objectId and content. + // All pages have been added so the object ID can be estimated to start right after. + // This does not modify the current objectNumber; It must be updated after the newObjects are output. + var newAdditionalObject = (API.__private__.newAdditionalObject = function() { + var objId = newObjectDeferred(); + var obj = { + objId: objId, + content: "" + }; + additionalObjects.push(obj); + return obj; + }); + + var rootDictionaryObjId = newObjectDeferred(); + var resourceDictionaryObjId = newObjectDeferred(); + + ///////////////////// + // Private functions + ///////////////////// + + var decodeColorString = (API.__private__.decodeColorString = function(color) { + var colorEncoded = color.split(" "); + if ( + colorEncoded.length === 2 && + (colorEncoded[1] === "g" || colorEncoded[1] === "G") + ) { + // convert grayscale value to rgb so that it can be converted to hex for consistency + var floatVal = parseFloat(colorEncoded[0]); + colorEncoded = [floatVal, floatVal, floatVal, "r"]; + } else if ( + colorEncoded.length === 5 && + (colorEncoded[4] === "k" || colorEncoded[4] === "K") + ) { + // convert CMYK values to rbg so that it can be converted to hex for consistency + var red = (1.0 - colorEncoded[0]) * (1.0 - colorEncoded[3]); + var green = (1.0 - colorEncoded[1]) * (1.0 - colorEncoded[3]); + var blue = (1.0 - colorEncoded[2]) * (1.0 - colorEncoded[3]); + + colorEncoded = [red, green, blue, "r"]; + } + var colorAsRGB = "#"; + for (var i = 0; i < 3; i++) { + colorAsRGB += ( + "0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16) + ).slice(-2); + } + return colorAsRGB; + }); + + var encodeColorString = (API.__private__.encodeColorString = function( + options + ) { + var color; + + if (typeof options === "string") { + options = { + ch1: options + }; + } + var ch1 = options.ch1; + var ch2 = options.ch2; + var ch3 = options.ch3; + var ch4 = options.ch4; + var letterArray = + options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"]; + + if (typeof ch1 === "string" && ch1.charAt(0) !== "#") { + var rgbColor = new RGBColor(ch1); + if (rgbColor.ok) { + ch1 = rgbColor.toHex(); + } else if (!/^\d*\.?\d*$/.test(ch1)) { + throw new Error( + 'Invalid color "' + ch1 + '" passed to jsPDF.encodeColorString.' + ); + } + } + //convert short rgb to long form + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) { + ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3]; + } + + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) { + var hex = parseInt(ch1.substr(1), 16); + ch1 = (hex >> 16) & 255; + ch2 = (hex >> 8) & 255; + ch3 = hex & 255; + } + + if ( + typeof ch2 === "undefined" || + (typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3) + ) { + // Gray color space. + if (typeof ch1 === "string") { + color = ch1 + " " + letterArray[0]; + } else { + switch (options.precision) { + case 2: + color = f2(ch1 / 255) + " " + letterArray[0]; + break; + case 3: + default: + color = f3(ch1 / 255) + " " + letterArray[0]; + } + } + } else if (typeof ch4 === "undefined" || typeof ch4 === "object") { + // assume RGBA + if (ch4 && !isNaN(ch4.a)) { + //TODO Implement transparency. + //WORKAROUND use white for now, if transparent, otherwise handle as rgb + if (ch4.a === 0) { + color = ["1.", "1.", "1.", letterArray[1]].join(" "); + return color; + } + } + // assume RGB + if (typeof ch1 === "string") { + color = [ch1, ch2, ch3, letterArray[1]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [ + f2(ch1 / 255), + f2(ch2 / 255), + f2(ch3 / 255), + letterArray[1] + ].join(" "); + break; + default: + case 3: + color = [ + f3(ch1 / 255), + f3(ch2 / 255), + f3(ch3 / 255), + letterArray[1] + ].join(" "); + } + } + } else { + // assume CMYK + if (typeof ch1 === "string") { + color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join( + " " + ); + break; + case 3: + default: + color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join( + " " + ); + } + } + } + return color; + }); + + var getFilters = (API.__private__.getFilters = function() { + return filters; + }); + + var putStream = (API.__private__.putStream = function(options) { + options = options || {}; + var data = options.data || ""; + var filters = options.filters || getFilters(); + var alreadyAppliedFilters = options.alreadyAppliedFilters || []; + var addLength1 = options.addLength1 || false; + var valueOfLength1 = data.length; + var objectId = options.objectId; + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null && typeof objectId == "undefined") { + throw new Error( + "ObjectId must be passed to putStream for file encryption" + ); + } + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } + + var processedData = {}; + if (filters === true) { + filters = ["FlateEncode"]; + } + var keyValues = options.additionalKeyValues || []; + if (typeof jsPDF.API.processDataByFilters !== "undefined") { + processedData = jsPDF.API.processDataByFilters(data, filters); + } else { + processedData = { data: data, reverseChain: [] }; + } + var filterAsString = + processedData.reverseChain + + (Array.isArray(alreadyAppliedFilters) + ? alreadyAppliedFilters.join(" ") + : alreadyAppliedFilters.toString()); + + if (processedData.data.length !== 0) { + keyValues.push({ + key: "Length", + value: processedData.data.length + }); + if (addLength1 === true) { + keyValues.push({ + key: "Length1", + value: valueOfLength1 + }); + } + } + + if (filterAsString.length != 0) { + if (filterAsString.split("/").length - 1 === 1) { + keyValues.push({ + key: "Filter", + value: filterAsString + }); + } else { + keyValues.push({ + key: "Filter", + value: "[" + filterAsString + "]" + }); + + for (var j = 0; j < keyValues.length; j += 1) { + if (keyValues[j].key === "DecodeParms") { + var decodeParmsArray = []; + + for ( + var i = 0; + i < processedData.reverseChain.split("/").length - 1; + i += 1 + ) { + decodeParmsArray.push("null"); + } + + decodeParmsArray.push(keyValues[j].value); + keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]"; + } + } + } + } + + out("<<"); + for (var k = 0; k < keyValues.length; k++) { + out("/" + keyValues[k].key + " " + keyValues[k].value); + } + out(">>"); + if (processedData.data.length !== 0) { + out("stream"); + out(encryptor(processedData.data)); + out("endstream"); + } + }); + + var putPage = (API.__private__.putPage = function(page) { + var pageNumber = page.number; + var data = page.data; + var pageObjectNumber = page.objId; + var pageContentsObjId = page.contentsObjId; + + newObjectDeferredBegin(pageObjectNumber, true); + out("<>"); + out("endobj"); + // Page content + var pageContent = data.join("\n"); + + if (apiMode === ApiMode.ADVANCED) { + // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again + pageContent += "\nQ"; + } + + newObjectDeferredBegin(pageContentsObjId, true); + putStream({ + data: pageContent, + filters: getFilters(), + objectId: pageContentsObjId + }); + out("endobj"); + return pageObjectNumber; + }); + + var putPages = (API.__private__.putPages = function() { + var n, + i, + pageObjectNumbers = []; + + for (n = 1; n <= page; n++) { + pagesContext[n].objId = newObjectDeferred(); + pagesContext[n].contentsObjId = newObjectDeferred(); + } + + for (n = 1; n <= page; n++) { + pageObjectNumbers.push( + putPage({ + number: n, + data: pages[n], + objId: pagesContext[n].objId, + contentsObjId: pagesContext[n].contentsObjId, + mediaBox: pagesContext[n].mediaBox, + cropBox: pagesContext[n].cropBox, + bleedBox: pagesContext[n].bleedBox, + trimBox: pagesContext[n].trimBox, + artBox: pagesContext[n].artBox, + userUnit: pagesContext[n].userUnit, + rootDictionaryObjId: rootDictionaryObjId, + resourceDictionaryObjId: resourceDictionaryObjId + }) + ); + } + newObjectDeferredBegin(rootDictionaryObjId, true); + out("<>"); + out("endobj"); + events.publish("postPutPages"); + }); + + var putFont = function(font) { + events.publish("putFont", { + font: font, + out: out, + newObject: newObject, + putStream: putStream + }); + + if (font.isAlreadyPutted !== true) { + font.objectNumber = newObject(); + out("<<"); + out("/Type /Font"); + out("/BaseFont /" + toPDFName(font.postScriptName)); + out("/Subtype /Type1"); + if (typeof font.encoding === "string") { + out("/Encoding /" + font.encoding); + } + out("/FirstChar 32"); + out("/LastChar 255"); + out(">>"); + out("endobj"); + } + }; + + var putFonts = function() { + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + if ( + putOnlyUsedFonts === false || + (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) + ) { + putFont(fonts[fontKey]); + } + } + } + }; + + var putXObject = function(xObject) { + xObject.objectNumber = newObject(); + + var options = []; + options.push({ key: "Type", value: "/XObject" }); + options.push({ key: "Subtype", value: "/Form" }); + options.push({ + key: "BBox", + value: + "[" + + [ + hpf(xObject.x), + hpf(xObject.y), + hpf(xObject.x + xObject.width), + hpf(xObject.y + xObject.height) + ].join(" ") + + "]" + }); + options.push({ + key: "Matrix", + value: "[" + xObject.matrix.toString() + "]" + }); + // TODO: /Resources + + var stream = xObject.pages[1].join("\n"); + putStream({ + data: stream, + additionalKeyValues: options, + objectId: xObject.objectNumber + }); + out("endobj"); + }; + + var putXObjects = function() { + for (var xObjectKey in renderTargets) { + if (renderTargets.hasOwnProperty(xObjectKey)) { + putXObject(renderTargets[xObjectKey]); + } + } + }; + + var interpolateAndEncodeRGBStream = function(colors, numberSamples) { + var tValues = []; + var t; + var dT = 1.0 / (numberSamples - 1); + for (t = 0.0; t < 1.0; t += dT) { + tValues.push(t); + } + tValues.push(1.0); + // add first and last control point if not present + if (colors[0].offset != 0.0) { + var c0 = { + offset: 0.0, + color: colors[0].color + }; + colors.unshift(c0); + } + if (colors[colors.length - 1].offset != 1.0) { + var c1 = { + offset: 1.0, + color: colors[colors.length - 1].color + }; + colors.push(c1); + } + var out = ""; + var index = 0; + + for (var i = 0; i < tValues.length; i++) { + t = tValues[i]; + while (t > colors[index + 1].offset) index++; + var a = colors[index].offset; + var b = colors[index + 1].offset; + var d = (t - a) / (b - a); + + var aColor = colors[index].color; + var bColor = colors[index + 1].color; + + out += + padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) + + padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) + + padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16)); + } + return out.trim(); + }; + + var putShadingPattern = function(pattern, numberSamples) { + /* + Axial patterns shade between the two points specified in coords, radial patterns between the inner + and outer circle. + The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now + interpolated to equidistant samples and written to pdf as a sample (type 0) function. + */ + // The number of color samples that should be used to describe the shading. + // The higher, the more accurate the gradient will be. + numberSamples || (numberSamples = 21); + var funcObjectNumber = newObject(); + var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples); + + var options = []; + options.push({ key: "FunctionType", value: "0" }); + options.push({ key: "Domain", value: "[0.0 1.0]" }); + options.push({ key: "Size", value: "[" + numberSamples + "]" }); + options.push({ key: "BitsPerSample", value: "8" }); + options.push({ key: "Range", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); + options.push({ key: "Decode", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); + + putStream({ + data: stream, + additionalKeyValues: options, + alreadyAppliedFilters: ["/ASCIIHexDecode"], + objectId: funcObjectNumber + }); + out("endobj"); + + pattern.objectNumber = newObject(); + out("<< /ShadingType " + pattern.type); + out("/ColorSpace /DeviceRGB"); + var coords = + "/Coords [" + + hpf(parseFloat(pattern.coords[0])) + + " " + // x1 + hpf(parseFloat(pattern.coords[1])) + + " "; // y1 + if (pattern.type === 2) { + // axial + coords += + hpf(parseFloat(pattern.coords[2])) + + " " + // x2 + hpf(parseFloat(pattern.coords[3])); // y2 + } else { + // radial + coords += + hpf(parseFloat(pattern.coords[2])) + + " " + // r1 + hpf(parseFloat(pattern.coords[3])) + + " " + // x2 + hpf(parseFloat(pattern.coords[4])) + + " " + // y2 + hpf(parseFloat(pattern.coords[5])); // r2 + } + coords += "]"; + out(coords); + + if (pattern.matrix) { + out("/Matrix [" + pattern.matrix.toString() + "]"); + } + out("/Function " + funcObjectNumber + " 0 R"); + out("/Extend [true true]"); + out(">>"); + out("endobj"); + }; + + var putTilingPattern = function(pattern, deferredResourceDictionaryIds) { + var resourcesObjectId = newObjectDeferred(); + var patternObjectId = newObject(); + + deferredResourceDictionaryIds.push({ + resourcesOid: resourcesObjectId, + objectOid: patternObjectId + }); + + pattern.objectNumber = patternObjectId; + var options = []; + options.push({ key: "Type", value: "/Pattern" }); + options.push({ key: "PatternType", value: "1" }); // tiling pattern + options.push({ key: "PaintType", value: "1" }); // colored tiling pattern + options.push({ key: "TilingType", value: "1" }); // constant spacing + options.push({ + key: "BBox", + value: "[" + pattern.boundingBox.map(hpf).join(" ") + "]" + }); + options.push({ key: "XStep", value: hpf(pattern.xStep) }); + options.push({ key: "YStep", value: hpf(pattern.yStep) }); + options.push({ key: "Resources", value: resourcesObjectId + " 0 R" }); + if (pattern.matrix) { + options.push({ + key: "Matrix", + value: "[" + pattern.matrix.toString() + "]" + }); + } + + putStream({ + data: pattern.stream, + additionalKeyValues: options, + objectId: pattern.objectNumber + }); + out("endobj"); + }; + + var putPatterns = function(deferredResourceDictionaryIds) { + var patternKey; + for (patternKey in patterns) { + if (patterns.hasOwnProperty(patternKey)) { + if (patterns[patternKey] instanceof ShadingPattern) { + putShadingPattern(patterns[patternKey]); + } else if (patterns[patternKey] instanceof TilingPattern) { + putTilingPattern(patterns[patternKey], deferredResourceDictionaryIds); + } + } + } + }; + + var putGState = function(gState) { + gState.objectNumber = newObject(); + out("<<"); + for (var p in gState) { + switch (p) { + case "opacity": + out("/ca " + f2(gState[p])); + break; + case "stroke-opacity": + out("/CA " + f2(gState[p])); + break; + } + } + out(">>"); + out("endobj"); + }; + + var putGStates = function() { + var gStateKey; + for (gStateKey in gStates) { + if (gStates.hasOwnProperty(gStateKey)) { + putGState(gStates[gStateKey]); + } + } + }; + + var putXobjectDict = function() { + out("/XObject <<"); + for (var xObjectKey in renderTargets) { + if ( + renderTargets.hasOwnProperty(xObjectKey) && + renderTargets[xObjectKey].objectNumber >= 0 + ) { + out( + "/" + + xObjectKey + + " " + + renderTargets[xObjectKey].objectNumber + + " 0 R" + ); + } + } + + // Loop through images, or other data objects + events.publish("putXobjectDict"); + out(">>"); + }; + + var putEncryptionDict = function() { + encryption.oid = newObject(); + out("<<"); + out("/Filter /Standard"); + out("/V " + encryption.v); + out("/R " + encryption.r); + out("/U <" + encryption.toHexString(encryption.U) + ">"); + out("/O <" + encryption.toHexString(encryption.O) + ">"); + out("/P " + encryption.P); + out(">>"); + out("endobj"); + }; + + var putFontDict = function() { + out("/Font <<"); + + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + if ( + putOnlyUsedFonts === false || + (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) + ) { + out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R"); + } + } + } + out(">>"); + }; + + var putShadingPatternDict = function() { + if (Object.keys(patterns).length > 0) { + out("/Shading <<"); + for (var patternKey in patterns) { + if ( + patterns.hasOwnProperty(patternKey) && + patterns[patternKey] instanceof ShadingPattern && + patterns[patternKey].objectNumber >= 0 + ) { + out( + "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" + ); + } + } + + events.publish("putShadingPatternDict"); + out(">>"); + } + }; + + var putTilingPatternDict = function(objectOid) { + if (Object.keys(patterns).length > 0) { + out("/Pattern <<"); + for (var patternKey in patterns) { + if ( + patterns.hasOwnProperty(patternKey) && + patterns[patternKey] instanceof API.TilingPattern && + patterns[patternKey].objectNumber >= 0 && + patterns[patternKey].objectNumber < objectOid // prevent cyclic dependencies + ) { + out( + "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" + ); + } + } + events.publish("putTilingPatternDict"); + out(">>"); + } + }; + + var putGStatesDict = function() { + if (Object.keys(gStates).length > 0) { + var gStateKey; + out("/ExtGState <<"); + for (gStateKey in gStates) { + if ( + gStates.hasOwnProperty(gStateKey) && + gStates[gStateKey].objectNumber >= 0 + ) { + out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R"); + } + } + + events.publish("putGStateDict"); + out(">>"); + } + }; + + var putResourceDictionary = function(objectIds) { + newObjectDeferredBegin(objectIds.resourcesOid, true); + out("<<"); + out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"); + putFontDict(); + putShadingPatternDict(); + putTilingPatternDict(objectIds.objectOid); + putGStatesDict(); + putXobjectDict(); + out(">>"); + out("endobj"); + }; + + var putResources = function() { + // FormObjects, Patterns etc. might use other FormObjects/Patterns/Images + // which means their resource dictionaries must contain the already resolved + // object ids. For this reason we defer the serialization of the resource + // dicts until all objects have been serialized and have object ids. + // + // In order to prevent cyclic dependencies (which Adobe Reader doesn't like), + // we only put all oids that are smaller than the oid of the object the + // resource dict belongs to. This is correct behavior, since the streams + // may only use other objects that have already been defined and thus appear + // earlier in their respective collection. + // Currently, this only affects tiling patterns, but a (more) correct + // implementation of FormObjects would also define their own resource dicts. + var deferredResourceDictionaryIds = []; + + putFonts(); + putGStates(); + putXObjects(); + putPatterns(deferredResourceDictionaryIds); + + events.publish("putResources"); + deferredResourceDictionaryIds.forEach(putResourceDictionary); + putResourceDictionary({ + resourcesOid: resourceDictionaryObjId, + objectOid: Number.MAX_SAFE_INTEGER // output all objects + }); + events.publish("postPutResources"); + }; + + var putAdditionalObjects = function() { + events.publish("putAdditionalObjects"); + for (var i = 0; i < additionalObjects.length; i++) { + var obj = additionalObjects[i]; + newObjectDeferredBegin(obj.objId, true); + out(obj.content); + out("endobj"); + } + events.publish("postPutAdditionalObjects"); + }; + + var addFontToFontDictionary = function(font) { + fontmap[font.fontName] = fontmap[font.fontName] || {}; + fontmap[font.fontName][font.fontStyle] = font.id; + }; + + var addFont = function( + postScriptName, + fontName, + fontStyle, + encoding, + isStandardFont + ) { + var font = { + id: "F" + (Object.keys(fonts).length + 1).toString(10), + postScriptName: postScriptName, + fontName: fontName, + fontStyle: fontStyle, + encoding: encoding, + isStandardFont: isStandardFont || false, + metadata: {} + }; + + events.publish("addFont", { + font: font, + instance: this + }); + + fonts[font.id] = font; + addFontToFontDictionary(font); + return font.id; + }; + + var addFonts = function(arrayOfFonts) { + for (var i = 0, l = standardFonts.length; i < l; i++) { + var fontKey = addFont.call( + this, + arrayOfFonts[i][0], + arrayOfFonts[i][1], + arrayOfFonts[i][2], + standardFonts[i][3], + true + ); + + if (putOnlyUsedFonts === false) { + usedFonts[fontKey] = true; + } + // adding aliases for standard fonts, this time matching the capitalization + var parts = arrayOfFonts[i][0].split("-"); + addFontToFontDictionary({ + id: fontKey, + fontName: parts[0], + fontStyle: parts[1] || "" + }); + } + events.publish("addFonts", { + fonts: fonts, + dictionary: fontmap + }); + }; + + var SAFE = function __safeCall(fn) { + fn.foo = function __safeCallWrapper() { + try { + return fn.apply(this, arguments); + } catch (e) { + var stack = e.stack || ""; + if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1]; + var m = + "Error in function " + + stack.split("\n")[0].split("<")[0] + + ": " + + e.message; + if (globalObject.console) { + globalObject.console.error(m, e); + if (globalObject.alert) alert(m); + } else { + throw new Error(m); + } + } + }; + fn.foo.bar = fn; + return fn.foo; + }; + + var to8bitStream = function(text, flags) { + /** + * PDF 1.3 spec: + * "For text strings encoded in Unicode, the first two bytes must be 254 followed by + * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts + * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely + * to be a meaningful beginning of a word or phrase.) The remainder of the + * string consists of Unicode character codes, according to the UTF-16 encoding + * specified in the Unicode standard, version 2.0. Commonly used Unicode values + * are represented as 2 bytes per character, with the high-order byte appearing first + * in the string." + * + * In other words, if there are chars in a string with char code above 255, we + * recode the string to UCS2 BE - string doubles in length and BOM is prepended. + * + * HOWEVER! + * Actual *content* (body) text (as opposed to strings used in document properties etc) + * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) + * + * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have + * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could + * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode + * code page. There, however, all characters in the stream are treated as GIDs, + * including BOM, which is the reason we need to skip BOM in content text (i.e. that + * that is tied to a font). + * + * To signal this "special" PDFEscape / to8bitStream handling mode, + * API.text() function sets (unless you overwrite it with manual values + * given to API.text(.., flags) ) + * flags.autoencode = true + * flags.noBOM = true + * + * =================================================================================== + * `flags` properties relied upon: + * .sourceEncoding = string with encoding label. + * "Unicode" by default. = encoding of the incoming text. + * pass some non-existing encoding name + * (ex: 'Do not touch my strings! I know what I am doing.') + * to make encoding code skip the encoding step. + * .outputEncoding = Either valid PDF encoding name + * (must be supported by jsPDF font metrics, otherwise no encoding) + * or a JS object, where key = sourceCharCode, value = outputCharCode + * missing keys will be treated as: sourceCharCode === outputCharCode + * .noBOM + * See comment higher above for explanation for why this is important + * .autoencode + * See comment higher above for explanation for why this is important + */ + + var i, + l, + sourceEncoding, + encodingBlock, + outputEncoding, + newtext, + isUnicode, + ch, + bch; + + flags = flags || {}; + sourceEncoding = flags.sourceEncoding || "Unicode"; + outputEncoding = flags.outputEncoding; + + // This 'encoding' section relies on font metrics format + // attached to font objects by, among others, + // "Willow Systems' standard_font_metrics plugin" + // see jspdf.plugin.standard_font_metrics.js for format + // of the font.metadata.encoding Object. + // It should be something like + // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} + // .widths = {0:width, code:width, ..., 'fof':divisor} + // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} + if ( + (flags.autoencode || outputEncoding) && + fonts[activeFontKey].metadata && + fonts[activeFontKey].metadata[sourceEncoding] && + fonts[activeFontKey].metadata[sourceEncoding].encoding + ) { + encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; + + // each font has default encoding. Some have it clearly defined. + if (!outputEncoding && fonts[activeFontKey].encoding) { + outputEncoding = fonts[activeFontKey].encoding; + } + + // Hmmm, the above did not work? Let's try again, in different place. + if (!outputEncoding && encodingBlock.codePages) { + outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default + } + + if (typeof outputEncoding === "string") { + outputEncoding = encodingBlock[outputEncoding]; + } + // we want output encoding to be a JS Object, where + // key = sourceEncoding's character code and + // value = outputEncoding's character code. + if (outputEncoding) { + isUnicode = false; + newtext = []; + for (i = 0, l = text.length; i < l; i++) { + ch = outputEncoding[text.charCodeAt(i)]; + if (ch) { + newtext.push(String.fromCharCode(ch)); + } else { + newtext.push(text[i]); + } + + // since we are looping over chars anyway, might as well + // check for residual unicodeness + if (newtext[i].charCodeAt(0) >> 8) { + /* more than 255 */ + isUnicode = true; + } + } + text = newtext.join(""); + } + } + + i = text.length; + // isUnicode may be set to false above. Hence the triple-equal to undefined + while (isUnicode === undefined && i !== 0) { + if (text.charCodeAt(i - 1) >> 8) { + /* more than 255 */ + isUnicode = true; + } + i--; + } + if (!isUnicode) { + return text; + } + + newtext = flags.noBOM ? [] : [254, 255]; + for (i = 0, l = text.length; i < l; i++) { + ch = text.charCodeAt(i); + bch = ch >> 8; // divide by 256 + if (bch >> 8) { + /* something left after dividing by 256 second time */ + throw new Error( + "Character at position " + + i + + " of string '" + + text + + "' exceeds 16bits. Cannot be encoded into UCS-2 BE" + ); + } + newtext.push(bch); + newtext.push(ch - (bch << 8)); + } + return String.fromCharCode.apply(undefined, newtext); + }; + + var pdfEscape = (API.__private__.pdfEscape = API.pdfEscape = function( + text, + flags + ) { + /** + * Replace '/', '(', and ')' with pdf-safe versions + * + * Doing to8bitStream does NOT make this PDF display unicode text. For that + * we also need to reference a unicode font and embed it - royal pain in the rear. + * + * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, + * which JavaScript Strings are happy to provide. So, while we still cannot display + * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) + * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF + * is still parseable. + * This will allow immediate support for unicode in document properties strings. + */ + return to8bitStream(text, flags) + .replace(/\\/g, "\\\\") + .replace(/\(/g, "\\(") + .replace(/\)/g, "\\)"); + }); + + var beginPage = (API.__private__.beginPage = function(format) { + pages[++page] = []; + pagesContext[page] = { + objId: 0, + contentsObjId: 0, + userUnit: Number(userUnit), + artBox: null, + bleedBox: null, + cropBox: null, + trimBox: null, + mediaBox: { + bottomLeftX: 0, + bottomLeftY: 0, + topRightX: Number(format[0]), + topRightY: Number(format[1]) + } + }; + _setPage(page); + setOutputDestination(pages[currentPage]); + }); + + var _addPage = function(parmFormat, parmOrientation) { + var dimensions, width, height; + + orientation = parmOrientation || orientation; + + if (typeof parmFormat === "string") { + dimensions = getPageFormat(parmFormat.toLowerCase()); + if (Array.isArray(dimensions)) { + width = dimensions[0]; + height = dimensions[1]; + } + } + + if (Array.isArray(parmFormat)) { + width = parmFormat[0] * scaleFactor; + height = parmFormat[1] * scaleFactor; + } + + if (isNaN(width)) { + width = format[0]; + height = format[1]; + } + + if (width > 14400 || height > 14400) { + console.warn( + "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400" + ); + width = Math.min(14400, width); + height = Math.min(14400, height); + } + + format = [width, height]; + + switch (orientation.substr(0, 1)) { + case "l": + if (height > width) { + format = [height, width]; + } + break; + case "p": + if (width > height) { + format = [height, width]; + } + break; + } + + beginPage(format); + + // Set line width + setLineWidth(lineWidth); + // Set draw color + out(strokeColor); + // resurrecting non-default line caps, joins + if (lineCapID !== 0) { + out(lineCapID + " J"); + } + if (lineJoinID !== 0) { + out(lineJoinID + " j"); + } + events.publish("addPage", { + pageNumber: page + }); + }; + + var _deletePage = function(n) { + if (n > 0 && n <= page) { + pages.splice(n, 1); + pagesContext.splice(n, 1); + page--; + if (currentPage > page) { + currentPage = page; + } + this.setPage(currentPage); + } + }; + + var _setPage = function(n) { + if (n > 0 && n <= page) { + currentPage = n; + } + }; + + var getNumberOfPages = (API.__private__.getNumberOfPages = API.getNumberOfPages = function() { + return pages.length - 1; + }); + + /** + * Returns a document-specific font key - a label assigned to a + * font name + font type combination at the time the font was added + * to the font inventory. + * + * Font key is used as label for the desired font for a block of text + * to be added to the PDF document stream. + * @private + * @function + * @param fontName {string} can be undefined on "falthy" to indicate "use current" + * @param fontStyle {string} can be undefined on "falthy" to indicate "use current" + * @returns {string} Font key. + * @ignore + */ + var getFont = function(fontName, fontStyle, options) { + var key = undefined, + fontNameLowerCase; + options = options || {}; + + fontName = + fontName !== undefined ? fontName : fonts[activeFontKey].fontName; + fontStyle = + fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; + fontNameLowerCase = fontName.toLowerCase(); + + if ( + fontmap[fontNameLowerCase] !== undefined && + fontmap[fontNameLowerCase][fontStyle] !== undefined + ) { + key = fontmap[fontNameLowerCase][fontStyle]; + } else if ( + fontmap[fontName] !== undefined && + fontmap[fontName][fontStyle] !== undefined + ) { + key = fontmap[fontName][fontStyle]; + } else { + if (options.disableWarning === false) { + console.warn( + "Unable to look up font label for font '" + + fontName + + "', '" + + fontStyle + + "'. Refer to getFontList() for available fonts." + ); + } + } + + if (!key && !options.noFallback) { + key = fontmap["times"][fontStyle]; + if (key == null) { + key = fontmap["times"]["normal"]; + } + } + return key; + }; + + var putInfo = (API.__private__.putInfo = function() { + var objectId = newObject(); + var encryptor = function(data) { + return data; + }; + if (encryptionOptions !== null) { + encryptor = encryption.encryptor(objectId, 0); + } + out("<<"); + out("/Producer (" + pdfEscape(encryptor("jsPDF " + jsPDF.version)) + ")"); + for (var key in documentProperties) { + if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { + out( + "/" + + key.substr(0, 1).toUpperCase() + + key.substr(1) + + " (" + + pdfEscape(encryptor(documentProperties[key])) + + ")" + ); + } + } + out("/CreationDate (" + pdfEscape(encryptor(creationDate)) + ")"); + out(">>"); + out("endobj"); + }); + + var putCatalog = (API.__private__.putCatalog = function(options) { + options = options || {}; + var tmpRootDictionaryObjId = + options.rootDictionaryObjId || rootDictionaryObjId; + newObject(); + out("<<"); + out("/Type /Catalog"); + out("/Pages " + tmpRootDictionaryObjId + " 0 R"); + // PDF13ref Section 7.2.1 + if (!zoomMode) zoomMode = "fullwidth"; + switch (zoomMode) { + case "fullwidth": + out("/OpenAction [3 0 R /FitH null]"); + break; + case "fullheight": + out("/OpenAction [3 0 R /FitV null]"); + break; + case "fullpage": + out("/OpenAction [3 0 R /Fit]"); + break; + case "original": + out("/OpenAction [3 0 R /XYZ null null 1]"); + break; + default: + var pcn = "" + zoomMode; + if (pcn.substr(pcn.length - 1) === "%") + zoomMode = parseInt(zoomMode) / 100; + if (typeof zoomMode === "number") { + out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]"); + } + } + if (!layoutMode) layoutMode = "continuous"; + switch (layoutMode) { + case "continuous": + out("/PageLayout /OneColumn"); + break; + case "single": + out("/PageLayout /SinglePage"); + break; + case "two": + case "twoleft": + out("/PageLayout /TwoColumnLeft"); + break; + case "tworight": + out("/PageLayout /TwoColumnRight"); + break; + } + if (pageMode) { + /** + * A name object specifying how the document should be displayed when opened: + * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT + * UseOutlines : Document outline visible + * UseThumbs : Thumbnail images visible + * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible + */ + out("/PageMode /" + pageMode); + } + events.publish("putCatalog"); + out(">>"); + out("endobj"); + }); + + var putTrailer = (API.__private__.putTrailer = function() { + out("trailer"); + out("<<"); + out("/Size " + (objectNumber + 1)); + // Root and Info must be the last and second last objects written respectively + out("/Root " + objectNumber + " 0 R"); + out("/Info " + (objectNumber - 1) + " 0 R"); + if (encryptionOptions !== null) { + out("/Encrypt " + encryption.oid + " 0 R"); + } + out("/ID [ <" + fileId + "> <" + fileId + "> ]"); + out(">>"); + }); + + var putHeader = (API.__private__.putHeader = function() { + out("%PDF-" + pdfVersion); + out("%\xBA\xDF\xAC\xE0"); + }); + + var putXRef = (API.__private__.putXRef = function() { + var p = "0000000000"; + + out("xref"); + out("0 " + (objectNumber + 1)); + out("0000000000 65535 f "); + for (var i = 1; i <= objectNumber; i++) { + var offset = offsets[i]; + if (typeof offset === "function") { + out((p + offsets[i]()).slice(-10) + " 00000 n "); + } else { + if (typeof offsets[i] !== "undefined") { + out((p + offsets[i]).slice(-10) + " 00000 n "); + } else { + out("0000000000 00000 n "); + } + } + } + }); + + var buildDocument = (API.__private__.buildDocument = function() { + resetDocument(); + setOutputDestination(content); + + events.publish("buildDocument"); + + putHeader(); + putPages(); + putAdditionalObjects(); + putResources(); + if (encryptionOptions !== null) putEncryptionDict(); + putInfo(); + putCatalog(); + + var offsetOfXRef = contentLength; + putXRef(); + putTrailer(); + out("startxref"); + out("" + offsetOfXRef); + out("%%EOF"); + + setOutputDestination(pages[currentPage]); + + return content.join("\n"); + }); + + var getBlob = (API.__private__.getBlob = function(data) { + return new Blob([getArrayBuffer(data)], { + type: "application/pdf" + }); + }); + + /** + * Generates the PDF document. + * + * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. + * + * @param {string} type A string identifying one of the possible output types.
    + * Possible values are:
    + * 'arraybuffer' -> (ArrayBuffer)
    + * 'blob' -> (Blob)
    + * 'bloburi'/'bloburl' -> (string)
    + * 'datauristring'/'dataurlstring' -> (string)
    + * 'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring
    + * @param {Object|string} options An object providing some additional signalling to PDF generator.
    + * Possible options are 'filename'.
    + * A string can be passed instead of {filename:string} and defaults to 'generated.pdf' + * @function + * @instance + * @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined} + * @memberof jsPDF# + * @name output + */ + var output = (API.output = API.__private__.output = SAFE(function output( + type, + options + ) { + options = options || {}; + + if (typeof options === "string") { + options = { + filename: options + }; + } else { + options.filename = options.filename || "generated.pdf"; + } + + switch (type) { + case undefined: + return buildDocument(); + case "save": + API.save(options.filename); + break; + case "arraybuffer": + return getArrayBuffer(buildDocument()); + case "blob": + return getBlob(buildDocument()); + case "bloburi": + case "bloburl": + // Developer is responsible of calling revokeObjectURL + if ( + typeof globalObject.URL !== "undefined" && + typeof globalObject.URL.createObjectURL === "function" + ) { + return ( + (globalObject.URL && + globalObject.URL.createObjectURL(getBlob(buildDocument()))) || + void 0 + ); + } else { + console.warn( + "bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser." + ); + } + break; + case "datauristring": + case "dataurlstring": + var dataURI = ""; + var pdfDocument = buildDocument(); + try { + dataURI = btoa(pdfDocument); + } catch (e) { + dataURI = btoa(unescape(encodeURIComponent(pdfDocument))); + } + return ( + "data:application/pdf;filename=" + + options.filename + + ";base64," + + dataURI + ); + case "datauri": + case "dataurl": + return (globalObject.document.location.href = this.output( + "datauristring", + options + )); + default: + return null; + } + })); + + /** + * Used to see if a supplied hotfix was requested when the pdf instance was created. + * @param {string} hotfixName - The name of the hotfix to check. + * @returns {boolean} + */ + var hasHotfix = function(hotfixName) { + return ( + Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1 + ); + }; + + switch (unit) { + case "pt": + scaleFactor = 1; + break; + case "mm": + scaleFactor = 72 / 25.4; + break; + case "cm": + scaleFactor = 72 / 2.54; + break; + case "in": + scaleFactor = 72; + break; + case "px": + if (hasHotfix("px_scaling") == true) { + scaleFactor = 72 / 96; + } else { + scaleFactor = 96 / 72; + } + break; + case "pc": + scaleFactor = 12; + break; + case "em": + scaleFactor = 12; + break; + case "ex": + scaleFactor = 6; + break; + default: + if (typeof unit === "number") { + scaleFactor = unit; + } else { + throw new Error("Invalid unit: " + unit); + } + } + + var encryption = null; + setCreationDate(); + setFileId(); + + var getEncryptor = function(objectId) { + if (encryptionOptions !== null) { + return encryption.encryptor(objectId, 0); + } + return function(data) { + return data; + }; + }; + + //--------------------------------------- + // Public API + + var getPageInfo = (API.__private__.getPageInfo = API.getPageInfo = function( + pageNumberOneBased + ) { + if (isNaN(pageNumberOneBased) || pageNumberOneBased % 1 !== 0) { + throw new Error("Invalid argument passed to jsPDF.getPageInfo"); + } + var objId = pagesContext[pageNumberOneBased].objId; + return { + objId: objId, + pageNumber: pageNumberOneBased, + pageContext: pagesContext[pageNumberOneBased] + }; + }); + + var getPageInfoByObjId = (API.__private__.getPageInfoByObjId = function( + objId + ) { + if (isNaN(objId) || objId % 1 !== 0) { + throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId"); + } + for (var pageNumber in pagesContext) { + if (pagesContext[pageNumber].objId === objId) { + break; + } + } + return getPageInfo(pageNumber); + }); + + var getCurrentPageInfo = (API.__private__.getCurrentPageInfo = API.getCurrentPageInfo = function() { + return { + objId: pagesContext[currentPage].objId, + pageNumber: currentPage, + pageContext: pagesContext[currentPage] + }; + }); + + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @param format {String/Array} The format of the new page. Can be:
    • a0 - a10
    • b0 - b10
    • c0 - c10
    • dl
    • letter
    • government-letter
    • legal
    • junior-legal
    • ledger
    • tabloid
    • credit-card

    + * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] + * @param orientation {string} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l"). + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name addPage + */ + API.addPage = function() { + _addPage.apply(this, arguments); + return this; + }; + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name setPage + * @param {number} page Switch the active page to the page number specified (indexed starting at 1). + * @example + * doc = jsPDF() + * doc.addPage() + * doc.addPage() + * doc.text('I am on page 3', 10, 10) + * doc.setPage(1) + * doc.text('I am on page 1', 10, 10) + */ + API.setPage = function() { + _setPage.apply(this, arguments); + setOutputDestination.call(this, pages[currentPage]); + return this; + }; + + /** + * @name insertPage + * @memberof jsPDF# + * + * @function + * @instance + * @param {Object} beforePage + * @returns {jsPDF} + */ + API.insertPage = function(beforePage) { + this.addPage(); + this.movePage(currentPage, beforePage); + return this; + }; + + /** + * @name movePage + * @memberof jsPDF# + * @function + * @instance + * @param {number} targetPage + * @param {number} beforePage + * @returns {jsPDF} + */ + API.movePage = function(targetPage, beforePage) { + var tmpPages, tmpPagesContext; + if (targetPage > beforePage) { + tmpPages = pages[targetPage]; + tmpPagesContext = pagesContext[targetPage]; + for (var i = targetPage; i > beforePage; i--) { + pages[i] = pages[i - 1]; + pagesContext[i] = pagesContext[i - 1]; + } + pages[beforePage] = tmpPages; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } else if (targetPage < beforePage) { + tmpPages = pages[targetPage]; + tmpPagesContext = pagesContext[targetPage]; + for (var j = targetPage; j < beforePage; j++) { + pages[j] = pages[j + 1]; + pagesContext[j] = pagesContext[j + 1]; + } + pages[beforePage] = tmpPages; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } + return this; + }; + + /** + * Deletes a page from the PDF. + * @name deletePage + * @memberof jsPDF# + * @function + * @param {number} targetPage + * @instance + * @returns {jsPDF} + */ + API.deletePage = function() { + _deletePage.apply(this, arguments); + return this; + }; + + /** + * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. + * + * @function + * @instance + * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page. + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page. + * @param {Object} [options] - Collection of settings signaling how the text must be encoded. + * @param {string} [options.align=left] - The alignment of the text, possible values: left, center, right, justify. + * @param {string} [options.baseline=alphabetic] - Sets text baseline used when drawing the text, possible values: alphabetic, ideographic, bottom, top, middle, hanging + * @param {number|Matrix} [options.angle=0] - Rotate the text clockwise or counterclockwise. Expects the angle in degree. + * @param {number} [options.rotationDirection=1] - Direction of the rotation. 0 = clockwise, 1 = counterclockwise. + * @param {number} [options.charSpace=0] - The space between each letter. + * @param {number} [options.horizontalScale=1] - Horizontal scale of the text as a factor of the regular size. + * @param {number} [options.lineHeightFactor=1.15] - The lineheight of each line. + * @param {Object} [options.flags] - Flags for to8bitStream. + * @param {boolean} [options.flags.noBOM=true] - Don't add BOM to Unicode-text. + * @param {boolean} [options.flags.autoencode=true] - Autoencode the Text. + * @param {number} [options.maxWidth=0] - Split the text by given width, 0 = no split. + * @param {string} [options.renderingMode=fill] - Set how the text should be rendered, possible values: fill, stroke, fillThenStroke, invisible, fillAndAddForClipping, strokeAndAddPathForClipping, fillThenStrokeAndAddToPathForClipping, addToPathForClipping. + * @param {boolean} [options.isInputVisual] - Option for the BidiEngine + * @param {boolean} [options.isOutputVisual] - Option for the BidiEngine + * @param {boolean} [options.isInputRtl] - Option for the BidiEngine + * @param {boolean} [options.isOutputRtl] - Option for the BidiEngine + * @param {boolean} [options.isSymmetricSwapping] - Option for the BidiEngine + * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the anchor set by x and y. + * + * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing + * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this + * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a + * DIFFERENT result. A matrix is only allowed in "advanced" API mode. + * @returns {jsPDF} + * @memberof jsPDF# + * @name text + */ + API.__private__.text = API.text = function(text, x, y, options, transform) { + /* + * Inserts something like this into PDF + * BT + * /F1 16 Tf % Font name + size + * 16 TL % How many units down for next line in multiline text + * 0 g % color + * 28.35 813.54 Td % position + * (line one) Tj + * T* (line two) Tj + * T* (line three) Tj + * ET + */ + options = options || {}; + var scope = options.scope || this; + var payload, da, angle, align, charSpace, maxWidth, flags, horizontalScale; + + // Pre-August-2012 the order of arguments was function(x, y, text, flags) + // in effort to make all calls have similar signature like + // function(data, coordinates... , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if ( + typeof text === "number" && + typeof x === "number" && + (typeof y === "string" || Array.isArray(y)) + ) { + var tmp = y; + y = x; + x = text; + text = tmp; + } + + var transformationMatrix; + + if (arguments[3] instanceof Matrix === false) { + flags = arguments[3]; + angle = arguments[4]; + align = arguments[5]; + + if (typeof flags !== "object" || flags === null) { + if (typeof angle === "string") { + align = angle; + angle = null; + } + if (typeof flags === "string") { + align = flags; + flags = null; + } + if (typeof flags === "number") { + angle = flags; + flags = null; + } + options = { + flags: flags, + angle: angle, + align: align + }; + } + } else { + advancedApiModeTrap( + "The transform parameter of text() with a Matrix value" + ); + transformationMatrix = transform; + } + + if (isNaN(x) || isNaN(y) || typeof text === "undefined" || text === null) { + throw new Error("Invalid arguments passed to jsPDF.text"); + } + + if (text.length === 0) { + return scope; + } + + var xtra = ""; + var isHex = false; + var lineHeight = + typeof options.lineHeightFactor === "number" + ? options.lineHeightFactor + : lineHeightFactor; + var scaleFactor = scope.internal.scaleFactor; + + function ESC(s) { + s = s.split("\t").join(Array(options.TabLen || 9).join(" ")); + return pdfEscape(s, flags); + } + + function transformTextToSpecialArray(text) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(curDa); + } else { + if ( + Array.isArray(text) && + (curDa.length === 1 || + (curDa[1] === undefined && curDa[2] === undefined)) + ) { + da.push(curDa[0]); + } else { + da.push([curDa[0], curDa[1], curDa[2]]); + } + } + } + return da; + } + + function processTextByFunction(text, processingFunction) { + var result; + if (typeof text === "string") { + result = processingFunction(text)[0]; + } else if (Array.isArray(text)) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + var tmpResult; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(processingFunction(curDa)[0]); + } else if (Array.isArray(curDa) && typeof curDa[0] === "string") { + tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]); + da.push([tmpResult[0], tmpResult[1], tmpResult[2]]); + } + } + result = da; + } + return result; + } + + //Check if text is of type String + var textIsOfTypeString = false; + var tmpTextIsOfTypeString = true; + + if (typeof text === "string") { + textIsOfTypeString = true; + } else if (Array.isArray(text)) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if ( + typeof curDa !== "string" || + (Array.isArray(curDa) && typeof curDa[0] !== "string") + ) { + tmpTextIsOfTypeString = false; + } + } + textIsOfTypeString = tmpTextIsOfTypeString; + } + if (textIsOfTypeString === false) { + throw new Error( + 'Type of text must be string or Array. "' + + text + + '" is not recognized.' + ); + } + + //If there are any newlines in text, we assume + //the user wanted to print multiple lines, so break the + //text up into an array. If the text is already an array, + //we assume the user knows what they are doing. + //Convert text into an array anyway to simplify + //later code. + + if (typeof text === "string") { + if (text.match(/[\r?\n]/)) { + text = text.split(/\r\n|\r|\n/g); + } else { + text = [text]; + } + } + + //baseline + var height = activeFontSize / scope.internal.scaleFactor; + var descent = height * (lineHeight - 1); + + switch (options.baseline) { + case "bottom": + y -= descent; + break; + case "top": + y += height - descent; + break; + case "hanging": + y += height - 2 * descent; + break; + case "middle": + y += height / 2 - descent; + break; + } + + //multiline + maxWidth = options.maxWidth || 0; + + if (maxWidth > 0) { + if (typeof text === "string") { + text = scope.splitTextToSize(text, maxWidth); + } else if (Object.prototype.toString.call(text) === "[object Array]") { + text = text.reduce(function(acc, textLine) { + return acc.concat(scope.splitTextToSize(textLine, maxWidth)); + }, []); + } + } + + //creating Payload-Object to make text byRef + payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize + } + }; + events.publish("preProcessText", payload); + + text = payload.text; + options = payload.options; + + //angle + angle = options.angle; + + if ( + transformationMatrix instanceof Matrix === false && + angle && + typeof angle === "number" + ) { + angle *= Math.PI / 180; + + if (options.rotationDirection === 0) { + angle = -angle; + } + + if (apiMode === ApiMode.ADVANCED) { + angle = -angle; + } + + var c = Math.cos(angle); + var s = Math.sin(angle); + transformationMatrix = new Matrix(c, s, -s, c, 0, 0); + } else if (angle && angle instanceof Matrix) { + transformationMatrix = angle; + } + + if (apiMode === ApiMode.ADVANCED && !transformationMatrix) { + transformationMatrix = identityMatrix; + } + + //charSpace + + charSpace = options.charSpace || activeCharSpace; + + if (typeof charSpace !== "undefined") { + xtra += hpf(scale(charSpace)) + " Tc\n"; + this.setCharSpace(this.getCharSpace() || 0); + } + + horizontalScale = options.horizontalScale; + if (typeof horizontalScale !== "undefined") { + xtra += hpf(horizontalScale * 100) + " Tz\n"; + } + + //lang + + options.lang; + + //renderingMode + var renderingMode = -1; + var parmRenderingMode = + typeof options.renderingMode !== "undefined" + ? options.renderingMode + : options.stroke; + var pageContext = scope.internal.getCurrentPageInfo().pageContext; + + switch (parmRenderingMode) { + case 0: + case false: + case "fill": + renderingMode = 0; + break; + case 1: + case true: + case "stroke": + renderingMode = 1; + break; + case 2: + case "fillThenStroke": + renderingMode = 2; + break; + case 3: + case "invisible": + renderingMode = 3; + break; + case 4: + case "fillAndAddForClipping": + renderingMode = 4; + break; + case 5: + case "strokeAndAddPathForClipping": + renderingMode = 5; + break; + case 6: + case "fillThenStrokeAndAddToPathForClipping": + renderingMode = 6; + break; + case 7: + case "addToPathForClipping": + renderingMode = 7; + break; + } + + var usedRenderingMode = + typeof pageContext.usedRenderingMode !== "undefined" + ? pageContext.usedRenderingMode + : -1; + + //if the coder wrote it explicitly to use a specific + //renderingMode, then use it + if (renderingMode !== -1) { + xtra += renderingMode + " Tr\n"; + //otherwise check if we used the rendering Mode already + //if so then set the rendering Mode... + } else if (usedRenderingMode !== -1) { + xtra += "0 Tr\n"; + } + + if (renderingMode !== -1) { + pageContext.usedRenderingMode = renderingMode; + } + + //align + align = options.align || "left"; + var leading = activeFontSize * lineHeight; + var pageWidth = scope.internal.pageSize.getWidth(); + var activeFont = fonts[activeFontKey]; + charSpace = options.charSpace || activeCharSpace; + maxWidth = options.maxWidth || 0; + + var lineWidths; + flags = Object.assign({ autoencode: true, noBOM: true }, options.flags); + + var wordSpacingPerLine = []; + var findWidth = function(v) { + return ( + (scope.getStringUnitWidth(v, { + font: activeFont, + charSpace: charSpace, + fontSize: activeFontSize, + doKerning: false + }) * + activeFontSize) / + scaleFactor + ); + }; + if (Object.prototype.toString.call(text) === "[object Array]") { + da = transformTextToSpecialArray(text); + var newY; + if (align !== "left") { + lineWidths = da.map(findWidth); + } + //The first line uses the "main" Td setting, + //and the subsequent lines are offset by the + //previous line's x coordinate. + var prevWidth = 0; + var newX; + if (align === "right") { + //The passed in x coordinate defines the + //rightmost point of the text. + x -= lineWidths[0]; + text = []; + len = da.length; + for (var i = 0; i < len; i++) { + if (i === 0) { + newX = getHorizontalCoordinate(x); + newY = getVerticalCoordinate(y); + } else { + newX = scale(prevWidth - lineWidths[i]); + newY = -leading; + } + text.push([da[i], newX, newY]); + prevWidth = lineWidths[i]; + } + } else if (align === "center") { + //The passed in x coordinate defines + //the center point. + x -= lineWidths[0] / 2; + text = []; + len = da.length; + for (var j = 0; j < len; j++) { + if (j === 0) { + newX = getHorizontalCoordinate(x); + newY = getVerticalCoordinate(y); + } else { + newX = scale((prevWidth - lineWidths[j]) / 2); + newY = -leading; + } + text.push([da[j], newX, newY]); + prevWidth = lineWidths[j]; + } + } else if (align === "left") { + text = []; + len = da.length; + for (var h = 0; h < len; h++) { + text.push(da[h]); + } + } else if (align === "justify" && activeFont.encoding === "Identity-H") { + // when using unicode fonts, wordSpacePerLine does not apply + text = []; + len = da.length; + maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; + let backToStartX = 0; + for (var l = 0; l < len; l++) { + newY = l === 0 ? getVerticalCoordinate(y) : -leading; + newX = l === 0 ? getHorizontalCoordinate(x) : backToStartX; + if (l < len - 1) { + let spacing = scale( + (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1) + ); + let words = da[l].split(" "); + text.push([words[0] + " ", newX, newY]); + backToStartX = 0; // distance to reset back to the left + for (let i = 1; i < words.length; i++) { + let shiftAmount = + (findWidth(words[i - 1] + " " + words[i]) - + findWidth(words[i])) * + scaleFactor + + spacing; + if (i == words.length - 1) text.push([words[i], shiftAmount, 0]); + else text.push([words[i] + " ", shiftAmount, 0]); + backToStartX -= shiftAmount; + } + } else { + text.push([da[l], newX, newY]); + } + } + text.push(["", backToStartX, 0]); + } else if (align === "justify") { + text = []; + len = da.length; + maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; + for (var l = 0; l < len; l++) { + newY = l === 0 ? getVerticalCoordinate(y) : -leading; + newX = l === 0 ? getHorizontalCoordinate(x) : 0; + + const numSpaces = da[l].split(" ").length - 1; + const spacing = + numSpaces > 0 ? (maxWidth - lineWidths[l]) / numSpaces : 0; + + if (l < len - 1) { + wordSpacingPerLine.push(hpf(scale(spacing))); + } else { + wordSpacingPerLine.push(0); + } + text.push([da[l], newX, newY]); + } + } else { + throw new Error( + 'Unrecognized alignment option, use "left", "center", "right" or "justify".' + ); + } + } + + //R2L + var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L; + if (doReversing === true) { + text = processTextByFunction(text, function(text, posX, posY) { + return [ + text + .split("") + .reverse() + .join(""), + posX, + posY + ]; + }); + } + + //creating Payload-Object to make text byRef + payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize + } + }; + events.publish("postProcessText", payload); + + text = payload.text; + isHex = payload.mutex.isHex || false; + + //Escaping + var activeFontEncoding = fonts[activeFontKey].encoding; + + if ( + activeFontEncoding === "WinAnsiEncoding" || + activeFontEncoding === "StandardEncoding" + ) { + text = processTextByFunction(text, function(text, posX, posY) { + return [ESC(text), posX, posY]; + }); + } + + da = transformTextToSpecialArray(text); + + text = []; + var STRING = 0; + var ARRAY = 1; + var variant = Array.isArray(da[0]) ? ARRAY : STRING; + var posX; + var posY; + var content; + var wordSpacing = ""; + + var generatePosition = function( + parmPosX, + parmPosY, + parmTransformationMatrix + ) { + var position = ""; + if (parmTransformationMatrix instanceof Matrix) { + // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y + // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied + // in the coordinate system established by this matrix + if (typeof options.angle === "number") { + parmTransformationMatrix = matrixMult( + parmTransformationMatrix, + new Matrix(1, 0, 0, 1, parmPosX, parmPosY) + ); + } else { + parmTransformationMatrix = matrixMult( + new Matrix(1, 0, 0, 1, parmPosX, parmPosY), + parmTransformationMatrix + ); + } + + if (apiMode === ApiMode.ADVANCED) { + parmTransformationMatrix = matrixMult( + new Matrix(1, 0, 0, -1, 0, 0), + parmTransformationMatrix + ); + } + + position = parmTransformationMatrix.join(" ") + " Tm\n"; + } else { + position = hpf(parmPosX) + " " + hpf(parmPosY) + " Td\n"; + } + return position; + }; + + for (var lineIndex = 0; lineIndex < da.length; lineIndex++) { + wordSpacing = ""; + + switch (variant) { + case ARRAY: + content = + (isHex ? "<" : "(") + da[lineIndex][0] + (isHex ? ">" : ")"); + posX = parseFloat(da[lineIndex][1]); + posY = parseFloat(da[lineIndex][2]); + break; + case STRING: + content = (isHex ? "<" : "(") + da[lineIndex] + (isHex ? ">" : ")"); + posX = getHorizontalCoordinate(x); + posY = getVerticalCoordinate(y); + break; + } + + if ( + typeof wordSpacingPerLine !== "undefined" && + typeof wordSpacingPerLine[lineIndex] !== "undefined" + ) { + wordSpacing = wordSpacingPerLine[lineIndex] + " Tw\n"; + } + + if (lineIndex === 0) { + text.push( + wordSpacing + + generatePosition(posX, posY, transformationMatrix) + + content + ); + } else if (variant === STRING) { + text.push(wordSpacing + content); + } else if (variant === ARRAY) { + text.push( + wordSpacing + + generatePosition(posX, posY, transformationMatrix) + + content + ); + } + } + + text = variant === STRING ? text.join(" Tj\nT* ") : text.join(" Tj\n"); + text += " Tj\n"; + + var result = "BT\n/"; + result += activeFontKey + " " + activeFontSize + " Tf\n"; // font face, style, size + result += hpf(activeFontSize * lineHeight) + " TL\n"; // line spacing + result += textColor + "\n"; + result += xtra; + result += text; + result += "ET"; + + out(result); + usedFonts[activeFontKey] = true; + return scope; + }; + + // PDF supports these path painting and clip path operators: + // + // S - stroke + // s - close/stroke + // f (F) - fill non-zero + // f* - fill evenodd + // B - fill stroke nonzero + // B* - fill stroke evenodd + // b - close fill stroke nonzero + // b* - close fill stroke evenodd + // n - nothing (consume path) + // W - clip nonzero + // W* - clip evenodd + // + // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close() + // method. + /** + * + * @name clip + * @function + * @instance + * @param {string} rule Only possible value is 'evenodd' + * @returns {jsPDF} + * @memberof jsPDF# + * @description All .clip() after calling drawing ops with a style argument of null. + */ + var clip = (API.__private__.clip = API.clip = function(rule) { + // Call .clip() after calling drawing ops with a style argument of null + // W is the PDF clipping op + if ("evenodd" === rule) { + out("W*"); + } else { + out("W"); + } + return this; + }); + + /** + * @name clipEvenOdd + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @description Modify the current clip path by intersecting it with the current path using the even-odd rule. Note + * that this will NOT consume the current path. In order to only use this path for clipping call + * {@link API.discardPath} afterwards. + */ + API.clipEvenOdd = function() { + return clip("evenodd"); + }; + + /** + * Consumes the current path without any effect. Mainly used in combination with {@link clip} or + * {@link clipEvenOdd}. The PDF "n" operator. + * @name discardPath + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.__private__.discardPath = API.discardPath = function() { + out("n"); + return this; + }; + + var isValidStyle = (API.__private__.isValidStyle = function(style) { + var validStyleVariants = [ + undefined, + null, + "S", + "D", + "F", + "DF", + "FD", + "f", + "f*", + "B", + "B*", + "n" + ]; + var result = false; + if (validStyleVariants.indexOf(style) !== -1) { + result = true; + } + return result; + }); + + API.__private__.setDefaultPathOperation = API.setDefaultPathOperation = function( + operator + ) { + if (isValidStyle(operator)) { + defaultPathOperation = operator; + } + return this; + }; + + var getStyle = (API.__private__.getStyle = API.getStyle = function(style) { + // see path-painting operators in PDF spec + var op = defaultPathOperation; // stroke + + switch (style) { + case "D": + case "S": + op = "S"; // stroke + break; + case "F": + op = "f"; // fill + break; + case "FD": + case "DF": + op = "B"; + break; + case "f": + case "f*": + case "B": + case "B*": + /* + Allow direct use of these PDF path-painting operators: + - f fill using nonzero winding number rule + - f* fill using even-odd rule + - B fill then stroke with fill using non-zero winding number rule + - B* fill then stroke with fill using even-odd rule + */ + op = style; + break; + } + return op; + }); + + /** + * Close the current path. The PDF "h" operator. + * @name close + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + var close = (API.close = function() { + out("h"); + return this; + }); + + /** + * Stroke the path. The PDF "S" operator. + * @name stroke + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.stroke = function() { + out("S"); + return this; + }; + + /** + * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled + * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator. + * @name fill + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be filled with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fill = function(pattern) { + fillWithOptionalPattern("f", pattern); + return this; + }; + + /** + * Fill the current path using the even-odd rule. The PDF f* operator. + * @see API.fill + * @name fillEvenOdd + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be filled with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillEvenOdd = function(pattern) { + fillWithOptionalPattern("f*", pattern); + return this; + }; + + /** + * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator. + * @see API.fill + * @name fillStroke + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be stroked with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillStroke = function(pattern) { + fillWithOptionalPattern("B", pattern); + return this; + }; + + /** + * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator. + * @see API.fill + * @name fillStrokeEvenOdd + * @function + * @instance + * @param {PatternData=} pattern If provided the path will be fill-stroked with this pattern + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.fillStrokeEvenOdd = function(pattern) { + fillWithOptionalPattern("B*", pattern); + return this; + }; + + var fillWithOptionalPattern = function(style, pattern) { + if (typeof pattern === "object") { + fillWithPattern(pattern, style); + } else { + out(style); + } + }; + + var putStyle = function(style) { + if ( + style === null || + (apiMode === ApiMode.ADVANCED && style === undefined) + ) { + return; + } + + style = getStyle(style); + + // stroking / filling / both the path + out(style); + }; + + function cloneTilingPattern(patternKey, boundingBox, xStep, yStep, matrix) { + var clone = new TilingPattern( + boundingBox || this.boundingBox, + xStep || this.xStep, + yStep || this.yStep, + this.gState, + matrix || this.matrix + ); + clone.stream = this.stream; + var key = patternKey + "$$" + this.cloneIndex++ + "$$"; + addPattern(key, clone); + return clone; + } + + var fillWithPattern = function(patternData, style) { + var patternId = patternMap[patternData.key]; + var pattern = patterns[patternId]; + + if (pattern instanceof ShadingPattern) { + out("q"); + + out(clipRuleFromStyle(style)); + + if (pattern.gState) { + API.setGState(pattern.gState); + } + out(patternData.matrix.toString() + " cm"); + out("/" + patternId + " sh"); + out("Q"); + } else if (pattern instanceof TilingPattern) { + // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation, + // so we must flip them + var matrix = new Matrix(1, 0, 0, -1, 0, getPageHeight()); + + if (patternData.matrix) { + matrix = matrix.multiply(patternData.matrix || identityMatrix); + // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances + // for each use + patternId = cloneTilingPattern.call( + pattern, + patternData.key, + patternData.boundingBox, + patternData.xStep, + patternData.yStep, + matrix + ).id; + } + + out("q"); + out("/Pattern cs"); + out("/" + patternId + " scn"); + + if (pattern.gState) { + API.setGState(pattern.gState); + } + + out(style); + out("Q"); + } + }; + + var clipRuleFromStyle = function(style) { + switch (style) { + case "f": + case "F": + return "W n"; + case "f*": + return "W* n"; + case "B": + return "W S"; + case "B*": + return "W* S"; + + // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern + // and "n"/"S" as style would still fill/fill and stroke the path) + case "S": + return "W S"; + case "n": + return "W n"; + } + }; + + /** + * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator. + * @param {number} x + * @param {number} y + * @name moveTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var moveTo = (API.moveTo = function(x, y) { + out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " m"); + return this; + }); + + /** + * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator. + * @param {number} x + * @param {number} y + * @memberof jsPDF# + * @name lineTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var lineTo = (API.lineTo = function(x, y) { + out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " l"); + return this; + }); + + /** + * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point + * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3). + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @memberof jsPDF# + * @name curveTo + * @function + * @instance + * @memberof jsPDF# + * @returns {jsPDF} + */ + var curveTo = (API.curveTo = function(x1, y1, x2, y2, x3, y3) { + out( + [ + hpf(scale(x1)), + hpf(transformScaleY(y1)), + hpf(scale(x2)), + hpf(transformScaleY(y2)), + hpf(scale(x3)), + hpf(transformScaleY(y3)), + "c" + ].join(" ") + ); + return this; + }); + + /** + * Draw a line on the current page. + * + * @name line + * @function + * @instance + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {string} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. default: 'S' + * @returns {jsPDF} + * @memberof jsPDF# + */ + API.__private__.line = API.line = function(x1, y1, x2, y2, style) { + if ( + isNaN(x1) || + isNaN(y1) || + isNaN(x2) || + isNaN(y2) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.line"); + } + if (apiMode === ApiMode.COMPAT) { + return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], style || "S"); + } else { + return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke(); + } + }; + + /** + * @typedef {Object} PatternData + * {Matrix|undefined} matrix + * {Number|undefined} xStep + * {Number|undefined} yStep + * {Array.|undefined} boundingBox + */ + + /** + * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. + * All data points in `lines` are relative to last line origin. + * `x`, `y` become x1,y1 for first line / curve in the set. + * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. + * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. + * + * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, [1,1], 'F', false) // line, line, bezier curve, line + * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name lines + */ + API.__private__.lines = API.lines = function( + lines, + x, + y, + scale, + style, + closed + ) { + var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4, tmp; + + // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) + // in effort to make all calls have similar signature like + // function(content, coordinateX, coordinateY , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof lines === "number") { + tmp = y; + y = x; + x = lines; + lines = tmp; + } + + scale = scale || [1, 1]; + closed = closed || false; + + if ( + isNaN(x) || + isNaN(y) || + !Array.isArray(lines) || + !Array.isArray(scale) || + !isValidStyle(style) || + typeof closed !== "boolean" + ) { + throw new Error("Invalid arguments passed to jsPDF.lines"); + } + + // starting point + moveTo(x, y); + + scalex = scale[0]; + scaley = scale[1]; + l = lines.length; + //, x2, y2 // bezier only. In page default measurement "units", *after* scaling + //, x3, y3 // bezier only. In page default measurement "units", *after* scaling + // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling + x4 = x; // last / ending point = starting point for first item. + y4 = y; // last / ending point = starting point for first item. + + for (i = 0; i < l; i++) { + leg = lines[i]; + if (leg.length === 2) { + // simple line + x4 = leg[0] * scalex + x4; // here last x4 was prior ending point + y4 = leg[1] * scaley + y4; // here last y4 was prior ending point + lineTo(x4, y4); + } else { + // bezier curve + x2 = leg[0] * scalex + x4; // here last x4 is prior ending point + y2 = leg[1] * scaley + y4; // here last y4 is prior ending point + x3 = leg[2] * scalex + x4; // here last x4 is prior ending point + y3 = leg[3] * scaley + y4; // here last y4 is prior ending point + x4 = leg[4] * scalex + x4; // here last x4 was prior ending point + y4 = leg[5] * scaley + y4; // here last y4 was prior ending point + curveTo(x2, y2, x3, y3, x4, y4); + } + } + + if (closed) { + close(); + } + + putStyle(style); + return this; + }; + + /** + * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative. + * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to) + * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c" + * six and "h" an empty array (or undefined). + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name path + */ + API.path = function(lines) { + for (var i = 0; i < lines.length; i++) { + var leg = lines[i]; + var coords = leg.c; + switch (leg.op) { + case "m": + moveTo(coords[0], coords[1]); + break; + case "l": + lineTo(coords[0], coords[1]); + break; + case "c": + curveTo.apply(this, coords); + break; + case "h": + close(); + break; + } + } + + return this; + }; + + /** + * Adds a rectangle to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} w Width (in units declared at inception of PDF document) + * @param {number} h Height (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name rect + */ + API.__private__.rect = API.rect = function(x, y, w, h, style) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || !isValidStyle(style)) { + throw new Error("Invalid arguments passed to jsPDF.rect"); + } + if (apiMode === ApiMode.COMPAT) { + h = -h; + } + + out( + [ + hpf(scale(x)), + hpf(transformScaleY(y)), + hpf(scale(w)), + hpf(scale(h)), + "re" + ].join(" ") + ); + + putStyle(style); + return this; + }; + + /** + * Adds a triangle to PDF. + * + * @param {number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name triangle + */ + API.__private__.triangle = API.triangle = function( + x1, + y1, + x2, + y2, + x3, + y3, + style + ) { + if ( + isNaN(x1) || + isNaN(y1) || + isNaN(x2) || + isNaN(y2) || + isNaN(x3) || + isNaN(y3) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.triangle"); + } + this.lines( + [ + [x2 - x1, y2 - y1], // vector to point 2 + [x3 - x2, y3 - y2], // vector to point 3 + [x1 - x3, y1 - y3] // closing vector back to point 1 + ], + x1, + y1, // start of path + [1, 1], + style, + true + ); + return this; + }; + + /** + * Adds a rectangle with rounded corners to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} w Width (in units declared at inception of PDF document) + * @param {number} h Height (in units declared at inception of PDF document) + * @param {number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {number} ry Radius along y axis (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name roundedRect + */ + API.__private__.roundedRect = API.roundedRect = function( + x, + y, + w, + h, + rx, + ry, + style + ) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(w) || + isNaN(h) || + isNaN(rx) || + isNaN(ry) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.roundedRect"); + } + var MyArc = (4 / 3) * (Math.SQRT2 - 1); + + rx = Math.min(rx, w * 0.5); + ry = Math.min(ry, h * 0.5); + + this.lines( + [ + [w - 2 * rx, 0], + [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], + [0, h - 2 * ry], + [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], + [-w + 2 * rx, 0], + [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], + [0, -h + 2 * ry], + [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry] + ], + x + rx, + y, // start of path + [1, 1], + style, + true + ); + return this; + }; + + /** + * Adds an ellipse to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {number} ry Radius along y axis (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name ellipse + */ + API.__private__.ellipse = API.ellipse = function(x, y, rx, ry, style) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(rx) || + isNaN(ry) || + !isValidStyle(style) + ) { + throw new Error("Invalid arguments passed to jsPDF.ellipse"); + } + var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, + ly = (4 / 3) * (Math.SQRT2 - 1) * ry; + + moveTo(x + rx, y); + curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry); + curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y); + curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry); + curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); + + putStyle(style); + return this; + }; + + /** + * Adds an circle to PDF. + * + * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} r Radius (in units declared at inception of PDF document) + * @param {string=} style A string specifying the painting style or null. Valid styles include: + * 'S' [default] - stroke, + * 'F' - fill, + * and 'DF' (or 'FD') - fill then stroke. + * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple + * method calls. The last drawing method call used to define the shape should not have a null style argument. + * + * In "advanced" API mode this parameter is deprecated. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name circle + */ + API.__private__.circle = API.circle = function(x, y, r, style) { + if (isNaN(x) || isNaN(y) || isNaN(r) || !isValidStyle(style)) { + throw new Error("Invalid arguments passed to jsPDF.circle"); + } + return this.ellipse(x, y, r, r, style); + }; + + /** + * Sets text font face, variant for upcoming text elements. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {string} fontName Font name or family. Example: "times". + * @param {string} fontStyle Font style or variant. Example: "italic". + * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFont + */ + API.setFont = function(fontName, fontStyle, fontWeight) { + if (fontWeight) { + fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); + } + activeFontKey = getFont(fontName, fontStyle, { + disableWarning: false + }); + return this; + }; + + /** + * Gets text font face, variant for upcoming text elements. + * + * @function + * @instance + * @returns {Object} + * @memberof jsPDF# + * @name getFont + */ + var getFontEntry = (API.__private__.getFont = API.getFont = function() { + return fonts[getFont.apply(API, arguments)]; + }); + + /** + * Returns an object - a tree of fontName to fontStyle relationships available to + * active PDF document. + * + * @public + * @function + * @instance + * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } + * @memberof jsPDF# + * @name getFontList + */ + API.__private__.getFontList = API.getFontList = function() { + var list = {}, + fontName, + fontStyle; + + for (fontName in fontmap) { + if (fontmap.hasOwnProperty(fontName)) { + list[fontName] = []; + for (fontStyle in fontmap[fontName]) { + if (fontmap[fontName].hasOwnProperty(fontStyle)) { + list[fontName].push(fontStyle); + } + } + } + } + return list; + }; + + /** + * Add a custom font to the current instance. + * + * @param {string} postScriptName PDF specification full name for the font. + * @param {string} id PDF-document-instance-specific label assinged to the font. + * @param {string} fontStyle Style of the Font. + * @param {number | string} fontWeight Weight of the Font. + * @param {Object} encoding Encoding_name-to-Font_metrics_object mapping. + * @function + * @instance + * @memberof jsPDF# + * @name addFont + * @returns {string} fontId + */ + API.addFont = function( + postScriptName, + fontName, + fontStyle, + fontWeight, + encoding + ) { + var encodingOptions = [ + "StandardEncoding", + "MacRomanEncoding", + "Identity-H", + "WinAnsiEncoding" + ]; + if (arguments[3] && encodingOptions.indexOf(arguments[3]) !== -1) { + //IE 11 fix + encoding = arguments[3]; + } else if (arguments[3] && encodingOptions.indexOf(arguments[3]) == -1) { + fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); + } + encoding = encoding || "Identity-H"; + return addFont.call(this, postScriptName, fontName, fontStyle, encoding); + }; + + var lineWidth = options.lineWidth || 0.200025; // 2mm + /** + * Gets the line width, default: 0.200025. + * + * @function + * @instance + * @returns {number} lineWidth + * @memberof jsPDF# + * @name getLineWidth + */ + var getLineWidth = (API.__private__.getLineWidth = API.getLineWidth = function() { + return lineWidth; + }); + + /** + * Sets line width for upcoming lines. + * + * @param {number} width Line width (in units declared at inception of PDF document). + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineWidth + */ + var setLineWidth = (API.__private__.setLineWidth = API.setLineWidth = function( + width + ) { + lineWidth = width; + out(hpf(scale(width)) + " w"); + return this; + }); + + /** + * Sets the dash pattern for upcoming lines. + * + * To reset the settings simply call the method without any parameters. + * @param {Array} dashArray An array containing 0-2 numbers. The first number sets the length of the + * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered + * to be as long as the dashes. An empty array means solid, unbroken lines. + * @param {number} dashPhase The phase lines start with. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineDashPattern + */ + API.__private__.setLineDash = jsPDF.API.setLineDash = jsPDF.API.setLineDashPattern = function( + dashArray, + dashPhase + ) { + dashArray = dashArray || []; + dashPhase = dashPhase || 0; + + if (isNaN(dashPhase) || !Array.isArray(dashArray)) { + throw new Error("Invalid arguments passed to jsPDF.setLineDash"); + } + + dashArray = dashArray + .map(function(x) { + return hpf(scale(x)); + }) + .join(" "); + dashPhase = hpf(scale(dashPhase)); + + out("[" + dashArray + "] " + dashPhase + " d"); + return this; + }; + + var lineHeightFactor; + + var getLineHeight = (API.__private__.getLineHeight = API.getLineHeight = function() { + return activeFontSize * lineHeightFactor; + }); + + API.__private__.getLineHeight = API.getLineHeight = function() { + return activeFontSize * lineHeightFactor; + }; + + /** + * Sets the LineHeightFactor of proportion. + * + * @param {number} value LineHeightFactor value. Default: 1.15. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineHeightFactor + */ + var setLineHeightFactor = (API.__private__.setLineHeightFactor = API.setLineHeightFactor = function( + value + ) { + value = value || 1.15; + if (typeof value === "number") { + lineHeightFactor = value; + } + return this; + }); + + /** + * Gets the LineHeightFactor, default: 1.15. + * + * @function + * @instance + * @returns {number} lineHeightFactor + * @memberof jsPDF# + * @name getLineHeightFactor + */ + var getLineHeightFactor = (API.__private__.getLineHeightFactor = API.getLineHeightFactor = function() { + return lineHeightFactor; + }); + + setLineHeightFactor(options.lineHeight); + + var getHorizontalCoordinate = (API.__private__.getHorizontalCoordinate = function( + value + ) { + return scale(value); + }); + + var getVerticalCoordinate = (API.__private__.getVerticalCoordinate = function( + value + ) { + if (apiMode === ApiMode.ADVANCED) { + return value; + } else { + var pageHeight = + pagesContext[currentPage].mediaBox.topRightY - + pagesContext[currentPage].mediaBox.bottomLeftY; + return pageHeight - scale(value); + } + }); + + var getHorizontalCoordinateString = (API.__private__.getHorizontalCoordinateString = API.getHorizontalCoordinateString = function( + value + ) { + return hpf(getHorizontalCoordinate(value)); + }); + + var getVerticalCoordinateString = (API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function( + value + ) { + return hpf(getVerticalCoordinate(value)); + }); + + var strokeColor = options.strokeColor || "0 G"; + + /** + * Gets the stroke color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getDrawColor + */ + API.__private__.getStrokeColor = API.getDrawColor = function() { + return decodeColorString(strokeColor); + }; + + /** + * Sets the stroke color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setDrawColor + */ + API.__private__.setStrokeColor = API.setDrawColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "draw", + precision: 2 + }; + + strokeColor = encodeColorString(options); + out(strokeColor); + return this; + }; + + var fillColor = options.fillColor || "0 g"; + + /** + * Gets the fill color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getFillColor + */ + API.__private__.getFillColor = API.getFillColor = function() { + return decodeColorString(fillColor); + }; + + /** + * Sets the fill color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setFillColor + */ + API.__private__.setFillColor = API.setFillColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "fill", + precision: 2 + }; + + fillColor = encodeColorString(options); + out(fillColor); + return this; + }; + + var textColor = options.textColor || "0 g"; + /** + * Gets the text color for upcoming elements. + * + * @function + * @instance + * @returns {string} colorAsHex + * @memberof jsPDF# + * @name getTextColor + */ + var getTextColor = (API.__private__.getTextColor = API.getTextColor = function() { + return decodeColorString(textColor); + }); + /** + * Sets the text color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. + * @param {Number} ch2 Color channel value. + * @param {Number} ch3 Color channel value. + * @param {Number} ch4 Color channel value. + * + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setTextColor + */ + API.__private__.setTextColor = API.setTextColor = function( + ch1, + ch2, + ch3, + ch4 + ) { + var options = { + ch1: ch1, + ch2: ch2, + ch3: ch3, + ch4: ch4, + pdfColorType: "text", + precision: 3 + }; + textColor = encodeColorString(options); + + return this; + }; + + var activeCharSpace = options.charSpace; + + /** + * Get global value of CharSpace. + * + * @function + * @instance + * @returns {number} charSpace + * @memberof jsPDF# + * @name getCharSpace + */ + var getCharSpace = (API.__private__.getCharSpace = API.getCharSpace = function() { + return parseFloat(activeCharSpace || 0); + }); + + /** + * Set global value of CharSpace. + * + * @param {number} charSpace + * @function + * @instance + * @returns {jsPDF} jsPDF-instance + * @memberof jsPDF# + * @name setCharSpace + */ + API.__private__.setCharSpace = API.setCharSpace = function(charSpace) { + if (isNaN(charSpace)) { + throw new Error("Invalid argument passed to jsPDF.setCharSpace"); + } + activeCharSpace = charSpace; + return this; + }; + + var lineCapID = 0; + /** + * Is an Object providing a mapping from human-readable to + * integer flag values designating the varieties of line cap + * and join styles. + * + * @memberof jsPDF# + * @name CapJoinStyles + */ + API.CapJoinStyles = { + 0: 0, + butt: 0, + but: 0, + miter: 0, + 1: 1, + round: 1, + rounded: 1, + circle: 1, + 2: 2, + projecting: 2, + project: 2, + square: 2, + bevel: 2 + }; + + /** + * Sets the line cap styles. + * See {jsPDF.CapJoinStyles} for variants. + * + * @param {String|Number} style A string or number identifying the type of line cap. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineCap + */ + API.__private__.setLineCap = API.setLineCap = function(style) { + var id = API.CapJoinStyles[style]; + if (id === undefined) { + throw new Error( + "Line cap style of '" + + style + + "' is not recognized. See or extend .CapJoinStyles property for valid styles" + ); + } + lineCapID = id; + out(id + " J"); + + return this; + }; + + var lineJoinID = 0; + /** + * Sets the line join styles. + * See {jsPDF.CapJoinStyles} for variants. + * + * @param {String|Number} style A string or number identifying the type of line join. + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineJoin + */ + API.__private__.setLineJoin = API.setLineJoin = function(style) { + var id = API.CapJoinStyles[style]; + if (id === undefined) { + throw new Error( + "Line join style of '" + + style + + "' is not recognized. See or extend .CapJoinStyles property for valid styles" + ); + } + lineJoinID = id; + out(id + " j"); + + return this; + }; + /** + * Sets the miterLimit property, which effects the maximum miter length. + * + * @param {number} length The length of the miter + * @function + * @instance + * @returns {jsPDF} + * @memberof jsPDF# + * @name setLineMiterLimit + */ + API.__private__.setLineMiterLimit = API.__private__.setMiterLimit = API.setLineMiterLimit = API.setMiterLimit = function( + length + ) { + length = length || 0; + if (isNaN(length)) { + throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit"); + } + out(hpf(scale(length)) + " M"); + + return this; + }; + + /** + * An object representing a pdf graphics state. + * @class GState + */ + + /** + * + * @param parameters A parameter object that contains all properties this graphics state wants to set. + * Supported are: opacity, stroke-opacity + * @constructor + */ + API.GState = GState; + + /** + * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}. + * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState + * it will be added before use. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name setGState + */ + API.setGState = function(gState) { + if (typeof gState === "string") { + gState = gStates[gStatesMap[gState]]; + } else { + gState = addGState(null, gState); + } + + if (!gState.equals(activeGState)) { + out("/" + gState.id + " gs"); + activeGState = gState; + } + }; + + /** + * Adds a new Graphics State. Duplicates are automatically eliminated. + * @param {String} key Might also be null, if no later reference to this gState is needed + * @param {Object} gState The gState object + */ + var addGState = function(key, gState) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (key && gStatesMap[key]) return; + var duplicate = false; + for (var s in gStates) { + if (gStates.hasOwnProperty(s)) { + if (gStates[s].equals(gState)) { + duplicate = true; + break; + } + } + } + + if (duplicate) { + gState = gStates[s]; + } else { + var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10); + gStates[gStateKey] = gState; + gState.id = gStateKey; + } + + // several user keys may point to the same GState object + key && (gStatesMap[key] = gState.id); + + events.publish("addGState", gState); + + return gState; + }; + + /** + * Adds a new {@link GState} for later use. See {@link setGState}. + * @param {String} key + * @param {GState} gState + * @function + * @instance + * @returns {jsPDF} + * + * @memberof jsPDF# + * @name addGState + */ + API.addGState = function(key, gState) { + addGState(key, gState); + return this; + }; + + /** + * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState} + * later. Here, the general pdf graphics state is meant, also including the current transformation matrix, + * fill and stroke colors etc. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name saveGraphicsState + */ + API.saveGraphicsState = function() { + out("q"); + // as we cannot set font key and size independently we must keep track of both + fontStateStack.push({ + key: activeFontKey, + size: activeFontSize, + color: textColor + }); + return this; + }; + + /** + * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack"). + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name restoreGraphicsState + */ + API.restoreGraphicsState = function() { + out("Q"); + + // restore previous font state + var fontState = fontStateStack.pop(); + activeFontKey = fontState.key; + activeFontSize = fontState.size; + textColor = fontState.color; + + activeGState = null; + + return this; + }; + + /** + * Appends this matrix to the left of all previously applied matrices. + * + * @param {Matrix} matrix + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name setCurrentTransformationMatrix + */ + API.setCurrentTransformationMatrix = function(matrix) { + out(matrix.toString() + " cm"); + return this; + }; + + /** + * Inserts a debug comment into the generated pdf. + * @function + * @instance + * @param {String} text + * @returns {jsPDF} + * @memberof jsPDF# + * @name comment + */ + API.comment = function(text) { + out("#" + text); + return this; + }; + + /** + * Point + */ + var Point = function(x, y) { + var _x = x || 0; + Object.defineProperty(this, "x", { + enumerable: true, + get: function() { + return _x; + }, + set: function(value) { + if (!isNaN(value)) { + _x = parseFloat(value); + } + } + }); + + var _y = y || 0; + Object.defineProperty(this, "y", { + enumerable: true, + get: function() { + return _y; + }, + set: function(value) { + if (!isNaN(value)) { + _y = parseFloat(value); + } + } + }); + + var _type = "pt"; + Object.defineProperty(this, "type", { + enumerable: true, + get: function() { + return _type; + }, + set: function(value) { + _type = value.toString(); + } + }); + return this; + }; + + /** + * Rectangle + */ + var Rectangle = function(x, y, w, h) { + Point.call(this, x, y); + this.type = "rect"; + + var _w = w || 0; + Object.defineProperty(this, "w", { + enumerable: true, + get: function() { + return _w; + }, + set: function(value) { + if (!isNaN(value)) { + _w = parseFloat(value); + } + } + }); + + var _h = h || 0; + Object.defineProperty(this, "h", { + enumerable: true, + get: function() { + return _h; + }, + set: function(value) { + if (!isNaN(value)) { + _h = parseFloat(value); + } + } + }); + + return this; + }; + + /** + * FormObject/RenderTarget + */ + + var RenderTarget = function() { + this.page = page; + this.currentPage = currentPage; + this.pages = pages.slice(0); + this.pagesContext = pagesContext.slice(0); + this.x = pageX; + this.y = pageY; + this.matrix = pageMatrix; + this.width = getUnscaledPageWidth(currentPage); + this.height = getUnscaledPageHeight(currentPage); + this.outputDestination = outputDestination; + + this.id = ""; // set by endFormObject() + this.objectNumber = -1; // will be set by putXObject() + }; + + RenderTarget.prototype.restore = function() { + page = this.page; + currentPage = this.currentPage; + pagesContext = this.pagesContext; + pages = this.pages; + pageX = this.x; + pageY = this.y; + pageMatrix = this.matrix; + setPageWidthWithoutScaling(currentPage, this.width); + setPageHeightWithoutScaling(currentPage, this.height); + outputDestination = this.outputDestination; + }; + + var beginNewRenderTarget = function(x, y, width, height, matrix) { + // save current state + renderTargetStack.push(new RenderTarget()); + + // clear pages + page = currentPage = 0; + pages = []; + pageX = x; + pageY = y; + + pageMatrix = matrix; + + beginPage([width, height]); + }; + + var endFormObject = function(key) { + // only add it if it is not already present (the keys provided by the user must be unique!) + if (renderTargetMap[key]) { + renderTargetStack.pop().restore(); + return; + } + + // save the created xObject + var newXObject = new RenderTarget(); + + var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10); + newXObject.id = xObjectId; + + renderTargetMap[key] = xObjectId; + renderTargets[xObjectId] = newXObject; + + events.publish("addFormObject", newXObject); + + // restore state from stack + renderTargetStack.pop().restore(); + }; + + /** + * Starts a new pdf form object, which means that all consequent draw calls target a new independent object + * until {@link endFormObject} is called. The created object can be referenced and drawn later using + * {@link doFormObject}. Nested form objects are possible. + * x, y, width, height set the bounding box that is used to clip the content. + * + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to + * the parent's. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name beginFormObject + */ + API.beginFormObject = function(x, y, width, height, matrix) { + // The user can set the output target to a new form object. Nested form objects are possible. + // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as + // the PDF-Spec states: + // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which + // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions, + // form XObjects may be independent of the content streams in which they appear, and this is strongly + // recommended although not required" + beginNewRenderTarget(x, y, width, height, matrix); + return this; + }; + + /** + * Completes and saves the form object. + * @param {String} key The key by which this form object can be referenced. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name endFormObject + */ + API.endFormObject = function(key) { + endFormObject(key); + return this; + }; + + /** + * Draws the specified form object by referencing to the respective pdf XObject created with + * {@link API.beginFormObject} and {@link endFormObject}. + * The location is determined by matrix. + * + * @param {String} key The key to the form object. + * @param {Matrix} matrix The matrix applied before drawing the form object. + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name doFormObject + */ + API.doFormObject = function(key, matrix) { + var xObject = renderTargets[renderTargetMap[key]]; + out("q"); + out(matrix.toString() + " cm"); + out("/" + xObject.id + " Do"); + out("Q"); + return this; + }; + + /** + * Returns the form object specified by key. + * @param key {String} + * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}} + * @function + * @returns {jsPDF} + * @memberof jsPDF# + * @name getFormObject + */ + API.getFormObject = function(key) { + var xObject = renderTargets[renderTargetMap[key]]; + return { + x: xObject.x, + y: xObject.y, + width: xObject.width, + height: xObject.height, + matrix: xObject.matrix + }; + }; + + /** + * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf'). + * Uses FileSaver.js-method saveAs. + * + * @memberof jsPDF# + * @name save + * @function + * @instance + * @param {string} filename The filename including extension. + * @param {Object} options An Object with additional options, possible options: 'returnPromise'. + * @returns {jsPDF|Promise} jsPDF-instance */ + API.save = function(filename, options) { + + options = options || {}; + options.returnPromise = options.returnPromise || false; + + return Promise.reject(Error('save function removed')); + }; + + // applying plugins (more methods) ON TOP of built-in API. + // this is intentional as we allow plugins to override + // built-ins + for (var plugin in jsPDF.API) { + if (jsPDF.API.hasOwnProperty(plugin)) { + if (plugin === "events" && jsPDF.API.events.length) { + (function(events, newEvents) { + // jsPDF.API.events is a JS Array of Arrays + // where each Array is a pair of event name, handler + // Events were added by plugins to the jsPDF instantiator. + // These are always added to the new instance and some ran + // during instantiation. + var eventname, handler_and_args, i; + + for (i = newEvents.length - 1; i !== -1; i--) { + // subscribe takes 3 args: 'topic', function, runonce_flag + // if undefined, runonce is false. + // users can attach callback directly, + // or they can attach an array with [callback, runonce_flag] + // that's what the "apply" magic is for below. + eventname = newEvents[i][0]; + handler_and_args = newEvents[i][1]; + events.subscribe.apply( + events, + [eventname].concat( + typeof handler_and_args === "function" + ? [handler_and_args] + : handler_and_args + ) + ); + } + })(events, jsPDF.API.events); + } else { + API[plugin] = jsPDF.API[plugin]; + } + } + } + + function getUnscaledPageWidth(pageNumber) { + return ( + pagesContext[pageNumber].mediaBox.topRightX - + pagesContext[pageNumber].mediaBox.bottomLeftX + ); + } + + function setPageWidthWithoutScaling(pageNumber, value) { + pagesContext[pageNumber].mediaBox.topRightX = + value + pagesContext[pageNumber].mediaBox.bottomLeftX; + } + + function getUnscaledPageHeight(pageNumber) { + return ( + pagesContext[pageNumber].mediaBox.topRightY - + pagesContext[pageNumber].mediaBox.bottomLeftY + ); + } + + function setPageHeightWithoutScaling(pageNumber, value) { + pagesContext[pageNumber].mediaBox.topRightY = + value + pagesContext[pageNumber].mediaBox.bottomLeftY; + } + + var getPageWidth = (API.getPageWidth = function(pageNumber) { + pageNumber = pageNumber || currentPage; + return getUnscaledPageWidth(pageNumber) / scaleFactor; + }); + + var setPageWidth = (API.setPageWidth = function(pageNumber, value) { + setPageWidthWithoutScaling(pageNumber, value * scaleFactor); + }); + + var getPageHeight = (API.getPageHeight = function(pageNumber) { + pageNumber = pageNumber || currentPage; + return getUnscaledPageHeight(pageNumber) / scaleFactor; + }); + + var setPageHeight = (API.setPageHeight = function(pageNumber, value) { + setPageHeightWithoutScaling(pageNumber, value * scaleFactor); + }); + + /** + * Object exposing internal API to plugins + * @public + * @ignore + */ + API.internal = { + pdfEscape: pdfEscape, + getStyle: getStyle, + getFont: getFontEntry, + getFontSize: getFontSize, + getCharSpace: getCharSpace, + getTextColor: getTextColor, + getLineHeight: getLineHeight, + getLineHeightFactor: getLineHeightFactor, + getLineWidth: getLineWidth, + write: write, + getHorizontalCoordinate: getHorizontalCoordinate, + getVerticalCoordinate: getVerticalCoordinate, + getCoordinateString: getHorizontalCoordinateString, + getVerticalCoordinateString: getVerticalCoordinateString, + collections: {}, + newObject: newObject, + newAdditionalObject: newAdditionalObject, + newObjectDeferred: newObjectDeferred, + newObjectDeferredBegin: newObjectDeferredBegin, + getFilters: getFilters, + putStream: putStream, + events: events, + scaleFactor: scaleFactor, + pageSize: { + getWidth: function() { + return getPageWidth(currentPage); + }, + setWidth: function(value) { + setPageWidth(currentPage, value); + }, + getHeight: function() { + return getPageHeight(currentPage); + }, + setHeight: function(value) { + setPageHeight(currentPage, value); + } + }, + encryptionOptions: encryptionOptions, + encryption: encryption, + getEncryptor: getEncryptor, + output: output, + getNumberOfPages: getNumberOfPages, + pages: pages, + out: out, + f2: f2, + f3: f3, + getPageInfo: getPageInfo, + getPageInfoByObjId: getPageInfoByObjId, + getCurrentPageInfo: getCurrentPageInfo, + getPDFVersion: getPdfVersion, + Point: Point, + Rectangle: Rectangle, + Matrix: Matrix, + hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them. + }; + + Object.defineProperty(API.internal.pageSize, "width", { + get: function() { + return getPageWidth(currentPage); + }, + set: function(value) { + setPageWidth(currentPage, value); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(API.internal.pageSize, "height", { + get: function() { + return getPageHeight(currentPage); + }, + set: function(value) { + setPageHeight(currentPage, value); + }, + enumerable: true, + configurable: true + }); + + ////////////////////////////////////////////////////// + // continuing initialization of jsPDF Document object + ////////////////////////////////////////////////////// + // Add the first page automatically + addFonts.call(API, standardFonts); + activeFontKey = "F1"; + _addPage(format, orientation); + + events.publish("initialized"); + return API; +} + +/** + * jsPDF.API is a STATIC property of jsPDF class. + * jsPDF.API is an object you can add methods and properties to. + * The methods / properties you add will show up in new jsPDF objects. + * + * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, + * callbacks to this object. These will be reassigned to all new instances of jsPDF. + * + * @static + * @public + * @memberof jsPDF# + * @name API + * + * @example + * jsPDF.API.mymethod = function(){ + * // 'this' will be ref to internal API object. see jsPDF source + * // , so you can refer to built-in methods like so: + * // this.line(....) + * // this.text(....) + * } + * var pdfdoc = new jsPDF() + * pdfdoc.mymethod() // <- !!!!!! + */ +jsPDF.API = { + events: [] +}; +/** + * The version of jsPDF. + * @name version + * @type {string} + * @memberof jsPDF# + */ +jsPDF.version = "3.0.3"; + +/* global jsPDF */ + +var jsPDFAPI = jsPDF.API; +var scaleFactor = 1; + +var pdfEscape = function(value) { + return value + .replace(/\\/g, "\\\\") + .replace(/\(/g, "\\(") + .replace(/\)/g, "\\)"); +}; +var pdfUnescape = function(value) { + return value + .replace(/\\\\/g, "\\") + .replace(/\\\(/g, "(") + .replace(/\\\)/g, ")"); +}; + +var f2 = function(number) { + return number.toFixed(2); // Ie, %.2f +}; + +var f5 = function(number) { + return number.toFixed(5); // Ie, %.2f +}; + +jsPDFAPI.__acroform__ = {}; +var inherit = function(child, parent) { + child.prototype = Object.create(parent.prototype); + child.prototype.constructor = child; +}; + +var scale = function(x) { + return x * scaleFactor; +}; + +var createFormXObject = function(formObject) { + var xobj = new AcroFormXObject(); + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + xobj.BBox = [0, 0, Number(f2(width)), Number(f2(height))]; + return xobj; +}; + +/** + * Bit-Operations + */ +var setBit = (jsPDFAPI.__acroform__.setBit = function(number, bitPosition) { + number = number || 0; + bitPosition = bitPosition || 0; + + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.setBit" + ); + } + var bitMask = 1 << bitPosition; + + number |= bitMask; + + return number; +}); + +var clearBit = (jsPDFAPI.__acroform__.clearBit = function(number, bitPosition) { + number = number || 0; + bitPosition = bitPosition || 0; + + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.clearBit" + ); + } + var bitMask = 1 << bitPosition; + + number &= ~bitMask; + + return number; +}); + +var getBit = (jsPDFAPI.__acroform__.getBit = function(number, bitPosition) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.getBit" + ); + } + return (number & (1 << bitPosition)) === 0 ? 0 : 1; +}); + +/* + * Ff starts counting the bit position at 1 and not like javascript at 0 + */ +var getBitForPdf = (jsPDFAPI.__acroform__.getBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.getBitForPdf" + ); + } + return getBit(number, bitPosition - 1); +}); + +var setBitForPdf = (jsPDFAPI.__acroform__.setBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.setBitForPdf" + ); + } + return setBit(number, bitPosition - 1); +}); + +var clearBitForPdf = (jsPDFAPI.__acroform__.clearBitForPdf = function( + number, + bitPosition +) { + if (isNaN(number) || isNaN(bitPosition)) { + throw new Error( + "Invalid arguments passed to jsPDF.API.__acroform__.clearBitForPdf" + ); + } + return clearBit(number, bitPosition - 1); +}); + +var calculateCoordinates = (jsPDFAPI.__acroform__.calculateCoordinates = function( + args, + scope +) { + var getHorizontalCoordinate = scope.internal.getHorizontalCoordinate; + var getVerticalCoordinate = scope.internal.getVerticalCoordinate; + var x = args[0]; + var y = args[1]; + var w = args[2]; + var h = args[3]; + + var coordinates = {}; + + coordinates.lowerLeft_X = getHorizontalCoordinate(x) || 0; + coordinates.lowerLeft_Y = getVerticalCoordinate(y + h) || 0; + coordinates.upperRight_X = getHorizontalCoordinate(x + w) || 0; + coordinates.upperRight_Y = getVerticalCoordinate(y) || 0; + + return [ + Number(f2(coordinates.lowerLeft_X)), + Number(f2(coordinates.lowerLeft_Y)), + Number(f2(coordinates.upperRight_X)), + Number(f2(coordinates.upperRight_Y)) + ]; +}); + +var calculateAppearanceStream = function(formObject) { + if (formObject.appearanceStreamContent) { + return formObject.appearanceStreamContent; + } + + if (!formObject.V && !formObject.DV) { + return; + } + + // else calculate it + + var stream = []; + var text = formObject._V || formObject.DV; + var calcRes = calculateX(formObject, text); + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + + //PDF 32000-1:2008, page 444 + stream.push("/Tx BMC"); + stream.push("q"); + stream.push("BT"); // Begin Text + stream.push(formObject.scope.__private__.encodeColorString(formObject.color)); + stream.push("/" + fontKey + " " + f2(calcRes.fontSize) + " Tf"); + stream.push("1 0 0 1 0 0 Tm"); // Transformation Matrix + stream.push(calcRes.text); + stream.push("ET"); // End Text + stream.push("Q"); + stream.push("EMC"); + + var appearanceStreamContent = createFormXObject(formObject); + appearanceStreamContent.scope = formObject.scope; + appearanceStreamContent.stream = stream.join("\n"); + return appearanceStreamContent; +}; + +var calculateX = function(formObject, text) { + var maxFontSize = + formObject.fontSize === 0 ? formObject.maxFontSize : formObject.fontSize; + var returnValue = { + text: "", + fontSize: "" + }; + // Remove Brackets + text = text.substr(0, 1) == "(" ? text.substr(1) : text; + text = + text.substr(text.length - 1) == ")" + ? text.substr(0, text.length - 1) + : text; + // split into array of words + var textSplit = text.split(" "); + if (formObject.multiline) { + textSplit = textSplit.map(word => word.split("\n")); + } else { + textSplit = textSplit.map(word => [word]); + } + + var fontSize = maxFontSize; // The Starting fontSize (The Maximum) + var lineSpacing = 2; + var borderPadding = 2; + + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + height = height < 0 ? -height : height; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + width = width < 0 ? -width : width; + + var isSmallerThanWidth = function(i, lastLine, fontSize) { + if (i + 1 < textSplit.length) { + var tmp = lastLine + " " + textSplit[i + 1][0]; + var TextWidth = calculateFontSpace(tmp, formObject, fontSize).width; + var FieldWidth = width - 2 * borderPadding; + return TextWidth <= FieldWidth; + } else { + return false; + } + }; + + fontSize++; + FontSize: while (fontSize > 0) { + text = ""; + fontSize--; + var textHeight = calculateFontSpace("3", formObject, fontSize).height; + var startY = formObject.multiline + ? height - fontSize + : (height - textHeight) / 2; + startY += lineSpacing; + var startX; + + var lastY = startY; + var firstWordInLine = 0, + lastWordInLine = 0; + var lastLength; + var currWord = 0; + + if (fontSize <= 0) { + // In case, the Text doesn't fit at all + fontSize = 12; + text = "(...) Tj\n"; + text += + "% Width of Text: " + + calculateFontSpace(text, formObject, fontSize).width + + ", FieldWidth:" + + width + + "\n"; + break; + } + + var lastLine = ""; + var lineCount = 0; + Line: for (var i = 0; i < textSplit.length; i++) { + if (textSplit.hasOwnProperty(i)) { + let isWithNewLine = false; + if (textSplit[i].length !== 1 && currWord !== textSplit[i].length - 1) { + if ( + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + continue FontSize; + } + + lastLine += textSplit[i][currWord]; + isWithNewLine = true; + lastWordInLine = i; + i--; + } else { + lastLine += textSplit[i][currWord] + " "; + lastLine = + lastLine.substr(lastLine.length - 1) == " " + ? lastLine.substr(0, lastLine.length - 1) + : lastLine; + var key = parseInt(i); + var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize); + var isLastWord = i >= textSplit.length - 1; + + if (nextLineIsSmaller && !isLastWord) { + lastLine += " "; + currWord = 0; + continue; // Line + } else if (!nextLineIsSmaller && !isLastWord) { + if (!formObject.multiline) { + continue FontSize; + } else { + if ( + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + // If the Text is higher than the + // FieldObject + continue FontSize; + } + lastWordInLine = key; + // go on + } + } else if (isLastWord) { + lastWordInLine = key; + } else { + if ( + formObject.multiline && + (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > + height + ) { + // If the Text is higher than the FieldObject + continue FontSize; + } + } + } + // Remove last blank + + var line = ""; + + for (var x = firstWordInLine; x <= lastWordInLine; x++) { + var currLine = textSplit[x]; + if (formObject.multiline) { + if (x === lastWordInLine) { + line += currLine[currWord] + " "; + currWord = (currWord + 1) % currLine.length; + continue; + } + if (x === firstWordInLine) { + line += currLine[currLine.length - 1] + " "; + continue; + } + } + line += currLine[0] + " "; + } + + // Remove last blank + line = + line.substr(line.length - 1) == " " + ? line.substr(0, line.length - 1) + : line; + // lastLength -= blankSpace.width; + lastLength = calculateFontSpace(line, formObject, fontSize).width; + + // Calculate startX + switch (formObject.textAlign) { + case "right": + startX = width - lastLength - borderPadding; + break; + case "center": + startX = (width - lastLength) / 2; + break; + case "left": + default: + startX = borderPadding; + break; + } + text += f2(startX) + " " + f2(lastY) + " Td\n"; + text += "(" + pdfEscape(line) + ") Tj\n"; + // reset X in PDF + text += -f2(startX) + " 0 Td\n"; + + // After a Line, adjust y position + lastY = -(fontSize + lineSpacing); + + // Reset for next iteration step + lastLength = 0; + firstWordInLine = isWithNewLine ? lastWordInLine : lastWordInLine + 1; + lineCount++; + + lastLine = ""; + continue Line; + } + } + break; + } + + returnValue.text = text; + returnValue.fontSize = fontSize; + + return returnValue; +}; + +/** + * Small workaround for calculating the TextMetric approximately. + * + * @param text + * @param fontsize + * @returns {TextMetrics} (Has Height and Width) + */ +var calculateFontSpace = function(text, formObject, fontSize) { + var font = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ); + var width = + formObject.scope.getStringUnitWidth(text, { + font: font, + fontSize: parseFloat(fontSize), + charSpace: 0 + }) * parseFloat(fontSize); + var height = + formObject.scope.getStringUnitWidth("3", { + font: font, + fontSize: parseFloat(fontSize), + charSpace: 0 + }) * + parseFloat(fontSize) * + 1.5; + return { height: height, width: width }; +}; + +var acroformPluginTemplate = { + fields: [], + xForms: [], + /** + * acroFormDictionaryRoot contains information about the AcroForm + * Dictionary 0: The Event-Token, the AcroFormDictionaryCallback has + * 1: The Object ID of the Root + */ + acroFormDictionaryRoot: null, + /** + * After the PDF gets evaluated, the reference to the root has to be + * reset, this indicates, whether the root has already been printed + * out + */ + printedOut: false, + internal: null, + isInitialized: false +}; + +var annotReferenceCallback = function(scope) { + //set objId to undefined and force it to get a new objId on buildDocument + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId = undefined; + var fields = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields; + for (var i in fields) { + if (fields.hasOwnProperty(i)) { + var formObject = fields[i]; + //set objId to undefined and force it to get a new objId on buildDocument + formObject.objId = undefined; + // add Annot Reference! + if (formObject.hasAnnotation) { + // If theres an Annotation Widget in the Form Object, put the + // Reference in the /Annot array + createAnnotationReference(formObject, scope); + } + } + } +}; + +var putForm = function(formObject) { + if (formObject.scope.internal.acroformPlugin.printedOut) { + formObject.scope.internal.acroformPlugin.printedOut = false; + formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot = null; + } + formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push( + formObject + ); +}; +/** + * Create the Reference to the widgetAnnotation, so that it gets referenced + * in the Annot[] int the+ (Requires the Annotation Plugin) + */ +var createAnnotationReference = function(object, scope) { + var options = { + type: "reference", + object: object + }; + var findEntry = function(entry) { + return entry.type === options.type && entry.object === options.object; + }; + if ( + scope.internal + .getPageInfo(object.page) + .pageContext.annotations.find(findEntry) === undefined + ) { + scope.internal + .getPageInfo(object.page) + .pageContext.annotations.push(options); + } +}; + +// Callbacks + +var putCatalogCallback = function(scope) { + // Put reference to AcroForm to DocumentCatalog + if ( + typeof scope.internal.acroformPlugin.acroFormDictionaryRoot !== "undefined" + ) { + // for safety, shouldn't normally be the case + scope.internal.write( + "/AcroForm " + + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId + + " " + + 0 + + " R" + ); + } else { + throw new Error("putCatalogCallback: Root missing."); + } +}; + +/** + * Adds /Acroform X 0 R to Document Catalog, and creates the AcroForm + * Dictionary + */ +var AcroFormDictionaryCallback = function(scope) { + // Remove event + scope.internal.events.unsubscribe( + scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID + ); + delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID; + scope.internal.acroformPlugin.printedOut = true; +}; + +/** + * Creates the single Fields and writes them into the Document + * + * If fieldArray is set, use the fields that are inside it instead of the + * fields from the AcroRoot (for the FormXObjects...) + */ +var createFieldCallback = function(fieldArray, scope) { + var standardFields = !fieldArray; + + if (!fieldArray) { + // in case there is no fieldArray specified, we want to print out + // the Fields of the AcroForm + // Print out Root + scope.internal.newObjectDeferredBegin( + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId, + true + ); + scope.internal.acroformPlugin.acroFormDictionaryRoot.putStream(); + } + + fieldArray = + fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids; + + for (var i in fieldArray) { + if (fieldArray.hasOwnProperty(i)) { + var fieldObject = fieldArray[i]; + var keyValueList = []; + var oldRect = fieldObject.Rect; + + if (fieldObject.Rect) { + fieldObject.Rect = calculateCoordinates(fieldObject.Rect, scope); + } + + // Start Writing the Object + scope.internal.newObjectDeferredBegin(fieldObject.objId, true); + + fieldObject.DA = AcroFormAppearance.createDefaultAppearanceStream( + fieldObject + ); + + if ( + typeof fieldObject === "object" && + typeof fieldObject.getKeyValueListForStream === "function" + ) { + keyValueList = fieldObject.getKeyValueListForStream(); + } + + fieldObject.Rect = oldRect; + + if ( + fieldObject.hasAppearanceStream && + !fieldObject.appearanceStreamContent + ) { + // Calculate Appearance + var appearance = calculateAppearanceStream(fieldObject); + keyValueList.push({ key: "AP", value: "<>" }); + + scope.internal.acroformPlugin.xForms.push(appearance); + } + + // Assume AppearanceStreamContent is a Array with N,R,D (at least + // one of them!) + if (fieldObject.appearanceStreamContent) { + var appearanceStreamString = ""; + // Iterate over N,R and D + for (var k in fieldObject.appearanceStreamContent) { + if (fieldObject.appearanceStreamContent.hasOwnProperty(k)) { + var value = fieldObject.appearanceStreamContent[k]; + appearanceStreamString += "/" + k + " "; + appearanceStreamString += "<<"; + if (Object.keys(value).length >= 1 || Array.isArray(value)) { + // appearanceStream is an Array or Object! + for (var i in value) { + if (value.hasOwnProperty(i)) { + var obj = value[i]; + if (typeof obj === "function") { + // if Function is referenced, call it in order + // to get the FormXObject + obj = obj.call(scope, fieldObject); + } + appearanceStreamString += "/" + i + " " + obj + " "; + + // In case the XForm is already used, e.g. OffState + // of CheckBoxes, don't add it + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) + scope.internal.acroformPlugin.xForms.push(obj); + } + } + } else { + obj = value; + if (typeof obj === "function") { + // if Function is referenced, call it in order to + // get the FormXObject + obj = obj.call(scope, fieldObject); + } + appearanceStreamString += "/" + i + " " + obj; + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) + scope.internal.acroformPlugin.xForms.push(obj); + } + appearanceStreamString += ">>"; + } + } + + // appearance stream is a normal Object.. + keyValueList.push({ + key: "AP", + value: "<<\n" + appearanceStreamString + ">>" + }); + } + + scope.internal.putStream({ + additionalKeyValues: keyValueList, + objectId: fieldObject.objId + }); + + scope.internal.out("endobj"); + } + } + if (standardFields) { + createXFormObjectCallback(scope.internal.acroformPlugin.xForms, scope); + } +}; + +var createXFormObjectCallback = function(fieldArray, scope) { + for (var i in fieldArray) { + if (fieldArray.hasOwnProperty(i)) { + var key = i; + var fieldObject = fieldArray[i]; + // Start Writing the Object + scope.internal.newObjectDeferredBegin(fieldObject.objId, true); + + if ( + typeof fieldObject === "object" && + typeof fieldObject.putStream === "function" + ) { + fieldObject.putStream(); + } + delete fieldArray[key]; + } + } +}; + +var initializeAcroForm = function(scope, formObject) { + formObject.scope = scope; + if ( + scope.internal !== undefined && + (scope.internal.acroformPlugin === undefined || + scope.internal.acroformPlugin.isInitialized === false) + ) { + AcroFormField.FieldNum = 0; + scope.internal.acroformPlugin = JSON.parse( + JSON.stringify(acroformPluginTemplate) + ); + if (scope.internal.acroformPlugin.acroFormDictionaryRoot) { + throw new Error("Exception while creating AcroformDictionary"); + } + scaleFactor = scope.internal.scaleFactor; + // The Object Number of the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary(); + scope.internal.acroformPlugin.acroFormDictionaryRoot.scope = scope; + + // add Callback for creating the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe( + "postPutResources", + function() { + AcroFormDictionaryCallback(scope); + } + ); + + scope.internal.events.subscribe("buildDocument", function() { + annotReferenceCallback(scope); + }); // buildDocument + + // Register event, that is triggered when the DocumentCatalog is + // written, in order to add /AcroForm + + scope.internal.events.subscribe("putCatalog", function() { + putCatalogCallback(scope); + }); + + // Register event, that creates all Fields + scope.internal.events.subscribe("postPutPages", function(fieldArray) { + createFieldCallback(fieldArray, scope); + }); + + scope.internal.acroformPlugin.isInitialized = true; + } +}; + +//PDF 32000-1:2008, page 26, 7.3.6 +var arrayToPdfArray = (jsPDFAPI.__acroform__.arrayToPdfArray = function( + array, + objId, + scope +) { + var encryptor = function(data) { + return data; + }; + if (Array.isArray(array)) { + var content = "["; + for (var i = 0; i < array.length; i++) { + if (i !== 0) { + content += " "; + } + switch (typeof array[i]) { + case "boolean": + case "number": + case "object": + content += array[i].toString(); + break; + case "string": + if (array[i].substr(0, 1) !== "/") { + if (typeof objId !== "undefined" && scope) + encryptor = scope.internal.getEncryptor(objId); + content += "(" + pdfEscape(encryptor(array[i].toString())) + ")"; + } else { + content += array[i].toString(); + } + break; + } + } + content += "]"; + return content; + } + throw new Error( + "Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray" + ); +}); +function getMatches(string, regex, index) { + index || (index = 1); // default to the first capturing group + var matches = []; + var match; + while ((match = regex.exec(string))) { + matches.push(match[index]); + } + return matches; +} +var pdfArrayToStringArray = function(array) { + var result = []; + if (typeof array === "string") { + result = getMatches(array, /\((.*?)\)/g); + } + return result; +}; + +var toPdfString = function(string, objId, scope) { + var encryptor = function(data) { + return data; + }; + if (typeof objId !== "undefined" && scope) + encryptor = scope.internal.getEncryptor(objId); + string = string || ""; + string.toString(); + string = "(" + pdfEscape(encryptor(string)) + ")"; + return string; +}; + +// ########################## +// Classes +// ########################## + +/** + * @class AcroFormPDFObject + * @classdesc A AcroFormPDFObject + */ +var AcroFormPDFObject = function() { + this._objId = undefined; + this._scope = undefined; + + /** + * @name AcroFormPDFObject#objId + * @type {any} + */ + Object.defineProperty(this, "objId", { + get: function() { + if (typeof this._objId === "undefined") { + if (typeof this.scope === "undefined") { + return undefined; + } + this._objId = this.scope.internal.newObjectDeferred(); + } + return this._objId; + }, + set: function(value) { + this._objId = value; + } + }); + Object.defineProperty(this, "scope", { + value: this._scope, + writable: true + }); +}; + +/** + * @function AcroFormPDFObject.toString + */ +AcroFormPDFObject.prototype.toString = function() { + return this.objId + " 0 R"; +}; + +AcroFormPDFObject.prototype.putStream = function() { + var keyValueList = this.getKeyValueListForStream(); + this.scope.internal.putStream({ + data: this.stream, + additionalKeyValues: keyValueList, + objectId: this.objId + }); + this.scope.internal.out("endobj"); +}; + +/** + * Returns an key-value-List of all non-configurable Variables from the Object + * + * @name getKeyValueListForStream + * @returns {string} + */ +AcroFormPDFObject.prototype.getKeyValueListForStream = function() { + var keyValueList = []; + var keys = Object.getOwnPropertyNames(this).filter(function(key) { + return ( + key != "content" && + key != "appearanceStreamContent" && + key != "scope" && + key != "objId" && + key.substring(0, 1) != "_" + ); + }); + + for (var i in keys) { + if (Object.getOwnPropertyDescriptor(this, keys[i]).configurable === false) { + var key = keys[i]; + var value = this[key]; + + if (value) { + if (Array.isArray(value)) { + keyValueList.push({ + key: key, + value: arrayToPdfArray(value, this.objId, this.scope) + }); + } else if (value instanceof AcroFormPDFObject) { + // In case it is a reference to another PDFObject, + // take the reference number + value.scope = this.scope; + keyValueList.push({ key: key, value: value.objId + " 0 R" }); + } else if (typeof value !== "function") { + keyValueList.push({ key: key, value: value }); + } + } + } + } + return keyValueList; +}; + +var AcroFormXObject = function() { + AcroFormPDFObject.call(this); + + Object.defineProperty(this, "Type", { + value: "/XObject", + configurable: false, + writable: true + }); + + Object.defineProperty(this, "Subtype", { + value: "/Form", + configurable: false, + writable: true + }); + + Object.defineProperty(this, "FormType", { + value: 1, + configurable: false, + writable: true + }); + + var _BBox = []; + Object.defineProperty(this, "BBox", { + configurable: false, + get: function() { + return _BBox; + }, + set: function(value) { + _BBox = value; + } + }); + + Object.defineProperty(this, "Resources", { + value: "2 0 R", + configurable: false, + writable: true + }); + + var _stream; + Object.defineProperty(this, "stream", { + enumerable: false, + configurable: true, + set: function(value) { + _stream = value.trim(); + }, + get: function() { + if (_stream) { + return _stream; + } else { + return null; + } + } + }); +}; + +inherit(AcroFormXObject, AcroFormPDFObject); + +var AcroFormDictionary = function() { + AcroFormPDFObject.call(this); + + var _Kids = []; + + Object.defineProperty(this, "Kids", { + enumerable: false, + configurable: true, + get: function() { + if (_Kids.length > 0) { + return _Kids; + } else { + return undefined; + } + } + }); + Object.defineProperty(this, "Fields", { + enumerable: false, + configurable: false, + get: function() { + return _Kids; + } + }); + + // Default Appearance + var _DA; + Object.defineProperty(this, "DA", { + enumerable: false, + configurable: false, + get: function() { + if (!_DA) { + return undefined; + } + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + return "(" + pdfEscape(encryptor(_DA)) + ")"; + }, + set: function(value) { + _DA = value; + } + }); +}; + +inherit(AcroFormDictionary, AcroFormPDFObject); + +/** + * The Field Object contains the Variables, that every Field needs + * + * @class AcroFormField + * @classdesc An AcroForm FieldObject + */ +var AcroFormField = function() { + AcroFormPDFObject.call(this); + + //Annotation-Flag See Table 165 + var _F = 4; + Object.defineProperty(this, "F", { + enumerable: false, + configurable: false, + get: function() { + return _F; + }, + set: function(value) { + if (!isNaN(value)) { + _F = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute F supplied.' + ); + } + } + }); + + /** + * (PDF 1.2) If set, print the annotation when the page is printed. If clear, never print the annotation, regardless of wether is is displayed on the screen. + * NOTE 2 This can be useful for annotations representing interactive pushbuttons, which would serve no meaningful purpose on the printed page. + * + * @name AcroFormField#showWhenPrinted + * @default true + * @type {boolean} + */ + Object.defineProperty(this, "showWhenPrinted", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(_F, 3)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.F = setBitForPdf(_F, 3); + } else { + this.F = clearBitForPdf(_F, 3); + } + } + }); + + var _Ff = 0; + Object.defineProperty(this, "Ff", { + enumerable: false, + configurable: false, + get: function() { + return _Ff; + }, + set: function(value) { + if (!isNaN(value)) { + _Ff = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute Ff supplied.' + ); + } + } + }); + + var _Rect = []; + Object.defineProperty(this, "Rect", { + enumerable: false, + configurable: false, + get: function() { + if (_Rect.length === 0) { + return undefined; + } + return _Rect; + }, + set: function(value) { + if (typeof value !== "undefined") { + _Rect = value; + } else { + _Rect = []; + } + } + }); + + /** + * The x-position of the field. + * + * @name AcroFormField#x + * @default null + * @type {number} + */ + Object.defineProperty(this, "x", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[0])) { + return 0; + } + return _Rect[0]; + }, + set: function(value) { + _Rect[0] = value; + } + }); + + /** + * The y-position of the field. + * + * @name AcroFormField#y + * @default null + * @type {number} + */ + Object.defineProperty(this, "y", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[1])) { + return 0; + } + return _Rect[1]; + }, + set: function(value) { + _Rect[1] = value; + } + }); + + /** + * The width of the field. + * + * @name AcroFormField#width + * @default null + * @type {number} + */ + Object.defineProperty(this, "width", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[2])) { + return 0; + } + return _Rect[2]; + }, + set: function(value) { + _Rect[2] = value; + } + }); + + /** + * The height of the field. + * + * @name AcroFormField#height + * @default null + * @type {number} + */ + Object.defineProperty(this, "height", { + enumerable: true, + configurable: true, + get: function() { + if (!_Rect || isNaN(_Rect[3])) { + return 0; + } + return _Rect[3]; + }, + set: function(value) { + _Rect[3] = value; + } + }); + + var _FT = ""; + Object.defineProperty(this, "FT", { + enumerable: true, + configurable: false, + get: function() { + return _FT; + }, + set: function(value) { + switch (value) { + case "/Btn": + case "/Tx": + case "/Ch": + case "/Sig": + _FT = value; + break; + default: + throw new Error( + 'Invalid value "' + value + '" for attribute FT supplied.' + ); + } + } + }); + + var _T = null; + + Object.defineProperty(this, "T", { + enumerable: true, + configurable: false, + get: function() { + if (!_T || _T.length < 1) { + // In case of a Child from a Radio´Group, you don't need a FieldName + if (this instanceof AcroFormChildClass) { + return undefined; + } + _T = "FieldObject" + AcroFormField.FieldNum++; + } + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + return "(" + pdfEscape(encryptor(_T)) + ")"; + }, + set: function(value) { + _T = value.toString(); + } + }); + + /** + * (Optional) The partial field name (see 12.7.3.2, “Field Names”). + * + * @name AcroFormField#fieldName + * @default null + * @type {string} + */ + Object.defineProperty(this, "fieldName", { + configurable: true, + enumerable: true, + get: function() { + return _T; + }, + set: function(value) { + _T = value; + } + }); + + var _fontName = "helvetica"; + /** + * The fontName of the font to be used. + * + * @name AcroFormField#fontName + * @default 'helvetica' + * @type {string} + */ + Object.defineProperty(this, "fontName", { + enumerable: true, + configurable: true, + get: function() { + return _fontName; + }, + set: function(value) { + _fontName = value; + } + }); + + var _fontStyle = "normal"; + /** + * The fontStyle of the font to be used. + * + * @name AcroFormField#fontStyle + * @default 'normal' + * @type {string} + */ + Object.defineProperty(this, "fontStyle", { + enumerable: true, + configurable: true, + get: function() { + return _fontStyle; + }, + set: function(value) { + _fontStyle = value; + } + }); + + var _fontSize = 0; + /** + * The fontSize of the font to be used. + * + * @name AcroFormField#fontSize + * @default 0 (for auto) + * @type {number} + */ + Object.defineProperty(this, "fontSize", { + enumerable: true, + configurable: true, + get: function() { + return _fontSize; + }, + set: function(value) { + _fontSize = value; + } + }); + + var _maxFontSize = undefined; + /** + * The maximum fontSize of the font to be used. + * + * @name AcroFormField#maxFontSize + * @default 0 (for auto) + * @type {number} + */ + Object.defineProperty(this, "maxFontSize", { + enumerable: true, + configurable: true, + get: function() { + if (_maxFontSize === undefined) { + // use the old default value here - the value is some kind of random as it depends on the scaleFactor (user unit) + // ("50" is transformed to the "user space" but then used in "pdf space") + return 50 / scaleFactor; + } else { + return _maxFontSize; + } + }, + set: function(value) { + _maxFontSize = value; + } + }); + + var _color = "black"; + /** + * The color of the text + * + * @name AcroFormField#color + * @default 'black' + * @type {string|rgba} + */ + Object.defineProperty(this, "color", { + enumerable: true, + configurable: true, + get: function() { + return _color; + }, + set: function(value) { + _color = value; + } + }); + + var _DA = "/F1 0 Tf 0 g"; + // Defines the default appearance (Needed for variable Text) + Object.defineProperty(this, "DA", { + enumerable: true, + configurable: false, + get: function() { + if ( + !_DA || + this instanceof AcroFormChildClass || + this instanceof AcroFormTextField + ) { + return undefined; + } + return toPdfString(_DA, this.objId, this.scope); + }, + set: function(value) { + value = value.toString(); + _DA = value; + } + }); + + var _DV = null; + Object.defineProperty(this, "DV", { + enumerable: false, + configurable: false, + get: function() { + if (!_DV) { + return undefined; + } + if (this instanceof AcroFormButton === false) { + return toPdfString(_DV, this.objId, this.scope); + } + return _DV; + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === false) { + if (value.substr(0, 1) === "(") { + _DV = pdfUnescape(value.substr(1, value.length - 2)); + } else { + _DV = pdfUnescape(value); + } + } else { + _DV = value; + } + } + }); + + /** + * (Optional; inheritable) The default value to which the field reverts when a reset-form action is executed (see 12.7.5.3, “Reset-Form Action”). The format of this value is the same as that of value. + * + * @name AcroFormField#defaultValue + * @default null + * @type {any} + */ + Object.defineProperty(this, "defaultValue", { + enumerable: true, + configurable: true, + get: function() { + if (this instanceof AcroFormButton === true) { + return pdfUnescape(_DV.substr(1, _DV.length - 1)); + } else { + return _DV; + } + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === true) { + _DV = "/" + value; + } else { + _DV = value; + } + } + }); + + var _V = null; + Object.defineProperty(this, "_V", { + enumerable: false, + configurable: false, + get: function() { + if (!_V) { + return undefined; + } + return _V; + }, + set: function(value) { + this.V = value; + } + }); + Object.defineProperty(this, "V", { + enumerable: false, + configurable: false, + get: function() { + if (!_V) { + return undefined; + } + if (this instanceof AcroFormButton === false) { + return toPdfString(_V, this.objId, this.scope); + } + return _V; + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === false) { + if (value.substr(0, 1) === "(") { + _V = pdfUnescape(value.substr(1, value.length - 2)); + } else { + _V = pdfUnescape(value); + } + } else { + _V = value; + } + } + }); + + /** + * (Optional; inheritable) The field’s value, whose format varies depending on the field type. See the descriptions of individual field types for further information. + * + * @name AcroFormField#value + * @default null + * @type {any} + */ + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + get: function() { + if (this instanceof AcroFormButton === true) { + return pdfUnescape(_V.substr(1, _V.length - 1)); + } else { + return _V; + } + }, + set: function(value) { + value = value.toString(); + if (this instanceof AcroFormButton === true) { + _V = "/" + value; + } else { + _V = value; + } + } + }); + + /** + * Check if field has annotations + * + * @name AcroFormField#hasAnnotation + * @readonly + * @type {boolean} + */ + Object.defineProperty(this, "hasAnnotation", { + enumerable: true, + configurable: true, + get: function() { + return this.Rect; + } + }); + + Object.defineProperty(this, "Type", { + enumerable: true, + configurable: false, + get: function() { + return this.hasAnnotation ? "/Annot" : null; + } + }); + + Object.defineProperty(this, "Subtype", { + enumerable: true, + configurable: false, + get: function() { + return this.hasAnnotation ? "/Widget" : null; + } + }); + + var _hasAppearanceStream = false; + /** + * true if field has an appearanceStream + * + * @name AcroFormField#hasAppearanceStream + * @readonly + * @type {boolean} + */ + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return _hasAppearanceStream; + }, + set: function(value) { + value = Boolean(value); + _hasAppearanceStream = value; + } + }); + + /** + * The page on which the AcroFormField is placed + * + * @name AcroFormField#page + * @type {number} + */ + var _page; + Object.defineProperty(this, "page", { + enumerable: true, + configurable: true, + get: function() { + if (!_page) { + return undefined; + } + return _page; + }, + set: function(value) { + _page = value; + } + }); + + /** + * If set, the user may not change the value of the field. Any associated widget annotations will not interact with the user; that is, they will not respond to mouse clicks or change their appearance in response to mouse motions. This flag is useful for fields whose values are computed or imported from a database. + * + * @name AcroFormField#readOnly + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "readOnly", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 1)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 1); + } else { + this.Ff = clearBitForPdf(this.Ff, 1); + } + } + }); + + /** + * If set, the field shall have a value at the time it is exported by a submitform action (see 12.7.5.2, “Submit-Form Action”). + * + * @name AcroFormField#required + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "required", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 2)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 2); + } else { + this.Ff = clearBitForPdf(this.Ff, 2); + } + } + }); + + /** + * If set, the field shall not be exported by a submit-form action (see 12.7.5.2, “Submit-Form Action”) + * + * @name AcroFormField#noExport + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "noExport", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 3)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 3); + } else { + this.Ff = clearBitForPdf(this.Ff, 3); + } + } + }); + + var _Q = null; + Object.defineProperty(this, "Q", { + enumerable: true, + configurable: false, + get: function() { + if (_Q === null) { + return undefined; + } + return _Q; + }, + set: function(value) { + if ([0, 1, 2].indexOf(value) !== -1) { + _Q = value; + } else { + throw new Error( + 'Invalid value "' + value + '" for attribute Q supplied.' + ); + } + } + }); + + /** + * (Optional; inheritable) A code specifying the form of quadding (justification) that shall be used in displaying the text: + * 'left', 'center', 'right' + * + * @name AcroFormField#textAlign + * @default 'left' + * @type {string} + */ + Object.defineProperty(this, "textAlign", { + get: function() { + var result; + switch (_Q) { + case 0: + default: + result = "left"; + break; + case 1: + result = "center"; + break; + case 2: + result = "right"; + break; + } + return result; + }, + configurable: true, + enumerable: true, + set: function(value) { + switch (value) { + case "right": + case 2: + _Q = 2; + break; + case "center": + case 1: + _Q = 1; + break; + case "left": + case 0: + default: + _Q = 0; + } + } + }); +}; + +inherit(AcroFormField, AcroFormPDFObject); + +/** + * @class AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormChoiceField = function() { + AcroFormField.call(this); + // Field Type = Choice Field + this.FT = "/Ch"; + // options + this.V = "()"; + + this.fontName = "zapfdingbats"; + // Top Index + var _TI = 0; + + Object.defineProperty(this, "TI", { + enumerable: true, + configurable: false, + get: function() { + return _TI; + }, + set: function(value) { + _TI = value; + } + }); + + /** + * (Optional) For scrollable list boxes, the top index (the index in the Opt array of the first option visible in the list). Default value: 0. + * + * @name AcroFormChoiceField#topIndex + * @default 0 + * @type {number} + */ + Object.defineProperty(this, "topIndex", { + enumerable: true, + configurable: true, + get: function() { + return _TI; + }, + set: function(value) { + _TI = value; + } + }); + + var _Opt = []; + Object.defineProperty(this, "Opt", { + enumerable: true, + configurable: false, + get: function() { + return arrayToPdfArray(_Opt, this.objId, this.scope); + }, + set: function(value) { + _Opt = pdfArrayToStringArray(value); + } + }); + + /** + * @memberof AcroFormChoiceField + * @name getOptions + * @function + * @instance + * @returns {array} array of Options + */ + this.getOptions = function() { + return _Opt; + }; + + /** + * @memberof AcroFormChoiceField + * @name setOptions + * @function + * @instance + * @param {array} value + */ + this.setOptions = function(value) { + _Opt = value; + if (this.sort) { + _Opt.sort(); + } + }; + + /** + * @memberof AcroFormChoiceField + * @name addOption + * @function + * @instance + * @param {string} value + */ + this.addOption = function(value) { + value = value || ""; + value = value.toString(); + _Opt.push(value); + if (this.sort) { + _Opt.sort(); + } + }; + + /** + * @memberof AcroFormChoiceField + * @name removeOption + * @function + * @instance + * @param {string} value + * @param {boolean} allEntries (default: false) + */ + this.removeOption = function(value, allEntries) { + allEntries = allEntries || false; + value = value || ""; + value = value.toString(); + + while (_Opt.indexOf(value) !== -1) { + _Opt.splice(_Opt.indexOf(value), 1); + if (allEntries === false) { + break; + } + } + }; + + /** + * If set, the field is a combo box; if clear, the field is a list box. + * + * @name AcroFormChoiceField#combo + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "combo", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 18)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 18); + } else { + this.Ff = clearBitForPdf(this.Ff, 18); + } + } + }); + + /** + * If set, the combo box shall include an editable text box as well as a drop-down list; if clear, it shall include only a drop-down list. This flag shall be used only if the Combo flag is set. + * + * @name AcroFormChoiceField#edit + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "edit", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 19)); + }, + set: function(value) { + //PDF 32000-1:2008, page 444 + if (this.combo === true) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 19); + } else { + this.Ff = clearBitForPdf(this.Ff, 19); + } + } + } + }); + + /** + * If set, the field’s option items shall be sorted alphabetically. This flag is intended for use by writers, not by readers. Conforming readers shall display the options in the order in which they occur in the Opt array (see Table 231). + * + * @name AcroFormChoiceField#sort + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "sort", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 20)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 20); + _Opt.sort(); + } else { + this.Ff = clearBitForPdf(this.Ff, 20); + } + } + }); + + /** + * (PDF 1.4) If set, more than one of the field’s option items may be selected simultaneously; if clear, at most one item shall be selected + * + * @name AcroFormChoiceField#multiSelect + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "multiSelect", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 22)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 22); + } else { + this.Ff = clearBitForPdf(this.Ff, 22); + } + } + }); + + /** + * (PDF 1.4) If set, text entered in the field shall not be spellchecked. This flag shall not be used unless the Combo and Edit flags are both set. + * + * @name AcroFormChoiceField#doNotSpellCheck + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "doNotSpellCheck", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 23)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 23); + } else { + this.Ff = clearBitForPdf(this.Ff, 23); + } + } + }); + + /** + * (PDF 1.5) If set, the new value shall be committed as soon as a selection is made (commonly with the pointing device). In this case, supplying a value for a field involves three actions: selecting the field for fill-in, selecting a choice for the fill-in value, and leaving that field, which finalizes or “commits” the data choice and triggers any actions associated with the entry or changing of this data. If this flag is on, then processing does not wait for leaving the field action to occur, but immediately proceeds to the third step. + * This option enables applications to perform an action once a selection is made, without requiring the user to exit the field. If clear, the new value is not committed until the user exits the field. + * + * @name AcroFormChoiceField#commitOnSelChange + * @default false + * @type {boolean} + */ + Object.defineProperty(this, "commitOnSelChange", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 27)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 27); + } else { + this.Ff = clearBitForPdf(this.Ff, 27); + } + } + }); + + this.hasAppearanceStream = false; +}; +inherit(AcroFormChoiceField, AcroFormField); + +/** + * @class AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormListBox = function() { + AcroFormChoiceField.call(this); + this.fontName = "helvetica"; + + //PDF 32000-1:2008, page 444 + this.combo = false; +}; +inherit(AcroFormListBox, AcroFormChoiceField); + +/** + * @class AcroFormComboBox + * @extends AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormComboBox = function() { + AcroFormListBox.call(this); + this.combo = true; +}; +inherit(AcroFormComboBox, AcroFormListBox); + +/** + * @class AcroFormEditBox + * @extends AcroFormComboBox + * @extends AcroFormListBox + * @extends AcroFormChoiceField + * @extends AcroFormField + */ +var AcroFormEditBox = function() { + AcroFormComboBox.call(this); + this.edit = true; +}; +inherit(AcroFormEditBox, AcroFormComboBox); + +/** + * @class AcroFormButton + * @extends AcroFormField + */ +var AcroFormButton = function() { + AcroFormField.call(this); + this.FT = "/Btn"; + + /** + * (Radio buttons only) If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect. If clear, clicking the selected button deselects it, leaving no button selected. + * + * @name AcroFormButton#noToggleToOff + * @type {boolean} + */ + Object.defineProperty(this, "noToggleToOff", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 15)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 15); + } else { + this.Ff = clearBitForPdf(this.Ff, 15); + } + } + }); + + /** + * If set, the field is a set of radio buttons; if clear, the field is a checkbox. This flag may be set only if the Pushbutton flag is clear. + * + * @name AcroFormButton#radio + * @type {boolean} + */ + Object.defineProperty(this, "radio", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 16)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 16); + } else { + this.Ff = clearBitForPdf(this.Ff, 16); + } + } + }); + + /** + * If set, the field is a pushbutton that does not retain a permanent value. + * + * @name AcroFormButton#pushButton + * @type {boolean} + */ + Object.defineProperty(this, "pushButton", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 17)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 17); + } else { + this.Ff = clearBitForPdf(this.Ff, 17); + } + } + }); + + /** + * (PDF 1.5) If set, a group of radio buttons within a radio button field that use the same value for the on state will turn on and off in unison; that is if one is checked, they are all checked. If clear, the buttons are mutually exclusive (the same behavior as HTML radio buttons). + * + * @name AcroFormButton#radioIsUnison + * @type {boolean} + */ + Object.defineProperty(this, "radioIsUnison", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 26)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 26); + } else { + this.Ff = clearBitForPdf(this.Ff, 26); + } + } + }); + + var _MK = {}; + Object.defineProperty(this, "MK", { + enumerable: false, + configurable: false, + get: function() { + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + if (Object.keys(_MK).length !== 0) { + var result = []; + result.push("<<"); + var key; + for (key in _MK) { + result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); + } + result.push(">>"); + return result.join("\n"); + } + return undefined; + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); + + /** + * From the PDF reference: + * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. + * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). + * + * - '8' = Cross, + * - 'l' = Circle, + * - '' = nothing + * @name AcroFormButton#caption + * @type {string} + */ + Object.defineProperty(this, "caption", { + enumerable: true, + configurable: true, + get: function() { + return _MK.CA || ""; + }, + set: function(value) { + if (typeof value === "string") { + _MK.CA = value; + } + } + }); + + var _AS; + Object.defineProperty(this, "AS", { + enumerable: false, + configurable: false, + get: function() { + return _AS; + }, + set: function(value) { + _AS = value; + } + }); + + /** + * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") + * + * @name AcroFormButton#appearanceState + * @type {any} + */ + Object.defineProperty(this, "appearanceState", { + enumerable: true, + configurable: true, + get: function() { + return _AS.substr(1, _AS.length - 1); + }, + set: function(value) { + _AS = "/" + value; + } + }); +}; +inherit(AcroFormButton, AcroFormField); + +/** + * @class AcroFormPushButton + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormPushButton = function() { + AcroFormButton.call(this); + this.pushButton = true; +}; +inherit(AcroFormPushButton, AcroFormButton); + +/** + * @class AcroFormRadioButton + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormRadioButton = function() { + AcroFormButton.call(this); + this.radio = true; + this.pushButton = false; + + var _Kids = []; + Object.defineProperty(this, "Kids", { + enumerable: true, + configurable: false, + get: function() { + return _Kids; + }, + set: function(value) { + if (typeof value !== "undefined") { + _Kids = value; + } else { + _Kids = []; + } + } + }); +}; +inherit(AcroFormRadioButton, AcroFormButton); + +/** + * The Child class of a RadioButton (the radioGroup) -> The single Buttons + * + * @class AcroFormChildClass + * @extends AcroFormField + * @ignore + */ +var AcroFormChildClass = function() { + AcroFormField.call(this); + + var _parent; + Object.defineProperty(this, "Parent", { + enumerable: false, + configurable: false, + get: function() { + return _parent; + }, + set: function(value) { + _parent = value; + } + }); + + var _optionName; + Object.defineProperty(this, "optionName", { + enumerable: false, + configurable: true, + get: function() { + return _optionName; + }, + set: function(value) { + _optionName = value; + } + }); + + var _MK = {}; + Object.defineProperty(this, "MK", { + enumerable: false, + configurable: false, + get: function() { + var encryptor = function(data) { + return data; + }; + if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); + var result = []; + result.push("<<"); + var key; + for (key in _MK) { + result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); + } + result.push(">>"); + return result.join("\n"); + }, + set: function(value) { + if (typeof value === "object") { + _MK = value; + } + } + }); + + /** + * From the PDF reference: + * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. + * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). + * + * - '8' = Cross, + * - 'l' = Circle, + * - '' = nothing + * @name AcroFormButton#caption + * @type {string} + */ + Object.defineProperty(this, "caption", { + enumerable: true, + configurable: true, + get: function() { + return _MK.CA || ""; + }, + set: function(value) { + if (typeof value === "string") { + _MK.CA = value; + } + } + }); + + var _AS; + Object.defineProperty(this, "AS", { + enumerable: false, + configurable: false, + get: function() { + return _AS; + }, + set: function(value) { + _AS = value; + } + }); + + /** + * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") + * + * @name AcroFormButton#appearanceState + * @type {any} + */ + Object.defineProperty(this, "appearanceState", { + enumerable: true, + configurable: true, + get: function() { + return _AS.substr(1, _AS.length - 1); + }, + set: function(value) { + _AS = "/" + value; + } + }); + this.caption = "l"; + this.appearanceState = "Off"; + // todo: set AppearanceType as variable that can be set from the + // outside... + this._AppearanceType = AcroFormAppearance.RadioButton.Circle; + // The Default appearanceType is the Circle + this.appearanceStreamContent = this._AppearanceType.createAppearanceStream( + this.optionName + ); +}; +inherit(AcroFormChildClass, AcroFormField); + +AcroFormRadioButton.prototype.setAppearance = function(appearance) { + if (!("createAppearanceStream" in appearance && "getCA" in appearance)) { + throw new Error( + "Couldn't assign Appearance to RadioButton. Appearance was Invalid!" + ); + } + for (var objId in this.Kids) { + if (this.Kids.hasOwnProperty(objId)) { + var child = this.Kids[objId]; + child.appearanceStreamContent = appearance.createAppearanceStream( + child.optionName + ); + child.caption = appearance.getCA(); + } + } +}; + +AcroFormRadioButton.prototype.createOption = function(name) { + // Create new Child for RadioGroup + var child = new AcroFormChildClass(); + child.Parent = this; + child.optionName = name; + // Add to Parent + this.Kids.push(child); + + addField.call(this.scope, child); + + return child; +}; + +/** + * @class AcroFormCheckBox + * @extends AcroFormButton + * @extends AcroFormField + */ +var AcroFormCheckBox = function() { + AcroFormButton.call(this); + + this.fontName = "zapfdingbats"; + this.caption = "3"; + this.appearanceState = "On"; + this.value = "On"; + this.textAlign = "center"; + this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream(); +}; +inherit(AcroFormCheckBox, AcroFormButton); + +/** + * @class AcroFormTextField + * @extends AcroFormField + */ +var AcroFormTextField = function() { + AcroFormField.call(this); + this.FT = "/Tx"; + + /** + * If set, the field may contain multiple lines of text; if clear, the field’s text shall be restricted to a single line. + * + * @name AcroFormTextField#multiline + * @type {boolean} + */ + Object.defineProperty(this, "multiline", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 13)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 13); + } else { + this.Ff = clearBitForPdf(this.Ff, 13); + } + } + }); + + /** + * (PDF 1.4) If set, the text entered in the field represents the pathname of a file whose contents shall be submitted as the value of the field. + * + * @name AcroFormTextField#fileSelect + * @type {boolean} + */ + Object.defineProperty(this, "fileSelect", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 21)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 21); + } else { + this.Ff = clearBitForPdf(this.Ff, 21); + } + } + }); + + /** + * (PDF 1.4) If set, text entered in the field shall not be spell-checked. + * + * @name AcroFormTextField#doNotSpellCheck + * @type {boolean} + */ + Object.defineProperty(this, "doNotSpellCheck", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 23)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 23); + } else { + this.Ff = clearBitForPdf(this.Ff, 23); + } + } + }); + + /** + * (PDF 1.4) If set, the field shall not scroll (horizontally for single-line fields, vertically for multiple-line fields) to accommodate more text than fits within its annotation rectangle. Once the field is full, no further text shall be accepted for interactive form filling; for noninteractive form filling, the filler should take care not to add more character than will visibly fit in the defined area. + * + * @name AcroFormTextField#doNotScroll + * @type {boolean} + */ + Object.defineProperty(this, "doNotScroll", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 24)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 24); + } else { + this.Ff = clearBitForPdf(this.Ff, 24); + } + } + }); + + /** + * (PDF 1.5) May be set only if the MaxLen entry is present in the text field dictionary (see Table 229) and if the Multiline, Password, and FileSelect flags are clear. If set, the field shall be automatically divided into as many equally spaced positions, or combs, as the value of MaxLen, and the text is laid out into those combs. + * + * @name AcroFormTextField#comb + * @type {boolean} + */ + Object.defineProperty(this, "comb", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 25)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 25); + } else { + this.Ff = clearBitForPdf(this.Ff, 25); + } + } + }); + + /** + * (PDF 1.5) If set, the value of this field shall be a rich text string (see 12.7.3.4, “Rich Text Strings”). If the field has a value, the RV entry of the field dictionary (Table 222) shall specify the rich text string. + * + * @name AcroFormTextField#richText + * @type {boolean} + */ + Object.defineProperty(this, "richText", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 26)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 26); + } else { + this.Ff = clearBitForPdf(this.Ff, 26); + } + } + }); + + var _MaxLen = null; + Object.defineProperty(this, "MaxLen", { + enumerable: true, + configurable: false, + get: function() { + return _MaxLen; + }, + set: function(value) { + _MaxLen = value; + } + }); + + /** + * (Optional; inheritable) The maximum length of the field’s text, in characters. + * + * @name AcroFormTextField#maxLength + * @type {number} + */ + Object.defineProperty(this, "maxLength", { + enumerable: true, + configurable: true, + get: function() { + return _MaxLen; + }, + set: function(value) { + if (Number.isInteger(value)) { + _MaxLen = value; + } + } + }); + + Object.defineProperty(this, "hasAppearanceStream", { + enumerable: true, + configurable: true, + get: function() { + return this.V || this.DV; + } + }); +}; +inherit(AcroFormTextField, AcroFormField); + +/** + * @class AcroFormPasswordField + * @extends AcroFormTextField + * @extends AcroFormField + */ +var AcroFormPasswordField = function() { + AcroFormTextField.call(this); + + /** + * If set, the field is intended for entering a secure password that should not be echoed visibly to the screen. Characters typed from the keyboard shall instead be echoed in some unreadable form, such as asterisks or bullet characters. + * NOTE To protect password confidentiality, readers should never store the value of the text field in the PDF file if this flag is set. + * + * @name AcroFormTextField#password + * @type {boolean} + */ + Object.defineProperty(this, "password", { + enumerable: true, + configurable: true, + get: function() { + return Boolean(getBitForPdf(this.Ff, 14)); + }, + set: function(value) { + if (Boolean(value) === true) { + this.Ff = setBitForPdf(this.Ff, 14); + } else { + this.Ff = clearBitForPdf(this.Ff, 14); + } + } + }); + this.password = true; +}; +inherit(AcroFormPasswordField, AcroFormTextField); + +// Contains Methods for creating standard appearances +var AcroFormAppearance = { + CheckBox: { + createAppearanceStream: function() { + var appearance = { + N: { + On: AcroFormAppearance.CheckBox.YesNormal + }, + D: { + On: AcroFormAppearance.CheckBox.YesPushDown, + Off: AcroFormAppearance.CheckBox.OffPushDown + } + }; + + return appearance; + }, + /** + * Returns the standard On Appearance for a CheckBox + * + * @returns {AcroFormXObject} + */ + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var calcRes = calculateX(formObject, formObject.caption); + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + stream.push("BMC"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push( + "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor + ); + stream.push("BT"); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + stream.push("EMC"); + xobj.stream = stream.join("\n"); + return xobj; + }, + + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var stream = []; + var height = AcroFormAppearance.internal.getHeight(formObject); + var width = AcroFormAppearance.internal.getWidth(formObject); + var calcRes = calculateX(formObject, formObject.caption); + stream.push("1 g"); + stream.push("0 0 " + f2(width) + " " + f2(height) + " re"); + stream.push("f"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push("0 0 " + f2(width - 1) + " " + f2(height - 1) + " re"); + stream.push("W"); + stream.push("n"); + stream.push("0 g"); + stream.push("BT"); + stream.push( + "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor + ); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + + /** + * Returns the standard Off Appearance for a CheckBox + * + * @returns {AcroFormXObject} + */ + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, + + RadioButton: { + Circle: { + createAppearanceStream: function(name) { + var appearanceStreamContent = { + D: { + Off: AcroFormAppearance.RadioButton.Circle.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = + AcroFormAppearance.RadioButton.Circle.YesNormal; + appearanceStreamContent.D[name] = + AcroFormAppearance.RadioButton.Circle.YesPushDown; + return appearanceStreamContent; + }, + getCA: function() { + return "l"; + }, + + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + // Make the Radius of the Circle relative to min(height, width) of formObject + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + var c = AcroFormAppearance.internal.Bezier_C; + var DotRadiusBezier = Number((DotRadius * c).toFixed(5)); + /* + * The Following is a Circle created with Bezier-Curves. + */ + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(DotRadius + " 0 m"); + stream.push( + DotRadius + + " " + + DotRadiusBezier + + " " + + DotRadiusBezier + + " " + + DotRadius + + " 0 " + + DotRadius + + " c" + ); + stream.push( + "-" + + DotRadiusBezier + + " " + + DotRadius + + " -" + + DotRadius + + " " + + DotRadiusBezier + + " -" + + DotRadius + + " 0 c" + ); + stream.push( + "-" + + DotRadius + + " -" + + DotRadiusBezier + + " -" + + DotRadiusBezier + + " -" + + DotRadius + + " 0 -" + + DotRadius + + " c" + ); + stream.push( + DotRadiusBezier + + " -" + + DotRadius + + " " + + DotRadius + + " -" + + DotRadiusBezier + + " " + + DotRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + // Save results for later use; no need to waste + // processor ticks on doing math + var k = Number((DotRadius * 2).toFixed(5)); + var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); + var dc = Number( + (DotRadius * AcroFormAppearance.internal.Bezier_C).toFixed(5) + ); + + stream.push("0.749023 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push( + "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" + ); + stream.push( + "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" + ); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + stream.push("0 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(DotRadius + " 0 m"); + stream.push( + "" + + DotRadius + + " " + + dc + + " " + + dc + + " " + + DotRadius + + " 0 " + + DotRadius + + " c" + ); + stream.push( + "-" + + dc + + " " + + DotRadius + + " -" + + DotRadius + + " " + + dc + + " -" + + DotRadius + + " 0 c" + ); + stream.push( + "-" + + DotRadius + + " -" + + dc + + " -" + + dc + + " -" + + DotRadius + + " 0 -" + + DotRadius + + " c" + ); + stream.push( + dc + + " -" + + DotRadius + + " " + + DotRadius + + " -" + + dc + + " " + + DotRadius + + " 0 c" + ); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var DotRadius = + AcroFormAppearance.internal.getWidth(formObject) <= + AcroFormAppearance.internal.getHeight(formObject) + ? AcroFormAppearance.internal.getWidth(formObject) / 4 + : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius = Number((DotRadius * 0.9).toFixed(5)); + // Save results for later use; no need to waste + // processor ticks on doing math + var k = Number((DotRadius * 2).toFixed(5)); + var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); + + stream.push("0.749023 g"); + stream.push("q"); + stream.push( + "1 0 0 1 " + + f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + + " " + + f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + + " cm" + ); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push( + "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" + ); + stream.push( + "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" + ); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, + + Cross: { + /** + * Creates the Actual AppearanceDictionary-References + * + * @param {string} name + * @returns {Object} + * @ignore + */ + createAppearanceStream: function(name) { + var appearanceStreamContent = { + D: { + Off: AcroFormAppearance.RadioButton.Cross.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = + AcroFormAppearance.RadioButton.Cross.YesNormal; + appearanceStreamContent.D[name] = + AcroFormAppearance.RadioButton.Cross.YesPushDown; + return appearanceStreamContent; + }, + getCA: function() { + return "8"; + }, + + YesNormal: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + var cross = AcroFormAppearance.internal.calculateCross(formObject); + stream.push("q"); + stream.push( + "1 1 " + + f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + + " re" + ); + stream.push("W"); + stream.push("n"); + stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); + stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); + stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); + stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var cross = AcroFormAppearance.internal.calculateCross(formObject); + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + stream.push("q"); + stream.push( + "1 1 " + + f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + + " re" + ); + stream.push("W"); + stream.push("n"); + stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); + stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); + stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); + stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function(formObject) { + var xobj = createFormXObject(formObject); + xobj.scope = formObject.scope; + var stream = []; + stream.push("0.749023 g"); + stream.push( + "0 0 " + + f2(AcroFormAppearance.internal.getWidth(formObject)) + + " " + + f2(AcroFormAppearance.internal.getHeight(formObject)) + + " re" + ); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; + } + } + }, + + /** + * Returns the standard Appearance + * + * @returns {AcroFormXObject} + */ + createDefaultAppearanceStream: function(formObject) { + // Set Helvetica to Standard Font (size: auto) + // Color: Black + var fontKey = formObject.scope.internal.getFont( + formObject.fontName, + formObject.fontStyle + ).id; + var encodedColor = formObject.scope.__private__.encodeColorString( + formObject.color + ); + var fontSize = formObject.fontSize; + var result = "/" + fontKey + " " + fontSize + " Tf " + encodedColor; + return result; + } +}; + +AcroFormAppearance.internal = { + Bezier_C: 0.551915024494, + + calculateCross: function(formObject) { + var width = AcroFormAppearance.internal.getWidth(formObject); + var height = AcroFormAppearance.internal.getHeight(formObject); + var a = Math.min(width, height); + + var cross = { + x1: { + // upperLeft + x: (width - a) / 2, + y: (height - a) / 2 + a // height - borderPadding + }, + x2: { + // lowerRight + x: (width - a) / 2 + a, + y: (height - a) / 2 // borderPadding + }, + x3: { + // lowerLeft + x: (width - a) / 2, + y: (height - a) / 2 // borderPadding + }, + x4: { + // upperRight + x: (width - a) / 2 + a, + y: (height - a) / 2 + a // height - borderPadding + } + }; + + return cross; + } +}; +AcroFormAppearance.internal.getWidth = function(formObject) { + var result = 0; + if (typeof formObject === "object") { + result = scale(formObject.Rect[2]); + } + return result; +}; +AcroFormAppearance.internal.getHeight = function(formObject) { + var result = 0; + if (typeof formObject === "object") { + result = scale(formObject.Rect[3]); + } + return result; +}; + +// Public: + +/** + * Add an AcroForm-Field to the jsPDF-instance + * + * @name addField + * @function + * @instance + * @param {Object} fieldObject + * @returns {jsPDF} + */ +var addField = (jsPDFAPI.addField = function(fieldObject) { + initializeAcroForm(this, fieldObject); + + if (fieldObject instanceof AcroFormField) { + putForm(fieldObject); + } else { + throw new Error("Invalid argument passed to jsPDF.addField."); + } + fieldObject.page = fieldObject.scope.internal.getCurrentPageInfo().pageNumber; + return this; +}); + +jsPDFAPI.AcroFormChoiceField = AcroFormChoiceField; +jsPDFAPI.AcroFormListBox = AcroFormListBox; +jsPDFAPI.AcroFormComboBox = AcroFormComboBox; +jsPDFAPI.AcroFormEditBox = AcroFormEditBox; +jsPDFAPI.AcroFormButton = AcroFormButton; +jsPDFAPI.AcroFormPushButton = AcroFormPushButton; +jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton; +jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox; +jsPDFAPI.AcroFormTextField = AcroFormTextField; +jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField; +jsPDFAPI.AcroFormAppearance = AcroFormAppearance; + +jsPDFAPI.AcroForm = { + ChoiceField: AcroFormChoiceField, + ListBox: AcroFormListBox, + ComboBox: AcroFormComboBox, + EditBox: AcroFormEditBox, + Button: AcroFormButton, + PushButton: AcroFormPushButton, + RadioButton: AcroFormRadioButton, + CheckBox: AcroFormCheckBox, + TextField: AcroFormTextField, + PasswordField: AcroFormPasswordField, + Appearance: AcroFormAppearance +}; + +jsPDF.AcroForm = { + ChoiceField: AcroFormChoiceField, + ListBox: AcroFormListBox, + ComboBox: AcroFormComboBox, + EditBox: AcroFormEditBox, + Button: AcroFormButton, + PushButton: AcroFormPushButton, + RadioButton: AcroFormRadioButton, + CheckBox: AcroFormCheckBox, + TextField: AcroFormTextField, + PasswordField: AcroFormPasswordField, + Appearance: AcroFormAppearance +}; + +var AcroForm = jsPDF.AcroForm; + +/** @license + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function(jsPDFAPI) { + + var namespace = "addImage_"; + jsPDFAPI.__addimage__ = {}; + + var UNKNOWN = "UNKNOWN"; + + // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow. + // With too small batch iteration will be slow as more calls are made, + // higher values cause larger and slower garbage collection. + var ARRAY_APPLY_BATCH = 8192; + + var imageFileTypeHeaders = { + PNG: [[0x89, 0x50, 0x4e, 0x47]], + TIFF: [ + [0x4d, 0x4d, 0x00, 0x2a], //Motorola + [0x49, 0x49, 0x2a, 0x00] //Intel + ], + JPEG: [ + [ + 0xff, + 0xd8, + 0xff, + 0xe0, + undefined, + undefined, + 0x4a, + 0x46, + 0x49, + 0x46, + 0x00 + ], //JFIF + [ + 0xff, + 0xd8, + 0xff, + 0xe1, + undefined, + undefined, + 0x45, + 0x78, + 0x69, + 0x66, + 0x00, + 0x00 + ], //Exif + [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW + [0xff, 0xd8, 0xff, 0xee] //EXIF RAW + ], + JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]], + GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]], + GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]], + WEBP: [ + [ + 0x52, + 0x49, + 0x46, + 0x46, + undefined, + undefined, + undefined, + undefined, + 0x57, + 0x45, + 0x42, + 0x50 + ] + ], + BMP: [ + [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc. + [0x42, 0x41], //BA - OS/2 struct bitmap array + [0x43, 0x49], //CI - OS/2 struct color icon + [0x43, 0x50], //CP - OS/2 const color pointer + [0x49, 0x43], //IC - OS/2 struct icon + [0x50, 0x54] //PT - OS/2 pointer + ] + }; + + /** + * Recognize filetype of Image by magic-bytes + * + * https://en.wikipedia.org/wiki/List_of_file_signatures + * + * @name getImageFileTypeByImageData + * @public + * @function + * @param {string|arraybuffer} imageData imageData as binary String or arraybuffer + * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG' + * + * @returns {string} filetype of Image + */ + var getImageFileTypeByImageData = (jsPDFAPI.__addimage__.getImageFileTypeByImageData = function( + imageData, + fallbackFormat + ) { + fallbackFormat = fallbackFormat || UNKNOWN; + var i; + var j; + var result = UNKNOWN; + var headerSchemata; + var compareResult; + var fileType; + + if ( + fallbackFormat === "RGBA" || + (imageData.data !== undefined && + imageData.data instanceof Uint8ClampedArray && + "height" in imageData && + "width" in imageData) + ) { + return "RGBA"; + } + + if (isArrayBufferView(imageData)) { + for (fileType in imageFileTypeHeaders) { + headerSchemata = imageFileTypeHeaders[fileType]; + for (i = 0; i < headerSchemata.length; i += 1) { + compareResult = true; + for (j = 0; j < headerSchemata[i].length; j += 1) { + if (headerSchemata[i][j] === undefined) { + continue; + } + if (headerSchemata[i][j] !== imageData[j]) { + compareResult = false; + break; + } + } + if (compareResult === true) { + result = fileType; + break; + } + } + } + } else { + for (fileType in imageFileTypeHeaders) { + headerSchemata = imageFileTypeHeaders[fileType]; + for (i = 0; i < headerSchemata.length; i += 1) { + compareResult = true; + for (j = 0; j < headerSchemata[i].length; j += 1) { + if (headerSchemata[i][j] === undefined) { + continue; + } + if (headerSchemata[i][j] !== imageData.charCodeAt(j)) { + compareResult = false; + break; + } + } + if (compareResult === true) { + result = fileType; + break; + } + } + } + } + + if (result === UNKNOWN && fallbackFormat !== UNKNOWN) { + result = fallbackFormat; + } + return result; + }); + + // Image functionality ported from pdf.js + var putImage = function(image) { + var out = this.internal.write; + var putStream = this.internal.putStream; + var getFilters = this.internal.getFilters; + + var filter = getFilters(); + while (filter.indexOf("FlateEncode") !== -1) { + filter.splice(filter.indexOf("FlateEncode"), 1); + } + + image.objectId = this.internal.newObject(); + + var additionalKeyValues = []; + additionalKeyValues.push({ key: "Type", value: "/XObject" }); + additionalKeyValues.push({ key: "Subtype", value: "/Image" }); + additionalKeyValues.push({ key: "Width", value: image.width }); + additionalKeyValues.push({ key: "Height", value: image.height }); + + if (image.colorSpace === color_spaces.INDEXED) { + additionalKeyValues.push({ + key: "ColorSpace", + value: + "[/Indexed /DeviceRGB " + + // if an indexed png defines more than one colour with transparency, we've created a sMask + (image.palette.length / 3 - 1) + + " " + + ("sMask" in image && typeof image.sMask !== "undefined" + ? image.objectId + 2 + : image.objectId + 1) + + " 0 R]" + }); + } else { + additionalKeyValues.push({ + key: "ColorSpace", + value: "/" + image.colorSpace + }); + if (image.colorSpace === color_spaces.DEVICE_CMYK) { + additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" }); + } + } + additionalKeyValues.push({ + key: "BitsPerComponent", + value: image.bitsPerComponent + }); + if ( + "decodeParameters" in image && + typeof image.decodeParameters !== "undefined" + ) { + additionalKeyValues.push({ + key: "DecodeParms", + value: "<<" + image.decodeParameters + ">>" + }); + } + if ( + "transparency" in image && + Array.isArray(image.transparency) && + image.transparency.length > 0 + ) { + var transparency = "", + i = 0, + len = image.transparency.length; + for (; i < len; i++) + transparency += + image.transparency[i] + " " + image.transparency[i] + " "; + + additionalKeyValues.push({ + key: "Mask", + value: "[" + transparency + "]" + }); + } + if (typeof image.sMask !== "undefined") { + additionalKeyValues.push({ + key: "SMask", + value: image.objectId + 1 + " 0 R" + }); + } + + var alreadyAppliedFilters = + typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined; + + putStream({ + data: image.data, + additionalKeyValues: additionalKeyValues, + alreadyAppliedFilters: alreadyAppliedFilters, + objectId: image.objectId + }); + + out("endobj"); + + // Soft mask + if ("sMask" in image && typeof image.sMask !== "undefined") { + const sMaskBitsPerComponent = + image.sMaskBitsPerComponent ?? image.bitsPerComponent; + const sMask = { + width: image.width, + height: image.height, + colorSpace: "DeviceGray", + bitsPerComponent: sMaskBitsPerComponent, + data: image.sMask + }; + if ("filter" in image) { + sMask.decodeParameters = `/Predictor ${image.predictor} /Colors 1 /BitsPerComponent ${sMaskBitsPerComponent} /Columns ${image.width}`; + sMask.filter = image.filter; + } + putImage.call(this, sMask); + } + + //Palette + if (image.colorSpace === color_spaces.INDEXED) { + var objId = this.internal.newObject(); + //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); + //putStream(zlib.compress(img['pal'])); + putStream({ + data: arrayBufferToBinaryString(new Uint8Array(image.palette)), + objectId: objId + }); + out("endobj"); + } + }; + var putResourcesCallback = function() { + var images = this.internal.collections[namespace + "images"]; + for (var i in images) { + putImage.call(this, images[i]); + } + }; + var putXObjectsDictCallback = function() { + var images = this.internal.collections[namespace + "images"], + out = this.internal.write, + image; + for (var i in images) { + image = images[i]; + out("/I" + image.index, image.objectId, "0", "R"); + } + }; + + var checkCompressValue = function(value) { + if (value && typeof value === "string") value = value.toUpperCase(); + return value in jsPDFAPI.image_compression ? value : image_compression.NONE; + }; + + var initialize = function() { + if (!this.internal.collections[namespace + "images"]) { + this.internal.collections[namespace + "images"] = {}; + this.internal.events.subscribe("putResources", putResourcesCallback); + this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback); + } + }; + + var getImages = function() { + var images = this.internal.collections[namespace + "images"]; + initialize.call(this); + return images; + }; + var getImageIndex = function() { + return Object.keys(this.internal.collections[namespace + "images"]).length; + }; + var notDefined = function(value) { + return typeof value === "undefined" || value === null || value.length === 0; + }; + var generateAliasFromImageData = function(imageData) { + if (typeof imageData === "string" || isArrayBufferView(imageData)) { + return sHashCode(imageData); + } else if (isArrayBufferView(imageData.data)) { + return sHashCode(imageData.data); + } + + return null; + }; + + var isImageTypeSupported = function(type) { + return typeof jsPDFAPI["process" + type.toUpperCase()] === "function"; + }; + + var isDOMElement = function(object) { + return typeof object === "object" && object.nodeType === 1; + }; + + var getImageDataFromElement = function(element, format) { + //if element is an image which uses data url definition, just return the dataurl + if (element.nodeName === "IMG" && element.hasAttribute("src")) { + var src = "" + element.getAttribute("src"); + + //is base64 encoded dataUrl, directly process it + if (src.indexOf("data:image/") === 0) { + return atob( + unescape(src) + .split("base64,") + .pop() + ); + } + + //it is probably an url, try to load it + var tmpImageData = jsPDFAPI.loadFile(src, true); + if (tmpImageData !== undefined) { + return tmpImageData; + } + } + + if (element.nodeName === "CANVAS") { + if (element.width === 0 || element.height === 0) { + throw new Error( + "Given canvas must have data. Canvas width: " + + element.width + + ", height: " + + element.height + ); + } + var mimeType; + switch (format) { + case "PNG": + mimeType = "image/png"; + break; + case "WEBP": + mimeType = "image/webp"; + break; + case "JPEG": + case "JPG": + default: + mimeType = "image/jpeg"; + break; + } + return atob( + element + .toDataURL(mimeType, 1.0) + .split("base64,") + .pop() + ); + } + }; + + var checkImagesForAlias = function(alias) { + var images = this.internal.collections[namespace + "images"]; + if (images) { + for (var e in images) { + if (alias === images[e].alias) { + return images[e]; + } + } + } + }; + + var determineWidthAndHeight = function(width, height, image) { + if (!width && !height) { + width = -96; + height = -96; + } + if (width < 0) { + width = (-1 * image.width * 72) / width / this.internal.scaleFactor; + } + if (height < 0) { + height = (-1 * image.height * 72) / height / this.internal.scaleFactor; + } + if (width === 0) { + width = (height * image.width) / image.height; + } + if (height === 0) { + height = (width * image.height) / image.width; + } + + return [width, height]; + }; + + var writeImageToPDF = function(x, y, width, height, image, rotation) { + var dims = determineWidthAndHeight.call(this, width, height, image), + coord = this.internal.getCoordinateString, + vcoord = this.internal.getVerticalCoordinateString; + + var images = getImages.call(this); + + width = dims[0]; + height = dims[1]; + images[image.index] = image; + + if (rotation) { + rotation *= Math.PI / 180; + var c = Math.cos(rotation); + var s = Math.sin(rotation); + //like in pdf Reference do it 4 digits instead of 2 + var f4 = function(number) { + return number.toFixed(4); + }; + var rotationTransformationMatrix = [ + f4(c), + f4(s), + f4(s * -1), + f4(c), + 0, + 0, + "cm" + ]; + } + this.internal.write("q"); //Save graphics state + if (rotation) { + this.internal.write( + [1, "0", "0", 1, coord(x), vcoord(y + height), "cm"].join(" ") + ); //Translate + this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate + this.internal.write( + [coord(width), "0", "0", coord(height), "0", "0", "cm"].join(" ") + ); //Scale + } else { + this.internal.write( + [ + coord(width), + "0", + "0", + coord(height), + coord(x), + vcoord(y + height), + "cm" + ].join(" ") + ); //Translate and Scale + } + + if (this.isAdvancedAPI()) { + // draw image bottom up when in "advanced" API mode + this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" ")); + } + + this.internal.write("/I" + image.index + " Do"); //Paint Image + this.internal.write("Q"); //Restore graphics state + }; + + /** + * COLOR SPACES + */ + var color_spaces = (jsPDFAPI.color_spaces = { + DEVICE_RGB: "DeviceRGB", + DEVICE_GRAY: "DeviceGray", + DEVICE_CMYK: "DeviceCMYK", + CAL_GREY: "CalGray", + CAL_RGB: "CalRGB", + LAB: "Lab", + ICC_BASED: "ICCBased", + INDEXED: "Indexed", + PATTERN: "Pattern", + SEPARATION: "Separation", + DEVICE_N: "DeviceN" + }); + + /** + * DECODE METHODS + */ + jsPDFAPI.decode = { + DCT_DECODE: "DCTDecode", + FLATE_DECODE: "FlateDecode", + LZW_DECODE: "LZWDecode", + JPX_DECODE: "JPXDecode", + JBIG2_DECODE: "JBIG2Decode", + ASCII85_DECODE: "ASCII85Decode", + ASCII_HEX_DECODE: "ASCIIHexDecode", + RUN_LENGTH_DECODE: "RunLengthDecode", + CCITT_FAX_DECODE: "CCITTFaxDecode" + }; + + /** + * IMAGE COMPRESSION TYPES + */ + var image_compression = (jsPDFAPI.image_compression = { + NONE: "NONE", + FAST: "FAST", + MEDIUM: "MEDIUM", + SLOW: "SLOW" + }); + + /** + * @name sHashCode + * @function + * @param {string} data + * @returns {string} + */ + var sHashCode = (jsPDFAPI.__addimage__.sHashCode = function(data) { + var hash = 0, + i, + len; + + if (typeof data === "string") { + len = data.length; + for (i = 0; i < len; i++) { + hash = (hash << 5) - hash + data.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + } else if (isArrayBufferView(data)) { + len = data.byteLength / 2; + for (i = 0; i < len; i++) { + hash = (hash << 5) - hash + data[i]; + hash |= 0; // Convert to 32bit integer + } + } + return hash; + }); + + /** + * Validates if given String is a valid Base64-String + * + * @name validateStringAsBase64 + * @public + * @function + * @param {String} possible Base64-String + * + * @returns {boolean} + */ + var validateStringAsBase64 = (jsPDFAPI.__addimage__.validateStringAsBase64 = function( + possibleBase64String + ) { + possibleBase64String = possibleBase64String || ""; + possibleBase64String.toString().trim(); + + var result = true; + + if (possibleBase64String.length === 0) { + result = false; + } + + if (possibleBase64String.length % 4 !== 0) { + result = false; + } + + if ( + /^[A-Za-z0-9+/]+$/.test( + possibleBase64String.substr(0, possibleBase64String.length - 2) + ) === false + ) { + result = false; + } + + if ( + /^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test( + possibleBase64String.substr(-2) + ) === false + ) { + result = false; + } + return result; + }); + + /** + * Strips out and returns info from a valid base64 data URI + * + * @name extractImageFromDataUrl + * @function + * @param {string} dataUrl a valid data URI of format 'data:[][;base64],' + * @returns {string} The raw Base64-encoded data. + */ + var extractImageFromDataUrl = (jsPDFAPI.__addimage__.extractImageFromDataUrl = function( + dataUrl + ) { + if (dataUrl == null) { + return null; + } + + // avoid using a regexp for parsing because it might be vulnerable against ReDoS attacks + + dataUrl = dataUrl.trim(); + + if (!dataUrl.startsWith("data:")) { + return null; + } + + const commaIndex = dataUrl.indexOf(","); + if (commaIndex < 0) { + return null; + } + + const dataScheme = dataUrl.substring(0, commaIndex).trim(); + if (!dataScheme.endsWith("base64")) { + return null; + } + + return dataUrl.substring(commaIndex + 1); + }); + + /** + * Tests supplied object to determine if ArrayBuffer + * + * @name isArrayBuffer + * @function + * @param {Object} object an Object + * + * @returns {boolean} + */ + jsPDFAPI.__addimage__.isArrayBuffer = function(object) { + return object instanceof ArrayBuffer; + }; + + /** + * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface + * + * @name isArrayBufferView + * @function + * @param {Object} object an Object + * @returns {boolean} + */ + var isArrayBufferView = (jsPDFAPI.__addimage__.isArrayBufferView = function( + object + ) { + return ( + object instanceof Int8Array || + object instanceof Uint8Array || + object instanceof Uint8ClampedArray || + object instanceof Int16Array || + object instanceof Uint16Array || + object instanceof Int32Array || + object instanceof Uint32Array || + object instanceof Float32Array || + object instanceof Float64Array + ); + }); + + /** + * Convert Binary String to ArrayBuffer + * + * @name binaryStringToUint8Array + * @public + * @function + * @param {string} BinaryString with ImageData + * @returns {Uint8Array} + */ + var binaryStringToUint8Array = (jsPDFAPI.__addimage__.binaryStringToUint8Array = function( + binary_string + ) { + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }); + + /** + * Convert the Buffer to a Binary String + * + * @name arrayBufferToBinaryString + * @public + * @function + * @param {ArrayBuffer|ArrayBufferView} ArrayBuffer buffer or bufferView with ImageData + * + * @returns {String} + */ + var arrayBufferToBinaryString = (jsPDFAPI.__addimage__.arrayBufferToBinaryString = function( + buffer + ) { + var out = ""; + // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView. + // Do not copy the array if input is already an array. + var buf = isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer); + for (var i = 0; i < buf.length; i += ARRAY_APPLY_BATCH) { + // Limit the amount of characters being parsed to prevent overflow. + // Note that while TextDecoder would be faster, it does not have the same + // functionality as fromCharCode with any provided encodings as of 3/2021. + out += String.fromCharCode.apply( + null, + buf.subarray(i, i + ARRAY_APPLY_BATCH) + ); + } + return out; + }); + + /** + * Possible parameter for addImage, an RGBA buffer with size. + * + * @typedef {Object} RGBAData + * @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData. + * @property {number} width - Image width as the data does not carry this information in itself. + * @property {number} height - Image height as the data does not carry this information in itself. + */ + + /** + * Adds an Image to the PDF. + * + * @name addImage + * @public + * @function + * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData). + * @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP' + * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {number} width width of the image (in units declared at inception of PDF document) + * @param {number} height height of the Image (in units declared at inception of PDF document) + * @param {string} alias alias of the image (if used multiple times) + * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW' + * @param {number} rotation rotation of the image in degrees (0-359) + * + * @returns jsPDF + */ + jsPDFAPI.addImage = function() { + var imageData, format, x, y, w, h, alias, compression, rotation; + + imageData = arguments[0]; + if (typeof arguments[1] === "number") { + format = UNKNOWN; + x = arguments[1]; + y = arguments[2]; + w = arguments[3]; + h = arguments[4]; + alias = arguments[5]; + compression = arguments[6]; + rotation = arguments[7]; + } else { + format = arguments[1]; + x = arguments[2]; + y = arguments[3]; + w = arguments[4]; + h = arguments[5]; + alias = arguments[6]; + compression = arguments[7]; + rotation = arguments[8]; + } + + if ( + typeof imageData === "object" && + !isDOMElement(imageData) && + "imageData" in imageData + ) { + var options = imageData; + + imageData = options.imageData; + format = options.format || format || UNKNOWN; + x = options.x || x || 0; + y = options.y || y || 0; + w = options.w || options.width || w; + h = options.h || options.height || h; + alias = options.alias || alias; + compression = options.compression || compression; + rotation = options.rotation || options.angle || rotation; + } + + //If compression is not explicitly set, determine if we should use compression + var filter = this.internal.getFilters(); + if (compression === undefined && filter.indexOf("FlateEncode") !== -1) { + compression = "SLOW"; + } + + if (isNaN(x) || isNaN(y)) { + throw new Error("Invalid coordinates passed to jsPDF.addImage"); + } + + initialize.call(this); + + var image = processImageData.call( + this, + imageData, + format, + alias, + compression + ); + + writeImageToPDF.call(this, x, y, w, h, image, rotation); + + return this; + }; + + var processImageData = function(imageData, format, alias, compression) { + var result, dataAsBinaryString; + + if ( + typeof imageData === "string" && + getImageFileTypeByImageData(imageData) === UNKNOWN + ) { + imageData = unescape(imageData); + var tmpImageData = convertBase64ToBinaryString(imageData, false); + + if (tmpImageData !== "") { + imageData = tmpImageData; + } else { + tmpImageData = jsPDFAPI.loadFile(imageData, true); + if (tmpImageData !== undefined) { + imageData = tmpImageData; + } + } + } + + if (isDOMElement(imageData)) { + imageData = getImageDataFromElement(imageData, format); + } + + format = getImageFileTypeByImageData(imageData, format); + if (!isImageTypeSupported(format)) { + throw new Error( + "addImage does not support files of type '" + + format + + "', please ensure that a plugin for '" + + format + + "' support is added." + ); + } + + // now do the heavy lifting + + if (notDefined(alias)) { + alias = generateAliasFromImageData(imageData); + } + result = checkImagesForAlias.call(this, alias); + + if (!result) { + // no need to convert if imageData is already uint8array + if (!(imageData instanceof Uint8Array) && format !== "RGBA") { + dataAsBinaryString = imageData; + imageData = binaryStringToUint8Array(imageData); + } + + result = this["process" + format.toUpperCase()]( + imageData, + getImageIndex.call(this), + alias, + checkCompressValue(compression), + dataAsBinaryString + ); + } + + if (!result) { + throw new Error("An unknown error occurred whilst processing the image."); + } + return result; + }; + + /** + * @name convertBase64ToBinaryString + * @function + * @param {string} stringData + * @returns {string} binary string + */ + var convertBase64ToBinaryString = (jsPDFAPI.__addimage__.convertBase64ToBinaryString = function( + stringData, + throwError + ) { + throwError = typeof throwError === "boolean" ? throwError : true; + var imageData = ""; + var rawData; + + if (typeof stringData === "string") { + rawData = extractImageFromDataUrl(stringData) ?? stringData; + + try { + imageData = atob(rawData); + } catch (e) { + if (throwError) { + if (!validateStringAsBase64(rawData)) { + throw new Error( + "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString " + ); + } else { + throw new Error( + "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message + ); + } + } + } + } + return imageData; + }); + + /** + * @name getImageProperties + * @function + * @param {Object} imageData + * @returns {Object} + */ + jsPDFAPI.getImageProperties = function(imageData) { + var image; + var tmpImageData = ""; + var format; + + if (isDOMElement(imageData)) { + imageData = getImageDataFromElement(imageData); + } + + if ( + typeof imageData === "string" && + getImageFileTypeByImageData(imageData) === UNKNOWN + ) { + tmpImageData = convertBase64ToBinaryString(imageData, false); + + if (tmpImageData === "") { + tmpImageData = jsPDFAPI.loadFile(imageData) || ""; + } + imageData = tmpImageData; + } + + format = getImageFileTypeByImageData(imageData); + if (!isImageTypeSupported(format)) { + throw new Error( + "addImage does not support files of type '" + + format + + "', please ensure that a plugin for '" + + format + + "' support is added." + ); + } + + if (!(imageData instanceof Uint8Array)) { + imageData = binaryStringToUint8Array(imageData); + } + + image = this["process" + format.toUpperCase()](imageData); + + if (!image) { + throw new Error("An unknown error occurred whilst processing the image"); + } + + image.fileType = format; + + return image; + }; +})(jsPDF.API); + +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +(function(jsPDFAPI) { + + var notEmpty = function(obj) { + if (typeof obj != "undefined") { + if (obj != "") { + return true; + } + } + }; + + jsPDF.API.events.push([ + "addPage", + function(addPageData) { + var pageInfo = this.internal.getPageInfo(addPageData.pageNumber); + pageInfo.pageContext.annotations = []; + } + ]); + + jsPDFAPI.events.push([ + "putPage", + function(putPageData) { + var getHorizontalCoordinateString = this.internal.getCoordinateString; + var getVerticalCoordinateString = this.internal + .getVerticalCoordinateString; + var pageInfo = this.internal.getPageInfoByObjId(putPageData.objId); + var pageAnnos = putPageData.pageContext.annotations; + + var anno, rect, line; + var found = false; + for (var a = 0; a < pageAnnos.length && !found; a++) { + anno = pageAnnos[a]; + switch (anno.type) { + case "link": + if ( + notEmpty(anno.options.url) || + notEmpty(anno.options.pageNumber) + ) { + found = true; + } + break; + case "reference": + case "text": + case "freetext": + found = true; + break; + } + } + if (found == false) { + return; + } + + this.internal.write("/Annots ["); + for (var i = 0; i < pageAnnos.length; i++) { + anno = pageAnnos[i]; + var escape = this.internal.pdfEscape; + var encryptor = this.internal.getEncryptor(putPageData.objId); + + switch (anno.type) { + case "reference": + // References to Widget Annotations (for AcroForm Fields) + this.internal.write(" " + anno.object.objId + " 0 R "); + break; + case "text": + // Create a an object for both the text and the popup + var objText = this.internal.newAdditionalObject(); + var objPopup = this.internal.newAdditionalObject(); + var encryptorText = this.internal.getEncryptor(objText.objId); + + var title = anno.title || "Note"; + rect = + "/Rect [" + + getHorizontalCoordinateString(anno.bounds.x) + + " " + + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + + " " + + getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + + " " + + getVerticalCoordinateString(anno.bounds.y) + + "] "; + + line = + "<>"; + objText.content = line; + + var parent = objText.objId + " 0 R"; + var popoff = 30; + rect = + "/Rect [" + + getHorizontalCoordinateString(anno.bounds.x + popoff) + + " " + + getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + + " " + + getHorizontalCoordinateString( + anno.bounds.x + anno.bounds.w + popoff + ) + + " " + + getVerticalCoordinateString(anno.bounds.y) + + "] "; + line = + "<>"; + } else if (anno.options.pageNumber) { + // first page is 0 + var info = this.internal.getPageInfo(anno.options.pageNumber); + line = + "< pageNumber or url [required] + *

    If pageNumber is specified, top and zoom may also be specified

    + * @name link + * @function + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + * @param {Object} options + */ + jsPDFAPI.link = function(x, y, w, h, options) { + var pageInfo = this.internal.getCurrentPageInfo(); + var getHorizontalCoordinateString = this.internal.getCoordinateString; + var getVerticalCoordinateString = this.internal.getVerticalCoordinateString; + + pageInfo.pageContext.annotations.push({ + finalBounds: { + x: getHorizontalCoordinateString(x), + y: getVerticalCoordinateString(y), + w: getHorizontalCoordinateString(x + w), + h: getVerticalCoordinateString(y + h) + }, + options: options, + type: "link" + }); + }; + + /** + * Currently only supports single line text. + * Returns the width of the text/link + * + * @name textWithLink + * @function + * @param {string} text + * @param {number} x + * @param {number} y + * @param {Object} options + * @returns {number} width the width of the text/link + */ + jsPDFAPI.textWithLink = function(text, x, y, options) { + var totalLineWidth = this.getTextWidth(text); + var lineHeight = this.internal.getLineHeight() / this.internal.scaleFactor; + var linkHeight, linkWidth; + + // Checking if maxWidth option is passed to determine lineWidth and number of lines for each line + if (options.maxWidth !== undefined) { + var { maxWidth } = options; + linkWidth = maxWidth; + var numOfLines = this.splitTextToSize(text, linkWidth).length; + linkHeight = Math.ceil(lineHeight * numOfLines); + } else { + linkWidth = totalLineWidth; + linkHeight = lineHeight; + } + + this.text(text, x, y, options); + + //TODO We really need the text baseline height to do this correctly. + // Or ability to draw text on top, bottom, center, or baseline. + y += lineHeight * 0.2; + //handle x position based on the align option + if (options.align === "center") { + x = x - totalLineWidth / 2; //since starting from center move the x position by half of text width + } + if (options.align === "right") { + x = x - totalLineWidth; + } + this.link(x, y - lineHeight, linkWidth, linkHeight, options); + return totalLineWidth; + }; + + //TODO move into external library + /** + * @name getTextWidth + * @function + * @param {string} text + * @returns {number} txtWidth + */ + jsPDFAPI.getTextWidth = function(text) { + var fontSize = this.internal.getFontSize(); + var txtWidth = + (this.getStringUnitWidth(text) * fontSize) / this.internal.scaleFactor; + return txtWidth; + }; + + return this; +})(jsPDF.API); + +/** + * @license + * Copyright (c) 2017 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * jsPDF arabic parser PlugIn + * + * @name arabic + * @module + */ +(function(jsPDFAPI) { + + /** + * Arabic shape substitutions: char code => (isolated, final, initial, medial). + * Arabic Substition A + */ + var arabicSubstitionA = { + 0x0621: [0xfe80], // ARABIC LETTER HAMZA + 0x0622: [0xfe81, 0xfe82], // ARABIC LETTER ALEF WITH MADDA ABOVE + 0x0623: [0xfe83, 0xfe84], // ARABIC LETTER ALEF WITH HAMZA ABOVE + 0x0624: [0xfe85, 0xfe86], // ARABIC LETTER WAW WITH HAMZA ABOVE + 0x0625: [0xfe87, 0xfe88], // ARABIC LETTER ALEF WITH HAMZA BELOW + 0x0626: [0xfe89, 0xfe8a, 0xfe8b, 0xfe8c], // ARABIC LETTER YEH WITH HAMZA ABOVE + 0x0627: [0xfe8d, 0xfe8e], // ARABIC LETTER ALEF + 0x0628: [0xfe8f, 0xfe90, 0xfe91, 0xfe92], // ARABIC LETTER BEH + 0x0629: [0xfe93, 0xfe94], // ARABIC LETTER TEH MARBUTA + 0x062a: [0xfe95, 0xfe96, 0xfe97, 0xfe98], // ARABIC LETTER TEH + 0x062b: [0xfe99, 0xfe9a, 0xfe9b, 0xfe9c], // ARABIC LETTER THEH + 0x062c: [0xfe9d, 0xfe9e, 0xfe9f, 0xfea0], // ARABIC LETTER JEEM + 0x062d: [0xfea1, 0xfea2, 0xfea3, 0xfea4], // ARABIC LETTER HAH + 0x062e: [0xfea5, 0xfea6, 0xfea7, 0xfea8], // ARABIC LETTER KHAH + 0x062f: [0xfea9, 0xfeaa], // ARABIC LETTER DAL + 0x0630: [0xfeab, 0xfeac], // ARABIC LETTER THAL + 0x0631: [0xfead, 0xfeae], // ARABIC LETTER REH + 0x0632: [0xfeaf, 0xfeb0], // ARABIC LETTER ZAIN + 0x0633: [0xfeb1, 0xfeb2, 0xfeb3, 0xfeb4], // ARABIC LETTER SEEN + 0x0634: [0xfeb5, 0xfeb6, 0xfeb7, 0xfeb8], // ARABIC LETTER SHEEN + 0x0635: [0xfeb9, 0xfeba, 0xfebb, 0xfebc], // ARABIC LETTER SAD + 0x0636: [0xfebd, 0xfebe, 0xfebf, 0xfec0], // ARABIC LETTER DAD + 0x0637: [0xfec1, 0xfec2, 0xfec3, 0xfec4], // ARABIC LETTER TAH + 0x0638: [0xfec5, 0xfec6, 0xfec7, 0xfec8], // ARABIC LETTER ZAH + 0x0639: [0xfec9, 0xfeca, 0xfecb, 0xfecc], // ARABIC LETTER AIN + 0x063a: [0xfecd, 0xfece, 0xfecf, 0xfed0], // ARABIC LETTER GHAIN + 0x0641: [0xfed1, 0xfed2, 0xfed3, 0xfed4], // ARABIC LETTER FEH + 0x0642: [0xfed5, 0xfed6, 0xfed7, 0xfed8], // ARABIC LETTER QAF + 0x0643: [0xfed9, 0xfeda, 0xfedb, 0xfedc], // ARABIC LETTER KAF + 0x0644: [0xfedd, 0xfede, 0xfedf, 0xfee0], // ARABIC LETTER LAM + 0x0645: [0xfee1, 0xfee2, 0xfee3, 0xfee4], // ARABIC LETTER MEEM + 0x0646: [0xfee5, 0xfee6, 0xfee7, 0xfee8], // ARABIC LETTER NOON + 0x0647: [0xfee9, 0xfeea, 0xfeeb, 0xfeec], // ARABIC LETTER HEH + 0x0648: [0xfeed, 0xfeee], // ARABIC LETTER WAW + 0x0649: [0xfeef, 0xfef0, 64488, 64489], // ARABIC LETTER ALEF MAKSURA + 0x064a: [0xfef1, 0xfef2, 0xfef3, 0xfef4], // ARABIC LETTER YEH + 0x0671: [0xfb50, 0xfb51], // ARABIC LETTER ALEF WASLA + 0x0677: [0xfbdd], // ARABIC LETTER U WITH HAMZA ABOVE + 0x0679: [0xfb66, 0xfb67, 0xfb68, 0xfb69], // ARABIC LETTER TTEH + 0x067a: [0xfb5e, 0xfb5f, 0xfb60, 0xfb61], // ARABIC LETTER TTEHEH + 0x067b: [0xfb52, 0xfb53, 0xfb54, 0xfb55], // ARABIC LETTER BEEH + 0x067e: [0xfb56, 0xfb57, 0xfb58, 0xfb59], // ARABIC LETTER PEH + 0x067f: [0xfb62, 0xfb63, 0xfb64, 0xfb65], // ARABIC LETTER TEHEH + 0x0680: [0xfb5a, 0xfb5b, 0xfb5c, 0xfb5d], // ARABIC LETTER BEHEH + 0x0683: [0xfb76, 0xfb77, 0xfb78, 0xfb79], // ARABIC LETTER NYEH + 0x0684: [0xfb72, 0xfb73, 0xfb74, 0xfb75], // ARABIC LETTER DYEH + 0x0686: [0xfb7a, 0xfb7b, 0xfb7c, 0xfb7d], // ARABIC LETTER TCHEH + 0x0687: [0xfb7e, 0xfb7f, 0xfb80, 0xfb81], // ARABIC LETTER TCHEHEH + 0x0688: [0xfb88, 0xfb89], // ARABIC LETTER DDAL + 0x068c: [0xfb84, 0xfb85], // ARABIC LETTER DAHAL + 0x068d: [0xfb82, 0xfb83], // ARABIC LETTER DDAHAL + 0x068e: [0xfb86, 0xfb87], // ARABIC LETTER DUL + 0x0691: [0xfb8c, 0xfb8d], // ARABIC LETTER RREH + 0x0698: [0xfb8a, 0xfb8b], // ARABIC LETTER JEH + 0x06a4: [0xfb6a, 0xfb6b, 0xfb6c, 0xfb6d], // ARABIC LETTER VEH + 0x06a6: [0xfb6e, 0xfb6f, 0xfb70, 0xfb71], // ARABIC LETTER PEHEH + 0x06a9: [0xfb8e, 0xfb8f, 0xfb90, 0xfb91], // ARABIC LETTER KEHEH + 0x06ad: [0xfbd3, 0xfbd4, 0xfbd5, 0xfbd6], // ARABIC LETTER NG + 0x06af: [0xfb92, 0xfb93, 0xfb94, 0xfb95], // ARABIC LETTER GAF + 0x06b1: [0xfb9a, 0xfb9b, 0xfb9c, 0xfb9d], // ARABIC LETTER NGOEH + 0x06b3: [0xfb96, 0xfb97, 0xfb98, 0xfb99], // ARABIC LETTER GUEH + 0x06ba: [0xfb9e, 0xfb9f], // ARABIC LETTER NOON GHUNNA + 0x06bb: [0xfba0, 0xfba1, 0xfba2, 0xfba3], // ARABIC LETTER RNOON + 0x06be: [0xfbaa, 0xfbab, 0xfbac, 0xfbad], // ARABIC LETTER HEH DOACHASHMEE + 0x06c0: [0xfba4, 0xfba5], // ARABIC LETTER HEH WITH YEH ABOVE + 0x06c1: [0xfba6, 0xfba7, 0xfba8, 0xfba9], // ARABIC LETTER HEH GOAL + 0x06c5: [0xfbe0, 0xfbe1], // ARABIC LETTER KIRGHIZ OE + 0x06c6: [0xfbd9, 0xfbda], // ARABIC LETTER OE + 0x06c7: [0xfbd7, 0xfbd8], // ARABIC LETTER U + 0x06c8: [0xfbdb, 0xfbdc], // ARABIC LETTER YU + 0x06c9: [0xfbe2, 0xfbe3], // ARABIC LETTER KIRGHIZ YU + 0x06cb: [0xfbde, 0xfbdf], // ARABIC LETTER VE + 0x06cc: [0xfbfc, 0xfbfd, 0xfbfe, 0xfbff], // ARABIC LETTER FARSI YEH + 0x06d0: [0xfbe4, 0xfbe5, 0xfbe6, 0xfbe7], //ARABIC LETTER E + 0x06d2: [0xfbae, 0xfbaf], // ARABIC LETTER YEH BARREE + 0x06d3: [0xfbb0, 0xfbb1] // ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + }; + + /* + var ligaturesSubstitutionA = { + 0xFBEA: []// ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM + }; + */ + + var ligatures = { + 0xfedf: { + 0xfe82: 0xfef5, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + 0xfe84: 0xfef7, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + 0xfe88: 0xfef9, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + 0xfe8e: 0xfefb // ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + }, + 0xfee0: { + 0xfe82: 0xfef6, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM + 0xfe84: 0xfef8, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM + 0xfe88: 0xfefa, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM + 0xfe8e: 0xfefc // ARABIC LIGATURE LAM WITH ALEF FINAL FORM + }, + 0xfe8d: { 0xfedf: { 0xfee0: { 0xfeea: 0xfdf2 } } }, // ALLAH + 0x0651: { + 0x064c: 0xfc5e, // Shadda + Dammatan + 0x064d: 0xfc5f, // Shadda + Kasratan + 0x064e: 0xfc60, // Shadda + Fatha + 0x064f: 0xfc61, // Shadda + Damma + 0x0650: 0xfc62 // Shadda + Kasra + } + }; + + var arabic_diacritics = { + 1612: 64606, // Shadda + Dammatan + 1613: 64607, // Shadda + Kasratan + 1614: 64608, // Shadda + Fatha + 1615: 64609, // Shadda + Damma + 1616: 64610 // Shadda + Kasra + }; + + var alfletter = [1570, 1571, 1573, 1575]; + + var noChangeInForm = -1; + var isolatedForm = 0; + var finalForm = 1; + var initialForm = 2; + var medialForm = 3; + + jsPDFAPI.__arabicParser__ = {}; + + //private + var isInArabicSubstitutionA = (jsPDFAPI.__arabicParser__.isInArabicSubstitutionA = function( + letter + ) { + return typeof arabicSubstitionA[letter.charCodeAt(0)] !== "undefined"; + }); + + var isArabicLetter = (jsPDFAPI.__arabicParser__.isArabicLetter = function( + letter + ) { + return ( + typeof letter === "string" && + /^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test( + letter + ) + ); + }); + + var isArabicEndLetter = (jsPDFAPI.__arabicParser__.isArabicEndLetter = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length <= 2 + ); + }); + + var isArabicAlfLetter = (jsPDFAPI.__arabicParser__.isArabicAlfLetter = function( + letter + ) { + return ( + isArabicLetter(letter) && alfletter.indexOf(letter.charCodeAt(0)) >= 0 + ); + }); + + jsPDFAPI.__arabicParser__.arabicLetterHasIsolatedForm = function(letter) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 1 + ); + }; + + var arabicLetterHasFinalForm = (jsPDFAPI.__arabicParser__.arabicLetterHasFinalForm = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 2 + ); + }); + + jsPDFAPI.__arabicParser__.arabicLetterHasInitialForm = function(letter) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length >= 3 + ); + }; + + var arabicLetterHasMedialForm = (jsPDFAPI.__arabicParser__.arabicLetterHasMedialForm = function( + letter + ) { + return ( + isArabicLetter(letter) && + isInArabicSubstitutionA(letter) && + arabicSubstitionA[letter.charCodeAt(0)].length == 4 + ); + }); + + var resolveLigatures = (jsPDFAPI.__arabicParser__.resolveLigatures = function( + letters + ) { + var i = 0; + var tmpLigatures = ligatures; + var result = ""; + var effectedLetters = 0; + + for (i = 0; i < letters.length; i += 1) { + if (typeof tmpLigatures[letters.charCodeAt(i)] !== "undefined") { + effectedLetters++; + tmpLigatures = tmpLigatures[letters.charCodeAt(i)]; + + if (typeof tmpLigatures === "number") { + result += String.fromCharCode(tmpLigatures); + tmpLigatures = ligatures; + effectedLetters = 0; + } + if (i === letters.length - 1) { + tmpLigatures = ligatures; + result += letters.charAt(i - (effectedLetters - 1)); + i = i - (effectedLetters - 1); + effectedLetters = 0; + } + } else { + tmpLigatures = ligatures; + result += letters.charAt(i - effectedLetters); + i = i - effectedLetters; + effectedLetters = 0; + } + } + + return result; + }); + + jsPDFAPI.__arabicParser__.isArabicDiacritic = function(letter) { + return ( + letter !== undefined && + arabic_diacritics[letter.charCodeAt(0)] !== undefined + ); + }; + + var getCorrectForm = (jsPDFAPI.__arabicParser__.getCorrectForm = function( + currentChar, + beforeChar, + nextChar + ) { + if (!isArabicLetter(currentChar)) { + return -1; + } + + if (isInArabicSubstitutionA(currentChar) === false) { + return noChangeInForm; + } + if ( + !arabicLetterHasFinalForm(currentChar) || + (!isArabicLetter(beforeChar) && !isArabicLetter(nextChar)) || + (!isArabicLetter(nextChar) && isArabicEndLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && !isArabicLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && isArabicAlfLetter(beforeChar)) || + (isArabicEndLetter(currentChar) && isArabicEndLetter(beforeChar)) + ) { + return isolatedForm; + } + + if ( + arabicLetterHasMedialForm(currentChar) && + isArabicLetter(beforeChar) && + !isArabicEndLetter(beforeChar) && + isArabicLetter(nextChar) && + arabicLetterHasFinalForm(nextChar) + ) { + return medialForm; + } + + if (isArabicEndLetter(currentChar) || !isArabicLetter(nextChar)) { + return finalForm; + } + return initialForm; + }); + + /** + * @name processArabic + * @function + * @param {string} text + * @returns {string} + */ + var parseArabic = function(text) { + text = text || ""; + + var result = ""; + var i = 0; + var j = 0; + var position = 0; + var currentLetter = ""; + var prevLetter = ""; + var nextLetter = ""; + + var words = text.split("\\s+"); + var newWords = []; + for (i = 0; i < words.length; i += 1) { + newWords.push(""); + for (j = 0; j < words[i].length; j += 1) { + currentLetter = words[i][j]; + prevLetter = words[i][j - 1]; + nextLetter = words[i][j + 1]; + if (isArabicLetter(currentLetter)) { + position = getCorrectForm(currentLetter, prevLetter, nextLetter); + if (position !== -1) { + newWords[i] += String.fromCharCode( + arabicSubstitionA[currentLetter.charCodeAt(0)][position] + ); + } else { + newWords[i] += currentLetter; + } + } else { + newWords[i] += currentLetter; + } + } + + newWords[i] = resolveLigatures(newWords[i]); + } + result = newWords.join(" "); + + return result; + }; + + var processArabic = (jsPDFAPI.__arabicParser__.processArabic = jsPDFAPI.processArabic = function() { + var text = + typeof arguments[0] === "string" ? arguments[0] : arguments[0].text; + var tmpText = []; + var result; + + if (Array.isArray(text)) { + var i = 0; + tmpText = []; + for (i = 0; i < text.length; i += 1) { + if (Array.isArray(text[i])) { + tmpText.push([parseArabic(text[i][0]), text[i][1], text[i][2]]); + } else { + tmpText.push([parseArabic(text[i])]); + } + } + result = tmpText; + } else { + result = parseArabic(text); + } + if (typeof arguments[0] === "string") { + return result; + } else { + arguments[0].text = result; + return arguments[0]; + } + }); + + jsPDFAPI.events.push(["preProcessText", processArabic]); +})(jsPDF.API); + +/** @license + * jsPDF Autoprint Plugin + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * @name autoprint + * @module + */ +(function(jsPDFAPI) { + + /** + * Makes the PDF automatically open the print-Dialog when opened in a PDF-viewer. + * + * @name autoPrint + * @function + * @param {Object} options (optional) Set the attribute variant to 'non-conform' (default) or 'javascript' to activate different methods of automatic printing when opening in a PDF-viewer . + * @returns {jsPDF} + * @example + * var doc = new jsPDF(); + * doc.text(10, 10, 'This is a test'); + * doc.autoPrint({variant: 'non-conform'}); + * doc.save('autoprint.pdf'); + */ + jsPDFAPI.autoPrint = function(options) { + var refAutoPrintTag; + options = options || {}; + options.variant = options.variant || "non-conform"; + + switch (options.variant) { + case "javascript": + //https://github.com/Rob--W/pdf.js/commit/c676ecb5a0f54677b9f3340c3ef2cf42225453bb + this.addJS("print({});"); + break; + case "non-conform": + default: + this.internal.events.subscribe("postPutResources", function() { + refAutoPrintTag = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/S /Named"); + this.internal.out("/Type /Action"); + this.internal.out("/N /Print"); + this.internal.out(">>"); + this.internal.out("endobj"); + }); + + this.internal.events.subscribe("putCatalog", function() { + this.internal.out("/OpenAction " + refAutoPrintTag + " 0 R"); + }); + break; + } + return this; + }; +})(jsPDF.API); + +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * jsPDF Canvas PlugIn + * This plugin mimics the HTML5 Canvas + * + * The goal is to provide a way for current canvas users to print directly to a PDF. + * @name canvas + * @module + */ +(function(jsPDFAPI) { + + /** + * @class Canvas + * @classdesc A Canvas Wrapper for jsPDF + */ + var Canvas = function() { + var jsPdfInstance = undefined; + Object.defineProperty(this, "pdf", { + get: function() { + return jsPdfInstance; + }, + set: function(value) { + jsPdfInstance = value; + } + }); + + var _width = 150; + /** + * The height property is a positive integer reflecting the height HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 150 is used. + * This is one of the two properties, the other being width, that controls the size of the canvas. + * + * @name width + */ + Object.defineProperty(this, "width", { + get: function() { + return _width; + }, + set: function(value) { + if (isNaN(value) || Number.isInteger(value) === false || value < 0) { + _width = 150; + } else { + _width = value; + } + if (this.getContext("2d").pageWrapXEnabled) { + this.getContext("2d").pageWrapX = _width + 1; + } + } + }); + + var _height = 300; + /** + * The width property is a positive integer reflecting the width HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 300 is used. + * This is one of the two properties, the other being height, that controls the size of the canvas. + * + * @name height + */ + Object.defineProperty(this, "height", { + get: function() { + return _height; + }, + set: function(value) { + if (isNaN(value) || Number.isInteger(value) === false || value < 0) { + _height = 300; + } else { + _height = value; + } + if (this.getContext("2d").pageWrapYEnabled) { + this.getContext("2d").pageWrapY = _height + 1; + } + } + }); + + var _childNodes = []; + Object.defineProperty(this, "childNodes", { + get: function() { + return _childNodes; + }, + set: function(value) { + _childNodes = value; + } + }); + + var _style = {}; + Object.defineProperty(this, "style", { + get: function() { + return _style; + }, + set: function(value) { + _style = value; + } + }); + + Object.defineProperty(this, "parentNode", {}); + }; + + /** + * The getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported. + * + * @name getContext + * @function + * @param {string} contextType Is a String containing the context identifier defining the drawing context associated to the canvas. Possible value is "2d", leading to the creation of a Context2D object representing a two-dimensional rendering context. + * @param {object} contextAttributes + */ + Canvas.prototype.getContext = function(contextType, contextAttributes) { + contextType = contextType || "2d"; + var key; + + if (contextType !== "2d") { + return null; + } + for (key in contextAttributes) { + if (this.pdf.context2d.hasOwnProperty(key)) { + this.pdf.context2d[key] = contextAttributes[key]; + } + } + this.pdf.context2d._canvas = this; + return this.pdf.context2d; + }; + + /** + * The toDataURL() method is just a stub to throw an error if accidently called. + * + * @name toDataURL + * @function + */ + Canvas.prototype.toDataURL = function() { + throw new Error("toDataURL is not implemented."); + }; + + jsPDFAPI.events.push([ + "initialized", + function() { + this.canvas = new Canvas(); + this.canvas.pdf = this; + } + ]); + + return this; +})(jsPDF.API); + +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * @name cell + * @module + */ +(function(jsPDFAPI) { + + var NO_MARGINS = { left: 0, top: 0, bottom: 0, right: 0 }; + + var px2pt = (0.264583 * 72) / 25.4; + var printingHeaderRow = false; + + var _initialize = function() { + if (typeof this.internal.__cell__ === "undefined") { + this.internal.__cell__ = {}; + this.internal.__cell__.padding = 3; + this.internal.__cell__.headerFunction = undefined; + this.internal.__cell__.margins = Object.assign({}, NO_MARGINS); + this.internal.__cell__.margins.width = this.getPageWidth(); + _reset.call(this); + } + }; + + var _reset = function() { + this.internal.__cell__.lastCell = new Cell(); + this.internal.__cell__.pages = 1; + }; + + var Cell = function() { + var _x = arguments[0]; + Object.defineProperty(this, "x", { + enumerable: true, + get: function() { + return _x; + }, + set: function(value) { + _x = value; + } + }); + var _y = arguments[1]; + Object.defineProperty(this, "y", { + enumerable: true, + get: function() { + return _y; + }, + set: function(value) { + _y = value; + } + }); + var _width = arguments[2]; + Object.defineProperty(this, "width", { + enumerable: true, + get: function() { + return _width; + }, + set: function(value) { + _width = value; + } + }); + var _height = arguments[3]; + Object.defineProperty(this, "height", { + enumerable: true, + get: function() { + return _height; + }, + set: function(value) { + _height = value; + } + }); + var _text = arguments[4]; + Object.defineProperty(this, "text", { + enumerable: true, + get: function() { + return _text; + }, + set: function(value) { + _text = value; + } + }); + var _lineNumber = arguments[5]; + Object.defineProperty(this, "lineNumber", { + enumerable: true, + get: function() { + return _lineNumber; + }, + set: function(value) { + _lineNumber = value; + } + }); + var _align = arguments[6]; + Object.defineProperty(this, "align", { + enumerable: true, + get: function() { + return _align; + }, + set: function(value) { + _align = value; + } + }); + + return this; + }; + + Cell.prototype.clone = function() { + return new Cell( + this.x, + this.y, + this.width, + this.height, + this.text, + this.lineNumber, + this.align + ); + }; + + Cell.prototype.toArray = function() { + return [ + this.x, + this.y, + this.width, + this.height, + this.text, + this.lineNumber, + this.align + ]; + }; + + /** + * @name setHeaderFunction + * @function + * @param {function} func + */ + jsPDFAPI.setHeaderFunction = function(func) { + _initialize.call(this); + this.internal.__cell__.headerFunction = + typeof func === "function" ? func : undefined; + return this; + }; + + /** + * @name getTextDimensions + * @function + * @param {string} txt + * @returns {Object} dimensions + */ + jsPDFAPI.getTextDimensions = function(text, options) { + _initialize.call(this); + options = options || {}; + var fontSize = options.fontSize || this.getFontSize(); + var font = options.font || this.getFont(); + var scaleFactor = options.scaleFactor || this.internal.scaleFactor; + var width = 0; + var amountOfLines = 0; + var height = 0; + var tempWidth = 0; + var scope = this; + + if (!Array.isArray(text) && typeof text !== "string") { + if (typeof text === "number") { + text = String(text); + } else { + throw new Error( + "getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings." + ); + } + } + + const maxWidth = options.maxWidth; + if (maxWidth > 0) { + if (typeof text === "string") { + text = this.splitTextToSize(text, maxWidth); + } else if (Object.prototype.toString.call(text) === "[object Array]") { + text = text.reduce(function(acc, textLine) { + return acc.concat(scope.splitTextToSize(textLine, maxWidth)); + }, []); + } + } else { + // Without the else clause, it will not work if you do not pass along maxWidth + text = Array.isArray(text) ? text : [text]; + } + + for (var i = 0; i < text.length; i++) { + tempWidth = this.getStringUnitWidth(text[i], { font: font }) * fontSize; + if (width < tempWidth) { + width = tempWidth; + } + } + + if (width !== 0) { + amountOfLines = text.length; + } + + width = width / scaleFactor; + height = Math.max( + (amountOfLines * fontSize * this.getLineHeightFactor() - + fontSize * (this.getLineHeightFactor() - 1)) / + scaleFactor, + 0 + ); + return { w: width, h: height }; + }; + + /** + * @name cellAddPage + * @function + */ + jsPDFAPI.cellAddPage = function() { + _initialize.call(this); + + this.addPage(); + + var margins = this.internal.__cell__.margins || NO_MARGINS; + this.internal.__cell__.lastCell = new Cell( + margins.left, + margins.top, + undefined, + undefined + ); + this.internal.__cell__.pages += 1; + + return this; + }; + + /** + * @name cell + * @function + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {string} text + * @param {number} lineNumber lineNumber + * @param {string} align + * @return {jsPDF} jsPDF-instance + */ + var cell = (jsPDFAPI.cell = function() { + var currentCell; + + if (arguments[0] instanceof Cell) { + currentCell = arguments[0]; + } else { + currentCell = new Cell( + arguments[0], + arguments[1], + arguments[2], + arguments[3], + arguments[4], + arguments[5] + ); + } + _initialize.call(this); + var lastCell = this.internal.__cell__.lastCell; + var padding = this.internal.__cell__.padding; + var margins = this.internal.__cell__.margins || NO_MARGINS; + var tableHeaderRow = this.internal.__cell__.tableHeaderRow; + var printHeaders = this.internal.__cell__.printHeaders; + // If this is not the first cell, we must change its position + if (typeof lastCell.lineNumber !== "undefined") { + if (lastCell.lineNumber === currentCell.lineNumber) { + //Same line + currentCell.x = (lastCell.x || 0) + (lastCell.width || 0); + currentCell.y = lastCell.y || 0; + } else { + //New line + if ( + lastCell.y + lastCell.height + currentCell.height + margins.bottom > + this.getPageHeight() + ) { + this.cellAddPage(); + currentCell.y = margins.top; + if (printHeaders && tableHeaderRow) { + this.printHeaderRow(currentCell.lineNumber, true); + currentCell.y += tableHeaderRow[0].height; + } + } else { + currentCell.y = lastCell.y + lastCell.height || currentCell.y; + } + } + } + + if (typeof currentCell.text[0] !== "undefined") { + this.rect( + currentCell.x, + currentCell.y, + currentCell.width, + currentCell.height, + printingHeaderRow === true ? "FD" : undefined + ); + if (currentCell.align === "right") { + this.text( + currentCell.text, + currentCell.x + currentCell.width - padding, + currentCell.y + padding, + { align: "right", baseline: "top" } + ); + } else if (currentCell.align === "center") { + this.text( + currentCell.text, + currentCell.x + currentCell.width / 2, + currentCell.y + padding, + { + align: "center", + baseline: "top", + maxWidth: currentCell.width - padding - padding + } + ); + } else { + this.text( + currentCell.text, + currentCell.x + padding, + currentCell.y + padding, + { + align: "left", + baseline: "top", + maxWidth: currentCell.width - padding - padding + } + ); + } + } + this.internal.__cell__.lastCell = currentCell; + return this; + }); + + /** + * Create a table from a set of data. + * @name table + * @function + * @param {Integer} [x] : left-position for top-left corner of table + * @param {Integer} [y] top-position for top-left corner of table + * @param {Object[]} [data] An array of objects containing key-value pairs corresponding to a row of data. + * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost + + * @param {Object} [config.printHeaders] True to print column headers at the top of every page + * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value + * @param {Object} [config.margins] margin values for left, top, bottom, and width + * @param {Object} [config.fontSize] Integer fontSize to use (optional) + * @param {Object} [config.padding] cell-padding in pt to use (optional) + * @param {Object} [config.headerBackgroundColor] default is #c8c8c8 (optional) + * @param {Object} [config.headerTextColor] default is #000 (optional) + * @param {Object} [config.rowStart] callback to handle before print each row (optional) + * @param {Object} [config.cellStart] callback to handle before print each cell (optional) + * @returns {jsPDF} jsPDF-instance + */ + + jsPDFAPI.table = function(x, y, data, headers, config) { + _initialize.call(this); + if (!data) { + throw new Error("No data for PDF table."); + } + + config = config || {}; + + var headerNames = [], + headerLabels = [], + headerAligns = [], + i, + columnMatrix = {}, + columnWidths = {}, + column, + columnMinWidths = [], + j, + tableHeaderConfigs = [], + //set up defaults. If a value is provided in config, defaults will be overwritten: + autoSize = config.autoSize || false, + printHeaders = config.printHeaders === false ? false : true, + fontSize = + config.css && typeof config.css["font-size"] !== "undefined" + ? config.css["font-size"] * 16 + : config.fontSize || 12, + margins = + config.margins || + Object.assign({ width: this.getPageWidth() }, NO_MARGINS), + padding = typeof config.padding === "number" ? config.padding : 3, + headerBackgroundColor = config.headerBackgroundColor || "#c8c8c8", + headerTextColor = config.headerTextColor || "#000"; + + _reset.call(this); + + this.internal.__cell__.printHeaders = printHeaders; + this.internal.__cell__.margins = margins; + this.internal.__cell__.table_font_size = fontSize; + this.internal.__cell__.padding = padding; + this.internal.__cell__.headerBackgroundColor = headerBackgroundColor; + this.internal.__cell__.headerTextColor = headerTextColor; + this.setFontSize(fontSize); + + // Set header values + if (headers === undefined || headers === null) { + // No headers defined so we derive from data + headerNames = Object.keys(data[0]); + headerLabels = headerNames; + headerAligns = headerNames.map(function() { + return "left"; + }); + } else if (Array.isArray(headers) && typeof headers[0] === "object") { + headerNames = headers.map(function(header) { + return header.name; + }); + headerLabels = headers.map(function(header) { + return header.prompt || header.name || ""; + }); + headerAligns = headers.map(function(header) { + return header.align || "left"; + }); + // Split header configs into names and prompts + for (i = 0; i < headers.length; i += 1) { + columnWidths[headers[i].name] = headers[i].width * px2pt; + } + } else if (Array.isArray(headers) && typeof headers[0] === "string") { + headerNames = headers; + headerLabels = headerNames; + headerAligns = headerNames.map(function() { + return "left"; + }); + } + + if ( + autoSize || + (Array.isArray(headers) && typeof headers[0] === "string") + ) { + var headerName; + for (i = 0; i < headerNames.length; i += 1) { + headerName = headerNames[i]; + + // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} + + columnMatrix[headerName] = data.map(function(rec) { + return rec[headerName]; + }); + + // get header width + this.setFont(undefined, "bold"); + columnMinWidths.push( + this.getTextDimensions(headerLabels[i], { + fontSize: this.internal.__cell__.table_font_size, + scaleFactor: this.internal.scaleFactor + }).w + ); + column = columnMatrix[headerName]; + + // get cell widths + this.setFont(undefined, "normal"); + for (j = 0; j < column.length; j += 1) { + columnMinWidths.push( + this.getTextDimensions(column[j], { + fontSize: this.internal.__cell__.table_font_size, + scaleFactor: this.internal.scaleFactor + }).w + ); + } + + // get final column width + columnWidths[headerName] = + Math.max.apply(null, columnMinWidths) + padding + padding; + + //have to reset + columnMinWidths = []; + } + } + + // -- Construct the table + + if (printHeaders) { + var row = {}; + for (i = 0; i < headerNames.length; i += 1) { + row[headerNames[i]] = {}; + row[headerNames[i]].text = headerLabels[i]; + row[headerNames[i]].align = headerAligns[i]; + } + + var rowHeight = calculateLineHeight.call(this, row, columnWidths); + + // Construct the header row + tableHeaderConfigs = headerNames.map(function(value) { + return new Cell( + x, + y, + columnWidths[value], + rowHeight, + row[value].text, + undefined, + row[value].align + ); + }); + + // Store the table header config + this.setTableHeaderRow(tableHeaderConfigs); + + // Print the header for the start of the table + this.printHeaderRow(1, false); + } + + // Construct the data rows + + var align = headers.reduce(function(pv, cv) { + pv[cv.name] = cv.align; + return pv; + }, {}); + for (i = 0; i < data.length; i += 1) { + if ("rowStart" in config && config.rowStart instanceof Function) { + config.rowStart( + { + row: i, + data: data[i] + }, + this + ); + } + var lineHeight = calculateLineHeight.call(this, data[i], columnWidths); + + for (j = 0; j < headerNames.length; j += 1) { + var cellData = data[i][headerNames[j]]; + if ("cellStart" in config && config.cellStart instanceof Function) { + config.cellStart( + { + row: i, + col: j, + data: cellData + }, + this + ); + } + cell.call( + this, + new Cell( + x, + y, + columnWidths[headerNames[j]], + lineHeight, + cellData, + i + 2, + align[headerNames[j]] + ) + ); + } + } + this.internal.__cell__.table_x = x; + this.internal.__cell__.table_y = y; + return this; + }; + + /** + * Calculate the height for containing the highest column + * + * @name calculateLineHeight + * @function + * @param {Object[]} model is the line of data we want to calculate the height of + * @param {Integer[]} columnWidths is size of each column + * @returns {number} lineHeight + * @private + */ + var calculateLineHeight = function calculateLineHeight(model, columnWidths) { + var padding = this.internal.__cell__.padding; + var fontSize = this.internal.__cell__.table_font_size; + var scaleFactor = this.internal.scaleFactor; + + return Object.keys(model) + .map(function(key) { + var value = model[key]; + return this.splitTextToSize( + value.hasOwnProperty("text") ? value.text : value, + columnWidths[key] - padding - padding + ); + }, this) + .map(function(value) { + return ( + (this.getLineHeightFactor() * value.length * fontSize) / scaleFactor + + padding + + padding + ); + }, this) + .reduce(function(pv, cv) { + return Math.max(pv, cv); + }, 0); + }; + + /** + * Store the config for outputting a table header + * + * @name setTableHeaderRow + * @function + * @param {Object[]} config + * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell + * except the lineNumber parameter is excluded + */ + jsPDFAPI.setTableHeaderRow = function(config) { + _initialize.call(this); + this.internal.__cell__.tableHeaderRow = config; + }; + + /** + * Output the store header row + * + * @name printHeaderRow + * @function + * @param {number} lineNumber The line number to output the header at + * @param {boolean} new_page + */ + jsPDFAPI.printHeaderRow = function(lineNumber, new_page) { + _initialize.call(this); + if (!this.internal.__cell__.tableHeaderRow) { + throw new Error("Property tableHeaderRow does not exist."); + } + + var tableHeaderCell; + + printingHeaderRow = true; + if (typeof this.internal.__cell__.headerFunction === "function") { + var position = this.internal.__cell__.headerFunction( + this, + this.internal.__cell__.pages + ); + this.internal.__cell__.lastCell = new Cell( + position[0], + position[1], + position[2], + position[3], + undefined, + -1 + ); + } + this.setFont(undefined, "bold"); + + var tempHeaderConf = []; + for (var i = 0; i < this.internal.__cell__.tableHeaderRow.length; i += 1) { + tableHeaderCell = this.internal.__cell__.tableHeaderRow[i].clone(); + if (new_page) { + tableHeaderCell.y = this.internal.__cell__.margins.top || 0; + tempHeaderConf.push(tableHeaderCell); + } + tableHeaderCell.lineNumber = lineNumber; + var currentTextColor = this.getTextColor(); + this.setTextColor(this.internal.__cell__.headerTextColor); + this.setFillColor(this.internal.__cell__.headerBackgroundColor); + cell.call(this, tableHeaderCell); + this.setTextColor(currentTextColor); + } + if (tempHeaderConf.length > 0) { + this.setTableHeaderRow(tempHeaderConf); + } + this.setFont(undefined, "normal"); + printingHeaderRow = false; + }; +})(jsPDF.API); + +function toLookup(arr) { + return arr.reduce(function(lookup, name, index) { + lookup[name] = index; + + return lookup; + }, {}); +} + +var fontStyleOrder = { + italic: ["italic", "oblique", "normal"], + oblique: ["oblique", "italic", "normal"], + normal: ["normal", "oblique", "italic"] +}; + +var fontStretchOrder = [ + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "normal", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded" +]; + +// For a given font-stretch value, we need to know where to start our search +// from in the fontStretchOrder list. +var fontStretchLookup = toLookup(fontStretchOrder); + +var fontWeights = [100, 200, 300, 400, 500, 600, 700, 800, 900]; +var fontWeightsLookup = toLookup(fontWeights); + +function normalizeFontStretch(stretch) { + stretch = stretch || "normal"; + + return typeof fontStretchLookup[stretch] === "number" ? stretch : "normal"; +} + +function normalizeFontStyle(style) { + style = style || "normal"; + + return fontStyleOrder[style] ? style : "normal"; +} + +function normalizeFontWeight(weight) { + if (!weight) { + return 400; + } + + if (typeof weight === "number") { + // Ignore values which aren't valid font-weights. + return weight >= 100 && weight <= 900 && weight % 100 === 0 ? weight : 400; + } + + if (/^\d00$/.test(weight)) { + return parseInt(weight); + } + + switch (weight) { + case "bold": + return 700; + + case "normal": + default: + return 400; + } +} + +function normalizeFontFace(fontFace) { + var family = fontFace.family.replace(/"|'/g, "").toLowerCase(); + + var style = normalizeFontStyle(fontFace.style); + var weight = normalizeFontWeight(fontFace.weight); + var stretch = normalizeFontStretch(fontFace.stretch); + + return { + family: family, + style: style, + weight: weight, + stretch: stretch, + src: fontFace.src || [], + + // The ref property maps this font-face to the font + // added by the .addFont() method. + ref: fontFace.ref || { + name: family, + style: [stretch, style, weight].join(" ") + } + }; +} + +/** + * Turns a list of font-faces into a map, for easier lookup when resolving + * fonts. + * @private + */ +function buildFontFaceMap(fontFaces) { + var map = {}; + + for (var i = 0; i < fontFaces.length; ++i) { + var normalized = normalizeFontFace(fontFaces[i]); + + var name = normalized.family; + var stretch = normalized.stretch; + var style = normalized.style; + var weight = normalized.weight; + + map[name] = map[name] || {}; + + map[name][stretch] = map[name][stretch] || {}; + map[name][stretch][style] = map[name][stretch][style] || {}; + map[name][stretch][style][weight] = normalized; + } + + return map; +} + +/** + * Searches a map of stretches, weights, etc. in the given direction and + * then, if no match has been found, in the opposite directions. + * + * @param {Object.} matchingSet A map of the various font variations. + * @param {any[]} order The order of the different variations + * @param {number} pivot The starting point of the search in the order list. + * @param {number} dir The initial direction of the search (desc = -1, asc = 1) + * @private + */ + +function searchFromPivot(matchingSet, order, pivot, dir) { + var i; + + for (i = pivot; i >= 0 && i < order.length; i += dir) { + if (matchingSet[order[i]]) { + return matchingSet[order[i]]; + } + } + + for (i = pivot; i >= 0 && i < order.length; i -= dir) { + if (matchingSet[order[i]]) { + return matchingSet[order[i]]; + } + } +} + +function resolveFontStretch(stretch, matchingSet) { + if (matchingSet[stretch]) { + return matchingSet[stretch]; + } + + var pivot = fontStretchLookup[stretch]; + + // If the font-stretch value is normal or more condensed, we want to + // start with a descending search, otherwise we should do ascending. + var dir = pivot <= fontStretchLookup["normal"] ? -1 : 1; + var match = searchFromPivot(matchingSet, fontStretchOrder, pivot, dir); + + if (!match) { + // Since a font-family cannot exist without having at least one stretch value + // we should never reach this point. + throw new Error( + "Could not find a matching font-stretch value for " + stretch + ); + } + + return match; +} + +function resolveFontStyle(fontStyle, matchingSet) { + if (matchingSet[fontStyle]) { + return matchingSet[fontStyle]; + } + + var ordering = fontStyleOrder[fontStyle]; + + for (var i = 0; i < ordering.length; ++i) { + if (matchingSet[ordering[i]]) { + return matchingSet[ordering[i]]; + } + } + + // Since a font-family cannot exist without having at least one style value + // we should never reach this point. + throw new Error("Could not find a matching font-style for " + fontStyle); +} + +function resolveFontWeight(weight, matchingSet) { + if (matchingSet[weight]) { + return matchingSet[weight]; + } + + if (weight === 400 && matchingSet[500]) { + return matchingSet[500]; + } + + if (weight === 500 && matchingSet[400]) { + return matchingSet[400]; + } + + var pivot = fontWeightsLookup[weight]; + + // If the font-stretch value is normal or more condensed, we want to + // start with a descending search, otherwise we should do ascending. + var dir = weight < 400 ? -1 : 1; + var match = searchFromPivot(matchingSet, fontWeights, pivot, dir); + + if (!match) { + // Since a font-family cannot exist without having at least one stretch value + // we should never reach this point. + throw new Error( + "Could not find a matching font-weight for value " + weight + ); + } + + return match; +} + +var defaultGenericFontFamilies = { + "sans-serif": "helvetica", + fixed: "courier", + monospace: "courier", + terminal: "courier", + cursive: "times", + fantasy: "times", + serif: "times" +}; + +var systemFonts = { + caption: "times", + icon: "times", + menu: "times", + "message-box": "times", + "small-caption": "times", + "status-bar": "times" +}; + +function ruleToString(rule) { + return [rule.stretch, rule.style, rule.weight, rule.family].join(" "); +} + +function resolveFontFace(fontFaceMap, rules, opts) { + opts = opts || {}; + + var defaultFontFamily = opts.defaultFontFamily || "times"; + var genericFontFamilies = Object.assign( + {}, + defaultGenericFontFamilies, + opts.genericFontFamilies || {} + ); + + var rule = null; + var matches = null; + + for (var i = 0; i < rules.length; ++i) { + rule = normalizeFontFace(rules[i]); + + if (genericFontFamilies[rule.family]) { + rule.family = genericFontFamilies[rule.family]; + } + + if (fontFaceMap.hasOwnProperty(rule.family)) { + matches = fontFaceMap[rule.family]; + + break; + } + } + + // Always fallback to a known font family. + matches = matches || fontFaceMap[defaultFontFamily]; + + if (!matches) { + // At this point we should definitiely have a font family, but if we + // don't there is something wrong with our configuration + throw new Error( + "Could not find a font-family for the rule '" + + ruleToString(rule) + + "' and default family '" + + defaultFontFamily + + "'." + ); + } + + matches = resolveFontStretch(rule.stretch, matches); + matches = resolveFontStyle(rule.style, matches); + matches = resolveFontWeight(rule.weight, matches); + + if (!matches) { + // We should've fount + throw new Error( + "Failed to resolve a font for the rule '" + ruleToString(rule) + "'." + ); + } + + return matches; +} + +function eatWhiteSpace(input) { + return input.trimLeft(); +} + +function parseQuotedFontFamily(input, quote) { + var index = 0; + + while (index < input.length) { + var current = input.charAt(index); + + if (current === quote) { + return [input.substring(0, index), input.substring(index + 1)]; + } + + index += 1; + } + + // Unexpected end of input + return null; +} + +function parseNonQuotedFontFamily(input) { + // It implements part of the identifier parser here: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + // + // NOTE: This parser pretty much ignores escaped identifiers and that there is a thing called unicode. + // + // Breakdown of regexp: + // -[a-z_] - when identifier starts with a hyphen, you're not allowed to have another hyphen or a digit + // [a-z_] - allow a-z and underscore at beginning of input + // [a-z0-9_-]* - after that, anything goes + var match = input.match(/^(-[a-z_]|[a-z_])[a-z0-9_-]*/i); + + // non quoted value contains illegal characters + if (match === null) { + return null; + } + + return [match[0], input.substring(match[0].length)]; +} + +var defaultFont = ["times"]; + +function parseFontFamily(input) { + var result = []; + var ch, parsed; + var remaining = input.trim(); + + if (remaining === "") { + return defaultFont; + } + + if (remaining in systemFonts) { + return [systemFonts[remaining]]; + } + + while (remaining !== "") { + parsed = null; + remaining = eatWhiteSpace(remaining); + ch = remaining.charAt(0); + + switch (ch) { + case '"': + case "'": + parsed = parseQuotedFontFamily(remaining.substring(1), ch); + break; + + default: + parsed = parseNonQuotedFontFamily(remaining); + break; + } + + if (parsed === null) { + return defaultFont; + } + + result.push(parsed[0]); + + remaining = eatWhiteSpace(parsed[1]); + + // We expect end of input or a comma separator here + if (remaining !== "" && remaining.charAt(0) !== ",") { + return defaultFont; + } + + remaining = remaining.replace(/^,/, ""); + } + + return result; +} + +/* eslint-disable no-fallthrough */ + +/** + * This plugin mimics the HTML5 CanvasRenderingContext2D. + * + * The goal is to provide a way for current canvas implementations to print directly to a PDF. + * + * @name context2d + * @module + */ +(function(jsPDFAPI) { + var ContextLayer = function(ctx) { + ctx = ctx || {}; + this.isStrokeTransparent = ctx.isStrokeTransparent || false; + this.strokeOpacity = ctx.strokeOpacity || 1; + this.strokeStyle = ctx.strokeStyle || "#000000"; + this.fillStyle = ctx.fillStyle || "#000000"; + this.isFillTransparent = ctx.isFillTransparent || false; + this.fillOpacity = ctx.fillOpacity || 1; + this.font = ctx.font || "10px sans-serif"; + this.textBaseline = ctx.textBaseline || "alphabetic"; + this.textAlign = ctx.textAlign || "left"; + this.lineWidth = ctx.lineWidth || 1; + this.lineJoin = ctx.lineJoin || "miter"; + this.lineCap = ctx.lineCap || "butt"; + this.path = ctx.path || []; + this.transform = + typeof ctx.transform !== "undefined" + ? ctx.transform.clone() + : new Matrix(); + this.globalCompositeOperation = ctx.globalCompositeOperation || "normal"; + this.globalAlpha = ctx.globalAlpha || 1.0; + this.clip_path = ctx.clip_path || []; + this.currentPoint = ctx.currentPoint || new Point(); + this.miterLimit = ctx.miterLimit || 10.0; + this.lastPoint = ctx.lastPoint || new Point(); + this.lineDashOffset = ctx.lineDashOffset || 0.0; + this.lineDash = ctx.lineDash || []; + this.margin = ctx.margin || [0, 0, 0, 0]; + this.prevPageLastElemOffset = ctx.prevPageLastElemOffset || 0; + + this.ignoreClearRect = + typeof ctx.ignoreClearRect === "boolean" ? ctx.ignoreClearRect : true; + return this; + }; + + //stub + var f2, + getHorizontalCoordinateString, + getVerticalCoordinateString, + getHorizontalCoordinate, + getVerticalCoordinate, + Point, + Rectangle, + Matrix, + _ctx; + jsPDFAPI.events.push([ + "initialized", + function() { + this.context2d = new Context2D(this); + + f2 = this.internal.f2; + getHorizontalCoordinateString = this.internal.getCoordinateString; + getVerticalCoordinateString = this.internal.getVerticalCoordinateString; + getHorizontalCoordinate = this.internal.getHorizontalCoordinate; + getVerticalCoordinate = this.internal.getVerticalCoordinate; + Point = this.internal.Point; + Rectangle = this.internal.Rectangle; + Matrix = this.internal.Matrix; + _ctx = new ContextLayer(); + } + ]); + + var Context2D = function(pdf) { + Object.defineProperty(this, "canvas", { + get: function() { + return { parentNode: false, style: false }; + } + }); + + var _pdf = pdf; + Object.defineProperty(this, "pdf", { + get: function() { + return _pdf; + } + }); + + var _pageWrapXEnabled = false; + /** + * @name pageWrapXEnabled + * @type {boolean} + * @default false + */ + Object.defineProperty(this, "pageWrapXEnabled", { + get: function() { + return _pageWrapXEnabled; + }, + set: function(value) { + _pageWrapXEnabled = Boolean(value); + } + }); + + var _pageWrapYEnabled = false; + /** + * @name pageWrapYEnabled + * @type {boolean} + * @default true + */ + Object.defineProperty(this, "pageWrapYEnabled", { + get: function() { + return _pageWrapYEnabled; + }, + set: function(value) { + _pageWrapYEnabled = Boolean(value); + } + }); + + var _posX = 0; + /** + * @name posX + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "posX", { + get: function() { + return _posX; + }, + set: function(value) { + if (!isNaN(value)) { + _posX = value; + } + } + }); + + var _posY = 0; + /** + * @name posY + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "posY", { + get: function() { + return _posY; + }, + set: function(value) { + if (!isNaN(value)) { + _posY = value; + } + } + }); + + /** + * Gets or sets the page margin when using auto paging. Has no effect when {@link autoPaging} is off. + * @name margin + * @type {number|number[]} + * @default [0, 0, 0, 0] + */ + Object.defineProperty(this, "margin", { + get: function() { + return _ctx.margin; + }, + set: function(value) { + var margin; + if (typeof value === "number") { + margin = [value, value, value, value]; + } else { + margin = new Array(4); + margin[0] = value[0]; + margin[1] = value.length >= 2 ? value[1] : margin[0]; + margin[2] = value.length >= 3 ? value[2] : margin[0]; + margin[3] = value.length >= 4 ? value[3] : margin[1]; + } + _ctx.margin = margin; + } + }); + + var _autoPaging = false; + /** + * Gets or sets the auto paging mode. When auto paging is enabled, the context2d will automatically draw on the + * next page if a shape or text chunk doesn't fit entirely on the current page. The context2d will create new + * pages if required. + * + * Context2d supports different modes: + *
      + *
    • + * false: Auto paging is disabled. + *
    • + *
    • + * true or 'slice': Will cut shapes or text chunks across page breaks. Will possibly + * slice text in half, making it difficult to read. + *
    • + *
    • + * 'text': Trys not to cut text in half across page breaks. Works best for documents consisting + * mostly of a single column of text. + *
    • + *
    + * @name Context2D#autoPaging + * @type {boolean|"slice"|"text"} + * @default false + */ + Object.defineProperty(this, "autoPaging", { + get: function() { + return _autoPaging; + }, + set: function(value) { + _autoPaging = value; + } + }); + + var lastBreak = 0; + /** + * @name lastBreak + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "lastBreak", { + get: function() { + return lastBreak; + }, + set: function(value) { + lastBreak = value; + } + }); + + var pageBreaks = []; + /** + * Y Position of page breaks. + * @name pageBreaks + * @type {number} + * @default 0 + */ + Object.defineProperty(this, "pageBreaks", { + get: function() { + return pageBreaks; + }, + set: function(value) { + pageBreaks = value; + } + }); + + /** + * @name ctx + * @type {object} + * @default {} + */ + Object.defineProperty(this, "ctx", { + get: function() { + return _ctx; + }, + set: function(value) { + if (value instanceof ContextLayer) { + _ctx = value; + } + } + }); + + /** + * @name path + * @type {array} + * @default [] + */ + Object.defineProperty(this, "path", { + get: function() { + return _ctx.path; + }, + set: function(value) { + _ctx.path = value; + } + }); + + /** + * @name ctxStack + * @type {array} + * @default [] + */ + var _ctxStack = []; + Object.defineProperty(this, "ctxStack", { + get: function() { + return _ctxStack; + }, + set: function(value) { + _ctxStack = value; + } + }); + + /** + * Sets or returns the color, gradient, or pattern used to fill the drawing + * + * @name fillStyle + * @default #000000 + * @property {(color|gradient|pattern)} value The color of the drawing. Default value is #000000
    + * A gradient object (linear or radial) used to fill the drawing (not supported by context2d)
    + * A pattern object to use to fill the drawing (not supported by context2d) + */ + Object.defineProperty(this, "fillStyle", { + get: function() { + return this.ctx.fillStyle; + }, + set: function(value) { + var rgba; + rgba = getRGBA(value); + + this.ctx.fillStyle = rgba.style; + this.ctx.isFillTransparent = rgba.a === 0; + this.ctx.fillOpacity = rgba.a; + + this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); + this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); + } + }); + + /** + * Sets or returns the color, gradient, or pattern used for strokes + * + * @name strokeStyle + * @default #000000 + * @property {color} color A CSS color value that indicates the stroke color of the drawing. Default value is #000000 (not supported by context2d) + * @property {gradient} gradient A gradient object (linear or radial) used to create a gradient stroke (not supported by context2d) + * @property {pattern} pattern A pattern object used to create a pattern stroke (not supported by context2d) + */ + Object.defineProperty(this, "strokeStyle", { + get: function() { + return this.ctx.strokeStyle; + }, + set: function(value) { + var rgba = getRGBA(value); + + this.ctx.strokeStyle = rgba.style; + this.ctx.isStrokeTransparent = rgba.a === 0; + this.ctx.strokeOpacity = rgba.a; + + if (rgba.a === 0) { + this.pdf.setDrawColor(255, 255, 255); + } else if (rgba.a === 1) { + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } else { + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } + } + }); + + /** + * Sets or returns the style of the end caps for a line + * + * @name lineCap + * @default butt + * @property {(butt|round|square)} lineCap butt A flat edge is added to each end of the line
    + * round A rounded end cap is added to each end of the line
    + * square A square end cap is added to each end of the line
    + */ + Object.defineProperty(this, "lineCap", { + get: function() { + return this.ctx.lineCap; + }, + set: function(value) { + if (["butt", "round", "square"].indexOf(value) !== -1) { + this.ctx.lineCap = value; + this.pdf.setLineCap(value); + } + } + }); + + /** + * Sets or returns the current line width + * + * @name lineWidth + * @default 1 + * @property {number} lineWidth The current line width, in pixels + */ + Object.defineProperty(this, "lineWidth", { + get: function() { + return this.ctx.lineWidth; + }, + set: function(value) { + if (!isNaN(value)) { + this.ctx.lineWidth = value; + this.pdf.setLineWidth(value); + } + } + }); + + /** + * Sets or returns the type of corner created, when two lines meet + */ + Object.defineProperty(this, "lineJoin", { + get: function() { + return this.ctx.lineJoin; + }, + set: function(value) { + if (["bevel", "round", "miter"].indexOf(value) !== -1) { + this.ctx.lineJoin = value; + this.pdf.setLineJoin(value); + } + } + }); + + /** + * A number specifying the miter limit ratio in coordinate space units. Zero, negative, Infinity, and NaN values are ignored. The default value is 10.0. + * + * @name miterLimit + * @default 10 + */ + Object.defineProperty(this, "miterLimit", { + get: function() { + return this.ctx.miterLimit; + }, + set: function(value) { + if (!isNaN(value)) { + this.ctx.miterLimit = value; + this.pdf.setMiterLimit(value); + } + } + }); + + Object.defineProperty(this, "textBaseline", { + get: function() { + return this.ctx.textBaseline; + }, + set: function(value) { + this.ctx.textBaseline = value; + } + }); + + Object.defineProperty(this, "textAlign", { + get: function() { + return this.ctx.textAlign; + }, + set: function(value) { + if (["right", "end", "center", "left", "start"].indexOf(value) !== -1) { + this.ctx.textAlign = value; + } + } + }); + + var _fontFaceMap = null; + + function getFontFaceMap(pdf, fontFaces) { + if (_fontFaceMap === null) { + var fontMap = pdf.getFontList(); + + var convertedFontFaces = convertToFontFaces(fontMap); + + _fontFaceMap = buildFontFaceMap(convertedFontFaces.concat(fontFaces)); + } + + return _fontFaceMap; + } + + function convertToFontFaces(fontMap) { + var fontFaces = []; + + Object.keys(fontMap).forEach(function(family) { + var styles = fontMap[family]; + + styles.forEach(function(style) { + var fontFace = null; + + switch (style) { + case "bold": + fontFace = { + family: family, + weight: "bold" + }; + break; + + case "italic": + fontFace = { + family: family, + style: "italic" + }; + break; + + case "bolditalic": + fontFace = { + family: family, + weight: "bold", + style: "italic" + }; + break; + + case "": + case "normal": + fontFace = { + family: family + }; + break; + } + + // If font-face is still null here, it is a font with some styling we don't recognize and + // cannot map or it is a font added via the fontFaces option of .html(). + if (fontFace !== null) { + fontFace.ref = { + name: family, + style: style + }; + + fontFaces.push(fontFace); + } + }); + }); + + return fontFaces; + } + + var _fontFaces = null; + /** + * A map of available font-faces, as passed in the options of + * .html(). If set a limited implementation of the font style matching + * algorithm defined by https://www.w3.org/TR/css-fonts-3/#font-matching-algorithm + * will be used. If not set it will fallback to previous behavior. + */ + + Object.defineProperty(this, "fontFaces", { + get: function() { + return _fontFaces; + }, + set: function(value) { + _fontFaceMap = null; + _fontFaces = value; + } + }); + + Object.defineProperty(this, "font", { + get: function() { + return this.ctx.font; + }, + set: function(value) { + this.ctx.font = value; + var rx, matches; + + //source: https://stackoverflow.com/a/10136041 + // eslint-disable-next-line no-useless-escape + rx = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z]+?)\s*$/i; + matches = rx.exec(value); + if (matches !== null) { + var fontStyle = matches[1]; + matches[2]; + var fontWeight = matches[3]; + var fontSize = matches[4]; + matches[5]; + var fontFamily = matches[6]; + } else { + return; + } + var rxFontSize = /^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i; + var fontSizeUnit = rxFontSize.exec(fontSize)[2]; + + if ("px" === fontSizeUnit) { + fontSize = Math.floor( + parseFloat(fontSize) * this.pdf.internal.scaleFactor + ); + } else if ("em" === fontSizeUnit) { + fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); + } else { + fontSize = Math.floor( + parseFloat(fontSize) * this.pdf.internal.scaleFactor + ); + } + + this.pdf.setFontSize(fontSize); + var parts = parseFontFamily(fontFamily); + + if (this.fontFaces) { + var fontFaceMap = getFontFaceMap(this.pdf, this.fontFaces); + + var rules = parts.map(function(ff) { + return { + family: ff, + stretch: "normal", // TODO: Extract font-stretch from font rule (perhaps write proper parser for it?) + weight: fontWeight, + style: fontStyle + }; + }); + + var font = resolveFontFace(fontFaceMap, rules); + this.pdf.setFont(font.ref.name, font.ref.style); + return; + } + + var style = ""; + if ( + fontWeight === "bold" || + parseInt(fontWeight, 10) >= 700 || + fontStyle === "bold" + ) { + style = "bold"; + } + + if (fontStyle === "italic") { + style += "italic"; + } + + if (style.length === 0) { + style = "normal"; + } + var jsPdfFontName = ""; + + var fallbackFonts = { + arial: "Helvetica", + Arial: "Helvetica", + verdana: "Helvetica", + Verdana: "Helvetica", + helvetica: "Helvetica", + Helvetica: "Helvetica", + "sans-serif": "Helvetica", + fixed: "Courier", + monospace: "Courier", + terminal: "Courier", + cursive: "Times", + fantasy: "Times", + serif: "Times" + }; + + for (var i = 0; i < parts.length; i++) { + if ( + this.pdf.internal.getFont(parts[i], style, { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + break; + } else if ( + style === "bolditalic" && + this.pdf.internal.getFont(parts[i], "bold", { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + style = "bold"; + } else if ( + this.pdf.internal.getFont(parts[i], "normal", { + noFallback: true, + disableWarning: true + }) !== undefined + ) { + jsPdfFontName = parts[i]; + style = "normal"; + break; + } + } + if (jsPdfFontName === "") { + for (var j = 0; j < parts.length; j++) { + if (fallbackFonts[parts[j]]) { + jsPdfFontName = fallbackFonts[parts[j]]; + break; + } + } + } + jsPdfFontName = jsPdfFontName === "" ? "Times" : jsPdfFontName; + this.pdf.setFont(jsPdfFontName, style); + } + }); + + Object.defineProperty(this, "globalCompositeOperation", { + get: function() { + return this.ctx.globalCompositeOperation; + }, + set: function(value) { + this.ctx.globalCompositeOperation = value; + } + }); + + Object.defineProperty(this, "globalAlpha", { + get: function() { + return this.ctx.globalAlpha; + }, + set: function(value) { + this.ctx.globalAlpha = value; + } + }); + + /** + * A float specifying the amount of the line dash offset. The default value is 0.0. + * + * @name lineDashOffset + * @default 0.0 + */ + Object.defineProperty(this, "lineDashOffset", { + get: function() { + return this.ctx.lineDashOffset; + }, + set: function(value) { + this.ctx.lineDashOffset = value; + setLineDash.call(this); + } + }); + + // Not HTML API + Object.defineProperty(this, "lineDash", { + get: function() { + return this.ctx.lineDash; + }, + set: function(value) { + this.ctx.lineDash = value; + setLineDash.call(this); + } + }); + + // Not HTML API + Object.defineProperty(this, "ignoreClearRect", { + get: function() { + return this.ctx.ignoreClearRect; + }, + set: function(value) { + this.ctx.ignoreClearRect = Boolean(value); + } + }); + }; + + /** + * Sets the line dash pattern used when stroking lines. + * @name setLineDash + * @function + * @description It uses an array of values that specify alternating lengths of lines and gaps which describe the pattern. + */ + Context2D.prototype.setLineDash = function(dashArray) { + this.lineDash = dashArray; + }; + + /** + * gets the current line dash pattern. + * @name getLineDash + * @function + * @returns {Array} An Array of numbers that specify distances to alternately draw a line and a gap (in coordinate space units). If the number, when setting the elements, is odd, the elements of the array get copied and concatenated. For example, setting the line dash to [5, 15, 25] will result in getting back [5, 15, 25, 5, 15, 25]. + */ + Context2D.prototype.getLineDash = function() { + if (this.lineDash.length % 2) { + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getLineDash#return_value + return this.lineDash.concat(this.lineDash); + } else { + // The copied value is returned to prevent contamination from outside. + return this.lineDash.slice(); + } + }; + + Context2D.prototype.fill = function() { + pathPreProcess.call(this, "fill", false); + }; + + /** + * Actually draws the path you have defined + * + * @name stroke + * @function + * @description The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. The default color is black. + */ + Context2D.prototype.stroke = function() { + pathPreProcess.call(this, "stroke", false); + }; + + /** + * Begins a path, or resets the current + * + * @name beginPath + * @function + * @description The beginPath() method begins a path, or resets the current path. + */ + Context2D.prototype.beginPath = function() { + this.path = [ + { + type: "begin" + } + ]; + }; + + /** + * Moves the path to the specified point in the canvas, without creating a line + * + * @name moveTo + * @function + * @param x {Number} The x-coordinate of where to move the path to + * @param y {Number} The y-coordinate of where to move the path to + */ + Context2D.prototype.moveTo = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console.error("jsPDF.context2d.moveTo: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.moveTo"); + } + + var pt = this.ctx.transform.applyToPoint(new Point(x, y)); + + this.path.push({ + type: "mt", + x: pt.x, + y: pt.y + }); + this.ctx.lastPoint = new Point(x, y); + }; + + /** + * Creates a path from the current point back to the starting point + * + * @name closePath + * @function + * @description The closePath() method creates a path from the current point back to the starting point. + */ + Context2D.prototype.closePath = function() { + var pathBegin = new Point(0, 0); + var i = 0; + for (i = this.path.length - 1; i !== -1; i--) { + if (this.path[i].type === "begin") { + if ( + typeof this.path[i + 1] === "object" && + typeof this.path[i + 1].x === "number" + ) { + pathBegin = new Point(this.path[i + 1].x, this.path[i + 1].y); + break; + } + } + } + this.path.push({ + type: "close" + }); + this.ctx.lastPoint = new Point(pathBegin.x, pathBegin.y); + }; + + /** + * Adds a new point and creates a line to that point from the last specified point in the canvas + * + * @name lineTo + * @function + * @param x The x-coordinate of where to create the line to + * @param y The y-coordinate of where to create the line to + * @description The lineTo() method adds a new point and creates a line TO that point FROM the last specified point in the canvas (this method does not draw the line). + */ + Context2D.prototype.lineTo = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console.error("jsPDF.context2d.lineTo: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.lineTo"); + } + + var pt = this.ctx.transform.applyToPoint(new Point(x, y)); + + this.path.push({ + type: "lt", + x: pt.x, + y: pt.y + }); + this.ctx.lastPoint = new Point(pt.x, pt.y); + }; + + /** + * Clips a region of any shape and size from the original canvas + * + * @name clip + * @function + * @description The clip() method clips a region of any shape and size from the original canvas. + */ + Context2D.prototype.clip = function() { + this.ctx.clip_path = JSON.parse(JSON.stringify(this.path)); + pathPreProcess.call(this, null, true); + }; + + /** + * Creates a cubic Bézier curve + * + * @name quadraticCurveTo + * @function + * @param cpx {Number} The x-coordinate of the Bézier control point + * @param cpy {Number} The y-coordinate of the Bézier control point + * @param x {Number} The x-coordinate of the ending point + * @param y {Number} The y-coordinate of the ending point + * @description The quadraticCurveTo() method adds a point to the current path by using the specified control points that represent a quadratic Bézier curve.

    A quadratic Bézier curve requires two points. The first point is a control point that is used in the quadratic Bézier calculation and the second point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. + */ + Context2D.prototype.quadraticCurveTo = function(cpx, cpy, x, y) { + if (isNaN(x) || isNaN(y) || isNaN(cpx) || isNaN(cpy)) { + console.error( + "jsPDF.context2d.quadraticCurveTo: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.quadraticCurveTo" + ); + } + + var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); + var pt1 = this.ctx.transform.applyToPoint(new Point(cpx, cpy)); + + this.path.push({ + type: "qct", + x1: pt1.x, + y1: pt1.y, + x: pt0.x, + y: pt0.y + }); + this.ctx.lastPoint = new Point(pt0.x, pt0.y); + }; + + /** + * Creates a cubic Bézier curve + * + * @name bezierCurveTo + * @function + * @param cp1x {Number} The x-coordinate of the first Bézier control point + * @param cp1y {Number} The y-coordinate of the first Bézier control point + * @param cp2x {Number} The x-coordinate of the second Bézier control point + * @param cp2y {Number} The y-coordinate of the second Bézier control point + * @param x {Number} The x-coordinate of the ending point + * @param y {Number} The y-coordinate of the ending point + * @description The bezierCurveTo() method adds a point to the current path by using the specified control points that represent a cubic Bézier curve.

    A cubic bezier curve requires three points. The first two points are control points that are used in the cubic Bézier calculation and the last point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. + */ + Context2D.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(cp1x) || + isNaN(cp1y) || + isNaN(cp2x) || + isNaN(cp2y) + ) { + console.error( + "jsPDF.context2d.bezierCurveTo: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.bezierCurveTo" + ); + } + var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); + var pt1 = this.ctx.transform.applyToPoint(new Point(cp1x, cp1y)); + var pt2 = this.ctx.transform.applyToPoint(new Point(cp2x, cp2y)); + + this.path.push({ + type: "bct", + x1: pt1.x, + y1: pt1.y, + x2: pt2.x, + y2: pt2.y, + x: pt0.x, + y: pt0.y + }); + this.ctx.lastPoint = new Point(pt0.x, pt0.y); + }; + + /** + * Creates an arc/curve (used to create circles, or parts of circles) + * + * @name arc + * @function + * @param x {Number} The x-coordinate of the center of the circle + * @param y {Number} The y-coordinate of the center of the circle + * @param radius {Number} The radius of the circle + * @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {Number} The ending angle, in radians + * @param counterclockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @description The arc() method creates an arc/curve (used to create circles, or parts of circles). + */ + Context2D.prototype.arc = function( + x, + y, + radius, + startAngle, + endAngle, + counterclockwise + ) { + if ( + isNaN(x) || + isNaN(y) || + isNaN(radius) || + isNaN(startAngle) || + isNaN(endAngle) + ) { + console.error("jsPDF.context2d.arc: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.arc"); + } + counterclockwise = Boolean(counterclockwise); + + if (!this.ctx.transform.isIdentity) { + var xpt = this.ctx.transform.applyToPoint(new Point(x, y)); + x = xpt.x; + y = xpt.y; + + var x_radPt = this.ctx.transform.applyToPoint(new Point(0, radius)); + var x_radPt0 = this.ctx.transform.applyToPoint(new Point(0, 0)); + radius = Math.sqrt( + Math.pow(x_radPt.x - x_radPt0.x, 2) + + Math.pow(x_radPt.y - x_radPt0.y, 2) + ); + } + if (Math.abs(endAngle - startAngle) >= 2 * Math.PI) { + startAngle = 0; + endAngle = 2 * Math.PI; + } + + this.path.push({ + type: "arc", + x: x, + y: y, + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + counterclockwise: counterclockwise + }); + // this.ctx.lastPoint(new Point(pt.x,pt.y)); + }; + + /** + * Creates an arc/curve between two tangents + * + * @name arcTo + * @function + * @param x1 {Number} The x-coordinate of the first tangent + * @param y1 {Number} The y-coordinate of the first tangent + * @param x2 {Number} The x-coordinate of the second tangent + * @param y2 {Number} The y-coordinate of the second tangent + * @param radius The radius of the arc + * @description The arcTo() method creates an arc/curve between two tangents on the canvas. + */ + // eslint-disable-next-line no-unused-vars + Context2D.prototype.arcTo = function(x1, y1, x2, y2, radius) { + throw new Error("arcTo not implemented."); + }; + + /** + * Creates a rectangle + * + * @name rect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The rect() method creates a rectangle. + */ + Context2D.prototype.rect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console.error("jsPDF.context2d.rect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.rect"); + } + this.moveTo(x, y); + this.lineTo(x + w, y); + this.lineTo(x + w, y + h); + this.lineTo(x, y + h); + this.lineTo(x, y); + this.lineTo(x + w, y); + this.lineTo(x, y); + }; + + /** + * Draws a "filled" rectangle + * + * @name fillRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The fillRect() method draws a "filled" rectangle. The default color of the fill is black. + */ + Context2D.prototype.fillRect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console.error("jsPDF.context2d.fillRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.fillRect"); + } + if (isFillTransparent.call(this)) { + return; + } + var tmp = {}; + if (this.lineCap !== "butt") { + tmp.lineCap = this.lineCap; + this.lineCap = "butt"; + } + if (this.lineJoin !== "miter") { + tmp.lineJoin = this.lineJoin; + this.lineJoin = "miter"; + } + + this.beginPath(); + this.rect(x, y, w, h); + this.fill(); + + if (tmp.hasOwnProperty("lineCap")) { + this.lineCap = tmp.lineCap; + } + if (tmp.hasOwnProperty("lineJoin")) { + this.lineJoin = tmp.lineJoin; + } + }; + + /** + * Draws a rectangle (no fill) + * + * @name strokeRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle, in pixels + * @param h {Number} The height of the rectangle, in pixels + * @description The strokeRect() method draws a rectangle (no fill). The default color of the stroke is black. + */ + Context2D.prototype.strokeRect = function strokeRect(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console.error("jsPDF.context2d.strokeRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.strokeRect"); + } + if (isStrokeTransparent.call(this)) { + return; + } + this.beginPath(); + this.rect(x, y, w, h); + this.stroke(); + }; + + /** + * Clears the specified pixels within a given rectangle + * + * @name clearRect + * @function + * @param x {Number} The x-coordinate of the upper-left corner of the rectangle + * @param y {Number} The y-coordinate of the upper-left corner of the rectangle + * @param w {Number} The width of the rectangle to clear, in pixels + * @param h {Number} The height of the rectangle to clear, in pixels + * @description We cannot clear PDF commands that were already written to PDF, so we use white instead.
    + * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set. + * This results in all calls to clearRect() to do nothing, and keep the canvas transparent. + * This flag is stored in the save/restore context and is managed the same way as other drawing states. + * + */ + Context2D.prototype.clearRect = function(x, y, w, h) { + if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { + console.error("jsPDF.context2d.clearRect: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.clearRect"); + } + if (this.ignoreClearRect) { + return; + } + + this.fillStyle = "#ffffff"; + this.fillRect(x, y, w, h); + }; + + /** + * Saves the state of the current context + * + * @name save + * @function + */ + Context2D.prototype.save = function(doStackPush) { + doStackPush = typeof doStackPush === "boolean" ? doStackPush : true; + var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; + for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { + this.pdf.setPage(i + 1); + this.pdf.internal.out("q"); + } + this.pdf.setPage(tmpPageNumber); + + if (doStackPush) { + this.ctx.fontSize = this.pdf.internal.getFontSize(); + var ctx = new ContextLayer(this.ctx); + this.ctxStack.push(this.ctx); + this.ctx = ctx; + } + }; + + /** + * Returns previously saved path state and attributes + * + * @name restore + * @function + */ + Context2D.prototype.restore = function(doStackPop) { + doStackPop = typeof doStackPop === "boolean" ? doStackPop : true; + var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; + for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { + this.pdf.setPage(i + 1); + this.pdf.internal.out("Q"); + } + this.pdf.setPage(tmpPageNumber); + + if (doStackPop && this.ctxStack.length !== 0) { + this.ctx = this.ctxStack.pop(); + this.fillStyle = this.ctx.fillStyle; + this.strokeStyle = this.ctx.strokeStyle; + this.font = this.ctx.font; + this.lineCap = this.ctx.lineCap; + this.lineWidth = this.ctx.lineWidth; + this.lineJoin = this.ctx.lineJoin; + this.lineDash = this.ctx.lineDash; + this.lineDashOffset = this.ctx.lineDashOffset; + } + }; + + /** + * @name toDataURL + * @function + */ + Context2D.prototype.toDataURL = function() { + throw new Error("toDataUrl not implemented."); + }; + + //helper functions + + /** + * Get the decimal values of r, g, b and a + * + * @name getRGBA + * @function + * @private + * @ignore + */ + var getRGBA = function(style) { + var rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; + var rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/; + var rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; + + var r, g, b, a; + + if (style.isCanvasGradient === true) { + style = style.getColor(); + } + + if (!style) { + return { r: 0, g: 0, b: 0, a: 0, style: style }; + } + + if (rxTransparent.test(style)) { + r = 0; + g = 0; + b = 0; + a = 0; + } else { + var matches = rxRgb.exec(style); + if (matches !== null) { + r = parseInt(matches[1]); + g = parseInt(matches[2]); + b = parseInt(matches[3]); + a = 1; + } else { + matches = rxRgba.exec(style); + if (matches !== null) { + r = parseInt(matches[1]); + g = parseInt(matches[2]); + b = parseInt(matches[3]); + a = parseFloat(matches[4]); + } else { + a = 1; + + if (typeof style === "string" && style.charAt(0) !== "#") { + var rgbColor = new RGBColor(style); + if (rgbColor.ok) { + style = rgbColor.toHex(); + } else { + style = "#000000"; + } + } + + if (style.length === 4) { + r = style.substring(1, 2); + r += r; + g = style.substring(2, 3); + g += g; + b = style.substring(3, 4); + b += b; + } else { + r = style.substring(1, 3); + g = style.substring(3, 5); + b = style.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + } + } + return { r: r, g: g, b: b, a: a, style: style }; + }; + + /** + * @name isFillTransparent + * @function + * @private + * @ignore + * @returns {Boolean} + */ + var isFillTransparent = function() { + return this.ctx.isFillTransparent || this.globalAlpha == 0; + }; + + /** + * @name isStrokeTransparent + * @function + * @private + * @ignore + * @returns {Boolean} + */ + var isStrokeTransparent = function() { + return Boolean(this.ctx.isStrokeTransparent || this.globalAlpha == 0); + }; + + /** + * Draws "filled" text on the canvas + * + * @name fillText + * @function + * @param text {String} Specifies the text that will be written on the canvas + * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) + * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) + * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels + * @description The fillText() method draws filled text on the canvas. The default color of the text is black. + */ + Context2D.prototype.fillText = function(text, x, y, maxWidth) { + if (isNaN(x) || isNaN(y) || typeof text !== "string") { + console.error("jsPDF.context2d.fillText: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.fillText"); + } + maxWidth = isNaN(maxWidth) ? undefined : maxWidth; + if (isFillTransparent.call(this)) { + return; + } + + var degs = rad2deg(this.ctx.transform.rotation); + + // We only use X axis as scale hint + var scale = this.ctx.transform.scaleX; + + putText.call(this, { + text: text, + x: x, + y: y, + scale: scale, + angle: degs, + align: this.textAlign, + maxWidth: maxWidth + }); + }; + + /** + * Draws text on the canvas (no fill) + * + * @name strokeText + * @function + * @param text {String} Specifies the text that will be written on the canvas + * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) + * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) + * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels + * @description The strokeText() method draws text (with no fill) on the canvas. The default color of the text is black. + */ + Context2D.prototype.strokeText = function(text, x, y, maxWidth) { + if (isNaN(x) || isNaN(y) || typeof text !== "string") { + console.error("jsPDF.context2d.strokeText: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.strokeText"); + } + if (isStrokeTransparent.call(this)) { + return; + } + + maxWidth = isNaN(maxWidth) ? undefined : maxWidth; + + var degs = rad2deg(this.ctx.transform.rotation); + var scale = this.ctx.transform.scaleX; + + putText.call(this, { + text: text, + x: x, + y: y, + scale: scale, + renderingMode: "stroke", + angle: degs, + align: this.textAlign, + maxWidth: maxWidth + }); + }; + + /** + * Returns an object that contains the width of the specified text + * + * @name measureText + * @function + * @param text {String} The text to be measured + * @description The measureText() method returns an object that contains the width of the specified text, in pixels. + * @returns {Number} + */ + Context2D.prototype.measureText = function(text) { + if (typeof text !== "string") { + console.error( + "jsPDF.context2d.measureText: Invalid arguments", + arguments + ); + throw new Error( + "Invalid arguments passed to jsPDF.context2d.measureText" + ); + } + var pdf = this.pdf; + var k = this.pdf.internal.scaleFactor; + + var fontSize = pdf.internal.getFontSize(); + var txtWidth = + (pdf.getStringUnitWidth(text) * fontSize) / pdf.internal.scaleFactor; + txtWidth *= Math.round(((k * 96) / 72) * 10000) / 10000; + + var TextMetrics = function(options) { + options = options || {}; + var _width = options.width || 0; + Object.defineProperty(this, "width", { + get: function() { + return _width; + } + }); + return this; + }; + return new TextMetrics({ width: txtWidth }); + }; + + //Transformations + + /** + * Scales the current drawing bigger or smaller + * + * @name scale + * @function + * @param scalewidth {Number} Scales the width of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) + * @param scaleheight {Number} Scales the height of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) + * @description The scale() method scales the current drawing, bigger or smaller. + */ + Context2D.prototype.scale = function(scalewidth, scaleheight) { + if (isNaN(scalewidth) || isNaN(scaleheight)) { + console.error("jsPDF.context2d.scale: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.scale"); + } + var matrix = new Matrix(scalewidth, 0.0, 0.0, scaleheight, 0.0, 0.0); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; + + /** + * Rotates the current drawing + * + * @name rotate + * @function + * @param angle {Number} The rotation angle, in radians. + * @description To calculate from degrees to radians: degrees*Math.PI/180.
    + * Example: to rotate 5 degrees, specify the following: 5*Math.PI/180 + */ + Context2D.prototype.rotate = function(angle) { + if (isNaN(angle)) { + console.error("jsPDF.context2d.rotate: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.rotate"); + } + var matrix = new Matrix( + Math.cos(angle), + Math.sin(angle), + -Math.sin(angle), + Math.cos(angle), + 0.0, + 0.0 + ); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; + + /** + * Remaps the (0,0) position on the canvas + * + * @name translate + * @function + * @param x {Number} The value to add to horizontal (x) coordinates + * @param y {Number} The value to add to vertical (y) coordinates + * @description The translate() method remaps the (0,0) position on the canvas. + */ + Context2D.prototype.translate = function(x, y) { + if (isNaN(x) || isNaN(y)) { + console.error("jsPDF.context2d.translate: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.translate"); + } + var matrix = new Matrix(1.0, 0.0, 0.0, 1.0, x, y); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; + + /** + * Replaces the current transformation matrix for the drawing + * + * @name transform + * @function + * @param a {Number} Horizontal scaling + * @param b {Number} Horizontal skewing + * @param c {Number} Vertical skewing + * @param d {Number} Vertical scaling + * @param e {Number} Horizontal moving + * @param f {Number} Vertical moving + * @description Each object on the canvas has a current transformation matrix.

    The transform() method replaces the current transformation matrix. It multiplies the current transformation matrix with the matrix described by:



    a c e

    b d f

    0 0 1

    In other words, the transform() method lets you scale, rotate, move, and skew the current context. + */ + Context2D.prototype.transform = function(a, b, c, d, e, f) { + if (isNaN(a) || isNaN(b) || isNaN(c) || isNaN(d) || isNaN(e) || isNaN(f)) { + console.error("jsPDF.context2d.transform: Invalid arguments", arguments); + throw new Error("Invalid arguments passed to jsPDF.context2d.transform"); + } + var matrix = new Matrix(a, b, c, d, e, f); + this.ctx.transform = this.ctx.transform.multiply(matrix); + }; + + /** + * Resets the current transform to the identity matrix. Then runs transform() + * + * @name setTransform + * @function + * @param a {Number} Horizontal scaling + * @param b {Number} Horizontal skewing + * @param c {Number} Vertical skewing + * @param d {Number} Vertical scaling + * @param e {Number} Horizontal moving + * @param f {Number} Vertical moving + * @description Each object on the canvas has a current transformation matrix.

    The setTransform() method resets the current transform to the identity matrix, and then runs transform() with the same arguments.

    In other words, the setTransform() method lets you scale, rotate, move, and skew the current context. + */ + Context2D.prototype.setTransform = function(a, b, c, d, e, f) { + a = isNaN(a) ? 1 : a; + b = isNaN(b) ? 0 : b; + c = isNaN(c) ? 0 : c; + d = isNaN(d) ? 1 : d; + e = isNaN(e) ? 0 : e; + f = isNaN(f) ? 0 : f; + this.ctx.transform = new Matrix(a, b, c, d, e, f); + }; + + var hasMargins = function() { + return ( + this.margin[0] > 0 || + this.margin[1] > 0 || + this.margin[2] > 0 || + this.margin[3] > 0 + ); + }; + + /** + * Draws an image, canvas, or video onto the canvas + * + * @function + * @param img {} Specifies the image, canvas, or video element to use + * @param sx {Number} Optional. The x coordinate where to start clipping + * @param sy {Number} Optional. The y coordinate where to start clipping + * @param swidth {Number} Optional. The width of the clipped image + * @param sheight {Number} Optional. The height of the clipped image + * @param x {Number} The x coordinate where to place the image on the canvas + * @param y {Number} The y coordinate where to place the image on the canvas + * @param width {Number} Optional. The width of the image to use (stretch or reduce the image) + * @param height {Number} Optional. The height of the image to use (stretch or reduce the image) + */ + Context2D.prototype.drawImage = function( + img, + sx, + sy, + swidth, + sheight, + x, + y, + width, + height + ) { + var imageProperties = this.pdf.getImageProperties(img); + var factorX = 1; + var factorY = 1; + + var clipFactorX = 1; + var clipFactorY = 1; + + if (typeof swidth !== "undefined" && typeof width !== "undefined") { + clipFactorX = width / swidth; + clipFactorY = height / sheight; + factorX = ((imageProperties.width / swidth) * width) / swidth; + factorY = ((imageProperties.height / sheight) * height) / sheight; + } + + //is sx and sy are set and x and y not, set x and y with values of sx and sy + if (typeof x === "undefined") { + x = sx; + y = sy; + sx = 0; + sy = 0; + } + + if (typeof swidth !== "undefined" && typeof width === "undefined") { + width = swidth; + height = sheight; + } + if (typeof swidth === "undefined" && typeof width === "undefined") { + width = imageProperties.width; + height = imageProperties.height; + } + + var decomposedTransformationMatrix = this.ctx.transform.decompose(); + var angle = rad2deg(decomposedTransformationMatrix.rotate.shx); + var matrix = new Matrix(); + matrix = matrix.multiply(decomposedTransformationMatrix.translate); + matrix = matrix.multiply(decomposedTransformationMatrix.skew); + matrix = matrix.multiply(decomposedTransformationMatrix.scale); + var xRect = matrix.applyToRectangle( + new Rectangle( + x - sx * clipFactorX, + y - sy * clipFactorY, + swidth * factorX, + sheight * factorY + ) + ); + var pageArray = getPagesByPath.call(this, xRect); + var pages = []; + for (var ii = 0; ii < pageArray.length; ii += 1) { + if (pages.indexOf(pageArray[ii]) === -1) { + pages.push(pageArray[ii]); + } + } + + sortPages(pages); + + var clipPath; + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var i = min; i < max + 1; i++) { + this.pdf.setPage(i); + + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + drawPaths.call(this, "fill", true); + this.path = tmpPaths; + } + var tmpRect = JSON.parse(JSON.stringify(xRect)); + tmpRect = pathPositionRedo( + [tmpRect], + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + )[0]; + + const needsClipping = (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + this.pdf.addImage( + img, + "JPEG", + tmpRect.x, + tmpRect.y, + tmpRect.w, + tmpRect.h, + null, + null, + angle + ); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + } else { + this.pdf.addImage( + img, + "JPEG", + xRect.x, + xRect.y, + xRect.w, + xRect.h, + null, + null, + angle + ); + } + }; + + var getPagesByPath = function(path, pageWrapX, pageWrapY) { + var result = []; + pageWrapX = pageWrapX || this.pdf.internal.pageSize.width; + pageWrapY = + pageWrapY || + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var yOffset = this.posY + this.ctx.prevPageLastElemOffset; + + switch (path.type) { + default: + case "mt": + case "lt": + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); + break; + case "arc": + result.push( + Math.floor((path.y + yOffset - path.radius) / pageWrapY) + 1 + ); + result.push( + Math.floor((path.y + yOffset + path.radius) / pageWrapY) + 1 + ); + break; + case "qct": + var rectOfQuadraticCurve = getQuadraticCurveBoundary( + this.ctx.lastPoint.x, + this.ctx.lastPoint.y, + path.x1, + path.y1, + path.x, + path.y + ); + result.push( + Math.floor((rectOfQuadraticCurve.y + yOffset) / pageWrapY) + 1 + ); + result.push( + Math.floor( + (rectOfQuadraticCurve.y + rectOfQuadraticCurve.h + yOffset) / + pageWrapY + ) + 1 + ); + break; + case "bct": + var rectOfBezierCurve = getBezierCurveBoundary( + this.ctx.lastPoint.x, + this.ctx.lastPoint.y, + path.x1, + path.y1, + path.x2, + path.y2, + path.x, + path.y + ); + result.push( + Math.floor((rectOfBezierCurve.y + yOffset) / pageWrapY) + 1 + ); + result.push( + Math.floor( + (rectOfBezierCurve.y + rectOfBezierCurve.h + yOffset) / pageWrapY + ) + 1 + ); + break; + case "rect": + result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); + result.push(Math.floor((path.y + path.h + yOffset) / pageWrapY) + 1); + } + + for (var i = 0; i < result.length; i += 1) { + while (this.pdf.internal.getNumberOfPages() < result[i]) { + addPage.call(this); + } + } + return result; + }; + + var addPage = function() { + var fillStyle = this.fillStyle; + var strokeStyle = this.strokeStyle; + var font = this.font; + var lineCap = this.lineCap; + var lineWidth = this.lineWidth; + var lineJoin = this.lineJoin; + this.pdf.addPage(); + this.fillStyle = fillStyle; + this.strokeStyle = strokeStyle; + this.font = font; + this.lineCap = lineCap; + this.lineWidth = lineWidth; + this.lineJoin = lineJoin; + }; + + var pathPositionRedo = function(paths, x, y) { + for (var i = 0; i < paths.length; i++) { + switch (paths[i].type) { + case "bct": + paths[i].x2 += x; + paths[i].y2 += y; + case "qct": + paths[i].x1 += x; + paths[i].y1 += y; + case "mt": + case "lt": + case "arc": + default: + paths[i].x += x; + paths[i].y += y; + } + } + return paths; + }; + + var sortPages = function(pages) { + return pages.sort(function(a, b) { + return a - b; + }); + }; + + var pathPreProcess = function(rule, isClip) { + var fillStyle = this.fillStyle; + var strokeStyle = this.strokeStyle; + var lineCap = this.lineCap; + var oldLineWidth = this.lineWidth; + var lineWidth = Math.abs(oldLineWidth * this.ctx.transform.scaleX); + var lineJoin = this.lineJoin; + + var origPath = JSON.parse(JSON.stringify(this.path)); + var xPath = JSON.parse(JSON.stringify(this.path)); + var clipPath; + var tmpPath; + var pages = []; + + for (var i = 0; i < xPath.length; i++) { + if (typeof xPath[i].x !== "undefined") { + var page = getPagesByPath.call(this, xPath[i]); + + for (var ii = 0; ii < page.length; ii += 1) { + if (pages.indexOf(page[ii]) === -1) { + pages.push(page[ii]); + } + } + } + } + + for (var j = 0; j < pages.length; j++) { + while (this.pdf.internal.getNumberOfPages() < pages[j]) { + addPage.call(this); + } + } + sortPages(pages); + + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var k = min; k < max + 1; k++) { + this.pdf.setPage(k); + + this.fillStyle = fillStyle; + this.strokeStyle = strokeStyle; + this.lineCap = lineCap; + this.lineWidth = lineWidth; + this.lineJoin = lineJoin; + + var pageWidthMinusMargins = + this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; + var topMargin = k === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusMargins = + this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; + var previousPageHeightSum = + k === 1 ? 0 : firstPageHeight + (k - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + drawPaths.call(this, rule, true); + this.path = tmpPaths; + } + tmpPath = JSON.parse(JSON.stringify(origPath)); + this.path = pathPositionRedo( + tmpPath, + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + ); + if (isClip === false || k === 0) { + const needsClipping = (k > min || k < max) && hasMargins.call(this); + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + drawPaths.call(this, rule, isClip); + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + this.lineWidth = oldLineWidth; + } + } else { + this.lineWidth = lineWidth; + drawPaths.call(this, rule, isClip); + this.lineWidth = oldLineWidth; + } + this.path = origPath; + }; + + /** + * Processes the paths + * + * @function + * @param rule {String} + * @param isClip {Boolean} + * @private + * @ignore + */ + var drawPaths = function(rule, isClip) { + if (rule === "stroke" && !isClip && isStrokeTransparent.call(this)) { + return; + } + + if (rule !== "stroke" && !isClip && isFillTransparent.call(this)) { + return; + } + + var moves = []; + + //var alpha = (this.ctx.fillOpacity < 1) ? this.ctx.fillOpacity : this.ctx.globalAlpha; + var delta; + var xPath = this.path; + for (var i = 0; i < xPath.length; i++) { + var pt = xPath[i]; + + switch (pt.type) { + case "begin": + moves.push({ + begin: true + }); + break; + + case "close": + moves.push({ + close: true + }); + break; + + case "mt": + moves.push({ + start: pt, + deltas: [], + abs: [] + }); + break; + + case "lt": + var iii = moves.length; + if (xPath[i - 1] && !isNaN(xPath[i - 1].x)) { + delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + if (iii > 0) { + for (iii; iii >= 0; iii--) { + if ( + moves[iii - 1].close !== true && + moves[iii - 1].begin !== true + ) { + moves[iii - 1].deltas.push(delta); + moves[iii - 1].abs.push(pt); + break; + } + } + } + } + break; + + case "bct": + delta = [ + pt.x1 - xPath[i - 1].x, + pt.y1 - xPath[i - 1].y, + pt.x2 - xPath[i - 1].x, + pt.y2 - xPath[i - 1].y, + pt.x - xPath[i - 1].x, + pt.y - xPath[i - 1].y + ]; + moves[moves.length - 1].deltas.push(delta); + break; + + case "qct": + var x1 = xPath[i - 1].x + (2.0 / 3.0) * (pt.x1 - xPath[i - 1].x); + var y1 = xPath[i - 1].y + (2.0 / 3.0) * (pt.y1 - xPath[i - 1].y); + var x2 = pt.x + (2.0 / 3.0) * (pt.x1 - pt.x); + var y2 = pt.y + (2.0 / 3.0) * (pt.y1 - pt.y); + var x3 = pt.x; + var y3 = pt.y; + delta = [ + x1 - xPath[i - 1].x, + y1 - xPath[i - 1].y, + x2 - xPath[i - 1].x, + y2 - xPath[i - 1].y, + x3 - xPath[i - 1].x, + y3 - xPath[i - 1].y + ]; + moves[moves.length - 1].deltas.push(delta); + break; + + case "arc": + moves.push({ + deltas: [], + abs: [], + arc: true + }); + + if (Array.isArray(moves[moves.length - 1].abs)) { + moves[moves.length - 1].abs.push(pt); + } + break; + } + } + var style; + if (!isClip) { + if (rule === "stroke") { + style = "stroke"; + } else { + style = "fill"; + } + } else { + style = null; + } + + var began = false; + for (var k = 0; k < moves.length; k++) { + if (moves[k].arc) { + var arcs = moves[k].abs; + + for (var ii = 0; ii < arcs.length; ii++) { + var arc = arcs[ii]; + + if (arc.type === "arc") { + drawArc.call( + this, + arc.x, + arc.y, + arc.radius, + arc.startAngle, + arc.endAngle, + arc.counterclockwise, + undefined, + isClip, + !began + ); + } else { + drawLine.call(this, arc.x, arc.y); + } + began = true; + } + } else if (moves[k].close === true) { + this.pdf.internal.out("h"); + began = false; + } else if (moves[k].begin !== true) { + var x = moves[k].start.x; + var y = moves[k].start.y; + drawLines.call(this, moves[k].deltas, x, y); + began = true; + } + } + + if (style) { + putStyle.call(this, style); + } + if (isClip) { + doClip.call(this); + } + }; + + var getBaseline = function(y) { + var height = + this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; + var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); + switch (this.ctx.textBaseline) { + case "bottom": + return y - descent; + case "top": + return y + height - descent; + case "hanging": + return y + height - 2 * descent; + case "middle": + return y + height / 2 - descent; + case "ideographic": + // TODO not implemented + return y; + case "alphabetic": + default: + return y; + } + }; + + var getTextBottom = function(yBaseLine) { + var height = + this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; + var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); + return yBaseLine + descent; + }; + + Context2D.prototype.createLinearGradient = function createLinearGradient() { + var canvasGradient = function canvasGradient() {}; + + canvasGradient.colorStops = []; + canvasGradient.addColorStop = function(offset, color) { + this.colorStops.push([offset, color]); + }; + + canvasGradient.getColor = function() { + if (this.colorStops.length === 0) { + return "#000000"; + } + + return this.colorStops[0][1]; + }; + + canvasGradient.isCanvasGradient = true; + return canvasGradient; + }; + Context2D.prototype.createPattern = function createPattern() { + return this.createLinearGradient(); + }; + Context2D.prototype.createRadialGradient = function createRadialGradient() { + return this.createLinearGradient(); + }; + + /** + * + * @param x Edge point X + * @param y Edge point Y + * @param r Radius + * @param a1 start angle + * @param a2 end angle + * @param counterclockwise + * @param style + * @param isClip + */ + var drawArc = function( + x, + y, + r, + a1, + a2, + counterclockwise, + style, + isClip, + includeMove + ) { + // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html + var curves = createArc.call(this, r, a1, a2, counterclockwise); + + for (var i = 0; i < curves.length; i++) { + var curve = curves[i]; + if (i === 0) { + if (includeMove) { + doMove.call(this, curve.x1 + x, curve.y1 + y); + } else { + drawLine.call(this, curve.x1 + x, curve.y1 + y); + } + } + drawCurve.call( + this, + x, + y, + curve.x2, + curve.y2, + curve.x3, + curve.y3, + curve.x4, + curve.y4 + ); + } + + if (!isClip) { + putStyle.call(this, style); + } else { + doClip.call(this); + } + }; + + var putStyle = function(style) { + switch (style) { + case "stroke": + this.pdf.internal.out("S"); + break; + case "fill": + this.pdf.internal.out("f"); + break; + } + }; + + var doClip = function() { + this.pdf.clip(); + this.pdf.discardPath(); + }; + + var doMove = function(x, y) { + this.pdf.internal.out( + getHorizontalCoordinateString(x) + + " " + + getVerticalCoordinateString(y) + + " m" + ); + }; + + var putText = function(options) { + var textAlign; + switch (options.align) { + case "right": + case "end": + textAlign = "right"; + break; + case "center": + textAlign = "center"; + break; + case "left": + case "start": + default: + textAlign = "left"; + break; + } + + var textDimensions = this.pdf.getTextDimensions(options.text); + var yBaseLine = getBaseline.call(this, options.y); + var yBottom = getTextBottom.call(this, yBaseLine); + var yTop = yBottom - textDimensions.h; + + var pt = this.ctx.transform.applyToPoint(new Point(options.x, yBaseLine)); + var decomposedTransformationMatrix = this.ctx.transform.decompose(); + var matrix = new Matrix(); + matrix = matrix.multiply(decomposedTransformationMatrix.translate); + matrix = matrix.multiply(decomposedTransformationMatrix.skew); + matrix = matrix.multiply(decomposedTransformationMatrix.scale); + + var baselineRect = this.ctx.transform.applyToRectangle( + new Rectangle(options.x, yBaseLine, textDimensions.w, textDimensions.h) + ); + var textBounds = matrix.applyToRectangle( + new Rectangle(options.x, yTop, textDimensions.w, textDimensions.h) + ); + var pageArray = getPagesByPath.call(this, textBounds); + var pages = []; + for (var ii = 0; ii < pageArray.length; ii += 1) { + if (pages.indexOf(pageArray[ii]) === -1) { + pages.push(pageArray[ii]); + } + } + + sortPages(pages); + + var clipPath, oldSize, oldLineWidth; + if (this.autoPaging) { + var min = pages[0]; + var max = pages[pages.length - 1]; + for (var i = min; i < max + 1; i++) { + this.pdf.setPage(i); + + var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; + var firstPageHeight = + this.pdf.internal.pageSize.height - + this.posY - + this.margin[0] - + this.margin[2]; + var pageHeightMinusBottomMargin = + this.pdf.internal.pageSize.height - this.margin[2]; + var pageHeightMinusMargins = + pageHeightMinusBottomMargin - this.margin[0]; + var pageWidthMinusRightMargin = + this.pdf.internal.pageSize.width - this.margin[1]; + var pageWidthMinusMargins = pageWidthMinusRightMargin - this.margin[3]; + var previousPageHeightSum = + i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; + + if (this.ctx.clip_path.length !== 0) { + var tmpPaths = this.path; + clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); + this.path = pathPositionRedo( + clipPath, + this.posX + this.margin[3], + -1 * previousPageHeightSum + topMargin + ); + drawPaths.call(this, "fill", true); + this.path = tmpPaths; + } + var textBoundsOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(textBounds))], + this.posX + this.margin[3], + -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset + )[0]; + + if (options.scale >= 0.01) { + oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * options.scale); + oldLineWidth = this.lineWidth; + this.lineWidth = oldLineWidth * options.scale; + } + + var doSlice = this.autoPaging !== "text"; + + if ( + doSlice || + textBoundsOnPage.y + textBoundsOnPage.h <= pageHeightMinusBottomMargin + ) { + if ( + doSlice || + (textBoundsOnPage.y >= topMargin && + textBoundsOnPage.x <= pageWidthMinusRightMargin) + ) { + var croppedText = doSlice + ? options.text + : this.pdf.splitTextToSize( + options.text, + options.maxWidth || + pageWidthMinusRightMargin - textBoundsOnPage.x + )[0]; + var baseLineRectOnPage = pathPositionRedo( + [JSON.parse(JSON.stringify(baselineRect))], + this.posX + this.margin[3], + -previousPageHeightSum + + topMargin + + this.ctx.prevPageLastElemOffset + )[0]; + + const needsClipping = + doSlice && (i > min || i < max) && hasMargins.call(this); + + if (needsClipping) { + this.pdf.saveGraphicsState(); + this.pdf + .rect( + this.margin[3], + this.margin[0], + pageWidthMinusMargins, + pageHeightMinusMargins, + null + ) + .clip() + .discardPath(); + } + + this.pdf.text( + croppedText, + baseLineRectOnPage.x, + baseLineRectOnPage.y, + { + angle: options.angle, + align: textAlign, + renderingMode: options.renderingMode + } + ); + + if (needsClipping) { + this.pdf.restoreGraphicsState(); + } + } + } else { + // This text is the last element of the page, but it got cut off due to the margin + // so we render it in the next page + + if (textBoundsOnPage.y < pageHeightMinusBottomMargin) { + // As a result, all other elements have their y offset increased + this.ctx.prevPageLastElemOffset += + pageHeightMinusBottomMargin - textBoundsOnPage.y; + } + } + + if (options.scale >= 0.01) { + this.pdf.setFontSize(oldSize); + this.lineWidth = oldLineWidth; + } + } + } else { + if (options.scale >= 0.01) { + oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * options.scale); + oldLineWidth = this.lineWidth; + this.lineWidth = oldLineWidth * options.scale; + } + this.pdf.text(options.text, pt.x + this.posX, pt.y + this.posY, { + angle: options.angle, + align: textAlign, + renderingMode: options.renderingMode, + maxWidth: options.maxWidth + }); + + if (options.scale >= 0.01) { + this.pdf.setFontSize(oldSize); + this.lineWidth = oldLineWidth; + } + } + }; + + var drawLine = function(x, y, prevX, prevY) { + prevX = prevX || 0; + prevY = prevY || 0; + + this.pdf.internal.out( + getHorizontalCoordinateString(x + prevX) + + " " + + getVerticalCoordinateString(y + prevY) + + " l" + ); + }; + + var drawLines = function(lines, x, y) { + return this.pdf.lines(lines, x, y, null, null); + }; + + var drawCurve = function(x, y, x1, y1, x2, y2, x3, y3) { + this.pdf.internal.out( + [ + f2(getHorizontalCoordinate(x1 + x)), + f2(getVerticalCoordinate(y1 + y)), + f2(getHorizontalCoordinate(x2 + x)), + f2(getVerticalCoordinate(y2 + y)), + f2(getHorizontalCoordinate(x3 + x)), + f2(getVerticalCoordinate(y3 + y)), + "c" + ].join(" ") + ); + }; + + /** + * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius. + * + * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + * @function createArc + */ + var createArc = function(radius, startAngle, endAngle, anticlockwise) { + var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below + var twoPi = Math.PI * 2; + var halfPi = Math.PI / 2.0; + + while (startAngle > endAngle) { + startAngle = startAngle - twoPi; + } + var totalAngle = Math.abs(endAngle - startAngle); + if (totalAngle < twoPi) { + if (anticlockwise) { + totalAngle = twoPi - totalAngle; + } + } + + // Compute the sequence of arc curves, up to PI/2 at a time. + var curves = []; + + // clockwise or counterclockwise + var sgn = anticlockwise ? -1 : +1; + + var a1 = startAngle; + for (; totalAngle > EPSILON; ) { + var remain = sgn * Math.min(totalAngle, halfPi); + var a2 = a1 + remain; + curves.push(createSmallArc.call(this, radius, a1, a2)); + totalAngle -= Math.abs(a2 - a1); + a1 = a2; + } + + return curves; + }; + + /** + * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r. + * + * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + * + * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378. + */ + var createSmallArc = function(r, a1, a2) { + var a = (a2 - a1) / 2.0; + + var x4 = r * Math.cos(a); + var y4 = r * Math.sin(a); + var x1 = x4; + var y1 = -y4; + + var q1 = x1 * x1 + y1 * y1; + var q2 = q1 + x1 * x4 + y1 * y4; + var k2 = ((4 / 3) * (Math.sqrt(2 * q1 * q2) - q2)) / (x1 * y4 - y1 * x4); + + var x2 = x1 - k2 * y1; + var y2 = y1 + k2 * x1; + var x3 = x2; + var y3 = -y2; + + var ar = a + a1; + var cos_ar = Math.cos(ar); + var sin_ar = Math.sin(ar); + + return { + x1: r * Math.cos(a1), + y1: r * Math.sin(a1), + x2: x2 * cos_ar - y2 * sin_ar, + y2: x2 * sin_ar + y2 * cos_ar, + x3: x3 * cos_ar - y3 * sin_ar, + y3: x3 * sin_ar + y3 * cos_ar, + x4: r * Math.cos(a2), + y4: r * Math.sin(a2) + }; + }; + + var rad2deg = function(value) { + return (value * 180) / Math.PI; + }; + + var getQuadraticCurveBoundary = function(sx, sy, cpx, cpy, ex, ey) { + var midX1 = sx + (cpx - sx) * 0.5; + var midY1 = sy + (cpy - sy) * 0.5; + var midX2 = ex + (cpx - ex) * 0.5; + var midY2 = ey + (cpy - ey) * 0.5; + var resultX1 = Math.min(sx, ex, midX1, midX2); + var resultX2 = Math.max(sx, ex, midX1, midX2); + var resultY1 = Math.min(sy, ey, midY1, midY2); + var resultY2 = Math.max(sy, ey, midY1, midY2); + return new Rectangle( + resultX1, + resultY1, + resultX2 - resultX1, + resultY2 - resultY1 + ); + }; + + //De Casteljau algorithm + var getBezierCurveBoundary = function(ax, ay, bx, by, cx, cy, dx, dy) { + var tobx = bx - ax; + var toby = by - ay; + var tocx = cx - bx; + var tocy = cy - by; + var todx = dx - cx; + var tody = dy - cy; + var precision = 40; + var d, + i, + px, + py, + qx, + qy, + rx, + ry, + tx, + ty, + sx, + sy, + x, + y, + minx, + miny, + maxx, + maxy, + toqx, + toqy, + torx, + tory, + totx, + toty; + for (i = 0; i < precision + 1; i++) { + d = i / precision; + px = ax + d * tobx; + py = ay + d * toby; + qx = bx + d * tocx; + qy = by + d * tocy; + rx = cx + d * todx; + ry = cy + d * tody; + toqx = qx - px; + toqy = qy - py; + torx = rx - qx; + tory = ry - qy; + + sx = px + d * toqx; + sy = py + d * toqy; + tx = qx + d * torx; + ty = qy + d * tory; + totx = tx - sx; + toty = ty - sy; + + x = sx + d * totx; + y = sy + d * toty; + if (i == 0) { + minx = x; + miny = y; + maxx = x; + maxy = y; + } else { + minx = Math.min(minx, x); + miny = Math.min(miny, y); + maxx = Math.max(maxx, x); + maxy = Math.max(maxy, y); + } + } + return new Rectangle( + Math.round(minx), + Math.round(miny), + Math.round(maxx - minx), + Math.round(maxy - miny) + ); + }; + + var getPrevLineDashValue = function(lineDash, lineDashOffset) { + return JSON.stringify({ + lineDash: lineDash, + lineDashOffset: lineDashOffset + }); + }; + + var setLineDash = function() { + // Avoid unnecessary line dash declarations. + if ( + !this.prevLineDash && + !this.ctx.lineDash.length && + !this.ctx.lineDashOffset + ) { + return; + } + + // Avoid unnecessary line dash declarations. + const nextLineDash = getPrevLineDashValue( + this.ctx.lineDash, + this.ctx.lineDashOffset + ); + if (this.prevLineDash !== nextLineDash) { + this.pdf.setLineDash(this.ctx.lineDash, this.ctx.lineDashOffset); + this.prevLineDash = nextLineDash; + } + }; +})(jsPDF.API); + +// DEFLATE is a complex format; to read this code, you should probably check the RFC first: + +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array; +// fixed length extra bits +var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + // numbers here are at max 18 bits + var r = new i32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; + } + } + return { b: b, r: r }; +}; +var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), revfd = _b.r; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1; +} +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) { + if (cd[i]) + ++l[cd[i] - 1]; + } + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 1; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >> rvb] = sv; + } + } + } + } + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); + } + } + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9, 0); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5, 0); +// get end of byte +var shft = function (p) { return ((p + 7) / 8) | 0; }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + return new u8(v.subarray(s, e)); +}; +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; +}; +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; + d[o + 2] |= v >> 16; +}; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return { t: et, l: 0 }; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return { t: v, l: 1 }; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; + } + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; + } + dt >>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; + } + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } + } + mbt = mb; + } + return { t: new u8(tr), l: mbt }; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; + } + } + return { c: cl.subarray(0, cli), n: s }; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l; + var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l; + var _c = lc(dlt), lclt = _c.c, nlc = _c.n; + var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + ++lcfreq[lclt[i] & 31]; + for (var i = 0; i < lcdt.length; ++i) + ++lcfreq[lcdt[i] & 31]; + var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; + if (bs >= 0 && flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12; + } + } + } + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; + } + for (var i = 0; i < li; ++i) { + var sym = syms[i]; + if (sym > 255) { + var len = (sym >> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (sym >> 23) & 31), p += fleb[len]; + var dst = sym & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst]; + } + else { + wbits16(out, p, lm[sym]), p += ll[sym]; + } + } + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, st) { + var s = st.z || dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var lst = st.l; + var pos = (st.r || 0) & 7; + if (lvl) { + if (pos) + w[0] = st.r >> 3; + var opt = deo[lvl - 1]; + var n = opt >> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new i32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx blkpos + var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0; + for (; i + 2 < s; ++i) { + // hash value + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = i - dif + j & 32767; + var pti = prev[ti]; + var cd = ti - pti & 32767; + if (cd > md) + md = cd, pimod = ti; + } + } + } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += imod - pimod & 32767; + } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one int32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + } + } + for (i = Math.max(i, wi); i < s; ++i) { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + if (!lst) { + st.r = (pos & 7) | w[(pos / 8) | 0] << 3; + // shft(pos) now 1 less if pos & 7 != 0 + pos -= 7; + st.h = head, st.p = prev, st.i = i, st.w = wi; + } + } + else { + for (var i = st.w || 0; i < s + lst; i += 65535) { + // end + var e = i + 65535; + if (e >= s) { + // write final block + w[(pos / 8) | 0] = lst; + e = s; + } + pos = wfblk(w, pos + 1, dat.subarray(i, e)); + } + st.i = s; + } + return slc(o, 0, pre + shft(pos) + post); +}; +// Adler32 +var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length | 0; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); + } + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8); + } + }; +}; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + if (!st) { + st = { l: 1 }; + if (opt.dictionary) { + var dict = opt.dictionary.subarray(-32768); + var newDat = new u8(dict.length + dat.length); + newDat.set(dict); + newDat.set(dat, dict.length); + dat = newDat; + st.w = dict.length; + } + } + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, st); +}; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +// zlib header +var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32); + c[1] |= 31 - ((c[0] << 8) | c[1]) % 31; + if (o.dictionary) { + var h = adler(); + h.p(o.dictionary); + wbytes(c, 2, h.d()); + } +}; +/** + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data + */ +function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; +} +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} +catch (e) { } + +/** + * @license + * jsPDF filters PlugIn + * Copyright (c) 2014 Aras Abbasi + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +(function(jsPDFAPI) { + + var ASCII85Encode = function(a) { + var b, c, d, e, f, g, h, i, j, k; + // eslint-disable-next-line no-control-regex + for ( + !/[^\x00-\xFF]/.test(a), + b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), + a += b, + c = [], + d = 0, + e = a.length; + e > d; + d += 4 + ) + (f = + (a.charCodeAt(d) << 24) + + (a.charCodeAt(d + 1) << 16) + + (a.charCodeAt(d + 2) << 8) + + a.charCodeAt(d + 3)), + 0 !== f + ? ((k = f % 85), + (f = (f - k) / 85), + (j = f % 85), + (f = (f - j) / 85), + (i = f % 85), + (f = (f - i) / 85), + (h = f % 85), + (f = (f - h) / 85), + (g = f % 85), + c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) + : c.push(122); + return ( + (function(a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(c, b.length), + String.fromCharCode.apply(String, c) + "~>" + ); + }; + + var ASCII85Decode = function(a) { + var c, + d, + e, + f, + g, + h = String, + l = "length", + w = 255, + x = "charCodeAt", + y = "slice", + z = "replace"; + for ( + "~>" === a[y](-2), + a = a[y](0, -2) + [z](/\s/g, "") + [z]("z", "!!!!!"), + c = "uuuuu"[y](a[l] % 5 || 5), + a += c, + e = [], + f = 0, + g = a[l]; + g > f; + f += 5 + ) + (d = + 52200625 * (a[x](f) - 33) + + 614125 * (a[x](f + 1) - 33) + + 7225 * (a[x](f + 2) - 33) + + 85 * (a[x](f + 3) - 33) + + (a[x](f + 4) - 33)), + e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); + return ( + (function(a, b) { + for (var c = b; c > 0; c--) a.pop(); + })(e, c[l]), + h.fromCharCode.apply(h, e) + ); + }; + + var ASCIIHexEncode = function(value) { + return ( + value + .split("") + .map(function(value) { + return ("0" + value.charCodeAt().toString(16)).slice(-2); + }) + .join("") + ">" + ); + }; + + var ASCIIHexDecode = function(value) { + var regexCheckIfHex = new RegExp(/^([0-9A-Fa-f]{2})+$/); + value = value.replace(/\s/g, ""); + if (value.indexOf(">") !== -1) { + value = value.substr(0, value.indexOf(">")); + } + if (value.length % 2) { + value += "0"; + } + if (regexCheckIfHex.test(value) === false) { + return ""; + } + var result = ""; + for (var i = 0; i < value.length; i += 2) { + result += String.fromCharCode("0x" + (value[i] + value[i + 1])); + } + return result; + }; + /* + var FlatePredictors = { + None: 1, + TIFF: 2, + PNG_None: 10, + PNG_Sub: 11, + PNG_Up: 12, + PNG_Average: 13, + PNG_Paeth: 14, + PNG_Optimum: 15 + }; + */ + + var FlateEncode = function(data) { + var arr = new Uint8Array(data.length); + var i = data.length; + while (i--) { + arr[i] = data.charCodeAt(i); + } + arr = zlibSync(arr); + data = arr.reduce(function(data, byte) { + return data + String.fromCharCode(byte); + }, ""); + return data; + }; + + jsPDFAPI.processDataByFilters = function(origData, filterChain) { + var i = 0; + var data = origData || ""; + var reverseChain = []; + filterChain = filterChain || []; + + if (typeof filterChain === "string") { + filterChain = [filterChain]; + } + + for (i = 0; i < filterChain.length; i += 1) { + switch (filterChain[i]) { + case "ASCII85Decode": + case "/ASCII85Decode": + data = ASCII85Decode(data); + reverseChain.push("/ASCII85Encode"); + break; + case "ASCII85Encode": + case "/ASCII85Encode": + data = ASCII85Encode(data); + reverseChain.push("/ASCII85Decode"); + break; + case "ASCIIHexDecode": + case "/ASCIIHexDecode": + data = ASCIIHexDecode(data); + reverseChain.push("/ASCIIHexEncode"); + break; + case "ASCIIHexEncode": + case "/ASCIIHexEncode": + data = ASCIIHexEncode(data); + reverseChain.push("/ASCIIHexDecode"); + break; + case "FlateEncode": + case "/FlateEncode": + data = FlateEncode(data); + reverseChain.push("/FlateDecode"); + break; + default: + throw new Error( + 'The filter: "' + filterChain[i] + '" is not implemented' + ); + } + } + + return { data: data, reverseChain: reverseChain.reverse().join(" ") }; + }; +})(jsPDF.API); + +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * jsPDF JavaScript plugin + * + * @name javascript + * @module + */ +(function(jsPDFAPI) { + var jsNamesObj, jsJsObj, text; + /** + * @name addJS + * @function + * @param {string} javascript The javascript to be embedded into the PDF-file. + * @returns {jsPDF} + */ + jsPDFAPI.addJS = function(javascript) { + text = javascript; + this.internal.events.subscribe("postPutResources", function() { + jsNamesObj = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/Names [(EmbeddedJS) " + (jsNamesObj + 1) + " 0 R]"); + this.internal.out(">>"); + this.internal.out("endobj"); + + jsJsObj = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/S /JavaScript"); + this.internal.out("/JS (" + text + ")"); + this.internal.out(">>"); + this.internal.out("endobj"); + }); + this.internal.events.subscribe("putCatalog", function() { + if (jsNamesObj !== undefined && jsJsObj !== undefined) { + this.internal.out("/Names <>"); + } + }); + return this; + }; +})(jsPDF.API); + +/** + * @license + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * jsPDF Outline PlugIn + * + * Generates a PDF Outline + * @name outline + * @module + */ +(function(jsPDFAPI) { + + var namesOid; + //var destsGoto = []; + + jsPDFAPI.events.push([ + "postPutResources", + function() { + var pdf = this; + var rx = /^(\d+) 0 obj$/; + + // Write action goto objects for each page + // this.outline.destsGoto = []; + // for (var i = 0; i < totalPages; i++) { + // var id = pdf.internal.newObject(); + // this.outline.destsGoto.push(id); + // pdf.internal.write("<> endobj"); + // } + // + // for (var i = 0; i < dests.length; i++) { + // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 + // R"); + // } + // + if (this.outline.root.children.length > 0) { + var lines = pdf.outline.render().split(/\r\n/); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var m = rx.exec(line); + if (m != null) { + var oid = m[1]; + pdf.internal.newObjectDeferredBegin(oid, false); + } + pdf.internal.write(line); + } + } + + // This code will write named destination for each page reference + // (page_1, etc) + if (this.outline.createNamedDestinations) { + var totalPages = this.internal.pages.length; + // WARNING: this assumes jsPDF starts on page 3 and pageIDs + // follow 5, 7, 9, etc + // Write destination objects for each page + var dests = []; + for (var i = 0; i < totalPages; i++) { + var id = pdf.internal.newObject(); + dests.push(id); + var info = pdf.internal.getPageInfo(i + 1); + pdf.internal.write( + "<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj" + ); + } + + // assign a name for each destination + var names2Oid = pdf.internal.newObject(); + pdf.internal.write("<< /Names [ "); + for (var i = 0; i < dests.length; i++) { + pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R"); + } + pdf.internal.write(" ] >>", "endobj"); + + // var kids = pdf.internal.newObject(); + // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R'); + // pdf.internal.write(' ] >>', 'endobj'); + + namesOid = pdf.internal.newObject(); + pdf.internal.write("<< /Dests " + names2Oid + " 0 R"); + pdf.internal.write(">>", "endobj"); + } + } + ]); + + jsPDFAPI.events.push([ + "putCatalog", + function() { + var pdf = this; + if (pdf.outline.root.children.length > 0) { + pdf.internal.write( + "/Outlines", + this.outline.makeRef(this.outline.root) + ); + if (this.outline.createNamedDestinations) { + pdf.internal.write("/Names " + namesOid + " 0 R"); + } + // Open with Bookmarks showing + // pdf.internal.write("/PageMode /UseOutlines"); + } + } + ]); + + jsPDFAPI.events.push([ + "initialized", + function() { + var pdf = this; + + pdf.outline = { + createNamedDestinations: false, + root: { + children: [] + } + }; + + /** + * Options: pageNumber + */ + pdf.outline.add = function(parent, title, options) { + var item = { + title: title, + options: options, + children: [] + }; + if (parent == null) { + parent = this.root; + } + parent.children.push(item); + return item; + }; + + pdf.outline.render = function() { + this.ctx = {}; + this.ctx.val = ""; + this.ctx.pdf = pdf; + + this.genIds_r(this.root); + this.renderRoot(this.root); + this.renderItems(this.root); + + return this.ctx.val; + }; + + pdf.outline.genIds_r = function(node) { + node.id = pdf.internal.newObjectDeferred(); + for (var i = 0; i < node.children.length; i++) { + this.genIds_r(node.children[i]); + } + }; + + pdf.outline.renderRoot = function(node) { + this.objStart(node); + this.line("/Type /Outlines"); + if (node.children.length > 0) { + this.line("/First " + this.makeRef(node.children[0])); + this.line( + "/Last " + this.makeRef(node.children[node.children.length - 1]) + ); + } + this.line( + "/Count " + + this.count_r( + { + count: 0 + }, + node + ) + ); + this.objEnd(); + }; + + pdf.outline.renderItems = function(node) { + var getVerticalCoordinateString = this.ctx.pdf.internal + .getVerticalCoordinateString; + for (var i = 0; i < node.children.length; i++) { + var item = node.children[i]; + this.objStart(item); + + this.line("/Title " + this.makeString(item.title)); + + this.line("/Parent " + this.makeRef(node)); + if (i > 0) { + this.line("/Prev " + this.makeRef(node.children[i - 1])); + } + if (i < node.children.length - 1) { + this.line("/Next " + this.makeRef(node.children[i + 1])); + } + if (item.children.length > 0) { + this.line("/First " + this.makeRef(item.children[0])); + this.line( + "/Last " + this.makeRef(item.children[item.children.length - 1]) + ); + } + + var count = (this.count = this.count_r( + { + count: 0 + }, + item + )); + if (count > 0) { + this.line("/Count " + count); + } + + if (item.options) { + if (item.options.pageNumber) { + // Explicit Destination + //WARNING this assumes page ids are 3,5,7, etc. + var info = pdf.internal.getPageInfo(item.options.pageNumber); + this.line( + "/Dest " + + "[" + + info.objId + + " 0 R /XYZ 0 " + + getVerticalCoordinateString(0) + + " 0]" + ); + // this line does not work on all clients (pageNumber instead of page ref) + //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.getHeight() + ' 0]'); + + // Named Destination + // this.line('/Dest (page_' + (item.options.pageNumber) + ')'); + + // Action Destination + // var id = pdf.internal.newObject(); + // pdf.internal.write('<> endobj'); + // this.line('/A ' + id + ' 0 R' ); + } + } + this.objEnd(); + } + for (var z = 0; z < node.children.length; z++) { + this.renderItems(node.children[z]); + } + }; + + pdf.outline.line = function(text) { + this.ctx.val += text + "\r\n"; + }; + + pdf.outline.makeRef = function(node) { + return node.id + " 0 R"; + }; + + pdf.outline.makeString = function(val) { + return "(" + pdf.internal.pdfEscape(val) + ")"; + }; + + pdf.outline.objStart = function(node) { + this.ctx.val += "\r\n" + node.id + " 0 obj" + "\r\n<<\r\n"; + }; + + pdf.outline.objEnd = function() { + this.ctx.val += ">> \r\n" + "endobj" + "\r\n"; + }; + + pdf.outline.count_r = function(ctx, node) { + for (var i = 0; i < node.children.length; i++) { + ctx.count++; + this.count_r(ctx, node.children[i]); + } + return ctx.count; + }; + } + ]); + + return this; +})(jsPDF.API); + +function decode(bytes, encoding = 'utf8') { + const decoder = new TextDecoder(encoding); + return decoder.decode(bytes); +} +const encoder = new TextEncoder(); +function encode(str) { + return encoder.encode(str); +} + +const defaultByteLength = 1024 * 8; +const hostBigEndian = (() => { + const array = new Uint8Array(4); + const view = new Uint32Array(array.buffer); + return !((view[0] = 1) & array[0]); +})(); +const typedArrays = { + int8: globalThis.Int8Array, + uint8: globalThis.Uint8Array, + int16: globalThis.Int16Array, + uint16: globalThis.Uint16Array, + int32: globalThis.Int32Array, + uint32: globalThis.Uint32Array, + uint64: globalThis.BigUint64Array, + int64: globalThis.BigInt64Array, + float32: globalThis.Float32Array, + float64: globalThis.Float64Array, +}; +class IOBuffer { + /** + * Reference to the internal ArrayBuffer object. + */ + buffer; + /** + * Byte length of the internal ArrayBuffer. + */ + byteLength; + /** + * Byte offset of the internal ArrayBuffer. + */ + byteOffset; + /** + * Byte length of the internal ArrayBuffer. + */ + length; + /** + * The current offset of the buffer's pointer. + */ + offset; + lastWrittenByte; + littleEndian; + _data; + _mark; + _marks; + /** + * Create a new IOBuffer. + * @param data - The data to construct the IOBuffer with. + * If data is a number, it will be the new buffer's length
    + * If data is `undefined`, the buffer will be initialized with a default length of 8Kb
    + * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance, + * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer. + * @param options - An object for the options. + * @returns A new IOBuffer instance. + */ + constructor(data = defaultByteLength, options = {}) { + let dataIsGiven = false; + if (typeof data === 'number') { + data = new ArrayBuffer(data); + } + else { + dataIsGiven = true; + this.lastWrittenByte = data.byteLength; + } + const offset = options.offset ? options.offset >>> 0 : 0; + const byteLength = data.byteLength - offset; + let dvOffset = offset; + if (ArrayBuffer.isView(data) || data instanceof IOBuffer) { + if (data.byteLength !== data.buffer.byteLength) { + dvOffset = data.byteOffset + offset; + } + data = data.buffer; + } + if (dataIsGiven) { + this.lastWrittenByte = byteLength; + } + else { + this.lastWrittenByte = 0; + } + this.buffer = data; + this.length = byteLength; + this.byteLength = byteLength; + this.byteOffset = dvOffset; + this.offset = 0; + this.littleEndian = true; + this._data = new DataView(this.buffer, dvOffset, byteLength); + this._mark = 0; + this._marks = []; + } + /** + * Checks if the memory allocated to the buffer is sufficient to store more + * bytes after the offset. + * @param byteLength - The needed memory in bytes. + * @returns `true` if there is sufficient space and `false` otherwise. + */ + available(byteLength = 1) { + return this.offset + byteLength <= this.length; + } + /** + * Check if little-endian mode is used for reading and writing multi-byte + * values. + * @returns `true` if little-endian mode is used, `false` otherwise. + */ + isLittleEndian() { + return this.littleEndian; + } + /** + * Set little-endian mode for reading and writing multi-byte values. + * @returns This. + */ + setLittleEndian() { + this.littleEndian = true; + return this; + } + /** + * Check if big-endian mode is used for reading and writing multi-byte values. + * @returns `true` if big-endian mode is used, `false` otherwise. + */ + isBigEndian() { + return !this.littleEndian; + } + /** + * Switches to big-endian mode for reading and writing multi-byte values. + * @returns This. + */ + setBigEndian() { + this.littleEndian = false; + return this; + } + /** + * Move the pointer n bytes forward. + * @param n - Number of bytes to skip. + * @returns This. + */ + skip(n = 1) { + this.offset += n; + return this; + } + /** + * Move the pointer n bytes backward. + * @param n - Number of bytes to move back. + * @returns This. + */ + back(n = 1) { + this.offset -= n; + return this; + } + /** + * Move the pointer to the given offset. + * @param offset - The offset to move to. + * @returns This. + */ + seek(offset) { + this.offset = offset; + return this; + } + /** + * Store the current pointer offset. + * @see {@link IOBuffer#reset} + * @returns This. + */ + mark() { + this._mark = this.offset; + return this; + } + /** + * Move the pointer back to the last pointer offset set by mark. + * @see {@link IOBuffer#mark} + * @returns This. + */ + reset() { + this.offset = this._mark; + return this; + } + /** + * Push the current pointer offset to the mark stack. + * @see {@link IOBuffer#popMark} + * @returns This. + */ + pushMark() { + this._marks.push(this.offset); + return this; + } + /** + * Pop the last pointer offset from the mark stack, and set the current + * pointer offset to the popped value. + * @see {@link IOBuffer#pushMark} + * @returns This. + */ + popMark() { + const offset = this._marks.pop(); + if (offset === undefined) { + throw new Error('Mark stack empty'); + } + this.seek(offset); + return this; + } + /** + * Move the pointer offset back to 0. + * @returns This. + */ + rewind() { + this.offset = 0; + return this; + } + /** + * Make sure the buffer has sufficient memory to write a given byteLength at + * the current pointer offset. + * If the buffer's memory is insufficient, this method will create a new + * buffer (a copy) with a length that is twice (byteLength + current offset). + * @param byteLength - The needed memory in bytes. + * @returns This. + */ + ensureAvailable(byteLength = 1) { + if (!this.available(byteLength)) { + const lengthNeeded = this.offset + byteLength; + const newLength = lengthNeeded * 2; + const newArray = new Uint8Array(newLength); + newArray.set(new Uint8Array(this.buffer)); + this.buffer = newArray.buffer; + this.length = newLength; + this.byteLength = newLength; + this._data = new DataView(this.buffer); + } + return this; + } + /** + * Read a byte and return false if the byte's value is 0, or true otherwise. + * Moves pointer forward by one byte. + * @returns The read boolean. + */ + readBoolean() { + return this.readUint8() !== 0; + } + /** + * Read a signed 8-bit integer and move pointer forward by 1 byte. + * @returns The read byte. + */ + readInt8() { + return this._data.getInt8(this.offset++); + } + /** + * Read an unsigned 8-bit integer and move pointer forward by 1 byte. + * @returns The read byte. + */ + readUint8() { + return this._data.getUint8(this.offset++); + } + /** + * Alias for {@link IOBuffer#readUint8}. + * @returns The read byte. + */ + readByte() { + return this.readUint8(); + } + /** + * Read `n` bytes and move pointer forward by `n` bytes. + * @param n - Number of bytes to read. + * @returns The read bytes. + */ + readBytes(n = 1) { + return this.readArray(n, 'uint8'); + } + /** + * Creates an array of corresponding to the type `type` and size `size`. + * For example type `uint8` will create a `Uint8Array`. + * @param size - size of the resulting array + * @param type - number type of elements to read + * @returns The read array. + */ + readArray(size, type) { + const bytes = typedArrays[type].BYTES_PER_ELEMENT * size; + const offset = this.byteOffset + this.offset; + const slice = this.buffer.slice(offset, offset + bytes); + if (this.littleEndian === hostBigEndian && + type !== 'uint8' && + type !== 'int8') { + const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes)); + slice.reverse(); + const returnArray = new typedArrays[type](slice.buffer); + this.offset += bytes; + returnArray.reverse(); + return returnArray; + } + const returnArray = new typedArrays[type](slice); + this.offset += bytes; + return returnArray; + } + /** + * Read a 16-bit signed integer and move pointer forward by 2 bytes. + * @returns The read value. + */ + readInt16() { + const value = this._data.getInt16(this.offset, this.littleEndian); + this.offset += 2; + return value; + } + /** + * Read a 16-bit unsigned integer and move pointer forward by 2 bytes. + * @returns The read value. + */ + readUint16() { + const value = this._data.getUint16(this.offset, this.littleEndian); + this.offset += 2; + return value; + } + /** + * Read a 32-bit signed integer and move pointer forward by 4 bytes. + * @returns The read value. + */ + readInt32() { + const value = this._data.getInt32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 32-bit unsigned integer and move pointer forward by 4 bytes. + * @returns The read value. + */ + readUint32() { + const value = this._data.getUint32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 32-bit floating number and move pointer forward by 4 bytes. + * @returns The read value. + */ + readFloat32() { + const value = this._data.getFloat32(this.offset, this.littleEndian); + this.offset += 4; + return value; + } + /** + * Read a 64-bit floating number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readFloat64() { + const value = this._data.getFloat64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 64-bit signed integer number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readBigInt64() { + const value = this._data.getBigInt64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes. + * @returns The read value. + */ + readBigUint64() { + const value = this._data.getBigUint64(this.offset, this.littleEndian); + this.offset += 8; + return value; + } + /** + * Read a 1-byte ASCII character and move pointer forward by 1 byte. + * @returns The read character. + */ + readChar() { + // eslint-disable-next-line unicorn/prefer-code-point + return String.fromCharCode(this.readInt8()); + } + /** + * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes. + * @param n - Number of characters to read. + * @returns The read characters. + */ + readChars(n = 1) { + let result = ''; + for (let i = 0; i < n; i++) { + result += this.readChar(); + } + return result; + } + /** + * Read the next `n` bytes, return a UTF-8 decoded string and move pointer + * forward by `n` bytes. + * @param n - Number of bytes to read. + * @returns The decoded string. + */ + readUtf8(n = 1) { + return decode(this.readBytes(n)); + } + /** + * Read the next `n` bytes, return a string decoded with `encoding` and move pointer + * forward by `n` bytes. + * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8} + * @param n - Number of bytes to read. + * @param encoding - The encoding to use. Default is 'utf8'. + * @returns The decoded string. + */ + decodeText(n = 1, encoding = 'utf8') { + return decode(this.readBytes(n), encoding); + } + /** + * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer + * forward by 1 byte. + * @param value - The value to write. + * @returns This. + */ + writeBoolean(value) { + this.writeUint8(value ? 0xff : 0x00); + return this; + } + /** + * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte. + * @param value - The value to write. + * @returns This. + */ + writeInt8(value) { + this.ensureAvailable(1); + this._data.setInt8(this.offset++, value); + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as an 8-bit unsigned integer and move pointer forward by 1 + * byte. + * @param value - The value to write. + * @returns This. + */ + writeUint8(value) { + this.ensureAvailable(1); + this._data.setUint8(this.offset++, value); + this._updateLastWrittenByte(); + return this; + } + /** + * An alias for {@link IOBuffer#writeUint8}. + * @param value - The value to write. + * @returns This. + */ + writeByte(value) { + return this.writeUint8(value); + } + /** + * Write all elements of `bytes` as uint8 values and move pointer forward by + * `bytes.length` bytes. + * @param bytes - The array of bytes to write. + * @returns This. + */ + writeBytes(bytes) { + this.ensureAvailable(bytes.length); + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < bytes.length; i++) { + this._data.setUint8(this.offset++, bytes[i]); + } + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 16-bit signed integer and move pointer forward by 2 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeInt16(value) { + this.ensureAvailable(2); + this._data.setInt16(this.offset, value, this.littleEndian); + this.offset += 2; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 16-bit unsigned integer and move pointer forward by 2 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeUint16(value) { + this.ensureAvailable(2); + this._data.setUint16(this.offset, value, this.littleEndian); + this.offset += 2; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit signed integer and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeInt32(value) { + this.ensureAvailable(4); + this._data.setInt32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit unsigned integer and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeUint32(value) { + this.ensureAvailable(4); + this._data.setUint32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 32-bit floating number and move pointer forward by 4 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeFloat32(value) { + this.ensureAvailable(4); + this._data.setFloat32(this.offset, value, this.littleEndian); + this.offset += 4; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit floating number and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeFloat64(value) { + this.ensureAvailable(8); + this._data.setFloat64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit signed bigint and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeBigInt64(value) { + this.ensureAvailable(8); + this._data.setBigInt64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8 + * bytes. + * @param value - The value to write. + * @returns This. + */ + writeBigUint64(value) { + this.ensureAvailable(8); + this._data.setBigUint64(this.offset, value, this.littleEndian); + this.offset += 8; + this._updateLastWrittenByte(); + return this; + } + /** + * Write the charCode of `str`'s first character as an 8-bit unsigned integer + * and move pointer forward by 1 byte. + * @param str - The character to write. + * @returns This. + */ + writeChar(str) { + // eslint-disable-next-line unicorn/prefer-code-point + return this.writeUint8(str.charCodeAt(0)); + } + /** + * Write the charCodes of all `str`'s characters as 8-bit unsigned integers + * and move pointer forward by `str.length` bytes. + * @param str - The characters to write. + * @returns This. + */ + writeChars(str) { + for (let i = 0; i < str.length; i++) { + // eslint-disable-next-line unicorn/prefer-code-point + this.writeUint8(str.charCodeAt(i)); + } + return this; + } + /** + * UTF-8 encode and write `str` to the current pointer offset and move pointer + * forward according to the encoded length. + * @param str - The string to write. + * @returns This. + */ + writeUtf8(str) { + return this.writeBytes(encode(str)); + } + /** + * Export a Uint8Array view of the internal buffer. + * The view starts at the byte offset and its length + * is calculated to stop at the last written byte or the original length. + * @returns A new Uint8Array view. + */ + toArray() { + return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte); + } + /** + * Get the total number of bytes written so far, regardless of the current offset. + * @returns - Total number of bytes. + */ + getWrittenByteLength() { + return this.lastWrittenByte - this.byteOffset; + } + /** + * Update the last written byte offset + * @private + */ + _updateLastWrittenByte() { + if (this.offset > this.lastWrittenByte) { + this.lastWrittenByte = this.offset; + } + } +} + +/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +/* eslint-disable space-unary-ops */ + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +//const Z_FILTERED = 1; +//const Z_HUFFMAN_ONLY = 2; +//const Z_RLE = 3; +const Z_FIXED$1 = 4; +//const Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +const Z_BINARY = 0; +const Z_TEXT = 1; +//const Z_ASCII = 1; // = Z_TEXT +const Z_UNKNOWN$1 = 2; + +/*============================================================================*/ + + +function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } + +// From zutil.h + +const STORED_BLOCK = 0; +const STATIC_TREES = 1; +const DYN_TREES = 2; +/* The three kinds of block type */ + +const MIN_MATCH$1 = 3; +const MAX_MATCH$1 = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +const LENGTH_CODES$1 = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +const LITERALS$1 = 256; +/* number of literal bytes 0..255 */ + +const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; +/* number of Literal or Length codes, including the END_BLOCK code */ + +const D_CODES$1 = 30; +/* number of distance codes */ + +const BL_CODES$1 = 19; +/* number of codes used to transfer the bit lengths */ + +const HEAP_SIZE$1 = 2 * L_CODES$1 + 1; +/* maximum heap size */ + +const MAX_BITS$1 = 15; +/* All codes must not exceed MAX_BITS bits */ + +const Buf_size = 16; +/* size of bit buffer in bi_buf */ + + +/* =========================================================================== + * Constants + */ + +const MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +const END_BLOCK = 256; +/* end of block literal code */ + +const REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +const REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +const REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* eslint-disable comma-spacing,array-bracket-spacing */ +const extra_lbits = /* extra bits for each length code */ + new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]); + +const extra_dbits = /* extra bits for each distance code */ + new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]); + +const extra_blbits = /* extra bits for each bit length code */ + new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]); + +const bl_order = + new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]); +/* eslint-enable comma-spacing,array-bracket-spacing */ + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 +const static_ltree = new Array((L_CODES$1 + 2) * 2); +zero$1(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +const static_dtree = new Array(D_CODES$1 * 2); +zero$1(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +const _dist_code = new Array(DIST_CODE_LEN); +zero$1(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); +zero$1(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +const base_length = new Array(LENGTH_CODES$1); +zero$1(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +const base_dist = new Array(D_CODES$1); +zero$1(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + + +function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { + + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +} + + +let static_l_desc; +let static_d_desc; +let static_bl_desc; + + +function TreeDesc(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +} + + + +const d_code = (dist) => { + + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +}; + + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +const put_short = (s, w) => { +// put_byte(s, (uch)((w) & 0xff)); +// put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +}; + + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +const send_bits = (s, value, length) => { + + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +}; + + +const send_code = (s, c, tree) => { + + send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); +}; + + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +const bi_reverse = (code, len) => { + + let res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +}; + + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +const bi_flush = (s) => { + + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +}; + + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +const gen_bitlen = (s, desc) => { +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ + + const tree = desc.dyn_tree; + const max_code = desc.max_code; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const extra = desc.stat_desc.extra_bits; + const base = desc.stat_desc.extra_base; + const max_length = desc.stat_desc.max_length; + let h; /* heap index */ + let n, m; /* iterate over the tree elements */ + let bits; /* bit length */ + let xbits; /* extra bits */ + let f; /* frequency */ + let overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS$1; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1]/*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) { continue; } /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n - base]; + } + f = tree[n * 2]/*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); + } + } + if (overflow === 0) { return; } + + // Tracev((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) { bits--; } + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) { continue; } + if (tree[m * 2 + 1]/*.Len*/ !== bits) { + // Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; + tree[m * 2 + 1]/*.Len*/ = bits; + } + n--; + } + } +}; + + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +const gen_codes = (tree, max_code, bl_count) => { +// ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ + + const next_code = new Array(MAX_BITS$1 + 1); /* next code value for each bit length */ + let code = 0; /* running code value */ + let bits; /* bit index */ + let n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS$1; bits++) { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = code; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< { + + let n; /* iterates over tree elements */ + let bits; /* bit counter */ + let length; /* length value */ + let code; /* code value */ + let dist; /* distance index */ + const bl_count = new Array(MAX_BITS$1 + 1); + /* number of codes at each bit length for an optimal tree */ + + // do check in _tr_init() + //if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ +/*#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif*/ + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES$1 - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + _length_code[length++] = code; + } + } + //Assert (length == 256, "tr_static_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + _length_code[length - 1] = code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + _dist_code[dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES$1; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS$1; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n * 2 + 1]/*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n * 2 + 1]/*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES$1 + 1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES$1; n++) { + static_dtree[n * 2 + 1]/*.Len*/ = 5; + static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1); + static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES$1, MAX_BITS$1); + static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES$1, MAX_BL_BITS); + + //static_init_done = true; +}; + + +/* =========================================================================== + * Initialize a new block. + */ +const init_block = (s) => { + + let n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES$1; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES$1; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES$1; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } + + s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.sym_next = s.matches = 0; +}; + + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +const bi_windup = (s) => +{ + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +}; + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +const smaller = (tree, n, m, depth) => { + + const _n2 = n * 2; + const _m2 = m * 2; + return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || + (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); +}; + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +const pqdownheap = (s, tree, k) => { +// deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ + + const v = s.heap[k]; + let j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s.heap_len && + smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) { break; } + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +}; + + +// inlined manually +// const SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +const compress_block = (s, ltree, dtree) => { +// deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ + + let dist; /* distance of matched string */ + let lc; /* match length or unmatched char (if dist == 0) */ + let sx = 0; /* running index in sym_buf */ + let code; /* the code to send */ + let extra; /* number of extra bits to send */ + + if (s.sym_next !== 0) { + do { + dist = s.pending_buf[s.sym_buf + sx++] & 0xff; + dist += (s.pending_buf[s.sym_buf + sx++] & 0xff) << 8; + lc = s.pending_buf[s.sym_buf + sx++]; + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and sym_buf is ok: */ + //Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); + + } while (sx < s.sym_next); + } + + send_code(s, END_BLOCK, ltree); +}; + + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +const build_tree = (s, desc) => { +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ + + const tree = desc.dyn_tree; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const elems = desc.stat_desc.elems; + let n, m; /* iterate over heap elements */ + let max_code = -1; /* largest code with non zero frequency */ + let node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE$1; + + for (n = 0; n < elems; n++) { + if (tree[n * 2]/*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + + } else { + tree[n * 2 + 1]/*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2]/*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node * 2 + 1]/*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1/*SMALLEST*/]; + s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1/*SMALLEST*/); + /***/ + + m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1/*SMALLEST*/] = node++; + pqdownheap(s, tree, 1/*SMALLEST*/); + + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +}; + + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +const scan_tree = (s, tree, max_code) => { +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ + + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + s.bl_tree[curlen * 2]/*.Freq*/ += count; + + } else if (curlen !== 0) { + + if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } + s.bl_tree[REP_3_6 * 2]/*.Freq*/++; + + } else if (count <= 10) { + s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; + + } else { + s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +}; + + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +const send_tree = (s, tree, max_code) => { +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ + + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); + + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count - 3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count - 3, 3); + + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count - 11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +}; + + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +const build_bl_tree = (s) => { + + let max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +}; + + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +const send_all_trees = (s, lcodes, dcodes, blcodes) => { +// deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ + + let rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +}; + + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "block list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +const detect_data_type = (s) => { + /* block_mask is the bit mask of block-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + let block_mask = 0xf3ffc07f; + let n; + + /* Check for non-textual ("block-listed") bytes. */ + for (n = 0; n <= 31; n++, block_mask >>>= 1) { + if ((block_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("allow-listed") bytes. */ + if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + for (n = 32; n < LITERALS$1; n++) { + if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "block-listed" or "allow-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +}; + + +let static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +const _tr_init$1 = (s) => +{ + + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +}; + + +/* =========================================================================== + * Send a stored block + */ +const _tr_stored_block$1 = (s, buf, stored_len, last) => { +//DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ + + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ + bi_windup(s); /* align on byte boundary */ + put_short(s, stored_len); + put_short(s, ~stored_len); + if (stored_len) { + s.pending_buf.set(s.window.subarray(buf, buf + stored_len), s.pending); + } + s.pending += stored_len; +}; + + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +const _tr_align$1 = (s) => { + send_bits(s, STATIC_TREES << 1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +}; + + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and write out the encoded block. + */ +const _tr_flush_block$1 = (s, buf, stored_len, last) => { +//DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ + + let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + let max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN$1) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->sym_next / 3)); + + if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } + + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block$1(s, buf, stored_len, last); + + } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) { + + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + + } else { + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +}; + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +const _tr_tally$1 = (s, dist, lc) => { +// deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ + + s.pending_buf[s.sym_buf + s.sym_next++] = dist; + s.pending_buf[s.sym_buf + s.sym_next++] = dist >> 8; + s.pending_buf[s.sym_buf + s.sym_next++] = lc; + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc * 2]/*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2]/*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; + } + + return (s.sym_next === s.sym_end); +}; + +var _tr_init_1 = _tr_init$1; +var _tr_stored_block_1 = _tr_stored_block$1; +var _tr_flush_block_1 = _tr_flush_block$1; +var _tr_tally_1 = _tr_tally$1; +var _tr_align_1 = _tr_align$1; + +var trees = { + _tr_init: _tr_init_1, + _tr_stored_block: _tr_stored_block_1, + _tr_flush_block: _tr_flush_block_1, + _tr_tally: _tr_tally_1, + _tr_align: _tr_align_1 +}; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It isn't worth it to make additional optimizations as in original. +// Small size is preferable. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const adler32 = (adler, buf, len, pos) => { + let s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +}; + + +var adler32_1 = adler32; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// Use ordinary array, since untyped makes no boost here +const makeTable = () => { + let c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +}; + +// Create table on load. Just 255 signed longs. Not a problem. +const crcTable$1 = new Uint32Array(makeTable()); + + +const crc32 = (crc, buf, len, pos) => { + const t = crcTable$1; + const end = pos + len; + + crc ^= -1; + + for (let i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +}; + + +var crc32_1 = crc32; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var messages = { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var constants$2 = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = trees; + + + + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH: Z_NO_FLUSH$2, Z_PARTIAL_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$3, Z_BLOCK: Z_BLOCK$1, + Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_BUF_ERROR: Z_BUF_ERROR$1, + Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1, + Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1, + Z_UNKNOWN, + Z_DEFLATED: Z_DEFLATED$2 +} = constants$2; + +/*============================================================================*/ + + +const MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +const MAX_WBITS$1 = 15; +/* 32K LZ77 window */ +const DEF_MEM_LEVEL = 8; + + +const LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +const LITERALS = 256; +/* number of literal bytes 0..255 */ +const L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +const D_CODES = 30; +/* number of distance codes */ +const BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +const HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ +const MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +const MIN_MATCH = 3; +const MAX_MATCH = 258; +const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + +const PRESET_DICT = 0x20; + +const INIT_STATE = 42; /* zlib header -> BUSY_STATE */ +//#ifdef GZIP +const GZIP_STATE = 57; /* gzip header -> BUSY_STATE | EXTRA_STATE */ +//#endif +const EXTRA_STATE = 69; /* gzip extra block -> NAME_STATE */ +const NAME_STATE = 73; /* gzip file name -> COMMENT_STATE */ +const COMMENT_STATE = 91; /* gzip comment -> HCRC_STATE */ +const HCRC_STATE = 103; /* gzip header CRC -> BUSY_STATE */ +const BUSY_STATE = 113; /* deflate -> FINISH_STATE */ +const FINISH_STATE = 666; /* stream complete */ + +const BS_NEED_MORE = 1; /* block not completed, need more input or more output */ +const BS_BLOCK_DONE = 2; /* block flush performed */ +const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ +const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +const err = (strm, errorCode) => { + strm.msg = messages[errorCode]; + return errorCode; +}; + +const rank = (f) => { + return ((f) * 2) - ((f) > 4 ? 9 : 0); +}; + +const zero = (buf) => { + let len = buf.length; while (--len >= 0) { buf[len] = 0; } +}; + +/* =========================================================================== + * Slide the hash table when sliding the window down (could be avoided with 32 + * bit values at the expense of memory usage). We slide even when level == 0 to + * keep the hash table consistent if we switch back to level > 0 later. + */ +const slide_hash = (s) => { + let n, m; + let p; + let wsize = s.w_size; + + n = s.hash_size; + p = n; + do { + m = s.head[--p]; + s.head[p] = (m >= wsize ? m - wsize : 0); + } while (--n); + n = wsize; +//#ifndef FASTEST + p = n; + do { + m = s.prev[--p]; + s.prev[p] = (m >= wsize ? m - wsize : 0); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +//#endif +}; + +/* eslint-disable new-cap */ +let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask; +// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 +// But breaks binary compatibility +//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; +let HASH = HASH_ZLIB; + + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output, except for + * some deflate_stored() output, goes through this function so some + * applications may wish to modify it to avoid allocating a large + * strm->next_out buffer and copying into it. (See also read_buf()). + */ +const flush_pending = (strm) => { + const s = strm.state; + + //_tr_flush_bits(s); + let len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) { return; } + + strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +}; + + +const flush_block_only = (s, last) => { + _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); + s.block_start = s.strstart; + flush_pending(s.strm); +}; + + +const put_byte = (s, b) => { + s.pending_buf[s.pending++] = b; +}; + + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +const putShortMSB = (s, b) => { + + // put_byte(s, (Byte)(b >> 8)); +// put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +}; + + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +const read_buf = (strm, buf, start, size) => { + + let len = strm.avail_in; + + if (len > size) { len = size; } + if (len === 0) { return 0; } + + strm.avail_in -= len; + + // zmemcpy(buf, strm->next_in, len); + buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); + if (strm.state.wrap === 1) { + strm.adler = adler32_1(strm.adler, buf, len, start); + } + + else if (strm.state.wrap === 2) { + strm.adler = crc32_1(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +}; + + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +const longest_match = (s, cur_match) => { + + let chain_length = s.max_chain_length; /* max hash chain length */ + let scan = s.strstart; /* current string */ + let match; /* matched string */ + let len; /* length of current match */ + let best_len = s.prev_length; /* best match length so far */ + let nice_match = s.nice_match; /* stop if match long enough */ + const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? + s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; + + const _win = s.window; // shortcut + + const wmask = s.w_mask; + const prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + const strend = s.strstart + MAX_MATCH; + let scan_end1 = _win[scan + best_len - 1]; + let scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) { nice_match = s.lookahead; } + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if (_win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1]) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +}; + + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +const fill_window = (s) => { + + const _w_size = s.w_size; + let n, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + + s.window.set(s.window.subarray(_w_size, _w_size + _w_size - more), 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + slide_hash(s); + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); +//#if MIN_MATCH != 3 +// Call update_hash() MIN_MATCH-3 more times +//#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ +// if (s.high_water < s.window_size) { +// const curr = s.strstart + s.lookahead; +// let init = 0; +// +// if (s.high_water < curr) { +// /* Previous high water mark below current data -- zero WIN_INIT +// * bytes or up to end of window, whichever is less. +// */ +// init = s.window_size - curr; +// if (init > WIN_INIT) +// init = WIN_INIT; +// zmemzero(s->window + curr, (unsigned)init); +// s->high_water = curr + init; +// } +// else if (s->high_water < (ulg)curr + WIN_INIT) { +// /* High water mark at or above current data, but below current data +// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up +// * to end of window, whichever is less. +// */ +// init = (ulg)curr + WIN_INIT - s->high_water; +// if (init > s->window_size - s->high_water) +// init = s->window_size - s->high_water; +// zmemzero(s->window + s->high_water, (unsigned)init); +// s->high_water += init; +// } +// } +// +// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, +// "not enough room for search"); +}; + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * + * In case deflateParams() is used to later switch to a non-zero compression + * level, s->matches (otherwise unused when storing) keeps track of the number + * of hash table slides to perform. If s->matches is 1, then one hash table + * slide will be done when switching. If s->matches is 2, the maximum value + * allowed here, then the hash table will be cleared, since two or more slides + * is the same as a clear. + * + * deflate_stored() is written to minimize the number of times an input byte is + * copied. It is most efficient with large input and output buffers, which + * maximizes the opportunites to have a single copy from next_in to next_out. + */ +const deflate_stored = (s, flush) => { + + /* Smallest worthy block size when not flushing or finishing. By default + * this is 32K. This can be as small as 507 bytes for memLevel == 1. For + * large input and output buffers, the stored block size will be larger. + */ + let min_block = s.pending_buf_size - 5 > s.w_size ? s.w_size : s.pending_buf_size - 5; + + /* Copy as many min_block or larger stored blocks directly to next_out as + * possible. If flushing, copy the remaining available input to next_out as + * stored blocks, if there is enough space. + */ + let len, left, have, last = 0; + let used = s.strm.avail_in; + do { + /* Set len to the maximum size block that we can copy directly with the + * available input data and output space. Set left to how much of that + * would be copied from what's left in the window. + */ + len = 65535/* MAX_STORED */; /* maximum deflate stored block length */ + have = (s.bi_valid + 42) >> 3; /* number of header bytes */ + if (s.strm.avail_out < have) { /* need room for header */ + break; + } + /* maximum stored block length that will fit in avail_out: */ + have = s.strm.avail_out - have; + left = s.strstart - s.block_start; /* bytes left in window */ + if (len > left + s.strm.avail_in) { + len = left + s.strm.avail_in; /* limit len to the input */ + } + if (len > have) { + len = have; /* limit len to the output */ + } + + /* If the stored block would be less than min_block in length, or if + * unable to copy all of the available input when flushing, then try + * copying to the window and the pending buffer instead. Also don't + * write an empty block when flushing -- deflate() does that. + */ + if (len < min_block && ((len === 0 && flush !== Z_FINISH$3) || + flush === Z_NO_FLUSH$2 || + len !== left + s.strm.avail_in)) { + break; + } + + /* Make a dummy stored block in pending to get the header bytes, + * including any pending bits. This also updates the debugging counts. + */ + last = flush === Z_FINISH$3 && len === left + s.strm.avail_in ? 1 : 0; + _tr_stored_block(s, 0, 0, last); + + /* Replace the lengths in the dummy stored block with len. */ + s.pending_buf[s.pending - 4] = len; + s.pending_buf[s.pending - 3] = len >> 8; + s.pending_buf[s.pending - 2] = ~len; + s.pending_buf[s.pending - 1] = ~len >> 8; + + /* Write the stored block header bytes. */ + flush_pending(s.strm); + +//#ifdef ZLIB_DEBUG +// /* Update debugging counts for the data about to be copied. */ +// s->compressed_len += len << 3; +// s->bits_sent += len << 3; +//#endif + + /* Copy uncompressed bytes from the window to next_out. */ + if (left) { + if (left > len) { + left = len; + } + //zmemcpy(s->strm->next_out, s->window + s->block_start, left); + s.strm.output.set(s.window.subarray(s.block_start, s.block_start + left), s.strm.next_out); + s.strm.next_out += left; + s.strm.avail_out -= left; + s.strm.total_out += left; + s.block_start += left; + len -= left; + } + + /* Copy uncompressed bytes directly from next_in to next_out, updating + * the check value. + */ + if (len) { + read_buf(s.strm, s.strm.output, s.strm.next_out, len); + s.strm.next_out += len; + s.strm.avail_out -= len; + s.strm.total_out += len; + } + } while (last === 0); + + /* Update the sliding window with the last s->w_size bytes of the copied + * data, or append all of the copied data to the existing window if less + * than s->w_size bytes were copied. Also update the number of bytes to + * insert in the hash tables, in the event that deflateParams() switches to + * a non-zero compression level. + */ + used -= s.strm.avail_in; /* number of input bytes directly copied */ + if (used) { + /* If any input was used, then no unused input remains in the window, + * therefore s->block_start == s->strstart. + */ + if (used >= s.w_size) { /* supplant the previous history */ + s.matches = 2; /* clear hash */ + //zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); + s.window.set(s.strm.input.subarray(s.strm.next_in - s.w_size, s.strm.next_in), 0); + s.strstart = s.w_size; + s.insert = s.strstart; + } + else { + if (s.window_size - s.strstart <= used) { + /* Slide the window down. */ + s.strstart -= s.w_size; + //zmemcpy(s->window, s->window + s->w_size, s->strstart); + s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); + if (s.matches < 2) { + s.matches++; /* add a pending slide_hash() */ + } + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + } + //zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); + s.window.set(s.strm.input.subarray(s.strm.next_in - used, s.strm.next_in), s.strstart); + s.strstart += used; + s.insert += used > s.w_size - s.insert ? s.w_size - s.insert : used; + } + s.block_start = s.strstart; + } + if (s.high_water < s.strstart) { + s.high_water = s.strstart; + } + + /* If the last block was written to next_out, then done. */ + if (last) { + return BS_FINISH_DONE; + } + + /* If flushing and all input has been consumed, then done. */ + if (flush !== Z_NO_FLUSH$2 && flush !== Z_FINISH$3 && + s.strm.avail_in === 0 && s.strstart === s.block_start) { + return BS_BLOCK_DONE; + } + + /* Fill the window with any remaining input. */ + have = s.window_size - s.strstart; + if (s.strm.avail_in > have && s.block_start >= s.w_size) { + /* Slide the window down. */ + s.block_start -= s.w_size; + s.strstart -= s.w_size; + //zmemcpy(s->window, s->window + s->w_size, s->strstart); + s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); + if (s.matches < 2) { + s.matches++; /* add a pending slide_hash() */ + } + have += s.w_size; /* more space now */ + if (s.insert > s.strstart) { + s.insert = s.strstart; + } + } + if (have > s.strm.avail_in) { + have = s.strm.avail_in; + } + if (have) { + read_buf(s.strm, s.window, s.strstart, have); + s.strstart += have; + s.insert += have > s.w_size - s.insert ? s.w_size - s.insert : have; + } + if (s.high_water < s.strstart) { + s.high_water = s.strstart; + } + + /* There was not enough avail_out to write a complete worthy or flushed + * stored block to next_out. Write a stored block to pending instead, if we + * have enough input for a worthy block, or if flushing and there is enough + * room for the remaining input as a stored block in the pending buffer. + */ + have = (s.bi_valid + 42) >> 3; /* number of header bytes */ + /* maximum stored block length that will fit in pending: */ + have = s.pending_buf_size - have > 65535/* MAX_STORED */ ? 65535/* MAX_STORED */ : s.pending_buf_size - have; + min_block = have > s.w_size ? s.w_size : have; + left = s.strstart - s.block_start; + if (left >= min_block || + ((left || flush === Z_FINISH$3) && flush !== Z_NO_FLUSH$2 && + s.strm.avail_in === 0 && left <= have)) { + len = left > have ? have : left; + last = flush === Z_FINISH$3 && s.strm.avail_in === 0 && + len === left ? 1 : 0; + _tr_stored_block(s, s.block_start, len, last); + s.block_start += len; + flush_pending(s.strm); + } + + /* We've done all we can with the available input and output. */ + return last ? BS_FINISH_STARTED : BS_NEED_MORE; +}; + + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +const deflate_fast = (s, flush) => { + + let hash_head; /* head of the hash chain */ + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else + { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); + +//#if MIN_MATCH != 3 +// Call UPDATE_HASH() MIN_MATCH-3 more times +//#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +const deflate_slow = (s, flush) => { + + let hash_head; /* head of hash chain */ + let bflush; /* set if current block must be flushed */ + + let max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH - 1; + + if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if (s.match_length <= 5 && + (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH - 1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length - 1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH - 1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +}; + + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +const deflate_rle = (s, flush) => { + + let bflush; /* set if current block must be flushed */ + let prev; /* byte at distance one to match */ + let scan, strend; /* scan goes up to strend for length of run */ + + const _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while (prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +const deflate_huff = (s, flush) => { + + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.sym_next) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +function Config(good_length, max_lazy, nice_length, max_chain, func) { + + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +} + +const configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ +]; + + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +const lm_init = (s) => { + + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +}; + + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED$2; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); + this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); + this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new Uint16Array(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new Uint16Array(2 * L_CODES + 1); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.sym_buf = 0; /* buffer for distances and literals/lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.sym_next = 0; /* running index in sym_buf */ + this.sym_end = 0; /* symbol table full when sym_next reaches this */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + + +/* ========================================================================= + * Check for a valid deflate stream state. Return 0 if ok, 1 if not. + */ +const deflateStateCheck = (strm) => { + + if (!strm) { + return 1; + } + const s = strm.state; + if (!s || s.strm !== strm || (s.status !== INIT_STATE && +//#ifdef GZIP + s.status !== GZIP_STATE && +//#endif + s.status !== EXTRA_STATE && + s.status !== NAME_STATE && + s.status !== COMMENT_STATE && + s.status !== HCRC_STATE && + s.status !== BUSY_STATE && + s.status !== FINISH_STATE)) { + return 1; + } + return 0; +}; + + +const deflateResetKeep = (strm) => { + + if (deflateStateCheck(strm)) { + return err(strm, Z_STREAM_ERROR$2); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + const s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = +//#ifdef GZIP + s.wrap === 2 ? GZIP_STATE : +//#endif + s.wrap ? INIT_STATE : BUSY_STATE; + strm.adler = (s.wrap === 2) ? + 0 // crc32(0, Z_NULL, 0) + : + 1; // adler32(0, Z_NULL, 0) + s.last_flush = -2; + _tr_init(s); + return Z_OK$3; +}; + + +const deflateReset = (strm) => { + + const ret = deflateResetKeep(strm); + if (ret === Z_OK$3) { + lm_init(strm.state); + } + return ret; +}; + + +const deflateSetHeader = (strm, head) => { + + if (deflateStateCheck(strm) || strm.state.wrap !== 2) { + return Z_STREAM_ERROR$2; + } + strm.state.gzhead = head; + return Z_OK$3; +}; + + +const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { + + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR$2; + } + let wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION$1) { + level = 6; + } + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } + + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) { + return err(strm, Z_STREAM_ERROR$2); + } + + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + const s = new DeflateState(); + + strm.state = s; + s.strm = strm; + s.status = INIT_STATE; /* to pass state test in deflateReset() */ + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new Uint8Array(s.w_size * 2); + s.head = new Uint16Array(s.hash_size); + s.prev = new Uint16Array(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + /* We overlay pending_buf and sym_buf. This works since the average size + * for length/distance pairs over any compressed block is assured to be 31 + * bits or less. + * + * Analysis: The longest fixed codes are a length code of 8 bits plus 5 + * extra bits, for lengths 131 to 257. The longest fixed distance codes are + * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest + * possible fixed-codes length/distance pair is then 31 bits total. + * + * sym_buf starts one-fourth of the way into pending_buf. So there are + * three bytes in sym_buf for every four bytes in pending_buf. Each symbol + * in sym_buf is three bytes -- two for the distance and one for the + * literal/length. As each symbol is consumed, the pointer to the next + * sym_buf value to read moves forward three bytes. From that symbol, up to + * 31 bits are written to pending_buf. The closest the written pending_buf + * bits gets to the next sym_buf symbol to read is just before the last + * code is written. At that time, 31*(n-2) bits have been written, just + * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at + * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 + * symbols are written.) The closest the writing gets to what is unread is + * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and + * can range from 128 to 32768. + * + * Therefore, at a minimum, there are 142 bits of space between what is + * written and what is read in the overlain buffers, so the symbols cannot + * be overwritten by the compressed data. That space is actually 139 bits, + * due to the three-bit fixed-code block header. + * + * That covers the case where either Z_FIXED is specified, forcing fixed + * codes, or when the use of fixed codes is chosen, because that choice + * results in a smaller compressed block than dynamic codes. That latter + * condition then assures that the above analysis also covers all dynamic + * blocks. A dynamic-code block will only be chosen to be emitted if it has + * fewer bits than a fixed-code block would for the same set of symbols. + * Therefore its average symbol length is assured to be less than 31. So + * the compressed data for a dynamic block also cannot overwrite the + * symbols from which it is being constructed. + */ + + s.pending_buf_size = s.lit_bufsize * 4; + s.pending_buf = new Uint8Array(s.pending_buf_size); + + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->sym_buf = s->pending_buf + s->lit_bufsize; + s.sym_buf = s.lit_bufsize; + + //s->sym_end = (s->lit_bufsize - 1) * 3; + s.sym_end = (s.lit_bufsize - 1) * 3; + /* We avoid equality with lit_bufsize*3 because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +}; + +const deflateInit = (strm, level) => { + + return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1); +}; + + +/* ========================================================================= */ +const deflate$2 = (strm, flush) => { + + if (deflateStateCheck(strm) || flush > Z_BLOCK$1 || flush < 0) { + return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2; + } + + const s = strm.state; + + if (!strm.output || + (strm.avail_in !== 0 && !strm.input) || + (s.status === FINISH_STATE && flush !== Z_FINISH$3)) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2); + } + + const old_flush = s.last_flush; + s.last_flush = flush; + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK$3; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH$3) { + return err(strm, Z_BUF_ERROR$1); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR$1); + } + + /* Write the header */ + if (s.status === INIT_STATE && s.wrap === 0) { + s.status = BUSY_STATE; + } + if (s.status === INIT_STATE) { + /* zlib header */ + let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8; + let level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= (level_flags << 6); + if (s.strstart !== 0) { header |= PRESET_DICT; } + header += 31 - (header % 31); + + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + } +//#ifdef GZIP + if (s.status === GZIP_STATE) { + /* gzip header */ + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + } + else { + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let left = (s.gzhead.extra.length & 0xffff) - s.gzindex; + while (s.pending + left > s.pending_buf_size) { + let copy = s.pending_buf_size - s.pending; + // zmemcpy(s.pending_buf + s.pending, + // s.gzhead.extra + s.gzindex, copy); + s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending); + s.pending = s.pending_buf_size; + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex += copy; + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + beg = 0; + left -= copy; + } + // JS specific: s.gzhead.extra may be TypedArray or Array for backward compatibility + // TypedArray.slice and TypedArray.from don't exist in IE10-IE11 + let gzhead_extra = new Uint8Array(s.gzhead.extra); + // zmemcpy(s->pending_buf + s->pending, + // s->gzhead->extra + s->gzindex, left); + s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending); + s.pending += left; + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex = 0; + } + s.status = NAME_STATE; + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let val; + do { + if (s.pending === s.pending_buf_size) { + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + beg = 0; + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + s.gzindex = 0; + } + s.status = COMMENT_STATE; + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + let beg = s.pending; /* start of bytes to update crc */ + let val; + do { + if (s.pending === s.pending_buf_size) { + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + beg = 0; + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + //--- HCRC_UPDATE(beg) ---// + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + //---// + } + s.status = HCRC_STATE; + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + } + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + } + s.status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s.pending !== 0) { + s.last_flush = -1; + return Z_OK$3; + } + } +//#endif + + /* Start a new block or continue the current one. + */ + if (strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE)) { + let bstate = s.level === 0 ? deflate_stored(s, flush) : + s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : + s.strategy === Z_RLE ? deflate_rle(s, flush) : + configuration_table[s.level].func(s, flush); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK$3; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + _tr_align(s); + } + else if (flush !== Z_BLOCK$1) { /* FULL_FLUSH or SYNC_FLUSH */ + + _tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH$1) { + /*** CLEAR_HASH(s); ***/ /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK$3; + } + } + } + + if (flush !== Z_FINISH$3) { return Z_OK$3; } + if (s.wrap <= 0) { return Z_STREAM_END$3; } + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } + else + { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) { s.wrap = -s.wrap; } + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3; +}; + + +const deflateEnd = (strm) => { + + if (deflateStateCheck(strm)) { + return Z_STREAM_ERROR$2; + } + + const status = strm.state.status; + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3; +}; + + +/* ========================================================================= + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. + */ +const deflateSetDictionary = (strm, dictionary) => { + + let dictLength = dictionary.length; + + if (deflateStateCheck(strm)) { + return Z_STREAM_ERROR$2; + } + + const s = strm.state; + const wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR$2; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + let tmpDict = new Uint8Array(s.w_size); + tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + const avail = strm.avail_in; + const next = strm.next_in; + const input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + let str = s.strstart; + let n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK$3; +}; + + +var deflateInit_1 = deflateInit; +var deflateInit2_1 = deflateInit2; +var deflateReset_1 = deflateReset; +var deflateResetKeep_1 = deflateResetKeep; +var deflateSetHeader_1 = deflateSetHeader; +var deflate_2$1 = deflate$2; +var deflateEnd_1 = deflateEnd; +var deflateSetDictionary_1 = deflateSetDictionary; +var deflateInfo = 'pako deflate (from Nodeca project)'; + +/* Not implemented +module.exports.deflateBound = deflateBound; +module.exports.deflateCopy = deflateCopy; +module.exports.deflateGetDictionary = deflateGetDictionary; +module.exports.deflateParams = deflateParams; +module.exports.deflatePending = deflatePending; +module.exports.deflatePrime = deflatePrime; +module.exports.deflateTune = deflateTune; +*/ + +var deflate_1$2 = { + deflateInit: deflateInit_1, + deflateInit2: deflateInit2_1, + deflateReset: deflateReset_1, + deflateResetKeep: deflateResetKeep_1, + deflateSetHeader: deflateSetHeader_1, + deflate: deflate_2$1, + deflateEnd: deflateEnd_1, + deflateSetDictionary: deflateSetDictionary_1, + deflateInfo: deflateInfo +}; + +const _has = (obj, key) => { + return Object.prototype.hasOwnProperty.call(obj, key); +}; + +var assign = function (obj /*from1, from2, from3, ...*/) { + const sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + const source = sources.shift(); + if (!source) { continue; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be non-object'); + } + + for (const p in source) { + if (_has(source, p)) { + obj[p] = source[p]; + } + } + } + + return obj; +}; + + +// Join array of chunks to single array. +var flattenChunks = (chunks) => { + // calculate data length + let len = 0; + + for (let i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } + + // join chunks + const result = new Uint8Array(len); + + for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { + let chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } + + return result; +}; + +var common = { + assign: assign, + flattenChunks: flattenChunks +}; + +// String encode/decode helpers + + +// Quick check if we can use fast array to bin string conversion +// +// - apply(Array) can fail on Android 2.2 +// - apply(Uint8Array) can fail on iOS 5.1 Safari +// +let STR_APPLY_UIA_OK = true; + +try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } + + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +const _utf8len = new Uint8Array(256); +for (let q = 0; q < 256; q++) { + _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); +} +_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start + + +// convert string to array (typed, when possible) +var string2buf = (str) => { + if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { + return new TextEncoder().encode(str); + } + + let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + buf = new Uint8Array(buf_len); + + // convert + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Helper +const buf2binstring = (buf, len) => { + // On Chrome, the arguments in a function call that are allowed is `65534`. + // If the length of the buffer is smaller than that, we can use this optimization, + // otherwise we will take a slower path. + if (len < 65534) { + if (buf.subarray && STR_APPLY_UIA_OK) { + return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); + } + } + + let result = ''; + for (let i = 0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; +}; + + +// convert array to string +var buf2string = (buf, max) => { + const len = max || buf.length; + + if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { + return new TextDecoder().decode(buf.subarray(0, max)); + } + + let i, out; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + const utf16buf = new Array(len * 2); + + for (out = 0, i = 0; i < len;) { + let c = buf[i++]; + // quick process ascii + if (c < 0x80) { utf16buf[out++] = c; continue; } + + let c_len = _utf8len[c]; + // skip 5 & 6 byte codes + if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + return buf2binstring(utf16buf, out); +}; + + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = (buf, max) => { + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + let pos = max - 1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means buffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +var strings = { + string2buf: string2buf, + buf2string: buf2string, + utf8border: utf8border +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +var zstream = ZStream; + +const toString$1 = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH: Z_FINISH$2, + Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, + Z_DEFAULT_COMPRESSION, + Z_DEFAULT_STRATEGY, + Z_DEFLATED: Z_DEFLATED$1 +} = constants$2; + +/* ===========================================================================*/ + + +/** + * class Deflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[deflate]], + * [[deflateRaw]] and [[gzip]]. + **/ + +/* internal + * Deflate.chunks -> Array + * + * Chunks of output data, if [[Deflate#onData]] not overridden. + **/ + +/** + * Deflate.result -> Uint8Array + * + * Compressed result, generated by default [[Deflate#onData]] + * and [[Deflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Deflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Deflate.err -> Number + * + * Error code after deflate finished. 0 (Z_OK) on success. + * You will not need it in real life, because deflate errors + * are possible only on wrong options or bad `onData` / `onEnd` + * custom handlers. + **/ + +/** + * Deflate.msg -> String + * + * Error message, if [[Deflate.err]] != 0 + **/ + + +/** + * new Deflate(options) + * - options (Object): zlib deflate options. + * + * Creates new deflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `level` + * - `windowBits` + * - `memLevel` + * - `strategy` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw deflate + * - `gzip` (Boolean) - create gzip wrapper + * - `header` (Object) - custom header for gzip + * - `text` (Boolean) - true if compressed data believed to be text + * - `time` (Number) - modification time, unix timestamp + * - `os` (Number) - operation system code + * - `extra` (Array) - array of bytes with extra data (max 65536) + * - `name` (String) - file name (binary string) + * - `comment` (String) - comment (binary string) + * - `hcrc` (Boolean) - true if header crc should be added + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const deflate = new pako.Deflate({ level: 3}); + * + * deflate.push(chunk1, false); + * deflate.push(chunk2, true); // true -> last chunk + * + * if (deflate.err) { throw new Error(deflate.err); } + * + * console.log(deflate.result); + * ``` + **/ +function Deflate$1(options) { + this.options = common.assign({ + level: Z_DEFAULT_COMPRESSION, + method: Z_DEFLATED$1, + chunkSize: 16384, + windowBits: 15, + memLevel: 8, + strategy: Z_DEFAULT_STRATEGY + }, options || {}); + + let opt = this.options; + + if (opt.raw && (opt.windowBits > 0)) { + opt.windowBits = -opt.windowBits; + } + + else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { + opt.windowBits += 16; + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + let status = deflate_1$2.deflateInit2( + this.strm, + opt.level, + opt.method, + opt.windowBits, + opt.memLevel, + opt.strategy + ); + + if (status !== Z_OK$2) { + throw new Error(messages[status]); + } + + if (opt.header) { + deflate_1$2.deflateSetHeader(this.strm, opt.header); + } + + if (opt.dictionary) { + let dict; + // Convert data if needed + if (typeof opt.dictionary === 'string') { + // If we need to compress text, change encoding to utf8. + dict = strings.string2buf(opt.dictionary); + } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') { + dict = new Uint8Array(opt.dictionary); + } else { + dict = opt.dictionary; + } + + status = deflate_1$2.deflateSetDictionary(this.strm, dict); + + if (status !== Z_OK$2) { + throw new Error(messages[status]); + } + + this._dict_set = true; + } +} + +/** + * Deflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be + * converted to utf8 byte sequence. + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. + * + * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with + * new compressed chunks. Returns `true` on success. The last data block must + * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending + * buffers and call [[Deflate#onEnd]]. + * + * On fail call [[Deflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Deflate$1.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + let status, _flush_mode; + + if (this.ended) { return false; } + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; + + // Convert data if needed + if (typeof data === 'string') { + // If we need to compress text, change encoding to utf8. + strm.input = strings.string2buf(data); + } else if (toString$1.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + // Make sure avail_out > 6 to avoid repeating markers + if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + status = deflate_1$2.deflate(strm, _flush_mode); + + // Ended => flush and finish + if (status === Z_STREAM_END$2) { + if (strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + } + status = deflate_1$2.deflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === Z_OK$2; + } + + // Flush if out buffer full + if (strm.avail_out === 0) { + this.onData(strm.output); + continue; + } + + // Flush if requested and has data + if (_flush_mode > 0 && strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + + +/** + * Deflate#onData(chunk) -> Void + * - chunk (Uint8Array): output data. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Deflate$1.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + + +/** + * Deflate#onEnd(status) -> Void + * - status (Number): deflate status. 0 (Z_OK) on success, + * other if not. + * + * Called once after you tell deflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Deflate$1.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK$2) { + this.result = common.flattenChunks(this.chunks); + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// See state defs from inflate.js +const BAD$1 = 16209; /* got a data error -- remain here until reset */ +const TYPE$1 = 16191; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +var inffast = function inflate_fast(strm, start) { + let _in; /* local strm.input */ + let last; /* have enough input while in < last */ + let _out; /* local strm.output */ + let beg; /* inflate()'s initial strm.output */ + let end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + let dmax; /* maximum distance from zlib header */ +//#endif + let wsize; /* window size or zero if not using window */ + let whave; /* valid bytes in the window */ + let wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + let s_window; /* allocated sliding window, if wsize != 0 */ + let hold; /* local strm.hold */ + let bits; /* local strm.bits */ + let lcode; /* local strm.lencode */ + let dcode; /* local strm.distcode */ + let lmask; /* mask for first level of length codes */ + let dmask; /* mask for first level of distance codes */ + let here; /* retrieved table entry */ + let op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + let len; /* match length, unused bytes */ + let dist; /* match distance */ + let from; /* where to copy match from */ + let from_source; + + + let input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + const state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD$1; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD$1; + break top; + } + +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD$1; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE$1; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD$1; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const MAXBITS = 15; +const ENOUGH_LENS$1 = 852; +const ENOUGH_DISTS$1 = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const CODES$1 = 0; +const LENS$1 = 1; +const DISTS$1 = 2; + +const lbase = new Uint16Array([ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]); + +const lext = new Uint8Array([ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]); + +const dbase = new Uint16Array([ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]); + +const dext = new Uint8Array([ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]); + +const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => +{ + const bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + let len = 0; /* a code's length in bits */ + let sym = 0; /* index of code symbols */ + let min = 0, max = 0; /* minimum and maximum code lengths */ + let root = 0; /* number of index bits for root table */ + let curr = 0; /* number of index bits for current table */ + let drop = 0; /* code bits to drop for sub-table */ + let left = 0; /* number of prefix codes available */ + let used = 0; /* code entries in table used */ + let huff = 0; /* Huffman code */ + let incr; /* for incrementing code, index */ + let fill; /* index for replicating entries */ + let low; /* low bits for current root entry */ + let mask; /* mask for low root bits */ + let next; /* next available space in table */ + let base = null; /* base value table to use */ +// let shoextra; /* extra bits table to use */ + let match; /* use base and extra for symbol >= match */ + const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + let extra = null; + + let here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES$1 || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES$1) { + base = extra = work; /* dummy value--not used */ + match = 20; + + } else if (type === LENS$1) { + base = lbase; + extra = lext; + match = 257; + + } else { /* DISTS */ + base = dbase; + extra = dext; + match = 0; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1)) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] + 1 < match) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] >= match) { + here_op = extra[work[sym] - match]; + here_val = base[work[sym] - match]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + + +var inftrees = inflate_table; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + + + + + + +const CODES = 0; +const LENS = 1; +const DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES, + Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR, + Z_DEFLATED +} = constants$2; + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +const HEAD = 16180; /* i: waiting for magic header */ +const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ +const TIME = 16182; /* i: waiting for modification time (gzip) */ +const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ +const EXLEN = 16184; /* i: waiting for extra length (gzip) */ +const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ +const NAME = 16186; /* i: waiting for end of file name (gzip) */ +const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ +const HCRC = 16188; /* i: waiting for header crc (gzip) */ +const DICTID = 16189; /* i: waiting for dictionary check value */ +const DICT = 16190; /* waiting for inflateSetDictionary() call */ +const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ +const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ +const STORED = 16193; /* i: waiting for stored size (length and complement) */ +const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ +const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ +const TABLE = 16196; /* i: waiting for dynamic block table lengths */ +const LENLENS = 16197; /* i: waiting for code length code lengths */ +const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ +const LEN_ = 16199; /* i: same as LEN below, but only first time in */ +const LEN = 16200; /* i: waiting for length/lit/eob code */ +const LENEXT = 16201; /* i: waiting for length extra bits */ +const DIST = 16202; /* i: waiting for distance code */ +const DISTEXT = 16203; /* i: waiting for distance extra bits */ +const MATCH = 16204; /* o: waiting for output space to copy string */ +const LIT = 16205; /* o: waiting for output space to write literal */ +const CHECK = 16206; /* i: waiting for 32-bit check value */ +const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ +const DONE = 16208; /* finished check, done -- remain here until reset */ +const BAD = 16209; /* got a data error -- remain here until reset */ +const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ +const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +const ENOUGH_LENS = 852; +const ENOUGH_DISTS = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const MAX_WBITS = 15; +/* 32K LZ77 window */ +const DEF_WBITS = MAX_WBITS; + + +const zswap32 = (q) => { + + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +}; + + +function InflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, + bit 2 true to validate check value */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib), or + -1 if raw or no header yet */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new Uint16Array(320); /* temporary storage for code lengths */ + this.work = new Uint16Array(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new Int32Array(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + + +const inflateStateCheck = (strm) => { + + if (!strm) { + return 1; + } + const state = strm.state; + if (!state || state.strm !== strm || + state.mode < HEAD || state.mode > SYNC) { + return 1; + } + return 0; +}; + + +const inflateResetKeep = (strm) => { + + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.flags = -1; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); + state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK$1; +}; + + +const inflateReset = (strm) => { + + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +}; + + +const inflateReset2 = (strm, windowBits) => { + let wrap; + + /* get the state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 5; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR$1; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +}; + + +const inflateInit2 = (strm, windowBits) => { + + if (!strm) { return Z_STREAM_ERROR$1; } + //strm.msg = Z_NULL; /* in case we return an error */ + + const state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.strm = strm; + state.window = null/*Z_NULL*/; + state.mode = HEAD; /* to pass state test in inflateReset2() */ + const ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK$1) { + strm.state = null/*Z_NULL*/; + } + return ret; +}; + + +const inflateInit = (strm) => { + + return inflateInit2(strm, DEF_WBITS); +}; + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +let virgin = true; + +let lenfix, distfix; // We have no pointers in JS, so keep tables separate + + +const fixedtables = (state) => { + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + lenfix = new Int32Array(512); + distfix = new Int32Array(32); + + /* literal/length table */ + let sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +}; + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +const updatewindow = (strm, src, end, copy) => { + + let dist; + const state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new Uint8Array(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + state.window.set(src.subarray(end - state.wsize, end), 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + state.window.set(src.subarray(end - copy, end), 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +}; + + +const inflate$2 = (strm, flush) => { + + let state; + let input, output; // input/output buffers + let next; /* next input INDEX */ + let put; /* next output INDEX */ + let have, left; /* available input and output */ + let hold; /* bit buffer */ + let bits; /* bits in bit buffer */ + let _in, _out; /* save starting available input and output */ + let copy; /* number of stored or match bytes to copy */ + let from; /* where to copy match bytes from */ + let from_source; + let here = 0; /* current decoding table entry */ + let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //let last; /* parent table entry */ + let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + let len; /* length to copy for repeats, bits to drop */ + let ret; /* return code */ + const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ + let opts; + + let n; // temporary variable for NEED_BITS + + const order = /* permutation of code lengths */ + new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); + + + if (inflateStateCheck(strm) || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR$1; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK$1; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + if (state.wbits === 0) { + state.wbits = 15; + } + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + if (len > 15 || len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + + // !!! pako patch. Force use `options.windowBits` if passed. + // Required to always use max window size by default. + state.dmax = 1 << state.wbits; + //state.dmax = 1 << len; + + state.flags = 0; /* indicate zlib header */ + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32_1(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more convenient processing later + state.head.extra = new Uint8Array(state.head.extra_len); + } + state.head.extra.set( + input.subarray( + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + next + copy + ), + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if ((state.flags & 0x0200) && (state.wrap & 4)) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT$1; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + output.set(input.subarray(next, next + copy), put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inffast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' instead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = + /*UPDATE_CHECK(state.check, put - _out, _out);*/ + (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END$1; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR$1; + break inf_leave; + case MEM: + return Z_MEM_ERROR$1; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR$1; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH$1))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if ((state.wrap & 4) && _out) { + strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { + ret = Z_BUF_ERROR; + } + return ret; +}; + + +const inflateEnd = (strm) => { + + if (inflateStateCheck(strm)) { + return Z_STREAM_ERROR$1; + } + + let state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK$1; +}; + + +const inflateGetHeader = (strm, head) => { + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + const state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK$1; +}; + + +const inflateSetDictionary = (strm, dictionary) => { + const dictLength = dictionary.length; + + let state; + let dictid; + let ret; + + /* check state */ + if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR$1; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32_1(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR$1; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR$1; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK$1; +}; + + +var inflateReset_1 = inflateReset; +var inflateReset2_1 = inflateReset2; +var inflateResetKeep_1 = inflateResetKeep; +var inflateInit_1 = inflateInit; +var inflateInit2_1 = inflateInit2; +var inflate_2$1 = inflate$2; +var inflateEnd_1 = inflateEnd; +var inflateGetHeader_1 = inflateGetHeader; +var inflateSetDictionary_1 = inflateSetDictionary; +var inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +module.exports.inflateCodesUsed = inflateCodesUsed; +module.exports.inflateCopy = inflateCopy; +module.exports.inflateGetDictionary = inflateGetDictionary; +module.exports.inflateMark = inflateMark; +module.exports.inflatePrime = inflatePrime; +module.exports.inflateSync = inflateSync; +module.exports.inflateSyncPoint = inflateSyncPoint; +module.exports.inflateUndermine = inflateUndermine; +module.exports.inflateValidate = inflateValidate; +*/ + +var inflate_1$2 = { + inflateReset: inflateReset_1, + inflateReset2: inflateReset2_1, + inflateResetKeep: inflateResetKeep_1, + inflateInit: inflateInit_1, + inflateInit2: inflateInit2_1, + inflate: inflate_2$1, + inflateEnd: inflateEnd_1, + inflateGetHeader: inflateGetHeader_1, + inflateSetDictionary: inflateSetDictionary_1, + inflateInfo: inflateInfo +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ''; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ''; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} + +var gzheader = GZheader; + +const toString = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, Z_FINISH, + Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR +} = constants$2; + +/* ===========================================================================*/ + + +/** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + **/ + +/* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overridden. + **/ + +/** + * Inflate.result -> Uint8Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + **/ + +/** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + **/ + +/** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + **/ + + +/** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + **/ +function Inflate$1(options) { + this.options = common.assign({ + chunkSize: 1024 * 64, + windowBits: 15, + to: '' + }, options || {}); + + const opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) { opt.windowBits = -15; } + } + + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ((opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits)) { + opt.windowBits += 32; + } + + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ''; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + let status = inflate_1$2.inflateInit2( + this.strm, + opt.windowBits + ); + + if (status !== Z_OK) { + throw new Error(messages[status]); + } + + this.header = new gzheader(); + + inflate_1$2.inflateGetHeader(this.strm, this.header); + + // Setup dictionary + if (opt.dictionary) { + // Convert data if needed + if (typeof opt.dictionary === 'string') { + opt.dictionary = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { + opt.dictionary = new Uint8Array(opt.dictionary); + } + if (opt.raw) { //In raw mode we need to set the dictionary early + status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); + if (status !== Z_OK) { + throw new Error(messages[status]); + } + } + } +} + +/** + * Inflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer): input data + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE + * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, + * `true` means Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. If end of stream detected, + * [[Inflate#onEnd]] will be called. + * + * `flush_mode` is not needed for normal operation, because end of stream + * detected automatically. You may try to use it for advanced things, but + * this functionality was not tested. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + **/ +Inflate$1.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + const dictionary = this.options.dictionary; + let status, _flush_mode, last_avail_out; + + if (this.ended) return false; + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; + + // Convert data if needed + if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + status = inflate_1$2.inflate(strm, _flush_mode); + + if (status === Z_NEED_DICT && dictionary) { + status = inflate_1$2.inflateSetDictionary(strm, dictionary); + + if (status === Z_OK) { + status = inflate_1$2.inflate(strm, _flush_mode); + } else if (status === Z_DATA_ERROR) { + // Replace code with more verbose + status = Z_NEED_DICT; + } + } + + // Skip snyc markers if more data follows and not raw mode + while (strm.avail_in > 0 && + status === Z_STREAM_END && + strm.state.wrap > 0 && + data[strm.next_in] !== 0) + { + inflate_1$2.inflateReset(strm); + status = inflate_1$2.inflate(strm, _flush_mode); + } + + switch (status) { + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + this.onEnd(status); + this.ended = true; + return false; + } + + // Remember real `avail_out` value, because we may patch out buffer content + // to align utf8 strings boundaries. + last_avail_out = strm.avail_out; + + if (strm.next_out) { + if (strm.avail_out === 0 || status === Z_STREAM_END) { + + if (this.options.to === 'string') { + + let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); + + let tail = strm.next_out - next_out_utf8; + let utf8str = strings.buf2string(strm.output, next_out_utf8); + + // move tail & realign counters + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); + + this.onData(utf8str); + + } else { + this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); + } + } + } + + // Must repeat iteration if out buffer is full + if (status === Z_OK && last_avail_out === 0) continue; + + // Finalize if end of stream reached. + if (status === Z_STREAM_END) { + status = inflate_1$2.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return true; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + + +/** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|String): output data. When string output requested, + * each chunk will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + **/ +Inflate$1.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + + +/** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + **/ +Inflate$1.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK) { + if (this.options.to === 'string') { + this.result = this.chunks.join(''); + } else { + this.result = common.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + + +/** + * inflate(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * + * ##### Example: + * + * ```javascript + * const pako = require('pako'); + * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); + * let output; + * + * try { + * output = pako.inflate(input); + * } catch (err) { + * console.log(err); + * } + * ``` + **/ +function inflate$1(input, options) { + const inflator = new Inflate$1(options); + + inflator.push(input); + + // That will never happens, if you don't cheat with options :) + if (inflator.err) throw inflator.msg || messages[inflator.err]; + + return inflator.result; +} + + +/** + * inflateRaw(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * The same as [[inflate]], but creates raw data, without wrapper + * (header and adler32 crc). + **/ +function inflateRaw$1(input, options) { + options = options || {}; + options.raw = true; + return inflate$1(input, options); +} + + +/** + * ungzip(data[, options]) -> Uint8Array|String + * - data (Uint8Array|ArrayBuffer): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + **/ + + +var Inflate_1$1 = Inflate$1; +var inflate_2 = inflate$1; +var inflateRaw_1$1 = inflateRaw$1; +var ungzip$1 = inflate$1; +var constants = constants$2; + +var inflate_1$1 = { + Inflate: Inflate_1$1, + inflate: inflate_2, + inflateRaw: inflateRaw_1$1, + ungzip: ungzip$1, + constants: constants +}; + +const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1; +var Inflate_1 = Inflate; +var inflate_1 = inflate; + +const crcTable = []; +for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + if (c & 1) { + c = 0xedb88320 ^ (c >>> 1); + } + else { + c = c >>> 1; + } + } + crcTable[n] = c; +} +const initialCrc = 0xffffffff; +function updateCrc(currentCrc, data, length) { + let c = currentCrc; + for (let n = 0; n < length; n++) { + c = crcTable[(c ^ data[n]) & 0xff] ^ (c >>> 8); + } + return c; +} +function crc(data, length) { + return (updateCrc(initialCrc, data, length) ^ initialCrc) >>> 0; +} +function checkCrc(buffer, crcLength, chunkName) { + const expectedCrc = buffer.readUint32(); + const actualCrc = crc(new Uint8Array(buffer.buffer, buffer.byteOffset + buffer.offset - crcLength - 4, crcLength), crcLength); // "- 4" because we already advanced by reading the CRC + if (actualCrc !== expectedCrc) { + throw new Error(`CRC mismatch for chunk ${chunkName}. Expected ${expectedCrc}, found ${actualCrc}`); + } +} + +function unfilterNone(currentLine, newLine, bytesPerLine) { + for (let i = 0; i < bytesPerLine; i++) { + newLine[i] = currentLine[i]; + } +} +function unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel) { + let i = 0; + for (; i < bytesPerPixel; i++) { + // just copy first bytes + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; + } +} +function unfilterUp(currentLine, newLine, prevLine, bytesPerLine) { + let i = 0; + if (prevLine.length === 0) { + // just copy bytes for first line + for (; i < bytesPerLine; i++) { + newLine[i] = currentLine[i]; + } + } + else { + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; + } + } +} +function unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { + let i = 0; + if (prevLine.length === 0) { + for (; i < bytesPerPixel; i++) { + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + (newLine[i - bytesPerPixel] >> 1)) & 0xff; + } + } + else { + for (; i < bytesPerPixel; i++) { + newLine[i] = (currentLine[i] + (prevLine[i] >> 1)) & 0xff; + } + for (; i < bytesPerLine; i++) { + newLine[i] = + (currentLine[i] + ((newLine[i - bytesPerPixel] + prevLine[i]) >> 1)) & + 0xff; + } + } +} +function unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { + let i = 0; + if (prevLine.length === 0) { + for (; i < bytesPerPixel; i++) { + newLine[i] = currentLine[i]; + } + for (; i < bytesPerLine; i++) { + newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; + } + } + else { + for (; i < bytesPerPixel; i++) { + newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; + } + for (; i < bytesPerLine; i++) { + newLine[i] = + (currentLine[i] + + paethPredictor$1(newLine[i - bytesPerPixel], prevLine[i], prevLine[i - bytesPerPixel])) & + 0xff; + } + } +} +function paethPredictor$1(a, b, c) { + const p = a + b - c; + const pa = Math.abs(p - a); + const pb = Math.abs(p - b); + const pc = Math.abs(p - c); + if (pa <= pb && pa <= pc) + return a; + else if (pb <= pc) + return b; + else + return c; +} + +/** + * Apllies filter on scanline based on the filter type. + * @param filterType - The filter type to apply. + * @param currentLine - The current line of pixel data. + * @param newLine - The new line of pixel data. + * @param prevLine - The previous line of pixel data. + * @param passLineBytes - The number of bytes in the pass line. + * @param bytesPerPixel - The number of bytes per pixel. + */ +function applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel) { + switch (filterType) { + case 0: + unfilterNone(currentLine, newLine, passLineBytes); + break; + case 1: + unfilterSub(currentLine, newLine, passLineBytes, bytesPerPixel); + break; + case 2: + unfilterUp(currentLine, newLine, prevLine, passLineBytes); + break; + case 3: + unfilterAverage(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + break; + case 4: + unfilterPaeth(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + break; + default: + throw new Error(`Unsupported filter: ${filterType}`); + } +} + +const uint16$1 = new Uint16Array([0x00ff]); +const uint8$1 = new Uint8Array(uint16$1.buffer); +const osIsLittleEndian$1 = uint8$1[0] === 0xff; +/** + * Decodes the Adam7 interlaced PNG data. + * + * @param params - DecodeInterlaceNullParams + * @returns - array of pixel data. + */ +function decodeInterlaceAdam7(params) { + const { data, width, height, channels, depth } = params; + // Adam7 interlacing pattern + const passes = [ + { x: 0, y: 0, xStep: 8, yStep: 8 }, // Pass 1 + { x: 4, y: 0, xStep: 8, yStep: 8 }, // Pass 2 + { x: 0, y: 4, xStep: 4, yStep: 8 }, // Pass 3 + { x: 2, y: 0, xStep: 4, yStep: 4 }, // Pass 4 + { x: 0, y: 2, xStep: 2, yStep: 4 }, // Pass 5 + { x: 1, y: 0, xStep: 2, yStep: 2 }, // Pass 6 + { x: 0, y: 1, xStep: 1, yStep: 2 }, // Pass 7 + ]; + const bytesPerPixel = Math.ceil(depth / 8) * channels; + const resultData = new Uint8Array(height * width * bytesPerPixel); + let offset = 0; + // Process each pass + for (let passIndex = 0; passIndex < 7; passIndex++) { + const pass = passes[passIndex]; + // Calculate pass dimensions + const passWidth = Math.ceil((width - pass.x) / pass.xStep); + const passHeight = Math.ceil((height - pass.y) / pass.yStep); + if (passWidth <= 0 || passHeight <= 0) + continue; + const passLineBytes = passWidth * bytesPerPixel; + const prevLine = new Uint8Array(passLineBytes); + // Process each scanline in this pass + for (let y = 0; y < passHeight; y++) { + // First byte is the filter type + const filterType = data[offset++]; + const currentLine = data.subarray(offset, offset + passLineBytes); + offset += passLineBytes; + // Create a new line for the unfiltered data + const newLine = new Uint8Array(passLineBytes); + // Apply the appropriate unfilter + applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); + prevLine.set(newLine); + for (let x = 0; x < passWidth; x++) { + const outputX = pass.x + x * pass.xStep; + const outputY = pass.y + y * pass.yStep; + if (outputX >= width || outputY >= height) + continue; + for (let i = 0; i < bytesPerPixel; i++) { + resultData[(outputY * width + outputX) * bytesPerPixel + i] = + newLine[x * bytesPerPixel + i]; + } + } + } + } + if (depth === 16) { + const uint16Data = new Uint16Array(resultData.buffer); + if (osIsLittleEndian$1) { + for (let k = 0; k < uint16Data.length; k++) { + // PNG is always big endian. Swap the bytes. + uint16Data[k] = swap16$1(uint16Data[k]); + } + } + return uint16Data; + } + else { + return resultData; + } +} +function swap16$1(val) { + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); +} + +const uint16 = new Uint16Array([0x00ff]); +const uint8 = new Uint8Array(uint16.buffer); +const osIsLittleEndian = uint8[0] === 0xff; +const empty = new Uint8Array(0); +function decodeInterlaceNull(params) { + const { data, width, height, channels, depth } = params; + const bytesPerPixel = Math.ceil(depth / 8) * channels; + const bytesPerLine = Math.ceil((depth / 8) * channels * width); + const newData = new Uint8Array(height * bytesPerLine); + let prevLine = empty; + let offset = 0; + let currentLine; + let newLine; + for (let i = 0; i < height; i++) { + currentLine = data.subarray(offset + 1, offset + 1 + bytesPerLine); + newLine = newData.subarray(i * bytesPerLine, (i + 1) * bytesPerLine); + switch (data[offset]) { + case 0: + unfilterNone(currentLine, newLine, bytesPerLine); + break; + case 1: + unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel); + break; + case 2: + unfilterUp(currentLine, newLine, prevLine, bytesPerLine); + break; + case 3: + unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); + break; + case 4: + unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); + break; + default: + throw new Error(`Unsupported filter: ${data[offset]}`); + } + prevLine = newLine; + offset += bytesPerLine + 1; + } + if (depth === 16) { + const uint16Data = new Uint16Array(newData.buffer); + if (osIsLittleEndian) { + for (let k = 0; k < uint16Data.length; k++) { + // PNG is always big endian. Swap the bytes. + uint16Data[k] = swap16(uint16Data[k]); + } + } + return uint16Data; + } + else { + return newData; + } +} +function swap16(val) { + return ((val & 0xff) << 8) | ((val >> 8) & 0xff); +} + +// https://www.w3.org/TR/PNG/#5PNG-file-signature +const pngSignature = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10); +function checkSignature(buffer) { + if (!hasPngSignature(buffer.readBytes(pngSignature.length))) { + throw new Error('wrong PNG signature'); + } +} +function hasPngSignature(array) { + if (array.length < pngSignature.length) { + return false; + } + for (let i = 0; i < pngSignature.length; i++) { + if (array[i] !== pngSignature[i]) { + return false; + } + } + return true; +} + +// https://www.w3.org/TR/png/#11tEXt +const textChunkName = 'tEXt'; +const NULL = 0; +const latin1Decoder = new TextDecoder('latin1'); +function validateKeyword(keyword) { + validateLatin1(keyword); + if (keyword.length === 0 || keyword.length > 79) { + throw new Error('keyword length must be between 1 and 79'); + } +} +// eslint-disable-next-line no-control-regex +const latin1Regex = /^[\u0000-\u00FF]*$/; +function validateLatin1(text) { + if (!latin1Regex.test(text)) { + throw new Error('invalid latin1 text'); + } +} +function decodetEXt(text, buffer, length) { + const keyword = readKeyword(buffer); + text[keyword] = readLatin1(buffer, length - keyword.length - 1); +} +// https://www.w3.org/TR/png/#11keywords +function readKeyword(buffer) { + buffer.mark(); + while (buffer.readByte() !== NULL) { + /* advance */ + } + const end = buffer.offset; + buffer.reset(); + const keyword = latin1Decoder.decode(buffer.readBytes(end - buffer.offset - 1)); + // NULL + buffer.skip(1); + validateKeyword(keyword); + return keyword; +} +function readLatin1(buffer, length) { + return latin1Decoder.decode(buffer.readBytes(length)); +} + +const ColorType = { + UNKNOWN: -1, + GREYSCALE: 0, + TRUECOLOUR: 2, + INDEXED_COLOUR: 3, + GREYSCALE_ALPHA: 4, + TRUECOLOUR_ALPHA: 6, +}; +const CompressionMethod = { + UNKNOWN: -1, + DEFLATE: 0, +}; +const FilterMethod = { + UNKNOWN: -1, + ADAPTIVE: 0, +}; +const InterlaceMethod = { + UNKNOWN: -1, + NO_INTERLACE: 0, + ADAM7: 1, +}; +const DisposeOpType = { + NONE: 0, + BACKGROUND: 1, + PREVIOUS: 2, +}; +const BlendOpType = { + SOURCE: 0, + OVER: 1, +}; + +class PngDecoder extends IOBuffer { + _checkCrc; + _inflator; + _png; + _apng; + _end; + _hasPalette; + _palette; + _hasTransparency; + _transparency; + _compressionMethod; + _filterMethod; + _interlaceMethod; + _colorType; + _isAnimated; + _numberOfFrames; + _numberOfPlays; + _frames; + _writingDataChunks; + constructor(data, options = {}) { + super(data); + const { checkCrc = false } = options; + this._checkCrc = checkCrc; + this._inflator = new Inflate_1(); + this._png = { + width: -1, + height: -1, + channels: -1, + data: new Uint8Array(0), + depth: 1, + text: {}, + }; + this._apng = { + width: -1, + height: -1, + channels: -1, + depth: 1, + numberOfFrames: 1, + numberOfPlays: 0, + text: {}, + frames: [], + }; + this._end = false; + this._hasPalette = false; + this._palette = []; + this._hasTransparency = false; + this._transparency = new Uint16Array(0); + this._compressionMethod = CompressionMethod.UNKNOWN; + this._filterMethod = FilterMethod.UNKNOWN; + this._interlaceMethod = InterlaceMethod.UNKNOWN; + this._colorType = ColorType.UNKNOWN; + this._isAnimated = false; + this._numberOfFrames = 1; + this._numberOfPlays = 0; + this._frames = []; + this._writingDataChunks = false; + // PNG is always big endian + // https://www.w3.org/TR/PNG/#7Integers-and-byte-order + this.setBigEndian(); + } + decode() { + checkSignature(this); + while (!this._end) { + const length = this.readUint32(); + const type = this.readChars(4); + this.decodeChunk(length, type); + } + this.decodeImage(); + return this._png; + } + decodeApng() { + checkSignature(this); + while (!this._end) { + const length = this.readUint32(); + const type = this.readChars(4); + this.decodeApngChunk(length, type); + } + this.decodeApngImage(); + return this._apng; + } + // https://www.w3.org/TR/PNG/#5Chunk-layout + decodeChunk(length, type) { + const offset = this.offset; + switch (type) { + // 11.2 Critical chunks + case 'IHDR': // 11.2.2 IHDR Image header + this.decodeIHDR(); + break; + case 'PLTE': // 11.2.3 PLTE Palette + this.decodePLTE(length); + break; + case 'IDAT': // 11.2.4 IDAT Image data + this.decodeIDAT(length); + break; + case 'IEND': // 11.2.5 IEND Image trailer + this._end = true; + break; + // 11.3 Ancillary chunks + case 'tRNS': // 11.3.2.1 tRNS Transparency + this.decodetRNS(length); + break; + case 'iCCP': // 11.3.3.3 iCCP Embedded ICC profile + this.decodeiCCP(length); + break; + case textChunkName: // 11.3.4.3 tEXt Textual data + decodetEXt(this._png.text, this, length); + break; + case 'pHYs': // 11.3.5.3 pHYs Physical pixel dimensions + this.decodepHYs(); + break; + default: + this.skip(length); + break; + } + if (this.offset - offset !== length) { + throw new Error(`Length mismatch while decoding chunk ${type}`); + } + if (this._checkCrc) { + checkCrc(this, length + 4, type); + } + else { + this.skip(4); + } + } + decodeApngChunk(length, type) { + const offset = this.offset; + if (type !== 'fdAT' && type !== 'IDAT' && this._writingDataChunks) { + this.pushDataToFrame(); + } + switch (type) { + case 'acTL': + this.decodeACTL(); + break; + case 'fcTL': + this.decodeFCTL(); + break; + case 'fdAT': + this.decodeFDAT(length); + break; + default: + this.decodeChunk(length, type); + this.offset = offset + length; + break; + } + if (this.offset - offset !== length) { + throw new Error(`Length mismatch while decoding chunk ${type}`); + } + if (this._checkCrc) { + checkCrc(this, length + 4, type); + } + else { + this.skip(4); + } + } + // https://www.w3.org/TR/PNG/#11IHDR + decodeIHDR() { + const image = this._png; + image.width = this.readUint32(); + image.height = this.readUint32(); + image.depth = checkBitDepth(this.readUint8()); + const colorType = this.readUint8(); + this._colorType = colorType; + let channels; + switch (colorType) { + case ColorType.GREYSCALE: + channels = 1; + break; + case ColorType.TRUECOLOUR: + channels = 3; + break; + case ColorType.INDEXED_COLOUR: + channels = 1; + break; + case ColorType.GREYSCALE_ALPHA: + channels = 2; + break; + case ColorType.TRUECOLOUR_ALPHA: + channels = 4; + break; + // Kept for exhaustiveness. + // eslint-disable-next-line unicorn/no-useless-switch-case + case ColorType.UNKNOWN: + default: + throw new Error(`Unknown color type: ${colorType}`); + } + this._png.channels = channels; + this._compressionMethod = this.readUint8(); + if (this._compressionMethod !== CompressionMethod.DEFLATE) { + throw new Error(`Unsupported compression method: ${this._compressionMethod}`); + } + this._filterMethod = this.readUint8(); + this._interlaceMethod = this.readUint8(); + } + decodeACTL() { + this._numberOfFrames = this.readUint32(); + this._numberOfPlays = this.readUint32(); + this._isAnimated = true; + } + decodeFCTL() { + const image = { + sequenceNumber: this.readUint32(), + width: this.readUint32(), + height: this.readUint32(), + xOffset: this.readUint32(), + yOffset: this.readUint32(), + delayNumber: this.readUint16(), + delayDenominator: this.readUint16(), + disposeOp: this.readUint8(), + blendOp: this.readUint8(), + data: new Uint8Array(0), + }; + this._frames.push(image); + } + // https://www.w3.org/TR/PNG/#11PLTE + decodePLTE(length) { + if (length % 3 !== 0) { + throw new RangeError(`PLTE field length must be a multiple of 3. Got ${length}`); + } + const l = length / 3; + this._hasPalette = true; + const palette = []; + this._palette = palette; + for (let i = 0; i < l; i++) { + palette.push([this.readUint8(), this.readUint8(), this.readUint8()]); + } + } + // https://www.w3.org/TR/PNG/#11IDAT + decodeIDAT(length) { + this._writingDataChunks = true; + const dataLength = length; + const dataOffset = this.offset + this.byteOffset; + this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + this.skip(length); + } + decodeFDAT(length) { + this._writingDataChunks = true; + let dataLength = length; + let dataOffset = this.offset + this.byteOffset; + dataOffset += 4; + dataLength -= 4; + this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + this.skip(length); + } + // https://www.w3.org/TR/PNG/#11tRNS + decodetRNS(length) { + switch (this._colorType) { + case ColorType.GREYSCALE: + case ColorType.TRUECOLOUR: { + if (length % 2 !== 0) { + throw new RangeError(`tRNS chunk length must be a multiple of 2. Got ${length}`); + } + if (length / 2 > this._png.width * this._png.height) { + throw new Error(`tRNS chunk contains more alpha values than there are pixels (${length / 2} vs ${this._png.width * this._png.height})`); + } + this._hasTransparency = true; + this._transparency = new Uint16Array(length / 2); + for (let i = 0; i < length / 2; i++) { + this._transparency[i] = this.readUint16(); + } + break; + } + case ColorType.INDEXED_COLOUR: { + if (length > this._palette.length) { + throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${length} vs ${this._palette.length})`); + } + let i = 0; + for (; i < length; i++) { + const alpha = this.readByte(); + this._palette[i].push(alpha); + } + for (; i < this._palette.length; i++) { + this._palette[i].push(255); + } + break; + } + // Kept for exhaustiveness. + /* eslint-disable unicorn/no-useless-switch-case */ + case ColorType.UNKNOWN: + case ColorType.GREYSCALE_ALPHA: + case ColorType.TRUECOLOUR_ALPHA: + default: { + throw new Error(`tRNS chunk is not supported for color type ${this._colorType}`); + } + /* eslint-enable unicorn/no-useless-switch-case */ + } + } + // https://www.w3.org/TR/PNG/#11iCCP + decodeiCCP(length) { + const name = readKeyword(this); + const compressionMethod = this.readUint8(); + if (compressionMethod !== CompressionMethod.DEFLATE) { + throw new Error(`Unsupported iCCP compression method: ${compressionMethod}`); + } + const compressedProfile = this.readBytes(length - name.length - 2); + this._png.iccEmbeddedProfile = { + name, + profile: inflate_1(compressedProfile), + }; + } + // https://www.w3.org/TR/PNG/#11pHYs + decodepHYs() { + const ppuX = this.readUint32(); + const ppuY = this.readUint32(); + const unitSpecifier = this.readByte(); + this._png.resolution = { x: ppuX, y: ppuY, unit: unitSpecifier }; + } + decodeApngImage() { + this._apng.width = this._png.width; + this._apng.height = this._png.height; + this._apng.channels = this._png.channels; + this._apng.depth = this._png.depth; + this._apng.numberOfFrames = this._numberOfFrames; + this._apng.numberOfPlays = this._numberOfPlays; + this._apng.text = this._png.text; + this._apng.resolution = this._png.resolution; + for (let i = 0; i < this._numberOfFrames; i++) { + const newFrame = { + sequenceNumber: this._frames[i].sequenceNumber, + delayNumber: this._frames[i].delayNumber, + delayDenominator: this._frames[i].delayDenominator, + data: this._apng.depth === 8 + ? new Uint8Array(this._apng.width * this._apng.height * this._apng.channels) + : new Uint16Array(this._apng.width * this._apng.height * this._apng.channels), + }; + const frame = this._frames.at(i); + if (frame) { + frame.data = decodeInterlaceNull({ + data: frame.data, + width: frame.width, + height: frame.height, + channels: this._apng.channels, + depth: this._apng.depth, + }); + if (this._hasPalette) { + this._apng.palette = this._palette; + } + if (this._hasTransparency) { + this._apng.transparency = this._transparency; + } + if (i === 0 || + (frame.xOffset === 0 && + frame.yOffset === 0 && + frame.width === this._png.width && + frame.height === this._png.height)) { + newFrame.data = frame.data; + } + else { + const prevFrame = this._apng.frames.at(i - 1); + this.disposeFrame(frame, prevFrame, newFrame); + this.addFrameDataToCanvas(newFrame, frame); + } + this._apng.frames.push(newFrame); + } + } + return this._apng; + } + disposeFrame(frame, prevFrame, imageFrame) { + switch (frame.disposeOp) { + case DisposeOpType.NONE: + break; + case DisposeOpType.BACKGROUND: + for (let row = 0; row < this._png.height; row++) { + for (let col = 0; col < this._png.width; col++) { + const index = (row * frame.width + col) * this._png.channels; + for (let channel = 0; channel < this._png.channels; channel++) { + imageFrame.data[index + channel] = 0; + } + } + } + break; + case DisposeOpType.PREVIOUS: + imageFrame.data.set(prevFrame.data); + break; + default: + throw new Error('Unknown disposeOp'); + } + } + addFrameDataToCanvas(imageFrame, frame) { + const maxValue = 1 << this._png.depth; + const calculatePixelIndices = (row, col) => { + const index = ((row + frame.yOffset) * this._png.width + frame.xOffset + col) * + this._png.channels; + const frameIndex = (row * frame.width + col) * this._png.channels; + return { index, frameIndex }; + }; + switch (frame.blendOp) { + case BlendOpType.SOURCE: + for (let row = 0; row < frame.height; row++) { + for (let col = 0; col < frame.width; col++) { + const { index, frameIndex } = calculatePixelIndices(row, col); + for (let channel = 0; channel < this._png.channels; channel++) { + imageFrame.data[index + channel] = + frame.data[frameIndex + channel]; + } + } + } + break; + // https://www.w3.org/TR/png-3/#13Alpha-channel-processing + case BlendOpType.OVER: + for (let row = 0; row < frame.height; row++) { + for (let col = 0; col < frame.width; col++) { + const { index, frameIndex } = calculatePixelIndices(row, col); + for (let channel = 0; channel < this._png.channels; channel++) { + const sourceAlpha = frame.data[frameIndex + this._png.channels - 1] / maxValue; + const foregroundValue = channel % (this._png.channels - 1) === 0 + ? 1 + : frame.data[frameIndex + channel]; + const value = Math.floor(sourceAlpha * foregroundValue + + (1 - sourceAlpha) * imageFrame.data[index + channel]); + imageFrame.data[index + channel] += value; + } + } + } + break; + default: + throw new Error('Unknown blendOp'); + } + } + decodeImage() { + if (this._inflator.err) { + throw new Error(`Error while decompressing the data: ${this._inflator.err}`); + } + const data = this._isAnimated + ? (this._frames?.at(0)).data + : this._inflator.result; + if (this._filterMethod !== FilterMethod.ADAPTIVE) { + throw new Error(`Filter method ${this._filterMethod} not supported`); + } + if (this._interlaceMethod === InterlaceMethod.NO_INTERLACE) { + this._png.data = decodeInterlaceNull({ + data: data, + width: this._png.width, + height: this._png.height, + channels: this._png.channels, + depth: this._png.depth, + }); + } + else if (this._interlaceMethod === InterlaceMethod.ADAM7) { + this._png.data = decodeInterlaceAdam7({ + data: data, + width: this._png.width, + height: this._png.height, + channels: this._png.channels, + depth: this._png.depth, + }); + } + else { + throw new Error(`Interlace method ${this._interlaceMethod} not supported`); + } + if (this._hasPalette) { + this._png.palette = this._palette; + } + if (this._hasTransparency) { + this._png.transparency = this._transparency; + } + } + pushDataToFrame() { + const result = this._inflator.result; + const lastFrame = this._frames.at(-1); + if (lastFrame) { + lastFrame.data = result; + } + else { + this._frames.push({ + sequenceNumber: 0, + width: this._png.width, + height: this._png.height, + xOffset: 0, + yOffset: 0, + delayNumber: 0, + delayDenominator: 0, + disposeOp: DisposeOpType.NONE, + blendOp: BlendOpType.SOURCE, + data: result, + }); + } + this._inflator = new Inflate_1(); + this._writingDataChunks = false; + } +} +function checkBitDepth(value) { + if (value !== 1 && + value !== 2 && + value !== 4 && + value !== 8 && + value !== 16) { + throw new Error(`invalid bit depth: ${value}`); + } + return value; +} + +var ResolutionUnitSpecifier; +(function (ResolutionUnitSpecifier) { + /** + * Unit is unknown + */ + ResolutionUnitSpecifier[ResolutionUnitSpecifier["UNKNOWN"] = 0] = "UNKNOWN"; + /** + * Unit is the metre + */ + ResolutionUnitSpecifier[ResolutionUnitSpecifier["METRE"] = 1] = "METRE"; +})(ResolutionUnitSpecifier || (ResolutionUnitSpecifier = {})); + +function decodePng(data, options) { + const decoder = new PngDecoder(data, options); + return decoder.decode(); +} + +/** + * @license + * + * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/* + * @see http://www.w3.org/TR/PNG-Chunks.html + * + Color Allowed Interpretation + Type Bit Depths + + 0 1,2,4,8,16 Each pixel is a grayscale sample. + + 2 8,16 Each pixel is an R,G,B triple. + + 3 1,2,4,8 Each pixel is a palette index; + a PLTE chunk must appear. + + 4 8,16 Each pixel is a grayscale sample, + followed by an alpha sample. + + 6 8,16 Each pixel is an R,G,B triple, + followed by an alpha sample. +*/ + +/* + * @name processPNG + * Entry point: process a PNG and return image dict and metadata for jsPDF + */ +jsPDF.API.processPNG = function(imageData, index, alias, compression) { + if (this.__addimage__.isArrayBuffer(imageData)) { + imageData = new Uint8Array(imageData); + } + if (!this.__addimage__.isArrayBufferView(imageData)) { + return; + } + + const decodedPng = decodePng(imageData, { checkCrc: true }); + const { + width, + height, + channels, + palette: decodedPalette, + depth: bitsPerComponent + } = decodedPng; + + let result; + if (decodedPalette && channels === 1) { + result = processIndexedPNG(decodedPng); + } else if (channels === 2 || channels === 4) { + result = processAlphaPNG(decodedPng); + } else { + result = processOpaquePNG(decodedPng); + } + + const { + colorSpace, + colorsPerPixel, + sMaskBitsPerComponent, + colorBytes, + alphaBytes, + needSMask, + palette, + mask + } = result; + + let predictor = null; + + let filter, decodeParameters, sMask; + if (canCompress(compression)) { + predictor = getPredictorFromCompression(compression); + filter = this.decode.FLATE_DECODE; + decodeParameters = `/Predictor ${predictor} /Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; + + const rowByteLength = Math.ceil( + (width * colorsPerPixel * bitsPerComponent) / 8 + ); + + imageData = compressBytes( + colorBytes, + rowByteLength, + colorsPerPixel, + bitsPerComponent, + compression + ); + if (needSMask) { + const sMaskRowByteLength = Math.ceil((width * sMaskBitsPerComponent) / 8); + sMask = compressBytes( + alphaBytes, + sMaskRowByteLength, + 1, + sMaskBitsPerComponent, + compression + ); + } + } else { + filter = undefined; + decodeParameters = undefined; + imageData = colorBytes; + if (needSMask) sMask = alphaBytes; + } + + if ( + this.__addimage__.isArrayBuffer(imageData) || + this.__addimage__.isArrayBufferView(imageData) + ) { + imageData = this.__addimage__.arrayBufferToBinaryString(imageData); + } + + if ( + (sMask && this.__addimage__.isArrayBuffer(sMask)) || + this.__addimage__.isArrayBufferView(sMask) + ) { + sMask = this.__addimage__.arrayBufferToBinaryString(sMask); + } + + return { + alias, + data: imageData, + index, + filter, + decodeParameters, + transparency: mask, + palette, + sMask, + predictor, + width, + height, + bitsPerComponent, + sMaskBitsPerComponent, + colorSpace + }; +}; + +/* + * PNG filter method types + * + * @see http://www.w3.org/TR/PNG-Filters.html + * @see http://www.libpng.org/pub/png/book/chapter09.html + * + * This is what the value 'Predictor' in decode params relates to + * + * 15 is "optimal prediction", which means the prediction algorithm can change from line to line. + * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte. + * + 0 None + 1 Sub + 2 Up + 3 Average + 4 Paeth + */ + +function canCompress(value) { + return value !== jsPDF.API.image_compression.NONE && hasCompressionJS(); +} + +function hasCompressionJS() { + return typeof zlibSync === "function"; +} +function compressBytes( + bytes, + lineByteLength, + channels, + bitsPerComponent, + compression +) { + let level = 4; + let filter_method = filterUp; + + switch (compression) { + case jsPDF.API.image_compression.FAST: + level = 1; + filter_method = filterSub; + break; + + case jsPDF.API.image_compression.MEDIUM: + level = 6; + filter_method = filterAverage; + break; + + case jsPDF.API.image_compression.SLOW: + level = 9; + filter_method = filterPaeth; + break; + } + + const bytesPerPixel = Math.ceil((channels * bitsPerComponent) / 8); + bytes = applyPngFilterMethod( + bytes, + lineByteLength, + bytesPerPixel, + filter_method + ); + const dat = zlibSync(bytes, { level: level }); + return jsPDF.API.__addimage__.arrayBufferToBinaryString(dat); +} + +function applyPngFilterMethod( + bytes, + lineByteLength, + bytesPerPixel, + filter_method +) { + const lines = bytes.length / lineByteLength; + const result = new Uint8Array(bytes.length + lines); + const filter_methods = getFilterMethods(); + let prevLine; + + for (let i = 0; i < lines; i += 1) { + const offset = i * lineByteLength; + const line = bytes.subarray(offset, offset + lineByteLength); + + if (filter_method) { + result.set(filter_method(line, bytesPerPixel, prevLine), offset + i); + } else { + const len = filter_methods.length; + const results = []; + + for (let j = 0; j < len; j += 1) { + results[j] = filter_methods[j](line, bytesPerPixel, prevLine); + } + + const ind = getIndexOfSmallestSum(results.concat()); + + result.set(results[ind], offset + i); + } + + prevLine = line; + } + + return result; +} + +function filterNone(line) { + /*const result = new Uint8Array(line.length + 1); + result[0] = 0; + result.set(line, 1);*/ + + const result = Array.apply([], line); + result.unshift(0); + + return result; +} + +function filterSub(line, colorsPerPixel) { + const len = line.length; + const result = []; + + result[0] = 1; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + result[i + 1] = (line[i] - left + 0x0100) & 0xff; + } + + return result; +} + +function filterUp(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 2; + + for (let i = 0; i < len; i += 1) { + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] - up + 0x0100) & 0xff; + } + + return result; +} + +function filterAverage(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 3; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff; + } + + return result; +} + +function filterPaeth(line, colorsPerPixel, prevLine) { + const len = line.length; + const result = []; + + result[0] = 4; + + for (let i = 0; i < len; i += 1) { + const left = line[i - colorsPerPixel] || 0; + const up = (prevLine && prevLine[i]) || 0; + const upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0; + const paeth = paethPredictor(left, up, upLeft); + result[i + 1] = (line[i] - paeth + 0x0100) & 0xff; + } + + return result; +} + +function paethPredictor(left, up, upLeft) { + if (left === up && up === upLeft) { + return left; + } + const pLeft = Math.abs(up - upLeft), + pUp = Math.abs(left - upLeft), + pUpLeft = Math.abs(left + up - upLeft - upLeft); + return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft; +} + +function getFilterMethods() { + return [filterNone, filterSub, filterUp, filterAverage, filterPaeth]; +} + +function getIndexOfSmallestSum(arrays) { + const sum = arrays.map(function(value) { + return value.reduce(function(pv, cv) { + return pv + Math.abs(cv); + }, 0); + }); + return sum.indexOf(Math.min.apply(null, sum)); +} + +function getPredictorFromCompression(compression) { + let predictor; + switch (compression) { + case jsPDF.API.image_compression.FAST: + predictor = 11; + break; + + case jsPDF.API.image_compression.MEDIUM: + predictor = 13; + break; + + case jsPDF.API.image_compression.SLOW: + predictor = 14; + break; + + default: + predictor = 12; + break; + } + return predictor; +} + +// Extracted helper for Indexed PNGs (palette-based) +function processIndexedPNG(decodedPng) { + const { width, height, data, palette: decodedPalette, depth } = decodedPng; + let needSMask = false; + let palette = []; + let mask = []; + let alphaBytes = undefined; + let hasSemiTransparency = false; + + const maxMaskLength = 1; + let maskLength = 0; + + for (let i = 0; i < decodedPalette.length; i++) { + const [r, g, b, a] = decodedPalette[i]; + palette.push(r, g, b); + if (a != null) { + if (a === 0) { + maskLength++; + if (mask.length < maxMaskLength) { + mask.push(i); + } + } else if (a < 255) { + hasSemiTransparency = true; + } + } + } + + if (hasSemiTransparency || maskLength > maxMaskLength) { + needSMask = true; + mask = undefined; + + const totalPixels = width * height; + // per PNG spec, palettes always use 8 bits per component + alphaBytes = new Uint8Array(totalPixels); + const dataView = new DataView(data.buffer); + for (let p = 0; p < totalPixels; p++) { + const paletteIndex = readSample(dataView, p, depth); + const [, , , alpha] = decodedPalette[paletteIndex]; + alphaBytes[p] = alpha; + } + } else if (maskLength === 0) { + mask = undefined; + } + + return { + colorSpace: "Indexed", + colorsPerPixel: 1, + sMaskBitsPerComponent: needSMask ? 8 : undefined, + colorBytes: data, + alphaBytes, + needSMask, + palette, + mask + }; +} + +/* + * Splits color and alpha values into separate buffers + */ +function processAlphaPNG(decodedPng) { + const { data, width, height, channels, depth } = decodedPng; + + const colorSpace = channels === 2 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = channels - 1; + + const totalPixels = width * height; + const colorChannels = colorsPerPixel; // 1 for Gray, 3 for RGB + const alphaChannels = 1; + const totalColorSamples = totalPixels * colorChannels; + const totalAlphaSamples = totalPixels * alphaChannels; + + const colorByteLen = Math.ceil((totalColorSamples * depth) / 8); + const alphaByteLen = Math.ceil((totalAlphaSamples * depth) / 8); + const colorBytes = new Uint8Array(colorByteLen); + const alphaBytes = new Uint8Array(alphaByteLen); + + const dataView = new DataView(data.buffer); + const colorView = new DataView(colorBytes.buffer); + const alphaView = new DataView(alphaBytes.buffer); + + let needSMask = false; + for (let p = 0; p < totalPixels; p++) { + const pixelStartIndex = p * channels; + for (let s = 0; s < colorChannels; s++) { + const sampleIndex = pixelStartIndex + s; + const colorValue = readSample(dataView, sampleIndex, depth); + writeSample(colorView, colorValue, p * colorChannels + s, depth); + } + const sampleIndex = pixelStartIndex + colorChannels; + const alphaValue = readSample(dataView, sampleIndex, depth); + if (alphaValue < (1 << depth) - 1) { + needSMask = true; + } + writeSample(alphaView, alphaValue, p * alphaChannels, depth); + } + + return { + colorSpace, + colorsPerPixel, + sMaskBitsPerComponent: needSMask ? depth : undefined, + colorBytes, + alphaBytes, + needSMask + }; +} + +function processOpaquePNG(decodedPng) { + const { data, channels } = decodedPng; + const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB"; + const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3; + + let colorBytes; + if (data instanceof Uint16Array) { + colorBytes = convertUint16ArrayToUint8Array(data); + } else { + colorBytes = data; + } + + return { colorSpace, colorsPerPixel, colorBytes, needSMask: false }; +} + +function convertUint16ArrayToUint8Array(data) { + // PNG/PDF expect MSB-first byte order. Since EcmaScript does not specify + // the byte order of Uint16Array, we need to use a DataView to ensure the + // correct byte order. + const sampleCount = data.length; + const out = new Uint8Array(sampleCount * 2); + const outView = new DataView(out.buffer, out.byteOffset, out.byteLength); + + for (let i = 0; i < sampleCount; i++) { + outView.setUint16(i * 2, data[i], false); + } + return out; +} + +function readSample(view, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const word = safeGetUint16(view, byteIndex); + return (word >> bitOffset) & bitMask; +} + +function writeSample(view, value, sampleIndex, depth) { + const bitIndex = sampleIndex * depth; + const byteIndex = Math.floor(bitIndex / 8); + const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); + const bitMask = (1 << depth) - 1; + const writeValue = (value & bitMask) << bitOffset; + const word = + safeGetUint16(view, byteIndex) & ~(bitMask << bitOffset) & 0xffff; + safeSetUint16(view, byteIndex, word | writeValue); +} + +function safeGetUint16(view, byteIndex) { + if (byteIndex + 1 < view.byteLength) { + return view.getUint16(byteIndex, false); + } + const b0 = view.getUint8(byteIndex); + return b0 << 8; +} + +function safeSetUint16(view, byteIndex, value) { + if (byteIndex + 1 < view.byteLength) { + view.setUint16(byteIndex, value, false); + return; + } + const byteToWrite = (value >> 8) & 0xff; + view.setUint8(byteIndex, byteToWrite); +} + +/** + * @license + * + * Copyright (c) 2021 Antti Palola, https://github.com/Pantura + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * jsPDF RGBA array PlugIn + * @name rgba_support + * @module + */ +(function(jsPDFAPI) { + + /** + * @name processRGBA + * @function + * + * Process RGBA Array. This is a one-dimension array with pixel data [red, green, blue, alpha, red, green, ...]. + * RGBA array data can be obtained from DOM canvas getImageData. + * @ignore + */ + jsPDFAPI.processRGBA = function(imageData, index, alias) { + + var imagePixels = imageData.data; + var length = imagePixels.length; + // jsPDF takes alpha data separately so extract that. + var rgbOut = new Uint8Array((length / 4) * 3); + var alphaOut = new Uint8Array(length / 4); + var outIndex = 0; + var alphaIndex = 0; + + for (var i = 0; i < length; i += 4) { + var r = imagePixels[i]; + var g = imagePixels[i + 1]; + var b = imagePixels[i + 2]; + var alpha = imagePixels[i + 3]; + rgbOut[outIndex++] = r; + rgbOut[outIndex++] = g; + rgbOut[outIndex++] = b; + alphaOut[alphaIndex++] = alpha; + } + + var rgbData = this.__addimage__.arrayBufferToBinaryString(rgbOut); + var alphaData = this.__addimage__.arrayBufferToBinaryString(alphaOut); + + return { + alpha: alphaData, + data: rgbData, + index: index, + alias: alias, + colorSpace: "DeviceRGB", + bitsPerComponent: 8, + width: imageData.width, + height: imageData.height + }; + }; +})(jsPDF.API); + +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * jsPDF setLanguage Plugin + * + * @name setLanguage + * @module + */ +(function(jsPDFAPI) { + + /** + * Add Language Tag to the generated PDF + * + * @name setLanguage + * @function + * @param {string} langCode The Language code as ISO-639-1 (e.g. 'en') or as country language code (e.g. 'en-GB'). + * @returns {jsPDF} + * @example + * var doc = new jsPDF() + * doc.text(10, 10, 'This is a test') + * doc.setLanguage("en-US") + * doc.save('english.pdf') + */ + jsPDFAPI.setLanguage = function(langCode) { + + var langCodes = { + af: "Afrikaans", + sq: "Albanian", + ar: "Arabic (Standard)", + "ar-DZ": "Arabic (Algeria)", + "ar-BH": "Arabic (Bahrain)", + "ar-EG": "Arabic (Egypt)", + "ar-IQ": "Arabic (Iraq)", + "ar-JO": "Arabic (Jordan)", + "ar-KW": "Arabic (Kuwait)", + "ar-LB": "Arabic (Lebanon)", + "ar-LY": "Arabic (Libya)", + "ar-MA": "Arabic (Morocco)", + "ar-OM": "Arabic (Oman)", + "ar-QA": "Arabic (Qatar)", + "ar-SA": "Arabic (Saudi Arabia)", + "ar-SY": "Arabic (Syria)", + "ar-TN": "Arabic (Tunisia)", + "ar-AE": "Arabic (U.A.E.)", + "ar-YE": "Arabic (Yemen)", + an: "Aragonese", + hy: "Armenian", + as: "Assamese", + ast: "Asturian", + az: "Azerbaijani", + eu: "Basque", + be: "Belarusian", + bn: "Bengali", + bs: "Bosnian", + br: "Breton", + bg: "Bulgarian", + my: "Burmese", + ca: "Catalan", + ch: "Chamorro", + ce: "Chechen", + zh: "Chinese", + "zh-HK": "Chinese (Hong Kong)", + "zh-CN": "Chinese (PRC)", + "zh-SG": "Chinese (Singapore)", + "zh-TW": "Chinese (Taiwan)", + cv: "Chuvash", + co: "Corsican", + cr: "Cree", + hr: "Croatian", + cs: "Czech", + da: "Danish", + nl: "Dutch (Standard)", + "nl-BE": "Dutch (Belgian)", + en: "English", + "en-AU": "English (Australia)", + "en-BZ": "English (Belize)", + "en-CA": "English (Canada)", + "en-IE": "English (Ireland)", + "en-JM": "English (Jamaica)", + "en-NZ": "English (New Zealand)", + "en-PH": "English (Philippines)", + "en-ZA": "English (South Africa)", + "en-TT": "English (Trinidad & Tobago)", + "en-GB": "English (United Kingdom)", + "en-US": "English (United States)", + "en-ZW": "English (Zimbabwe)", + eo: "Esperanto", + et: "Estonian", + fo: "Faeroese", + fj: "Fijian", + fi: "Finnish", + fr: "French (Standard)", + "fr-BE": "French (Belgium)", + "fr-CA": "French (Canada)", + "fr-FR": "French (France)", + "fr-LU": "French (Luxembourg)", + "fr-MC": "French (Monaco)", + "fr-CH": "French (Switzerland)", + fy: "Frisian", + fur: "Friulian", + gd: "Gaelic (Scots)", + "gd-IE": "Gaelic (Irish)", + gl: "Galacian", + ka: "Georgian", + de: "German (Standard)", + "de-AT": "German (Austria)", + "de-DE": "German (Germany)", + "de-LI": "German (Liechtenstein)", + "de-LU": "German (Luxembourg)", + "de-CH": "German (Switzerland)", + el: "Greek", + gu: "Gujurati", + ht: "Haitian", + he: "Hebrew", + hi: "Hindi", + hu: "Hungarian", + is: "Icelandic", + id: "Indonesian", + iu: "Inuktitut", + ga: "Irish", + it: "Italian (Standard)", + "it-CH": "Italian (Switzerland)", + ja: "Japanese", + kn: "Kannada", + ks: "Kashmiri", + kk: "Kazakh", + km: "Khmer", + ky: "Kirghiz", + tlh: "Klingon", + ko: "Korean", + "ko-KP": "Korean (North Korea)", + "ko-KR": "Korean (South Korea)", + la: "Latin", + lv: "Latvian", + lt: "Lithuanian", + lb: "Luxembourgish", + mk: "North Macedonia", + ms: "Malay", + ml: "Malayalam", + mt: "Maltese", + mi: "Maori", + mr: "Marathi", + mo: "Moldavian", + nv: "Navajo", + ng: "Ndonga", + ne: "Nepali", + no: "Norwegian", + nb: "Norwegian (Bokmal)", + nn: "Norwegian (Nynorsk)", + oc: "Occitan", + or: "Oriya", + om: "Oromo", + fa: "Persian", + "fa-IR": "Persian/Iran", + pl: "Polish", + pt: "Portuguese", + "pt-BR": "Portuguese (Brazil)", + pa: "Punjabi", + "pa-IN": "Punjabi (India)", + "pa-PK": "Punjabi (Pakistan)", + qu: "Quechua", + rm: "Rhaeto-Romanic", + ro: "Romanian", + "ro-MO": "Romanian (Moldavia)", + ru: "Russian", + "ru-MO": "Russian (Moldavia)", + sz: "Sami (Lappish)", + sg: "Sango", + sa: "Sanskrit", + sc: "Sardinian", + sd: "Sindhi", + si: "Singhalese", + sr: "Serbian", + sk: "Slovak", + sl: "Slovenian", + so: "Somani", + sb: "Sorbian", + es: "Spanish", + "es-AR": "Spanish (Argentina)", + "es-BO": "Spanish (Bolivia)", + "es-CL": "Spanish (Chile)", + "es-CO": "Spanish (Colombia)", + "es-CR": "Spanish (Costa Rica)", + "es-DO": "Spanish (Dominican Republic)", + "es-EC": "Spanish (Ecuador)", + "es-SV": "Spanish (El Salvador)", + "es-GT": "Spanish (Guatemala)", + "es-HN": "Spanish (Honduras)", + "es-MX": "Spanish (Mexico)", + "es-NI": "Spanish (Nicaragua)", + "es-PA": "Spanish (Panama)", + "es-PY": "Spanish (Paraguay)", + "es-PE": "Spanish (Peru)", + "es-PR": "Spanish (Puerto Rico)", + "es-ES": "Spanish (Spain)", + "es-UY": "Spanish (Uruguay)", + "es-VE": "Spanish (Venezuela)", + sx: "Sutu", + sw: "Swahili", + sv: "Swedish", + "sv-FI": "Swedish (Finland)", + "sv-SV": "Swedish (Sweden)", + ta: "Tamil", + tt: "Tatar", + te: "Teluga", + th: "Thai", + tig: "Tigre", + ts: "Tsonga", + tn: "Tswana", + tr: "Turkish", + tk: "Turkmen", + uk: "Ukrainian", + hsb: "Upper Sorbian", + ur: "Urdu", + ve: "Venda", + vi: "Vietnamese", + vo: "Volapuk", + wa: "Walloon", + cy: "Welsh", + xh: "Xhosa", + ji: "Yiddish", + zu: "Zulu" + }; + + if (this.internal.languageSettings === undefined) { + this.internal.languageSettings = {}; + this.internal.languageSettings.isSubscribed = false; + } + + if (langCodes[langCode] !== undefined) { + this.internal.languageSettings.languageCode = langCode; + if (this.internal.languageSettings.isSubscribed === false) { + this.internal.events.subscribe("putCatalog", function() { + this.internal.write( + "/Lang (" + this.internal.languageSettings.languageCode + ")" + ); + }); + this.internal.languageSettings.isSubscribed = true; + } + } + return this; + }; +})(jsPDF.API); + +/** @license + * MIT license. + * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * jsPDF split_text_to_size plugin + * + * @name split_text_to_size + * @module + */ +(function(API) { + /** + * Returns an array of length matching length of the 'word' string, with each + * cell occupied by the width of the char in that position. + * + * @name getCharWidthsArray + * @function + * @param {string} text + * @param {Object} options + * @returns {Array} + */ + var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { + options = options || {}; + + var activeFont = options.font || this.internal.getFont(); + var fontSize = options.fontSize || this.internal.getFontSize(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + + var widths = options.widths + ? options.widths + : activeFont.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning + ? options.kerning + : activeFont.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + var doKerning = options.doKerning === false ? false : true; + var kerningValue = 0; + + var i; + var length = text.length; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0; i < length; i++) { + char_code = text.charCodeAt(i); + + if (typeof activeFont.metadata.widthOfString === "function") { + output.push( + (activeFont.metadata.widthOfGlyph( + activeFont.metadata.characterToGlyph(char_code) + ) + + charSpace * (1000 / fontSize) || 0) / 1000 + ); + } else { + if ( + doKerning && + typeof kerning[char_code] === "object" && + !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) + ) { + kerningValue = + kerning[char_code][prior_char_code] / kerningFractionOf; + } else { + kerningValue = 0; + } + output.push( + (widths[char_code] || default_char_width) / widthsFractionOf + + kerningValue + ); + } + prior_char_code = char_code; + } + + return output; + }); + + /** + * Returns a widths of string in a given font, if the font size is set as 1 point. + * + * In other words, this is "proportional" value. For 1 unit of font size, the length + * of the string will be that much. + * + * Multiply by font size to get actual width in *points* + * Then divide by 72 to get inches or divide by (72/25.4) to get 'mm' etc. + * + * @name getStringUnitWidth + * @public + * @function + * @param {string} text + * @param {string} options + * @returns {number} result + */ + var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { + options = options || {}; + + var fontSize = options.fontSize || this.internal.getFontSize(); + var font = options.font || this.internal.getFont(); + var charSpace = options.charSpace || this.internal.getCharSpace(); + var result = 0; + + if (API.processArabic) { + text = API.processArabic(text); + } + + if (typeof font.metadata.widthOfString === "function") { + result = + font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; + } else { + result = getCharWidthsArray + .apply(this, arguments) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } + return result; + }); + + /** + returns array of lines + */ + var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { + var answer = []; + + // 1st, chop off the piece that can fit on the hanging line. + var i = 0, + l = word.length, + workingLen = 0; + while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { + workingLen += widths_array[i]; + i++; + } + // this is first line. + answer.push(word.slice(0, i)); + + // 2nd. Split the rest into maxLen pieces. + var startOfLine = i; + workingLen = 0; + while (i !== l) { + if (workingLen + widths_array[i] > maxLen) { + answer.push(word.slice(startOfLine, i)); + workingLen = 0; + startOfLine = i; + } + workingLen += widths_array[i]; + i++; + } + if (startOfLine !== i) { + answer.push(word.slice(startOfLine, i)); + } + + return answer; + }; + + // Note, all sizing inputs for this function must be in "font measurement units" + // By default, for PDF, it's "point". + var splitParagraphIntoLines = function(text, maxlen, options) { + // at this time works only on Western scripts, ones with space char + // separating the words. Feel free to expand. + + if (!options) { + options = {}; + } + + var line = [], + lines = [line], + line_length = options.textIndent || 0, + separator_length = 0, + current_word_length = 0, + word, + widths_array, + words = text.split(" "), + spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], + i, + l, + tmp, + lineIndent; + + if (options.lineIndent === -1) { + lineIndent = words[0].length + 2; + } else { + lineIndent = options.lineIndent || 0; + } + if (lineIndent) { + var pad = Array(lineIndent).join(" "), + wrds = []; + words.map(function(wrd) { + wrd = wrd.split(/\s*\n/); + if (wrd.length > 1) { + wrds = wrds.concat( + wrd.map(function(wrd, idx) { + return (idx && wrd.length ? "\n" : "") + wrd; + }) + ); + } else { + wrds.push(wrd[0]); + } + }); + words = wrds; + lineIndent = getStringUnitWidth.apply(this, [pad, options]); + } + + for (i = 0, l = words.length; i < l; i++) { + var force = 0; + + word = words[i]; + if (lineIndent && word[0] == "\n") { + word = word.substr(1); + force = 1; + } + widths_array = getCharWidthsArray.apply(this, [word, options]); + current_word_length = widths_array.reduce(function(pv, cv) { + return pv + cv; + }, 0); + + if ( + line_length + separator_length + current_word_length > maxlen || + force + ) { + if (current_word_length > maxlen) { + // this happens when you have space-less long URLs for example. + // we just chop these to size. We do NOT insert hiphens + tmp = splitLongWord.apply(this, [ + word, + widths_array, + maxlen - (line_length + separator_length), + maxlen + ]); + // first line we add to existing line object + line.push(tmp.shift()); // it's ok to have extra space indicator there + // last line we make into new line object + line = [tmp.pop()]; + // lines in the middle we apped to lines object as whole lines + while (tmp.length) { + lines.push([tmp.shift()]); // single fragment occupies whole line + } + current_word_length = widths_array + .slice(word.length - (line[0] ? line[0].length : 0)) + .reduce(function(pv, cv) { + return pv + cv; + }, 0); + } else { + // just put it on a new line + line = [word]; + } + + // now we attach new line to lines + lines.push(line); + line_length = current_word_length + lineIndent; + separator_length = spaceCharWidth; + } else { + line.push(word); + + line_length += separator_length + current_word_length; + separator_length = spaceCharWidth; + } + } + + var postProcess; + if (lineIndent) { + postProcess = function(ln, idx) { + return (idx ? pad : "") + ln.join(" "); + }; + } else { + postProcess = function(ln) { + return ln.join(" "); + }; + } + + return lines.map(postProcess); + }; + + /** + * Splits a given string into an array of strings. Uses 'size' value + * (in measurement units declared as default for the jsPDF instance) + * and the font's "widths" and "Kerning" tables, where available, to + * determine display length of a given string for a given font. + * + * We use character's 100% of unit size (height) as width when Width + * table or other default width is not available. + * + * @name splitTextToSize + * @public + * @function + * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. + * @param {number} size Nominal number, measured in units default to this instance of jsPDF. + * @param {Object} options Optional flags needed for chopper to do the right thing. + * @returns {Array} array Array with strings chopped to size. + */ + API.splitTextToSize = function(text, maxlen, options) { + + options = options || {}; + + var fsize = options.fontSize || this.internal.getFontSize(), + newOptions = function(options) { + var widths = { + 0: 1 + }, + kerning = {}; + + if (!options.widths || !options.kerning) { + var f = this.internal.getFont(options.fontName, options.fontStyle), + encoding = "Unicode"; + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE + // Actual JavaScript-native String's 16bit char codes used. + // no multi-byte logic here + + if (f.metadata[encoding]) { + return { + widths: f.metadata[encoding].widths || widths, + kerning: f.metadata[encoding].kerning || kerning + }; + } else { + return { + font: f.metadata, + fontSize: this.internal.getFontSize(), + charSpace: this.internal.getCharSpace() + }; + } + } else { + return { + widths: options.widths, + kerning: options.kerning + }; + } + }.call(this, options); + + // first we split on end-of-line chars + var paragraphs; + if (Array.isArray(text)) { + paragraphs = text; + } else { + paragraphs = String(text).split(/\r?\n/); + } + + // now we convert size (max length of line) into "font size units" + // at present time, the "font size unit" is always 'point' + // 'proportional' means, "in proportion to font size" + var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; + // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. + // this may change in the future? + // until then, proportional_maxlen is likely to be in 'points' + + // If first line is to be indented (shorter or longer) than maxLen + // we indicate that by using CSS-style "text-indent" option. + // here it's in font units too (which is likely 'points') + // it can be negative (which makes the first line longer than maxLen) + newOptions.textIndent = options.textIndent + ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize + : 0; + newOptions.lineIndent = options.lineIndent; + + var i, + l, + output = []; + for (i = 0, l = paragraphs.length; i < l; i++) { + output = output.concat( + splitParagraphIntoLines.apply(this, [ + paragraphs[i], + fontUnit_maxLen, + newOptions + ]) + ); + } + + return output; + }; +})(jsPDF.API); + +/** @license + jsPDF standard_fonts_metrics plugin + * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems + * MIT license. + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * This file adds the standard font metrics to jsPDF. + * + * Font metrics data is reprocessed derivative of contents of + * "Font Metrics for PDF Core 14 Fonts" package, which exhibits the following copyright and license: + * + * Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved. + * + * This file and the 14 PostScript(R) AFM files it accompanies may be used, + * copied, and distributed for any purpose and without charge, with or without + * modification, provided that all copyright notices are retained; that the AFM + * files are not distributed without this file; that all modifications to this + * file or any of the AFM files are prominently noted in the modified file(s); + * and that this paragraph is not modified. Adobe Systems has no responsibility + * or obligation to support the use of the AFM files. + * + * @name standard_fonts_metrics + * @module + */ + +(function(API) { + API.__fontmetrics__ = API.__fontmetrics__ || {}; + + var decoded = "0123456789abcdef", + encoded = "klmnopqrstuvwxyz", + mappingUncompress = {}, + mappingCompress = {}; + + for (var i = 0; i < encoded.length; i++) { + mappingUncompress[encoded[i]] = decoded[i]; + mappingCompress[decoded[i]] = encoded[i]; + } + + var hex = function(value) { + return "0x" + parseInt(value, 10).toString(16); + }; + + var compress = (API.__fontmetrics__.compress = function(data) { + var vals = ["{"]; + var value, keystring, valuestring, numberprefix; + + for (var key in data) { + value = data[key]; + + if (!isNaN(parseInt(key, 10))) { + key = parseInt(key, 10); + keystring = hex(key).slice(2); + keystring = + keystring.slice(0, -1) + mappingCompress[keystring.slice(-1)]; + } else { + keystring = "'" + key + "'"; + } + + if (typeof value == "number") { + if (value < 0) { + valuestring = hex(value).slice(3); + numberprefix = "-"; + } else { + valuestring = hex(value).slice(2); + numberprefix = ""; + } + valuestring = + numberprefix + + valuestring.slice(0, -1) + + mappingCompress[valuestring.slice(-1)]; + } else { + if (typeof value === "object") { + valuestring = compress(value); + } else { + throw new Error( + "Don't know what to do with value type " + typeof value + "." + ); + } + } + vals.push(keystring + valuestring); + } + vals.push("}"); + return vals.join(""); + }); + + /** + * Uncompresses data compressed into custom, base16-like format. + * + * @public + * @function + * @param + * @returns {Type} + */ + var uncompress = (API.__fontmetrics__.uncompress = function(data) { + if (typeof data !== "string") { + throw new Error("Invalid argument passed to uncompress."); + } + + var output = {}, + sign = 1, + stringparts, // undef. will be [] in string mode + activeobject = output, + parentchain = [], + parent_key_pair, + keyparts = "", + valueparts = "", + key, // undef. will be Truthy when Key is resolved. + datalen = data.length - 1, // stripping ending } + ch; + + for (var i = 1; i < datalen; i += 1) { + // - { } ' are special. + + ch = data[i]; + + if (ch == "'") { + if (stringparts) { + // end of string mode + key = stringparts.join(""); + stringparts = undefined; + } else { + // start of string mode + stringparts = []; + } + } else if (stringparts) { + stringparts.push(ch); + } else if (ch == "{") { + // start of object + parentchain.push([activeobject, key]); + activeobject = {}; + key = undefined; + } else if (ch == "}") { + // end of object + parent_key_pair = parentchain.pop(); + parent_key_pair[0][parent_key_pair[1]] = activeobject; + key = undefined; + activeobject = parent_key_pair[0]; + } else if (ch == "-") { + sign = -1; + } else { + // must be number + if (key === undefined) { + if (mappingUncompress.hasOwnProperty(ch)) { + keyparts += mappingUncompress[ch]; + key = parseInt(keyparts, 16) * sign; + sign = +1; + keyparts = ""; + } else { + keyparts += ch; + } + } else { + if (mappingUncompress.hasOwnProperty(ch)) { + valueparts += mappingUncompress[ch]; + activeobject[key] = parseInt(valueparts, 16) * sign; + sign = +1; + key = undefined; + valueparts = ""; + } else { + valueparts += ch; + } + } + } + } + return output; + }); + + // encoding = 'Unicode' + // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE. NO clever BOM behavior + // Actual 16bit char codes used. + // no multi-byte logic here + + // Unicode characters to WinAnsiEncoding: + // {402: 131, 8211: 150, 8212: 151, 8216: 145, 8217: 146, 8218: 130, 8220: 147, 8221: 148, 8222: 132, 8224: 134, 8225: 135, 8226: 149, 8230: 133, 8364: 128, 8240:137, 8249: 139, 8250: 155, 710: 136, 8482: 153, 338: 140, 339: 156, 732: 152, 352: 138, 353: 154, 376: 159, 381: 142, 382: 158} + // as you can see, all Unicode chars are outside of 0-255 range. No char code conflicts. + // this means that you can give Win cp1252 encoded strings to jsPDF for rendering directly + // as well as give strings with some (supported by these fonts) Unicode characters and + // these will be mapped to win cp1252 + // for example, you can send char code (cp1252) 0x80 or (unicode) 0x20AC, getting "Euro" glyph displayed in both cases. + + var encodingBlock = { + codePages: ["WinAnsiEncoding"], + WinAnsiEncoding: uncompress( + "{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}" + ) + }; + var encodings = { + Unicode: { + Courier: encodingBlock, + "Courier-Bold": encodingBlock, + "Courier-BoldOblique": encodingBlock, + "Courier-Oblique": encodingBlock, + Helvetica: encodingBlock, + "Helvetica-Bold": encodingBlock, + "Helvetica-BoldOblique": encodingBlock, + "Helvetica-Oblique": encodingBlock, + "Times-Roman": encodingBlock, + "Times-Bold": encodingBlock, + "Times-BoldItalic": encodingBlock, + "Times-Italic": encodingBlock + // , 'Symbol' + // , 'ZapfDingbats' + } + }; + + var fontMetrics = { + Unicode: { + // all sizing numbers are n/fontMetricsFractionOf = one font size unit + // this means that if fontMetricsFractionOf = 1000, and letter A's width is 476, it's + // width is 476/1000 or 47.6% of its height (regardless of font size) + // At this time this value applies to "widths" and "kerning" numbers. + + // char code 0 represents "default" (average) width - use it for chars missing in this table. + // key 'fof' represents the "fontMetricsFractionOf" value + + "Courier-Oblique": uncompress( + "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" + ), + "Times-BoldItalic": uncompress( + "{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}" + ), + "Helvetica-Bold": uncompress( + "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" + ), + Courier: uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), + "Courier-BoldOblique": uncompress( + "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" + ), + "Times-Bold": uncompress( + "{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}" + ), + Symbol: uncompress( + "{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}" + ), + Helvetica: uncompress( + "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" + ), + "Helvetica-BoldOblique": uncompress( + "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" + ), + ZapfDingbats: uncompress("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"), + "Courier-Bold": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), + "Times-Italic": uncompress( + "{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}" + ), + "Times-Roman": uncompress( + "{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}" + ), + "Helvetica-Oblique": uncompress( + "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" + ) + } + }; + + /* + This event handler is fired when a new jsPDF object is initialized + This event handler appends metrics data to standard fonts within + that jsPDF instance. The metrics are mapped over Unicode character + codes, NOT CIDs or other codes matching the StandardEncoding table of the + standard PDF fonts. + Future: + Also included is the encoding maping table, converting Unicode (UCS-2, UTF-16) + char codes to StandardEncoding character codes. The encoding table is to be used + somewhere around "pdfEscape" call. + */ + API.events.push([ + "addFont", + function(data) { + var font = data.font; + + var metrics = fontMetrics["Unicode"][font.postScriptName]; + if (metrics) { + font.metadata["Unicode"] = {}; + font.metadata["Unicode"].widths = metrics.widths; + font.metadata["Unicode"].kerning = metrics.kerning; + } + + var encodingBlock = encodings["Unicode"][font.postScriptName]; + if (encodingBlock) { + font.metadata["Unicode"].encoding = encodingBlock; + font.encoding = encodingBlock.codePages[0]; + } + } + ]); // end of adding event handler +})(jsPDF.API); + +/** + * @license + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * @name ttfsupport + * @module + */ +(function(jsPDF) { + + var binaryStringToUint8Array = function(binary_string) { + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }; + + var addFont = function(font, file) { + // eslint-disable-next-line no-control-regex + if (/^\x00\x01\x00\x00/.test(file)) { + file = binaryStringToUint8Array(file); + } else { + file = binaryStringToUint8Array(atob(file)); + } + font.metadata = jsPDF.API.TTFFont.open(file); + font.metadata.Unicode = font.metadata.Unicode || { + encoding: {}, + kerning: {}, + widths: [] + }; + font.metadata.glyIdsUsed = [0]; + }; + + jsPDF.API.events.push([ + "addFont", + function(data) { + var file = undefined; + var font = data.font; + var instance = data.instance; + if (font.isStandardFont) { + return; + } + if (typeof instance !== "undefined") { + if (instance.existsFileInVFS(font.postScriptName) === false) { + file = instance.loadFile(font.postScriptName); + } else { + file = instance.getFileFromVFS(font.postScriptName); + } + if (typeof file !== "string") { + throw new Error( + "Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('" + + font.postScriptName + + "')." + ); + } + addFont(font, file); + } else { + throw new Error( + "Font does not exist in vFS, import fonts or remove declaration doc.addFont('" + + font.postScriptName + + "')." + ); + } + } + ]); // end of adding event handler +})(jsPDF); + +/** + * @license + * ==================================================================== + * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * jsPDF total_pages plugin + * @name total_pages + * @module + */ +(function(jsPDFAPI) { + /** + * @name putTotalPages + * @function + * @param {string} pageExpression Regular Expression + * @returns {jsPDF} jsPDF-instance + */ + + jsPDFAPI.putTotalPages = function(pageExpression) { + + var replaceExpression; + var totalNumberOfPages = 0; + if (parseInt(this.internal.getFont().id.substr(1), 10) < 15) { + replaceExpression = new RegExp(pageExpression, "g"); + totalNumberOfPages = this.internal.getNumberOfPages(); + } else { + replaceExpression = new RegExp( + this.pdfEscape16(pageExpression, this.internal.getFont()), + "g" + ); + totalNumberOfPages = this.pdfEscape16( + this.internal.getNumberOfPages() + "", + this.internal.getFont() + ); + } + + for (var n = 1; n <= this.internal.getNumberOfPages(); n++) { + for (var i = 0; i < this.internal.pages[n].length; i++) { + this.internal.pages[n][i] = this.internal.pages[n][i].replace( + replaceExpression, + totalNumberOfPages + ); + } + } + + return this; + }; +})(jsPDF.API); + +/** + * @license + * jsPDF viewerPreferences Plugin + * @author Aras Abbasi (github.com/arasabbasi) + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * Adds the ability to set ViewerPreferences and by thus + * controlling the way the document is to be presented on the + * screen or in print. + * @name viewerpreferences + * @module + */ +(function(jsPDFAPI) { + /** + * Set the ViewerPreferences of the generated PDF + * + * @name viewerPreferences + * @function + * @public + * @param {Object} options Array with the ViewerPreferences
    + * Example: doc.viewerPreferences({"FitWindow":true});
    + *
    + * You can set following preferences:
    + *
    + * HideToolbar (boolean)
    + * Default value: false
    + *
    + * HideMenubar (boolean)
    + * Default value: false.
    + *
    + * HideWindowUI (boolean)
    + * Default value: false.
    + *
    + * FitWindow (boolean)
    + * Default value: false.
    + *
    + * CenterWindow (boolean)
    + * Default value: false
    + *
    + * DisplayDocTitle (boolean)
    + * Default value: false.
    + *
    + * NonFullScreenPageMode (string)
    + * Possible values: UseNone, UseOutlines, UseThumbs, UseOC
    + * Default value: UseNone
    + *
    + * Direction (string)
    + * Possible values: L2R, R2L
    + * Default value: L2R.
    + *
    + * ViewArea (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox.
    + *
    + * ViewClip (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox
    + *
    + * PrintArea (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox
    + *
    + * PrintClip (string)
    + * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
    + * Default value: CropBox.
    + *
    + * PrintScaling (string)
    + * Possible values: AppDefault, None
    + * Default value: AppDefault.
    + *
    + * Duplex (string)
    + * Possible values: Simplex, DuplexFlipLongEdge, DuplexFlipShortEdge + * Default value: none
    + *
    + * PickTrayByPDFSize (boolean)
    + * Default value: false
    + *
    + * PrintPageRange (Array)
    + * Example: [[1,5], [7,9]]
    + * Default value: as defined by PDF viewer application
    + *
    + * NumCopies (Number)
    + * Possible values: 1, 2, 3, 4, 5
    + * Default value: 1
    + *
    + * For more information see the PDF Reference, sixth edition on Page 577 + * @param {boolean} doReset True to reset the settings + * @function + * @returns jsPDF jsPDF-instance + * @example + * var doc = new jsPDF() + * doc.text('This is a test', 10, 10) + * doc.viewerPreferences({'FitWindow': true}, true) + * doc.save("viewerPreferences.pdf") + * + * // Example printing 10 copies, using cropbox, and hiding UI. + * doc.viewerPreferences({ + * 'HideWindowUI': true, + * 'PrintArea': 'CropBox', + * 'NumCopies': 10 + * }) + */ + jsPDFAPI.viewerPreferences = function(options, doReset) { + options = options || {}; + doReset = doReset || false; + + var configuration; + var configurationTemplate = { + HideToolbar: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + HideMenubar: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + HideWindowUI: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + FitWindow: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + CenterWindow: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.3 + }, + DisplayDocTitle: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.4 + }, + NonFullScreenPageMode: { + defaultValue: "UseNone", + value: "UseNone", + type: "name", + explicitSet: false, + valueSet: ["UseNone", "UseOutlines", "UseThumbs", "UseOC"], + pdfVersion: 1.3 + }, + Direction: { + defaultValue: "L2R", + value: "L2R", + type: "name", + explicitSet: false, + valueSet: ["L2R", "R2L"], + pdfVersion: 1.3 + }, + ViewArea: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + ViewClip: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintArea: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintClip: { + defaultValue: "CropBox", + value: "CropBox", + type: "name", + explicitSet: false, + valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], + pdfVersion: 1.4 + }, + PrintScaling: { + defaultValue: "AppDefault", + value: "AppDefault", + type: "name", + explicitSet: false, + valueSet: ["AppDefault", "None"], + pdfVersion: 1.6 + }, + Duplex: { + defaultValue: "", + value: "none", + type: "name", + explicitSet: false, + valueSet: [ + "Simplex", + "DuplexFlipShortEdge", + "DuplexFlipLongEdge", + "none" + ], + pdfVersion: 1.7 + }, + PickTrayByPDFSize: { + defaultValue: false, + value: false, + type: "boolean", + explicitSet: false, + valueSet: [true, false], + pdfVersion: 1.7 + }, + PrintPageRange: { + defaultValue: "", + value: "", + type: "array", + explicitSet: false, + valueSet: null, + pdfVersion: 1.7 + }, + NumCopies: { + defaultValue: 1, + value: 1, + type: "integer", + explicitSet: false, + valueSet: null, + pdfVersion: 1.7 + } + }; + + var configurationKeys = Object.keys(configurationTemplate); + + var rangeArray = []; + var i = 0; + var j = 0; + var k = 0; + var isValid; + + var method; + var value; + + function arrayContainsElement(array, element) { + var iterator; + var result = false; + + for (iterator = 0; iterator < array.length; iterator += 1) { + if (array[iterator] === element) { + result = true; + } + } + return result; + } + + if (this.internal.viewerpreferences === undefined) { + this.internal.viewerpreferences = {}; + this.internal.viewerpreferences.configuration = JSON.parse( + JSON.stringify(configurationTemplate) + ); + this.internal.viewerpreferences.isSubscribed = false; + } + configuration = this.internal.viewerpreferences.configuration; + + if (options === "reset" || doReset === true) { + var len = configurationKeys.length; + + for (k = 0; k < len; k += 1) { + configuration[configurationKeys[k]].value = + configuration[configurationKeys[k]].defaultValue; + configuration[configurationKeys[k]].explicitSet = false; + } + } + + if (typeof options === "object") { + for (method in options) { + value = options[method]; + if ( + arrayContainsElement(configurationKeys, method) && + value !== undefined + ) { + if ( + configuration[method].type === "boolean" && + typeof value === "boolean" + ) { + configuration[method].value = value; + } else if ( + configuration[method].type === "name" && + arrayContainsElement(configuration[method].valueSet, value) + ) { + configuration[method].value = value; + } else if ( + configuration[method].type === "integer" && + Number.isInteger(value) + ) { + configuration[method].value = value; + } else if (configuration[method].type === "array") { + for (i = 0; i < value.length; i += 1) { + isValid = true; + if (value[i].length === 1 && typeof value[i][0] === "number") { + rangeArray.push(String(value[i] - 1)); + } else if (value[i].length > 1) { + for (j = 0; j < value[i].length; j += 1) { + if (typeof value[i][j] !== "number") { + isValid = false; + } + } + if (isValid === true) { + rangeArray.push([value[i][0] - 1, value[i][1] - 1].join(" ")); + } + } + } + configuration[method].value = "[" + rangeArray.join(" ") + "]"; + } else { + configuration[method].value = configuration[method].defaultValue; + } + + configuration[method].explicitSet = true; + } + } + } + + if (this.internal.viewerpreferences.isSubscribed === false) { + this.internal.events.subscribe("putCatalog", function() { + var pdfDict = []; + var vPref; + for (vPref in configuration) { + if (configuration[vPref].explicitSet === true) { + if (configuration[vPref].type === "name") { + pdfDict.push("/" + vPref + " /" + configuration[vPref].value); + } else { + pdfDict.push("/" + vPref + " " + configuration[vPref].value); + } + } + } + if (pdfDict.length !== 0) { + this.internal.write( + "/ViewerPreferences\n<<\n" + pdfDict.join("\n") + "\n>>" + ); + } + }); + this.internal.viewerpreferences.isSubscribed = true; + } + + this.internal.viewerpreferences.configuration = configuration; + return this; + }; +})(jsPDF.API); + +/** ==================================================================== + * @license + * jsPDF XMP metadata plugin + * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * ==================================================================== + */ + +/** + * @name xmp_metadata + * @module + */ +(function(jsPDFAPI) { + + var postPutResources = function() { + var xmpmeta_beginning = ''; + var rdf_beginning = + ''; + var rdf_ending = ""; + var xmpmeta_ending = ""; + var utf8_xmpmeta_beginning = unescape( + encodeURIComponent(xmpmeta_beginning) + ); + var utf8_rdf_beginning = unescape(encodeURIComponent(rdf_beginning)); + var utf8_metadata = unescape( + encodeURIComponent(this.internal.__metadata__.metadata) + ); + var utf8_rdf_ending = unescape(encodeURIComponent(rdf_ending)); + var utf8_xmpmeta_ending = unescape(encodeURIComponent(xmpmeta_ending)); + + var total_len = + utf8_rdf_beginning.length + + utf8_metadata.length + + utf8_rdf_ending.length + + utf8_xmpmeta_beginning.length + + utf8_xmpmeta_ending.length; + + this.internal.__metadata__.metadata_object_number = this.internal.newObject(); + this.internal.write( + "<< /Type /Metadata /Subtype /XML /Length " + total_len + " >>" + ); + this.internal.write("stream"); + this.internal.write( + utf8_xmpmeta_beginning + + utf8_rdf_beginning + + utf8_metadata + + utf8_rdf_ending + + utf8_xmpmeta_ending + ); + this.internal.write("endstream"); + this.internal.write("endobj"); + }; + + var putCatalog = function() { + if (this.internal.__metadata__.metadata_object_number) { + this.internal.write( + "/Metadata " + + this.internal.__metadata__.metadata_object_number + + " 0 R" + ); + } + }; + + /** + * Adds XMP formatted metadata to PDF + * + * @name addMetadata + * @function + * @param {String} metadata The actual metadata to be added. The metadata shall be stored as XMP simple value. Note that if the metadata string contains XML markup characters "<", ">" or "&", those characters should be written using XML entities. + * @param {String} namespaceuri Sets the namespace URI for the metadata. Last character should be slash or hash. + * @returns {jsPDF} jsPDF-instance + */ + jsPDFAPI.addMetadata = function(metadata, namespaceuri) { + if (typeof this.internal.__metadata__ === "undefined") { + this.internal.__metadata__ = { + metadata: metadata, + namespaceuri: namespaceuri || "http://jspdf.default.namespaceuri/" + }; + this.internal.events.subscribe("putCatalog", putCatalog); + + this.internal.events.subscribe("postPutResources", postPutResources); + } + return this; + }; +})(jsPDF.API); + +/** + * @name utf8 + * @module + */ +(function(jsPDF) { + var jsPDFAPI = jsPDF.API; + + /***************************************************************************************************/ + /* function : pdfEscape16 */ + /* comment : The character id of a 2-byte string is converted to a hexadecimal number by obtaining */ + /* the corresponding glyph id and width, and then adding padding to the string. */ + /***************************************************************************************************/ + var pdfEscape16 = (jsPDFAPI.pdfEscape16 = function(text, font) { + var widths = font.metadata.Unicode.widths; + var padz = ["", "0", "00", "000", "0000"]; + var ar = [""]; + for (var i = 0, l = text.length, t; i < l; ++i) { + t = font.metadata.characterToGlyph(text.charCodeAt(i)); + font.metadata.glyIdsUsed.push(t); + font.metadata.toUnicode[t] = text.charCodeAt(i); + if (widths.indexOf(t) == -1) { + widths.push(t); + widths.push([parseInt(font.metadata.widthOfGlyph(t), 10)]); + } + if (t == "0") { + //Spaces are not allowed in cmap. + return ar.join(""); + } else { + t = t.toString(16); + ar.push(padz[4 - t.length], t); + } + } + return ar.join(""); + }); + + var toUnicodeCmap = function(map) { + var code, codes, range, unicode, unicodeMap, _i, _len; + unicodeMap = + "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000>\nendcodespacerange"; + codes = Object.keys(map).sort(function(a, b) { + return a - b; + }); + + range = []; + for (_i = 0, _len = codes.length; _i < _len; _i++) { + code = codes[_i]; + if (range.length >= 100) { + unicodeMap += + "\n" + + range.length + + " beginbfchar\n" + + range.join("\n") + + "\nendbfchar"; + range = []; + } + + if ( + map[code] !== undefined && + map[code] !== null && + typeof map[code].toString === "function" + ) { + unicode = ("0000" + map[code].toString(16)).slice(-4); + code = ("0000" + (+code).toString(16)).slice(-4); + range.push("<" + code + "><" + unicode + ">"); + } + } + + if (range.length) { + unicodeMap += + "\n" + + range.length + + " beginbfchar\n" + + range.join("\n") + + "\nendbfchar\n"; + } + unicodeMap += + "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"; + return unicodeMap; + }; + + var identityHFunction = function(options) { + var font = options.font; + var out = options.out; + var newObject = options.newObject; + var putStream = options.putStream; + + if ( + font.metadata instanceof jsPDF.API.TTFFont && + font.encoding === "Identity-H" + ) { + //Tag with Identity-H + var widths = font.metadata.Unicode.widths; + var data = font.metadata.subset.encode(font.metadata.glyIdsUsed, 1); + var pdfOutput = data; + var pdfOutput2 = ""; + for (var i = 0; i < pdfOutput.length; i++) { + pdfOutput2 += String.fromCharCode(pdfOutput[i]); + } + var fontTable = newObject(); + putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); + out("endobj"); + + var cmap = newObject(); + var cmapData = toUnicodeCmap(font.metadata.toUnicode); + putStream({ data: cmapData, addLength1: true, objectId: cmap }); + out("endobj"); + + var fontDescriptor = newObject(); + out("<<"); + out("/Type /FontDescriptor"); + out("/FontName /" + toPDFName(font.fontName)); + out("/FontFile2 " + fontTable + " 0 R"); + out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); + out("/Flags " + font.metadata.flags); + out("/StemV " + font.metadata.stemV); + out("/ItalicAngle " + font.metadata.italicAngle); + out("/Ascent " + font.metadata.ascender); + out("/Descent " + font.metadata.decender); + out("/CapHeight " + font.metadata.capHeight); + out(">>"); + out("endobj"); + + var DescendantFont = newObject(); + out("<<"); + out("/Type /Font"); + out("/BaseFont /" + toPDFName(font.fontName)); + out("/FontDescriptor " + fontDescriptor + " 0 R"); + out("/W " + jsPDF.API.PDFObject.convert(widths)); + out("/CIDToGIDMap /Identity"); + out("/DW 1000"); + out("/Subtype /CIDFontType2"); + out("/CIDSystemInfo"); + out("<<"); + out("/Supplement 0"); + out("/Registry (Adobe)"); + out("/Ordering (" + font.encoding + ")"); + out(">>"); + out(">>"); + out("endobj"); + + font.objectNumber = newObject(); + out("<<"); + out("/Type /Font"); + out("/Subtype /Type0"); + out("/ToUnicode " + cmap + " 0 R"); + out("/BaseFont /" + toPDFName(font.fontName)); + out("/Encoding /" + font.encoding); + out("/DescendantFonts [" + DescendantFont + " 0 R]"); + out(">>"); + out("endobj"); + + font.isAlreadyPutted = true; + } + }; + + jsPDFAPI.events.push([ + "putFont", + function(args) { + identityHFunction(args); + } + ]); + + var winAnsiEncodingFunction = function(options) { + var font = options.font; + var out = options.out; + var newObject = options.newObject; + var putStream = options.putStream; + + if ( + font.metadata instanceof jsPDF.API.TTFFont && + font.encoding === "WinAnsiEncoding" + ) { + //Tag with WinAnsi encoding + var data = font.metadata.rawData; + var pdfOutput = data; + var pdfOutput2 = ""; + for (var i = 0; i < pdfOutput.length; i++) { + pdfOutput2 += String.fromCharCode(pdfOutput[i]); + } + var fontTable = newObject(); + putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); + out("endobj"); + + var cmap = newObject(); + var cmapData = toUnicodeCmap(font.metadata.toUnicode); + putStream({ data: cmapData, addLength1: true, objectId: cmap }); + out("endobj"); + + var fontDescriptor = newObject(); + out("<<"); + out("/Descent " + font.metadata.decender); + out("/CapHeight " + font.metadata.capHeight); + out("/StemV " + font.metadata.stemV); + out("/Type /FontDescriptor"); + out("/FontFile2 " + fontTable + " 0 R"); + out("/Flags 96"); + out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); + out("/FontName /" + toPDFName(font.fontName)); + out("/ItalicAngle " + font.metadata.italicAngle); + out("/Ascent " + font.metadata.ascender); + out(">>"); + out("endobj"); + font.objectNumber = newObject(); + for (var j = 0; j < font.metadata.hmtx.widths.length; j++) { + font.metadata.hmtx.widths[j] = parseInt( + font.metadata.hmtx.widths[j] * (1000 / font.metadata.head.unitsPerEm) + ); //Change the width of Em units to Point units. + } + out( + "<>" + ); + out("endobj"); + font.isAlreadyPutted = true; + } + }; + + jsPDFAPI.events.push([ + "putFont", + function(args) { + winAnsiEncodingFunction(args); + } + ]); + + var utf8TextFunction = function(args) { + var text = args.text || ""; + var x = args.x; + var y = args.y; + var options = args.options || {}; + var mutex = args.mutex || {}; + + var pdfEscape = mutex.pdfEscape; + var activeFontKey = mutex.activeFontKey; + var fonts = mutex.fonts; + var key = activeFontKey; + + var str = "", + s = 0, + cmapConfirm; + var strText = ""; + var encoding = fonts[key].encoding; + + if (fonts[key].encoding !== "Identity-H") { + return { + text: text, + x: x, + y: y, + options: options, + mutex: mutex + }; + } + strText = text; + + key = activeFontKey; + if (Array.isArray(text)) { + strText = text[0]; + } + for (s = 0; s < strText.length; s += 1) { + if (fonts[key].metadata.hasOwnProperty("cmap")) { + cmapConfirm = + fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; + /* + if (Object.prototype.toString.call(text) === '[object Array]') { + var i = 0; + // for (i = 0; i < text.length; i += 1) { + if (Object.prototype.toString.call(text[s]) === '[object Array]') { + cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s][0].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id + } else { + + } + //} + + } else { + cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id + }*/ + } + if (!cmapConfirm) { + if ( + strText[s].charCodeAt(0) < 256 && + fonts[key].metadata.hasOwnProperty("Unicode") + ) { + str += strText[s]; + } else { + str += ""; + } + } else { + str += strText[s]; + } + } + var result = ""; + if (parseInt(key.slice(1)) < 14 || encoding === "WinAnsiEncoding") { + //For the default 13 font + result = pdfEscape(str, key) + .split("") + .map(function(cv) { + return cv.charCodeAt(0).toString(16); + }) + .join(""); + } else if (encoding === "Identity-H") { + result = pdfEscape16(str, fonts[key]); + } + mutex.isHex = true; + + return { + text: result, + x: x, + y: y, + options: options, + mutex: mutex + }; + }; + + var utf8EscapeFunction = function(parms) { + var text = parms.text || "", + x = parms.x, + y = parms.y, + options = parms.options, + mutex = parms.mutex; + var tmpText = []; + var args = { + text: text, + x: x, + y: y, + options: options, + mutex: mutex + }; + + if (Array.isArray(text)) { + var i = 0; + for (i = 0; i < text.length; i += 1) { + if (Array.isArray(text[i])) { + if (text[i].length === 3) { + tmpText.push([ + utf8TextFunction(Object.assign({}, args, { text: text[i][0] })) + .text, + text[i][1], + text[i][2] + ]); + } else { + tmpText.push( + utf8TextFunction(Object.assign({}, args, { text: text[i] })).text + ); + } + } else { + tmpText.push( + utf8TextFunction(Object.assign({}, args, { text: text[i] })).text + ); + } + } + parms.text = tmpText; + } else { + parms.text = utf8TextFunction( + Object.assign({}, args, { text: text }) + ).text; + } + }; + + jsPDFAPI.events.push(["postProcessText", utf8EscapeFunction]); +})(jsPDF); + +/** + * @license + * jsPDF virtual FileSystem functionality + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + +/** + * Use the vFS to handle files + * + * @name vFS + * @module + */ +(function(jsPDFAPI) { + + var _initializeVFS = function() { + if (typeof this.internal.vFS === "undefined") { + this.internal.vFS = {}; + } + return true; + }; + + /** + * Check if the file exists in the vFS + * + * @name existsFileInVFS + * @function + * @param {string} Possible filename in the vFS. + * @returns {boolean} + * @example + * doc.existsFileInVFS("someFile.txt"); + */ + jsPDFAPI.existsFileInVFS = function(filename) { + _initializeVFS.call(this); + return typeof this.internal.vFS[filename] !== "undefined"; + }; + + /** + * Add a file to the vFS + * + * @name addFileToVFS + * @function + * @param {string} filename The name of the file which should be added. + * @param {string} filecontent The content of the file. + * @returns {jsPDF} + * @example + * doc.addFileToVFS("someFile.txt", "BADFACE1"); + */ + jsPDFAPI.addFileToVFS = function(filename, filecontent) { + _initializeVFS.call(this); + this.internal.vFS[filename] = filecontent; + return this; + }; + + /** + * Get the file from the vFS + * + * @name getFileFromVFS + * @function + * @param {string} The name of the file which gets requested. + * @returns {string} + * @example + * doc.getFileFromVFS("someFile.txt"); + */ + jsPDFAPI.getFileFromVFS = function(filename) { + _initializeVFS.call(this); + + if (typeof this.internal.vFS[filename] !== "undefined") { + return this.internal.vFS[filename]; + } + return null; + }; +})(jsPDF.API); + +/** + * @license + * Unicode Bidi Engine based on the work of Alex Shensis (@asthensis) + * MIT License + */ + +(function(jsPDF) { + /** + * Table of Unicode types. + * + * Generated by: + * + * var bidi = require("./bidi/index"); + * var bidi_accumulate = bidi.slice(0, 256).concat(bidi.slice(0x0500, 0x0500 + 256 * 3)). + * concat(bidi.slice(0x2000, 0x2000 + 256)).concat(bidi.slice(0xFB00, 0xFB00 + 256)). + * concat(bidi.slice(0xFE00, 0xFE00 + 2 * 256)); + * + * for( var i = 0; i < bidi_accumulate.length; i++) { + * if(bidi_accumulate[i] === undefined || bidi_accumulate[i] === 'ON') + * bidi_accumulate[i] = 'N'; //mark as neutral to conserve space and substitute undefined + * } + * var bidiAccumulateStr = 'return [ "' + bidi_accumulate.toString().replace(/,/g, '", "') + '" ];'; + * require("fs").writeFile('unicode-types.js', bidiAccumulateStr); + * + * Based on: + * https://github.com/mathiasbynens/unicode-8.0.0 + */ + var bidiUnicodeTypes = [ + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "S", + "B", + "S", + "WS", + "B", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "B", + "B", + "B", + "S", + "WS", + "N", + "N", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "ES", + "CS", + "ES", + "CS", + "CS", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "B", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "CS", + "N", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "L", + "N", + "N", + "BN", + "N", + "N", + "ET", + "ET", + "EN", + "EN", + "N", + "L", + "N", + "N", + "N", + "EN", + "L", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "L", + "N", + "N", + "N", + "N", + "N", + "ET", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "R", + "NSM", + "R", + "NSM", + "NSM", + "R", + "NSM", + "NSM", + "R", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "N", + "N", + "AL", + "ET", + "ET", + "AL", + "CS", + "AL", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "AN", + "ET", + "AN", + "AN", + "AL", + "AL", + "AL", + "NSM", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AN", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "NSM", + "NSM", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "AL", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "AL", + "AL", + "NSM", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "AL", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "R", + "R", + "N", + "N", + "N", + "N", + "R", + "N", + "N", + "N", + "N", + "N", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "WS", + "BN", + "BN", + "BN", + "L", + "R", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "WS", + "B", + "LRE", + "RLE", + "PDF", + "LRO", + "RLO", + "CS", + "ET", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "WS", + "BN", + "BN", + "BN", + "BN", + "BN", + "N", + "LRI", + "RLI", + "FSI", + "PDI", + "BN", + "BN", + "BN", + "BN", + "BN", + "BN", + "EN", + "L", + "N", + "N", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "ES", + "ES", + "N", + "N", + "N", + "L", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "ES", + "ES", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "R", + "NSM", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "ES", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "N", + "R", + "R", + "R", + "R", + "R", + "N", + "R", + "N", + "R", + "R", + "N", + "R", + "R", + "N", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "R", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "NSM", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "CS", + "N", + "CS", + "N", + "N", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "ET", + "N", + "N", + "ES", + "ES", + "N", + "N", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "AL", + "N", + "N", + "BN", + "N", + "N", + "N", + "ET", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "ES", + "CS", + "ES", + "CS", + "CS", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "EN", + "CS", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "L", + "L", + "L", + "N", + "N", + "L", + "L", + "L", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "ET", + "ET", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N", + "N" + ]; + + /** + * Unicode Bidi algorithm compliant Bidi engine. + * For reference see http://unicode.org/reports/tr9/ + */ + + /** + * constructor ( options ) + * + * Initializes Bidi engine + * + * @param {Object} See 'setOptions' below for detailed description. + * options are cashed between invocation of 'doBidiReorder' method + * + * sample usage pattern of BidiEngine: + * var opt = { + * isInputVisual: true, + * isInputRtl: false, + * isOutputVisual: false, + * isOutputRtl: false, + * isSymmetricSwapping: true + * } + * var sourceToTarget = [], levels = []; + * var bidiEng = Globalize.bidiEngine(opt); + * var src = "text string to be reordered"; + * var ret = bidiEng.doBidiReorder(src, sourceToTarget, levels); + */ + + jsPDF.__bidiEngine__ = jsPDF.prototype.__bidiEngine__ = function(options) { + var _UNICODE_TYPES = _bidiUnicodeTypes; + + var _STATE_TABLE_LTR = [ + [0, 3, 0, 1, 0, 0, 0], + [0, 3, 0, 1, 2, 2, 0], + [0, 3, 0, 0x11, 2, 0, 1], + [0, 3, 5, 5, 4, 1, 0], + [0, 3, 0x15, 0x15, 4, 0, 1], + [0, 3, 5, 5, 4, 2, 0] + ]; + + var _STATE_TABLE_RTL = [ + [2, 0, 1, 1, 0, 1, 0], + [2, 0, 1, 1, 0, 2, 0], + [2, 0, 2, 1, 3, 2, 0], + [2, 0, 2, 0x21, 3, 1, 1] + ]; + + var _TYPE_NAMES_MAP = { L: 0, R: 1, EN: 2, AN: 3, N: 4, B: 5, S: 6 }; + + var _UNICODE_RANGES_MAP = { + 0: 0, + 5: 1, + 6: 2, + 7: 3, + 0x20: 4, + 0xfb: 5, + 0xfe: 6, + 0xff: 7 + }; + + var _SWAP_TABLE = [ + "\u0028", + "\u0029", + "\u0028", + "\u003C", + "\u003E", + "\u003C", + "\u005B", + "\u005D", + "\u005B", + "\u007B", + "\u007D", + "\u007B", + "\u00AB", + "\u00BB", + "\u00AB", + "\u2039", + "\u203A", + "\u2039", + "\u2045", + "\u2046", + "\u2045", + "\u207D", + "\u207E", + "\u207D", + "\u208D", + "\u208E", + "\u208D", + "\u2264", + "\u2265", + "\u2264", + "\u2329", + "\u232A", + "\u2329", + "\uFE59", + "\uFE5A", + "\uFE59", + "\uFE5B", + "\uFE5C", + "\uFE5B", + "\uFE5D", + "\uFE5E", + "\uFE5D", + "\uFE64", + "\uFE65", + "\uFE64" + ]; + + var _LTR_RANGES_REG_EXPR = new RegExp( + /^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/ + ); + + var _lastArabic = false, + _hasUbatB, + _hasUbatS, + DIR_LTR = 0, + DIR_RTL = 1, + _isInVisual, + _isInRtl, + _isOutVisual, + _isOutRtl, + _isSymmetricSwapping, + _dir = DIR_LTR; + + this.__bidiEngine__ = {}; + + var _init = function(text, sourceToTargetMap) { + if (sourceToTargetMap) { + for (var i = 0; i < text.length; i++) { + sourceToTargetMap[i] = i; + } + } + if (_isInRtl === undefined) { + _isInRtl = _isContextualDirRtl(text); + } + if (_isOutRtl === undefined) { + _isOutRtl = _isContextualDirRtl(text); + } + }; + + // for reference see 3.2 in http://unicode.org/reports/tr9/ + // + var _getCharType = function(ch) { + var charCode = ch.charCodeAt(), + range = charCode >> 8, + rangeIdx = _UNICODE_RANGES_MAP[range]; + + if (rangeIdx !== undefined) { + return _UNICODE_TYPES[rangeIdx * 256 + (charCode & 0xff)]; + } else if (range === 0xfc || range === 0xfd) { + return "AL"; + } else if (_LTR_RANGES_REG_EXPR.test(range)) { + //unlikely case + return "L"; + } else if (range === 8) { + // even less likely + return "R"; + } + return "N"; //undefined type, mark as neutral + }; + + var _isContextualDirRtl = function(text) { + for (var i = 0, charType; i < text.length; i++) { + charType = _getCharType(text.charAt(i)); + if (charType === "L") { + return false; + } else if (charType === "R") { + return true; + } + } + return false; + }; + + // for reference see 3.3.4 & 3.3.5 in http://unicode.org/reports/tr9/ + // + var _resolveCharType = function(chars, types, resolvedTypes, index) { + var cType = types[index], + wType, + nType, + i, + len; + switch (cType) { + case "L": + case "R": + _lastArabic = false; + break; + case "N": + case "AN": + break; + + case "EN": + if (_lastArabic) { + cType = "AN"; + } + break; + + case "AL": + _lastArabic = true; + cType = "R"; + break; + + case "WS": + cType = "N"; + break; + + case "CS": + if ( + index < 1 || + index + 1 >= types.length || + ((wType = resolvedTypes[index - 1]) !== "EN" && wType !== "AN") || + ((nType = types[index + 1]) !== "EN" && nType !== "AN") + ) { + cType = "N"; + } else if (_lastArabic) { + nType = "AN"; + } + cType = nType === wType ? nType : "N"; + break; + + case "ES": + wType = index > 0 ? resolvedTypes[index - 1] : "B"; + cType = + wType === "EN" && + index + 1 < types.length && + types[index + 1] === "EN" + ? "EN" + : "N"; + break; + + case "ET": + if (index > 0 && resolvedTypes[index - 1] === "EN") { + cType = "EN"; + break; + } else if (_lastArabic) { + cType = "N"; + break; + } + i = index + 1; + len = types.length; + while (i < len && types[i] === "ET") { + i++; + } + if (i < len && types[i] === "EN") { + cType = "EN"; + } else { + cType = "N"; + } + break; + + case "NSM": + if (_isInVisual && !_isInRtl) { + //V->L + len = types.length; + i = index + 1; + while (i < len && types[i] === "NSM") { + i++; + } + if (i < len) { + var c = chars[index]; + var rtlCandidate = (c >= 0x0591 && c <= 0x08ff) || c === 0xfb1e; + wType = types[i]; + if (rtlCandidate && (wType === "R" || wType === "AL")) { + cType = "R"; + break; + } + } + } + if (index < 1 || (wType = types[index - 1]) === "B") { + cType = "N"; + } else { + cType = resolvedTypes[index - 1]; + } + break; + + case "B": + _lastArabic = false; + _hasUbatB = true; + cType = _dir; + break; + + case "S": + _hasUbatS = true; + cType = "N"; + break; + + case "LRE": + case "RLE": + case "LRO": + case "RLO": + case "PDF": + _lastArabic = false; + break; + case "BN": + cType = "N"; + break; + } + return cType; + }; + + var _handleUbatS = function(types, levels, length) { + for (var i = 0; i < length; i++) { + if (types[i] === "S") { + levels[i] = _dir; + for (var j = i - 1; j >= 0; j--) { + if (types[j] === "WS") { + levels[j] = _dir; + } else { + break; + } + } + } + } + }; + + var _invertString = function(text, sourceToTargetMap, levels) { + var charArray = text.split(""); + if (levels) { + _computeLevels(charArray, levels, { hiLevel: _dir }); + } + charArray.reverse(); + sourceToTargetMap && sourceToTargetMap.reverse(); + return charArray.join(""); + }; + + // For reference see 3.3 in http://unicode.org/reports/tr9/ + // + var _computeLevels = function(chars, levels, params) { + var action, + condition, + i, + index, + newLevel, + prevState, + condPos = -1, + len = chars.length, + newState = 0, + resolvedTypes = [], + stateTable = _dir ? _STATE_TABLE_RTL : _STATE_TABLE_LTR, + types = []; + + _lastArabic = false; + _hasUbatB = false; + _hasUbatS = false; + for (i = 0; i < len; i++) { + types[i] = _getCharType(chars[i]); + } + for (index = 0; index < len; index++) { + prevState = newState; + resolvedTypes[index] = _resolveCharType( + chars, + types, + resolvedTypes, + index + ); + newState = stateTable[prevState][_TYPE_NAMES_MAP[resolvedTypes[index]]]; + action = newState & 0xf0; + newState &= 0x0f; + levels[index] = newLevel = stateTable[newState][5]; + if (action > 0) { + if (action === 0x10) { + for (i = condPos; i < index; i++) { + levels[i] = 1; + } + condPos = -1; + } else { + condPos = -1; + } + } + condition = stateTable[newState][6]; + if (condition) { + if (condPos === -1) { + condPos = index; + } + } else { + if (condPos > -1) { + for (i = condPos; i < index; i++) { + levels[i] = newLevel; + } + condPos = -1; + } + } + if (types[index] === "B") { + levels[index] = 0; + } + params.hiLevel |= newLevel; + } + if (_hasUbatS) { + _handleUbatS(types, levels, len); + } + }; + + // for reference see 3.4 in http://unicode.org/reports/tr9/ + // + var _invertByLevel = function( + level, + charArray, + sourceToTargetMap, + levels, + params + ) { + if (params.hiLevel < level) { + return; + } + if (level === 1 && _dir === DIR_RTL && !_hasUbatB) { + charArray.reverse(); + sourceToTargetMap && sourceToTargetMap.reverse(); + return; + } + var ch, + high, + end, + low, + len = charArray.length, + start = 0; + + while (start < len) { + if (levels[start] >= level) { + end = start + 1; + while (end < len && levels[end] >= level) { + end++; + } + for (low = start, high = end - 1; low < high; low++, high--) { + ch = charArray[low]; + charArray[low] = charArray[high]; + charArray[high] = ch; + if (sourceToTargetMap) { + ch = sourceToTargetMap[low]; + sourceToTargetMap[low] = sourceToTargetMap[high]; + sourceToTargetMap[high] = ch; + } + } + start = end; + } + start++; + } + }; + + // for reference see 7 & BD16 in http://unicode.org/reports/tr9/ + // + var _symmetricSwap = function(charArray, levels, params) { + if (params.hiLevel !== 0 && _isSymmetricSwapping) { + for (var i = 0, index; i < charArray.length; i++) { + if (levels[i] === 1) { + index = _SWAP_TABLE.indexOf(charArray[i]); + if (index >= 0) { + charArray[i] = _SWAP_TABLE[index + 1]; + } + } + } + } + }; + + var _reorder = function(text, sourceToTargetMap, levels) { + var charArray = text.split(""), + params = { hiLevel: _dir }; + + if (!levels) { + levels = []; + } + _computeLevels(charArray, levels, params); + _symmetricSwap(charArray, levels, params); + _invertByLevel(DIR_RTL + 1, charArray, sourceToTargetMap, levels, params); + _invertByLevel(DIR_RTL, charArray, sourceToTargetMap, levels, params); + return charArray.join(""); + }; + + // doBidiReorder( text, sourceToTargetMap, levels ) + // Performs Bidi reordering by implementing Unicode Bidi algorithm. + // Returns reordered string + // @text [String]: + // - input string to be reordered, this is input parameter + // $sourceToTargetMap [Array] (optional) + // - resultant mapping between input and output strings, this is output parameter + // $levels [Array] (optional) + // - array of calculated Bidi levels, , this is output parameter + this.__bidiEngine__.doBidiReorder = function( + text, + sourceToTargetMap, + levels + ) { + _init(text, sourceToTargetMap); + if (!_isInVisual && _isOutVisual && !_isOutRtl) { + // LLTR->VLTR, LRTL->VLTR + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else if (_isInVisual && _isOutVisual && _isInRtl ^ _isOutRtl) { + // VRTL->VLTR, VLTR->VRTL + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _invertString(text, sourceToTargetMap, levels); + } else if (!_isInVisual && _isOutVisual && _isOutRtl) { + // LLTR->VRTL, LRTL->VRTL + _dir = _isInRtl ? DIR_RTL : DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } else if (_isInVisual && !_isInRtl && !_isOutVisual && !_isOutRtl) { + // VLTR->LLTR + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else if (_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { + // VLTR->LRTL, VRTL->LLTR + text = _invertString(text, sourceToTargetMap); + if (_isInRtl) { + //LLTR -> VLTR + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + } else { + //LRTL -> VRTL + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } + } else if (_isInVisual && _isInRtl && !_isOutVisual && _isOutRtl) { + // VRTL->LRTL + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + } else if (!_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { + // LRTL->LLTR, LLTR->LRTL + var isSymmetricSwappingOrig = _isSymmetricSwapping; + if (_isInRtl) { + //LRTL->LLTR + _dir = DIR_RTL; + text = _reorder(text, sourceToTargetMap, levels); + _dir = DIR_LTR; + _isSymmetricSwapping = false; + text = _reorder(text, sourceToTargetMap, levels); + _isSymmetricSwapping = isSymmetricSwappingOrig; + } else { + //LLTR->LRTL + _dir = DIR_LTR; + text = _reorder(text, sourceToTargetMap, levels); + text = _invertString(text, sourceToTargetMap); + _dir = DIR_RTL; + _isSymmetricSwapping = false; + text = _reorder(text, sourceToTargetMap, levels); + _isSymmetricSwapping = isSymmetricSwappingOrig; + text = _invertString(text, sourceToTargetMap); + } + } + return text; + }; + + /** + * @name setOptions( options ) + * @function + * Sets options for Bidi conversion + * @param {Object}: + * - isInputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) + * - isInputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong character of input string) + * - isOutputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) + * - isOutputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong characterof input string) + * - isSymmetricSwapping {boolean} (defaults to false): allowed values true(needs symmetric swapping), false (no need in symmetric swapping), + */ + this.__bidiEngine__.setOptions = function(options) { + if (options) { + _isInVisual = options.isInputVisual; + _isOutVisual = options.isOutputVisual; + _isInRtl = options.isInputRtl; + _isOutRtl = options.isOutputRtl; + _isSymmetricSwapping = options.isSymmetricSwapping; + } + }; + + this.__bidiEngine__.setOptions(options); + return this.__bidiEngine__; + }; + + var _bidiUnicodeTypes = bidiUnicodeTypes; + + var bidiEngine = new jsPDF.__bidiEngine__({ isInputVisual: true }); + + var bidiEngineFunction = function(args) { + var text = args.text; + args.x; + args.y; + var options = args.options || {}; + args.mutex || {}; + options.lang; + var tmpText = []; + + options.isInputVisual = + typeof options.isInputVisual === "boolean" ? options.isInputVisual : true; + bidiEngine.setOptions(options); + + if (Object.prototype.toString.call(text) === "[object Array]") { + var i = 0; + tmpText = []; + for (i = 0; i < text.length; i += 1) { + if (Object.prototype.toString.call(text[i]) === "[object Array]") { + tmpText.push([ + bidiEngine.doBidiReorder(text[i][0]), + text[i][1], + text[i][2] + ]); + } else { + tmpText.push([bidiEngine.doBidiReorder(text[i])]); + } + } + args.text = tmpText; + } else { + args.text = bidiEngine.doBidiReorder(text); + } + bidiEngine.setOptions({ isInputVisual: true }); + }; + + jsPDF.API.events.push(["postProcessText", bidiEngineFunction]); +})(jsPDF); + +/* eslint-disable no-control-regex */ + +jsPDF.API.TTFFont = (function() { + /************************************************************************/ + /* function : open */ + /* comment : Decode the encoded ttf content and create a TTFFont object. */ + /************************************************************************/ + TTFFont.open = function(file) { + return new TTFFont(file); + }; + /***************************************************************/ + /* function : TTFFont gernerator */ + /* comment : Decode TTF contents are parsed, Data, */ + /* Subset object is created, and registerTTF function is called.*/ + /***************************************************************/ + function TTFFont(rawData) { + var data; + this.rawData = rawData; + data = this.contents = new Data(rawData); + this.contents.pos = 4; + if (data.readString(4) === "ttcf") { + throw new Error("TTCF not supported."); + } else { + data.pos = 0; + this.parse(); + this.subset = new Subset(this); + this.registerTTF(); + } + } + /********************************************************/ + /* function : parse */ + /* comment : TTF Parses the file contents by each table.*/ + /********************************************************/ + TTFFont.prototype.parse = function() { + this.directory = new Directory(this.contents); + this.head = new HeadTable(this); + this.name = new NameTable(this); + this.cmap = new CmapTable(this); + this.toUnicode = {}; + this.hhea = new HheaTable(this); + this.maxp = new MaxpTable(this); + this.hmtx = new HmtxTable(this); + this.post = new PostTable(this); + this.os2 = new OS2Table(this); + this.loca = new LocaTable(this); + this.glyf = new GlyfTable(this); + this.ascender = + (this.os2.exists && this.os2.ascender) || this.hhea.ascender; + this.decender = + (this.os2.exists && this.os2.decender) || this.hhea.decender; + this.lineGap = (this.os2.exists && this.os2.lineGap) || this.hhea.lineGap; + return (this.bbox = [ + this.head.xMin, + this.head.yMin, + this.head.xMax, + this.head.yMax + ]); + }; + /***************************************************************/ + /* function : registerTTF */ + /* comment : Get the value to assign pdf font descriptors. */ + /***************************************************************/ + TTFFont.prototype.registerTTF = function() { + var e, hi, low, raw, _ref; + this.scaleFactor = 1000.0 / this.head.unitsPerEm; + this.bbox = function() { + var _i, _len, _ref, _results; + _ref = this.bbox; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + e = _ref[_i]; + _results.push(Math.round(e * this.scaleFactor)); + } + return _results; + }.call(this); + this.stemV = 0; + if (this.post.exists) { + raw = this.post.italic_angle; + hi = raw >> 16; + low = raw & 0xff; + if ((hi & 0x8000) !== 0) { + hi = -((hi ^ 0xffff) + 1); + } + this.italicAngle = +("" + hi + "." + low); + } else { + this.italicAngle = 0; + } + this.ascender = Math.round(this.ascender * this.scaleFactor); + this.decender = Math.round(this.decender * this.scaleFactor); + this.lineGap = Math.round(this.lineGap * this.scaleFactor); + this.capHeight = (this.os2.exists && this.os2.capHeight) || this.ascender; + this.xHeight = (this.os2.exists && this.os2.xHeight) || 0; + this.familyClass = ((this.os2.exists && this.os2.familyClass) || 0) >> 8; + this.isSerif = + (_ref = this.familyClass) === 1 || + _ref === 2 || + _ref === 3 || + _ref === 4 || + _ref === 5 || + _ref === 7; + this.isScript = this.familyClass === 10; + this.flags = 0; + if (this.post.isFixedPitch) { + this.flags |= 1 << 0; + } + if (this.isSerif) { + this.flags |= 1 << 1; + } + if (this.isScript) { + this.flags |= 1 << 3; + } + if (this.italicAngle !== 0) { + this.flags |= 1 << 6; + } + this.flags |= 1 << 5; + if (!this.cmap.unicode) { + throw new Error("No unicode cmap for font"); + } + }; + TTFFont.prototype.characterToGlyph = function(character) { + var _ref; + return ( + ((_ref = this.cmap.unicode) != null ? _ref.codeMap[character] : void 0) || + 0 + ); + }; + TTFFont.prototype.widthOfGlyph = function(glyph) { + var scale; + scale = 1000.0 / this.head.unitsPerEm; + return this.hmtx.forGlyph(glyph).advance * scale; + }; + TTFFont.prototype.widthOfString = function(string, size, charSpace) { + var charCode, i, scale, width, _ref; + string = "" + string; + width = 0; + for ( + i = 0, _ref = string.length; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + charCode = string.charCodeAt(i); + width += + this.widthOfGlyph(this.characterToGlyph(charCode)) + + charSpace * (1000 / size) || 0; + } + scale = size / 1000; + return width * scale; + }; + TTFFont.prototype.lineHeight = function(size, includeGap) { + var gap; + if (includeGap == null) { + includeGap = false; + } + gap = includeGap ? this.lineGap : 0; + return ((this.ascender + gap - this.decender) / 1000) * size; + }; + return TTFFont; +})(); + +/************************************************************************************************/ +/* function : Data */ +/* comment : The ttf data decoded and stored in an array is read and written to the Data object.*/ +/************************************************************************************************/ +var Data = (function() { + function Data(data) { + this.data = data != null ? data : []; + this.pos = 0; + this.length = this.data.length; + } + Data.prototype.readByte = function() { + return this.data[this.pos++]; + }; + Data.prototype.writeByte = function(byte) { + return (this.data[this.pos++] = byte); + }; + Data.prototype.readUInt32 = function() { + var b1, b2, b3, b4; + b1 = this.readByte() * 0x1000000; + b2 = this.readByte() << 16; + b3 = this.readByte() << 8; + b4 = this.readByte(); + return b1 + b2 + b3 + b4; + }; + Data.prototype.writeUInt32 = function(val) { + this.writeByte((val >>> 24) & 0xff); + this.writeByte((val >> 16) & 0xff); + this.writeByte((val >> 8) & 0xff); + return this.writeByte(val & 0xff); + }; + Data.prototype.readInt32 = function() { + var int; + int = this.readUInt32(); + if (int >= 0x80000000) { + return int - 0x100000000; + } else { + return int; + } + }; + Data.prototype.writeInt32 = function(val) { + if (val < 0) { + val += 0x100000000; + } + return this.writeUInt32(val); + }; + Data.prototype.readUInt16 = function() { + var b1, b2; + b1 = this.readByte() << 8; + b2 = this.readByte(); + return b1 | b2; + }; + Data.prototype.writeUInt16 = function(val) { + this.writeByte((val >> 8) & 0xff); + return this.writeByte(val & 0xff); + }; + Data.prototype.readInt16 = function() { + var int; + int = this.readUInt16(); + if (int >= 0x8000) { + return int - 0x10000; + } else { + return int; + } + }; + Data.prototype.writeInt16 = function(val) { + if (val < 0) { + val += 0x10000; + } + return this.writeUInt16(val); + }; + Data.prototype.readString = function(length) { + var i, ret; + ret = []; + for ( + i = 0; + 0 <= length ? i < length : i > length; + i = 0 <= length ? ++i : --i + ) { + ret[i] = String.fromCharCode(this.readByte()); + } + return ret.join(""); + }; + Data.prototype.writeString = function(val) { + var i, _ref, _results; + _results = []; + for ( + i = 0, _ref = val.length; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + _results.push(this.writeByte(val.charCodeAt(i))); + } + return _results; + }; + /*Data.prototype.stringAt = function (pos, length) { + this.pos = pos; + return this.readString(length); + };*/ + Data.prototype.readShort = function() { + return this.readInt16(); + }; + Data.prototype.writeShort = function(val) { + return this.writeInt16(val); + }; + Data.prototype.readLongLong = function() { + var b1, b2, b3, b4, b5, b6, b7, b8; + b1 = this.readByte(); + b2 = this.readByte(); + b3 = this.readByte(); + b4 = this.readByte(); + b5 = this.readByte(); + b6 = this.readByte(); + b7 = this.readByte(); + b8 = this.readByte(); + if (b1 & 0x80) { + return ( + ((b1 ^ 0xff) * 0x100000000000000 + + (b2 ^ 0xff) * 0x1000000000000 + + (b3 ^ 0xff) * 0x10000000000 + + (b4 ^ 0xff) * 0x100000000 + + (b5 ^ 0xff) * 0x1000000 + + (b6 ^ 0xff) * 0x10000 + + (b7 ^ 0xff) * 0x100 + + (b8 ^ 0xff) + + 1) * + -1 + ); + } + return ( + b1 * 0x100000000000000 + + b2 * 0x1000000000000 + + b3 * 0x10000000000 + + b4 * 0x100000000 + + b5 * 0x1000000 + + b6 * 0x10000 + + b7 * 0x100 + + b8 + ); + }; + Data.prototype.writeLongLong = function(val) { + var high, low; + high = Math.floor(val / 0x100000000); + low = val & 0xffffffff; + this.writeByte((high >> 24) & 0xff); + this.writeByte((high >> 16) & 0xff); + this.writeByte((high >> 8) & 0xff); + this.writeByte(high & 0xff); + this.writeByte((low >> 24) & 0xff); + this.writeByte((low >> 16) & 0xff); + this.writeByte((low >> 8) & 0xff); + return this.writeByte(low & 0xff); + }; + Data.prototype.readInt = function() { + return this.readInt32(); + }; + Data.prototype.writeInt = function(val) { + return this.writeInt32(val); + }; + /*Data.prototype.slice = function (start, end) { + return this.data.slice(start, end); + };*/ + Data.prototype.read = function(bytes) { + var buf, i; + buf = []; + for ( + i = 0; + 0 <= bytes ? i < bytes : i > bytes; + i = 0 <= bytes ? ++i : --i + ) { + buf.push(this.readByte()); + } + return buf; + }; + Data.prototype.write = function(bytes) { + var byte, i, _len, _results; + _results = []; + for (i = 0, _len = bytes.length; i < _len; i++) { + byte = bytes[i]; + _results.push(this.writeByte(byte)); + } + return _results; + }; + return Data; +})(); + +var Directory = (function() { + var checksum; + + /*****************************************************************************************************/ + /* function : Directory generator */ + /* comment : Initialize the offset, tag, length, and checksum for each table for the font to be used.*/ + /*****************************************************************************************************/ + function Directory(data) { + var entry, i, _ref; + this.scalarType = data.readInt(); + this.tableCount = data.readShort(); + this.searchRange = data.readShort(); + this.entrySelector = data.readShort(); + this.rangeShift = data.readShort(); + this.tables = {}; + for ( + i = 0, _ref = this.tableCount; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + entry = { + tag: data.readString(4), + checksum: data.readInt(), + offset: data.readInt(), + length: data.readInt() + }; + this.tables[entry.tag] = entry; + } + } + /********************************************************************************************************/ + /* function : encode */ + /* comment : It encodes and stores the font table object and information used for the directory object. */ + /********************************************************************************************************/ + Directory.prototype.encode = function(tables) { + var adjustment, + directory, + directoryLength, + entrySelector, + headOffset, + log2, + offset, + rangeShift, + searchRange, + sum, + table, + tableCount, + tableData, + tag; + tableCount = Object.keys(tables).length; + log2 = Math.log(2); + searchRange = Math.floor(Math.log(tableCount) / log2) * 16; + entrySelector = Math.floor(searchRange / log2); + rangeShift = tableCount * 16 - searchRange; + directory = new Data(); + directory.writeInt(this.scalarType); + directory.writeShort(tableCount); + directory.writeShort(searchRange); + directory.writeShort(entrySelector); + directory.writeShort(rangeShift); + directoryLength = tableCount * 16; + offset = directory.pos + directoryLength; + headOffset = null; + tableData = []; + for (tag in tables) { + table = tables[tag]; + directory.writeString(tag); + directory.writeInt(checksum(table)); + directory.writeInt(offset); + directory.writeInt(table.length); + tableData = tableData.concat(table); + if (tag === "head") { + headOffset = offset; + } + offset += table.length; + while (offset % 4) { + tableData.push(0); + offset++; + } + } + directory.write(tableData); + sum = checksum(directory.data); + adjustment = 0xb1b0afba - sum; + directory.pos = headOffset + 8; + directory.writeUInt32(adjustment); + return directory.data; + }; + /***************************************************************/ + /* function : checksum */ + /* comment : Duplicate the table for the tag. */ + /***************************************************************/ + checksum = function(data) { + var i, sum, tmp, _ref; + data = __slice.call(data); + while (data.length % 4) { + data.push(0); + } + tmp = new Data(data); + sum = 0; + for (i = 0, _ref = data.length; i < _ref; i = i += 4) { + sum += tmp.readUInt32(); + } + return sum & 0xffffffff; + }; + return Directory; +})(); + +var Table, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { + for (var key in parent) { + if (__hasProp.call(parent, key)) child[key] = parent[key]; + } + + function ctor() { + this.constructor = child; + } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + child.__super__ = parent.prototype; + return child; + }; + +/***************************************************************/ +/* function : Table */ +/* comment : Save info for each table, and parse the table. */ +/***************************************************************/ +Table = (function() { + function Table(file) { + var info; + this.file = file; + info = this.file.directory.tables[this.tag]; + this.exists = !!info; + if (info) { + (this.offset = info.offset), (this.length = info.length); + this.parse(this.file.contents); + } + } + Table.prototype.parse = function() {}; + Table.prototype.encode = function() {}; + Table.prototype.raw = function() { + if (!this.exists) { + return null; + } + this.file.contents.pos = this.offset; + return this.file.contents.read(this.length); + }; + return Table; +})(); + +var HeadTable = (function(_super) { + __extends(HeadTable, _super); + + function HeadTable() { + return HeadTable.__super__.constructor.apply(this, arguments); + } + HeadTable.prototype.tag = "head"; + HeadTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.revision = data.readInt(); + this.checkSumAdjustment = data.readInt(); + this.magicNumber = data.readInt(); + this.flags = data.readShort(); + this.unitsPerEm = data.readShort(); + this.created = data.readLongLong(); + this.modified = data.readLongLong(); + this.xMin = data.readShort(); + this.yMin = data.readShort(); + this.xMax = data.readShort(); + this.yMax = data.readShort(); + this.macStyle = data.readShort(); + this.lowestRecPPEM = data.readShort(); + this.fontDirectionHint = data.readShort(); + this.indexToLocFormat = data.readShort(); + return (this.glyphDataFormat = data.readShort()); + }; + HeadTable.prototype.encode = function(indexToLocFormat) { + var table; + table = new Data(); + table.writeInt(this.version); + table.writeInt(this.revision); + table.writeInt(this.checkSumAdjustment); + table.writeInt(this.magicNumber); + table.writeShort(this.flags); + table.writeShort(this.unitsPerEm); + table.writeLongLong(this.created); + table.writeLongLong(this.modified); + table.writeShort(this.xMin); + table.writeShort(this.yMin); + table.writeShort(this.xMax); + table.writeShort(this.yMax); + table.writeShort(this.macStyle); + table.writeShort(this.lowestRecPPEM); + table.writeShort(this.fontDirectionHint); + table.writeShort(indexToLocFormat); + table.writeShort(this.glyphDataFormat); + return table.data; + }; + return HeadTable; +})(Table); + +/************************************************************************************/ +/* function : CmapEntry */ +/* comment : Cmap Initializes and encodes object information (required by pdf spec).*/ +/************************************************************************************/ +var CmapEntry = (function() { + function CmapEntry(data, offset) { + var code, + count, + endCode, + glyphId, + glyphIds, + i, + idDelta, + idRangeOffset, + index, + saveOffset, + segCount, + segCountX2, + start, + startCode, + tail, + _j, + _k, + _len; + this.platformID = data.readUInt16(); + this.encodingID = data.readShort(); + this.offset = offset + data.readInt(); + saveOffset = data.pos; + data.pos = this.offset; + this.format = data.readUInt16(); + this.length = data.readUInt16(); + this.language = data.readUInt16(); + this.isUnicode = + (this.platformID === 3 && this.encodingID === 1 && this.format === 4) || + (this.platformID === 0 && this.format === 4) || + (this.platformID === 1 && this.encodingID === 0 && this.format === 0); + this.codeMap = {}; + switch (this.format) { + case 0: + for (i = 0; i < 256; ++i) { + this.codeMap[i] = data.readByte(); + } + break; + case 4: + segCountX2 = data.readUInt16(); + segCount = segCountX2 / 2; + data.pos += 6; + endCode = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + data.pos += 2; + startCode = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + idDelta = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + idRangeOffset = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= segCount ? _j < segCount : _j > segCount; + i = 0 <= segCount ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + count = (this.length - data.pos + this.offset) / 2; + glyphIds = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= count ? _j < count : _j > count; + i = 0 <= count ? ++_j : --_j + ) { + _results.push(data.readUInt16()); + } + return _results; + })(); + for (i = _j = 0, _len = endCode.length; _j < _len; i = ++_j) { + tail = endCode[i]; + start = startCode[i]; + for ( + code = _k = start; + start <= tail ? _k <= tail : _k >= tail; + code = start <= tail ? ++_k : --_k + ) { + if (idRangeOffset[i] === 0) { + glyphId = code + idDelta[i]; + } else { + index = idRangeOffset[i] / 2 + (code - start) - (segCount - i); + glyphId = glyphIds[index] || 0; + if (glyphId !== 0) { + glyphId += idDelta[i]; + } + } + this.codeMap[code] = glyphId & 0xffff; + } + } + } + data.pos = saveOffset; + } + CmapEntry.encode = function(charmap, encoding) { + var charMap, + code, + codeMap, + codes, + delta, + deltas, + diff, + endCode, + endCodes, + entrySelector, + glyphIDs, + i, + id, + indexes, + last, + map, + nextID, + offset, + old, + rangeOffsets, + rangeShift, + searchRange, + segCount, + segCountX2, + startCode, + startCodes, + startGlyph, + subtable, + _i, + _j, + _k, + _l, + _len, + _len1, + _len2, + _len3, + _len4, + _len5, + _len6, + _len7, + _m, + _n, + _name, + _o, + _p, + _q; + subtable = new Data(); + codes = Object.keys(charmap).sort(function(a, b) { + return a - b; + }); + switch (encoding) { + case "macroman": + id = 0; + indexes = (function() { + var _results = []; + for (i = 0; i < 256; ++i) { + _results.push(0); + } + return _results; + })(); + map = { + 0: 0 + }; + codeMap = {}; + for (_i = 0, _len = codes.length; _i < _len; _i++) { + code = codes[_i]; + if (map[(_name = charmap[code])] == null) { + map[_name] = ++id; + } + codeMap[code] = { + old: charmap[code], + new: map[charmap[code]] + }; + indexes[code] = map[charmap[code]]; + } + subtable.writeUInt16(1); + subtable.writeUInt16(0); + subtable.writeUInt32(12); + subtable.writeUInt16(0); + subtable.writeUInt16(262); + subtable.writeUInt16(0); + subtable.write(indexes); + return { + charMap: codeMap, + subtable: subtable.data, + maxGlyphID: id + 1 + }; + case "unicode": + startCodes = []; + endCodes = []; + nextID = 0; + map = {}; + charMap = {}; + last = diff = null; + for (_j = 0, _len1 = codes.length; _j < _len1; _j++) { + code = codes[_j]; + old = charmap[code]; + if (map[old] == null) { + map[old] = ++nextID; + } + charMap[code] = { + old: old, + new: map[old] + }; + delta = map[old] - code; + if (last == null || delta !== diff) { + if (last) { + endCodes.push(last); + } + startCodes.push(code); + diff = delta; + } + last = code; + } + if (last) { + endCodes.push(last); + } + endCodes.push(0xffff); + startCodes.push(0xffff); + segCount = startCodes.length; + segCountX2 = segCount * 2; + searchRange = 2 * Math.pow(Math.log(segCount) / Math.LN2, 2); + entrySelector = Math.log(searchRange / 2) / Math.LN2; + rangeShift = 2 * segCount - searchRange; + deltas = []; + rangeOffsets = []; + glyphIDs = []; + for (i = _k = 0, _len2 = startCodes.length; _k < _len2; i = ++_k) { + startCode = startCodes[i]; + endCode = endCodes[i]; + if (startCode === 0xffff) { + deltas.push(0); + rangeOffsets.push(0); + break; + } + startGlyph = charMap[startCode]["new"]; + if (startCode - startGlyph >= 0x8000) { + deltas.push(0); + rangeOffsets.push(2 * (glyphIDs.length + segCount - i)); + for ( + code = _l = startCode; + startCode <= endCode ? _l <= endCode : _l >= endCode; + code = startCode <= endCode ? ++_l : --_l + ) { + glyphIDs.push(charMap[code]["new"]); + } + } else { + deltas.push(startGlyph - startCode); + rangeOffsets.push(0); + } + } + subtable.writeUInt16(3); + subtable.writeUInt16(1); + subtable.writeUInt32(12); + subtable.writeUInt16(4); + subtable.writeUInt16(16 + segCount * 8 + glyphIDs.length * 2); + subtable.writeUInt16(0); + subtable.writeUInt16(segCountX2); + subtable.writeUInt16(searchRange); + subtable.writeUInt16(entrySelector); + subtable.writeUInt16(rangeShift); + for (_m = 0, _len3 = endCodes.length; _m < _len3; _m++) { + code = endCodes[_m]; + subtable.writeUInt16(code); + } + subtable.writeUInt16(0); + for (_n = 0, _len4 = startCodes.length; _n < _len4; _n++) { + code = startCodes[_n]; + subtable.writeUInt16(code); + } + for (_o = 0, _len5 = deltas.length; _o < _len5; _o++) { + delta = deltas[_o]; + subtable.writeUInt16(delta); + } + for (_p = 0, _len6 = rangeOffsets.length; _p < _len6; _p++) { + offset = rangeOffsets[_p]; + subtable.writeUInt16(offset); + } + for (_q = 0, _len7 = glyphIDs.length; _q < _len7; _q++) { + id = glyphIDs[_q]; + subtable.writeUInt16(id); + } + return { + charMap: charMap, + subtable: subtable.data, + maxGlyphID: nextID + 1 + }; + } + }; + return CmapEntry; +})(); + +var CmapTable = (function(_super) { + __extends(CmapTable, _super); + + function CmapTable() { + return CmapTable.__super__.constructor.apply(this, arguments); + } + CmapTable.prototype.tag = "cmap"; + CmapTable.prototype.parse = function(data) { + var entry, i, tableCount; + data.pos = this.offset; + this.version = data.readUInt16(); + tableCount = data.readUInt16(); + this.tables = []; + this.unicode = null; + for ( + i = 0; + 0 <= tableCount ? i < tableCount : i > tableCount; + i = 0 <= tableCount ? ++i : --i + ) { + entry = new CmapEntry(data, this.offset); + this.tables.push(entry); + if (entry.isUnicode) { + if (this.unicode == null) { + this.unicode = entry; + } + } + } + return true; + }; + /*************************************************************************/ + /* function : encode */ + /* comment : Encode the cmap table corresponding to the input character. */ + /*************************************************************************/ + CmapTable.encode = function(charmap, encoding) { + var result, table; + if (encoding == null) { + encoding = "macroman"; + } + result = CmapEntry.encode(charmap, encoding); + table = new Data(); + table.writeUInt16(0); + table.writeUInt16(1); + result.table = table.data.concat(result.subtable); + return result; + }; + return CmapTable; +})(Table); + +var HheaTable = (function(_super) { + __extends(HheaTable, _super); + + function HheaTable() { + return HheaTable.__super__.constructor.apply(this, arguments); + } + HheaTable.prototype.tag = "hhea"; + HheaTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.ascender = data.readShort(); + this.decender = data.readShort(); + this.lineGap = data.readShort(); + this.advanceWidthMax = data.readShort(); + this.minLeftSideBearing = data.readShort(); + this.minRightSideBearing = data.readShort(); + this.xMaxExtent = data.readShort(); + this.caretSlopeRise = data.readShort(); + this.caretSlopeRun = data.readShort(); + this.caretOffset = data.readShort(); + data.pos += 4 * 2; + this.metricDataFormat = data.readShort(); + return (this.numberOfMetrics = data.readUInt16()); + }; + /*HheaTable.prototype.encode = function (ids) { + var i, table, _i, _ref; + table = new Data; + table.writeInt(this.version); + table.writeShort(this.ascender); + table.writeShort(this.decender); + table.writeShort(this.lineGap); + table.writeShort(this.advanceWidthMax); + table.writeShort(this.minLeftSideBearing); + table.writeShort(this.minRightSideBearing); + table.writeShort(this.xMaxExtent); + table.writeShort(this.caretSlopeRise); + table.writeShort(this.caretSlopeRun); + table.writeShort(this.caretOffset); + for (i = _i = 0, _ref = 4 * 2; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + table.writeByte(0); + } + table.writeShort(this.metricDataFormat); + table.writeUInt16(ids.length); + return table.data; + };*/ + return HheaTable; +})(Table); + +var OS2Table = (function(_super) { + __extends(OS2Table, _super); + + function OS2Table() { + return OS2Table.__super__.constructor.apply(this, arguments); + } + OS2Table.prototype.tag = "OS/2"; + OS2Table.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readUInt16(); + this.averageCharWidth = data.readShort(); + this.weightClass = data.readUInt16(); + this.widthClass = data.readUInt16(); + this.type = data.readShort(); + this.ySubscriptXSize = data.readShort(); + this.ySubscriptYSize = data.readShort(); + this.ySubscriptXOffset = data.readShort(); + this.ySubscriptYOffset = data.readShort(); + this.ySuperscriptXSize = data.readShort(); + this.ySuperscriptYSize = data.readShort(); + this.ySuperscriptXOffset = data.readShort(); + this.ySuperscriptYOffset = data.readShort(); + this.yStrikeoutSize = data.readShort(); + this.yStrikeoutPosition = data.readShort(); + this.familyClass = data.readShort(); + this.panose = (function() { + var i, _results; + _results = []; + for (i = 0; i < 10; ++i) { + _results.push(data.readByte()); + } + return _results; + })(); + this.charRange = (function() { + var i, _results; + _results = []; + for (i = 0; i < 4; ++i) { + _results.push(data.readInt()); + } + return _results; + })(); + this.vendorID = data.readString(4); + this.selection = data.readShort(); + this.firstCharIndex = data.readShort(); + this.lastCharIndex = data.readShort(); + if (this.version > 0) { + this.ascent = data.readShort(); + this.descent = data.readShort(); + this.lineGap = data.readShort(); + this.winAscent = data.readShort(); + this.winDescent = data.readShort(); + this.codePageRange = (function() { + var i, _results; + _results = []; + for (i = 0; i < 2; i = ++i) { + _results.push(data.readInt()); + } + return _results; + })(); + if (this.version > 1) { + this.xHeight = data.readShort(); + this.capHeight = data.readShort(); + this.defaultChar = data.readShort(); + this.breakChar = data.readShort(); + return (this.maxContext = data.readShort()); + } + } + }; + /*OS2Table.prototype.encode = function () { + return this.raw(); + };*/ + return OS2Table; +})(Table); + +var PostTable = (function(_super) { + __extends(PostTable, _super); + + function PostTable() { + return PostTable.__super__.constructor.apply(this, arguments); + } + PostTable.prototype.tag = "post"; + PostTable.prototype.parse = function(data) { + var length, numberOfGlyphs, _results; + data.pos = this.offset; + this.format = data.readInt(); + this.italicAngle = data.readInt(); + this.underlinePosition = data.readShort(); + this.underlineThickness = data.readShort(); + this.isFixedPitch = data.readInt(); + this.minMemType42 = data.readInt(); + this.maxMemType42 = data.readInt(); + this.minMemType1 = data.readInt(); + this.maxMemType1 = data.readInt(); + switch (this.format) { + case 0x00010000: + break; + case 0x00020000: + numberOfGlyphs = data.readUInt16(); + this.glyphNameIndex = []; + var i; + for ( + i = 0; + 0 <= numberOfGlyphs ? i < numberOfGlyphs : i > numberOfGlyphs; + i = 0 <= numberOfGlyphs ? ++i : --i + ) { + this.glyphNameIndex.push(data.readUInt16()); + } + this.names = []; + _results = []; + while (data.pos < this.offset + this.length) { + length = data.readByte(); + _results.push(this.names.push(data.readString(length))); + } + return _results; + case 0x00025000: + numberOfGlyphs = data.readUInt16(); + return (this.offsets = data.read(numberOfGlyphs)); + case 0x00030000: + break; + case 0x00040000: + return (this.map = function() { + var _j, _ref, _results1; + _results1 = []; + for ( + i = _j = 0, _ref = this.file.maxp.numGlyphs; + 0 <= _ref ? _j < _ref : _j > _ref; + i = 0 <= _ref ? ++_j : --_j + ) { + _results1.push(data.readUInt32()); + } + return _results1; + }.call(this)); + } + }; + return PostTable; +})(Table); + +/*********************************************************************************************************/ +/* function : NameEntry */ +/* comment : Store copyright information, platformID, encodingID, and languageID in the NameEntry object.*/ +/*********************************************************************************************************/ +var NameEntry = (function() { + function NameEntry(raw, entry) { + this.raw = raw; + this.length = raw.length; + this.platformID = entry.platformID; + this.encodingID = entry.encodingID; + this.languageID = entry.languageID; + } + return NameEntry; +})(); + +var NameTable = (function(_super) { + __extends(NameTable, _super); + + function NameTable() { + return NameTable.__super__.constructor.apply(this, arguments); + } + NameTable.prototype.tag = "name"; + NameTable.prototype.parse = function(data) { + var count, + entries, + entry, + i, + name, + stringOffset, + strings, + text, + _j, + _len, + _name; + data.pos = this.offset; + data.readShort(); //format + count = data.readShort(); + stringOffset = data.readShort(); + entries = []; + for ( + i = 0; + 0 <= count ? i < count : i > count; + i = 0 <= count ? ++i : --i + ) { + entries.push({ + platformID: data.readShort(), + encodingID: data.readShort(), + languageID: data.readShort(), + nameID: data.readShort(), + length: data.readShort(), + offset: this.offset + stringOffset + data.readShort() + }); + } + strings = {}; + for (i = _j = 0, _len = entries.length; _j < _len; i = ++_j) { + entry = entries[i]; + data.pos = entry.offset; + text = data.readString(entry.length); + name = new NameEntry(text, entry); + if (strings[(_name = entry.nameID)] == null) { + strings[_name] = []; + } + strings[entry.nameID].push(name); + } + this.strings = strings; + this.copyright = strings[0]; + this.fontFamily = strings[1]; + this.fontSubfamily = strings[2]; + this.uniqueSubfamily = strings[3]; + this.fontName = strings[4]; + this.version = strings[5]; + try { + this.postscriptName = strings[6][0].raw.replace( + /[\x00-\x19\x80-\xff]/g, + "" + ); + } catch (e) { + this.postscriptName = strings[4][0].raw.replace( + /[\x00-\x19\x80-\xff]/g, + "" + ); + } + this.trademark = strings[7]; + this.manufacturer = strings[8]; + this.designer = strings[9]; + this.description = strings[10]; + this.vendorUrl = strings[11]; + this.designerUrl = strings[12]; + this.license = strings[13]; + this.licenseUrl = strings[14]; + this.preferredFamily = strings[15]; + this.preferredSubfamily = strings[17]; + this.compatibleFull = strings[18]; + return (this.sampleText = strings[19]); + }; + /*NameTable.prototype.encode = function () { + var id, list, nameID, nameTable, postscriptName, strCount, strTable, string, strings, table, val, _i, _len, _ref; + strings = {}; + _ref = this.strings; + for (id in _ref) { + val = _ref[id]; + strings[id] = val; + } + postscriptName = new NameEntry("" + subsetTag + "+" + this.postscriptName, { + platformID: 1 + , encodingID: 0 + , languageID: 0 + }); + strings[6] = [postscriptName]; + subsetTag = successorOf(subsetTag); + strCount = 0; + for (id in strings) { + list = strings[id]; + if (list != null) { + strCount += list.length; + } + } + table = new Data; + strTable = new Data; + table.writeShort(0); + table.writeShort(strCount); + table.writeShort(6 + 12 * strCount); + for (nameID in strings) { + list = strings[nameID]; + if (list != null) { + for (_i = 0, _len = list.length; _i < _len; _i++) { + string = list[_i]; + table.writeShort(string.platformID); + table.writeShort(string.encodingID); + table.writeShort(string.languageID); + table.writeShort(nameID); + table.writeShort(string.length); + table.writeShort(strTable.pos); + strTable.writeString(string.raw); + } + } + } + return nameTable = { + postscriptName: postscriptName.raw + , table: table.data.concat(strTable.data) + }; + };*/ + return NameTable; +})(Table); + +var MaxpTable = (function(_super) { + __extends(MaxpTable, _super); + + function MaxpTable() { + return MaxpTable.__super__.constructor.apply(this, arguments); + } + MaxpTable.prototype.tag = "maxp"; + MaxpTable.prototype.parse = function(data) { + data.pos = this.offset; + this.version = data.readInt(); + this.numGlyphs = data.readUInt16(); + this.maxPoints = data.readUInt16(); + this.maxContours = data.readUInt16(); + this.maxCompositePoints = data.readUInt16(); + this.maxComponentContours = data.readUInt16(); + this.maxZones = data.readUInt16(); + this.maxTwilightPoints = data.readUInt16(); + this.maxStorage = data.readUInt16(); + this.maxFunctionDefs = data.readUInt16(); + this.maxInstructionDefs = data.readUInt16(); + this.maxStackElements = data.readUInt16(); + this.maxSizeOfInstructions = data.readUInt16(); + this.maxComponentElements = data.readUInt16(); + return (this.maxComponentDepth = data.readUInt16()); + }; + /*MaxpTable.prototype.encode = function (ids) { + var table; + table = new Data; + table.writeInt(this.version); + table.writeUInt16(ids.length); + table.writeUInt16(this.maxPoints); + table.writeUInt16(this.maxContours); + table.writeUInt16(this.maxCompositePoints); + table.writeUInt16(this.maxComponentContours); + table.writeUInt16(this.maxZones); + table.writeUInt16(this.maxTwilightPoints); + table.writeUInt16(this.maxStorage); + table.writeUInt16(this.maxFunctionDefs); + table.writeUInt16(this.maxInstructionDefs); + table.writeUInt16(this.maxStackElements); + table.writeUInt16(this.maxSizeOfInstructions); + table.writeUInt16(this.maxComponentElements); + table.writeUInt16(this.maxComponentDepth); + return table.data; + };*/ + return MaxpTable; +})(Table); + +var HmtxTable = (function(_super) { + __extends(HmtxTable, _super); + + function HmtxTable() { + return HmtxTable.__super__.constructor.apply(this, arguments); + } + HmtxTable.prototype.tag = "hmtx"; + HmtxTable.prototype.parse = function(data) { + var i, last, lsbCount, m, _j, _ref, _results; + data.pos = this.offset; + this.metrics = []; + for ( + i = 0, _ref = this.file.hhea.numberOfMetrics; + 0 <= _ref ? i < _ref : i > _ref; + i = 0 <= _ref ? ++i : --i + ) { + this.metrics.push({ + advance: data.readUInt16(), + lsb: data.readInt16() + }); + } + lsbCount = this.file.maxp.numGlyphs - this.file.hhea.numberOfMetrics; + this.leftSideBearings = (function() { + var _j, _results; + _results = []; + for ( + i = _j = 0; + 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; + i = 0 <= lsbCount ? ++_j : --_j + ) { + _results.push(data.readInt16()); + } + return _results; + })(); + this.widths = function() { + var _j, _len, _ref1, _results; + _ref1 = this.metrics; + _results = []; + for (_j = 0, _len = _ref1.length; _j < _len; _j++) { + m = _ref1[_j]; + _results.push(m.advance); + } + return _results; + }.call(this); + last = this.widths[this.widths.length - 1]; + _results = []; + for ( + i = _j = 0; + 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; + i = 0 <= lsbCount ? ++_j : --_j + ) { + _results.push(this.widths.push(last)); + } + return _results; + }; + /***************************************************************/ + /* function : forGlyph */ + /* comment : Returns the advance width and lsb for this glyph. */ + /***************************************************************/ + HmtxTable.prototype.forGlyph = function(id) { + if (id in this.metrics) { + return this.metrics[id]; + } + return { + advance: this.metrics[this.metrics.length - 1].advance, + lsb: this.leftSideBearings[id - this.metrics.length] + }; + }; + /*HmtxTable.prototype.encode = function (mapping) { + var id, metric, table, _i, _len; + table = new Data; + for (_i = 0, _len = mapping.length; _i < _len; _i++) { + id = mapping[_i]; + metric = this.forGlyph(id); + table.writeUInt16(metric.advance); + table.writeUInt16(metric.lsb); + } + return table.data; + };*/ + return HmtxTable; +})(Table); + +var __slice = [].slice; + +var GlyfTable = (function(_super) { + __extends(GlyfTable, _super); + + function GlyfTable() { + return GlyfTable.__super__.constructor.apply(this, arguments); + } + GlyfTable.prototype.tag = "glyf"; + GlyfTable.prototype.parse = function() { + return (this.cache = {}); + }; + GlyfTable.prototype.glyphFor = function(id) { + var data, + index, + length, + loca, + numberOfContours, + raw, + xMax, + xMin, + yMax, + yMin; + if (id in this.cache) { + return this.cache[id]; + } + loca = this.file.loca; + data = this.file.contents; + index = loca.indexOf(id); + length = loca.lengthOf(id); + if (length === 0) { + return (this.cache[id] = null); + } + data.pos = this.offset + index; + raw = new Data(data.read(length)); + numberOfContours = raw.readShort(); + xMin = raw.readShort(); + yMin = raw.readShort(); + xMax = raw.readShort(); + yMax = raw.readShort(); + if (numberOfContours === -1) { + this.cache[id] = new CompoundGlyph(raw, xMin, yMin, xMax, yMax); + } else { + this.cache[id] = new SimpleGlyph( + raw, + numberOfContours, + xMin, + yMin, + xMax, + yMax + ); + } + return this.cache[id]; + }; + GlyfTable.prototype.encode = function(glyphs, mapping, old2new) { + var glyph, id, offsets, table, _i, _len; + table = []; + offsets = []; + for (_i = 0, _len = mapping.length; _i < _len; _i++) { + id = mapping[_i]; + glyph = glyphs[id]; + offsets.push(table.length); + if (glyph) { + table = table.concat(glyph.encode(old2new)); + } + } + offsets.push(table.length); + return { + table: table, + offsets: offsets + }; + }; + return GlyfTable; +})(Table); + +var SimpleGlyph = (function() { + /**************************************************************************/ + /* function : SimpleGlyph */ + /* comment : Stores raw, xMin, yMin, xMax, and yMax values for this glyph.*/ + /**************************************************************************/ + function SimpleGlyph(raw, numberOfContours, xMin, yMin, xMax, yMax) { + this.raw = raw; + this.numberOfContours = numberOfContours; + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + this.compound = false; + } + SimpleGlyph.prototype.encode = function() { + return this.raw.data; + }; + return SimpleGlyph; +})(); + +var CompoundGlyph = (function() { + var ARG_1_AND_2_ARE_WORDS, + MORE_COMPONENTS, + WE_HAVE_AN_X_AND_Y_SCALE, + WE_HAVE_A_SCALE, + WE_HAVE_A_TWO_BY_TWO; + ARG_1_AND_2_ARE_WORDS = 0x0001; + WE_HAVE_A_SCALE = 0x0008; + MORE_COMPONENTS = 0x0020; + WE_HAVE_AN_X_AND_Y_SCALE = 0x0040; + WE_HAVE_A_TWO_BY_TWO = 0x0080; + + /********************************************************************************************************************/ + /* function : CompoundGlypg generator */ + /* comment : It stores raw, xMin, yMin, xMax, yMax, glyph id, and glyph offset for the corresponding compound glyph.*/ + /********************************************************************************************************************/ + function CompoundGlyph(raw, xMin, yMin, xMax, yMax) { + var data, flags; + this.raw = raw; + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + this.compound = true; + this.glyphIDs = []; + this.glyphOffsets = []; + data = this.raw; + while (true) { + flags = data.readShort(); + this.glyphOffsets.push(data.pos); + this.glyphIDs.push(data.readUInt16()); + if (!(flags & MORE_COMPONENTS)) { + break; + } + if (flags & ARG_1_AND_2_ARE_WORDS) { + data.pos += 4; + } else { + data.pos += 2; + } + if (flags & WE_HAVE_A_TWO_BY_TWO) { + data.pos += 8; + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + data.pos += 4; + } else if (flags & WE_HAVE_A_SCALE) { + data.pos += 2; + } + } + } + /****************************************************************************************************************/ + /* function : CompoundGlypg encode */ + /* comment : After creating a table for the characters you typed, you call directory.encode to encode the table.*/ + /****************************************************************************************************************/ + CompoundGlyph.prototype.encode = function() { + var i, result, _len, _ref; + result = new Data(__slice.call(this.raw.data)); + _ref = this.glyphIDs; + for (i = 0, _len = _ref.length; i < _len; ++i) { + result.pos = this.glyphOffsets[i]; + } + return result.data; + }; + return CompoundGlyph; +})(); + +var LocaTable = (function(_super) { + __extends(LocaTable, _super); + + function LocaTable() { + return LocaTable.__super__.constructor.apply(this, arguments); + } + LocaTable.prototype.tag = "loca"; + LocaTable.prototype.parse = function(data) { + var format, i; + data.pos = this.offset; + format = this.file.head.indexToLocFormat; + if (format === 0) { + return (this.offsets = function() { + var _ref, _results; + _results = []; + for (i = 0, _ref = this.length; i < _ref; i += 2) { + _results.push(data.readUInt16() * 2); + } + return _results; + }.call(this)); + } else { + return (this.offsets = function() { + var _ref, _results; + _results = []; + for (i = 0, _ref = this.length; i < _ref; i += 4) { + _results.push(data.readUInt32()); + } + return _results; + }.call(this)); + } + }; + LocaTable.prototype.indexOf = function(id) { + return this.offsets[id]; + }; + LocaTable.prototype.lengthOf = function(id) { + return this.offsets[id + 1] - this.offsets[id]; + }; + LocaTable.prototype.encode = function(offsets, activeGlyphs) { + var LocaTable = new Uint32Array(this.offsets.length); + var glyfPtr = 0; + var listGlyf = 0; + for (var k = 0; k < LocaTable.length; ++k) { + LocaTable[k] = glyfPtr; + if (listGlyf < activeGlyphs.length && activeGlyphs[listGlyf] == k) { + ++listGlyf; + LocaTable[k] = glyfPtr; + var start = this.offsets[k]; + var len = this.offsets[k + 1] - start; + if (len > 0) { + glyfPtr += len; + } + } + } + var newLocaTable = new Array(LocaTable.length * 4); + for (var j = 0; j < LocaTable.length; ++j) { + newLocaTable[4 * j + 3] = LocaTable[j] & 0x000000ff; + newLocaTable[4 * j + 2] = (LocaTable[j] & 0x0000ff00) >> 8; + newLocaTable[4 * j + 1] = (LocaTable[j] & 0x00ff0000) >> 16; + newLocaTable[4 * j] = (LocaTable[j] & 0xff000000) >> 24; + } + return newLocaTable; + }; + return LocaTable; +})(Table); + +/************************************************************************************/ +/* function : invert */ +/* comment : Change the object's (key: value) to create an object with (value: key).*/ +/************************************************************************************/ +var invert = function(object) { + var key, ret, val; + ret = {}; + for (key in object) { + val = object[key]; + ret[val] = key; + } + return ret; +}; + +/*var successorOf = function (input) { + var added, alphabet, carry, i, index, isUpperCase, last, length, next, result; + alphabet = 'abcdefghijklmnopqrstuvwxyz'; + length = alphabet.length; + result = input; + i = input.length; + while (i >= 0) { + last = input.charAt(--i); + if (isNaN(last)) { + index = alphabet.indexOf(last.toLowerCase()); + if (index === -1) { + next = last; + carry = true; + } + else { + next = alphabet.charAt((index + 1) % length); + isUpperCase = last === last.toUpperCase(); + if (isUpperCase) { + next = next.toUpperCase(); + } + carry = index + 1 >= length; + if (carry && i === 0) { + added = isUpperCase ? 'A' : 'a'; + result = added + next + result.slice(1); + break; + } + } + } + else { + next = +last + 1; + carry = next > 9; + if (carry) { + next = 0; + } + if (carry && i === 0) { + result = '1' + next + result.slice(1); + break; + } + } + result = result.slice(0, i) + next + result.slice(i + 1); + if (!carry) { + break; + } + } + return result; + };*/ + +var Subset = (function() { + function Subset(font) { + this.font = font; + this.subset = {}; + this.unicodes = {}; + this.next = 33; + } + /*Subset.prototype.use = function (character) { + var i, _i, _ref; + if (typeof character === 'string') { + for (i = _i = 0, _ref = character.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + this.use(character.charCodeAt(i)); + } + return; + } + if (!this.unicodes[character]) { + this.subset[this.next] = character; + return this.unicodes[character] = this.next++; + } + };*/ + /*Subset.prototype.encodeText = function (text) { + var char, i, string, _i, _ref; + string = ''; + for (i = _i = 0, _ref = text.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + char = this.unicodes[text.charCodeAt(i)]; + string += String.fromCharCode(char); + } + return string; + };*/ + /***************************************************************/ + /* function : generateCmap */ + /* comment : Returns the unicode cmap for this font. */ + /***************************************************************/ + Subset.prototype.generateCmap = function() { + var mapping, roman, unicode, unicodeCmap, _ref; + unicodeCmap = this.font.cmap.tables[0].codeMap; + mapping = {}; + _ref = this.subset; + for (roman in _ref) { + unicode = _ref[roman]; + mapping[roman] = unicodeCmap[unicode]; + } + return mapping; + }; + /*Subset.prototype.glyphIDs = function () { + var ret, roman, unicode, unicodeCmap, val, _ref; + unicodeCmap = this.font.cmap.tables[0].codeMap; + ret = [0]; + _ref = this.subset; + for (roman in _ref) { + unicode = _ref[roman]; + val = unicodeCmap[unicode]; + if ((val != null) && __indexOf.call(ret, val) < 0) { + ret.push(val); + } + } + return ret.sort(); + };*/ + /******************************************************************/ + /* function : glyphsFor */ + /* comment : Returns simple glyph objects for the input character.*/ + /******************************************************************/ + Subset.prototype.glyphsFor = function(glyphIDs) { + var additionalIDs, glyph, glyphs, id, _i, _len, _ref; + glyphs = {}; + for (_i = 0, _len = glyphIDs.length; _i < _len; _i++) { + id = glyphIDs[_i]; + glyphs[id] = this.font.glyf.glyphFor(id); + } + additionalIDs = []; + for (id in glyphs) { + glyph = glyphs[id]; + if (glyph != null ? glyph.compound : void 0) { + additionalIDs.push.apply(additionalIDs, glyph.glyphIDs); + } + } + if (additionalIDs.length > 0) { + _ref = this.glyphsFor(additionalIDs); + for (id in _ref) { + glyph = _ref[id]; + glyphs[id] = glyph; + } + } + return glyphs; + }; + /***************************************************************/ + /* function : encode */ + /* comment : Encode various tables for the characters you use. */ + /***************************************************************/ + Subset.prototype.encode = function(glyID, indexToLocFormat) { + var cmap, + code, + glyf, + glyphs, + id, + ids, + loca, + new2old, + newIDs, + nextGlyphID, + old2new, + oldID, + oldIDs, + tables, + _ref; + cmap = CmapTable.encode(this.generateCmap(), "unicode"); + glyphs = this.glyphsFor(glyID); + old2new = { + 0: 0 + }; + _ref = cmap.charMap; + for (code in _ref) { + ids = _ref[code]; + old2new[ids.old] = ids["new"]; + } + nextGlyphID = cmap.maxGlyphID; + for (oldID in glyphs) { + if (!(oldID in old2new)) { + old2new[oldID] = nextGlyphID++; + } + } + new2old = invert(old2new); + newIDs = Object.keys(new2old).sort(function(a, b) { + return a - b; + }); + oldIDs = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = newIDs.length; _i < _len; _i++) { + id = newIDs[_i]; + _results.push(new2old[id]); + } + return _results; + })(); + glyf = this.font.glyf.encode(glyphs, oldIDs, old2new); + loca = this.font.loca.encode(glyf.offsets, oldIDs); + tables = { + cmap: this.font.cmap.raw(), + glyf: glyf.table, + loca: loca, + hmtx: this.font.hmtx.raw(), + hhea: this.font.hhea.raw(), + maxp: this.font.maxp.raw(), + post: this.font.post.raw(), + name: this.font.name.raw(), + head: this.font.head.encode(indexToLocFormat) + }; + if (this.font.os2.exists) { + tables["OS/2"] = this.font.os2.raw(); + } + return this.font.directory.encode(tables); + }; + return Subset; +})(); + +jsPDF.API.PDFObject = (function() { + var pad; + + function PDFObject() {} + pad = function(str, length) { + return (Array(length + 1).join("0") + str).slice(-length); + }; + /*****************************************************************************/ + /* function : convert */ + /* comment :Converts pdf tag's / FontBBox and array values in / W to strings */ + /*****************************************************************************/ + PDFObject.convert = function(object) { + var e, items, key, out, val; + if (Array.isArray(object)) { + items = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = object.length; _i < _len; _i++) { + e = object[_i]; + _results.push(PDFObject.convert(e)); + } + return _results; + })().join(" "); + return "[" + items + "]"; + } else if (typeof object === "string") { + return "/" + object; + } else if (object != null ? object.isString : void 0) { + return "(" + object + ")"; + } else if (object instanceof Date) { + return ( + "(D:" + + pad(object.getUTCFullYear(), 4) + + pad(object.getUTCMonth(), 2) + + pad(object.getUTCDate(), 2) + + pad(object.getUTCHours(), 2) + + pad(object.getUTCMinutes(), 2) + + pad(object.getUTCSeconds(), 2) + + "Z)" + ); + } else if ({}.toString.call(object) === "[object Object]") { + out = ["<<"]; + for (key in object) { + val = object[key]; + out.push("/" + key + " " + PDFObject.convert(val)); + } + out.push(">>"); + return out.join("\n"); + } else { + return "" + object; + } + }; + return PDFObject; +})(); + +export { AcroForm, AcroFormAppearance, AcroFormButton, AcroFormCheckBox, AcroFormChoiceField, AcroFormComboBox, AcroFormEditBox, AcroFormListBox, AcroFormPasswordField, AcroFormPushButton, AcroFormRadioButton, AcroFormTextField, GState, ShadingPattern, TilingPattern, jsPDF as default, jsPDF }; diff --git a/modules/base/latex.mjs b/modules/base/latex.mjs index 9a63f9b65..114e48cd6 100644 --- a/modules/base/latex.mjs +++ b/modules/base/latex.mjs @@ -1,8 +1,10 @@ -import { loadScript, settings, isNodeJs, isStr, source_dir, browser } from '../core.mjs'; +import { loadScript, settings, isNodeJs, isStr, source_dir, browser, isBatchMode } from '../core.mjs'; import { getElementRect, _loadJSDOM, makeTranslate } from './BasePainter.mjs'; -import { FontHandler } from './FontHandler.mjs'; +import { FontHandler, kSymbol, kWingdings, kTimes } from './FontHandler.mjs'; +/* eslint-disable one-var */ + const symbols_map = { // greek letters from symbols.ttf '#alpha': '\u03B1', @@ -60,7 +62,7 @@ const symbols_map = { '#varUpsilon': '\u03D2', '#epsilon': '\u03B5', - // second set from symbols.ttf + // second set from symbols.ttf '#leq': '\u2264', '#/': '\u2044', '#infty': '\u221E', @@ -184,39 +186,41 @@ const symbols_map = { '#P': '\u00B6', // paragraph - // only required for MathJax to provide correct replacement + // only required for MathJax to provide correct replacement '#sqrt': '\u221A', '#bar': '', '#overline': '', '#underline': '', '#strike': '' -}, - +}; /** @summary Create a single regex to detect any symbol to replace, apply longer symbols first * @private */ -symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'), +const symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'); /** @summary Simple replacement of latex letters * @private */ -translateLaTeX = str => { - while ((str.length > 2) && (str[0] === '{') && (str[str.length - 1] === '}')) +function translateLaTeX(str) { + while ((str.length > 2) && (str.at(0) === '{') && (str.at(-1) === '}')) str = str.slice(1, str.length - 1); return str.replace(symbolsRegexCache, ch => symbols_map[ch]).replace(/\{\}/g, ''); -}, +} // array with relative width of base symbols from range 32..126 // eslint-disable-next-line -base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,431,570,468,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069], +const base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,200,570,200,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069]; // eslint-disable-next-line -extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; +const extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; -/** @ummary Calculate approximate labels width +/** @summary Calculate approximate labels width * @private */ function approximateLabelWidth(label, font, fsize) { + if (Number.isInteger(font)) + font = new FontHandler(font, fsize); + const len = label.length, symbol_width = (fsize || font.size) * font.aver_width; if (font.isMonospace()) @@ -231,22 +235,23 @@ function approximateLabelWidth(label, font, fsize) { sum += extra_symbols_width[code] || 1000; } - return sum/1000*symbol_width; + return sum / 1000 * symbol_width; } /** @summary array defines features supported by latex parser, used by both old and new parsers * @private */ const latex_features = [ - { name: '#it{' }, // italic - { name: '#bf{' }, // bold + { name: '#it{', bi: 'italic' }, // italic + { name: '#bf{', bi: 'bold' }, // bold { name: '#underline{', deco: 'underline' }, // underline { name: '#overline{', deco: 'overline' }, // overline { name: '#strike{', deco: 'line-through' }, // line through - { name: '#kern[', arg: 'float' }, // horizontal shift - { name: '#lower[', arg: 'float' }, // vertical shift + { name: '#kern[', arg: 'float', shift: 'x' }, // horizontal shift + { name: '#lower[', arg: 'float', shift: 'y' }, // vertical shift { name: '#scale[', arg: 'float' }, // font scale { name: '#color[', arg: 'int' }, // font color { name: '#font[', arg: 'int' }, // font face + { name: '#url[', arg: 'string' }, // url link { name: '_{', low_up: 'low' }, // subscript { name: '^{', low_up: 'up' }, // superscript { name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305' @@ -259,7 +264,8 @@ const latex_features = [ { name: '#tilde{', accent: '\u02DC', hasw: true }, // '\u0303' { name: '#slash{', accent: '\u2215' }, // '\u0337' { name: '#vec{', accent: '\u02ED', hasw: true }, // '\u0350' arrowhead - { name: '#frac{', twolines: 'line' }, + { name: '#frac{', twolines: 'line', middle: true }, + { name: '#splitmline{', twolines: true, middle: true }, { name: '#splitline{', twolines: true }, { name: '#sqrt[', arg: 'int', sqrt: true }, // root with arbitrary power { name: '#sqrt{', sqrt: true }, // square root @@ -273,17 +279,17 @@ const latex_features = [ { name: '#(){', braces: '()' }, { name: '#{}{', braces: '{}' }, { name: '#||{', braces: '||' } -], +]; // taken from: https://sites.math.washington.edu/~marshall/cxseminar/symbol.htm, starts from 33 // eslint-disable-next-line -symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993], +const symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993]; // taken from http://www.alanwood.net/demos/wingdings.html, starts from 33 // eslint-disable-next-line -wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505], +const wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505]; -symbolsPdfMap = {}; +const symbolsPdfMap = {}; /** @summary Return code for symbols from symbols.ttf * @desc Used in PDF generation @@ -294,14 +300,17 @@ function remapSymbolTtfCode(code) { for (const key in symbols_map) { const symbol = symbols_map[key]; if (symbol.length === 1) { - let letter = 0; + let letter; if (cnt < 54) { const opGreek = cnt; // see code in TLatex.cxx, line 1302 letter = 97 + opGreek; - if (opGreek > 25) letter -= 58; - if (opGreek === 52) letter = 0o241; // varUpsilon - if (opGreek === 53) letter = 0o316; // epsilon + if (opGreek > 25) + letter -= 58; + if (opGreek === 52) + letter = 0o241; // varUpsilon + if (opGreek === 53) + letter = 0o316; // epsilon } else { // see code in TLatex.cxx, line 1323 const opSpec = cnt - 54; @@ -313,11 +322,17 @@ function remapSymbolTtfCode(code) { case 81: letter = 0o44; break; // #exists } } - const code = symbol.charCodeAt(0); - if (code > 0x80) - symbolsPdfMap[code] = letter; + const scode = symbol.charCodeAt(0); + if (scode > 0x80) + symbolsPdfMap[scode] = letter; } - if (++cnt > 54 + 82) break; + if (++cnt > 54 + 82) + break; + } + for (let k = 0; k < symbolsMap.length; ++k) { + const scode2 = symbolsMap[k]; + if (scode2) + symbolsPdfMap[scode2] = k + 33; } } return symbolsPdfMap[code] ?? code; @@ -328,6 +343,18 @@ function remapSymbolTtfCode(code) { * @desc Used in PDF generation where greek symbols are not available * @private */ function replaceSymbolsInTextNode(node) { + if (node.$text && node.$font) { + node.$originalHTML = node.innerHTML; + node.$originalFont = node.getAttribute('font-family'); + + node.innerHTML = node.$text; + if (settings.LoadSymbolTtf) + node.setAttribute('font-family', node.$font.isSymbol); + else + node.setAttribute('font-family', (node.$font.isSymbol === kWingdings) ? 'zapfdingbats' : 'symbol'); + return node.$font.isSymbol; + } + if (node.childNodes.length !== 1) return false; const txt = node.textContent; @@ -338,7 +365,7 @@ function replaceSymbolsInTextNode(node) { const code = txt.charCodeAt(i), newcode = remapSymbolTtfCode(code); if (code !== newcode) { - new_html += txt.slice(lasti+1, i) + ''+String.fromCharCode(newcode)+''; + new_html += txt.slice(lasti + 1, i) + `${String.fromCharCode(newcode)} `; lasti = i; } } @@ -346,20 +373,24 @@ function replaceSymbolsInTextNode(node) { if (lasti < 0) return false; - if (lasti < txt.length-1) - new_html += txt.slice(lasti+1, txt.length); + if (lasti < txt.length - 1) + new_html += txt.slice(lasti + 1, txt.length); node.$originalHTML = node.innerHTML; + node.$originalFont = node.getAttribute('font-family'); node.innerHTML = new_html; - return true; + return kSymbol; } -function replaceSymbols(s, kind) { - const m = (kind === 'Wingdings') ? wingdingsMap : symbolsMap; + +/** @summary Replace codes from symbols.ttf into normal font - when symbols.ttf cannot be used + * @private */ +function replaceSymbols(s, name) { + const m = name === kWingdings ? wingdingsMap : symbolsMap; let res = ''; for (let k = 0; k < s.length; ++k) { const code = s.charCodeAt(k), - new_code = (code > 32) ? m[code-33] : 0; + new_code = (code > 32) ? m[code - 33] : 0; res += String.fromCodePoint(new_code || code); } return res; @@ -371,9 +402,11 @@ function producePlainText(painter, txt_node, arg) { arg.plain = true; if (arg.simple_latex) arg.text = translateLaTeX(arg.text); // replace latex symbols - if (arg.font && arg.font.isSymbol) + if (arg.font?.isSymbol) { txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol)); - else + txt_node.property('$text', arg.text); + txt_node.property('$font', arg.font); + } else txt_node.text(arg.text); } @@ -383,17 +416,21 @@ function isPlainText(txt) { return !txt || ((txt.indexOf('#') < 0) && (txt.indexOf('{') < 0)); } -/** @ummary translate TLatex and draw inside provided g element +/** @summary translate TLatex and draw inside provided g element * @desc use together with normal elements * @private */ function parseLatex(node, arg, label, curr) { let nelements = 0; - const currG = () => { if (!curr.g) curr.g = node.append('svg:g'); return curr.g; }, + const currG = () => { + if (!curr.g) + curr.g = node.append('svg:g'); + return curr.g; + }; - shiftX = dx => { curr.x += Math.round(dx); }, + const shiftX = dx => { curr.x += Math.round(dx); }; - extendPosition = (x1, y1, x2, y2) => { + const extendPosition = (x1, y1, x2, y2) => { if (!curr.rect) curr.rect = { x1, y1, x2, y2 }; else { @@ -410,15 +447,15 @@ function parseLatex(node, arg, label, curr) { if (!curr.parent) arg.text_rect = curr.rect; - }, + }; - addSpaces = nspaces => { + const addSpaces = nspaces => { extendPosition(curr.x, curr.y, curr.x + nspaces * curr.fsize * 0.4, curr.y); shiftX(nspaces * curr.fsize * 0.4); - }, + }; /** Position pos.g node which directly attached to curr.g and uses curr.g coordinates */ - positionGNode = (pos, x, y, inside_gg) => { + const positionGNode = (pos, x, y, inside_gg) => { x = Math.round(x); y = Math.round(y); @@ -432,35 +469,37 @@ function parseLatex(node, arg, label, curr) { extendPosition(curr.x + pos.rect.x1, curr.y + pos.rect.y1, curr.x + pos.rect.x2, curr.y + pos.rect.y2); else extendPosition(pos.rect.x1, pos.rect.y1, pos.rect.x2, pos.rect.y2); - }, + }; /** Create special sub-container for elements like sqrt or braces */ - createGG = () => { + const createGG = is_a => { const gg = currG(); // this is indicator that gg element will be the only one, one can use directly main container - if ((nelements === 1) && !label && !curr.x && !curr.y) + if ((nelements === 1) && !label && !curr.x && !curr.y && !is_a) return gg; - return makeTranslate(gg.append('svg:g'), curr.x, curr.y); - }, + return makeTranslate(gg.append(is_a ? 'svg:a' : 'svg:g'), curr.x, curr.y); + }; - extractSubLabel = (check_first, lbrace, rbrace) => { + const extractSubLabel = (check_first, lbrace, rbrace) => { let pos = 0, n = 1, extra_braces = false; - if (!lbrace) lbrace = '{'; - if (!rbrace) rbrace = '}'; + if (!lbrace) + lbrace = '{'; + if (!rbrace) + rbrace = '}'; - const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos+br.length) === br); + const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos + br.length) === br); if (check_first) { if (!match(lbrace)) { console.log(`not starting with ${lbrace} in ${label}`); return -1; - } else - label = label.slice(lbrace.length); + } + label = label.slice(lbrace.length); } - while ((n !== 0) && (pos < label.length)) { + while (n && (pos < label.length)) { if (match(lbrace)) { n++; pos += lbrace.length; @@ -476,30 +515,35 @@ function parseLatex(node, arg, label, curr) { } } else pos++; } - if (n !== 0) { + if (n) { console.log(`mismatch with open ${lbrace} and closing ${rbrace} in ${label}`); return -1; } let sublabel = label.slice(0, pos - rbrace.length); - if (extra_braces) sublabel = lbrace + sublabel + rbrace; + if (extra_braces) + sublabel = lbrace + sublabel + rbrace; label = label.slice(pos); return sublabel; - }, + }; - createPath = (gg, d, dofill) => { + const createPath = (gg, d, dofill) => { return gg.append('svg:path') + .attr('d', d || 'M0,0') // provide dummy d value as placeholder, preserve order of attributes .style('stroke', dofill ? 'none' : (curr.color || arg.color)) - .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize*(curr.font.weight ? 0.1 : 0.07)))) - .style('fill', dofill ? (curr.color || arg.color) : 'none') - .attr('d', d ?? null); - }, + .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize * (curr.font.weight ? 0.1 : 0.07)))) + .style('fill', dofill ? (curr.color || arg.color) : 'none'); + }; - createSubPos = fscale => { - return { lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize*(fscale || 1), color: curr.color, font: curr.font, parent: curr, painter: curr.painter }; + const createSubPos = fscale => { + return { + lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize * (fscale || 1), + color: curr.color, font: curr.font, parent: curr, + painter: curr.painter, italic: curr.italic, bold: curr.bold + }; }; while (label) { @@ -507,7 +551,10 @@ function parseLatex(node, arg, label, curr) { for (let n = 0; n < latex_features.length; ++n) { const pos = label.indexOf(latex_features[n].name); - if ((pos >= 0) && (pos < best)) { best = pos; found = latex_features[n]; } + if ((pos >= 0) && (pos < best)) { + best = pos; + found = latex_features[n]; + } } if (best > 0) { @@ -537,7 +584,8 @@ function parseLatex(node, arg, label, curr) { const g = curr.g || (alone ? node : currG()), elem = g.append('svg:text'); - if (alone && !curr.g) curr.g = elem; + if (alone && !curr.g) + curr.g = elem; // apply font attributes only once, inherited by all other elements if (curr.ufont) { @@ -555,25 +603,28 @@ function parseLatex(node, arg, label, curr) { elem.attr('fill', curr.color || arg.color || null); // set font size directly to element to avoid complex control - if (curr.fisze !== curr.font.size) - elem.attr('font-size', Math.round(curr.fsize)); + elem.attr('font-size', Math.max(1, Math.round(curr.fsize))); - if (curr.font && curr.font.isSymbol) + if (curr.font?.isSymbol) { elem.text(replaceSymbols(s, curr.font.isSymbol)); - else + elem.property('$text', s); + elem.property('$font', curr.font); + } else elem.text(s); const rect = !isNodeJs() && !settings.ApproxTextSize && !arg.fast ? getElementRect(elem, 'nopadding') : { height: curr.fsize * 1.2, width: approximateLabelWidth(s, curr.font, curr.fsize) }; - if (curr.x) elem.attr('x', curr.x); - if (curr.y) elem.attr('y', curr.y); + if (curr.x) + elem.attr('x', curr.x); + if (curr.y) + elem.attr('y', curr.y); - // for single symbols like f,l.i one gets wrong estimation of total width, use it in sup/sub-scripts - const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1*curr.fsize : 0; + // for single symbols like f,l,i,j one gets wrong estimation of total width, use it in sup/sub-scripts + const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1 * curr.fsize : 0; - extendPosition(curr.x, curr.y - rect.height*0.8, curr.x + rect.width, curr.y + rect.height*0.2); + extendPosition(curr.x, curr.y - rect.height * 0.8, curr.x + rect.width, curr.y + rect.height * 0.2); if (!alone) { shiftX(rect.width + xgap); @@ -581,23 +632,25 @@ function parseLatex(node, arg, label, curr) { curr.xgap = 0; } else if (curr.deco) { elem.attr('text-decoration', curr.deco); - delete curr.deco; // inform that decoration was applied + curr.deco = ''; // inform that decoration was applied } else - curr.xgap = xgap; // may be used in accent or somewere else + curr.xgap = xgap; // may be used in accent or somewhere else } else addSpaces(nendspaces); } - if (!found) return true; + if (!found) + return true; - // remove preceeding block and tag itself + // remove preceding block and tag itself label = label.slice(best + found.name.length); nelements++; if (found.accent) { const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (sublabel === -1) + return false; const gg = createGG(), subpos = createSubPos(), @@ -606,8 +659,8 @@ function parseLatex(node, arg, label, curr) { parseLatex(gg, arg, sublabel, subpos); const minw = curr.fsize * 0.6, - y1 = Math.round(subpos.rect.y1*reduce), - dy2 = Math.round(curr.fsize*0.1), dy = dy2*2, + y1 = Math.round(subpos.rect.y1 * reduce), + dy2 = Math.round(curr.fsize * 0.1), dy = dy2 * 2, dot = `a${dy2},${dy2},0,0,1,${dy},0a${dy2},${dy2},0,0,1,${-dy},0z`; let xpos = 0, w = subpos.rect.width; @@ -617,21 +670,21 @@ function parseLatex(node, arg, label, curr) { xpos = (minw - subpos.rect.width) / 2; } - const w5 = Math.round(w*0.5), w3 = Math.round(w*0.3), w2 = w5-w3, w8 = w5+w3; - w = w5*2; + const w5 = Math.round(w * 0.5), w3 = Math.round(w * 0.3), w2 = w5 - w3, w8 = w5 + w3; + w = w5 * 2; positionGNode(subpos, xpos, 0, true); switch (found.name) { - case '#check{': createPath(gg, `M${w2},${y1-dy}L${w5},${y1}L${w8},${y1-dy}`); break; + case '#check{': createPath(gg, `M${w2},${y1 - dy}L${w5},${y1}L${w8},${y1 - dy}`); break; case '#acute{': createPath(gg, `M${w5},${y1}l${dy},${-dy}`); break; case '#grave{': createPath(gg, `M${w5},${y1}l${-dy},${-dy}`); break; - case '#dot{': createPath(gg, `M${w5-dy2},${y1}${dot}`, true); break; - case '#ddot{': createPath(gg, `M${w5-3*dy2},${y1}${dot} M${w5+dy2},${y1}${dot}`, true); break; + case '#dot{': createPath(gg, `M${w5 - dy2},${y1}${dot}`, true); break; + case '#ddot{': createPath(gg, `M${w5 - 3 * dy2},${y1}${dot} M${w5 + dy2},${y1}${dot}`, true); break; case '#tilde{': createPath(gg, `M${w2},${y1} a${w3},${dy},0,0,1,${w3},0 a${w3},${dy},0,0,0,${w3},0`); break; case '#slash{': createPath(gg, `M${w},${y1}L0,${Math.round(subpos.rect.y2)}`); break; - case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8-dy},${y1-dy}l${dy},${dy}l${-dy},${dy}`); break; - default: createPath(gg, `M${w2},${y1}L${w5},${y1-dy}L${w8},${y1}`); // #hat{ + case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8 - dy},${y1 - dy}l${dy},${dy}l${-dy},${dy}`); break; + default: createPath(gg, `M${w2},${y1}L${w5},${y1 - dy}L${w8},${y1}`); // #hat{ } shiftX(subpos.rect.width + (subpos.xgap ?? 0)); @@ -642,29 +695,31 @@ function parseLatex(node, arg, label, curr) { if (found.twolines) { curr.twolines = true; - const line1 = extractSubLabel(), line2 = extractSubLabel(true); - if ((line1 === -1) || (line2 === -1)) return false; + const line1 = extractSubLabel(), + line2 = extractSubLabel(true); + if ((line1 === -1) || (line2 === -1)) + return false; const gg = createGG(), - fscale = (curr.parent && curr.parent.twolines) ? 0.7 : 1, + fscale = curr.parent?.twolines ? 0.7 : 1, subpos1 = createSubPos(fscale); parseLatex(gg, arg, line1, subpos1); - const path = (found.twolines === 'line') ? createPath(gg) : null, + const path = found.twolines === 'line' ? createPath(gg) : null, subpos2 = createSubPos(fscale); parseLatex(gg, arg, line2, subpos2); const w = Math.max(subpos1.rect.width, subpos2.rect.width), dw = subpos1.rect.width - subpos2.rect.width, - dy = -curr.fsize*0.35; // approximate position of middle line + dy = -curr.fsize * 0.35; // approximate position of middle line - positionGNode(subpos1, (dw < 0 ? -dw/2 : 0), dy - subpos1.rect.y2, true); + positionGNode(subpos1, found.middle && (dw < 0) ? -dw / 2 : 0, dy - subpos1.rect.y2, true); - positionGNode(subpos2, (dw > 0 ? dw/2 : 0), dy - subpos2.rect.y1, true); + positionGNode(subpos2, found.middle && (dw > 0) ? dw / 2 : 0, dy - subpos2.rect.y1, true); - if (path) path.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize*0.1)}`); + path?.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize * 0.1)}`); shiftX(w); @@ -675,23 +730,25 @@ function parseLatex(node, arg, label, curr) { const extractLowUp = name => { const res = {}; + if (name) { label = '{' + label; res[name] = extractSubLabel(name === 'low' ? '_' : '^'); - if (res[name] === -1) return false; + if (res[name] === -1) + return false; } while (label) { - if (label[0] === '_') { + if ((label[0] === '_') && !res.low) { label = label.slice(1); - res.low = !res.low ? extractSubLabel('_') : -1; + res.low = extractSubLabel('_'); if (res.low === -1) { - console.log(`error with ${found.name} low limit`); + console.log(`error with ${found.name} low limit ${label}`); return false; } - } else if (label[0] === '^') { + } else if ((label[0] === '^') && !res.up) { label = label.slice(1); - res.up = !res.up ? extractSubLabel('^') : -1; + res.up = extractSubLabel('^'); if (res.up === -1) { console.log(`error with ${found.name} upper limit ${label}`); return false; @@ -703,10 +760,10 @@ function parseLatex(node, arg, label, curr) { if (found.low_up) { const subs = extractLowUp(found.low_up); - if (!subs) return false; - - const x = curr.x, dx = 0.03*curr.fsize, ylow = 0.25*curr.fsize; + if (!subs) + return false; + const x = curr.x, dx = 0.03 * curr.fsize, ylow = 0.25 * curr.fsize; let pos_up, pos_low, w1 = 0, w2 = 0, yup = -curr.fsize; if (subs.up) { @@ -720,13 +777,14 @@ function parseLatex(node, arg, label, curr) { } if (pos_up) { - if (!pos_low) yup = Math.min(yup, curr.rect.last_y1); - positionGNode(pos_up, x+dx, yup - pos_up.rect.y1 - curr.fsize*0.1); + if (!pos_low && curr.rect) + yup = Math.min(yup, curr.rect.last_y1); + positionGNode(pos_up, x + dx, yup - pos_up.rect.y1 - curr.fsize * 0.1); w1 = pos_up.rect.width; } if (pos_low) { - positionGNode(pos_low, x+dx, ylow - pos_low.rect.y2 + curr.fsize*0.1); + positionGNode(pos_low, x + dx, ylow - pos_low.rect.y2 + curr.fsize * 0.1); w2 = pos_low.rect.width; } @@ -740,30 +798,31 @@ function parseLatex(node, arg, label, curr) { const subs = extractLowUp() || {}, gg = createGG(), path = createPath(gg), - h = Math.round(curr.fsize*1.7), w = Math.round(curr.fsize), r = Math.round(h*0.1); - let x_up, x_low; + h = Math.round(curr.fsize * 1.7), w = Math.round(curr.fsize), r = Math.round(h * 0.1); + let x_up, x_low; if (found.name === '#sum') { - x_up = x_low = w/2; - path.attr('d', `M${w},${Math.round(-0.75*h)}h${-w}l${Math.round(0.4*w)},${Math.round(0.3*h)}l${Math.round(-0.4*w)},${Math.round(0.7*h)}h${w}`); + x_up = x_low = w / 2; + path.attr('d', `M${w},${Math.round(-0.75 * h)}h${-w}l${Math.round(0.4 * w)},${Math.round(0.3 * h)}l${Math.round(-0.4 * w)},${Math.round(0.7 * h)}h${w}`); } else { - x_up = 3*r; x_low = r; - path.attr('d', `M0,${Math.round(0.25*h-r)}a${r},${r},0,0,0,${2*r},0v${2*r-h}a${r},${r},0,1,1,${2*r},0`); + x_up = 3 * r; + x_low = r; + path.attr('d', `M0,${Math.round(0.25 * h - r)}a${r},${r},0,0,0,${2 * r},0v${2 * r - h}a${r},${r},0,1,1,${2 * r},0`); // path.attr('transform','skewX(-3)'); could use skewX for italic-like style } - extendPosition(curr.x, curr.y - 0.6*h, curr.x + w, curr.y + 0.4*h); + extendPosition(curr.x, curr.y - 0.6 * h, curr.x + w, curr.y + 0.4 * h); if (subs.low) { const subpos1 = createSubPos(0.6); parseLatex(gg, arg, subs.low, subpos1); - positionGNode(subpos1, (x_low - subpos1.rect.width/2), 0.25*h - subpos1.rect.y1, true); + positionGNode(subpos1, (x_low - subpos1.rect.width / 2), 0.25 * h - subpos1.rect.y1, true); } if (subs.up) { const subpos2 = createSubPos(0.6); parseLatex(gg, arg, subs.up, subpos2); - positionGNode(subpos2, (x_up - subpos2.rect.width/2), -0.75*h - subpos2.rect.y2, true); + positionGNode(subpos2, (x_up - subpos2.rect.width / 2), -0.75 * h - subpos2.rect.y2, true); } shiftX(w); @@ -782,33 +841,33 @@ function parseLatex(node, arg, label, curr) { parseLatex(gg, arg, sublabel, subpos); const path2 = createPath(gg), - w = Math.max(2, Math.round(curr.fsize*0.2)), + w = Math.max(2, Math.round(curr.fsize * 0.2)), r = subpos.rect, dy = Math.round(r.y2 - r.y1), r_y1 = Math.round(r.y1), r_width = Math.round(r.width); switch (found.braces) { case '||': path1.attr('d', `M${w},${r_y1}v${dy}`); - path2.attr('d', `M${3*w+r_width},${r_y1}v${dy}`); + path2.attr('d', `M${3 * w + r_width},${r_y1}v${dy}`); break; case '[]': - path1.attr('d', `M${2*w},${r_y1}h${-w}v${dy}h${w}`); - path2.attr('d', `M${2*w+r_width},${r_y1}h${w}v${dy}h${-w}`); + path1.attr('d', `M${2 * w},${r_y1}h${-w}v${dy}h${w}`); + path2.attr('d', `M${2 * w + r_width},${r_y1}h${w}v${dy}h${-w}`); break; case '{}': - path1.attr('d', `M${2*w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy/2-2*w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy/2-2*w}a${w},${w},0,0,0,${w},${w}`); - path2.attr('d', `M${2*w+r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy/2-2*w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy/2-2*w}a${w},${w},0,0,1,${-w},${w}`); + path1.attr('d', `M${2 * w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}`); + path2.attr('d', `M${2 * w + r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}`); break; default: // () - path1.attr('d', `M${w},${r_y1}a${4*dy},${4*dy},0,0,0,0,${dy}`); - path2.attr('d', `M${3*w+r_width},${r_y1}a${4*dy},${4*dy},0,0,1,0,${dy}`); + path1.attr('d', `M${w},${r_y1}a${4 * dy},${4 * dy},0,0,0,0,${dy}`); + path2.attr('d', `M${3 * w + r_width},${r_y1}a${4 * dy},${4 * dy},0,0,1,0,${dy}`); } - positionGNode(subpos, 2*w, 0, true); + positionGNode(subpos, 2 * w, 0, true); - extendPosition(curr.x, curr.y + r.y1, curr.x + 4*w + r.width, curr.y + r.y2); + extendPosition(curr.x, curr.y + r.y1, curr.x + 4 * w + r.width, curr.y + r.y2); - shiftX(4*w + r.width); + shiftX(4 * w + r.width); continue; } @@ -823,32 +882,31 @@ function parseLatex(node, arg, label, curr) { parseLatex(gg, arg, sublabel, subpos); const r = subpos.rect; - if (subpos.deco) { - const path = createPath(gg), r_width = Math.round(r.width); - switch (subpos.deco) { - case 'underline': path.attr('d', `M0,${Math.round(r.y2)}h${r_width}`); break; - case 'overline': path.attr('d', `M0,${Math.round(r.y1)}h${r_width}`); break; - case 'line-through': path.attr('d', `M0,${Math.round(0.45*r.y1+0.55*r.y2)}h${r_width}`); break; - } + switch (subpos.deco) { + case 'underline': + createPath(gg, `M0,${Math.round(r.y2)}h${Math.round(r.width)}`); + break; + case 'overline': + createPath(gg, `M0,${Math.round(r.y1)}h${Math.round(r.width)}`); + break; + case 'line-through': + createPath(gg, `M0,${Math.round(0.45 * r.y1 + 0.55 * r.y2)}h${Math.round(r.width)}`); + break; } positionGNode(subpos, 0, 0, true); - shiftX(r.width); - continue; } - if (found.name === '#bf{' || found.name === '#it{') { + if (found.bi) { // bold or italic const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (sublabel === -1) + return false; const subpos = createSubPos(); - if (found.name === '#bf{') - subpos.bold = !subpos.bold; - else - subpos.italic = !subpos.italic; + subpos[found.bi] = !subpos[found.bi]; parseLatex(currG(), arg, sublabel, subpos); @@ -863,46 +921,88 @@ function parseLatex(node, arg, label, curr) { if (found.arg) { const pos = label.indexOf(']{'); - if (pos < 0) { console.log('missing argument for ', found.name); return false; } + if (pos < 0) { + console.log('missing argument for ', found.name); + return false; + } foundarg = label.slice(0, pos); if (found.arg === 'int') { foundarg = parseInt(foundarg); - if (!Number.isInteger(foundarg)) { console.log('wrong int argument', label.slice(0, pos)); return false; } + if (!Number.isInteger(foundarg)) { + console.log('wrong int argument', label.slice(0, pos)); + return false; + } } else if (found.arg === 'float') { foundarg = parseFloat(foundarg); - if (!Number.isFinite(foundarg)) { console.log('wrong float argument', label.slice(0, pos)); return false; } + if (!Number.isFinite(foundarg)) { + console.log('wrong float argument', label.slice(0, pos)); + return false; + } } label = label.slice(pos + 2); } - if ((found.name === '#kern[') || (found.name === '#lower[')) { + if (found.shift) { const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (sublabel === -1) + return false; const subpos = createSubPos(); parseLatex(currG(), arg, sublabel, subpos); let shiftx = 0, shifty = 0; - if (found.name === 'kern[') shiftx = foundarg; else shifty = foundarg; + if (found.shift === 'x') + shiftx = foundarg * subpos.rect.width; + else + shifty = foundarg * subpos.rect.height; - positionGNode(subpos, curr.x + shiftx * subpos.rect.width, curr.y + shifty * subpos.rect.height); + positionGNode(subpos, curr.x + shiftx, curr.y + shifty); shiftX(subpos.rect.width * (shiftx > 0 ? 1 + foundarg : 1)); continue; } + if (found.name === '#url[') { + const sublabel = extractSubLabel(); + if (sublabel === -1) + return false; + + const gg = createGG(true), + subpos = createSubPos(); + + gg.attr('href', foundarg); + if (!isBatchMode() && !curr.painter?.isBatchMode()) { + gg.on('mouseenter', () => gg.style('text-decoration', 'underline')) + .on('mouseleave', () => gg.style('text-decoration', null)) + .append('svg:title').text(`link on ${foundarg}`); + } + + parseLatex(gg, arg, sublabel, subpos); + + positionGNode(subpos, 0, 0, true); + shiftX(subpos.rect.width); + continue; + } + if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) { const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (sublabel === -1) + return false; const subpos = createSubPos(); if (found.name === '#color[') subpos.color = curr.painter.getColor(foundarg); else if (found.name === '#font[') { - subpos.font = new FontHandler(foundarg); + subpos.font = new FontHandler(foundarg, subpos.fsize); + // here symbols embedding not works, use replacement + if ((subpos.font.name === kSymbol) && !subpos.font.isSymbol) { + subpos.font.isSymbol = kSymbol; + subpos.font.name = kTimes; + } + subpos.font.setUseFullStyle(true); // while embedding - need to enforce full style subpos.ufont = true; // mark that custom font is applied } else subpos.fsize *= foundarg; @@ -916,9 +1016,10 @@ function parseLatex(node, arg, label, curr) { continue; } - if (found.sqrt) { + if (found.sqrt) { const sublabel = extractSubLabel(); - if (sublabel === -1) return false; + if (sublabel === -1) + return false; const gg = createGG(), subpos = createSubPos(); let subpos0; @@ -935,29 +1036,29 @@ function parseLatex(node, arg, label, curr) { const r = subpos.rect, h = Math.round(r.height), - h1 = Math.round(r.height*0.1), - w = Math.round(r.width), midy = Math.round((r.y1 + r.y2)/2), - f2 = Math.round(curr.fsize*0.2), r_y2 = Math.round(r.y2); + h1 = Math.round(r.height * 0.1), + w = Math.round(r.width), midy = Math.round((r.y1 + r.y2) / 2), + f2 = Math.round(curr.fsize * 0.2), r_y2 = Math.round(r.y2); if (subpos0) - positionGNode(subpos0, 0, midy - subpos0.fsize*0.3, true); + positionGNode(subpos0, 0, midy - subpos0.fsize * 0.3, true); - path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2-midy-f2}l${h1},${-h+f2}h${Math.round(h*0.2+w)}v${h1}`); + path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2 - midy - f2}l${h1},${f2 - h}h${Math.round(h * 0.2 + w)}v${h1}`); - positionGNode(subpos, h*0.4, 0, true); + positionGNode(subpos, h * 0.4, 0, true); - extendPosition(curr.x, curr.y + r.y1-curr.fsize*0.1, curr.x + w + h*0.6, curr.y + r.y2); + extendPosition(curr.x, curr.y + r.y1 - curr.fsize * 0.1, curr.x + w + h * 0.6, curr.y + r.y2); - shiftX(w + h*0.6); + shiftX(w + h * 0.6); continue; - } + } } return true; } -/** @ummary translate TLatex and draw inside provided g element +/** @summary translate TLatex and draw inside provided g element * @desc use together with normal elements * @private */ function produceLatex(painter, node, arg) { @@ -976,26 +1077,33 @@ async function loadMathjax() { if (!loading && (typeof globalThis.MathJax !== 'undefined')) return globalThis.MathJax; - if (!loading) _mj_loading = []; + if (!loading) + _mj_loading = []; - const promise = new Promise(resolve => { _mj_loading ? _mj_loading.push(resolve) : resolve(globalThis.MathJax); }); + const promise = new Promise(resolve => { + if (_mj_loading) + _mj_loading.push(resolve); + else + resolve(globalThis.MathJax); + }); - if (loading) return promise; + if (loading) + return promise; const svg = { - scale: 1, // global scaling factor for all expressions - minScale: 0.5, // smallest scaling factor to use - mtextInheritFont: false, // true to make mtext elements use surrounding font - merrorInheritFont: true, // true to make merror text use surrounding font - mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules - skipAttributes: {}, // RFDa and other attributes NOT to copy to the output - exFactor: 0.5, // default size of ex in em units - displayAlign: 'center', // default for indentalign when set to 'auto' - displayIndent: '0', // default for indentshift when set to 'auto' - fontCache: 'local', // or 'global' or 'none' - localID: null, // ID to use for local font cache (for single equation processing) - internalSpeechTitles: true, // insert tags with speech content - titleID: 0 // initial id number to use for aria-labeledby titles + scale: 1, // global scaling factor for all expressions + minScale: 0.5, // smallest scaling factor to use + mtextInheritFont: false, // true to make mtext elements use surrounding font + merrorInheritFont: true, // true to make merror text use surrounding font + mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules + skipAttributes: {}, // RFDa and other attributes NOT to copy to the output + exFactor: 0.5, // default size of ex in em units + displayAlign: 'center', // default for indentalign when set to 'auto' + displayIndent: '0', // default for indentshift when set to 'auto' + fontCache: 'local', // or 'global' or 'none' + localID: null, // ID to use for local font cache (for single equation processing) + internalSpeechTitles: true, // insert <title> tags with speech content + titleID: 0 // initial id number to use for aria-labeledby titles }; if (!isNodeJs()) { @@ -1012,7 +1120,6 @@ async function loadMathjax() { svg, startup: { ready() { - // eslint-disable-next-line no-undef MathJax.startup.defaultReady(); const arr = _mj_loading; _mj_loading = undefined; @@ -1035,35 +1142,34 @@ async function loadMathjax() { return _loadJSDOM().then(handle => { JSDOM = handle.JSDOM; return import('mathjax'); - }).then(mj => { + }).then(mj0 => { // return Promise with mathjax loading - mj.init({ + mj0.init({ loader: { load: ['input/tex', 'output/svg', '[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - config: { - JSDOM - }, - startup: { - typeset: false, - ready() { - // eslint-disable-next-line no-undef - const mj = MathJax; - - mj.startup.registerConstructor('jsdomAdaptor', () => { - return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); - }); - mj.startup.useAdaptor('jsdomAdaptor', true); - mj.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(mj)); - } - } + }, + tex: { + packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } + }, + svg, + config: { + JSDOM + }, + startup: { + typeset: false, + ready() { + const mj = MathJax; + + mj.startup.registerConstructor('jsdomAdaptor', () => { + return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); + }); + mj.startup.useAdaptor('jsdomAdaptor', true); + mj.startup.defaultReady(); + const arr = _mj_loading; + _mj_loading = undefined; + arr.forEach(func => func(mj)); + } + } }); return promise; @@ -1071,76 +1177,76 @@ async function loadMathjax() { } const math_symbols_map = { - '#LT': '\\langle', - '#GT': '\\rangle', - '#club': '\\clubsuit', - '#spade': '\\spadesuit', - '#heart': '\\heartsuit', - '#diamond': '\\diamondsuit', - '#voidn': '\\wp', - '#voidb': 'f', - '#copyright': '(c)', - '#ocopyright': '(c)', - '#trademark': 'TM', - '#void3': 'TM', - '#oright': 'R', - '#void1': 'R', - '#3dots': '\\ldots', - '#lbar': '\\mid', - '#void8': '\\mid', - '#divide': '\\div', - '#Jgothic': '\\Im', - '#Rgothic': '\\Re', - '#doublequote': '"', - '#plus': '+', - '#minus': '-', - '#/': '/', - '#upoint': '.', - '#aa': '\\mathring{a}', - '#AA': '\\mathring{A}', - '#omicron': 'o', - '#Alpha': 'A', - '#Beta': 'B', - '#Epsilon': 'E', - '#Zeta': 'Z', - '#Eta': 'H', - '#Iota': 'I', - '#Kappa': 'K', - '#Mu': 'M', - '#Nu': 'N', - '#Omicron': 'O', - '#Rho': 'P', - '#Tau': 'T', - '#Chi': 'X', - '#varomega': '\\varpi', - '#corner': '?', - '#ltbar': '?', - '#bottombar': '?', - '#notsubset': '?', - '#arcbottom': '?', - '#cbar': '?', - '#arctop': '?', - '#topbar': '?', - '#arcbar': '?', - '#downleftarrow': '?', - '#splitline': '\\genfrac{}{}{0pt}{}', - '#it': '\\textit', - '#bf': '\\textbf', - '#frac': '\\frac', - '#left{': '\\lbrace', - '#right}': '\\rbrace', - '#left\\[': '\\lbrack', - '#right\\]': '\\rbrack', - '#\\[\\]{': '\\lbrack', - ' } ': '\\rbrack', - '#\\[': '\\lbrack', - '#\\]': '\\rbrack', - '#{': '\\lbrace', - '#}': '\\rbrace', - ' ': '\\;' -}, - -mathjax_remap = { + '#LT': '\\langle', + '#GT': '\\rangle', + '#club': '\\clubsuit', + '#spade': '\\spadesuit', + '#heart': '\\heartsuit', + '#diamond': '\\diamondsuit', + '#voidn': '\\wp', + '#voidb': 'f', + '#copyright': '(c)', + '#ocopyright': '(c)', + '#trademark': 'TM', + '#void3': 'TM', + '#oright': 'R', + '#void1': 'R', + '#3dots': '\\ldots', + '#lbar': '\\mid', + '#void8': '\\mid', + '#divide': '\\div', + '#Jgothic': '\\Im', + '#Rgothic': '\\Re', + '#doublequote': '"', + '#plus': '+', + '#minus': '-', + '#/': '/', + '#upoint': '.', + '#aa': '\\mathring{a}', + '#AA': '\\mathring{A}', + '#omicron': 'o', + '#Alpha': 'A', + '#Beta': 'B', + '#Epsilon': 'E', + '#Zeta': 'Z', + '#Eta': 'H', + '#Iota': 'I', + '#Kappa': 'K', + '#Mu': 'M', + '#Nu': 'N', + '#Omicron': 'O', + '#Rho': 'P', + '#Tau': 'T', + '#Chi': 'X', + '#varomega': '\\varpi', + '#corner': '?', + '#ltbar': '?', + '#bottombar': '?', + '#notsubset': '?', + '#arcbottom': '?', + '#cbar': '?', + '#arctop': '?', + '#topbar': '?', + '#arcbar': '?', + '#downleftarrow': '?', + '#splitline': '\\genfrac{}{}{0pt}{}', + '#it': '\\textit', + '#bf': '\\textbf', + '#frac': '\\frac', + '#left{': '\\lbrace', + '#right}': '\\rbrace', + '#left\\[': '\\lbrack', + '#right\\]': '\\rbrack', + '#\\[\\]{': '\\lbrack', + ' } ': '\\rbrack', + '#\\[': '\\lbrack', + '#\\]': '\\rbrack', + '#{': '\\lbrace', + '#}': '\\rbrace', + ' ': '\\;' +}; + +const mathjax_remap = { upDelta: 'Updelta', upGamma: 'Upgamma', upLambda: 'Uplambda', @@ -1168,9 +1274,9 @@ mathjax_remap = { iiintop: 'iiint', iintop: 'iint', ointop: 'oint' -}, +}; -mathjax_unicode = { +const mathjax_unicode = { Digamma: 0x3DC, upDigamma: 0x3DC, digamma: 0x3DD, @@ -1266,9 +1372,9 @@ mathjax_unicode = { oiint: 0x222F, slashintop: 0x2A0F, slashint: 0x2A0F -}, +}; -mathjax_asis = ['"', '\'', '`', '=', '~']; +const mathjax_asis = ['"', '\'', '`', '=', '~']; /** @summary Function translates ROOT TLatex into MathJax format * @private */ @@ -1282,23 +1388,29 @@ function translateMath(str, kind, color, painter) { str = str.replace(new RegExp(x, 'g'), '\\' + x.slice(1)); } - // replace all #color[]{} occurances + // replace all #color[]{} occurrences let clean = '', first = true; while (str) { let p = str.indexOf('#color['); - if ((p < 0) && first) { clean = str; break; } + if ((p < 0) && first) { + clean = str; + break; + } first = false; - if (p !== 0) { + if (p) { const norm = (p < 0) ? str : str.slice(0, p); clean += norm; - if (p < 0) break; + if (p < 0) + break; } str = str.slice(p + 7); p = str.indexOf(']{'); - if (p <= 0) break; + if (p <= 0) + break; const colindx = parseInt(str.slice(0, p)); - if (!Number.isInteger(colindx)) break; + if (!Number.isInteger(colindx)) + break; const col = painter.getColor(colindx); let cnt = 1; str = str.slice(p + 2); @@ -1309,7 +1421,8 @@ function translateMath(str, kind, color, painter) { else if (str[p] === '}') cnt--; } - if (cnt !== 0) break; + if (cnt) + break; const part = str.slice(0, p); str = str.slice(p + 1); @@ -1319,8 +1432,10 @@ function translateMath(str, kind, color, painter) { str = clean; } else { - if (str === '\\^') str = '\\unicode{0x5E}'; - if (str === '\\vec') str = '\\unicode{0x2192}'; + if (str === '\\^') + str = '\\unicode{0x5E}'; + if (str === '\\vec') + str = '\\unicode{0x2192}'; str = str.replace(/\\\./g, '\\unicode{0x2E}').replace(/\\\^/g, '\\hat'); for (const x in mathjax_unicode) str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\unicode{0x${mathjax_unicode[x].toString(16)}}`); @@ -1331,7 +1446,8 @@ function translateMath(str, kind, color, painter) { str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\${mathjax_remap[x]}`); } - if (!isStr(color)) return str; + if (!isStr(color)) + return str; // MathJax SVG converter use colors in normal form // if (color.indexOf('rgb(') >= 0) @@ -1345,9 +1461,11 @@ function translateMath(str, kind, color, painter) { * @private */ function repairMathJaxSvgSize(painter, mj_node, svg, arg) { const transform = value => { - if (!value || !isStr(value) || (value.length < 3)) return null; + if (!value || !isStr(value) || (value.length < 3)) + return null; const p = value.indexOf('ex'); - if ((p < 0) || (p !== value.length - 2)) return null; + if ((p < 0) || (p !== value.length - 2)) + return null; value = parseFloat(value.slice(0, p)); return Number.isFinite(value) ? value * arg.font.size * 0.5 : null; }; @@ -1386,10 +1504,18 @@ function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_fac let mw = parseInt(svg.attr('width')), mh = parseInt(svg.attr('height')); + if (isNodeJs()) { + // workaround for NaN in viewBox produced by MathJax + const vb = svg.attr('viewBox'); + if (isStr(vb) && vb.indexOf('NaN') > 0) + svg.attr('viewBox', vb.replaceAll('NaN', '600')); + // console.log('Problematic viewBox', vb, svg.select('text').node()?.innerHTML); + } + if (Number.isInteger(mh) && Number.isInteger(mw)) { if (svg_factor > 0) { - mw = mw / svg_factor; - mh = mh / svg_factor; + mw /= svg_factor; + mh /= svg_factor; svg.attr('width', Math.round(mw)).attr('height', Math.round(mh)); } } else { @@ -1398,9 +1524,11 @@ function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_fac mh = box.height || mh || 10; } - if ((svg_factor > 0) && arg.valign) arg.valign = arg.valign / svg_factor; + if ((svg_factor > 0) && arg.valign) + arg.valign /= svg_factor; - if (arg.valign === null) arg.valign = (font_size - mh) / 2; + if (arg.valign === null) + arg.valign = (font_size - mh) / 2; const sign = { x: 1, y: 1 }; let nx = 'x', ny = 'y'; @@ -1409,7 +1537,8 @@ function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_fac else if ((arg.rotate === 270) || (arg.rotate === 90)) { sign.x = (arg.rotate === 270) ? -1 : 1; sign.y = -sign.x; - nx = 'y'; ny = 'x'; // replace names to which align applied + nx = 'y'; + ny = 'x'; // replace names to which align applied } if (arg.align[0] === 'middle') @@ -1425,10 +1554,8 @@ function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_fac arg[ny] += sign.y * (arg.height - mh - arg.valign); let trans = makeTranslate(arg.x, arg.y) || ''; - if (arg.rotate) { - if (trans) trans += ' '; - trans += `rotate(${arg.rotate})`; - } + if (arg.rotate) + trans += `${trans ? ' ' : ''}rotate(${arg.rotate})`; mj_node.attr('transform', trans || null).attr('visibility', null); } @@ -1437,21 +1564,21 @@ function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_fac * @private */ async function produceMathjax(painter, mj_node, arg) { const mtext = translateMath(arg.text, arg.latex, arg.color, painter), - options = { em: arg.font.size, ex: arg.font.size/2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; + options = { em: arg.font.size, ex: arg.font.size / 2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; return loadMathjax() .then(mj => mj.tex2svgPromise(mtext, options)) .then(elem => { - // when adding element to new node, it will be removed from original parent - const svg = elem.querySelector('svg'); + // when adding element to new node, it will be removed from original parent + const svg = elem.querySelector('svg'); - mj_node.append(() => svg); + mj_node.append(() => svg); - repairMathJaxSvgSize(painter, mj_node, svg, arg); + repairMathJaxSvgSize(painter, mj_node, svg, arg); - arg.applyAttributesToMathJax = applyAttributesToMathJax; - return true; - }); + arg.mj_func = applyAttributesToMathJax; + return true; + }); } /** @summary Just typeset HTML node with MathJax diff --git a/modules/base/latex3d.mjs b/modules/base/latex3d.mjs new file mode 100644 index 000000000..4ef61cfcf --- /dev/null +++ b/modules/base/latex3d.mjs @@ -0,0 +1,277 @@ +import { isStr, clTLatex } from '../core.mjs'; +import { THREE, getMaterialArgs, getHelveticaFont } from './base3d.mjs'; +import { isPlainText, translateLaTeX, produceLatex } from './latex.mjs'; +import { ObjectPainter } from './ObjectPainter.mjs'; + +class TextParseWrapper { + + constructor(kind, parent, font_size) { + this.kind = kind ?? 'g'; + this.childs = []; + this.x = 0; + this.y = 0; + this.font_size = parent?.font_size ?? font_size; + this.stroke_width = parent?.stroke_width ?? 5; + parent?.childs.push(this); + } + + append(kind) { + if (kind === 'svg:g') + return new TextParseWrapper('g', this); + if (kind === 'svg:text') + return new TextParseWrapper('text', this); + if (kind === 'svg:path') + return new TextParseWrapper('path', this); + console.warn('missing handle for svg', kind); + } + + style(name, value) { + if ((name === 'stroke-width') && value) + this.stroke_width = Number.parseInt(value); + return this; + } + + property(name, value) { + if (value === undefined) + return this[name]; + this[name] = value; + return this; + } + + attr(name, value) { + const get = () => { + if (!value) + return ''; + const res = value[0]; + value = value.slice(1); + return res; + }, getN = skip => { + let p = 0; + while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) + p++; + const res = Number.parseInt(value.slice(0, p)); + value = value.slice(p); + if (skip) + get(); + return res; + }; + + if ((name === 'font-size') && value) + this.font_size = Number.parseInt(value); + else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { + const arr = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')')).split(','); + this.x += arr[0] ? Number.parseInt(arr[0]) * 0.01 : 0; + this.y -= arr[1] ? Number.parseInt(arr[1]) * 0.01 : 0; + } else if ((name === 'x') && (this.kind === 'text')) + this.x += Number.parseInt(value) * 0.01; + else if ((name === 'y') && (this.kind === 'text')) + this.y -= Number.parseInt(value) * 0.01; + else if ((name === 'fill') && (this.kind === 'text')) + this.fill = value; + else if ((name === 'd') && (this.kind === 'path') && (value !== 'M0,0')) { + if (get() !== 'M') + return console.error('Not starts with M'); + let x1 = getN(true), y1 = getN(), next; + const pnts = [], add_line = (x2, y2) => { + const angle = Math.atan2(y2 - y1, x2 - x1), + dx = 0.5 * this.stroke_width * Math.sin(angle), + dy = -0.5 * this.stroke_width * Math.cos(angle); + // front side + pnts.push(x1 - dx, y1 - dy, 0, x2 - dx, y2 - dy, 0, x2 + dx, y2 + dy, 0, x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x1 + dx, y1 + dy, 0); + // back side + pnts.push(x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x2 - dx, y2 - dy, 0, x1 - dx, y1 - dy, 0, x1 + dx, y1 + dy, 0, x2 + dx, y2 + dy, 0); + x1 = x2; + y1 = y2; + }; + + while ((next = get())) { + switch (next) { + case 'L': + add_line(getN(true), getN()); + continue; + case 'l': + add_line(x1 + getN(true), y1 + getN()); + continue; + case 'H': + add_line(getN(), y1); + continue; + case 'h': + add_line(x1 + getN(), y1); + continue; + case 'V': + add_line(x1, getN()); + continue; + case 'v': + add_line(x1, y1 + getN()); + continue; + case 'a': { + const rx = getN(true), ry = getN(true), + angle = getN(true) / 180 * Math.PI, flag1 = getN(true); + getN(true); // skip unused flag2 + const x2 = x1 + getN(true), + y2 = y1 + getN(), + x0 = x1 + rx * Math.cos(angle), + y0 = y1 + ry * Math.sin(angle); + let angle2 = Math.atan2(y0 - y2, x0 - x2); + if (flag1 && (angle2 < angle)) + angle2 += 2 * Math.PI; + else if (!flag1 && (angle2 > angle)) + angle2 -= 2 * Math.PI; + + for (let cnt = 0; cnt < 10; ++cnt) { + const a = angle + (angle2 - angle) / 10 * (cnt + 1); + add_line(x0 - rx * Math.cos(a), y0 - ry * Math.sin(a)); + } + continue; + } + default: + console.log('not supported path operator', next); + } + } + + if (pnts.length) { + const pos = new Float32Array(pnts); + this.geom = new THREE.BufferGeometry(); + this.geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + this.geom.scale(0.01, -0.01, 0.01); + this.geom.computeVertexNormals(); + } + } + return this; + } + + text(v) { + if (this.kind === 'text') + this._text = v; + } + + collect(geoms, geom_args, as_array) { + if (this._text) { + geom_args.size = Math.round(0.01 * this.font_size); + const geom = new THREE.TextGeometry(this._text, geom_args); + if (as_array) { + // this is latex parsing + // while three.js uses full height, make it more like normal fonts + geom.scale(1, 0.9, 1); + geom.translate(0, 0.0005 * this.font_size, 0); + } + geom.translate(this.x, this.y, 0); + geom._fill = this.fill; + geoms.push(geom); + } + if (this.geom) { + this.geom.translate(this.x, this.y, 0); + this.geom._fill = this.fill; + geoms.push(this.geom); + } + + this.childs.forEach(chld => { + chld.x += this.x; + chld.y += this.y; + chld.collect(geoms, geom_args, as_array); + }); + } + +} // class TextParseWrapper + + +function createLatexGeometry(painter, lbl, size, as_array, use_latex = true) { + const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }, + font_size = size * 100, + node = new TextParseWrapper('g', null, font_size), + arg = { font_size, latex: use_latex ? 1 : 0, x: 0, y: 0, text: lbl, align: ['start', 'top'], fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }, + geoms = []; + + if (THREE.REVISION > 162) + geom_args.depth = 0; + else + geom_args.height = 0; + + if (!isPlainText(lbl)) { + produceLatex(painter, node, arg); + node.collect(geoms, geom_args, as_array); + } + + if (!geoms.length) { + geom_args.size = size; + const res = new THREE.TextGeometry(translateLaTeX(lbl), geom_args); + return as_array ? [res] : res; + } + + if (as_array) + return geoms; + + if (geoms.length === 1) + return geoms[0]; + + let total_size = 0; + geoms.forEach(geom => { total_size += geom.getAttribute('position').array.length; }); + + const pos = new Float32Array(total_size), + norm = new Float32Array(total_size); + let indx = 0; + + geoms.forEach(geom => { + const p1 = geom.getAttribute('position').array, + n1 = geom.getAttribute('normal').array; + for (let i = 0; i < p1.length; ++i, ++indx) { + pos[indx] = p1[i]; + norm[indx] = n1[i]; + } + }); + + const fullgeom = new THREE.BufferGeometry(); + fullgeom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + fullgeom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); + return fullgeom; +} + + +/** @summary Build three.js object for the TLatex + * @private */ +function build3dlatex(obj, opt, painter, fp) { + if (!painter) + painter = new ObjectPainter(null, obj, opt); + const handle = painter.createAttText({ attr: obj }), + valign = handle.align % 10, + halign = (handle.align - valign) / 10, + text_size = handle.size > 1 ? handle.size : 2 * handle.size * (fp?.size_z3d || 100), + arr3d = createLatexGeometry(painter, obj.fTitle, text_size || 10, true, fp || (obj._typename === clTLatex)), + bb = new THREE.Box3().makeEmpty(); + + arr3d.forEach(geom => { + geom.computeBoundingBox(); + bb.expandByPoint(geom.boundingBox.max); + bb.expandByPoint(geom.boundingBox.min); + }); + + let dx = 0, dy = 0; + if (halign === 2) + dx = 0.5 * (bb.max.x + bb.min.x); + else if (halign === 3) + dx = bb.max.x; + + if (valign === 2) + dy = 0.5 * (bb.max.y + bb.min.y); + else if (valign === 3) + dy = bb.max.y; + + const obj3d = new THREE.Object3D(), + materials = [], + getMaterial = color => { + if (!color) + color = 'black'; + if (!materials[color]) + materials[color] = new THREE.MeshBasicMaterial(getMaterialArgs(color, { vertexColors: false })); + return materials[color]; + }; + + arr3d.forEach(geom => { + geom.translate(-dx, -dy, 0); + obj3d.add(new THREE.Mesh(geom, getMaterial(geom._fill || handle.color))); + }); + + return arr3d.length === 1 ? obj3d.children[0] : obj3d; +} + +export { createLatexGeometry, build3dlatex }; diff --git a/modules/base/lzma.mjs b/modules/base/lzma.mjs index aebf6e5fb..85db16942 100644 --- a/modules/base/lzma.mjs +++ b/modules/base/lzma.mjs @@ -121,7 +121,7 @@ function $CopyBlock(this$static, distance, len) { if (pos < 0) pos += this$static._windowSize; - for (; len !== 0; --len) { + for (; len; --len) { if (pos >= this$static._windowSize) pos = 0; @@ -485,7 +485,7 @@ function $BitTreeDecoder(this$static, numBitLevels) { function $Decode_0(this$static, rangeDecoder) { let m = 1; - for (let bitIndex = this$static.NumBitLevels; bitIndex !== 0; --bitIndex) + for (let bitIndex = this$static.NumBitLevels; bitIndex; --bitIndex) m = (m << 1) + $DecodeBit(rangeDecoder, this$static.Models, m); return m - (1 << this$static.NumBitLevels); @@ -538,7 +538,7 @@ function $DecodeBit(this$static, probs, index) { function $DecodeDirectBits(this$static, numTotalBits) { let i, t, result = 0; - for (i = numTotalBits; i !== 0; --i) { + for (i = numTotalBits; i; --i) { this$static.Range >>>= 1; t = this$static.Code - this$static.Range >>> 31; this$static.Code -= this$static.Range & t - 1; diff --git a/modules/base/makepdf.mjs b/modules/base/makepdf.mjs new file mode 100644 index 000000000..e1700034c --- /dev/null +++ b/modules/base/makepdf.mjs @@ -0,0 +1,180 @@ +import { select as d3_select } from '../d3.mjs'; +import { jsPDF } from './jspdf.mjs'; +import { svg2pdf } from './svg2pdf.mjs'; +import { isNodeJs, internals, settings } from '../core.mjs'; +import { FontHandler, detectPdfFont, kArial, kCourier, kSymbol, kWingdings } from './FontHandler.mjs'; +import { approximateLabelWidth, replaceSymbolsInTextNode } from './latex.mjs'; + + +/** @summary Create pdf for existing SVG element + * @return {Promise} with produced PDF file as url string + * @private */ +async function makePDF(svg, args) { + const nodejs = isNodeJs(); + let need_symbols = false; + + const restore_fonts = [], restore_symb = [], restore_wing = [], restore_dominant = [], restore_oblique = [], restore_text = [], + node_transform = svg.node.getAttribute('transform'), custom_fonts = {}; + + if (svg.reset_tranform) + svg.node.removeAttribute('transform'); + + d3_select(svg.node).selectAll('g').each(function() { + if (this.hasAttribute('font-family')) { + const name = this.getAttribute('font-family'); + if (name === kCourier) { + this.setAttribute('font-family', 'courier'); + if (!svg.can_modify) + restore_fonts.push(this); // keep to restore it + } + if (name === kSymbol) { + this.setAttribute('font-family', 'symbol'); + if (!svg.can_modify) + restore_symb.push(this); // keep to restore it + } + if (name === kWingdings) { + this.setAttribute('font-family', 'zapfdingbats'); + if (!svg.can_modify) + restore_wing.push(this); // keep to restore it + } + + if (((name === kArial) || (name === kCourier)) && (this.getAttribute('font-weight') === 'bold') && (this.getAttribute('font-style') === 'oblique')) { + this.setAttribute('font-style', 'italic'); + if (!svg.can_modify) + restore_oblique.push(this); // keep to restore it + } else if ((name === kCourier) && (this.getAttribute('font-style') === 'oblique')) { + this.setAttribute('font-style', 'italic'); + if (!svg.can_modify) + restore_oblique.push(this); // keep to restore it + } + } + }); + + d3_select(svg.node).selectAll('text').each(function() { + if (this.hasAttribute('dominant-baseline')) { + this.setAttribute('dy', '.2em'); // slightly different as in plain text + this.removeAttribute('dominant-baseline'); + if (!svg.can_modify) + restore_dominant.push(this); // keep to restore it + } else if (svg.can_modify && nodejs && this.getAttribute('dy') === '.4em') + this.setAttribute('dy', '.2em'); // better alignment in PDF + + if (replaceSymbolsInTextNode(this)) { + need_symbols = true; + if (!svg.can_modify) + restore_text.push(this); // keep to restore it + } + }); + + let pr = Promise.resolve(); + + if (nodejs) { + const doc = internals.nodejs_document; + doc.originalCreateElementNS = doc.createElementNS; + globalThis.document = doc; + globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet; + globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule; + doc.createElementNS = function(ns, kind) { + const res = doc.originalCreateElementNS(ns, kind); + res.getBBox = function() { + let width = 50, height = 10; + if (this.tagName === 'text') { + // TODO: use jsDOC fonts for label width estimation + const font = detectPdfFont(this); + width = approximateLabelWidth(this.textContent, font); + height = font.size * 1.2; + } + + return { x: 0, y: 0, width, height }; + }; + return res; + }; + + pr = import('canvas').then(handle => { + globalThis.Image = handle.Image; + }); + } + + const orientation = (svg.width < svg.height) ? 'portrait' : 'landscape'; + + let doc = args?.as_doc ? args.doc : null; + + if (doc) { + doc.addPage({ + orientation, + unit: 'px', + format: [svg.width + 10, svg.height + 10] + }); + } else { + doc = new jsPDF({ + orientation, + unit: 'px', + format: [svg.width + 10, svg.height + 10] + }); + if (args?.as_doc) + args.doc = doc; + } + + // add custom fonts to PDF document, only TTF format supported + d3_select(svg.node).selectAll('style').each(function() { + const fcfg = this.$fontcfg; + if (!fcfg?.n || !fcfg?.base64) + return; + const name = fcfg.n; + if ((name === kSymbol) || (name === kWingdings)) + return; + if (custom_fonts[name]) + return; + custom_fonts[name] = true; + + const filename = name.toLowerCase().replace(/\s/g, '') + '.ttf'; + doc.addFileToVFS(filename, fcfg.base64); + doc.addFont(filename, fcfg.n, fcfg.s || 'normal'); + }); + + if (need_symbols && !custom_fonts[kSymbol] && settings.LoadSymbolTtf) { + const handler = new FontHandler(122, 10); + pr = pr.then(() => handler.load()).then(() => { + handler.addCustomFontToSvg(d3_select(svg.node)); + doc.addFileToVFS(kSymbol + '.ttf', handler.base64); + doc.addFont(kSymbol + '.ttf', kSymbol, 'normal'); + }); + } + + return pr.then(() => svg2pdf(svg.node, doc, { x: 5, y: 5, width: svg.width, height: svg.height })).then(() => { + if (svg.reset_tranform && !svg.can_modify && node_transform) + svg.node.setAttribute('transform', node_transform); + + restore_fonts.forEach(node => node.setAttribute('font-family', kCourier)); + restore_symb.forEach(node => node.setAttribute('font-family', kSymbol)); + restore_wing.forEach(node => node.setAttribute('font-family', kWingdings)); + restore_oblique.forEach(node => node.setAttribute('font-style', 'oblique')); + restore_dominant.forEach(node => { + node.setAttribute('dominant-baseline', 'middle'); + node.removeAttribute('dy'); + }); + + restore_text.forEach(node => { + node.innerHTML = node.$originalHTML; + if (node.$originalFont) + node.setAttribute('font-family', node.$originalFont); + else + node.removeAttribute('font-family'); + }); + + const res = args?.as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); + if (nodejs) { + globalThis.document = undefined; + globalThis.CSSStyleSheet = undefined; + globalThis.CSSStyleRule = undefined; + globalThis.Image = undefined; + internals.nodejs_document.createElementNS = internals.nodejs_document.originalCreateElementNS; + if (args?.as_buffer) + return Buffer.from(res); + } + + return res; + }); +} + +export { makePDF }; diff --git a/modules/base/math.mjs b/modules/base/math.mjs index 3cc1019ac..5dca57c73 100644 --- a/modules/base/math.mjs +++ b/modules/base/math.mjs @@ -1,20 +1,22 @@ /** * A math namespace - all functions can be exported from base/math.mjs. - * Also all these functions can be used with TFormula calcualtions + * Also all these functions can be used with TFormula calculations * @namespace Math */ -/* eslint-disable no-loss-of-precision */ -/* eslint-disable space-in-parens */ /* eslint-disable curly */ -/* eslint-disable operator-linebreak */ -/* eslint-disable no-floating-decimal */ -/* eslint-disable brace-style */ -/* eslint-disable comma-spacing */ +/* eslint-disable no-loss-of-precision */ +/* eslint-disable no-useless-assignment */ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-else-return */ +/* eslint-disable no-shadow */ +/* eslint-disable operator-assignment */ +/* eslint-disable @stylistic/js/comma-spacing */ +/* eslint-disable @stylistic/js/no-floating-decimal */ +/* eslint-disable @stylistic/js/space-in-parens */ // this can be improved later -/* eslint-disable no-unreachable-loop */ /* eslint-disable eqeqeq */ const kMACHEP = 1.11022302462515654042363166809e-16, @@ -31,11 +33,12 @@ const kMACHEP = 1.11022302462515654042363166809e-16, * a[0]x^N+a[1]x^(N-1) + ... + a[N] * @memberof Math */ function Polynomialeval(x, a, N) { - if (!N) return a[0]; + if (!N) + return a[0]; let pom = a[0]; for (let i = 1; i <= N; ++i) - pom = pom *x + a[i]; + pom = pom * x + a[i]; return pom; } @@ -44,11 +47,12 @@ function Polynomialeval(x, a, N) { * x^N+a[0]x^(N-1) + ... + a[N-1] * @memberof Math */ function Polynomial1eval(x, a, N) { - if (!N) return a[0]; + if (!N) + return a[0]; let pom = x + a[0]; for (let i = 1; i < N; ++i) - pom = pom *x + a[i]; + pom = pom * x + a[i]; return pom; } @@ -58,28 +62,28 @@ function lgam(x) { let p, q, u, w, z; const kMAXLGM = 2.556348e305, LS2PI = 0.91893853320467274178, - A = [ - 8.11614167470508450300E-4, - -5.95061904284301438324E-4, - 7.93650340457716943945E-4, - -2.77777777730099687205E-3, - 8.33333333333331927722E-2 - ], B = [ - -1.37825152569120859100E3, - -3.88016315134637840924E4, - -3.31612992738871184744E5, - -1.16237097492762307383E6, - -1.72173700820839662146E6, - -8.53555664245765465627E5 - ], C = [ - /* 1.00000000000000000000E0, */ - -3.51815701436523470549E2, - -1.70642106651881159223E4, - -2.20528590553854454839E5, - -1.13933444367982507207E6, - -2.53252307177582951285E6, - -2.01889141433532773231E6 - ]; + A = [ + 8.11614167470508450300E-4, + -5.95061904284301438324E-4, + 7.93650340457716943945E-4, + -2.77777777730099687205E-3, + 8.33333333333331927722E-2 + ], B = [ + -1.37825152569120859100E3, + -3.88016315134637840924E4, + -3.31612992738871184744E5, + -1.16237097492762307383E6, + -1.72173700820839662146E6, + -8.53555664245765465627E5 + ], C = [ + /* 1.00000000000000000000E0, */ + -3.51815701436523470549E2, + -1.70642106651881159223E4, + -2.20528590553854454839E5, + -1.13933444367982507207E6, + -2.53252307177582951285E6, + -2.01889141433532773231E6 + ]; if ((x >= Number.MAX_VALUE) || (x == Number.POSITIVE_INFINITY)) return Number.POSITIVE_INFINITY; @@ -134,10 +138,10 @@ function lgam(x) { if ( x > 1.0e8 ) return q; - p = 1.0/(x*x); + p = 1.0 / (x * x); if ( x >= 1000.0 ) q += ((7.9365079365079365079365e-4 * p - - 2.7777777777777777777778e-3) *p + - 2.7777777777777777777778e-3) * p + 0.0833333333333333333333) / x; else q += Polynomialeval( p, A, 4 ) / x; @@ -155,13 +159,13 @@ function stirf(x) { -2.68132617805781232825E-3, 3.47222221605458667310E-3, 8.33333333333482257126E-2 - ], SQTPI = Math.sqrt(2*Math.PI); + ], SQTPI = Math.sqrt(2 * Math.PI); - w = 1.0/x; + w = 1.0 / x; w = 1.0 + w * Polynomialeval( w, STIR, 4 ); y = Math.exp(x); -/* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ + /* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ if ( x > kMAXSTIR ) { /* Avoid overflow in pow() */ @@ -238,7 +242,7 @@ function erfc(a) { p = Polynomialeval( x, erfR, 5 ); q = Polynomial1eval( x, erfS, 6 ); } - y = (z * p)/q; + y = (z * p) / q; if (a < 0) y = 2.0 - y; @@ -267,9 +271,7 @@ function erf(x) { 4.59432382970980127987E3, 2.26290000613890934246E4, 4.92673942608635921086E4 - ], - - z = x * x; + ], z = x * x; return x * Polynomialeval(z, erfT, 4) / Polynomial1eval(z, erfU, 5); } @@ -277,50 +279,59 @@ function erf(x) { /** @summary lognormal_cdf_c function * @memberof Math */ function lognormal_cdf_c(x, m, s, x0) { - if (x0 === undefined) x0 = 0; - const z = (Math.log((x-x0))-m)/(s*kSqrt2); - if (z > 1.) return 0.5*erfc(z); - else return 0.5*(1.0 - erf(z)); + if (x0 === undefined) + x0 = 0; + const z = (Math.log((x - x0)) - m) / (s * kSqrt2); + if (z > 1.) + return 0.5 * erfc(z); + else + return 0.5 * (1.0 - erf(z)); } /** @summary lognormal_cdf_c function * @memberof Math */ function lognormal_cdf(x, m, s, x0 = 0) { - const z = (Math.log((x-x0))-m)/(s*kSqrt2); - if (z < -1.) return 0.5*erfc(-z); - else return 0.5*(1.0 + erf(z)); + const z = (Math.log((x - x0)) - m) / (s * kSqrt2); + if (z < -1.) + return 0.5 * erfc(-z); + else + return 0.5 * (1.0 + erf(z)); } /** @summary normal_cdf_c function * @memberof Math */ function normal_cdf_c(x, sigma, x0 = 0) { - const z = (x-x0)/(sigma*kSqrt2); - if (z > 1.) return 0.5*erfc(z); - else return 0.5*(1.-erf(z)); + const z = (x - x0) / (sigma * kSqrt2); + if (z > 1.) + return 0.5 * erfc(z); + else + return 0.5 * (1. - erf(z)); } /** @summary normal_cdf function * @memberof Math */ function normal_cdf(x, sigma, x0 = 0) { - const z = (x-x0)/(sigma*kSqrt2); - if (z < -1.) return 0.5*erfc(-z); - else return 0.5*(1.0 + erf(z)); + const z = (x - x0) / (sigma * kSqrt2); + if (z < -1.) + return 0.5 * erfc(-z); + else + return 0.5 * (1.0 + erf(z)); } /** @summary log normal pdf * @memberof Math */ function lognormal_pdf(x, m, s, x0 = 0) { - if ((x-x0) <= 0) + if ((x - x0) <= 0) return 0.0; - const tmp = (Math.log((x-x0)) - m)/s; - return 1.0 / ((x-x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) /2); + const tmp = (Math.log((x - x0)) - m) / s; + return 1.0 / ((x - x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) / 2); } /** @summary normal pdf * @memberof Math */ function normal_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x-x0)/sigma; - return (1.0/(Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp*tmp/2); + const tmp = (x - x0) / sigma; + return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); } /** @summary gamma calculation @@ -370,7 +381,7 @@ function gamma(x) { z *= x; } - let small = false; + let small = false; while (( x < 0.0 ) && !small) { if ( x > -1.E-9 ) @@ -394,7 +405,7 @@ function gamma(x) { if ( x == 0 ) return Number.POSITIVE_INFINITY; else - return z/((1.0 + 0.5772156649015329 * x) * x); + return z / ((1.0 + 0.5772156649015329 * x) * x); } if ( x == 2.0 ) @@ -433,58 +444,58 @@ function ndtri(y0) { return Number.POSITIVE_INFINITY; const P0 = [ - -5.99633501014107895267E1, - 9.80010754185999661536E1, - -5.66762857469070293439E1, - 1.39312609387279679503E1, - -1.23916583867381258016E0 + -5.99633501014107895267E1, + 9.80010754185999661536E1, + -5.66762857469070293439E1, + 1.39312609387279679503E1, + -1.23916583867381258016E0 ], Q0 = [ - 1.95448858338141759834E0, - 4.67627912898881538453E0, - 8.63602421390890590575E1, - -2.25462687854119370527E2, - 2.00260212380060660359E2, - -8.20372256168333339912E1, - 1.59056225126211695515E1, - -1.18331621121330003142E0 + 1.95448858338141759834E0, + 4.67627912898881538453E0, + 8.63602421390890590575E1, + -2.25462687854119370527E2, + 2.00260212380060660359E2, + -8.20372256168333339912E1, + 1.59056225126211695515E1, + -1.18331621121330003142E0 ], P1 = [ - 4.05544892305962419923E0, - 3.15251094599893866154E1, - 5.71628192246421288162E1, - 4.40805073893200834700E1, - 1.46849561928858024014E1, - 2.18663306850790267539E0, - -1.40256079171354495875E-1, - -3.50424626827848203418E-2, - -8.57456785154685413611E-4 + 4.05544892305962419923E0, + 3.15251094599893866154E1, + 5.71628192246421288162E1, + 4.40805073893200834700E1, + 1.46849561928858024014E1, + 2.18663306850790267539E0, + -1.40256079171354495875E-1, + -3.50424626827848203418E-2, + -8.57456785154685413611E-4 ], Q1 = [ - 1.57799883256466749731E1, - 4.53907635128879210584E1, - 4.13172038254672030440E1, - 1.50425385692907503408E1, - 2.50464946208309415979E0, - -1.42182922854787788574E-1, - -3.80806407691578277194E-2, - -9.33259480895457427372E-4 + 1.57799883256466749731E1, + 4.53907635128879210584E1, + 4.13172038254672030440E1, + 1.50425385692907503408E1, + 2.50464946208309415979E0, + -1.42182922854787788574E-1, + -3.80806407691578277194E-2, + -9.33259480895457427372E-4 ], P2 = [ - 3.23774891776946035970E0, - 6.91522889068984211695E0, - 3.93881025292474443415E0, - 1.33303460815807542389E0, - 2.01485389549179081538E-1, - 1.23716634817820021358E-2, - 3.01581553508235416007E-4, - 2.65806974686737550832E-6, - 6.23974539184983293730E-9 + 3.23774891776946035970E0, + 6.91522889068984211695E0, + 3.93881025292474443415E0, + 1.33303460815807542389E0, + 2.01485389549179081538E-1, + 1.23716634817820021358E-2, + 3.01581553508235416007E-4, + 2.65806974686737550832E-6, + 6.23974539184983293730E-9 ], Q2 = [ - 6.02427039364742014255E0, - 3.67983563856160859403E0, - 1.37702099489081330271E0, - 2.16236993594496635890E-1, - 1.34204006088543189037E-2, - 3.28014464682127739104E-4, - 2.89247864745380683936E-6, - 6.79019408009981274425E-9 + 6.02427039364742014255E0, + 3.67983563856160859403E0, + 1.37702099489081330271E0, + 2.16236993594496635890E-1, + 1.34204006088543189037E-2, + 3.28014464682127739104E-4, + 2.89247864745380683936E-6, + 6.79019408009981274425E-9 ], s2pi = 2.50662827463100050242e0, dd = 0.13533528323661269189; let code = 1, y = y0, x, y2, x1; @@ -496,17 +507,17 @@ function ndtri(y0) { if ( y > dd ) { y = y - 0.5; y2 = y * y; - x = y + y * (y2 * Polynomialeval( y2, P0, 4)/ Polynomial1eval( y2, Q0, 8 )); + x = y + y * (y2 * Polynomialeval( y2, P0, 4) / Polynomial1eval( y2, Q0, 8 )); x = x * s2pi; return x; } x = Math.sqrt(-2.0 * Math.log(y)); - const x0 = x - Math.log(x)/x, - z = 1.0/x; + const x0 = x - Math.log(x) / x, + z = 1.0 / x; if ( x < 8.0 ) - x1 = z * Polynomialeval( z, P1, 8 )/ Polynomial1eval( z, Q1, 8 ); + x1 = z * Polynomialeval( z, P1, 8 ) / Polynomial1eval( z, Q1, 8 ); else - x1 = z * Polynomialeval( z, P2, 8 )/ Polynomial1eval( z, Q2, 8 ); + x1 = z * Polynomialeval( z, P2, 8 ) / Polynomial1eval( z, Q2, 8 ); x = x0 - x1; if ( code != 0 ) x = -x; @@ -530,9 +541,11 @@ function normal_quantile_c(z, sigma) { function igamc(a,x) { // LM: for negative values returns 0.0 // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) return 0.0; + if (a <= 0) + return 0.0; - if (x <= 0) return 1.0; + if (x <= 0) + return 1.0; if ((x < 1.0) || (x < a)) return (1.0 - igam(a,x)); @@ -551,7 +564,7 @@ function igamc(a,x) { qkm2 = x, pkm1 = x + 1.0, qkm1 = z * x, - ans = pkm1/qkm1, + ans = pkm1 / qkm1, yc, r, t, pk, qk; do { @@ -563,8 +576,8 @@ function igamc(a,x) { qk = qkm1 * z - qkm2 * yc; if (qk) { - r = pk/qk; - t = Math.abs( (ans - r)/r ); + r = pk / qk; + t = Math.abs( (ans - r) / r ); ans = r; } else @@ -590,9 +603,11 @@ function igamc(a,x) { function igam(a, x) { // LM: for negative values returns 1.0 instead of zero // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) return 1.0; + if (a <= 0) + return 1.0; - if (x <= 0) return 0.0; + if (x <= 0) + return 0.0; if ((x > 1.0) && (x > a)) return 1.0 - igamc(a,x); @@ -609,11 +624,11 @@ function igam(a, x) { do { r += 1.0; - c *= x/r; + c *= x / r; ans += c; - } while ( c/ans > kMACHEP ); + } while ( c / ans > kMACHEP ); - return ans * ax/a; + return ans * ax / a; } @@ -635,13 +650,13 @@ function igami(a, y0) { let x0 = kMAXNUM, x1 = 0, x, yl = 0, yh = 1, y, d, lgm, i, dir; /* approximation to inverse function */ - d = 1.0/(9.0*a); + d = 1.0 / (9.0 * a); y = 1.0 - d - ndtri(y0) * Math.sqrt(d); x = a * y * y * y; lgm = lgam(a); - for ( i=0; i<10; ++i ) { + for ( i = 0; i < 10; ++i ) { if ( x > x0 || x < x1 ) break; y = igamc(a,x); @@ -661,8 +676,8 @@ function igami(a, y0) { break; d = -Math.exp(d); /* compute the step to the next approximation of x */ - d = (y - y0)/d; - if ( Math.abs(d/x) < kMACHEP ) + d = (y - y0) / d; + if ( Math.abs(d / x) < kMACHEP ) return x; x = x - d; } @@ -685,13 +700,13 @@ function igami(a, y0) { d = 0.5; dir = 0; - for ( i=0; i<400; ++i ) { + for ( i = 0; i < 400; ++i ) { x = x1 + d * (x0 - x1); y = igamc( a, x ); - lgm = (x0 - x1)/(x1 + x0); + lgm = (x0 - x1) / (x1 + x0); if ( Math.abs(lgm) < dithresh ) break; - lgm = (y - y0)/y0; + lgm = (y - y0) / y0; if ( Math.abs(lgm) < dithresh ) break; if ( x <= 0.0 ) @@ -706,7 +721,7 @@ function igami(a, y0) { else if ( dir > 1 ) d = 0.5 * d + 0.5; else - d = (y0 - yl)/(yh - yl); + d = (y0 - yl) / (yh - yl); dir += 1; } else { @@ -719,7 +734,7 @@ function igami(a, y0) { else if ( dir < -1 ) d = 0.5 * d; else - d = (y0 - yl)/(yh - yl); + d = (y0 - yl) / (yh - yl); dir -= 1; } } @@ -731,8 +746,9 @@ function igami(a, y0) { * same algorithm is used in GSL * @memberof Math */ function landau_pdf(x, xi, x0 = 0) { - if (xi <= 0) return 0; - const v = (x - x0)/xi; + if (xi <= 0) + return 0; + const v = (x - x0) / xi; let u, ue, us, denlan; const p1 = [0.4259894875,-0.1249762550, 0.03984243700, -0.006298287635, 0.001511162253], q1 = [1.0 ,-0.3388260629, 0.09594393323, -0.01608042283, 0.003778942063], @@ -750,48 +766,51 @@ function landau_pdf(x, xi, x0 = 0) { a2 = [-1.845568670,-4.284640743]; if (v < -5.5) { - u = Math.exp(v+1.0); - if (u < 1e-10) return 0.0; - ue = Math.exp(-1/u); + u = Math.exp(v + 1.0); + if (u < 1e-10) + return 0.0; + ue = Math.exp(-1 / u); us = Math.sqrt(u); - denlan = 0.3989422803*(ue/us)*(1+(a1[0]+(a1[1]+a1[2]*u)*u)*u); + denlan = 0.3989422803 * (ue / us) * (1 + (a1[0] + (a1[1] + a1[2] * u) * u) * u); } else if (v < -1) { - u = Math.exp(-v-1); - denlan = Math.exp(-u)*Math.sqrt(u)* - (p1[0]+(p1[1]+(p1[2]+(p1[3]+p1[4]*v)*v)*v)*v)/ - (q1[0]+(q1[1]+(q1[2]+(q1[3]+q1[4]*v)*v)*v)*v); + u = Math.exp(-v - 1); + denlan = Math.exp(-u) * Math.sqrt(u) * + (p1[0] + (p1[1] + (p1[2] + (p1[3] + p1[4] * v) * v) * v) * v) / + (q1[0] + (q1[1] + (q1[2] + (q1[3] + q1[4] * v) * v) * v) * v); } else if (v < 1) { - denlan = (p2[0]+(p2[1]+(p2[2]+(p2[3]+p2[4]*v)*v)*v)*v)/ - (q2[0]+(q2[1]+(q2[2]+(q2[3]+q2[4]*v)*v)*v)*v); + denlan = (p2[0] + (p2[1] + (p2[2] + (p2[3] + p2[4] * v) * v) * v) * v) / + (q2[0] + (q2[1] + (q2[2] + (q2[3] + q2[4] * v) * v) * v) * v); } else if (v < 5) { - denlan = (p3[0]+(p3[1]+(p3[2]+(p3[3]+p3[4]*v)*v)*v)*v)/ - (q3[0]+(q3[1]+(q3[2]+(q3[3]+q3[4]*v)*v)*v)*v); + denlan = (p3[0] + (p3[1] + (p3[2] + (p3[3] + p3[4] * v) * v) * v) * v) / + (q3[0] + (q3[1] + (q3[2] + (q3[3] + q3[4] * v) * v) * v) * v); } else if (v < 12) { - u = 1/v; - denlan = u*u*(p4[0]+(p4[1]+(p4[2]+(p4[3]+p4[4]*u)*u)*u)*u)/ - (q4[0]+(q4[1]+(q4[2]+(q4[3]+q4[4]*u)*u)*u)*u); + u = 1 / v; + denlan = u * u * (p4[0] + (p4[1] + (p4[2] + (p4[3] + p4[4] * u) * u) * u) * u) / + (q4[0] + (q4[1] + (q4[2] + (q4[3] + q4[4] * u) * u) * u) * u); } else if (v < 50) { - u = 1/v; - denlan = u*u*(p5[0]+(p5[1]+(p5[2]+(p5[3]+p5[4]*u)*u)*u)*u)/ - (q5[0]+(q5[1]+(q5[2]+(q5[3]+q5[4]*u)*u)*u)*u); + u = 1 / v; + denlan = u * u * (p5[0] + (p5[1] + (p5[2] + (p5[3] + p5[4] * u) * u) * u) * u) / + (q5[0] + (q5[1] + (q5[2] + (q5[3] + q5[4] * u) * u) * u) * u); } else if (v < 300) { - u = 1/v; - denlan = u*u*(p6[0]+(p6[1]+(p6[2]+(p6[3]+p6[4]*u)*u)*u)*u)/ - (q6[0]+(q6[1]+(q6[2]+(q6[3]+q6[4]*u)*u)*u)*u); + u = 1 / v; + denlan = u * u * (p6[0] + (p6[1] + (p6[2] + (p6[3] + p6[4] * u) * u) * u) * u) / + (q6[0] + (q6[1] + (q6[2] + (q6[3] + q6[4] * u) * u) * u) * u); } else { - u = 1/(v-v*Math.log(v)/(v+1)); - denlan = u*u*(1+(a2[0]+a2[1]*u)*u); + u = 1 / (v - v * Math.log(v) / (v + 1)); + denlan = u * u * (1 + (a2[0] + a2[1] * u) * u); } - return denlan/xi; + return denlan / xi; } /** @summary Landau function * @memberof Math */ function Landau(x, mpv, sigma, norm) { - if (sigma <= 0) return 0; + if (sigma <= 0) + return 0; const den = landau_pdf((x - mpv) / sigma, 1, 0); - if (!norm) return den; - return den/sigma; + if (!norm) + return den; + return den / sigma; } /** @summary inc_gamma_c @@ -815,31 +834,38 @@ function lgamma(z) { /** @summary Probability density function of the beta distribution. * @memberof Math */ function beta_pdf(x, a, b) { - if (x < 0 || x > 1.0) return 0; - if (x == 0 ) { - if (a < 1) return Number.POSITIVE_INFINITY; - else if (a > 1) return 0; - else if ( a == 1) return b; // to avoid a nan from log(0)*0 + if (x < 0 || x > 1.0) + return 0; + if (x == 0 ) { + if (a < 1) + return Number.POSITIVE_INFINITY; + else if (a > 1) + return 0; + else if ( a == 1) + return b; // to avoid a nan from log(0)*0 } if (x == 1 ) { - if (b < 1) return Number.POSITIVE_INFINITY; - else if (b > 1) return 0; - else if ( b == 1) return a; // to avoid a nan from log(0)*0 + if (b < 1) + return Number.POSITIVE_INFINITY; + else if (b > 1) + return 0; + else if ( b == 1) + return a; // to avoid a nan from log(0)*0 } return Math.exp(lgamma(a + b) - lgamma(a) - lgamma(b) + - Math.log(x) * (a -1.) + Math.log1p(-x) * (b - 1.)); + Math.log(x) * (a - 1.) + Math.log1p(-x) * (b - 1.)); } /** @summary beta * @memberof Math */ function beta(x,y) { - return Math.exp(lgamma(x)+lgamma(y)-lgamma(x+y)); + return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); } /** @summary chisquared_cdf_c * @memberof Math */ function chisquared_cdf_c(x,r,x0 = 0) { - return inc_gamma_c(0.5 * r, 0.5*(x-x0)); + return inc_gamma_c(0.5 * r, 0.5 * (x - x0)); } /** @summary Continued fraction expansion #1 for incomplete beta integral @@ -868,7 +894,7 @@ function incbcf(a,b,x) { n = 0; do { - xk = -( x * k1 * k2 )/( k3 * k4 ); + xk = -( x * k1 * k2 ) / ( k3 * k4 ); pk = pkm1 + pkm2 * xk; qk = qkm1 + qkm2 * xk; pkm2 = pkm1; @@ -876,7 +902,7 @@ function incbcf(a,b,x) { qkm2 = qkm1; qkm1 = qk; - xk = ( x * k5 * k6 )/( k7 * k8 ); + xk = ( x * k5 * k6 ) / ( k7 * k8 ); pk = pkm1 + pkm2 * xk; qk = qkm1 + qkm2 * xk; pkm2 = pkm1; @@ -884,11 +910,11 @@ function incbcf(a,b,x) { qkm2 = qkm1; qkm1 = qk; - if ( qk !=0 ) - r = pk/qk; + if ( qk != 0 ) + r = pk / qk; if ( r != 0 ) { - t = Math.abs( (ans - r)/r ); + t = Math.abs( (ans - r) / r ); ans = r; } else @@ -921,14 +947,14 @@ function incbcf(a,b,x) { } while ( ++n < 300 ); -// cdone: + // cdone: return ans; } /** @summary Continued fraction expansion #2 for incomplete beta integral * @memberof Math */ function incbd(a,b,x) { - const z = x / (1.0-x), + const z = x / (1.0 - x), thresh = 3.0 * kMACHEP; let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, @@ -952,7 +978,7 @@ function incbd(a,b,x) { r = 1.0; n = 0; do { - xk = -( z * k1 * k2 )/( k3 * k4 ); + xk = -( z * k1 * k2 ) / ( k3 * k4 ); pk = pkm1 + pkm2 * xk; qk = qkm1 + qkm2 * xk; pkm2 = pkm1; @@ -960,7 +986,7 @@ function incbd(a,b,x) { qkm2 = qkm1; qkm1 = qk; - xk = ( z * k5 * k6 )/( k7 * k8 ); + xk = ( z * k5 * k6 ) / ( k7 * k8 ); pk = pkm1 + pkm2 * xk; qk = qkm1 + qkm2 * xk; pkm2 = pkm1; @@ -969,10 +995,10 @@ function incbd(a,b,x) { qkm1 = qk; if ( qk != 0 ) - r = pk/qk; + r = pk / qk; if ( r != 0 ) { - t = Math.abs( (ans - r)/r ); + t = Math.abs( (ans - r) / r ); ans = r; } else @@ -1004,7 +1030,7 @@ function incbd(a,b,x) { } } while ( ++n < 300 ); -// cdone: + // cdone: return ans; } @@ -1032,14 +1058,14 @@ function pseries(a,b,x) { s += ai; u = a * Math.log(x); - if ( (a+b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) + if ( (a + b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) { - t = gamma(a+b) / (gamma(a)*gamma(b)); + t = gamma(a + b) / (gamma(a) * gamma(b)); s = s * t * Math.pow(x,a); } else { - t = lgam(a+b) - lgam(a) - lgam(b) + u + Math.log(s); + t = lgam(a + b) - lgam(a) - lgam(b) + u + Math.log(s); if ( t < kMINLOG ) s = 0.0; else @@ -1057,12 +1083,14 @@ function incbet(aa,bb,xx) { return 0.0; // LM: changed: for X > 1 return 1. - if (xx <= 0.0) return 0.0; - if ( xx >= 1.0) return 1.0; + if (xx <= 0.0) + return 0.0; + if (xx >= 1.0) + return 1.0; flag = 0; -/* - to test if that way is better for large b/ (comment out from Cephes version) + /* - to test if that way is better for large b/ (comment out from Cephes version) if ( (bb * xx) <= 1.0 && xx <= 0.95) { t = pseries(aa, bb, xx); @@ -1072,9 +1100,9 @@ function incbet(aa,bb,xx) { **/ w = 1.0 - xx; -/* Reverse a and b if x is greater than the mean. */ -/* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ - if (xx > (aa/(aa+bb))) + /* Reverse a and b if x is greater than the mean. */ + /* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ + if (xx > (aa / (aa + bb))) { flag = 1; a = bb; @@ -1095,30 +1123,30 @@ function incbet(aa,bb,xx) { // goto done; } else { /* Choose expansion for better convergence. */ - y = x * (a+b-2.0) - (a-1.0); + y = x * (a + b - 2.0) - (a - 1.0); if ( y < 0.0 ) w = incbcf( a, b, x ); else w = incbd( a, b, x ) / xc; - /* Multiply w by the factor + /* Multiply w by the factor a b _ _ _ x (1-x) | (a+b) / (a | (a) | (b)) . */ y = a * Math.log(x); t = b * Math.log(xc); - if ( (a+b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) + if ( (a + b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) { t = Math.pow(xc,b); t *= Math.pow(x,a); t /= a; t *= w; - t *= gamma(a+b) / (gamma(a) * gamma(b)); + t *= gamma(a + b) / (gamma(a) * gamma(b)); // goto done; } else { /* Resort to logarithms. */ - y += t + lgam(a+b) - lgam(a) - lgam(b); - y += Math.log(w/a); + y += t + lgam(a + b) - lgam(a) - lgam(b); + y += Math.log(w / a); if ( y < kMINLOG ) t = 0.0; else @@ -1126,7 +1154,7 @@ function incbet(aa,bb,xx) { } } -// done: + // done: if (flag == 1) { if ( t <= kMACHEP ) @@ -1181,14 +1209,14 @@ function incbi(aa,bb,yy0) { a = aa; b = bb; y0 = yy0; - x = a/(a+b); + x = a / (a + b); y = incbet( a, b, x ); // goto ihalve; // will start } else { dithresh = 1.0e-4; -/* approximation to inverse function */ + /* approximation to inverse function */ yp = -ndtri(yy0); @@ -1208,11 +1236,11 @@ function incbi(aa,bb,yy0) { y0 = yy0; } - lgm = (yp * yp - 3.0)/6.0; - x = 2.0/(1.0/(2.0*a-1.0) + 1.0/(2.0*b-1.0)); + lgm = (yp * yp - 3.0) / 6.0; + x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); d = yp * Math.sqrt( x + lgm ) / x - - (1.0/(2.0*b-1.0) - 1.0/(2.0*a-1.0)) - * (lgm + 5.0/6.0 - 2.0/(3.0*x)); + - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) + * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); d = 2.0 * d; if ( d < kMINLOG ) { // x = 1.0; @@ -1220,23 +1248,23 @@ function incbi(aa,bb,yy0) { x = 0.0; return process_done(); } - x = a/(a + b * Math.exp(d)); + x = a / (a + b * Math.exp(d)); y = incbet( a, b, x ); - yp = (y - y0)/y0; + yp = (y - y0) / y0; if ( Math.abs(yp) < 0.2 ) ihalve = false; // instead goto newt; exclude ihalve for the first time } - let mainloop = 1000; + let mainloop = 1000; - // endless loop until coverage - while (mainloop-- > 0) { + // endless loop until coverage + while (mainloop-- > 0) { /* Resort to interval halving if not close enough. */ // ihalve: if (ihalve) { dir = 0; di = 0.5; - for ( i=0; i<100; i++ ) + for ( i = 0; i < 100; i++ ) { if ( i != 0 ) { @@ -1251,10 +1279,10 @@ function incbi(aa,bb,yy0) { return process_done(); // goto under; } y = incbet( a, b, x ); - yp = (x1 - x0)/(x1 + x0); + yp = (x1 - x0) / (x1 + x0); if ( Math.abs(yp) < dithresh ) break; // goto newt; - yp = (y-y0)/y0; + yp = (y - y0) / y0; if ( Math.abs(yp) < dithresh ) break; // goto newt; } @@ -1272,7 +1300,7 @@ function incbi(aa,bb,yy0) { else if ( dir > 1 ) di = 0.5 * di + 0.5; else - di = (y0 - y)/(yh - yl); + di = (y0 - y) / (yh - yl); dir += 1; if ( x0 > 0.75 ) { @@ -1318,7 +1346,7 @@ function incbi(aa,bb,yy0) { else if ( dir < -1 ) di = 0.5 * di; else - di = (y - y0)/(yh - yl); + di = (y - y0) / (yh - yl); dir -= 1; } } @@ -1337,14 +1365,14 @@ function incbi(aa,bb,yy0) { ihalve = true; // enter loop next time - // newt: + // newt: if ( nflg ) return process_done(); // goto done; nflg = 1; - lgm = lgam(a+b) - lgam(a) - lgam(b); + lgm = lgam(a + b) - lgam(a) - lgam(b); - for ( i=0; i<8; i++ ) + for ( i = 0; i < 8; i++ ) { /* Compute the function at this point. */ if ( i != 0 ) @@ -1372,14 +1400,14 @@ function incbi(aa,bb,yy0) { if ( x == 1.0 || x == 0.0 ) break; /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0-x) + lgm; + d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0 - x) + lgm; if ( d < kMINLOG ) return process_done(); // goto done; if ( d > kMAXLOG ) break; d = Math.exp(d); /* Compute the step to the next approximation of x. */ - d = (y - y0)/d; + d = (y - y0) / d; xt = x - d; if ( xt <= x0 ) { @@ -1396,14 +1424,14 @@ function incbi(aa,bb,yy0) { break; } x = xt; - if ( Math.abs(d/x) < 128.0 * kMACHEP ) + if ( Math.abs(d / x) < 128.0 * kMACHEP ) return process_done(); // goto done; } /* Did not converge. */ dithresh = 256.0 * kMACHEP; } // endless loop instead of // goto ihalve; -// done: + // done: return process_done(); } @@ -1425,13 +1453,13 @@ function beta_quantile(z,a,b) { /** @summary Complement of the cumulative distribution function of the beta distribution. * @memberof Math */ function beta_cdf_c(x,a,b) { - return inc_beta(1-x, b, a); + return inc_beta(1 - x, b, a); } /** @summary chisquared_cdf * @memberof Math */ -function chisquared_cdf(x,r,x0=0) { - return inc_gamma(0.5 * r, 0.5*(x-x0)); +function chisquared_cdf(x,r,x0 = 0) { + return inc_gamma(0.5 * r, 0.5 * (x - x0)); } /** @summary gamma_quantile_c function @@ -1443,44 +1471,44 @@ function gamma_quantile_c(z, alpha, theta) { /** @summary gamma_quantile function * @memberof Math */ function gamma_quantile(z, alpha, theta) { - return theta * igami( alpha, 1.- z); + return theta * igami( alpha, 1. - z); } /** @summary breitwigner_cdf_c function * @memberof Math */ function breitwigner_cdf_c(x,gamma, x0 = 0) { - return 0.5 - Math.atan(2.0 * (x-x0) / gamma) / M_PI; + return 0.5 - Math.atan(2.0 * (x - x0) / gamma) / M_PI; } /** @summary breitwigner_cdf function * @memberof Math */ function breitwigner_cdf(x, gamma, x0 = 0) { - return 0.5 + Math.atan(2.0 * (x-x0) / gamma) / M_PI; + return 0.5 + Math.atan(2.0 * (x - x0) / gamma) / M_PI; } /** @summary cauchy_cdf_c function * @memberof Math */ function cauchy_cdf_c(x, b, x0 = 0) { - return 0.5 - Math.atan( (x-x0) / b) / M_PI; + return 0.5 - Math.atan( (x - x0) / b) / M_PI; } /** @summary cauchy_cdf function * @memberof Math */ function cauchy_cdf(x, b, x0 = 0) { - return 0.5 + Math.atan( (x-x0) / b) / M_PI; + return 0.5 + Math.atan( (x - x0) / b) / M_PI; } /** @summary cauchy_pdf function * @memberof Math */ function cauchy_pdf(x, b = 1, x0 = 0) { - return b/(M_PI * ((x-x0)*(x-x0) + b*b)); + return b / (M_PI * ((x - x0) * (x - x0) + b * b)); } /** @summary gaussian_pdf function * @memberof Math */ function gaussian_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x-x0)/sigma; - return (1.0/(Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp*tmp/2); + const tmp = (x - x0) / sigma; + return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); } /** @summary gamma_pdf function @@ -1501,7 +1529,7 @@ function gamma_pdf(x, alpha, theta, x0 = 0) { function tdistribution_cdf_c(x, r, x0 = 0) { const p = x - x0, sign = (p > 0) ? 1. : -1; - return .5 - .5*inc_beta(p*p/(r + p*p), .5, .5*r)*sign; + return .5 - .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; } /** @summary tdistribution_cdf function @@ -1509,37 +1537,39 @@ function tdistribution_cdf_c(x, r, x0 = 0) { function tdistribution_cdf(x, r, x0 = 0) { const p = x - x0, sign = (p > 0) ? 1. : -1; - return .5 + .5*inc_beta(p*p/(r + p*p), .5, .5*r)*sign; + return .5 + .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; } /** @summary tdistribution_pdf function * @memberof Math */ function tdistribution_pdf(x, r, x0 = 0) { - return (Math.exp(lgamma((r + 1.0)/2.0) - lgamma(r/2.0)) / Math.sqrt(M_PI * r)) - * Math.pow((1.0 + (x-x0)*(x-x0)/r), -(r + 1.0)/2.0); + return (Math.exp(lgamma((r + 1.0) / 2.0) - lgamma(r / 2.0)) / Math.sqrt(M_PI * r)) + * Math.pow((1.0 + (x - x0) * (x - x0) / r), -(r + 1.0) / 2.0); } /** @summary exponential_cdf_c function * @memberof Math */ function exponential_cdf_c(x, lambda, x0 = 0) { - return ((x-x0) < 0) ? 1.0 : Math.exp(-lambda * (x-x0)); + return ((x - x0) < 0) ? 1.0 : Math.exp(-lambda * (x - x0)); } /** @summary exponential_cdf function * @memberof Math */ function exponential_cdf(x, lambda, x0 = 0) { - return ((x-x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x-x0)); + return ((x - x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x - x0)); } /** @summary chisquared_pdf * @memberof Math */ function chisquared_pdf(x, r, x0 = 0) { - if ((x-x0) < 0) return 0.0; - const a = r/2 -1.; + if ((x - x0) < 0) + return 0.0; + const a = r / 2 - 1.; // let return inf for case x = x0 and treat special case of r = 2 otherwise will return nan - if (x == x0 && a == 0) return 0.5; + if (x == x0 && a == 0) + return 0.5; - return Math.exp((r/2 - 1) * Math.log((x-x0)/2) - (x-x0)/2 - lgamma(r/2))/2; + return Math.exp((r / 2 - 1) * Math.log((x - x0) / 2) - (x - x0) / 2 - lgamma(r / 2)) / 2; } /** @summary Probability density function of the F-distribution. @@ -1547,21 +1577,23 @@ function chisquared_pdf(x, r, x0 = 0) { function fdistribution_pdf(x, n, m, x0 = 0) { if (n < 0 || m < 0) return Number.NaN; - if ((x-x0) < 0) + if ((x - x0) < 0) return 0.0; - return Math.exp((n/2) * Math.log(n) + (m/2) * Math.log(m) + lgamma((n+m)/2) - lgamma(n/2) - lgamma(m/2) - + (n/2 -1) * Math.log(x-x0) - ((n+m)/2) * Math.log(m + n*(x-x0))); + return Math.exp((n / 2) * Math.log(n) + (m / 2) * Math.log(m) + lgamma((n + m) / 2) - lgamma(n / 2) - lgamma(m / 2) + + (n / 2 - 1) * Math.log(x - x0) - ((n + m) / 2) * Math.log(m + n * (x - x0))); } /** @summary fdistribution_cdf_c function * @memberof Math */ function fdistribution_cdf_c(x, n, m, x0 = 0) { - if (n < 0 || m < 0) return Number.NaN; + if (n < 0 || m < 0) + return Number.NaN; const z = m / (m + n * (x - x0)); // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) return 1. - fdistribution_cdf(x, n, m, x0); + if (z > 0.9 && n > 1 && m > 1) + return 1. - fdistribution_cdf(x, n, m, x0); // for the complement use the fact that IB(x,a,b) = 1. - IB(1-x,b,a) return inc_beta(m / (m + n * (x - x0)), .5 * m, .5 * n); @@ -1570,7 +1602,8 @@ function fdistribution_cdf_c(x, n, m, x0 = 0) { /** @summary fdistribution_cdf function * @memberof Math */ function fdistribution_cdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) return Number.NaN; + if (n < 0 || m < 0) + return Number.NaN; const z = n * (x - x0) / (m + n * (x - x0)); // fox z->1 and large a and b IB looses precision use complement function @@ -1583,11 +1616,14 @@ function fdistribution_cdf(x, n, m, x0 = 0) { /** @summary Prob function * @memberof Math */ function Prob(chi2, ndf) { - if (ndf <= 0) return 0; // Set CL to zero in case ndf <= 0 + if (ndf <= 0) + return 0; // Set CL to zero in case ndf <= 0 if (chi2 <= 0) { - if (chi2 < 0) return 0; - else return 1; + if (chi2 < 0) + return 0; + else + return 1; } return chisquared_cdf_c(chi2,ndf,0); @@ -1596,54 +1632,58 @@ function Prob(chi2, ndf) { /** @summary Gaus function * @memberof Math */ function Gaus(x, mean, sigma, norm) { - if (!sigma) return 1e30; + if (!sigma) + return 1e30; const arg = (x - mean) / sigma; - if (arg < -39 || arg > 39) return 0; - const res = Math.exp(-0.5*arg*arg); - return norm ? res/(2.50662827463100024*sigma) : res; // sqrt(2*Pi)=2.50662827463100024 + if (arg < -39 || arg > 39) + return 0; + const res = Math.exp(-0.5 * arg * arg); + return norm ? res / (2.50662827463100024 * sigma) : res; // sqrt(2*Pi)=2.50662827463100024 } /** @summary BreitWigner function * @memberof Math */ function BreitWigner(x, mean, gamma) { - return gamma/((x-mean)*(x-mean) + gamma*gamma/4) / 2 / Math.PI; + return gamma / ((x - mean) * (x - mean) + gamma * gamma / 4) / 2 / Math.PI; } /** @summary Calculates Beta-function Gamma(p)*Gamma(q)/Gamma(p+q). * @memberof Math */ function Beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x+y)); + return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); } /** @summary GammaDist function * @memberof Math */ function GammaDist(x, gamma, mu = 0, beta = 1) { - if ((x < mu) || (gamma <= 0) || (beta <= 0)) return 0; + if ((x < mu) || (gamma <= 0) || (beta <= 0)) + return 0; return gamma_pdf(x, gamma, beta, mu); } /** @summary probability density function of Laplace distribution * @memberof Math */ function LaplaceDist(x, alpha = 0, beta = 1) { - return Math.exp(-Math.abs((x-alpha)/beta)) / (2.*beta); + return Math.exp(-Math.abs((x - alpha) / beta)) / (2. * beta); } /** @summary distribution function of Laplace distribution * @memberof Math */ function LaplaceDistI(x, alpha = 0, beta = 1) { - return (x <= alpha) ? 0.5*Math.exp(-Math.abs((x-alpha)/beta)) : 1 - 0.5*Math.exp(-Math.abs((x-alpha)/beta)); + return (x <= alpha) ? 0.5 * Math.exp(-Math.abs((x - alpha) / beta)) : 1 - 0.5 * Math.exp(-Math.abs((x - alpha) / beta)); } /** @summary density function for Student's t- distribution * @memberof Math */ function Student(T, ndf) { - if (ndf < 1) return 0; + if (ndf < 1) + return 0; const r = ndf, - rh = 0.5*r, + rh = 0.5 * r, rh1 = rh + 0.5, - denom = Math.sqrt(r*Math.PI)*gamma(rh)*Math.pow(1+T*T/r, rh1); - return gamma(rh1)/denom; + denom = Math.sqrt(r * Math.PI) * gamma(rh) * Math.pow(1 + T * T / r, rh1); + return gamma(rh1) / denom; } /** @summary cumulative distribution function of Student's @@ -1652,14 +1692,15 @@ function StudentI(T, ndf) { const r = ndf; return (T > 0) - ? (1 - 0.5*BetaIncomplete((r/(r + T*T)), r*0.5, 0.5)) - : 0.5*BetaIncomplete((r/(r + T*T)), r*0.5, 0.5); + ? (1 - 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5)) + : 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5); } /** @summary LogNormal function * @memberof Math */ function LogNormal(x, sigma, theta = 0, m = 1) { - if ((x < theta) || (sigma <= 0) || (m <= 0)) return 0; + if ((x < theta) || (sigma <= 0) || (m <= 0)) + return 0; return lognormal_pdf(x, Math.log(m), sigma, theta); } @@ -1667,110 +1708,118 @@ function LogNormal(x, sigma, theta = 0, m = 1) { * @memberof Math */ function BetaDist(x, p, q) { if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; + return 0; const beta = Beta(p, q); - return Math.pow(x, p-1) * Math.pow(1-x, q-1) / beta; + return Math.pow(x, p - 1) * Math.pow(1 - x, q - 1) / beta; } /** @summary Computes the distribution function of the Beta distribution. * @memberof Math */ function BetaDistI(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) return 0; + if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) + return 0; return BetaIncomplete(x, p, q); } /** @summary gaus function for TFormula * @memberof Math */ function gaus(f, x, i) { - return f.GetParValue(i+0) * Math.exp(-0.5 * Math.pow((x-f.GetParValue(i+1)) / f.GetParValue(i+2), 2)); + return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)); } /** @summary gausn function for TFormula * @memberof Math */ function gausn(f, x, i) { - return gaus(f, x, i)/(Math.sqrt(2 * Math.PI) * f.GetParValue(i+2)); + return gaus(f, x, i) / (Math.sqrt(2 * Math.PI) * f.GetParValue(i + 2)); } /** @summary gausxy function for TFormula * @memberof Math */ function gausxy(f, x, y, i) { - return f.GetParValue(i+0) * Math.exp(-0.5 * Math.pow((x-f.GetParValue(i+1)) / f.GetParValue(i+2), 2)) - * Math.exp(-0.5 * Math.pow((y-f.GetParValue(i+3)) / f.GetParValue(i+4), 2)); + return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)) + * Math.exp(-0.5 * Math.pow((y - f.GetParValue(i + 3)) / f.GetParValue(i + 4), 2)); } /** @summary expo function for TFormula * @memberof Math */ function expo(f, x, i) { - return Math.exp(f.GetParValue(i+0) + f.GetParValue(i+1) * x); + return Math.exp(f.GetParValue(i + 0) + f.GetParValue(i + 1) * x); } /** @summary landau function for TFormula * @memberof Math */ function landau(f, x, i) { - return Landau(x, f.GetParValue(i+1),f.GetParValue(i+2), false); + return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), false); } /** @summary landaun function for TFormula * @memberof Math */ function landaun(f, x, i) { - return Landau(x, f.GetParValue(i+1),f.GetParValue(i+2), true); + return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), true); } /** @summary Crystal ball function * @memberof Math */ function crystalball_function(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) return 0.; - let z = (x - mean)/sigma; - if (alpha < 0) z = -z; + if (sigma < 0.) + return 0.; + let z = (x - mean) / sigma; + if (alpha < 0) + z = -z; const abs_alpha = Math.abs(alpha); if (z > -abs_alpha) return Math.exp(-0.5 * z * z); - const nDivAlpha = n/abs_alpha, - AA = Math.exp(-0.5*abs_alpha*abs_alpha), + const nDivAlpha = n / abs_alpha, + AA = Math.exp(-0.5 * abs_alpha * abs_alpha), B = nDivAlpha - abs_alpha, - arg = nDivAlpha/(B-z); - return AA * Math.pow(arg,n); + arg = nDivAlpha / (B - z); + return AA * Math.pow(arg,n); } /** @summary pdf definition of the crystal_ball which is defined only for n > 1 otherwise integral is diverging * @memberof Math */ function crystalball_pdf(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) return 0.; - if (n <= 1) return Number.NaN; // pdf is not normalized for n <=1 + if (sigma < 0.) + return 0.; + if (n <= 1) + return Number.NaN; // pdf is not normalized for n <=1 const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1.+erf(abs_alpha/Math.sqrt(2.))), - N = 1./(sigma*(C+D)); + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + N = 1. / (sigma * (C + D)); return N * crystalball_function(x,alpha,n,sigma,mean); } /** @summary compute the integral of the crystal ball function * @memberof Math */ function crystalball_integral(x, alpha, n, sigma, mean = 0) { - if (sigma == 0) return 0; - if (alpha == 0) return 0.; + if (sigma == 0) + return 0; + if (alpha == 0) + return 0.; const useLog = (n == 1.0), abs_alpha = Math.abs(alpha); - let z = (x-mean)/sigma, intgaus = 0., intpow = 0.; - if (alpha < 0 ) z = -z; + let z = (x - mean) / sigma, intgaus = 0., intpow = 0.; + if (alpha < 0 ) + z = -z; - const sqrtpiover2 = Math.sqrt(M_PI/2.), - sqrt2pi = Math.sqrt( 2.*M_PI), - oneoversqrt2 = 1./Math.sqrt(2.); + const sqrtpiover2 = Math.sqrt(M_PI / 2.), + sqrt2pi = Math.sqrt( 2. * M_PI), + oneoversqrt2 = 1. / Math.sqrt(2.); if (z <= -abs_alpha) { - const A = Math.pow(n/abs_alpha,n) * Math.exp(-0.5 * alpha*alpha), - B = n/abs_alpha - abs_alpha; + const A = Math.pow(n / abs_alpha,n) * Math.exp(-0.5 * alpha * alpha), + B = n / abs_alpha - abs_alpha; if (!useLog) { - const C = (n/abs_alpha) * (1./(n-1)) * Math.exp(-alpha*alpha/2.); - intpow = C - A /(n-1.) * Math.pow(B-z,-n+1); + const C = (n / abs_alpha) * (1. / (n - 1)) * Math.exp(-alpha * alpha / 2.); + intpow = C - A / (n - 1.) * Math.pow(B - z,-n + 1); } else { // for n=1 the primitive of 1/x is log(x) intpow = -A * Math.log( n / abs_alpha ) + A * Math.log(B - z); } - intgaus = sqrtpiover2*(1. + erf(abs_alpha*oneoversqrt2)); + intgaus = sqrtpiover2 * (1. + erf(abs_alpha * oneoversqrt2)); } else { intgaus = normal_cdf_c(z, 1); intgaus *= sqrt2pi; @@ -1786,12 +1835,12 @@ function crystalball_cdf(x, alpha, n, sigma, mean = 0) { return Number.NaN; const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1. + erf(abs_alpha/Math.sqrt(2.))), - totIntegral = sigma*(C+D), + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + totIntegral = sigma * (C + D), integral = crystalball_integral(x,alpha,n,sigma,mean); - return (alpha > 0) ? 1. - integral/totIntegral : integral/totIntegral; + return (alpha > 0) ? 1. - integral / totIntegral : integral / totIntegral; } /** @summary crystalball_cdf_c function @@ -1801,12 +1850,12 @@ function crystalball_cdf_c(x, alpha, n, sigma, mean = 0) { return Number.NaN; const abs_alpha = Math.abs(alpha), - C = n/abs_alpha * 1./(n-1.) * Math.exp(-alpha*alpha/2.), - D = Math.sqrt(M_PI/2.)*(1. + erf(abs_alpha/Math.sqrt(2.))), - totIntegral = sigma*(C+D), + C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), + D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), + totIntegral = sigma * (C + D), integral = crystalball_integral(x,alpha,n,sigma,mean); - return (alpha > 0) ? integral/totIntegral : 1. - (integral/totIntegral); + return (alpha > 0) ? integral / totIntegral : 1. - (integral / totIntegral); } /** @summary ChebyshevN function @@ -1833,13 +1882,13 @@ function Chebyshev0(_x, c0) { /** @summary Chebyshev1 function * @memberof Math */ function Chebyshev1(x, c0, c1) { - return c0 + c1*x; + return c0 + c1 * x; } /** @summary Chebyshev2 function * @memberof Math */ function Chebyshev2(x, c0, c1, c2) { - return c0 + c1*x + c2*(2.0*x*x - 1.0); + return c0 + c1 * x + c2 * (2.0 * x * x - 1.0); } /** @summary Chebyshev3 function @@ -1892,22 +1941,23 @@ function Chebyshev10(x, ...args) { // ========================================================================= -/** @summary Caluclate ClopperPearson +/** @summary Calculate ClopperPearson * @memberof Math */ function eff_ClopperPearson(total,passed,level,bUpper) { const alpha = (1.0 - level) / 2; if (bUpper) - return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total-passed)); + return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total - passed)); - return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total-passed+1.0)); + return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total - passed + 1.0)); } -/** @summary Caluclate normal +/** @summary Calculate normal * @memberof Math */ function eff_Normal(total,passed,level,bUpper) { - if (total == 0) return bUpper ? 1 : 0; + if (total == 0) + return bUpper ? 1 : 0; - const alpha = (1.0 - level)/2, + const alpha = (1.0 - level) / 2, average = passed / total, sigma = Math.sqrt(average * (1 - average) / total), delta = normal_quantile(1 - alpha, sigma); @@ -1921,12 +1971,13 @@ function eff_Normal(total,passed,level,bUpper) { /** @summary Calculates the boundaries for the frequentist Wilson interval * @memberof Math */ function eff_Wilson(total,passed,level,bUpper) { - const alpha = (1.0 - level)/2; - if (total == 0) return bUpper ? 1 : 0; + const alpha = (1.0 - level) / 2; + if (total == 0) + return bUpper ? 1 : 0; const average = passed / total, kappa = normal_quantile(1 - alpha,1), mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa / (total + kappa*kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); + delta = kappa / (total + kappa * kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); if (bUpper) return ((mode + delta) > 1) ? 1.0 : (mode + delta); @@ -1937,21 +1988,21 @@ function eff_Wilson(total,passed,level,bUpper) { /** @summary Calculates the boundaries for the frequentist Agresti-Coull interval * @memberof Math */ function eff_AgrestiCoull(total,passed,level,bUpper) { - const alpha = (1.0 - level)/2, + const alpha = (1.0 - level) / 2, kappa = normal_quantile(1 - alpha,1), mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), delta = kappa * Math.sqrt(mode * (1 - mode) / (total + kappa * kappa)); - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); + if (bUpper) + return ((mode + delta) > 1) ? 1.0 : (mode + delta); - return ((mode - delta) < 0) ? 0.0 : (mode - delta); + return ((mode - delta) < 0) ? 0.0 : (mode - delta); } /** @summary Calculates the boundaries using the mid-P binomial * @memberof Math */ function eff_MidPInterval(total,passed,level,bUpper) { - const alpha = 1. - level, equal_tailed = true, alpha_min = equal_tailed ? alpha/2 : alpha, tol = 1e-9; // tolerance + const alpha = 1. - level, equal_tailed = true, alpha_min = equal_tailed ? alpha / 2 : alpha, tol = 1e-9; // tolerance let pmin = 0, pmax = 1, p = 0; // treat special case for 0<passed<1 @@ -1964,15 +2015,17 @@ function eff_MidPInterval(total,passed,level,bUpper) { } while (Math.abs(pmax - pmin) > tol) { - p = (pmin + pmax)/2; + p = (pmin + pmax) / 2; // double v = 0.5 * ROOT::Math::binomial_pdf(int(passed), p, int(total)); // make it work for non integer using the binomial - beta relationship - let v = 0.5 * beta_pdf(p, passed+1., total-passed+1)/(total+1); - // if (passed > 0) v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); + let v = 0.5 * beta_pdf(p, passed + 1., total - passed + 1) / (total + 1); + // if (passed > 0) + // v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); // compute the binomial cdf at passed -1 - if ( (passed-1) >= 0) v += beta_cdf_c(p, passed, total-passed+1); + if ((passed - 1) >= 0) + v += beta_cdf_c(p, passed, total - passed + 1); - const vmin = bUpper ? alpha_min : 1.- alpha_min; + const vmin = bUpper ? alpha_min : 1. - alpha_min; if (v > vmin) pmin = p; else @@ -1986,43 +2039,48 @@ function eff_MidPInterval(total,passed,level,bUpper) { * @memberof Math */ function eff_Bayesian(total,passed,level,bUpper,alpha,beta) { const a = passed + alpha, - b = total - passed + beta; + b = total - passed + beta; if (bUpper) { if ((a > 0) && (b > 0)) - return beta_quantile((1+level)/2,a,b); + return beta_quantile((1 + level) / 2,a,b); else return 1; - } else { - if ((a > 0) && (b > 0)) - return beta_quantile((1-level)/2,a,b); - else - return 0; - } + } else if ((a > 0) && (b > 0)) + return beta_quantile((1 - level) / 2,a,b); + + return 0; } /** @summary Return function to calculate boundary of TEfficiency * @memberof Math */ function getTEfficiencyBoundaryFunc(option, isbayessian) { const kFCP = 0, // Clopper-Pearson interval (recommended by PDG) - kFNormal = 1, // Normal approximation - kFWilson = 2, // Wilson interval - kFAC = 3, // Agresti-Coull interval - kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript - // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) - // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) - // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) - kMidP = 8; // Mid-P Lancaster interval + kFNormal = 1, // Normal approximation + kFWilson = 2, // Wilson interval + kFAC = 3, // Agresti-Coull interval + kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript + // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) + // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) + // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) + kMidP = 8; // Mid-P Lancaster interval if (isbayessian) return eff_Bayesian; switch (option) { - case kFCP: return eff_ClopperPearson; - case kFNormal: return eff_Normal; - case kFWilson: return eff_Wilson; - case kFAC: return eff_AgrestiCoull; - case kFFC: console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); return eff_ClopperPearson; - case kMidP: return eff_MidPInterval; + case kFCP: + return eff_ClopperPearson; + case kFNormal: + return eff_Normal; + case kFWilson: + return eff_Wilson; + case kFAC: + return eff_AgrestiCoull; + case kFFC: + console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); + return eff_ClopperPearson; + case kMidP: + return eff_MidPInterval; // case kBJeffrey: // case kBUniform: // case kBBayesian: return eff_ClopperPearson; @@ -2071,7 +2129,6 @@ function InvPi() } - export { gamma, gamma as tgamma, gamma as Gamma, Polynomialeval, Polynomial1eval, stirf, gamma_pdf, ndtri, normal_quantile, normal_quantile_c, lognormal_cdf_c, lognormal_cdf, diff --git a/modules/base/md5.mjs b/modules/base/md5.mjs deleted file mode 100644 index 36b674c3f..000000000 --- a/modules/base/md5.mjs +++ /dev/null @@ -1,275 +0,0 @@ -/* - * JavaScript MD5 - * https://github.com/blueimp/JavaScript-MD5 - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * https://opensource.org/licenses/MIT - * - * Based on - * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message - * Digest Algorithm, as defined in RFC 1321. - * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for more info. - */ - - -/** @private */ -function safeAdd(x, y) { - const lsw = (x & 0xffff) + (y & 0xffff), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xffff); -} - -/** @private */ -function bitRotateLeft(num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)); -} - -/** @private */ -function md5cmn(q, a, b, x, s, t) { - return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); -} - -/** @private */ -function md5ff(a, b, c, d, x, s, t) { - return md5cmn((b & c) | (~b & d), a, b, x, s, t); -} - -/** @private */ -function md5gg(a, b, c, d, x, s, t) { - return md5cmn((b & d) | (c & ~d), a, b, x, s, t); -} - -/** @private */ -function md5hh(a, b, c, d, x, s, t) { - return md5cmn(b ^ c ^ d, a, b, x, s, t); -} - -/** @private */ -function md5ii(a, b, c, d, x, s, t) { - return md5cmn(c ^ (b | ~d), a, b, x, s, t); -} - -/** - * Calculate the MD5 of an array of little-endian words, and a bit length. - * - * @param {Array} x Array of little-endian words - * @param {number} len Bit length - * @returns {Array<number>} MD5 Array - * @private */ -function binlMD5(x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << len % 32; - x[(((len + 64) >>> 9) << 4) + 14] = len; - - let a = 1732584193, - b = -271733879, - c = -1732584194, - d = 271733878; - - for (let i = 0; i < x.length; i += 16) { - const olda = a, oldb = b, oldc = c, oldd = d; - - a = md5ff(a, b, c, d, x[i], 7, -680876936); - d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); - c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); - b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); - a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); - d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); - c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); - b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); - a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); - d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); - c = md5ff(c, d, a, b, x[i + 10], 17, -42063); - b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); - a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); - d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); - c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); - b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); - - a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); - d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); - c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); - b = md5gg(b, c, d, a, x[i], 20, -373897302); - a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); - d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); - c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); - b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); - a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); - d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); - c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); - b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); - a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); - d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); - c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); - b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); - - a = md5hh(a, b, c, d, x[i + 5], 4, -378558); - d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); - c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); - b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); - a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); - d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); - c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); - b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); - a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); - d = md5hh(d, a, b, c, x[i], 11, -358537222); - c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); - b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); - a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); - d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); - c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); - b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); - - a = md5ii(a, b, c, d, x[i], 6, -198630844); - d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); - c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); - b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); - a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); - d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); - c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); - b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); - a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); - d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); - c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); - b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); - a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); - d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); - c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); - b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); - - a = safeAdd(a, olda); - b = safeAdd(b, oldb); - c = safeAdd(c, oldc); - d = safeAdd(d, oldd); - } - return [a, b, c, d]; -} - -/** - * Convert an array of little-endian words to a string - * - * @param {Array<number>} input MD5 Array - * @returns {string} MD5 string - */ -function binl2rstr(input) { - let output = ''; - const length32 = input.length * 32; - for (let i = 0; i < length32; i += 8) - output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); - return output; -} - -/** - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - * - * @param {string} input Raw input string - * @returns {Array<number>} Array of little-endian words - */ -function rstr2binl(input) { - const output = []; - output[(input.length >> 2) - 1] = undefined; - for (let i = 0; i < output.length; i += 1) - output[i] = 0; - const length8 = input.length * 8; - for (let i = 0; i < length8; i += 8) - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; - return output; -} - -function rstr2binl_2(input, arr) { - const fulllen = input.length + arr.length, - output = []; - - output[(fulllen >> 2) - 1] = undefined; - for (let i = 0; i < output.length; i += 1) - output[i] = 0; - const length8 = input.length * 8; - for (let i = 0; i < length8; i += 8) - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; - - const length8_2 = arr.length * 8; - for (let i2 = 0; i2 < length8_2; i2 += 8) { - const i = length8 + i2; - output[i >> 5] |= (arr[i2 / 8] & 0xff) << i % 32; - } - - return output; -} - -/** - * Calculate the MD5 of a raw string - * - * @param {string} s Input string - * @returns {string} Raw MD5 string - */ -function rstrMD5(s) { - return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); -} - -function rstrMD5_2(s, arr) { - return binl2rstr(binlMD5(rstr2binl_2(s, arr), (s.length + arr.length) * 8)); -} - -/** - * Convert a raw string to a hex string - * - * @param {string} input Raw input string - * @returns {string} Hex encoded string - */ -function rstr2hex(input) { - const hexTab = '0123456789abcdef'; - let output = ''; - for (let i = 0; i < input.length; i += 1) { - const x = input.charCodeAt(i); - output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f); - } - return output; -} - -/** - * Encode a string as UTF-8 - * - * @param {string} input Input string - * @returns {string} UTF8 string - */ -function str2rstrUTF8(input) { - return decodeURIComponent(encodeURIComponent(input)); -} - -/** - * Encodes input string as raw MD5 string - * - * @param {string} s Input string - * @returns {string} Raw MD5 string - */ -function rawMD5(s) { - return rstrMD5(str2rstrUTF8(s)); -} - -function rawMD5_2(s, arr) { - return rstrMD5_2(str2rstrUTF8(s), arr); -} - -/** - * Encodes input string as Hex encoded string - * - * @param {string} s Input string - * @returns {string} Hex encoded string - */ -function hexMD5(s) { - return rstr2hex(rawMD5(s)); -} - -function hexMD5_2(s, arr) { - return rstr2hex(rawMD5_2(s, arr)); -} - - -export { hexMD5, hexMD5_2 }; diff --git a/modules/base/sha256.mjs b/modules/base/sha256.mjs index 39de41622..8c6adc6e8 100644 --- a/modules/base/sha256.mjs +++ b/modules/base/sha256.mjs @@ -108,7 +108,7 @@ class Sha256 { } if (this.bytes > 4294967295) { this.hBytes += this.bytes / 4294967296 << 0; - this.bytes = this.bytes % 4294967296; + this.bytes %= 4294967296; } return this; } @@ -142,7 +142,7 @@ class Sha256 { h = this.h7, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; for (j = 16; j < 64; ++j) { - // rightrotate + // right rotate t1 = blocks[j - 15]; s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); t1 = blocks[j - 2]; diff --git a/modules/base/svg2pdf.mjs b/modules/base/svg2pdf.mjs new file mode 100644 index 000000000..a50e2e2fa --- /dev/null +++ b/modules/base/svg2pdf.mjs @@ -0,0 +1,5920 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015-2023 yWorks GmbH + * Copyright (c) 2013-2015 by Vitaly Puzrin + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { jsPDF, GState, ShadingPattern, TilingPattern } from './jspdf.mjs'; + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types */ +var RGBColor = /** @class */ (function () { + function RGBColor(colorString) { + this.a = undefined; + this.r = 0; + this.g = 0; + this.b = 0; + this.simpleColors = {}; + // eslint-disable-next-line @typescript-eslint/ban-types + this.colorDefs = []; + this.ok = false; + if (!colorString) { + return; + } + // strip any leading # + if (colorString.charAt(0) == '#') { + // remove # if any + colorString = colorString.substr(1, 6); + } + colorString = colorString.replace(/ /g, ''); + colorString = colorString.toLowerCase(); + // before getting into regexps, try simple matches + // and overwrite the input + this.simpleColors = { + aliceblue: 'f0f8ff', + antiquewhite: 'faebd7', + aqua: '00ffff', + aquamarine: '7fffd4', + azure: 'f0ffff', + beige: 'f5f5dc', + bisque: 'ffe4c4', + black: '000000', + blanchedalmond: 'ffebcd', + blue: '0000ff', + blueviolet: '8a2be2', + brown: 'a52a2a', + burlywood: 'deb887', + cadetblue: '5f9ea0', + chartreuse: '7fff00', + chocolate: 'd2691e', + coral: 'ff7f50', + cornflowerblue: '6495ed', + cornsilk: 'fff8dc', + crimson: 'dc143c', + cyan: '00ffff', + darkblue: '00008b', + darkcyan: '008b8b', + darkgoldenrod: 'b8860b', + darkgray: 'a9a9a9', + darkgrey: 'a9a9a9', + darkgreen: '006400', + darkkhaki: 'bdb76b', + darkmagenta: '8b008b', + darkolivegreen: '556b2f', + darkorange: 'ff8c00', + darkorchid: '9932cc', + darkred: '8b0000', + darksalmon: 'e9967a', + darkseagreen: '8fbc8f', + darkslateblue: '483d8b', + darkslategray: '2f4f4f', + darkslategrey: '2f4f4f', + darkturquoise: '00ced1', + darkviolet: '9400d3', + deeppink: 'ff1493', + deepskyblue: '00bfff', + dimgray: '696969', + dimgrey: '696969', + dodgerblue: '1e90ff', + feldspar: 'd19275', + firebrick: 'b22222', + floralwhite: 'fffaf0', + forestgreen: '228b22', + fuchsia: 'ff00ff', + gainsboro: 'dcdcdc', + ghostwhite: 'f8f8ff', + gold: 'ffd700', + goldenrod: 'daa520', + gray: '808080', + grey: '808080', + green: '008000', + greenyellow: 'adff2f', + honeydew: 'f0fff0', + hotpink: 'ff69b4', + indianred: 'cd5c5c', + indigo: '4b0082', + ivory: 'fffff0', + khaki: 'f0e68c', + lavender: 'e6e6fa', + lavenderblush: 'fff0f5', + lawngreen: '7cfc00', + lemonchiffon: 'fffacd', + lightblue: 'add8e6', + lightcoral: 'f08080', + lightcyan: 'e0ffff', + lightgoldenrodyellow: 'fafad2', + lightgray: 'd3d3d3', + lightgrey: 'd3d3d3', + lightgreen: '90ee90', + lightpink: 'ffb6c1', + lightsalmon: 'ffa07a', + lightseagreen: '20b2aa', + lightskyblue: '87cefa', + lightslateblue: '8470ff', + lightslategray: '778899', + lightslategrey: '778899', + lightsteelblue: 'b0c4de', + lightyellow: 'ffffe0', + lime: '00ff00', + limegreen: '32cd32', + linen: 'faf0e6', + magenta: 'ff00ff', + maroon: '800000', + mediumaquamarine: '66cdaa', + mediumblue: '0000cd', + mediumorchid: 'ba55d3', + mediumpurple: '9370d8', + mediumseagreen: '3cb371', + mediumslateblue: '7b68ee', + mediumspringgreen: '00fa9a', + mediumturquoise: '48d1cc', + mediumvioletred: 'c71585', + midnightblue: '191970', + mintcream: 'f5fffa', + mistyrose: 'ffe4e1', + moccasin: 'ffe4b5', + navajowhite: 'ffdead', + navy: '000080', + oldlace: 'fdf5e6', + olive: '808000', + olivedrab: '6b8e23', + orange: 'ffa500', + orangered: 'ff4500', + orchid: 'da70d6', + palegoldenrod: 'eee8aa', + palegreen: '98fb98', + paleturquoise: 'afeeee', + palevioletred: 'd87093', + papayawhip: 'ffefd5', + peachpuff: 'ffdab9', + peru: 'cd853f', + pink: 'ffc0cb', + plum: 'dda0dd', + powderblue: 'b0e0e6', + purple: '800080', + red: 'ff0000', + rosybrown: 'bc8f8f', + royalblue: '4169e1', + saddlebrown: '8b4513', + salmon: 'fa8072', + sandybrown: 'f4a460', + seagreen: '2e8b57', + seashell: 'fff5ee', + sienna: 'a0522d', + silver: 'c0c0c0', + skyblue: '87ceeb', + slateblue: '6a5acd', + slategray: '708090', + slategrey: '708090', + snow: 'fffafa', + springgreen: '00ff7f', + steelblue: '4682b4', + tan: 'd2b48c', + teal: '008080', + thistle: 'd8bfd8', + tomato: 'ff6347', + turquoise: '40e0d0', + violet: 'ee82ee', + violetred: 'd02090', + wheat: 'f5deb3', + white: 'ffffff', + whitesmoke: 'f5f5f5', + yellow: 'ffff00', + yellowgreen: '9acd32' + }; + for (var key in this.simpleColors) { + if (colorString == key) { + colorString = this.simpleColors[key]; + } + } + // emd of simple type-in colors + // array of color definition objects + this.colorDefs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], + process: function (bits) { + return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])]; + } + }, + { + re: /^rgb\(([0-9.]+)%,\s*([0-9.]+)%,\s*([0-9.]+)%\)$/, + example: ['rgb(50.5%, 25.75%, 75.5%)', 'rgb(100%,0%,0%)'], + process: function (bits) { + return [ + Math.round(parseFloat(bits[1]) * 2.55), + Math.round(parseFloat(bits[2]) * 2.55), + Math.round(parseFloat(bits[3]) * 2.55) + ]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ['#00ff00', '336699'], + process: function (bits) { + return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16)]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ['#fb0', 'f0f'], + process: function (bits) { + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; + // search through the definitions to find a match + for (var i = 0; i < this.colorDefs.length; i++) { + var re = this.colorDefs[i].re; + var processor = this.colorDefs[i].process; + var bits = re.exec(colorString); + if (bits) { + var channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + } + // validate/cleanup values + this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r; + this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g; + this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; + } + RGBColor.prototype.toRGB = function () { + return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; + }; + RGBColor.prototype.toRGBA = function () { + return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + (this.a || '1') + ')'; + }; + RGBColor.prototype.toHex = function () { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) + r = '0' + r; + if (g.length == 1) + g = '0' + g; + if (b.length == 1) + b = '0' + b; + return '#' + r + g + b; + }; + // help + RGBColor.prototype.getHelpXML = function () { + var examples = []; + // add regexps + for (var i = 0; i < this.colorDefs.length; i++) { + var example = this.colorDefs[i].example; + for (var j = 0; j < example.length; j++) { + examples[examples.length] = example[j]; + } + } + // add type-in colors + for (var sc in this.simpleColors) { + examples[examples.length] = sc; + } + var xml = document.createElement('ul'); + xml.setAttribute('id', 'rgbcolor-examples'); + for (var i = 0; i < examples.length; i++) { + try { + var listItem = document.createElement('li'); + var listColor = new RGBColor(examples[i]); + var exampleDiv = document.createElement('div'); + exampleDiv.style.cssText = + 'margin: 3px; ' + + 'border: 1px solid black; ' + + 'background:' + + listColor.toHex() + + '; ' + + 'color:' + + listColor.toHex(); + exampleDiv.appendChild(document.createTextNode('test')); + var listItemValue = document.createTextNode(' ' + examples[i] + ' -> ' + listColor.toRGB() + ' -> ' + listColor.toHex()); + listItem.appendChild(exampleDiv); + listItem.appendChild(listItemValue); + xml.appendChild(listItem); + } + catch (e) { } + } + return xml; + }; + return RGBColor; +}()); + +var ColorFill = /** @class */ (function () { + function ColorFill(color) { + this.color = color; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ColorFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, undefined]; + }); + }); + }; + return ColorFill; +}()); + +var AttributeState = /** @class */ (function () { + function AttributeState() { + this.xmlSpace = ''; + this.whiteSpace = ''; + this.fill = null; + this.fillOpacity = 1.0; + // public fillRule: string = null + this.fontFamily = ''; + this.fontSize = 16; + this.fontStyle = ''; + // public fontVariant: string + this.fontWeight = ''; + this.opacity = 1.0; + this.stroke = null; + this.strokeDasharray = null; + this.strokeDashoffset = 0; + this.strokeLinecap = ''; + this.strokeLinejoin = ''; + this.strokeMiterlimit = 4.0; + this.strokeOpacity = 1.0; + this.strokeWidth = 1.0; + // public textAlign: string + this.alignmentBaseline = ''; + this.textAnchor = ''; + this.visibility = ''; + this.color = null; + this.contextFill = null; + this.contextStroke = null; + this.fillRule = null; + } + AttributeState.prototype.clone = function () { + var clone = new AttributeState(); + clone.xmlSpace = this.xmlSpace; + clone.whiteSpace = this.whiteSpace; + clone.fill = this.fill; + clone.fillOpacity = this.fillOpacity; + // clone.fillRule = this.fillRule; + clone.fontFamily = this.fontFamily; + clone.fontSize = this.fontSize; + clone.fontStyle = this.fontStyle; + // clone.fontVariant = this.fontVariant; + clone.fontWeight = this.fontWeight; + clone.opacity = this.opacity; + clone.stroke = this.stroke; + clone.strokeDasharray = this.strokeDasharray; + clone.strokeDashoffset = this.strokeDashoffset; + clone.strokeLinecap = this.strokeLinecap; + clone.strokeLinejoin = this.strokeLinejoin; + clone.strokeMiterlimit = this.strokeMiterlimit; + clone.strokeOpacity = this.strokeOpacity; + clone.strokeWidth = this.strokeWidth; + // clone.textAlign = this.textAlign; + clone.textAnchor = this.textAnchor; + clone.alignmentBaseline = this.alignmentBaseline; + clone.visibility = this.visibility; + clone.color = this.color; + clone.fillRule = this.fillRule; + clone.contextFill = this.contextFill; + clone.contextStroke = this.contextStroke; + return clone; + }; + AttributeState.default = function () { + var attributeState = new AttributeState(); + attributeState.xmlSpace = 'default'; + attributeState.whiteSpace = 'normal'; + attributeState.fill = new ColorFill(new RGBColor('rgb(0, 0, 0)')); + attributeState.fillOpacity = 1.0; + // attributeState.fillRule = "nonzero"; + attributeState.fontFamily = 'times'; + attributeState.fontSize = 16; + attributeState.fontStyle = 'normal'; + // attributeState.fontVariant = "normal"; + attributeState.fontWeight = 'normal'; + attributeState.opacity = 1.0; + attributeState.stroke = null; + attributeState.strokeDasharray = null; + attributeState.strokeDashoffset = 0; + attributeState.strokeLinecap = 'butt'; + attributeState.strokeLinejoin = 'miter'; + attributeState.strokeMiterlimit = 4.0; + attributeState.strokeOpacity = 1.0; + attributeState.strokeWidth = 1.0; + // attributeState.textAlign = "start"; + attributeState.alignmentBaseline = 'baseline'; + attributeState.textAnchor = 'start'; + attributeState.visibility = 'visible'; + attributeState.color = new RGBColor('rgb(0, 0, 0)'); + attributeState.fillRule = 'nonzero'; + attributeState.contextFill = null; + attributeState.contextStroke = null; + return attributeState; + }; + AttributeState.getContextColors = function (context, includeCurrentColor) { + if (includeCurrentColor === void 0) { includeCurrentColor = false; } + var colors = {}; + if (context.attributeState.contextFill) { + colors['contextFill'] = context.attributeState.contextFill; + } + if (context.attributeState.contextStroke) { + colors['contextStroke'] = context.attributeState.contextStroke; + } + if (includeCurrentColor && context.attributeState.color) { + colors['color'] = context.attributeState.color; + } + return colors; + }; + return AttributeState; +}()); + +/** + * + * @package + * @param values + * @constructor + * @property pdf + * @property attributeState Keeps track of parent attributes that are inherited automatically + * @property refsHandler The handler that will render references on demand + * @property styleSheets + * @property textMeasure + * @property transform The current transformation matrix + * @property withinClipPath + */ +var Context = /** @class */ (function () { + function Context(pdf, values) { + var _a, _b, _c; + this.pdf = pdf; + this.svg2pdfParameters = values.svg2pdfParameters; + this.attributeState = values.attributeState + ? values.attributeState.clone() + : AttributeState.default(); + this.viewport = values.viewport; + this.refsHandler = values.refsHandler; + this.styleSheets = values.styleSheets; + this.textMeasure = values.textMeasure; + this.transform = (_a = values.transform) !== null && _a !== void 0 ? _a : this.pdf.unitMatrix; + this.withinClipPath = (_b = values.withinClipPath) !== null && _b !== void 0 ? _b : false; + this.withinUse = (_c = values.withinUse) !== null && _c !== void 0 ? _c : false; + } + Context.prototype.clone = function (values) { + var _a, _b, _c, _d; + if (values === void 0) { values = {}; } + return new Context(this.pdf, { + svg2pdfParameters: this.svg2pdfParameters, + attributeState: values.attributeState + ? values.attributeState.clone() + : this.attributeState.clone(), + viewport: (_a = values.viewport) !== null && _a !== void 0 ? _a : this.viewport, + refsHandler: this.refsHandler, + styleSheets: this.styleSheets, + textMeasure: this.textMeasure, + transform: (_b = values.transform) !== null && _b !== void 0 ? _b : this.transform, + withinClipPath: (_c = values.withinClipPath) !== null && _c !== void 0 ? _c : this.withinClipPath, + withinUse: (_d = values.withinUse) !== null && _d !== void 0 ? _d : this.withinUse + }); + }; + return Context; +}()); + +var ReferencesHandler = /** @class */ (function () { + function ReferencesHandler(idMap) { + this.renderedElements = {}; + this.idMap = idMap; + this.idPrefix = String(ReferencesHandler.instanceCounter++); + } + ReferencesHandler.prototype.getRendered = function (id, contextColors, renderCallback) { + return __awaiter(this, void 0, void 0, function () { + var key, svgNode; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + key = this.generateKey(id, contextColors); + if (this.renderedElements.hasOwnProperty(key)) { + return [2 /*return*/, this.renderedElements[id]]; + } + svgNode = this.get(id); + this.renderedElements[key] = svgNode; + return [4 /*yield*/, renderCallback(svgNode)]; + case 1: + _a.sent(); + return [2 /*return*/, svgNode]; + } + }); + }); + }; + ReferencesHandler.prototype.get = function (id) { + // return this.idMap[cssEsc(id, { isIdentifier: true })] + return this.idMap[id]; // jsroot uses plain ids + }; + ReferencesHandler.prototype.generateKey = function (id, contextColors) { + var colorHash = ''; + var keys = ['color', 'contextFill', 'contextStroke']; + if (contextColors) { + colorHash = keys.map(function (key) { var _a, _b; return (_b = (_a = contextColors[key]) === null || _a === void 0 ? void 0 : _a.toRGBA()) !== null && _b !== void 0 ? _b : ''; }).join('|'); + } + return this.idPrefix + '|' + id + '|' + colorHash; + }; + ReferencesHandler.instanceCounter = 0; + return ReferencesHandler; +}()); + +function getAngle(from, to) { + return Math.atan2(to[1] - from[1], to[0] - from[0]); +} +var cToQ = 2 / 3; // ratio to convert quadratic bezier curves to cubic ones +// transforms a cubic bezier control point to a quadratic one: returns from + (2/3) * (to - from) +function toCubic(from, to) { + return [cToQ * (to[0] - from[0]) + from[0], cToQ * (to[1] - from[1]) + from[1]]; +} +function normalize(v) { + var length = Math.sqrt(v[0] * v[0] + v[1] * v[1]); + return [v[0] / length, v[1] / length]; +} +function getDirectionVector(from, to) { + var v = [to[0] - from[0], to[1] - from[1]]; + return normalize(v); +} +function addVectors(v1, v2) { + return [v1[0] + v2[0], v1[1] + v2[1]]; +} +// multiplies a vector with a matrix: vec' = vec * matrix +function multVecMatrix(vec, matrix) { + var x = vec[0]; + var y = vec[1]; + return [matrix.a * x + matrix.c * y + matrix.e, matrix.b * x + matrix.d * y + matrix.f]; +} + +var Path = /** @class */ (function () { + function Path() { + this.segments = []; + } + Path.prototype.moveTo = function (x, y) { + this.segments.push(new MoveTo(x, y)); + return this; + }; + Path.prototype.lineTo = function (x, y) { + this.segments.push(new LineTo(x, y)); + return this; + }; + Path.prototype.curveTo = function (x1, y1, x2, y2, x, y) { + this.segments.push(new CurveTo(x1, y1, x2, y2, x, y)); + return this; + }; + Path.prototype.close = function () { + this.segments.push(new Close()); + return this; + }; + /** + * Transforms the path in place + */ + Path.prototype.transform = function (matrix) { + this.segments.forEach(function (seg) { + if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { + var p = multVecMatrix([seg.x, seg.y], matrix); + seg.x = p[0]; + seg.y = p[1]; + } + if (seg instanceof CurveTo) { + var p1 = multVecMatrix([seg.x1, seg.y1], matrix); + var p2 = multVecMatrix([seg.x2, seg.y2], matrix); + seg.x1 = p1[0]; + seg.y1 = p1[1]; + seg.x2 = p2[0]; + seg.y2 = p2[1]; + } + }); + }; + Path.prototype.draw = function (context) { + var p = context.pdf; + this.segments.forEach(function (s) { + if (s instanceof MoveTo) { + p.moveTo(s.x, s.y); + } + else if (s instanceof LineTo) { + p.lineTo(s.x, s.y); + } + else if (s instanceof CurveTo) { + p.curveTo(s.x1, s.y1, s.x2, s.y2, s.x, s.y); + } + else { + p.close(); + } + }); + }; + return Path; +}()); +var MoveTo = /** @class */ (function () { + function MoveTo(x, y) { + this.x = x; + this.y = y; + } + return MoveTo; +}()); +var LineTo = /** @class */ (function () { + function LineTo(x, y) { + this.x = x; + this.y = y; + } + return LineTo; +}()); +var CurveTo = /** @class */ (function () { + function CurveTo(x1, y1, x2, y2, x, y) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.x = x; + this.y = y; + } + return CurveTo; +}()); +var Close = /** @class */ (function () { + function Close() { + } + return Close; +}()); + +function nodeIs(node, tagsString) { + return tagsString.split(',').indexOf((node.nodeName || node.tagName).toLowerCase()) >= 0; +} +function forEachChild(node, fn) { + // copy list of children, as the original might be modified + var children = []; + for (var i = 0; i < node.childNodes.length; i++) { + var childNode = node.childNodes[i]; + if (childNode.nodeName.charAt(0) !== '#') + children.push(childNode); + } + for (var i = 0; i < children.length; i++) { + fn(i, children[i]); + } +} +// returns an attribute of a node, either from the node directly or from css +function getAttribute(node, styleSheets, propertyNode, propertyCss) { + var _a; + if (propertyCss === void 0) { propertyCss = propertyNode; } + var attribute = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propertyCss); + if (attribute) { + return attribute; + } + else { + var propertyValue = styleSheets.getPropertyValue(node, propertyCss); + if (propertyValue) { + return propertyValue; + } + else if (node.hasAttribute(propertyNode)) { + return node.getAttribute(propertyNode) || undefined; + } + else { + return undefined; + } + } +} +function svgNodeIsVisible(svgNode, parentVisible, context) { + if (getAttribute(svgNode.element, context.styleSheets, 'display') === 'none') { + return false; + } + var visible = parentVisible; + var visibility = getAttribute(svgNode.element, context.styleSheets, 'visibility'); + if (visibility) { + visible = visibility !== 'hidden'; + } + return visible; +} +function svgNodeAndChildrenVisible(svgNode, parentVisible, context) { + var visible = svgNodeIsVisible(svgNode, parentVisible, context); + if (svgNode.element.childNodes.length === 0) { + return false; + } + svgNode.children.forEach(function (child) { + if (child.isVisible(visible, context)) { + visible = true; + } + }); + return visible; +} + +/** + * @constructor + * @property {Marker[]} markers + */ +var MarkerList = /** @class */ (function () { + function MarkerList() { + this.markers = []; + } + MarkerList.prototype.addMarker = function (markers) { + this.markers.push(markers); + }; + MarkerList.prototype.draw = function (context) { + return __awaiter(this, void 0, void 0, function () { + var i, marker, tf, angle, anchor, cos, sin, contextColors; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + i = 0; + _a.label = 1; + case 1: + if (!(i < this.markers.length)) return [3 /*break*/, 4]; + marker = this.markers[i]; + tf = void 0; + angle = marker.angle, anchor = marker.anchor; + cos = Math.cos(angle); + sin = Math.sin(angle); + // position at and rotate around anchor + tf = context.pdf.Matrix(cos, sin, -sin, cos, anchor[0], anchor[1]); + // scale with stroke-width + tf = context.pdf.matrixMult(context.pdf.Matrix(context.attributeState.strokeWidth, 0, 0, context.attributeState.strokeWidth, 0, 0), tf); + tf = context.pdf.matrixMult(tf, context.transform); + // as the marker is already scaled by the current line width we must not apply the line width twice! + context.pdf.saveGraphicsState(); + contextColors = AttributeState.getContextColors(context); + return [4 /*yield*/, context.refsHandler.getRendered(marker.id, contextColors, function (node) { + return node.apply(context); + })]; + case 2: + _a.sent(); + context.pdf.doFormObject(context.refsHandler.generateKey(marker.id, contextColors), tf); + context.pdf.restoreGraphicsState(); + _a.label = 3; + case 3: + i++; + return [3 /*break*/, 1]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + return MarkerList; +}()); +/** + * @param {string} id + * @param {[number,number]} anchor + * @param {number} angle + */ +var Marker = /** @class */ (function () { + function Marker(id, anchor, angle, isStartMarker) { + if (isStartMarker === void 0) { isStartMarker = false; } + this.id = id; + this.anchor = anchor; + this.angle = angle; + this.isStartMarker = isStartMarker; + } + return Marker; +}()); + +var iriReference = /url\(["']?#([^"']+)["']?\)/; +var alignmentBaselineMap = { + bottom: 'bottom', + 'text-bottom': 'bottom', + top: 'top', + 'text-top': 'top', + hanging: 'hanging', + middle: 'middle', + central: 'middle', + center: 'middle', + mathematical: 'middle', + ideographic: 'ideographic', + alphabetic: 'alphabetic', + baseline: 'alphabetic' +}; +var svgNamespaceURI = 'http://www.w3.org/2000/svg'; + +/** + * Convert em, px and bare number attributes to pixel values + * @param {string} value + * @param {number} pdfFontSize + */ +function toPixels(value, pdfFontSize) { + var match; + // em + match = value && value.toString().match(/^([\-0-9.]+)em$/); + if (match) { + return parseFloat(match[1]) * pdfFontSize; + } + // pixels + match = value && value.toString().match(/^([\-0-9.]+)(px|)$/); + if (match) { + return parseFloat(match[1]); + } + return 0; +} +function mapAlignmentBaseline(value) { + return alignmentBaselineMap[value] || 'alphabetic'; +} + +function parseFloats(str) { + var floats = []; + var regex = /[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.?\d+))(?:[eE][+-]?\d+)?/g; + var match; + while ((match = regex.exec(str))) { + floats.push(parseFloat(match[0])); + } + return floats; +} +/** + * extends RGBColor by rgba colors as RGBColor is not capable of it + * currentcolor: the color to return if colorString === 'currentcolor' + */ +function parseColor(colorString, contextColors) { + if (colorString === 'transparent') { + var transparent = new RGBColor('rgb(0,0,0)'); + transparent.a = 0; + return transparent; + } + if (contextColors && colorString.toLowerCase() === 'currentcolor') { + return contextColors.color || new RGBColor('rgb(0,0,0)'); + } + if (contextColors && colorString.toLowerCase() === 'context-stroke') { + return contextColors.contextStroke || new RGBColor('rgb(0,0,0)'); + } + if (contextColors && colorString.toLowerCase() === 'context-fill') { + return contextColors.contextFill || new RGBColor('rgb(0,0,0)'); + } + var match = /\s*rgba\(((?:[^,\)]*,){3}[^,\)]*)\)\s*/.exec(colorString); + if (match) { + var floats = parseFloats(match[1]); + var color = new RGBColor('rgb(' + floats.slice(0, 3).join(',') + ')'); + color.a = floats[3]; + return color; + } + else { + return new RGBColor(colorString); + } +} + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +var fontFamilyPapandreou; +var hasRequiredFontFamilyPapandreou; + +function requireFontFamilyPapandreou () { + if (hasRequiredFontFamilyPapandreou) return fontFamilyPapandreou; + hasRequiredFontFamilyPapandreou = 1; + // parse + // ===== + + // states + // ------ + + var PLAIN = 0; + var STRINGS = 1; + var ESCAPING = 2; + var IDENTIFIER = 3; + var SEPARATING = 4; + var SPACEAFTERIDENTIFIER = 5; + + // patterns + // -------- + + var identifierPattern = /[a-z0-9_-]/i; + var spacePattern = /[\s\t]/; + + // --- + + var parse = function(str) { + + // vars + // ---- + + var starting = true; + var state = PLAIN; + var buffer = ''; + var i = 0; + var quote; + var c; + + // result + // ------ + + var names = []; + + // parse + // ----- + + while (true) { + + c = str[i]; + + if (state === PLAIN) { + + if (!c && starting) { + + break; + + } else if (!c && !starting) { + + throw new Error('Parse error'); + + } else if (c === '"' || c === "'") { + + quote = c; + state = STRINGS; + starting = false; + + } else if (spacePattern.test(c)) ; else if (identifierPattern.test(c)) { + + state = IDENTIFIER; + starting = false; + i--; + + } else { + + throw new Error('Parse error'); + + } + + } else if (state === STRINGS) { + + if (!c) { + + throw new Error('Parse Error'); + + } else if (c === "\\") { + + state = ESCAPING; + + } else if (c === quote) { + + names.push(buffer); + buffer = ''; + state = SEPARATING; + + } else { + + buffer += c; + + } + + } else if (state === ESCAPING) { + + if (c === quote || c === "\\") { + + buffer += c; + state = STRINGS; + + } else { + + throw new Error('Parse error'); + + } + + } else if (state === IDENTIFIER) { + + if (!c) { + + names.push(buffer); + break; + + } else if (identifierPattern.test(c)) { + + buffer += c; + + } else if (c === ',') { + + names.push(buffer); + buffer = ''; + state = PLAIN; + + } else if (spacePattern.test(c)) { + + state = SPACEAFTERIDENTIFIER; + } else { + + throw new Error('Parse error'); + + } + } else if (state === SPACEAFTERIDENTIFIER) { + + if (!c) { + + names.push(buffer); + break; + + } else if (identifierPattern.test(c)) { + + buffer += ' ' + c; + state = IDENTIFIER; + + } else if (c === ',') { + + names.push(buffer); + buffer = ''; + state = PLAIN; + + } else if (spacePattern.test(c)) ; else { + + throw new Error('Parse error'); + + } + + } else if (state === SEPARATING) { + + if (!c) { + + break; + + } else if (c === ',') { + + state = PLAIN; + + } else if (spacePattern.test(c)) ; else { + + throw new Error('Parse error'); + + } + + } + + i++; + + } + + // result + // ------ + + return names; + + }; + + // stringify + // ========= + + // pattern + // ------- + + var stringsPattern = /[^a-z0-9_-]/i; + + // --- + + var stringify = function(names, options) { + + // quote + // ----- + + var quote = options && options.quote || '"'; + if (quote !== '"' && quote !== "'") { + throw new Error('Quote must be `\'` or `"`'); + } + var quotePattern = new RegExp(quote, 'g'); + + // stringify + // --------- + + var safeNames = []; + + for (var i = 0; i < names.length; ++i) { + var name = names[i]; + + if (stringsPattern.test(name)) { + name = name + .replace(/\\/g, "\\\\") + .replace(quotePattern, "\\" + quote); + name = quote + name + quote; + } + safeNames.push(name); + } + + // result + // ------ + + return safeNames.join(', '); + }; + + // export + // ====== + + fontFamilyPapandreou = { + parse: parse, + stringify: stringify, + }; + return fontFamilyPapandreou; +} + +var fontFamilyPapandreouExports = requireFontFamilyPapandreou(); +var FontFamily = /*@__PURE__*/getDefaultExportFromCjs(fontFamilyPapandreouExports); + +var fontAliases = { + 'sans-serif': 'helvetica', + verdana: 'helvetica', + arial: 'helvetica', + fixed: 'courier', + monospace: 'courier', + terminal: 'courier', + serif: 'times', + cursive: 'times', + fantasy: 'times' +}; +function findFirstAvailableFontFamily(attributeState, fontFamilies, context) { + var fontType = combineFontStyleAndFontWeight(attributeState.fontStyle, attributeState.fontWeight); + var availableFonts = context.pdf.getFontList(); + var firstAvailable = ''; + var fontIsAvailable = fontFamilies.some(function (font) { + var availableStyles = availableFonts[font]; + if (availableStyles && availableStyles.indexOf(fontType) >= 0) { + firstAvailable = font; + return true; + } + font = font.toLowerCase(); + if (fontAliases.hasOwnProperty(font)) { + firstAvailable = font; + return true; + } + return false; + }); + if (!fontIsAvailable) { + firstAvailable = 'times'; + } + return firstAvailable; +} +var isJsPDF23 = (function () { + var parts = jsPDF.version.split('.'); + return parseFloat(parts[0]) === 2 && parseFloat(parts[1]) === 3; +})(); +function combineFontStyleAndFontWeight(fontStyle, fontWeight) { + if (isJsPDF23) { + return fontWeight == 400 + ? fontStyle == 'italic' + ? 'italic' + : 'normal' + : fontWeight == 700 && fontStyle !== 'italic' + ? 'bold' + : fontStyle + '' + fontWeight; + } + else { + return fontWeight == 400 || fontWeight === 'normal' + ? fontStyle === 'italic' + ? 'italic' + : 'normal' + : (fontWeight == 700 || fontWeight === 'bold') && fontStyle === 'normal' + ? 'bold' + : (fontWeight == 700 ? 'bold' : fontWeight) + '' + fontStyle; + } +} + +function getBoundingBoxByChildren(context, svgnode) { + if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') { + return [0, 0, 0, 0]; + } + var boundingBox = []; + svgnode.children.forEach(function (child) { + var nodeBox = child.getBoundingBox(context); + if (nodeBox[0] === 0 && nodeBox[1] === 0 && nodeBox[2] === 0 && nodeBox[3] === 0) + return; + var transform = child.computeNodeTransform(context); + // TODO: take into account rotation matrix + nodeBox[0] = nodeBox[0] * transform.sx + transform.tx; + nodeBox[1] = nodeBox[1] * transform.sy + transform.ty; + nodeBox[2] = nodeBox[2] * transform.sx; + nodeBox[3] = nodeBox[3] * transform.sy; + if (boundingBox.length === 0) + boundingBox = nodeBox; + else + boundingBox = [ + Math.min(boundingBox[0], nodeBox[0]), + Math.min(boundingBox[1], nodeBox[1]), + Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - + Math.min(boundingBox[0], nodeBox[0]), + Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - + Math.min(boundingBox[1], nodeBox[1]) + ]; + }); + return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox; +} +function defaultBoundingBox(element, context) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var pf = parseFloat; + // TODO: check if there are other possible coordinate attributes + var x1 = pf(element.getAttribute('x1')) || + pf(getAttribute(element, context.styleSheets, 'x')) || + pf(getAttribute(element, context.styleSheets, 'cx')) - + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var x2 = pf(element.getAttribute('x2')) || + x1 + pf(getAttribute(element, context.styleSheets, 'width')) || + pf(getAttribute(element, context.styleSheets, 'cx')) + + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var y1 = pf(element.getAttribute('y1')) || + pf(getAttribute(element, context.styleSheets, 'y')) || + pf(getAttribute(element, context.styleSheets, 'cy')) - + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + var y2 = pf(element.getAttribute('y2')) || + y1 + pf(getAttribute(element, context.styleSheets, 'height')) || + pf(getAttribute(element, context.styleSheets, 'cy')) + + pf(getAttribute(element, context.styleSheets, 'r')) || + 0; + return [ + Math.min(x1, x2), + Math.min(y1, y2), + Math.max(x1, x2) - Math.min(x1, x2), + Math.max(y1, y2) - Math.min(y1, y2) + ]; +} + +function computeViewBoxTransform(node, viewBox, eX, eY, eWidth, eHeight, context, noTranslate) { + if (noTranslate === void 0) { noTranslate = false; } + var vbX = viewBox[0]; + var vbY = viewBox[1]; + var vbWidth = viewBox[2]; + var vbHeight = viewBox[3]; + var scaleX = eWidth / vbWidth; + var scaleY = eHeight / vbHeight; + var align, meetOrSlice; + var preserveAspectRatio = node.getAttribute('preserveAspectRatio'); + if (preserveAspectRatio) { + var alignAndMeetOrSlice = preserveAspectRatio.split(' '); + if (alignAndMeetOrSlice[0] === 'defer') { + alignAndMeetOrSlice = alignAndMeetOrSlice.slice(1); + } + align = alignAndMeetOrSlice[0]; + meetOrSlice = alignAndMeetOrSlice[1] || 'meet'; + } + else { + align = 'xMidYMid'; + meetOrSlice = 'meet'; + } + if (align !== 'none') { + if (meetOrSlice === 'meet') { + // uniform scaling with min scale + scaleX = scaleY = Math.min(scaleX, scaleY); + } + else if (meetOrSlice === 'slice') { + // uniform scaling with max scale + scaleX = scaleY = Math.max(scaleX, scaleY); + } + } + if (noTranslate) { + return context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); + } + var translateX = eX - vbX * scaleX; + var translateY = eY - vbY * scaleY; + if (align.indexOf('xMid') >= 0) { + translateX += (eWidth - vbWidth * scaleX) / 2; + } + else if (align.indexOf('xMax') >= 0) { + translateX += eWidth - vbWidth * scaleX; + } + if (align.indexOf('YMid') >= 0) { + translateY += (eHeight - vbHeight * scaleY) / 2; + } + else if (align.indexOf('YMax') >= 0) { + translateY += eHeight - vbHeight * scaleY; + } + var translate = context.pdf.Matrix(1, 0, 0, 1, translateX, translateY); + var scale = context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); + return context.pdf.matrixMult(scale, translate); +} +// parses the "transform" string +function parseTransform(transformString, context) { + if (!transformString || transformString === 'none') + return context.pdf.unitMatrix; + var mRegex = /^[\s,]*matrix\(([^)]+)\)\s*/, tRegex = /^[\s,]*translate\(([^)]+)\)\s*/, rRegex = /^[\s,]*rotate\(([^)]+)\)\s*/, sRegex = /^[\s,]*scale\(([^)]+)\)\s*/, sXRegex = /^[\s,]*skewX\(([^)]+)\)\s*/, sYRegex = /^[\s,]*skewY\(([^)]+)\)\s*/; + var resultMatrix = context.pdf.unitMatrix; + var m; + var tSLength; + while (transformString.length > 0 && transformString.length !== tSLength) { + tSLength = transformString.length; + var match = mRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], m[1], m[2], m[3], m[4], m[5]), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = rRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + var a = (Math.PI * m[0]) / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0), resultMatrix); + if (m[1] || m[2]) { + var t1 = context.pdf.Matrix(1, 0, 0, 1, m[1], m[2]); + var t2 = context.pdf.Matrix(1, 0, 0, 1, -m[1], -m[2]); + resultMatrix = context.pdf.matrixMult(t2, context.pdf.matrixMult(resultMatrix, t1)); + } + transformString = transformString.substr(match[0].length); + } + match = tRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, m[0], m[1] || 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sRegex.exec(transformString); + if (match) { + m = parseFloats(match[1]); + if (!m[1]) + m[1] = m[0]; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], 0, 0, m[1], 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sXRegex.exec(transformString); + if (match) { + m = parseFloat(match[1]); + m *= Math.PI / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, Math.tan(m), 1, 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + match = sYRegex.exec(transformString); + if (match) { + m = parseFloat(match[1]); + m *= Math.PI / 180; + resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, Math.tan(m), 0, 1, 0, 0), resultMatrix); + transformString = transformString.substr(match[0].length); + } + } + return resultMatrix; +} + +var SvgNode = /** @class */ (function () { + function SvgNode(element, children) { + this.element = element; + this.children = children; + this.parent = null; + } + SvgNode.prototype.setParent = function (parent) { + this.parent = parent; + }; + SvgNode.prototype.getParent = function () { + return this.parent; + }; + SvgNode.prototype.getBoundingBox = function (context) { + if (getAttribute(this.element, context.styleSheets, 'display') === 'none') { + return [0, 0, 0, 0]; + } + return this.getBoundingBoxCore(context); + }; + SvgNode.prototype.computeNodeTransform = function (context) { + var nodeTransform = this.computeNodeTransformCore(context); + var transformString = getAttribute(this.element, context.styleSheets, 'transform'); + if (!transformString) + return nodeTransform; + else + return context.pdf.matrixMult(nodeTransform, parseTransform(transformString, context)); + }; + return SvgNode; +}()); + +var NonRenderedNode = /** @class */ (function (_super) { + __extends(NonRenderedNode, _super); + function NonRenderedNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + NonRenderedNode.prototype.render = function (parentContext) { + return Promise.resolve(); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + NonRenderedNode.prototype.getBoundingBoxCore = function (context) { + return []; + }; + NonRenderedNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return NonRenderedNode; +}(SvgNode)); + +var Gradient = /** @class */ (function (_super) { + __extends(Gradient, _super); + function Gradient(pdfGradientType, element, children) { + var _this = _super.call(this, element, children) || this; + _this.pdfGradientType = pdfGradientType; + _this.contextColor = undefined; + return _this; + } + Gradient.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var id, colors, opacitySum, hasOpacity, gState, pattern; + return __generator(this, function (_a) { + id = this.element.getAttribute('id'); + if (!id) { + return [2 /*return*/]; + } + colors = this.getStops(context.styleSheets); + opacitySum = 0; + hasOpacity = false; + colors.forEach(function (_a) { + var opacity = _a.opacity; + if (opacity && opacity !== 1) { + opacitySum += opacity; + hasOpacity = true; + } + }); + if (hasOpacity) { + gState = new GState({ opacity: opacitySum / colors.length }); + } + pattern = new ShadingPattern(this.pdfGradientType, this.getCoordinates(), colors, gState); + context.pdf.addShadingPattern(id, pattern); + return [2 /*return*/]; + }); + }); + }; + Gradient.prototype.getStops = function (styleSheets) { + var _this = this; + if (this.stops) { + return this.stops; + } + // Only need to calculate contextColor once + if (this.contextColor === undefined) { + this.contextColor = null; + var ancestor = this; + while (ancestor) { + var colorAttr = getAttribute(ancestor.element, styleSheets, 'color'); + if (colorAttr) { + this.contextColor = parseColor(colorAttr, null); + break; + } + ancestor = ancestor.getParent(); + } + } + var stops = []; + this.children.forEach(function (stop) { + if (stop.element.tagName.toLowerCase() === 'stop') { + var colorAttr = getAttribute(stop.element, styleSheets, 'color'); + var color = parseColor(getAttribute(stop.element, styleSheets, 'stop-color') || '', colorAttr + ? { color: parseColor(colorAttr, null) } + : { color: _this.contextColor }); + var opacity = parseFloat(getAttribute(stop.element, styleSheets, 'stop-opacity') || '1'); + stops.push({ + offset: Gradient.parseGradientOffset(stop.element.getAttribute('offset') || '0'), + color: [color.r, color.g, color.b], + opacity: opacity + }); + } + }); + return (this.stops = stops); + }; + Gradient.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Gradient.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Gradient.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + /** + * Convert percentage to decimal + */ + Gradient.parseGradientOffset = function (value) { + var parsedValue = parseFloat(value); + if (!isNaN(parsedValue) && value.indexOf('%') >= 0) { + return parsedValue / 100; + } + return parsedValue; + }; + return Gradient; +}(NonRenderedNode)); + +var LinearGradient = /** @class */ (function (_super) { + __extends(LinearGradient, _super); + function LinearGradient(element, children) { + return _super.call(this, 'axial', element, children) || this; + } + LinearGradient.prototype.getCoordinates = function () { + return [ + parseFloat(this.element.getAttribute('x1') || '0'), + parseFloat(this.element.getAttribute('y1') || '0'), + parseFloat(this.element.getAttribute('x2') || '1'), + parseFloat(this.element.getAttribute('y2') || '0') + ]; + }; + return LinearGradient; +}(Gradient)); + +var RadialGradient = /** @class */ (function (_super) { + __extends(RadialGradient, _super); + function RadialGradient(element, children) { + return _super.call(this, 'radial', element, children) || this; + } + RadialGradient.prototype.getCoordinates = function () { + var cx = this.element.getAttribute('cx'); + var cy = this.element.getAttribute('cy'); + var fx = this.element.getAttribute('fx'); + var fy = this.element.getAttribute('fy'); + return [ + parseFloat(fx || cx || '0.5'), + parseFloat(fy || cy || '0.5'), + 0, + parseFloat(cx || '0.5'), + parseFloat(cy || '0.5'), + parseFloat(this.element.getAttribute('r') || '0.5') + ]; + }; + return RadialGradient; +}(Gradient)); + +var GradientFill = /** @class */ (function () { + function GradientFill(key, gradient) { + this.key = key; + this.gradient = gradient; + } + GradientFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + var gradientUnitsMatrix, bBox, gradientTransform; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { + return node.apply(new Context(context.pdf, { + refsHandler: context.refsHandler, + textMeasure: context.textMeasure, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters + })); + }) + // matrix to convert between gradient space and user space + // for "userSpaceOnUse" this is the current transformation: tfMatrix + // for "objectBoundingBox" or default, the gradient gets scaled and transformed to the bounding box + ]; + case 1: + _a.sent(); + if (!this.gradient.element.hasAttribute('gradientUnits') || + this.gradient.element.getAttribute('gradientUnits').toLowerCase() === 'objectboundingbox') { + bBox = forNode.getBoundingBox(context); + gradientUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]); + } + else { + gradientUnitsMatrix = context.pdf.unitMatrix; + } + gradientTransform = parseTransform(getAttribute(this.gradient.element, context.styleSheets, 'gradientTransform', 'transform'), context); + return [2 /*return*/, { + key: this.key, + matrix: context.pdf.matrixMult(gradientTransform, gradientUnitsMatrix) + }]; + } + }); + }); + }; + return GradientFill; +}()); + +var Pattern = /** @class */ (function (_super) { + __extends(Pattern, _super); + function Pattern() { + return _super !== null && _super.apply(this, arguments) || this; + } + Pattern.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var id, bBox, pattern, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + id = this.element.getAttribute('id'); + if (!id) { + return [2 /*return*/]; + } + bBox = this.getBoundingBox(context); + pattern = new TilingPattern([bBox[0], bBox[1], bBox[0] + bBox[2], bBox[1] + bBox[3]], bBox[2], bBox[3]); + context.pdf.beginTilingPattern(pattern); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(new Context(context.pdf, { + attributeState: context.attributeState, + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + context.pdf.endTilingPattern(id, pattern); + return [2 /*return*/]; + } + }); + }); + }; + Pattern.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Pattern.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Pattern.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + return Pattern; +}(NonRenderedNode)); + +var PatternFill = /** @class */ (function () { + function PatternFill(key, pattern) { + this.key = key; + this.pattern = pattern; + } + PatternFill.prototype.getFillData = function (forNode, context) { + return __awaiter(this, void 0, void 0, function () { + var patternData, bBox, patternUnitsMatrix, fillBBox, x, y, width, height, patternContentUnitsMatrix, fillBBox, x, y, width, height, patternTransformMatrix, patternTransform, matrix; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { + return node.apply(new Context(context.pdf, { + refsHandler: context.refsHandler, + textMeasure: context.textMeasure, + styleSheets: context.styleSheets, + viewport: context.viewport, + svg2pdfParameters: context.svg2pdfParameters + })); + })]; + case 1: + _a.sent(); + patternData = { + key: this.key, + boundingBox: undefined, + xStep: 0, + yStep: 0, + matrix: undefined + }; + patternUnitsMatrix = context.pdf.unitMatrix; + if (!this.pattern.element.hasAttribute('patternUnits') || + this.pattern.element.getAttribute('patternUnits').toLowerCase() === 'objectboundingbox') { + bBox = forNode.getBoundingBox(context); + patternUnitsMatrix = context.pdf.Matrix(1, 0, 0, 1, bBox[0], bBox[1]); + fillBBox = this.pattern.getBoundingBox(context); + x = fillBBox[0] * bBox[0] || 0; + y = fillBBox[1] * bBox[1] || 0; + width = fillBBox[2] * bBox[2] || 0; + height = fillBBox[3] * bBox[3] || 0; + patternData.boundingBox = [x, y, x + width, y + height]; + patternData.xStep = width; + patternData.yStep = height; + } + patternContentUnitsMatrix = context.pdf.unitMatrix; + if (this.pattern.element.hasAttribute('patternContentUnits') && + this.pattern.element.getAttribute('patternContentUnits').toLowerCase() === + 'objectboundingbox') { + bBox || (bBox = forNode.getBoundingBox(context)); + patternContentUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], 0, 0); + fillBBox = patternData.boundingBox || this.pattern.getBoundingBox(context); + x = fillBBox[0] / bBox[0] || 0; + y = fillBBox[1] / bBox[1] || 0; + width = fillBBox[2] / bBox[2] || 0; + height = fillBBox[3] / bBox[3] || 0; + patternData.boundingBox = [x, y, x + width, y + height]; + patternData.xStep = width; + patternData.yStep = height; + } + patternTransformMatrix = context.pdf.unitMatrix; + patternTransform = getAttribute(this.pattern.element, context.styleSheets, 'patternTransform', 'transform'); + if (patternTransform) { + patternTransformMatrix = parseTransform(patternTransform, context); + } + matrix = patternContentUnitsMatrix; + matrix = context.pdf.matrixMult(matrix, patternUnitsMatrix); // translate by + matrix = context.pdf.matrixMult(matrix, patternTransformMatrix); + matrix = context.pdf.matrixMult(matrix, context.transform); + patternData.matrix = matrix; + return [2 /*return*/, patternData]; + } + }); + }); + }; + return PatternFill; +}()); + +function parseFill(fill, context) { + var url = iriReference.exec(fill); + if (url) { + var fillUrl = url[1]; + var fillNode = context.refsHandler.get(fillUrl); + if (fillNode && (fillNode instanceof LinearGradient || fillNode instanceof RadialGradient)) { + return getGradientFill(fillUrl, fillNode, context); + } + else if (fillNode && fillNode instanceof Pattern) { + return new PatternFill(fillUrl, fillNode); + } + else { + // unsupported fill argument -> fill black + return new ColorFill(new RGBColor('rgb(0, 0, 0)')); + } + } + else { + // plain color + var fillColor = parseColor(fill, context.attributeState); + if (fillColor.ok) { + return new ColorFill(fillColor); + } + else if (fill === 'none') { + return null; + } + else { + return null; + } + } +} +function getGradientFill(fillUrl, gradient, context) { + // "It is necessary that at least two stops are defined to have a gradient effect. If no stops are + // defined, then painting shall occur as if 'none' were specified as the paint style. If one stop + // is defined, then paint with the solid color fill using the color defined for that gradient + // stop." + var stops = gradient.getStops(context.styleSheets); + if (stops.length === 0) { + return null; + } + if (stops.length === 1) { + var stopColor = stops[0].color; + var rgbColor = new RGBColor(); + rgbColor.ok = true; + rgbColor.r = stopColor[0]; + rgbColor.g = stopColor[1]; + rgbColor.b = stopColor[2]; + rgbColor.a = stops[0].opacity; + return new ColorFill(rgbColor); + } + return new GradientFill(fillUrl, gradient); +} + +function parseAttributes(context, svgNode, node) { + var domNode = node || svgNode.element; + // update color first so currentColor becomes available for this node + var color = getAttribute(domNode, context.styleSheets, 'color'); + if (color) { + var fillColor = parseColor(color, context.attributeState); + if (fillColor.ok) { + context.attributeState.color = fillColor; + } + else { + // invalid color passed, reset to black + context.attributeState.color = new RGBColor('rgb(0,0,0)'); + } + } + var visibility = getAttribute(domNode, context.styleSheets, 'visibility'); + if (visibility) { + context.attributeState.visibility = visibility; + } + // fill mode + var fill = getAttribute(domNode, context.styleSheets, 'fill'); + if (fill) { + context.attributeState.fill = parseFill(fill, context); + } + // opacity is realized via a pdf graphics state + var fillOpacity = getAttribute(domNode, context.styleSheets, 'fill-opacity'); + if (fillOpacity) { + context.attributeState.fillOpacity = parseFloat(fillOpacity); + } + var strokeOpacity = getAttribute(domNode, context.styleSheets, 'stroke-opacity'); + if (strokeOpacity) { + context.attributeState.strokeOpacity = parseFloat(strokeOpacity); + } + var opacity = getAttribute(domNode, context.styleSheets, 'opacity'); + if (opacity) { + context.attributeState.opacity = parseFloat(opacity); + } + // stroke mode + var strokeWidth = getAttribute(domNode, context.styleSheets, 'stroke-width'); + if (strokeWidth !== void 0 && strokeWidth !== '') { + context.attributeState.strokeWidth = Math.abs(parseFloat(strokeWidth)); + } + var stroke = getAttribute(domNode, context.styleSheets, 'stroke'); + if (stroke) { + if (stroke === 'none') { + context.attributeState.stroke = null; + } + else { + // gradients, patterns not supported for strokes ... + var strokeRGB = parseColor(stroke, context.attributeState); + if (strokeRGB.ok) { + context.attributeState.stroke = new ColorFill(strokeRGB); + } + } + } + if (stroke && context.attributeState.stroke instanceof ColorFill) { + context.attributeState.contextStroke = context.attributeState.stroke.color; + } + if (fill && context.attributeState.fill instanceof ColorFill) { + context.attributeState.contextFill = context.attributeState.fill.color; + } + var lineCap = getAttribute(domNode, context.styleSheets, 'stroke-linecap'); + if (lineCap) { + context.attributeState.strokeLinecap = lineCap; + } + var lineJoin = getAttribute(domNode, context.styleSheets, 'stroke-linejoin'); + if (lineJoin) { + context.attributeState.strokeLinejoin = lineJoin; + } + var dashArray = getAttribute(domNode, context.styleSheets, 'stroke-dasharray'); + if (dashArray) { + var dashOffset = parseInt(getAttribute(domNode, context.styleSheets, 'stroke-dashoffset') || '0'); + context.attributeState.strokeDasharray = parseFloats(dashArray); + context.attributeState.strokeDashoffset = dashOffset; + } + var miterLimit = getAttribute(domNode, context.styleSheets, 'stroke-miterlimit'); + if (miterLimit !== void 0 && miterLimit !== '') { + context.attributeState.strokeMiterlimit = parseFloat(miterLimit); + } + var xmlSpace = domNode.getAttribute('xml:space'); + if (xmlSpace) { + context.attributeState.xmlSpace = xmlSpace; + } + var whiteSpace = getAttribute(domNode, context.styleSheets, 'white-space'); + if (whiteSpace) { + context.attributeState.whiteSpace = whiteSpace; + } + var fontWeight = getAttribute(domNode, context.styleSheets, 'font-weight'); + if (fontWeight) { + context.attributeState.fontWeight = fontWeight; + } + var fontStyle = getAttribute(domNode, context.styleSheets, 'font-style'); + if (fontStyle) { + context.attributeState.fontStyle = fontStyle; + } + var fontFamily = getAttribute(domNode, context.styleSheets, 'font-family'); + if (fontFamily) { + var fontFamilies = FontFamily.parse(fontFamily); + context.attributeState.fontFamily = findFirstAvailableFontFamily(context.attributeState, fontFamilies, context); + } + var fontSize = getAttribute(domNode, context.styleSheets, 'font-size'); + if (fontSize) { + var pdfFontSize = context.pdf.getFontSize(); + context.attributeState.fontSize = toPixels(fontSize, pdfFontSize); + } + var alignmentBaseline = getAttribute(domNode, context.styleSheets, 'vertical-align') || + getAttribute(domNode, context.styleSheets, 'alignment-baseline'); + if (alignmentBaseline) { + var matchArr = alignmentBaseline.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/); + if (matchArr) { + context.attributeState.alignmentBaseline = matchArr[0]; + } + } + var textAnchor = getAttribute(domNode, context.styleSheets, 'text-anchor'); + if (textAnchor) { + context.attributeState.textAnchor = textAnchor; + } + var fillRule = getAttribute(domNode, context.styleSheets, 'fill-rule'); + if (fillRule) { + context.attributeState.fillRule = fillRule; + } +} +function applyAttributes(childContext, parentContext, node) { + var fillOpacity = 1.0, strokeOpacity = 1.0; + fillOpacity *= childContext.attributeState.fillOpacity; + fillOpacity *= childContext.attributeState.opacity; + if (childContext.attributeState.fill instanceof ColorFill && + typeof childContext.attributeState.fill.color.a !== 'undefined') { + fillOpacity *= childContext.attributeState.fill.color.a; + } + strokeOpacity *= childContext.attributeState.strokeOpacity; + strokeOpacity *= childContext.attributeState.opacity; + if (childContext.attributeState.stroke instanceof ColorFill && + typeof childContext.attributeState.stroke.color.a !== 'undefined') { + strokeOpacity *= childContext.attributeState.stroke.color.a; + } + var hasFillOpacity = fillOpacity < 1.0; + var hasStrokeOpacity = strokeOpacity < 1.0; + // This is a workaround for symbols that are used multiple times with different + // fill/stroke attributes. All paths within symbols are both filled and stroked + // and we set the fill/stroke to transparent if the use element has + // fill/stroke="none". + if (nodeIs(node, 'use')) { + hasFillOpacity = true; + hasStrokeOpacity = true; + fillOpacity *= childContext.attributeState.fill ? 1 : 0; + strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; + } + else if (childContext.withinUse) { + if (childContext.attributeState.fill !== parentContext.attributeState.fill) { + hasFillOpacity = true; + fillOpacity *= childContext.attributeState.fill ? 1 : 0; + } + else if (hasFillOpacity && !childContext.attributeState.fill) { + fillOpacity = 0; + } + if (childContext.attributeState.stroke !== parentContext.attributeState.stroke) { + hasStrokeOpacity = true; + strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; + } + else if (hasStrokeOpacity && !childContext.attributeState.stroke) { + strokeOpacity = 0; + } + } + if (hasFillOpacity || hasStrokeOpacity) { + var gState = {}; + hasFillOpacity && (gState['opacity'] = fillOpacity); + hasStrokeOpacity && (gState['stroke-opacity'] = strokeOpacity); + childContext.pdf.setGState(new GState(gState)); + } + if (childContext.attributeState.fill && + childContext.attributeState.fill !== parentContext.attributeState.fill && + childContext.attributeState.fill instanceof ColorFill && + childContext.attributeState.fill.color.ok && + !nodeIs(node, 'text')) { + // text fill color will be applied through setTextColor() + childContext.pdf.setFillColor(childContext.attributeState.fill.color.r, childContext.attributeState.fill.color.g, childContext.attributeState.fill.color.b); + } + if (childContext.attributeState.strokeWidth !== parentContext.attributeState.strokeWidth) { + childContext.pdf.setLineWidth(childContext.attributeState.strokeWidth); + } + if (childContext.attributeState.stroke !== parentContext.attributeState.stroke && + childContext.attributeState.stroke instanceof ColorFill) { + childContext.pdf.setDrawColor(childContext.attributeState.stroke.color.r, childContext.attributeState.stroke.color.g, childContext.attributeState.stroke.color.b); + } + if (childContext.attributeState.strokeLinecap !== parentContext.attributeState.strokeLinecap) { + childContext.pdf.setLineCap(childContext.attributeState.strokeLinecap); + } + if (childContext.attributeState.strokeLinejoin !== parentContext.attributeState.strokeLinejoin) { + childContext.pdf.setLineJoin(childContext.attributeState.strokeLinejoin); + } + if ((childContext.attributeState.strokeDasharray !== parentContext.attributeState.strokeDasharray || + childContext.attributeState.strokeDashoffset !== + parentContext.attributeState.strokeDashoffset) && + childContext.attributeState.strokeDasharray) { + childContext.pdf.setLineDashPattern(childContext.attributeState.strokeDasharray, childContext.attributeState.strokeDashoffset); + } + if (childContext.attributeState.strokeMiterlimit !== parentContext.attributeState.strokeMiterlimit) { + childContext.pdf.setLineMiterLimit(childContext.attributeState.strokeMiterlimit); + } + var font; + if (childContext.attributeState.fontFamily !== parentContext.attributeState.fontFamily) { + if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { + font = fontAliases[childContext.attributeState.fontFamily]; + } + else { + font = childContext.attributeState.fontFamily; + } + } + if (childContext.attributeState.fill && + childContext.attributeState.fill !== parentContext.attributeState.fill && + childContext.attributeState.fill instanceof ColorFill && + childContext.attributeState.fill.color.ok) { + var fillColor = childContext.attributeState.fill.color; + childContext.pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); + } + var fontStyle; + if (childContext.attributeState.fontWeight !== parentContext.attributeState.fontWeight || + childContext.attributeState.fontStyle !== parentContext.attributeState.fontStyle) { + fontStyle = combineFontStyleAndFontWeight(childContext.attributeState.fontStyle, childContext.attributeState.fontWeight); + } + if (font !== undefined || fontStyle !== undefined) { + if (font === undefined) { + if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { + font = fontAliases[childContext.attributeState.fontFamily]; + } + else { + font = childContext.attributeState.fontFamily; + } + } + childContext.pdf.setFont(font, fontStyle); + } + if (childContext.attributeState.fontSize !== parentContext.attributeState.fontSize) { + // correct for a jsPDF-instance measurement unit that differs from `pt` + childContext.pdf.setFontSize(childContext.attributeState.fontSize * childContext.pdf.internal.scaleFactor); + } +} +function applyContext(context) { + var attributeState = context.attributeState, pdf = context.pdf; + var fillOpacity = 1.0, strokeOpacity = 1.0; + fillOpacity *= attributeState.fillOpacity; + fillOpacity *= attributeState.opacity; + if (attributeState.fill instanceof ColorFill && + typeof attributeState.fill.color.a !== 'undefined') { + fillOpacity *= attributeState.fill.color.a; + } + strokeOpacity *= attributeState.strokeOpacity; + strokeOpacity *= attributeState.opacity; + if (attributeState.stroke instanceof ColorFill && + typeof attributeState.stroke.color.a !== 'undefined') { + strokeOpacity *= attributeState.stroke.color.a; + } + var gState = {}; + gState['opacity'] = fillOpacity; + gState['stroke-opacity'] = strokeOpacity; + pdf.setGState(new GState(gState)); + if (attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok) { + // text fill color will be applied through setTextColor() + pdf.setFillColor(attributeState.fill.color.r, attributeState.fill.color.g, attributeState.fill.color.b); + } + else { + pdf.setFillColor(0, 0, 0); + } + pdf.setLineWidth(attributeState.strokeWidth); + if (attributeState.stroke instanceof ColorFill) { + pdf.setDrawColor(attributeState.stroke.color.r, attributeState.stroke.color.g, attributeState.stroke.color.b); + } + else { + pdf.setDrawColor(0, 0, 0); + } + pdf.setLineCap(attributeState.strokeLinecap); + pdf.setLineJoin(attributeState.strokeLinejoin); + if (attributeState.strokeDasharray) { + pdf.setLineDashPattern(attributeState.strokeDasharray, attributeState.strokeDashoffset); + } + else { + pdf.setLineDashPattern([], 0); + } + pdf.setLineMiterLimit(attributeState.strokeMiterlimit); + var font; + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily]; + } + else { + font = attributeState.fontFamily; + } + if (attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok) { + var fillColor = attributeState.fill.color; + pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); + } + else { + pdf.setTextColor(0, 0, 0); + } + var fontStyle = ''; + if (attributeState.fontWeight === 'bold') { + fontStyle = 'bold'; + } + if (attributeState.fontStyle === 'italic') { + fontStyle += 'italic'; + } + if (fontStyle === '') { + fontStyle = 'normal'; + } + if (font !== undefined || fontStyle !== undefined) { + if (font === undefined) { + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily]; + } + else { + font = attributeState.fontFamily; + } + } + pdf.setFont(font, fontStyle); + } + else { + pdf.setFont('helvetica', fontStyle); + } + // correct for a jsPDF-instance measurement unit that differs from `pt` + pdf.setFontSize(attributeState.fontSize * pdf.internal.scaleFactor); +} + +function getClipPathNode(clipPathAttr, targetNode, context) { + var match = iriReference.exec(clipPathAttr); + if (!match) { + return undefined; + } + var clipPathId = match[1]; + var clipNode = context.refsHandler.get(clipPathId); + return clipNode || undefined; +} +function applyClipPath(targetNode, clipPathNode, context) { + return __awaiter(this, void 0, void 0, function () { + var clipContext, bBox; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + clipContext = context.clone(); + if (clipPathNode.element.hasAttribute('clipPathUnits') && + clipPathNode.element.getAttribute('clipPathUnits').toLowerCase() === 'objectboundingbox') { + bBox = targetNode.getBoundingBox(context); + clipContext.transform = context.pdf.matrixMult(context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]), context.transform); + } + return [4 /*yield*/, clipPathNode.apply(clipContext)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} + +var RenderedNode = /** @class */ (function (_super) { + __extends(RenderedNode, _super); + function RenderedNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + RenderedNode.prototype.render = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var context, clipPathAttribute, hasClipPath, clipNode; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { + return [2 /*return*/]; + } + context = parentContext.clone(); + context.transform = context.pdf.matrixMult(this.computeNodeTransform(context), parentContext.transform); + parseAttributes(context, this); + clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); + hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; + if (!hasClipPath) return [3 /*break*/, 5]; + clipNode = getClipPathNode(clipPathAttribute, this, context); + if (!clipNode) return [3 /*break*/, 4]; + if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; + context.pdf.saveGraphicsState(); + return [4 /*yield*/, applyClipPath(this, clipNode, context)]; + case 1: + _a.sent(); + return [3 /*break*/, 3]; + case 2: return [2 /*return*/]; + case 3: return [3 /*break*/, 5]; + case 4: + hasClipPath = false; + _a.label = 5; + case 5: + if (!context.withinClipPath) { + context.pdf.saveGraphicsState(); + } + applyAttributes(context, parentContext, this.element); + return [4 /*yield*/, this.renderCore(context)]; + case 6: + _a.sent(); + if (!context.withinClipPath) { + context.pdf.restoreGraphicsState(); + } + if (hasClipPath) { + context.pdf.restoreGraphicsState(); + } + return [2 /*return*/]; + } + }); + }); + }; + return RenderedNode; +}(SvgNode)); + +var GraphicsNode = /** @class */ (function (_super) { + __extends(GraphicsNode, _super); + function GraphicsNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + return GraphicsNode; +}(RenderedNode)); + +var GeometryNode = /** @class */ (function (_super) { + __extends(GeometryNode, _super); + function GeometryNode(hasMarkers, element, children) { + var _this = _super.call(this, element, children) || this; + _this.cachedPath = null; + _this.hasMarkers = hasMarkers; + return _this; + } + GeometryNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var path; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + path = this.getCachedPath(context); + if (path === null || path.segments.length === 0) { + return [2 /*return*/]; + } + if (context.withinClipPath) { + path.transform(context.transform); + } + else { + context.pdf.setCurrentTransformationMatrix(context.transform); + } + path.draw(context); + return [4 /*yield*/, this.fillOrStroke(context)]; + case 1: + _a.sent(); + if (!this.hasMarkers) return [3 /*break*/, 3]; + return [4 /*yield*/, this.drawMarkers(context, path)]; + case 2: + _a.sent(); + _a.label = 3; + case 3: return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.getCachedPath = function (context) { + return this.cachedPath || (this.cachedPath = this.getPath(context)); + }; + GeometryNode.prototype.drawMarkers = function (context, path) { + return __awaiter(this, void 0, void 0, function () { + var markers; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + markers = this.getMarkers(path, context); + return [4 /*yield*/, markers.draw(context.clone({ transform: context.pdf.unitMatrix }))]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.fillOrStroke = function (context) { + return __awaiter(this, void 0, void 0, function () { + var fill, stroke, fillData, _a, isNodeFillRuleEvenOdd; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (context.withinClipPath) { + return [2 /*return*/]; + } + fill = context.attributeState.fill; + stroke = context.attributeState.stroke && context.attributeState.strokeWidth !== 0; + if (!fill) return [3 /*break*/, 2]; + return [4 /*yield*/, fill.getFillData(this, context)]; + case 1: + _a = _b.sent(); + return [3 /*break*/, 3]; + case 2: + _a = undefined; + _b.label = 3; + case 3: + fillData = _a; + isNodeFillRuleEvenOdd = context.attributeState.fillRule === 'evenodd'; + // This is a workaround for symbols that are used multiple times with different + // fill/stroke attributes. All paths within symbols are both filled and stroked + // and we set the fill/stroke to transparent if the use element has + // fill/stroke="none". + if ((fill && stroke) || context.withinUse) { + if (isNodeFillRuleEvenOdd) { + context.pdf.fillStrokeEvenOdd(fillData); + } + else { + context.pdf.fillStroke(fillData); + } + } + else if (fill) { + if (isNodeFillRuleEvenOdd) { + context.pdf.fillEvenOdd(fillData); + } + else { + context.pdf.fill(fillData); + } + } + else if (stroke) { + context.pdf.stroke(); + } + else { + context.pdf.discardPath(); + } + return [2 /*return*/]; + } + }); + }); + }; + GeometryNode.prototype.getBoundingBoxCore = function (context) { + var path = this.getCachedPath(context); + if (!path || !path.segments.length) { + return [0, 0, 0, 0]; + } + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = Number.NEGATIVE_INFINITY; + var maxY = Number.NEGATIVE_INFINITY; + var x = 0, y = 0; + for (var i = 0; i < path.segments.length; i++) { + var seg = path.segments[i]; + if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { + x = seg.x; + y = seg.y; + } + if (seg instanceof CurveTo) { + minX = Math.min(minX, x, seg.x1, seg.x2, seg.x); + maxX = Math.max(maxX, x, seg.x1, seg.x2, seg.x); + minY = Math.min(minY, y, seg.y1, seg.y2, seg.y); + maxY = Math.max(maxY, y, seg.y1, seg.y2, seg.y); + } + else { + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + } + return [minX, minY, maxX - minX, maxY - minY]; + }; + GeometryNode.prototype.getMarkers = function (path, context) { + var markerStart = getAttribute(this.element, context.styleSheets, 'marker-start'); + var markerMid = getAttribute(this.element, context.styleSheets, 'marker-mid'); + var markerEnd = getAttribute(this.element, context.styleSheets, 'marker-end'); + var markers = new MarkerList(); + if (markerStart || markerMid || markerEnd) { + markerEnd && (markerEnd = iri(markerEnd)); + markerStart && (markerStart = iri(markerStart)); + markerMid && (markerMid = iri(markerMid)); + var list_1 = path.segments; + var prevAngle = [1, 0], curAngle = void 0, first = false, firstAngle = [1, 0], last_1 = false; + var _loop_1 = function (i) { + var curr = list_1[i]; + var hasStartMarker = markerStart && + (i === 1 || (!(list_1[i] instanceof MoveTo) && list_1[i - 1] instanceof MoveTo)); + if (hasStartMarker) { + list_1.forEach(function (value, index) { + if (!last_1 && value instanceof Close && index > i) { + var tmp = list_1[index - 1]; + last_1 = + (tmp instanceof MoveTo || tmp instanceof LineTo || tmp instanceof CurveTo) && tmp; + } + }); + } + var hasEndMarker = markerEnd && + (i === list_1.length - 1 || (!(list_1[i] instanceof MoveTo) && list_1[i + 1] instanceof MoveTo)); + var hasMidMarker = markerMid && i > 0 && !(i === 1 && list_1[i - 1] instanceof MoveTo); + var prev = list_1[i - 1] || null; + if (prev instanceof MoveTo || prev instanceof LineTo || prev instanceof CurveTo) { + if (curr instanceof CurveTo) { + hasStartMarker && + markers.addMarker(new Marker(markerStart, [prev.x, prev.y], + // @ts-ignore + getAngle(last_1 ? [last_1.x, last_1.y] : [prev.x, prev.y], [curr.x1, curr.y1]), true)); + hasEndMarker && + markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], getAngle([curr.x2, curr.y2], [curr.x, curr.y]))); + if (hasMidMarker) { + curAngle = getDirectionVector([prev.x, prev.y], [curr.x1, curr.y1]); + curAngle = + prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(curAngle[1], curAngle[0]))); + } + prevAngle = getDirectionVector([curr.x2, curr.y2], [curr.x, curr.y]); + } + else if (curr instanceof MoveTo || curr instanceof LineTo) { + curAngle = getDirectionVector([prev.x, prev.y], [curr.x, curr.y]); + if (hasStartMarker) { + // @ts-ignore + var angle = last_1 ? getDirectionVector([last_1.x, last_1.y], [curr.x, curr.y]) : curAngle; + markers.addMarker(new Marker(markerStart, [prev.x, prev.y], Math.atan2(angle[1], angle[0]), true)); + } + hasEndMarker && + markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], Math.atan2(curAngle[1], curAngle[0]))); + if (hasMidMarker) { + var angle = curr instanceof MoveTo + ? prevAngle + : prev instanceof MoveTo + ? curAngle + : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); + } + prevAngle = curAngle; + } + else if (curr instanceof Close) { + // @ts-ignore + curAngle = getDirectionVector([prev.x, prev.y], [first.x, first.y]); + if (hasMidMarker) { + var angle = prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); + markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); + } + if (hasEndMarker) { + var angle = normalize(addVectors(curAngle, firstAngle)); + markers.addMarker( + // @ts-ignore + new Marker(markerEnd, [first.x, first.y], Math.atan2(angle[1], angle[0]))); + } + prevAngle = curAngle; + } + } + else { + first = curr instanceof MoveTo && curr; + var next = list_1[i + 1]; + if (next instanceof MoveTo || next instanceof LineTo || next instanceof CurveTo) { + // @ts-ignore + firstAngle = getDirectionVector([first.x, first.y], [next.x, next.y]); + } + } + }; + for (var i = 0; i < list_1.length; i++) { + _loop_1(i); + } + } + markers.markers.forEach(function (marker) { + var markerNode = context.refsHandler.get(marker.id); + if (!markerNode) + return; + var orient = getAttribute(markerNode.element, context.styleSheets, 'orient'); + if (orient == null) + return; + if (marker.isStartMarker && orient === 'auto-start-reverse') { + marker.angle += Math.PI; + } + if (!isNaN(Number(orient))) { + marker.angle = (parseFloat(orient) / 180) * Math.PI; + } + }); + return markers; + }; + return GeometryNode; +}(GraphicsNode)); +function iri(attribute) { + var match = iriReference.exec(attribute); + return (match && match[1]) || undefined; +} + +var Line = /** @class */ (function (_super) { + __extends(Line, _super); + function Line(node, children) { + return _super.call(this, true, node, children) || this; + } + Line.prototype.getPath = function (context) { + if (context.withinClipPath || context.attributeState.stroke === null) { + return null; + } + var x1 = parseFloat(this.element.getAttribute('x1') || '0'), y1 = parseFloat(this.element.getAttribute('y1') || '0'); + var x2 = parseFloat(this.element.getAttribute('x2') || '0'), y2 = parseFloat(this.element.getAttribute('y2') || '0'); + if (!(x1 || x2 || y1 || y2)) { + return null; + } + return new Path().moveTo(x1, y1).lineTo(x2, y2); + }; + Line.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Line.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Line.prototype.fillOrStroke = function (context) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + context.attributeState.fill = null; + return [4 /*yield*/, _super.prototype.fillOrStroke.call(this, context)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return Line; +}(GeometryNode)); + +var Symbol$1 = /** @class */ (function (_super) { + __extends(Symbol, _super); + function Symbol() { + return _super !== null && _super.apply(this, arguments) || this; + } + Symbol.prototype.apply = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var context, clipPathAttribute, hasClipPath, clipNode, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { + return [2 /*return*/]; + } + context = parentContext.clone(); + context.transform = context.pdf.unitMatrix; + parseAttributes(context, this); + clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); + hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; + if (!hasClipPath) return [3 /*break*/, 3]; + clipNode = getClipPathNode(clipPathAttribute, this, context); + if (!clipNode) return [3 /*break*/, 3]; + if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; + return [4 /*yield*/, applyClipPath(this, clipNode, context)]; + case 1: + _b.sent(); + return [3 /*break*/, 3]; + case 2: return [2 /*return*/]; + case 3: + applyAttributes(context, parentContext, this.element); + _i = 0, _a = this.children; + _b.label = 4; + case 4: + if (!(_i < _a.length)) return [3 /*break*/, 7]; + child = _a[_i]; + return [4 /*yield*/, child.render(context)]; + case 5: + _b.sent(); + _b.label = 6; + case 6: + _i++; + return [3 /*break*/, 4]; + case 7: return [2 /*return*/]; + } + }); + }); + }; + Symbol.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + Symbol.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Symbol.prototype.computeNodeTransformCore = function (context) { + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); + var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + // TODO: implement refX/refY - this is still to do because common browsers don't seem to support the feature yet + // x += parseFloat(this.element.getAttribute("refX")) || 0; ??? + // y += parseFloat(this.element.getAttribute("refY")) || 0; ??? + var viewBox = this.element.getAttribute('viewBox'); + if (viewBox) { + var box = parseFloats(viewBox); + var width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || + getAttribute(this.element.ownerSVGElement, context.styleSheets, 'width') || + viewBox[2]); + var height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || + getAttribute(this.element.ownerSVGElement, context.styleSheets, 'height') || + viewBox[3]); + return computeViewBoxTransform(this.element, box, x, y, width, height, context); + } + else { + return context.pdf.Matrix(1, 0, 0, 1, x, y); + } + }; + return Symbol; +}(NonRenderedNode)); + +var Viewport = /** @class */ (function () { + function Viewport(width, height) { + this.width = width; + this.height = height; + } + return Viewport; +}()); + +/** + * Draws the element referenced by a use node, makes use of pdf's XObjects/FormObjects so nodes are only written once + * to the pdf document. This highly reduces the file size and computation time. + */ +var Use = /** @class */ (function (_super) { + __extends(Use, _super); + function Use() { + return _super !== null && _super.apply(this, arguments) || this; + } + Use.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var pf, url, id, refNode, refNodeOpensViewport, x, y, width, height, t, viewBox, contextColors, refContext; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + pf = parseFloat; + url = this.element.getAttribute('href') || this.element.getAttribute('xlink:href'); + // just in case someone has the idea to use empty use-tags, wtf??? + if (!url) + return [2 /*return*/]; + id = url.substring(1); + refNode = context.refsHandler.get(id); + refNodeOpensViewport = nodeIs(refNode.element, 'symbol,svg') && refNode.element.hasAttribute('viewBox'); + x = pf(getAttribute(this.element, context.styleSheets, 'x') || '0'); + y = pf(getAttribute(this.element, context.styleSheets, 'y') || '0'); + width = undefined; + height = undefined; + if (refNodeOpensViewport) { + // <use> inherits width/height only to svg/symbol + // if there is no viewBox attribute, width/height don't have an effect + // in theory, the default value for width/height is 100%, but we currently don't support this + width = pf(getAttribute(this.element, context.styleSheets, 'width') || + getAttribute(refNode.element, context.styleSheets, 'width') || + '0'); + height = pf(getAttribute(this.element, context.styleSheets, 'height') || + getAttribute(refNode.element, context.styleSheets, 'height') || + '0'); + // accumulate x/y to calculate the viewBox transform + x += pf(getAttribute(refNode.element, context.styleSheets, 'x') || '0'); + y += pf(getAttribute(refNode.element, context.styleSheets, 'y') || '0'); + viewBox = parseFloats(refNode.element.getAttribute('viewBox')); + t = computeViewBoxTransform(refNode.element, viewBox, x, y, width, height, context); + } + else { + t = context.pdf.Matrix(1, 0, 0, 1, x, y); + } + contextColors = AttributeState.getContextColors(context, true); + refContext = new Context(context.pdf, { + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + withinUse: true, + viewport: refNodeOpensViewport ? new Viewport(width, height) : context.viewport, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure, + attributeState: Object.assign(AttributeState.default(), contextColors) + }); + return [4 /*yield*/, context.refsHandler.getRendered(id, contextColors, function (node) { + return Use.renderReferencedNode(node, id, refContext); + })]; + case 1: + _a.sent(); + context.pdf.saveGraphicsState(); + context.pdf.setCurrentTransformationMatrix(context.transform); + // apply the bbox (i.e. clip) if needed + if (refNodeOpensViewport && + getAttribute(refNode.element, context.styleSheets, 'overflow') !== 'visible') { + context.pdf.rect(x, y, width, height); + context.pdf.clip().discardPath(); + } + context.pdf.doFormObject(context.refsHandler.generateKey(id, contextColors), t); + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + } + }); + }); + }; + Use.renderReferencedNode = function (node, id, refContext) { + return __awaiter(this, void 0, void 0, function () { + var bBox; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + bBox = node.getBoundingBox(refContext); + // The content of a PDF form object is implicitly clipped at its /BBox property. + // SVG, however, applies its clip rect at the <use> attribute, which may modify it. + // So, make the bBox a lot larger than it needs to be and hope any thick strokes are + // still within. + bBox = [bBox[0] - 0.5 * bBox[2], bBox[1] - 0.5 * bBox[3], bBox[2] * 2, bBox[3] * 2]; + refContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], refContext.pdf.unitMatrix); + if (!(node instanceof Symbol$1)) return [3 /*break*/, 2]; + return [4 /*yield*/, node.apply(refContext)]; + case 1: + _a.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, node.render(refContext)]; + case 3: + _a.sent(); + _a.label = 4; + case 4: + refContext.pdf.endFormObject(refContext.refsHandler.generateKey(id, refContext.attributeState)); + return [2 /*return*/]; + } + }); + }); + }; + Use.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + Use.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Use.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return Use; +}(GraphicsNode)); + +var Rect = /** @class */ (function (_super) { + __extends(Rect, _super); + function Rect(element, children) { + return _super.call(this, false, element, children) || this; + } + Rect.prototype.getPath = function (context) { + var w = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'); + var h = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'); + if (!isFinite(w) || w <= 0 || !isFinite(h) || h <= 0) { + return null; + } + var rxAttr = getAttribute(this.element, context.styleSheets, 'rx'); + var ryAttr = getAttribute(this.element, context.styleSheets, 'ry'); + var rx = Math.min(parseFloat(rxAttr || ryAttr || '0'), w * 0.5); + var ry = Math.min(parseFloat(ryAttr || rxAttr || '0'), h * 0.5); + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); + var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + var arc = (4 / 3) * (Math.SQRT2 - 1); + if (rx === 0 && ry === 0) { + return new Path() + .moveTo(x, y) + .lineTo(x + w, y) + .lineTo(x + w, y + h) + .lineTo(x, y + h) + .close(); + } + else { + return new Path() + .moveTo((x += rx), y) + .lineTo((x += w - 2 * rx), y) + .curveTo(x + rx * arc, y, x + rx, y + (ry - ry * arc), (x += rx), (y += ry)) + .lineTo(x, (y += h - 2 * ry)) + .curveTo(x, y + ry * arc, x - rx * arc, y + ry, (x -= rx), (y += ry)) + .lineTo((x += -w + 2 * rx), y) + .curveTo(x - rx * arc, y, x - rx, y - ry * arc, (x -= rx), (y -= ry)) + .lineTo(x, (y += -h + 2 * ry)) + .curveTo(x, y - ry * arc, x + rx * arc, y - ry, (x += rx), (y -= ry)) + .close(); + } + }; + Rect.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Rect.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return Rect; +}(GeometryNode)); + +var EllipseBase = /** @class */ (function (_super) { + __extends(EllipseBase, _super); + function EllipseBase(element, children) { + return _super.call(this, false, element, children) || this; + } + EllipseBase.prototype.getPath = function (context) { + var rx = this.getRx(context); + var ry = this.getRy(context); + if (!isFinite(rx) || ry <= 0 || !isFinite(ry) || ry <= 0) { + return null; + } + var x = parseFloat(getAttribute(this.element, context.styleSheets, 'cx') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'cy') || '0'); + var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, ly = (4 / 3) * (Math.SQRT2 - 1) * ry; + return new Path() + .moveTo(x + rx, y) + .curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry) + .curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y) + .curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry) + .curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); + }; + EllipseBase.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + EllipseBase.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return EllipseBase; +}(GeometryNode)); + +var Ellipse = /** @class */ (function (_super) { + __extends(Ellipse, _super); + function Ellipse(element, children) { + return _super.call(this, element, children) || this; + } + Ellipse.prototype.getRx = function (context) { + return parseFloat(getAttribute(this.element, context.styleSheets, 'rx') || '0'); + }; + Ellipse.prototype.getRy = function (context) { + return parseFloat(getAttribute(this.element, context.styleSheets, 'ry') || '0'); + }; + return Ellipse; +}(EllipseBase)); + +function getTextRenderingMode(attributeState) { + var renderingMode = 'invisible'; + var doStroke = attributeState.stroke && attributeState.strokeWidth !== 0; + var doFill = attributeState.fill; + if (doFill && doStroke) { + renderingMode = 'fillThenStroke'; + } + else if (doFill) { + renderingMode = 'fill'; + } + else if (doStroke) { + renderingMode = 'stroke'; + } + return renderingMode; +} +function transformXmlSpace(trimmedText, attributeState) { + trimmedText = removeNewlines(trimmedText); + trimmedText = replaceTabsBySpace(trimmedText); + var shouldPreserve = attributeState.xmlSpace === 'preserve' || attributeState.whiteSpace === 'pre'; + if (!shouldPreserve) { + trimmedText = trimmedText.trim(); + trimmedText = consolidateSpaces(trimmedText); + } + return trimmedText; +} +function removeNewlines(str) { + return str.replace(/[\n\r]/g, ''); +} +function replaceTabsBySpace(str) { + return str.replace(/[\t]/g, ' '); +} +function consolidateSpaces(str) { + return str.replace(/ +/g, ' '); +} +// applies text transformations to a text node +function transformText(node, text, context) { + var textTransform = getAttribute(node, context.styleSheets, 'text-transform'); + switch (textTransform) { + case 'uppercase': + return text.toUpperCase(); + case 'lowercase': + return text.toLowerCase(); + default: + return text; + // TODO: capitalize, full-width + } +} +function trimLeft(str) { + return str.replace(/^\s+/, ''); +} +function trimRight(str) { + return str.replace(/\s+$/, ''); +} + +/** + * @param {string} textAnchor + * @param {number} originX + * @param {number} originY + * @constructor + */ +var TextChunk = /** @class */ (function () { + function TextChunk(parent, textAnchor, originX, originY) { + this.textNode = parent; + this.texts = []; + this.textNodes = []; + this.contexts = []; + this.textAnchor = textAnchor; + this.originX = originX; + this.originY = originY; + this.textMeasures = []; + } + TextChunk.prototype.setX = function (originX) { + this.originX = originX; + }; + TextChunk.prototype.setY = function (originY) { + this.originY = originY; + }; + TextChunk.prototype.add = function (tSpan, text, context) { + this.texts.push(text); + this.textNodes.push(tSpan); + this.contexts.push(context); + }; + TextChunk.prototype.rightTrimText = function () { + for (var r = this.texts.length - 1; r >= 0; r--) { + var shouldPreserve = this.contexts[r].attributeState.xmlSpace === 'preserve' || + this.contexts[r].attributeState.whiteSpace === 'pre'; + if (!shouldPreserve) { + this.texts[r] = trimRight(this.texts[r]); + } + // If find a letter, stop right-trimming + if (this.texts[r].match(/[^\s]/)) { + return false; + } + } + return true; + }; + TextChunk.prototype.measureText = function (context) { + for (var i = 0; i < this.texts.length; i++) { + this.textMeasures.push({ + width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), + length: this.texts[i].length + }); + } + }; + TextChunk.prototype.put = function (context, charSpace) { + var i, textNode, textNodeContext, textMeasure; + var alreadySeen = []; + var xs = [], ys = []; + var currentTextX = this.originX, currentTextY = this.originY; + var minX = currentTextX, maxX = currentTextX; + for (i = 0; i < this.textNodes.length; i++) { + textNode = this.textNodes[i]; + textNodeContext = this.contexts[i]; + textMeasure = this.textMeasures[i] || { + width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), + length: this.texts[i].length + }; + var x = currentTextX; + var y = currentTextY; + if (textNode.nodeName !== '#text') { + if (!alreadySeen.includes(textNode)) { + alreadySeen.push(textNode); + var tSpanDx = TextChunk.resolveRelativePositionAttribute(textNode, 'dx'); + if (tSpanDx !== null) { + x += toPixels(tSpanDx, textNodeContext.attributeState.fontSize); + } + var tSpanDy = TextChunk.resolveRelativePositionAttribute(textNode, 'dy'); + if (tSpanDy !== null) { + y += toPixels(tSpanDy, textNodeContext.attributeState.fontSize); + } + } + } + xs[i] = x; + ys[i] = y; + currentTextX = x + textMeasure.width + textMeasure.length * charSpace; + currentTextY = y; + minX = Math.min(minX, x); + maxX = Math.max(maxX, currentTextX); + } + var textOffset = 0; + switch (this.textAnchor) { + case 'start': + textOffset = 0; + break; + case 'middle': + textOffset = (maxX - minX) / 2; + break; + case 'end': + textOffset = maxX - minX; + break; + } + for (i = 0; i < this.textNodes.length; i++) { + textNode = this.textNodes[i]; + textNodeContext = this.contexts[i]; + if (textNode.nodeName !== '#text') { + if (textNodeContext.attributeState.visibility === 'hidden') { + continue; + } + } + context.pdf.saveGraphicsState(); + applyAttributes(textNodeContext, context, textNode); + var alignmentBaseline = textNodeContext.attributeState.alignmentBaseline; + var textRenderingMode = getTextRenderingMode(textNodeContext.attributeState); + context.pdf.text(this.texts[i], xs[i] - textOffset, ys[i], { + baseline: mapAlignmentBaseline(alignmentBaseline), + angle: context.transform, + renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, + charSpace: charSpace === 0 ? void 0 : charSpace + }); + context.pdf.restoreGraphicsState(); + } + return [currentTextX, currentTextY]; + }; + /** + * Resolves a positional attribute (dx, dy) on a given tSpan, possibly + * inheriting it from the nearest ancestor. Positional attributes + * are only inherited from a parent to its first child. + */ + TextChunk.resolveRelativePositionAttribute = function (element, attributeName) { + var _a; + var currentElement = element; + while (currentElement && nodeIs(currentElement, 'tspan')) { + if (currentElement.hasAttribute(attributeName)) { + return currentElement.getAttribute(attributeName); + } + if (!(((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.firstChild) === element)) { + // positional attributes are only inherited from a parent to its first child + break; + } + currentElement = currentElement.parentElement; + } + return null; + }; + return TextChunk; +}()); + +var TextNode = /** @class */ (function (_super) { + __extends(TextNode, _super); + function TextNode() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.boundingBox = []; + return _this; + } + TextNode.prototype.processTSpans = function (textNode, node, context, textChunks, currentTextSegment, trimInfo) { + var pdfFontSize = context.pdf.getFontSize(); + var shouldPreserve = context.attributeState.xmlSpace === 'preserve' || context.attributeState.whiteSpace === 'pre'; + var firstText = true, initialSpace = false; + for (var i = 0; i < node.childNodes.length; i++) { + var childNode = node.childNodes[i]; + if (!childNode.textContent) { + continue; + } + var textContent = childNode.textContent; + if (childNode.nodeName === '#text') { + var trimmedText = removeNewlines(textContent); + trimmedText = replaceTabsBySpace(trimmedText); + if (!shouldPreserve) { + trimmedText = consolidateSpaces(trimmedText); + // If first text in tspan and starts with a space + if (firstText && trimmedText.match(/^\s/)) { + initialSpace = true; + } + // No longer the first text if we've found a letter + if (trimmedText.match(/[^\s]/)) { + firstText = false; + } + // Consolidate spaces across different children + if (trimInfo.prevText.match(/\s$/)) { + trimmedText = trimLeft(trimmedText); + } + } + var transformedText = transformText(node, trimmedText, context); + currentTextSegment.add(node, transformedText, context); + trimInfo.prevText = textContent; + trimInfo.prevContext = context; + } + else if (nodeIs(childNode, 'title')) ; + else if (nodeIs(childNode, 'tspan')) { + var tSpan = childNode; + var tSpanAbsX = tSpan.getAttribute('x'); + if (tSpanAbsX !== null) { + var x = toPixels(tSpanAbsX, pdfFontSize); + currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || + context.attributeState.textAnchor, x, 0); + textChunks.push({ type: 'y', chunk: currentTextSegment }); + } + var tSpanAbsY = tSpan.getAttribute('y'); + if (tSpanAbsY !== null) { + var y = toPixels(tSpanAbsY, pdfFontSize); + currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || + context.attributeState.textAnchor, 0, y); + textChunks.push({ type: 'x', chunk: currentTextSegment }); + } + var childContext = context.clone(); + parseAttributes(childContext, textNode, tSpan); + this.processTSpans(textNode, tSpan, childContext, textChunks, currentTextSegment, trimInfo); + } + } + return initialSpace; + }; + TextNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var xOffset, charSpace, lengthAdjustment, pdfFontSize, textX, textY, dx, dy, textLength, visibility, tSpanCount, textContent, trimmedText, transformedText, defaultSize, shouldPreserve, alignmentBaseline, textRenderingMode, textChunks, currentTextSegment, initialSpace, trimRight, r, totalDefaultWidth_1, totalLength_1; + return __generator(this, function (_a) { + context.pdf.saveGraphicsState(); + xOffset = 0; + charSpace = 0; + lengthAdjustment = 1; + pdfFontSize = context.pdf.getFontSize(); + textX = toPixels(this.element.getAttribute('x'), pdfFontSize); + textY = toPixels(this.element.getAttribute('y'), pdfFontSize); + dx = toPixels(this.element.getAttribute('dx'), pdfFontSize); + dy = toPixels(this.element.getAttribute('dy'), pdfFontSize); + textLength = parseFloat(this.element.getAttribute('textLength') || '0'); + visibility = context.attributeState.visibility; + tSpanCount = this.element.childElementCount; + if (tSpanCount === 0) { + textContent = this.element.textContent || ''; + trimmedText = transformXmlSpace(textContent, context.attributeState); + transformedText = transformText(this.element, trimmedText, context); + xOffset = context.textMeasure.getTextOffset(transformedText, context.attributeState); + if (textLength > 0) { + defaultSize = context.textMeasure.measureTextWidth(transformedText, context.attributeState); + shouldPreserve = context.attributeState.xmlSpace === 'preserve' || context.attributeState.whiteSpace === 'pre'; + if (!shouldPreserve && textContent.match(/^\s/)) { + lengthAdjustment = 0; + } + charSpace = (textLength - defaultSize) / (transformedText.length - lengthAdjustment) || 0; + } + if (visibility === 'visible') { + alignmentBaseline = context.attributeState.alignmentBaseline; + textRenderingMode = getTextRenderingMode(context.attributeState); + context.pdf.text(transformedText, textX + dx - xOffset, textY + dy, { + baseline: mapAlignmentBaseline(alignmentBaseline), + angle: context.transform, + renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, + charSpace: charSpace === 0 ? void 0 : charSpace + }); + this.boundingBox = [ + textX + dx - xOffset, + textY + dy + 0.1 * pdfFontSize, + context.textMeasure.measureTextWidth(transformedText, context.attributeState), + pdfFontSize + ]; + } + } + else { + textChunks = []; + currentTextSegment = new TextChunk(this, context.attributeState.textAnchor, textX + dx, textY + dy); + textChunks.push({ type: '', chunk: currentTextSegment }); + initialSpace = this.processTSpans(this, this.element, context, textChunks, currentTextSegment, + // Set prevText to ' ' so any spaces on left of <text> are trimmed + { prevText: ' ', prevContext: context }); + lengthAdjustment = initialSpace ? 0 : 1; + trimRight = true; + for (r = textChunks.length - 1; r >= 0; r--) { + if (trimRight) { + trimRight = textChunks[r].chunk.rightTrimText(); + } + } + if (textLength > 0) { + totalDefaultWidth_1 = 0; + totalLength_1 = 0; + textChunks.forEach(function (_a) { + var chunk = _a.chunk; + chunk.measureText(context); + chunk.textMeasures.forEach(function (_a) { + var width = _a.width, length = _a.length; + totalDefaultWidth_1 += width; + totalLength_1 += length; + }); + }); + charSpace = (textLength - totalDefaultWidth_1) / (totalLength_1 - lengthAdjustment); + } + // Put the textchunks + textChunks.reduce(function (lastPositions, _a) { + var type = _a.type, chunk = _a.chunk; + if (type === 'x') { + chunk.setX(lastPositions[0]); + } + else if (type === 'y') { + chunk.setY(lastPositions[1]); + } + return chunk.put(context, charSpace); + }, [0, 0]); + } + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + }); + }); + }; + TextNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + TextNode.prototype.getBoundingBoxCore = function (context) { + return this.boundingBox.length > 0 + ? this.boundingBox + : defaultBoundingBox(this.element, context); + }; + TextNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return TextNode; +}(GraphicsNode)); + +var path_parse; +var hasRequiredPath_parse; + +function requirePath_parse () { + if (hasRequiredPath_parse) return path_parse; + hasRequiredPath_parse = 1; + + + var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 }; + + var SPECIAL_SPACES = [ + 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, + 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF + ]; + + function isSpace(ch) { + return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators + // White spaces + (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) || + (ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0); + } + + function isCommand(code) { + /*eslint-disable no-bitwise*/ + switch (code | 0x20) { + case 0x6D/* m */: + case 0x7A/* z */: + case 0x6C/* l */: + case 0x68/* h */: + case 0x76/* v */: + case 0x63/* c */: + case 0x73/* s */: + case 0x71/* q */: + case 0x74/* t */: + case 0x61/* a */: + case 0x72/* r */: + return true; + } + return false; + } + + function isArc(code) { + return (code | 0x20) === 0x61; + } + + function isDigit(code) { + return (code >= 48 && code <= 57); // 0..9 + } + + function isDigitStart(code) { + return (code >= 48 && code <= 57) || /* 0..9 */ + code === 0x2B || /* + */ + code === 0x2D || /* - */ + code === 0x2E; /* . */ + } + + + function State(path) { + this.index = 0; + this.path = path; + this.max = path.length; + this.result = []; + this.param = 0.0; + this.err = ''; + this.segmentStart = 0; + this.data = []; + } + + function skipSpaces(state) { + while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) { + state.index++; + } + } + + + function scanFlag(state) { + var ch = state.path.charCodeAt(state.index); + + if (ch === 0x30/* 0 */) { + state.param = 0; + state.index++; + return; + } + + if (ch === 0x31/* 1 */) { + state.param = 1; + state.index++; + return; + } + + state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')'; + } + + + function scanParam(state) { + var start = state.index, + index = start, + max = state.max, + zeroFirst = false, + hasCeiling = false, + hasDecimal = false, + hasDot = false, + ch; + + if (index >= max) { + state.err = 'SvgPath: missed param (at pos ' + index + ')'; + return; + } + ch = state.path.charCodeAt(index); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + index++; + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } + + // This logic is shamelessly borrowed from Esprima + // https://github.com/ariya/esprimas + // + if (!isDigit(ch) && ch !== 0x2E/* . */) { + state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')'; + return; + } + + if (ch !== 0x2E/* . */) { + zeroFirst = (ch === 0x30/* 0 */); + index++; + + ch = (index < max) ? state.path.charCodeAt(index) : 0; + + if (zeroFirst && index < max) { + // decimal number starts with '0' such as '09' is illegal. + if (ch && isDigit(ch)) { + state.err = 'SvgPath: numbers started with `0` such as `09` are illegal (at pos ' + start + ')'; + return; + } + } + + while (index < max && isDigit(state.path.charCodeAt(index))) { + index++; + hasCeiling = true; + } + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } + + if (ch === 0x2E/* . */) { + hasDot = true; + index++; + while (isDigit(state.path.charCodeAt(index))) { + index++; + hasDecimal = true; + } + ch = (index < max) ? state.path.charCodeAt(index) : 0; + } + + if (ch === 0x65/* e */ || ch === 0x45/* E */) { + if (hasDot && !hasCeiling && !hasDecimal) { + state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; + return; + } + + index++; + + ch = (index < max) ? state.path.charCodeAt(index) : 0; + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + index++; + } + if (index < max && isDigit(state.path.charCodeAt(index))) { + while (index < max && isDigit(state.path.charCodeAt(index))) { + index++; + } + } else { + state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; + return; + } + } + + state.index = index; + state.param = parseFloat(state.path.slice(start, index)) + 0.0; + } + + + function finalizeSegment(state) { + var cmd, cmdLC; + + // Process duplicated commands (without comand name) + + // This logic is shamelessly borrowed from Raphael + // https://github.com/DmitryBaranovskiy/raphael/ + // + cmd = state.path[state.segmentStart]; + cmdLC = cmd.toLowerCase(); + + var params = state.data; + + if (cmdLC === 'm' && params.length > 2) { + state.result.push([ cmd, params[0], params[1] ]); + params = params.slice(2); + cmdLC = 'l'; + cmd = (cmd === 'm') ? 'l' : 'L'; + } + + if (cmdLC === 'r') { + state.result.push([ cmd ].concat(params)); + } else { + + while (params.length >= paramCounts[cmdLC]) { + state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC]))); + if (!paramCounts[cmdLC]) { + break; + } + } + } + } + + + function scanSegment(state) { + var max = state.max, + cmdCode, is_arc, comma_found, need_params, i; + + state.segmentStart = state.index; + cmdCode = state.path.charCodeAt(state.index); + is_arc = isArc(cmdCode); + + if (!isCommand(cmdCode)) { + state.err = 'SvgPath: bad command ' + state.path[state.index] + ' (at pos ' + state.index + ')'; + return; + } + + need_params = paramCounts[state.path[state.index].toLowerCase()]; + + state.index++; + skipSpaces(state); + + state.data = []; + + if (!need_params) { + // Z + finalizeSegment(state); + return; + } + + comma_found = false; + + for (;;) { + for (i = need_params; i > 0; i--) { + if (is_arc && (i === 3 || i === 4)) scanFlag(state); + else scanParam(state); + + if (state.err.length) { + return; + } + state.data.push(state.param); + + skipSpaces(state); + comma_found = false; + + if (state.index < max && state.path.charCodeAt(state.index) === 0x2C/* , */) { + state.index++; + skipSpaces(state); + comma_found = true; + } + } + + // after ',' param is mandatory + if (comma_found) { + continue; + } + + if (state.index >= state.max) { + break; + } + + // Stop on next segment + if (!isDigitStart(state.path.charCodeAt(state.index))) { + break; + } + } + + finalizeSegment(state); + } + + + /* Returns array of segments: + * + * [ + * [ command, coord1, coord2, ... ] + * ] + */ + path_parse = function pathParse(svgPath) { + var state = new State(svgPath); + var max = state.max; + + skipSpaces(state); + + while (state.index < max && !state.err.length) { + scanSegment(state); + } + + if (state.err.length) { + state.result = []; + + } else if (state.result.length) { + + if ('mM'.indexOf(state.result[0][0]) < 0) { + state.err = 'SvgPath: string should start with `M` or `m`'; + state.result = []; + } else { + state.result[0][0] = 'M'; + } + } + + return { + err: state.err, + segments: state.result + }; + }; + return path_parse; +} + +var matrix; +var hasRequiredMatrix; + +function requireMatrix () { + if (hasRequiredMatrix) return matrix; + hasRequiredMatrix = 1; + + // combine 2 matrixes + // m1, m2 - [a, b, c, d, e, g] + // + function combine(m1, m2) { + return [ + m1[0] * m2[0] + m1[2] * m2[1], + m1[1] * m2[0] + m1[3] * m2[1], + m1[0] * m2[2] + m1[2] * m2[3], + m1[1] * m2[2] + m1[3] * m2[3], + m1[0] * m2[4] + m1[2] * m2[5] + m1[4], + m1[1] * m2[4] + m1[3] * m2[5] + m1[5] + ]; + } + + + function Matrix() { + if (!(this instanceof Matrix)) { return new Matrix(); } + this.queue = []; // list of matrixes to apply + this.cache = null; // combined matrix cache + } + + + Matrix.prototype.matrix = function (m) { + if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0) { + return this; + } + this.cache = null; + this.queue.push(m); + return this; + }; + + + Matrix.prototype.translate = function (tx, ty) { + if (tx !== 0 || ty !== 0) { + this.cache = null; + this.queue.push([ 1, 0, 0, 1, tx, ty ]); + } + return this; + }; + + + Matrix.prototype.scale = function (sx, sy) { + if (sx !== 1 || sy !== 1) { + this.cache = null; + this.queue.push([ sx, 0, 0, sy, 0, 0 ]); + } + return this; + }; + + + Matrix.prototype.rotate = function (angle, rx, ry) { + var rad, cos, sin; + + if (angle !== 0) { + this.translate(rx, ry); + + rad = angle * Math.PI / 180; + cos = Math.cos(rad); + sin = Math.sin(rad); + + this.queue.push([ cos, sin, -sin, cos, 0, 0 ]); + this.cache = null; + + this.translate(-rx, -ry); + } + return this; + }; + + + Matrix.prototype.skewX = function (angle) { + if (angle !== 0) { + this.cache = null; + this.queue.push([ 1, 0, Math.tan(angle * Math.PI / 180), 1, 0, 0 ]); + } + return this; + }; + + + Matrix.prototype.skewY = function (angle) { + if (angle !== 0) { + this.cache = null; + this.queue.push([ 1, Math.tan(angle * Math.PI / 180), 0, 1, 0, 0 ]); + } + return this; + }; + + + // Flatten queue + // + Matrix.prototype.toArray = function () { + if (this.cache) { + return this.cache; + } + + if (!this.queue.length) { + this.cache = [ 1, 0, 0, 1, 0, 0 ]; + return this.cache; + } + + this.cache = this.queue[0]; + + if (this.queue.length === 1) { + return this.cache; + } + + for (var i = 1; i < this.queue.length; i++) { + this.cache = combine(this.cache, this.queue[i]); + } + + return this.cache; + }; + + + // Apply list of matrixes to (x,y) point. + // If `isRelative` set, `translate` component of matrix will be skipped + // + Matrix.prototype.calc = function (x, y, isRelative) { + var m; + + // Don't change point on empty transforms queue + if (!this.queue.length) { return [ x, y ]; } + + // Calculate final matrix, if not exists + // + // NB. if you deside to apply transforms to point one-by-one, + // they should be taken in reverse order + + if (!this.cache) { + this.cache = this.toArray(); + } + + m = this.cache; + + // Apply matrix to point + return [ + x * m[0] + y * m[2] + (isRelative ? 0 : m[4]), + x * m[1] + y * m[3] + (isRelative ? 0 : m[5]) + ]; + }; + + + matrix = Matrix; + return matrix; +} + +var transform_parse; +var hasRequiredTransform_parse; + +function requireTransform_parse () { + if (hasRequiredTransform_parse) return transform_parse; + hasRequiredTransform_parse = 1; + + + var Matrix = requireMatrix(); + + var operations = { + matrix: true, + scale: true, + rotate: true, + translate: true, + skewX: true, + skewY: true + }; + + var CMD_SPLIT_RE = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/; + var PARAMS_SPLIT_RE = /[\s,]+/; + + + transform_parse = function transformParse(transformString) { + var matrix = new Matrix(); + var cmd, params; + + // Split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', ''] + transformString.split(CMD_SPLIT_RE).forEach(function (item) { + + // Skip empty elements + if (!item.length) { return; } + + // remember operation + if (typeof operations[item] !== 'undefined') { + cmd = item; + return; + } + + // extract params & att operation to matrix + params = item.split(PARAMS_SPLIT_RE).map(function (i) { + return +i || 0; + }); + + // If params count is not correct - ignore command + switch (cmd) { + case 'matrix': + if (params.length === 6) { + matrix.matrix(params); + } + return; + + case 'scale': + if (params.length === 1) { + matrix.scale(params[0], params[0]); + } else if (params.length === 2) { + matrix.scale(params[0], params[1]); + } + return; + + case 'rotate': + if (params.length === 1) { + matrix.rotate(params[0], 0, 0); + } else if (params.length === 3) { + matrix.rotate(params[0], params[1], params[2]); + } + return; + + case 'translate': + if (params.length === 1) { + matrix.translate(params[0], 0); + } else if (params.length === 2) { + matrix.translate(params[0], params[1]); + } + return; + + case 'skewX': + if (params.length === 1) { + matrix.skewX(params[0]); + } + return; + + case 'skewY': + if (params.length === 1) { + matrix.skewY(params[0]); + } + return; + } + }); + + return matrix; + }; + return transform_parse; +} + +var a2c; +var hasRequiredA2c; + +function requireA2c () { + if (hasRequiredA2c) return a2c; + hasRequiredA2c = 1; + + + var TAU = Math.PI * 2; + + + /* eslint-disable space-infix-ops */ + + // Calculate an angle between two unit vectors + // + // Since we measure angle between radii of circular arcs, + // we can use simplified math (without length normalization) + // + function unit_vector_angle(ux, uy, vx, vy) { + var sign = (ux * vy - uy * vx < 0) ? -1 : 1; + var dot = ux * vx + uy * vy; + + // Add this to work with arbitrary vectors: + // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); + + // rounding errors, e.g. -1.0000000000000002 can screw up this + if (dot > 1.0) { dot = 1.0; } + if (dot < -1.0) { dot = -1.0; } + + return sign * Math.acos(dot); + } + + + // Convert from endpoint to center parameterization, + // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // + // Return [cx, cy, theta1, delta_theta] + // + function get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi) { + // Step 1. + // + // Moving an ellipse so origin will be the middlepoint between our two + // points. After that, rotate it to line up ellipse axes with coordinate + // axes. + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + var rx_sq = rx * rx; + var ry_sq = ry * ry; + var x1p_sq = x1p * x1p; + var y1p_sq = y1p * y1p; + + // Step 2. + // + // Compute coordinates of the centre of this ellipse (cx', cy') + // in the new coordinate system. + // + var radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); + + if (radicant < 0) { + // due to rounding errors it might be e.g. -1.3877787807814457e-17 + radicant = 0; + } + + radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); + radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); + + var cxp = radicant * rx/ry * y1p; + var cyp = radicant * -ry/rx * x1p; + + // Step 3. + // + // Transform back to get centre coordinates (cx, cy) in the original + // coordinate system. + // + var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2; + var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2; + + // Step 4. + // + // Compute angles (theta1, delta_theta). + // + var v1x = (x1p - cxp) / rx; + var v1y = (y1p - cyp) / ry; + var v2x = (-x1p - cxp) / rx; + var v2y = (-y1p - cyp) / ry; + + var theta1 = unit_vector_angle(1, 0, v1x, v1y); + var delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); + + if (fs === 0 && delta_theta > 0) { + delta_theta -= TAU; + } + if (fs === 1 && delta_theta < 0) { + delta_theta += TAU; + } + + return [ cx, cy, theta1, delta_theta ]; + } + + // + // Approximate one unit arc segment with bézier curves, + // see http://math.stackexchange.com/questions/873224 + // + function approximate_unit_arc(theta1, delta_theta) { + var alpha = 4/3 * Math.tan(delta_theta/4); + + var x1 = Math.cos(theta1); + var y1 = Math.sin(theta1); + var x2 = Math.cos(theta1 + delta_theta); + var y2 = Math.sin(theta1 + delta_theta); + + return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ]; + } + + a2c = function a2c(x1, y1, x2, y2, fa, fs, rx, ry, phi) { + var sin_phi = Math.sin(phi * TAU / 360); + var cos_phi = Math.cos(phi * TAU / 360); + + // Make sure radii are valid + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + if (x1p === 0 && y1p === 0) { + // we're asked to draw line to itself + return []; + } + + if (rx === 0 || ry === 0) { + // one of the radii is zero + return []; + } + + + // Compensate out-of-range radii + // + rx = Math.abs(rx); + ry = Math.abs(ry); + + var lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); + if (lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + + // Get center parameters (cx, cy, theta1, delta_theta) + // + var cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); + + var result = []; + var theta1 = cc[2]; + var delta_theta = cc[3]; + + // Split an arc to multiple segments, so each segment + // will be less than τ/4 (= 90°) + // + var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); + delta_theta /= segments; + + for (var i = 0; i < segments; i++) { + result.push(approximate_unit_arc(theta1, delta_theta)); + theta1 += delta_theta; + } + + // We have a bezier approximation of a unit circle, + // now need to transform back to the original ellipse + // + return result.map(function (curve) { + for (var i = 0; i < curve.length; i += 2) { + var x = curve[i + 0]; + var y = curve[i + 1]; + + // scale + x *= rx; + y *= ry; + + // rotate + var xp = cos_phi*x - sin_phi*y; + var yp = sin_phi*x + cos_phi*y; + + // translate + curve[i + 0] = xp + cc[0]; + curve[i + 1] = yp + cc[1]; + } + + return curve; + }); + }; + return a2c; +} + +var ellipse; +var hasRequiredEllipse; + +function requireEllipse () { + if (hasRequiredEllipse) return ellipse; + hasRequiredEllipse = 1; + + /* eslint-disable space-infix-ops */ + + // The precision used to consider an ellipse as a circle + // + var epsilon = 0.0000000001; + + // To convert degree in radians + // + var torad = Math.PI / 180; + + // Class constructor : + // an ellipse centred at 0 with radii rx,ry and x - axis - angle ax. + // + function Ellipse(rx, ry, ax) { + if (!(this instanceof Ellipse)) { return new Ellipse(rx, ry, ax); } + this.rx = rx; + this.ry = ry; + this.ax = ax; + } + + // Apply a linear transform m to the ellipse + // m is an array representing a matrix : + // - - + // | m[0] m[2] | + // | m[1] m[3] | + // - - + // + Ellipse.prototype.transform = function (m) { + // We consider the current ellipse as image of the unit circle + // by first scale(rx,ry) and then rotate(ax) ... + // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle. + var c = Math.cos(this.ax * torad), s = Math.sin(this.ax * torad); + var ma = [ + this.rx * (m[0]*c + m[2]*s), + this.rx * (m[1]*c + m[3]*s), + this.ry * (-m[0]*s + m[2]*c), + this.ry * (-m[1]*s + m[3]*c) + ]; + + // ma * transpose(ma) = [ J L ] + // [ L K ] + // L is calculated later (if the image is not a circle) + var J = ma[0]*ma[0] + ma[2]*ma[2], + K = ma[1]*ma[1] + ma[3]*ma[3]; + + // the discriminant of the characteristic polynomial of ma * transpose(ma) + var D = ((ma[0]-ma[3])*(ma[0]-ma[3]) + (ma[2]+ma[1])*(ma[2]+ma[1])) * + ((ma[0]+ma[3])*(ma[0]+ma[3]) + (ma[2]-ma[1])*(ma[2]-ma[1])); + + // the "mean eigenvalue" + var JK = (J + K) / 2; + + // check if the image is (almost) a circle + if (D < epsilon * JK) { + // if it is + this.rx = this.ry = Math.sqrt(JK); + this.ax = 0; + return this; + } + + // if it is not a circle + var L = ma[0]*ma[1] + ma[2]*ma[3]; + + D = Math.sqrt(D); + + // {l1,l2} = the two eigen values of ma * transpose(ma) + var l1 = JK + D/2, + l2 = JK - D/2; + // the x - axis - rotation angle is the argument of the l1 - eigenvector + /*eslint-disable indent*/ + this.ax = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? + 90 + : + Math.atan(Math.abs(L) > Math.abs(l1 - K) ? + (l1 - J) / L + : + L / (l1 - K) + ) * 180 / Math.PI; + /*eslint-enable indent*/ + + // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90 + if (this.ax >= 0) { + // if ax in [0,90] + this.rx = Math.sqrt(l1); + this.ry = Math.sqrt(l2); + } else { + // if ax in ]-90,0[ => exchange axes + this.ax += 90; + this.rx = Math.sqrt(l2); + this.ry = Math.sqrt(l1); + } + + return this; + }; + + // Check if the ellipse is (almost) degenerate, i.e. rx = 0 or ry = 0 + // + Ellipse.prototype.isDegenerate = function () { + return (this.rx < epsilon * this.ry || this.ry < epsilon * this.rx); + }; + + ellipse = Ellipse; + return ellipse; +} + +var svgpath$1; +var hasRequiredSvgpath$1; + +function requireSvgpath$1 () { + if (hasRequiredSvgpath$1) return svgpath$1; + hasRequiredSvgpath$1 = 1; + + + var pathParse = requirePath_parse(); + var transformParse = requireTransform_parse(); + var matrix = requireMatrix(); + var a2c = requireA2c(); + var ellipse = requireEllipse(); + + + // Class constructor + // + function SvgPath(path) { + if (!(this instanceof SvgPath)) { return new SvgPath(path); } + + var pstate = pathParse(path); + + // Array of path segments. + // Each segment is array [command, param1, param2, ...] + this.segments = pstate.segments; + + // Error message on parse error. + this.err = pstate.err; + + // Transforms stack for lazy evaluation + this.__stack = []; + } + + SvgPath.from = function (src) { + if (typeof src === 'string') return new SvgPath(src); + + if (src instanceof SvgPath) { + // Create empty object + var s = new SvgPath(''); + + // Clone properies + s.err = src.err; + s.segments = src.segments.map(function (sgm) { return sgm.slice(); }); + s.__stack = src.__stack.map(function (m) { + return matrix().matrix(m.toArray()); + }); + + return s; + } + + throw new Error('SvgPath.from: invalid param type ' + src); + }; + + + SvgPath.prototype.__matrix = function (m) { + var self = this, i; + + // Quick leave for empty matrix + if (!m.queue.length) { return; } + + this.iterate(function (s, index, x, y) { + var p, result, name, isRelative; + + switch (s[0]) { + + // Process 'assymetric' commands separately + case 'v': + p = m.calc(0, s[1], true); + result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ]; + break; + + case 'V': + p = m.calc(x, s[1], false); + result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ]; + break; + + case 'h': + p = m.calc(s[1], 0, true); + result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ]; + break; + + case 'H': + p = m.calc(s[1], y, false); + result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ]; + break; + + case 'a': + case 'A': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + + // Drop segment if arc is empty (end point === start point) + /*if ((s[0] === 'A' && s[6] === x && s[7] === y) || + (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { + return []; + }*/ + + // Transform rx, ry and the x-axis-rotation + var ma = m.toArray(); + var e = ellipse(s[1], s[2], s[3]).transform(ma); + + // flip sweep-flag if matrix is not orientation-preserving + if (ma[0] * ma[3] - ma[1] * ma[2] < 0) { + s[5] = s[5] ? '0' : '1'; + } + + // Transform end point as usual (without translation for relative notation) + p = m.calc(s[6], s[7], s[0] === 'a'); + + // Empty arcs can be ignored by renderer, but should not be dropped + // to avoid collisions with `S A S` and so on. Replace with empty line. + if ((s[0] === 'A' && s[6] === x && s[7] === y) || + (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { + result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; + break; + } + + // if the resulting ellipse is (almost) a segment ... + if (e.isDegenerate()) { + // replace the arc by a line + result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; + } else { + // if it is a real ellipse + // s[0], s[4] and s[5] are not modified + result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ]; + } + + break; + + case 'm': + // Edge case. The very first `m` should be processed as absolute, if happens. + // Make sense for coord shift transforms. + isRelative = index > 0; + + p = m.calc(s[1], s[2], isRelative); + result = [ 'm', p[0], p[1] ]; + break; + + default: + name = s[0]; + result = [ name ]; + isRelative = (name.toLowerCase() === name); + + // Apply transformations to the segment + for (i = 1; i < s.length; i += 2) { + p = m.calc(s[i], s[i + 1], isRelative); + result.push(p[0], p[1]); + } + } + + self.segments[index] = result; + }, true); + }; + + + // Apply stacked commands + // + SvgPath.prototype.__evaluateStack = function () { + var m, i; + + if (!this.__stack.length) { return; } + + if (this.__stack.length === 1) { + this.__matrix(this.__stack[0]); + this.__stack = []; + return; + } + + m = matrix(); + i = this.__stack.length; + + while (--i >= 0) { + m.matrix(this.__stack[i].toArray()); + } + + this.__matrix(m); + this.__stack = []; + }; + + + // Convert processed SVG Path back to string + // + SvgPath.prototype.toString = function () { + var result = '', prevCmd = '', cmdSkipped = false; + + this.__evaluateStack(); + + for (var i = 0, len = this.segments.length; i < len; i++) { + var segment = this.segments[i]; + var cmd = segment[0]; + + // Command not repeating => store + if (cmd !== prevCmd || cmd === 'm' || cmd === 'M') { + // workaround for FontForge SVG importing bug, keep space between "z m". + if (cmd === 'm' && prevCmd === 'z') result += ' '; + result += cmd; + + cmdSkipped = false; + } else { + cmdSkipped = true; + } + + // Store segment params + for (var pos = 1; pos < segment.length; pos++) { + var val = segment[pos]; + // Space can be skipped + // 1. After command (always) + // 2. For negative value (with '-' at start) + if (pos === 1) { + if (cmdSkipped && val >= 0) result += ' '; + } else if (val >= 0) result += ' '; + + result += val; + } + + prevCmd = cmd; + } + + return result; + }; + + + // Translate path to (x [, y]) + // + SvgPath.prototype.translate = function (x, y) { + this.__stack.push(matrix().translate(x, y || 0)); + return this; + }; + + + // Scale path to (sx [, sy]) + // sy = sx if not defined + // + SvgPath.prototype.scale = function (sx, sy) { + this.__stack.push(matrix().scale(sx, (!sy && (sy !== 0)) ? sx : sy)); + return this; + }; + + + // Rotate path around point (sx [, sy]) + // sy = sx if not defined + // + SvgPath.prototype.rotate = function (angle, rx, ry) { + this.__stack.push(matrix().rotate(angle, rx || 0, ry || 0)); + return this; + }; + + + // Skew path along the X axis by `degrees` angle + // + SvgPath.prototype.skewX = function (degrees) { + this.__stack.push(matrix().skewX(degrees)); + return this; + }; + + + // Skew path along the Y axis by `degrees` angle + // + SvgPath.prototype.skewY = function (degrees) { + this.__stack.push(matrix().skewY(degrees)); + return this; + }; + + + // Apply matrix transform (array of 6 elements) + // + SvgPath.prototype.matrix = function (m) { + this.__stack.push(matrix().matrix(m)); + return this; + }; + + + // Transform path according to "transform" attr of SVG spec + // + SvgPath.prototype.transform = function (transformString) { + if (!transformString.trim()) { + return this; + } + this.__stack.push(transformParse(transformString)); + return this; + }; + + + // Round coords with given decimal precition. + // 0 by default (to integers) + // + SvgPath.prototype.round = function (d) { + var contourStartDeltaX = 0, contourStartDeltaY = 0, deltaX = 0, deltaY = 0, l; + + d = d || 0; + + this.__evaluateStack(); + + this.segments.forEach(function (s) { + var isRelative = (s[0].toLowerCase() === s[0]); + + switch (s[0]) { + case 'H': + case 'h': + if (isRelative) { s[1] += deltaX; } + deltaX = s[1] - s[1].toFixed(d); + s[1] = +s[1].toFixed(d); + return; + + case 'V': + case 'v': + if (isRelative) { s[1] += deltaY; } + deltaY = s[1] - s[1].toFixed(d); + s[1] = +s[1].toFixed(d); + return; + + case 'Z': + case 'z': + deltaX = contourStartDeltaX; + deltaY = contourStartDeltaY; + return; + + case 'M': + case 'm': + if (isRelative) { + s[1] += deltaX; + s[2] += deltaY; + } + + deltaX = s[1] - s[1].toFixed(d); + deltaY = s[2] - s[2].toFixed(d); + + contourStartDeltaX = deltaX; + contourStartDeltaY = deltaY; + + s[1] = +s[1].toFixed(d); + s[2] = +s[2].toFixed(d); + return; + + case 'A': + case 'a': + // [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + if (isRelative) { + s[6] += deltaX; + s[7] += deltaY; + } + + deltaX = s[6] - s[6].toFixed(d); + deltaY = s[7] - s[7].toFixed(d); + + s[1] = +s[1].toFixed(d); + s[2] = +s[2].toFixed(d); + s[3] = +s[3].toFixed(d + 2); // better precision for rotation + s[6] = +s[6].toFixed(d); + s[7] = +s[7].toFixed(d); + return; + + default: + // a c l q s t + l = s.length; + + if (isRelative) { + s[l - 2] += deltaX; + s[l - 1] += deltaY; + } + + deltaX = s[l - 2] - s[l - 2].toFixed(d); + deltaY = s[l - 1] - s[l - 1].toFixed(d); + + s.forEach(function (val, i) { + if (!i) { return; } + s[i] = +s[i].toFixed(d); + }); + return; + } + }); + + return this; + }; + + + // Apply iterator function to all segments. If function returns result, + // current segment will be replaced to array of returned segments. + // If empty array is returned, current regment will be deleted. + // + SvgPath.prototype.iterate = function (iterator, keepLazyStack) { + var segments = this.segments, + replacements = {}, + needReplace = false, + lastX = 0, + lastY = 0, + countourStartX = 0, + countourStartY = 0; + var i, j, newSegments; + + if (!keepLazyStack) { + this.__evaluateStack(); + } + + segments.forEach(function (s, index) { + + var res = iterator(s, index, lastX, lastY); + + if (Array.isArray(res)) { + replacements[index] = res; + needReplace = true; + } + + var isRelative = (s[0] === s[0].toLowerCase()); + + // calculate absolute X and Y + switch (s[0]) { + case 'm': + case 'M': + lastX = s[1] + (isRelative ? lastX : 0); + lastY = s[2] + (isRelative ? lastY : 0); + countourStartX = lastX; + countourStartY = lastY; + return; + + case 'h': + case 'H': + lastX = s[1] + (isRelative ? lastX : 0); + return; + + case 'v': + case 'V': + lastY = s[1] + (isRelative ? lastY : 0); + return; + + case 'z': + case 'Z': + // That make sence for multiple contours + lastX = countourStartX; + lastY = countourStartY; + return; + + default: + lastX = s[s.length - 2] + (isRelative ? lastX : 0); + lastY = s[s.length - 1] + (isRelative ? lastY : 0); + } + }); + + // Replace segments if iterator return results + + if (!needReplace) { return this; } + + newSegments = []; + + for (i = 0; i < segments.length; i++) { + if (typeof replacements[i] !== 'undefined') { + for (j = 0; j < replacements[i].length; j++) { + newSegments.push(replacements[i][j]); + } + } else { + newSegments.push(segments[i]); + } + } + + this.segments = newSegments; + + return this; + }; + + + // Converts segments from relative to absolute + // + SvgPath.prototype.abs = function () { + + this.iterate(function (s, index, x, y) { + var name = s[0], + nameUC = name.toUpperCase(), + i; + + // Skip absolute commands + if (name === nameUC) { return; } + + s[0] = nameUC; + + switch (name) { + case 'v': + // v has shifted coords parity + s[1] += y; + return; + + case 'a': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // touch x, y only + s[6] += x; + s[7] += y; + return; + + default: + for (i = 1; i < s.length; i++) { + s[i] += i % 2 ? x : y; // odd values are X, even - Y + } + } + }, true); + + return this; + }; + + + // Converts segments from absolute to relative + // + SvgPath.prototype.rel = function () { + + this.iterate(function (s, index, x, y) { + var name = s[0], + nameLC = name.toLowerCase(), + i; + + // Skip relative commands + if (name === nameLC) { return; } + + // Don't touch the first M to avoid potential confusions. + if (index === 0 && name === 'M') { return; } + + s[0] = nameLC; + + switch (name) { + case 'V': + // V has shifted coords parity + s[1] -= y; + return; + + case 'A': + // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // touch x, y only + s[6] -= x; + s[7] -= y; + return; + + default: + for (i = 1; i < s.length; i++) { + s[i] -= i % 2 ? x : y; // odd values are X, even - Y + } + } + }, true); + + return this; + }; + + + // Converts arcs to cubic bézier curves + // + SvgPath.prototype.unarc = function () { + this.iterate(function (s, index, x, y) { + var new_segments, nextX, nextY, result = [], name = s[0]; + + // Skip anything except arcs + if (name !== 'A' && name !== 'a') { return null; } + + if (name === 'a') { + // convert relative arc coordinates to absolute + nextX = x + s[6]; + nextY = y + s[7]; + } else { + nextX = s[6]; + nextY = s[7]; + } + + new_segments = a2c(x, y, nextX, nextY, s[4], s[5], s[1], s[2], s[3]); + + // Degenerated arcs can be ignored by renderer, but should not be dropped + // to avoid collisions with `S A S` and so on. Replace with empty line. + if (new_segments.length === 0) { + return [ [ s[0] === 'a' ? 'l' : 'L', s[6], s[7] ] ]; + } + + new_segments.forEach(function (s) { + result.push([ 'C', s[2], s[3], s[4], s[5], s[6], s[7] ]); + }); + + return result; + }); + + return this; + }; + + + // Converts smooth curves (with missed control point) to generic curves + // + SvgPath.prototype.unshort = function () { + var segments = this.segments; + var prevControlX, prevControlY, prevSegment; + var curControlX, curControlY; + + // TODO: add lazy evaluation flag when relative commands supported + + this.iterate(function (s, idx, x, y) { + var name = s[0], nameUC = name.toUpperCase(), isRelative; + + // First command MUST be M|m, it's safe to skip. + // Protect from access to [-1] for sure. + if (!idx) { return; } + + if (nameUC === 'T') { // quadratic curve + isRelative = (name === 't'); + + prevSegment = segments[idx - 1]; + + if (prevSegment[0] === 'Q') { + prevControlX = prevSegment[1] - x; + prevControlY = prevSegment[2] - y; + } else if (prevSegment[0] === 'q') { + prevControlX = prevSegment[1] - prevSegment[3]; + prevControlY = prevSegment[2] - prevSegment[4]; + } else { + prevControlX = 0; + prevControlY = 0; + } + + curControlX = -prevControlX; + curControlY = -prevControlY; + + if (!isRelative) { + curControlX += x; + curControlY += y; + } + + segments[idx] = [ + isRelative ? 'q' : 'Q', + curControlX, curControlY, + s[1], s[2] + ]; + + } else if (nameUC === 'S') { // cubic curve + isRelative = (name === 's'); + + prevSegment = segments[idx - 1]; + + if (prevSegment[0] === 'C') { + prevControlX = prevSegment[3] - x; + prevControlY = prevSegment[4] - y; + } else if (prevSegment[0] === 'c') { + prevControlX = prevSegment[3] - prevSegment[5]; + prevControlY = prevSegment[4] - prevSegment[6]; + } else { + prevControlX = 0; + prevControlY = 0; + } + + curControlX = -prevControlX; + curControlY = -prevControlY; + + if (!isRelative) { + curControlX += x; + curControlY += y; + } + + segments[idx] = [ + isRelative ? 'c' : 'C', + curControlX, curControlY, + s[1], s[2], s[3], s[4] + ]; + } + }); + + return this; + }; + + + svgpath$1 = SvgPath; + return svgpath$1; +} + +var svgpath; +var hasRequiredSvgpath; + +function requireSvgpath () { + if (hasRequiredSvgpath) return svgpath; + hasRequiredSvgpath = 1; + + svgpath = requireSvgpath$1(); + return svgpath; +} + +var svgpathExports = requireSvgpath(); +var SvgPath = /*@__PURE__*/getDefaultExportFromCjs(svgpathExports); + +var PathNode = /** @class */ (function (_super) { + __extends(PathNode, _super); + function PathNode(node, children) { + return _super.call(this, true, node, children) || this; + } + PathNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + PathNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + PathNode.prototype.getPath = function (context) { + var svgPath = new SvgPath(getAttribute(this.element, context.styleSheets, 'd') || '') + .unshort() + .unarc() + .abs(); + var path = new Path(); + var prevX; + var prevY; + svgPath.iterate(function (seg) { + switch (seg[0]) { + case 'M': + path.moveTo(seg[1], seg[2]); + break; + case 'L': + path.lineTo(seg[1], seg[2]); + break; + case 'H': + path.lineTo(seg[1], prevY); + break; + case 'V': + path.lineTo(prevX, seg[1]); + break; + case 'C': + path.curveTo(seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]); + break; + case 'Q': + var p2 = toCubic([prevX, prevY], [seg[1], seg[2]]); + var p3 = toCubic([seg[3], seg[4]], [seg[1], seg[2]]); + path.curveTo(p2[0], p2[1], p3[0], p3[1], seg[3], seg[4]); + break; + case 'Z': + path.close(); + break; + } + switch (seg[0]) { + case 'M': + case 'L': + prevX = seg[1]; + prevY = seg[2]; + break; + case 'H': + prevX = seg[1]; + break; + case 'V': + prevY = seg[1]; + break; + case 'C': + prevX = seg[5]; + prevY = seg[6]; + break; + case 'Q': + prevX = seg[3]; + prevY = seg[4]; + break; + } + }); + return path; + }; + return PathNode; +}(GeometryNode)); + +// groups: 1: mime-type (+ charset), 2: mime-type (w/o charset), 3: charset, 4: base64?, 5: body +var dataUriRegex = /^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,((?:.|\s)*)$/i; +var ImageNode = /** @class */ (function (_super) { + __extends(ImageNode, _super); + function ImageNode(element, children) { + var _this = _super.call(this, element, children) || this; + _this.imageLoadingPromise = null; + _this.imageUrl = _this.element.getAttribute('xlink:href') || _this.element.getAttribute('href'); + if (_this.imageUrl) { + // start loading the image as early as possible + _this.imageLoadingPromise = ImageNode.fetchImageData(_this.imageUrl); + } + return _this; + } + ImageNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var width, height, x, y, _a, data, format, parser, svgElement, preserveAspectRatio, idMap, svgnode, dataUri, _b, imgWidth, imgHeight, viewBox, transform, e_1; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + if (!this.imageLoadingPromise) { + return [2 /*return*/]; + } + context.pdf.setCurrentTransformationMatrix(context.transform); + width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'), height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'), x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); + if (!isFinite(width) || width <= 0 || !isFinite(height) || height <= 0) { + return [2 /*return*/]; + } + return [4 /*yield*/, this.imageLoadingPromise]; + case 1: + _a = _c.sent(), data = _a.data, format = _a.format; + if (!(format.indexOf('svg') === 0)) return [3 /*break*/, 3]; + parser = new DOMParser(); + svgElement = parser.parseFromString(data, 'image/svg+xml').firstElementChild; + preserveAspectRatio = this.element.getAttribute('preserveAspectRatio'); + if (!preserveAspectRatio || + preserveAspectRatio.indexOf('defer') < 0 || + !svgElement.getAttribute('preserveAspectRatio')) { + svgElement.setAttribute('preserveAspectRatio', preserveAspectRatio || ''); + } + svgElement.setAttribute('x', String(x)); + svgElement.setAttribute('y', String(y)); + svgElement.setAttribute('width', String(width)); + svgElement.setAttribute('height', String(height)); + idMap = {}; + svgnode = parse(svgElement, idMap); + return [4 /*yield*/, svgnode.render(new Context(context.pdf, { + refsHandler: new ReferencesHandler(idMap), + styleSheets: context.styleSheets, + viewport: new Viewport(width, height), + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _c.sent(); + return [2 /*return*/]; + case 3: + dataUri = "data:image/".concat(format, ";base64,").concat(btoa(data)); + _c.label = 4; + case 4: + _c.trys.push([4, 6, , 7]); + return [4 /*yield*/, ImageNode.getImageDimensions(dataUri)]; + case 5: + _b = _c.sent(), imgWidth = _b[0], imgHeight = _b[1]; + viewBox = [0, 0, imgWidth, imgHeight]; + transform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); + context.pdf.setCurrentTransformationMatrix(transform); + context.pdf.addImage(dataUri, '', // will be ignored anyways if imageUrl is a data url + 0, 0, imgWidth, imgHeight); + return [3 /*break*/, 7]; + case 6: + e_1 = _c.sent(); + typeof console === 'object' && + console.warn && + console.warn("Could not load image ".concat(this.imageUrl, ". \n").concat(e_1)); + return [3 /*break*/, 7]; + case 7: return [2 /*return*/]; + } + }); + }); + }; + ImageNode.prototype.getBoundingBoxCore = function (context) { + return defaultBoundingBox(this.element, context); + }; + ImageNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + ImageNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + ImageNode.fetchImageData = function (imageUrl) { + return __awaiter(this, void 0, void 0, function () { + var data, format, match, mimeType, mimeTypeParts; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + match = imageUrl.match(dataUriRegex); + if (!match) return [3 /*break*/, 1]; + mimeType = match[2]; + mimeTypeParts = mimeType.split('/'); + if (mimeTypeParts[0] !== 'image') { + throw new Error("Unsupported image URL: ".concat(imageUrl)); + } + format = mimeTypeParts[1]; + data = match[5]; + if (match[4] === 'base64') { + data = data.replace(/\s/g, ''); + data = atob(data); + } + else { + data = decodeURIComponent(data); + } + return [3 /*break*/, 3]; + case 1: return [4 /*yield*/, ImageNode.fetchImage(imageUrl)]; + case 2: + data = _a.sent(); + format = imageUrl.substring(imageUrl.lastIndexOf('.') + 1); + _a.label = 3; + case 3: return [2 /*return*/, { + data: data, + format: format + }]; + } + }); + }); + }; + ImageNode.fetchImage = function (imageUrl) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', imageUrl, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function () { + if (xhr.status !== 200) { + throw new Error("Error ".concat(xhr.status, ": Failed to load image '").concat(imageUrl, "'")); + } + var bytes = new Uint8Array(xhr.response); + var data = ''; + for (var i = 0; i < bytes.length; i++) { + data += String.fromCharCode(bytes[i]); + } + resolve(data); + }; + xhr.onerror = reject; + xhr.onabort = reject; + xhr.send(null); + }); + }; + ImageNode.getMimeType = function (format) { + format = format.toLowerCase(); + switch (format) { + case 'jpg': + case 'jpeg': + return 'image/jpeg'; + default: + return "image/".concat(format); + } + }; + ImageNode.getImageDimensions = function (src) { + return new Promise(function (resolve, reject) { + var img = new Image(); + img.onload = function () { + resolve([img.width, img.height]); + }; + img.onerror = reject; + img.src = src; + }); + }; + return ImageNode; +}(GraphicsNode)); + +var Traverse = /** @class */ (function (_super) { + __extends(Traverse, _super); + function Traverse(closed, node, children) { + var _this = _super.call(this, true, node, children) || this; + _this.closed = closed; + return _this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Traverse.prototype.getPath = function (context) { + if (!this.element.hasAttribute('points') || this.element.getAttribute('points') === '') { + return null; + } + // @ts-ignore + var points = Traverse.parsePointsString(this.element.getAttribute('points')); + var path = new Path(); + if (points.length < 1) { + return path; + } + path.moveTo(points[0][0], points[0][1]); + for (var i = 1; i < points.length; i++) { + path.lineTo(points[i][0], points[i][1]); + } + if (this.closed) { + path.close(); + } + return path; + }; + Traverse.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + Traverse.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + Traverse.parsePointsString = function (string) { + var floats = parseFloats(string); + var result = []; + for (var i = 0; i < floats.length - 1; i += 2) { + var x = floats[i]; + var y = floats[i + 1]; + result.push([x, y]); + } + return result; + }; + return Traverse; +}(GeometryNode)); + +var Polygon = /** @class */ (function (_super) { + __extends(Polygon, _super); + function Polygon(node, children) { + return _super.call(this, true, node, children) || this; + } + return Polygon; +}(Traverse)); + +var VoidNode = /** @class */ (function (_super) { + __extends(VoidNode, _super); + function VoidNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + VoidNode.prototype.render = function (parentContext) { + return Promise.resolve(); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + VoidNode.prototype.getBoundingBoxCore = function (context) { + return [0, 0, 0, 0]; + }; + VoidNode.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + VoidNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeIsVisible(this, parentVisible, context); + }; + return VoidNode; +}(SvgNode)); + +var MarkerNode = /** @class */ (function (_super) { + __extends(MarkerNode, _super); + function MarkerNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + MarkerNode.prototype.apply = function (parentContext) { + return __awaiter(this, void 0, void 0, function () { + var tfMatrix, bBox, contextColors, childContext, _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + tfMatrix = this.computeNodeTransform(parentContext); + bBox = this.getBoundingBox(parentContext); + parentContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], tfMatrix); + contextColors = AttributeState.getContextColors(parentContext); + childContext = new Context(parentContext.pdf, { + refsHandler: parentContext.refsHandler, + styleSheets: parentContext.styleSheets, + viewport: parentContext.viewport, + svg2pdfParameters: parentContext.svg2pdfParameters, + textMeasure: parentContext.textMeasure, + attributeState: Object.assign(AttributeState.default(), contextColors) + }); + // "Properties do not inherit from the element referencing the 'marker' into the contents of the + // marker. However, by using the context-stroke value for the fill or stroke on elements in its + // definition, a single marker can be designed to match the style of the element referencing the + // marker." + // -> we need to reset all attributes + applyContext(childContext); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(childContext)]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + parentContext.pdf.endFormObject(childContext.refsHandler.generateKey(this.element.getAttribute('id'), contextColors)); + return [2 /*return*/]; + } + }); + }); + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + MarkerNode.prototype.getBoundingBoxCore = function (context) { + var viewBox = this.element.getAttribute('viewBox'); + var vb; + if (viewBox) { + vb = parseFloats(viewBox); + } + return [ + (vb && vb[0]) || 0, + (vb && vb[1]) || 0, + (vb && vb[2]) || parseFloat(this.element.getAttribute('markerWidth') || '3'), + (vb && vb[3]) || parseFloat(this.element.getAttribute('markerHeight') || '3') + ]; + }; + MarkerNode.prototype.computeNodeTransformCore = function (context) { + var refX = parseFloat(this.element.getAttribute('refX') || '0'); + var refY = parseFloat(this.element.getAttribute('refY') || '0'); + var viewBox = this.element.getAttribute('viewBox'); + var nodeTransform; + if (viewBox) { + var bounds = parseFloats(viewBox); + // "Markers are drawn such that their reference point (i.e., attributes ‘refX’ and ‘refY’) + // is positioned at the given vertex." - The "translate" part of the viewBox transform is + // ignored. + nodeTransform = computeViewBoxTransform(this.element, bounds, 0, 0, parseFloat(this.element.getAttribute('markerWidth') || '3'), parseFloat(this.element.getAttribute('markerHeight') || '3'), context, true); + nodeTransform = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, -refX, -refY), nodeTransform); + } + else { + nodeTransform = context.pdf.Matrix(1, 0, 0, 1, -refX, -refY); + } + return nodeTransform; + }; + MarkerNode.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + return MarkerNode; +}(NonRenderedNode)); + +var Circle = /** @class */ (function (_super) { + __extends(Circle, _super); + function Circle(node, children) { + return _super.call(this, node, children) || this; + } + Circle.prototype.getR = function (context) { + var _a; + return ((_a = this.r) !== null && _a !== void 0 ? _a : (this.r = parseFloat(getAttribute(this.element, context.styleSheets, 'r') || '0'))); + }; + Circle.prototype.getRx = function (context) { + return this.getR(context); + }; + Circle.prototype.getRy = function (context) { + return this.getR(context); + }; + return Circle; +}(EllipseBase)); + +var Polyline = /** @class */ (function (_super) { + __extends(Polyline, _super); + function Polyline(node, children) { + return _super.call(this, false, node, children) || this; + } + return Polyline; +}(Traverse)); + +var ContainerNode = /** @class */ (function (_super) { + __extends(ContainerNode, _super); + function ContainerNode() { + return _super !== null && _super.apply(this, arguments) || this; + } + ContainerNode.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var _i, _a, child; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(context)]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + ContainerNode.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + return ContainerNode; +}(RenderedNode)); + +var Svg = /** @class */ (function (_super) { + __extends(Svg, _super); + function Svg() { + return _super !== null && _super.apply(this, arguments) || this; + } + Svg.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Svg.prototype.render = function (context) { + return __awaiter(this, void 0, void 0, function () { + var x, y, width, height, transform; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.isVisible(context.attributeState.visibility !== 'hidden', context)) { + return [2 /*return*/]; + } + x = this.getX(context); + y = this.getY(context); + width = this.getWidth(context); + height = this.getHeight(context); + context.pdf.saveGraphicsState(); + transform = context.transform; + if (this.element.hasAttribute('transform')) { + // SVG 2 allows transforms on SVG elements + // "The transform should be applied as if the ‘svg’ had a parent element with that transform set." + transform = context.pdf.matrixMult( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parseTransform(this.element.getAttribute('transform'), context), transform); + } + context.pdf.setCurrentTransformationMatrix(transform); + if (!context.withinUse && + getAttribute(this.element, context.styleSheets, 'overflow') !== 'visible') { + // establish a new viewport + context.pdf + .rect(x, y, width, height) + .clip() + .discardPath(); + } + return [4 /*yield*/, _super.prototype.render.call(this, context.clone({ + transform: context.pdf.unitMatrix, + viewport: context.withinUse ? context.viewport : new Viewport(width, height) + }))]; + case 1: + _a.sent(); + context.pdf.restoreGraphicsState(); + return [2 /*return*/]; + } + }); + }); + }; + Svg.prototype.computeNodeTransform = function (context) { + return this.computeNodeTransformCore(context); + }; + Svg.prototype.computeNodeTransformCore = function (context) { + if (context.withinUse) { + return context.pdf.unitMatrix; + } + var x = this.getX(context); + var y = this.getY(context); + var viewBox = this.getViewBox(); + var nodeTransform; + if (viewBox) { + var width = this.getWidth(context); + var height = this.getHeight(context); + nodeTransform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); + } + else { + nodeTransform = context.pdf.Matrix(1, 0, 0, 1, x, y); + } + return nodeTransform; + }; + Svg.prototype.getWidth = function (context) { + if (this.width !== undefined) { + return this.width; + } + var width; + var parameters = context.svg2pdfParameters; + if (this.isOutermostSvg(context)) { + // special treatment for the outermost SVG element + if (parameters.width != null) { + // if there is a user defined width, use it + width = parameters.width; + } + else { + // otherwise check if the SVG element defines the width itself + var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); + if (widthAttr) { + width = parseFloat(widthAttr); + } + else { + // if not, check if we can figure out the aspect ratio from the viewBox attribute + var viewBox = this.getViewBox(); + if (viewBox && + (parameters.height != null || getAttribute(this.element, context.styleSheets, 'height'))) { + // if there is a viewBox and the height is defined, use the width that matches the height together with the aspect ratio + var aspectRatio = viewBox[2] / viewBox[3]; + width = this.getHeight(context) * aspectRatio; + } + else { + // if there is no viewBox use a default of 300 or the largest size that fits into the outer viewport + // at an aspect ratio of 2:1 + width = Math.min(300, context.viewport.width, context.viewport.height * 2); + } + } + } + } + else { + var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); + width = widthAttr ? parseFloat(widthAttr) : context.viewport.width; + } + return (this.width = width); + }; + Svg.prototype.getHeight = function (context) { + if (this.height !== undefined) { + return this.height; + } + var height; + var parameters = context.svg2pdfParameters; + if (this.isOutermostSvg(context)) { + // special treatment for the outermost SVG element + if (parameters.height != null) { + // if there is a user defined height, use it + height = parameters.height; + } + else { + // otherwise check if the SVG element defines the height itself + var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); + if (heightAttr) { + height = parseFloat(heightAttr); + } + else { + // if not, check if we can figure out the aspect ratio from the viewBox attribute + var viewBox = this.getViewBox(); + if (viewBox) { + // if there is a viewBox, use the height that matches the width together with the aspect ratio + var aspectRatio = viewBox[2] / viewBox[3]; + height = this.getWidth(context) / aspectRatio; + } + else { + // if there is no viewBox use a default of 150 or the largest size that fits into the outer viewport + // at an aspect ratio of 2:1 + height = Math.min(150, context.viewport.width / 2, context.viewport.height); + } + } + } + } + else { + var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); + height = heightAttr ? parseFloat(heightAttr) : context.viewport.height; + } + return (this.height = height); + }; + Svg.prototype.getX = function (context) { + if (this.x !== undefined) { + return this.x; + } + if (this.isOutermostSvg(context)) { + return (this.x = 0); + } + var xAttr = getAttribute(this.element, context.styleSheets, 'x'); + return (this.x = xAttr ? parseFloat(xAttr) : 0); + }; + Svg.prototype.getY = function (context) { + if (this.y !== undefined) { + return this.y; + } + if (this.isOutermostSvg(context)) { + return (this.y = 0); + } + var yAttr = getAttribute(this.element, context.styleSheets, 'y'); + return (this.y = yAttr ? parseFloat(yAttr) : 0); + }; + Svg.prototype.getViewBox = function () { + if (this.viewBox !== undefined) { + return this.viewBox; + } + var viewBox = this.element.getAttribute('viewBox'); + return (this.viewBox = viewBox ? parseFloats(viewBox) : undefined); + }; + Svg.prototype.isOutermostSvg = function (context) { + return context.svg2pdfParameters.element === this.element; + }; + return Svg; +}(ContainerNode)); + +var Group = /** @class */ (function (_super) { + __extends(Group, _super); + function Group() { + return _super !== null && _super.apply(this, arguments) || this; + } + Group.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + Group.prototype.computeNodeTransformCore = function (context) { + return context.pdf.unitMatrix; + }; + return Group; +}(ContainerNode)); + +var Anchor = /** @class */ (function (_super) { + __extends(Anchor, _super); + function Anchor() { + return _super !== null && _super.apply(this, arguments) || this; + } + Anchor.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var href, box, scale, ph; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, _super.prototype.renderCore.call(this, context)]; + case 1: + _a.sent(); + href = getAttribute(this.element, context.styleSheets, 'href'); + if (href) { + box = this.getBoundingBox(context); + scale = context.pdf.internal.scaleFactor; + ph = context.pdf.internal.pageSize.getHeight(); + context.pdf.link(scale * (box[0] * context.transform.sx + context.transform.tx), scale * (ph - box[1] * context.transform.sy - context.transform.ty), scale * context.transform.sx * box[2], scale * context.transform.sy * box[3], { url: href }); + } + return [2 /*return*/]; + } + }); + }); + }; + return Anchor; +}(Group)); + +var ClipPath = /** @class */ (function (_super) { + __extends(ClipPath, _super); + function ClipPath() { + return _super !== null && _super.apply(this, arguments) || this; + } + ClipPath.prototype.apply = function (context) { + return __awaiter(this, void 0, void 0, function () { + var clipPathMatrix, _i, _a, child, hasClipRuleFromFirstChild, clipRule; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!this.isVisible(true, context)) { + return [2 /*return*/]; + } + clipPathMatrix = context.pdf.matrixMult(this.computeNodeTransform(context), context.transform); + context.pdf.setCurrentTransformationMatrix(clipPathMatrix); + _i = 0, _a = this.children; + _b.label = 1; + case 1: + if (!(_i < _a.length)) return [3 /*break*/, 4]; + child = _a[_i]; + return [4 /*yield*/, child.render(new Context(context.pdf, { + refsHandler: context.refsHandler, + styleSheets: context.styleSheets, + viewport: context.viewport, + withinClipPath: true, + svg2pdfParameters: context.svg2pdfParameters, + textMeasure: context.textMeasure + }))]; + case 2: + _b.sent(); + _b.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + hasClipRuleFromFirstChild = this.children.length > 0 && !!getAttribute(this.children[0].element, context.styleSheets, 'clip-rule'); + clipRule = hasClipRuleFromFirstChild + ? this.getClipRuleAttr(this.children[0].element, context.styleSheets) + : this.getClipRuleAttr(this.element, context.styleSheets); + context.pdf.clip(clipRule).discardPath(); + // as we cannot use restoreGraphicsState() to reset the transform (this would reset the clipping path, as well), + // we must append the inverse instead + context.pdf.setCurrentTransformationMatrix(clipPathMatrix.inversed()); + return [2 /*return*/]; + } + }); + }); + }; + ClipPath.prototype.getBoundingBoxCore = function (context) { + return getBoundingBoxByChildren(context, this); + }; + ClipPath.prototype.isVisible = function (parentVisible, context) { + return svgNodeAndChildrenVisible(this, parentVisible, context); + }; + ClipPath.prototype.getClipRuleAttr = function (element, styleSheets) { + return getAttribute(element, styleSheets, 'clip-rule') === 'evenodd' ? 'evenodd' : undefined; + }; + return ClipPath; +}(NonRenderedNode)); + +function parse(node, idMap) { + var svgnode; + var children = []; + forEachChild(node, function (i, n) { return children.push(parse(n, idMap)); }); + switch (node.tagName.toLowerCase()) { + case 'a': + svgnode = new Anchor(node, children); + break; + case 'g': + svgnode = new Group(node, children); + break; + case 'circle': + svgnode = new Circle(node, children); + break; + case 'clippath': + svgnode = new ClipPath(node, children); + break; + case 'ellipse': + svgnode = new Ellipse(node, children); + break; + case 'lineargradient': + svgnode = new LinearGradient(node, children); + break; + case 'image': + svgnode = new ImageNode(node, children); + break; + case 'line': + svgnode = new Line(node, children); + break; + case 'marker': + svgnode = new MarkerNode(node, children); + break; + case 'path': + svgnode = new PathNode(node, children); + break; + case 'pattern': + svgnode = new Pattern(node, children); + break; + case 'polygon': + svgnode = new Polygon(node, children); + break; + case 'polyline': + svgnode = new Polyline(node, children); + break; + case 'radialgradient': + svgnode = new RadialGradient(node, children); + break; + case 'rect': + svgnode = new Rect(node, children); + break; + case 'svg': + svgnode = new Svg(node, children); + break; + case 'symbol': + svgnode = new Symbol$1(node, children); + break; + case 'text': + svgnode = new TextNode(node, children); + break; + case 'use': + svgnode = new Use(node, children); + break; + default: + svgnode = new VoidNode(node, children); + break; + } + if (idMap != undefined && svgnode.element.hasAttribute('id')) { + // const id = cssesc(svgnode.element.id, { isIdentifier: true }) + var id = svgnode.element.id; // jsroot has plain ids + idMap[id] = idMap[id] || svgnode; + } + svgnode.children.forEach(function (c) { return c.setParent(svgnode); }); + return svgnode; +} + +var StyleSheets = /** @class */ (function () { + function StyleSheets(rootSvg, loadExtSheets) { + this.rootSvg = rootSvg; + this.loadExternalSheets = loadExtSheets; + this.styleSheets = []; + } + StyleSheets.prototype.load = function () { + return __awaiter(this, void 0, void 0, function () { + var sheetTexts; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.collectStyleSheetTexts()]; + case 1: + sheetTexts = _a.sent(); + this.parseCssSheets(sheetTexts); + return [2 /*return*/]; + } + }); + }); + }; + StyleSheets.prototype.collectStyleSheetTexts = function () { + return __awaiter(this, void 0, void 0, function () { + var sheetTexts, i, node, styleElements, i, styleElement; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + sheetTexts = []; + if (this.loadExternalSheets && this.rootSvg.ownerDocument) { + for (i = 0; i < this.rootSvg.ownerDocument.childNodes.length; i++) { + node = this.rootSvg.ownerDocument.childNodes[i]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (node.nodeName === 'xml-stylesheet' && typeof node.data === 'string') { + sheetTexts.push(StyleSheets.loadSheet( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + node.data + .match(/href=["'].*?["']/)[0] + .split('=')[1] + .slice(1, -1))); + } + } + } + styleElements = this.rootSvg.querySelectorAll('style,link'); + for (i = 0; i < styleElements.length; i++) { + styleElement = styleElements[i]; + if (nodeIs(styleElement, 'style')) { + sheetTexts.push(styleElement.textContent); + } + else if (this.loadExternalSheets && + nodeIs(styleElement, 'link') && + styleElement.getAttribute('rel') === 'stylesheet' && + styleElement.hasAttribute('href')) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sheetTexts.push(StyleSheets.loadSheet(styleElement.getAttribute('href'))); + } + } + return [4 /*yield*/, Promise.all(sheetTexts)]; + case 1: return [2 /*return*/, (_a.sent()).filter(function (sheet) { return sheet !== null; })]; + } + }); + }); + }; + StyleSheets.prototype.parseCssSheets = function (sheetTexts) { + var styleDoc = document.implementation.createHTMLDocument(''); + for (var _i = 0, sheetTexts_1 = sheetTexts; _i < sheetTexts_1.length; _i++) { + var sheetText = sheetTexts_1[_i]; + var style = styleDoc.createElement('style'); + style.textContent = sheetText; + styleDoc.body.appendChild(style); + var sheet = style.sheet; + if (sheet instanceof CSSStyleSheet) { + for (var i = sheet.cssRules.length - 1; i >= 0; i--) { + var cssRule = sheet.cssRules[i]; + if (!(cssRule instanceof CSSStyleRule)) { + sheet.deleteRule(i); + continue; + } + var cssStyleRule = cssRule; + if (cssStyleRule.selectorText.indexOf(',') >= 0) { + sheet.deleteRule(i); + var body = cssStyleRule.cssText.substring(cssStyleRule.selectorText.length); + var selectors = StyleSheets.splitSelectorAtCommas(cssStyleRule.selectorText); + for (var j = 0; j < selectors.length; j++) { + sheet.insertRule(selectors[j] + body, i + j); + } + } + } + this.styleSheets.push(sheet); + } + } + }; + StyleSheets.splitSelectorAtCommas = function (selectorText) { + var initialRegex = /,|["']/g; + var closingDoubleQuotesRegex = /[^\\]["]/g; + var closingSingleQuotesRegex = /[^\\][']/g; + var parts = []; + var state = 'initial'; + var match; + var lastCommaIndex = -1; + var closingQuotesRegex = closingDoubleQuotesRegex; + for (var i = 0; i < selectorText.length;) { + switch (state) { + case 'initial': + initialRegex.lastIndex = i; + match = initialRegex.exec(selectorText); + if (match) { + if (match[0] === ',') { + parts.push(selectorText.substring(lastCommaIndex + 1, initialRegex.lastIndex - 1).trim()); + lastCommaIndex = initialRegex.lastIndex - 1; + } + else { + state = 'withinQuotes'; + closingQuotesRegex = + match[0] === '"' ? closingDoubleQuotesRegex : closingSingleQuotesRegex; + } + i = initialRegex.lastIndex; + } + else { + parts.push(selectorText.substring(lastCommaIndex + 1).trim()); + i = selectorText.length; + } + break; + case 'withinQuotes': + closingQuotesRegex.lastIndex = i; + match = closingQuotesRegex.exec(selectorText); + if (match) { + i = closingQuotesRegex.lastIndex; + state = 'initial'; + } + // else this is a syntax error - omit the last part... + break; + } + } + return parts; + }; + StyleSheets.loadSheet = function (url) { + return (new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'text'; + xhr.onload = function () { + if (xhr.status !== 200) { + reject(new Error("Error ".concat(xhr.status, ": Failed to load '").concat(url, "'"))); + } + resolve(xhr.responseText); + }; + xhr.onerror = reject; + xhr.onabort = reject; + xhr.send(null); + }) + // ignore the error since some stylesheets may not be accessible + // due to CORS policies + .catch(function () { return null; })); + }; + StyleSheets.prototype.getPropertyValue = function (node, propertyCss) { + var matchingRules = []; + for (var _i = 0, _a = this.styleSheets; _i < _a.length; _i++) { + var sheet = _a[_i]; + for (var i = 0; i < sheet.cssRules.length; i++) { + var rule = sheet.cssRules[i]; + if (rule.style.getPropertyValue(propertyCss) && node.matches(rule.selectorText)) { + matchingRules.push(rule); + } + } + } + if (matchingRules.length === 0) { + return undefined; + } + var compare = function (a, b) { + var priorityA = a.style.getPropertyPriority(propertyCss); + var priorityB = b.style.getPropertyPriority(propertyCss); + if (priorityA !== priorityB) { + return priorityA === 'important' ? 1 : -1; + } + // console.log('removed specificity check ', a.selectorText, b.selectorText); + return 0; + // return compareSpecificity(a.selectorText, b.selectorText) + }; + var mostSpecificRule = matchingRules.reduce(function (previousValue, currentValue) { + return compare(previousValue, currentValue) === 1 ? previousValue : currentValue; + }); + return mostSpecificRule.style.getPropertyValue(propertyCss) || undefined; + }; + return StyleSheets; +}()); + +var TextMeasure = /** @class */ (function () { + function TextMeasure() { + this.measureMethods = {}; + } + TextMeasure.prototype.getTextOffset = function (text, attributeState) { + var textAnchor = attributeState.textAnchor; + if (textAnchor === 'start') { + return 0; + } + var width = this.measureTextWidth(text, attributeState); + var xOffset = 0; + switch (textAnchor) { + case 'end': + xOffset = width; + break; + case 'middle': + xOffset = width / 2; + break; + } + return xOffset; + }; + TextMeasure.prototype.measureTextWidth = function (text, attributeState) { + if (text.length === 0) { + return 0; + } + var fontFamily = attributeState.fontFamily; + var measure = this.getMeasureFunction(fontFamily); + return measure.call(this, text, attributeState.fontFamily, attributeState.fontSize + 'px', attributeState.fontStyle, attributeState.fontWeight); + }; + TextMeasure.prototype.getMeasurementTextNode = function () { + if (!this.textMeasuringTextElement) { + this.textMeasuringTextElement = document.createElementNS(svgNamespaceURI, 'text'); + var svg = document.createElementNS(svgNamespaceURI, 'svg'); + svg.appendChild(this.textMeasuringTextElement); + svg.style.setProperty('position', 'absolute'); + svg.style.setProperty('visibility', 'hidden'); + document.body.appendChild(svg); + } + return this.textMeasuringTextElement; + }; + TextMeasure.prototype.canvasTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight) { + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + if (context != null) { + context.font = [fontStyle, fontWeight, fontSize, fontFamily].join(' '); + return context.measureText(text).width; + } + return 0; + }; + TextMeasure.prototype.svgTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight, measurementTextNode) { + if (measurementTextNode === void 0) { measurementTextNode = this.getMeasurementTextNode(); } + var textNode = measurementTextNode; + textNode.setAttribute('font-family', fontFamily); + textNode.setAttribute('font-size', fontSize); + textNode.setAttribute('font-style', fontStyle); + textNode.setAttribute('font-weight', fontWeight); + textNode.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + textNode.textContent = text; + return textNode.getBBox().width; + }; + /** + * Canvas text measuring is a lot faster than svg measuring. However, it is inaccurate for some fonts. So test each + * font once and decide if canvas is accurate enough. + */ + TextMeasure.prototype.getMeasureFunction = function (fontFamily) { + var method = this.measureMethods[fontFamily]; + if (!method) { + var fontSize = '16px'; + var fontStyle = 'normal'; + var fontWeight = 'normal'; + var canvasWidth = this.canvasTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); + var svgWidth = this.svgTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); + method = + Math.abs(canvasWidth - svgWidth) < TextMeasure.epsilon + ? this.canvasTextMeasure + : this.svgTextMeasure; + this.measureMethods[fontFamily] = method; + } + return method; + }; + TextMeasure.prototype.cleanupTextMeasuring = function () { + if (this.textMeasuringTextElement) { + var parentNode = this.textMeasuringTextElement.parentNode; + if (parentNode) { + document.body.removeChild(parentNode); + } + this.textMeasuringTextElement = undefined; + } + }; + TextMeasure.testString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!"$%&/()=?\'\\+*-_.:,;^}][{#~|<>'; + TextMeasure.epsilon = 0.1; + return TextMeasure; +}()); + +function svg2pdf(element_1, pdf_1) { + return __awaiter(this, arguments, void 0, function (element, pdf, options) { + var x, y, extCss, idMap, refsHandler, styleSheets, viewport, svg2pdfParameters, textMeasure, context, fill, node; + var _a, _b, _c; + if (options === void 0) { options = {}; } + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + x = (_a = options.x) !== null && _a !== void 0 ? _a : 0.0; + y = (_b = options.y) !== null && _b !== void 0 ? _b : 0.0; + extCss = (_c = options.loadExternalStyleSheets) !== null && _c !== void 0 ? _c : false; + idMap = {}; + refsHandler = new ReferencesHandler(idMap); + styleSheets = new StyleSheets(element, extCss); + return [4 /*yield*/, styleSheets.load() + // start with the entire page size as viewport + ]; + case 1: + _d.sent(); + viewport = new Viewport(pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight()); + svg2pdfParameters = __assign(__assign({}, options), { element: element }); + textMeasure = new TextMeasure(); + context = new Context(pdf, { + refsHandler: refsHandler, + styleSheets: styleSheets, + viewport: viewport, + svg2pdfParameters: svg2pdfParameters, + textMeasure: textMeasure + }); + pdf.advancedAPI(); + pdf.saveGraphicsState(); + // set offsets + pdf.setCurrentTransformationMatrix(pdf.Matrix(1, 0, 0, 1, x, y)); + // set default values that differ from pdf defaults + pdf.setLineWidth(context.attributeState.strokeWidth); + fill = context.attributeState.fill.color; + pdf.setFillColor(fill.r, fill.g, fill.b); + pdf.setFont(context.attributeState.fontFamily); + // correct for a jsPDF-instance measurement unit that differs from `pt` + pdf.setFontSize(context.attributeState.fontSize * pdf.internal.scaleFactor); + node = parse(element, idMap); + return [4 /*yield*/, node.render(context)]; + case 2: + _d.sent(); + pdf.restoreGraphicsState(); + pdf.compatAPI(); + context.textMeasure.cleanupTextMeasuring(); + return [2 /*return*/, pdf]; + } + }); + }); +} +jsPDF.API.svg = function (element, options) { + if (options === void 0) { options = {}; } + return svg2pdf(element, this, options); +}; + +export { svg2pdf }; diff --git a/modules/core.mjs b/modules/core.mjs index 349fb33b6..3fe979f42 100644 --- a/modules/core.mjs +++ b/modules/core.mjs @@ -1,10 +1,12 @@ +/* eslint-disable @stylistic/js/indent */ + /** @summary version id * @desc For the JSROOT release the string in format 'major.minor.patch' like '7.0.0' */ const version_id = 'dev', /** @summary version date * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '29/05/2024', +version_date = '28/11/2025', /** @summary version id and date * @desc Produced by concatenation of {@link version_id} and {@link version_date} @@ -13,7 +15,7 @@ version = version_id + ' ' + version_date, /** @summary Is node.js flag * @private */ -nodejs = !!((typeof process === 'object') && isObject(process.versions) && process.versions.node && process.versions.v8), +nodejs = Boolean((typeof process === 'object') && process.versions?.node && process.versions.v8), /** @summary internal data * @private */ @@ -22,7 +24,41 @@ internals = { id_counter: 1 }, -_src = import.meta?.url; +_src = import.meta?.url, + +_src_dir = '$jsrootsys'; + + +/** @summary Check if argument is a not-null Object + * @private */ +function isObject(arg) { return arg && typeof arg === 'object'; } + +/** @summary Check if argument is a Function + * @private */ +function isFunc(arg) { return typeof arg === 'function'; } + +/** @summary Check if argument is a String + * @private */ +function isStr(arg) { return typeof arg === 'string'; } + +/** @summary Check if object is a Promise + * @private */ +function isPromise(obj) { return isObject(obj) && isFunc(obj.then); } + +/** @summary Postpone func execution and return result in promise + * @private */ +function postponePromise(func, timeout) { + return new Promise(resolveFunc => { + setTimeout(() => { + const res = isFunc(func) ? func() : func; + resolveFunc(res); + }, timeout); + }); +} + +/** @summary Provide promise in any case + * @private */ +function getPromise(obj) { return isPromise(obj) ? obj : Promise.resolve(obj); } /** @summary Location of JSROOT modules @@ -30,17 +66,25 @@ _src = import.meta?.url; * @private */ let source_dir = ''; -if (_src && isStr(_src)) { - const pos = _src.indexOf('modules/core.mjs'); - if (pos >= 0) { +if (_src_dir[0] !== '$') + source_dir = _src_dir; +else if (_src && isStr(_src)) { + let pos = _src.indexOf('modules/core.mjs'); + if (pos < 0) + pos = _src.indexOf('build/jsroot.js'); + if (pos < 0) + pos = _src.indexOf('build/jsroot.min.js'); + if (pos >= 0) source_dir = _src.slice(0, pos); - if (!nodejs) - console.log(`Set jsroot source_dir to ${source_dir}, ${version}`); - } else { - if (!nodejs) - console.log(`jsroot bundle, ${version}`); + else internals.ignore_v6 = true; - } +} + +if (!nodejs) { + if (source_dir) + console.log(`Set jsroot source_dir to ${source_dir}, ${version}`); + else + console.log(`jsroot bundle, ${version}`); } /** @summary Is batch mode flag @@ -52,7 +96,7 @@ function isBatchMode() { return batch_mode; } /** @summary Set batch mode * @private */ -function setBatchMode(on) { batch_mode = !!on; } +function setBatchMode(on) { batch_mode = Boolean(on); } /** @summary Indicates if running inside Node.js */ function isNodeJs() { return nodejs; } @@ -86,11 +130,12 @@ if ((typeof document !== 'undefined') && (typeof window !== 'undefined') && (typ } else { browser.isFirefox = navigator.userAgent.indexOf('Firefox') >= 0; browser.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - browser.isChrome = !!window.chrome; + browser.isChrome = Boolean(window.chrome); browser.isChromeHeadless = navigator.userAgent.indexOf('HeadlessChrome') >= 0; - browser.chromeVersion = (browser.isChrome || browser.isChromeHeadless) ? parseInt(navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)[1]) : 0; + browser.chromeVersion = (browser.isChrome || browser.isChromeHeadless) ? (navigator.userAgent.indexOf('Chrom') > 0 ? parseInt(navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)[1]) : 134) : 0; browser.isWin = navigator.userAgent.indexOf('Windows') >= 0; } + browser.android = /android/i.test(navigator.userAgent); browser.touches = ('ontouchend' in document); // identify if touch events are supported browser.screenWidth = window.screen?.width ?? 1200; } @@ -99,9 +144,11 @@ if ((typeof document !== 'undefined') && (typeof window !== 'undefined') && (typ * @return {Number} 0 - not array, 1 - regular array, 2 - typed array * @private */ function isArrayProto(proto) { - if ((proto.length < 14) || (proto.indexOf('[object ') !== 0)) return 0; + if ((proto.length < 14) || proto.indexOf('[object ')) + return 0; const p = proto.indexOf('Array]'); - if ((p < 0) || (p !== proto.length - 6)) return 0; + if ((p < 0) || (p !== proto.length - 6)) + return 0; // plain array has only '[object Array]', typed array type name inside return proto.length === 14 ? 1 : 2; } @@ -118,12 +165,19 @@ const constants = { WebGL: 1, /** @summary Use WebGL rendering, but convert into svg image, not interactive */ WebGLImage: 2, - /** @summary Use SVG rendering, slow, inprecise and not interactive, nor recommendet */ + /** @summary Use SVG rendering, slow, imprecise and not interactive, not recommended */ SVG: 3, + /** @summary Disable renderer, used for three.js model creation, only for internal use recommended */ + None: 4, fromString(s) { - if ((s === 'webgl') || (s === 'gl')) return this.WebGL; - if (s === 'img') return this.WebGLImage; - if (s === 'svg') return this.SVG; + if ((s === 'webgl') || (s === 'gl')) + return this.WebGL; + if (s === 'img') + return this.WebGLImage; + if (s === 'svg') + return this.SVG; + if (s === 'none') + return this.None; return this.Default; } }, @@ -132,18 +186,20 @@ const constants = { Embed3D: { /** @summary Do not embed 3D drawing, use complete space */ NoEmbed: -1, - /** @summary Default embeding mode - on Firefox and latest Chrome is real ```Embed```, on all other ```Overlay``` */ + /** @summary Default embedding mode - on Firefox and latest Chrome is real ```Embed```, on all other ```Overlay``` */ Default: 0, - /** @summary WebGL canvas not inserted into SVG, but just overlayed The only way how earlier Chrome browser can be used */ + /** @summary WebGL canvas not inserted into SVG, but just overlay. The only way how old Chrome browser can be used */ Overlay: 1, /** @summary Really embed WebGL Canvas into SVG */ Embed: 2, - /** @summary Embeding, but when SVG rendering or SVG image converion is used */ + /** @summary Embedding, but when SVG rendering or SVG image conversion is used */ EmbedSVG: 3, /** @summary Convert string values into number */ fromString(s) { - if (s === 'embed') return this.Embed; - if (s === 'overlay') return this.Overlay; + if (s === 'embed') + return this.Embed; + if (s === 'overlay') + return this.Overlay; return this.Default; } }, @@ -193,6 +249,12 @@ settings = { Render3DBatch: constants.Render3D.Default, /** @summary Way to embed 3D drawing in SVG, see {@link constants.Embed3D} for possible values */ Embed3D: constants.Embed3D.Default, + /** @summary Default canvas width */ + CanvasWidth: 1200, + /** @summary Default canvas height */ + CanvasHeight: 800, + /** @summary Canvas pixel ratio between viewport and display, default 1 */ + CanvasScale: 1, /** @summary Enable or disable tooltips, default on */ Tooltip: !nodejs, /** @summary Time in msec for appearance of tooltips, 0 - no animation */ @@ -207,16 +269,18 @@ settings = { ZoomWheel: !nodejs, /** @summary Zooming on touch devices */ ZoomTouch: !nodejs, - /** @summary Enables move and resize of elements like statbox, title, pave, colz */ + /** @summary Enables move and resize of elements like statistic box, title, pave, colz */ MoveResize: !browser.touches && !nodejs, - /** @summary Configures keybord key press handling - * @desc Can be disabled to prevent keys heandling in complex HTML layouts + /** @summary Configures keyboard key press handling + * @desc Can be disabled to prevent keys handling in complex HTML layouts * @default true */ HandleKeys: !nodejs, /** @summary enables drag and drop functionality */ DragAndDrop: !nodejs, /** @summary Interactive dragging of TGraph points */ DragGraphs: true, + /** @summary Value of user-select style in interactive drawings */ + UserSelect: 'none', /** @summary Show progress box, can be false, true or 'modal' */ ProgressBox: !nodejs, /** @summary Show additional tool buttons on the canvas, false - disabled, true - enabled, 'popup' - only toggle button */ @@ -231,12 +295,14 @@ settings = { CanAdjustFrame: false, /** @summary calculation of text size consumes time and can be skipped to improve performance (but with side effects on text adjustments) */ ApproxTextSize: false, + /** @summary Load symbol.ttf font to display greek labels. By default font file not loaded and unicode is used */ + LoadSymbolTtf: false, /** @summary Histogram drawing optimization: 0 - disabled, 1 - only for large (>5000 1d bins, >50 2d bins) histograms, 2 - always */ OptimizeDraw: 1, /** @summary Automatically create stats box, default on */ AutoStat: true, /** @summary Default frame position in NFC - * @deprecated Use gStyle.fPad[Left/Right/Top/Bottom]Margin values instead */ + * @deprecated Use gStyle.fPad[Left/Right/Top/Bottom]Margin values instead, to be removed in v8 */ FrameNDC: {}, /** @summary size of pad, where many features will be deactivated like text draw or zooming */ SmallPad: { width: 150, height: 100 }, @@ -262,22 +328,34 @@ settings = { YValuesFormat: undefined, /** @summary custom format for all Z values, when not specified {@link gStyle.fStatFormat} is used */ ZValuesFormat: undefined, - /** @summary Let detect and solve problem when browser returns wrong content-length parameter + /** @summary Let detect and solve problem when server returns wrong Content-Length header * @desc See [jsroot#189]{@link https://github.com/root-project/jsroot/issues/189} for more info * Can be enabled by adding 'wrong_http_response' parameter to URL when using JSROOT UI * @default false */ HandleWrongHttpResponse: false, /** @summary Tweak browser caching with stamp URL parameter * @desc When specified, extra URL parameter like ```?stamp=unique_value``` append to each files loaded - * In such case browser will be forced to load file content disregards of server cache settings + * In such case browser will be forced to load file content disregards of browser or server cache settings * Can be disabled by providing &usestamp=false in URL or via Settings/Files sub-menu - * @default true */ - UseStamp: true, + * Disabled by default on node.js, enabled in the web browsers */ + UseStamp: !nodejs, /** @summary Maximal number of bytes ranges in http 'Range' header - * @desc Some http server has limitations for number of bytes rannges therefore let change maximal number via setting + * @desc Some http server has limitations for number of bytes ranges therefore let change maximal number via setting * @default 200 */ MaxRanges: 200, - /** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */ + /** @summary Number of bytes requested once by TTree::Draw processing + * @desc TTree can be very large and data from baskets read by portion specified by this variable + * @default 1e6 */ + TreeReadBunchSize: 1e6, + /** @summary File read timeout in ms + * @desc Configures timeout for each http operation for reading ROOT files + * @default 0 */ + FilesTimeout: 0, + /** @summary Default remap object for files loading + * @desc Allows to retry files reading if original URL fails + * @private */ + FilesRemap: { 'https://root.cern/': 'https://root-eos.web.cern.ch/' }, + /** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */ WithCredentials: false, /** @summary Skip streamer infos from the GUI */ SkipStreamerInfos: false, @@ -291,6 +369,8 @@ settings = { AxisTiltAngle: 25, /** @summary Strip axis labels trailing 0 or replace 10^0 by 1 */ StripAxisLabels: true, + /** @summary If true exclude (cut off) axis labels which may exceed graphical range, also axis name can be specified */ + CutAxisLabels: false, /** @summary Draw TF1 by default as curve or line */ FuncAsCurve: false, /** @summary Time zone used for date/time display, local by default, can be 'UTC' or 'Europe/Berlin' or any other valid value */ @@ -300,12 +380,14 @@ settings = { /** @summary Extra parameters which will be append to the url when item shown in new tab */ NewTabUrlPars: '', /** @summary Export different settings in output URL */ - NewTabUrlExportSettings: false + NewTabUrlExportSettings: false, + /** @summary Enable more debug output, also via 'WebGui.Debug: yes' on ROOT side */ + Debug: false }, /** @namespace - * @summary Insiance of TStyle object like in ROOT - * @desc Includes default draw styles, can be changed after loading of JSRoot.core.js + * @summary Instance of TStyle object like in ROOT + * @desc Includes default draw styles, can be changed after loading of core.mjs * or can be load from the file providing style=itemname in the URL * See [TStyle docu]{@link https://root.cern/doc/master/classTStyle.html} 'Private attributes' section for more detailed info about each value */ gStyle = { @@ -316,9 +398,13 @@ gStyle = { fOptLogy: 0, /** @summary Default log z scale */ fOptLogz: 0, + /** @summary Show date on canvas */ fOptDate: 0, + /** @summary Show file name on canvas */ fOptFile: 0, + /** @summary X position of date on canvas */ fDateX: 0.01, + /** @summary Y position of date on canvas */ fDateY: 0.01, /** @summary Draw histogram title */ fOptTitle: 1, @@ -326,19 +412,29 @@ gStyle = { fCanvasColor: 0, /** @summary Pad fill color */ fPadColor: 0, + /** @summary Pad bottom margin */ fPadBottomMargin: 0.1, + /** @summary Pad top margin */ fPadTopMargin: 0.1, + /** @summary Pad left margin */ fPadLeftMargin: 0.1, + /** @summary Pad right margin */ fPadRightMargin: 0.1, /** @summary TPad.fGridx default value */ fPadGridX: false, /** @summary TPad.fGridy default value */ fPadGridY: false, + /** @summary TPad.fTickx default value */ fPadTickX: 0, + /** @summary TPad.fTicky default value */ fPadTickY: 0, + /** @summary Pad border size */ fPadBorderSize: 2, + /** @summary Pad border mode */ fPadBorderMode: 0, + /** @summary Canvas border size */ fCanvasBorderSize: 2, + /** @summary Canvas border mode */ fCanvasBorderMode: 0, /** @summary fill color for stat box */ fStatColor: 0, @@ -354,16 +450,27 @@ gStyle = { fStatBorderSize: 1, /** @summary Printing format for stats */ fStatFormat: '6.4g', + /** @summary Stat box X position - top right corner */ fStatX: 0.98, + /** @summary Stat box Y position - top right corner */ fStatY: 0.935, + /** @summary Stat box width */ fStatW: 0.2, + /** @summary Stat box height */ fStatH: 0.16, + /** @summary Title text align */ fTitleAlign: 23, + /** @summary Title fill color */ fTitleColor: 0, + /** @summary Title text color */ fTitleTextColor: 1, + /** @summary Title border size */ fTitleBorderSize: 0, + /** @summary Title text font */ fTitleFont: 42, + /** @summary Title font size */ fTitleFontSize: 0.05, + /** @summary Title fill style */ fTitleStyle: 0, /** @summary X position of top left corner of title box */ fTitleX: 0.5, @@ -375,18 +482,31 @@ gStyle = { fTitleH: 0, /** @summary Printing format for fit parameters */ fFitFormat: '5.4g', + /** @summary Default optstat value */ fOptStat: 1111, + /** @summary Default optfit value */ fOptFit: 0, + /** @summary Default number of colors in contour */ fNumberContours: 20, + /** @summary Grids color */ fGridColor: 0, + /** @summary Grids line style */ fGridStyle: 3, + /** @summary Grids line width */ fGridWidth: 1, + /** @summary Frame fill color */ fFrameFillColor: 0, + /** @summary Frame fill style */ fFrameFillStyle: 1001, + /** @summary Frame line color */ fFrameLineColor: 1, + /** @summary Frame line width */ fFrameLineWidth: 1, + /** @summary Frame line style */ fFrameLineStyle: 1, + /** @summary Frame border size */ fFrameBorderSize: 1, + /** @summary Frame border mode */ fFrameBorderMode: 0, /** @summary size in pixels of end error for E1 draw options */ fEndErrorSize: 2, @@ -396,25 +516,39 @@ gStyle = { fHistMinimumZero: false, /** @summary Margin between histogram's top and pad's top */ fHistTopMargin: 0.05, + /** @summary Histogram fill color */ fHistFillColor: 0, + /** @summary Histogram fill style */ fHistFillStyle: 1001, + /** @summary Histogram line color */ fHistLineColor: 602, + /** @summary Histogram line style */ fHistLineStyle: 1, + /** @summary Histogram line width */ fHistLineWidth: 1, /** @summary format for bin content */ fPaintTextFormat: 'g', /** @summary default time offset, UTC time at 01/01/95 */ fTimeOffset: 788918400, + /** @summary Legend border size */ fLegendBorderSize: 1, + /** @summary Legend text font */ fLegendFont: 42, + /** @summary Legend font size */ fLegendTextSize: 0, + /** @summary Legend fill color */ fLegendFillColor: 0, + /** @summary Legend fill style */ + fLegendFillStyle: 1001, + /** @summary Hatches line width in fill styles */ fHatchesLineWidth: 1, fHatchesSpacing: 1, fCandleWhiskerRange: 1.0, fCandleBoxRange: 0.5, fCandleScaled: false, fViolinScaled: true, + fCandleCircleLineWidth: 1, + fCandleCrossLineWidth: 1, fOrthoCamera: false, fXAxisExpXOffset: 0, fXAxisExpYOffset: 0, @@ -422,7 +556,10 @@ gStyle = { fYAxisExpYOffset: 0, fAxisMaxDigits: 5, fStripDecimals: true, - fBarWidth: 1 + /** @summary Width of bar for graphs */ + fBarWidth: 1, + /** @summary Offset of bar for graphs */ + fBarOffset: 0 }; /** @summary Method returns current document in use @@ -437,124 +574,10 @@ function getDocument() { return undefined; } -/** @summary Inject javascript code - * @desc Replacement for eval - * @return {Promise} when code is injected +/** @summary Ensure global JSROOT and v6 support methods * @private */ -async function injectCode(code) { - if (nodejs) { - let name, fs; - return import('tmp').then(tmp => { - name = tmp.tmpNameSync() + '.js'; - return import('fs'); - }).then(_fs => { - fs = _fs; - fs.writeFileSync(name, code); - return import(/* webpackIgnore: true */ 'file://' + name); - }).finally(() => fs.unlinkSync(name)); - } - - if (typeof document !== 'undefined') { - // check if code already loaded - to avoid duplication - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (scripts[n].innerHTML === code) - return true; - } - - const promise = code.indexOf('JSROOT.require') >= 0 ? _ensureJSROOT() : Promise.resolve(true); - - return promise.then(() => { - const element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.innerHTML = code; - document.head.appendChild(element); - return postponePromise(true, 10); // while onload event not fired, just postpone resolve - }); - } - - return false; -} - -/** @summary Load script or CSS file into the browser - * @param {String} url - script or css file URL (or array, in this case they all loaded sequentially) - * @return {Promise} */ -async function loadScript(url) { - if (!url) - return true; - - if (isStr(url) && (url.indexOf(';') >= 0)) - url = url.split(';'); - - if (!isStr(url)) { - const scripts = url, loadNext = () => { - if (!scripts.length) return true; - return loadScript(scripts.shift()).then(loadNext, loadNext); - }; - return loadNext(); - } - - if (url.indexOf('$$$') === 0) { - url = url.slice(3); - if ((url.indexOf('style/') === 0) && (url.indexOf('.css') < 0)) - url += '.css'; - url = source_dir + url; - } - - const isstyle = url.indexOf('.css') > 0; - - if (nodejs) { - if (isstyle) - return null; - if ((url.indexOf('http:') === 0) || (url.indexOf('https:') === 0)) - return httpRequest(url, 'text').then(code => injectCode(code)); - - // local files, read and use it - if (url.indexOf('./') === 0) - return import('fs').then(fs => injectCode(fs.readFileSync(url))); - - return import(/* webpackIgnore: true */ url); - } - - const match_url = src => { - if (src === url) return true; - const indx = src.indexOf(url); - return (indx > 0) && (indx + url.length === src.length) && (src[indx-1] === '/'); - }; - - if (isstyle) { - const styles = document.getElementsByTagName('link'); - for (let n = 0; n < styles.length; ++n) { - if (!styles[n].href || (styles[n].type !== 'text/css') || (styles[n].rel !== 'stylesheet')) continue; - if (match_url(styles[n].href)) - return true; - } - } else { - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (match_url(scripts[n].src)) - return true; - } - } +let _ensureJSROOT = null; - let element; - if (isstyle) { - element = document.createElement('link'); - element.setAttribute('rel', 'stylesheet'); - element.setAttribute('type', 'text/css'); - element.setAttribute('href', url); - } else { - element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.setAttribute('src', url); - } - - return new Promise((resolveFunc, rejectFunc) => { - element.onload = () => resolveFunc(true); - element.onerror = () => { element.remove(); rejectFunc(Error(`Fail to load ${url}`)); }; - document.head.appendChild(element); - }); -} /** @summary Generate mask for given bit * @param {number} n bit number @@ -566,13 +589,15 @@ function BIT(n) { return 1 << n; } * @return {object} cloned object * @private */ function clone(src, map, nofunc) { - if (!src) return null; + if (!src) + return null; if (!map) map = { obj: [], clones: [], nofunc }; else { const i = map.obj.indexOf(src); - if (i >= 0) return map.clones[i]; + if (i >= 0) + return map.clones[i]; } const arr_kind = isArrayProto(Object.prototype.toString.apply(src)); @@ -617,35 +642,39 @@ const extend = Object.assign; /** @summary Adds specific methods to the object. * @desc JSROOT implements some basic methods for different ROOT classes. + * @function * @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) { - extend(obj, getMethods(typename || obj._typename, obj)); -} +let addMethods = null; /** @summary Should be used to parse JSON string produced with TBufferJSON class * @desc Replace all references inside object like { "$ref": "1" } * @param {object|string} json object where references will be replaced * @return {object} parsed object */ function parse(json) { - if (!json) return null; + if (!json) + return null; const obj = isStr(json) ? JSON.parse(json) : json, map = []; let newfmt; const unref_value = value => { - if ((value === null) || (value === undefined)) return; + if ((value === null) || (value === undefined)) + return; if (isStr(value)) { - if (newfmt || (value.length < 6) || (value.indexOf('$ref:') !== 0)) return; + if (newfmt || (value.length < 6) || value.indexOf('$ref:')) + return; const ref = parseInt(value.slice(5)); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) return; + if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) + return; newfmt = false; return map[ref]; } - if (typeof value !== 'object') return; + if (typeof value !== 'object') + return; const proto = Object.prototype.toString.apply(value); @@ -653,7 +682,8 @@ function parse(json) { if (isArrayProto(proto) > 0) { for (let i = 0; i < value.length; ++i) { const res = unref_value(value[i]); - if (res !== undefined) value[i] = res; + if (res !== undefined) + value[i] = res; } return; } @@ -662,7 +692,8 @@ function parse(json) { if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { const ref = parseInt(value.$ref); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) return; + if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) + return; newfmt = true; return map[ref]; } @@ -691,8 +722,8 @@ function parse(json) { const buf = atob_func(value.b); if (arr.buffer) { const dv = new DataView(arr.buffer, value.o || 0), - len = Math.min(buf.length, dv.byteLength); - for (let k = 0; k < len; ++k) + blen = Math.min(buf.length, dv.byteLength); + for (let k = 0; k < blen; ++k) dv.setUint8(k, buf.charCodeAt(k)); } else throw new Error('base64 coding supported only for native arrays with binary data'); @@ -700,8 +731,10 @@ function parse(json) { // compressed coding let 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`); + 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`); const v = value[ks[nkey++]]; // value if (typeof v === 'object') { for (let k = 0; k < v.length; ++k) @@ -710,7 +743,8 @@ function parse(json) { arr[p++] = v; if ((nkey < len) && (ks[nkey][0] === 'n')) { let cnt = value[ks[nkey++]]; // counter - while (--cnt) arr[p++] = v; + while (--cnt) + arr[p++] = v; } } } @@ -723,25 +757,30 @@ function parse(json) { newfmt = true; const f1 = unref_value(value.first), s1 = unref_value(value.second); - if (f1 !== undefined) value.first = f1; - if (s1 !== undefined) value.second = s1; + 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 } // prevent endless loop - if (map.indexOf(value) >= 0) return; + if (map.indexOf(value) >= 0) + return; // add object to object map map.push(value); // add methods to all objects, where _typename is specified - if (value._typename) addMethods(value); + if (value._typename) + addMethods(value); for (let k = 0; k < len; ++k) { const i = ks[k], res = unref_value(value[i]); - if (res !== undefined) value[i] = res; + if (res !== undefined) + value[i] = res; } }; @@ -755,7 +794,8 @@ function parse(json) { * @param {string} json string to parse * @return {Array} array of parsed elements */ function parseMulti(json) { - if (!json) return null; + if (!json) + return null; const arr = JSON.parse(json); if (arr?.length) { for (let i = 0; i < arr.length; ++i) @@ -765,8 +805,8 @@ function parseMulti(json) { } /** @summary Method converts JavaScript object into ROOT-like JSON - * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html#a2ecf0daacdad801e60b8093a404c897d} method to read data back with C++ - * Or one can again parse json with {@link parse} function + * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html} + * method to read data back with C++. Or one can again parse json with {@link parse} function * @param {object} obj - JavaScript object to convert * @param {number} [spacing] - optional line spacing in JSON * @return {string} produced JSON code @@ -777,13 +817,16 @@ function parseMulti(json) { * obj.fTitle = 'New histogram title'; * let json = toJSON(obj); */ function toJSON(obj, spacing) { - if (!isObject(obj)) return ''; + if (!isObject(obj)) + return ''; const map = [], // map of stored objects copy_value = value => { - if (isFunc(value)) return undefined; + if (isFunc(value)) + return undefined; - if ((value === undefined) || (value === null) || !isObject(value)) return value; + if ((value === undefined) || (value === null) || !isObject(value)) + return value; // typed array need to be converted into normal array, otherwise looks strange if (isArrayProto(Object.prototype.toString.apply(value)) > 0) { @@ -795,7 +838,8 @@ function toJSON(obj, spacing) { // this is how reference is code const refid = map.indexOf(value); - if (refid >= 0) return { $ref: refid }; + if (refid >= 0) + return { $ref: refid }; const ks = Object.keys(value), len = ks.length, tgt = {}; @@ -837,27 +881,42 @@ function decodeUrl(url) { const res = { opts: {}, has(opt) { return this.opts[opt] !== undefined; }, - get(opt, dflt) { const v = this.opts[opt]; return v !== undefined ? v : dflt; } + get(opt, dflt) { return this.opts[opt] ?? dflt; } }; if (!url || !isStr(url)) { - if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) return res; + if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) + return res; url = document.URL; } res.url = url; const p1 = url.indexOf('?'); - if (p1 < 0) return res; - url = decodeURI(url.slice(p1+1)); + if (p1 < 0) + return res; + url = decodeURI(url.slice(p1 + 1)); while (url) { // try to correctly handle quotes in the URL let pos = 0, nq = 0, eq = -1, firstq = -1; - while ((pos < url.length) && ((nq !== 0) || ((url[pos] !== '&') && (url[pos] !== '#')))) { + while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) { switch (url[pos]) { - case '\'': if (nq >= 0) nq = (nq+1) % 2; if (firstq < 0) firstq = pos; break; - case '"': if (nq <= 0) nq = (nq-1) % 2; if (firstq < 0) firstq = pos; break; - case '=': if ((firstq < 0) && (eq < 0)) eq = pos; break; + case '\'': + if (nq >= 0) + nq = (nq + 1) % 2; + if (firstq < 0) + firstq = pos; + break; + case '"': + if (nq <= 0) + nq = (nq - 1) % 2; + if (firstq < 0) + firstq = pos; + break; + case '=': + if ((firstq < 0) && (eq < 0)) + eq = pos; + break; } pos++; } @@ -866,13 +925,15 @@ function decodeUrl(url) { res.opts[url.slice(0, pos)] = ''; else if (eq > 0) { let val = url.slice(eq + 1, pos); - if (((val[0] === '\'') || (val[0] === '"')) && (val[0] === val[val.length-1])) val = val.slice(1, val.length-1); + if (((val[0] === '\'') || (val[0] === '"')) && (val.at(0) === val.at(-1))) + val = val.slice(1, val.length - 1); res.opts[url.slice(0, eq)] = val; } - if ((pos >= url.length) || (url[pos] === '#')) break; + if ((pos >= url.length) || (url[pos] === '#')) + break; - url = url.slice(pos+1); + url = url.slice(pos + 1); } return res; @@ -881,8 +942,10 @@ function decodeUrl(url) { /** @summary Find function with given name * @private */ function findFunction(name) { - if (isFunc(name)) return name; - if (!isStr(name)) return null; + if (isFunc(name)) + return name; + if (!isStr(name)) + return null; const names = name.split('.'); let elem = globalThis; @@ -897,19 +960,36 @@ function findFunction(name) { function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise) { function configureXhr(xhr) { xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {}; - xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) { console.warn(err.message); this.http_callback(null); }.bind(xhr); + xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) { + console.warn(err.message); + this.http_callback(null); + }.bind(xhr); - if (!kind) kind = 'buf'; + if (!kind) + kind = 'buf'; let method = 'GET', is_async = true; const p = kind.indexOf(';sync'); - if (p > 0) { kind = kind.slice(0, p); is_async = false; } + if (p > 0) { + kind = kind.slice(0, p); + is_async = false; + } switch (kind) { - case 'head': method = 'HEAD'; break; - case 'posttext': method = 'POST'; kind = 'text'; break; - case 'postbuf': method = 'POST'; kind = 'buf'; break; + case 'head': + method = 'HEAD'; + break; + case 'posttext': + method = 'POST'; + kind = 'text'; + break; + case 'postbuf': + method = 'POST'; + kind = 'buf'; + break; case 'post': - case 'multi': method = 'POST'; break; + case 'multi': + method = 'POST'; + break; } xhr.kind = kind; @@ -928,26 +1008,28 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback } xhr.onreadystatechange = function() { - if (this.did_abort) return; + if (this.did_abort) + return; if ((this.readyState === 2) && this.expected_size) { const len = parseInt(this.getResponseHeader('Content-Length')); if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) { - this.did_abort = true; + this.did_abort = 'large'; this.abort(); return this.error_callback(Error(`Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`), 599); } } - if (this.readyState !== 4) return; + if (this.readyState !== 4) + return; - if ((this.status !== 200) && (this.status !== 206) && !browser.qt5 && + if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 && // in these special cases browsers not always set status !((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0)))) return this.error_callback(Error(`Fail to load url ${url}`), this.status); if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) { - // special handling of gzipped JSON objects in Node.js + // special handling of gzip JSON objects in Node.js return import('zlib').then(handle => { const res = handle.unzipSync(Buffer.from(this.response)), obj = JSON.parse(res); // zlib returns Buffer, use JSON to parse it @@ -995,7 +1077,6 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback if (isNodeJs()) { if (!use_promise) throw Error('Not allowed to create http requests in node.js without promise'); - // eslint-disable-next-line new-cap return import('xhr2').then(h => configureXhr(new h.default())); } @@ -1003,7 +1084,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback return use_promise ? Promise.resolve(xhr) : xhr; } -/** @summary Submit asynchronoues http request +/** @summary Submit asynchronous http request * @desc Following requests kind can be specified: * - 'bin' - abstract binary data, result as string * - 'buf' - abstract binary data, result as ArrayBuffer (default) @@ -1013,7 +1094,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback * - 'xml' - returns req.responseXML * - 'head' - returns request itself, uses 'HEAD' request method * - 'post' - creates post request, submits req.send(post_data) - * - 'postbuf' - creates post request, expectes binary data as response + * - 'postbuf' - creates post request, expects binary data as response * @param {string} url - URL for the request * @param {string} kind - kind of requested data * @param {string} [post_data] - data submitted with post kind of request @@ -1029,8 +1110,160 @@ async function httpRequest(url, kind, post_data) { }); } +/** @summary Inject javascript code + * @desc Replacement for eval + * @return {Promise} when code is injected + * @private */ +async function injectCode(code) { + if (nodejs) { + let name, fs; + return import('tmp').then(tmp => { + name = tmp.tmpNameSync() + '.js'; + return import('fs'); + }).then(_fs => { + fs = _fs; + fs.writeFileSync(name, code); + return import(/* webpackIgnore: true */ 'file://' + name); + }).finally(() => fs.unlinkSync(name)); + } + + if (typeof document !== 'undefined') { + // check if code already loaded - to avoid duplication + const scripts = document.getElementsByTagName('script'); + for (let n = 0; n < scripts.length; ++n) { + if (scripts[n].innerText === code) + return true; + } + + // try to detect if code includes import and must be treated as module + const is_v6 = code.indexOf('JSROOT.require') >= 0, + is_mjs = !is_v6 && (code.indexOf('import {') > 0) && (code.indexOf('} from \'') > 0), + is_batch = !is_v6 && !is_mjs && (code.indexOf('JSROOT.ObjectPainter') >= 0), + promise = (is_v6 ? _ensureJSROOT() : Promise.resolve(true)); + + if (is_batch && !globalThis.JSROOT) + globalThis.JSROOT = internals.jsroot; + + return promise.then(() => { + const element = document.createElement('script'); + element.setAttribute('type', is_mjs ? 'module' : 'text/javascript'); + element.innerText = code; + document.head.appendChild(element); + // while onload event not fired, just postpone resolve + return isBatchMode() ? true : postponePromise(true, 10); + }); + } + + return false; +} + +/** @summary Load ES6 modules + * @param {String} arg - single URL or array of URLs + * @return {Promise} */ +async function loadModules(arg) { + if (isStr(arg)) + arg = arg.split(';'); + if (!arg.length) + return true; + return import(/* webpackIgnore: true */ arg.shift()).then(() => loadModules(arg)); +} + +/** @summary Load script or CSS file into the browser + * @param {String} url - script or css file URL (or array, in this case they all loaded sequentially) + * @return {Promise} */ +async function loadScript(url) { + if (!url) + return true; + + if (isStr(url) && (url.indexOf(';') >= 0)) + url = url.split(';'); + + if (!isStr(url)) { + const scripts = url, loadNext = () => { + return !scripts.length ? true : loadScript(scripts.shift()).then(loadNext, loadNext); + }; + return loadNext(); + } + + if (url.indexOf('$$$') === 0) { + url = url.slice(3); + if ((url.indexOf('style/') === 0) && (url.indexOf('.css') < 0)) + url += '.css'; + url = source_dir + url; + } + + const isstyle = url.indexOf('.css') > 0; + + if (nodejs) { + if (isstyle) + return null; + if ((url.indexOf('http:') === 0) || (url.indexOf('https:') === 0)) + return httpRequest(url, 'text').then(code => injectCode(code)); + + // local files, read and use it + if (url.indexOf('./') === 0) + return import('fs').then(fs => injectCode(fs.readFileSync(url))); + + return import(/* webpackIgnore: true */ url); + } + + const match_url = src => { + if (src === url) + return true; + const indx = src.indexOf(url); + return (indx > 0) && (indx + url.length === src.length) && (src[indx - 1] === '/'); + }; + + if (isstyle) { + const styles = document.getElementsByTagName('link'); + for (let n = 0; n < styles.length; ++n) { + if (!styles[n].href || (styles[n].type !== 'text/css') || (styles[n].rel !== 'stylesheet')) + continue; + if (match_url(styles[n].href)) + return true; + } + } else { + const scripts = document.getElementsByTagName('script'); + for (let n = 0; n < scripts.length; ++n) { + if (match_url(scripts[n].src)) + return true; + } + } + + let element; + if (isstyle) { + element = document.createElement('link'); + element.setAttribute('rel', 'stylesheet'); + element.setAttribute('type', 'text/css'); + element.setAttribute('href', url); + } else { + element = document.createElement('script'); + element.setAttribute('type', 'text/javascript'); + element.setAttribute('src', url); + } + + return new Promise((resolveFunc, rejectFunc) => { + element.onload = () => resolveFunc(true); + element.onerror = () => { + element.remove(); + rejectFunc(Error(`Fail to load ${url}`)); + }; + document.head.appendChild(element); + }); +} + +_ensureJSROOT = async function() { + const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(source_dir + 'scripts/JSRoot.core.js'); + + return pr.then(() => { + if (globalThis.JSROOT?._complete_loading) + return globalThis.JSROOT._complete_loading(); + }).then(() => globalThis.JSROOT); +}; + + const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = 'TString', clTObjString = 'TObjString', - clTKey = 'TKey', clTFile = 'TFile', + clTKey = 'TKey', clTFile = 'TFile', clTTree = 'TTree', clTList = 'TList', clTHashList = 'THashList', clTMap = 'TMap', clTObjArray = 'TObjArray', clTClonesArray = 'TClonesArray', clTAttLine = 'TAttLine', clTAttFill = 'TAttFill', clTAttMarker = 'TAttMarker', clTAttText = 'TAttText', clTHStack = 'THStack', clTGraph = 'TGraph', clTMultiGraph = 'TMultiGraph', clTCutG = 'TCutG', @@ -1040,16 +1273,17 @@ const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = clTPaveLabel = 'TPaveLabel', clTPaveClass = 'TPaveClass', clTDiamond = 'TDiamond', clTLegend = 'TLegend', clTLegendEntry = 'TLegendEntry', clTPaletteAxis = 'TPaletteAxis', clTImagePalette = 'TImagePalette', - clTText = 'TText', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', - clTColor = 'TColor', clTLine = 'TLine', clTBox = 'TBox', clTPolyLine = 'TPolyLine', + clTText = 'TText', clTLink = 'TLink', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', + clTColor = 'TColor', clTLine = 'TLine', clTMarker = 'TMarker', clTBox = 'TBox', clTPolyLine = 'TPolyLine', clTPolyLine3D = 'TPolyLine3D', clTPolyMarker3D = 'TPolyMarker3D', clTAttPad = 'TAttPad', clTPad = 'TPad', clTCanvas = 'TCanvas', clTFrame = 'TFrame', clTAttCanvas = 'TAttCanvas', clTGaxis = 'TGaxis', clTAttAxis = 'TAttAxis', clTAxis = 'TAxis', clTStyle = 'TStyle', - clTH1 = 'TH1', clTH1I = 'TH1I', clTH1D = 'TH1D', clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH3 = 'TH3', - clTF1 = 'TF1', clTF2 = 'TF2', clTF3 = 'TF3', clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', + clTH1 = 'TH1', clTH1I = 'TH1I', clTH1F = 'TH1F', clTH1D = 'TH1D', clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH2D = 'TH2D', clTH3 = 'TH3', + clTF1 = 'TF1', clTF12 = 'TF12', clTF2 = 'TF2', clTF3 = 'TF3', clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', clTGeoVolume = 'TGeoVolume', clTGeoNode = 'TGeoNode', clTGeoNodeMatrix = 'TGeoNodeMatrix', - nsREX = 'ROOT::Experimental::', nsSVG = 'http://www.w3.org/2000/svg', - kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title'; + nsROOT = 'ROOT::', nsREX = nsROOT + 'Experimental::', nsSVG = 'http://www.w3.org/2000/svg', + kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title', + urlClassPrefix = 'https://root.cern/doc/master/class'; /** @summary Create some ROOT classes @@ -1074,6 +1308,9 @@ function create(typename, target) { case clTHashList: extend(obj, { name: typename, arr: [], opt: [] }); break; + case clTObjArray: + extend(obj, { name: typename, arr: [] }); + break; case clTAttAxis: extend(obj, { fNdivisions: 510, fAxisColor: 1, fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, @@ -1099,6 +1336,11 @@ function create(typename, target) { create(clTAttLine, obj); extend(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); break; + case clTMarker: + create(clTObject, obj); + create(clTAttMarker, obj); + extend(obj, { fX: 0, fY: 0 }); + break; case clTBox: create(clTObject, obj); create(clTAttLine, obj); @@ -1107,7 +1349,7 @@ function create(typename, target) { break; case clTPave: create(clTBox, obj); - extend(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, + extend(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 0, fY2NDC: 0, fBorderSize: 0, fInit: 1, fShadowColor: 1, fCornerRadius: 0, fOption: 'brNDC', fName: '' }); break; @@ -1124,13 +1366,14 @@ function create(typename, target) { extend(obj, { fFillColor: gStyle.fStatColor, fFillStyle: gStyle.fStatStyle, fTextFont: gStyle.fStatFont, fTextSize: gStyle.fStatFontSize, fTextColor: gStyle.fStatTextColor, fBorderSize: gStyle.fStatBorderSize, - fOptFit: 0, fOptStat: 0, fFitFormat: '', fStatFormat: '', fParent: null }); + fOptFit: gStyle.fOptFit, fOptStat: gStyle.fOptStat, fFitFormat: gStyle.fFitFormat, fStatFormat: gStyle.fStatFormat, fParent: null }); break; case clTLegend: create(clTPave, obj); create(clTAttText, obj); extend(obj, { fColumnSeparation: 0, fEntrySeparation: 0.1, fMargin: 0.25, fNColumns: 1, fPrimitives: create(clTList), fName: clTPave, - fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, fFillColor: gStyle.fLegendFillColor }); + fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, + fFillColor: gStyle.fLegendFillColor, fFillStyle: gStyle.fLegendFillStyle }); break; case clTPaletteAxis: create(clTPave, obj); @@ -1147,12 +1390,12 @@ function create(typename, target) { case clTText: create(clTNamed, obj); create(clTAttText, obj); - extend(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); + extend(obj, { fX: 0, fY: 0 }); break; case clTLatex: create(clTText, obj); create(clTAttLine, obj); - extend(obj, { fX: 0, fY: 0 }); + extend(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); break; case clTObjString: create(clTObject, obj); @@ -1233,9 +1476,9 @@ function create(typename, target) { create(clTNamed, obj); create(clTAttText, obj); create(clTAttLine, obj); - extend(obj, { fRadian: true, fDegree: false, fGrad: false, fPolarLabelColor: 1, fRadialLabelColor: 1, + extend(obj, { fRadian: false, 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, + 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 clTPolyLine: @@ -1302,7 +1545,7 @@ function create(typename, target) { fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, fBorderSize: gStyle.fCanvasBorderSize, fBorderMode: gStyle.fCanvasBorderMode, - fCw: 500, fCh: 300, fCatt: create(clTAttCanvas), + fCw: settings.CanvasWidth, fCh: settings.CanvasHeight, fCatt: create(clTAttCanvas), kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true }); break; @@ -1359,25 +1602,50 @@ function create(typename, target) { * h1.fXaxis.fLabelSize = 0.02; */ function createHistogram(typename, nbinsx, nbinsy, nbinsz) { const 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 }); + 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; + 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 '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': - case 'D': histo.fArray = new Float64Array(histo.fNcells); break; - default: histo.fArray = new Array(histo.fNcells); + case 'D': + histo.fArray = new Float64Array(histo.fNcells); + break; + default: + histo.fArray = new Array(histo.fNcells); } histo.fArray.fill(0); } @@ -1388,15 +1656,19 @@ function createHistogram(typename, nbinsx, nbinsy, nbinsz) { * @desc Title may include axes titles, provided with ';' symbol like "Title;x;y;z" */ function setHistogramTitle(histo, title) { - if (!histo) return; + if (!histo || !isStr(title)) + return; if (title.indexOf(';') < 0) histo.fTitle = title; else { const arr = title.split(';'); histo.fTitle = arr[0]; - if (arr.length > 1) histo.fXaxis.fTitle = arr[1]; - if (arr.length > 2) histo.fYaxis.fTitle = arr[2]; - if (arr.length > 3) histo.fZaxis.fTitle = arr[3]; + if (arr.length > 1) + histo.fXaxis.fTitle = arr[1]; + if (arr.length > 2) + histo.fYaxis.fTitle = arr[2]; + if (arr.length > 3) + histo.fZaxis.fTitle = arr[3]; } } @@ -1433,8 +1705,8 @@ function createTGraph(npoints, xpts, ypts) { usey = isObject(ypts) && (ypts.length === npoints); for (let i = 0; i < npoints; ++i) { - graph.fX.push(usex ? xpts[i] : i/npoints); - graph.fY.push(usey ? ypts[i] : i/npoints); + graph.fX.push(usex ? xpts[i] : i / npoints); + graph.fY.push(usey ? ypts[i] : i / npoints); } } @@ -1450,10 +1722,10 @@ function createTGraph(npoints, xpts, ypts) { * let h2 = createHistogram('TH1F', nbinsx); * let h3 = createHistogram('TH1F', nbinsx); * let stack = createTHStack(h1, h2, h3); */ -function createTHStack() { +function createTHStack(...args) { const stack = create(clTHStack); - for (let i = 0; i < arguments.length; ++i) - stack.fHists.Add(arguments[i], ''); + for (let i = 0; i < args.length; ++i) + stack.fHists.Add(args[i], ''); return stack; } @@ -1465,10 +1737,10 @@ function createTHStack() { * let gr2 = createTGraph(100); * let gr3 = createTGraph(100); * let mgr = createTMultiGraph(gr1, gr2, gr3); */ -function createTMultiGraph() { +function createTMultiGraph(...args) { const mgraph = create(clTMultiGraph); - for (let i = 0; i < arguments.length; ++i) - mgraph.fGraphs.Add(arguments[i], ''); + for (let i = 0; i < args.length; ++i) + mgraph.fGraphs.Add(args[i], ''); return mgraph; } @@ -1481,30 +1753,33 @@ const methodsCache = {}; function getMethods(typename, obj) { let m = methodsCache[typename]; const has_methods = (m !== undefined); - if (!has_methods) m = {}; + 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 === clTObject) || (typename === clTNamed) || (obj?.fBits !== undefined)) { if (typeof m.TestBit === 'undefined') { - m.TestBit = function(f) { return (this.fBits & f) !== 0; }; - m.InvertBit = function(f) { this.fBits = this.fBits ^ (f & 0xffffff); }; + m.TestBit = function(f) { return Boolean(this.fBits & f); }; + m.InvertBit = function(f) { this.fBits ^= f & 0xffffff; }; + m.SetBit = function(f, on = true) { this.fBits = on ? this.fBits | f : this.fBits & ~f; }; } } - if (has_methods) return m; + if (has_methods) + return m; if ((typename === clTList) || (typename === clTHashList)) { m.Clear = function() { this.arr = []; this.opt = []; }; - m.Add = function(obj, opt) { - this.arr.push(obj); + m.Add = function(elem, opt) { + this.arr.push(elem); this.opt.push(isStr(opt) ? opt : ''); }; - m.AddFirst = function(obj, opt) { - this.arr.unshift(obj); + m.AddFirst = function(elem, opt) { + this.arr.unshift(elem); this.opt.unshift(isStr(opt) ? opt : ''); }; m.RemoveAt = function(indx) { @@ -1528,11 +1803,13 @@ function getMethods(typename, obj) { }; } - if ((typename.indexOf(clTF1) === 0) || (typename === clTF2)) { - m.addFormula = function(obj) { - if (!obj) return; - if (this.formulas === undefined) this.formulas = []; - this.formulas.push(obj); + if ((typename.indexOf(clTF1) === 0) || (typename === clTF12) || (typename === clTF2) || (typename === clTF3)) { + m.addFormula = function(formula) { + if (formula) { + if (this.formulas === undefined) + this.formulas = []; + this.formulas.push(formula); + } }; m.GetParName = function(n) { if (this.fParams?.fParNames) @@ -1546,10 +1823,11 @@ function getMethods(typename, obj) { return (this.fNames && this.fNames[n]) ? this.fNames[n] : `p${n}`; }; m.GetParValue = function(n) { - if (this.fParams?.fParameters) return this.fParams.fParameters[n]; - if (this.fFormula?.fClingParameters) return this.fFormula.fClingParameters[n]; - if (this.fParams) return this.fParams[n]; - return undefined; + if (this.fParams?.fParameters) + return this.fParams.fParameters[n]; + if (this.fFormula?.fClingParameters) + return this.fFormula.fClingParameters[n]; + return this.fParams ? this.fParams[n] : undefined; }; m.GetParError = function(n) { return this.fParErrors ? this.fParErrors[n] : undefined; @@ -1567,7 +1845,7 @@ function getMethods(typename, obj) { for (; 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) + if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) oddNodes = !oddNodes; } j = i; @@ -1583,8 +1861,10 @@ function getMethods(typename, obj) { // 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.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])); @@ -1610,61 +1890,64 @@ function getMethods(typename, obj) { } if (typename.indexOf(clTH2) === 0) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins+2) * y); }; + 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) { const a1 = this.fXaxis, a2 = this.fYaxis, bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2)*bin2] += weight ?? 1; + this.fArray[bin1 + (a1.fNbins + 2) * bin2] += weight ?? 1; this.fEntries++; }; } if (typename.indexOf(clTH3) === 0) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins+2) * (y + (this.fYaxis.fNbins+2) * z)); }; + 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) { const a1 = this.fXaxis, a2 = this.fYaxis, a3 = this.fZaxis, bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))), bin3 = Math.max(0, 1 + Math.min(a3.fNbins, Math.floor((z - a3.fXmin) / (a3.fXmax - a3.fXmin) * a3.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2)*bin3)] += weight ?? 1; + this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2) * bin3)] += weight ?? 1; this.fEntries++; }; } if (typename === clTPad || typename === clTCanvas) { - m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01) { + m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01, color = 0) { if (!ny) { const ndiv = nx; - if (ndiv < 2) return this; + if (ndiv < 2) + return this; nx = ny = Math.round(Math.sqrt(ndiv)); - if (nx * ny < ndiv) nx += 1; + if (nx * ny < ndiv) + nx += 1; } - if (nx*ny < 2) + if (nx * ny < 2) return 0; this.fPrimitives.Clear(); - const dy = 1/ny, dx = 1/nx; + const dy = 1 / ny, dx = 1 / nx; let n = 0; for (let iy = 0; iy < ny; iy++) { - const y2 = 1 - iy*dy - ymargin; - let y1 = y2 - dy + 2*ymargin; - if (y1 < 0) y1 = 0; - if (y1 > y2) continue; + const y2 = 1 - iy * dy - ymargin, + y1 = Math.max(0, y2 - dy + 2 * ymargin); + if (y1 > y2) + continue; for (let ix = 0; ix < nx; ix++) { - const x1 = ix*dx + xmargin, - x2 = x1 + dx -2*xmargin; - if (x1 > x2) continue; + const x1 = ix * dx + xmargin, + x2 = x1 + dx - 2 * xmargin; + if (x1 > x2) + continue; n++; const pad = create(clTPad); pad.fName = pad.fTitle = `${this.fName}_${n}`; pad.fNumber = n; if (this._typename !== clTCanvas) { - pad.fAbsWNDC = (x2-x1) * this.fAbsWNDC; - pad.fAbsHNDC = (y2-y1) * this.fAbsHNDC; + pad.fAbsWNDC = (x2 - x1) * this.fAbsWNDC; + pad.fAbsHNDC = (y2 - y1) * this.fAbsHNDC; pad.fAbsXlowNDC = this.fAbsXlowNDC + x1 * this.fAbsWNDC; - pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsWNDC; + pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsHNDC; } else { pad.fAbsWNDC = x2 - x1; pad.fAbsHNDC = y2 - y1; @@ -1672,6 +1955,8 @@ function getMethods(typename, obj) { pad.fAbsYlowNDC = y1; } + pad.fFillColor = color || this.fFillColor; + this.fPrimitives.Add(pad); } } @@ -1684,41 +1969,43 @@ function getMethods(typename, obj) { if (typename.indexOf(clTProfile) === 0) { if (typename === clTProfile3D) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins+2) * (y + (this.fYaxis.fNbins+2) * z)); }; + m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; m.getBinContent = function(x, y, z) { const bin = this.getBin(x, y, z); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) return 0; - return this.fArray ? this.fArray[bin]/this.fBinEntries[bin] : 0; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; }; m.getBinEntries = function(x, y, z) { const bin = this.getBin(x, y, z); return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; }; } else if (typename === clTProfile2D) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins+2) * y); }; + m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; m.getBinContent = function(x, y) { const 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]; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; }; m.getBinEntries = function(x, y) { const bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells) return 0; - return this.fBinEntries[bin]; + return (bin < 0 || bin >= this.fNcells) ? 0 : 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]; + if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) + return 0; + return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; + }; + m.getBinEntries = function(bin) { + return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; }; } m.getBinEffectiveEntries = function(bin) { - if (bin < 0 || bin >= this.fNcells) return 0; + if (bin < 0 || bin >= this.fNcells) + return 0; const sumOfWeights = this.fBinEntries[bin]; if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) // this can happen when reading an old file @@ -1727,48 +2014,70 @@ function getMethods(typename, obj) { return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; }; m.getBinError = function(bin) { - if (bin < 0 || bin >= this.fNcells) return 0; + if (bin < 0 || bin >= this.fNcells) + return 0; const 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 + if (sum < 1e-300) + return 0; // for empty bins const 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); + return 1.0 / Math.sqrt(sum); // compute variance in y (eprim2) and standard deviation in y (eprim) - const contsum = cont/sum, eprim = Math.sqrt(Math.abs(err2/sum - contsum**2)); + const contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum ** 2)); if (this.fErrorMode === EErrorType.kERRORSPREADI) { - if (eprim !== 0) return eprim/Math.sqrt(neff); + if (eprim) + 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); + 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; + if (this.fErrorMode === EErrorType.kERRORSPREAD) + return eprim; // default case : fErrorMode = kERRORMEAN // return standard error on the mean of y - return eprim/Math.sqrt(neff); + return eprim / Math.sqrt(neff); }; } if (typename === clTAxis) { 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; + if (this.fNbins <= 0) + return 0; + if (this.fXbins.length) { + if ((bin > 0) && (bin <= this.fNbins + 1)) + return this.fXbins[bin - 1]; + if (bin === 0) // underflow + return 2 * this.fXbins[0] - this.fXbins[1]; + if (bin === this.fNbins + 2) // right border of overflow bin + return 2 * this.fXbins[bin - 2] - this.fXbins[bin - 3]; + return 0; + } + 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 (this.fNbins <= 0) + return 0; + if (this.fXbins.length) { + if ((bin > 0) && (bin <= this.fNbins)) + return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; + if (bin === 0) // underflow + return 1.5 * this.fXbins[0] - 0.5 * this.fXbins[1]; + if (bin === this.fNbins + 1) // overflow + return 1.5 * this.fXbins[bin - 1] - 0.5 * this.fXbins[bin - 2]; + return 0; + } + return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; }; } - if (typename.indexOf('ROOT::Math::LorentzVector') === 0) { + if (typename.indexOf(nsROOT + 'Math::LorentzVector') === 0) { m.Px = m.X = function() { return this.fCoordinates.Px(); }; m.Py = m.Y = function() { return this.fCoordinates.Py(); }; m.Pz = m.Z = function() { return this.fCoordinates.Pz(); }; @@ -1779,28 +2088,32 @@ function getMethods(typename, obj) { m.P2 = function() { return this.P() * this.P(); }; m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; m.Phi = m.phi = function() { return Math.atan2(this.fCoordinates.Py(), this.fCoordinates.Px()); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz()/this.P()); }; + m.Eta = m.eta = function() { return Math.atanh(this.Pz() / this.P()); }; } - if (typename.indexOf('ROOT::Math::PxPyPzE4D') === 0) { + if (typename.indexOf(nsROOT + 'Math::PxPyPzE4D') === 0) { m.Px = m.X = function() { return this.fX; }; m.Py = m.Y = function() { return this.fY; }; m.Pz = m.Z = function() { return this.fZ; }; m.E = m.T = function() { return this.fT; }; - m.P2 = function() { return this.fX**2 + this.fY**2 + this.fZ**2; }; + m.P2 = function() { return this.fX ** 2 + this.fY ** 2 + this.fZ ** 2; }; m.R = m.P = function() { return Math.sqrt(this.P2()); }; - m.Mag2 = m.M2 = function() { return this.fT**2 - this.fX**2 - this.fY**2 - this.fZ**2; }; + m.Mag2 = m.M2 = function() { return this.fT ** 2 - this.fX ** 2 - this.fY ** 2 - this.fZ ** 2; }; m.Mag = m.M = function() { return (this.M2() >= 0) ? Math.sqrt(this.M2()) : -Math.sqrt(-this.M2()); }; - m.Perp2 = m.Pt2 = function() { return this.fX**2 + this.fY**2; }; + m.Perp2 = m.Pt2 = function() { return this.fX ** 2 + this.fY ** 2; }; m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; m.Phi = m.phi = function() { return Math.atan2(this.fY, this.fX); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz/this.P()); }; + m.Eta = m.eta = function() { return Math.atanh(this.Pz / this.P()); }; } methodsCache[typename] = m; return m; } +addMethods = function(obj, typename) { + extend(obj, getMethods(typename || obj._typename, obj)); +}; + gStyle.fXaxis = create(clTAttAxis); gStyle.fYaxis = create(clTAttAxis); gStyle.fZaxis = create(clTAttAxis); @@ -1819,70 +2132,56 @@ function registerMethods(typename, m) { * @private */ function isRootCollection(lst, typename) { if (isObject(lst)) { - if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) return true; - if (!typename) typename = lst._typename; + if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) + return true; + if (!typename) + typename = lst._typename; } return (typename === clTList) || (typename === clTHashList) || (typename === clTMap) || (typename === clTObjArray) || (typename === clTClonesArray); } -/** @summary Check if argument is a not-null Object +/** @summary Return kind string for type + * @desc Used when internal objects without clear ROOT type are used + * For such objects abstract 'kind' is defined. Used in hpainter and draw handles * @private */ -function isObject(arg) { return arg && typeof arg === 'object'; } - -/** @summary Check if argument is a Function - * @private */ -function isFunc(arg) { return typeof arg === 'function'; } - -/** @summary Check if argument is a atring - * @private */ -function isStr(arg) { return typeof arg === 'string'; } - -/** @summary Check if object is a Promise - * @private */ -function isPromise(obj) { return isObject(obj) && isFunc(obj.then); } - -/** @summary Postpone func execution and return result in promise - * @private */ -function postponePromise(func, timeout) { - return new Promise(resolveFunc => { - setTimeout(() => { - const res = isFunc(func) ? func() : func; - resolveFunc(res); - }, timeout); - }); +function getKindForType(typ) { + return (!isStr(typ) || (typ.indexOf(nsROOT) !== 0)) ? prROOT + typ : typ; } -/** @summary Provide promise in any case +/** @summary Return type name from kind string * @private */ -function getPromise(obj) { return isPromise(obj) ? obj : Promise.resolve(obj); } +function getTypeForKind(kind) { + if (!isStr(kind)) + return null; + if (kind.indexOf(prROOT) === 0) + return kind.slice(prROOT.length); + if (kind.indexOf(nsROOT) === 0) + return kind; + return null; +} -/** @summary Ensure global JSROOT and v6 support methods +/** @summary Internal collection of functions potentially used by batch scripts * @private */ -async function _ensureJSROOT() { - const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(source_dir + 'scripts/JSRoot.core.js'); - - return pr.then(() => { - if (globalThis.JSROOT?._complete_loading) - return globalThis.JSROOT._complete_loading(); - }).then(() => globalThis.JSROOT); -} +internals.jsroot = { version, source_dir, settings, gStyle, parse, isBatchMode }; export { version_id, version_date, version, source_dir, isNodeJs, isBatchMode, setBatchMode, browser, internals, constants, settings, gStyle, atob_func, btoa_func, prROOT, clTObject, clTNamed, clTString, clTObjString, - clTKey, clTFile, + clTKey, clTFile, clTTree, clTList, clTHashList, clTMap, clTObjArray, clTClonesArray, clTAttLine, clTAttFill, clTAttMarker, clTAttText, clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, - clTLegend, clTLegendEntry, clTPaletteAxis, clTImagePalette, clTText, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, - clTColor, clTLine, clTBox, clTPolyLine, clTPad, clTCanvas, clTFrame, clTAttCanvas, clTGaxis, - clTAxis, clTStyle, clTH1, clTH1I, clTH1D, clTH2, clTH2I, clTH2F, clTH3, clTF1, clTF2, clTF3, + clTLegend, clTLegendEntry, clTPaletteAxis, clTImagePalette, clTText, clTLink, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, + clTColor, clTLine, clTMarker, clTBox, clTPolyLine, clTPad, clTCanvas, clTFrame, clTAttCanvas, clTGaxis, + clTAxis, clTStyle, clTH1, clTH1I, clTH1F, clTH1D, clTH2, clTH2I, clTH2F, clTH2D, clTH3, clTF1, clTF12, clTF2, clTF3, clTProfile, clTProfile2D, clTProfile3D, clTHStack, clTGraph, clTGraph2DErrors, clTGraph2DAsymmErrors, clTGraphPolar, clTGraphPolargram, clTGraphTime, clTCutG, - clTPolyLine3D, clTPolyMarker3D, clTGeoVolume, clTGeoNode, clTGeoNodeMatrix, nsREX, nsSVG, kNoZoom, kNoStats, kInspect, kTitle, + clTPolyLine3D, clTPolyMarker3D, clTGeoVolume, clTGeoNode, clTGeoNodeMatrix, + nsROOT, nsREX, nsSVG, kNoZoom, kNoStats, kInspect, kTitle, urlClassPrefix, isArrayProto, getDocument, BIT, clone, addMethods, parse, parseMulti, toJSON, - decodeUrl, findFunction, createHttpRequest, httpRequest, loadScript, injectCode, + decodeUrl, findFunction, createHttpRequest, httpRequest, loadModules, loadScript, injectCode, create, createHistogram, setHistogramTitle, createTPolyLine, createTGraph, createTHStack, createTMultiGraph, - getMethods, registerMethods, isRootCollection, isObject, isFunc, isStr, isPromise, getPromise, postponePromise, _ensureJSROOT }; + getMethods, registerMethods, isRootCollection, isObject, isFunc, isStr, isPromise, getPromise, postponePromise, + getKindForType, getTypeForKind, _ensureJSROOT }; diff --git a/modules/d3.mjs b/modules/d3.mjs index 4797d4c1a..f4c74eb09 100644 --- a/modules/d3.mjs +++ b/modules/d3.mjs @@ -531,10 +531,10 @@ define(Hcl, hcl, extend(Color, { })); var A = -0.14861, - B = +1.78277, + B = 1.78277, C = -0.29227, D = -0.90649, - E = +1.97294, + E = 1.97294, ED = E * D, EB = E * B, BC_DA = B * C - D * A; @@ -2418,26 +2418,18 @@ function number$2(x) { } function* numbers(values, valueof) { - if (valueof === undefined) { + { for (let value of values) { if (value != null && (value = +value) >= value) { yield value; } } - } else { - let index = -1; - for (let value of values) { - if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { - yield value; - } - } } } const ascendingBisect = bisector(ascending); const bisectRight = ascendingBisect.right; bisector(number$2).center; -var bisect = bisectRight; class InternMap extends Map { constructor(entries, key = keyof) { @@ -2556,42 +2548,26 @@ function tickStep(start, stop, count) { function max$1(values, valueof) { let max; - if (valueof === undefined) { + { for (const value of values) { if (value != null && (max < value || (max === undefined && value >= value))) { max = value; } } - } else { - let index = -1; - for (let value of values) { - if ((value = valueof(value, ++index, values)) != null - && (max < value || (max === undefined && value >= value))) { - max = value; - } - } } return max; } function min$1(values, valueof) { let min; - if (valueof === undefined) { + { for (const value of values) { if (value != null && (min > value || (min === undefined && value >= value))) { min = value; } } - } else { - let index = -1; - for (let value of values) { - if ((value = valueof(value, ++index, values)) != null - && (min > value || (min === undefined && value >= value))) { - min = value; - } - } } return min; } @@ -2649,7 +2625,7 @@ function swap(array, i, j) { } function quantile$1(values, p, valueof) { - values = Float64Array.from(numbers(values, valueof)); + values = Float64Array.from(numbers(values)); if (!(n = values.length) || isNaN(p = +p)) return; if (p <= 0 || n < 2) return min$1(values); if (p >= 1) return max$1(values); @@ -3221,7 +3197,7 @@ function polymap(domain, range, interpolate) { } return function(x) { - var i = bisect(domain, x, 1, j) - 1; + var i = bisectRight(domain, x, 1, j) - 1; return r[i](d[i](x)); }; } @@ -4049,7 +4025,7 @@ function quantile() { } function scale(x) { - return x == null || isNaN(x = +x) ? unknown : range[bisect(thresholds, x)]; + return x == null || isNaN(x = +x) ? unknown : range[bisectRight(thresholds, x)]; } scale.invertExtent = function(y) { @@ -4099,7 +4075,7 @@ function quantize() { unknown; function scale(x) { - return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; + return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; } function rescale() { @@ -4150,7 +4126,7 @@ function threshold() { n = 1; function scale(x) { - return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; + return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; } scale.domain = function(_) { @@ -4224,7 +4200,7 @@ function timeInterval(floori, offseti, count, field) { if (step < 0) while (++step <= 0) { while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty } else while (--step >= 0) { - while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty + while (offseti(date, 1), !test(date)) {} // eslint-disable-line no-empty } } }); @@ -5270,8 +5246,6 @@ var formatIso = Date.prototype.toISOString ? formatIsoNative : utcFormat(isoSpecifier); -var formatIso$1 = formatIso; - function parseIsoNative(string) { var date = new Date(string); return isNaN(date) ? null : date; @@ -5281,8 +5255,6 @@ var parseIso = +new Date("2000-01-01T00:00:00.000Z") ? parseIsoNative : utcParse(isoSpecifier); -var parseIso$1 = parseIso; - function date(t) { return new Date(t); } @@ -5458,7 +5430,7 @@ function sequentialQuantile() { interpolator = identity$2; function scale(x) { - if (x != null && !isNaN(x = +x)) return interpolator((bisect(domain, x, 1) - 1) / (domain.length - 1)); + if (x != null && !isNaN(x = +x)) return interpolator((bisectRight(domain, x, 1) - 1) / (domain.length - 1)); } scale.domain = function(_) { @@ -6905,4 +6877,4 @@ function active(node, name) { return null; } -export { active, arc, chord, chordDirected, chordTranspose, color, create$1 as create, creator, cubehelix, drag, nodrag as dragDisable, yesdrag as dragEnable, gray, hcl, hsl, interrupt, formatIso$1 as isoFormat, parseIso$1 as isoParse, lab, lch, local, matcher, namespace, namespaces, pointer, pointers, rgb, ribbon$1 as ribbon, ribbonArrow, band as scaleBand, diverging as scaleDiverging, divergingLog as scaleDivergingLog, divergingPow as scaleDivergingPow, divergingSqrt as scaleDivergingSqrt, divergingSymlog as scaleDivergingSymlog, identity as scaleIdentity, implicit as scaleImplicit, linear as scaleLinear, log as scaleLog, ordinal as scaleOrdinal, point as scalePoint, pow as scalePow, quantile as scaleQuantile, quantize as scaleQuantize, radial as scaleRadial, sequential as scaleSequential, sequentialLog as scaleSequentialLog, sequentialPow as scaleSequentialPow, sequentialQuantile as scaleSequentialQuantile, sequentialSqrt as scaleSequentialSqrt, sequentialSymlog as scaleSequentialSymlog, sqrt$1 as scaleSqrt, symlog as scaleSymlog, threshold as scaleThreshold, time as scaleTime, utcTime as scaleUtc, select, selectAll, selection, selector, selectorAll, styleValue as style, tickFormat, timeFormat, defaultLocale as timeFormatDefaultLocale, formatLocale as timeFormatLocale, timeParse, transition, utcFormat, utcParse, version, defaultView as window }; +export { active, arc, chord, chordDirected, chordTranspose, color, create$1 as create, creator, cubehelix, drag, nodrag as dragDisable, yesdrag as dragEnable, gray, hcl, hsl, interrupt, formatIso as isoFormat, parseIso as isoParse, lab, lch, local, matcher, namespace, namespaces, pointer, pointers, rgb, ribbon$1 as ribbon, ribbonArrow, band as scaleBand, diverging as scaleDiverging, divergingLog as scaleDivergingLog, divergingPow as scaleDivergingPow, divergingSqrt as scaleDivergingSqrt, divergingSymlog as scaleDivergingSymlog, identity as scaleIdentity, implicit as scaleImplicit, linear as scaleLinear, log as scaleLog, ordinal as scaleOrdinal, point as scalePoint, pow as scalePow, quantile as scaleQuantile, quantize as scaleQuantize, radial as scaleRadial, sequential as scaleSequential, sequentialLog as scaleSequentialLog, sequentialPow as scaleSequentialPow, sequentialQuantile as scaleSequentialQuantile, sequentialSqrt as scaleSequentialSqrt, sequentialSymlog as scaleSequentialSymlog, sqrt$1 as scaleSqrt, symlog as scaleSymlog, threshold as scaleThreshold, time as scaleTime, utcTime as scaleUtc, select, selectAll, selection, selector, selectorAll, styleValue as style, tickFormat, timeFormat, defaultLocale as timeFormatDefaultLocale, formatLocale as timeFormatLocale, timeParse, transition, utcFormat, utcParse, version, defaultView as window }; diff --git a/modules/draw.mjs b/modules/draw.mjs index 01179b797..9f763bd66 100644 --- a/modules/draw.mjs +++ b/modules/draw.mjs @@ -1,11 +1,11 @@ import { select as d3_select } from './d3.mjs'; -import { loadScript, findFunction, internals, getPromise, isNodeJs, isObject, isFunc, isStr, _ensureJSROOT, - prROOT, +import { loadScript, loadModules, findFunction, internals, settings, getPromise, isNodeJs, isObject, isFunc, isStr, + _ensureJSROOT, getKindForType, getTypeForKind, clTObject, clTNamed, clTString, clTAttLine, clTAttFill, clTAttMarker, clTAttText, clTObjString, clTFile, clTList, clTHashList, clTMap, clTObjArray, clTClonesArray, clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, clTLegend, clTPaletteAxis, - clTText, clTLine, clTBox, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, clTH2, clTF1, clTF2, clTF3, clTH3, - clTProfile, clTProfile2D, clTProfile3D, clTFrame, + clTText, clTLink, clTLine, clTMarker, clTBox, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, clTH2, clTF1, clTF12, clTF2, clTF3, clTH3, + clTProfile, clTProfile2D, clTProfile3D, clTFrame, clTTree, clTColor, clTHStack, clTGraph, clTGraph2DErrors, clTGraph2DAsymmErrors, clTGraphPolar, clTGraphPolargram, clTGraphTime, clTCutG, clTPolyLine, clTPolyLine3D, clTPolyMarker3D, clTPad, clTStyle, clTCanvas, clTGaxis, clTGeoVolume, kInspect, nsREX, nsSVG, atob_func } from './core.mjs'; @@ -14,6 +14,7 @@ import { clTBranchFunc } from './tree.mjs'; import { BasePainter, compressSVG, svgToImage, _loadJSDOM } from './base/BasePainter.mjs'; import { ObjectPainter, cleanup, drawRawText, getElementCanvPainter, getElementMainPainter } from './base/ObjectPainter.mjs'; import { TPadPainter, clTButton } from './gpad/TPadPainter.mjs'; +import { makePDF } from './base/makepdf.mjs'; async function import_more() { return import('./draw/more.mjs'); } @@ -24,25 +25,21 @@ async function import_tree() { return import('./draw/TTree.mjs'); } async function import_h() { return import('./gui/HierarchyPainter.mjs'); } -async function import_geo() { - return import('./geom/TGeoPainter.mjs').then(geo => { - const handle = getDrawHandle(prROOT + 'TGeoVolumeAssembly'); - if (handle) handle.icon = 'img_geoassembly'; - return geo; - }); -} +let import_v7 = null, import_geo = null; const clTGraph2D = 'TGraph2D', clTH2Poly = 'TH2Poly', clTEllipse = 'TEllipse', - clTSpline3 = 'TSpline3', clTTree = 'TTree', clTCanvasWebSnapshot = 'TCanvasWebSnapshot', - fPrimitives = 'fPrimitives', fFunctions = 'fFunctions', + clTSpline3 = 'TSpline3', clTCanvasWebSnapshot = 'TCanvasWebSnapshot', + fPrimitives = 'fPrimitives', fFunctions = 'fFunctions'; /** @summary list of registered draw functions * @private */ -drawFuncs = { lst: [ +/* eslint-disable-next-line one-var */ +const drawFuncs = { lst: [ { name: clTCanvas, icon: 'img_canvas', class: () => import_canvas().then(h => h.TCanvasPainter), opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, { name: clTPad, icon: 'img_canvas', func: TPadPainter.draw, opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, { name: 'TSlider', icon: 'img_canvas', func: TPadPainter.draw }, { name: clTButton, icon: 'img_canvas', func: TPadPainter.draw }, + { name: 'TInspectCanvas', icon: 'img_canvas', sameas: clTCanvas }, { name: clTFrame, icon: 'img_frame', draw: () => import_canvas().then(h => h.drawTFrame) }, { name: clTPave, icon: 'img_pavetext', class: () => import('./hist/TPavePainter.mjs').then(h => h.TPavePainter) }, { name: clTPaveText, sameas: clTPave }, @@ -53,20 +50,21 @@ drawFuncs = { lst: [ { name: clTDiamond, sameas: clTPave }, { name: clTLegend, icon: 'img_pavelabel', sameas: clTPave }, { name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave }, - { name: clTLatex, icon: 'img_text', draw: () => import_more().then(h => h.drawText), direct: true }, - { name: clTMathText, sameas: clTLatex }, - { name: clTText, sameas: clTLatex }, - { name: clTAnnotation, sameas: clTLatex }, + { name: clTText, icon: 'img_text', class: () => import('./draw/TTextPainter.mjs').then(h => h.TTextPainter), build3d: () => import('./base/latex3d.mjs').then(h => h.build3dlatex) }, + { name: clTMathText, sameas: clTText }, + { name: clTLatex, sameas: clTText }, + { name: clTLink, sameas: clTText }, + { name: clTAnnotation, icon: 'img_text', class: () => import('./draw/TAnnotation3DPainter.mjs').then(h => h.TAnnotation3DPainter) }, { name: /^TH1/, icon: 'img_histo1d', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;C;B;B1;A;TEXT;LEGO;same', ctrl: 'l', expand_item: fFunctions, for_derived: true }, - { name: clTProfile, icon: 'img_profile', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist', expand_item: fFunctions }, + { name: clTProfile, icon: 'img_profile', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist;projx;projxb;projxc=e;projxw', expand_item: fFunctions }, { name: clTH2Poly, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), opt: ';COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same', expand_item: 'fBins', theonly: true }, { name: 'TProfile2Poly', sameas: clTH2Poly }, { name: 'TH2PolyBin', icon: 'img_histo2d', draw_field: 'fPoly', draw_field_opt: 'L' }, - { name: /^TH2/, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), dflt: 'col', opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true }, - { name: clTProfile2D, sameas: clTH2 }, + { name: /^TH2/, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;COL_POL;COL_ARR;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;PROJXY1;PROJXY2;PROJXY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;CHORD;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true }, + { name: clTProfile2D, sameas: clTH2, opt2: ';projxyb;projxyc=e;projxyw' }, { name: /^TH3/, icon: 'img_histo3d', class: () => import('./hist/TH3Painter.mjs').then(h => h.TH3Painter), opt: ';SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL', expand_item: fFunctions, for_derived: true }, { name: clTProfile3D, sameas: clTH3 }, - { name: clTHStack, icon: 'img_histo1d', class: () => import('./hist/THStackPainter.mjs').then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;E;PFC;PLC' }, + { name: clTHStack, icon: 'img_histo1d', class: () => import('./hist/THStackPainter.mjs').then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;COL;LEGO;E;PFC;PLC;PADS' }, { name: clTPolyMarker3D, icon: 'img_histo3d', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyMarker3D), direct: true, frame: '3d' }, { name: clTPolyLine3D, icon: 'img_graph', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyLine3D), direct: true, frame: '3d' }, { name: 'TGraphStruct' }, @@ -79,14 +77,15 @@ drawFuncs = { lst: [ { name: clTGraphPolargram, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolargramPainter), theonly: true }, { name: clTGraphPolar, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolarPainter), opt: ';F;L;P;PE', theonly: true }, { name: /^TGraph/, icon: 'img_graph', class: () => import('./hist2d/TGraphPainter.mjs').then(h => h.TGraphPainter), opt: ';L;P' }, - { name: 'TEfficiency', icon: 'img_graph', class: () => import('./hist/TEfficiencyPainter.mjs').then(h => h.TEfficiencyPainter), opt: ';AP' }, + { name: 'TEfficiency', icon: 'img_graph', class: () => import('./hist/TEfficiencyPainter.mjs').then(h => h.TEfficiencyPainter), opt: ';AP;A4P;B' }, { name: clTCutG, sameas: clTGraph }, { name: /^RooHist/, sameas: clTGraph }, { name: /^RooCurve/, sameas: clTGraph }, + { name: /^RooEllipse/, sameas: clTGraph }, { name: 'TScatter', icon: 'img_graph', class: () => import('./hist2d/TScatterPainter.mjs').then(h => h.TScatterPainter), opt: ';A' }, - { name: 'RooPlot', icon: 'img_canvas', func: drawRooPlot }, + { name: 'RooPlot', icon: 'img_canvas', draw: () => import('./hist/TGraphTimePainter.mjs').then(h => h.drawRooPlot) }, { name: 'TRatioPlot', icon: 'img_mgraph', class: () => import('./draw/TRatioPlotPainter.mjs').then(h => h.TRatioPlotPainter), opt: '' }, - { name: clTMultiGraph, icon: 'img_mgraph', class: () => import('./hist/TMultiGraphPainter.mjs').then(h => h.TMultiGraphPainter), opt: ';l;p;3d', expand_item: 'fGraphs' }, + { name: clTMultiGraph, icon: 'img_mgraph', class: () => import('./hist/TMultiGraphPainter.mjs').then(h => h.TMultiGraphPainter), opt: ';ac;l;p;3d;pads', expand_item: 'fGraphs' }, { name: clTStreamerInfoList, icon: 'img_question', draw: () => import_h().then(h => h.drawStreamerInfo) }, { name: 'TWebPainting', icon: 'img_graph', class: () => import('./draw/TWebPaintingPainter.mjs').then(h => h.TWebPaintingPainter) }, { name: clTCanvasWebSnapshot, icon: 'img_canvas', draw: () => import_canvas().then(h => h.drawTPadSnapshot) }, @@ -94,6 +93,7 @@ drawFuncs = { lst: [ { name: 'kind:Text', icon: 'img_text', func: drawRawText }, { name: clTObjString, icon: 'img_text', func: drawRawText }, { name: clTF1, icon: 'img_tf1', class: () => import('./hist/TF1Painter.mjs').then(h => h.TF1Painter), opt: ';L;C;FC;FL' }, + { name: clTF12, sameas: clTF1 }, { name: clTF2, icon: 'img_tf2', class: () => import('./hist/TF2Painter.mjs').then(h => h.TF2Painter), opt: ';BOX;ARR;SURF;SURF1;SURF2;SURF4;SURF6;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same' }, { name: clTF3, icon: 'img_histo3d', class: () => import('./hist/TF3Painter.mjs').then(h => h.TF3Painter), opt: ';SURF' }, { name: clTSpline3, icon: 'img_tf1', class: () => import('./draw/TSplinePainter.mjs').then(h => h.TSplinePainter) }, @@ -101,28 +101,28 @@ drawFuncs = { lst: [ { name: clTEllipse, icon: 'img_graph', draw: () => import_more().then(h => h.drawEllipse), direct: true }, { name: 'TArc', sameas: clTEllipse }, { name: 'TCrown', sameas: clTEllipse }, - { name: 'TPie', icon: 'img_graph', draw: () => import_more().then(h => h.drawPie), direct: true }, + { name: 'TPie', icon: 'img_graph', class: () => import('./draw/TPiePainter.mjs').then(h => h.TPiePainter), opt: ';3D' }, { name: 'TPieSlice', icon: 'img_graph', dummy: true }, { name: 'TExec', icon: 'img_graph', dummy: true }, { name: clTLine, icon: 'img_graph', class: () => import('./draw/TLinePainter.mjs').then(h => h.TLinePainter) }, { name: 'TArrow', icon: 'img_graph', class: () => import('./draw/TArrowPainter.mjs').then(h => h.TArrowPainter) }, - { name: clTPolyLine, icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyLine), direct: true }, + { name: clTPolyLine, icon: 'img_graph', class: () => import('./draw/TPolyLinePainter.mjs').then(h => h.TPolyLinePainter), opt: ';F' }, { name: 'TCurlyLine', sameas: clTPolyLine }, { name: 'TCurlyArc', sameas: clTPolyLine }, { name: 'TParallelCoord', icon: 'img_graph', dummy: true }, { name: clTGaxis, icon: 'img_graph', class: () => import('./draw/TGaxisPainter.mjs').then(h => h.TGaxisPainter) }, - { name: clTBox, icon: 'img_graph', draw: () => import_more().then(h => h.drawBox), direct: true }, + { name: clTBox, icon: 'img_graph', class: () => import('./draw/TBoxPainter.mjs').then(h => h.TBoxPainter), opt: ';L' }, { name: 'TWbox', sameas: clTBox }, { name: 'TSliderBox', sameas: clTBox }, - { name: 'TMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true }, + { name: clTMarker, icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true }, { name: 'TPolyMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyMarker), direct: true }, { name: 'TASImage', icon: 'img_mgraph', class: () => import('./draw/TASImagePainter.mjs').then(h => h.TASImagePainter), opt: ';z' }, { name: 'TJSImage', icon: 'img_mgraph', draw: () => import_more().then(h => h.drawJSImage), opt: ';scale;center' }, { name: clTGeoVolume, icon: 'img_histo3d', class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;count;projx;projz;wire;no_screen;dflt', ctrl: 'dflt' }, { name: 'TEveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, - { name: nsREX+'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, + { name: nsREX + 'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, { name: 'TGeoOverlap', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt', dflt: 'dflt', ctrl: 'expand' }, - { name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', ctrl: 'dflt', noappend: true, exapnd_after_draw: true }, + { name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', pm: true, ctrl: 'dflt', noappend: true, expand_after_draw: true }, { name: 'TGeoVolumeAssembly', sameas: clTGeoVolume, /* icon: 'img_geoassembly', */ opt: ';more;all;count' }, { name: /^TGeo/, class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;axis;compa;count;projx;projz;wire;no_screen;dflt', dflt: 'dflt', ctrl: 'expand' }, { name: 'TAxis3D', icon: 'img_graph', draw: () => import_geo().then(h => h.drawAxis3D), direct: true }, @@ -130,60 +130,67 @@ drawFuncs = { lst: [ { name: 'kind:Command', icon: 'img_execute', execute: true }, { name: 'TFolder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true, get_expand: () => import_h().then(h => h.folderHierarchy) }, { name: 'TTask', icon: 'img_task', get_expand: () => import_h().then(h => h.taskHierarchy), for_derived: true }, - { name: clTTree, icon: 'img_tree', get_expand: () => import('./tree.mjs').then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect }, + { name: clTTree, icon: 'img_tree', get_expand: () => import('./tree.mjs').then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect, pm: true, transform: true }, { name: 'TNtuple', sameas: clTTree }, { name: 'TNtupleD', sameas: clTTree }, - { name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump', noinspect: true }, - { name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true }, - { name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump', ctrl: 'dump', ignore_online: true, always_draw: true }, + { name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump', noinspect: true, transform: true }, + { name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, + { name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump', ctrl: 'dump', ignore_online: true, always_draw: true, transform: true }, + { name: 'ROOT::RNTuple', icon: 'img_tree', get_expand: () => import('./rntuple.mjs').then(h => h.tupleHierarchy), draw: () => import('./draw/RNTuple.mjs').then(h => h.drawRNTuple), dflt: 'expand', pm: true, transform: true }, + { name: 'ROOT::RNTupleField', icon: 'img_leaf', draw: () => import('./draw/RNTuple.mjs').then(h => h.drawRNTuple), opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, { name: clTList, icon: 'img_list', draw: () => import_h().then(h => h.drawList), get_expand: () => import_h().then(h => h.listHierarchy), dflt: 'expand' }, { name: clTHashList, sameas: clTList }, { name: clTObjArray, sameas: clTList }, { name: clTClonesArray, sameas: clTList }, { name: clTMap, sameas: clTList }, { name: clTColor, icon: 'img_color' }, - { name: clTFile, icon: 'img_file', noinspect: true }, - { name: 'TMemFile', icon: 'img_file', noinspect: true }, + { name: clTFile, icon: 'img_file', noinspect: true, pm: true }, + { name: 'TMemFile', icon: 'img_file', noinspect: true, pm: true }, { name: clTStyle, icon: 'img_question', noexpand: true }, { name: 'Session', icon: 'img_globe' }, { name: 'kind:TopFolder', icon: 'img_base' }, { name: 'kind:Folder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true }, - { name: nsREX+'RCanvas', icon: 'img_canvas', class: () => init_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, - { name: nsREX+'RCanvasDisplayItem', icon: 'img_canvas', draw: () => init_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, - { name: nsREX+'RHist1Drawable', icon: 'img_histo1d', class: () => init_v7('rh1').then(h => h.RH1Painter), opt: '' }, - { name: nsREX+'RHist2Drawable', icon: 'img_histo2d', class: () => init_v7('rh2').then(h => h.RH2Painter), opt: '' }, - { name: nsREX+'RHist3Drawable', icon: 'img_histo3d', class: () => init_v7('rh3').then(h => h.RH3Painter), opt: '' }, - { name: nsREX+'RHistDisplayItem', icon: 'img_histo1d', draw: () => init_v7('rh3').then(h => h.drawHistDisplayItem), opt: '' }, - { name: nsREX+'RText', icon: 'img_text', draw: () => init_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, - { name: nsREX+'RFrameTitle', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, - { name: nsREX+'RPaletteDrawable', icon: 'img_text', class: () => init_v7('more').then(h => h.RPalettePainter), opt: '' }, - { name: nsREX+'RDisplayHistStat', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RHistStatsPainter), opt: '' }, - { name: nsREX+'RLine', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, - { name: nsREX+'RBox', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, - { name: nsREX+'RMarker', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, - { name: nsREX+'RPave', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPavePainter), opt: '' }, - { name: nsREX+'RLegend', icon: 'img_graph', class: () => init_v7('pave').then(h => h.RLegendPainter), opt: '' }, - { name: nsREX+'RPaveText', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, - { name: nsREX+'RFrame', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRFrame), opt: '' }, - { name: nsREX+'RFont', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, - { name: nsREX+'RAxisDrawable', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRAxis), opt: '' } + { name: nsREX + 'RCanvas', icon: 'img_canvas', class: () => import_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, + { name: nsREX + 'RCanvasDisplayItem', icon: 'img_canvas', draw: () => import_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, + { name: nsREX + 'RText', icon: 'img_text', draw: () => import_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, + { name: nsREX + 'RFrameTitle', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, + { name: nsREX + 'RPaletteDrawable', icon: 'img_text', class: () => import_v7('more').then(h => h.RPalettePainter), opt: '' }, + { name: nsREX + 'RLine', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, + { name: nsREX + 'RBox', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, + { name: nsREX + 'RMarker', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, + { name: nsREX + 'RPave', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPavePainter), opt: '' }, + { name: nsREX + 'RLegend', icon: 'img_graph', class: () => import_v7('pave').then(h => h.RLegendPainter), opt: '' }, + { name: nsREX + 'RPaveText', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, + { name: nsREX + 'RFrame', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRFrame), opt: '' }, + { name: nsREX + 'RFont', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, + { name: nsREX + 'RAxisDrawable', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRAxis), opt: '' }, + { name: nsREX + 'RTreeMapPainter', class: () => import('./draw/RTreeMapPainter.mjs').then(h => h.RTreeMapPainter), opt: '' } ], cache: {} }; /** @summary Register draw function for the class - * @desc List of supported draw options could be provided, separated with ';' * @param {object} args - arguments - * @param {string|regexp} args.name - class name or regexp pattern + * @param {string|regexp} args.name - class name or regexp pattern or '*' * @param {function} [args.func] - draw function + * @param {string} [args.sameas] - let behave same as specified class * @param {function} [args.draw] - async function to load draw function * @param {function} [args.class] - async function to load painter class with static draw function * @param {boolean} [args.direct] - if true, function is just Redraw() method of ObjectPainter * @param {string} [args.opt] - list of supported draw options (separated with semicolon) like 'col;scat;' * @param {string} [args.icon] - icon name shown for the class in hierarchy browser * @param {string} [args.draw_field] - draw only data member from object, like fHistogram + * @param {string} [args.noinspect] - disable inspect + * @param {string} [args.noexpand] - disable expand + * @param {string} [args.pm] - always show plus or minus sign even when no child items exists + * @desc List of supported draw options could be provided, separated with ';' + * If args.name parameter is '*', function will be invoked before object drawing. + * If such function does not return value - normal drawing will be continued. * @protected */ function addDrawFunc(args) { - drawFuncs.lst.push(args); + if (args?.name === '*') + internals._alt_draw = isFunc(args.func) ? args.func : null; + else + drawFuncs.lst.push(args); return args; } @@ -194,25 +201,28 @@ function addDrawFunc(args) { * or just sequence id * @private */ function getDrawHandle(kind, selector) { - if (!isStr(kind)) return null; - if (selector === '') selector = null; + if (!isStr(kind)) + return null; + if (selector === '') + selector = null; let first = null; if ((selector === null) && (kind in drawFuncs.cache)) return drawFuncs.cache[kind]; - const search = (kind.indexOf(prROOT) === 0) ? kind.slice(5) : `kind:${kind}`; + const search = getTypeForKind(kind) || `kind:${kind}`; let counter = 0; for (let i = 0; i < drawFuncs.lst.length; ++i) { const h = drawFuncs.lst[i]; if (isStr(h.name)) { - if (h.name !== search) continue; + if (h.name !== search) + continue; } else if (!search.match(h.name)) continue; if (h.sameas) { - const hs = getDrawHandle(prROOT + h.sameas, selector); + const hs = getDrawHandle(getKindForType(h.sameas), selector); if (hs) { for (const key in hs) { if (h[key] === undefined) @@ -225,19 +235,23 @@ function getDrawHandle(kind, selector) { if ((selector === null) || (selector === undefined)) { // store found handle in cache, can reuse later - if (!(kind in drawFuncs.cache)) drawFuncs.cache[kind] = h; + if (!(kind in drawFuncs.cache)) + drawFuncs.cache[kind] = h; return h; } else if (isStr(selector)) { - if (!first) first = h; - // if drawoption specified, check it present in the list + if (!first) + first = h; + // if draw option specified, check it present in the list if (selector === '::expand') { - if (('expand' in h) || ('expand_item' in h)) return h; + if (('expand' in h) || ('expand_item' in h)) + return h; } else if ('opt' in h) { const opts = h.opt.split(';'); - for (let j = 0; j < opts.length; ++j) - opts[j] = opts[j].toLowerCase(); - if (opts.indexOf(selector.toLowerCase()) >= 0) return h; + for (let j = 0; j < opts.length; ++j) { + if (opts[j].toLowerCase() === selector.toLowerCase()) + return h; + } } } else if (selector === counter) return h; @@ -252,55 +266,75 @@ function getDrawHandle(kind, selector) { function canDrawHandle(h) { if (isStr(h)) h = getDrawHandle(h); - if (!isObject(h)) return false; - return h.func || h.class || h.draw || h.draw_field; + if (!isObject(h)) + return false; + return (h.func || h.class || h.draw || h.draw_field || h.opt === 'inspect'); } /** @summary Provide draw settings for specified class or kind * @private */ function getDrawSettings(kind, selector) { const res = { opts: null, inspect: false, expand: false, draw: false, handle: null }; - if (!isStr(kind)) return res; + if (!isStr(kind)) + return res; let isany = false, noinspect = false, canexpand = false; - if (!isStr(selector)) selector = ''; + if (!isStr(selector)) + selector = ''; for (let cnt = 0; cnt < 1000; ++cnt) { const h = getDrawHandle(kind, cnt); - if (!h) break; - if (!res.handle) res.handle = h; - if (h.noinspect) noinspect = true; - if (h.noappend) res.noappend = true; - if (h.expand || h.get_expand || h.expand_item || h.can_expand) canexpand = true; - if (!h.func && !h.class && !h.draw) break; + if (!h) + break; + if (!res.handle) + res.handle = h; + if (h.noinspect) + noinspect = true; + if (h.noappend) + res.noappend = true; + if (h.expand || h.get_expand || h.expand_item || h.can_expand) + canexpand = true; + if (!h.func && !h.class && !h.draw) + break; isany = true; - if (!('opt' in h)) continue; - const opts = h.opt.split(';'); + if (h.opt === undefined) + continue; + let opt = h.opt; + if (isStr(h.opt2)) + opt += h.opt2; + const opts = opt.split(';'); for (let i = 0; i < opts.length; ++i) { opts[i] = opts[i].toLowerCase(); if (opts[i].indexOf('same') === 0) { res.has_same = true; - if (selector.indexOf('nosame') >= 0) continue; + if (selector.indexOf('nosame') >= 0) + continue; } - if (res.opts === null) res.opts = []; - if (res.opts.indexOf(opts[i]) < 0) res.opts.push(opts[i]); + if (res.opts === null) + res.opts = []; + if (res.opts.indexOf(opts[i]) < 0) + res.opts.push(opts[i]); } - if (h.theonly) break; + if (h.theonly) + break; } - if (selector.indexOf('noinspect') >= 0) noinspect = true; + if (selector.indexOf('noinspect') >= 0) + noinspect = true; - if (isany && (res.opts === null)) res.opts = ['']; + if (isany && (res.opts === null)) + res.opts = ['']; // if no any handle found, let inspect ROOT-based objects - if (!isany && (kind.indexOf(prROOT) === 0) && !noinspect) res.opts = []; + if (!isany && getTypeForKind(kind) && !noinspect) + res.opts = []; if (!noinspect && res.opts) res.opts.push(kInspect); res.inspect = !noinspect; res.expand = canexpand; - res.draw = !!res.opts; + res.draw = Boolean(res.opts); return res; } @@ -311,9 +345,20 @@ function getDrawSettings(kind, selector) { setDefaultDrawOpt('TH1', 'text'); setDefaultDrawOpt('TH2', 'col'); */ function setDefaultDrawOpt(classname, opt) { - const handle = getDrawHandle(prROOT + classname, 0); - if (handle) - handle.dflt = opt; + if (!classname) + return; + if ((opt === undefined) && isStr(classname) && (classname.indexOf(':') > 0)) { + // special usage to set list of options like TH2:lego2;TH3:glbox2 + opt.split(';').forEach(part => { + const arr = part.split(':'); + if (arr.length >= 1) + setDefaultDrawOpt(arr[0], arr[1] || ''); + }); + } else { + const handle = getDrawHandle(getKindForType(classname), 0); + if (handle) + handle.dflt = opt; + } } /** @summary Draw object in specified HTML element with given draw options. @@ -339,7 +384,7 @@ async function draw(dom, obj, opt) { let handle, type_info; if ('_typename' in obj) { type_info = 'type ' + obj._typename; - handle = getDrawHandle(prROOT + obj._typename, opt); + handle = getDrawHandle(getKindForType(obj._typename), opt); } else if ('_kind' in obj) { type_info = 'kind ' + obj._kind; handle = getDrawHandle(obj._kind, opt); @@ -356,6 +401,12 @@ async function draw(dom, obj, opt) { if (handle.draw_field && obj[handle.draw_field]) return draw(dom, obj[handle.draw_field], opt || handle.draw_field_opt); + if (internals._alt_draw && !handle.transform) { + const v = internals._alt_draw(dom, obj, opt); + if (v) + return v; + } + if (!canDrawHandle(handle)) { if (opt && (opt.indexOf('same') >= 0)) { const main_painter = getElementMainPainter(dom); @@ -389,7 +440,7 @@ async function draw(dom, obj, opt) { if (painter === false) return null; if (!painter) - throw Error(`Fail to draw object ${type_info}`); + throw Error(`Fail to draw object ${type_info}`); if (isObject(painter) && !painter.options) painter.options = { original: opt || '' }; // keep original draw options return painter; @@ -410,21 +461,38 @@ async function draw(dom, obj, opt) { promise = handle.draw().then(h => { handle.func = h; }); } else if (!handle.func || !isStr(handle.func)) return Promise.reject(Error(`Draw function or class not specified to draw ${type_info}`)); - else if (!handle.prereq && !handle.script) - return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); else { - const init_promise = internals.ignore_v6 - ? Promise.resolve(true) - : _ensureJSROOT().then(v6 => { - const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true); - return pr.then(() => { - if (handle.script) - return loadScript(handle.script); - }).then(() => v6._complete_loading()); - }); + let func = findFunction(handle.func); + if (isFunc(func)) { + handle.func = func; + return performDraw(); + } + let modules = null; + if (isStr(handle.script)) { + if (handle.script.indexOf('modules:') === 0) + modules = handle.script.slice(8); + else if (handle.script.indexOf('.mjs') > 0) + modules = handle.script; + } + + if (!modules && !handle.prereq && !handle.script) + return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); + + let init_promise = Promise.resolve(true); + if (modules) + init_promise = loadModules(modules); + else if (!internals.ignore_v6) { + init_promise = _ensureJSROOT().then(v6 => { + const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true); + return pr.then(() => { + if (handle.script) + return loadScript(handle.script); + }).then(() => v6._complete_loading()); + }); + } promise = init_promise.then(() => { - const func = findFunction(handle.func); + func = findFunction(handle.func); if (!isFunc(func)) return Promise.reject(Error(`Fail to find function ${handle.func} after loading ${handle.prereq || handle.script}`)); @@ -461,25 +529,23 @@ async function redraw(dom, obj, opt) { const can_painter = getElementCanvPainter(dom); let handle, res_painter = null, redraw_res; if (obj._typename) - handle = getDrawHandle(prROOT + obj._typename); + handle = getDrawHandle(getKindForType(obj._typename)); if (handle?.draw_field && obj[handle.draw_field]) obj = obj[handle.draw_field]; if (can_painter) { if (can_painter.matchObjectType(obj._typename)) { redraw_res = can_painter.redrawObject(obj, opt); - if (redraw_res) res_painter = can_painter; + if (redraw_res) + res_painter = can_painter; } else { - for (let i = 0; i < can_painter.painters.length; ++i) { - const painter = can_painter.painters[i]; - if (painter.matchObjectType(obj._typename)) { + can_painter.forEachPainterInPad(painter => { + if (!res_painter && painter.matchObjectType(obj._typename)) { redraw_res = painter.redrawObject(obj, opt); - if (redraw_res) { + if (redraw_res) res_painter = painter; - break; - } } - } + }, 'objects'); } } else { const top = new BasePainter(dom).getTopPainter(); @@ -487,7 +553,8 @@ async function redraw(dom, obj, opt) { // it can be object painter here or can be specially introduce method to handling redraw! if (isFunc(top?.redrawObject)) { redraw_res = top.redrawObject(obj, opt); - if (redraw_res) res_painter = top; + if (redraw_res) + res_painter = top; } } @@ -499,11 +566,36 @@ async function redraw(dom, obj, opt) { return draw(dom, obj, opt); } +/** @summary Create three.js model for object + * @param {object} obj - object + * @param {string} opt - draw options + * @return {Promise} with three.js model */ + +async function build3d(obj, opt) { + if (!isObject(obj) || !obj?._typename) + return Promise.reject(Error('not an object in build3d')); + + const handle = getDrawHandle(getKindForType(obj._typename)); + if (!handle?.class && !handle.build3d) + return Promise.reject(Error(`not able to create three.js for ${obj._typename}`)); + + if (handle.build3d) + return handle.build3d().then(func => func(obj, opt)); + + return handle.class().then(cl => { + if (!isFunc(cl?.build3d)) + return Promise.reject(Error(`painter class for ${obj._typename} does not implement build3d method`)); + + return cl.build3d(obj, opt); + }); +} + /** @summary Scan streamer infos for derived classes * @desc Assign draw functions for such derived classes * @private */ function addStreamerInfosForPainter(lst) { - if (!lst) return; + if (!lst) + return; const basics = [clTObject, clTNamed, clTString, 'TCollection', clTAttLine, clTAttFill, clTAttMarker, clTAttText]; @@ -515,9 +607,9 @@ function addStreamerInfosForPainter(lst) { if (basics.indexOf(element.fName) >= 0) return null; - let handle = getDrawHandle(prROOT + element.fName); + let handle = getDrawHandle(getKindForType(element.fName)); if (handle && !handle.for_derived) - handle = null; + handle = null; // now try find that base class of base in the list if (handle === null) { @@ -533,7 +625,8 @@ function addStreamerInfosForPainter(lst) { } lst.arr.forEach(si => { - if (getDrawHandle(prROOT + si.fName) !== null) return; + if (getDrawHandle(getKindForType(si.fName)) !== null) + return; const handle = checkBaseClasses(si, 0); if (handle) { @@ -565,25 +658,22 @@ function addStreamerInfosForPainter(lst) { * let png64 = await makeImage({ format: 'png', object, option: 'colz', width: 1200, height: 800 }); * let pngbuf = await makeImage({ format: 'png', as_buffer: true, object, option: 'colz', width: 1200, height: 800 }); */ async function makeImage(args) { - if (!args) args = {}; + if (!args) + args = {}; if (!isObject(args.object)) return Promise.reject(Error('No object specified to generate SVG')); if (!args.format) args.format = 'svg'; if (!args.width) - args.width = 1200; + args.width = settings.CanvasWidth; if (!args.height) - args.height = 800; - - if (args.use_canvas_size && (args.object?._typename === clTCanvas) && args.object.fCw && args.object.fCh) { - args.width = args.object.fCw; - args.height = args.object.fCh; - } + args.height = settings.CanvasHeight; async function build(main) { main.attr('width', args.width).attr('height', args.height) .style('width', args.width + 'px').style('height', args.height + 'px') + .property('_batch_use_canvsize', args.use_canvas_size ?? false) .property('_batch_mode', true) .property('_batch_format', args.format !== 'svg' ? args.format : null); @@ -612,15 +702,22 @@ async function makeImage(args) { } } - main.select('svg') - .attr('xmlns', nsSVG) - .attr('width', args.width) - .attr('height', args.height) - .attr('style', null).attr('class', null).attr('x', null).attr('y', null); + const mainsvg = main.select('svg'), + style_filter = mainsvg.style('filter'); + + mainsvg.attr('xmlns', nsSVG) + .attr('style', null).attr('class', null).attr('x', null).attr('y', null); + + if (!mainsvg.attr('width') && !mainsvg.attr('height')) + mainsvg.attr('width', args.width).attr('height', args.height); + + if (style_filter) + mainsvg.style('filter', style_filter); function clear_element() { const elem = d3_select(this); - if (elem.style('display') === 'none') elem.remove(); + if (elem.style('display') === 'none') + elem.remove(); } main.selectAll('g.root_frame').each(clear_element); @@ -628,14 +725,14 @@ async function makeImage(args) { let svg; if (args.format === 'pdf') - svg = { node: main.select('svg').node(), width: args.width, height: args.height, can_modify: true }; + svg = { node: mainsvg.node(), width: args.width, height: args.height, can_modify: true }; else { svg = compressSVG(main.html()); if (args.format === 'svg') return complete(svg); } - return svgToImage(svg, args.format, args.as_buffer).then(complete); + return svgToImage(svg, args.format, args).then(complete); }); } @@ -662,52 +759,46 @@ async function makeImage(args) { * let object = await file.readObject('hpxpy;1'); * let svg = await makeSVG({ object, option: 'lego2,pal50', width: 1200, height: 800 }); */ async function makeSVG(args) { - if (!args) args = {}; + if (!args) + args = {}; args.format = 'svg'; return makeImage(args); } -internals.addDrawFunc = addDrawFunc; - function assignPadPainterDraw(PadPainterClass) { - PadPainterClass.prototype.drawObject = (...args) => - draw(...args).catch(err => { console.log(`Error ${err?.message ?? err} at ${err?.stack ?? 'uncknown place'}`); return null; }); + PadPainterClass.prototype.drawObject = draw; PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings; } // only now one can draw primitives in the canvas assignPadPainterDraw(TPadPainter); -// load v7 only by demand -async function init_v7(arg) { +import_geo = async function() { + return import('./geom/TGeoPainter.mjs').then(geo => { + const handle = getDrawHandle(getKindForType('TGeoVolumeAssembly')); + if (handle) + handle.icon = 'img_geoassembly'; + return geo; + }); +}; + +// load v7 only on demand +import_v7 = async function(arg) { return import('./gpad/RCanvasPainter.mjs').then(h => { // only now one can draw primitives in the canvas assignPadPainterDraw(h.RPadPainter); switch (arg) { case 'more': return import('./draw/v7more.mjs'); case 'pave': return import('./hist/RPavePainter.mjs'); - case 'rh1': return import('./hist/RH1Painter.mjs'); - case 'rh2': return import('./hist/RH2Painter.mjs'); - case 'rh3': return import('./hist/RH3Painter.mjs'); } return h; }); -} - +}; -// to avoid cross-dependnecy between io.mjs and draw.mjs -internals.addStreamerInfosForPainter = addStreamerInfosForPainter; +// to avoid cross-dependency between modules +Object.assign(internals, { addStreamerInfosForPainter, addDrawFunc, setDefaultDrawOpt, makePDF }); -/** @summary Draw TRooPlot - * @private */ -async function drawRooPlot(dom, plot) { - return draw(dom, plot._hist, 'hist').then(async hp => { - const arr = []; - for (let i = 0; i < plot._items.arr.length; ++i) - arr.push(draw(dom, plot._items.arr[i], plot._items.opt[i])); - return Promise.all(arr).then(() => hp); - }); -} +Object.assign(internals.jsroot, { draw, redraw, makeSVG, makeImage, addDrawFunc }); export { addDrawFunc, getDrawHandle, canDrawHandle, getDrawSettings, setDefaultDrawOpt, - draw, redraw, cleanup, makeSVG, makeImage, drawRooPlot, assignPadPainterDraw }; + draw, redraw, cleanup, build3d, makeSVG, makeImage, assignPadPainterDraw }; diff --git a/modules/draw/RNTuple.mjs b/modules/draw/RNTuple.mjs new file mode 100644 index 000000000..c1c1ee977 --- /dev/null +++ b/modules/draw/RNTuple.mjs @@ -0,0 +1,36 @@ +import { isStr } from '../core.mjs'; +import { treeDrawProgress } from './TTree.mjs'; +import { rntupleDraw } from '../rntuple.mjs'; + +/** @summary function called from draw() + * @desc just envelope for real TTree::Draw method which do the main job + * Can be also used for the branch and leaf object + * @private */ +async function drawRNTuple(dom, obj, opt) { + const args = {}; + let tuple; + + if (obj?.$tuple) { + // case of fictional ROOT::RNTupleField + tuple = obj.$tuple; + args.expr = obj._name; + if (isStr(opt) && opt.indexOf('dump') === 0) + args.expr += '>>' + opt; + else if (opt) + args.expr += opt; + } else { + tuple = obj; + args.expr = opt; + } + + if (!tuple) + throw Error('No RNTuple object available for drawing'); + + args.drawid = dom; + + args.progress = treeDrawProgress.bind(args); + + return rntupleDraw(tuple, args).then(res => args.progress(res, true)); +} + +export { drawRNTuple }; diff --git a/modules/draw/RTreeMapPainter.mjs b/modules/draw/RTreeMapPainter.mjs new file mode 100644 index 000000000..b82ce1a77 --- /dev/null +++ b/modules/draw/RTreeMapPainter.mjs @@ -0,0 +1,387 @@ +import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { RTreeMapTooltip } from './RTreeMapTooltip.mjs'; + + +function computeFnv(str) { + const FNV_offset = 14695981039346656037n, FNV_prime = 1099511628211n; + let h = FNV_offset; + for (let i = 0; i < str.length; ++i) { + const octet = BigInt(str.charCodeAt(i) & 0xFF); + h ^= octet; + h *= FNV_prime; + } + return h; +} + +class RTreeMapPainter extends ObjectPainter { + + static CONSTANTS = { + STROKE_WIDTH: 0.15, + STROKE_COLOR: 'black', + + COLOR_HOVER_BOOST: 10, + + DATA_UNITS: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], + + MAIN_TREEMAP: { LEFT: 0.025, BOTTOM: 0.05, RIGHT: 0.825, TOP: 0.9 }, + + LEGEND: { + START_Y: 0.835, + ITEM_HEIGHT: 0.05, + BOX_WIDTH: 0.05, + TEXT_OFFSET_X: 0.01, + TEXT_OFFSET_Y: 0.01, + TEXT_LINE_SPACING: 0.015, + MAX_ITEMS: 10 + }, + + TEXT: { SIZE_VW: 0.6, MIN_RECT_WIDTH: 0.025, MIN_RECT_HEIGHT: 0.05, PADDING: 10, LEAF_OFFSET_Y: 0.015 }, + + INDENT: 0.005, + LEGEND_INDENT_MULTIPLIER: 4 + }; + + constructor(dom, obj, opt) { + super(dom, obj, opt); + this.tooltip = new RTreeMapTooltip(this); + this.rootIndex = 0; + this.parentIndices = []; + } + + cleanup() { + if (this._frame_hidden) { + delete this._frame_hidden; + this.getPadPainter()?.getFrameSvg().style('display', null); + } + + this.tooltip.cleanup(); + super.cleanup(); + } + + appendRect(begin, end, color, strokeColor = RTreeMapPainter.CONSTANTS.STROKE_COLOR, strokeWidth = RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node = null) { + const rect = this.getG() + .append('rect') + .attr('x', this.axisToSvg('x', begin.x, this.isndc)) + .attr('y', this.axisToSvg('y', begin.y, this.isndc)) + .attr('width', `${Math.abs(end.x - begin.x) * 100}%`) + .attr('height', `${Math.abs(end.y - begin.y) * 100}%`) + .attr('fill', color) + .attr('stroke', strokeColor) + .attr('stroke-width', strokeWidth) + .attr('pointer-events', 'fill'); + + if (node) { + rect.datum(node); + this.attachPointerEventsTreeMap(rect, node); + } + return rect; + } + + appendText(content, pos, size, color, anchor = 'start') { + return this.getG() + .append('text') + .attr('x', this.axisToSvg('x', pos.x, this.isndc)) + .attr('y', this.axisToSvg('y', pos.y, this.isndc)) + .attr('font-size', `${size}vw`) + .attr('fill', color) + .attr('text-anchor', anchor) + .attr('pointer-events', 'none') + .text(content); + } + + getRgbList(rgbStr) { return rgbStr.slice(4, -1).split(',').map((x) => parseInt(x)); } + + toRgbStr(rgbList) { return `rgb(${rgbList.join()})`; } + + attachPointerEventsTreeMap(element, node) { + const original_color = element.attr('fill'), hovered_color = this.toRgbStr(this.getRgbList(original_color) + .map((color) => Math.min(color + RTreeMapPainter.CONSTANTS.COLOR_HOVER_BOOST, 255))), + mouseenter = () => { + element.attr('fill', hovered_color); + this.tooltip.content = this.tooltip.generateTooltipContent(node); + this.tooltip.x = 0; + this.tooltip.y = 0; + }, + mouseleave = () => { + element.attr('fill', original_color); + this.tooltip.hideTooltip(); + }, + mousemove = (event) => { + this.tooltip.x = event.pageX; + this.tooltip.y = event.pageY; + this.tooltip.showTooltip(); + }, + click = () => { + const obj = this.getObject(), nodeIndex = obj.fNodes.findIndex((elem) => elem === node); + if (nodeIndex === this.rootIndex) + this.rootIndex = this.parentIndices[nodeIndex]; + else { + let parentIndex = nodeIndex; + while (this.parentIndices[parentIndex] !== this.rootIndex) + parentIndex = this.parentIndices[parentIndex]; + this.rootIndex = parentIndex; + if (obj.fNodes[parentIndex].fNChildren === 0) + this.rootIndex = this.parentIndices[nodeIndex]; + } + this.redraw(); + }; + this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove, click }); + } + + attachPointerEventsLegend(element, type) { + const rects = this.getG().selectAll('rect'), + mouseenter = () => { rects.filter((node) => node !== undefined && node.fType !== type).attr('opacity', '0.5'); }, + mouseleave = () => { rects.attr('opacity', '1'); }; + this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove: () => {}, click: () => {} }); + } + + attachPointerEvents(element, events) { + for (const [key, value] of Object.entries(events)) + element.on(key, value); + } + + computeColor(n) { + const hash = Number(computeFnv(String(n)) & 0xFFFFFFFFn), + r = (hash >> 16) & 0xFF, + g = (hash >> 8) & 0xFF, + b = hash & 0xFF; + return this.toRgbStr([r, g, b]); + } + + getDataStr(bytes) { + const units = RTreeMapPainter.CONSTANTS.DATA_UNITS, + order = Math.floor(Math.log10(bytes) / 3), + finalSize = bytes / Math.pow(1000, order); + return `${finalSize.toFixed(2)}${units[order]}`; + } + + computeWorstRatio(row, width, height, totalSize, horizontalRows) { + if (row.length === 0) + return 0; + + const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); + if (sumRow === 0) + return 0; + + let worstRatio = 0; + for (const child of row) { + const ratio = horizontalRows ? (child.fSize * width * totalSize) / (sumRow * sumRow * height) + : (child.fSize * height * totalSize) / (sumRow * sumRow * width), + aspectRatio = Math.max(ratio, 1 / ratio); + if (aspectRatio > worstRatio) + worstRatio = aspectRatio; + } + return worstRatio; + } + + squarifyChildren(children, rect, horizontalRows, totalSize) { + const width = rect.topRight.x - rect.bottomLeft.x, + height = rect.topRight.y - rect.bottomLeft.y, + remaining = [...children].sort((a, b) => b.fSize - a.fSize), + result = [], + remainingBegin = { ...rect.bottomLeft }; + + while (remaining.length > 0) { + const row = []; + let currentWorstRatio = Infinity; + const remainingWidth = rect.topRight.x - remainingBegin.x, + remainingHeight = rect.topRight.y - remainingBegin.y; + + if (remainingWidth <= 0 || remainingHeight <= 0) + break; + + while (remaining.length > 0) { + row.push(remaining.shift()); + const newWorstRatio = + this.computeWorstRatio(row, remainingWidth, remainingHeight, totalSize, horizontalRows); + if (newWorstRatio > currentWorstRatio) { + remaining.unshift(row.pop()); + break; + } + currentWorstRatio = newWorstRatio; + } + + const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); + if (sumRow === 0) + continue; + + const dimension = horizontalRows ? (sumRow / totalSize * height) : (sumRow / totalSize * width); + let position = 0; + + for (const child of row) { + const childDimension = child.fSize / sumRow * (horizontalRows ? width : height), + childBegin = horizontalRows ? { x: remainingBegin.x + position, y: remainingBegin.y } + : { x: remainingBegin.x, y: remainingBegin.y + position }, + childEnd = horizontalRows + ? { x: remainingBegin.x + position + childDimension, y: remainingBegin.y + dimension } + : { x: remainingBegin.x + dimension, y: remainingBegin.y + position + childDimension }; + + result.push({ node: child, rect: { bottomLeft: childBegin, topRight: childEnd } }); + position += childDimension; + } + + if (horizontalRows) + remainingBegin.y += dimension; + else + remainingBegin.x += dimension; + } + return result; + } + + drawLegend() { + const obj = this.getObject(), + diskMap = {}; + + let stack = [this.rootIndex]; + while (stack.length > 0) { + const node = obj.fNodes[stack.pop()]; + if (node.fNChildren === 0) + diskMap[node.fType] = (diskMap[node.fType] || 0) + node.fSize; + stack = stack.concat(Array.from({ length: node.fNChildren }, (_, a) => a + node.fChildrenIdx)); + } + + const diskEntries = Object.entries(diskMap) + .sort((a, b) => b[1] - a[1]) + .slice(0, RTreeMapPainter.CONSTANTS.LEGEND.MAX_ITEMS) + .filter(([, size]) => size > 0), + + legend = RTreeMapPainter.CONSTANTS.LEGEND; + + diskEntries.forEach(([typeName, size], index) => { + const posY = legend.START_Y - index * legend.ITEM_HEIGHT, + posX = legend.START_Y + legend.ITEM_HEIGHT + legend.TEXT_OFFSET_X, + textSize = RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, + + rect = this.appendRect({ x: legend.START_Y, y: posY }, + { x: legend.START_Y + legend.ITEM_HEIGHT, y: posY - legend.ITEM_HEIGHT }, + this.computeColor(typeName)); + this.attachPointerEventsLegend(rect, typeName); + + const diskOccupPercent = `${(size / obj.fNodes[this.rootIndex].fSize * 100).toFixed(2)}%`, + diskOccup = `(${this.getDataStr(size)} / ${this.getDataStr(obj.fNodes[this.rootIndex].fSize)})`; + + [typeName, diskOccup, diskOccupPercent].forEach( + (content, i) => + this.appendText(content, { x: posX, y: posY - legend.TEXT_OFFSET_Y - legend.TEXT_LINE_SPACING * (i) }, + textSize, 'black')); + }); + } + + trimText(textElement, rect) { + const nodeElem = textElement.node(); + let textContent = nodeElem.textContent; + const availablePx = Math.abs(this.axisToSvg('x', rect.topRight.x, this.isndc) - + this.axisToSvg('x', rect.bottomLeft.x, this.isndc)) - + RTreeMapPainter.CONSTANTS.TEXT.PADDING; + + while (nodeElem.getComputedTextLength && nodeElem.getComputedTextLength() > availablePx && textContent.length > 0) { + textContent = textContent.slice(0, -1); + nodeElem.textContent = textContent + '…'; + } + return textContent; + } + + drawTreeMap(node, rect, depth = 0) { + const isLeaf = node.fNChildren === 0, + color = isLeaf ? this.computeColor(node.fType) : 'rgb(100,100,100)'; + this.appendRect({ x: rect.bottomLeft.x, y: rect.topRight.y }, { x: rect.topRight.x, y: rect.bottomLeft.y }, color, + RTreeMapPainter.CONSTANTS.STROKE_COLOR, RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node); + + const rectWidth = rect.topRight.x - rect.bottomLeft.x, + rectHeight = rect.topRight.y - rect.bottomLeft.y, + labelBase = `${node.fName} (${this.getDataStr(node.fSize)})`, + + textConstants = RTreeMapPainter.CONSTANTS.TEXT, + textSize = (rectWidth <= textConstants.MIN_RECT_WIDTH || rectHeight <= textConstants.MIN_RECT_HEIGHT) + ? 0 + : textConstants.SIZE_VW; + + if (textSize > 0) { + const textElement = this.appendText(labelBase, { + x: rect.bottomLeft.x + (isLeaf ? rectWidth / 2 : RTreeMapPainter.CONSTANTS.INDENT), + y: isLeaf ? (rect.bottomLeft.y + rect.topRight.y) / 2 : (rect.topRight.y - textConstants.LEAF_OFFSET_Y) + }, + textSize, 'white', isLeaf ? 'middle' : 'start'); + textElement.textContent = this.trimText(textElement, rect); + } + + if (!isLeaf && node.fNChildren > 0) { + const obj = this.getObject(), + children = obj.fNodes.slice(node.fChildrenIdx, node.fChildrenIdx + node.fNChildren), + totalSize = children.reduce((sum, child) => sum + child.fSize, 0); + + if (totalSize > 0) { + const indent = RTreeMapPainter.CONSTANTS.INDENT, + innerRect = { + bottomLeft: { x: rect.bottomLeft.x + indent, y: rect.bottomLeft.y + indent }, + topRight: { + x: rect.topRight.x - indent, + y: rect.topRight.y - indent * RTreeMapPainter.CONSTANTS.LEGEND_INDENT_MULTIPLIER + } + }, + + width = innerRect.topRight.x - innerRect.bottomLeft.x, + height = innerRect.topRight.y - innerRect.bottomLeft.y, + horizontalRows = width > height, + + rects = this.squarifyChildren(children, innerRect, horizontalRows, totalSize); + rects.forEach( + ({ node: childNode, rect: childRect }) => { this.drawTreeMap(childNode, childRect, depth + 1); }); + } + } + } + + createParentIndices() { + const obj = this.getObject(); + this.parentIndices = new Array(obj.fNodes.length).fill(0); + obj.fNodes.forEach((node, index) => { + for (let i = node.fChildrenIdx; i < node.fChildrenIdx + node.fNChildren; i++) + this.parentIndices[i] = index; + }); + } + + getDirectory() { + const obj = this.getObject(); + let result = '', + currentIndex = this.rootIndex; + while (currentIndex !== 0) { + result = obj.fNodes[currentIndex].fName + '/' + result; + currentIndex = this.parentIndices[currentIndex]; + } + return result; + } + + redraw() { + const svg = this.getPadPainter().getFrameSvg(); + if (!svg.empty()) { + svg.style('display', 'none'); + this._frame_hidden = true; + } + + const obj = this.getObject(); + this.createG(); + this.isndc = true; + + if (obj.fNodes && obj.fNodes.length > 0) { + this.createParentIndices(); + const mainArea = RTreeMapPainter.CONSTANTS.MAIN_TREEMAP; + this.drawTreeMap( + obj.fNodes[this.rootIndex], + { bottomLeft: { x: mainArea.LEFT, y: mainArea.BOTTOM }, topRight: { x: mainArea.RIGHT, y: mainArea.TOP } }); + this.drawLegend(); + this.appendText(this.getDirectory(), { x: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.LEFT, y: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.TOP + 0.01 }, + RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, 'black'); + } + return this; + } + + static async draw(dom, obj, opt) { + const painter = new RTreeMapPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } + +} // class RTreeMapPainter + + +export { RTreeMapPainter }; diff --git a/modules/draw/RTreeMapTooltip.mjs b/modules/draw/RTreeMapTooltip.mjs new file mode 100644 index 000000000..9e3b669e8 --- /dev/null +++ b/modules/draw/RTreeMapTooltip.mjs @@ -0,0 +1,87 @@ +class RTreeMapTooltip { + + static CONSTANTS = { DELAY: 0, OFFSET_X: 10, OFFSET_Y: -10, PADDING: 8, BORDER_RADIUS: 4 }; + + constructor(painter) + { + this.painter = painter; + this.tooltip = null; + this.content = ''; + this.x = 0; + this.y = 0; + } + + cleanup() { + if (this.tooltip !== null) { + document.body.removeChild(this.tooltip); + this.tooltip = null; + } + } + + createTooltip() + { + if (this.tooltip) + return; + + this.tooltip = document.createElement('div'); + this.tooltip.style.cssText = ` + position: absolute; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: ${RTreeMapTooltip.CONSTANTS.PADDING}px; + border-radius: ${RTreeMapTooltip.CONSTANTS.BORDER_RADIUS}px; + font-size: 12px; + pointer-events: none; + z-index: 10000; + opacity: 0; + transition: opacity 0.2s; + max-width: 200px; + word-wrap: break-word; + `; + document.body.appendChild(this.tooltip); + } + + showTooltip() + { + if (!this.tooltip) + this.createTooltip(); + + this.tooltip.innerHTML = this.content; + this.tooltip.style.left = (this.x + RTreeMapTooltip.CONSTANTS.OFFSET_X) + 'px'; + this.tooltip.style.top = (this.y + RTreeMapTooltip.CONSTANTS.OFFSET_Y) + 'px'; + this.tooltip.style.opacity = '1'; + } + + hideTooltip() + { + if (this.tooltip) + this.tooltip.style.opacity = '0'; + } + + generateTooltipContent(node) + { + const isLeaf = node.fNChildren === 0; + let content = (node.fName.length > 0) ? `<strong>${node.fName}</strong><br>` : ''; + + content += `<i>${(isLeaf ? 'Column' : 'Field')}</i><br>`; + content += `Size: ${this.painter.getDataStr(node.fSize)}<br>`; + + if (isLeaf && node.fType !== undefined) + content += `Type: ${node.fType}<br>`; + + if (!isLeaf) + content += `Children: ${node.fNChildren}<br>`; + + const obj = this.painter.getObject(); + if (obj.fNodes && obj.fNodes.length > 0) { + const totalSize = obj.fNodes[0].fSize, + percentage = ((node.fSize / totalSize) * 100).toFixed(2); + content += `Disk Usage: ${percentage}%`; + } + + return content; + } + +} + +export { RTreeMapTooltip }; diff --git a/modules/draw/TASImagePainter.mjs b/modules/draw/TASImagePainter.mjs index 3dd10d477..32ba75011 100644 --- a/modules/draw/TASImagePainter.mjs +++ b/modules/draw/TASImagePainter.mjs @@ -1,6 +1,6 @@ import { create, settings, isNodeJs, isStr, btoa_func, clTAxis, clTPaletteAxis, clTImagePalette, getDocument } from '../core.mjs'; -import { toHex } from '../base/colors.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; +import { toColor } from '../base/colors.mjs'; +import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { TPavePainter } from '../hist/TPavePainter.mjs'; @@ -14,51 +14,61 @@ import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; class TASImagePainter extends ObjectPainter { + #contour; + /** @summary Decode options string */ decodeOptions(opt) { - const d = new DrawOptions(opt); - - this.options = { Zscale: false }; + const d = new DrawOptions(opt), + obj = this.getObject(); - const obj = this.getObject(); + if (d.check('CONST') && obj) + obj.fConstRatio = true; - if (d.check('CONST')) { - this.options.constRatio = true; - if (obj) obj.fConstRatio = true; - console.log('use const'); - } - if (d.check('Z')) this.options.Zscale = true; + this.setOptions({ Zscale: d.check('Z') }); } /** @summary Create RGBA buffers */ createRGBA(nlevels) { const obj = this.getObject(), pal = obj?.fPalette; - if (!pal) return null; + if (!pal) + return null; - const rgba = new Array((nlevels+1) * 4).fill(0); // precaclucated colors + const rgba = new Array((nlevels + 1) * 4).fill(0); // precalculated colors for (let lvl = 0, indx = 1; lvl <= nlevels; ++lvl) { - const l = lvl/nlevels; - while ((pal.fPoints[indx] < l) && (indx < pal.fPoints.length-1)) indx++; + const l = lvl / nlevels; + while ((pal.fPoints[indx] < l) && (indx < pal.fPoints.length - 1)) + indx++; - const r1 = (pal.fPoints[indx] - l) / (pal.fPoints[indx] - pal.fPoints[indx-1]), - r2 = (l - pal.fPoints[indx-1]) / (pal.fPoints[indx] - pal.fPoints[indx-1]); + const r1 = (pal.fPoints[indx] - l) / (pal.fPoints[indx] - pal.fPoints[indx - 1]), + r2 = (l - pal.fPoints[indx - 1]) / (pal.fPoints[indx] - pal.fPoints[indx - 1]); - rgba[lvl*4] = Math.min(255, Math.round((pal.fColorRed[indx-1] * r1 + pal.fColorRed[indx] * r2) / 256)); - rgba[lvl*4+1] = Math.min(255, Math.round((pal.fColorGreen[indx-1] * r1 + pal.fColorGreen[indx] * r2) / 256)); - rgba[lvl*4+2] = Math.min(255, Math.round((pal.fColorBlue[indx-1] * r1 + pal.fColorBlue[indx] * r2) / 256)); - rgba[lvl*4+3] = Math.min(255, Math.round((pal.fColorAlpha[indx-1] * r1 + pal.fColorAlpha[indx] * r2) / 256)); + rgba[lvl * 4] = Math.min(255, Math.round((pal.fColorRed[indx - 1] * r1 + pal.fColorRed[indx] * r2) / 256)); + rgba[lvl * 4 + 1] = Math.min(255, Math.round((pal.fColorGreen[indx - 1] * r1 + pal.fColorGreen[indx] * r2) / 256)); + rgba[lvl * 4 + 2] = Math.min(255, Math.round((pal.fColorBlue[indx - 1] * r1 + pal.fColorBlue[indx] * r2) / 256)); + rgba[lvl * 4 + 3] = Math.min(255, Math.round((pal.fColorAlpha[indx - 1] * r1 + pal.fColorAlpha[indx] * r2) / 256)); } return rgba; } + /** @summary Cleanup painter + * @private */ + cleanup() { + this.#contour = undefined; + super.cleanup(); + } + + /** @summary Return colors contour + * @private */ + getContour() { return this.#contour; } + /** @summary Create url using image buffer * @private */ async makeUrlFromImageBuf(obj, fp) { const nlevels = 1000; - this.rgba = this.createRGBA(nlevels); // precaclucated colors + this.rgba = this.createRGBA(nlevels); // precalculated colors let min = obj.fImgBuf[0], max = obj.fImgBuf[0]; for (let k = 1; k < obj.fImgBuf.length; ++k) { @@ -71,21 +81,23 @@ class TASImagePainter extends ObjectPainter { // min = Math.min.apply(null, obj.fImgBuf), // max = Math.max.apply(null, obj.fImgBuf); - // create countor like in hist painter to allow palette drawing - this.fContour = { + // create contour like in hist painter to allow palette drawing + this.#contour = { arr: new Array(200), rgba: this.rgba, getLevels() { return this.arr; }, getPaletteColor(pal, zval) { - if (!this.arr || !this.rgba) return 'white'; - const indx = Math.round((zval - this.arr[0]) / (this.arr[this.arr.length-1] - this.arr[0]) * (this.rgba.length-4)/4) * 4; - return '#' + toHex(this.rgba[indx], 1) + toHex(this.rgba[indx+1], 1) + toHex(this.rgba[indx+2], 1) + toHex(this.rgba[indx+3], 1); + if (!this.arr || !this.rgba) + return 'white'; + const indx = Math.round((zval - this.arr[0]) / (this.arr.at(-1) - this.arr.at(0)) * (this.rgba.length - 4) / 4) * 4; + return toColor(this.rgba[indx] / 255, this.rgba[indx + 1] / 255, this.rgba[indx + 2] / 255, this.rgba[indx + 3] / 255); } }; for (let k = 0; k < 200; k++) - this.fContour.arr[k] = min + (max-min)/(200-1)*k; + this.#contour.arr[k] = min + (max - min) / (200 - 1) * k; - if (min >= max) max = min + 1; + if (min >= max) + max = min + 1; const z = this.getImageZoomRange(fp, obj.fConstRatio, obj.fWidth, obj.fHeight), pr = isNodeJs() @@ -111,7 +123,7 @@ class TASImagePainter extends ObjectPainter { arr[dst++] = this.rgba[iii++]; arr[dst++] = this.rgba[iii++]; arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; + arr[dst++] = this.rgba[iii]; } } @@ -123,21 +135,22 @@ class TASImagePainter extends ObjectPainter { getImageZoomRange(fp, constRatio, width, height) { const res = { xmin: 0, xmax: width, ymin: 0, ymax: height }; - if (!fp) return res; + if (!fp) + return res; let offx = 0, offy = 0, sizex = width, sizey = height; if (constRatio) { - const image_ratio = height/width, + const image_ratio = height / width, frame_ratio = fp.getFrameHeight() / fp.getFrameWidth(); if (image_ratio > frame_ratio) { const w2 = height / frame_ratio; - offx = Math.round((w2 - width)/2); + offx = Math.round((w2 - width) / 2); sizex = Math.round(w2); } else { const h2 = frame_ratio * width; - offy = Math.round((h2 - height)/2); + offy = Math.round((h2 - height) / 2); sizey = Math.round(h2); } } @@ -193,15 +206,15 @@ class TASImagePainter extends ObjectPainter { arr2 = imageData2.data; for (let i = z.ymin; i < z.ymax; ++i) { - let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4, - src = ((image.height - i - 1) * image.width + z.xmin) * 4; - for (let j = z.xmin; j < z.xmax; ++j) { - // copy rgba value for specified point - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - } + let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4, + src = ((image.height - i - 1) * image.width + z.xmin) * 4; + for (let j = z.xmin; j < z.xmax; ++j) { + // copy rgba value for specified point + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + arr2[dst++] = arr[src++]; + } } context2.putImageData(imageData2, 0, 0); @@ -217,14 +230,16 @@ class TASImagePainter extends ObjectPainter { }); } + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } + /** @summary Draw image */ async drawImage() { const obj = this.getObject(), fp = this.getFramePainter(), rect = fp?.getFrameRect() ?? this.getPadPainter().getPadRect(); - this.wheel_zoomy = true; - if (obj._blob) { // try to process blob data due to custom streamer if ((obj._blob.length === 15) && !obj._blob[0]) { @@ -232,15 +247,15 @@ class TASImagePainter extends ObjectPainter { obj.fImageCompression = obj._blob[2]; obj.fConstRatio = obj._blob[3]; obj.fPalette = { - _typename: clTImagePalette, - fUniqueID: obj._blob[4], - fBits: obj._blob[5], - fNumPoints: obj._blob[6], - fPoints: obj._blob[7], - fColorRed: obj._blob[8], - fColorGreen: obj._blob[9], - fColorBlue: obj._blob[10], - fColorAlpha: obj._blob[11] + _typename: clTImagePalette, + fUniqueID: obj._blob[4], + fBits: obj._blob[5], + fNumPoints: obj._blob[6], + fPoints: obj._blob[7], + fColorRed: obj._blob[8], + fColorGreen: obj._blob[9], + fColorBlue: obj._blob[10], + fColorAlpha: obj._blob[11] }; obj.fWidth = obj._blob[12]; @@ -278,7 +293,7 @@ class TASImagePainter extends ObjectPainter { if (!res?.url) return this; - const img = this.createG(!!fp) + const img = this.createG(fp) .append('image') .attr('href', res.url) .attr('width', rect.width) @@ -293,12 +308,12 @@ class TASImagePainter extends ObjectPainter { img.style('cursor', 'pointer'); } - assignContextMenu(this); + assignContextMenu(this, kNoReorder); if (!fp || !res.can_zoom) return this; - return this.drawColorPalette(this.options.Zscale, true).then(() => { + return this.drawColorPalette(this.getOptions().Zscale, true).then(() => { fp.setAxesRanges(create(clTAxis), 0, 1, create(clTAxis), 0, 1, null, 0, 0); fp.createXY({ ndim: 2, check_pad_range: false }); return fp.addInteractivity(); @@ -306,9 +321,9 @@ class TASImagePainter extends ObjectPainter { }); } - /** @summary Fill TASImage context */ + /** @summary Fill TASImage context menu */ fillContextMenuItems(menu) { - const obj = this.getObject(); + const obj = this.getObject(), o = this.getOptions(); if (obj) { menu.addchk(obj.fConstRatio, 'Const ratio', flag => { obj.fConstRatio = flag; @@ -316,8 +331,8 @@ class TASImagePainter extends ObjectPainter { }, 'Change const ratio flag of image'); } if (obj?.fPalette) { - menu.addchk(this.options.Zscale, 'Color palette', flag => { - this.options.Zscale = flag; + menu.addchk(o.Zscale, 'Color palette', flag => { + o.Zscale = flag; this.drawColorPalette(flag, true); }, 'Toggle color palette'); } @@ -330,11 +345,16 @@ class TASImagePainter extends ObjectPainter { if (!obj) return false; - if (((axis === 'x') || (axis === 'y')) && (max - min > 0.01)) return true; + if (((axis === 'x') || (axis === 'y')) && (max - min > 0.01)) + return true; return false; } + /** @summary Return palette - dummy here + * @private */ + getHistPalette() { return true; } + /** @summary Draw color palette * @private */ async drawColorPalette(enabled, can_move) { @@ -346,7 +366,6 @@ class TASImagePainter extends ObjectPainter { Object.assign(pal, { fX1NDC: 0.91, fX2NDC: 0.95, fY1NDC: 0.1, fY2NDC: 0.9, fInit: 1 }); pal.fAxis.fChopt = '+'; this.draw_palette = pal; - this._color_palette = true; // to emulate behaviour of hist painter } let pal_painter = this.getPadPainter().findPainterFor(this.draw_palette); @@ -375,14 +394,11 @@ class TASImagePainter extends ObjectPainter { return pal_painter.drawPave(''); } - const prev_name = this.selectCurrentPad(this.getPadName()); - - return TPavePainter.draw(this.getDom(), this.draw_palette).then(p => { + return TPavePainter.draw(this.getPadPainter(), this.draw_palette).then(p => { pal_painter = p; - this.selectCurrentPad(prev_name); // mark painter as secondary - not in list of TCanvas primitives - pal_painter.setSecondary(this); + pal_painter.setSecondaryId(this); // make dummy redraw, palette will be updated only from histogram painter pal_painter.redraw = function() {}; @@ -393,8 +409,9 @@ class TASImagePainter extends ObjectPainter { * @private */ toggleColz() { if (this.getObject()?.fPalette) { - this.options.Zscale = !this.options.Zscale; - return this.drawColorPalette(this.options.Zscale, true); + const o = this.getOptions(); + o.Zscale = !o.Zscale; + return this.drawColorPalette(o.Zscale, true); } } @@ -429,8 +446,8 @@ class TASImagePainter extends ObjectPainter { return ensureTCanvas(painter, false) .then(() => painter.drawImage()) .then(() => { - painter.fillToolbar(); - return painter; + painter.fillToolbar(); + return painter; }); } diff --git a/modules/draw/TAnnotation3DPainter.mjs b/modules/draw/TAnnotation3DPainter.mjs new file mode 100644 index 000000000..e0169fbcb --- /dev/null +++ b/modules/draw/TAnnotation3DPainter.mjs @@ -0,0 +1,72 @@ +import { isFunc } from '../core.mjs'; +import { makeTranslate } from '../base/BasePainter.mjs'; +import { TTextPainter } from './TTextPainter.mjs'; +import { build3dlatex } from '../base/latex3d.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; + +function getRotation(camera, mesh) { + const dx = camera.position.x - mesh.position.x, + dy = camera.position.y - mesh.position.y; + return Math.atan2(dy, dx) + Math.PI / 2; +} + +class TAnnotation3DPainter extends TTextPainter { + + /** @summary Redraw annotation + * @desc handle 3d and 2d mode */ + + async redraw() { + const fp = this.getFramePainter(), + text = this.getObject(); + + if (fp?.mode3d && !this.use_2d) { + const mesh = build3dlatex(text, '', this, fp); + mesh.traverse(o => o.geometry?.rotateX(Math.PI / 2)); + mesh.position.set(fp.grx(text.fX), fp.gry(text.fY), fp.grz(text.fZ)); + mesh.rotation.set(0, 0, getRotation(fp.camera, mesh)); + fp.processRender3D = true; + fp.add3DMesh(mesh, this, true); + fp.render3D(100); + return this; + } + + const mode = fp?.mode3d && isFunc(fp?.convert3DtoPadNDC) ? '3d' : '2d'; + let x = text.fX, y = text.fY; + + if (mode === '3d') { + const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ); + x = pos.x; + y = pos.y; + } + + return this._redrawText(x, y, mode).then(() => { + fp.processRender3D = mode === '3d'; + return this; + }); + } + + /** @summary Extra handling during 3d rendering + * @desc Allows to reposition annotation when rotate/zoom drawing */ + handleRender3D() { + const text = this.getObject(), + fp = this.getFramePainter(); + if (this.use_2d) { + const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ), + new_x = this.axisToSvg('x', pos.x, true), + new_y = this.axisToSvg('y', pos.y, true); + makeTranslate(this.getG(), new_x - this.pos_x, new_y - this.pos_y); + } else + fp.get3DMeshes(this).forEach(mesh => mesh.rotation.set(0, 0, getRotation(fp.camera, mesh))); + } + + /** @summary draw TAnnotation3D object */ + static async draw(dom, obj, opt) { + const painter = new TAnnotation3DPainter(dom, obj, opt); + painter.use_2d = (opt === '2d') || (opt === '2D'); + return ensureTCanvas(painter, painter.use_2d ? true : '3d').then(() => painter.redraw()); + } + +} // class TAnnotation3DPainter + + +export { TAnnotation3DPainter }; diff --git a/modules/draw/TArrowPainter.mjs b/modules/draw/TArrowPainter.mjs index 2aa95cd6f..bd94e2d3a 100644 --- a/modules/draw/TArrowPainter.mjs +++ b/modules/draw/TArrowPainter.mjs @@ -2,52 +2,63 @@ import { TLinePainter } from './TLinePainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -/** @summary Drawing TArrow - * @private */ +/** + * @summary Painter for TArrow class + * @private + */ + class TArrowPainter extends TLinePainter { + #beg; + #mid; + #end; + #angle2; // half of angle in rad + #wsize; // arrow size + /** @summary Create line segment with rotation */ rotate(angle, x0, y0) { - let dx = this.wsize * Math.cos(angle), dy = this.wsize * Math.sin(angle), res = ''; + let dx = this.#wsize * Math.cos(angle), dy = this.#wsize * Math.sin(angle), res = ''; if ((x0 !== undefined) && (y0 !== undefined)) - res = `M${Math.round(x0-dx)},${Math.round(y0-dy)}`; + res = `M${Math.round(x0 - dx)},${Math.round(y0 - dy)}`; else { - dx = -dx; dy = -dy; + dx = -dx; + dy = -dy; } res += `l${Math.round(dx)},${Math.round(dy)}`; - if (x0 && (y0 === undefined)) res += 'z'; + if (x0 && (y0 === undefined)) + res += 'z'; return res; } /** @summary Create SVG path for the arrow */ createPath() { const angle = Math.atan2(this.y2 - this.y1, this.x2 - this.x1), - dlen = this.wsize * Math.cos(this.angle2), - dx = dlen*Math.cos(angle), dy = dlen*Math.sin(angle); + dlen = this.#wsize * Math.cos(this.#angle2), + dx = dlen * Math.cos(angle), dy = dlen * Math.sin(angle); let path = ''; - if (this.beg) { - path += this.rotate(angle - Math.PI - this.angle2, this.x1, this.y1) + - this.rotate(angle - Math.PI + this.angle2, this.beg > 10); + if (this.#beg) { + path += this.rotate(angle - Math.PI - this.#angle2, this.x1, this.y1) + + this.rotate(angle - Math.PI + this.#angle2, this.#beg > 10); } - if (this.mid % 10 === 2) { - path += this.rotate(angle - Math.PI - this.angle2, (this.x1+this.x2-dx)/2, (this.y1+this.y2-dy)/2) + - this.rotate(angle - Math.PI + this.angle2, this.mid > 10); + if (this.#mid % 10 === 2) { + path += this.rotate(angle - Math.PI - this.#angle2, (this.x1 + this.x2 - dx) / 2, (this.y1 + this.y2 - dy) / 2) + + this.rotate(angle - Math.PI + this.#angle2, this.#mid > 10); } - if (this.mid % 10 === 1) { - path += this.rotate(angle - this.angle2, (this.x1+this.x2+dx)/2, (this.y1+this.y2+dy)/2) + - this.rotate(angle + this.angle2, this.mid > 10); + if (this.#mid % 10 === 1) { + path += this.rotate(angle - this.#angle2, (this.x1 + this.x2 + dx) / 2, (this.y1 + this.y2 + dy) / 2) + + this.rotate(angle + this.#angle2, this.#mid > 10); } - if (this.end) { - path += this.rotate(angle - this.angle2, this.x2, this.y2) + - this.rotate(angle + this.angle2, this.end > 10); + if (this.#end) { + path += this.rotate(angle - this.#angle2, this.x2, this.y2) + + this.rotate(angle + this.#angle2, this.#end > 10); } - return `M${Math.round(this.x1 + (this.beg > 10 ? dx : 0))},${Math.round(this.y1 + (this.beg > 10 ? dy : 0))}` + - `L${Math.round(this.x2 - (this.end > 10 ? dx : 0))},${Math.round(this.y2 - (this.end > 10 ? dy : 0))}` + + return `M${Math.round(this.x1 + (this.#beg > 10 ? dx : 0))},${Math.round(this.y1 + (this.#beg > 10 ? dy : 0))}` + + `L${Math.round(this.x2 - (this.#end > 10 ? dx : 0))},${Math.round(this.y2 - (this.#end > 10 ? dy : 0))}` + path; } @@ -59,34 +70,31 @@ class TArrowPainter extends TLinePainter { oo = arrow.fOption, rect = this.getPadPainter().getPadRect(); - this.wsize = Math.max(3, Math.round(Math.max(rect.width, rect.height) * arrow.fArrowSize * 0.8)); - this.angle2 = arrow.fAngle/2/180 * Math.PI; - this.beg = this.mid = this.end = 0; + this.#wsize = Math.max(3, Math.round(Math.max(rect.width, rect.height) * arrow.fArrowSize * 0.8)); + this.#angle2 = arrow.fAngle / 2 / 180 * Math.PI; + this.#beg = this.#mid = this.#end = 0; if (oo.indexOf('<') === 0) - this.beg = (oo.indexOf('<|') === 0) ? 12 : 2; + this.#beg = (oo.indexOf('<|') === 0) ? 12 : 2; if (oo.indexOf('->-') >= 0) - this.mid = 1; + this.#mid = 1; else if (oo.indexOf('-|>-') >= 0) - this.mid = 11; + this.#mid = 11; else if (oo.indexOf('-<-') >= 0) - this.mid = 2; + this.#mid = 2; else if (oo.indexOf('-<|-') >= 0) - this.mid = 12; + this.#mid = 12; const p1 = oo.lastIndexOf('>'), p2 = oo.lastIndexOf('|>'), len = oo.length; - if ((p1 >= 0) && (p1 === len-1)) - this.end = ((p2 >= 0) && (p2 === len-2)) ? 11 : 1; + if ((p1 >= 0) && (p1 === len - 1)) + this.#end = ((p2 >= 0) && (p2 === len - 2)) ? 11 : 1; - this.createAttFill({ attr: arrow }); + this.createAttFill({ attr: arrow, enable: (this.#beg > 10) || (this.#end > 10) }); } /** @summary Add extras to path for TArrow */ addExtras(elem) { - if ((this.beg > 10) || (this.end > 10)) - elem.call(this.fillatt.func); - else - elem.style('fill', 'none'); + elem.call(this.fillatt.func); } /** @summary Draw TArrow object */ diff --git a/modules/draw/TBoxPainter.mjs b/modules/draw/TBoxPainter.mjs new file mode 100644 index 000000000..0143b6ad4 --- /dev/null +++ b/modules/draw/TBoxPainter.mjs @@ -0,0 +1,164 @@ +import { rgb as d3_rgb, select as d3_select } from '../d3.mjs'; +import { DrawOptions, getBoxDecorations } from '../base/BasePainter.mjs'; +import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { addMoveHandler } from '../gui/utils.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; + +/** + * @summary Painter for TBox class + * @private + */ + +class TBoxPainter extends ObjectPainter { + + #border_mode; + #border_size; + + /** @summary start of drag handler + * @private */ + moveStart(x, y) { + const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2); + + this.c_x1 = Math.abs(x - this.x2) > ww * 0.1; + this.c_x2 = Math.abs(x - this.x1) > ww * 0.1; + this.c_y1 = Math.abs(y - this.y2) > hh * 0.1; + this.c_y2 = Math.abs(y - this.y1) > hh * 0.1; + if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2) + this.c_y1 = this.c_y2 = false; + if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2) + this.c_x1 = this.c_x2 = false; + } + + /** @summary drag handler + * @private */ + moveDrag(dx, dy) { + this.x1 += this.c_x1 ? dx : 0; + this.x2 += this.c_x2 ? dx : 0; + this.y1 += this.c_y1 ? dy : 0; + this.y2 += this.c_y2 ? dy : 0; + + const nodes = this.getG().selectAll('path').nodes(), + pathes = this.getPathes(); + + pathes.forEach((path, i) => d3_select(nodes[i]).attr('d', path)); + } + + /** @summary end of drag handler + * @private */ + moveEnd(not_changed) { + if (not_changed) + return; + const box = this.getObject(), X = this.swap_xy ? 'Y' : 'X', Y = this.swap_xy ? 'X' : 'Y'; + let exec = ''; + if (this.c_x1) { + const v = this.svgToAxis('x', this.x1); + box[`f${X}1`] = v; + exec += `Set${X}1(${v});;`; + } + if (this.c_x2) { + const v = this.svgToAxis('x', this.x2); + box[`f${X}2`] = v; + exec += `Set${X}2(${v});;`; + } + if (this.c_y1) { + const v = this.svgToAxis('y', this.y1); + box[`f${Y}1`] = v; + exec += `Set${Y}1(${v});;`; + } + if (this.c_y2) { + const v = this.svgToAxis('y', this.y2); + box[`f${Y}2`] = v; + exec += `Set${Y}2(${v});;`; + } + this.submitCanvExec(exec + 'Notify();;'); + } + + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const box = this.getObject(), + minx = Math.min(box.fX1, box.fX2), + maxx = Math.max(box.fX1, box.fX2), + miny = Math.min(box.fY1, box.fY2), + maxy = Math.max(box.fY1, box.fY2); + return { minx, miny, maxx, maxy }; + } + + /** @summary Create path */ + getPathes() { + const xx = Math.round(Math.min(this.x1, this.x2)), + yy = Math.round(Math.min(this.y1, this.y2)), + ww = Math.round(Math.abs(this.x2 - this.x1)), + hh = Math.round(Math.abs(this.y1 - this.y2)), + path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`; + if (!this.#border_mode) + return [path]; + return [path].concat(getBoxDecorations(xx, yy, ww, hh, this.#border_mode, this.#border_size, this.#border_size)); + } + + /** @summary Redraw box */ + redraw() { + const box = this.getObject(), + d = new DrawOptions(this.getDrawOpt()), + fp = d.check('FRAME') ? this.getFramePainter() : null, + draw_line = d.check('L'); + + this.createAttLine({ attr: box }); + this.createAttFill({ attr: box }); + + this.swap_xy = fp?.swap_xy(); + + // if box filled, contour line drawn only with 'L' draw option: + if (!this.fillatt.empty() && !draw_line) + this.lineatt.color = 'none'; + + const g = this.createG(fp); + + this.x1 = this.axisToSvg('x', box.fX1); + this.x2 = this.axisToSvg('x', box.fX2); + this.y1 = this.axisToSvg('y', box.fY1); + this.y2 = this.axisToSvg('y', box.fY2); + + if (this.swap_xy) + [this.x1, this.x2, this.y1, this.y2] = [this.y1, this.y2, this.x1, this.x2]; + + this.#border_mode = (box.fBorderMode && this.fillatt.hasColor()) ? box.fBorderMode : 0; + this.#border_size = box.fBorderSize || 2; + + const paths = this.getPathes(); + + g.append('svg:path') + .attr('d', paths[0]) + .call(this.lineatt.func) + .call(this.fillatt.func); + + if (this.#border_mode) { + g.append('svg:path') + .attr('d', paths[1]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); + + g.append('svg:path') + .attr('d', paths[2]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); + } + + assignContextMenu(this); + + addMoveHandler(this); + + return this; + } + + /** @summary Draw TBox object */ + static async draw(dom, obj, opt) { + const painter = new TBoxPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } + +} // class TBoxPainter + + +export { TBoxPainter }; diff --git a/modules/draw/TGaxisPainter.mjs b/modules/draw/TGaxisPainter.mjs index 98b09b105..ed64e68d4 100644 --- a/modules/draw/TGaxisPainter.mjs +++ b/modules/draw/TGaxisPainter.mjs @@ -4,7 +4,7 @@ import { makeTranslate } from '../base/BasePainter.mjs'; import { EAxisBits, TAxisPainter } from '../gpad/TAxisPainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; +import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; import { getHPainter } from '../gui/display.mjs'; import { proivdeEvalPar } from '../base/func.mjs'; @@ -22,14 +22,14 @@ class TGaxisPainter extends TAxisPainter { y2 = this.axisToSvg('y', gaxis.fY2); if (opt === 'ndc') { - const pw = this.getPadPainter().getPadWidth(), - ph = this.getPadPainter().getPadHeight(); - - gaxis.fX1 = x1 / pw; - gaxis.fX2 = x2 / pw; - gaxis.fY1 = (ph - y1) / ph; - gaxis.fY2 = (ph - y2)/ ph; - this.use_ndc = true; + const pw = this.getPadPainter().getPadWidth(), + ph = this.getPadPainter().getPadHeight(); + + gaxis.fX1 = x1 / pw; + gaxis.fX2 = x2 / pw; + gaxis.fY1 = (ph - y1) / ph; + gaxis.fY2 = (ph - y2) / ph; + this.use_ndc = true; } else if (opt === 'frame') { const rect = this.getFramePainter().getFrameRect(); gaxis.fX1 = (x1 - rect.x) / rect.width; @@ -49,7 +49,8 @@ class TGaxisPainter extends TAxisPainter { /** @summary Drag end handle */ moveEnd(not_changed) { - if (not_changed) return; + if (not_changed) + return; const gaxis = this.getObject(); @@ -127,20 +128,18 @@ class TGaxisPainter extends TAxisPainter { axis_func: this.axis_func }); - this.createG(); - this.gaxis_x = x1; this.gaxis_y = y2; - return this.drawAxis(this.getG(), Math.abs(w), Math.abs(h), makeTranslate(this.gaxis_x, this.gaxis_y) || '').then(() => { + return this.drawAxis(this.createG(), Math.abs(w), Math.abs(h), makeTranslate(this.gaxis_x, this.gaxis_y) || '').then(() => { addMoveHandler(this); - assignContextMenu(this); + assignContextMenu(this, kNoReorder); return this; }); } - /** @summary Fill TGaxis context */ - fillContextMenu(menu) { + /** @summary Fill TGaxis context menu items */ + fillContextMenuItems(menu) { menu.addTAxisMenu(EAxisBits, this, this.getObject(), ''); } @@ -181,7 +180,7 @@ class TGaxisPainter extends TAxisPainter { res.eval = function(v) { try { v = res._func.evalPar(v); - } catch (err) { + } catch { v = 0; } return Number.isFinite(v) ? v : 0; @@ -190,13 +189,13 @@ class TGaxisPainter extends TAxisPainter { const vmin = res.eval(smin), vmax = res.eval(smax); if ((vmin < vmax) === (smin < smax)) { res._vmin = vmin; - res._vk = 1/(vmax - vmin); + res._vk = 1 / (vmax - vmin); } else if (vmin === vmax) { res._vmin = 0; res._vk = 1; } else { res._vmin = vmax; - res._vk = 1/(vmin - vmax); + res._vk = 1 / (vmin - vmax); } res._range = [0, 100]; res.range = function(arr) { @@ -224,7 +223,8 @@ class TGaxisPainter extends TAxisPainter { const painter = new TGaxisPainter(dom, obj, false); return ensureTCanvas(painter, false).then(() => { - if (opt) painter.convertTo(opt); + if (opt) + painter.convertTo(opt); return painter.checkFuncion(); }).then(() => painter.redraw()); } diff --git a/modules/draw/TGraphPolarPainter.mjs b/modules/draw/TGraphPolarPainter.mjs index 76f56d22f..6ee48482a 100644 --- a/modules/draw/TGraphPolarPainter.mjs +++ b/modules/draw/TGraphPolarPainter.mjs @@ -1,33 +1,75 @@ -import { settings, create } from '../core.mjs'; -import { scaleLinear, select as d3_select, pointer as d3_pointer } from '../d3.mjs'; +import { settings, create, BIT } from '../core.mjs'; +import { scaleLinear, pointer as d3_pointer } from '../d3.mjs'; import { DrawOptions, buildSvgCurve, makeTranslate } from '../base/BasePainter.mjs'; import { ObjectPainter, getElementMainPainter } from '../base/ObjectPainter.mjs'; +import { drawObjectTitle } from '../hist/TPavePainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { TooltipHandler } from '../gpad/TFramePainter.mjs'; +import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; +const kNoTitle = BIT(17); /** * @summary Painter for TGraphPolargram objects. * * @private */ -class TGraphPolargramPainter extends ObjectPainter { +class TGraphPolargramPainter extends TooltipHandler { /** @summary Create painter * @param {object|string} dom - DOM element for drawing or element id * @param {object} polargram - object to draw */ - constructor(dom, polargram) { - super(dom, polargram); + constructor(dom, polargram, opt) { + super(dom, polargram, opt); this.$polargram = true; // indicate that this is polargram this.zoom_rmin = this.zoom_rmax = 0; + this.t0 = 0; + this.mult = 1; + this.decodeOptions(opt); + this.setTooltipEnabled(true); + } + + /** @summary Returns true if fixed coordinates are configured */ + isNormalAngles() { + const polar = this.getObject(); + return polar?.fRadian || polar?.fGrad || polar?.fDegree; + } + + /** @summary Decode draw options */ + decodeOptions(opt) { + const d = new DrawOptions(opt); + + this.setOptions({ + rdot: d.check('RDOT'), + rangle: d.check('RANGLE', true) ? d.partAsInt() : 0, + NoLabels: d.check('N'), + OrthoLabels: d.check('O') + }); + + this.storeDrawOpt(opt); + } + + /** @summary Set angles range displayed by the polargram */ + setAnglesRange(tmin, tmax, set_obj) { + if (tmin >= tmax) + tmax = tmin + 1; + if (set_obj) { + const polar = this.getObject(); + polar.fRwtmin = tmin; + polar.fRwtmax = tmax; + } + this.t0 = tmin; + this.mult = 2 * Math.PI / (tmax - tmin); } /** @summary Translate coordinates */ - translate(angle, radius, keep_float) { + translate(input_angle, radius, keep_float) { + // recalculate angle + const angle = (input_angle - this.t0) * this.mult; let rx = this.r(radius), - ry = rx/this.szx*this.szy, - grx = rx * Math.cos(-angle - this.angle), - gry = ry * Math.sin(-angle - this.angle); + ry = rx / this.szx * this.szy, + grx = rx * Math.cos(-angle), + gry = ry * Math.sin(-angle); if (!keep_float) { grx = Math.round(grx); @@ -40,21 +82,24 @@ class TGraphPolargramPainter extends ObjectPainter { /** @summary format label for radius ticks */ format(radius) { - if (radius === Math.round(radius)) return radius.toString(); - if (this.ndig > 10) return radius.toExponential(4); - + if (radius === Math.round(radius)) + return radius.toString(); + if (this.ndig > 10) + return radius.toExponential(4); return radius.toFixed((this.ndig > 0) ? this.ndig : 0); } /** @summary Convert axis values to text */ axisAsText(axis, value) { if (axis === 'r') { - if (value === Math.round(value)) return value.toString(); - if (this.ndig>10) return value.toExponential(4); - return value.toFixed(this.ndig+2); + if (value === Math.round(value)) + return value.toString(); + if (this.ndig > 10) + return value.toExponential(4); + return value.toFixed(this.ndig + 2); } - value *= 180/Math.PI; + value *= 180 / Math.PI; return (value === Math.round(value)) ? value.toString() : value.toFixed(1); } @@ -67,17 +112,17 @@ class TGraphPolargramPainter extends ObjectPainter { rect = {}; if (pad) { - rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin))*w); - rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin))*h); + rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin)) * w); + rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin)) * h); } else { - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); } - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); rect.hint_delta_x = rect.szx; rect.hint_delta_y = rect.szy; @@ -89,17 +134,11 @@ class TGraphPolargramPainter extends ObjectPainter { /** @summary Process mouse event */ mouseEvent(kind, evnt) { - const layer = this.getLayerSvg('primitives_layer'), - interactive = layer.select('.interactive_ellipse'); - if (interactive.empty()) return; - let pnt = null; - if (kind !== 'leave') { - const pos = d3_pointer(evnt, interactive.node()); + const pos = d3_pointer(evnt, this.getG()?.node()); pnt = { x: pos[0], y: pos[1], touch: false }; } - this.processFrameTooltipEvent(pnt); } @@ -111,10 +150,12 @@ class TGraphPolargramPainter extends ObjectPainter { this.processFrameTooltipEvent(null); // remove all tooltips const polar = this.getObject(); - if (!polar) return; + if (!polar) + return; let delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - if (!delta) return; + if (!delta) + return; delta = (delta < 0) ? -0.2 : 0.2; @@ -122,9 +163,10 @@ class TGraphPolargramPainter extends ObjectPainter { const range = rmax - rmin; // rmin -= delta*range; - rmax += delta*range; + rmax += delta * range; - if ((rmin<polar.fRwrmin) || (rmax>polar.fRwrmax)) rmin = rmax = 0; + if ((rmin < polar.fRwrmin) || (rmax > polar.fRwrmax)) + rmin = rmax = 0; if ((this.zoom_rmin !== rmin) || (this.zoom_rmax !== rmax)) { this.zoom_rmin = rmin; @@ -133,16 +175,66 @@ class TGraphPolargramPainter extends ObjectPainter { } } + /** @summary Process mouse double click event */ + mouseDoubleClick() { + if (this.zoom_rmin || this.zoom_rmax) { + this.zoom_rmin = this.zoom_rmax = 0; + this.redrawPad(); + } + } + + /** @summary Draw polargram polar labels */ + async drawPolarLabels(polar, nmajor) { + const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2), + o = this.getOptions(); + + return this.startTextDrawingAsync(polar.fPolarLabelFont, fontsize).then(() => { + const lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}'], + aligns = [12, 11, 21, 31, 32, 33, 23, 13]; + + for (let n = 0; n < nmajor; ++n) { + const angle = -n * 2 * Math.PI / nmajor; + this.getG().append('svg:path') + .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) + .call(this.lineatt.func); + + let align = 12, rotate = 0; + + if (o.OrthoLabels) { + rotate = -n / nmajor * 360; + if ((rotate > -271) && (rotate < -91)) { + align = 32; + rotate += 180; + } + } else { + const aindx = Math.round(16 - angle / Math.PI * 4) % 8; // index in align table, here absolute angle is important + align = aligns[aindx]; + } + + this.drawText({ + align, rotate, + x: Math.round((this.szx + fontsize) * Math.cos(angle)), + y: Math.round((this.szy + fontsize / this.szx * this.szy) * (Math.sin(angle))), + text: lbls[n], + color: this.getColor(polar.fPolarLabelColor), latex: 1 + }); + } + + return this.finishTextDrawing(); + }); + } + /** @summary Redraw polargram */ - redraw() { - if (!this.isMainPainter()) return; + async redraw() { + if (!this.isMainPainter()) + return; const polar = this.getObject(), - rect = this.getPadPainter().getFrameRect(); - - this.createG(); + o = this.getOptions(), + rect = this.getPadPainter().getFrameRect(), + g = this.createG(); - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); + makeTranslate(g, Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); this.szx = rect.szx; this.szy = rect.szy; @@ -154,141 +246,187 @@ class TGraphPolargramPainter extends ObjectPainter { } this.r = scaleLinear().domain([this.scale_rmin, this.scale_rmax]).range([0, this.szx]); - this.angle = polar.fAxisAngle || 0; + + if (polar.fRadian) { + polar.fRwtmin = 0; + polar.fRwtmax = 2 * Math.PI; + } else if (polar.fDegree) { + polar.fRwtmin = 0; + polar.fRwtmax = 360; + } else if (polar.fGrad) { + polar.fRwtmin = 0; + polar.fRwtmax = 200; + } + + this.setAnglesRange(polar.fRwtmin, polar.fRwtmax); const ticks = this.r.ticks(5); - let nminor = Math.floor((polar.fNdivRad % 10000) / 100); + let nminor = Math.floor((polar.fNdivRad % 10000) / 100), + nmajor = polar.fNdivPol % 100; + if (nmajor !== 3) + nmajor = 8; this.createAttLine({ attr: polar }); - if (!this.gridatt) this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false }); + if (!this.gridatt) + this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false }); const range = Math.abs(polar.fRwrmax - polar.fRwrmin); this.ndig = (range <= 0) ? -3 : Math.round(Math.log10(ticks.length / range)); // verify that all radius labels are unique let lbls = [], indx = 0; - while (indx<ticks.length) { + while (indx < ticks.length) { const lbl = this.format(ticks[indx]); if (lbls.indexOf(lbl) >= 0) { - if (++this.ndig>10) break; - lbls = []; indx = 0; continue; - } + if (++this.ndig > 10) + break; + lbls = []; + indx = 0; + continue; + } lbls.push(lbl); indx++; } let exclude_last = false; + const pointer_events = this.isBatchMode() ? null : 'visibleFill'; - if ((ticks[ticks.length-1] < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) { + if ((ticks.at(-1) < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) { ticks.push(polar.fRwrmax); exclude_last = true; } - this.startTextDrawing(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)); - - for (let n = 0; n < ticks.length; ++n) { - let rx = this.r(ticks[n]), ry = rx/this.szx*this.szy; - this.draw_g.append('ellipse') + return this.startTextDrawingAsync(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)).then(() => { + const axis_angle = - (o.rangle || polar.fAxisAngle) / 180 * Math.PI, + ca = Math.cos(axis_angle), + sa = Math.sin(axis_angle); + for (let n = 0; n < ticks.length; ++n) { + let rx = this.r(ticks[n]), + ry = rx / this.szx * this.szy; + g.append('ellipse') .attr('cx', 0) .attr('cy', 0) .attr('rx', Math.round(rx)) .attr('ry', Math.round(ry)) .style('fill', 'none') + .style('pointer-events', pointer_events) .call(this.lineatt.func); - if ((n < ticks.length-1) || !exclude_last) { - this.drawText({ align: 23, x: Math.round(rx), y: Math.round(polar.fRadialTextSize * this.szy * 0.5), - text: this.format(ticks[n]), color: this.getColor(polar.fRadialLabelColor), latex: 0 }); - } + if ((n < ticks.length - 1) || !exclude_last) { + const halign = ca > 0.7 ? 1 : (ca > 0 ? 3 : (ca > -0.7 ? 1 : 3)), + valign = Math.abs(ca) < 0.7 ? 1 : 3; + this.drawText({ + align: 10 * halign + valign, + x: Math.round(rx * ca), + y: Math.round(ry * sa), + text: this.format(ticks[n]), + color: this.getColor(polar.fRadialLabelColor), latex: 0 + }); + if (o.rdot) { + g.append('ellipse') + .attr('cx', Math.round(rx * ca)) + .attr('cy', Math.round(ry * sa)) + .attr('rx', 3) + .attr('ry', 3) + .style('fill', 'red'); + } + } - if ((nminor>1) && ((n < ticks.length-1) || !exclude_last)) { - const dr = (ticks[1] - ticks[0]) / nminor; - for (let nn = 1; nn < nminor; ++nn) { - const gridr = ticks[n] + dr*nn; - if (gridr > this.scale_rmax) break; - rx = this.r(gridr); ry = rx/this.szx*this.szy; - this.draw_g.append('ellipse') + if ((nminor > 1) && ((n < ticks.length - 1) || !exclude_last)) { + const dr = (ticks[1] - ticks[0]) / nminor; + for (let nn = 1; nn < nminor; ++nn) { + const gridr = ticks[n] + dr * nn; + if (gridr > this.scale_rmax) + break; + rx = this.r(gridr); + ry = rx / this.szx * this.szy; + g.append('ellipse') .attr('cx', 0) .attr('cy', 0) .attr('rx', Math.round(rx)) .attr('ry', Math.round(ry)) .style('fill', 'none') + .style('pointer-events', pointer_events) .call(this.gridatt.func); + } } } - } - let nmajor = polar.fNdivPol % 100; - if ((nmajor !== 8) && (nmajor !== 3)) nmajor = 8; - - return this.finishTextDrawing().then(() => { - const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2); - this.startTextDrawing(polar.fPolarLabelFont, fontsize); - - lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}']; - const aligns = [12, 11, 21, 31, 32, 33, 23, 13]; - - for (let n = 0; n < nmajor; ++n) { - const angle = -n*2*Math.PI/nmajor - this.angle; - this.draw_g.append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`) - .call(this.lineatt.func); - - const aindx = Math.round(16 -angle/Math.PI*4) % 8; // index in align table, here absolute angle is important - - this.drawText({ align: aligns[aindx], - x: Math.round((this.szx+fontsize)*Math.cos(angle)), - y: Math.round((this.szy + fontsize/this.szx*this.szy)*(Math.sin(angle))), - text: lbls[n], - color: this.getColor(polar.fPolarLabelColor), latex: 1 }); + if (ca < 0.999) { + g.append('path') + .attr('d', `M0,0L${Math.round(this.szx * ca)},${Math.round(this.szy * sa)}`) + .style('pointer-events', pointer_events) + .call(this.lineatt.func); } return this.finishTextDrawing(); + }).then(() => { + return o.NoLabels ? true : this.drawPolarLabels(polar, nmajor); }).then(() => { nminor = Math.floor((polar.fNdivPol % 10000) / 100); if (nminor > 1) { - for (let n = 0; n < nmajor*nminor; ++n) { - if (n % nminor === 0) continue; - const angle = -n*2*Math.PI/nmajor/nminor - this.angle; - this.draw_g.append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`) - .call(this.gridatt.func); + for (let n = 0; n < nmajor * nminor; ++n) { + if (n % nminor === 0) + continue; + const angle = -n * 2 * Math.PI / nmajor / nminor; + g.append('svg:path') + .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) + .call(this.gridatt.func); } } - if (this.isBatchMode()) return; - - TooltipHandler.assign(this); - - const layer = this.getLayerSvg('primitives_layer'); - let interactive = layer.select('.interactive_ellipse'); - - if (interactive.empty()) { - interactive = layer.append('g') - .classed('most_upper_primitives', true) - .append('ellipse') - .classed('interactive_ellipse', true) - .attr('cx', 0) - .attr('cy', 0) - .style('fill', 'none') - .style('pointer-events', 'visibleFill') - .on('mouseenter', evnt => this.mouseEvent('enter', evnt)) - .on('mousemove', evnt => this.mouseEvent('move', evnt)) - .on('mouseleave', evnt => this.mouseEvent('leave', evnt)); - } + if (this.isBatchMode()) + return; - interactive.attr('rx', this.szx).attr('ry', this.szy); + assignContextMenu(this, kNoReorder); - d3_select(interactive.node().parentNode).attr('transform', this.draw_g.attr('transform')); + this.assignZoomHandler(g); + }); + } - if (settings.Zooming && settings.ZoomWheel) - interactive.on('wheel', evnt => this.mouseWheel(evnt)); + /** @summary Fill TGraphPolargram context menu */ + fillContextMenuItems(menu) { + const pp = this.getObject(), o = this.getOptions(); + menu.sub('Axis range'); + menu.addchk(pp.fRadian, 'Radian', flag => { + pp.fRadian = flag; + pp.fDegree = pp.fGrad = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToRadian()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as radian range 0..2*Pi'); + menu.addchk(pp.fDegree, 'Degree', flag => { + pp.fDegree = flag; + pp.fRadian = pp.fGrad = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToDegree()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as degree range 0..360'); + menu.addchk(pp.fGrad, 'Grad', flag => { + pp.fGrad = flag; + pp.fRadian = pp.fDegree = false; + this.interactiveRedraw('pad', flag ? 'exec:SetToGrad()' : 'exec:SetTwoPi()'); + }, 'Handle data angles as grad range 0..200'); + menu.endsub(); + menu.addSizeMenu('Axis angle', 0, 315, 45, o.rangle || pp.fAxisAngle, v => { + o.rangle = pp.fAxisAngle = v; + this.interactiveRedraw('pad', `exec:SetAxisAngle(${v})`); }); } + /** @summary Assign zoom handler to element + * @private */ + assignZoomHandler(elem) { + elem.on('mouseenter', evnt => this.mouseEvent('enter', evnt)) + .on('mousemove', evnt => this.mouseEvent('move', evnt)) + .on('mouseleave', evnt => this.mouseEvent('leave', evnt)); + + if (settings.Zooming) + elem.on('dblclick', evnt => this.mouseDoubleClick(evnt)); + + if (settings.Zooming && settings.ZoomWheel) + elem.on('wheel', evnt => this.mouseWheel(evnt)); + } + /** @summary Draw TGraphPolargram */ - static async draw(dom, polargram /* , opt */) { + static async draw(dom, polargram, opt) { const main = getElementMainPainter(dom); if (main) { if (main.getObject() === polargram) @@ -296,7 +434,7 @@ class TGraphPolargramPainter extends ObjectPainter { throw Error('Cannot superimpose TGraphPolargram with any other drawings'); } - const painter = new TGraphPolargramPainter(dom, polargram); + const painter = new TGraphPolargramPainter(dom, polargram, opt); return ensureTCanvas(painter, false).then(() => { painter.setAsMainPainter(); return painter.redraw(); @@ -314,126 +452,196 @@ class TGraphPolargramPainter extends ObjectPainter { class TGraphPolarPainter extends ObjectPainter { - /** @summary Redraw TGraphPolar */ - redraw() { - this.drawGraphPolar(); - } - /** @summary Decode options for drawing TGraphPolar */ decodeOptions(opt) { - const d = new DrawOptions(opt || 'L'); + const d = new DrawOptions(opt || 'L'), + rdot = d.check('RDOT'), + rangle = d.check('RANGLE', true) ? d.partAsInt() : 0, + o = this.setOptions({ + mark: d.check('P'), + err: d.check('E'), + fill: d.check('F'), + line: d.check('L'), + curve: d.check('C'), + radian: d.check('R'), + degree: d.check('D'), + grad: d.check('G'), + Axis: d.check('N') ? 'N' : '' + }, opt); + + if (d.check('O')) + o.Axis += 'O'; + if (rdot) + o.Axis += '_rdot'; + if (rangle) + o.Axis += `_rangle${rangle}`; - if (!this.options) this.options = {}; + this.storeDrawOpt(opt); + } - Object.assign(this.options, { - mark: d.check('P'), - err: d.check('E'), - fill: d.check('F'), - line: d.check('L'), - curve: d.check('C') - }); + /** @summary Update TGraphPolar with polargram */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; - this.storeDrawOpt(opt); + if (opt && (opt !== this.getOptions().original)) + this.decodeOptions(opt); + + if (this._draw_axis && obj.fPolargram) + this.getMainPainter().updateObject(obj.fPolargram); + + delete obj.fPolargram; + // copy all properties but not polargram + Object.assign(this.getObject(), obj); + return true; + } + + /** @summary Draw TGraphPolar title */ + async drawTitle(first_time) { + return drawObjectTitle(this, first_time, this._draw_axis, !this.getObject()?.TestBit(kNoTitle)); + } + + /** @summary Redraw TGraphPolar */ + async redraw() { + return this.drawGraphPolar() + .then(() => this.drawTitle()); } /** @summary Drawing TGraphPolar */ - drawGraphPolar() { + async drawGraphPolar() { const graph = this.getObject(), + o = this.getOptions(), main = this.getMainPainter(); - if (!graph || !main?.$polargram) return; - - if (this.options.mark) this.createAttMarker({ attr: graph }); - if (this.options.err || this.options.line || this.options.curve) this.createAttLine({ attr: graph }); - if (this.options.fill) this.createAttFill({ attr: graph }); + if (!graph || !main?.$polargram) + return; - this.createG(); + if (o.mark) + this.createAttMarker({ attr: graph }); + if (o.err || o.line || o.curve) + this.createAttLine({ attr: graph }); + if (o.fill) + this.createAttFill({ attr: graph }); + + const g = this.createG(); + + if (this._draw_axis && !main.isNormalAngles()) { + const has_err = graph.fEX?.length; + let rwtmin = graph.fX[0], + rwtmax = graph.fX[0]; + for (let n = 0; n < graph.fNpoints; ++n) { + rwtmin = Math.min(rwtmin, graph.fX[n] - (has_err ? graph.fEX[n] : 0)); + rwtmax = Math.max(rwtmax, graph.fX[n] + (has_err ? graph.fEX[n] : 0)); + } + rwtmax += (rwtmax - rwtmin) / graph.fNpoints; + main.setAnglesRange(rwtmin, rwtmax, true); + } - this.draw_g.attr('transform', main.draw_g.attr('transform')); + g.attr('transform', main.getG().attr('transform')); let mpath = '', epath = ''; - const bins = []; + const bins = [], pointer_events = this.isBatchMode() ? null : 'visibleFill'; for (let n = 0; n < graph.fNpoints; ++n) { - if (graph.fY[n] > main.scale_rmax) continue; - - if (this.options.err) { - let pos1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]), - pos2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]); - epath += `M${pos1.grx},${pos1.gry}L${pos2.grx},${pos2.gry}`; + if (graph.fY[n] > main.scale_rmax) + continue; - pos1 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]); - pos2 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]); + if (o.err) { + const p1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]), + p2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]), + p3 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]), + p4 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]); - epath += `M${pos1.grx},${pos1.gry}A${pos2.rx},${pos2.ry},0,0,1,${pos2.grx},${pos2.gry}`; + epath += `M${p1.grx},${p1.gry}L${p2.grx},${p2.gry}` + + `M${p3.grx},${p3.gry}A${p4.rx},${p4.ry},0,0,1,${p4.grx},${p4.gry}`; } const pos = main.translate(graph.fX[n], graph.fY[n]); - if (this.options.mark) + if (o.mark) mpath += this.markeratt.create(pos.grx, pos.gry); - if (this.options.curve || this.options.line || this.options.fill) + if (o.curve || o.line || o.fill) bins.push(pos); } - if ((this.options.fill || this.options.line) && bins.length) { + if ((o.fill || o.line) && bins.length) { const lpath = buildSvgCurve(bins, { line: true }); - if (this.options.fill) { - this.draw_g.append('svg:path') - .attr('d', lpath + 'Z') - .call(this.fillatt.func); + if (o.fill) { + g.append('svg:path') + .attr('d', lpath + 'Z') + .style('pointer-events', pointer_events) + .call(this.fillatt.func); } - if (this.options.line) { - this.draw_g.append('svg:path') - .attr('d', lpath) - .style('fill', 'none') - .call(this.lineatt.func); + if (o.line) { + g.append('svg:path') + .attr('d', lpath) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); } } - if (this.options.curve && bins.length) { - this.draw_g.append('svg:path') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .call(this.lineatt.func); + if (o.curve && bins.length) { + g.append('svg:path') + .attr('d', buildSvgCurve(bins)) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); } if (epath) { - this.draw_g.append('svg:path') - .attr('d', epath) - .style('fill', 'none') - .call(this.lineatt.func); + g.append('svg:path') + .attr('d', epath) + .style('fill', 'none') + .style('pointer-events', pointer_events) + .call(this.lineatt.func); } if (mpath) { - this.draw_g.append('svg:path') - .attr('d', mpath) - .call(this.markeratt.func); + g.append('svg:path') + .attr('d', mpath) + .style('pointer-events', pointer_events) + .call(this.markeratt.func); + } + + if (!this.isBatchMode()) { + assignContextMenu(this, kNoReorder); + main.assignZoomHandler(g); } } /** @summary Create polargram object */ - createPolargram() { - const polargram = create('TGraphPolargram'), - gr = this.getObject(); + createPolargram(gr) { + const o = this.getOptions(); + if (!gr.fPolargram) { + gr.fPolargram = create('TGraphPolargram'); + if (o.radian) + gr.fPolargram.fRadian = true; + else if (o.degree) + gr.fPolargram.fDegree = true; + else if (o.grad) + gr.fPolargram.fGrad = true; + } let rmin = gr.fY[0] || 0, rmax = rmin; + const has_err = gr.fEY?.length; for (let n = 0; n < gr.fNpoints; ++n) { - rmin = Math.min(rmin, gr.fY[n] - gr.fEY[n]); - rmax = Math.max(rmax, gr.fY[n] + gr.fEY[n]); + rmin = Math.min(rmin, gr.fY[n] - (has_err ? gr.fEY[n] : 0)); + rmax = Math.max(rmax, gr.fY[n] + (has_err ? gr.fEY[n] : 0)); } - polargram.fRwrmin = rmin - (rmax-rmin)*0.1; - polargram.fRwrmax = rmax + (rmax-rmin)*0.1; + gr.fPolargram.fRwrmin = rmin - (rmax - rmin) * 0.1; + gr.fPolargram.fRwrmax = rmax + (rmax - rmin) * 0.1; - return polargram; + return gr.fPolargram; } /** @summary Provide tooltip at specified point */ extractTooltip(pnt) { - if (!pnt) return null; + if (!pnt) + return null; const graph = this.getObject(), main = this.getMainPainter(); @@ -441,19 +649,25 @@ class TGraphPolarPainter extends ObjectPainter { for (let n = 0; n < graph.fNpoints; ++n) { const pos = main.translate(graph.fX[n], graph.fY[n]), - dist2 = (pos.x-pnt.x)**2 + (pos.y-pnt.y)**2; - if (dist2 < best_dist2) { best_dist2 = dist2; bestindx = n; bestpos = pos; } + dist2 = (pos.grx - pnt.x) ** 2 + (pos.gry - pnt.y) ** 2; + if (dist2 < best_dist2) { + best_dist2 = dist2; + bestindx = n; + bestpos = pos; + } } let match_distance = 5; - if (this.markeratt?.used) match_distance = this.markeratt.getFullSize(); + if (this.markeratt?.used) + match_distance = this.markeratt.getFullSize(); - if (Math.sqrt(best_dist2) > match_distance) return null; + if (Math.sqrt(best_dist2) > match_distance) + return null; const res = { name: this.getObject().fName, title: this.getObject().fTitle, - x: bestpos.x, y: bestpos.y, - color1: this.markeratt?.used ? this.markeratt.color : this.lineatt.color, + x: bestpos.grx, y: bestpos.gry, + color1: (this.markeratt?.used ? this.markeratt.color : undefined) ?? (this.fillatt?.used ? this.fillatt.color : undefined) ?? this.lineatt?.color, exact: Math.sqrt(best_dist2) < 4, lines: [this.getObjectHint()], binindx: bestindx, @@ -475,15 +689,15 @@ class TGraphPolarPainter extends ObjectPainter { /** @summary Show tooltip */ showTooltip(hint) { - let ttcircle = this.draw_g?.selectChild('.tooltip_bin'); + let ttcircle = this.getG()?.selectChild('.tooltip_bin'); - if (!hint || !this.draw_g) { + if (!hint || !this.getG()) { ttcircle?.remove(); return; } if (ttcircle.empty()) { - ttcircle = this.draw_g.append('svg:ellipse') + ttcircle = this.getG().append('svg:ellipse') .attr('class', 'tooltip_bin') .style('pointer-events', 'none'); } @@ -504,13 +718,14 @@ class TGraphPolarPainter extends ObjectPainter { /** @summary Process tooltip event */ processTooltipEvent(pnt) { const hint = this.extractTooltip(pnt); - if (!pnt || !pnt.disabled) this.showTooltip(hint); + if (!pnt || !pnt.disabled) + this.showTooltip(hint); return hint; } /** @summary Draw TGraphPolar */ static async draw(dom, graph, opt) { - const painter = new TGraphPolarPainter(dom, graph); + const painter = new TGraphPolarPainter(dom, graph, opt); painter.decodeOptions(opt); const main = painter.getMainPainter(); @@ -521,16 +736,16 @@ class TGraphPolarPainter extends ObjectPainter { let pr = Promise.resolve(null); if (!main) { - if (!graph.fPolargram) - graph.fPolargram = painter.createPolargram(); - pr = TGraphPolargramPainter.draw(dom, graph.fPolargram); + // indicate that axis defined by this graph + painter._draw_axis = true; + pr = TGraphPolargramPainter.draw(dom, painter.createPolargram(graph), painter.options.Axis); } - return pr.then(() => { + return pr.then(gram_painter => { + gram_painter?.setSecondaryId(painter, 'polargram'); painter.addToPadPrimitives(); - painter.drawGraphPolar(); - return painter; - }); + return painter.drawGraphPolar(); + }).then(() => painter.drawTitle(true)); } } // class TGraphPolarPainter diff --git a/modules/draw/TLinePainter.mjs b/modules/draw/TLinePainter.mjs index 98d4750b9..884bf55d8 100644 --- a/modules/draw/TLinePainter.mjs +++ b/modules/draw/TLinePainter.mjs @@ -1,61 +1,107 @@ import { BIT } from '../core.mjs'; +import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu, kToFront } from '../gui/menu.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; const kLineNDC = BIT(14); +/** + * @summary Painter for TLine class + * @private + */ + class TLinePainter extends ObjectPainter { + #side; // side which is interactively moved + /** @summary Start interactive moving */ moveStart(x, y) { - const fullsize = Math.sqrt((this.x1-this.x2)**2 + (this.y1-this.y2)**2), - sz1 = Math.sqrt((x-this.x1)**2 + (y-this.y1)**2)/fullsize, - sz2 = Math.sqrt((x-this.x2)**2 + (y-this.y2)**2)/fullsize; + const fullsize = Math.max(1, Math.sqrt((this.x1 - this.x2) ** 2 + (this.y1 - this.y2) ** 2)), + sz1 = Math.sqrt((x - this.x1) ** 2 + (y - this.y1) ** 2) / fullsize, + sz2 = Math.sqrt((x - this.x2) ** 2 + (y - this.y2) ** 2) / fullsize; if (sz1 > 0.9) - this.side = 1; + this.#side = 1; else if (sz2 > 0.9) - this.side = -1; + this.#side = -1; else - this.side = 0; + this.#side = 0; } /** @summary Continue interactive moving */ moveDrag(dx, dy) { - if (this.side !== 1) { this.x1 += dx; this.y1 += dy; } - if (this.side !== -1) { this.x2 += dx; this.y2 += dy; } - this.draw_g.select('path').attr('d', this.createPath()); + if (this.#side !== 1) { + this.x1 += dx; + this.y1 += dy; + } + if (this.#side !== -1) { + this.x2 += dx; + this.y2 += dy; + } + this.getG().select('path').attr('d', this.createPath()); } /** @summary Finish interactive moving */ moveEnd(not_changed) { - if (not_changed) return; + if (not_changed) + return; const line = this.getObject(); - let exec = ''; - line.fX1 = this.svgToAxis('x', this.x1, this.isndc); - line.fX2 = this.svgToAxis('x', this.x2, this.isndc); - line.fY1 = this.svgToAxis('y', this.y1, this.isndc); - line.fY2 = this.svgToAxis('y', this.y2, this.isndc); - if (this.side !== 1) exec += `SetX1(${line.fX1});;SetY1(${line.fY1});;`; - if (this.side !== -1) exec += `SetX2(${line.fX2});;SetY2(${line.fY2});;`; + let exec = '', + fx1 = this.svgToAxis('x', this.x1, this.isndc), + fx2 = this.svgToAxis('x', this.x2, this.isndc), + fy1 = this.svgToAxis('y', this.y1, this.isndc), + fy2 = this.svgToAxis('y', this.y2, this.isndc); + if (this.swap_xy) + [fx1, fy1, fx2, fy2] = [fy1, fx1, fy2, fx2]; + line.fX1 = fx1; + line.fX2 = fx2; + line.fY1 = fy1; + line.fY2 = fy2; + if (this.#side !== 1) + exec += `SetX1(${fx1});;SetY1(${fy1});;`; + if (this.#side !== -1) + exec += `SetX2(${fx2});;SetY2(${fy2});;`; this.submitCanvExec(exec + 'Notify();;'); } + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const line = this.getObject(), + isndc = line.TestBit(kLineNDC); + if (isndc) + return null; + const minx = Math.min(line.fX1, line.fX2), + maxx = Math.max(line.fX1, line.fX2), + miny = Math.min(line.fY1, line.fY2), + maxy = Math.max(line.fY1, line.fY2); + return { minx, miny, maxx, maxy }; + } + /** @summary Calculate line coordinates */ prepareDraw() { const line = this.getObject(); this.isndc = line.TestBit(kLineNDC); - const func = this.getAxisToSvgFunc(this.isndc, true, true); + const use_frame = this.isndc ? false : new DrawOptions(this.getDrawOpt()).check('FRAME'); + + this.createG(use_frame ? 'frame2d' : undefined); + + this.swap_xy = use_frame && this.getFramePainter()?.swap_xy(); + + const func = this.getAxisToSvgFunc(this.isndc, true); this.x1 = func.x(line.fX1); this.y1 = func.y(line.fY1); this.x2 = func.x(line.fX2); this.y2 = func.y(line.fY2); + if (this.swap_xy) + [this.x1, this.y1, this.x2, this.y2] = [this.y1, this.x1, this.y2, this.x2]; + this.createAttLine({ attr: line }); } @@ -70,12 +116,9 @@ class TLinePainter extends ObjectPainter { /** @summary Redraw line */ redraw() { - this.createG(); - this.prepareDraw(); - const elem = this.draw_g.append('svg:path') - .attr('d', this.createPath()) + const elem = this.appendPath(this.createPath()) .call(this.lineatt.func); if (this.getObject()?.$do_not_draw) @@ -83,7 +126,7 @@ class TLinePainter extends ObjectPainter { else { this.addExtras(elem); addMoveHandler(this); - assignContextMenu(this, kToFront); + assignContextMenu(this); } return this; diff --git a/modules/draw/TPiePainter.mjs b/modules/draw/TPiePainter.mjs new file mode 100644 index 000000000..be52f2c11 --- /dev/null +++ b/modules/draw/TPiePainter.mjs @@ -0,0 +1,524 @@ +import { makeTranslate, DrawOptions, floatToString } from '../base/BasePainter.mjs'; +import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { drawObjectTitle } from '../hist/TPavePainter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { addMoveHandler, getColorId, getColorExec } from '../gui/utils.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; + + +/** + * @summary Painter for TBox class + * @private + */ + +class TPiePainter extends ObjectPainter { + + #cx; // recent cx + #cy; // recent cy + #rx; // recent rx + #ry; // recent ry + #slices; // recent slices + #movex; // moving X coordinate + #movey; // moving Y coordinate + #angle0; // initial angle + #offset0; // initial offset + #slice; // moving slice + #mode; // moving mode + #padh; // pad height + + /** @summary Decode options */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + o = this.getOptions(); + o.is3d = d.check('3D'); + o.lblor = 0; + o.sort = 0; + o.samecolor = false; + o.same = d.check('SAME'); + if (d.check('SC')) + o.samecolor = true; // around + if (d.check('T')) + o.lblor = 2; // around + if (d.check('R')) + o.lblor = 1; // along the radius + if (d.check('>')) + o.sort = 1; + if (d.check('<')) + o.sort = -1; + } + + #findDrawnSlice(x, y) { + if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) + return null; + let angle = Math.atan2(y / this.#ry, x / this.#rx); + + while (angle < 0.5 * Math.PI) + angle += 2 * Math.PI; + + return this.#slices.find(elem => { + return ((elem.a1 < angle) && (angle < elem.a2)) || + ((elem.a1 < angle + 2 * Math.PI) && (angle + 2 * Math.PI < elem.a2)); + }); + } + + /** @summary start of drag handler + * @private */ + moveStart(x, y) { + if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) + return; + let angle = Math.atan2(y / this.#ry, x / this.#rx); + + while (angle < 0.5 * Math.PI) + angle += 2 * Math.PI; + + const pie = this.getObject(), + len = Math.sqrt((x / this.#rx) ** 2 + (y / this.#ry) ** 2), + slice = this.#findDrawnSlice(x, y); + + // kind of cursor shown + this.#mode = ((len > 0.95) && (x > this.#rx * 0.95) && this.options.is3d) ? 'n-resize' : ((slice && len - slice.offset < 0.7) ? 'grab' : 'w-resize'); + + this.#movex = x; + this.#movey = y; + + this.getG().style('cursor', this.#mode); + + if (this.#mode === 'grab') { + this.#slice = slice.n; + this.#angle0 = len; + this.#offset0 = pie.fPieSlices[this.#slice].fRadiusOffset; + } else if (this.#mode === 'n-resize') { + this.#padh = this.getPadPainter().getPadHeight(); + this.#angle0 = pie.fAngle3D; + this.#offset0 = y; + } else { + this.#angle0 = angle; + this.#offset0 = pie.fAngularOffset; + } + } + + /** @summary drag handler + * @private */ + moveDrag(dx, dy) { + this.#movex += dx; + this.#movey += dy; + + const pie = this.getObject(); + + if (this.#mode === 'grab') { + const len = Math.sqrt((this.#movex / this.#rx) ** 2 + (this.#movey / this.#ry) ** 2); + pie.fPieSlices[this.#slice].fRadiusOffset = Math.max(0, this.#offset0 + 0.25 * (len - this.#angle0)); + } else if (this.#mode === 'n-resize') + pie.fAngle3D = Math.max(5, Math.min(85, this.#angle0 + (this.#movey - this.#offset0) / this.#padh * 180)); + else { + const angle = Math.atan2(this.#movey / this.#ry, this.#movex / this.#rx); + pie.fAngularOffset = this.#offset0 - (angle - this.#angle0) / Math.PI * 180; + } + + this.drawPie(); + } + + /** @summary end of drag handler + * @private */ + moveEnd(not_changed) { + if (not_changed) + return; + + const pie = this.getObject(); + + let exec; + + if (this.#mode === 'grab') + exec = `SetEntryRadiusOffset(${this.#slice},${pie.fPieSlices[this.#slice].fRadiusOffset})`; + else if (this.#mode === 'n-resize') + exec = `SetAngle3D(${pie.fAngle3D})`; + else + exec = `SetAngularOffset(${pie.fAngularOffset})`; + + if (exec) + this.submitCanvExec(exec + ';;Notify();;'); + + this.#mode = null; + + this.getG().style('cursor', null); + } + + /** @summary Update TPie object */ + updateObject(obj, opt) { + if (!this.matchObjectType(obj)) + return false; + + this.decodeOptions(opt); + + Object.assign(this.getObject(), obj); + + return true; + } + + /** @summary Redraw pie */ + async drawPie() { + const maing = this.createG(), + pie = this.getObject(), + o = this.getOptions(), + pp = this.getPadPainter(), + radX = pie.fRadius; + + this.#cx = this.axisToSvg('x', pie.fX); + this.#cy = this.axisToSvg('y', pie.fY); + + let radY = radX, pixelHeight = 1; + + if (o.is3d) { + radY *= Math.sin(pie.fAngle3D / 180 * Math.PI); + pixelHeight = this.axisToSvg('y', pie.fY - pie.fHeight) - this.#cy; + } + + maing.style('cursor', this.#mode || null); + + this.createAttText({ attr: pie }); + + const rx = this.axisToSvg('x', pie.fX + radX) - this.#cx, + ry = this.axisToSvg('y', pie.fY - radY) - this.#cy, + dist_to_15pi = a => { + while (a < 0.5 * Math.PI) + a += 2 * Math.PI; + while (a >= 2.5 * Math.PI) + a -= 2 * Math.PI; + return Math.abs(a - 1.5 * Math.PI); + }; + + makeTranslate(maing, this.#cx, this.#cy); + + // pie.fPieSlices[4].fValue = 100; + + const arr = []; + let total = 0, af = -pie.fAngularOffset / 180 * Math.PI; + // ensure all angles are positive + while (af <= 2 * Math.PI) + af += 2 * Math.PI; + + for (let n = 0; n < pie.fPieSlices.length; n++) { + const slice = pie.fPieSlices[n], + value = slice.fValue; + total += value; + arr.push({ + n, value, slice, + offset: slice.fRadiusOffset, + attline: this.createAttLine(slice), + attfill: this.createAttFill(slice) + }); + } + + // sort in increase/decrease order + if (o.sort !== 0) + arr.sort((v1, v2) => { return o.sort * (v1.value - v2.value); }); + + // now assign angles for each slice + + for (let n = 0; n < arr.length; n++) { + const entry = arr[n]; + entry.seq = n; + entry.a2 = af; + af -= entry.value / total * 2 * Math.PI; + entry.a1 = af; + + entry.x1 = Math.round(rx * Math.cos(entry.a1)); + entry.y1 = Math.round(ry * Math.sin(entry.a1)); + entry.x2 = Math.round(rx * Math.cos(entry.a2)); + entry.y2 = Math.round(ry * Math.sin(entry.a2)); + + if (entry.offset) { + const coef = radX > 0 ? entry.offset / radX : 0.1, + mid_angle = (entry.a1 + entry.a2) / 2; + entry.dx = Math.round(rx * coef * Math.cos(mid_angle)); + entry.dy = Math.round(ry * coef * Math.sin(mid_angle)); + } else + entry.dx = entry.dy = 0; + } + + const add_path = (entry, path) => { + const elem = maing.append('svg:path') + .attr('d', path) + .call(entry.attline.func) + .call(entry.attfill.func); + if (entry.offset) + makeTranslate(elem, entry.dx, entry.dy); + }, build_pie = (entry, func) => { + // use same segments for side and top/bottom curves + let a = entry.a1, border = 0; + while (border <= entry.a1) + border += Math.PI; + while (a < entry.a2) { + if (border >= entry.a2) { + func(a, entry.a2, entry); + a = entry.a2; + } else { + func(a, border, entry); + a = border; + border += Math.PI; + } + } + }, add_curved_side = (aa1, aa2, entry) => { + if (dist_to_15pi((aa1 + aa2) / 2) < 0.5 * Math.PI) + return; + const xx1 = Math.round(rx * Math.cos(aa1)), + yy1 = Math.round(ry * Math.sin(aa1)), + xx2 = Math.round(rx * Math.cos(aa2)), + yy2 = Math.round(ry * Math.sin(aa2)); + add_path(entry, `M${xx1},${yy1}a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}v${pixelHeight}a${rx},${ry},0,0,0,${xx1 - xx2},${yy1 - yy2}z`); + }, add_planar_side = (x, y, entry) => { + add_path(entry, `M0,0v${pixelHeight}l${x},${y}v${-pixelHeight}z`); + }; + + // build main paths for each slice + + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; + if (o.is3d) { + entry.pie_path = ''; + build_pie(entry, (aa1, aa2) => { + const xx1 = Math.round(rx * Math.cos(aa1)), + yy1 = Math.round(ry * Math.sin(aa1)), + xx2 = Math.round(rx * Math.cos(aa2)), + yy2 = Math.round(ry * Math.sin(aa2)); + entry.pie_path += `a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}`; + }); + } else + entry.pie_path = `a${rx},${ry},0,0,1,${entry.x2 - entry.x1},${entry.y2 - entry.y1}`; + } + + // code to create 3d effect + + if (o.is3d) { + let start_indx = -1, border = Math.PI / 2; + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; + + // first add bottom + add_path(entry, `M0,${pixelHeight}l${entry.x1},${entry.y1}${entry.pie_path}z`); + + if ((entry.a1 <= 1.5 * Math.PI) && (entry.a2 >= 1.5 * Math.PI)) + start_indx = indx; + else if ((entry.a1 <= 3.5 * Math.PI) && (entry.a2 >= 3.5 * Math.PI)) { + start_indx = indx; + border = 2.5 * Math.PI; + } + } + + if (start_indx < 0) { + console.error('fail to find start index, use default'); + start_indx = 0; + } + + let indx = start_indx, cnt = arr.length; + + while ((arr[indx].a1 > border) && (cnt-- > 0)) { + const entry1 = arr[indx]; + indx++; + if (indx === arr.length) { + indx = 0; + border += 2 * Math.PI; + } + const entry2 = arr[indx]; + + if (entry1.offset || entry2.offset) { + add_planar_side(entry1.x1, entry1.y1, entry1); + add_planar_side(entry2.x2, entry2.y2, entry2); + } + // curved + build_pie(entry1, add_curved_side); + } + + indx = start_indx; + + while (cnt-- > 0) { + const entry1 = arr[indx]; + indx = (indx === 0) ? arr.length - 1 : indx - 1; + const entry2 = arr[indx]; + + if (entry1.offset || entry2.offset) { + add_planar_side(entry1.x2, entry1.y2, entry1); + add_planar_side(entry2.x1, entry2.y1, entry2); + } + + build_pie(entry2, add_curved_side); + } + } + + // add main path + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx]; + add_path(entry, `M0,0l${entry.x1},${entry.y1}${entry.pie_path}z`); + } + + // at the end draw text + + return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp), maing).then(() => { + for (let indx = 0; indx < arr.length; indx++) { + const entry = arr[indx], + slice = entry.slice, + mid_angle = (entry.a1 + entry.a2) / 2, + frac = total ? slice.fValue / total : 0; + + let tmptxt = pie.fLabelFormat; + tmptxt = tmptxt.replaceAll('%txt', slice.fTitle); + tmptxt = tmptxt.replaceAll('%val', floatToString(slice.fValue, pie.fValueFormat)); + tmptxt = tmptxt.replaceAll('%frac', floatToString(frac, pie.fFractionFormat)); + tmptxt = tmptxt.replaceAll('%perc', floatToString(frac * 100, pie.fPercentFormat) + '%'); + + const arg = { + draw_g: maing, + x: entry.dx + rx * (1 + pie.fLabelsOffset) * Math.cos(mid_angle), + y: entry.dy + ry * (1 + pie.fLabelsOffset) * Math.sin(mid_angle), + latex: 1, + align: 22, + text: tmptxt + }; + + if (o.samecolor) + arg.color = this.getColor(slice.fFillColor); + + if (o.lblor === 1) { + // radial positioning of the labels + arg.rotate = Math.atan2(arg.y, arg.x) / Math.PI * 180; + if (arg.x > 0) + arg.align = 12; + else { + arg.align = 32; + arg.rotate += 180; + } + } else if (o.lblor === 2) { + // in the slice + arg.rotate = Math.atan2(entry.y2 - entry.y1, entry.x2 - entry.x1) / Math.PI * 180; + if ((arg.rotate > 90) || (arg.rotate < -90)) { + arg.rotate += 180; + arg.align = 21; + } else + arg.align = 23; + } else if ((arg.x >= 0) && (arg.y >= 0)) { + arg.align = 13; + if (o.is3d) + arg.y += pixelHeight; + } else if ((arg.x > 0) && (arg.y < 0)) + arg.align = 11; + else if ((arg.x < 0) && (arg.y >= 0)) { + arg.align = 33; + if (o.is3d) + arg.y += pixelHeight; + } else if ((arg.x < 0) && (arg.y < 0)) + arg.align = 31; + + this.drawText(arg); + } + return this.finishTextDrawing(maing); + }).then(() => { + this.#rx = rx; + this.#ry = ry; + this.#slices = arr; + return this; + }); + } + + /** @summary Draw TPie title */ + async drawTitle(first_time) { + return drawObjectTitle(this, first_time, !this.options.same, true); + } + + /** @summary Redraw TPie object */ + async redraw() { + return this.drawPie().then(() => this.drawTitle()).then(() => { + assignContextMenu(this); + addMoveHandler(this); + return this; + }); + } + + /** @summary Fill specific items */ + fillContextMenuItems(menu) { + const pie = this.getObject(); + menu.add('Change title', () => menu.input('Enter new title', pie.fTitle).then(t => { + pie.fTitle = t; + this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); + })); + menu.add('Angular offset', () => menu.input('Enter new angular offset', pie.fAngularOffset, 'float').then(v => { + pie.fAngularOffset = v; + this.interactiveRedraw('pad', `exec:SetAngularOffset(${v})`); + })); + if (this.options.is3d) { + menu.add('Angle 3D', () => menu.input('Enter new angle 3D', pie.fAngle3D, 'float', 0, 90).then(v => { + pie.fAngle3D = v; + this.interactiveRedraw('pad', `exec:SetAngle3D(${v})`); + })); + } + + if (!menu.getEventPosition()) + return; + + const svg = this.getPadPainter()?.getPadSvg(), + rect = svg.node().getBoundingClientRect(), + x = menu.getEventPosition().clientX - rect.left - svg.node().clientLeft, + y = menu.getEventPosition().clientY - rect.top - svg.node().clientTop, + elem = this.#findDrawnSlice(x - this.#cx, y - this.#cy); + if (!elem) + return; + + menu.sub(`Slice${elem.n}`); + + menu.add('Title', () => menu.input('Enter new title', elem.slice.fTitle).then(t => { + elem.slice.fTitle = t; + this.interactiveRedraw('pad', `exec:SetEntryLabel(${elem.n},"${t}")`); + })); + menu.add('Offset', () => menu.input('Enter new slice offset', elem.slice.fRadiusOffset, 'float', 0, 1).then(v => { + elem.slice.fRadiusOffset = v; + this.interactiveRedraw('pad', `exec:SetEntryRadiusOffset(${elem.n},${v})`); + })); + + menu.sub('Line att'); + menu.addSizeMenu('width', 1, 10, 1, elem.attline.width, arg => { + elem.slice.fLineWidth = arg; + this.interactiveRedraw('pad', `exec:SetEntryLineWidth(${elem.n},${arg})`); + }); + if (!elem.attline.nocolor) { + menu.addColorMenu('color', elem.attline.color, arg => { + elem.slice.fLineColor = getColorId(arg).id; + this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryLineColor', elem.n)); + }); + } + menu.addLineStyleMenu('style', elem.attline.style, id => { + elem.slice.fLineStyle = id; + this.interactiveRedraw('pad', `exec:SetEntryLineStyle(${elem.n},${id})`); + }); + menu.endsub(); + + menu.sub('Fill att'); + menu.addColorMenu('color', elem.attfill.colorindx, arg => { + elem.slice.fFillColor = getColorId(arg).id; + this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryFillColor', elem.n)); + }, elem.attfill.kind); + menu.addFillStyleMenu('style', elem.attfill.pattern, elem.attfill.colorindx, id => { + elem.slice.fFillStyle = id; + this.interactiveRedraw('pad', `exec:SetEntryFillStyle(${elem.n},${id})`); + }); + menu.endsub(); + + menu.endsub(); + } + + /** @summary Draw TPie object */ + static async draw(dom, obj, opt) { + const painter = new TPiePainter(dom, obj, opt); + painter.decodeOptions(opt); + return ensureTCanvas(painter, false) + .then(() => painter.drawPie()) + .then(() => painter.drawTitle(true)) + .then(() => { + assignContextMenu(painter); + addMoveHandler(painter); + return painter; + }); + } + +} // class TPiePainter + + +export { TPiePainter }; diff --git a/modules/draw/TPolyLinePainter.mjs b/modules/draw/TPolyLinePainter.mjs new file mode 100644 index 000000000..5c05009de --- /dev/null +++ b/modules/draw/TPolyLinePainter.mjs @@ -0,0 +1,108 @@ +import { BIT, isStr, clTPolyLine } from '../core.mjs'; +import { makeTranslate } from '../base/BasePainter.mjs'; +import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { addMoveHandler } from '../gui/utils.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; + + +const kPolyLineNDC = BIT(14); + +/** + * @summary Painter for TPolyLine class + * @private + */ + +class TPolyLinePainter extends ObjectPainter { + + #dx; // interactive change + #dy; // interactive change + #isndc; // if NDC coordinates used + + /** @summary Dragging object + * @private */ + moveDrag(dx, dy) { + this.#dx += dx; + this.#dy += dy; + makeTranslate(this.getG().select('path'), this.#dx, this.#dy); + } + + /** @summary End dragging object + * @private */ + moveEnd(not_changed) { + if (not_changed) + return; + const polyline = this.getObject(), + func = this.getAxisToSvgFunc(this.#isndc); + let exec = ''; + + for (let n = 0; n <= polyline.fLastPoint; ++n) { + const x = this.svgToAxis('x', func.x(polyline.fX[n]) + this.#dx, this.#isndc), + y = this.svgToAxis('y', func.y(polyline.fY[n]) + this.#dy, this.#isndc); + polyline.fX[n] = x; + polyline.fY[n] = y; + exec += `SetPoint(${n},${x},${y});;`; + } + this.submitCanvExec(exec + 'Notify();;'); + this.redraw(); + } + + /** @summary Returns object ranges + * @desc Can be used for newly created canvas */ + getUserRanges() { + const polyline = this.getObject(), + isndc = polyline.TestBit(kPolyLineNDC); + if (isndc || !polyline.fLastPoint) + return null; + let minx = polyline.fX[0], maxx = minx, + miny = polyline.fY[0], maxy = miny; + for (let n = 1; n <= polyline.fLastPoint; ++n) { + minx = Math.min(minx, polyline.fX[n]); + maxx = Math.max(maxx, polyline.fX[n]); + miny = Math.min(miny, polyline.fY[n]); + maxy = Math.max(maxy, polyline.fY[n]); + } + return { minx, miny, maxx, maxy }; + } + + /** @summary Redraw poly line */ + redraw() { + const g = this.createG(), + polyline = this.getObject(), + isndc = polyline.TestBit(kPolyLineNDC), + opt = this.getDrawOpt() || polyline.fOption, + dofill = (polyline._typename === clTPolyLine) && (isStr(opt) && opt.toLowerCase().indexOf('f') >= 0), + func = this.getAxisToSvgFunc(isndc); + + this.createAttLine({ attr: polyline }); + this.createAttFill({ attr: polyline, enable: dofill }); + + let cmd = ''; + for (let n = 0; n <= polyline.fLastPoint; ++n) + cmd += `${n > 0 ? 'L' : 'M'}${func.x(polyline.fX[n])},${func.y(polyline.fY[n])}`; + + g.append('svg:path') + .attr('d', cmd + (dofill ? 'Z' : '')) + .call(dofill ? () => {} : this.lineatt.func) + .call(this.fillatt.func); + + assignContextMenu(this); + + addMoveHandler(this); + + this.#dx = this.#dy = 0; + this.#isndc = isndc; + + return this; + } + + /** @summary Draw TPolyLine object */ + static async draw(dom, obj, opt) { + const painter = new TPolyLinePainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } + +} // class TPolyLinePainter + + +export { TPolyLinePainter }; diff --git a/modules/draw/TPolyMarker3D.mjs b/modules/draw/TPolyMarker3D.mjs index 295eb24a8..0090fc29e 100644 --- a/modules/draw/TPolyMarker3D.mjs +++ b/modules/draw/TPolyMarker3D.mjs @@ -16,34 +16,35 @@ async function drawPolyMarker3D() { for (let i = 0; i < fP.length; i += 3) { if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i+1] < fp.scale_ymin) || (fP[i+1] > fp.scale_ymax) || - (fP[i+2] < fp.scale_zmin) || (fP[i+2] > fp.scale_zmax)) continue; + (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || + (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) + continue; ++numselect; } - if ((settings.OptimizeDraw > 0) && (numselect > sizelimit)) { - step = Math.floor(numselect/sizelimit); - if (step <= 2) step = 2; - } + if ((settings.OptimizeDraw > 0) && (numselect > sizelimit)) + step = Math.max(2, Math.floor(numselect / sizelimit)); - const size = Math.floor(numselect/step), - pnts = new PointsCreator(size, fp.webgl, fp.size_x3d/100), + const size = Math.floor(numselect / step), + pnts = new PointsCreator(size, fp.webgl, fp.size_x3d / 100), index = new Int32Array(size); let select = 0, icnt = 0; for (let i = 0; i < fP.length; i += 3) { if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i+1] < fp.scale_ymin) || (fP[i+1] > fp.scale_ymax) || - (fP[i+2] < fp.scale_zmin) || (fP[i+2] > fp.scale_zmax)) continue; + (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || + (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) + continue; if (step > 1) { - select = (select+1) % step; - if (select !== 0) continue; + select = (select + 1) % step; + if (select) + continue; } index[icnt++] = i; - pnts.addPoint(fp.grx(fP[i]), fp.gry(fP[i+1]), fp.grz(fP[i+2])); + pnts.addPoint(fp.grx(fP[i]), fp.gry(fP[i + 1]), fp.grz(fP[i + 2])); } return pnts.createPoints({ color: this.getColor(poly.fMarkerColor), style: poly.fMarkerStyle }).then(mesh => { @@ -51,21 +52,22 @@ async function drawPolyMarker3D() { mesh.tip_name = poly.fName || 'Poly3D'; mesh.poly = poly; mesh.fp = fp; - mesh.scale0 = 0.7*pnts.scale; + mesh.scale0 = 0.7 * pnts.scale; mesh.index = index; fp.add3DMesh(mesh, this, true); mesh.tooltip = function(intersect) { let indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.index.length)) return null; + if ((indx < 0) || (indx >= this.index.length)) + return null; indx = this.index[indx]; - const fp = this.fp, - grx = fp.grx(this.poly.fP[indx]), - gry = fp.gry(this.poly.fP[indx+1]), - grz = fp.grz(this.poly.fP[indx+2]); + const fp2 = this.fp, + grx = fp2.grx(this.poly.fP[indx]), + gry = fp2.gry(this.poly.fP[indx + 1]), + grz = fp2.grz(this.poly.fP[indx + 2]); return { x1: grx - this.scale0, @@ -75,12 +77,13 @@ async function drawPolyMarker3D() { z1: grz - this.scale0, z2: grz + this.scale0, color: this.tip_color, - lines: [this.tip_name, - 'pnt: ' + indx/3, - 'x: ' + fp.axisAsText('x', this.poly.fP[indx]), - 'y: ' + fp.axisAsText('y', this.poly.fP[indx+1]), - 'z: ' + fp.axisAsText('z', this.poly.fP[indx+2]) - ] + lines: [ + this.tip_name, + 'pnt: ' + indx / 3, + 'x: ' + fp2.axisAsText('x', this.poly.fP[indx]), + 'y: ' + fp2.axisAsText('y', this.poly.fP[indx + 1]), + 'z: ' + fp2.axisAsText('z', this.poly.fP[indx + 2]) + ] }; }; diff --git a/modules/draw/TRatioPlotPainter.mjs b/modules/draw/TRatioPlotPainter.mjs index bb5dc1de4..a14047de5 100644 --- a/modules/draw/TRatioPlotPainter.mjs +++ b/modules/draw/TRatioPlotPainter.mjs @@ -4,6 +4,9 @@ import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { TLinePainter } from './TLinePainter.mjs'; +const k_upper_pad = 'upper_pad', k_lower_pad = 'lower_pad', k_top_pad = 'top_pad'; + + /** * @summary Painter class for TRatioPlot * @@ -12,12 +15,15 @@ import { TLinePainter } from './TLinePainter.mjs'; class TRatioPlotPainter extends ObjectPainter { + #oldratio; // old ratio plot + /** @summary Set grids range */ setGridsRange(xmin, xmax, ymin, ymax, low_p) { const ratio = this.getObject(); if (xmin === xmax) { - const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad)?.getFramePainter()?.x_handle; - if (!x_handle) return; + const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad)?.getFramePainter()?.x_handle; + if (!x_handle) + return; if (xmin === 0) { // in case of unzoom full range should be used xmin = x_handle.full_min; @@ -49,9 +55,9 @@ class TRatioPlotPainter extends ObjectPainter { configureInteractive() { const ratio = this.getObject(), pp = this.getPadPainter(), - up_p = pp.findPainterFor(ratio.fUpperPad, 'upper_pad', clTPad), + up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), + low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), low_fp = low_p?.getFramePainter(); if (!up_p || !low_p) @@ -98,8 +104,8 @@ class TRatioPlotPainter extends ObjectPainter { xmin = up_fp.xmin; xmax = up_fp.xmax; } else { - if (xmin < up_fp.xmin) xmin = up_fp.xmin; - if (xmax > up_fp.xmax) xmax = up_fp.xmax; + xmin = Math.min(xmin, up_fp.xmin); + xmax = Math.max(xmax, up_fp.xmax); } this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax); return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax)); @@ -118,7 +124,7 @@ class TRatioPlotPainter extends ObjectPainter { async redrawOld() { const ratio = this.getObject(), pp = this.getPadPainter(), - top_p = pp.findPainterFor(ratio.fTopPad, 'top_pad', clTPad), + top_p = pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad), pad = pp.getRootPad(), mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, tick_x = pad.fTickx || mirrow_axis, @@ -126,10 +132,10 @@ class TRatioPlotPainter extends ObjectPainter { top_p?.disablePadDrawing(); - const up_p = pp.findPainterFor(ratio.fUpperPad, 'upper_pad', clTPad), + const up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), up_main = up_p?.getMainPainter(), up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), + low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), low_main = low_p?.getMainPainter(), low_fp = low_p?.getFramePainter(); let promise_up = Promise.resolve(true); @@ -166,14 +172,14 @@ class TRatioPlotPainter extends ObjectPainter { low_p.getRootPad().fTicky = tick_y; const arr = []; - let currpad; // add missing lines in old ratio painter - if ((ratio.fGridlinePositions.length > 0) && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) { + if (ratio.fGridlinePositions.length && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) { ratio.fGridlinePositions.forEach(gridy => { let found = false; ratio.fGridlines.forEach(line => { - if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) found = true; + if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) + found = true; }); if (!found) { const line = create(clTLine); @@ -182,18 +188,15 @@ class TRatioPlotPainter extends ObjectPainter { line.fY1 = line.fY2 = gridy; line.fLineStyle = 2; ratio.fGridlines.push(line); - if (currpad === undefined) - currpad = this.selectCurrentPad(ratio.fLowerPad.fName); - arr.push(TLinePainter.draw(this.getDom(), line)); + arr.push(TLinePainter.draw(low_p, line)); } }); } - return Promise.all(arr).then(() => { - if (currpad !== undefined) - this.selectCurrentPad(currpad); - return low_fp.zoom(up_fp.scale_xmin, up_fp.scale_xmax); - }); + return Promise.all(arr) + .then(() => low_fp.zoomSingle('x', up_fp.scale_xmin, up_fp.scale_xmax)) + .then(changed => { return changed ? true : low_p.redrawPad(); }) + .then(() => this); }); } @@ -202,13 +205,13 @@ class TRatioPlotPainter extends ObjectPainter { const ratio = this.getObject(), pp = this.getPadPainter(); - if (this.$oldratio === undefined) - this.$oldratio = !!pp.findPainterFor(ratio.fTopPad, 'top_pad', clTPad); + if (this.#oldratio === undefined) + this.#oldratio = Boolean(pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad)); // configure ratio interactive at the end pp.$userInteractive = () => this.configureInteractive(); - if (this.$oldratio) + if (this.#oldratio) return this.redrawOld(); const pad = pp.getRootPad(), @@ -233,7 +236,6 @@ class TRatioPlotPainter extends ObjectPainter { /** @summary Draw TRatioPlot */ static async draw(dom, ratio, opt) { const painter = new TRatioPlotPainter(dom, ratio, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); } diff --git a/modules/draw/TSplinePainter.mjs b/modules/draw/TSplinePainter.mjs index 6cd0db342..48ac3353e 100644 --- a/modules/draw/TSplinePainter.mjs +++ b/modules/draw/TSplinePainter.mjs @@ -5,23 +5,28 @@ import { TH1Painter } from '../hist/TH1Painter.mjs'; /** - * @summary Painter for TSpline objects. + * @summary Painter for TSpline classes. * * @private */ class TSplinePainter extends ObjectPainter { + #knot_size; // graphical size of each knot + /** @summary Update TSpline object * @private */ updateObject(obj, opt) { const spline = this.getObject(); - if (spline._typename !== obj._typename) return false; + if (spline._typename !== obj._typename) + return false; - if (spline !== obj) Object.assign(spline, obj); + if (spline !== obj) + Object.assign(spline, obj); - if (opt !== undefined) this.decodeOptions(opt); + if (opt !== undefined) + this.decodeOptions(opt); return true; } @@ -32,10 +37,10 @@ class TSplinePainter extends ObjectPainter { const dx = x - knot.fX; if (knot._typename === 'TSplinePoly3') - return knot.fY + dx*(knot.fB + dx*(knot.fC + dx*knot.fD)); + return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * knot.fD)); if (knot._typename === 'TSplinePoly5') - return knot.fY + dx*(knot.fB + dx*(knot.fC + dx*(knot.fD + dx*(knot.fE + dx*knot.fF)))); + return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * (knot.fD + dx * (knot.fE + dx * knot.fF)))); return knot.fY + dx; } @@ -46,23 +51,27 @@ class TSplinePainter extends ObjectPainter { const spline = this.getObject(); let klow = 0, khig = spline.fNp - 1; - if (x <= spline.fXmin) return 0; - if (x >= spline.fXmax) return khig; + if (x <= spline.fXmin) + return klow; + if (x >= spline.fXmax) + return khig; if (spline.fKstep) { - // Equidistant knots, use histogramming - klow = Math.round((x - spline.fXmin)/spline.fDelta); + // Equidistant knots, use histogram + klow = Math.round((x - spline.fXmin) / spline.fDelta); // Correction for rounding errors if (x < spline.fPoly[klow].fX) - klow = Math.max(klow-1, 0); - else if (klow < khig) - if (x > spline.fPoly[klow+1].fX) ++klow; + klow = Math.max(klow - 1, 0); + else if ((klow < khig) && (x > spline.fPoly[klow + 1].fX)) + ++klow; } else { // Non equidistant knots, binary search while (khig - klow > 1) { - const khalf = Math.round((klow + khig)/2); - if (x > spline.fPoly[khalf].fX) klow = khalf; - else khig = khalf; + const khalf = Math.round((klow + khig) / 2); + if (x > spline.fPoly[khalf].fX) + klow = khalf; + else + khig = khalf; } } return klow; @@ -85,8 +94,10 @@ class TSplinePainter extends ObjectPainter { ymax = Math.max(knot.fY, ymax); }); - if (ymax > 0) ymax *= (1 + gStyle.fHistTopMargin); - if (ymin < 0) ymin *= (1 + gStyle.fHistTopMargin); + if (ymax > 0) + ymax *= (1 + gStyle.fHistTopMargin); + if (ymin < 0) + ymin *= (1 + gStyle.fHistTopMargin); } const histo = createHistogram(clTH1I, 10); @@ -99,6 +110,8 @@ class TSplinePainter extends ObjectPainter { histo.fXaxis.fXmax = xmax; histo.fYaxis.fXmin = ymin; histo.fYaxis.fXmax = ymax; + histo.fMinimum = ymin; + histo.fMaximum = ymax; return histo; } @@ -107,37 +120,41 @@ class TSplinePainter extends ObjectPainter { * @private */ processTooltipEvent(pnt) { const spline = this.getObject(), - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); let cleanup = false, xx, yy, knot = null, indx = 0; if ((pnt === null) || !spline || !funcs) cleanup = true; - else { + else { xx = funcs.revertAxis('x', pnt.x); indx = this.findX(xx); knot = spline.fPoly[indx]; yy = this.eval(knot, xx); - if ((indx < spline.fN-1) && (Math.abs(spline.fPoly[indx+1].fX-xx) < Math.abs(xx-knot.fX))) knot = spline.fPoly[++indx]; + if ((indx < spline.fN - 1) && (Math.abs(spline.fPoly[indx + 1].fX - xx) < Math.abs(xx - knot.fX))) + knot = spline.fPoly[++indx]; - if (Math.abs(funcs.grx(knot.fX) - pnt.x) < 0.5*this.knot_size) { - xx = knot.fX; yy = knot.fY; + if (Math.abs(funcs.grx(knot.fX) - pnt.x) < 0.5 * this.#knot_size) { + xx = knot.fX; + yy = knot.fY; } else { knot = null; - if ((xx < spline.fXmin) || (xx > spline.fXmax)) cleanup = true; + if ((xx < spline.fXmin) || (xx > spline.fXmax)) + cleanup = true; } } - let gbin = this.draw_g?.selectChild('.tooltip_bin'); + let gbin = this.getG()?.selectChild('.tooltip_bin'); const radius = this.lineatt.width + 3; - if (cleanup || !this.draw_g) { + if (cleanup || !this.getG()) { gbin?.remove(); return null; } if (gbin.empty()) { - gbin = this.draw_g.append('svg:circle') + gbin = this.getG().append('svg:circle') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .attr('r', radius) @@ -145,17 +162,19 @@ class TSplinePainter extends ObjectPainter { .call(this.lineatt.func); } - const res = { name: this.getObject().fName, - title: this.getObject().fTitle, - x: funcs.grx(xx), - y: funcs.gry(yy), - color1: this.lineatt.color, - lines: [], - exact: (knot !== null) || (Math.abs(funcs.gry(yy) - pnt.y) < radius) }; + const res = { + name: this.getObject().fName, + title: this.getObject().fTitle, + x: funcs.grx(xx), + y: funcs.gry(yy), + color1: this.lineatt.color, + lines: [], + exact: (knot !== null) || (Math.abs(funcs.gry(yy) - pnt.y) < radius) + }; res.changed = gbin.property('current_xx') !== xx; res.menu = res.exact; - res.menu_dist = Math.sqrt((res.x-pnt.x)**2 + (res.y-pnt.y)**2); + res.menu_dist = Math.sqrt((res.x - pnt.x) ** 2 + (res.y - pnt.y) ** 2); if (res.changed) { gbin.attr('cx', Math.round(res.x)) @@ -164,7 +183,8 @@ class TSplinePainter extends ObjectPainter { } const name = this.getObjectHint(); - if (name) res.lines.push(name); + if (name) + res.lines.push(name); res.lines.push(`x = ${funcs.axisAsText('x', xx)}`, `y = ${funcs.axisAsText('y', yy)}`); if (knot !== null) { @@ -185,105 +205,106 @@ class TSplinePainter extends ObjectPainter { * @private */ redraw() { const spline = this.getObject(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - w = pmain.getFrameWidth(), - h = pmain.getFrameHeight(); - - this.createG(true); + o = this.getOptions(), + funcs = this.getFramePainter().getGrFuncs(o.second_x, o.second_y), + w = funcs.getFrameWidth(), + h = funcs.getFrameHeight(), + g = this.createG(true); - this.knot_size = 5; // used in tooltip handling + this.#knot_size = 5; // used in tooltip handling this.createAttLine({ attr: spline }); - if (this.options.Line || this.options.Curve) { + if (o.Line || o.Curve) { const npx = Math.max(10, spline.fNpx), bins = []; // index of current knot - let xmin = Math.max(pmain.scale_xmin, spline.fXmin), - xmax = Math.min(pmain.scale_xmax, spline.fXmax), + let xmin = Math.max(funcs.scale_xmin, spline.fXmin), + xmax = Math.min(funcs.scale_xmax, spline.fXmax), indx = this.findX(xmin); - if (pmain.logx) { + if (funcs.logx) { xmin = Math.log(xmin); xmax = Math.log(xmax); } for (let n = 0; n < npx; ++n) { - let x = xmin + (xmax-xmin)/npx*(n-1); - if (pmain.logx) x = Math.exp(x); + let x = xmin + (xmax - xmin) / npx * (n - 1); + if (funcs.logx) + x = Math.exp(x); - while ((indx < spline.fNp-1) && (x > spline.fPoly[indx+1].fX)) ++indx; + while ((indx < spline.fNp - 1) && (x > spline.fPoly[indx + 1].fX)) + ++indx; const y = this.eval(spline.fPoly[indx], x); bins.push({ x, y, grx: funcs.grx(x), gry: funcs.gry(y) }); } - this.draw_g.append('svg:path') - .attr('class', 'line') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .call(this.lineatt.func); + g.append('svg:path') + .attr('class', 'line') + .attr('d', buildSvgCurve(bins)) + .style('fill', 'none') + .call(this.lineatt.func); } - if (this.options.Mark) { + if (o.Mark) { // for tooltips use markers only if nodes where not created - let path = ''; this.createAttMarker({ attr: spline }); this.markeratt.resetPos(); - this.knot_size = this.markeratt.getFullSize(); + this.#knot_size = this.markeratt.getFullSize(); + + let path = ''; for (let n = 0; n < spline.fPoly.length; n++) { const knot = spline.fPoly[n], - grx = funcs.grx(knot.fX); - if ((grx > -this.knot_size) && (grx < w + this.knot_size)) { + grx = funcs.grx(knot.fX); + if ((grx > -this.#knot_size) && (grx < w + this.#knot_size)) { const gry = funcs.gry(knot.fY); - if ((gry > -this.knot_size) && (gry < h + this.knot_size)) + if ((gry > -this.#knot_size) && (gry < h + this.#knot_size)) path += this.markeratt.create(grx, gry); } } if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); } } } /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis /* , min, max */) { - if (axis !== 'x') return false; - // spline can always be calculated and therefore one can zoom inside - return !!this.getObject(); + return (axis !== 'x') ? false : Boolean(this.getObject()); } /** @summary Decode options for TSpline drawing */ decodeOptions(opt) { - const d = new DrawOptions(opt); - - if (!this.options) this.options = {}; - - const has_main = !!this.getMainPainter(); - - Object.assign(this.options, { - Same: d.check('SAME'), - Line: d.check('L'), - Curve: d.check('C'), - Mark: d.check('P'), - Hopt: '', - second_x: false, - second_y: false - }); - - if (!this.options.Line && !this.options.Curve && !this.options.Mark) - this.options.Curve = true; - - if (d.check('X+')) { this.options.Hopt += 'X+'; this.options.second_x = has_main; } - if (d.check('Y+')) { this.options.Hopt += 'Y+'; this.options.second_y = has_main; } + const d = new DrawOptions(opt), + o = this.setOptions({ + Same: d.check('SAME'), + Line: d.check('L'), + Curve: d.check('C'), + Mark: d.check('P'), + Hopt: '', + second_x: false, + second_y: false + }); + + if (!o.Line && !o.Curve && !o.Mark) + o.Curve = true; + + if (d.check('X+')) { + o.Hopt += 'X+'; + o.second_x = Boolean(this.getMainPainter()); + } + if (d.check('Y+')) { + o.Hopt += 'Y+'; + o.second_y = Boolean(this.getMainPainter()); + } this.storeDrawOpt(opt); } diff --git a/modules/draw/TTextPainter.mjs b/modules/draw/TTextPainter.mjs new file mode 100644 index 000000000..4a9574f1d --- /dev/null +++ b/modules/draw/TTextPainter.mjs @@ -0,0 +1,133 @@ +import { clTLink, clTLatex, clTMathText, BIT } from '../core.mjs'; +import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { makeTranslate, DrawOptions } from '../base/BasePainter.mjs'; +import { addMoveHandler } from '../gui/utils.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; + + +const kTextNDC = BIT(14); + +class TTextPainter extends ObjectPainter { + + async _redrawText(x, y, annot) { + const text = this.getObject(), + pp = this.getPadPainter(), + fp = this.getFramePainter(), + is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://'); + + // special handling of dummy frame painter + if (fp?.getDrawDom() === null) + return this; + + let fact = 1, use_frame = false; + + this.createAttText({ attr: text }); + + if ((annot === '3d') || text.TestBit(kTextNDC)) + this.isndc = true; + else if (!annot && pp?.getRootPad(true)) { + // force pad coordinates + const d = new DrawOptions(this.getDrawOpt()); + use_frame = d.check('FRAME'); + } else if (!annot) { + // place in the middle + this.isndc = true; + x = y = 0.5; + text.fTextAlign = 22; + } + + const g = this.createG(use_frame ? 'frame2d' : undefined, is_url); + + g.attr('transform', null); // remove transform from interactive changes + + x = this.axisToSvg('x', x, this.isndc); + y = this.axisToSvg('y', y, this.isndc); + this.swap_xy = use_frame && fp?.swap_xy(); + + if (this.swap_xy) + [x, y] = [y, x]; + + const arg = this.textatt.createArg({ x, y, text: text.fTitle, latex: 0 }); + + if (this.matchObjectType(clTLatex) || annot) + arg.latex = 1; + else if (this.matchObjectType(clTMathText)) { + arg.latex = 2; + fact = 0.8; + } + + if (is_url) { + g.attr('href', text.fName); + if (!this.isBatchMode()) + g.append('svg:title').text(`link on ${text.fName}`); + } + + return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp, fact)) + .then(() => this.drawText(arg)) + .then(() => this.finishTextDrawing()) + .then(() => { + if (this.isBatchMode()) + return this; + + if (pp.isButton() && !pp.isEditable()) { + g.on('click', () => this.getCanvPainter().selectActivePad(pp)); + return this; + } + + Object.assign(this, { pos_x: x, pos_y: y, pos_dx: 0, pos_dy: 0 }); + + if (annot !== '3d') + addMoveHandler(this, true, is_url); + + assignContextMenu(this); + + if (this.matchObjectType(clTLink)) + g.style('cursor', 'pointer').on('click', () => this.submitCanvExec('ExecuteEvent(kButton1Up, 0, 0);;')); + + return this; + }); + } + + async redraw() { + return this._redrawText(this.getObject().fX, this.getObject().fY); + } + + moveDrag(dx, dy) { + this.pos_dx += dx; + this.pos_dy += dy; + makeTranslate(this.getG(), this.pos_dx, this.pos_dy); + } + + moveEnd(not_changed) { + if (not_changed) + return; + const txt = this.getObject(); + let fx = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc), + fy = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc); + if (this.swap_xy) + [fx, fy] = [fy, fx]; + + txt.fX = fx; + txt.fY = fy; + this.submitCanvExec(`SetX(${fx});;SetY(${fy});;`); + } + + fillContextMenuItems(menu) { + const text = this.getObject(); + menu.add('Change text', () => menu.input('Enter new text', text.fTitle).then(t => { + text.fTitle = t; + this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); + })); + } + + /** @summary draw TText-derived object */ + static async draw(dom, obj, opt) { + const painter = new TTextPainter(dom, obj, opt); + return ensureTCanvas(painter, false).then(() => painter.redraw()); + } + +} // class TTextPainter + + +export { TTextPainter }; diff --git a/modules/draw/TTree.mjs b/modules/draw/TTree.mjs index 408ca4e5b..3716e10b2 100644 --- a/modules/draw/TTree.mjs +++ b/modules/draw/TTree.mjs @@ -1,5 +1,5 @@ -import { internals, httpRequest, isBatchMode, isFunc, isStr, create, toJSON, - prROOT, clTObjString, clTGraph, clTPolyMarker3D, clTH1, clTH2, clTH3 } from '../core.mjs'; +import { internals, httpRequest, isBatchMode, isFunc, isStr, create, toJSON, getPromise, + getKindForType, clTObjString, clTGraph, clTPolyMarker3D, clTH1, clTH2, clTH3 } from '../core.mjs'; import { select as d3_select } from '../d3.mjs'; import { kTString, kObject, kAnyP } from '../io.mjs'; import { kClonesNode, kSTLNode, clTBranchFunc, treeDraw, treeIOTest, TDrawSelector } from '../tree.mjs'; @@ -25,7 +25,8 @@ TDrawSelector.prototype.ShowProgress = function(value) { } else { if (this.last_progress !== value) { const diff = value - this.last_progress; - if (!this.aver_diff) this.aver_diff = diff; + if (!this.aver_diff) + this.aver_diff = diff; this.aver_diff = diff * 0.3 + this.aver_diff * 0.7; } @@ -43,7 +44,7 @@ TDrawSelector.prototype.ShowProgress = function(value) { msg = `TTree draw ${(value * 100).toFixed(ndig)} % `; } - showProgress(msg, -1, () => { this._break = 1; }); + showProgress(msg, 0, () => { this._break = 1; }); return ret; }; @@ -55,6 +56,12 @@ async function drawTreeDrawResult(dom, obj, opt) { if (!typ || !isStr(typ)) return Promise.reject(Error('Object without type cannot be draw with TTree')); + if (internals._alt_draw) { + const v = internals._alt_draw(dom, obj, opt); + if (v) + return v; + } + if (typ.indexOf(clTH1) === 0) return TH1Painter.draw(dom, obj, opt); if (typ.indexOf(clTH2) === 0) @@ -83,8 +90,9 @@ async function treeDrawProgress(obj, final) { if (!final && !this.last_pr) return; - if (this.dump || this.testio) { - if (!final) return; + if (this.dump || this.dump_entries || this.testio) { + if (!final) + return; if (isBatchMode()) { const painter = new BasePainter(this.drawid); painter.selectDom().property('_json_object_', obj); @@ -101,20 +109,23 @@ async function treeDrawProgress(obj, final) { // while TTree reading not synchronized with drawing, // next portion can appear before previous is drawn // critical is last drawing which should wait for previous one - // therefore last_pr is kept as inidication that promise is not yet processed + // therefore last_pr is kept as indication that promise is not yet processed - if (!this.last_pr) this.last_pr = Promise.resolve(true); + if (!this.last_pr) + this.last_pr = Promise.resolve(true); return this.last_pr.then(() => { if (this.obj_painter) - this.last_pr = this.obj_painter.redrawObject(obj).then(() => this.obj_painter); + this.last_pr = getPromise(this.obj_painter.redrawObject(obj)).then(() => this.obj_painter); else if (!obj) { - if (final) console.log('no result after tree drawing'); + if (final) + console.log('no result after tree drawing'); this.last_pr = false; // return false indicating no drawing is done } else { - this.last_pr = drawTreeDrawResult(this.drawid, obj).then(p => { + this.last_pr = drawTreeDrawResult(this.drawid, obj, this.drawopt).then(p => { this.obj_painter = p; - if (!final) this.last_pr = null; + if (!final) + this.last_pr = null; return p; // return painter for histogram }); } @@ -143,15 +154,15 @@ function createTreePlayer(player) { player.showExtraButtons = function(args) { const main = this.selectDom(), - numentries = this.local_tree?.fEntries || 0; + numentries = this.local_tree?.fEntries || 0; main.select('.treedraw_more').remove(); // remove more button first main.select('.treedraw_buttons').node().innerHTML += - 'Cut: <input class="treedraw_cut ui-corner-all ui-widget" style="width:8em;margin-left:5px" title="cut expression"></input>'+ - 'Opt: <input class="treedraw_opt ui-corner-all ui-widget" style="width:5em;margin-left:5px" title="histogram draw options"></input>'+ - `Num: <input class="treedraw_number" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="number of entries to process (default all)"></input>`+ - `First: <input class="treedraw_first" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="first entry to process (default first)"></input>`+ + 'Cut: <input class="treedraw_cut ui-corner-all ui-widget" style="width:8em;margin-left:5px" title="cut expression"></input>' + + 'Opt: <input class="treedraw_opt ui-corner-all ui-widget" style="width:5em;margin-left:5px" title="histogram draw options"></input>' + + `Num: <input class="treedraw_number" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="number of entries to process (default all)"></input>` + + `First: <input class="treedraw_first" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="first entry to process (default first)"></input>` + '<button class="treedraw_clear" title="Clear drawing">Clear</button>'; main.select('.treedraw_exe').on('click', () => this.performDraw()); @@ -169,10 +180,10 @@ function createTreePlayer(player) { const show_extra = args?.parse_cut || args?.numentries || args?.firstentry; - main.html('<div style="display:flex; flex-flow:column; height:100%; width:100%;">'+ + main.html('<div style="display:flex; flex-flow:column; height:100%; width:100%;">' + '<div class="treedraw_buttons" style="flex: 0 1 auto;margin-top:0.2em;">' + '<button class="treedraw_exe" title="Execute draw expression" style="margin-left:0.5em">Draw</button>' + - 'Expr:<input class="treedraw_varexp treedraw_varexp_info" style="width:12em;margin-left:5px" title="draw expression"></input>'+ + 'Expr:<input class="treedraw_varexp treedraw_varexp_info" style="width:12em;margin-left:5px" title="draw expression"></input>' + '<label class="treedraw_varexp_info">\u24D8</label>' + '<button class="treedraw_more">More</button>' + '</div>' + @@ -215,34 +226,41 @@ function createTreePlayer(player) { player.getValue = function(sel) { const elem = this.selectDom().select(sel); - if (elem.empty()) return; - const val = elem.property('value'); - if (val !== undefined) return val; - return elem.attr('value'); + if (elem.empty()) + return; + return elem.property('value') ?? elem.attr('value'); }; player.performLocalDraw = function() { - if (!this.local_tree) return; + if (!this.local_tree) + return; const frame = this.selectDom(), args = { expr: this.getValue('.treedraw_varexp') }; if (frame.select('.treedraw_more').empty()) { args.cut = this.getValue('.treedraw_cut'); - if (!args.cut) delete args.cut; + if (!args.cut) + delete args.cut; args.drawopt = this.getValue('.treedraw_opt'); - if (args.drawopt === 'dump') { args.dump = true; args.drawopt = ''; } - if (!args.drawopt) delete args.drawopt; + if (args.drawopt === 'dump') { + args.dump = true; + args.drawopt = ''; + } + if (!args.drawopt) + delete args.drawopt; args.numentries = parseInt(this.getValue('.treedraw_number')); - if (!Number.isInteger(args.numentries)) delete args.numentries; + if (!Number.isInteger(args.numentries)) + delete args.numentries; args.firstentry = parseInt(this.getValue('.treedraw_first')); - if (!Number.isInteger(args.firstentry)) delete args.firstentry; + if (!Number.isInteger(args.firstentry)) + delete args.firstentry; } - /* if (args.drawopt) */ cleanup(this.drawid); + cleanup(this.drawid); args.drawid = this.drawid; @@ -254,7 +272,8 @@ function createTreePlayer(player) { player.getDrawOpt = function() { let res = 'player'; const expr = this.getValue('.treedraw_varexp'); - if (expr) res += ':' + expr; + if (expr) + res += ':' + expr; return res; }; @@ -270,11 +289,13 @@ function createTreePlayer(player) { if (pos < 0) expr += `>>${hname}`; - else { - hname = expr.slice(pos+2); - if (hname[0] === '+') hname = hname.slice(1); + else { + hname = expr.slice(pos + 2); + if (hname[0] === '+') + hname = hname.slice(1); const pos2 = hname.indexOf('('); - if (pos2 > 0) hname = hname.slice(0, pos2); + if (pos2 > 0) + hname = hname.slice(0, pos2); } if (frame.select('.treedraw_more').empty()) { @@ -287,8 +308,10 @@ function createTreePlayer(player) { url += `&prototype="const char*,const char*,Option_t*,Long64_t,Long64_t"&varexp="${expr}"&selection="${cut}"`; // provide all optional arguments - default value kMaxEntries not works properly in ROOT6 - if (!nentries) nentries = 'TTree::kMaxEntries'; // kMaxEntries available since ROOT 6.05/03 - if (!firstentry) firstentry = '0'; + if (!nentries) + nentries = 'TTree::kMaxEntries'; // kMaxEntries available since ROOT 6.05/03 + if (!firstentry) + firstentry = '0'; url += `&option="${option}"&nentries=${nentries}&firstentry=${firstentry}`; } else url += `&prototype="Option_t*"&opt="${expr}"`; @@ -326,34 +349,40 @@ function drawTreePlayer(hpainter, itemname, askey, asleaf) { let item = hpainter.findItem(itemname), expr = '', leaf_cnt = 0; const top = hpainter.getTopOnlineItem(item); - if (!item || !top) return null; + if (!item || !top) + return null; if (asleaf) { expr = item._name; - while (item && !item._ttree) item = item._parent; - if (!item) return null; + while (item && !item._ttree) + item = item._parent; + if (!item) + return null; itemname = hpainter.itemFullName(item); } const url = hpainter.getOnlineItemUrl(itemname); - if (!url) return null; + if (!url) + return null; const root_version = top._root_version || 400129, // by default use version number 6-27-01 - - mdi = hpainter.getDisplay(); - if (!mdi) return null; + mdi = hpainter.getDisplay(); + if (!mdi) + return null; const frame = mdi.findFrame(itemname, true); - if (!frame) return null; + if (!frame) + return null; const divid = d3_select(frame).attr('id'), - player = new BasePainter(divid); + player = new BasePainter(divid); if (item._childs && !asleaf) { for (let n = 0; n < item._childs.length; ++n) { const leaf = item._childs[n]; - if (leaf && leaf._kind && (leaf._kind.indexOf(prROOT + 'TLeaf') === 0) && (leaf_cnt < 2)) { - if (leaf_cnt++ > 0) expr += ':'; + if (isStr(leaf?._kind) && (leaf._kind.indexOf(getKindForType('TLeaf')) === 0) && (leaf_cnt < 2)) { + if (leaf_cnt++ > 0) + expr += ':'; expr += leaf._name; } } @@ -408,13 +437,16 @@ async function drawTree(dom, obj, opt) { // if generic object tried to be drawn without specifying any options, it will be just dump if (!opt && obj.fStreamerType && (obj.fStreamerType !== kTString) && - (obj.fStreamerType >= kObject) && (obj.fStreamerType <= kAnyP)) opt = 'dump'; + (obj.fStreamerType >= kObject) && (obj.fStreamerType <= kAnyP)) + opt = 'dump'; args = { expr: opt, branch: obj }; tree = obj.$tree; } else { - if (!args) args = 'player'; - if (isStr(args)) args = { expr: args }; + if (!args) + args = 'player'; + if (isStr(args)) + args = { expr: args }; } if (!tree) @@ -425,11 +457,13 @@ async function drawTree(dom, obj, opt) { if (p === 0) { args.player = true; args.expr = args.expr.slice(6); - if (args.expr[0] === ':') args.expr = args.expr.slice(1); - } else if ((p >= 0) && (p === args.expr.length-6)) { + if (args.expr[0] === ':') + args.expr = args.expr.slice(1); + } else if ((p >= 0) && (p === args.expr.length - 6)) { args.player = true; args.expr = args.expr.slice(0, p); - if ((p > 0) && (args.expr[p-1] === ';')) args.expr = args.expr.slice(0, p-1); + if ((p > 0) && (args.expr[p - 1] === ';')) + args.expr = args.expr.slice(0, p - 1); } } @@ -456,10 +490,10 @@ async function drawTree(dom, obj, opt) { pr = treeIOTest(tree, args); } else if (args.expr || args.branch) pr = treeDraw(tree, args); - else + else return painter; return pr.then(res => args.progress(res, true)); } -export { drawTree, drawTreePlayer, drawTreePlayerKey, drawLeafPlayer }; +export { drawTree, drawTreePlayer, drawTreePlayerKey, drawLeafPlayer, treeDrawProgress }; diff --git a/modules/draw/TWebPaintingPainter.mjs b/modules/draw/TWebPaintingPainter.mjs index 997b49f3f..42a0bb0e8 100644 --- a/modules/draw/TWebPaintingPainter.mjs +++ b/modules/draw/TWebPaintingPainter.mjs @@ -1,56 +1,104 @@ import { getColor } from '../base/colors.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { pointer as d3_pointer } from '../d3.mjs'; +import { urlClassPrefix } from '../core.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; -/** @summary Draw direct TVirtualX commands into SVG - * @private */ +/** + * @summary Painter for TWebPainting classes. + * + * @private + */ class TWebPaintingPainter extends ObjectPainter { /** @summary Update TWebPainting object */ updateObject(obj) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; this.assignObject(obj); return true; } + /** @summary Provides menu header */ + getMenuHeader() { + return this.getObject()?.fClassName || 'TWebPainting'; + } + + /** @summary Fill context menu + * @desc Create only header, items will be requested from server */ + fillContextMenu(menu) { + const cl = this.getMenuHeader(); + menu.header(cl, `${urlClassPrefix}${cl}.html`); + return true; + } + + /** @summary Mouse click handler + * @desc Redirect mouse click events to the ROOT application + * @private */ + handleMouseClick(evnt) { + const pp = this.getPadPainter(), + rect = pp?.getPadRect(); + + if (pp && rect && this.getSnapId()) { + const pos = d3_pointer(evnt, this.getG().node()); + pp.selectObjectPainter(this, { x: pos[0] + rect.x, y: pos[1] + rect.y }); + } + } + /** @summary draw TWebPainting object */ async redraw() { const obj = this.getObject(), func = this.getAxisToSvgFunc(); - if (!obj?.fOper || !func) return; + if (!obj?.fOper || !func) + return this; let indx = 0, attr = {}, lastpath = null, lastkind = 'none', d = '', oper, npoints, n; - const arr = obj.fOper.split(';'), - check_attributes = kind => { - if (kind === lastkind) return; + /* eslint-disable one-var */ + const g = this.createG(), + arr = obj.fOper.split(';'); + const check_attributes = kind => { + if (kind === lastkind) + return; if (lastpath) { lastpath.attr('d', d); // flush previous - d = ''; lastpath = null; lastkind = 'none'; + d = ''; + lastpath = null; + lastkind = 'none'; } - if (!kind) return; + if (!kind) + return; lastkind = kind; - lastpath = this.draw_g.append('svg:path'); + lastpath = g.append('svg:path').attr('d', ''); // placeholder for 'd' to have it always in front switch (kind) { - case 'f': lastpath.call(this.fillatt.func); break; - case 'l': lastpath.call(this.lineatt.func).style('fill', 'none'); break; - case 'm': lastpath.call(this.markeratt.func); break; + case 'f': + lastpath.call(this.fillatt.func); + break; + case 'l': + lastpath.call(this.lineatt.func).style('fill', 'none'); + break; + case 'm': + lastpath.call(this.markeratt.func); + break; } - }, read_attr = (str, names) => { + }; + const read_attr = (str, names) => { let lastp = 0; - const obj = { _typename: 'any' }; + const obj2 = { _typename: 'any' }; for (let k = 0; k < names.length; ++k) { - const p = str.indexOf(':', lastp+1); - obj[names[k]] = parseInt(str.slice(lastp+1, (p > lastp) ? p : undefined)); + const p = str.indexOf(':', lastp + 1); + obj2[names[k]] = parseInt(str.slice(lastp + 1, p > lastp ? p : undefined)); lastp = p; } - return obj; - }, process = k => { + return obj2; + }; + const process = k => { while (++k < arr.length) { oper = arr[k][0]; switch (oper) { @@ -68,7 +116,8 @@ class TWebPaintingPainter extends ObjectPainter { continue; case 'o': attr = read_attr(arr[k], ['fTextColor', 'fTextFont', 'fTextSize', 'fTextAlign', 'fTextAngle']); - if (attr.fTextSize < 0) attr.fTextSize *= -0.001; + if (attr.fTextSize < 0) + attr.fTextSize *= -0.001; check_attributes(); continue; case 'r': @@ -76,12 +125,11 @@ class TWebPaintingPainter extends ObjectPainter { check_attributes((oper === 'b') ? 'f' : 'l'); const x1 = func.x(obj.fBuf[indx++]), - y1 = func.y(obj.fBuf[indx++]), - x2 = func.x(obj.fBuf[indx++]), - y2 = func.y(obj.fBuf[indx++]); - - d += `M${x1},${y1}h${x2-x1}v${y2-y1}h${x1-x2}z`; + y1 = func.y(obj.fBuf[indx++]), + x2 = func.x(obj.fBuf[indx++]), + y2 = func.y(obj.fBuf[indx++]); + d += `M${x1},${y1}h${x2 - x1}v${y2 - y1}h${x1 - x2}z`; continue; } case 'l': @@ -91,9 +139,10 @@ class TWebPaintingPainter extends ObjectPainter { npoints = parseInt(arr[k].slice(1)); for (n = 0; n < npoints; ++n) - d += `${(n>0)?'L':'M'}${func.x(obj.fBuf[indx++])},${func.y(obj.fBuf[indx++])}`; + d += `${(n > 0) ? 'L' : 'M'}${func.x(obj.fBuf[indx++])},${func.y(obj.fBuf[indx++])}`; - if (oper === 'f') d += 'Z'; + if (oper === 'f') + d += 'Z'; continue; } @@ -116,31 +165,34 @@ class TWebPaintingPainter extends ObjectPainter { check_attributes(); const height = (attr.fTextSize > 1) ? attr.fTextSize : this.getPadPainter().getPadHeight() * attr.fTextSize, - group = this.draw_g.append('svg:g'); - let angle = attr.fTextAngle, - txt = arr[k].slice(1); - - if (angle >= 360) angle -= Math.floor(angle/360) * 360; - - this.startTextDrawing(attr.fTextFont, height, group); - - if (oper === 'h') { - let res = ''; - for (n = 0; n < txt.length; n += 2) - res += String.fromCharCode(parseInt(txt.slice(n, n+2), 16)); - txt = res; - } - - // todo - correct support of angle - this.drawText({ align: attr.fTextAlign, - x: func.x(obj.fBuf[indx++]), - y: func.y(obj.fBuf[indx++]), - rotate: -angle, - text: txt, - color: getColor(attr.fTextColor), - latex: 0, draw_g: group }); - - return this.finishTextDrawing(group).then(() => process(k)); + group = g.append('svg:g'); + + return this.startTextDrawingAsync(attr.fTextFont, height, group).then(() => { + let text = arr[k].slice(1), + angle = attr.fTextAngle; + if (angle >= 360) + angle -= Math.floor(angle / 360) * 360; + + if (oper === 'h') { + let res = ''; + for (n = 0; n < text.length; n += 2) + res += String.fromCharCode(parseInt(text.slice(n, n + 2), 16)); + text = res; + } + + // todo - correct support of angle + this.drawText({ + align: attr.fTextAlign, + x: func.x(obj.fBuf[indx++]), + y: func.y(obj.fBuf[indx++]), + rotate: -angle, + text, + color: getColor(attr.fTextColor), + latex: 0, draw_g: group + }); + + return this.finishTextDrawing(group); + }).then(() => process(k)); } continue; } @@ -153,9 +205,14 @@ class TWebPaintingPainter extends ObjectPainter { return Promise.resolve(true); }; - this.createG(); - return process(-1).then(() => { check_attributes(); return this; }); + return process(-1).then(() => { + check_attributes(); + assignContextMenu(this); + if (!this.isBatchMode()) + g.on('click', evnt => this.handleMouseClick(evnt)); + return this; + }); } static async draw(dom, obj) { @@ -166,4 +223,5 @@ class TWebPaintingPainter extends ObjectPainter { } // class TWebPaintingPainter + export { TWebPaintingPainter }; diff --git a/modules/draw/draw3d.mjs b/modules/draw/draw3d.mjs index 4ef5c5745..c8a4c8e9c 100644 --- a/modules/draw/draw3d.mjs +++ b/modules/draw/draw3d.mjs @@ -24,9 +24,10 @@ function before3DDraw(painter) { return pr.then(geop => { const pp = painter.getPadPainter(); - if (pp) pp._disable_dragging = true; + if (pp) + pp.options._disable_dragging = true; - if (geop._dummy && isFunc(painter.get3DBox)) + if (geop.options.dummy && isFunc(painter.get3DBox)) geop.extendCustomBoundingBox(painter.get3DBox()); return geop.drawExtras(painter.getObject(), '', true, true); }); @@ -89,11 +90,11 @@ async function drawPolyLine3D() { if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) return fp; - const limit = 3*line.fN, p = line.fP, pnts = []; + const limit = 3 * line.fN, p = line.fP, pnts = []; for (let n = 3; n < limit; n += 3) { - pnts.push(fp.grx(p[n-3]), fp.gry(p[n-2]), fp.grz(p[n-1]), - fp.grx(p[n]), fp.gry(p[n+1]), fp.grz(p[n+2])); + pnts.push(fp.grx(p[n - 3]), fp.gry(p[n - 2]), fp.grz(p[n - 1]), + fp.grx(p[n]), fp.gry(p[n + 1]), fp.grz(p[n + 2])); } const lines = createLineSegments(pnts, create3DLineMaterial(this, line)); diff --git a/modules/draw/more.mjs b/modules/draw/more.mjs index 803d6ebf1..84d06f62e 100644 --- a/modules/draw/more.mjs +++ b/modules/draw/more.mjs @@ -1,165 +1,9 @@ -import { BIT, isFunc, clTLatex, clTMathText, clTAnnotation, clTPolyLine } from '../core.mjs'; -import { rgb as d3_rgb, select as d3_select } from '../d3.mjs'; -import { BasePainter, makeTranslate } from '../base/BasePainter.mjs'; +import { BIT } from '../core.mjs'; +import { BasePainter, makeTranslate, DrawOptions } from '../base/BasePainter.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu, kToFront } from '../gui/menu.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; -/** @summary Draw TText - * @private */ -async function drawText() { - const text = this.getObject(), - pp = this.getPadPainter(), - w = pp.getPadWidth(), - h = pp.getPadHeight(), - fp = this.getFramePainter(); - let pos_x = text.fX, pos_y = text.fY, - fact = 1, - annot = this.matchObjectType(clTAnnotation); - - this.createAttText({ attr: text }); - - if (annot && fp?.mode3d && isFunc(fp?.convert3DtoPadNDC)) { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ); - pos_x = pos.x; - pos_y = pos.y; - this.isndc = true; - annot = '3d'; - } else if (text.TestBit(BIT(14))) { - // NDC coordinates - this.isndc = true; - } else if (pp.getRootPad(true)) { - // force pad coordiantes - } else { - // place in the middle - this.isndc = true; - pos_x = pos_y = 0.5; - text.fTextAlign = 22; - } - - this.createG(); - - this.draw_g.attr('transform', null); // remove transofrm from interactive changes - - this.pos_x = this.axisToSvg('x', pos_x, this.isndc); - this.pos_y = this.axisToSvg('y', pos_y, this.isndc); - - const arg = this.textatt.createArg({ x: this.pos_x, y: this.pos_y, text: text.fTitle, latex: 0 }); - - if ((text._typename === clTLatex) || annot) { - arg.latex = 1; - fact = 0.9; - } else if (text._typename === clTMathText) { - arg.latex = 2; - fact = 0.8; - } - - this.startTextDrawing(this.textatt.font, this.textatt.getSize(w, h, fact, 0.05)); - - this.drawText(arg); - - return this.finishTextDrawing().then(() => { - if (this.isBatchMode()) return this; - - this.pos_dx = this.pos_dy = 0; - - if (!this.moveDrag) { - this.moveDrag = function(dx, dy) { - this.pos_dx += dx; - this.pos_dy += dy; - makeTranslate(this.draw_g, this.pos_dx, this.pos_dy); - }; - } - - if (!this.moveEnd) { - this.moveEnd = function(not_changed) { - if (not_changed) return; - const text = this.getObject(); - text.fX = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc); - text.fY = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc); - this.submitCanvExec(`SetX(${text.fX});;SetY(${text.fY});;`); - }; - } - - if (annot !== '3d') - addMoveHandler(this); - else { - fp.processRender3D = true; - this.handleRender3D = () => { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ), - new_x = this.axisToSvg('x', pos.x, true), - new_y = this.axisToSvg('y', pos.y, true); - makeTranslate(this.draw_g, new_x - this.pos_x, new_y - this.pos_y); - }; - } - - assignContextMenu(this, kToFront); - - return this; - }); -} - - -/** @summary Draw TPolyLine - * @private */ -function drawPolyLine() { - this.createG(); - - const polyline = this.getObject(), - kPolyLineNDC = BIT(14), - isndc = polyline.TestBit(kPolyLineNDC), - opt = this.getDrawOpt() || polyline.fOption, - dofill = (polyline._typename === clTPolyLine) && ((opt === 'f') || (opt === 'F')), - func = this.getAxisToSvgFunc(isndc); - - this.createAttLine({ attr: polyline }); - this.createAttFill({ attr: polyline }); - - let cmd = ''; - for (let n = 0; n <= polyline.fLastPoint; ++n) - cmd += `${n>0?'L':'M'}${func.x(polyline.fX[n])},${func.y(polyline.fY[n])}`; - - if (dofill) - cmd += 'Z'; - - const elem = this.draw_g.append('svg:path').attr('d', cmd); - - if (dofill) - elem.call(this.fillatt.func); - else - elem.call(this.lineatt.func).style('fill', 'none'); - - assignContextMenu(this, kToFront); - - addMoveHandler(this); - - this.dx = this.dy = 0; - this.isndc = isndc; - - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); - }; - - this.moveEnd = function(not_changed) { - if (not_changed) return; - const polyline = this.getObject(), - func = this.getAxisToSvgFunc(this.isndc); - let exec = ''; - - for (let n = 0; n <= polyline.fLastPoint; ++n) { - const x = this.svgToAxis('x', func.x(polyline.fX[n]) + this.dx, this.isndc), - y = this.svgToAxis('y', func.y(polyline.fY[n]) + this.dy, this.isndc); - polyline.fX[n] = x; - polyline.fY[n] = y; - exec += `SetPoint(${n},${x},${y});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - this.redraw(); - }; -} - /** @summary Draw TEllipse * @private */ function drawEllipse() { @@ -170,31 +14,32 @@ function drawEllipse() { this.createAttLine({ attr: ellipse }); this.createAttFill({ attr: ellipse }); - this.createG(); - - const funcs = this.getAxisToSvgFunc(), + const g = this.createG(), + funcs = this.getAxisToSvgFunc(), x = funcs.x(ellipse.fX1), y = funcs.y(ellipse.fY1), rx = is_crown && (ellipse.fR1 <= 0) ? (funcs.x(ellipse.fX1 + ellipse.fR2) - x) : (funcs.x(ellipse.fX1 + ellipse.fR1) - x), - ry = y - funcs.y(ellipse.fY1 + ellipse.fR2); + ry = y - funcs.y(ellipse.fY1 + ellipse.fR2), + dr = Math.PI / 180; let path = ''; if (is_crown && (ellipse.fR1 > 0)) { - const rx1 = rx, ry2 = ry, - ry1 = y - funcs.y(ellipse.fY1 + ellipse.fR1), + const ratio = ellipse.fYXRatio ?? 1, + rx1 = rx, ry2 = ratio * ry, + ry1 = ratio * (y - funcs.y(ellipse.fY1 + ellipse.fR1)), rx2 = funcs.x(ellipse.fX1 + ellipse.fR2) - x; if (closed_ellipse) { path = `M${-rx1},0A${rx1},${ry1},0,1,0,${rx1},0A${rx1},${ry1},0,1,0,${-rx1},0` + `M${-rx2},0A${rx2},${ry2},0,1,0,${rx2},0A${rx2},${ry2},0,1,0,${-rx2},0`; } else { - const large_arc = (ellipse.fPhimax-ellipse.fPhimin>=180) ? 1 : 0, - a1 = ellipse.fPhimin*Math.PI/180, a2 = ellipse.fPhimax*Math.PI/180, - dx1 = Math.round(rx1*Math.cos(a1)), dy1 = Math.round(ry1*Math.sin(a1)), - dx2 = Math.round(rx1*Math.cos(a2)), dy2 = Math.round(ry1*Math.sin(a2)), - dx3 = Math.round(rx2*Math.cos(a1)), dy3 = Math.round(ry2*Math.sin(a1)), - dx4 = Math.round(rx2*Math.cos(a2)), dy4 = Math.round(ry2*Math.sin(a2)); + const large_arc = (ellipse.fPhimax - ellipse.fPhimin >= 180) ? 1 : 0, + a1 = ellipse.fPhimin * dr, a2 = ellipse.fPhimax * dr, + dx1 = Math.round(rx1 * Math.cos(a1)), dy1 = Math.round(ry1 * Math.sin(a1)), + dx2 = Math.round(rx1 * Math.cos(a2)), dy2 = Math.round(ry1 * Math.sin(a2)), + dx3 = Math.round(rx2 * Math.cos(a1)), dy3 = Math.round(ry2 * Math.sin(a1)), + dx4 = Math.round(rx2 * Math.cos(a2)), dy4 = Math.round(ry2 * Math.sin(a2)); path = `M${dx2},${dy2}A${rx1},${ry1},0,${large_arc},0,${dx1},${dy1}` + `L${dx3},${dy3}A${rx2},${ry2},0,${large_arc},1,${dx4},${dy4}Z`; @@ -203,203 +48,67 @@ function drawEllipse() { if (closed_ellipse) path = `M${-rx},0A${rx},${ry},0,1,0,${rx},0A${rx},${ry},0,1,0,${-rx},0Z`; else { - const x1 = Math.round(rx * Math.cos(ellipse.fPhimin*Math.PI/180)), - y1 = Math.round(ry * Math.sin(ellipse.fPhimin*Math.PI/180)), - x2 = Math.round(rx * Math.cos(ellipse.fPhimax*Math.PI/180)), - y2 = Math.round(ry * Math.sin(ellipse.fPhimax*Math.PI/180)); + const x1 = Math.round(rx * Math.cos(ellipse.fPhimin * dr)), + y1 = Math.round(ry * Math.sin(ellipse.fPhimin * dr)), + x2 = Math.round(rx * Math.cos(ellipse.fPhimax * dr)), + y2 = Math.round(ry * Math.sin(ellipse.fPhimax * dr)); path = `M0,0L${x1},${y1}A${rx},${ry},0,1,1,${x2},${y2}Z`; } } else { - const ct = Math.cos(ellipse.fTheta*Math.PI/180), - st = Math.sin(ellipse.fTheta*Math.PI/180), - phi1 = ellipse.fPhimin*Math.PI/180, - phi2 = ellipse.fPhimax*Math.PI/180, - np = 200, - dphi = (phi2-phi1) / (np - (closed_ellipse ? 0 : 1)); - let lastx = 0, lasty = 0; - if (!closed_ellipse) path = 'M0,0'; - for (let n = 0; n < np; ++n) { - const angle = phi1 + n*dphi, + const ct = Math.cos(ellipse.fTheta * dr), + st = Math.sin(ellipse.fTheta * dr), + phi1 = ellipse.fPhimin * dr, + phi2 = ellipse.fPhimax * dr, + np = 200, + dphi = (phi2 - phi1) / (np - (closed_ellipse ? 0 : 1)); + let lastx = 0, lasty = 0; + if (!closed_ellipse) + path = 'M0,0'; + for (let n = 0; n < np; ++n) { + const angle = phi1 + n * dphi, dx = ellipse.fR1 * Math.cos(angle), dy = ellipse.fR2 * Math.sin(angle), - px = funcs.x(ellipse.fX1 + dx*ct - dy*st) - x, - py = funcs.y(ellipse.fY1 + dx*st + dy*ct) - y; + px = funcs.x(ellipse.fX1 + dx * ct - dy * st) - x, + py = funcs.y(ellipse.fY1 + dx * st + dy * ct) - y; if (!path) path = `M${px},${py}`; else if (lastx === px) - path += `v${py-lasty}`; + path += `v${py - lasty}`; else if (lasty === py) - path += `h${px-lastx}`; + path += `h${px - lastx}`; else - path += `l${px-lastx},${py-lasty}`; - lastx = px; lasty = py; - } - path += 'Z'; + path += `l${px - lastx},${py - lasty}`; + lastx = px; + lasty = py; + } + path += 'Z'; } this.x = x; this.y = y; - makeTranslate(this.draw_g.append('svg:path'), x, y) + makeTranslate(g.append('svg:path'), x, y) .attr('d', path) .call(this.lineatt.func) .call(this.fillatt.func); - assignContextMenu(this, kToFront); + assignContextMenu(this); addMoveHandler(this); this.moveDrag = function(dx, dy) { this.x += dx; this.y += dy; - makeTranslate(this.draw_g.select('path'), this.x, this.y); + makeTranslate(this.getG().select('path'), this.x, this.y); }; this.moveEnd = function(not_changed) { - if (not_changed) return; - const ellipse = this.getObject(); - ellipse.fX1 = this.svgToAxis('x', this.x); - ellipse.fY1 = this.svgToAxis('y', this.y); - this.submitCanvExec(`SetX1(${ellipse.fX1});;SetY1(${ellipse.fY1});;Notify();;`); - }; -} - -/** @summary Draw TPie - * @private */ -function drawPie() { - this.createG(); - - const pie = this.getObject(), - nb = pie.fPieSlices.length, - xc = this.axisToSvg('x', pie.fX), - yc = this.axisToSvg('y', pie.fY), - rx = this.axisToSvg('x', pie.fX + pie.fRadius) - xc, - ry = this.axisToSvg('y', pie.fY + pie.fRadius) - yc; - - makeTranslate(this.draw_g, xc, yc); - - // Draw the slices - let total = 0, - af = (pie.fAngularOffset*Math.PI)/180, - x1 = Math.round(rx*Math.cos(af)), - y1 = Math.round(ry*Math.sin(af)); - - for (let n = 0; n < nb; n++) - total += pie.fPieSlices[n].fValue; - - for (let n = 0; n < nb; n++) { - const slice = pie.fPieSlices[n]; - - this.createAttLine({ attr: slice }); - this.createAttFill({ attr: slice }); - - af += slice.fValue/total*2*Math.PI; - const x2 = Math.round(rx*Math.cos(af)), - y2 = Math.round(ry*Math.sin(af)); - - this.draw_g - .append('svg:path') - .attr('d', `M0,0L${x1},${y1}A${rx},${ry},0,0,0,${x2},${y2}z`) - .call(this.lineatt.func) - .call(this.fillatt.func); - x1 = x2; y1 = y2; - } -} - -/** @summary Draw TBox - * @private */ -function drawBox() { - const box = this.getObject(), - opt = this.getDrawOpt(), - draw_line = (opt.toUpperCase().indexOf('L') >= 0); - - this.createAttLine({ attr: box }); - this.createAttFill({ attr: box }); - - // if box filled, contour line drawn only with 'L' draw option: - if (!this.fillatt.empty() && !draw_line) - this.lineatt.color = 'none'; - - this.createG(); - - this.x1 = this.axisToSvg('x', box.fX1); - this.x2 = this.axisToSvg('x', box.fX2); - this.y1 = this.axisToSvg('y', box.fY1); - this.y2 = this.axisToSvg('y', box.fY2); - this.borderMode = (box.fBorderMode && box.fBorderSize && this.fillatt.hasColor()) ? box.fBorderMode : 0; - this.borderSize = box.fBorderSize; - - this.getPathes = () => { - const xx = Math.min(this.x1, this.x2), yy = Math.min(this.y1, this.y2), - ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2), - path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`; - if (!this.borderMode) - return [path]; - const pww = this.borderSize, phh = this.borderSize, - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; - - return (this.borderMode > 0) ? [path, side1, side2] : [path, side2, side1]; - }; - - const paths = this.getPathes(); - - this.draw_g - .append('svg:path') - .attr('d', paths[0]) - .call(this.lineatt.func) - .call(this.fillatt.func); - - if (this.borderMode) { - this.draw_g.append('svg:path') - .attr('d', paths[1]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); - - this.draw_g.append('svg:path') - .attr('d', paths[2]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatHex()); - } - - assignContextMenu(this, kToFront); - - addMoveHandler(this); - - this.moveStart = function(x, y) { - const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2); - - this.c_x1 = Math.abs(x - this.x2) > ww*0.1; - this.c_x2 = Math.abs(x - this.x1) > ww*0.1; - this.c_y1 = Math.abs(y - this.y2) > hh*0.1; - this.c_y2 = Math.abs(y - this.y1) > hh*0.1; - if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2) - this.c_y1 = this.c_y2 = false; - if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2) - this.c_x1 = this.c_x2 = false; - }; - - this.moveDrag = function(dx, dy) { - if (this.c_x1) this.x1 += dx; - if (this.c_x2) this.x2 += dx; - if (this.c_y1) this.y1 += dy; - if (this.c_y2) this.y2 += dy; - - const nodes = this.draw_g.selectAll('path').nodes(), - pathes = this.getPathes(); - - pathes.forEach((path, i) => d3_select(nodes[i]).attr('d', path)); - }; - - this.moveEnd = function(not_changed) { - if (not_changed) return; - const box = this.getObject(); - let exec = ''; - if (this.c_x1) { box.fX1 = this.svgToAxis('x', this.x1); exec += `SetX1(${box.fX1});;`; } - if (this.c_x2) { box.fX2 = this.svgToAxis('x', this.x2); exec += `SetX2(${box.fX2});;`; } - if (this.c_y1) { box.fY1 = this.svgToAxis('y', this.y1); exec += `SetY1(${box.fY1});;`; } - if (this.c_y2) { box.fY2 = this.svgToAxis('y', this.y2); exec += `SetY2(${box.fY2});;`; } - this.submitCanvExec(exec + 'Notify();;'); + if (not_changed) + return; + const ell = this.getObject(); + ell.fX1 = this.svgToAxis('x', this.x); + ell.fY1 = this.svgToAxis('y', this.y); + this.submitCanvExec(`SetX1(${ell.fX1});;SetY1(${ell.fY1});;Notify();;`); }; } @@ -411,21 +120,31 @@ function drawMarker() { this.isndc = marker.TestBit(kMarkerNDC); + const d = new DrawOptions(this.getDrawOpt()), + use_frame = this.isndc ? false : d.check('FRAME'), + swap_xy = use_frame && this.getFramePainter()?.swap_xy(); + this.createAttMarker({ attr: marker }); - this.createG(); + const g = this.createG(use_frame ? 'frame2d' : undefined); + + let x = this.axisToSvg('x', marker.fX, this.isndc), + y = this.axisToSvg('y', marker.fY, this.isndc); + if (swap_xy) + [x, y] = [y, x]; - const x = this.axisToSvg('x', marker.fX, this.isndc), - y = this.axisToSvg('y', marker.fY, this.isndc), - path = this.markeratt.create(x, y); + const path = this.markeratt.create(x, y); if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); } - assignContextMenu(this, kToFront); + if (d.check('NO_INTERACTIVE')) + return; + + assignContextMenu(this); addMoveHandler(this); @@ -434,15 +153,21 @@ function drawMarker() { this.moveDrag = function(dx, dy) { this.dx += dx; this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); + if (this.getG()) + makeTranslate(this.getG().select('path'), this.dx, this.dy); }; this.moveEnd = function(not_changed) { - if (not_changed) return; - const marker = this.getObject(); - marker.fX = this.svgToAxis('x', this.axisToSvg('x', marker.fX, this.isndc) + this.dx, this.isndc); - marker.fY = this.svgToAxis('y', this.axisToSvg('y', marker.fY, this.isndc) + this.dy, this.isndc); - this.submitCanvExec(`SetX(${marker.fX});;SetY(${marker.fY});;Notify();;`); + if (not_changed || !this.getG()) + return; + const mrk = this.getObject(); + let fx = this.svgToAxis('x', this.axisToSvg('x', mrk.fX, this.isndc) + this.dx, this.isndc), + fy = this.svgToAxis('y', this.axisToSvg('y', mrk.fY, this.isndc) + this.dy, this.isndc); + if (swap_xy) + [fx, fy] = [fy, fx]; + mrk.fX = fx; + mrk.fY = fy; + this.submitCanvExec(`SetX(${fx});;SetY(${fy});;Notify();;`); this.redraw(); }; } @@ -455,19 +180,19 @@ function drawPolyMarker() { this.createAttMarker({ attr: poly }); - this.createG(); + const g = this.createG(); let path = ''; for (let n = 0; n <= poly.fLastPoint; ++n) path += this.markeratt.create(func.x(poly.fX[n]), func.y(poly.fY[n])); if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); } - assignContextMenu(this, kToFront); + assignContextMenu(this); addMoveHandler(this); @@ -476,20 +201,21 @@ function drawPolyMarker() { this.moveDrag = function(dx, dy) { this.dx += dx; this.dy += dy; - makeTranslate(this.draw_g.select('path'), this.dx, this.dy); + if (this.getG()) + makeTranslate(this.getG().select('path'), this.dx, this.dy); }; this.moveEnd = function(not_changed) { - if (not_changed) return; - const poly = this.getObject(), - func = this.getAxisToSvgFunc(); - + if (not_changed || !this.getG()) + return; + const poly2 = this.getObject(), + func2 = this.getAxisToSvgFunc(); let exec = ''; - for (let n = 0; n <= poly.fLastPoint; ++n) { - const x = this.svgToAxis('x', func.x(poly.fX[n]) + this.dx), - y = this.svgToAxis('y', func.y(poly.fY[n]) + this.dy); - poly.fX[n] = x; - poly.fY[n] = y; + for (let n = 0; n <= poly2.fLastPoint; ++n) { + const x = this.svgToAxis('x', func2.x(poly2.fX[n]) + this.dx), + y = this.svgToAxis('y', func2.y(poly2.fY[n]) + this.dy); + poly2.fX[n] = x; + poly2.fY[n] = y; exec += `SetPoint(${n},${x},${y});;`; } this.submitCanvExec(exec + 'Notify();;'); @@ -516,5 +242,4 @@ function drawJSImage(dom, obj, opt) { return painter; } -export { drawText, drawPolyLine, drawEllipse, drawPie, drawBox, - drawMarker, drawPolyMarker, drawJSImage }; +export { drawEllipse, drawMarker, drawPolyMarker, drawJSImage }; diff --git a/modules/draw/v7more.mjs b/modules/draw/v7more.mjs index 71b7a39a3..ab8044b16 100644 --- a/modules/draw/v7more.mjs +++ b/modules/draw/v7more.mjs @@ -11,84 +11,78 @@ import { createMenu } from '../gui/menu.mjs'; * @private */ function drawText() { const text = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(text.fPos, onframe), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }); + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p = pp.getCoordinate(text.fPos, onframe), + textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }); this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - this.startTextDrawing(textFont, 'font'); - - this.drawText({ x: p.x, y: p.y, text: text.fText, latex: 1 }); - - return this.finishTextDrawing(); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + this.drawText({ x: p.x, y: p.y, text: text.fText, latex: 1 }); + return this.finishTextDrawing(); + }); } /** @summary draw RLine object * @private */ function drawLine() { - const line = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(line.fP1, onframe), - p2 = pp.getCoordinate(line.fP2, onframe); - - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - - this.createv7AttLine(); - - this.draw_g - .append('svg:path') - .attr('d', `M${p1.x},${p1.y}L${p2.x},${p2.y}`) - .call(this.lineatt.func); + const line = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p1 = pp.getCoordinate(line.fP1, onframe), + p2 = pp.getCoordinate(line.fP2, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + + this.createv7AttLine(); + + g.append('svg:path') + .attr('d', `M${p1.x},${p1.y}L${p2.x},${p2.y}`) + .call(this.lineatt.func); } /** @summary draw RBox object * @private */ function drawBox() { const box = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(box.fP1, onframe), - p2 = pp.getCoordinate(box.fP2, onframe); - - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p1 = pp.getCoordinate(box.fP1, onframe), + p2 = pp.getCoordinate(box.fP2, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); this.createv7AttLine('border_'); this.createv7AttFill(); - this.draw_g - .append('svg:path') - .attr('d', `M${p1.x},${p1.y}H${p2.x}V${p2.y}H${p1.x}Z`) - .call(this.lineatt.func) - .call(this.fillatt.func); + g.append('svg:path') + .attr('d', `M${p1.x},${p1.y}H${p2.x}V${p2.y}H${p1.x}Z`) + .call(this.lineatt.func) + .call(this.fillatt.func); } /** @summary draw RMarker object * @private */ function drawMarker() { - const marker = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(marker.fP, onframe); - - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); + const marker = this.getObject(), + pp = this.getPadPainter(), + onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, + clipping = onframe ? this.v7EvalAttr('clipping', false) : false, + p = pp.getCoordinate(marker.fP, onframe), + g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - this.createv7AttMarker(); + this.createv7AttMarker(); - const path = this.markeratt.create(p.x, p.y); + const path = this.markeratt.create(p.x, p.y); - if (path) { - this.draw_g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } + if (path) { + g.append('svg:path') + .attr('d', path) + .call(this.markeratt.func); + } } /** @summary painter for RPalette @@ -112,22 +106,23 @@ class RPalettePainter extends RObjectPainter { drawPalette(drag) { const palette = this.getHistPalette(), contour = palette.getContour(), - framep = this.getFramePainter(); + fp = this.getFramePainter(); if (!contour) return console.log('no contour - no palette'); // frame painter must be there - if (!framep) + if (!fp) return console.log('no frame painter - no palette'); - const zmin = contour[0], - zmax = contour[contour.length-1], - rect = framep.getFrameRect(), + const zmin = contour.at(0), + zmax = contour.at(-1), + rect = fp.getFrameRect(), pad_width = this.getPadPainter().getPadWidth(), pad_height = this.getPadPainter().getPadHeight(), visible = this.v7EvalAttr('visible', true), - vertical = this.v7EvalAttr('vertical', true); + vertical = this.v7EvalAttr('vertical', true), + g = this.getG(); let gmin = palette.full_min, gmax = palette.full_max, palette_x, palette_y, palette_width, palette_height; @@ -146,111 +141,124 @@ class RPalettePainter extends RObjectPainter { } this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server } else { - if (vertical) { + if (vertical) { const margin = this.v7EvalLength('margin', pad_width, 0.02); palette_x = Math.round(rect.x + rect.width + margin); palette_width = this.v7EvalLength('width', pad_width, 0.05); palette_y = rect.y; palette_height = rect.height; - } else { + } else { const margin = this.v7EvalLength('margin', pad_height, 0.02); palette_x = rect.x; palette_width = rect.width; palette_y = Math.round(rect.y + rect.height + margin); palette_height = this.v7EvalLength('width', pad_height, 0.05); - } + } - // x,y,width,height attributes used for drag functionality - makeTranslate(this.draw_g, palette_x, palette_y); + // x,y,width,height attributes used for drag functionality + makeTranslate(g, palette_x, palette_y); } - let g_btns = this.draw_g.selectChild('.colbtns'); + let g_btns = g.selectChild('.colbtns'); if (g_btns.empty()) - g_btns = this.draw_g.append('svg:g').attr('class', 'colbtns'); + g_btns = g.append('svg:g').attr('class', 'colbtns'); else g_btns.selectAll('*').remove(); - if (!visible) return; + if (!visible) + return; g_btns.append('svg:path') .attr('d', `M0,0H${palette_width}V${palette_height}H0Z`) .style('stroke', 'black') .style('fill', 'none'); - if ((gmin === undefined) || (gmax === undefined)) { gmin = zmin; gmax = zmax; } + if ((gmin === undefined) || (gmax === undefined)) { + gmin = zmin; + gmax = zmax; + } if (vertical) - framep.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, true, [palette_height, 0], -palette_height, { reverse: false }); + fp.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, true, [palette_height, 0], -palette_height, { reverse: false }); else - framep.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, false, [0, palette_width], palette_width, { reverse: false }); - - for (let i = 0; i < contour.length-1; ++i) { - const z0 = Math.round(framep.z_handle.gr(contour[i])), - z1 = Math.round(framep.z_handle.gr(contour[i+1])), - col = palette.getContourColor((contour[i]+contour[i+1])/2), - - r = g_btns.append('svg:path') - .attr('d', vertical ? `M0,${z1}H${palette_width}V${z0}H0Z` : `M${z0},0V${palette_height}H${z1}V0Z`) - .style('fill', col) - .style('stroke', col) - .property('fill0', col) - .property('fill1', d3_rgb(col).darker(0.5).toString()); + fp.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, false, [0, palette_width], palette_width, { reverse: false }); + + for (let i = 0; i < contour.length - 1; ++i) { + const z0 = Math.round(fp.z_handle.gr(contour[i])), + z1 = Math.round(fp.z_handle.gr(contour[i + 1])), + col = palette.getContourColor((contour[i] + contour[i + 1]) / 2), + r = g_btns.append('svg:path') + .attr('d', vertical ? `M0,${z1}H${palette_width}V${z0}H0Z` : `M${z0},0V${palette_height}H${z1}V0Z`) + .style('fill', col) + .style('stroke', col) + .property('fill0', col) + .property('fill1', d3_rgb(col).darker(0.5).formatRgb()); + + if (this.isBatchMode()) + continue; if (this.isTooltipAllowed()) { r.on('mouseover', function() { d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill1')); }).on('mouseout', function() { d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill0')); - }).append('svg:title').text(contour[i].toFixed(2) + ' - ' + contour[i+1].toFixed(2)); + }).append('svg:title').text(contour[i].toFixed(2) + ' - ' + contour[i + 1].toFixed(2)); } if (settings.Zooming) - r.on('dblclick', () => framep.unzoom('z')); + r.on('dblclick', () => fp.unzoom('z')); } - framep.z_handle.maxTickSize = Math.round(palette_width*0.3); + fp.z_handle.maxTickSize = Math.round(palette_width * 0.3); - const promise = framep.z_handle.drawAxis(this.draw_g, makeTranslate(vertical ? palette_width : 0, palette_height), vertical ? -1 : 1); + const promise = fp.z_handle.drawAxis(g, makeTranslate(vertical ? palette_width : 0, palette_height), vertical ? -1 : 1); if (this.isBatchMode() || drag) return promise; return promise.then(() => { if (settings.ContextMenu) { - this.draw_g.on('contextmenu', evnt => { + g.on('contextmenu', evnt => { evnt.stopPropagation(); // disable main context menu evnt.preventDefault(); // disable browser context menu createMenu(evnt, this).then(menu => { - menu.add('header:Palette'); - menu.addchk(vertical, 'Vertical', flag => { this.v7SetAttr('vertical', flag); this.redrawPad(); }); - framep.z_handle.fillAxisContextMenu(menu, 'z'); + menu.header('Palette'); + menu.addchk(vertical, 'Vertical', flag => { + this.v7SetAttr('vertical', flag); + this.redrawPad(); + }); + fp.z_handle.fillAxisContextMenu(menu, 'z'); menu.show(); }); }); } - addDragHandler(this, { x: palette_x, y: palette_y, width: palette_width, height: palette_height, - minwidth: 20, minheight: 20, no_change_x: !vertical, no_change_y: vertical, redraw: d => this.drawPalette(d) }); + addDragHandler(this, { + x: palette_x, y: palette_y, width: palette_width, height: palette_height, + minwidth: 20, minheight: 20, no_change_x: !vertical, no_change_y: vertical, redraw: d => this.drawPalette(d) + }); - if (!settings.Zooming) return; + if (!settings.Zooming) + return; let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect, zoom_rect_visible, moving_labels, last_pos; const moveRectSel = evnt => { - if (!doing_zoom) return; + if (!doing_zoom) + return; evnt.preventDefault(); - last_pos = d3_pointer(evnt, this.draw_g.node()); + last_pos = d3_pointer(evnt, this.getG().node()); if (moving_labels) - return framep.z_handle.processLabelsMove('move', last_pos); + return fp.z_handle.processLabelsMove('move', last_pos); if (vertical) sel2 = Math.min(Math.max(last_pos[1], 0), palette_height); else sel2 = Math.min(Math.max(last_pos[0], 0), palette_width); - const sz = Math.abs(sel2-sel1); + const sz = Math.abs(sel2 - sel1); if (!zoom_rect_visible && (sz > 1)) { zoom_rect.style('display', null); @@ -262,7 +270,8 @@ class RPalettePainter extends RObjectPainter { else zoom_rect.attr('x', Math.min(sel1, sel2)).attr('width', sz); }, endRectSel = evnt => { - if (!doing_zoom) return; + if (!doing_zoom) + return; evnt.preventDefault(); d3_select(window).on('mousemove.colzoomRect', null) @@ -272,20 +281,21 @@ class RPalettePainter extends RObjectPainter { doing_zoom = false; if (moving_labels) - framep.z_handle.processLabelsMove('stop', last_pos); - else { - const z = framep.z_handle.func, z1 = z.invert(sel1), z2 = z.invert(sel2); - this.getFramePainter().zoom('z', Math.min(z1, z2), Math.max(z1, z2)); + fp.z_handle.processLabelsMove('stop', last_pos); + else { + const z = fp.z_handle.func, z1 = z.invert(sel1), z2 = z.invert(sel2); + fp.zoomSingle('z', Math.min(z1, z2), Math.max(z1, z2)); } }, startRectSel = evnt => { // ignore when touch selection is activated - if (doing_zoom) return; + if (doing_zoom) + return; doing_zoom = true; evnt.preventDefault(); evnt.stopPropagation(); - last_pos = d3_pointer(evnt, this.draw_g.node()); + last_pos = d3_pointer(evnt, this.getG().node()); sel1 = sel2 = last_pos[vertical ? 1 : 0]; zoom_rect_visible = false; moving_labels = false; @@ -304,29 +314,29 @@ class RPalettePainter extends RObjectPainter { setTimeout(() => { if (!zoom_rect_visible && doing_zoom) - moving_labels = framep.z_handle.processLabelsMove('start', last_pos); + moving_labels = fp.z_handle.processLabelsMove('start', last_pos); }, 500); }, assignHandlers = () => { - this.draw_g.selectAll('.axis_zoom, .axis_labels') + this.getG().selectAll('.axis_zoom, .axis_labels') .on('mousedown', startRectSel) - .on('dblclick', () => framep.unzoom('z')); + .on('dblclick', () => fp.unzoom('z')); if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { + this.getG().on('wheel', evnt => { evnt.stopPropagation(); evnt.preventDefault(); - const pos = d3_pointer(evnt, this.draw_g.node()), - coord = vertical ? (1 - pos[1] / palette_height) : pos[0] / palette_width, + const pos = d3_pointer(evnt, this.getG().node()), + coord = vertical ? (1 - pos[1] / palette_height) : pos[0] / palette_width, - item = framep.z_handle.analyzeWheelEvent(evnt, coord); + item = fp.z_handle.analyzeWheelEvent(evnt, coord); if (item.changed) - framep.zoom('z', item.min, item.max); + fp.zoomSingle('z', item.min, item.max); }); } }; - framep.z_handle.setAfterDrawHandler(assignHandlers); + fp.z_handle.setAfterDrawHandler(assignHandlers); assignHandlers(); }); diff --git a/modules/geom/TGeoPainter.mjs b/modules/geom/TGeoPainter.mjs index eb8bef65f..fa6d68f94 100644 --- a/modules/geom/TGeoPainter.mjs +++ b/modules/geom/TGeoPainter.mjs @@ -1,32 +1,24 @@ import { httpRequest, browser, source_dir, settings, internals, constants, create, clone, findFunction, isBatchMode, isNodeJs, getDocument, isObject, isFunc, isStr, postponePromise, getPromise, - prROOT, clTNamed, clTList, clTAxis, clTObjArray, clTPolyMarker3D, clTPolyLine3D, + getKindForType, clTTree, clTNamed, clTList, clTAxis, clTObjArray, clTPolyMarker3D, clTPolyLine3D, clTGeoVolume, clTGeoNode, clTGeoNodeMatrix, nsREX, nsSVG, kInspect } from '../core.mjs'; -import { REVISION, DoubleSide, FrontSide, - Color, Vector2, Vector3, Matrix4, Object3D, Box3, Group, Plane, PlaneHelper, - Euler, Quaternion, Mesh, InstancedMesh, MeshLambertMaterial, MeshBasicMaterial, - LineSegments, LineBasicMaterial, LineDashedMaterial, BufferAttribute, - BufferGeometry, BoxGeometry, CircleGeometry, SphereGeometry, - Scene, Fog, OrthographicCamera, PerspectiveCamera, - DirectionalLight, AmbientLight, HemisphereLight } from '../three.mjs'; -import { EffectComposer, RenderPass, UnrealBloomPass, TextGeometry } from '../three_addons.mjs'; import { showProgress, injectStyle, ToolbarIcons } from '../gui/utils.mjs'; import { GUI } from '../gui/lil-gui.mjs'; -import { assign3DHandler, disposeThreejsObject, createOrbitControl, - createLineSegments, InteractiveControl, PointsCreator, +import { THREE, assign3DHandler, disposeThreejsObject, createOrbitControl, + createLineSegments, InteractiveControl, PointsCreator, importThreeJs, createRender3D, beforeRender3D, afterRender3D, getRender3DKind, cleanupRender3D, - HelveticerRegularFont } from '../base/base3d.mjs'; + createTextGeometry } from '../base/base3d.mjs'; import { getColor, getRootColors } from '../base/colors.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { createMenu, closeMenu } from '../gui/menu.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { kindGeo, kindEve, +import { kindGeo, kindEve, kGetMesh, kDeleteMesh, clTGeoBBox, clTGeoCompositeShape, geoCfg, geoBITS, ClonedNodes, testGeoBit, setGeoBit, toggleGeoBit, setInvisibleAll, countNumShapes, getNodeKind, produceRenderOrder, createServerGeometry, - projectGeometry, countGeometryFaces, createMaterial, createFrustum, createProjectionMatrix, + projectGeometry, numGeometryFaces, createMaterial, createFrustum, createProjectionMatrix, getBoundingBox, provideObjectInfo, isSameStack, checkDuplicates, getObjectName, cleanupShape, getShapeIcon } from './geobase.mjs'; @@ -49,13 +41,13 @@ function buildOverlapVolume(overlap) { node1.fName = overlap.fVolume1.fName || 'Overlap1'; node1.fMatrix = overlap.fMatrix1; node1.fVolume = overlap.fVolume1; - // node1.fVolume.fLineColor = 2; // color assigned with _splitColors + // node1.fVolume.fLineColor = 2; // color assigned with ctrl.split_colors const node2 = create(clTGeoNodeMatrix); node2.fName = overlap.fVolume2.fName || 'Overlap2'; node2.fMatrix = overlap.fMatrix2; node2.fVolume = overlap.fVolume2; - // node2.fVolume.fLineColor = 3; // color assigned with _splitColors + // node2.fVolume.fLineColor = 3; // color assigned with ctrl.split_colors vol.fNodes = create(clTList); vol.fNodes.Add(node1); @@ -69,7 +61,8 @@ let $comp_col_cnt = 0; /** @summary Function used to build hierarchy of elements of composite shapes * @private */ function buildCompositeVolume(comp, maxlvl, side) { - if (maxlvl === undefined) maxlvl = 1; + if (maxlvl === undefined) + maxlvl = 1; if (!side) { $comp_col_cnt = 0; side = ''; @@ -86,7 +79,8 @@ function buildCompositeVolume(comp, maxlvl, side) { return vol; } - if (side) side += '/'; + if (side) + side += '/'; vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy vol.fName = ''; @@ -95,14 +89,14 @@ function buildCompositeVolume(comp, maxlvl, side) { setGeoBit(node1, geoBITS.kVisDaughters, true); node1.fName = 'Left'; node1.fMatrix = comp.fNode.fLeftMat; - node1.fVolume = buildCompositeVolume(comp.fNode.fLeft, maxlvl-1, side + 'Left'); + node1.fVolume = buildCompositeVolume(comp.fNode.fLeft, maxlvl - 1, side + 'Left'); const node2 = create(clTGeoNodeMatrix); setGeoBit(node2, geoBITS.kVisThis, true); setGeoBit(node2, geoBITS.kVisDaughters, true); node2.fName = 'Right'; node2.fMatrix = comp.fNode.fRightMat; - node2.fVolume = buildCompositeVolume(comp.fNode.fRight, maxlvl-1, side + 'Right'); + node2.fVolume = buildCompositeVolume(comp.fNode.fRight, maxlvl - 1, side + 'Right'); vol.fNodes = create(clTList); vol.fNodes.Add(node1); @@ -124,17 +118,17 @@ function getHistPainter3DCfg(painter) { offset_x = 0, offset_y = 0, offset_z = 0; if (main.scale_xmax > main.scale_xmin) { - scale_x = 2 * main.size_x3d/(main.scale_xmax - main.scale_xmin); + scale_x = 2 * main.size_x3d / (main.scale_xmax - main.scale_xmin); offset_x = (main.scale_xmax + main.scale_xmin) / 2 * scale_x; } if (main.scale_ymax > main.scale_ymin) { - scale_y = 2 * main.size_y3d/(main.scale_ymax - main.scale_ymin); + scale_y = 2 * main.size_y3d / (main.scale_ymax - main.scale_ymin); offset_y = (main.scale_ymax + main.scale_ymin) / 2 * scale_y; } if (main.scale_zmax > main.scale_zmin) { - scale_z = 2 * main.size_z3d/(main.scale_zmax - main.scale_zmin); + scale_z = 2 * main.size_z3d / (main.scale_zmax - main.scale_zmin); offset_z = (main.scale_zmax + main.scale_zmin) / 2 * scale_z - main.size_z3d; } @@ -149,121 +143,17 @@ function getHistPainter3DCfg(painter) { scale_x, scale_y, scale_z, offset_x, offset_y, offset_z }; - } -} - - -/** @summary create list entity for geo object - * @private */ -function createList(parent, lst, name, title) { - if (!lst?.arr?.length) return; - - const list_item = { - _name: name, - _kind: prROOT + clTList, - _title: title, - _more: true, - _geoobj: lst, - _parent: parent, - _get(item /*, itemname */) { - return Promise.resolve(item._geoobj || null); - }, - _expand(node, lst) { - // only childs - - if (lst.fVolume) - lst = lst.fVolume.fNodes; - - if (!lst.arr) return false; - - node._childs = []; - - checkDuplicates(null, lst.arr); - - for (const n in lst.arr) - createItem(node, lst.arr[n]); - - return true; - } - }; - - if (!parent._childs) - parent._childs = []; - parent._childs.push(list_item); -} - - -/** @summary Expand geo object - * @private */ -function expandGeoObject(parent, obj) { - injectGeoStyle(); - - if (!parent || !obj) return false; - - const isnode = (obj._typename.indexOf(clTGeoNode) === 0), - isvolume = (obj._typename.indexOf(clTGeoVolume) === 0), - ismanager = (obj._typename === clTGeoManager), - iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)), - isoverlap = (obj._typename === clTGeoOverlap); - - if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) return false; - - if (parent._childs) return true; - - if (ismanager) { - createList(parent, obj.fMaterials, 'Materials', 'list of materials'); - createList(parent, obj.fMedia, 'Media', 'list of media'); - createList(parent, obj.fTracks, 'Tracks', 'list of tracks'); - createList(parent, obj.fOverlaps, 'Overlaps', 'list of detected overlaps'); - createItem(parent, obj.fMasterVolume); - return true; - } - - if (isoverlap) { - createItem(parent, obj.fVolume1); - createItem(parent, obj.fVolume2); - createItem(parent, obj.fMarker, 'Marker'); - return true; - } - - let volume, subnodes, shape; - - if (iseve) { - subnodes = obj.fElements?.arr; - shape = obj.fShape; - } else { - volume = isnode ? obj.fVolume : obj; - subnodes = volume?.fNodes?.arr; - shape = volume?.fShape; - } - - if (!subnodes && (shape?._typename === clTGeoCompositeShape) && shape?.fNode) { - if (!parent._childs) { // deepscan-disable-line - createItem(parent, shape.fNode.fLeft, 'Left'); - createItem(parent, shape.fNode.fRight, 'Right'); - } - - return true; } - - if (!subnodes) return false; - - checkDuplicates(obj, subnodes); - - for (let i = 0; i < subnodes.length; ++i) - createItem(parent, subnodes[i]); - - return true; } -/** @summary find item with 3d painter +/** @summary find item with geometry painter * @private */ -function findItemWithPainter(hitem, funcname) { +function findItemWithGeoPainter(hitem, test_changes) { while (hitem) { - if (hitem._painter?._camera) { - if (funcname && isFunc(hitem._painter[funcname])) - hitem._painter[funcname](); + if (isFunc(hitem._painter?.testGeomChanges)) { + if (test_changes) + hitem._painter.testGeomChanges(); return hitem; } hitem = hitem._parent; @@ -280,11 +170,15 @@ function provideVisStyle(obj) { const vis = !testGeoBit(obj, geoBITS.kVisNone) && testGeoBit(obj, geoBITS.kVisThis); let chld = testGeoBit(obj, geoBITS.kVisDaughters); - if (chld && !obj.fNodes?.arr?.length) chld = false; + if (chld && !obj.fNodes?.arr?.length) + chld = false; - if (vis && chld) return ' geovis_all'; - if (vis) return ' geovis_this'; - if (chld) return ' geovis_daughters'; + if (vis && chld) + return ' geovis_all'; + if (vis) + return ' geovis_this'; + if (chld) + return ' geovis_daughters'; return ''; } @@ -292,7 +186,8 @@ function provideVisStyle(obj) { /** @summary update icons * @private */ function updateBrowserIcons(obj, hpainter) { - if (!obj || !hpainter) return; + if (!obj || !hpainter) + return; hpainter.forEachItem(m => { // update all items with that volume @@ -308,7 +203,8 @@ function updateBrowserIcons(obj, hpainter) { * @private */ function getIntersectStack(item) { const obj = item?.object; - if (!obj) return null; + if (!obj) + return null; if (obj.stack) return obj.stack; if (obj.stacks && item.instanceId !== undefined && item.instanceId < obj.stacks.length) @@ -357,10 +253,11 @@ class Toolbar { /** @summary change brightness */ changeBrightness(bright) { - if (this.bright === bright) return; - this.element.selectAll('*').remove(); - this.bright = bright; - this.createButtons(); + if (this.bright !== bright) { + this.element.selectAll('*').remove(); + this.bright = bright; + this.createButtons(); + } } /** @summary cleanup toolbar */ @@ -394,7 +291,8 @@ class GeoDrawingControl extends InteractiveControl { /** @summary draw special */ drawSpecial(col, indx) { const c = this.mesh; - if (!c?.material) return; + if (!c?.material) + return; if (c.isInstancedMesh) { if (c._highlight_mesh) { @@ -403,16 +301,16 @@ class GeoDrawingControl extends InteractiveControl { } if (col && indx !== undefined) { - const h = new Mesh(c.geometry, c.material.clone()); + const h = new THREE.Mesh(c.geometry, c.material.clone()); if (this.bloom) { h.layers.enable(_BLOOM_SCENE); - h.material.emissive = new Color(0x00ff00); + h.material.emissive = new THREE.Color(0x00ff00); } else { - h.material.color = new Color(col); + h.material.color = new THREE.Color(col); h.material.opacity = 1.0; } - const m = new Matrix4(); + const m = new THREE.Matrix4(); c.getMatrixAt(indx, m); h.applyMatrix4(m); c.add(h); @@ -427,18 +325,18 @@ class GeoDrawingControl extends InteractiveControl { if (col) { if (!c.origin) { c.origin = { - color: c.material.color, - emissive: c.material.emissive, - opacity: c.material.opacity, - width: c.material.linewidth, - size: c.material.size - }; + color: c.material.color, + emissive: c.material.emissive, + opacity: c.material.opacity, + width: c.material.linewidth, + size: c.material.size + }; } if (this.bloom) { c.layers.enable(_BLOOM_SCENE); - c.material.emissive = new Color(0x00ff00); + c.material.emissive = new THREE.Color(0x00ff00); } else { - c.material.color = new Color(col); + c.material.color = new THREE.Color(col); c.material.opacity = 1.0; } @@ -477,6 +375,83 @@ const stageInit = 0, stageCollect = 1, stageWorkerCollect = 2, stageAnalyze = 3, class TGeoPainter extends ObjectPainter { + #geo_manager; // TGeoManager instance - if assigned in drawing + #superimpose; // set when superimposed with other 3D drawings + #complete_handler; // callback, assigned by ROOT GeomViewer + #geom_viewer; // true when processing drawing from ROOT GeomViewer + #extra_objects; // TList with extra objects configured for drawing + #webgl; // true when normal WebGL drawing is enabled + #worker; // extra Worker to run different calculations + #worker_ready; // is worker started and initialized + #worker_jobs; // number of submitted to worker jobs + #clones; // instance of ClonedNodes + #clones_owner; // is instance managed by the painter + #draw_nodes; // drawn nodes from geometry + #build_shapes; // build shapes required for drawings + #current_face_limit; // current face limit + #draw_all_nodes; // flag using in drawing + #draw_nodes_again; // flag set if drawing should be started again when completed + #drawing_ready; // if drawing completed + #drawing_log; // current drawing log information + #draw_resolveFuncs; // resolve call-backs for start drawing calls + #start_drawing_time; // time when drawing started + #start_render_tm; // time when rendering started + #first_render_tm; // first time when rendering was performed + #last_render_tm; // last time when rendering was performed + #last_render_meshes; // last value of rendered meshes + #render_resolveFuncs; // resolve call-backs from render calls + #render_tmout; // timeout used in Render3D() + #first_drawing; // true when very first drawing is performed + #full_redrawing; // if full redraw must be performed + #last_clip_cfg; // last config of clip panels + #redraw_timer; // timer used in redraw + #did_update; // flag used in update + #custom_bounding_box; // custom bounding box for extra objects + #clip_planes; // clip planes + #central_painter; // pointer on central painter + #subordinate_painters; // list of subordinate painters + #effectComposer; // extra composer for special effects, used in EVE + #bloomComposer; // extra composer for bloom highlight + #new_draw_nodes; // temporary list of new draw nodes + #new_append_nodes; // temporary list of new append nodes + #more_nodes; // more nodes from ROOT geometry viewer + #provided_more_nodes; // staged more nodes from ROOT geometry viewer + #on_pad; // true when drawing done on TPad + #last_manifest; // last node which was "manifest" via menu + #last_hidden; // last node which was "hidden" via menu + #gui; // dat.GUI instance + #toolbar; // tool buttons + #controls; // orbit control + #highlight_handlers; // highlight handlers + #animating; // set when animation started + #last_camera_position; // last camera position + #fullgeom_proj; // full geometry to produce projection + #selected_mesh; // used in highlight + #fog; // fog for the scene + #lookat; // position to which camera looks at + #scene_size; // stored scene size to control changes + #scene_width; // actual scene width + #scene_height; // actual scene width + #fit_main_area; // true if drawing must fit to DOM + #overall_size; // overall size + #scene; // three.js Scene object + #camera; // three.js camera object + #renderer; // three.js renderer object + #toplevel; // three.js top level Object3D + #camera0pos; // camera on opposite side + #vrControllers; // used in VR display + #controllersMeshes; // used in VR display + #dolly; // used in VR display + #vrDisplay; // used in VR display + #vr_camera_position; // used in VR display + #vr_camera_rotation; // used in VR display + #vr_camera_near; // used in VR display + #standingMatrix; // used in VR display + #raycasterEnd; // used in VR display + #raycasterOrigin; // used in VR display + #adjust_camera_with_render; // flag to readjust camera when rendering + #did_cleanup; // flag set after cleanup + /** @summary Constructor * @param {object|string} dom - DOM element for drawing or element id * @param {object} obj - supported TGeo object */ @@ -492,15 +467,13 @@ class TGeoPainter extends ObjectPainter { super(dom, obj); - if (getHistPainter3DCfg(this.getMainPainter())) - this.superimpose = true; + this.#superimpose = Boolean(getHistPainter3DCfg(this.getMainPainter())); + this.#geo_manager = gm; - if (gm) this.geo_manager = gm; - - this.no_default_title = true; // do not set title to main DIV + this._no_default_title = true; // do not set title to main DIV this.mode3d = true; // indication of 3D mode this.drawing_stage = stageInit; // - this.drawing_log = 'Init'; + this.#drawing_log = 'Init'; this.ctrl = { clipIntersect: true, clipVisualize: false, @@ -534,7 +507,7 @@ class TGeoPainter extends ObjectPainter { ], trans_radial: 0, trans_z: 0, - scale: new Vector3(1, 1, 1), + scale: new THREE.Vector3(1, 1, 1), zoom: 1.0, rotatey: 0, rotatez: 0, depthMethodItems: [ { name: 'Default', value: 'dflt' }, @@ -542,8 +515,8 @@ class TGeoPainter extends ObjectPainter { { name: 'Boundary box', value: 'box' }, { name: 'Mesh size', value: 'size' }, { name: 'Central point', value: 'pnt' } - ], - cameraKindItems: [ + ], + cameraKindItems: [ { name: 'Perspective', value: 'perspective' }, { name: 'Perspective (Floor XOZ)', value: 'perspXOZ' }, { name: 'Perspective (Floor YOZ)', value: 'perspYOZ' }, @@ -586,30 +559,77 @@ class TGeoPainter extends ObjectPainter { { name: 'MeshLambertMaterial', value: 'lambert', emissive: true, props: [{ name: 'flatShading' }] }, { name: 'MeshBasicMaterial', value: 'basic' }, { name: 'MeshStandardMaterial', value: 'standard', emissive: true, - props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }] }, + props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }] }, { name: 'MeshPhysicalMaterial', value: 'physical', emissive: true, props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }, { name: 'reflectivity', min: 0, max: 1, step: 0.001 }] }, { name: 'MeshPhongMaterial', value: 'phong', emissive: true, - props: [{ name: 'flatShading' }, { name: 'shininess', min: 0, max: 100, step: 0.1 }] }, + props: [{ name: 'flatShading' }, { name: 'shininess', min: 0, max: 100, step: 0.1 }] }, { name: 'MeshNormalMaterial', value: 'normal', props: [{ name: 'flatShading' }] }, { name: 'MeshDepthMaterial', value: 'depth' }, { name: 'MeshMatcapMaterial', value: 'matcap' }, { name: 'MeshToonMaterial', value: 'toon' } ], - getMaterialCfg: function() { - let cfg; - this.materialKinds.forEach(item => { - if (item.value === this.material_kind) - cfg = item; - }); - return cfg; + getMaterialCfg() { + let cfg; + this.materialKinds.forEach(item => { + if (item.value === this.material_kind) + cfg = item; + }); + return cfg; } }; + this.#draw_resolveFuncs = []; + this.#render_resolveFuncs = []; + this.cleanup(true); } - /** @summary Function callled by framework when dark mode is changed + /** @summary Returns scene */ + getScene() { return this.#scene; } + + /** @summary Returns camera */ + getCamera() { return this.#camera; } + + /** @summary Returns renderer */ + getRenderer() { return this.#renderer; } + + /** @summary Returns top Object3D instance */ + getTopObject3D() { return this.#toplevel; } + + /** @summary Assign geometry viewer mode + * @private */ + setGeomViewer(on) { this.#geom_viewer = on; } + + /** @summary Assign or remove subordinate painter */ + assignSubordinate(painter, do_assign = true) { + if (this.#subordinate_painters === undefined) + this.#subordinate_painters = []; + const pos = this.#subordinate_painters.indexOf(painter); + if (do_assign && pos < 0) + this.#subordinate_painters.push(painter); + else if (!do_assign && pos >= 0) + this.#subordinate_painters.splice(pos, 1); + } + + /** @summary Return list of subordinate painters */ + getSubordinates() { return this.#subordinate_painters ?? []; } + + /** @summary Assign or cleanup central painter */ + assignCentral(painter, do_assign = true) { + if (do_assign) + this.#central_painter = painter; + else if (this.#central_painter === painter) + this.#central_painter = undefined; + } + + /** @summary Returns central painter */ + getCentral() { return this.#central_painter; } + + /** @summary Returns extra objects configured for drawing */ + getExtraObjects() { return this.#extra_objects; } + + /** @summary Function called by framework when dark mode is changed * @private */ changeDarkMode(mode) { if ((this.ctrl.background === '#000000') || (this.ctrl.background === '#ffffff')) @@ -636,7 +656,7 @@ class TGeoPainter extends ObjectPainter { default: msg = `stage ${value}`; } } - this.drawing_log = msg; + this.#drawing_log = msg; } /** @summary Check drawing stage */ @@ -644,9 +664,12 @@ class TGeoPainter extends ObjectPainter { isBatchMode() { return isBatchMode() || this.batch_mode; } + getControls() { return this.#controls; } + /** @summary Create toolbar */ createToolbar() { - if (this._toolbar || !this._webgl || this.ctrl.notoolbar || this.isBatchMode()) return; + if (this.#toolbar || !this.#webgl || this.ctrl.notoolbar || this.isBatchMode()) + return; const buttonList = [{ name: 'toImage', title: 'Save as PNG', @@ -684,42 +707,44 @@ class TGeoPainter extends ObjectPainter { evnt.preventDefault(); evnt.stopPropagation(); - if (closeMenu()) return; + if (closeMenu()) + return; createMenu(evnt, this).then(menu => { - menu.painter.fillContextMenu(menu); - menu.show(); + menu.painter.fillContextMenu(menu); + menu.show(); }); } }); } - const bkgr = new Color(this.ctrl.background); + const bkgr = new THREE.Color(this.ctrl.background); - this._toolbar = new Toolbar(this.selectDom(), (bkgr.r + bkgr.g + bkgr.b) < 1, buttonList); + this.#toolbar = new Toolbar(this.selectDom(), (bkgr.r + bkgr.g + bkgr.b) < 1, buttonList); - this._toolbar.createButtons(); + this.#toolbar.createButtons(); } /** @summary Initialize VR mode */ initVRMode() { // Dolly contains camera and controllers in VR Mode // Allows moving the user in the scene - this._dolly = new Group(); - this._scene.add(this._dolly); - this._standingMatrix = new Matrix4(); + this.#dolly = new THREE.Group(); + this.#scene.add(this.#dolly); + this.#standingMatrix = new THREE.Matrix4(); // Raycaster temp variables to avoid one per frame allocation. - this._raycasterEnd = new Vector3(); - this._raycasterOrigin = new Vector3(); + this.#raycasterEnd = new THREE.Vector3(); + this.#raycasterOrigin = new THREE.Vector3(); navigator.getVRDisplays().then(displays => { const vrDisplay = displays[0]; - if (!vrDisplay) return; - this._renderer.vr.setDevice(vrDisplay); - this._vrDisplay = vrDisplay; + if (!vrDisplay) + return; + this.#renderer.vr.setDevice(vrDisplay); + this.#vrDisplay = vrDisplay; if (vrDisplay.stageParameters) - this._standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); + this.#standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); this.initVRControllersGeometry(); }); @@ -728,24 +753,24 @@ class TGeoPainter extends ObjectPainter { /** @summary Init VR controllers geometry * @private */ initVRControllersGeometry() { - const geometry = new SphereGeometry(0.025, 18, 36), - material = new MeshBasicMaterial({ color: 'grey', vertexColors: false }), - rayMaterial = new MeshBasicMaterial({ color: 'fuchsia', vertexColors: false }), - rayGeometry = new BoxGeometry(0.001, 0.001, 2), - ray1Mesh = new Mesh(rayGeometry, rayMaterial), - ray2Mesh = new Mesh(rayGeometry, rayMaterial), - sphere1 = new Mesh(geometry, material), - sphere2 = new Mesh(geometry, material); - - this._controllersMeshes = []; - this._controllersMeshes.push(sphere1); - this._controllersMeshes.push(sphere2); + const geometry = new THREE.SphereGeometry(0.025, 18, 36), + material = new THREE.MeshBasicMaterial({ color: 'grey', vertexColors: false }), + rayMaterial = new THREE.MeshBasicMaterial({ color: 'fuchsia', vertexColors: false }), + rayGeometry = new THREE.BoxGeometry(0.001, 0.001, 2), + ray1Mesh = new THREE.Mesh(rayGeometry, rayMaterial), + ray2Mesh = new THREE.Mesh(rayGeometry, rayMaterial), + sphere1 = new THREE.Mesh(geometry, material), + sphere2 = new THREE.Mesh(geometry, material); + + this.#controllersMeshes = []; + this.#controllersMeshes.push(sphere1); + this.#controllersMeshes.push(sphere2); ray1Mesh.position.z -= 1; ray2Mesh.position.z -= 1; sphere1.add(ray1Mesh); sphere2.add(ray2Mesh); - this._dolly.add(sphere1); - this._dolly.add(sphere2); + this.#dolly.add(sphere1); + this.#dolly.add(sphere2); // Controller mesh hidden by default sphere1.visible = false; sphere2.visible = false; @@ -756,17 +781,19 @@ class TGeoPainter extends ObjectPainter { updateVRControllersList() { const gamepads = navigator.getGamepads && navigator.getGamepads(); // Has controller list changed? - if (this.vrControllers && (gamepads.length === this.vrControllers.length)) return; + if (this.#vrControllers && (gamepads.length === this.#vrControllers.length)) + return; // Hide meshes. - this._controllersMeshes.forEach(mesh => { mesh.visible = false; }); - this._vrControllers = []; + this.#controllersMeshes.forEach(mesh => { mesh.visible = false; }); + this.#vrControllers = []; for (let i = 0; i < gamepads.length; ++i) { - if (!gamepads[i] || !gamepads[i].pose) continue; - this._vrControllers.push({ + if (!gamepads[i] || !gamepads[i].pose) + continue; + this.#vrControllers.push({ gamepad: gamepads[i], - mesh: this._controllersMeshes[i] + mesh: this.#controllersMeshes[i] }); - this._controllersMeshes[i].visible = true; + this.#controllersMeshes[i].visible = true; } } @@ -774,16 +801,16 @@ class TGeoPainter extends ObjectPainter { * @private */ processVRControllerIntersections() { let intersects = []; - for (let i = 0; i < this._vrControllers.length; ++i) { - const controller = this._vrControllers[i].mesh, - end = controller.localToWorld(this._raycasterEnd.set(0, 0, -1)), - origin = controller.localToWorld(this._raycasterOrigin.set(0, 0, 0)); + for (let i = 0; i < this.#vrControllers.length; ++i) { + const controller = this.#vrControllers[i].mesh, + end = controller.localToWorld(this.#raycasterEnd.set(0, 0, -1)), + origin = controller.localToWorld(this.#raycasterOrigin.set(0, 0, 0)); end.sub(origin).normalize(); - intersects = intersects.concat(this._controls.getOriginDirectionIntersects(origin, end)); + intersects = intersects.concat(this.#controls.getOriginDirectionIntersects(origin, end)); } // Remove duplicates. intersects = intersects.filter((item, pos) => { return intersects.indexOf(item) === pos; }); - this._controls.processMouseMove(intersects); + this.#controls.processMouseMove(intersects); } /** @summary Update VR controllers @@ -791,15 +818,17 @@ class TGeoPainter extends ObjectPainter { updateVRControllers() { this.updateVRControllersList(); // Update pose. - for (let i = 0; i < this._vrControllers.length; ++i) { - const controller = this._vrControllers[i], + for (let i = 0; i < this.#vrControllers.length; ++i) { + const controller = this.#vrControllers[i], orientation = controller.gamepad.pose.orientation, position = controller.gamepad.pose.position, controllerMesh = controller.mesh; - if (orientation) controllerMesh.quaternion.fromArray(orientation); - if (position) controllerMesh.position.fromArray(position); + if (orientation) + controllerMesh.quaternion.fromArray(orientation); + if (position) + controllerMesh.position.fromArray(position); controllerMesh.updateMatrix(); - controllerMesh.applyMatrix4(this._standingMatrix); + controllerMesh.applyMatrix4(this.#standingMatrix); controllerMesh.matrixWorldNeedsUpdate = true; } this.processVRControllerIntersections(); @@ -808,60 +837,62 @@ class TGeoPainter extends ObjectPainter { /** @summary Toggle VR mode * @private */ toggleVRMode() { - if (!this._vrDisplay) return; + if (!this.#vrDisplay) + return; // Toggle VR mode off - if (this._vrDisplay.isPresenting) { + if (this.#vrDisplay.isPresenting) { this.exitVRMode(); return; } - this._previousCameraPosition = this._camera.position.clone(); - this._previousCameraRotation = this._camera.rotation.clone(); - this._vrDisplay.requestPresent([{ source: this._renderer.domElement }]).then(() => { - this._previousCameraNear = this._camera.near; - this._dolly.position.set(this._camera.position.x/4, -this._camera.position.y/8, -this._camera.position.z/4); - this._camera.position.set(0, 0, 0); - this._dolly.add(this._camera); - this._camera.near = 0.1; - this._camera.updateProjectionMatrix(); - this._renderer.vr.enabled = true; - this._renderer.setAnimationLoop(() => { + this.#vr_camera_position = this.#camera.position.clone(); + this.#vr_camera_rotation = this.#camera.rotation.clone(); + this.#vrDisplay.requestPresent([{ source: this.#renderer.domElement }]).then(() => { + this.#vr_camera_near = this.#camera.near; + this.#dolly.position.set(this.#camera.position.x / 4, -this.#camera.position.y / 8, -this.#camera.position.z / 4); + this.#camera.position.set(0, 0, 0); + this.#dolly.add(this.#camera); + this.#camera.near = 0.1; + this.#camera.updateProjectionMatrix(); + this.#renderer.vr.enabled = true; + this.#renderer.setAnimationLoop(() => { this.updateVRControllers(); this.render3D(0); }); }); - this._renderer.vr.enabled = true; + this.#renderer.vr.enabled = true; window.addEventListener('keydown', evnt => { // Esc Key turns VR mode off - if (evnt.code === 'Escape') this.exitVRMode(); + if (evnt.code === 'Escape') + this.exitVRMode(); }); } /** @summary Exit VR mode * @private */ exitVRMode() { - if (!this._vrDisplay.isPresenting) return; - this._renderer.vr.enabled = false; - this._dolly.remove(this._camera); - this._scene.add(this._camera); + if (!this.#vrDisplay.isPresenting) + return; + this.#renderer.vr.enabled = false; + this.#dolly.remove(this.#camera); + this.#scene.add(this.#camera); // Restore Camera pose - this._camera.position.copy(this._previousCameraPosition); - this._previousCameraPosition = undefined; - this._camera.rotation.copy(this._previousCameraRotation); - this._previousCameraRotation = undefined; - this._camera.near = this._previousCameraNear; - this._camera.updateProjectionMatrix(); - this._vrDisplay.exitPresent(); + this.#camera.position.copy(this.#vr_camera_position); + this.#vr_camera_position = undefined; + this.#camera.rotation.copy(this.#vr_camera_rotation); + this.#vr_camera_rotation = undefined; + this.#camera.near = this.#vr_camera_near; + this.#camera.updateProjectionMatrix(); + this.#vrDisplay.exitPresent(); } /** @summary Returns main geometry object */ - getGeometry() { - return this.getObject(); - } + getGeometry() { return this.getObject(); } /** @summary Modify visibility of provided node by name */ modifyVisisbility(name, sign) { - if (getNodeKind(this.getGeometry()) !== 0) return; + if (getNodeKind(this.getGeometry()) !== kindGeo) + return; if (!name) return setGeoBit(this.getGeometry().fVolume, geoBITS.kVisThis, (sign === '+')); @@ -870,7 +901,7 @@ class TGeoPainter extends ObjectPainter { // arg.node.fVolume if (name.indexOf('*') < 0) { - regexp = new RegExp('^'+name+'$'); + regexp = new RegExp('^' + name + '$'); exact = true; } else { regexp = new RegExp('^' + name.split('*').join('.*') + '$'); @@ -885,33 +916,39 @@ class TGeoPainter extends ObjectPainter { /** @summary Decode drawing options */ decodeOptions(opt) { - if (!isStr(opt)) opt = ''; + if (!isStr(opt)) + opt = ''; - if (this.superimpose && (opt.indexOf('same') === 0)) + if (this.#superimpose && (opt.indexOf('same') === 0)) opt = opt.slice(4); - const res = this.ctrl, + const res = this.ctrl, macro = opt.indexOf('macro:'); - macro = opt.indexOf('macro:'); if (macro >= 0) { - let separ = opt.indexOf(';', macro+6); - if (separ < 0) separ = opt.length; - res.script_name = opt.slice(macro+6, separ); - opt = opt.slice(0, macro) + opt.slice(separ+1); + let separ = opt.indexOf(';', macro + 6); + if (separ < 0) + separ = opt.length; + res.script_name = opt.slice(macro + 6, separ); + opt = opt.slice(0, macro) + opt.slice(separ + 1); console.log(`script ${res.script_name} rest ${opt}`); } while (true) { const pp = opt.indexOf('+'), pm = opt.indexOf('-'); - if ((pp < 0) && (pm < 0)) break; + if ((pp < 0) && (pm < 0)) + break; let p1 = pp, sign = '+'; - if ((p1 < 0) || ((pm >= 0) && (pm < pp))) { p1 = pm; sign = '-'; } + if ((p1 < 0) || ((pm >= 0) && (pm < pp))) { + p1 = pm; + sign = '-'; + } - let p2 = p1+1; + let p2 = p1 + 1; const regexp = /[,; .]/; - while ((p2 < opt.length) && !regexp.test(opt[p2]) && (opt[p2] !== '+') && (opt[p2] !== '-')) p2++; + while ((p2 < opt.length) && !regexp.test(opt[p2]) && (opt[p2] !== '+') && (opt[p2] !== '-')) + p2++; - const name = opt.substring(p1+1, p2); + const name = opt.substring(p1 + 1, p2); opt = opt.slice(0, p1) + opt.slice(p2); this.modifyVisisbility(name, sign); @@ -919,39 +956,76 @@ class TGeoPainter extends ObjectPainter { const d = new DrawOptions(opt); - if (d.check('MAIN')) res.is_main = true; - - if (d.check('TRACKS')) res.tracks = true; // only for TGeoManager - if (d.check('SHOWTOP')) res.showtop = true; // only for TGeoManager - if (d.check('NO_SCREEN')) res.no_screen = true; // ignore kVisOnScreen bits for visibility - - if (d.check('NOINSTANCING')) res.instancing = -1; // disable usage of InstancedMesh - if (d.check('INSTANCING')) res.instancing = 1; // force usage of InstancedMesh - - if (d.check('ORTHO_CAMERA')) { res.camera_kind = 'orthoXOY'; res.can_rotate = 0; } - if (d.check('ORTHO', true)) { res.camera_kind = 'ortho' + d.part; res.can_rotate = 0; } - if (d.check('OVERLAY', true)) res.camera_overlay = d.part.toLowerCase(); - if (d.check('CAN_ROTATE')) res.can_rotate = true; - if (d.check('PERSPECTIVE')) { res.camera_kind = 'perspective'; res.can_rotate = true; } - if (d.check('PERSP', true)) { res.camera_kind = 'persp' + d.part; res.can_rotate = true; } - if (d.check('MOUSE_CLICK')) res.mouse_click = true; - - if (d.check('DEPTHRAY') || d.check('DRAY')) res.depthMethod = 'ray'; - if (d.check('DEPTHBOX') || d.check('DBOX')) res.depthMethod = 'box'; - if (d.check('DEPTHPNT') || d.check('DPNT')) res.depthMethod = 'pnt'; - if (d.check('DEPTHSIZE') || d.check('DSIZE')) res.depthMethod = 'size'; - if (d.check('DEPTHDFLT') || d.check('DDFLT')) res.depthMethod = 'dflt'; - - if (d.check('ZOOM', true)) res.zoom = d.partAsFloat(0, 100) / 100; - if (d.check('ROTY', true)) res.rotatey = d.partAsFloat(); - if (d.check('ROTZ', true)) res.rotatez = d.partAsFloat(); - - if (d.check('PHONG')) res.material_kind = 'phong'; - if (d.check('LAMBERT')) res.material_kind = 'lambert'; - if (d.check('MATCAP')) res.material_kind = 'matcap'; - if (d.check('TOON')) res.material_kind = 'toon'; - - if (d.check('AMBIENT')) res.light.kind = 'ambient'; + if (d.check('MAIN')) + res.is_main = true; + + if (d.check('DUMMY')) + res.dummy = true; + + if (d.check('TRACKS')) + res.tracks = true; // only for TGeoManager + if (d.check('SHOWTOP')) + res.showtop = true; // only for TGeoManager + if (d.check('NO_SCREEN')) + res.no_screen = true; // ignore kVisOnScreen bits for visibility + + if (d.check('NOINSTANCING')) + res.instancing = -1; // disable usage of InstancedMesh + if (d.check('INSTANCING')) + res.instancing = 1; // force usage of InstancedMesh + + if (d.check('ORTHO_CAMERA')) { + res.camera_kind = 'orthoXOY'; + res.can_rotate = 0; + } + if (d.check('ORTHO', true)) { + res.camera_kind = 'ortho' + d.part; + res.can_rotate = 0; + } + if (d.check('OVERLAY', true)) + res.camera_overlay = d.part.toLowerCase(); + if (d.check('CAN_ROTATE')) + res.can_rotate = true; + if (d.check('PERSPECTIVE')) { + res.camera_kind = 'perspective'; + res.can_rotate = true; + } + if (d.check('PERSP', true)) { + res.camera_kind = 'persp' + d.part; + res.can_rotate = true; + } + if (d.check('MOUSE_CLICK')) + res.mouse_click = true; + + if (d.check('DEPTHRAY') || d.check('DRAY')) + res.depthMethod = 'ray'; + if (d.check('DEPTHBOX') || d.check('DBOX')) + res.depthMethod = 'box'; + if (d.check('DEPTHPNT') || d.check('DPNT')) + res.depthMethod = 'pnt'; + if (d.check('DEPTHSIZE') || d.check('DSIZE')) + res.depthMethod = 'size'; + if (d.check('DEPTHDFLT') || d.check('DDFLT')) + res.depthMethod = 'dflt'; + + if (d.check('ZOOM', true)) + res.zoom = d.partAsFloat(0, 100) / 100; + if (d.check('ROTY', true)) + res.rotatey = d.partAsFloat(); + if (d.check('ROTZ', true)) + res.rotatez = d.partAsFloat(); + + if (d.check('PHONG')) + res.material_kind = 'phong'; + if (d.check('LAMBERT')) + res.material_kind = 'lambert'; + if (d.check('MATCAP')) + res.material_kind = 'matcap'; + if (d.check('TOON')) + res.material_kind = 'toon'; + + if (d.check('AMBIENT')) + res.light.kind = 'ambient'; const getCamPart = () => { let neg = 1; @@ -962,182 +1036,268 @@ class TGeoPainter extends ObjectPainter { return neg * d.partAsFloat(); }; - if (d.check('CAMX', true)) res.camx = getCamPart(); - if (d.check('CAMY', true)) res.camy = getCamPart(); - if (d.check('CAMZ', true)) res.camz = getCamPart(); - if (d.check('CAMLX', true)) res.camlx = getCamPart(); - if (d.check('CAMLY', true)) res.camly = getCamPart(); - if (d.check('CAMLZ', true)) res.camlz = getCamPart(); - - if (d.check('BLACK')) res.background = '#000000'; - if (d.check('WHITE')) res.background = '#FFFFFF'; + if (d.check('CAMX', true)) + res.camx = getCamPart(); + if (d.check('CAMY', true)) + res.camy = getCamPart(); + if (d.check('CAMZ', true)) + res.camz = getCamPart(); + if (d.check('CAMLX', true)) + res.camlx = getCamPart(); + if (d.check('CAMLY', true)) + res.camly = getCamPart(); + if (d.check('CAMLZ', true)) + res.camlz = getCamPart(); + + if (d.check('BLACK')) + res.background = '#000000'; + if (d.check('WHITE')) + res.background = '#FFFFFF'; if (d.check('BKGR_', true)) { let bckgr = null; if (d.partAsInt(1) > 0) bckgr = getColor(d.partAsInt()); - else { + else { for (let col = 0; col < 8; ++col) { if (getColor(col).toUpperCase() === d.part) bckgr = getColor(col); } } - if (bckgr) res.background = '#' + new Color(bckgr).getHexString(); + if (bckgr) + res.background = '#' + new THREE.Color(bckgr).getHexString(); } if (d.check('R3D_', true)) res.Render3D = constants.Render3D.fromString(d.part.toLowerCase()); - if (d.check('MORE', true)) res.more = d.partAsInt(0, 2) ?? 2; - if (d.check('ALL')) { res.more = 100; res.vislevel = 99; } - - if (d.check('VISLVL', true)) res.vislevel = d.partAsInt(); - if (d.check('MAXNODES', true)) res.maxnodes = d.partAsInt(); - if (d.check('MAXFACES', true)) res.maxfaces = d.partAsInt(); - - if (d.check('CONTROLS') || d.check('CTRL')) res.show_controls = true; - - if (d.check('CLIPXYZ')) res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; - if (d.check('CLIPX')) res.clip[0].enabled = true; - if (d.check('CLIPY')) res.clip[1].enabled = true; - if (d.check('CLIPZ')) res.clip[2].enabled = true; - if (d.check('CLIP')) res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; - - if (d.check('PROJX', true)) { res.project = 'x'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } - if (d.check('PROJY', true)) { res.project = 'y'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } - if (d.check('PROJZ', true)) { res.project = 'z'; if (d.partAsInt(1) > 0) res.projectPos = d.partAsInt(); res.can_rotate = 0; } + if (d.check('MORE', true)) + res.more = d.partAsInt(0, 2) ?? 2; + if (d.check('ALL')) { + res.more = 100; + res.vislevel = 99; + } + + if (d.check('VISLVL', true)) + res.vislevel = d.partAsInt(); + if (d.check('MAXNODES', true)) + res.maxnodes = d.partAsInt(); + if (d.check('MAXFACES', true)) + res.maxfaces = d.partAsInt(); + + if (d.check('CONTROLS') || d.check('CTRL')) + res.show_controls = true; + + if (d.check('CLIPXYZ')) + res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; + if (d.check('CLIPX')) + res.clip[0].enabled = true; + if (d.check('CLIPY')) + res.clip[1].enabled = true; + if (d.check('CLIPZ')) + res.clip[2].enabled = true; + if (d.check('CLIP')) + res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; + + if (d.check('PROJX', true)) { + res.project = 'x'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } + if (d.check('PROJY', true)) { + res.project = 'y'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } + if (d.check('PROJZ', true)) { + res.project = 'z'; + if (d.partAsInt(1) > 0) + res.projectPos = d.partAsInt(); + res.can_rotate = 0; + } - if (d.check('DFLT_COLORS') || d.check('DFLT')) res.dflt_colors = true; + if (d.check('DFLT_COLORS') || d.check('DFLT')) + res.dflt_colors = true; d.check('SSAO'); // deprecated - if (d.check('NOBLOOM')) res.highlight_bloom = false; - if (d.check('BLOOM')) res.highlight_bloom = true; - if (d.check('OUTLINE')) res.outline = true; - - if (d.check('NOWORKER')) res.use_worker = -1; - if (d.check('WORKER')) res.use_worker = 1; - - if (d.check('NOFOG')) res.use_fog = false; - if (d.check('FOG')) res.use_fog = true; - - if (d.check('NOHIGHLIGHT') || d.check('NOHIGH')) res.highlight_scene = res.highlight = false; - if (d.check('HIGHLIGHT')) res.highlight_scene = res.highlight = true; - if (d.check('HSCENEONLY')) { res.highlight_scene = true; res.highlight = false; } - if (d.check('NOHSCENE')) res.highlight_scene = false; - if (d.check('HSCENE')) res.highlight_scene = true; - - if (d.check('WIREFRAME') || d.check('WIRE')) res.wireframe = true; - if (d.check('ROTATE')) res.rotate = true; - - if (d.check('INVX') || d.check('INVERTX')) res.scale.x = -1; - if (d.check('INVY') || d.check('INVERTY')) res.scale.y = -1; - if (d.check('INVZ') || d.check('INVERTZ')) res.scale.z = -1; - - if (d.check('COUNT')) res._count = true; + if (d.check('NOBLOOM')) + res.highlight_bloom = false; + if (d.check('BLOOM')) + res.highlight_bloom = true; + if (d.check('OUTLINE')) + res.outline = true; + + if (d.check('NOWORKER')) + res.use_worker = -1; + if (d.check('WORKER')) + res.use_worker = 1; + + if (d.check('NOFOG')) + res.use_fog = false; + if (d.check('FOG')) + res.use_fog = true; + + if (d.check('NOHIGHLIGHT') || d.check('NOHIGH')) + res.highlight_scene = res.highlight = false; + if (d.check('HIGHLIGHT')) + res.highlight_scene = res.highlight = true; + if (d.check('HSCENEONLY')) { + res.highlight_scene = true; + res.highlight = false; + } + if (d.check('NOHSCENE')) + res.highlight_scene = false; + if (d.check('HSCENE')) + res.highlight_scene = true; + + if (d.check('WIREFRAME') || d.check('WIRE')) + res.wireframe = true; + if (d.check('ROTATE')) + res.rotate = true; + + if (d.check('INVX') || d.check('INVERTX')) + res.scale.x = -1; + if (d.check('INVY') || d.check('INVERTY')) + res.scale.y = -1; + if (d.check('INVZ') || d.check('INVERTZ')) + res.scale.z = -1; + + if (d.check('COUNT')) + res._count = true; if (d.check('TRANSP', true)) - res.transparency = d.partAsInt(0, 100)/100; + res.transparency = d.partAsInt(0, 100) / 100; if (d.check('OPACITY', true)) - res.transparency = 1 - d.partAsInt(0, 100)/100; - - if (d.check('AXISCENTER') || d.check('AXISC') || d.check('AC')) res._axis = 2; - if (d.check('AXIS') || d.check('A')) res._axis = 1; + res.transparency = 1 - d.partAsInt(0, 100) / 100; - if (d.check('TRR', true)) res.trans_radial = d.partAsInt()/100; - if (d.check('TRZ', true)) res.trans_z = d.partAsInt()/100; + if (d.check('AXISCENTER') || d.check('AXISC') || d.check('AC')) + res._axis = 2; + if (d.check('AXIS') || d.check('A')) + res._axis = 1; + if (d.check('TRR', true)) + res.trans_radial = d.partAsInt() / 100; + if (d.check('TRZ', true)) + res.trans_z = d.partAsInt() / 100; - if (d.check('W')) res.wireframe = true; - if (d.check('Y')) res._yup = true; - if (d.check('Z')) res._yup = false; + if (d.check('W')) + res.wireframe = true; + if (d.check('Y')) + res._yup = true; + if (d.check('Z')) + res._yup = false; // when drawing geometry without TCanvas, yup = true by default if (res._yup === undefined) res._yup = this.getCanvSvg().empty(); // let reuse for storing origin options - this.options = res; + this.setOptions(res, true); } /** @summary Activate specified items in the browser */ activateInBrowser(names, force) { - if (isStr(names)) names = [names]; + if (isStr(names)) + names = [names]; - if (this._hpainter) { + const h = this.getHPainter(); + + if (h) { // show browser if it not visible - this._hpainter.activateItems(names, force); + h.activateItems(names, force); // if highlight in the browser disabled, suppress in few seconds if (!this.ctrl.update_browser) - setTimeout(() => this._hpainter.activateItems([]), 2000); + setTimeout(() => h.activateItems([]), 2000); } } + /** @summary Return ClonedNodes instance from the painter */ + getClones() { return this.#clones; } + + /** @summary Assign clones, created outside. + * @desc Used by ROOT geometry painter, where clones are handled by the server */ + assignClones(clones, owner = true) { + if (this.#clones_owner) + this.#clones?.cleanup(this.#draw_nodes, this.#build_shapes); + this.#draw_nodes = undefined; + this.#build_shapes = undefined; + this.#drawing_ready = false; + + this.#clones = clones; + this.#clones_owner = owner; + } + /** @summary method used to check matrix calculations performance with current three.js model */ testMatrixes() { let errcnt = 0, totalcnt = 0, totalmax = 0; const arg = { - domatrix: true, - func: (/* node */) => { - let m2 = this.getmatrix(); - const entry = this.copyStack(), - mesh = this._clones.createObject3D(entry.stack, this._toplevel, 'mesh'); - if (!mesh) return true; - - totalcnt++; - - const m1 = mesh.matrixWorld; - if (m1.equals(m2)) return true; - if ((m1.determinant() > 0) && (m2.determinant() < -0.9)) { - const flip = new Vector3(1, 1, -1); - m2 = m2.clone().scale(flip); - if (m1.equals(m2)) return true; - } + domatrix: true, + func: (/* node */) => { + let m2 = this.getmatrix(); + const entry = this.copyStack(), + mesh = this.#clones.createObject3D(entry.stack, this.#toplevel, kGetMesh); + if (!mesh) + return true; - let max = 0; - for (let k = 0; k < 16; ++k) - max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k])); + totalcnt++; + + const m1 = mesh.matrixWorld; + if (m1.equals(m2)) + return true; + if ((m1.determinant() > 0) && (m2.determinant() < -0.9)) { + const flip = new THREE.Vector3(1, 1, -1); + m2 = m2.clone().scale(flip); + if (m1.equals(m2)) + return true; + } - totalmax = Math.max(max, totalmax); + let max = 0; + for (let k = 0; k < 16; ++k) + max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k])); - if (max < 1e-4) return true; + totalmax = Math.max(max, totalmax); - console.log(`${this._clones.resolveStack(entry.stack).name} maxdiff ${max} determ ${m1.determinant()} ${m2.determinant()}`); + if (max < 1e-4) + return true; - errcnt++; + console.log(`${this.#clones.resolveStack(entry.stack).name} maxdiff ${max} determ ${m1.determinant()} ${m2.determinant()}`); - return false; - } - }, + errcnt++; - tm1 = new Date().getTime(); + return false; + } + }, tm1 = new Date().getTime(); - /* let cnt = */ this._clones.scanVisible(arg); + this.#clones.scanVisible(arg); const tm2 = new Date().getTime(); - console.log(`Compare matrixes total ${totalcnt} errors ${errcnt} takes ${tm2-tm1} maxdiff ${totalmax}`); + console.log(`Compare matrixes total ${totalcnt} errors ${errcnt} takes ${tm2 - tm1} maxdiff ${totalmax}`); } /** @summary Fill context menu */ fillContextMenu(menu) { - menu.add('header: Draw options'); + menu.header('Draw options'); menu.addchk(this.ctrl.update_browser, 'Browser update', () => { this.ctrl.update_browser = !this.ctrl.update_browser; - if (!this.ctrl.update_browser) this.activateInBrowser([]); + if (!this.ctrl.update_browser) + this.activateInBrowser([]); }); menu.addchk(this.ctrl.show_controls, 'Show Controls', () => this.showControlGui('toggle')); - menu.add('sub:Show axes', () => this.setAxesDraw('toggle')); + menu.sub('Show axes', () => this.setAxesDraw('toggle')); menu.addchk(this.ctrl._axis === 0, 'off', 0, arg => this.setAxesDraw(parseInt(arg))); menu.addchk(this.ctrl._axis === 1, 'side', 1, arg => this.setAxesDraw(parseInt(arg))); menu.addchk(this.ctrl._axis === 2, 'center', 2, arg => this.setAxesDraw(parseInt(arg))); - menu.add('endsub:'); + menu.endsub(); - if (this.geo_manager) + if (this.#geo_manager) menu.addchk(this.ctrl.showtop, 'Show top volume', () => this.setShowTop(!this.ctrl.showtop)); menu.addchk(this.ctrl.wireframe, 'Wire frame', () => this.toggleWireFrame()); @@ -1145,7 +1305,7 @@ class TGeoPainter extends ObjectPainter { if (!this.getCanvPainter()) menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - menu.add('sub:Highlight'); + menu.sub('Highlight'); menu.addchk(!this.ctrl.highlight, 'Off', () => { this.ctrl.highlight = false; @@ -1162,21 +1322,21 @@ class TGeoPainter extends ObjectPainter { this.changedHighlight(); }); - menu.add('separator'); + menu.separator(); menu.addchk(this.ctrl.highlight_scene, 'Scene', flag => { this.ctrl.highlight_scene = flag; this.changedHighlight(); }); - menu.add('endsub:'); + menu.endsub(); - menu.add('sub:Camera'); + menu.sub('Camera'); menu.add('Reset position', () => this.focusCamera()); if (!this.ctrl.project) - menu.addchk(this.ctrl.rotate, 'Autorotate', () => this.setAutoRotate(!this.ctrl.rotate)); + menu.addchk(this.ctrl.rotate, 'Autorotate', () => this.setAutoRotate(!this.ctrl.rotate)); - if (!this._geom_viewer) { + if (!this.#geom_viewer) { menu.addchk(this.canRotateCamera(), 'Can rotate', () => this.changeCanRotate(!this.ctrl.can_rotate)); menu.add('Get position', () => menu.info('Position (as url)', '&opt=' + this.produceCameraUrl())); @@ -1187,29 +1347,30 @@ class TGeoPainter extends ObjectPainter { }); } - menu.add('sub:Kind'); + menu.sub('Kind'); this.ctrl.cameraKindItems.forEach(item => menu.addchk(this.ctrl.camera_kind === item.value, item.name, item.value, arg => { this.ctrl.camera_kind = arg; this.changeCamera(); })); - menu.add('endsub:'); + menu.endsub(); if (this.isOrthoCamera()) { - menu.add('sub:Overlay'); + menu.sub('Overlay'); this.ctrl.cameraOverlayItems.forEach(item => menu.addchk(this.ctrl.camera_overlay === item.value, item.name, item.value, arg => { this.ctrl.camera_overlay = arg; this.changeCamera(); })); - menu.add('endsub:'); + menu.endsub(); } } - menu.add('endsub:'); + menu.endsub(); menu.addchk(this.ctrl.select_in_view, 'Select in view', () => { this.ctrl.select_in_view = !this.ctrl.select_in_view; - if (this.ctrl.select_in_view) this.startDrawGeometry(); + if (this.ctrl.select_in_view) + this.startDrawGeometry(); }); } @@ -1221,7 +1382,7 @@ class TGeoPainter extends ObjectPainter { if (func || (transparency === undefined)) transparency = this.ctrl.transparency; - this._toplevel?.traverse(node => { + this.#toplevel?.traverse(node => { // ignore all kind of extra elements if (node?.material?.inherentOpacity === undefined) return; @@ -1241,7 +1402,7 @@ class TGeoPainter extends ObjectPainter { /** @summary Method used to interactively change material kinds */ changedMaterial() { - this._toplevel?.traverse(node => { + this.#toplevel?.traverse(node => { // ignore all kind of extra elements if (node.material?.inherentArgs !== undefined) node.material = createMaterial(this.ctrl, node.material.inherentArgs); @@ -1256,9 +1417,10 @@ class TGeoPainter extends ObjectPainter { if (value === undefined) return console.error('No property ', name); - this._toplevel?.traverse(node => { + this.#toplevel?.traverse(node => { // ignore all kind of extra elements - if (node.material?.inherentArgs === undefined) return; + if (node.material?.inherentArgs === undefined) + return; if (node.material[name] !== undefined) { node.material[name] = value; @@ -1276,16 +1438,17 @@ class TGeoPainter extends ObjectPainter { /** @summary Method should be called when transformation parameters were changed */ changedTransformation(arg) { - if (!this._toplevel) return; + if (!this.#toplevel) + return; const ctrl = this.ctrl, - translation = new Matrix4(), - vect2 = new Vector3(); + translation = new THREE.Matrix4(), + vect2 = new THREE.Vector3(); if (arg === 'reset') ctrl.trans_z = ctrl.trans_radial = 0; - this._toplevel.traverse(mesh => { + this.#toplevel.traverse(mesh => { if (mesh.stack !== undefined) { const node = mesh.parent; @@ -1304,14 +1467,14 @@ class TGeoPainter extends ObjectPainter { if (node.vect0 === undefined) { node.matrix0 = node.matrix.clone(); - node.minvert = new Matrix4().copy(node.matrixWorld).invert(); + node.minvert = new THREE.Matrix4().copy(node.matrixWorld).invert(); const box3 = getBoundingBox(mesh, null, true), - signz = mesh._flippedMesh ? -1 : 1; + signz = mesh._flippedMesh ? -1 : 1; // real center of mesh in local coordinates - node.vect0 = new Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld); - node.vect1 = new Vector3(0, 0, 0).applyMatrix4(node.minvert); + node.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld); + node.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(node.minvert); } vect2.set(ctrl.trans_radial * node.vect0.x, ctrl.trans_radial * node.vect0.y, ctrl.trans_z * node.vect0.z).applyMatrix4(node.minvert).sub(node.vect1); @@ -1337,8 +1500,8 @@ class TGeoPainter extends ObjectPainter { for (let i = 0; i < mesh.count; i++) { const item = { - matrix0: new Matrix4(), - minvert: new Matrix4() + matrix0: new THREE.Matrix4(), + minvert: new THREE.Matrix4() }; mesh.trans[i] = item; @@ -1346,14 +1509,14 @@ class TGeoPainter extends ObjectPainter { mesh.getMatrixAt(i, item.matrix0); item.minvert.copy(item.matrix0).invert(); - const box3 = new Box3().copy(mesh.geometry.boundingBox).applyMatrix4(item.matrix0); + const box3 = new THREE.Box3().copy(mesh.geometry.boundingBox).applyMatrix4(item.matrix0); - item.vect0 = new Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, (box3.max.z + box3.min.z) / 2); - item.vect1 = new Vector3(0, 0, 0).applyMatrix4(item.minvert); + item.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, (box3.max.z + box3.min.z) / 2); + item.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(item.minvert); } } - const mm = new Matrix4(); + const mm = new THREE.Matrix4(); mesh.trans?.forEach((item, i) => { vect2.set(ctrl.trans_radial * item.vect0.x, ctrl.trans_radial * item.vect0.y, ctrl.trans_z * item.vect0.z).applyMatrix4(item.minvert).sub(item.vect1); @@ -1365,14 +1528,14 @@ class TGeoPainter extends ObjectPainter { } }); - this._toplevel.updateMatrixWorld(); + this.#toplevel.updateMatrixWorld(); // axes drawing always triggers rendering if (arg !== 'norender') this.drawAxesAndOverlay(); } - /** @summary Should be called when autorotate property changed */ + /** @summary Should be called when auto rotate property changed */ changedAutoRotate() { this.autorotate(2.5); } @@ -1389,54 +1552,56 @@ class TGeoPainter extends ObjectPainter { changedBackground(val) { if (val !== undefined) this.ctrl.background = val; - this._scene.background = new Color(this.ctrl.background); - this._renderer.setClearColor(this._scene.background, 1); + this.#scene.background = new THREE.Color(this.ctrl.background); + this.#renderer.setClearColor(this.#scene.background, 1); this.render3D(0); - if (this._toolbar) { - const bkgr = new Color(this.ctrl.background); - this._toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1); + if (this.#toolbar) { + const bkgr = new THREE.Color(this.ctrl.background); + this.#toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1); } } /** @summary Display control GUI */ showControlGui(on) { // while complete geo drawing can be removed until dat is loaded - just check and ignore callback - if (!this.ctrl) return; + if (!this.ctrl) + return; if (on === 'toggle') - on = !this._gui; - else if (on === undefined) + on = !this.#gui; + else if (on === undefined) on = this.ctrl.show_controls; - this.ctrl.show_controls = on; - if (this._gui) { + if (this.#gui) { if (!on) { - this._gui.destroy(); - delete this._gui; + this.#gui.destroy(); + this.#gui = undefined; } return; } - if (!on || !this._renderer) + if (!on || !this.#renderer) return; - const main = this.selectDom(); if (main.style('position') === 'static') main.style('position', 'relative'); - this._gui = new GUI({ container: main.node(), closeFolders: true, width: Math.min(300, this._scene_width / 2), - title: 'Settings' }); + this.#gui = new GUI({ + container: main.node(), closeFolders: true, + width: Math.min(300, this.#scene_width / 2), + title: 'Settings' + }); - const dom = this._gui.domElement; + const dom = this.#gui.domElement; dom.style.position = 'absolute'; dom.style.top = 0; dom.style.right = 0; - this._gui.painter = this; + this.#gui.painter = this; const makeLil = items => { const lil = {}; @@ -1445,12 +1610,12 @@ class TGeoPainter extends ObjectPainter { }; if (!this.ctrl.project) { - const selection = this._gui.addFolder('Selection'); + const selection = this.#gui.addFolder('Selection'); if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = this._clones?.getMaxVisNodes() ?? 10000; + this.ctrl.maxnodes = this.#clones?.getMaxVisNodes() ?? 10000; if (!this.ctrl.vislevel) - this.ctrl.vislevel = this._clones?.getVisLevel() ?? 3; + this.ctrl.vislevel = this.#clones?.getVisLevel() ?? 3; if (!this.ctrl.maxfaces) this.ctrl.maxfaces = 200000 * this.ctrl.more; this.ctrl.more = 1; @@ -1468,22 +1633,22 @@ class TGeoPainter extends ObjectPainter { if (this.ctrl.project) { const bound = this.getGeomBoundingBox(this.getProjectionSource(), 0.01), - axis = this.ctrl.project; + axis = this.ctrl.project; if (this.ctrl.projectPos === undefined) - this.ctrl.projectPos = (bound.min[axis] + bound.max[axis])/2; + this.ctrl.projectPos = (bound.min[axis] + bound.max[axis]) / 2; - this._gui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis]) + this.#gui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis]) .name(axis.toUpperCase() + ' projection') .onChange(() => this.startDrawGeometry()); } else { // Clipping Options - const clipFolder = this._gui.addFolder('Clipping'); + const clipFolder = this.#gui.addFolder('Clipping'); for (let naxis = 0; naxis < 3; ++naxis) { const cc = this.ctrl.clip[naxis], - axisC = cc.name.toUpperCase(); + axisC = cc.name.toUpperCase(); clipFolder.add(cc, 'enabled') .name('Enable ' + axisC) @@ -1503,7 +1668,9 @@ class TGeoPainter extends ObjectPainter { // Scene Options - const scene = this._gui.addFolder('Scene'); + const scene = this.#gui.addFolder('Scene'); + // following items used in handlers and cannot be constants + let light_pnts = null, strength = null, hcolor = null, overlay = null; scene.add(this.ctrl.light, 'kind', makeLil(this.ctrl.lightKindItems)).name('Light') .listen().onChange(() => { @@ -1512,7 +1679,7 @@ class TGeoPainter extends ObjectPainter { }); this.ctrl.light._pnts = this.ctrl.light.specular ? 0 : (this.ctrl.light.front ? 1 : 2); - const light_pnts = scene.add(this.ctrl.light, '_pnts', { specular: 0, front: 1, box: 2 }) + light_pnts = scene.add(this.ctrl.light, '_pnts', { specular: 0, front: 1, box: 2 }) .name('Positions') .show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points') .onChange(v => { @@ -1528,10 +1695,9 @@ class TGeoPainter extends ObjectPainter { scene.add(this.ctrl, 'use_fog').name('Fog') .listen().onChange(() => this.changedUseFog()); - // Appearance Options - const appearance = this._gui.addFolder('Appearance'); + const appearance = this.#gui.addFolder('Appearance'); this.ctrl._highlight = !this.ctrl.highlight ? 0 : this.ctrl.highlight_bloom ? 2 : 1; appearance.add(this.ctrl, '_highlight', { none: 0, normal: 1, bloom: 2 }).name('Highlight Selection') @@ -1541,9 +1707,9 @@ class TGeoPainter extends ObjectPainter { hcolor.show(this.ctrl._highlight === 1); }); - const hcolor = appearance.addColor(this.ctrl, 'highlight_color').name('Hightlight color') - .show(this.ctrl._highlight === 1), - strength = appearance.add(this.ctrl, 'bloom_strength', 0, 3).name('Bloom strength') + hcolor = appearance.addColor(this.ctrl, 'highlight_color').name('Hightlight color') + .show(this.ctrl._highlight === 1); + strength = appearance.add(this.ctrl, 'bloom_strength', 0, 3).name('Bloom strength') .listen().onChange(() => this.changedHighlight()) .show(this.ctrl._highlight === 2); @@ -1560,7 +1726,7 @@ class TGeoPainter extends ObjectPainter { // Material options - const material = this._gui.addFolder('Material'); + const material = this.#gui.addFolder('Material'); let material_props = []; const addMaterialProp = () => { @@ -1568,7 +1734,8 @@ class TGeoPainter extends ObjectPainter { material_props = []; const props = this.ctrl.getMaterialCfg()?.props; - if (!props) return; + if (!props) + return; props.forEach(prop => { const f = material.add(this.ctrl, prop.name, prop.min, prop.max, prop.step).onChange(() => { @@ -1580,11 +1747,11 @@ class TGeoPainter extends ObjectPainter { material.add(this.ctrl, 'material_kind', makeLil(this.ctrl.materialKinds)).name('Kind') .listen().onChange(() => { - addMaterialProp(); - this.ensureBloom(false); - this.changedMaterial(); - this.changedHighlight(); // for some materials bloom will not work - }); + addMaterialProp(); + this.ensureBloom(false); + this.changedMaterial(); + this.changedHighlight(); // for some materials bloom will not work + }); material.add(this.ctrl, 'transparency', 0, 1, 0.001).name('Transparency') .listen().onChange(value => this.changedGlobalTransparency(value)); @@ -1598,26 +1765,26 @@ class TGeoPainter extends ObjectPainter { // Camera options - const camera = this._gui.addFolder('Camera'); + const camera = this.#gui.addFolder('Camera'); camera.add(this.ctrl, 'camera_kind', makeLil(this.ctrl.cameraKindItems)) .name('Kind').listen().onChange(() => { - overlay.show(this.ctrl.camera_kind.indexOf('ortho') === 0); - this.changeCamera(); - }); + overlay.show(this.ctrl.camera_kind.indexOf('ortho') === 0); + this.changeCamera(); + }); camera.add(this.ctrl, 'can_rotate').name('Can rotate') .listen().onChange(() => this.changeCanRotate()); camera.add(this, 'focusCamera').name('Reset position'); - const overlay = camera.add(this.ctrl, 'camera_overlay', makeLil(this.ctrl.cameraOverlayItems)) + overlay = camera.add(this.ctrl, 'camera_overlay', makeLil(this.ctrl.cameraOverlayItems)) .name('Overlay').listen().onChange(() => this.changeCamera()) .show(this.ctrl.camera_kind.indexOf('ortho') === 0); // Advanced Options - if (this._webgl) { - const advanced = this._gui.addFolder('Advanced'); + if (this.#webgl) { + const advanced = this.#gui.addFolder('Advanced'); advanced.add(this.ctrl, 'depthTest').name('Depth test') .listen().onChange(() => this.changedDepthTest()); @@ -1631,7 +1798,7 @@ class TGeoPainter extends ObjectPainter { // Transformation Options if (!this.ctrl.project) { - const transform = this._gui.addFolder('Transform'); + const transform = this.#gui.addFolder('Transform'); transform.add(this.ctrl, 'trans_z', 0.0, 3.0, 0.01) .name('Z axis') .listen().onChange(() => this.changedTransformation()); @@ -1640,12 +1807,12 @@ class TGeoPainter extends ObjectPainter { .listen().onChange(() => this.changedTransformation()); transform.add(this, 'resetTransformation').name('Reset'); - - if (this.ctrl.trans_z || this.ctrl.trans_radial) transform.open(); + if (this.ctrl.trans_z || this.ctrl.trans_radial) + transform.open(); } } - /** @summary show material docu */ + /** @summary show material documentation from https://threejs.org */ showMaterialDocu() { const cfg = this.ctrl.getMaterialCfg(); if (cfg?.name && typeof window !== 'undefined') @@ -1655,7 +1822,7 @@ class TGeoPainter extends ObjectPainter { /** @summary Should be called when configuration of highlight is changed */ changedHighlight(arg) { if (arg !== undefined) { - this.ctrl.highlight = arg !== 0; + this.ctrl.highlight = Boolean(arg); if (this.ctrl.highlight) this.ctrl.highlight_bloom = (arg === 2); } @@ -1665,7 +1832,7 @@ class TGeoPainter extends ObjectPainter { if (!this.ctrl.highlight) this.highlightMesh(null); - this._slave_painters?.forEach(p => { + this.getSubordinates()?.forEach(p => { p.ctrl.highlight = this.ctrl.highlight; p.ctrl.highlight_bloom = this.ctrl.highlight_bloom; p.ctrl.bloom_strength = this.ctrl.bloom_strength; @@ -1677,13 +1844,13 @@ class TGeoPainter extends ObjectPainter { changeCanRotate(on) { if (on !== undefined) this.ctrl.can_rotate = on; - if (this._controls) - this._controls.enableRotate = this.ctrl.can_rotate; + if (this.#controls) + this.#controls.enableRotate = this.ctrl.can_rotate; } /** @summary Change use fog property */ changedUseFog() { - this._scene.fog = this.ctrl.use_fog ? this._fog : null; + this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; this.render3D(); } @@ -1691,9 +1858,9 @@ class TGeoPainter extends ObjectPainter { /** @summary Handle change of camera kind */ changeCamera() { // force control recreation - if (this._controls) { - this._controls.cleanup(); - delete this._controls; + if (this.#controls) { + this.#controls.cleanup(); + this.#controls = undefined; } this.ensureBloom(false); @@ -1712,9 +1879,9 @@ class TGeoPainter extends ObjectPainter { this.render3D(); - // delete this._scene_size; // ensure reassign of camera position + // this.#scene_size = undefined; // ensure reassign of camera position - // this._first_drawing = true; + // this.#first_drawing = true; // this.startDrawGeometry(true); } @@ -1722,32 +1889,32 @@ class TGeoPainter extends ObjectPainter { ensureBloom(on) { if (on === undefined) { if (this.ctrl.highlight_bloom === 0) - this.ctrl.highlight_bloom = this._webgl; + this.ctrl.highlight_bloom = this.#webgl && !browser.android; on = this.ctrl.highlight_bloom && this.ctrl.getMaterialCfg()?.emissive; } - if (on && !this._bloomComposer) { - this._camera.layers.enable(_BLOOM_SCENE); - this._bloomComposer = new EffectComposer(this._renderer); - this._bloomComposer.addPass(new RenderPass(this._scene, this._camera)); - const pass = new UnrealBloomPass(new Vector2(this._scene_width, this._scene_height), 1.5, 0.4, 0.85); + if (on && !this.#bloomComposer) { + this.#camera.layers.enable(_BLOOM_SCENE); + this.#bloomComposer = new THREE.EffectComposer(this.#renderer); + this.#bloomComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); + const pass = new THREE.UnrealBloomPass(new THREE.Vector2(this.#scene_width, this.#scene_height), 1.5, 0.4, 0.85); pass.threshold = 0; pass.radius = 0; pass.renderToScreen = true; - this._bloomComposer.addPass(pass); - this._renderer.autoClear = false; - } else if (!on && this._bloomComposer) { - this._bloomComposer.dispose(); - delete this._bloomComposer; - if (this._renderer) - this._renderer.autoClear = true; - this._camera?.layers.disable(_BLOOM_SCENE); - this._camera?.layers.set(_ENTIRE_SCENE); + this.#bloomComposer.addPass(pass); + this.#renderer.autoClear = false; + } else if (!on && this.#bloomComposer) { + this.#bloomComposer.dispose(); + this.#bloomComposer = undefined; + if (this.#renderer) + this.#renderer.autoClear = true; + this.#camera?.layers.disable(_BLOOM_SCENE); + this.#camera?.layers.set(_ENTIRE_SCENE); } - if (this._bloomComposer?.passes) - this._bloomComposer.passes[1].strength = this.ctrl.bloom_strength; + if (this.#bloomComposer?.passes) + this.#bloomComposer.passes[1].strength = this.ctrl.bloom_strength; } @@ -1758,17 +1925,20 @@ class TGeoPainter extends ObjectPainter { let numitems = 0, numnodes = 0, cnt = 0; if (intersects) { for (let n = 0; n < intersects.length; ++n) { - if (getIntersectStack(intersects[n])) numnodes++; - if (intersects[n].geo_name) numitems++; + if (getIntersectStack(intersects[n])) + numnodes++; + if (intersects[n].geo_name) + numitems++; } } if (numnodes + numitems === 0) this.fillContextMenu(menu); - else { + else { const many = (numnodes + numitems) > 1; - if (many) menu.add('header:' + ((numitems > 0) ? 'Items' : 'Nodes')); + if (many) + menu.header((numitems > 0) ? 'Items' : 'Nodes'); for (let n = 0; n < intersects.length; ++n) { const obj = intersects[n].object, @@ -1779,11 +1949,12 @@ class TGeoPainter extends ObjectPainter { itemname = obj.geo_name; if (itemname.indexOf('<prnt>') === 0) itemname = (this.getItemName() || 'top') + itemname.slice(6); - name = itemname.slice(itemname.lastIndexOf('/')+1); - if (!name) name = itemname; + name = itemname.slice(itemname.lastIndexOf('/') + 1); + if (!name) + name = itemname; hdr = name; } else if (stack) { - name = this._clones.getStackName(stack); + name = this.#clones.getStackName(stack); itemname = this.getStackFullName(stack); hdr = this.getItemName(); if (name.indexOf('Nodes/') === 0) @@ -1802,8 +1973,8 @@ class TGeoPainter extends ObjectPainter { menu.add('Browse', itemname, arg => this.activateInBrowser([arg], true)); - if (this._hpainter) - menu.add('Inspect', itemname, arg => this._hpainter.display(arg, kInspect)); + if (this.getHPainter()) + menu.add('Inspect', itemname, arg => this.getHPainter().display(arg, kInspect)); if (isFunc(this.hidePhysicalNode)) { menu.add('Hide', itemname, arg => this.hidePhysicalNode([arg])); @@ -1811,8 +1982,9 @@ class TGeoPainter extends ObjectPainter { menu.add('Hide all before', n, indx => { const items = []; for (let i = 0; i < indx; ++i) { - const stack = getIntersectStack(intersects[i]); - if (stack) items.push(this.getStackFullName(stack)); + const stack2 = getIntersectStack(intersects[i]); + if (stack2) + items.push(this.getStackFullName(stack2)); } this.hidePhysicalNode(items); }); @@ -1821,11 +1993,13 @@ class TGeoPainter extends ObjectPainter { menu.add('Hide', n, indx => { const mesh = intersects[indx].object; mesh.visible = false; // just disable mesh - if (mesh.geo_object) mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw + if (mesh.geo_object) + mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw menu.painter.render3D(); }, 'Hide this physical node'); - if (many) menu.add('endsub:'); + if (many) + menu.endsub(); continue; } @@ -1841,22 +2015,21 @@ class TGeoPainter extends ObjectPainter { if (cnt > 1) { menu.add('Manifest', n, indx => { - if (this._last_manifest) - this._last_manifest.wireframe = !this._last_manifest.wireframe; + if (this.#last_manifest) + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; - if (this._last_hidden) - this._last_hidden.forEach(obj => { obj.visible = true; }); + this.#last_hidden?.forEach(obj2 => { obj2.visible = true; }); - this._last_hidden = []; + this.#last_hidden = []; for (let i = 0; i < indx; ++i) - this._last_hidden.push(intersects[i].object); + this.#last_hidden.push(intersects[i].object); - this._last_hidden.forEach(obj => { obj.visible = false; }); + this.#last_hidden.forEach(obj2 => { obj2.visible = false; }); - this._last_manifest = intersects[indx].object.material; + this.#last_manifest = intersects[indx].object.material; - this._last_manifest.wireframe = !this._last_manifest.wireframe; + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; this.render3D(); }, 'Manifest selected node'); @@ -1866,33 +2039,34 @@ class TGeoPainter extends ObjectPainter { this.focusCamera(intersects[indx].object); }); - if (!this._geom_viewer) { + if (!this.#geom_viewer) { menu.add('Hide', n, indx => { - const resolve = this._clones.resolveStack(intersects[indx].object.stack); + const resolve = this.#clones.resolveStack(intersects[indx].object.stack); if (resolve.obj && (resolve.node.kind === kindGeo) && resolve.obj.fVolume) { setGeoBit(resolve.obj.fVolume, geoBITS.kVisThis, false); - updateBrowserIcons(resolve.obj.fVolume, this._hpainter); + updateBrowserIcons(resolve.obj.fVolume, this.getHPainter()); } else if (resolve.obj && (resolve.node.kind === kindEve)) { resolve.obj.fRnrSelf = false; - updateBrowserIcons(resolve.obj, this._hpainter); + updateBrowserIcons(resolve.obj, this.getHPainter()); } this.testGeomChanges();// while many volumes may disappear, recheck all of them }, 'Hide all logical nodes of that kind'); menu.add('Hide only this', n, indx => { - this._clones.setPhysNodeVisibility(getIntersectStack(intersects[indx]), false); + this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[indx]), false); this.testGeomChanges(); }, 'Hide only this physical node'); if (n > 1) { - menu.add('Hide all before', n, indx => { + menu.add('Hide all before', n, indx => { for (let k = 0; k < indx; ++k) - this._clones.setPhysNodeVisibility(getIntersectStack(intersects[k]), false); + this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[k]), false); this.testGeomChanges(); }, 'Hide all physical nodes before that'); } } - if (many) menu.add('endsub:'); + if (many) + menu.endsub(); } } menu.show(); @@ -1904,7 +2078,7 @@ class TGeoPainter extends ObjectPainter { if (!intersects?.length) return intersects; - // check redirections + // check redirection for (let n = 0; n < intersects.length; ++n) { if (intersects[n].object.geo_highlight) intersects[n].object = intersects[n].object.geo_highlight; @@ -1916,17 +2090,19 @@ class TGeoPainter extends ObjectPainter { const obj = intersects[n].object; let unique = obj.visible && (getIntersectStack(intersects[n]) || (obj.geo_name !== undefined)); - if (unique && obj.material && (obj.material.opacity !== undefined)) - unique = (obj.material.opacity >= 0.1); + if (unique && (obj.material?.opacity !== undefined)) + unique = obj.material.opacity >= 0.1; - if (obj.jsroot_special) unique = false; + if (obj.jsroot_special) + unique = false; for (let k = 0; (k < n) && unique; ++k) { if (intersects[k].object === obj) unique = false; } - if (!unique) intersects.splice(n, 1); + if (!unique) + intersects.splice(n, 1); } const clip = this.ctrl.clip; @@ -1938,11 +2114,15 @@ class TGeoPainter extends ObjectPainter { const point = intersects[i].point, special = (intersects[i].object.type === 'Points'); let clipped = true; - if (clip[0].enabled && ((this._clipPlanes[0].normal.dot(point) > this._clipPlanes[0].constant) ^ special)) clipped = false; - if (clip[1].enabled && ((this._clipPlanes[1].normal.dot(point) > this._clipPlanes[1].constant) ^ special)) clipped = false; - if (clip[2].enabled && (this._clipPlanes[2].normal.dot(point) > this._clipPlanes[2].constant)) clipped = false; + if (clip[0].enabled && ((this.#clip_planes[0].normal.dot(point) > this.#clip_planes[0].constant) ^ special)) + clipped = false; + if (clip[1].enabled && ((this.#clip_planes[1].normal.dot(point) > this.#clip_planes[1].constant) ^ special)) + clipped = false; + if (clip[2].enabled && (this.#clip_planes[2].normal.dot(point) > this.#clip_planes[2].constant)) + clipped = false; - if (!clipped) clippedIntersects.push(intersects[i]); + if (!clipped) + clippedIntersects.push(intersects[i]); } intersects = clippedIntersects; @@ -1955,10 +2135,11 @@ class TGeoPainter extends ObjectPainter { * @desc function analyzes camera position and start redraw of geometry * if objects in view may be changed */ testCameraPositionChange() { - if (!this.ctrl.select_in_view || this._draw_all_nodes) return; + if (!this.ctrl.select_in_view || this.#draw_all_nodes) + return; - const matrix = createProjectionMatrix(this._camera), - frustum = createFrustum(matrix); + const matrix = createProjectionMatrix(this.#camera), + frustum = createFrustum(matrix); // check if overall bounding box seen if (!frustum.CheckBox(this.getGeomBoundingBox())) @@ -1967,14 +2148,14 @@ class TGeoPainter extends ObjectPainter { /** @summary Resolve stack */ resolveStack(stack) { - return this._clones && stack ? this._clones.resolveStack(stack) : null; + return this.#clones && stack ? this.#clones.resolveStack(stack) : null; } /** @summary Returns stack full name * @desc Includes item name of top geo object */ getStackFullName(stack) { const mainitemname = this.getItemName(), - sub = this.resolveStack(stack); + sub = this.resolveStack(stack); if (!sub || !sub.name) return mainitemname; return mainitemname ? mainitemname + '/' + sub.name : sub.name; @@ -1983,10 +2164,11 @@ class TGeoPainter extends ObjectPainter { /** @summary Add handler which will be called when element is highlighted in geometry drawing * @desc Handler should have highlightMesh function with same arguments as TGeoPainter */ addHighlightHandler(handler) { - if (!isFunc(handler?.highlightMesh)) return; - if (!this._highlight_handlers) - this._highlight_handlers = []; - this._highlight_handlers.push(handler); + if (!isFunc(handler?.highlightMesh)) + return; + if (!this.#highlight_handlers) + this.#highlight_handlers = []; + this.#highlight_handlers.push(handler); } /** @summary perform mesh highlight */ @@ -1996,13 +2178,15 @@ class TGeoPainter extends ObjectPainter { const extras = this.getExtrasContainer(); if (extras) { extras.traverse(obj3d => { - if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d) < 0)) active_mesh.push(obj3d); + if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d) < 0)) + active_mesh.push(obj3d); }); } - } else if (geo_stack && this._toplevel) { + } else if (geo_stack && this.#toplevel) { active_mesh = []; - this._toplevel.traverse(mesh => { - if ((mesh instanceof Mesh) && isSameStack(mesh.stack, geo_stack)) active_mesh.push(mesh); + this.#toplevel.traverse(mesh => { + if ((mesh instanceof THREE.Mesh) && isSameStack(mesh.stack, geo_stack)) + active_mesh.push(mesh); }); } else active_mesh = active_mesh ? [active_mesh] : []; @@ -2013,20 +2197,24 @@ class TGeoPainter extends ObjectPainter { if (active_mesh) { // check if highlight is disabled for correspondent objects kinds if (active_mesh[0].geo_object) { - if (!this.ctrl.highlight_scene) active_mesh = null; + if (!this.ctrl.highlight_scene) + active_mesh = null; } else - if (!this.ctrl.highlight) active_mesh = null; + if (!this.ctrl.highlight) + active_mesh = null; } if (!no_recursive) { // check all other painters if (active_mesh) { - if (!geo_object) geo_object = active_mesh[0].geo_object; - if (!geo_stack) geo_stack = active_mesh[0].stack; + if (!geo_object) + geo_object = active_mesh[0].geo_object; + if (!geo_stack) + geo_stack = active_mesh[0].stack; } - const lst = this._highlight_handlers || (!this._main_painter ? this._slave_painters : this._main_painter._slave_painters.concat([this._main_painter])); + const lst = this.#highlight_handlers || this.getCentral()?.getSubordinates().concat([this.getCentral()]) || this.getSubordinates(); for (let k = 0; k < lst?.length; ++k) { if (lst[k] !== this) @@ -2034,48 +2222,47 @@ class TGeoPainter extends ObjectPainter { } } - const curr_mesh = this._selected_mesh; - - if (!curr_mesh && !active_mesh) return false; + const curr_mesh = this.#selected_mesh; + if (!curr_mesh && !active_mesh) + return false; - const get_ctrl = mesh => mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh, this.ctrl.highlight_bloom && this._bloomComposer); + const get_ctrl = mesh => { return mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh, this.ctrl.highlight_bloom && this.#bloomComposer); }; let same = false; // check if selections are the same if (curr_mesh && active_mesh && (curr_mesh.length === active_mesh.length)) { same = true; - for (let k = 0; (k < curr_mesh.length) && same; ++k) - if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) same = false; + for (let k = 0; (k < curr_mesh.length) && same; ++k) { + if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) + same = false; + } } - if (same) return !!curr_mesh; + if (same) + return Boolean(curr_mesh); - if (curr_mesh) { - for (let k = 0; k < curr_mesh.length; ++k) - get_ctrl(curr_mesh[k]).setHighlight(); - } + curr_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight()); - this._selected_mesh = active_mesh; + this.#selected_mesh = active_mesh; - if (active_mesh) { - for (let k = 0; k < active_mesh.length; ++k) - get_ctrl(active_mesh[k]).setHighlight(color || new Color(this.ctrl.highlight_color), geo_index); - } + active_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight(color || new THREE.Color(this.ctrl.highlight_color), geo_index)); this.render3D(0); - return !!active_mesh; + return Boolean(active_mesh); } /** @summary handle mouse click event */ processMouseClick(pnt, intersects, evnt) { - if (!intersects.length) return; + if (!intersects.length) + return; const mesh = intersects[0].object; - if (!mesh.get_ctrl) return; + if (!mesh.get_ctrl) + return; const ctrl = mesh.get_ctrl(), - click_indx = ctrl.extractIndex(intersects[0]); + click_indx = ctrl.extractIndex(intersects[0]); ctrl.evnt = evnt; @@ -2090,8 +2277,8 @@ class TGeoPainter extends ObjectPainter { if (this.ctrl) this.ctrl.mouse_tmout = val; - if (this._controls) - this._controls.mouse_tmout = val; + if (this.#controls) + this.#controls.mouse_tmout = val; } /** @summary Configure depth method, used for render order production. @@ -2112,36 +2299,40 @@ class TGeoPainter extends ObjectPainter { /** @summary Add orbit control */ addOrbitControls() { - if (this._controls || !this._webgl || this.isBatchMode() || this.superimpose || isNodeJs()) return; + if (this.#controls || !this.#webgl || this.isBatchMode() || this.#superimpose || isNodeJs()) + return; if (!this.getCanvPainter()) this.setTooltipAllowed(settings.Tooltip); - this._controls = createOrbitControl(this, this._camera, this._scene, this._renderer, this._lookat); + this.#controls = createOrbitControl(this, this.#camera, this.#scene, this.#renderer, this.#lookat); - this._controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing + this.#controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing if (!this.canRotateCamera()) - this._controls.enableRotate = false; + this.#controls.enableRotate = false; - this._controls.contextMenu = this.orbitContext.bind(this); + this.#controls.contextMenu = this.orbitContext.bind(this); - this._controls.processMouseMove = intersects => { + this.#controls.processMouseMove = intersects => { // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this._controls) return; + if (!this.ctrl || !this.#controls) + return; let active_mesh = null, tooltip = null, resolve = null, names = [], geo_object, geo_index, geo_stack; // try to find mesh from intersections for (let k = 0; k < intersects.length; ++k) { const obj = intersects[k].object, stack = getIntersectStack(intersects[k]); - if (!obj || !obj.visible) continue; + if (!obj || !obj.visible) + continue; let info = null; if (obj.geo_object) info = obj.geo_name; else if (stack) info = this.getStackFullName(stack); - if (!info) continue; + if (!info) + continue; if (info.indexOf('<prnt>') === 0) info = this.getItemName() + info.slice(6); @@ -2161,7 +2352,8 @@ class TGeoPainter extends ObjectPainter { if (geo_stack) { resolve = this.resolveStack(geo_stack); - if (obj.stacks) geo_index = intersects[k].instanceId; + if (obj.stacks) + geo_index = intersects[k].instanceId; } } } @@ -2169,7 +2361,8 @@ class TGeoPainter extends ObjectPainter { this.highlightMesh(active_mesh, undefined, geo_object, geo_index); if (this.ctrl.update_browser) { - if (this.ctrl.highlight && tooltip) names = [tooltip]; + if (this.ctrl.highlight && tooltip) + names = [tooltip]; this.activateInBrowser(names); } @@ -2182,20 +2375,21 @@ class TGeoPainter extends ObjectPainter { return { name: resolve.obj.fName, title: resolve.obj.fTitle || resolve.obj._typename, lines }; }; - this._controls.processMouseLeave = function() { + this.#controls.processMouseLeave = function() { this.processMouseMove([]); // to disable highlight and reset browser }; - this._controls.processDblClick = () => { + this.#controls.processDblClick = () => { // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this._controls) return; - - if (this._last_manifest) { - this._last_manifest.wireframe = !this._last_manifest.wireframe; - if (this._last_hidden) - this._last_hidden.forEach(obj => { obj.visible = true; }); - delete this._last_hidden; - delete this._last_manifest; + if (!this.ctrl || !this.#controls) + return; + + if (this.#last_manifest) { + this.#last_manifest.wireframe = !this.#last_manifest.wireframe; + if (this.#last_hidden) + this.#last_hidden.forEach(obj => { obj.visible = true; }); + this.#last_hidden = undefined; + this.#last_manifest = undefined; } else this.adjustCameraPosition(true); @@ -2210,32 +2404,37 @@ class TGeoPainter extends ObjectPainter { * - 1 when call after short timeout required * - 2 when call must be done from processWorkerReply */ nextDrawAction() { - if (!this._clones || this.isStage(stageInit)) return false; + if (!this.#clones || this.isStage(stageInit)) + return false; if (this.isStage(stageCollect)) { - if (this._geom_viewer) { - this._draw_all_nodes = false; + if (this.#geom_viewer) { + this.#draw_all_nodes = false; this.changeStage(stageAnalyze); return true; } // wait until worker is really started if (this.ctrl.use_worker > 0) { - if (!this._worker) { this.startWorker(); return 1; } - if (!this._worker_ready) return 1; + if (!this.#worker) { + this.startWorker(); + return 1; + } + if (!this.#worker_ready) + return 1; } // first copy visibility flags and check how many unique visible nodes exists - let numvis = this._first_drawing ? this._clones.countVisibles() : 0, + let numvis = this.#first_drawing ? this.#clones.countVisibles() : 0, matrix = null, frustum = null; if (!numvis) - numvis = this._clones.markVisibles(false, false, !!this.geo_manager && !this.ctrl.showtop); + numvis = this.#clones.markVisibles(false, false, Boolean(this.#geo_manager) && !this.ctrl.showtop); - if (this.ctrl.select_in_view && !this._first_drawing) { + if (this.ctrl.select_in_view && !this.#first_drawing) { // extract camera projection matrix for selection - matrix = createProjectionMatrix(this._camera); + matrix = createProjectionMatrix(this.#camera); frustum = createFrustum(matrix); @@ -2246,12 +2445,13 @@ class TGeoPainter extends ObjectPainter { } } - this._current_face_limit = this.ctrl.maxfaces; - if (matrix) this._current_face_limit *= 1.25; + this.#current_face_limit = this.ctrl.maxfaces; + if (matrix) + this.#current_face_limit *= 1.25; // here we decide if we need worker for the drawings // main reason - too large geometry and large time to scan all camera positions - let need_worker = !this.isBatchMode() && browser.isChrome && ((numvis > 10000) || (matrix && (this._clones.scanVisible() > 1e5))); + let need_worker = !this.isBatchMode() && browser.isChrome && ((numvis > 10000) || (matrix && (this.#clones.scanVisible() > 1e5))); // worker does not work when starting from file system if (need_worker && source_dir.indexOf('file://') === 0) { @@ -2259,23 +2459,23 @@ class TGeoPainter extends ObjectPainter { need_worker = false; } - if (need_worker && !this._worker && (this.ctrl.use_worker >= 0)) + if (need_worker && !this.#worker && (this.ctrl.use_worker >= 0)) this.startWorker(); // we starting worker, but it may not be ready so fast - if (!need_worker || !this._worker_ready) { - const res = this._clones.collectVisibles(this._current_face_limit, frustum); - this._new_draw_nodes = res.lst; - this._draw_all_nodes = res.complete; + if (!need_worker || !this.#worker_ready) { + const res = this.#clones.collectVisibles(this.#current_face_limit, frustum); + this.#new_draw_nodes = res.lst; + this.#draw_all_nodes = res.complete; this.changeStage(stageAnalyze); return true; } const job = { - collect: this._current_face_limit, // indicator for the command - flags: this._clones.getVisibleFlags(), + collect: this.#current_face_limit, // indicator for the command + flags: this.#clones.getVisibleFlags(), matrix: matrix ? matrix.elements : null, - vislevel: this._clones.getVisLevel(), - maxvisnodes: this._clones.getMaxVisNodes() + vislevel: this.#clones.getVisLevel(), + maxvisnodes: this.#clones.getMaxVisNodes() }; this.submitToWorker(job); @@ -2294,37 +2494,37 @@ class TGeoPainter extends ObjectPainter { // here we merge new and old list of nodes for drawing, // normally operation is fast and can be implemented with one c - if (this._new_append_nodes) { - this._new_draw_nodes = this._draw_nodes.concat(this._new_append_nodes); + if (this.#new_append_nodes) { + this.#new_draw_nodes = this.#draw_nodes.concat(this.#new_append_nodes); - delete this._new_append_nodes; - } else if (this._draw_nodes) { + this.#new_append_nodes = undefined; + } else if (this.#draw_nodes) { let del; - if (this._geom_viewer) - del = this._draw_nodes; + if (this.#geom_viewer) + del = this.#draw_nodes; else - del = this._clones.mergeVisibles(this._new_draw_nodes, this._draw_nodes); + del = this.#clones.mergeVisibles(this.#new_draw_nodes, this.#draw_nodes); // remove should be fast, do it here for (let n = 0; n < del.length; ++n) - this._clones.createObject3D(del[n].stack, this._toplevel, 'delete_mesh'); + this.#clones.createObject3D(del[n].stack, this.#toplevel, kDeleteMesh); - if (del.length > 0) - this.drawing_log = `Delete ${del.length} nodes`; + if (del.length) + this.#drawing_log = `Delete ${del.length} nodes`; } - this._draw_nodes = this._new_draw_nodes; - delete this._new_draw_nodes; + this.#draw_nodes = this.#new_draw_nodes; + this.#new_draw_nodes = undefined; this.changeStage(stageCollShapes); return true; } if (this.isStage(stageCollShapes)) { // collect shapes - const shapes = this._clones.collectShapes(this._draw_nodes); + const shapes = this.#clones.collectShapes(this.#draw_nodes); // merge old and new list with produced shapes - this._build_shapes = this._clones.mergeShapesLists(this._build_shapes, shapes); + this.#build_shapes = this.#clones.mergeShapesLists(this.#build_shapes, shapes); this.changeStage(stageStartBuild); return true; @@ -2335,25 +2535,22 @@ class TGeoPainter extends ObjectPainter { // one can ask worker to build them or do it ourself if (this.canSubmitToWorker()) { - const job = { limit: this._current_face_limit, shapes: [] }; + const job = { limit: this.#current_face_limit, shapes: [] }; let cnt = 0; - for (let n = 0; n < this._build_shapes.length; ++n) { - let cl = null; - const item = this._build_shapes[n]; + for (let n = 0; n < this.#build_shapes.length; ++n) { + const item = this.#build_shapes[n]; // only submit not-done items if (item.ready || item.geom) { // this is place holder for existing geometry - cl = { id: item.id, ready: true, nfaces: countGeometryFaces(item.geom), refcnt: item.refcnt }; + job.shapes.push({ id: item.id, ready: true, nfaces: numGeometryFaces(item.geom), refcnt: item.refcnt }); } else { - cl = clone(item, null, true); + job.shapes.push(clone(item, null, true)); cnt++; } - - job.shapes.push(cl); } if (cnt > 0) { - /// only if some geom missing, submit job to the worker + // only if some geom missing, submit job to the worker this.submitToWorker(job); this.changeStage(stageWorkerBuild); return 2; @@ -2372,39 +2569,42 @@ class TGeoPainter extends ObjectPainter { if (this.isStage(stageBuild)) { // building shapes - const res = this._clones.buildShapes(this._build_shapes, this._current_face_limit, 500); + const res = this.#clones.buildShapes(this.#build_shapes, this.#current_face_limit, 500); if (res.done) { - this.ctrl.info.num_shapes = this._build_shapes.length; + this.ctrl.info.num_shapes = this.#build_shapes.length; this.changeStage(stageBuildReady); } else { this.ctrl.info.num_shapes = res.shapes; - this.drawing_log = `Creating: ${res.shapes} / ${this._build_shapes.length} shapes, ${res.faces} faces`; + this.#drawing_log = `Creating: ${res.shapes} / ${this.#build_shapes.length} shapes, ${res.faces} faces`; return true; - // if (res.notusedshapes < 30) return true; } } // final stage, create all meshes const tm0 = new Date().getTime(), - toplevel = this.ctrl.project ? this._full_geom : this._toplevel; + toplevel = this.ctrl.project ? this.#fullgeom_proj : this.#toplevel; let build_instanced = false, ready = true; if (!this.ctrl.project) - build_instanced = this._clones.createInstancedMeshes(this.ctrl, toplevel, this._draw_nodes, this._build_shapes, getRootColors()); + build_instanced = this.#clones.createInstancedMeshes(this.ctrl, toplevel, this.#draw_nodes, this.#build_shapes, getRootColors()); if (!build_instanced) { - for (let n = 0; n < this._draw_nodes.length; ++n) { - const entry = this._draw_nodes[n]; - if (entry.done) continue; + for (let n = 0; n < this.#draw_nodes.length; ++n) { + const entry = this.#draw_nodes[n]; + if (entry.done) + continue; - /// shape can be provided with entry itself - const shape = entry.server_shape || this._build_shapes[entry.shapeid]; + // shape can be provided with entry itself + const shape = entry.server_shape || this.#build_shapes[entry.shapeid]; this.createEntryMesh(entry, shape, toplevel); const tm1 = new Date().getTime(); - if (tm1 - tm0 > 500) { ready = false; break; } + if (tm1 - tm0 > 500) { + ready = false; + break; + } } } @@ -2418,18 +2618,18 @@ class TGeoPainter extends ObjectPainter { } if (!this.isStage(stageBuild)) - this.drawing_log = `Building meshes ${this.ctrl.info.num_meshes} / ${this.ctrl.info.num_faces}`; + this.#drawing_log = `Building meshes ${this.ctrl.info.num_meshes} / ${this.ctrl.info.num_faces}`; return true; } if (this.isStage(stageWaitMain)) { // wait for main painter to be ready - - if (!this._main_painter) { + if (!this.getCentral()) { this.changeStage(stageInit, 'Lost main painter'); return false; } - if (!this._main_painter._drawing_ready) return 1; + if (!this.getCentral().isDrawingReady()) + return 1; this.changeStage(stageBuildProj); // just do projection } @@ -2448,14 +2648,14 @@ class TGeoPainter extends ObjectPainter { /** @summary Insert appropriate mesh for given entry */ createEntryMesh(entry, shape, toplevel) { // workaround for the TGeoOverlap, where two branches should get predefined color - if (this._splitColors && entry.stack) { + if (this.ctrl.split_colors && entry.stack) { if (entry.stack[0] === 0) entry.custom_color = 'green'; else if (entry.stack[0] === 1) entry.custom_color = 'blue'; } - this._clones.createEntryMesh(this.ctrl, toplevel, entry, shape, getRootColors()); + this.#clones.createEntryMesh(this.ctrl, toplevel, entry, shape, getRootColors()); return true; } @@ -2465,82 +2665,80 @@ class TGeoPainter extends ObjectPainter { * Shape already should be created and assigned to the node */ appendMoreNodes(nodes, from_drawing) { if (!this.isStage(stageInit) && !from_drawing) { - this._provided_more_nodes = nodes; + this.#provided_more_nodes = nodes; return; } // delete old nodes - if (this._more_nodes) { - for (let n = 0; n < this._more_nodes.length; ++n) { - const entry = this._more_nodes[n], - obj3d = this._clones.createObject3D(entry.stack, this._toplevel, 'delete_mesh'); + if (this.#more_nodes) { + for (let n = 0; n < this.#more_nodes.length; ++n) { + const entry = this.#more_nodes[n], + obj3d = this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); disposeThreejsObject(obj3d); cleanupShape(entry.server_shape); delete entry.server_shape; } + this.#more_nodes = undefined; } - delete this._more_nodes; - - if (!nodes) return; + if (!nodes) + return; const real_nodes = []; - for (let k = 0; k < nodes.length; ++k) { const entry = nodes[k], - shape = entry.server_shape; - if (!shape?.ready) continue; + shape = entry.server_shape; + if (!shape?.ready) + continue; - if (this.createEntryMesh(entry, shape, this._toplevel)) + if (this.createEntryMesh(entry, shape, this.#toplevel)) real_nodes.push(entry); } // remember additional nodes only if they include shape - otherwise one can ignore them - if (real_nodes.length > 0) - this._more_nodes = real_nodes; + if (real_nodes.length) + this.#more_nodes = real_nodes; - if (!from_drawing) this.render3D(); + if (!from_drawing) + this.render3D(); } /** @summary Returns hierarchy of 3D objects used to produce projection. * @desc Typically external master painter is used, but also internal data can be used */ getProjectionSource() { - if (this._clones_owner) - return this._full_geom; - if (!this._main_painter) { - console.warn('MAIN PAINTER DISAPPER'); - return null; - } - if (!this._main_painter._drawing_ready) { + if (this.#clones_owner) + return this.#fullgeom_proj; + if (!this.getCentral()?.isDrawingReady()) { console.warn('MAIN PAINTER NOT READY WHEN DO PROJECTION'); return null; } - return this._main_painter._toplevel; + return this.getCentral().#toplevel; } /** @summary Extend custom geometry bounding box */ extendCustomBoundingBox(box) { - if (!box) return; - if (!this._customBoundingBox) - this._customBoundingBox = new Box3().makeEmpty(); + if (!box) + return; + if (!this.#custom_bounding_box) + this.#custom_bounding_box = new THREE.Box3().makeEmpty(); - const origin = this._customBoundingBox.clone(); - this._customBoundingBox.union(box); + const origin = this.#custom_bounding_box.clone(); + this.#custom_bounding_box.union(box); - if (!this._customBoundingBox.equals(origin)) - this._adjust_camera_with_render = true; + if (!this.#custom_bounding_box.equals(origin)) + this.#adjust_camera_with_render = true; } /** @summary Calculate geometry bounding box */ getGeomBoundingBox(topitem, scalar) { - const box3 = new Box3(), check_any = !this._clones; + const box3 = new THREE.Box3(), check_any = !this.#clones; if (topitem === undefined) - topitem = this._toplevel; + topitem = this.#toplevel; box3.makeEmpty(); - if (this._customBoundingBox && (topitem === this._toplevel)) { - box3.union(this._customBoundingBox); + if (this.#custom_bounding_box && (topitem === this.#toplevel)) { + box3.union(this.#custom_bounding_box); return box3; } @@ -2551,17 +2749,17 @@ class TGeoPainter extends ObjectPainter { } topitem.traverse(mesh => { - if (check_any || (mesh.stack && (mesh instanceof Mesh)) || - (mesh.main_track && (mesh instanceof LineSegments)) || (mesh.stacks && (mesh instanceof InstancedMesh))) + if (check_any || (mesh.stack && (mesh instanceof THREE.Mesh)) || + (mesh.main_track && (mesh instanceof THREE.LineSegments)) || (mesh.stacks && (mesh instanceof THREE.InstancedMesh))) getBoundingBox(mesh, box3); }); if (scalar === 'original') { - box3.translate(new Vector3(-topitem.position.x, -topitem.position.y, -topitem.position.z)); - box3.min.multiply(new Vector3(1/topitem.scale.x, 1/topitem.scale.y, 1/topitem.scale.z)); - box3.max.multiply(new Vector3(1/topitem.scale.x, 1/topitem.scale.y, 1/topitem.scale.z)); + box3.translate(new THREE.Vector3(-topitem.position.x, -topitem.position.y, -topitem.position.z)); + box3.min.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); + box3.max.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); } else if (scalar !== undefined) - box3.expandByVector(box3.getSize(new Vector3()).multiplyScalar(scalar)); + box3.expandByVector(box3.getSize(new THREE.Vector3()).multiplyScalar(scalar)); return box3; } @@ -2570,34 +2768,34 @@ class TGeoPainter extends ObjectPainter { doProjection() { const toplevel = this.getProjectionSource(); - if (!toplevel) return false; + if (!toplevel) + return false; - disposeThreejsObject(this._toplevel, true); + disposeThreejsObject(this.#toplevel, true); // let axis = this.ctrl.project; if (this.ctrl.projectPos === undefined) { const bound = this.getGeomBoundingBox(toplevel), min = bound.min[this.ctrl.project], max = bound.max[this.ctrl.project]; - let mean = (min + max)/2; + let mean = (min + max) / 2; - if ((min < 0) && (max > 0) && (Math.abs(mean) < 0.2*Math.max(-min, max))) mean = 0; // if middle is around 0, use 0 + if ((min < 0) && (max > 0) && (Math.abs(mean) < 0.2 * Math.max(-min, max))) + mean = 0; // if middle is around 0, use 0 this.ctrl.projectPos = mean; } toplevel.traverse(mesh => { - if (!(mesh instanceof Mesh) || !mesh.stack) return; + if (!(mesh instanceof THREE.Mesh) || !mesh.stack) + return; const geom2 = projectGeometry(mesh.geometry, mesh.parent.absMatrix || mesh.parent.matrixWorld, this.ctrl.project, this.ctrl.projectPos, mesh._flippedMesh); - - if (!geom2) return; - - const mesh2 = new Mesh(geom2, mesh.material.clone()); - - this._toplevel.add(mesh2); - - mesh2.stack = mesh.stack; + if (geom2) { + const mesh2 = new THREE.Mesh(geom2, mesh.material.clone()); + this.#toplevel.add(mesh2); + mesh2.stack = mesh.stack; + } }); return true; @@ -2605,231 +2803,261 @@ class TGeoPainter extends ObjectPainter { /** @summary Should be invoked when light configuration changed */ changedLight(box) { - if (!this._camera) return; + if (!this.#camera) + return; const need_render = !box; - if (!box) box = this.getGeomBoundingBox(); + if (!box) + box = this.getGeomBoundingBox(); const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - plights = [], p = (this.ctrl.light.power ?? 1) * 0.5; + sizey = box.max.y - box.min.y, + sizez = box.max.z - box.min.z, + plights = [], p = (this.ctrl.light.power ?? 1) * 0.5; - if (this._camera._lights !== this.ctrl.light.kind) { + if (this.#camera._lights !== this.ctrl.light.kind) { // remove all childs and recreate only necessary lights - disposeThreejsObject(this._camera, true); - - this._camera._lights = this.ctrl.light.kind; - - switch (this._camera._lights) { - case 'ambient' : this._camera.add(new AmbientLight(0xefefef, p)); break; - case 'hemisphere' : this._camera.add(new HemisphereLight(0xffffbb, 0x080820, p)); break; - case 'mix': this._camera.add(new AmbientLight(0xefefef, p)); // intentionally without break - - // eslint-disable-next-line no-fallthrough + disposeThreejsObject(this.#camera, true); + + this.#camera._lights = this.ctrl.light.kind; + + switch (this.#camera._lights) { + case 'ambient': + this.#camera.add(new THREE.AmbientLight(0xefefef, p)); + break; + case 'hemisphere': + this.#camera.add(new THREE.HemisphereLight(0xffffbb, 0x080820, p)); + break; + case 'mix': + this.#camera.add(new THREE.AmbientLight(0xefefef, p)); + // eslint-disable-next-line no-fallthrough default: // 6 point lights for (let n = 0; n < 6; ++n) { - const l = new DirectionalLight(0xefefef, p); - this._camera.add(l); + const l = new THREE.DirectionalLight(0xefefef, p); + this.#camera.add(l); l._id = n; } } } - for (let k = 0; k < this._camera.children.length; ++k) { - const light = this._camera.children[k]; + for (let k = 0; k < this.#camera.children.length; ++k) { + const light = this.#camera.children[k]; let enabled = false; if (light.isAmbientLight || light.isHemisphereLight) { light.intensity = p; continue; } - if (!light.isDirectionalLight) continue; + if (!light.isDirectionalLight) + continue; switch (light._id) { - case 0: light.position.set(sizex/5, sizey/5, sizez/5); enabled = this.ctrl.light.specular; break; - case 1: light.position.set(0, 0, sizez/2); enabled = this.ctrl.light.front; break; - case 2: light.position.set(0, 2*sizey, 0); enabled = this.ctrl.light.top; break; - case 3: light.position.set(0, -2*sizey, 0); enabled = this.ctrl.light.bottom; break; - case 4: light.position.set(-2*sizex, 0, 0); enabled = this.ctrl.light.left; break; - case 5: light.position.set(2*sizex, 0, 0); enabled = this.ctrl.light.right; break; + case 0: + light.position.set(sizex / 5, sizey / 5, sizez / 5); + enabled = this.ctrl.light.specular; + break; + case 1: + light.position.set(0, 0, sizez / 2); + enabled = this.ctrl.light.front; + break; + case 2: + light.position.set(0, 2 * sizey, 0); + enabled = this.ctrl.light.top; + break; + case 3: + light.position.set(0, -2 * sizey, 0); + enabled = this.ctrl.light.bottom; + break; + case 4: + light.position.set(-2 * sizex, 0, 0); + enabled = this.ctrl.light.left; + break; + case 5: + light.position.set(2 * sizex, 0, 0); + enabled = this.ctrl.light.right; + break; } - light.power = enabled ? p*Math.PI*4 : 0; - if (enabled) plights.push(light); + light.power = enabled ? p * Math.PI * 4 : 0; + if (enabled) + plights.push(light); } - // keep light power of all soources constant - plights.forEach(ll => { ll.power = p*4*Math.PI/plights.length; }); + // keep light power of all sources constant + plights.forEach(ll => { ll.power = p * 4 * Math.PI / plights.length; }); - if (need_render) this.render3D(); + if (need_render) + this.render3D(); } - /** @summary Returns true if orthogarphic camera is used */ + /** @summary Returns true if orthographic camera is used */ isOrthoCamera() { return this.ctrl.camera_kind.indexOf('ortho') === 0; } /** @summary Create configured camera */ createCamera() { - if (this._camera) { - this._scene.remove(this._camera); - disposeThreejsObject(this._camera); - delete this._camera; - } + if (this.#camera) { + this.#scene.remove(this.#camera); + disposeThreejsObject(this.#camera); + this.#camera = undefined; + } if (this.isOrthoCamera()) - this._camera = new OrthographicCamera(-this._scene_width/2, this._scene_width/2, this._scene_height/2, -this._scene_height/2, 1, 10000); - else { - this._camera = new PerspectiveCamera(25, this._scene_width / this._scene_height, 1, 10000); - this._camera.up = this.ctrl._yup ? new Vector3(0, 1, 0) : new Vector3(0, 0, 1); + this.#camera = new THREE.OrthographicCamera(-this.#scene_width / 2, this.#scene_width / 2, this.#scene_height / 2, -this.#scene_height / 2, 1, 10000); + else { + this.#camera = new THREE.PerspectiveCamera(25, this.#scene_width / this.#scene_height, 1, 10000); + this.#camera.up = this.ctrl._yup ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(0, 0, 1); } // Light - add default directional light, adjust later - const light = new DirectionalLight(0xefefef, 0.1); + const light = new THREE.DirectionalLight(0xefefef, 0.1); light.position.set(10, 10, 10); - this._camera.add(light); + this.#camera.add(light); - this._scene.add(this._camera); + this.#scene.add(this.#camera); } /** @summary Create special effects */ createSpecialEffects() { - if (this._webgl && this.ctrl.outline && isFunc(this.createOutline)) { + if (this.#webgl && this.ctrl.outline && isFunc(this.createOutline)) { // code used with jsroot-based geometry drawing in EVE7, not important any longer - this._effectComposer = new EffectComposer(this._renderer); - this._effectComposer.addPass(new RenderPass(this._scene, this._camera)); - this.createOutline(this._scene_width, this._scene_height); + this.#effectComposer = new THREE.EffectComposer(this.#renderer); + this.#effectComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); + this.createOutline(this.#scene, this.#camera, this.#scene_width, this.#scene_height); } this.ensureBloom(); } + /** @summary Return current effect composer */ + getEffectComposer() { return this.#effectComposer; } + /** @summary Initial scene creation */ async createScene(w, h, render3d) { - if (this.superimpose) { + if (this.#superimpose) { const cfg = getHistPainter3DCfg(this.getMainPainter()); if (cfg?.renderer) { - this._scene = cfg.scene; - this._scene_width = cfg.scene_width; - this._scene_height = cfg.scene_height; - this._renderer = cfg.renderer; - this._webgl = (this._renderer.jsroot_render3d === constants.Render3D.WebGL); + this.#scene = cfg.scene; + this.#scene_width = cfg.scene_width; + this.#scene_height = cfg.scene_height; + this.#renderer = cfg.renderer; + this.#webgl = (this.#renderer.jsroot_render3d === constants.Render3D.WebGL); - this._toplevel = new Object3D(); - this._scene.add(this._toplevel); + this.#toplevel = new THREE.Object3D(); + this.#scene.add(this.#toplevel); if (cfg.scale_x || cfg.scale_y || cfg.scale_z) - this._toplevel.scale.set(cfg.scale_x, cfg.scale_y, cfg.scale_z); + this.#toplevel.scale.set(cfg.scale_x, cfg.scale_y, cfg.scale_z); if (cfg.offset_x || cfg.offset_y || cfg.offset_z) - this._toplevel.position.set(cfg.offset_x, cfg.offset_y, cfg.offset_z); - this._toplevel.updateMatrix(); - this._toplevel.updateMatrixWorld(); + this.#toplevel.position.set(cfg.offset_x, cfg.offset_y, cfg.offset_z); + this.#toplevel.updateMatrix(); + this.#toplevel.updateMatrixWorld(); - this._camera = cfg.camera; + this.#camera = cfg.camera; } - return this._renderer?.jsroot_dom; + return this.#renderer?.jsroot_dom; } - // three.js 3D drawing - this._scene = new Scene(); - this._fog = new Fog(0xffffff, 1, 10000); - this._scene.fog = this.ctrl.use_fog ? this._fog : null; + return importThreeJs().then(() => { + // three.js 3D drawing + this.#scene = new THREE.Scene(); + this.#fog = new THREE.Fog(0xffffff, 1, 10000); + this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; - this._scene.overrideMaterial = new MeshLambertMaterial({ color: 0x7000ff, vertexColors: false, transparent: true, opacity: 0.2, depthTest: false }); + this.#scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0x7000ff, vertexColors: false, transparent: true, opacity: 0.2, depthTest: false }); - this._scene_width = w; - this._scene_height = h; + this.#scene_width = w; + this.#scene_height = h; - this.createCamera(); + this.createCamera(); - this._selected_mesh = null; + this.#selected_mesh = null; - this._overall_size = 10; + this.#overall_size = 10; - this._toplevel = new Object3D(); + this.#toplevel = new THREE.Object3D(); - this._scene.add(this._toplevel); + this.#scene.add(this.#toplevel); - this._scene.background = new Color(this.ctrl.background); + this.#scene.background = new THREE.Color(this.ctrl.background); - return createRender3D(w, h, render3d, { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true }) - .then(r => { - this._renderer = r; + return createRender3D(w, h, render3d, { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true }); + }).then(r => { + this.#renderer = r; if (this.batch_format) r.jsroot_image_format = this.batch_format; - this._webgl = (this._renderer.jsroot_render3d === constants.Render3D.WebGL); + this.#webgl = (r.jsroot_render3d === constants.Render3D.WebGL); - if (this._renderer.setPixelRatio && !isNodeJs()) - this._renderer.setPixelRatio(window.devicePixelRatio); - this._renderer.setSize(w, h, !this._fit_main_area); - this._renderer.localClippingEnabled = true; + if (isFunc(r.setPixelRatio) && !isNodeJs() && !browser.android) + r.setPixelRatio(window.devicePixelRatio); + r.setSize(w, h, !this.#fit_main_area); + r.localClippingEnabled = true; - this._renderer.setClearColor(this._scene.background, 1); + r.setClearColor(this.#scene.background, 1); - if (this._fit_main_area && this._webgl) { - this._renderer.domElement.style.width = '100%'; - this._renderer.domElement.style.height = '100%'; + if (this.#fit_main_area && this.#webgl) { + r.domElement.style.width = '100%'; + r.domElement.style.height = '100%'; const main = this.selectDom(); if (main.style('position') === 'static') main.style('position', 'relative'); } - this._animating = false; + this.#animating = false; this.ctrl.doubleside = false; // both sides need for clipping this.createSpecialEffects(); - if (this._fit_main_area && !this._webgl) { - // create top-most SVG for geomtery drawings + if (this.#fit_main_area && !this.#webgl) { + // create top-most SVG for geometry drawings const doc = getDocument(), svg = doc.createElementNS(nsSVG, 'svg'); svg.setAttribute('width', w); svg.setAttribute('height', h); - svg.appendChild(this._renderer.jsroot_dom); + svg.appendChild(this.#renderer.jsroot_dom); return svg; } - return this._renderer.jsroot_dom; + return this.#renderer.jsroot_dom; }); } /** @summary Start geometry drawing */ startDrawGeometry(force) { if (!force && !this.isStage(stageInit)) { - this._draw_nodes_again = true; + this.#draw_nodes_again = true; return; } - if (this._clones_owner && this._clones) - this._clones.setDefaultColors(this.ctrl.dflt_colors); + if (this.#clones_owner) + this.#clones?.setDefaultColors(this.ctrl.dflt_colors); - this._startm = new Date().getTime(); - this._last_render_tm = this._startm; - this._last_render_meshes = 0; + this.#last_render_tm = this.#start_render_tm = new Date().getTime(); + this.#last_render_meshes = 0; this.changeStage(stageCollect); - this._drawing_ready = false; + this.#drawing_ready = false; this.ctrl.info.num_meshes = 0; this.ctrl.info.num_faces = 0; this.ctrl.info.num_shapes = 0; - this._selected_mesh = null; + this.#selected_mesh = null; if (this.ctrl.project) { - if (this._clones_owner) { - if (this._full_geom) + if (this.#clones_owner) { + if (this.#fullgeom_proj) this.changeStage(stageBuildProj); - else - this._full_geom = new Object3D(); + else + this.#fullgeom_proj = new THREE.Object3D(); } else this.changeStage(stageWaitMain); } - delete this._last_manifest; - delete this._last_hidden; // clear list of hidden objects + this.#last_manifest = undefined; + this.#last_hidden = undefined; // clear list of hidden objects - delete this._draw_nodes_again; // forget about such flag + this.#draw_nodes_again = undefined; // forget about such flag this.continueDraw(); } @@ -2846,24 +3074,27 @@ class TGeoPainter extends ObjectPainter { /** @summary returns maximal dimension */ getOverallSize(force) { - if (!this._overall_size || force || this._customBoundingBox) { + if (!this.#overall_size || force || this.#custom_bounding_box) { const box = this.getGeomBoundingBox(); // if detect of coordinates fails - ignore - if (!Number.isFinite(box.min.x)) return 1000; + if (!Number.isFinite(box.min.x)) + return 1000; - this._overall_size = 2 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z); + this.#overall_size = 2 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z); } - return this._overall_size; + return this.#overall_size; } /** @summary Create png image with drawing snapshot. */ createSnapshot(filename) { - if (!this._renderer) return; + if (!this.#renderer) + return; this.render3D(0); - const dataUrl = this._renderer.domElement.toDataURL('image/png'); - if (filename === 'asis') return dataUrl; + const dataUrl = this.#renderer.domElement.toDataURL('image/png'); + if (filename === 'asis') + return dataUrl; dataUrl.replace('image/png', 'image/octet-stream'); const doc = getDocument(), link = doc.createElement('a'); @@ -2879,44 +3110,49 @@ class TGeoPainter extends ObjectPainter { /** @summary Returns url parameters defining camera position. * @desc Either absolute position are provided (arg === true) or zoom, roty, rotz parameters */ produceCameraUrl(arg) { - if (!this._camera) + if (!this.#camera) return ''; - if (this._camera.isOrthographicCamera) { - const zoom = Math.round(this._camera.zoom * 100); + if (this.#camera.isOrthographicCamera) { + const zoom = Math.round(this.#camera.zoom * 100); return this.ctrl.camera_kind + (zoom === 100 ? '' : `,zoom=${zoom}`); } let kind = ''; if (this.ctrl.camera_kind !== 'perspective') - kind = this.ctrl.camera_kind + ','; + kind = this.ctrl.camera_kind + ','; if (arg === true) { - const p = this._camera?.position, t = this._controls?.target; - if (!p || !t) return ''; + const p = this.#camera.position, t = this.#controls?.target; + if (!p || !t) + return ''; const conv = v => { let s = ''; - if (v < 0) { s = 'n'; v = -v; } + if (v < 0) { + s = 'n'; + v = -v; + } return s + v.toFixed(0); }; let res = `${kind}camx${conv(p.x)},camy${conv(p.y)},camz${conv(p.z)}`; - if (t.x || t.y || t.z) res += `,camlx${conv(t.x)},camly${conv(t.y)},camlz${conv(t.z)}`; + if (t.x || t.y || t.z) + res += `,camlx${conv(t.x)},camly${conv(t.y)},camlz${conv(t.z)}`; return res; } - if (!this._lookat || !this._camera0pos) + if (!this.#lookat || !this.#camera0pos) return ''; - const pos1 = new Vector3().add(this._camera0pos).sub(this._lookat), - pos2 = new Vector3().add(this._camera.position).sub(this._lookat), - zoom = Math.min(10000, Math.max(1, this.ctrl.zoom * pos2.length() / pos1.length() * 100)); + const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), + pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat), + zoom = Math.min(10000, Math.max(1, this.ctrl.zoom * pos2.length() / pos1.length() * 100)); pos1.normalize(); pos2.normalize(); - const quat = new Quaternion(), euler = new Euler(); + const quat = new THREE.Quaternion(), euler = new THREE.Euler(); quat.setFromUnitVectors(pos1, pos2); euler.setFromQuaternion(quat, 'YZX'); @@ -2924,16 +3160,18 @@ class TGeoPainter extends ObjectPainter { let roty = euler.y / Math.PI * 180, rotz = euler.z / Math.PI * 180; - if (roty < 0) roty += 360; - if (rotz < 0) rotz += 360; + if (roty < 0) + roty += 360; + if (rotz < 0) + rotz += 360; return `${kind}roty${roty.toFixed(0)},rotz${rotz.toFixed(0)},zoom${zoom.toFixed(0)}`; } /** @summary Calculates current zoom factor */ calculateZoom() { - if (this._camera0pos && this._camera && this._lookat) { - const pos1 = new Vector3().add(this._camera0pos).sub(this._lookat), - pos2 = new Vector3().add(this._camera.position).sub(this._lookat); + if (this.#camera0pos && this.#camera && this.#lookat) { + const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), + pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat); return pos2.length() / pos1.length(); } @@ -2942,17 +3180,18 @@ class TGeoPainter extends ObjectPainter { /** @summary Place camera to default position, * @param arg - true forces camera readjustment, 'first' is called when suppose to be first after complete drawing - * @param keep_zoom - tries to keep zomming factor of the camera */ + * @param keep_zoom - tries to keep zooming factor of the camera */ adjustCameraPosition(arg, keep_zoom) { - if (!this._toplevel || this.superimpose) return; + if (!this.#toplevel || this.#superimpose) + return; const force = (arg === true), - first_time = (arg === 'first') || force, - only_set = (arg === 'only_set'), - box = this.getGeomBoundingBox(); + first_time = (arg === 'first') || force, + only_set = (arg === 'only_set'), + box = this.getGeomBoundingBox(); - // let box2 = new Box3().makeEmpty(); - // box2.expandByObject(this._toplevel, true); + // let box2 = new THREE.Box3().makeEmpty(); + // box2.expandByObject(this.#toplevel, true); // console.log('min,max', box.min.x, box.max.x, box.min.y, box.max.y, box.min.z, box.max.z ); // if detect of coordinates fails - ignore @@ -2964,33 +3203,33 @@ class TGeoPainter extends ObjectPainter { const sizex = box.max.x - box.min.x, sizey = box.max.y - box.min.y, sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x)/2, - midy = (box.max.y + box.min.y)/2, - midz = (box.max.z + box.min.z)/2, + midx = (box.max.x + box.min.x) / 2, + midy = (box.max.y + box.min.y) / 2, + midz = (box.max.z + box.min.z) / 2, more = this.ctrl._axis || (this.ctrl.camera_overlay === 'bar') ? 0.2 : 0.1; - if (this._scene_size && !force) { - const d = this._scene_size, test = (v1, v2, scale) => { - if (!scale) scale = Math.abs((v1+v2)/2); - return scale <= 1e-20 ? true : Math.abs(v2-v1)/scale > 0.01; - }, - large_change = test(sizex, d.sizex) || test(sizey, d.sizey) || test(sizez, d.sizez) || - test(midx, d.midx, d.sizex) || test(midy, d.midy, d.sizey) || test(midz, d.midz, d.sizez); - if (!large_change) { + if (this.#scene_size && !force) { + const test = (v1, v2, scale) => { + if (!scale) + scale = Math.abs((v1 + v2) / 2); + return scale <= 1e-20 ? true : Math.abs(v2 - v1) / scale > 0.01; + }, d = this.#scene_size; + if (!test(sizex, d.sizex) && !test(sizey, d.sizey) && !test(sizez, d.sizez) && + !test(midx, d.midx, d.sizex) && !test(midy, d.midy, d.sizey) && !test(midz, d.midz, d.sizez)) { if (this.ctrl.select_in_view) this.startDrawGeometry(); return; } } - this._scene_size = { sizex, sizey, sizez, midx, midy, midz }; + this.#scene_size = { sizex, sizey, sizez, midx, midy, midz }; - this._overall_size = 2 * Math.max(sizex, sizey, sizez); + this.#overall_size = 2 * Math.max(sizex, sizey, sizez); - this._camera.near = this._overall_size / 350; - this._camera.far = this._overall_size * 100; - this._fog.near = this._overall_size * 0.5; - this._fog.far = this._overall_size * 5; + this.#camera.near = this.#overall_size / 350; + this.#camera.far = this.#overall_size * 100; + this.#fog.near = this.#overall_size * 0.5; + this.#fog.far = this.#overall_size * 5; if (first_time) { for (let naxis = 0; naxis < 3; ++naxis) { @@ -2998,8 +3237,8 @@ class TGeoPainter extends ObjectPainter { cc.min = box.min[cc.name]; cc.max = box.max[cc.name]; const sz = cc.max - cc.min; - cc.max += sz*0.01; - cc.min -= sz*0.01; + cc.max += sz * 0.01; + cc.min -= sz * 0.01; if (sz > 100) cc.step = 0.1; else if (sz > 1) @@ -3016,136 +3255,142 @@ class TGeoPainter extends ObjectPainter { } } - let k = 2*this.ctrl.zoom; + let k = 2 * this.ctrl.zoom; const max_all = Math.max(sizex, sizey, sizez), sign = this.ctrl.camera_kind.indexOf('N') > 0 ? -1 : 1; - this._lookat = new Vector3(midx, midy, midz); - this._camera0pos = new Vector3(-2*max_all, 0, 0); // virtual 0 position, where rotation starts + this.#lookat = new THREE.Vector3(midx, midy, midz); + this.#camera0pos = new THREE.Vector3(-2 * max_all, 0, 0); // virtual 0 position, where rotation starts - this._camera.updateMatrixWorld(); - this._camera.updateProjectionMatrix(); + this.#camera.updateMatrixWorld(); + this.#camera.updateProjectionMatrix(); if ((this.ctrl.rotatey || this.ctrl.rotatez) && this.ctrl.can_rotate) { const prev_zoom = this.calculateZoom(); - if (keep_zoom && prev_zoom) k = 2*prev_zoom; + if (keep_zoom && prev_zoom) + k = 2 * prev_zoom; - const euler = new Euler(0, this.ctrl.rotatey/180*Math.PI, this.ctrl.rotatez/180*Math.PI, 'YZX'); + const euler = new THREE.Euler(0, this.ctrl.rotatey / 180 * Math.PI, this.ctrl.rotatez / 180 * Math.PI, 'YZX'); - this._camera.position.set(-k*max_all, 0, 0); - this._camera.position.applyEuler(euler); - this._camera.position.add(new Vector3(midx, midy, midz)); + this.#camera.position.set(-k * max_all, 0, 0); + this.#camera.position.applyEuler(euler); + this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); if (keep_zoom && prev_zoom) { const actual_zoom = this.calculateZoom(); - k *= prev_zoom/actual_zoom; + k *= prev_zoom / actual_zoom; - this._camera.position.set(-k*max_all, 0, 0); - this._camera.position.applyEuler(euler); - this._camera.position.add(new Vector3(midx, midy, midz)); + this.#camera.position.set(-k * max_all, 0, 0); + this.#camera.position.applyEuler(euler); + this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); } } else if (this.ctrl.camx !== undefined && this.ctrl.camy !== undefined && this.ctrl.camz !== undefined) { - this._camera.position.set(this.ctrl.camx, this.ctrl.camy, this.ctrl.camz); - this._lookat.set(this.ctrl.camlx || 0, this.ctrl.camly || 0, this.ctrl.camlz || 0); + this.#camera.position.set(this.ctrl.camx, this.ctrl.camy, this.ctrl.camz); + this.#lookat.set(this.ctrl.camlx || 0, this.ctrl.camly || 0, this.ctrl.camlz || 0); this.ctrl.camx = this.ctrl.camy = this.ctrl.camz = this.ctrl.camlx = this.ctrl.camly = this.ctrl.camlz = undefined; } else if ((this.ctrl.camera_kind === 'orthoXOY') || (this.ctrl.camera_kind === 'orthoXNOY')) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(sign < 0 ? midx*2 : 0, 0, midz + sign*sizez*2); - this._lookat.set(sign < 0 ? midx*2 : 0, 0, midz); - this._camera.left = box.min.x - more*sizex; - this._camera.right = box.max.x + more*sizex; - this._camera.top = box.max.y + more*sizey; - this._camera.bottom = box.min.y - more*sizey; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoSign = sign; - this._camera.orthoZ = [midz, sizez/2]; + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(sign < 0 ? midx * 2 : 0, 0, midz + sign * sizez * 2); + this.#lookat.set(sign < 0 ? midx * 2 : 0, 0, midz); + this.#camera.left = box.min.x - more * sizex; + this.#camera.right = box.max.x + more * sizex; + this.#camera.top = box.max.y + more * sizey; + this.#camera.bottom = box.min.y - more * sizey; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midz, sizez / 2]; } else if ((this.ctrl.camera_kind === 'orthoXOZ') || (this.ctrl.camera_kind === 'orthoXNOZ')) { - this._camera.up.set(0, 0, 1); - this._camera.position.set(sign < 0 ? midx*2 : 0, midy - sign*sizey*2, 0); - this._lookat.set(sign < 0 ? midx*2 : 0, midy, 0); - this._camera.left = box.min.x - more*sizex; - this._camera.right = box.max.x + more*sizex; - this._camera.top = box.max.z + more*sizez; - this._camera.bottom = box.min.z - more*sizez; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [0, 2, 1]; - this._camera.orthoRotation = geom => geom.rotateX(Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midy, -sizey/2]; + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(sign < 0 ? midx * 2 : 0, midy - sign * sizey * 2, 0); + this.#lookat.set(sign < 0 ? midx * 2 : 0, midy, 0); + this.#camera.left = box.min.x - more * sizex; + this.#camera.right = box.max.x + more * sizex; + this.#camera.top = box.max.z + more * sizez; + this.#camera.bottom = box.min.z - more * sizez; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [0, 2, 1]; + this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midy, -sizey / 2]; } else if ((this.ctrl.camera_kind === 'orthoZOY') || (this.ctrl.camera_kind === 'orthoZNOY')) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx - sign*sizex*2, 0, sign < 0 ? midz*2 : 0); - this._lookat.set(midx, 0, sign < 0 ? midz*2 : 0); - this._camera.left = box.min.z - more*sizez; - this._camera.right = box.max.z + more*sizez; - this._camera.top = box.max.y + more*sizey; - this._camera.bottom = box.min.y - more*sizey; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [2, 1, 0]; - this._camera.orthoRotation = geom => geom.rotateY(-Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midx, -sizex/2]; + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - sign * sizex * 2, 0, sign < 0 ? midz * 2 : 0); + this.#lookat.set(midx, 0, sign < 0 ? midz * 2 : 0); + this.#camera.left = box.min.z - more * sizez; + this.#camera.right = box.max.z + more * sizez; + this.#camera.top = box.max.y + more * sizey; + this.#camera.bottom = box.min.y - more * sizey; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [2, 1, 0]; + this.#camera.orthoRotation = geom => geom.rotateY(-Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midx, -sizex / 2]; } else if ((this.ctrl.camera_kind === 'orthoZOX') || (this.ctrl.camera_kind === 'orthoZNOX')) { - this._camera.up.set(1, 0, 0); - this._camera.position.set(0, midy - sign*sizey*2, sign > 0 ? midz*2 : 0); - this._lookat.set(0, midy, sign > 0 ? midz*2 : 0); - this._camera.left = box.min.z - more*sizez; - this._camera.right = box.max.z + more*sizez; - this._camera.top = box.max.x + more*sizex; - this._camera.bottom = box.min.x - more*sizex; - if (!keep_zoom) this._camera.zoom = this.ctrl.zoom || 1; - this._camera.orthoIndicies = [2, 0, 1]; - this._camera.orthoRotation = geom => geom.rotateX(Math.PI/2).rotateY(Math.PI/2); - this._camera.orthoSign = sign; - this._camera.orthoZ = [midy, -sizey/2]; + this.#camera.up.set(1, 0, 0); + this.#camera.position.set(0, midy - sign * sizey * 2, sign > 0 ? midz * 2 : 0); + this.#lookat.set(0, midy, sign > 0 ? midz * 2 : 0); + this.#camera.left = box.min.z - more * sizez; + this.#camera.right = box.max.z + more * sizez; + this.#camera.top = box.max.x + more * sizex; + this.#camera.bottom = box.min.x - more * sizex; + if (!keep_zoom) + this.#camera.zoom = this.ctrl.zoom || 1; + this.#camera.orthoIndicies = [2, 0, 1]; + this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2).rotateY(Math.PI / 2); + this.#camera.orthoSign = sign; + this.#camera.orthoZ = [midy, -sizey / 2]; } else if (this.ctrl.project) { switch (this.ctrl.project) { - case 'x': this._camera.position.set(k*1.5*Math.max(sizey, sizez), 0, 0); break; - case 'y': this._camera.position.set(0, k*1.5*Math.max(sizex, sizez), 0); break; - case 'z': this._camera.position.set(0, 0, k*1.5*Math.max(sizex, sizey)); break; + case 'x': this.#camera.position.set(k * 1.5 * Math.max(sizey, sizez), 0, 0); break; + case 'y': this.#camera.position.set(0, k * 1.5 * Math.max(sizex, sizez), 0); break; + case 'z': this.#camera.position.set(0, 0, k * 1.5 * Math.max(sizex, sizey)); break; } } else if (this.ctrl.camera_kind === 'perspXOZ') { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx - 3*max_all, midy, midz); + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - 3 * max_all, midy, midz); } else if (this.ctrl.camera_kind === 'perspYOZ') { - this._camera.up.set(1, 0, 0); - this._camera.position.set(midx, midy - 3*max_all, midz); + this.#camera.up.set(1, 0, 0); + this.#camera.position.set(midx, midy - 3 * max_all, midz); } else if (this.ctrl.camera_kind === 'perspXOY') { - this._camera.up.set(0, 0, 1); - this._camera.position.set(midx - 3*max_all, midy, midz); + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(midx - 3 * max_all, midy, midz); } else if (this.ctrl._yup) { - this._camera.up.set(0, 1, 0); - this._camera.position.set(midx-k*Math.max(sizex, sizez), midy+k*sizey, midz-k*Math.max(sizex, sizez)); + this.#camera.up.set(0, 1, 0); + this.#camera.position.set(midx - k * Math.max(sizex, sizez), midy + k * sizey, midz - k * Math.max(sizex, sizez)); } else { - this._camera.up.set(0, 0, 1); - this._camera.position.set(midx-k*Math.max(sizex, sizey), midy-k*Math.max(sizex, sizey), midz+k*sizez); + this.#camera.up.set(0, 0, 1); + this.#camera.position.set(midx - k * Math.max(sizex, sizey), midy - k * Math.max(sizex, sizey), midz + k * sizez); } - if (this._camera.isOrthographicCamera && this.isOrthoCamera() && this._scene_width && this._scene_height) { - const screen_ratio = this._scene_width / this._scene_height, - szx = this._camera.right - this._camera.left, szy = this._camera.top - this._camera.bottom; + if (this.#camera.isOrthographicCamera && this.isOrthoCamera() && this.#scene_width && this.#scene_height) { + const screen_ratio = this.#scene_width / this.#scene_height, + szx = this.#camera.right - this.#camera.left, szy = this.#camera.top - this.#camera.bottom; if (screen_ratio > szx / szy) { // screen wider than actual geometry - const m = (this._camera.right + this._camera.left) / 2; - this._camera.left = m - szy * screen_ratio / 2; - this._camera.right = m + szy * screen_ratio / 2; + const m = (this.#camera.right + this.#camera.left) / 2; + this.#camera.left = m - szy * screen_ratio / 2; + this.#camera.right = m + szy * screen_ratio / 2; } else { - // screen heigher than actual geometry - const m = (this._camera.top + this._camera.bottom) / 2; - this._camera.top = m + szx / screen_ratio / 2; - this._camera.bottom = m - szx / screen_ratio / 2; + // screen higher than actual geometry + const m = (this.#camera.top + this.#camera.bottom) / 2; + this.#camera.top = m + szx / screen_ratio / 2; + this.#camera.bottom = m - szx / screen_ratio / 2; } } - this._camera.lookAt(this._lookat); - this._camera.updateProjectionMatrix(); + this.#camera.lookAt(this.#lookat); + this.#camera.updateProjectionMatrix(); this.changedLight(box); - if (this._controls) { - this._controls.target.copy(this._lookat); - if (!only_set) this._controls.update(); + if (this.#controls) { + this.#controls.target.copy(this.#lookat); + if (!only_set) + this.#controls.update(); } // recheck which elements to draw @@ -3155,13 +3400,14 @@ class TGeoPainter extends ObjectPainter { /** @summary Specifies camera position as rotation around geometry center */ setCameraPosition(rotatey, rotatez, zoom) { - if (!this.ctrl) return; + if (!this.ctrl) + return; this.ctrl.rotatey = rotatey || 0; this.ctrl.rotatez = rotatez || 0; let preserve_zoom = false; if (zoom && Number.isFinite(zoom)) this.ctrl.zoom = zoom; - else + else preserve_zoom = true; this.adjustCameraPosition(false, preserve_zoom); @@ -3183,85 +3429,86 @@ class TGeoPainter extends ObjectPainter { /** @summary focus on item */ focusOnItem(itemname) { - if (!itemname || !this._clones) return; + if (!itemname || !this.#clones) + return; - const stack = this._clones.findStackByName(itemname); + const stack = this.#clones.findStackByName(itemname); if (stack) - this.focusCamera(this._clones.resolveStack(stack, true), false); + this.focusCamera(this.#clones.resolveStack(stack, true), false); } - /** @summary focus camera on speicifed position */ + /** @summary focus camera on specified position */ focusCamera(focus, autoClip) { if (this.ctrl.project || this.isOrthoCamera()) { this.adjustCameraPosition(true); return this.render3D(); } - let box = new Box3(); + let box = new THREE.Box3(); if (focus === undefined) box = this.getGeomBoundingBox(); - else if (focus instanceof Mesh) + else if (focus instanceof THREE.Mesh) box.setFromObject(focus); - else { - const center = new Vector3().setFromMatrixPosition(focus.matrix), - node = focus.node, - halfDelta = new Vector3(node.fDX, node.fDY, node.fDZ).multiplyScalar(0.5); + else { + const center = new THREE.Vector3().setFromMatrixPosition(focus.matrix), + node = focus.node, + halfDelta = new THREE.Vector3(node.fDX, node.fDY, node.fDZ).multiplyScalar(0.5); box.min = center.clone().sub(halfDelta); box.max = center.clone().add(halfDelta); } const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x)/2, - midy = (box.max.y + box.min.y)/2, - midz = (box.max.z + box.min.z)/2; + sizey = box.max.y - box.min.y, + sizez = box.max.z - box.min.z, + midx = (box.max.x + box.min.x) / 2, + midy = (box.max.y + box.min.y) / 2, + midz = (box.max.z + box.min.z) / 2; let position, frames = 50, step = 0; if (this.ctrl._yup) - position = new Vector3(midx-2*Math.max(sizex, sizez), midy+2*sizey, midz-2*Math.max(sizex, sizez)); + position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizez), midy + 2 * sizey, midz - 2 * Math.max(sizex, sizez)); else - position = new Vector3(midx-2*Math.max(sizex, sizey), midy-2*Math.max(sizex, sizey), midz+2*sizez); + position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizey), midy - 2 * Math.max(sizex, sizey), midz + 2 * sizez); - const target = new Vector3(midx, midy, midz), - oldTarget = this._controls.target, + const target = new THREE.Vector3(midx, midy, midz), + oldTarget = this.#controls.target, // Amount to change camera position at each step - posIncrement = position.sub(this._camera.position).divideScalar(frames), + posIncrement = position.sub(this.#camera.position).divideScalar(frames), // Amount to change 'lookAt' so it will end pointed at target targetIncrement = target.sub(oldTarget).divideScalar(frames); - autoClip = autoClip && this._webgl; + autoClip = autoClip && this.#webgl; // Automatic Clipping if (autoClip) { for (let axis = 0; axis < 3; ++axis) { const cc = this.ctrl.clip[axis]; - if (!cc.enabled) { cc.value = cc.min; cc.enabled = true; } + if (!cc.enabled) { + cc.value = cc.min; + cc.enabled = true; + } cc.inc = ((cc.min + cc.max) / 2 - cc.value) / frames; } this.updateClipping(); } - this._animating = true; - - // Interpolate // + this.#animating = true; const animate = () => { - if (this._animating === undefined) return; + if (this.#animating === undefined) + return; - if (this._animating) + if (this.#animating) requestAnimationFrame(animate); - else { - if (!this._geom_viewer) - this.startDrawGeometry(); - } - const smoothFactor = -Math.cos((2.0*Math.PI*step)/frames) + 1.0; - this._camera.position.add(posIncrement.clone().multiplyScalar(smoothFactor)); + else if (!this.#geom_viewer) + this.startDrawGeometry(); + const smoothFactor = -Math.cos((2.0 * Math.PI * step) / frames) + 1.0; + this.#camera.position.add(posIncrement.clone().multiplyScalar(smoothFactor)); oldTarget.add(targetIncrement.clone().multiplyScalar(smoothFactor)); - this._lookat = oldTarget; - this._camera.lookAt(this._lookat); - this._camera.updateProjectionMatrix(); + this.#lookat = oldTarget; + this.#camera.lookAt(this.#lookat); + this.#camera.updateProjectionMatrix(); const tm1 = new Date().getTime(); if (autoClip) { @@ -3272,61 +3519,63 @@ class TGeoPainter extends ObjectPainter { this.render3D(0); const tm2 = new Date().getTime(); - if ((step === 0) && (tm2-tm1 > 200)) frames = 20; + if ((step === 0) && (tm2 - tm1 > 200)) + frames = 20; step++; - this._animating = step < frames; + this.#animating = step < frames; }; animate(); - // this._controls.update(); + // this.#controls.update(); } - /** @summary actiavte auto rotate */ + /** @summary activate auto rotate */ autorotate(speed) { const rotSpeed = (speed === undefined) ? 2.0 : speed; let last = new Date(); const animate = () => { - if (!this._renderer || !this.ctrl) return; + if (!this.#renderer || !this.ctrl) + return; const current = new Date(); if (this.ctrl.rotate) requestAnimationFrame(animate); - if (this._controls) { - this._controls.autoRotate = this.ctrl.rotate; - this._controls.autoRotateSpeed = rotSpeed * (current.getTime() - last.getTime()) / 16.6666; - this._controls.update(); + if (this.#controls) { + this.#controls.autoRotate = this.ctrl.rotate; + this.#controls.autoRotateSpeed = rotSpeed * (current.getTime() - last.getTime()) / 16.6666; + this.#controls.update(); } last = new Date(); this.render3D(0); }; - if (this._webgl) animate(); + if (this.#webgl) + animate(); } /** @summary called at the end of scene drawing */ - completeScene() { - } + completeScene() {} /** @summary Drawing with 'count' option - * @desc Scans hieararchy and check for unique nodes + * @desc Scans hierarchy and check for unique nodes * @return {Promise} with object drawing ready */ async drawCount(unqievis, clonetm) { const makeTime = tm => (this.isBatchMode() ? 'anytime' : tm.toString()) + ' ms', - res = ['Unique nodes: ' + this._clones.nodes.length, - 'Unique visible: ' + unqievis, - 'Time to clone: ' + makeTime(clonetm)]; + res = ['Unique nodes: ' + this.#clones.nodes.length, + 'Unique visible: ' + unqievis, + 'Time to clone: ' + makeTime(clonetm)]; // need to fill cached value line numvischld - this._clones.scanVisible(); + this.#clones.scanVisible(); let nshapes = 0; const arg = { - clones: this._clones, + clones: this.#clones, cnt: [], func(node) { if (this.cnt[this.last] === undefined) @@ -3340,7 +3589,7 @@ class TGeoPainter extends ObjectPainter { }; let tm1 = new Date().getTime(), - numvis = this._clones.scanVisible(arg), + numvis = this.#clones.scanVisible(arg), tm2 = new Date().getTime(); res.push(`Total visible nodes: ${numvis}`, `Total shapes: ${nshapes}`); @@ -3350,7 +3599,7 @@ class TGeoPainter extends ObjectPainter { res.push(` lvl${lvl}: ${arg.cnt[lvl]}`); } - res.push(`Time to scan: ${makeTime(tm2-tm1)}`, '', 'Check timing for matrix calculations ...'); + res.push(`Time to scan: ${makeTime(tm2 - tm1)}`, '', 'Check timing for matrix calculations ...'); const elem = this.selectDom().style('overflow', 'auto'); @@ -3362,10 +3611,10 @@ class TGeoPainter extends ObjectPainter { return postponePromise(() => { arg.domatrix = true; tm1 = new Date().getTime(); - numvis = this._clones.scanVisible(arg); + numvis = this.#clones.scanVisible(arg); tm2 = new Date().getTime(); - const last_str = `Time to scan with matrix: ${makeTime(tm2-tm1)}`; + const last_str = `Time to scan with matrix: ${makeTime(tm2 - tm1)}`; if (this.isBatchMode()) res.push(last_str); else @@ -3381,22 +3630,24 @@ class TGeoPainter extends ObjectPainter { * By default function with name 'extract_geo_tracks' is checked * @return {Promise} handling of drop operation */ async performDrop(obj, itemname, hitem, opt) { - if (obj?.$kind === 'TTree') { + if (obj?.$kind === clTTree) { // drop tree means function call which must extract tracks from provided tree let funcname = 'extract_geo_tracks'; if (opt && opt.indexOf('$') > 0) { funcname = opt.slice(0, opt.indexOf('$')); - opt = opt.slice(opt.indexOf('$')+1); + opt = opt.slice(opt.indexOf('$') + 1); } const func = findFunction(funcname); - if (!func) return Promise.reject(Error(`Function ${funcname} not found`)); + if (!func) + return Promise.reject(Error(`Function ${funcname} not found`)); return func(obj, opt).then(tracks => { - if (!tracks) return this; + if (!tracks) + return this; // FIXME: probably tracks should be remembered? return this.drawExtras(tracks, '', false).then(() => { @@ -3407,9 +3658,11 @@ class TGeoPainter extends ObjectPainter { } return this.drawExtras(obj, itemname).then(is_any => { - if (!is_any) return this; + if (!is_any) + return this; - if (hitem) hitem._painter = this; // set for the browser item back pointer + if (hitem) + hitem._painter = this; // set for the browser item back pointer return this.render3D(100); }); @@ -3417,12 +3670,14 @@ class TGeoPainter extends ObjectPainter { /** @summary function called when mouse is going over the item in the browser */ mouseOverHierarchy(on, itemname, hitem) { - if (!this.ctrl) return; // protection for cleaned-up painter + if (!this.ctrl) + return; // protection for cleaned-up painter const obj = hitem._obj; // let's highlight tracks and hits only for the time being - if (!obj || (obj._typename !== clTEveTrack && obj._typename !== clTEvePointSet && obj._typename !== clTPolyMarker3D)) return; + if (!obj || (obj._typename !== clTEveTrack && obj._typename !== clTEvePointSet && obj._typename !== clTPolyMarker3D)) + return; this.highlightMesh(null, 0x00ff00, on ? obj : null); } @@ -3430,7 +3685,7 @@ class TGeoPainter extends ObjectPainter { /** @summary clear extra drawn objects like tracks or hits */ clearExtras() { this.getExtrasContainer('delete'); - delete this._extraObjects; // workaround, later will be normal function + this.#extra_objects = undefined; // workaround, later will be normal function this.render3D(); } @@ -3438,37 +3693,40 @@ class TGeoPainter extends ObjectPainter { * @desc Rendered after main geometry volumes are created * Check if object already exists to prevent duplication */ addExtra(obj, itemname) { - if (this._extraObjects === undefined) - this._extraObjects = create(clTList); + if (!this.#extra_objects) + this.#extra_objects = create(clTList); - if (this._extraObjects.arr.indexOf(obj) >= 0) + if (this.#extra_objects.arr.indexOf(obj) >= 0) return false; - this._extraObjects.Add(obj, itemname); + this.#extra_objects.Add(obj, itemname); delete obj.$hidden_via_menu; // remove previous hidden property return true; } - /** @summary manipulate visisbility of extra objects, used for HierarchyPainter + /** @summary manipulate visibility of extra objects, used for HierarchyPainter * @private */ extraObjectVisible(hpainter, hitem, toggle) { - if (!this._extraObjects) return; + if (!this.#extra_objects) + return; const itemname = hpainter.itemFullName(hitem); - let indx = this._extraObjects.opt.indexOf(itemname); + let indx = this.#extra_objects.opt.indexOf(itemname); if ((indx < 0) && hitem._obj) { - indx = this._extraObjects.arr.indexOf(hitem._obj); + indx = this.#extra_objects.arr.indexOf(hitem._obj); // workaround - if object found, replace its name - if (indx >= 0) this._extraObjects.opt[indx] = itemname; + if (indx >= 0) + this.#extra_objects.opt[indx] = itemname; } - if (indx < 0) return; + if (indx < 0) + return; - const obj = this._extraObjects.arr[indx]; - let res = !!obj.$hidden_via_menu; + const obj = this.#extra_objects.arr[indx]; + let res = Boolean(obj.$hidden_via_menu); if (toggle) { obj.$hidden_via_menu = res; @@ -3476,7 +3734,10 @@ class TGeoPainter extends ObjectPainter { let mesh = null; // either found painted object or just draw once again - this._toplevel.traverse(node => { if (node.geo_object === obj) mesh = node; }); + this.#toplevel.traverse(node => { + if (node.geo_object === obj) + mesh = node; + }); if (mesh) { mesh.visible = res; @@ -3510,12 +3771,14 @@ class TGeoPainter extends ObjectPainter { let promise = false; if ((obj._typename === clTList) || (obj._typename === clTObjArray)) { - if (!obj.arr) return false; + if (!obj.arr) + return false; const parr = []; for (let n = 0; n < obj.arr.length; ++n) { const sobj = obj.arr[n]; let sname = obj.opt ? obj.opt[n] : ''; - if (!sname) sname = (itemname || '<prnt>') + `/[${n}]`; + if (!sname) + sname = (itemname || '<prnt>') + `/[${n}]`; parr.push(this.drawExtras(sobj, sname, add_objects)); } promise = Promise.all(parr).then(ress => ress.indexOf(true) >= 0); @@ -3554,35 +3817,45 @@ class TGeoPainter extends ObjectPainter { /** @summary returns container for extra objects */ getExtrasContainer(action, name) { - if (!this._toplevel) return null; + if (!this.#toplevel) + return null; - if (!name) name = 'tracks'; + if (!name) + name = 'tracks'; let extras = null; const lst = []; - for (let n = 0; n < this._toplevel.children.length; ++n) { - const chld = this._toplevel.children[n]; - if (!chld._extras) continue; - if (action === 'collect') { lst.push(chld); continue; } - if (chld._extras === name) { extras = chld; break; } + for (let n = 0; n < this.#toplevel.children.length; ++n) { + const chld = this.#toplevel.children[n]; + if (!chld._extras) + continue; + if (action === 'collect') { + lst.push(chld); + continue; + } + if (chld._extras === name) { + extras = chld; + break; + } } if (action === 'collect') { for (let k = 0; k < lst.length; ++k) - this._toplevel.remove(lst[k]); + this.#toplevel.remove(lst[k]); return lst; } if (action === 'delete') { - if (extras) this._toplevel.remove(extras); + if (extras) + this.#toplevel.remove(extras); disposeThreejsObject(extras); return null; } if ((action !== 'get') && !extras) { - extras = new Object3D(); + extras = new THREE.Object3D(); extras._extras = name; - this._toplevel.add(extras); + this.#toplevel.add(extras); } return extras; @@ -3594,7 +3867,7 @@ class TGeoPainter extends ObjectPainter { const container = this.getExtrasContainer('', name); if (container) container.add(obj); - else { + else { console.warn('Fail to add object to extras'); disposeThreejsObject(obj); } @@ -3602,27 +3875,28 @@ class TGeoPainter extends ObjectPainter { /** @summary drawing TGeoTrack */ drawGeoTrack(track, itemname) { - if (!track?.fNpoints) return false; + if (!track?.fNpoints) + return false; const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), // line width not supported on windows color = getColor(track.fLineColor) || '#ff00ff', - npoints = Math.round(track.fNpoints/4), // each track point has [x,y,z,t] coordinate - buf = new Float32Array((npoints-1)*6), + npoints = Math.round(track.fNpoints / 4), // each track point has [x,y,z,t] coordinate + buf = new Float32Array((npoints - 1) * 6), projv = this.ctrl.projectPos, projx = (this.ctrl.project === 'x'), projy = (this.ctrl.project === 'y'), projz = (this.ctrl.project === 'z'); - for (let k = 0, pos = 0; k < npoints-1; ++k, pos+=6) { - buf[pos] = projx ? projv : track.fPoints[k*4]; - buf[pos+1] = projy ? projv : track.fPoints[k*4+1]; - buf[pos+2] = projz ? projv : track.fPoints[k*4+2]; - buf[pos+3] = projx ? projv : track.fPoints[k*4+4]; - buf[pos+4] = projy ? projv : track.fPoints[k*4+5]; - buf[pos+5] = projz ? projv : track.fPoints[k*4+6]; + for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : track.fPoints[k * 4]; + buf[pos + 1] = projy ? projv : track.fPoints[k * 4 + 1]; + buf[pos + 2] = projz ? projv : track.fPoints[k * 4 + 2]; + buf[pos + 3] = projx ? projv : track.fPoints[k * 4 + 4]; + buf[pos + 4] = projy ? projv : track.fPoints[k * 4 + 5]; + buf[pos + 5] = projz ? projv : track.fPoints[k * 4 + 6]; } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), line = createLineSegments(buf, lineMaterial); line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front @@ -3640,29 +3914,30 @@ class TGeoPainter extends ObjectPainter { /** @summary drawing TPolyLine3D */ drawPolyLine(line, itemname) { - if (!line) return false; + if (!line) + return false; const linewidth = browser.isWin ? 1 : (line.fLineWidth || 1), color = getColor(line.fLineColor) || '#ff00ff', npoints = line.fN, fP = line.fP, - buf = new Float32Array((npoints-1)*6), + buf = new Float32Array((npoints - 1) * 6), projv = this.ctrl.projectPos, projx = (this.ctrl.project === 'x'), projy = (this.ctrl.project === 'y'), projz = (this.ctrl.project === 'z'); - for (let k = 0, pos = 0; k < npoints-1; ++k, pos += 6) { - buf[pos] = projx ? projv : fP[k*3]; - buf[pos+1] = projy ? projv : fP[k*3+1]; - buf[pos+2] = projz ? projv : fP[k*3+2]; - buf[pos+3] = projx ? projv : fP[k*3+3]; - buf[pos+4] = projy ? projv : fP[k*3+4]; - buf[pos+5] = projz ? projv : fP[k*3+5]; + for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : fP[k * 3]; + buf[pos + 1] = projy ? projv : fP[k * 3 + 1]; + buf[pos + 2] = projz ? projv : fP[k * 3 + 2]; + buf[pos + 3] = projx ? projv : fP[k * 3 + 3]; + buf[pos + 4] = projy ? projv : fP[k * 3 + 4]; + buf[pos + 5] = projz ? projv : fP[k * 3 + 5]; } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), - line3d = createLineSegments(buf, lineMaterial); + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), + line3d = createLineSegments(buf, lineMaterial); line3d.defaultOrder = line3d.renderOrder = 1000000; // to bring line to the front line3d.geo_name = itemname; @@ -3676,26 +3951,27 @@ class TGeoPainter extends ObjectPainter { /** @summary Drawing TEveTrack */ drawEveTrack(track, itemname) { - if (!track || (track.fN <= 0)) return false; + if (!track || (track.fN <= 0)) + return false; const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), color = getColor(track.fLineColor) || '#ff00ff', - buf = new Float32Array((track.fN-1)*6), + buf = new Float32Array((track.fN - 1) * 6), projv = this.ctrl.projectPos, projx = (this.ctrl.project === 'x'), projy = (this.ctrl.project === 'y'), projz = (this.ctrl.project === 'z'); - for (let k = 0, pos = 0; k < track.fN-1; ++k, pos+=6) { - buf[pos] = projx ? projv : track.fP[k*3]; - buf[pos+1] = projy ? projv : track.fP[k*3+1]; - buf[pos+2] = projz ? projv : track.fP[k*3+2]; - buf[pos+3] = projx ? projv : track.fP[k*3+3]; - buf[pos+4] = projy ? projv : track.fP[k*3+4]; - buf[pos+5] = projz ? projv : track.fP[k*3+5]; + for (let k = 0, pos = 0; k < track.fN - 1; ++k, pos += 6) { + buf[pos] = projx ? projv : track.fP[k * 3]; + buf[pos + 1] = projy ? projv : track.fP[k * 3 + 1]; + buf[pos + 2] = projz ? projv : track.fP[k * 3 + 2]; + buf[pos + 3] = projx ? projv : track.fP[k * 3 + 3]; + buf[pos + 4] = projy ? projv : track.fP[k * 3 + 4]; + buf[pos + 5] = projz ? projv : track.fP[k * 3 + 5]; } - const lineMaterial = new LineBasicMaterial({ color, linewidth }), + const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), line = createLineSegments(buf, lineMaterial); line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front @@ -3720,13 +3996,13 @@ class TGeoPainter extends ObjectPainter { projx = (this.ctrl.project === 'x'), projy = (this.ctrl.project === 'y'), projz = (this.ctrl.project === 'z'), - hit_scale = Math.max(hit.fMarkerSize * this.getOverallSize() * (this._dummy ? 0.015 : 0.005), 0.2), - pnts = new PointsCreator(nhits, this._webgl, hit_scale); + hit_scale = Math.max(hit.fMarkerSize * this.getOverallSize() * (this.getOptions().dummy ? 0.015 : 0.005), 0.2), + pnts = new PointsCreator(nhits, this.#webgl, hit_scale); for (let i = 0; i < nhits; i++) { - pnts.addPoint(projx ? projv : hit.fP[i*3], - projy ? projv : hit.fP[i*3+1], - projz ? projv : hit.fP[i*3+2]); + pnts.addPoint(projx ? projv : hit.fP[i * 3], + projy ? projv : hit.fP[i * 3 + 1], + projz ? projv : hit.fP[i * 3 + 2]); } return pnts.createPoints({ color: getColor(hit.fMarkerColor) || '#0000ff', style: hit.fMarkerStyle }).then(mesh => { @@ -3741,8 +4017,10 @@ class TGeoPainter extends ObjectPainter { /** @summary Draw extra shape on the geometry */ drawExtraShape(obj, itemname) { + // eslint-disable-next-line no-use-before-define const mesh = build(obj); - if (!mesh) return false; + if (!mesh) + return false; mesh.geo_name = itemname; mesh.geo_object = obj; @@ -3751,27 +4029,31 @@ class TGeoPainter extends ObjectPainter { return true; } - /** @summary Serach for specified node + /** @summary Search for specified node * @private */ findNodeWithVolume(name, action, prnt, itemname, volumes) { let first_level = false, res = null; if (!prnt) { prnt = this.getGeometry(); - if (!prnt && (getNodeKind(prnt) !== 0)) return null; - itemname = this.geo_manager ? prnt.fName : ''; + if (!prnt && (getNodeKind(prnt) !== kindGeo)) + return null; + itemname = this.#geo_manager ? prnt.fName : ''; first_level = true; volumes = []; } else { - if (itemname) itemname += '/'; + if (itemname) + itemname += '/'; itemname += prnt.fName; } - if (!prnt.fVolume || prnt.fVolume._searched) return null; + if (!prnt.fVolume || prnt.fVolume._searched) + return null; if (name.test(prnt.fVolume.fName)) { res = action({ node: prnt, item: itemname }); - if (res) return res; + if (res) + return res; } prnt.fVolume._searched = true; @@ -3780,7 +4062,8 @@ class TGeoPainter extends ObjectPainter { if (prnt.fVolume.fNodes) { for (let n = 0, len = prnt.fVolume.fNodes.arr.length; n < len; ++n) { res = this.findNodeWithVolume(name, action, prnt.fVolume.fNodes.arr[n], itemname, volumes); - if (res) break; + if (res) + break; } } @@ -3792,60 +4075,75 @@ class TGeoPainter extends ObjectPainter { return res; } + /** @summary Search for created shape for nodeid + * @desc Used in ROOT geometry painter */ + findNodeShape(nodeid) { + if ((nodeid !== undefined) && !this.#draw_nodes) { + for (let k = 0; k < this.#draw_nodes.length; ++k) { + const item = this.geo_painter._draw_nodes[k]; + if ((item.nodeid === nodeid) && item.server_shape) + return item.server_shape; + } + } + } + /** @summary Process script option - load and execute some gGeoManager-related calls */ async loadMacro(script_name) { const result = { obj: this.getGeometry(), prefix: '' }; - if (this.geo_manager) + if (this.#geo_manager) result.prefix = result.obj.fName; - if (!script_name || (script_name.length < 3) || (getNodeKind(result.obj) !== 0)) + if (!script_name || (script_name.length < 3) || (getNodeKind(result.obj) !== kindGeo)) return result; const mgr = { - GetVolume: name => { - const regexp = new RegExp('^'+name+'$'), - currnode = this.findNodeWithVolume(regexp, arg => arg); - - if (!currnode) console.log(`Did not found ${name} volume`); - - // return proxy object with several methods, typically used in ROOT geom scripts - return { - found: currnode, - fVolume: currnode?.node?.fVolume, - InvisibleAll(flag) { - setInvisibleAll(this.fVolume, flag); - }, - Draw() { - if (!this.found || !this.fVolume) return; - result.obj = this.found.node; - result.prefix = this.found.item; - console.log(`Select volume for drawing ${this.fVolume.fName} ${result.prefix}`); - }, - SetTransparency(lvl) { - if (this.fVolume?.fMedium?.fMaterial) - this.fVolume.fMedium.fMaterial.fFillStyle = 3000 + lvl; - }, - SetLineColor(col) { - if (this.fVolume) this.fVolume.fLineColor = col; - } - }; - }, - - DefaultColors: () => { - this.ctrl.dflt_colors = true; - }, - - SetMaxVisNodes: limit => { - if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = parseInt(limit) || 0; - }, - - SetVisLevel: limit => { - if (!this.ctrl.vislevel) - this.ctrl.vislevel = parseInt(limit) || 0; - } - }; + GetVolume: name => { + const regexp = new RegExp('^' + name + '$'), + currnode = this.findNodeWithVolume(regexp, arg => arg); + + if (!currnode) + console.log(`Did not found ${name} volume`); + + // return proxy object with several methods, typically used in ROOT geom scripts + return { + found: currnode, + fVolume: currnode?.node?.fVolume, + InvisibleAll(flag) { + setInvisibleAll(this.fVolume, flag); + }, + Draw() { + if (!this.found || !this.fVolume) + return; + result.obj = this.found.node; + result.prefix = this.found.item; + console.log(`Select volume for drawing ${this.fVolume.fName} ${result.prefix}`); + }, + SetTransparency(lvl) { + if (this.fVolume?.fMedium?.fMaterial) + this.fVolume.fMedium.fMaterial.fFillStyle = 3000 + lvl; + }, + SetLineColor(col) { + if (this.fVolume) + this.fVolume.fLineColor = col; + } + }; + }, + + DefaultColors: () => { + this.ctrl.dflt_colors = true; + }, + + SetMaxVisNodes: limit => { + if (!this.ctrl.maxnodes) + this.ctrl.maxnodes = parseInt(limit) || 0; + }, + + SetVisLevel: limit => { + if (!this.ctrl.vislevel) + this.ctrl.vislevel = parseInt(limit) || 0; + } + }; showProgress(`Loading macro ${script_name}`); @@ -3856,9 +4154,11 @@ class TGeoPainter extends ObjectPainter { while (indx < lines.length) { let line = lines[indx++].trim(); - if (line.indexOf('//') === 0) continue; + if (line.indexOf('//') === 0) + continue; - if (line.indexOf('gGeoManager') < 0) continue; + if (line.indexOf('gGeoManager') < 0) + continue; line = line.replace('->GetVolume', '.GetVolume'); line = line.replace('->InvisibleAll', '.InvisibleAll'); line = line.replace('->SetMaxVisNodes', '.SetMaxVisNodes'); @@ -3867,12 +4167,13 @@ class TGeoPainter extends ObjectPainter { line = line.replace('->SetTransparency', '.SetTransparency'); line = line.replace('->SetLineColor', '.SetLineColor'); line = line.replace('->SetVisLevel', '.SetVisLevel'); - if (line.indexOf('->') >= 0) continue; + if (line.indexOf('->') >= 0) + continue; try { const func = new Function('gGeoManager', line); func(mgr); - } catch (err) { + } catch { console.error(`Problem by processing ${line}`); } } @@ -3884,44 +4185,34 @@ class TGeoPainter extends ObjectPainter { }); } - /** @summary Assign clones, created outside. - * @desc Used by geometry painter, where clones are handled by the server */ - assignClones(clones) { - this._clones_owner = true; - this._clones = clones; - } - - /** @summary Extract shapes from draw message of geometry painter - * @desc For the moment used in batch production */ + /** @summary Extract shapes from draw message of geometry painter + * @desc For the moment used in batch production */ extractRawShapes(draw_msg, recreate) { let nodes = null, old_gradpersegm = 0; // array for descriptors for each node // if array too large (>1M), use JS object while only ~1K nodes are expected to be used - if (recreate) { - // if (draw_msg.kind !== 'draw') return false; + if (recreate) nodes = (draw_msg.numnodes > 1e6) ? { length: draw_msg.numnodes } : new Array(draw_msg.numnodes); // array for all nodes - } draw_msg.nodes.forEach(node => { node = ClonedNodes.formatServerElement(node); if (nodes) nodes[node.id] = node; else - this._clones.updateNode(node); + this.#clones.updateNode(node); }); if (recreate) { - this._clones_owner = true; - this._clones = new ClonedNodes(null, nodes); - this._clones.name_prefix = this._clones.getNodeName(0); - this._clones.setConfig(this.ctrl); + this.assignClones(new ClonedNodes(null, nodes), true); + this.#clones.name_prefix = this.#clones.getNodeName(0); + this.#clones.setConfig(this.ctrl); // normally only need when making selection, not used in geo viewer - // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo_clones.setVisLevel(draw_msg.vislevel); + // this.geo#clones.setMaxVisNodes(draw_msg.maxvisnodes); + // this.geo#clones.setVisLevel(draw_msg.vislevel); // TODO: provide from server - this._clones.maxdepth = 20; + this.#clones.maxdepth = 20; } let nsegm = 0; @@ -3951,57 +4242,53 @@ class TGeoPainter extends ObjectPainter { * @desc Return value used as promise for painter */ async prepareObjectDraw(draw_obj, name_prefix) { // if did cleanup - ignore all kind of activity - if (this.did_cleanup) + if (this.#did_cleanup) return null; if (name_prefix === '__geom_viewer_append__') { - this._new_append_nodes = draw_obj; + this.#new_append_nodes = draw_obj; this.ctrl.use_worker = 0; - this._geom_viewer = true; // indicate that working with geom viewer - } else if ((name_prefix === '__geom_viewer_selection__') && this._clones) { + this.setGeomViewer(true); // indicate that working with geom viewer + } else if ((name_prefix === '__geom_viewer_selection__') && this.#clones) { // these are selection done from geom viewer - this._new_draw_nodes = draw_obj; + this.#new_draw_nodes = draw_obj; this.ctrl.use_worker = 0; - this._geom_viewer = true; // indicate that working with geom viewer - } else if (this._main_painter) { - this._clones_owner = false; - this._clones = this._main_painter._clones; - console.log(`Reuse clones ${this._clones.nodes.length} from main painter`); - } else if (!draw_obj) { - this._clones_owner = false; - this._clones = null; - } else { - this._start_drawing_time = new Date().getTime(); - this._clones_owner = true; - this._clones = new ClonedNodes(draw_obj); + this.setGeomViewer(true); // indicate that working with geom viewer + } else if (this.getCentral()) + this.assignClones(this.getCentral().getClones(), false); + else if (!draw_obj) + this.assignClones(undefined, undefined); + else { + this.#start_drawing_time = new Date().getTime(); + this.assignClones(new ClonedNodes(draw_obj), true); let lvl = this.ctrl.vislevel, maxnodes = this.ctrl.maxnodes; - if (this.geo_manager) { - if (!lvl && this.geo_manager.fVisLevel) - lvl = this.geo_manager.fVisLevel; + if (this.#geo_manager) { + if (!lvl && this.#geo_manager.fVisLevel) + lvl = this.#geo_manager.fVisLevel; if (!maxnodes) - maxnodes = this.geo_manager.fMaxVisNodes; + maxnodes = this.#geo_manager.fMaxVisNodes; } - this._clones.setVisLevel(lvl); - this._clones.setMaxVisNodes(maxnodes, this.ctrl.more); - this._clones.setConfig(this.ctrl); + this.#clones.setVisLevel(lvl); + this.#clones.setMaxVisNodes(maxnodes, this.ctrl.more); + this.#clones.setConfig(this.ctrl); - this._clones.name_prefix = name_prefix; + this.#clones.name_prefix = name_prefix; - const hide_top_volume = !!this.geo_manager && !this.ctrl.showtop; - let uniquevis = this.ctrl.no_screen ? 0 : this._clones.markVisibles(true, false, hide_top_volume); + const hide_top_volume = Boolean(this.#geo_manager) && !this.ctrl.showtop; + let uniquevis = this.ctrl.no_screen ? 0 : this.#clones.markVisibles(true, false, hide_top_volume); if (uniquevis <= 0) - uniquevis = this._clones.markVisibles(false, false, hide_top_volume); + uniquevis = this.#clones.markVisibles(false, false, hide_top_volume); else - uniquevis = this._clones.markVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits + uniquevis = this.#clones.markVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits - this._clones.produceIdShifts(); + this.#clones.produceIdShifts(); - const spent = new Date().getTime() - this._start_drawing_time; + const spent = new Date().getTime() - this.#start_drawing_time; - if (!this._scene) - console.log(`Creating clones ${this._clones.nodes.length} takes ${spent} ms uniquevis ${uniquevis}`); + if (!this.#scene && (settings.Debug || spent > 1000)) + console.log(`Creating clones ${this.#clones.nodes.length} takes ${spent} ms uniquevis ${uniquevis}`); if (this.ctrl._count) return this.drawCount(uniquevis, spent); @@ -4009,14 +4296,14 @@ class TGeoPainter extends ObjectPainter { let promise = Promise.resolve(true); - if (!this._scene) { - this._first_drawing = true; + if (!this.#scene) { + this.#first_drawing = true; const pp = this.getPadPainter(); - this._on_pad = !!pp; + this.#on_pad = Boolean(pp); - if (this._on_pad) { + if (this.#on_pad) { let size, render3d, fp; promise = ensureTCanvas(this, '3d').then(() => { if (pp.fillatt?.color) @@ -4031,7 +4318,7 @@ class TGeoPainter extends ObjectPainter { size = fp.getSizeFor3d(undefined, render3d); - this._fit_main_area = (size.can3d === -1); + this.#fit_main_area = (size.can3d === -1); return this.createScene(size.width, size.height, render3d) .then(dom => fp.add3dCanvas(size, dom, render3d === constants.Render3D.WebGL)); @@ -4042,7 +4329,7 @@ class TGeoPainter extends ObjectPainter { this.batch_mode = isBatchMode() || (!dom.empty() && dom.property('_batch_mode')); this.batch_format = dom.property('_batch_format'); - const render3d = getRender3DKind(this.options.Render3D, this.batch_mode); + const render3d = getRender3DKind(this.getOptions().Render3D, this.batch_mode); // activate worker if ((this.ctrl.use_worker > 0) && !this.batch_mode) @@ -4052,16 +4339,16 @@ class TGeoPainter extends ObjectPainter { const size = this.getSizeFor3d(undefined, render3d); - this._fit_main_area = (size.can3d === -1); + this.#fit_main_area = (size.can3d === -1); promise = this.createScene(size.width, size.height, render3d) - .then(dom => this.add3dCanvas(size, dom, this._webgl)); + .then(dom2 => this.add3dCanvas(size, dom2, this.#webgl)); } } return promise.then(() => { // this is limit for the visible faces, number of volumes does not matter - if (this._first_drawing && !this.ctrl.maxfaces) + if (this.#first_drawing && !this.ctrl.maxfaces) this.ctrl.maxfaces = 200000 * this.ctrl.more; // set top painter only when first child exists @@ -4070,11 +4357,11 @@ class TGeoPainter extends ObjectPainter { this.createToolbar(); // just draw extras and complete drawing if there are no main model - if (!this._clones) + if (!this.#clones) return this.completeDraw(); return new Promise(resolveFunc => { - this._resolveFunc = resolveFunc; + this.#draw_resolveFuncs.push(resolveFunc); this.showDrawInfo('Drawing geometry'); this.startDrawGeometry(true); }); @@ -4083,70 +4370,76 @@ class TGeoPainter extends ObjectPainter { /** @summary methods show info when first geometry drawing is performed */ showDrawInfo(msg) { - if (this.isBatchMode() || !this._first_drawing || !this._start_drawing_time) return; + if (this.isBatchMode() || !this.#first_drawing || !this.#start_drawing_time) + return; - const main = this._renderer.domElement.parentNode; - if (!main) return; + const main = this.#renderer.domElement.parentNode; + if (!main) + return; let info = main.querySelector('.geo_info'); if (!msg) info?.remove(); - else { - const spent = (new Date().getTime() - this._start_drawing_time)*1e-3; + else { + const spent = (new Date().getTime() - this.#start_drawing_time) * 1e-3; if (!info) { info = getDocument().createElement('p'); info.setAttribute('class', 'geo_info'); info.setAttribute('style', 'position: absolute; text-align: center; vertical-align: middle; top: 45%; left: 40%; color: red; font-size: 150%;'); main.append(info); } - info.innerHTML = `${msg}, ${spent.toFixed(1)}s`; + info.innerText = `${msg}, ${spent.toFixed(1)}s`; } } /** @summary Reentrant method to perform geometry drawing step by step */ continueDraw() { // nothing to do - exit - if (this.isStage(stageInit)) return; + if (this.isStage(stageInit)) + return; const tm0 = new Date().getTime(), - interval = this._first_drawing ? 1000 : 200; + interval = this.#first_drawing ? 1000 : 200; let now = tm0; while (true) { const res = this.nextDrawAction(); - if (!res) break; + if (!res) + break; now = new Date().getTime(); // stop creation after 100 sec, render as is - if (now - this._startm > 1e5) { + if (now - this.#start_render_tm > 1e5) { this.changeStage(stageInit, 'Abort build after 100s'); break; } // if we are that fast, do next action - if ((res === true) && (now - tm0 < interval)) continue; + if ((res === true) && (now - tm0 < interval)) + continue; if ((now - tm0 > interval) || (res === 1) || (res === 2)) { - showProgress(this.drawing_log); + showProgress(this.#drawing_log); - this.showDrawInfo(this.drawing_log); + this.showDrawInfo(this.#drawing_log); - if (this._first_drawing && this._webgl && (this._num_meshes - this._last_render_meshes > 100) && (now - this._last_render_tm > 2.5*interval)) { + if (this.#first_drawing && this.#webgl && (this.ctrl.info.num_meshes - this.#last_render_meshes > 100) && (now - this.#last_render_tm > 2.5 * interval)) { this.adjustCameraPosition(); this.render3D(-1); - this._last_render_meshes = this.ctrl.info.num_meshes; + this.#last_render_meshes = this.ctrl.info.num_meshes; } - if (res !== 2) setTimeout(() => this.continueDraw(), (res === 1) ? 100 : 1); + if (res !== 2) + setTimeout(() => this.continueDraw(), (res === 1) ? 100 : 1); return; } } - const take_time = now - this._startm; + const take_time = now - this.#start_render_tm; - if (this._first_drawing || this._full_redrawing) + if ((this.#first_drawing || this.#full_redrawing) && (settings.Debug || take_time > 1000)) console.log(`Create tm = ${take_time} meshes ${this.ctrl.info.num_meshes} faces ${this.ctrl.info.num_faces}`); if (take_time > 300) { @@ -4161,21 +4454,22 @@ class TGeoPainter extends ObjectPainter { /** @summary Checks camera position and recalculate rendering order if needed * @param force - if specified, forces calculations of render order */ testCameraPosition(force) { - this._camera.updateMatrixWorld(); + this.#camera.updateMatrixWorld(); this.drawOverlay(); - const origin = this._camera.position.clone(); - if (!force && this._last_camera_position) { + const origin = this.#camera.position.clone(); + if (!force && this.#last_camera_position) { // if camera position does not changed a lot, ignore such change - const dist = this._last_camera_position.distanceTo(origin); - if (dist < (this._overall_size || 1000)*1e-4) return; + const dist = this.#last_camera_position.distanceTo(origin); + if (dist < (this.#overall_size || 1000) * 1e-4) + return; } - this._last_camera_position = origin; // remember current camera position + this.#last_camera_position = origin; // remember current camera position if (this.ctrl._axis) { - const vect = (this._controls?.target || this._lookat).clone().sub(this._camera.position).normalize(); + const vect = (this.#controls?.target || this.#lookat).clone().sub(this.#camera.position).normalize(); this.getExtrasContainer('get', 'axis')?.traverse(obj3d => { if (isFunc(obj3d._axis_flip)) obj3d._axis_flip(vect); @@ -4183,7 +4477,7 @@ class TGeoPainter extends ObjectPainter { } if (!this.ctrl.project) - produceRenderOrder(this._toplevel, origin, this.ctrl.depthMethod, this._clones); + produceRenderOrder(this.#toplevel, origin, this.ctrl.depthMethod, this.#clones); } /** @summary Call 3D rendering of the geometry @@ -4191,177 +4485,184 @@ class TGeoPainter extends ObjectPainter { * @param [measure] - when true, for the first time printout rendering time * @return {Promise} when tmout bigger than 0 is specified * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings - * superimposed with each other. If tmeout <= 0, rendering performed immediately + * superimposed with each other. If tmout <= 0, rendering performed immediately * Several special values are used: * -1 - force recheck of rendering order based on camera position */ render3D(tmout, measure) { - if (!this._renderer) { - if (!this.did_cleanup) - console.warn('renderer object not exists - check code'); - else + if (!this.#renderer) { + if (this.#did_cleanup) console.warn('try to render after cleanup'); + else + console.warn('renderer object not exists - check code'); return this; } const ret_promise = (tmout !== undefined) && (tmout > 0) && (measure !== 'nopromise'); - if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout + if (tmout === undefined) + tmout = 5; // by default, rendering happens with timeout - if ((tmout > 0) && this._webgl) { - if (this.isBatchMode()) tmout = 1; // use minimal timeout in batch mode + if ((tmout > 0) && this.#webgl) { + if (this.isBatchMode()) + tmout = 1; // use minimal timeout in batch mode if (ret_promise) { return new Promise(resolveFunc => { - if (!this._render_resolveFuncs) - this._render_resolveFuncs = []; - this._render_resolveFuncs.push(resolveFunc); - if (!this.render_tmout) - this.render_tmout = setTimeout(() => this.render3D(0), tmout); + this.#render_resolveFuncs.push(resolveFunc); + if (!this.#render_tmout) + this.#render_tmout = setTimeout(() => this.render3D(0), tmout); }); } - if (!this.render_tmout) - this.render_tmout = setTimeout(() => this.render3D(0), tmout); + if (!this.#render_tmout) + this.#render_tmout = setTimeout(() => this.render3D(0), tmout); return this; } - if (this.render_tmout) { - clearTimeout(this.render_tmout); - delete this.render_tmout; + if (this.#render_tmout) { + clearTimeout(this.#render_tmout); + this.#render_tmout = undefined; } - beforeRender3D(this._renderer); + beforeRender3D(this.#renderer); const tm1 = new Date(); - if (this._adjust_camera_with_render) { + if (this.#adjust_camera_with_render) { this.adjustCameraPosition('only_set'); - delete this._adjust_camera_with_render; + this.#adjust_camera_with_render = undefined; } this.testCameraPosition(tmout === -1); // its needed for outlinePass - do rendering, most consuming time - if (this._webgl && this._effectComposer && (this._effectComposer.passes.length > 0)) - this._effectComposer.render(); - else if (this._webgl && this._bloomComposer && (this._bloomComposer.passes.length > 0)) { - this._renderer.clear(); - this._camera.layers.set(_BLOOM_SCENE); - this._bloomComposer.render(); - this._renderer.clearDepth(); - this._camera.layers.set(_ENTIRE_SCENE); - this._renderer.render(this._scene, this._camera); + if (this.#webgl && this.#effectComposer?.passes) + this.#effectComposer.render(); + else if (this.#webgl && this.#bloomComposer?.passes) { + this.#renderer.clear(); + this.#camera.layers.set(_BLOOM_SCENE); + this.#bloomComposer.render(); + this.#renderer.clearDepth(); + this.#camera.layers.set(_ENTIRE_SCENE); + this.#renderer.render(this.#scene, this.#camera); } else - this._renderer.render(this._scene, this._camera); - + this.#renderer.render(this.#scene, this.#camera); const tm2 = new Date(); - this.last_render_tm = tm2.getTime(); + this.#last_render_tm = tm2.getTime(); - if ((this.first_render_tm === 0) && (measure === true)) { - this.first_render_tm = tm2.getTime() - tm1.getTime(); - if (this.first_render_tm > 500) - console.log(`three.js r${REVISION}, first render tm = ${this.first_render_tm}`); + if ((this.#first_render_tm === 0) && (measure === true)) { + this.#first_render_tm = tm2.getTime() - tm1.getTime(); + if (this.#first_render_tm > 500) + console.log(`three.js r${THREE.REVISION}, first render tm = ${this.#first_render_tm}`); } - afterRender3D(this._renderer); + afterRender3D(this.#renderer); - if (this._render_resolveFuncs) { - const arr = this._render_resolveFuncs; - delete this._render_resolveFuncs; - arr.forEach(func => func(this)); - } + const arr = this.#render_resolveFuncs; + this.#render_resolveFuncs = []; + arr?.forEach(func => func(this)); } /** @summary Start geo worker */ - startWorker() { - if (this._worker) return; - - this._worker_ready = false; - this._worker_jobs = 0; // counter how many requests send to worker - - // TODO: modules not yet working, see https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/ - this._worker = new Worker(source_dir + 'scripts/geoworker.js' /*, { type: 'module' } */); - - this._worker.onmessage = e => { - if (!isObject(e.data)) return; - - if ('log' in e.data) - return console.log(`geo: ${e.data.log}`); - - if ('progress' in e.data) - return showProgress(e.data.progress); + async startWorker() { + if (this.#worker) + return; - e.data.tm3 = new Date().getTime(); + this.#worker_ready = false; + this.#worker_jobs = 0; // counter how many requests send to worker - if ('init' in e.data) { - this._worker_ready = true; - console.log(`Worker ready: ${e.data.tm3 - e.data.tm0}`); - } else - this.processWorkerReply(e.data); - }; + let pr; - // send initialization message with clones - this._worker.postMessage({ - init: true, // indicate init command for worker - browser, - tm0: new Date().getTime(), - vislevel: this._clones.getVisLevel(), - maxvisnodes: this._clones.getMaxVisNodes(), - clones: this._clones.nodes, - sortmap: this._clones.sortmap + if (isNodeJs()) { + pr = import('node:worker_threads').then(h => { + const wrk = new h.Worker(source_dir.slice(7) + 'modules/geom/nodeworker.mjs', { type: 'module' }); + wrk.on('message', msg => this.processWorkerReply(msg)); + return wrk; + }); + } else { + // Finally use ES6 module, see https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/ + const wrk = new Worker(source_dir + 'modules/geom/geoworker.mjs', { type: 'module' }); + wrk.onmessage = e => this.processWorkerReply(e?.data); + pr = Promise.resolve(wrk); + } + + return pr.then(wrk => { + this.#worker = wrk; + // send initialization message with clones + wrk.postMessage({ + init: true, // indicate init command for worker + tm0: new Date().getTime(), + vislevel: this.#clones.getVisLevel(), + maxvisnodes: this.#clones.getMaxVisNodes(), + clones: this.#clones.nodes, + sortmap: this.#clones.sortmap + }); }); } /** @summary check if one can submit request to worker * @private */ canSubmitToWorker(force) { - if (!this._worker) return false; - - return this._worker_ready && ((this._worker_jobs === 0) || force); + return this.#worker ? this.#worker_ready && ((this.#worker_jobs === 0) || force) : false; } /** @summary submit request to worker * @private */ submitToWorker(job) { - if (!this._worker) return false; + if (!this.#worker) + return false; - this._worker_jobs++; + this.#worker_jobs++; job.tm0 = new Date().getTime(); - this._worker.postMessage(job); + this.#worker.postMessage(job); } /** @summary process reply from worker * @private */ processWorkerReply(job) { - this._worker_jobs--; + if (!job || !isObject(job)) + return; + + if (job.log) + return console.log(`geo: ${job.log}`); + + if (job.progress) + return showProgress(job.progress); + + job.tm3 = new Date().getTime(); + + if (job.init) { + this.#worker_ready = true; + return; + } + + this.#worker_jobs--; if ('collect' in job) { - this._new_draw_nodes = job.new_nodes; - this._draw_all_nodes = job.complete; + this.#new_draw_nodes = job.new_nodes; + this.#draw_all_nodes = job.complete; this.changeStage(stageAnalyze); // invoke methods immediately return this.continueDraw(); } if ('shapes' in job) { - for (let n=0; n<job.shapes.length; ++n) { + for (let n = 0; n < job.shapes.length; ++n) { const item = job.shapes[n], - origin = this._build_shapes[n]; - - // let shape = this._clones.getNodeShape(item.nodeid); + origin = this.#build_shapes[n]; if (item.buf_pos && item.buf_norm) { - if (item.buf_pos.length === 0) + if (!item.buf_pos.length) origin.geom = null; - else if (item.buf_pos.length !== item.buf_norm.length) { + else if (item.buf_pos.length !== item.buf_norm.length) { console.error(`item.buf_pos.length ${item.buf_pos.length} !== item.buf_norm.length ${item.buf_norm.length}`); origin.geom = null; } else { - origin.geom = new BufferGeometry(); + origin.geom = new THREE.BufferGeometry(); - origin.geom.setAttribute('position', new BufferAttribute(item.buf_pos, 3)); - origin.geom.setAttribute('normal', new BufferAttribute(item.buf_norm, 3)); + origin.geom.setAttribute('position', new THREE.BufferAttribute(item.buf_pos, 3)); + origin.geom.setAttribute('normal', new THREE.BufferAttribute(item.buf_norm, 3)); } origin.ready = true; @@ -4378,27 +4679,26 @@ class TGeoPainter extends ObjectPainter { } } - /** @summary start draw geometries on master and all slaves + /** @summary start draw geometries on central and all subordinate painters * @private */ testGeomChanges() { - if (this._main_painter) { - console.warn('Get testGeomChanges call for slave painter'); - return this._main_painter.testGeomChanges(); + if (this.getCentral()) { + console.warn('Get testGeomChanges call for subordinate painter'); + return this.getCentral().testGeomChanges(); } this.startDrawGeometry(); - for (let k = 0; k < this._slave_painters.length; ++k) - this._slave_painters[k].startDrawGeometry(); + this.getSubordinates()?.forEach(p => p.startDrawGeometry()); } /** @summary Draw axes and camera overlay */ drawAxesAndOverlay(norender) { const res1 = this.drawAxes(), - res2 = this.drawOverlay(); + res2 = this.drawOverlay(); if (!res1 && !res2) return norender ? null : this.render3D(); - else - return this.changedDepthMethod(norender ? 'norender' : undefined); + + return this.changedDepthMethod(norender ? 'norender' : undefined); } /** @summary Draw overlay for the orthographic cameras */ @@ -4407,49 +4707,49 @@ class TGeoPainter extends ObjectPainter { if (!this.isOrthoCamera() || (this.ctrl.camera_overlay === 'none')) return false; - const zoom = 0.5 / this._camera.zoom, - midx = (this._camera.left + this._camera.right) / 2, - midy = (this._camera.bottom + this._camera.top) / 2, - xmin = midx - (this._camera.right - this._camera.left) * zoom, - xmax = midx + (this._camera.right - this._camera.left) * zoom, - ymin = midy - (this._camera.top - this._camera.bottom) * zoom, - ymax = midy + (this._camera.top - this._camera.bottom) * zoom, - tick_size = (ymax - ymin) * 0.02, - text_size = (ymax - ymin) * 0.015, - grid_gap = (ymax - ymin) * 0.001, - x1 = xmin + text_size * 5, x2 = xmax - text_size * 5, - y1 = ymin + text_size * 3, y2 = ymax - text_size * 3, - x_handle = new TAxisPainter(null, create(clTAxis)); + const zoom = 0.5 / this.#camera.zoom, + midx = (this.#camera.left + this.#camera.right) / 2, + midy = (this.#camera.bottom + this.#camera.top) / 2, + xmin = midx - (this.#camera.right - this.#camera.left) * zoom, + xmax = midx + (this.#camera.right - this.#camera.left) * zoom, + ymin = midy - (this.#camera.top - this.#camera.bottom) * zoom, + ymax = midy + (this.#camera.top - this.#camera.bottom) * zoom, + tick_size = (ymax - ymin) * 0.02, + text_size = (ymax - ymin) * 0.015, + grid_gap = (ymax - ymin) * 0.001, + x1 = xmin + text_size * 5, x2 = xmax - text_size * 5, + y1 = ymin + text_size * 3, y2 = ymax - text_size * 3, + x_handle = new TAxisPainter(null, create(clTAxis)); x_handle.configureAxis('xaxis', x1, x2, x1, x2, false, [x1, x2], { log: 0, reverse: false }); const y_handle = new TAxisPainter(null, create(clTAxis)); y_handle.configureAxis('yaxis', y1, y2, y1, y2, false, [y1, y2], - { log: 0, reverse: false }); + { log: 0, reverse: false }); - const ii = this._camera.orthoIndicies ?? [0, 1, 2]; + const ii = this.#camera.orthoIndicies ?? [0, 1, 2]; let buf, pos, midZ = 0, gridZ = 0; - if (this._camera.orthoZ) - gridZ = midZ = this._camera.orthoZ[0]; + if (this.#camera.orthoZ) + gridZ = midZ = this.#camera.orthoZ[0]; const addPoint = (x, y, z) => { - buf[pos+ii[0]] = x; - buf[pos+ii[1]] = y; - buf[pos+ii[2]] = z ?? gridZ; + buf[pos + ii[0]] = x; + buf[pos + ii[1]] = y; + buf[pos + ii[2]] = z ?? gridZ; pos += 3; }, createText = (lbl, size) => { - const text3d = new TextGeometry(lbl, { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); + const text3d = createTextGeometry(lbl, size); text3d.computeBoundingBox(); text3d._width = text3d.boundingBox.max.x - text3d.boundingBox.min.x; text3d._height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - text3d.translate(-text3d._width/2, -text3d._height/2, 0); - if (this._camera.orthoSign < 0) + text3d.translate(-text3d._width / 2, -text3d._height / 2, 0); + if (this.#camera.orthoSign < 0) text3d.rotateY(Math.PI); - if (isFunc(this._camera.orthoRotation)) - this._camera.orthoRotation(text3d); + if (isFunc(this.#camera.orthoRotation)) + this.#camera.orthoRotation(text3d); return text3d; }, createTextMesh = (geom, material, x, y, z) => { @@ -4457,7 +4757,7 @@ class TGeoPainter extends ObjectPainter { tgt[ii[0]] = x; tgt[ii[1]] = y; tgt[ii[2]] = z ?? gridZ; - const mesh = new Mesh(geom, material); + const mesh = new THREE.Mesh(geom, material); mesh.translateX(tgt[0]).translateY(tgt[1]).translateZ(tgt[2]); return mesh; }; @@ -4465,81 +4765,85 @@ class TGeoPainter extends ObjectPainter { if (this.ctrl.camera_overlay === 'bar') { const container = this.getExtrasContainer('create', 'overlay'); - let x1 = xmin * 0.15 + xmax * 0.85, - x2 = xmin * 0.05 + xmax * 0.95; - const y1 = ymax * 0.9 + ymin * 0.1, - y2 = ymax * 0.86 + ymin * 0.14, + let ox1 = xmin * 0.15 + xmax * 0.85, + ox2 = xmin * 0.05 + xmax * 0.95; + const oy1 = ymax * 0.9 + ymin * 0.1, + oy2 = ymax * 0.86 + ymin * 0.14, ticks = x_handle.createTicks(); if (ticks.major?.length > 1) { - x1 = ticks.major[ticks.major.length-2]; - x2 = ticks.major[ticks.major.length-1]; + ox1 = ticks.major.at(-2); + ox2 = ticks.major.at(-1); } - buf = new Float32Array(3*6); pos = 0; + buf = new Float32Array(3 * 6); + pos = 0; - addPoint(x1, y1, midZ); - addPoint(x1, y2, midZ); + addPoint(ox1, oy1, midZ); + addPoint(ox1, oy2, midZ); - addPoint(x1, (y1 + y2) / 2, midZ); - addPoint(x2, (y1 + y2) / 2, midZ); + addPoint(ox1, (oy1 + oy2) / 2, midZ); + addPoint(ox2, (oy1 + oy2) / 2, midZ); - addPoint(x2, y1, midZ); - addPoint(x2, y2, midZ); + addPoint(ox2, oy1, midZ); + addPoint(ox2, oy2, midZ); - const lineMaterial = new LineBasicMaterial({ color: 'green' }), - textMaterial = new MeshBasicMaterial({ color: 'green', vertexColors: false }); + const lineMaterial = new THREE.LineBasicMaterial({ color: 'green' }), + textMaterial = new THREE.MeshBasicMaterial({ color: 'green', vertexColors: false }); container.add(createLineSegments(buf, lineMaterial)); - const text3d = createText(x_handle.format(x2-x1, true), Math.abs(y2-y1)); + const text3d = createText(x_handle.format(ox2 - ox1, true), Math.abs(oy2 - oy1)); - container.add(createTextMesh(text3d, textMaterial, (x2 + x1) / 2, (y1 + y2) / 2 + text3d._height * 0.8, midZ)); + container.add(createTextMesh(text3d, textMaterial, (ox2 + ox1) / 2, (oy1 + oy2) / 2 + text3d._height * 0.8, midZ)); return true; } const show_grid = this.ctrl.camera_overlay.indexOf('grid') === 0; - if (show_grid && this._camera.orthoZ) { + if (show_grid && this.#camera.orthoZ) { if (this.ctrl.camera_overlay === 'gridf') - gridZ += this._camera.orthoSign * this._camera.orthoZ[1]; + gridZ += this.#camera.orthoSign * this.#camera.orthoZ[1]; else if (this.ctrl.camera_overlay === 'gridb') - gridZ -= this._camera.orthoSign * this._camera.orthoZ[1]; + gridZ -= this.#camera.orthoSign * this.#camera.orthoZ[1]; } if ((this.ctrl.camera_overlay === 'axis') || show_grid) { const container = this.getExtrasContainer('create', 'overlay'), - lineMaterial = new LineBasicMaterial({ color: new Color('black') }), - gridMaterial1 = show_grid ? new LineBasicMaterial({ color: new Color(0xbbbbbb) }) : null, - gridMaterial2 = show_grid ? new LineDashedMaterial({ color: new Color(0xdddddd), dashSize: grid_gap, gapSize: grid_gap }) : null, - textMaterial = new MeshBasicMaterial({ color: 'black', vertexColors: false }), + lineMaterial = new THREE.LineBasicMaterial({ color: new THREE.Color('black') }), + gridMaterial1 = show_grid ? new THREE.LineBasicMaterial({ color: new THREE.Color(0xbbbbbb) }) : null, + gridMaterial2 = show_grid ? new THREE.LineDashedMaterial({ color: new THREE.Color(0xdddddd), dashSize: grid_gap, gapSize: grid_gap }) : null, + textMaterial = new THREE.MeshBasicMaterial({ color: 'black', vertexColors: false }), xticks = x_handle.createTicks(); while (xticks.next()) { const x = xticks.tick, k = (xticks.kind === 1) ? 1.0 : 0.6; if (show_grid) { - buf = new Float32Array(2*3); pos = 0; - addPoint(x, ymax - k*tick_size - grid_gap); - addPoint(x, ymin + k*tick_size + grid_gap); + buf = new Float32Array(2 * 3); + pos = 0; + addPoint(x, ymax - k * tick_size - grid_gap); + addPoint(x, ymin + k * tick_size + grid_gap); container.add(createLineSegments(buf, xticks.kind === 1 ? gridMaterial1 : gridMaterial2)); } - buf = new Float32Array(4*3); pos = 0; + buf = new Float32Array(4 * 3); + pos = 0; addPoint(x, ymax); - addPoint(x, ymax - k*tick_size); + addPoint(x, ymax - k * tick_size); addPoint(x, ymin); - addPoint(x, ymin + k*tick_size); + addPoint(x, ymin + k * tick_size); container.add(createLineSegments(buf, lineMaterial)); - if (xticks.kind !== 1) continue; + if (xticks.kind !== 1) + continue; const text3d = createText(x_handle.format(x, true), text_size); - container.add(createTextMesh(text3d, textMaterial, x, ymax - tick_size - text_size/2 - text3d._height/2)); + container.add(createTextMesh(text3d, textMaterial, x, ymax - tick_size - text_size / 2 - text3d._height / 2)); - container.add(createTextMesh(text3d, textMaterial, x, ymin + tick_size + text_size/2 + text3d._height/2)); + container.add(createTextMesh(text3d, textMaterial, x, ymin + tick_size + text_size / 2 + text3d._height / 2)); } const yticks = y_handle.createTicks(); @@ -4548,27 +4852,30 @@ class TGeoPainter extends ObjectPainter { const y = yticks.tick, k = (yticks.kind === 1) ? 1.0 : 0.6; if (show_grid) { - buf = new Float32Array(2*3); pos = 0; - addPoint(xmin + k*tick_size + grid_gap, y); - addPoint(xmax - k*tick_size - grid_gap, y); + buf = new Float32Array(2 * 3); + pos = 0; + addPoint(xmin + k * tick_size + grid_gap, y); + addPoint(xmax - k * tick_size - grid_gap, y); container.add(createLineSegments(buf, yticks.kind === 1 ? gridMaterial1 : gridMaterial2)); } - buf = new Float32Array(4*3); pos = 0; + buf = new Float32Array(4 * 3); + pos = 0; addPoint(xmin, y); - addPoint(xmin + k*tick_size, y); + addPoint(xmin + k * tick_size, y); addPoint(xmax, y); - addPoint(xmax - k*tick_size, y); + addPoint(xmax - k * tick_size, y); container.add(createLineSegments(buf, lineMaterial)); - if (yticks.kind !== 1) continue; + if (yticks.kind !== 1) + continue; const text3d = createText(y_handle.format(y, true), text_size); - container.add(createTextMesh(text3d, textMaterial, xmin + tick_size + text_size/2 + text3d._width/2, y)); + container.add(createTextMesh(text3d, textMaterial, xmin + tick_size + text_size / 2 + text3d._width / 2, y)); - container.add(createTextMesh(text3d, textMaterial, xmax - tick_size - text_size/2 - text3d._width/2, y)); + container.add(createTextMesh(text3d, textMaterial, xmax - tick_size - text_size / 2 - text3d._width / 2, y)); } return true; @@ -4584,46 +4891,51 @@ class TGeoPainter extends ObjectPainter { if (!this.ctrl._axis) return false; - const box = this.getGeomBoundingBox(this._toplevel, this.superimpose ? 'original' : undefined), - container = this.getExtrasContainer('create', 'axis'), - text_size = 0.02 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z), - center = [0, 0, 0], - names = ['x', 'y', 'z'], - labels = ['X', 'Y', 'Z'], - colors = ['red', 'green', 'blue'], - ortho = this.isOrthoCamera(), - ckind = this.ctrl.camera_kind ?? 'perspective'; + const box = this.getGeomBoundingBox(this.#toplevel, this.#superimpose ? 'original' : undefined), + container = this.getExtrasContainer('create', 'axis'), + text_size = 0.02 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z), + center = [0, 0, 0], + names = ['x', 'y', 'z'], + labels = ['X', 'Y', 'Z'], + colors = ['red', 'green', 'blue'], + ortho = this.isOrthoCamera(), + ckind = this.ctrl.camera_kind ?? 'perspective'; if (this.ctrl._axis === 2) { for (let naxis = 0; naxis < 3; ++naxis) { const name = names[naxis]; - if ((box.min[name] <= 0) && (box.max[name] >= 0)) continue; - center[naxis] = (box.min[name] + box.max[name])/2; + if ((box.min[name] <= 0) && (box.max[name] >= 0)) + continue; + center[naxis] = (box.min[name] + box.max[name]) / 2; } } for (let naxis = 0; naxis < 3; ++naxis) { // exclude axis which is not seen - if (ortho && ckind.indexOf(labels[naxis]) < 0) continue; + if (ortho && ckind.indexOf(labels[naxis]) < 0) + continue; const buf = new Float32Array(6), - color = colors[naxis], - name = names[naxis], - - valueToString = val => { - if (!val) return '0'; - const lg = Math.log10(Math.abs(val)); - if (lg < 0) { - if (lg > -1) return val.toFixed(2); - if (lg > -2) return val.toFixed(3); - } else { - if (lg < 2) return val.toFixed(1); - if (lg < 4) return val.toFixed(0); - } - return val.toExponential(2); - }, - - lbl = valueToString(box.max[name]) + ' ' + labels[naxis]; + color = colors[naxis], + name = names[naxis], + valueToString = val => { + if (!val) + return '0'; + const lg = Math.log10(Math.abs(val)); + if (lg < 0) { + if (lg > -1) + return val.toFixed(2); + if (lg > -2) + return val.toFixed(3); + } else { + if (lg < 2) + return val.toFixed(1); + if (lg < 4) + return val.toFixed(0); + } + return val.toExponential(2); + }, + lbl = valueToString(box.max[name]) + ' ' + labels[naxis]; buf[0] = box.min.x; buf[1] = box.min.y; @@ -4634,29 +4946,31 @@ class TGeoPainter extends ObjectPainter { buf[5] = box.min.z; switch (naxis) { - case 0: buf[3] = box.max.x; break; - case 1: buf[4] = box.max.y; break; - case 2: buf[5] = box.max.z; break; + case 0: buf[3] = box.max.x; break; + case 1: buf[4] = box.max.y; break; + case 2: buf[5] = box.max.z; break; } if (this.ctrl._axis === 2) { - for (let k = 0; k < 6; ++k) - if ((k % 3) !== naxis) buf[k] = center[k%3]; + for (let k = 0; k < 6; ++k) { + if ((k % 3) !== naxis) + buf[k] = center[k % 3]; + } } - const lineMaterial = new LineBasicMaterial({ color }); + const lineMaterial = new THREE.LineBasicMaterial({ color }); let mesh = createLineSegments(buf, lineMaterial); mesh._no_clip = true; // skip from clipping container.add(mesh); - const textMaterial = new MeshBasicMaterial({ color, vertexColors: false }); + const textMaterial = new THREE.MeshBasicMaterial({ color, vertexColors: false }); if ((center[naxis] === 0) && (center[naxis] >= box.min[name]) && (center[naxis] <= box.max[name])) { if ((this.ctrl._axis !== 2) || (naxis === 0)) { - const geom = ortho ? new CircleGeometry(text_size*0.25) : new SphereGeometry(text_size*0.25); - mesh = new Mesh(geom, textMaterial); + const geom = ortho ? new THREE.CircleGeometry(text_size * 0.25) : new THREE.SphereGeometry(text_size * 0.25); + mesh = new THREE.Mesh(geom, textMaterial); mesh.translateX(naxis === 0 ? center[0] : buf[0]); mesh.translateY(naxis === 1 ? center[1] : buf[1]); mesh.translateZ(naxis === 2 ? center[2] : buf[2]); @@ -4665,14 +4979,14 @@ class TGeoPainter extends ObjectPainter { } } - let text3d = new TextGeometry(lbl, { font: HelveticerRegularFont, size: text_size, height: 0, curveSegments: 5 }); - mesh = new Mesh(text3d, textMaterial); + let text3d = createTextGeometry(lbl, text_size); + mesh = new THREE.Mesh(text3d, textMaterial); mesh._no_clip = true; // skip from clipping - function setSideRotation(mesh, normal) { - mesh._other_side = false; - mesh._axis_norm = normal ?? new Vector3(1, 0, 0); - mesh._axis_flip = function(vect) { + function setSideRotation(mesh2, normal) { + mesh2._other_side = false; + mesh2._axis_norm = normal ?? new THREE.Vector3(1, 0, 0); + mesh2._axis_flip = function(vect) { const other_side = vect.dot(this._axis_norm) < 0; if (this._other_side !== other_side) { this._other_side = other_side; @@ -4681,10 +4995,10 @@ class TGeoPainter extends ObjectPainter { }; } - function setTopRotation(mesh, first_angle = -1) { - mesh._last_angle = first_angle; - mesh._axis_flip = function(vect) { - let angle = 0; + function setTopRotation(mesh2, first_angle = -1) { + mesh2._last_angle = first_angle; + mesh2._axis_flip = function(vect) { + let angle; switch (this._axis_name) { case 'x': angle = -Math.atan2(vect.y, vect.z); break; case 'y': angle = -Math.atan2(vect.z, vect.x); break; @@ -4692,15 +5006,15 @@ class TGeoPainter extends ObjectPainter { } angle = Math.round(angle / Math.PI * 2 + 2) % 4; if (this._last_angle !== angle) { - this.rotateX((angle - this._last_angle) * Math.PI/2); + this.rotateX((angle - this._last_angle) * Math.PI / 2); this._last_angle = angle; } }; } - let textbox = new Box3().setFromObject(mesh); + let textbox = new THREE.Box3().setFromObject(mesh); - text3d.translate(-textbox.max.x*0.5, -textbox.max.y/2, 0); + text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); mesh.translateX(buf[3]); mesh.translateY(buf[4]); @@ -4711,49 +5025,50 @@ class TGeoPainter extends ObjectPainter { if (naxis === 0) { if (ortho && ckind.indexOf('OX') > 0) setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new Vector3(0, 1, 0)); - mesh.rotateX(Math.PI/2); + else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) + setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); + else { + setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); + mesh.rotateX(Math.PI / 2); } - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); } else if (naxis === 1) { if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); + mesh.rotateX(-Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); } else { setSideRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); + mesh.rotateX(Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); } } else if (naxis === 2) { if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI/2); - if (zox) mesh.rotateX(-Math.PI/2); + setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); + mesh.rotateY(-Math.PI / 2); + if (zox) + mesh.rotateX(-Math.PI / 2); } else { setTopRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateZ(Math.PI/2); + mesh.rotateX(Math.PI / 2); + mesh.rotateZ(Math.PI / 2); } - mesh.translateX(text_size*0.5 + textbox.max.x*0.5); + mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); } container.add(mesh); - text3d = new TextGeometry(valueToString(box.min[name]), { font: HelveticerRegularFont, size: text_size, height: 0, curveSegments: 5 }); + text3d = createTextGeometry(valueToString(box.min[name]), text_size); - mesh = new Mesh(text3d, textMaterial); + mesh = new THREE.Mesh(text3d, textMaterial); mesh._no_clip = true; // skip from clipping - textbox = new Box3().setFromObject(mesh); + textbox = new THREE.Box3().setFromObject(mesh); - text3d.translate(-textbox.max.x*0.5, -textbox.max.y/2, 0); + text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); mesh._axis_name = name; @@ -4764,37 +5079,38 @@ class TGeoPainter extends ObjectPainter { if (naxis === 0) { if (ortho && ckind.indexOf('OX') > 0) setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new Vector3(0, 1, 0)); - mesh.rotateX(Math.PI/2); + else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) + setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); + else { + setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); + mesh.rotateX(Math.PI / 2); } - mesh.translateX(-text_size*0.5 - textbox.max.x*0.5); + mesh.translateX(-text_size * 0.5 - textbox.max.x * 0.5); } else if (naxis === 1) { if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); + mesh.rotateX(-Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); } else { setSideRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateY(-Math.PI/2); - mesh.translateX(textbox.max.x*0.5 + text_size*0.5); + mesh.rotateX(Math.PI / 2); + mesh.rotateY(-Math.PI / 2); + mesh.translateX(textbox.max.x * 0.5 + text_size * 0.5); } } else if (naxis === 2) { if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI/2); - if (zox) mesh.rotateX(-Math.PI/2); + setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); + mesh.rotateY(-Math.PI / 2); + if (zox) + mesh.rotateX(-Math.PI / 2); } else { setTopRotation(mesh); - mesh.rotateX(Math.PI/2); - mesh.rotateZ(Math.PI/2); + mesh.rotateX(Math.PI / 2); + mesh.rotateZ(Math.PI / 2); } - mesh.translateX(-textbox.max.x*0.5 - text_size*0.5); + mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); } container.add(mesh); @@ -4815,8 +5131,10 @@ class TGeoPainter extends ObjectPainter { /** @summary Set auto rotate mode */ setAutoRotate(on) { - if (this.ctrl.project) return; - if (on !== undefined) this.ctrl.rotate = on; + if (this.ctrl.project) + return; + if (on !== undefined) + this.ctrl.rotate = on; this.autorotate(2.5); } @@ -4828,14 +5146,14 @@ class TGeoPainter extends ObjectPainter { /** @summary Specify wireframe mode */ setWireFrame(on) { - this.ctrl.wireframe = !!on; + this.ctrl.wireframe = Boolean(on); this.changedWireFrame(); } /** @summary Specify showtop draw options, relevant only for TGeoManager */ setShowTop(on) { - this.ctrl.showtop = !!on; - this.redrawObject('same'); + this.ctrl.showtop = Boolean(on); + return this.startRedraw(); } /** @summary Should be called when configuration of particular axis is changed */ @@ -4846,10 +5164,11 @@ class TGeoPainter extends ObjectPainter { /** @summary Should be called when depth test flag is changed */ changedDepthTest() { - if (!this._toplevel) return; + if (!this.#toplevel) + return; const flag = this.ctrl.depthTest; - this._toplevel.traverse(node => { - if (node instanceof Mesh) + this.#toplevel.traverse(node => { + if (node instanceof THREE.Mesh) node.material.depthTest = flag; }); @@ -4858,8 +5177,8 @@ class TGeoPainter extends ObjectPainter { /** @summary Should be called when depth method is changed */ changedDepthMethod(arg) { - // force recalculatiion of render order - delete this._last_camera_position; + // force recalculation of render order + this.#last_camera_position = undefined; if (arg !== 'norender') return this.render3D(); } @@ -4867,12 +5186,13 @@ class TGeoPainter extends ObjectPainter { /** @summary Assign clipping attributes to the meshes - supported only for webgl */ updateClipping(without_render, force_traverse) { // do not try clipping with SVG renderer - if (this._renderer?.jsroot_render3d === constants.Render3D.SVG) return; + if (this.#renderer?.jsroot_render3d === constants.Render3D.SVG) + return; - if (!this._clipPlanes) { - this._clipPlanes = [new Plane(new Vector3(1, 0, 0), 0), - new Plane(new Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0), - new Plane(new Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0)]; + if (!this.#clip_planes) { + this.#clip_planes = [new THREE.Plane(new THREE.Vector3(1, 0, 0), 0), + new THREE.Plane(new THREE.Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0), + new THREE.Plane(new THREE.Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0)]; } const clip = this.ctrl.clip, @@ -4884,32 +5204,33 @@ class TGeoPainter extends ObjectPainter { for (let k = 0; k < 3; ++k) { if (clip[k].enabled) clip_cfg += 2 << k; - if (this._clipPlanes[k].constant !== clip_constants[k]) { - if (clip[k].enabled) changed = true; - this._clipPlanes[k].constant = clip_constants[k]; + if (this.#clip_planes[k].constant !== clip_constants[k]) { + if (clip[k].enabled) + changed = true; + this.#clip_planes[k].constant = clip_constants[k]; } if (clip[k].enabled) - panels.push(this._clipPlanes[k]); + panels.push(this.#clip_planes[k]); if (container && clip[k].enabled) { - const helper = new PlaneHelper(this._clipPlanes[k], (clip[k].max - clip[k].min)); + const helper = new THREE.PlaneHelper(this.#clip_planes[k], (clip[k].max - clip[k].min)); helper._no_clip = true; container.add(helper); } } - if (panels.length === 0) + if (!panels.length) panels = null; - if (this._clipCfg !== clip_cfg) + if (this.#last_clip_cfg !== clip_cfg) changed = true; - this._clipCfg = clip_cfg; + this.#last_clip_cfg = clip_cfg; - const any_clipping = !!panels, ci = this.ctrl.clipIntersect, - material_side = any_clipping ? DoubleSide : FrontSide; + const any_clipping = Boolean(panels), ci = this.ctrl.clipIntersect, + material_side = any_clipping ? THREE.DoubleSide : THREE.FrontSide; if (force_traverse || changed) { - this._scene.traverse(node => { + this.#scene.traverse(node => { if (!node._no_clip && (node.material?.clippingPlanes !== undefined)) { if (node.material.clippingPlanes !== panels) { node.material.clipIntersection = ci; @@ -4929,7 +5250,8 @@ class TGeoPainter extends ObjectPainter { this.ctrl.doubleside = any_clipping; - if (!without_render) this.render3D(0); + if (!without_render) + this.render3D(0); return changed; } @@ -4938,7 +5260,7 @@ class TGeoPainter extends ObjectPainter { * @desc Used together with web-based geometry viewer * @private */ setCompleteHandler(callback) { - this._complete_handler = callback; + this.#complete_handler = callback; } /** @summary Completes drawing procedure @@ -4951,31 +5273,30 @@ class TGeoPainter extends ObjectPainter { return this; } - let promise = Promise.resolve(true); + let promise; - if (!this._clones) { + if (!this.#clones) { check_extras = false; // if extra object where append, redraw them at the end this.getExtrasContainer('delete'); // delete old container - const extras = (this._main_painter ? this._main_painter._extraObjects : null) || this._extraObjects; - promise = this.drawExtras(extras, '', false); - } else if (this._first_drawing || this._full_redrawing) { - if (this.ctrl.tracks && this.geo_manager) - promise = this.drawExtras(this.geo_manager.fTracks, '<prnt>/Tracks'); - } + promise = this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); + } else if ((this.#first_drawing || this.#full_redrawing) && this.ctrl.tracks && this.#geo_manager) + promise = this.drawExtras(this.#geo_manager.fTracks, '<prnt>/Tracks'); + else + promise = Promise.resolve(true); return promise.then(() => { - if (this._full_redrawing) { + if (this.#full_redrawing) { this.adjustCameraPosition('first'); - this._full_redrawing = false; + this.#full_redrawing = false; full_redraw = true; this.changedDepthMethod('norender'); } - if (this._first_drawing) { + if (this.#first_drawing) { this.adjustCameraPosition('first'); this.showDrawInfo(); - this._first_drawing = false; + this.#first_drawing = false; first_time = true; full_redraw = true; } @@ -4989,82 +5310,82 @@ class TGeoPainter extends ObjectPainter { if (full_redraw) return this.drawAxesAndOverlay(true); }).then(() => { - this._scene.overrideMaterial = null; + this.#scene.overrideMaterial = null; - if (this._provided_more_nodes !== undefined) { - this.appendMoreNodes(this._provided_more_nodes, true); - delete this._provided_more_nodes; + if (this.#provided_more_nodes !== undefined) { + this.appendMoreNodes(this.#provided_more_nodes, true); + this.#provided_more_nodes = undefined; } if (check_extras) { // if extra object where append, redraw them at the end this.getExtrasContainer('delete'); // delete old container - const extras = this._main_painter?._extraObjects || this._extraObjects; - return this.drawExtras(extras, '', false); + return this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); } }).then(() => { this.updateClipping(true); // do not render this.render3D(0, true); - if (close_progress) showProgress(); + if (close_progress) + showProgress(); this.addOrbitControls(); if (first_time && !this.isBatchMode()) { // after first draw check if highlight can be enabled if (this.ctrl.highlight === 0) - this.ctrl.highlight = (this.first_render_tm < 1000); + this.ctrl.highlight = (this.#first_render_tm < 1000); // also highlight of scene object can be assigned at the first draw if (this.ctrl.highlight_scene === 0) this.ctrl.highlight_scene = this.ctrl.highlight; // if rotation was enabled, do it - if (this._webgl && this.ctrl.rotate && !this.ctrl.project) this.autorotate(2.5); - if (this._webgl && this.ctrl.show_controls) this.showControlGui(true); + if (this.#webgl && this.ctrl.rotate && !this.ctrl.project) + this.autorotate(2.5); + if (this.#webgl && this.ctrl.show_controls) + this.showControlGui(true); } this.setAsMainPainter(); - if (isFunc(this._resolveFunc)) { - this._resolveFunc(this); - delete this._resolveFunc; - } + const arr = this.#draw_resolveFuncs; + this.#draw_resolveFuncs = []; + arr?.forEach(func => func(this)); - if (isFunc(this._complete_handler)) - this._complete_handler(this); + if (isFunc(this.#complete_handler)) + this.#complete_handler(this); - if (this._draw_nodes_again) + if (this.#draw_nodes_again) this.startDrawGeometry(); // relaunch drawing else - this._drawing_ready = true; // indicate that drawing is completed + this.#drawing_ready = true; // indicate that drawing is completed return this; }); } /** @summary Returns true if geometry drawing is completed */ - isDrawingReady() { - return this._drawing_ready || false; - } + isDrawingReady() { return this.#drawing_ready ?? false; } /** @summary Remove already drawn node. Used by geom viewer */ removeDrawnNode(nodeid) { - if (!this._draw_nodes) return; + if (!this.#draw_nodes) + return; const new_nodes = []; - for (let n = 0; n < this._draw_nodes.length; ++n) { - const entry = this._draw_nodes[n]; - if ((entry.nodeid === nodeid) || this._clones.isIdInStack(nodeid, entry.stack)) - this._clones.createObject3D(entry.stack, this._toplevel, 'delete_mesh'); - else + for (let n = 0; n < this.#draw_nodes.length; ++n) { + const entry = this.#draw_nodes[n]; + if ((entry.nodeid === nodeid) || this.#clones.isIdInStack(nodeid, entry.stack)) + this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); + else new_nodes.push(entry); } - if (new_nodes.length < this._draw_nodes.length) { - this._draw_nodes = new_nodes; + if (new_nodes.length < this.#draw_nodes.length) { + this.#draw_nodes = new_nodes; this.render3D(); } } @@ -5074,36 +5395,32 @@ class TGeoPainter extends ObjectPainter { if (!first_time) { let can3d = 0; - if (!this.superimpose) { + if (!this.#superimpose) { this.clearTopPainter(); // remove as pointer - if (this._on_pad) { + if (this.#on_pad) { const fp = this.getFramePainter(); if (fp?.mode3d) { fp.clear3dCanvas(); fp.mode3d = false; } - } else + } else if (isFunc(this.clear3dCanvas)) can3d = this.clear3dCanvas(); // remove 3d canvas from main HTML element - - disposeThreejsObject(this._scene); + disposeThreejsObject(this.#scene); } - this._toolbar?.cleanup(); // remove toolbar + this.#toolbar?.cleanup(); // remove toolbar - disposeThreejsObject(this._full_geom); + disposeThreejsObject(this.#fullgeom_proj); - this._controls?.cleanup(); + this.#controls?.cleanup(); - if (this._context_menu) - this._renderer.domElement.removeEventListener('contextmenu', this._context_menu, false); + this.#gui?.destroy(); - this._gui?.destroy(); + this.#worker?.terminate(); - this._worker?.terminate(); - - delete this._animating; + this.#animating = undefined; const obj = this.getGeometry(); if (obj && this.ctrl.is_main) { @@ -5113,106 +5430,93 @@ class TGeoPainter extends ObjectPainter { delete obj.fVolume.$geo_painter; } - if (this._main_painter?._slave_painters) { - const pos = this._main_painter._slave_painters.indexOf(this); - if (pos >= 0) this._main_painter._slave_painters.splice(pos, 1); - } - - for (let k = 0; k < this._slave_painters?.length; ++k) { - const slave = this._slave_painters[k]; - if (slave?._main_painter === this) slave._main_painter = null; - } + this.#central_painter?.assignSubordinate(this, false); + this.#subordinate_painters?.forEach(p => { + p.assignCentral(this, false); + if (p.getClones() === this.getClones()) + p.assignClones(undefined, undefined); + }); - delete this.geo_manager; - delete this._highlight_handlers; + this.#geo_manager = undefined; + this.#highlight_handlers = undefined; super.cleanup(); delete this.ctrl; - delete this.options; - this.did_cleanup = true; + this.#did_cleanup = true; - if (can3d < 0) this.selectDom().html(''); + if (can3d < 0) + this.selectDom().html(''); } - if (this._slave_painters) { - for (const k in this._slave_painters) { - const slave = this._slave_painters[k]; - slave._main_painter = null; - if (slave._clones === this._clones) slave._clones = null; - } - } - - this._main_painter = null; - this._slave_painters = []; + this.#central_painter = undefined; + this.#subordinate_painters = []; - if (this._render_resolveFuncs) { - this._render_resolveFuncs.forEach(func => func(this)); - delete this._render_resolveFuncs; - } + const arr = this.#render_resolveFuncs; + this.#render_resolveFuncs = []; + arr?.forEach(func => func(this)); - if (!this.superimpose) - cleanupRender3D(this._renderer); + if (!this.#superimpose) + cleanupRender3D(this.#renderer); this.ensureBloom(false); - delete this._effectComposer; - - delete this._scene; - delete this._scene_size; - this._scene_width = 0; - this._scene_height = 0; - this._renderer = null; - this._toplevel = null; - delete this._full_geom; - delete this._fog; - delete this._camera; - delete this._camera0pos; - delete this._lookat; - delete this._selected_mesh; - - if (this._clones && this._clones_owner) - this._clones.cleanup(this._draw_nodes, this._build_shapes); - delete this._clones; - delete this._clones_owner; - delete this._draw_nodes; - delete this._drawing_ready; - delete this._build_shapes; - delete this._new_draw_nodes; - delete this._new_append_nodes; - delete this._last_camera_position; - - this.first_render_tm = 0; // time needed for first rendering - this.last_render_tm = 0; + this.#effectComposer = undefined; + + this.#scene = undefined; + this.#scene_size = undefined; + this.#scene_width = 0; + this.#scene_height = 0; + this.#renderer = null; + this.#toplevel = null; + this.#fullgeom_proj = undefined; + this.#fog = undefined; + this.#camera = undefined; + this.#camera0pos = undefined; + this.#lookat = undefined; + this.#selected_mesh = undefined; + this.#custom_bounding_box = undefined; + + this.assignClones(undefined, undefined); + this.#new_draw_nodes = undefined; + this.#new_append_nodes = undefined; + this.#last_camera_position = undefined; + + this.#on_pad = undefined; + + this.#start_render_tm = 0; + this.#first_render_tm = 0; // time needed for first rendering + this.#last_render_tm = 0; this.changeStage(stageInit, 'cleanup'); - delete this.drawing_log; + this.#drawing_log = undefined; - delete this._gui; - delete this._controls; - delete this._context_menu; - delete this._toolbar; + this.#gui = undefined; + this.#controls = undefined; + this.#toolbar = undefined; - delete this._worker; + this.#worker = undefined; } /** @summary perform resize */ performResize(width, height) { - if ((this._scene_width === width) && (this._scene_height === height)) return false; - if ((width < 10) || (height < 10)) return false; + if ((this.#scene_width === width) && (this.#scene_height === height)) + return false; + if ((width < 10) || (height < 10)) + return false; - this._scene_width = width; - this._scene_height = height; + this.#scene_width = width; + this.#scene_height = height; - if (this._camera && this._renderer) { - if (this._camera.isPerspectiveCamera) - this._camera.aspect = this._scene_width / this._scene_height; - else if (this._camera.isOrthographicCamera) + if (this.#camera && this.#renderer) { + if (this.#camera.isPerspectiveCamera) + this.#camera.aspect = this.#scene_width / this.#scene_height; + else if (this.#camera.isOrthographicCamera) this.adjustCameraPosition(true, true); - this._camera.updateProjectionMatrix(); - this._renderer.setSize(this._scene_width, this._scene_height, !this._fit_main_area); - this._effectComposer?.setSize(this._scene_width, this._scene_height); - this._bloomComposer?.setSize(this._scene_width, this._scene_height); + this.#camera.updateProjectionMatrix(); + this.#renderer.setSize(this.#scene_width, this.#scene_height, !this.#fit_main_area); + this.#effectComposer?.setSize(this.#scene_width, this.#scene_height); + this.#bloomComposer?.setSize(this.#scene_width, this.#scene_height); if (this.isStage(stageInit)) this.render3D(); @@ -5227,24 +5531,25 @@ class TGeoPainter extends ObjectPainter { // firefox is the only browser which correctly supports resize of embedded canvas, // for others we should force canvas redrawing at every step - if (cp && !cp.checkCanvasResize(arg)) return false; + if (cp && !cp.checkCanvasResize(arg)) + return false; const sz = this.getSizeFor3d(); - return this.performResize(sz.width, sz.height); } /** @summary Toggle enlarge state */ toggleEnlarge() { if (this.enlargeMain('toggle')) - this.checkResize(); + this.checkResize(); } /** @summary either change mesh wireframe or return current value * @return undefined when wireframe cannot be accessed * @private */ accessObjectWireFrame(obj, on) { - if (!obj?.material) return; + if (!obj?.material) + return; if ((on !== undefined) && obj.stack) obj.material.wireframe = on; @@ -5255,7 +5560,7 @@ class TGeoPainter extends ObjectPainter { /** @summary handle wireframe flag change in GUI * @private */ changedWireFrame() { - this._scene?.traverse(obj => this.accessObjectWireFrame(obj, this.ctrl.wireframe)); + this.#scene?.traverse(obj => this.accessObjectWireFrame(obj, this.ctrl.wireframe)); this.render3D(); } @@ -5276,10 +5581,10 @@ class TGeoPainter extends ObjectPainter { if (obj._typename.indexOf(clTGeoVolume) === 0) obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - if (this.geo_manager && gm) { - this.geo_manager = gm; + if (this.#geo_manager && gm) { + this.#geo_manager = gm; this.assignObject(obj); - this._did_update = true; + this.#did_update = true; return true; } @@ -5287,50 +5592,44 @@ class TGeoPainter extends ObjectPainter { return false; this.assignObject(obj); - this._did_update = true; + this.#did_update = true; return true; } /** @summary Cleanup TGeo drawings */ clearDrawings() { - if (this._clones && this._clones_owner) - this._clones.cleanup(this._draw_nodes, this._build_shapes); - delete this._clones; - delete this._clones_owner; - delete this._draw_nodes; - delete this._drawing_ready; - delete this._build_shapes; + this.assignClones(undefined, undefined); - delete this._extraObjects; - delete this._clipCfg; + this.#extra_objects = undefined; + this.#last_clip_cfg = undefined; // only remove all childs from top level object - disposeThreejsObject(this._toplevel, true); + disposeThreejsObject(this.#toplevel, true); - this._full_redrawing = true; + this.#full_redrawing = true; } - /** @summary Redraw TGeo object inside TPad */ + /** @summary Redraw TGeo object inside TPad */ redraw() { - if (this.superimpose) { + if (this.#superimpose) { const cfg = getHistPainter3DCfg(this.getMainPainter()); if (cfg) { - this._toplevel.scale.set(cfg.scale_x ?? 1, cfg.scale_y ?? 1, cfg.scale_z ?? 1); - this._toplevel.position.set(cfg.offset_x ?? 0, cfg.offset_y ?? 0, cfg.offset_z ?? 0); - this._toplevel.updateMatrix(); - this._toplevel.updateMatrixWorld(); + this.#toplevel.scale.set(cfg.scale_x ?? 1, cfg.scale_y ?? 1, cfg.scale_z ?? 1); + this.#toplevel.position.set(cfg.offset_x ?? 0, cfg.offset_y ?? 0, cfg.offset_z ?? 0); + this.#toplevel.updateMatrix(); + this.#toplevel.updateMatrixWorld(); } } - if (this._did_update) + if (this.#did_update) return this.startRedraw(); - const main = this._on_pad ? this.getFramePainter() : null; - if (!main) + const fp = this.#on_pad ? this.getFramePainter() : null; + if (!fp) return Promise.resolve(false); - const sz = main.getSizeFor3d(main.access3dKind()); - main.apply3dSize(sz); + const sz = fp.getSizeFor3d(fp.access3dKind()); + fp.apply3dSize(sz); return this.performResize(sz.width, sz.height); } @@ -5344,75 +5643,232 @@ class TGeoPainter extends ObjectPainter { /** @summary Start geometry redraw */ startRedraw(tmout) { + if (this.#redraw_timer) { + clearTimeout(this.#redraw_timer); + this.#redraw_timer = undefined; + } + if (tmout) { - if (this._redraw_timer) - clearTimeout(this._redraw_timer); - this._redraw_timer = setTimeout(() => this.startRedraw(), tmout); + this.#redraw_timer = setTimeout(() => this.startRedraw(), tmout); return; } - delete this._redraw_timer; - delete this._did_update; + this.#did_update = undefined; this.clearDrawings(); const draw_obj = this.getGeometry(), - name_prefix = this.geo_manager ? draw_obj.fName : ''; + name_prefix = this.#geo_manager ? draw_obj.fName : ''; return this.prepareObjectDraw(draw_obj, name_prefix); } - /** @summary draw TGeo object */ + /** @summary Build three.js object */ + static build3d(obj, sopt) { + if (!obj) + return null; + + let opt = sopt || {}; + if (isStr(sopt)) { + const painter = new TGeoPainter(null, obj); + painter.decodeOptions(sopt); + opt = painter.ctrl; + opt.numfaces = opt.maxfaces; + opt.numnodes = opt.maxnodes; + } + + if (!opt.numfaces) + opt.numfaces = 100000; + if (!opt.numnodes) + opt.numnodes = 1000; + if (!opt.frustum) + opt.frustum = null; + + opt.res_mesh = opt.res_faces = 0; + + if (opt.instancing === undefined) + opt.instancing = -1; + + opt.info = { num_meshes: 0, num_faces: 0 }; + + let clones, visibles; + + if (obj.visibles && obj.nodes && obj.numnodes) { + // case of draw message from geometry viewer + + const nodes = obj.numnodes > 1e6 ? { length: obj.numnodes } : new Array(obj.numnodes); + + obj.nodes.forEach(node => { + nodes[node.id] = ClonedNodes.formatServerElement(node); + }); + + clones = new ClonedNodes(null, nodes); + clones.name_prefix = clones.getNodeName(0); + + // normally only need when making selection, not used in geo viewer + // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); + // this.geo_clones.setVisLevel(draw_msg.vislevel); + // TODO: provide from server + clones.maxdepth = 20; + + const nsegm = obj.cfg?.nsegm || 30; + + for (let cnt = 0; cnt < obj.visibles.length; ++cnt) { + const item = obj.visibles[cnt], rd = item.ri; + + // entry may be provided without shape - it is ok + if (rd) + item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); + } + + visibles = obj.visibles; + } else { + let shape = null, hide_top = false; + + if (('fShapeBits' in obj) && ('fShapeId' in obj)) { + shape = obj; + obj = null; + } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) + shape = obj.fShape; + else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) + shape = obj.fShape; + else if (obj._typename === clTGeoManager) { + obj = obj.fMasterVolume; + hide_top = !opt.showtop; + shape = obj.fShape; + } else if (obj.fVolume) + shape = obj.fVolume.fShape; + else + obj = null; + + if (opt.composite && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) + obj = buildCompositeVolume(shape); + + if (!obj && shape) + obj = Object.assign(create(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); + + if (!obj) + return null; + + if (obj._typename.indexOf(clTGeoVolume) === 0) + obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; + + clones = new ClonedNodes(obj); + clones.setVisLevel(opt.vislevel); + clones.setMaxVisNodes(opt.numnodes); + + if (opt.dflt_colors) + clones.setDefaultColors(true); + + const uniquevis = opt.no_screen ? 0 : clones.markVisibles(true); + if (uniquevis <= 0) + clones.markVisibles(false, false, hide_top); + else + clones.markVisibles(true, true, hide_top); // copy bits once and use normal visibility bits + + clones.produceIdShifts(); + + // collect visible nodes + const res = clones.collectVisibles(opt.numfaces, opt.frustum); + + visibles = res.lst; + } + + if (!opt.material_kind) + opt.material_kind = 'lambert'; + if (opt.set_names === undefined) + opt.set_names = true; + + clones.setConfig(opt); + + // collect shapes + const shapes = clones.collectShapes(visibles); + + clones.buildShapes(shapes, opt.numfaces); + + const toplevel = new THREE.Object3D(); + toplevel.clones = clones; // keep reference on JSROOT data + + const colors = getRootColors(); + + if (clones.createInstancedMeshes(opt, toplevel, visibles, shapes, colors)) + return toplevel; + + for (let n = 0; n < visibles.length; ++n) { + const entry = visibles[n]; + if (entry.done) + continue; + + const shape = entry.server_shape || shapes[entry.shapeid]; + if (!shape.ready) { + console.warn('shape marked as not ready when it should'); + break; + } + + clones.createEntryMesh(opt, toplevel, entry, shape, colors); + } + + return toplevel; + } + + /** @summary draw TGeo object */ static async draw(dom, obj, opt) { - if (!obj) return null; + if (!obj) + return null; let shape = null, extras = null, extras_path = '', is_eve = false; if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; obj = null; + shape = obj; + obj = null; } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) shape = obj.fShape; else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - shape = obj.fShape; is_eve = true; + shape = obj.fShape; + is_eve = true; } else if (obj._typename === clTGeoManager) shape = obj.fMasterVolume.fShape; else if (obj._typename === clTGeoOverlap) { - extras = obj.fMarker; extras_path = '<prnt>/Marker'; + extras = obj.fMarker; + extras_path = '<prnt>/Marker'; obj = buildOverlapVolume(obj); - if (!opt) opt = 'wire'; - } else if ('fVolume' in obj) { - if (obj.fVolume) shape = obj.fVolume.fShape; - } else + if (!opt) + opt = 'wire'; + } else if ('fVolume' in obj) + shape = obj.fVolume?.fShape; + else obj = null; - if (isStr(opt) && opt.indexOf('comp') === 0 && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { let maxlvl = 1; opt = opt.slice(4); - if (opt[0] === 'x') { maxlvl = 999; opt = opt.slice(1) + '_vislvl999'; } + if (opt[0] === 'x') { + maxlvl = 999; + opt = opt.slice(1) + '_vislvl999'; + } obj = buildCompositeVolume(shape, maxlvl); } - if (!obj && shape) { - obj = Object.assign(create(clTNamed), - { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); - } + if (!obj && shape) + obj = Object.assign(create(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); - if (!obj) return null; + if (!obj) + return null; + // eslint-disable-next-line no-use-before-define const painter = createGeoPainter(dom, obj, opt); if (painter.ctrl.is_main && !obj.$geo_painter) obj.$geo_painter = painter; if (!painter.ctrl.is_main && painter.ctrl.project && obj.$geo_painter) { - painter._main_painter = obj.$geo_painter; - painter._main_painter._slave_painters.push(painter); + painter.assignCentral(obj.$geo_painter); + obj.$geo_painter.assignSubordinate(painter); } if (is_eve && (!painter.ctrl.vislevel || (painter.ctrl.vislevel < 9))) painter.ctrl.vislevel = 9; if (extras) { - painter._splitColors = true; + painter.ctrl.split_colors = true; painter.addExtra(extras, extras_path); } @@ -5424,6 +5880,55 @@ class TGeoPainter extends ObjectPainter { let add_settings = false; +/** @summary Get icon for the browser + * @private */ +function getBrowserIcon(hitem, hpainter) { + let icon = ''; + switch (hitem._kind) { + case getKindForType(clTEveTrack): icon = 'img_evetrack'; break; + case getKindForType(clTEvePointSet): icon = 'img_evepoints'; break; + case getKindForType(clTPolyMarker3D): icon = 'img_evepoints'; break; + } + if (icon) { + const drawitem = findItemWithGeoPainter(hitem); + if (drawitem?._painter?.extraObjectVisible(hpainter, hitem)) + icon += ' geovis_this'; + } + return icon; +} + +/** @summary handle click on browser icon + * @private */ +function browserIconClick(hitem, hpainter) { + if (hitem._volume) { + if (hitem._more && hitem._volume.fNodes?.arr?.length) + toggleGeoBit(hitem._volume, geoBITS.kVisDaughters); + else + toggleGeoBit(hitem._volume, geoBITS.kVisThis); + + updateBrowserIcons(hitem._volume, hpainter); + + findItemWithGeoPainter(hitem, true); + return false; // no need to update icon - we did it ourself + } + + if (hitem._geoobj && ((hitem._geoobj._typename === clTEveGeoShapeExtract) || (hitem._geoobj._typename === clREveGeoShapeExtract))) { + hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf; + + updateBrowserIcons(hitem._geoobj, hpainter); + findItemWithGeoPainter(hitem, true); + return false; // no need to update icon - we did it ourself + } + + // first check that geo painter assigned with the item + const drawitem = findItemWithGeoPainter(hitem), + newstate = drawitem?._painter?.extraObjectVisible(hpainter, hitem, true); + + // return true means browser should update icon for the item + return newstate !== undefined; +} + + /** @summary Create geo-related css entries * @private */ function injectGeoStyle() { @@ -5495,30 +6000,33 @@ function createGeoPainter(dom, obj, opt) { /** @summary provide menu for geo object * @private */ function provideMenu(menu, item, hpainter) { - if (!item._geoobj) return false; + if (!item._geoobj) + return false; const obj = item._geoobj, vol = item._volume, iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)); - if (!vol && !iseve) return false; + if (!vol && !iseve) + return false; - menu.add('separator'); + menu.separator(); - const scanEveVisible = (obj, arg, skip_this) => { - if (!arg) arg = { visible: 0, hidden: 0 }; + const scanEveVisible = (obj2, arg, skip_this) => { + if (!arg) + arg = { visible: 0, hidden: 0 }; if (!skip_this) { if (arg.assign !== undefined) - obj.fRnrSelf = arg.assign; - else if (obj.fRnrSelf) + obj2.fRnrSelf = arg.assign; + else if (obj2.fRnrSelf) arg.vis++; else arg.hidden++; } - if (obj.fElements) { - for (let n = 0; n < obj.fElements.arr.length; ++n) - scanEveVisible(obj.fElements.arr[n], arg, false); + if (obj2.fElements) { + for (let n = 0; n < obj2.fElements.arr.length; ++n) + scanEveVisible(obj2.fElements.arr[n], arg, false); } return arg; @@ -5538,7 +6046,7 @@ function provideMenu(menu, item, hpainter) { }, item); } - findItemWithPainter(item, 'testGeomChanges'); + findItemWithGeoPainter(item, true); }, toggleMenuBit = arg => { toggleGeoBit(vol, arg); const newname = item._icon.split(' ')[0] + provideVisStyle(vol); @@ -5551,14 +6059,14 @@ function provideMenu(menu, item, hpainter) { }); hpainter.updateTreeNode(item); - findItemWithPainter(item, 'testGeomChanges'); - }, drawitem = findItemWithPainter(item), - fullname = drawitem ? hpainter.itemFullName(item, drawitem) : ''; + findItemWithGeoPainter(item, true); + }, drawitem = findItemWithGeoPainter(item), + fullname = drawitem ? hpainter.itemFullName(item, drawitem) : ''; if ((item._geoobj._typename.indexOf(clTGeoNode) === 0) && drawitem) { menu.add('Focus', () => { - if (drawitem && isFunc(drawitem._painter?.focusOnItem)) - drawitem._painter.focusOnItem(fullname); + if (drawitem && isFunc(drawitem._painter?.focusOnItem)) + drawitem._painter.focusOnItem(fullname); }); } @@ -5566,99 +6074,162 @@ function provideMenu(menu, item, hpainter) { menu.addchk(obj.fRnrSelf, 'Visible', 'self', toggleEveVisibility); const res = scanEveVisible(obj, undefined, true); if (res.hidden + res.visible > 0) - menu.addchk((res.hidden === 0), 'Daughters', res.hidden !== 0 ? 'true' : 'false', toggleEveVisibility); + menu.addchk((res.hidden === 0), 'Daughters', res.hidden ? 'true' : 'false', toggleEveVisibility); } else { - const stack = drawitem?._painter?._clones?.findStackByName(fullname), - phys_vis = stack ? drawitem._painter._clones.getPhysNodeVisibility(stack) : null, - is_visible = testGeoBit(vol, geoBITS.kVisThis); + const stack = drawitem?._painter?.getClones()?.findStackByName(fullname), + phys_vis = stack ? drawitem._painter.getClones().getPhysNodeVisibility(stack) : null, + is_visible = testGeoBit(vol, geoBITS.kVisThis); menu.addchk(testGeoBit(vol, geoBITS.kVisNone), 'Invisible', geoBITS.kVisNone, toggleMenuBit); if (stack) { const changePhysVis = arg => { - drawitem._painter._clones.setPhysNodeVisibility(stack, (arg === 'off') ? false : arg); - findItemWithPainter(item, 'testGeomChanges'); + drawitem._painter.getClones().setPhysNodeVisibility(stack, (arg === 'off') ? false : arg); + findItemWithGeoPainter(item, true); }; - menu.add('sub:Physical vis', 'Physical node visibility - only for this instance'); + menu.sub('Physical vis', 'Physical node visibility - only for this instance'); menu.addchk(phys_vis?.visible, 'on', 'on', changePhysVis, 'Enable visibility of phys node'); menu.addchk(phys_vis && !phys_vis.visible, 'off', 'off', changePhysVis, 'Disable visibility of physical node'); menu.add('reset', 'clear', changePhysVis, 'Reset custom visibility of physical node'); menu.add('reset all', 'clearall', changePhysVis, 'Reset all custom settings for all nodes'); - menu.add('endsub:'); + menu.endsub(); } menu.addchk(is_visible, 'Logical vis', - geoBITS.kVisThis, toggleMenuBit, 'Logical node visibility - all instances'); + geoBITS.kVisThis, toggleMenuBit, 'Logical node visibility - all instances'); menu.addchk(testGeoBit(vol, geoBITS.kVisDaughters), 'Daughters', - geoBITS.kVisDaughters, toggleMenuBit, 'Logical node daugthers visibility'); + geoBITS.kVisDaughters, toggleMenuBit, 'Logical node daugthers visibility'); } return true; } -/** @summary handle click on browser icon +let createItem = null; + +/** @summary create list entity for geo object * @private */ -function browserIconClick(hitem, hpainter) { - if (hitem._volume) { - if (hitem._more && hitem._volume.fNodes?.arr?.length) - toggleGeoBit(hitem._volume, geoBITS.kVisDaughters); - else - toggleGeoBit(hitem._volume, geoBITS.kVisThis); +function createList(parent, lst, name, title) { + if (!lst?.arr?.length) + return; - updateBrowserIcons(hitem._volume, hpainter); + const list_item = { + _name: name, + _kind: getKindForType(clTList), + _title: title, + _more: true, + _geoobj: lst, + _parent: parent, + _get(item /* , itemname */) { + return Promise.resolve(item._geoobj || null); + }, + _expand(node, lst2) { + // only childs + + if (lst2.fVolume) + lst2 = lst2.fVolume.fNodes; + + if (!lst2.arr) + return false; - findItemWithPainter(hitem, 'testGeomChanges'); - return false; // no need to update icon - we did it ourself - } + node._childs = []; - if (hitem._geoobj && ((hitem._geoobj._typename === clTEveGeoShapeExtract) || (hitem._geoobj._typename === clREveGeoShapeExtract))) { - hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf; + checkDuplicates(null, lst2.arr); - updateBrowserIcons(hitem._geoobj, hpainter); - findItemWithPainter(hitem, 'testGeomChanges'); - return false; // no need to update icon - we did it ourself - } + for (const n in lst2.arr) + createItem(node, lst2.arr[n]); - // first check that geo painter assigned with the item - const drawitem = findItemWithPainter(hitem), - newstate = drawitem?._painter?.extraObjectVisible(hpainter, hitem, true); + return true; + } + }; - // return true means browser should update icon for the item - return newstate !== undefined; + if (!parent._childs) + parent._childs = []; + parent._childs.push(list_item); } -/** @summary Get icon for the browser +/** @summary Expand geo object * @private */ -function getBrowserIcon(hitem, hpainter) { - let icon = ''; - switch (hitem._kind) { - case prROOT + clTEveTrack: icon = 'img_evetrack'; break; - case prROOT + clTEvePointSet: icon = 'img_evepoints'; break; - case prROOT + clTPolyMarker3D: icon = 'img_evepoints'; break; +function expandGeoObject(parent, obj) { + injectGeoStyle(); + + if (!parent || !obj) + return false; + + const isnode = (obj._typename.indexOf(clTGeoNode) === 0), + isvolume = (obj._typename.indexOf(clTGeoVolume) === 0), + ismanager = (obj._typename === clTGeoManager), + iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)), + isoverlap = (obj._typename === clTGeoOverlap); + + if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) + return false; + + if (parent._childs) + return true; + + if (ismanager) { + createList(parent, obj.fMaterials, 'Materials', 'list of materials'); + createList(parent, obj.fMedia, 'Media', 'list of media'); + createList(parent, obj.fTracks, 'Tracks', 'list of tracks'); + createList(parent, obj.fOverlaps, 'Overlaps', 'list of detected overlaps'); + createItem(parent, obj.fMasterVolume); + return true; } - if (icon) { - const drawitem = findItemWithPainter(hitem); - if (drawitem?._painter?.extraObjectVisible(hpainter, hitem)) - icon += ' geovis_this'; + + if (isoverlap) { + createItem(parent, obj.fVolume1); + createItem(parent, obj.fVolume2); + createItem(parent, obj.fMarker, 'Marker'); + return true; } - return icon; + + let volume, subnodes, shape; + + if (iseve) { + subnodes = obj.fElements?.arr; + shape = obj.fShape; + } else { + volume = isnode ? obj.fVolume : obj; + subnodes = volume?.fNodes?.arr; + shape = volume?.fShape; + } + + if (!subnodes && (shape?._typename === clTGeoCompositeShape) && shape?.fNode) { + if (!parent._childs) { + createItem(parent, shape.fNode.fLeft, 'Left'); + createItem(parent, shape.fNode.fRight, 'Right'); + } + + return true; + } + + if (!subnodes) + return false; + + checkDuplicates(obj, subnodes); + + for (let i = 0; i < subnodes.length; ++i) + createItem(parent, subnodes[i]); + + return true; } /** @summary create hierarchy item for geo object * @private */ -function createItem(node, obj, name) { +createItem = function(node, obj, name) { const sub = { - _kind: prROOT + obj._typename, + _kind: getKindForType(obj._typename), _name: name || getObjectName(obj), _title: obj.fTitle, _parent: node, _geoobj: obj, _get(item /* ,itemname */) { - // mark object as belong to the hierarchy, require to - if (item._geoobj) item._geoobj.$geoh = true; - return Promise.resolve(item._geoobj); + // mark object as belong to the hierarchy, require to + if (item._geoobj) + item._geoobj.$geoh = true; + return Promise.resolve(item._geoobj); } }; let volume, shape, subnodes, iseve = false; @@ -5671,7 +6242,8 @@ function createItem(node, obj, name) { sub._icon = 'img_geomixture'; else if ((obj._typename.indexOf(clTGeoNode) === 0) && obj.fVolume) { sub._title = 'node:' + obj._typename; - if (obj.fTitle) sub._title += ' ' + obj.fTitle; + if (obj.fTitle) + sub._title += ' ' + obj.fTitle; volume = obj.fVolume; } else if (obj._typename.indexOf(clTGeoVolume) === 0) volume = obj; @@ -5688,7 +6260,8 @@ function createItem(node, obj, name) { } if (volume || shape || subnodes) { - if (volume) sub._volume = volume; + if (volume) + sub._volume = volume; if (subnodes) { sub._more = true; @@ -5696,9 +6269,9 @@ function createItem(node, obj, name) { } else if (shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { sub._more = true; sub._shape = shape; - sub._expand = function(node /*, obj */) { - createItem(node, node._shape.fNode.fLeft, 'Left'); - createItem(node, node._shape.fNode.fRight, 'Right'); + sub._expand = function(node2 /* , obj */) { + createItem(node2, node2._shape.fNode.fLeft, 'Left'); + createItem(node2, node2._shape.fNode.fRight, 'Right'); return true; }; } @@ -5723,13 +6296,14 @@ function createItem(node, obj, name) { sub._icon_click = browserIconClick; } - if (!node._childs) node._childs = []; + if (!node._childs) + node._childs = []; if (!sub._name) { if (isStr(node._name)) { sub._name = node._name; - if (sub._name.lastIndexOf('s') === sub._name.length-1) - sub._name = sub._name.slice(0, sub._name.length-1); + if (sub._name.at(-1) === 's') + sub._name = sub._name.slice(0, sub._name.length - 1); sub._name += '_' + node._childs.length; } else sub._name = 'item_' + node._childs.length; @@ -5738,7 +6312,7 @@ function createItem(node, obj, name) { node._childs.push(sub); return sub; -} +}; /** @summary Draw dummy geometry * @private */ @@ -5752,15 +6326,16 @@ async function drawDummy3DGeom(painter) { shape.fShapeBits = 0; shape.fOrigin = [0, 0, 0]; - const obj = Object.assign(create(clTNamed), - { _typename: clTEveGeoShapeExtract, - fTrans: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], - fShape: shape, fRGBA: [0, 0, 0, 0], fElements: null, fRnrSelf: false }), + const obj = Object.assign(create(clTNamed), { + _typename: clTEveGeoShapeExtract, + fTrans: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + fShape: shape, fRGBA: [0, 0, 0, 0], fElements: null, fRnrSelf: false + }), pp = painter.getPadPainter(), - opt = (pp?.pad?.fFillColor && (pp?.pad?.fFillStyle > 1000)) ? 'bkgr_' + pp.pad.fFillColor : ''; + pad = pp?.getRootPad(true), + opt = 'dummy;' + (pad?.fFillColor && (pad?.fFillStyle > 1000) ? 'bkgr_' + pad.fFillColor : ''); - return TGeoPainter.draw(painter.getDom(), obj, opt) - .then(geop => { geop._dummy = true; return geop; }); + return TGeoPainter.draw(pp || painter.getDom(), obj, opt); } /** @summary Direct draw function for TAxis3D @@ -5794,136 +6369,7 @@ function drawAxis3D() { * // this is three.js object and can be now inserted in the scene */ function build(obj, opt) { - if (!obj) return null; - - if (!opt) opt = {}; - if (!opt.numfaces) opt.numfaces = 100000; - if (!opt.numnodes) opt.numnodes = 1000; - if (!opt.frustum) opt.frustum = null; - - opt.res_mesh = opt.res_faces = 0; - - if (opt.instancing === undefined) - opt.instancing = -1; - - opt.info = { num_meshes: 0, num_faces: 0 }; - - let clones = null, visibles = null; - - if (obj.visibles && obj.nodes && obj.numnodes) { - // case of draw message from geometry viewer - - const nodes = obj.numnodes > 1e6 ? { length: obj.numnodes } : new Array(obj.numnodes); - - obj.nodes.forEach(node => { - nodes[node.id] = ClonedNodes.formatServerElement(node); - }); - - clones = new ClonedNodes(null, nodes); - clones.name_prefix = clones.getNodeName(0); - - // normally only need when making selection, not used in geo viewer - // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo_clones.setVisLevel(draw_msg.vislevel); - // TODO: provide from server - clones.maxdepth = 20; - - const nsegm = obj.cfg?.nsegm || 30; - - for (let cnt = 0; cnt < obj.visibles.length; ++cnt) { - const item = obj.visibles[cnt], rd = item.ri; - - // entry may be provided without shape - it is ok - if (rd) - item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); - } - - visibles = obj.visibles; - } else { - let shape = null, hide_top = false; - - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; obj = null; - } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) - shape = obj.fShape; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) - shape = obj.fShape; - else if (obj._typename === clTGeoManager) { - obj = obj.fMasterVolume; - hide_top = !opt.showtop; - shape = obj.fShape; - } else if (obj.fVolume) - shape = obj.fVolume.fShape; - else - obj = null; - - - if (opt.composite && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) - obj = buildCompositeVolume(shape); - - if (!obj && shape) - obj = Object.assign(create(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); - - if (!obj) return null; - - if (obj._typename.indexOf(clTGeoVolume) === 0) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - - clones = new ClonedNodes(obj); - clones.setVisLevel(opt.vislevel); - clones.setMaxVisNodes(opt.numnodes); - - if (opt.dflt_colors) - clones.setDefaultColors(true); - - const uniquevis = opt.no_screen ? 0 : clones.markVisibles(true); - if (uniquevis <= 0) - clones.markVisibles(false, false, hide_top); - else - clones.markVisibles(true, true, hide_top); // copy bits once and use normal visibility bits - - clones.produceIdShifts(); - - // collect visible nodes - const res = clones.collectVisibles(opt.numfaces, opt.frustum); - - visibles = res.lst; - } - - if (!opt.material_kind) - opt.material_kind = 'lambert'; - if (opt.set_names === undefined) - opt.set_names = true; - - clones.setConfig(opt); - - // collect shapes - const shapes = clones.collectShapes(visibles); - - clones.buildShapes(shapes, opt.numfaces); - - const toplevel = new Object3D(); - toplevel.clones = clones; // keep reference on JSROOT data - - const colors = getRootColors(); - - if (clones.createInstancedMeshes(opt, toplevel, visibles, shapes, colors)) - return toplevel; - - for (let n = 0; n < visibles.length; ++n) { - const entry = visibles[n]; - if (entry.done) continue; - - const shape = entry.server_shape || shapes[entry.shapeid]; - if (!shape.ready) { - console.warn('shape marked as not ready when it should'); - break; - } - - clones.createEntryMesh(opt, toplevel, entry, shape, colors); - } - - return toplevel; + return TGeoPainter.build3d(obj, opt); } export { ClonedNodes, build, TGeoPainter, GeoDrawingControl, diff --git a/modules/geom/bundle.mjs b/modules/geom/bundle.mjs index 68912e4c3..b2d425e74 100644 --- a/modules/geom/bundle.mjs +++ b/modules/geom/bundle.mjs @@ -1,5 +1,5 @@ // Only build function to create three.js model -export { version, parse, settings } from '../core.mjs'; +export { version, parse, settings, httpRequest } from '../core.mjs'; export { openFile } from '../io.mjs'; -export { build } from './TGeoPainter.mjs'; +export { build, produceRenderOrder } from './TGeoPainter.mjs'; diff --git a/modules/geom/csg.mjs b/modules/geom/csg.mjs index cece2fe1c..54df92147 100644 --- a/modules/geom/csg.mjs +++ b/modules/geom/csg.mjs @@ -1,6 +1,6 @@ -/// CSG library for THREE.js +// CSG library for THREE.js -import { BufferGeometry, BufferAttribute, Mesh } from '../three.mjs'; +import { THREE } from '../base/base3d.mjs'; const EPSILON = 1e-5, COPLANAR = 0, @@ -73,7 +73,7 @@ class Vertex { normalize() { - const length = Math.sqrt(this.x**2 + this.y**2 + this.z**2); + const length = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2); this.x /= length; this.y /= length; @@ -83,19 +83,19 @@ class Vertex { } dot(vertex) { - return this.x*vertex.x + this.y*vertex.y + this.z*vertex.z; + return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; } diff(vertex) { const dx = (this.x - vertex.x), - dy = (this.y - vertex.y), - dz = (this.z - vertex.z), - len2 = this.x**2 + this.y**2 + this.z**2; + dy = (this.y - vertex.y), + dz = (this.z - vertex.z), + len2 = this.x ** 2 + this.y ** 2 + this.z ** 2; - return (dx**2 + dy**2 + dz**2) / (len2 > 0 ? len2 : 1e-10); + return (dx ** 2 + dy ** 2 + dz ** 2) / (len2 > 0 ? len2 : 1e-10); } -/* + /* lerp( a, t ) { this.add( a.clone().subtract( this ).multiplyScalar( t ) @@ -119,8 +119,8 @@ class Vertex { interpolate(a, t) { const 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); + 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); } applyMatrix4(m) { @@ -133,7 +133,9 @@ class Vertex { 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; @@ -152,7 +154,7 @@ class Polygon { this.nsign = 1; if (parent) this.copyProperties(parent, more); - else if (this.vertices.length > 0) + else if (this.vertices.length) this.calculateProperties(); } @@ -168,11 +170,12 @@ class Polygon { } calculateProperties(force) { - if (this.normal && !force) return; + if (this.normal && !force) + return; const a = this.vertices[0], - b = this.vertices[1], - c = this.vertices[2]; + b = this.vertices[1], + c = this.vertices[2]; this.nsign = 1; @@ -186,7 +189,7 @@ class Polygon { clone() { const vertice_count = this.vertices.length, - vertices = []; + vertices = []; for (let i = 0; i < vertice_count; ++i) vertices.push(this.vertices[i].clone()); @@ -195,7 +198,7 @@ class Polygon { } flip() { - /// normal is not changed, only sign variable + // normal is not changed, only sign variable // this.normal.multiplyScalar( -1 ); // this.w *= -1; @@ -208,9 +211,10 @@ class Polygon { classifyVertex(vertex) { const side_value = this.nsign * (this.normal.dot(vertex) - this.w); - - if (side_value < -EPSILON) return BACK; - if (side_value > EPSILON) return FRONT; + if (side_value < -EPSILON) + return BACK; + if (side_value > EPSILON) + return FRONT; return COPLANAR; } @@ -222,13 +226,16 @@ class Polygon { const classification = this.classifyVertex(polygon.vertices[i]); if (classification === FRONT) ++num_positive; - else if (classification === BACK) + else if (classification === BACK) ++num_negative; } - 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; + 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; } @@ -239,21 +246,21 @@ class Polygon { ((this.nsign * polygon.nsign * this.normal.dot(polygon.normal) > 0) ? coplanar_front : coplanar_back).push(polygon); - else if (classification === FRONT) + else if (classification === FRONT) front.push(polygon); - else if (classification === BACK) + else if (classification === BACK) back.push(polygon); - else { + else { const vertice_count = polygon.vertices.length, nnx = this.normal.x, nny = this.normal.y, nnz = this.normal.z, f = [], b = []; - let i, j, ti, tj, vi, vj, t, v; + let i, j, ti, tj, vi, vj, t, v; for (i = 0; i < vertice_count; ++i) { j = (i + 1) % vertice_count; @@ -262,13 +269,15 @@ class Polygon { ti = this.classifyVertex(vi); tj = this.classifyVertex(vj); - if (ti !== BACK) f.push(vi); - if (ti !== FRONT) b.push(vi); + 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)); + 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); @@ -276,10 +285,14 @@ class Polygon { } } - // 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, polygon, true)); - if (b.length >= 3) back.push(new Polygon(b, polygon, true)); + // 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, polygon, true)); + if (b.length >= 3) + back.push(new Polygon(b, polygon, true)); } } @@ -292,12 +305,13 @@ class Node { this.polygons = []; this.front = this.back = undefined; - if (!polygons) return; + if (!polygons) + return; this.divider = polygons[0].clone(); const polygon_count = polygons.length, - front = [], back = []; + front = [], back = []; for (let i = 0; i < polygon_count; ++i) { if (nodeid !== undefined) { @@ -305,19 +319,20 @@ class Node { delete polygons[i].parent; } - // by difinition polygon should be COPLANAR for itself + // by definition polygon should be COPLANAR for itself if (i === 0) this.polygons.push(polygons[0]); else this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); } - if (nodeid !== undefined) this.maxnodeid = nodeid; + if (nodeid !== undefined) + this.maxnodeid = nodeid; - if (front.length > 0) + if (front.length) this.front = new Node(front); - if (back.length > 0) + if (back.length) this.back = new Node(back); } @@ -325,7 +340,8 @@ class Node { // let 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; + // if ( i !== j && polygons[i].classifySide( polygons[j] ) !== BACK ) + // return false; // return true; // } @@ -343,13 +359,15 @@ class Node { for (let i = first; 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 Node(); + if (front.length) { + if (!this.front) + this.front = new Node(); this.front.build(front); } - if (back.length > 0) { - if (!this.back) this.back = new Node(); + if (back.length) { + if (!this.back) + this.back = new Node(); this.back.build(back); } } @@ -387,8 +405,10 @@ class Node { this.polygons[i].flip(); this.divider.flip(); - if (this.front) this.front.invert(); - if (this.back) this.back.invert(); + if (this.front) + this.front.invert(); + if (this.back) + this.back.invert(); const temp = this.front; this.front = this.back; @@ -398,7 +418,8 @@ class Node { } clipPolygons(polygons) { - if (!this.divider) return polygons.slice(); + if (!this.divider) + return polygons.slice(); const polygon_count = polygons.length; let front = [], back = []; @@ -406,20 +427,21 @@ class Node { for (let 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); + + back = this.back?.clipPolygons(back) ?? []; return front.concat(back); } clipTo(node) { this.polygons = node.clipPolygons(this.polygons); - if (this.front) this.front.clipTo(node); - if (this.back) this.back.clipTo(node); + this.front?.clipTo(node); + this.back?.clipTo(node); } - } // class Node +} // class Node function createBufferGeometry(polygons) { @@ -435,27 +457,27 @@ function createBufferGeometry(polygons) { function CopyVertex(vertex) { positions_buf[iii] = vertex.x; - positions_buf[iii+1] = vertex.y; - positions_buf[iii+2] = vertex.z; + 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; + 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 - 1]); CopyVertex(polygon.vertices[j]); } } - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(positions_buf, 3)); - geometry.setAttribute('normal', new BufferAttribute(normals_buf, 3)); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); + geometry.setAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); // geometry.computeVertexNormals(); return geometry; @@ -467,7 +489,7 @@ class Geometry { constructor(geometry, transfer_matrix, nodeid, flippedMesh) { // Convert BufferGeometry to ThreeBSP - if (geometry instanceof Mesh) { + if (geometry instanceof THREE.Mesh) { // #todo: add hierarchy support geometry.updateMatrix(); transfer_matrix = this.matrix = geometry.matrix.clone(); @@ -475,35 +497,41 @@ class Geometry { } else if (geometry instanceof Node) { this.tree = geometry; this.matrix = null; // new Matrix4; - return this; - } else if (geometry instanceof BufferGeometry) { + return; + } else if (geometry instanceof THREE.BufferGeometry) { const pos_buf = geometry.getAttribute('position').array, norm_buf = geometry.getAttribute('normal').array, polygons = []; let polygon, vert1, vert2, vert3; - for (let i=0; i < pos_buf.length; i+=9) { + for (let i = 0; i < pos_buf.length; i += 9) { polygon = new Polygon(); - 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); + 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); - 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); + 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); - 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); + 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); - if (flippedMesh) polygon.vertices.push(vert1, vert3, vert2); - else polygon.vertices.push(vert1, vert2, vert3); + if (flippedMesh) + polygon.vertices.push(vert1, vert3, vert2); + else + polygon.vertices.push(vert1, vert2, vert3); polygon.calculateProperties(true); polygons.push(polygon); } this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; + return; } else if (geometry.polygons && (geometry.polygons[0] instanceof Polygon)) { const polygons = geometry.polygons; @@ -522,12 +550,12 @@ class Geometry { } this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; + return; } else throw Error('ThreeBSP: Given geometry is unsupported'); - const polygons = [], nfaces = geometry.faces.length; let face, polygon, vertex, normal, useVertexNormals; @@ -540,24 +568,30 @@ class Geometry { useVertexNormals = face.vertexNormals && (face.vertexNormals.length === 3); vertex = geometry.vertices[face.a]; - if (useVertexNormals) normal = face.vertexNormals[0]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; + if (useVertexNormals) + normal = face.vertexNormals[0]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); polygon.vertices.push(vertex); vertex = geometry.vertices[face.b]; - if (useVertexNormals) normal = face.vertexNormals[1]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; + if (useVertexNormals) + normal = face.vertexNormals[1]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); polygon.vertices.push(vertex); vertex = geometry.vertices[face.c]; - if (useVertexNormals) normal = face.vertexNormals[2]; - // uvs = faceVertexUvs ? new Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; + if (useVertexNormals) + normal = face.vertexNormals[2]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + if (transfer_matrix) + vertex.applyMatrix4(transfer_matrix); polygon.vertices.push(vertex); polygon.calculateProperties(true); @@ -565,7 +599,8 @@ class Geometry { } this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; + if (nodeid !== undefined) + this.maxid = this.tree.maxnodeid; } subtract(other_tree) { @@ -617,7 +652,8 @@ class Geometry { } tryToCompress(polygons) { - if (this.maxid === undefined) return; + if (this.maxid === undefined) + return; const arr = []; let parts, foundpair, @@ -627,15 +663,18 @@ class Geometry { // 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] = []; + 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; + if (parts === undefined) + continue; len = parts.length; @@ -644,10 +683,11 @@ class Geometry { while (foundpair) { foundpair = false; - for (i1 = 0; i1 < len-1; ++i1) { + for (i1 = 0; i1 < len - 1; ++i1) { p1 = parts[i1]; - if (!p1?.parent) continue; - for (i2 = i1+1; i2 < len; ++i2) { + if (!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) @@ -655,7 +695,8 @@ class Geometry { nreduce++; parts[i1] = p1.parent; parts[i2] = null; - if (p1.parent.vertices.length < 3) console.log('something wrong with parent'); + if (p1.parent.vertices.length < 3) + console.log('something wrong with parent'); foundpair = true; break; } @@ -670,8 +711,10 @@ class Geometry { 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 (i1 = 0, len = parts.length; i1 < len; ++i1) { + if (parts[i1]) + polygons.push(parts[i1]); + } } } } @@ -765,7 +808,7 @@ class Geometry { toMesh(material) { const geometry = this.toBufferGeometry(), - mesh = new Mesh(geometry, material); + mesh = new THREE.Mesh(geometry, material); if (this.matrix) { mesh.position.setFromMatrixPosition(this.matrix); @@ -780,25 +823,26 @@ class Geometry { /** @summary create geometry to make cut on specified axis * @private */ function createNormal(axis_name, pos, size) { - if (!size || (size < 10000)) size = 10000; + if (!size || (size < 10000)) + size = 10000; let vertices; switch (axis_name) { case 'x': - vertices = [new Vertex(pos, -3*size, size, 1, 0, 0), - new Vertex(pos, size, -3*size, 1, 0, 0), + vertices = [new Vertex(pos, -3 * size, size, 1, 0, 0), + new Vertex(pos, size, -3 * size, 1, 0, 0), new Vertex(pos, size, size, 1, 0, 0)]; break; case 'y': - vertices = [new Vertex(-3*size, pos, size, 0, 1, 0), + vertices = [new Vertex(-3 * size, pos, size, 0, 1, 0), new Vertex(size, pos, size, 0, 1, 0), - new Vertex(size, pos, -3*size, 0, 1, 0)]; + new Vertex(size, pos, -3 * size, 0, 1, 0)]; break; // case 'z': default: - vertices = [new Vertex(-3*size, size, pos, 0, 0, 1), - new Vertex(size, -3*size, pos, 0, 0, 1), + vertices = [new Vertex(-3 * size, size, pos, 0, 0, 1), + new Vertex(size, -3 * size, pos, 0, 0, 1), new Vertex(size, size, pos, 0, 0, 1)]; } diff --git a/modules/geom/geobase.mjs b/modules/geom/geobase.mjs index b8d6395b7..02c5cf6fc 100644 --- a/modules/geom/geobase.mjs +++ b/modules/geom/geobase.mjs @@ -1,47 +1,50 @@ -import { DoubleSide, FrontSide, Object3D, Box3, Mesh, InstancedMesh, Vector2, Vector3, Matrix4, - MeshLambertMaterial, MeshBasicMaterial, MeshStandardMaterial, MeshNormalMaterial, - MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, - Color, PerspectiveCamera, Frustum, Raycaster, - ShapeUtils, BufferGeometry, BufferAttribute } from '../three.mjs'; -import { isObject, isFunc, BIT } from '../core.mjs'; +import { isObject, isFunc, isStr, BIT } from '../core.mjs'; +import { getRootColors } from '../base/colors.mjs'; +import { THREE } from '../base/base3d.mjs'; import { createBufferGeometry, createNormal, Vertex as CsgVertex, Geometry as CsgGeometry, Polygon as CsgPolygon } from './csg.mjs'; - -const cfg = { +const _cfg = { GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes CompressComp: true // use faces compression in composite shapes -}; +}, kShapeType = '$$Shape$$'; + +/** @summary Returns or set geometry config values + * @desc Supported 'GradPerSegm' and 'CompressComp' + * @private */ function geoCfg(name, value) { if (value === undefined) - return cfg[name]; + return _cfg[name]; - cfg[name] = value; + _cfg[name] = value; } const kindGeo = 0, // TGeoNode / TGeoShape kindEve = 1, // TEveShape / TEveGeoShapeExtract kindShape = 2, // special kind for single shape handling -/** @summary TGeo-related bits - * @private */ - geoBITS = { - kVisOverride: BIT(0), // volume's vis. attributes are overwritten - kVisNone: BIT(1), // the volume/node is invisible, as well as daughters - kVisThis: BIT(2), // this volume/node is visible - kVisDaughters: BIT(3), // all leaves are visible - kVisOneLevel: BIT(4), // first level daughters are visible (not used) - kVisStreamed: BIT(5), // true if attributes have been streamed - kVisTouched: BIT(6), // true if attributes are changed after closing geom - kVisOnScreen: BIT(7), // true if volume is visible on screen - kVisContainers: BIT(12), // all containers visible - kVisOnly: BIT(13), // just this visible - kVisBranch: BIT(14), // only a given branch visible - kVisRaytrace: BIT(15) // raytracing flag -}, - - clTGeoBBox = 'TGeoBBox', + kGetMesh = 'mesh', // return mesh from createObject3D + kDeleteMesh = 'delete_mesh', // delete mesh in createObject3D + + /** @summary TGeo-related bits + * @private */ + geoBITS = { + kVisOverride: BIT(0), // volume's vis. attributes are overwritten + kVisNone: BIT(1), // the volume/node is invisible, as well as daughters + kVisThis: BIT(2), // this volume/node is visible + kVisDaughters: BIT(3), // all leaves are visible + kVisOneLevel: BIT(4), // first level daughters are visible (not used) + kVisStreamed: BIT(5), // true if attributes have been streamed + kVisTouched: BIT(6), // true if attributes are changed after closing geom + kVisOnScreen: BIT(7), // true if volume is visible on screen + kVisContainers: BIT(12), // all containers visible + kVisOnly: BIT(13), // just this visible + kVisBranch: BIT(14), // only a given branch visible + kVisRaytrace: BIT(15) // raytracing flag + }, + + clTGeoBBox = 'TGeoBBox', clTGeoArb8 = 'TGeoArb8', clTGeoCone = 'TGeoCone', clTGeoConeSeg = 'TGeoConeSeg', @@ -70,28 +73,26 @@ const kindGeo = 0, // TGeoNode / TGeoShape * @private */ function testGeoBit(volume, f) { const att = volume.fGeoAtt; - return att === undefined ? false : ((att & f) !== 0); + return att === undefined ? false : Boolean(att & f); } /** @summary Set fGeoAtt bit * @private */ function setGeoBit(volume, f, value) { - if (volume.fGeoAtt === undefined) return; - volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); + if (volume.fGeoAtt !== undefined) + volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); } /** @summary Toggle fGeoAttBit * @private */ function toggleGeoBit(volume, f) { if (volume.fGeoAtt !== undefined) - volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff); + volume.fGeoAtt ^= f & 0xffffff; } /** @summary Implementation of TGeoVolume::InvisibleAll * @private */ -function setInvisibleAll(volume, flag) { - if (flag === undefined) flag = true; - +function setInvisibleAll(volume, flag = true) { setGeoBit(volume, geoBITS.kVisThis, !flag); // setGeoBit(this, geoBITS.kVisDaughters, !flag); @@ -109,9 +110,10 @@ const _warn_msgs = {}; /** @summary method used to avoid duplication of warnings * @private */ function geoWarn(msg) { - if (_warn_msgs[msg] !== undefined) return; - _warn_msgs[msg] = true; - console.warn(msg); + if (_warn_msgs[msg] === undefined) { + _warn_msgs[msg] = true; + console.warn(msg); + } } /** @summary Analyze TGeo node kind @@ -121,7 +123,8 @@ function geoWarn(msg) { * @return detected node kind * @private */ function getNodeKind(obj) { - if (!isObject(obj)) return -1; + if (!isObject(obj)) + return -1; return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo; } @@ -129,8 +132,10 @@ function getNodeKind(obj) { * @desc Used to count total shapes number in composites * @private */ function countNumShapes(shape) { - if (!shape) return 0; - if (shape._typename !== clTGeoCompositeShape) return 1; + if (!shape) + return 0; + if (shape._typename !== clTGeoCompositeShape) + return 1; return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight); } @@ -146,21 +151,23 @@ function getObjectName(obj) { * @private */ function checkDuplicates(parent, chlds) { if (parent) { - if (parent.$geo_checked) return; + if (parent.$geo_checked) + return; parent.$geo_checked = true; } const names = [], cnts = []; for (let k = 0; k < chlds.length; ++k) { const chld = chlds[k]; - if (!chld?.fName) continue; + if (!chld?.fName) + continue; if (!chld.$geo_suffix) { const indx = names.indexOf(chld.fName); if (indx >= 0) { let cnt = cnts[indx] || 1; - while (names.indexOf(chld.fName+'#'+cnt) >= 0) ++cnt; + while (names.indexOf(chld.fName + '#' + cnt) >= 0) ++cnt; chld.$geo_suffix = '#' + cnt; - cnts[indx] = cnt+1; + cnts[indx] = cnt + 1; } } names.push(getObjectName(chld)); @@ -171,11 +178,11 @@ function checkDuplicates(parent, chlds) { /** @summary Create normal to plane, defined with three points * @private */ function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - const pA = new Vector3(x1, y1, z1), - pB = new Vector3(x2, y2, z2), - pC = new Vector3(x3, y3, z3), - cb = new Vector3(), - ab = new Vector3(); + const 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(); cb.subVectors(pC, pB); ab.subVectors(pA, pB); @@ -199,22 +206,22 @@ class GeometryCreator { constructor(numfaces) { this.nfaces = numfaces; this.indx = 0; - this.pos = new Float32Array(numfaces*9); - this.norm = new Float32Array(numfaces*9); + this.pos = new Float32Array(numfaces * 9); + this.norm = new Float32Array(numfaces * 9); } /** @summary Add face with 3 vertices */ addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { const 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; + 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; } @@ -235,28 +242,28 @@ class GeometryCreator { 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; + 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 (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; + 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; } this.last4 = (indx !== this.indx + 9); @@ -275,27 +282,27 @@ class GeometryCreator { 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; + 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; } 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; + 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; } } @@ -306,19 +313,19 @@ class GeometryCreator { let indx = last - (this.last4 ? 18 : 9); while (indx < last) { - pos[indx+2] = func(pos[indx], pos[indx+1], pos[indx+2]); - indx+=3; + pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); + indx += 3; } } - /** @summary Caclualte normal */ + /** @summary Calculate normal */ calcNormal() { if (!this.cb) { - this.pA = new Vector3(); - this.pB = new Vector3(); - this.pC = new Vector3(); - this.cb = new Vector3(); - this.ab = new Vector3(); + 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.fromArray(this.pos, this.indx - 9); @@ -337,60 +344,58 @@ class GeometryCreator { let indx = this.indx - 9; const 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; + 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; + 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; } } /** @summary Set normal * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ - setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce) { - if (reduce === undefined) reduce = 0; - + setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce = 0) { let indx = this.indx - ((reduce > 0) ? 9 : 18); const 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; + 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; + 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; } } /** @summary Create geometry */ create() { - if (this.nfaces !== this.indx/9) - console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx/9} number of faces`); + if (this.nfaces !== this.indx / 9) + console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx / 9} number of faces`); - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(this.pos, 3)); - geometry.setAttribute('normal', new BufferAttribute(this.norm, 3)); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); + geometry.setAttribute('normal', new THREE.BufferAttribute(this.norm, 3)); return geometry; } @@ -418,7 +423,8 @@ class PolygonsCreator { /** @summary Stop polygon */ stopPolygon() { - if (!this.multi) return; + if (!this.multi) + return; this.multi = 0; console.error('Polygon should be already closed at this moment'); } @@ -432,18 +438,16 @@ class PolygonsCreator { * @desc 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 */ - addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) { - if (reduce === undefined) reduce = 0; - + addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce = 0) { this.v1 = new CsgVertex(x1, y1, z1, 0, 0, 0); this.v2 = (reduce === 1) ? null : new CsgVertex(x2, y2, z2, 0, 0, 0); this.v3 = new CsgVertex(x3, y3, z3, 0, 0, 0); this.v4 = (reduce === 2) ? null : new CsgVertex(x4, y4, z4, 0, 0, 0); - this.reduce = reduce; 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'); let polygon; @@ -453,40 +457,33 @@ class PolygonsCreator { 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 - const last = this.mnormal ? polygon.vertices[polygon.vertices.length-1] : polygon.vertices[0], - comp = this.mnormal ? this.v2 : this.v3; + polygon = this.polygons.at(-1); + // check that last vertex equals to v2 + const last = this.mnormal ? polygon.vertices.at(-1) : polygon.vertices.at(0), + comp = this.mnormal ? this.v2 : this.v3; if (comp.diff(last) > 1e-12) console.error('vertex missmatch when building polygon'); } - const first = this.mnormal ? polygon.vertices[0] : polygon.vertices[polygon.vertices.length-1], + const first = this.mnormal ? polygon.vertices[0] : polygon.vertices.at(-1), next = this.mnormal ? this.v3 : this.v2; - if (next.diff(first) < 1e-12) { - // console.log(`polygon closed!!! nvertices = ${polygon.vertices.length}`); + if (next.diff(first) < 1e-12) this.multi = 0; - } else - if (this.mnormal) + else if (this.mnormal) polygon.vertices.push(this.v3); - else + else polygon.vertices.unshift(this.v2); - - - return; - } - - const polygon = new CsgPolygon(); - - 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; + } else { + const polygon = new CsgPolygon(); + 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); } - - this.polygons.push(polygon); } /** @summary Specify normal for face with 4 vertices @@ -494,28 +491,28 @@ class PolygonsCreator { * reduce has same meaning and should be the same */ setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) { this.v1.setnormal(nx1, ny1, nz1); - if (this.v2) this.v2.setnormal(nx2, ny2, nz2); + this.v2?.setnormal(nx2, ny2, nz2); this.v3.setnormal(nx3, ny3, nz3); - if (this.v4) this.v4.setnormal(nx4, ny4, nz4); + this.v4?.setnormal(nx4, ny4, nz4); } /** @summary Set normal * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) { this.v1.setnormal(nx12, ny12, nz12); - if (this.v2) this.v2.setnormal(nx12, ny12, nz12); + this.v2?.setnormal(nx12, ny12, nz12); this.v3.setnormal(nx34, ny34, nz34); - if (this.v4) this.v4.setnormal(nx34, ny34, nz34); + this.v4?.setnormal(nx34, ny34, nz34); } /** @summary Calculate normal */ calcNormal() { if (!this.cb) { - this.pA = new Vector3(); - this.pB = new Vector3(); - this.pC = new Vector3(); - this.cb = new Vector3(); - this.ab = new Vector3(); + 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); @@ -538,17 +535,19 @@ class PolygonsCreator { /** @summary Set normal */ setNormal(nx, ny, nz) { this.v1.setnormal(nx, ny, nz); - if (this.v2) this.v2.setnormal(nx, ny, nz); + this.v2?.setnormal(nx, ny, nz); this.v3.setnormal(nx, ny, nz); - if (this.v4) this.v4.setnormal(nx, ny, nz); + this.v4?.setnormal(nx, ny, nz); } /** @summary Recalculate Z with provided func */ recalcZ(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); + 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); + if (this.v4) + this.v4.z = func(this.v4.x, this.v4.y, this.v4.z); } /** @summary Create geometry @@ -561,25 +560,32 @@ class PolygonsCreator { // ================= all functions to create geometry =================================== -/** @summary Creates cube geometrey +/** @summary Creates cube geometry * @private */ function createCubeBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + if (faces_limit < 0) + return 12; const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ, 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(-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, -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); - 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(); } @@ -591,12 +597,12 @@ function create8edgesBuffer(v, faces_limit) { creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12); for (let n = 0; n < indicies.length; n += 4) { - const 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]); + const 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) @@ -608,44 +614,47 @@ function create8edgesBuffer(v, faces_limit) { return creator.create(); } -/** @summary Creates PARA geometrey +/** @summary Creates PARA geometry * @private */ function createParaBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + if (faces_limit < 0) + return 12; const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, 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]; + -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); } -/** @summary Creates Ttrapezoid geometrey +/** @summary Creates trapezoid geometry * @private */ function createTrapezoidBuffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + if (faces_limit < 0) + return 12; let y1, y2; if (shape._typename === clTGeoTrd1) y1 = y2 = shape.fDY; - else { - y1 = shape.fDy1; y2 = shape.fDy2; + else { + y1 = shape.fDy1; + y2 = shape.fDy2; } const v = [ -shape.fDx1, y1, -shape.fDZ, - shape.fDx1, y1, -shape.fDZ, - 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, + shape.fDx2, -y2, shape.fDZ, -shape.fDx2, -y2, shape.fDZ ]; @@ -653,10 +662,11 @@ function createTrapezoidBuffer(shape, faces_limit) { } -/** @summary Creates arb8 geometrey +/** @summary Creates arb8 geometry * @private */ function createArb8Buffer(shape, faces_limit) { - if (faces_limit < 0) return 12; + if (faces_limit < 0) + return 12; const vertices = [ shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ, @@ -668,21 +678,23 @@ function createArb8Buffer(shape, faces_limit) { 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, 3, 7, 4, 4, 0, 3, - 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6, - 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; + indicies = [ + 4, 7, 6, 6, 5, 4, 3, 7, 4, 4, 0, 3, + 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6, + 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; // detect same vertices on both Z-layers - for (let side = 0; side < vertices.length; side += vertices.length/2) { - for (let n1 = side; n1 < side + vertices.length/2 - 3; n1+=3) { - for (let 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 (let k=0; k<indicies.length; ++k) - if (indicies[k] === n2/3) indicies[k] = n1/3; + for (let side = 0; side < vertices.length; side += vertices.length / 2) { + for (let n1 = side; n1 < side + vertices.length / 2 - 3; n1 += 3) { + for (let 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 (let k = 0; k < indicies.length; ++k) { + if (indicies[k] === n2 / 3) + indicies[k] = n1 / 3; } + } } } } @@ -691,13 +703,13 @@ function createArb8Buffer(shape, faces_limit) { let numfaces = 0; for (let k = 0; k < indicies.length; k += 3) { - const 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]; + const 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]) || + 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; + indicies[k] = indicies[k + 1] = indicies[k + 2] = -1; else { map.push(id1, id2, id3); numfaces++; @@ -708,53 +720,54 @@ function createArb8Buffer(shape, faces_limit) { for (let n = 0; n < indicies.length; n += 6) { const 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; + 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; let 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 Vector3(0, 0, 1); + norm = new THREE.Vector3(0, 0, 1); else if (n === 30) - norm = new Vector3(0, 0, -1); + norm = new THREE.Vector3(0, 0, -1); else { - const norm1 = produceNormal(vertices[i1], vertices[i1+1], vertices[i1+2], - vertices[i2], vertices[i2+1], vertices[i2+2], - vertices[i3], vertices[i3+1], vertices[i3+2]); + const norm1 = produceNormal(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(); - const norm2 = produceNormal(vertices[i4], vertices[i4+1], vertices[i4+2], - vertices[i5], vertices[i5+1], vertices[i5+2], - vertices[i6], vertices[i6+1], vertices[i6+2]); + const norm2 = produceNormal(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; + if (norm1.distanceToSquared(norm2) < 1e-12) + norm = norm1; } } 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.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 { 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.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.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(); } } @@ -763,7 +776,7 @@ function createArb8Buffer(shape, faces_limit) { return creator.create(); } -/** @summary Creates sphere geometrey +/** @summary Creates sphere geometry * @private */ function createSphereBuffer(shape, faces_limit) { const radius = [shape.fRmax, shape.fRmin], @@ -779,8 +792,8 @@ function createSphereBuffer(shape, faces_limit) { const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; if (fact > 1.0) { - widthSegments = Math.max(4, Math.floor(widthSegments/Math.sqrt(fact))); - heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(fact))); + widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); + heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); } } @@ -790,33 +803,41 @@ function createSphereBuffer(shape, faces_limit) { const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4), epsilon = 1e-10; - if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + if (faces_limit < 0) + return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; - const _sinp = new Float32Array(widthSegments+1), - _cosp = new Float32Array(widthSegments+1), - _sint = new Float32Array(heightSegments+1), - _cost = new Float32Array(heightSegments+1); + const _sinp = new Float32Array(widthSegments + 1), + _cosp = new Float32Array(widthSegments + 1), + _sint = new Float32Array(heightSegments + 1), + _cost = new Float32Array(heightSegments + 1); for (let n = 0; n <= heightSegments; ++n) { - const theta = (thetaStart + thetaLength/heightSegments*n)*Math.PI/180; + const theta = (thetaStart + thetaLength / heightSegments * n) * Math.PI / 180; _sint[n] = Math.sin(theta); _cost[n] = Math.cos(theta); } for (let n = 0; n <= widthSegments; ++n) { - const phi = (phiStart + phiLength/widthSegments*n)*Math.PI/180; + const 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; } + if (Math.abs(_sint[0]) <= epsilon) { + numoutside -= widthSegments; + numtop = 0; + } + if (Math.abs(_sint[heightSegments]) <= epsilon) { + numoutside -= widthSegments; + numbottom = 0; + } const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut, creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); for (let side = 0; side < 2; ++side) { - if ((side === 1) && noInside) break; + if ((side === 1) && noInside) + break; const r = radius[side], s = (side === 0) ? 1 : -1, @@ -826,22 +847,24 @@ function createSphereBuffer(shape, faces_limit) { for (let k = 0; k < heightSegments; ++k) { const k1 = k + d1, k2 = k + d2; let skip = 0; - if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else - if (Math.abs(_sint[k2]) <= epsilon) skip = 2; + if (Math.abs(_sint[k1]) <= epsilon) + skip = 1; + else if (Math.abs(_sint[k2]) <= epsilon) + skip = 2; for (let 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); + 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); + 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); } } } @@ -853,10 +876,10 @@ function createSphereBuffer(shape, faces_limit) { d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; for (let 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, + 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(); } @@ -865,16 +888,16 @@ function createSphereBuffer(shape, faces_limit) { // cut left/right sides if (phiLength < 360) { - for (let side=0; side<=widthSegments; side+=widthSegments) { + for (let side = 0; side <= widthSegments; side += widthSegments) { const ss = _sinp[side], cc = _cosp[side], - d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; + d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; - for (let k=0; k<heightSegments; ++k) { + for (let 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], + 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(); } @@ -884,7 +907,7 @@ function createSphereBuffer(shape, faces_limit) { return creator.create(); } -/** @summary Creates tube geometrey +/** @summary Creates tube geometry * @private */ function createTubeBuffer(shape, faces_limit) { let outerR, innerR; // inner/outer tube radius @@ -904,7 +927,7 @@ function createTubeBuffer(shape, faces_limit) { thetaLength = shape.fPhi2 - shape.fPhi1; } - const radiusSegments = Math.max(4, Math.round(thetaLength/cfg.GradPerSegm)); + const radiusSegments = Math.max(4, Math.round(thetaLength / _cfg.GradPerSegm)); // external surface let numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); @@ -914,82 +937,89 @@ function createTubeBuffer(shape, faces_limit) { numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); // upper cap - if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1); + 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 (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; + if (faces_limit < 0) + return numfaces; - const phi0 = thetaStart*Math.PI/180, - dphi = thetaLength/radiusSegments*Math.PI/180, - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); + const phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + _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); + _cos[seg] = Math.cos(phi0 + seg * dphi); + _sin[seg] = Math.sin(phi0 + seg * dphi); } const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), - calcZ = (shape._typename !== clTGeoCtub) - ? null - : (x, y, z) => { - const arr = (z < 0) ? shape.fNlow : shape.fNhigh; - return ((z < 0) ? -shape.fDz : shape.fDz) - (x*arr[0] + y*arr[1]) / arr[2]; - }; + calcZ = (shape._typename !== clTGeoCtub) ? null : (x, y, z) => { + const 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 (let side = 0; side < 2; ++side) { - if ((side === 1) && !hasrmin) break; + if ((side === 1) && !hasrmin) + break; const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side; let nxy = 1, nz = 0; if (R[0] !== R[1]) { - const angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ); + const 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; } + if (side === 1) { + nxy *= -1; + nz *= -1; + } const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0); for (let 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); + 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); + 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, + 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 (let side = 0; side < 2; ++side) { - if (outerR[side] <= 0) continue; + if (outerR[side] <= 0) + continue; - const d1 = side, d2 = 1- side, - sign = (side === 0) ? 1 : -1, - reduce = (innerR[side] <= 0) ? 2 : 0; + const 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 (let 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); + 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(); @@ -1006,55 +1036,60 @@ function createTubeBuffer(shape, faces_limit) { 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); + (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)); + (outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0)); - if (calcZ) creator.recalcZ(calcZ); + if (calcZ) + creator.recalcZ(calcZ); creator.calcNormal(); } return creator.create(); } -/** @summary Creates eltu geometrey +/** @summary Creates eltu geometry * @private */ function createEltuBuffer(shape, faces_limit) { - const radiusSegments = Math.max(4, Math.round(360/cfg.GradPerSegm)); + const radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)); - if (faces_limit < 0) return radiusSegments*4; + if (faces_limit < 0) + return radiusSegments * 4; // calculate all sin/cos tables in advance - const x = new Float32Array(radiusSegments+1), - y = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - const phi = seg/radiusSegments*2*Math.PI; - x[seg] = shape.fRmin*Math.cos(phi); - y[seg] = shape.fRmax*Math.sin(phi); + const x = new Float32Array(radiusSegments + 1), + y = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + const phi = seg / radiusSegments * 2 * Math.PI; + x[seg] = shape.fRmin * Math.cos(phi); + y[seg] = shape.fRmax * Math.sin(phi); } - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments*4); + const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments * 4); let nx1, ny1, nx2 = 1, ny2 = 0; // create tube faces for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(x[seg], y[seg], +shape.fDZ, + 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); + 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; - const dist = Math.sqrt(nx2**2 + ny2**2); - nx2 = nx2 / dist; ny2 = ny2/dist; + nx1 = nx2; + ny1 = ny2; + nx2 = x[seg + 1] * shape.fRmax / shape.fRmin; + ny2 = y[seg + 1] * shape.fRmin / shape.fRmax; + const dist = Math.sqrt(nx2 ** 2 + ny2 ** 2); + nx2 /= dist; + ny2 /= dist; creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); } @@ -1062,10 +1097,10 @@ function createEltuBuffer(shape, faces_limit) { // create top/bottom sides for (let side = 0; side < 2; ++side) { const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; - for (let 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); + for (let 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); } } @@ -1073,59 +1108,71 @@ function createEltuBuffer(shape, faces_limit) { return creator.create(); } -/** @summary Creates torus geometrey +/** @summary Creates torus geometry * @private */ function createTorusBuffer(shape, faces_limit) { const radius = shape.fR; - let radialSegments = Math.max(6, Math.round(360/cfg.GradPerSegm)), - tubularSegments = Math.max(8, Math.round(shape.fDphi/cfg.GradPerSegm)), + let radialSegments = Math.max(6, Math.round(360 / _cfg.GradPerSegm)), + tubularSegments = Math.max(8, Math.round(shape.fDphi / _cfg.GradPerSegm)), numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - if (faces_limit < 0) return numfaces; + 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)); + 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)); } - const _sinr = new Float32Array(radialSegments+1), - _cosr = new Float32Array(radialSegments+1), - _sint = new Float32Array(tubularSegments+1), - _cost = new Float32Array(tubularSegments+1); + const _sinr = new Float32Array(radialSegments + 1), + _cosr = new Float32Array(radialSegments + 1), + _sint = new Float32Array(tubularSegments + 1), + _cost = new Float32Array(tubularSegments + 1); for (let n = 0; n <= radialSegments; ++n) { - _sinr[n] = Math.sin(n/radialSegments*2*Math.PI); - _cosr[n] = Math.cos(n/radialSegments*2*Math.PI); + _sinr[n] = Math.sin(n / radialSegments * 2 * Math.PI); + _cosr[n] = Math.cos(n / radialSegments * 2 * Math.PI); } for (let t = 0; t <= tubularSegments; ++t) { - const angle = (shape.fPhi1 + shape.fDphi*t/tubularSegments)/180*Math.PI; + const angle = (shape.fPhi1 + shape.fDphi * t / tubularSegments) / 180 * Math.PI; _sint[t] = Math.sin(angle); _cost[t] = Math.cos(angle); } const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), // use vectors for normals calculation - p1 = new Vector3(), p2 = new Vector3(), p3 = new Vector3(), p4 = new Vector3(), - n1 = new Vector3(), n2 = new Vector3(), n3 = new Vector3(), n4 = new Vector3(), - center1 = new Vector3(), center2 = new Vector3(); + 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 (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; + if ((side > 0) && (shape.fRmin <= 0)) + break; const tube = (side > 0) ? shape.fRmin : shape.fRmax, d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; for (let t = 0; t < tubularSegments; ++t) { const 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]; + center1.x = radius * _cost[t1]; + center1.y = radius * _sint[t1]; + center2.x = radius * _cost[t2]; + center2.y = radius * _sint[t2]; for (let 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]; + 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, @@ -1137,10 +1184,10 @@ function createTorusBuffer(shape, faces_limit) { 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); + 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); } } } @@ -1148,15 +1195,15 @@ function createTorusBuffer(shape, faces_limit) { if (shape.fDphi !== 360) { for (let t = 0; t <= tubularSegments; t += tubularSegments) { const 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; + d1 = t > 0 ? 0 : 1, d2 = 1 - d1, + skip = shape.fRmin > 0 ? 0 : 1, + nsign = t > 0 ? 1 : -1; for (let 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); + 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); } } } @@ -1165,70 +1212,72 @@ function createTorusBuffer(shape, faces_limit) { } -/** @summary Creates polygon geometrey +/** @summary Creates polygon geometry * @private */ function createPolygonBuffer(shape, faces_limit) { const thetaStart = shape.fPhi1, - thetaLength = shape.fDphi; + thetaLength = shape.fDphi; let radiusSegments, factor; if (shape._typename === clTGeoPgon) { radiusSegments = shape.fNedges; - factor = 1.0 / Math.cos(Math.PI/180 * thetaLength / radiusSegments / 2); + factor = 1.0 / Math.cos(Math.PI / 180 * thetaLength / radiusSegments / 2); } else { - radiusSegments = Math.max(5, Math.round(thetaLength/cfg.GradPerSegm)); + radiusSegments = Math.max(5, Math.round(thetaLength / _cfg.GradPerSegm)); factor = 1; } - const usage = new Int16Array(2*shape.fNz); + const usage = new Int16Array(2 * shape.fNz); let numusedlayers = 0, hasrmin = false; for (let layer = 0; layer < shape.fNz; ++layer) - if (shape.fRmin[layer] > 0) hasrmin = true; + hasrmin = hasrmin || (shape.fRmin[layer] > 0); // return very rough estimation, number of faces may be much less - if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz-1); + if (faces_limit < 0) + return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz - 1); // coordinate of point on cut edge (x,z) const pnts = (thetaLength === 360) ? null : []; - // first analyse levels - if we need to create all of them + // first analyze levels - if we need to create all of them for (let side = 0; side < 2; ++side) { const rside = (side === 0) ? 'fRmax' : 'fRmin'; - for (let layer=0; layer < shape.fNz; ++layer) { + for (let layer = 0; layer < shape.fNz; ++layer) { // first create points for the layer const layerz = shape.fZ[layer], rad = shape[rside][layer]; - usage[layer*2+side] = 0; + 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))) { + 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; + usage[layer * 2 + side] = 1; numusedlayers++; } if (pnts !== null) { if (side === 0) - pnts.push(new Vector2(factor*rad, layerz)); - else if (rad < shape.fRmax[layer]) - pnts.unshift(new Vector2(factor*rad, layerz)); + pnts.push(new THREE.Vector2(factor * rad, layerz)); + else if (rad < shape.fRmax[layer]) + pnts.unshift(new THREE.Vector2(factor * rad, layerz)); } } } - let 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); + let 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); let cut_faces = null; @@ -1236,27 +1285,28 @@ function createPolygonBuffer(shape, faces_limit) { if (pnts.length === shape.fNz * 2) { // special case - all layers are there, create faces ourself cut_faces = []; - for (let layer = shape.fNz-1; layer > 0; --layer) { - if (shape.fZ[layer] === shape.fZ[layer-1]) continue; - const right = 2*shape.fNz - 1 - layer; + for (let layer = shape.fNz - 1; layer > 0; --layer) { + if (shape.fZ[layer] === shape.fZ[layer - 1]) + continue; + const right = 2 * shape.fNz - 1 - layer; cut_faces.push([right, layer - 1, layer]); - cut_faces.push([right, right + 1, layer-1]); + cut_faces.push([right, right + 1, layer - 1]); } } else { // let three.js calculate our faces - // console.log(`triangulate polygon ${shape.fShapeId}`); - cut_faces = ShapeUtils.triangulateShape(pnts, []); + cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); } - numfaces += cut_faces.length*2; + numfaces += cut_faces.length * 2; } - const phi0 = thetaStart*Math.PI/180, dphi = thetaLength/radiusSegments*Math.PI/180, - // calculate all sin/cos tables in advance - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); + const phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + // calculate all sin/cos tables in advance + _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); + _cos[seg] = Math.cos(phi0 + seg * dphi); + _sin[seg] = Math.sin(phi0 + seg * dphi); } const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); @@ -1265,52 +1315,58 @@ function createPolygonBuffer(shape, faces_limit) { for (let side = 0; side < 2; ++side) { const rside = (side === 0) ? 'fRmax' : 'fRmin', d1 = 1 - side, d2 = side; - let z1 = shape.fZ[0], r1 = factor*shape[rside][0]; + let z1 = shape.fZ[0], r1 = factor * shape[rside][0]; for (let layer = 0; layer < shape.fNz; ++layer) { - if (usage[layer*2+side] === 0) continue; + if (usage[layer * 2 + side] === 0) + continue; - const z2 = shape.fZ[layer], r2 = factor*shape[rside][layer]; + const z2 = shape.fZ[layer], r2 = factor * shape[rside][layer]; let nxy = 1, nz = 0; if ((r2 !== r1)) { - const angle = Math.atan2((r2-r1), (z2-z1)); + const angle = Math.atan2((r2 - r1), (z2 - z1)); nxy = Math.cos(angle); nz = Math.sin(angle); } - if (side > 0) { nxy*=-1; nz*=-1; } + if (side > 0) { + nxy *= -1; + nz *= -1; + } for (let 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); + 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; + z1 = z2; + r1 = r2; } } // add top/bottom - for (let layer = 0; layer < shape.fNz; layer += (shape.fNz-1)) { - const rmin = factor*shape.fRmin[layer], rmax = factor*shape.fRmax[layer]; + for (let layer = 0; layer < shape.fNz; layer += (shape.fNz - 1)) { + const rmin = factor * shape.fRmin[layer], rmax = factor * shape.fRmax[layer]; - if (rmin === rmax) continue; + if (rmin === rmax) + continue; const layerz = shape.fZ[layer], d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1, - normalz = (layer === 0) ? -1: 1; + normalz = (layer === 0) ? -1 : 1; if (!hasrmin && !cut_faces) creator.startPolygon(layer > 0); for (let 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, + 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); } @@ -1321,10 +1377,10 @@ function createPolygonBuffer(shape, faces_limit) { if (cut_faces) { for (let seg = 0; seg <= radiusSegments; seg += radiusSegments) { const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; - for (let n=0; n<cut_faces.length; ++n) { + for (let n = 0; n < cut_faces.length; ++n) { const a = pnts[cut_faces[n][0]], - b = pnts[cut_faces[n][d1]], - c = pnts[cut_faces[n][d2]]; + 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, @@ -1338,20 +1394,21 @@ function createPolygonBuffer(shape, faces_limit) { return creator.create(); } -/** @summary Creates xtru geometrey +/** @summary Creates xtru geometry * @private */ function createXtruBuffer(shape, faces_limit) { - let nfaces = (shape.fNz-1) * shape.fNvert * 2; + let nfaces = (shape.fNz - 1) * shape.fNvert * 2; - if (faces_limit < 0) return nfaces + shape.fNvert*3; + if (faces_limit < 0) + return nfaces + shape.fNvert * 3; // create points const pnts = []; for (let vert = 0; vert < shape.fNvert; ++vert) - pnts.push(new Vector2(shape.fX[vert], shape.fY[vert])); + pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); - let faces = ShapeUtils.triangulateShape(pnts, []); - if (faces.length < pnts.length-2) { + let faces = THREE.ShapeUtils.triangulateShape(pnts, []); + if (faces.length < pnts.length - 2) { geoWarn(`Problem with XTRU shape ${shape.fName} with ${pnts.length} vertices`); faces = []; } else @@ -1359,14 +1416,14 @@ function createXtruBuffer(shape, faces_limit) { const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(nfaces); - for (let layer = 0; layer < shape.fNz-1; ++layer) { + for (let layer = 0; layer < shape.fNz - 1; ++layer) { const 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]; + 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) { - const vert2 = (vert1+1) % shape.fNvert; + const 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, @@ -1375,7 +1432,7 @@ function createXtruBuffer(shape, faces_limit) { } } - for (let layer = 0; layer <= shape.fNz-1; layer += (shape.fNz-1)) { + for (let layer = 0; layer <= shape.fNz - 1; layer += (shape.fNz - 1)) { const z = shape.fZ[layer], scale = shape.fScale[layer], x0 = shape.fX0[layer], y0 = shape.fY0[layer]; @@ -1395,147 +1452,165 @@ function createXtruBuffer(shape, faces_limit) { return creator.create(); } -/** @summary Creates para geometrey +/** @summary Creates para geometry * @private */ function createParaboloidBuffer(shape, faces_limit) { - let radiusSegments = Math.max(4, Math.round(360/cfg.GradPerSegm)), + let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), heightSegments = 30; if (faces_limit > 0) { - const fact = 2*radiusSegments*(heightSegments+1) / faces_limit; + const fact = 2 * radiusSegments * (heightSegments + 1) / faces_limit; if (fact > 1.0) { - radiusSegments = Math.max(5, Math.floor(radiusSegments/Math.sqrt(fact))); - heightSegments = Math.max(5, Math.floor(heightSegments/Math.sqrt(fact))); + radiusSegments = Math.max(5, Math.floor(radiusSegments / Math.sqrt(fact))); + heightSegments = Math.max(5, Math.floor(heightSegments / Math.sqrt(fact))); } } const rmin = shape.fRlo, rmax = shape.fRhi; - let numfaces = (heightSegments+1) * radiusSegments*2; + let numfaces = (heightSegments + 1) * radiusSegments * 2; - if (rmin === 0) numfaces -= radiusSegments*2; // complete layer - if (rmax === 0) numfaces -= radiusSegments*2; // complete layer + if (rmin === 0) + numfaces -= radiusSegments * 2; // complete layer + if (rmax === 0) + numfaces -= radiusSegments * 2; // complete layer - if (faces_limit < 0) return numfaces; + if (faces_limit < 0) + return numfaces; let zmin = -shape.fDZ, zmax = shape.fDZ; // 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; - - - const ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax), + if (shape.fA >= 0) + zmin = Math.max(zmin, shape.fB); + else + zmax = Math.min(shape.fB, zmax); - // calculate all sin/cos tables in advance - _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); - _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); + const ttmin = Math.atan2(zmin, rmin), + ttmax = Math.atan2(zmax, rmax), + // calculate all sin/cos tables in advance + _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); } const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); let lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; for (let layer = 0; layer <= heightSegments + 1; ++layer) { - let layerz = 0, radius = 0, nxy = 0, nz = -1; + if ((layer === 0) && (rmin === 0)) + continue; - if ((layer === 0) && (rmin === 0)) continue; + if ((layer === heightSegments + 1) && (lastr === 0)) + break; - if ((layer === heightSegments + 1) && (lastr === 0)) break; + let layerz, radius; switch (layer) { - case 0: layerz = zmin; radius = rmin; break; - case heightSegments: layerz = zmax; radius = rmax; break; - case heightSegments + 1: layerz = zmax; radius = 0; break; + case 0: + layerz = zmin; + radius = rmin; + break; + case heightSegments: + layerz = zmax; + radius = rmax; + break; + case heightSegments + 1: + layerz = zmax; + radius = 0; + break; default: { - const tt = Math.tan(ttmin + (ttmax-ttmin) * layer / heightSegments), - delta = tt**2 - 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; + const tt = Math.tan(ttmin + (ttmax - ttmin) * layer / heightSegments), + delta = tt ** 2 - 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; + break; } } - nxy = shape.fA * radius; - nz = (shape.fA > 0) ? -1 : 1; - - const skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0); + const nxy = shape.fA * radius, + nz = (shape.fA > 0) ? -1 : 1, + skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0); for (let 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); + 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); + 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; + lastz = layerz; + lastr = radius; + lastnxy = nxy; + lastnz = nz; } return creator.create(); } -/** @summary Creates hype geometrey +/** @summary Creates hype geometry * @private */ function createHypeBuffer(shape, faces_limit) { if ((shape.fTin === 0) && (shape.fTout === 0)) return createTubeBuffer(shape, faces_limit); - let radiusSegments = Math.max(4, Math.round(360/cfg.GradPerSegm)), + let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), heightSegments = 30, numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); - if (faces_limit < 0) return numfaces; + 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))); + 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 - const _sin = new Float32Array(radiusSegments+1), - _cos = new Float32Array(radiusSegments+1); - for (let seg=0; seg<=radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI); - _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI); + const _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); } const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); // in-out side for (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; + if ((side > 0) && (shape.fRmin <= 0)) + break; const r0 = (side > 0) ? shape.fRmin : shape.fRmax, tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, - d1 = 1- side, d2 = 1 - d1; + d1 = 1 - side, d2 = 1 - d1; // vertical layers for (let layer = 0; layer < heightSegments; ++layer) { - const z1 = -shape.fDz + layer/heightSegments*2*shape.fDz, - z2 = -shape.fDz + (layer+1)/heightSegments*2*shape.fDz, - r1 = Math.sqrt(r0**2 + tsq*z1**2), - r2 = Math.sqrt(r0**2 + tsq*z2**2); + const z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, + z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, + r1 = Math.sqrt(r0 ** 2 + tsq * z1 ** 2), + r2 = Math.sqrt(r0 ** 2 + tsq * z2 ** 2); for (let 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.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(); } } @@ -1544,29 +1619,30 @@ function createHypeBuffer(shape, faces_limit) { // add caps for (let layer = 0; layer < 2; ++layer) { const z = (layer === 0) ? shape.fDz : -shape.fDz, - r1 = Math.sqrt(shape.fRmax**2 + shape.fToutsq*z**2), - r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin**2 + shape.fTinsq*z**2) : 0, + r1 = Math.sqrt(shape.fRmax ** 2 + shape.fToutsq * z ** 2), + r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin ** 2 + shape.fTinsq * z ** 2) : 0, skip = (shape.fRmin > 0) ? 0 : 1, d1 = 1 - layer, d2 = 1 - d1; for (let 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], 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(); } -/** @summary Creates tessalated geometrey +/** @summary Creates tessellated geometry * @private */ function createTessellatedBuffer(shape, faces_limit) { let numfaces = 0; for (let i = 0; i < shape.fFacets.length; ++i) numfaces += (shape.fFacets[i].fNvert === 4) ? 2 : 1; - if (faces_limit < 0) return numfaces; + if (faces_limit < 0) + return numfaces; const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); @@ -1592,20 +1668,27 @@ function createTessellatedBuffer(shape, faces_limit) { /** @summary Creates Matrix4 from TGeoMatrix * @private */ function createMatrix(matrix) { - if (!matrix) return null; + if (!matrix) + return null; let translation, rotation, scale; switch (matrix._typename) { - case 'TGeoTranslation': translation = matrix.fTranslation; break; - case 'TGeoRotation': rotation = matrix.fRotationMatrix; break; - case 'TGeoScale': scale = matrix.fScale; break; + 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 - // eslint-disable-next-line no-fallthrough + // eslint-disable-next-line no-fallthrough case 'TGeoCombiTrans': translation = matrix.fTranslation; - if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix; + rotation = matrix.fRotation?.fRotationMatrix; break; case 'TGeoHMatrix': translation = matrix.fTranslation; @@ -1618,22 +1701,23 @@ function createMatrix(matrix) { console.warn(`unsupported matrix ${matrix._typename}`); } - if (!translation && !rotation && !scale) return null; + if (!translation && !rotation && !scale) + return null; - const res = new Matrix4(); + const 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, - 0, 0, 0, 1); + 0, 0, 0, 1); } if (translation) res.setPosition(translation[0], translation[1], translation[2]); if (scale) - res.scale(new Vector3(scale[0], scale[1], scale[2])); + res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); return res; } @@ -1647,75 +1731,67 @@ function getNodeMatrix(kind, node) { if (kind === kindEve) { // special handling for EVE nodes - matrix = new Matrix4(); - + matrix = new THREE.Matrix4(); if (node.fTrans) { 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); + 0, 0, 0, 1); // second - set position with proper sign matrix.setPosition(node.fTrans[12], node.fTrans[13], node.fTrans[14]); } } else if (node.fMatrix) matrix = createMatrix(node.fMatrix); - else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) { - const kPatternReflected = BIT(14); - if ((node.fFinder.fBits & kPatternReflected) !== 0) - geoWarn('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': { - const _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; - - matrix = new Matrix4(); - - switch (node.fFinder._typename[node.fFinder._typename.length-1]) { - case 'X': matrix.setPosition(_shift, 0, 0); break; - case 'Y': matrix.setPosition(0, _shift, 0); break; - case 'Z': matrix.setPosition(0, 0, _shift); break; - } - break; - } - - case 'TGeoPatternCylPhi': { - const phi = (Math.PI/180)*(node.fFinder.fStart+(node.fIndex+0.5)*node.fFinder.fStep), - _cos = Math.cos(phi), _sin = Math.sin(phi); - - matrix = new Matrix4(); - - matrix.set(_cos, -_sin, 0, 0, - _sin, _cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - break; - } - - case 'TGeoPatternCylR': + else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) { + const kPatternReflected = BIT(14), + finder = node.fFinder, + typ = finder._typename; + if (finder.fBits & kPatternReflected) + geoWarn(`Unsupported reflected pattern ${typ}`); + if (typ.indexOf('TGeoPattern')) + geoWarn(`Abnormal pattern type ${typ}`); + const part = typ.slice(11); + matrix = new THREE.Matrix4(); + switch (part) { + case 'X': + case 'Y': + case 'Z': + case 'ParaX': + case 'ParaY': + case 'ParaZ': { + const _shift = finder.fStart + (node.fIndex + 0.5) * finder.fStep; + switch (part.at(-1)) { + case 'X': matrix.setPosition(_shift, 0, 0); break; + case 'Y': matrix.setPosition(0, _shift, 0); break; + case 'Z': matrix.setPosition(0, 0, _shift); break; + } + break; + } + case 'CylPhi': { + const phi = (Math.PI / 180) * (finder.fStart + (node.fIndex + 0.5) * finder.fStep), + _cos = Math.cos(phi), _sin = Math.sin(phi); + matrix.set(_cos, -_sin, 0, 0, + _sin, _cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + break; + } + case 'CylR': // seems to be, require no transformation - matrix = new Matrix4(); break; - - case 'TGeoPatternTrapZ': { - const dz = node.fFinder.fStart + (node.fIndex+0.5)*node.fFinder.fStep; - matrix = new Matrix4(); - matrix.setPosition(node.fFinder.fTxz*dz, node.fFinder.fTyz*dz, dz); - break; - } - - default: - geoWarn(`Unsupported pattern type ${node.fFinder._typename}`); - break; + case 'TrapZ': { + const dz = finder.fStart + (node.fIndex + 0.5) * finder.fStep; + matrix.setPosition(finder.fTxz * dz, finder.fTyz * dz, dz); + break; + } + // case 'CylR': break; + // case 'SphR': break; + // case 'SphTheta': break; + // case 'SphPhi': break; + // case 'Honeycomb': break; + default: + geoWarn(`Unsupported pattern type ${typ}`); + break; } } @@ -1723,10 +1799,11 @@ function getNodeMatrix(kind, node) { } /** @summary Returns number of faces for provided geometry - * @param {Object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons + * @param geom - can be BufferGeometry, CsgGeometry or interim array of polygons * @private */ function numGeometryFaces(geom) { - if (!geom) return 0; + if (!geom) + return 0; if (geom instanceof CsgGeometry) return geom.tree.numPolygons(); @@ -1743,7 +1820,8 @@ function numGeometryFaces(geom) { * @param {Object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons * @private */ function numGeometryVertices(geom) { - if (!geom) return 0; + if (!geom) + return 0; if (geom instanceof CsgGeometry) return geom.tree.numPolygons() * 3; @@ -1751,14 +1829,14 @@ function numGeometryVertices(geom) { if (geom.polygons) return geom.polygons.length * 4; - const attr = geom.getAttribute('position'); - return attr?.count || 0; + return geom.getAttribute('position')?.count || 0; } /** @summary Returns geometry bounding box * @private */ function geomBoundingBox(geom) { - if (!geom) return null; + if (!geom) + return null; let polygons = null; @@ -1768,7 +1846,7 @@ function geomBoundingBox(geom) { polygons = geom.polygons; if (polygons !== null) { - const box = new Box3(); + const box = new THREE.Box3(); for (let n = 0; n < polygons.length; ++n) { const polygon = polygons[n], nvert = polygon.vertices.length; for (let k = 0; k < nvert; ++k) @@ -1787,10 +1865,11 @@ function geomBoundingBox(geom) { * @desc Just big-enough triangle to make BSP calculations * @private */ function createHalfSpace(shape, geom) { - if (!shape?.fN || !shape?.fP) return null; + if (!shape?.fN || !shape?.fP) + return null; - const vertex = new Vector3(shape.fP[0], shape.fP[1], shape.fP[2]), - normal = new Vector3(shape.fN[0], shape.fN[1], shape.fN[2]); + const vertex = new THREE.Vector3(shape.fP[0], shape.fP[1], shape.fP[2]), + normal = new THREE.Vector3(shape.fN[0], shape.fN[1], shape.fN[2]); normal.normalize(); @@ -1798,51 +1877,37 @@ function createHalfSpace(shape, geom) { if (geom) { // using real size of other geometry, we probably improve precision const box = geomBoundingBox(geom); - if (box) sz = box.getSize(new Vector3()).length() * 1000; - } - - const v0 = new Vector3(-sz, -sz/2, 0), - v1 = new Vector3(0, sz, 0), - v2 = new Vector3(sz, -sz/2, 0), - v3 = new Vector3(0, 0, -sz), - geometry = new BufferGeometry(), - positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z, - v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z, - v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, - v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]); - geometry.setAttribute('position', new BufferAttribute(positions, 3)); + if (box) + sz = box.getSize(new THREE.Vector3()).length() * 1000; + } + + const v0 = new THREE.Vector3(-sz, -sz / 2, 0), + v1 = new THREE.Vector3(0, sz, 0), + v2 = new THREE.Vector3(sz, -sz / 2, 0), + v3 = new THREE.Vector3(0, 0, -sz), + geometry = new THREE.BufferGeometry(), + positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z, + v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z, + v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, + v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.computeVertexNormals(); geometry.lookAt(normal); geometry.computeVertexNormals(); for (let k = 0; k < positions.length; k += 3) { - positions[k] = positions[k] + vertex.x; - positions[k+1] = positions[k+1] + vertex.y; - positions[k+2] = positions[k+2] + vertex.z; + positions[k] += vertex.x; + positions[k + 1] += vertex.y; + positions[k + 2] += vertex.z; } return geometry; } -/** @summary Returns number of faces for provided geometry - * @param geom - can be BufferGeometry, CsgGeometry or interim array of polygons - * @private */ -function countGeometryFaces(geom) { - if (!geom) return 0; - - if (geom instanceof CsgGeometry) - return geom.tree.numPolygons(); - - // special array of polygons - if (geom.polygons) - return geom.polygons.length; - - const attr = geom.getAttribute('position'); - return attr?.count ? Math.round(attr.count / 3) : 0; -} +let createGeometry = null; -/** @summary Creates geometrey for composite shape +/** @summary Creates geometry for composite shape * @private */ function createComposite(shape, faces_limit) { if (faces_limit < 0) { @@ -1854,8 +1919,10 @@ function createComposite(shape, faces_limit) { const matrix1 = createMatrix(shape.fNode.fLeftMat), matrix2 = createMatrix(shape.fNode.fRightMat); - if (faces_limit === 0) faces_limit = 4000; - else return_bsp = true; + if (faces_limit === 0) + faces_limit = 4000; + else + return_bsp = true; if (matrix1 && (matrix1.determinant() < -0.9)) geoWarn('Axis reflection in left composite shape - not supported'); @@ -1865,33 +1932,36 @@ function createComposite(shape, faces_limit) { if (shape.fNode.fLeft._typename === clTGeoHalfSpace) geom1 = createHalfSpace(shape.fNode.fLeft); - else + else geom1 = createGeometry(shape.fNode.fLeft, faces_limit); - if (!geom1) return null; + if (!geom1) + return null; - let n1 = countGeometryFaces(geom1), n2 = 0; - if (geom1._exceed_limit) n1 += faces_limit; + let n1 = numGeometryFaces(geom1), n2 = 0; + if (geom1._exceed_limit) + n1 += faces_limit; if (n1 < faces_limit) { if (shape.fNode.fRight._typename === clTGeoHalfSpace) geom2 = createHalfSpace(shape.fNode.fRight, geom1); - else + else geom2 = createGeometry(shape.fNode.fRight, faces_limit); - n2 = countGeometryFaces(geom2); + n2 = numGeometryFaces(geom2); } if ((n1 + n2 >= faces_limit) || !geom2) { if (geom1.polygons) geom1 = createBufferGeometry(geom1.polygons); - if (matrix1) geom1.applyMatrix4(matrix1); + if (matrix1) + geom1.applyMatrix4(matrix1); geom1._exceed_limit = true; return geom1; } - let bsp1 = new CsgGeometry(geom1, matrix1, cfg.CompressComp ? 0 : undefined); + let bsp1 = new CsgGeometry(geom1, matrix1, _cfg.CompressComp ? 0 : undefined); const bsp2 = new CsgGeometry(geom2, matrix2, bsp1.maxid); @@ -1899,17 +1969,24 @@ function createComposite(shape, faces_limit) { 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; // '/' + case 'TGeoIntersection': // '*' + bsp1.direct_intersect(bsp2); + break; + case 'TGeoUnion': // '+' + bsp1.direct_union(bsp2); + break; + case 'TGeoSubtraction': // '/' + bsp1.direct_subtract(bsp2); + break; default: - geoWarn('unsupported bool operation ' + shape.fNode._typename + ', use first geom'); + geoWarn(`unsupported bool operation ${shape.fNode._typename}, use first geom`); + break; } - if (countGeometryFaces(bsp1) === 0) { + if (numGeometryFaces(bsp1) === 0) { geoWarn('Zero faces in comp shape' + - ` left: ${shape.fNode.fLeft._typename} ${countGeometryFaces(geom1)} faces` + - ` right: ${shape.fNode.fRight._typename} ${countGeometryFaces(geom2)} faces` + + ` left: ${shape.fNode.fLeft._typename} ${numGeometryFaces(geom1)} faces` + + ` right: ${shape.fNode.fRight._typename} ${numGeometryFaces(geom2)} faces` + ' use first'); bsp1 = new CsgGeometry(geom1, matrix1); } @@ -1919,24 +1996,23 @@ function createComposite(shape, faces_limit) { /** @summary Try to create projected geometry * @private */ -function projectGeometry(geom, matrix, projection, position, flippedMesh) { - if (!geom.boundingBox) geom.computeBoundingBox(); +function projectGeometry(geom, matrix, projection, position = 0, flippedMesh = false) { + if (!geom.boundingBox) + geom.computeBoundingBox(); const 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 const bsp1 = new CsgGeometry(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)); + 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)); let size = 10000; switch (projection) { @@ -1956,13 +2032,11 @@ function projectGeometry(geom, matrix, projection, position, flippedMesh) { * @param {Object} shape - instance of TGeoShape object * @param {Number} limit - defines return value, see details * @desc - * - if limit === 0 (or undefined) returns BufferGeometry + * - if limit === 0 returns BufferGeometry * - if limit < 0 just returns estimated number of faces * - if limit > 0 return list of CsgPolygons (used only for composite shapes) * @private */ -function createGeometry(shape, limit) { - if (limit === undefined) limit = 0; - +createGeometry = function(shape, limit = 0) { try { switch (shape._typename) { case clTGeoBBox: return createCubeBuffer(shape, limit); @@ -1995,9 +2069,9 @@ function createGeometry(shape, limit) { return res; } case clTGeoHalfSpace: - if (limit < 0) return 1; // half space if just plane used in composite - // no break here - warning should appear - // eslint-disable-next-line no-fallthrough + if (limit < 0) + return 1; // half space if just plane used in composite + // eslint-disable-next-line no-fallthrough default: geoWarn(`unsupported shape type ${shape._typename}`); } @@ -2005,14 +2079,13 @@ function createGeometry(shape, limit) { let 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; + place = place.indexOf(e.message) >= 0 ? e.stack.split('\n')[1] : 'at: ' + place; } geoWarn(`${shape._typename} err: ${e.message} ${place}`); } return limit < 0 ? 0 : null; -} +}; /** @summary Create single shape from EVE7 render date @@ -2022,20 +2095,20 @@ function makeEveGeometry(rd) { if (rd.sz[0]) { rd.vtxBuff = new Float32Array(rd.raw.buffer, off, rd.sz[0]); - off += rd.sz[0]*4; + off += rd.sz[0] * 4; } if (rd.sz[1]) { // normals were not used // rd.nrmBuff = new Float32Array(rd.raw.buffer, off, rd.sz[1]); - off += rd.sz[1]*4; + off += rd.sz[1] * 4; } if (rd.sz[2]) { // these are special values in the buffer begin rd.prefixBuf = new Uint32Array(rd.raw.buffer, off, 2); - off += 2*4; - rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2]-2); + off += 2 * 4; + rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2] - 2); // off += (rd.sz[2]-2)*4; } @@ -2049,22 +2122,21 @@ function makeEveGeometry(rd) { if (rd.idxBuff.length !== nVert) throw Error('Expect single list of triangles in index buffer.'); - const body = new BufferGeometry(); - body.setAttribute('position', new BufferAttribute(rd.vtxBuff, 3)); - body.setIndex(new BufferAttribute(rd.idxBuff, 1)); + const body = new THREE.BufferGeometry(); + body.setAttribute('position', new THREE.BufferAttribute(rd.vtxBuff, 3)); + body.setIndex(new THREE.BufferAttribute(rd.idxBuff, 1)); body.computeVertexNormals(); return body; } -/** @summary Create single shape from geometry veiwer render date +/** @summary Create single shape from geometry viewer render date * @private */ function makeViewerGeometry(rd) { - const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength/4), - - body = new BufferGeometry(); - body.setAttribute('position', new BufferAttribute(vtxBuff, 3)); - body.setIndex(new BufferAttribute(new Uint32Array(rd.idx), 1)); + const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength / 4), + body = new THREE.BufferGeometry(); + body.setAttribute('position', new THREE.BufferAttribute(vtxBuff, 3)); + body.setIndex(new THREE.BufferAttribute(new Uint32Array(rd.idx), 1)); body.computeVertexNormals(); return body; } @@ -2078,29 +2150,26 @@ function createServerGeometry(rd, nsegm) { rd.nsegm = nsegm; - let g = null; + let geom; if (rd.shape) { // case when TGeoShape provided as is - g = createGeometry(rd.shape); + geom = createGeometry(rd.shape); } else { if (!rd.raw?.buffer) { console.error('No raw data at all'); return null; } - if (rd.sz) - g = makeEveGeometry(rd); - else - g = makeViewerGeometry(rd); + geom = rd.sz ? makeEveGeometry(rd) : makeViewerGeometry(rd); } // shape handle is similar to created in TGeoPainter return { - _typename: '$$Shape$$', // indicate that shape can be used as is + _typename: kShapeType, // indicate that shape can be used as is ready: true, - geom: g, - nfaces: numGeometryFaces(g) + geom, + nfaces: numGeometryFaces(geom) }; } @@ -2123,26 +2192,29 @@ function provideObjectInfo(obj) { } const sz = Math.max(shape.fDX, shape.fDY, shape.fDZ), - useexp = (sz > 1e7) || (sz < 1e-7), - 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); - }; + useexp = (sz > 1e7) || (sz < 1e-7), + 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 clTGeoBBox: break; - case clTGeoPara: info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); break; - case clTGeoTrd2: info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy1)}`); // no break - // eslint-disable-next-line no-fallthrough - case clTGeoTrd1: info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx1)}`); break; - case clTGeoArb8: break; - case clTGeoTrap: break; - case clTGeoGtra: break; + case clTGeoPara: + info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); + break; + case clTGeoTrd2: + info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy1)}`); // no break + // eslint-disable-next-line no-fallthrough + case clTGeoTrd1: + info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx1)}`); + break; case clTGeoSphere: info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, `Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`, @@ -2150,8 +2222,7 @@ function provideObjectInfo(obj) { break; case clTGeoConeSeg: info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - // no break; - // eslint-disable-next-line no-fallthrough + // eslint-disable-next-line no-fallthrough case clTGeoCone: info.push(`Rmin1=${conv(shape.fRmin1)} Rmax1=${conv(shape.fRmax1)}`, `Rmin2=${conv(shape.fRmin2)} Rmax2=${conv(shape.fRmax2)}`); @@ -2159,8 +2230,7 @@ function provideObjectInfo(obj) { case clTGeoCtub: case clTGeoTubeSeg: info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - // no break - // eslint-disable-next-line no-fallthrough + // eslint-disable-next-line no-fallthrough case clTGeoEltu: case clTGeoTube: info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`); @@ -2169,9 +2239,6 @@ function provideObjectInfo(obj) { info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, `Phi1=${shape.fPhi1} Dphi=${shape.fDphi}`); break; - case clTGeoPcon: - case clTGeoPgon: break; - case clTGeoXtru: break; case clTGeoParaboloid: info.push(`Rlo=${conv(shape.fRlo)} Rhi=${conv(shape.fRhi)}`, `A=${conv(shape.fA)} B=${conv(shape.fB)}`); @@ -2180,13 +2247,36 @@ function provideObjectInfo(obj) { info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, `StIn=${conv(shape.fStIn)} StOut=${conv(shape.fStOut)}`); break; - case clTGeoCompositeShape: break; - case clTGeoShapeAssembly: break; case clTGeoScaledShape: info = provideObjectInfo(shape.fShape); if (shape.fScale) info.unshift(`Scale X=${shape.fScale.fScale[0]} Y=${shape.fScale.fScale[1]} Z=${shape.fScale.fScale[2]}`); break; + case clTGeoPcon: + case clTGeoPgon: + info.push(`Phi1=${conv(shape.fPhi1)} Dphi=${conv(shape.fDphi)}`, + `Nz=${shape.fNz}`); + break; + case clTGeoXtru: + info.push(`Nz=${shape.fNz} Nvert=${shape.fNvert}`); + break; + case clTGeoCompositeShape: + info.push(`Type ${shape.fNode?._typename}`, + `Left=${shape.fNode?.fLeft?._typename} Right=${shape.fNode?.fRight?._typename}`); + break; + case clTGeoBBox: + info.push(`Origin=[${conv(shape.fOrigin[0])},${conv(shape.fOrigin[1])},${conv(shape.fOrigin[2])}]`); + break; + case clTGeoArb8: + break; + case clTGeoGtra: + info.push(`TwistAngle=${conv(shape.fTwistAngle)}`); + // eslint-disable-next-line no-fallthrough + case clTGeoTrap: + info.push(`Phi=${conv(shape.fPhi)} Theta=${conv(shape.fTheta)}`); + break; + case clTGeoShapeAssembly: + break; } return info; @@ -2195,7 +2285,7 @@ function provideObjectInfo(obj) { /** @summary Creates projection matrix for the camera * @private */ function createProjectionMatrix(camera) { - const cameraProjectionMatrix = new Matrix4(); + const cameraProjectionMatrix = new THREE.Matrix4(); camera.updateMatrixWorld(); @@ -2208,61 +2298,57 @@ function createProjectionMatrix(camera) { /** @summary Creates frustum * @private */ function createFrustum(source) { - if (!source) return null; + if (!source) + return null; - if (source instanceof PerspectiveCamera) + if (source instanceof THREE.PerspectiveCamera) source = createProjectionMatrix(source); - const frustum = new Frustum(); + const frustum = new THREE.Frustum(); frustum.setFromProjectionMatrix(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, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, - 0, 0, 0 // also check center of the shape + 0, 0, 0 // also check center of the shape ]); - frustum.test = new Vector3(0, 0, 0); + frustum.test = new THREE.Vector3(0, 0, 0); frustum.CheckShape = function(matrix, shape) { const pnt = this.test, len = this.corners.length, corners = this.corners; - for (let i = 0; i < len; i+=3) { + for (let 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; - } + pnt.y = corners[i + 1] * shape.fDY; + pnt.z = corners[i + 2] * shape.fDZ; + if (this.containsPoint(pnt.applyMatrix4(matrix))) + return true; + } - return false; + return false; + }; + + frustum.CheckPoint = function(x, y, z) { + return this.containsPoint(this.test.set(x, y, z)) ? 1 : 0; }; frustum.CheckBox = function(box) { - const pnt = this.test; - let 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 + // only if 6 edges and more are seen, we think that box is fully visible + return this.CheckPoint(box.min.x, box.min.y, box.min.z) + + this.CheckPoint(box.min.x, box.min.y, box.max.z) + + this.CheckPoint(box.min.x, box.max.y, box.min.z) + + this.CheckPoint(box.min.x, box.max.y, box.max.z) + + this.CheckPoint(box.max.x, box.max.y, box.max.z) + + this.CheckPoint(box.max.x, box.min.y, box.max.z) + + this.CheckPoint(box.max.x, box.max.y, box.min.z) + + this.CheckPoint(box.max.x, box.max.y, box.max.z) > 5; }; return frustum; @@ -2271,7 +2357,8 @@ function createFrustum(source) { /** @summary Create node material * @private */ function createMaterial(cfg, args0) { - if (!cfg) cfg = { material_kind: 'lambert' }; + if (!cfg) + cfg = { material_kind: 'lambert' }; const args = Object.assign({}, args0); @@ -2282,42 +2369,43 @@ function createMaterial(cfg, args0) { args.opacity = Math.min(1 - cfg.transparency, args.opacity); args.wireframe = cfg.wireframe ?? false; - if (!args.color) args.color = 'red'; - args.side = FrontSide; + if (!args.color) + args.color = 'red'; + args.side = THREE.FrontSide; args.transparent = args.opacity < 1; args.depthWrite = args.opactity === 1; let material; if (cfg.material_kind === 'basic') - material = new MeshBasicMaterial(args); - else if (cfg.material_kind === 'depth') { + material = new THREE.MeshBasicMaterial(args); + else if (cfg.material_kind === 'depth') { delete args.color; - material = new MeshDepthMaterial(args); + material = new THREE.MeshDepthMaterial(args); } else if (cfg.material_kind === 'toon') - material = new MeshToonMaterial(args); - else if (cfg.material_kind === 'matcap') { + material = new THREE.MeshToonMaterial(args); + else if (cfg.material_kind === 'matcap') { delete args.wireframe; - material = new MeshMatcapMaterial(args); + material = new THREE.MeshMatcapMaterial(args); } else if (cfg.material_kind === 'standard') { args.metalness = cfg.metalness ?? 0.5; args.roughness = cfg.roughness ?? 0.1; - material = new MeshStandardMaterial(args); + material = new THREE.MeshStandardMaterial(args); } else if (cfg.material_kind === 'normal') { delete args.color; - material = new MeshNormalMaterial(args); + material = new THREE.MeshNormalMaterial(args); } else if (cfg.material_kind === 'physical') { args.metalness = cfg.metalness ?? 0.5; args.roughness = cfg.roughness ?? 0.1; args.reflectivity = cfg.reflectivity ?? 0.5; args.emissive = args.color; - material = new MeshPhysicalMaterial(args); + material = new THREE.MeshPhysicalMaterial(args); } else if (cfg.material_kind === 'phong') { args.shininess = cfg.shininess ?? 0.9; - material = new MeshPhongMaterial(args); + material = new THREE.MeshPhongMaterial(args); } else { args.vertexColors = false; - material = new MeshLambertMaterial(args); + material = new THREE.MeshLambertMaterial(args); } if ((material.flatShading !== undefined) && (cfg.flatShading !== undefined)) @@ -2328,7 +2416,6 @@ function createMaterial(cfg, args0) { return material; } - /** @summary Compares two stacks. * @return {Number} 0 if same, -1 when stack1 < stack2, +1 when stack1 > stack2 * @private */ @@ -2353,15 +2440,98 @@ function compare_stacks(stack1, stack2) { /** @summary Checks if two stack arrays are identical * @private */ function isSameStack(stack1, stack2) { - if (!stack1 || !stack2) return false; - if (stack1 === stack2) return true; - if (stack1.length !== stack2.length) return false; - for (let k = 0; k < stack1.length; ++k) - if (stack1[k] !== stack2[k]) return false; + if (!stack1 || !stack2) + return false; + if (stack1 === stack2) + return true; + if (stack1.length !== stack2.length) + return false; + for (let k = 0; k < stack1.length; ++k) { + if (stack1[k] !== stack2[k]) + return false; + } return true; } +function createFlippedGeom(geom) { + let pos = geom.getAttribute('position').array, + norm = geom.getAttribute('normal').array; + const index = geom.getIndex(); + + if (index) { + // we need to unfold all points to + const arr = index.array, + i0 = geom.drawRange.start; + let ilen = geom.drawRange.count; + if (i0 + ilen > arr.length) + ilen = arr.length - i0; + + const dpos = new Float32Array(ilen * 3), + dnorm = new Float32Array(ilen * 3); + for (let ii = 0; ii < ilen; ++ii) { + const k = arr[i0 + ii]; + if ((k < 0) || (k * 3 >= pos.length)) + console.log(`strange index ${k * 3} totallen = ${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; + } + + const len = pos.length, + newpos = new Float32Array(len), + newnorm = new Float32Array(len); + + // we should swap second and third point in each face + for (let n = 0, shift = 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 + } + + const geomZ = new THREE.BufferGeometry(); + geomZ.setAttribute('position', new THREE.BufferAttribute(newpos, 3)); + geomZ.setAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); + + return geomZ; +} + + +/** @summary Create flipped mesh for the shape + * @desc When transformation matrix includes one or several inversion of axis, + * one should inverse geometry object, otherwise three.js cannot correctly draw it + * @param {Object} shape - TGeoShape object + * @param {Object} material - material + * @private */ +function createFlippedMesh(shape, material) { + if (shape.geomZ === undefined) + shape.geomZ = createFlippedGeom(shape.geom); + + const mesh = new THREE.Mesh(shape.geomZ, material); + mesh.scale.copy(new THREE.Vector3(1, 1, -1)); + mesh.updateMatrix(); + + mesh._flippedMesh = true; + + return mesh; +} + + /** * @summary class for working with cloned nodes * @@ -2376,10 +2546,11 @@ class ClonedNodes { this.name_prefix = ''; // name prefix used for nodes names this.maxdepth = 1; // maximal hierarchy depth, required for transparency this.vislevel = 4; // maximal depth of nodes visibility aka gGeoManager->SetVisLevel, same default - this.maxnodes = 10000; // maximal number of visisble nodes aka gGeoManager->fMaxVisNodes + this.maxnodes = 10000; // maximal number of visible nodes aka gGeoManager->fMaxVisNodes if (obj) { - if (obj.$geoh) this.toplevel = false; + if (obj.$geoh) + this.toplevel = false; this.createClones(obj); } else if (clones) this.nodes = clones; @@ -2395,9 +2566,10 @@ class ClonedNodes { return this.vislevel; } - /** @summary Set maximal number of visible nodes */ + /** @summary Set maximal number of visible nodes + * @desc By default 10000 nodes will be visualized */ setMaxVisNodes(v, more) { - this.maxnodes = Number.isFinite(v) ? v : 10000; + this.maxnodes = (v === Infinity) ? 1e9 : (Number.isFinite(v) ? v : 10000); if (more && Number.isFinite(more)) this.maxnodes *= more; } @@ -2420,15 +2592,12 @@ class ClonedNodes { /** @summary Returns TGeoShape for element with given indx */ getNodeShape(indx) { - if (!this.origin || !this.nodes) return null; + if (!this.origin || !this.nodes) + return null; const obj = this.origin[indx], clone = this.nodes[indx]; - if (!obj || !clone) return null; - if (clone.kind === kindGeo) { - if (obj.fVolume) return obj.fVolume.fShape; - } else - return obj.fShape; - - return null; + if (!obj || !clone) + return null; + return clone.kind === kindGeo ? obj.fVolume?.fShape : obj.fShape; } /** @summary function to cleanup as much as possible structures @@ -2464,7 +2633,7 @@ class ClonedNodes { /** @summary Create complete description for provided Geo object */ createClones(obj, sublevel, kind) { if (!sublevel) { - if (obj?._typename === '$$Shape$$') + if (obj?._typename === kShapeType) return this.createClonesForShape(obj); this.origin = []; @@ -2472,13 +2641,14 @@ class ClonedNodes { kind = getNodeKind(obj); } - if ((kind < 0) || !obj || ('_refid' in obj)) return; + if ((kind < 0) || !obj || ('_refid' in obj)) + return; obj._refid = this.origin.length; this.origin.push(obj); - if (sublevel > this.maxdepth) this.maxdepth = sublevel; + this.maxdepth = Math.max(this.maxdepth, sublevel); - let chlds = null; + let chlds; if (kind === kindGeo) chlds = obj.fVolume?.fNodes?.arr || null; else @@ -2490,7 +2660,8 @@ class ClonedNodes { this.createClones(chlds[i], sublevel + 1, kind); } - if (sublevel > 1) return; + if (sublevel > 1) + return; this.nodes = []; @@ -2506,46 +2677,42 @@ class ClonedNodes { // than fill children lists for (let n = 0; n < this.origin.length; ++n) { - const obj = this.origin[n], clone = this.nodes[n]; - let chlds = null, shape = null; - - if (kind === kindEve) { - 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 obj2 = this.origin[n], + clone = this.nodes[n], + shape = kind === kindEve ? obj2.fShape : obj2.fVolume.fShape, + chlds2 = kind === kindEve ? obj2.fElements?.arr : obj2.fVolume.fNodes?.arr, + matrix = getNodeMatrix(kind, obj2); - 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) { + if (clone.matrix && (clone.matrix[0] === 1)) { let 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 (issimple) + delete clone.matrix; } - if (clone.matrix && (kind === kindEve)) // deepscan-disable-line INSUFFICIENT_NULL_CHECK + if (clone.matrix && (kind === kindEve)) clone.abs_matrix = true; } if (shape) { clone.fDX = shape.fDX; clone.fDY = shape.fDY; clone.fDZ = shape.fDZ; - clone.vol = Math.sqrt(shape.fDX**2 + shape.fDY**2 + shape.fDZ**2); + clone.vol = Math.sqrt(shape.fDX ** 2 + shape.fDY ** 2 + shape.fDZ ** 2); if (shape.$nfaces === undefined) shape.$nfaces = createGeometry(shape, -1); clone.nfaces = shape.$nfaces; - if (clone.nfaces <= 0) clone.vol = 0; + if (clone.nfaces <= 0) + clone.vol = 0; } - if (!chlds) continue; - - // in cloned object children is only list of ids - clone.chlds = new Array(chlds.length); - for (let k = 0; k < chlds.length; ++k) - clone.chlds[k] = chlds[k]._refid; + if (chlds2) { + // in cloned object children is only list of ids + clone.chlds = new Array(chlds2.length); + for (let k = 0; k < chlds2.length; ++k) + clone.chlds[k] = chlds2[k]._refid; + } } // remove _refid identifiers from original objects @@ -2571,27 +2738,27 @@ class ClonedNodes { // indicate that just plain shape is used this.plain_shape = obj; - const node = { - id: 0, sortid: 0, kind: kindShape, - name: 'Shape', - nfaces: obj.nfaces, - fDX: 1, fDY: 1, fDZ: 1, vol: 1, - vis: true - }; - - this.nodes = [node]; + this.nodes = [{ + id: 0, sortid: 0, kind: kindShape, + name: 'Shape', + nfaces: obj.nfaces, + fDX: 1, fDY: 1, fDZ: 1, vol: 1, + vis: true + }]; } - /** @summary Count all visisble nodes */ + /** @summary Count all visible nodes */ countVisibles() { const len = this.nodes?.length || 0; let cnt = 0; - for (let k = 0; k < len; ++k) - if (this.nodes[k].vis) cnt++; + for (let k = 0; k < len; ++k) { + if (this.nodes[k].vis) + cnt++; + } return cnt; } - /** @summary Mark visisble nodes. + /** @summary Mark visible nodes. * @desc Set only basic flags, actual visibility depends from hierarchy */ markVisibles(on_screen, copy_bits, hide_top_volume) { if (this.plain_shape) @@ -2613,7 +2780,8 @@ class ClonedNodes { // on screen bits used always, childs always checked clone.vis = testGeoBit(obj.fVolume, geoBITS.kVisOnScreen) ? 99 : 0; - if ((n === 0) && clone.vis && hide_top_volume) clone.vis = 0; + if ((n === 0) && clone.vis && hide_top_volume) + clone.vis = 0; if (copy_bits) { setGeoBit(obj.fVolume, geoBITS.kVisNone, false); @@ -2633,7 +2801,8 @@ class ClonedNodes { // special handling for top node if (n === 0) { - if (hide_top_volume) clone.vis = 0; + if (hide_top_volume) + clone.vis = 0; delete clone.nochlds; } } @@ -2642,21 +2811,24 @@ class ClonedNodes { clone.vis = obj.fRnrSelf ? 99 : 0; // when the only node is selected, draw it - if ((n === 0) && (this.nodes.length === 1)) clone.vis = 99; + if ((n === 0) && (this.nodes.length === 1)) + clone.vis = 99; this.vislevel = 9999; // automatically take all volumes } // shape with zero volume or without faces will not be observed - if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = 0; + if ((clone.vol <= 0) || (clone.nfaces <= 0)) + clone.vis = 0; - if (clone.vis) res++; + if (clone.vis) + res++; } return res; } - /** @summary After visibility flags is set, produce idshift for all nodes as it would be maximum level */ + /** @summary After visibility flags is set, produce id shifts for all nodes as it would be maximum level */ produceIdShifts() { for (let k = 0; k < this.nodes.length; ++k) this.nodes[k].idshift = -1; @@ -2665,7 +2837,7 @@ class ClonedNodes { if (node.idshift < 0) { node.idshift = 0; if (node.chlds) { - for (let k = 0; k<node.chlds.length; ++k) + for (let k = 0; k < node.chlds.length; ++k) node.idshift += scan_func(nodes, nodes[node.chlds[k]]); } } @@ -2680,7 +2852,7 @@ class ClonedNodes { * @desc Used to transfer them to the worker */ getVisibleFlags() { const res = new Array(this.nodes.length); - for (let n=0; n<this.nodes.length; ++n) + for (let n = 0; n < this.nodes.length; ++n) res[n] = { vis: this.nodes[n].vis, nochlds: this.nodes[n].nochlds }; return res; } @@ -2695,7 +2867,8 @@ class ClonedNodes { const clone = this.nodes[n]; clone.vis = flags[n].vis; clone.nochlds = flags[n].nochlds; - if (clone.vis) res++; + if (clone.vis) + res++; } return res; @@ -2710,22 +2883,24 @@ class ClonedNodes { return; } else if (on === 'clear') { do_clear = true; - if (!this.fVisibility) return; + if (!this.fVisibility) + return; } else - on = !!on; - if (!stack) return; + on = Boolean(on); + if (!stack) + return; if (!this.fVisibility) this.fVisibility = []; for (let indx = 0; indx < this.fVisibility.length; ++indx) { const item = this.fVisibility[indx], - res = compare_stacks(item.stack, stack); + res = compare_stacks(item.stack, stack); if (res === 0) { if (do_clear) { this.fVisibility.splice(indx, 1); - if (this.fVisibility.length === 0) + if (!this.fVisibility.length) delete this.fVisibility; } else item.visible = on; @@ -2750,7 +2925,7 @@ class ClonedNodes { return null; for (let indx = 0; indx < this.fVisibility.length; ++indx) { const item = this.fVisibility[indx], - res = compare_stacks(item.stack, stack); + res = compare_stacks(item.stack, stack); if (res === 0) return item; if (res > 0) @@ -2763,13 +2938,16 @@ class ClonedNodes { /** @summary Scan visible nodes in hierarchy, starting from nodeid * @desc Each entry in hierarchy get its unique id, which is not changed with visibility flags */ scanVisible(arg, vislvl) { - if (!this.nodes) return 0; + if (!this.nodes) + return 0; if (vislvl === undefined) { - if (!arg) arg = {}; + if (!arg) + arg = {}; vislvl = arg.vislvl || this.vislevel || 4; // default 3 in ROOT - if (vislvl > 88) vislvl = 88; + if (vislvl > 88) + vislvl = 88; arg.stack = new Array(100); // current stack arg.nodeid = 0; @@ -2777,15 +2955,16 @@ class ClonedNodes { arg.last = 0; arg.copyStack = function(factor) { const entry = { nodeid: this.nodeid, seqid: this.counter, stack: new Array(this.last) }; - if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be built as first + if (factor) + entry.factor = factor; // factor used to indicate importance of entry, will be built as first for (let n = 0; n < this.last; ++n) - entry.stack[n] = this.stack[n+1]; // copy stack + entry.stack[n] = this.stack[n + 1]; // copy stack return entry; }; if (arg.domatrix) { arg.matrices = []; - arg.mpool = [new Matrix4()]; // pool of Matrix objects to avoid permanent creation + arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation arg.getmatrix = function() { return this.matrices[this.last]; }; } @@ -2797,7 +2976,7 @@ class ClonedNodes { if (!this.vstack || (this.vstack?.length !== this.last)) return undefined; for (let n = 0; n < this.last; ++n) { - if (this.vstack[n] !== this.stack[n+1]) + if (this.vstack[n] !== this.stack[n + 1]) return undefined; } const res = this.varray[this.vindx++].visible; @@ -2811,13 +2990,13 @@ class ClonedNodes { let res = 0; if (arg.domatrix) { - if (!arg.mpool[arg.last+1]) - arg.mpool[arg.last+1] = new Matrix4(); + if (!arg.mpool[arg.last + 1]) + arg.mpool[arg.last + 1] = new THREE.Matrix4(); - const prnt = (arg.last > 0) ? arg.matrices[arg.last-1] : new Matrix4(); + const 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)); + arg.matrices[arg.last].multiply(arg.mpool[arg.last + 1].fromArray(node.matrix)); } else arg.matrices[arg.last] = prnt; } @@ -2827,10 +3006,10 @@ class ClonedNodes { if ((arg.nodeid === 0) && arg.main_visible) node_vis = vislvl + 1; else if (arg.testPhysVis) { - const res = arg.testPhysVis(); - if (res !== undefined) { - node_vis = res && !node.chlds ? vislvl + 1 : 0; - node_nochlds = !res; + const res2 = arg.testPhysVis(); + if (res2 !== undefined) { + node_vis = res2 && !node.chlds ? vislvl + 1 : 0; + node_nochlds = !res2; } } @@ -2849,7 +3028,7 @@ class ClonedNodes { for (let 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 - res += this.scanVisible(arg, vislvl-1); + res += this.scanVisible(arg, vislvl - 1); } arg.last--; } else @@ -2889,11 +3068,10 @@ class ClonedNodes { resolveStack(stack, withmatrix) { const res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix || '' }; - // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ''; - if (withmatrix) { - res.matrix = new Matrix4(); - if (res.node.matrix) res.matrix.fromArray(res.node.matrix); + res.matrix = new THREE.Matrix4(); + if (res.node.matrix) + res.matrix.fromArray(res.node.matrix); } if (this.origin) @@ -2912,12 +3090,13 @@ class ClonedNodes { const subname = this.getNodeName(res.id); if (subname) { - if (res.name) res.name += '/'; + if (res.name) + res.name += '/'; res.name += subname; } if (withmatrix && res.node.matrix) - res.matrix.multiply(new Matrix4().fromArray(res.node.matrix)); + res.matrix.multiply(new THREE.Matrix4().fromArray(res.node.matrix)); } } @@ -2933,9 +3112,10 @@ class ClonedNodes { /** @summary Create stack array based on nodes ids array. * @desc Ids list should correspond to existing nodes hierarchy */ buildStackByIds(ids) { - if (!ids) return null; + if (!ids) + return null; - if (ids[0] !== 0) { + if (ids[0]) { console.error('wrong ids - first should be 0'); return null; } @@ -2945,10 +3125,11 @@ class ClonedNodes { for (let k = 1; k < ids.length; ++k) { const nodeid = ids[k]; - if (!node) return null; + if (!node) + return null; const chindx = node.chlds.indexOf(nodeid); if (chindx < 0) { - console.error(`wrong nodes ids ${ids[k]} is not child of ${ids[k-1]}`); + console.error(`wrong nodes ids ${ids[k]} is not child of ${ids[k - 1]}`); return null; } @@ -2959,9 +3140,10 @@ class ClonedNodes { return stack; } - /** @summary Retuns ids array which correspond to the stack */ + /** @summary Returns ids array which correspond to the stack */ buildIdsByStack(stack) { - if (!stack) return null; + if (!stack) + return null; let node = this.nodes[0]; const ids = [0]; for (let k = 0; k < stack.length; ++k) { @@ -2972,7 +3154,7 @@ class ClonedNodes { return ids; } - /** @summary Retuns node id by stack */ + /** @summary Returns node id by stack */ getNodeIdByStack(stack) { if (!stack || !this.nodes) return -1; @@ -2986,13 +3168,15 @@ class ClonedNodes { /** @summary Returns true if stack includes at any place provided nodeid */ isIdInStack(nodeid, stack) { - if (!nodeid) return true; + if (!nodeid) + return true; - let node = this.nodes[0], id = 0; + let node = this.nodes[0]; for (let lvl = 0; lvl < stack.length; ++lvl) { - id = node.chlds[stack[lvl]]; - if (id === nodeid) return true; + const id = node.chlds[stack[lvl]]; + if (id === nodeid) + return true; node = this.nodes[id]; } @@ -3004,11 +3188,13 @@ class ClonedNodes { const names = fullname.split('/'), stack = []; let currid = 0; - if (this.getNodeName(currid) !== names[0]) return null; + if (this.getNodeName(currid) !== names[0]) + return null; for (let n = 1; n < names.length; ++n) { const node = this.nodes[currid]; - if (!node.chlds) return null; + if (!node.chlds) + return null; for (let k = 0; k < node.chlds.length; ++k) { const chldid = node.chlds[k]; @@ -3020,7 +3206,8 @@ class ClonedNodes { } // no new entry - not found stack - if (stack.length === n - 1) return null; + if (stack.length === n - 1) + return null; } return stack; @@ -3030,28 +3217,27 @@ class ClonedNodes { setDefaultColors(on) { this.use_dflt_colors = on; if (this.use_dflt_colors && !this.dflt_table) { - const dflt = { kWhite: 0, kBlack: 1, kGray: 920, - kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kCyan: 432, - kOrange: 800, kSpring: 820, kTeal: 840, kAzure: 860, kViolet: 880, kPink: 900 }, - - nmax = 110, col = []; - for (let i=0; i<nmax; i++) col.push(dflt.kGray); + const nmax = 110, col = [], dflt = { kWhite: 0, kBlack: 1, kGray: 920, + kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kCyan: 432, + kOrange: 800, kSpring: 820, kTeal: 840, kAzure: 860, kViolet: 880, kPink: 900 }; + for (let i = 0; i < nmax; i++) + col.push(dflt.kGray); // here we should create a new TColor with the same rgb as in the default // ROOT colors used below - col[3] = dflt.kYellow-10; - col[4] = col[5] = dflt.kGreen-10; - col[6] = col[7] = dflt.kBlue-7; - col[8] = col[9] = dflt.kMagenta-3; - col[10] = col[11] = dflt.kRed-10; - col[12] = dflt.kGray+1; - col[13] = dflt.kBlue-10; - col[14] = dflt.kOrange+7; - col[16] = dflt.kYellow+1; - col[20] = dflt.kYellow-10; - col[24] = col[25] = col[26] = dflt.kBlue-8; - col[29] = dflt.kOrange+9; - col[79] = dflt.kOrange-2; + col[3] = dflt.kYellow - 10; + col[4] = col[5] = dflt.kGreen - 10; + col[6] = col[7] = dflt.kBlue - 7; + col[8] = col[9] = dflt.kMagenta - 3; + col[10] = col[11] = dflt.kRed - 10; + col[12] = dflt.kGray + 1; + col[13] = dflt.kBlue - 10; + col[14] = dflt.kOrange + 7; + col[16] = dflt.kYellow + 1; + col[20] = dflt.kYellow - 10; + col[24] = col[25] = col[26] = dflt.kBlue - 8; + col[29] = dflt.kOrange + 9; + col[79] = dflt.kOrange - 2; this.dflt_table = col; } @@ -3064,14 +3250,14 @@ class ClonedNodes { if (clone.kind === kindShape) { const prop = { name: clone.name, nname: clone.name, shape: null, material: null, chlds: null }, - opacity = entry.opacity || 1, col = entry.color || '#0000FF'; - prop.fillcolor = new Color(col[0] === '#' ? col : `rgb(${col})`); + opacity = entry.opacity || 1, col = entry.color || '#0000FF'; + prop.fillcolor = new THREE.Color(col[0] === '#' ? col : `rgb(${col})`); prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); return prop; } if (!this.origin) { - console.error('origin not there - kind', clone.kind, entry.nodeid, clone); + console.error(`origin not there - kind ${clone.kind} id ${entry.nodeid}`); return null; } @@ -3082,25 +3268,27 @@ class ClonedNodes { const prop = { name: getObjectName(node), nname: getObjectName(node), shape: node.fShape, material: null, chlds: null }; - if (node.fElements !== null) prop.chlds = node.fElements.arr; + if (node.fElements !== null) + prop.chlds = node.fElements.arr; if (visible) { const opacity = Math.min(1, node.fRGBA[3]); - prop.fillcolor = new Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); + prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); } return prop; } - const volume = node.fVolume, - prop = { name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null, - chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth }; + const volume = node.fVolume, prop = { + name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null, + chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth + }; if (visible) { - // TODO: maybe correctly extract ROOT colors here? let opacity = 1.0; - if (!root_colors) root_colors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; + if (!root_colors) + root_colors = getRootColors(); if (entry.custom_color) prop.fillcolor = entry.custom_color; @@ -3118,7 +3306,8 @@ class ClonedNodes { if (this.use_dflt_colors) { const matZ = Math.round(mat.fZ), icol = this.dflt_table[matZ]; prop.fillcolor = root_colors[icol]; - if (mat.fDensity < 0.1) transparency = 60; + if (mat.fDensity < 0.1) + transparency = 60; } if (transparency > 0) @@ -3143,7 +3332,7 @@ class ClonedNodes { const force = isObject(options) || (options === 'force'); for (let lvl = 0; lvl <= stack.length; ++lvl) { - const nchld = (lvl > 0) ? stack[lvl-1] : 0, + const nchld = (lvl > 0) ? stack[lvl - 1] : 0, // extract current node child = (lvl > 0) ? this.nodes[node.chlds[nchld]] : node; if (!child) { @@ -3166,13 +3355,15 @@ class ClonedNodes { if (obj3d) { three_prnt = obj3d; - if (obj3d.$jsroot_drawable) draw_depth++; + if (obj3d.$jsroot_drawable) + draw_depth++; continue; } - if (!force) return null; + if (!force) + return null; - obj3d = new Object3D(); + obj3d = new THREE.Object3D(); if (this._cfg?.set_names) obj3d.name = this.getNodeName(node.id); @@ -3181,7 +3372,7 @@ class ClonedNodes { obj3d.userData = this.origin[node.id]; if (node.abs_matrix) { - obj3d.absMatrix = new Matrix4(); + obj3d.absMatrix = new THREE.Matrix4(); obj3d.absMatrix.fromArray(node.matrix); } else if (node.matrix) { obj3d.matrix.fromArray(node.matrix); @@ -3207,22 +3398,24 @@ class ClonedNodes { three_prnt = obj3d; } - if ((options === 'mesh') || (options === 'delete_mesh')) { + if ((options === kGetMesh) || (options === kDeleteMesh)) { let mesh = null; if (three_prnt) { for (let n = 0; (n < three_prnt.children.length) && !mesh; ++n) { const chld = three_prnt.children[n]; - if ((chld.type === 'Mesh') && (chld.nchld === undefined)) mesh = chld; + if ((chld.type === 'Mesh') && (chld.nchld === undefined)) + mesh = chld; } } - if ((options === 'mesh') || !mesh) return mesh; + if ((options === kGetMesh) || !mesh) + return mesh; const res = three_prnt; while (mesh && (mesh !== toplevel)) { three_prnt = mesh.parent; three_prnt.remove(mesh); - mesh = (three_prnt.children.length === 0) ? three_prnt : null; + mesh = !three_prnt.children.length ? three_prnt : null; } return res; @@ -3246,7 +3439,7 @@ class ClonedNodes { if (!shape.geom || !shape.nfaces) { // node is visible, but shape does not created - this.createObject3D(entry.stack, toplevel, 'delete_mesh'); + this.createObject3D(entry.stack, toplevel, kDeleteMesh); return null; } @@ -3256,12 +3449,12 @@ class ClonedNodes { prop.material.wireframe = ctrl.wireframe; - prop.material.side = ctrl.doubleside ? DoubleSide : FrontSide; + prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; let mesh; if (matrix.determinant() > -0.9) - mesh = new Mesh(shape.geom, prop.material); - else + mesh = new THREE.Mesh(shape.geom, prop.material); + else mesh = createFlippedMesh(shape, prop.material); obj3d.add(mesh); @@ -3308,9 +3501,10 @@ class ClonedNodes { for (let n = 0; n < draw_nodes.length; ++n) { const entry = draw_nodes[n]; - if (entry.done) continue; + if (entry.done) + continue; - /// shape can be provided with entry itself + // shape can be provided with entry itself const shape = entry.server_shape || build_shapes[entry.shapeid]; if (!shape || !shape.ready) { console.warn(`Problem with shape id ${entry.shapeid} when building`); @@ -3346,11 +3540,11 @@ class ClonedNodes { shape.used = true; shape.instances.forEach(instance => { const entry0 = instance.entries[0], - prop = this.getDrawEntryProperties(entry0, colors); + prop = this.getDrawEntryProperties(entry0, colors); prop.material.wireframe = ctrl.wireframe; - prop.material.side = ctrl.doubleside ? DoubleSide : FrontSide; + prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; if (instance.entries.length === 1) this.createEntryMesh(ctrl, toplevel, entry0, shape, colors); @@ -3371,8 +3565,8 @@ class ClonedNodes { entry.done = true; }); - if (arr1.length > 0) { - const mesh1 = new InstancedMesh(shape.geom, prop.material, arr1.length); + if (arr1.length) { + const mesh1 = new THREE.InstancedMesh(shape.geom, prop.material, arr1.length); mesh1.stacks = stacks1; arr1.forEach((matrix, i) => mesh1.setMatrixAt(i, matrix)); @@ -3391,17 +3585,17 @@ class ClonedNodes { mesh1.$jsroot_order = 1; ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces*arr1.length; + ctrl.info.num_faces += shape.nfaces * arr1.length; } - if (arr2.length > 0) { + if (arr2.length) { if (shape.geomZ === undefined) shape.geomZ = createFlippedGeom(shape.geom); - const mesh2 = new InstancedMesh(shape.geomZ, prop.material, arr2.length); + const mesh2 = new THREE.InstancedMesh(shape.geomZ, prop.material, arr2.length); mesh2.stacks = stacks2; - const m = new Matrix4().makeScale(1, 1, -1); + const m = new THREE.Matrix4().makeScale(1, 1, -1); arr2.forEach((matrix, i) => { mesh2.setMatrixAt(i, matrix.multiply(m)); }); @@ -3419,7 +3613,7 @@ class ClonedNodes { mesh2.$jsroot_order = 1; ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces*arr2.length; + ctrl.info.num_faces += shape.nfaces * arr2.length; } } }); @@ -3439,13 +3633,15 @@ class ClonedNodes { return result; } - let maxNode, currNode, cnt=0, facecnt=0; + let maxNode, currNode, cnt = 0, facecnt = 0; for (let n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { const id = this.sortmap[n]; - if (viscnt[id] === 0) continue; + if (viscnt[id] === 0) + continue; currNode = this.nodes[id]; - if (!maxNode) maxNode = currNode; + if (!maxNode) + maxNode = currNode; cnt += viscnt[id]; facecnt += viscnt[id] * currNode.nfaces; } @@ -3455,7 +3651,6 @@ class ClonedNodes { 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 @@ -3478,7 +3673,6 @@ class ClonedNodes { this.facecnt = 0; this.viscnt.fill(0); }, - // nodes: this.nodes, func(node) { this.total++; this.facecnt += node.nfaces; @@ -3509,7 +3703,7 @@ class ClonedNodes { this.actual_level = arg.vislvl; // not used, can be shown somewhere in the gui - let minVol = 0, maxVol = 0, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1; + let minVol = 0, maxVol, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1; if (arg.facecnt > maxnumfaces) { const bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), @@ -3522,34 +3716,32 @@ class ClonedNodes { 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; - }; + 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 (let 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; + this.scanVisible(arg); - camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); + if (arg.totalcam > maxnumfaces * 0.2) + camVol = this.getVolumeBoundary(arg.viscnt, maxnumfaces * 0.2, maxnumnodes * 0.2).min; + else + camVol = 0; - // console.log(`Limit for camera ${camVol} faces in camera view ${arg.totalcam}`); + camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); } } @@ -3558,7 +3750,7 @@ class ClonedNodes { arg.func = function(node) { if (node.sortid < sortidcut) this.items.push(this.copyStack()); - else if ((camVol >= 0) && (node.vol > camVol)) { + else if ((camVol >= 0) && (node.vol > camVol)) { if (this.frustum.CheckShape(this.getmatrix(), node)) this.items.push(this.copyStack(camFact)); } @@ -3582,7 +3774,8 @@ class ClonedNodes { if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { - if (prev[indx2].done) current[indx1].done = true; // copy ready flag + if (prev[indx2].done) + current[indx1].done = true; // copy ready flag indx2++; } } @@ -3605,9 +3798,10 @@ class ClonedNodes { for (let i = 0; i < lst.length; ++i) { const entry = lst[i], - shape = this.getNodeShape(entry.nodeid); + shape = this.getNodeShape(entry.nodeid); - if (!shape) continue; // strange, but avoid misleading + if (!shape) + continue; // strange, but avoid misleading if (shape._id === undefined) { shape._id = shapes.length; @@ -3622,12 +3816,12 @@ class ClonedNodes { 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)) + if (entry.factor && (entry.factor > entry.shape.factor)) entry.shape.factor = entry.factor; } // now sort shapes in volume decrease order - shapes.sort((a, b) => b.vol*b.factor - a.vol*a.factor); + shapes.sort((a, b) => b.vol * b.factor - a.vol * a.factor); // now set new shape ids according to the sorted order and delete temporary field for (let n = 0; n < shapes.length; ++n) { @@ -3650,7 +3844,8 @@ class ClonedNodes { /** @summary Merge shape lists */ mergeShapesLists(oldlst, newlst) { - if (!oldlst) return newlst; + if (!oldlst) + return newlst; // set geometry to shape object itself for (let n = 0; n < oldlst.length; ++n) { @@ -3700,27 +3895,33 @@ class ClonedNodes { const item = lst[n]; // if enough faces are produced, nothing else is required - if (res.done) { item.ready = true; continue; } + if (res.done) { + item.ready = true; + continue; + } if (!item.ready) { - item._typename = '$$Shape$$'; // let reuse item for direct drawing + item._typename = kShapeType; // let reuse item for direct drawing item.ready = true; if (item.geom === undefined) { item.geom = createGeometry(item.shape); - if (item.geom) created++; // indicate that at least one shape was created + if (item.geom) + created++; // indicate that at least one shape was created } - item.nfaces = countGeometryFaces(item.geom); + item.nfaces = numGeometryFaces(item.geom); } res.shapes++; - if (!item.used) res.notusedshapes++; + 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)) { + else if ((created > 0.01 * lst.length) && (timelimit !== undefined)) { const tm2 = new Date().getTime(); - if (tm2-tm1 > timelimit) return res; + if (tm2 - tm1 > timelimit) + return res; } } @@ -3733,119 +3934,61 @@ class ClonedNodes { * @private */ static formatServerElement(elem) { elem.kind = 2; // special element for geom viewer, used in TGeoPainter - elem.vis = 2; // visibility is alwys on + elem.vis = 2; // visibility is always on const m = elem.matr; delete elem.matr; - if (!m?.length) return elem; + if (!m?.length) + return elem; if (m.length === 16) elem.matrix = m; - else { + else { const nm = elem.matrix = new Array(16); nm.fill(0); nm[0] = nm[5] = nm[10] = nm[15] = 1; if (m.length === 3) { - // translation martix - nm[12] = m[0]; nm[13] = m[1]; nm[14] = m[2]; + // translation matrix + nm[12] = m[0]; + nm[13] = m[1]; + nm[14] = m[2]; } else if (m.length === 4) { // scale matrix - nm[0] = m[0]; nm[5] = m[1]; nm[10] = m[2]; nm[15] = m[3]; + nm[0] = m[0]; + nm[5] = m[1]; + nm[10] = m[2]; + nm[15] = m[3]; } else if (m.length === 9) { // rotation matrix - nm[0] = m[0]; nm[4] = m[1]; nm[8] = m[2]; - nm[1] = m[3]; nm[5] = m[4]; nm[9] = m[5]; - nm[2] = m[6]; nm[6] = m[7]; nm[10] = m[8]; + nm[0] = m[0]; + nm[4] = m[1]; + nm[8] = m[2]; + nm[1] = m[3]; + nm[5] = m[4]; + nm[9] = m[5]; + nm[2] = m[6]; + nm[6] = m[7]; + nm[10] = m[8]; } else console.error(`wrong number of elements ${m.length} in the matrix`); } return elem; } -} - -function createFlippedGeom(geom) { - let pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; - const index = geom.getIndex(); - - if (index) { - // we need to unfold all points to - const arr = index.array, - i0 = geom.drawRange.start; - let ilen = geom.drawRange.count; - if (i0 + ilen > arr.length) ilen = arr.length - i0; - - const dpos = new Float32Array(ilen*3), dnorm = new Float32Array(ilen*3); - for (let ii = 0; ii < ilen; ++ii) { - const k = arr[i0 + ii]; - if ((k < 0) || (k*3 >= pos.length)) - console.log(`strange index ${k*3} totallen = ${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; - } - - const len = pos.length, - newpos = new Float32Array(len), - newnorm = new Float32Array(len); - - // we should swap second and third point in each face - for (let n = 0, shift = 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 - } - - const geomZ = new BufferGeometry(); - geomZ.setAttribute('position', new BufferAttribute(newpos, 3)); - geomZ.setAttribute('normal', new BufferAttribute(newnorm, 3)); - - return geomZ; -} - - -/** @summary Create flipped mesh for the shape - * @desc When transformation matrix includes one or several inversion of axis, - * one should inverse geometry object, otherwise three.js cannot correctly draw it - * @param {Object} shape - TGeoShape object - * @param {Object} material - material - * @private */ -function createFlippedMesh(shape, material) { - if (shape.geomZ === undefined) - shape.geomZ = createFlippedGeom(shape.geom); - - const mesh = new Mesh(shape.geomZ, material); - mesh.scale.copy(new Vector3(1, 1, -1)); - mesh.updateMatrix(); - - mesh._flippedMesh = true; - - return mesh; -} +} // class ClonedNodes /** @summary extract code of Box3.expandByObject * @desc Major difference - do not traverse hierarchy, support InstancedMesh * @private */ function getBoundingBox(node, box3, local_coordinates) { - if (!node?.geometry) return box3; + if (!node?.geometry) + return box3; - if (!box3) box3 = new Box3().makeEmpty(); + if (!box3) + box3 = new THREE.Box3().makeEmpty(); if (node.isInstancedMesh) { - const m = new Matrix4(), b = new Box3().makeEmpty(); + const m = new THREE.Matrix4(), b = new THREE.Box3().makeEmpty(); node.geometry.computeBoundingBox(); @@ -3857,15 +4000,17 @@ function getBoundingBox(node, box3, local_coordinates) { return box3; } - if (!local_coordinates) node.updateWorldMatrix(false, false); + if (!local_coordinates) + node.updateWorldMatrix(false, false); - const v1 = new Vector3(), attribute = node.geometry.attributes?.position; + const v1 = new THREE.Vector3(), attribute = node.geometry.attributes?.position; if (attribute !== undefined) { for (let i = 0, l = attribute.count; i < l; i++) { // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); v1.fromBufferAttribute(attribute, i); - if (!local_coordinates) v1.applyMatrix4(node.matrixWorld); + if (!local_coordinates) + v1.applyMatrix4(node.matrixWorld); box3.expandByPoint(v1); } } @@ -3876,7 +4021,8 @@ function getBoundingBox(node, box3, local_coordinates) { /** @summary Cleanup shape entity * @private */ function cleanupShape(shape) { - if (!shape) return; + if (!shape) + return; if (isFunc(shape.geom?.dispose)) shape.geom.dispose(); @@ -3894,21 +4040,23 @@ function cleanupShape(shape) { * @param origin - camera position used to provide sorting * @param method - name of sorting method like 'pnt', 'ray', 'size', 'dflt' */ function produceRenderOrder(toplevel, origin, method, clones) { - const raycast = new Raycaster(); + const raycast = new THREE.Raycaster(); function setdefaults(top) { - if (!top) return; - top.traverse(obj => { + top?.traverse(obj => { obj.renderOrder = obj.defaultOrder || 0; - if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled + 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.$jsroot_depth === undefined) + // return; - if (!obj.children) return; + if (!obj.children) + return; for (let k = 0; k < obj.children.length; ++k) { const chld = obj.children[k]; @@ -3932,11 +4080,11 @@ function produceRenderOrder(toplevel, origin, method, clones) { if (arr.length > 1000) { // too many of them, just set basic level and exit for (let i = 0; i < arr.length; ++i) - arr[i].renderOrder = (minorder + maxorder)/2; + arr[i].renderOrder = (minorder + maxorder) / 2; return false; } - const tmp_vect = new Vector3(); + const tmp_vect = new THREE.Vector3(); // first calculate distance to the camera // it gives preliminary order of volumes @@ -3948,8 +4096,8 @@ function produceRenderOrder(toplevel, origin, method, clones) { mesh.$jsroot_box3 = box3 = getBoundingBox(mesh); if (method === 'size') { - const sz = box3.getSize(new Vector3()); - mesh.$jsroot_distance = sz.x*sz.y*sz.z; + const sz = box3.getSize(new THREE.Vector3()); + mesh.$jsroot_distance = sz.x * sz.y * sz.z; continue; } @@ -3959,7 +4107,7 @@ function produceRenderOrder(toplevel, origin, method, clones) { } let dist = Math.min(origin.distanceTo(box3.min), origin.distanceTo(box3.max)); - const pnt = new Vector3(box3.min.x, box3.min.y, box3.max.z); + const 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); @@ -3986,7 +4134,7 @@ function produceRenderOrder(toplevel, origin, method, clones) { } if (method === 'ray') { - for (let i=arr.length - 1; i >= 0; --i) { + for (let i = arr.length - 1; i >= 0; --i) { const mesh = arr[i], box3 = mesh.$jsroot_box3; let intersects, direction = box3.getCenter(tmp_vect); @@ -4001,7 +4149,6 @@ function produceRenderOrder(toplevel, origin, method, clones) { for (let 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; @@ -4009,22 +4156,24 @@ function produceRenderOrder(toplevel, origin, method, clones) { if ((intersects.indexOf(mesh) < 0) && (ntry > 0)) console.log(`MISS ${clones?.resolveStack(mesh.stack)?.name}`); - if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break; + if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) + break; const pos = mesh.geometry.attributes.position.array; - direction = new Vector3((pos[0]+pos[3]+pos[6])/3, (pos[1]+pos[4]+pos[7])/3, (pos[2]+pos[5]+pos[8])/3); + 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 (let k1 = 0; k1 < intersects.length - 1; ++k1) { - const mesh1 = intersects[k1], mesh2 = intersects[k1+1], - i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; - if (i1 < i2) continue; + const mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], + i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; + if (i1 < i2) + continue; for (let ii = i2; ii < i1; ++ii) { - resort[ii] = resort[ii+1]; + resort[ii] = resort[ii + 1]; resort[ii].$jsroot_index = ii; } resort[i1] = mesh2; @@ -4034,7 +4183,7 @@ function produceRenderOrder(toplevel, origin, method, clones) { } for (let i = 0; i < resort.length; ++i) { - resort[i].renderOrder = Math.round(maxorder - (i+1) / (resort.length + 1) * (maxorder - minorder)); + resort[i].renderOrder = Math.round(maxorder - (i + 1) / (resort.length + 1) * (maxorder - minorder)); delete resort[i].$jsroot_index; delete resort[i].$jsroot_distance; } @@ -4048,14 +4197,16 @@ function produceRenderOrder(toplevel, origin, method, clones) { traverse(obj, lvl, arr); - if (!arr.length) return; + if (!arr.length) + return; if (minorder === maxorder) { for (let 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; + did_sort = sort(arr, minorder, maxorder); + if (!did_sort) + minorder = maxorder = (minorder + maxorder) / 2; } for (let k = 0; k < arr.length; ++k) { @@ -4067,7 +4218,7 @@ function produceRenderOrder(toplevel, origin, method, clones) { min = max - (maxorder - minorder) / (arr.length + 2); } - process(next, lvl+1, min, max); + process(next, lvl + 1, min, max); } } @@ -4106,9 +4257,105 @@ function getShapeIcon(shape) { return 'img_geotube'; } -export { kindGeo, kindEve, kindShape, +function runGeoWorker(ctxt, data, doPost) { + if (isStr(data)) { + console.log(`Worker get message ${data}`); + return; + } + + if (!isObject(data)) + return; + + data.tm1 = new Date().getTime(); + + if (data.init) { + if (data.clones) { + ctxt.clones = new ClonedNodes(null, data.clones); + ctxt.clones.setVisLevel(data.vislevel); + ctxt.clones.setMaxVisNodes(data.maxvisnodes); + ctxt.clones.sortmap = data.sortmap; + delete data.clones; + } + + data.tm2 = new Date().getTime(); + + return doPost(data); + } + + if (data.shapes) { + // this is task to create geometries in the worker + + const shapes = data.shapes, transferables = []; + + // build all shapes up to specified limit, also limit execution time + for (let n = 0; n < 100; ++n) { + const res = ctxt.clones.buildShapes(shapes, data.limit, 1000); + if (res.done) + break; + doPost({ progress: `Worker creating: ${res.shapes} / ${shapes.length} shapes, ${res.faces} faces` }); + } + + for (let n = 0; n < shapes.length; ++n) { + const item = shapes[n]; + + if (item.geom) { + let bufgeom; + if (item.geom instanceof THREE.BufferGeometry) + bufgeom = item.geom; + else + bufgeom = new THREE.BufferGeometry().fromGeometry(item.geom); + + item.buf_pos = bufgeom.attributes.position.array; + item.buf_norm = bufgeom.attributes.normal.array; + + // use nice feature of HTML workers with transferable + // we allow to take ownership of buffer from local array + // therefore buffer content not need to be copied + transferables.push(item.buf_pos.buffer, item.buf_norm.buffer); + + delete item.geom; + } + + delete item.shape; // no need to send back shape + } + + data.tm2 = new Date().getTime(); + + return doPost(data, transferables); + } + + if (data.collect !== undefined) { + // this is task to collect visible nodes using camera position + + // first mark all visible flags + ctxt.clones.setVisibleFlags(data.flags); + ctxt.clones.setVisLevel(data.vislevel); + ctxt.clones.setMaxVisNodes(data.maxvisnodes); + + delete data.flags; + + ctxt.clones.produceIdShifts(); + + let matrix = null; + if (data.matrix) + matrix = new THREE.Matrix4().fromArray(data.matrix); + delete data.matrix; + + const res = ctxt.clones.collectVisibles(data.collect, createFrustum(matrix)); + + data.new_nodes = res.lst; + data.complete = res.complete; // inform if all nodes are selected + + data.tm2 = new Date().getTime(); + + return doPost(data); + } +} + +export { kindGeo, kindEve, kindShape, kGetMesh, kDeleteMesh, clTGeoBBox, clTGeoCompositeShape, geoCfg, geoBITS, ClonedNodes, isSameStack, checkDuplicates, getObjectName, testGeoBit, setGeoBit, toggleGeoBit, setInvisibleAll, countNumShapes, getNodeKind, produceRenderOrder, createFlippedGeom, createFlippedMesh, cleanupShape, createGeometry, numGeometryFaces, numGeometryVertices, createServerGeometry, createMaterial, - projectGeometry, countGeometryFaces, createFrustum, createProjectionMatrix, getBoundingBox, provideObjectInfo, getShapeIcon }; + projectGeometry, createFrustum, createProjectionMatrix, + getBoundingBox, provideObjectInfo, getShapeIcon, runGeoWorker }; diff --git a/modules/geom/geoworker.mjs b/modules/geom/geoworker.mjs new file mode 100644 index 000000000..e74a75c53 --- /dev/null +++ b/modules/geom/geoworker.mjs @@ -0,0 +1,15 @@ +import { runGeoWorker } from './geobase.mjs'; + +const ctxt = {}; + +onmessage = function(e) { + if (!e?.data) + return; + + if (typeof e.data === 'string') { + console.log(`Worker get message ${e.data}`); + return; + } + + runGeoWorker(ctxt, e.data, postMessage); +}; diff --git a/modules/geom/nodeworker.mjs b/modules/geom/nodeworker.mjs new file mode 100644 index 000000000..d81d1e045 --- /dev/null +++ b/modules/geom/nodeworker.mjs @@ -0,0 +1,6 @@ +import { runGeoWorker } from './geobase.mjs'; +import { parentPort } from 'node:worker_threads'; + +const ctxt = {}; + +parentPort.on('message', msg => runGeoWorker(ctxt, msg, reply => parentPort.postMessage(reply))); diff --git a/modules/gpad/RAxisPainter.mjs b/modules/gpad/RAxisPainter.mjs index f651afae5..dc6bc8346 100644 --- a/modules/gpad/RAxisPainter.mjs +++ b/modules/gpad/RAxisPainter.mjs @@ -1,8 +1,8 @@ -import { settings, isFunc } from '../core.mjs'; import { select as d3_select, pointer as d3_pointer, drag as d3_drag, timeFormat as d3_timeFormat, scaleTime as d3_scaleTime, scaleSymlog as d3_scaleSymlog, scaleLog as d3_scaleLog, scaleLinear as d3_scaleLinear } from '../d3.mjs'; +import { settings, isFunc, urlClassPrefix } from '../core.mjs'; import { makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; import { AxisPainterMethods, chooseTimeFormat } from './TAxisPainter.mjs'; import { createMenu } from '../gui/menu.mjs'; @@ -68,10 +68,11 @@ class RAxisPainter extends RObjectPainter { /** @summary Configure axis painter * @desc Axis can be drawn inside frame <g> group with offset to 0 point for the frame - * Therefore one should distinguish when caclulated coordinates used for axis drawing itself or for calculation of frame coordinates + * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates * @private */ configureAxis(name, min, max, smin, smax, vertical, frame_range, axis_range, opts) { - if (!opts) opts = {}; + if (!opts) + opts = {}; this.name = name; this.full_min = min; this.full_max = max; @@ -79,7 +80,7 @@ class RAxisPainter extends RObjectPainter { this.vertical = vertical; this.log = false; const _log = this.v7EvalAttr('log', 0), - _symlog = this.v7EvalAttr('symlog', 0); + _symlog = this.v7EvalAttr('symlog', 0); this.reverse = opts.reverse || false; if (this.v7EvalAttr('time')) { @@ -88,24 +89,25 @@ class RAxisPainter extends RObjectPainter { let toffset = this.v7EvalAttr('timeOffset'); if (toffset !== undefined) { toffset = parseFloat(toffset); - if (Number.isFinite(toffset)) this.timeoffset = toffset*1000; + if (Number.isFinite(toffset)) + this.timeoffset = toffset * 1000; } } else if (this.axis?.fLabelsIndex) { this.kind = kAxisLabels; delete this.own_labels; } else if (opts.labels) this.kind = kAxisLabels; - else + else this.kind = kAxisNormal; - if (this.kind === kAxisTime) this.func = d3_scaleTime().domain([this.convertDate(smin), this.convertDate(smax)]); else if (_symlog && (_symlog > 0)) { this.symlog = _symlog; this.func = d3_scaleSymlog().constant(_symlog).domain([smin, smax]); } else if (_log) { - if (smax <= 0) smax = 1; + if (smax <= 0) + smax = 1; if ((smin <= 0) || (smin >= smax)) smin = smax * 0.0001; this.log = true; @@ -135,7 +137,7 @@ class RAxisPainter extends RObjectPainter { if (this.kind === kAxisTime) this.gr = val => this.func(this.convertDate(val)); else if (this.log) - this.gr = val => (val < this.scale_min) ? (this.vertical ? this.func.range()[0]+5 : -5) : this.func(val); + this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; else this.gr = this.func; @@ -145,14 +147,14 @@ class RAxisPainter extends RObjectPainter { this.nticks = ndiv % 100; this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv/10000); + this.nticks3 = Math.floor(ndiv / 10000); - if (this.nticks > 20) this.nticks = 20; + this.nticks = Math.min(this.nticks, 20); const gr_range = Math.abs(this.gr_range) || 100; if (this.kind === kAxisTime) { - if (this.nticks > 8) this.nticks = 8; + this.nticks = Math.min(this.nticks, 8); const scale_range = this.scale_max - this.scale_min, tf2 = chooseTimeFormat(scale_range / gr_range, false); @@ -172,7 +174,8 @@ class RAxisPainter extends RObjectPainter { this.nticks2 = 1; } this.noexp = this.v7EvalAttr('noexp', false); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && (this.logbase === 10)) this.noexp = true; + if ((this.scale_max < 300) && (this.scale_min > 0.3) && (this.logbase === 10)) + this.noexp = true; this.moreloglabels = this.v7EvalAttr('moreloglbls', false); this.format = this.formatLog; @@ -205,10 +208,12 @@ class RAxisPainter extends RObjectPainter { formatLabels(d) { const indx = Math.round(d); if (this.axis?.fLabelsIndex) { - if ((indx < 0) || (indx >= this.axis.fNBinsNoOver)) return null; + if ((indx < 0) || (indx >= this.axis.fNBinsNoOver)) + return null; for (let i = 0; i < this.axis.fLabelsIndex.length; ++i) { const pair = this.axis.fLabelsIndex[i]; - if (pair.second === indx) return pair.first; + if (pair.second === indx) + return pair.first; } } else { const labels = this.getObject().fLabels; @@ -220,15 +225,18 @@ class RAxisPainter extends RObjectPainter { /** @summary Creates array with minor/middle/major ticks */ createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) this.noticksopt = true; + if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) + this.noticksopt = true; const ticks = this.produceTicks(this.nticks), handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: ticks, middle: ticks, major: ticks }; if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min)*1e-5; - if (res[0] > this.scale_min + delta) res.unshift(this.scale_min); - if (res[res.length-1] < this.scale_max - delta) res.push(this.scale_max); + const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; + if (res.at(0) > this.scale_min + delta) + res.unshift(this.scale_min); + if (res.at(-1) < this.scale_max - delta) + res.push(this.scale_max); return res; } @@ -238,11 +246,12 @@ class RAxisPainter extends RObjectPainter { const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) + if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { + else if ((this.nticks3 > 1) && !this.log) { handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) handle.minor = handle.middle; + if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) + handle.minor = handle.middle; } } @@ -251,11 +260,13 @@ class RAxisPainter extends RObjectPainter { }; handle.next = function(doround) { - if (this.nminor >= this.minor.length) return false; + if (this.nminor >= this.minor.length) + return false; this.tick = this.minor[this.nminor++]; this.grpos = this.func(this.tick); - if (doround) this.grpos = Math.round(this.grpos); + if (doround) + this.grpos = Math.round(this.grpos); this.kind = 3; if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { @@ -275,8 +286,7 @@ class RAxisPainter extends RObjectPainter { }; handle.next_major_grpos = function() { - if (this.nmajor >= this.major.length) return null; - return this.func(this.major[this.nmajor]); + return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); }; handle.get_modifier = function() { return null; }; @@ -286,45 +296,62 @@ class RAxisPainter extends RObjectPainter { // at the moment when drawing labels, we can try to find most optimal text representation for them - if ((this.kind === kAxisNormal) && !this.log && (handle.major.length > 0)) { + if ((this.kind === kAxisNormal) && !this.log && handle.major.length) { let maxorder = 0, minorder = 0, exclorder3 = false; if (!optionNoexp) { - const maxtick = Math.max(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - mintick = Math.min(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick)/3)*3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick)/3)*3 : 0; + const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, + ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 + exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } + if (maxtick || mintick) { + maxorder = Math.max(ord1, ord2) + 3; + minorder = Math.min(ord1, ord2) - 3; + } } // now try to find best combination of order and ndig for labels let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - for (let order = minorder; order <= maxorder; order+=3) { - if (exclorder3 && (order===3)) continue; + for (let order = minorder; order <= maxorder; order += 3) { + if (exclorder3 && (order === 3)) + continue; this.order = order; this.ndig = 0; let lbls = [], indx = 0, totallen = 0; - while (indx<handle.major.length) { - const lbl = this.format(handle.major[indx], true); - if (lbls.indexOf(lbl) < 0) { + while (indx < handle.major.length) { + const v0 = handle.major[indx], + lbl = this.format(v0, true); + + let bad_value = lbls.indexOf(lbl) >= 0; + if (!bad_value) { + try { + const v1 = parseFloat(lbl) * Math.pow(10, order); + bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); + } catch { + console.warn('Failure by parsing of', lbl); + bad_value = true; + } + } + if (bad_value) { + if (++this.ndig > 15) { + totallen += 1e10; + break; // not too many digits, anyway it will be exponential + } + lbls = []; + indx = totallen = 0; + } else { lbls.push(lbl); totallen += lbl.length; indx++; - continue; } - if (++this.ndig > 11) break; // not too many digits, anyway it will be exponential - lbls = []; indx = 0; totallen = 0; } - // for order === 0 we should virually remove '0.' and extra label on top + // for order === 0 we should virtually remove '0.' and extra label on top if (!order && (this.ndig < 4)) totallen -= (handle.major.length * 2 + 3); @@ -339,8 +366,10 @@ class RAxisPainter extends RObjectPainter { this.ndig = bestndig; if (optionInt) { - if (this.order) console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); + if (this.order) + console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); + if (this.ndig) + console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); this.ndig = 0; this.order = 0; } @@ -351,18 +380,25 @@ class RAxisPainter extends RObjectPainter { /** @summary Is labels should be centered */ isCenteredLabels() { - if (this.kind === kAxisLabels) return true; - if (this.kind === 'log') return false; + if (this.kind === kAxisLabels) + return true; + if (this.kind === 'log') + return false; return this.v7EvalAttr('labels_center', false); } + /** @summary Is labels should be rotated */ + isRotateLabels() { return false; } + /** @summary Used to move axis labels instead of zooming * @private */ processLabelsMove(arg, pos) { - if (this.optionUnlab || !this.axis_g) return false; + if (this.optionUnlab || !this.axis_g) + return false; const label_g = this.axis_g.select('.axis_labels'); - if (!label_g || (label_g.size() !== 1)) return false; + if (!label_g || (label_g.size() !== 1)) + return false; if (arg === 'start') { // no moving without labels @@ -378,7 +414,7 @@ class RAxisPainter extends RObjectPainter { .call(addHighlightStyle, true); if (this.vertical) this.drag_pos0 = pos[0]; - else + else this.drag_pos0 = pos[1]; @@ -389,12 +425,13 @@ class RAxisPainter extends RObjectPainter { if (this.vertical) { offset += Math.round(pos[0] - this.drag_pos0); - label_g.attr('transform', `translate(${offset})`); + makeTranslate(label_g, offset); } else { offset += Math.round(pos[1] - this.drag_pos0); - label_g.attr('transform', `translate(0,${offset})`); + makeTranslate(label_g, 0, offset); } - if (!offset) label_g.attr('transform', null); + if (!offset) + makeTranslate(label_g); if (arg === 'stop') { label_g.select('rect.drag').remove(); @@ -412,7 +449,8 @@ class RAxisPainter extends RObjectPainter { /** @summary Add interactive elements to draw axes title */ addTitleDrag(title_g, side) { - if (!settings.MoveResize || this.isBatchMode()) return; + if (!settings.MoveResize || this.isBatchMode()) + return; let drag_rect = null, acc_x, acc_y, new_x, new_y, alt_pos, curr_indx; @@ -425,7 +463,7 @@ class RAxisPainter extends RObjectPainter { evnt.sourceEvent.stopPropagation(); const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = this.vertical ? box.height : box.width; + title_length = this.vertical ? box.height : box.width; new_x = acc_x = title_g.property('shift_x'); new_y = acc_y = title_g.property('shift_y'); @@ -436,18 +474,18 @@ class RAxisPainter extends RObjectPainter { curr_indx = (this.titlePos === 'left') ? 0 : 2; // let d = ((this.gr_range > 0) && this.vertical) ? title_length : 0; - alt_pos = [0, this.gr_range/2, this.gr_range]; // possible positions + alt_pos = [0, this.gr_range / 2, this.gr_range]; // possible positions const off = this.vertical ? -title_length : title_length, - swap = this.isReverseAxis() ? 2 : 0; + swap = this.isReverseAxis() ? 2 : 0; if (this.title_align === 'middle') { - alt_pos[swap] += off/2; - alt_pos[2-swap] -= off/2; + alt_pos[swap] += off / 2; + alt_pos[2 - swap] -= off / 2; } else if ((this.title_align === 'begin') ^ this.isTitleRotated()) { - alt_pos[1] -= off/2; - alt_pos[2-swap] -= off; + alt_pos[1] -= off / 2; + alt_pos[2 - swap] -= off; } else { // end alt_pos[swap] += off; - alt_pos[1] += off/2; + alt_pos[1] += off / 2; } alt_pos[curr_indx] = this.vertical ? acc_y : acc_x; @@ -459,65 +497,73 @@ class RAxisPainter extends RObjectPainter { .attr('height', box.height) .style('cursor', 'move') .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) return; + // .style('pointer-events','none'); // let forward double click to underlying elements + }).on('drag', evnt => { + if (!drag_rect) + return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - acc_x += evnt.dx; - acc_y += evnt.dy; + acc_x += evnt.dx; + acc_y += evnt.dy; - const p = this.vertical ? acc_y : acc_x; - let set_x, set_y, besti = 0; + const p = this.vertical ? acc_y : acc_x; + let set_x, set_y, besti = 0; - for (let i = 1; i < 3; ++i) - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) besti = i; + for (let i = 1; i < 3; ++i) { + if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) + besti = i; + } - if (this.vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_x = alt_pos[besti]; - set_y = acc_y; - } + if (this.vertical) { + set_x = acc_x; + set_y = alt_pos[besti]; + } else { + set_x = alt_pos[besti]; + set_y = acc_y; + } - new_x = set_x; new_y = set_y; curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - }).on('end', evnt => { - if (!drag_rect) return; + new_x = set_x; + new_y = set_y; + curr_indx = besti; + makeTranslate(title_g, new_x, new_y); + }).on('end', evnt => { + if (!drag_rect) + return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); + evnt.sourceEvent.preventDefault(); + evnt.sourceEvent.stopPropagation(); - const basepos = title_g.property('basepos') || 0; + const basepos = title_g.property('basepos') || 0; - title_g.property('shift_x', new_x) + title_g.property('shift_x', new_x) .property('shift_y', new_y); - this.titleOffset = (this.vertical ? basepos - new_x : new_y - basepos) * side; + this.titleOffset = (this.vertical ? basepos - new_x : new_y - basepos) * side; - if (curr_indx === 1) - this.titlePos = 'center'; - else if (curr_indx === 0) - this.titlePos = 'left'; - else - this.titlePos = 'right'; + if (curr_indx === 1) + this.titlePos = 'center'; + else if (curr_indx === 0) + this.titlePos = 'left'; + else + this.titlePos = 'right'; - this.changeAxisAttr(0, 'title_position', this.titlePos, 'title_offset', this.titleOffset / this.scalingSize); + this.changeAxisAttr(0, 'title_position', this.titlePos, 'title_offset', this.titleOffset / this.scalingSize); - drag_rect.remove(); - drag_rect = null; - }); + drag_rect.remove(); + drag_rect = null; + }); title_g.style('cursor', 'move').call(drag_move); } /** @summary checks if value inside graphical range, taking into account delta */ isInsideGrRange(pos, delta1, delta2) { - if (!delta1) delta1 = 0; - if (delta2 === undefined) delta2 = delta1; + if (!delta1) + delta1 = 0; + if (delta2 === undefined) + delta2 = delta1; if (this.gr_range < 0) return (pos >= this.gr_range - delta2) && (pos <= delta1); return (pos >= -delta1) && (pos <= this.gr_range + delta2); @@ -525,7 +571,8 @@ class RAxisPainter extends RObjectPainter { /** @summary returns graphical range */ getGrRange(delta) { - if (!delta) delta = 0; + if (!delta) + delta = 0; if (this.gr_range < 0) return this.gr_range - delta; return this.gr_range + delta; @@ -543,7 +590,7 @@ class RAxisPainter extends RObjectPainter { if (this.endingSize && this.endingStyle) { let sz = (this.gr_range > 0) ? -this.endingSize : this.endingSize; - const sz7 = Math.round(sz*0.7); + const sz7 = Math.round(sz * 0.7); sz = Math.round(sz); if (this.vertical) ending = `l${sz7},${sz}M0,${this.gr_range}l${-sz7},${sz}`; @@ -561,7 +608,8 @@ class RAxisPainter extends RObjectPainter { * @return {Object} with gaps on left and right side * @private */ drawTicks(axis_g, side, main_draw) { - if (main_draw) this.ticks = []; + if (main_draw) + this.ticks = []; this.handle.reset(); @@ -572,26 +620,30 @@ class RAxisPainter extends RObjectPainter { } while (this.handle.next(true)) { - let h1 = Math.round(this.ticksSize/4), h2 = 0; + let h1 = Math.round(this.ticksSize / 4), h2; if (this.handle.kind < 3) - h1 = Math.round(this.ticksSize/2); + h1 = Math.round(this.ticksSize / 2); const grpos = this.handle.grpos - this.axis_shift; - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(grpos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) continue; + if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(grpos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) + continue; if (this.handle.kind === 1) { // if not showing labels, not show large tick - if ((this.kind === kAxisLabels) || (this.format(this.handle.tick, true) !== null)) h1 = this.ticksSize; + if ((this.kind === kAxisLabels) || (this.format(this.handle.tick, true) !== null)) + h1 = this.ticksSize; - if (main_draw) this.ticks.push(grpos); // keep graphical positions of major ticks + if (main_draw) + this.ticks.push(grpos); // keep graphical positions of major ticks } if (ticks_plusminus > 0) h2 = -h1; else if (side < 0) { - h2 = -h1; h1 = 0; + h2 = -h1; + h1 = 0; } else h2 = 0; @@ -603,18 +655,20 @@ class RAxisPainter extends RObjectPainter { .attr('d', res) .style('stroke', this.ticksColor || this.lineatt.color) .style('stroke-width', !this.ticksWidth || (this.ticksWidth === 1) ? null : this.ticksWidth); - } + } - const gap0 = Math.round(0.25*this.ticksSize), gap = Math.round(1.25*this.ticksSize); - return { '-1': (side > 0) || ticks_plusminus ? gap : gap0, - 1: (side < 0) || ticks_plusminus ? gap : gap0 }; + const gap0 = Math.round(0.25 * this.ticksSize), gap = Math.round(1.25 * this.ticksSize); + return { + '-1': (side > 0) || ticks_plusminus ? gap : gap0, + 1: (side < 0) || ticks_plusminus ? gap : gap0 + }; } /** @summary Performs labels drawing * @return {Promise} with gaps in both direction */ async drawLabels(axis_g, side, gaps) { const center_lbls = this.isCenteredLabels(), - rotate_lbls = this.labelsFont.angle !== 0, + rotate_lbls = Boolean(this.labelsFont.angle), label_g = axis_g.append('svg:g').attr('class', 'axis_labels').property('side', side), lbl_pos = this.handle.lbl_pos || this.handle.major; let textscale = 1, maxtextlen = 0, lbls_tilt = false, @@ -628,9 +682,7 @@ class RAxisPainter extends RObjectPainter { const textwidth = this.result_width; if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { - let maxwidth = this.gap_before*0.45 + this.gap_after*0.45; - if (!this.gap_before) maxwidth = 0.9*this.gap_after; else - if (!this.gap_after) maxwidth = 0.9*this.gap_before; + const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); textscale = Math.min(textscale, maxwidth / textwidth); } @@ -639,7 +691,7 @@ class RAxisPainter extends RObjectPainter { const scale = textscale * (lbls_tilt ? 3 : 1); if ((scale > 0.0001) && (scale < 1)) - painter.scaleTextDrawing(1/scale, label_g); + painter.scaleTextDrawing(1 / scale, label_g); } const fix_offset = Math.round((this.vertical ? -side : side) * this.labelsOffset), @@ -647,94 +699,97 @@ class RAxisPainter extends RObjectPainter { let lastpos = 0; if (fix_offset) - label_g.attr('transform', this.vertical ? `translate(${fix_offset})` : `translate(0,${fix_offset})`); + makeTranslate(label_g, this.vertical ? fix_offset : 0, this.vertical ? 0 : fix_offset); label_g.property('fix_offset', fix_offset); - this.startTextDrawing(this.labelsFont, 'font', label_g); - - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - const lbl = this.format(lbl_pos[nmajor], true); - if (lbl === null) continue; + return this.startTextDrawingAsync(this.labelsFont, 'font', label_g).then(() => { + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + const lbl = this.format(lbl_pos[nmajor], true); + if (lbl === null) + continue; - const arg = { text: lbl, latex: 1, draw_g: label_g }; - let pos = Math.round(this.func(lbl_pos[nmajor])); + const arg = { text: lbl, latex: 1, draw_g: label_g }; + let pos = Math.round(this.func(lbl_pos[nmajor])); - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor-1]))) : 0; - arg.gap_after = (nmajor < lbl_pos.length-1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor+1])-pos)) : 0; + arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; + arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - (this.vertical ? 0.5*gap : -0.5*gap)); - if (!this.isInsideGrRange(pos, 5)) continue; - } + if (center_lbls) { + const gap = arg.gap_after || arg.gap_before; + pos = Math.round(pos - (this.vertical ? 0.5 * gap : -0.5 * gap)); + if (!this.isInsideGrRange(pos, 5)) + continue; + } - maxtextlen = Math.max(maxtextlen, lbl.length); + maxtextlen = Math.max(maxtextlen, lbl.length); - pos -= this.axis_shift; + pos -= this.axis_shift; - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(pos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) continue; + if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(pos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) + continue; - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += this.labelsFont.size; + if (this.vertical) { + arg.x = fix_coord; + arg.y = pos; + arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); + } else { + arg.x = pos; + arg.y = fix_coord; + arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); + if (this.log && !this.noexp && !this.vertical && arg.align === 23) { + arg.align = 21; + arg.y += this.labelsFont.size; + } } - } - arg.post_process = process_drawtext_ready; + arg.post_process = process_drawtext_ready; - this.drawText(arg); + this.drawText(arg); - if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos-lastpos); - textscale = Math.min(textscale, 0.9*axis_step/this.labelsFont.size); - } + if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + const axis_step = Math.abs(pos - lastpos); + textscale = Math.min(textscale, 0.9 * axis_step / this.labelsFont.size); + } - lastpos = pos; - } + lastpos = pos; + } - if (this.order) { - this.drawText({ x: this.vertical ? side*5 : this.getGrRange(5), - y: this.has_obstacle ? fix_coord : (this.vertical ? this.getGrRange(3) : -3*side), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g }); - } + if (this.order) { + this.drawText({ x: this.vertical ? side * 5 : this.getGrRange(5), + y: this.has_obstacle ? fix_coord : (this.vertical ? this.getGrRange(3) : -3 * side), + align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), + latex: 1, + text: '#times' + this.formatExp(10, this.order), + draw_g: label_g }); + } - return this.finishTextDrawing(label_g).then(() => { - if (lbls_tilt) { - label_g.selectAll('text').each(function() { + return this.finishTextDrawing(label_g); + }).then(() => { + if (lbls_tilt) { + label_g.selectAll('text').each(function() { const txt = d3_select(this), tr = txt.attr('transform'); txt.attr('transform', tr + ' rotate(25)').style('text-anchor', 'start'); - }); + }); } if (this.vertical) - gaps[side] += Math.round(rotate_lbls ? 1.2*max_lbl_height : max_lbl_width + 0.4*this.labelsFont.size) - side*fix_offset; + gaps[side] += Math.round(rotate_lbls ? 1.2 * max_lbl_height : max_lbl_width + 0.4 * this.labelsFont.size) - side * fix_offset; else { - const tilt_height = lbls_tilt ? max_lbl_width * Math.sin(25/180*Math.PI) + max_lbl_height * (Math.cos(25/180*Math.PI) + 0.2) : 0; + const tilt_height = lbls_tilt ? max_lbl_width * Math.sin(25 / 180 * Math.PI) + max_lbl_height * (Math.cos(25 / 180 * Math.PI) + 0.2) : 0; - gaps[side] += Math.round(Math.max(rotate_lbls ? max_lbl_width + 0.4*this.labelsFont.size : 1.2*max_lbl_height, 1.2*this.labelsFont.size, tilt_height)) + fix_offset; + gaps[side] += Math.round(Math.max(rotate_lbls ? max_lbl_width + 0.4 * this.labelsFont.size : 1.2 * max_lbl_height, 1.2 * this.labelsFont.size, tilt_height)) + fix_offset; } return gaps; }); } - /** @summary Add zomming rect to axis drawing */ + /** @summary Add zooming rect to axis drawing */ addZoomingRect(axis_g, side, lgaps) { if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { const sz = Math.max(lgaps[side], 10), - d = this.vertical ? `v${this.gr_range}h${-side*sz}v${-this.gr_range}` : `h${this.gr_range}v${side*sz}h${-this.gr_range}`; + d = this.vertical ? `v${this.gr_range}h${-side * sz}v${-this.gr_range}` : `h${this.gr_range}v${side * sz}h${-this.gr_range}`; axis_g.append('svg:path') .attr('d', `M0,0${d}z`) .attr('class', 'axis_zoom') @@ -755,41 +810,46 @@ class RAxisPainter extends RObjectPainter { const title_g = axis_g.append('svg:g').attr('class', 'axis_title'), rotated = this.isTitleRotated(); - let title_shift_x = 0, title_shift_y = 0, title_basepos = 0; - this.startTextDrawing(this.titleFont, 'font', title_g); + return this.startTextDrawingAsync(this.titleFont, 'font', title_g).then(() => { + let title_shift_x, title_shift_y, title_basepos; - this.title_align = this.titleCenter ? 'middle' : (this.titleOpposite ^ (this.isReverseAxis() || rotated) ? 'begin' : 'end'); + this.title_align = this.titleCenter ? 'middle' : (this.titleOpposite ^ (this.isReverseAxis() || rotated) ? 'begin' : 'end'); - if (this.vertical) { - title_basepos = Math.round(-side*(lgaps[side])); - title_shift_x = title_basepos + Math.round(-side*this.titleOffset); - title_shift_y = Math.round(this.titleCenter ? this.gr_range/2 : (this.titleOpposite ? 0 : this.gr_range)); - this.drawText({ align: [this.title_align, ((side < 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g }); - } else { - title_shift_x = Math.round(this.titleCenter ? this.gr_range/2 : (this.titleOpposite ? 0 : this.gr_range)); - title_basepos = Math.round(side*lgaps[side]); - title_shift_y = title_basepos + Math.round(side*this.titleOffset); - this.drawText({ align: [this.title_align, ((side > 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g }); - } + if (this.vertical) { + title_basepos = Math.round(-side * (lgaps[side])); + title_shift_x = title_basepos + Math.round(-side * this.titleOffset); + title_shift_y = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); + this.drawText({ + align: [this.title_align, ((side < 0) ^ rotated ? 'top' : 'bottom')], + text: this.fTitle, draw_g: title_g + }); + } else { + title_shift_x = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); + title_basepos = Math.round(side * lgaps[side]); + title_shift_y = title_basepos + Math.round(side * this.titleOffset); + this.drawText({ + align: [this.title_align, ((side > 0) ^ rotated ? 'top' : 'bottom')], + text: this.fTitle, draw_g: title_g + }); + } - makeTranslate(title_g, title_shift_x, title_shift_y) - .property('basepos', title_basepos) - .property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); + makeTranslate(title_g, title_shift_x, title_shift_y) + .property('basepos', title_basepos) + .property('shift_x', title_shift_x) + .property('shift_y', title_shift_y); - this.addTitleDrag(title_g, side); + this.addTitleDrag(title_g, side); - return this.finishTextDrawing(title_g); + return this.finishTextDrawing(title_g); + }); } /** @summary Extract major draw attributes, which are also used in interactive operations * @private */ extractDrawAttributes(scalingSize) { const pp = this.getPadPainter(), - rect = pp?.getPadRect() || { width: 10, height: 10 }; + rect = pp?.getPadRect() || { width: 10, height: 10 }; this.scalingSize = scalingSize || (this.vertical ? rect.width : rect.height); @@ -826,10 +886,12 @@ class RAxisPainter extends RObjectPainter { // TODO: remove old scaling factors for labels and ticks this.labelsFont = this.v7EvalFont('labels', { size: scalingSize ? 0.05 : 0.03 }); this.labelsFont.roundAngle(180); - if (this.labelsFont.angle) this.labelsFont.angle = 270; + if (this.labelsFont.angle) + this.labelsFont.angle = 270; this.labelsOffset = this.v7EvalLength('labels_offset', this.scalingSize, 0); - if (scalingSize) this.ticksSize = this.labelsFont.size*0.5; // old lego scaling factor + if (scalingSize) + this.ticksSize = this.labelsFont.size * 0.5; // old lego scaling factor if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) this.ticksSize = this.maxTickSize; @@ -840,7 +902,8 @@ class RAxisPainter extends RObjectPainter { async drawAxis(layer, transform, side) { let axis_g = layer; - if (side === undefined) side = 1; + if (side === undefined) + side = 1; if (!this.standalone) { axis_g = layer.selectChild(`.${this.name}_container`); @@ -856,7 +919,8 @@ class RAxisPainter extends RObjectPainter { this.axis_g = axis_g; this.side = side; - if (this.ticksSide === 'invert') side = -side; + if (this.ticksSide === 'invert') + side = -side; if (this.standalone) this.drawMainLine(axis_g); @@ -869,8 +933,7 @@ class RAxisPainter extends RObjectPainter { // first draw ticks const tgaps = this.drawTicks(axis_g, side, true), - // draw labels - labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); + labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); // draw labels return labelsPromise.then(lgaps => { // when drawing axis on frame, zoom rect should be always outside @@ -881,7 +944,7 @@ class RAxisPainter extends RObjectPainter { } /** @summary Assign handler, which is called when axis redraw by interactive changes - * @desc Used by palette painter to reassign iteractive handlers + * @desc Used by palette painter to reassign interactive handlers * @private */ setAfterDrawHandler(handler) { this._afterDrawAgain = handler; @@ -889,14 +952,16 @@ class RAxisPainter extends RObjectPainter { /** @summary Draw axis with the same settings, used by interactive changes */ drawAxisAgain() { - if (!this.axis_g || !this.side) return; + if (!this.axis_g || !this.side) + return; this.axis_g.selectAll('*').remove(); this.extractDrawAttributes(); let side = this.side; - if (this.ticksSide === 'invert') side = -side; + if (this.ticksSide === 'invert') + side = -side; if (this.standalone) this.drawMainLine(this.axis_g); @@ -926,13 +991,12 @@ class RAxisPainter extends RObjectPainter { axis_g.attr('transform', transform); - if (this.ticksSide === 'invert') side = -side; + if (this.ticksSide === 'invert') + side = -side; - // draw ticks again + // draw ticks and labels again const tgaps = this.drawTicks(axis_g, side, false), - - // draw labels again - promise = this.optionUnlab || only_ticks ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); + promise = this.optionUnlab || only_ticks ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); return promise.then(lgaps => { this.addZoomingRect(axis_g, side, lgaps); @@ -942,7 +1006,7 @@ class RAxisPainter extends RObjectPainter { /** @summary Change zooming in standalone mode */ zoomStandalone(min, max) { - this.changeAxisAttr(1, 'zoomMin', min, 'zoomMax', max); + return this.changeAxisAttr(1, 'zoomMin', min, 'zoomMax', max); } /** @summary Redraw axis, used in standalone mode for RAxisDrawable */ @@ -954,56 +1018,62 @@ class RAxisPainter extends RObjectPainter { labels_len = drawable.fLabels.length, min = (labels_len > 0) ? 0 : this.v7EvalAttr('min', 0), max = (labels_len > 0) ? labels_len : this.v7EvalAttr('max', 100); - let len = pp.getPadLength(drawable.fVertical, drawable.fLength); + let len = pp.getPadLength(drawable.fVertical, drawable.fLength), + smin = this.v7EvalAttr('zoomMin'), + smax = this.v7EvalAttr('zoomMax'); // in vertical direction axis drawn in negative direction - if (drawable.fVertical) len -= pp.getPadHeight(); + if (drawable.fVertical) + len -= pp.getPadHeight(); - let smin = this.v7EvalAttr('zoomMin'), - smax = this.v7EvalAttr('zoomMax'); if (smin === smax) { - smin = min; smax = max; + smin = min; + smax = max; } this.configureAxis('axis', min, max, smin, smax, drawable.fVertical, undefined, len, { reverse, labels: labels_len > 0 }); - this.createG(); + const g = this.createG(); this.standalone = true; // no need to clean axis container - const promise = this.drawAxis(this.draw_g, makeTranslate(pos.x, pos.y)); + const promise = this.drawAxis(g, makeTranslate(pos.x, pos.y)); - if (this.isBatchMode()) return promise; + if (this.isBatchMode()) + return promise; return promise.then(() => { if (settings.ContextMenu) { - this.draw_g.on('contextmenu', evnt => { + g.on('contextmenu', evnt => { evnt.stopPropagation(); // disable main context menu evnt.preventDefault(); // disable browser context menu createMenu(evnt, this).then(menu => { - menu.add('header:RAxisDrawable'); - menu.add('Unzoom', () => this.zoomStandalone()); - this.fillAxisContextMenu(menu, ''); - menu.show(); + menu.header('RAxisDrawable', `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); + menu.add('Unzoom', () => this.zoomStandalone()); + this.fillAxisContextMenu(menu, ''); + menu.show(); }); }); } - addDragHandler(this, { x: pos.x, y: pos.y, width: this.vertical ? 10 : len, height: this.vertical ? len : 10, - only_move: true, redraw: d => this.positionChanged(d) }); + addDragHandler(this, { + x: pos.x, y: pos.y, width: this.vertical ? 10 : len, height: this.vertical ? len : 10, + only_move: true, redraw: d => this.positionChanged(d) + }); - this.draw_g.on('dblclick', () => this.zoomStandalone()); + g.on('dblclick', () => this.zoomStandalone()); if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { + g.on('wheel', evnt => { evnt.stopPropagation(); evnt.preventDefault(); - const pos = d3_pointer(evnt, this.draw_g.node()), - coord = this.vertical ? (1 - pos[1] / len) : pos[0] / len, - item = this.analyzeWheelEvent(evnt, coord); + const pos2 = d3_pointer(evnt, this.getG().node()), + coord = this.vertical ? (1 - pos2[1] / len) : pos2[0] / len, + item = this.analyzeWheelEvent(evnt, coord); - if (item.changed) this.zoomStandalone(item.min, item.max); + if (item.changed) + this.zoomStandalone(item.min, item.max); }); } }); @@ -1012,9 +1082,9 @@ class RAxisPainter extends RObjectPainter { /** @summary Process interactive moving of the axis drawing */ positionChanged(drag) { const drawable = this.getObject(), - rect = this.getPadPainter().getPadRect(), - xn = drag.x / rect.width, - yn = 1 - drag.y / rect.height; + rect = this.getPadPainter().getPadRect(), + xn = drag.x / rect.width, + yn = 1 - drag.y / rect.height; drawable.fPos.fHoriz.fArr = [xn]; drawable.fPos.fVert.fArr = [yn]; @@ -1024,12 +1094,12 @@ class RAxisPainter extends RObjectPainter { /** @summary Change axis attribute, submit changes to server and redraw axis when specified * @desc Arguments as redraw_mode, name1, value1, name2, value2, ... */ - changeAxisAttr(redraw_mode) { + changeAxisAttr(redraw_mode, ...args) { const changes = {}; - let indx = 1; - while (indx < arguments.length - 1) { - this.v7AttrChange(changes, arguments[indx], arguments[indx+1]); - this.v7SetAttr(arguments[indx], arguments[indx+1]); + let indx = 0; + while (indx < args.length) { + this.v7AttrChange(changes, args[indx], args[indx + 1]); + this.v7SetAttr(args[indx], args[indx + 1]); indx += 2; } this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server @@ -1044,49 +1114,53 @@ class RAxisPainter extends RObjectPainter { /** @summary Change axis log scale kind */ changeAxisLog(arg) { - if ((this.kind === kAxisLabels) || (this.kind === kAxisTime)) return; - if (arg === 'toggle') arg = this.log ? 0 : 10; + if ((this.kind === kAxisLabels) || (this.kind === kAxisTime)) + return; + if (arg === 'toggle') + arg = this.log ? 0 : 10; arg = parseFloat(arg); - if (Number.isFinite(arg)) this.changeAxisAttr(2, 'log', arg, 'symlog', 0); + if (Number.isFinite(arg)) + this.changeAxisAttr(2, 'log', arg, 'symlog', 0); } /** @summary Provide context menu for axis */ fillAxisContextMenu(menu, kind) { - if (kind) menu.add('Unzoom', () => this.getFramePainter().unzoom(kind)); + if (kind) + menu.add('Unzoom', () => this.getFramePainter().unzoom(kind)); - menu.add('sub:Log scale', () => this.changeAxisLog('toggle')); + menu.sub('Log scale', () => this.changeAxisLog('toggle')); menu.addchk(!this.log && !this.symlog, 'linear', 0, arg => this.changeAxisLog(arg)); menu.addchk(this.log && !this.symlog && (this.logbase === 10), 'log10', () => this.changeAxisLog(10)); menu.addchk(this.log && !this.symlog && (this.logbase === 2), 'log2', () => this.changeAxisLog(2)); menu.addchk(this.log && !this.symlog && Math.abs(this.logbase - Math.exp(1)) < 0.1, 'ln', () => this.changeAxisLog(Math.exp(1))); menu.addchk(!this.log && this.symlog, 'symlog', 0, () => menu.input('set symlog constant', this.symlog || 10, 'float').then(v => this.changeAxisAttr(2, 'symlog', v))); - menu.add('endsub:'); + menu.endsub(); menu.add('Divisions', () => menu.input('Set axis devisions', this.v7EvalAttr('ndiv', 508), 'int').then(val => this.changeAxisAttr(2, 'ndiv', val))); - menu.add('sub:Ticks'); + menu.sub('Ticks'); menu.addRColorMenu('color', this.ticksColor, col => this.changeAxisAttr(1, 'ticks_color', col)); - menu.addSizeMenu('size', 0, 0.05, 0.01, this.ticksSize/this.scalingSize, sz => this.changeAxisAttr(1, 'ticks_size', sz)); + menu.addSizeMenu('size', 0, 0.05, 0.01, this.ticksSize / this.scalingSize, sz => this.changeAxisAttr(1, 'ticks_size', sz)); menu.addSelectMenu('side', ['normal', 'invert', 'both'], this.ticksSide, side => this.changeAxisAttr(1, 'ticks_side', side)); - menu.add('endsub:'); + menu.endsub(); if (!this.optionUnlab && this.labelsFont) { - menu.add('sub:Labels'); - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.labelsOffset/this.scalingSize, - offset => this.changeAxisAttr(1, 'labels_offset', offset)); + menu.sub('Labels'); + menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.labelsOffset / this.scalingSize, + offset => this.changeAxisAttr(1, 'labels_offset', offset)); menu.addRAttrTextItems(this.labelsFont, { noangle: 1, noalign: 1 }, - change => this.changeAxisAttr(1, 'labels_' + change.name, change.value)); + change => this.changeAxisAttr(1, 'labels_' + change.name, change.value)); menu.addchk(this.labelsFont.angle, 'rotate', res => this.changeAxisAttr(1, 'labels_angle', res ? 180 : 0)); - menu.add('endsub:'); + menu.endsub(); } - menu.add('sub:Title', () => menu.input('Enter axis title', this.fTitle).then(t => this.changeAxisAttr(1, 'title_value', t))); + menu.sub('Title', () => menu.input('Enter axis title', this.fTitle).then(t => this.changeAxisAttr(1, 'title_value', t))); if (this.fTitle) { - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.titleOffset/this.scalingSize, - offset => this.changeAxisAttr(1, 'title_offset', offset)); + menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.titleOffset / this.scalingSize, + offset => this.changeAxisAttr(1, 'title_offset', offset)); menu.addSelectMenu('position', ['left', 'center', 'right'], this.titlePos, pos => this.changeAxisAttr(1, 'title_position', pos)); @@ -1096,7 +1170,7 @@ class RAxisPainter extends RObjectPainter { menu.addRAttrTextItems(this.titleFont, { noangle: 1, noalign: 1 }, change => this.changeAxisAttr(1, 'title_' + change.name, change.value)); } - menu.add('endsub:'); + menu.endsub(); return true; } diff --git a/modules/gpad/RCanvasPainter.mjs b/modules/gpad/RCanvasPainter.mjs index d055c496e..4dea6b437 100644 --- a/modules/gpad/RCanvasPainter.mjs +++ b/modules/gpad/RCanvasPainter.mjs @@ -3,13 +3,13 @@ import { select as d3_select, rgb as d3_rgb } from '../d3.mjs'; import { closeCurrentWindow, showProgress, loadOpenui5, ToolbarIcons, getColorExec } from '../gui/utils.mjs'; import { GridDisplay, getHPainter } from '../gui/display.mjs'; import { makeTranslate } from '../base/BasePainter.mjs'; +import { convertColor } from '../base/colors.mjs'; import { selectActivePad, cleanup, resize, EAxisBits } from '../base/ObjectPainter.mjs'; import { RObjectPainter } from '../base/RObjectPainter.mjs'; import { RAxisPainter } from './RAxisPainter.mjs'; import { RFramePainter } from './RFramePainter.mjs'; import { RPadPainter } from './RPadPainter.mjs'; import { addDragHandler } from './TFramePainter.mjs'; -import { WebWindowHandle } from '../webwindow.mjs'; /** @@ -20,35 +20,45 @@ import { WebWindowHandle } from '../webwindow.mjs'; class RCanvasPainter extends RPadPainter { + #websocket; // WebWindow handle used for communication with server + #changed_layout; // modified layout + #submreq; // submitted requests + #nextreqid; // id of next request + /** @summary constructor */ - constructor(dom, canvas) { - super(dom, canvas, true); - this._websocket = null; + constructor(dom, canvas, opt) { + super(dom, canvas, opt, true); + this.#websocket = null; + this.#submreq = {}; this.tooltip_allowed = settings.Tooltip; this.v7canvas = true; - if ((dom === null) && (canvas === null)) { - // for web canvas details are important - settings.SmallPad.width = 20; - settings.SmallPad.height = 10; - } } /** @summary Cleanup canvas painter */ cleanup() { - delete this._websocket; - delete this._submreq; + this.#websocket = undefined; + this.#submreq = {}; - if (this._changed_layout) + if (this.#changed_layout) this.setLayoutKind('simple'); - delete this._changed_layout; + this.#changed_layout = undefined; super.cleanup(); } + /** @summary Returns readonly flag */ + isReadonly() { return false; } + + /** @summary Returns canvas name */ + getCanvasName() { + const title = this.getRootPad()?.fTitle; + return (!title || !isStr(title)) ? 'rcanvas' : title.replace(/ /g, '_'); + } + /** @summary Returns layout kind */ getLayoutKind() { const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); + layout = origin.empty() ? '' : origin.property('layout'); return layout || 'simple'; } @@ -56,10 +66,11 @@ class RCanvasPainter extends RPadPainter { setLayoutKind(kind, main_selector) { const origin = this.selectDom('origin'); if (!origin.empty()) { - if (!kind) kind = 'simple'; + if (!kind) + kind = 'simple'; origin.property('layout', kind); origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this._changed_layout = (kind !== 'simple'); // use in cleanup + this.#changed_layout = (kind !== 'simple'); // use in cleanup } } @@ -135,7 +146,8 @@ class RCanvasPainter extends RPadPainter { async toggleProjection(kind) { delete this.proj_painter; - if (kind) this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed + if (kind) + this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed if (isFunc(this.showUI5ProjectionArea)) return this.showUI5ProjectionArea(kind); @@ -143,13 +155,28 @@ class RCanvasPainter extends RPadPainter { let layout = 'simple', mainid; switch (kind) { - case 'XY': layout = 'projxy'; mainid = 2; break; + case 'XY': + layout = 'projxy'; + mainid = 2; + break; case 'X': - case 'bottom': layout = 'vert2_31'; mainid = 0; break; + case 'bottom': + layout = 'vert2_31'; + mainid = 0; + break; case 'Y': - case 'left': layout = 'horiz2_13'; mainid = 1; break; - case 'top': layout = 'vert2_13'; mainid = 1; break; - case 'right': layout = 'horiz2_31'; mainid = 0; break; + case 'left': + layout = 'horiz2_13'; + mainid = 1; + break; + case 'top': + layout = 'vert2_13'; + mainid = 1; + break; + case 'right': + layout = 'horiz2_31'; + mainid = 0; + break; } return this.changeLayout(layout, mainid); @@ -157,7 +184,7 @@ class RCanvasPainter extends RPadPainter { /** @summary Draw projection for specified histogram * @private */ - async drawProjection(/* kind,hist,hopt */) { + async drawProjection(/* kind, hist, hopt */) { // dummy for the moment return false; } @@ -174,9 +201,7 @@ class RCanvasPainter extends RPadPainter { * @desc Function should be used only from the func which supposed to be replaced by ui5 * @private */ testUI5() { - if (!this.use_openui) return false; - console.warn('full ui5 should be used - not loaded yet? Please check!!'); - return true; + return this.use_openui ?? false; } /** @summary Show message @@ -190,22 +215,28 @@ class RCanvasPainter extends RPadPainter { /** @summary Function called when canvas menu item Save is called */ saveCanvasAsFile(fname) { const pnt = fname.indexOf('.'); - this.createImage(fname.slice(pnt+1)) + this.createImage(fname.slice(pnt + 1)) .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); } /** @summary Send command to server to save canvas with specified name * @desc Should be only used in web-based canvas * @private */ - sendSaveCommand(fname) { - this.sendWebsocket('PRODUCE:' + fname); - } + sendSaveCommand(fname) { this.sendWebsocket('PRODUCE:' + fname); } + + /** @summary Return assigned web socket + * @private */ + getWebsocket() { return this.#websocket; } + + /** @summary Return true if message can be send via web socket + * @private */ + canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } /** @summary Send message via web socket * @private */ sendWebsocket(msg) { - if (this._websocket?.canSend()) { - this._websocket.send(msg); + if (this.#websocket?.canSend()) { + this.#websocket.send(msg); return true; } @@ -215,10 +246,10 @@ class RCanvasPainter extends RPadPainter { /** @summary Close websocket connection to canvas * @private */ closeWebsocket(force) { - if (this._websocket) { - this._websocket.close(force); - this._websocket.cleanup(); - delete this._websocket; + if (this.#websocket) { + this.#websocket.close(force); + this.#websocket.cleanup(); + this.#websocket = undefined; } } @@ -227,42 +258,45 @@ class RCanvasPainter extends RPadPainter { useWebsocket(handle) { this.closeWebsocket(); - this._websocket = handle; - this._websocket.setReceiver(this); - this._websocket.connect(); + this.#websocket = handle; + this.#websocket.setReceiver(this); + this.#websocket.connect(); } /** @summary set, test or reset timeout of specified name * @desc Used to prevent overloading of websocket for specific function */ websocketTimeout(name, tm) { - if (!this._websocket) + if (!this.#websocket) return; - if (!this._websocket._tmouts) - this._websocket._tmouts = {}; + if (!this.#websocket._tmouts) + this.#websocket._tmouts = {}; - const handle = this._websocket._tmouts[name]; + const handle = this.#websocket._tmouts[name]; if (tm === undefined) return handle !== undefined; if (tm === 'reset') { - if (handle) { clearTimeout(handle); delete this._websocket._tmouts[name]; } + if (handle) { + clearTimeout(handle); + delete this.#websocket._tmouts[name]; + } } else if (!handle && Number.isInteger(tm)) - this._websocket._tmouts[name] = setTimeout(() => { delete this._websocket._tmouts[name]; }, tm); + this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); } - /** @summary Hanler for websocket open event + /** @summary Handler for websocket open event * @private */ onWebsocketOpened(/* handle */) { } - /** @summary Hanler for websocket close event + /** @summary Handler for websocket close event * @private */ onWebsocketClosed(/* handle */) { if (!this.embed_canvas) closeCurrentWindow(); } - /** @summary Hanler for websocket message + /** @summary Handler for websocket message * @private */ onWebsocketMsg(handle, msg) { // console.log('GET_MSG ' + msg.slice(0,30)); @@ -273,39 +307,43 @@ class RCanvasPainter extends RPadPainter { } else if (msg.slice(0, 5) === 'SNAP:') { msg = msg.slice(5); const p1 = msg.indexOf(':'), - snapid = msg.slice(0, p1), - snap = parse(msg.slice(p1+1)); + snapid = msg.slice(0, p1), + snap = parse(msg.slice(p1 + 1)); this.syncDraw(true) .then(() => { - if (!this.snapid && snap?.fWinSize) + if (!this.getSnapId() && snap?.fWinSize) this.resizeBrowser(snap.fWinSize[0], snap.fWinSize[1]); }).then(() => this.redrawPadSnap(snap)) .then(() => { - this.addPadInteractive(); - handle.send(`SNAPDONE:${snapid}`); // send ready message back when drawing completed - this.confirmDraw(); - }); + this.addPadInteractive(); + handle.send(`SNAPDONE:${snapid}`); // send ready message back when drawing completed + this.confirmDraw(); + }).catch(err => { + if (isFunc(this.showConsoleError)) + this.showConsoleError(err); + else + console.log(err); + }); } else if (msg.slice(0, 4) === 'JSON') { const obj = parse(msg.slice(4)); - // console.log('get JSON ', msg.length-4, obj._typename); this.redrawObject(obj); } else if (msg.slice(0, 9) === 'REPL_REQ:') this.processDrawableReply(msg.slice(9)); - else if (msg.slice(0, 4) === 'CMD:') { + else if (msg.slice(0, 4) === 'CMD:') { msg = msg.slice(4); const p1 = msg.indexOf(':'), - cmdid = msg.slice(0, p1), - cmd = msg.slice(p1+1), - reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG')) { + cmdid = msg.slice(0, p1), + cmd = msg.slice(p1 + 1), + reply = `REPLY:${cmdid}:`; + if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { this.createImage(cmd.toLowerCase()) .then(res => handle.send(reply + res)); } else if (cmd.indexOf('ADDPANEL:') === 0) { - const relative_path = cmd.slice(9); if (!isFunc(this.showUI5Panel)) handle.send(reply + 'false'); - else { - const conn = new WebWindowHandle(handle.kind); + else { + const window_path = cmd.slice(9), + conn = handle.createNewInstance(window_path); // set interim receiver until first message arrives conn.setReceiver({ @@ -314,8 +352,8 @@ class RCanvasPainter extends RPadPainter { onWebsocketOpened() { }, - onWebsocketMsg(panel_handle, msg) { - const panel_name = (msg.indexOf('SHOWPANEL:') === 0) ? msg.slice(10) : ''; + onWebsocketMsg(panel_handle, msg2) { + const panel_name = (msg2.indexOf('SHOWPANEL:') === 0) ? msg2.slice(10) : ''; this.cpainter.showUI5Panel(panel_name, panel_handle) .then(res => handle.send(reply + (res ? 'true' : 'false'))); }, @@ -332,15 +370,8 @@ class RCanvasPainter extends RPadPainter { }); - let addr = handle.href; - if (relative_path.indexOf('../') === 0) { - const ddd = addr.lastIndexOf('/', addr.length-2); - addr = addr.slice(0, ddd) + relative_path.slice(2); - } else - addr += relative_path; - // only when connection established, panel will be activated - conn.connect(addr); + conn.connect(); } } else { console.log('Unrecognized command ' + cmd); @@ -348,24 +379,25 @@ class RCanvasPainter extends RPadPainter { } } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { const kind = msg[1], - hist = parse(msg.slice(7)); + hist = parse(msg.slice(7)); this.drawProjection(kind, hist); } else if (msg.slice(0, 5) === 'SHOW:') { const that = msg.slice(5), - on = that[that.length-1] === '1'; - this.showSection(that.slice(0, that.length-2), on); + on = that.at(-1) === '1'; + this.showSection(that.slice(0, that.length - 2), on); } else console.log(`unrecognized msg len: ${msg.length} msg: ${msg.slice(0, 30)}`); } /** @summary Submit request to RDrawable object on server side */ submitDrawableRequest(kind, req, painter, method) { - if (!this._websocket || !req || !req._typename || - !painter.snapid || !isStr(painter.snapid)) return null; + if (!this.getWebsocket() || !req?._typename || !painter.getSnapId()) + return null; if (kind && method) { // if kind specified - check if such request already was submitted - if (!painter._requests) painter._requests = {}; + if (!painter._requests) + painter._requests = {}; const prevreq = painter._requests[kind]; @@ -381,11 +413,12 @@ class RCanvasPainter extends RPadPainter { painter._requests[kind] = req; // keep reference on the request } - req.id = painter.snapid; + req.id = painter.getSnapId(); if (method) { - if (!this._nextreqid) this._nextreqid = 1; - req.reqid = this._nextreqid++; + if (!this.#nextreqid) + this.#nextreqid = 1; + req.reqid = this.#nextreqid++; } else req.reqid = 0; // request will not be replied @@ -398,12 +431,9 @@ class RCanvasPainter extends RPadPainter { req._method = method; req._tm = new Date().getTime(); - if (!this._submreq) this._submreq = {}; - this._submreq[req.reqid] = req; // fast access to submitted requests + this.#submreq[req.reqid] = req; // fast access to submitted requests } - // console.log('Sending request ', msg.slice(0,60)); - this.sendWebsocket('REQ:' + msg); return req; } @@ -422,20 +452,20 @@ class RCanvasPainter extends RPadPainter { /** @summary Submit executable command for given painter */ submitExec(painter, exec, subelem) { - // snapid is intentionally ignored - only painter.snapid has to be used - if (!this._websocket) return; - if (subelem && isStr(subelem)) { const len = subelem.length; - if ((len > 2) && (subelem.indexOf('#x') === len - 2)) subelem = 'x'; else - if ((len > 2) && (subelem.indexOf('#y') === len - 2)) subelem = 'y'; else - if ((len > 2) && (subelem.indexOf('#z') === len - 2)) subelem = 'z'; + if ((len > 2) && (subelem.indexOf('#x') === len - 2)) + subelem = 'x'; + else if ((len > 2) && (subelem.indexOf('#y') === len - 2)) + subelem = 'y'; + else if ((len > 2) && (subelem.indexOf('#z') === len - 2)) + subelem = 'z'; if ((subelem === 'x') || (subelem === 'y') || (subelem === 'z')) exec = subelem + 'axis#' + exec; else - return console.log(`not recoginzed subelem ${subelem} in SubmitExec`); - } + return console.log(`not recoginzed subelem ${subelem} in submitExec`); + } this.submitDrawableRequest('', { _typename: `${nsREX}RDrawableExecRequest`, exec }, painter); } @@ -443,13 +473,15 @@ class RCanvasPainter extends RPadPainter { /** @summary Process reply from request to RDrawable */ processDrawableReply(msg) { const reply = parse(msg); - if (!reply || !reply.reqid || !this._submreq) return false; + if (!reply?.reqid) + return false; - const req = this._submreq[reply.reqid]; - if (!req) return false; + const req = this.#submreq[reply.reqid]; + if (!req) + return false; // remove reference first - delete this._submreq[reply.reqid]; + this.#submreq[reply.reqid] = undefined; // remove blocking reference for that kind if (req._kind && req._painter?._requests) { @@ -482,10 +514,12 @@ class RCanvasPainter extends RPadPainter { * @private */ processChanges(kind, painter, subelem) { // check if we could send at least one message more - for some meaningful actions - if (!this._websocket || !this._websocket.canSend(2) || !isStr(kind)) return; + if (!this.canSendWebsocket(2) || !isStr(kind)) + return; const msg = ''; - if (!painter) painter = this; + if (!painter) + painter = this; switch (kind) { case 'sbits': console.log('Status bits in RCanvas are changed - that to do?'); @@ -498,9 +532,9 @@ class RCanvasPainter extends RPadPainter { console.log('TPave is moved inside RCanvas - that to do?'); break; default: - if ((kind.slice(0, 5) === 'exec:') && painter?.snapid) + if ((kind.slice(0, 5) === 'exec:') && painter?.getSnapId()) this.submitExec(painter, kind.slice(5), subelem); - else + else console.log('UNPROCESSED CHANGES', kind); } @@ -520,7 +554,8 @@ class RCanvasPainter extends RPadPainter { /** @summary returns true when event status area exist for the canvas */ hasEventStatus() { - if (this.testUI5()) return false; + if (this.testUI5()) + return false; if (this.brlayout) return this.brlayout.hasStatus(); const hp = getHPainter(); @@ -549,23 +584,25 @@ class RCanvasPainter extends RPadPainter { /** @summary Show online canvas status * @private */ showCanvasStatus(...msgs) { - if (this.testUI5()) return; + if (this.testUI5()) + return; const br = this.brlayout || getHPainter()?.brlayout; - br?.showStatus(...msgs); } /** @summary Returns true if GED is present on the canvas */ hasGed() { - if (this.testUI5()) return false; + if (this.testUI5()) + return false; return this.brlayout?.hasContent() ?? false; } /** @summary Function used to de-activate GED * @private */ removeGed() { - if (this.testUI5()) return; + if (this.testUI5()) + return; this.registerForPadEvents(null); @@ -661,17 +698,16 @@ class RCanvasPainter extends RPadPainter { resizeBrowser(fullW, fullH) { if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) return; - this._websocket?.resizeWindow(fullW, fullH); + this.getWebsocket()?.resizeWindow(fullW, fullH); } /** @summary draw RCanvas object */ - static async draw(dom, can /*, opt */) { + static async draw(dom, can, opt) { const nocanvas = !can; if (nocanvas) can = create(`${nsREX}RCanvas`); - const painter = new RCanvasPainter(dom, can); - painter.normal_canvas = !nocanvas; + const painter = new RCanvasPainter(dom, can, opt); painter.createCanvasSvg(0); selectActivePad({ pp: painter, active: false }); @@ -689,9 +725,8 @@ class RCanvasPainter extends RPadPainter { /** @summary draw RPadSnapshot object * @private */ -function drawRPadSnapshot(dom, snap /*, opt */) { - const painter = new RCanvasPainter(dom, null); - painter.normal_canvas = false; +function drawRPadSnapshot(dom, snap, opt) { + const painter = new RCanvasPainter(dom, null, opt); painter.batch_mode = isBatchMode(); return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { painter.confirmDraw(); @@ -706,18 +741,19 @@ function drawRPadSnapshot(dom, snap /*, opt */) { * @desc Assigns DOM, creates and draw RCanvas and RFrame if necessary, add painter to pad list of painters * @return {Promise} for ready * @private */ -async function ensureRCanvas(painter, frame_kind) { +async function ensureRCanvas(painter /* , frame_kind */) { if (!painter) return Promise.reject(Error('Painter not provided in ensureRCanvas')); // simple check - if canvas there, can use painter - const pr = painter.getCanvSvg().empty() ? RCanvasPainter.draw(painter.getDom(), null /* noframe */) : Promise.resolve(true); - - return pr.then(() => { - if ((frame_kind !== false) && painter.getFrameSvg().selectChild('.main_layer').empty()) - return RFramePainter.draw(painter.getDom(), null, isStr(frame_kind) ? frame_kind : ''); - }).then(() => { - painter.addToPadPrimitives(); + const pad_painter = painter.getPadPainter(), + pr = pad_painter ? Promise.resolve(pad_painter) : + RCanvasPainter.draw(painter.getDom(), null /* noframe */); + + return pr.then(pp => { + // if ((frame_kind !== false) && pp.getFrameSvg().selectChild('.main_layer').empty()) + // return RFramePainter.draw(painter.getDom(), null, isStr(frame_kind) ? frame_kind : ''); + painter.addToPadPrimitives(pp); return painter; }); } @@ -751,28 +787,28 @@ function drawRFrameTitle(reason, drag) { this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server } - this.createG(); - - makeTranslate(this.draw_g, fx, Math.round(fy-title_margin-title_height)); - - const arg = { x: title_width/2, y: title_height/2, text: title.fText, latex: 1 }; - - this.startTextDrawing(textFont, 'font'); + const g = this.createG(); - this.drawText(arg); + makeTranslate(g, fx, Math.round(fy - title_margin - title_height)); - return this.finishTextDrawing().then(() => - addDragHandler(this, { x: fx, y: Math.round(fy-title_margin-title_height), width: title_width, height: title_height, - minwidth: 20, minheight: 20, no_change_x: true, redraw: d => this.redraw('drag', d) }) - ); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + this.drawText({ x: title_width / 2, y: title_height / 2, text: title.fText, latex: 1 }); + return this.finishTextDrawing(); + }).then(() => { + addDragHandler(this, { + x: fx, y: Math.round(fy - title_margin - title_height), width: title_width, height: title_height, + minwidth: 20, minheight: 20, no_change_x: true, redraw: d => this.redraw('drag', d) + }); + }); } -/// ///////////////////////////////////////////////////////////////////////////////////////// +// ========================================================== registerMethods(`${nsREX}RPalette`, { extractRColor(rcolor) { - return rcolor.fColor || 'black'; + const col = rcolor.fColor || 'black'; + return convertColor(col); }, getColor(indx) { @@ -781,21 +817,26 @@ registerMethods(`${nsREX}RPalette`, { getContourIndex(zc) { const cntr = this.fContour; - let l = 0, r = cntr.length-1, mid; + let l = 0, r = cntr.length - 1; - if (zc < cntr[0]) return -1; - if (zc >= cntr[r]) return r-1; + if (zc < cntr[0]) + return -1; + if (zc >= cntr[r]) + return r - 1; if (this.fCustomContour) { - while (l < r-1) { - mid = Math.round((l+r)/2); - if (cntr[mid] > zc) r = mid; else l = mid; + while (l < r - 1) { + const mid = Math.round((l + r) / 2); + if (cntr[mid] > zc) + r = mid; + else + l = mid; } return l; } // last color in palette starts from level cntr[r-1] - return Math.floor((zc-cntr[0]) / (cntr[r-1] - cntr[0]) * (r-1)); + return Math.floor((zc - cntr[0]) / (cntr[r - 1] - cntr[0]) * (r - 1)); }, getContourColor(zc) { @@ -808,25 +849,25 @@ registerMethods(`${nsREX}RPalette`, { }, deleteContour() { - delete this.fContour; + this.fContour = undefined; }, calcColor(value, entry1, entry2) { const dist = entry2.fOrdinal - entry1.fOrdinal, - r1 = entry2.fOrdinal - value, - r2 = value - entry1.fOrdinal; + r1 = entry2.fOrdinal - value, + r2 = value - entry1.fOrdinal; if (!this.fInterpolate || (dist <= 0)) - return (r1 < r2) ? entry2.fColor : entry1.fColor; + return convertColor((r1 < r2) ? entry2.fColor : entry1.fColor); // interpolate const col1 = d3_rgb(this.extractRColor(entry1.fColor)), - col2 = d3_rgb(this.extractRColor(entry2.fColor)), - color = d3_rgb(Math.round((col1.r*r1 + col2.r*r2)/dist), - Math.round((col1.g*r1 + col2.g*r2)/dist), - Math.round((col1.b*r1 + col2.b*r2)/dist)); + col2 = d3_rgb(this.extractRColor(entry2.fColor)), + color = d3_rgb(Math.round((col1.r * r1 + col2.r * r2) / dist), + Math.round((col1.g * r1 + col2.g * r2) / dist), + Math.round((col1.b * r1 + col2.b * r2) / dist)); - return color.toString(); + return color.formatRgb(); }, createPaletteColors(len) { @@ -834,16 +875,15 @@ registerMethods(`${nsREX}RPalette`, { let indx = 0; while (arr.length < len) { - const value = arr.length / (len-1), - - entry = this.fColors[indx]; + const value = arr.length / (len - 1), + entry = this.fColors[indx]; if ((Math.abs(entry.fOrdinal - value) < 0.0001) || (indx === this.fColors.length - 1)) { arr.push(this.extractRColor(entry.fColor)); continue; } - const next = this.fColors[indx+1]; + const next = this.fColors[indx + 1]; if (next.fOrdinal <= value) indx++; else @@ -865,13 +905,13 @@ registerMethods(`${nsREX}RPalette`, { // TODO: implement better way to find index let entry, next = this.fColors[0]; - for (let indx = 0; indx < this.fColors.length-1; ++indx) { + for (let indx = 0; indx < this.fColors.length - 1; ++indx) { entry = next; if (Math.abs(entry.fOrdinal - value) < 0.0001) return this.extractRColor(entry.fColor); - next = this.fColors[indx+1]; + next = this.fColors[indx + 1]; if (next.fOrdinal > value) return this.calcColor(value, entry, next); } @@ -892,32 +932,33 @@ registerMethods(`${nsREX}RPalette`, { this.colzmax = zmax; if (logz) { - if (this.colzmax <= 0) this.colzmax = 1.0; + if (this.colzmax <= 0) + this.colzmax = 1.0; if (this.colzmin <= 0) { if ((zminpositive === undefined) || (zminpositive <= 0)) - this.colzmin = 0.0001*this.colzmax; + this.colzmin = 0.0001 * this.colzmax; else - this.colzmin = ((zminpositive < 3) || (zminpositive>100)) ? 0.3*zminpositive : 1; + this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3 * zminpositive : 1; } if (this.colzmin >= this.colzmax) - this.colzmin = 0.0001*this.colzmax; + this.colzmin = 0.0001 * this.colzmax; - const logmin = Math.log(this.colzmin)/Math.log(10), - logmax = Math.log(this.colzmax)/Math.log(10), - dz = (logmax-logmin)/nlevels; + const logmin = Math.log(this.colzmin) / Math.log(10), + logmax = Math.log(this.colzmax) / Math.log(10), + dz = (logmax - logmin) / nlevels; this.fContour.push(this.colzmin); - for (let level=1; level<nlevels; level++) - this.fContour.push(Math.exp((logmin + dz*level)*Math.log(10))); + for (let level = 1; level < nlevels; level++) + this.fContour.push(Math.exp((logmin + dz * level) * Math.log(10))); this.fContour.push(this.colzmax); this.fCustomContour = true; } else { - if ((this.colzmin === this.colzmax) && (this.colzmin !== 0)) { - this.colzmax += 0.01*Math.abs(this.colzmax); - this.colzmin -= 0.01*Math.abs(this.colzmin); + if ((this.colzmin === this.colzmax) && this.colzmin) { + this.colzmax += 0.01 * Math.abs(this.colzmax); + this.colzmin -= 0.01 * Math.abs(this.colzmin); } - const dz = (this.colzmax-this.colzmin)/nlevels; - for (let level=0; level<=nlevels; level++) - this.fContour.push(this.colzmin + dz*level); + const dz = (this.colzmax - this.colzmin) / nlevels; + for (let level = 0; level <= nlevels; level++) + this.fContour.push(this.colzmin + dz * level); } if (!this.palette || (this.palette.length !== nlevels)) @@ -931,7 +972,7 @@ registerMethods(`${nsREX}RPalette`, { function drawRFont() { const font = this.getObject(), svg = this.getCanvSvg(), - clname = 'custom_font_' + font.fFamily+font.fWeight+font.fStyle; + clname = 'custom_font_' + font.fFamily + font.fWeight + font.fStyle; let defs = svg.selectChild('.canvas_defs'); if (defs.empty()) @@ -950,7 +991,7 @@ function drawRFont() { is_ttf = font.fSrc.indexOf('data:application/font-ttf') > 0; // TODO: for the moment only ttf format supported by jsPDF if (is_ttf) - entry.property('$fonthandler', { name: font.fFamily, format: 'ttf', base64 }); + entry.property('$fontcfg', { n: font.fFamily, base64 }); } } @@ -974,7 +1015,8 @@ function drawRAxis(dom, obj, opt) { * @private */ function drawRFrame(dom, obj, opt) { const p = new RFramePainter(dom, obj); - if (opt === '3d') p.mode3d = true; + if (opt === '3d') + p.mode3d = true; return ensureRCanvas(p, false).then(() => p.redraw()); } diff --git a/modules/gpad/RFramePainter.mjs b/modules/gpad/RFramePainter.mjs index 32724d850..02e49831a 100644 --- a/modules/gpad/RFramePainter.mjs +++ b/modules/gpad/RFramePainter.mjs @@ -1,8 +1,8 @@ -import { gStyle, settings, create, isFunc, isStr, clTAxis, nsREX } from '../core.mjs'; +import { gStyle, settings, internals, create, isFunc, isStr, clTAxis, nsREX, urlClassPrefix } from '../core.mjs'; import { pointer as d3_pointer } from '../d3.mjs'; import { getSvgLineStyle } from '../base/TAttLineHandler.mjs'; import { makeTranslate } from '../base/BasePainter.mjs'; -import { TAxisPainter } from './TAxisPainter.mjs'; +import { EAxisBits, TAxisPainter } from './TAxisPainter.mjs'; import { RAxisPainter } from './RAxisPainter.mjs'; import { FrameInteractive, getEarthProjectionFunc } from './TFramePainter.mjs'; import { RObjectPainter } from '../base/RObjectPainter.mjs'; @@ -16,17 +16,32 @@ import { RObjectPainter } from '../base/RObjectPainter.mjs'; class RFramePainter extends RObjectPainter { + #frame_x; // frame X coordinate + #frame_y; // frame Y coordinate + #frame_width; // frame width + #frame_height; // frame height + #frame_trans; // transform of frame element + #swap_xy; // swap X/Y axis on the frame + #reverse_x; // reverse X axis + #reverse_y; // reverse Y axis + #axes_drawn; // when axes are drawn + #projection; // id of projection function + #click_handler; // handle for click events + #dblclick_handler; // handle for double click events + #keys_handler; // assigned handler for keyboard events + #enabled_keys; // when keyboard press handling enabled + #last_event_pos; // position of last event + /** @summary constructor * @param {object|string} dom - DOM element for drawing or element id - * @param {object} tframe - RFrame object */ - constructor(dom, tframe) { - super(dom, tframe, '', 'frame'); + * @param {object} frame - RFrame object */ + constructor(dom, frame) { + super(dom, frame, '', 'frame'); this.mode3d = false; this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.axes_drawn = false; - this.keys_handler = null; - this.projection = 0; // different projections + this.#axes_drawn = false; + this.#projection = 0; // different projections this.v7_frame = true; // indicator of v7, used in interactive part } @@ -40,25 +55,36 @@ class RFramePainter extends RObjectPainter { /** @summary Set active flag for frame - can block some events * @private */ setFrameActive(on) { - this.enabledKeys = on && settings.HandleKeys; + this.#enabled_keys = on && settings.HandleKeys; // used only in 3D mode if (this.control) - this.control.enableKeys = this.enabledKeys; + this.control.enableKeys = this.#enabled_keys; } - setLastEventPos(pnt) { - // set position of last context menu event, can be - this.fLastEventPnt = pnt; - } + /** @summary Returns true if keys handling enabled + * @private */ + isEnabledKeys() { return this.#enabled_keys; } - getLastEventPos() { - // return position of last event - return this.fLastEventPnt; - } + /** @summary Returns true if X/Y axis swapped */ + swap_xy() { return this.#swap_xy; } + + /** @summary Is reverse x */ + reverse_x() { return this.#reverse_x; } + + /** @summary Is reverse x */ + reverse_y() { return this.#reverse_y; } + + /** @summary Set position of last context menu event + * @private */ + setLastEventPos(pnt) { this.#last_event_pos = pnt; } + + /** @summary Return position of last event + * @private */ + getLastEventPos() { return this.#last_event_pos; } /** @summary Update graphical attributes */ updateAttributes(force) { - if ((this.fX1NDC === undefined) || (force && !this.modified_NDC)) { + if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { const rect = this.getPadPainter().getPadRect(); this.fX1NDC = this.v7EvalLength('margins_left', rect.width, gStyle.fPadLeftMargin) / rect.width; this.fY1NDC = this.v7EvalLength('margins_bottom', rect.height, gStyle.fPadBottomMargin) / rect.height; @@ -73,25 +99,26 @@ class RFramePainter extends RObjectPainter { } /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.projection); } + getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - /** @summary Rcalculate frame ranges using specified projection functions + /** @summary Recalculate frame ranges using specified projection functions * @desc Not yet used in v7 */ recalculateRange(Proj) { - this.projection = Proj || 0; + this.#projection = Proj || 0; - if ((this.projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >=90))) { + if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { console.warn(`Mercator Projection: latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.projection = 0; + this.#projection = 0; } const func = this.getProjectionFunc(); - if (!func) return; + if (!func) + return; const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; + func(this.scale_xmin, this.scale_ymax), + func(this.scale_xmax, this.scale_ymax), + func(this.scale_xmax, this.scale_ymin)]; if (this.scale_xmin < 0 && this.scale_xmax > 0) { pnts.push(func(0, this.scale_ymin)); pnts.push(func(0, this.scale_ymax)); @@ -117,6 +144,9 @@ class RFramePainter extends RObjectPainter { } } + getFrameSvg() { return this.getPadPainter().getFrameSvg(); } + + /** @summary Draw axes grids * @desc Called immediately after axes drawing */ drawGrids() { @@ -139,8 +169,8 @@ class RFramePainter extends RObjectPainter { if (this.x_handle?.draw_grid) { let grid = ''; for (let n = 0; n < this.x_handle.ticks.length; ++n) { - grid += this.swap_xy - ? `M0,${h+this.x_handle.ticks[n]}h${w}` + grid += this.#swap_xy + ? `M0,${h + this.x_handle.ticks[n]}h${w}` : `M${this.x_handle.ticks[n]},0v${h}`; } @@ -161,9 +191,9 @@ class RFramePainter extends RObjectPainter { if (this.y_handle?.draw_grid) { let grid = ''; for (let n = 0; n < this.y_handle.ticks.length; ++n) { - grid += this.swap_xy + grid += this.#swap_xy ? `M${this.y_handle.ticks[n]},0v${h}` - : `M0,${h+this.y_handle.ticks[n]}h${w}`; + : `M0,${h + this.y_handle.ticks[n]}h${w}`; } if (grid) { @@ -184,15 +214,18 @@ class RFramePainter extends RObjectPainter { return handle ? handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']) : value.toPrecision(4); } - /** @summary Set axix range */ + /** @summary Set axis range */ _setAxisRange(prefix, vmin, vmax) { const nmin = `${prefix}min`, nmax = `${prefix}max`; - if (this[nmin] !== this[nmax]) return; + if (this[nmin] !== this[nmax]) + return; let min = this.v7EvalAttr(`${prefix}_min`), max = this.v7EvalAttr(`${prefix}_max`); - if (min !== undefined) vmin = min; - if (max !== undefined) vmax = max; + if (min !== undefined) + vmin = min; + if (max !== undefined) + vmax = max; if (vmin < vmax) { this[nmin] = vmin; @@ -214,7 +247,8 @@ class RFramePainter extends RObjectPainter { /** @summary Set axes ranges for drawing, check configured attributes if range already specified */ setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax) { - if (this.axes_drawn) return; + if (this.#axes_drawn) + return; this.xaxis = xaxis; this._setAxisRange('x', xmin, xmax); this.yaxis = yaxis; @@ -239,21 +273,23 @@ class RFramePainter extends RObjectPainter { * @desc Must be used only for v6 objects, see TFramePainter for more details * @private */ createXY(opts) { - if (this.self_drawaxes) return; + if (this.self_drawaxes) + return; this.cleanXY(); // remove all previous configurations - if (!opts) opts = { ndim: 1 }; + if (!opts) + opts = { ndim: 1 }; this.v6axes = true; - this.swap_xy = opts.swap_xy || false; - this.reverse_x = opts.reverse_x || false; - this.reverse_y = opts.reverse_y || false; + this.#swap_xy = opts.swap_xy || false; + this.#reverse_x = opts.reverse_x || false; + this.#reverse_y = opts.reverse_y || false; this.logx = this.v7EvalAttr('x_log', 0); this.logy = this.v7EvalAttr('y_log', 0); - const w = this.getFrameWidth(), h = this.getFrameHeight(); + const w = this.getFrameWidth(), h = this.getFrameHeight(), pp = this.getPadPainter(); this.scales_ndim = opts.ndim; @@ -264,11 +300,11 @@ class RFramePainter extends RObjectPainter { this.scale_ymax = this.ymax; if (opts.extra_y_space) { - const log_scale = this.swap_xy ? this.logx : this.logy; + const log_scale = this.#swap_xy ? this.logx : this.logy; if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax)*1.1); + this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); else - this.scale_ymax += (this.scale_ymax - this.scale_ymin)*0.1; + this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; } if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { @@ -292,64 +328,63 @@ class RFramePainter extends RObjectPainter { } let xaxis = this.xaxis, yaxis = this.yaxis; - if (xaxis?._typename !== clTAxis) xaxis = create(clTAxis); - if (yaxis?._typename !== clTAxis) yaxis = create(clTAxis); + if (xaxis?._typename !== clTAxis) + xaxis = create(clTAxis); + if (yaxis?._typename !== clTAxis) + yaxis = create(clTAxis); - this.x_handle = new TAxisPainter(this.getDom(), xaxis, true); - this.x_handle.setPadName(this.getPadName()); + this.x_handle = new TAxisPainter(pp, xaxis, true); this.x_handle.optionUnlab = this.v7EvalAttr('x_labels_hide', false); - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x, - log: this.swap_xy ? this.logy : this.logx, - symlog: this.swap_xy ? opts.symlog_y : opts.symlog_x, - logcheckmin: this.swap_xy, - logminfactor: 0.0001 }); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x, + log: this.#swap_xy ? this.logy : this.logx, + symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: 0.0001 + }); this.x_handle.assignFrameMembers(this, 'x'); - this.y_handle = new TAxisPainter(this.getDom(), yaxis, true); - this.y_handle.setPadName(this.getPadName()); + this.y_handle = new TAxisPainter(pp, yaxis, true); this.y_handle.optionUnlab = this.v7EvalAttr('y_labels_hide', false); - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y, - log: this.swap_xy ? this.logx : this.logy, - symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.ymax) ? 0.5 * opts.ymin_nz : 0, - logminfactor: 3e-4 }); + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + reverse: this.#reverse_y, + log: this.#swap_xy ? this.logx : this.logy, + symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.ymax) ? 0.5 * opts.ymin_nz : 0, + logminfactor: 3e-4 + }); this.y_handle.assignFrameMembers(this, 'y'); } /** @summary Identify if requested axes are drawn * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { - return !second_x && !second_y ? this.axes_drawn : false; - } + hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : false; } /** @summary Draw configured axes on the frame * @desc axes can be drawn only for main histogram */ async drawAxes() { - if (this.axes_drawn || (this.xmin === this.xmax) || (this.ymin === this.ymax)) - return this.axes_drawn; + if (this.#axes_drawn || (this.xmin === this.xmax) || (this.ymin === this.ymax)) + return this.#axes_drawn; const ticksx = this.v7EvalAttr('ticksX', 1), - ticksy = this.v7EvalAttr('ticksY', 1); - let sidex = 1, sidey = 1; - - if (this.v7EvalAttr('swapX', false)) sidex = -1; - if (this.v7EvalAttr('swapY', false)) sidey = -1; - - const w = this.getFrameWidth(), h = this.getFrameHeight(); + ticksy = this.v7EvalAttr('ticksY', 1), + sidex = this.v7EvalAttr('swapX', false) ? -1 : 1, + sidey = this.v7EvalAttr('swapY', false) ? -1 : 1, + w = this.getFrameWidth(), + h = this.getFrameHeight(), + pp = this.getPadPainter(); if (!this.v6axes) { // this is partially same as v6 createXY method this.cleanupAxes(); - this.swap_xy = false; + this.#swap_xy = false; if (this.zoom_xmin !== this.zoom_xmax) { this.scale_xmin = this.zoom_xmin; @@ -369,21 +404,18 @@ class RFramePainter extends RObjectPainter { this.recalculateRange(0); - this.x_handle = new RAxisPainter(this.getDom(), this, this.xaxis, 'x_'); - this.x_handle.setPadName(this.getPadName()); - this.x_handle.snapid = this.snapid; + this.x_handle = new RAxisPainter(pp, this, this.xaxis, 'x_'); + this.x_handle.assignSnapId(this.getSnapId()); this.x_handle.draw_swapside = (sidex < 0); this.x_handle.draw_ticks = ticksx; - this.y_handle = new RAxisPainter(this.getDom(), this, this.yaxis, 'y_'); - this.y_handle.setPadName(this.getPadName()); - this.y_handle.snapid = this.snapid; + this.y_handle = new RAxisPainter(pp, this, this.yaxis, 'y_'); + this.y_handle.assignSnapId(this.getSnapId()); this.y_handle.draw_swapside = (sidey < 0); this.y_handle.draw_ticks = ticksy; - this.z_handle = new RAxisPainter(this.getDom(), this, this.zaxis, 'z_'); - this.z_handle.setPadName(this.getPadName()); - this.z_handle.snapid = this.snapid; + this.z_handle = new RAxisPainter(pp, this, this.zaxis, 'z_'); + this.z_handle.assignSnapId(this.getSnapId()); this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, false, [0, w], w, { reverse: false }); this.x_handle.assignFrameMembers(this, 'x'); @@ -399,13 +431,13 @@ class RFramePainter extends RObjectPainter { this.x_handle.has_obstacle = false; - const draw_horiz = this.swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.swap_xy ? this.x_handle : this.y_handle; + const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, + draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; let pr; - if (this.getPadPainter()?._fast_drawing) + if (this.getPadPainter()?.isFastDrawing()) pr = Promise.resolve(true); // do nothing - else if (this.v6axes) { + else if (this.v6axes) { // in v7 ticksx/y values shifted by 1 relative to v6 // In v7 ticksx === 0 means no ticks, ticksx === 1 equivalent to === 0 in v6 @@ -415,14 +447,14 @@ class RFramePainter extends RObjectPainter { draw_vertical.disable_ticks = (ticksy <= 0); const pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - (ticksx > 1) ? -h : 0, disable_x_draw, - undefined, false, this.getPadPainter().getPadHeight() - h - this.getFrameY()), + draw_horiz.invert_side ? null : `translate(0,${h})`, + (ticksx > 1) ? -h : 0, disable_x_draw, + undefined, false, this.getPadPainter().getPadHeight() - h - this.getFrameY()), - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - (ticksy > 1) ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this._frame_x, can_adjust_frame); + pr2 = draw_vertical.drawAxis(layer, w, h, + draw_vertical.invert_side ? `translate(${w})` : null, + (ticksy > 1) ? w : 0, disable_y_draw, + draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); pr = Promise.all([pr1, pr2]).then(() => this.drawGrids()); } else { @@ -446,14 +478,15 @@ class RFramePainter extends RObjectPainter { } return pr.then(() => { - this.axes_drawn = true; + this.#axes_drawn = true; return true; }); } - /** @summary Draw secondary configuread axes */ + /** @summary Draw secondary configured axes */ drawAxes2(second_x, second_y) { const w = this.getFrameWidth(), h = this.getFrameHeight(), + pp = this.getPadPainter(), layer = this.getFrameSvg().selectChild('.axis_layer'); let pr1, pr2; @@ -462,12 +495,11 @@ class RFramePainter extends RObjectPainter { this.scale_x2min = this.zoom_x2min; this.scale_x2max = this.zoom_x2max; } else { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; + this.scale_x2min = this.x2min; + this.scale_x2max = this.x2max; } - this.x2_handle = new RAxisPainter(this.getDom(), this, this.x2axis, 'x2_'); - this.x2_handle.setPadName(this.getPadName()); - this.x2_handle.snapid = this.snapid; + this.x2_handle = new RAxisPainter(pp, this, this.x2axis, 'x2_'); + this.x2_handle.assignSnapId(this.getSnapId()); this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, false, [0, w], w, { reverse: false }); this.x2_handle.assignFrameMembers(this, 'x2'); @@ -484,9 +516,8 @@ class RFramePainter extends RObjectPainter { this.scale_y2max = this.y2max; } - this.y2_handle = new RAxisPainter(this.getDom(), this, this.y2axis, 'y2_'); - this.y2_handle.setPadName(this.getPadName()); - this.y2_handle.snapid = this.snapid; + this.y2_handle = new RAxisPainter(pp, this, this.y2axis, 'y2_'); + this.y2_handle.assignSnapId(this.getSnapId()); this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, true, [h, 0], -h, { reverse: false }); this.y2_handle.assignFrameMembers(this, 'y2'); @@ -502,8 +533,9 @@ class RFramePainter extends RObjectPainter { * @private */ getGrFuncs(second_x, second_y) { const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) return this; + use_y2 = second_y && this.gry2; + if (!use_x2 && !use_y2) + return this; return { use_x2, @@ -518,16 +550,20 @@ class RFramePainter extends RObjectPainter { logy: use_y2 ? this.y2_handle.log : this.y_handle.log, scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - swap_xy: this.swap_xy, fp: this, + swap_xy() { return this.fp.swap_xy(); }, revertAxis(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; + if ((name === 'x') && this.use_x2) + name = 'x2'; + if ((name === 'y') && this.use_y2) + name = 'y2'; return this.fp.revertAxis(name, v); }, axisAsText(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; + if ((name === 'x') && this.use_x2) + name = 'x2'; + if ((name === 'y') && this.use_y2) + name = 'y2'; return this.fp.axisAsText(name, v); } }; @@ -571,8 +607,8 @@ class RFramePainter extends RObjectPainter { cleanupAxes() { this.cleanXY(); - this.draw_g?.selectChild('.axis_layer').selectAll('*').remove(); - this.axes_drawn = false; + this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); + this.#axes_drawn = false; } /** @summary Removes all drawn elements of the frame @@ -585,7 +621,7 @@ class RFramePainter extends RObjectPainter { this.cleanupAxes(); const clean = (name) => { - this[name+'min'] = this[name+'max'] = 0; + this[name + 'min'] = this[name + 'max'] = 0; this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; this[`scale_${name}min`] = this[`scale_${name}max`] = 0; }; @@ -596,8 +632,8 @@ class RFramePainter extends RObjectPainter { clean('x2'); clean('y2'); - this.draw_g?.selectChild('.main_layer').selectAll('*').remove(); - this.draw_g?.selectChild('.upper_layer').selectAll('*').remove(); + this.getG()?.selectChild('.main_layer').selectAll('*').remove(); + this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); } /** @summary Fully cleanup frame @@ -605,20 +641,18 @@ class RFramePainter extends RObjectPainter { cleanup() { this.cleanFrameDrawings(); - if (this.draw_g) { - this.draw_g.selectAll('*').remove(); - this.draw_g.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null); - } + this.getG()?.selectAll('*').remove(); + this.getG()?.on('mousedown', null) + .on('dblclick', null) + .on('wheel', null) + .on('contextmenu', null) + .property('interactive_set', null); - if (this.keys_handler) { - window.removeEventListener('keydown', this.keys_handler, false); - this.keys_handler = null; + if (this.#keys_handler) { + window.removeEventListener('keydown', this.#keys_handler, false); + this.#keys_handler = undefined; } - delete this.enabledKeys; + this.#enabled_keys = undefined; delete this.self_drawaxes; delete this.xaxis; @@ -627,14 +661,12 @@ class RFramePainter extends RObjectPainter { delete this.x2axis; delete this.y2axis; - delete this.draw_g; // frame <g> element managet by the pad + this.setG(undefined); // frame <g> element managed by the pad - delete this._click_handler; - delete this._dblclick_handler; + this.#click_handler = undefined; + this.#dblclick_handler = undefined; - const pp = this.getPadPainter(); - if (pp?.frame_painter_ref === this) - delete pp.frame_painter_ref; + this.getPadPainter()?.setFramePainter(this, false); super.cleanup(); } @@ -643,82 +675,73 @@ class RFramePainter extends RObjectPainter { * @private */ redraw() { const pp = this.getPadPainter(); - if (pp) pp.frame_painter_ref = this; + pp?.setFramePainter(this, true); // first update all attributes from objects this.updateAttributes(); const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)); - let w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)), - rotate = false, fixpos = false, trans; - - if (pp?.options) { - if (pp.options.RotateFrame) rotate = true; - if (pp.options.FixFrame) fixpos = true; - } - - if (rotate) { - trans = `rotate(-90,${lm},${tm}) translate(${lm-h},${tm})`; - [w, h] = [h, w]; - } else - trans = makeTranslate(lm, tm); - + tm = Math.round(rect.height * (1 - this.fY2NDC)), + rotate = pp?.options?.RotateFrame, + w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), + h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); // update values here to let access even when frame is not really updated - this._frame_x = lm; - this._frame_y = tm; - this._frame_width = w; - this._frame_height = h; - this._frame_rotate = rotate; - this._frame_fixpos = fixpos; - - if (this.mode3d) return this; // no need for real draw in mode3d + this.#frame_x = lm; + this.#frame_y = tm; + this.#frame_width = rotate ? h : w; + this.#frame_height = rotate ? w : h; + this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); + this.$can_drag = !rotate && !pp?.options?.FixFrame; + + return this.mode3d ? this : this.createFrameG(); + } + /** @summary Create frame element and update all attributes + * @private */ + createFrameG() { // this is svg:g object - container for every other items belonging to frame - this.draw_g = this.getFrameSvg(); - - let top_rect, main_svg; + let g = this.setG(this.getFrameSvg()), + top_rect, main_svg; - if (this.draw_g.empty()) { - this.draw_g = this.getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame'); + if (g.empty()) { + g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); if (!this.isBatchMode()) - this.draw_g.append('svg:title').text(''); + g.append('svg:title').text(''); - top_rect = this.draw_g.append('svg:rect'); + top_rect = g.append('svg:rect'); - main_svg = this.draw_g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); + main_svg = g.append('svg:svg') + .attr('class', 'main_layer') + .attr('x', 0) + .attr('y', 0) + .attr('overflow', 'hidden'); - this.draw_g.append('svg:g').attr('class', 'axis_layer'); - this.draw_g.append('svg:g').attr('class', 'upper_layer'); + g.append('svg:g').attr('class', 'axis_layer'); + g.append('svg:g').attr('class', 'upper_layer'); } else { - top_rect = this.draw_g.selectChild('rect'); - main_svg = this.draw_g.selectChild('.main_layer'); + top_rect = g.selectChild('rect'); + main_svg = g.selectChild('.main_layer'); } - this.axes_drawn = false; + this.#axes_drawn = false; - this.draw_g.attr('transform', trans); + g.attr('transform', this.#frame_trans); top_rect.attr('x', 0) .attr('y', 0) - .attr('width', w) - .attr('height', h) + .attr('width', this.#frame_width) + .attr('height', this.#frame_height) .attr('rx', this.lineatt.rx || null) .attr('ry', this.lineatt.ry || null) .call(this.fillatt.func) .call(this.lineatt.func); - main_svg.attr('width', w) - .attr('height', h) - .attr('viewBox', `0 0 ${w} ${h}`); + main_svg.attr('width', this.#frame_width) + .attr('height', this.#frame_height) + .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); let pr = Promise.resolve(true); @@ -732,25 +755,25 @@ class RFramePainter extends RObjectPainter { } /** @summary Returns frame X position */ - getFrameX() { return this._frame_x || 0; } + getFrameX() { return this.#frame_x || 0; } /** @summary Returns frame Y position */ - getFrameY() { return this._frame_y || 0; } + getFrameY() { return this.#frame_y || 0; } /** @summary Returns frame width */ - getFrameWidth() { return this._frame_width || 0; } + getFrameWidth() { return this.#frame_width || 0; } /** @summary Returns frame height */ - getFrameHeight() { return this._frame_height || 0; } + getFrameHeight() { return this.#frame_height || 0; } /** @summary Returns frame rectangle plus extra info for hint display */ getFrameRect() { return { - x: this._frame_x || 0, - y: this._frame_y || 0, + x: this.#frame_x || 0, + y: this.#frame_y || 0, width: this.getFrameWidth(), height: this.getFrameHeight(), - transform: this.draw_g?.attr('transform') || '', + transform: this.getG()?.attr('transform') || '', hint_delta_x: 0, hint_delta_y: 0 }; @@ -762,98 +785,152 @@ class RFramePainter extends RObjectPainter { } /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed + * @desc Function will be called every time when frame click was performed * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of click will be disabled */ configureUserClickHandler(handler) { - this._click_handler = isFunc(handler) ? handler : null; + this.#click_handler = isFunc(handler) ? handler : null; } + /** @summary Returns actual click handler */ + getClickHandler() { return this.#click_handler; } + /** @summary Configure user-defined dblclick handler * @desc Function will be called every time when double click was called * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ configureUserDblclickHandler(handler) { - this._dblclick_handler = isFunc(handler) ? handler : null; + this.#dblclick_handler = isFunc(handler) ? handler : null; } + /** @summary Returns actual double-click handler */ + getDblclickHandler() { return this.#dblclick_handler; } + /** @summary function can be used for zooming into specified range * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax) { + async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { // disable zooming when axis conversion is enabled - if (this.projection) return false; + if (this.#projection) + return false; - if (xmin === 'x') { xmin = xmax; xmax = ymin; ymin = undefined; } else - if (xmin === 'y') { ymax = ymin; ymin = xmax; xmin = xmax = undefined; } else - if (xmin === 'z') { zmin = xmax; zmax = ymin; xmin = xmax = ymin = undefined; } + if (xmin === 'x') { + xmin = xmax; + xmax = ymin; + interactive = ymax; + ymin = ymax = undefined; + } else if (xmin === 'y') { + interactive = ymax; + ymax = ymin; + ymin = xmax; + xmin = xmax = undefined; + } else if (xmin === 'z') { + interactive = ymax; + zmin = xmax; + zmax = ymin; + xmin = xmax = ymin = ymax = undefined; + } let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), unzoom_x = false, unzoom_y = false, unzoom_z = false; if (zoom_x) { let cnt = 0; - if (xmin <= this.xmin) { xmin = this.xmin; cnt++; } - if (xmax >= this.xmax) { xmax = this.xmax; cnt++; } - if (cnt === 2) { zoom_x = false; unzoom_x = true; } + if (xmin <= this.xmin) { + xmin = this.xmin; + cnt++; + } + if (xmax >= this.xmax) { + xmax = this.xmax; + cnt++; + } + if (cnt === 2) { + zoom_x = false; + unzoom_x = true; + } } else unzoom_x = (xmin === xmax) && (xmin === 0); - if (zoom_y) { let cnt = 0; - if (ymin <= this.ymin) { ymin = this.ymin; cnt++; } - if (ymax >= this.ymax) { ymax = this.ymax; cnt++; } - if (cnt === 2) { zoom_y = false; unzoom_y = true; } + if (ymin <= this.ymin) { + ymin = this.ymin; + cnt++; + } + if (ymax >= this.ymax) { + ymax = this.ymax; + cnt++; + } + if (cnt === 2) { + zoom_y = false; + unzoom_y = true; + } } else unzoom_y = (ymin === ymax) && (ymin === 0); - if (zoom_z) { let cnt = 0; - // if (this.logz && this.ymin_nz && this.getDimension()===2) main_zmin = 0.3*this.ymin_nz; - if (zmin <= this.zmin) { zmin = this.zmin; cnt++; } - if (zmax >= this.zmax) { zmax = this.zmax; cnt++; } - if (cnt === 2) { zoom_z = false; unzoom_z = true; } + if (zmin <= this.zmin) { + zmin = this.zmin; + cnt++; + } + if (zmax >= this.zmax) { + zmax = this.zmax; + cnt++; + } + if (cnt === 2) { + zoom_z = false; + unzoom_z = true; + } } else unzoom_z = (zmin === zmax) && (zmin === 0); - let changed = false, r_x = '', r_y = '', r_z = '', is_any_check = false; const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false] - }, - - checkZooming = (painter, force) => { - if (!force && !isFunc(painter.canZoomInside)) return; + _typename: `${nsREX}RFrame::RUserRanges`, + values: [0, 0, 0, 0, 0, 0], + flags: [false, false, false, false, false, false] + }, checkZooming = (painter, force) => { + if (!force && !isFunc(painter.canZoomInside)) + return; is_any_check = true; if (zoom_x && (force || painter.canZoomInside('x', xmin, xmax))) { this.zoom_xmin = xmin; this.zoom_xmax = xmax; - changed = true; r_x = '0'; + changed = true; + r_x = '0'; zoom_x = false; - req.values[0] = xmin; req.values[1] = xmax; + req.values[0] = xmin; + req.values[1] = xmax; req.flags[0] = req.flags[1] = true; + if (interactive) + this.zoomChangedInteractive('x', interactive); } if (zoom_y && (force || painter.canZoomInside('y', ymin, ymax))) { this.zoom_ymin = ymin; this.zoom_ymax = ymax; - changed = true; r_y = '1'; + changed = true; + r_y = '1'; zoom_y = false; - req.values[2] = ymin; req.values[3] = ymax; + req.values[2] = ymin; + req.values[3] = ymax; req.flags[2] = req.flags[3] = true; + if (interactive) + this.zoomChangedInteractive('y', interactive); } if (zoom_z && (force || painter.canZoomInside('z', zmin, zmax))) { this.zoom_zmin = zmin; this.zoom_zmax = zmax; - changed = true; r_z = '2'; + changed = true; + r_z = '2'; zoom_z = false; - req.values[4] = zmin; req.values[5] = zmax; + req.values[4] = zmin; + req.values[5] = zmax; req.flags[4] = req.flags[5] = true; + if (interactive) + this.zoomChangedInteractive('z', interactive); } }; @@ -868,23 +945,39 @@ class RFramePainter extends RObjectPainter { // and process unzoom, if any if (unzoom_x || unzoom_y || unzoom_z) { if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) { changed = true; r_x = '0'; } + if (this.zoom_xmin !== this.zoom_xmax) { + changed = true; + r_x = '0'; + } this.zoom_xmin = this.zoom_xmax = 0; req.values[0] = req.values[1] = -1; + if (interactive) + this.zoomChangedInteractive('x', interactive); } if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { changed = true; r_y = '1'; } + if (this.zoom_ymin !== this.zoom_ymax) { + changed = true; + r_y = '1'; + } this.zoom_ymin = this.zoom_ymax = 0; req.values[2] = req.values[3] = -1; + if (interactive) + this.zoomChangedInteractive('y', interactive); } if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) { changed = true; r_z = '2'; } + if (this.zoom_zmin !== this.zoom_zmax) { + changed = true; + r_z = '2'; + } this.zoom_zmin = this.zoom_zmax = 0; req.values[4] = req.values[5] = -1; + if (interactive) + this.zoomChangedInteractive('z', interactive); } } - if (!changed) return false; + if (!changed) + return false; if (this.v7NormalMode()) this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); @@ -892,45 +985,61 @@ class RFramePainter extends RObjectPainter { return this.interactiveRedraw('pad', 'zoom' + r_x + r_y + r_z).then(() => true); } - /** @summary Provide zooming of single axis - * @desc One can specify names like x/y/z but also second axis x2 or y2 */ - async zoomSingle(name, vmin, vmax) { + /** @summary Zooming of single axis + * @param {String} name - axis name like x/y/z but also second axis x2 or y2 + * @param {Number} vmin - axis minimal value, 0 for unzoom + * @param {Number} vmax - axis maximal value, 0 for unzoom + * @param {Boolean} [interactive] - if change was performed interactively + * @protected */ + async zoomSingle(name, vmin, vmax, interactive) { const names = ['x', 'y', 'z', 'x2', 'y2'], indx = names.indexOf(name); // disable zooming when axis conversion is enabled - if (this.projection || !this[name+'_handle'] || (indx < 0)) + if (this.#projection || (!this[`${name}_handle`] && (name !== 'z')) || (indx < 0)) return false; let zoom_v = (vmin !== vmax), unzoom_v = false; if (zoom_v) { let cnt = 0; - if (vmin <= this[name+'min']) { vmin = this[name+'min']; cnt++; } - if (vmax >= this[name+'max']) { vmax = this[name+'max']; cnt++; } - if (cnt === 2) { zoom_v = false; unzoom_v = true; } + if (vmin <= this[name + 'min']) { + vmin = this[name + 'min']; + cnt++; + } + if (vmax >= this[name + 'max']) { + vmax = this[name + 'max']; + cnt++; + } + if (cnt === 2) { + zoom_v = false; + unzoom_v = true; + } } else unzoom_v = (vmin === vmax) && (vmin === 0); let changed = false, is_any_check = false; const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false, false, false, false, false] - }, + _typename: `${nsREX}RFrame::RUserRanges`, + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + flags: [false, false, false, false, false, false, false, false, false, false] + }; - checkZooming = (painter, force) => { - if (!force && !isFunc(painter?.canZoomInside)) return; + // eslint-disable-next-line one-var + const checkZooming = (painter, force) => { + if (!force && !isFunc(painter?.canZoomInside)) + return; is_any_check = true; if (zoom_v && (force || painter.canZoomInside(name[0], vmin, vmax))) { - this['zoom_' + name + 'min'] = vmin; - this['zoom_' + name + 'max'] = vmax; + this[`zoom_${name}min`] = vmin; + this[`zoom_${name}max`] = vmax; changed = true; zoom_v = false; - req.values[indx*2] = vmin; req.values[indx*2+1] = vmax; - req.flags[indx*2] = req.flags[indx*2+1] = true; + req.values[indx * 2] = vmin; + req.values[indx * 2 + 1] = vmax; + req.flags[indx * 2] = req.flags[indx * 2 + 1] = true; } }; @@ -943,12 +1052,17 @@ class RFramePainter extends RObjectPainter { checkZooming(null, true); if (unzoom_v) { - if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) changed = true; + if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) + changed = true; this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - req.values[indx*2] = req.values[indx*2+1] = -1; + req.values[indx * 2] = req.values[indx * 2 + 1] = -1; } - if (!changed) return false; + if (!changed) + return false; + + if (interactive) + this.zoomChangedInteractive(name, interactive); if (this.v7NormalMode()) this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); @@ -956,6 +1070,11 @@ class RFramePainter extends RObjectPainter { return this.interactiveRedraw('pad', `zoom${indx}`).then(() => true); } + /** @summary Unzoom single axis */ + async unzoomSingle(name, interactive) { + return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); + } + /** @summary Checks if specified axis zoomed */ isAxisZoomed(axis) { return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; @@ -967,24 +1086,30 @@ class RFramePainter extends RObjectPainter { if (dox === 'all') return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); - if ((dox === 'x2') || (dox === 'y2')) { - return this.zoomSingle(dox, 0, 0).then(changed => { - if (changed) this.zoomChangedInteractive(dox, 'unzoom'); - return changed; - }); - } + if ((dox === 'x2') || (dox === 'y2')) + return this.unzoomSingle(dox); - if (typeof dox === 'undefined') dox = doy = doz = true; else - if (isStr(dox)) { doz = dox.indexOf('z') >= 0; doy = dox.indexOf('y') >= 0; dox = dox.indexOf('x') >= 0; } + if (typeof dox === 'undefined') + dox = doy = doz = true; + else if (isStr(dox)) { + doz = dox.indexOf('z') >= 0; + doy = dox.indexOf('y') >= 0; + dox = dox.indexOf('x') >= 0; + } return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined).then(changed => { - if (changed && dox) this.zoomChangedInteractive('x', 'unzoom'); - if (changed && doy) this.zoomChangedInteractive('y', 'unzoom'); - if (changed && doz) this.zoomChangedInteractive('z', 'unzoom'); + doz ? 0 : undefined, doz ? 0 : undefined, + 'unzoom'); + } - return changed; + /** @summary Reset all zoom attributes + * @private */ + resetZoom() { + ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { + this[`zoom_${n}min`] = undefined; + this[`zoom_${n}max`] = undefined; + this[`zoom_changed_${n}`] = undefined; }); } @@ -998,10 +1123,12 @@ class RFramePainter extends RObjectPainter { if (!axis || axis === 'any') return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) return; + if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) + return; const fld = 'zoom_changed_' + axis; - if (value === undefined) return this[fld]; + if (value === undefined) + return this[fld]; if (value === 'unzoom') { // special handling of unzoom, only if was never changed before flag set to true @@ -1009,18 +1136,17 @@ class RFramePainter extends RObjectPainter { return; } - if (value) this[fld] = true; + if (value) + this[fld] = true; } /** @summary Fill menu for frame when server is not there */ fillObjectOfflineMenu(menu, kind) { - if ((kind !== 'x') && (kind !== 'y')) return; + if ((kind !== 'x') && (kind !== 'y')) + return; menu.add('Unzoom', () => this.unzoom(kind)); - // if (this[kind+'_kind'] === kAxisNormal) - // menu.addchk(this['log'+kind], 'SetLog'+kind, this.toggleAxisLog.bind(this, kind)); - // here should be all axes attributes in offline } @@ -1034,24 +1160,32 @@ class RFramePainter extends RObjectPainter { } /** @summary Fill context menu */ - fillContextMenu(menu, kind /* , obj */) { - // when fill and show context menu, remove all zooming - - if (kind === 'pal') kind = 'z'; + fillContextMenu(menu, kind, obj) { + if (kind === 'pal') + kind = 'z'; if ((kind === 'x') || (kind === 'y') || (kind === 'x2') || (kind === 'y2')) { - const handle = this[kind+'_handle']; - if (!handle) return false; - menu.add('header: ' + kind.toUpperCase() + ' axis'); + const handle = this[kind + '_handle'], + faxis = obj || this[kind + 'axis']; + if (!handle) + return false; + menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); + + if (isFunc(faxis?.TestBit)) { + const main = this.getMainPainter(true); + menu.addTAxisMenu(EAxisBits, main || this, faxis, kind); + return true; + } + return handle.fillAxisContextMenu(menu, kind); } const alone = menu.size() === 0; if (alone) - menu.add('header:Frame'); + menu.header('Frame', `${urlClassPrefix}ROOT_1_1Experimental_1_1RFrame.html`); else - menu.add('separator'); + menu.separator(); if (this.zoom_xmin !== this.zoom_xmax) menu.add('Unzoom X', () => this.unzoom('x')); @@ -1065,7 +1199,7 @@ class RFramePainter extends RObjectPainter { menu.add('Unzoom Y2', () => this.unzoom('y2')); menu.add('Unzoom all', () => this.unzoom('all')); - menu.add('separator'); + menu.separator(); menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); @@ -1078,28 +1212,31 @@ class RFramePainter extends RObjectPainter { if (this.y_handle && !this.y2_handle) menu.addchk(this.y_handle.draw_swapside, 'Swap y', flag => this.changeFrameAttr('swapY', flag)); if (this.x_handle && !this.x2_handle) { - menu.add('sub:Ticks x'); + menu.sub('Ticks x'); menu.addchk(this.x_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksX', 0)); menu.addchk(this.x_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksX', 1)); menu.addchk(this.x_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksX', 2)); menu.addchk(this.x_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksX', 3)); - menu.add('endsub:'); - } + menu.endsub(); + } if (this.y_handle && !this.y2_handle) { - menu.add('sub:Ticks y'); + menu.sub('Ticks y'); menu.addchk(this.y_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksY', 0)); menu.addchk(this.y_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksY', 1)); menu.addchk(this.y_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksY', 2)); menu.addchk(this.y_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksY', 3)); - menu.add('endsub:'); - } + menu.endsub(); + } menu.addAttributesMenu(this, alone ? '' : 'Frame '); - menu.add('separator'); + menu.separator(); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`frame.${fmt}`, () => this.getPadPainter().saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.add('endsub:'); + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => this.getPadPainter().saveAs(fmt, 'frame', `frame.${fmt}`))); + menu.endsub(); return true; } @@ -1115,7 +1252,8 @@ class RFramePainter extends RObjectPainter { m = d3_pointer(evnt, this.getFrameSvg().node()); let id = (axis_name === 'x') ? 0 : 1; - if (this.swap_xy) id = 1 - id; + if (this.#swap_xy) + id = 1 - id; const axis_value = this.revertAxis(axis_name, m[id]); @@ -1125,9 +1263,14 @@ class RFramePainter extends RObjectPainter { /** @summary Add interactive keys handlers * @private */ addKeysHandler() { - if (this.isBatchMode()) return; + if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) + return; + FrameInteractive.assign(this); - this.addFrameKeysHandler(); + + this.#keys_handler = evnt => this.processKeyPress(evnt); + + window.addEventListener('keydown', this.#keys_handler, false); } /** @summary Add interactive functionality to the frame @@ -1150,7 +1293,7 @@ class RFramePainter extends RObjectPainter { /** @summary Toggle log scale on the specified axes */ toggleAxisLog(axis) { - const handle = this[axis+'_handle']; + const handle = this[axis + '_handle']; return handle?.changeAxisLog('toggle'); } diff --git a/modules/gpad/RPadPainter.mjs b/modules/gpad/RPadPainter.mjs index 040c04f20..91ffe9082 100644 --- a/modules/gpad/RPadPainter.mjs +++ b/modules/gpad/RPadPainter.mjs @@ -1,13 +1,14 @@ -import { gStyle, settings, constants, internals, addMethods, - isPromise, getPromise, postponePromise, isBatchMode, isObject, isFunc, isStr, clTPad, clTFrame, nsREX } from '../core.mjs'; -import { ColorPalette, addColor, getRootColors } from '../base/colors.mjs'; +import { gStyle, settings, browser, constants, internals, addMethods, isPromise, getPromise, postponePromise, + isBatchMode, isObject, isFunc, isStr, clTFrame, nsREX, nsSVG, urlClassPrefix } from '../core.mjs'; +import { select as d3_select } from '../d3.mjs'; +import { ColorPalette, addColor, getRootColors, convertColor } from '../base/colors.mjs'; import { RObjectPainter } from '../base/RObjectPainter.mjs'; -import { getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, svgToImage } from '../base/BasePainter.mjs'; -import { selectActivePad, getActivePad } from '../base/ObjectPainter.mjs'; +import { prSVG, getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, svgToImage } from '../base/BasePainter.mjs'; +import { selectActivePad, getActivePad, isPadPainter } from '../base/ObjectPainter.mjs'; import { registerForResize, saveFile } from '../gui/utils.mjs'; -import { BrowserLayout } from '../gui/display.mjs'; +import { BrowserLayout, getHPainter } from '../gui/display.mjs'; import { createMenu, closeMenu } from '../gui/menu.mjs'; -import { PadButtonsHandler } from './TPadPainter.mjs'; +import { PadButtonsHandler, webSnapIds } from './TPadPainter.mjs'; /** @@ -18,27 +19,64 @@ import { PadButtonsHandler } from './TPadPainter.mjs'; class RPadPainter extends RObjectPainter { + #iscan; // is canvas flag + #pad_name; // name of the pad + #pad; // RPad object + #painters; // painters in the pad + #pad_scale; // scaling factor of the pad + #pad_x; // pad x coordinate + #pad_y; // pad y coordinate + #pad_width; // pad width + #pad_height; // pad height + #doing_draw; // drawing handles + #custom_palette; // custom palette + #frame_painter_ref; // frame painter + #main_painter_ref; // main painter on the pad + #num_primitives; // number of primitives + #auto_color_cnt; // counter for auto colors + #fixed_size; // fixed size flag + #has_canvas; // when top-level canvas exists + #fast_drawing; // fast drawing mode + #resize_tmout; // timeout handle for resize + #start_draw_tm; // time when start drawing primitives + /** @summary constructor */ - constructor(dom, pad, iscan) { + constructor(dom, pad, opt, iscan, add_to_primitives) { super(dom, pad, '', 'pad'); - this.pad = pad; - this.iscan = iscan; // indicate if working with canvas - this.this_pad_name = ''; - if (!this.iscan && (pad !== null)) { + this.#pad = pad; + this.#iscan = iscan; // indicate if working with canvas + this.#pad_name = ''; + if (!iscan && pad) { if (pad.fObjectID) - this.this_pad_name = 'pad' + pad.fObjectID; // use objectid as padname + this.#pad_name = 'pad' + pad.fObjectID; // use objectid as pad name else - this.this_pad_name = 'ppp' + internals.id_counter++; // artificical name + this.#pad_name = 'ppp' + internals.id_counter++; // artificial name } - this.painters = []; // complete list of all painters in the pad - this.has_canvas = true; + this.#painters = []; // complete list of all painters in the pad + this.#has_canvas = true; this.forEachPainter = this.forEachPainterInPad; const d = this.selectDom(); if (!d.empty() && d.property('_batch_mode')) this.batch_mode = true; + + if (opt !== undefined) + this.decodeOptions(opt); + + if (add_to_primitives) { + if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { + this.#has_canvas = false; + this.#pad_name = ''; + this.setTopPainter(); + } else + this.addToPadPrimitives(); // must be here due to pad painter + } } + /** @summary Returns pad name + * @protected */ + getPadName() { return this.#pad_name; } + /** @summary Indicates that drawing runs in batch mode * @private */ isBatchMode() { @@ -48,10 +86,7 @@ class RPadPainter extends RObjectPainter { if (isBatchMode()) return true; - if (!this.iscan && this.has_canvas) - return this.getCanvPainter()?.isBatchMode(); - - return false; + return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); } /** @summary Indicates that is not Root6 pad painter @@ -59,59 +94,106 @@ class RPadPainter extends RObjectPainter { isRoot6() { return false; } /** @summary Returns true if pad is editable */ - isEditable() { - return true; + isEditable() { return true; } + + /** @summary Returns true if button */ + isButton() { return false; } + + /** @summary Returns true if it is canvas + * @param {Boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ + isCanvas(is_online = false) { + if (!this.#iscan) + return false; + if (is_online === true) + return isFunc(this.getWebsocket) && this.getWebsocket(); + return isStr(is_online) ? this.#iscan === is_online : true; } - /** @summary Returns SVG element for the pad itself - * @private */ - svg_this_pad() { - return this.getPadSvg(this.this_pad_name); + /** @summary Returns true if it is canvas or top pad without canvas */ + isTopPad() { return this.isCanvas() || !this.#has_canvas; } + + /** @summary returns pad painter + * @protected */ + getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } + + /** @summary returns canvas painter + * @protected */ + getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } + + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg + * @protected */ + getCanvSvg() { return this.selectDom().select('.root_canvas'); } + + /** @summary Pad svg element + * @return {object} d3 selection with pad svg + * @protected */ + getPadSvg() { + const c = this.getCanvSvg(); + if (!this.#pad_name || c.empty()) + return c; + + return c.select('.primitives_layer .__root_pad_' + this.#pad_name); + } + + /** @summary Method selects immediate layer under canvas/pad main element + * @param {string} name - layer name like 'primitives_layer', 'btns_layer', 'info_layer' + * @protected */ + getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } + + /** @summary Returns svg element for the frame in current pad + * @protected */ + getFrameSvg() { + const layer = this.getLayerSvg('primitives_layer'); + if (layer.empty()) + return layer; + let node = layer.node().firstChild; + while (node) { + const elem = d3_select(node); + if (elem.classed('root_frame')) + return elem; + node = node.nextSibling; + } + return d3_select(null); } /** @summary Returns main painter on the pad * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { - return this.main_painter_ref || null; - } + * @private */ + getMainPainter() { return this.#main_painter_ref || null; } /** @summary Assign main painter on the pad * @private */ setMainPainter(painter, force) { - if (!this.main_painter_ref || force) - this.main_painter_ref = painter; + if (!this.#main_painter_ref || force) + this.#main_painter_ref = painter; } /** @summary cleanup pad and all primitives inside */ cleanup() { - if (this._doing_draw) + if (this.#doing_draw) console.error('pad drawing is not completed when cleanup is called'); - this.painters.forEach(p => p.cleanup()); + this.#painters.forEach(p => p.cleanup()); - const svg_p = this.svg_this_pad(); + const svg_p = this.getPadSvg(); if (!svg_p.empty()) { svg_p.property('pad_painter', null); - if (!this.iscan) svg_p.remove(); + if (!this.isCanvas()) + svg_p.remove(); } - delete this.main_painter_ref; - delete this.frame_painter_ref; - delete this.pads_cache; - delete this._pad_x; - delete this._pad_y; - delete this._pad_width; - delete this._pad_height; - delete this._doing_draw; + this.#main_painter_ref = undefined; + this.#frame_painter_ref = undefined; + this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; + this.#doing_draw = undefined; delete this._dfltRFont; - this.painters = []; - this.pad = null; - this.draw_object = null; - this.pad_frame = null; - this.this_pad_name = undefined; - this.has_canvas = false; + this.#painters = []; + this.#pad = undefined; + this.assignObject(null); + this.#pad_name = undefined; + this.#has_canvas = false; selectActivePad({ pp: this, active: false }); @@ -120,42 +202,55 @@ class RPadPainter extends RObjectPainter { /** @summary Returns frame painter inside the pad * @private */ - getFramePainter() { return this.frame_painter_ref; } + getFramePainter() { return this.#frame_painter_ref; } + + /** @summary Assign actual frame painter + * @private */ + setFramePainter(fp, on) { + if (on) + this.#frame_painter_ref = fp; + else if (this.#frame_painter_ref === fp) + this.#frame_painter_ref = undefined; + } /** @summary get pad width */ - getPadWidth() { return this._pad_width || 0; } + getPadWidth() { return this.#pad_width || 0; } /** @summary get pad height */ - getPadHeight() { return this._pad_height || 0; } + getPadHeight() { return this.#pad_height || 0; } + + /** @summary get pad height */ + getPadScale() { return this.#pad_scale || 1; } /** @summary return pad log state x or y are allowed */ - getPadLog(name) { return false; } + getPadLog(/* name */) { return false; } /** @summary get pad rect */ getPadRect() { return { - x: this._pad_x || 0, - y: this._pad_y || 0, + x: this.#pad_x || 0, + y: this.#pad_y || 0, width: this.getPadWidth(), height: this.getPadHeight() }; } - /** @summary Returns frame coordiantes - also when frame is not drawn */ + /** @summary Returns frame coordinates - also when frame is not drawn */ getFrameRect() { const fp = this.getFramePainter(); - if (fp) return fp.getFrameRect(); + if (fp) + return fp.getFrameRect(); const w = this.getPadWidth(), h = this.getPadHeight(), rect = {}; - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); rect.hint_delta_x = rect.szx; rect.hint_delta_y = rect.szy; rect.transform = makeTranslate(rect.x, rect.y) || ''; @@ -164,41 +259,79 @@ class RPadPainter extends RObjectPainter { /** @summary return RPad object */ getRootPad(is_root6) { - return (is_root6 === undefined) || !is_root6 ? this.pad : null; + return (is_root6 === undefined) || !is_root6 ? this.#pad : null; } /** @summary Cleanup primitives from pad - selector lets define which painters to remove * @private */ cleanPrimitives(selector) { - if (!isFunc(selector)) return; + // remove all primitives + if (selector === true) + selector = () => true; + + if (!isFunc(selector)) + return false; - for (let k = this.painters.length-1; k >= 0; --k) { - if (selector(this.painters[k])) { - this.painters[k].cleanup(); - this.painters.splice(k, 1); + let is_any = false; + + for (let k = this.#painters.length - 1; k >= 0; --k) { + const subp = this.#painters[k]; + if (selector(subp)) { + subp.cleanup(); + this.#painters.splice(k, 1); + is_any = true; } } + + return is_any; + } + + /** @summary Divide pad on sub-pads */ + async divide(/* nx, ny, use_existing */) { + if (settings.Debug) + console.warn('RPadPainter.divide not implemented'); + return this; } /** @summary Removes and cleanup specified primitive * @desc also secondary primitives will be removed * @return new index to continue loop or -111 if main painter removed * @private */ - removePrimitive(indx) { - const prim = this.painters[indx], arr = []; - let resindx = indx; - for (let k = this.painters.length-1; k >= 0; --k) { - if ((k === indx) || this.painters[k].isSecondary(prim)) { - arr.push(this.painters[k]); - this.painters.splice(k, 1); - if (k <= indx) resindx--; + removePrimitive(arg, clean_only_secondary) { + let indx, prim = null; + if (Number.isInteger(arg)) { + indx = arg; + prim = this.#painters[indx]; + } else { + indx = this.#painters.indexOf(arg); + prim = arg; + } + if (indx < 0) + return indx; + + const arr = []; + let resindx = indx - 1; // object removed itself + arr.push(prim); + this.#painters.splice(indx, 1); + + let len0 = 0; + while (len0 < arr.length) { + for (let k = this.#painters.length - 1; k >= 0; --k) { + if (this.#painters[k].isSecondary(arr[len0])) { + arr.push(this.#painters[k]); + this.#painters.splice(k, 1); + if (k <= indx) + resindx--; + } } + len0++; } arr.forEach(painter => { - painter.cleanup(); - if (this.main_painter_ref === painter) { - delete this.main_painter_ref; + if ((painter !== prim) || !clean_only_secondary) + painter.cleanup(); + if (this.getMainPainter() === painter) { + delete this.setMainPainter(undefined, true); resindx = -111; } }); @@ -209,8 +342,9 @@ class RPadPainter extends RObjectPainter { /** @summary try to find object by name in list of pad primitives * @desc used to find title drawing * @private */ - findInPrimitives(objname, objtype) { - console.warn('findInPrimitives not implemented for RPad'); + findInPrimitives(/* objname, objtype */) { + if (settings.Debug) + console.warn('findInPrimitives not implemented for RPad'); return null; } @@ -219,14 +353,19 @@ class RPadPainter extends RObjectPainter { * histogram functions * @private */ findPainterFor(selobj, selname, seltype) { - return this.painters.find(p => { + return this.#painters.find(p => { const pobj = p.getObject(); - if (!pobj) return false; + if (!pobj) + return false; - if (selobj && (pobj === selobj)) return true; - if (!selname && !seltype) return false; - if (selname && (pobj.fName !== selname)) return false; - if (seltype && (pobj._typename !== seltype)) return false; + if (selobj && (pobj === selobj)) + return true; + if (!selname && !seltype) + return false; + if (selname && (pobj.fName !== selname)) + return false; + if (seltype && (pobj._typename !== seltype)) + return false; return true; }); } @@ -236,7 +375,8 @@ class RPadPainter extends RObjectPainter { getHistPalette() { const pp = this.findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`); - if (pp) return pp.getHistPalette(); + if (pp) + return pp.getHistPalette(); if (!this.fDfltPalette) { this.fDfltPalette = { @@ -250,8 +390,8 @@ class RPadPainter extends RObjectPainter { { fOrdinal: 0.75, fColor: { fColor: 'rgb(209, 187, 89)' } }, { fOrdinal: 0.875, fColor: { fColor: 'rgb(254, 200, 50)' } }, { fOrdinal: 1, fColor: { fColor: 'rgb(249, 251, 14)' } }], - fInterpolate: true, - fNormalized: true + fInterpolate: true, + fNormalized: true }; addMethods(this.fDfltPalette, `${nsREX}RPalette`); } @@ -259,22 +399,40 @@ class RPadPainter extends RObjectPainter { return this.fDfltPalette; } - /** @summary Returns number of painters + /** @summary Returns custom palette * @private */ - getNumPainters() { return this.painters.length; } + getCustomPalette(no_recursion) { + return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); + } + + /** @summary Returns number of painters + * @protected */ + getNumPainters() { return this.#painters.length; } + + /** @summary Add painter to pad list of painters + * @protected */ + addToPrimitives(painter) { + if (this.#painters.indexOf(painter) < 0) + this.#painters.push(painter); + return this; + } /** @summary Call function for each painter in pad * @param {function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and subpads, 'objects' only for object in current pad + * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad * @private */ forEachPainterInPad(userfunc, kind) { - if (!kind) kind = 'all'; - if (kind !== 'objects') userfunc(this); - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; + if (!kind) + kind = 'all'; + if (kind !== 'objects') + userfunc(this); + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') userfunc(sub); + if (kind !== 'objects') + sub.forEachPainterInPad(userfunc, kind); + } else if (kind !== 'pads') + userfunc(sub); } } @@ -288,43 +446,45 @@ class RPadPainter extends RObjectPainter { /** @summary Generate pad events, normally handled by GED * @desc in pad painter, while pad may be drawn without canvas * @private */ - producePadEvent(what, padpainter, painter, position, place) { + producePadEvent(what, padpainter, painter, position) { if ((what === 'select') && isFunc(this.selectActivePad)) this.selectActivePad(padpainter, painter, position); - if (this.pad_events_receiver) - this.pad_events_receiver({ what, padpainter, painter, position, place }); + if (isFunc(this.pad_events_receiver)) + this.pad_events_receiver({ what, padpainter, painter, position }); } /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos, place) { - const istoppad = (this.iscan || !this.has_canvas), - canp = istoppad ? this : this.getCanvPainter(); + selectObjectPainter(painter, pos) { + const canp = this.isTopPad() ? this : this.getCanvPainter(); - if (painter === undefined) painter = this; + if (painter === undefined) + painter = this; - if (pos && !istoppad) - pos = getAbsPosInCanvas(this.svg_this_pad(), pos); + if (pos && !this.isTopPad()) + pos = getAbsPosInCanvas(this.getPadSvg(), pos); selectActivePad({ pp: this, active: true }); - canp.producePadEvent('select', this, painter, pos, place); + canp.producePadEvent('select', this, painter, pos); } /** @summary Set fast drawing property depending on the size * @private */ setFastDrawing(w, h) { - const was_fast = this._fast_drawing; - this._fast_drawing = settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this._fast_drawing) + const was_fast = this.#fast_drawing; + this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); + if (was_fast !== this.#fast_drawing) this.showPadButtons(); } + /** @summary Return fast drawing flag + * @private */ + isFastDrawing() { return this.#fast_drawing; } + /** @summary Returns true if canvas configured with grayscale * @private */ - isGrayscale() { - return false; - } + isGrayscale() { return false; } /** @summary Set grayscale mode for the canvas * @private */ @@ -332,14 +492,20 @@ class RPadPainter extends RObjectPainter { console.error('grayscale mode not implemented for RCanvas'); } + /** @summary Returns true if default pad range is configured + * @private */ + isDefaultPadRange() { + return true; + } + /** @summary Create SVG element for the canvas */ createCanvasSvg(check_resize, new_size) { const lmt = 5; - let factor = null, svg = null, rect = null, btns, frect; + let factor, svg, rect, btns, frect; if (check_resize > 0) { - if (this._fixed_size) - return check_resize > 1; // flag used to force re-drawing of all subpads + if (this.#fixed_size) + return check_resize > 1; // flag used to force re-drawing of all sub-pads svg = this.getCanvSvg(); if (svg.empty()) @@ -353,7 +519,7 @@ class RPadPainter extends RObjectPainter { return false; if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + btns = this.getLayerSvg('btns_layer'); frect = svg.selectChild('.canvas_fillrect'); } else { @@ -365,7 +531,6 @@ class RPadPainter extends RObjectPainter { svg = render_to.append('svg') .attr('class', 'jsroot root_canvas') .property('pad_painter', this) // this is custom property - .property('current_pad', '') // this is custom property .property('redraw_by_resize', false); // could be enabled to force redraw by each resize this.setTopPainter(); // assign canvas as top painter of that element @@ -373,6 +538,9 @@ class RPadPainter extends RObjectPainter { if (!this.isBatchMode() && !this.online_canvas) svg.append('svg:title').text('ROOT canvas'); + if (!this.isBatchMode()) + svg.style('user-select', settings.UserSelect || null); + frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); if (!this.isBatchMode()) { frect.style('pointer-events', 'visibleFill') @@ -392,14 +560,15 @@ class RPadPainter extends RObjectPainter { } factor = 0.66; - if (this.pad && this.pad.fWinSize[0] && this.pad.fWinSize[1]) { - factor = this.pad.fWinSize[1] / this.pad.fWinSize[0]; - if ((factor < 0.1) || (factor > 10)) factor = 0.66; + if (this.#pad?.fWinSize[0] && this.#pad.fWinSize[1]) { + factor = this.#pad.fWinSize[1] / this.#pad.fWinSize[0]; + if ((factor < 0.1) || (factor > 10)) + factor = 0.66; } - if (this._fixed_size) { + if (this.#fixed_size) { render_to.style('overflow', 'auto'); - rect = { width: this.pad.fWinSize[0], height: this.pad.fWinSize[1] }; + rect = { width: this.#pad.fWinSize[0], height: this.#pad.fWinSize[1] }; if (!rect.width || !rect.height) rect = getElementRect(render_to); } else @@ -409,25 +578,35 @@ class RPadPainter extends RObjectPainter { this.createAttFill({ pattern: 1001, color: 0 }); if ((rect.width <= lmt) || (rect.height <= lmt)) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - rect.width = 200; rect.height = 100; // just to complete drawing + if (!this.hasSnapId()) { + svg.style('display', 'none'); + console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); + } + if (this.#pad_width && this.#pad_height) { + // use last valid dimensions + rect.width = this.#pad_width; + rect.height = this.#pad_height; + } else { + // just to complete drawing. + rect.width = 800; + rect.height = 600; + } } else svg.style('display', null); - if (this._fixed_size) { + if (this.#fixed_size) { svg.attr('x', 0) .attr('y', 0) .attr('width', rect.width) .attr('height', rect.height) .style('position', 'absolute'); } else { - svg.attr('x', 0) - .attr('y', 0) - .style('width', '100%') - .style('height', '100%') - .style('position', 'absolute') - .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); + svg.attr('x', 0) + .attr('y', 0) + .style('width', '100%') + .style('height', '100%') + .style('position', 'absolute') + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); } svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); @@ -440,17 +619,17 @@ class RPadPainter extends RObjectPainter { .property('draw_width', rect.width) .property('draw_height', rect.height); - this._pad_x = 0; - this._pad_y = 0; - this._pad_width = rect.width; - this._pad_height = rect.height; + this.#pad_x = 0; + this.#pad_y = 0; + this.#pad_width = rect.width; + this.#pad_height = rect.height; frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) .call(this.fillatt.func); this.setFastDrawing(rect.width, rect.height); - if (this.alignButtons && btns) + if (isFunc(this.alignButtons) && btns) this.alignButtons(btns, rect.width, rect.height); return true; @@ -466,25 +645,27 @@ class RPadPainter extends RObjectPainter { evnt?.preventDefault(); evnt?.stopPropagation(); - // ignore double click on canvas itself for enlarge - if (is_dblclick && this._websocket && (this.enlargeMain('state') === 'off')) + // ignore double click on online canvas itself for enlarge + if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) return; const svg_can = this.getCanvSvg(), pad_enlarged = svg_can.property('pad_enlarged'); - if (this.iscan || !this.has_canvas || (!pad_enlarged && !this.hasObjectsToDraw() && !this.painters)) { - if (this._fixed_size) return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) return; + if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { + if (this.#fixed_size) + return; // canvas cannot be enlarged in such mode + if (!this.enlargeMain(is_escape ? false : 'toggle')) + return; if (this.enlargeMain('state') === 'off') svg_can.property('pad_enlarged', null); else selectActivePad({ pp: this, active: true }); } else if (!pad_enlarged && !is_escape) { this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.pad); + svg_can.property('pad_enlarged', this.#pad); selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.pad) { + } else if (pad_enlarged === this.#pad) { this.enlargeMain(false); svg_can.property('pad_enlarged', null); } else if (!is_escape && is_dblclick) @@ -496,48 +677,56 @@ class RPadPainter extends RObjectPainter { /** @summary Create SVG element for the pad * @return true when pad is displayed and all its items should be redrawn */ createPadSvg(only_resize) { - if (!this.has_canvas) { + if (this.isTopPad()) { this.createCanvasSvg(only_resize ? 2 : 0); return true; } - const svg_parent = this.getPadSvg(this.pad_name), // this.pad_name MUST be here to select parent pad + const svg_parent = this.getPadPainter()?.getPadSvg(), svg_can = this.getCanvSvg(), width = svg_parent.property('draw_width'), height = svg_parent.property('draw_height'), pad_enlarged = svg_can.property('pad_enlarged'); let pad_visible = true, w = width, h = height, x = 0, y = 0, - svg_pad = null, svg_rect = null, btns = null; + svg_pad, svg_rect, btns = null; - if (this.pad?.fPos && this.pad?.fSize) { - x = Math.round(width * this.pad.fPos.fHoriz.fArr[0]); - y = Math.round(height * this.pad.fPos.fVert.fArr[0]); - w = Math.round(width * this.pad.fSize.fHoriz.fArr[0]); - h = Math.round(height * this.pad.fSize.fVert.fArr[0]); + if (this.#pad?.fPos && this.#pad?.fSize) { + x = Math.round(width * this.#pad.fPos.fHoriz.fArr[0]); + y = Math.round(height * this.#pad.fPos.fVert.fArr[0]); + w = Math.round(width * this.#pad.fSize.fHoriz.fArr[0]); + h = Math.round(height * this.#pad.fSize.fVert.fArr[0]); } if (pad_enlarged) { pad_visible = false; - if (pad_enlarged === this.pad) + if (pad_enlarged === this.#pad) pad_visible = true; - else - this.forEachPainterInPad(pp => { if (pp.getObject() === pad_enlarged) pad_visible = true; }, 'pads'); + else { + this.forEachPainterInPad(pp => { + if (pp.getObject() === pad_enlarged) + pad_visible = true; + }, 'pads'); + } - if (pad_visible) { w = width; h = height; x = y = 0; } + if (pad_visible) { + w = width; + h = height; + x = y = 0; + } } if (only_resize) { - svg_pad = this.svg_this_pad(); + svg_pad = this.getPadSvg(); svg_rect = svg_pad.selectChild('.root_pad_border'); if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + btns = this.getLayerSvg('btns_layer'); this.addPadInteractive(true); } else { svg_pad = svg_parent.selectChild('.primitives_layer') - .append('svg:svg') // here was g before, svg used to blend all drawin outside - .classed('__root_pad_' + this.this_pad_name, true) - .attr('pad', this.this_pad_name) // set extra attribute to mark pad name + .append('svg:svg') // here was g before, svg used to blend all drawings outside + .classed('__root_pad_' + this.#pad_name, true) + .attr('pad', this.#pad_name) // set extra attribute to mark pad name .property('pad_painter', this); // this is custom property if (!this.isBatchMode()) @@ -564,9 +753,9 @@ class RPadPainter extends RObjectPainter { } } - this.createAttFill({ attr: this.pad }); + this.createAttFill({ attr: this.#pad }); - this.createAttLine({ attr: this.pad, color0: this.pad.fBorderMode === 0 ? 'none' : '' }); + this.createAttLine({ attr: this.#pad, color0: this.#pad.fBorderMode === 0 ? 'none' : '' }); svg_pad.style('display', pad_visible ? null : 'none') .attr('viewBox', `0 0 ${w} ${h}`) // due to svg @@ -580,10 +769,10 @@ class RPadPainter extends RObjectPainter { .property('draw_width', w) .property('draw_height', h); - this._pad_x = x; - this._pad_y = y; - this._pad_width = w; - this._pad_height = h; + this.#pad_x = x; + this.#pad_y = y; + this.#pad_width = w; + this.#pad_height = h; svg_rect.attr('d', `M0,0H${w}V${h}H0Z`) .call(this.fillatt.func) @@ -593,18 +782,19 @@ class RPadPainter extends RObjectPainter { // special case of 3D canvas overlay if (svg_pad.property('can3d') === constants.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.this_pad_name) + this.selectDom().select('.draw3d_' + this.#pad_name) .style('display', pad_visible ? '' : 'none'); } - if (this.alignButtons && btns) this.alignButtons(btns, w, h); + if (this.alignButtons && btns) + this.alignButtons(btns, w, h); return pad_visible; } /** @summary Add pad interactive features like dragging and resize * @private */ - addPadInteractive(cleanup = false) { + addPadInteractive(/* cleanup = false */) { if (isFunc(this.$userInteractive)) { this.$userInteractive(); delete this.$userInteractive; @@ -615,7 +805,7 @@ class RPadPainter extends RObjectPainter { /** @summary returns true if any objects beside sub-pads exists in the pad */ hasObjectsToDraw() { - return this.pad?.fPrimitives?.find(obj => obj._typename !== `${nsREX}RPadDisplayItem`); + return this.#pad?.fPrimitives?.find(obj => obj._typename !== `${nsREX}RPadDisplayItem`); } /** @summary sync drawing/redrawing/resize of the pad @@ -624,14 +814,14 @@ class RPadPainter extends RObjectPainter { * @private */ syncDraw(kind) { const entry = { kind: kind || 'redraw' }; - if (this._doing_draw === undefined) { - this._doing_draw = [entry]; + if (this.#doing_draw === undefined) { + this.#doing_draw = [entry]; return Promise.resolve(true); } // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this._doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) + if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) return false; - this._doing_draw.push(entry); + this.#doing_draw.push(entry); return new Promise(resolveFunc => { entry.func = resolveFunc; }); @@ -640,14 +830,17 @@ class RPadPainter extends RObjectPainter { /** @summary confirms that drawing is completed, may trigger next drawing immediately * @private */ confirmDraw() { - if (this._doing_draw === undefined) + if (this.#doing_draw === undefined) return console.warn('failure, should not happen'); - this._doing_draw.shift(); - if (this._doing_draw.length === 0) - delete this._doing_draw; + this.#doing_draw.shift(); + if (!this.#doing_draw.length) + this.#doing_draw = undefined; else { - const entry = this._doing_draw[0]; - if (entry.func) { entry.func(); delete entry.func; } + const entry = this.#doing_draw[0]; + if (entry.func) { + entry.func(); + delete entry.func; + } } } @@ -661,53 +854,66 @@ class RPadPainter extends RObjectPainter { * @private */ async drawPrimitives(indx) { if (indx === undefined) { - if (this.iscan) - this._start_tm = new Date().getTime(); + if (this.isCanvas()) + this.#start_draw_tm = new Date().getTime(); - // set number of primitves - this._num_primitives = this.pad?.fPrimitives?.length ?? 0; + // set number of primitives + this.#num_primitives = this.#pad?.fPrimitives?.length ?? 0; return this.syncDraw(true).then(() => this.drawPrimitives(0)); } - if (!this.pad || (indx >= this._num_primitives)) { + if (!this.#pad || (indx >= this.#num_primitives)) { this.confirmDraw(); - if (this._start_tm) { - const spenttm = new Date().getTime() - this._start_tm; - if (spenttm > 3000) console.log(`Canvas drawing took ${(spenttm*1e-3).toFixed(2)}s`); - delete this._start_tm; + if (this.#start_draw_tm) { + const spenttm = new Date().getTime() - this.#start_draw_tm; + if (spenttm > 3000) + console.log(`Canvas drawing took ${(spenttm * 1e-3).toFixed(2)}s`); + this.#start_draw_tm = undefined; } return; } // handle used to invoke callback only when necessary - return this.drawObject(this.getDom(), this.pad.fPrimitives[indx], '').then(op => { + return this.drawObject(this, this.#pad.fPrimitives[indx], '').then(op => { // mark painter as belonging to primitives if (isObject(op)) op._primitive = true; - return this.drawPrimitives(indx+1); + return this.drawPrimitives(indx + 1); }); } + /** @summary Provide autocolor + * @private */ + getAutoColor() { + const pal = this.getHistPalette(), + cnt = this.#auto_color_cnt++, + num = Math.max(this.#num_primitives - 1, 2); + return pal?.getColorOrdinal((cnt % num) / num) ?? 'blue'; + } + /** @summary Process tooltip event in the pad * @private */ processPadTooltipEvent(pnt) { const painters = [], hints = []; // first count - how many processors are there - this.painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) painters.push(obj); + this.#painters?.forEach(obj => { + if (isFunc(obj.processTooltipEvent)) + painters.push(obj); }); - if (pnt) pnt.nproc = painters.length; + if (pnt) + pnt.nproc = painters.length; painters.forEach(obj => { const hint = obj.processTooltipEvent(pnt) || { user_info: null }; hints.push(hint); - if (pnt?.painters) hint.painter = obj; + if (pnt?.painters) + hint.painter = obj; }); return hints; @@ -722,23 +928,24 @@ class RPadPainter extends RObjectPainter { /** @summary Fill pad context menu * @private */ fillContextMenu(menu) { - if (this.iscan) - menu.add('header: RCanvas'); - else - menu.add('header: RPad'); + const clname = this.isCanvas() ? 'RCanvas' : 'RPad'; + + menu.header(clname, `${urlClassPrefix}ROOT_1_1Experimental_1_1${clname}.html`); menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - if (!this._websocket) { + if (!this.isCanvas(true)) { + // if not online canvas menu.addAttributesMenu(this); - if (this.iscan) { + if (this.isCanvas()) { menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') this.changeDarkMode(); + if (arg === 'dark') + this.changeDarkMode(); }); } } - menu.add('separator'); + menu.separator(); if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); @@ -748,13 +955,13 @@ class RPadPainter extends RObjectPainter { menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); } - if (this.enlargeMain() || (this.has_canvas && this.hasObjectsToDraw())) - menu.addchk((this.enlargeMain('state') === 'on'), 'Enlarge ' + (this.iscan ? 'canvas' : 'pad'), () => this.enlargePad()); + if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) + menu.addchk((this.enlargeMain('state') === 'on'), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - const fname = this.this_pad_name || (this.iscan ? 'canvas' : 'pad'); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.iscan, `${fname}.${fmt}`))); - menu.add('endsub:'); + const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); + menu.sub('Save as'); + ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`))); + menu.endsub(); return true; } @@ -777,6 +984,15 @@ class RPadPainter extends RObjectPainter { }).then(menu => menu.show()); } + /** @summary Redraw legend object + * @desc Used when object attributes are changed to ensure that legend is up to date + * @private */ + async redrawLegend() {} + + /** @summary Deliver mouse move or click event to the web canvas + * @private */ + deliverWebCanvasEvent() {} + /** @summary Redraw pad means redraw ourself * @return {Promise} when redrawing ready */ async redrawPad(reason) { @@ -788,10 +1004,10 @@ class RPadPainter extends RObjectPainter { let showsubitems = true; const redrawNext = indx => { - while (indx < this.painters.length) { - const sub = this.painters[indx++]; + while (indx < this.#painters.length) { + const sub = this.#painters[indx++]; let res = 0; - if (showsubitems || sub.this_pad_name) + if (showsubitems || isPadPainter(sub)) res = sub.redraw(reason); if (isPromise(res)) @@ -801,9 +1017,9 @@ class RPadPainter extends RObjectPainter { }; return sync_promise.then(() => { - if (this.iscan) + if (this.isCanvas()) this.createCanvasSvg(2); - else + else showsubitems = this.createPadSvg(true); return redrawNext(0); @@ -825,12 +1041,15 @@ class RPadPainter extends RObjectPainter { /** @summary Checks if pad should be redrawn by resize * @private */ needRedrawByResize() { - const elem = this.svg_this_pad(); - if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) return true; + const elem = this.getPadSvg(); + if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) + return true; - for (let i = 0; i < this.painters.length; ++i) { - if (isFunc(this.painters[i].needRedrawByResize)) - if (this.painters[i].needRedrawByResize()) return true; + for (let i = 0; i < this.#painters.length; ++i) { + if (isFunc(this.#painters[i].needRedrawByResize)) { + if (this.#painters[i].needRedrawByResize()) + return true; + } } return false; @@ -838,47 +1057,52 @@ class RPadPainter extends RObjectPainter { /** @summary Check resize of canvas */ checkCanvasResize(size, force) { - if (this._ignore_resize) + if (this._ignore_resize || !this.isTopPad()) return false; - if (!this.iscan && this.has_canvas) return false; - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) return false; + if (sync_promise === false) + return false; - if ((size === true) || (size === false)) { force = size; size = null; } + if ((size === true) || (size === false)) { + force = size; + size = null; + } - if (isObject(size) && size.force) force = true; + if (isObject(size) && size.force) + force = true; - if (!force) force = this.needRedrawByResize(); + if (!force) + force = this.needRedrawByResize(); let changed = false; const redrawNext = indx => { - if (!changed || (indx >= this.painters.length)) { + if (!changed || (indx >= this.#painters.length)) { this.confirmDraw(); return changed; } - return getPromise(this.painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx+1)); + return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); }; return sync_promise.then(() => { changed = this.createCanvasSvg(force ? 2 : 1, size); - if (changed && this.iscan && this.pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this._resize_tmout) - clearTimeout(this._resize_tmout); - this._resize_tmout = setTimeout(() => { - delete this._resize_tmout; - if (!this.pad?.fWinSize) return; + if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { + if (this.#resize_tmout) + clearTimeout(this.#resize_tmout); + this.#resize_tmout = setTimeout(() => { + this.#resize_tmout = undefined; + if (!this.#pad?.fWinSize) + return; const cw = this.getPadWidth(), ch = this.getPadHeight(); - if ((cw > 0) && (ch > 0) && ((this.pad.fWinSize[0] !== cw) || (this.pad.fWinSize[1] !== ch))) { - this.pad.fWinSize[0] = cw; - this.pad.fWinSize[1] = ch; + if ((cw > 0) && (ch > 0) && ((this.#pad.fWinSize[0] !== cw) || (this.#pad.fWinSize[1] !== ch))) { + this.#pad.fWinSize[0] = cw; + this.#pad.fWinSize[1] = ch; this.sendWebsocket(`RESIZED:[${cw},${ch}]`); } - }, 1000); // long enough delay to prevent multiple occurence + }, 1000); // long enough delay to prevent multiple occurrence } // if canvas changed, redraw all its subitems. @@ -890,42 +1114,43 @@ class RPadPainter extends RObjectPainter { /** @summary update RPad object * @private */ updateObject(obj) { - if (!obj) return false; + if (!obj) + return false; - this.pad.fStyle = obj.fStyle; - this.pad.fAttr = obj.fAttr; + this.#pad.fStyle = obj.fStyle; + this.#pad.fAttr = obj.fAttr; - if (this.iscan) { - this.pad.fTitle = obj.fTitle; - this.pad.fWinSize = obj.fWinSize; + if (this.isCanvas()) { + this.#pad.fTitle = obj.fTitle; + this.#pad.fWinSize = obj.fWinSize; } else { - this.pad.fPos = obj.fPos; - this.pad.fSize = obj.fSize; + this.#pad.fPos = obj.fPos; + this.#pad.fSize = obj.fSize; } return true; } - /** @summary Add object painter to list of primitives * @private */ addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && (objpainter.snapid === undefined)) { + if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { // keep snap id in painter, will be used for the - if (this.painters.indexOf(objpainter) < 0) - this.painters.push(objpainter); + if (this.#painters.indexOf(objpainter) < 0) + this.#painters.push(objpainter); objpainter.assignSnapId(lst[indx].fObjectID); - if (!objpainter.rstyle) objpainter.rstyle = lst[indx].fStyle || this.rstyle; + if (!objpainter.rstyle) + objpainter.rstyle = lst[indx].fStyle || this.rstyle; } } /** @summary Extract properties from TObjectDisplayItem */ extractTObjectProp(snap) { if (snap.fColIndex && snap.fColValue) { - const colors = this.root_colors || getRootColors(); + const colors = this.getColors() || getRootColors(); for (let k = 0; k < snap.fColIndex.length; ++k) - colors[snap.fColIndex[k]] = snap.fColValue[k]; - } + colors[snap.fColIndex[k]] = convertColor(snap.fColValue[k]); + } // painter used only for evaluation of attributes const pattr = new RObjectPainter(), obj = snap.fObject; @@ -937,7 +1162,8 @@ class RPadPainter extends RObjectPainter { const extract_color = (member_name, attr_name) => { const col = pattr.v7EvalColor(attr_name, ''); - if (col) obj[member_name] = addColor(col, this.root_colors); + if (col) + obj[member_name] = addColor(col, this.getColors()); }; // handle TAttLine @@ -973,13 +1199,12 @@ class RPadPainter extends RObjectPainter { /** @summary Function called when drawing next snapshot from the list * @return {Promise} with pad painter when ready * @private */ - async drawNextSnap(lst, indx) { + async drawNextSnap(lst, pindx, indx) { if (indx === undefined) { indx = -1; // flag used to prevent immediate pad redraw during first draw - this._snaps_map = {}; // to control how much snaps are drawn - this._num_primitives = lst ? lst.length : 0; - this._auto_color_cnt = 0; + this.#num_primitives = lst?.length ?? 0; + this.#auto_color_cnt = 0; } delete this.next_rstyle; @@ -987,145 +1212,131 @@ class RPadPainter extends RObjectPainter { ++indx; // change to the next snap if (!lst || indx >= lst.length) { - delete this._snaps_map; - delete this._auto_color_cnt; + this.#auto_color_cnt = undefined; return this; } - const snap = lst[indx], - snapid = snap.fObjectID; - let cnt = this._snaps_map[snapid], - objpainter = null; - - if (cnt) cnt++; else cnt=1; - this._snaps_map[snapid] = cnt; // check how many objects with same snapid drawn, use them again + const snap = lst[indx], is_subpad = snap._typename === `${nsREX}RPadDisplayItem`; // empty object, no need to do something, take next - if (snap.fDummy) return this.drawNextSnap(lst, indx); - - // first appropriate painter for the object - // if same object drawn twice, two painters will exists - for (let k = 0; k < this.painters.length; ++k) { - if (this.painters[k].snapid === snapid) - if (--cnt === 0) { objpainter = this.painters[k]; break; } - } - - if (objpainter) { - if (snap._typename === `${nsREX}RPadDisplayItem`) { - // subpad - return objpainter.redrawPadSnap(snap).then(ppainter => { - this.addObjectPainter(ppainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); - } - - if (snap._typename === `${nsREX}TObjectDisplayItem`) - this.extractTObjectProp(snap); - - let promise; - - if (objpainter.updateObject(snap.fDrawable || snap.fObject || snap, snap.fOption || '', true)) - promise = objpainter.redraw(); - - return getPromise(promise).then(() => this.drawNextSnap(lst, indx)); // call next - } - - if (snap._typename === `${nsREX}RPadDisplayItem`) { // subpad - const subpad = snap, // not subpad, but just attributes - - padpainter = new RPadPainter(this.getDom(), subpad, false); - padpainter.decodeOptions(''); - padpainter.addToPadPrimitives(this.this_pad_name); // only set parent pad name - padpainter.assignSnapId(snap.fObjectID); - padpainter.rstyle = snap.fStyle; - - padpainter.createPadSvg(); - - if (snap.fPrimitives && snap.fPrimitives.length > 0) - padpainter.addPadButtons(); - - // we select current pad, where all drawing is performed - const prev_name = padpainter.selectCurrentPad(padpainter.this_pad_name); - - return padpainter.drawNextSnap(snap.fPrimitives).then(() => { - padpainter.addPadInteractive(); - padpainter.selectCurrentPad(prev_name); - return this.drawNextSnap(lst, indx); - }); - } - - // will be used in addToPadPrimitives to assign style to sub-painters - this.next_rstyle = lst[indx].fStyle || this.rstyle; + if (snap.fDummy) + return this.drawNextSnap(lst, pindx + 1, indx); if (snap._typename === `${nsREX}TObjectDisplayItem`) { - // identifier used in RObjectDrawable - const webSnapIds = { kNone: 0, kObject: 1, kColors: 4, kStyle: 5, kPalette: 6 }; + // identifier used in TObjectDrawable if (snap.fKind === webSnapIds.kStyle) { Object.assign(gStyle, snap.fObject); - return this.drawNextSnap(lst, indx); + return this.drawNextSnap(lst, pindx, indx); } if (snap.fKind === webSnapIds.kColors) { - const ListOfColors = [], arr = snap.fObject.arr; + const colors = [], arr = snap.fObject.arr; for (let n = 0; n < arr.length; ++n) { const name = arr[n].fString, p = name.indexOf('='); if (p > 0) - ListOfColors[parseInt(name.slice(0, p))] = name.slice(p+1); + colors[parseInt(name.slice(0, p))] = convertColor(name.slice(p + 1)); } - this.root_colors = ListOfColors; + this.setColors(colors); // set global list of colors // adoptRootColors(ListOfColors); - return this.drawNextSnap(lst, indx); + return this.drawNextSnap(lst, pindx, indx); } if (snap.fKind === webSnapIds.kPalette) { const arr = snap.fObject.arr, palette = []; for (let n = 0; n < arr.length; ++n) palette[n] = arr[n].fString; - this.custom_palette = new ColorPalette(palette); - return this.drawNextSnap(lst, indx); + this.#custom_palette = new ColorPalette(palette); + return this.drawNextSnap(lst, pindx, indx); } + if (snap.fKind === webSnapIds.kFont) + return this.drawNextSnap(lst, pindx, indx); + if (!this.getFramePainter()) { - return this.drawObject(this.getDom(), { _typename: clTFrame, $dummy: true }, '') - .then(() => this.drawNextSnap(lst, indx-1)); - } // call same object again + // draw dummy frame which is not provided by RCanvas + return this.drawObject(this, { _typename: clTFrame, $dummy: true }, '') + .then(() => this.drawNextSnap(lst, pindx, indx - 1)); + } this.extractTObjectProp(snap); } - // TODO - fDrawable is v7, fObject from v6, maybe use same data member? - return this.drawObject(this.getDom(), snap.fDrawable || snap.fObject || snap, snap.fOption || '').then(objpainter => { - this.addObjectPainter(objpainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); + // try to locate existing object painter, only allowed when redrawing pad snap + let objpainter, promise; + + while ((pindx !== undefined) && (pindx < this.#painters.length)) { + const subp = this.#painters[pindx++]; + + if (subp.getSnapId() === snap.fObjectID) { + objpainter = subp; + break; + } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { + console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fDrawable?._typename}`); + break; + } + } + + if (objpainter) { + if (is_subpad) + promise = objpainter.redrawPadSnap(snap); + else if (objpainter.updateObject(snap.fDrawable || snap.fObject || snap, snap.fOption || '', true)) + promise = objpainter.redraw(); + } else if (is_subpad) { + const padpainter = new RPadPainter(this, snap, '', false, 'webpad'); + padpainter.assignSnapId(snap.fObjectID); + padpainter.rstyle = snap.fStyle; + + padpainter.createPadSvg(); + + if (snap.fPrimitives?.length) + padpainter.addPadButtons(); + + pindx++; // new painter will be add + promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); + } else { + // will be used in addToPadPrimitives to assign style to sub-painters + this.next_rstyle = snap.fStyle || this.rstyle; + pindx++; // new painter will be add + + // TODO - fDrawable is v7, fObject from v6, maybe use same data member? + promise = this.drawObject(this, snap.fDrawable || snap.fObject || snap, snap.fOption || '') + .then(objp => this.addObjectPainter(objp, lst, indx)); + } + + return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next } /** @summary Search painter with specified snapid, also sub-pads are checked * @private */ findSnap(snapid, onlyid) { function check(checkid) { - if (!checkid || !isStr(checkid)) return false; - if (checkid === snapid) return true; + if (!checkid || !isStr(checkid)) + return false; + if (checkid === snapid) + return true; return onlyid && (checkid.length > snapid.length) && (checkid.indexOf(snapid) === (checkid.length - snapid.length)); } - if (check(this.snapid)) return this; + if (check(this.getSnapId())) + return this; - if (!this.painters) return null; + if (!this.#painters) + return null; - for (let k=0; k<this.painters.length; ++k) { - let sub = this.painters[k]; + for (let k = 0; k < this.#painters.length; ++k) { + let sub = this.#painters[k]; if (!onlyid && isFunc(sub.findSnap)) sub = sub.findSnap(snapid); - else if (!check(sub.snapid)) + else if (!check(sub.getSnapId())) sub = null; - if (sub) return sub; + if (sub) + return sub; } return null; @@ -1140,29 +1351,23 @@ class RPadPainter extends RObjectPainter { if (!snap || !snap.fPrimitives) return this; - // for the moment only window size attributes are provided - // let padattr = { fCw: snap.fWinSize[0], fCh: snap.fWinSize[1], fTitle: snap.fTitle }; - - // if canvas size not specified in batch mode, temporary use 900x700 size - // if (this.isBatchMode() && this.iscan && (!padattr.fCw || !padattr.fCh)) { padattr.fCw = 900; padattr.fCh = 700; } - - if (this.iscan && this._websocket && snap.fTitle && !this.embed_canvas && (typeof document !== 'undefined')) + if (this.isCanvas(true) && snap.fTitle && !this.embed_canvas && (typeof document !== 'undefined')) document.title = snap.fTitle; - if (this.snapid === undefined) { + if (!this.hasSnapId()) { // first time getting snap, create all gui elements first this.assignSnapId(snap.fObjectID); - this.draw_object = snap; - this.pad = snap; + this.assignObject(snap); + this.#pad = snap; - if (this.isBatchMode() && this.iscan) - this._fixed_size = true; + if (this.isBatchMode() && this.isCanvas()) + this.#fixed_size = true; const mainid = this.selectDom().attr('id'); - if (!this.isBatchMode() && !this.use_openui && !this.brlayout && mainid && isStr(mainid)) { + if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { this.brlayout = new BrowserLayout(mainid, null, this); this.brlayout.create(mainid, true); this.setDom(this.brlayout.drawing_divid()); // need to create canvas @@ -1172,73 +1377,78 @@ class RPadPainter extends RObjectPainter { this.createCanvasSvg(0); this.addPadButtons(true); - return this.drawNextSnap(snap.fPrimitives); + return this.drawNextSnap(snap.fPrimitives).then(() => { + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); + return this; + }); } // update only pad/canvas attributes this.updateObject(snap); // apply all changes in the object (pad or canvas) - if (this.iscan) + if (this.isCanvas()) this.createCanvasSvg(2); - else + else this.createPadSvg(true); + let missmatch = false, i = 0, k = 0; - let isanyfound = false, isanyremove = false; + // match painters with new list of primitives + while (k < this.#painters.length) { + const sub = this.#painters[k]; - // find and remove painters which no longer exists in the list - for (let k = 0; k < this.painters.length; ++k) { - let sub = this.painters[k]; - if (sub.snapid === undefined) continue; // look only for painters with snapid + // skip check secondary painters or painters without snapid + // also frame painter will be excluded here + if (!sub.hasSnapId() || sub.isSecondary()) { + k++; + continue; // look only for painters with snapid + } - snap.fPrimitives.forEach(prim => { - if (sub && (prim.fObjectID === sub.snapid)) { - sub = null; isanyfound = true; - } - }); + if (i >= snap.fPrimitives.length) + break; - if (sub) { - // remove painter which does not found in the list of snaps - this.painters.splice(k--, 1); - sub.cleanup(); // cleanup such painter - isanyremove = true; - if (this.main_painter_ref === sub) - delete this.main_painter_ref; - } - } - if (isanyremove) - delete this.pads_cache; + const prim = snap.fPrimitives[i]; - if (!isanyfound) { - let fp = this.getFramePainter(); - // cannot preserve ROOT6 frame - it must be recreated - if (fp?.is_root6()) fp = null; - for (let k = 0; k < this.painters.length; ++k) { - if (fp !== this.painters[k]) - this.painters[k].cleanup(); - } - this.painters = []; - delete this.main_painter_ref; - if (fp) { - this.painters.push(fp); - fp.cleanFrameDrawings(); - fp.redraw(); // need to create all layers again + if (prim.fObjectID === sub.getSnapId()) { + i++; + k++; + } else if (prim.fDummy || !prim.fObjectID || ((prim._typename === `${nsREX}TObjectDisplayItem`) && ((prim.fKind === webSnapIds.kStyle) || (prim.fKind === webSnapIds.kColors) || (prim.fKind === webSnapIds.kPalette) || (prim.fKind === webSnapIds.kFont)))) { + // ignore primitives without snapid or which are not produce drawings + i++; + } else { + missmatch = true; + break; } + } + + let cnt = 1000; + // remove painters without primitives, limit number of checks + while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { + if (this.removePrimitive(k) === -111) + missmatch = true; + } + if (cnt < 0) + missmatch = true; + + if (missmatch) { + const old_painters = this.#painters; + this.#painters = []; + old_painters.forEach(objp => objp.cleanup()); + this.setMainPainter(undefined, true); if (isFunc(this.removePadButtons)) this.removePadButtons(); this.addPadButtons(true); } - const prev_name = this.selectCurrentPad(this.this_pad_name); - - return this.drawNextSnap(snap.fPrimitives).then(() => { + return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { this.addPadInteractive(); - this.selectCurrentPad(prev_name); - if (getActivePad() === this) this.getCanvPainter()?.producePadEvent('padredraw', this); + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); return this; }); } @@ -1248,79 +1458,89 @@ class RPadPainter extends RObjectPainter { * @return {Promise} with image data, coded with btoa() function * @private */ async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'pdf')) { + if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg')) return res; + if (!res || (format === 'svg')) + return res; const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ+7) : ''; + return (separ > 0) ? res.slice(separ + 7) : ''; }); } - return ''; } /** @summary Show context menu for specified item * @private */ itemContextMenu(name) { - const rrr = this.svg_this_pad().node().getBoundingClientRect(), - evnt = { clientX: rrr.left+10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getMainPainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) selp = this.painters[indx]; - } - } - - if (!isFunc(selp?.fillContextMenu)) return; - - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.snapid) - selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); + const rrr = this.getPadSvg().node().getBoundingClientRect(), + evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; + + // use timeout to avoid conflict with mouse click and automatic menu close + if (name === 'pad') + return postponePromise(() => this.padContextMenu(evnt), 50); + + let selp = null, selkind; + + switch (name) { + case 'xaxis': + case 'yaxis': + case 'zaxis': + selp = this.getMainPainter(); + selkind = name[0]; + break; + case 'frame': + selp = this.getFramePainter(); + break; + default: { + const indx = parseInt(name); + if (Number.isInteger(indx)) + selp = this.#painters[indx]; + } + } + + if (!isFunc(selp?.fillContextMenu)) + return; + + return createMenu(evnt, selp).then(menu => { + const offline_menu = selp.fillContextMenu(menu, selkind); + if (offline_menu || selp.getSnapId()) + selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); + }); } /** @summary Save pad in specified format * @desc Used from context menu */ saveAs(kind, full_canvas, filename) { if (!filename) - filename = (this.this_pad_name || (this.iscan ? 'canvas' : 'pad')) + '.' + kind; + filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; this.produceImage(full_canvas, kind).then(imgdata => { if (!imgdata) return console.error(`Fail to produce image ${filename}`); - saveFile(filename, (kind !== 'svg') ? imgdata : 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(imgdata)); + if ((browser.qt6 || browser.cef3) && this.getSnapId()) { + console.warn(`sending file ${filename} to server`); + let res = imgdata; + if (kind !== 'svg') { + const separ = res.indexOf('base64,'); + res = (separ > 0) ? res.slice(separ + 7) : ''; + } + if (res) + this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); + } else + saveFile(filename, (kind !== 'svg') ? imgdata : prSVG + encodeURIComponent(imgdata)); }); } /** @summary Search active pad * @return {Object} pad painter for active pad */ - findActivePad() { - return null; - } + findActivePad() { return null; } - /** @summary Prodce image for the pad + /** @summary Produce image for the pad * @return {Promise} with created image */ - async produceImage(full_canvas, file_format) { + async produceImage(full_canvas, file_format, args) { const use_frame = (full_canvas === 'frame'), - elem = use_frame ? this.getFrameSvg(this.this_pad_name) : (full_canvas ? this.getCanvSvg() : this.svg_this_pad()), + elem = use_frame ? this.getFrameSvg() : (full_canvas ? this.getCanvSvg() : this.getPadSvg()), painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, items = []; // keep list of replaced elements, which should be moved back at the end @@ -1338,11 +1558,11 @@ class RPadPainter extends RObjectPainter { if (!use_frame) { // do not make transformations for the frame painter.forEachPainterInPad(pp => { - const item = { prnt: pp.svg_this_pad() }; + const item = { prnt: pp.getPadSvg() }; items.push(item); - // remove buttons from each subpad - const btns = pp.getLayerSvg('btns_layer', this.this_pad_name); + // remove buttons from each sub-pad + const btns = pp.getLayerSvg('btns_layer'); item.btns_node = btns.node(); if (item.btns_node) { item.btns_prnt = item.btns_node.parentNode; @@ -1350,18 +1570,21 @@ class RPadPainter extends RObjectPainter { btns.remove(); } - const main = pp.getFramePainter(); - if (!isFunc(main?.render3D) || !isFunc(main?.access3dKind)) return; - - const can3d = main.access3dKind(); + const fp = pp.getFramePainter(); + if (!isFunc(fp?.access3dKind)) + return; - if ((can3d !== constants.Embed3D.Overlay) && (can3d !== constants.Embed3D.Embed)) return; + const can3d = fp.access3dKind(); + if ((can3d !== constants.Embed3D.Overlay) && (can3d !== constants.Embed3D.Embed)) + return; - const sz2 = main.getSizeFor3d(constants.Embed3D.Embed), // get size and position of DOM element as it will be embed - canvas = main.renderer.domElement; + const main = isFunc(fp.getRenderer) ? fp : fp.getMainPainter(), + canvas = isFunc(main.getRenderer) ? main.getRenderer()?.domElement : null; + if (!isFunc(main?.render3D) || !isObject(canvas)) + return; + const sz2 = fp.getSizeFor3d(constants.Embed3D.Embed); // get size and position of DOM element as it will be embed main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately - const dataUrl = canvas.toDataURL('image/png'); // remove 3D drawings @@ -1370,7 +1593,7 @@ class RPadPainter extends RObjectPainter { item.foreign.remove(); } - const svg_frame = main.getFrameSvg(); + const svg_frame = fp.getFrameSvg(); item.frame_node = svg_frame.node(); if (item.frame_node) { item.frame_next = item.frame_node.nextSibling; @@ -1396,9 +1619,9 @@ class RPadPainter extends RObjectPainter { const arg = (file_format === 'pdf') ? { node: elem.node(), width, height, reset_tranform: use_frame } - : compressSVG(`<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">${elem.node().innerHTML}</svg>`); + : compressSVG(`<svg width="${width}" height="${height}" xmlns="${nsSVG}">${elem.node().innerHTML}</svg>`); - return svgToImage(arg, file_format).then(res => { + return svgToImage(arg, file_format, args).then(res => { for (let k = 0; k < items.length; ++k) { const item = items[k]; @@ -1436,15 +1659,13 @@ class RPadPainter extends RObjectPainter { if (funcname === 'PadContextMenus') { evnt?.preventDefault(); evnt?.stopPropagation(); - if (closeMenu()) return; + if (closeMenu()) + return; return createMenu(evnt, this).then(menu => { - menu.add('header:Menus'); + menu.header('Menus'); - if (this.iscan) - menu.add('Canvas', 'pad', this.itemContextMenu); - else - menu.add('Pad', 'pad', this.itemContextMenu); + menu.add(this.isCanvas() ? 'Canvas' : 'Pad', 'pad', this.itemContextMenu); if (this.getFramePainter()) menu.add('Frame', 'frame', this.itemContextMenu); @@ -1458,14 +1679,16 @@ class RPadPainter extends RObjectPainter { menu.add('Z axis', 'zaxis', this.itemContextMenu); } - if (this.painters?.length) { - menu.add('separator'); + if (this.#painters?.length) { + menu.separator(); const shown = []; - this.painters.forEach((pp, indx) => { + this.#painters.forEach((pp, indx) => { const obj = pp?.getObject(); - if (!obj || (shown.indexOf(obj) >= 0) || pp.isSecondary()) return; + if (!obj || (shown.indexOf(obj) >= 0) || pp.isSecondary()) + return; let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); - if (name) name += '::'; + if (name) + name += '::'; name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); menu.add(name, indx, this.itemContextMenu); shown.push(obj); @@ -1481,15 +1704,16 @@ class RPadPainter extends RObjectPainter { let done = false; const prs = []; - for (let i = 0; i < this.painters.length; ++i) { - const pp = this.painters[i]; + for (let i = 0; i < this.#painters.length; ++i) { + const pp = this.#painters[i]; if (isFunc(pp.clickPadButton)) prs.push(pp.clickPadButton(funcname, evnt)); if (!done && isFunc(pp.clickButton)) { done = pp.clickButton(funcname); - if (isPromise(done)) prs.push(done); + if (isPromise(done)) + prs.push(done); } } @@ -1499,32 +1723,36 @@ class RPadPainter extends RObjectPainter { /** @summary Add button to the pad * @private */ addPadButton(btn, tooltip, funcname, keyname) { - if (!settings.ToolBar || this.isBatchMode()) return; + if (!settings.ToolBar || this.isBatchMode()) + return; - if (!this._buttons) this._buttons = []; + if (!this._buttons) + this._buttons = []; // check if there are duplications - for (let k = 0; k < this._buttons.length; ++k) - if (this._buttons[k].funcname === funcname) return; + for (let k = 0; k < this._buttons.length; ++k) { + if (this._buttons[k].funcname === funcname) + return; + } this._buttons.push({ btn, tooltip, funcname, keyname }); - const iscan = this.iscan || !this.has_canvas; - if (!iscan && (funcname.indexOf('Pad') !== 0) && (funcname !== 'enlargePad')) { + if (!this.isTopPad() && funcname.indexOf('Pad') && (funcname !== 'enlargePad')) { const cp = this.getCanvPainter(); - if (cp && (cp !== this)) cp.addPadButton(btn, tooltip, funcname); + if (cp && (cp !== this)) + cp.addPadButton(btn, tooltip, funcname); } } /** @summary Add buttons for pad or canvas * @private */ addPadButtons(is_online) { - this.addPadButton('camera', 'Create PNG', this.iscan ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); + this.addPadButton('camera', 'Create PNG', this.isCanvas() ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); if (settings.ContextMenu) this.addPadButton('question', 'Access context menus', 'PadContextMenus'); - const add_enlarge = !this.iscan && this.has_canvas && this.hasObjectsToDraw(); + const add_enlarge = !this.isTopPad() && this.hasObjectsToDraw(); if (add_enlarge || this.enlargeMain('verify')) this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); @@ -1538,7 +1766,8 @@ class RPadPainter extends RObjectPainter { /** @summary Show pad buttons * @private */ showPadButtons() { - if (!this._buttons) return; + if (!this._buttons) + return; PadButtonsHandler.assign(this); this.showPadButtons(); @@ -1548,7 +1777,7 @@ class RPadPainter extends RObjectPainter { getPadLength(vertical, len, frame_painter) { let rect, res; const sign = vertical ? -1 : 1, - getV = (indx, dflt) => (indx < len.fArr.length) ? len.fArr[indx] : dflt, + getV = (indx, dflt) => { return (indx < len.fArr.length) ? len.fArr[indx] : dflt; }, getRect = () => { if (!rect) rect = frame_painter ? frame_painter.getFrameRect() : this.getPadRect(); @@ -1566,7 +1795,7 @@ class RPadPainter extends RObjectPainter { const norm = getV(0, 0), pixel = getV(1, 0); - res += sign*pixel; + res += sign * pixel; if (norm) res += sign * (vertical ? getRect().height : getRect().width) * norm; @@ -1588,62 +1817,70 @@ class RPadPainter extends RObjectPainter { /** @summary Decode pad draw options */ decodeOptions(opt) { const pad = this.getObject(); - if (!pad) return; - - const d = new DrawOptions(opt); - - if (!this.options) this.options = {}; - - Object.assign(this.options, { GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false }); - - if (d.check('NOCOLORS') || d.check('NOCOL')) this.options.GlobalColors = this.options.LocalColors = false; - if (d.check('LCOLORS') || d.check('LCOL')) { this.options.GlobalColors = false; this.options.LocalColors = true; } - if (d.check('NOPALETTE') || d.check('NOPAL')) this.options.IgnorePalette = true; - if (d.check('ROTATE')) this.options.RotateFrame = true; - if (d.check('FIXFRAME')) this.options.FixFrame = true; - - if (d.check('WHITE')) pad.fFillColor = 0; - if (d.check('LOGX')) pad.fLogx = 1; - if (d.check('LOGY')) pad.fLogy = 1; - if (d.check('LOGZ')) pad.fLogz = 1; - if (d.check('LOG')) pad.fLogx = pad.fLogy = pad.fLogz = 1; - if (d.check('GRIDX')) pad.fGridx = 1; - if (d.check('GRIDY')) pad.fGridy = 1; - if (d.check('GRID')) pad.fGridx = pad.fGridy = 1; - if (d.check('TICKX')) pad.fTickx = 1; - if (d.check('TICKY')) pad.fTicky = 1; - if (d.check('TICK')) pad.fTickx = pad.fTicky = 1; + if (!pad) + return; + + const d = new DrawOptions(opt), + o = this.setOptions({ GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false }); + + if (d.check('NOCOLORS') || d.check('NOCOL')) + o.GlobalColors = o.LocalColors = false; + if (d.check('LCOLORS') || d.check('LCOL')) { + o.GlobalColors = false; + o.LocalColors = true; + } + if (d.check('NOPALETTE') || d.check('NOPAL')) + o.IgnorePalette = true; + if (d.check('ROTATE')) + o.RotateFrame = true; + if (d.check('FIXFRAME')) + o.FixFrame = true; + + if (d.check('WHITE')) + pad.fFillColor = 0; + if (d.check('LOGX')) + pad.fLogx = 1; + if (d.check('LOGY')) + pad.fLogy = 1; + if (d.check('LOGZ')) + pad.fLogz = 1; + if (d.check('LOG')) + pad.fLogx = pad.fLogy = pad.fLogz = 1; + if (d.check('GRIDX')) + pad.fGridx = 1; + if (d.check('GRIDY')) + pad.fGridy = 1; + if (d.check('GRID')) + pad.fGridx = pad.fGridy = 1; + if (d.check('TICKX2')) + pad.fTickx = 2; + if (d.check('TICKY2')) + pad.fTicky = 2; + if (d.check('TICK2')) + pad.fTickx = pad.fTicky = 2; + if (d.check('TICKX')) + pad.fTickx = 1; + if (d.check('TICKY')) + pad.fTicky = 1; + if (d.check('TICK')) + pad.fTickx = pad.fTicky = 1; } /** @summary draw RPad object */ static async draw(dom, pad, opt) { - const painter = new RPadPainter(dom, pad, false); - painter.decodeOptions(opt); - - if (painter.getCanvSvg().empty()) { - painter.has_canvas = false; - painter.this_pad_name = ''; - painter.setTopPainter(); - } else - painter.addToPadPrimitives(painter.pad_name); // must be here due to pad painter - + const painter = new RPadPainter(dom, pad, opt, false, true); painter.createPadSvg(); - if (painter.matchObjectType(clTPad) && (!painter.has_canvas || painter.hasObjectsToDraw())) + if (painter.matchObjectType(nsREX + 'RPad') && (painter.isTopPad() || painter.hasObjectsToDraw())) painter.addPadButtons(); - // we select current pad, where all drawing is performed - const prev_name = painter.has_canvas ? painter.selectCurrentPad(painter.this_pad_name) : undefined; - selectActivePad({ pp: painter, active: false }); // flag used to prevent immediate pad redraw during first draw return painter.drawPrimitives().then(() => { painter.addPadInteractive(); painter.showPadButtons(); - // we restore previous pad name - painter.selectCurrentPad(prev_name); return painter; }); } diff --git a/modules/gpad/TAxisPainter.mjs b/modules/gpad/TAxisPainter.mjs index 97575dfc7..c149bb947 100644 --- a/modules/gpad/TAxisPainter.mjs +++ b/modules/gpad/TAxisPainter.mjs @@ -1,4 +1,4 @@ -import { gStyle, settings, constants, clTAxis, clTGaxis, isFunc } from '../core.mjs'; +import { gStyle, settings, constants, clTAxis, clTGaxis, isFunc, isStr } from '../core.mjs'; import { select as d3_select, drag as d3_drag, timeFormat as d3_timeFormat, utcFormat as d3_utcFormat, scaleTime as d3_scaleTime, scaleSymlog as d3_scaleSymlog, scaleLog as d3_scaleLog, scaleLinear as d3_scaleLinear } from '../d3.mjs'; @@ -12,9 +12,11 @@ import { FontHandler } from '../base/FontHandler.mjs'; function getTimeOffset(axis) { const dflt_time_offset = 788918400000; - if (!axis) return dflt_time_offset; + if (!axis) + return dflt_time_offset; const idF = axis.fTimeFormat.indexOf('%F'); - if (idF < 0) return gStyle.fTimeOffset * 1000; + if (idF < 0) + return gStyle.fTimeOffset * 1000; let sof = axis.fTimeFormat.slice(idF + 2); // default string in axis offset if (sof.indexOf('1995-01-01 00:00:00s0') === 0) @@ -23,23 +25,28 @@ function getTimeOffset(axis) { if (sof.indexOf('1970-01-01 00:00:00s0') === 0) return 0; // special case, used from DABC painters - if ((sof === '0') || (sof === '')) return 0; + if ((sof === '0') || (sof === '')) + return 0; // decode time from ROOT string const next = (separ, min, max) => { const pos = sof.indexOf(separ); - if (pos < 0) return min; + if (pos < 0) + return min; const val = parseInt(sof.slice(0, pos)); sof = sof.slice(pos + 1); - if (!Number.isInteger(val) || (val < min) || (val > max)) return min; + if (!Number.isInteger(val) || (val < min) || (val > max)) + return min; return val; - }, year = next('-', 1900, 2900), - month = next('-', 1, 12) - 1, - day = next(' ', 1, 31), - hour = next(':', 0, 23), - min = next(':', 0, 59), - sec = next('s', 0, 59), - msec = next(' ', 0, 999); + }; + // eslint-disable-next-line one-var + const year = next('-', 1900, 2900), + month = next('-', 1, 12) - 1, + day = next(' ', 1, 31), + hour = next(':', 0, 23), + min = next(':', 0, 59), + sec = next('s', 0, 59), + msec = next(' ', 0, 999); let offset = Date.UTC(year, month, day, hour, min, sec, msec); @@ -50,7 +57,10 @@ function getTimeOffset(axis) { sof = sof.slice(4).trim(); if (sof.length > 3) { let p = 0, sign = 1000; - if (sof[0] === '-') { p = 1; sign = -1000; } + if (sof[0] === '-') { + p = 1; + sign = -1000; + } offset -= sign * (parseInt(sof.slice(p, p + 2)) * 3600 + parseInt(sof.slice(p + 2, p + 4)) * 60); } } @@ -68,13 +78,25 @@ function getTimeGMT(axis) { /** @summary Tries to choose time format for provided time interval * @private */ function chooseTimeFormat(awidth, ticks) { - if (awidth < 0.5) return ticks ? '%S.%L' : '%H:%M:%S.%L'; - if (awidth < 30) return ticks ? '%Mm%S' : '%H:%M:%S'; - awidth /= 60; if (awidth < 30) return ticks ? '%Hh%M' : '%d/%m %H:%M'; - awidth /= 60; if (awidth < 12) return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; - awidth /= 24; if (awidth < 15.218425) return ticks ? '%d/%m' : '%d/%m/%y'; - awidth /= 30.43685; if (awidth < 6) return '%d/%m/%y'; - awidth /= 12; if (awidth < 2) return ticks ? '%m/%y' : '%d/%m/%y'; + if (awidth < 0.5) + return ticks ? '%S.%L' : '%H:%M:%S.%L'; + if (awidth < 30) + return ticks ? '%Mm%S' : '%H:%M:%S'; + awidth /= 60; + if (awidth < 30) + return ticks ? '%Hh%M' : '%d/%m %H:%M'; + awidth /= 60; + if (awidth < 12) + return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; + awidth /= 24; + if (awidth < 15.218425) + return ticks ? '%d/%m' : '%d/%m/%y'; + awidth /= 30.43685; + if (awidth < 6) + return '%d/%m/%y'; + awidth /= 12; + if (awidth < 2) + return ticks ? '%m/%y' : '%d/%m/%y'; return '%Y'; } @@ -119,14 +141,14 @@ const AxisPainterMethods = { /** @summary Convert axis value into the Date object */ convertDate(v) { - const dt = new Date(this.timeoffset + v*1000); + const dt = new Date(this.timeoffset + v * 1000); let res = dt; if (!this.timegmt && settings.TimeZone) { try { const ms = dt.getMilliseconds(); res = new Date(dt.toLocaleString('en-US', { timeZone: settings.TimeZone })); res.setMilliseconds(ms); - } catch (err) { + } catch { res = dt; } } @@ -149,10 +171,12 @@ const AxisPainterMethods = { const val = parseFloat(d), rnd = Math.round(val); if (!asticks) return ((rnd === val) && (Math.abs(rnd) < 1e9)) ? rnd.toString() : floatToString(val, fmt || gStyle.fStatFormat); - if (val <= 0) return null; + if (val <= 0) + return null; let vlog = Math.log10(val); const base = this.logbase; - if (base !== 10) vlog = vlog / Math.log10(base); + if (base !== 10) + vlog /= Math.log10(base); if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog)) < 0.001)) { if (!this.noexp && (asticks !== 2)) return this.formatExp(base, Math.floor(vlog + 0.01), val); @@ -163,11 +187,22 @@ const AxisPainterMethods = { return null; }, + /** @summary Detect if tick is extra-ticks */ + isExtraLogTick(val) { + if (!this.log) + return false; + let vlog = Math.log10(val); + if (this.logbase !== 10) + vlog /= Math.log10(this.logbase); + + return Math.abs(vlog - Math.round(vlog)) >= 0.001; + }, + /** @summary Provide label for normal axis */ formatNormal(d, asticks, fmt) { let val = parseFloat(d); if (asticks && this.order) - val = val / Math.pow(10, this.order); + val /= Math.pow(10, this.order); if (gStyle.fStripDecimals && (val === Math.round(val))) return Math.abs(val) < 1e9 ? val.toFixed(0) : val.toExponential(4); @@ -178,37 +213,36 @@ const AxisPainterMethods = { let res = val.toFixed(this.ndig); const p = res.indexOf('.'); if ((p > 0) && settings.StripAxisLabels) { - while ((res.length >= p) && ((res[res.length-1] === '0') || (res[res.length-1] === '.'))) + while ((res.length >= p) && ((res.at(-1) === '0') || (res.at(-1) === '.'))) res = res.slice(0, res.length - 1); } return res; } - return floatToString(val, fmt || gStyle.fStatFormat); + return floatToString(val, fmt || '8.6g'); }, /** @summary Provide label for exponential form */ formatExp(base, order, value) { let res = ''; + const sbase = Math.abs(base - Math.E) < 0.001 ? 'e' : base.toString(); if (value) { - value = Math.round(value/Math.pow(base, order)); - if ((value !== 0) && (value !== 1)) res = value.toString() + (settings.Latex ? '#times' : 'x'); - } - if (Math.abs(base - Math.E) < 0.001) - res += 'e'; - else - res += base.toString(); - if (settings.StripAxisLabels) { - if (order === 0) - return '1'; - else if (order === 1) - return res; + value = Math.round(value / Math.pow(base, order)); + if (settings.StripAxisLabels) { + if (order === 0) + return value.toString(); + else if ((order === 1) && (value === 1)) + return sbase; + } + if (value !== 1) + res = value.toString() + (settings.Latex ? '#times' : 'x'); } + res += sbase; if (settings.Latex > constants.Latex.Symbols) return res + `^{${order}}`; const superscript_symbols = { - 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', - 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' + 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', + 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' }, str = order.toString(); for (let n = 0; n < str.length; ++n) res += superscript_symbols[str[n]]; @@ -228,24 +262,28 @@ const AxisPainterMethods = { * @desc Fixing following problem, described [here]{@link https://stackoverflow.com/questions/64649793} */ poduceLogTicks(func, number) { const linearArray = arr => { + if (arr.length < 2) + return false; let sum1 = 0, sum2 = 0; for (let k = 1; k < arr.length; ++k) { - const diff = (arr[k] - arr[k-1]); + const diff = (arr[k] - arr[k - 1]); sum1 += diff; - sum2 += diff**2; + sum2 += diff ** 2; } - const mean = sum1/(arr.length-1), - dev = sum2/(arr.length-1) - mean**2; - - if (dev <= 0) return true; - if (Math.abs(mean) < 1e-100) return false; - return Math.sqrt(dev)/mean < 1e-6; + const mean = sum1 / (arr.length - 1), + dev = sum2 / (arr.length - 1) - mean ** 2; + + if (dev <= 0) + return true; + if (Math.abs(mean) < 1e-100) + return false; + return Math.sqrt(dev) / mean < 1e-6; }; let arr = func.ticks(number); while ((number > 4) && linearArray(arr)) { - number = Math.round(number*0.8); + number = Math.round(number * 0.8); arr = func.ticks(number); } @@ -254,9 +292,11 @@ const AxisPainterMethods = { const arr2 = []; arr.forEach(val => { const pow = Math.log10(val) / Math.log10(this.logbase); - if (Math.abs(Math.round(pow) - pow) < 0.01) arr2.push(val); + if (Math.abs(Math.round(pow) - pow) < 0.01) + arr2.push(val); }); - if (arr2.length > 0) arr = arr2; + if (arr2.length) + arr = arr2; } return arr; @@ -267,42 +307,55 @@ const AxisPainterMethods = { if (!this.noticksopt) { const total = ndiv * (ndiv2 || 1); - if (this.log) return this.poduceLogTicks(this.func, total); + if (this.log) + return this.poduceLogTicks(this.func, total); const dom = this.func.domain(), - check = ticks => { - if (ticks.length <= total) return true; - if (ticks.length > total + 1) return false; - return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range - }, res1 = this.func.ticks(total); - if (ndiv2 || check(res1)) return res1; + check = ticks => { + if (ticks.length <= total) + return true; + if (ticks.length > total + 1) + return false; + return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range + }, res1 = this.func.ticks(total); + if (ndiv2 || check(res1)) + return res1; const res2 = this.func.ticks(Math.round(total * 0.7)); return (res2.length > 2) && check(res2) ? res2 : res1; } const dom = this.func.domain(), ticks = []; - if (ndiv2) ndiv = (ndiv-1) * ndiv2; + if (ndiv2) + ndiv = (ndiv - 1) * ndiv2; for (let n = 0; n <= ndiv; ++n) - ticks.push((dom[0]*(ndiv-n) + dom[1]*n)/ndiv); + ticks.push((dom[0] * (ndiv - n) + dom[1] * n) / ndiv); return ticks; }, /** @summary Method analyze mouse wheel event and returns item with suggested zooming range */ analyzeWheelEvent(evnt, dmin, item, test_ignore) { - if (!item) item = {}; + if (!item) + item = {}; let delta = 0, delta_left = 1, delta_right = 1; - if ('dleft' in item) { delta_left = item.dleft; delta = 1; } - if ('dright' in item) { delta_right = item.dright; delta = 1; } + if ('dleft' in item) { + delta_left = item.dleft; + delta = 1; + } + if ('dright' in item) { + delta_right = item.dright; + delta = 1; + } if (item.delta) delta = item.delta; - else if (evnt) + else if (evnt) delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - if (!delta || (test_ignore && item.ignore)) return; + if (!delta || (test_ignore && item.ignore)) + return; delta = (delta < 0) ? -0.2 : 0.2; delta_left *= delta; @@ -318,57 +371,67 @@ const AxisPainterMethods = { item.max = gmax; } - if (item.min >= item.max) return; + if (item.min >= item.max) + return; - if (item.reverse) dmin = 1 - dmin; + if (item.reverse) + dmin = 1 - dmin; if ((dmin > 0) && (dmin < 1)) { if (this.log) { - let factor = (item.min > 0) ? Math.log10(item.max/item.min) : 2; - if (factor > 10) factor = 10; else if (factor < 0.01) factor = 0.01; - item.min = item.min / Math.pow(10, factor*delta_left*dmin); - item.max = item.max * Math.pow(10, factor*delta_right*(1-dmin)); + let factor = (item.min > 0) ? Math.log10(item.max / item.min) : 2; + if (factor > 10) + factor = 10; + else if (factor < 0.01) + factor = 0.01; + item.min /= Math.pow(10, factor * delta_left * dmin); + item.max *= Math.pow(10, factor * delta_right * (1 - dmin)); + // special handling for Z scale - limit zooming of color scale + if (this.minposbin && this.name === 'zaxis') + item.min = Math.max(item.min, 0.3 * this.minposbin); } else if ((delta_left === -delta_right) && !item.reverse) { // shift left/right, try to keep range constant - let delta = (item.max - item.min) * delta_right * dmin; + let delta_shift = (item.max - item.min) * delta_right * dmin; - if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta) > 1)) delta = Math.round(delta); + if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta_shift) > 1)) + delta_shift = Math.round(delta_shift); - if (item.min + delta < gmin) - delta = gmin - item.min; - else if (item.max + delta > gmax) - delta = gmax - item.max; + if (item.min + delta_shift < gmin) + delta_shift = gmin - item.min; + else if (item.max + delta_shift > gmax) + delta_shift = gmax - item.max; - if (delta !== 0) { - item.min += delta; - item.max += delta; - } else { + if (delta_shift) { + item.min += delta_shift; + item.max += delta_shift; + } else { delete item.min; delete item.max; } } else { let rx_left = (item.max - item.min), rx_right = rx_left; - if (delta_left > 0) rx_left = 1.001 * rx_left / (1-delta_left); - item.min += -delta_left*dmin*rx_left; - if (delta_right > 0) rx_right = 1.001 * rx_right / (1-delta_right); - item.max -= -delta_right*(1-dmin)*rx_right; + if (delta_left > 0) + rx_left = 1.001 * rx_left / (1 - delta_left); + item.min += -delta_left * dmin * rx_left; + if (delta_right > 0) + rx_right = 1.001 * rx_right / (1 - delta_right); + item.max -= -delta_right * (1 - dmin) * rx_right; } if (item.min >= item.max) item.min = item.max = undefined; - else if (delta_left !== delta_right) { + else if (delta_left !== delta_right) { // extra check case when moving left or right if (((item.min < gmin) && (lmin === gmin)) || ((item.max > gmax) && (lmax === gmax))) - item.min = item.max = undefined; + item.min = item.max = undefined; } else { - if (item.min < gmin) item.min = gmin; - if (item.max > gmax) item.max = gmax; + item.min = Math.max(item.min, gmin); + item.max = Math.min(item.max, gmax); } } else item.min = item.max = undefined; - - item.changed = ((item.min !== undefined) && (item.max !== undefined)); + item.changed = (item.min !== undefined) && (item.max !== undefined); return item; } @@ -396,7 +459,7 @@ class TAxisPainter extends ObjectPainter { Object.assign(this, AxisPainterMethods); this.initAxisPainter(); - this.embedded = embedded; // indicate that painter embedded into the histo painter + this.embedded = embedded; // indicate that painter embedded into the histogram painter this.invert_side = false; this.lbls_both_sides = false; // draw labels on both sides } @@ -404,6 +467,9 @@ class TAxisPainter extends ObjectPainter { /** @summary cleanup painter */ cleanup() { this.cleanupAxisPainter(); + delete this.hist_painter; + delete this.hist_axis; + delete this.is_gaxis; super.cleanup(); } @@ -412,23 +478,29 @@ class TAxisPainter extends ObjectPainter { /** @summary Configure axis painter * @desc Axis can be drawn inside frame <g> group with offset to 0 point for the frame - * Therefore one should distinguish when caclulated coordinates used for axis drawing itself or for calculation of frame coordinates + * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates * @private */ configureAxis(name, min, max, smin, smax, vertical, range, opts) { + const axis = this.getObject(); + this.name = name; this.full_min = min; this.full_max = max; this.kind = kAxisNormal; this.vertical = vertical; this.log = opts.log || 0; + this.minposbin = opts.minposbin; + this.ignore_labels = opts.ignore_labels; this.noexp_changed = opts.noexp_changed; this.symlog = opts.symlog || false; this.reverse = opts.reverse || false; + // special flag to change align of labels on vertical axis + // it is workaround shown in TGaxis docu + this.reverseAlign = this.vertical && this.reverse && this.is_gaxis && (axis.fX1 !== axis.fX2); this.swap_side = opts.swap_side || false; this.fixed_ticks = opts.fixed_ticks || null; this.maxTickSize = opts.maxTickSize || 0; - - const axis = this.getObject(); + this.value_axis = opts.value_axis ?? false; // use fMinimum/fMaximum from source object if (opts.time_scale || axis.fTimeDisplay) { this.kind = kAxisTime; @@ -437,8 +509,7 @@ class TAxisPainter extends ObjectPainter { } else if (opts.axis_func) this.kind = kAxisFunc; else - this.kind = !axis.fLabels ? kAxisNormal : kAxisLabels; - + this.kind = !axis.fLabels || this.ignore_labels ? kAxisNormal : kAxisLabels; if (this.kind === kAxisTime) this.func = d3_scaleTime().domain([this.convertDate(smin), this.convertDate(smax)]); @@ -450,17 +521,27 @@ class TAxisPainter extends ObjectPainter { else this.logbase = Math.round(this.log); - if (smax <= 0) smax = 1; + if (smax <= 0) + smax = 1; - if ((smin <= 0) && axis && !opts.logcheckmin) { + if (opts.log_min_nz) + this.log_min_nz = opts.log_min_nz; + else if (axis && opts.logcheckmin) { + let v = 0; for (let i = 0; i < axis.fNbins; ++i) { - smin = Math.max(smin, axis.GetBinLowEdge(i+1)); - if (smin > 0) break; + v = axis.GetBinLowEdge(i + 1); + if (v > 0) + break; + v = axis.GetBinCenter(i + 1); + if (v > 0) + break; } + if (v > 0) + this.log_min_nz = v; } - if ((smin <= 0) && opts.log_min_nz) - smin = this.log_min_nz = opts.log_min_nz; + if ((smin <= 0) && this.log_min_nz) + smin = this.log_min_nz; if ((smin <= 0) || (smin >= smax)) smin = smax * (opts.logminfactor || 1e-4); @@ -472,19 +553,17 @@ class TAxisPainter extends ObjectPainter { } else if (this.symlog) { let v = Math.max(Math.abs(smin), Math.abs(smax)); if (Number.isInteger(this.symlog) && (this.symlog > 0)) - v *= Math.pow(10, -1*this.symlog); + v *= Math.pow(10, -1 * this.symlog); else v *= 0.01; this.func = d3_scaleSymlog().constant(v).domain([smin, smax]); } else if (this.kind === kAxisFunc) this.func = this.createFuncHandle(opts.axis_func, 0, smin, smax); - else + else this.func = d3_scaleLinear().domain([smin, smax]); - - if (this.vertical ^ this.reverse) { - const d = range[0]; range[0] = range[1]; range[1] = d; - } + if (this.vertical ^ this.reverse) + [range[0], range[1]] = [range[1], range[0]]; this.func.range(range); @@ -494,7 +573,7 @@ class TAxisPainter extends ObjectPainter { if (this.kind === kAxisTime) this.gr = val => this.func(this.convertDate(val)); else if (this.log) - this.gr = val => (val < this.scale_min) ? (this.vertical ? this.func.range()[0]+5 : -5) : this.func(val); + this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; else this.gr = this.func; @@ -503,24 +582,22 @@ class TAxisPainter extends ObjectPainter { let ndiv = 508; if (this.is_gaxis) ndiv = axis.fNdiv; - else if (axis) { - if (!axis.fNdivisions) - ndiv = 0; - else - ndiv = Math.max(axis.fNdivisions, 4); - } + else if (axis) + ndiv = axis.fNdivisions ? Math.max(axis.fNdivisions, 4) : 0; this.nticks = ndiv % 100; this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv/10000); + this.nticks3 = Math.floor(ndiv / 10000); - if (axis && !this.is_gaxis && (this.nticks > 20)) this.nticks = 20; + if (axis && !this.is_gaxis) + this.nticks = Math.min(this.nticks, 20); let gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - if (gr_range <= 0) gr_range = 100; + if (gr_range <= 0) + gr_range = 100; if (this.kind === kAxisTime) { - if (this.nticks > 8) this.nticks = 8; + this.nticks = Math.min(this.nticks, 8); const scale_range = this.scale_max - this.scale_min, idF = axis.fTimeFormat.indexOf('%F'), @@ -541,7 +618,8 @@ class TAxisPainter extends ObjectPainter { this.nticks2 = 1; } this.noexp = axis?.TestBit(EAxisBits.kNoExponent); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed) this.noexp = true; + if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed && (this.log === 1)) + this.noexp = true; this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels); this.format = this.formatLog; } else if (this.kind === kAxisLabels) { @@ -552,9 +630,9 @@ class TAxisPainter extends ObjectPainter { this.regular_labels = true; - if (axis && axis.fNbins && axis.fLabels) { + if (axis?.fNbins && axis?.fLabels) { if ((axis.fNbins !== Math.round(axis.fXmax - axis.fXmin)) || - (axis.fXmin !== 0) || (axis.fXmax !== axis.fNbins)) + axis.fXmin || (axis.fXmax !== axis.fNbins)) this.regular_labels = false; } @@ -568,6 +646,12 @@ class TAxisPainter extends ObjectPainter { } } + /** @summary Check zooming value for log scale + * @private */ + checkZoomMin(value) { + return this.log && this.log_min_nz ? Math.max(value, this.log_min_nz) : value; + } + /** @summary Return scale min */ getScaleMin() { return this.func?.domain()[0] ?? 0; @@ -578,18 +662,29 @@ class TAxisPainter extends ObjectPainter { return this.func?.domain()[1] ?? 0; } + /** @summary Return true if labels may be removed while they are not fit to graphical range */ + cutLabels() { + if (!settings.CutAxisLabels) + return false; + if (isStr(settings.CutAxisLabels)) + return settings.CutAxisLabels.indexOf(this.name) >= 0; + return this.vertical; // cut vertical axis by default + } + /** @summary Provide label for axis value */ formatLabels(d) { const a = this.getObject(); let indx = parseFloat(d); if (!this.regular_labels) - indx = Math.round((indx - a.fXmin)/(a.fXmax - a.fXmin) * a.fNbins); + indx = Math.round((indx - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins); else indx = Math.floor(indx); - if ((indx < 0) || (indx >= a.fNbins)) return null; - for (let i = 0; i < a.fLabels.arr.length; ++i) { - const tstr = a.fLabels.arr[i]; - if (tstr.fUniqueID === indx+1) return tstr.fString; + if ((indx < 0) || (indx >= a.fNbins)) + return null; + const arr = a.fLabels.arr; + for (let i = 0; i < arr.length; ++i) { + if (arr[i].fUniqueID === indx + 1) + return arr[i].fString; } return null; } @@ -600,22 +695,21 @@ class TAxisPainter extends ObjectPainter { this.noticksopt = true; const handle = { painter: this, nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: [], middle: [], major: [] }; - let ticks; + let ticks = []; if (this.fixed_ticks) { - ticks = []; this.fixed_ticks.forEach(v => { - if ((v >= this.scale_min) && (v <= this.scale_max)) ticks.push(v); + if ((v >= this.scale_min) && (v <= this.scale_max)) + ticks.push(v); }); - } else if ((this.kind === kAxisLabels) && !this.regular_labels) { - ticks = []; + } else if (this.kind === kAxisLabels) { handle.lbl_pos = []; const axis = this.getObject(); - for (let n = 0; n < axis.fNbins; ++n) { - const x = axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); - if ((x >= this.scale_min) && (x < this.scale_max)) { + for (let n = 0; n <= axis.fNbins; ++n) { + const x = this.regular_labels ? n : axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); + if ((x >= this.scale_min) && (x <= this.scale_max)) { handle.lbl_pos.push(x); - if (x > this.scale_min) ticks.push(x); + ticks.push(x); } } } else @@ -624,23 +718,23 @@ class TAxisPainter extends ObjectPainter { handle.minor = handle.middle = handle.major = ticks; if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min)*1e-5; - if (res[0] > this.scale_min + delta) res.unshift(this.scale_min); - if (res[res.length-1] < this.scale_max - delta) res.push(this.scale_max); + const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; + if (res.at(0) > this.scale_min + delta) + res.unshift(this.scale_min); + if (res.at(-1) < this.scale_max - delta) + res.push(this.scale_max); return res; } if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10)) && !this.fixed_ticks) { handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) + if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { + else if ((this.nticks3 > 1) && !this.log) { handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) + if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) handle.minor = handle.middle; } } @@ -650,11 +744,13 @@ class TAxisPainter extends ObjectPainter { }; handle.next = function(doround) { - if (this.nminor >= this.minor.length) return false; + if (this.nminor >= this.minor.length) + return false; this.tick = this.minor[this.nminor++]; this.grpos = this.func(this.tick); - if (doround) this.grpos = Math.round(this.grpos); + if (doround) + this.grpos = Math.round(this.grpos); this.kind = 3; if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { @@ -674,48 +770,65 @@ class TAxisPainter extends ObjectPainter { }; handle.next_major_grpos = function() { - if (this.nmajor >= this.major.length) return null; - return this.func(this.major[this.nmajor]); + return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); }; handle.get_modifier = function() { - return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor-1, this.major); + return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor - 1, this.major); }; this.order = 0; this.ndig = 0; // at the moment when drawing labels, we can try to find most optimal text representation for them - - if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && (handle.major.length > 0)) { + if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && handle.major.length) { let maxorder = 0, minorder = 0, exclorder3 = false; - if (!optionNoexp) { - const maxtick = Math.max(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - mintick = Math.min(Math.abs(handle.major[0]), Math.abs(handle.major[handle.major.length-1])), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick)/3)*3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick)/3)*3 : 0; + if (!optionNoexp && !this.cutLabels()) { + const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), + ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, + ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 + exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } + if (maxtick || mintick) { + maxorder = Math.max(ord1, ord2) + 3; + minorder = Math.min(ord1, ord2) - 3; + } } // now try to find best combination of order and ndig for labels - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - for (let order = minorder; order <= maxorder; order+=3) { - if (exclorder3 && (order === 3)) continue; + for (let order = minorder; order <= maxorder; order += 3) { + if (exclorder3 && (order === 3)) + continue; this.order = order; this.ndig = 0; let lbls = [], indx = 0, totallen = 0; while (indx < handle.major.length) { - const lbl = this.format(handle.major[indx], true); - if (lbls.indexOf(lbl) < 0) { + const v0 = handle.major[indx], + lbl = this.format(v0, true); + + let bad_value = lbls.indexOf(lbl) >= 0; + if (!bad_value) { + try { + const v1 = parseFloat(lbl) * Math.pow(10, order); + bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); + } catch { + console.warn('Failure by parsing of', lbl); + bad_value = true; + } + } + if (bad_value) { + if (++this.ndig > 15) { + totallen += 1e10; + break; // not too many digits, anyway it will be exponential + } + lbls = []; + indx = totallen = 0; + } else { lbls.push(lbl); const p = lbl.indexOf('.'); if (!order && !optionNoexp && ((p > gStyle.fAxisMaxDigits) || ((p < 0) && (lbl.length > gStyle.fAxisMaxDigits)))) { @@ -724,10 +837,7 @@ class TAxisPainter extends ObjectPainter { } totallen += lbl.length; indx++; - continue; } - if (++this.ndig > 15) break; // not too many digits, anyway it will be exponential - lbls = []; indx = 0; totallen = 0; } // for order === 0 we should virtually remove '0.' and extra label on top @@ -745,8 +855,10 @@ class TAxisPainter extends ObjectPainter { this.ndig = bestndig; if (optionInt) { - if (this.order) console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); + if (this.order) + console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); + if (this.ndig) + console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); this.ndig = 0; this.order = 0; } @@ -757,17 +869,30 @@ class TAxisPainter extends ObjectPainter { /** @summary Is labels should be centered */ isCenteredLabels() { - if (this.kind === kAxisLabels) return true; - if (this.log) return false; + if (this.kind === kAxisLabels) + return true; + if (this.log) + return false; return this.getObject()?.TestBit(EAxisBits.kCenterLabels); } + /** @summary Is labels should be rotated */ + isRotateLabels() { + return this.getObject()?.TestBit(EAxisBits.kLabelsVert); + } + + /** @summary Is title should be rotated */ + isRotateTitle() { + return this.getObject()?.TestBit(EAxisBits.kRotateTitle); + } + /** @summary Add interactive elements to draw axes title */ addTitleDrag(title_g, vertical, offset_k, reverse, axis_length) { - if (!settings.MoveResize || this.isBatchMode()) return; + if (!settings.MoveResize || this.isBatchMode()) + return; - let drag_rect = null, - acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx; + let drag_rect = null, x_0, y_0, i_0, + acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx, can_indx0 = true; const drag_move = d3_drag().subject(Object); drag_move.on('start', evnt => { @@ -775,34 +900,36 @@ class TAxisPainter extends ObjectPainter { evnt.sourceEvent.stopPropagation(); const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = vertical ? box.height : box.width; + title_length = vertical ? box.height : box.width; - new_x = acc_x = title_g.property('shift_x'); - new_y = acc_y = title_g.property('shift_y'); + x_0 = new_x = acc_x = title_g.property('shift_x'); + y_0 = new_y = acc_y = title_g.property('shift_y'); sign_0 = vertical ? (acc_x > 0) : (acc_y > 0); // sign should remain + can_indx0 = !this.hist_painter?.getSnapId(); // online canvas does not allow alternate position - alt_pos = vertical ? [axis_length, axis_length/2, 0] : [0, axis_length/2, axis_length]; // possible positions - const off = vertical ? -title_length/2 : title_length/2; + alt_pos = vertical ? [axis_length, axis_length / 2, 0] : [0, axis_length / 2, axis_length]; // possible positions + const off = vertical ? -title_length / 2 : title_length / 2; if (this.title_align === 'middle') { alt_pos[0] += off; alt_pos[2] -= off; } else if (this.title_align === 'begin') { alt_pos[1] -= off; - alt_pos[2] -= 2*off; + alt_pos[2] -= 2 * off; } else { // end - alt_pos[0] += 2*off; + alt_pos[0] += 2 * off; alt_pos[1] += off; } if (this.titleCenter) curr_indx = 1; - else if (reverse ^ this.titleOpposite) + else if ((reverse ^ this.titleOpposite) && can_indx0) curr_indx = 0; else curr_indx = 2; alt_pos[curr_indx] = vertical ? acc_y : acc_x; + i_0 = curr_indx; drag_rect = title_g.append('rect') .attr('x', box.x) @@ -813,7 +940,8 @@ class TAxisPainter extends ObjectPainter { .call(addHighlightStyle, true); // .style('pointer-events','none'); // let forward double click to underlying elements }).on('drag', evnt => { - if (!drag_rect) return; + if (!drag_rect) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); @@ -821,11 +949,13 @@ class TAxisPainter extends ObjectPainter { acc_x += evnt.dx; acc_y += evnt.dy; - let set_x, set_y, besti = 0; + let set_x, set_y, besti = can_indx0 ? 0 : 1; const p = vertical ? acc_y : acc_x; - for (let i = 1; i < 3; ++i) - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) besti = i; + for (let i = 1; i < 3; ++i) { + if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) + besti = i; + } if (vertical) { set_x = acc_x; @@ -836,11 +966,14 @@ class TAxisPainter extends ObjectPainter { } if (sign_0 === (vertical ? (set_x > 0) : (set_y > 0))) { - new_x = set_x; new_y = set_y; curr_indx = besti; + new_x = set_x; + new_y = set_y; + curr_indx = besti; makeTranslate(title_g, new_x, new_y); } }).on('end', evnt => { - if (!drag_rect) return; + if (!drag_rect) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); @@ -850,30 +983,42 @@ class TAxisPainter extends ObjectPainter { const axis = this.getObject(), axis2 = this.source_axis, setBit = (bit, on) => { - if (axis && axis.TestBit(bit) !== on) axis.InvertBit(bit); - if (axis2 && axis2.TestBit(bit) !== on) axis2.InvertBit(bit); + axis?.SetBit(bit, on); + axis2?.SetBit(bit, on); }; this.titleOffset = (vertical ? new_x : new_y) / offset_k; const offset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis) axis.fTitleOffset = offset; - if (axis2) axis2.fTitleOffset = offset; + if (axis) + axis.fTitleOffset = offset; + if (axis2) + axis2.fTitleOffset = offset; if (curr_indx === 1) { - setBit(EAxisBits.kCenterTitle, true); this.titleCenter = true; - setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, true); + this.titleCenter = true; + setBit(EAxisBits.kOppositeTitle, false); + this.titleOpposite = false; } else if (curr_indx === 0) { - setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, true); this.titleOpposite = true; + setBit(EAxisBits.kCenterTitle, false); + this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, true); + this.titleOpposite = true; } else { - setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, false); + this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, false); + this.titleOpposite = false; } - this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter?1:0})`); - drag_rect.remove(); drag_rect = null; + + if ((x_0 !== new_x) || (y_0 !== new_y) || (i_0 !== curr_indx)) + this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter ? 1 : 0})`); + + if (this.hist_painter && this.hist_axis) + this.hist_painter.getCanvPainter()?.producePadEvent('select', this.hist_painter.getPadPainter(), this); }); title_g.style('cursor', 'move').call(drag_move); @@ -889,7 +1034,7 @@ class TAxisPainter extends ObjectPainter { /** @summary Submit exec for the axis - if possible * @private */ submitAxisExec(exec, only_gaxis) { - const snapid = this.hist_painter?.snapid; + const snapid = this.hist_painter?.getSnapId(); if (snapid && this.hist_axis && !only_gaxis) this.submitCanvExec(exec, `${snapid}#${this.hist_axis}`); else if (this.is_gaxis) @@ -902,28 +1047,30 @@ class TAxisPainter extends ObjectPainter { this.ticks = []; while (handle.next(true)) { - let h1 = Math.round(tickSize/4), h2 = 0; + let h1 = Math.round(tickSize / 4), h2 = 0; if (handle.kind < 3) - h1 = Math.round(tickSize/2); + h1 = Math.round(tickSize / 2); if (handle.kind === 1) { // if not showing labels, not show large tick - // FIXME: for labels last tick is smaller, - if (/* (this.kind === kAxisLabels) || */ (this.format(handle.tick, true) !== null)) h1 = tickSize; + // FIXME: for labels last tick is smaller + if (!this.isExtraLogTick(handle.tick) && (this.format(handle.tick, true) !== null)) + h1 = tickSize; this.ticks.push(handle.grpos); // keep graphical positions of major ticks } if (ticksPlusMinus > 0) h2 = -h1; - else if (side < 0) { - h2 = -h1; h1 = 0; + else if (side < 0) { + h2 = -h1; + h1 = 0; } path1 += this.vertical ? `M${h1},${handle.grpos}H${h2}` : `M${handle.grpos},${-h1}V${-h2}`; if (secondShift) - path2 += this.vertical ? `M${secondShift-h1},${handle.grpos}H${secondShift-h2}` : `M${handle.grpos},${secondShift+h1}V${secondShift+h2}`; + path2 += this.vertical ? `M${secondShift - h1},${handle.grpos}H${secondShift - h2}` : `M${handle.grpos},${secondShift + h1}V${secondShift + h2}`; } return real_draw ? path1 + path2 : ''; @@ -931,39 +1078,40 @@ class TAxisPainter extends ObjectPainter { /** @summary Returns modifier for axis label */ findLabelModifier(axis, nlabel, positions) { - if (!axis.fModLabs) return null; + if (!axis.fModLabs) + return null; for (let n = 0; n < axis.fModLabs.arr.length; ++n) { const mod = axis.fModLabs.arr[n]; if ((mod.fLabValue !== undefined) && (mod.fLabNum === 0)) { - const eps = this.log ? positions[nlabel]*1e-6 : (this.scale_max - this.scale_min)*1e-6; + const eps = this.log ? positions[nlabel] * 1e-6 : (this.scale_max - this.scale_min) * 1e-6; if (Math.abs(mod.fLabValue - positions[nlabel]) < eps) return mod; } if ((mod.fLabNum === nlabel + 1) || ((mod.fLabNum < 0) && (nlabel === positions.length + mod.fLabNum))) - return mod; + return mod; } return null; } /** @summary Draw axis labels * @return {Promise} with array label size and max width */ - async drawLabels(axis_g, axis, w, h, handle, side, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { + async drawLabels(axis_g, axis, w, h, handle, labelsside, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { const center_lbls = this.isCenteredLabels(), - rotate_lbls = axis.TestBit(EAxisBits.kLabelsVert), label_g = [axis_g.append('svg:g').attr('class', 'axis_labels')], lbl_pos = handle.lbl_pos || handle.major, tilt_angle = gStyle.AxisTiltAngle ?? 25; - let textscale = 1, maxtextlen = 0, applied_scale = 0, + let rotate_lbls = this.isRotateLabels(), + textscale = 1, flipscale = 1, maxtextlen = 0, applied_scale = 0, lbl_tilt = false, any_modified = false, max_textwidth = 0, max_tiltsize = 0; if (this.lbls_both_sides) label_g.push(axis_g.append('svg:g').attr('class', 'axis_labels').attr('transform', this.vertical ? `translate(${w})` : `translate(0,${-h})`)); if (frame_ygap > 0) - max_tiltsize = frame_ygap / Math.sin(tilt_angle/180*Math.PI) - Math.tan(tilt_angle/180*Math.PI); + max_tiltsize = frame_ygap / Math.sin(tilt_angle / 180 * Math.PI) - Math.tan(tilt_angle / 180 * Math.PI); // function called when text is drawn to analyze width, required to correctly scale all labels // must be function to correctly handle 'this' argument @@ -971,25 +1119,27 @@ class TAxisPainter extends ObjectPainter { const textwidth = this.result_width; max_textwidth = Math.max(max_textwidth, textwidth); - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { - let maxwidth = this.gap_before*0.45 + this.gap_after*0.45; - if (!this.gap_before) - maxwidth = 0.9*this.gap_after; - else if (!this.gap_after) - maxwidth = 0.9*this.gap_before; + const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); + + if (!painter.vertical && !rotate_lbls && this.result_height && maxwidth) + flipscale = Math.min(flipscale, maxwidth / this.result_height); + + if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) textscale = Math.min(textscale, maxwidth / textwidth); - } else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) + else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth); if ((textscale > 0.0001) && (textscale < 0.7) && !any_modified && - !painter.vertical && !rotate_lbls && (maxtextlen > 5) && (label_g.length === 1) && (lbl_tilt === false)) - lbl_tilt = true; + !painter.vertical && !rotate_lbls && (label_g.length === 1) && (lbl_tilt === false)) { + if (maxtextlen > 5) + lbl_tilt = true; + } let scale = textscale; if (lbl_tilt) { if (max_tiltsize && max_textwidth) { - scale = Math.min(1, 0.8*max_tiltsize/max_textwidth); + scale = Math.min(1, 0.8 * max_tiltsize / max_textwidth); if (scale < textscale) { // if due to tilt scale is even smaller - ignore tilting lbl_tilt = 0; @@ -1000,116 +1150,161 @@ class TAxisPainter extends ObjectPainter { } if (((scale > 0.0001) && (scale < 1)) || (lbl_tilt !== false)) { - applied_scale = 1/scale; + applied_scale = 1 / scale; painter.scaleTextDrawing(applied_scale, label_g[0]); } } - for (let lcnt = 0; lcnt < label_g.length; ++lcnt) { - if (lcnt > 0) side = -side; + // check if short labels can be rotated + if (!this.vertical && this.regular_labels && !rotate_lbls) { + let tlen = 0; + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + const text = this.format(lbl_pos[nmajor], true); + if (text) + tlen = Math.max(tlen, text.length); + } - let lastpos = 0; - const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; + if ((tlen > 2) && (tlen <= 5) && (lbl_pos.length * labelsFont.size > w / 2)) { + rotate_lbls = true; + lbl_tilt = 0; + } + } - this.startTextDrawing(labelsFont, 'font', label_g[lcnt]); + const draw_labels = (lcnt, side) => { + return this.startTextDrawingAsync(labelsFont, 'font', label_g[lcnt]).then(() => { + let lastpos = 0; + const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - let text = this.format(lbl_pos[nmajor], true); - if (text === null) continue; + for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { + let text = this.format(lbl_pos[nmajor], true); + if (text === null) + continue; - const mod = this.findLabelModifier(axis, nmajor, lbl_pos); - if (mod?.fTextSize === 0) continue; + const mod = this.findLabelModifier(axis, nmajor, lbl_pos); + if (mod?.fTextSize === 0) + continue; - if (mod) any_modified = true; - if (mod?.fLabText) text = mod.fLabText; + if (mod) + any_modified = true; + if (mod?.fLabText) + text = mod.fLabText; - const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; - let pos = Math.round(this.func(lbl_pos[nmajor])); + const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; + let pos = Math.round(this.func(lbl_pos[nmajor])); - if (mod?.fTextColor > 0) arg.color = this.getColor(mod.fTextColor); + // exclude labels for extra log ticks + if (lastpos && this.vertical && (Math.abs(pos - lastpos) < labelsFont.size * 1.1) && this.isExtraLogTick(lbl_pos[nmajor])) { + lastpos = pos; + continue; + } - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; + if (mod?.fTextColor > 0) + arg.color = this.getColor(mod.fTextColor); - arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; + arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); - if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) continue; - } + arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - maxtextlen = Math.max(maxtextlen, text.length); + if (center_lbls) { + const gap = arg.gap_after || arg.gap_before; + pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); + if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) + continue; + } - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += labelsFont.size; - } else if (arg.align % 10 === 3) - arg.y -= labelsFont.size*0.1; // font takes 10% more by top align - } + maxtextlen = Math.max(maxtextlen, text.length); + + if (this.vertical) { + arg.x = fix_coord; + arg.y = pos; + const flag = this.optionLeft || this.reverseAlign || (side < 0); + arg.align = rotate_lbls ? (flag ? 23 : 21) : (flag ? 12 : 32); + if (this.cutLabels()) { + const gap = labelsFont.size * (rotate_lbls ? 1.5 : 0.6); + if ((pos < gap) || (pos > h - gap)) + continue; + } + } else { + arg.x = pos; + arg.y = fix_coord; + arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 21 : 23); + if (this.log && !this.noexp && !this.vertical && arg.align === 23) { + arg.align = 21; + arg.y += labelsFont.size; + } else if (arg.align % 10 === 3) + arg.y -= labelsFont.size * 0.1; // font takes 10% more by top align + + if (this.cutLabels()) { + const gap = labelsFont.size * (rotate_lbls ? 0.4 : 1.5); + if ((pos < gap) || (pos > w - gap)) + continue; + } + } + + if (rotate_lbls) + arg.rotate = 270; + else if (mod && mod.fTextAngle !== -1) + arg.rotate = -mod.fTextAngle; - if (rotate_lbls) - arg.rotate = 270; - else if (mod && mod.fTextAngle !== -1) - arg.rotate = -mod.fTextAngle; + // only for major text drawing scale factor need to be checked + // for modified labels ignore scaling + if ((lcnt === 0) && !mod?.fLabText) + arg.post_process = process_drawtext_ready; - // only for major text drawing scale factor need to be checked - if (lcnt === 0) arg.post_process = process_drawtext_ready; + this.drawText(arg); - this.drawText(arg); + // workaround for symlog where labels can be compressed to close + if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + const axis_step = Math.abs(pos - lastpos); + textscale = Math.min(textscale, 1.1 * axis_step / labelsFont.size); + } - // workaround for symlog where labels can be compressed to close - if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos - lastpos); - textscale = Math.min(textscale, 1.1*axis_step/labelsFont.size); + lastpos = pos; } - lastpos = pos; - } + if (this.order) { + let xoff = 0, yoff = 0; + if (this.name === 'xaxis') { + xoff = gStyle.fXAxisExpXOffset || 0; + yoff = gStyle.fXAxisExpYOffset || 0; + } else if (this.name === 'yaxis') { + xoff = gStyle.fYAxisExpXOffset || 0; + yoff = gStyle.fYAxisExpYOffset || 0; + } - if (this.order) { - let xoff = 0, yoff = 0; - if (this.name === 'xaxis') { - xoff = gStyle.fXAxisExpXOffset || 0; - yoff = gStyle.fXAxisExpYOffset || 0; - } else if (this.name === 'yaxis') { - xoff = gStyle.fYAxisExpXOffset || 0; - yoff = gStyle.fYAxisExpYOffset || 0; + if (xoff) + xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); + if (yoff) + yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); + + this.drawText({ + color: labelsFont.color, + x: xoff + (this.vertical ? side * 5 : w + 5), + y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3 * side)), + align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), + latex: 1, + text: '#times' + this.formatExp(10, this.order), + draw_g: label_g[lcnt] + }); } - if (xoff) xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); - if (yoff) yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); + if ((lcnt > 0) && applied_scale) + this.scaleTextDrawing(applied_scale, label_g[lcnt]); - this.drawText({ color: labelsFont.color, - x: xoff + (this.vertical ? side*5 : w+5), - y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3*side)), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g[lcnt] }); - } - } + return this.finishTextDrawing(label_g[lcnt], true); + }); + }; - // first complete major labels drawing - return this.finishTextDrawing(label_g[0], true).then(() => { - if (label_g.length > 1) { - // now complete drawing of second half with scaling if necessary - if (applied_scale) - this.scaleTextDrawing(applied_scale, label_g[1]); - return this.finishTextDrawing(label_g[1], true); - } + return draw_labels(0, labelsside).then(() => { + return label_g.length < 2 ? true : draw_labels(1, -labelsside); }).then(() => { + this._maxlbllen = maxtextlen; // for internal use in palette painter + if (lbl_tilt) { label_g[0].selectAll('text').each(function() { const txt = d3_select(this), tr = txt.attr('transform'); - txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); + if (lbl_tilt) + txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); }); } @@ -1124,34 +1319,44 @@ class TAxisPainter extends ObjectPainter { let pp = this.getPadPainter(); if (axis.$use_top_pad) pp = pp?.getPadPainter(); // workaround for ratio plot - const pad_w = pp?.getPadWidth() || scalingSize || w/0.8, // use factor 0.8 as ratio between frame and pad size - pad_h = pp?.getPadHeight() || scalingSize || h/0.8, + const pad_w = pp?.getPadWidth() || scalingSize || w / 0.8, // use factor 0.8 as ratio between frame and pad size + pad_h = pp?.getPadHeight() || scalingSize || h / 0.8, // if no external scaling size use scaling as in TGaxis.cxx:1448 - NDC axis length is in the scaling factor - tickScalingSize = scalingSize || (this.vertical ? h/pad_h*pad_w : w/pad_w*pad_h); + tickScalingSize = scalingSize || (this.vertical ? h / pad_h * pad_w : w / pad_w * pad_h), + bit_plus = axis.TestBit(EAxisBits.kTickPlus), bit_minus = axis.TestBit(EAxisBits.kTickMinus); - let tickSize = 0, titleColor, titleFontId, offset; + let tickSize, titleColor, titleFontId, offset; this.scalingSize = scalingSize || Math.max(Math.min(pad_w, pad_h), 10); if (this.is_gaxis) { const optionSize = axis.fChopt.indexOf('S') >= 0; this.optionUnlab = axis.fChopt.indexOf('U') >= 0; - this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || axis.TestBit(EAxisBits.kTickMinus); - this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || axis.TestBit(EAxisBits.kTickPlus); + this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || bit_minus; + this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || bit_plus; this.optionNoopt = (axis.fChopt.indexOf('N') >= 0); // no ticks position optimization this.optionInt = (axis.fChopt.indexOf('I') >= 0); // integer labels this.optionText = (axis.fChopt.indexOf('T') >= 0); // text scaling? + this.optionLeft = (axis.fChopt.indexOf('L') >= 0); // left text align + this.optionRight = (axis.fChopt.indexOf('R') >= 0); // right text align + this.optionCenter = (axis.fChopt.indexOf('C') >= 0); // center text align this.createAttLine({ attr: axis }); tickSize = optionSize ? axis.fTickSize : 0.03; titleColor = this.getColor(axis.fTextColor); titleFontId = axis.fTextFont; offset = axis.fLabelOffset; - if ((this.vertical && axis.fY1 > axis.fY2 && !this.optionMinus) || (!this.vertical && axis.fX1 > axis.fX2)) + // workaround for old reverse axes where offset is not properly working + if (this.reverse && (!this.vertical || (!this.optionMinus && (axis.fX1 !== axis.fX2)))) offset = -offset; } else { this.optionUnlab = false; - this.optionMinus = this.vertical ^ this.invert_side; - this.optionPlus = !this.optionMinus; + if (!bit_plus && !bit_minus) { + this.optionMinus = this.vertical ^ this.invert_side; + this.optionPlus = !this.optionMinus; + } else { + this.optionPlus = bit_plus; + this.optionMinus = bit_minus; + } this.optionNoopt = false; // no ticks position optimization this.optionInt = false; // integer labels this.optionText = false; @@ -1173,7 +1378,8 @@ class TAxisPainter extends ObjectPainter { if (scalingSize && (this.ticksSize < 0)) this.ticksSize = -this.ticksSize; - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) this.ticksSize = this.maxTickSize; + if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) + this.ticksSize = this.maxTickSize; // now used only in 3D drawing this.ticksColor = this.lineatt.color; @@ -1183,7 +1389,8 @@ class TAxisPainter extends ObjectPainter { this.labelSize = Math.round((axis.fLabelSize < 1) ? k * axis.fLabelSize * this.scalingSize : k * axis.fLabelSize); this.labelsOffset = Math.round(offset * this.scalingSize); this.labelsFont = new FontHandler(axis.fLabelFont, this.labelSize, scalingSize); - if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) this.optionUnlab = true; // disable labels when size not specified + if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) + this.optionUnlab = true; // disable labels when size not specified this.labelsFont.setColor(this.getColor(axis.fLabelColor)); this.fTitle = axis.fTitle; @@ -1224,10 +1431,7 @@ class TAxisPainter extends ObjectPainter { this.extractDrawAttributes(undefined, w, h); if (this.is_gaxis) - draw_lines = axis.fLineColor !== 0; - - // indicate that attributes created not for TAttLine, therefore cannot be updated as TAttLine in GED - this.lineatt.not_standard = true; + draw_lines = Boolean(axis.fLineColor); if (!this.is_gaxis || (this.name === 'zaxis')) { axis_g = layer.selectChild(`.${this.name}_container`); @@ -1248,9 +1452,9 @@ class TAxisPainter extends ObjectPainter { let side = 1, ticksPlusMinus = 0; - if (this.optionPlus && this.optionMinus) { - side = 1; ticksPlusMinus = 1; - } else if (this.optionMinus) + if (this.optionPlus && this.optionMinus) + side = ticksPlusMinus = 1; + else if (this.optionMinus) side = (swap_side ^ this.vertical) ? 1 : -1; else if (this.optionPlus) side = (swap_side ^ this.vertical) ? -1 : 1; @@ -1267,9 +1471,10 @@ class TAxisPainter extends ObjectPainter { .call(this.lineatt.func); } - let title_shift_x = 0, title_shift_y = 0, title_g = null, labelsMaxWidth = 0; + let title_shift_x = 0, title_shift_y = 0, title_g, labelsMaxWidth = 0; // draw labels (sometime on both sides) - const pr = (disable_axis_drawing || this.optionUnlab) + const labelSize = Math.max(this.labelsFont.size, 5), + pr = (disable_axis_drawing || this.optionUnlab) ? Promise.resolve(0) : this.drawLabels(axis_g, axis, w, h, handle, side, this.labelsFont, this.labelsOffset, this.ticksSize, ticksPlusMinus, max_text_width, frame_ygap); @@ -1277,14 +1482,13 @@ class TAxisPainter extends ObjectPainter { labelsMaxWidth = maxw; if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const labelSize = Math.max(this.labelsFont.size, 5), - r = axis_g.append('svg:rect') + const r = axis_g.append('svg:rect') .attr('class', 'axis_zoom') .style('opacity', '0') .style('cursor', 'crosshair'); if (this.vertical) { - const rw = (labelsMaxWidth || 2*labelSize) + 3; + const rw = Math.max(labelsMaxWidth, 2 * labelSize) + 3; r.attr('x', (side > 0) ? -rw : 0).attr('y', 0) .attr('width', rw).attr('height', h); } else { @@ -1296,26 +1500,31 @@ class TAxisPainter extends ObjectPainter { this.position = 0; if (calculate_position) { - const node1 = axis_g.node(), node2 = this.getPadSvg().node(); + const node1 = axis_g.node(), + node2 = this.getPadPainter()?.getPadSvg().node(); if (isFunc(node1?.getBoundingClientRect) && isFunc(node2?.getBoundingClientRect)) { const rect1 = node1.getBoundingClientRect(), - rect2 = node2.getBoundingClientRect(); + rect2 = node2.getBoundingClientRect(); this.position = rect1.left - rect2.left; // use to control left position of Y scale } if (node1 && !node2) console.warn('Why PAD element missing when search for position'); } - if (!this.fTitle || disable_axis_drawing) return true; + if (!this.fTitle || disable_axis_drawing) + return; title_g = axis_g.append('svg:g').attr('class', 'axis_title'); - let title_offest_k = side; - const rotate = axis.TestBit(EAxisBits.kRotateTitle) ? -1 : 1; + return this.startTextDrawingAsync(this.titleFont, 'font', title_g); + }).then(() => { + if (!title_g) + return; - this.startTextDrawing(this.titleFont, 'font', title_g); + const rotate = this.isRotateTitle() ? -1 : 1, + xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; - const xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; + let title_offest_k = side; this.title_align = this.titleCenter ? 'middle' : (myxor ? 'begin' : 'end'); @@ -1324,26 +1533,23 @@ class TAxisPainter extends ObjectPainter { title_shift_x = Math.round(title_offest_k * this.titleOffset); - // if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { - // // special handling for color palette labels - draw them always on right side - // const rect = axis_g.node().getBoundingClientRect(); - // if (title_shift_x < rect.width - this.ticksSize) - // title_shift_x = Math.round(rect.width - this.ticksSize); - // } - - title_shift_y = Math.round(this.titleCenter ? h/2 : (xor_reverse ? h : 0)); + title_shift_y = Math.round(this.titleCenter ? h / 2 : (xor_reverse ? h : 0)); - this.drawText({ align: this.title_align+';middle', - rotate: (rotate < 0) ? 90 : 270, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g }); + this.drawText({ + align: this.title_align + ';middle', + rotate: (rotate < 0) ? 90 : 270, + text: this.fTitle, color: this.titleFont.color, draw_g: title_g + }); } else { title_offest_k *= 1.6; - title_shift_x = Math.round(this.titleCenter ? w/2 : (xor_reverse ? 0 : w)); + title_shift_x = Math.round(this.titleCenter ? w / 2 : (xor_reverse ? 0 : w)); title_shift_y = Math.round(title_offest_k * this.titleOffset); - this.drawText({ align: this.title_align+';middle', - rotate: (rotate < 0) ? 180 : 0, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g }); + this.drawText({ + align: this.title_align + ';middle', + rotate: (rotate < 0) ? 180 : 0, + text: this.fTitle, color: this.titleFont.color, draw_g: title_g + }); } this.addTitleDrag(title_g, this.vertical, title_offest_k, swap_side, this.vertical ? h : w); @@ -1351,11 +1557,11 @@ class TAxisPainter extends ObjectPainter { return this.finishTextDrawing(title_g); }).then(() => { if (title_g) { - if (!this.titleOffset && this.vertical && labelsMaxWidth) - title_shift_x = Math.round(-side * (labelsMaxWidth + 0.7*this.offsetScaling*this.titleSize)); + if (!this.titleOffset && this.vertical) + title_shift_x = Math.round(-side * ((labelsMaxWidth || labelSize) + 0.7 * this.offsetScaling * this.titleSize)); makeTranslate(title_g, title_shift_x, title_shift_y); title_g.property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); + .property('shift_y', title_shift_y); } return this; @@ -1364,4 +1570,5 @@ class TAxisPainter extends ObjectPainter { } // class TAxisPainter + export { EAxisBits, chooseTimeFormat, AxisPainterMethods, TAxisPainter }; diff --git a/modules/gpad/TCanvasPainter.mjs b/modules/gpad/TCanvasPainter.mjs index 6eb2b2484..b9304d8ec 100644 --- a/modules/gpad/TCanvasPainter.mjs +++ b/modules/gpad/TCanvasPainter.mjs @@ -1,19 +1,19 @@ -import { BIT, settings, browser, create, parse, toJSON, loadScript, isFunc, isStr, clTCanvas } from '../core.mjs'; +import { BIT, settings, gStyle, internals, browser, create, parse, toJSON, loadScript, isFunc, isStr, clTCanvas } from '../core.mjs'; import { select as d3_select } from '../d3.mjs'; import { closeCurrentWindow, showProgress, loadOpenui5, ToolbarIcons, getColorExec } from '../gui/utils.mjs'; import { GridDisplay, getHPainter } from '../gui/display.mjs'; -import { cleanup, resize, selectActivePad, EAxisBits } from '../base/ObjectPainter.mjs'; +import { cleanup, resize, selectActivePad, EAxisBits, getDomCanvasPainter } from '../base/ObjectPainter.mjs'; import { TFramePainter } from './TFramePainter.mjs'; import { TPadPainter, clTButton, createWebObjectOptions } from './TPadPainter.mjs'; const kShowEventStatus = BIT(15), - // kAutoExec = BIT(16), + // kAutoExec = BIT(16), kMenuBar = BIT(17), kShowToolBar = BIT(18), kShowEditor = BIT(19), - // kMoveOpaque = BIT(20), - // kResizeOpaque = BIT(21), - // kIsGrayscale = BIT(22), + // kMoveOpaque = BIT(20), + // kResizeOpaque = BIT(21), + // kIsGrayscale = BIT(22), kShowToolTips = BIT(23); /** @summary direct draw of TFrame object, @@ -22,7 +22,8 @@ const kShowEventStatus = BIT(15), function directDrawTFrame(dom, obj, opt) { const fp = new TFramePainter(dom, obj); fp.addToPadPrimitives(); - if (opt === '3d') fp.mode3d = true; + if (opt === '3d') + fp.mode3d = true; return fp.redraw(); } @@ -34,30 +35,35 @@ function directDrawTFrame(dom, obj, opt) { class TCanvasPainter extends TPadPainter { + #websocket; // WebWindow handle used for communication with server + #changed_layout; // modified layout + #getmenu_callback; // function called when menu items get from server + #online_fixed_size; // when size fixed for online canvas + #all_sections_showed; // set once after online canvas drawn + #last_highlight_msg; // last highligh msg send to server + /** @summary Constructor */ - constructor(dom, canvas) { - super(dom, canvas, true); - this._websocket = null; + constructor(dom, canvas, opt, kind = true) { + super(dom, canvas, opt, kind); + this.#websocket = null; this.tooltip_allowed = settings.Tooltip; - if ((dom === null) && (canvas === null)) { - // for web canvas details are important - settings.SmallPad.width = 20; - settings.SmallPad.height = 10; - } } /** @summary Cleanup canvas painter */ cleanup() { - if (this._changed_layout) + if (this.#changed_layout) this.setLayoutKind('simple'); - delete this._changed_layout; + this.#changed_layout = undefined; super.cleanup(); } + /** @summary Returns canvas name */ + getCanvasName() { return this.getObjectName(); } + /** @summary Returns layout kind */ getLayoutKind() { const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); + layout = origin.empty() ? '' : origin.property('layout'); return layout || 'simple'; } @@ -66,10 +72,11 @@ class TCanvasPainter extends TPadPainter { setLayoutKind(kind, main_selector) { const origin = this.selectDom('origin'); if (!origin.empty()) { - if (!kind) kind = 'simple'; + if (!kind) + kind = 'simple'; origin.property('layout', kind); origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this._changed_layout = (kind !== 'simple'); // use in cleanup + this.#changed_layout = (kind !== 'simple'); // use in cleanup } } @@ -145,7 +152,8 @@ class TCanvasPainter extends TPadPainter { async toggleProjection(kind) { delete this.proj_painter; - if (kind) this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed + if (kind) + this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed if (isFunc(this.showUI5ProjectionArea)) return this.showUI5ProjectionArea(kind); @@ -153,13 +161,28 @@ class TCanvasPainter extends TPadPainter { let layout = 'simple', mainid; switch (kind) { - case 'XY': layout = 'projxy'; mainid = 2; break; + case 'XY': + layout = 'projxy'; + mainid = 2; + break; case 'X': - case 'bottom': layout = 'vert2_31'; mainid = 0; break; + case 'bottom': + layout = 'vert2_31'; + mainid = 0; + break; case 'Y': - case 'left': layout = 'horiz2_13'; mainid = 1; break; - case 'top': layout = 'vert2_13'; mainid = 1; break; - case 'right': layout = 'horiz2_31'; mainid = 0; break; + case 'left': + layout = 'horiz2_13'; + mainid = 1; + break; + case 'top': + layout = 'vert2_13'; + mainid = 1; + break; + case 'right': + layout = 'horiz2_31'; + mainid = 0; + break; } return this.changeLayout(layout, mainid); @@ -173,29 +196,30 @@ class TCanvasPainter extends TPadPainter { if (hopt === undefined) hopt = 'hist'; - if (!kind) kind = 'X'; + if (!kind) + kind = 'X'; if (!this.proj_painter[kind]) { this.proj_painter[kind] = 'init'; const canv = create(clTCanvas), - pad = this.pad, - main = this.getFramePainter(); + pad = this.getRootPad(), + fp = this.getFramePainter(); let drawopt; if (kind === 'X') { canv.fLeftMargin = pad.fLeftMargin; canv.fRightMargin = pad.fRightMargin; - canv.fLogx = main.logx; - canv.fUxmin = main.logx ? Math.log10(main.scale_xmin) : main.scale_xmin; - canv.fUxmax = main.logx ? Math.log10(main.scale_xmax) : main.scale_xmax; + canv.fLogx = fp.logx; + canv.fUxmin = fp.logx ? Math.log10(fp.scale_xmin) : fp.scale_xmin; + canv.fUxmax = fp.logx ? Math.log10(fp.scale_xmax) : fp.scale_xmax; drawopt = 'fixframe'; } else if (kind === 'Y') { canv.fBottomMargin = pad.fBottomMargin; canv.fTopMargin = pad.fTopMargin; - canv.fLogx = main.logy; - canv.fUxmin = main.logy ? Math.log10(main.scale_ymin) : main.scale_ymin; - canv.fUxmax = main.logy ? Math.log10(main.scale_ymax) : main.scale_ymax; + canv.fLogx = fp.logy; + canv.fUxmin = fp.logy ? Math.log10(fp.scale_ymin) : fp.scale_ymin; + canv.fUxmax = fp.logy ? Math.log10(fp.scale_ymax) : fp.scale_ymax; drawopt = 'rotate'; } @@ -205,7 +229,10 @@ class TCanvasPainter extends TPadPainter { ? this.drawInUI5ProjectionArea(canv, drawopt, kind) : this.drawInSidePanel(canv, drawopt, kind); - return promise.then(painter => { this.proj_painter[kind] = painter; return painter; }); + return promise.then(painter => { + this.proj_painter[kind] = painter; + return painter; + }); } else if (isStr(this.proj_painter[kind])) { console.log('Not ready with first painting', kind); return true; @@ -219,9 +246,7 @@ class TCanvasPainter extends TPadPainter { * @desc Function should be used only from the func which supposed to be replaced by ui5 * @private */ testUI5() { - if (!this.use_openui) return false; - console.warn('full ui5 should be used - not loaded yet? Please check!!'); - return true; + return this.use_openui ?? false; } /** @summary Draw in side panel @@ -243,7 +268,7 @@ class TCanvasPainter extends TPadPainter { /** @summary Function called when canvas menu item Save is called */ saveCanvasAsFile(fname) { const pnt = fname.indexOf('.'); - this.createImage(fname.slice(pnt+1)) + this.createImage(fname.slice(pnt + 1)) .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); } @@ -259,7 +284,7 @@ class TCanvasPainter extends TPadPainter { async submitMenuRequest(_painter, _kind, reqid) { // only single request can be handled, no limit better in RCanvas return new Promise(resolveFunc => { - this._getmenu_callback = resolveFunc; + this.#getmenu_callback = resolveFunc; this.sendWebsocket('GETMENU:' + reqid); // request menu items for given painter }); } @@ -267,19 +292,29 @@ class TCanvasPainter extends TPadPainter { /** @summary Submit object exec request * @private */ submitExec(painter, exec, snapid) { - if (this._readonly || !painter) return; + if (this.isReadonly() || !painter) + return; - if (!snapid) snapid = painter.snapid; + if (!snapid) + snapid = painter.getSnapId(); if (snapid && isStr(snapid) && exec) return this.sendWebsocket(`OBJEXEC:${snapid}:${exec}`); } + /** @summary Return assigned web socket + * @private */ + getWebsocket() { return this.#websocket; } + + /** @summary Return true if message can be send via web socket + * @private */ + canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } + /** @summary Send text message with web socket * @desc used for communication with server-side of web canvas * @private */ sendWebsocket(msg) { - if (this._websocket?.canSend()) { - this._websocket.send(msg); + if (this.#websocket?.canSend()) { + this.#websocket.send(msg); return true; } console.warn(`DROP SEND: ${msg}`); @@ -289,10 +324,10 @@ class TCanvasPainter extends TPadPainter { /** @summary Close websocket connection to canvas * @private */ closeWebsocket(force) { - if (this._websocket) { - this._websocket.close(force); - this._websocket.cleanup(); - delete this._websocket; + if (this.#websocket) { + this.#websocket.close(force); + this.#websocket.cleanup(); + this.#websocket = undefined; } } @@ -301,36 +336,39 @@ class TCanvasPainter extends TPadPainter { useWebsocket(handle) { this.closeWebsocket(); - this._websocket = handle; - this._websocket.setReceiver(this); - this._websocket.connect(); + this.#websocket = handle; + this.#websocket.setReceiver(this); + this.#websocket.connect(); } /** @summary set, test or reset timeout of specified name * @desc Used to prevent overloading of websocket for specific function */ websocketTimeout(name, tm) { - if (!this._websocket) + if (!this.#websocket) return; - if (!this._websocket._tmouts) - this._websocket._tmouts = {}; + if (!this.#websocket._tmouts) + this.#websocket._tmouts = {}; - const handle = this._websocket._tmouts[name]; + const handle = this.#websocket._tmouts[name]; if (tm === undefined) return handle !== undefined; if (tm === 'reset') { - if (handle) { clearTimeout(handle); delete this._websocket._tmouts[name]; } + if (handle) { + clearTimeout(handle); + delete this.#websocket._tmouts[name]; + } } else if (!handle && Number.isInteger(tm)) - this._websocket._tmouts[name] = setTimeout(() => { delete this._websocket._tmouts[name]; }, tm); + this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); } - /** @summary Hanler for websocket open event + /** @summary Handler for websocket open event * @private */ onWebsocketOpened(/* handle */) { - // indicate that we are ready to recieve any following commands + // indicate that we are ready to receive any following commands } - /** @summary Hanler for websocket close event + /** @summary Handler for websocket close event * @private */ onWebsocketClosed(/* handle */) { if (!this.embed_canvas) @@ -340,7 +378,7 @@ class TCanvasPainter extends TPadPainter { /** @summary Handle websocket messages * @private */ onWebsocketMsg(handle, msg) { - // console.log(`GET MSG len:${msg.length} ${msg.slice(0,60)}`); + // console.log(`GET len:${msg.length} msg:${msg.slice(0,60)}`); if (msg === 'CLOSE') { this.onWebsocketClosed(); @@ -349,37 +387,43 @@ class TCanvasPainter extends TPadPainter { // This is snapshot, produced with TWebCanvas const p1 = msg.indexOf(':', 6), version = msg.slice(6, p1), - snap = parse(msg.slice(p1+1)); + snap = parse(msg.slice(p1 + 1)); this.syncDraw(true) .then(() => { - if (!this.snapid) + if (!this.getSnapId()) this.resizeBrowser(snap.fSnapshot.fWindowWidth, snap.fSnapshot.fWindowHeight); - if (!this.snapid && isFunc(this.setFixedCanvasSize)) - this._online_fixed_size = this.setFixedCanvasSize(snap.fSnapshot.fCw, snap.fSnapshot.fCh, snap.fFixedSize); + if (!this.getSnapId() && isFunc(this.setFixedCanvasSize)) + this.#online_fixed_size = this.setFixedCanvasSize(snap.fSnapshot.fCw, snap.fSnapshot.fCh, snap.fFixedSize); }) .then(() => this.redrawPadSnap(snap)) .then(() => { this.completeCanvasSnapDrawing(); - let ranges = this.getWebPadOptions(); // all data, including subpads - if (ranges) ranges = ':' + ranges; + let ranges = this.getWebPadOptions(); // all data, including sub-pads + if (ranges) + ranges = ':' + ranges; handle.send(`READY6:${version}${ranges}`); // send ready message back when drawing completed this.confirmDraw(); + }).catch(err => { + if (isFunc(this.showConsoleError)) + this.showConsoleError(err); + else + console.log(err); }); } else if (msg.slice(0, 5) === 'MENU:') { // this is menu with exact identifier for object const lst = parse(msg.slice(5)); - if (isFunc(this._getmenu_callback)) { - this._getmenu_callback(lst); - delete this._getmenu_callback; + if (isFunc(this.#getmenu_callback)) { + this.#getmenu_callback(lst); + this.#getmenu_callback = undefined; } } else if (msg.slice(0, 4) === 'CMD:') { msg = msg.slice(4); const p1 = msg.indexOf(':'), cmdid = msg.slice(0, p1), - cmd = msg.slice(p1+1), + cmd = msg.slice(p1 + 1), reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG')) { + if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { this.createImage(cmd.toLowerCase()) .then(res => handle.send(reply + res)); } else { @@ -405,7 +449,7 @@ class TCanvasPainter extends TPadPainter { resized = true; } if (ctrl.cw && ctrl.ch && isFunc(this.setFixedCanvasSize)) { - this._online_fixed_size = this.setFixedCanvasSize(Number.parseInt(ctrl.cw), Number.parseInt(ctrl.ch), true); + this.#online_fixed_size = this.setFixedCanvasSize(Number.parseInt(ctrl.cw), Number.parseInt(ctrl.ch), true); resized = true; } const kinds = ['Menu', 'StatusBar', 'Editor', 'ToolBar', 'ToolTips']; @@ -438,17 +482,18 @@ class TCanvasPainter extends TPadPainter { /** @summary Send RESIZED message to client to inform about changes in canvas/window geometry * @private */ sendResized(force) { - if (!this.pad || (typeof window === 'undefined')) + const pad = this.getRootPad(); + if (!pad || (typeof window === 'undefined')) return; const cw = this.getPadWidth(), ch = this.getPadHeight(), wx = window.screenLeft, wy = window.screenTop, ww = window.outerWidth, wh = window.outerHeight, - fixed = this._online_fixed_size ? 1 : 0; + fixed = this.#online_fixed_size ? 1 : 0; if (!force) { - force = (cw > 0) && (ch > 0) && ((this.pad.fCw !== cw) || (this.pad.fCh !== ch)); + force = (cw > 0) && (ch > 0) && ((pad.fCw !== cw) || (pad.fCh !== ch)); if (force) { - this.pad.fCw = cw; - this.pad.fCh = ch; + pad.fCw = cw; + pad.fCh = ch; } } if (force) @@ -494,23 +539,25 @@ class TCanvasPainter extends TPadPainter { /** @summary Show online canvas status * @private */ showCanvasStatus(...msgs) { - if (this.testUI5()) return; + if (this.testUI5()) + return; const br = this.brlayout || getHPainter()?.brlayout; - br?.showStatus(...msgs); } /** @summary Returns true if GED is present on the canvas */ hasGed() { - if (this.testUI5()) return false; + if (this.testUI5()) + return false; return this.brlayout?.hasContent() ?? false; } /** @summary Function used to de-activate GED * @private */ removeGed() { - if (this.testUI5()) return; + if (this.testUI5()) + return; this.registerForPadEvents(null); @@ -586,7 +633,6 @@ class TCanvasPainter extends TPadPainter { objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - console.log('activate GED'); this.processChanges('sbits', this); resolveFunc(true); @@ -604,44 +650,69 @@ class TCanvasPainter extends TPadPainter { switch (that) { case 'Menu': break; case 'StatusBar': this.activateStatusBar(on); break; - case 'Editor': return this.activateGed(this, null, !!on); + case 'Editor': return this.activateGed(this, null, on); case 'ToolBar': break; case 'ToolTips': this.setTooltipAllowed(on); break; } return true; } + /** @summary Send command to start fit panel code on the server + * @private */ + startFitPanel(standalone) { + if (!this.getWebsocket()) + return false; + + const new_conn = standalone ? null : this.getWebsocket().createChannel(); + + this.sendWebsocket('FITPANEL:' + (standalone ? 'standalone' : new_conn.getChannelId())); + + return new_conn; + } + /** @summary Complete handling of online canvas drawing * @private */ completeCanvasSnapDrawing() { - if (!this.pad) return; + const pad = this.getRootPad(); + if (!pad) + return; this.addPadInteractive(); - if ((typeof document !== 'undefined') && !this.embed_canvas && this._websocket) - document.title = this.pad.fTitle; + if ((typeof document !== 'undefined') && !this.embed_canvas && this.getWebsocket()) + document.title = pad.fTitle; + + if (this.#all_sections_showed) + return; + this.#all_sections_showed = true; + + // used in Canvas.controller.js to avoid browser resize because of initial sections show/hide + this._ignore_section_resize = true; - if (this._all_sections_showed) return; - this._all_sections_showed = true; - this.showSection('Menu', this.pad.TestBit(kMenuBar)); - this.showSection('StatusBar', this.pad.TestBit(kShowEventStatus)); - this.showSection('ToolBar', this.pad.TestBit(kShowToolBar)); - this.showSection('Editor', this.pad.TestBit(kShowEditor)); - this.showSection('ToolTips', this.pad.TestBit(kShowToolTips) || this._highlight_connect); + this.showSection('Menu', pad.TestBit(kMenuBar)); + this.showSection('StatusBar', pad.TestBit(kShowEventStatus)); + this.showSection('ToolBar', pad.TestBit(kShowToolBar)); + this.showSection('Editor', pad.TestBit(kShowEditor)); + this.showSection('ToolTips', pad.TestBit(kShowToolTips) || this._highlight_connect); + + this._ignore_section_resize = false; } /** @summary Handle highlight in canvas - deliver information to server * @private */ processHighlightConnect(hints) { - if (!hints || hints.length === 0 || !this._highlight_connect || - !this._websocket || this.doingDraw() || !this._websocket.canSend(2)) return; + if (!hints?.length || !this._highlight_connect || + this.doingDraw() || !this.canSendWebsocket(2)) + return; const hint = hints[0] || hints[1]; - if (!hint || !hint.painter || !hint.painter.snapid || !hint.user_info) return; + if (!hint || !hint.painter?.getSnapId() || !hint.user_info) + return; const pp = hint.painter.getPadPainter() || this; - if (!pp.snapid) return; + if (!pp.getSnapId()) + return; - const arr = [pp.snapid, hint.painter.snapid, '0', '0']; + const arr = [pp.getSnapId(), hint.painter.getSnapId(), '0', '0']; if ((hint.user_info.binx !== undefined) && (hint.user_info.biny !== undefined)) { arr[2] = hint.user_info.binx.toString(); @@ -649,11 +720,10 @@ class TCanvasPainter extends TPadPainter { } else if (hint.user_info.bin !== undefined) arr[2] = hint.user_info.bin.toString(); - const msg = JSON.stringify(arr); - if (this._last_highlight_msg !== msg) { - this._last_highlight_msg = msg; + if (this.#last_highlight_msg !== msg) { + this.#last_highlight_msg = msg; this.sendWebsocket(`HIGHLIGHT:${msg}`); } } @@ -663,10 +733,12 @@ class TCanvasPainter extends TPadPainter { * @private */ processChanges(kind, painter, subelem) { // check if we could send at least one message more - for some meaningful actions - if (!this._websocket || this._readonly || !this._websocket.canSend(2) || !isStr(kind)) return; + if (this.isReadonly() || !this.canSendWebsocket(2) || !isStr(kind)) + return; let msg = ''; - if (!painter) painter = this; + if (!painter) + painter = this; switch (kind) { case 'sbits': msg = 'STATUSBITS:' + this.getStatusBits(); @@ -682,21 +754,22 @@ class TCanvasPainter extends TPadPainter { msg = 'OPTIONS6:' + painter.getWebPadOptions('with_subpads'); break; case 'drawopt': - if (painter.snapid) - msg = 'DRAWOPT:' + JSON.stringify([painter.snapid.toString(), painter.getDrawOpt() || '']); + if (painter.getSnapId()) + msg = 'DRAWOPT:' + JSON.stringify([painter.getSnapId(), painter.getDrawOpt() || '']); break; case 'pave_moved': { const info = createWebObjectOptions(painter); - if (info) msg = 'PRIMIT6:' + toJSON(info); + if (info) + msg = 'PRIMIT6:' + toJSON(info); break; } case 'logx': case 'logy': case 'logz': { - const pp = painter.getPadPainter(); - - if (pp?.snapid && pp?.pad) { - const name = 'SetLog' + kind[3], value = pp.pad['fLog' + kind[3]]; + const pp = painter.getPadPainter(), + pad = pp?.getRootPad(); + if (pp?.getSnapId() && pad) { + const name = 'SetLog' + kind[3], value = pad['fLog' + kind[3]]; painter = pp; kind = `exec:${name}(${value})`; } @@ -707,43 +780,47 @@ class TCanvasPainter extends TPadPainter { if (!msg && isFunc(painter?.getSnapId) && (kind.slice(0, 5) === 'exec:')) { const snapid = painter.getSnapId(subelem); if (snapid) { - msg = 'PRIMIT6:' + toJSON({ _typename: 'TWebObjectOptions', - snapid, opt: kind.slice(5), fcust: 'exec', fopt: [] }); + msg = 'PRIMIT6:' + toJSON({ + _typename: 'TWebObjectOptions', + snapid, opt: kind.slice(5), fcust: 'exec', fopt: [] + }); } } if (msg) { // console.log(`Sending ${msg.length} ${msg.slice(0,40)}`); - this._websocket.send(msg); + this.sendWebsocket(msg); } else console.log(`Unprocessed changes ${kind} for painter of ${painter?.getObject()?._typename} subelem ${subelem}`); } /** @summary Select active pad on the canvas */ selectActivePad(pad_painter, obj_painter, click_pos) { - if (!this.snapid || !pad_painter) return; // only interactive canvas + if (!this.getSnapId() || !pad_painter) + return; // only interactive canvas let arg = null, ischanged = false; const is_button = pad_painter.matchObjectType(clTButton); - if (pad_painter.snapid && this._websocket) - arg = { _typename: 'TWebPadClick', padid: pad_painter.snapid.toString(), objid: '', x: -1, y: -1, dbl: false }; + if (pad_painter.getSnapId() && this.getWebsocket()) + arg = { _typename: 'TWebPadClick', padid: pad_painter.getSnapId(), objid: '', x: -1, y: -1, dbl: false }; if (!pad_painter.is_active_pad && !is_button) { ischanged = true; this.forEachPainterInPad(pp => pp.drawActiveBorder(null, pp === pad_painter), 'pads'); } - if ((obj_painter?.snapid !== undefined) && arg) { + if (obj_painter?.hasSnapId() && arg) { ischanged = true; - arg.objid = obj_painter.snapid.toString(); + arg.objid = obj_painter.getSnapId(); } if (click_pos && arg) { ischanged = true; arg.x = Math.round(click_pos.x || 0); arg.y = Math.round(click_pos.y || 0); - if (click_pos.dbl) arg.dbl = true; + if (click_pos.dbl) + arg.dbl = true; } if (arg && (ischanged || is_button)) @@ -753,65 +830,81 @@ class TCanvasPainter extends TPadPainter { /** @summary Return actual TCanvas status bits */ getStatusBits() { let bits = 0; - if (this.hasEventStatus()) bits |= kShowEventStatus; - if (this.hasGed()) bits |= kShowEditor; - if (this.isTooltipAllowed()) bits |= kShowToolTips; - if (this.use_openui) bits |= kMenuBar; + if (this.hasEventStatus()) + bits |= kShowEventStatus; + if (this.hasGed()) + bits |= kShowEditor; + if (this.isTooltipAllowed()) + bits |= kShowToolTips; + if (this.use_openui) + bits |= kMenuBar; return bits; } /** @summary produce JSON for TCanvas, which can be used to display canvas once again */ - produceJSON() { - const canv = this.getObject(), - fill0 = (canv.fFillStyle === 0), - axes = []; + produceJSON(spacing) { + const canv = this.getObject(); - if (fill0) canv.fFillStyle = 1001; + if (canv._typename !== clTCanvas) + return; + + const fill0 = (canv.fFillStyle === 0), + axes = [], hists = [], prims = []; + + if (fill0) + canv.fFillStyle = 1001; - // write selected range into TAxis properties this.forEachPainterInPad(pp => { + const pad = pp.getRootPad(true); + if (pp.getNumPainters() && pad?.fPrimitives && !pad.fPrimitives.arr.length) { + // create list of primitives when missing + prims.push(pad.fPrimitives); + pp.forEachPainterInPad(p => { + // ignore all secondary painters + if (p.isSecondary()) + return; + const subobj = p.getObject(); + if (subobj?._typename) + pad.fPrimitives.Add(subobj, p.getDrawOpt()); + }, 'objects'); + } + const main = pp.getMainPainter(), fp = pp.getFramePainter(); - if (!isFunc(main?.getHisto) || !isFunc(main?.getDimension)) return; + if (!isFunc(main?.getHisto) || !isFunc(main?.getDimension)) + return; + // write selected range into TAxis properties const hist = main.getHisto(), ndim = main.getDimension(); - if (!hist?.fXaxis) return; + if (!hist?.fXaxis) + return; const setAxisRange = (name, axis) => { if (fp?.zoomChangedInteractive(name)) { axes.push({ axis, f: axis.fFirst, l: axis.fLast, b: axis.fBits }); - axis.fFirst = main.getSelectIndex(name, 'left'); + axis.fFirst = main.getSelectIndex(name, 'left', 1); axis.fLast = main.getSelectIndex(name, 'right'); - const has_range = (axis.fFirst > 0) || (axis.fLast < axis.fNbins); - if (has_range !== axis.TestBit(EAxisBits.kAxisRange)) - axis.InvertBit(EAxisBits.kAxisRange); + axis.SetBit(EAxisBits.kAxisRange, (axis.fFirst > 0) || (axis.fLast < axis.fNbins)); } }; setAxisRange('x', hist.fXaxis); - if (ndim > 1) setAxisRange('y', hist.fYaxis); - if (ndim > 2) setAxisRange('z', hist.fZaxis); + if (ndim > 1) + setAxisRange('y', hist.fYaxis); + if (ndim > 2) + setAxisRange('z', hist.fZaxis); + if ((ndim === 2) && fp?.zoomChangedInteractive('z')) { + hists.push({ hist, min: hist.fMinimum, max: hist.fMaximum }); + hist.fMinimum = fp.zoom_zmin ?? fp.zmin; + hist.fMaximum = fp.zoom_zmax ?? fp.zmax; + } }, 'pads'); - if (!this.normal_canvas) { - // fill list of primitives from painters - this.forEachPainterInPad(p => { - // ignore all secondary painters - if (p.isSecondary()) - return; - const subobj = p.getObject(); - if (subobj?._typename) - canv.fPrimitives.Add(subobj, p.getDrawOpt()); - }, 'objects'); - } - - // const fp = this.getFramePainter(); - // fp?.setRootPadRange(this.getRootPad()); + const res = toJSON(canv, spacing); - const res = toJSON(canv); - - if (fill0) canv.fFillStyle = 0; + if (fill0) + canv.fFillStyle = 0; axes.forEach(e => { e.axis.fFirst = e.f; @@ -819,8 +912,12 @@ class TCanvasPainter extends TPadPainter { e.axis.fBits = e.b; }); - if (!this.normal_canvas) - canv.fPrimitives.Clear(); + hists.forEach(e => { + e.hist.fMinimum = e.min; + e.hist.fMaximum = e.max; + }); + + prims.forEach(lst => lst.Clear()); return res; } @@ -830,39 +927,61 @@ class TCanvasPainter extends TPadPainter { if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) return; - // workaround for qt5-based display where inner window size is used - if (browser.qt5 && fullW > 100 && fullH > 60) { + // workaround for qt-based display where inner window size is used + if (browser.qt6 && fullW > 100 && fullH > 60) { fullW -= 3; fullH -= 30; } - this._websocket?.resizeWindow(fullW, fullH); + this.getWebsocket()?.resizeWindow(fullW, fullH); + } + + /** @summary create three.js object for TCanvas */ + static async build3d(can, opt, get_painter) { + const painter = new TCanvasPainter(null, can, opt, true); + painter.checkSpecialsInPrimitives(can, true); + + const fp = new TFramePainter(null, null); + // return dummy frame painter as result + painter.getFramePainter = () => fp; + + return painter.drawPrimitives().then(() => { + return get_painter ? painter : fp.create3DScene(-1, true); + }); } /** @summary draw TCanvas */ static async draw(dom, can, opt) { const nocanvas = !can; - if (nocanvas) can = create(clTCanvas); - - const painter = new TCanvasPainter(dom, can); - painter.checkSpecialsInPrimitives(can); - - if (!nocanvas && can.fCw && can.fCh && !painter.isBatchMode()) { - const rect0 = painter.selectDom().node().getBoundingClientRect(); - if (!rect0.height && (rect0.width > 0.1*can.fCw)) { - painter.selectDom().style('width', can.fCw+'px').style('height', can.fCh+'px'); - painter._fixed_size = true; + if (nocanvas) + can = create(clTCanvas); + + const painter = new TCanvasPainter(dom, can, opt, nocanvas ? 'auto' : true); + painter.checkSpecialsInPrimitives(can, true); + + if (!nocanvas && can.fCw && can.fCh) { + const d = painter.selectDom(); + let apply_size; + if (!painter.isBatchMode()) { + const rect0 = d.node().getBoundingClientRect(); + apply_size = !rect0.height && (rect0.width > 0.1 * can.fCw); + } else { + const arg = d.property('_batch_use_canvsize'); + apply_size = arg || (arg === undefined); + } + if (apply_size) { + d.style('width', can.fCw + 'px').style('height', can.fCh + 'px') + .attr('width', can.fCw).attr('height', can.fCh); + painter._setFixedSize(true); } } - painter.decodeOptions(opt); - painter.normal_canvas = !nocanvas; painter.createCanvasSvg(0); painter.addPadButtons(); if (nocanvas && opt.indexOf('noframe') < 0) - directDrawTFrame(dom, null); + directDrawTFrame(painter, null); // select global reference - required for keys handling selectActivePad({ pp: painter, active: true }); @@ -887,25 +1006,39 @@ async function ensureTCanvas(painter, frame_kind) { // simple check - if canvas there, can use painter const noframe = (frame_kind === false) || (frame_kind === '3d') ? 'noframe' : '', - promise = painter.getCanvSvg().empty() - ? TCanvasPainter.draw(painter.getDom(), null, noframe) - : Promise.resolve(true); - - return promise.then(() => { - if ((frame_kind !== false) && painter.getFrameSvg().selectChild('.main_layer').empty() && !painter.getFramePainter()) - directDrawTFrame(painter.getDom(), null, frame_kind); - - painter.addToPadPrimitives(); + createCanv = () => { + if ((noframe !== 'noframe') || !isFunc(painter.getUserRanges)) + return null; + const ranges = painter.getUserRanges(); + if (!ranges) + return null; + const canv = create(clTCanvas), + dx = (ranges.maxx - ranges.minx) || 1, + dy = (ranges.maxy - ranges.miny) || 1; + canv.fX1 = ranges.minx - dx * gStyle.fPadLeftMargin; + canv.fX2 = ranges.maxx + dx * gStyle.fPadRightMargin; + canv.fY1 = ranges.miny - dy * gStyle.fPadBottomMargin; + canv.fY2 = ranges.maxy + dy * gStyle.fPadTopMargin; + return canv; + }, + pad_painter = painter.getPadPainter() || getDomCanvasPainter(painter.selectDom()), + promise = pad_painter ? Promise.resolve(pad_painter) : + TCanvasPainter.draw(painter.getDom(), createCanv(), noframe); + + return promise.then(pp => { + if ((frame_kind !== false) && pp.getFrameSvg().selectChild('.main_layer').empty() && !pp.getFramePainter()) + directDrawTFrame(pp, null, frame_kind); + + painter.addToPadPrimitives(pp); return painter; }); } /** @summary draw TPad snapshot from TWebCanvas * @private */ -async function drawTPadSnapshot(dom, snap /*, opt */) { +async function drawTPadSnapshot(dom, snap, opt) { const can = create(clTCanvas), - painter = new TCanvasPainter(dom, can); - painter.normal_canvas = false; + painter = new TCanvasPainter(dom, can, opt); painter.addPadButtons(); return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { @@ -923,4 +1056,6 @@ async function drawTFrame(dom, obj, opt) { return ensureTCanvas(fp, false).then(() => fp.redraw()); } +Object.assign(internals.jsroot, { ensureTCanvas, TPadPainter, TCanvasPainter }); + export { ensureTCanvas, drawTPadSnapshot, drawTFrame, TPadPainter, TCanvasPainter }; diff --git a/modules/gpad/TFramePainter.mjs b/modules/gpad/TFramePainter.mjs index 0170bdfe3..f7930fc56 100644 --- a/modules/gpad/TFramePainter.mjs +++ b/modules/gpad/TFramePainter.mjs @@ -1,11 +1,13 @@ -import { gStyle, settings, isFunc, isStr, postponePromise, browser, clTAxis, kNoZoom } from '../core.mjs'; -import { select as d3_select, pointer as d3_pointer, pointers as d3_pointers, drag as d3_drag } from '../d3.mjs'; -import { getElementRect, getAbsPosInCanvas, makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; +import { gStyle, settings, internals, isFunc, isStr, postponePromise, browser, + clTAxis, clTFrame, kNoZoom, urlClassPrefix } from '../core.mjs'; +import { select as d3_select, pointer as d3_pointer, + pointers as d3_pointers, drag as d3_drag, rgb as d3_rgb } from '../d3.mjs'; +import { getElementRect, getAbsPosInCanvas, makeTranslate, addHighlightStyle, getBoxDecorations } from '../base/BasePainter.mjs'; import { getActivePad, ObjectPainter, EAxisBits, kAxisLabels } from '../base/ObjectPainter.mjs'; import { getSvgLineStyle } from '../base/TAttLineHandler.mjs'; import { TAxisPainter } from './TAxisPainter.mjs'; import { FontHandler } from '../base/FontHandler.mjs'; -import { createMenu, closeMenu, showPainterMenu } from '../gui/menu.mjs'; +import { createMenu, closeMenu, showPainterMenu, hasMenu } from '../gui/menu.mjs'; import { detectRightButton } from '../gui/utils.mjs'; @@ -14,7 +16,8 @@ const logminfactorX = 0.0001, logminfactorY = 3e-4; /** @summary Configure tooltip enable flag for painter * @private */ function setPainterTooltipEnabled(painter, on) { - if (!painter) return; + if (!painter) + return; const fp = painter.getFramePainter(); if (isFunc(fp?.setTooltipEnabled)) { @@ -38,37 +41,38 @@ function getEarthProjectionFunc(id) { switch (id) { // Aitoff2xy case 1: return (l, b) => { - const DegToRad = Math.PI/180, - alpha2 = (l/2)*DegToRad, - delta = b*DegToRad, + const DegToRad = Math.PI / 180, + alpha2 = (l / 2) * DegToRad, + delta = b * DegToRad, r2 = Math.sqrt(2), - f = 2*r2/Math.PI, + f = 2 * r2 / Math.PI, cdec = Math.cos(delta), - denom = Math.sqrt(1.0 + cdec*Math.cos(alpha2)); + denom = Math.sqrt(1.0 + cdec * Math.cos(alpha2)); return { - x: cdec*Math.sin(alpha2)*2.0*r2/denom/f/DegToRad, - y: Math.sin(delta)*r2/denom/f/DegToRad + x: cdec * Math.sin(alpha2) * 2.0 * r2 / denom / f / DegToRad, + y: Math.sin(delta) * r2 / denom / f / DegToRad }; }; // mercator - case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI/2 + b/180*Math.PI)/2)) }; }; + case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI / 2 + b / 180 * Math.PI) / 2)) }; }; // sinusoidal - case 3: return (l, b) => { return { x: l*Math.cos(b/180*Math.PI), y: b }; }; + case 3: return (l, b) => { return { x: l * Math.cos(b / 180 * Math.PI), y: b }; }; // parabolic - case 4: return (l, b) => { return { x: l*(2.0*Math.cos(2*b/180*Math.PI/3) - 1), y: 180*Math.sin(b/180*Math.PI/3) }; }; + case 4: return (l, b) => { return { x: l * (2.0 * Math.cos(2 * b / 180 * Math.PI / 3) - 1), y: 180 * Math.sin(b / 180 * Math.PI / 3) }; }; // Mollweide projection case 5: return (l, b) => { - const theta0 = b * Math.PI/180; + const theta0 = b * Math.PI / 180; let theta = theta0, num, den; for (let i = 0; i < 100; i++) { num = 2 * theta + Math.sin(2 * theta) - Math.PI * Math.sin(theta0); - den = 4 * (Math.cos(theta)**2); + den = 4 * (Math.cos(theta) ** 2); if (den < 1e-20) { theta = theta0; break; } theta -= num / den; - if (Math.abs(num / den) < 1e-4) break; + if (Math.abs(num / den) < 1e-4) + break; } return { x: l * Math.cos(theta), @@ -83,14 +87,15 @@ function getEarthProjectionFunc(id) { * axis range can be wider. Or for normal histogram drawing when preselected range smaller than histogram range * @private */ function unzoomHistogramYRange(main) { - if (!isFunc(main?.getDimension) || main.getDimension() !== 1) return; + if (!isFunc(main?.getDimension) || main.getDimension() !== 1) + return; - const ymin = main.draw_content ? main.hmin : main.ymin, - ymax = main.draw_content ? main.hmax : main.ymax; + const ymin = main.draw_content ? main.hmin : main.ymin, + ymax = main.draw_content ? main.hmax : main.ymax; - if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && + if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && (ymin <= main.zoom_ymin) && (main.zoom_ymax <= ymax)) - main.zoom_ymin = main.zoom_ymax = 0; + main.zoom_ymin = main.zoom_ymax = 0; } // global, allow single drag at once @@ -105,39 +110,44 @@ function is_dragging(painter, kind) { /** @summary Add drag for interactive rectangular elements for painter * @private */ function addDragHandler(_painter, arg) { - if (!settings.MoveResize) return; + if (!settings.MoveResize) + return; const painter = _painter, pp = painter.getPadPainter(); - if (pp?._fast_drawing || pp?.isBatchMode()) return; - // cleanup all drag elements when canvas is not ediatable + if (pp?.isFastDrawing() || pp?.isBatchMode()) + return; + // cleanup all drag elements when canvas is not editable if (pp?.isEditable() === false) arg.cleanup = true; if (!isFunc(arg.getDrawG)) - arg.getDrawG = () => painter?.draw_g; + arg.getDrawG = () => painter?.getG(); function makeResizeElements(group, handler) { function addElement(cursor, d) { const clname = 'js_' + cursor.replace(/[-]/g, '_'); let elem = group.selectChild('.' + clname); - if (arg.cleanup) return elem.remove(); - if (elem.empty()) elem = group.append('path').classed(clname, true); + if (arg.cleanup) + return elem.remove(); + if (elem.empty()) + elem = group.append('path').classed(clname, true); elem.style('opacity', 0).style('cursor', cursor).attr('d', d); - if (handler) elem.call(handler); + if (handler) + elem.call(handler); } addElement('nw-resize', 'M2,2h15v-5h-20v20h5Z'); - addElement('ne-resize', `M${arg.width-2},2h-15v-5h20v20h-5 Z`); - addElement('sw-resize', `M2,${arg.height-2}h15v5h-20v-20h5Z`); - addElement('se-resize', `M${arg.width-2},${arg.height-2}h-15v5h20v-20h-5Z`); + addElement('ne-resize', `M${arg.width - 2},2h-15v-5h20v20h-5 Z`); + addElement('sw-resize', `M2,${arg.height - 2}h15v5h-20v-20h5Z`); + addElement('se-resize', `M${arg.width - 2},${arg.height - 2}h-15v5h20v-20h-5Z`); if (!arg.no_change_x) { - addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height-2*18)}h-5Z`); - addElement('e-resize', `M${arg.width+3},18h-5v${Math.max(0, arg.height-2*18)}h5Z`); + addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height - 2 * 18)}h-5Z`); + addElement('e-resize', `M${arg.width + 3},18h-5v${Math.max(0, arg.height - 2 * 18)}h5Z`); } if (!arg.no_change_y) { - addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width-2*18)}v-5Z`); - addElement('s-resize', `M18,${arg.height+3}v-5h${Math.max(0, arg.width-2*18)}v5Z`); + addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width - 2 * 18)}v-5Z`); + addElement('s-resize', `M18,${arg.height + 3}v-5h${Math.max(0, arg.width - 2 * 18)}v5Z`); } } @@ -156,13 +166,18 @@ function addDragHandler(_painter, arg) { const oldx = arg.x, oldy = arg.y; - if (arg.minwidth && newwidth < arg.minwidth) newwidth = arg.minwidth; - if (arg.minheight && newheight < arg.minheight) newheight = arg.minheight; + if (arg.minwidth && newwidth < arg.minwidth) + newwidth = arg.minwidth; + if (arg.minheight && newheight < arg.minheight) + newheight = arg.minheight; const change_size = (newwidth !== arg.width) || (newheight !== arg.height), change_pos = (newx !== oldx) || (newy !== oldy); - arg.x = newx; arg.y = newy; arg.width = newwidth; arg.height = newheight; + arg.x = newx; + arg.y = newy; + arg.width = newwidth; + arg.height = newheight; if (!arg.no_transform) makeTranslate(draw_g, newx, newy); @@ -185,7 +200,7 @@ function addDragHandler(_painter, arg) { arg.obj.fX2NDC = (newx + newwidth) / rect.width; arg.obj.fY1NDC = 1 - (newy + newheight) / rect.height; arg.obj.fY2NDC = 1 - newy / rect.height; - arg.obj.modified_NDC = true; // indicate that NDC was interactively changed, block in updated + arg.obj.$modifiedNDC = true; // indicate that NDC was interactively changed, block in updated } else if (isFunc(arg.move_resize)) arg.move_resize(newx, newy, newwidth, newheight); @@ -195,16 +210,16 @@ function addDragHandler(_painter, arg) { } return change_size || change_pos; - }, - drag_move = d3_drag().subject(Object), - drag_move_off = d3_drag().subject(Object); + }, drag_move = d3_drag().subject(Object), drag_move_off = d3_drag().subject(Object); drag_move_off.on('start', null).on('drag', null).on('end', null); drag_move - .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) return; - if (isFunc(arg.is_disabled) && arg.is_disabled('move')) return; + .on('start', evnt => { + if (detectRightButton(evnt.sourceEvent) || drag_kind) + return; + if (isFunc(arg.is_disabled) && arg.is_disabled('move')) + return; closeMenu(); // close menu setPainterTooltipEnabled(painter, false); // disable tooltip @@ -230,8 +245,9 @@ function addDragHandler(_painter, arg) { .style('pointer-events', 'none') // let forward double click to underlying elements .property('drag_handle', handle) .call(addHighlightStyle, true); - }).on('drag', function(evnt) { - if (!is_dragging(painter, 'move')) return; + }).on('drag', evnt => { + if (!is_dragging(painter, 'move')) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); @@ -247,8 +263,9 @@ function addDragHandler(_painter, arg) { handle.y = Math.min(Math.max(handle.acc_y1, 0), handle.pad_h); drag_rect.attr('d', `M${handle.x},${handle.y}${handle.path}`); - }).on('end', function(evnt) { - if (!is_dragging(painter, 'move')) return; + }).on('end', evnt => { + if (!is_dragging(painter, 'move')) + return; evnt.sourceEvent.stopPropagation(); evnt.sourceEvent.preventDefault(); @@ -269,8 +286,10 @@ function addDragHandler(_painter, arg) { drag_resize .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) return; - if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) return; + if (detectRightButton(evnt.sourceEvent) || drag_kind) + return; + if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) + return; closeMenu(); // close menu setPainterTooltipEnabled(painter, false); // disable tooltip @@ -297,26 +316,29 @@ function addDragHandler(_painter, arg) { .property('drag_handle', handle) .call(addHighlightStyle, true); }).on('drag', function(evnt) { - if (!is_dragging(painter, 'resize')) return; + if (!is_dragging(painter, 'resize')) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); const handle = drag_rect.property('drag_handle'), - elem = d3_select(this); - let dx = evnt.dx, dy = evnt.dy; - - if (arg.no_change_x) dx = 0; - if (arg.no_change_y) dy = 0; + elem = d3_select(this), + dx = arg.no_change_x ? 0 : evnt.dx, + dy = arg.no_change_y ? 0 : evnt.dy; if (elem.classed('js_nw_resize')) { - handle.acc_x1 += dx; handle.acc_y1 += dy; + handle.acc_x1 += dx; + handle.acc_y1 += dy; } else if (elem.classed('js_ne_resize')) { - handle.acc_x2 += dx; handle.acc_y1 += dy; + handle.acc_x2 += dx; + handle.acc_y1 += dy; } else if (elem.classed('js_sw_resize')) { - handle.acc_x1 += dx; handle.acc_y2 += dy; + handle.acc_x1 += dx; + handle.acc_y2 += dy; } else if (elem.classed('js_se_resize')) { - handle.acc_x2 += dx; handle.acc_y2 += dy; + handle.acc_x2 += dx; + handle.acc_y2 += dy; } else if (elem.classed('js_w_resize')) handle.acc_x1 += dx; else if (elem.classed('js_n_resize')) @@ -335,8 +357,9 @@ function addDragHandler(_painter, arg) { handle.height = Math.abs(y2 - y1); drag_rect.attr('x', handle.x).attr('y', handle.y).attr('width', handle.width).attr('height', handle.height); - }).on('end', function(evnt) { - if (!is_dragging(painter, 'resize')) return; + }).on('end', evnt => { + if (!is_dragging(painter, 'resize')) + return; evnt.sourceEvent.preventDefault(); @@ -352,33 +375,37 @@ function addDragHandler(_painter, arg) { makeResizeElements(arg.getDrawG(), drag_resize); } -const TooltipHandler = { +/** @summary Tooltip handler class + * @private */ +class TooltipHandler extends ObjectPainter { + + // cannot use private members because of RFramePainter /** @desc only canvas info_layer can be used while other pads can overlay * @return layer where frame tooltips are shown */ hints_layer() { return this.getCanvPainter()?.getLayerSvg('info_layer') ?? d3_select(null); - }, + } /** @return true if tooltip is shown, use to prevent some other action */ isTooltipShown() { if (!this.tooltip_enabled || !this.isTooltipAllowed()) return false; const hintsg = this.hints_layer().selectChild('.objects_hints'); - return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadName(); - }, + return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadPainter()?.getPadName(); + } /** @summary set tooltips enabled on/off */ setTooltipEnabled(enabled) { if (enabled !== undefined) this.tooltip_enabled = enabled; - }, + } /** @summary central function which let show selected hints for the object */ processFrameTooltipEvent(pnt, evnt) { if (pnt?.handler) { // special use of interactive handler in the frame painter - const rect = this.draw_g?.selectChild('.main_layer'); + const rect = this.getG()?.selectChild('.main_layer'); if (!rect || rect.empty()) pnt = null; // disable else if (pnt.touch && evnt) { @@ -390,11 +417,13 @@ const TooltipHandler = { } } - let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false, textheight = 11; + let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false; const hmargin = 3, wmargin = 3, hstep = 1.2, frame_rect = this.getFrameRect(), pp = this.getPadPainter(), pad_width = pp?.getPadWidth(), + scale = pp?.getPadScale() ?? 1, + textheight = (pnt?.touch ? 15 : 11) * scale, font = new FontHandler(160, textheight), disable_tootlips = !this.isTooltipAllowed() || !this.tooltip_enabled; @@ -406,19 +435,18 @@ const TooltipHandler = { // collect tooltips from pad painter - it has list of all drawn objects const hints = pp?.processPadTooltipEvent(pnt) ?? []; - if (pp?._deliver_webcanvas_events && pp?.is_active_pad && pnt && isFunc(pp?.deliverWebCanvasEvent)) - pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints); - - if (pnt?.touch) textheight = 15; + if (pnt && frame_rect) + pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints ? hints[0]?.painter?.getSnapId() : ''); for (let n = 0; n < hints.length; ++n) { const hint = hints[n]; - if (!hint) continue; + if (!hint) + continue; if (hint.user_info !== undefined) hint.painter?.provideUserTooltip(hint.user_info); - if (!hint.lines || (hint.lines.length === 0)) { + if (!hint.lines?.length) { hints[n] = null; continue; } @@ -427,23 +455,32 @@ const TooltipHandler = { for (let k = 0; k < n; ++k) { const hprev = hints[k]; let diff = false; - if (!hprev || (hprev.lines.length !== hint.lines.length)) continue; - for (let l = 0; l < hint.lines.length && !diff; ++l) - if (hprev.lines[l] !== hint.lines[l]) diff = true; - if (!diff) { hints[n] = null; break; } + if (!hprev || (hprev.lines.length !== hint.lines.length)) + continue; + for (let l = 0; l < hint.lines.length && !diff; ++l) { + if (hprev.lines[l] !== hint.lines[l]) + diff = true; + } + if (!diff) { + hints[n] = null; + break; + } } - if (!hints[n]) continue; + if (!hints[n]) + continue; nhints++; - if (hint.exact) nexact++; + if (hint.exact) + nexact++; hint.lines.forEach(line => { maxlen = Math.max(maxlen, line.length); }); hint.height = Math.round(hint.lines.length * textheight * hstep + 2 * hmargin - textheight * (hstep - 1)); if ((hint.color1 !== undefined) && (hint.color1 !== 'none')) { - if ((lastcolor1 !== 0) && (lastcolor1 !== hint.color1)) usecolor1 = true; + if (lastcolor1 && (lastcolor1 !== hint.color1)) + usecolor1 = true; lastcolor1 = hint.color1; } } @@ -466,35 +503,46 @@ const TooltipHandler = { coordinates = pnt ? Math.round(pnt.x) + ',' + Math.round(pnt.y) : ''; let hintsg = layer.selectChild('.objects_hints'), // group with all tooltips title = '', name = '', info = '', - hint = null, best_dist2 = 1e10, best_hint = null; + hint0 = null, best_dist2 = 1e10, best_hint = null; // try to select hint with exact match of the position when several hints available for (let k = 0; k < hints.length; ++k) { - if (!hints[k]) continue; - if (!hint) hint = hints[k]; + if (!hints[k]) + continue; + if (!hint0) + hint0 = hints[k]; // select exact hint if this is the only one - if (hints[k].exact && (nexact < 2) && (!hint || !hint.exact)) { hint = hints[k]; break; } + if (hints[k].exact && (nexact < 2) && (!hint0 || !hint0.exact)) { + hint0 = hints[k]; + break; + } - if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) continue; + if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) + continue; const dist2 = (pnt.x - hints[k].x) ** 2 + (pnt.y - hints[k].y) ** 2; - if (dist2 < best_dist2) { best_dist2 = dist2; best_hint = hints[k]; } + if (dist2 < best_dist2) { + best_dist2 = dist2; + best_hint = hints[k]; + } } - if ((!hint || !hint.exact) && (best_dist2 < 400)) hint = best_hint; + if ((!hint0 || !hint0.exact) && (best_dist2 < 400)) + hint0 = best_hint; - if (hint) { - name = (hint.lines && hint.lines.length > 1) ? hint.lines[0] : hint.name; - title = hint.title || ''; - info = hint.line; - if (!info && hint.lines) info = hint.lines.slice(1).join(' '); + if (hint0) { + name = (hint0.lines && hint0.lines.length > 1) ? hint0.lines[0] : hint0.name; + title = hint0.title || ''; + info = hint0.line; + if (!info && hint0.lines) + info = hint0.lines.slice(1).join(' '); } this.showObjectStatus(name, title, info, coordinates); // end of closing tooltips - if (!pnt || disable_tootlips || (hints.length === 0) || (maxlen === 0) || (show_only_best && !best_hint)) { + if (!pnt || disable_tootlips || !hints.length || (maxlen === 0) || (show_only_best && !best_hint)) { hintsg.remove(); return; } @@ -509,18 +557,18 @@ const TooltipHandler = { } let frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || ''; - if (!pp.iscan) { - frame_shift = getAbsPosInCanvas(this.getPadSvg(), frame_shift); + if (!pp?.isCanvas()) { + frame_shift = getAbsPosInCanvas(pp.getPadSvg(), frame_shift); trans = `translate(${frame_shift.x},${frame_shift.y}) ${trans}`; } // copy transform attributes from frame itself hintsg.attr('transform', trans) .property('last_point', pnt) - .property('hints_pad', this.getPadName()); + .property('hints_pad', pp.getPadName()); let viewmode = hintsg.property('viewmode') || '', - actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; + actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; if (show_only_best || (nhints === 1)) { viewmode = 'single'; @@ -577,20 +625,22 @@ const TooltipHandler = { .attr('opacity', 0) // use attribute, not style to make animation with d3.transition() .style('overflow', 'hidden') .style('pointer-events', 'none'); - } + } if (viewmode === 'single') curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y; - else { - for (let n = 0; (n < hints.length) && (gapy < maxhinty); ++n) { - const hint = hints[n]; - if (!hint) continue; - if ((hint.y >= gapy - 5) && (hint.y <= gapy + hint.height + 5)) { - gapy = hint.y + 10; - n = -1; + else { + for (let n2 = 0; (n2 < hints.length) && (gapy < maxhinty); ++n2) { + const hint2 = hints[n2]; + if (!hint2) + continue; + if ((hint2.y >= gapy - 5) && (hint2.y <= gapy + hint2.height + 5)) { + gapy = hint2.y + 10; + n2 = -1; } } - if ((gapminx === -1111) && (gapmaxx === -1111)) gapminx = gapmaxx = hint.x; + if ((gapminx === -1111) && (gapmaxx === -1111)) + gapminx = gapmaxx = hint.x; gapminx = Math.min(gapminx, hint.x); gapmaxx = Math.min(gapmaxx, hint.x); } @@ -638,7 +688,7 @@ const TooltipHandler = { .style('pointer-events', 'none') .call(font.func) .text(line), - box = getElementRect(txt, 'bbox'); + box = getElementRect(txt, 'bbox'); actualw = Math.max(actualw, box.width); } @@ -693,33 +743,31 @@ const TooltipHandler = { if (cp._highlight_connect && isFunc(cp.processHighlightConnect)) cp.processHighlightConnect(hints); - }, - - /** @summary Assigns tooltip methods */ - assign(painter) { - Object.assign(painter, this, { tooltip_enabled: true }); } -}, // TooltipHandler +} // class TooltipHandler -/** @summary Set of frame interactivity methods +/** @summary Frame interactivity class * @private */ +class FrameInteractive extends TooltipHandler { - FrameInteractive = { + // cannot use private members because of RFramePainter /** @summary Adding basic interactivity */ addBasicInteractivity() { - TooltipHandler.assign(this); + this.setTooltipEnabled(true); - if (!this._frame_rotate && !this._frame_fixpos) { - addDragHandler(this, { obj: this, x: this._frame_x, y: this._frame_y, width: this.getFrameWidth(), height: this.getFrameHeight(), - is_disabled: kind => { return (kind === 'move') && this.mode3d; }, - only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() }); + if (this.$can_drag) { + addDragHandler(this, { + obj: this, x: this.getFrameX(), y: this.getFrameY(), width: this.getFrameWidth(), height: this.getFrameHeight(), + is_disabled: kind => { return (kind === 'move') && this.mode3d; }, + only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() + }); } - const top_rect = this.draw_g.selectChild('path'), - main_svg = this.draw_g.selectChild('.main_layer'); + const top_rect = this.getG().selectChild('path'), + main_svg = this.getG().selectChild('.main_layer'); top_rect.style('pointer-events', 'visibleFill') // let process mouse events inside frame .style('cursor', 'default'); // show normal cursor @@ -728,12 +776,11 @@ const TooltipHandler = { .style('cursor', 'default') .property('handlers_set', 0); - const pp = this.getPadPainter(), - handlers_set = pp?._fast_drawing ? 0 : 1; + const handlers_set = this.getPadPainter()?.isFastDrawing() ? 0 : 1; if (main_svg.property('handlers_set') !== handlers_set) { - const close_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, null) : null, - mouse_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, { handler: true, touch: false }) : null; + const close_handler = handlers_set ? evnt => this.processFrameTooltipEvent(null, evnt) : null, + mouse_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: false }, evnt) : null; main_svg.property('handlers_set', handlers_set) .on('mouseenter', mouse_handler) @@ -741,7 +788,7 @@ const TooltipHandler = { .on('mouseleave', close_handler); if (browser.touches) { - const touch_handler = handlers_set ? this.processFrameTooltipEvent.bind(this, { handler: true, touch: true }) : null; + const touch_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: true }, evnt) : null; main_svg.on('touchstart', touch_handler) .on('touchmove', touch_handler) @@ -757,15 +804,17 @@ const TooltipHandler = { const hintsg = this.hints_layer().selectChild('.objects_hints'); // if tooltips were visible before, try to reconstruct them after short timeout - if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadName())) - setTimeout(this.processFrameTooltipEvent.bind(this, hintsg.property('last_point'), null), 10); - }, + if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadPainter()?.getPadName())) + setTimeout(() => this.processFrameTooltipEvent(hintsg.property('last_point'), null), 10); + } + + getFrameSvg() { return this.getPadPainter().getFrameSvg(); } /** @summary Add interactive handlers */ async addFrameInteractivity(for_second_axes) { const pp = this.getPadPainter(), svg = this.getFrameSvg(); - if (pp?._fast_drawing || svg.empty()) + if (pp?.isFastDrawing() || svg.empty()) return this; if (for_second_axes) { @@ -787,12 +836,14 @@ const TooltipHandler = { this.can_zoom_x = this.can_zoom_y = settings.Zooming; if (pp?.options) { - if (pp.options.NoZoomX) this.can_zoom_x = false; - if (pp.options.NoZoomY) this.can_zoom_y = false; + if (pp.options.NoZoomX) + this.can_zoom_x = false; + if (pp.options.NoZoomY) + this.can_zoom_y = false; } if (!svg.property('interactive_set')) { - this.addFrameKeysHandler(); + this.addKeysHandler(); this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches) this.zoom_rect = null; @@ -828,54 +879,79 @@ const TooltipHandler = { svg.property('interactive_set', true); return this; - }, - - /** @summary Add keys handler */ - addFrameKeysHandler() { - if (this.keys_handler || (typeof window === 'undefined')) return; - - this.keys_handler = evnt => this.processKeyPress(evnt); - - window.addEventListener('keydown', this.keys_handler, false); - }, + } /** @summary Handle key press */ processKeyPress(evnt) { + // no custom keys handling when menu is present + if (hasMenu()) + return true; + const allowed = ['PageUp', 'PageDown', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'PrintScreen', 'Escape', '*'], main = this.selectDom(), pp = this.getPadPainter(); let key = evnt.key; - if (!settings.HandleKeys || main.empty() || (this.enabledKeys === false) || - (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) return false; + if (!settings.HandleKeys || main.empty() || (this.isEnabledKeys() === false) || + (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) + return false; - if (evnt.shiftKey) key = `Shift ${key}`; - if (evnt.altKey) key = `Alt ${key}`; - if (evnt.ctrlKey) key = `Ctrl ${key}`; + if (evnt.shiftKey) + key = `Shift ${key}`; + if (evnt.altKey) + key = `Alt ${key}`; + if (evnt.ctrlKey) + key = `Ctrl ${key}`; const zoom = { name: 'x', dleft: 0, dright: 0 }; switch (key) { - case 'ArrowLeft': zoom.dleft = -1; zoom.dright = 1; break; - case 'ArrowRight': zoom.dleft = 1; zoom.dright = -1; break; - case 'Ctrl ArrowLeft': zoom.dleft = zoom.dright = -1; break; - case 'Ctrl ArrowRight': zoom.dleft = zoom.dright = 1; break; - case 'ArrowUp': zoom.name = 'y'; zoom.dleft = 1; zoom.dright = -1; break; - case 'ArrowDown': zoom.name = 'y'; zoom.dleft = -1; zoom.dright = 1; break; - case 'Ctrl ArrowUp': zoom.name = 'y'; zoom.dleft = zoom.dright = 1; break; - case 'Ctrl ArrowDown': zoom.name = 'y'; zoom.dleft = zoom.dright = -1; break; - case 'Escape': pp?.enlargePad(null, false, true); return true; + case 'ArrowLeft': + zoom.dleft = -1; + zoom.dright = 1; + break; + case 'ArrowRight': + zoom.dleft = 1; + zoom.dright = -1; + break; + case 'Ctrl ArrowLeft': + zoom.dleft = zoom.dright = -1; + break; + case 'Ctrl ArrowRight': + zoom.dleft = zoom.dright = 1; + break; + case 'ArrowUp': + zoom.name = 'y'; + zoom.dleft = 1; + zoom.dright = -1; + break; + case 'ArrowDown': + zoom.name = 'y'; + zoom.dleft = -1; + zoom.dright = 1; + break; + case 'Ctrl ArrowUp': + zoom.name = 'y'; + zoom.dleft = zoom.dright = 1; + break; + case 'Ctrl ArrowDown': + zoom.name = 'y'; + zoom.dleft = zoom.dright = -1; + break; + case 'Escape': + pp?.enlargePad(null, false, true); + return true; } if (zoom.dleft || zoom.dright) { - if (!settings.Zooming) return false; - // in 3dmode with orbit control ignore simple arrows - if (this.mode3d && (key.indexOf('Ctrl') !== 0)) return false; + if (!settings.Zooming) + return false; + // in 3d mode with orbit control ignore simple arrows + if (this.mode3d && key.indexOf('Ctrl')) + return false; this.analyzeMouseWheelEvent(null, zoom, 0.5); - if (zoom.changed) { - this.zoom(zoom.name, zoom.min, zoom.max); - this.zoomChangedInteractive(zoom.name, true); - } + if (zoom.changed) + this.zoomSingle(zoom.name, zoom.min, zoom.max, true); evnt.stopPropagation(); evnt.preventDefault(); } else { @@ -888,13 +964,14 @@ const TooltipHandler = { } return true; // just process any key press - }, + } /** @summary Function called when frame is clicked and object selection can be performed * @desc such event can be used to select */ processFrameClick(pnt, dblckick) { const pp = this.getPadPainter(); - if (!pp) return; + if (!pp) + return; pnt.painters = true; // provide painters reference in the hints pnt.disabled = true; // do not invoke graphics @@ -908,27 +985,28 @@ const TooltipHandler = { } if (exact) { - const handler = dblckick ? this._dblclick_handler : this._click_handler; - if (handler) res = handler(exact.user_info, pnt); + const handler = dblckick ? this.getDblclickHandler() : this.getClickHandler(); + if (isFunc(handler)) + res = handler(exact.user_info, pnt); } if (!dblckick) { pp.selectObjectPainter(exact ? exact.painter : this, - { x: pnt.x + (this._frame_x || 0), y: pnt.y + (this._frame_y || 0) }); + { x: pnt.x + this.getFrameX(), y: pnt.y + this.getFrameY() }); } return res; - }, + } /** @summary Check mouse moving */ shiftMoveHanlder(evnt, pos0) { if (evnt.buttons === this._shifting_buttons) { const frame = this.getFrameSvg(), - pos = d3_pointer(evnt, frame.node()), - main_svg = this.draw_g.selectChild('.main_layer'), - dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); + pos = d3_pointer(evnt, frame.node()), + main_svg = this.getG().selectChild('.main_layer'), + dx = pos0[0] - pos[0], + dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], + w = this.getFrameWidth(), h = this.getFrameHeight(); this._shifting_dx = dx; this._shifting_dy = dy; @@ -938,7 +1016,7 @@ const TooltipHandler = { evnt.preventDefault(); evnt.stopPropagation(); } - }, + } /** @summary mouse up handler for shifting */ shiftUpHanlder(evnt) { @@ -949,22 +1027,21 @@ const TooltipHandler = { if ((this._shifting_dx !== undefined) && (this._shifting_dy !== undefined)) this.performScalesShift(); - }, + } - /** @summary Shift scales on defined positions */ + /** @summary Shift scales on defined positions */ performScalesShift() { const w = this.getFrameWidth(), h = this.getFrameHeight(), - main_svg = this.draw_g.selectChild('.main_layer'), - gr = this.getGrFuncs(), - xmin = gr.revertAxis('x', this._shifting_dx), - xmax = gr.revertAxis('x', this._shifting_dx + w), - ymin = gr.revertAxis('y', this._shifting_dy + h), - ymax = gr.revertAxis('y', this._shifting_dy); + main_svg = this.getG().selectChild('.main_layer'), + gr = this.getGrFuncs(), + xmin = gr.revertAxis('x', this._shifting_dx), + xmax = gr.revertAxis('x', this._shifting_dx + w), + ymin = gr.revertAxis('y', this._shifting_dy + h), + ymax = gr.revertAxis('y', this._shifting_dy); main_svg.attr('viewBox', `0 0 ${w} ${h}`); - delete this._shifting_dx; - delete this._shifting_dy; + this._shifting_dx = this._shifting_dy = undefined; setPainterTooltipEnabled(this, true); @@ -972,12 +1049,13 @@ const TooltipHandler = { this.zoomSingle('x', xmin, xmax); else this.zoom(xmin, xmax, ymin, ymax); - }, + } /** @summary Start mouse rect zooming */ startRectSel(evnt) { // ignore when touch selection is activated - if (this.zoom_kind > 100) return; + if (this.zoom_kind > 100) + return; const frame = this.getFrameSvg(), pos = d3_pointer(evnt, frame.node()); @@ -987,8 +1065,8 @@ const TooltipHandler = { this._shifting_buttons = evnt.buttons; if (!evnt.$emul) { - d3_select(window).on('mousemove.shiftHandler', evnt => this.shiftMoveHanlder(evnt, pos)) - .on('mouseup.shiftHandler', evnt => this.shiftUpHanlder(evnt), true); + d3_select(window).on('mousemove.shiftHandler', evnt2 => this.shiftMoveHanlder(evnt2, pos)) + .on('mouseup.shiftHandler', evnt2 => this.shiftUpHanlder(evnt2), true); } setPainterTooltipEnabled(this, false); @@ -998,7 +1076,8 @@ const TooltipHandler = { } // ignore all events from non-left button - if (evnt.button !== 0) return; + if (evnt.button) + return; evnt.preventDefault(); @@ -1031,8 +1110,8 @@ const TooltipHandler = { } if (!evnt.$emul) { - d3_select(window).on('mousemove.zoomRect', evnt => this.moveRectSel(evnt)) - .on('mouseup.zoomRect', evnt => this.endRectSel(evnt), true); + d3_select(window).on('mousemove.zoomRect', evnt2 => this.moveRectSel(evnt2)) + .on('mouseup.zoomRect', evnt2 => this.endRectSel(evnt2), true); } this.zoom_rect = null; @@ -1044,23 +1123,26 @@ const TooltipHandler = { if (this.zoom_kind !== 1) return postponePromise(() => this.startLabelsMove(), 500); - }, + } /** @summary Starts labels move */ startLabelsMove() { - if (this.zoom_rect) return; + if (this.zoom_rect) + return; const handle = (this.zoom_kind === 2) ? this.x_handle : this.y_handle; - if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) return; + if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) + return; if (handle.processLabelsMove('start', this.zoom_lastpos)) this.zoom_labels = handle; - }, + } /** @summary Process mouse rect zooming */ moveRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) return; + if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) + return; evnt.preventDefault(); const m = d3_pointer(evnt, this.getFrameSvg().node()); @@ -1075,9 +1157,16 @@ const TooltipHandler = { m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); switch (this.zoom_kind) { - case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break; - case 2: this.zoom_curr[0] = m[0]; break; - case 3: this.zoom_curr[1] = m[1]; break; + case 1: + this.zoom_curr[0] = m[0]; + this.zoom_curr[1] = m[1]; + break; + case 2: + this.zoom_curr[0] = m[0]; + break; + case 3: + this.zoom_curr[1] = m[1]; + break; } const x = Math.min(this.zoom_origin[0], this.zoom_curr[0]), @@ -1087,7 +1176,8 @@ const TooltipHandler = { if (!this.zoom_rect) { // ignore small changes, can be switching to labels move - if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) return; + if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) + return; this.zoom_rect = this.getFrameSvg() .append('rect') @@ -1096,11 +1186,12 @@ const TooltipHandler = { } this.zoom_rect.attr('x', x).attr('y', y).attr('width', w).attr('height', h); - }, + } /** @summary Finish mouse rect zooming */ endRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) return; + if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) + return; evnt.preventDefault(); @@ -1114,47 +1205,61 @@ const TooltipHandler = { if (this.zoom_labels) this.zoom_labels.processLabelsMove('stop', m); - else { + else { const changed = [this.can_zoom_x, this.can_zoom_y]; m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); switch (this.zoom_kind) { - case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break; - case 2: this.zoom_curr[0] = m[0]; changed[1] = false; break; // only X - case 3: this.zoom_curr[1] = m[1]; changed[0] = false; break; // only Y + case 1: + this.zoom_curr[0] = m[0]; + this.zoom_curr[1] = m[1]; + break; + case 2: // only X + this.zoom_curr[0] = m[0]; + changed[1] = false; + break; + case 3: // only Y + this.zoom_curr[1] = m[1]; + changed[0] = false; + break; } - const idx = this.swap_xy ? 1 : 0, idy = 1 - idx; let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; - if (changed[idx] && (Math.abs(this.zoom_curr[idx] - this.zoom_origin[idx]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 2)) namex = 'x2'; - xmin = Math.min(this.revertAxis(namex, this.zoom_origin[idx]), this.revertAxis(namex, this.zoom_curr[idx])); - xmax = Math.max(this.revertAxis(namex, this.zoom_origin[idx]), this.revertAxis(namex, this.zoom_curr[idx])); + if (changed[0] && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 5)) { + if (this.zoom_second && (this.zoom_kind === 2)) + namex = 'x2'; + const v1 = this.revertAxis(namex, this.zoom_origin[0]), + v2 = this.revertAxis(namex, this.zoom_curr[0]); + xmin = Math.min(v1, v2); + xmax = Math.max(v1, v2); isany = true; } - if (changed[idy] && (Math.abs(this.zoom_curr[idy] - this.zoom_origin[idy]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 3)) namey = 'y2'; - ymin = Math.min(this.revertAxis(namey, this.zoom_origin[idy]), this.revertAxis(namey, this.zoom_curr[idy])); - ymax = Math.max(this.revertAxis(namey, this.zoom_origin[idy]), this.revertAxis(namey, this.zoom_curr[idy])); + if (changed[1] && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 5)) { + if (this.zoom_second && (this.zoom_kind === 3)) + namey = 'y2'; + + const v1 = this.revertAxis(namey, this.zoom_origin[1]), + v2 = this.revertAxis(namey, this.zoom_curr[1]); + ymin = Math.min(v1, v2); + ymax = Math.max(v1, v2); isany = true; } + if (this.swap_xy() && !this.zoom_second) + [xmin, xmax, ymin, ymax] = [ymin, ymax, xmin, xmax]; + if (namex === 'x2') { - this.zoomChangedInteractive(namex, true); - pr = this.zoomSingle(namex, xmin, xmax); + pr = this.zoomSingle(namex, xmin, xmax, true); kind = 0; } else if (namey === 'y2') { - this.zoomChangedInteractive(namey, true); - pr = this.zoomSingle(namey, ymin, ymax); + pr = this.zoomSingle(namey, ymin, ymax, true); kind = 0; } else if (isany) { - this.zoomChangedInteractive('x', true); - this.zoomChangedInteractive('y', true); - pr = this.zoom(xmin, xmax, ymin, ymax); + pr = this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); kind = 0; } } @@ -1169,16 +1274,16 @@ const TooltipHandler = { this.processFrameClick(pnt); break; case 2: - this.getPadPainter()?.selectObjectPainter(this, null, 'xaxis'); + this.getPadPainter()?.selectObjectPainter(this.x_handle); break; case 3: - this.getPadPainter()?.selectObjectPainter(this, null, 'yaxis'); + this.getPadPainter()?.selectObjectPainter(this.y_handle); break; } // return promise - if any return pr; - }, + } /** @summary Handle mouse double click on frame */ mouseDoubleClick(evnt) { @@ -1190,25 +1295,32 @@ const TooltipHandler = { const valid_x = (m[0] >= 0) && (m[0] <= fw), valid_y = (m[1] >= 0) && (m[1] <= fh); - if (valid_x && valid_y && this._dblclick_handler) - if (this.processFrameClick({ x: m[0], y: m[1] }, true)) return; + if (valid_x && valid_y && this.getDblclickHandler()) { + if (this.processFrameClick({ x: m[0], y: m[1] }, true)) + return; + } let kind = (this.can_zoom_x ? 'x' : '') + (this.can_zoom_y ? 'y' : '') + 'z'; if (!valid_x) { - if (!this.can_zoom_y) return; - kind = this.swap_xy ? 'x' : 'y'; - if ((m[0] > fw) && this[kind+'2_handle']) kind += '2'; // let unzoom second axis + if (!this.can_zoom_y) + return; + kind = this.swap_xy() ? 'x' : 'y'; + if ((m[0] > fw) && this[kind + '2_handle']) + kind += '2'; // let unzoom second axis } else if (!valid_y) { - if (!this.can_zoom_x) return; - kind = this.swap_xy ? 'y' : 'x'; - if ((m[1] < 0) && this[kind+'2_handle']) kind += '2'; // let unzoom second axis + if (!this.can_zoom_x) + return; + kind = this.swap_xy() ? 'y' : 'x'; + if ((m[1] < 0) && this[kind + '2_handle']) + kind += '2'; // let unzoom second axis } return this.unzoom(kind).then(changed => { - if (changed) return; + if (changed) + return; const pp = this.getPadPainter(), rect = this.getFrameRect(); return pp?.selectObjectPainter(pp, { x: m[0] + rect.x, y: m[1] + rect.y, dbl: true }); }); - }, + } /** @summary Start touch zoom */ startTouchZoom(evnt) { @@ -1217,7 +1329,7 @@ const TooltipHandler = { // in case when zooming was started, block any other kind of events // also prevent zooming together with active dragging - if ((this.zoom_kind !== 0) || drag_kind) + if (this.zoom_kind || drag_kind) return; const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); @@ -1288,15 +1400,16 @@ const TooltipHandler = { .call(addHighlightStyle, true); if (!evnt.$emul) { - d3_select(window).on('touchmove.zoomRect', evnt => this.moveTouchZoom(evnt)) - .on('touchcancel.zoomRect', evnt => this.endTouchZoom(evnt)) - .on('touchend.zoomRect', evnt => this.endTouchZoom(evnt)); + d3_select(window).on('touchmove.zoomRect', evnt2 => this.moveTouchZoom(evnt2)) + .on('touchcancel.zoomRect', evnt2 => this.endTouchZoom(evnt2)) + .on('touchend.zoomRect', evnt2 => this.endTouchZoom(evnt2)); } - }, + } /** @summary Move touch zooming */ moveTouchZoom(evnt) { - if (this.zoom_kind < 100) return; + if (this.zoom_kind < 100) + return; evnt.preventDefault(); @@ -1325,11 +1438,12 @@ const TooltipHandler = { setPainterTooltipEnabled(this, false); evnt.stopPropagation(); - }, + } /** @summary End touch zooming handler */ endTouchZoom(evnt) { - if (this.zoom_kind < 100) return; + if (this.zoom_kind < 100) + return; drag_kind = ''; // reset global flag @@ -1341,20 +1455,24 @@ const TooltipHandler = { } let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; - const xid = this.swap_xy ? 1 : 0, yid = 1 - xid, changed = [true, true]; + const xid = this.swap_xy() ? 1 : 0, yid = 1 - xid, changed = [true, true]; - if (this.zoom_kind === 102) changed[1] = false; - if (this.zoom_kind === 103) changed[0] = false; + if (this.zoom_kind === 102) + changed[1] = false; + if (this.zoom_kind === 103) + changed[0] = false; if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 102)) namex = 'x2'; + if (this.zoom_second && (this.zoom_kind === 102)) + namex = 'x2'; xmin = Math.min(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); xmax = Math.max(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); isany = true; } if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 103)) namey = 'y2'; + if (this.zoom_second && (this.zoom_kind === 103)) + namey = 'y2'; ymin = Math.min(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); ymax = Math.max(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); isany = true; @@ -1363,20 +1481,15 @@ const TooltipHandler = { this.clearInteractiveElements(); delete this.last_touch_time; - if (namex === 'x2') { - this.zoomChangedInteractive(namex, true); - this.zoomSingle(namex, xmin, xmax); - } else if (namey === 'y2') { - this.zoomChangedInteractive(namey, true); - this.zoomSingle(namey, ymin, ymax); - } else if (isany) { - this.zoomChangedInteractive('x', true); - this.zoomChangedInteractive('y', true); - this.zoom(xmin, xmax, ymin, ymax); - } + if (namex === 'x2') + this.zoomSingle(namex, xmin, xmax, true); + else if (namey === 'y2') + this.zoomSingle(namey, ymin, ymax, true); + else if (isany) + this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); evnt.stopPropagation(); - }, + } /** @summary Analyze zooming with mouse wheel */ analyzeMouseWheelEvent(event, item, dmin, test_ignore, second_side) { @@ -1388,25 +1501,21 @@ const TooltipHandler = { } const handle = this[item.name + '_handle']; return handle?.analyzeWheelEvent(event, dmin, item, test_ignore); - }, + } - /** @summary return true if default Y zooming should be enabled + /** @summary return true if default Y zooming should be enabled * @desc it is typically for 2-Dim histograms or * when histogram not draw, defined by other painters */ isAllowedDefaultYZooming() { - if (this.self_drawaxes) return true; - - const pad_painter = this.getPadPainter(); - if (pad_painter?.painters) { - for (let k = 0; k < pad_painter.painters.length; ++k) { - const subpainter = pad_painter.painters[k]; - if (subpainter?.wheel_zoomy !== undefined) - return subpainter.wheel_zoomy; - } - } + if (this.self_drawaxes) + return true; - return false; - }, + let res; + this.forEachPainter(objp => { + res = res ?? objp._wheel_zoomy; + }, 'objects'); + return res; + } /** @summary Handles mouse wheel event */ mouseWheel(evnt) { @@ -1414,33 +1523,27 @@ const TooltipHandler = { evnt.preventDefault(); this.clearInteractiveElements(); - const itemx = { name: 'x', reverse: this.reverse_x }, - itemy = { name: 'y', reverse: this.reverse_y, ignore: !this.isAllowedDefaultYZooming() }, + const itemx = { name: 'x', reverse: this.reverse_x() }, + itemy = { name: 'y', reverse: this.reverse_y(), ignore: !this.isAllowedDefaultYZooming() }, cur = d3_pointer(evnt, this.getFrameSvg().node()), w = this.getFrameWidth(), h = this.getFrameHeight(); if (this.can_zoom_x) - this.analyzeMouseWheelEvent(evnt, this.swap_xy ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); + this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); if (this.can_zoom_y) - this.analyzeMouseWheelEvent(evnt, this.swap_xy ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); + this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); - let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max); + let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max, undefined, undefined, itemx.changed || itemy.changed); - if (itemx.changed) this.zoomChangedInteractive('x', true); - if (itemy.changed) this.zoomChangedInteractive('y', true); + if (itemx.second) + pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max, itemx.second.changed)); - if (itemx.second) { - pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max)); - if (itemx.second.changed) this.zoomChangedInteractive('x2', true); - } - if (itemy.second) { - pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max)); - if (itemy.second.changed) this.zoomChangedInteractive('y2', true); - } + if (itemy.second) + pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max, itemy.second.changed)); return pr; - }, + } /** @summary Show frame context menu */ showContextMenu(kind, evnt, obj) { @@ -1449,7 +1552,8 @@ const TooltipHandler = { return evnt.preventDefault(); // ignore context menu when touches zooming is ongoing or - if (('zoom_kind' in this) && (this.zoom_kind > 100)) return; + if (this.zoom_kind && (this.zoom_kind > 100)) + return; let pnt, menu_painter = this, exec_painter = null, frame_corner = false, fp = null; // object used to show context menu @@ -1461,22 +1565,22 @@ const TooltipHandler = { const ms = d3_pointer(evnt, svg_node), tch = get_touch_pointers(evnt, svg_node); if (tch.length === 1) - pnt = { x: tch[0][0], y: tch[0][1], touch: true }; + pnt = { x: tch[0][0], y: tch[0][1], touch: true }; else if (ms.length === 2) - pnt = { x: ms[0], y: ms[1], touch: false }; - } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { - pnt = evnt; - const rect = svg_node.getBoundingClientRect(); - evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; - } - - if ((kind === 'painter') && obj) { - menu_painter = obj; - kind = ''; - } else if (kind === 'main') { - menu_painter = this.getMainPainter(true); - kind = ''; - } else if (!kind) { + pnt = { x: ms[0], y: ms[1], touch: false }; + } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { + pnt = evnt; + const rect = svg_node.getBoundingClientRect(); + evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; + } + + if ((kind === 'painter') && obj) { + menu_painter = obj; + kind = ''; + } else if (kind === 'main') { + menu_painter = this.getMainPainter(true); + kind = ''; + } else if (!kind) { const pp = this.getPadPainter(); let sel = null; @@ -1488,15 +1592,21 @@ const TooltipHandler = { for (let n = 0; n < hints.length; ++n) { if (hints[n]?.menu) { const dist = hints[n].menu_dist ?? 7; - if (dist < bestdist) { sel = hints[n].painter; bestdist = dist; } + if (dist < bestdist) { + sel = hints[n].painter; + bestdist = dist; + } } } } - if (sel) menu_painter = sel; - else kind = 'frame'; + if (sel) + menu_painter = sel; + else + kind = 'frame'; - if (pnt) frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); + if (pnt) + frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); fp.setLastEventPos(pnt); } else if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'pal')) { @@ -1506,9 +1616,11 @@ const TooltipHandler = { exec_painter = null; } - if (!exec_painter) exec_painter = menu_painter; + if (!exec_painter) + exec_painter = menu_painter; - if (!isFunc(menu_painter?.fillContextMenu)) return; + if (!isFunc(menu_painter?.fillContextMenu)) + return; this.clearInteractiveElements(); @@ -1520,20 +1632,21 @@ const TooltipHandler = { domenu = fp.fillContextMenu(menu); if (domenu) { - return exec_painter.fillObjectExecMenu(menu, kind).then(menu => { - // suppress any running zooming - setPainterTooltipEnabled(menu.painter, false); - return menu.show().then(() => setPainterTooltipEnabled(menu.painter, true)); + return exec_painter.fillObjectExecMenu(menu, kind).then(menu2 => { + // suppress any running zooming + setPainterTooltipEnabled(menu2.painter, false); + return menu2.show().then(() => setPainterTooltipEnabled(menu2.painter, true)); }); } }); - }, + } - /** @summary Activate touch handling on frame + /** @summary Activate touch handling on frame * @private */ startSingleTouchHandling(kind, evnt) { const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - if (arr.length !== 1) return; + if (arr.length !== 1) + return; evnt.preventDefault(); evnt.stopPropagation(); @@ -1546,24 +1659,24 @@ const TooltipHandler = { setPainterTooltipEnabled(this, false); - d3_select(window).on('touchmove.singleTouch', kind ? null : evnt => this.moveTouchHandling(evnt, kind, arr[0])) - .on('touchcancel.singleTouch', evnt => this.endSingleTouchHandling(evnt, kind, arr[0], tm)) - .on('touchend.singleTouch', evnt => this.endSingleTouchHandling(evnt, kind, arr[0], tm)); - }, + d3_select(window).on('touchmove.singleTouch', kind ? null : evnt2 => this.moveTouchHandling(evnt2, kind, arr[0])) + .on('touchcancel.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)) + .on('touchend.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)); + } /** @summary Moving of touch pointer * @private */ moveTouchHandling(evnt, kind, pos0) { const frame = this.getFrameSvg(), - main_svg = this.draw_g.selectChild('.main_layer'); + main_svg = this.getG().selectChild('.main_layer'); let pos; try { - pos = get_touch_pointers(evnt, frame.node())[0]; - } catch (err) { - pos = [0, 0]; - if (evnt?.changedTouches) - pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; + pos = get_touch_pointers(evnt, frame.node())[0]; + } catch { + pos = [0, 0]; + if (evnt?.changedTouches) + pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; } const dx = pos0[0] - pos[0], @@ -1574,7 +1687,7 @@ const TooltipHandler = { this._shifting_dy = dy; main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - }, + } /** @summary Process end-touch event, which can cause content menu to appear * @private */ @@ -1588,13 +1701,14 @@ const TooltipHandler = { .on('touchcancel.singleTouch', null) .on('touchend.singleTouch', null); - if (evnt === null) return; + if (evnt === null) + return; if (Math.abs(this._shifting_dx) > 2 || Math.abs(this._shifting_dy) > 2) this.performScalesShift(); - else if (new Date().getTime() - tm > 700) + else if (new Date().getTime() - tm > 700) this.showContextMenu(kind, { x: pos[0], y: pos[1] }); - }, + } /** @summary Clear frame interactive elements */ clearInteractiveElements() { @@ -1609,37 +1723,69 @@ const TooltipHandler = { // enable tooltip in frame painter setPainterTooltipEnabled(this, true); - }, - - /** @summary Assign frame interactive methods */ - assign(painter) { - Object.assign(painter, this); } -}; // FrameInterative + /** @summary Assign interactive methods to frame painter */ + static assign(fpainter) { + const src = FrameInteractive.prototype, + src2 = TooltipHandler.prototype; + Object.getOwnPropertyNames(src).forEach(name => { + if (name !== 'constructor') + fpainter[name] = src[name]; + }); + Object.getOwnPropertyNames(src2).forEach(name => { + if (name !== 'constructor') + fpainter[name] = src2[name]; + }); + } +} // class FrameInteractive /** * @summary Painter class for TFrame, main handler for interactivity * @private */ -class TFramePainter extends ObjectPainter { +class TFramePainter extends FrameInteractive { + + #frame_x; // frame X coordinate + #frame_y; // frame Y coordinate + #frame_width; // frame width + #frame_height; // frame height + #frame_trans; // transform of frame element + #swap_xy; // swap X/Y axis on the frame + #reverse_x; // reverse X axis + #reverse_y; // reverse Y axis + #reverse_x2; // reverse X2 axis + #reverse_y2; // reverse Y2 axis + #border_mode; // frame border mode + #border_size; // frame border size + #axes_drawn; // when axes are drawn + #axes2_drawn; // when axes are drawn + #shrink_frame_left; // shrink frame on left side + #projection; // id of projection function + #click_handler; // handle for click events + #dblclick_handler; // handle for double click events + #keys_handler; // assigned handler for keyboard events + #enabled_keys; // when keyboard press handling enabled + #last_event_pos; // position of last event /** @summary constructor * @param {object|string} dom - DOM element for drawing or element id - * @param {object} tframe - TFrame object */ - constructor(dom, tframe) { - super(dom, (tframe && tframe.$dummy) ? null : tframe); + * @param {object} frame - TFrame object */ + constructor(dom, frame) { + super(dom, frame?.$dummy ? null : frame); this.zoom_kind = 0; this.mode3d = false; - this.shrink_frame_left = 0.0; + this.#shrink_frame_left = 0.0; this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing this.ranges_set = false; - this.axes_drawn = false; - this.keys_handler = null; - this.projection = 0; // different projections + this.#axes_drawn = false; + this.#axes2_drawn = false; + this.#border_mode = gStyle.fFrameBorderMode; + this.#border_size = gStyle.fFrameBorderSize; + this.#projection = 0; // different projections } /** @summary Returns frame painter - object itself */ @@ -1651,20 +1797,35 @@ class TFramePainter extends ObjectPainter { /** @summary Returns frame or sub-objects, used in GED editor */ getObject(place) { - if (place === 'xaxis') return this.xaxis; - if (place === 'yaxis') return this.yaxis; + if (place === 'xaxis') + return this.xaxis; + if (place === 'yaxis') + return this.yaxis; return super.getObject(); } /** @summary Set active flag for frame - can block some events * @private */ setFrameActive(on) { - this.enabledKeys = on && settings.HandleKeys; + this.#enabled_keys = on && settings.HandleKeys; // used only in 3D mode where control is used if (this.control) - this.control.enableKeys = this.enabledKeys; + this.control.enableKeys = this.#enabled_keys; } + /** @summary Returns true if keys handling enabled + * @private */ + isEnabledKeys() { return this.#enabled_keys; } + + /** @summary Returns true if X/Y axis swapped */ + swap_xy() { return this.#swap_xy; } + + /** @summary Is reverse x */ + reverse_x() { return this.#reverse_x; } + + /** @summary Is reverse x */ + reverse_y() { return this.#reverse_y; } + /** @summary Shrink frame size * @private */ shrinkFrame(shrink_left, shrink_right) { @@ -1672,34 +1833,34 @@ class TFramePainter extends ObjectPainter { this.fX2NDC -= shrink_right; } - /** @summary Set position of last context menu event */ - setLastEventPos(pnt) { - this.fLastEventPnt = pnt; - } + /** @summary Set position of last context menu event + * @private */ + setLastEventPos(pnt) { this.#last_event_pos = pnt; } /** @summary Return position of last event * @private */ - getLastEventPos() { return this.fLastEventPnt; } + getLastEventPos() { return this.#last_event_pos; } /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.projection); } + getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - /** @summary Rcalculate frame ranges using specified projection functions */ + /** @summary Recalculate frame ranges using specified projection functions */ recalculateRange(Proj, change_x, change_y) { - this.projection = Proj || 0; + this.#projection = Proj || 0; - if ((this.projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { + if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { console.warn(`Mercator Projection: Latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.projection = 0; + this.#projection = 0; } const func = this.getProjectionFunc(); - if (!func) return; + if (!func) + return; const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; + func(this.scale_xmin, this.scale_ymax), + func(this.scale_xmax, this.scale_ymax), + func(this.scale_xmax, this.scale_ymin)]; if (this.scale_xmin < 0 && this.scale_xmax > 0) { pnts.push(func(0, this.scale_ymin)); pnts.push(func(0, this.scale_ymax)); @@ -1766,7 +1927,7 @@ class TFramePainter extends ObjectPainter { } if (hpainter.options.maximum !== kNoZoom) { this.zoom_zmax = hpainter.options.maximum; - if (this.zoom_zmin === undefined) this.zoom_zmin = this.zmin; + this.zoom_zmin ??= this.zmin; } } } @@ -1785,7 +1946,7 @@ class TFramePainter extends ObjectPainter { } } - /** @summary Retuns associated axis object */ + /** @summary Returns associated axis object */ getAxis(name) { switch (name) { case 'x': return this.xaxis; @@ -1800,37 +1961,36 @@ class TFramePainter extends ObjectPainter { /** @summary Apply axis zooming from pad user range * @private */ applyPadUserRange(pad, name) { - if (!pad) return; + if (!pad) + return; - // seems to be, not allways user range calculated + // seems to be, not always user range calculated let umin = pad[`fU${name}min`], umax = pad[`fU${name}max`], eps = 1e-7; - if (name === 'x') { + if (name === 'x') { if ((Math.abs(pad.fX1) > eps) || (Math.abs(pad.fX2 - 1) > eps)) { const dx = pad.fX2 - pad.fX1; - umin = pad.fX1 + dx*pad.fLeftMargin; - umax = pad.fX2 - dx*pad.fRightMargin; - } - } else { - if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { - const dy = pad.fY2 - pad.fY1; - umin = pad.fY1 + dy*pad.fBottomMargin; - umax = pad.fY2 - dy*pad.fTopMargin; + umin = pad.fX1 + dx * pad.fLeftMargin; + umax = pad.fX2 - dx * pad.fRightMargin; } + } else if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { + const dy = pad.fY2 - pad.fY1; + umin = pad.fY1 + dy * pad.fBottomMargin; + umax = pad.fY2 - dy * pad.fTopMargin; } - if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax-1) < eps)) return; + if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax - 1) < eps)) + return; if (pad[`fLog${name}`] > 0) { umin = Math.exp(umin * Math.log(10)); umax = Math.exp(umax * Math.log(10)); } - let aname = name; - if (this.swap_xy) aname = (name === 'x') ? 'y' : 'x'; - const smin = this[`scale_${aname}min`], + const aname = !this.#swap_xy ? name : (name === 'x' ? 'y' : 'x'), + smin = this[`scale_${aname}min`], smax = this[`scale_${aname}max`]; eps = (smax - smin) * 1e-7; @@ -1843,7 +2003,8 @@ class TFramePainter extends ObjectPainter { /** @summary Apply zooming from TAxis attributes */ applyAxisZoom(name) { - if (this.zoomChangedInteractive(name)) return; + if (this.zoomChangedInteractive(name)) + return; this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; const axis = this.getAxis(name); @@ -1853,8 +2014,9 @@ class TFramePainter extends ObjectPainter { this[`zoom_${name}min`] = axis.fFirst > 1 ? axis.GetBinLowEdge(axis.fFirst) : axis.fXmin; this[`zoom_${name}max`] = axis.fLast < axis.fNbins ? axis.GetBinLowEdge(axis.fLast + 1) : axis.fXmax; // reset user range for main painter - axis.InvertBit(EAxisBits.kAxisRange); - axis.fFirst = 1; axis.fLast = axis.fNbins; + axis.SetBit(EAxisBits.kAxisRange, false); + axis.fFirst = 1; + axis.fLast = axis.fNbins; } } } @@ -1869,11 +2031,12 @@ class TFramePainter extends ObjectPainter { createXY(opts) { this.cleanXY(); // remove all previous configurations - if (!opts) opts = { ndim: 1 }; + if (!opts) + opts = { ndim: 1 }; - this.swap_xy = opts.swap_xy || false; - this.reverse_x = opts.reverse_x || false; - this.reverse_y = opts.reverse_y || false; + this.#swap_xy = opts.swap_xy || false; + this.#reverse_x = opts.reverse_x || false; + this.#reverse_y = opts.reverse_y || false; this.logx = this.logy = 0; @@ -1891,18 +2054,20 @@ class TFramePainter extends ObjectPainter { this.scale_ymax = this.ymax; if (opts.extra_y_space) { - const log_scale = this.swap_xy ? pad_logx : pad_logy; + const log_scale = this.#swap_xy ? pad_logx : pad_logy; if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax)*1.1); + this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); else - this.scale_ymax += (this.scale_ymax - this.scale_ymin)*0.1; + this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; } if (opts.check_pad_range) { // take zooming out of pad or axis attributes this.applyAxisZoom('x'); - if (opts.ndim > 1) this.applyAxisZoom('y'); - if (opts.ndim > 2) this.applyAxisZoom('z'); + if (opts.ndim > 1) + this.applyAxisZoom('y'); + if (opts.ndim > 2) + this.applyAxisZoom('z'); // Use configured pad range - only when main histogram drawn with SAME draw option if (opts.check_pad_range === 'pad_range') { @@ -1938,32 +2103,38 @@ class TFramePainter extends ObjectPainter { // projection should be assigned this.recalculateRange(opts.Proj, orig_x, orig_y); - this.x_handle = new TAxisPainter(this.getDom(), this.xaxis, true); - this.x_handle.setPadName(this.getPadName()); + this.x_handle = new TAxisPainter(pp, this.xaxis, true); this.x_handle.setHistPainter(opts.hist_painter, 'x'); - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x, - log: this.swap_xy ? pad_logy : pad_logx, - noexp_changed: this.x_noexp_changed, - symlog: this.swap_xy ? opts.symlog_y : opts.symlog_x, - logcheckmin: this.swap_xy, - logminfactor: logminfactorX }); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x, + log: this.#swap_xy ? pad_logy : pad_logx, + ignore_labels: this.x_ignore_labels, + noexp_changed: this.x_noexp_changed, + fixed_ticks: opts.xticks, + symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, + log_min_nz: opts.xmin_nz && (opts.xmin_nz <= this.xmax) ? 0.9 * opts.xmin_nz : 0, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: logminfactorX + }); this.x_handle.assignFrameMembers(this, 'x'); - this.y_handle = new TAxisPainter(this.getDom(), this.yaxis, true); - this.y_handle.setPadName(this.getPadName()); + this.y_handle = new TAxisPainter(pp, this.yaxis, true); this.y_handle.setHistPainter(opts.hist_painter, 'y'); - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y, - log: this.swap_xy ? pad_logx : pad_logy, - noexp_changed: this.y_noexp_changed, - symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5*opts.ymin_nz : 0, - logminfactor: logminfactorY }); + this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + value_axis: opts.ndim === 1, + reverse: this.#reverse_y, + log: this.#swap_xy ? pad_logx : pad_logy, + ignore_labels: this.y_ignore_labels, + noexp_changed: this.y_noexp_changed, + fixed_ticks: opts.yticks, + symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, + log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5 * opts.ymin_nz : 0, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + logminfactor: logminfactorY + }); this.y_handle.assignFrameMembers(this, 'y'); @@ -1973,16 +2144,16 @@ class TFramePainter extends ObjectPainter { /** @summary Create x,y objects for drawing of second axes * @private */ createXY2(opts) { - if (!opts) opts = { ndim: this.scales_ndim ?? 1 }; + if (!opts) + opts = { ndim: this.scales_ndim ?? 1 }; - this.reverse_x2 = opts.reverse_x || false; - this.reverse_y2 = opts.reverse_y || false; + this.#reverse_x2 = opts.reverse_x || false; + this.#reverse_y2 = opts.reverse_y || false; this.logx2 = this.logy2 = 0; const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(); + pp = this.getPadPainter(), pad = pp.getRootPad(); if (opts.second_x) { this.scale_x2min = this.x2min; @@ -1995,11 +2166,11 @@ class TFramePainter extends ObjectPainter { } if (opts.extra_y_space && opts.second_y) { - const log_scale = this.swap_xy ? pad.fLogx : pad.fLogy; + const log_scale = this.#swap_xy ? pad.fLogx : pad.fLogy; if (log_scale && (this.scale_y2max > 0)) - this.scale_y2max = Math.exp(Math.log(this.scale_y2max)*1.1); + this.scale_y2max = Math.exp(Math.log(this.scale_y2max) * 1.1); else - this.scale_y2max += (this.scale_y2max - this.scale_y2min)*0.1; + this.scale_y2max += (this.scale_y2max - this.scale_y2min) * 0.1; } if ((this.zoom_x2min !== this.zoom_x2max) && opts.second_x) { @@ -2013,32 +2184,34 @@ class TFramePainter extends ObjectPainter { } if (opts.second_x) { - this.x2_handle = new TAxisPainter(this.getDom(), this.x2axis, true); - this.x2_handle.setPadName(this.getPadName()); + this.x2_handle = new TAxisPainter(pp, this.x2axis, true); this.x2_handle.setHistPainter(opts.hist_painter, 'x'); - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.swap_xy, this.swap_xy ? [0, h] : [0, w], - { reverse: this.reverse_x2, - log: this.swap_xy ? pad.fLogy : pad.fLogx, - noexp_changed: this.x2_noexp_changed, - logcheckmin: this.swap_xy, - logminfactor: logminfactorX }); + this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { + reverse: this.#reverse_x2, + log: this.#swap_xy ? pad.fLogy : pad.fLogx, + ignore_labels: this.x2_ignore_labels, + noexp_changed: this.x2_noexp_changed, + logcheckmin: (opts.ndim > 1) || !this.#swap_xy, + logminfactor: logminfactorX + }); this.x2_handle.assignFrameMembers(this, 'x2'); } if (opts.second_y) { - this.y2_handle = new TAxisPainter(this.getDom(), this.y2axis, true); - this.y2_handle.setPadName(this.getPadName()); + this.y2_handle = new TAxisPainter(pp, this.y2axis, true); this.y2_handle.setHistPainter(opts.hist_painter, 'y'); - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.swap_xy, this.swap_xy ? [0, w] : [0, h], - { reverse: this.reverse_y2, - log: this.swap_xy ? pad.fLogx : pad.fLogy, - noexp_changed: this.y2_noexp_changed, - logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, - logminfactor: logminfactorY }); + this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { + reverse: this.#reverse_y2, + log: this.#swap_xy ? pad.fLogx : pad.fLogy, + ignore_labels: this.y2_ignore_labels, + noexp_changed: this.y2_noexp_changed, + logcheckmin: (opts.ndim > 1) || this.#swap_xy, + log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, + logminfactor: logminfactorY + }); this.y2_handle.assignFrameMembers(this, 'y2'); } @@ -2050,7 +2223,8 @@ class TFramePainter extends ObjectPainter { getGrFuncs(second_x, second_y) { const use_x2 = second_x && this.grx2, use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) return this; + if (!use_x2 && !use_y2) + return this; return { use_x2, @@ -2065,55 +2239,53 @@ class TFramePainter extends ObjectPainter { y_handle: use_y2 ? this.y2_handle : this.y_handle, scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - swap_xy: this.swap_xy, fp: this, - revertAxis(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.revertAxis(name, v); + _remap(name) { + if ((name === 'x') && this.use_x2) + return 'x2'; + if ((name === 'y') && this.use_y2) + return 'y2'; + return name; }, - axisAsText(name, v) { - if ((name === 'x') && this.use_x2) name = 'x2'; - if ((name === 'y') && this.use_y2) name = 'y2'; - return this.fp.axisAsText(name, v); - } + swap_xy() { return this.fp.swap_xy(); }, + isAxisZoomed(name) { return this.fp.isAxisZoomed(this._remap(name)); }, + revertAxis(name, v) { return this.fp.revertAxis(this._remap(name), v); }, + axisAsText(name, v) { return this.fp.axisAsText(this._remap(name), v); }, + getFrameWidth() { return this.fp.getFrameWidth(); }, + getFrameHeight() { return this.fp.getFrameHeight(); } }; } /** @summary Set selected range back to TPad object * @private */ setRootPadRange(pad, is3d) { - if (!pad || !this.ranges_set) return; + if (!pad || !this.ranges_set) + return; if (is3d) { // this is fake values, algorithm should be copied from TView3D class of ROOT - // pad.fLogx = pad.fLogy = 0; pad.fUxmin = pad.fUymin = -0.9; pad.fUxmax = pad.fUymax = 0.9; } else { - pad.fLogx = this.swap_xy ? this.logy : this.logx; + pad.fLogx = this.#swap_xy ? this.logy : this.logx; pad.fUxmin = pad.fLogx ? Math.log10(this.scale_xmin) : this.scale_xmin; pad.fUxmax = pad.fLogx ? Math.log10(this.scale_xmax) : this.scale_xmax; - pad.fLogy = this.swap_xy ? this.logx : this.logy; + pad.fLogy = this.#swap_xy ? this.logx : this.logy; pad.fUymin = pad.fLogy ? Math.log10(this.scale_ymin) : this.scale_ymin; pad.fUymax = pad.fLogy ? Math.log10(this.scale_ymax) : this.scale_ymax; } const rx = pad.fUxmax - pad.fUxmin, - ry = pad.fUymax - pad.fUymin; - let mx = 1 - pad.fLeftMargin - pad.fRightMargin, - my = 1 - pad.fBottomMargin - pad.fTopMargin; - - if (mx <= 0) mx = 0.01; // to prevent overflow - if (my <= 0) my = 0.01; - - pad.fX1 = pad.fUxmin - rx/mx*pad.fLeftMargin; - pad.fX2 = pad.fUxmax + rx/mx*pad.fRightMargin; - pad.fY1 = pad.fUymin - ry/my*pad.fBottomMargin; - pad.fY2 = pad.fUymax + ry/my*pad.fTopMargin; + ry = pad.fUymax - pad.fUymin, + mx = Math.max(0.001, 1 - pad.fLeftMargin - pad.fRightMargin), + my = Math.max(0.001, 1 - pad.fBottomMargin - pad.fTopMargin); + + pad.fX1 = pad.fUxmin - rx / mx * pad.fLeftMargin; + pad.fX2 = pad.fUxmax + rx / mx * pad.fRightMargin; + pad.fY1 = pad.fUymin - ry / my * pad.fBottomMargin; + pad.fY2 = pad.fUymax + ry / my * pad.fTopMargin; } - /** @summary Draw axes grids * @desc Called immediately after axes drawing */ drawGrids(draw_grids) { @@ -2123,18 +2295,17 @@ class TFramePainter extends ObjectPainter { layer.selectAll('.ygrid').remove(); const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - h = this.getFrameHeight(), - w = this.getFrameWidth(), - grid_style = gStyle.fGridStyle; + pad = pp?.getRootPad(true), + h = this.getFrameHeight(), + w = this.getFrameWidth(), + grid_style = gStyle.fGridStyle; // add a grid on x axis, if the option is set if (pad?.fGridx && draw_grids && this.x_handle?.ticks) { const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('x')?.fAxisColor ?? 1); let gridx = ''; - this.x_handle.ticks.forEach(pos => { - gridx += this.swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; + gridx += this.#swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; }); layer.append('svg:path') @@ -2149,9 +2320,8 @@ class TFramePainter extends ObjectPainter { if (pad?.fGridy && draw_grids && this.y_handle?.ticks) { const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('y')?.fAxisColor ?? 1); let gridy = ''; - this.y_handle.ticks.forEach(pos => { - gridy += this.swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; + gridy += this.#swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; }); layer.append('svg:path') @@ -2175,20 +2345,18 @@ class TFramePainter extends ObjectPainter { /** @summary Identify if requested axes are drawn * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { - return !second_x && !second_y ? this.axes_drawn : false; - } + hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : this.#axes2_drawn; } /** @summary draw axes, * @return {Promise} which ready when drawing is completed */ - async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, - AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { + async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { this.cleanAxesDrawings(); if ((this.xmin === this.xmax) || (this.ymin === this.ymax)) return false; - if (AxisPos === undefined) AxisPos = 0; + if (AxisPos === undefined) + AxisPos = 0; const layer = this.getFrameSvg().selectChild('.axis_layer'), w = this.getFrameWidth(), @@ -2205,64 +2373,67 @@ class TFramePainter extends ObjectPainter { this.y_handle.lbls_both_sides = !this.y_handle.invert_side && (pad?.fTicky > 1); // labels on both sides this.y_handle.has_obstacle = has_y_obstacle; - const draw_horiz = this.swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.swap_xy ? this.x_handle : this.y_handle; + const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, + draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; - if ((!disable_x_draw || !disable_y_draw) && pp._fast_drawing) + if ((!disable_x_draw || !disable_y_draw) && pp.isFastDrawing()) disable_x_draw = disable_y_draw = true; let pr = Promise.resolve(true); if (!disable_x_draw || !disable_y_draw || draw_grids) { + draw_vertical.optionLeft = draw_vertical.invert_side; // text align + const can_adjust_frame = !shrink_forbidden && settings.CanAdjustFrame, - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, disable_x_draw, - undefined, false, pp.getPadHeight() - h - this.getFrameY()), + pr1 = draw_horiz.drawAxis(layer, w, h, + draw_horiz.invert_side ? null : `translate(0,${h})`, + pad?.fTickx ? -h : 0, disable_x_draw, + undefined, false, pp.getPadHeight() - h - this.getFrameY()), - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this._frame_x, can_adjust_frame); + pr2 = draw_vertical.drawAxis(layer, w, h, + draw_vertical.invert_side ? `translate(${w})` : null, + pad?.fTicky ? w : 0, disable_y_draw, + draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); pr = Promise.all([pr1, pr2]).then(() => { this.drawGrids(draw_grids); - if (!can_adjust_frame) return; + if (!can_adjust_frame) + return; let shrink = 0.0; const ypos = draw_vertical.position; if ((-0.2 * w < ypos) && (ypos < 0)) { shrink = -ypos / w + 0.001; - this.shrink_frame_left += shrink; - } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.shrink_frame_left > 0) && (ypos / w > this.shrink_frame_left)) { - shrink = -this.shrink_frame_left; - this.shrink_frame_left = 0.0; + this.#shrink_frame_left += shrink; + } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.#shrink_frame_left > 0) && (ypos / w > this.#shrink_frame_left)) { + shrink = -this.#shrink_frame_left; + this.#shrink_frame_left = 0.0; } - if (!shrink) return; - - this.shrinkFrame(shrink, 0); - return this.redraw().then(() => this.drawAxes(true)); + if (shrink) { + this.shrinkFrame(shrink, 0); + return this.redraw().then(() => this.drawAxes(true)); + } }); } - return pr.then(() => { - if (!shrink_forbidden) - this.axes_drawn = true; - return true; - }); + return pr.then(() => { + if (!shrink_forbidden) + this.#axes_drawn = true; + return true; + }); } /** @summary draw second axes (if any) */ - drawAxes2(second_x, second_y) { + async drawAxes2(second_x, second_y) { const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true); + w = this.getFrameWidth(), + h = this.getFrameHeight(), + pp = this.getPadPainter(), + pad = pp.getRootPad(true); if (second_x) { this.x2_handle.invert_side = true; @@ -2275,10 +2446,10 @@ class TFramePainter extends ObjectPainter { this.y2_handle.lbls_both_sides = false; } - let draw_horiz = this.swap_xy ? this.y2_handle : this.x2_handle, - draw_vertical = this.swap_xy ? this.x2_handle : this.y2_handle; + let draw_horiz = this.#swap_xy ? this.y2_handle : this.x2_handle, + draw_vertical = this.#swap_xy ? this.x2_handle : this.y2_handle; - if ((draw_horiz || draw_vertical) && pp._fast_drawing) + if ((draw_horiz || draw_vertical) && pp.isFastDrawing()) draw_horiz = draw_vertical = null; let pr1, pr2; @@ -2291,13 +2462,17 @@ class TFramePainter extends ObjectPainter { } if (draw_vertical) { + draw_vertical.optionLeft = draw_vertical.invert_side; pr2 = draw_vertical.drawAxis(layer, w, h, draw_vertical.invert_side ? `translate(${w})` : null, pad?.fTicky ? w : 0, false, - draw_vertical.invert_side ? 0 : this._frame_x, false); + draw_vertical.invert_side ? 0 : this.#frame_x, false); } - return Promise.all([pr1, pr2]); + return Promise.all([pr1, pr2]).then(() => { + this.#axes2_drawn = true; + return true; + }); } @@ -2305,10 +2480,10 @@ class TFramePainter extends ObjectPainter { * @private */ updateAttributes(force) { const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - tframe = this.getObject(); + pad = pp?.getRootPad(true), + tframe = this.getObject(); - if ((this.fX1NDC === undefined) || (force && !this.modified_NDC)) { + if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { if (!pad) { this.fX1NDC = gStyle.fPadLeftMargin; this.fX2NDC = 1 - gStyle.fPadRightMargin; @@ -2322,27 +2497,36 @@ class TFramePainter extends ObjectPainter { } } - if (this.fillatt === undefined) { - if (tframe) - this.createAttFill({ attr: tframe }); - else if (pad?.fFrameFillColor) + if (tframe) { + this.createAttFill({ attr: tframe }); + this.#border_mode = tframe.fBorderMode; + this.#border_size = tframe.fBorderSize; + } else if (!this.fillatt) { + if (pad?.fFrameFillColor) this.createAttFill({ pattern: pad.fFrameFillStyle, color: pad.fFrameFillColor }); else if (pad) this.createAttFill({ attr: pad }); else - this.createAttFill({ pattern: 1001, color: 0 }); + this.createAttFill({ pattern: gStyle.fFrameFillStyle, color: gStyle.fFrameFillColor }); // force white color for the canvas frame - if (!tframe && this.fillatt.empty() && pp?.iscan) + if (!tframe && this.fillatt.empty() && pp?.isCanvas()) this.fillatt.setSolidColor('white'); else if ((pad?.fFillStyle === 4000) && !this.fillatt.empty()) // special case of transpad.C macro, which set transparent pad this.fillatt.setOpacity(0); + + if (pad && (pad.fFrameBorderMode || (pad.fFrameBorderSize !== 1))) { + this.#border_mode = pad.fFrameBorderMode; + this.#border_size = pad.fFrameBorderSize; + } } if (!tframe && (pad?.fFrameLineColor !== undefined)) this.createAttLine({ color: pad.fFrameLineColor, width: pad.fFrameLineWidth, style: pad.fFrameLineStyle }); - else + else if (tframe) this.createAttLine({ attr: tframe, color: 'black' }); + else + this.createAttLine({ color: gStyle.fFrameLineColor, width: gStyle.fFrameLineWidth, style: gStyle.fFrameLineStyle }); } /** @summary Function called at the end of resize of frame @@ -2362,7 +2546,7 @@ class TFramePainter extends ObjectPainter { this.interactiveRedraw('pad', 'frame'); } - /** @summary Remove all kinds of X/Y function for axes transformation */ + /** @summary Remove all kinds of X/Y function for axes transformation */ cleanXY() { delete this.grx; delete this.gry; @@ -2391,8 +2575,8 @@ class TFramePainter extends ObjectPainter { this.x2_handle?.removeG(); this.y2_handle?.removeG(); - this.draw_g?.selectChild('.axis_layer').selectAll('*').remove(); - this.axes_drawn = false; + this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); + this.#axes_drawn = this.#axes2_drawn = false; } /** @summary Returns frame rectangle plus extra info for hint display */ @@ -2418,41 +2602,37 @@ class TFramePainter extends ObjectPainter { this.scale_ymin = this.scale_ymax = 0; this.scale_zmin = this.scale_zmax = 0; - this.draw_g?.selectChild('.main_layer').selectAll('*').remove(); - this.draw_g?.selectChild('.upper_layer').selectAll('*').remove(); + this.getG()?.selectChild('.main_layer').selectAll('*').remove(); + this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); this.xaxis = null; this.yaxis = null; this.zaxis = null; - if (this.draw_g) { - this.draw_g.selectAll('*').remove(); - this.draw_g.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null); - this.draw_g.remove(); - } + this.getG()?.selectAll('*').remove(); + this.getG()?.on('mousedown', null) + .on('dblclick', null) + .on('wheel', null) + .on('contextmenu', null) + .property('interactive_set', null) + .remove(); - delete this.draw_g; // frame <g> element managet by the pad + this.setG(undefined); // frame <g> element managed by the pad - if (this.keys_handler) { - window.removeEventListener('keydown', this.keys_handler, false); - this.keys_handler = null; + if (this.#keys_handler) { + window.removeEventListener('keydown', this.#keys_handler, false); + this.#keys_handler = undefined; } } /** @summary Cleanup frame */ cleanup() { this.cleanFrameDrawings(); - delete this._click_handler; - delete this._dblclick_handler; - delete this.enabledKeys; + this.#click_handler = undefined; + this.#dblclick_handler = undefined; + this.#enabled_keys = undefined; - const pp = this.getPadPainter(); - if (pp?.frame_painter_ref === this) - delete pp.frame_painter_ref; + this.getPadPainter()?.setFramePainter(this, false); super.cleanup(); } @@ -2460,99 +2640,109 @@ class TFramePainter extends ObjectPainter { /** @summary Redraw TFrame */ redraw(/* reason */) { const pp = this.getPadPainter(); - if (pp) pp.frame_painter_ref = this; // keep direct reference to the frame painter + pp?.setFramePainter(this, true); // first update all attributes from objects this.updateAttributes(); const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)); - let w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)), - rotate = false, fixpos = false, trans; - - if (pp?.options) { - if (pp.options.RotateFrame) rotate = true; - if (pp.options.FixFrame) fixpos = true; - } - - if (rotate) { - trans = `rotate(-90,${lm},${tm}) translate(${lm-h},${tm})`; - [w, h] = [h, w]; - } else - trans = makeTranslate(lm, tm); - - this._frame_x = lm; - this._frame_y = tm; - this._frame_width = w; - this._frame_height = h; - this._frame_rotate = rotate; - this._frame_fixpos = fixpos; - - if (this.mode3d) return this; // no need to create any elements in 3d mode + tm = Math.round(rect.height * (1 - this.fY2NDC)), + rotate = pp?.options?.RotateFrame, + w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), + h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); + + this.#frame_x = lm; + this.#frame_y = tm; + this.#frame_width = rotate ? h : w; + this.#frame_height = rotate ? w : h; + this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); + this.$can_drag = !rotate && !pp?.options?.FixFrame; + + return this.mode3d ? this : this.createFrameG(); + } + /** @summary Create frame element and update all attributes + * @private */ + createFrameG() { // this is svg:g object - container for every other items belonging to frame - this.draw_g = this.getFrameSvg(); - - let top_rect, main_svg; + let g = this.setG(this.getFrameSvg()), + top_rect, main_svg; - if (this.draw_g.empty()) { - this.draw_g = this.getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame'); + if (g.empty()) { + g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); // empty title on the frame required to suppress title of the canvas if (!this.isBatchMode()) - this.draw_g.append('svg:title').text(''); + g.append('svg:title').text(''); - top_rect = this.draw_g.append('svg:path'); + top_rect = g.append('svg:path'); - main_svg = this.draw_g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); + main_svg = g.append('svg:svg') + .attr('class', 'main_layer') + .attr('x', 0) + .attr('y', 0) + .attr('overflow', 'hidden'); - this.draw_g.append('svg:g').attr('class', 'axis_layer'); - this.draw_g.append('svg:g').attr('class', 'upper_layer'); + g.append('svg:g').attr('class', 'axis_layer'); + g.append('svg:g').attr('class', 'upper_layer'); } else { - top_rect = this.draw_g.selectChild('path'); - main_svg = this.draw_g.selectChild('.main_layer'); + top_rect = g.selectChild('path'); + main_svg = g.selectChild('.main_layer'); } - this.axes_drawn = false; + this.#axes_drawn = this.#axes2_drawn = false; - this.draw_g.attr('transform', trans); + g.attr('transform', this.#frame_trans); - top_rect.attr('d', `M0,0H${w}V${h}H0Z`) + top_rect.attr('d', `M0,0H${this.#frame_width}V${this.#frame_height}H0Z`) .call(this.fillatt.func) .call(this.lineatt.func); - main_svg.attr('width', w) - .attr('height', h) - .attr('viewBox', `0 0 ${w} ${h}`); + main_svg.attr('width', this.#frame_width) + .attr('height', this.#frame_height) + .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); + + g.selectAll('.frame_deco').remove(); + if (this.#border_mode && this.fillatt.hasColor()) { + const paths = getBoxDecorations(0, 0, this.#frame_width, this.#frame_height, this.#border_mode, this.#border_size || 2, this.#border_size || 2); + g.insert('svg:path', '.main_layer') + .attr('class', 'frame_deco') + .attr('d', paths[0]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); + g.insert('svg:path', '.main_layer') + .attr('class', 'frame_deco') + .attr('d', paths[1]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); + } return this; } /** @summary Change log state of specified axis + * @param {string} axis - name of axis like 'x' or 'y' * @param {number} value - 0 (linear), 1 (log) or 2 (log2) */ changeAxisLog(axis, value) { const pp = this.getPadPainter(), pad = pp?.getRootPad(true); - if (!pad) return; + if (!pad) + return; - pp._interactively_changed = true; + pp.options._interactively_changed = true; const name = `fLog${axis}`; // do not allow log scale for labels if (!pad[name]) { - if (this.swap_xy && axis === 'x') + if (this.#swap_xy && axis === 'x') axis = 'y'; - else if (this.swap_xy && axis === 'y') + else if (this.#swap_xy && axis === 'y') axis = 'x'; const handle = this[`${axis}_handle`]; - if (handle?.kind === kAxisLabels) return; + if (handle?.kind === kAxisLabels) + return; } if ((value === 'toggle') || (value === undefined)) @@ -2573,23 +2763,82 @@ class TFramePainter extends ObjectPainter { * @desc It could be appended to the histogram menus */ fillContextMenu(menu, kind, obj) { const main = this.getMainPainter(true), - pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - is_pal = kind === 'pal'; + wrk = main?.$stack_hist ? main.getPrimary() : main, + pp = this.getPadPainter(), + pad = pp?.getRootPad(true), + is_pal = kind === 'pal'; - if (is_pal) kind = 'z'; + if (is_pal) + kind = 'z'; if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'x2') || (kind === 'y2')) { - const faxis = obj || this[kind+'axis'], + const faxis = obj || this[kind + 'axis'], handle = this[`${kind}_handle`]; - if (!isFunc(faxis?.TestBit)) - return false; + if (!isFunc(faxis?.TestBit)) + return false; + const hist_painter = handle?.hist_painter || main; + + menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}${clTAxis}.html`); + + menu.sub('Range'); + menu.add('Zoom', () => { + let min = this[`zoom_${kind}min`] ?? this[`${kind}min`], + max = this[`zoom_${kind}max`] ?? this[`${kind}max`]; + if (min === max) { + min = this[`${kind}min`]; + max = this[`${kind}max`]; + } + menu.input('Enter zoom range like: [min, max]', `[${min}, ${max}]`).then(v => { + const arr = JSON.parse(v); + if (arr && Array.isArray(arr) && (arr.length === 2)) { + let flag = false; + if (arr[0] < faxis.fXmin) { + faxis.fFirst = 0; + flag = true; + } else + faxis.fFirst = 1; + if (arr[1] > faxis.fXmax) { + faxis.fLast = faxis.fNbins + 1; + flag = true; + } else + faxis.fLast = faxis.fNbins; + faxis.SetBit(EAxisBits.kAxisRange, flag); + hist_painter?.scanContent(); + this.zoomSingle(kind, arr[0], arr[1], true).then(res => { + if (!res && flag) + this.interactiveRedraw('pad'); + }); + } + }); + }); + menu.add('Unzoom', () => { + this.unzoomSingle(kind).then(res => { + if (!res && (faxis.fFirst !== faxis.fLast)) { + faxis.fFirst = faxis.fLast = 0; + hist_painter?.scanContent(); + this.interactiveRedraw('pad'); + } + }); + }); + if (handle?.value_axis && isFunc(wrk?.accessMM)) { + menu.add('Minimum', () => { + menu.input(`Enter minimum value or ${kNoZoom} as default`, wrk.accessMM(true), 'float').then(v => { + this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; + wrk.accessMM(true, v); + }); + }); + menu.add('Maximum', () => { + menu.input(`Enter maximum value or ${kNoZoom} as default`, wrk.accessMM(false), 'float').then(v => { + this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; + wrk.accessMM(false, v); + }); + }); + } + menu.endsub(); - menu.add(`header: ${kind.toUpperCase()} axis`); - menu.add('Unzoom', () => this.unzoom(kind)); if (pad) { - const member = 'fLog'+kind[0]; - menu.add('sub:SetLog '+kind[0], () => { + const member = 'fLog' + kind[0]; + menu.sub('SetLog ' + kind[0], () => { menu.input('Enter log kind: 0 - off, 1 - log10, 2 - log2, 3 - ln, ...', pad[member], 'int', 0, 10000).then(v => { this.changeAxisLog(kind[0], v); }); @@ -2600,50 +2849,39 @@ class TFramePainter extends ObjectPainter { menu.addchk(pad[member] === 3, 'ln', () => this.changeAxisLog(kind[0], 3)); menu.addchk(pad[member] === 4, 'log4', () => this.changeAxisLog(kind[0], 4)); menu.addchk(pad[member] === 8, 'log8', () => this.changeAxisLog(kind[0], 8)); - menu.add('endsub:'); + menu.endsub(); } menu.addchk(faxis.TestBit(EAxisBits.kMoreLogLabels), 'More log', flag => { - faxis.InvertBit(EAxisBits.kMoreLogLabels); - if (main?.snapid && (kind.length === 1)) - main.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); + faxis.SetBit(EAxisBits.kMoreLogLabels, flag); + if (hist_painter?.getSnapId() && (kind.length === 1)) + hist_painter.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); else this.interactiveRedraw('pad'); }); menu.addchk(handle?.noexp ?? faxis.TestBit(EAxisBits.kNoExponent), 'No exponent', flag => { - if (flag !== faxis.TestBit(EAxisBits.kNoExponent)) - faxis.InvertBit(EAxisBits.kNoExponent); - if (handle) handle.noexp_changed = true; + faxis.SetBit(EAxisBits.kNoExponent, flag); + if (handle) + handle.noexp_changed = true; this[`${kind}_noexp_changed`] = true; - if (main?.snapid && (kind.length === 1)) - main.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); + if (hist_painter?.getSnapId() && (kind.length === 1)) + hist_painter.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); else this.interactiveRedraw('pad'); }); - if ((kind === 'z') && isFunc(main?.fillPaletteMenu)) - main.fillPaletteMenu(menu, !is_pal); - - if ((handle?.kind === kAxisLabels) && (faxis.fNbins > 20)) { - menu.add('Find label', () => menu.input('Label id').then(id => { - if (!id) return; - for (let bin = 0; bin < faxis.fNbins; ++bin) { - const lbl = handle.formatLabels(bin); - if (lbl === id) - return this.zoom(kind, Math.max(0, bin - 4), Math.min(faxis.fNbins, bin+5)); - } - })); - } + if ((kind === 'z') && isFunc(hist_painter?.fillPaletteMenu)) + hist_painter.fillPaletteMenu(menu, !is_pal); - menu.addTAxisMenu(EAxisBits, main || this, faxis, kind); + menu.addTAxisMenu(EAxisBits, hist_painter || this, faxis, kind, handle, this); return true; } const alone = menu.size() === 0; if (alone) - menu.add('header:Frame'); + menu.header('Frame', `${urlClassPrefix}${clTFrame}.html`); else - menu.add('separator'); + menu.separator(); if (this.zoom_xmin !== this.zoom_xmax) menu.add('Unzoom X', () => this.unzoom('x')); @@ -2663,11 +2901,23 @@ class TFramePainter extends ObjectPainter { if (isFunc(main?.getDimension) && (main.getDimension() > 1)) menu.addchk(pad.fLogz, 'SetLogz', () => this.toggleAxisLog('z')); - menu.add('separator'); + menu.separator(); } menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); menu.addAttributesMenu(this, alone ? '' : 'Frame '); + + menu.sub('Border'); + menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], this.#border_mode + 1, v => { + this.#border_mode = v - 1; + this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); + }, 'Frame border mode'); + menu.addSizeMenu('Size', 0, 20, 2, this.#border_size, v => { + this.#border_size = v; + this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); + }, 'Frame border size'); + menu.endsub(); + menu.add('Save to gStyle', () => { gStyle.fPadBottomMargin = this.fY1NDC; gStyle.fPadTopMargin = 1 - this.fY2NDC; @@ -2675,13 +2925,18 @@ class TFramePainter extends ObjectPainter { gStyle.fPadRightMargin = 1 - this.fX2NDC; this.fillatt?.saveToStyle('fFrameFillColor', 'fFrameFillStyle'); this.lineatt?.saveToStyle('fFrameLineColor', 'fFrameLineWidth', 'fFrameLineStyle'); + gStyle.fFrameBorderMode = this.#border_mode; + gStyle.fFrameBorderSize = this.#border_size; }, 'Store frame position and graphical attributes to gStyle'); - menu.add('separator'); + menu.separator(); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.add('endsub:'); + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); + menu.endsub(); return true; } @@ -2694,79 +2949,112 @@ class TFramePainter extends ObjectPainter { } /** @summary Returns frame X position */ - getFrameX() { return this._frame_x || 0; } + getFrameX() { return this.#frame_x || 0; } /** @summary Returns frame Y position */ - getFrameY() { return this._frame_y || 0; } + getFrameY() { return this.#frame_y || 0; } /** @summary Returns frame width */ - getFrameWidth() { return this._frame_width || 0; } + getFrameWidth() { return this.#frame_width || 0; } /** @summary Returns frame height */ - getFrameHeight() { return this._frame_height || 0; } + getFrameHeight() { return this.#frame_height || 0; } /** @summary Returns frame rectangle plus extra info for hint display */ getFrameRect() { return { - x: this._frame_x || 0, - y: this._frame_y || 0, + x: this.#frame_x || 0, + y: this.#frame_y || 0, width: this.getFrameWidth(), height: this.getFrameHeight(), - transform: this.draw_g?.attr('transform') || '', + transform: this.getG()?.attr('transform') || '', hint_delta_x: 0, hint_delta_y: 0 }; } /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was perfromed + * @desc Function will be called every time when frame click was performed * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of click will be disabled */ configureUserClickHandler(handler) { - this._click_handler = isFunc(handler) ? handler : null; + this.#click_handler = isFunc(handler) ? handler : null; } + /** @summary Returns actual click handler */ + getClickHandler() { return this.#click_handler; } + /** @summary Configure user-defined dblclick handler * @desc Function will be called every time when double click was called * As argument, tooltip object with selected bins will be provided * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ configureUserDblclickHandler(handler) { - this._dblclick_handler = isFunc(handler) ? handler : null; + this.#dblclick_handler = isFunc(handler) ? handler : null; } + /** @summary Returns actual double-click handler */ + getDblclickHandler() { return this.#dblclick_handler; } + /** @summary Function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed + * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be un-zoomed * @param {number} xmin * @param {number} xmax * @param {number} [ymin] * @param {number} [ymax] * @param {number} [zmin] * @param {number} [zmax] + * @param [interactive] - if changes was performed interactively * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax) { - if (xmin === 'x') { xmin = xmax; xmax = ymin; ymin = undefined; } else - if (xmin === 'y') { ymax = ymin; ymin = xmax; xmin = xmax = undefined; } else - if (xmin === 'z') { zmin = xmax; zmax = ymin; xmin = xmax = ymin = undefined; } + async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { + if (xmin === 'x') { + xmin = xmax; + xmax = ymin; + interactive = ymax; + ymin = ymax = undefined; + } else if (xmin === 'y') { + interactive = ymax; + ymax = ymin; + ymin = xmax; + xmin = xmax = undefined; + } else if (xmin === 'z') { + interactive = ymax; + zmin = xmax; + zmax = ymin; + xmin = xmax = ymin = ymax = undefined; + } let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), unzoom_x = false, unzoom_y = false, unzoom_z = false; if (zoom_x) { let cnt = 0; - if (xmin <= this.xmin) { xmin = this.xmin; cnt++; } - if (xmax >= this.xmax) { xmax = this.xmax; cnt++; } - if (cnt === 2) { zoom_x = false; unzoom_x = true; } + xmin = this.x_handle?.checkZoomMin(xmin) ?? xmin; + if (xmin <= this.xmin) { + xmin = this.xmin; + cnt++; + } + if (xmax >= this.xmax) { + xmax = this.xmax; + cnt++; + } + if (cnt === 2) { + zoom_x = false; + unzoom_x = true; + } } else unzoom_x = (xmin === xmax) && (xmin === 0); if (zoom_y) { let cnt = 0; - if ((ymin <= this.ymin) || (!this.ymin && this.logy && - ((!this.y_handle?.log_min_nz && ymin < logminfactorY*this.ymax) || (ymin < this.y_handle?.log_min_nz)))) { - ymin = this.ymin; - cnt++; - } - if (ymax >= this.ymax) { ymax = this.ymax; cnt++; } + ymin = this.y_handle?.checkZoomMin(ymin) ?? ymin; + if (ymin <= this.ymin) { + ymin = this.ymin; + cnt++; + } + if (ymax >= this.ymax) { + ymax = this.ymax; + cnt++; + } if ((cnt === 2) && (this.scales_ndim !== 1)) { zoom_y = false; unzoom_y = true; @@ -2776,36 +3064,52 @@ class TFramePainter extends ObjectPainter { if (zoom_z) { let cnt = 0; - if (zmin <= this.zmin) { zmin = this.zmin; cnt++; } - if (zmax >= this.zmax) { zmax = this.zmax; cnt++; } - if ((cnt === 2) && (this.scales_ndim > 2)) { zoom_z = false; unzoom_z = true; } + zmin = this.z_handle?.checkZoomMin(zmin) ?? zmin; + if (zmin <= this.zmin) { + zmin = this.zmin; + cnt++; + } + if (zmax >= this.zmax) { + zmax = this.zmax; + cnt++; + } + if ((cnt === 2) && (this.scales_ndim > 2)) { + zoom_z = false; + unzoom_z = true; + } } else unzoom_z = (zmin === zmax) && (zmin === 0); - let changed = false; // first process zooming (if any) if (zoom_x || zoom_y || zoom_z) { this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) return; + if (!isFunc(obj.canZoomInside)) + return; if (zoom_x && obj.canZoomInside('x', xmin, xmax)) { this.zoom_xmin = xmin; this.zoom_xmax = xmax; changed = true; zoom_x = false; + if (interactive) + this.zoomChangedInteractive('x', interactive); } if (zoom_y && obj.canZoomInside('y', ymin, ymax)) { this.zoom_ymin = ymin; this.zoom_ymax = ymax; changed = true; zoom_y = false; + if (interactive) + this.zoomChangedInteractive('y', interactive); } if (zoom_z && obj.canZoomInside('z', zmin, zmax)) { this.zoom_zmin = zmin; this.zoom_zmax = zmax; changed = true; zoom_z = false; + if (interactive) + this.zoomChangedInteractive('z', interactive); } }); } @@ -2813,8 +3117,11 @@ class TFramePainter extends ObjectPainter { // and process unzoom, if any if (unzoom_x || unzoom_y || unzoom_z) { if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) changed = true; + if (this.zoom_xmin !== this.zoom_xmax) + changed = true; this.zoom_xmin = this.zoom_xmax = 0; + if (interactive) + this.zoomChangedInteractive('x', interactive); } if (unzoom_y) { if (this.zoom_ymin !== this.zoom_ymax) { @@ -2822,53 +3129,72 @@ class TFramePainter extends ObjectPainter { unzoomHistogramYRange(this.getMainPainter()); } this.zoom_ymin = this.zoom_ymax = 0; + if (interactive) + this.zoomChangedInteractive('y', interactive); } if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) changed = true; + if (this.zoom_zmin !== this.zoom_zmax) + changed = true; this.zoom_zmin = this.zoom_zmax = 0; + if (interactive) + this.zoomChangedInteractive('z', interactive); } // than try to unzoom all overlapped objects if (!changed) { - this.getPadPainter()?.painters?.forEach(painter => { + this.forEachPainter(painter => { if (isFunc(painter?.unzoomUserRange)) { if (painter.unzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) changed = true; - } - }); + } + }, 'objects'); } } + if (!changed) + return false; - return changed ? this.interactiveRedraw('pad', 'zoom').then(() => true) : false; + return this.interactiveRedraw('pad', 'zoom').then(() => true); } - /** @summary Provide zooming of single axis - * @desc One can specify names like x/y/z but also second axis x2 or y2 - * @private */ - async zoomSingle(name, vmin, vmax) { - if (!this[name+'_handle']) + /** @summary Zooming of single axis + * @param {String} name - axis name like x/y/z but also second axis x2 or y2 + * @param {Number} vmin - axis minimal value, 0 for unzoom + * @param {Number} vmax - axis maximal value, 0 for unzoom + * @param {Boolean} [interactive] - if change was performed interactively + * @protected */ + async zoomSingle(name, vmin, vmax, interactive) { + const handle = this[`${name}_handle`], name_min = `zoom_${name}min`, name_max = `zoom_${name}max`; + if (!handle && (name !== 'z')) return false; let zoom_v = (vmin !== vmax), unzoom_v = false; if (zoom_v) { let cnt = 0; - if (vmin <= this[name+'min']) { vmin = this[name+'min']; cnt++; } - if (vmax >= this[name+'max']) { vmax = this[name+'max']; cnt++; } - if (cnt === 2) { zoom_v = false; unzoom_v = true; } + vmin = handle?.checkZoomMin(vmin) ?? vmin; + if (vmin <= this[name + 'min']) { + vmin = this[name + 'min']; + cnt++; + } + if (vmax >= this[name + 'max']) { + vmax = this[name + 'max']; + cnt++; + } + if (cnt === 2) { + zoom_v = false; + unzoom_v = true; + } } else unzoom_v = (vmin === vmax) && (vmin === 0); - let changed = false; // first process zooming if (zoom_v) { this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) return; - if (zoom_v && obj.canZoomInside(name[0], vmin, vmax)) { - this[`zoom_${name}min`] = vmin; - this[`zoom_${name}max`] = vmax; + if (zoom_v && isFunc(obj.canZoomInside) && obj.canZoomInside(name[0], vmin, vmax)) { + this[name_min] = vmin; + this[name_max] = vmax; changed = true; zoom_v = false; } @@ -2877,47 +3203,63 @@ class TFramePainter extends ObjectPainter { // and process unzoom, if any if (unzoom_v) { - if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) { + if (this[name_min] !== this[name_max]) { changed = true; - if (name === 'y') unzoomHistogramYRange(this.getMainPainter()); + if (name === 'y') + unzoomHistogramYRange(this.getMainPainter()); } - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; + this[name_min] = this[name_max] = 0; } - if (!changed) return false; + if (!changed) + return false; + + if (interactive) + this.zoomChangedInteractive(name, interactive); return this.interactiveRedraw('pad', 'zoom').then(() => true); } + /** @summary Unzoom single axis */ + async unzoomSingle(name, interactive) { + return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); + } + /** @summary Checks if specified axis zoomed */ isAxisZoomed(axis) { return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; } - /** @summary Unzoom speicified axes + /** @summary Unzoom specified axes * @return {Promise} with boolean flag if zooming changed */ async unzoom(dox, doy, doz) { if (dox === 'all') - return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); + return this.unzoomSingle('x2').then(() => this.unzoomSingle('y2')).then(() => this.unzoom('xyz')); - if ((dox === 'x2') || (dox === 'y2')) { - return this.zoomSingle(dox, 0, 0).then(changed => { - if (changed) this.zoomChangedInteractive(dox, 'unzoom'); - return changed; - }); - } + if ((dox === 'x2') || (dox === 'y2')) + return this.unzoomSingle(dox); - if (typeof dox === 'undefined') dox = doy = doz = true; else - if (isStr(dox)) { doz = dox.indexOf('z') >= 0; doy = dox.indexOf('y') >= 0; dox = dox.indexOf('x') >= 0; } + if (typeof dox === 'undefined') + dox = doy = doz = true; + else if (isStr(dox)) { + doz = dox.indexOf('z') >= 0; + doy = dox.indexOf('y') >= 0; + dox = dox.indexOf('x') >= 0; + } return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined).then(changed => { - if (changed && dox) this.zoomChangedInteractive('x', 'unzoom'); - if (changed && doy) this.zoomChangedInteractive('y', 'unzoom'); - if (changed && doz) this.zoomChangedInteractive('z', 'unzoom'); + doz ? 0 : undefined, doz ? 0 : undefined, + 'unzoom'); + } - return changed; + /** @summary Reset all zoom attributes + * @private */ + resetZoom() { + ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { + this[`zoom_${n}min`] = undefined; + this[`zoom_${n}max`] = undefined; + this[`zoom_changed_${n}`] = undefined; }); } @@ -2931,24 +3273,26 @@ class TFramePainter extends ObjectPainter { if (!axis || axis === 'any') return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) return; + if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) + return; const fld = 'zoom_changed_' + axis; if (value === undefined) return this[fld]; - if (value === 'unzoom') { - // special handling of unzoom, only if was never changed before flag set to true + // special handling of unzoom, only if was never changed before flag set to true + if (value === 'unzoom') this[fld] = (this[fld] === undefined); - return; - } - - if (value) + else if (value) this[fld] = true; } /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; } + revertAxis(axis, pnt) { + if (this.#swap_xy) + axis = (axis[0] === 'x') ? 'y' : 'x'; + return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; + } /** @summary Show axis status message * @desc method called normally when mouse enter main object element @@ -2964,7 +3308,8 @@ class TFramePainter extends ObjectPainter { hint_name = taxis.fName; hint_title = taxis.fTitle || `TAxis object for ${axis_name}`; } - if (this.swap_xy) id = 1 - id; + if (this.#swap_xy) + id = 1 - id; const axis_value = this.revertAxis(axis_name, m[id]); @@ -2974,9 +3319,12 @@ class TFramePainter extends ObjectPainter { /** @summary Add interactive keys handlers * @private */ addKeysHandler() { - if (this.isBatchMode()) return; - FrameInteractive.assign(this); - this.addFrameKeysHandler(); + if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) + return; + + this.#keys_handler = evnt => this.processKeyPress(evnt); + + window.addEventListener('keydown', this.#keys_handler, false); } /** @summary Add interactive functionality to the frame @@ -2985,7 +3333,6 @@ class TFramePainter extends ObjectPainter { if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) return false; - FrameInteractive.assign(this); if (!for_second_axes) this.addBasicInteractivity(); diff --git a/modules/gpad/TPadPainter.mjs b/modules/gpad/TPadPainter.mjs index 67dbfefdf..ca104e064 100644 --- a/modules/gpad/TPadPainter.mjs +++ b/modules/gpad/TPadPainter.mjs @@ -1,13 +1,13 @@ import { gStyle, settings, constants, browser, internals, BIT, - create, toJSON, isBatchMode, loadScript, injectCode, isPromise, getPromise, postponePromise, - isObject, isFunc, isStr, clTObjArray, clTPaveText, clTColor, clTPad, clTFrame, clTStyle, clTLegend, - clTHStack, clTMultiGraph, clTLegendEntry, nsSVG, kTitle } from '../core.mjs'; + create, toJSON, isBatchMode, loadModules, loadScript, injectCode, isPromise, getPromise, postponePromise, + isObject, isFunc, isStr, clTObjArray, clTColor, clTPad, clTFrame, clTStyle, clTLegend, + clTHStack, clTMultiGraph, clTLegendEntry, nsSVG, kTitle, clTList, urlClassPrefix } from '../core.mjs'; import { select as d3_select, rgb as d3_rgb } from '../d3.mjs'; import { ColorPalette, adoptRootColors, getColorPalette, getGrayColors, extendRootColors, getRGBfromTColor, decodeWebCanvasColors } from '../base/colors.mjs'; -import { getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, - getTDatime, convertDate, svgToImage } from '../base/BasePainter.mjs'; -import { ObjectPainter, selectActivePad, getActivePad } from '../base/ObjectPainter.mjs'; +import { prSVG, prJSON, getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, + getTDatime, convertDate, svgToImage, getBoxDecorations } from '../base/BasePainter.mjs'; +import { ObjectPainter, selectActivePad, getActivePad, isPadPainter } from '../base/ObjectPainter.mjs'; import { TAttLineHandler } from '../base/TAttLineHandler.mjs'; import { addCustomFont } from '../base/FontHandler.mjs'; import { addDragHandler } from './TFramePainter.mjs'; @@ -16,61 +16,68 @@ import { ToolbarIcons, registerForResize, saveFile } from '../gui/utils.mjs'; import { BrowserLayout, getHPainter } from '../gui/display.mjs'; -const clTButton = 'TButton', kIsGrayscale = BIT(22); +const clTButton = 'TButton', kIsGrayscale = BIT(22), + // identifier used in TWebCanvas painter + webSnapIds = { kNone: 0, kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; -function getButtonSize(handler, fact) { - return Math.round((fact || 1) * (handler.iscan || !handler.has_canvas ? 16 : 12)); -} +// eslint-disable-next-line one-var +const PadButtonsHandler = { + + getButtonSize(fact) { + const cp = this.getCanvPainter(); + return Math.round((fact || 1) * (cp?.getPadScale() || 1) * (cp === this ? 16 : 12)); + }, + + toggleButtonsVisibility(action, evnt) { + evnt?.preventDefault(); + evnt?.stopPropagation(); -function toggleButtonsVisibility(handler, action, evnt) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - const group = handler.getLayerSvg('btns_layer', handler.this_pad_name), - btn = group.select('[name=\'Toggle\']'); - - if (btn.empty()) return; - - let state = btn.property('buttons_state'); - - if (btn.property('timout_handler')) { - if (action !== 'timeout') clearTimeout(btn.property('timout_handler')); - btn.property('timout_handler', null); - } - - let is_visible = false; - switch (action) { - case 'enable': - is_visible = true; - handler.btns_active_flag = true; - break; - case 'enterbtn': - handler.btns_active_flag = true; - return; // do nothing, just cleanup timeout - case 'timeout': is_visible = false; break; - case 'toggle': - state = !state; - btn.property('buttons_state', state); - is_visible = state; - break; - case 'disable': - case 'leavebtn': - handler.btns_active_flag = false; - if (!state) - btn.property('timout_handler', setTimeout(() => toggleButtonsVisibility(handler, 'timeout'), 1200)); + const group = this.getLayerSvg('btns_layer'), + btn = group.select('[name=\'Toggle\']'); + if (btn.empty()) return; - } - group.selectAll('svg').each(function() { - if (this !== btn.node()) - d3_select(this).style('display', is_visible ? '' : 'none'); - }); -} + let state = btn.property('buttons_state'); + + if (btn.property('timout_handler')) { + if (action !== 'timeout') + clearTimeout(btn.property('timout_handler')); + btn.property('timout_handler', null); + } + + let is_visible = false; + switch (action) { + case 'enable': + is_visible = true; + this.btns_active_flag = true; + break; + case 'enterbtn': + this.btns_active_flag = true; + return; // do nothing, just cleanup timeout + case 'timeout': + break; + case 'toggle': + state = !state; + btn.property('buttons_state', state); + is_visible = state; + break; + case 'disable': + case 'leavebtn': + this.btns_active_flag = false; + if (!state) + btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout'), 1200)); + return; + } + + group.selectAll('svg').each(function() { + if (this !== btn.node()) + d3_select(this).style('display', is_visible ? '' : 'none'); + }); + }, -const PadButtonsHandler = { alignButtons(btns, width, height) { - const sz0 = getButtonSize(this, 1.25), nextx = (btns.property('nextx') || 0) + sz0; + const sz0 = this.getButtonSize(1.25), nextx = (btns.property('nextx') || 0) + sz0; let btns_x, btns_y; if (btns.property('vertical')) { @@ -85,7 +92,7 @@ const PadButtonsHandler = { }, findPadButton(keyname) { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); + const group = this.getLayerSvg('btns_layer'); let found_func = ''; if (!group.empty()) { group.selectAll('svg').each(function() { @@ -97,7 +104,7 @@ const PadButtonsHandler = { }, removePadButtons() { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); + const group = this.getLayerSvg('btns_layer'); if (!group.empty()) { group.selectAll('*').remove(); group.property('nextx', null); @@ -105,27 +112,29 @@ const PadButtonsHandler = { }, showPadButtons() { - const group = this.getLayerSvg('btns_layer', this.this_pad_name); - if (group.empty()) return; + const group = this.getLayerSvg('btns_layer'); + if (group.empty()) + return; // clean all previous buttons group.selectAll('*').remove(); - if (!this._buttons) return; + if (!this._buttons) + return; - const iscan = this.iscan || !this.has_canvas, y = 0; - let ctrl, x = group.property('leftside') ? getButtonSize(this, 1.25) : 0; + const istop = this.isTopPad(), y = 0; + let ctrl, x = group.property('leftside') ? this.getButtonSize(1.25) : 0; - if (this._fast_drawing) { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, getButtonSize(this), 'enlargePad', false) + if (this.isFastDrawing()) { + ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, this.getButtonSize(), 'enlargePad', false) .attr('name', 'Enlarge').attr('x', 0).attr('y', 0) .on('click', evnt => this.clickPadButton('enlargePad', evnt)); } else { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.rect, getButtonSize(this), 'Toggle tool buttons', false) + ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.rect, this.getButtonSize(), 'Toggle tool buttons', false) .attr('name', 'Toggle').attr('x', 0).attr('y', 0) .property('buttons_state', (settings.ToolBar !== 'popup') || browser.touches) - .on('click', evnt => toggleButtonsVisibility(this, 'toggle', evnt)); - ctrl.node()._mouseenter = () => toggleButtonsVisibility(this, 'enable'); - ctrl.node()._mouseleave = () => toggleButtonsVisibility(this, 'disable'); + .on('click', evnt => this.toggleButtonsVisibility('toggle', evnt)); + ctrl.node()._mouseenter = () => this.toggleButtonsVisibility('enable'); + ctrl.node()._mouseleave = () => this.toggleButtonsVisibility('disable'); for (let k = 0; k < this._buttons.length; ++k) { const item = this._buttons[k]; @@ -136,11 +145,11 @@ const PadButtonsHandler = { if (!btn) btn = ToolbarIcons.circle; - const svg = ToolbarIcons.createSVG(group, btn, getButtonSize(this), - item.tooltip + (iscan ? '' : (` on pad ${this.this_pad_name}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); + const svg = ToolbarIcons.createSVG(group, btn, this.getButtonSize(), + item.tooltip + (istop ? '' : (` on pad ${this.getPadName()}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); if (group.property('vertical')) - svg.attr('x', y).attr('y', x); + svg.attr('x', y).attr('y', x); else svg.attr('x', x).attr('y', y); @@ -149,10 +158,10 @@ const PadButtonsHandler = { .attr('key', item.keyname || null) .on('click', evnt => this.clickPadButton(item.funcname, evnt)); - svg.node()._mouseenter = () => toggleButtonsVisibility(this, 'enterbtn'); - svg.node()._mouseleave = () => toggleButtonsVisibility(this, 'leavebtn'); + svg.node()._mouseenter = () => this.toggleButtonsVisibility('enterbtn'); + svg.node()._mouseleave = () => this.toggleButtonsVisibility('leavebtn'); - x += getButtonSize(this, 1.25); + x += this.getButtonSize(1.25); } } @@ -170,19 +179,16 @@ const PadButtonsHandler = { Object.assign(painter, this); } -}, // PadButtonsHandler - -// identifier used in TWebCanvas painter -webSnapIds = { kNone: 0, kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; +}; // PadButtonsHandler /** @summary Fill TWebObjectOptions for painter * @private */ function createWebObjectOptions(painter) { - if (!painter?.snapid) + if (!painter?.getSnapId()) return null; - const obj = { _typename: 'TWebObjectOptions', snapid: painter.snapid.toString(), opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; + const obj = { _typename: 'TWebObjectOptions', snapid: painter.getSnapId(), opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; if (isFunc(painter.fillWebObjectOptions)) painter.fillWebObjectOptions(obj); return obj; @@ -196,29 +202,94 @@ function createWebObjectOptions(painter) { class TPadPainter extends ObjectPainter { + #iscan; // is canvas flag + #pad_name; // name of the pad + #pad; // TPad object + #painters; // painters in the pad + #pad_scale; // scale factor of the pad + #pad_x; // pad x coordinate + #pad_y; // pad y coordinate + #pad_width; // pad width + #pad_height; // pad height + #doing_draw; // drawing handles + #pad_draw_disabled; // disable drawing of the pad + #last_grayscale; // grayscale change flag + #custom_palette; // custom palette + #custom_colors; // custom colors + #custom_palette_indexes; // custom palette indexes + #custom_palette_colors; // custom palette colors + #frame_painter_ref; // frame painter + #main_painter_ref; // main painter on the pad + #snap_primitives; // stored snap primitives from web canvas + #has_execs; // indicate is pad has TExec objects assigned + #deliver_move_events; // deliver move events to server + #readonly; // if changes on pad is not allowed + #num_primitives; // number of primitives + #num_specials; // number of special objects - if counted + #auto_color_cnt; // counter used in assigning auto colors + #auto_palette; // palette for creating of automatic colors + #fixed_size; // fixed size flag + #has_canvas; // indicate if top canvas painter exists + #fast_drawing; // fast drawing flag + #resize_tmout; // timeout handle for resize + #start_draw_tm; // time when start drawing primitives + /** @summary constructor * @param {object|string} dom - DOM element for drawing or element id * @param {object} pad - TPad object to draw - * @param {boolean} [iscan] - if TCanvas object */ - constructor(dom, pad, iscan) { + * @param {String} [opt] - draw option + * @param {boolean} [iscan] - if TCanvas object + * @param [add_to_primitives] - add pad painter to canvas + * */ + constructor(dom, pad, opt, iscan, add_to_primitives) { super(dom, pad); - this.pad = pad; - this.iscan = iscan; // indicate if working with canvas - this.this_pad_name = ''; - if (!this.iscan && pad?.fName) { - this.this_pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name + this.#pad = pad; + this.#iscan = iscan; // indicate if working with canvas + this.#pad_name = ''; + if (!iscan && pad?.fName) { + this.#pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name const regexp = /^[A-Za-z][A-Za-z0-9_]*$/; - if (!regexp.test(this.this_pad_name) || ((this.this_pad_name === 'button') && (pad._typename === clTButton))) - this.this_pad_name = 'jsroot_pad_' + internals.id_counter++; + if (!regexp.test(this.#pad_name) || ((this.#pad_name === 'button') && (pad._typename === clTButton))) + this.#pad_name = 'jsroot_pad_' + internals.id_counter++; } - this.painters = []; // complete list of all painters in the pad - this.has_canvas = true; + this.#painters = []; // complete list of all painters in the pad + this.#has_canvas = true; this.forEachPainter = this.forEachPainterInPad; const d = this.selectDom(); if (!d.empty() && d.property('_batch_mode')) this.batch_mode = true; + + if (opt !== undefined) + this.decodeOptions(opt); + + if (add_to_primitives) { + if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { + // one can draw pad without canvas + this.#has_canvas = false; + this.#pad_name = ''; + this.setTopPainter(); + } else { + // pad painter will be registered in the parent pad + this.addToPadPrimitives(); + } + } + + if (pad?.$disable_drawing) + this.#pad_draw_disabled = true; } + /** @summary returns pad painter + * @protected */ + getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } + + /** @summary returns canvas painter + * @protected */ + getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } + + /** @summary Returns pad name + * @protected */ + getPadName() { return this.#pad_name; } + /** @summary Indicates that drawing runs in batch mode * @private */ isBatchMode() { @@ -228,10 +299,7 @@ class TPadPainter extends ObjectPainter { if (isBatchMode()) return true; - if (!this.iscan && this.has_canvas) - return this.getCanvPainter()?.isBatchMode(); - - return false; + return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); } /** @summary Indicates that is is Root6 pad painter @@ -239,65 +307,103 @@ class TPadPainter extends ObjectPainter { isRoot6() { return true; } /** @summary Returns true if pad is editable */ - isEditable() { - return this.pad?.fEditable ?? true; + isEditable() { return this.#pad?.fEditable ?? true; } + + /** @summary Returns true if button */ + isButton() { return this.matchObjectType(clTButton); } + + /** @summary Returns true if read-only mode is enabled */ + isReadonly() { return this.#readonly; } + + /** @summary Returns true if it is canvas + * @param {Boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ + isCanvas(is_online = false) { + if (!this.#iscan) + return false; + if (is_online === true) + return isFunc(this.getWebsocket) && this.getWebsocket(); + return isStr(is_online) ? this.#iscan === is_online : true; } - /** @summary Returns SVG element for the pad itself - * @private */ - svg_this_pad() { - return this.getPadSvg(this.this_pad_name); + /** @summary Returns true if it is canvas or top pad without canvas */ + isTopPad() { return this.isCanvas() || !this.#has_canvas; } + + /** @summary Canvas main svg element + * @return {object} d3 selection with canvas svg + * @protected */ + getCanvSvg() { return this.selectDom().select('.root_canvas'); } + + /** @summary Pad svg element + * @return {object} d3 selection with pad svg + * @protected */ + getPadSvg() { + const c = this.getCanvSvg(); + if (!this.#pad_name || c.empty()) + return c; + + return c.select('.primitives_layer .__root_pad_' + this.#pad_name); + } + + /** @summary Method selects immediate layer under canvas/pad main element + * @param {string} name - layer name lik 'primitives_layer', 'btns_layer', 'info_layer' + * @protected */ + getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } + + /** @summary Returns svg element for the frame in current pad + * @protected */ + getFrameSvg() { + const layer = this.getLayerSvg('primitives_layer'); + if (layer.empty()) + return layer; + let node = layer.node().firstChild; + while (node) { + const elem = d3_select(node); + if (elem.classed('root_frame')) + return elem; + node = node.nextSibling; + } + return d3_select(null); } /** @summary Returns main painter on the pad * @desc Typically main painter is TH1/TH2 object which is drawing axes * @private */ - getMainPainter() { - return this.main_painter_ref || null; - } + getMainPainter() { return this.#main_painter_ref || null; } /** @summary Assign main painter on the pad * @desc Typically main painter is TH1/TH2 object which is drawing axes * @private */ setMainPainter(painter, force) { - if (!this.main_painter_ref || force) - this.main_painter_ref = painter; + if (!this.#main_painter_ref || force) + this.#main_painter_ref = painter; } /** @summary cleanup pad and all primitives inside */ cleanup() { - if (this._doing_draw) + if (this.#doing_draw) console.error('pad drawing is not completed when cleanup is called'); - this.painters.forEach(p => p.cleanup()); + this.#painters.forEach(p => p.cleanup()); - const svg_p = this.svg_this_pad(); + const svg_p = this.getPadSvg(); if (!svg_p.empty()) { svg_p.property('pad_painter', null); - if (!this.iscan) svg_p.remove(); - } - - delete this.main_painter_ref; - delete this.frame_painter_ref; - delete this.pads_cache; - delete this.custom_palette; - delete this._pad_x; - delete this._pad_y; - delete this._pad_width; - delete this._pad_height; - delete this._doing_draw; - delete this._interactively_changed; - delete this._snap_primitives; - delete this._last_grayscale; - delete this._custom_colors; - delete this._custom_palette_indexes; - delete this._custom_palette_colors; - delete this.root_colors; - - this.painters = []; - this.pad = null; - this.this_pad_name = undefined; - this.has_canvas = false; + if (!this.isCanvas()) + svg_p.remove(); + } + + this.#main_painter_ref = undefined; + this.#frame_painter_ref = undefined; + this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; + this.#doing_draw = undefined; + this.#snap_primitives = undefined; + this.#last_grayscale = undefined; + this.#custom_palette = this.#custom_colors = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; + + this.#painters = []; + this.#pad = undefined; + this.#pad_name = undefined; + this.#has_canvas = false; selectActivePad({ pp: this, active: false }); @@ -306,19 +412,31 @@ class TPadPainter extends ObjectPainter { /** @summary Returns frame painter inside the pad * @private */ - getFramePainter() { return this.frame_painter_ref; } + getFramePainter() { return this.#frame_painter_ref; } + + /** @summary Assign actual frame painter + * @private */ + setFramePainter(fp, on) { + if (on) + this.#frame_painter_ref = fp; + else if (this.#frame_painter_ref === fp) + this.#frame_painter_ref = undefined; + } /** @summary get pad width */ - getPadWidth() { return this._pad_width || 0; } + getPadWidth() { return this.#pad_width || 0; } /** @summary get pad height */ - getPadHeight() { return this._pad_height || 0; } + getPadHeight() { return this.#pad_height || 0; } + + /** @summary get pad height */ + getPadScale() { return this.#pad_scale || 1; } /** @summary get pad rect */ getPadRect() { return { - x: this._pad_x || 0, - y: this._pad_y || 0, + x: this.#pad_x || 0, + y: this.#pad_y || 0, width: this.getPadWidth(), height: this.getPadHeight() }; @@ -334,27 +452,28 @@ class TPadPainter extends ObjectPainter { return false; } - /** @summary Returns frame coordiantes - also when frame is not drawn */ + /** @summary Returns frame coordinates - also when frame is not drawn */ getFrameRect() { const fp = this.getFramePainter(); - if (fp) return fp.getFrameRect(); + if (fp) + return fp.getFrameRect(); const w = this.getPadWidth(), h = this.getPadHeight(), rect = {}; - if (this.pad) { - rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.pad.fLeftMargin, this.pad.fRightMargin))*w); - rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.pad.fBottomMargin, this.pad.fTopMargin))*h); + if (this.#pad) { + rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fLeftMargin, this.#pad.fRightMargin)) * w); + rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fBottomMargin, this.#pad.fTopMargin)) * h); } else { - rect.szx = Math.round(0.5*w); - rect.szy = Math.round(0.5*h); + rect.szx = Math.round(0.5 * w); + rect.szy = Math.round(0.5 * h); } - rect.width = 2*rect.szx; - rect.height = 2*rect.szy; - rect.x = Math.round(w/2 - rect.szx); - rect.y = Math.round(h/2 - rect.szy); + rect.width = 2 * rect.szx; + rect.height = 2 * rect.szy; + rect.x = Math.round(w / 2 - rect.szx); + rect.y = Math.round(h / 2 - rect.szy); rect.hint_delta_x = rect.szx; rect.hint_delta_y = rect.szy; rect.transform = makeTranslate(rect.x, rect.y) || ''; @@ -364,95 +483,149 @@ class TPadPainter extends ObjectPainter { /** @summary return RPad object */ getRootPad(is_root6) { - return (is_root6 === undefined) || is_root6 ? this.pad : null; + return (is_root6 === undefined) || is_root6 ? this.#pad : null; } - /** @summary Cleanup primitives from pad - selector lets define which painters to remove */ + /** @summary Cleanup primitives from pad - selector lets define which painters to remove + * @return true if any painter was removed */ cleanPrimitives(selector) { - if (!isFunc(selector)) return; + // remove all primitives + if (selector === true) + selector = () => true; + + if (!isFunc(selector)) + return false; - for (let k = this.painters.length-1; k >= 0; --k) { - if (selector(this.painters[k])) { - this.painters[k].cleanup(); - this.painters.splice(k, 1); + let is_any = false; + + for (let k = this.#painters.length - 1; k >= 0; --k) { + const subp = this.#painters[k]; + if (!subp || selector(subp)) { + subp?.cleanup(); + this.#painters.splice(k, 1); + is_any = true; } } + + return is_any; } /** @summary Removes and cleanup specified primitive * @desc also secondary primitives will be removed * @return new index to continue loop or -111 if main painter removed * @private */ - removePrimitive(indx) { - const prim = this.painters[indx], arr = []; - let resindx = indx; - for (let k = this.painters.length-1; k >= 0; --k) { - if ((k === indx) || this.painters[k].isSecondary(prim)) { - arr.push(this.painters[k]); - this.painters.splice(k, 1); - if (k <= indx) resindx--; + removePrimitive(arg, clean_only_secondary) { + let indx, prim; + if (Number.isInteger(arg)) { + indx = arg; + prim = this.#painters[indx]; + } else { + indx = this.#painters.indexOf(arg); + prim = arg; + } + if (indx < 0) + return indx; + + const arr = [], get_main = clean_only_secondary ? this.getMainPainter() : null; + let resindx = indx - 1; // object removed itself + arr.push(prim); + this.#painters.splice(indx, 1); + + // loop to extract all dependent painters + let len0 = 0; + while (len0 < arr.length) { + for (let k = this.#painters.length - 1; k >= 0; --k) { + if (this.#painters[k].isSecondary(arr[len0])) { + arr.push(this.#painters[k]); + this.#painters.splice(k, 1); + if (k < indx) + resindx--; + } } + len0++; } arr.forEach(painter => { - painter.cleanup(); - if (this.main_painter_ref === painter) { - delete this.main_painter_ref; + if ((painter !== prim) || !clean_only_secondary) + painter.cleanup(); + if (this.getMainPainter() === painter) { + this.setMainPainter(undefined, true); resindx = -111; } }); + // when main painter disappears because of special cleanup - also reset zooming + if (clean_only_secondary && get_main && !this.getMainPainter()) + this.getFramePainter()?.resetZoom(); + return resindx; } - /** @summary returns custom palette associated with pad or top canvas + /** @summary returns custom palette associated with pad or top canvas * @private */ - getCustomPalette() { - return this.custom_palette || this.getCanvPainter()?.custom_palette; + getCustomPalette(no_recursion) { + return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); } - /** @summary Returns number of painters - * @private */ - getNumPainters() { return this.painters.length; } + _getCustomPaletteIndexes() { return this.#custom_palette_indexes; } /** @summary Provides automatic color * @desc Uses ROOT colors palette if possible * @private */ getAutoColor(numprimitives) { - if (!numprimitives) - numprimitives = this._num_primitives || 5; - if (numprimitives < 2) numprimitives = 2; + numprimitives = Math.max(numprimitives || (this.#num_primitives || 5) - (this.#num_specials || 0), 2); - let indx = this._auto_color ?? 0; - this._auto_color = (indx + 1) % numprimitives; - if (indx >= numprimitives) indx = numprimitives - 1; + let indx = this.#auto_color_cnt ?? 0; + this.#auto_color_cnt = (indx + 1) % numprimitives; + if (indx >= numprimitives) + indx = numprimitives - 1; - const indexes = this._custom_palette_indexes || this.getCanvPainter()?._custom_palette_indexes; + let indexes = this._getCustomPaletteIndexes(); + if (!indexes) { + const cp = this.getCanvPainter(); + if ((cp !== this) && isFunc(cp?._getCustomPaletteIndexes)) + indexes = cp._getCustomPaletteIndexes(); + } if (indexes?.length) { const p = Math.round(indx * (indexes.length - 3) / (numprimitives - 1)); return indexes[p]; } - if (!this._auto_palette) - this._auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); - const palindx = Math.round(indx * (this._auto_palette.getLength()-3) / (numprimitives-1)), - colvalue = this._auto_palette.getColor(palindx); + if (!this.#auto_palette) + this.#auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); + const palindx = Math.round(indx * (this.#auto_palette.getLength() - 3) / (numprimitives - 1)), + colvalue = this.#auto_palette.getColor(palindx); return this.addColor(colvalue); } + /** @summary Returns number of painters + * @protected */ + getNumPainters() { return this.#painters.length; } + + /** @summary Add painter to pad list of painters + * @protected */ + addToPrimitives(painter) { + if (this.#painters.indexOf(painter) < 0) + this.#painters.push(painter); + return this; + } + /** @summary Call function for each painter in pad * @param {function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and subpads, 'objects' only for object in current pad + * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad * @private */ forEachPainterInPad(userfunc, kind) { - if (!kind) kind = 'all'; - if (kind !== 'objects') userfunc(this); - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; + if (!kind) + kind = 'all'; + if (kind !== 'objects') + userfunc(this); + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') sub.forEachPainterInPad(userfunc, kind); + if (kind !== 'objects') + sub.forEachPainterInPad(userfunc, kind); } else if (kind !== 'pads') userfunc(sub); } @@ -467,47 +640,49 @@ class TPadPainter extends ObjectPainter { /** @summary Generate pad events, normally handled by GED * @desc in pad painter, while pad may be drawn without canvas * @private */ - producePadEvent(what, padpainter, painter, position, place) { + producePadEvent(what, padpainter, painter, position) { if ((what === 'select') && isFunc(this.selectActivePad)) this.selectActivePad(padpainter, painter, position); if (isFunc(this.pad_events_receiver)) - this.pad_events_receiver({ what, padpainter, painter, position, place }); + this.pad_events_receiver({ what, padpainter, painter, position }); } /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos, place) { - const istoppad = this.iscan || !this.has_canvas, - canp = istoppad ? this : this.getCanvPainter(); + selectObjectPainter(painter, pos) { + const canp = this.isTopPad() ? this : this.getCanvPainter(); - if (painter === undefined) painter = this; + if (painter === undefined) + painter = this; - if (pos && !istoppad) - pos = getAbsPosInCanvas(this.svg_this_pad(), pos); + if (pos && !this.isTopPad()) + pos = getAbsPosInCanvas(this.getPadSvg(), pos); selectActivePad({ pp: this, active: true }); - canp?.producePadEvent('select', this, painter, pos, place); + canp?.producePadEvent('select', this, painter, pos); } /** @summary Draw pad active border * @private */ drawActiveBorder(svg_rect, is_active) { if (is_active !== undefined) { - if (this.is_active_pad === is_active) return; + if (this.is_active_pad === is_active) + return; this.is_active_pad = is_active; } - if (this.is_active_pad === undefined) return; + if (this.is_active_pad === undefined) + return; if (!svg_rect) - svg_rect = this.iscan ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.svg_this_pad().selectChild('.root_pad_border'); + svg_rect = this.isCanvas() ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.getPadSvg().selectChild('.root_pad_border'); const cp = this.getCanvPainter(); let lineatt = this.is_active_pad && cp?.highlight_gpad ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; - - if (!lineatt) lineatt = new TAttLineHandler({ color: 'none' }); + if (!lineatt) + lineatt = new TAttLineHandler({ color: 'none' }); svg_rect.call(lineatt.func); } @@ -515,52 +690,74 @@ class TPadPainter extends ObjectPainter { /** @summary Set fast drawing property depending on the size * @private */ setFastDrawing(w, h) { - const was_fast = this._fast_drawing; - this._fast_drawing = settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this._fast_drawing) + const was_fast = this.#fast_drawing; + this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); + if (was_fast !== this.#fast_drawing) this.showPadButtons(); } + /** @summary Return fast drawing flag + * @private */ + isFastDrawing() { return this.#fast_drawing; } + /** @summary Returns true if canvas configured with grayscale * @private */ isGrayscale() { - if (!this.iscan) return false; - return this.pad?.TestBit(kIsGrayscale) ?? false; + if (!this.isCanvas()) + return false; + return this.#pad?.TestBit(kIsGrayscale) ?? false; + } + + /** @summary Returns true if default pad range is configured + * @private */ + isDefaultPadRange() { + if (!this.#pad) + return true; + return (this.#pad.fX1 === 0) && (this.#pad.fX2 === 1) && (this.#pad.fY1 === 0) && (this.#pad.fY2 === 1); } /** @summary Set grayscale mode for the canvas * @private */ setGrayscale(flag) { - if (!this.iscan) return; + if (!this.isTopPad()) + return; let changed = false; if (flag === undefined) { - flag = this.pad?.TestBit(kIsGrayscale) ?? false; - changed = (this._last_grayscale !== undefined) && (this._last_grayscale !== flag); - } else if (flag !== this.pad?.TestBit(kIsGrayscale)) { - this.pad?.InvertBit(kIsGrayscale); + flag = this.#pad?.TestBit(kIsGrayscale) ?? false; + changed = (this.#last_grayscale !== undefined) && (this.#last_grayscale !== flag); + } else if (flag !== this.#pad?.TestBit(kIsGrayscale)) { + this.#pad?.InvertBit(kIsGrayscale); changed = true; } - if (changed) - this.forEachPainter(p => { delete p._color_palette; }); + if (changed) { + this.forEachPainter(p => { + if (isFunc(p.clearHistPalette)) + p.clearHistPalette(); + }); + } - this.root_colors = flag ? getGrayColors(this._custom_colors) : this._custom_colors; + this.setColors(flag ? getGrayColors(this.#custom_colors) : this.#custom_colors); - this._last_grayscale = flag; + this.#last_grayscale = flag; - this.custom_palette = this._custom_palette_colors ? new ColorPalette(this._custom_palette_colors, flag) : null; + this.#custom_palette = this.#custom_palette_colors ? new ColorPalette(this.#custom_palette_colors, flag) : null; } + /** @summary Set fixed-size canvas + * @private */ + _setFixedSize(on) { this.#fixed_size = on; } + /** @summary Create SVG element for canvas */ createCanvasSvg(check_resize, new_size) { const is_batch = this.isBatchMode(), lmt = 5; - let factor = null, svg = null, rect = null, btns, info, frect; + let factor, svg, rect, btns, info, frect; if (check_resize > 0) { - if (this._fixed_size) - return check_resize > 1; // flag used to force re-drawing of all subpads + if (this.#fixed_size) + return check_resize > 1; // flag used to force re-drawing of all sub-pads svg = this.getCanvSvg(); if (svg.empty()) @@ -574,9 +771,9 @@ class TPadPainter extends ObjectPainter { return false; if (!is_batch) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + btns = this.getLayerSvg('btns_layer'); - info = this.getLayerSvg('info_layer', this.this_pad_name); + info = this.getLayerSvg('info_layer'); frect = svg.selectChild('.canvas_fillrect'); } else { const render_to = this.selectDom(); @@ -587,7 +784,6 @@ class TPadPainter extends ObjectPainter { svg = render_to.append('svg') .attr('class', 'jsroot root_canvas') .property('pad_painter', this) // this is custom property - .property('current_pad', '') // this is custom property .property('redraw_by_resize', false); // could be enabled to force redraw by each resize this.setTopPainter(); // assign canvas as top painter of that element @@ -597,7 +793,10 @@ class TPadPainter extends ObjectPainter { else if (!this.online_canvas) svg.append('svg:title').text('ROOT canvas'); - if (!is_batch || (this.pad.fFillStyle > 0)) + if (!is_batch) + svg.style('user-select', settings.UserSelect || null); + + if (!is_batch || (this.#pad.fFillStyle > 0)) frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); if (!is_batch) { @@ -618,14 +817,15 @@ class TPadPainter extends ObjectPainter { } factor = 0.66; - if (this.pad?.fCw && this.pad?.fCh && (this.pad?.fCw > 0)) { - factor = this.pad.fCh / this.pad.fCw; - if ((factor < 0.1) || (factor > 10)) factor = 0.66; + if (this.#pad?.fCw && this.#pad?.fCh && (this.#pad?.fCw > 0)) { + factor = this.#pad.fCh / this.#pad.fCw; + if ((factor < 0.1) || (factor > 10)) + factor = 0.66; } - if (this._fixed_size) { + if (this.#fixed_size) { render_to.style('overflow', 'auto'); - rect = { width: this.pad.fCw, height: this.pad.fCh }; + rect = { width: this.#pad.fCw, height: this.#pad.fCh }; if (!rect.width || !rect.height) rect = getElementRect(render_to); } else @@ -634,15 +834,17 @@ class TPadPainter extends ObjectPainter { this.setGrayscale(); - this.createAttFill({ attr: this.pad }); + this.createAttFill({ attr: this.#pad }); if ((rect.width <= lmt) || (rect.height <= lmt)) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - if (this._pad_width && this._pad_height) { + if (!this.hasSnapId()) { + svg.style('display', 'none'); + console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); + } + if (this.#pad_width && this.#pad_height) { // use last valid dimensions - rect.width = this._pad_width; - rect.height = this._pad_height; + rect.width = this.#pad_width; + rect.height = this.#pad_height; } else { // just to complete drawing. rect.width = 800; @@ -653,50 +855,47 @@ class TPadPainter extends ObjectPainter { svg.attr('x', 0).attr('y', 0).style('position', 'absolute'); - if (this._fixed_size) + if (this.#fixed_size) svg.attr('width', rect.width).attr('height', rect.height); else svg.style('width', '100%').style('height', '100%').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - svg.style('filter', settings.DarkMode || this.pad?.$dark ? 'invert(100%)' : null); + svg.style('filter', settings.DarkMode || this.#pad?.$dark ? 'invert(100%)' : null); + + this.#pad_scale = settings.CanvasScale || 1; + this.#pad_x = 0; + this.#pad_y = 0; + this.#pad_width = rect.width * this.#pad_scale; + this.#pad_height = rect.height * this.#pad_scale; - svg.attr('viewBox', `0 0 ${rect.width} ${rect.height}`) + svg.attr('viewBox', `0 0 ${this.#pad_width} ${this.#pad_height}`) .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio .property('height_factor', factor) - .property('draw_x', 0) - .property('draw_y', 0) - .property('draw_width', rect.width) - .property('draw_height', rect.height); + .property('draw_x', this.#pad_x) + .property('draw_y', this.#pad_y) + .property('draw_width', this.#pad_width) + .property('draw_height', this.#pad_height); - this._pad_x = 0; - this._pad_y = 0; - this._pad_width = rect.width; - this._pad_height = rect.height; + this.addPadBorder(svg, frect); - if (frect) { - frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) - .call(this.fillatt.func); - this.drawActiveBorder(frect); - } - - this.setFastDrawing(rect.width * (1 - this.pad.fLeftMargin - this.pad.fRightMargin), rect.height * (1 - this.pad.fBottomMargin - this.pad.fTopMargin)); + this.setFastDrawing(this.#pad_width * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), this.#pad_height * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); if (this.alignButtons && btns) - this.alignButtons(btns, rect.width, rect.height); + this.alignButtons(btns, this.#pad_width, this.#pad_height); let dt = info.selectChild('.canvas_date'); if (!gStyle.fOptDate) dt.remove(); - else { + else { if (dt.empty()) - dt = info.append('text').attr('class', 'canvas_date'); - const posy = Math.round(rect.height * (1 - gStyle.fDateY)), + dt = info.append('text').attr('class', 'canvas_date'); + const posy = Math.round(this.#pad_height * (1 - gStyle.fDateY)), date = new Date(); - let posx = Math.round(rect.width * gStyle.fDateX); + let posx = Math.round(this.#pad_width * gStyle.fDateX); if (!is_batch && (posx < 25)) posx = 25; if (gStyle.fOptDate > 3) - date.setTime(gStyle.fOptDate*1000); + date.setTime(gStyle.fOptDate * 1000); makeTranslate(dt, posx, posy) .style('text-anchor', 'start') @@ -715,14 +914,14 @@ class TPadPainter extends ObjectPainter { /** @summary Draw item name on canvas if gStyle.fOptFile is configured * @private */ drawItemNameOnCanvas(item_name) { - const info = this.getLayerSvg('info_layer', this.this_pad_name); + const info = this.getLayerSvg('info_layer'); let df = info.selectChild('.canvas_item'); const fitem = getHPainter().findRootFileForItem(item_name), fname = (gStyle.fOptFile === 3) ? item_name : ((gStyle.fOptFile === 2) ? fitem?._fullurl : fitem?._name); if (!gStyle.fOptFile || !fname) df.remove(); - else { + else { if (df.empty()) df = info.append('text').attr('class', 'canvas_item'); const rect = this.getPadRect(); @@ -738,9 +937,9 @@ class TPadPainter extends ObjectPainter { /** @summary Return true if this pad enlarged */ isPadEnlarged() { - if (this.iscan || !this.has_canvas) + if (this.isTopPad()) return this.enlargeMain('state') === 'on'; - return this.getCanvSvg().property('pad_enlarged') === this.pad; + return this.getCanvSvg().property('pad_enlarged') === this.#pad; } /** @summary Enlarge pad draw element when possible */ @@ -748,25 +947,27 @@ class TPadPainter extends ObjectPainter { evnt?.preventDefault(); evnt?.stopPropagation(); - // ignore double click on canvas itself for enlarge - if (is_dblclick && this._websocket && (this.enlargeMain('state') === 'off')) + // ignore double click on online canvas itself for enlarge + if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) return; const svg_can = this.getCanvSvg(), pad_enlarged = svg_can.property('pad_enlarged'); - if (this.iscan || !this.has_canvas || (!pad_enlarged && !this.hasObjectsToDraw() && !this.painters)) { - if (this._fixed_size) return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) return; + if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { + if (this.#fixed_size) + return; // canvas cannot be enlarged in such mode + if (!this.enlargeMain(is_escape ? false : 'toggle')) + return; if (this.enlargeMain('state') === 'off') svg_can.property('pad_enlarged', null); else selectActivePad({ pp: this, active: true }); } else if (!pad_enlarged && !is_escape) { this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.pad); + svg_can.property('pad_enlarged', this.#pad); selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.pad) { + } else if (pad_enlarged === this.#pad) { this.enlargeMain(false); svg_can.property('pad_enlarged', null); } else if (!is_escape && is_dblclick) @@ -778,7 +979,7 @@ class TPadPainter extends ObjectPainter { /** @summary Create main SVG element for pad * @return true when pad is displayed and all its items should be redrawn */ createPadSvg(only_resize) { - if (!this.has_canvas) { + if (this.isTopPad()) { this.createCanvasSvg(only_resize ? 2 : 0); return true; } @@ -787,34 +988,38 @@ class TPadPainter extends ObjectPainter { width = svg_can.property('draw_width'), height = svg_can.property('draw_height'), pad_enlarged = svg_can.property('pad_enlarged'), - pad_visible = !this.pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.pad)), + pad_visible = !this.#pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.#pad)), is_batch = this.isBatchMode(); - let w = Math.round(this.pad.fAbsWNDC * width), - h = Math.round(this.pad.fAbsHNDC * height), - x = Math.round(this.pad.fAbsXlowNDC * width), - y = Math.round(height * (1 - this.pad.fAbsYlowNDC)) - h, + let w = Math.round(this.#pad.fAbsWNDC * width), + h = Math.round(this.#pad.fAbsHNDC * height), + x = Math.round(this.#pad.fAbsXlowNDC * width), + y = Math.round(height * (1 - this.#pad.fAbsYlowNDC)) - h, svg_pad, svg_border, btns; - if (pad_enlarged === this.pad) { w = width; h = height; x = y = 0; } + if (pad_enlarged === this.#pad) { + w = width; + h = height; + x = y = 0; + } if (only_resize) { - svg_pad = this.svg_this_pad(); + svg_pad = this.getPadSvg(); svg_border = svg_pad.selectChild('.root_pad_border'); if (!is_batch) - btns = this.getLayerSvg('btns_layer', this.this_pad_name); + btns = this.getLayerSvg('btns_layer'); this.addPadInteractive(true); } else { svg_pad = svg_can.selectChild('.primitives_layer') .append('svg:svg') // svg used to blend all drawings outside - .classed('__root_pad_' + this.this_pad_name, true) - .attr('pad', this.this_pad_name) // set extra attribute to mark pad name + .classed('__root_pad_' + this.#pad_name, true) + .attr('pad', this.#pad_name) // set extra attribute to mark pad name .property('pad_painter', this); // this is custom property if (!is_batch) - svg_pad.append('svg:title').text('subpad ' + this.this_pad_name); + svg_pad.append('svg:title').text('subpad ' + this.#pad_name); // need to check attributes directly while attributes objects will be created later - if (!is_batch || (this.pad.fFillStyle > 0) || ((this.pad.fLineStyle > 0) && (this.pad.fLineColor > 0))) + if (!is_batch || (this.#pad.fFillStyle > 0) || ((this.#pad.fLineStyle > 0) && (this.#pad.fLineColor > 0))) svg_border = svg_pad.append('svg:path').attr('class', 'root_pad_border'); if (!is_batch) { @@ -834,8 +1039,8 @@ class TPadPainter extends ObjectPainter { } } - this.createAttFill({ attr: this.pad }); - this.createAttLine({ attr: this.pad, color0: !this.pad.fBorderMode ? 'none' : '' }); + this.createAttFill({ attr: this.#pad }); + this.createAttLine({ attr: this.#pad, color0: !this.#pad.fBorderMode ? 'none' : '' }); svg_pad.style('display', pad_visible ? null : 'none') .attr('viewBox', `0 0 ${w} ${h}`) // due to svg @@ -849,56 +1054,64 @@ class TPadPainter extends ObjectPainter { .property('draw_width', w) .property('draw_height', h); - this._pad_x = x; - this._pad_y = y; - this._pad_width = w; - this._pad_height = h; - - if (svg_border) { - svg_border.attr('d', `M0,0H${w}V${h}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - this.drawActiveBorder(svg_border); - - let svg_border1 = svg_pad.selectChild('.root_pad_border1'), - svg_border2 = svg_pad.selectChild('.root_pad_border2'); - - if (this.pad.fBorderMode && this.pad.fBorderSize) { - const pw = this.pad.fBorderSize, ph = this.pad.fBorderSize, - side1 = `M0,0h${w}l${-pw},${ph}h${2*pw-w}v${h-2*ph}l${-pw},${ph}z`, - side2 = `M${w},${h}v${-h}l${-pw},${ph}v${h-2*ph}h${2*pw-w}l${-pw},${ph}z`; - - if (svg_border2.empty()) - svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); - if (svg_border1.empty()) - svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); - - svg_border1.attr('d', this.pad.fBorderMode > 0 ? side1 : side2) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); - svg_border2.attr('d', this.pad.fBorderMode > 0 ? side2 : side1) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatHex()); - } else { - svg_border1.remove(); - svg_border2.remove(); - } - } + this.#pad_scale = this.getCanvPainter().getPadScale(); + this.#pad_x = x; + this.#pad_y = y; + this.#pad_width = w; + this.#pad_height = h; - this.setFastDrawing(w * (1 - this.pad.fLeftMargin-this.pad.fRightMargin), h * (1 - this.pad.fBottomMargin - this.pad.fTopMargin)); + this.addPadBorder(svg_pad, svg_border, true); + + this.setFastDrawing(w * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), h * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); // special case of 3D canvas overlay if (svg_pad.property('can3d') === constants.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.this_pad_name) + this.selectDom().select('.draw3d_' + this.#pad_name) .style('display', pad_visible ? '' : 'none'); } if (this.alignButtons && btns) - this.alignButtons(btns, w, h); + this.alignButtons(btns, this.#pad_width, this.#pad_height); return pad_visible; } + /** @summary Add border decorations + * @private */ + addPadBorder(svg_pad, svg_border, draw_line) { + if (!svg_border) + return; + + svg_border.attr('d', `M0,0H${this.#pad_width}V${this.#pad_height}H0Z`) + .call(this.fillatt.func); + if (draw_line) + svg_border.call(this.lineatt.func); + + this.drawActiveBorder(svg_border); + + let svg_border1 = svg_pad.selectChild('.root_pad_border1'), + svg_border2 = svg_pad.selectChild('.root_pad_border2'); + + if (this.#pad.fBorderMode && this.#pad.fBorderSize) { + const arr = getBoxDecorations(0, 0, this.#pad_width, this.#pad_height, this.#pad.fBorderMode, this.#pad.fBorderSize, this.#pad.fBorderSize); + + if (svg_border2.empty()) + svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); + if (svg_border1.empty()) + svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); + + svg_border1.attr('d', arr[0]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); + svg_border2.attr('d', arr[1]) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); + } else { + svg_border1.remove(); + svg_border2.remove(); + } + } + /** @summary Add pad interactive features like dragging and resize * @private */ addPadInteractive(cleanup = false) { @@ -907,7 +1120,7 @@ class TPadPainter extends ObjectPainter { delete this.$userInteractive; } - if (this.isBatchMode() || this.iscan) + if (this.isBatchMode() || this.isCanvas() || !this.isEditable()) return; const svg_can = this.getCanvSvg(), @@ -916,25 +1129,26 @@ class TPadPainter extends ObjectPainter { addDragHandler(this, { cleanup, // do cleanup to let assign new handlers later on - x: this._pad_x, y: this._pad_y, width: this._pad_width, height: this._pad_height, no_transform: true, - only_resize: true, // !cleanup && (this._disable_dragging || this.getFramePainter()?.mode3d), + x: this.#pad_x, y: this.#pad_y, width: this.#pad_width, height: this.#pad_height, no_transform: true, + only_resize: true, is_disabled: kind => svg_can.property('pad_enlarged') || this.btns_active_flag || - (kind === 'move' && (this._disable_dragging || this.getFramePainter()?.mode3d)), - getDrawG: () => this.svg_this_pad(), + (kind === 'move' && (this.options._disable_dragging || this.getFramePainter()?.mode3d)), + getDrawG: () => this.getPadSvg(), pad_rect: { width, height }, minwidth: 20, minheight: 20, move_resize: (_x, _y, _w, _h) => { - const x0 = this.pad.fAbsXlowNDC, - y0 = this.pad.fAbsYlowNDC, - scale_w = _w / width / this.pad.fAbsWNDC, - scale_h = _h / height / this.pad.fAbsHNDC, - shift_x = _x / width - x0, - shift_y = 1 - (_y + _h) / height - y0; + const x0 = this.#pad.fAbsXlowNDC, + y0 = this.#pad.fAbsYlowNDC, + scale_w = _w / width / this.#pad.fAbsWNDC, + scale_h = _h / height / this.#pad.fAbsHNDC, + shift_x = _x / width - x0, + shift_y = 1 - (_y + _h) / height - y0; this.forEachPainterInPad(p => { - p.pad.fAbsXlowNDC += (p.pad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; - p.pad.fAbsYlowNDC += (p.pad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; - p.pad.fAbsWNDC *= scale_w; - p.pad.fAbsHNDC *= scale_h; + const subpad = p.getRootPad(); + subpad.fAbsXlowNDC += (subpad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; + subpad.fAbsYlowNDC += (subpad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; + subpad.fAbsWNDC *= scale_w; + subpad.fAbsHNDC *= scale_h; }, 'pads'); }, redraw: () => this.interactiveRedraw('pad', 'padpos') @@ -944,8 +1158,8 @@ class TPadPainter extends ObjectPainter { /** @summary Disable pad drawing * @desc Complete SVG element will be hidden */ disablePadDrawing() { - if (!this.pad_draw_disabled && this.has_canvas && !this.iscan) { - this.pad_draw_disabled = true; + if (!this.#pad_draw_disabled && !this.isTopPad()) { + this.#pad_draw_disabled = true; this.createPadSvg(true); } } @@ -954,29 +1168,37 @@ class TPadPainter extends ObjectPainter { * @desc It can be TStyle or list of colors or palette object * @return {boolean} true if any */ checkSpecial(obj) { - if (!obj) return false; + if (!obj) + return false; if (obj._typename === clTStyle) { Object.assign(gStyle, obj); return true; } + const o = this.getOptions(true); + if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { - if (this.options?.CreatePalette) { + if (o?.CreatePalette) { let arr = []; - for (let n = obj.arr.length - this.options.CreatePalette; n < obj.arr.length; ++n) { + for (let n = obj.arr.length - o.CreatePalette; n < obj.arr.length; ++n) { const col = getRGBfromTColor(obj.arr[n]); - if (!col) { console.log('Fail to create color for palette'); arr = null; break; } + if (!col) { + console.log('Fail to create color for palette'); + arr = null; + break; + } arr.push(col); } - if (arr) this.custom_palette = new ColorPalette(arr); + if (arr.length) + this.#custom_palette = new ColorPalette(arr); } - if (!this.options || this.options.GlobalColors) // set global list of colors + if (!o || o.GlobalColors) // set global list of colors adoptRootColors(obj); // copy existing colors and extend with new values - this._custom_colors = this.options?.LocalColors ? extendRootColors(null, obj) : null; + this.#custom_colors = o?.LocalColors ? extendRootColors(null, obj) : null; return true; } @@ -994,9 +1216,9 @@ class TPadPainter extends ObjectPainter { } } - const apply = (!this.options || (!missing && !this.options.IgnorePalette)); - this._custom_palette_indexes = apply ? indx : null; - this._custom_palette_colors = apply ? arr : null; + const apply = (!o || (!missing && !o.IgnorePalette)); + this.#custom_palette_indexes = apply ? indx : null; + this.#custom_palette_colors = apply ? arr : null; return true; } @@ -1006,14 +1228,17 @@ class TPadPainter extends ObjectPainter { /** @summary Check if special objects appears in primitives * @desc it could be list of colors or palette */ - checkSpecialsInPrimitives(can) { + checkSpecialsInPrimitives(can, count_specials) { const lst = can?.fPrimitives; - if (!lst) return; + if (count_specials) + this.#num_specials = 0; + if (!lst) + return; for (let i = 0; i < lst.arr?.length; ++i) { if (this.checkSpecial(lst.arr[i])) { - lst.arr.splice(i, 1); - lst.opt.splice(i, 1); - i--; + lst.arr[i].$special = true; // mark object as special one, do not use in drawing + if (count_specials) + this.#num_specials++; } } } @@ -1023,10 +1248,9 @@ class TPadPainter extends ObjectPainter { * @private */ findInPrimitives(objname, objtype) { const match = obj => obj && (obj?.fName === objname) && (objtype ? (obj?._typename === objtype) : true), - snap = this._snap_primitives?.find(snap => match((snap.fKind === webSnapIds.kObject) ? snap.fSnapshot : null)); - if (snap) return snap.fSnapshot; + snap = this.#snap_primitives?.find(s => match((s.fKind === webSnapIds.kObject) ? s.fSnapshot : null)); - return this.pad?.fPrimitives?.arr.find(match); + return snap ? snap.fSnapshot : this.#pad?.fPrimitives?.arr.find(match); } /** @summary Try to find painter for specified object @@ -1038,21 +1262,26 @@ class TPadPainter extends ObjectPainter { * @return {object} - painter for specified object (if any) * @private */ findPainterFor(selobj, selname, seltype) { - return this.painters.find(p => { + return this.#painters.find(p => { const pobj = p.getObject(); - if (!pobj) return false; + if (!pobj) + return false; - if (selobj && (pobj === selobj)) return true; - if (!selname && !seltype) return false; - if (selname && (pobj.fName !== selname)) return false; - if (seltype && (pobj._typename !== seltype)) return false; + if (selobj && (pobj === selobj)) + return true; + if (!selname && !seltype) + return false; + if (selname && (pobj.fName !== selname)) + return false; + if (seltype && (pobj._typename !== seltype)) + return false; return true; }); } /** @summary Return true if any objects beside sub-pads exists in the pad */ hasObjectsToDraw() { - return this.pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); + return this.#pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); } /** @summary sync drawing/redrawing/resize of the pad @@ -1061,14 +1290,14 @@ class TPadPainter extends ObjectPainter { * @private */ syncDraw(kind) { const entry = { kind: kind || 'redraw' }; - if (this._doing_draw === undefined) { - this._doing_draw = [entry]; + if (this.#doing_draw === undefined) { + this.#doing_draw = [entry]; return Promise.resolve(true); } // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this._doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) + if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) return false; - this._doing_draw.push(entry); + this.#doing_draw.push(entry); return new Promise(resolveFunc => { entry.func = resolveFunc; }); @@ -1076,21 +1305,22 @@ class TPadPainter extends ObjectPainter { /** @summary indicates if painter performing objects draw * @private */ - doingDraw() { - return this._doing_draw !== undefined; - } + doingDraw() { return this.#doing_draw !== undefined; } /** @summary confirms that drawing is completed, may trigger next drawing immediately * @private */ confirmDraw() { - if (this._doing_draw === undefined) + if (this.#doing_draw === undefined) return console.warn('failure, should not happen'); - this._doing_draw.shift(); - if (this._doing_draw.length === 0) - delete this._doing_draw; - else { - const entry = this._doing_draw[0]; - if (entry.func) { entry.func(); delete entry.func; } + this.#doing_draw.shift(); + if (!this.#doing_draw.length) + this.#doing_draw = undefined; + else { + const entry = this.#doing_draw[0]; + if (entry.func) { + entry.func(); + delete entry.func; + } } } @@ -1105,52 +1335,77 @@ class TPadPainter extends ObjectPainter { * @private */ async drawPrimitives(indx) { if (indx === undefined) { - if (this.iscan) - this._start_tm = new Date().getTime(); + if (this.isCanvas()) + this.#start_draw_tm = new Date().getTime(); - // set number of primitves - this._num_primitives = this.pad?.fPrimitives?.arr?.length || 0; + // set number of primitives + this.#num_primitives = this.#pad?.fPrimitives?.arr?.length || 0; // sync to prevent immediate pad redraw during normal drawing sequence return this.syncDraw(true).then(() => this.drawPrimitives(0)); } - if (!this.pad || (indx >= this._num_primitives)) { - if (this._start_tm) { - const spenttm = new Date().getTime() - this._start_tm; - if (spenttm > 1000) console.log(`Canvas ${this.pad?.fName || '---'} drawing took ${(spenttm*1e-3).toFixed(2)}s`); - delete this._start_tm; + if (!this.#pad || (indx >= this.#num_primitives)) { + if (this.#start_draw_tm) { + const spenttm = new Date().getTime() - this.#start_draw_tm; + if (spenttm > 1000) + console.log(`Canvas ${this.#pad?.fName || '---'} drawing took ${(spenttm * 1e-3).toFixed(2)}s`); + this.#start_draw_tm = undefined; } this.confirmDraw(); return; } - const obj = this.pad.fPrimitives.arr[indx]; + const obj = this.#pad.fPrimitives.arr[indx]; - if (!obj || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) - return this.drawPrimitives(indx+1); + if (!obj || obj.$special || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) + return this.drawPrimitives(indx + 1); // use of Promise should avoid large call-stack depth when many primitives are drawn - return this.drawObject(this.getDom(), obj, this.pad.fPrimitives.opt[indx]).then(op => { + return this.drawObject(this, obj, this.#pad.fPrimitives.opt[indx]).then(op => { if (isObject(op)) op._primitive = true; // mark painter as belonging to primitives - return this.drawPrimitives(indx+1); + return this.drawPrimitives(indx + 1); }); } - /** @summary Divide pad on subpads + /** @summary Divide pad on sub-pads * @return {Promise} when finished * @private */ - async divide(nx, ny) { - if (!this.pad.Divide(nx, ny)) + async divide(nx, ny, use_existing) { + let color = this.#pad.fFillColor; + if (!use_existing) { + if (color < 15) + color = 19; + else if (color < 20) + color--; + } + + if (nx && !ny && use_existing) { + for (let k = 0; k < nx; ++k) { + if (!this.getSubPadPainter(k + 1)) { + use_existing = false; + break; + } + } + if (use_existing) + return this; + } + + this.cleanPrimitives(isPadPainter); + if (!this.#pad.fPrimitives) + this.#pad.fPrimitives = create(clTList); + this.#pad.fPrimitives.Clear(); + + if ((!nx && !ny) || !this.#pad.Divide(nx, ny, 0.01, 0.01, color)) return this; const drawNext = indx => { - if (indx >= this.pad.fPrimitives.arr.length) + if (indx >= this.#pad.fPrimitives.arr.length) return this; - return this.drawObject(this.getDom(), this.pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); + return this.drawObject(this, this.#pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); }; return drawNext(0); @@ -1159,31 +1414,33 @@ class TPadPainter extends ObjectPainter { /** @summary Return sub-pads painter, only direct childs are checked * @private */ getSubPadPainter(n) { - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if (sub.pad && isFunc(sub.forEachPainterInPad) && (sub.pad.fNumber === n)) return sub; + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (isPadPainter(sub) && (sub.getRootPad()?.fNumber === n)) + return sub; } return null; } - /** @summary Process tooltip event in the pad * @private */ processPadTooltipEvent(pnt) { const painters = [], hints = []; // first count - how many processors are there - this.painters?.forEach(obj => { + this.#painters?.forEach(obj => { if (isFunc(obj.processTooltipEvent)) painters.push(obj); }); - if (pnt) pnt.nproc = painters.length; + if (pnt) + pnt.nproc = painters.length; painters.forEach(obj => { const hint = obj.processTooltipEvent(pnt) || { user_info: null }; hints.push(hint); - if (pnt?.painters) hint.painter = obj; + if (pnt?.painters) + hint.painter = obj; }); return hints; @@ -1198,59 +1455,121 @@ class TPadPainter extends ObjectPainter { /** @summary Fill pad context menu * @private */ fillContextMenu(menu) { - if (this.pad) - menu.add(`header:${this.pad._typename}::${this.pad.fName}`); - else - menu.add('header:Canvas'); + const pad = this.getRootPad(true); + if (!pad) + return false; + + menu.header(`${pad._typename}::${pad.fName}`, `${urlClassPrefix}${pad._typename}.html`); menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - if (!this._websocket) { - function SetPadField(arg) { - this.pad[arg.slice(1)] = parseInt(arg[0]); - this.interactiveRedraw('pad', arg.slice(1)); - } + menu.addchk(pad.fGridx, 'Grid x', flag => { + pad.fGridx = flag ? 1 : 0; + this.interactiveRedraw('pad', `exec:SetGridx(${flag ? 1 : 0})`); + }); + menu.addchk(pad.fGridy, 'Grid y', flag => { + pad.fGridy = flag ? 1 : 0; + this.interactiveRedraw('pad', `exec:SetGridy(${flag ? 1 : 0})`); + }); + menu.sub('Ticks x'); + menu.addchk(pad.fTickx === 0, 'normal', () => { + pad.fTickx = 0; + this.interactiveRedraw('pad', 'exec:SetTickx(0)'); + }); + menu.addchk(pad.fTickx === 1, 'ticks on both sides', () => { + pad.fTickx = 1; + this.interactiveRedraw('pad', 'exec:SetTickx(1)'); + }); + menu.addchk(pad.fTickx === 2, 'labels on both sides', () => { + pad.fTickx = 2; + this.interactiveRedraw('pad', 'exec:SetTickx(2)'); + }); + menu.endsub(); + menu.sub('Ticks y'); + menu.addchk(pad.fTicky === 0, 'normal', () => { + pad.fTicky = 0; + this.interactiveRedraw('pad', 'exec:SetTicky(0)'); + }); + menu.addchk(pad.fTicky === 1, 'ticks on both sides', () => { + pad.fTicky = 1; + this.interactiveRedraw('pad', 'exec:SetTicky(1)'); + }); + menu.addchk(pad.fTicky === 2, 'labels on both sides', () => { + pad.fTicky = 2; + this.interactiveRedraw('pad', 'exec:SetTicky(2)'); + }); + menu.endsub(); + + menu.addchk(pad.fEditable, 'Editable', flag => { + pad.fEditable = flag; + this.interactiveRedraw('pad', `exec:SetEditable(${flag})`); + }); + + if (this.isCanvas()) { + menu.addchk(pad.TestBit(kIsGrayscale), 'Gray scale', flag => { + this.setGrayscale(flag); + this.interactiveRedraw('pad', `exec:SetGrayscale(${flag})`); + }); + } - menu.addchk(this.pad?.fGridx, 'Grid x', (this.pad?.fGridx ? '0' : '1') + 'fGridx', SetPadField); - menu.addchk(this.pad?.fGridy, 'Grid y', (this.pad?.fGridy ? '0' : '1') + 'fGridy', SetPadField); - menu.add('sub:Ticks x'); - menu.addchk(this.pad?.fTickx === 0, 'normal', '0fTickx', SetPadField); - menu.addchk(this.pad?.fTickx === 1, 'ticks on both sides', '1fTickx', SetPadField); - menu.addchk(this.pad?.fTickx === 2, 'labels on both sides', '2fTickx', SetPadField); - menu.add('endsub:'); - menu.add('sub:Ticks y'); - menu.addchk(this.pad?.fTicky === 0, 'normal', '0fTicky', SetPadField); - menu.addchk(this.pad?.fTicky === 1, 'ticks on both sides', '1fTicky', SetPadField); - menu.addchk(this.pad?.fTicky === 2, 'labels on both sides', '2fTicky', SetPadField); - menu.add('endsub:'); - menu.addchk(this.pad?.fEditable, 'Editable', flag => { this.pad.fEditable = flag; this.interactiveRedraw('pad'); }); - if (this.iscan) - menu.addchk(this.pad?.TestBit(kIsGrayscale), 'Gray scale', flag => { this.setGrayscale(flag); this.interactiveRedraw('pad'); }); + menu.sub('Border'); + menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], pad.fBorderMode + 1, v => { + pad.fBorderMode = v - 1; + this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); + }, 'Pad border mode'); + menu.addSizeMenu('Size', 0, 20, 2, pad.fBorderSize, v => { + pad.fBorderSize = v; + this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); + }, 'Pad border size'); + menu.endsub(); + + menu.addAttributesMenu(this); + + if (!this.isCanvas(true)) { + // if not online canvas - + const do_divide = arg => { + if (!arg || !isStr(arg)) + return; + // delete auto_canvas flag to prevent deletion + if (this.#iscan === 'auto') + this.#iscan = true; + this.cleanPrimitives(true); + if (arg === 'reset') + return; + const arr = arg.split('x'); + if (arr.length === 1) + this.divide(Number.parseInt(arr[0])); + else if (arr.length === 2) + this.divide(Number.parseInt(arr[0]), Number.parseInt(arr[1])); + }; if (isFunc(this.drawObject)) menu.add('Build legend', () => this.buildLegend()); - menu.addAttributesMenu(this); + menu.sub('Divide', () => menu.input('Input divide arg', '2x2').then(do_divide), 'Divide on sub-pads'); + ['1x2', '2x1', '2x2', '2x3', '3x2', '3x3', '4x4', 'reset'].forEach(item => menu.add(item, item, do_divide)); + menu.endsub(); + menu.add('Save to gStyle', () => { - if (!this.pad) return; - this.fillatt?.saveToStyle(this.iscan ? 'fCanvasColor' : 'fPadColor'); - gStyle.fPadGridX = this.pad.fGridx; - gStyle.fPadGridY = this.pad.fGridy; - gStyle.fPadTickX = this.pad.fTickx; - gStyle.fPadTickY = this.pad.fTicky; - gStyle.fOptLogx = this.pad.fLogx; - gStyle.fOptLogy = this.pad.fLogy; - gStyle.fOptLogz = this.pad.fLogz; + this.fillatt?.saveToStyle(this.isCanvas() ? 'fCanvasColor' : 'fPadColor'); + gStyle.fPadGridX = pad.fGridx; + gStyle.fPadGridY = pad.fGridy; + gStyle.fPadTickX = pad.fTickx; + gStyle.fPadTickY = pad.fTicky; + gStyle.fOptLogx = pad.fLogx; + gStyle.fOptLogy = pad.fLogy; + gStyle.fOptLogz = pad.fLogz; }, 'Store pad fill attributes, grid, tick and log scale settings to gStyle'); - if (this.iscan) { + if (this.isCanvas()) { menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') this.changeDarkMode(); + if (arg === 'dark') + this.changeDarkMode(); }); } } - menu.add('separator'); + menu.separator(); if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); @@ -1260,13 +1579,21 @@ class TPadPainter extends ObjectPainter { menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); } - if (this.enlargeMain() || (this.has_canvas && this.hasObjectsToDraw())) - menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.iscan ? 'canvas' : 'pad'), () => this.enlargePad()); + if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) + menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - const fname = this.this_pad_name || (this.iscan ? 'canvas' : 'pad'); - menu.add('sub:Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.iscan, `${fname}.${fmt}`))); - menu.add('endsub:'); + const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); + menu.sub('Save as'); + const fmts = ['svg', 'png', 'jpeg', 'webp']; + if (internals.makePDF) + fmts.push('pdf'); + fmts.forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`))); + if (this.isCanvas()) { + menu.separator(); + menu.add(`${fname}.json`, () => this.saveAs('json', true, `${fname}.json`), 'Produce JSON with line spacing'); + menu.add(`${fname}0.json`, () => this.saveAs('json', false, `${fname}0.json`), 'Produce JSON without line spacing'); + } + menu.endsub(); return true; } @@ -1287,21 +1614,28 @@ class TPadPainter extends ObjectPainter { }).then(menu => menu.show()); } + /** @summary Redraw TLegend object + * @desc Used when object attributes are changed to ensure that legend is up to date + * @private */ + async redrawLegend() { + return this.findPainterFor(null, '', clTLegend)?.redraw(); + } + /** @summary Redraw pad means redraw ourself * @return {Promise} when redrawing ready */ async redrawPad(reason) { const sync_promise = this.syncDraw(reason); if (sync_promise === false) { - console.log(`Prevent redrawing of ${this.pad.fName}`); + console.log(`Prevent redrawing of ${this.#pad.fName}`); return false; } let showsubitems = true; const redrawNext = indx => { - while (indx < this.painters.length) { - const sub = this.painters[indx++]; + while (indx < this.#painters.length) { + const sub = this.#painters[indx++]; let res = 0; - if (showsubitems || sub.this_pad_name) + if (showsubitems || isPadPainter(sub)) res = sub.redraw(reason); if (isPromise(res)) @@ -1311,7 +1645,7 @@ class TPadPainter extends ObjectPainter { }; return sync_promise.then(() => { - if (this.iscan) + if (this.isCanvas()) this.createCanvasSvg(2); else showsubitems = this.createPadSvg(true); @@ -1327,17 +1661,18 @@ class TPadPainter extends ObjectPainter { /** @summary redraw pad */ redraw(reason) { - // intentially do not return Promise to let re-draw sub-pads in parallel + // intentionally do not return Promise to let re-draw sub-pads in parallel this.redrawPad(reason); } /** @summary Checks if pad should be redrawn by resize * @private */ needRedrawByResize() { - const elem = this.svg_this_pad(); - if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) return true; + const elem = this.getPadSvg(); + if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) + return true; - return this.painters.findIndex(objp => { + return this.#painters.findIndex(objp => { return isFunc(objp.needRedrawByResize) ? objp.needRedrawByResize() : false; }) >= 0; } @@ -1345,43 +1680,47 @@ class TPadPainter extends ObjectPainter { /** @summary Check resize of canvas * @return {Promise} with result or false */ checkCanvasResize(size, force) { - if (this._ignore_resize) + if (this._ignore_resize || !this.isTopPad()) return false; - if (!this.iscan && this.has_canvas) return false; - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) return false; + if (sync_promise === false) + return false; - if ((size === true) || (size === false)) { force = size; size = null; } + if ((size === true) || (size === false)) { + force = size; + size = null; + } - if (isObject(size) && size.force) force = true; + if (isObject(size) && size.force) + force = true; - if (!force) force = this.needRedrawByResize(); + if (!force) + force = this.needRedrawByResize(); let changed = false; const redrawNext = indx => { - if (!changed || (indx >= this.painters.length)) { + if (!changed || (indx >= this.#painters.length)) { this.confirmDraw(); return changed; } - return getPromise(this.painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx+1)); + return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); }; - // return sync_promise.then(() => this.ensureBrowserSize(this.pad?.fCw, this.pad?.fCh)).then(() => { + // return sync_promise.then(() => this.ensureBrowserSize(this.#pad?.fCw, this.#pad?.fCh)).then(() => { return sync_promise.then(() => { changed = this.createCanvasSvg(force ? 2 : 1, size); - if (changed && this.iscan && this.pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this._resize_tmout) - clearTimeout(this._resize_tmout); - this._resize_tmout = setTimeout(() => { - delete this._resize_tmout; + if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { + if (this.#resize_tmout) + clearTimeout(this.#resize_tmout); + this.#resize_tmout = setTimeout(() => { + this.#resize_tmout = undefined; if (isFunc(this.sendResized)) this.sendResized(); - }, 1000); // long enough delay to prevent multiple occurence + }, 1000); // long enough delay to prevent multiple occurrence } // if canvas changed, redraw all its subitems. @@ -1392,57 +1731,69 @@ class TPadPainter extends ObjectPainter { /** @summary Update TPad object */ updateObject(obj) { - if (!obj) return false; - - this.pad.fBits = obj.fBits; - this.pad.fTitle = obj.fTitle; - - this.pad.fGridx = obj.fGridx; - this.pad.fGridy = obj.fGridy; - this.pad.fTickx = obj.fTickx; - this.pad.fTicky = obj.fTicky; - this.pad.fLogx = obj.fLogx; - this.pad.fLogy = obj.fLogy; - this.pad.fLogz = obj.fLogz; - - this.pad.fUxmin = obj.fUxmin; - this.pad.fUxmax = obj.fUxmax; - this.pad.fUymin = obj.fUymin; - this.pad.fUymax = obj.fUymax; - - this.pad.fX1 = obj.fX1; - this.pad.fX2 = obj.fX2; - this.pad.fY1 = obj.fY1; - this.pad.fY2 = obj.fY2; - - this.pad.fLeftMargin = obj.fLeftMargin; - this.pad.fRightMargin = obj.fRightMargin; - this.pad.fBottomMargin = obj.fBottomMargin; - this.pad.fTopMargin = obj.fTopMargin; - - this.pad.fFillColor = obj.fFillColor; - this.pad.fFillStyle = obj.fFillStyle; - this.pad.fLineColor = obj.fLineColor; - this.pad.fLineStyle = obj.fLineStyle; - this.pad.fLineWidth = obj.fLineWidth; - - this.pad.fPhi = obj.fPhi; - this.pad.fTheta = obj.fTheta; - this.pad.fEditable = obj.fEditable; - - if (this.iscan) + const pad = this.getRootPad(); + if (!obj || !pad) + return false; + + pad.fBits = obj.fBits; + pad.fTitle = obj.fTitle; + + pad.fGridx = obj.fGridx; + pad.fGridy = obj.fGridy; + pad.fTickx = obj.fTickx; + pad.fTicky = obj.fTicky; + pad.fLogx = obj.fLogx; + pad.fLogy = obj.fLogy; + pad.fLogz = obj.fLogz; + + pad.fUxmin = obj.fUxmin; + pad.fUxmax = obj.fUxmax; + pad.fUymin = obj.fUymin; + pad.fUymax = obj.fUymax; + + pad.fX1 = obj.fX1; + pad.fX2 = obj.fX2; + pad.fY1 = obj.fY1; + pad.fY2 = obj.fY2; + + // this is main coordinates for sub-pad relative to canvas + pad.fAbsWNDC = obj.fAbsWNDC; + pad.fAbsHNDC = obj.fAbsHNDC; + pad.fAbsXlowNDC = obj.fAbsXlowNDC; + pad.fAbsYlowNDC = obj.fAbsYlowNDC; + + pad.fLeftMargin = obj.fLeftMargin; + pad.fRightMargin = obj.fRightMargin; + pad.fBottomMargin = obj.fBottomMargin; + pad.fTopMargin = obj.fTopMargin; + + pad.fFillColor = obj.fFillColor; + pad.fFillStyle = obj.fFillStyle; + pad.fLineColor = obj.fLineColor; + pad.fLineStyle = obj.fLineStyle; + pad.fLineWidth = obj.fLineWidth; + + pad.fPhi = obj.fPhi; + pad.fTheta = obj.fTheta; + pad.fEditable = obj.fEditable; + + if (this.isCanvas()) this.checkSpecialsInPrimitives(obj); const fp = this.getFramePainter(); - if (fp) fp.updateAttributes(!fp.modified_NDC); + fp?.updateAttributes(!fp.$modifiedNDC); - if (!obj.fPrimitives) return false; + if (!obj.fPrimitives) + return false; let isany = false, p = 0; for (let n = 0; n < obj.fPrimitives.arr?.length; ++n) { - while (p < this.painters.length) { - const op = this.painters[p++]; - if (!op._primitive) continue; + if (obj.fPrimitives.arr[n].$special) + continue; + while (p < this.#painters.length) { + const op = this.#painters[p++]; + if (!op._primitive) + continue; if (op.updateObject(obj.fPrimitives.arr[n], obj.fPrimitives.opt[n])) isany = true; break; @@ -1465,8 +1816,8 @@ class TPadPainter extends ObjectPainter { leg.fPrimitives.Clear(); - for (let k = 0; k < this.painters.length; ++k) { - const painter = this.painters[k], + for (let k = 0; k < this.#painters.length; ++k) { + const painter = this.#painters[k], obj = painter.getObject(); if (!obj || obj.fName === kTitle || obj.fName === 'stats' || painter.draw_content === false || obj._typename === clTLegend || obj._typename === clTHStack || obj._typename === clTMultiGraph) @@ -1476,9 +1827,10 @@ class TPadPainter extends ObjectPainter { entry.fObject = obj; entry.fLabel = painter.getItemName(); if ((opt === 'all') || !entry.fLabel) - entry.fLabel = obj.fName; + entry.fLabel = obj.fName; entry.fOption = ''; - if (!entry.fLabel) continue; + if (!entry.fLabel) + continue; if (painter.lineatt?.used) entry.fOption += 'l'; @@ -1498,8 +1850,10 @@ class TPadPainter extends ObjectPainter { const szx = 0.4; let szy = leg.fPrimitives.arr.length; // no entries - no need to draw legend - if (!szy) return null; - if (szy > 8) szy = 8; + if (!szy) + return null; + if (szy > 8) + szy = 8; szy *= 0.1; if ((x1 === x2) || (y1 === y2)) { @@ -1507,7 +1861,8 @@ class TPadPainter extends ObjectPainter { leg.fY1NDC = (1 - szy) * (1 - pad.fTopMargin) + szy * pad.fBottomMargin; leg.fX2NDC = 0.99 - pad.fRightMargin; leg.fY2NDC = 0.99 - pad.fTopMargin; - if (opt === undefined) opt = 'autoplace'; + if (opt === undefined) + opt = 'autoplace'; } else { leg.fX1NDC = x1; leg.fY1NDC = y1; @@ -1517,29 +1872,25 @@ class TPadPainter extends ObjectPainter { leg.fFillStyle = 1001; leg.fTitle = title ?? ''; - const prev_name = this.has_canvas ? this.selectCurrentPad(this.this_pad_name) : undefined; - - return this.drawObject(this.getDom(), leg, opt).then(p => { - this.selectCurrentPad(prev_name); - return p; - }); + return this.drawObject(this, leg, opt); } /** @summary Add object painter to list of primitives * @private */ addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && (objpainter.snapid === undefined)) { + if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { // keep snap id in painter, will be used for the - if (this.painters.indexOf(objpainter) < 0) - this.painters.push(objpainter); + if (this.#painters.indexOf(objpainter) < 0) + this.#painters.push(objpainter); - objpainter.snapid = lst[indx].fObjectID; + objpainter.assignSnapId(lst[indx].fObjectID); const setSubSnaps = p => { - if (!p._unique_painter_id) return; - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; - if ((sub._main_painter_id === p._unique_painter_id) && sub._secondary_id) { - sub.snapid = p.snapid + '#' + sub._secondary_id; + if (!p.isPrimary()) + return; + for (let k = 0; k < this.#painters.length; ++k) { + const sub = this.#painters[k]; + if (sub.isSecondary(p) && sub.getSecondaryId()) { + sub.assignSnapId(p.getSnapId() + '#' + sub.getSecondaryId()); setSubSnaps(sub); } } @@ -1558,33 +1909,31 @@ class TPadPainter extends ObjectPainter { /** @summary Process snap with colors * @private */ processSnapColors(snap) { - const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper); + const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper), + o = this.getOptions(true); // set global list of colors - if (!this.options || this.options.GlobalColors) + if (!o || o.GlobalColors) adoptRootColors(ListOfColors); - const greyscale = this.pad?.TestBit(kIsGrayscale) ?? false, + const greyscale = this.#pad?.TestBit(kIsGrayscale) ?? false, colors = extendRootColors(null, ListOfColors, greyscale); // copy existing colors and extend with new values - this._custom_colors = this.options?.LocalColors ? colors : null; + this.#custom_colors = o?.LocalColors ? colors : null; // set palette - if (snap.fSnapshot.fBuf && (!this.options || !this.options.IgnorePalette)) { + if (snap.fSnapshot.fBuf && (!o || !o.IgnorePalette)) { const indexes = [], palette = []; for (let n = 0; n < snap.fSnapshot.fBuf.length; ++n) { indexes[n] = Math.round(snap.fSnapshot.fBuf[n]); palette[n] = colors[indexes[n]]; } - this._custom_palette_indexes = indexes; - this._custom_palette_colors = palette; - this.custom_palette = new ColorPalette(palette, greyscale); - } else { - delete this._custom_palette_indexes; - delete this._custom_palette_colors; - delete this.custom_palette; - } + this.#custom_palette_indexes = indexes; + this.#custom_palette_colors = palette; + this.#custom_palette = new ColorPalette(palette, greyscale); + } else + this.#custom_palette = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; } /** @summary Process snap with custom font @@ -1619,127 +1968,104 @@ class TPadPainter extends ObjectPainter { /** @summary Function called when drawing next snapshot from the list * @return {Promise} for drawing of the snap * @private */ - async drawNextSnap(lst, indx) { + async drawNextSnap(lst, pindx, indx) { if (indx === undefined) { indx = -1; - this._snaps_map = {}; // to control how much snaps are drawn - this._num_primitives = lst ? lst.length : 0; + this.#num_primitives = lst?.length ?? 0; } ++indx; // change to the next snap - if (!lst || (indx >= lst.length)) { - delete this._snaps_map; + if (!lst || (indx >= lst.length)) return this; - } - const snap = lst[indx]; + const snap = lst[indx], is_subpad = (snap.fKind === webSnapIds.kSubPad); // gStyle object if (snap.fKind === webSnapIds.kStyle) { this.processSnapStyle(snap); - return this.drawNextSnap(lst, indx); // call next + return this.drawNextSnap(lst, pindx, indx); // call next } // list of colors if (snap.fKind === webSnapIds.kColors) { this.processSnapColors(snap); - return this.drawNextSnap(lst, indx); // call next + return this.drawNextSnap(lst, pindx, indx); // call next } - const snapid = snap.fObjectID; - let cnt = (this._snaps_map[snapid] || 0) + 1, - objpainter = null; - - this._snaps_map[snapid] = cnt; // check how many objects with same snapid drawn, use them again + // try to locate existing object painter, only allowed when redrawing pad snap + let objpainter, promise; - // first appropriate painter for the object - // if same object drawn twice, two painters will exists - for (let k = 0; k < this.painters.length; ++k) { - const subp = this.painters[k]; - if (subp.snapid === snapid) - if (--cnt === 0) { objpainter = subp; break; } + while ((pindx !== undefined) && (pindx < this.#painters.length)) { + const subp = this.#painters[pindx++]; + if (subp.getSnapId() === snap.fObjectID) { + objpainter = subp; + break; + } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { + console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fSnapshot?._typename}`); + break; + } } if (objpainter) { - if (snap.fKind === webSnapIds.kSubPad) // subpad - return objpainter.redrawPadSnap(snap).then(() => this.drawNextSnap(lst, indx)); - - let promise; - - if (snap.fKind === webSnapIds.kObject) { // object itself + // painter exists - try to update drawing + if (is_subpad) + promise = objpainter.redrawPadSnap(snap); + else if (snap.fKind === webSnapIds.kObject) { // object itself if (objpainter.updateObject(snap.fSnapshot, snap.fOption, true)) promise = objpainter.redraw(); } else if (snap.fKind === webSnapIds.kSVG) { // update SVG if (objpainter.updateObject(snap.fSnapshot)) promise = objpainter.redraw(); } - - return getPromise(promise).then(() => this.drawNextSnap(lst, indx)); // call next - } - - if (snap.fKind === webSnapIds.kSubPad) { // subpad + } else if (is_subpad) { const subpad = snap.fSnapshot; subpad.fPrimitives = null; // clear primitives, they just because of I/O - const padpainter = new TPadPainter(this.getDom(), subpad, false); - padpainter.decodeOptions(snap.fOption); - padpainter.addToPadPrimitives(this.this_pad_name); - padpainter.snapid = snap.fObjectID; - padpainter.is_active_pad = !!snap.fActive; // enforce boolean flag - padpainter._readonly = snap.fReadOnly ?? false; // readonly flag - padpainter._snap_primitives = snap.fPrimitives; // keep list to be able find primitive - padpainter._has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - - if (subpad.$disable_drawing) - padpainter.pad_draw_disabled = true; + const padpainter = new TPadPainter(this, subpad, snap.fOption, false, 'webpad'); + padpainter.assignSnapId(snap.fObjectID); + padpainter.is_active_pad = Boolean(snap.fActive); // enforce boolean flag + padpainter.#readonly = snap.fReadOnly ?? false; // readonly flag + padpainter.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive + padpainter.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features padpainter.processSpecialSnaps(snap.fPrimitives); // need to process style and colors before creating graph elements padpainter.createPadSvg(); - if (padpainter.matchObjectType(clTPad) && (snap.fPrimitives.length > 0)) + if (padpainter.matchObjectType(clTPad) && snap.fPrimitives.length) padpainter.addPadButtons(true); - - // we select current pad, where all drawing is performed - const prev_name = padpainter.selectCurrentPad(padpainter.this_pad_name); - return padpainter.drawNextSnap(snap.fPrimitives).then(() => { - padpainter.addPadInteractive(); - padpainter.selectCurrentPad(prev_name); - return this.drawNextSnap(lst, indx); // call next - }); + pindx++; // new painter will be add + promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); + } else if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { + // here the case of normal drawing + pindx++; // new painter will be add + promise = this.drawObject(this, snap.fSnapshot, snap.fOption).then(objp => this.addObjectPainter(objp, lst, indx)); } - // here the case of normal drawing, will be handled in promise - if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { - return this.drawObject(this.getDom(), snap.fSnapshot, snap.fOption).then(objpainter => { - this.addObjectPainter(objpainter, lst, indx); - return this.drawNextSnap(lst, indx); - }); - } - - return this.drawNextSnap(lst, indx); + return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next } /** @summary Return painter with specified id * @private */ findSnap(snapid) { - if (this.snapid === snapid) + if (this.getSnapId() === snapid) return this; - if (!this.painters) + if (!this.#painters) return null; - for (let k = 0; k < this.painters.length; ++k) { - let sub = this.painters[k]; + for (let k = 0; k < this.#painters.length; ++k) { + let sub = this.#painters[k]; if (isFunc(sub.findSnap)) sub = sub.findSnap(snapid); - else if (sub.snapid !== snapid) + else if (sub.getSnapId() !== snapid) sub = null; - if (sub) return sub; + if (sub) + return sub; } return null; @@ -1756,38 +2082,42 @@ class TPadPainter extends ObjectPainter { if (!snap?.fPrimitives) return this; - this.is_active_pad = !!snap.fActive; // enforce boolean flag - this._readonly = snap.fReadOnly ?? false; // readonly flag - this._snap_primitives = snap.fPrimitives; // keep list to be able find primitive - this._has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features + this.is_active_pad = Boolean(snap.fActive); // enforce boolean flag + this.#readonly = snap.fReadOnly ?? false; // readonly flag + this.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive + this.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features const first = snap.fSnapshot; first.fPrimitives = null; // primitives are not interesting, they are disabled in IO // if there are execs in the pad, deliver events to the server - this._deliver_webcanvas_events = first.fExecs?.arr?.length > 0; + this.#deliver_move_events = this.#has_execs || first.fExecs?.arr?.length; - if (this.snapid === undefined) { + if (!this.hasSnapId()) { // first time getting snap, create all gui elements first - this.snapid = snap.fObjectID; + this.assignSnapId(snap.fObjectID); - this.draw_object = this.pad = first; // first object is pad + this.assignObject(first); + this.#pad = first; // first object is pad - // this._fixed_size = true; + // this._setFixedSize(true); // if canvas size not specified in batch mode, temporary use 900x700 size - if (this.isBatchMode() && (!first.fCw || !first.fCh)) { first.fCw = 900; first.fCh = 700; } + if (this.isBatchMode() && (!first.fCw || !first.fCh)) { + first.fCw = 900; + first.fCh = 700; + } // case of ROOT7 with always dummy TPad as first entry - if (!first.fCw || !first.fCh) this._fixed_size = false; + if (!first.fCw || !first.fCh) + this._setFixedSize(false); const mainid = this.selectDom().attr('id'); - if (!this.isBatchMode() && !this.use_openui && !this.brlayout && mainid && isStr(mainid)) { + if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { this.brlayout = new BrowserLayout(mainid, null, this); this.brlayout.create(mainid, true); - // this.brlayout.toggleBrowserKind('float'); this.setDom(this.brlayout.drawing_divid()); // need to create canvas registerForResize(this.brlayout); } @@ -1805,126 +2135,109 @@ class TPadPainter extends ObjectPainter { let pr = Promise.resolve(true); if (isStr(snap.fScripts) && snap.fScripts) { - let src = ''; + let src = '', m = null; - if (snap.fScripts.indexOf('load:') === 0) + if (snap.fScripts.indexOf('modules:') === 0) + m = snap.fScripts.slice(8).split(';'); + else if (snap.fScripts.indexOf('load:') === 0) src = snap.fScripts.slice(5).split(';'); else if (snap.fScripts.indexOf('assert:') === 0) src = snap.fScripts.slice(7); - pr = src ? loadScript(src) : injectCode(snap.fScripts); + pr = (m !== null) ? loadModules(m) : (src ? loadScript(src) : injectCode(snap.fScripts)); } - return pr.then(() => this.drawNextSnap(snap.fPrimitives)); + return pr.then(() => this.drawNextSnap(snap.fPrimitives)).then(() => { + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); + return this; + }); } this.updateObject(first); // update only object attributes // apply all changes in the object (pad or canvas) - if (this.iscan) + if (this.isCanvas()) this.createCanvasSvg(2); - else + else this.createPadSvg(true); - const matchPrimitive = (painters, primitives, class_name, obj_name) => { - const painter = painters.find(p => { - if (p.snapid === undefined) return false; - if (!p.matchObjectType(class_name)) return false; - if (obj_name && (!p.getObject() || (p.getObject().fName !== obj_name))) return false; - return true; - }); - if (!painter) return; - const primitive = primitives.find(pr => { - if ((pr.fKind !== 1) || !pr.fSnapshot || (pr.fSnapshot._typename !== class_name)) return false; - if (obj_name && (pr.fSnapshot.fName !== obj_name)) return false; - return true; - }); - if (!primitive) return; - - // force painter to use new object id - if (painter.snapid !== primitive.fObjectID) - painter.snapid = primitive.fObjectID; - }; + let missmatch = false; - // check if frame or title was recreated, we could reassign handlers for them directly - // while this is temporary objects, which can be recreated very often, try to catch such situation ourselfs + // match painters with new list of primitives if (!snap.fWithoutPrimitives) { - matchPrimitive(this.painters, snap.fPrimitives, clTFrame); - matchPrimitive(this.painters, snap.fPrimitives, clTPaveText, kTitle); - } + let i = 0, k = 0; + while (k < this.#painters.length) { + const sub = this.#painters[k]; + + // skip check secondary painters or painters without snapid + if (!sub.hasSnapId() || sub.isSecondary()) { + k++; + continue; // look only for painters with snapid + } - let isanyfound = false, isanyremove = false; + if (i >= snap.fPrimitives.length) + break; - // find and remove painters which no longer exists in the list - if (!snap.fWithoutPrimitives) { - for (let k = 0; k < this.painters.length; ++k) { - const sub = this.painters[k]; + const prim = snap.fPrimitives[i]; - // skip secondary painters or painters without snapid - if (!isStr(sub.snapid) || sub.isSecondary()) continue; // look only for painters with snapid + // only real objects drawing checked for existing painters + if ((prim.fKind !== webSnapIds.kSubPad) && (prim.fKind !== webSnapIds.kObject) && (prim.fKind !== webSnapIds.kSVG)) { + i++; + continue; // look only for primitives of real objects + } - const prim = snap.fPrimitives.find(prim => (prim.fObjectID === sub.snapid && !prim.$checked)); - if (prim) { - isanyfound = true; - prim.$checked = true; + if (prim.fObjectID === sub.getSnapId()) { + i++; + k++; } else { - // remove painter which does not found in the list of snaps - k = this.removePrimitive(k); // index modified - isanyremove = true; - if (k === -111) { - // main painter is removed - do full cleanup and redraw - isanyfound = false; - break; - } + missmatch = true; + break; } } - } - if (isanyremove) - delete this.pads_cache; - - if (!isanyfound && !snap.fWithoutPrimitives) { - // TODO: maybe just remove frame painter? - const fp = this.getFramePainter(), - old_painters = this.painters; - this.painters = []; - old_painters.forEach(objp => { - if (fp !== objp) objp.cleanup(); - }); - delete this.main_painter_ref; - if (fp) { - this.painters.push(fp); - fp.cleanFrameDrawings(); - fp.redraw(); + let cnt = 1000; + // remove painters without primitives, limit number of checks + while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { + if (this.removePrimitive(k) === -111) + missmatch = true; } - if (isFunc(this.removePadButtons)) this.removePadButtons(); - this.addPadButtons(true); + if (cnt < 0) + missmatch = true; } - const prev_name = this.selectCurrentPad(this.this_pad_name); + if (missmatch) { + const old_painters = this.#painters; + this.#painters = []; + old_painters.forEach(objp => objp.cleanup()); + this.setMainPainter(undefined, true); + if (isFunc(this.removePadButtons)) + this.removePadButtons(); + this.addPadButtons(true); + } - return this.drawNextSnap(snap.fPrimitives).then(() => { + return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { this.addPadInteractive(); - this.selectCurrentPad(prev_name); if (getActivePad() === this) this.getCanvPainter()?.producePadEvent('padredraw', this); + if (isFunc(this.onCanvasUpdated)) + this.onCanvasUpdated(this); return this; }); } /** @summary Deliver mouse move or click event to the web canvas * @private */ - deliverWebCanvasEvent(kind, x, y, hints) { - if (!this._deliver_webcanvas_events || !this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) return; + deliverWebCanvasEvent(kind, x, y, snapid) { + if (!this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) + return; + if ((kind === 'move') && !this.#deliver_move_events) + return; const cp = this.getCanvPainter(); - if (!cp || !cp._websocket || !cp._websocket.canSend(2) || cp._readonly) return; - - let selobj_snapid = ''; - if (hints && hints[0] && hints[0].painter?.snapid) - selobj_snapid = hints[0].painter.snapid.toString(); - - const msg = JSON.stringify([this.snapid, kind, x.toString(), y.toString(), selobj_snapid]); + if (!cp || !cp.canSendWebsocket(2) || cp.isReadonly()) + return; + const msg = JSON.stringify([this.getSnapId(), kind, x.toString(), y.toString(), snapid || '']); cp.sendWebsocket(`EVENT:${msg}`); } @@ -1933,14 +2246,14 @@ class TPadPainter extends ObjectPainter { * @return {Promise} with image data, coded with btoa() function * @private */ async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'pdf')) { + if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg')) return res; + if (!res || (format === 'svg')) + return res; const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ+7) : ''; + return (separ > 0) ? res.slice(separ + 7) : ''; }); } - return ''; } @@ -1950,7 +2263,7 @@ class TPadPainter extends ObjectPainter { getWebPadOptions(arg, cp) { let is_top = (arg === undefined), elem = null, scan_subpads = true; // no any options need to be collected in readonly mode - if (is_top && this._readonly) + if (is_top && this.isReadonly()) return ''; if (arg === 'only_this') { is_top = true; @@ -1959,23 +2272,27 @@ class TPadPainter extends ObjectPainter { is_top = true; scan_subpads = true; } - if (is_top) arg = []; - if (!cp) cp = this.iscan ? this : this.getCanvPainter(); - - if (this.snapid) { - elem = { _typename: 'TWebPadOptions', snapid: this.snapid.toString(), - active: !!this.is_active_pad, - cw: 0, ch: 0, w: [], - bits: 0, primitives: [], - logx: this.pad.fLogx, logy: this.pad.fLogy, logz: this.pad.fLogz, - gridx: this.pad.fGridx, gridy: this.pad.fGridy, - tickx: this.pad.fTickx, ticky: this.pad.fTicky, - mleft: this.pad.fLeftMargin, mright: this.pad.fRightMargin, - mtop: this.pad.fTopMargin, mbottom: this.pad.fBottomMargin, - xlow: 0, ylow: 0, xup: 1, yup: 1, - zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0 }; - - if (this.iscan) { + if (is_top) + arg = []; + if (!cp) + cp = this.isCanvas() ? this : this.getCanvPainter(); + + if (this.getSnapId()) { + elem = { + _typename: 'TWebPadOptions', snapid: this.getSnapId(), + active: Boolean(this.is_active_pad), + cw: 0, ch: 0, w: [], + bits: 0, primitives: [], + logx: this.#pad.fLogx, logy: this.#pad.fLogy, logz: this.#pad.fLogz, + gridx: this.#pad.fGridx, gridy: this.#pad.fGridy, + tickx: this.#pad.fTickx, ticky: this.#pad.fTicky, + mleft: this.#pad.fLeftMargin, mright: this.#pad.fRightMargin, + mtop: this.#pad.fTopMargin, mbottom: this.#pad.fBottomMargin, + xlow: 0, ylow: 0, xup: 1, yup: 1, + zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0, phi: 0, theta: 0 + }; + + if (this.isCanvas()) { elem.bits = this.getStatusBits(); elem.cw = this.getPadWidth(); elem.ch = this.getPadHeight(); @@ -1990,15 +2307,21 @@ class TPadPainter extends ObjectPainter { elem.yup = elem.ylow + rect.height / ch; } + if ((this.#pad.fTheta !== 30) || (this.#pad.fPhi !== 30)) { + elem.phi = this.#pad.fPhi; + elem.theta = this.#pad.fTheta; + } + if (this.getPadRanges(elem)) arg.push(elem); else - console.log(`fail to get ranges for pad ${this.pad.fName}`); + console.log(`fail to get ranges for pad ${this.#pad.fName}`); } - this.painters.forEach(sub => { + this.#painters.forEach(sub => { if (isFunc(sub.getWebPadOptions)) { - if (scan_subpads) sub.getWebPadOptions(arg, cp); + if (scan_subpads) + sub.getWebPadOptions(arg, cp); } else { const opt = createWebObjectOptions(sub); if (opt) @@ -2006,64 +2329,73 @@ class TPadPainter extends ObjectPainter { } }); - if (is_top) return toJSON(arg); + if (is_top) + return toJSON(arg); } /** @summary returns actual ranges in the pad, which can be applied to the server * @private */ getPadRanges(r) { - if (!r) return false; + if (!r) + return false; - const main = this.getFramePainter(), - p = this.svg_this_pad(); + const fp = this.getFramePainter(), + p = this.getPadSvg(); - r.ranges = main?.ranges_set ?? false; // indicate that ranges are assigned + r.ranges = fp?.ranges_set ?? false; // indicate that ranges are assigned - r.ux1 = r.px1 = r.ranges ? main.scale_xmin : 0; // need to initialize for JSON reader - r.uy1 = r.py1 = r.ranges ? main.scale_ymin : 0; - r.ux2 = r.px2 = r.ranges ? main.scale_xmax : 0; - r.uy2 = r.py2 = r.ranges ? main.scale_ymax : 0; - r.uz1 = r.ranges ? (main.scale_zmin ?? 0) : 0; - r.uz2 = r.ranges ? (main.scale_zmax ?? 0) : 0; + r.ux1 = r.px1 = r.ranges ? fp.scale_xmin : 0; // need to initialize for JSON reader + r.uy1 = r.py1 = r.ranges ? fp.scale_ymin : 0; + r.ux2 = r.px2 = r.ranges ? fp.scale_xmax : 0; + r.uy2 = r.py2 = r.ranges ? fp.scale_ymax : 0; + r.uz1 = r.ranges ? (fp.scale_zmin ?? 0) : 0; + r.uz2 = r.ranges ? (fp.scale_zmax ?? 0) : 0; - if (main) { - if (main.zoom_xmin !== main.zoom_xmax) { - r.zx1 = main.zoom_xmin; r.zx2 = main.zoom_xmax; + if (fp) { + if (fp.zoom_xmin !== fp.zoom_xmax) { + r.zx1 = fp.zoom_xmin; + r.zx2 = fp.zoom_xmax; } - if (main.zoom_ymin !== main.zoom_ymax) { - r.zy1 = main.zoom_ymin; r.zy2 = main.zoom_ymax; + if (fp.zoom_ymin !== fp.zoom_ymax) { + r.zy1 = fp.zoom_ymin; + r.zy2 = fp.zoom_ymax; } - if (main.zoom_zmin !== main.zoom_zmax) { - r.zz1 = main.zoom_zmin; r.zz2 = main.zoom_zmax; + if (fp.zoom_zmin !== fp.zoom_zmax) { + r.zz1 = fp.zoom_zmin; + r.zz2 = fp.zoom_zmax; } } - if (!r.ranges || p.empty()) return true; + if (!r.ranges || p.empty()) + return true; // calculate user range for full pad const func = (log, value, err) => { - if (!log) return value; - if (value <= 0) return err; + if (!log) + return value; + if (value <= 0) + return err; value = Math.log10(value); - if (log > 1) value = value/Math.log10(log); + if (log > 1) + value /= Math.log10(log); return value; - }, frect = main.getFrameRect(); + }, frect = fp.getFrameRect(); - r.ux1 = func(main.logx, r.ux1, 0); - r.ux2 = func(main.logx, r.ux2, 1); + r.ux1 = func(fp.logx, r.ux1, 0); + r.ux2 = func(fp.logx, r.ux2, 1); - let k = (r.ux2 - r.ux1)/(frect.width || 10); - r.px1 = r.ux1 - k*frect.x; - r.px2 = r.px1 + k*this.getPadWidth(); + let k = (r.ux2 - r.ux1) / (frect.width || 10); + r.px1 = r.ux1 - k * frect.x; + r.px2 = r.px1 + k * this.getPadWidth(); - r.uy1 = func(main.logy, r.uy1, 0); - r.uy2 = func(main.logy, r.uy2, 1); + r.uy1 = func(fp.logy, r.uy1, 0); + r.uy2 = func(fp.logy, r.uy2, 1); - k = (r.uy2 - r.uy1)/(frect.height || 10); - r.py1 = r.uy1 - k*frect.y; - r.py2 = r.py1 + k*this.getPadHeight(); + k = (r.uy2 - r.uy1) / (frect.height || 10); + r.py1 = r.uy1 - k * frect.y; + r.py2 = r.py1 + k * this.getPadHeight(); return true; } @@ -2071,39 +2403,40 @@ class TPadPainter extends ObjectPainter { /** @summary Show context menu for specified item * @private */ itemContextMenu(name) { - const rrr = this.svg_this_pad().node().getBoundingClientRect(), - evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getFramePainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) - selp = this.painters[indx]; - } - } - - if (!isFunc(selp?.fillContextMenu)) return; - - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.snapid) - return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); + const rrr = this.getPadSvg().node().getBoundingClientRect(), + evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; + + // use timeout to avoid conflict with mouse click and automatic menu close + if (name === 'pad') + return postponePromise(() => this.padContextMenu(evnt), 50); + + let selp = null, selkind; + + switch (name) { + case 'xaxis': + case 'yaxis': + case 'zaxis': + selp = this.getFramePainter(); + selkind = name[0]; + break; + case 'frame': + selp = this.getFramePainter(); + break; + default: { + const indx = parseInt(name); + if (Number.isInteger(indx)) + selp = this.#painters[indx]; + } + } + + if (!isFunc(selp?.fillContextMenu)) + return; + + return createMenu(evnt, selp).then(menu => { + const offline_menu = selp.fillContextMenu(menu, selkind); + if (offline_menu || selp.getSnapId()) + return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); + }); } /** @summary Save pad as image @@ -2117,13 +2450,25 @@ class TPadPainter extends ObjectPainter { * canvas_painter.saveAs('png', true, 'canvas.png'); */ saveAs(kind, full_canvas, filename) { if (!filename) - filename = (this.this_pad_name || (this.iscan ? 'canvas' : 'pad')) + '.' + kind; + filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; this.produceImage(full_canvas, kind).then(imgdata => { if (!imgdata) return console.error(`Fail to produce image ${filename}`); - saveFile(filename, (kind !== 'svg') ? imgdata : 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(imgdata)); + if ((browser.qt6 || browser.cef3) && this.getSnapId()) { + console.warn(`sending file ${filename} to server`); + let res = imgdata; + if (kind !== 'svg') { + const separ = res.indexOf('base64,'); + res = (separ > 0) ? res.slice(separ + 7) : ''; + } + if (res) + this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); + } else { + const prefix = (kind === 'svg') ? prSVG : (kind === 'json' ? prJSON : ''); + saveFile(filename, prefix ? prefix + encodeURIComponent(imgdata) : imgdata); + } }); } @@ -2138,11 +2483,14 @@ class TPadPainter extends ObjectPainter { return active_pp; } - /** @summary Prodce image for the pad + /** @summary Produce image for the pad * @return {Promise} with created image */ - async produceImage(full_canvas, file_format) { + async produceImage(full_canvas, file_format, args) { + if (file_format === 'json') + return isFunc(this.produceJSON) ? this.produceJSON(full_canvas ? 2 : 0) : ''; + const use_frame = (full_canvas === 'frame'), - elem = use_frame ? this.getFrameSvg(this.this_pad_name) : (full_canvas ? this.getCanvSvg() : this.svg_this_pad()), + elem = use_frame ? this.getFrameSvg() : (full_canvas ? this.getCanvSvg() : this.getPadSvg()), painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, items = []; // keep list of replaced elements, which should be moved back at the end @@ -2163,14 +2511,14 @@ class TPadPainter extends ObjectPainter { active_pp = pp; active_pp.drawActiveBorder(null, false); } + if (use_frame) + return; // do not make transformations for the frame - if (use_frame) return; // do not make transformations for the frame - - const item = { prnt: pp.svg_this_pad() }; + const item = { prnt: pp.getPadSvg() }; items.push(item); - // remove buttons from each subpad - const btns = pp.getLayerSvg('btns_layer', pp.this_pad_name); + // remove buttons from each sub-pad + const btns = pp.getLayerSvg('btns_layer'); item.btns_node = btns.node(); if (item.btns_node) { item.btns_prnt = item.btns_node.parentNode; @@ -2178,15 +2526,20 @@ class TPadPainter extends ObjectPainter { btns.remove(); } - const main = pp.getFramePainter(); - if (!isFunc(main?.render3D) || !isFunc(main?.access3dKind)) return; + const fp = pp.getFramePainter(); + if (!isFunc(fp?.access3dKind)) + return; - const can3d = main.access3dKind(); - if ((can3d !== constants.Embed3D.Overlay) && (can3d !== constants.Embed3D.Embed)) return; + const can3d = fp.access3dKind(); + if ((can3d !== constants.Embed3D.Overlay) && (can3d !== constants.Embed3D.Embed)) + return; - const sz2 = main.getSizeFor3d(constants.Embed3D.Embed), // get size and position of DOM element as it will be embed + const main = isFunc(fp.getRenderer) ? fp : fp.getMainPainter(), + canvas = isFunc(main.getRenderer) ? main.getRenderer()?.domElement : null; + if (!isFunc(main?.render3D) || !isObject(canvas)) + return; - canvas = main.renderer.domElement; + const sz2 = fp.getSizeFor3d(constants.Embed3D.Embed); // get size and position of DOM element as it will be embed main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately const dataUrl = canvas.toDataURL('image/png'); @@ -2196,7 +2549,7 @@ class TPadPainter extends ObjectPainter { item.foreign.remove(); } - const svg_frame = main.getFrameSvg(); + const svg_frame = fp.getFrameSvg(); item.frame_node = svg_frame.node(); if (item.frame_node) { item.frame_next = item.frame_node.nextSibling; @@ -2212,18 +2565,28 @@ class TPadPainter extends ObjectPainter { .attr('href', dataUrl); }, 'pads'); - let width = elem.property('draw_width'), height = elem.property('draw_height'); + let width = elem.property('draw_width'), + height = elem.property('draw_height'), + viewBox = ''; if (use_frame) { const fp = this.getFramePainter(); width = fp.getFrameWidth(); height = fp.getFrameHeight(); } + const scale = this.getPadScale(); + if (scale !== 1) { + viewBox = ` viewBox="0 0 ${width} ${height}"`; + width = Math.round(width / scale); + height = Math.round(height / scale); + } + if (settings.DarkMode || this.#pad?.$dark) + viewBox += ' style="filter: invert(100%)"'; const arg = (file_format === 'pdf') - ? { node: elem.node(), width, height, reset_tranform: use_frame } - : compressSVG(`<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">${elem.node().innerHTML}</svg>`); + ? { node: elem.node(), width, height, scale, reset_tranform: use_frame } + : compressSVG(`<svg width="${width}" height="${height}"${viewBox} xmlns="${nsSVG}">${elem.node().innerHTML}</svg>`); - return svgToImage(arg, file_format).then(res => { + return svgToImage(arg, file_format, args).then(res => { // reactivate border active_pp?.drawActiveBorder(null, true); @@ -2264,15 +2627,13 @@ class TPadPainter extends ObjectPainter { if (funcname === 'PadContextMenus') { evnt?.preventDefault(); evnt?.stopPropagation(); - if (closeMenu()) return; + if (closeMenu()) + return; return createMenu(evnt, this).then(menu => { - menu.add('header:Menus'); + menu.header('Menus'); - if (this.iscan) - menu.add('Canvas', 'pad', this.itemContextMenu); - else - menu.add('Pad', 'pad', this.itemContextMenu); + menu.add(this.isCanvas() ? 'Canvas' : 'Pad', 'pad', this.itemContextMenu); if (this.getFramePainter()) menu.add('Frame', 'frame', this.itemContextMenu); @@ -2286,15 +2647,22 @@ class TPadPainter extends ObjectPainter { menu.add('Z axis', 'zaxis', this.itemContextMenu); } - if (this.painters?.length) { - menu.add('separator'); + if (this.#painters?.length) { + menu.separator(); const shown = []; - this.painters.forEach((pp, indx) => { + this.#painters.forEach((pp, indx) => { const obj = pp?.getObject(); - if (!obj || (shown.indexOf(obj) >= 0)) return; - let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); - if (name) name += '::'; - name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); + if (!obj || (shown.indexOf(obj) >= 0)) + return; + let name; + if (isFunc(pp.getMenuHeader)) + name = pp.getMenuHeader(); + else { + name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); + if (name) + name += '::'; + name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); + } menu.add(name, indx, this.itemContextMenu); shown.push(obj); }); @@ -2309,15 +2677,16 @@ class TPadPainter extends ObjectPainter { let done = false; const prs = []; - for (let i = 0; i < this.painters.length; ++i) { - const pp = this.painters[i]; + for (let i = 0; i < this.#painters.length; ++i) { + const pp = this.#painters[i]; if (isFunc(pp.clickPadButton)) prs.push(pp.clickPadButton(funcname, evnt)); if (!done && isFunc(pp.clickButton)) { done = pp.clickButton(funcname); - if (isPromise(done)) prs.push(done); + if (isPromise(done)) + prs.push(done); } } @@ -2327,41 +2696,46 @@ class TPadPainter extends ObjectPainter { /** @summary Add button to the pad * @private */ addPadButton(btn, tooltip, funcname, keyname) { - if (!settings.ToolBar || this.isBatchMode()) return; + if (!settings.ToolBar || this.isBatchMode()) + return; - if (!this._buttons) this._buttons = []; + if (!this._buttons) + this._buttons = []; // check if there are duplications - for (let k = 0; k < this._buttons.length; ++k) - if (this._buttons[k].funcname === funcname) return; + for (let k = 0; k < this._buttons.length; ++k) { + if (this._buttons[k].funcname === funcname) + return; + } this._buttons.push({ btn, tooltip, funcname, keyname }); - const iscan = this.iscan || !this.has_canvas; - if (!iscan && (funcname.indexOf('Pad') !== 0) && (funcname !== 'enlargePad')) { + if (!this.isTopPad() && funcname.indexOf('Pad') && (funcname !== 'enlargePad')) { const cp = this.getCanvPainter(); - if (cp && (cp !== this)) cp.addPadButton(btn, tooltip, funcname); + if (cp && (cp !== this)) + cp.addPadButton(btn, tooltip, funcname); } } /** @summary Show pad buttons * @private */ showPadButtons() { - if (!this._buttons) return; + if (!this._buttons) + return; - PadButtonsHandler.assign(this); - this.showPadButtons(); + PadButtonsHandler.assign(this); + this.showPadButtons(); } /** @summary Add buttons for pad or canvas * @private */ addPadButtons(is_online) { - this.addPadButton('camera', 'Create PNG', this.iscan ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); + this.addPadButton('camera', 'Create PNG', this.isCanvas() ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); if (settings.ContextMenu) this.addPadButton('question', 'Access context menus', 'PadContextMenus'); - const add_enlarge = !this.iscan && this.has_canvas && this.hasObjectsToDraw(); + const add_enlarge = !this.isTopPad() && this.hasObjectsToDraw(); if (add_enlarge || this.enlargeMain('verify')) this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); @@ -2376,30 +2750,36 @@ class TPadPainter extends ObjectPainter { * @private */ decodeOptions(opt) { const pad = this.getObject(); - if (!pad) return; - - const d = new DrawOptions(opt); - - if (!this.options) this.options = {}; - - Object.assign(this.options, { GlobalColors: true, LocalColors: false, CreatePalette: 0, IgnorePalette: false, RotateFrame: false, FixFrame: false }); - - if (d.check('NOCOLORS') || d.check('NOCOL')) this.options.GlobalColors = this.options.LocalColors = false; - if (d.check('LCOLORS') || d.check('LCOL')) { this.options.GlobalColors = false; this.options.LocalColors = true; } - if (d.check('NOPALETTE') || d.check('NOPAL')) this.options.IgnorePalette = true; - if (d.check('ROTATE')) this.options.RotateFrame = true; - if (d.check('FIXFRAME')) this.options.FixFrame = true; - if (d.check('FIXSIZE') && this.iscan) this._fixed_size = true; - - if (d.check('CP', true)) this.options.CreatePalette = d.partAsInt(0, 0); + if (!pad) + return; - if (d.check('NOZOOMX')) this.options.NoZoomX = true; - if (d.check('NOZOOMY')) this.options.NoZoomY = true; - if (d.check('GRAYSCALE') && !pad.TestBit(kIsGrayscale)) - pad.InvertBit(kIsGrayscale); + const d = new DrawOptions(opt), + o = this.setOptions({ GlobalColors: true, LocalColors: false, CreatePalette: 0, IgnorePalette: false, RotateFrame: false, FixFrame: false }, opt); + + if (d.check('NOCOLORS') || d.check('NOCOL')) + o.GlobalColors = o.LocalColors = false; + if (d.check('LCOLORS') || d.check('LCOL')) { + o.GlobalColors = false; + o.LocalColors = true; + } + if (d.check('NOPALETTE') || d.check('NOPAL')) + o.IgnorePalette = true; + if (d.check('ROTATE')) + o.RotateFrame = true; + if (d.check('FIXFRAME')) + o.FixFrame = true; + if (d.check('FIXSIZE') && this.isCanvas()) + this._setFixedSize(true); + if (d.check('CP', true)) + o.CreatePalette = d.partAsInt(0, 0); + if (d.check('NOZOOMX')) + o.NoZoomX = true; + if (d.check('NOZOOMY')) + o.NoZoomY = true; + if (d.check('GRAYSCALE')) + pad.SetBit(kIsGrayscale, true); function forEach(func, p) { - if (!p) p = pad; func(p); const arr = p.fPrimitives?.arr || []; for (let i = 0; i < arr.length; ++i) { @@ -2408,63 +2788,59 @@ class TPadPainter extends ObjectPainter { } } - if (d.check('NOMARGINS')) forEach(p => { p.fLeftMargin = p.fRightMargin = p.fBottomMargin = p.fTopMargin = 0; }); - if (d.check('WHITE')) forEach(p => { p.fFillColor = 0; }); - if (d.check('LOG2X')) forEach(p => { p.fLogx = 2; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LOGX')) forEach(p => { p.fLogx = 1; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LOG2Y')) forEach(p => { p.fLogy = 2; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LOGY')) forEach(p => { p.fLogy = 1; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LOG2Z')) forEach(p => { p.fLogz = 2; }); - if (d.check('LOGZ')) forEach(p => { p.fLogz = 1; }); - if (d.check('LOGV')) forEach(p => { p.fLogv = 1; }); - if (d.check('LOG2')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 2; }); - if (d.check('LOG')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 1; }); - if (d.check('LNX')) forEach(p => { p.fLogx = 3; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); - if (d.check('LNY')) forEach(p => { p.fLogy = 3; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); - if (d.check('LN')) forEach(p => { p.fLogx = p.fLogy = p.fLogz = 3; }); - if (d.check('GRIDX')) forEach(p => { p.fGridx = 1; }); - if (d.check('GRIDY')) forEach(p => { p.fGridy = 1; }); - if (d.check('GRID')) forEach(p => { p.fGridx = p.fGridy = 1; }); - if (d.check('TICKX')) forEach(p => { p.fTickx = 1; }); - if (d.check('TICKY')) forEach(p => { p.fTicky = 1; }); - if (d.check('TICKZ')) forEach(p => { p.fTickz = 1; }); - if (d.check('TICK')) forEach(p => { p.fTickx = p.fTicky = 1; }); - if (d.check('OTX')) forEach(p => { p.$OTX = true; }); - if (d.check('OTY')) forEach(p => { p.$OTY = true; }); - if (d.check('CTX')) forEach(p => { p.$CTX = true; }); - if (d.check('CTY')) forEach(p => { p.$CTY = true; }); - if (d.check('RX')) forEach(p => { p.$RX = true; }); - if (d.check('RY')) forEach(p => { p.$RY = true; }); + function padOpt(name, func) { + if (d.check(name)) + forEach(func, pad); + } + + /* eslint-disable @stylistic/js/max-statements-per-line */ + padOpt('NOMARGINS', p => { p.fLeftMargin = p.fRightMargin = p.fBottomMargin = p.fTopMargin = 0; }); + padOpt('WHITE', p => { p.fFillColor = 0; }); + padOpt('LOG2X', p => { p.fLogx = 2; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LOGX', p => { p.fLogx = 1; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LOG2Y', p => { p.fLogy = 2; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LOGY', p => { p.fLogy = 1; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LOG2Z', p => { p.fLogz = 2; }); + padOpt('LOGZ', p => { p.fLogz = 1; }); + padOpt('LOGV', p => { p.fLogv = 1; }); + padOpt('LOG2', p => { p.fLogx = p.fLogy = p.fLogz = 2; }); + padOpt('LOG', p => { p.fLogx = p.fLogy = p.fLogz = 1; }); + padOpt('LNX', p => { p.fLogx = 3; p.fUxmin = 0; p.fUxmax = 1; p.fX1 = 0; p.fX2 = 1; }); + padOpt('LNY', p => { p.fLogy = 3; p.fUymin = 0; p.fUymax = 1; p.fY1 = 0; p.fY2 = 1; }); + padOpt('LN', p => { p.fLogx = p.fLogy = p.fLogz = 3; }); + padOpt('GRIDX', p => { p.fGridx = 1; }); + padOpt('GRIDY', p => { p.fGridy = 1; }); + padOpt('GRID', p => { p.fGridx = p.fGridy = 1; }); + padOpt('TICKX2', p => { p.fTickx = 2; }); + padOpt('TICKY2', p => { p.fTicky = 2; }); + padOpt('TICKZ2', p => { p.fTickz = 2; }); + padOpt('TICK2', p => { p.fTickx = p.fTicky = 2; }); + padOpt('TICKX', p => { p.fTickx = 1; }); + padOpt('TICKY', p => { p.fTicky = 1; }); + padOpt('TICKZ', p => { p.fTickz = 1; }); + padOpt('TICK', p => { p.fTickx = p.fTicky = 1; }); + ['OTX', 'OTY', 'CTX', 'CTY', 'NOEX', 'NOEY', 'RX', 'RY'] + .forEach(name => padOpt(name, p => { p['$' + name] = true; })); + + if (!d.empty() && pad?.fPrimitives) { + for (let n = 0; n < pad.fPrimitives.arr.length; ++n) { + if (d.check(`SUB${n}_`, true)) + pad.fPrimitives.opt[n] = d.part; + } + } this.storeDrawOpt(opt); } /** @summary draw TPad object */ static async draw(dom, pad, opt) { - const painter = new TPadPainter(dom, pad, false); - painter.decodeOptions(opt); - - if (painter.getCanvSvg().empty()) { - // one can draw pad without canvas - painter.has_canvas = false; - painter.this_pad_name = ''; - painter.setTopPainter(); - } else { - // pad painter will be registered in the canvas painters list - painter.addToPadPrimitives(painter.pad_name); - } - - if (pad?.$disable_drawing) - painter.pad_draw_disabled = true; + const painter = new TPadPainter(dom, pad, opt, false, true); painter.createPadSvg(); - if (painter.matchObjectType(clTPad) && (!painter.has_canvas || painter.hasObjectsToDraw())) + if (painter.matchObjectType(clTPad) && (painter.isTopPad() || painter.hasObjectsToDraw())) painter.addPadButtons(); - // we select current pad, where all drawing is performed - const prev_name = painter.has_canvas ? painter.selectCurrentPad(painter.this_pad_name) : undefined; - // set active pad selectActivePad({ pp: painter, active: true }); @@ -2472,12 +2848,10 @@ class TPadPainter extends ObjectPainter { return painter.drawPrimitives().then(() => { painter.showPadButtons(); painter.addPadInteractive(); - // we restore previous pad name - painter.selectCurrentPad(prev_name); return painter; }); } } // class TPadPainter -export { TPadPainter, PadButtonsHandler, clTButton, kIsGrayscale, createWebObjectOptions }; +export { TPadPainter, PadButtonsHandler, clTButton, kIsGrayscale, createWebObjectOptions, webSnapIds }; diff --git a/modules/gui.mjs b/modules/gui.mjs index 8a3b45014..35a75a0d5 100644 --- a/modules/gui.mjs +++ b/modules/gui.mjs @@ -1,20 +1,26 @@ -import { decodeUrl, settings, constants, gStyle, internals, browser, findFunction, parse, isFunc, isStr, isObject } from './core.mjs'; +import { decodeUrl, settings, constants, gStyle, internals, browser, + findFunction, parse, isFunc, isStr, isObject, isBatchMode, setBatchMode } from './core.mjs'; import { select as d3_select } from './d3.mjs'; -import { HierarchyPainter } from './gui/HierarchyPainter.mjs'; +import { addColor } from './base/colors.mjs'; +import { HierarchyPainter, parseAsArray } from './gui/HierarchyPainter.mjs'; import { setStoragePrefix, readSettings, readStyle } from './gui/utils.mjs'; +import { setDefaultDrawOpt } from './draw.mjs'; +import { createMenu, closeMenu } from './gui/menu.mjs'; /** @summary Read style and settings from URL * @private */ function readStyleFromURL(url) { - // first try to read settings from coockies + // first try to read settings from local storage const d = decodeUrl(url), prefix = d.get('storage_prefix'); if (isStr(prefix) && prefix) setStoragePrefix(prefix); - readSettings(); + if (readSettings()) + setDefaultDrawOpt(settings._dflt_drawopt); + readStyle(); function get_bool(name, field, special) { @@ -37,14 +43,28 @@ function readStyleFromURL(url) { } } + if (d.has('scale')) { + const s = parseInt(d.get('scale')); + settings.CanvasScale = Number.isInteger(s) ? s : 2; + } + + const b = d.get('batch'); + if (b !== undefined) { + setBatchMode(d !== 'off'); + if (b === 'png') + internals.batch_png = true; + } + get_bool('lastcycle', 'OnlyLastCycle'); get_bool('usestamp', 'UseStamp'); get_bool('dark', 'DarkMode'); + get_bool('approx_text_size', 'ApproxTextSize'); let mr = d.get('maxranges'); if (mr) { mr = parseInt(mr); - if (Number.isInteger(mr)) settings.MaxRanges = mr; + if (Number.isInteger(mr)) + settings.MaxRanges = mr; } if (d.has('wrong_http_response')) @@ -72,9 +92,15 @@ function readStyleFromURL(url) { inter = '000000'; if (inter.length === 6) { switch (inter[0]) { - case '0': settings.ToolBar = false; break; - case '1': settings.ToolBar = 'popup'; break; - case '2': settings.ToolBar = true; break; + case '0': + settings.ToolBar = false; + break; + case '1': + settings.ToolBar = 'popup'; + break; + case '2': + settings.ToolBar = true; + break; } inter = inter.slice(1); } @@ -96,24 +122,38 @@ function readStyleFromURL(url) { if (latex !== null) settings.Latex = constants.Latex.fromString(latex); - if (d.has('nomenu')) settings.ContextMenu = false; + if (d.has('nomenu')) + settings.ContextMenu = false; if (d.has('noprogress')) settings.ProgressBox = false; else get_bool('progress', 'ProgressBox', 'modal'); - if (d.has('notouch')) browser.touches = false; - if (d.has('adjframe')) settings.CanAdjustFrame = true; + if (d.has('notouch')) + browser.touches = false; + if (d.has('adjframe')) + settings.CanAdjustFrame = true; const has_toolbar = d.has('toolbar'); if (has_toolbar) { const toolbar = d.get('toolbar', ''); let val = null; - if (toolbar.indexOf('popup') >= 0) val = 'popup'; - if (toolbar.indexOf('left') >= 0) { settings.ToolBarSide = 'left'; val = 'popup'; } - if (toolbar.indexOf('right') >= 0) { settings.ToolBarSide = 'right'; val = 'popup'; } - if (toolbar.indexOf('vert') >= 0) { settings.ToolBarVert = true; val = 'popup'; } - if (toolbar.indexOf('show') >= 0) val = true; + if (toolbar.indexOf('popup') >= 0) + val = 'popup'; + if (toolbar.indexOf('left') >= 0) { + settings.ToolBarSide = 'left'; + val = 'popup'; + } + if (toolbar.indexOf('right') >= 0) { + settings.ToolBarSide = 'right'; + val = 'popup'; + } + if (toolbar.indexOf('vert') >= 0) { + settings.ToolBarVert = true; + val = 'popup'; + } + if (toolbar.indexOf('show') >= 0) + val = true; settings.ToolBar = val || ((toolbar.indexOf('0') < 0) && (toolbar.indexOf('false') < 0) && (toolbar.indexOf('off') < 0)); } @@ -125,19 +165,25 @@ function readStyleFromURL(url) { if (d.has('palette')) { const palette = parseInt(d.get('palette')); - if (Number.isInteger(palette) && (palette > 0) && (palette < 113)) settings.Palette = palette; + if (Number.isInteger(palette) && (palette > 0) && (palette < 113)) + settings.Palette = palette; } const render3d = d.get('render3d'), embed3d = d.get('embed3d'), geosegm = d.get('geosegm'); - if (render3d) settings.Render3D = constants.Render3D.fromString(render3d); - if (embed3d) settings.Embed3D = constants.Embed3D.fromString(embed3d); - if (geosegm) settings.GeoGradPerSegm = Math.max(2, parseInt(geosegm)); + if (render3d) + settings.Render3D = constants.Render3D.fromString(render3d); + if (embed3d) + settings.Embed3D = constants.Embed3D.fromString(embed3d); + if (geosegm) + settings.GeoGradPerSegm = Math.max(2, parseInt(geosegm)); get_bool('geocomp', 'GeoCompressComp'); - if (d.has('hlimit')) settings.HierarchyLimit = parseInt(d.get('hlimit')); + if (d.has('hlimit')) + settings.HierarchyLimit = parseInt(d.get('hlimit')); function get_int_style(name, field, dflt) { - if (!d.has(name)) return; + if (!d.has(name)) + return; const val = d.get(name); if (!val || (val === 'true') || (val === 'on')) gStyle[field] = dflt; @@ -145,18 +191,21 @@ function readStyleFromURL(url) { gStyle[field] = 0; else gStyle[field] = parseInt(val); - return gStyle[field] !== 0; + return gStyle[field]; } function get_float_style(name, field) { - if (!d.has(name)) return; + if (!d.has(name)) + return; const val = d.get(name), flt = Number.parseFloat(val); if (Number.isFinite(flt)) gStyle[field] = flt; } - if (d.has('histzero')) gStyle.fHistMinimumZero = true; - if (d.has('histmargin')) gStyle.fHistTopMargin = parseFloat(d.get('histmargin')); + if (d.has('histzero')) + gStyle.fHistMinimumZero = true; + if (d.has('histmargin')) + gStyle.fHistTopMargin = parseFloat(d.get('histmargin')); get_int_style('optstat', 'fOptStat', 1111); get_int_style('optfit', 'fOptFit', 0); const has_date = get_int_style('optdate', 'fOptDate', 1), @@ -165,6 +214,8 @@ function readStyleFromURL(url) { settings.ToolBarVert = true; get_float_style('datex', 'fDateX'); get_float_style('datey', 'fDateY'); + get_float_style('barwidth', 'fBarWidth'); + get_float_style('baroffset', 'fBarOffset'); get_int_style('opttitle', 'fOptTitle', 1); if (d.has('utc')) @@ -181,6 +232,20 @@ function readStyleFromURL(url) { gStyle.fStatFormat = d.get('statfmt', gStyle.fStatFormat); gStyle.fFitFormat = d.get('fitfmt', gStyle.fFitFormat); + + if (d.has('colors')) { + parseAsArray(d.get('colors')).forEach(elem => { + if (!isStr(elem)) + return; + const p = elem.indexOf('_'); + if (p < 0) + return; + const id = parseInt(elem.slice(0, p)), + col = elem.slice(p + 1); + if (col.match(/^[a-fA-F0-9]+/) && ((col.length === 6) || (col.length === 8))) + addColor('#' + col, null, id); + }); + } } @@ -199,7 +264,14 @@ async function buildGUI(gui_element, gui_kind = '') { myDiv.html(''); // clear element - const d = decodeUrl(); + const d = decodeUrl(), getSize = name => { + const res = d.has(name) ? d.get(name).split('x') : []; + if (res.length !== 2) + return null; + res[0] = parseInt(res[0]); + res[1] = parseInt(res[1]); + return res[0] > 0 && res[1] > 0 ? res : null; + }; let online = (gui_kind === 'online'), nobrowser = false, drawing = false; if (gui_kind === 'draw') @@ -212,25 +284,31 @@ async function buildGUI(gui_element, gui_kind = '') { readStyleFromURL(); - if (nobrowser) { - let guisize = d.get('divsize'); - if (guisize) { - guisize = guisize.split('x'); - if (guisize.length !== 2) guisize = null; - } + if (isBatchMode()) + nobrowser = true; - if (guisize) - myDiv.style('position', 'relative').style('width', guisize[0] + 'px').style('height', guisize[1] + 'px'); - else { - d3_select('html').style('height', '100%'); - d3_select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden'); - myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px'); - } + const divsize = getSize('divsize'), canvsize = getSize('canvsize'), smallpad = getSize('smallpad'); + if (divsize) + myDiv.style('position', 'relative').style('width', divsize[0] + 'px').style('height', divsize[1] + 'px'); + else if (!isBatchMode()) { + d3_select('html').style('height', '100%'); + d3_select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden'); + myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px'); + } + if (canvsize) { + settings.CanvasWidth = canvsize[0]; + settings.CanvasHeight = canvsize[1]; + } + if (smallpad) { + settings.SmallPad.width = smallpad[0]; + settings.SmallPad.height = smallpad[1]; } const hpainter = new HierarchyPainter('root', null); - if (online) hpainter.is_online = drawing ? 'draw' : 'online'; - if (drawing) hpainter.exclude_browser = true; + if (online) + hpainter.is_online = drawing ? 'draw' : 'online'; + if (drawing || isBatchMode()) + hpainter.exclude_browser = true; hpainter.start_without_browser = nobrowser; return hpainter.startGUI(myDiv).then(() => { @@ -241,7 +319,7 @@ async function buildGUI(gui_element, gui_kind = '') { const func = internals.getCachedObject || findFunction('GetCachedObject'), obj = isFunc(func) ? parse(func()) : undefined; if (isObject(obj)) - hpainter._cached_draw_object = obj; + hpainter.setCachedObject(obj); let opt = d.get('opt', ''); if (d.has('websocket')) opt += ';websocket'; @@ -249,4 +327,4 @@ async function buildGUI(gui_element, gui_kind = '') { }).then(() => hpainter); } -export { buildGUI, internals, readStyleFromURL, HierarchyPainter }; +export { buildGUI, internals, readStyleFromURL, HierarchyPainter, createMenu, closeMenu }; diff --git a/modules/gui/HierarchyPainter.mjs b/modules/gui/HierarchyPainter.mjs index 4df41f243..107c8ff44 100644 --- a/modules/gui/HierarchyPainter.mjs +++ b/modules/gui/HierarchyPainter.mjs @@ -1,19 +1,21 @@ -import { version, gStyle, httpRequest, create, createHttpRequest, loadScript, decodeUrl, - source_dir, settings, internals, browser, findFunction, - isArrayProto, isRootCollection, isBatchMode, isNodeJs, isObject, isFunc, isStr, _ensureJSROOT, - prROOT, clTList, clTMap, clTObjString, clTKey, clTFile, clTText, clTLatex, clTColor, clTStyle, kInspect, isPromise } from '../core.mjs'; +import { version, gStyle, httpRequest, create, createHttpRequest, loadScript, loadModules, decodeUrl, + source_dir, settings, internals, browser, findFunction, toJSON, + isArrayProto, isRootCollection, isBatchMode, isNodeJs, isObject, isFunc, isStr, getPromise, _ensureJSROOT, + clTList, clTMap, clTObjString, clTKey, clTFile, clTText, clTLatex, clTColor, clTStyle, + getKindForType, getTypeForKind, kInspect, isPromise } from '../core.mjs'; import { select as d3_select } from '../d3.mjs'; import { openFile, kBaseClass, clTStreamerInfoList, clTDirectory, clTDirectoryFile, nameStreamerInfo, addUserStreamer } from '../io.mjs'; import { getRGBfromTColor } from '../base/colors.mjs'; -import { BasePainter, getElementRect, _loadJSDOM, getTDatime, convertDate } from '../base/BasePainter.mjs'; +import { prJSON, BasePainter, getElementRect, _loadJSDOM, getTDatime, convertDate } from '../base/BasePainter.mjs'; import { getElementMainPainter, getElementCanvPainter, cleanup, ObjectPainter } from '../base/ObjectPainter.mjs'; import { createMenu } from './menu.mjs'; import { getDrawSettings, getDrawHandle, canDrawHandle, addDrawFunc, draw, redraw } from '../draw.mjs'; import { BatchDisplay, GridDisplay, TabsDisplay, FlexibleDisplay, BrowserLayout, getHPainter, setHPainter } from './display.mjs'; -import { showProgress, ToolbarIcons, registerForResize, injectStyle } from './utils.mjs'; +import { showProgress, ToolbarIcons, registerForResize, injectStyle, saveFile } from './utils.mjs'; -const kTopFolder = 'TopFolder'; +const kTopFolder = 'TopFolder', kExpand = 'expand', kPM = 'plusminus', kDfltDrawOpt = '__default_draw_option__', + cssValueNum = 'h_value_num', cssButton = 'h_button', cssItem = 'h_item', cssTree = 'h_tree'; function injectHStyle(node) { function img(name, sz, fmt, code) { @@ -21,21 +23,21 @@ function injectHStyle(node) { } const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', - border_color = settings.DarkMode ? 'green' : 'black', - shadow_color = settings.DarkMode ? '#555' : '#aaa'; + border_color = settings.DarkMode ? 'green' : 'black', + shadow_color = settings.DarkMode ? '#555' : '#aaa'; injectStyle(` -.jsroot .h_tree { display: block; white-space: nowrap; } -.jsroot .h_tree * { padding: 0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; box-sizing: content-box; line-height: 14px } -.jsroot .h_tree img { border: 0px; vertical-align: middle; } -.jsroot .h_tree a { text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } -.jsroot .h_tree p { font-weight: bold; white-space: nowrap; text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } +.jsroot .${cssTree} { display: block; white-space: nowrap; } +.jsroot .${cssTree} * { padding: 0; margin: 0; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; box-sizing: content-box; line-height: 14px } +.jsroot .${cssTree} img { border: 0px; vertical-align: middle; } +.jsroot .${cssTree} a { text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } +.jsroot .${cssTree} p { font-weight: bold; white-space: nowrap; text-decoration: none; vertical-align: top; white-space: nowrap; padding: 1px 2px 0px 2px; display: inline-block; margin: 0; } .jsroot .h_value_str { color: green; } -.jsroot .h_value_num { color: blue; } +.jsroot .${cssValueNum} { color: blue; } .jsroot .h_line { height: 18px; display: block; } -.jsroot .h_button { cursor: pointer; color: blue; text-decoration: underline; } -.jsroot .h_item { cursor: pointer; } -.jsroot .h_item:hover { text-decoration: underline; } +.jsroot .${cssButton} { cursor: pointer; color: blue; text-decoration: underline; } +.jsroot .${cssItem} { cursor: pointer; user-select: none; } +.jsroot .${cssItem}:hover { text-decoration: underline; } .jsroot .h_childs { overflow: hidden; display: block; } .jsroot_fastcmd_btn { height: 32px; width: 32px; display: inline-block; margin: 2px; padding: 2px; background-position: left 2px top 2px; background-repeat: no-repeat; background-size: 24px 24px; border-color: inherit; } @@ -89,34 +91,49 @@ function getSizeStr(sz) { if (sz < 10000) return sz.toFixed(0) + 'B'; if (sz < 1e6) - return (sz/1e3).toFixed(2) + 'KiB'; + return (sz / 1e3).toFixed(2) + 'KiB'; if (sz < 1e9) - return (sz/1e6).toFixed(2) + 'MiB'; - return (sz/1e9).toFixed(2) + 'GiB'; + return (sz / 1e6).toFixed(2) + 'MiB'; + return (sz / 1e9).toFixed(2) + 'GiB'; +} + +/** @summary Return ROOT version as string + * @private */ +function getVersionStr(v) { + const major = Math.floor(v / 10000); + let minor = Math.floor((v - major * 10000) / 100).toString(), + patch = (v % 100).toString(); + if (minor.length < 2) + minor = '0' + minor; + if (patch.length < 2) + patch = '0' + patch; + return `${major}.${minor}.${patch}`; } /** @summary draw list content * @desc used to draw all items from TList or TObjArray inserted into the TCanvas list of primitives * @private */ async function drawList(dom, lst, opt) { - if (!lst || !lst.arr) + if (!lst?.arr) return null; const handle = { - dom, lst, opt, - indx: -1, painter: null, - draw_next() { - while (++this.indx < this.lst.arr.length) { - const item = this.lst.arr[this.indx], - opt = (this.lst.opt && this.lst.opt[this.indx]) ? this.lst.opt[this.indx] : this.opt; - if (!item) continue; - return draw(this.dom, item, opt).then(p => { - if (p && !this.painter) this.painter = p; - return this.draw_next(); // reenter loop - }); - } - return this.painter; - } + dom, lst, opt, + indx: -1, painter: null, + draw_next() { + while (++this.indx < this.lst.arr.length) { + const item = this.lst.arr[this.indx], + opt2 = (this.lst.opt && this.lst.opt[this.indx]) ? this.lst.opt[this.indx] : this.opt; + if (!item) + continue; + return draw(this.dom, item, opt2).then(p => { + if (p && !this.painter) + this.painter = p; + return this.draw_next(); // reenter loop + }); + } + return this.painter; + } }; return handle.draw_next(); @@ -130,7 +147,7 @@ function folderHierarchy(item, obj) { if (!obj?.fFolders) return false; - if (obj.fFolders.arr.length === 0) { + if (!obj.fFolders.arr.length) { item._more = false; return true; } @@ -141,35 +158,7 @@ function folderHierarchy(item, obj) { const chld = obj.fFolders.arr[i]; item._childs.push({ _name: chld.fName, - _kind: prROOT + chld._typename, - _obj: chld - }); - } - return true; -} - -/** @summary Create hierarchy elements for TTask object - * @private */ -function taskHierarchy(item, obj) { - // function can be used for different derived classes - // we show not only child tasks, but all complex data members - - if (!obj?.fTasks) return false; - - objectHierarchy(item, obj, { exclude: ['fTasks', 'fName'] }); - - if ((obj.fTasks.arr.length === 0) && (item._childs.length === 0)) { - item._more = false; - return true; - } - - // item._childs = []; - - for (let i = 0; i < obj.fTasks.arr.length; ++i) { - const chld = obj.fTasks.arr[i]; - item._childs.push({ - _name: chld.fName, - _kind: prROOT + chld._typename, + _kind: getKindForType(chld._typename), _obj: chld }); } @@ -179,16 +168,18 @@ function taskHierarchy(item, obj) { /** @summary Create hierarchy elements for TList object * @private */ function listHierarchy(folder, lst) { - if (!isRootCollection(lst)) return false; + if (!isRootCollection(lst)) + return false; - if ((lst.arr === undefined) || (lst.arr.length === 0)) { + if (!lst.arr?.length) { folder._more = false; return true; } let do_context = false, prnt = folder; while (prnt) { - if (prnt._do_context) do_context = true; + if (prnt._do_context) + do_context = true; prnt = prnt._parent; } @@ -197,13 +188,15 @@ function listHierarchy(folder, lst) { for (let i = 0; i < lst.arr.length; ++i) { const obj = ismap ? lst.arr[i].first : lst.arr[i]; - if (!obj) continue; // for such objects index will be used as name + if (!obj) + continue; // for such objects index will be used as name const objname = obj.fName || obj.name; - if (!objname) continue; + if (!objname) + continue; const indx = names.indexOf(objname); if (indx >= 0) cnt[indx]++; - else { + else { cnt[names.length] = cycle[names.length] = 1; names.push(objname); } @@ -216,28 +209,37 @@ function listHierarchy(folder, lst) { if (!obj?._typename) { item = { _name: i.toString(), - _kind: prROOT + 'NULL', + _kind: getKindForType('NULL'), _title: 'NULL', _value: 'null', _obj: null - }; + }; } else { item = { _name: obj.fName || obj.name, - _kind: prROOT + obj._typename, + _kind: getKindForType(obj._typename), _title: `${obj.fTitle || ''} type:${obj._typename}`, _obj: obj }; switch (obj._typename) { - case clTColor: item._value = getRGBfromTColor(obj); break; + case clTColor: + item._value = getRGBfromTColor(obj); + break; case clTText: - case clTLatex: item._value = obj.fTitle; break; - case clTObjString: item._value = obj.fString; break; - default: if (lst.opt && lst.opt[i] && lst.opt[i].length) item._value = lst.opt[i]; + case clTLatex: + item._value = obj.fTitle; + break; + case clTObjString: + item._value = obj.fString; + break; + default: + if (lst.opt && lst.opt[i] && lst.opt[i].length) + item._value = lst.opt[i]; } - if (do_context && canDrawHandle(obj._typename)) item._direct_context = true; + if (do_context && canDrawHandle(obj._typename)) + item._direct_context = true; // if name is integer value, it should match array index if (!item._name || (Number.isInteger(parseInt(item._name)) && (parseInt(item._name) !== i)) || (lst.arr.indexOf(obj) < i)) @@ -261,19 +263,21 @@ function listHierarchy(folder, lst) { /** @summary Create hierarchy of TKey lists in file or sub-directory * @private */ function keysHierarchy(folder, keys, file, dirname) { - if (keys === undefined) return false; + if (keys === undefined) + return false; folder._childs = []; for (let i = 0; i < keys.length; ++i) { const key = keys[i]; - if (settings.OnlyLastCycle && (i > 0) && (key.fName === keys[i-1].fName) && (key.fCycle < keys[i-1].fCycle)) continue; + if (settings.OnlyLastCycle && (i > 0) && (key.fName === keys[i - 1].fName) && (key.fCycle < keys[i - 1].fCycle)) + continue; const item = { _name: key.fName + ';' + key.fCycle, _cycle: key.fCycle, - _kind: prROOT + key.fClassName, + _kind: getKindForType(key.fClassName), _title: key.fTitle + ` (size: ${getSizeStr(key.fObjlen)})`, _keyname: key.fName, _readobj: null, @@ -297,9 +301,10 @@ function keysHierarchy(folder, keys, file, dirname) { }; } } else if ((key.fClassName === clTList) && (key.fName === nameStreamerInfo)) { - if (settings.SkipStreamerInfos) continue; + if (settings.SkipStreamerInfos) + continue; item._name = nameStreamerInfo; - item._kind = prROOT + clTStreamerInfoList; + item._kind = getKindForType(clTStreamerInfoList); item._title = 'List of streamer infos for binary I/O'; item._readobj = file.fStreamerInfos; } @@ -313,7 +318,8 @@ function keysHierarchy(folder, keys, file, dirname) { /** @summary Create hierarchy for arbitrary object * @private */ function objectHierarchy(top, obj, args = undefined) { - if (!top || (obj === null)) return false; + if (!top || (obj === null)) + return false; top._childs = []; @@ -321,10 +327,10 @@ function objectHierarchy(top, obj, args = undefined) { if (proto === '[object DataView]') { let item = { - _parent: top, - _name: 'size', - _value: obj.byteLength.toString(), - _vclass: 'h_value_num' + _parent: top, + _name: 'size', + _value: obj.byteLength.toString(), + _vclass: cssValueNum }; top._childs.push(item); @@ -333,10 +339,10 @@ function objectHierarchy(top, obj, args = undefined) { for (let k = 0; k < obj.byteLength; ++k) { if (k % 16 === 0) { item = { - _parent: top, - _name: k.toString(), - _value: '', - _vclass: 'h_value_num' + _parent: top, + _name: k.toString(), + _value: '', + _vclass: cssValueNum }; while (item._name.length < namelen) item._name = '0' + item._name; @@ -344,7 +350,8 @@ function objectHierarchy(top, obj, args = undefined) { } let val = obj.getUint8(k).toString(16); - while (val.length < 2) val = '0'+val; + while (val.length < 2) + val = '0' + val; if (item._value) item._value += (k % 4 === 0) ? ' | ' : ' '; @@ -353,11 +360,15 @@ function objectHierarchy(top, obj, args = undefined) { return true; } - // check nosimple property in all parents + // check _nosimple property in all parents let nosimple = true, do_context = false, prnt = top; while (prnt) { - if (prnt._do_context) do_context = true; - if ('_nosimple' in prnt) { nosimple = prnt._nosimple; break; } + if (prnt._do_context) + do_context = true; + if ('_nosimple' in prnt) { + nosimple = prnt._nosimple; + break; + } prnt = prnt._parent; } @@ -365,13 +376,15 @@ function objectHierarchy(top, obj, args = undefined) { compress = isarray && (obj.length > settings.HierarchyLimit); let arrcompress = false; - if (isarray && (top._name === 'Object') && !top._parent) top._name = 'Array'; + if (isarray && (top._name === 'Object') && !top._parent) + top._name = 'Array'; if (compress) { arrcompress = true; for (let k = 0; k < obj.length; ++k) { const typ = typeof obj[k]; - if ((typ === 'number') || (typ === 'boolean') || ((typ === 'string') && (obj[k].length < 16))) continue; + if ((typ === 'number') || (typ === 'boolean') || ((typ === 'string') && (obj[k].length < 16))) + continue; arrcompress = false; break; } } @@ -383,24 +396,27 @@ function objectHierarchy(top, obj, args = undefined) { if (!top._title) { if (obj._typename) - top._title = prROOT + obj._typename; + top._title = getKindForType(obj._typename); else if (isarray) top._title = 'Array len: ' + obj.length; } if (arrcompress) { for (let k = 0; k < obj.length;) { - let nextk = Math.min(k+10, obj.length), allsame = true, prevk = k; + let nextk = Math.min(k + 10, obj.length), allsame = true, prevk = k; while (allsame) { allsame = true; - for (let d=prevk; d<nextk; ++d) - if (obj[k]!==obj[d]) allsame = false; + for (let d = prevk; d < nextk; ++d) { + if (obj[k] !== obj[d]) + allsame = false; + } if (allsame) { - if (nextk===obj.length) break; + if (nextk === obj.length) + break; prevk = nextk; - nextk = Math.min(nextk+10, obj.length); + nextk = Math.min(nextk + 10, obj.length); } else if (prevk !== k) { // last block with similar nextk = prevk; @@ -409,14 +425,14 @@ function objectHierarchy(top, obj, args = undefined) { } } - const item = { _parent: top, _name: k+'..'+(nextk-1), _vclass: 'h_value_num' }; + const item = { _parent: top, _name: k + '..' + (nextk - 1), _vclass: cssValueNum }; if (allsame) item._value = obj[k].toString(); - else { + else { item._value = ''; for (let d = k; d < nextk; ++d) - item._value += ((d===k) ? '[ ' : ', ') + obj[d].toString(); + item._value += ((d === k) ? '[ ' : ', ') + obj[d].toString(); item._value += ' ]'; } @@ -430,23 +446,37 @@ function objectHierarchy(top, obj, args = undefined) { let lastitem, lastkey, lastfield, cnt; for (const key in obj) { - if ((key === '_typename') || (key[0] === '$')) continue; + if ((key === '_typename') || (key[0] === '$')) + continue; const fld = obj[key]; - if (isFunc(fld)) continue; - if (args?.exclude && (args.exclude.indexOf(key) >= 0)) continue; + if (isFunc(fld)) + continue; + if (args?.exclude && (args.exclude.indexOf(key) >= 0)) + continue; if (compress && lastitem) { - if (lastfield===fld) { ++cnt; lastkey = key; continue; } - if (cnt > 0) lastitem._name += '..' + lastkey; + if (lastfield === fld) { + ++cnt; + lastkey = key; + continue; + } + if (cnt > 0) + lastitem._name += '..' + lastkey; } const item = { _parent: top, _name: key }; - if (compress) { lastitem = item; lastkey = key; lastfield = fld; cnt = 0; } + if (compress) { + lastitem = item; + lastkey = key; + lastfield = fld; + cnt = 0; + } if (fld === null) { item._value = item._title = 'null'; - if (!nosimple) top._childs.push(item); + if (!nosimple) + top._childs.push(item); continue; } @@ -458,7 +488,7 @@ function objectHierarchy(top, obj, args = undefined) { if (isArrayProto(proto) > 0) { item._title = 'array len=' + fld.length; simple = (proto !== '[object Array]'); - if (fld.length === 0) { + if (!fld.length) { item._value = '[ ]'; item._more = false; // hpainter will not try to expand again } else { @@ -477,14 +507,15 @@ function objectHierarchy(top, obj, args = undefined) { item._more = false; item._title = 'Date'; item._value = fld.toString(); - item._vclass = 'h_value_num'; + item._vclass = cssValueNum; } else { if (fld.$kind || fld._typename) - item._kind = item._title = prROOT + (fld.$kind || fld._typename); + item._kind = item._title = getKindForType(fld.$kind || fld._typename); if (fld._typename) { item._title = fld._typename; - if (do_context && canDrawHandle(fld._typename)) item._direct_context = true; + if (do_context && canDrawHandle(fld._typename)) + item._direct_context = true; } // check if object already shown in hierarchy (circular dependency) @@ -496,7 +527,7 @@ function objectHierarchy(top, obj, args = undefined) { if (inparent) { item._value = '{ prnt }'; - item._vclass = 'h_value_num'; + item._vclass = cssValueNum; item._more = false; simple = true; } else { @@ -504,15 +535,22 @@ function objectHierarchy(top, obj, args = undefined) { item._more = false; switch (fld._typename) { - case clTColor: item._value = getRGBfromTColor(fld); break; + case clTColor: + item._value = getRGBfromTColor(fld); + break; case clTText: - case clTLatex: item._value = fld.fTitle; break; - case clTObjString: item._value = fld.fString; break; + case clTLatex: + item._value = fld.fTitle; + break; + case clTObjString: + item._value = fld.fString; + break; default: if (isRootCollection(fld) && isObject(fld.arr)) { item._value = fld.arr.length ? '[...]' : '[]'; item._title += ', size:' + fld.arr.length; - if (fld.arr.length > 0) item._more = true; + if (fld.arr.length) + item._more = true; } else { item._more = true; item._value = '{ }'; @@ -520,21 +558,21 @@ function objectHierarchy(top, obj, args = undefined) { } } } - } else if ((typeof fld === 'number') || (typeof fld === 'boolean')) { + } else if ((typeof fld === 'number') || (typeof fld === 'boolean') || (typeof fld === 'bigint')) { simple = true; if (key === 'fBits') item._value = '0x' + fld.toString(16); else item._value = fld.toString(); - item._vclass = 'h_value_num'; + item._vclass = cssValueNum; } else if (isStr(fld)) { simple = true; - item._value = '"' + fld.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>') + '"'; + item._value = '"' + fld + '"'; item._vclass = 'h_value_str'; } else if (typeof fld === 'undefined') { simple = true; item._value = 'undefined'; - item._vclass = 'h_value_num'; + item._vclass = cssValueNum; } else { simple = true; alert(`miss ${key} type ${typeof fld}`); @@ -550,6 +588,33 @@ function objectHierarchy(top, obj, args = undefined) { return true; } +/** @summary Create hierarchy elements for TTask object + * @desc function can be used for different derived classes + * we show not only child tasks, but all complex data members + * @private */ +function taskHierarchy(item, obj) { + if (!obj?.fTasks) + return false; + + objectHierarchy(item, obj, { exclude: ['fTasks', 'fName'] }); + + if (!obj.fTasks.arr.length && !item._childs.length) { + item._more = false; + return true; + } + + for (let i = 0; i < obj.fTasks.arr.length; ++i) { + const chld = obj.fTasks.arr[i]; + item._childs.push({ + _name: chld.fName, + _kind: getKindForType(chld._typename), + _obj: chld + }); + } + + return true; +} + /** @summary Create hierarchy for streamer info object * @private */ function createStreamerInfoContent(lst) { @@ -558,7 +623,8 @@ function createStreamerInfoContent(lst) { for (let i = 0; i < lst.arr.length; ++i) { const entry = lst.arr[i]; - if (entry._typename === clTList) continue; + if (entry._typename === clTList) + continue; if (typeof entry.fName === 'undefined') { console.warn(`strange element in StreamerInfo with type ${entry._typename}`); @@ -582,7 +648,8 @@ function createStreamerInfoContent(lst) { continue; for (let l = 0; l < entry.fElements.arr.length; ++l) { const elem = entry.fElements.arr[l]; - if (!elem?.fName) continue; + if (!elem?.fName) + continue; let _name = `${elem.fTypeName} ${elem.fName}`; const _title = `${elem.fTypeName} type:${elem.fType}`; if (elem.fArrayDim === 1) @@ -621,7 +688,7 @@ function markAsStreamerInfo(h, item, obj) { /** @summary Create hierarchy for object inspector * @private */ function createInspectorContent(obj) { - const h = { _name: 'Object', _title: '', _click_action: 'expand', _nosimple: false, _do_context: true }; + const h = { _name: 'Object', _title: '', _click_action: kExpand, _nosimple: false, _do_context: true }; if (isStr(obj.fName) && obj.fName) h._name = obj.fName; @@ -649,13 +716,15 @@ function createInspectorContent(obj) { function parseAsArray(val) { const res = []; - if (!isStr(val)) return res; + if (!isStr(val)) + return res; val = val.trim(); - if (!val) return res; + if (!val) + return res; // return as array with single element - if ((val.length < 2) || (val[0] !== '[') || (val[val.length-1] !== ']')) { + if ((val.length < 2) || (val.at(0) !== '[') || (val.at(-1) !== ']')) { res.push(val); return res; } @@ -665,33 +734,45 @@ function parseAsArray(val) { for (let indx = 1; indx < val.length; ++indx) { if (nquotes > 0) { - if (val[indx] === '\'') nquotes--; + if (val[indx] === '\'') + nquotes--; continue; } if (ndouble > 0) { - if (val[indx] === '"') ndouble--; + 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; } - // eslint-disable-next-line no-fallthrough + case '\'': + nquotes++; + break; + case '"': + ndouble++; + break; + case '[': + nbr++; + break; + case ']': + if (indx < val.length - 1) { + nbr--; + break; + } + // eslint-disable-next-line no-fallthrough case ',': if (nbr === 0) { let sub = val.substring(last, indx).trim(); - if ((sub.length > 1) && (sub[0] === sub[sub.length-1]) && ((sub[0] === '"') || (sub[0] === '\''))) - sub = sub.slice(1, sub.length-1); + if ((sub.length > 1) && (sub.at(0) === sub.at(-1)) && ((sub[0] === '"') || (sub[0] === '\''))) + sub = sub.slice(1, sub.length - 1); res.push(sub); - last = indx+1; + last = indx + 1; } break; } } - if (res.length === 0) - res.push(val.slice(1, val.length-1).trim()); + if (!res.length) + res.push(val.slice(1, val.length - 1).trim()); return res; } @@ -700,7 +781,7 @@ function parseAsArray(val) { /** @summary central function for expand of all online items * @private */ function onlineHierarchy(node, obj) { - if (obj && node && ('_childs' in obj)) { + if (node && obj?._childs) { for (let n = 0; n < obj._childs.length; ++n) { if (obj._childs[n]._more || obj._childs[n]._childs) obj._childs[n]._expand = onlineHierarchy; @@ -720,7 +801,7 @@ function canExpandHandle(handle) { return handle?.expand || handle?.get_expand || handle?.expand_item; } -const kindTFile = prROOT + clTFile; +const kindTFile = getKindForType(clTFile); /** * @summary Painter of hierarchical structures @@ -737,6 +818,14 @@ const kindTFile = prROOT + clTFile; class HierarchyPainter extends BasePainter { + #monitoring_interval; // monitoring time interval + #monitoring_on; // if monitoring enabled + #monitoring_handle; // timer handle for monitoring + #monitoring_frame; // animation frame for monitoring + #one_by_one; // process drop items one by one + #topname; // top item name + #cached_draw_object; // cached object for first draw + /** @summary Create painter * @param {string} name - symbolic name * @param {string} frameid - element id where hierarchy is drawn @@ -781,10 +870,11 @@ class HierarchyPainter extends BasePainter { * @private */ fileHierarchy(file, folder) { const painter = this; - if (!folder) folder = {}; + if (!folder) + folder = {}; folder._name = file.fFileName; - folder._title = (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}, modified: ${convertDate(getTDatime(file.fDatimeM))}`; + folder._title = (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}, version: ${getVersionStr(file.fVersion)}, modified: ${convertDate(getTDatime(file.fDatimeM))}`; folder._kind = kindTFile; folder._file = file; folder._fullurl = file.fFullURL; @@ -792,52 +882,59 @@ class HierarchyPainter extends BasePainter { folder._had_direct_read = false; // this is central get method, item or itemname can be used, returns promise folder._get = function(item, itemname) { - if (item?._readobj) - return Promise.resolve(item._readobj); - - if (item) itemname = painter.itemFullName(item, this); - - const readFileObject = file => { - if (!this._file) this._file = file; - - if (!file) return Promise.resolve(null); - - return file.readObject(itemname).then(obj => { - // if object was read even when item did not exist try to reconstruct new hierarchy - if (!item && obj) { - // first try to found last read directory - const d = painter.findItem({ name: itemname, top: this, last_exists: true, check_keys: true }); - if ((d?.last !== undefined) && (d.last !== this)) { - // reconstruct only subdir hierarchy - const dir = file.getDir(painter.itemFullName(d.last, this)); - if (dir) { - d.last._name = d.last._keyname; - const dirname = painter.itemFullName(d.last, this); - keysHierarchy(d.last, dir.fKeys, file, dirname + '/'); - } - } else { - // reconstruct full file hierarchy - keysHierarchy(this, file.fKeys, file, ''); + if (item?._readobj) + return Promise.resolve(item._readobj); + + if (item) + itemname = painter.itemFullName(item, this); + + const readFileObject = file2 => { + if (!this._file) + this._file = file2; + + if (!file2) + return Promise.resolve(null); + + return file2.readObject(itemname).then(obj => { + // if object was read even when item did not exist try to reconstruct new hierarchy + if (!item && obj) { + // first try to found last read directory + const d = painter.findItem({ name: itemname, top: this, last_exists: true, check_keys: true }); + if ((d?.last !== undefined) && (d.last !== this)) { + // reconstruct only sub-directory hierarchy + const dir = file2.getDir(painter.itemFullName(d.last, this)); + if (dir) { + d.last._name = d.last._keyname; + const dirname = painter.itemFullName(d.last, this); + keysHierarchy(d.last, dir.fKeys, file2, dirname + '/'); } - item = painter.findItem({ name: itemname, top: this }); - } - - if (item) { - item._readobj = obj; - // remove cycle number for objects supporting expand - if ('_expand' in item) item._name = item._keyname; + } else { + // reconstruct full file hierarchy + keysHierarchy(this, file2.fKeys, file2, ''); } + item = painter.findItem({ name: itemname, top: this }); + } - return obj; - }); - }; + if (item) { + item._readobj = obj; + // remove cycle number for objects supporting expand + if ('_expand' in item) + item._name = item._keyname; + } - if (this._file) return readFileObject(this._file); - if (this._localfile) return openFile(this._localfile).then(f => readFileObject(f)); - if (this._fullurl) return openFile(this._fullurl).then(f => readFileObject(f)); - return Promise.resolve(null); + return obj; + }); }; + if (this._file) + return readFileObject(this._file); + if (this._localfile) + return openFile(this._localfile).then(f => readFileObject(f)); + if (this._fullurl) + return openFile(this._fullurl).then(f => readFileObject(f)); + return Promise.resolve(null); + }; + keysHierarchy(folder, file.fKeys, file, ''); return folder; @@ -849,13 +946,14 @@ class HierarchyPainter extends BasePainter { * @private */ forEachItem(func, top) { function each_item(item, prnt) { - if (!item) return; - if (prnt) item._parent = prnt; + if (!item) + return; + if (prnt) + item._parent = prnt; func(item); - if ('_childs' in item) { - for (let n = 0; n < item._childs.length; ++n) - each_item(item._childs[n], item); - } + const len = item._childs?.length ?? 0; + for (let n = 0; n < len; ++n) + each_item(item._childs[n], item); } if (isFunc(func)) @@ -873,27 +971,31 @@ class HierarchyPainter extends BasePainter { * @private */ findItem(arg) { function find_in_hierarchy(top, fullname) { - if (!fullname || !top) return top; + if (!fullname || !top) + return top; let pos = fullname.length; if (!top._parent && (top._kind !== kTopFolder) && (fullname.indexOf(top._name) === 0)) { // it is allowed to provide item name, which includes top-parent like file.root/folder/item // but one could skip top-item name, if there are no other items - if (fullname === top._name) return top; + if (fullname === top._name) + return top; const len = top._name.length; if (fullname[len] === '/') { - fullname = fullname.slice(len+1); + fullname = fullname.slice(len + 1); pos = fullname.length; } } function process_child(child, ignore_prnt) { // set parent pointer when searching child - if (!ignore_prnt) child._parent = top; + if (!ignore_prnt) + child._parent = top; - if ((pos >= fullname.length-1) || (pos < 0)) return child; + if ((pos >= fullname.length - 1) || (pos < 0)) + return child; return find_in_hierarchy(child, fullname.slice(pos + 1)); } @@ -921,19 +1023,20 @@ class HierarchyPainter extends BasePainter { if (arg.check_keys) { let newest = null; for (let i = 0; i < top._childs.length; ++i) { - if (top._childs[i]._keyname === localname) { - if (!newest || (newest._cycle < top._childs[i]._cycle)) - newest = top._childs[i]; - } + if (top._childs[i]._keyname === localname) { + if (!newest || (newest._cycle < top._childs[i]._cycle)) + newest = top._childs[i]; + } } - if (newest) return process_child(newest); + if (newest) + return process_child(newest); } let allow_index = arg.allow_index; - if ((localname[0] === '[') && (localname[localname.length-1] === ']') && - /^\d+$/.test(localname.slice(1, localname.length-1))) { + if ((localname.at(0) === '[') && (localname.at(-1) === ']') && + /^\d+$/.test(localname.slice(1, localname.length - 1))) { allow_index = true; - localname = localname.slice(1, localname.length-1); + localname = localname.slice(1, localname.length - 1); } // when search for the elements it could be allowed to check index @@ -948,18 +1051,19 @@ class HierarchyPainter extends BasePainter { } if (arg.force) { - // if did not found element with given name we just generate it - if (top._childs === undefined) top._childs = []; - pos = fullname.indexOf('/'); - const child = { _name: ((pos < 0) ? fullname : fullname.slice(0, pos)) }; - top._childs.push(child); - return process_child(child); + // if did not found element with given name we just generate it + if (top._childs === undefined) + top._childs = []; + pos = fullname.indexOf('/'); + const child = { _name: ((pos < 0) ? fullname : fullname.slice(0, pos)) }; + top._childs.push(child); + return process_child(child); } return arg.last_exists ? { last: top, rest: fullname } : null; } - let top = this.h, itemname = ''; + let top = this.h, itemname; if (isStr(arg)) { itemname = arg; @@ -994,11 +1098,15 @@ class HierarchyPainter extends BasePainter { while (node) { // online items never includes top-level folder - if ((node._online !== undefined) && !uptoparent) return res; + if ((node._online !== undefined) && !uptoparent) + return res; - if ((node === uptoparent) || (node._kind === kTopFolder)) break; - if (compact && !node._parent) break; // in compact form top-parent is not included - if (res) res = '/' + res; + if ((node === uptoparent) || (node._kind === kTopFolder)) + break; + if (compact && !node._parent) + break; // in compact form top-parent is not included + if (res) + res = '/' + res; res = node._name + res; node = node._parent; } @@ -1006,43 +1114,43 @@ class HierarchyPainter extends BasePainter { return res; } - /** @summary Executes item marked as 'Command' - * @desc If command requires additional arguments, they could be specified as extra arguments arg1, arg2, ... - * @param {String} itemname - name of command item - * @param {Object} [elem] - HTML element for command execution - * @param [arg1] - first optional argument - * @param [arg2] - second optional argument and so on - * @return {Promise} with command result */ - async executeCommand(itemname, elem) { + /** @summary Executes item marked as 'Command' + * @desc If command requires additional arguments, they could be specified as extra arguments arg1, arg2, ... + * @param {String} itemname - name of command item + * @param {Object} [elem] - HTML element for command execution + * @param [arg1] - first optional argument + * @param [arg2] - second optional argument and so on + * @return {Promise} with command result */ + async executeCommand(itemname, elem, ...userargs) { const hitem = this.findItem(itemname), url = this.getOnlineItemUrl(hitem) + '/cmd.json', d3node = d3_select(elem), cmdargs = []; - if ('_numargs' in hitem) { - for (let n = 0; n < hitem._numargs; ++n) - cmdargs.push((n+2 < arguments.length) ? arguments[n+2] : ''); - } + for (let n = 0; n < (hitem._numargs ?? 0); ++n) + cmdargs.push(n < userargs.length ? userargs[n] : ''); - const promise = (cmdargs.length === 0) || !elem + const promise = !cmdargs.length || !elem ? Promise.resolve(cmdargs) : createMenu().then(menu => menu.showCommandArgsDialog(hitem._name, cmdargs)); return promise.then(args => { - if (args === null) return false; + if (args === null) + return false; let urlargs = ''; for (let k = 0; k < args.length; ++k) - urlargs += `${k>0?'&':'?'}arg${k+1}=${args[k]}`; + urlargs += `${k > 0 ? '&' : '?'}arg${k + 1}=${args[k]}`; - if (!d3node.empty()) { + if (!d3node.empty()) { d3node.style('background', 'yellow'); if (hitem._title) d3node.attr('title', 'Executing ' + hitem._title); } return httpRequest(url + urlargs, 'text').then(res => { - if (d3node.empty()) return res; + if (d3node.empty()) + return res; const col = (res && (res !== 'false')) ? 'green' : 'red'; d3node.style('background', col); if (hitem._title) @@ -1078,7 +1186,7 @@ class HierarchyPainter extends BasePainter { if (isStr(arg)) itemname = arg; - else if (isObject(arg)) { + else if (isObject(arg)) { if ((arg._parent !== undefined) && (arg._name !== undefined) && (arg._kind !== undefined)) item = arg; else if (arg.name !== undefined) @@ -1095,8 +1203,10 @@ class HierarchyPainter extends BasePainter { return result; } - if (item) itemname = this.itemFullName(item); - else item = this.findItem({ name: itemname, allow_index: true, check_keys: true }); + if (item) + itemname = this.itemFullName(item); + else + item = this.findItem({ name: itemname, allow_index: true, check_keys: true }); // if item not found, try to find nearest parent which could allow us to get inside @@ -1115,9 +1225,11 @@ class HierarchyPainter extends BasePainter { } return this.expandItem(parentname, undefined, options !== 'hierarchy_expand_verbose').then(res => { - if (!res) return result; + if (!res) + return result; let newparentname = this.itemFullName(d.last); - if (newparentname) newparentname += '/'; + if (newparentname) + newparentname += '/'; return this.getObject({ name: newparentname + d.rest, rest: d.rest }, options); }); } @@ -1132,9 +1244,13 @@ class HierarchyPainter extends BasePainter { // normally search _get method in the parent items let curr = item; while (curr) { - if (isFunc(curr._get)) - return curr._get(item, null, options).then(obj => { result.obj = obj; return result; }); - curr = ('_parent' in curr) ? curr._parent : null; + if (isFunc(curr._get)) { + return curr._get(item, null, options).then(obj => { + result.obj = obj; + return result; + }); + } + curr = curr._parent; } return result; @@ -1143,19 +1259,24 @@ class HierarchyPainter extends BasePainter { /** @summary returns true if item is last in parent childs list * @private */ isLastSibling(hitem) { - if (!hitem || !hitem._parent || !hitem._parent._childs) return false; + if (!hitem || !hitem._parent || !hitem._parent._childs) + return false; const chlds = hitem._parent._childs; let indx = chlds.indexOf(hitem); - if (indx < 0) return false; - while (++indx < chlds.length) - if (!('_hidden' in chlds[indx])) return false; + if (indx < 0) + return false; + while (++indx < chlds.length) { + if (!('_hidden' in chlds[indx])) + return false; + } return true; } /** @summary Create item html code * @private */ addItemHtml(hitem, d3prnt, arg) { - if (!hitem || ('_hidden' in hitem)) return true; + if (!hitem || ('_hidden' in hitem)) + return true; const isroot = (hitem === this.h), has_childs = ('_childs' in hitem), @@ -1164,16 +1285,21 @@ class HierarchyPainter extends BasePainter { let img1 = '', img2 = '', can_click = false, break_list = false, d3cont; if (handle) { - if ('icon' in handle) img1 = handle.icon; - if ('icon2' in handle) img2 = handle.icon2; + if ('icon' in handle) + img1 = handle.icon; + if ('icon2' in handle) + img2 = handle.icon2; if (!img1 && isFunc(handle.icon_get)) img1 = handle.icon_get(hitem, this); if (canDrawHandle(handle) || ('execute' in handle) || ('aslink' in handle) || - (canExpandHandle(handle) && (hitem._more !== false))) can_click = true; + (canExpandHandle(handle) && (hitem._more !== false))) + can_click = true; } - if ('_icon' in hitem) img1 = hitem._icon; - if ('_icon2' in hitem) img2 = hitem._icon2; + if ('_icon' in hitem) + img1 = hitem._icon; + if ('_icon2' in hitem) + img2 = hitem._icon2; if (!img1 && ('_online' in hitem)) hitem._icon = img1 = 'img_globe'; if (!img1 && isroot) @@ -1183,55 +1309,58 @@ class HierarchyPainter extends BasePainter { can_click = true; let can_menu = can_click; - if (!can_menu && isStr(hitem._kind) && (hitem._kind.indexOf(prROOT) === 0)) + if (!can_menu && getTypeForKind(hitem._kind)) can_menu = can_click = true; - if (!img2) img2 = img1; - if (!img1) img1 = (has_childs || hitem._more) ? 'img_folder' : 'img_page'; - if (!img2) img2 = (has_childs || hitem._more) ? 'img_folderopen' : 'img_page'; + if (!img2) + img2 = img1; + if (!img1) + img1 = (has_childs || hitem._more) ? 'img_folder' : 'img_page'; + if (!img2) + img2 = (has_childs || hitem._more) ? 'img_folderopen' : 'img_page'; if (arg === 'update') { d3prnt.selectAll('*').remove(); d3cont = d3prnt; } else { d3cont = d3prnt.append('div'); - if (arg && (arg >= (hitem._parent._show_limit || settings.HierarchyLimit))) break_list = true; + if (arg && (arg >= (hitem._parent._show_limit || settings.HierarchyLimit))) + break_list = true; } hitem._d3cont = d3cont.node(); // set for direct referencing d3cont.attr('item', itemname); // line with all html elements for this item (excluding childs) - const d3line = d3cont.append('div').attr('class', 'h_line'); + const h = this, d3line = d3cont.append('div').attr('class', 'h_line'); // build indent - let prnt = isroot ? null : hitem._parent; + let prnt = isroot ? null : hitem._parent, upcnt = 1; while (prnt && (prnt !== this.h)) { - d3line.insert('div', ':first-child') - .attr('class', this.isLastSibling(prnt) ? 'img_empty' : 'img_line'); + const is_last = this.isLastSibling(prnt), + d3icon = d3line.insert('div', ':first-child').attr('class', is_last ? 'img_empty' : 'img_line'); + if (!is_last) + d3icon.style('cursor', 'pointer').property('upcnt', upcnt).on('click', function(evnt) { h.tree_click(evnt, this, 'parentminus'); }); prnt = prnt._parent; + upcnt++; } let icon_class = '', plusminus = false; if (isroot) { // for root node no extra code - } else if (has_childs && !break_list) { + } else if ((has_childs && !break_list) || handle?.pm) { icon_class = hitem._isopen ? 'img_minus' : 'img_plus'; plusminus = true; - } else /* if (hitem._more) { - icon_class = 'img_plus'; // should be special plus ??? - plusminus = true; - } else */ + } else icon_class = 'img_join'; - const h = this; - if (icon_class) { - if (break_list || this.isLastSibling(hitem)) icon_class += 'bottom'; + if (break_list || this.isLastSibling(hitem)) + icon_class += 'bottom'; const d3icon = d3line.append('div').attr('class', icon_class); if (plusminus) - d3icon.style('cursor', 'pointer').on('click', function(evnt) { h.tree_click(evnt, this, 'plusminus'); }); + d3icon.style('cursor', 'pointer').on('click', function(evnt) { h.tree_click(evnt, this, kPM); }); } // make node icons @@ -1250,24 +1379,24 @@ class HierarchyPainter extends BasePainter { .style('width', '18px') .style('height', '18px'); - if (('_icon_click' in hitem) || (handle && ('icon_click' in handle))) + if (hitem._icon_click || handle?.icon_click) d3img.on('click', function(evnt) { h.tree_click(evnt, this, 'icon'); }); } const d3a = d3line.append('a'); if (can_click || has_childs || break_list) - d3a.attr('class', 'h_item').on('click', function(evnt) { h.tree_click(evnt, this); }); + d3a.attr('class', cssItem).on('click', function(evnt) { h.tree_click(evnt, this); }); if (break_list) { hitem._break_point = true; // indicate that list was broken here - d3a.attr('title', 'there are ' + (hitem._parent._childs.length-arg) + ' more items') + d3a.attr('title', 'there are ' + (hitem._parent._childs.length - arg) + ' more items') .text('...more...'); return false; } if ('disp_kind' in h) { if (settings.DragAndDrop && can_click) - this.enableDrag(d3a, itemname); + this.enableDrag(d3a, itemname); if (settings.ContextMenu && can_menu) d3a.on('contextmenu', function(evnt) { h.tree_contextmenu(evnt, this); }); @@ -1291,23 +1420,32 @@ class HierarchyPainter extends BasePainter { if (!element_title) element_title = element_name; + if (hitem._filter) + element_name += ' *'; + d3a.attr('title', element_title) .text(element_name + ('_value' in hitem ? ':' : '')) .style('background', hitem._background ? hitem._background : null); if ('_value' in hitem) { const d3p = d3line.append('p'); - if ('_vclass' in hitem) d3p.attr('class', hitem._vclass); - if (!hitem._isopen) d3p.html(hitem._value); + if ('_vclass' in hitem) + d3p.attr('class', hitem._vclass); + if (!hitem._isopen) + d3p.text(hitem._value); } if (has_childs && (isroot || hitem._isopen)) { const d3chlds = d3cont.append('div').attr('class', 'h_childs'); - if (this.show_overflow) d3chlds.style('overflow', 'initial'); + if (this.show_overflow) + d3chlds.style('overflow', 'initial'); for (let i = 0; i < hitem._childs.length; ++i) { const chld = hitem._childs[i]; chld._parent = hitem; - if (!this.addItemHtml(chld, d3chlds, i)) break; // if too many items, skip rest + if (hitem._filter && chld._name && chld._name.indexOf(hitem._filter) < 0) + continue; + if (!this.addItemHtml(chld, d3chlds, i)) + break; // if too many items, skip rest } } @@ -1322,17 +1460,20 @@ class HierarchyPainter extends BasePainter { const hitem = h || this.h; if (hitem._childs === undefined) { - if (!isopen) return false; + if (!isopen) + return false; if (this.with_icons) { // in normal hierarchy check precisely if item can be expand - if (!hitem._more && !hitem._expand && !this.canExpandItem(hitem)) return false; + if (!hitem._more && !hitem._expand && !this.canExpandItem(hitem)) + return false; } const pr = this.expandItem(this.itemFullName(hitem)); if (isPromise(pr) && isObject(promises)) promises.push(pr); - if (hitem._childs !== undefined) hitem._isopen = true; + if (hitem._childs !== undefined) + hitem._isopen = true; return hitem._isopen; } @@ -1354,20 +1495,27 @@ class HierarchyPainter extends BasePainter { return true; } - if (!h) this.refreshHtml(); + if (!h) + this.refreshHtml(); return false; } - /** @summary Exapnd to specified level + /** @summary Expand to specified level * @protected */ - async exapndToLevel(level) { - if (!level || !Number.isFinite(level) || (level < 0)) return this; + async expandToLevel(level) { + if (!level || !Number.isFinite(level) || (level < 0)) + return this; const promises = []; this.toggleOpenState(true, this.h, promises); - return Promise.all(promises).then(() => this.exapndToLevel(level - 1)); + return Promise.all(promises).then(() => this.expandToLevel(level - 1)); } + /** @summary Expand to specified level + * @deprecated will be removed in version 8, kept only for backward compatibility + * @protected */ + async exapndToLevel(level) { return this.expandToLevel(level); } + /** @summary Refresh HTML code of hierarchy painter * @return {Promise} when done */ async refreshHtml() { @@ -1386,8 +1534,10 @@ class HierarchyPainter extends BasePainter { let status_item = null; this.forEachItem(item => { delete item._d3cont; // remove html container - if (('_fastcmd' in item) && (item._kind === 'Command')) factcmds.push(item); - if (('_status' in item) && !status_item) status_item = item; + if (('_fastcmd' in item) && (item._kind === 'Command')) + factcmds.push(item); + if (('_status' in item) && !status_item) + status_item = item; }); if (!this.h || d3elem.empty()) @@ -1409,27 +1559,33 @@ class HierarchyPainter extends BasePainter { } const d3btns = d3elem.append('p').attr('class', 'jsroot').style('margin-bottom', '3px').style('margin-top', 0); - d3btns.append('a').attr('class', 'h_button').text('expand all') + d3btns.append('a').attr('class', cssButton).text('expand all') .attr('title', 'expand all items in the browser').on('click', () => this.toggleOpenState(true)); d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('collapse all') + d3btns.append('a').attr('class', cssButton).text('collapse all') .attr('title', 'collapse all items in the browser').on('click', () => this.toggleOpenState(false)); + if (isFunc(this.storeAsJson)) { + d3btns.append('text').text(' | '); + d3btns.append('a').attr('class', cssButton).text('json') + .attr('title', 'dump to json file').on('click', () => this.storeAsJson()); + } + if (isFunc(this.removeInspector)) { d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('remove') + d3btns.append('a').attr('class', cssButton).text('remove') .attr('title', 'remove inspector').on('click', () => this.removeInspector()); } if ('_online' in this.h) { d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('reload') + d3btns.append('a').attr('class', cssButton).text('reload') .attr('title', 'reload object list from the server').on('click', () => this.reload()); } if ('disp_kind' in this) { d3btns.append('text').text(' | '); - d3btns.append('a').attr('class', 'h_button').text('clear') + d3btns.append('a').attr('class', cssButton).text('clear') .attr('title', 'clear all drawn objects').on('click', () => this.clearHierarchy(false)); } @@ -1443,22 +1599,23 @@ class HierarchyPainter extends BasePainter { maindiv.style('overflow', 'auto'); if (this.background) { - // case of object inspector and streamer infos display - maindiv.style('background-color', this.background) - .style('margin', '2px').style('padding', '2px'); + // case of object inspector and streamer infos display + maindiv.style('background-color', this.background) + .style('margin', '2px').style('padding', '2px'); } if (this.textcolor) maindiv.style('color', this.textcolor); - this.addItemHtml(this.h, maindiv.append('div').attr('class', 'h_tree')); + this.addItemHtml(this.h, maindiv.append('div').attr('class', cssTree)); - this.setTopPainter(); // assign hpainter as top painter + this.setTopPainter(); // assign this hierarchy painter as top painter if (status_item && !this.status_disabled && !decodeUrl().has('nostatus')) { const func = findFunction(status_item._status); if (isFunc(func)) { return this.createStatusLine().then(sdiv => { - if (sdiv) func(sdiv, this.itemFullName(status_item)); + if (sdiv) + func(sdiv, this.itemFullName(status_item)); }); } } @@ -1476,7 +1633,8 @@ class HierarchyPainter extends BasePainter { d3cont = this.selectDom().select(`[item='${name}']`); if (d3cont.empty() && ('_cycle' in hitem)) d3cont = this.selectDom().select(`[item='${name};${hitem._cycle}']`); - if (d3cont.empty()) return; + if (d3cont.empty()) + return; } this.addItemHtml(hitem, d3cont, 'update'); @@ -1487,14 +1645,14 @@ class HierarchyPainter extends BasePainter { /** @summary Update item background * @private */ updateBackground(hitem, scroll_into_view) { - if (!hitem || !hitem._d3cont) return; + if (!hitem || !hitem._d3cont) + return; const d3cont = d3_select(hitem._d3cont); + if (d3cont.empty()) + return; - if (d3cont.empty()) return; - - const d3a = d3cont.select('.h_item'); - + const d3a = d3cont.select(`.${cssItem}`); d3a.style('background', hitem._background ? hitem._background : null); if (scroll_into_view && hitem._background) @@ -1503,7 +1661,7 @@ class HierarchyPainter extends BasePainter { /** @summary Focus on hierarchy item * @param {Object|string} hitem - item to open or its name - * @desc all parents to the otem will be opened first + * @desc all parents to the item will be opened first * @return {Promise} when done * @private */ async focusOnItem(hitem) { @@ -1511,7 +1669,8 @@ class HierarchyPainter extends BasePainter { hitem = this.findItem(hitem); const name = hitem ? this.itemFullName(hitem) : ''; - if (!name) return false; + if (!name) + return false; let itm = hitem, need_refresh = false; @@ -1527,7 +1686,8 @@ class HierarchyPainter extends BasePainter { return promise.then(() => { const d3cont = this.selectDom().select(`[item='${name}']`); - if (d3cont.empty()) return false; + if (d3cont.empty()) + return false; d3cont.node().scrollIntoView(); return true; }); @@ -1536,13 +1696,25 @@ class HierarchyPainter extends BasePainter { /** @summary Handler for click event of item in the hierarchy * @private */ tree_click(evnt, node, place) { - if (!node) return; + if (!node) + return; - const d3cont = d3_select(node.parentNode.parentNode), + let d3cont = d3_select(node.parentNode.parentNode), itemname = d3cont.attr('item'), hitem = itemname ? this.findItem(itemname) : null; + if (!hitem) + return; - if (!hitem) return; + if (place === 'parentminus') { + let upcnt = d3_select(node).property('upcnt') || 1; + while (upcnt-- > 0) + hitem = hitem?._parent; + if (!hitem) + return; + itemname = this.itemFullName(hitem); + d3cont = d3_select(hitem?._d3cont || null); + place = kPM; + } if (hitem._break_point) { // special case of more item @@ -1553,16 +1725,18 @@ class HierarchyPainter extends BasePainter { this.addItemHtml(hitem, d3cont, 'update'); const prnt = hitem._parent, indx = prnt._childs.indexOf(hitem), - d3chlds = d3_select(d3cont.node().parentNode); + d3chlds = d3_select(d3cont.node().parentNode); - if (indx < 0) return console.error('internal error'); + if (indx < 0) + return console.error('internal error'); prnt._show_limit = (prnt._show_limit || settings.HierarchyLimit) * 2; - for (let n = indx+1; n < prnt._childs.length; ++n) { + for (let n = indx + 1; n < prnt._childs.length; ++n) { const chld = prnt._childs[n]; chld._parent = prnt; - if (!this.addItemHtml(chld, d3chlds, n)) break; // if too many items, skip rest + if (!this.addItemHtml(chld, d3chlds, n)) + break; // if too many items, skip rest } return; @@ -1570,12 +1744,14 @@ class HierarchyPainter extends BasePainter { let prnt = hitem, dflt; while (prnt) { - if ((dflt = prnt._click_action) !== undefined) break; + if ((dflt = prnt._click_action) !== undefined) + break; prnt = prnt._parent; } - if (!place) place = 'item'; - const selector = (hitem._kind === prROOT + clTKey && hitem._more) ? 'noinspect' : '', + if (!place) + place = 'item'; + const selector = (hitem._kind === getKindForType(clTKey) && hitem._more) ? 'noinspect' : '', sett = getDrawSettings(hitem._kind, selector), handle = sett.handle; if (place === 'icon') { @@ -1590,14 +1766,15 @@ class HierarchyPainter extends BasePainter { } // special feature - all items with '_expand' function are not drawn by click - if ((place === 'item') && ('_expand' in hitem) && !evnt.ctrlKey && !evnt.shiftKey) place = 'plusminus'; + if ((place === 'item') && ('_expand' in hitem) && !hitem._expand_miss && !evnt.ctrlKey && !evnt.shiftKey) + place = kPM; // special case - one should expand item - if (((place === 'plusminus') && !('_childs' in hitem) && hitem._more) || - ((place === 'item') && (dflt === 'expand'))) + if (((place === kPM) && !('_childs' in hitem) && hitem._more) || + ((place === kPM) && handle?.pm) || + ((place === 'item') && (dflt === kExpand))) return this.expandItem(itemname, d3cont); - if (place === 'item') { if (hitem._player) return this.player(itemname); @@ -1608,16 +1785,18 @@ class HierarchyPainter extends BasePainter { if (handle?.execute) return this.executeCommand(itemname, node.parentNode); - if (handle?.ignore_online && this.isOnlineItem(hitem)) return; + if (handle?.ignore_online && this.isOnlineItem(hitem)) + return; - const dflt_expand = (this.default_by_click === 'expand'); + const dflt_expand = (this.default_by_click === kExpand); let can_draw = hitem._can_draw, can_expand = hitem._more, drawopt = ''; if (evnt.shiftKey) { drawopt = handle?.shift || kInspect; - if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0) && handle?.noinspect) drawopt = ''; + if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0) && handle?.noinspect) + drawopt = ''; } if (evnt.ctrlKey && handle?.ctrl) drawopt = handle.ctrl; @@ -1633,31 +1812,36 @@ class HierarchyPainter extends BasePainter { } } - if (hitem._childs) can_expand = false; + if (hitem._childs) + can_expand = false; - if (can_draw === undefined) can_draw = sett.draw; - if (can_expand === undefined) can_expand = sett.expand || sett.get_expand; + if (can_draw === undefined) + can_draw = sett.draw; + if (can_expand === undefined) + can_expand = sett.expand || sett.get_expand; if (can_draw && can_expand && !drawopt) { // if default action specified as expand, disable drawing // if already displayed, try to expand - if (dflt_expand || (handle?.dflt === 'expand') || (handle?.exapnd_after_draw && this.isItemDisplayed(itemname))) can_draw = false; + if (dflt_expand || (handle?.dflt === kExpand) || (handle?.expand_after_draw && this.isItemDisplayed(itemname))) + can_draw = false; } if (can_draw && !drawopt) - drawopt = '__default_draw_option__'; + drawopt = kDfltDrawOpt; if (can_draw) - return this.display(itemname, drawopt, true); + return this.display(itemname, drawopt, null, true); if (can_expand || dflt_expand) return this.expandItem(itemname, d3cont); // cannot draw, but can inspect ROOT objects - if (isStr(hitem._kind) && (hitem._kind.indexOf(prROOT) === 0) && sett.inspect && (can_draw !== false)) - return this.display(itemname, kInspect, true); + if (getTypeForKind(hitem._kind) && sett.inspect && (can_draw !== false)) + return this.display(itemname, kInspect, null, true); - if (!hitem._childs || (hitem === this.h)) return; + if (!hitem._childs || (hitem === this.h)) + return; } if (hitem._isopen) @@ -1672,9 +1856,9 @@ class HierarchyPainter extends BasePainter { * @private */ tree_mouseover(on, elem) { const itemname = d3_select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); - - if (!hitem) return; + hitem = this.findItem(itemname); + if (!hitem) + return; let painter, prnt = hitem; while (prnt && !painter) { @@ -1691,8 +1875,9 @@ class HierarchyPainter extends BasePainter { direct_contextmenu(evnt, elem) { evnt.preventDefault(); const itemname = d3_select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); - if (!hitem) return; + hitem = this.findItem(itemname); + if (!hitem) + return; if (isFunc(this.fill_context)) { createMenu(evnt, this).then(menu => { @@ -1732,8 +1917,7 @@ class HierarchyPainter extends BasePainter { if (isFunc(this.disp?.changeDarkMode)) this.disp.changeDarkMode(); this.disp?.forEachFrame(frame => { - let p = getElementCanvPainter(frame); - if (!p) p = getElementMainPainter(frame); + const p = getElementCanvPainter(frame) || getElementMainPainter(frame); if (isFunc(p?.changeDarkMode) && (p !== this)) p.changeDarkMode(); }); @@ -1746,13 +1930,14 @@ class HierarchyPainter extends BasePainter { this.changeDarkMode(); } - /** @summary Handle context menu in the hieararchy + /** @summary Handle context menu in the hierarchy * @private */ tree_contextmenu(evnt, elem) { evnt.preventDefault(); const itemname = d3_select(elem.parentNode.parentNode).attr('item'), - hitem = this.findItem(itemname); - if (!hitem) return; + hitem = this.findItem(itemname); + if (!hitem) + return; const onlineprop = this.getOnlineProp(itemname), fileprop = this.getFileProp(itemname); @@ -1767,7 +1952,7 @@ class HierarchyPainter extends BasePainter { createMenu(evnt, this).then(menu => { if ((!itemname || !hitem._parent) && !('_jsonfile' in hitem)) { let addr = '', cnt = 0; - const files = [], separ = () => (cnt++ > 0) ? '&' : '?'; + const files = [], separ = () => { return (cnt++ > 0) ? '&' : '?'; }; this.forEachRootFile(item => files.push(item._file.fFullURL)); @@ -1794,11 +1979,12 @@ class HierarchyPainter extends BasePainter { if (item) opt = top.getDrawOpt() || top.getItemDrawOpt(); - else { + else { top = null; dummy.forEachPainter(p => { const _item = p.getItemName(); - if (!_item) return; + if (!_item) + return; let _opt = p.getDrawOpt() || p.getItemDrawOpt() || ''; if (!top) { top = p; @@ -1821,7 +2007,7 @@ class HierarchyPainter extends BasePainter { if (items.length === 1) addr += separ() + 'item=' + items[0] + separ() + 'opt=' + opts[0]; - else if (items.length > 1) + else if (items.length > 1) addr += separ() + 'items=' + JSON.stringify(items) + separ() + 'opts=' + JSON.stringify(opts); @@ -1830,12 +2016,13 @@ class HierarchyPainter extends BasePainter { this.fillSettingsMenu(menu); } else if (onlineprop) this.fillOnlineMenu(menu, onlineprop, itemname); - else { + else { const sett = getDrawSettings(hitem._kind, 'nosame'); // allow to draw item even if draw function is not defined if (hitem._can_draw) { - if (!sett.opts) sett.opts = ['']; + if (!sett.opts) + sett.opts = ['']; if (sett.opts.indexOf('') < 0) sett.opts.unshift(''); } @@ -1860,7 +2047,8 @@ class HierarchyPainter extends BasePainter { filepath = `${fileprop.kind}=${filepath}`; if (fileprop.itemname) { let name = fileprop.itemname; - if (name.search(/\+| |,/) >= 0) name = `'${name}'`; + if (name.search(/\+| |,/) >= 0) + name = `'${name}'`; filepath += `&item=${name}`; } @@ -1872,11 +2060,11 @@ class HierarchyPainter extends BasePainter { if (settings.NewTabUrlExportSettings) { if (gStyle.fOptStat !== 1111) arg0 += `&optstat=${gStyle.fOptStat}`; - if (gStyle.fOptFit !== 0) + if (gStyle.fOptFit) arg0 += `&optfit=${gStyle.fOptFit}`; - if (gStyle.fOptDate !== 0) + if (gStyle.fOptDate) arg0 += `&optdate=${gStyle.fOptDate}`; - if (gStyle.fOptFile !== 0) + if (gStyle.fOptFile) arg0 += `&optfile=${gStyle.fOptFile}`; if (gStyle.fOptTitle !== 1) arg0 += `&opttitle=${gStyle.fOptTitle}`; @@ -1917,8 +2105,17 @@ class HierarchyPainter extends BasePainter { if ((sett.expand || sett.get_expand) && (hitem._more || hitem._more === undefined)) { if (hitem._childs === undefined) - menu.add('Expand', () => this.expandItem(itemname), 'Exapnd content of object'); + menu.add('Expand', () => this.expandItem(itemname), 'Expand content of object'); else { + if (sett.handle?.pm || (hitem._childs.length > 25)) { + menu.add('Filter...', () => menu.input('Enter items to select', hitem._filter, f => { + const changed = hitem._filter !== f; + hitem._filter = f; + if (changed) + this.updateTreeNode(hitem); + }), 'Filter out items based on input pattern'); + } + menu.add('Unexpand', () => { hitem._more = true; delete hitem._childs; @@ -1930,7 +2127,7 @@ class HierarchyPainter extends BasePainter { } } - if (hitem._kind === prROOT + clTStyle) + if (hitem._kind === getKindForType(clTStyle)) menu.add('Apply', () => this.applyStyle(itemname)); } @@ -1939,7 +2136,8 @@ class HierarchyPainter extends BasePainter { if (menu.size() > 0) { menu.tree_node = elem.parentNode; - if (menu.separ) menu.add('separator'); // add separator at the end + if (menu.separ) + menu.separator(); // add separator at the end menu.add('Close'); menu.show(); } @@ -1956,10 +2154,10 @@ class HierarchyPainter extends BasePainter { async player(itemname, option) { const item = this.findItem(itemname); - if (!item || !item._player || !isStr(item._player)) + if (!isStr(item?._player)) return null; - let player_func = null; + let player_func; if (item._module) { const hh = await this.importModule(item._module); @@ -1980,10 +2178,14 @@ class HierarchyPainter extends BasePainter { /** @summary Checks if item can be displayed with given draw option * @private */ canDisplay(item, drawopt) { - if (!item) return false; - if (item._player) return true; - if (item._can_draw !== undefined) return item._can_draw; - if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0)) return true; + if (!item) + return false; + if (item._player) + return true; + if (item._can_draw !== undefined) + return item._can_draw; + if (isStr(drawopt) && (drawopt.indexOf(kInspect) === 0)) + return true; const handle = getDrawHandle(item._kind, drawopt); return canDrawHandle(handle); } @@ -1992,119 +2194,140 @@ class HierarchyPainter extends BasePainter { * @param {string} itemname - item name */ isItemDisplayed(itemname) { const mdi = this.getDisplay(); - return mdi ? mdi.findFrame(itemname) !== null : false; + return mdi?.findFrame(itemname) !== null; } /** @summary Display specified item * @param {string} itemname - item name * @param {string} [drawopt] - draw option for the item + * @param {string|Object} [dom] - place where to draw item, same as for @ref draw function * @param {boolean} [interactive] - if display was called in interactive mode, will activate selected drawing * @return {Promise} with created painter object */ - async display(itemname, drawopt, interactive) { - const display_itemname = itemname, - marker = '::_display_on_frame_::'; + async display(itemname, drawopt, dom = null, interactive = false) { + const display_itemname = itemname; let painter = null, updating = false, item = null, frame_name = itemname; - const p = drawopt?.indexOf(marker) ?? -1; - if (p >= 0) { - frame_name = drawopt.slice(p + marker.length); - drawopt = drawopt.slice(0, p); + // only to support old API where dom was not there + if ((dom === true) || (dom === false)) { + interactive = dom; + dom = null; + } + + if (isStr(dom) && (dom.indexOf('frame:') === 0)) { + frame_name = dom.slice(6); + dom = null; } const complete = (respainter, err) => { - if (err) console.log('When display ', itemname, 'got', err); + if (err) + console.log('When display ', itemname, 'got', err); - if (updating && item) delete item._doing_update; - if (!updating) showProgress(); + if (updating && item) + delete item._doing_update; + if (!updating) + showProgress(); if (isFunc(respainter?.setItemName)) { respainter.setItemName(display_itemname, updating ? null : drawopt, this); // mark painter as created from hierarchy - if (item && !item._painter) item._painter = respainter; + + if (item && !item._painter) + item._painter = respainter; } return respainter || painter; }; return this.createDisplay().then(mdi => { - if (!mdi) return complete(); + if (!mdi) + return complete(); item = this.findItem(display_itemname); - if (item && ('_player' in item)) + if (item?._player) return this.player(display_itemname, drawopt).then(res => complete(res)); updating = isStr(drawopt) && (drawopt.indexOf('update:') === 0); if (updating) { drawopt = drawopt.slice(7); - if (!item || item._doing_update) return complete(); + if (!item || item._doing_update) + return complete(); item._doing_update = true; } - if (item && !this.canDisplay(item, drawopt)) return complete(); - - let divid = '', use_dflt_opt = false; - if (isStr(drawopt) && (drawopt.indexOf('divid:') >= 0)) { - const pos = drawopt.indexOf('divid:'); - divid = drawopt.slice(pos+6); - drawopt = drawopt.slice(0, pos); - } + if (item && !this.canDisplay(item, drawopt)) + return complete(); - if (drawopt === '__default_draw_option__') { - use_dflt_opt = true; + const use_dflt_opt = drawopt === kDfltDrawOpt; + if (use_dflt_opt) drawopt = ''; - } - if (!updating) showProgress(`Loading ${display_itemname} ...`); + if (!updating) + showProgress(`Loading ${display_itemname} ...`); return this.getObject(display_itemname, drawopt).then(result => { - if (!updating) showProgress(); + if (!updating) + showProgress(); - if (!item) item = result.item; + if (!item) + item = result.item; let obj = result.obj; - if (!obj) return complete(); + if (!obj) + return complete(); - if (!updating) showProgress(`Drawing ${display_itemname} ...`); + if (!updating) + showProgress(`Drawing ${display_itemname} ...`); - let handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; + let handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; if (handle?.draw_field && obj[handle.draw_field]) { obj = obj[handle.draw_field]; - if (!drawopt) drawopt = handle.draw_field_opt || ''; - handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; + if (!drawopt) + drawopt = handle.draw_field_opt || ''; + handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; } - if (use_dflt_opt && !drawopt && handle?.dflt && (handle.dflt !== 'expand')) + if (use_dflt_opt && !drawopt && handle?.dflt && (handle.dflt !== kExpand)) drawopt = handle.dflt; - if (divid) { - const func = updating ? redraw : draw; - return func(divid, obj, drawopt).then(p => complete(p)).catch(err => complete(null, err)); - } + if (dom) + return (updating ? redraw : draw)(dom, obj, drawopt).then(p => complete(p)).catch(err => complete(null, err)); - let did_actiavte = false; + let did_activate = false; + const arr = []; mdi.forEachPainter((p, frame) => { - if (p.getItemName() !== display_itemname) return; + if (p.getItemName() !== display_itemname) + return; const itemopt = p.getItemDrawOpt(); - if (use_dflt_opt && interactive) drawopt = itemopt; + if (use_dflt_opt && interactive) + drawopt = itemopt; // verify that object was drawn with same option as specified now (if any) - if (!updating && drawopt && (itemopt !== drawopt)) return; + if (!updating && drawopt && (itemopt !== drawopt)) + return; - if (interactive && !did_actiavte) { - did_actiavte = true; + if (interactive && !did_activate) { + did_activate = true; mdi.activateFrame(frame); } - if (isFunc(p.redrawObject) && p.redrawObject(obj, drawopt)) painter = p; + if (isFunc(p.redrawObject)) { + const pr = p.redrawObject(obj, drawopt); + + if (pr) { + painter = p; + arr.push(pr); + } + } }); - if (painter) return complete(); + if (painter) + return Promise.all(arr).then(() => complete()); if (updating) { console.warn(`something went wrong - did not found painter when doing update of ${display_itemname}`); @@ -2116,15 +2339,15 @@ class HierarchyPainter extends BasePainter { mdi.activateFrame(frame); return draw(frame, obj, drawopt) - .then(p => complete(p)) - .catch(err => complete(null, err)); + .then(p => complete(p)) + .catch(err => complete(null, err)); }); }); } /** @summary Enable drag of the element * @private */ - enableDrag(d3elem /*, itemname */) { + enableDrag(d3elem /* , itemname */) { d3elem.attr('draggable', 'true').on('dragstart', function(ev) { const itemname = this.parentNode.parentNode.getAttribute('item'); ev.dataTransfer.setData('item', itemname); @@ -2135,11 +2358,11 @@ class HierarchyPainter extends BasePainter { * @private */ enableDrop(frame) { const h = this; - d3_select(frame).on('dragover', function(ev) { + d3_select(frame).on('dragover', ev => { const itemname = ev.dataTransfer.getData('item'), - ditem = h.findItem(itemname); - if (isStr(ditem?._kind) && (ditem._kind.indexOf(prROOT) === 0)) - ev.preventDefault(); // let accept drop, otherwise it will be refuced + ditem = h.findItem(itemname); + if (getTypeForKind(ditem?._kind)) + ev.preventDefault(); // let accept drop, otherwise it will be refused }).on('dragenter', function() { d3_select(this).classed('jsroot_drag_area', true); }).on('dragleave', function() { @@ -2147,7 +2370,27 @@ class HierarchyPainter extends BasePainter { }).on('drop', function(ev) { d3_select(this).classed('jsroot_drag_area', false); const itemname = ev.dataTransfer.getData('item'); - if (itemname) h.dropItem(itemname, this); + if (!itemname) + return; + const painters = [], elements = []; + let pad_painter = getElementCanvPainter(this), + target = ev.target; + pad_painter?.forEachPainter(pp => { + painters.push(pp); + elements.push(pp.getPadSvg().node()); + }, 'pads'); + // only if there are sub-pads - try to find them + if (painters.length > 1) { + while (target && (target !== this)) { + const p = elements.indexOf(target); + if (p > 0) { + pad_painter = painters[p]; + break; + } + target = target.parentNode; + } + } + h.dropItem(itemname, pad_painter || this); }); } @@ -2157,20 +2400,21 @@ class HierarchyPainter extends BasePainter { d3_select(frame).on('dragover', null).on('dragenter', null).on('dragleave', null).on('drop', null); } - /** @summary Drop item on specified element for drawing + /** @summary Drop item on specified element for drawing * @return {Promise} when completed * @private */ - async dropItem(itemname, divid, opt) { - if (!opt || !isStr(opt)) opt = ''; + async dropItem(itemname, dom, opt) { + if (!opt || !isStr(opt)) + opt = ''; - const drop_complete = (drop_painter, is_main_painter) => { - if (!is_main_painter && isFunc(drop_painter?.setItemName)) + const drop_complete = (drop_painter, is_main) => { + if (!is_main && isFunc(drop_painter?.setItemName)) drop_painter.setItemName(itemname, null, this); return drop_painter; }; if (itemname === '$legend') { - const cp = getElementCanvPainter(divid); + const cp = getElementCanvPainter(dom); if (isFunc(cp?.buildLegend)) return cp.buildLegend(0, 0, 0, 0, '', opt).then(lp => drop_complete(lp)); console.error('Not possible to build legend'); @@ -2178,18 +2422,31 @@ class HierarchyPainter extends BasePainter { } return this.getObject(itemname).then(res => { - if (!res.obj) return null; + if (!res.obj) + return null; + + const mp = getElementMainPainter(dom); + + if (isFunc(mp?.performDrop)) + return mp.performDrop(res.obj, itemname, res.item, opt).then(p => drop_complete(p, mp === p)); - const main_painter = getElementMainPainter(divid); + const sett = res.obj._typename ? getDrawSettings(getKindForType(res.obj._typename)) : null; + if (!sett?.draw) + return null; - if (isFunc(main_painter?.performDrop)) - return main_painter.performDrop(res.obj, itemname, res.item, opt).then(p => drop_complete(p, main_painter === p)); + const cp = getElementCanvPainter(dom); - if (main_painter?.accept_drops) - return draw(divid, res.obj, 'same ' + opt).then(p => drop_complete(p, main_painter === p)); + if (cp) { + if (sett?.has_same && mp) + opt = 'same ' + opt; + } else + this.cleanupFrame(dom); + + // if drop on sub-pad painter - call add pad buttons + if (isFunc(dom?.addPadButtons)) + dom.addPadButtons(); - this.cleanupFrame(divid); - return draw(divid, res.obj, opt).then(p => drop_complete(p)); + return draw(dom, res.obj, opt).then(p => drop_complete(p, mp === p)); }); } @@ -2208,32 +2465,36 @@ class HierarchyPainter extends BasePainter { arg = [arg]; else if (!isObject(arg)) { if (arg === undefined) - arg = !this.isMonitoring(); + arg = !this.isMonitoring(); want_update_all = true; - only_auto_items = !!arg; + only_auto_items = Boolean(arg); } // first collect items this.disp.forEachPainter(p => { const itemname = p.getItemName(); - if (!isStr(itemname) || (allitems.indexOf(itemname) >= 0)) return; + if (!isStr(itemname) || (allitems.indexOf(itemname) >= 0)) + return; if (want_update_all) { const item = this.findItem(itemname); - if (!item || ('_not_monitor' in item) || ('_player' in item)) return; + if (!item || item._not_monitor || item._player) + return; if (!('_always_monitor' in item)) { const handle = getDrawHandle(item._kind); let forced = false; if (handle?.monitor !== undefined) { - if ((handle.monitor === false) || (handle.monitor === 'never')) return; - if (handle.monitor === 'always') forced = true; + if ((handle.monitor === false) || (handle.monitor === 'never')) + return; + if (handle.monitor === 'always') + forced = true; } - if (!forced && only_auto_items) return; + if (!forced && only_auto_items) + return; } - } else - if (arg.indexOf(itemname) < 0) return; - + } else if (arg.indexOf(itemname) < 0) + return; allitems.push(itemname); options.push('update:' + p.getItemDrawOpt()); @@ -2254,23 +2515,24 @@ class HierarchyPainter extends BasePainter { * @return {Promise} when drawing finished * @private */ async displayItems(items, options) { - if (!items || (items.length === 0)) + if (!items?.length) return true; const h = this; - if (!options) options = []; + if (!options) + options = []; while (options.length < items.length) - options.push('__default_draw_option__'); + options.push(kDfltDrawOpt); if ((options.length === 1) && (options[0] === 'iotest')) { this.clearHierarchy(); - d3_select('#' + this.disp_frameid).html('<h2>Start I/O test</h2>'); + d3_select('#' + this.disp_frameid).html('').append('h2').text('Start I/O test'); const tm0 = new Date(); - return this.getObject(items[0]).then(() => { + return this.getObject(items[0]).then(res => { const tm1 = new Date(); - d3_select('#' + this.disp_frameid).append('h2').html('Item ' + items[0] + ' reading time = ' + (tm1.getTime() - tm0.getTime()) + 'ms'); + d3_select('#' + this.disp_frameid).append('h2').text(`Item ${items[0]} reading ` + (res?.obj ? `type ${res?.obj._typename} time = ${tm1.getTime() - tm0.getTime()}ms` : 'fail')); return true; }); } @@ -2286,17 +2548,23 @@ class HierarchyPainter extends BasePainter { const item = items[i]; let can_split = true; - if (item?.indexOf('img:') === 0) { images[i] = true; continue; } + if (item?.indexOf('img:') === 0) { + images[i] = true; + continue; + } - if ((item?.length > 1) && (item[0] === '\'') && (item[item.length - 1] === '\'')) { - items[i] = item.slice(1, item.length-1); + if ((item?.length > 1) && (item.at(0) === '\'') && (item.at(-1) === '\'')) { + items[i] = item.slice(1, item.length - 1); can_split = false; } let elem = h.findItem({ name: items[i], check_keys: true }); - if (elem) { items[i] = h.itemFullName(elem); continue; } + if (elem) { + items[i] = h.itemFullName(elem); + continue; + } - if (can_split && (items[i][0] === '[') && (items[i][items[i].length - 1] === ']')) { + if (can_split && (items[i].at(0) === '[') && (items[i].at(-1) === ']')) { dropitems[i] = parseAsArray(items[i]); items[i] = dropitems[i].shift(); } else if (can_split && (items[i].indexOf('+') > 0)) { @@ -2304,7 +2572,7 @@ class HierarchyPainter extends BasePainter { items[i] = dropitems[i].shift(); } - if (dropitems[i] && dropitems[i].length > 0) { + if (dropitems[i]?.length) { // allow to specify _same_ item in different file for (let j = 0; j < dropitems[i].length; ++j) { const pos = dropitems[i][j].indexOf('_same_'); @@ -2312,10 +2580,11 @@ class HierarchyPainter extends BasePainter { dropitems[i][j] = dropitems[i][j].slice(0, pos) + items[i].slice(pos); elem = h.findItem({ name: dropitems[i][j], check_keys: true }); - if (elem) dropitems[i][j] = h.itemFullName(elem); + if (elem) + dropitems[i][j] = h.itemFullName(elem); } - if ((options[i][0] === '[') && (options[i][options[i].length-1] === ']')) { + if ((options[i].at(0) === '[') && (options[i].at(-1) === ']')) { dropopts[i] = parseAsArray(options[i]); options[i] = dropopts[i].shift(); } else if (options[i].indexOf('+') > 0) { @@ -2335,14 +2604,17 @@ class HierarchyPainter extends BasePainter { items[i] = items[i].slice(0, pos) + items[0].slice(pos); elem = h.findItem({ name: items[i], check_keys: true }); - if (elem) items[i] = h.itemFullName(elem); + if (elem) + items[i] = h.itemFullName(elem); } // now check that items can be displayed for (let n = items.length - 1; n >= 0; --n) { - if (images[n]) continue; + if (images[n]) + continue; const hitem = h.findItem(items[n]); - if (!hitem || h.canDisplay(hitem, options[n])) continue; + if (!hitem || h.canDisplay(hitem, options[n])) + continue; // try to expand specified item h.expandItem(items[n], null, true); items.splice(n, 1); @@ -2350,17 +2622,18 @@ class HierarchyPainter extends BasePainter { dropitems.splice(n, 1); } - if (items.length === 0) + if (!items.length) return true; const frame_names = new Array(items.length), items_wait = new Array(items.length); for (let n = 0; n < items.length; ++n) { items_wait[n] = 0; let fname = items[n], k = 0; - if (items.indexOf(fname) < n) items_wait[n] = true; // if same item specified, one should wait first drawing before start next + if (items.indexOf(fname) < n) + items_wait[n] = true; // if same item specified, one should wait first drawing before start next const p = options[n].indexOf('frameid:'); if (p >= 0) { - fname = options[n].slice(p+8); + fname = options[n].slice(p + 8); options[n] = options[n].slice(0, p); } else { while (frame_names.indexOf(fname) >= 0) @@ -2372,10 +2645,11 @@ class HierarchyPainter extends BasePainter { // now check if several same items present - select only one for the drawing // if draw option includes 'main', such item will be drawn first for (let n = 0; n < items.length; ++n) { - if (items_wait[n] !== 0) continue; + if (items_wait[n] !== 0) + continue; let found_main = n; for (let k = 0; k < items.length; ++k) { - if ((items[n]===items[k]) && (options[k].indexOf('main') >= 0)) + if ((items[n] === items[k]) && (options[k].indexOf('main') >= 0)) found_main = k; } for (let k = 0; k < items.length; ++k) { @@ -2385,54 +2659,61 @@ class HierarchyPainter extends BasePainter { } return this.createDisplay().then(mdi => { - if (!mdi) return false; + if (!mdi) + return false; + + const doms = new Array(items.length); // Than create empty frames for each item for (let i = 0; i < items.length; ++i) { - if (options[i].indexOf('update:') !== 0) { + if (options[i].indexOf('update:')) { mdi.createFrame(frame_names[i]); - options[i] += '::_display_on_frame_::'+frame_names[i]; + doms[i] = 'frame:' + frame_names[i]; } } function dropNextItem(indx, painter) { - if (painter && dropitems[indx] && (dropitems[indx].length > 0)) - return h.dropItem(dropitems[indx].shift(), painter.getDom(), dropopts[indx].shift()).then(() => dropNextItem(indx, painter)); + if (painter && dropitems[indx]?.length) + return h.dropItem(dropitems[indx].shift(), painter.getDrawDom(), dropopts[indx].shift()).then(() => dropNextItem(indx, painter)); dropitems[indx] = null; // mark that all drop items are processed items[indx] = null; // mark item as ready for (let cnt = 0; cnt < items.length; ++cnt) { - if (items[cnt] === null) continue; // ignore completed item + if (items[cnt] === null) + continue; // ignore completed item if (items_wait[cnt] && items.indexOf(items[cnt]) === cnt) { items_wait[cnt] = false; - return h.display(items[cnt], options[cnt]).then(painter => dropNextItem(cnt, painter)); + return h.display(items[cnt], options[cnt], doms[cnt]).then(drop_painter => dropNextItem(cnt, drop_painter)); } } } const promises = []; - if (this._one_by_one) { + if (this.#one_by_one) { function processNext(indx) { if (indx >= items.length) return true; if (items_wait[indx]) return processNext(indx + 1); - return h.display(items[indx], options[indx]) - .then(painter => dropNextItem(indx, painter)) - .then(() => processNext(indx + 1)); + return h.display(items[indx], options[indx], doms[indx]) + .then(painter => dropNextItem(indx, painter)) + .then(() => processNext(indx + 1)); } promises.push(processNext(0)); } else { // We start display of all items parallel, but only if they are not the same for (let i = 0; i < items.length; ++i) { if (!items_wait[i]) - promises.push(h.display(items[i], options[i]).then(painter => dropNextItem(i, painter))); + promises.push(h.display(items[i], options[i], doms[i]).then(painter => dropNextItem(i, painter))); } } - return Promise.all(promises); + return Promise.all(promises).then(() => { + if (mdi?.createFinalBatchFrame && isBatchMode() && !isNodeJs()) + mdi.createFinalBatchFrame(); + }); }); } @@ -2449,24 +2730,26 @@ class HierarchyPainter extends BasePainter { * @param {boolean} [force] - if specified, all required sub-levels will be opened * @private */ activateItems(items, force) { - if (isStr(items)) items = [items]; + if (isStr(items)) + items = [items]; const active = [], // array of elements to activate - update = []; // array of elements to update - this.forEachItem(item => { if (item._background) { active.push(item); delete item._background; } }); - - const mark_active = () => { - for (let n = update.length-1; n >= 0; --n) - this.updateTreeNode(update[n]); - - for (let n = 0; n < active.length; ++n) - this.updateBackground(active[n], force); - }, + update = []; // array of elements to update + this.forEachItem(item => { + if (item._background) { + active.push(item); + delete item._background; + } + }); - find_next = (itemname, prev_found) => { + const find_next = (itemname, prev_found) => { if (itemname === undefined) { // extract next element - if (items.length === 0) return mark_active(); + if (!items.length) { + update.reverse().forEach(node => this.updateTreeNode(node)); + active.forEach(item => this.updateBackground(item, force)); + return; + } itemname = items.shift(); } @@ -2474,24 +2757,28 @@ class HierarchyPainter extends BasePainter { if (!hitem) { const d = this.findItem({ name: itemname, last_exists: true, check_keys: true, allow_index: true }); - if (!d || !d.last) return find_next(); + if (!d || !d.last) + return find_next(); d.now_found = this.itemFullName(d.last); if (force) { // if after last expand no better solution found - skip it - if ((prev_found !== undefined) && (d.now_found === prev_found)) return find_next(); + if ((prev_found !== undefined) && (d.now_found === prev_found)) + return find_next(); return this.expandItem(d.now_found).then(res => { - if (!res) return find_next(); + if (!res) + return find_next(); let newname = this.itemFullName(d.last); - if (newname) newname += '/'; + if (newname) + newname += '/'; find_next(newname + d.rest, d.now_found); }); } hitem = d.last; } - if (hitem) { // deepscan-disable-line + if (hitem) { // check that item is visible (opened), otherwise should enable parent let prnt = hitem._parent; @@ -2499,7 +2786,8 @@ class HierarchyPainter extends BasePainter { if (!prnt._isopen) { if (force) { prnt._isopen = true; - if (update.indexOf(prnt) < 0) update.push(prnt); + if (update.indexOf(prnt) < 0) + update.push(prnt); } else { hitem = prnt; break; } @@ -2508,7 +2796,8 @@ class HierarchyPainter extends BasePainter { } hitem._background = 'LightSteelBlue'; - if (active.indexOf(hitem) < 0) active.push(hitem); + if (active.indexOf(hitem) < 0) + active.push(hitem); } find_next(); @@ -2516,7 +2805,7 @@ class HierarchyPainter extends BasePainter { if (force && this.brlayout) { if (!this.brlayout.browser_kind) - return this.createBrowser('float', true).then(() => find_next()); + return this.createBrowser('float', true).then(() => find_next()); if (!this.brlayout.browser_visible) this.brlayout.toggleBrowserVisisbility(); } @@ -2528,8 +2817,10 @@ class HierarchyPainter extends BasePainter { /** @summary Check if item can be (potentially) expand * @private */ canExpandItem(item) { - if (!item) return false; - if (item._expand) return true; + if (!item) + return false; + if (item._expand) + return true; const handle = getDrawHandle(item._kind, '::expand'); return handle && canExpandHandle(handle); } @@ -2543,18 +2834,35 @@ class HierarchyPainter extends BasePainter { if (!hitem && d3cont) return; + function doneExpandItem(_item) { + if (_item._childs === undefined) + _item._expand_miss = true; + else { + _item._isopen = true; + if (_item._parent && !_item._parent._isopen) { + _item._parent._isopen = true; // also show parent + if (!silent) + hpainter.updateTreeNode(_item._parent); + } else if (!silent) + hpainter.updateTreeNode(_item, d3cont); + } + return _item; + } + async function doExpandItem(_item, _obj) { + delete _item._expand_miss; + if (isStr(_item._expand)) _item._expand = findFunction(_item._expand); if (!isFunc(_item._expand)) { let handle = getDrawHandle(_item._kind, '::expand'); - // in inspector show all memebers + // in inspector show all members if (handle?.expand_item && !hpainter._inspector) { _obj = _obj[handle.expand_item]; - _item.expand_item = handle.expand_item; // remember that was exapnd item - handle = _obj?._typename ? getDrawHandle(prROOT + _obj._typename, '::expand') : null; + _item.expand_item = handle.expand_item; // remember that was expand item + handle = _obj?._typename ? getDrawHandle(getKindForType(_obj._typename), '::expand') : null; } if (handle?.expand || handle?.get_expand) { @@ -2574,32 +2882,16 @@ class HierarchyPainter extends BasePainter { // try to use expand function if (_obj && isFunc(_item._expand)) { - if (_item._expand(_item, _obj)) { - _item._isopen = true; - if (_item._parent && !_item._parent._isopen) { - _item._parent._isopen = true; // also show parent - if (!silent) - hpainter.updateTreeNode(_item._parent); - } else { - if (!silent) - hpainter.updateTreeNode(_item, d3cont); - } - return _item; - } + const res = _item._expand(_item, _obj); + if (res) + return getPromise(res).then(() => doneExpandItem(_item)); } - if (_obj && objectHierarchy(_item, _obj)) { - _item._isopen = true; - if (_item._parent && !_item._parent._isopen) { - _item._parent._isopen = true; // also show parent - if (!silent) hpainter.updateTreeNode(_item._parent); - } else { - if (!silent) - hpainter.updateTreeNode(_item, d3cont); - } - return _item; - } + if (_obj && objectHierarchy(_item, _obj)) + return doneExpandItem(_item); + // mark as expand miss - behaves as normal object + _item._expand_miss = true; return -1; } @@ -2612,21 +2904,25 @@ class HierarchyPainter extends BasePainter { if (hitem._childs && hitem._isopen) { hitem._isopen = false; - if (!silent) this.updateTreeNode(hitem, d3cont); + if (!silent) + this.updateTreeNode(hitem, d3cont); return; } - if (hitem._obj) promise = doExpandItem(hitem, hitem._obj); + if (hitem._obj) + promise = doExpandItem(hitem, hitem._obj); } return promise.then(res => { - if (res !== -1) return res; // done + if (res !== -1) + return res; // done showProgress('Loading ' + itemname); - return this.getObject(itemname, silent ? 'hierarchy_expand' : 'hierarchy_expand_verbose').then(res => { + return this.getObject(itemname, silent ? 'hierarchy_expand' : 'hierarchy_expand_verbose').then(res2 => { showProgress(); - if (res.obj) return doExpandItem(res.item, res.obj).then(res => { return (res !== -1) ? res : undefined; }); + if (res2.obj) + return doExpandItem(res2.item, res2.obj).then(res3 => { return res3 !== -1 ? res3 : undefined; }); }); }); } @@ -2635,29 +2931,33 @@ class HierarchyPainter extends BasePainter { * @private */ getTopOnlineItem(item) { if (item) { - while (item && (!('_online' in item))) item = item._parent; + while (item && (!('_online' in item))) + item = item._parent; return item; } - if (!this.h) return null; - if ('_online' in this.h) return this.h; - if (this.h._childs && ('_online' in this.h._childs[0])) return this.h._childs[0]; + if (!this.h) + return null; + if (this.h._online) + return this.h; + if (this.h._childs && this.h._childs[0]?._online) + return this.h._childs[0]; return null; } /** @summary Call function for each item which corresponds to JSON file * @private */ forEachJsonFile(func) { - if (!this.h) return; - if ('_jsonfile' in this.h) + if (!this.h) + return; + + if (this.h._jsonfile) return func(this.h); - if (this.h._childs) { - for (let n = 0; n < this.h._childs.length; ++n) { - const item = this.h._childs[n]; - if ('_jsonfile' in item) func(item); - } - } + this.h._childs?.forEach(item => { + if (item._jsonfile) + func(item); + }); } /** @summary Open JSON file @@ -2665,28 +2965,34 @@ class HierarchyPainter extends BasePainter { * @return {Promise} when object ready */ async openJsonFile(filepath) { let isfileopened = false; - this.forEachJsonFile(item => { if (item._jsonfile === filepath) isfileopened = true; }); - if (isfileopened) return; + this.forEachJsonFile(item => { + if (item._jsonfile === filepath) + isfileopened = true; + }); + if (isfileopened) + return; - return httpRequest(filepath, 'object').then(res => { - if (!res) return; - const h1 = { _jsonfile: filepath, _kind: prROOT + res._typename, _jsontmp: res, _name: filepath.split('/').pop() }; - if (res.fTitle) h1._title = res.fTitle; + return httpRequest(filepath, 'object').then(res2 => { + if (!res2) + return; + const h1 = { _jsonfile: filepath, _kind: getKindForType(res2._typename), _jsontmp: res2, _name: filepath.split('/').pop() }; + if (res2.fTitle) + h1._title = res2.fTitle; h1._get = function(item /* ,itemname */) { if (item._jsontmp) return Promise.resolve(item._jsontmp); return httpRequest(item._jsonfile, 'object') - .then(res => { - item._jsontmp = res; - return res; - }); + .then(res3 => { + item._jsontmp = res3; + return res3; + }); }; if (!this.h) this.h = h1; else if (this.h._kind === kTopFolder) this.h._childs.push(h1); else { - const h0 = this.h, topname = ('_jsonfile' in h0) ? 'Files' : 'Items'; + const h0 = this.h, topname = h0?._jsonfile ? 'Files' : 'Items'; this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1] }; } @@ -2697,17 +3003,15 @@ class HierarchyPainter extends BasePainter { /** @summary Call function for each item which corresponds to ROOT file * @private */ forEachRootFile(func) { - if (!this.h) return; + if (!this.h) + return; if ((this.h._kind === kindTFile) && this.h._file) return func(this.h); - if (this.h._childs) { - for (let n = 0; n < this.h._childs.length; ++n) { - const item = this.h._childs[n]; - if ((item._kind === kindTFile) && ('_fullurl' in item)) - func(item); - } - } + this.h._childs?.forEach(item => { + if ((item._kind === kindTFile) && item._fullurl) + func(item); + }); } /** @summary Find ROOT file which corresponds to provided item name @@ -2727,8 +3031,12 @@ class HierarchyPainter extends BasePainter { * @return {Promise} when file is opened */ async openRootFile(filepath) { let isfileopened = false; - this.forEachRootFile(item => { if (item._fullurl === filepath) isfileopened = true; }); - if (isfileopened) return; + this.forEachRootFile(item => { + if (item._fullurl === filepath) + isfileopened = true; + }); + if (isfileopened) + return; const msg = isStr(filepath) ? filepath : 'file'; @@ -2739,10 +3047,11 @@ class HierarchyPainter extends BasePainter { h1._isopen = true; if (!this.h) { this.h = h1; - if (this._topname) h1._name = this._topname; + if (this.#topname) + h1._name = this.#topname; } else if (this.h._kind === kTopFolder) this.h._childs.push(h1); - else { + else { const h0 = this.h, topname = (h0._kind === kindTFile) ? 'Files' : 'Items'; this.h = { _name: topname, _kind: kTopFolder, _childs: [h0, h1], _isopen: true }; } @@ -2761,26 +3070,35 @@ class HierarchyPainter extends BasePainter { /** @summary Create list of files for specified directory */ async listServerDir(dirname) { return httpRequest(dirname, 'text').then(res => { - if (!res) return false; - const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }; + if (!res) + return false; + const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }, fmap = {}; let p = 0; while (p < res.length) { - p = res.indexOf('a href="', p+1); - if (p < 0) break; + p = res.indexOf('a href="', p + 1); + if (p < 0) + break; p += 8; - const p2 = res.indexOf('"', p+1); - if (p2 < 0) break; + const p2 = res.indexOf('"', p + 1); + if (p2 < 0) + break; const fname = res.slice(p, p2); p = p2 + 1; + + if (fmap[fname]) + continue; + fmap[fname] = true; + if ((fname.lastIndexOf('.root') === fname.length - 5) && (fname.length > 5)) { h._childs.push({ _name: fname, _title: dirname + fname, _url: dirname + fname, _kind: kindTFile, - _click_action: 'expand', _more: true, _obj: {}, + _click_action: kExpand, _more: true, _obj: {}, _expand: item => { return openFile(item._url).then(file => { - if (!file) return false; - delete item._exapnd; + if (!file) + return false; + delete item._expand; delete item._more; delete item._click_action; delete item._obj; @@ -2795,19 +3113,19 @@ class HierarchyPainter extends BasePainter { h._childs.push({ _name: fname, _title: dirname + fname, _jsonfile: dirname + fname, _can_draw: true, _get: item => { - return httpRequest(item._jsonfile, 'object').then(res => { - if (res) { - item._kind = prROOT + res._typename; - item._jsontmp = res; - this.updateTreeNode(item); + return httpRequest(item._jsonfile, 'object').then(res2 => { + if (res2) { + item._kind = getKindForType(res2._typename); + item._jsontmp = res2; + this.updateTreeNode(item); } - return res; + return res2; }); } }); } } - if (h._childs.length > 0) + if (h._childs.length) this.h = h; return true; }); @@ -2836,22 +3154,23 @@ class HierarchyPainter extends BasePainter { }); } - /** @summary Provides information abouf file item + /** @summary Provides information about file item * @private */ getFileProp(itemname) { let item = this.findItem(itemname); - if (!item) return null; + if (!item) + return null; - let subname = item._name; + itemname = item._name; while (item._parent) { item = item._parent; - if ('_file' in item) - return { kind: 'file', fileurl: item._file.fURL, itemname: subname, localfile: !!item._file.fLocalFile }; + if (item._file) + return { kind: 'file', fileurl: item._file.fURL, itemname, localfile: Boolean(item._file.fLocalFile) }; - if ('_jsonfile' in item) - return { kind: 'json', fileurl: item._jsonfile, itemname: subname }; + if (item._jsonfile) + return { kind: 'json', fileurl: item._jsonfile, itemname }; - subname = item._name + '/' + subname; + itemname = item._name + '/' + itemname; } return null; @@ -2862,9 +3181,11 @@ class HierarchyPainter extends BasePainter { * @return string or null if item is not online * @private */ getOnlineItemUrl(item) { - if (isStr(item)) item = this.findItem(item); + if (isStr(item)) + item = this.findItem(item); let prnt = item; - while (prnt && (prnt._online === undefined)) prnt = prnt._parent; + while (prnt && (prnt._online === undefined)) + prnt = prnt._parent; return prnt ? (prnt._online + this.itemFullName(item, prnt)) : null; } @@ -2874,7 +3195,7 @@ class HierarchyPainter extends BasePainter { return this.getOnlineItemUrl(item) !== null; } - /** @summary Dynamic module import, supports special shorcuts from core or draw_tree + /** @summary Dynamic module import, supports special shortcuts from core or draw_tree * @return {Promise} with module * @private */ async importModule(module) { @@ -2886,6 +3207,12 @@ class HierarchyPainter extends BasePainter { return import(/* webpackIgnore: true */ module); } + /** @summary set cached object for gui drawing + * @private */ + setCachedObject(obj) { + this.#cached_draw_object = obj; + } + /** @summary method used to request object from the http server * @return {Promise} with requested object * @private */ @@ -2900,7 +3227,8 @@ class HierarchyPainter extends BasePainter { if (item) { url = this.getOnlineItemUrl(item); let func = null; - if ('_kind' in item) draw_handle = getDrawHandle(item._kind); + if (item._kind) + draw_handle = getDrawHandle(item._kind); if (h_get) { req = 'h.json?compact=3'; @@ -2922,28 +3250,31 @@ class HierarchyPainter extends BasePainter { if (dreq) { if (isStr(dreq)) req = dreq; - else { - if ('req' in dreq) req = dreq.req; - if ('kind' in dreq) req_kind = dreq.kind; + else { + if (dreq.req) + req = dreq.req; + if (dreq.kind) + req_kind = dreq.kind; } } } - if (!req && (item._kind.indexOf(prROOT) !== 0)) - req = 'item.json.gz?compact=3'; + if (!req && !getTypeForKind(item._kind)) + req = 'item.json.gz?compact=3'; } - if (!itemname && item && ('_cached_draw_object' in this) && !req) { + if (!itemname && item && this.#cached_draw_object && !req) { // special handling for online draw when cashed - const obj = this._cached_draw_object; - delete this._cached_draw_object; + const obj = this.#cached_draw_object; + this.#cached_draw_object = undefined; return obj; } if (!req) req = 'root.json.gz?compact=23'; - if (url) url += '/'; + if (url) + url += '/'; url += req; return new Promise(resolveFunc => { @@ -2953,7 +3284,8 @@ class HierarchyPainter extends BasePainter { const handleAfterRequest = func => { if (isFunc(func)) { const res = func(this, item, obj, option, itemreq); - if (isObject(res)) obj = res; + if (isObject(res)) + obj = res; } resolveFunc(obj); }; @@ -2965,7 +3297,10 @@ class HierarchyPainter extends BasePainter { handleAfterRequest(findFunction(item._after_request)); // v6 support } else handleAfterRequest(draw_handle?.after_request); - }, undefined, true).then(xhr => { itemreq = xhr; xhr.send(null); }); + }, undefined, true).then(xhr => { + itemreq = xhr; + xhr.send(null); + }); }); } @@ -2998,12 +3333,14 @@ class HierarchyPainter extends BasePainter { if (item._autoload) { const arr = item._autoload.split(';'); arr.forEach(name => { - if ((name.length > 4) && (name.lastIndexOf('.mjs') === name.length-4)) + if ((name.length > 4) && (name.lastIndexOf('.mjs') === name.length - 4)) v7_imports.push(this.importModule(name)); - else if ((name.length > 3) && (name.lastIndexOf('.js') === name.length-3)) { - if (!scripts.find(elem => elem === name)) scripts.push(name); - } else if ((name.length > 4) && (name.lastIndexOf('.css') === name.length-4)) { - if (!styles.find(elem => elem === name)) styles.push(name); + else if ((name.length > 3) && (name.lastIndexOf('.js') === name.length - 3)) { + if (!scripts.find(elem => elem === name)) + scripts.push(name); + } else if ((name.length > 4) && (name.lastIndexOf('.css') === name.length - 4)) { + if (!styles.find(elem => elem === name)) + styles.push(name); } else if (name && !v6_modules.find(elem => elem === name)) v6_modules.push(name); }); @@ -3015,11 +3352,10 @@ class HierarchyPainter extends BasePainter { .then(() => Promise.all(v7_imports)) .then(() => { this.forEachItem(item => { - if (!('_drawfunc' in item) || !('_kind' in item)) return; - let typename = 'kind:' + item._kind; - if (item._kind.indexOf(prROOT) === 0) - typename = item._kind.slice(5); - const drawopt = item._drawopt; + if (!('_drawfunc' in item) || !('_kind' in item)) + return; + const typename = getTypeForKind(item._kind) || `kind:${item._kind}`, + drawopt = item._drawopt; if (!canDrawHandle(typename) || drawopt) addDrawFunc({ name: typename, func: item._drawfunc, script: item._drawscript, opt: drawopt }); }); @@ -3028,7 +3364,8 @@ class HierarchyPainter extends BasePainter { }); }; - if (!server_address) server_address = ''; + if (!server_address) + server_address = ''; if (isObject(server_address)) { const h = server_address; @@ -3043,19 +3380,16 @@ class HierarchyPainter extends BasePainter { * @private */ getOnlineProp(itemname) { let item = this.findItem(itemname); - if (!item) return null; + if (!item) + return null; - let subname = item._name; + itemname = item._name; while (item._parent) { item = item._parent; - if ('_online' in item) { - return { - server: item._online, - itemname: subname - }; - } - subname = item._name + '/' + subname; + if (item._online) + return { server: item._online, itemname }; + itemname = item._name + '/' + itemname; } return null; @@ -3065,9 +3399,9 @@ class HierarchyPainter extends BasePainter { * @private */ fillOnlineMenu(menu, onlineprop, itemname) { const node = this.findItem(itemname), - sett = getDrawSettings(node._kind, 'nosame;noinspect'), - handle = getDrawHandle(node._kind), - root_type = isStr(node._kind) ? node._kind.indexOf(prROOT) === 0 : false; + sett = getDrawSettings(node._kind, 'nosame;noinspect'), + handle = getDrawHandle(node._kind), + root_type = getTypeForKind(node._kind); if (sett.opts && (node._can_draw !== false)) { sett.opts.push(kInspect); @@ -3082,18 +3416,18 @@ class HierarchyPainter extends BasePainter { if (sett.opts && (node._can_draw !== false)) { menu.addDrawMenu('Draw in new window', sett.opts, - arg => window.open(onlineprop.server + `?nobrowser&item=${onlineprop.itemname}` + + arg => window.open(onlineprop.server + `?nobrowser&item=${onlineprop.itemname}` + (this.isMonitoring() ? `&monitoring=${this.getMonitoringInterval()}` : '') + (arg ? `&opt=${arg}` : ''))); - } + } if (sett.opts?.length && root_type && (node._can_draw !== false)) { menu.addDrawMenu('Draw as png', sett.opts, - arg => window.open(onlineprop.server + onlineprop.itemname + '/root.png?w=600&h=400' + (arg ? '&opt=' + arg : '')), - 'Request PNG image from the server'); + arg => window.open(onlineprop.server + onlineprop.itemname + '/root.png?w=600&h=400' + (arg ? '&opt=' + arg : '')), + 'Request PNG image from the server'); } - if ('_player' in node) + if (node._player) menu.add('Player', () => this.player(itemname)); } @@ -3108,63 +3442,59 @@ class HierarchyPainter extends BasePainter { * @param {number} interval - repetition interval in ms * @param {boolean} flag - initial monitoring state */ setMonitoring(interval, monitor_on) { - this._runMonitoring('cleanup'); + this.#runMonitoring('cleanup'); if (interval) { interval = parseInt(interval); if (Number.isInteger(interval) && (interval > 0)) { - this._monitoring_interval = Math.max(100, interval); + this.#monitoring_interval = Math.max(100, interval); monitor_on = true; } else - this._monitoring_interval = 3000; + this.#monitoring_interval = 3000; } - this._monitoring_on = monitor_on; + this.#monitoring_on = monitor_on; if (this.isMonitoring()) - this._runMonitoring(); + this.#runMonitoring(); } /** @summary Runs monitoring event loop * @private */ - _runMonitoring(arg) { + #runMonitoring(arg) { if ((arg === 'cleanup') || !this.isMonitoring()) { - if (this._monitoring_handle) { - clearTimeout(this._monitoring_handle); - delete this._monitoring_handle; + if (this.#monitoring_handle) { + clearTimeout(this.#monitoring_handle); + this.#monitoring_handle = undefined; } - if (this._monitoring_frame) { - cancelAnimationFrame(this._monitoring_frame); - delete this._monitoring_frame; + if (this.#monitoring_frame) { + cancelAnimationFrame(this.#monitoring_frame); + this.#monitoring_frame = undefined; } return; } if (arg === 'frame') { // process of timeout, request animation frame - delete this._monitoring_handle; - this._monitoring_frame = requestAnimationFrame(this._runMonitoring.bind(this, 'draw')); + this.#monitoring_handle = undefined; + this.#monitoring_frame = requestAnimationFrame(() => this.#runMonitoring('draw')); return; } if (arg === 'draw') { - delete this._monitoring_frame; + this.#monitoring_frame = undefined; this.updateItems(); } - this._monitoring_handle = setTimeout(this._runMonitoring.bind(this, 'frame'), this.getMonitoringInterval()); + this.#monitoring_handle = setTimeout(() => this.#runMonitoring('frame'), this.getMonitoringInterval()); } /** @summary Returns configured monitoring interval in ms */ - getMonitoringInterval() { - return this._monitoring_interval || 3000; - } + getMonitoringInterval() { return this.#monitoring_interval || 3000; } /** @summary Returns true when monitoring is enabled */ - isMonitoring() { - return this._monitoring_on; - } + isMonitoring() { return this.#monitoring_on; } /** @summary Assign default layout and place where drawing will be performed * @param {string} layout - layout like 'simple' or 'grid2x2' @@ -3190,7 +3520,7 @@ class HierarchyPainter extends BasePainter { return this.disp_kind; } - /** @summary Remove painter reference from hierarhcy + /** @summary Remove painter reference from hierarchy * @private */ removePainter(obj_painter) { this.forEachItem(item => { @@ -3198,7 +3528,8 @@ class HierarchyPainter extends BasePainter { // delete painter reference delete item._painter; // also clear data which could be associated with item - if (isFunc(item.clear)) item.clear(); + if (isFunc(item.clear)) + item.clear(); } }); } @@ -3216,8 +3547,10 @@ class HierarchyPainter extends BasePainter { this.forEachItem(item => { delete item._painter; // remove reference on the painter // when only display cleared, try to clear all browser items - if (!withbrowser && isFunc(item.clear)) item.clear(); - if (withbrowser) plainarr.push(item); + if (!withbrowser && isFunc(item.clear)) + item.clear(); + if (withbrowser) + plainarr.push(item); }); if (withbrowser) { @@ -3225,13 +3558,18 @@ class HierarchyPainter extends BasePainter { this.enableMonitoring(false); // simplify work for javascript and delete all (ok, most of) cross-references this.selectDom().html(''); - plainarr.forEach(d => { delete d._parent; delete d._childs; delete d._obj; delete d._d3cont; }); + plainarr.forEach(d => { + delete d._parent; + delete d._childs; + delete d._obj; + delete d._d3cont; + }); delete this.h; } } /** @summary Returns actual MDI display object - * @desc It should an instance of {@link MDIDsiplay} class */ + * @desc It should an instance of {@link MDIDisplay} class */ getDisplay() { return this.disp; } @@ -3247,7 +3585,7 @@ class HierarchyPainter extends BasePainter { const lst = cleanup(frame); // we remove all painters references from items - if (lst.length > 0) { + if (lst.length) { this.forEachItem(item => { if (item._painter && lst.indexOf(item._painter) >= 0) delete item._painter; @@ -3259,7 +3597,7 @@ class HierarchyPainter extends BasePainter { * @return {Promise} when ready * @private */ async createDisplay() { - if ('disp' in this) { + if (this.disp) { if ((this.disp.numDraw() > 0) || (this.disp_kind === 'custom')) return this.disp; this.disp.cleanup(); @@ -3275,10 +3613,12 @@ class HierarchyPainter extends BasePainter { } // check that we can found frame where drawing should be done - if (!document.getElementById(this.disp_frameid)) + if (!this.disp_frameid || !document.getElementById(this.disp_frameid)) return null; - if ((this.disp_kind.indexOf('flex') === 0) || (this.disp_kind.indexOf('coll') === 0)) + if (isBatchMode()) + this.disp = new BatchDisplay(settings.CanvasWidth, settings.CanvasHeight); + else if ((this.disp_kind.indexOf('flex') === 0) || (this.disp_kind.indexOf('coll') === 0)) this.disp = new FlexibleDisplay(this.disp_frameid); else if (this.disp_kind === 'tabs') this.disp = new TabsDisplay(this.disp_frameid); @@ -3287,7 +3627,7 @@ class HierarchyPainter extends BasePainter { this.disp.cleanupFrame = this.cleanupFrame.bind(this); if (settings.DragAndDrop) - this.disp.setInitFrame(this.enableDrop.bind(this)); + this.disp.setInitFrame(this.enableDrop.bind(this)); return this.disp; } @@ -3318,20 +3658,19 @@ class HierarchyPainter extends BasePainter { /** @summary function updates object drawings for other painters * @private */ updateOnOtherFrames(painter, obj) { - const mdi = this.disp; - if (!mdi) return false; - - const handle = obj._typename ? getDrawHandle(prROOT + obj._typename) : null; + const handle = obj._typename ? getDrawHandle(getKindForType(obj._typename)) : null; if (handle?.draw_field && obj[handle?.draw_field]) obj = obj[handle?.draw_field]; let isany = false; - mdi.forEachPainter((p /*, frame */) => { - if ((p === painter) || (p.getItemName() !== painter.getItemName())) return; + this.disp?.forEachPainter((p /* , frame */) => { + if ((p === painter) || (p.getItemName() !== painter.getItemName())) + return; - // do not actiavte frame when doing update + // do not activate frame when doing update // mdi.activateFrame(frame); - if (isFunc(p.redrawObject) && p.redrawObject(obj)) isany = true; + if (isFunc(p.redrawObject) && p.redrawObject(obj)) + isany = true; }); return isany; } @@ -3339,7 +3678,7 @@ class HierarchyPainter extends BasePainter { /** @summary Process resize event * @private */ checkResize(size) { - if (this.disp) this.disp.checkMDIResize(null, size); + this.disp?.checkMDIResize(null, size); } /** @summary Load and execute scripts, kept to support v6 applications @@ -3348,6 +3687,9 @@ class HierarchyPainter extends BasePainter { if (!scripts?.length && !modules?.length) return true; + if (use_inject && scripts.indexOf('.mjs') > 0) + return loadModules(scripts.split(';')); + if (use_inject && !globalThis.JSROOT) { globalThis.JSROOT = { version, gStyle, create, httpRequest, loadScript, decodeUrl, @@ -3372,68 +3714,74 @@ class HierarchyPainter extends BasePainter { async startGUI(gui_div, url) { const d = decodeUrl(url), - getOption = opt => { - let res = d.get(opt, null); - if ((res === null) && gui_div && !gui_div.empty() && gui_div.node().hasAttribute(opt)) - res = gui_div.attr(opt); - return res; - }, - - getUrlOptionAsArray = opt => { - let res = []; - - while (opt) { - const separ = opt.indexOf(';'); - let part = (separ > 0) ? opt.slice(0, separ) : opt; + getOption = opt => { + let res = d.get(opt, null); + if ((res === null) && gui_div && !gui_div.empty() && gui_div.node().hasAttribute(opt)) + res = gui_div.attr(opt); + return res; + }, - opt = (separ > 0) ? opt.slice(separ+1) : ''; + getUrlOptionAsArray = opt => { + let res = []; - let canarray = true; - if (part[0] === '#') { part = part.slice(1); canarray = false; } + while (opt) { + const separ = opt.indexOf(';'); + let part = (separ > 0) ? opt.slice(0, separ) : opt; - const val = d.get(part, null); - - if (canarray) - res = res.concat(parseAsArray(val)); - else if (val !== null) - res.push(val); - } - return res; - }, + opt = (separ > 0) ? opt.slice(separ + 1) : ''; - getOptionAsArray = opt => { - let res = getUrlOptionAsArray(opt); - if (res.length > 0 || !gui_div || gui_div.empty()) return res; - while (opt) { - const separ = opt.indexOf(';'); - let part = separ > 0 ? opt.slice(0, separ) : opt; - if (separ > 0) opt = opt.slice(separ+1); else opt = ''; + let canarray = true; + if (part[0] === '#') { + part = part.slice(1); + canarray = false; + } - let canarray = true; - if (part[0] === '#') { part = part.slice(1); canarray = false; } - if (part === 'files') continue; // special case for normal UI + const val = d.get(part, null); - if (!gui_div.node().hasAttribute(part)) continue; + if (canarray) + res = res.concat(parseAsArray(val)); + else if (val !== null) + res.push(val); + } + return res; + }, + + getOptionAsArray = opt => { + let res = getUrlOptionAsArray(opt); + if (res.length || !gui_div || gui_div.empty()) + return res; + while (opt) { + const separ = opt.indexOf(';'); + let part = separ > 0 ? opt.slice(0, separ) : opt; + opt = separ > 0 ? opt.slice(separ + 1) : ''; + + let canarray = true; + if (part[0] === '#') { + part = part.slice(1); + canarray = false; + } + if (part === 'files' || !gui_div.node().hasAttribute(part)) + continue; - const val = gui_div.attr(part); + const val = gui_div.attr(part); - if (canarray) - res = res.concat(parseAsArray(val)); - else if (val !== null) - res.push(val); - } - return res; - }, + if (canarray) + res = res.concat(parseAsArray(val)); + else if (val !== null) + res.push(val); + } + return res; + }, - filesdir = d.get('path') || '', // path used in normal gui - jsonarr = getOptionAsArray('#json;jsons'), - expanditems = getOptionAsArray('expand'), - focusitem = getOption('focus'), - layout = getOption('layout'), - style = getOptionAsArray('#style'), - title = getOption('title'); + filesdir = d.get('path') || '', // path used in normal gui + jsonarr = getOptionAsArray('#json;jsons'), + expanditems = getOptionAsArray('expand'), + focusitem = getOption('focus'), + layout = getOption('layout'), + style = getOptionAsArray('#style'), + title = getOption('title'); - this._one_by_one = settings.drop_items_one_by_one ?? (getOption('one_by_one') !== null); + this.#one_by_one = settings.drop_items_one_by_one ?? (getOption('one_by_one') !== null); let prereq = getOption('prereq') || '', load = getOption('load'), @@ -3445,8 +3793,7 @@ class HierarchyPainter extends BasePainter { monitor = getOption('monitoring'), statush = 0, status = getOption('status'), browser_kind = getOption('browser'), - browser_configured = !!browser_kind; - + browser_configured = Boolean(browser_kind); if (monitor === null) monitor = 0; @@ -3467,6 +3814,7 @@ class HierarchyPainter extends BasePainter { browser_kind = 'float'; this.no_select = getOption('noselect'); + this.top_info = getOption('info'); if (getOption('files_monitoring') !== null) this.files_monitoring = true; @@ -3474,16 +3822,18 @@ class HierarchyPainter extends BasePainter { if (title && (typeof document !== 'undefined')) document.title = title; - if (expanditems.length === 0 && (getOption('expand') === '')) expanditems.push(''); + if (!expanditems.length && (getOption('expand') === '')) + expanditems.push(''); if (filesdir) { - for (let i = 0; i < filesarr.length; ++i) filesarr[i] = filesdir + filesarr[i]; - for (let i = 0; i < jsonarr.length; ++i) jsonarr[i] = filesdir + jsonarr[i]; + for (let i = 0; i < filesarr.length; ++i) + filesarr[i] = filesdir + filesarr[i]; + for (let i = 0; i < jsonarr.length; ++i) + jsonarr[i] = filesdir + jsonarr[i]; } - if ((itemsarr.length === 0) && getOption('item') === '') itemsarr.push(''); - - if ((jsonarr.length === 1) && (itemsarr.length === 0) && (expanditems.length === 0)) itemsarr.push(''); + if (!itemsarr.length && ((getOption('item') === '') || (jsonarr.length === 1 && !expanditems.length))) + itemsarr.push(''); if (!this.disp_kind) { if (isStr(layout) && layout) @@ -3491,19 +3841,9 @@ class HierarchyPainter extends BasePainter { else if (settings.DislpayKind && settings.DislpayKind !== 'simple') this.disp_kind = settings.DislpayKind; else { - switch (itemsarr.length) { - case 0: - case 1: this.disp_kind = 'simple'; break; - case 2: this.disp_kind = 'vert2'; break; - case 3: this.disp_kind = 'vert21'; break; - case 4: this.disp_kind = 'vert22'; break; - case 5: this.disp_kind = 'vert32'; break; - case 6: this.disp_kind = 'vert222'; break; - case 7: this.disp_kind = 'vert322'; break; - case 8: this.disp_kind = 'vert332'; break; - case 9: this.disp_kind = 'vert333'; break; - default: this.disp_kind = 'flex'; - } + const _kinds = ['simple', 'simple', 'vert2', 'vert21', 'vert22', 'vert32', + 'vert222', 'vert322', 'vert332', 'vert333']; + this.disp_kind = _kinds[itemsarr.length] || 'flex'; } } @@ -3516,10 +3856,12 @@ class HierarchyPainter extends BasePainter { status = true; else if (status !== null) { statush = parseInt(status); - if (!Number.isInteger(statush) || (statush < 5)) statush = 0; + if (!Number.isInteger(statush) || (statush < 5)) + statush = 0; status = true; } - if (this.no_select === '') this.no_select = true; + if (this.no_select === '') + this.no_select = true; if (!browser_kind) browser_kind = 'fix'; @@ -3533,35 +3875,41 @@ class HierarchyPainter extends BasePainter { if (getOption('nofloat') !== null) this.float_browser_disabled = true; - if (this.start_without_browser) browser_kind = ''; + if (this.start_without_browser) + browser_kind = ''; - this._topname = getOption('topname'); + this.#topname = getOption('topname'); const openAllFiles = () => { let promise; if (load || prereq) { - promise = this.loadScripts(load, prereq); load = ''; prereq = ''; + promise = this.loadScripts(load, prereq); + load = prereq = ''; } else if (inject) { - promise = this.loadScripts(inject, '', true); inject = ''; + promise = this.loadScripts(inject, '', true); + inject = ''; } else if (browser_kind) { - promise = this.createBrowser(browser_kind); browser_kind = ''; + promise = this.createBrowser(browser_kind); + browser_kind = ''; } else if (status !== null) { - promise = this.createStatusLine(statush, status); status = null; - } else if (jsonarr.length > 0) + promise = this.createStatusLine(statush, status); + status = null; + } else if (jsonarr.length) promise = this.openJsonFile(jsonarr.shift()); - else if (filesarr.length > 0) + else if (filesarr.length) promise = this.openRootFile(filesarr.shift()); else if (dir) { - promise = this.listServerDir(dir); dir = ''; - } else if (expanditems.length > 0) + promise = this.listServerDir(dir); + dir = ''; + } else if (expanditems.length) promise = this.expandItem(expanditems.shift()); - else if (style.length > 0) + else if (style.length) promise = this.applyStyle(style.shift()); else { return this.refreshHtml() .then(() => this.displayItems(itemsarr, optionsarr)) - .then(() => focusitem ? this.focusOnItem(focusitem) : this) + .then(() => { return focusitem ? this.focusOnItem(focusitem) : this; }) .then(() => { this.setMonitoring(monitor); return itemsarr ? this.refreshHtml() : this; // this is final return @@ -3586,24 +3934,29 @@ class HierarchyPainter extends BasePainter { if (h0 !== null) { return this.openOnline(h0).then(() => { // check if server enables monitoring - if (!this.exclude_browser && !browser_configured && ('_browser' in this.h)) { + if (!this.exclude_browser && !browser_configured && this.h._browser) { browser_kind = this.h._browser; - if (browser_kind === 'no') browser_kind = ''; else - if (browser_kind === 'off') { browser_kind = ''; status = null; this.exclude_browser = true; } + if (browser_kind === 'no') + browser_kind = ''; + else if (browser_kind === 'off') { + browser_kind = ''; + status = null; + this.exclude_browser = true; + } } if (('_monitoring' in this.h) && !monitor) monitor = this.h._monitoring; - if (('_loadfile' in this.h) && (filesarr.length === 0)) + if (this.h._loadfile && !filesarr.length) filesarr = parseAsArray(this.h._loadfile); - if (('_drawitem' in this.h) && (itemsarr.length === 0)) { + if (('_drawitem' in this.h) && !itemsarr.length) { itemsarr = parseAsArray(this.h._drawitem); optionsarr = parseAsArray(this.h._drawopt); } - if (('_layout' in this.h) && !layout && ((this.is_online !== 'draw') || (itemsarr.length > 1))) + if (this.h._layout && !layout && ((this.is_online !== 'draw') || (itemsarr.length > 1))) this.disp_kind = this.h._layout; if (('_toptitle' in this.h) && this.exclude_browser && (typeof document !== 'undefined')) @@ -3638,10 +3991,12 @@ class HierarchyPainter extends BasePainter { /** @summary Create shortcut buttons */ createButtons() { - if (this.exclude_browser) return; + if (this.exclude_browser) + return; const btns = this.brlayout?.createBrowserBtns(); - if (!btns) return; + if (!btns) + return; ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos browser', 'browser') .style('margin', '3px').on('click', () => this.createBrowser('fix', true)); @@ -3657,7 +4012,7 @@ class HierarchyPainter extends BasePainter { } } - /** @summary Returns trus if status is exists */ + /** @summary Returns true if status is exists */ hasStatusLine() { if (this.status_disabled || !this.gui_div || !this.brlayout) return false; @@ -3678,12 +4033,13 @@ class HierarchyPainter extends BasePainter { * @desc works only when inspector or streamer info is displayed * @private */ redrawObject(obj) { - if (!this._inspector && !this._streamer_info) return false; + if (!this._inspector && !this._streamer_info) + return false; if (this._streamer_info) this.h = createStreamerInfoContent(obj); else this.h = createInspectorContent(obj); - return this.refreshHtml().then(() => { this.setTopPainter(); }); + return this.refreshHtml().then(() => this.setTopPainter()); } /** @summary Create browser elements @@ -3704,7 +4060,8 @@ class HierarchyPainter extends BasePainter { // this is case when browser created, // if update_html specified, hidden state will be toggled - if (update_html) this.brlayout.toggleKind(browser_kind); + if (update_html) + this.brlayout.toggleKind(browser_kind); return true; } @@ -3716,8 +4073,8 @@ class HierarchyPainter extends BasePainter { '<div style="display:inline; vertical-align:middle; white-space: nowrap;">' + '<label style="margin-right:5px"><input type="checkbox" name="monitoring" class="gui_monitoring"/>Monitoring</label>'; } else if (!this.no_select) { - const myDiv = d3_select('#'+this.gui_div), - files = myDiv.attr('files') || '../files/hsimple.root', + const myDiv = d3_select('#' + this.gui_div), + files = myDiv.attr('files') || 'https://root.cern/js/files/hsimple.root', path = decodeUrl().get('path') || myDiv.attr('path') || '', arrFiles = files.split(';'); @@ -3748,7 +4105,7 @@ class HierarchyPainter extends BasePainter { this.brlayout.setBrowserContent(guiCode); - const title_elem = this.brlayout.setBrowserTitle(this.is_online ? 'ROOT online server' : 'Read a ROOT file'); + const title_elem = this.brlayout.setBrowserTitle(this.top_info || (this.is_online ? 'ROOT online server' : 'Read a ROOT file')); title_elem?.on('contextmenu', evnt => { evnt.preventDefault(); createMenu(evnt).then(menu => { @@ -3762,7 +4119,8 @@ class HierarchyPainter extends BasePainter { if (!this.is_online && !this.no_select) { this.readSelectedFile = function() { const filename = main.select('.gui_urlToLoad').property('value').trim(); - if (!filename) return; + if (!filename) + return; if (filename.toLowerCase().lastIndexOf('.json') === filename.length - 5) this.openJsonFile(filename); @@ -3779,7 +4137,8 @@ class HierarchyPainter extends BasePainter { main.select('.gui_ResetUIBtn').on('click', () => this.clearHierarchy(true)); main.select('.gui_urlToLoad').on('keyup', evnt => { - if (evnt.code === 'Enter') this.readSelectedFile(); + if (evnt.code === 'Enter') + this.readSelectedFile(); }); main.select('.gui_localFile').on('change', evnt => { @@ -3796,7 +4155,7 @@ class HierarchyPainter extends BasePainter { const layout = main.select('.gui_layout'); if (!layout.empty()) { ['simple', 'vert2', 'vert3', 'vert231', 'horiz2', 'horiz32', 'flex', 'tabs', - 'grid 2x2', 'grid 1x3', 'grid 2x3', 'grid 3x3', 'grid 4x4'].forEach(kind => layout.append('option').attr('value', kind).html(kind)); + 'grid 2x2', 'grid 1x3', 'grid 2x3', 'grid 3x3', 'grid 4x4'].forEach(kind => layout.append('option').attr('value', kind).text(kind)); layout.on('change', ev => { const kind = ev.target.value || 'flex'; @@ -3818,7 +4177,8 @@ class HierarchyPainter extends BasePainter { /** @summary Initialize browser elements */ initializeBrowser() { const main = d3_select(`#${this.gui_div} .jsroot_browser`); - if (main.empty() || !this.brlayout) return; + if (main.empty() || !this.brlayout) + return; this.brlayout.adjustBrowserSize(); @@ -3828,15 +4188,17 @@ class HierarchyPainter extends BasePainter { let found = false; for (const i in selects.options) { const s = selects.options[i].text; - if (!isStr(s)) continue; + if (!isStr(s)) + continue; if ((s === this.getLayout()) || (s.replace(/ /g, '') === this.getLayout())) { - selects.selectedIndex = i; found = true; + selects.selectedIndex = i; + found = true; break; } } if (!found) { const opt = document.createElement('option'); - opt.innerHTML = opt.value = this.getLayout(); + opt.innerText = opt.value = this.getLayout(); selects.appendChild(opt); selects.selectedIndex = selects.options.length - 1; } @@ -3848,12 +4210,15 @@ class HierarchyPainter extends BasePainter { main.select('.gui_monitoring') .property('checked', this.isMonitoring()) .on('click', evnt => { - this.enableMonitoring(evnt.target.checked); - this.updateItems(); - }); + this.enableMonitoring(evnt.target.checked); + this.updateItems(); + }); } else if (!this.no_select) { let fname = ''; - this.forEachRootFile(item => { if (!fname) fname = item._fullurl; }); + this.forEachRootFile(item => { + if (!fname) + fname = item._fullurl; + }); main.select('.gui_urlToLoad').property('value', fname); } } @@ -3869,34 +4234,7 @@ class HierarchyPainter extends BasePainter { } // class HierarchyPainter - - -/** @summary Show object in inspector for provided object - * @protected */ -ObjectPainter.prototype.showInspector = function(opt, obj) { - if (opt === 'check') - return true; - - const main = this.selectDom(), - rect = getElementRect(main), - w = Math.round(rect.width * 0.05) + 'px', - h = Math.round(rect.height * 0.05) + 'px', - id = 'root_inspector_' + internals.id_counter++; - - main.append('div') - .attr('id', id) - .attr('class', 'jsroot_inspector') - .style('position', 'absolute') - .style('top', h) - .style('bottom', h) - .style('left', w) - .style('right', w); - - if (!obj?._typename) - obj = isFunc(this.getPrimaryObject) ? this.getPrimaryObject() : this.getObject(); - - return drawInspector(id, obj, opt); -}; +// ====================================================================================== /** @summary Display streamer info @@ -3921,8 +4259,6 @@ async function drawStreamerInfo(dom, lst) { }); } -// ====================================================================================== - /** @summary Display inspector * @private */ async function drawInspector(dom, obj, opt) { @@ -3935,14 +4271,14 @@ async function drawInspector(dom, obj, opt) { return painter; } - painter.default_by_click = 'expand'; // default action + painter.default_by_click = kExpand; // default action painter.with_icons = false; painter._inspector = true; // keep let expand_level = 0; if (isStr(opt) && opt.indexOf(kInspect) === 0) { opt = opt.slice(kInspect.length); - if (opt.length > 0) + if (opt) expand_level = Number.parseInt(opt); } @@ -3950,23 +4286,32 @@ async function drawInspector(dom, obj, opt) { painter.removeInspector = function() { this.selectDom().remove(); }; + + if (!browser.qt6 && !browser.cef3) { + painter.storeAsJson = function() { + const json = toJSON(obj, 2), + fname = obj.fName || 'file'; + saveFile(`${fname}.json`, prJSON + encodeURIComponent(json)); + }; + } } painter.fill_context = function(menu, hitem) { const sett = getDrawSettings(hitem._kind, 'nosame'); if (sett.opts) { menu.addDrawMenu('nosub:Draw', sett.opts, arg => { - if (!hitem?._obj) return; - const obj = hitem._obj; + if (!hitem?._obj) + return; + const obj2 = hitem._obj; let ddom = this.selectDom().node(); if (isFunc(this.removeInspector)) { ddom = ddom.parentNode; this.removeInspector(); if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg, obj); + return this.showInspector(arg, obj2); } cleanup(ddom); - draw(ddom, obj, arg); + draw(ddom, obj2, arg); }); } }; @@ -3975,12 +4320,40 @@ async function drawInspector(dom, obj, opt) { return painter.refreshHtml().then(() => { painter.setTopPainter(); - return painter.exapndToLevel(expand_level); + return painter.expandToLevel(expand_level); }); } + +/** @summary Show object in inspector for provided object + * @protected */ +ObjectPainter.prototype.showInspector = function(opt, obj) { + if (opt === 'check') + return true; + + const main = this.selectDom(), + rect = getElementRect(main), + w = Math.round(rect.width * 0.05) + 'px', + h = Math.round(rect.height * 0.05) + 'px', + id = 'root_inspector_' + internals.id_counter++; + + main.append('div') + .attr('id', id) + .attr('class', 'jsroot_inspector') + .style('position', 'absolute') + .style('top', h) + .style('bottom', h) + .style('left', w) + .style('right', w); + + if (!obj?._typename) + obj = isFunc(this.getPrimaryObject) ? this.getPrimaryObject() : this.getObject(); + + return drawInspector(id, obj, opt); +}; + + internals.drawInspector = drawInspector; -export { getHPainter, HierarchyPainter, - drawInspector, drawStreamerInfo, drawList, markAsStreamerInfo, - folderHierarchy, taskHierarchy, listHierarchy, objectHierarchy, keysHierarchy }; +export { HierarchyPainter, drawInspector, drawStreamerInfo, drawList, markAsStreamerInfo, + folderHierarchy, taskHierarchy, listHierarchy, objectHierarchy, keysHierarchy, parseAsArray }; diff --git a/modules/gui/display.mjs b/modules/gui/display.mjs index 4c5340503..0e66e60fa 100644 --- a/modules/gui/display.mjs +++ b/modules/gui/display.mjs @@ -1,6 +1,6 @@ import { select as d3_select, drag as d3_drag } from '../d3.mjs'; -import { browser, internals, toJSON, settings, isObject, isFunc, isStr, nsSVG } from '../core.mjs'; -import { compressSVG, BasePainter } from '../base/BasePainter.mjs'; +import { browser, internals, toJSON, settings, isObject, isFunc, isStr, nsSVG, btoa_func } from '../core.mjs'; +import { compressSVG, BasePainter, svgToImage } from '../base/BasePainter.mjs'; import { getElementCanvPainter, selectActivePad, cleanup, resize, ObjectPainter } from '../base/ObjectPainter.mjs'; import { createMenu } from './menu.mjs'; import { detectRightButton, injectStyle } from './utils.mjs'; @@ -9,15 +9,15 @@ import { detectRightButton, injectStyle } from './utils.mjs'; /** @summary Current hierarchy painter * @desc Instance of {@link HierarchyPainter} object * @private */ -let first_hpainter = null; +let _first_hpainter = null; /** @summary Returns current hierarchy painter object * @private */ -function getHPainter() { return first_hpainter; } +function getHPainter() { return _first_hpainter; } /** @summary Set hierarchy painter object * @private */ -function setHPainter(hp) { first_hpainter = hp; } +function setHPainter(hp) { _first_hpainter = hp; } /** * @summary Base class to manage multiple document interface for drawings @@ -63,7 +63,7 @@ class MDIDisplay extends BasePainter { console.warn(`forEachFrame not implemented in MDIDisplay ${typeof userfunc} ${only_visible}`); } - /** @summary method dedicated to iterate over existing panles + /** @summary method dedicated to iterate over existing panels * @param {function} userfunc is called with arguments (painter, frame) * @param {boolean} only_visible let select only visible frames */ forEachPainter(userfunc, only_visible) { @@ -79,7 +79,7 @@ class MDIDisplay extends BasePainter { return cnt; } - /** @summary Serach for the frame using item name */ + /** @summary Search for the frame using item name */ findFrame(searchtitle, force) { let found_frame = null; @@ -106,11 +106,13 @@ class MDIDisplay extends BasePainter { let resized_frame = null; this.forEachPainter((painter, frame) => { - if (only_frame_id && (d3_select(frame).attr('id') !== only_frame_id)) return; + if (only_frame_id && (d3_select(frame).attr('id') !== only_frame_id)) + return; if ((painter.getItemName() !== null) && isFunc(painter.checkResize)) { // do not call resize for many painters on the same frame - if (resized_frame === frame) return; + if (resized_frame === frame) + return; painter.checkResize(size); resized_frame = frame; } @@ -151,7 +153,7 @@ class CustomDisplay extends MDIDisplay { forEachFrame(userfunc) { const ks = Object.keys(this.frames); for (let k = 0; k < ks.length; ++k) { - const node = d3_select('#'+ks[k]); + const node = d3_select('#' + ks[k]); if (!node.empty()) userfunc(node.node()); } @@ -163,8 +165,8 @@ class CustomDisplay extends MDIDisplay { const ks = Object.keys(this.frames); for (let k = 0; k < ks.length; ++k) { const items = this.frames[ks[k]]; - if (items.indexOf(title+';') >= 0) - return d3_select('#'+ks[k]).node(); + if (items.indexOf(title + ';') >= 0) + return d3_select('#' + ks[k]).node(); } return null; } @@ -184,7 +186,7 @@ class CustomDisplay extends MDIDisplay { class GridDisplay extends MDIDisplay { - /** @summary Create GridDisplay instance + /** @summary Create GridDisplay instance * @param {string} frameid - where grid display is created * @param {string} kind - kind of grid * @desc following kinds are supported @@ -227,8 +229,10 @@ class GridDisplay extends MDIDisplay { kind = ''; this.match_sizes = true; } else if ((kind.indexOf('grid') === 0) || kind2) { - if (kind2) kind = kind + 'x' + kind2; - else kind = kind.slice(4).trim(); + if (kind2) + kind = kind + 'x' + kind2; + else + kind = kind.slice(4).trim(); this.use_separarators = false; if (kind[0] === 'i') { this.use_separarators = true; @@ -244,9 +248,10 @@ class GridDisplay extends MDIDisplay { } else sizex = sizey = parseInt(kind); - - if (!Number.isInteger(sizex)) sizex = 3; - if (!Number.isInteger(sizey)) sizey = 3; + if (!Number.isInteger(sizex)) + sizex = 3; + if (!Number.isInteger(sizey)) + sizey = 3; if (sizey > 1) { this.vertical = true; @@ -266,14 +271,15 @@ class GridDisplay extends MDIDisplay { } if (kind && kind.indexOf('_') > 0) { - let arg = parseInt(kind.slice(kind.indexOf('_')+1), 10); + let arg = parseInt(kind.slice(kind.indexOf('_') + 1), 10); if (Number.isInteger(arg) && (arg > 10)) { kind = kind.slice(0, kind.indexOf('_')); sizes = []; while (arg > 0) { sizes.unshift(Math.max(arg % 10, 1)); - arg = Math.round((arg-sizes[0])/10); - if (sizes[0] === 0) sizes[0] = 1; + arg = Math.round((arg - sizes[0]) / 10); + if (sizes[0] === 0) + sizes[0] = 1; } } } @@ -282,12 +288,13 @@ class GridDisplay extends MDIDisplay { if (Number.isInteger(kind) && (kind > 1)) { if (kind < 10) num = kind; - else { + else { arr = []; while (kind > 0) { arr.unshift(kind % 10); - kind = Math.round((kind-arr[0])/10); - if (arr[0] === 0) arr[0] = 1; + kind = Math.round((kind - arr[0]) / 10); + if (arr[0] === 0) + arr[0] = 1; } num = arr.length; } @@ -305,19 +312,22 @@ class GridDisplay extends MDIDisplay { /** @summary Create frames group * @private */ createGroup(handle, main, num, childs, sizes, childs_sizes) { - if (!sizes) sizes = new Array(num); + if (!sizes) + sizes = new Array(num); let sum1 = 0, sum2 = 0; for (let n = 0; n < num; ++n) sum1 += (sizes[n] || 1); for (let n = 0; n < num; ++n) { sizes[n] = Math.round(100 * (sizes[n] || 1) / sum1); sum2 += sizes[n]; - if (n === num-1) sizes[n] += (100-sum2); // make 100% + if (n === num - 1) + sizes[n] += (100 - sum2); // make 100% } for (let cnt = 0; cnt < num; ++cnt) { const group = { id: cnt, drawid: -1, position: 0, size: sizes[cnt], parent: handle }; - if (cnt > 0) group.position = handle.groups[cnt-1].position + handle.groups[cnt-1].size; + if (cnt > 0) + group.position = handle.groups[cnt - 1].position + handle.groups[cnt - 1].size; group.position0 = group.position; if (!childs || !childs[cnt] || childs[cnt] < 2) @@ -332,9 +342,9 @@ class GridDisplay extends MDIDisplay { group.node = elem.node(); if (handle.vertical) - elem.style('float', 'bottom').style('height', group.size.toFixed(2)+'%').style('width', '100%'); + elem.style('float', 'bottom').style('height', group.size.toFixed(2) + '%').style('width', '100%'); else - elem.style('float', 'left').style('width', group.size.toFixed(2)+'%').style('height', '100%'); + elem.style('float', 'left').style('width', group.size.toFixed(2) + '%').style('height', '100%'); if (group.drawid >= 0) { elem.classed('jsroot_newgrid', true); @@ -343,7 +353,6 @@ class GridDisplay extends MDIDisplay { } else elem.style('display', 'flex').style('flex-direction', handle.vertical ? 'row' : 'column'); - if (childs && (childs[cnt] > 1)) { group.vertical = !handle.vertical; group.groups = []; @@ -358,7 +367,7 @@ class GridDisplay extends MDIDisplay { } } - /** @summary Handle interactive sepearator movement + /** @summary Handle interactive separator movement * @private */ handleSeparator(elem, action) { const findGroup = (node, grid) => { @@ -372,7 +381,7 @@ class GridDisplay extends MDIDisplay { return d3_select(node).select(`[groupid='${grid}']`); }, setGroupSize = (h, node, grid) => { const name = h.vertical ? 'height' : 'width', - size = h.groups[grid].size.toFixed(2)+'%'; + size = h.groups[grid].size.toFixed(2) + '%'; findGroup(node, grid).style(name, size) .selectAll('.jsroot_separator').style(name, size); }, resizeGroup = (node, grid) => { @@ -382,12 +391,13 @@ class GridDisplay extends MDIDisplay { sel.each(function() { resize(this); }); }, posSepar = (h, group, separ) => { separ.style(h.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`); - }, separ = d3_select(elem), - parent = elem.parentNode, - handle = separ.property('handle'), - id = separ.property('separator_id'), - group = handle.groups[id]; - let needResize = false, needSetSize = false; + }; + // eslint-disable-next-line one-var + const separ = d3_select(elem), + parent = elem.parentNode, + handle = separ.property('handle'), + id = separ.property('separator_id'), + group = handle.groups[id]; if (action === 'start') { group.startpos = group.position; @@ -395,6 +405,8 @@ class GridDisplay extends MDIDisplay { return; } + let needResize, needSetSize = false; + if (action === 'end') { if (Math.abs(group.startpos - group.position) < 0.5) return; @@ -402,23 +414,20 @@ class GridDisplay extends MDIDisplay { } else { let pos; if (action === 'restore') - pos = group.position0; - else if (handle.vertical) { - group.acc_drag += action.dy; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientHeight) * 100; + pos = group.position0; + else if (handle.vertical) { + group.acc_drag += action.dy; + pos = group.startpos + ((group.acc_drag + 2) / parent.clientHeight) * 100; } else { - group.acc_drag += action.dx; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientWidth) * 100; + group.acc_drag += action.dx; + pos = group.startpos + ((group.acc_drag + 2) / parent.clientWidth) * 100; } const diff = group.position - pos; + if ((Math.abs(diff) < 0.3) || (Math.min(handle.groups[id - 1].size - diff, group.size + diff) < 3)) + return; // if no significant change, do nothing - if (Math.abs(diff) < 0.3) return; // if no significant change, do nothing - - // do not change if size too small - if (Math.min(handle.groups[id-1].size - diff, group.size+diff) < 3) return; - - handle.groups[id-1].size -= diff; + handle.groups[id - 1].size -= diff; group.size += diff; group.position = pos; @@ -429,12 +438,12 @@ class GridDisplay extends MDIDisplay { } if (needSetSize) { - setGroupSize(handle, parent, id-1); + setGroupSize(handle, parent, id - 1); setGroupSize(handle, parent, id); } if (needResize) { - resizeGroup(parent, id-1); + resizeGroup(parent, id - 1); resizeGroup(parent, id); } @@ -444,24 +453,25 @@ class GridDisplay extends MDIDisplay { for (let k = 0; k < handle.parent.groups.length; ++k) { const hh = handle.parent.groups[k]; - if ((hh === handle) || !hh.node) continue; + if ((hh === handle) || !hh.node) + continue; hh.groups[id].size = handle.groups[id].size; hh.groups[id].position = handle.groups[id].position; - hh.groups[id-1].size = handle.groups[id-1].size; - hh.groups[id-1].position = handle.groups[id-1].position; + hh.groups[id - 1].size = handle.groups[id - 1].size; + hh.groups[id - 1].position = handle.groups[id - 1].position; if (needSetSize) { d3_select(hh.node).selectAll('.jsroot_separator').each(function() { const s = d3_select(this); if (s.property('separator_id') === id) posSepar(hh, hh.groups[id], s); }); - setGroupSize(hh, hh.node, id-1); + setGroupSize(hh, hh.node, id - 1); setGroupSize(hh, hh.node, id); } if (needResize) { - resizeGroup(hh.node, id-1); + resizeGroup(hh.node, id - 1); resizeGroup(hh.node, id); - } + } } } @@ -475,7 +485,7 @@ class GridDisplay extends MDIDisplay { .property('separator_id', group.id) .attr('style', 'pointer-events: all; border: 0; margin: 0; padding: 0; position: absolute;') .style(handle.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`) - .style(handle.vertical ? 'width' : 'height', (handle.size?.toFixed(2) || 100)+'%') + .style(handle.vertical ? 'width' : 'height', (handle.size?.toFixed(2) || 100) + '%') .style(handle.vertical ? 'height' : 'width', '5px') .style('cursor', handle.vertical ? 'ns-resize' : 'ew-resize') .append('div').attr('style', 'position: absolute;' + (handle.vertical @@ -494,16 +504,12 @@ class GridDisplay extends MDIDisplay { main.on('touchmove', () => {}); } - /** @summary Call function for each frame */ forEachFrame(userfunc) { if (this.simple_layout) userfunc(this.getGridFrame()); - else { - this.selectDom().selectAll('.jsroot_newgrid').each(function() { - userfunc(this); - }); - } + else + this.selectDom().selectAll('.jsroot_newgrid').each(function() { userfunc(this); }); } /** @summary Returns active frame */ @@ -512,9 +518,10 @@ class GridDisplay extends MDIDisplay { return this.getGridFrame(); let found = super.getActiveFrame(); - if (found) return found; - - this.forEachFrame(frame => { if (!found) found = frame; }); + this.forEachFrame(frame => { + if (!found) + found = frame; + }); return found; } @@ -528,7 +535,8 @@ class GridDisplay extends MDIDisplay { return this.selectDom('origin').node(); let res = null; this.selectDom().selectAll('.jsroot_newgrid').each(function() { - if (id-- === 0) res = this; + if (id-- === 0) + res = this; }); return res; } @@ -542,9 +550,10 @@ class GridDisplay extends MDIDisplay { while (!frame && maxloop--) { frame = this.getGridFrame(this.getcnt); if (!this.simple_layout && this.framecnt) - this.getcnt = (this.getcnt+1) % this.framecnt; + this.getcnt = (this.getcnt + 1) % this.framecnt; - if (d3_select(frame).classed('jsroot_fixed_frame')) frame = null; + if (d3_select(frame).classed('jsroot_fixed_frame')) + frame = null; } if (frame) { @@ -583,11 +592,13 @@ class TabsDisplay extends MDIDisplay { /** @summary call function for each frame */ forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) return; + if (!isFunc(userfunc)) + return; if (only_visible) { const active = this.getActiveFrame(); - if (active) userfunc(active); + if (active) + userfunc(active); return; } @@ -601,13 +612,13 @@ class TabsDisplay extends MDIDisplay { /** @summary modify tab state by id */ modifyTabsFrame(frame_id, action) { const top = this.selectDom().select('.jsroot_tabs'), - labels = top.select('.jsroot_tabs_labels'), - main = top.select('.jsroot_tabs_main'); + labels = top.select('.jsroot_tabs_labels'), + main = top.select('.jsroot_tabs_main'); labels.selectAll('.jsroot_tabs_label').each(function() { const id = d3_select(this).property('frame_id'), - is_same = (id === frame_id), - active_color = settings.DarkMode ? '#333' : 'white'; + is_same = (id === frame_id), + active_color = settings.DarkMode ? '#333' : 'white'; if (action === 'activate') { d3_select(this).style('background', is_same ? active_color : (settings.DarkMode ? 'black' : '#ddd')) @@ -629,12 +640,13 @@ class TabsDisplay extends MDIDisplay { d3_select(this).style('background', settings.DarkMode ? 'black' : 'white'); }); - if (!selected_frame) return; + if (!selected_frame) + return; if (action === 'activate') selected_frame.parentNode.appendChild(selected_frame); // super.activateFrame(selected_frame); - else if (action === 'close') { + else if (action === 'close') { const was_active = (selected_frame === this.getActiveFrame()); cleanup(selected_frame); selected_frame.remove(); @@ -644,7 +656,7 @@ class TabsDisplay extends MDIDisplay { } } - /** @summary actiavte frame */ + /** @summary activate frame */ activateFrame(frame) { if (frame) this.modifyTabsFrame(d3_select(frame).property('frame_id'), 'activate'); @@ -673,22 +685,24 @@ class TabsDisplay extends MDIDisplay { const frame_id = this.cnt++, mdi = this; let lbl = title; - if (!lbl || !isStr(lbl)) lbl = `frame_${frame_id}`; + if (!lbl || !isStr(lbl)) + lbl = `frame_${frame_id}`; if (lbl.length > 15) { let p = lbl.lastIndexOf('/'); - if (p === lbl.length-1) p = lbl.lastIndexOf('/', p-1); + if (p === lbl.length - 1) + p = lbl.lastIndexOf('/', p - 1); if ((p > 0) && (lbl.length - p < 20) && (lbl.length - p > 1)) - lbl = lbl.slice(p+1); + lbl = lbl.slice(p + 1); else - lbl = '...' + lbl.slice(lbl.length-17); + lbl = '...' + lbl.slice(lbl.length - 17); } labels.append('span') .attr('tabindex', 0) .append('label') .attr('class', 'jsroot_tabs_label') - .attr('style', 'border: 1px solid; display: inline-block; font-size: 1rem; left: 1px;'+ + .attr('style', 'border: 1px solid; display: inline-block; font-size: 1rem; left: 1px;' + 'margin-left: 3px; padding: 0px 5px 1px 5px; position: relative; vertical-align: bottom;') .property('frame_id', frame_id) .text(lbl) @@ -699,7 +713,7 @@ class TabsDisplay extends MDIDisplay { }).append('button') .attr('title', 'close') .attr('style', 'margin-left: .5em; padding: 0; font-size: 0.5em; width: 1.8em; height: 1.8em; vertical-align: center;') - .html('✕') + .text('\u2715') .on('click', function() { mdi.modifyTabsFrame(d3_select(this.parentNode).property('frame_id'), 'close'); }); @@ -749,13 +763,15 @@ class FlexibleDisplay extends MDIDisplay { /** @summary call function for each frame */ forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) return; + if (!isFunc(userfunc)) + return; const mdi = this, top = this.selectDom().select('.jsroot_flex_top'); top.selectAll('.jsroot_flex_draw').each(function() { // check if only visible specified - if (only_visible && (mdi.getFrameState(this) === 'min')) return; + if (only_visible && (mdi.getFrameState(this) === 'min')) + return; userfunc(this); }); @@ -764,24 +780,26 @@ class FlexibleDisplay extends MDIDisplay { /** @summary return active frame */ getActiveFrame() { let found = super.getActiveFrame(); - if (found && d3_select(found.parentNode).property('state') !== 'min') return found; + if (found && d3_select(found.parentNode).property('state') !== 'min') + return found; found = null; this.forEachFrame(frame => { found = frame; }, true); return found; } - /** @summary actiavte frame */ + /** @summary activate frame */ activateFrame(frame) { if ((frame === 'first') || (frame === 'last')) { let res = null; - this.forEachFrame(f => { if (frame === 'last' || !res) res = f; }, true); + this.forEachFrame(f => { + if (frame === 'last' || !res) + res = f; + }, true); frame = res; } - if (!frame) return; - if (frame.getAttribute('class') !== 'jsroot_flex_draw') return; - - if (this.getActiveFrame() === frame) return; + if ((frame?.getAttribute('class') !== 'jsroot_flex_draw') || (this.getActiveFrame() === frame)) + return; super.activateFrame(frame); @@ -808,22 +826,23 @@ class FlexibleDisplay extends MDIDisplay { } const main = d3_select(frame.parentNode), left = main.style('left'), top = main.style('top'); - - return { x: parseInt(left.slice(0, left.length-2)), y: parseInt(top.slice(0, top.length-2)), - w: main.node().clientWidth, h: main.node().clientHeight }; + return { + x: parseInt(left.slice(0, left.length - 2)), y: parseInt(top.slice(0, top.length - 2)), + w: main.node().clientWidth, h: main.node().clientHeight + }; } /** @summary change frame state */ changeFrameState(frame, newstate, no_redraw) { const main = d3_select(frame.parentNode), - state = main.property('state'), - top = this.selectDom().select('.jsroot_flex_top'); + state = main.property('state'), + top = this.selectDom().select('.jsroot_flex_top'); if (state === newstate) return false; if (state === 'normal') - main.property('original_style', main.attr('style')); + main.property('original_style', main.attr('style')); // clear any previous settings top.style('overflow', null); @@ -847,9 +866,9 @@ class FlexibleDisplay extends MDIDisplay { const btn = d3_select(this); if (((d.t === 'minimize') && (newstate === 'min')) || ((d.t === 'maximize') && (newstate === 'max'))) - btn.html('▞').attr('title', 'restore'); + btn.text('\u259E').attr('title', 'restore'); else - btn.html(d.n).attr('title', d.t); + btn.text(d.n).attr('title', d.t); }); main.property('state', newstate); @@ -858,14 +877,16 @@ class FlexibleDisplay extends MDIDisplay { // adjust position of new minified rect if (newstate === 'min') { const rect = this.getFrameRect(frame), - top = this.selectDom().select('.jsroot_flex_top'), ww = top.node().clientWidth, hh = top.node().clientHeight, arr = [], step = 4, crossX = (r1, r2) => ((r1.x <= r2.x) && (r1.x + r1.w >= r2.x)) || ((r2.x <= r1.x) && (r2.x + r2.w >= r1.x)), crossY = (r1, r2) => ((r1.y <= r2.y) && (r1.y + r1.h >= r2.y)) || ((r2.y <= r1.y) && (r2.y + r2.h >= r1.y)); - this.forEachFrame(f => { if ((f!==frame) && (this.getFrameState(f) === 'min')) arr.push(this.getFrameRect(f)); }); + this.forEachFrame(f => { + if ((f !== frame) && (this.getFrameState(f) === 'min')) + arr.push(this.getFrameRect(f)); + }); rect.y = hh; do { @@ -875,18 +896,22 @@ class FlexibleDisplay extends MDIDisplay { arr.forEach(r => { if (crossY(r, rect)) { maxx = Math.max(maxx, r.x + r.w + step); - if (crossX(r, rect)) iscrossed = true; + if (crossX(r, rect)) + iscrossed = true; } }); - if (iscrossed) rect.x = maxx; + if (iscrossed) + rect.x = maxx; } while ((rect.x + rect.w > ww - step) && (rect.y > 0)); - if (rect.y < 0) { rect.x = step; rect.y = hh - rect.h - step; } + if (rect.y < 0) { + rect.x = step; + rect.y = hh - rect.h - step; + } main.style('left', rect.x + 'px').style('top', rect.y + 'px'); } else if (!no_redraw) resize(frame); - return true; } @@ -894,13 +919,13 @@ class FlexibleDisplay extends MDIDisplay { * @private */ _clickButton(btn) { const kind = d3_select(btn).datum(), - main = d3_select(btn.parentNode.parentNode), - frame = main.select('.jsroot_flex_draw').node(); + main = d3_select(btn.parentNode.parentNode), + frame = main.select('.jsroot_flex_draw').node(); if (kind.t === 'close') { this.cleanupFrame(frame); main.remove(); - this.activateFrame('last'); // set active as last non-minfied window + this.activateFrame('last'); // set active as last non-minified window return; } @@ -930,34 +955,37 @@ class FlexibleDisplay extends MDIDisplay { } const w = top.node().clientWidth, - h = top.node().clientHeight, - main = top.append('div'); + h = top.node().clientHeight, + main = top.append('div'); main.html('<div class=\'jsroot_flex_header\' style=\'height: 23px; overflow: hidden; background-color: lightblue\'>' + - `<p style='margin: 1px; float: left; font-size: 14px; padding-left: 5px'>${title}</p></div>`+ - `<div id='${this.frameid}_cont${this.cnt}' class='jsroot_flex_draw' style='overflow: hidden; width: 100%; height: calc(100% - 24px); background: white'></div>`+ + '<p style=\'margin: 1px; float: left; font-size: 14px; padding-left: 5px\'></p></div>' + + `<div id='${this.frameid}_cont${this.cnt}' class='jsroot_flex_draw' style='overflow: hidden; width: 100%; height: calc(100% - 24px); background: white'></div>` + '<div class=\'jsroot_flex_resize\' style=\'position: absolute; right: 3px; bottom: 1px; overflow: hidden; cursor: nwse-resize\'>◿</div>'); + main.select('.jsroot_flex_header p').text(title); + main.attr('class', 'jsroot_flex_frame') - .style('position', 'absolute') - .style('left', Math.round(w * (this.cnt % 5)/10) + 'px') - .style('top', Math.round(h * (this.cnt % 5)/10) + 'px') - .style('width', Math.round(w * 0.58) + 'px') - .style('height', Math.round(h * 0.58) + 'px') - .style('border', '1px solid black') - .style('box-shadow', '1px 1px 2px 2px #aaa') - .property('state', 'normal') - .select('.jsroot_flex_header') - .on('click', function() { mdi.activateFrame(d3_select(this.parentNode).select('.jsroot_flex_draw').node()); }) - .selectAll('button') - .data([{ n: '✕', t: 'close' }, { n: '▔', t: 'maximize' }, { n: '▁', t: 'minimize' }]) - .enter() - .append('button') - .attr('type', 'button') - .attr('style', 'float: right; padding: 0; width: 1.4em; text-align: center; font-size: 10px; margin-top: 2px; margin-right: 4px') - .attr('title', d => d.t) - .html(d => d.n) - .on('click', function() { mdi._clickButton(this); }); + .style('position', 'absolute') + .style('left', Math.round(w * (this.cnt % 5) / 10) + 'px') + .style('top', Math.round(h * (this.cnt % 5) / 10) + 'px') + .style('width', Math.round(w * 0.58) + 'px') + .style('height', Math.round(h * 0.58) + 'px') + .style('border', '1px solid black') + .style('box-shadow', '1px 1px 2px 2px #aaa') + .property('state', 'normal') + .select('.jsroot_flex_header') + .on('contextmenu', evnt => mdi.showContextMenu(evnt, true)) + .on('click', function() { mdi.activateFrame(d3_select(this.parentNode).select('.jsroot_flex_draw').node()); }) + .selectAll('button') + .data([{ n: '\u2715', t: 'close' }, { n: '\u2594', t: 'maximize' }, { n: '\u2581', t: 'minimize' }]) + .enter() + .append('button') + .attr('type', 'button') + .attr('style', 'float: right; padding: 0; width: 1.4em; text-align: center; font-size: 10px; margin-top: 2px; margin-right: 4px') + .attr('title', d => d.t) + .text(d => d.n) + .on('click', function() { mdi._clickButton(this); }); let moving_frame = null, moving_div = null, doing_move = false, current = []; const drag_object = d3_drag().subject(Object); @@ -965,39 +993,43 @@ class FlexibleDisplay extends MDIDisplay { if (evnt.sourceEvent.target.type === 'button') return mdi._clickButton(evnt.sourceEvent.target); - if (detectRightButton(evnt.sourceEvent)) return; + if (detectRightButton(evnt.sourceEvent)) + return; - const main = d3_select(this.parentNode); - if (!main.classed('jsroot_flex_frame') || (main.property('state') === 'max')) return; + const mframe = d3_select(this.parentNode); + if (!mframe.classed('jsroot_flex_frame') || (mframe.property('state') === 'max')) + return; doing_move = !d3_select(this).classed('jsroot_flex_resize'); - if (!doing_move && (main.property('state') === 'min')) return; + if (!doing_move && (mframe.property('state') === 'min')) + return; - mdi.activateFrame(main.select('.jsroot_flex_draw').node()); + mdi.activateFrame(mframe.select('.jsroot_flex_draw').node()); - moving_div = top.append('div').attr('style', main.attr('style')).style('border', '2px dotted #00F'); + moving_div = top.append('div').attr('style', mframe.attr('style')).style('border', '2px dotted #00F'); - if (main.property('state') === 'min') { - moving_div.style('width', main.node().clientWidth + 'px') - .style('height', main.node().clientHeight + 'px'); + if (mframe.property('state') === 'min') { + moving_div.style('width', mframe.node().clientWidth + 'px') + .style('height', mframe.node().clientHeight + 'px'); } evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); - moving_frame = main; + moving_frame = mframe; current = []; - }).on('drag', function(evnt) { - if (!moving_div) return; + }).on('drag', evnt => { + if (!moving_div) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); const changeProp = (i, name, dd) => { if (i >= current.length) { const v = moving_div.style(name); - current[i] = parseInt(v.slice(0, v.length-2)); + current[i] = parseInt(v.slice(0, v.length - 2)); } current[i] += dd; - moving_div.style(name, Math.max(0, current[i])+'px'); + moving_div.style(name, Math.max(0, current[i]) + 'px'); }; if (doing_move) { changeProp(0, 'left', evnt.dx); @@ -1006,8 +1038,9 @@ class FlexibleDisplay extends MDIDisplay { changeProp(0, 'width', evnt.dx); changeProp(1, 'height', evnt.dy); } - }).on('end', function(evnt) { - if (!moving_div) return; + }).on('end', evnt => { + if (!moving_div) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); if (doing_move) { @@ -1062,45 +1095,56 @@ class FlexibleDisplay extends MDIDisplay { const arr = []; this.forEachFrame(frame => { const state = this.getFrameState(frame); - if (state === 'min') return; - if (state === 'max') this.changeFrameState(frame, 'normal', true); + if (state === 'min') + return; + if (state === 'max') + this.changeFrameState(frame, 'normal', true); arr.push(frame); }); - if (arr.length === 0) return; + if (!arr.length) + return; const top = this.selectDom(), w = top.node().clientWidth, h = top.node().clientHeight, - dx = Math.min(40, Math.round(w*0.4/arr.length)), - dy = Math.min(40, Math.round(h*0.4/arr.length)); + dx = Math.min(40, Math.round(w * 0.4 / arr.length)), + dy = Math.min(40, Math.round(h * 0.4 / arr.length)); let nx = Math.ceil(Math.sqrt(arr.length)), ny = nx; // calculate number of divisions for 'tile' sorting - if ((nx > 1) && (nx*(nx-1) >= arr.length)) - if (w > h) ny--; else nx--; + if ((nx > 1) && (nx * (nx - 1) >= arr.length)) { + if (w > h) + ny--; + else + nx--; + } arr.forEach((frame, i) => { const main = d3_select(frame.parentNode); if (kind === 'cascade') { - main.style('left', (i*dx) + 'px') - .style('top', (i*dy) + 'px') + main.style('left', (i * dx) + 'px') + .style('top', (i * dy) + 'px') .style('width', Math.round(w * 0.58) + 'px') .style('height', Math.round(h * 0.58) + 'px'); } else { - main.style('left', Math.round(w/nx*(i%nx)) + 'px') - .style('top', Math.round(h/ny*((i-i%nx)/nx)) + 'px') - .style('width', Math.round(w/nx - 4) + 'px') - .style('height', Math.round(h/ny - 4) + 'px'); + main.style('left', Math.round(w / nx * (i % nx)) + 'px') + .style('top', Math.round(h / ny * ((i - i % nx) / nx)) + 'px') + .style('width', Math.round(w / nx - 4) + 'px') + .style('height', Math.round(h / ny - 4) + 'px'); } resize(frame); }); } /** @summary context menu */ - showContextMenu(evnt) { - // handle context menu only for MDI area - if ((evnt.target.getAttribute('class') !== 'jsroot_flex_top') || (this.numDraw() === 0)) return; + showContextMenu(evnt, is_header) { + // no context menu for no windows + if (this.numDraw() === 0) + return; + // handle context menu only for MDI area or for window header + if (!is_header && evnt.target.getAttribute('class') !== 'jsroot_flex_top') + return; evnt.preventDefault(); @@ -1108,14 +1152,15 @@ class FlexibleDisplay extends MDIDisplay { let nummin = 0; this.forEachFrame(f => { arr.push(f); - if (this.getFrameState(f) === 'min') nummin++; + if (this.getFrameState(f) === 'min') + nummin++; }); const active = this.getActiveFrame(); arr.sort((f1, f2) => (d3_select(f1).property('frame_cnt') < d3_select(f2).property('frame_cnt') ? -1 : 1)); createMenu(evnt, this).then(menu => { - menu.add('header:Flex'); + menu.header('Flex'); menu.add('Cascade', () => this.sortFrames('cascade'), 'Cascade frames'); menu.add('Tile', () => this.sortFrames('tile'), 'Tile all frames'); if (nummin < arr.length) @@ -1123,15 +1168,16 @@ class FlexibleDisplay extends MDIDisplay { if (nummin > 0) menu.add('Show all', () => this.showAll(), 'Restore minimized frames'); menu.add('Close all', () => this.closeAllFrames()); - menu.add('separator'); - - arr.forEach((f, i) => menu.addchk((f===active), ((this.getFrameState(f) === 'min') ? '[min] ' : '') + d3_select(f).attr('frame_title'), i, - arg => { - const frame = arr[arg]; - if (this.getFrameState(frame) === 'min') - this.changeFrameState(frame, 'normal'); - this.activateFrame(frame); - })); + menu.separator(); + + arr.forEach((f, i) => { + menu.addchk((f === active), ((this.getFrameState(f) === 'min') ? '[min] ' : '') + d3_select(f).attr('frame_title'), i, arg => { + const frame = arr[arg]; + if (this.getFrameState(frame) === 'min') + this.changeFrameState(frame, 'normal'); + this.activateFrame(frame); + }); + }); menu.show(); }); @@ -1152,8 +1198,8 @@ class BatchDisplay extends MDIDisplay { constructor(width, height, jsdom_body) { super('$batch$'); this.frames = []; // array of configured frames - this.width = width || 1200; - this.height = height || 800; + this.width = width || settings.CanvasWidth; + this.height = height || settings.CanvasHeight; this.jsdom_body = jsdom_body || d3_select('body'); // d3 body handle } @@ -1179,42 +1225,83 @@ class BatchDisplay extends MDIDisplay { return this.afterCreateFrame(frame.node()); } + /** @summary Create final frame */ + createFinalBatchFrame() { + const cnt = this.numFrames(), prs = []; + + for (let n = 0; n < cnt; ++n) { + const json = this.makeJSON(n, 1, true); + if (json) + d3_select(this.frames[n]).text('json:' + btoa_func(json)); + else + prs.push(this.makeSVG(n, true)); + } + + return Promise.all(prs).then(() => { + this.jsdom_body.append('div') + .attr('id', 'jsroot_batch_final') + .text(`${cnt}`); + }); + } + /** @summary Returns number of created frames */ numFrames() { return this.frames.length; } /** @summary returns JSON representation if any * @desc Now works only for inspector, can be called once */ - makeJSON(id, spacing) { + makeJSON(id, spacing, keep_frame) { const frame = this.frames[id]; - if (!frame) return; + if (!frame) + return; const obj = d3_select(frame).property('_json_object_'); if (obj) { d3_select(frame).property('_json_object_', null); cleanup(frame); - d3_select(frame).remove(); + if (!keep_frame) + d3_select(frame).remove(); return toJSON(obj, spacing); } } - /** @summary Create SVG for specified frame id */ - makeSVG(id) { + /** @summary Create SVG for specified frame id - used in testing */ + makeSVG(id, keep_frame) { const frame = this.frames[id]; - if (!frame) return; - const main = d3_select(frame); - main.select('svg') - .attr('xmlns', nsSVG) - .attr('width', this.width) - .attr('height', this.height) - .attr('title', null).attr('style', null).attr('class', null).attr('x', null).attr('y', null); + if (!frame) + return; + const main = d3_select(frame), + mainsvg = main.select('svg'); + if (mainsvg.empty()) + return; + + const style_filter = mainsvg.style('filter'); + + mainsvg.attr('xmlns', nsSVG) + .attr('title', null).attr('style', null).attr('class', null).attr('x', null).attr('y', null); + + if (!mainsvg.attr('width') && !mainsvg.attr('height')) + mainsvg.attr('width', this.width).attr('height', this.height); + + if (style_filter) + mainsvg.style('filter', style_filter); function clear_element() { const elem = d3_select(this); - if (elem.style('display') === 'none') elem.remove(); + if (elem.style('display') === 'none') + elem.remove(); } main.selectAll('g.root_frame').each(clear_element); main.selectAll('svg').each(clear_element); + if (internals.batch_png) { + return svgToImage(compressSVG(main.html()), 'png').then(href => { + d3_select(this.frames[id]).text('png:' + href); + }); + } + + if (keep_frame) + return true; + const svg = compressSVG(main.html()); cleanup(frame); @@ -1235,10 +1322,23 @@ class BatchDisplay extends MDIDisplay { class BrowserLayout { + #float_left; + #float_top; + #max_left; + #max_top; + #float_width; + #float_height; + #max_width; + #max_height; + #hsepar_position; + #vsepar_position; + #hsepar_move; + #vsepar_move; + /** @summary Constructor */ constructor(id, hpainter, objpainter) { this.gui_div = id; - this.hpainter = hpainter; // painter for brwoser area (if any) + this.hpainter = hpainter; // painter for browser area (if any) this.objpainter = objpainter; // painter for object area (if any) this.browser_kind = null; // should be 'float' or 'fix' } @@ -1269,23 +1369,23 @@ class BrowserLayout { /** @summary Create or update CSS style */ createStyle() { const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', - title_color = settings.DarkMode ? '#ccc' : 'inherit', - text_color = settings.DarkMode ? '#ddd' : 'inherit', - input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; + title_color = settings.DarkMode ? '#ccc' : 'inherit', + text_color = settings.DarkMode ? '#ddd' : 'inherit', + input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; injectStyle( - '.jsroot_browser { pointer-events: none; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; margin: 0px; border: 0px; overflow: hidden; }'+ - `.jsroot_draw_area { background-color: ${bkgr_color}; overflow: hidden; margin: 0px; border: 0px; }`+ - `.jsroot_browser_area { color: ${text_color}; background-color: ${bkgr_color}; font-size: 12px; font-family: Verdana; pointer-events: all; box-sizing: initial; }`+ - `.jsroot_browser_area input { ${input_style} }`+ - `.jsroot_browser_area select { ${input_style} }`+ - `.jsroot_browser_title { font-family: Verdana; font-size: 20px; color: ${title_color}; }`+ - '.jsroot_browser_btns { pointer-events: all; display: flex; flex-direction: column; }'+ - '.jsroot_browser_area p { margin-top: 5px; margin-bottom: 5px; white-space: nowrap; }'+ - '.jsroot_browser_hierarchy { flex: 1; margin-top: 2px; }'+ - `.jsroot_status_area { background-color: ${bkgr_color}; overflow: hidden; font-size: 12px; font-family: Verdana; pointer-events: all; }`+ + '.jsroot_browser { pointer-events: none; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; margin: 0px; border: 0px; overflow: hidden; }' + + `.jsroot_draw_area { background-color: ${bkgr_color}; overflow: hidden; margin: 0px; border: 0px; }` + + `.jsroot_browser_area { color: ${text_color}; background-color: ${bkgr_color}; font-size: 12px; font-family: Verdana; pointer-events: all; box-sizing: initial; }` + + `.jsroot_browser_area input { ${input_style} }` + + `.jsroot_browser_area select { ${input_style} }` + + `.jsroot_browser_title { font-family: Verdana; font-size: 20px; color: ${title_color}; }` + + '.jsroot_browser_btns { pointer-events: all; display: flex; flex-direction: column; }' + + '.jsroot_browser_area p { margin-top: 5px; margin-bottom: 5px; white-space: nowrap; }' + + '.jsroot_browser_hierarchy { flex: 1; margin-top: 2px; }' + + `.jsroot_status_area { background-color: ${bkgr_color}; overflow: hidden; font-size: 12px; font-family: Verdana; pointer-events: all; }` + '.jsroot_browser_resize { position: absolute; right: 3px; bottom: 3px; margin-bottom: 0px; margin-right: 0px; opacity: 0.5; cursor: se-resize; z-index: 1; }', - this.main().node(), 'browser_layout_style'); + this.main().node(), 'browser_layout_style'); } /** @summary method used to create basic elements @@ -1307,7 +1407,8 @@ class BrowserLayout { /** @summary Create buttons in the layout */ createBrowserBtns() { const br = this.browser(); - if (br.empty()) return; + if (br.empty()) + return; let btns = br.select('.jsroot_browser_btns'); if (btns.empty()) { btns = br.append('div') @@ -1326,7 +1427,8 @@ class BrowserLayout { /** @summary Set browser content */ setBrowserContent(guiCode) { const main = this.browser(); - if (main.empty()) return; + if (main.empty()) + return; main.insert('div', '.jsroot_browser_btns').classed('jsroot_browser_area', true) .style('position', 'absolute').style('left', '0px').style('top', '0px').style('bottom', '0px').style('width', '250px') @@ -1345,7 +1447,8 @@ class BrowserLayout { /** @summary Delete content */ deleteContent(keep_status) { const main = this.browser(); - if (main.empty()) return; + if (main.empty()) + return; if (!keep_status) this.createStatusLine(0, 'delete'); @@ -1376,8 +1479,9 @@ class BrowserLayout { * @desc Title also used for dragging of the float browser */ setBrowserTitle(title) { const main = this.browser(), - elem = !main.empty() ? main.select('.jsroot_browser_title') : null; - if (elem) elem.text(title).style('cursor', this.browser_kind === 'flex' ? 'move' : null); + elem = !main.empty() ? main.select('.jsroot_browser_title') : null; + if (elem) + elem.text(title).style('cursor', this.browser_kind === 'flex' ? 'move' : null); return elem; } @@ -1385,8 +1489,10 @@ class BrowserLayout { * @desc used together with browser buttons */ toggleKind(browser_kind) { if (this.browser_visible !== 'changing') { - if (browser_kind === this.browser_kind) this.toggleBrowserVisisbility(); - else this.toggleBrowserKind(browser_kind); + if (browser_kind === this.browser_kind) + this.toggleBrowserVisisbility(); + else + this.toggleBrowserKind(browser_kind); } } @@ -1397,15 +1503,18 @@ class BrowserLayout { return ''; const id = this.gui_div + '_status', - line = d3_select('#'+id), - is_visible = !line.empty(); + line = d3_select('#' + id), + is_visible = !line.empty(); if (mode === 'toggle') mode = !is_visible; - else if (mode === 'delete') { - mode = false; height = 0; delete this.status_layout; + else if (mode === 'delete') { + mode = false; + height = 0; + delete this.status_layout; } else if (mode === undefined) { - mode = true; this.status_layout = 'app'; + mode = true; + this.status_layout = 'app'; } if (is_visible) { @@ -1441,21 +1550,21 @@ class BrowserLayout { .style('margin', 0).style('border', 0); const separ_color = settings.DarkMode ? 'grey' : 'azure', - hsepar = main.insert('div', '.jsroot_browser_area') + hsepar = main.insert('div', '.jsroot_browser_area') .classed('jsroot_h_separator', true) .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; left: ${left_pos}; right: 0; bottom: 20px; height: 5px; cursor: ns-resize;`), - drag_move = d3_drag().on('start', () => { - this._hsepar_move = this._hsepar_position; - hsepar.style('background-color', 'grey'); - }).on('drag', evnt => { - this._hsepar_move -= evnt.dy; // hsepar is position from bottom - this.adjustSeparators(null, Math.max(5, Math.round(this._hsepar_move))); - }).on('end', () => { - delete this._hsepar_move; - hsepar.style('background-color', null); - this.checkResize(); - }); + drag_move = d3_drag().on('start', () => { + this.#hsepar_move = this.#hsepar_position; + hsepar.style('background-color', 'grey'); + }).on('drag', evnt => { + this.#hsepar_move -= evnt.dy; // hsepar is position from bottom + this.adjustSeparators(null, Math.max(5, Math.round(this.#hsepar_move))); + }).on('end', () => { + this.#hsepar_move = undefined; + hsepar.style('background-color', null); + this.checkResize(); + }); hsepar.call(drag_move); @@ -1463,7 +1572,8 @@ class BrowserLayout { if (browser.touches && !main.on('touchmove')) main.on('touchmove', () => {}); - if (!height || isStr(height)) height = this.last_hsepar_height || 20; + if (!height || isStr(height)) + height = this.last_hsepar_height || 20; this.adjustSeparators(null, height, true); @@ -1475,8 +1585,8 @@ class BrowserLayout { const frame_titles = ['object name', 'object title', 'mouse coordinates', 'object info']; for (let k = 0; k < 4; ++k) { d3_select(this.status_layout.getGridFrame(k)) - .attr('title', frame_titles[k]).style('overflow', 'hidden') - .append('label').attr('style', 'margin: 3px; margin-left: 5px; font-size: 14px; vertical-align: middle; white-space: nowrap;'); + .attr('title', frame_titles[k]).style('overflow', 'hidden').style('display', 'flex').style('align-items', 'center') + .append('label').attr('style', 'margin: 5px 5px 5px 3px; font-size: 14px; white-space: nowrap;'); } internals.showStatus = this.status_handler = this.showStatus.bind(this); @@ -1486,15 +1596,16 @@ class BrowserLayout { /** @summary Adjust separator positions */ adjustSeparators(vsepar, hsepar, redraw, first_time) { - if (!this.gui_div) return; + if (!this.gui_div) + return; const main = this.browser(), w = 5; if ((hsepar === null) && first_time && !main.select('.jsroot_h_separator').empty()) { // if separator set for the first time, check if status line present hsepar = main.select('.jsroot_h_separator').style('bottom'); - if (isStr(hsepar) && (hsepar.length > 2) && (hsepar.indexOf('px') === hsepar.length-2)) - hsepar = hsepar.slice(0, hsepar.length-2); + if (isStr(hsepar) && (hsepar.length > 2) && (hsepar.indexOf('px') === hsepar.length - 2)) + hsepar = hsepar.slice(0, hsepar.length - 2); else hsepar = null; } @@ -1505,63 +1616,70 @@ class BrowserLayout { let hlimit = 0; if (!elem.empty()) { - if (hsepar < 5) hsepar = 5; + if (hsepar < 5) + hsepar = 5; const maxh = main.node().clientHeight - w; if (maxh > 0) { - if (hsepar < 0) hsepar += maxh; - if (hsepar > maxh) hsepar = maxh; + if (hsepar < 0) + hsepar += maxh; + if (hsepar > maxh) + hsepar = maxh; } this.last_hsepar_height = hsepar; - elem.style('bottom', hsepar+'px').style('height', w+'px'); - this.status().style('height', hsepar+'px'); + elem.style('bottom', hsepar + 'px').style('height', w + 'px'); + this.status().style('height', hsepar + 'px'); hlimit = hsepar + w; } - this._hsepar_position = hsepar; + this.#hsepar_position = hsepar; this.drawing().style('bottom', `${hlimit}px`); } if (vsepar !== null) { vsepar = Math.max(50, Number.parseInt(vsepar)); - this._vsepar_position = vsepar; - main.select('.jsroot_browser_area').style('width', (vsepar-5)+'px'); - this.drawing().style('left', (vsepar+w)+'px'); - main.select('.jsroot_h_separator').style('left', (vsepar+w)+'px'); - this.status().style('left', (vsepar+w)+'px'); - main.select('.jsroot_v_separator').style('left', vsepar+'px').style('width', w+'px'); + this.#vsepar_position = vsepar; + main.select('.jsroot_browser_area').style('width', (vsepar - 5) + 'px'); + this.drawing().style('left', (vsepar + w) + 'px'); + main.select('.jsroot_h_separator').style('left', (vsepar + w) + 'px'); + this.status().style('left', (vsepar + w) + 'px'); + main.select('.jsroot_v_separator').style('left', vsepar + 'px').style('width', w + 'px'); } - if (redraw) this.checkResize(); + if (redraw) + this.checkResize(); } /** @summary Show status information inside special fields of browser layout */ showStatus(...msgs) { - if (!isObject(this.status_layout) || !isFunc(this.status_layout.getGridFrame)) return; + if (!isObject(this.status_layout) || !isFunc(this.status_layout.getGridFrame)) + return; let maxh = 0; for (let n = 0; n < 4; ++n) { const lbl = this.status_layout.getGridFrame(n).querySelector('label'); maxh = Math.max(maxh, lbl.clientHeight); - lbl.innerHTML = msgs[n] || ''; + lbl.innerText = msgs[n] || ''; } if (!this.status_layout.first_check) { this.status_layout.first_check = true; - if ((maxh > 5) && ((maxh > this.last_hsepar_height) || (maxh < this.last_hsepar_height+5))) + if ((maxh > 5) && ((maxh > this.last_hsepar_height) || (maxh < this.last_hsepar_height + 5))) this.adjustSeparators(null, maxh, true); } } /** @summary Toggle browser visibility */ toggleBrowserVisisbility(fast_close) { - if (!this.gui_div || isStr(this.browser_visible)) return; - - const main = this.browser(), area = main.select('.jsroot_browser_area'); + if (!this.gui_div || isStr(this.browser_visible)) + return; - if (area.empty()) return; + const main = this.browser(), + area = main.select('.jsroot_browser_area'); + if (area.empty()) + return; const vsepar = main.select('.jsroot_v_separator'), drawing = d3_select(`#${this.gui_div}_drawing`); @@ -1570,7 +1688,8 @@ class BrowserLayout { tgt_drawing = area.property('last_drawing'); if (!this.browser_visible) { - if (fast_close) return; + if (fast_close) + return; area.property('last_left', null).property('last_vsepar', null).property('last_drawing', null); } else { area.property('last_left', area.style('left')); @@ -1582,8 +1701,8 @@ class BrowserLayout { tgt = (-area.node().clientWidth - 10) + 'px'; const mainw = main.node().clientWidth; - if (vsepar.empty() && (area.node().offsetLeft > mainw/2)) - tgt = (mainw+10) + 'px'; + if (vsepar.empty() && (area.node().offsetLeft > mainw / 2)) + tgt = (mainw + 10) + 'px'; tgt_separ = '-10px'; tgt_drawing = '0px'; @@ -1594,9 +1713,11 @@ class BrowserLayout { this.browser_visible = 'changing'; area.transition().style('left', tgt).duration(_duration).on('end', () => { - if (fast_close) return; + if (fast_close) + return; this.browser_visible = visible_at_the_end; - if (visible_at_the_end) this.setButtonsPosition(); + if (visible_at_the_end) + this.setButtonsPosition(); }); if (!visible_at_the_end) @@ -1615,10 +1736,12 @@ class BrowserLayout { /** @summary Adjust browser size */ adjustBrowserSize(onlycheckmax) { - if (!this.gui_div || (this.browser_kind !== 'float')) return; + if (!this.gui_div || (this.browser_kind !== 'float')) + return; const main = this.browser(); - if (main.empty()) return; + if (main.empty()) + return; const area = main.select('.jsroot_browser_area'), cont = main.select('.jsroot_browser_hierarchy'), @@ -1630,20 +1753,24 @@ class BrowserLayout { return; } - if (chld.empty()) return; + if (chld.empty()) + return; + const h1 = cont.node().clientHeight, h2 = chld.node().clientHeight; - - if ((h2 !== undefined) && (h2 < h1*0.7)) area.style('bottom', ''); + if ((h2 !== undefined) && (h2 < h1 * 0.7)) + area.style('bottom', ''); } /** @summary Set buttons position */ setButtonsPosition() { - if (!this.gui_div) return; + if (!this.gui_div) + return; const main = this.browser(), btns = main.select('.jsroot_browser_btns'); - if (btns.empty()) return; + if (btns.empty()) + return; let top = 7, left = 7; if (this.browser_visible) { @@ -1670,12 +1797,12 @@ class BrowserLayout { area = main.select('.jsroot_browser_area'); if (this.browser_kind === 'float') { - area.style('bottom', '0px') - .style('top', '0px') - .style('width', '') - .style('height', '') - .classed('jsroot_float_browser', false) - .style('border', null); + area.style('bottom', '0px') + .style('top', '0px') + .style('width', '') + .style('height', '') + .classed('jsroot_float_browser', false) + .style('border', null); } else if (this.browser_kind === 'fix') { main.select('.jsroot_v_separator').remove(); area.style('left', '0px'); @@ -1698,57 +1825,59 @@ class BrowserLayout { const drag_move = d3_drag().on('start', () => { const sl = area.style('left'), st = area.style('top'); - this._float_left = parseInt(sl.slice(0, sl.length-2)); - this._float_top = parseInt(st.slice(0, st.length-2)); - this._max_left = Math.max(0, main.node().clientWidth - area.node().offsetWidth - 1); - this._max_top = Math.max(0, main.node().clientHeight - area.node().offsetHeight - 1); + this.#float_left = parseInt(sl.slice(0, sl.length - 2)); + this.#float_top = parseInt(st.slice(0, st.length - 2)); + this.#max_left = Math.max(0, main.node().clientWidth - area.node().offsetWidth - 1); + this.#max_top = Math.max(0, main.node().clientHeight - area.node().offsetHeight - 1); }).filter(evnt => { return main.select('.jsroot_browser_title').node() === evnt.target; }).on('drag', evnt => { - this._float_left += evnt.dx; - this._float_top += evnt.dy; - area.style('left', Math.min(Math.max(0, this._float_left), this._max_left) + 'px') - .style('top', Math.min(Math.max(0, this._float_top), this._max_top) + 'px'); + this.#float_left += evnt.dx; + this.#float_top += evnt.dy; + area.style('left', Math.min(Math.max(0, this.#float_left), this.#max_left) + 'px') + .style('top', Math.min(Math.max(0, this.#float_top), this.#max_top) + 'px'); this.setButtonsPosition(); - }), + }); - drag_resize = d3_drag().on('start', () => { + // eslint-disable-next-line one-var + const drag_resize = d3_drag().on('start', () => { const sw = area.style('width'); - this._float_width = parseInt(sw.slice(0, sw.length-2)); - this._float_height = area.node().clientHeight; - this._max_width = main.node().clientWidth - area.node().offsetLeft - 1; - this._max_height = main.node().clientHeight - area.node().offsetTop - 1; + this.#float_width = parseInt(sw.slice(0, sw.length - 2)); + this.#float_height = area.node().clientHeight; + this.#max_width = main.node().clientWidth - area.node().offsetLeft - 1; + this.#max_height = main.node().clientHeight - area.node().offsetTop - 1; }).on('drag', evnt => { - this._float_width += evnt.dx; - this._float_height += evnt.dy; + this.#float_width += evnt.dx; + this.#float_height += evnt.dy; - area.style('width', Math.min(Math.max(100, this._float_width), this._max_width) + 'px') - .style('height', Math.min(Math.max(100, this._float_height), this._max_height) + 'px'); + area.style('width', Math.min(Math.max(100, this.#float_width), this.#max_width) + 'px') + .style('height', Math.min(Math.max(100, this.#float_height), this.#max_height) + 'px'); this.setButtonsPosition(); }); - main.call(drag_move); - main.select('.jsroot_browser_resize').call(drag_resize); + main.call(drag_move); + main.select('.jsroot_browser_resize').call(drag_resize); - this.adjustBrowserSize(); + this.adjustBrowserSize(); } else { area.style('left', '0px').style('top', '0px').style('bottom', '0px').style('height', null); const separ_color = settings.DarkMode ? 'grey' : 'azure', vsepar = main.append('div').classed('jsroot_v_separator', true) - .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; top: 0; bottom: 0; cursor: ew-resize;`), + .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; top: 0; bottom: 0; cursor: ew-resize;`); - drag_move = d3_drag().on('start', () => { - this._vsepar_move = this._vsepar_position; + // eslint-disable-next-line one-var + const drag_move = d3_drag().on('start', () => { + this.#vsepar_move = this.#vsepar_position; vsepar.style('background-color', 'grey'); }).on('drag', evnt => { - this._vsepar_move += evnt.dx; + this.#vsepar_move += evnt.dx; this.setButtonsPosition(); - settings.BrowserWidth = Math.max(50, Math.round(this._vsepar_move)); + settings.BrowserWidth = Math.max(50, Math.round(this.#vsepar_move)); this.adjustSeparators(settings.BrowserWidth, null); }).on('end', () => { - delete this._vsepar_move; + this.#vsepar_move = undefined; vsepar.style('background-color', null); this.checkResize(); }); @@ -1757,7 +1886,7 @@ class BrowserLayout { // need to get touches events handling in drag if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); + main.on('touchmove', () => {}); this.adjustSeparators(settings.BrowserWidth, null, true, true); } diff --git a/modules/gui/lil-gui.mjs b/modules/gui/lil-gui.mjs index 04501ed1e..3e4a112c7 100644 --- a/modules/gui/lil-gui.mjs +++ b/modules/gui/lil-gui.mjs @@ -1,7 +1,7 @@ /** * lil-gui * https://lil-gui.georgealways.com - * @version 0.18.2 + * @version 0.20.0 * @author George Michael Brower * @license MIT */ @@ -11,7 +11,7 @@ */ class Controller { - constructor( parent, object, property, className, widgetTag = 'div' ) { + constructor( parent, object, property, className, elementType = 'div' ) { /** * The GUI that contains this controller. @@ -33,7 +33,7 @@ class Controller { /** * Used to determine if the controller is disabled. - * Use `controller.disable( true|false )` to modify this value + * Use `controller.disable( true|false )` to modify this value. * @type {boolean} */ this._disabled = false; @@ -55,7 +55,7 @@ class Controller { * The outermost container DOM element for this controller. * @type {HTMLElement} */ - this.domElement = document.createElement( 'div' ); + this.domElement = document.createElement( elementType ); this.domElement.classList.add( 'controller' ); this.domElement.classList.add( className ); @@ -73,11 +73,11 @@ class Controller { * The DOM element that contains the controller's "widget" (which differs by controller type). * @type {HTMLElement} */ - this.$widget = document.createElement( widgetTag ); + this.$widget = document.createElement( 'div' ); this.$widget.classList.add( 'widget' ); /** - * The DOM element that receives the disabled attribute when using disable() + * The DOM element that receives the disabled attribute when using disable(). * @type {HTMLElement} */ this.$disable = this.$widget; @@ -111,7 +111,7 @@ class Controller { * @type {string} */ this._name = name; - this.$name.innerHTML = name; + this.$name.textContent = name; return this; } @@ -273,29 +273,28 @@ class Controller { } /** - * Destroys this controller and replaces it with a new option controller. Provided as a more - * descriptive syntax for `gui.add`, but primarily for compatibility with dat.gui. + * Changes this controller into a dropdown of options. * - * Use caution, as this method will destroy old references to this controller. It will also - * change controller order if called out of sequence, moving the option controller to the end of - * the GUI. + * Calling this method on an option controller will simply update the options. However, if this + * controller was not already an option controller, old references to this controller are + * destroyed, and a new controller is added to the end of the GUI. * @example * // safe usage * - * gui.add( object1, 'property' ).options( [ 'a', 'b', 'c' ] ); - * gui.add( object2, 'property' ); + * gui.add( obj, 'prop1' ).options( [ 'a', 'b', 'c' ] ); + * gui.add( obj, 'prop2' ).options( { Big: 10, Small: 1 } ); + * gui.add( obj, 'prop3' ); * * // danger * - * const c = gui.add( object1, 'property' ); - * gui.add( object2, 'property' ); - * - * c.options( [ 'a', 'b', 'c' ] ); - * // controller is now at the end of the GUI even though it was added first + * const ctrl1 = gui.add( obj, 'prop1' ); + * gui.add( obj, 'prop2' ); * - * assert( c.parent.children.indexOf( c ) === -1 ) - * // c references a controller that no longer exists + * // calling options out of order adds a new controller to the end... + * const ctrl2 = ctrl1.options( [ 'a', 'b', 'c' ] ); * + * // ...and ctrl1 now references a controller that doesn't exist + * assert( ctrl2 !== ctrl1 ) * @param {object|Array} options * @returns {Controller} */ @@ -405,10 +404,17 @@ class Controller { * @returns {this} */ setValue( value ) { - this.object[ this.property ] = value; - this._callOnChange(); - this.updateDisplay(); + + if ( this.getValue() !== value ) { + + this.object[ this.property ] = value; + this._callOnChange(); + this.updateDisplay(); + + } + return this; + } /** @@ -1174,16 +1180,24 @@ class NumberController extends Controller { _snap( value ) { - // This would be the logical way to do things, but floating point errors. - // return Math.round( value / this._step ) * this._step; + // Make the steps "start" at min or max. + let offset = 0; + if ( this._hasMin ) { + offset = this._min; + } else if ( this._hasMax ) { + offset = this._max; + } + + value -= offset; + + value = Math.round( value / this._step ) * this._step; - // Using inverse step solves a lot of them, but not all - // const inverseStep = 1 / this._step; - // return Math.round( value * inverseStep ) / inverseStep; + value += offset; - // Not happy about this, but haven't seen it break. - const r = Math.round( value / this._step ) * this._step; - return parseFloat( r.toPrecision( 15 ) ); + // Used to prevent "flyaway" decimals like 1.00000000000001 + value = parseFloat( value.toPrecision( 15 ) ); + + return value; } @@ -1225,15 +1239,6 @@ class OptionController extends Controller { this.$display = document.createElement( 'div' ); this.$display.classList.add( 'display' ); - this._values = Array.isArray( options ) ? options : Object.values( options ); - this._names = Array.isArray( options ) ? options : Object.keys( options ); - - this._names.forEach( name => { - const $option = document.createElement( 'option' ); - $option.innerHTML = name; - this.$select.appendChild( $option ); - } ); - this.$select.addEventListener( 'change', () => { this.setValue( this._values[ this.$select.selectedIndex ] ); this._callOnFinishChange(); @@ -1252,15 +1257,34 @@ class OptionController extends Controller { this.$disable = this.$select; + this.options( options ); + + } + + options( options ) { + + this._values = Array.isArray( options ) ? options : Object.values( options ); + this._names = Array.isArray( options ) ? options : Object.keys( options ); + + this.$select.replaceChildren(); + + this._names.forEach( name => { + const $option = document.createElement( 'option' ); + $option.textContent = name; + this.$select.appendChild( $option ); + } ); + this.updateDisplay(); + return this; + } updateDisplay() { const value = this.getValue(); const index = this._values.indexOf( value ); this.$select.selectedIndex = index; - this.$display.innerHTML = index === -1 ? value : this._names[ index ]; + this.$display.textContent = index === -1 ? value : this._names[ index ]; return this; } @@ -1274,6 +1298,7 @@ class StringController extends Controller { this.$input = document.createElement( 'input' ); this.$input.setAttribute( 'type', 'text' ); + this.$input.setAttribute( 'spellcheck', 'false' ); this.$input.setAttribute( 'aria-labelledby', this.$name.id ); this.$input.addEventListener( 'input', () => { @@ -1305,14 +1330,13 @@ class StringController extends Controller { } -const stylesheet = `.lil-gui { +var stylesheet = `.lil-gui { font-family: var(--font-family); font-size: var(--font-size); line-height: 1; font-weight: normal; font-style: normal; text-align: left; - background-color: var(--background-color); color: var(--text-color); user-select: none; -webkit-user-select: none; @@ -1355,6 +1379,7 @@ const stylesheet = `.lil-gui { width: var(--width, 245px); display: flex; flex-direction: column; + background: var(--background-color); } .lil-gui.root > .title { background: var(--title-background-color); @@ -1434,7 +1459,7 @@ const stylesheet = `.lil-gui { .lil-gui .controller.string input { color: var(--string-color); } -.lil-gui .controller.boolean .widget { +.lil-gui .controller.boolean { cursor: pointer; } .lil-gui .controller.color .display { @@ -1526,7 +1551,7 @@ const stylesheet = `.lil-gui { .lil-gui .controller.number .slider { width: 100%; height: var(--widget-height); - background-color: var(--widget-color); + background: var(--widget-color); border-radius: var(--widget-border-radius); padding-right: var(--slider-knob-width); overflow: hidden; @@ -1535,11 +1560,11 @@ const stylesheet = `.lil-gui { } @media (hover: hover) { .lil-gui .controller.number .slider:hover { - background-color: var(--hover-color); + background: var(--hover-color); } } .lil-gui .controller.number .slider.active { - background-color: var(--focus-color); + background: var(--focus-color); } .lil-gui .controller.number .slider.active .fill { opacity: 0.95; @@ -1563,12 +1588,11 @@ const stylesheet = `.lil-gui { .lil-gui .title { height: var(--title-height); - line-height: calc(var(--title-height) - 4px); font-weight: 600; padding: 0 var(--padding); - -webkit-tap-highlight-color: transparent; - cursor: pointer; - outline: none; + width: 100%; + text-align: left; + background: none; text-decoration-skip: objects; } .lil-gui .title:before { @@ -1645,8 +1669,10 @@ const stylesheet = `.lil-gui { border: none; } -.lil-gui input { +.lil-gui label, .lil-gui input, .lil-gui button { -webkit-tap-highlight-color: transparent; +} +.lil-gui input { border: 0; outline: none; font-family: var(--font-family); @@ -1671,24 +1697,16 @@ const stylesheet = `.lil-gui { .lil-gui input[type=text], .lil-gui input[type=number] { padding: var(--widget-padding); + -moz-appearance: textfield; } .lil-gui input[type=text]:focus, .lil-gui input[type=number]:focus { background: var(--focus-color); } -.lil-gui input::-webkit-outer-spin-button, -.lil-gui input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.lil-gui input[type=number] { - -moz-appearance: textfield; -} .lil-gui input[type=checkbox] { appearance: none; - -webkit-appearance: none; - height: var(--checkbox-size); width: var(--checkbox-size); + height: var(--checkbox-size); border-radius: var(--widget-border-radius); text-align: center; cursor: pointer; @@ -1705,31 +1723,29 @@ const stylesheet = `.lil-gui { } } .lil-gui button { - -webkit-tap-highlight-color: transparent; outline: none; cursor: pointer; font-family: var(--font-family); font-size: var(--font-size); color: var(--text-color); width: 100%; + border: none; +} +.lil-gui .controller button { height: var(--widget-height); text-transform: none; background: var(--widget-color); border-radius: var(--widget-border-radius); - border: 1px solid var(--widget-color); - text-align: center; - line-height: calc(var(--widget-height) - 4px); } @media (hover: hover) { - .lil-gui button:hover { + .lil-gui .controller button:hover { background: var(--hover-color); - border-color: var(--hover-color); } - .lil-gui button:focus { - border-color: var(--focus-color); + .lil-gui .controller button:focus { + box-shadow: inset 0 0 0 1px var(--focus-color); } } -.lil-gui button:active { +.lil-gui .controller button:active { background: var(--focus-color); } @@ -1749,6 +1765,7 @@ function _injectStyles( cssContent ) { } } + let stylesInjected = false; class GUI { @@ -1768,7 +1785,7 @@ class GUI { * * @param {number} [options.width=245] * Width of the GUI in pixels, usually set when name labels become too long. Note that you can make - * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }` + * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }`. * * @param {string} [options.title=Controls] * Name to display in the title bar. @@ -1785,7 +1802,6 @@ class GUI { * * @param {GUI} [options.parent] * Adds this GUI as a child in another GUI. Usually this is done for you by `addFolder()`. - * */ constructor( { parent, @@ -1851,19 +1867,11 @@ class GUI { * The DOM element that contains the title. * @type {HTMLElement} */ - this.$title = document.createElement( 'div' ); + this.$title = document.createElement( 'button' ); this.$title.classList.add( 'title' ); - this.$title.setAttribute( 'role', 'button' ); this.$title.setAttribute( 'aria-expanded', true ); - this.$title.setAttribute( 'tabindex', 0 ); this.$title.addEventListener( 'click', () => this.openAnimated( this._closed ) ); - this.$title.addEventListener( 'keydown', e => { - if ( e.code === 'Enter' || e.code === 'Space' ) { - e.preventDefault(); - this.$title.click(); - } - } ); // enables :active pseudo class on mobile this.$title.addEventListener( 'touchstart', () => {}, { passive: true } ); @@ -2113,7 +2121,7 @@ class GUI { /** * Opens a GUI or folder. GUI and folders are open by default. - * @param {boolean} open Pass false to close + * @param {boolean} open Pass false to close. * @returns {this} * @example * gui.open(); // open @@ -2223,7 +2231,7 @@ class GUI { * @type {string} */ this._title = title; - this.$title.innerHTML = title; + this.$title.textContent = title; return this; } @@ -2339,7 +2347,7 @@ class GUI { } /** - * Destroys all DOM elements and event listeners associated with this GUI + * Destroys all DOM elements and event listeners associated with this GUI. */ destroy() { @@ -2382,5 +2390,4 @@ class GUI { } -export default GUI; -export { BooleanController, ColorController, Controller, FunctionController, GUI, NumberController, OptionController, StringController }; +export { BooleanController, ColorController, Controller, FunctionController, GUI, NumberController, OptionController, StringController, GUI as default }; diff --git a/modules/gui/menu.mjs b/modules/gui/menu.mjs index 59af0ead8..384878147 100644 --- a/modules/gui/menu.mjs +++ b/modules/gui/menu.mjs @@ -1,12 +1,19 @@ import { settings, internals, browser, gStyle, isObject, isFunc, isStr, clTGaxis, nsSVG, kInspect, getDocument } from '../core.mjs'; -import { rgb as d3_rgb, select as d3_select } from '../d3.mjs'; +import { rgb as d3_rgb, select as d3_select, drag as d3_drag, pointer as d3_pointer } from '../d3.mjs'; import { selectgStyle, saveSettings, readSettings, saveStyle, getColorExec, changeObjectMember } from './utils.mjs'; import { getColor } from '../base/colors.mjs'; import { TAttMarkerHandler } from '../base/TAttMarkerHandler.mjs'; import { getSvgLineStyle } from '../base/TAttLineHandler.mjs'; -import { FontHandler } from '../base/FontHandler.mjs'; +import { TAttFillHandler } from '../base/TAttFillHandler.mjs'; +import { FontHandler, kArial } from '../base/FontHandler.mjs'; +import { kAxisLabels } from '../base/ObjectPainter.mjs'; -const kToFront = '__front__'; + +const kToFront = '__front__', kNoReorder = '__no_reorder', + sDfltName = 'root_ctx_menu', sDfltDlg = '_dialog', + sSub = 'sub:', sEndsub = 'endsub:', + sColumn = 'column:', sEndcolumn = 'endcolumn:', + sSeparator = 'separator', sHeader = 'header:'; /** * @summary Abstract class for creating context menu @@ -32,7 +39,7 @@ class JSRootMenu { async load() { return this; } - /** @summary Returns object with mouse event position when context menu was actiavted + /** @summary Returns object with mouse event position when context menu was activated * @desc Return object will have members 'clientX' and 'clientY' */ getEventPosition() { return this.show_evnt; } @@ -70,7 +77,8 @@ class JSRootMenu { /** @summary Add checked menu item * @param {boolean} flag - flag * @param {string} name - item name - * @param {function} func - func called when item is selected */ + * @param {function} func - func called when item is selected + * @param {string} [title] - optional title */ addchk(flag, name, arg, func, title) { let handler = func; if (isFunc(arg)) { @@ -82,6 +90,28 @@ class JSRootMenu { this.add((flag ? 'chk:' : 'unk:') + name, arg, handler, title); } + /** @summary Add sub-menu */ + sub(name, arg, func, title) { + this.add(sSub + name, arg, func, title); + } + + /** @summary Mark end of submenu */ + endsub() { this.add(sEndsub); } + + /** @summary Start column with items */ + column() { this.add(sColumn); } + + /** @summary End column with items */ + endcolumn() { this.add(sEndcolumn); } + + /** @summary Add separator */ + separator() { this.add(sSeparator); } + + /** @summary Add menu header - must be first entry */ + header(name, title) { + this.add(sHeader + name, undefined, undefined, title); + } + /** @summary Add draw sub-menu with draw options * @protected */ addDrawMenu(top_name, opts, call_back, title) { @@ -101,31 +131,46 @@ class JSRootMenu { return; } + const used = {}; + if (!without_sub) - this.add('sub:' + top_name, opts[0], call_back, title); + this.sub(top_name, opts[0], call_back, title); - for (let i = 1; i < opts.length; ++i) { - let name = opts[i] || (this._use_plain_text ? '<dflt>' : '<dflt>'), - group = i+1; + if ((opts.indexOf('') >= 0) && (!without_sub || opts[0])) + this.add(this._use_plain_text ? '<dflt>' : '<dflt>', '', call_back); + + for (let i = 0; i < opts.length; ++i) { + let name = opts[i]; + if (!name || used[name]) + continue; + used[name] = true; + + const group = []; if (opts.length > 5) { - // check if there are similar options, which can be grouped once again - while ((group < opts.length) && (opts[group].indexOf(name) === 0)) group++; + // check if there are similar options, which can be grouped again + for (let i2 = i + 1; i2 < opts.length; ++i2) { + if (opts[i2] && !used[opts[i2]] && (opts[i2].indexOf(name) === 0)) + group.push(opts[i2]); + else if (name.length < 4) + break; + } } if (without_sub) name = top_name + ' ' + name; - if (group >= i+2) { - this.add('sub:' + name, opts[i], call_back); - for (let k = i+1; k < group; ++k) - this.add(opts[k], opts[k], call_back); - this.add('endsub:'); - i = group - 1; + if (group.length) { + this.sub(name, opts[i], call_back); + group.forEach(sub => { + this.add(sub, sub, call_back); + used[sub] = true; + }); + this.endsub(); } else if (name === kInspect) { - this.add('sub:' + name, opts[i], call_back, 'Inspect object content'); + this.sub(name, opts[i], call_back, 'Inspect object content'); for (let k = 0; k < 10; ++k) this.add(k.toString(), kInspect + k, call_back, `Inspect object and expand to level ${k}`); - this.add('endsub:'); + this.endsub(); } else this.add(name, opts[i], call_back); } @@ -134,37 +179,55 @@ class JSRootMenu { const opt = isFunc(this.painter?.getDrawOpt) ? this.painter.getDrawOpt() : opts[0]; this.input('Provide draw option', opt, 'text').then(call_back); }, 'Enter draw option in dialog'); - this.add('endsub:'); + this.endsub(); } } + /** @summary Add redraw menu for the painter + * @protected */ + addRedrawMenu(painter) { + if (!painter || !isFunc(painter.redrawWith) || !isFunc(painter.getSupportedDrawOptions)) + return false; + + const opts = painter.getSupportedDrawOptions(); + + this.addDrawMenu(`Draw ${painter.getClassName()} with`, opts, arg => { + if ((arg.indexOf(kInspect) === 0) && isFunc(painter.showInspector)) + return painter.showInspector(arg); + + painter.redrawWith(arg); + }); + return true; + } + /** @summary Add color selection menu entries * @protected */ addColorMenu(name, value, set_func, fill_kind) { - if (value === undefined) return; + if (value === undefined) + return; const useid = !isStr(value); - this.add('sub:' + name, () => { + this.sub(name, () => { this.input('Enter color ' + (useid ? '(only id number)' : '(name or id)'), value, useid ? 'int' : 'text', useid ? 0 : undefined, useid ? 9999 : undefined).then(col => { const id = parseInt(col); if (Number.isInteger(id) && getColor(id)) col = getColor(id); - else - if (useid) return; + else if (useid) + return; set_func(useid ? id : col); }); }); for (let ncolumn = 0; ncolumn < 5; ++ncolumn) { - this.add('column:'); + this.column(); for (let nrow = 0; nrow < 10; nrow++) { - let n = ncolumn*10 + nrow; - if (!useid) --n; // use -1 as none color + let n = ncolumn * 10 + nrow; + if (!useid) + --n; // use -1 as none color - let col = (n < 0) ? 'none' : getColor(n); - if ((n === 0) && (fill_kind === 1)) col = 'none'; - const lbl = (n <= 0) || (col[0] !== '#') ? col : `col ${n}`, + const col = (n < 0) || ((n === 0) && (fill_kind === 1)) ? 'none' : getColor(n), + lbl = (n <= 0) || ((col[0] !== '#') && (col.indexOf('rgb') < 0)) ? col : `col ${n}`, fill = (n === 1) ? 'white' : 'black', stroke = (n === 1) ? 'red' : 'black', rect = (value === (useid ? n : col)) ? `<rect width="50" height="18" style="fill:none;stroke-width:3px;stroke:${stroke}"></rect>` : '', @@ -173,33 +236,39 @@ class JSRootMenu { this.add(svg, (useid ? n : col), res => set_func(useid ? parseInt(res) : res), 'Select color ' + col); } - this.add('endcolumn:'); - if (!this.native()) break; + this.endcolumn(); + if (!this.native()) + break; } - this.add('endsub:'); + this.endsub(); } /** @summary Add size selection menu entries * @protected */ addSizeMenu(name, min, max, step, size_value, set_func, title) { - if (size_value === undefined) return; + if (size_value === undefined) + return; let values = [], miss_current = false; if (isObject(step)) { - values = step; step = 1; + values = step; + step = 1; } else { for (let sz = min; sz <= max; sz += step) values.push(sz); } - const match = v => Math.abs(v-size_value) < (max - min)*1e-5, + const match = v => Math.abs(v - size_value) < (max - min) * 1e-5, conv = (v, more) => { - if ((v === size_value) && miss_current) more = true; - if (step >= 1) return v.toFixed(0); - if (step >= 0.1) return v.toFixed(more ? 2 : 1); + if ((v === size_value) && miss_current) + more = true; + if (step >= 1) + return v.toFixed(0); + if (step >= 0.1) + return v.toFixed(more ? 2 : 1); return v.toFixed(more ? 4 : 2); - }; + }; if (values.findIndex(match) < 0) { miss_current = true; @@ -207,9 +276,9 @@ class JSRootMenu { values = values.sort((a, b) => a > b); } - this.add('sub:' + name, () => this.input('Enter value of ' + name, conv(size_value, true), (step >= 1) ? 'int' : 'float').then(set_func), title); + this.sub(name, () => this.input('Enter value of ' + name, conv(size_value, true), (step >= 1) ? 'int' : 'float').then(set_func), title); values.forEach(v => this.addchk(match(v), conv(v), v, res => set_func((step >= 1) ? Number.parseInt(res) : Number.parseFloat(res)))); - this.add('endsub:'); + this.endsub(); } /** @summary Add palette menu entries @@ -220,14 +289,14 @@ class JSRootMenu { name = `pal ${id}`; else if (!title) title = name; - if (title) title += `, code ${id}`; - this.addchk((id === curr) || more, '<nobr>' + name + '</nobr>', id, set_func, title || name); + if (title) + title += `, code ${id}`; + this.addchk((id === curr) || more, name, id, set_func, title || name); }; - this.add('sub:Palette', () => this.input('Enter palette code [1..113]', curr, 'int', 1, 113).then(set_func)); - - this.add('column:'); + this.sub('Palette', () => this.input('Enter palette code [1..113]', curr, 'int', 1, 113).then(set_func)); + this.column(); add(57, 'Bird', 'Default color palette', (curr > 113)); add(55, 'Rainbow'); add(51, 'Deep Sea'); @@ -241,14 +310,12 @@ class JSRootMenu { add(59, '', 'Green Red Violet'); add(60, '', 'Blue Red Yellow'); add(61, 'Ocean'); - - this.add('endcolumn:'); + this.endcolumn(); if (!this.native()) - return this.add('endsub:'); - - this.add('column:'); + return this.endsub(); + this.column(); add(62, '', 'Color Printable On Grey'); add(63, 'Alpine'); add(64, 'Aquamarine'); @@ -262,10 +329,9 @@ class JSRootMenu { add(72, 'Brown Cyan'); add(73, 'CMYK'); add(74, 'Candy'); + this.endcolumn(); - this.add('endcolumn:'); - this.add('column:'); - + this.column(); add(75, 'Cherry'); add(76, 'Coffee'); add(77, '', 'Dark Rain Bow'); @@ -279,10 +345,9 @@ class JSRootMenu { add(85, 'Island'); add(86, 'Lake'); add(87, '', 'Light Temperature'); + this.endcolumn(); - this.add('endcolumn:'); - this.add('column:'); - + this.column(); add(88, '', 'Light Terrain'); add(89, 'Mint'); add(90, 'Neon'); @@ -296,10 +361,9 @@ class JSRootMenu { add(98, '', 'Sandy Terrain'); add(99, 'Sienna'); add(100, 'Solar'); + this.endcolumn(); - this.add('endcolumn:'); - this.add('column:'); - + this.column(); add(101, '', 'South West'); add(102, '', 'Starry Night'); add(103, '', 'Sunset'); @@ -313,42 +377,41 @@ class JSRootMenu { add(111, '', 'Gist Earth'); add(112, 'Viridis'); add(113, 'Cividis'); + this.endcolumn(); - this.add('endcolumn:'); - - this.add('endsub:'); + this.endsub(); } /** @summary Add rebin menu entries * @protected */ addRebinMenu(rebin_func) { - this.add('sub:Rebin', () => this.input('Enter rebin value', 2, 'int', 2).then(rebin_func)); + this.sub('Rebin', () => this.input('Enter rebin value', 2, 'int', 2).then(rebin_func)); for (let sz = 2; sz <= 7; sz++) this.add(sz.toString(), sz, res => rebin_func(parseInt(res))); - this.add('endsub:'); + this.endsub(); } /** @summary Add selection menu entries * @param {String} name - name of submenu * @param {Array} values - array of string entries used as list for selection - * @param {String|Number} value - currently elected value, either name or index + * @param {String|Number} value - currently selected value, either name or index * @param {Function} set_func - function called when item selected, either name or index depending from value parameter + * @param {String} [title] - optional title for menu items * @protected */ - addSelectMenu(name, values, value, set_func) { + addSelectMenu(name, values, value, set_func, title) { const use_number = (typeof value === 'number'); - this.add('sub:' + name); + this.sub(name, undefined, undefined, title); for (let n = 0; n < values.length; ++n) this.addchk(use_number ? (n === value) : (values[n] === value), values[n], use_number ? n : values[n], res => set_func(use_number ? Number.parseInt(res) : res)); - this.add('endsub:'); + this.endsub(); } /** @summary Add RColor selection menu entries * @protected */ addRColorMenu(name, value, set_func) { - // if (value === undefined) return; const colors = ['default', 'black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; - this.add('sub:' + name, () => { + this.sub(name, () => { this.input('Enter color name - empty string will reset color', value).then(set_func); }); let fillcol = 'black'; @@ -367,20 +430,21 @@ class JSRootMenu { const svg = `<svg width='100' height='18' style='margin:0px;${bkgr}'><text x='4' y='12' style='font-size:12px' fill='${fillcol}'>${coltxt}</text></svg>`; this.addchk(match, svg, coltxt, res => set_func(res === 'default' ? null : res)); } - this.add('endsub:'); + this.endsub(); } /** @summary Add items to change RAttrText * @protected */ addRAttrTextItems(fontHandler, opts, set_func) { - if (!opts) opts = {}; + if (!opts) + opts = {}; this.addRColorMenu('color', fontHandler.color, value => set_func({ name: 'color', value })); if (fontHandler.scaled) - this.addSizeMenu('size', 0.01, 0.10, 0.01, fontHandler.size /fontHandler.scale, value => set_func({ name: 'size', value })); + this.addSizeMenu('size', 0.01, 0.10, 0.01, fontHandler.size / fontHandler.scale, value => set_func({ name: 'size', value })); else this.addSizeMenu('size', 6, 20, 2, fontHandler.size, value => set_func({ name: 'size', value })); - this.addSelectMenu('family', ['Arial', 'Times New Roman', 'Courier New', 'Symbol'], fontHandler.name, value => set_func({ name: 'font_family', value })); + this.addSelectMenu('family', [kArial, 'Times New Roman', 'Courier New', 'Symbol'], fontHandler.name, value => set_func({ name: 'font_family', value })); this.addSelectMenu('style', ['normal', 'italic', 'oblique'], fontHandler.style || 'normal', res => set_func({ name: 'font_style', value: res === 'normal' ? null : res })); @@ -395,38 +459,54 @@ class JSRootMenu { /** @summary Add line style menu * @private */ addLineStyleMenu(name, value, set_func) { - this.add('sub:'+name, () => this.input('Enter line style id (1-solid)', value, 'int', 1, 11).then(val => { - if (getSvgLineStyle(val)) set_func(val); + this.sub(name, () => this.input('Enter line style id (1-solid)', value, 'int', 1, 11).then(val => { + if (getSvgLineStyle(val)) + set_func(val); })); for (let n = 1; n < 11; ++n) { const dash = getSvgLineStyle(n), - svg = `<svg width='100' height='14'><text x='2' y='13' style='font-size:12px'>${n}</text><line x1='30' y1='7' x2='100' y2='7' stroke='black' stroke-width='3' stroke-dasharray='${dash}'></line></svg>`; + svg = `<svg width='100' height='14'><text x='2' y='13' style='font-size:12px'>${n}</text><line x1='30' y1='7' x2='100' y2='7' stroke='black' stroke-width='3' stroke-dasharray='${dash}'></line></svg>`; this.addchk((value === n), svg, n, arg => set_func(parseInt(arg))); } - this.add('endsub:'); + this.endsub(); } /** @summary Add fill style menu * @private */ - addFillStyleMenu(name, value, color_index, painter, set_func) { - this.add('sub:' + name, () => { - this.input('Enter fill style id (1001-solid, 3000..3010)', value, 'int', 0, 4000).then(id => { - if ((id >= 0) && (id <= 4000)) set_func(id); + addFillStyleMenu(name, value, color_index, set_func) { + this.sub(name, () => { + this.input('Enter fill style id (1001-solid, 3100..4000)', value, 'int', 0, 4000).then(id => { + if ((id >= 0) && (id <= 4000)) + set_func(id); }); }); - const supported = [1, 1001, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3010, 3021, 3022]; + const supported = [1, 1001]; + for (let k = 3001; k < 3025; ++k) + supported.push(k); + supported.push(3144, 3244, 3344, 3305, 3315, 3325, 3490, 3481, 3472); for (let n = 0; n < supported.length; ++n) { - let svg = supported[n]; - if (painter) { - const sample = painter.createAttFill({ std: false, pattern: supported[n], color: color_index || 1 }); - svg = `<svg width='100' height='18'><text x='1' y='12' style='font-size:12px'>${supported[n].toString()}</text><rect x='40' y='0' width='60' height='18' stroke='none' fill='${sample.getFillColor()}'></rect></svg>`; - } - this.addchk(value === supported[n], svg, supported[n], arg => set_func(parseInt(arg))); + if (n % 7 === 0) + this.column(); + + const selected = (value === supported[n]); + + if (typeof document !== 'undefined') { + const svgelement = d3_select(document.createElement('svg')), + handler = new TAttFillHandler({ color: color_index || 1, pattern: supported[n], svg: svgelement }); + svgelement.attr('width', 60).attr('height', 24); + if (selected) + svgelement.append('rect').attr('x', 0).attr('y', 0).attr('width', 60).attr('height', 24).style('stroke', 'red').style('fill', 'none').style('stroke-width', '3px'); + svgelement.append('rect').attr('x', 3).attr('y', 3).attr('width', 54).attr('height', 18).style('stroke', 'none').call(handler.func); + this.add(svgelement.node().outerHTML, supported[n], arg => set_func(parseInt(arg)), `Pattern : ${supported[n]}` + (selected ? ' Active' : '')); + } else + this.addchk(selected, supported[n].toString(), supported[n], arg => set_func(parseInt(arg))); + if (n % 7 === 6) + this.endcolumn(); } - this.add('endsub:'); + this.endsub(); } /** @summary Add font selection menu @@ -434,60 +514,70 @@ class JSRootMenu { addFontMenu(name, value, set_func) { const prec = value && Number.isInteger(value) ? value % 10 : 2; - this.add('sub:' + name, () => { - this.input('Enter font id from [0..20]', Math.floor(value/10), 'int', 0, 20).then(id => { - if ((id >= 0) && (id <= 20)) set_func(id*10 + prec); + this.sub(name, () => { + this.input('Enter font id from [0..20]', Math.floor(value / 10), 'int', 0, 20).then(id => { + if ((id >= 0) && (id <= 20)) + set_func(id * 10 + prec); }); }); - this.add('column:'); + this.column(); const doc = getDocument(); for (let n = 1; n < 20; ++n) { - const id = n*10 + prec, + const id = n * 10 + prec, handler = new FontHandler(id, 14), txt = d3_select(doc.createElementNS(nsSVG, 'text')); let fullname = handler.getFontName(), qual = ''; - if (handler.weight) { qual += 'b'; fullname += ' ' + handler.weight; } - if (handler.style) { qual += handler.style[0]; fullname += ' ' + handler.style; } - if (qual) qual = ' ' + qual; + if (handler.weight) { + qual += 'b'; + fullname += ' ' + handler.weight; + } + if (handler.style) { + qual += handler.style[0]; + fullname += ' ' + handler.style; + } + if (qual) + qual = ' ' + qual; txt.attr('x', 1).attr('y', 15).text(fullname.split(' ')[0] + qual); handler.setFont(txt); const rect = (value !== id) ? '' : '<rect width=\'90\' height=\'18\' style=\'fill:none;stroke:black\'></rect>', - svg = `<svg width='90' height='18'>${txt.node().outerHTML}${rect}</svg>`; + svg = `<svg width='90' height='18'>${txt.node().outerHTML}${rect}</svg>`; this.add(svg, id, arg => set_func(parseInt(arg)), `${id}: ${fullname}`); if (n === 10) { - this.add('endcolumn:'); - this.add('column:'); + this.endcolumn(); + this.column(); } } - this.add('endcolumn:'); - this.add('endsub:'); + this.endcolumn(); + this.endsub(); } /** @summary Add align selection menu * @private */ addAlignMenu(name, value, set_func) { - this.add(`sub:${name}`, () => { + this.sub(name, () => { this.input('Enter align like 12 or 31', value).then(arg => { const id = parseInt(arg); - if ((id < 11) || (id > 33)) return; - const h = Math.floor(id/10), v = id % 10; - if ((h > 0) && (h < 4) && (v > 0) && (v < 4)) set_func(id); + if ((id < 11) || (id > 33)) + return; + const h = Math.floor(id / 10), v = id % 10; + if ((h > 0) && (h < 4) && (v > 0) && (v < 4)) + set_func(id); }); }); const hnames = ['left', 'middle', 'right'], vnames = ['bottom', 'centered', 'top']; for (let h = 1; h < 4; ++h) { for (let v = 1; v < 4; ++v) - this.addchk(h*10+v === value, `${h*10+v}: ${hnames[h-1]} ${vnames[v-1]}`, h*10+v, arg => set_func(parseInt(arg))); + this.addchk(h * 10 + v === value, `${h * 10 + v}: ${hnames[h - 1]} ${vnames[v - 1]}`, h * 10 + v, arg => set_func(parseInt(arg))); } - this.add('endsub:'); + this.endsub(); } /** @summary Fill context menu for graphical attributes in painter @@ -497,93 +587,97 @@ class JSRootMenu { * @private */ addAttributesMenu(painter, preffix) { const is_frame = painter === painter.getFramePainter(), - pp = is_frame ? painter.getPadPainter() : null; - if (!preffix) preffix = ''; + pp = is_frame ? painter.getPadPainter() : null, + redraw_arg = !preffix && !is_frame ? 'attribute' : true; + if (!preffix) + preffix = ''; if (painter.lineatt?.used) { - this.add(`sub:${preffix}Line att`); + this.sub(`${preffix}Line att`); this.addSizeMenu('width', 1, 10, 1, painter.lineatt.width, arg => { painter.lineatt.change(undefined, arg); changeObjectMember(painter, 'fLineWidth', arg); - if (pp) changeObjectMember(pp, 'fFrameLineWidth', arg); - painter.interactiveRedraw(true, `exec:SetLineWidth(${arg})`); - }); - this.addColorMenu('color', painter.lineatt.color, arg => { - painter.lineatt.change(arg); - changeObjectMember(painter, 'fLineColor', arg, true); - if (pp) changeObjectMember(pp, 'fFrameLineColor', arg, true); - painter.interactiveRedraw(true, getColorExec(arg, 'SetLineColor')); + changeObjectMember(pp, 'fFrameLineWidth', arg); + painter.interactiveRedraw(redraw_arg, `exec:SetLineWidth(${arg})`); }); + if (!painter.lineatt.nocolor) { + this.addColorMenu('color', painter.lineatt.color, arg => { + painter.lineatt.change(arg); + changeObjectMember(painter, 'fLineColor', arg, true); + changeObjectMember(pp, 'fFrameLineColor', arg, true); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetLineColor')); + }); + } this.addLineStyleMenu('style', painter.lineatt.style, id => { painter.lineatt.change(undefined, undefined, id); changeObjectMember(painter, 'fLineStyle', id); - if (pp) changeObjectMember(pp, 'fFrameLineStyle', id); - painter.interactiveRedraw(true, `exec:SetLineStyle(${id})`); + changeObjectMember(pp, 'fFrameLineStyle', id); + painter.interactiveRedraw(redraw_arg, `exec:SetLineStyle(${id})`); }); - this.add('endsub:'); + this.endsub(); if (!is_frame && painter.lineatt?.excl_side) { - this.add('sub:Exclusion'); - this.add('sub:side'); + this.sub('Exclusion'); + this.sub('side'); for (let side = -1; side <= 1; ++side) { this.addchk((painter.lineatt.excl_side === side), side, side, - arg => { painter.lineatt.changeExcl(parseInt(arg)); painter.interactiveRedraw(); }); + arg => { painter.lineatt.changeExcl(parseInt(arg)); painter.interactiveRedraw(); }); } - this.add('endsub:'); + this.endsub(); this.addSizeMenu('width', 10, 100, 10, painter.lineatt.excl_width, - arg => { painter.lineatt.changeExcl(undefined, arg); painter.interactiveRedraw(); }); + arg => { painter.lineatt.changeExcl(undefined, arg); painter.interactiveRedraw(); }); - this.add('endsub:'); + this.endsub(); } } if (painter.fillatt?.used) { - this.add(`sub:${preffix}Fill att`); + this.sub(`${preffix}Fill att`); this.addColorMenu('color', painter.fillatt.colorindx, arg => { painter.fillatt.change(arg, undefined, painter.getCanvSvg()); changeObjectMember(painter, 'fFillColor', arg, true); - if (pp) changeObjectMember(pp, 'fFrameFillColor', arg, true); - painter.interactiveRedraw(true, getColorExec(arg, 'SetFillColor')); + changeObjectMember(pp, 'fFrameFillColor', arg, true); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetFillColor')); }, painter.fillatt.kind); - this.addFillStyleMenu('style', painter.fillatt.pattern, painter.fillatt.colorindx, painter, id => { + this.addFillStyleMenu('style', painter.fillatt.pattern, painter.fillatt.colorindx, id => { painter.fillatt.change(undefined, id, painter.getCanvSvg()); changeObjectMember(painter, 'fFillStyle', id); - if (pp) changeObjectMember(pp, 'fFrameFillStyle', id); - painter.interactiveRedraw(true, `exec:SetFillStyle(${id})`); + changeObjectMember(pp, 'fFrameFillStyle', id); + painter.interactiveRedraw(redraw_arg, `exec:SetFillStyle(${id})`); }); - this.add('endsub:'); + this.endsub(); } if (painter.markeratt?.used) { - this.add(`sub:${preffix}Marker att`); + this.sub(`${preffix}Marker att`); this.addColorMenu('color', painter.markeratt.color, arg => { changeObjectMember(painter, 'fMarkerColor', arg, true); painter.markeratt.change(arg); - painter.interactiveRedraw(true, getColorExec(arg, 'SetMarkerColor')); + painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetMarkerColor')); }); this.addSizeMenu('size', 0.5, 6, 0.5, painter.markeratt.size, arg => { changeObjectMember(painter, 'fMarkerSize', arg); painter.markeratt.change(undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetMarkerSize(${arg})`); + painter.interactiveRedraw(redraw_arg, `exec:SetMarkerSize(${arg})`); }); - this.add('sub:style'); + this.sub('style'); const supported = [1, 2, 3, 4, 5, 6, 7, 8, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]; for (let n = 0; n < supported.length; ++n) { const clone = new TAttMarkerHandler({ style: supported[n], color: painter.markeratt.color, size: 1.7 }), - svg = `<svg width='60' height='18'><text x='1' y='12' style='font-size:12px'>${supported[n].toString()}</text><path stroke='black' fill='${clone.fill?'black':'none'}' d='${clone.create(40, 8)}'></path></svg>`; + svg = `<svg width='60' height='18'><text x='1' y='12' style='font-size:12px'>${supported[n].toString()}</text><path stroke='black' fill='${clone.fill ? 'black' : 'none'}' d='${clone.create(40, 8)}'></path></svg>`; this.addchk(painter.markeratt.style === supported[n], svg, supported[n], - arg => { painter.markeratt.change(undefined, parseInt(arg)); painter.interactiveRedraw(true, `exec:SetMarkerStyle(${arg})`); }); + arg => { painter.markeratt.change(undefined, parseInt(arg)); painter.interactiveRedraw(redraw_arg, `exec:SetMarkerStyle(${arg})`); }); } - this.add('endsub:'); - this.add('endsub:'); + this.endsub(); + this.endsub(); } if (painter.textatt?.used) { - this.add(`sub:${preffix}Text att`); + this.sub(`${preffix}Text att`); this.addFontMenu('font', painter.textatt.font, arg => { changeObjectMember(painter, 'fTextFont', arg); @@ -619,33 +713,53 @@ class JSRootMenu { }); } - this.add('endsub:'); + this.endsub(); } } /** @summary Fill context menu for axis * @private */ - addTAxisMenu(EAxisBits, painter, faxis, kind) { + addTAxisMenu(EAxisBits, painter, faxis, kind, axis_painter, frame_painter) { const is_gaxis = faxis._typename === clTGaxis; this.add('Divisions', () => this.input('Set Ndivisions', faxis.fNdivisions, 'int', 0).then(val => { faxis.fNdivisions = val; painter.interactiveRedraw('pad', `exec:SetNdivisions(${val})`, kind); })); - this.add('sub:Labels'); + this.sub('Labels'); this.addchk(faxis.TestBit(EAxisBits.kCenterLabels), 'Center', - arg => { faxis.InvertBit(EAxisBits.kCenterLabels); painter.interactiveRedraw('pad', `exec:CenterLabels(${arg})`, kind); }); + arg => { faxis.SetBit(EAxisBits.kCenterLabels, arg); painter.interactiveRedraw('pad', `exec:CenterLabels(${arg})`, kind); }); this.addchk(faxis.TestBit(EAxisBits.kLabelsVert), 'Rotate', - arg => { faxis.InvertBit(EAxisBits.kLabelsVert); painter.interactiveRedraw('pad', `exec:SetBit(TAxis::kLabelsVert,${arg})`, kind); }); + arg => { faxis.SetBit(EAxisBits.kLabelsVert, arg); painter.interactiveRedraw('pad', `exec:SetBit(TAxis::kLabelsVert,${arg})`, kind); }); this.addColorMenu('Color', faxis.fLabelColor, - arg => { faxis.fLabelColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLabelColor'), kind); }); + arg => { faxis.fLabelColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLabelColor'), kind); }); this.addSizeMenu('Offset', -0.02, 0.1, 0.01, faxis.fLabelOffset, - arg => { faxis.fLabelOffset = arg; painter.interactiveRedraw('pad', `exec:SetLabelOffset(${arg})`, kind); }); + arg => { faxis.fLabelOffset = arg; painter.interactiveRedraw('pad', `exec:SetLabelOffset(${arg})`, kind); }); let a = faxis.fLabelSize >= 1; this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fLabelSize, - arg => { faxis.fLabelSize = arg; painter.interactiveRedraw('pad', `exec:SetLabelSize(${arg})`, kind); }); - this.add('endsub:'); - this.add('sub:Title'); + arg => { faxis.fLabelSize = arg; painter.interactiveRedraw('pad', `exec:SetLabelSize(${arg})`, kind); }); + + if (frame_painter && (axis_painter?.kind === kAxisLabels) && (faxis.fNbins > 20)) { + this.add('Find label', () => this.input('Label id').then(id => { + if (!id) + return; + for (let bin = 0; bin < faxis.fNbins; ++bin) { + const lbl = axis_painter.formatLabels(bin); + if (lbl === id) + return frame_painter.zoomSingle(kind, Math.max(0, bin - 4), Math.min(faxis.fNbins, bin + 5)); + } + }), 'Zoom into region around specific label'); + } + if (frame_painter && faxis.fLabels) { + const ignore = `${kind}_ignore_labels`; + this.addchk(!frame_painter[ignore], 'Custom', flag => { + frame_painter[ignore] = !flag; + painter.interactiveRedraw('pad'); + }, `Use of custom labels in axis ${kind}`); + } + this.endsub(); + + this.sub('Title'); this.add('SetTitle', () => { this.input('Enter axis title', faxis.fTitle).then(t => { faxis.fTitle = t; @@ -653,41 +767,46 @@ class JSRootMenu { }); }); this.addchk(faxis.TestBit(EAxisBits.kCenterTitle), 'Center', - arg => { faxis.InvertBit(EAxisBits.kCenterTitle); painter.interactiveRedraw('pad', `exec:CenterTitle(${arg})`, kind); }); - this.addchk(faxis.TestBit(EAxisBits.kOppositeTitle), 'Opposite', - () => { faxis.InvertBit(EAxisBits.kOppositeTitle); painter.redrawPad(); }); - this.addchk(faxis.TestBit(EAxisBits.kRotateTitle), 'Rotate', - arg => { faxis.InvertBit(EAxisBits.kRotateTitle); painter.interactiveRedraw('pad', is_gaxis ? `exec:SetBit(TAxis::kRotateTitle, ${arg})` : `exec:RotateTitle(${arg})`, kind); }); - if (is_gaxis) { - this.addColorMenu('Color', faxis.fTextColor, - arg => { faxis.fTextColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); }); - } else { - this.addColorMenu('Color', faxis.fTitleColor, - arg => { faxis.fTitleColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); }); + arg => { faxis.SetBit(EAxisBits.kCenterTitle, arg); painter.interactiveRedraw('pad', `exec:CenterTitle(${arg})`, kind); }); + if (!painter?.hasSnapId()) { + this.addchk(faxis.TestBit(EAxisBits.kOppositeTitle), 'Opposite', + arg => { faxis.SetBit(EAxisBits.kOppositeTitle, arg); painter.redrawPad(); }); } + this.addchk(faxis.TestBit(EAxisBits.kRotateTitle), 'Rotate', + arg => { faxis.SetBit(EAxisBits.kRotateTitle, arg); painter.interactiveRedraw('pad', is_gaxis ? `exec:SetBit(TAxis::kRotateTitle, ${arg})` : `exec:RotateTitle(${arg})`, kind); }); + this.addColorMenu('Color', is_gaxis ? faxis.fTextColor : faxis.fTitleColor, arg => { + if (is_gaxis) + faxis.fTextColor = arg; + else + faxis.fTitleColor = arg; + + painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); + }); this.addSizeMenu('Offset', 0, 3, 0.2, faxis.fTitleOffset, - arg => { faxis.fTitleOffset = arg; painter.interactiveRedraw('pad', `exec:SetTitleOffset(${arg})`, kind); }); + arg => { faxis.fTitleOffset = arg; painter.interactiveRedraw('pad', `exec:SetTitleOffset(${arg})`, kind); }); a = faxis.fTitleSize >= 1; this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fTitleSize, - arg => { faxis.fTitleSize = arg; painter.interactiveRedraw('pad', `exec:SetTitleSize(${arg})`, kind); }); - this.add('endsub:'); - this.add('sub:Ticks'); + arg => { faxis.fTitleSize = arg; painter.interactiveRedraw('pad', `exec:SetTitleSize(${arg})`, kind); }); + this.endsub(); + + this.sub('Ticks'); if (is_gaxis) { this.addColorMenu('Color', faxis.fLineColor, - arg => { faxis.fLineColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLineColor'), kind); }); + arg => { faxis.fLineColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLineColor'), kind); }); this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickSize, - arg => { faxis.fTickSize = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); + arg => { faxis.fTickSize = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); } else { this.addColorMenu('Color', faxis.fAxisColor, - arg => { faxis.fAxisColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetAxisColor'), kind); }); + arg => { faxis.fAxisColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetAxisColor'), kind); }); this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickLength, - arg => { faxis.fTickLength = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); + arg => { faxis.fTickLength = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); } - this.add('endsub:'); + this.endsub(); if (is_gaxis) { this.add('Options', () => this.input('Enter TGaxis options like +L or -G', faxis.fChopt, 'string').then(arg => { - faxis.fChopt = arg; painter.interactiveRedraw('pad', `exec:SetOption("${arg}")`, kind); + faxis.fChopt = arg; + painter.interactiveRedraw('pad', `exec:SetOption("${arg}")`, kind); })); } } @@ -695,148 +814,174 @@ class JSRootMenu { /** @summary Fill menu to edit settings properties * @private */ addSettingsMenu(with_hierarchy, alone, handle_func) { + if (!isFunc(handle_func)) + handle_func = () => {}; if (alone) - this.add('header:Settings'); + this.header('Settings'); else - this.add('sub:Settings'); + this.sub('Settings'); - this.add('sub:Files'); + this.sub('Files'); if (with_hierarchy) { this.addchk(settings.OnlyLastCycle, 'Last cycle', flag => { settings.OnlyLastCycle = flag; - if (handle_func) handle_func('refresh'); + handle_func('refresh'); }); this.addchk(!settings.SkipStreamerInfos, 'Streamer infos', flag => { settings.SkipStreamerInfos = !flag; - if (handle_func) handle_func('refresh'); + handle_func('refresh'); }); } this.addchk(settings.UseStamp, 'Use stamp arg', flag => { settings.UseStamp = flag; }); this.addSizeMenu('Max ranges', 1, 1000, [1, 10, 20, 50, 200, 1000], settings.MaxRanges, value => { settings.MaxRanges = value; }, 'Maximal number of ranges in single http request'); - this.addchk(settings.HandleWrongHttpResponse, 'Handle wrong http response', flag => { settings.HandleWrongHttpResponse = flag; }); + this.addchk(settings.HandleWrongHttpResponse, 'Handle wrong http response', flag => { settings.HandleWrongHttpResponse = flag; }, 'Let detect and solve problem when server returns wrong Content-Length header, see https://github.com/root-project/jsroot/issues/189'); this.addchk(settings.WithCredentials, 'With credentials', flag => { settings.WithCredentials = flag; }, 'Submit http request with user credentials'); - this.add('endsub:'); + this.endsub(); - this.add('sub:Toolbar'); + this.sub('Toolbar'); this.addchk(settings.ToolBar === false, 'Off', flag => { settings.ToolBar = !flag; }); this.addchk(settings.ToolBar === true, 'On', flag => { settings.ToolBar = flag; }); this.addchk(settings.ToolBar === 'popup', 'Popup', flag => { settings.ToolBar = flag ? 'popup' : false; }); - this.add('separator'); + this.separator(); this.addchk(settings.ToolBarSide === 'left', 'Left side', flag => { settings.ToolBarSide = flag ? 'left' : 'right'; }); this.addchk(settings.ToolBarVert, 'Vertical', flag => { settings.ToolBarVert = flag; }); - this.add('endsub:'); + this.endsub(); - this.add('sub:Interactive'); + this.sub('Interactive'); this.addchk(settings.Tooltip, 'Tooltip', flag => { settings.Tooltip = flag; }); this.addchk(settings.ContextMenu, 'Context menus', flag => { settings.ContextMenu = flag; }); - this.add('sub:Zooming'); + this.sub('Zooming'); this.addchk(settings.Zooming, 'Global', flag => { settings.Zooming = flag; }); this.addchk(settings.ZoomMouse, 'Mouse', flag => { settings.ZoomMouse = flag; }); this.addchk(settings.ZoomWheel, 'Wheel', flag => { settings.ZoomWheel = flag; }); this.addchk(settings.ZoomTouch, 'Touch', flag => { settings.ZoomTouch = flag; }); - this.add('endsub:'); + this.endsub(); this.addchk(settings.HandleKeys, 'Keypress handling', flag => { settings.HandleKeys = flag; }); + this.addchk(!settings.UserSelect, 'User select', flag => { settings.UserSelect = flag ? '' : 'none'; }, 'Set "user-select: none" for drawings to avoid text selection '); this.addchk(settings.MoveResize, 'Move and resize', flag => { settings.MoveResize = flag; }); this.addchk(settings.DragAndDrop, 'Drag and drop', flag => { settings.DragAndDrop = flag; }); this.addchk(settings.DragGraphs, 'Drag graph points', flag => { settings.DragGraphs = flag; }); this.addSelectMenu('Progress box', ['off', 'on', 'modal'], isStr(settings.ProgressBox) ? settings.ProgressBox : (settings.ProgressBox ? 'on' : 'off'), value => { settings.ProgressBox = (value === 'off') ? false : (value === ' on' ? true : value); }); - this.add('endsub:'); - - this.add('sub:Drawing'); - this.addSelectMenu('Optimize', ['None', 'Smart', 'Always'], settings.OptimizeDraw, value => { settings.OptimizeDraw = value; }); + this.endsub(); + + this.sub('Drawing'); + this.addSelectMenu('Optimize', ['None', 'Smart', 'Always'], settings.OptimizeDraw, value => { settings.OptimizeDraw = value; }, 'Histogram drawing optimization'); + this.sub('SmallPad', undefined, undefined, 'Minimal pad size drawn normally'); + this.add(`width ${settings.SmallPad?.width ?? 0}px`, () => this.input('Small pad width', settings.SmallPad?.width, 'int', 1, 1000).then(val => { settings.SmallPad.width = val; })); + this.add(`height ${settings.SmallPad?.height ?? 0}px`, () => this.input('Small pad height', settings.SmallPad?.height, 'int', 1, 800).then(val => { settings.SmallPad.height = val; })); + this.add('disable', () => { settings.SmallPad = { width: 0, height: 0 }; }, 'disable small pad drawing optimization'); + this.add('default', () => { settings.SmallPad = { width: 150, height: 100 }; }, 'Set to default 150x100 dimension'); + this.endsub(); this.addPaletteMenu(settings.Palette, pal => { settings.Palette = pal; }); this.addchk(settings.AutoStat, 'Auto stat box', flag => { settings.AutoStat = flag; }); + this.addchk(settings.LoadSymbolTtf, 'Load symbol.ttf', flag => { settings.LoadSymbolTtf = flag; }, 'Use symbol.ttf font file to render greek symbols, also used in PDF'); + + this.sub('Axis'); + this.addchk(settings.StripAxisLabels, 'Strip labels', flag => { settings.StripAxisLabels = flag; }, 'Provide shorter labels like 10^0 -> 1'); + this.addchk(settings.CutAxisLabels, 'Cut labels', flag => { settings.CutAxisLabels = flag; }, 'Remove labels which may exceed graphical range'); + this.add(`Tilt angle ${settings.AxisTiltAngle}`, () => this.input('Axis tilt angle', settings.AxisTiltAngle, 'int', 0, 180).then(val => { settings.AxisTiltAngle = val; })); + this.add(`X format ${settings.XValuesFormat ?? gStyle.fStatFormat}`, () => this.input('X axis format', settings.XValuesFormat).then(val => { settings.XValuesFormat = val; })); + this.add(`Y format ${settings.YValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Y axis format', settings.YValuesFormat).then(val => { settings.YValuesFormat = val; })); + this.add(`Z format ${settings.ZValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Z axis format', settings.ZValuesFormat).then(val => { settings.ZValuesFormat = val; })); + this.endsub(); this.addSelectMenu('Latex', ['Off', 'Symbols', 'Normal', 'MathJax', 'Force MathJax'], settings.Latex, value => { settings.Latex = value; }); this.addSelectMenu('3D rendering', ['Default', 'WebGL', 'Image'], settings.Render3D, value => { settings.Render3D = value; }); this.addSelectMenu('WebGL embeding', ['Default', 'Overlay', 'Embed'], settings.Embed3D, value => { settings.Embed3D = value; }); + if (internals.setDefaultDrawOpt) { + this.add('Default options', () => this.input('List of options like TH2:lego2;TH3:glbox2', settings._dflt_drawopt || '').then(v => { + settings._dflt_drawopt = v; + internals.setDefaultDrawOpt(v); + }), 'Configure custom default draw options for some classes'); + } + this.endsub(); - this.add('endsub:'); - - this.add('sub:Geometry'); + this.sub('Geometry'); this.add('Grad per segment: ' + settings.GeoGradPerSegm, () => this.input('Grad per segment in geometry', settings.GeoGradPerSegm, 'int', 1, 60).then(val => { settings.GeoGradPerSegm = val; })); this.addchk(settings.GeoCompressComp, 'Compress composites', flag => { settings.GeoCompressComp = flag; }); - this.add('endsub:'); + this.endsub(); if (with_hierarchy) { - this.add('sub:Browser'); + this.sub('Browser'); this.add('Hierarchy limit: ' + settings.HierarchyLimit, () => this.input('Max number of items in hierarchy', settings.HierarchyLimit, 'int', 10, 100000).then(val => { settings.HierarchyLimit = val; - if (handle_func) handle_func('refresh'); + handle_func('refresh'); })); this.add('Browser width: ' + settings.BrowserWidth, () => this.input('Browser width in px', settings.BrowserWidth, 'int', 50, 2000).then(val => { settings.BrowserWidth = val; - if (handle_func) handle_func('width'); + handle_func('width'); })); - this.add('endsub:'); + this.endsub(); } this.add('Dark mode: ' + (settings.DarkMode ? 'On' : 'Off'), () => { settings.DarkMode = !settings.DarkMode; - if (handle_func) handle_func('dark'); + handle_func('dark'); }); const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, addStyleIntField = (name, field, arr) => { - this.add('sub:' + name); - const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; - for (let v = 0; v < arr.length; ++v) - this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); - this.add('endsub:'); - }; + this.sub(name); + const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; + for (let v = 0; v < arr.length; ++v) + this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); + this.endsub(); + }; - this.add('sub:gStyle'); + this.sub('gStyle'); - this.add('sub:Canvas'); + this.sub('Canvas'); this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); addStyleIntField('Draw date', 'fOptDate', ['Off', 'Current time', 'File create time', 'File modify time']); this.add(`Time zone: ${settings.TimeZone}`, () => this.input('Input time zone like UTC. empty string - local timezone', settings.TimeZone, 'string').then(val => { settings.TimeZone = val; })); addStyleIntField('Draw file', 'fOptFile', ['Off', 'File name', 'Full file URL', 'Item name']); this.addSizeMenu('Date X', 0.01, 0.1, 0.01, gStyle.fDateX, x => { gStyle.fDateX = x; }, 'configure gStyle.fDateX for date/item name drawings'); this.addSizeMenu('Date Y', 0.01, 0.1, 0.01, gStyle.fDateY, y => { gStyle.fDateY = y; }, 'configure gStyle.fDateY for date/item name drawings'); - this.add('endsub:'); + this.endsub(); - this.add('sub:Pad'); + this.sub('Pad'); this.addColorMenu('Color', gStyle.fPadColor, col => { gStyle.fPadColor = col; }); - this.add('sub:Grid'); + this.sub('Grid'); this.addchk(gStyle.fPadGridX, 'X', flag => { gStyle.fPadGridX = flag; }); this.addchk(gStyle.fPadGridY, 'Y', flag => { gStyle.fPadGridY = flag; }); this.addColorMenu('Color', gStyle.fGridColor, col => { gStyle.fGridColor = col; }); this.addSizeMenu('Width', 1, 10, 1, gStyle.fGridWidth, w => { gStyle.fGridWidth = w; }); this.addLineStyleMenu('Style', gStyle.fGridStyle, st => { gStyle.fGridStyle = st; }); - this.add('endsub:'); + this.endsub(); addStyleIntField('Ticks X', 'fPadTickX', ['normal', 'ticks on both sides', 'labels on both sides']); addStyleIntField('Ticks Y', 'fPadTickY', ['normal', 'ticks on both sides', 'labels on both sides']); addStyleIntField('Log X', 'fOptLogx', ['off', 'on', 'log 2']); addStyleIntField('Log Y', 'fOptLogy', ['off', 'on', 'log 2']); addStyleIntField('Log Z', 'fOptLogz', ['off', 'on', 'log 2']); - this.add('endsub:'); + this.endsub(); - this.add('sub:Frame'); + this.sub('Frame'); this.addColorMenu('Fill color', gStyle.fFrameFillColor, col => { gStyle.fFrameFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fFrameFillStyle, gStyle.fFrameFillColor, null, id => { gStyle.fFrameFillStyle = id; }); + this.addFillStyleMenu('Fill style', gStyle.fFrameFillStyle, gStyle.fFrameFillColor, id => { gStyle.fFrameFillStyle = id; }); this.addColorMenu('Line color', gStyle.fFrameLineColor, col => { gStyle.fFrameLineColor = col; }); this.addSizeMenu('Line width', 1, 10, 1, gStyle.fFrameLineWidth, w => { gStyle.fFrameLineWidth = w; }); this.addLineStyleMenu('Line style', gStyle.fFrameLineStyle, st => { gStyle.fFrameLineStyle = st; }); this.addSizeMenu('Border size', 0, 10, 1, gStyle.fFrameBorderSize, sz => { gStyle.fFrameBorderSize = sz; }); + this.addSelectMenu('Border mode', ['Down', 'Off', 'Up'], gStyle.fFrameBorderMode + 1, v => { gStyle.fFrameBorderMode = v - 1; }); + // fFrameBorderMode: 0, - this.add('sub:Margins'); + this.sub('Margins'); this.addSizeMenu('Bottom', 0, 0.5, 0.05, gStyle.fPadBottomMargin, v => { gStyle.fPadBottomMargin = v; }); this.addSizeMenu('Top', 0, 0.5, 0.05, gStyle.fPadTopMargin, v => { gStyle.fPadTopMargin = v; }); this.addSizeMenu('Left', 0, 0.5, 0.05, gStyle.fPadLeftMargin, v => { gStyle.fPadLeftMargin = v; }); this.addSizeMenu('Right', 0, 0.5, 0.05, gStyle.fPadRightMargin, v => { gStyle.fPadRightMargin = v; }); - this.add('endsub:'); - this.add('endsub:'); + this.endsub(); + this.endsub(); - this.add('sub:Title'); + this.sub('Title'); this.addColorMenu('Fill color', gStyle.fTitleColor, col => { gStyle.fTitleColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fTitleStyle, gStyle.fTitleColor, null, id => { gStyle.fTitleStyle = id; }); + this.addFillStyleMenu('Fill style', gStyle.fTitleStyle, gStyle.fTitleColor, id => { gStyle.fTitleStyle = id; }); this.addColorMenu('Text color', gStyle.fTitleTextColor, col => { gStyle.fTitleTextColor = col; }); this.addSizeMenu('Border size', 0, 10, 1, gStyle.fTitleBorderSize, sz => { gStyle.fTitleBorderSize = sz; }); this.addSizeMenu('Font size', 0.01, 0.1, 0.01, gStyle.fTitleFontSize, sz => { gStyle.fTitleFontSize = sz; }); @@ -845,11 +990,11 @@ class JSRootMenu { this.addSizeMenu('Y: ' + gStyle.fTitleY.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleY, v => { gStyle.fTitleY = v; }); this.addSizeMenu('W: ' + gStyle.fTitleW.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleW, v => { gStyle.fTitleW = v; }); this.addSizeMenu('H: ' + gStyle.fTitleH.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleH, v => { gStyle.fTitleH = v; }); - this.add('endsub:'); + this.endsub(); - this.add('sub:Stat box'); + this.sub('Stat box'); this.addColorMenu('Fill color', gStyle.fStatColor, col => { gStyle.fStatColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fStatStyle, gStyle.fStatColor, null, id => { gStyle.fStatStyle = id; }); + this.addFillStyleMenu('Fill style', gStyle.fStatStyle, gStyle.fStatColor, id => { gStyle.fStatStyle = id; }); this.addColorMenu('Text color', gStyle.fStatTextColor, col => { gStyle.fStatTextColor = col; }); this.addSizeMenu('Border size', 0, 10, 1, gStyle.fStatBorderSize, sz => { gStyle.fStatBorderSize = sz; }); this.addSizeMenu('Font size', 0, 30, 5, gStyle.fStatFontSize, sz => { gStyle.fStatFontSize = sz; }); @@ -859,16 +1004,17 @@ class JSRootMenu { this.addSizeMenu('Y: ' + gStyle.fStatY.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatY, v => { gStyle.fStatY = v; }); this.addSizeMenu('Width: ' + gStyle.fStatW.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatW, v => { gStyle.fStatW = v; }); this.addSizeMenu('Height: ' + gStyle.fStatH.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatH, v => { gStyle.fStatH = v; }); - this.add('endsub:'); + this.endsub(); - this.add('sub:Legend'); + this.sub('Legend'); this.addColorMenu('Fill color', gStyle.fLegendFillColor, col => { gStyle.fLegendFillColor = col; }); + this.addFillStyleMenu('Fill style', gStyle.fLegendFillStyle, gStyle.fLegendFillColor, id => { gStyle.fLegendFillStyle = id; }); this.addSizeMenu('Border size', 0, 10, 1, gStyle.fLegendBorderSize, sz => { gStyle.fLegendBorderSize = sz; }); this.addFontMenu('Font', gStyle.fLegendFont, fnt => { gStyle.fLegendFont = fnt; }); this.addSizeMenu('Text size', 0, 0.1, 0.01, gStyle.fLegendTextSize, v => { gStyle.fLegendTextSize = v; }, 'legend text size, when 0 - auto adjustment is used'); - this.add('endsub:'); + this.endsub(); - this.add('sub:Histogram'); + this.sub('Histogram'); this.addchk(gStyle.fOptTitle === 1, 'Hist title', flag => { gStyle.fOptTitle = flag ? 1 : 0; }); this.addchk(gStyle.fOrthoCamera, 'Orthographic camera', flag => { gStyle.fOrthoCamera = flag; }); this.addchk(gStyle.fHistMinimumZero, 'Base0', flag => { gStyle.fHistMinimumZero = flag; }, 'when true, BAR and LEGO drawing using base = 0'); @@ -878,32 +1024,41 @@ class JSRootMenu { this.addSizeMenu('End error', 0, 12, 1, gStyle.fEndErrorSize, v => { gStyle.fEndErrorSize = v; }, 'size in pixels of end error for E1 draw options, gStyle.fEndErrorSize'); this.addSizeMenu('Top margin', 0.0, 0.5, 0.05, gStyle.fHistTopMargin, v => { gStyle.fHistTopMargin = v; }, 'Margin between histogram top and frame top'); this.addColorMenu('Fill color', gStyle.fHistFillColor, col => { gStyle.fHistFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fHistFillStyle, gStyle.fHistFillColor, null, id => { gStyle.fHistFillStyle = id; }); + this.addFillStyleMenu('Fill style', gStyle.fHistFillStyle, gStyle.fHistFillColor, id => { gStyle.fHistFillStyle = id; }); this.addColorMenu('Line color', gStyle.fHistLineColor, col => { gStyle.fHistLineColor = col; }); this.addSizeMenu('Line width', 1, 10, 1, gStyle.fHistLineWidth, w => { gStyle.fHistLineWidth = w; }); this.addLineStyleMenu('Line style', gStyle.fHistLineStyle, st => { gStyle.fHistLineStyle = st; }); - this.add('endsub:'); + this.endsub(); - this.add('separator'); - this.add('sub:Predefined'); + this.separator(); + this.sub('Predefined'); ['Modern', 'Plain', 'Bold'].forEach(name => this.addchk((gStyle.fName === name), name, name, selectgStyle)); - this.add('endsub:'); + this.endsub(); - this.add('endsub:'); // gStyle + this.endsub(); // gStyle - this.add('separator'); + this.separator(); this.add('Save settings', () => { const promise = readSettings(true) ? Promise.resolve(true) : this.confirm('Save settings', 'Pressing OK one agreess that JSROOT will store settings in browser local storage'); - promise.then(res => { if (res) { saveSettings(); saveStyle(); } }); + promise.then(res => { + if (res) { + saveSettings(); + saveStyle(); + } + }); }, 'Store settings and gStyle in browser local storage'); - this.add('Delete settings', () => { saveSettings(-1); saveStyle(-1); }, 'Delete settings and gStyle from browser local storage'); + this.add('Delete settings', () => { + saveSettings(-1); + saveStyle(-1); + }, 'Delete settings and gStyle from browser local storage'); - if (!alone) this.add('endsub:'); + if (!alone) + this.endsub(); } /** @summary Run modal dialog - * @return {Promise} with html element inside dialg + * @return {Promise} with html element inside dialog * @private */ async runModal() { throw Error('runModal() must be reimplemented'); @@ -923,7 +1078,7 @@ class JSRootMenu { * @return {Promise} with true when 'Ok' pressed or false when 'Cancel' pressed * @protected */ async confirm(title, message) { - return this.runModal(title, message, { btns: true, height: 120, width: 400 }).then(elem => { return !!elem; }); + return this.runModal(title, message, { btns: true, height: 120, width: 400 }).then(elem => Boolean(elem)); } /** @summary Input value @@ -933,34 +1088,53 @@ class JSRootMenu { * @param {string} [kind] - use 'text' (default), 'number', 'float' or 'int' * @protected */ async input(title, value, kind, min, max) { - if (!kind) kind = 'text'; - const inp_type = (kind === 'int') ? 'number' : 'text'; + let onchange = null; + if (isFunc(kind)) { + onchange = kind; + kind = ''; + } + if (!kind) + kind = 'text'; + const inp_type = (kind === 'int') ? 'number' : 'text', value0 = value; let ranges = ''; - if ((value === undefined) || (value === null)) value = ''; + if ((value === undefined) || (value === null)) + value = ''; if (kind === 'int') { - if (min !== undefined) ranges += ` min="${min}"`; - if (max !== undefined) ranges += ` max="${max}"`; - } + if (min !== undefined) + ranges += ` min="${min}"`; + if (max !== undefined) + ranges += ` max="${max}"`; + } const main_content = - '<form><fieldset style="padding:0; border:0">'+ - `<input type="${inp_type}" value="${value}" ${ranges} style="width:98%;display:block" class="jsroot_dlginp"/>`+ - '</fieldset></form>'; + '<form><fieldset style="padding:0; border:0">' + + `<input type="${inp_type}" value="${value}" ${ranges} style="width:98%;display:block" class="jsroot_dlginp"/>` + + '</fieldset></form>', oninit = !onchange ? null : elem => { + const inp = elem.querySelector('.jsroot_dlginp'); + if (inp) + inp.oninput = () => onchange(inp.value); + }; return new Promise(resolveFunc => { - this.runModal(title, main_content, { btns: true, height: 150, width: 400 }).then(element => { - if (!element) return; - let val = element.querySelector('.jsroot_dlginp').value; + this.runModal(title, main_content, { btns: true, height: 150, width: 400, oninit }).then(element => { + if (!element) { + if (onchange) + onchange(value0); + return; + } + let val = element.querySelector('.jsroot_dlginp').value, isok = true; if (kind === 'float') { val = Number.parseFloat(val); - if (Number.isFinite(val)) - resolveFunc(val); + isok = Number.isFinite(val); } else if (kind === 'int') { val = parseInt(val); - if (Number.isInteger(val)) - resolveFunc(val); - } else + isok = Number.isInteger(val); + } + if (isok) { + if (onchange) + onchange(val); resolveFunc(val); + } }); }); } @@ -968,34 +1142,40 @@ class JSRootMenu { /** @summary Let input arguments from the method * @return {Promise} with method argument */ async showMethodArgsDialog(method) { - const dlg_id = this.menuname + '_dialog'; + const dlg_id = this.menuname + sDfltDlg; let main_content = '<form> <fieldset style="padding:0; border:0">'; for (let n = 0; n < method.fArgs.length; ++n) { const arg = method.fArgs[n]; arg.fValue = arg.fDefault; - if (arg.fValue === '""') arg.fValue = ''; + if (arg.fValue === '""') + arg.fValue = ''; main_content += `<label for="${dlg_id}_inp${n}">${arg.fName}</label> - <input type='text' tabindex="${n+1}" id="${dlg_id}_inp${n}" value="${arg.fValue}" style="width:100%;display:block"/>`; + <input type='text' tabindex="${n + 1}" id="${dlg_id}_inp${n}" value="${arg.fValue}" style="width:100%;display:block"/>`; } main_content += '</fieldset></form>'; return new Promise(resolveFunc => { - this.runModal(method.fClassName + '::' + method.fName, main_content, { btns: true, height: 100 + method.fArgs.length*60, width: 400, resizable: true }).then(element => { - if (!element) return; + this.runModal(method.fClassName + '::' + method.fName, main_content, { btns: true, height: 100 + method.fArgs.length * 60, width: 400, resizable: true }).then(element => { + if (!element) + return; let args = ''; for (let k = 0; k < method.fArgs.length; ++k) { const arg = method.fArgs[k]; let value = element.querySelector(`#${dlg_id}_inp${k}`).value; - if (value === '') value = arg.fDefault; + if (value === '') + value = arg.fDefault; if ((arg.fTitle === 'Option_t*') || (arg.fTitle === 'const char*')) { // check quotes, // TODO: need to make more precise checking of escape characters - if (!value) value = '""'; - if (value[0] !== '"') value = '"' + value; - if (value[value.length-1] !== '"') value += '"'; + if (!value) + value = '""'; + if (value[0] !== '"') + value = '"' + value; + if (value.at(-1) !== '"') + value += '"'; } args += (k > 0 ? ',' : '') + value; @@ -1009,18 +1189,18 @@ class JSRootMenu { /** @summary Let input arguments from the Command * @return {Promise} with command argument */ async showCommandArgsDialog(cmdname, args) { - const dlg_id = this.menuname + '_dialog'; + const dlg_id = this.menuname + sDfltDlg; let main_content = '<form> <fieldset style="padding:0; border:0">'; for (let n = 0; n < args.length; ++n) { - main_content += `<label for="${dlg_id}_inp${n}">arg${n+1}</label>`+ + main_content += `<label for="${dlg_id}_inp${n}">arg${n + 1}</label>` + `<input type='text' id="${dlg_id}_inp${n}" value="${args[n]}" style="width:100%;display:block"/>`; - } + } main_content += '</fieldset></form>'; return new Promise(resolveFunc => { - this.runModal('Arguments for command ' + cmdname, main_content, { btns: true, height: 110 + args.length*60, width: 400, resizable: true }).then(element => { + this.runModal('Arguments for command ' + cmdname, main_content, { btns: true, height: 110 + args.length * 60, width: 400, resizable: true }).then(element => { if (!element) return resolveFunc(null); @@ -1061,39 +1241,42 @@ class StandaloneMenu extends JSRootMenu { * @param {string} name - item name * @param {function} func - func called when item is selected */ add(name, arg, func, title) { - let curr = this.stack[this.stack.length-1]; + let curr = this.stack.at(-1); - if (name === 'separator') + if (name === sSeparator) return curr.push({ divider: true }); - if (name.indexOf('header:') === 0) - return curr.push({ text: name.slice(7), header: true }); + if (name.indexOf(sHeader) === 0) + return curr.push({ text: name.slice(sHeader.length), header: true, title }); - if (name === 'endsub:') { + if (name === sEndsub) { this.stack.pop(); - curr = this.stack[this.stack.length-1]; - if (curr[curr.length-1].sub.length === 0) - curr[curr.length-1].sub = undefined; + curr = this.stack.at(-1); + if (!curr.at(-1).sub.length) + curr.at(-1).sub = undefined; return; } - if (name === 'endcolumn:') + if (name === sEndcolumn) return this.stack.pop(); - - if (isFunc(arg)) { title = func; func = arg; arg = name; } + if (isFunc(arg)) { + title = func; + func = arg; + arg = name; + } const elem = {}; curr.push(elem); - if (name === 'column:') { + if (name === sColumn) { elem.column = true; elem.sub = []; this.stack.push(elem.sub); return; } - if (name.indexOf('sub:') === 0) { + if (name.indexOf(sSub) === 0) { name = name.slice(4); elem.sub = []; this.stack.push(elem.sub); @@ -1121,44 +1304,50 @@ class StandaloneMenu extends JSRootMenu { _buildContextmenu(menu, left, top, loc) { const doc = getDocument(), outer = doc.createElement('div'), + clname = 'jsroot_ctxt_container', + clfocus = 'jsroot_ctxt_focus', + clcolumn = 'jsroot_ctxt_column', container_style = - 'position: absolute; top: 0; user-select: none; z-index: 100000; background-color: rgb(250, 250, 250); margin: 0; padding: 0px; width: auto;'+ - 'min-width: 100px; box-shadow: 0px 0px 10px rgb(0, 0, 0, 0.2); border: 3px solid rgb(215, 215, 215); font-family: Arial, helvetica, sans-serif, serif;'+ + 'position: absolute; top: 0; user-select: none; z-index: 100000; background-color: rgb(250, 250, 250); margin: 0; padding: 0px; width: auto;' + + 'min-width: 100px; box-shadow: 0px 0px 10px rgb(0, 0, 0, 0.2); border: 3px solid rgb(215, 215, 215); font-family: Arial, helvetica, sans-serif, serif;' + 'font-size: 13px; color: rgb(0, 0, 0, 0.8); line-height: 15px;'; // if loc !== doc.body then its a submenu, so it needs to have position: relative; if (loc === doc.body) { - // delete all elements with className jsroot_ctxt_container - const deleteElems = doc.getElementsByClassName('jsroot_ctxt_container'); - while (deleteElems.length > 0) - deleteElems[0].parentNode.removeChild(deleteElems[0]); + // delete all elements with menu className + const deleteElems = doc.getElementsByClassName(clname); + for (let k = deleteElems.length - 1; k >= 0; --k) + deleteElems[k].parentNode.removeChild(deleteElems[k]); - outer.className = 'jsroot_ctxt_container'; + outer.className = clname; outer.style = container_style; outer.style.position = 'fixed'; outer.style.left = left + 'px'; outer.style.top = top + 'px'; } else if ((left < 0) && (top === left)) { // column - outer.className = 'jsroot_ctxt_column'; + outer.className = clcolumn; outer.style.float = 'left'; - outer.style.width = (100/-left).toFixed(1) + '%'; + outer.style.width = (100 / -left).toFixed(1) + '%'; } else { - outer.className = 'jsroot_ctxt_container'; + outer.className = clname; outer.style = container_style; outer.style.left = -loc.offsetLeft + loc.offsetWidth + 'px'; } let need_check_area = false, ncols = 0; menu.forEach(d => { - if (d.checked) need_check_area = true; - if (d.column) ncols++; + if (d.checked) + need_check_area = true; + if (d.column) + ncols++; }); menu.forEach(d => { if (ncols > 0) { outer.style.display = 'flex'; - if (d.column) this._buildContextmenu(d.sub, -ncols, -ncols, outer); + if (d.column) + this._buildContextmenu(d.sub, -ncols, -ncols, outer); return; } @@ -1175,20 +1364,56 @@ class StandaloneMenu extends JSRootMenu { if (d.header) { item.style = 'background-color: lightblue; padding: 3px 7px; font-weight: bold; border-bottom: 1px;'; - item.innerHTML = d.text; + + let url = '', title = ''; + if (d.title) { + const p = d.title.indexOf('https://'); + if (p >= 0) { + url = d.title.slice(p); + title = d.title.slice(0, p); + } else + title = d.title; + } + if (!url) + item.innerText = d.text; + else { + item.style.display = 'flex'; + item.style['justify-content'] = 'space-between'; + + const txt = doc.createElement('span'); + txt.innerText = d.text; + txt.style = 'display: inline-block; margin: 0;'; + item.appendChild(txt); + + const anchor = doc.createElement('span'); + anchor.style = 'margin: 0; color: blue; opacity: 0.1; margin-left: 7px; right: 3px; display: inline-block; cursor: pointer;'; + anchor.textContent = '?'; + anchor.title = url; + anchor.addEventListener('click', () => { + const cp = this.painter?.getCanvPainter(); + if (cp?.canSendWebsocket()) + cp.sendWebsocket(`SHOWURL:${url}`); + else + window.open(url); + }); + anchor.addEventListener('mouseenter', () => { anchor.style.opacity = 1; }); + anchor.addEventListener('mouseleave', () => { anchor.style.opacity = 0.1; }); + item.appendChild(anchor); + } + if (title) + item.setAttribute('title', title); + return; } const hovArea = doc.createElement('div'); - hovArea.style.width = '100%'; - hovArea.style.height = '100%'; - hovArea.style.display = 'flex'; - hovArea.style.justifyContent = 'space-between'; - hovArea.style.cursor = 'pointer'; - if (d.title) hovArea.setAttribute('title', d.title); + hovArea.style = 'width: 100%; height: 100%; display: flex; justify-content: space-between; cursor: pointer;'; + if (d.title) + hovArea.setAttribute('title', d.title); item.appendChild(hovArea); - if (!d.text) d.text = 'item'; + if (!d.text) + d.text = 'item'; const text = doc.createElement('div'); text.style = 'margin: 0; padding: 3px 7px; pointer-events: none; white-space: nowrap'; @@ -1198,7 +1423,7 @@ class StandaloneMenu extends JSRootMenu { text.style.display = 'flex'; const chk = doc.createElement('span'); - chk.innerHTML = d.checked ? '\u2713' : ''; + chk.innerText = d.checked ? '\u2713' : ''; chk.style.display = 'inline-block'; chk.style.width = '1em'; text.appendChild(chk); @@ -1211,37 +1436,34 @@ class StandaloneMenu extends JSRootMenu { } else { if (need_check_area) { const chk = doc.createElement('span'); - chk.innerHTML = d.checked ? '\u2713' : ''; + chk.innerText = d.checked ? '\u2713' : ''; chk.style.display = 'inline-block'; chk.style.width = '1em'; text.appendChild(chk); } const sub = doc.createElement('span'); - if (d.text.indexOf('<nobr>') === 0) - sub.textContent = d.text.slice(6, d.text.length-7); - else - sub.textContent = d.text; + sub.textContent = d.text; text.appendChild(sub); } hovArea.appendChild(text); - function changeFocus(item, on) { + function changeFocus(fitem, on) { if (on) { - item.classList.add('jsroot_ctxt_focus'); - item.style['background-color'] = 'rgb(220, 220, 220)'; - } else if (item.classList.contains('jsroot_ctxt_focus')) { - item.style['background-color'] = null; - item.classList.remove('jsroot_ctxt_focus'); - item.querySelector('.jsroot_ctxt_container')?.remove(); + fitem.classList.add(clfocus); + fitem.style['background-color'] = 'rgb(220, 220, 220)'; + } else if (fitem.classList.contains(clfocus)) { + fitem.style['background-color'] = null; + fitem.classList.remove(clfocus); + fitem.querySelector(`.${clname}`)?.remove(); } } if (d.extraText || d.sub) { const extraText = doc.createElement('span'); extraText.className = 'jsroot_ctxt_extraText'; - extraText.style = 'margin: 0; padding: 3px 7px; color: rgb(0, 0, 0, 0.6);'; + extraText.style = 'margin: 0; padding: 3px 7px; color: rgba(0, 0, 0, 0.6);'; extraText.textContent = d.sub ? '\u25B6' : d.extraText; hovArea.appendChild(extraText); @@ -1249,7 +1471,7 @@ class StandaloneMenu extends JSRootMenu { extraText.addEventListener('click', evnt => { evnt.preventDefault(); evnt.stopPropagation(); - const was_active = item.parentNode.querySelector('.jsroot_ctxt_focus'); + const was_active = item.parentNode.querySelector(`.${clfocus}`); if (was_active) changeFocus(was_active, false); @@ -1308,7 +1530,7 @@ class StandaloneMenu extends JSRootMenu { // Does contextmenu overflow window height? outer.style.top = (docHeight - outer.offsetHeight) + 'px'; } - } else if (outer.className !== 'jsroot_ctxt_column') { + } else if (outer.className !== clcolumn) { // if its sub-contextmenu const dimensionsLoc = loc.getBoundingClientRect(), dimensionsOuter = outer.getBoundingClientRect(); @@ -1342,15 +1564,15 @@ class StandaloneMenu extends JSRootMenu { async show(event) { this.remove(); - if (!event && this.show_evnt) event = this.show_evnt; + if (!event && this.show_evnt) + event = this.show_evnt; const doc = getDocument(), woffset = typeof window === 'undefined' ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY }; doc.body.addEventListener('click', this.remove_handler); - const oldmenu = doc.getElementById(this.menuname); - if (oldmenu) oldmenu.remove(); + doc.getElementById(this.menuname)?.remove(); this.element = this._buildContextmenu(this.code, (event?.clientX || 0) + woffset.x, (event?.clientY || 0) + woffset.y, doc.body); @@ -1361,40 +1583,55 @@ class StandaloneMenu extends JSRootMenu { /** @summary Run modal elements with standalone code */ createModal(title, main_content, args) { - if (!args) args = {}; + if (!args) + args = {}; - if (!args.Ok) args.Ok = 'Ok'; + if (!args.Ok) + args.Ok = 'Ok'; - const modal = { args }, dlg_id = (this?.menuname ?? 'root_modal') + '_dialog'; + const modal = { args }, dlg_id = (this?.menuname ?? sDfltName) + sDfltDlg; d3_select(`#${dlg_id}`).remove(); d3_select(`#${dlg_id}_block`).remove(); - const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)); - modal.block = d3_select('body').append('div') - .attr('id', `${dlg_id}_block`) - .attr('class', 'jsroot_dialog_block') - .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); - modal.element = d3_select('body') - .append('div') - .attr('id', dlg_id) - .attr('class', 'jsroot_dialog') - .style('position', 'absolute') - .style('width', `${w}px`) - .style('left', '50%') - .style('top', '50%') - .style('z-index', 100001) - .attr('tabindex', '0') - .html( - '<div style=\'position: relative; left: -50%; top: -50%; border: solid green 3px; padding: 5px; display: flex; flex-flow: column; background-color: white\'>'+ - `<div style='flex: 0 1 auto; padding: 5px'>${title}</div>`+ - `<div class='jsroot_dialog_content' style='flex: 1 1 auto; padding: 5px'>${main_content}</div>`+ - '<div class=\'jsroot_dialog_footer\' style=\'flex: 0 1 auto; padding: 5px\'>'+ - `<button class='jsroot_dialog_button' style='float: right; width: fit-content; margin-right: 1em'>${args.Ok}</button>`+ + const w = Math.min(args.width || 450, Math.round(0.9 * browser.screenWidth)), + b = d3_select('body'); + modal.block = b.append('div') + .attr('id', `${dlg_id}_block`) + .attr('class', 'jsroot_dialog_block') + .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); + modal.element = b.append('div') + .attr('id', dlg_id) + .attr('class', 'jsroot_dialog') + .style('position', 'absolute') + .style('width', `${w}px`) + .style('left', '50%') + .style('top', '50%') + .style('z-index', 100001) + .attr('tabindex', '0'); + + modal.element.html( + '<div style=\'position: relative; left: -50%; top: -50%; border: solid green 3px; padding: 5px; display: flex; flex-flow: column; background-color: white\'>' + + `<div style='flex: 0 1 auto; padding: 5px; cursor: pointer;' class='jsroot_dialog_title'>${title}</div>` + + `<div class='jsroot_dialog_content' style='flex: 1 1 auto; padding: 5px'>${main_content}</div>` + + '<div class=\'jsroot_dialog_footer\' style=\'flex: 0 1 auto; padding: 5px\'>' + + `<button class='jsroot_dialog_button' style='float: right; width: fit-content; margin-right: 1em'>${args.Ok}</button>` + (args.btns ? '<button class=\'jsroot_dialog_button\' style=\'float: right; width: fit-content; margin-right: 1em\'>Cancel</button>' : '') + - '</div></div>'); + '</div></div>' + ); + + const drag_move = d3_drag().on('start', () => { modal.y0 = 0; }).on('drag', evnt => { + if (!modal.y0) + modal.y0 = d3_pointer(evnt, modal.element.node())[1]; + let p0 = Math.max(0, d3_pointer(evnt, b.node())[1] - modal.y0); + if (b.node().clientHeight) + p0 = Math.min(p0, 0.8 * b.node().clientHeight); + modal.element.style('top', `${p0}px`); + }); + modal.element.select('.jsroot_dialog_title').call(drag_move); modal.done = function(res) { - if (this._done) return; + if (this._done) + return; this._done = true; if (isFunc(this.call_back)) this.call_back(res); @@ -1430,8 +1667,12 @@ class StandaloneMenu extends JSRootMenu { }); let f = modal.element.select('.jsroot_dialog_content').select('input'); - if (f.empty()) f = modal.element.select('.jsroot_dialog_footer').select('button'); - if (!f.empty()) f.node().focus(); + if (f.empty()) + f = modal.element.select('.jsroot_dialog_footer').select('button'); + if (!f.empty()) + f.node().focus(); + if (isFunc(args.oninit)) + args.oninit(modal.element.node()); return modal; } @@ -1443,9 +1684,9 @@ class StandaloneMenu extends JSRootMenu { }); } - } // class StandaloneMenu + /** @summary Create JSROOT menu * @desc See {@link JSRootMenu} class for detailed list of methods * @param {object} [evnt] - event object like mouse context menu event @@ -1459,16 +1700,27 @@ class StandaloneMenu extends JSRootMenu { * menu.addchk(flag, 'Checked', arg => console.log(`Now flag is ${arg}`)); * menu.show(); */ function createMenu(evnt, handler, menuname) { - const menu = new StandaloneMenu(handler, menuname || 'root_ctx_menu', evnt); + const menu = new StandaloneMenu(handler, menuname || sDfltName, evnt); return menu.load(); } -/** @summary Close previousely created and shown JSROOT menu +/** @summary Close previously created and shown JSROOT menu * @param {string} [menuname] - optional menu name */ function closeMenu(menuname) { - const element = getDocument().getElementById(menuname || 'root_ctx_menu'); + const element = getDocument().getElementById(menuname || sDfltName); element?.remove(); - return !!element; + return Boolean(element); +} + +/** @summary Returns true if menu or modal dialog present + * @private */ +function hasMenu(menuname) { + const doc = getDocument(); + if (doc.getElementById(menuname || sDfltName)) + return true; + if (doc.getElementById((menuname || sDfltName) + sDfltDlg)) + return true; + return false; } /** @summary Fill and show context menu for painter object @@ -1481,10 +1733,12 @@ function showPainterMenu(evnt, painter, kind) { createMenu(evnt, painter).then(menu => { painter.fillContextMenu(menu); - if ((kind === kToFront) && isFunc(painter.bringToFront)) { + if (kind === kNoReorder) + kind = undefined; + else if (isFunc(painter.bringToFront)) menu.add('Bring to front', () => painter.bringToFront(true)); + if (kind === kToFront) kind = undefined; - } return painter.fillObjectExecMenu(menu, kind); }).then(menu => menu.show()); } @@ -1509,8 +1763,10 @@ internals._modalProgress = function(msg, click_handle) { /** @summary Assign handler for context menu for painter draw element * @private */ function assignContextMenu(painter, kind) { - if (!painter?.isBatchMode() && painter?.draw_g) - painter.draw_g.on('contextmenu', settings.ContextMenu ? evnt => showPainterMenu(evnt, painter, kind) : null); + if (!painter?.isBatchMode()) + painter?.getG()?.on('contextmenu', settings.ContextMenu ? evnt => showPainterMenu(evnt, painter, kind) : null); } -export { createMenu, closeMenu, showPainterMenu, assignContextMenu, kToFront }; +Object.assign(internals.jsroot, { createMenu, closeMenu, assignContextMenu, kToFront, kNoReorder }); + +export { createMenu, closeMenu, showPainterMenu, assignContextMenu, hasMenu, kToFront, kNoReorder }; diff --git a/modules/gui/utils.mjs b/modules/gui/utils.mjs index 97c8326e5..cd98f258d 100644 --- a/modules/gui/utils.mjs +++ b/modules/gui/utils.mjs @@ -1,9 +1,10 @@ import { settings, internals, browser, gStyle, isBatchMode, isNodeJs, isObject, isFunc, isStr, source_dir, atob_func, btoa_func } from '../core.mjs'; import { select as d3_select, pointer as d3_pointer, drag as d3_drag, color as d3_color } from '../d3.mjs'; -import { BasePainter } from '../base/BasePainter.mjs'; +import { prSVG, prJSON, BasePainter } from '../base/BasePainter.mjs'; import { resize } from '../base/ObjectPainter.mjs'; import { getRootColors } from '../base/colors.mjs'; + /** @summary Display progress message in the left bottom corner. * @desc Previous message will be overwritten * if no argument specified, any shown messages will be removed @@ -19,13 +20,16 @@ function showProgress(msg, tmout, click_handle) { let box = d3_select('#' + id); if (!settings.ProgressBox) { - if (modal) modal(); + if (modal) + modal(); return box.remove(); } - if ((arguments.length === 0) || !msg) { - if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) box.remove(); - if (modal) modal(); + if (!arguments.length || !msg) { + if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) + box.remove(); + if (modal) + modal(); return; } @@ -65,15 +69,16 @@ function showProgress(msg, tmout, click_handle) { * therefore try several workarounds * @private */ function closeCurrentWindow() { - if (typeof window === 'undefined') return; - window.close(); - window.open('', '_self').close(); + if (typeof window !== 'undefined') { + window.close(); + window.open('', '_self').close(); + } } /** @summary Tries to open ui5 * @private */ function tryOpenOpenUI(sources, args) { - if (!sources || (sources.length === 0)) { + if (!sources?.length) { if (isFunc(args.rejectFunc)) { args.rejectFunc(Error('openui5 was not possible to load')); args.rejectFunc = null; @@ -87,6 +92,9 @@ function tryOpenOpenUI(sources, args) { if ((src.indexOf('roothandler') === 0) && (src.indexOf('://') < 0)) src = src.replace(/:\//g, '://'); + if (settings.Debug) + console.log('Try openui5 from ' + src); + const element = document.createElement('script'); element.setAttribute('type', 'text/javascript'); element.setAttribute('id', 'sap-ui-bootstrap'); @@ -96,8 +104,8 @@ function tryOpenOpenUI(sources, args) { element.setAttribute('src', src + (args.ui5dbg ? 'resources/sap-ui-core-dbg.js' : 'resources/sap-ui-core.js')); // latest openui5 version element.setAttribute('data-sap-ui-libs', args.openui5libs ?? 'sap.m, sap.ui.layout, sap.ui.unified, sap.ui.commons'); - - element.setAttribute('data-sap-ui-theme', args.openui5theme || 'sap_belize'); + // element.setAttribute('data-sap-ui-language', args.openui5language ?? 'en'); + element.setAttribute('data-sap-ui-theme', args.openui5theme || (settings.DarkMode ? 'sap_fiori_3_dark' : 'sap_fiori_3')); element.setAttribute('data-sap-ui-compatVersion', 'edge'); element.setAttribute('data-sap-ui-async', 'true'); // element.setAttribute('data-sap-ui-bindingSyntax', 'complex'); @@ -114,7 +122,7 @@ function tryOpenOpenUI(sources, args) { }; element.onload = function() { - console.log(`Load openui5 from ${src}`); + args.load_src = src; }; document.head.appendChild(element); @@ -129,7 +137,8 @@ async function loadOpenui5(args) { if (typeof globalThis.sap === 'object') return globalThis.sap; - if (!args) args = {}; + if (!args) + args = {}; let rootui5sys = source_dir.replace(/jsrootsys/g, 'rootui5sys'); @@ -142,16 +151,28 @@ async function loadOpenui5(args) { } const openui5_sources = []; - let openui5_dflt = 'https://openui5.hana.ondemand.com/1.98.0/', + let openui5_dflt = 'https://openui5.hana.ondemand.com/1.135.0/', openui5_root = rootui5sys ? rootui5sys + 'distribution/' : ''; if (isStr(args.openui5src)) { switch (args.openui5src) { - case 'nodefault': openui5_dflt = ''; break; - case 'default': openui5_sources.push(openui5_dflt); openui5_dflt = ''; break; - case 'nojsroot': /* openui5_root = ''; */ break; - case 'jsroot': openui5_sources.push(openui5_root); openui5_root = ''; break; - default: openui5_sources.push(args.openui5src); break; + case 'nodefault': + openui5_dflt = ''; + break; + case 'default': + openui5_sources.push(openui5_dflt); + openui5_dflt = ''; + break; + case 'nojsroot': + /* openui5_root = ''; */ + break; + case 'jsroot': + openui5_sources.push(openui5_root); + openui5_root = ''; + break; + default: + openui5_sources.push(args.openui5src); + break; } } else if (args.ui5dbg) openui5_root = ''; // exclude ROOT version in debug mode @@ -166,6 +187,8 @@ async function loadOpenui5(args) { args.rejectFunc = reject; globalThis.completeUI5Loading = function() { + console.log(`Load openui5 version ${globalThis.sap.ui.version} from ${args.load_src}`); + globalThis.sap.ui.loader.config({ paths: { jsroot: source_dir, @@ -177,15 +200,17 @@ async function loadOpenui5(args) { args.resolveFunc(globalThis.sap); args.resolveFunc = null; } + + delete globalThis.completeUI5Loading; }; tryOpenOpenUI(openui5_sources, args); }); } -/* eslint-disable key-spacing */ -/* eslint-disable comma-spacing */ -/* eslint-disable object-curly-spacing */ +/* eslint-disable @stylistic/js/key-spacing */ +/* eslint-disable @stylistic/js/comma-spacing */ +/* eslint-disable @stylistic/js/object-curly-spacing */ // some icons taken from http://uxrepo.com/ const ToolbarIcons = { @@ -212,16 +237,20 @@ const ToolbarIcons = { th2colorz: { recs: [{ x: 128, y: 486, w: 256, h: 26, f: 'rgb(38,62,168)' }, { y: 461, f: 'rgb(22,82,205)' }, { y: 435, f: 'rgb(16,100,220)' }, { y: 410, f: 'rgb(18,114,217)' }, { y: 384, f: 'rgb(20,129,214)' }, { y: 358, f: 'rgb(14,143,209)' }, { y: 333, f: 'rgb(9,157,204)' }, { y: 307, f: 'rgb(13,167,195)' }, { y: 282, f: 'rgb(30,175,179)' }, { y: 256, f: 'rgb(46,183,164)' }, { y: 230, f: 'rgb(82,186,146)' }, { y: 205, f: 'rgb(116,189,129)' }, { y: 179, f: 'rgb(149,190,113)' }, { y: 154, f: 'rgb(179,189,101)' }, { y: 128, f: 'rgb(209,187,89)' }, { y: 102, f: 'rgb(226,192,75)' }, { y: 77, f: 'rgb(244,198,59)' }, { y: 51, f: 'rgb(253,210,43)' }, { y: 26, f: 'rgb(251,230,29)' }, { y: 0, f: 'rgb(249,249,15)' }] }, th2color: { recs: [{x:0,y:256,w:13,h:39,f:'rgb(38,62,168)'},{x:13,y:371,w:39,h:39},{y:294,h:39},{y:256,h:39},{y:218,h:39},{x:51,y:410,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:90,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294},{y:256,h:39,f:'rgb(16,100,220)'},{y:218,h:39},{y:179,h:39,f:'rgb(22,82,205)'},{y:141,h:39},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:0,h:27},{x:128,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294,f:'rgb(20,129,214)'},{y:256,h:39,f:'rgb(9,157,204)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(20,129,214)'},{y:141,h:39,f:'rgb(16,100,220)'},{y:102,h:39,f:'rgb(22,82,205)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{y:0,h:27},{x:166,y:486,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(82,186,146)'},{y:256,h:39,f:'rgb(179,189,101)'},{y:218,h:39,f:'rgb(116,189,129)'},{y:179,h:39,f:'rgb(82,186,146)'},{y:141,h:39,f:'rgb(14,143,209)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:205,y:486,w:39,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(16,100,220)'},{y:333,h:39,f:'rgb(9,157,204)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(244,198,59)'},{y:218,h:39},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(13,167,195)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(22,82,205)'},{y:26,h:39,f:'rgb(38,62,168)'},{x:243,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(30,175,179)'},{y:294,f:'rgb(209,187,89)'},{y:256,h:39,f:'rgb(251,230,29)'},{y:218,h:39,f:'rgb(249,249,15)'},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(30,175,179)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:282,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(14,143,209)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(226,192,75)'},{y:218,h:39,f:'rgb(244,198,59)'},{y:179,h:39,f:'rgb(149,190,113)'},{y:141,h:39,f:'rgb(9,157,204)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:320,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(46,183,164)'},{y:256,h:39},{y:218,h:39,f:'rgb(82,186,146)'},{y:179,h:39,f:'rgb(9,157,204)'},{y:141,h:39,f:'rgb(20,129,214)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:358,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39},{y:294,f:'rgb(16,100,220)'},{y:256,h:39,f:'rgb(20,129,214)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(18,114,217)'},{y:141,h:39,f:'rgb(22,82,205)'},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:26,h:39},{x:397,y:448,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294,f:'rgb(22,82,205)'},{y:256,h:39},{y:218,h:39},{y:179,h:39,f:'rgb(38,62,168)'},{y:141,h:39},{y:102,h:39},{y:64},{y:26,h:39},{x:435,y:410,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:474,y:256,h:39},{y:179,h:39}] }, th2draw3d: { - path: 'M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z '+ - 'M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z '+ - 'M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z '+ + path: 'M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z ' + + 'M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z ' + + 'M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z ' + 'M460.293,256.149H339.237c-28.521,0-51.721,23.199-51.721,51.726v89.915c0,28.504,23.2,51.715,51.721,51.715h121.045 c28.521,0,51.721-23.199,51.721-51.715v-89.915C512.002,279.354,488.802,256.149,460.293,256.149z M465.03,397.784 c0,2.615-2.122,4.736-4.748,4.736H339.237c-2.614,0-4.747-2.121-4.747-4.736v-89.909c0-2.626,2.121-4.753,4.747-4.753h121.045 c2.615,0,4.748,2.116,4.748,4.753V397.784z' }, + /* eslint-enable @stylistic/js/key-spacing */ + /* eslint-enable @stylistic/js/comma-spacing */ + /* eslint-enable @stylistic/js/object-curly-spacing */ + createSVG(group, btn, size, title, arg) { const use_dark = (arg === true) || (arg === false) ? arg : settings.DarkMode, - opacity0 = (arg === 'browser') ? (browser.touches ? 0.2 : 0) : (use_dark ? 0.8 : 0.2), - svg = group.append('svg:svg') + opacity0 = (arg === 'browser') ? (browser.touches ? 0.2 : 0) : (use_dark ? 0.8 : 0.2), + svg = group.append('svg:svg') .attr('width', size + 'px') .attr('height', size + 'px') .attr('viewBox', '0 0 512 512') @@ -235,13 +264,15 @@ const ToolbarIcons = { const elem = d3_select(this); elem.style('opacity', elem.property('opacity1')); const func = elem.node()._mouseenter; - if (isFunc(func)) func(); + if (isFunc(func)) + func(); }) .on('mouseleave', function() { const elem = d3_select(this); elem.style('opacity', elem.property('opacity0')); const func = elem.node()._mouseleave; - if (isFunc(func)) func(); + if (isFunc(func)) + func(); }); if ('recs' in btn) { @@ -275,10 +306,10 @@ const ToolbarIcons = { * @param {number} [delay] - one could specify delay after which resize event will be handled * @protected */ function registerForResize(handle, delay) { - if (!handle || isBatchMode() || (typeof window === 'undefined') || (typeof document === 'undefined')) return; + if (!handle || isBatchMode() || (typeof window === 'undefined') || (typeof document === 'undefined')) + return; - let myInterval = null, myDelay = delay || 300; - if (myDelay < 20) myDelay = 20; + let myInterval = null; function ResizeTimer() { myInterval = null; @@ -294,7 +325,7 @@ function registerForResize(handle, delay) { const mdi = node.property('mdi'); if (isFunc(mdi?.checkMDIResize)) mdi.checkMDIResize(); - else + else resize(node.node()); } } @@ -302,8 +333,9 @@ function registerForResize(handle, delay) { } window.addEventListener('resize', () => { - if (myInterval !== null) clearTimeout(myInterval); - myInterval = setTimeout(ResizeTimer, myDelay); + if (myInterval) + clearTimeout(myInterval); + myInterval = setTimeout(ResizeTimer, Math.max(20, delay || 300)); }); } @@ -315,17 +347,18 @@ function detectRightButton(event) { /** @summary Add move handlers for drawn element * @private */ -function addMoveHandler(painter, enabled = true) { - if (!settings.MoveResize || painter.isBatchMode() || !painter.draw_g) return; +function addMoveHandler(painter, enabled = true, hover_handler = false) { + if (!settings.MoveResize || painter.isBatchMode() || !painter.getG()) + return; if (painter.getPadPainter()?.isEditable() === false) enabled = false; if (!enabled) { - if (painter.draw_g.property('assigned_move')) { + if (painter.getG().property('assigned_move')) { const drag_move = d3_drag().subject(Object); drag_move.on('start', null).on('drag', null).on('end', null); - painter.draw_g + painter.getG() .style('cursor', null) .property('assigned_move', null) .call(drag_move); @@ -333,7 +366,8 @@ function addMoveHandler(painter, enabled = true) { return; } - if (painter.draw_g.property('assigned_move')) return; + if (painter.getG().property('assigned_move')) + return; const drag_move = d3_drag().subject(Object); let not_changed = true, move_disabled = false; @@ -341,41 +375,48 @@ function addMoveHandler(painter, enabled = true) { drag_move .on('start', function(evnt) { move_disabled = this.moveEnabled ? !this.moveEnabled() : false; - if (move_disabled) return; - if (detectRightButton(evnt.sourceEvent)) return; + if (move_disabled || detectRightButton(evnt.sourceEvent)) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); - const pos = d3_pointer(evnt, this.draw_g.node()); + const pos = d3_pointer(evnt, this.getG().node()); not_changed = true; if (this.moveStart) - this.moveStart(pos[0], pos[1]); + this.moveStart(pos[0], pos[1], evnt.sourceEvent); }.bind(painter)).on('drag', function(evnt) { - if (move_disabled) return; + if (move_disabled) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); not_changed = false; if (this.moveDrag) - this.moveDrag(evnt.dx, evnt.dy); + this.moveDrag(evnt.dx, evnt.dy, evnt.sourceEvent); }.bind(painter)).on('end', function(evnt) { - if (move_disabled) return; + if (move_disabled) + return; evnt.sourceEvent.preventDefault(); evnt.sourceEvent.stopPropagation(); if (this.moveEnd) - this.moveEnd(not_changed); + this.moveEnd(not_changed, evnt.sourceEvent); let arg = null; if (not_changed) { // if not changed - provide click position - const pos = d3_pointer(evnt, this.draw_g.node()); + const pos = d3_pointer(evnt, this.getG().node()); arg = { x: pos[0], y: pos[1], dbl: false }; } this.getPadPainter()?.selectObjectPainter(this, arg); }.bind(painter)); - painter.draw_g - .style('cursor', 'move') + painter.getG() + .style('cursor', hover_handler ? 'pointer' : 'move') .property('assigned_move', true) .call(drag_move); + + if (hover_handler) { + painter.getG().on('mouseenter', () => painter.getG().style('text-decoration', 'underline')) + .on('mouseleave', () => painter.getG().style('text-decoration', null)); + } } /** @summary Inject style @@ -388,17 +429,18 @@ function injectStyle(code, node, tag) { const styles = (node || document).getElementsByTagName('style'); for (let n = 0; n < styles.length; ++n) { if (tag && styles[n].getAttribute('tag') === tag) { - styles[n].innerHTML = code; + styles[n].innerText = code; return true; } - if (styles[n].innerHTML === code) + if (styles[n].innerText === code) return true; } const element = document.createElement('style'); - if (tag) element.setAttribute('tag', tag); - element.innerHTML = code; + if (tag) + element.setAttribute('tag', tag); + element.innerText = code; (node || document.head).appendChild(element); return true; } @@ -469,7 +511,8 @@ function saveSettings(expires = 365, name = 'settings') { * @private */ function readSettings(only_check = false, name = 'settings') { const s = readLocalStorage(name); - if (!s) return false; + if (!s) + return false; if (!only_check) Object.assign(settings, s); return true; @@ -489,7 +532,8 @@ function saveStyle(expires = 365, name = 'style') { * @private */ function readStyle(only_check = false, name = 'style') { const s = readLocalStorage(name); - if (!s) return false; + if (!s) + return false; if (!only_check) Object.assign(gStyle, s); return true; @@ -500,24 +544,32 @@ let _saveFileFunc = null; /** @summary Returns image file content as it should be stored on the disc * @desc Replaces all kind of base64 coding * @private */ - function getBinFileContent(content) { - const svg_prefix = 'data:image/svg+xml;charset=utf-8,'; + if (content.indexOf(prSVG) === 0) + return decodeURIComponent(content.slice(prSVG.length)); - if (content.indexOf(svg_prefix) === 0) - return decodeURIComponent(content.slice(svg_prefix.length)); + if (content.indexOf(prJSON) === 0) + return decodeURIComponent(content.slice(prJSON.length)); - if (content.indexOf('data:image/') === 0) { + if ((content.indexOf('data:image/') === 0) || (content.indexOf('data:application/pdf') === 0)) { const p = content.indexOf('base64,'); - if (p > 0) { - const base64 = content.slice(p + 7); - return atob_func(base64); - } + if (p > 0) + return atob_func(content.slice(p + 7)); } return content; } +/** @summary Returns type of file content + * @private */ +function getContentType(content) { + if (content.indexOf('data:')) + return ''; + + const p = content.indexOf(';'); + return (p > 0) ? content.slice(5, p) : ''; +} + /** @summary Function store content as file with filename * @private */ async function saveFile(filename, content) { @@ -528,18 +580,44 @@ async function saveFile(filename, content) { fs.writeFileSync(filename, getBinFileContent(content)); return true; }); - } else if (typeof document !== 'undefined') { - const a = document.createElement('a'); - a.download = filename; + } else if (typeof document === 'undefined') + return false; + + const a = document.createElement('a'); + a.download = filename; + a.style.display = 'none'; + let fileURL = ''; + const contentType = getContentType(content); + + if ((content.length > 1e6) && (contentType === 'application/pdf')) { + // large PDF files do not work in the browser with plain base64 coding + const bindata = getBinFileContent(content), + len = bindata.length, + buffer = new ArrayBuffer(len), + view = new DataView(buffer, 0, len); + for (let i = 0; i < len; ++i) + view.setUint8(i, bindata.charCodeAt(i)); + const blob = new Blob([buffer], { type: contentType }); + fileURL = URL.createObjectURL(blob); + a.href = fileURL; + } else a.href = content; - document.body.appendChild(a); - return new Promise(resolve => { - a.addEventListener('click', () => { a.parentNode.removeChild(a); resolve(true); }); - a.click(); + document.body.appendChild(a); + + return new Promise(resolve => { + a.addEventListener('click', () => { + if (fileURL) { + setTimeout(() => { + a.parentNode.removeChild(a); + URL.revokeObjectURL(fileURL); + }, 3000); + } else + a.parentNode.removeChild(a); + resolve(true); }); - } - return false; + a.click(); + }); } /** @summary Function store content as file with filename @@ -556,9 +634,13 @@ function getColorId(col) { if (isStr(col)) { if (!col || (col === 'none')) id = 0; - else { - for (let k = 1; k < arr.length; ++k) - if (arr[k] === col) { id = k; break; } + else { + for (let k = 1; k < arr.length; ++k) { + if (arr[k] === col) { + id = k; + break; + } + } } if ((id < 0) && (col.indexOf('rgb') === 0)) id = 9999; @@ -570,23 +652,28 @@ function getColorId(col) { return { id, col }; } -/** @summary Produce exec string for WebCanas to set color value +/** @summary Produce exec string for WebCanvas to set color value * @desc Color can be id or string, but should belong to list of known colors * For higher color numbers TColor::GetColor(r,g,b) will be invoked to ensure color is exists * @private */ -function getColorExec(col, method) { +function getColorExec(col, method, extra_arg) { const d = getColorId(col); if (d.id < 0) return ''; + if (!extra_arg) + extra_arg = ''; + else + extra_arg += ','; + // for higher color numbers ensure that such color exists if (d.id >= 50) { const c = d3_color(d.col); d.id = `TColor::GetColor(${c.r},${c.g},${c.b})`; - } + } - return `exec:${method}(${d.id})`; + return `exec:${method}(${extra_arg}${d.id})`; } /** @summary Change object member in the painter @@ -606,7 +693,9 @@ function changeObjectMember(painter, member, val, is_color) { obj[member] = val; } +Object.assign(internals.jsroot, { addMoveHandler, registerForResize }); + export { showProgress, closeCurrentWindow, loadOpenui5, ToolbarIcons, registerForResize, detectRightButton, addMoveHandler, injectStyle, selectgStyle, setStoragePrefix, saveSettings, readSettings, saveStyle, readStyle, - saveFile, setSaveFile, getBinFileContent, getColorExec, changeObjectMember }; + saveFile, setSaveFile, getBinFileContent, getColorId, getColorExec, changeObjectMember }; diff --git a/modules/hist/RH1Painter.mjs b/modules/hist/RH1Painter.mjs deleted file mode 100644 index 8f9252696..000000000 --- a/modules/hist/RH1Painter.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import { settings, gStyle } from '../core.mjs'; -import { RH1Painter as RH1Painter2D } from '../hist2d/RH1Painter.mjs'; -import { RAxisPainter } from '../gpad/RAxisPainter.mjs'; -import { assignFrame3DMethods, drawBinsLego } from './hist3d.mjs'; - - -class RH1Painter extends RH1Painter2D { - - /** @summary Draw 1-D histogram in 3D mode */ - draw3D(reason) { - this.mode3d = true; - - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(), // is main histogram - zmult = 1 + 2*gStyle.fHistTopMargin; - let pr = Promise.resolve(this); - - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - return pr; - } - - this.deleteAttr(); - - this.scanContent(true); // may be required for axis drawings - - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, null, this.ymin, this.ymax, null, 0, 0); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { use_y_for_z: true, zmult, zoom: settings.Zooming, ndim: 1, draw: true, v7: true }); - }); - } - - if (!main.mode3d) - return pr; - - return pr.then(() => this.drawingBins(reason)).then(() => { - // called when bins received from server, must be reentrant - const main = this.getFramePainter(); - - drawBinsLego(this, true); - this.updatePaletteDraw(); - main.render3D(); - main.addKeysHandler(); - return this; - }); - } - - /** @summary draw RH1 object */ - static async draw(dom, histo, opt) { - return RH1Painter._draw(new RH1Painter(dom, histo), opt); - } - -} // class RH1Painter - -export { RH1Painter }; diff --git a/modules/hist/RH2Painter.mjs b/modules/hist/RH2Painter.mjs deleted file mode 100644 index 692b766ec..000000000 --- a/modules/hist/RH2Painter.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import { settings, gStyle, kNoZoom } from '../core.mjs'; -import { RH2Painter as RH2Painter2D } from '../hist2d/RH2Painter.mjs'; -import { RAxisPainter } from '../gpad/RAxisPainter.mjs'; -import { assignFrame3DMethods, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D } from './hist3d.mjs'; - - -class RH2Painter extends RH2Painter2D { - - /** Draw histogram bins in 3D, using provided draw options */ - draw3DBins() { - if (!this.draw_content) return; - - if (this.options.Surf) - return drawBinsSurf3D(this, true); - - if (this.options.Error) - return drawBinsError3D(this, true); - - if (this.options.Contour) - return drawBinsContour3D(this, true, true); - - drawBinsLego(this, true); - this.updatePaletteDraw(); - } - - draw3D(reason) { - this.mode3d = true; - - const main = this.getFramePainter(), // who makes axis drawing - is_main = this.isMainPainter(); // is main histogram - let pr = Promise.resolve(this); - - if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - return pr; - } - - let zmult = 1 + 2*gStyle.fHistTopMargin; - - this.zmin = main.logz ? this.gminposbin * 0.3 : this.gminbin; - this.zmax = this.gmaxbin; - if (this.options.minimum !== kNoZoom) this.zmin = this.options.minimum; - if (this.options.maximum !== kNoZoom) { this.zmax = this.options.maximum; zmult = 1; } - if (main.logz && (this.zmin <= 0)) this.zmin = this.zmax * 1e-5; - - this.deleteAttr(); - - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, null, this.zmin, this.zmax); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { zmult, zoom: settings.Zooming, ndim: 2, draw: true, v7: true }); - }); - } - - if (!main.mode3d) - return pr; - - return pr.then(() => this.drawingBins(reason)).then(() => { - // called when bins received from server, must be reentrant - const main = this.getFramePainter(); - - this.draw3DBins(); - main.render3D(); - main.addKeysHandler(); - - return this; - }); - } - - /** @summary draw RH2 object */ - static async draw(dom, obj, opt) { - // create painter and add it to canvas - return RH2Painter._draw(new RH2Painter(dom, obj), opt); - } - -} // class RH2Painter - -export { RH2Painter }; diff --git a/modules/hist/RH3Painter.mjs b/modules/hist/RH3Painter.mjs deleted file mode 100644 index 0b56eb22e..000000000 --- a/modules/hist/RH3Painter.mjs +++ /dev/null @@ -1,749 +0,0 @@ -import { gStyle, settings, kNoZoom, kInspect } from '../core.mjs'; -import { Matrix4, Mesh, MeshBasicMaterial, MeshLambertMaterial, SphereGeometry, - LineBasicMaterial, BufferAttribute, BufferGeometry } from '../three.mjs'; -import { floatToString, TRandom } from '../base/BasePainter.mjs'; -import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs'; -import { RAxisPainter } from '../gpad/RAxisPainter.mjs'; -import { RHistPainter } from '../hist2d/RHistPainter.mjs'; -import { createLineSegments, PointsCreator, Box3D } from '../base/base3d.mjs'; -import { RH1Painter } from './RH1Painter.mjs'; -import { RH2Painter } from './RH2Painter.mjs'; -import { assignFrame3DMethods } from './hist3d.mjs'; - -/** - * @summary Painter for RH3 classes - * - * @private - */ - -class RH3Painter extends RHistPainter { - - /** @summary Returns histogram dimension */ - getDimension() { return 3; } - - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) return; - - const histo = this.getHisto(); - if (!histo) return; - - this.extractAxesProperties(3); - - // global min/max, used at the moment in 3D drawing - - if (this.isDisplayItem()) { - // take min/max values from the display item - this.gminbin = histo.fContMin; - this.gminposbin = histo.fContMinPos > 0 ? histo.fContMinPos : null; - this.gmaxbin = histo.fContMax; - } else { - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1, 1); - - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) { - for (let k = 0; k < this.nbinsz; ++k) { - const bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content < this.gminbin) this.gminbin = bin_content; else - if (bin_content > this.gmaxbin) this.gmaxbin = bin_content; - } - } - } - } - - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); - } - - /** @summary Count histogram statistic */ - countStat() { - const histo = this.getHisto(), - xaxis = this.getAxis('x'), - yaxis = this.getAxis('y'), - zaxis = this.getAxis('z'), - i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - res = { name: histo.fName, entries: 0, integral: 0, meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0 }; - let stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0, - xi, yi, zi, xx, xside, yy, yside, zz, zside, cont; - - for (xi = 1; xi <= this.nbinsx; ++xi) { - xx = xaxis.GetBinCoord(xi - 0.5); - xside = (xi <= i1+1) ? 0 : (xi > i2+1 ? 2 : 1); - - for (yi = 1; yi <= this.nbinsy; ++yi) { - yy = yaxis.GetBinCoord(yi - 0.5); - yside = (yi <= j1+1) ? 0 : (yi > j2+1 ? 2 : 1); - - for (zi = 1; zi <= this.nbinsz; ++zi) { - zz = zaxis.GetBinCoord(zi - 0.5); - zside = (zi <= k1+1) ? 0 : (zi > k2+1 ? 2 : 1); - - cont = histo.getBinContent(xi, yi, zi); - res.entries += cont; - - if ((xside === 1) && (yside === 1) && (zside === 1)) { - stat_sum0 += cont; - stat_sumx1 += xx * cont; - stat_sumy1 += yy * cont; - stat_sumz1 += zz * cont; - stat_sumx2 += xx**2 * cont; - stat_sumy2 += yy**2 * cont; - stat_sumz2 += zz**2 * cont; - } - } - } - } - - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.meanz = stat_sumz1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); - res.rmsz = Math.sqrt(Math.abs(stat_sumz2 / stat_sum0 - res.meanz**2)); - } - - res.integral = stat_sum0; - - if (histo.fEntries > 1) - res.entries = histo.fEntries; - - return res; - } - - /** @summary Fill statistic */ - fillStatistic(stat, dostat /*, dofit */) { - const data = this.countStat(), - print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - // print_under = Math.floor(dostat / 10000) % 10, - // print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10; - // print_skew = Math.floor(dostat / 10000000) % 10; - // print_kurt = Math.floor(dostat / 100000000) % 10; - - stat.clearStat(); - - if (print_name > 0) - stat.addText(data.name); - - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); - stat.addText('Mean z = ' + stat.format(data.meanz)); - } - - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - stat.addText('Std Dev z = ' + stat.format(data.rmsz)); - } - - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); - - - return true; - } - - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(ix, iy, iz) { - const lines = [], histo = this.getHisto(); - let dx = 1, dy = 1, dz = 1; - - if (this.isDisplayItem()) { - dx = histo.stepx || 1; - dy = histo.stepy || 1; - dz = histo.stepz || 1; - } - - lines.push(this.getObjectHint(), - `x = ${this.getAxisBinTip('x', ix, dx)} xbin=${ix+1}`, - `y = ${this.getAxisBinTip('y', iy, dy)} ybin=${iy+1}`, - `z = ${this.getAxisBinTip('z', iz, dz)} zbin=${iz+1}`); - - const binz = histo.getBinContent(ix+1, iy+1, iz+1), - lbl = 'entries = '+ ((dx > 1) || (dy > 1) || (dz > 1) ? '~' : ''); - if (binz === Math.round(binz)) - lines.push(lbl + binz); - else - lines.push(lbl + floatToString(binz, gStyle.fStatFormat)); - - return lines; - } - - /** @summary Try to draw 3D histogram as scatter plot - * @desc If there are too many points, returns promise with false */ - async draw3DScatter(handle) { - const histo = this.getHisto(), - main = this.getFramePainter(), - i1 = handle.i1, i2 = handle.i2, di = handle.stepi, - j1 = handle.j1, j2 = handle.j2, dj = handle.stepj, - k1 = handle.k1, k2 = handle.k2, dk = handle.stepk; - - if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) - return true; - - // scale down factor if too large values - const coef = (this.gmaxbin > 1000) ? 1000/this.gmaxbin : 1, - content_lmt = Math.max(0, this.gminbin); - let i, j, k, bin_content, numpixels = 0, sumz = 0; - - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - sumz += bin_content; - if (bin_content <= content_lmt) continue; - numpixels += Math.round(bin_content*coef); - } - } - } - - // too many pixels - use box drawing - if (numpixels > (main.webgl ? 100000 : 30000)) - return false; - - const pnts = new PointsCreator(numpixels, main.webgl, main.size_x3d/200), - bins = new Int32Array(numpixels), - xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), zaxis = this.getAxis('z'), - rnd = new TRandom(sumz); - let nbin = 0; - - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content <= content_lmt) continue; - const num = Math.round(bin_content*coef); - - for (let n=0; n<num; ++n) { - const binx = xaxis.GetBinCoord(i + rnd.random()), - biny = yaxis.GetBinCoord(j + rnd.random()), - binz = zaxis.GetBinCoord(k + rnd.random()); - - // remember bin index for tooltip - bins[nbin++] = histo.getBin(i+1, j+1, k+1); - - pnts.addPoint(main.grx(binx), main.gry(biny), main.grz(binz)); - } - } - } - } - - return pnts.createPoints({ color: this.v7EvalColor('fill_color', 'red') }).then(mesh => { - main.add3DMesh(mesh); - - mesh.bins = bins; - mesh.painter = this; - mesh.tip_color = 0x00FF00; - - mesh.tooltip = function(intersect) { - const indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]); - - tip.x1 = main.grx(p.getAxis('x').GetBinLowEdge(tip.ix)); - tip.x2 = main.grx(p.getAxis('x').GetBinLowEdge(tip.ix+di)); - tip.y1 = main.gry(p.getAxis('y').GetBinLowEdge(tip.iy)); - tip.y2 = main.gry(p.getAxis('y').GetBinLowEdge(tip.iy+dj)); - tip.z1 = main.grz(p.getAxis('z').GetBinLowEdge(tip.iz)); - tip.z2 = main.grz(p.getAxis('z').GetBinLowEdge(tip.iz+dk)); - tip.color = this.tip_color; - tip.opacity = 0.3; - - return tip; - }; - - return true; - }); - } - - /** @summary Drawing of 3D histogram */ - draw3DBins(handle) { - const main = this.getFramePainter(); - let fillcolor = this.v7EvalColor('fill_color', 'red'), - buffer_size = 0, use_lambert = false, - use_helper = false, use_colors = false, use_opacity = 1, use_scale = true, - single_bin_verts, single_bin_norms, - tipscale = 0.5; - - if (this.options.Sphere) { - // drawing spheres - tipscale = 0.4; - use_lambert = true; - if (this.options.Sphere === 11) use_colors = true; - - const geom = main.webgl ? new SphereGeometry(0.5, 16, 12) : new SphereGeometry(0.5, 8, 6); - geom.applyMatrix4(new Matrix4().makeRotationX(Math.PI/2)); - geom.computeVertexNormals(); - - const indx = geom.getIndex().array, - pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; - - buffer_size = indx.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); - - for (let k=0; k<indx.length; ++k) { - const iii = indx[k]*3; - single_bin_verts[k*3] = pos[iii]; - single_bin_verts[k*3+1] = pos[iii+1]; - single_bin_verts[k*3+2] = pos[iii+2]; - single_bin_norms[k*3] = norm[iii]; - single_bin_norms[k*3+1] = norm[iii+1]; - single_bin_norms[k*3+2] = norm[iii+2]; - } - } else { - const indicies = Box3D.Indexes, - normals = Box3D.Normals, - vertices = Box3D.Vertices; - - buffer_size = indicies.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); - - for (let k = 0, nn = -3; k < indicies.length; ++k) { - const vert = vertices[indicies[k]]; - single_bin_verts[k*3] = vert.x-0.5; - single_bin_verts[k*3+1] = vert.y-0.5; - single_bin_verts[k*3+2] = vert.z-0.5; - - if (k%6 === 0) nn+=3; - single_bin_norms[k*3] = normals[nn]; - single_bin_norms[k*3+1] = normals[nn+1]; - single_bin_norms[k*3+2] = normals[nn+2]; - } - use_helper = true; - - if (this.options.Box === 11) use_colors = true; else - if (this.options.Box === 12) { use_colors = true; use_helper = false; } else - if (this.options.Color) { use_colors = true; use_opacity = 0.5; use_scale = false; use_helper = false; use_lambert = true; } - } - - if (use_scale) - use_scale = (this.gminbin || this.gmaxbin) ? 1 / Math.max(Math.abs(this.gminbin), Math.abs(this.gmaxbin)) : 1; - - const histo = this.getHisto(), - i1 = handle.i1, i2 = handle.i2, di = handle.stepi, - j1 = handle.j1, j2 = handle.j2, dj = handle.stepj, - k1 = handle.k1, k2 = handle.k2, dk = handle.stepk; - let palette = null; - - if (use_colors) { - palette = main.getHistPalette(); - this.createContour(main, palette); - } - - if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) - return true; - - const cols_size = []; - let nbins = 0, i, j, k, wei, bin_content, num_colors = 0, cols_sequence = []; - - for (i = i1; i < i2; i += di) { - for (j = j1; j < j2; j += dj) { - for (k = k1; k < k2; k += dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.Color && ((bin_content === 0) || (bin_content < this.gminbin))) continue; - wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1; - if (wei < 1e-3) continue; // do not draw empty or very small bins - - nbins++; - - if (!use_colors) continue; - - const colindx = palette.getContourIndex(bin_content); - if (colindx >= 0) { - if (cols_size[colindx] === undefined) { - cols_size[colindx] = 0; - cols_sequence[colindx] = num_colors++; - } - cols_size[colindx]+=1; - } else - console.error(`not found color for value = ${bin_content}`); - } - } - } - - if (!use_colors) { - cols_size.push(nbins); - num_colors = 1; - cols_sequence = [0]; - } - - const cols_nbins = new Array(num_colors), - bin_verts = new Array(num_colors), - bin_norms = new Array(num_colors), - bin_tooltips = new Array(num_colors), - helper_kind = new Array(num_colors), - helper_indexes = new Array(num_colors), // helper_kind === 1, use original vertices - helper_positions = new Array(num_colors); // helper_kind === 2, all vertices copied into separate buffer - - for (let ncol = 0; ncol < cols_size.length; ++ncol) { - if (!cols_size[ncol]) continue; // ignore dummy colors - - nbins = cols_size[ncol]; // how many bins with specified color - const nseq = cols_sequence[ncol]; - - cols_nbins[nseq] = 0; // counter for the filled bins - - helper_kind[nseq] = 0; - - // 1 - use same vertices to create helper, one can use maximal 64K vertices - // 2 - all vertices copied into separate buffer - if (use_helper) - helper_kind[nseq] = (nbins * buffer_size / 3 > 0xFFF0) ? 2 : 1; - - bin_verts[nseq] = new Float32Array(nbins * buffer_size); - bin_norms[nseq] = new Float32Array(nbins * buffer_size); - bin_tooltips[nseq] = new Int32Array(nbins); - - if (helper_kind[nseq] === 1) - helper_indexes[nseq] = new Uint16Array(nbins * Box3D.MeshSegments.length); - - if (helper_kind[nseq] === 2) - helper_positions[nseq] = new Float32Array(nbins * Box3D.Segments.length * 3); - } - - const xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), zaxis = this.getAxis('z'); - let grx1, grx2, gry1, gry2, grz1, grz2; - - for (i = i1; i < i2; i += di) { - grx1 = main.grx(xaxis.GetBinLowEdge(i+1)); - grx2 = main.grx(xaxis.GetBinLowEdge(i+2)); - for (j = j1; j < j2; j += dj) { - gry1 = main.gry(yaxis.GetBinLowEdge(j+1)); - gry2 = main.gry(yaxis.GetBinLowEdge(j+2)); - for (k = k1; k < k2; k +=dk) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.Color && ((bin_content === 0) || (bin_content < this.gminbin))) continue; - - wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1; - if (wei < 1e-3) continue; // do not show very small bins - - let nseq = 0; - if (use_colors) { - const colindx = palette.getContourIndex(bin_content); - if (colindx < 0) continue; - nseq = cols_sequence[colindx]; - } - - nbins = cols_nbins[nseq]; - - grz1 = main.grz(zaxis.GetBinLowEdge(k+1)); - grz2 = main.grz(zaxis.GetBinLowEdge(k+2)); - - // remember bin index for tooltip - bin_tooltips[nseq][nbins] = histo.getBin(i+1, j+1, k+1); - - let vvv = nbins * buffer_size; - const bin_v = bin_verts[nseq], bin_n = bin_norms[nseq]; - - // Grab the coordinates and scale that are being assigned to each bin - for (let vi = 0; vi < buffer_size; vi += 3, vvv += 3) { - bin_v[vvv] = (grx2 + grx1) / 2 + single_bin_verts[vi] * (grx2 - grx1) * wei; - bin_v[vvv+1] = (gry2 + gry1) / 2 + single_bin_verts[vi+1] * (gry2 - gry1) * wei; - bin_v[vvv+2] = (grz2 + grz1) / 2 + single_bin_verts[vi+2] * (grz2 - grz1) * wei; - - bin_n[vvv] = single_bin_norms[vi]; - bin_n[vvv+1] = single_bin_norms[vi+1]; - bin_n[vvv+2] = single_bin_norms[vi+2]; - } - - if (helper_kind[nseq] === 1) { - // reuse vertices created for the mesh - const helper_segments = Box3D.MeshSegments; - vvv = nbins * helper_segments.length; - const shift = Math.round(nbins * buffer_size/3), - helper_i = helper_indexes[nseq]; - for (let n = 0; n < helper_segments.length; ++n) - helper_i[vvv+n] = shift + helper_segments[n]; - } - - if (helper_kind[nseq] === 2) { - const helper_segments = Box3D.Segments, - helper_p = helper_positions[nseq]; - vvv = nbins * helper_segments.length * 3; - for (let n = 0; n < helper_segments.length; ++n, vvv += 3) { - const vert = Box3D.Vertices[helper_segments[n]]; - helper_p[vvv] = (grx2 + grx1) / 2 + (vert.x - 0.5) * (grx2 - grx1) * wei; - helper_p[vvv+1] = (gry2 + gry1) / 2 + (vert.y - 0.5) * (gry2 - gry1) * wei; - helper_p[vvv+2] = (grz2 + grz1) / 2 + (vert.z - 0.5) * (grz2 - grz1) * wei; - } - } - - cols_nbins[nseq] = nbins+1; - } - } - } - - for (let ncol = 0; ncol < cols_size.length; ++ncol) { - if (!cols_size[ncol]) continue; // ignore dummy colors - - const nseq = cols_sequence[ncol], - // BufferGeometries that store geometry of all bins - all_bins_buffgeom = new BufferGeometry(); - - // Create mesh from bin buffergeometry - all_bins_buffgeom.setAttribute('position', new BufferAttribute(bin_verts[nseq], 3)); - all_bins_buffgeom.setAttribute('normal', new BufferAttribute(bin_norms[nseq], 3)); - - if (use_colors) fillcolor = palette.getColor(ncol); - - const material = use_lambert - ? new MeshLambertMaterial({ color: fillcolor, opacity: use_opacity, transparent: use_opacity < 1, vertexColors: false }) - : new MeshBasicMaterial({ color: fillcolor, opacity: use_opacity, transparent: use_opacity < 1, vertexColors: false }), - combined_bins = new Mesh(all_bins_buffgeom, material); - - combined_bins.bins = bin_tooltips[nseq]; - combined_bins.bins_faces = buffer_size/9; - combined_bins.painter = this; - combined_bins.tipscale = tipscale; - combined_bins.tip_color = 0x00FF00; - combined_bins.use_scale = use_scale; - - combined_bins.tooltip = function(intersect) { - const indx = Math.floor(intersect.faceIndex / this.bins_faces); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]), - grx1 = main.grx(xaxis.GetBinCoord(tip.ix-1)), - grx2 = main.grx(xaxis.GetBinCoord(tip.ix)), - gry1 = main.gry(yaxis.GetBinCoord(tip.iy-1)), - gry2 = main.gry(yaxis.GetBinCoord(tip.iy)), - grz1 = main.grz(zaxis.GetBinCoord(tip.iz-1)), - grz2 = main.grz(zaxis.GetBinCoord(tip.iz)), - wei2 = (this.use_scale ? Math.pow(Math.abs(tip.value*this.use_scale), 0.3333) : 1) * this.tipscale; - - tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; - tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; - tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; - tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; - tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; - tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; - tip.color = this.tip_color; - - return tip; - }; - - main.add3DMesh(combined_bins); - - if (helper_kind[nseq] > 0) { - const lcolor = this.v7EvalColor('line_color', 'lightblue'), - helper_material = new LineBasicMaterial({ color: lcolor }), - lines = (helper_kind[nseq] === 1) - // reuse positions from the mesh - only special index was created - ? createLineSegments(bin_verts[nseq], helper_material, helper_indexes[nseq]) - : createLineSegments(helper_positions[nseq], helper_material); - - main.add3DMesh(lines); - } - } - - if (use_colors) - this.updatePaletteDraw(); - } - - draw3D() { - if (!this.draw_content) - return false; - - // this.options.Scatter = false; - // this.options.Box = true; - - const handle = this.prepareDraw({ only_indexes: true, extra: -0.5, right_extra: -1 }), - pr = this.options.Scatter ? this.draw3DScatter(handle) : Promise.resolve(false); - - return pr.then(res => { - return res || this.draw3DBins(handle); - }); - } - - /** @summary Redraw histogram */ - redraw(reason) { - const main = this.getFramePainter(); // who makes axis and 3D drawing - - if (reason === 'resize') { - if (main.resize3D()) main.render3D(); - return this; - } - - assignFrame3DMethods(main); - return main.create3DScene(this.options.Render3D).then(() => { - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, this.getAxis('z'), this.zmin, this.zmax); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, RAxisPainter, { zoom: settings.Zooming, ndim: 3, draw: true, v7: true }); - return this.drawingBins(reason); - }).then(() => this.draw3D()).then(() => { - main.render3D(); - main.addKeysHandler(); - return this; - }); - } - - /** @summary Fill pad toolbar with RH3-related functions */ - fillToolbar() { - const pp = this.getPadPainter(); - if (!pp) return; - - pp.addPadButton('auto_zoom', 'Unzoom all axes', 'ToggleZoom', 'Ctrl *'); - if (this.draw_content) - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - pp.showPadButtons(); - } - - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - let obj = this.getHisto(); - if (obj) obj = obj['f'+axis.toUpperCase()+'axis']; - return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); - } - - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - const i1 = this.getSelectIndex('x', 'left'), - i2 = this.getSelectIndex('x', 'right'), - j1 = this.getSelectIndex('y', 'left'), - j2 = this.getSelectIndex('y', 'right'), - k1 = this.getSelectIndex('z', 'left'), - k2 = this.getSelectIndex('z', 'right'), - histo = this.getHisto(); - let i, j, k; - - if ((i1 === i2) || (j1 === j2) || (k1 === k2)) return; - - // first find minimum - let min = histo.getBinContent(i1 + 1, j1 + 1, k1+1); - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) - min = Math.min(min, histo.getBinContent(i+1, j+1, k+1)); - } - } - - if (min > 0) return; // if all points positive, no chance for autoscale - - let ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1; - - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - if (histo.getBinContent(i+1, j+1, k+1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - if (k < kleft) kleft = k; - if (k >= kright) kright = k + 1; - } - } - } - } - - let xmin, xmax, ymin, ymax, zmin, zmax, isany = false; - - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } - if ((kleft === kright-1) && (kleft > k1+1) && (kright < k2-1)) { kleft--; kright++; } - - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = this.getAxis('x').GetBinLowEdge(ileft+1); - xmax = this.getAxis('x').GetBinLowEdge(iright+1); - isany = true; - } - - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = this.getAxis('y').GetBinLowEdge(jleft+1); - ymax = this.getAxis('y').GetBinLowEdge(jright+1); - isany = true; - } - - if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) { - zmin = this.getAxis('z').GetBinLowEdge(kleft+1); - zmax = this.getAxis('z').GetBinLowEdge(kright+1); - isany = true; - } - - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax, zmin, zmax); - } - - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - const opts = this.getSupportedDrawOptions(); - - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); - - this.decodeOptions(arg); - - this.interactiveRedraw(true, 'drawopt'); - }); - } - - /** @summary draw RH3 object */ - static async draw(dom, histo /* ,opt */) { - const painter = new RH3Painter(dom, histo); - painter.mode3d = true; - - return ensureRCanvas(painter, '3d').then(() => { - painter.setAsMainPainter(); - - painter.options = { Box: 0, Scatter: false, Sphere: 0, Color: false, minimum: kNoZoom, maximum: kNoZoom, FrontBox: false, BackBox: false }; - - const kind = painter.v7EvalAttr('kind', ''), - sub = painter.v7EvalAttr('sub', 0), - o = painter.options; - - switch (kind) { - case 'box': o.Box = 10 + sub; break; - case 'sphere': o.Sphere = 10 + sub; break; - case 'col': o.Color = true; break; - case 'scat': o.Scatter = true; break; - default: o.Box = 10; - } - - painter.scanContent(); - return painter.redraw(); - }); - } - -} // class RH3Painter - -/** @summary draw RHistDisplayItem object - * @private */ -function drawHistDisplayItem(dom, obj, opt) { - if (!obj) - return null; - - if (obj.fAxes.length === 1) - return RH1Painter.draw(dom, obj, opt); - - if (obj.fAxes.length === 2) - return RH2Painter.draw(dom, obj, opt); - - if (obj.fAxes.length === 3) - return RH3Painter.draw(dom, obj, opt); - - return null; -} - -export { RH3Painter, drawHistDisplayItem }; diff --git a/modules/hist/RPavePainter.mjs b/modules/hist/RPavePainter.mjs index 964ca6317..d38c7965b 100644 --- a/modules/hist/RPavePainter.mjs +++ b/modules/hist/RPavePainter.mjs @@ -1,9 +1,8 @@ -import { settings, isFunc, isStr, gStyle, nsREX } from '../core.mjs'; -import { floatToString, makeTranslate } from '../base/BasePainter.mjs'; +import { settings } from '../core.mjs'; +import { makeTranslate } from '../base/BasePainter.mjs'; import { RObjectPainter } from '../base/RObjectPainter.mjs'; import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs'; import { addDragHandler } from '../gpad/TFramePainter.mjs'; -import { createMenu } from '../gui/menu.mjs'; const ECorner = { kTopLeft: 1, kTopRight: 2, kBottomLeft: 3, kBottomRight: 4 }; @@ -32,11 +31,10 @@ class RPavePainter extends RObjectPainter { offsetx = this.v7EvalLength('offsetX', rect.width, 0.02), offsety = this.v7EvalLength('offsetY', rect.height, 0.02), pave_width = this.v7EvalLength('width', rect.width, 0.3), - pave_height = this.v7EvalLength('height', rect.height, 0.3); + pave_height = this.v7EvalLength('height', rect.height, 0.3), + g = this.createG(); - this.createG(); - - this.draw_g.classed('most_upper_primitives', true); // this primitive will remain on top of list + g.classed('most_upper_primitives', true); // this primitive will remain on top of list if (!visible) return this; @@ -66,15 +64,15 @@ class RPavePainter extends RObjectPainter { pave_y = fr.y + offsety; } - makeTranslate(this.draw_g, pave_x, pave_y); + makeTranslate(g, pave_x, pave_y); - this.draw_g.append('svg:rect') - .attr('x', 0) - .attr('width', pave_width) - .attr('y', 0) - .attr('height', pave_height) - .call(this.lineatt.func) - .call(this.fillatt.func); + g.append('svg:rect') + .attr('x', 0) + .attr('width', pave_width) + .attr('y', 0) + .attr('height', pave_height) + .call(this.lineatt.func) + .call(this.fillatt.func); this.pave_width = pave_width; this.pave_height = pave_height; @@ -85,10 +83,12 @@ class RPavePainter extends RObjectPainter { if (!this.isBatchMode()) { // TODO: provide pave context menu as in v6 if (settings.ContextMenu && this.paveContextMenu) - this.draw_g.on('contextmenu', evnt => this.paveContextMenu(evnt)); + g.on('contextmenu', evnt => this.paveContextMenu(evnt)); - addDragHandler(this, { x: pave_x, y: pave_y, width: pave_width, height: pave_height, - minwidth: 20, minheight: 20, redraw: d => this.sizeChanged(d) }); + addDragHandler(this, { + x: pave_x, y: pave_y, width: pave_width, height: pave_height, + minwidth: 20, minheight: 20, redraw: d => this.sizeChanged(d) + }); } return this; @@ -105,7 +105,7 @@ class RPavePainter extends RObjectPainter { rect = this.getPadPainter().getPadRect(), fr = this.onFrame ? this.getFramePainter().getFrameRect() : rect, changes = {}; - let offsetx = 0, offsety = 0; + let offsetx, offsety; switch (this.corner) { case ECorner.kTopLeft: @@ -132,7 +132,7 @@ class RPavePainter extends RObjectPainter { this.v7AttrChange(changes, 'height', this.pave_height / rect.height); this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - this.draw_g.selectChild('rect') + this.getG().selectChild('rect') .attr('width', this.pave_width) .attr('height', this.pave_height); @@ -170,67 +170,66 @@ class RLegendPainter extends RPavePainter { pp = this.getPadPainter(); let nlines = legend.fEntries.length; - if (legend.fTitle) nlines++; + if (legend.fTitle) + nlines++; - if (!nlines || !pp) return this; + if (!nlines || !pp) + return this; const stepy = height / nlines, margin_x = 0.02 * width; - let posy = 0; - - textFont.setSize(height/(nlines * 1.2)); - this.startTextDrawing(textFont, 'font'); - if (legend.fTitle) { - this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: legend.fTitle }); - posy += stepy; - } + textFont.setSize(height / (nlines * 1.2)); + return this.startTextDrawingAsync(textFont, 'font').then(() => { + let posy = 0; - for (let i = 0; i < legend.fEntries.length; ++i) { - const entry = legend.fEntries[i], w4 = Math.round(width/4); - let objp = null; + if (legend.fTitle) { + this.drawText({ latex: 1, width: width - 2 * margin_x, height: stepy, x: margin_x, y: posy, text: legend.fTitle }); + posy += stepy; + } - this.drawText({ latex: 1, width: 0.75*width - 3*margin_x, height: stepy, x: 2*margin_x + w4, y: posy, text: entry.fLabel }); + for (let i = 0; i < legend.fEntries.length; ++i) { + const entry = legend.fEntries[i], w4 = Math.round(width / 4); + let objp = null; + + this.drawText({ latex: 1, width: 0.75 * width - 3 * margin_x, height: stepy, x: 2 * margin_x + w4, y: posy, text: entry.fLabel }); + + if (entry.fDrawableId !== 'custom') + objp = pp.findSnap(entry.fDrawableId, true); + else if (entry.fDrawable.fIO) { + objp = new RObjectPainter(this.getPadPainter(), entry.fDrawable.fIO); + if (entry.fLine) + objp.createv7AttLine(); + if (entry.fFill) + objp.createv7AttFill(); + if (entry.fMarker) + objp.createv7AttMarker(); + } - if (entry.fDrawableId !== 'custom') - objp = pp.findSnap(entry.fDrawableId, true); - else if (entry.fDrawable.fIO) { - objp = new RObjectPainter(this.getDom(), entry.fDrawable.fIO); - if (entry.fLine) objp.createv7AttLine(); - if (entry.fFill) objp.createv7AttFill(); - if (entry.fMarker) objp.createv7AttMarker(); - } + if (entry.fFill && objp?.fillatt) { + this.appendPath(`M${Math.round(margin_x)},${Math.round(posy + stepy * 0.1)}h${w4}v${Math.round(stepy * 0.8)}h${-w4}z`) + .call(objp.fillatt.func); + } - if (entry.fFill && objp?.fillatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy*0.1)}h${w4}v${Math.round(stepy*0.8)}h${-w4}z`) - .call(objp.fillatt.func); - } + if (entry.fLine && objp?.lineatt) { + this.appendPath(`M${Math.round(margin_x)},${Math.round(posy + stepy / 2)}h${w4}`) + .call(objp.lineatt.func); + } - if (entry.fLine && objp?.lineatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy/2)}h${w4}`) - .call(objp.lineatt.func); - } + if (entry.fError && objp?.lineatt) { + this.appendPath(`M${Math.round(margin_x + width / 8)},${Math.round(posy + stepy * 0.2)}v${Math.round(stepy * 0.6)}`) + .call(objp.lineatt.func); + } - if (entry.fError && objp?.lineatt) { - this.draw_g - .append('svg:path') - .attr('d', `M${Math.round(margin_x + width/8)},${Math.round(posy + stepy*0.2)}v${Math.round(stepy*0.6)}`) - .call(objp.lineatt.func); - } + if (entry.fMarker && objp?.markeratt) { + this.appendPath(objp.markeratt.create(margin_x + width / 8, posy + stepy / 2)) + .call(objp.markeratt.func); + } - if (entry.fMarker && objp?.markeratt) { - this.draw_g.append('svg:path') - .attr('d', objp.markeratt.create(margin_x + width/8, posy + stepy/2)) - .call(objp.markeratt.func); + posy += stepy; } - posy += stepy; - } - - return this.finishTextDrawing(); + return this.finishTextDrawing(); + }); } /** @summary draw RLegend object */ @@ -251,30 +250,26 @@ class RLegendPainter extends RPavePainter { class RPaveTextPainter extends RPavePainter { /** @summary draw RPaveText content */ - drawContent() { + async drawContent() { const pavetext = this.getObject(), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), - width = this.pave_width, - height = this.pave_height, - nlines = pavetext.fText.length; - - if (!nlines) return; - - const stepy = height / nlines, margin_x = 0.02 * width; - let posy = 0; + nlines = pavetext?.fText.length; - textFont.setSize(height/(nlines * 1.2)); + if (!nlines) + return; - this.startTextDrawing(textFont, 'font'); + const textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }), + width = this.pave_width, + height = this.pave_height, + stepy = height / nlines, margin_x = 0.02 * width; - for (let i = 0; i < pavetext.fText.length; ++i) { - const line = pavetext.fText[i]; + textFont.setSize(height / (nlines * 1.2)); - this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: line }); - posy += stepy; - } + return this.startTextDrawingAsync(textFont, 'font').then(() => { + for (let i = 0, posy = 0; i < pavetext.fText.length; ++i, posy += stepy) + this.drawText({ latex: 1, width: width - 2 * margin_x, height: stepy, x: margin_x, y: posy, text: pavetext.fText[i] }); - return this.finishTextDrawing(undefined, true); + return this.finishTextDrawing(undefined, true); + }); } /** @summary draw RPaveText object */ @@ -285,222 +280,4 @@ class RPaveTextPainter extends RPavePainter { } // class RPaveTextPainter -/** - * @summary Painter for RHistStats class - * - * @private - */ - -class RHistStatsPainter extends RPavePainter { - - /** @summary clear entries from stat box */ - clearStat() { - this.stats_lines = []; - } - - /** @summary add text entry to stat box */ - addText(line) { - this.stats_lines.push(line); - } - - /** @summary update statistic from the server */ - updateStatistic(reply) { - this.stats_lines = reply.lines; - this.drawStatistic(this.stats_lines); - } - - /** @summary fill statistic */ - fillStatistic() { - const pp = this.getPadPainter(); - if (pp?._fast_drawing) return false; - - const obj = this.getObject(); - if (obj.fLines !== undefined) { - this.stats_lines = obj.fLines; - delete obj.fLines; - return true; - } - - if (this.v7OfflineMode()) { - const main = this.getMainPainter(); - if (!isFunc(main?.fillStatistic)) return false; - // we take statistic from main painter - return main.fillStatistic(this, gStyle.fOptStat, gStyle.fOptFit); - } - - // show lines which are exists, maybe server request will be recieved later - return (this.stats_lines !== undefined); - } - - /** @summary format float value as string - * @private */ - format(value, fmt) { - if (!fmt) fmt = 'stat'; - - switch (fmt) { - case 'stat' : fmt = gStyle.fStatFormat; break; - case 'fit': fmt = gStyle.fFitFormat; break; - case 'entries': if ((Math.abs(value) < 1e9) && (Math.round(value) === value)) return value.toFixed(0); fmt = '14.7g'; break; - case 'last': fmt = this.lastformat; break; - } - - const res = floatToString(value, fmt || '6.4g', true); - - this.lastformat = res[1]; - - return res[0]; - } - - /** @summary Draw content */ - async drawContent() { - if (this.fillStatistic()) - return this.drawStatistic(this.stats_lines); - - return this; - } - - /** @summary Change mask */ - changeMask(nbit) { - const obj = this.getObject(), mask = (1<<nbit); - if (obj.fShowMask & mask) - obj.fShowMask = obj.fShowMask & ~mask; - else - obj.fShowMask = obj.fShowMask | mask; - - if (this.fillStatistic()) - this.drawStatistic(this.stats_lines); - } - - /** @summary Context menu */ - statsContextMenu(evnt) { - evnt.preventDefault(); - evnt.stopPropagation(); // disable main context menu - - createMenu(evnt, this).then(menu => { - const obj = this.getObject(), - action = this.changeMask.bind(this); - - menu.add('header: StatBox'); - - for (let n=0; n<obj.fEntries.length; ++n) - menu.addchk((obj.fShowMask & (1<<n)), obj.fEntries[n], n, action); - - return this.fillObjectExecMenu(menu); - }).then(menu => menu.show()); - } - - /** @summary Draw statistic */ - async drawStatistic(lines) { - if (!lines) return this; - const textFont = this.v7EvalFont('stats_text', { size: 12, color: 'black', align: 22 }), - width = this.pave_width, - height = this.pave_height, - nlines = lines.length; - let first_stat = 0, num_cols = 0, maxlen = 0; - - // adjust font size - for (let j = 0; j < nlines; ++j) { - const line = lines[j]; - if (j > 0) maxlen = Math.max(maxlen, line.length); - if ((j === 0) || (line.indexOf('|') < 0)) continue; - if (first_stat === 0) first_stat = j; - const parts = line.split('|'); - if (parts.length > num_cols) - num_cols = parts.length; - } - - // for characters like 'p' or 'y' several more pixels required to stay in the box when drawn in last line - const stepy = height / nlines, margin_x = 0.02 * width; - let has_head = false, - text_g = this.draw_g.selectChild('.statlines'); - if (text_g.empty()) - text_g = this.draw_g.append('svg:g').attr('class', 'statlines'); - else - text_g.selectAll('*').remove(); - - textFont.setSize(height/(nlines * 1.2)); - this.startTextDrawing(textFont, 'font', text_g); - - if (nlines === 1) - this.drawText({ width, height, text: lines[0], latex: 1, draw_g: text_g }); - else { - for (let j = 0; j < nlines; ++j) { - const posy = j*stepy; - - if (first_stat && (j >= first_stat)) { - const parts = lines[j].split('|'); - for (let n = 0; n < parts.length; ++n) { - this.drawText({ align: 'middle', x: width * n / num_cols, y: posy, latex: 0, - width: width/num_cols, height: stepy, text: parts[n], draw_g: text_g }); - } - } else if (lines[j].indexOf('=') < 0) { - if (j === 0) { - has_head = true; - const max_hlen = Math.max(maxlen, Math.round((width-2*margin_x)/stepy/0.65)); - if (lines[j].length > max_hlen + 5) - lines[j] = lines[j].slice(0, max_hlen+2) + '...'; - } - this.drawText({ align: (j === 0) ? 'middle' : 'start', x: margin_x, y: posy, - width: width - 2*margin_x, height: stepy, text: lines[j], draw_g: text_g }); - } else { - const parts = lines[j].split('='), args = []; - - for (let n = 0; n < 2; ++n) { - const arg = { - align: (n === 0) ? 'start' : 'end', x: margin_x, y: posy, - width: width-2*margin_x, height: stepy, text: parts[n], draw_g: text_g, - _expected_width: width-2*margin_x, _args: args, - post_process(painter) { - if (this._args[0].ready && this._args[1].ready) - painter.scaleTextDrawing(1.05*(this._args[0].result_width && this._args[1].result_width)/this.__expected_width, this.draw_g); - } - }; - args.push(arg); - } - - for (let n = 0; n < 2; ++n) - this.drawText(args[n]); - } - } - } - - let lpath = ''; - - if (has_head) - lpath += 'M0,' + Math.round(stepy) + 'h' + width; - - if ((first_stat > 0) && (num_cols > 1)) { - for (let nrow = first_stat; nrow < nlines; ++nrow) - lpath += 'M0,' + Math.round(nrow * stepy) + 'h' + width; - for (let ncol = 0; ncol < num_cols - 1; ++ncol) - lpath += 'M' + Math.round(width / num_cols * (ncol + 1)) + ',' + Math.round(first_stat * stepy) + 'V' + height; - } - - if (lpath) this.draw_g.append('svg:path').attr('d', lpath); - - return this.finishTextDrawing(text_g); - } - - /** @summary Redraw stats box */ - async redraw(reason) { - if (reason && isStr(reason) && (reason.indexOf('zoom') === 0) && this.v7NormalMode()) { - const req = { - _typename: `${nsREX}RHistStatBoxBase::RRequest`, - mask: this.getObject().fShowMask // lines to show in stat box - }; - - this.v7SubmitRequest('stat', req, reply => this.updateStatistic(reply)); - } - - return this.drawPave(); - } - - /** @summary draw RHistStats object */ - static async draw(dom, stats, opt) { - const painter = new RHistStatsPainter(dom, stats, opt, stats); - return ensureRCanvas(painter, false).then(() => painter.drawPave()); - } - -} // class RHistStatsPainter - -export { RPavePainter, RLegendPainter, RPaveTextPainter, RHistStatsPainter }; +export { RPavePainter, RLegendPainter, RPaveTextPainter }; diff --git a/modules/hist/TEfficiencyPainter.mjs b/modules/hist/TEfficiencyPainter.mjs index ee7f86861..687e719f3 100644 --- a/modules/hist/TEfficiencyPainter.mjs +++ b/modules/hist/TEfficiencyPainter.mjs @@ -1,18 +1,19 @@ import { BIT, create, createHistogram, isStr, clTH1, clTH2, clTH2F, kNoStats } from '../core.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { TGraphPainter, clTGraphAsymmErrors } from '../hist2d/TGraphPainter.mjs'; -import { TF1Painter } from '../hist/TF1Painter.mjs'; -import { TH2Painter } from '../hist2d/TH2Painter.mjs'; +import { TF1Painter } from './TF1Painter.mjs'; +import { TH1Painter } from './TH1Painter.mjs'; +import { TH2Painter } from './TH2Painter.mjs'; import { getTEfficiencyBoundaryFunc } from '../base/math.mjs'; const kIsBayesian = BIT(14), // Bayesian statistics are used kPosteriorMode = BIT(15), // Use posterior mean for best estimate (Bayesian statistics) - // kShortestInterval = BIT(16), // Use shortest interval, not implemented - too complicated + // kShortestInterval = BIT(16), // Use shortest interval, not implemented - too complicated kUseBinPrior = BIT(17), // Use a different prior for each bin kUseWeights = BIT(18), // Use weights - getBetaAlpha = (obj, bin) => (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].first : obj.fBeta_alpha, - getBetaBeta = (obj, bin) => (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].second : obj.fBeta_beta; + getBetaAlpha = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].first : obj.fBeta_alpha; }, + getBetaBeta = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].second : obj.fBeta_beta; }; /** * @summary Painter for TEfficiency object @@ -22,20 +23,24 @@ const kIsBayesian = BIT(14), // Bayesian statistics are used class TEfficiencyPainter extends ObjectPainter { - /** @summary Caluclate efficiency */ + /** @summary Calculate efficiency */ getEfficiency(obj, bin) { - const BetaMean = (a, b) => (a <= 0 || b <= 0) ? 0 : a / (a + b), + const BetaMean = (a, b) => { return (a <= 0 || b <= 0) ? 0 : a / (a + b); }, BetaMode = (a, b) => { - if (a <= 0 || b <= 0) return 0; - if (a <= 1 || b <= 1) { - if (a < b) return 0; - if (a > b) return 1; - if (a === b) return 0.5; // cannot do otherwise - } - return (a - 1.0) / (a + b -2.0); - }, - total = obj.fTotalHistogram.fArray[bin], // should work for both 1-d and 2-d - passed = obj.fPassedHistogram.fArray[bin]; // should work for both 1-d and 2-d + if (a <= 0 || b <= 0) + return 0; + if (a <= 1 || b <= 1) { + if (a < b) + return 0; + if (a > b) + return 1; + if (a === b) + return 0.5; // cannot do otherwise + } + return (a - 1.0) / (a + b - 2.0); + }, + total = obj.fTotalHistogram.fArray[bin], // should work for both 1-d and 2-d + passed = obj.fPassedHistogram.fArray[bin]; // should work for both 1-d and 2-d if (obj.TestBit(kIsBayesian)) { // parameters for the beta prior distribution @@ -45,13 +50,14 @@ class TEfficiencyPainter extends ObjectPainter { let aa, bb; if (obj.TestBit(kUseWeights)) { const tw = total, // fTotalHistogram->GetBinContent(bin); - tw2 = obj.fTotalHistogram.fSumw2 ? obj.fTotalHistogram.fSumw2[bin] : Math.abs(total), - pw = passed; // fPassedHistogram->GetBinContent(bin); + tw2 = obj.fTotalHistogram.fSumw2 ? obj.fTotalHistogram.fSumw2[bin] : Math.abs(total), + pw = passed; // fPassedHistogram->GetBinContent(bin); - if (tw2 <= 0) return pw/tw; + if (tw2 <= 0) + return pw / tw; - // tw/tw2 renormalize the weights - const norm = tw/tw2; + // tw/tw2 re-normalize the weights + const norm = tw / tw2; aa = pw * norm + alpha; bb = (tw - pw) * norm + beta; } else { @@ -59,16 +65,13 @@ class TEfficiencyPainter extends ObjectPainter { bb = total - passed + beta; } - if (!obj.TestBit(kPosteriorMode)) - return BetaMean(aa, bb); - else - return BetaMode(aa, bb); + return !obj.TestBit(kPosteriorMode) ? BetaMean(aa, bb) : BetaMode(aa, bb); } - return total ? passed/total : 0; + return total ? passed / total : 0; } - /** @summary Caluclate efficiency error low */ + /** @summary Calculate efficiency error low */ getEfficiencyErrorLow(obj, bin, value) { const total = obj.fTotalHistogram.fArray[bin], passed = obj.fPassedHistogram.fArray[bin]; @@ -81,7 +84,7 @@ class TEfficiencyPainter extends ObjectPainter { return value - this.fBoundary(total, passed, obj.fConfLevel, false, alpha, beta); } - /** @summary Caluclate efficiency error low up */ + /** @summary Calculate efficiency error low up */ getEfficiencyErrorUp(obj, bin, value) { const total = obj.fTotalHistogram.fArray[bin], passed = obj.fPassedHistogram.fArray[bin]; @@ -94,7 +97,7 @@ class TEfficiencyPainter extends ObjectPainter { return this.fBoundary(total, passed, obj.fConfLevel, true, alpha, beta) - value; } - /** @summary Copy drawning attributes */ + /** @summary Copy drawing attributes */ copyAttributes(obj, eff) { ['fLineColor', 'fLineStyle', 'fLineWidth', 'fFillColor', 'fFillStyle', 'fMarkerColor', 'fMarkerStyle', 'fMarkerSize'].forEach(name => { obj[name] = eff[name]; }); } @@ -125,16 +128,17 @@ class TEfficiencyPainter extends ObjectPainter { plot0Bins = (opt.indexOf('e0') >= 0); for (let n = 0, j = 0; n < npoints; ++n) { - if (!plot0Bins && eff.fTotalHistogram.getBinContent(n+1) === 0) continue; + if (!plot0Bins && eff.fTotalHistogram.getBinContent(n + 1) === 0) + continue; - const value = this.getEfficiency(eff, n+1); + const value = this.getEfficiency(eff, n + 1); - gr.fX[j] = xaxis.GetBinCenter(n+1); + gr.fX[j] = xaxis.GetBinCenter(n + 1); gr.fY[j] = value; - gr.fEXlow[j] = xaxis.GetBinCenter(n+1) - xaxis.GetBinLowEdge(n+1); - gr.fEXhigh[j] = xaxis.GetBinLowEdge(n+2) - xaxis.GetBinCenter(n+1); - gr.fEYlow[j] = this.getEfficiencyErrorLow(eff, n+1, value); - gr.fEYhigh[j] = this.getEfficiencyErrorUp(eff, n+1, value); + gr.fEXlow[j] = xaxis.GetBinCenter(n + 1) - xaxis.GetBinLowEdge(n + 1); + gr.fEXhigh[j] = xaxis.GetBinLowEdge(n + 2) - xaxis.GetBinCenter(n + 1); + gr.fEYlow[j] = this.getEfficiencyErrorLow(eff, n + 1, value); + gr.fEYhigh[j] = this.getEfficiencyErrorUp(eff, n + 1, value); gr.fNpoints = ++j; } @@ -149,16 +153,15 @@ class TEfficiencyPainter extends ObjectPainter { nbinsx = hist.fXaxis.fNbins, nbinsy = hist.fYaxis.fNbins; - for (let i = 0; i < nbinsx+2; ++i) { - for (let j = 0; j < nbinsy+2; ++j) { - const bin = hist.getBin(i, j), - value = this.getEfficiency(eff, bin); - hist.fArray[bin] = value; + for (let i = 0; i < nbinsx + 2; ++i) { + for (let j = 0; j < nbinsy + 2; ++j) { + const bin = hist.getBin(i, j); + hist.fArray[bin] = this.getEfficiency(eff, bin); } } hist.fTitle = eff.fTitle; - hist.fBits = hist.fBits | kNoStats; + hist.fBits |= kNoStats; this.copyAttributes(hist, eff); } @@ -169,7 +172,59 @@ class TEfficiencyPainter extends ObjectPainter { if (!eff?.fFunctions || (indx >= eff.fFunctions.arr.length)) return this; - return TF1Painter.draw(this.getDom(), eff.fFunctions.arr[indx], eff.fFunctions.opt[indx]).then(() => this.drawFunction(indx+1)); + return TF1Painter.draw(this.getPadPainter(), eff.fFunctions.arr[indx], eff.fFunctions.opt[indx]) + .then(funcp => { + funcp?.setSecondaryId(this, `func_${indx}`); + return this.drawFunction(indx + 1); + }); + } + + /** @summary Fill context menu */ + fillContextMenuItems(menu) { + menu.addRedrawMenu(this); + } + + /** @summary Fully redraw efficiency with new draw options */ + async redrawWith(opt, skip_cleanup) { + if (!skip_cleanup) + this.getPadPainter()?.removePrimitive(this, true); + + if (!opt || !isStr(opt)) + opt = ''; + opt = opt.toLowerCase(); + + let promise, draw_total = false; + + const eff = this.getObject(), + dom = this.getDrawDom(); + + if (opt[0] === 'b') { + draw_total = true; + promise = (this.ndim === 1 ? TH1Painter : TH2Painter).draw(dom, eff.fTotalHistogram, opt.slice(1)); + } else if (this.ndim === 1) { + if (!opt) + opt = 'ap'; + if ((opt.indexOf('same') < 0) && (opt.indexOf('a') < 0)) + opt += 'a'; + if (opt.indexOf('p') < 0) + opt += 'p'; + + const gr = this.createGraph(eff); + this.fillGraph(gr, opt); + promise = TGraphPainter.draw(dom, gr, opt); + } else { + if (!opt) + opt = 'col'; + const hist = this.createHisto(eff); + this.fillHisto(hist, opt); + promise = TH2Painter.draw(dom, hist, opt); + } + + return promise.then(subp => { + subp?.setSecondaryId(this, 'eff'); + this.addToPadPrimitives(); + return draw_total ? this : this.drawFunction(0); + }); } /** @summary Draw TEfficiency object */ @@ -177,43 +232,18 @@ class TEfficiencyPainter extends ObjectPainter { if (!eff || !eff.fTotalHistogram) return null; - if (!opt || !isStr(opt)) opt = ''; - opt = opt.toLowerCase(); + const painter = new TEfficiencyPainter(dom, eff); - let ndim = 0; if (eff.fTotalHistogram._typename.indexOf(clTH1) === 0) - ndim = 1; + painter.ndim = 1; else if (eff.fTotalHistogram._typename.indexOf(clTH2) === 0) - ndim = 2; + painter.ndim = 2; else return null; - const painter = new TEfficiencyPainter(dom, eff); - painter.ndim = ndim; - painter.fBoundary = getTEfficiencyBoundaryFunc(eff.fStatisticOption, eff.TestBit(kIsBayesian)); - let promise; - - if (ndim === 1) { - if (!opt) opt = 'ap'; - if ((opt.indexOf('same') < 0) && (opt.indexOf('a') < 0)) opt += 'a'; - if (opt.indexOf('p') < 0) opt += 'p'; - - const gr = painter.createGraph(eff); - painter.fillGraph(gr, opt); - promise = TGraphPainter.draw(dom, gr, opt); - } else { - if (!opt) opt = 'col'; - const hist = painter.createHisto(eff); - painter.fillHisto(hist, opt); - promise = TH2Painter.draw(dom, hist, opt); - } - - return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawFunction(0); - }); + return painter.redrawWith(opt, true); } } // class TEfficiencyPainter diff --git a/modules/hist/TF1Painter.mjs b/modules/hist/TF1Painter.mjs index 788411058..3fed5f604 100644 --- a/modules/hist/TF1Painter.mjs +++ b/modules/hist/TF1Painter.mjs @@ -1,6 +1,6 @@ -import { settings, gStyle, isStr, isFunc, clTH1D, createHistogram, setHistogramTitle, clTF1, kNoStats } from '../core.mjs'; +import { settings, gStyle, isStr, isFunc, clTH1D, createHistogram, setHistogramTitle, clTF1, clTF12, kNoStats } from '../core.mjs'; import { floatToString } from '../base/BasePainter.mjs'; -import { getElementMainPainter, ObjectPainter } from '../base/ObjectPainter.mjs'; +import { getElementPadPainter, getElementMainPainter } from '../base/ObjectPainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; import { TH1Painter } from '../hist2d/TH1Painter.mjs'; import { proivdeEvalPar, _getTF1Save } from '../base/func.mjs'; @@ -28,23 +28,24 @@ function produceTAxisLogScale(axis, num, min, max) { } function scanTF1Options(opt) { - if (!isStr(opt)) opt = ''; - let p = opt.indexOf(';webcanv_hist'), webcanv_hist = false, use_saved = 0; + if (!isStr(opt)) + opt = ''; + let p = opt.indexOf(';webcanv_hist'), _webcanv_hist = false, _use_saved = 0; if (p >= 0) { - webcanv_hist = true; + _webcanv_hist = true; opt = opt.slice(0, p); } p = opt.indexOf(';force_saved'); if (p >= 0) { - use_saved = 2; + _use_saved = 2; opt = opt.slice(0, p); } p = opt.indexOf(';prefer_saved'); if (p >= 0) { - use_saved = 1; + _use_saved = 1; opt = opt.slice(0, p); } - return { opt, webcanv_hist, use_saved }; + return { opt, _webcanv_hist, _use_saved }; } @@ -56,30 +57,42 @@ function scanTF1Options(opt) { class TF1Painter extends TH1Painter { + #use_saved_points; // use saved points for drawing + #func; // func object + #tmp_tooltip; // temporary tooltip + #fail_eval; // fail evaluation of function + + /** @summary Assign function */ + setFunc(f) { this.#func = f; } + /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + getObjectName() { return this.#func?.fName ?? 'func'; } /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF1; } + getClassName() { return this.#func?._typename ?? clTF1; } /** @summary Returns true while function is drawn */ isTF1() { return true; } + isTF12() { return this.getClassName() === clTF12; } + /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } + getPrimaryObject() { return this.#func; } /** @summary Update function */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; delete obj.evalPar; const histo = this.getHisto(); - if (this.webcanv_hist) { + if (this._webcanv_hist) { const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH1D); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); } - this.$func = obj; + this.setFunc(obj); this.createTF1Histogram(obj, histo); this.scanContent(); return true; @@ -88,8 +101,8 @@ class TF1Painter extends TH1Painter { /** @summary Redraw TF1 * @private */ redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'zoom')) { - this.createTF1Histogram(this.$func, this.getHisto()); + if (!this.#use_saved_points && (reason === 'logx' || reason === 'zoom')) { + this.createTF1Histogram(this.#func, this.getHisto()); this.scanContent(); } @@ -113,7 +126,7 @@ class TF1Painter extends TH1Painter { xmax = Math.min(xmax, gr.zoom_xmax + dx); } - this._use_saved_points = (tf1.fSave.length > 3) && (settings.PreferSavedPoints || (this.use_saved > 1)); + this.#use_saved_points = (tf1.fSave.length > 3) && (settings.PreferSavedPoints || (this._use_saved > 1)); const ensureBins = num => { if (hist.fNcells !== num + 2) { @@ -125,16 +138,23 @@ class TF1Painter extends TH1Painter { hist.fXaxis.fXbins = []; }; - delete this._fail_eval; + this.#fail_eval = undefined; - // this._use_saved_points = true; + // this.#use_saved_points = true; - if (!this._use_saved_points) { + if (!this.#use_saved_points) { let iserror = false; if (!tf1.evalPar) { try { - if (!proivdeEvalPar(tf1)) + if (this.isTF12()) { + if (proivdeEvalPar(tf1.fF2)) { + tf1.evalPar = function(x) { + return this.fCase ? this.fF2.evalPar(x, this.fXY) : this.fF2.evalPar(this.fXY, x); + }; + } else + iserror = true; + } else if (!proivdeEvalPar(tf1)) iserror = true; } catch { iserror = true; @@ -145,7 +165,7 @@ class TF1Painter extends TH1Painter { if (logx) produceTAxisLogScale(hist.fXaxis, np, xmin, xmax); - else { + else { hist.fXaxis.fXmin = xmin; hist.fXaxis.fXmax = xmax; } @@ -155,7 +175,7 @@ class TF1Painter extends TH1Painter { let y = 0; try { y = tf1.evalPar(x); - } catch (err) { + } catch { iserror = true; } @@ -164,22 +184,21 @@ class TF1Painter extends TH1Painter { } if (iserror) - this._fail_eval = true; + this.#fail_eval = true; if (iserror && (tf1.fSave.length > 3)) - this._use_saved_points = true; + this.#use_saved_points = true; } // in the case there were points have saved and we cannot calculate function // if we don't have the user's function - if (this._use_saved_points) { + if (this.#use_saved_points) { np = tf1.fSave.length - 3; let custom_xaxis = null; xmin = tf1.fSave[np + 1]; xmax = tf1.fSave[np + 2]; if (xmin === xmax) { - // xmin = tf1.fSave[np]; const mp = this.getMainPainter(); if (isFunc(mp?.getHisto)) custom_xaxis = mp?.getHisto()?.fXaxis; @@ -225,9 +244,9 @@ class TF1Painter extends TH1Painter { extractAxesProperties(ndim) { super.extractAxesProperties(ndim); - const func = this.$func, nsave = func?.fSave.length ?? 0; + const func = this.#func, nsave = func?.fSave.length ?? 0; - if (nsave > 3 && this._use_saved_points) { + if (nsave > 3 && this.#use_saved_points) { this.xmin = Math.min(this.xmin, func.fSave[nsave - 2]); this.xmax = Math.max(this.xmax, func.fSave[nsave - 1]); } @@ -239,13 +258,13 @@ class TF1Painter extends TH1Painter { /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis, min, max) { - const nsave = this.$func?.fSave.length ?? 0; - if ((nsave > 3) && this._use_saved_points && (axis === 'x')) { + const nsave = this.#func?.fSave.length ?? 0; + if ((nsave > 3) && this.#use_saved_points && (axis === 'x')) { // in the case where the points have been saved, useful for example // if we don't have the user's function const nb_points = nsave - 2, - xmin = this.$func.fSave[nsave - 2], - xmax = this.$func.fSave[nsave - 1]; + xmin = this.#func.fSave[nsave - 2], + xmax = this.#func.fSave[nsave - 1]; return Math.abs(xmax - xmin) / nb_points < Math.abs(max - min); } @@ -254,13 +273,14 @@ class TF1Painter extends TH1Painter { return (axis === 'x') || (axis === 'y'); } - /** @summary retrurn tooltips for TF2 */ + /** @summary return tooltips for TF2 */ getTF1Tooltips(pnt) { - delete this.$tmp_tooltip; + this.#tmp_tooltip = undefined; const lines = [this.getObjectHint()], - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); - if (!funcs || !isFunc(this.$func?.evalPar)) { + if (!funcs || !isFunc(this.#func?.evalPar)) { lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); return lines; } @@ -268,63 +288,66 @@ class TF1Painter extends TH1Painter { const x = funcs.revertAxis('x', pnt.x); let y = 0, gry = 0, iserror = false; - try { - y = this.$func.evalPar(x); - gry = Math.round(funcs.gry(y)); - } catch { - iserror = true; - } + try { + y = this.#func.evalPar(x); + gry = Math.round(funcs.gry(y)); + } catch { + iserror = true; + } lines.push('x = ' + funcs.axisAsText('x', x), 'value = ' + (iserror ? '<fail>' : floatToString(y, gStyle.fStatFormat))); if (!iserror) - this.$tmp_tooltip = { y, gry }; + this.#tmp_tooltip = { y, gry }; return lines; } /** @summary process tooltip event for TF1 object */ processTooltipEvent(pnt) { - if (this._use_saved_points) + if (this.#use_saved_points) return super.processTooltipEvent(pnt); - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - if (!this.draw_g || !pnt) { + if (!this.getG() || !pnt) { ttrect?.remove(); return null; } - const res = { name: this.$func?.fName, title: this.$func?.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getTF1Tooltips(pnt), exact: true, menu: true }; + const res = { + name: this.#func?.fName, title: this.#func?.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getTF1Tooltips(pnt), exact: true, menu: true + }; if (pnt.disabled) ttrect.remove(); else { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .style('fill', 'none') - .attr('r', (this.lineatt?.width ?? 1) + 4); + ttrect = this.getG().append('svg:circle') + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .style('fill', 'none') + .attr('r', (this.lineatt?.width ?? 1) + 4); } ttrect.attr('cx', pnt.x) - .attr('cy', this.$tmp_tooltip.gry ?? pnt.y) - .call(this.lineatt?.func); + .attr('cy', this.#tmp_tooltip?.gry ?? pnt.y); + if (this.lineatt) + ttrect.call(this.lineatt.func); } return res; } /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed + * @desc Used to inform web canvas when evaluation failed * @private */ fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } /** @summary draw TF1 object */ @@ -334,10 +357,8 @@ class TF1Painter extends TH1Painter { delete web.opt; let hist; - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH1D); - } + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH1D); if (!hist) { hist = createHistogram(clTH1D, 100); @@ -349,7 +370,7 @@ class TF1Painter extends TH1Painter { const painter = new TF1Painter(dom, hist); - painter.$func = tf1; + painter.setFunc(tf1); Object.assign(painter, web); painter.createTF1Histogram(tf1, hist); diff --git a/modules/hist/TF2Painter.mjs b/modules/hist/TF2Painter.mjs index 4c13b0326..af6d14431 100644 --- a/modules/hist/TF2Painter.mjs +++ b/modules/hist/TF2Painter.mjs @@ -1,8 +1,8 @@ -import { createHistogram, setHistogramTitle, kNoStats, settings, gStyle, clTF2, clTH2F, isStr, isFunc } from '../core.mjs'; +import { createHistogram, setHistogramTitle, kNoStats, settings, gStyle, clTF2, clTH2F, isFunc } from '../core.mjs'; import { TH2Painter } from '../hist/TH2Painter.mjs'; import { proivdeEvalPar } from '../base/func.mjs'; import { produceTAxisLogScale, scanTF1Options } from '../hist/TF1Painter.mjs'; -import { ObjectPainter, getElementMainPainter } from '../base/ObjectPainter.mjs'; +import { getElementPadPainter } from '../base/ObjectPainter.mjs'; import { DrawOptions, floatToString } from '../base/BasePainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; @@ -15,30 +15,39 @@ import { THistPainter } from '../hist2d/THistPainter.mjs'; class TF2Painter extends TH2Painter { + #use_saved_points; // use saved points for drawing + #func; // func object + #fail_eval; // fail evaluation of function + + /** @summary Assign function */ + setFunc(f) { this.#func = f; } + /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + getObjectName() { return this.#func?.fName ?? 'func'; } /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF2; } + getClassName() { return this.#func?._typename ?? clTF2; } /** @summary Returns true while function is drawn */ isTF1() { return true; } /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } + getPrimaryObject() { return this.#func; } /** @summary Update histogram */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; delete obj.evalPar; const histo = this.getHisto(); - if (this.webcanv_hist) { + if (this._webcanv_hist) { const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); } - this.$func = obj; + this.setFunc(obj); this.createTF2Histogram(obj, histo); this.scanContent(); return true; @@ -47,8 +56,8 @@ class TF2Painter extends TH2Painter { /** @summary Redraw TF2 * @private */ redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'zoom')) { - this.createTF2Histogram(this.$func, this.getHisto()); + if (!this.#use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'zoom')) { + this.createTF2Histogram(this.#func, this.getHisto()); this.scanContent(); } @@ -59,10 +68,10 @@ class TF2Painter extends TH2Painter { * @private */ createTF2Histogram(func, hist) { let nsave = func.fSave.length - 6; - if ((nsave > 0) && (nsave !== (func.fSave[nsave+4]+1) * (func.fSave[nsave+5]+1))) + if ((nsave > 0) && (nsave !== (func.fSave[nsave + 4] + 1) * (func.fSave[nsave + 5] + 1))) nsave = 0; - this._use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this.use_saved > 1)); + this.#use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this._use_saved > 1)); const fp = this.getFramePainter(), pad = this.getPadPainter()?.getRootPad(true), @@ -101,9 +110,9 @@ class TF2Painter extends TH2Painter { hist.fYaxis.fXbins = []; }; - delete this._fail_eval; + this.#fail_eval = undefined; - if (!this._use_saved_points) { + if (!this.#use_saved_points) { let iserror = false; if (!func.evalPar && !proivdeEvalPar(func)) @@ -122,8 +131,8 @@ class TF2Painter extends TH2Painter { for (let j = 0; (j < npy) && !iserror; ++j) { for (let i = 0; (i < npx) && !iserror; ++i) { - const x = hist.fXaxis.GetBinCenter(i+1), - y = hist.fYaxis.GetBinCenter(j+1); + const x = hist.fXaxis.GetBinCenter(i + 1), + y = hist.fYaxis.GetBinCenter(j + 1); let z = 0; try { @@ -138,36 +147,36 @@ class TF2Painter extends TH2Painter { } if (iserror) - this._fail_eval = true; + this.#fail_eval = true; if (iserror && (nsave > 6)) - this._use_saved_points = true; + this.#use_saved_points = true; } - if (this._use_saved_points) { - npx = Math.round(func.fSave[nsave+4]); - npy = Math.round(func.fSave[nsave+5]); - const xmin = func.fSave[nsave], xmax = func.fSave[nsave+1], - ymin = func.fSave[nsave+2], ymax = func.fSave[nsave+3], - dx = (xmax - xmin) / npx, - dy = (ymax - ymin) / npy; - function getSave(x, y) { - if (x < xmin || x > xmax) return 0; - if (dx <= 0) return 0; - if (y < ymin || y > ymax) return 0; - if (dy <= 0) return 0; - const ibin = Math.min(npx-1, Math.floor((x-xmin)/dx)), - jbin = Math.min(npy-1, Math.floor((y-ymin)/dy)), - xlow = xmin + ibin*dx, - ylow = ymin + jbin*dy, - t = (x-xlow)/dx, - u = (y-ylow)/dy, - k1 = jbin*(npx+1) + ibin, - k2 = jbin*(npx+1) + ibin +1, - k3 = (jbin+1)*(npx+1) + ibin +1, - k4 = (jbin+1)*(npx+1) + ibin; - return (1-t)*(1-u)*func.fSave[k1] +t*(1-u)*func.fSave[k2] +t*u*func.fSave[k3] + (1-t)*u*func.fSave[k4]; - } + if (this.#use_saved_points) { + npx = Math.round(func.fSave[nsave + 4]); + npy = Math.round(func.fSave[nsave + 5]); + xmin = func.fSave[nsave]; + xmax = func.fSave[nsave + 1]; + ymin = func.fSave[nsave + 2]; + ymax = func.fSave[nsave + 3]; + const dx = (xmax - xmin) / npx, dy = (ymax - ymin) / npy, getSave = (x, y) => { + if (x < xmin || x > xmax || dx <= 0) + return 0; + if (y < ymin || y > ymax || dy <= 0) + return 0; + const ibin = Math.min(npx - 1, Math.floor((x - xmin) / dx)), + jbin = Math.min(npy - 1, Math.floor((y - ymin) / dy)), + xlow = xmin + ibin * dx, + ylow = ymin + jbin * dy, + t = (x - xlow) / dx, + u = (y - ylow) / dy, + k1 = jbin * (npx + 1) + ibin, + k2 = jbin * (npx + 1) + ibin + 1, + k3 = (jbin + 1) * (npx + 1) + ibin + 1, + k4 = (jbin + 1) * (npx + 1) + ibin; + return (1 - t) * (1 - u) * func.fSave[k1] + t * (1 - u) * func.fSave[k2] + t * u * func.fSave[k3] + (1 - t) * u * func.fSave[k4]; + }; ensureBins(func.fNpx, func.fNpy); hist.fXaxis.fXmin = func.fXmin; @@ -180,7 +189,7 @@ class TF2Painter extends TH2Painter { for (let i = 0; i < func.fNpx; ++i) { const x = hist.fXaxis.GetBinCenter(i + 1), z = getSave(x, y); - hist.setBinContent(hist.getBin(i+1, j+1), Number.isFinite(z) ? z : 0); + hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); } } } @@ -207,13 +216,13 @@ class TF2Painter extends TH2Painter { extractAxesProperties(ndim) { super.extractAxesProperties(ndim); - const func = this.$func, nsave = func?.fSave.length ?? 0; + const func = this.#func, nsave = func?.fSave.length ?? 0; - if (nsave > 6 && this._use_saved_points) { - this.xmin = Math.min(this.xmin, func.fSave[nsave-6]); - this.xmax = Math.max(this.xmax, func.fSave[nsave-5]); - this.ymin = Math.min(this.ymin, func.fSave[nsave-4]); - this.ymax = Math.max(this.ymax, func.fSave[nsave-3]); + if (nsave > 6 && this.#use_saved_points) { + this.xmin = Math.min(this.xmin, func.fSave[nsave - 6]); + this.xmax = Math.max(this.xmax, func.fSave[nsave - 5]); + this.ymin = Math.min(this.ymin, func.fSave[nsave - 4]); + this.ymax = Math.max(this.ymax, func.fSave[nsave - 3]); } if (func) { this.xmin = Math.min(this.xmin, func.fXmin); @@ -223,12 +232,13 @@ class TF2Painter extends TH2Painter { } } - /** @summary retrurn tooltips for TF2 */ + /** @summary return tooltips for TF2 */ getTF2Tooltips(pnt) { const lines = [this.getObjectHint()], - funcs = this.getFramePainter()?.getGrFuncs(this.options.second_x, this.options.second_y); + o = this.getOptions(), + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); - if (!funcs || !isFunc(this.$func?.evalPar)) { + if (!funcs || !isFunc(this.#func?.evalPar)) { lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); return lines; } @@ -237,11 +247,11 @@ class TF2Painter extends TH2Painter { y = funcs.revertAxis('y', pnt.y); let z = 0, iserror = false; - try { - z = this.$func.evalPar(x, y); - } catch { - iserror = true; - } + try { + z = this.#func.evalPar(x, y); + } catch { + iserror = true; + } lines.push('x = ' + funcs.axisAsText('x', x), 'y = ' + funcs.axisAsText('y', y), @@ -251,27 +261,29 @@ class TF2Painter extends TH2Painter { /** @summary process tooltip event for TF2 object */ processTooltipEvent(pnt) { - if (this._use_saved_points) + if (this.#use_saved_points) return super.processTooltipEvent(pnt); - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - if (!this.draw_g || !pnt) { + if (!this.getG() || !pnt) { ttrect?.remove(); return null; } - const res = { name: this.$func?.fName, title: this.$func?.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getTF2Tooltips(pnt), exact: true, menu: true }; + const res = { + name: this.#func?.fName, title: this.#func?.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getTF2Tooltips(pnt), exact: true, menu: true + }; if (pnt.disabled) ttrect.remove(); else { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') + ttrect = this.getG().append('svg:circle') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .style('fill', 'none') @@ -279,18 +291,19 @@ class TF2Painter extends TH2Painter { } ttrect.attr('cx', pnt.x) - .attr('cy', pnt.y) - .call(this.lineatt?.func); + .attr('cy', pnt.y); + if (this.lineatt) + ttrect.call(this.lineatt.func); } return res; } /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed + * @desc Used to inform web canvas when evaluation failed * @private */ fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } /** @summary draw TF2 object */ @@ -305,22 +318,10 @@ class TF2Painter extends TH2Painter { else if (d.opt === 'SAME') opt = 'cont2 same'; - // workaround for old waves.C - const o2 = isStr(opt) ? opt.toUpperCase() : ''; - if (o2 === 'SAMECOLORZ' || o2 === 'SAMECOLOR' || o2 === 'SAMECOLZ') - opt = 'samecol'; - - if ((opt.indexOf('same') === 0) || (opt.indexOf('SAME') === 0)) { - if (!getElementMainPainter(dom)) - opt = 'A_ADJUST_FRAME_' + opt.slice(4); - } - let hist; - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH2F); - } + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH2F); if (!hist) { hist = createHistogram(clTH2F, 20, 20); @@ -329,9 +330,10 @@ class TF2Painter extends TH2Painter { const painter = new TF2Painter(dom, hist); - painter.$func = tf2; + painter.setFunc(tf2); Object.assign(painter, web); painter.createTF2Histogram(tf2, hist); + return THistPainter._drawHist(painter, opt); } diff --git a/modules/hist/TF3Painter.mjs b/modules/hist/TF3Painter.mjs index 328cbe32e..0a177131a 100644 --- a/modules/hist/TF3Painter.mjs +++ b/modules/hist/TF3Painter.mjs @@ -2,8 +2,8 @@ import { createHistogram, setHistogramTitle, kNoStats, settings, clTF3, clTH2F } import { TH2Painter } from '../hist/TH2Painter.mjs'; import { proivdeEvalPar } from '../base/func.mjs'; import { produceTAxisLogScale, scanTF1Options } from '../hist/TF1Painter.mjs'; -import { ObjectPainter, getElementMainPainter } from '../base/ObjectPainter.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; +import { getElementPadPainter } from '../base/ObjectPainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; @@ -11,12 +11,13 @@ function findZValue(arrz, arrv, cross = 0) { for (let i = arrz.length - 2; i >= 0; --i) { const v1 = arrv[i], v2 = arrv[i + 1], z1 = arrz[i], z2 = arrz[i + 1]; - if (v1 === cross) return z1; - if (v2 === cross) return z2; + if (v1 === cross) + return z1; + if (v2 === cross) + return z2; if ((v1 < cross) !== (v2 < cross)) return z1 + (cross - v1) / (v2 - v1) * (z2 - z1); } - return arrz[0] - 1; } @@ -29,30 +30,39 @@ function findZValue(arrz, arrv, cross = 0) { class TF3Painter extends TH2Painter { + #use_saved_points; // use saved points for drawing + #func; // func object + #fail_eval; // fail evaluation of function + + /** @summary Assign function */ + setFunc(f) { this.#func = f; } + /** @summary Returns drawn object name */ - getObjectName() { return this.$func?.fName ?? 'func'; } + getObjectName() { return this.#func?.fName ?? 'func'; } /** @summary Returns drawn object class name */ - getClassName() { return this.$func?._typename ?? clTF3; } + getClassName() { return this.#func?._typename ?? clTF3; } /** @summary Returns true while function is drawn */ isTF1() { return true; } /** @summary Returns primary function which was then drawn as histogram */ - getPrimaryObject() { return this.$func; } + getPrimaryObject() { return this.#func; } /** @summary Update histogram */ - updateObject(obj /*, opt */) { - if (!obj || (this.getClassName() !== obj._typename)) return false; + updateObject(obj /* , opt */) { + if (!obj || (this.getClassName() !== obj._typename)) + return false; delete obj.evalPar; const histo = this.getHisto(); - if (this.webcanv_hist) { + if (this._webcanv_hist) { const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); - if (h0) this.updateAxes(histo, h0, this.getFramePainter()); + if (h0) + this.updateAxes(histo, h0, this.getFramePainter()); } - this.$func = obj; + this.setFunc(obj); this.createTF3Histogram(obj, histo); this.scanContent(); return true; @@ -61,8 +71,8 @@ class TF3Painter extends TH2Painter { /** @summary Redraw TF2 * @private */ redraw(reason) { - if (!this._use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'logy' || reason === 'zoom')) { - this.createTF3Histogram(this.$func, this.getHisto()); + if (!this.#use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'logy' || reason === 'zoom')) { + this.createTF3Histogram(this.#func, this.getHisto()); this.scanContent(); } @@ -74,7 +84,7 @@ class TF3Painter extends TH2Painter { createTF3Histogram(func, hist) { const nsave = func.fSave.length - 9; - this._use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this.use_saved > 1)); + this.#use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this._use_saved > 1)); const fp = this.getFramePainter(), pad = this.getPadPainter()?.getRootPad(true), @@ -129,9 +139,9 @@ class TF3Painter extends TH2Painter { hist.fMaximum = zmax; }; - delete this._fail_eval; + this.#fail_eval = undefined; - if (!this._use_saved_points) { + if (!this.#use_saved_points) { let iserror = false; if (!func.evalPar && !proivdeEvalPar(func)) @@ -150,8 +160,8 @@ class TF3Painter extends TH2Painter { for (let j = 0; (j < npy) && !iserror; ++j) { for (let i = 0; (i < npx) && !iserror; ++i) { - const x = hist.fXaxis.GetBinCenter(i+1), - y = hist.fYaxis.GetBinCenter(j+1); + const x = hist.fXaxis.GetBinCenter(i + 1), + y = hist.fYaxis.GetBinCenter(j + 1); let z = 0; try { @@ -169,33 +179,36 @@ class TF3Painter extends TH2Painter { } if (iserror) - this._fail_eval = true; + this.#fail_eval = true; if (iserror && (nsave > 0)) - this._use_saved_points = true; + this.#use_saved_points = true; } - if (this._use_saved_points) { - xmin = func.fSave[nsave]; xmax = func.fSave[nsave+1]; - ymin = func.fSave[nsave+2]; ymax = func.fSave[nsave+3]; - zmin = func.fSave[nsave+4]; zmax = func.fSave[nsave+5]; - npx = Math.round(func.fSave[nsave+6]); - npy = Math.round(func.fSave[nsave+7]); - npz = Math.round(func.fSave[nsave+8]); - // dx = (xmax - xmin) / npx, - // dy = (ymax - ymin) / npy, + if (this.#use_saved_points) { + xmin = func.fSave[nsave]; + xmax = func.fSave[nsave + 1]; + ymin = func.fSave[nsave + 2]; + ymax = func.fSave[nsave + 3]; + zmin = func.fSave[nsave + 4]; + zmax = func.fSave[nsave + 5]; + npx = Math.round(func.fSave[nsave + 6]); + npy = Math.round(func.fSave[nsave + 7]); + npz = Math.round(func.fSave[nsave + 8]); + const dz = (zmax - zmin) / npz; ensureBins(npx + 1, npy + 1); - const arrv = new Array(npz + 1), arrz = new Array(npz + 1); + const arrv = new Array(npz + 1), + arrz = new Array(npz + 1); for (let k = 0; k <= npz; k++) - arrz[k] = zmin + k*dz; + arrz[k] = zmin + k * dz; for (let i = 0; i <= npx; ++i) { for (let j = 0; j <= npy; ++j) { for (let k = 0; k <= npz; k++) - arrv[k] = func.fSave[i + (npx + 1)*(j + (npy + 1)*k)]; + arrv[k] = func.fSave[i + (npx + 1) * (j + (npy + 1) * k)]; const z = findZValue(arrz, arrv); hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); } @@ -226,15 +239,15 @@ class TF3Painter extends TH2Painter { extractAxesProperties(ndim) { super.extractAxesProperties(ndim); - const func = this.$func, nsave = func?.fSave.length ?? 0; + const func = this.#func, nsave = func?.fSave.length ?? 0; - if (nsave > 9 && this._use_saved_points) { - this.xmin = Math.min(this.xmin, func.fSave[nsave-9]); - this.xmax = Math.max(this.xmax, func.fSave[nsave-8]); - this.ymin = Math.min(this.ymin, func.fSave[nsave-7]); - this.ymax = Math.max(this.ymax, func.fSave[nsave-6]); - this.zmin = Math.min(this.zmin, func.fSave[nsave-5]); - this.zmax = Math.max(this.zmax, func.fSave[nsave-4]); + if (nsave > 9 && this.#use_saved_points) { + this.xmin = Math.min(this.xmin, func.fSave[nsave - 9]); + this.xmax = Math.max(this.xmax, func.fSave[nsave - 8]); + this.ymin = Math.min(this.ymin, func.fSave[nsave - 7]); + this.ymax = Math.max(this.ymax, func.fSave[nsave - 6]); + this.zmin = Math.min(this.zmin, func.fSave[nsave - 5]); + this.zmax = Math.max(this.zmax, func.fSave[nsave - 4]); } if (func) { this.xmin = Math.min(this.xmin, func.fXmin); @@ -247,10 +260,10 @@ class TF3Painter extends TH2Painter { } /** @summary fill information for TWebCanvas - * @desc Used to inform webcanvas when evaluation failed + * @desc Used to inform web canvas when evaluation failed * @private */ fillWebObjectOptions(opt) { - opt.fcust = this._fail_eval && !this.use_saved ? 'func_fail' : ''; + opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } /** @summary draw TF3 object */ @@ -265,17 +278,10 @@ class TF3Painter extends TH2Painter { else if (d.opt === 'SAME') opt = 'surf1 same'; - if ((opt.indexOf('same') === 0) || (opt.indexOf('SAME') === 0)) { - if (!getElementMainPainter(dom)) - opt = 'A_ADJUST_FRAME_' + opt.slice(4); - } - let hist; - if (web.webcanv_hist) { - const dummy = new ObjectPainter(dom); - hist = dummy.getPadPainter()?.findInPrimitives('Func', clTH2F); - } + if (web._webcanv_hist) + hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH2F); if (!hist) { hist = createHistogram(clTH2F, 20, 20); @@ -284,7 +290,7 @@ class TF3Painter extends TH2Painter { const painter = new TF3Painter(dom, hist); - painter.$func = tf3; + painter.setFunc(tf3, clTF3); Object.assign(painter, web); painter.createTF3Histogram(tf3, hist); return THistPainter._drawHist(painter, opt); diff --git a/modules/hist/TGraph2DPainter.mjs b/modules/hist/TGraph2DPainter.mjs index 2cc69417b..6deaf2f7a 100644 --- a/modules/hist/TGraph2DPainter.mjs +++ b/modules/hist/TGraph2DPainter.mjs @@ -1,11 +1,10 @@ -import { settings, createHistogram, setHistogramTitle, kNoZoom, - clTH2F, clTGraph2DErrors, clTGraph2DAsymmErrors, clTPaletteAxis, kNoStats } from '../core.mjs'; -import { Color, DoubleSide, LineBasicMaterial, MeshBasicMaterial, Mesh } from '../three.mjs'; -import { DrawOptions } from '../base/BasePainter.mjs'; +import { settings, createHistogram, setHistogramTitle, getPromise, kNoZoom, + clTH2D, clTGraph2DErrors, clTGraph2DAsymmErrors, clTPaletteAxis, kNoStats } from '../core.mjs'; +import { buildSvgCurve, DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { TH2Painter } from './TH2Painter.mjs'; import { Triangles3DHandler } from '../hist2d/TH2Painter.mjs'; -import { createLineSegments, PointsCreator, getMaterialArgs } from '../base/base3d.mjs'; +import { createLineSegments, PointsCreator, getMaterialArgs, THREE } from '../base/base3d.mjs'; import { convertLegoBuf, createLegoGeom } from './hist3d.mjs'; function getMax(arr) { @@ -22,7 +21,7 @@ function getMin(arr) { return v; } -function TMath_Sort(np, values, indicies /*, down */) { +function TMath_Sort(np, values, indicies /* , down */) { const arr = new Array(np); for (let i = 0; i < np; ++i) arr[i] = { v: values[i], i }; @@ -75,20 +74,21 @@ class TGraphDelaunay { } ComputeZ(x, y) { - // Initialise the Delaunay algorithm if needed. + // Initialize the Delaunay algorithm if needed. // CreateTrianglesDataStructure computes fXoffset, fYoffset, // fXScaleFactor and fYScaleFactor; // needed in this function. this.Initialize(); // Find the z value corresponding to the point (x,y). - const xx = (x+this.fXoffset)*this.fXScaleFactor, - yy = (y+this.fYoffset)*this.fYScaleFactor; + const xx = (x + this.fXoffset) * this.fXScaleFactor, + yy = (y + this.fYoffset) * this.fYScaleFactor; let zz = this.Interpolate(xx, yy); // Wrong zeros may appear when points sit on a regular grid. // The following line try to avoid this problem. - if (zz === 0) zz = this.Interpolate(xx+0.0001, yy); + if (zz === 0) + zz = this.Interpolate(xx + 0.0001, yy); return zz; } @@ -102,19 +102,19 @@ class TGraphDelaunay { ymax = getMax(this.fGraph2D.fY), xmin = getMin(this.fGraph2D.fX), ymin = getMin(this.fGraph2D.fY); - this.fXoffset = -(xmax+xmin)/2; - this.fYoffset = -(ymax+ymin)/2; - this.fXScaleFactor = 1/(xmax-xmin); - this.fYScaleFactor = 1/(ymax-ymin); - this.fXNmax = (xmax+this.fXoffset)*this.fXScaleFactor; - this.fXNmin = (xmin+this.fXoffset)*this.fXScaleFactor; - this.fYNmax = (ymax+this.fYoffset)*this.fYScaleFactor; - this.fYNmin = (ymin+this.fYoffset)*this.fYScaleFactor; - this.fXN = new Array(this.fNpoints+1); - this.fYN = new Array(this.fNpoints+1); + this.fXoffset = -(xmax + xmin) / 2; + this.fYoffset = -(ymax + ymin) / 2; + this.fXScaleFactor = 1 / (xmax - xmin); + this.fYScaleFactor = 1 / (ymax - ymin); + this.fXNmax = (xmax + this.fXoffset) * this.fXScaleFactor; + this.fXNmin = (xmin + this.fXoffset) * this.fXScaleFactor; + this.fYNmax = (ymax + this.fYoffset) * this.fYScaleFactor; + this.fYNmin = (ymin + this.fYoffset) * this.fYScaleFactor; + this.fXN = new Array(this.fNpoints + 1); + this.fYN = new Array(this.fNpoints + 1); for (let n = 0; n < this.fNpoints; n++) { - this.fXN[n+1] = (this.fX[n]+this.fXoffset)*this.fXScaleFactor; - this.fYN[n+1] = (this.fY[n]+this.fYoffset)*this.fYScaleFactor; + this.fXN[n + 1] = (this.fX[n] + this.fXoffset) * this.fXScaleFactor; + this.fYN[n + 1] = (this.fY[n] + this.fYoffset) * this.fYScaleFactor; } // If needed, creates the arrays to hold the Delaunay triangles. @@ -126,7 +126,7 @@ class TGraphDelaunay { } - /// Is point e inside the triangle t1-t2-t3 ? + // Is point e inside the triangle t1-t2-t3 ? Enclose(t1, t2, t3, e) { const x = [this.fXN[t1], this.fXN[t2], this.fXN[t3], this.fXN[t1]], @@ -136,8 +136,8 @@ class TGraphDelaunay { let i = 0, j = x.length - 1, oddNodes = false; for (; i < x.length; ++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) + 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; @@ -147,18 +147,24 @@ class TGraphDelaunay { } - /// Files the triangle defined by the 3 vertices p, n and m into the - /// fxTried arrays. If these arrays are to small they are automatically - /// expanded. + // Files the triangle defined by the 3 vertices p, n and m into the + // fxTried arrays. If these arrays are to small they are automatically + // expanded. FileIt(p, n, m) { - let swap, tmp, ps = p, ns = n, ms = m; + let swap, ps = p, ns = n, ms = m; // order the vertices before storing them do { swap = false; - if (ns > ps) { tmp = ps; ps = ns; ns = tmp; swap = true; } - if (ms > ns) { tmp = ns; ns = ms; ms = tmp; swap = true; } + if (ns > ps) { + [ns, ps] = [ps, ns]; + swap = true; + } + if (ms > ns) { + [ms, ns] = [ns, ms]; + swap = true; + } } while (swap); // store a new Delaunay triangle @@ -169,25 +175,26 @@ class TGraphDelaunay { } - /// Attempt to find all the Delaunay triangles of the point set. It is not - /// guaranteed that it will fully succeed, and no check is made that it has - /// fully succeeded (such a check would be possible by referencing the points - /// that make up the convex hull). The method is to check if each triangle - /// shares all three of its sides with other triangles. If not, a point is - /// generated just outside the triangle on the side(s) not shared, and a new - /// triangle is found for that point. If this method is not working properly - /// (many triangles are not being found) it's probably because the new points - /// are too far beyond or too close to the non-shared sides. Fiddling with - /// the size of the `alittlebit' parameter may help. + // Attempt to find all the Delaunay triangles of the point set. It is not + // guaranteed that it will fully succeed, and no check is made that it has + // fully succeeded (such a check would be possible by referencing the points + // that make up the convex hull). The method is to check if each triangle + // shares all three of its sides with other triangles. If not, a point is + // generated just outside the triangle on the side(s) not shared, and a new + // triangle is found for that point. If this method is not working properly + // (many triangles are not being found) it's probably because the new points + // are too far beyond or too close to the non-shared sides. Fiddling with + // the size of the `alittlebit' parameter may help. FindAllTriangles() { - if (this.fAllTri) return; + if (this.fAllTri) + return; this.fAllTri = true; let xcntr, ycntr, xm, ym, xx, yy, sx, sy, nx, ny, mx, my, mdotn, nn, a, - t1, t2, pa, na, ma, pb, nb, mb, p1=0, p2=0, m, n, p3=0; + t1, t2, pa, na, ma, pb, nb, mb, p1 = 0, p2 = 0, m, n, p3 = 0; const s = [false, false, false], alittlebit = 0.0001; @@ -200,36 +207,36 @@ class TGraphDelaunay { xcntr = 0; ycntr = 0; for (n = 1; n <= this.fNhull; n++) { - xcntr += this.fXN[this.fHullPoints[n-1]]; - ycntr += this.fYN[this.fHullPoints[n-1]]; + xcntr += this.fXN[this.fHullPoints[n - 1]]; + ycntr += this.fYN[this.fHullPoints[n - 1]]; } - xcntr = xcntr/this.fNhull+alittlebit; - ycntr = ycntr/this.fNhull+alittlebit; + xcntr = xcntr / this.fNhull + alittlebit; + ycntr = ycntr / this.fNhull + alittlebit; // and calculate it's triangle this.Interpolate(xcntr, ycntr); // loop over all Delaunay triangles (including those constantly being // produced within the loop) and check to see if their 3 sides also // correspond to the sides of other Delaunay triangles, i.e. that they - // have all their neighbours. + // have all their neighbors. t1 = 1; while (t1 <= this.fNdt) { // get the three points that make up this triangle - pa = this.fPTried[t1-1]; - na = this.fNTried[t1-1]; - ma = this.fMTried[t1-1]; + pa = this.fPTried[t1 - 1]; + na = this.fNTried[t1 - 1]; + ma = this.fMTried[t1 - 1]; // produce three integers which will represent the three sides s[0] = false; s[1] = false; s[2] = false; // loop over all other Delaunay triangles - for (t2=1; t2<=this.fNdt; t2++) { + for (t2 = 1; t2 <= this.fNdt; t2++) { if (t2 !== t1) { // get the points that make up this triangle - pb = this.fPTried[t2-1]; - nb = this.fNTried[t2-1]; - mb = this.fMTried[t2-1]; + pb = this.fPTried[t2 - 1]; + nb = this.fNTried[t2 - 1]; + mb = this.fMTried[t2 - 1]; // do triangles t1 and t2 share a side? if ((pa === pb && na === nb) || (pa === pb && na === mb) || (pa === nb && na === mb)) { // they share side 1 @@ -244,14 +251,15 @@ class TGraphDelaunay { } // if t1 shares all its sides with other Delaunay triangles then // forget about it - if (s[0] && s[1] && s[2]) continue; + if (s[0] && s[1] && s[2]) + continue; } - // Looks like t1 is missing a neighbour on at least one side. + // Looks like t1 is missing a neighbor on at least one side. // For each side, take a point a little bit beyond it and calculate // the Delaunay triangle for that point, this should be the triangle // which shares the side. - for (m=1; m<=3; m++) { - if (!s[m-1]) { + for (m = 1; m <= 3; m++) { + if (!s[m - 1]) { // get the two points that make up this side if (m === 1) { p1 = pa; @@ -267,23 +275,23 @@ class TGraphDelaunay { p3 = pa; } // get the coordinates of the centre of this side - xm = (this.fXN[p1]+this.fXN[p2])/2.0; - ym = (this.fYN[p1]+this.fYN[p2])/2.0; + xm = (this.fXN[p1] + this.fXN[p2]) / 2.0; + ym = (this.fYN[p1] + this.fYN[p2]) / 2.0; // we want to add a little to these coordinates to get a point just // outside the triangle; (sx,sy) will be the vector that represents // the side - sx = this.fXN[p1]-this.fXN[p2]; - sy = this.fYN[p1]-this.fYN[p2]; + sx = this.fXN[p1] - this.fXN[p2]; + sy = this.fYN[p1] - this.fYN[p2]; // (nx,ny) will be the normal to the side, but don't know if it's // pointing in or out yet nx = sy; ny = -sx; - nn = Math.sqrt(nx*nx+ny*ny); - nx = nx/nn; - ny = ny/nn; - mx = this.fXN[p3]-xm; - my = this.fYN[p3]-ym; - mdotn = mx*nx+my*ny; + nn = Math.sqrt(nx * nx + ny * ny); + nx /= nn; + ny /= nn; + mx = this.fXN[p3] - xm; + my = this.fYN[p3] - ym; + mdotn = mx * nx + my * ny; if (mdotn > 0) { // (nx,ny) is pointing in, we want it pointing out nx = -nx; @@ -292,9 +300,9 @@ class TGraphDelaunay { // increase/decrease xm and ym a little to produce a point // just outside the triangle (ensuring that the amount added will // be large enough such that it won't be lost in rounding errors) - a = Math.abs(Math.max(alittlebit*xm, alittlebit*ym)); - xx = xm+nx*a; - yy = ym+ny*a; + a = Math.abs(Math.max(alittlebit * xm, alittlebit * ym)); + xx = xm + nx * a; + yy = ym + ny * a; // try and find a new Delaunay triangle for this point this.Interpolate(xx, yy); @@ -306,18 +314,18 @@ class TGraphDelaunay { } } - /// Finds those points which make up the convex hull of the set. If the xy - /// plane were a sheet of wood, and the points were nails hammered into it - /// at the respective coordinates, then if an elastic band were stretched - /// over all the nails it would form the shape of the convex hull. Those - /// nails in contact with it are the points that make up the hull. + // Finds those points which make up the convex hull of the set. If the xy + // plane were a sheet of wood, and the points were nails hammered into it + // at the respective coordinates, then if an elastic band were stretched + // over all the nails it would form the shape of the convex hull. Those + // nails in contact with it are the points that make up the hull. FindHull() { if (!this.fHullPoints) this.fHullPoints = new Array(this.fNpoints); let nhull_tmp = 0; - for (let n=1; n<=this.fNpoints; n++) { + for (let n = 1; n <= this.fNpoints; n++) { // if the point is not inside the hull of the set of all points // bar it, then it is part of the hull of the set of all points // including it @@ -326,14 +334,14 @@ class TGraphDelaunay { // cannot increment fNhull directly - InHull needs to know that // the hull has not yet been completely found nhull_tmp++; - this.fHullPoints[nhull_tmp-1] = n; + this.fHullPoints[nhull_tmp - 1] = n; } } this.fNhull = nhull_tmp; } - /// Is point e inside the hull defined by all points apart from x ? + // Is point e inside the hull defined by all points apart from x ? InHull(e, x) { let n1, n2, n, m, ntry, @@ -354,7 +362,7 @@ class TGraphDelaunay { } // n1 and n2 will represent the two points most separated by angle - // from point e. Initially the angle between them will be <180 degs. + // from point e. Initially the angle between them will be <180 degrees. // But subsequent points will increase the n1-e-n2 angle. If it // increases above 180 degrees then point e must be surrounded by // points - it is not part of the hull. @@ -368,45 +376,46 @@ class TGraphDelaunay { // Get the angle n1-e-n2 and set it to lastdphi - dx1 = xx-this.fXN[n1]; - dy1 = yy-this.fYN[n1]; - dx2 = xx-this.fXN[n2]; - dy2 = yy-this.fYN[n2]; + dx1 = xx - this.fXN[n1]; + dy1 = yy - this.fYN[n1]; + dx2 = xx - this.fXN[n2]; + dy2 = yy - this.fYN[n2]; phi1 = Math.atan2(dy1, dx1); phi2 = Math.atan2(dy2, dx2); - dphi = (phi1-phi2)-(Math.floor((phi1-phi2)/(Math.PI*2))*Math.PI*2); - if (dphi < 0) dphi += Math.PI*2; + dphi = (phi1 - phi2) - (Math.floor((phi1 - phi2) / (Math.PI * 2)) * Math.PI * 2); + if (dphi < 0) + dphi += Math.PI * 2; lastdphi = dphi; - for (n=1; n<=ntry; n++) { + for (n = 1; n <= ntry; n++) { if (this.fNhull > 0) { // Try hull point n - m = this.fHullPoints[n-1]; + m = this.fHullPoints[n - 1]; } else m = n; if ((m !== n1) && (m !== n2) && (m !== x)) { // Can the vector e->m be represented as a sum with positive // coefficients of vectors e->n1 and e->n2? - dx1 = xx-this.fXN[n1]; - dy1 = yy-this.fYN[n1]; - dx2 = xx-this.fXN[n2]; - dy2 = yy-this.fYN[n2]; - dx3 = xx-this.fXN[m]; - dy3 = yy-this.fYN[m]; - - dd1 = (dx2*dy1-dx1*dy2); - dd2 = (dx1*dy2-dx2*dy1); - - if (dd1*dd2 !== 0) { - u = (dx2*dy3-dx3*dy2)/dd1; - v = (dx1*dy3-dx3*dy1)/dd2; + dx1 = xx - this.fXN[n1]; + dy1 = yy - this.fYN[n1]; + dx2 = xx - this.fXN[n2]; + dy2 = yy - this.fYN[n2]; + dx3 = xx - this.fXN[m]; + dy3 = yy - this.fYN[m]; + + dd1 = (dx2 * dy1 - dx1 * dy2); + dd2 = (dx1 * dy2 - dx2 * dy1); + + if (dd1 * dd2) { + u = (dx2 * dy3 - dx3 * dy2) / dd1; + v = (dx1 * dy3 - dx3 * dy1) / dd2; if ((u < 0) || (v < 0)) { // No, it cannot - point m does not lie in-between n1 and n2 as // viewed from e. Replace either n1 or n2 to increase the // n1-e-n2 angle. The one to replace is the one which makes the // smallest angle with e->m - vNv1 = (dx1*dx3+dy1*dy3)/Math.sqrt(dx1*dx1+dy1*dy1); - vNv2 = (dx2*dx3+dy2*dy3)/Math.sqrt(dx2*dx2+dy2*dy2); + vNv1 = (dx1 * dx3 + dy1 * dy3) / Math.sqrt(dx1 * dx1 + dy1 * dy1); + vNv2 = (dx2 * dx3 + dy2 * dy3) / Math.sqrt(dx2 * dx2 + dy2 * dy2); if (vNv1 > vNv2) { n1 = m; phi1 = Math.atan2(dy3, dx3); @@ -416,11 +425,12 @@ class TGraphDelaunay { phi1 = Math.atan2(dy1, dx1); phi2 = Math.atan2(dy3, dx3); } - dphi = (phi1-phi2)-(Math.floor((phi1-phi2)/(Math.PI*2))*Math.PI*2); - if (dphi < 0) dphi += Math.PI*2; - if ((dphi - Math.PI)*(lastdphi - Math.PI) < 0) { + dphi = (phi1 - phi2) - (Math.floor((phi1 - phi2) / (Math.PI * 2)) * Math.PI * 2); + if (dphi < 0) + dphi += Math.PI * 2; + if ((dphi - Math.PI) * (lastdphi - Math.PI) < 0) { // The addition of point m means the angle n1-e-n2 has risen - // above 180 degs, the point is in the hull. + // above 180 degrees, the point is in the hull. deTinhull = true; return deTinhull; } @@ -433,18 +443,23 @@ class TGraphDelaunay { return deTinhull; } - - /// Finds the z-value at point e given that it lies - /// on the plane defined by t1,t2,t3 + // Finds the z-value at point e given that it lies + // on the plane defined by t1,t2,t3 InterpolateOnPlane(TI1, TI2, TI3, e) { - let tmp, swap, t1 = TI1, t2 = TI2, t3 = TI3; + let swap, t1 = TI1, t2 = TI2, t3 = TI3; // order the vertices do { swap = false; - if (t2 > t1) { tmp = t1; t1 = t2; t2 = tmp; swap = true; } - if (t3 > t2) { tmp = t2; t2 = t3; t3 = tmp; swap = true; } + if (t2 > t1) { + [t1, t2] = [t2, t1]; + swap = true; + } + if (t3 > t2) { + [t2, t3] = [t3, t2]; + swap = true; + } } while (swap); const x1 = this.fXN[t1], @@ -453,24 +468,25 @@ class TGraphDelaunay { y1 = this.fYN[t1], y2 = this.fYN[t2], y3 = this.fYN[t3], - f1 = this.fZ[t1-1], - f2 = this.fZ[t2-1], - f3 = this.fZ[t3-1], - u = (f1*(y2-y3)+f2*(y3-y1)+f3*(y1-y2))/(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2)), - v = (f1*(x2-x3)+f2*(x3-x1)+f3*(x1-x2))/(y1*(x2-x3)+y2*(x3-x1)+y3*(x1-x2)), - w = f1-u*x1-v*y1; - - return u*this.fXN[e] + v*this.fYN[e] + w; + f1 = this.fZ[t1 - 1], + f2 = this.fZ[t2 - 1], + f3 = this.fZ[t3 - 1], + u = (f1 * (y2 - y3) + f2 * (y3 - y1) + f3 * (y1 - y2)) / (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)), + v = (f1 * (x2 - x3) + f2 * (x3 - x1) + f3 * (x1 - x2)) / (y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2)), + w = f1 - u * x1 - v * y1; + + return u * this.fXN[e] + v * this.fYN[e] + w; } - /// Finds the Delaunay triangle that the point (xi,yi) sits in (if any) and - /// calculate a z-value for it by linearly interpolating the z-values that - /// make up that triangle. + // Finds the Delaunay triangle that the point (xi,yi) sits in (if any) and + // calculate a z-value for it by linearly interpolating the z-values that + // make up that triangle. Interpolate(xx, yy) { let thevalue, it, ntris_tried, p, n, m, i, j, k, l, z, f, d, o1, o2, a, b, t1, t2, t3, + /* eslint-disable-next-line no-useless-assignment */ ndegen = 0, degen = 0, fdegen = 0, o1degen = 0, o2degen = 0, vxN, vyN, d1, d2, d3, c1, c2, dko1, dko2, dfo1, @@ -478,7 +494,7 @@ class TGraphDelaunay { dx1, dx2, dx3, dy1, dy2, dy3, u, v; const dxz = [0, 0, 0], dyz = [0, 0, 0]; - // initialise the Delaunay algorithm if needed + // initialize the Delaunay algorithm if needed this.Initialize(); // create vectors needed for sorting @@ -502,10 +518,10 @@ class TGraphDelaunay { return thevalue; // check existing Delaunay triangles for a good one - for (it=1; it<=this.fNdt; it++) { - p = this.fPTried[it-1]; - n = this.fNTried[it-1]; - m = this.fMTried[it-1]; + for (it = 1; it <= this.fNdt; it++) { + p = this.fPTried[it - 1]; + n = this.fNTried[it - 1]; + m = this.fMTried[it - 1]; // p, n and m form a previously found Delaunay triangle, does it // enclose the point? if (this.Enclose(p, n, m, 0)) { @@ -523,38 +539,39 @@ class TGraphDelaunay { // it must be in a Delaunay triangle - find it... // order mass points by distance in mass plane from desired point - for (it=1; it<=this.fNpoints; it++) { + for (it = 1; it <= this.fNpoints; it++) { vxN = this.fXN[it]; vyN = this.fYN[it]; - this.fDist[it-1] = Math.sqrt((xx-vxN)*(xx-vxN)+(yy-vyN)*(yy-vyN)); + this.fDist[it - 1] = Math.sqrt((xx - vxN) * (xx - vxN) + (yy - vyN) * (yy - vyN)); } // sort array 'fDist' to find closest points - TMath_Sort(this.fNpoints, this.fDist, this.fOrder /*, false */); - for (it=0; it<this.fNpoints; it++) this.fOrder[it]++; + TMath_Sort(this.fNpoints, this.fDist, this.fOrder /* , false */); + for (it = 0; it < this.fNpoints; it++) + this.fOrder[it]++; // loop over triplets of close points to try to find a triangle that // encloses the point. - for (k=3; k<=this.fNpoints; k++) { - m = this.fOrder[k-1]; - for (j=2; j<=k-1; j++) { - n = this.fOrder[j-1]; - for (i=1; i<=j-1; i++) { + for (k = 3; k <= this.fNpoints; k++) { + m = this.fOrder[k - 1]; + for (j = 2; j <= k - 1; j++) { + n = this.fOrder[j - 1]; + for (i = 1; i <= j - 1; i++) { let skip_this_triangle = false; // used instead of goto L90 - p = this.fOrder[i-1]; + p = this.fOrder[i - 1]; if (ntris_tried > this.fMaxIter) { // perhaps this point isn't in the hull after all - /// Warning("Interpolate", - /// "Abandoning the effort to find a Delaunay triangle (and thus interpolated z-value) for point %g %g" - /// ,xx,yy); + /* Warning("Interpolate", + "Abandoning the effort to find a Delaunay triangle (and thus interpolated z-value) for point %g %g" + ,xx,yy); */ return thevalue; } ntris_tried++; // check the points aren't colinear - d1 = Math.sqrt((this.fXN[p]-this.fXN[n])**2+(this.fYN[p]-this.fYN[n])**2); - d2 = Math.sqrt((this.fXN[p]-this.fXN[m])**2+(this.fYN[p]-this.fYN[m])**2); - d3 = Math.sqrt((this.fXN[n]-this.fXN[m])**2+(this.fYN[n]-this.fYN[m])**2); - if ((d1+d2 <= d3) || (d1+d3 <= d2) || (d2+d3 <= d1)) + d1 = Math.sqrt((this.fXN[p] - this.fXN[n]) ** 2 + (this.fYN[p] - this.fYN[n]) ** 2); + d2 = Math.sqrt((this.fXN[p] - this.fXN[m]) ** 2 + (this.fYN[p] - this.fYN[m]) ** 2); + d3 = Math.sqrt((this.fXN[n] - this.fXN[m]) ** 2 + (this.fYN[n] - this.fYN[m]) ** 2); + if ((d1 + d2 <= d3) || (d1 + d3 <= d2) || (d2 + d3 <= d1)) continue; // does the triangle enclose the point? @@ -577,14 +594,17 @@ class TGraphDelaunay { // point z cannot be inside the triangle if it's further from (xx,yy) // than the furthest pointing making up the triangle - test this - for (l=1; l<=this.fNpoints; l++) { - if (this.fOrder[l-1] === z) { - if ((l<i) || (l<j) || (l<k)) { + for (l = 1; l <= this.fNpoints; l++) { + if (this.fOrder[l - 1] === z) { + if ((l < i) || (l < j) || (l < k)) { // point z is nearer to (xx,yy) than m, n or p - it could be in the // triangle so call enclose to find out // if it is inside the triangle this can't be a Delaunay triangle - if (this.Enclose(p, n, m, z)) { skip_this_triangle = true; break; } // goto L90; + if (this.Enclose(p, n, m, z)) { + skip_this_triangle = true; + break; + } // goto L90; } else { // there's no way it could be in the triangle so there's no point // calling enclose @@ -593,19 +613,20 @@ class TGraphDelaunay { } } - if (skip_this_triangle) break; + if (skip_this_triangle) + break; // is point z colinear with any pair of the triangle points? - // L1: - if (((this.fXN[p]-this.fXN[z])*(this.fYN[p]-this.fYN[n])) === ((this.fYN[p]-this.fYN[z])*(this.fXN[p]-this.fXN[n]))) { + // L1: + if (((this.fXN[p] - this.fXN[z]) * (this.fYN[p] - this.fYN[n])) === ((this.fYN[p] - this.fYN[z]) * (this.fXN[p] - this.fXN[n]))) { // z is colinear with p and n a = p; b = n; - } else if (((this.fXN[p]-this.fXN[z])*(this.fYN[p]-this.fYN[m])) === ((this.fYN[p]-this.fYN[z])*(this.fXN[p]-this.fXN[m]))) { + } else if (((this.fXN[p] - this.fXN[z]) * (this.fYN[p] - this.fYN[m])) === ((this.fYN[p] - this.fYN[z]) * (this.fXN[p] - this.fXN[m]))) { // z is colinear with p and m a = p; b = m; - } else if (((this.fXN[n]-this.fXN[z])*(this.fYN[n]-this.fYN[m])) === ((this.fYN[n]-this.fYN[z])*(this.fXN[n]-this.fXN[m]))) { + } else if (((this.fXN[n] - this.fXN[z]) * (this.fYN[n] - this.fYN[m])) === ((this.fYN[n] - this.fYN[z]) * (this.fXN[n] - this.fXN[m]))) { // z is colinear with n and m a = n; b = m; @@ -613,15 +634,15 @@ class TGraphDelaunay { a = 0; b = 0; } - if (a !== 0) { + if (a) { // point z is colinear with 2 of the triangle points, if it lies // between them it's in the circle otherwise it's outside if (this.fXN[a] !== this.fXN[b]) { - if (((this.fXN[z]-this.fXN[a])*(this.fXN[z]-this.fXN[b])) < 0) { + if (((this.fXN[z] - this.fXN[a]) * (this.fXN[z] - this.fXN[b])) < 0) { skip_this_triangle = true; break; // goto L90; - } else if (((this.fXN[z]-this.fXN[a])*(this.fXN[z]-this.fXN[b])) === 0) { + } else if (((this.fXN[z] - this.fXN[a]) * (this.fXN[z] - this.fXN[b])) === 0) { // At least two points are sitting on top of each other, we will // treat these as one and not consider this a 'multiple points lying // on a common circle' situation. It is a sign something could be @@ -629,23 +650,22 @@ class TGraphDelaunay { // different fZ's. If they don't then this is harmless. console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); } - } else { - if (((this.fYN[z]-this.fYN[a])*(this.fYN[z]-this.fYN[b])) < 0) { - skip_this_triangle = true; - break; - // goto L90; - } else if (((this.fYN[z]-this.fYN[a])*(this.fYN[z]-this.fYN[b])) === 0) { - // At least two points are sitting on top of each other - see above. - console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); - } + } else if (((this.fYN[z] - this.fYN[a]) * (this.fYN[z] - this.fYN[b])) < 0) { + skip_this_triangle = true; + break; + // goto L90; + } else if (((this.fYN[z] - this.fYN[a]) * (this.fYN[z] - this.fYN[b])) === 0) { + // At least two points are sitting on top of each other - see above. + console.warn(`Interpolate Two of these three points are coincident ${a} ${b} ${z}`); } // point is outside the circle, move to next point continue; // goto L50; } - if (skip_this_triangle) break; // deepscan-disable-line + if (skip_this_triangle) + break; - /// Error("Interpolate", "Should not get to here"); + /* Error("Interpolate", "Should not get to here"); */ // may as well soldier on // SL: initialize before try to find better values f = m; @@ -656,53 +676,59 @@ class TGraphDelaunay { // lying between the other two? (we're going to form a quadrilateral // from the points, and then demand certain properties of that // quadrilateral) - dxz[0] = this.fXN[p]-this.fXN[z]; - dyz[0] = this.fYN[p]-this.fYN[z]; - dxz[1] = this.fXN[n]-this.fXN[z]; - dyz[1] = this.fYN[n]-this.fYN[z]; - dxz[2] = this.fXN[m]-this.fXN[z]; - dyz[2] = this.fYN[m]-this.fYN[z]; - for (l=1; l<=3; l++) { - dx1 = dxz[l-1]; - dx2 = dxz[l%3]; - dx3 = dxz[(l+1)%3]; - dy1 = dyz[l-1]; - dy2 = dyz[l%3]; - dy3 = dyz[(l+1)%3]; + dxz[0] = this.fXN[p] - this.fXN[z]; + dyz[0] = this.fYN[p] - this.fYN[z]; + dxz[1] = this.fXN[n] - this.fXN[z]; + dyz[1] = this.fYN[n] - this.fYN[z]; + dxz[2] = this.fXN[m] - this.fXN[z]; + dyz[2] = this.fYN[m] - this.fYN[z]; + for (l = 1; l <= 3; l++) { + dx1 = dxz[l - 1]; + dx2 = dxz[l % 3]; + dx3 = dxz[(l + 1) % 3]; + dy1 = dyz[l - 1]; + dy2 = dyz[l % 3]; + dy3 = dyz[(l + 1) % 3]; // u et v are used only to know their sign. The previous // code computed them with a division which was long and // might be a division by 0. It is now replaced by a // multiplication. - u = (dy3*dx2-dx3*dy2)*(dy1*dx2-dx1*dy2); - v = (dy3*dx1-dx3*dy1)*(dy2*dx1-dx2*dy1); + u = (dy3 * dx2 - dx3 * dy2) * (dy1 * dx2 - dx1 * dy2); + v = (dy3 * dx1 - dx3 * dy1) * (dy2 * dx1 - dx2 * dy1); if ((u >= 0) && (v >= 0)) { // vector (dx3,dy3) is expressible as a sum of the other two vectors // with positive coefficients -> i.e. it lies between the other two vectors if (l === 1) { - f = m; o1 = p; o2 = n; // deepscan-disable-line + f = m; + o1 = p; + o2 = n; } else if (l === 2) { - f = p; o1 = n; o2 = m; + f = p; + o1 = n; + o2 = m; } else { - f = n; o1 = m; o2 = p; + f = n; + o1 = m; + o2 = p; } break; // goto L2; } } - // L2: + // L2: // this is not a valid quadrilateral if the diagonals don't cross, // check that points f and z lie on opposite side of the line o1-o2, // this is true if the angle f-o1-z is greater than o2-o1-z and o2-o1-f - cfo1k = ((this.fXN[f]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))* - ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))); - co2o1k = ((this.fXN[o2]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[o2]-this.fXN[o1])*(this.fXN[o2]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[o2]-this.fYN[o1]))* - ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1]) + (this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1]))); - co2o1f = ((this.fXN[o2]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))/ - Math.sqrt(((this.fXN[o2]-this.fXN[o1])*(this.fXN[o2]-this.fXN[o1])+(this.fYN[o2]-this.fYN[o1])*(this.fYN[o2]-this.fYN[o1]))* - ((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1]) + (this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1]))); + cfo1k = ((this.fXN[f] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])) / + Math.sqrt(((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])) * + ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1]))); + co2o1k = ((this.fXN[o2] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])) / + Math.sqrt(((this.fXN[o2] - this.fXN[o1]) * (this.fXN[o2] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[o2] - this.fYN[o1])) * + ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1]))); + co2o1f = ((this.fXN[o2] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])) / + Math.sqrt(((this.fXN[o2] - this.fXN[o1]) * (this.fXN[o2] - this.fXN[o1]) + (this.fYN[o2] - this.fYN[o1]) * (this.fYN[o2] - this.fYN[o1])) * + ((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1]))); if ((cfo1k > co2o1k) || (cfo1k > co2o1f)) { // not a valid quadrilateral - point z is definitely outside the circle continue; // goto L50; @@ -710,13 +736,13 @@ class TGraphDelaunay { // calculate the 2 internal angles of the quadrangle formed by joining // points z and f to points o1 and o2, at z and f. If they sum to less // than 180 degrees then z lies outside the circle - dko1 = Math.sqrt((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o1])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o1])); - dko2 = Math.sqrt((this.fXN[z]-this.fXN[o2])*(this.fXN[z]-this.fXN[o2])+(this.fYN[z]-this.fYN[o2])*(this.fYN[z]-this.fYN[o2])); - dfo1 = Math.sqrt((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o1])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o1])); - dfo2 = Math.sqrt((this.fXN[f]-this.fXN[o2])*(this.fXN[f]-this.fXN[o2])+(this.fYN[f]-this.fYN[o2])*(this.fYN[f]-this.fYN[o2])); - c1 = ((this.fXN[z]-this.fXN[o1])*(this.fXN[z]-this.fXN[o2])+(this.fYN[z]-this.fYN[o1])*(this.fYN[z]-this.fYN[o2]))/dko1/dko2; - c2 = ((this.fXN[f]-this.fXN[o1])*(this.fXN[f]-this.fXN[o2])+(this.fYN[f]-this.fYN[o1])*(this.fYN[f]-this.fYN[o2]))/dfo1/dfo2; - sin_sum = c1*Math.sqrt(1-c2*c2)+c2*Math.sqrt(1-c1*c1); + dko1 = Math.sqrt((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o1]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o1])); + dko2 = Math.sqrt((this.fXN[z] - this.fXN[o2]) * (this.fXN[z] - this.fXN[o2]) + (this.fYN[z] - this.fYN[o2]) * (this.fYN[z] - this.fYN[o2])); + dfo1 = Math.sqrt((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o1]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o1])); + dfo2 = Math.sqrt((this.fXN[f] - this.fXN[o2]) * (this.fXN[f] - this.fXN[o2]) + (this.fYN[f] - this.fYN[o2]) * (this.fYN[f] - this.fYN[o2])); + c1 = ((this.fXN[z] - this.fXN[o1]) * (this.fXN[z] - this.fXN[o2]) + (this.fYN[z] - this.fYN[o1]) * (this.fYN[z] - this.fYN[o2])) / dko1 / dko2; + c2 = ((this.fXN[f] - this.fXN[o1]) * (this.fXN[f] - this.fXN[o2]) + (this.fYN[f] - this.fYN[o1]) * (this.fYN[f] - this.fYN[o2])) / dfo1 / dfo2; + sin_sum = c1 * Math.sqrt(1 - c2 * c2) + c2 * Math.sqrt(1 - c1 * c1); // sin_sum doesn't always come out as zero when it should do. if (sin_sum < -1.e-6) { @@ -740,18 +766,19 @@ class TGraphDelaunay { // L50: continue; } // end of for ( z = 1 ...) loop - if (skip_this_triangle) continue; + if (skip_this_triangle) + continue; // This is a good triangle if (ndegen > 0) { // but is degenerate with at least one other, // haven't figured out what to do if more than 4 points are involved - /// if (ndegen > 1) { - /// Error("Interpolate", - /// "More than 4 points lying on a circle. No decision making process formulated for triangulating this region in a non-arbitrary way %d %d %d %d", - /// p,n,m,degen); - /// return thevalue; - /// } + /* if (ndegen > 1) { + Error("Interpolate", + "More than 4 points lying on a circle. No decision making process formulated for triangulating this region in a non-arbitrary way %d %d %d %d", + p,n,m,degen); + return thevalue; + } */ // we have a quadrilateral which can be split down either diagonal // (d<->f or o1<->o2) to form valid Delaunay triangles. Choose diagonal @@ -761,7 +788,7 @@ class TGraphDelaunay { f = fdegen; o1 = o1degen; o2 = o2degen; - if ((this.fZ[o1-1] + this.fZ[o2-1]) > (this.fZ[d-1] + this.fZ[f-1])) { + if ((this.fZ[o1 - 1] + this.fZ[o2 - 1]) > (this.fZ[d - 1] + this.fZ[f - 1])) { // best diagonalisation of quadrilateral is current one, we have // the triangle t1 = p; @@ -778,7 +805,7 @@ class TGraphDelaunay { t2 = d; if (this.Enclose(f, d, o1, 0)) t3 = o1; - else + else t3 = o2; // file the good triangles @@ -796,37 +823,266 @@ class TGraphDelaunay { thevalue = this.InterpolateOnPlane(t1, t2, t3, 0); return thevalue; - // L90: continue; + // L90: continue; } } } - if (shouldbein) // deepscan-disable-line + if (shouldbein) console.error(`Interpolate Point outside hull when expected inside: this point could be dodgy ${xx} ${yy} ${ntris_tried}`); return thevalue; } - /// Defines the number of triangles tested for a Delaunay triangle - /// (number of iterations) before abandoning the search - + /** @summary Defines the number of triangles tested for a Delaunay triangle + * @desc (number of iterations) before abandoning the search */ SetMaxIter(n = 100000) { this.fAllTri = false; this.fMaxIter = n; } - /// Sets the histogram bin height for points lying outside the convex hull ie: - /// the bins in the margin. - + /** @summary Sets the histogram bin height for points lying outside the convex hull ie: + * @desc the bins in the margin. */ SetMarginBinsContent(z) { this.fZout = z; } -} + /** @summary Returns the X and Y graphs building a contour. + * @desc A contour level may consist in several parts not connected to each other. + * This function finds them and returns them in a graphs' list. */ + GetContourList(contour) { + if (!this.fNdt) + return null; + + let graph, // current graph + // Find all the segments making the contour + r21, r20, r10, p0, p1, p2, x0, y0, z0, x1, y1, z1, x2, y2, z2, + it, i0, i1, i2, nbSeg = 0, + // Allocate space to store the segments. They cannot be more than the + // number of triangles. + xs0c, ys0c, xs1c, ys1c; + + const t = [0, 0, 0], + xs0 = new Array(this.fNdt).fill(0), + ys0 = new Array(this.fNdt).fill(0), + xs1 = new Array(this.fNdt).fill(0), + ys1 = new Array(this.fNdt).fill(0); + + // Loop over all the triangles in order to find all the line segments + // making the contour. + + // old implementation + for (it = 0; it < this.fNdt; it++) { + t[0] = this.fPTried[it]; + t[1] = this.fNTried[it]; + t[2] = this.fMTried[it]; + p0 = t[0] - 1; + p1 = t[1] - 1; + p2 = t[2] - 1; + x0 = this.fX[p0]; + x2 = this.fX[p0]; + y0 = this.fY[p0]; + y2 = this.fY[p0]; + z0 = this.fZ[p0]; + z2 = this.fZ[p0]; + + // Order along Z axis the points (xi,yi,zi) where "i" belongs to {0,1,2} + // After this z0 < z1 < z2 + /* eslint-disable-next-line no-useless-assignment */ + i0 = i1 = i2 = 0; + if (this.fZ[p1] <= z0) { + z0 = this.fZ[p1]; + x0 = this.fX[p1]; + y0 = this.fY[p1]; + i0 = 1; + } + if (this.fZ[p1] > z2) { + z2 = this.fZ[p1]; + x2 = this.fX[p1]; + y2 = this.fY[p1]; + i2 = 1; + } + if (this.fZ[p2] <= z0) { + z0 = this.fZ[p2]; + x0 = this.fX[p2]; + y0 = this.fY[p2]; + i0 = 2; + } + if (this.fZ[p2] > z2) { + z2 = this.fZ[p2]; + x2 = this.fX[p2]; + y2 = this.fY[p2]; + i2 = 2; + } + if (i0 === 0 && i2 === 0) { + console.error('GetContourList: wrong vertices ordering'); + return null; + } + + i1 = 3 - i2 - i0; + + x1 = this.fX[t[i1] - 1]; + y1 = this.fY[t[i1] - 1]; + z1 = this.fZ[t[i1] - 1]; + + if (contour >= z0 && contour <= z2) { + r20 = (contour - z0) / (z2 - z0); + xs0c = r20 * (x2 - x0) + x0; + ys0c = r20 * (y2 - y0) + y0; + if (contour >= z1 && contour <= z2) { + r21 = (contour - z1) / (z2 - z1); + xs1c = r21 * (x2 - x1) + x1; + ys1c = r21 * (y2 - y1) + y1; + } else { + r10 = (contour - z0) / (z1 - z0); + xs1c = r10 * (x1 - x0) + x0; + ys1c = r10 * (y1 - y0) + y0; + } + // do not take the segments equal to a point + if (xs0c !== xs1c || ys0c !== ys1c) { + nbSeg++; + xs0[nbSeg - 1] = xs0c; + ys0[nbSeg - 1] = ys0c; + xs1[nbSeg - 1] = xs1c; + ys1[nbSeg - 1] = ys1c; + } + } + } + + const list = [], // list holding all the graphs + segUsed = new Array(this.fNdt).fill(false); + + // Find all the graphs making the contour. There is two kind of graphs, + // either they are "opened" or they are "closed" + + // Find the opened graphs + let xc = 0, yc = 0, xnc = 0, ync = 0, + findNew, s0, s1, is, js; + + for (is = 0; is < nbSeg; is++) { + if (segUsed[is]) + continue; + s0 = s1 = false; + + // Find to which segment is is connected. It can be connected + // via 0, 1 or 2 vertices. + for (js = 0; js < nbSeg; js++) { + if (is === js) + continue; + if (xs0[is] === xs0[js] && ys0[is] === ys0[js]) + s0 = true; + if (xs0[is] === xs1[js] && ys0[is] === ys1[js]) + s0 = true; + if (xs1[is] === xs0[js] && ys1[is] === ys0[js]) + s1 = true; + if (xs1[is] === xs1[js] && ys1[is] === ys1[js]) + s1 = true; + } + + // Segment is is alone, not connected. It is stored in the + // list and the next segment is examined. + if (!s0 && !s1) { + graph = []; + graph.push(xs0[is], ys0[is]); + graph.push(xs1[is], ys1[is]); + segUsed[is] = true; + list.push(graph); + continue; + } + + // Segment is is connected via 1 vertex only and can be considered + // as the starting point of an opened contour. + if (!s0 || !s1) { + // Find all the segments connected to segment is + graph = []; + if (s0) { + xc = xs0[is]; + yc = ys0[is]; + xnc = xs1[is]; + ync = ys1[is]; + } + if (s1) { + xc = xs1[is]; + yc = ys1[is]; + xnc = xs0[is]; + ync = ys0[is]; + } + graph.push(xnc, ync); + segUsed[is] = true; + js = 0; + + while (true) { + findNew = false; + while (js < nbSeg && segUsed[js]) + js++; + + if (xc === xs0[js] && yc === ys0[js]) { + xc = xs1[js]; + yc = ys1[js]; + findNew = true; + } else if (xc === xs1[js] && yc === ys1[js]) { + xc = xs0[js]; + yc = ys0[js]; + findNew = true; + } + if (findNew) { + segUsed[js] = true; + graph.push(xc, yc); + js = 0; + } else if (++js >= nbSeg) + break; + } + list.push(graph); + } + } + - /** @summary Function handles tooltips in the mesh */ -function graph2DTooltip(intersect) { + // Find the closed graphs. At this point all the remaining graphs + // are closed. Any segment can be used to start the search. + for (is = 0; is < nbSeg; is++) { + if (segUsed[is]) + continue; + + // Find all the segments connected to segment is + graph = []; + segUsed[is] = true; + xc = xs0[is]; + yc = ys0[is]; + js = 0; + graph.push(xc, yc); + while (true) { + while (js < nbSeg && segUsed[js]) + js++; + findNew = false; + if (xc === xs0[js] && yc === ys0[js]) { + xc = xs1[js]; + yc = ys1[js]; + findNew = true; + } else if (xc === xs1[js] && yc === ys1[js]) { + xc = xs0[js]; + yc = ys0[js]; + findNew = true; + } + if (findNew) { + segUsed[js] = true; + graph.push(xc, yc); + js = 0; + } else if (++js >= nbSeg) + break; + } + graph.push(xs0[is], ys0[is]); + list.push(graph); + } + + return list; + } + +} // class TGraphDelaunay + +/** @summary Function handles tooltips in the mesh */ +function _graph2DTooltip(intersect) { let indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.index.length)) return null; - const sqr = v => v*v; + if ((indx < 0) || (indx >= this.index.length)) + return null; + const sqr = v => v * v; indx = this.index[indx]; @@ -835,13 +1091,16 @@ function graph2DTooltip(intersect) { gry = fp.gry(gr.fY[indx]), grz = fp.grz(gr.fZ[indx]); - if (this.check_next && indx+1<gr.fX.length) { + if (this.check_next && indx + 1 < gr.fX.length) { const d = intersect.point, - grx1 = fp.grx(gr.fX[indx+1]), - gry1 = fp.gry(gr.fY[indx+1]), - grz1 = fp.grz(gr.fZ[indx+1]); - if (sqr(d.x-grx1)+sqr(d.y-gry1)+sqr(d.z-grz1) < sqr(d.x-grx)+sqr(d.y-gry)+sqr(d.z-grz)) { - grx = grx1; gry = gry1; grz = grz1; indx++; + grx1 = fp.grx(gr.fX[indx + 1]), + gry1 = fp.gry(gr.fY[indx + 1]), + grz1 = fp.grz(gr.fZ[indx + 1]); + if (sqr(d.x - grx1) + sqr(d.y - gry1) + sqr(d.z - grz1) < sqr(d.x - grx) + sqr(d.y - gry) + sqr(d.z - grz)) { + grx = grx1; + gry = gry1; + grz = grz1; + indx++; } } @@ -854,16 +1113,14 @@ function graph2DTooltip(intersect) { z2: grz + this.scale0, color: this.tip_color, lines: [this.tip_name, - 'pnt: ' + indx, - 'x: ' + fp.axisAsText('x', gr.fX[indx]), - 'y: ' + fp.axisAsText('y', gr.fY[indx]), - 'z: ' + fp.axisAsText('z', gr.fZ[indx]) - ] + 'pnt: ' + indx, + 'x: ' + fp.axisAsText('x', gr.fX[indx]), + 'y: ' + fp.axisAsText('y', gr.fY[indx]), + 'z: ' + fp.axisAsText('z', gr.fZ[indx])] }; } - /** * @summary Painter for TGraph2D classes * @private @@ -871,14 +1128,13 @@ function graph2DTooltip(intersect) { class TGraph2DPainter extends ObjectPainter { - /** @summary Decode options string */ - decodeOptions(opt, _gr) { - const d = new DrawOptions(opt); - - if (!this.options) - this.options = {}; + #redraw_hist; // painter to redraw histogram + #delaunay; // used delaunay instance - const res = this.options, gr2d = this.getObject(); + /** @summary Decode options string */ + decodeOptions(opt) { + const d = new DrawOptions(opt), + gr2d = this.getObject(); if (d.check('FILL_', 'color') && gr2d) gr2d.fFillColor = d.color; @@ -887,39 +1143,51 @@ class TGraph2DPainter extends ObjectPainter { gr2d.fLineColor = d.color; d.check('SAME'); - if (d.check('TRI1')) - res.Triangles = 11; // wireframe and colors + + let Triangles = 0, Contour = 0; + + if (d.check('CONT5')) + Contour = 15; + else if (d.check('TRI1')) + Triangles = 11; // wire-frame and colors else if (d.check('TRI2')) - res.Triangles = 10; // only color triangles + Triangles = 10; // only color triangles else if (d.check('TRIW')) - res.Triangles = 1; + Triangles = 1; else if (d.check('TRI')) - res.Triangles = 2; - else - res.Triangles = 0; - res.Line = d.check('LINE'); - res.Error = d.check('ERR') && (this.matchObjectType(clTGraph2DErrors) || this.matchObjectType(clTGraph2DAsymmErrors)); + Triangles = 2; + + const res = this.setOptions({ + Triangles, + Contour, + Line: d.check('LINE'), + Error: d.check('ERR') && (this.matchObjectType(clTGraph2DErrors) || this.matchObjectType(clTGraph2DAsymmErrors)) + }); if (d.check('P0COL')) res.Color = res.Circles = res.Markers = true; - else { + else { res.Color = d.check('COL'); res.Circles = d.check('P0'); res.Markers = d.check('P'); } - if (!res.Markers) res.Color = false; + if (!res.Markers) + res.Color = false; - if (res.Color || res.Triangles >= 10) + if (res.Color || res.Triangles >= 10 || res.Contour) res.Zscale = d.check('Z'); res.isAny = function() { - return this.Markers || this.Error || this.Circles || this.Line || this.Triangles; + return this.Markers || this.Error || this.Circles || this.Line || this.Triangles || res.Contour; }; - if (res.isAny()) { + if (res.Contour) + res.Axis = ''; + else if (res.isAny()) { res.Axis = 'lego2'; - if (res.Zscale) res.Axis += 'z'; + if (res.Zscale) + res.Axis += 'z'; } else res.Axis = opt; @@ -929,6 +1197,7 @@ class TGraph2DPainter extends ObjectPainter { /** @summary Create histogram for axes drawing */ createHistogram() { const gr = this.getObject(), + o = this.getOptions(), asymm = this.matchObjectType(clTGraph2DAsymmErrors); let xmin = gr.fX[0], xmax = xmin, ymin = gr.fY[0], ymax = ymin, @@ -937,7 +1206,7 @@ class TGraph2DPainter extends ObjectPainter { for (let p = 0; p < gr.fNpoints; ++p) { const x = gr.fX[p], y = gr.fY[p], z = gr.fZ[p]; - if (this.options.Error) { + if (o.Error) { xmin = Math.min(xmin, x - (asymm ? gr.fEXlow[p] : gr.fEX[p])); xmax = Math.max(xmax, x + (asymm ? gr.fEXhigh[p] : gr.fEX[p])); ymin = Math.min(ymin, y - (asymm ? gr.fEYlow[p] : gr.fEY[p])); @@ -955,7 +1224,8 @@ class TGraph2DPainter extends ObjectPainter { } function calc_delta(min, max, margin) { - if (min < max) return margin * (max - min); + if (min < max) + return margin * (max - min); return Math.abs(min) < 1e5 ? 0.02 : 0.02 * Math.abs(min); } const dx = calc_delta(xmin, xmax, gr.fMargin), @@ -965,23 +1235,29 @@ class TGraph2DPainter extends ObjectPainter { uymin = ymin - dy, uymax = ymax + dy, uzmin = zmin - dz, uzmax = zmax + dz; - if ((uxmin < 0) && (xmin >= 0)) uxmin = xmin*0.98; - if ((uxmax > 0) && (xmax <= 0)) uxmax = 0; + if ((uxmin < 0) && (xmin >= 0)) + uxmin = xmin * 0.98; + if ((uxmax > 0) && (xmax <= 0)) + uxmax = 0; - if ((uymin < 0) && (ymin >= 0)) uymin = ymin*0.98; - if ((uymax > 0) && (ymax <= 0)) uymax = 0; + if ((uymin < 0) && (ymin >= 0)) + uymin = ymin * 0.98; + if ((uymax > 0) && (ymax <= 0)) + uymax = 0; - if ((uzmin < 0) && (zmin >= 0)) uzmin = zmin*0.98; - if ((uzmax > 0) && (zmax <= 0)) uzmax = 0; + if ((uzmin < 0) && (zmin >= 0)) + uzmin = zmin * 0.98; + if ((uzmax > 0) && (zmax <= 0)) + uzmax = 0; const graph = this.getObject(); - if (graph.fMinimum !== kNoZoom) uzmin = graph.fMinimum; - if (graph.fMaximum !== kNoZoom) uzmax = graph.fMaximum; - - this._own_histogram = true; // when histogram created on client side + if (graph.fMinimum !== kNoZoom) + uzmin = graph.fMinimum; + if (graph.fMaximum !== kNoZoom) + uzmax = graph.fMaximum; - const histo = createHistogram(clTH2F, graph.fNpx, graph.fNpy); + const histo = createHistogram(clTH2D, graph.fNpx, graph.fNpy); histo.fName = graph.fName + '_h'; setHistogramTitle(histo, graph.fTitle); histo.fXaxis.fXmin = uxmin; @@ -994,7 +1270,7 @@ class TGraph2DPainter extends ObjectPainter { histo.fMaximum = uzmax; histo.fBits |= kNoStats; - if (!this.options.isAny()) { + if (!o.isAny()) { const dulaunay = this.buildDelaunay(graph); if (dulaunay) { for (let i = 0; i < graph.fNpx; ++i) { @@ -1002,7 +1278,7 @@ class TGraph2DPainter extends ObjectPainter { for (let j = 0; j < graph.fNpy; ++j) { const yy = uymin + (j + 0.5) / graph.fNpy * (uymax - uymin), zz = dulaunay.ComputeZ(xx, yy); - histo.fArray[histo.getBin(i+1, j+1)] = zz; + histo.fArray[histo.getBin(i + 1, j + 1)] = zz; } } } @@ -1012,24 +1288,26 @@ class TGraph2DPainter extends ObjectPainter { } buildDelaunay(graph) { - if (!this._delaunay) { - this._delaunay = new TGraphDelaunay(graph); - this._delaunay.FindAllTriangles(); - if (!this._delaunay.fNdt) - delete this._delaunay; + if (!this.#delaunay) { + this.#delaunay = new TGraphDelaunay(graph); + this.#delaunay.FindAllTriangles(); + if (!this.#delaunay.fNdt) + this.#delaunay = undefined; } - return this._delaunay; + return this.#delaunay; } drawTriangles(fp, graph, levels, palette) { const dulaunay = this.buildDelaunay(graph); - if (!dulaunay) return; + if (!dulaunay) + return; - const main_grz = !fp.logz ? fp.grz : value => (value < fp.scale_zmin) ? -0.1 : fp.grz(value), - plain_mode = this.options.Triangles === 2, - do_faces = (this.options.Triangles >= 10) || plain_mode, - do_lines = (this.options.Triangles % 10 === 1) || (plain_mode && (graph.fLineColor !== graph.fFillColor)), - triangles = new Triangles3DHandler(levels, main_grz, 0, 2*fp.size_z3d, do_lines); + const main_grz = !fp.logz ? fp.grz : value => { return (value < fp.scale_zmin) ? -0.1 : fp.grz(value); }, + o = this.getOptions(), + plain_mode = o.Triangles === 2, + do_faces = (o.Triangles >= 10) || plain_mode, + do_lines = (o.Triangles % 10 === 1) || (plain_mode && (graph.fLineColor !== graph.fFillColor)), + triangles = new Triangles3DHandler(levels, main_grz, 0, 2 * fp.size_z3d, do_lines); for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { triangles.createBuffers(); @@ -1042,7 +1320,7 @@ class TGraph2DPainter extends ObjectPainter { const pnt = points[i] - 1; coord.push(fp.grx(graph.fX[pnt]), fp.gry(graph.fY[pnt]), main_grz(graph.fZ[pnt])); - if ((graph.fX[pnt] < fp.scale_xmin) || (graph.fX[pnt] > fp.scale_xmax) || + if ((graph.fX[pnt] < fp.scale_xmin) || (graph.fX[pnt] > fp.scale_xmax) || (graph.fY[pnt] < fp.scale_ymin) || (graph.fY[pnt] > fp.scale_ymax)) use_triangle = false; } @@ -1052,9 +1330,7 @@ class TGraph2DPainter extends ObjectPainter { if (do_lines && use_triangle) { triangles.addLineSegment(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]); - triangles.addLineSegment(coord[3], coord[4], coord[5], coord[6], coord[7], coord[8]); - triangles.addLineSegment(coord[6], coord[7], coord[8], coord[0], coord[1], coord[2]); } } @@ -1063,39 +1339,41 @@ class TGraph2DPainter extends ObjectPainter { triangles.callFuncs((lvl, pos) => { const geometry = createLegoGeom(this.getMainPainter(), pos, null, 100, 100), color = plain_mode ? this.getColor(graph.fFillColor) : palette.calcColor(lvl, levels.length), - material = new MeshBasicMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })), - - mesh = new Mesh(geometry, material); + material = new THREE.MeshBasicMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); fp.add3DMesh(mesh, this); mesh.painter = this; // to let use it with context menu }, (_isgrid, lpos) => { const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), - linemesh = createLineSegments(convertLegoBuf(this.getMainPainter(), lpos, 100, 100), material); + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), + linemesh = createLineSegments(convertLegoBuf(this.getMainPainter(), lpos, 100, 100), material); fp.add3DMesh(linemesh, this); }); } /** @summary Update TGraph2D object */ updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; - if (opt && (opt !== this.options.original)) + const o = this.getOptions(); + + if (opt && (opt !== o.original)) this.decodeOptions(opt, obj); Object.assign(this.getObject(), obj); - delete this._delaunay; // rebuild triangles + this.#delaunay = undefined; // rebuild triangles - delete this.$redraw_hist; + this.#redraw_hist = undefined; // if our own histogram was used as axis drawing, we need update histogram as well if (this.axes_draw) { const hist_painter = this.getMainPainter(); - hist_painter?.updateObject(this.createHistogram(), this.options.Axis); - this.$redraw_hist = hist_painter; + hist_painter?.updateObject(this.createHistogram(), o.Axis); + this.#redraw_hist = hist_painter; } return true; @@ -1105,46 +1383,83 @@ class TGraph2DPainter extends ObjectPainter { * @desc Update histogram drawing if necessary * @return {Promise} for drawing ready */ async redraw() { - let promise = Promise.resolve(true); + const promise = getPromise(this.#redraw_hist?.redraw()); - if (this.$redraw_hist) { - promise = this.$redraw_hist.redraw(); - delete this.$redraw_hist; - } + this.#redraw_hist = undefined; return promise.then(() => this.drawGraph2D()); } + async drawContour(fp, main, graph) { + const dulaunay = this.buildDelaunay(graph); + if (!dulaunay) + return this; + + const cntr = main.getContour(), + palette = main.getHistPalette(), + levels = cntr.getLevels(), + funcs = fp.getGrFuncs(), + g = this.createG(true); + + this.createAttLine({ attr: graph, nocolor: true }); + + for (let k = 0; k < levels.length; ++k) { + const lst = dulaunay.GetContourList(levels[k]), + color = cntr.getPaletteColor(palette, levels[k]); + let path = ''; + for (let i = 0; i < lst.length; ++i) { + const gr = lst[i], arr = []; + for (let n = 0; n < gr.length; n += 2) + arr.push({ grx: funcs.grx(gr[n]), gry: funcs.gry(gr[n + 1]) }); + path += buildSvgCurve(arr, { cmd: 'M', line: true }); + } + + this.lineatt.color = color; + + g.append('svg:path') + .attr('d', path) + .style('fill', 'none') + .call(this.lineatt.func); + } + + return this; + } + /** @summary Actual drawing of TGraph2D object * @return {Promise} for drawing ready */ async drawGraph2D() { - const main = this.getMainPainter(), - fp = this.getFramePainter(), - graph = this.getObject(); + const fp = this.getFramePainter(), + main = this.getMainPainter(), + graph = this.getObject(), + o = this.getOptions(); - if (!graph || !main || !fp || !fp.mode3d) + if (!graph || !main || !fp) + return this; + + if (o.Contour) + return this.drawContour(fp, main, graph); + + if (!fp.mode3d) return this; fp.remove3DMeshes(this); - if (!this.options.isAny()) { - // no need to draw somthing if histogram content was drawn + if (!o.isAny()) { + // no need to draw smoothing if histogram content was drawn if (main.draw_content) return this; if ((graph.fMarkerSize === 1) && (graph.fMarkerStyle === 1)) - this.options.Circles = true; + o.Circles = true; else - this.options.Markers = true; + o.Markers = true; } const countSelected = (zmin, zmax) => { let cnt = 0; for (let i = 0; i < graph.fNpoints; ++i) { - if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) || - (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) || - (graph.fZ[i] < zmin) || (graph.fZ[i] >= zmax)) continue; - - ++cnt; + if ((graph.fX[i] >= fp.scale_xmin) && (graph.fX[i] <= fp.scale_xmax) && + (graph.fY[i] >= fp.scale_ymin) && (graph.fY[i] <= fp.scale_ymax) && + (graph.fZ[i] >= zmin) && (graph.fZ[i] < zmax)) ++cnt; } return cnt; }; @@ -1153,12 +1468,10 @@ class TGraph2DPainter extends ObjectPainter { let step = 1; if ((settings.OptimizeDraw > 0) && !fp.webgl) { const numselected = countSelected(fp.scale_zmin, fp.scale_zmax), - sizelimit = 50000; + sizelimit = 50000; - if (numselected > sizelimit) { - step = Math.floor(numselected / sizelimit); - if (step <= 2) step = 2; - } + if (numselected > sizelimit) + step = Math.max(2, Math.floor(numselected / sizelimit)); } const markeratt = this.createAttMarker({ attr: graph, std: false }), @@ -1167,149 +1480,144 @@ class TGraph2DPainter extends ObjectPainter { levels = [fp.scale_zmin, fp.scale_zmax], scale = fp.size_x3d / 100 * markeratt.getFullSize(); - if (this.options.Circles) + if (o.Circles) scale = 0.06 * fp.size_x3d; - if (fp.usesvg) scale *= 0.3; + if (fp.usesvg) + scale *= 0.3; + + const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(); - scale *= 7 * Math.max(fp.size_x3d / fp.getFrameWidth(), fp.size_z3d / fp.getFrameHeight()); + if ((fw > 10) && (fh > 10)) + scale *= 7 * Math.max(fp.size_x3d / fw, fp.size_z3d / fh); - if (this.options.Color || (this.options.Triangles >= 10)) { + if (o.Color || (o.Triangles >= 10)) { levels = main.getContourLevels(true); palette = main.getHistPalette(); } - if (this.options.Triangles) + if (o.Triangles) this.drawTriangles(fp, graph, levels, palette); - for (let lvl = 0; lvl < levels.length-1; ++lvl) { + for (let lvl = 0; lvl < levels.length - 1; ++lvl) { const lvl_zmin = Math.max(levels[lvl], fp.scale_zmin), - lvl_zmax = Math.min(levels[lvl+1], fp.scale_zmax); + lvl_zmax = Math.min(levels[lvl + 1], fp.scale_zmax); - if (lvl_zmin >= lvl_zmax) continue; + if (lvl_zmin >= lvl_zmax) + continue; const size = Math.floor(countSelected(lvl_zmin, lvl_zmax) / step), - index = new Int32Array(size); - let pnts = null, select = 0, icnt = 0, - err = null, asymm = false, line = null, ierr = 0, iline = 0; - - if (this.options.Markers || this.options.Circles) - pnts = new PointsCreator(size, fp.webgl, scale/3); + index = new Int32Array(size), + pnts = o.Markers || o.Circles ? new PointsCreator(size, fp.webgl, scale / 3) : null, + err = o.Error ? new Float32Array(size * 6 * 3) : null, + asymm = err && this.matchObjectType(clTGraph2DAsymmErrors), + line = o.Line ? new Float32Array((size - 1) * 6) : null; - if (this.options.Error) { - err = new Float32Array(size*6*3); - asymm = this.matchObjectType(clTGraph2DAsymmErrors); - } - - if (this.options.Line) - line = new Float32Array((size-1)*6); + let select = 0, icnt = 0, ierr = 0, iline = 0; for (let i = 0; i < graph.fNpoints; ++i) { if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) || (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) || - (graph.fZ[i] < lvl_zmin) || (graph.fZ[i] >= lvl_zmax)) continue; + (graph.fZ[i] < lvl_zmin) || (graph.fZ[i] >= lvl_zmax)) + continue; if (step > 1) { - select = (select+1) % step; - if (select !== 0) continue; + select = (select + 1) % step; + if (select) + continue; } index[icnt++] = i; // remember point index for tooltip - const x = fp.grx(graph.fX[i]), - y = fp.gry(graph.fY[i]), - z = fp.grz(graph.fZ[i]); + const x = fp.grx(graph.fX[i]), y = fp.gry(graph.fY[i]), z = fp.grz(graph.fZ[i]); - if (pnts) pnts.addPoint(x, y, z); + pnts?.addPoint(x, y, z); if (err) { err[ierr] = fp.grx(graph.fX[i] - (asymm ? graph.fEXlow[i] : graph.fEX[i])); - err[ierr+1] = y; - err[ierr+2] = z; - err[ierr+3] = fp.grx(graph.fX[i] + (asymm ? graph.fEXhigh[i] : graph.fEX[i])); - err[ierr+4] = y; - err[ierr+5] = z; - ierr+=6; + err[ierr + 1] = y; + err[ierr + 2] = z; + err[ierr + 3] = fp.grx(graph.fX[i] + (asymm ? graph.fEXhigh[i] : graph.fEX[i])); + err[ierr + 4] = y; + err[ierr + 5] = z; + ierr += 6; err[ierr] = x; - err[ierr+1] = fp.gry(graph.fY[i] - (asymm ? graph.fEYlow[i] : graph.fEY[i])); - err[ierr+2] = z; - err[ierr+3] = x; - err[ierr+4] = fp.gry(graph.fY[i] + (asymm ? graph.fEYhigh[i] : graph.fEY[i])); - err[ierr+5] = z; - ierr+=6; + err[ierr + 1] = fp.gry(graph.fY[i] - (asymm ? graph.fEYlow[i] : graph.fEY[i])); + err[ierr + 2] = z; + err[ierr + 3] = x; + err[ierr + 4] = fp.gry(graph.fY[i] + (asymm ? graph.fEYhigh[i] : graph.fEY[i])); + err[ierr + 5] = z; + ierr += 6; err[ierr] = x; - err[ierr+1] = y; - err[ierr+2] = fp.grz(graph.fZ[i] - (asymm ? graph.fEZlow[i] : graph.fEZ[i])); - err[ierr+3] = x; - err[ierr+4] = y; - err[ierr+5] = fp.grz(graph.fZ[i] + (asymm ? graph.fEZhigh[i] : graph.fEZ[i])); - ierr+=6; + err[ierr + 1] = y; + err[ierr + 2] = fp.grz(graph.fZ[i] - (asymm ? graph.fEZlow[i] : graph.fEZ[i])); + err[ierr + 3] = x; + err[ierr + 4] = y; + err[ierr + 5] = fp.grz(graph.fZ[i] + (asymm ? graph.fEZhigh[i] : graph.fEZ[i])); + ierr += 6; } if (line) { - if (iline>=6) { - line[iline] = line[iline-3]; - line[iline+1] = line[iline-2]; - line[iline+2] = line[iline-1]; - iline+=3; + if (iline >= 6) { + line[iline] = line[iline - 3]; + line[iline + 1] = line[iline - 2]; + line[iline + 2] = line[iline - 1]; + iline += 3; } line[iline] = x; - line[iline+1] = y; - line[iline+2] = z; - iline+=3; + line[iline + 1] = y; + line[iline + 2] = z; + iline += 3; } } if (line && (iline > 3) && (line.length === iline)) { const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), linemesh = createLineSegments(line, material); fp.add3DMesh(linemesh, this); linemesh.graph = graph; linemesh.index = index; linemesh.fp = fp; - linemesh.scale0 = 0.7*scale; + linemesh.scale0 = 0.7 * scale; linemesh.tip_name = this.getObjectHint(); linemesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; linemesh.nvertex = 2; linemesh.check_next = true; - - linemesh.tooltip = graph2DTooltip; + linemesh.tooltip = _graph2DTooltip; } if (err) { const lcolor = this.getColor(graph.fLineColor), - material = new LineBasicMaterial({ color: new Color(lcolor), linewidth: graph.fLineWidth }), + material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor), linewidth: graph.fLineWidth }), errmesh = createLineSegments(err, material); fp.add3DMesh(errmesh, this); errmesh.graph = graph; errmesh.index = index; errmesh.fp = fp; - errmesh.scale0 = 0.7*scale; + errmesh.scale0 = 0.7 * scale; errmesh.tip_name = this.getObjectHint(); errmesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; errmesh.nvertex = 6; - - errmesh.tooltip = graph2DTooltip; + errmesh.tooltip = _graph2DTooltip; } if (pnts) { let color = 'blue'; - if (!this.options.Circles || this.options.Color) + if (!o.Circles || o.Color) color = palette?.calcColor(lvl, levels.length) ?? this.getColor(graph.fMarkerColor); - const pr = pnts.createPoints({ color, style: this.options.Circles ? 4 : graph.fMarkerStyle }).then(mesh => { + const pr = pnts.createPoints({ color, fill: o.Circles ? 'white' : undefined, style: o.Circles ? 4 : graph.fMarkerStyle }).then(mesh => { mesh.graph = graph; mesh.fp = fp; mesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - mesh.scale0 = 0.3*scale; + mesh.scale0 = 0.3 * scale; mesh.index = index; - mesh.tip_name = this.getObjectHint(); - mesh.tooltip = graph2DTooltip; + mesh.tooltip = _graph2DTooltip; fp.add3DMesh(mesh, this); }); @@ -1318,17 +1626,49 @@ class TGraph2DPainter extends ObjectPainter { } return Promise.all(promises).then(() => { - if (this.options.Zscale && this.axes_draw) { - const pal = this.getMainPainter()?.findFunction(clTPaletteAxis), - pal_painter = this.getPadPainter()?.findPainterFor(pal); - return pal_painter?.drawPave(); - } + const main2 = this.getMainPainter(), + handle_palette = this.axes_draw || (main2?.draw_content === false); + if (!handle_palette) + return; + + const pal = main2?.findFunction(clTPaletteAxis), + pal_painter = this.getPadPainter()?.findPainterFor(pal); + if (!pal_painter) + return; + + pal_painter.Enabled = o.Zscale; + + if (o.Zscale) + return pal_painter.drawPave(); + + pal_painter.removeG(); // completely remove drawing without need to redraw complete pad }).then(() => { fp.render3D(100); return this; }); } + /** @summary Build three.js of TGraph2D object */ + static async build3d(gr, opt) { + const painter = new TGraph2DPainter(null, gr); + painter.decodeOptions(opt, gr); + + if (painter.options.Contour) { + console.error('Contour plot is not 3d'); + return null; + } + + return TH2Painter.build3d(painter.createHistogram(), painter.options.Axis, true).then(hist_painter => { + painter.axes_draw = true; + const fp = hist_painter.getFramePainter(); + + painter.getFramePainter = () => fp; + painter.getMainPainter = () => hist_painter; + + return painter.drawGraph2D().then(() => fp.create3DScene(-1, true)); + }); + } + /** @summary draw TGraph2D object */ static async draw(dom, gr, opt) { const painter = new TGraph2DPainter(dom, gr); diff --git a/modules/hist/TGraphPainter.mjs b/modules/hist/TGraphPainter.mjs index a2027a07a..38ec0e3ae 100644 --- a/modules/hist/TGraphPainter.mjs +++ b/modules/hist/TGraphPainter.mjs @@ -11,22 +11,26 @@ class TGraphPainter extends TGraphPainter2D { if (!fp.mode3d || !fp.grx || !fp.gry || !fp.grz || !fp.toplevel) return console.log('Frame painter missing base 3d elements'); - if (fp.zoom_xmin !== fp.zoom_xmax) - if ((this.options.pos3d < fp.zoom_xmin) || (this.options.pos3d > fp.zoom_xmax)) return; + const o = this.getOptions(); + if ((fp.zoom_xmin !== fp.zoom_xmax) && ((o.pos3d < fp.zoom_xmin) || (o.pos3d > fp.zoom_xmax))) + return; this.createGraphDrawAttributes(true); const drawbins = this.optimizeBins(1000); - let first = 0, last = drawbins.length-1; + let first = 0, last = drawbins.length - 1; if (fp.zoom_ymin !== fp.zoom_ymax) { - while ((first < last) && (drawbins[first].x < fp.zoom_ymin)) first++; - while ((first < last) && (drawbins[last].x > fp.zoom_ymax)) last--; + while ((first < last) && (drawbins[first].x < fp.zoom_ymin)) + first++; + while ((first < last) && (drawbins[last].x > fp.zoom_ymax)) + last--; } - if (first === last) return; + if (first === last) + return; - const pnts = [], grx = fp.grx(this.options.pos3d); + const pnts = [], grx = fp.grx(o.pos3d); let p0 = drawbins[first]; for (let n = first + 1; n <= last; ++n) { @@ -46,9 +50,10 @@ class TGraphPainter extends TGraphPainter2D { /** @summary Draw axis histogram * @private */ async drawAxisHisto() { - return TH1Painter.draw(this.getDom(), this.createHistogram(), this.options.Axis); + return TH1Painter.draw(this.getDrawDom(), this.createHistogram(), this.getOptions().Axis); } + /** @summary Draw TGraph */ static async draw(dom, graph, opt) { return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); } diff --git a/modules/hist/TGraphTimePainter.mjs b/modules/hist/TGraphTimePainter.mjs index 276b3e8f9..9d778e59a 100644 --- a/modules/hist/TGraphTimePainter.mjs +++ b/modules/hist/TGraphTimePainter.mjs @@ -1,4 +1,4 @@ -import { internals } from '../core.mjs'; +import { internals, clTMarker } from '../core.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { TH1Painter } from '../hist2d/TH1Painter.mjs'; @@ -13,21 +13,31 @@ import { draw } from '../draw.mjs'; class TGraphTimePainter extends ObjectPainter { + #step; // step number + #selfid; // use to identify primitives which should be clean + #wait_animation_frame; // animation flag + #running_timeout; // timeout handle + + constructor(dom, gr, opt) { + super(dom, gr, opt); + this.decodeOptions(opt); + this.#selfid = 'grtime_' + internals.id_counter++; + } + /** @summary Redraw object */ redraw() { - if (this.step === undefined) this.startDrawing(); + if (this.#step === undefined) + this.startDrawing(); } /** @summary Decode drawing options */ decodeOptions(opt) { const d = new DrawOptions(opt || 'REPEAT'); - if (!this.options) this.options = {}; - - Object.assign(this.options, { - once: d.check('ONCE'), - repeat: d.check('REPEAT'), - first: d.check('FIRST') + this.setOptions({ + once: d.check('ONCE'), + repeat: d.check('REPEAT'), + first: d.check('FIRST') }); this.storeDrawOpt(opt); @@ -35,86 +45,81 @@ class TGraphTimePainter extends ObjectPainter { /** @summary Draw primitives */ async drawPrimitives(indx) { - if (!indx) { - indx = 0; - this._doing_primitives = true; - } - - const lst = this.getObject()?.fSteps.arr[this.step]; + const lst = this.getObject()?.fSteps.arr[this.#step]; - if (!lst || (indx >= lst.arr.length)) { - delete this._doing_primitives; + if (!lst || (indx >= lst.arr.length)) return; - } - return draw(this.getDom(), lst.arr[indx], lst.opt[indx]).then(p => { + const obj = lst.arr[indx], + opt = lst.opt[indx] + (obj._typename === clTMarker ? ';no_interactive' : ''); + + return draw(this.getPadPainter(), obj, opt).then(p => { if (p) { - p.$grtimeid = this.selfid; // indicator that painter created by ourself - p.$grstep = this.step; // remember step + p.$grtimeid = this.#selfid; // indicator that painter created by ourself + p.$grstep = this.#step; // remember step } - return this.drawPrimitives(indx+1); + return this.drawPrimitives(indx + 1); }); } /** @summary Continue drawing */ continueDrawing() { - if (!this.options) return; - - const gr = this.getObject(); + const gr = this.getObject(), + o = this.getOptions(); - if (this.options.first) { + if (o.first) { // draw only single frame, cancel all others - delete this.step; + this.#step = undefined; return; } - if (this.wait_animation_frame) { - delete this.wait_animation_frame; + if (this.#wait_animation_frame) { + this.#wait_animation_frame = undefined; // clear pad const pp = this.getPadPainter(); if (!pp) { // most probably, pad is cleared - delete this.step; + this.#step = undefined; return; } - // draw ptrimitives again - this.drawPrimitives().then(() => { + // draw primitives again + this.drawPrimitives(0).then(() => { // clear primitives produced by previous drawing to avoid flicking - pp.cleanPrimitives(p => { return (p.$grtimeid === this.selfid) && (p.$grstep !== this.step); }); + pp.cleanPrimitives(p => { return (p.$grtimeid === this.#selfid) && (p.$grstep !== this.#step); }); this.continueDrawing(); }); - } else if (this.running_timeout) { - clearTimeout(this.running_timeout); - delete this.running_timeout; + } else if (this.#running_timeout) { + clearTimeout(this.#running_timeout); + this.#running_timeout = undefined; - this.wait_animation_frame = true; + this.#wait_animation_frame = true; // use animation frame to disable update in inactive form requestAnimationFrame(() => this.continueDrawing()); } else { let sleeptime = Math.max(gr.fSleepTime, 10); - if (++this.step > gr.fSteps.arr.length) { - if (this.options.repeat) { - this.step = 0; // start again - sleeptime = Math.max(5000, 5*sleeptime); // increase sleep time + if (++this.#step > gr.fSteps.arr.length) { + if (o.repeat) { + this.#step = 0; // start again + sleeptime = Math.max(5000, 5 * sleeptime); // increase sleep time } else { - delete this.step; // clear indicator that animation running + this.#step = undefined; // clear indicator that animation running return; } } - this.running_timeout = setTimeout(() => this.continueDrawing(), sleeptime); + this.#running_timeout = setTimeout(() => this.continueDrawing(), sleeptime); } } - /** @ummary Start drawing of graph time */ + /** @summary Start drawing of TGraphTime */ startDrawing() { - this.step = 0; + this.#step = 0; - return this.drawPrimitives().then(() => { + return this.drawPrimitives(0).then(() => { this.continueDrawing(); return this; }); @@ -123,28 +128,26 @@ class TGraphTimePainter extends ObjectPainter { /** @summary Draw TGraphTime object */ static async draw(dom, gr, opt) { if (!gr.fFrame) { - console.error('Frame histogram not exists'); - return null; + console.error('Frame histogram not exists'); + return null; } - const painter = new TGraphTimePainter(dom, gr); + const painter = new TGraphTimePainter(dom, gr, opt); if (painter.getMainPainter()) { console.error('Cannot draw graph time on top of other histograms'); return null; } - painter.decodeOptions(opt); - if (!gr.fFrame.fTitle && gr.fTitle) { const arr = gr.fTitle.split(';'); gr.fFrame.fTitle = arr[0]; - if (arr[1]) gr.fFrame.fXaxis.fTitle = arr[1]; - if (arr[2]) gr.fFrame.fYaxis.fTitle = arr[2]; + if (arr[1]) + gr.fFrame.fXaxis.fTitle = arr[1]; + if (arr[2]) + gr.fFrame.fYaxis.fTitle = arr[2]; } - painter.selfid = 'grtime_' + internals.id_counter++; // use to identify primitives which should be clean - return TH1Painter.draw(dom, gr.fFrame, '').then(() => { painter.addToPadPrimitives(); return painter.startDrawing(); @@ -153,4 +156,16 @@ class TGraphTimePainter extends ObjectPainter { } // class TGraphTimePainter -export { TGraphTimePainter }; + +/** @summary Draw TRooPlot + * @private */ +async function drawRooPlot(dom, plot) { + return draw(dom, plot._hist, 'hist').then(async hp => { + const arr = []; + for (let i = 0; i < plot._items.arr.length; ++i) + arr.push(draw(dom, plot._items.arr[i], plot._items.opt[i])); + return Promise.all(arr).then(() => hp); + }); +} + +export { TGraphTimePainter, drawRooPlot }; diff --git a/modules/hist/TH1Painter.mjs b/modules/hist/TH1Painter.mjs index 1381d3293..544b8689e 100644 --- a/modules/hist/TH1Painter.mjs +++ b/modules/hist/TH1Painter.mjs @@ -1,6 +1,7 @@ -import { settings, gStyle } from '../core.mjs'; -import { assignFrame3DMethods, drawBinsLego } from './hist3d.mjs'; +import { gStyle } from '../core.mjs'; +import { crete3DFrame, drawBinsLego } from './hist3d.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; +import { TFramePainter } from '../gpad/TFramePainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; import { TH1Painter as TH1Painter2D } from '../hist2d/TH1Painter.mjs'; @@ -11,50 +12,69 @@ import { TH1Painter as TH1Painter2D } from '../hist2d/TH1Painter.mjs'; class TH1Painter extends TH1Painter2D { /** @summary draw TH1 object in 3D mode */ - draw3D(reason) { + async draw3D(reason) { this.mode3d = true; - const main = this.getFramePainter(), // who makes axis drawing + const fp = this.getFramePainter(), // who makes axis drawing is_main = this.isMainPainter(), // is main histogram - histo = this.getHisto(), - zmult = 1 + 2*gStyle.fHistTopMargin; - let pr = Promise.resolve(true); + o = this.getOptions(); + + o.zmult = 1 + 2 * gStyle.fHistTopMargin; + let pr = Promise.resolve(true), full_draw = true; if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - } else { + const res = is_main ? fp.resize3D() : false; + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); + } + } + + if (full_draw) { this.createHistDrawAttributes(true); - this.scanContent(true); // may be required for axis drawings + this.scanContent(reason === 'zoom'); // may be required for axis drawings - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { use_y_for_z: true, zmult, zoom: settings.Zooming, ndim: 1, - draw: (this.options.Axis !== -1), drawany: this.options.isCartesian() }); - }); - } + if (is_main) + pr = crete3DFrame(this, TAxisPainter, o.Render3D); - if (main.mode3d) { + if (fp.mode3d) { pr = pr.then(() => { drawBinsLego(this); - main.render3D(); + fp.render3D(); this.updateStatWebCanvas(); - main.addKeysHandler(); + fp.addKeysHandler(); }); } } if (is_main) - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && ((this.options.Lego === 12) || (this.options.Lego === 14)))); + pr = pr.then(() => this.drawColorPalette(o.Zscale && o.canHavePalette())); return pr.then(() => this.updateFunctions()) .then(() => this.updateHistTitle()) .then(() => this); } + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt) { + const painter = new TH1Painter(null, histo); + painter.decodeOptions(opt); + painter.scanContent(); + + painter.createHistDrawAttributes(true); + painter.options.zmult = 1 + 2 * gStyle.fHistTopMargin; + + const fp = new TFramePainter(null, null); + painter.getFramePainter = () => fp; + + return crete3DFrame(painter, TAxisPainter) + .then(() => drawBinsLego(painter)) + .then(() => fp.create3DScene(-1, true)); + } + + /** @summary draw TH1 object */ static async draw(dom, histo, opt) { return THistPainter._drawHist(new TH1Painter(dom, histo), opt); diff --git a/modules/hist/TH2Painter.mjs b/modules/hist/TH2Painter.mjs index 6cff26328..4f2643611 100644 --- a/modules/hist/TH2Painter.mjs +++ b/modules/hist/TH2Painter.mjs @@ -1,8 +1,8 @@ -import { settings, gStyle, clTMultiGraph, kNoZoom } from '../core.mjs'; -import { Vector2, BufferGeometry, BufferAttribute, Mesh, MeshBasicMaterial, ShapeUtils } from '../three.mjs'; -import { getMaterialArgs } from '../base/base3d.mjs'; -import { assignFrame3DMethods, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D } from './hist3d.mjs'; +import { gStyle, clTMultiGraph, kNoZoom } from '../core.mjs'; +import { getMaterialArgs, THREE } from '../base/base3d.mjs'; +import { crete3DFrame, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D } from './hist3d.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; +import { TFramePainter } from '../gpad/TFramePainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; import { TH2Painter as TH2Painter2D } from '../hist2d/TH2Painter.mjs'; @@ -11,11 +11,11 @@ import { TH2Painter as TH2Painter2D } from '../hist2d/TH2Painter.mjs'; * @private */ function drawTH2PolyLego(painter) { const histo = painter.getHisto(), - pmain = painter.getFramePainter(), - axis_zmin = pmain.z_handle.getScaleMin(), - axis_zmax = pmain.z_handle.getScaleMax(), + fp = painter.getFramePainter(), + axis_zmin = fp.z_handle.getScaleMin(), + axis_zmax = fp.z_handle.getScaleMax(), len = histo.fBins.arr.length, - z0 = pmain.grz(axis_zmin); + z0 = fp.grz(axis_zmin); let colindx, bin, i, z1; // use global coordinates @@ -27,16 +27,19 @@ function drawTH2PolyLego(painter) { for (i = 0; i < len; ++i) { bin = histo.fBins.arr[i]; - if (bin.fContent < axis_zmin) continue; + if (bin.fContent < axis_zmin) + continue; colindx = cntr.getPaletteIndex(palette, bin.fContent); - if (colindx === null) continue; + if (colindx === null) + continue; // check if bin outside visible range - if ((bin.fXmin > pmain.scale_xmax) || (bin.fXmax < pmain.scale_xmin) || - (bin.fYmin > pmain.scale_ymax) || (bin.fYmax < pmain.scale_ymin)) continue; + if ((bin.fXmin > fp.scale_xmax) || (bin.fXmax < fp.scale_xmin) || + (bin.fYmin > fp.scale_ymax) || (bin.fYmax < fp.scale_ymin)) + continue; - z1 = pmain.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent); + z1 = fp.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent); const all_pnts = [], all_faces = []; let ngraphs = 1, gr = bin.fPoly, nfaces = 0; @@ -47,11 +50,12 @@ function drawTH2PolyLego(painter) { } for (let ngr = 0; ngr < ngraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; const x = gr.fX, y = gr.fY; let npnts = gr.fNpoints; - while ((npnts>2) && (x[0]===x[npnts-1]) && (y[0]===y[npnts-1])) --npnts; + while ((npnts > 2) && (x[0] === x[npnts - 1]) && (y[0] === y[npnts - 1])) --npnts; let pnts, faces; @@ -59,18 +63,19 @@ function drawTH2PolyLego(painter) { // run two loops - on the first try to compress data, on second - run as is (removing duplication) let lastx, lasty, currx, curry, - dist2 = pmain.size_x3d*pmain.size_z3d; - const dist2limit = (ntry > 0) ? 0 : dist2/1e6; + dist2 = fp.size_x3d * fp.size_z3d; + const dist2limit = (ntry > 0) ? 0 : dist2 / 1e6; - pnts = []; faces = null; + pnts = []; + faces = null; for (let vert = 0; vert < npnts; ++vert) { - currx = pmain.grx(x[vert]); - curry = pmain.gry(y[vert]); + currx = fp.grx(x[vert]); + curry = fp.gry(y[vert]); if (vert > 0) - dist2 = (currx-lastx)*(currx-lastx) + (curry-lasty)*(curry-lasty); + dist2 = (currx - lastx) ** 2 + (curry - lasty) ** 2; if (dist2 > dist2limit) { - pnts.push(new Vector2(currx, curry)); + pnts.push(new THREE.Vector2(currx, curry)); lastx = currx; lasty = curry; } @@ -78,12 +83,13 @@ function drawTH2PolyLego(painter) { try { if (pnts.length > 2) - faces = ShapeUtils.triangulateShape(pnts, []); - } catch (e) { + faces = THREE.ShapeUtils.triangulateShape(pnts, []); + } catch { faces = null; } - if (faces && (faces.length > pnts.length-3)) break; + if (faces && (faces.length > pnts.length - 3)) + break; } if (faces?.length && pnts) { @@ -91,11 +97,12 @@ function drawTH2PolyLego(painter) { all_faces.push(faces); nfaces += faces.length * 2; - if (z1 > z0) nfaces += pnts.length*2; + if (z1 > z0) + nfaces += pnts.length * 2; } } - const pos = new Float32Array(nfaces*9); + const pos = new Float32Array(nfaces * 9); let indx = 0; for (let ngr = 0; ngr < all_pnts.length; ++ngr) { @@ -104,72 +111,72 @@ function drawTH2PolyLego(painter) { for (let layer = 0; layer < 2; ++layer) { for (let n = 0; n < faces.length; ++n) { const face = faces[n], - pnt1 = pnts[face[0]], - pnt2 = pnts[face[layer === 0 ? 2 : 1]], - pnt3 = pnts[face[layer === 0 ? 1 : 2]]; + pnt1 = pnts[face[0]], + pnt2 = pnts[face[layer === 0 ? 2 : 1]], + pnt3 = pnts[face[layer === 0 ? 1 : 2]]; pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; pos[indx] = pnt3.x; - pos[indx+1] = pnt3.y; - pos[indx+2] = layer ? z1 : z0; - indx+=3; + pos[indx + 1] = pnt3.y; + pos[indx + 2] = layer ? z1 : z0; + indx += 3; } } - if (z1>z0) { + if (z1 > z0) { for (let n = 0; n < pnts.length; ++n) { - const pnt1 = pnts[n], pnt2 = pnts[n > 0 ? n - 1 : pnts.length - 1]; + const pnt1 = pnts.at(n), pnt2 = pnts.at(n > 0 ? n - 1 : - 1); pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z0; - indx+=3; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z0; + indx += 3; pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z0; - indx+=3; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z0; + indx += 3; pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z1; - indx+=3; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z1; + indx += 3; pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z0; - indx+=3; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z0; + indx += 3; pos[indx] = pnt2.x; - pos[indx+1] = pnt2.y; - pos[indx+2] = z1; - indx+=3; + pos[indx + 1] = pnt2.y; + pos[indx + 2] = z1; + indx += 3; pos[indx] = pnt1.x; - pos[indx+1] = pnt1.y; - pos[indx+2] = z1; - indx+=3; + pos[indx + 1] = pnt1.y; + pos[indx + 2] = z1; + indx += 3; } } } - const geometry = new BufferGeometry(); - geometry.setAttribute('position', new BufferAttribute(pos, 3)); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3)); geometry.computeVertexNormals(); - const material = new MeshBasicMaterial(getMaterialArgs(painter._color_palette?.getColor(colindx), { vertexColors: false })), - mesh = new Mesh(geometry, material); + const material = new THREE.MeshBasicMaterial(getMaterialArgs(painter.getHistPalette()?.getColor(colindx), { vertexColors: false, side: THREE.DoubleSide })), + mesh = new THREE.Mesh(geometry, material); - pmain.add3DMesh(mesh); + fp.add3DMesh(mesh); mesh.painter = painter; mesh.bins_index = i; @@ -178,24 +185,21 @@ function drawTH2PolyLego(painter) { mesh.tip_color = 0x00FF00; mesh.tooltip = function(/* intersects */) { - const p = this.painter, main = p.getFramePainter(), - bin = p.getObject().fBins.arr[this.bins_index], - - tip = { - use_itself: true, // indicate that use mesh itself for highlighting - x1: main.grx(bin.fXmin), - x2: main.grx(bin.fXmax), - y1: main.gry(bin.fYmin), - y2: main.gry(bin.fYmax), - z1: this.draw_z0, - z2: this.draw_z1, - bin: this.bins_index, - value: bin.fContent, - color: this.tip_color, - lines: p.getPolyBinTooltips(this.bins_index) + const p = this.painter, + tbin = p.getObject().fBins.arr[this.bins_index]; + return { + use_itself: true, // indicate that use mesh itself for highlighting + x1: fp.grx(tbin.fXmin), + x2: fp.grx(tbin.fXmax), + y1: fp.gry(tbin.fYmin), + y2: fp.gry(tbin.fYmax), + z1: this.draw_z0, + z2: this.draw_z1, + bin: this.bins_index, + value: bin.fContent, + color: this.tip_color, + lines: p.getPolyBinTooltips(this.bins_index) }; - - return tip; }; } } @@ -204,75 +208,91 @@ function drawTH2PolyLego(painter) { * @private */ class TH2Painter extends TH2Painter2D { + /** @summary Check range for 3D + * @private */ + checkRangeFor3D(o) { + const pad = this.getPadPainter()?.getRootPad(true), + logz = pad?.fLogv ?? pad?.fLogz; + let zmult = 1; + + if (o.ohmin && o.ohmax) { + this.zmin = o.hmin; + this.zmax = o.hmax; + } else if (o.minimum !== kNoZoom && o.maximum !== kNoZoom) { + this.zmin = o.minimum; + this.zmax = o.maximum; + } else if (this.draw_content || this.gmaxbin) { + this.zmin = logz ? this.gminposbin * 0.3 : this.gminbin; + this.zmax = this.gmaxbin; + zmult = 1 + 2 * gStyle.fHistTopMargin; + } + + if (logz && (this.zmin <= 0)) + this.zmin = this.zmax * 1e-5; + + this.createHistDrawAttributes(true); + return zmult; + } + + /** @summary Create 3D object for histogram bins + * @private */ + draw3DBins(o) { + if (this.isTH2Poly()) + drawTH2PolyLego(this); + else if (o.Contour) + drawBinsContour3D(this, true); + else if (o.Surf) + drawBinsSurf3D(this); + else if (o.Error) + drawBinsError3D(this); + else + drawBinsLego(this); + } + /** @summary draw TH2 object in 3D mode */ async draw3D(reason) { this.mode3d = true; - const main = this.getFramePainter(), // who makes axis drawing + const fp = this.getFramePainter(), // who makes axis drawing is_main = this.isMainPainter(), // is main histogram - histo = this.getHisto(); - let pr = Promise.resolve(true); + o = this.getOptions(); + + let pr = Promise.resolve(true), full_draw = true; if (reason === 'resize') { - if (is_main && main.resize3D()) main.render3D(); - } else { - const pad = this.getPadPainter().getRootPad(true), - logz = pad?.fLogv ?? pad?.fLogz; - let zmult = 1; - - if (this.options.minimum !== kNoZoom && this.options.maximum !== kNoZoom) { - this.zmin = this.options.minimum; - this.zmax = this.options.maximum; - } else if (this.draw_content || (this.gmaxbin !== 0)) { - this.zmin = logz ? this.gminposbin * 0.3 : this.gminbin; - this.zmax = this.gmaxbin; - zmult = 1 + 2*gStyle.fHistTopMargin; + const res = is_main ? fp.resize3D() : false; + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); } + } - if (logz && (this.zmin <= 0)) - this.zmin = this.zmax * 1e-5; - - this.createHistDrawAttributes(true); + if (full_draw) { + o.zmult = this.checkRangeFor3D(o); - if (is_main) { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { zmult, zoom: settings.Zooming, ndim: 2, - draw: this.options.Axis !== -1, drawany: this.options.isCartesian(), - reverse_x: this.options.RevX, reverse_y: this.options.RevY }); - }); - } + if (is_main) + pr = crete3DFrame(this, TAxisPainter, o.Render3D); - if (main.mode3d) { + if (fp.mode3d) { pr = pr.then(() => { - if (this.draw_content) { - if (this.isTH2Poly()) - drawTH2PolyLego(this); - else if (this.options.Contour) - drawBinsContour3D(this, true); - else if (this.options.Surf) - drawBinsSurf3D(this); - else if (this.options.Error) - drawBinsError3D(this); - else - drawBinsLego(this); - } else if (this.options.Axis && this.options.Zscale) { + if (this.draw_content) + this.draw3DBins(o); + else if (o.Axis && o.Zscale) { this.getContourLevels(true); this.getHistPalette(); } - main.render3D(); + fp.render3D(); this.updateStatWebCanvas(); - main.addKeysHandler(); + fp.addKeysHandler(); }); } } // (re)draw palette by resize while canvas may change dimension if (is_main) { - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && ((this.options.Lego === 12) || (this.options.Lego === 14) || - (this.options.Surf === 11) || (this.options.Surf === 12)))); + pr = pr.then(() => this.drawColorPalette(o.Zscale && ((o.Lego === 12) || (o.Lego === 14) || + (o.Surf === 11) || (o.Surf === 12)))); } return pr.then(() => this.updateFunctions()) @@ -280,6 +300,30 @@ class TH2Painter extends TH2Painter2D { .then(() => this); } + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt, get_painter) { + const painter = new TH2Painter(null, histo); + painter.decodeOptions(opt); + + const o = painter.getOptions(); + if (painter.isTH2Poly()) + o.Lego = 12; + painter.scanContent(); + + o.zmult = painter.checkRangeFor3D(o); + + const fp = new TFramePainter(null, null); + // return dummy frame painter as result + painter.getFramePainter = () => fp; + + return crete3DFrame(painter, TAxisPainter).then(() => { + if (painter.draw_content) + painter.draw3DBins(o); + + return get_painter ? painter : fp.create3DScene(-1, true); + }); + } + /** @summary draw TH2 object */ static async draw(dom, histo, opt) { return THistPainter._drawHist(new TH2Painter(dom, histo), opt); diff --git a/modules/hist/TH3Painter.mjs b/modules/hist/TH3Painter.mjs index bc24a4f32..a3cf71903 100644 --- a/modules/hist/TH3Painter.mjs +++ b/modules/hist/TH3Painter.mjs @@ -1,12 +1,11 @@ -import { gStyle, settings, kInspect, clTF1, clTF3, clTProfile3D, BIT, isFunc } from '../core.mjs'; -import { Matrix4, BufferGeometry, BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, - LineBasicMaterial, SphereGeometry } from '../three.mjs'; +import { gStyle, kInspect, clTF1, clTF3, clTProfile3D, BIT, isFunc } from '../core.mjs'; import { TRandom, floatToString } from '../base/BasePainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; -import { createLineSegments, PointsCreator, Box3D } from '../base/base3d.mjs'; +import { TFramePainter } from '../gpad/TFramePainter.mjs'; +import { createLineSegments, PointsCreator, Box3D, THREE } from '../base/base3d.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; -import { assignFrame3DMethods } from './hist3d.mjs'; +import { crete3DFrame } from './hist3d.mjs'; import { proivdeEvalPar, getTF1Value } from '../base/func.mjs'; @@ -17,13 +16,16 @@ import { proivdeEvalPar, getTF1Value } from '../base/func.mjs'; class TH3Painter extends THistPainter { + #box_option; // actual box option + /** @summary Returns number of histogram dimensions */ getDimension() { return 3; } /** @summary Scan TH3 histogram content */ scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) return; + // no need to re-scan histogram while result does not depend from axis selection + if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) + return; const histo = this.getHisto(); @@ -36,7 +38,7 @@ class TH3Painter extends THistPainter { for (let i = 0; i < this.nbinsx; ++i) { for (let j = 0; j < this.nbinsy; ++j) { for (let k = 0; k < this.nbinsz; ++k) { - const bin_content = histo.getBinContent(i+1, j+1, k+1); + const bin_content = histo.getBinContent(i + 1, j + 1, k + 1); if (bin_content < this.gminbin) this.gminbin = bin_content; else if (bin_content > this.gmaxbin) @@ -49,13 +51,12 @@ class TH3Painter extends THistPainter { } if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; + this.gminposbin = this.gmaxbin * 1e-4; - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); + this.draw_content = this.gmaxbin || this.gminbin; this.transferFunc = this.findFunction(clTF1, 'TransferFunction'); - if (this.transferFunc && !this.transferFunc.TestBit(BIT(9))) // TF1::kNotDraw - this.transferFunc.InvertBit(BIT(9)); + this.transferFunc?.SetBit(BIT(9), true); // TF1::kNotDraw } /** @summary Count TH3 statistic */ @@ -68,29 +69,33 @@ class TH3Painter extends THistPainter { k1 = this.getSelectIndex('z', 'left'), k2 = this.getSelectIndex('z', 'right'), fp = this.getFramePainter(), - res = { name: histo.fName, entries: 0, eff_entries: 0, integral: 0, - meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0, - skewx: 0, skewy: 0, skewz: 0, skewd: 0, kurtx: 0, kurty: 0, kurtz: 0, kurtd: 0 }, + res = { + name: histo.fName, entries: 0, eff_entries: 0, integral: 0, + meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0, + skewx: 0, skewy: 0, skewz: 0, skewd: 0, kurtx: 0, kurty: 0, kurtz: 0, kurtd: 0 + }, has_counted_stat = (Math.abs(histo.fTsumw) > 1e-300) && !fp.isAxisZoomed('x') && !fp.isAxisZoomed('y') && !fp.isAxisZoomed('z'); let xi, yi, zi, xx, xside, yy, yside, zz, zside, cont, stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0; - if (!isFunc(cond)) cond = null; + if (!isFunc(cond)) + cond = null; - for (xi = 0; xi < this.nbinsx+2; ++xi) { + for (xi = 0; xi < this.nbinsx + 2; ++xi) { xx = xaxis.GetBinCoord(xi - 0.5); xside = (xi < i1) ? 0 : (xi > i2 ? 2 : 1); - for (yi = 0; yi < this.nbinsy+2; ++yi) { + for (yi = 0; yi < this.nbinsy + 2; ++yi) { yy = yaxis.GetBinCoord(yi - 0.5); yside = (yi < j1) ? 0 : (yi > j2 ? 2 : 1); - for (zi = 0; zi < this.nbinsz+2; ++zi) { + for (zi = 0; zi < this.nbinsz + 2; ++zi) { zz = zaxis.GetBinCoord(zi - 0.5); zside = (zi < k1) ? 0 : (zi > k2 ? 2 : 1); - if (cond && !cond(xx, yy, zz)) continue; + if (cond && !cond(xx, yy, zz)) + continue; cont = histo.getBinContent(xi, yi, zi); res.entries += cont; @@ -101,9 +106,9 @@ class TH3Painter extends THistPainter { stat_sumx1 += xx * cont; stat_sumy1 += yy * cont; stat_sumz1 += zz * cont; - stat_sumx2 += xx**2 * cont; - stat_sumy2 += yy**2 * cont; - stat_sumz2 += zz**2 * cont; + stat_sumx2 += xx ** 2 * cont; + stat_sumy2 += yy ** 2 * cont; + stat_sumz2 += zz ** 2 * cont; } } } @@ -131,21 +136,22 @@ class TH3Painter extends THistPainter { res.integral = stat_sum0; - if (histo.fEntries > 1) + if (histo.fEntries > 0) res.entries = histo.fEntries; - res.eff_entries = stat_sumw2 ? stat_sum0*stat_sum0/stat_sumw2 : Math.abs(stat_sum0); + res.eff_entries = stat_sumw2 ? stat_sum0 * stat_sum0 / stat_sumw2 : Math.abs(stat_sum0); if (count_skew && !this.isTH2Poly()) { - let sumx3 = 0, sumy3 = 0, sumz3 = 0, sumx4 = 0, sumy4 = 0, sumz4 = 0, np = 0, w = 0; - for (let xi = i1; xi < i2; ++xi) { + let sumx3 = 0, sumy3 = 0, sumz3 = 0, sumx4 = 0, sumy4 = 0, sumz4 = 0, np = 0; + for (xi = i1; xi < i2; ++xi) { xx = xaxis.GetBinCoord(xi + 0.5); - for (let yi = j1; yi < j2; ++yi) { + for (yi = j1; yi < j2; ++yi) { yy = yaxis.GetBinCoord(yi + 0.5); - for (let zi = k1; zi < k2; ++zi) { + for (zi = k1; zi < k2; ++zi) { zz = zaxis.GetBinCoord(zi + 0.5); - if (cond && !cond(xx, yy, zz)) continue; - w = histo.getBinContent(xi + 1, yi + 1, zi + 1); + if (cond && !cond(xx, yy, zz)) + continue; + const w = histo.getBinContent(xi + 1, yi + 1, zi + 1); np += w; sumx3 += w * Math.pow(xx - res.meanx, 3); sumy3 += w * Math.pow(yy - res.meany, 3); @@ -164,21 +170,21 @@ class TH3Painter extends THistPainter { stddev4y = Math.pow(res.rmsy, 4), stddev4z = Math.pow(res.rmsz, 4); - if (np * stddev3x !== 0) + if (np * stddev3x) res.skewx = sumx3 / (np * stddev3x); - if (np * stddev3y !== 0) + if (np * stddev3y) res.skewy = sumy3 / (np * stddev3y); - if (np * stddev3z !== 0) + if (np * stddev3z) res.skewz = sumz3 / (np * stddev3z); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; - if (np * stddev4x !== 0) + if (np * stddev4x) res.kurtx = sumx4 / (np * stddev4x) - 3; - if (np * stddev4y !== 0) + if (np * stddev4y) res.kurty = sumy4 / (np * stddev4y) - 3; - if (np * stddev4z !== 0) + if (np * stddev4z) res.kurtz = sumz4 / (np * stddev4z) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; } return res; @@ -190,7 +196,8 @@ class TH3Painter extends THistPainter { if (this.isIgnoreStatsFill()) return false; - if (dostat === 1) dostat = 1111; + if (dostat === 1) + dostat = 1111; const print_name = dostat % 10, print_entries = Math.floor(dostat / 10) % 10, @@ -200,8 +207,6 @@ class TH3Painter extends THistPainter { print_skew = Math.floor(dostat / 10000000) % 10, print_kurt = Math.floor(dostat / 100000000) % 10, data = this.countStat(undefined, (print_skew > 0) || (print_kurt > 0)); - // print_under = Math.floor(dostat / 10000) % 10, - // print_over = Math.floor(dostat / 100000) % 10; stat.clearPave(); @@ -246,7 +251,8 @@ class TH3Painter extends THistPainter { stat.addText(`Kurtosis z = ${stat.format(data.kurtz)}`); } - if (dofit) stat.fillFunctionStat(this.findFunction(clTF3), dofit, 3); + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF3), dofit, 3); return true; } @@ -256,18 +262,18 @@ class TH3Painter extends THistPainter { const lines = [], histo = this.getHisto(); lines.push(this.getObjectHint(), - `x = ${this.getAxisBinTip('x', histo.fXaxis, ix)} xbin=${ix+1}`, - `y = ${this.getAxisBinTip('y', histo.fYaxis, iy)} ybin=${iy+1}`, - `z = ${this.getAxisBinTip('z', histo.fZaxis, iz)} zbin=${iz+1}`); + `x = ${this.getAxisBinTip('x', histo.fXaxis, ix)} xbin=${ix + 1}`, + `y = ${this.getAxisBinTip('y', histo.fYaxis, iy)} ybin=${iy + 1}`, + `z = ${this.getAxisBinTip('z', histo.fZaxis, iz)} zbin=${iz + 1}`); - const binz = histo.getBinContent(ix+1, iy+1, iz+1); + const binz = histo.getBinContent(ix + 1, iy + 1, iz + 1); if (binz === Math.round(binz)) lines.push(`entries = ${binz}`); else lines.push(`entries = ${floatToString(binz, gStyle.fStatFormat)}`); if (this.matchObjectType(clTProfile3D)) { - const errz = histo.getBinError(histo.getBin(ix+1, iy+1, iz+1)); + const errz = histo.getBinError(histo.getBin(ix + 1, iy + 1, iz + 1)); lines.push('error = ' + ((errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat))); } @@ -279,7 +285,7 @@ class TH3Painter extends THistPainter { * @return {Promise|false} either Promise or just false that drawing cannot be performed */ draw3DScatter() { const histo = this.getObject(), - main = this.getFramePainter(), + fp = this.getFramePainter(), i1 = this.getSelectIndex('x', 'left', 0.5), i2 = this.getSelectIndex('x', 'right', 0), j1 = this.getSelectIndex('y', 'left', 0.5), @@ -292,26 +298,26 @@ class TH3Painter extends THistPainter { return Promise.resolve(true); // scale down factor if too large values - const coef = (this.gmaxbin > 1000) ? 1000/this.gmaxbin : 1, + const coef = (this.gmaxbin > 1000) ? 1000 / this.gmaxbin : 1, content_lmt = Math.max(0, this.gminbin); let numpixels = 0, sumz = 0; for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); + bin_content = histo.getBinContent(i + 1, j + 1, k + 1); sumz += bin_content; - if (bin_content <= content_lmt) continue; - numpixels += Math.round(bin_content*coef); + if (bin_content > content_lmt) + numpixels += Math.round(bin_content * coef); } } } // too many pixels - use box drawing - if (numpixels > (main.webgl ? 100000 : 30000)) + if (numpixels > (fp.webgl ? 100000 : 30000)) return false; - const pnts = new PointsCreator(numpixels, main.webgl, main.size_x3d/200), + const pnts = new PointsCreator(numpixels, fp.webgl, fp.size_x3d / 200), bins = new Int32Array(numpixels), rnd = new TRandom(sumz); let nbin = 0; @@ -319,45 +325,47 @@ class TH3Painter extends THistPainter { for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (bin_content <= content_lmt) continue; - const num = Math.round(bin_content*coef); + bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + if (bin_content <= content_lmt) + continue; + const num = Math.round(bin_content * coef); for (let n = 0; n < num; ++n) { const binx = histo.fXaxis.GetBinCoord(i + rnd.random()), - biny = histo.fYaxis.GetBinCoord(j + rnd.random()), - binz = histo.fZaxis.GetBinCoord(k + rnd.random()); + biny = histo.fYaxis.GetBinCoord(j + rnd.random()), + binz = histo.fZaxis.GetBinCoord(k + rnd.random()); // remember bin index for tooltip - bins[nbin++] = histo.getBin(i+1, j+1, k+1); + bins[nbin++] = histo.getBin(i + 1, j + 1, k + 1); - pnts.addPoint(main.grx(binx), main.gry(biny), main.grz(binz)); + pnts.addPoint(fp.grx(binx), fp.gry(biny), fp.grz(binz)); } } } } return pnts.createPoints({ color: this.getColor(histo.fMarkerColor) }).then(mesh => { - main.add3DMesh(mesh); + fp.add3DMesh(mesh); mesh.bins = bins; - mesh.painter = this; + mesh.tip_painter = this; mesh.tip_color = histo.fMarkerColor === 3 ? 0xFF0000 : 0x00FF00; mesh.tooltip = function(intersect) { const indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]); - - tip.x1 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix)); - tip.x2 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1)); - tip.y1 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy)); - tip.y2 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy+1)); - tip.z1 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz)); - tip.z2 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz+1)); + if ((indx < 0) || (indx >= this.bins.length)) + return null; + + const p = this.tip_painter, + thisto = p.getHisto(), + tip = p.get3DToolTip(this.bins[indx]); + + tip.x1 = fp.grx(thisto.fXaxis.GetBinLowEdge(tip.ix)); + tip.x2 = fp.grx(thisto.fXaxis.GetBinLowEdge(tip.ix + 1)); + tip.y1 = fp.gry(thisto.fYaxis.GetBinLowEdge(tip.iy)); + tip.y2 = fp.gry(thisto.fYaxis.GetBinLowEdge(tip.iy + 1)); + tip.z1 = fp.grz(thisto.fZaxis.GetBinLowEdge(tip.iz)); + tip.z2 = fp.grz(thisto.fZaxis.GetBinLowEdge(tip.iz + 1)); tip.color = this.tip_color; tip.opacity = 0.3; @@ -373,83 +381,68 @@ class TH3Painter extends THistPainter { if (!this.draw_content) return false; - let box_option = this.options.Box ? this.options.BoxStyle : 0; + const o = this.getOptions(); + + let box_option = o.BoxStyle; - if (!box_option && this.options.Scat) { + if (!box_option && o.Scat) { const promise = this.draw3DScatter(); - if (promise !== false) return promise; + if (promise !== false) + return promise; box_option = 12; // fall back to box2 draw option - } else if (!box_option && !this.options.GLBox && !this.options.GLColor && !this.options.Lego) + } else if (!box_option && !o.GLBox && !o.GLColor && !o.Lego) box_option = 12; // default draw option const histo = this.getHisto(), - main = this.getFramePainter(); + fp = this.getFramePainter(); - let buffer_size = 0, use_lambert = false, + let use_lambert = false, use_helper = false, use_colors = false, use_opacity = 1, exclude_content = -1, logv = this.getPadPainter()?.getRootPad()?.fLogv, use_scale = true, scale_offset = 0, - single_bin_verts, single_bin_norms, fillcolor = this.getColor(histo.fFillColor), - tipscale = 0.5; + tipscale = 0.5, single_bin_geom; - if (!box_option && this.options.Lego) - box_option = (this.options.Lego === 1) ? 10 : this.options.Lego; + if (!box_option && o.Lego) + box_option = (o.Lego === 1) ? 10 : o.Lego; - if ((this.options.GLBox === 11) || (this.options.GLBox === 12)) { + if ((o.GLBox === 11) || (o.GLBox === 12)) { tipscale = 0.4; use_lambert = true; - if (this.options.GLBox === 12) use_colors = true; - - const geom = main.webgl ? new SphereGeometry(0.5, 16, 12) : new SphereGeometry(0.5, 8, 6); - geom.applyMatrix4(new Matrix4().makeRotationX(Math.PI/2)); - geom.computeVertexNormals(); - - const indx = geom.getIndex().array, - pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; - - buffer_size = indx.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); - - for (let k = 0; k < indx.length; ++k) { - const iii = indx[k]*3; - single_bin_verts[k*3] = pos[iii]; - single_bin_verts[k*3+1] = pos[iii+1]; - single_bin_verts[k*3+2] = pos[iii+2]; - single_bin_norms[k*3] = norm[iii]; - single_bin_norms[k*3+1] = norm[iii+1]; - single_bin_norms[k*3+2] = norm[iii+2]; - } + if (o.GLBox === 12) + use_colors = true; + + single_bin_geom = new THREE.SphereGeometry(0.5, fp.webgl ? 16 : 8, fp.webgl ? 12 : 6); + single_bin_geom.applyMatrix4(new THREE.Matrix4().makeRotationX(Math.PI / 2)); + single_bin_geom.computeVertexNormals(); } else { const indicies = Box3D.Indexes, normals = Box3D.Normals, - vertices = Box3D.Vertices; - - buffer_size = indicies.length*3; - single_bin_verts = new Float32Array(buffer_size); - single_bin_norms = new Float32Array(buffer_size); + vertices = Box3D.Vertices, + buffer_size = indicies.length * 3, + single_bin_verts = new Float32Array(buffer_size), + single_bin_norms = new Float32Array(buffer_size); for (let k = 0, nn = -3; k < indicies.length; ++k) { const vert = vertices[indicies[k]]; - single_bin_verts[k*3] = vert.x-0.5; - single_bin_verts[k*3+1] = vert.y-0.5; - single_bin_verts[k*3+2] = vert.z-0.5; - - if (k%6 === 0) nn+=3; - single_bin_norms[k*3] = normals[nn]; - single_bin_norms[k*3+1] = normals[nn+1]; - single_bin_norms[k*3+2] = normals[nn+2]; + single_bin_verts[k * 3] = vert.x - 0.5; + single_bin_verts[k * 3 + 1] = vert.y - 0.5; + single_bin_verts[k * 3 + 2] = vert.z - 0.5; + + if (k % 6 === 0) + nn += 3; + single_bin_norms[k * 3] = normals[nn]; + single_bin_norms[k * 3 + 1] = normals[nn + 1]; + single_bin_norms[k * 3 + 2] = normals[nn + 2]; } use_helper = true; if (box_option === 12) use_colors = true; - else if (box_option === 13) { + else if (box_option === 13) { use_colors = true; use_helper = false; - } else if (this.options.GLColor) { + } else if (o.GLColor) { use_colors = true; use_opacity = 0.5; use_scale = false; @@ -457,14 +450,18 @@ class TH3Painter extends THistPainter { exclude_content = 0; use_lambert = true; } + + single_bin_geom = new THREE.BufferGeometry(); + single_bin_geom.setAttribute('position', new THREE.BufferAttribute(single_bin_verts, 3)); + single_bin_geom.setAttribute('normal', new THREE.BufferAttribute(single_bin_norms, 3)); } - this._box_option = box_option; + this.#box_option = box_option; if (use_scale && logv) { if (this.gminposbin && (this.gmaxbin > this.gminposbin)) { scale_offset = Math.log(this.gminposbin) - 0.1; - use_scale = 1/(Math.log(this.gmaxbin) - scale_offset); + use_scale = 1 / (Math.log(this.gmaxbin) - scale_offset); } else { logv = 0; use_scale = 1; @@ -473,270 +470,204 @@ class TH3Painter extends THistPainter { use_scale = (this.gminbin || this.gmaxbin) ? 1 / Math.max(Math.abs(this.gminbin), Math.abs(this.gmaxbin)) : 1; const get_bin_weight = content => { - if ((exclude_content >= 0) && (content < exclude_content)) return 0; - if (!use_scale) return 1; + if ((exclude_content >= 0) && (content < exclude_content)) + return 0; + if (!use_scale) + return 1; if (logv) { - if (content <= 0) return 0; + if (content <= 0) + return 0; content = Math.log(content) - scale_offset; } - return Math.pow(Math.abs(content*use_scale), 0.3333); - }, i1 = this.getSelectIndex('x', 'left', 0.5), - i2 = this.getSelectIndex('x', 'right', 0), - j1 = this.getSelectIndex('y', 'left', 0.5), - j2 = this.getSelectIndex('y', 'right', 0), - k1 = this.getSelectIndex('z', 'left', 0.5), - k2 = this.getSelectIndex('z', 'right', 0); + return Math.pow(Math.abs(content * use_scale), 0.3333); + }; + // eslint-disable-next-line one-var + const i1 = this.getSelectIndex('x', 'left', 0.5), + i2 = this.getSelectIndex('x', 'right', 0), + j1 = this.getSelectIndex('y', 'left', 0.5), + j2 = this.getSelectIndex('y', 'right', 0), + k1 = this.getSelectIndex('z', 'left', 0.5), + k2 = this.getSelectIndex('z', 'right', 0); if ((i2 <= i1) || (j2 <= j1) || (k2 <= k1)) return false; - const cols_size = {}, cols_sequence = {}, - cntr = use_colors ? this.getContour() : null, - palette = use_colors ? this.getHistPalette() : null; - let nbins = 0, i, j, k, wei, bin_content, num_colors = 0, transfer = null; - - if (this.transferFunc && proivdeEvalPar(this.transferFunc, true)) - transfer = this.transferFunc; - const getOpacityIndex = colindx => { - const bin_opactity = getTF1Value(transfer, bin_content, false) * 3; // try to get opacity - if (!bin_opactity || (bin_opactity < 0) || (bin_opactity >= 1)) - return colindx; - return colindx + Math.round(bin_opactity * 200) * 10000; // 200 steps between 0..1 - }; - - for (i = i1; i < i2; ++i) { - for (j = j1; j < j2; ++j) { - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) continue; - - wei = get_bin_weight(bin_content); - if (wei < 1e-3) continue; // do not draw empty or very small bins - - nbins++; + const cntr = use_colors ? this.getContour() : null, + palette = use_colors ? this.getHistPalette() : null, + bins_matrixes = [], bins_colors = [], bins_ids = [], negative_matrixes = [], bin_opacities = [], + transfer = (this.transferFunc && proivdeEvalPar(this.transferFunc, true)) ? this.transferFunc : null; + + for (let i = i1; i < i2; ++i) { + const grx1 = fp.grx(histo.fXaxis.GetBinLowEdge(i + 1)), + grx2 = fp.grx(histo.fXaxis.GetBinLowEdge(i + 2)); + for (let j = j1; j < j2; ++j) { + const gry1 = fp.gry(histo.fYaxis.GetBinLowEdge(j + 1)), + gry2 = fp.gry(histo.fYaxis.GetBinLowEdge(j + 2)); + for (let k = k1; k < k2; ++k) { + const bin_content = histo.getBinContent(i + 1, j + 1, k + 1); + if (!o.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) + continue; + + const wei = get_bin_weight(bin_content); + if (wei < 1e-3) + continue; // do not show very small bins - if (!use_colors) continue; - - let colindx = cntr.getPaletteIndex(palette, bin_content); - if (colindx !== null) { - if (transfer) - colindx = getOpacityIndex(colindx); - - if (cols_size[colindx] === undefined) { - cols_size[colindx] = 0; - cols_sequence[colindx] = num_colors++; - } - cols_size[colindx] += 1; - } else - console.error(`not found color for value = ${bin_content}`); - } - } - } - - if (!use_colors) { - cols_size[0] = nbins; - num_colors = 1; - cols_sequence[0] = 0; - } - - const cols_nbins = new Array(num_colors), - bin_verts = new Array(num_colors), - bin_norms = new Array(num_colors), - bin_tooltips = new Array(num_colors), - helper_kind = new Array(num_colors), - helper_indexes = new Array(num_colors), // helper_kind === 1, use original vertices - helper_positions = new Array(num_colors); // helper_kind === 2, all vertices copied into separate buffer - - for (const colindx in cols_size) { - nbins = cols_size[colindx]; // how many bins with specified color - const nseq = cols_sequence[colindx]; - - cols_nbins[nseq] = helper_kind[nseq] = 0; // counter for the filled bins - - // 1 - use same vertices to create helper, one can use maximal 64K vertices - // 2 - all vertices copied into separate buffer - if (use_helper) - helper_kind[nseq] = (nbins * buffer_size / 3 > 0xFFF0) ? 2 : 1; - - bin_verts[nseq] = new Float32Array(nbins * buffer_size); - bin_norms[nseq] = new Float32Array(nbins * buffer_size); - bin_tooltips[nseq] = new Int32Array(nbins); - - if (helper_kind[nseq] === 1) - helper_indexes[nseq] = new Uint16Array(nbins * Box3D.MeshSegments.length); - - if (helper_kind[nseq] === 2) - helper_positions[nseq] = new Float32Array(nbins * Box3D.Segments.length * 3); - } - - let grx1, grx2, gry1, gry2, grz1, grz2; - - for (i = i1; i < i2; ++i) { - grx1 = main.grx(histo.fXaxis.GetBinLowEdge(i+1)); - grx2 = main.grx(histo.fXaxis.GetBinLowEdge(i+2)); - for (j = j1; j < j2; ++j) { - gry1 = main.gry(histo.fYaxis.GetBinLowEdge(j+1)); - gry2 = main.gry(histo.fYaxis.GetBinLowEdge(j+2)); - for (k = k1; k < k2; ++k) { - bin_content = histo.getBinContent(i+1, j+1, k+1); - if (!this.options.GLColor && ((bin_content === 0) || (bin_content < this.gminbin))) continue; - - wei = get_bin_weight(bin_content); - if (wei < 1e-3) continue; // do not show very small bins - - let nseq = 0; if (use_colors) { - let colindx = cntr.getPaletteIndex(palette, bin_content); - if (colindx === null) continue; - if (transfer) - colindx = getOpacityIndex(colindx); - nseq = cols_sequence[colindx]; + const colindx = cntr.getPaletteIndex(palette, bin_content); + if (colindx === null) + continue; + bins_colors.push(palette.getColor(colindx)); + if (transfer) { + const op = getTF1Value(transfer, bin_content, false) * 3; + bin_opacities.push((!op || op < 0) ? 0 : (op > 1 ? 1 : op)); + } } - nbins = cols_nbins[nseq]; - - grz1 = main.grz(histo.fZaxis.GetBinLowEdge(k+1)); - grz2 = main.grz(histo.fZaxis.GetBinLowEdge(k+2)); + const grz1 = fp.grz(histo.fZaxis.GetBinLowEdge(k + 1)), + grz2 = fp.grz(histo.fZaxis.GetBinLowEdge(k + 2)); // remember bin index for tooltip - bin_tooltips[nseq][nbins] = histo.getBin(i+1, j+1, k+1); - - const bin_v = bin_verts[nseq], bin_n = bin_norms[nseq]; - let vvv = nbins * buffer_size; - - // Grab the coordinates and scale that are being assigned to each bin - for (let vi = 0; vi < buffer_size; vi+=3, vvv+=3) { - bin_v[vvv] = (grx2 + grx1) / 2 + single_bin_verts[vi] * (grx2 - grx1) * wei; - bin_v[vvv+1] = (gry2 + gry1) / 2 + single_bin_verts[vi+1] * (gry2 - gry1) * wei; - bin_v[vvv+2] = (grz2 + grz1) / 2 + single_bin_verts[vi+2] * (grz2 - grz1) * wei; - - bin_n[vvv] = single_bin_norms[vi]; - bin_n[vvv+1] = single_bin_norms[vi+1]; - bin_n[vvv+2] = single_bin_norms[vi+2]; - } - - if (helper_kind[nseq] === 1) { - // reuse vertices created for the mesh - const helper_segments = Box3D.MeshSegments; - vvv = nbins * helper_segments.length; - const shift = Math.round(nbins * buffer_size / 3), - helper_i = helper_indexes[nseq]; - for (let n = 0; n < helper_segments.length; ++n) - helper_i[vvv + n] = shift + helper_segments[n]; - } - - if (helper_kind[nseq] === 2) { - const helper_segments = Box3D.Segments, - helper_p = helper_positions[nseq]; - vvv = nbins * helper_segments.length * 3; - for (let n = 0; n < helper_segments.length; ++n, vvv += 3) { - const vert = Box3D.Vertices[helper_segments[n]]; - helper_p[vvv] = (grx2 + grx1) / 2 + (vert.x - 0.5) * (grx2 - grx1) * wei; - helper_p[vvv+1] = (gry2 + gry1) / 2 + (vert.y - 0.5) * (gry2 - gry1) * wei; - helper_p[vvv+2] = (grz2 + grz1) / 2 + (vert.z - 0.5) * (grz2 - grz1) * wei; - } - } - - cols_nbins[nseq] = nbins+1; + bins_ids.push(histo.getBin(i + 1, j + 1, k + 1)); + + const bin_matrix = new THREE.Matrix4(); + bin_matrix.scale(new THREE.Vector3((grx2 - grx1) * wei, (gry2 - gry1) * wei, (grz2 - grz1) * wei)); + bin_matrix.setPosition((grx2 + grx1) / 2, (gry2 + gry1) / 2, (grz2 + grz1) / 2); + bins_matrixes.push(bin_matrix); + if (bin_content < 0) + negative_matrixes.push(bin_matrix); } } } - for (const colindx in cols_size) { - const nseq = cols_sequence[colindx], - all_bins_buffgeom = new BufferGeometry(); // BufferGeometries that store geometry of all bins + function _getBinTooltip(intersect) { + let binid = this.binid; - // Create mesh from bin buffergeometry - all_bins_buffgeom.setAttribute('position', new BufferAttribute(bin_verts[nseq], 3)); - all_bins_buffgeom.setAttribute('normal', new BufferAttribute(bin_norms[nseq], 3)); + if (binid === undefined) { + if ((intersect.instanceId === undefined) || (intersect.instanceId >= this.bins.length)) + return; + binid = this.bins[intersect.instanceId]; + } - let opacity = use_opacity; + const p = this.tip_painter, + thisto = p.getHisto(), + tip = p.get3DToolTip(binid), + grx1 = fp.grx(thisto.fXaxis.GetBinCoord(tip.ix - 1)), + grx2 = fp.grx(thisto.fXaxis.GetBinCoord(tip.ix)), + gry1 = fp.gry(thisto.fYaxis.GetBinCoord(tip.iy - 1)), + gry2 = fp.gry(thisto.fYaxis.GetBinCoord(tip.iy)), + grz1 = fp.grz(thisto.fZaxis.GetBinCoord(tip.iz - 1)), + grz2 = fp.grz(thisto.fZaxis.GetBinCoord(tip.iz)), + wei2 = this.get_weight(tip.value) * this.tipscale; + + tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; + tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; + tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; + tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; + tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; + tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; + tip.color = this.tip_color; + + return tip; + } - if (use_colors) { - fillcolor = this._color_palette.getColor(colindx % 10000); - if (colindx > 10000) opacity = Math.floor(colindx / 10000) / 200; + if (use_colors && (transfer || (use_opacity !== 1))) { + // create individual meshes for each bin + for (let n = 0; n < bins_matrixes.length; ++n) { + const opacity = transfer ? bin_opacities[n] : use_opacity, + color = new THREE.Color(bins_colors[n]), + material = use_lambert ? new THREE.MeshLambertMaterial({ color, opacity, transparent: opacity < 1, vertexColors: false }) + : new THREE.MeshBasicMaterial({ color, opacity, transparent: opacity < 1, vertexColors: false }), + bin_mesh = new THREE.Mesh(single_bin_geom, material); + + bin_mesh.applyMatrix4(bins_matrixes[n]); + + bin_mesh.tip_painter = this; + bin_mesh.binid = bins_ids[n]; + bin_mesh.tipscale = tipscale; + bin_mesh.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; + bin_mesh.get_weight = get_bin_weight; + bin_mesh.tooltip = _getBinTooltip; + + fp.add3DMesh(bin_mesh); } + } else { + if (use_colors) + fillcolor = new THREE.Color(1, 1, 1); - const material = use_lambert - ? new MeshLambertMaterial({ color: fillcolor, opacity, transparent: opacity < 1, vertexColors: false }) - : new MeshBasicMaterial({ color: fillcolor, opacity, transparent: opacity < 1, vertexColors: false }), - combined_bins = new Mesh(all_bins_buffgeom, material); - - combined_bins.bins = bin_tooltips[nseq]; - combined_bins.bins_faces = buffer_size/9; - combined_bins.painter = this; - combined_bins.tipscale = tipscale; - combined_bins.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; - combined_bins.get_weight = get_bin_weight; - - combined_bins.tooltip = function(intersect) { - const indx = Math.floor(intersect.faceIndex / this.bins_faces); - if ((indx < 0) || (indx >= this.bins.length)) return null; - - const p = this.painter, - histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.bins[indx]), - grx1 = main.grx(histo.fXaxis.GetBinCoord(tip.ix-1)), - grx2 = main.grx(histo.fXaxis.GetBinCoord(tip.ix)), - gry1 = main.gry(histo.fYaxis.GetBinCoord(tip.iy-1)), - gry2 = main.gry(histo.fYaxis.GetBinCoord(tip.iy)), - grz1 = main.grz(histo.fZaxis.GetBinCoord(tip.iz-1)), - grz2 = main.grz(histo.fZaxis.GetBinCoord(tip.iz)), - wei2 = this.get_weight(tip.value) * this.tipscale; - - tip.x1 = (grx2 + grx1) / 2 - (grx2 - grx1) * wei2; - tip.x2 = (grx2 + grx1) / 2 + (grx2 - grx1) * wei2; - tip.y1 = (gry2 + gry1) / 2 - (gry2 - gry1) * wei2; - tip.y2 = (gry2 + gry1) / 2 + (gry2 - gry1) * wei2; - tip.z1 = (grz2 + grz1) / 2 - (grz2 - grz1) * wei2; - tip.z2 = (grz2 + grz1) / 2 + (grz2 - grz1) * wei2; - tip.color = this.tip_color; + const material = use_lambert ? new THREE.MeshLambertMaterial({ color: fillcolor, vertexColors: false }) + : new THREE.MeshBasicMaterial({ color: fillcolor, vertexColors: false }), + all_bins_mesh = new THREE.InstancedMesh(single_bin_geom, material, bins_matrixes.length); - return tip; - }; + for (let n = 0; n < bins_matrixes.length; ++n) { + all_bins_mesh.setMatrixAt(n, bins_matrixes[n]); + if (use_colors) + all_bins_mesh.setColorAt(n, new THREE.Color(bins_colors[n])); + } - main.add3DMesh(combined_bins); + all_bins_mesh.tip_painter = this; + all_bins_mesh.bins = bins_ids; + all_bins_mesh.tipscale = tipscale; + all_bins_mesh.tip_color = (histo.fFillColor === 3) ? 0xFF0000 : 0x00FF00; + all_bins_mesh.get_weight = get_bin_weight; + all_bins_mesh.tooltip = _getBinTooltip; - if (helper_kind[nseq] > 0) { - const helper_material = new LineBasicMaterial({ color: this.getColor(histo.fLineColor) }), - lines = (helper_kind[nseq] === 1) - // reuse positions from the mesh - only special index was created - ? createLineSegments(bin_verts[nseq], helper_material, helper_indexes[nseq]) - : createLineSegments(helper_positions[nseq], helper_material); + fp.add3DMesh(all_bins_mesh); + } - main.add3DMesh(lines); + if (use_helper) { + const helper_material = new THREE.LineBasicMaterial({ color: this.getColor(histo.fLineColor) }); + function addLines(segments, matrixes) { + if (!matrixes) + return; + const positions = new Float32Array(matrixes.length * segments.length * 3); + for (let i = 0, vvv = 0; i < matrixes.length; ++i) { + const m = matrixes[i].elements; + for (let n = 0; n < segments.length; ++n, vvv += 3) { + const vert = Box3D.Vertices[segments[n]]; + positions[vvv] = m[12] + (vert.x - 0.5) * m[0]; + positions[vvv + 1] = m[13] + (vert.y - 0.5) * m[5]; + positions[vvv + 2] = m[14] + (vert.z - 0.5) * m[10]; + } + } + fp.add3DMesh(createLineSegments(positions, helper_material)); } + + addLines(Box3D.Segments, bins_matrixes); + addLines(Box3D.Crosses, negative_matrixes); } return true; } + /** @summary Redraw TH3 histogram */ async redraw(reason) { - const main = this.getFramePainter(), // who makes axis and 3D drawing - histo = this.getHisto(); - let pr = Promise.resolve(true); + const fp = this.getFramePainter(), // who makes axis and 3D drawing + o = this.getOptions(); + + let pr = Promise.resolve(true), full_draw = true; if (reason === 'resize') { - if (main.resize3D()) main.render3D(); - } else { - assignFrame3DMethods(main); - pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => { - main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax, this); - main.set3DOptions(this.options); - main.drawXYZ(main.toplevel, TAxisPainter, { zoom: settings.Zooming, ndim: 3, - draw: this.options.Axis !== -1, drawany: this.options.isCartesian() }); - return this.draw3DBins(); - }).then(() => { - main.render3D(); - this.updateStatWebCanvas(); - main.addKeysHandler(); - }); + const res = fp.resize3D(); + if (res !== 1) { + full_draw = false; + if (res) + fp.render3D(); + } + } + + if (full_draw) { + pr = crete3DFrame(this, TAxisPainter, o.Render3D) + .then(() => this.draw3DBins()) + .then(() => { + fp.render3D(); + this.updateStatWebCanvas(); + fp.addKeysHandler(); + }); } if (this.isMainPainter()) - pr = pr.then(() => this.drawColorPalette(this.options.Zscale && (this._box_option === 12 || this._box_option === 13))); + pr = pr.then(() => this.drawColorPalette(o.Zscale && (this.#box_option === 12 || this.#box_option === 13 || o.GLBox === 12))); return pr.then(() => this.updateFunctions()) .then(() => this.updateHistTitle()) @@ -746,18 +677,21 @@ class TH3Painter extends THistPainter { /** @summary Fill pad toolbar with TH3-related functions */ fillToolbar() { const pp = this.getPadPainter(); - if (!pp) return; + if (!pp) + return; pp.addPadButton('auto_zoom', 'Unzoom all axes', 'ToggleZoom', 'Ctrl *'); if (this.draw_content) pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); + pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); pp.showPadButtons(); } /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis, min, max) { let obj = this.getHisto(); - if (obj) obj = obj[`f${axis.toUpperCase()}axis`]; + if (obj) + obj = obj[`f${axis.toUpperCase()}axis`]; return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); } @@ -772,30 +706,38 @@ class TH3Painter extends THistPainter { histo = this.getObject(); let i, j, k; - if ((i1 === i2) || (j1 === j2) || (k1 === k2)) return; + if ((i1 === i2) || (j1 === j2) || (k1 === k2)) + return; // first find minimum - let min = histo.getBinContent(i1+1, j1+1, k1+1); + let min = histo.getBinContent(i1 + 1, j1 + 1, k1 + 1); for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { for (k = k1; k < k2; ++k) - min = Math.min(min, histo.getBinContent(i+1, j+1, k+1)); + min = Math.min(min, histo.getBinContent(i + 1, j + 1, k + 1)); } } - if (min > 0) return; // if all points positive, no chance for autoscale + if (min > 0) + return; // if all points positive, no chance for auto-scale let ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1; for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { for (k = k1; k < k2; ++k) { - if (histo.getBinContent(i+1, j+1, k+1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - if (k < kleft) kleft = k; - if (k >= kright) kright = k + 1; + if (histo.getBinContent(i + 1, j + 1, k + 1) > min) { + if (i < ileft) + ileft = i; + if (i >= iright) + iright = i + 1; + if (j < jleft) + jleft = j; + if (j >= jright) + jright = j + 1; + if (k < kleft) + kleft = k; + if (k >= kright) + kright = k + 1; } } } @@ -803,25 +745,34 @@ class TH3Painter extends THistPainter { let xmin, xmax, ymin, ymax, zmin, zmax, isany = false; - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } - if ((kleft === kright-1) && (kleft > k1+1) && (kright < k2-1)) { kleft--; kright++; } + if ((ileft === iright - 1) && (ileft > i1 + 1) && (iright < i2 - 1)) { + ileft--; + iright++; + } + if ((jleft === jright - 1) && (jleft > j1 + 1) && (jright < j2 - 1)) { + jleft--; + jright++; + } + if ((kleft === kright - 1) && (kleft > k1 + 1) && (kright < k2 - 1)) { + kleft--; + kright++; + } if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = histo.fXaxis.GetBinLowEdge(ileft+1); - xmax = histo.fXaxis.GetBinLowEdge(iright+1); + xmin = histo.fXaxis.GetBinLowEdge(ileft + 1); + xmax = histo.fXaxis.GetBinLowEdge(iright + 1); isany = true; } if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = histo.fYaxis.GetBinLowEdge(jleft+1); - ymax = histo.fYaxis.GetBinLowEdge(jright+1); + ymin = histo.fYaxis.GetBinLowEdge(jleft + 1); + ymax = histo.fYaxis.GetBinLowEdge(jright + 1); isany = true; } if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) { - zmin = histo.fZaxis.GetBinLowEdge(kleft+1); - zmax = histo.fZaxis.GetBinLowEdge(kright+1); + zmin = histo.fZaxis.GetBinLowEdge(kleft + 1); + zmax = histo.fZaxis.GetBinLowEdge(kright + 1); isany = true; } @@ -843,6 +794,21 @@ class TH3Painter extends THistPainter { }); } + /** @summary Build three.js object for the histogram */ + static async build3d(histo, opt) { + const painter = new TH3Painter(null, histo); + painter.mode3d = true; + painter.decodeOptions(opt); + painter.scanContent(); + + const fp = new TFramePainter(null, null); + painter.getFramePainter = () => fp; + + return crete3DFrame(painter, TAxisPainter) + .then(() => painter.draw3DBins()) + .then(() => fp.create3DScene(-1, true)); + } + /** @summary draw TH3 object */ static async draw(dom, histo, opt) { const painter = new TH3Painter(dom, histo); diff --git a/modules/hist/THStackPainter.mjs b/modules/hist/THStackPainter.mjs index c4e4bdf24..ffd2074e5 100644 --- a/modules/hist/THStackPainter.mjs +++ b/modules/hist/THStackPainter.mjs @@ -1,424 +1,14 @@ -import { clone, create, createHistogram, setHistogramTitle, gStyle, clTList, clTH1I, clTH2, clTH2I, kNoZoom, kNoStats } from '../core.mjs'; -import { DrawOptions } from '../base/BasePainter.mjs'; -import { ObjectPainter, EAxisBits } from '../base/ObjectPainter.mjs'; +import { THStackPainter as THStackPainter2D } from '../hist2d/THStackPainter.mjs'; import { TH1Painter } from './TH1Painter.mjs'; import { TH2Painter } from './TH2Painter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -/** - * @summary Painter class for THStack - * - * @private - */ +class THStackPainter extends THStackPainter2D { -class THStackPainter extends ObjectPainter { - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} stack - THStack object - * @param {string} [opt] - draw options */ - constructor(dom, stack, opt) { - super(dom, stack, opt); - this.firstpainter = null; - this.painters = []; // keep painters to be able update objects - } - - /** @summary Cleanup THStack painter */ - cleanup() { - this.getPadPainter()?.cleanPrimitives(objp => { return (objp === this.firstpainter) || (this.painters.indexOf(objp) >= 0); }); - delete this.firstpainter; - delete this.painters; - super.cleanup(); - } - - /** @summary Build sum of all histograms - * @desc Build a separate list fStack containing the running sum of all histograms */ - buildStack(stack) { - if (!stack.fHists) return false; - const nhists = stack.fHists.arr.length; - if (nhists <= 0) return false; - const lst = create(clTList); - lst.Add(clone(stack.fHists.arr[0]), stack.fHists.opt[0]); - for (let i = 1; i < nhists; ++i) { - const hnext = clone(stack.fHists.arr[i]), - hnextopt = stack.fHists.opt[i], - hprev = lst.arr[i-1], - xnext = hnext.fXaxis, xprev = hprev.fXaxis; - - let match = (xnext.fNbins === xprev.fNbins) && - (xnext.fXmin === xprev.fXmin) && - (xnext.fXmax === xprev.fXmax); - - if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && - (Math.abs((xnext.fXmax - xnext.fXmin)/xnext.fNbins - (xprev.fXmax - xprev.fXmin)/xprev.fNbins) < 0.0001)) { - // simple extension of histogram to make sum - const arr = new Array(hprev.fNcells).fill(0); - for (let n = 1; n <= xnext.fNbins; ++n) - arr[n] = hnext.fArray[n]; - hnext.fNcells = hprev.fNcells; - Object.assign(xnext, xprev); - hnext.fArray = arr; - match = true; - } - if (!match) { - console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); - lst.Clear(); - return false; - } - - // trivial sum of histograms - for (let n = 0; n < hnext.fArray.length; ++n) - hnext.fArray[n] += hprev.fArray[n]; - - lst.Add(hnext, hnextopt); - } - stack.fStack = lst; - return true; - } - - /** @summary Returns stack min/max values */ - getMinMax(iserr) { - const stack = this.getObject(), - pad = this.getPadPainter().getRootPad(true); - let min = 0, max = 0; - - const getHistMinMax = (hist, witherr) => { - const res = { min: 0, max: 0 }; - let domin = true, domax = true; - if (hist.fMinimum !== kNoZoom) { - res.min = hist.fMinimum; - domin = false; - } - if (hist.fMaximum !== kNoZoom) { - res.max = hist.fMaximum; - domax = false; - } - - if (!domin && !domax) return res; - - let i1 = 1, i2 = hist.fXaxis.fNbins, j1 = 1, j2 = 1, first = true; - - if (hist.fXaxis.TestBit(EAxisBits.kAxisRange)) { - i1 = hist.fXaxis.fFirst; - i2 = hist.fXaxis.fLast; - } - - if (hist._typename.indexOf(clTH2) === 0) { - j2 = hist.fYaxis.fNbins; - if (hist.fYaxis.TestBit(EAxisBits.kAxisRange)) { - j1 = hist.fYaxis.fFirst; - j2 = hist.fYaxis.fLast; - } - } - for (let j = j1; j <= j2; ++j) { - for (let i = i1; i <= i2; ++i) { - const val = hist.getBinContent(i, j), - err = witherr ? hist.getBinError(hist.getBin(i, j)) : 0; - if (domin && (first || (val-err < res.min))) res.min = val-err; - if (domax && (first || (val+err > res.max))) res.max = val+err; - first = false; - } - } - - return res; - }; - - if (this.options.nostack) { - for (let i = 0; i < stack.fHists.arr.length; ++i) { - const resh = getHistMinMax(stack.fHists.arr[i], iserr); - if (i === 0) { - min = resh.min; max = resh.max; - } else { - min = Math.min(min, resh.min); - max = Math.max(max, resh.max); - } - } - } else { - min = getHistMinMax(stack.fStack.arr[0], iserr).min; - max = getHistMinMax(stack.fStack.arr[stack.fStack.arr.length-1], iserr).max; - } - - const adjustRange = () => { - if (pad && (pad.fLogv ?? (this.options.ndim === 1 ? pad.fLogy : pad.fLogz))) { - if (max <= 0) max = 1; - if (min <= 0) min = 1e-4*max; - const kmin = 1/(1 + 0.5*Math.log10(max / min)), - kmax = 1 + 0.2*Math.log10(max / min); - min *= kmin; - max *= kmax; - } else if ((min > 0) && (min < 0.05*max)) - min = 0; - }; - - max *= (1 + gStyle.fHistTopMargin); - - adjustRange(); - - let max0 = max, min0 = min, zoomed = false; - - if (stack.fMaximum !== kNoZoom) { - max = stack.fMaximum; - max0 = Math.max(max, max0); - zoomed = true; - } - - if (stack.fMinimum !== kNoZoom) { - min = stack.fMinimum; - min0 = Math.min(min, min0); - zoomed = true; - } - - if (zoomed) - adjustRange(); - else - min = max = kNoZoom; - - return { min, max, min0, max0, zoomed, hopt: `hmin:${min0};hmax:${max0};minimum:${min};maximum:${max}` }; - } - - /** @summary Provide draw options for the histogram */ - getHistDrawOption(hist, opt) { - let hopt = opt || hist.fOption || this.options.hopt; - if (hopt.toUpperCase().indexOf(this.options.hopt) < 0) - hopt += ' ' + this.options.hopt; - if (this.options.draw_errors && !hopt) - hopt = 'E'; - if (!this.options.pads) - hopt += ' same nostat' + this.options.auto; - return hopt; - } - - /** @summary Draw next stack histogram */ - async drawNextHisto(indx, pad_painter) { - const stack = this.getObject(), - hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length || 0; - - if (indx >= nhists) - return this; - - const rindx = this.options.horder ? indx : nhists-indx-1, - subid = this.options.nostack ? `hists_${rindx}` : `stack_${rindx}`, - hist = hlst.arr[rindx], - hopt = this.getHistDrawOption(hist, hlst.opt[rindx]); - - // handling of 'pads' draw option - if (pad_painter) { - const subpad_painter = pad_painter.getSubPadPainter(indx+1); - if (!subpad_painter) - return this; - - const prev_name = subpad_painter.selectCurrentPad(subpad_painter.this_pad_name); - - return this.hdraw_func(subpad_painter.getDom(), hist, hopt).then(subp => { - if (subp) { - subp.setSecondaryId(this, subid); - this.painters.push(subp); - } - subpad_painter.selectCurrentPad(prev_name); - return this.drawNextHisto(indx+1, pad_painter); - }); - } - - // special handling of stacked histograms - set $baseh object for correct drawing - // also used to provide tooltips - if ((rindx > 0) && !this.options.nostack) - hist.$baseh = hlst.arr[rindx - 1]; - // this number used for auto colors creation - if (this.options.auto) - hist.$num_histos = nhists; - - return this.hdraw_func(this.getDom(), hist, hopt).then(subp => { - subp.setSecondaryId(this, subid); - this.painters.push(subp); - return this.drawNextHisto(indx+1, pad_painter); - }); - } - - /** @summary Decode draw options of THStack painter */ - decodeOptions(opt) { - if (!this.options) this.options = {}; - Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }); - - const stack = this.getObject(), - hist = stack.fHistogram || (stack.fHists ? stack.fHists.arr[0] : null) || (stack.fStack ? stack.fStack.arr[0] : null), - - hasErrors = hist => { - if (hist.fSumw2 && (hist.fSumw2.length > 0)) { - for (let n = 0; n < hist.fSumw2.length; ++n) - if (hist.fSumw2[n] > 0) return true; - } - return false; - }; - - if (hist && (hist._typename.indexOf(clTH2) === 0)) - this.options.ndim = 2; - - if ((this.options.ndim === 2) && !opt) - opt = 'lego1'; - - if (stack.fHists && !this.options.nostack) { - for (let k = 0; k < stack.fHists.arr.length; ++k) - this.options.has_errors = this.options.has_errors || hasErrors(stack.fHists.arr[k]); - } - - this.options.nhist = stack.fHists?.arr?.length ?? 1; - - const d = new DrawOptions(opt); - - this.options.nostack = d.check('NOSTACK'); - if (d.check('STACK')) this.options.nostack = false; - this.options.same = d.check('SAME'); - - d.check('NOCLEAR'); // ignore noclear option - - ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) this.options.auto += ' ' + f; }); - - this.options.pads = d.check('PADS'); - if (this.options.pads) this.options.nostack = true; - - this.options.hopt = d.remain(); // use remaining draw options for histogram draw - - const dolego = d.check('LEGO'); - - this.options.errors = d.check('E'); - - // if any histogram appears with pre-calculated errors, use E for all histograms - if (!this.options.nostack && this.options.has_errors && !dolego && !d.check('HIST') && (this.options.hopt.indexOf('E') < 0)) - this.options.draw_errors = true; - - this.options.horder = this.options.nostack || dolego; - } - - /** @summary Create main histogram for THStack axis drawing */ - createHistogram(stack) { - const histos = stack.fHists, - numhistos = histos ? histos.arr.length : 0; - - if (!numhistos) { - const histo = createHistogram(clTH1I, 100); - setHistogramTitle(histo, stack.fTitle); - histo.fBits |= kNoStats; - return histo; - } - - const h0 = histos.arr[0], - histo = createHistogram((this.options.ndim === 1) ? clTH1I : clTH2I, h0.fXaxis.fNbins, h0.fYaxis.fNbins); - histo.fName = 'axis_hist'; - histo.fBits |= kNoStats; - Object.assign(histo.fXaxis, h0.fXaxis); - if (this.options.ndim === 2) - Object.assign(histo.fYaxis, h0.fYaxis); - - // this code is not exists in ROOT painter, can be skipped? - for (let n = 1; n < numhistos; ++n) { - const h = histos.arr[n]; - - if (!histo.fXaxis.fLabels) { - histo.fXaxis.fXmin = Math.min(histo.fXaxis.fXmin, h.fXaxis.fXmin); - histo.fXaxis.fXmax = Math.max(histo.fXaxis.fXmax, h.fXaxis.fXmax); - } - - if ((this.options.ndim === 2) && !histo.fYaxis.fLabels) { - histo.fYaxis.fXmin = Math.min(histo.fYaxis.fXmin, h.fYaxis.fXmin); - histo.fYaxis.fXmax = Math.max(histo.fYaxis.fXmax, h.fYaxis.fXmax); - } - } - - histo.fTitle = stack.fTitle; - - return histo; - } - - /** @summary Update thstack object */ - updateObject(obj) { - if (!this.matchObjectType(obj)) return false; - - const stack = this.getObject(); - - stack.fHists = obj.fHists; - stack.fStack = obj.fStack; - stack.fTitle = obj.fTitle; - stack.fMinimum = obj.fMinimum; - stack.fMaximum = obj.fMaximum; - - if (!this.options.nostack) - this.options.nostack = !this.buildStack(stack); - - if (this.firstpainter) { - let src = obj.fHistogram; - if (!src) - src = stack.fHistogram = this.createHistogram(stack); - - const mm = this.getMinMax(this.options.errors || this.options.draw_errors); - - this.firstpainter.options.minimum = mm.min; - this.firstpainter.options.maximum = mm.max; - this.firstpainter._checked_zooming = false; // force to check 3d zooming - - if (this.options.ndim === 1) { - this.firstpainter.ymin = mm.min0; - this.firstpainter.ymax = mm.max0; - } else { - this.firstpainter.zmin = mm.min0; - this.firstpainter.zmax = mm.max0; - } - - this.firstpainter.updateObject(src); - } - - // and now update histograms - const hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length ?? 0; - - if (nhists !== this.painters.length) { - this.did_update = 1; - this.getPadPainter()?.cleanPrimitives(objp => this.painters.indexOf(objp) >= 0); - this.painters = []; - } else { - this.did_update = 2; - for (let indx = 0; indx < nhists; ++indx) { - const rindx = this.options.horder ? indx : nhists - indx - 1, - hist = hlst.arr[rindx]; - this.painters[indx].updateObject(hist, this.getHistDrawOption(hist, hlst.opt[rindx])); - } - } - - return true; - } - - /** @summary Redraw THStack - * @desc Do something if previous update had changed number of histograms */ - redraw(reason) { - if (this.did_update === 1) { - delete this.did_update; - return this.drawNextHisto(0, this.options.pads ? this.getPadPainter() : null); - } else if (this.did_update === 2) { - delete this.did_update; - const redrawSub = indx => { - if (indx >= this.painters.length) - return Promise.resolve(this); - return this.painters[indx].redraw(reason).then(() => redrawSub(indx+1)); - }; - return redrawSub(0); - } - } - - /** @summary Fill hstack context menu */ - fillContextMenuItems(menu) { - menu.addchk(this.options.draw_errors, 'Draw errors', flag => { - this.options.draw_errors = flag; - const stack = this.getObject(), - hlst = this.options.nostack ? stack.fHists : stack.fStack, - nhists = hlst?.arr?.length ?? 0; - for (let indx = 0; indx < nhists; ++indx) { - const rindx = this.options.horder ? indx : nhists - indx - 1, - hist = hlst.arr[rindx]; - this.painters[indx].decodeOptions(this.getHistDrawOption(hist, hlst.opt[rindx])); - } - this.redrawPad(); - }, 'Change draw erros in the stack'); + /** @summary Invoke histogram drawing */ + drawHist(dom, hist, hopt) { + const func = (this.getOptions().ndim === 1) ? TH1Painter.draw : TH2Painter.draw; + return func(dom, hist, hopt); } /** @summary draw THStack object */ @@ -427,45 +17,8 @@ class THStackPainter extends ObjectPainter { return null; // drawing not needed const painter = new THStackPainter(dom, stack, opt); - let pad_painter = null, skip_drawing = false; - - return ensureTCanvas(painter, false).then(() => { - painter.decodeOptions(opt); - - painter.hdraw_func = (painter.options.ndim === 1) ? TH1Painter.draw : TH2Painter.draw; - - if (painter.options.pads) { - pad_painter = painter.getPadPainter(); - if (pad_painter.doingDraw() && pad_painter.pad?.fPrimitives && - (pad_painter.pad.fPrimitives.arr.length > 1) && (pad_painter.pad.fPrimitives.arr.indexOf(stack) === 0)) { - skip_drawing = true; - console.log('special case with THStack with is already rendered - do nothing'); - return; - } - - pad_painter.cleanPrimitives(p => p !== painter); - return pad_painter.divide(painter.options.nhist); - } - - if (!painter.options.nostack) - painter.options.nostack = !painter.buildStack(stack); - - if (painter.options.same) return; - - const no_histogram = !stack.fHistogram; - - if (no_histogram) - stack.fHistogram = painter.createHistogram(stack); - - const mm = painter.getMinMax(painter.options.errors || painter.options.draw_errors), - hopt = painter.options.hopt + ';' + mm.hopt; - return painter.hdraw_func(dom, stack.fHistogram, hopt).then(subp => { - painter.addToPadPrimitives(); - painter.firstpainter = subp; - subp.setSecondaryId(painter, 'hist'); // mark hist painter as created by hstack - }); - }).then(() => skip_drawing ? painter : painter.drawNextHisto(0, pad_painter)); + return painter.redrawWith(opt, true); } } // class THStackPainter diff --git a/modules/hist/TMultiGraphPainter.mjs b/modules/hist/TMultiGraphPainter.mjs index daaa31c15..99e954a30 100644 --- a/modules/hist/TMultiGraphPainter.mjs +++ b/modules/hist/TMultiGraphPainter.mjs @@ -6,23 +6,25 @@ import { TGraphPainter } from './TGraphPainter.mjs'; class TMultiGraphPainter extends TMultiGraphPainter2D { - /** @summary draw speical histogram for axis + /** @summary draw special histogram for axis * @return {Promise} when ready */ async drawAxisHist(histo, hopt) { - return this._3d - ? TH2Painter.draw(this.getDom(), histo, 'LEGO' + hopt) - : TH1Painter.draw(this.getDom(), histo, hopt); + const dom = this.getDrawDom(); + return this.is3d() ? TH2Painter.draw(dom, histo, 'LEGO' + hopt) + : TH1Painter.draw(dom, histo, hopt); } - /** @summary draw multigraph in 3D */ - async drawGraph(gr, opt, pos3d) { - if (this._3d) opt += 'pos3d_'+pos3d; - return TGraphPainter.draw(this.getDom(), gr, opt); + /** @summary draw multi graph in 3D */ + async drawGraph(dom, gr, opt, pos3d) { + if (this.is3d()) + opt += `pos3d_${pos3d}`; + return TGraphPainter.draw(dom, gr, opt); } /** @summary Draw TMultiGraph object */ static async draw(dom, mgraph, opt) { - return TMultiGraphPainter._drawMG(new TMultiGraphPainter(dom, mgraph), opt); + const painter = new TMultiGraphPainter(dom, mgraph, opt); + return painter.redrawWith(opt, true); } } // class TMultiGraphPainter diff --git a/modules/hist/TPavePainter.mjs b/modules/hist/TPavePainter.mjs index 4b190faa2..3dd7910f0 100644 --- a/modules/hist/TPavePainter.mjs +++ b/modules/hist/TPavePainter.mjs @@ -1,21 +1,24 @@ -import { gStyle, browser, settings, clone, isObject, isFunc, isStr, BIT, +import { gStyle, browser, settings, clone, create, isObject, isFunc, isStr, BIT, clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, clTLegend, clTPaletteAxis, - clTText, clTLatex, clTLine, clTBox, kTitle } from '../core.mjs'; + clTText, clTLatex, clTLine, clTBox, kTitle, isNodeJs, nsSVG } from '../core.mjs'; import { select as d3_select, rgb as d3_rgb, pointer as d3_pointer } from '../d3.mjs'; import { Prob } from '../base/math.mjs'; import { floatToString, makeTranslate, compressSVG, svgToImage, addHighlightStyle } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; +import { ObjectPainter, EAxisBits } from '../base/ObjectPainter.mjs'; +import { approximateLabelWidth } from '../base/latex.mjs'; import { showPainterMenu } from '../gui/menu.mjs'; +import { getColorExec } from '../gui/utils.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; import { addDragHandler } from '../gpad/TFramePainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -const kTakeStyle = BIT(17); + +const kTakeStyle = BIT(17), kPosTitle = 'postitle', kAutoPlace = 'autoplace', kDefaultDrawOpt = 'brNDC'; /** @summary Returns true if stat box on default place and can be adjusted * @private */ function isDefaultStatPosition(pt) { - const test = (v1, v2) => (Math.abs(v1-v2) < 1e-3); + const test = (v1, v2) => (Math.abs(v1 - v2) < 1e-3); return test(pt.fX1NDC, gStyle.fStatX - gStyle.fStatW) && test(pt.fY1NDC, gStyle.fStatY - gStyle.fStatH) && test(pt.fX2NDC, gStyle.fStatX) && @@ -28,28 +31,35 @@ function isDefaultStatPosition(pt) { * @private */ - class TPavePainter extends ObjectPainter { + #pave_x; // x position of pave + #pave_y; // y position of pave + #palette_vertical; // when palette drawing vertical + #swap_side; // swap palette side + #has_fit; // has fit info + #fit_dim; // dimension of fit function + #fit_cnt; // lines number in fit info + /** @summary constructor * @param {object|string} dom - DOM element for drawing or element id * @param {object} pave - TPave-based object */ - constructor(dom, pave) { - super(dom, pave); + constructor(dom, pave, opt) { + super(dom, pave, opt); this.Enabled = true; this.UseContextMenu = true; } - /** @summary Autoplace legend on the frame + /** @summary Auto place legend on the frame * @return {Promise} with boolean flag if position was changed */ async autoPlaceLegend(pt, pad, keep_origin) { - const main_svg = this.getFrameSvg().selectChild('.main_layer'); + const main_svg = this.getPadPainter().getFrameSvg().selectChild('.main_layer'); let svg_code = main_svg.node().outerHTML; svg_code = compressSVG(svg_code); - svg_code = '<svg xmlns="http://www.w3.org/2000/svg"' + svg_code.slice(4); + svg_code = `<svg xmlns="${nsSVG}"` + svg_code.slice(4); const lm = pad?.fLeftMargin ?? gStyle.fPadLeftMargin, rm = pad?.fRightMargin ?? gStyle.fPadRightMargin, @@ -57,16 +67,17 @@ class TPavePainter extends ObjectPainter { bm = pad?.fBottomMargin ?? gStyle.fPadBottomMargin; return svgToImage(svg_code).then(canvas => { - if (!canvas) return false; + if (!canvas) + return false; let nX = 100, nY = 100; const context = canvas.getContext('2d'), arr = context.getImageData(0, 0, canvas.width, canvas.height).data, boxW = Math.floor(canvas.width / nX), boxH = Math.floor(canvas.height / nY), - raster = new Array(nX*nY); + raster = new Array(nX * nY); if (arr.length !== canvas.width * canvas.height * 4) { - console.log(`Image size missmatch in TLegend autoplace ${arr.length} expected ${canvas.width*canvas.height * 4}`); + console.log(`Image size missmatch in TLegend autoplace ${arr.length} expected ${canvas.width * canvas.height * 4}`); nX = nY = 0; } @@ -79,34 +90,36 @@ class TPavePainter extends ObjectPainter { for (let x = px1; (x < px2) && !filled; ++x) { for (let y = py1; y < py2; ++y) { const indx = (y * canvas.width + x) * 4; - if (arr[indx] || arr[indx+1] || arr[indx+2] || arr[indx+3]) { + if (arr[indx] || arr[indx + 1] || arr[indx + 2] || arr[indx + 3]) { filled = 1; break; } } - } - raster[iy * nX + ix] = filled; + } + raster[iy * nX + ix] = filled; } } const legWidth = 0.3 / Math.max(0.2, (1 - lm - rm)), - legHeight = Math.min(0.5, Math.max(0.1, pt.fPrimitives.arr.length*0.05)) / Math.max(0.2, (1 - tm - bm)), + legHeight = Math.min(0.5, Math.max(0.1, pt.fPrimitives.arr.length * 0.05)) / Math.max(0.2, (1 - tm - bm)), needW = Math.round(legWidth * nX), needH = Math.round(legHeight * nY), - test = (x, y) => { - for (let ix = x; ix < x + needW; ++ix) { - for (let iy = y; iy < y + needH; ++iy) - if (raster[iy * nX + ix]) return false; - } - return true; - }; + test = (x, y) => { + for (let ix = x; ix < x + needW; ++ix) { + for (let iy = y; iy < y + needH; ++iy) { + if (raster[iy * nX + ix]) + return false; + } + } + return true; + }; for (let ix = 0; ix < (nX - needW); ++ix) { - for (let iy = nY-needH - 1; iy >= 0; --iy) { + for (let iy = nY - needH - 1; iy >= 0; --iy) { if (test(ix, iy)) { pt.fX1NDC = lm + ix / nX * (1 - lm - rm); pt.fX2NDC = pt.fX1NDC + legWidth * (1 - lm - rm); - pt.fY2NDC = 1 - tm - iy/nY * (1 - bm - tm); + pt.fY2NDC = 1 - tm - iy / nY * (1 - bm - tm); pt.fY1NDC = pt.fY2NDC - legHeight * (1 - bm - tm); return true; } @@ -118,13 +131,30 @@ class TPavePainter extends ObjectPainter { pt.fX1NDC = Math.max(lm ?? 0, pt.fX2NDC - 0.3); pt.fX2NDC = Math.min(pt.fX1NDC + 0.3, 1 - rm); - const h0 = Math.max(pt.fPrimitives ? pt.fPrimitives.arr.length*0.05 : 0, 0.2); + const h0 = Math.max(pt.fPrimitives ? pt.fPrimitives.arr.length * 0.05 : 0, 0.2); pt.fY2NDC = Math.min(1 - tm, pt.fY1NDC + h0); pt.fY1NDC = Math.max(pt.fY2NDC - h0, bm); return true; }); } + /** @summary Get draw option for the pave + * @desc only stats using fOption directly, all other classes - stored in the pad */ + getPaveDrawOption() { + let opt = this.getDrawOpt(); + if (this.isStats() || !opt) + opt = this.getObject()?.fOption; + return opt || kDefaultDrawOpt; + } + + /** @summary Change pave draw option */ + setPaveDrawOption(opt) { + if (this.isStats()) + this.getObject().fOption = opt; + else + this.storeDrawOpt(opt); + } + /** @summary Draw pave and content * @return {Promise} */ async drawPave(arg) { @@ -133,9 +163,15 @@ class TPavePainter extends ObjectPainter { return this; } - const pt = this.getObject(), opt = pt.fOption.toUpperCase(), + const pt = this.getObject(), + opt = this.getPaveDrawOption().toUpperCase(), fp = this.getFramePainter(), pp = this.getPadPainter(), pad = pp.getRootPad(true); + + // special handling of dummy frame painter + if (fp?.getDrawDom() === null) + return this; + let interactive_element, width, height; if (pt.fInit === 0) { @@ -154,9 +190,18 @@ class TPavePainter extends ObjectPainter { pt.fY1NDC = 0.1; pt.fY2NDC = 0.9; } - } else if (opt.indexOf('NDC') >= 0) { - pt.fX1NDC = pt.fX1; pt.fX2NDC = pt.fX2; - pt.fY1NDC = pt.fY1; pt.fY2NDC = pt.fY2; + } else if (pt.fOption.indexOf('NDC') >= 0) { + // check if NDC was modified but fInit was not set + // wired - ROOT checks fOption even when absolutely different draw option may be specified, + // happens in stressGraphics.cxx, sg30 where stats box not initialized when call C->Update() in batch mode + if (pt.fX1NDC < 1e-20 && pt.fX2NDC < 1e-20) { + pt.fX1NDC = pt.fX1; + pt.fX2NDC = pt.fX2; + } + if (pt.fY1NDC < 1e-20 && pt.fY2NDC < 1e-20) { + pt.fY1NDC = pt.fY1; + pt.fY2NDC = pt.fY2; + } } else if (pad && (pad.fX1 === 0) && (pad.fX2 === 1) && (pad.fY1 === 0) && (pad.fY2 === 1) && isStr(arg) && (arg.indexOf('postpone') >= 0)) { // special case when pad not yet initialized pt.fInit = 0; // do not init until axes drawn @@ -164,12 +209,16 @@ class TPavePainter extends ObjectPainter { pt.fX2NDC = pt.fY2NDC = 1; } else if (pad) { if (pad.fLogx) { - if (pt.fX1 > 0) pt.fX1 = Math.log10(pt.fX1); - if (pt.fX2 > 0) pt.fX2 = Math.log10(pt.fX2); + if (pt.fX1 > 0) + pt.fX1 = Math.log10(pt.fX1); + if (pt.fX2 > 0) + pt.fX2 = Math.log10(pt.fX2); } if (pad.fLogy) { - if (pt.fY1 > 0) pt.fY1 = Math.log10(pt.fY1); - if (pt.fY2 > 0) pt.fY2 = Math.log10(pt.fY2); + if (pt.fY1 > 0) + pt.fY1 = Math.log10(pt.fY1); + if (pt.fY2 > 0) + pt.fY2 = Math.log10(pt.fY2); } pt.fX1NDC = (pt.fX1 - pad.fX1) / (pad.fX2 - pad.fX1); pt.fY1NDC = (pt.fY1 - pad.fY1) / (pad.fY2 - pad.fY1); @@ -187,8 +236,10 @@ class TPavePainter extends ObjectPainter { promise = this.autoPlaceLegend(pt, pad).then(res => { delete this.AutoPlace; if (!res) { - pt.fX1NDC = fp.fX2NDC - 0.2; pt.fX2NDC = fp.fX2NDC; - pt.fY1NDC = fp.fY2NDC - 0.1; pt.fY2NDC = fp.fY2NDC; + pt.fX1NDC = fp.fX2NDC - 0.2; + pt.fX2NDC = fp.fX2NDC; + pt.fY1NDC = fp.fY2NDC - 0.1; + pt.fY2NDC = fp.fY2NDC; } return res; }); @@ -196,32 +247,43 @@ class TPavePainter extends ObjectPainter { return promise.then(() => { // fill stats before drawing to have coordinates early - if (this.isStats() && !this.NoFillStats && !pp._fast_drawing) { + if (this.isStats() && !this.NoFillStats && !pp.isFastDrawing()) { const main = pt.$main_painter || this.getMainPainter(); if (isFunc(main?.fillStatistic)) { - let dostat = parseInt(pt.fOptStat), dofit = parseInt(pt.fOptFit); - if (!Number.isInteger(dostat) || pt.TestBit(kTakeStyle)) dostat = gStyle.fOptStat; - if (!Number.isInteger(dofit)|| pt.TestBit(kTakeStyle)) dofit = gStyle.fOptFit; + let dostat = pt.fOptStat, dofit = pt.fOptFit; + if (pt.TestBit(kTakeStyle) || !Number.isInteger(dostat)) + dostat = gStyle.fOptStat; + if (pt.TestBit(kTakeStyle) || !Number.isInteger(dofit)) + dofit = gStyle.fOptFit; // we take statistic from main painter if (main.fillStatistic(this, dostat, dofit)) { // adjust the size of the stats box with the number of lines let nlines = pt.fLines?.arr.length || 0; - if ((nlines > 0) && !this.moved_interactive && isDefaultStatPosition(pt)) { - // in ROOT TH2 and TH3 always add full statsh for fit parameters - const extrah = this._has_fit && (this._fit_dim > 1) ? gStyle.fStatH : 0; + const set_default = (nlines > 0) && !this.moved_interactive && isDefaultStatPosition(pt), + // in ROOT TH2 and TH3 always add full stats for fit parameters + extrah = this.#has_fit && (this.#fit_dim > 1) ? gStyle.fStatH : 0; + if (extrah) + nlines -= this.#fit_cnt; + let stath = gStyle.fStatH, statw = gStyle.fStatW; + if (this.#has_fit) + statw = 1.8 * gStyle.fStatW; + if ((gStyle.fStatFontSize <= 0) || (gStyle.fStatFont % 10 === 3)) + stath = nlines * 0.25 * gStyle.fStatH; + else if (gStyle.fStatFontSize < 1) + stath = nlines * gStyle.fStatFontSize; + + if (set_default) { // but fit parameters not used in full size calculations - if (extrah) nlines -= this._fit_cnt; - let stath = gStyle.fStatH, statw = gStyle.fStatW; - if (this._has_fit) - statw = 1.8 * gStyle.fStatW; - if ((gStyle.fStatFontSize <= 0) || (gStyle.fStatFont % 10 === 3)) - stath = nlines * 0.25 * gStyle.fStatH; - else if (gStyle.fStatFontSize < 1) - stath = nlines * gStyle.fStatFontSize; - pt.fX1NDC = Math.max(0.02, pt.fX2NDC - statw); - pt.fY1NDC = Math.max(0.02, pt.fY2NDC - stath - extrah); + pt.fX1NDC = Math.max(0.005, pt.fX2NDC - statw); + pt.fY1NDC = Math.max(0.005, pt.fY2NDC - stath - extrah); + } else { + // when some NDC values are set directly and not match with each other + if (pt.fY1NDC > pt.fY2NDC) + pt.fY2NDC = Math.min(0.995, pt.fY1NDC + stath + extrah); + if (pt.fX1NDC > pt.fX2NDC) + pt.fY2NDC = Math.min(0.995, pt.fX1NDC + statw); } } } @@ -231,109 +293,161 @@ class TPavePainter extends ObjectPainter { brd = pt.fBorderSize, noborder = opt.indexOf('NB') >= 0, dx = (opt.indexOf('L') >= 0) ? -1 : ((opt.indexOf('R') >= 0) ? 1 : 0), - dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0); - - // container used to recalculate coordinates - this.createG(); + dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0), + g = this.createG(); // container used to recalculate coordinates - this._pave_x = Math.round(pt.fX1NDC * pad_rect.width); - this._pave_y = Math.round((1.0 - pt.fY2NDC) * pad_rect.height); + this.#pave_x = Math.round(pt.fX1NDC * pad_rect.width); + this.#pave_y = Math.round((1.0 - pt.fY2NDC) * pad_rect.height); width = Math.round((pt.fX2NDC - pt.fX1NDC) * pad_rect.width); height = Math.round((pt.fY2NDC - pt.fY1NDC) * pad_rect.height); - makeTranslate(this.draw_g, this._pave_x, this._pave_y); + const arc_radius = opt.indexOf('ARC') >= 0 && (pt.fCornerRadius > 0) ? Math.round(Math.min(width, height) * pt.fCornerRadius) : 0; + + makeTranslate(g, this.#pave_x, this.#pave_y); this.createAttLine({ attr: pt, width: (brd > 0) ? pt.fLineWidth : 0 }); this.createAttFill({ attr: pt }); + // need to fill pave while + if (this.fillatt.empty() && arc_radius) + this.fillatt.setSolidColor(this.getColor(pt.fFillColor) || 'white'); if (pt._typename === clTDiamond) { - const h2 = Math.round(height/2), w2 = Math.round(width/2), + const h2 = Math.round(height / 2), w2 = Math.round(width / 2), dpath = `l${w2},${-h2}l${w2},${h2}l${-w2},${h2}z`; - if ((brd > 1) && (pt.fShadowColor > 0) && (dx || dy) && !this.fillatt.empty() && !noborder) { - this.draw_g.append('svg:path') - .attr('d', 'M0,'+(h2+brd) + dpath) - .style('fill', this.getColor(pt.fShadowColor)) - .style('stroke', this.getColor(pt.fShadowColor)) - .style('stroke-width', '1px'); - } + if (!this.fillatt.empty()) + this.drawBorder(g, width, height, 0, dpath); - interactive_element = this.draw_g.append('svg:path') - .attr('d', 'M0,'+h2 +dpath) - .call(this.fillatt.func) - .call(this.lineatt.func); + interactive_element = g.append('svg:path') + .attr('d', 'M0,' + h2 + dpath) + .call(this.fillatt.func) + .call(this.lineatt.func); - const text_g = this.draw_g.append('svg:g'); - makeTranslate(text_g, Math.round(width/4), Math.round(height/4)); + const text_g = g.append('svg:g'); + makeTranslate(text_g, Math.round(width / 4), Math.round(height / 4)); return this.drawPaveText(w2, h2, arg, text_g); - } else { - // add shadow decoration before main rect - if ((brd > 1) && (pt.fShadowColor > 0) && !pt.fNpaves && (dx || dy) && !noborder) { - const scol = this.getColor(pt.fShadowColor); - let spath = ''; - - if ((dx < 0) && (dy < 0)) - spath = `M0,0v${height-brd}h${-brd}v${-height}h${width}v${brd}z`; - else if ((dx < 0) && (dy > 0)) - spath = `M0,${height}v${brd-height}h${-brd}v${height}h${width}v${-brd}z`; - else if ((dx > 0) && (dy < 0)) - spath = `M${brd},0v${-brd}h${width}v${height}h${-brd}v${brd-height}z`; - else - spath = `M${width},${brd}h${brd}v${height}h${-width}v${-brd}h${width-brd}z`; - - this.draw_g.append('svg:path') - .attr('d', spath) - .style('fill', scol) - .style('stroke', scol) - .style('stroke-width', '1px'); - } + } - if (pt.fNpaves) { - for (let n = pt.fNpaves-1; n > 0; --n) { - this.draw_g.append('svg:path') - .attr('d', `M${dx*4*n},${dy*4*n}h${width}v${height}h${-width}z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - } + if (pt.fNpaves) { + for (let n = pt.fNpaves - 1; n > 0; --n) { + g.append('svg:path') + .attr('d', `M${dx * 4 * n},${dy * 4 * n}h${width}v${height}h${-width}z`) + .call(this.fillatt.func) + .call(this.lineatt.func); } - - if (!this.isBatchMode() || !this.fillatt.empty() || (!this.lineatt.empty() && !noborder)) { - interactive_element = this.draw_g.append('svg:path') - .attr('d', `M0,0H${width}V${height}H0Z`) - .call(this.fillatt.func); - if (!noborder) - interactive_element.call(this.lineatt.func); + } else + this.drawBorder(g, width, height, arc_radius); + + if (!this.isBatchMode() || !this.fillatt.empty() || (!this.lineatt.empty() && !noborder)) { + if (arc_radius) { + interactive_element = g.append('svg:rect') + .attr('width', width) + .attr('height', height) + .attr('rx', arc_radius); + } else { + interactive_element = g.append('svg:path') + .attr('d', `M0,0H${width}V${height}H0Z`); } + interactive_element.call(this.fillatt.func); + if (!noborder) + interactive_element.call(this.lineatt.func); + } - return isFunc(this.paveDrawFunc) ? this.paveDrawFunc(width, height, arg) : true; + switch (pt._typename) { + case clTPaveLabel: + case clTPaveClass: + return this.drawPaveLabel(width, height, arg); + case clTPaveStats: + return this.drawPaveStats(width, height, arg); + case clTPaveText: + case clTPavesText: + case clTDiamond: + return this.drawPaveText(width, height, arg); + case clTLegend: + return this.drawLegend(width, height, arg); + case clTPaletteAxis: + return this.drawPaletteAxis(width, height, arg); } }).then(() => { if (this.isBatchMode() || (pt._typename === clTPave)) return this; // here all kind of interactive settings - if (interactive_element) { - interactive_element.style('pointer-events', 'visibleFill') - .on('mouseenter', () => this.showObjectStatus()); - } - - addDragHandler(this, { obj: pt, x: this._pave_x, y: this._pave_y, width, height, - minwidth: 10, minheight: 20, canselect: true, - redraw: () => { this.moved_interactive = true; this.interactiveRedraw(false, 'pave_moved'); this.drawPave(); }, - ctxmenu: browser.touches && settings.ContextMenu && this.UseContextMenu }); + interactive_element?.style('pointer-events', 'visibleFill') + .on('mouseenter', () => this.showObjectStatus()); + + addDragHandler(this, { + obj: pt, x: this.#pave_x, y: this.#pave_y, width, height, + minwidth: 10, minheight: 20, canselect: true, + ctxmenu: browser.touches && settings.ContextMenu && this.UseContextMenu, + redraw: () => { + this.moved_interactive = true; + this.interactiveRedraw(false, 'pave_moved').then(() => this.drawPave()); + } + }); if (this.UseContextMenu && settings.ContextMenu) - this.draw_g.on('contextmenu', evnt => this.paveContextMenu(evnt)); + this.getG().on('contextmenu', evnt => this.paveContextMenu(evnt)); - if (pt._typename === clTPaletteAxis) + if (this.isPalette()) this.interactivePaletteAxis(width, height); return this; }); } + drawBorder(draw_g, width, height, arc_radius, diamond) { + const pt = this.getObject(), + opt = this.getPaveDrawOption().toUpperCase().replaceAll('ARC', '').replaceAll('NDC', ''), + noborder = this.isPalette() || (opt.indexOf('NB') >= 0), + dx = (opt.indexOf('L') >= 0) ? -1 : ((opt.indexOf('R') >= 0) ? 1 : 0), + dy = (opt.indexOf('T') >= 0) ? -1 : ((opt.indexOf('B') >= 0) ? 1 : 0); + + if ((pt.fBorderSize < 2) || (pt.fShadowColor === 0) || (!dx && !dy) || noborder) + return; + + const scol = this.getColor(pt.fShadowColor), + brd = pt.fBorderSize, + brd_width = !this.lineatt.empty() && (this.lineatt.width > 2) ? `${this.lineatt.width - 1}px` : '1px'; + + if (diamond) { + draw_g.append('svg:path') + .attr('d', `M0,${Math.round(height / 2) + brd}${diamond}`) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } else if (arc_radius) { + draw_g.append('svg:rect') + .attr('width', width) + .attr('height', height) + .attr('rx', arc_radius) + .attr('x', dx * brd) + .attr('y', dy * brd) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } else { + let spath; + + if ((dx < 0) && (dy < 0)) + spath = `M0,0v${height - brd - 1}h${1 - brd}v${2 - height}h${width - 2}v${brd - 1}z`; + else if ((dx < 0) && (dy > 0)) + spath = `M0,${height}v${brd + 1 - height}h${1 - brd}v${height - 2}h${width - 2}v${1 - brd}z`; + else if ((dx > 0) && (dy < 0)) + spath = `M${brd + 1},0v${1 - brd}h${width - 2}v${height - 2}h${1 - brd}v${brd + 1 - height}z`; + else + spath = `M${width},${brd + 1}h${brd - 1}v${height - 2}h${2 - width}v${1 - brd}h${width - brd - 2}z`; + + draw_g.append('svg:path') + .attr('d', spath) + .style('fill', scol) + .style('stroke', scol) + .style('stroke-width', brd_width); + } + } + /** @summary Fill option object used in TWebCanvas */ fillWebObjectOptions(res) { const pave = this.getObject(); @@ -343,10 +457,10 @@ class TPavePainter extends ObjectPainter { res.fopt = [pave.fX1NDC, pave.fY1NDC, pave.fX2NDC, pave.fY2NDC]; if ((pave.fName === 'stats') && this.isStats()) { - pave.fLines.arr.forEach(entry => { - if ((entry._typename === clTText) || (entry._typename === clTLatex)) - res.fcust += `;;${entry.fTitle}`; - }); + pave.fLines.arr.forEach(entry => { + if ((entry._typename === clTText) || (entry._typename === clTLatex)) + res.fcust += `;;${entry.fTitle}`; + }); } } @@ -361,11 +475,9 @@ class TPavePainter extends ObjectPainter { this.createAttText({ attr: pave, can_rotate: false }); - this.startTextDrawing(this.textatt.font, height/1.2); - - this.drawText(this.textatt.createArg({ width, height, text: pave.fLabel, norotate: true })); - - return this.finishTextDrawing(); + return this.startTextDrawingAsync(this.textatt.font, height / 1.2) + .then(() => this.drawText(this.textatt.createArg({ width, height, text: pave.fLabel, norotate: true }))) + .then(() => this.finishTextDrawing()); } /** @summary draw TPaveStats object */ @@ -379,7 +491,7 @@ class TPavePainter extends ObjectPainter { if ((entry._typename === clTText) || (entry._typename === clTLatex)) { lines.push(entry.fTitle); colors.push(entry.fTextColor); - } + } } const nlines = lines.length; @@ -387,9 +499,12 @@ class TPavePainter extends ObjectPainter { // adjust font size for (let j = 0; j < nlines; ++j) { const line = lines[j]; - if (j > 0) maxlen = Math.max(maxlen, line.length); - if ((j === 0) || (line.indexOf('|') < 0)) continue; - if (first_stat === 0) first_stat = j; + if (j > 0) + maxlen = Math.max(maxlen, line.length); + if ((j === 0) || (line.indexOf('|') < 0)) + continue; + if (first_stat === 0) + first_stat = j; const parts = line.split('|'); if (parts.length > num_cols) num_cols = parts.length; @@ -401,224 +516,275 @@ class TPavePainter extends ObjectPainter { this.createAttText({ attr: pt, can_rotate: false }); - this.startTextDrawing(this.textatt.font, height/(nlines * 1.2)); - - if (nlines === 1) - this.drawText(this.textatt.createArg({ width, height, text: lines[0], latex: 1, norotate: true })); - else { - for (let j = 0; j < nlines; ++j) { - const y = j*stepy, - color = (colors[j] > 1) ? this.getColor(colors[j]) : this.textatt.color; + return this.startTextDrawingAsync(this.textatt.font, height / (nlines * 1.2)).then(() => { + if (nlines === 1) + this.drawText(this.textatt.createArg({ width, height, text: lines[0], latex: 1, norotate: true })); + else { + for (let j = 0; j < nlines; ++j) { + const y = j * stepy, + color = (colors[j] > 1) ? this.getColor(colors[j]) : this.textatt.color; + + if (first_stat && (j >= first_stat)) { + const parts = lines[j].split('|'); + for (let n = 0; n < parts.length; ++n) { + this.drawText({ + align: 'middle', x: width * n / num_cols, y, latex: 0, + width: width / num_cols, height: stepy, text: parts[n], color + }); + } + } else if (lines[j].indexOf('=') < 0) { + if (j === 0) { + has_head = true; + const max_hlen = Math.max(maxlen, Math.round((width - 2 * margin_x) / stepy / 0.65)); + if (lines[j].length > max_hlen + 5) + lines[j] = lines[j].slice(0, max_hlen + 2) + '...'; + } + this.drawText({ + align: (j === 0) ? 'middle' : 'start', x: margin_x, y, + width: width - 2 * margin_x, height: stepy, text: lines[j], color + }); + } else { + const parts = lines[j].split('='), args = []; + + for (let n = 0; n < 2; ++n) { + const arg = { + align: (n === 0) ? 'start' : 'end', x: margin_x, y, + width: width - 2 * margin_x, height: stepy, text: n > 0 ? parts[n].trimStart() : parts[n].trimEnd(), color, + _expected_width: width - 2 * margin_x, _args: args, + post_process(painter) { + if (this._args[0].ready && this._args[1].ready) + painter.scaleTextDrawing(1.05 * (this._args[0].result_width + this._args[1].result_width) / this._expected_width, painter.getG()); + } + }; + args.push(arg); + } - if (first_stat && (j >= first_stat)) { - const parts = lines[j].split('|'); - for (let n = 0; n < parts.length; ++n) { - this.drawText({ align: 'middle', x: width * n / num_cols, y, latex: 0, - width: width/num_cols, height: stepy, text: parts[n], color }); - } - } else if (lines[j].indexOf('=') < 0) { - if (j === 0) { - has_head = true; - const max_hlen = Math.max(maxlen, Math.round((width-2*margin_x)/stepy/0.65)); - if (lines[j].length > max_hlen + 5) - lines[j] = lines[j].slice(0, max_hlen+2) + '...'; + for (let n = 0; n < 2; ++n) + this.drawText(args[n]); } - this.drawText({ align: (j === 0) ? 'middle' : 'start', x: margin_x, y, - width: width-2*margin_x, height: stepy, text: lines[j], color }); - } else { - const parts = lines[j].split('='), args = []; - - for (let n = 0; n < 2; ++n) { - const arg = { - align: (n === 0) ? 'start' : 'end', x: margin_x, y, - width: width - 2*margin_x, height: stepy, text: parts[n], color, - _expected_width: width-2*margin_x, _args: args, - post_process(painter) { - if (this._args[0].ready && this._args[1].ready) - painter.scaleTextDrawing(1.05*(this._args[0].result_width+this._args[1].result_width)/this._expected_width, painter.draw_g); - } - }; - args.push(arg); - } - - for (let n = 0; n < 2; ++n) - this.drawText(args[n]); } } - } - let lpath = ''; + let lpath = ''; - if ((pt.fBorderSize > 0) && has_head) - lpath += `M0,${Math.round(stepy)}h${width}`; + if ((pt.fBorderSize > 0) && has_head) + lpath += `M0,${Math.round(stepy)}h${width}`; - if ((first_stat > 0) && (num_cols > 1)) { - for (let nrow = first_stat; nrow < nlines; ++nrow) - lpath += `M0,${Math.round(nrow * stepy)}h${width}`; - for (let ncol = 0; ncol < num_cols - 1; ++ncol) - lpath += `M${Math.round(width / num_cols * (ncol + 1))},${Math.round(first_stat * stepy)}V${height}`; - } + if ((first_stat > 0) && (num_cols > 1)) { + for (let nrow = first_stat; nrow < nlines; ++nrow) + lpath += `M0,${Math.round(nrow * stepy)}h${width}`; + for (let ncol = 0; ncol < num_cols - 1; ++ncol) + lpath += `M${Math.round(width / num_cols * (ncol + 1))},${Math.round(first_stat * stepy)}V${height}`; + } - if (lpath) this.draw_g.append('svg:path').attr('d', lpath).call(this.lineatt.func); + if (lpath) + this.appendPath(lpath).call(this.lineatt.func); - // this.draw_g.classed('most_upper_primitives', true); // this primitive will remain on top of list + // this.getG().classed('most_upper_primitives', true); // this primitive will remain on top of list - return this.finishTextDrawing(undefined, (nlines > 1)); + return this.finishTextDrawing(undefined, (nlines > 1)); + }); } /** @summary draw TPaveText object */ - drawPaveText(width, height, _dummy_arg, text_g) { + async drawPaveText(width, height, _dummy_arg, text_g) { const pt = this.getObject(), arr = pt.fLines?.arr || [], nlines = arr.length, pp = this.getPadPainter(), pad_height = pp.getPadHeight(), - draw_header = (pt.fLabel.length > 0), + draw_header = pt.fLabel.length, promises = [], margin_x = pt.fMargin * width, - stepy = height / (nlines || 1); + stepy = height / (nlines || 1), + dflt_font_size = 0.85 * stepy; let max_font_size = 0; this.createAttText({ attr: pt, can_rotate: false }); // for single line (typically title) limit font size if ((nlines === 1) && (this.textatt.size > 0)) - max_font_size = Math.max(3, this.textatt.getSize(pad_height)); + max_font_size = Math.max(3, this.textatt.getSize(pp)); - if (!text_g) text_g = this.draw_g; + if (!text_g) + text_g = this.getG(); - const fast = (nlines === 1) && pp._fast_drawing; - let num_default = 0; + const fast = (nlines === 1) && pp.isFastDrawing(); + let num_txt = 0, num_custom = 0, longest_line = 0, alt_text_size = 0; - for (let nline = 0; nline < nlines; ++nline) { - const entry = arr[nline], texty = nline*stepy; + arr.forEach(entry => { + if (((entry._typename !== clTText) && (entry._typename !== clTLatex)) || !entry.fTitle?.trim()) + return; + num_txt++; + if (entry.fX || entry.fY || entry.fTextSize) + num_custom++; - switch (entry._typename) { - case clTText: - case clTLatex: { - if (!entry.fTitle || !entry.fTitle.trim()) continue; + if (!entry.fTextSize && !this.textatt.size) + longest_line = Math.max(longest_line, approximateLabelWidth(entry.fTitle, this.textatt.font, dflt_font_size)); + }); - let color = entry.fTextColor ? this.getColor(entry.fTextColor) : ''; - if (!color) color = this.textatt.color; + if (longest_line) { + alt_text_size = dflt_font_size; + if (longest_line > 0.92 * width) + alt_text_size *= (0.92 * width / longest_line); + alt_text_size = Math.round(alt_text_size); + } - if (entry.fX || entry.fY || entry.fTextSize) { - // individual positioning - const align = entry.fTextAlign || this.textatt.align, - halign = Math.floor(align/10), - valign = align % 10, - x = entry.fX ? entry.fX*width : (halign === 1 ? margin_x : (halign === 2 ? width / 2 : width - margin_x)), - y = entry.fY ? (1 - entry.fY)*height : (texty + (valign === 2 ? stepy / 2 : (valign === 3 ? stepy : 0))), - sub_g = text_g.append('svg:g'); + const pr = (num_txt > num_custom) ? this.startTextDrawingAsync(this.textatt.font, this.$postitle ? this.textatt.getSize(pp, 1, 0.05) : dflt_font_size, text_g, max_font_size) : Promise.resolve(); - this.startTextDrawing(this.textatt.font, this.textatt.getAltSize(entry.fTextSize, pad_height), sub_g); + return pr.then(() => { + for (let nline = 0; nline < nlines; ++nline) { + const entry = arr[nline], texty = nline * stepy; - this.drawText({ align, x, y, text: entry.fTitle, color, - latex: (entry._typename === clTText) ? 0 : 1, draw_g: sub_g, fast }); + switch (entry._typename) { + case clTText: + case clTLatex: { + if (!entry.fTitle || !entry.fTitle.trim()) + continue; - promises.push(this.finishTextDrawing(sub_g)); - } else { - // default position - if (num_default++ === 0) - this.startTextDrawing(this.textatt.font, 0.85*height/nlines, text_g, max_font_size); - - this.drawText({ x: margin_x, y: texty, width: width - 2*margin_x, height: stepy, - align: entry.fTextAlign || this.textatt.align, - draw_g: text_g, latex: (entry._typename === clTText) ? 0 : 1, - text: entry.fTitle, color, fast }); + let color = entry.fTextColor ? this.getColor(entry.fTextColor) : ''; + if (!color) + color = this.textatt.color; + const align = entry.fTextAlign || this.textatt.align, + valign = align % 10, + halign = (align - valign) / 10; + + if (entry.fX || entry.fY || entry.fTextSize) { + // individual positioning + const x = entry.fX ? entry.fX * width : (halign === 1 ? margin_x : (halign === 2 ? width / 2 : width - margin_x)), + y = entry.fY ? (1 - entry.fY) * height : (texty + (valign === 2 ? stepy / 2 : (valign === 3 ? stepy : 0))), + draw_g = text_g.append('svg:g'); + + promises.push(this.startTextDrawingAsync(this.textatt.font, this.textatt.getAltSize(entry.fTextSize, pp) || alt_text_size, draw_g) + .then(() => this.drawText({ + align, x, y, text: entry.fTitle, color, + latex: (entry._typename === clTText) ? 0 : 1, draw_g, fast + })) + .then(() => this.finishTextDrawing(draw_g))); + } else { + const arg = { + x: 0, y: texty, draw_g: text_g, + latex: (entry._typename === clTText) ? 0 : 1, + text: entry.fTitle, color, fast + }; + + if (this.$postitle) { + // remember box produced by title text + arg.post_process = function(painter) { + painter.$titlebox = this.box; + }; + } else { + arg.align = align; + arg.x = (halign === 1) ? margin_x : 0; + arg.width = (halign === 2) ? width : width - margin_x; + arg.y = texty + 0.05 * stepy; + arg.height = 0.9 * stepy; + // prevent expand of normal title on full width + // if (this.isTitle() && (halign === 2) && (arg.width > 0.1*pad_width) && (arg.width < 0.7*pad_width)) { + // arg.width -= 0.02*pad_width; + // arg.x = 0.01*pad_width; + // } + } + + this.drawText(arg); + } + break; } - break; - } - case clTLine: { - const lx1 = entry.fX1 ? Math.round(entry.fX1*width) : 0, - lx2 = entry.fX2 ? Math.round(entry.fX2*width) : width, - ly1 = entry.fY1 ? Math.round((1 - entry.fY1)*height) : Math.round(texty + stepy*0.5), - ly2 = entry.fY2 ? Math.round((1 - entry.fY2)*height) : Math.round(texty + stepy*0.5), - lineatt = this.createAttLine(entry); - text_g.append('svg:path') - .attr('d', `M${lx1},${ly1}L${lx2},${ly2}`) - .call(lineatt.func); - break; - } - case clTBox: { - const bx1 = entry.fX1 ? Math.round(entry.fX1*width) : 0, - bx2 = entry.fX2 ? Math.round(entry.fX2*width) : width, - by1 = entry.fY1 ? Math.round((1 - entry.fY1)*height) : Math.round(texty), - by2 = entry.fY2 ? Math.round((1 - entry.fY2)*height) : Math.round(texty + stepy), - fillatt = this.createAttFill(entry); - text_g.append('svg:path') - .attr('d', `M${bx1},${by1}H${bx2}V${by2}H${bx1}Z`) - .call(fillatt.func); - break; + case clTLine: { + const lx1 = entry.fX1 ? Math.round(entry.fX1 * width) : 0, + lx2 = entry.fX2 ? Math.round(entry.fX2 * width) : width, + ly1 = entry.fY1 ? Math.round((1 - entry.fY1) * height) : Math.round(texty + stepy * 0.5), + ly2 = entry.fY2 ? Math.round((1 - entry.fY2) * height) : Math.round(texty + stepy * 0.5), + lineatt = this.createAttLine(entry); + text_g.append('svg:path') + .attr('d', `M${lx1},${ly1}L${lx2},${ly2}`) + .call(lineatt.func); + break; + } + case clTBox: { + const bx1 = entry.fX1 ? Math.round(entry.fX1 * width) : 0, + bx2 = entry.fX2 ? Math.round(entry.fX2 * width) : width, + by1 = entry.fY1 ? Math.round((1 - entry.fY1) * height) : Math.round(texty), + by2 = entry.fY2 ? Math.round((1 - entry.fY2) * height) : Math.round(texty + stepy), + fillatt = this.createAttFill(entry); + text_g.append('svg:path') + .attr('d', `M${bx1},${by1}H${bx2}V${by2}H${bx1}Z`) + .call(fillatt.func); + break; + } } } - } - if (num_default > 0) - promises.push(this.finishTextDrawing(text_g, num_default > 1)); + if (num_txt > num_custom) + promises.push(this.finishTextDrawing(text_g, num_txt > num_custom + 1)); - if (draw_header) { - const x = Math.round(width*0.25), - y = Math.round(-height*0.02), - w = Math.round(width*0.5), - h = Math.round(height*0.04), - lbl_g = text_g.append('svg:g'); + if (this.isTitle()) + this.getG().style('display', !num_txt ? 'none' : null); - lbl_g.append('svg:path') - .attr('d', `M${x},${y}h${w}v${h}h${-w}z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - this.startTextDrawing(this.textatt.font, h/1.5, lbl_g); + return Promise.all(promises).then(() => this); + }).then(() => { + if (!draw_header) + return; - this.drawText({ align: 22, x, y, width: w, height: h, text: pt.fLabel, color: this.textatt.color, draw_g: lbl_g }); + const w = Math.round(width * 0.5), + h = Math.round(pad_height * 0.04), + lbl_g = text_g.append('svg:g'); - promises.push(this.finishTextDrawing(lbl_g)); - } + makeTranslate(lbl_g, Math.round(width * 0.25), Math.round(-pad_height * 0.02)); + + this.drawBorder(lbl_g, w, h); - return Promise.all(promises).then(() => this); + lbl_g.append('svg:path') + .attr('d', `M${0},${0}h${w}v${h}h${-w}z`) + .call(this.fillatt.func) + .call(this.lineatt.func); + + return this.startTextDrawingAsync(this.textatt.font, 0.9 * h, lbl_g) + .then(() => this.drawText({ align: 22, x: 0, y: 0, width: w, height: h, text: pt.fLabel, color: this.textatt.color, draw_g: lbl_g })) + .then(() => promises.push(this.finishTextDrawing(lbl_g))); + }).then(() => { return this; }); } /** @summary Method used to convert value to string according specified format * @desc format can be like 5.4g or 4.2e or 6.4f or 'stat' or 'fit' or 'entries' */ format(value, fmt) { - if (!fmt) fmt = 'stat'; + if (!fmt) + fmt = 'stat'; const pave = this.getObject(); switch (fmt) { - case 'stat' : fmt = pave.fStatFormat || gStyle.fStatFormat; break; - case 'fit': fmt = pave.fFitFormat || gStyle.fFitFormat; break; + case 'stat': + fmt = pave.fStatFormat || gStyle.fStatFormat; + break; + case 'fit': + fmt = pave.fFitFormat || gStyle.fFitFormat; + break; case 'entries': if ((Math.abs(value) < 1e9) && (Math.round(value) === value)) return value.toFixed(0); fmt = '14.7g'; break; - case 'last': fmt = this.lastformat; break; } - const res = floatToString(value, fmt || '6.4g', true); - - this.lastformat = res[1]; - - return res[0]; + return floatToString(value, fmt || '6.4g'); } /** @summary Draw TLegend object */ drawLegend(w, h) { const legend = this.getObject(), - nlines = legend.fPrimitives.arr.length; - let ncols = legend.fNColumns, - nrows = nlines, + nlines = legend.fPrimitives.arr.length, + ncols = Math.max(1, legend.fNColumns); + let nrows = Math.round(nlines / ncols), any_text = false, custom_textg = false; // each text entry has own attributes - if (ncols < 2) - ncols = 1; - else - while ((nrows-1)*ncols >= nlines) nrows--; + if (nrows * ncols < nlines) + nrows++; - const isEmpty = entry => !entry.fObject && !entry.fOption && (!entry.fLabel || (entry.fLabel === ' ')); + const isEmpty = entry => !entry.fObject && !entry.fOption && (!entry.fLabel || !entry.fLabel.trim()); for (let ii = 0; ii < nlines; ++ii) { const entry = legend.fPrimitives.arr[ii]; @@ -629,20 +795,37 @@ class TPavePainter extends ObjectPainter { any_text = true; if ((entry.fTextFont && (entry.fTextFont !== legend.fTextFont)) || (entry.fTextSize && (entry.fTextSize !== legend.fTextSize))) - custom_textg = true; + custom_textg = true; } } - if (nrows < 1) nrows = 1; + if (nrows < 1) + nrows = 1; + + const padding_x = Math.round(0.03 * w / ncols), + padding_y = Math.round(0.03 * h), + row_height = (h - 2 * padding_y) / (nrows + (nrows - 1) * legend.fEntrySeparation), + gap_y = row_height * legend.fEntrySeparation; + + let gap_x = padding_x, + column_width0 = (w - 2 * padding_x - (ncols - 1) * gap_x) / ncols; + + if (legend.fColumnSeparation) { + column_width0 = (w - 2 * padding_x) / (ncols + (ncols - 1) * legend.fColumnSeparation); + gap_x = column_width0 * legend.fColumnSeparation; + } // calculate positions of columns by weight - means more letters, more weight - const column_pos = new Array(ncols + 1).fill(0); + const column_pos = new Array(ncols + 1).fill(padding_x), + column_boxwidth = column_width0 * legend.fMargin; if (ncols > 1) { - const column_weight = new Array(ncols).fill(1); + const column_weight = new Array(ncols).fill(1), + space_for_text = w - 2 * padding_x - (ncols - 1) * gap_x - ncols * column_boxwidth; for (let ii = 0; ii < nlines; ++ii) { const entry = legend.fPrimitives.arr[ii]; - if (isEmpty(entry)) continue; // let discard empty entry + if (isEmpty(entry)) + continue; // let discard empty entry const icol = ii % ncols; column_weight[icol] = Math.max(column_weight[icol], entry.fLabel.length); } @@ -650,203 +833,210 @@ class TPavePainter extends ObjectPainter { let sum_weight = 0; for (let icol = 0; icol < ncols; ++icol) sum_weight += column_weight[icol]; - for (let icol = 0; icol < ncols-1; ++icol) - column_pos[icol+1] = column_pos[icol] + legend.fMargin*w/ncols + column_weight[icol] * (1-legend.fMargin) * w / sum_weight; + + for (let icol = 0; icol < ncols - 1; ++icol) + column_pos[icol + 1] = column_pos[icol] + column_boxwidth + column_weight[icol] / sum_weight * space_for_text + gap_x; } - column_pos[ncols] = w; - - const padding_x = Math.round(0.03*w/ncols), - padding_y = Math.round(0.03*h), - step_y = (h - 2*padding_y)/nrows, - text_promises = [], - pp = this.getPadPainter(); - let font_size = 0.9*step_y, - max_font_size = 0, // not limited in the beggining + column_pos[ncols] = w - padding_x; + + let font_size = row_height, + max_font_size = 0, // not limited in the beginning any_opt = false; this.createAttText({ attr: legend, can_rotate: false }); - const tsz = this.textatt.getSize(pp.getPadHeight()); + const pp = this.getPadPainter(), + tsz = this.textatt.getSize(pp); if (tsz && (tsz < font_size)) font_size = max_font_size = tsz; - if (any_text && !custom_textg) - this.startTextDrawing(this.textatt.font, font_size, this.draw_g, max_font_size); - - for (let ii = 0, i = -1; ii < nlines; ++ii) { - const entry = legend.fPrimitives.arr[ii]; - if (isEmpty(entry)) continue; // let discard empty entry - - if (ncols === 1) ++i; else i = ii; - - const lopt = entry.fOption.toLowerCase(), - icol = i % ncols, irow = (i - icol) / ncols, - x0 = Math.round(column_pos[icol]), - column_width = Math.round(column_pos[icol + 1] - column_pos[icol]), - tpos_x = x0 + Math.round(legend.fMargin*w/ncols), - mid_x = Math.round((x0 + tpos_x)/2), - pos_y = Math.round(irow*step_y + padding_y), // top corner - mid_y = Math.round((irow+0.5)*step_y + padding_y), // center line - mo = entry.fObject, - draw_fill = lopt.indexOf('f') !== -1, - draw_line = lopt.indexOf('l') !== -1, - draw_error = lopt.indexOf('e') !== -1, - draw_marker = lopt.indexOf('p') !== -1; - - let o_fill = entry, o_marker = entry, o_line = entry, - painter = null, isany = false; - - if (isObject(mo)) { - if ('fLineColor' in mo) o_line = mo; - if ('fFillColor' in mo) o_fill = mo; - if ('fMarkerColor' in mo) o_marker = mo; - - painter = pp.findPainterFor(mo); - } + const text_promises = [], + pr = any_text && !custom_textg ? this.startTextDrawingAsync(this.textatt.font, font_size, undefined, max_font_size) : Promise.resolve(); + return pr.then(() => { + for (let ii = 0, i = -1; ii < nlines; ++ii) { + const entry = legend.fPrimitives.arr[ii]; + if (isEmpty(entry)) + continue; // let discard empty entry - // Draw fill pattern (in a box) - if (draw_fill) { - const fillatt = painter?.fillatt?.used ? painter.fillatt : this.createAttFill(o_fill); - let lineatt; - if (!draw_line && !draw_error && !draw_marker) { - lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); - if (lineatt.empty()) lineatt = null; + if (ncols === 1) + ++i; + else + i = ii; + + const lopt = entry.fOption.toLowerCase(), + icol = i % ncols, irow = (i - icol) / ncols, + x0 = Math.round(column_pos[icol]), + y0 = Math.round(padding_y + irow * (row_height + gap_y)), + tpos_x = Math.round(x0 + column_boxwidth), + mid_x = Math.round(x0 + (column_boxwidth - padding_x) / 2), + box_y = Math.round(y0 + row_height * 0.1), + box_height = Math.round(row_height * 0.8), + mid_y = Math.round(y0 + row_height * 0.5), // center line + mo = entry.fObject, + draw_fill = lopt.indexOf('f') !== -1, + draw_line = lopt.indexOf('l') !== -1, + draw_error = lopt.indexOf('e') !== -1, + draw_marker = lopt.indexOf('p') !== -1; + + let o_fill = entry, o_marker = entry, o_line = entry, + painter = null, isany = false; + + if (isObject(mo)) { + if ('fLineColor' in mo) + o_line = mo; + if ('fFillColor' in mo) + o_fill = mo; + if ('fMarkerColor' in mo) + o_marker = mo; + painter = pp.findPainterFor(mo); } - if (!fillatt.empty() || lineatt) { - isany = true; - // box total height is yspace*0.7 - // define x,y as the center of the symbol for this entry - const rect = this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${Math.round(pos_y+step_y*0.1)}v${Math.round(step_y*0.8)}h${tpos_x-2*padding_x-x0}v${-Math.round(step_y*0.8)}z`); - if (!fillatt.empty()) - rect.call(fillatt.func); - if (lineatt) - rect.call(lineatt.func); - } - } + // Draw fill pattern (in a box) + if (draw_fill) { + const fillatt = painter?.fillatt?.used ? painter.fillatt : this.createAttFill(o_fill); + let lineatt; + if (!draw_line && !draw_error && !draw_marker) { + lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); + if (lineatt.empty()) + lineatt = null; + } - // Draw line and/or error (when specified) - if (draw_line || draw_error) { - const lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); - if (!lineatt.empty()) { - isany = true; - if (draw_line) { - this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${mid_y}H${tpos_x - padding_x}`) - .call(lineatt.func); + if (!fillatt.empty() || lineatt) { + isany = true; + // define x,y as the center of the symbol for this entry + this.appendPath(`M${x0},${box_y}v${box_height}h${tpos_x - padding_x - x0}v${-box_height}z`) + .call(fillatt.func) + .call(lineatt ? lineatt.func : () => {}); } - if (draw_error) { - let endcaps = 0, edx = step_y*0.05; - if (isFunc(painter?.getHisto) && painter.options?.ErrorKind === 1) - endcaps = 1; // draw bars for e1 option in histogram - else if (isFunc(painter?.getGraph) && mo?.fLineWidth !== undefined && mo?.fMarkerSize !== undefined) { - endcaps = painter.options?.Ends ?? 1; // deafult is 1 - edx = mo.fLineWidth + gStyle.fEndErrorSize; - if (endcaps > 1) edx = Math.max(edx, mo.fMarkerSize*8*0.66); + } + + // Draw line and/or error (when specified) + if (draw_line || draw_error) { + const lineatt = painter?.lineatt?.used ? painter.lineatt : this.createAttLine(o_line); + if (!lineatt.empty()) { + isany = true; + if (draw_line) { + this.appendPath(`M${x0},${mid_y}h${tpos_x - padding_x - x0}`) + .call(lineatt.func); } + if (draw_error) { + let endcaps = 0, edx = row_height * 0.05; + if (isFunc(painter?.getHisto) && painter.options?.ErrorKind === 1) + endcaps = 1; // draw bars for e1 option in histogram + else if (isFunc(painter?.getGraph) && mo?.fLineWidth !== undefined && mo?.fMarkerSize !== undefined) { + endcaps = painter.options?.Ends ?? 1; // default is 1 + edx = mo.fLineWidth + gStyle.fEndErrorSize; + if (endcaps > 1) + edx = Math.max(edx, mo.fMarkerSize * 8 * 0.66); + } - const eoff = (endcaps === 3) ? 0.03 : 0, - ey1 = Math.round(pos_y+step_y*(0.1 + eoff)), - ey2 = Math.round(pos_y+step_y*(0.9 - eoff)), - edy = Math.round(edx * 0.66); - edx = Math.round(edx); - let path = `M${mid_x},${ey1}V${ey2}`; - switch (endcaps) { - case 1: path += `M${mid_x-edx},${ey1}h${2*edx}M${mid_x-edx},${ey2}h${2*edx}`; break; // bars - case 2: path += `M${mid_x-edx},${ey1+edy}v${-edy}h${2*edx}v${edy}M${mid_x-edx},${ey2-edy}v${edy}h${2*edx}v${-edy}`; break; // ] - case 3: path += `M${mid_x-edx},${ey1}h${2*edx}l${-edx},${-edy}zM${mid_x-edx},${ey2}h${2*edx}l${-edx},${edy}z`; break; // triangle - case 4: path += `M${mid_x-edx},${ey1+edy}l${edx},${-edy}l${edx},${edy}M${mid_x-edx},${ey2-edy}l${edx},${edy}l${edx},${-edy}`; break; // arrow + const eoff = (endcaps === 3) ? 0.2 : 0, + ey1 = Math.round(y0 + row_height * eoff), + ey2 = Math.round(y0 + row_height * (1 - eoff)), + edy = Math.round(edx * 0.66); + edx = Math.round(edx); + let path = `M${mid_x},${ey1}V${ey2}`; + switch (endcaps) { + case 1: path += `M${mid_x - edx},${ey1}h${2 * edx}M${mid_x - edx},${ey2}h${2 * edx}`; break; // bars + case 2: path += `M${mid_x - edx},${ey1 + edy}v${-edy}h${2 * edx}v${edy}M${mid_x - edx},${ey2 - edy}v${edy}h${2 * edx}v${-edy}`; break; // ] + case 3: path += `M${mid_x - edx},${ey1}h${2 * edx}l${-edx},${-edy}zM${mid_x - edx},${ey2}h${2 * edx}l${-edx},${edy}z`; break; // triangle + case 4: path += `M${mid_x - edx},${ey1 + edy}l${edx},${-edy}l${edx},${edy}M${mid_x - edx},${ey2 - edy}l${edx},${edy}l${edx},${-edy}`; break; // arrow + } + this.appendPath(path) + .call(lineatt.func) + .style('fill', endcaps > 1 ? 'none' : null); } - this.draw_g.append('svg:path') - .attr('d', path) - .call(lineatt.func) - .style('fill', endcaps > 1 ? 'none' : null); } } - } - // Draw Polymarker - if (draw_marker) { - const marker = painter?.markeratt?.used ? painter.markeratt : this.createAttMarker(o_marker); - if (!marker.empty()) { - isany = true; - this.draw_g - .append('svg:path') - .attr('d', marker.create((x0 + tpos_x)/2, mid_y)) - .call(marker.func); + // Draw Poly marker + if (draw_marker) { + const marker = painter?.markeratt?.used ? painter.markeratt : this.createAttMarker(o_marker); + if (!marker.empty()) { + isany = true; + this.appendPath(marker.create(mid_x, mid_y)) + .call(marker.func); + } } - } - - // special case - nothing draw, try to show rect with line attributes - if (!isany && painter?.lineatt && !painter.lineatt.empty()) { - this.draw_g.append('svg:path') - .attr('d', `M${x0 + padding_x},${Math.round(pos_y+step_y*0.1)}v${Math.round(step_y*0.8)}h${tpos_x-2*padding_x-x0}v${-Math.round(step_y*0.8)}z`) - .style('fill', 'none') - .call(painter.lineatt.func); - } - let pos_x = tpos_x; - if (isStr(lopt) && (lopt.toLowerCase() !== 'h')) - any_opt = true; - else if (!any_opt) - pos_x = x0 + padding_x; - - if (entry.fLabel) { - let lbl_g = this.draw_g; - const textatt = this.createAttText({ attr: entry, std: false, attr_alt: legend }); - if (custom_textg) { - lbl_g = this.draw_g.append('svg:g'); - const entry_font_size = textatt.getSize(pp.getPadHeight()); - this.startTextDrawing(textatt.font, entry_font_size, lbl_g, max_font_size); + // special case - nothing draw, try to show rect with line attributes + if (!isany && painter?.lineatt && !painter.lineatt.empty()) { + this.appendPath(`M${x0},${box_y}v${box_height}h${tpos_x - padding_x - x0}v${-box_height}z`) + .style('fill', 'none') + .call(painter.lineatt.func); } - this.drawText({ draw_g: lbl_g, align: textatt.align, x: pos_x, y: pos_y, - scale: (custom_textg && !entry.fTextSize) || !legend.fTextSize, - width: x0+column_width-pos_x-padding_x, height: step_y, - text: entry.fLabel, color: textatt.color }); - - if (custom_textg) - text_promises.push(this.finishTextDrawing(lbl_g)); + let pos_x = tpos_x; + if (isStr(lopt) && (lopt.toLowerCase() !== 'h')) + any_opt = true; + else if (!any_opt) + pos_x = x0; + + if (entry.fLabel) { + const textatt = this.createAttText({ attr: entry, std: false, attr_alt: legend }), + arg = { + draw_g: this.getG(), align: textatt.align, + x: pos_x, width: Math.round(column_pos[icol + 1] - pos_x), + y: y0, height: Math.round(row_height), + scale: (custom_textg && !entry.fTextSize) || !legend.fTextSize, + text: entry.fLabel, color: textatt.color + }; + if (custom_textg) { + arg.draw_g = this.getG().append('svg:g'); + text_promises.push(this.startTextDrawingAsync(textatt.font, textatt.getSize(pp), arg.draw_g, max_font_size) + .then(() => this.drawText(arg)) + .then(() => this.finishTextDrawing(arg.draw_g))); + } else + this.drawText(arg); + } } - } - if (any_text && !custom_textg) - text_promises.push(this.finishTextDrawing()); + if (any_text && !custom_textg) + text_promises.push(this.finishTextDrawing()); - // rescale after all entries are shown - return Promise.all(text_promises); + // rescale after all entries are shown + return Promise.all(text_promises); + }); } + /** @summary Returns true if palette drawn in vertical direction */ + isPaletteVertical() { return this.#palette_vertical; } + /** @summary draw color palette with axis */ drawPaletteAxis(s_width, s_height, arg) { const palette = this.getObject(), axis = palette.fAxis, + g = this.getG(), can_move = isStr(arg) && (arg.indexOf('can_move') >= 0), postpone_draw = isStr(arg) && (arg.indexOf('postpone') >= 0), cjust = isStr(arg) && (arg.indexOf('cjust') >= 0), + bring_stats_front = isStr(arg) && (arg.indexOf('bring_stats_front') >= 0), pp = this.getPadPainter(), width = pp.getPadWidth(), height = pp.getPadHeight(), pad = pp.getRootPad(true), main = palette.$main_painter || this.getMainPainter(), - framep = this.getFramePainter(), - contour = main.fContour, + fp = this.getFramePainter(), + contour = main.getContour(false), levels = contour?.getLevels(), is_th3 = isFunc(main.getDimension) && (main.getDimension() === 3), - log = (is_th3 ? pad?.fLogv : pad?.fLogz) ?? 0, - draw_palette = main._color_palette, - zaxis = main.getObject()?.fZaxis, + is_scatter = isFunc(main.getZaxis), + log = pad?.fLogv ?? (is_th3 ? false : pad?.fLogz), + draw_palette = main.getHistPalette(), + zaxis = is_scatter ? main.getZaxis() : main.getObject()?.fZaxis, sizek = pad?.fTickz ? 0.35 : 0.7; - let zmin = 0, zmax = 100, gzmin, gzmax, axis_transform = '', axis_second = 0; + let zmin = 0, zmax = 100, gzmin, gzmax, axis_transform, axis_second = 0; + + this.#palette_vertical = (palette.fX2NDC - palette.fX1NDC) < (palette.fY2NDC - palette.fY1NDC); - this._palette_vertical = (palette.fX2NDC - palette.fX1NDC) < (palette.fY2NDC - palette.fY1NDC); + axis.fTickSize = 0.03; // adjust axis ticks size - axis.fTickSize = 0.6 * s_width / width; // adjust axis ticks size if ((typeof zaxis?.fLabelOffset !== 'undefined') && !is_th3) { + axis.fBits = zaxis.fBits & ~EAxisBits.kTickMinus & ~EAxisBits.kTickPlus; axis.fTitle = zaxis.fTitle; + axis.fTickSize = zaxis.fTickLength; axis.fTitleSize = zaxis.fTitleSize; axis.fTitleOffset = zaxis.fTitleOffset; axis.fTextColor = zaxis.fTitleColor; @@ -856,129 +1046,177 @@ class TPavePainter extends ObjectPainter { axis.fLabelColor = zaxis.fLabelColor; axis.fLabelFont = zaxis.fLabelFont; axis.fLabelOffset = zaxis.fLabelOffset; - this.z_handle.setHistPainter(main, 'z'); + this.z_handle.setHistPainter(main, is_scatter ? 'hist#z' : 'z'); this.z_handle.source_axis = zaxis; } - if (contour && framep && !is_th3) { - if ((framep.zmin !== undefined) && (framep.zmax !== undefined) && (framep.zmin !== framep.zmax)) { - gzmin = framep.zmin; - gzmax = framep.zmax; - zmin = framep.zoom_zmin; - zmax = framep.zoom_zmax; - if (zmin === zmax) { zmin = gzmin; zmax = gzmax; } + if (contour && fp && !is_th3) { + if ((fp.zmin !== undefined) && (fp.zmax !== undefined) && (fp.zmin !== fp.zmax)) { + gzmin = fp.zmin; + gzmax = fp.zmax; + zmin = fp.zoom_zmin; + zmax = fp.zoom_zmax; + if (zmin === zmax) { + zmin = gzmin; + zmax = gzmax; + } } else { - zmin = levels[0]; - zmax = levels[levels.length-1]; + zmin = levels.at(0); + zmax = levels.at(-1); } - // zmin = Math.min(levels[0], framep.zmin); - // zmax = Math.max(levels[levels.length-1], framep.zmax); } else if ((main.gmaxbin !== undefined) && (main.gminbin !== undefined)) { // this is case of TH2 (needs only for size adjustment) - zmin = main.gminbin; zmax = main.gmaxbin; + zmin = main.gminbin; + zmax = main.gmaxbin; } else if ((main.hmin !== undefined) && (main.hmax !== undefined)) { // this is case of TH1 - zmin = main.hmin; zmax = main.hmax; + zmin = main.hmin; + zmax = main.hmax; } - this.draw_g.selectAll('rect').style('fill', 'white'); + g.selectAll('rect').style('fill', 'white'); if ((gzmin === undefined) || (gzmax === undefined) || (gzmin === gzmax)) { - gzmin = zmin; gzmax = zmax; + gzmin = zmin; + gzmax = zmax; } - if (this._palette_vertical) { - this._swap_side = palette.fX2NDC < 0.5; - this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, true, [0, s_height], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_width*sizek), swap_side: this._swap_side }); - axis_transform = this._swap_side ? null : `translate(${s_width})`; - if (pad?.fTickz) axis_second = this._swap_side ? s_width : -s_width; + if (this.#palette_vertical) { + this.#swap_side = palette.fX2NDC < 0.5; + axis.fChopt = 'S+' + (this.#swap_side ? 'R' : 'L'); // clearly configure text align + this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, true, [0, s_height], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_width * sizek), swap_side: this.#swap_side, minposbin: main.gminposbin }); + axis_transform = this.#swap_side ? null : `translate(${s_width})`; + if (pad?.fTickz) + axis_second = this.#swap_side ? s_width : -s_width; } else { - this._swap_side = palette.fY1NDC > 0.5; - this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, false, [0, s_width], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_height*sizek), swap_side: this._swap_side }); - axis_transform = this._swap_side ? null : `translate(0,${s_height})`; - if (pad?.fTickz) axis_second = this._swap_side ? s_height : -s_height; + this.#swap_side = palette.fY1NDC > 0.5; + axis.fChopt = 'S+'; + this.z_handle.configureAxis('zaxis', gzmin, gzmax, zmin, zmax, false, [0, s_width], { log, fixed_ticks: cjust ? levels : null, maxTickSize: Math.round(s_height * sizek), swap_side: this.#swap_side, minposbin: main.gminposbin }); + axis_transform = this.#swap_side ? null : `translate(0,${s_height})`; + if (pad?.fTickz) + axis_second = this.#swap_side ? s_height : -s_height; } if (!contour || !draw_palette || postpone_draw) { // we need such rect to correctly calculate size - this.draw_g.append('svg:path') - .attr('d', `M0,0H${s_width}V${s_height}H0Z`) - .style('fill', 'white'); + this.appendPath(`M0,0H${s_width}V${s_height}H0Z`) + .style('fill', 'white'); } else { - for (let i = 0; i < levels.length-1; ++i) { + for (let i = 0; i < levels.length - 1; ++i) { let z0 = Math.round(this.z_handle.gr(levels[i])), - z1 = Math.round(this.z_handle.gr(levels[i+1])), - lvl = (levels[i]+levels[i+1])/2, d; + z1 = Math.round(this.z_handle.gr(levels[i + 1])), + portion = 0.5, d; + + // when not full range fit to the drawn range, + // calculate portion value that it approximately in the + // middle of the still visible area - if (this._palette_vertical) { - if ((z1 >= s_height) || (z0 < 0)) continue; + if (this.#palette_vertical) { + if ((z1 >= s_height) || (z0 < 0)) + continue; z0 += 1; // ensure correct gap filling between colors if (z0 > s_height) { + if (z0 > z1 + 1) + portion = 0.5 * (s_height - z1) / (z0 - z1 - 1); z0 = s_height; - lvl = levels[i]*0.001+levels[i+1]*0.999; + if (z1 < 0) + z1 = 0; } else if (z1 < 0) { + if (z0 > 1) + portion = 1 - 0.5 * z0 / (z0 - z1 - 1); z1 = 0; - lvl = levels[i]*0.999+levels[i+1]*0.001; } d = `M0,${z1}H${s_width}V${z0}H0Z`; } else { - if ((z0 >= s_width) || (z1 < 0)) continue; + if ((z0 >= s_width) || (z1 < 0)) + continue; z1 += 1; // ensure correct gap filling between colors if (z1 > s_width) { + if (z1 > z0 + 1) + portion = 1 - 0.5 * (s_width - z0) / (z1 - z0 - 1); z1 = s_width; - lvl = levels[i]*0.999+levels[i+1]*0.001; + if (z0 < 0) + z0 = 0; } else if (z0 < 0) { + if (z1 > 1) + portion = 0.5 * (z1 - 1) / (z1 - z0 - 1); z0 = 0; - lvl = levels[i]*0.001+levels[i+1]*0.999; } d = `M${z0},0V${s_height}H${z1}V0Z`; } - const col = contour.getPaletteColor(draw_palette, lvl); - if (!col) continue; + const lvl = levels[i] * portion + levels[i + 1] * (1 - portion), + col = contour.getPaletteColor(draw_palette, lvl); + if (!col) + continue; - const r = this.draw_g.append('svg:path') - .attr('d', d) - .style('fill', col) - .property('fill0', col) - .property('fill1', d3_rgb(col).darker(0.5).formatHex()); + // console.log('z0, z1', z0, z1, 'height', s_height, 'col', col, 'portion', portion) + + const r = this.appendPath(d) + .style('fill', col) + .property('fill0', col) + .property('fill1', d3_rgb(col).darker(0.5).formatRgb()); + + if (this.isBatchMode()) + continue; if (this.isTooltipAllowed()) { r.on('mouseover', function() { d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill1')); }).on('mouseout', function() { d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill0')); - }).append('svg:title').text(levels[i].toFixed(2) + ' - ' + levels[i+1].toFixed(2)); + }).append('svg:title').text(this.z_handle.axisAsText(levels[i]) + ' - ' + this.z_handle.axisAsText(levels[i + 1])); } if (settings.Zooming) - r.on('dblclick', () => this.getFramePainter().unzoom('z')); + r.on('dblclick', () => this.getFramePainter().unzoomSingle('z')); } } - return this.z_handle.drawAxis(this.draw_g, s_width, s_height, axis_transform, axis_second).then(() => { - if (can_move && ('getBoundingClientRect' in this.draw_g.node())) { - const rect = this.draw_g.node().getBoundingClientRect(); + if (bring_stats_front) + this.getPadPainter()?.findPainterFor(null, '', clTPaveStats)?.bringToFront(); + + return this.z_handle.drawAxis(g, s_width, s_height, axis_transform, axis_second).then(() => { + let rect; + if (can_move) { + if (settings.ApproxTextSize || isNodeJs()) { + // for batch testing provide approx estimation + rect = { x: this.#pave_x, y: this.#pave_y, width: s_width, height: s_height }; + const fsz = this.z_handle.labelsFont?.size || 14; + if (this.#palette_vertical) { + const dx = (this.z_handle._maxlbllen || 3) * 0.6 * fsz; + rect.width += dx; + if (this.#swap_side) + rect.x -= dx; + } else { + rect.height += fsz; + if (this.#swap_side) + rect.y -= fsz; + } + } else if ('getBoundingClientRect' in g.node()) + rect = g.node().getBoundingClientRect(); + } + if (!rect) + return this; - if (this._palette_vertical) { - const shift = (this._pave_x + parseInt(rect.width)) - Math.round(0.995*width) + 3; + if (this.#palette_vertical) { + const shift = (this.#pave_x + parseInt(rect.width)) - Math.round(0.995 * width) + 3; - if (shift > 0) { - this._pave_x -= shift; - makeTranslate(this.draw_g, this._pave_x, this._pave_y); - palette.fX1NDC -= shift/width; - palette.fX2NDC -= shift/width; - } - } else { - const shift = Math.round((1.05 - gStyle.fTitleY)*height) - rect.y; - if (shift > 0) { - this._pave_y += shift; - makeTranslate(this.draw_g, this._pave_x, this._pave_y); - palette.fY1NDC -= shift/height; - palette.fY2NDC -= shift/height; - } + if (shift > 0) { + this.#pave_x -= shift; + makeTranslate(g, this.#pave_x, this.#pave_y); + palette.fX1NDC -= shift / width; + palette.fX2NDC -= shift / width; + } + } else { + const shift = Math.round((1.05 - gStyle.fTitleY) * height) - rect.y; + if (shift > 0) { + this.#pave_y += shift; + makeTranslate(g, this.#pave_x, this.#pave_y); + palette.fY1NDC -= shift / height; + palette.fY2NDC -= shift / height; } } @@ -991,21 +1229,23 @@ class TPavePainter extends ObjectPainter { let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect = null; const moveRectSel = evnt => { - if (!doing_zoom) return; + if (!doing_zoom) + return; evnt.preventDefault(); - const m = d3_pointer(evnt, this.draw_g.node()); - if (this._palette_vertical) { + const m = d3_pointer(evnt, this.getG().node()); + if (this.#palette_vertical) { sel2 = Math.min(Math.max(m[1], 0), s_height); zoom_rect.attr('y', Math.min(sel1, sel2)) - .attr('height', Math.abs(sel2-sel1)); + .attr('height', Math.abs(sel2 - sel1)); } else { sel2 = Math.min(Math.max(m[0], 0), s_width); zoom_rect.attr('x', Math.min(sel1, sel2)) - .attr('width', Math.abs(sel2-sel1)); + .attr('width', Math.abs(sel2 - sel1)); } }, endRectSel = evnt => { - if (!doing_zoom) return; + if (!doing_zoom) + return; evnt.preventDefault(); d3_select(window).on('mousemove.colzoomRect', null) @@ -1014,22 +1254,24 @@ class TPavePainter extends ObjectPainter { zoom_rect = null; doing_zoom = false; - const z = this.z_handle.gr, z1 = z.invert(sel1), z2 = z.invert(sel2); + const z1 = this.z_handle.revertPoint(sel1), + z2 = this.z_handle.revertPoint(sel2); - this.getFramePainter().zoom('z', Math.min(z1, z2), Math.max(z1, z2)); + this.getFramePainter().zoomSingle('z', Math.min(z1, z2), Math.max(z1, z2), true); }, startRectSel = evnt => { // ignore when touch selection is activated - if (doing_zoom) return; + if (doing_zoom) + return; doing_zoom = true; evnt.preventDefault(); evnt.stopPropagation(); - const origin = d3_pointer(evnt, this.draw_g.node()); + const origin = d3_pointer(evnt, this.getG().node()); - zoom_rect = this.draw_g.append('svg:rect').attr('id', 'colzoomRect').call(addHighlightStyle, true); + zoom_rect = this.getG().append('svg:rect').attr('id', 'colzoomRect').call(addHighlightStyle, true); - if (this._palette_vertical) { + if (this.#palette_vertical) { sel1 = sel2 = origin[1]; zoom_rect.attr('x', '0') .attr('width', s_width) @@ -1048,25 +1290,78 @@ class TPavePainter extends ObjectPainter { }; if (settings.Zooming) { - this.draw_g.selectAll('.axis_zoom') + this.getG().selectAll('.axis_zoom') .on('mousedown', startRectSel) - .on('dblclick', () => this.getFramePainter().unzoom('z')); + .on('dblclick', () => this.getFramePainter().zoomSingle('z', 0, 0, true)); } if (settings.ZoomWheel) { - this.draw_g.on('wheel', evnt => { - const pos = d3_pointer(evnt, this.draw_g.node()), - coord = this._palette_vertical ? (1 - pos[1] / s_height) : pos[0] / s_width, + this.getG().on('wheel', evnt => { + const pos = d3_pointer(evnt, this.getG().node()), + coord = this.#palette_vertical ? (1 - pos[1] / s_height) : pos[0] / s_width, item = this.z_handle.analyzeWheelEvent(evnt, coord); if (item?.changed) - this.getFramePainter().zoom('z', item.min, item.max); + this.getFramePainter().zoomSingle('z', item.min, item.max, true); }); - } + } } /** @summary Fill context menu items for the TPave object */ fillContextMenuItems(menu) { - const pave = this.getObject(); + const pave = this.getObject(), + set_opt = this.isStats() ? 'SetOption' : 'SetDrawOption'; + + menu.sub('Shadow'); + menu.addSizeMenu('size', 0, 12, 1, pave.fBorderSize, arg => { + pave.fBorderSize = arg; + this.interactiveRedraw(true, `exec:SetBorderSize(${arg})`); + }); + menu.addColorMenu('color', pave.fShadowColor, arg => { + pave.fShadowColor = arg; + this.interactiveRedraw(true, getColorExec(arg, 'SetShadowColor')); + }); + const posarr = ['nb', 'tr', 'tl', 'br', 'bl']; + let value = '', opt = this.getPaveDrawOption(), remain = opt; + posarr.forEach(nn => { + const p = remain.indexOf(nn); + if ((p >= 0) && !value) { + value = nn; + remain = remain.slice(0, p) + remain.slice(p + nn.length); + } + }); + menu.addSelectMenu('positon', posarr, value || 'nb', arg => { + arg += remain; + this.setPaveDrawOption(arg); + this.interactiveRedraw(true, `exec:${set_opt}("${arg}")`); + }, 'Direction of pave shadow or nb - off'); + menu.endsub(); + + menu.sub('Corner'); + const parc = opt.toLowerCase().indexOf('arc'); + menu.addchk(parc >= 0, 'arc', flag => { + if (flag) + opt += ' arc'; + else + opt = opt.slice(0, parc) + opt.slice(parc + 3); + this.setPaveDrawOption(opt); + this.interactiveRedraw(true, `exec:${set_opt}("${opt}")`); + }, 'Usage of ARC draw option'); + menu.addSizeMenu('radius', 0, 0.2, 0.02, pave.fCornerRadius, val => { + pave.fCornerRadius = val; + this.interactiveRedraw(true, `exec:SetCornerRadius(${val})`); + }, 'Corner radius when ARC is enabled'); + menu.endsub(); + + if (this.isStats() || this.isPaveText() || this.isPavesText()) { + menu.add('Label', () => menu.input('Enter new label', pave.fLabel).then(lbl => { + pave.fLabel = lbl; + this.interactiveRedraw('pad', `exec:SetLabel("${lbl}")`); + })); + menu.addSizeMenu('Margin', 0, 0.2, 0.02, pave.fMargin, val => { + pave.fMargin = val; + this.interactiveRedraw(true, `exec:SetMargin(${val})`); + }); + } if (this.isStats()) { menu.add('Default position', () => { @@ -1087,33 +1382,40 @@ class TPavePainter extends ObjectPainter { gStyle.fStatTextColor = pave.fTextColor; gStyle.fStatFontSize = pave.fTextSize; gStyle.fStatFont = pave.fTextFont; - }, 'Store stats position and graphical attributes to gStyle'); + gStyle.fFitFormat = pave.fFitFormat; + gStyle.fStatFormat = pave.fStatFormat; + gStyle.fOptStat = pave.fOptStat; + gStyle.fOptFit = pave.fOptFit; + }, 'Store stats attributes to gStyle'); + + menu.separator(); menu.add('SetStatFormat', () => { menu.input('Enter StatFormat', pave.fStatFormat).then(fmt => { - if (!fmt) return; - pave.fStatFormat = fmt; - this.interactiveRedraw(true, `exec:SetStatFormat("${fmt}")`); + if (fmt) { + pave.fStatFormat = fmt; + this.interactiveRedraw(true, `exec:SetStatFormat("${fmt}")`); + } }); }); menu.add('SetFitFormat', () => { menu.input('Enter FitFormat', pave.fFitFormat).then(fmt => { - if (!fmt) return; - pave.fFitFormat = fmt; - this.interactiveRedraw(true, `exec:SetFitFormat("${fmt}")`); + if (fmt) { + pave.fFitFormat = fmt; + this.interactiveRedraw(true, `exec:SetFitFormat("${fmt}")`); + } }); }); - menu.add('separator'); - menu.add('sub:SetOptStat', () => { + menu.sub('SetOptStat', () => { menu.input('Enter OptStat', pave.fOptStat, 'int').then(fmt => { pave.fOptStat = fmt; this.interactiveRedraw(true, `exec:SetOptStat(${fmt})`); }); }); const addStatOpt = (pos, name) => { - let opt = (pos < 10) ? pave.fOptStat : pave.fOptFit; - opt = parseInt(parseInt(opt) / parseInt(Math.pow(10, pos % 10))) % 10; - menu.addchk(opt, name, opt * 100 + pos, arg => { + let sopt = (pos < 10) ? pave.fOptStat : pave.fOptFit; + sopt = parseInt(parseInt(sopt) / parseInt(Math.pow(10, pos % 10))) % 10; + menu.addchk(sopt, name, sopt * 100 + pos, arg => { const oldopt = parseInt(arg / 100); let newopt = (arg % 100 < 10) ? pave.fOptStat : pave.fOptFit; newopt -= (oldopt > 0 ? oldopt : -1) * parseInt(Math.pow(10, arg % 10)); @@ -1136,9 +1438,9 @@ class TPavePainter extends ObjectPainter { addStatOpt(6, 'Integral'); addStatOpt(7, 'Skewness'); addStatOpt(8, 'Kurtosis'); - menu.add('endsub:'); + menu.endsub(); - menu.add('sub:SetOptFit', () => { + menu.sub('SetOptFit', () => { menu.input('Enter OptStat', pave.fOptFit, 'int').then(fmt => { pave.fOptFit = fmt; this.interactiveRedraw(true, `exec:SetOptFit(${fmt})`); @@ -1148,36 +1450,58 @@ class TPavePainter extends ObjectPainter { addStatOpt(11, 'Par errors'); addStatOpt(12, 'Chi square / NDF'); addStatOpt(13, 'Probability'); - menu.add('endsub:'); + menu.endsub(); + + menu.separator(); + } else if (this.isPaveText() || this.isPavesText()) { + if (this.isPavesText()) { + menu.addSizeMenu('Paves', 1, 10, 1, pave.fNpaves, val => { + pave.fNpaves = val; + this.interactiveRedraw(true, `exec:SetNpaves(${val})`); + }); + } - menu.add('separator'); + if (this.isTitle()) { + menu.add('Default position', () => { + pave.fX1NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX - gStyle.fTitleW / 2 : gStyle.fPadLeftMargin; + pave.fY1NDC = gStyle.fTitleY - Math.min(gStyle.fTitleFontSize * 1.1, 0.06); + pave.fX2NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX + gStyle.fTitleW / 2 : 1 - gStyle.fPadRightMargin; + pave.fY2NDC = gStyle.fTitleY; + pave.fInit = 1; + this.interactiveRedraw(true, 'pave_moved'); + }); + + menu.add('Save to gStyle', () => { + gStyle.fTitleX = (pave.fX2NDC + pave.fX1NDC) / 2; + gStyle.fTitleY = pave.fY2NDC; + this.fillatt?.saveToStyle('fTitleColor', 'fTitleStyle'); + gStyle.fTitleTextColor = pave.fTextColor; + gStyle.fTitleFontSize = pave.fTextSize; + gStyle.fTitleFont = pave.fTextFont; + }, 'Store title position and graphical attributes to gStyle'); + } } else if (pave._typename === clTLegend) { + menu.sub('Legend'); menu.add('Autoplace', () => { this.autoPlaceLegend(pave, this.getPadPainter()?.getRootPad(true), true).then(res => { - if (res) this.interactiveRedraw(true, 'pave_moved'); + if (res) + this.interactiveRedraw(true, 'pave_moved'); }); }); - } else if (pave.fName === kTitle) { - menu.add('Default position', () => { - pave.fX1NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX - gStyle.fTitleW/2 : gStyle.fPadLeftMargin; - pave.fY1NDC = gStyle.fTitleY - Math.min(gStyle.fTitleFontSize*1.1, 0.06); - pave.fX2NDC = gStyle.fTitleW > 0 ? gStyle.fTitleX + gStyle.fTitleW/2 : 1 - gStyle.fPadRightMargin; - pave.fY2NDC = gStyle.fTitleY; - pave.fInit = 1; - this.interactiveRedraw(true, 'pave_moved'); - }); - - menu.add('Save to gStyle', () => { - gStyle.fTitleX = (pave.fX2NDC + pave.fX1NDC)/2; - gStyle.fTitleY = pave.fY2NDC; - this.fillatt?.saveToStyle('fTitleColor', 'fTitleStyle'); - gStyle.fTitleTextColor = pave.fTextColor; - gStyle.fTitleFontSize = pave.fTextSize; - gStyle.fTitleFont = pave.fTextFont; - }, 'Store title position and graphical attributes to gStyle'); + menu.addSizeMenu('Entry separation', 0, 1, 0.1, pave.fEntrySeparation, v => { + pave.fEntrySeparation = v; + this.interactiveRedraw(true, `exec:SetEntrySeparation(${v})`); + }, 'Vertical entries separation, meaningful values between 0 and 1'); + menu.addSizeMenu('Columns separation', 0, 1, 0.1, pave.fColumnSeparation, v => { + pave.fColumnSeparation = v; + this.interactiveRedraw(true, `exec:SetColumnSeparation(${v})`); + }, 'Horizontal columns separation, meaningful values between 0 and 1'); + menu.addSizeMenu('Num columns', 1, 7, 1, pave.fNColumns, v => { + pave.fNColumns = v; + this.interactiveRedraw(true, `exec:SetNColumns(${v})`); + }, 'Number of columns in the legend'); + menu.endsub(); } - - menu.add('Bring to front', () => this.bringToFront(!this.isStats() && !this.z_handle)); } /** @summary Show pave context menu */ @@ -1185,9 +1509,9 @@ class TPavePainter extends ObjectPainter { if (this.z_handle) { const fp = this.getFramePainter(); if (isFunc(fp?.showContextMenu)) - fp.showContextMenu('pal', evnt); + fp.showContextMenu('pal', evnt); } else - showPainterMenu(evnt, this, this.isTitle() ? kTitle : undefined); + showPainterMenu(evnt, this); } /** @summary Returns true when stat box is drawn */ @@ -1195,9 +1519,24 @@ class TPavePainter extends ObjectPainter { return this.matchObjectType(clTPaveStats); } + /** @summary Returns true when stat box is drawn */ + isPaveText() { + return this.matchObjectType(clTPaveText); + } + + /** @summary Returns true when stat box is drawn */ + isPavesText() { + return this.matchObjectType(clTPavesText); + } + + /** @summary Returns true when stat box is drawn */ + isPalette() { + return this.matchObjectType(clTPaletteAxis); + } + /** @summary Returns true when title is drawn */ isTitle() { - return this.matchObjectType(clTPaveText) && (this.getObject()?.fName === kTitle); + return this.isPaveText() && (this.getObject()?.fName === kTitle); } /** @summary Clear text in the pave */ @@ -1210,83 +1549,107 @@ class TPavePainter extends ObjectPainter { this.getObject().AddText(txt); } + /** @summary Remade version of THistPainter::GetBestFormat + * @private */ + getBestFormat(tv, e) { + const ie = tv.indexOf('e'), id = tv.indexOf('.'); + + if (ie >= 0) + return (tv.indexOf('+') < 0) || (e >= 1) ? `.${ie - id - 1}e` : '.1f'; + if (id < 0) + return '.1f'; + + return `.${tv.length - id - 1}f`; + } + /** @summary Fill function parameters */ fillFunctionStat(f1, dofit, ndim = 1) { - this._has_fit = false; + this.#has_fit = dofit && f1; - if (!dofit || !f1) return false; + if (!this.#has_fit) + return false; - this._has_fit = true; - this._fit_dim = ndim; - this._fit_cnt = 0; + this.#fit_dim = ndim; + this.#fit_cnt = 0; const print_fval = (ndim === 1) ? dofit % 10 : 1, - print_ferrors = (ndim === 1) ? Math.floor(dofit/10) % 10 : 1, - print_fchi2 = (ndim === 1) ? Math.floor(dofit/100) % 10 : 1, - print_fprob = (ndim === 1) ? Math.floor(dofit/1000) % 10 : 0; + print_ferrors = (ndim === 1) ? Math.floor(dofit / 10) % 10 : 1, + print_fchi2 = (ndim === 1) ? Math.floor(dofit / 100) % 10 : 1, + print_fprob = (ndim === 1) ? Math.floor(dofit / 1000) % 10 : 0; if (print_fchi2) { this.addText('#chi^{2} / ndf = ' + this.format(f1.fChisquare, 'fit') + ' / ' + f1.fNDF); - this._fit_cnt++; + this.#fit_cnt++; } if (print_fprob) { this.addText('Prob = ' + this.format(Prob(f1.fChisquare, f1.fNDF))); - this._fit_cnt++; + this.#fit_cnt++; } if (print_fval) { for (let n = 0; n < f1.GetNumPars(); ++n) { const parname = f1.GetParName(n); let parvalue = f1.GetParValue(n), parerr = f1.GetParError(n); - - parvalue = (parvalue === undefined) ? '<not avail>' : this.format(Number(parvalue), 'fit'); - if (parerr !== undefined) { - parerr = this.format(parerr, 'last'); - if ((Number(parerr) === 0) && (f1.GetParError(n) !== 0)) - parerr = this.format(f1.GetParError(n), '4.2g'); + if (parvalue === undefined) { + parvalue = '<not avail>'; + parerr = null; + } else { + parvalue = this.format(Number(parvalue), 'fit'); + if (print_ferrors && (parerr !== undefined)) { + parerr = floatToString(parerr, this.getBestFormat(parvalue, parerr)); + if (!Number(parerr) && f1.GetParError(n)) + parerr = floatToString(f1.GetParError(n), '4.2g'); + } } if (print_ferrors && parerr) this.addText(`${parname} = ${parvalue} #pm ${parerr}`); else this.addText(`${parname} = ${parvalue}`); - this._fit_cnt++; + this.#fit_cnt++; } } - return true; } /** @summary Is dummy pos of the pave painter */ isDummyPos(p) { - if (!p) return true; - - return !p.fInit && !p.fX1 && !p.fX2 && !p.fY1 && !p.fY2 && !p.fX1NDC && !p.fX2NDC && !p.fY1NDC && !p.fY2NDC; + return !p ? true : !p.fInit && !p.fX1 && !p.fX2 && !p.fY1 && !p.fY2 && !p.fX1NDC && !p.fX2NDC && !p.fY1NDC && !p.fY2NDC; } /** @summary Update TPave object */ updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; - const pave = this.getObject(); + const pave = this.getObject(), + is_auto = opt === kAutoPlace; - if (!pave.modified_NDC && !this.isDummyPos(obj)) { + if (!pave.$modifiedNDC && !this.isDummyPos(obj)) { // if position was not modified interactively, update from source object if (this.stored && !obj.fInit && (this.stored.fX1 === obj.fX1) && (this.stored.fX2 === obj.fX2) && (this.stored.fY1 === obj.fY1) && (this.stored.fY2 === obj.fY2)) { // case when source object not initialized and original coordinates are not changed // take over only modified NDC coordinate, used in tutorials/graphics/canvas.C - if (this.stored.fX1NDC !== obj.fX1NDC) pave.fX1NDC = obj.fX1NDC; - if (this.stored.fX2NDC !== obj.fX2NDC) pave.fX2NDC = obj.fX2NDC; - if (this.stored.fY1NDC !== obj.fY1NDC) pave.fY1NDC = obj.fY1NDC; - if (this.stored.fY2NDC !== obj.fY2NDC) pave.fY2NDC = obj.fY2NDC; + if (this.stored.fX1NDC !== obj.fX1NDC) + pave.fX1NDC = obj.fX1NDC; + if (this.stored.fX2NDC !== obj.fX2NDC) + pave.fX2NDC = obj.fX2NDC; + if (this.stored.fY1NDC !== obj.fY1NDC) + pave.fY1NDC = obj.fY1NDC; + if (this.stored.fY2NDC !== obj.fY2NDC) + pave.fY2NDC = obj.fY2NDC; } else { pave.fInit = obj.fInit; - pave.fX1 = obj.fX1; pave.fX2 = obj.fX2; - pave.fY1 = obj.fY1; pave.fY2 = obj.fY2; - pave.fX1NDC = obj.fX1NDC; pave.fX2NDC = obj.fX2NDC; - pave.fY1NDC = obj.fY1NDC; pave.fY2NDC = obj.fY2NDC; + pave.fX1 = obj.fX1; + pave.fX2 = obj.fX2; + pave.fY1 = obj.fY1; + pave.fY2 = obj.fY2; + pave.fX1NDC = obj.fX1NDC; + pave.fX2NDC = obj.fX2NDC; + pave.fY1NDC = obj.fY1NDC; + pave.fY2NDC = obj.fY2NDC; } this.stored = Object.assign({}, obj); // store latest coordinates @@ -1306,24 +1669,24 @@ class TPavePainter extends ObjectPainter { case clTDiamond: case clTPaveText: pave.fLines = clone(obj.fLines); - return true; + break; case clTPavesText: pave.fLines = clone(obj.fLines); pave.fNpaves = obj.fNpaves; - return true; + break; case clTPaveLabel: case clTPaveClass: pave.fLabel = obj.fLabel; - return true; + break; case clTPaveStats: pave.fOptStat = obj.fOptStat; pave.fOptFit = obj.fOptFit; - return true; + break; case clTLegend: { const oldprim = pave.fPrimitives; pave.fPrimitives = obj.fPrimitives; pave.fNColumns = obj.fNColumns; - this.AutoPlace = opt === 'autoplace'; + this.AutoPlace = is_auto; if (oldprim?.arr?.length && (oldprim?.arr?.length === pave.fPrimitives?.arr?.length)) { // try to sync object reference, new object does not displayed automatically // in ideal case one should use snapids in the entries @@ -1338,10 +1701,14 @@ class TPavePainter extends ObjectPainter { case clTPaletteAxis: pave.fBorderSize = 1; pave.fShadowColor = 0; - return true; + break; + default: + return false; } - return false; + this.storeDrawOpt(is_auto ? kDefaultDrawOpt : opt); + + return true; } /** @summary redraw pave object */ @@ -1351,12 +1718,46 @@ class TPavePainter extends ObjectPainter { /** @summary cleanup pave painter */ cleanup() { - if (this.z_handle) { - this.z_handle.cleanup(); - delete this.z_handle; + this.z_handle?.cleanup(); + delete this.z_handle; + const pp = this.getObject(); + if (pp) + delete pp.$main_painter; + super.cleanup(); + } + + /** @summary Set position of title + * @private */ + setTitlePosition(pave, text_width, text_height) { + const posx = gStyle.fTitleX, posy = gStyle.fTitleY, + valign = gStyle.fTitleAlign % 10, halign = (gStyle.fTitleAlign - valign) / 10; + let w = gStyle.fTitleW, h = gStyle.fTitleH, need_readjust = false; + + if (h <= 0) { + if (text_height) + h = 1.1 * text_height / this.getPadPainter().getPadHeight(); + else { + h = 0.05; + need_readjust = true; + } } - super.cleanup(); + if (w <= 0) { + if (text_width) + w = Math.min(0.7, 0.02 + text_width / this.getPadPainter().getPadWidth()); + else { + w = 0.5; + need_readjust = true; + } + } + + pave.fX1NDC = halign < 2 ? posx : (halign > 2 ? posx - w : posx - w / 2); + pave.fY1NDC = valign < 2 ? posy : (valign > 2 ? posy - h : posy - h / 2); + pave.fX2NDC = pave.fX1NDC + w; + pave.fY2NDC = pave.fY1NDC + h; + pave.fInit = 1; + + return need_readjust; } /** @summary Returns true if object is supported */ @@ -1368,31 +1769,23 @@ class TPavePainter extends ObjectPainter { /** @summary Draw TPave */ static async draw(dom, pave, opt) { - const painter = new TPavePainter(dom, pave); + const arg_opt = opt, + pos_title = (opt === kPosTitle), + is_auto = (opt === kAutoPlace); + if (pos_title || is_auto || (isStr(opt) && (opt.indexOf(';') >= 0))) + opt = ''; // use default - or stored in TPave itself + + const painter = new TPavePainter(dom, pave, opt); return ensureTCanvas(painter, false).then(() => { - if ((pave.fName === kTitle) && (pave._typename === clTPaveText)) { - const tpainter = painter.getPadPainter().findPainterFor(null, kTitle, clTPaveText); - if (tpainter && (tpainter !== painter)) { - tpainter.removeFromPadPrimitives(); - tpainter.cleanup(); - } else if ((opt === 'postitle') || painter.isDummyPos(pave)) { - const st = gStyle, fp = painter.getFramePainter(); - if (st && fp) { - const midx = st.fTitleX, y2 = st.fTitleY; - let w = st.fTitleW, h = st.fTitleH; - - if (!h) h = (y2 - fp.fY2NDC) * 0.7; - if (!w) w = fp.fX2NDC - fp.fX1NDC; - if (!Number.isFinite(h) || (h <= 0)) h = 0.06; - if (!Number.isFinite(w) || (w <= 0)) w = 0.44; - - pave.fX1NDC = midx - w/2; - pave.fY1NDC = y2 - h; - pave.fX2NDC = midx + w/2; - pave.fY2NDC = y2; - pave.fInit = 1; - } + if (painter.isTitle()) { + const pp = painter.getPadPainter(), + prev_painter = pp.findPainterFor(null, kTitle, clTPaveText); + if (prev_painter && (prev_painter !== painter)) + pp.removePrimitive(prev_painter); + else if (pos_title || painter.isDummyPos(pave)) { + if (painter.setTitlePosition(pave)) + painter.$postitle = true; } } else if (pave._typename === clTPaletteAxis) { pave.fBorderSize = 1; @@ -1400,46 +1793,85 @@ class TPavePainter extends ObjectPainter { // check some default values of TGaxis object, otherwise axis will not be drawn if (pave.fAxis) { - if (!pave.fAxis.fChopt) pave.fAxis.fChopt = '+'; - if (!pave.fAxis.fNdiv) pave.fAxis.fNdiv = 12; - if (!pave.fAxis.fLabelOffset) pave.fAxis.fLabelOffset = 0.005; + if (!pave.fAxis.fChopt) + pave.fAxis.fChopt = '+'; + if (!pave.fAxis.fNdiv) + pave.fAxis.fNdiv = 12; + if (!pave.fAxis.fLabelOffset) + pave.fAxis.fLabelOffset = 0.005; } - painter.z_handle = new TAxisPainter(dom, pave.fAxis, true); - painter.z_handle.setPadName(painter.getPadName()); + painter.z_handle = new TAxisPainter(painter.getPadPainter(), pave.fAxis, true); painter.UseContextMenu = true; - } + } else if (pave._typename === clTLegend) + painter.AutoPlace = is_auto; - painter.NoFillStats = (opt === 'nofillstats') || (pave.fName !== 'stats'); + painter.NoFillStats = pave.fName !== 'stats'; - switch (pave._typename) { - case clTPaveLabel: - case clTPaveClass: - painter.paveDrawFunc = painter.drawPaveLabel; - break; - case clTPaveStats: - painter.paveDrawFunc = painter.drawPaveStats; - break; - case clTPaveText: - case clTPavesText: - case clTDiamond: - painter.paveDrawFunc = painter.drawPaveText; - break; - case clTLegend: - painter.AutoPlace = (opt === 'autoplace'); - painter.paveDrawFunc = painter.drawLegend; - break; - case clTPaletteAxis: - painter.paveDrawFunc = painter.drawPaletteAxis; - break; - } + return painter.drawPave(arg_opt); + }).then(() => { + const adjust_title = painter.$postitle && painter.$titlebox; + + if (adjust_title) + painter.setTitlePosition(pave, painter.$titlebox.width, painter.$titlebox.height); - return painter.drawPave(opt); + delete painter.$postitle; + delete painter.$titlebox; + + return adjust_title ? painter.drawPave(arg_opt) : painter; }); } } // class TPavePainter -export { TPavePainter }; +/** @summary Draw object title + * @return {Promise} with painter */ +async function drawObjectTitle(painter, first_time, is_enabled, is_draw) { + if (!is_enabled) + return painter; + + const st = gStyle, + obj = painter.getObject(), + pp = painter.getPadPainter(), + draw_title = is_draw && (st.fOptTitle > 0); + + if (first_time) { + let pt = pp.findInPrimitives(kTitle, clTPaveText); + if (pt) { + pt.Clear(); + if (draw_title) + pt.AddText(obj.fTitle); + return painter; + } + + pt = create(clTPaveText); + Object.assign(pt, { + fName: kTitle, fOption: 'blNDC', fFillColor: st.fTitleColor, fFillStyle: st.fTitleStyle, fBorderSize: st.fTitleBorderSize, + fTextFont: st.fTitleFont, fTextSize: st.fTitleFontSize, fTextColor: st.fTitleTextColor, fTextAlign: 22 + }); + + if (draw_title) + pt.AddText(obj.fTitle); + + return TPavePainter.draw(pp, pt, kPosTitle).then(p => { + p?.setSecondaryId(painter, kTitle); + return painter; + }); + } + + const tpainter = pp?.findPainterFor(null, kTitle, clTPaveText), + pt = tpainter?.getObject(); + + if (!tpainter || !pt) + return painter; + + pt.Clear(); + if (draw_title) + pt.AddText(obj.fTitle); + return tpainter.redraw().then(() => painter); +} + + +export { TPavePainter, kPosTitle, drawObjectTitle }; diff --git a/modules/hist/bundle.mjs b/modules/hist/bundle.mjs index a20f12b22..48d8eb277 100644 --- a/modules/hist/bundle.mjs +++ b/modules/hist/bundle.mjs @@ -5,3 +5,4 @@ export { cleanup } from '../base/ObjectPainter.mjs'; export { TH1Painter } from './TH1Painter.mjs'; export { TH2Painter } from './TH2Painter.mjs'; export { TH3Painter } from './TH3Painter.mjs'; +export { THStackPainter } from './THStackPainter.mjs'; diff --git a/modules/hist/hist3d.mjs b/modules/hist/hist3d.mjs index 24f71c32d..719e2aca8 100644 --- a/modules/hist/hist3d.mjs +++ b/modules/hist/hist3d.mjs @@ -1,178 +1,14 @@ -import { constants, isFunc, isStr, getDocument, isNodeJs } from '../core.mjs'; +import { constants, settings, isFunc, getDocument, isNodeJs } from '../core.mjs'; import { rgb as d3_rgb } from '../d3.mjs'; -import { REVISION, DoubleSide, Object3D, Color, Vector2, Vector3, Matrix4, Line3, - BufferGeometry, BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, - LineSegments, LineDashedMaterial, LineBasicMaterial, - Plane, Scene, PerspectiveCamera, OrthographicCamera, DirectionalLight, ShapeUtils } from '../three.mjs'; -import { TextGeometry } from '../three_addons.mjs'; -import { assign3DHandler, disposeThreejsObject, createOrbitControl, - createLineSegments, Box3D, getMaterialArgs, +import { THREE, assign3DHandler, disposeThreejsObject, createOrbitControl, + createLineSegments, Box3D, getMaterialArgs, importThreeJs, createRender3D, beforeRender3D, afterRender3D, getRender3DKind, - cleanupRender3D, HelveticerRegularFont, createSVGRenderer, create3DLineMaterial } from '../base/base3d.mjs'; -import { isPlainText, translateLaTeX, produceLatex } from '../base/latex.mjs'; + cleanupRender3D, createSVGRenderer, create3DLineMaterial } from '../base/base3d.mjs'; +import { createLatexGeometry } from '../base/latex3d.mjs'; import { kCARTESIAN, kPOLAR, kCYLINDRICAL, kSPHERICAL, kRAPIDITY } from '../hist2d/THistPainter.mjs'; import { buildHist2dContour, buildSurf3D } from '../hist2d/TH2Painter.mjs'; -function createTextGeometry(painter, lbl, size) { - if (isPlainText(lbl)) - return new TextGeometry(translateLaTeX(lbl), { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); - - const font_size = size * 100, geoms = []; - let stroke_width = 5; - - class TextParseWrapper { - - constructor(kind, parent) { - this.kind = kind ?? 'g'; - this.childs = []; - this.x = 0; - this.y = 0; - this.font_size = parent?.font_size ?? font_size; - parent?.childs.push(this); - } - - append(kind) { - if (kind === 'svg:g') - return new TextParseWrapper('g', this); - if (kind === 'svg:text') - return new TextParseWrapper('text', this); - if (kind === 'svg:path') - return new TextParseWrapper('path', this); - console.log('should create', kind); - } - - style(name, value) { - // console.log(`style ${name} = ${value}`); - if ((name === 'stroke-width') && value) - stroke_width = Number.parseInt(value); - return this; - } - - translate() { - if (this.geom) { - // special workaround for path elements, while 3d font is exact height, keep some space on the top - // let dy = this.kind === 'path' ? this.font_size*0.002 : 0; - this.geom.translate(this.x, this.y, 0); - } - this.childs.forEach(chld => { - chld.x += this.x; - chld.y += this.y; - chld.translate(); - }); - } - - attr(name, value) { - // console.log(`attr ${name} = ${value}`); - - const get = () => { - if (!value) return ''; - const res = value[0]; - value = value.slice(1); - return res; - }, getN = (skip) => { - let p = 0; - while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) p++; - const res = Number.parseInt(value.slice(0, p)); - value = value.slice(p); - if (skip) get(); - return res; - }; - - if ((name === 'font-size') && value) - this.font_size = Number.parseInt(value); - else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { - const arr = value.slice(value.indexOf('(')+1, value.lastIndexOf(')')).split(','); - this.x += arr[0] ? Number.parseInt(arr[0])*0.01 : 0; - this.y -= arr[1] ? Number.parseInt(arr[1])*0.01 : 0; - } else if ((name === 'x') && (this.kind === 'text')) - this.x += Number.parseInt(value)*0.01; - else if ((name === 'y') && (this.kind === 'text')) - this.y -= Number.parseInt(value)*0.01; - else if ((name === 'd') && (this.kind === 'path')) { - if (get() !== 'M') return console.error('Not starts with M'); - const pnts = []; - let x1 = getN(true), y1 = getN(), next; - - while ((next = get())) { - let x2 = x1, y2 = y1; - switch (next) { - case 'L': x2 = getN(true); y2 = getN(); break; - case 'l': x2 += getN(true); y2 += getN(); break; - case 'H': x2 = getN(); break; - case 'h': x2 += getN(); break; - case 'V': y2 = getN(); break; - case 'v': y2 += getN(); break; - default: console.log('not supported operator', next); - } - - const angle = Math.atan2(y2-y1, x2-x1), - dx = 0.5 * stroke_width * Math.sin(angle), - dy = -0.5 * stroke_width * Math.cos(angle); - - pnts.push(x1-dx, y1-dy, 0, x2-dx, y2-dy, 0, x2+dx, y2+dy, 0, x1-dx, y1-dy, 0, x2+dx, y2+dy, 0, x1+dx, y1+dy, 0); - - x1 = x2; y1 = y2; - } - - const pos = new Float32Array(pnts); - - this.geom = new BufferGeometry(); - this.geom.setAttribute('position', new BufferAttribute(pos, 3)); - this.geom.scale(0.01, -0.01, 0.01); - this.geom.computeVertexNormals(); - - geoms.push(this.geom); - } - return this; - } - - text(v) { - if (this.kind === 'text') { - this.geom = new TextGeometry(v, { font: HelveticerRegularFont, size: Math.round(0.01*this.font_size), height: 0, curveSegments: 5 }); - geoms.push(this.geom); - } - } - -} - - const node = new TextParseWrapper(), - arg = { font_size, latex: 1, x: 0, y: 0, text: lbl, align: ['start', 'top'], fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }; - - produceLatex(painter, node, arg); - - if (!geoms.length) - return new TextGeometry(translateLaTeX(lbl), { font: HelveticerRegularFont, size, height: 0, curveSegments: 5 }); - - node.translate(); // apply translate attributes - - if (geoms.length === 1) - return geoms[0]; - - let total_size = 0; - geoms.forEach(geom => { - total_size += geom.getAttribute('position').array.length; - }); - - const pos = new Float32Array(total_size), - norm = new Float32Array(total_size); - let indx = 0; - - geoms.forEach(geom => { - const p1 = geom.getAttribute('position').array, - n1 = geom.getAttribute('normal').array; - for (let i = 0; i < p1.length; ++i, ++indx) { - pos[indx] = p1[i]; - norm[indx] = n1[i]; - } - }); - - const fullgeom = new BufferGeometry(); - fullgeom.setAttribute('position', new BufferAttribute(pos, 3)); - fullgeom.setAttribute('normal', new BufferAttribute(norm, 3)); - return fullgeom; -} - /** @summary Text 3d axis visibility * @private */ function testAxisVisibility(camera, toplevel, fb = false, bb = false) { @@ -180,12 +16,14 @@ function testAxisVisibility(camera, toplevel, fb = false, bb = false) { if (toplevel?.children) { for (let n = 0; n < toplevel.children.length; ++n) { top = toplevel.children[n]; - if (top.axis_draw) break; + if (top.axis_draw) + break; top = undefined; } } - if (!top) return; + if (!top) + return; if (!camera) { // this is case when axis drawing want to be removed @@ -195,13 +33,17 @@ function testAxisVisibility(camera, toplevel, fb = false, bb = false) { const pos = camera.position; let qudrant = 1; - if ((pos.x < 0) && (pos.y >= 0)) qudrant = 2; - if ((pos.x >= 0) && (pos.y >= 0)) qudrant = 3; - if ((pos.x >= 0) && (pos.y < 0)) qudrant = 4; + if ((pos.x < 0) && (pos.y >= 0)) + qudrant = 2; + else if ((pos.x >= 0) && (pos.y >= 0)) + qudrant = 3; + else if ((pos.x >= 0) && (pos.y < 0)) + qudrant = 4; const testVisible = (id, range) => { - if (id <= qudrant) id += 4; - return (id > qudrant) && (id < qudrant+range); + if (id <= qudrant) + id += 4; + return (id > qudrant) && (id < qudrant + range); }, handleZoomMesh = obj3d => { for (let k = 0; k < obj3d.children?.length; ++k) { if (obj3d.children[k].zoom !== undefined) @@ -221,17 +63,26 @@ function testAxisVisibility(camera, toplevel, fb = false, bb = false) { handleZoomMesh(chld); } else if (chld.xyboxid) { let range = 5, shift = 0; - if (bb && !fb) { range = 3; shift = -2; } else - if (fb && !bb) range = 3; else - if (!fb && !bb) range = (chld.bottom ? 3 : 0); + if (bb && !fb) { + range = 3; + shift = -2; + } else if (fb && !bb) + range = 3; + else if (!fb && !bb) + range = chld.bottom ? 3 : 0; chld.visible = testVisible(chld.xyboxid + shift, range); if (!chld.visible && chld.bottom && bb) chld.visible = testVisible(chld.xyboxid, 3); } else if (chld.zboxid) { let range = 2, shift = 0; - if (fb && bb) range = 5; else - if (bb && !fb) range = 4; else - if (!bb && fb) { shift = -2; range = 4; } + if (fb && bb) + range = 5; + else if (bb && !fb) + range = 4; + else if (!bb && fb) { + shift = -2; + range = 4; + } chld.visible = testVisible(chld.zboxid + shift, range); } } @@ -242,47 +93,47 @@ function convertLegoBuf(painter, pos, binsx, binsy) { if (painter.options.System === kCARTESIAN) return pos; const fp = painter.getFramePainter(); - let kx = 1/fp.size_x3d, ky = 1/fp.size_y3d; + let kx = 1 / fp.size_x3d, ky = 1 / fp.size_y3d; if (binsx && binsy) { - kx *= binsx/(binsx-1); - ky *= binsy/(binsy-1); + kx *= binsx / (binsx - 1); + ky *= binsy / (binsy - 1); } if (painter.options.System === kPOLAR) { for (let i = 0; i < pos.length; i += 3) { const angle = (1 - pos[i] * kx) * Math.PI, - radius = 0.5 + 0.5 * pos[i + 1] * ky; + radius = 0.5 + 0.5 * pos[i + 1] * ky; pos[i] = Math.cos(angle) * radius * fp.size_x3d; - pos[i+1] = Math.sin(angle) * radius * fp.size_y3d; + pos[i + 1] = Math.sin(angle) * radius * fp.size_y3d; } } else if (painter.options.System === kCYLINDRICAL) { for (let i = 0; i < pos.length; i += 3) { const angle = (1 - pos[i] * kx) * Math.PI, - radius = 0.5 + pos[i + 2]/fp.size_z3d/4; + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; pos[i] = Math.cos(angle) * radius * fp.size_x3d; - pos[i+2] = (1 + Math.sin(angle) * radius) * fp.size_z3d; + pos[i + 2] = (1 + Math.sin(angle) * radius) * fp.size_z3d; } } else if (painter.options.System === kSPHERICAL) { for (let i = 0; i < pos.length; i += 3) { const phi = (1 + pos[i] * kx) * Math.PI, - theta = pos[i+1] * ky * Math.PI, - radius = 0.5 + pos[i+2]/fp.size_z3d/4; + theta = pos[i + 1] * ky * Math.PI, + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; pos[i] = radius * Math.cos(theta) * Math.cos(phi) * fp.size_x3d; - pos[i+1] = radius * Math.cos(theta) * Math.sin(phi) * fp.size_y3d; - pos[i+2] = (1 + radius * Math.sin(theta)) * fp.size_z3d; + pos[i + 1] = radius * Math.cos(theta) * Math.sin(phi) * fp.size_y3d; + pos[i + 2] = (1 + radius * Math.sin(theta)) * fp.size_z3d; } } else if (painter.options.System === kRAPIDITY) { for (let i = 0; i < pos.length; i += 3) { const phi = (1 - pos[i] * kx) * Math.PI, - theta = pos[i+1] * ky * Math.PI, - radius = 0.5 + pos[i+2]/fp.size_z3d/4; + theta = pos[i + 1] * ky * Math.PI, + radius = 0.5 + pos[i + 2] / fp.size_z3d / 4; pos[i] = radius * Math.cos(phi) * fp.size_x3d; - pos[i+1] = radius * Math.sin(theta) / Math.cos(theta) * fp.size_y3d / 2; - pos[i+2] = (1 + radius * Math.sin(phi)) * fp.size_z3d; + pos[i + 1] = radius * Math.sin(theta) / Math.cos(theta) * fp.size_y3d / 2; + pos[i + 2] = (1 + radius * Math.sin(phi)) * fp.size_z3d; } } @@ -290,16 +141,16 @@ function convertLegoBuf(painter, pos, binsx, binsy) { } function createLegoGeom(painter, positions, normals, binsx, binsy) { - const geometry = new BufferGeometry(); + const geometry = new THREE.BufferGeometry(); if (painter.options.System === kCARTESIAN) { - geometry.setAttribute('position', new BufferAttribute(positions, 3)); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); if (normals) - geometry.setAttribute('normal', new BufferAttribute(normals, 3)); + geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3)); else geometry.computeVertexNormals(); } else { convertLegoBuf(painter, positions, binsx, binsy); - geometry.setAttribute('position', new BufferAttribute(positions, 3)); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.computeVertexNormals(); } @@ -314,56 +165,72 @@ function create3DCamera(fp, orthographic) { } if (orthographic) - fp.camera = new OrthographicCamera(-1.3*fp.size_x3d, 1.3*fp.size_x3d, 2.3*fp.size_z3d, -0.7*fp.size_z3d, 0.001, 40*fp.size_z3d); + fp.camera = new THREE.OrthographicCamera(-1.3 * fp.size_x3d, 1.3 * fp.size_x3d, 2.3 * fp.size_z3d, -0.7 * fp.size_z3d, 0.001, 40 * fp.size_z3d); else - fp.camera = new PerspectiveCamera(45, fp.scene_width / fp.scene_height, 1, 40*fp.size_z3d); + fp.camera = new THREE.PerspectiveCamera(45, fp.scene_width / fp.scene_height, 1, 40 * fp.size_z3d); fp.camera.up.set(0, 0, 1); - fp.pointLight = new DirectionalLight(0xffffff, 3); - fp.pointLight.position.set(fp.size_x3d/2, fp.size_y3d/2, fp.size_z3d/2); + fp.pointLight = new THREE.DirectionalLight(0xffffff, 3); + fp.pointLight.position.set(fp.size_x3d / 2, fp.size_y3d / 2, fp.size_z3d / 2); fp.camera.add(fp.pointLight); - fp.lookat = new Vector3(0, 0, orthographic ? 0.3*fp.size_z3d : 0.8*fp.size_z3d); + fp.lookat = new THREE.Vector3(0, 0, orthographic ? 0.3 * fp.size_z3d : 0.8 * fp.size_z3d); fp.scene.add(fp.camera); } -/** @summary Set default camera position +/** @summary Returns camera default position * @private */ -function setCameraPosition(fp, first_time) { - const pad = fp.getPadPainter().getRootPad(true), +function getCameraDefaultPosition(fp, first_time) { + const pad = fp.getPadPainter()?.getRootPad(true), kz = fp.camera.isOrthographicCamera ? 1 : 1.4; - let max3dx = Math.max(0.75*fp.size_x3d, fp.size_z3d), - max3dy = Math.max(0.75*fp.size_y3d, fp.size_z3d); + let max3dx = Math.max(0.75 * fp.size_x3d, fp.size_z3d), + max3dy = Math.max(0.75 * fp.size_y3d, fp.size_z3d), + pos = null; if (first_time) { + pos = new THREE.Vector3(); if (max3dx === max3dy) - fp.camera.position.set(-1.6*max3dx, -3.5*max3dy, kz*fp.size_z3d); + pos.set(-1.6 * max3dx, -3.5 * max3dy, kz * fp.size_z3d); else if (max3dx > max3dy) - fp.camera.position.set(-2*max3dx, -3.5*max3dy, kz*fp.size_z3d); + pos.set(-2 * max3dx, -3.5 * max3dy, kz * fp.size_z3d); else - fp.camera.position.set(-3.5*max3dx, -2*max3dy, kz*fp.size_z3d); + pos.set(-3.5 * max3dx, -2 * max3dy, kz * fp.size_z3d); } if (pad && (first_time || !fp.zoomChangedInteractive())) { if (Number.isFinite(pad.fTheta) && Number.isFinite(pad.fPhi) && ((pad.fTheta !== fp.camera_Theta) || (pad.fPhi !== fp.camera_Phi))) { - fp.camera_Phi = pad.fPhi; - fp.camera_Theta = pad.fTheta; - max3dx = 3*Math.max(fp.size_x3d, fp.size_z3d); - max3dy = 3*Math.max(fp.size_y3d, fp.size_z3d); - const phi = (270-pad.fPhi)/180*Math.PI, theta = (pad.fTheta-10)/180*Math.PI; - fp.camera.position.set(max3dx*Math.cos(phi)*Math.cos(theta), - max3dy*Math.sin(phi)*Math.cos(theta), - fp.size_z3d + (kz-0.9)*(max3dx+max3dy)*Math.sin(theta)); - first_time = true; + if (!pos) + pos = new THREE.Vector3(); + max3dx = 3 * Math.max(fp.size_x3d, fp.size_z3d); + max3dy = 3 * Math.max(fp.size_y3d, fp.size_z3d); + const phi = (270 - pad.fPhi) / 180 * Math.PI, + theta = (pad.fTheta - 10) / 180 * Math.PI; + pos.set(max3dx * Math.cos(phi) * Math.cos(theta), + max3dy * Math.sin(phi) * Math.cos(theta), + fp.size_z3d + (kz - 0.9) * (max3dx + max3dy) * Math.sin(theta)); } } + return pos; +} + +/** @summary Set default camera position + * @private */ +function setCameraPosition(fp, first_time) { + const pos = getCameraDefaultPosition(fp, first_time); + + if (pos) { + fp.camera.position.copy(pos); + first_time = true; + } + if (first_time) fp.camera.lookAt(fp.lookat); if (first_time && fp.camera.isOrthographicCamera && fp.scene_width && fp.scene_height) { const screen_ratio = fp.scene_width / fp.scene_height, - szx = fp.camera.right - fp.camera.left, szy = fp.camera.top - fp.camera.bottom; + szx = fp.camera.right - fp.camera.left, + szy = fp.camera.top - fp.camera.bottom; if (screen_ratio > szx / szy) { // screen wider than actual geometry @@ -371,14 +238,31 @@ function setCameraPosition(fp, first_time) { fp.camera.left = m - szy * screen_ratio / 2; fp.camera.right = m + szy * screen_ratio / 2; } else { - // screen heigher than actual geometry + // screen higher than actual geometry const m = (fp.camera.top + fp.camera.bottom) / 2; fp.camera.top = m + szx / screen_ratio / 2; fp.camera.bottom = m - szx / screen_ratio / 2; } - } + } - fp.camera.updateProjectionMatrix(); + fp.camera.updateProjectionMatrix(); +} + +function getCameraPosition(fp) { + const p = fp.camera.position, p0 = fp.lookat, + dist = p.distanceTo(p0), + dist_xy = Math.sqrt((p.x - p0.x) ** 2 + (p.y - p0.y) ** 2), + new_theta = Math.atan2((p.z - p0.z) / dist, dist_xy / dist) / Math.PI * 180, + new_phi = 270 - Math.atan2((p.y - p0.y) / dist_xy, (p.x - p0.x) / dist_xy) / Math.PI * 180, + pad = fp.getPadPainter()?.getRootPad(true); + + fp.camera_Phi = new_phi >= 360 ? new_phi - 360 : new_phi; + fp.camera_Theta = new_theta; + + if (pad && Number.isFinite(fp.camera_Phi) && Number.isFinite(fp.camera_Theta)) { + pad.fPhi = fp.camera_Phi; + pad.fTheta = fp.camera_Theta; + } } function create3DControl(fp) { @@ -386,6 +270,13 @@ function create3DControl(fp) { const frame_painter = fp, obj_painter = fp.getMainPainter(); + if (fp.access3dKind() === constants.Embed3D.Embed) { + // tooltip scaling only need when GL canvas embed into + const scale = fp.getCanvPainter()?.getPadScale(); + if (scale) + fp.control.tooltip?.setScale(scale); + } + fp.control.processMouseMove = function(intersects) { let tip = null, mesh = null, zoom_mesh = null; const handle_tooltip = frame_painter.isTooltipAllowed(); @@ -393,19 +284,26 @@ function create3DControl(fp) { for (let i = 0; i < intersects.length; ++i) { if (handle_tooltip && isFunc(intersects[i].object?.tooltip)) { tip = intersects[i].object.tooltip(intersects[i]); - if (tip) { mesh = intersects[i].object; break; } + if (tip) { + mesh = intersects[i].object; + break; + } } else if (intersects[i].object?.zoom && !zoom_mesh) zoom_mesh = intersects[i].object; } if (tip && !tip.use_itself) { - const delta_x = 1e-4*frame_painter.size_x3d, - delta_y = 1e-4*frame_painter.size_y3d, - delta_z = 1e-4*frame_painter.size_z3d; - if ((tip.x1 > tip.x2) || (tip.y1 > tip.y2) || (tip.z1 > tip.z2)) console.warn('check 3D hints coordinates'); - tip.x1 -= delta_x; tip.x2 += delta_x; - tip.y1 -= delta_y; tip.y2 += delta_y; - tip.z1 -= delta_z; tip.z2 += delta_z; + const delta_x = 1e-4 * frame_painter.size_x3d, + delta_y = 1e-4 * frame_painter.size_y3d, + delta_z = 1e-4 * frame_painter.size_z3d; + if ((tip.x1 > tip.x2) || (tip.y1 > tip.y2) || (tip.z1 > tip.z2)) + console.warn('check 3D hints coordinates'); + tip.x1 -= delta_x; + tip.x2 += delta_x; + tip.y1 -= delta_y; + tip.y2 += delta_y; + tip.z1 -= delta_z; + tip.z2 += delta_z; } frame_painter.highlightBin3D(tip, mesh); @@ -415,12 +313,15 @@ function create3DControl(fp) { const pnt = zoom_mesh.globalIntersect(this.raycaster), axis_value = frame_painter.get3dZoomCoord(pnt, axis_name); - if ((axis_name === 'z') && zoom_mesh.use_y_for_z) axis_name = 'y'; + if ((axis_name === 'z') && zoom_mesh.use_y_for_z) + axis_name = 'y'; - return { name: axis_name, - title: 'axis object', - line: axis_name + ' : ' + frame_painter.axisAsText(axis_name, axis_value), - only_status: true }; + return { + name: axis_name, + title: 'axis object', + line: axis_name + ' : ' + frame_painter.axisAsText(axis_name, axis_value), + only_status: true + }; } return tip?.lines ? tip : ''; @@ -435,16 +336,21 @@ function create3DControl(fp) { if (intersects) { for (let n = 0; n < intersects.length; ++n) { const mesh = intersects[n].object; - if (mesh.zoom) { kind = mesh.zoom; p = null; break; } + if (mesh.zoom) { + kind = mesh.zoom; + p = null; + break; + } if (isFunc(mesh.painter?.fillContextMenu)) { - p = mesh.painter; break; + p = mesh.painter; + break; } } } - const fp = obj_painter.getFramePainter(); - if (isFunc(fp?.showContextMenu)) - fp.showContextMenu(kind, pos, p); + const ofp = obj_painter.getFramePainter(); + if (isFunc(ofp?.showContextMenu)) + ofp.showContextMenu(kind, pos, p); }; } @@ -453,13 +359,20 @@ function create3DControl(fp) { * @private */ function create3DScene(render3d, x3dscale, y3dscale, orthographic) { if (render3d === -1) { - if (!this.mode3d) return; + if (!this.mode3d) + return; if (!isFunc(this.clear3dCanvas)) { - console.error(`Strange, why mode3d=${this.mode3d} is configured!!!!`); + console.error(`Strange, why mode3d=${this.mode3d} is configured!`); return; } + const res = x3dscale ? this.toplevel : null; + if (res) { + this.scene?.remove(res); + this.toplevel = null; + } + testAxisVisibility(null, this.toplevel); this.clear3dCanvas(); @@ -486,7 +399,10 @@ function create3DScene(render3d, x3dscale, y3dscale, orthographic) { this.mode3d = false; - return; + if (this.getG() && !x3dscale) + this.createFrameG(); + + return res; } this.mode3d = true; // indicate 3d mode as hist painter does @@ -497,9 +413,9 @@ function create3DScene(render3d, x3dscale, y3dscale, orthographic) { disposeThreejsObject(this.toplevel); delete this.tooltip_mesh; delete this.toplevel; - if (this.control) this.control.HideTooltip(); + this.control?.hideTooltip(); - const newtop = new Object3D(); + const newtop = new THREE.Object3D(); this.scene.add(newtop); this.toplevel = newtop; @@ -517,33 +433,39 @@ function create3DScene(render3d, x3dscale, y3dscale, orthographic) { const sz = this.getSizeFor3d(undefined, render3d); this.size_z3d = 100; - this.size_x3d = this.size_y3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width/sz.height*this.size_z3d) : this.size_z3d; - if (x3dscale) this.size_x3d *= x3dscale; - if (y3dscale) this.size_y3d *= y3dscale; + this.x3dscale = x3dscale || 1; + this.y3dscale = y3dscale || 1; + const xy3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width / sz.height * this.size_z3d) : this.size_z3d; + this.size_x3d = xy3d * this.x3dscale; + this.size_y3d = xy3d * this.y3dscale; - // three.js 3D drawing - this.scene = new Scene(); - // scene.fog = new Fog(0xffffff, 500, 3000); + return importThreeJs().then(() => { + // three.js 3D drawing + this.scene = new THREE.Scene(); + // scene.fog = new Fog(0xffffff, 500, 3000); - this.toplevel = new Object3D(); - this.scene.add(this.toplevel); - this.scene_width = sz.width; - this.scene_height = sz.height; - this.scene_x = sz.x ?? 0; - this.scene_y = sz.y ?? 0; + this.toplevel = new THREE.Object3D(); + this.scene.add(this.toplevel); + this.scene_width = sz.width; + this.scene_height = sz.height; + this.scene_x = sz.x ?? 0; + this.scene_y = sz.y ?? 0; - this.camera_Phi = 30; - this.camera_Theta = 30; + this.camera_Phi = 30; + this.camera_Theta = 30; - create3DCamera(this, orthographic); + create3DCamera(this, orthographic); - setCameraPosition(this, true); + setCameraPosition(this, true); - return createRender3D(this.scene_width, this.scene_height, render3d).then(r => { + return createRender3D(this.scene_width, this.scene_height, render3d); + }).then(r => { this.renderer = r; + if (!r) + return this; - this.webgl = (render3d === constants.Render3D.WebGL); - this.add3dCanvas(sz, this.renderer.jsroot_dom, this.webgl); + this.webgl = r.jsroot_render3d === constants.Render3D.WebGL; + this.add3dCanvas(sz, r.jsroot_dom, this.webgl); this.first_render_tm = 0; this.enable_highlight = false; @@ -560,9 +482,9 @@ function create3DScene(render3d, x3dscale, y3dscale, orthographic) { function change3DCamera(orthographic) { let has_control = false; if (this.control) { - this.control.cleanup(); - delete this.control; - has_control = true; + this.control.cleanup(); + delete this.control; + has_control = true; } create3DCamera(this, orthographic); @@ -584,38 +506,45 @@ function add3DMesh(mesh, painter, the_only) { if (painter && the_only) this.remove3DMeshes(painter); this.toplevel.add(mesh); - mesh._painter = painter; + mesh.painter = painter; } -/** @summary Remove 3D meshed for specified painter +/** @summary Returns all 3D meshed for specific * @private */ -function remove3DMeshes(painter) { +function get3DMeshes(painter) { + const arr = []; if (!painter || !this.toplevel) - return; - let i = this.toplevel.children.length; - - while (i > 0) { - const mesh = this.toplevel.children[--i]; - if (mesh._painter === painter) { - this.toplevel.remove(mesh); - disposeThreejsObject(mesh); - } + return arr; + for (let i = 0; i < this.toplevel.children.length; ++i) { + const mesh = this.toplevel.children[i]; + if (mesh.painter === painter) + arr.push(mesh); } + return arr; } +/** @summary Remove 3D meshed for specified painter + * @private */ +function remove3DMeshes(painter) { + const arr = this.get3DMeshes(painter); + arr.forEach(mesh => { + this.toplevel.remove(mesh); + disposeThreejsObject(mesh); + }); +} /** @summary call 3D rendering of the frame * @param {number} tmout - specifies delay, after which actual rendering will be invoked * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings * superimposed with each other. - * If tmeout <= 0, rendering performed immediately + * If tmout <= 0, rendering performed immediately * If tmout === -1111, immediate rendering with SVG renderer is performed * @private */ function render3D(tmout) { if (tmout === -1111) { // special handling for direct SVG renderer const doc = getDocument(), - rrr = createSVGRenderer(false, 0, doc); + rrr = createSVGRenderer(false, 0, doc); rrr.setSize(this.scene_width, this.scene_height); rrr.render(this.scene, this.camera); if (rrr.makeOuterHTML) { @@ -627,7 +556,8 @@ function render3D(tmout) { return rrr.domElement; } - if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout + if (tmout === undefined) + tmout = 5; // by default, rendering happens with timeout const batch_mode = this.isBatchMode(); @@ -642,7 +572,8 @@ function render3D(tmout) { delete this.render_tmout; } - if (!this.renderer) return; + if (!this.renderer) + return; beforeRender3D(this.renderer); @@ -661,17 +592,22 @@ function render3D(tmout) { this.first_render_tm = tm2.getTime() - tm1.getTime(); this.enable_highlight = (this.first_render_tm < 1200) && this.isTooltipAllowed(); if (this.first_render_tm > 500) - console.log(`three.js r${REVISION}, first render tm = ${this.first_render_tm}`); - } + console.log(`three.js r${THREE.REVISION}, first render tm = ${this.first_render_tm}`); + } else + getCameraPosition(this); if (this.processRender3D) { - this.getPadPainter()?.painters?.forEach(objp => { + this.forEachPainter(objp => { if (isFunc(objp.handleRender3D)) objp.handleRender3D(); - }); + }, 'objects'); } } +/** @summary Returns assigned render object + * @private */ +function getRenderer() { return this.renderer; } + /** @summary Check is 3D drawing need to be resized * @private */ function resize3D() { @@ -679,9 +615,11 @@ function resize3D() { this.apply3dSize(sz); - if ((this.scene_width === sz.width) && (this.scene_height === sz.height)) return false; + if ((this.scene_width === sz.width) && (this.scene_height === sz.height)) + return false; - if ((sz.width < 10) || (sz.height < 10)) return false; + if ((sz.width < 10) || (sz.height < 10)) + return false; this.scene_width = sz.width; this.scene_height = sz.height; @@ -691,16 +629,28 @@ function resize3D() { this.renderer.setSize(this.scene_width, this.scene_height); + const xy3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width / sz.height * this.size_z3d) : this.size_z3d, + x3d = xy3d * this.x3dscale, + y3d = xy3d * this.y3dscale; + + if ((Math.abs(x3d - this.size_x3d) > 0.15 * this.size_z3d) || (Math.abs(y3d - this.size_y3d) > 0.15 * this.size_z3d)) { + this.size_x3d = x3d; + this.size_y3d = y3d; + this.control?.position0?.copy(getCameraDefaultPosition(this, true)); + return 1; // indicate significant resize + } + return true; } -/** @summary Hilight bin in frame painter 3D drawing +/** @summary Highlight bin in frame painter 3D drawing * @private */ function highlightBin3D(tip, selfmesh) { const want_remove = !tip || (tip.x1 === undefined) || !this.enable_highlight; let changed = false, tooltip_mesh = null, changed_self = true, mainp = this.getMainPainter(); - if (mainp && (!mainp.provideUserTooltip || !mainp.hasUserTooltip())) mainp = null; + if (!mainp?.provideUserTooltip || !mainp?.hasUserTooltip()) + mainp = null; if (this.tooltip_selfmesh) { changed_self = (this.tooltip_selfmesh !== selfmesh); @@ -717,14 +667,16 @@ function highlightBin3D(tip, selfmesh) { } if (want_remove) { - if (changed) this.render3D(); - if (changed && mainp) mainp.provideUserTooltip(null); + if (changed) { + this.render3D(); + mainp?.provideUserTooltip(null); + } return; } if (tip.use_itself) { selfmesh.save_color = selfmesh.material.color; - selfmesh.material.color = new Color(tip.color); + selfmesh.material.color = new THREE.Color(tip.color); this.tooltip_selfmesh = selfmesh; changed = changed_self; } else { @@ -733,19 +685,19 @@ function highlightBin3D(tip, selfmesh) { const indicies = Box3D.Indexes, normals = Box3D.Normals, vertices = Box3D.Vertices, - color = new Color(tip.color ? tip.color : 0xFF0000), + color = new THREE.Color(tip.color ? tip.color : 0xFF0000), opacity = tip.opacity || 1; let pos, norm; if (!tooltip_mesh) { - pos = new Float32Array(indicies.length*3); - norm = new Float32Array(indicies.length*3); - const geom = new BufferGeometry(); - geom.setAttribute('position', new BufferAttribute(pos, 3)); - geom.setAttribute('normal', new BufferAttribute(norm, 3)); - const material = new MeshBasicMaterial({ color, opacity, vertexColors: false }); - tooltip_mesh = new Mesh(geom, material); + pos = new Float32Array(indicies.length * 3); + norm = new Float32Array(indicies.length * 3); + const geom = new THREE.BufferGeometry(); + geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); + geom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); + const material = new THREE.MeshBasicMaterial({ color, opacity, vertexColors: false }); + tooltip_mesh = new THREE.Mesh(geom, material); } else { pos = tooltip_mesh.geometry.attributes.position.array; tooltip_mesh.geometry.attributes.position.needsUpdate = true; @@ -753,21 +705,25 @@ function highlightBin3D(tip, selfmesh) { tooltip_mesh.material.opacity = opacity; } - if (tip.x1 === tip.x2) console.warn(`same tip X ${tip.x1} ${tip.x2}`); - if (tip.y1 === tip.y2) console.warn(`same tip Y ${tip.y1} ${tip.y2}`); - if (tip.z1 === tip.z2) tip.z2 = tip.z1 + 0.0001; // avoid zero faces + if (tip.x1 === tip.x2) + console.warn(`same tip X ${tip.x1} ${tip.x2}`); + if (tip.y1 === tip.y2) + console.warn(`same tip Y ${tip.y1} ${tip.y2}`); + if (tip.z1 === tip.z2) + tip.z2 = tip.z1 + 0.0001; // avoid zero faces for (let k = 0, nn = -3; k < indicies.length; ++k) { const vert = vertices[indicies[k]]; - pos[k*3] = tip.x1 + vert.x * (tip.x2 - tip.x1); - pos[k*3+1] = tip.y1 + vert.y * (tip.y2 - tip.y1); - pos[k*3+2] = tip.z1 + vert.z * (tip.z2 - tip.z1); + pos[k * 3] = tip.x1 + vert.x * (tip.x2 - tip.x1); + pos[k * 3 + 1] = tip.y1 + vert.y * (tip.y2 - tip.y1); + pos[k * 3 + 2] = tip.z1 + vert.z * (tip.z2 - tip.z1); if (norm) { - if (k % 6 === 0) nn += 3; - norm[k*3] = normals[nn]; - norm[k*3+1] = normals[nn+1]; - norm[k*3+2] = normals[nn+2]; + if (k % 6 === 0) + nn += 3; + norm[k * 3] = normals[nn]; + norm[k * 3 + 1] = normals[nn + 1]; + norm[k * 3 + 2] = normals[nn + 2]; } } this.tooltip_mesh = tooltip_mesh; @@ -779,16 +735,19 @@ function highlightBin3D(tip, selfmesh) { } } - if (changed) this.render3D(); + if (changed) + this.render3D(); if (changed && tip.$projection && isFunc(tip.$painter?.redrawProjection)) - tip.$painter.redrawProjection(tip.ix-1, tip.ix, tip.iy-1, tip.iy); + tip.$painter.redrawProjection(tip.ix - 1, tip.ix, tip.iy - 1, tip.iy); if (changed && mainp?.getObject()) { - mainp.provideUserTooltip({ obj: mainp.getObject(), name: mainp.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1+tip.x2)/2, gry: (tip.y1+tip.y2)/2, grz: (tip.z1+tip.z2)/2 }); + mainp.provideUserTooltip({ + obj: mainp.getObject(), name: mainp.getObject().fName, + bin: tip.bin, cont: tip.value, + binx: tip.ix, biny: tip.iy, binz: tip.iz, + grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 + }); } } @@ -801,17 +760,18 @@ function set3DOptions(hopt) { /** @summary Draw axes in 3D mode * @private */ function drawXYZ(toplevel, AxisPainter, opts) { - if (!opts) opts = {}; + if (!opts) + opts = { ndim: 2 }; if (opts.drawany === false) opts.draw = false; else opts.drawany = true; - const pad = opts.v7 ? null : this.getPadPainter().getRootPad(true); + const pad = opts.v7 ? null : this.getPadPainter()?.getRootPad(true); let grminx = -this.size_x3d, grmaxx = this.size_x3d, grminy = -this.size_y3d, grmaxy = this.size_y3d, - grminz = 0, grmaxz = 2*this.size_z3d, + grminz = 0, grmaxz = 2 * this.size_z3d, scalingSize = this.size_z3d, xmin = this.xmin, xmax = this.xmax, ymin = this.ymin, ymax = this.ymax, @@ -819,97 +779,117 @@ function drawXYZ(toplevel, AxisPainter, opts) { y_zoomed = false, z_zoomed = false; if (!this.size_z3d) { - grminx = this.xmin; grmaxx = this.xmax; - grminy = this.ymin; grmaxy = this.ymax; - grminz = this.zmin; grmaxz = this.zmax; + grminx = this.xmin; + grmaxx = this.xmax; + grminy = this.ymin; + grmaxy = this.ymax; + grminz = this.zmin; + grmaxz = this.zmax; scalingSize = (grmaxz - grminz); } if (('zoom_xmin' in this) && ('zoom_xmax' in this) && (this.zoom_xmin !== this.zoom_xmax)) { - xmin = this.zoom_xmin; xmax = this.zoom_xmax; + xmin = this.zoom_xmin; + xmax = this.zoom_xmax; } if (('zoom_ymin' in this) && ('zoom_ymax' in this) && (this.zoom_ymin !== this.zoom_ymax)) { - ymin = this.zoom_ymin; ymax = this.zoom_ymax; y_zoomed = true; + ymin = this.zoom_ymin; + ymax = this.zoom_ymax; + y_zoomed = true; } if (('zoom_zmin' in this) && ('zoom_zmax' in this) && (this.zoom_zmin !== this.zoom_zmax)) { - zmin = this.zoom_zmin; zmax = this.zoom_zmax; z_zoomed = true; + zmin = this.zoom_zmin; + zmax = this.zoom_zmax; + z_zoomed = true; } if (opts.use_y_for_z) { - this.zmin = this.ymin; this.zmax = this.ymax; - zmin = ymin; zmax = ymax; z_zoomed = y_zoomed; - // if (!z_zoomed && (this.hmin!==this.hmax)) { zmin = this.hmin; zmax = this.hmax; } - ymin = 0; ymax = 1; + this.zmin = this.ymin; + this.zmax = this.ymax; + zmin = ymin; + zmax = ymax; + z_zoomed = y_zoomed; + ymin = 0; + ymax = 1; } // z axis range used for lego plot - this.lego_zmin = zmin; this.lego_zmax = zmax; + this.lego_zmin = zmin; + this.lego_zmax = zmax; // factor 1.1 used in ROOT for lego plots - if ((opts.zmult !== undefined) && !z_zoomed) zmax *= opts.zmult; + if ((opts.zmult !== undefined) && !z_zoomed) + zmax *= opts.zmult; this.x_handle = new AxisPainter(null, this.xaxis); if (opts.v7) { - this.x_handle.setPadName(this.getPadName()); - this.x_handle.snapid = this.snapid; - } + this.x_handle.setPadPainter(this.getPadPainter()); + this.x_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.x_handle.setHistPainter(opts.hist_painter, 'x'); + this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, xmin, xmax, false, [grminx, grmaxx], - { log: pad?.fLogx ?? 0, reverse: opts.reverse_x }); + { log: pad?.fLogx ?? 0, reverse: opts.reverse_x, logcheckmin: true }); this.x_handle.assignFrameMembers(this, 'x'); this.x_handle.extractDrawAttributes(scalingSize); this.y_handle = new AxisPainter(null, this.yaxis); if (opts.v7) { - this.y_handle.setPadName(this.getPadName()); - this.y_handle.snapid = this.snapid; - } + this.y_handle.setPadPainter(this.getPadPainter()); + this.y_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.y_handle.setHistPainter(opts.hist_painter, 'y'); this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, ymin, ymax, false, [grminy, grmaxy], - { log: pad && !opts.use_y_for_z ? pad.fLogy : 0, reverse: opts.reverse_y }); + { log: pad && !opts.use_y_for_z ? pad.fLogy : 0, reverse: opts.reverse_y, logcheckmin: opts.ndim > 1 }); this.y_handle.assignFrameMembers(this, 'y'); this.y_handle.extractDrawAttributes(scalingSize); this.z_handle = new AxisPainter(null, this.zaxis); if (opts.v7) { - this.z_handle.setPadName(this.getPadName()); - this.z_handle.snapid = this.snapid; - } - - this.z_handle.configureAxis('zaxis', this.zmin, this.zmax, zmin, zmax, false, [grminz, grmaxz], - { log: ((opts.use_y_for_z || (opts.ndim === 2)) ? pad?.fLogv : undefined) ?? pad?.fLogz ?? 0, - reverse: opts.reverse_z }); + this.z_handle.setPadPainter(this.getPadPainter()); + this.z_handle.assignSnapId(this.getSnapId()); + } else if (opts.hist_painter) + this.z_handle.setHistPainter(opts.hist_painter, 'z'); + this.z_handle.configureAxis('zaxis', this.zmin, this.zmax, zmin, zmax, false, [grminz, grmaxz], { + value_axis: (opts.ndim === 1) || (opts.ndim === 2), + log: ((opts.use_y_for_z || (opts.ndim === 2)) ? pad?.fLogv : undefined) ?? pad?.fLogz ?? 0, + reverse: opts.reverse_z, logcheckmin: opts.ndim > 2 + }); this.z_handle.assignFrameMembers(this, 'z'); this.z_handle.extractDrawAttributes(scalingSize); this.setRootPadRange(pad, true); // set some coordinates typical for 3D projections in ROOT const textMaterials = {}, lineMaterials = {}, - xticks = this.x_handle.createTicks(false, true), - yticks = this.y_handle.createTicks(false, true), - zticks = this.z_handle.createTicks(false, true); + xticks = this.x_handle.createTicks(false, true), + yticks = this.y_handle.createTicks(false, true), + zticks = this.z_handle.createTicks(false, true); let text_scale = 1; function getLineMaterial(handle, kind) { const col = ((kind === 'ticks') ? handle.ticksColor : handle.lineatt.color) || 'black', - linewidth = (kind === 'ticks') ? handle.ticksWidth : handle.lineatt.width, - name = `${col}_${linewidth}`; + linewidth = (kind === 'ticks') ? handle.ticksWidth : handle.lineatt.width, + name = `${col}_${linewidth}`; if (!lineMaterials[name]) - lineMaterials[name] = new LineBasicMaterial(getMaterialArgs(col, { linewidth, vertexColors: false })); + lineMaterials[name] = new THREE.LineBasicMaterial(getMaterialArgs(col, { linewidth, vertexColors: false })); return lineMaterials[name]; } function getTextMaterial(handle, kind, custom_color) { const col = custom_color || ((kind === 'title') ? handle.titleFont?.color : handle.labelsFont?.color) || 'black'; if (!textMaterials[col]) - textMaterials[col] = new MeshBasicMaterial(getMaterialArgs(col, { vertexColors: false })); + textMaterials[col] = new THREE.MeshBasicMaterial(getMaterialArgs(col, { vertexColors: false })); return textMaterials[col]; } // main element, where all axis elements are placed - const top = new Object3D(); + const top = new THREE.Object3D(); top.axis_draw = true; // mark element as axis drawing toplevel.add(top); - let ticks = [], lbls = [], maxtextheight = 0; + let ticks = [], lbls = [], maxtextheight = 0, maxtextwidth = 0; + const center_x = this.x_handle.isCenteredLabels(), + rotate_x = this.x_handle.isRotateLabels(); while (xticks.next()) { const grx = xticks.grpos; @@ -917,16 +897,19 @@ function drawXYZ(toplevel, AxisPainter, opts) { lbl = this.x_handle.format(xticks.tick, 2); if (xticks.last_major()) { - if (!this.x_handle.fTitle) lbl = 'x'; + if (!this.x_handle.fTitle) + lbl = 'x'; } else if (lbl === null) { - is_major = false; lbl = ''; + is_major = false; + lbl = ''; } - if (is_major && lbl && opts.draw) { + if (is_major && lbl && opts.draw && (!center_x || !xticks.last_major())) { const mod = xticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; + if (mod?.fLabText) + lbl = mod.fLabText; - const text3d = createTextGeometry(this, lbl, this.x_handle.labelsFont.size); + const text3d = createLatexGeometry(this, lbl, this.x_handle.labelsFont.size); text3d.computeBoundingBox(); const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; @@ -934,9 +917,11 @@ function drawXYZ(toplevel, AxisPainter, opts) { text3d.offsety = this.x_handle.labelsOffset + (grmaxy - grminy) * 0.005; + maxtextwidth = Math.max(maxtextwidth, draw_width); maxtextheight = Math.max(maxtextheight, draw_height); - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); text3d.grx = grx; lbls.push(text3d); @@ -944,26 +929,32 @@ function drawXYZ(toplevel, AxisPainter, opts) { if (!xticks.last_major()) { space = Math.abs(xticks.next_major_grpos() - grx); if ((draw_width > 0) && (space > 0)) - text_scale = Math.min(text_scale, 0.9*space/draw_width); + text_scale = Math.min(text_scale, 0.9 * space / draw_width); } + if (rotate_x) + text3d.rotate = 1; - if (this.x_handle.isCenteredLabels()) { - if (!space) space = Math.min(grx - grminx, grmaxx - grx); - text3d.grx += space/2; + if (center_x) { + if (!space) + space = Math.min(grx - grminx, grmaxx - grx); + text3d.grx += space / 2; } } - ticks.push(grx, 0, 0, grx, this.x_handle.ticksSize*(is_major ? -1 : -0.6), 0); + ticks.push(grx, 0, 0, grx, this.x_handle.ticksSize * (is_major ? -1 : -0.6), 0); } if (this.x_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.x_handle.fTitle, this.x_handle.titleFont.size); + const text3d = createLatexGeometry(this, this.x_handle.fTitle, this.x_handle.titleFont.size); text3d.computeBoundingBox(); text3d.center = this.x_handle.titleCenter; text3d.opposite = this.x_handle.titleOpposite; text3d.offsety = 1.6 * this.x_handle.titleOffset + (grmaxy - grminy) * 0.005; - text3d.grx = (grminx + grmaxx)/2; // default position for centered title + text3d.grx = (grminx + grmaxx) / 2; // default position for centered title text3d.kind = 'title'; + if (this.x_handle.isRotateTitle()) + text3d.rotate = 2; + lbls.push(text3d); } @@ -973,56 +964,62 @@ function drawXYZ(toplevel, AxisPainter, opts) { let pos = point[kind]; switch (kind) { - case 'x': pos = (pos + this.size_x3d)/2/this.size_x3d; break; - case 'y': pos = (pos + this.size_y3d)/2/this.size_y3d; break; - case 'z': pos = pos/2/this.size_z3d; break; + case 'x': pos = (pos + this.size_x3d) / 2 / this.size_x3d; break; + case 'y': pos = (pos + this.size_y3d) / 2 / this.size_y3d; break; + case 'z': pos = pos / 2 / this.size_z3d; break; } - if (this['log'+kind]) - pos = Math.exp(Math.log(min) + pos*(Math.log(max)-Math.log(min))); - else - pos = min + pos*(max-min); + if (this['log' + kind]) + pos = Math.exp(Math.log(min) + pos * (Math.log(max) - Math.log(min))); + else + pos = min + pos * (max - min); return pos; }; const createZoomMesh = (kind, size_3d, use_y_for_z) => { - const geom = new BufferGeometry(), tsz = Math.max(this[kind+'_handle'].ticksSize, 0.005 * size_3d); + const geom = new THREE.BufferGeometry(), tsz = Math.max(this[kind + '_handle'].ticksSize, 0.005 * size_3d); let positions; if (kind === 'z') - positions = new Float32Array([0, 0, 0, tsz*4, 0, 2*size_3d, tsz*4, 0, 0, 0, 0, 0, 0, 0, 2*size_3d, tsz*4, 0, 2*size_3d]); + positions = new Float32Array([0, 0, 0, tsz * 4, 0, 2 * size_3d, tsz * 4, 0, 0, 0, 0, 0, 0, 0, 2 * size_3d, tsz * 4, 0, 2 * size_3d]); else - positions = new Float32Array([-size_3d, 0, 0, size_3d, -tsz*4, 0, size_3d, 0, 0, -size_3d, 0, 0, -size_3d, -tsz*4, 0, size_3d, -tsz*4, 0]); + positions = new Float32Array([-size_3d, 0, 0, size_3d, -tsz * 4, 0, size_3d, 0, 0, -size_3d, 0, 0, -size_3d, -tsz * 4, 0, size_3d, -tsz * 4, 0]); - geom.setAttribute('position', new BufferAttribute(positions, 3)); + geom.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geom.computeVertexNormals(); - const material = new MeshBasicMaterial({ transparent: true, vertexColors: false, side: DoubleSide, opacity: 0 }), - mesh = new Mesh(geom, material); + const material = new THREE.MeshBasicMaterial({ transparent: true, vertexColors: false, side: THREE.DoubleSide, opacity: 0 }), + mesh = new THREE.Mesh(geom, material); mesh.zoom = kind; mesh.size_3d = size_3d; mesh.tsz = tsz; mesh.use_y_for_z = use_y_for_z; - if (kind === 'y') mesh.rotateZ(Math.PI/2).rotateX(Math.PI); + if (kind === 'y') + mesh.rotateZ(Math.PI / 2).rotateX(Math.PI); - mesh.v1 = new Vector3(positions[0], positions[1], positions[2]); - mesh.v2 = new Vector3(positions[6], positions[7], positions[8]); - mesh.v3 = new Vector3(positions[3], positions[4], positions[5]); + mesh.v1 = new THREE.Vector3(positions[0], positions[1], positions[2]); + mesh.v2 = new THREE.Vector3(positions[6], positions[7], positions[8]); + mesh.v3 = new THREE.Vector3(positions[3], positions[4], positions[5]); mesh.globalIntersect = function(raycaster) { - if (!this.v1 || !this.v2 || !this.v3) return undefined; + if (!this.v1 || !this.v2 || !this.v3) + return undefined; - const plane = new Plane(); + const plane = new THREE.Plane(); plane.setFromCoplanarPoints(this.v1, this.v2, this.v3); plane.applyMatrix4(this.matrixWorld); const v1 = raycaster.ray.origin.clone(), - v2 = v1.clone().addScaledVector(raycaster.ray.direction, 1e10), - pnt = plane.intersectLine(new Line3(v1, v2), new Vector3()); + v2 = v1.clone().addScaledVector(raycaster.ray.direction, 1e10), + pnt = plane.intersectLine(new THREE.Line3(v1, v2), new THREE.Vector3()); - if (!pnt) return undefined; + if (!pnt) + return undefined; let min = -this.size_3d, max = this.size_3d; - if (this.zoom === 'z') { min = 0; max = 2*this.size_3d; } + if (this.zoom === 'z') { + min = 0; + max = 2 * this.size_3d; + } if (pnt[this.zoom] < min) pnt[this.zoom] = min; @@ -1035,7 +1032,6 @@ function drawXYZ(toplevel, AxisPainter, opts) { mesh.showSelection = function(pnt1, pnt2) { // used to show selection - const kind = this.zoom; let tgtmesh = this.children ? this.children[0] : null, gg; if (!pnt1 || !pnt2) { if (tgtmesh) { @@ -1045,29 +1041,31 @@ function drawXYZ(toplevel, AxisPainter, opts) { return tgtmesh; } - if (!this.geometry) return false; + if (!this.geometry) + return false; if (!tgtmesh) { gg = this.geometry.clone(); const pos = gg.getAttribute('position').array; // original vertices [0, 2, 1, 0, 3, 2] - if (kind === 'z') pos[6] = pos[3] = pos[15] = this.tsz; - else pos[4] = pos[16] = pos[13] = -this.tsz; - tgtmesh = new Mesh(gg, new MeshBasicMaterial({ color: 0xFF00, side: DoubleSide, vertexColors: false })); + if (this.zoom === 'z') + pos[6] = pos[3] = pos[15] = this.tsz; + else + pos[4] = pos[16] = pos[13] = -this.tsz; + tgtmesh = new THREE.Mesh(gg, new THREE.MeshBasicMaterial({ color: 0xFF00, side: THREE.DoubleSide, vertexColors: false })); this.add(tgtmesh); } else gg = tgtmesh.geometry; - const pos = gg.getAttribute('position').array; - if (kind === 'z') { - pos[2] = pos[11] = pos[8] = pnt1[kind]; - pos[5] = pos[17] = pos[14] = pnt2[kind]; + if (this.zoom === 'z') { + pos[2] = pos[11] = pos[8] = pnt1[this.zoom]; + pos[5] = pos[17] = pos[14] = pnt2[this.zoom]; } else { - pos[0] = pos[9] = pos[12] = pnt1[kind]; - pos[6] = pos[3] = pos[15] = pnt2[kind]; + pos[0] = pos[9] = pos[12] = pnt1[this.zoom]; + pos[6] = pos[3] = pos[15] = pnt2[this.zoom]; } gg.getAttribute('position').needsUpdate = true; @@ -1078,10 +1076,11 @@ function drawXYZ(toplevel, AxisPainter, opts) { return mesh; }; - let xcont = new Object3D(), xtickslines; + let xcont = new THREE.Object3D(), xtickslines; xcont.position.set(0, grminy, grminz); - xcont.rotation.x = 1/4*Math.PI; + xcont.rotation.x = 1 / 4 * Math.PI; xcont.xyid = 2; + xcont.painter = this.x_handle; if (opts.draw) { xtickslines = createLineSegments(ticks, getLineMaterial(this.x_handle, 'ticks')); @@ -1089,17 +1088,28 @@ function drawXYZ(toplevel, AxisPainter, opts) { } lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posx = lbl.center ? lbl.grx - w/2 : (lbl.opposite ? grminx : grmaxx - w), - m = new Matrix4(); + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = lbl.center ? lbl.grx - w / 2 : (lbl.opposite ? grminx : grmaxx - w), + posy = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.x_handle.ticksSize - lbl.offsety, + m = new THREE.Matrix4(); // matrix to swap y and z scales and shift along z to its position m.set(text_scale, 0, 0, posx, - 0, text_scale, 0, -maxtextheight*text_scale - this.x_handle.ticksSize - lbl.offsety, + 0, text_scale, 0, posy, 0, 0, 1, 0, 0, 0, 0, 1); - const mesh = new Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); + + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); + mesh.applyMatrix4(m); xcont.add(mesh); }); @@ -1108,24 +1118,35 @@ function drawXYZ(toplevel, AxisPainter, opts) { xcont.add(createZoomMesh('x', this.size_x3d)); top.add(xcont); - xcont = new Object3D(); + xcont = new THREE.Object3D(); xcont.position.set(0, grmaxy, grminz); - xcont.rotation.x = 3/4*Math.PI; + xcont.rotation.x = 3 / 4 * Math.PI; + xcont.painter = this.x_handle; if (opts.draw) - xcont.add(new LineSegments(xtickslines.geometry, xtickslines.material)); + xcont.add(new THREE.LineSegments(xtickslines.geometry, xtickslines.material)); lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posx = (lbl.center ? lbl.grx + w/2 : lbl.opposite ? grminx + w : grmaxx), - m = new Matrix4(); + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = lbl.center ? lbl.grx + w / 2 : (lbl.opposite ? grminx + w : grmaxx), + posy = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.x_handle.ticksSize - lbl.offsety, + m = new THREE.Matrix4(); // matrix to swap y and z scales and shift along z to its position m.set(-text_scale, 0, 0, posx, - 0, text_scale, 0, -maxtextheight*text_scale - this.x_handle.ticksSize - lbl.offsety, + 0, text_scale, 0, posy, 0, 0, -1, 0, 0, 0, 0, 1); - const mesh = new Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); + + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.x_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); mesh.applyMatrix4(m); xcont.add(mesh); }); @@ -1135,7 +1156,13 @@ function drawXYZ(toplevel, AxisPainter, opts) { xcont.add(createZoomMesh('x', this.size_x3d)); top.add(xcont); - lbls = []; text_scale = 1; maxtextheight = 0; ticks = []; + lbls = []; + text_scale = 1; + maxtextwidth = maxtextheight = 0; + ticks = []; + + const center_y = this.y_handle.isCenteredLabels(), + rotate_y = this.y_handle.isRotateLabels(); while (yticks.next()) { const gry = yticks.grpos; @@ -1143,24 +1170,29 @@ function drawXYZ(toplevel, AxisPainter, opts) { lbl = this.y_handle.format(yticks.tick, 2); if (yticks.last_major()) { - if (!this.y_handle.fTitle) lbl = 'y'; + if (!this.y_handle.fTitle) + lbl = 'y'; } else if (lbl === null) { - is_major = false; lbl = ''; + is_major = false; + lbl = ''; } - if (is_major && lbl && opts.draw) { + if (is_major && lbl && opts.draw && (!center_y || !yticks.last_major())) { const mod = yticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; + if (mod?.fLabText) + lbl = mod.fLabText; - const text3d = createTextGeometry(this, lbl, this.y_handle.labelsFont.size); + const text3d = createLatexGeometry(this, lbl, this.y_handle.labelsFont.size); text3d.computeBoundingBox(); const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; text3d.center = true; + maxtextwidth = Math.max(maxtextwidth, draw_width); maxtextheight = Math.max(maxtextheight, draw_height); - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); text3d.gry = gry; text3d.offsetx = this.y_handle.labelsOffset + (grmaxx - grminx) * 0.005; lbls.push(text3d); @@ -1169,47 +1201,62 @@ function drawXYZ(toplevel, AxisPainter, opts) { if (!yticks.last_major()) { space = Math.abs(yticks.next_major_grpos() - gry); if (draw_width > 0) - text_scale = Math.min(text_scale, 0.9*space/draw_width); + text_scale = Math.min(text_scale, 0.9 * space / draw_width); } - if (this.y_handle.isCenteredLabels()) { - if (!space) space = Math.min(gry - grminy, grmaxy - gry); - text3d.gry += space/2; + if (center_y) { + if (!space) + space = Math.min(gry - grminy, grmaxy - gry); + text3d.gry += space / 2; } + if (rotate_y) + text3d.rotate = 1; } - ticks.push(0, gry, 0, this.y_handle.ticksSize*(is_major ? -1 : -0.6), gry, 0); + ticks.push(0, gry, 0, this.y_handle.ticksSize * (is_major ? -1 : -0.6), gry, 0); } if (this.y_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.y_handle.fTitle, this.y_handle.titleFont.size); + const text3d = createLatexGeometry(this, this.y_handle.fTitle, this.y_handle.titleFont.size); text3d.computeBoundingBox(); text3d.center = this.y_handle.titleCenter; text3d.opposite = this.y_handle.titleOpposite; text3d.offsetx = 1.6 * this.y_handle.titleOffset + (grmaxx - grminx) * 0.005; - text3d.gry = (grminy + grmaxy)/2; // default position for centered title + text3d.gry = (grminy + grmaxy) / 2; // default position for centered title text3d.kind = 'title'; + if (this.y_handle.isRotateTitle()) + text3d.rotate = 2; lbls.push(text3d); } if (!opts.use_y_for_z) { - let yticksline, ycont = new Object3D(); + let yticksline, ycont = new THREE.Object3D(); ycont.position.set(grminx, 0, grminz); - ycont.rotation.y = -1/4*Math.PI; + ycont.rotation.y = -1 / 4 * Math.PI; + ycont.painter = this.y_handle; if (opts.draw) { yticksline = createLineSegments(ticks, getLineMaterial(this.y_handle, 'ticks')); ycont.add(yticksline); } lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posy = lbl.center ? lbl.gry + w/2 : (lbl.opposite ? grminy + w : grmaxy), - m = new Matrix4(); - // matrix to swap y and z scales and shift along z to its position - m.set(0, text_scale, 0, -maxtextheight*text_scale - this.y_handle.ticksSize - lbl.offsetx, + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.y_handle.ticksSize - lbl.offsetx, + posy = lbl.center ? lbl.gry + w / 2 : (lbl.opposite ? grminy + w : grmaxy), + m = new THREE.Matrix4(); + m.set(0, text_scale, 0, posx, -text_scale, 0, 0, posy, 0, 0, 1, 0, 0, 0, 0, 1); - const mesh = new Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); + mesh.applyMatrix4(m); ycont.add(mesh); }); @@ -1219,22 +1266,34 @@ function drawXYZ(toplevel, AxisPainter, opts) { ycont.add(createZoomMesh('y', this.size_y3d)); top.add(ycont); - ycont = new Object3D(); + ycont = new THREE.Object3D(); ycont.position.set(grmaxx, 0, grminz); - ycont.rotation.y = -3/4*Math.PI; + ycont.rotation.y = -3 / 4 * Math.PI; + ycont.painter = this.y_handle; if (opts.draw) - ycont.add(new LineSegments(yticksline.geometry, yticksline.material)); + ycont.add(new THREE.LineSegments(yticksline.geometry, yticksline.material)); lbls.forEach(lbl => { - const w = lbl.boundingBox.max.x - lbl.boundingBox.min.x, - posy = lbl.center ? lbl.gry - w/2 : (lbl.opposite ? grminy : grmaxy - w), - m = new Matrix4(); - m.set(0, text_scale, 0, -maxtextheight*text_scale - this.y_handle.ticksSize - lbl.offsetx, + const dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x, + dy = lbl.boundingBox.max.y - lbl.boundingBox.min.y, + w = (lbl.rotate === 1) ? dy : dx, + posx = -text_scale * (lbl.rotate === 1 ? maxtextwidth : maxtextheight) - this.y_handle.ticksSize - lbl.offsetx, + posy = lbl.center ? lbl.gry - w / 2 : (lbl.opposite ? grminy : grmaxy - w), + m = new THREE.Matrix4(); + + m.set(0, text_scale, 0, posx, text_scale, 0, 0, posy, 0, 0, -1, 0, 0, 0, 0, 1); - const mesh = new Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.y_handle, lbl.kind, lbl.color)); + if (lbl.rotate) + mesh.rotateZ(lbl.rotate * Math.PI / 2); + if (lbl.rotate === 1) + mesh.translateY(-dy); + if (lbl.rotate === 2) + mesh.translateX(-dx); + mesh.applyMatrix4(m); ycont.add(mesh); }); @@ -1244,12 +1303,18 @@ function drawXYZ(toplevel, AxisPainter, opts) { top.add(ycont); } - lbls = []; text_scale = 1; ticks = []; // just array, will be used for the buffer geometry + lbls = []; + text_scale = 1; + ticks = []; // just array, will be used for the buffer geometry let zgridx = null, zgridy = null, lastmajorz = null, maxzlblwidth = 0; + const center_z = this.z_handle.isCenteredLabels(), + rotate_z = this.z_handle.isRotateLabels(); + if (this.size_z3d && opts.drawany) { - zgridx = []; zgridy = []; + zgridx = []; + zgridy = []; } while (zticks.next()) { @@ -1257,24 +1322,29 @@ function drawXYZ(toplevel, AxisPainter, opts) { let is_major = (zticks.kind === 1), lbl = this.z_handle.format(zticks.tick, 2); - if (lbl === null) { is_major = false; lbl = ''; } + if (lbl === null) { + is_major = false; + lbl = ''; + } - if (is_major && lbl && opts.draw) { + if (is_major && lbl && opts.draw && (!center_z || !zticks.last_major())) { const mod = zticks.get_modifier(); - if (mod?.fLabText) lbl = mod.fLabText; + if (mod?.fLabText) + lbl = mod.fLabText; - const text3d = createTextGeometry(this, lbl, this.z_handle.labelsFont.size); + const text3d = createLatexGeometry(this, lbl, this.z_handle.labelsFont.size); text3d.computeBoundingBox(); const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - text3d.translate(-draw_width, -draw_height/2, 0); + draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; + text3d.translate(-draw_width, -draw_height / 2, 0); - if (mod?.fTextColor) text3d.color = this.getColor(mod.fTextColor); + if (mod?.fTextColor) + text3d.color = this.getColor(mod.fTextColor); text3d.grz = grz; lbls.push(text3d); if ((lastmajorz !== null) && (draw_height > 0)) - text_scale = Math.min(text_scale, 0.9*(grz - lastmajorz)/draw_height); + text_scale = Math.min(text_scale, 0.9 * (grz - lastmajorz) / draw_height); maxzlblwidth = Math.max(maxzlblwidth, draw_width); @@ -1288,11 +1358,11 @@ function drawXYZ(toplevel, AxisPainter, opts) { if (zgridy && is_major) zgridy.push(0, grminy, grz, 0, grmaxy, grz); - ticks.push(0, 0, grz, this.z_handle.ticksSize*(is_major ? 1 : 0.6), 0, grz); + ticks.push(0, 0, grz, this.z_handle.ticksSize * (is_major ? 1 : 0.6), 0, grz); } - if (zgridx && (zgridx.length > 0)) { - const material = new LineDashedMaterial({ color: this.x_handle.ticksColor, dashSize: 2, gapSize: 2 }), + if (zgridx?.length) { + const material = new THREE.LineDashedMaterial({ color: this.x_handle.ticksColor, dashSize: 2, gapSize: 2 }), lines1 = createLineSegments(zgridx, material); lines1.position.set(0, grmaxy, 0); @@ -1300,15 +1370,15 @@ function drawXYZ(toplevel, AxisPainter, opts) { lines1.visible = false; top.add(lines1); - const lines2 = new LineSegments(lines1.geometry, material); + const lines2 = new THREE.LineSegments(lines1.geometry, material); lines2.position.set(0, grminy, 0); lines2.grid = 4; // mark as grid lines2.visible = false; top.add(lines2); } - if (zgridy && (zgridy.length > 0)) { - const material = new LineDashedMaterial({ color: this.y_handle.ticksColor, dashSize: 2, gapSize: 2 }), + if (zgridy?.length) { + const material = new THREE.LineDashedMaterial({ color: this.y_handle.ticksColor, dashSize: 2, gapSize: 2 }), lines1 = createLineSegments(zgridy, material); lines1.position.set(grmaxx, 0, 0); @@ -1316,7 +1386,7 @@ function drawXYZ(toplevel, AxisPainter, opts) { lines1.visible = false; top.add(lines1); - const lines2 = new LineSegments(lines1.geometry, material); + const lines2 = new THREE.LineSegments(lines1.geometry, material); lines2.position.set(grminx, 0, 0); lines2.grid = 1; // mark as grid lines2.visible = false; @@ -1325,102 +1395,115 @@ function drawXYZ(toplevel, AxisPainter, opts) { const zcont = [], zticksline = opts.draw ? createLineSegments(ticks, getLineMaterial(this.z_handle, 'ticks')) : null; for (let n = 0; n < 4; ++n) { - zcont.push(new Object3D()); + zcont.push(new THREE.Object3D()); lbls.forEach((lbl, indx) => { - const m = new Matrix4(); + const m = new THREE.Matrix4(), + dx = lbl.boundingBox.max.x - lbl.boundingBox.min.x; + let grz = lbl.grz; - if (this.z_handle.isCenteredLabels()) { + if (center_z) { if (indx < lbls.length - 1) - grz = (grz + lbls[indx+1].grz) / 2; + grz = (grz + lbls[indx + 1].grz) / 2; else if (indx > 0) - grz = Math.min(1.5*grz - lbls[indx-1].grz*0.5, grmaxz); + grz = Math.min(1.5 * grz - lbls[indx - 1].grz * 0.5, grmaxz); } // matrix to swap y and z scales and shift along z to its position m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + this.z_handle.labelsOffset, - 0, 0, 1, 0, - 0, text_scale, 0, grz); - const mesh = new Mesh(lbl, getTextMaterial(this.z_handle)); + 0, 0, 1, 0, + 0, text_scale, 0, grz); + const mesh = new THREE.Mesh(lbl, getTextMaterial(this.z_handle)); + if (rotate_z) + mesh.rotateZ(-Math.PI / 2).translateX(dx / 2); mesh.applyMatrix4(m); zcont[n].add(mesh); }); if (this.z_handle.fTitle && opts.draw) { - const text3d = createTextGeometry(this, this.z_handle.fTitle, this.z_handle.titleFont.size); + const text3d = createLatexGeometry(this, this.z_handle.fTitle, this.z_handle.titleFont.size); text3d.computeBoundingBox(); - const draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x, - posz = this.z_handle.titleCenter ? (grmaxz + grminz - draw_width)/2 : (this.z_handle.titleOpposite ? grminz : grmaxz - draw_width); - - text3d.rotateZ(Math.PI/2); + const dx = text3d.boundingBox.max.x - text3d.boundingBox.min.x, + dy = text3d.boundingBox.max.y - text3d.boundingBox.min.y, + rotate = this.z_handle.isRotateTitle(), + posz = this.z_handle.titleCenter ? (grmaxz + grminz - dx) / 2 : (this.z_handle.titleOpposite ? grminz : grmaxz - dx) + (rotate ? dx : 0), + m = new THREE.Matrix4(); - const m = new Matrix4(); m.set(-text_scale, 0, 0, this.z_handle.ticksSize + (grmaxx - grminx) * 0.005 + maxzlblwidth + this.z_handle.titleOffset, - 0, 0, 1, 0, - 0, text_scale, 0, posz); - const mesh = new Mesh(text3d, getTextMaterial(this.z_handle, 'title')); + 0, 0, 1, 0, + 0, text_scale, 0, posz); + const mesh = new THREE.Mesh(text3d, getTextMaterial(this.z_handle, 'title')); + mesh.rotateZ(Math.PI * (rotate ? 1.5 : 0.5)); + if (rotate) + mesh.translateY(-dy); + mesh.applyMatrix4(m); zcont[n].add(mesh); } if (opts.draw && zticksline) - zcont[n].add(n === 0 ? zticksline : new LineSegments(zticksline.geometry, zticksline.material)); + zcont[n].add(n === 0 ? zticksline : new THREE.LineSegments(zticksline.geometry, zticksline.material)); if (opts.zoom && opts.drawany) zcont[n].add(createZoomMesh('z', this.size_z3d, opts.use_y_for_z)); zcont[n].zid = n + 2; top.add(zcont[n]); + zcont[n].painter = this.z_handle; } zcont[0].position.set(grminx, grmaxy, 0); - zcont[0].rotation.z = 3/4*Math.PI; + zcont[0].rotation.z = 3 / 4 * Math.PI; zcont[1].position.set(grmaxx, grmaxy, 0); - zcont[1].rotation.z = 1/4*Math.PI; + zcont[1].rotation.z = 1 / 4 * Math.PI; zcont[2].position.set(grmaxx, grminy, 0); - zcont[2].rotation.z = -1/4*Math.PI; + zcont[2].rotation.z = -1 / 4 * Math.PI; zcont[3].position.set(grminx, grminy, 0); - zcont[3].rotation.z = -3/4*Math.PI; + zcont[3].rotation.z = -3 / 4 * Math.PI; if (!opts.drawany) return; const linex_material = getLineMaterial(this.x_handle), - linex_geom = createLineSegments([grminx, 0, 0, grmaxx, 0, 0], linex_material, null, true); + linex_geom = createLineSegments([grminx, 0, 0, grmaxx, 0, 0], linex_material, null, true); for (let n = 0; n < 2; ++n) { - let line = new LineSegments(linex_geom, linex_material); + let line = new THREE.LineSegments(linex_geom, linex_material); line.position.set(0, grminy, n === 0 ? grminz : grmaxz); - line.xyboxid = 2; line.bottom = (n === 0); + line.xyboxid = 2; + line.bottom = (n === 0); top.add(line); - line = new LineSegments(linex_geom, linex_material); + line = new THREE.LineSegments(linex_geom, linex_material); line.position.set(0, grmaxy, n === 0 ? grminz : grmaxz); - line.xyboxid = 4; line.bottom = (n === 0); + line.xyboxid = 4; + line.bottom = (n === 0); top.add(line); } const liney_material = getLineMaterial(this.y_handle), - liney_geom = createLineSegments([0, grminy, 0, 0, grmaxy, 0], liney_material, null, true); + liney_geom = createLineSegments([0, grminy, 0, 0, grmaxy, 0], liney_material, null, true); for (let n = 0; n < 2; ++n) { - let line = new LineSegments(liney_geom, liney_material); + let line = new THREE.LineSegments(liney_geom, liney_material); line.position.set(grminx, 0, n === 0 ? grminz : grmaxz); - line.xyboxid = 3; line.bottom = (n === 0); + line.xyboxid = 3; + line.bottom = n === 0; top.add(line); - line = new LineSegments(liney_geom, liney_material); + line = new THREE.LineSegments(liney_geom, liney_material); line.position.set(grmaxx, 0, n === 0 ? grminz : grmaxz); - line.xyboxid = 1; line.bottom = (n === 0); + line.xyboxid = 1; + line.bottom = n === 0; top.add(line); } const linez_material = getLineMaterial(this.z_handle), - linez_geom = createLineSegments([0, 0, grminz, 0, 0, grmaxz], linez_material, null, true); + linez_geom = createLineSegments([0, 0, grminz, 0, 0, grmaxz], linez_material, null, true); for (let n = 0; n < 4; ++n) { - const line = new LineSegments(linez_geom, linez_material); + const line = new THREE.LineSegments(linez_geom, linez_material); line.zboxid = zcont[n].zid; line.position.copy(zcont[n].position); top.add(line); @@ -1428,14 +1511,14 @@ function drawXYZ(toplevel, AxisPainter, opts) { } -/** @summary Converts 3D coordiante to the pad NDC +/** @summary Converts 3D coordinate to the pad NDC * @private */ function convert3DtoPadNDC(x, y, z) { x = this.x_handle.gr(x); y = this.y_handle.gr(y); z = this.z_handle.gr(z); - const vector = new Vector3().set(x, y, z); + const vector = new THREE.Vector3().set(x, y, z); // map to normalized device coordinate (NDC) space vector.project(this.camera); @@ -1444,8 +1527,8 @@ function convert3DtoPadNDC(x, y, z) { vector.y = (vector.y + 1) / 2; const pp = this.getPadPainter(), - pw = pp?.getPadWidth(), - ph = pp?.getPadHeight(); + pw = pp?.getPadWidth(), + ph = pp?.getPadHeight(); if (pw && ph) { vector.x = (this.scene_x + vector.x * this.scene_width) / pw; @@ -1457,14 +1540,88 @@ function convert3DtoPadNDC(x, y, z) { /** @summary Assign 3D methods for frame painter * @private */ -function assignFrame3DMethods(fpainter) { - Object.assign(fpainter, { create3DScene, add3DMesh, remove3DMeshes, render3D, resize3D, change3DCamera, highlightBin3D, set3DOptions, drawXYZ, convert3DtoPadNDC }); +function assignFrame3DMethods(fp) { + Object.assign(fp, { + create3DScene, add3DMesh, get3DMeshes, remove3DMeshes, getRenderer, + render3D, resize3D, change3DCamera, highlightBin3D, set3DOptions, drawXYZ, convert3DtoPadNDC + }); +} + + +/** @summary Create 3D objects in the frame + * @private */ +async function crete3DFrame(painter, AxisPainterClass, render3d = constants.Render3D.None) { + const fp = painter.getFramePainter(), + o = painter.getOptions(), + histo = painter.getHisto(), + ndim = painter.getDimension(); + + assignFrame3DMethods(fp); + + return fp.create3DScene(render3d, o.x3dscale, o.y3dscale, o.Ortho).then(() => { + fp.setAxesRanges(histo.fXaxis, painter.xmin, painter.xmax, histo.fYaxis, painter.ymin, painter.ymax, histo.fZaxis, painter.zmin, painter.zmax, painter); + fp.set3DOptions(o); + fp.drawXYZ(fp.toplevel, AxisPainterClass, { + ndim, + use_y_for_z: ndim === 1, + hist_painter: painter, + zmult: o.zmult ?? 1, + zoom: (render3d !== constants.Render3D.None) && settings.Zooming, + draw: o.Axis !== -1, + drawany: o.isCartesian(), + reverse_x: o.RevX, + reverse_y: o.RevY + }); + return fp; + }); +} + +function _meshLegoToolTip(intersect) { + if ((intersect.faceIndex < 0) || (intersect.faceIndex >= this.face_to_bins_index.length)) + return null; + + const p = this.tip_painter; + + if (!p) { + console.error('painter for tip handling is not there'); + return null; + } + + const handle = this.handle, + fp = p.getFramePainter(), + histo = p.getHisto(), + tip = p.get3DToolTip(this.face_to_bins_index[intersect.faceIndex]), + x1 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, handle.grx[tip.ix - 1] + handle.xbar1 * (handle.grx[tip.ix] - handle.grx[tip.ix - 1]))), + x2 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, handle.grx[tip.ix - 1] + handle.xbar2 * (handle.grx[tip.ix] - handle.grx[tip.ix - 1]))), + y1 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, handle.gry[tip.iy - 1] + handle.ybar1 * (handle.gry[tip.iy] - handle.gry[tip.iy - 1]))), + y2 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, handle.gry[tip.iy - 1] + handle.ybar2 * (handle.gry[tip.iy] - handle.gry[tip.iy - 1]))); + + tip.x1 = Math.min(x1, x2); + tip.x2 = Math.max(x1, x2); + tip.y1 = Math.min(y1, y2); + tip.y2 = Math.max(y1, y2); + + let binz1 = this.baseline, binz2 = tip.value; + if (histo.$baseh) + binz1 = histo.$baseh.getBinContent(tip.ix, tip.iy); + if (binz2 < binz1) + [binz1, binz2] = [binz2, binz1]; + + tip.z1 = fp.grz(Math.max(this.zmin, binz1)); + tip.z2 = fp.grz(Math.min(this.zmax, binz2)); + + tip.color = this.tip_color; + tip.$painter = p; + tip.$projection = (p.getDimension() === 2) && isFunc(p.isProjection) && p.isProjection(); + + return tip; } /** @summary Draw histograms in 3D mode * @private */ function drawBinsLego(painter, is_v7 = false) { - if (!painter.draw_content) return; + if (!painter.draw_content) + return; // Perform TH1/TH2 lego plot with BufferGeometry @@ -1475,8 +1632,8 @@ function drawBinsLego(painter, is_v7 = false) { // reduced line segments rsegments = [0, 1, 1, 2, 2, 3, 3, 0], // reduced vertices - rvertices = [new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0)], - main = painter.getFramePainter(), + rvertices = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 1, 0), new THREE.Vector3(1, 0, 0)], + fp = painter.getFramePainter(), handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), test_cutg = painter.options.cutg, i1 = handle.i1, i2 = handle.i2, j1 = handle.j1, j2 = handle.j2, @@ -1485,19 +1642,20 @@ function drawBinsLego(painter, is_v7 = false) { split_faces = (painter.options.Lego === 11) || (painter.options.Lego === 13), // split each layer on two parts use16indx = (histo.getBin(i2, j2) < 0xFFFF); // if bin ID fit into 16 bit, use smaller arrays for intersect indexes - if ((i1 >= i2) || (j1 >= j2)) return; + if ((i1 >= i2) || (j1 >= j2)) + return; - let zmin, zmax, i, j, k, vert, x1, x2, y1, y2, binz1, binz2, reduced, nobottom, notop, - axis_zmin = main.z_handle.getScaleMin(), - axis_zmax = main.z_handle.getScaleMax(); + let zmin, zmax, i, j, k, vert, binz1, binz2, reduced, nobottom, notop, + axis_zmin = fp.z_handle.getScaleMin(), + axis_zmax = fp.z_handle.getScaleMax(); const getBinContent = (ii, jj, level) => { // return bin content in binz1, binz2, reduced flags // return true if bin should be displayed - binz2 = histo.getBinContent(ii+1, jj+1); + binz2 = histo.getBinContent(ii + 1, jj + 1); if (basehisto) - binz1 = basehisto.getBinContent(ii+1, jj+1); + binz1 = basehisto.getBinContent(ii + 1, jj + 1); else if (painter.options.BaseLine !== false) binz1 = painter.options.BaseLine; else @@ -1505,20 +1663,24 @@ function drawBinsLego(painter, is_v7 = false) { if (binz2 < binz1) [binz1, binz2] = [binz2, binz1]; - if ((binz1 >= zmax) || (binz2 < zmin)) return false; + if ((binz1 >= zmax) || (binz2 < zmin)) + return false; - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(ii + 0.5), - histo.fYaxis.GetBinCoord(jj + 0.5))) return false; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(ii + 0.5), histo.fYaxis.GetBinCoord(jj + 0.5))) + return false; reduced = (binz2 === zmin) || (binz1 >= binz2); - if (!reduced || (level > 0)) return true; + if (!reduced || (level > 0)) + return true; - if (basehisto) return false; // do not draw empty bins on top of other bins + if (basehisto) + return false; // do not draw empty bins on top of other bins - if (painter.options.Zero || (axis_zmin > 0)) return true; + if (painter.options.Zero || (axis_zmin > 0)) + return true; - return painter._show_empty_bins; + return painter.options.ShowEmpty; }; let levels = [axis_zmin, axis_zmax], palette = null; @@ -1529,42 +1691,44 @@ function drawBinsLego(painter, is_v7 = false) { // drawing colors levels, axis can not exceed palette if (is_v7) { - palette = main.getHistPalette(); - painter.createContour(main, palette, { full_z_range: true }); + palette = fp.getHistPalette(); + painter.createContour(fp, palette, { full_z_range: true }); levels = palette.getContour(); - axis_zmin = levels[0]; - axis_zmax = levels[levels.length-1]; + axis_zmin = levels.at(0); + axis_zmax = levels.at(-1); } else { - const cntr = painter.createContour(histo.fContour ? histo.fContour.length : 20, main.lego_zmin, main.lego_zmax); + const cntr = painter.createContour(histo.fContour ? histo.fContour.length : 20, fp.lego_zmin, fp.lego_zmax); levels = cntr.arr; palette = painter.getHistPalette(); - // axis_zmin = levels[0]; - // axis_zmax = levels[levels.length-1]; } } - for (let nlevel = 0; nlevel < levels.length-1; ++nlevel) { + for (let nlevel = 0; nlevel < levels.length - 1; ++nlevel) { zmin = levels[nlevel]; - zmax = levels[nlevel+1]; + zmax = levels[nlevel + 1]; // artificially extend last level of color palette to maximal visible value - if (palette && (nlevel === levels.length-2) && zmax < axis_zmax) zmax = axis_zmax; + if (palette && (nlevel === levels.length - 2) && zmax < axis_zmax) + zmax = axis_zmax; - const grzmin = main.grz(zmin), grzmax = main.grz(zmax); - let z1 = 0, z2 = 0, numvertices = 0, num2vertices = 0; + const grzmin = fp.grz(zmin), grzmax = fp.grz(zmax); + let numvertices = 0, num2vertices = 0; // now calculate size of buffer geometry for boxes for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, nlevel)) continue; + if (!getBinContent(i, j, nlevel)) + continue; nobottom = !reduced && (nlevel > 0); - notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2); + notop = !reduced && (binz2 > zmax) && (nlevel < levels.length - 2); numvertices += (reduced ? 12 : indicies.length); - if (nobottom) numvertices -= 6; - if (notop) numvertices -= 6; + if (nobottom) + numvertices -= 6; + if (notop) + numvertices -= 6; if (split_faces && !reduced) { numvertices -= 12; @@ -1573,29 +1737,29 @@ function drawBinsLego(painter, is_v7 = false) { } } - const positions = new Float32Array(numvertices*3), - normals = new Float32Array(numvertices*3), - face_to_bins_index = use16indx ? new Uint16Array(numvertices/3) : new Uint32Array(numvertices/3), - pos2 = (num2vertices === 0) ? null : new Float32Array(num2vertices*3), - norm2 = (num2vertices === 0) ? null : new Float32Array(num2vertices*3), - face_to_bins_indx2 = (num2vertices === 0) ? null : (use16indx ? new Uint16Array(num2vertices/3) : new Uint32Array(num2vertices/3)); + const positions = new Float32Array(numvertices * 3), + normals = new Float32Array(numvertices * 3), + face_to_bins_index = use16indx ? new Uint16Array(numvertices / 3) : new Uint32Array(numvertices / 3), + pos2 = (num2vertices === 0) ? null : new Float32Array(num2vertices * 3), + norm2 = (num2vertices === 0) ? null : new Float32Array(num2vertices * 3), + face_to_bins_indx2 = (num2vertices === 0) ? null : (use16indx ? new Uint16Array(num2vertices / 3) : new Uint32Array(num2vertices / 3)); - let v = 0, v2 = 0, vert, k, nn; + let v = 0, v2 = 0, nn; for (i = i1; i < i2; ++i) { - x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1] - handle.grx[i]); - x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1] - handle.grx[i]); + const x1 = handle.grx[i] + handle.xbar1 * (handle.grx[i + 1] - handle.grx[i]), + x2 = handle.grx[i] + handle.xbar2 * (handle.grx[i + 1] - handle.grx[i]); for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, nlevel)) continue; + if (!getBinContent(i, j, nlevel)) + continue; nobottom = !reduced && (nlevel > 0); - notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2); + notop = !reduced && (binz2 > zmax) && (nlevel < levels.length - 2); - y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]); - y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]); - - z1 = (binz1 <= zmin) ? grzmin : main.grz(binz1); - z2 = (binz2 > zmax) ? grzmax : main.grz(binz2); + const y1 = handle.gry[j] + handle.ybar1 * (handle.gry[j + 1] - handle.gry[j]), + y2 = handle.gry[j] + handle.ybar2 * (handle.gry[j + 1] - handle.gry[j]), + z1 = (binz1 <= zmin) ? grzmin : fp.grz(binz1), + z2 = (binz2 > zmax) ? grzmax : fp.grz(binz2); nn = 0; // counter over the normals, each normals correspond to 6 vertices k = 0; // counter over vertices @@ -1606,9 +1770,10 @@ function drawBinsLego(painter, is_v7 = false) { k += 24; } - const bin_index = histo.getBin(i+1, j+1); + const bin_index = histo.getBin(i + 1, j + 1); let size = indicies.length; - if (nobottom) size -= 6; + if (nobottom) + size -= 6; // array over all vertices of the single bin while (k < size) { @@ -1616,23 +1781,25 @@ function drawBinsLego(painter, is_v7 = false) { if (split_faces && (k < 12)) { pos2[v2] = x1 + vert.x * (x2 - x1); - pos2[v2+1] = y1 + vert.y * (y2 - y1); - pos2[v2+2] = z1 + vert.z * (z2 - z1); + pos2[v2 + 1] = y1 + vert.y * (y2 - y1); + pos2[v2 + 2] = z1 + vert.z * (z2 - z1); norm2[v2] = vnormals[nn]; - norm2[v2+1] = vnormals[nn+1]; - norm2[v2+2] = vnormals[nn+2]; - if (v2 % 9 === 0) face_to_bins_indx2[v2/9] = bin_index; // remember which bin corresponds to the face + norm2[v2 + 1] = vnormals[nn + 1]; + norm2[v2 + 2] = vnormals[nn + 2]; + if (v2 % 9 === 0) + face_to_bins_indx2[v2 / 9] = bin_index; // remember which bin corresponds to the face v2 += 3; } else { positions[v] = x1 + vert.x * (x2 - x1); - positions[v+1] = y1 + vert.y * (y2 - y1); - positions[v+2] = z1 + vert.z * (z2 - z1); + positions[v + 1] = y1 + vert.y * (y2 - y1); + positions[v + 2] = z1 + vert.z * (z2 - z1); normals[v] = vnormals[nn]; - normals[v+1] = vnormals[nn+1]; - normals[v+2] = vnormals[nn+2]; - if (v % 9 === 0) face_to_bins_index[v/9] = bin_index; // remember which bin corresponds to the face + normals[v + 1] = vnormals[nn + 1]; + normals[v + 2] = vnormals[nn + 2]; + if (v % 9 === 0) + face_to_bins_index[v / 9] = bin_index; // remember which bin corresponds to the face v += 3; } @@ -1641,7 +1808,8 @@ function drawBinsLego(painter, is_v7 = false) { if (k % 6 === 0) { nn += 3; if (notop && (k === indicies.length - 12)) { - k += 6; nn += 3; // jump over notop indexes + k += 6; + nn += 3; // jump over no-top indexes } } } @@ -1654,86 +1822,58 @@ function drawBinsLego(painter, is_v7 = false) { if (palette) fcolor = is_v7 ? palette.getColor(nlevel) : palette.calcColor(nlevel, levels.length); - else if ((painter.options.Lego === 1) || (rootcolor < 2)) { + else if ((painter.options.Lego === 1) || (rootcolor < 2)) { rootcolor = 1; fcolor = 'white'; } - const material = new MeshBasicMaterial(getMaterialArgs(fcolor, { vertexColors: false })), - mesh = new Mesh(geometry, material); + const material = new THREE.MeshBasicMaterial(getMaterialArgs(fcolor, { vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); mesh.face_to_bins_index = face_to_bins_index; - mesh.painter = painter; + mesh.tip_painter = painter; mesh.zmin = axis_zmin; mesh.zmax = axis_zmax; mesh.baseline = (painter.options.BaseLine !== false) ? painter.options.BaseLine : (painter.options.Zero ? axis_zmin : 0); - mesh.tip_color = (rootcolor=== 3) ? 0xFF0000 : 0x00FF00; + mesh.tip_color = (rootcolor === 3) ? 0xFF0000 : 0x00FF00; mesh.handle = handle; + mesh.tooltip = _meshLegoToolTip; - mesh.tooltip = function(intersect) { - if ((intersect.faceIndex < 0) || (intersect.faceIndex >= this.face_to_bins_index.length)) return null; - - const p = this.painter, - handle = this.handle, - main = p.getFramePainter(), - histo = p.getHisto(), - tip = p.get3DToolTip(this.face_to_bins_index[intersect.faceIndex]), - x1 = Math.min(main.size_x3d, Math.max(-main.size_x3d, handle.grx[tip.ix-1] + handle.xbar1*(handle.grx[tip.ix] - handle.grx[tip.ix-1]))), - x2 = Math.min(main.size_x3d, Math.max(-main.size_x3d, handle.grx[tip.ix-1] + handle.xbar2*(handle.grx[tip.ix] - handle.grx[tip.ix-1]))), - y1 = Math.min(main.size_y3d, Math.max(-main.size_y3d, handle.gry[tip.iy-1] + handle.ybar1*(handle.gry[tip.iy] - handle.gry[tip.iy-1]))), - y2 = Math.min(main.size_y3d, Math.max(-main.size_y3d, handle.gry[tip.iy-1] + handle.ybar2*(handle.gry[tip.iy] - handle.gry[tip.iy-1]))); - - tip.x1 = Math.min(x1, x2); - tip.x2 = Math.max(x1, x2); - tip.y1 = Math.min(y1, y2); - tip.y2 = Math.max(y1, y2); - - let binz1 = this.baseline, binz2 = tip.value; - if (histo.$baseh) binz1 = histo.$baseh.getBinContent(tip.ix, tip.iy); - if (binz2 < binz1) [binz1, binz2] = [binz2, binz1]; - - tip.z1 = main.grz(Math.max(this.zmin, binz1)); - tip.z2 = main.grz(Math.min(this.zmax, binz2)); - - tip.color = this.tip_color; - tip.$painter = p; - tip.$projection = p.is_projection && (p.getDimension() === 2); - - return tip; - }; - - main.add3DMesh(mesh); + fp.add3DMesh(mesh); if (num2vertices > 0) { const geom2 = createLegoGeom(painter, pos2, norm2), - color2 = (rootcolor < 2) ? new Color(0xFF0000) : new Color(d3_rgb(fcolor).darker(0.5).toString()), - material2 = new MeshBasicMaterial({ color: color2, vertexColors: false }), - mesh2 = new Mesh(geom2, material2); + color2 = new THREE.Color(rootcolor < 2 ? 0xFF0000 : d3_rgb(fcolor).darker(0.5).toString()), + material2 = new THREE.MeshBasicMaterial({ color: color2, vertexColors: false }), + mesh2 = new THREE.Mesh(geom2, material2); mesh2.face_to_bins_index = face_to_bins_indx2; - mesh2.painter = painter; + mesh2.tip_painter = painter; mesh2.handle = mesh.handle; - mesh2.tooltip = mesh.tooltip; + mesh2.tooltip = _meshLegoToolTip; mesh2.zmin = mesh.zmin; mesh2.zmax = mesh.zmax; mesh2.baseline = mesh.baseline; mesh2.tip_color = mesh.tip_color; - main.add3DMesh(mesh2); + fp.add3DMesh(mesh2); } } // lego3 or lego4 do not draw border lines - if (painter.options.Lego > 12) return; + if (painter.options.Lego > 12) + return; // DRAW LINE BOXES let numlinevertices = 0, numsegments = 0; - zmax = axis_zmax; zmin = axis_zmin; + zmax = axis_zmax; + zmin = axis_zmin; for (i = i1; i < i2; ++i) { for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, 0)) continue; + if (!getBinContent(i, j, 0)) + continue; // calculate required buffer size for line segments numlinevertices += (reduced ? rvertices.length : vertices.length); @@ -1746,41 +1886,41 @@ function drawBinsLego(painter, is_v7 = false) { // skip index usage at all. It happens for relatively large histograms (100x100 bins) const uselineindx = (numlinevertices <= 0xFFF0); - if (!uselineindx) numlinevertices = numsegments*3; + if (!uselineindx) + numlinevertices = numsegments * 3; const lpositions = new Float32Array(numlinevertices * 3), lindicies = uselineindx ? new Uint16Array(numsegments) : null, - grzmin = main.grz(axis_zmin), - grzmax = main.grz(axis_zmax); - let z1 = 0, z2 = 0, ll = 0, ii = 0; + grzmin = fp.grz(axis_zmin), + grzmax = fp.grz(axis_zmax); + let ll = 0, ii = 0; for (i = i1; i < i2; ++i) { - x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1] - handle.grx[i]); - x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1] - handle.grx[i]); + const x1 = handle.grx[i] + handle.xbar1 * (handle.grx[i + 1] - handle.grx[i]), + x2 = handle.grx[i] + handle.xbar2 * (handle.grx[i + 1] - handle.grx[i]); for (j = j1; j < j2; ++j) { - if (!getBinContent(i, j, 0)) continue; - - y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]); - y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]); - - z1 = (binz1 <= axis_zmin) ? grzmin : main.grz(binz1); - z2 = (binz2 > axis_zmax) ? grzmax : main.grz(binz2); - - const seg = reduced ? rsegments : segments, + if (!getBinContent(i, j, 0)) + continue; + + const y1 = handle.gry[j] + handle.ybar1 * (handle.gry[j + 1] - handle.gry[j]), + y2 = handle.gry[j] + handle.ybar2 * (handle.gry[j + 1] - handle.gry[j]), + z1 = (binz1 <= axis_zmin) ? grzmin : fp.grz(binz1), + z2 = (binz2 > axis_zmax) ? grzmax : fp.grz(binz2), + seg = reduced ? rsegments : segments, vvv = reduced ? rvertices : vertices; if (uselineindx) { - // array of indicies for the lines, to avoid duplication of points + // array of indices for the lines, to avoid duplication of points for (k = 0; k < seg.length; ++k) { // intersect_index[ii] = bin_index; - lindicies[ii++] = ll/3 + seg[k]; + lindicies[ii++] = ll / 3 + seg[k]; } for (k = 0; k < vvv.length; ++k) { vert = vvv[k]; lpositions[ll] = x1 + vert.x * (x2 - x1); - lpositions[ll+1] = y1 + vert.y * (y2 - y1); - lpositions[ll+2] = z1 + vert.z * (z2 - z1); + lpositions[ll + 1] = y1 + vert.y * (y2 - y1); + lpositions[ll + 2] = z1 + vert.z * (z2 - z1); ll += 3; } } else { @@ -1788,8 +1928,8 @@ function drawBinsLego(painter, is_v7 = false) { for (k = 0; k < seg.length; ++k) { vert = vvv[seg[k]]; lpositions[ll] = x1 + vert.x * (x2 - x1); - lpositions[ll+1] = y1 + vert.y * (y2 - y1); - lpositions[ll+2] = z1 + vert.z * (z2 - z1); + lpositions[ll + 1] = y1 + vert.y * (y2 - y1); + lpositions[ll + 2] = z1 + vert.z * (z2 - z1); // intersect_index[ll/3] = bin_index; ll += 3; } @@ -1799,175 +1939,189 @@ function drawBinsLego(painter, is_v7 = false) { // create boxes const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), - material = new LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), + material = new THREE.LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), line = createLineSegments(convertLegoBuf(painter, lpositions), material, uselineindx ? lindicies : null); /* line.painter = painter; line.intersect_index = intersect_index; line.tooltip = function(intersect) { - if ((intersect.index < 0) || (intersect.index >= this.intersect_index.length)) return null; + if ((intersect.index < 0) || (intersect.index >= this.intersect_index.length)) + return null; return this.painter.get3DToolTip(this.intersect_index[intersect.index]); } */ - main.add3DMesh(line); + fp.add3DMesh(line); +} + +function _lineErrToolTip(intersect) { + const pos = Math.floor(intersect.index / 6); + if ((pos < 0) || (pos >= this.intersect_index.length)) + return null; + const p = this.tip_painter, + histo = p.getHisto(), + fp = p.getFramePainter(), + tip = p.get3DToolTip(this.intersect_index[pos]), + tx1 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, fp.grx(histo.fXaxis.GetBinLowEdge(tip.ix)))), + tx2 = Math.min(fp.size_x3d, Math.max(-fp.size_x3d, fp.grx(histo.fXaxis.GetBinLowEdge(tip.ix + 1)))), + ty1 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, fp.gry(histo.fYaxis.GetBinLowEdge(tip.iy)))), + ty2 = Math.min(fp.size_y3d, Math.max(-fp.size_y3d, fp.gry(histo.fYaxis.GetBinLowEdge(tip.iy + 1)))); + + tip.x1 = Math.min(tx1, tx2); + tip.x2 = Math.max(tx1, tx2); + tip.y1 = Math.min(ty1, ty2); + tip.y2 = Math.max(ty1, ty2); + + tip.z1 = fp.grz(tip.value - tip.error < this.zmin ? this.zmin : tip.value - tip.error); + tip.z2 = fp.grz(tip.value + tip.error > this.zmax ? this.zmax : tip.value + tip.error); + + tip.color = this.tip_color; + + return tip; } /** @summary Draw TH2 histogram in error mode * @private */ function drawBinsError3D(painter, is_v7 = false) { - const main = painter.getFramePainter(), + const fp = painter.getFramePainter(), histo = painter.getHisto(), handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1 }), - zmin = main.z_handle.getScaleMin(), - zmax = main.z_handle.getScaleMax(), + zmin = fp.z_handle.getScaleMin(), + zmax = fp.z_handle.getScaleMax(), test_cutg = painter.options.cutg; - let i, j, bin, binz, binerr, x1, y1, x2, y2, z1, z2, + let i, j, bin, binz, errs, x1, y1, x2, y2, z1, z2, nsegments = 0, lpos = null, binindx = null, lindx = 0; const check_skip_min = () => { - // return true if minimal histogram value should be skipped - if (painter.options.Zero || (zmin > 0)) return false; - return !painter._show_empty_bins; + // return true if minimal histogram value should be skipped + if (painter.options.Zero || (zmin > 0)) + return false; + return !painter.options.ShowEmpty; }; - // loop over the points - first loop counts points, second fill arrays + // loop over the points - first loop counts points, second fill arrays for (let loop = 0; loop < 2; ++loop) { for (i = handle.i1; i < handle.i2; ++i) { x1 = handle.grx[i]; x2 = handle.grx[i + 1]; for (j = handle.j1; j < handle.j2; ++j) { binz = histo.getBinContent(i + 1, j + 1); - if ((binz < zmin) || (binz > zmax)) continue; - if ((binz === zmin) && check_skip_min()) continue; + if ((binz < zmin) || (binz > zmax)) + continue; + if ((binz === zmin) && check_skip_min()) + continue; - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5))) + continue; // just count number of segments - if (loop === 0) { nsegments += 3; continue; } + if (loop === 0) { + nsegments += 3; + continue; + } bin = histo.getBin(i + 1, j + 1); - binerr = histo.getBinError(bin); + errs = painter.getBinErrors(histo, bin, binz); binindx[lindx / 18] = bin; y1 = handle.gry[j]; y2 = handle.gry[j + 1]; - z1 = main.grz((binz - binerr < zmin) ? zmin : binz - binerr); - z2 = main.grz((binz + binerr > zmax) ? zmax : binz + binerr); + z1 = fp.grz((binz - errs.low < zmin) ? zmin : binz - errs.low); + z2 = fp.grz((binz + errs.up > zmax) ? zmax : binz + errs.up); - lpos[lindx] = x1; lpos[lindx + 3] = x2; + lpos[lindx] = x1; + lpos[lindx + 3] = x2; lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; lindx += 6; lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; - lpos[lindx + 1] = y1; lpos[lindx + 4] = y2; + lpos[lindx + 1] = y1; + lpos[lindx + 4] = y2; lpos[lindx + 2] = lpos[lindx + 5] = (z1 + z2) / 2; lindx += 6; lpos[lindx] = lpos[lindx + 3] = (x1 + x2) / 2; lpos[lindx + 1] = lpos[lindx + 4] = (y1 + y2) / 2; - lpos[lindx + 2] = z1; lpos[lindx + 5] = z2; + lpos[lindx + 2] = z1; + lpos[lindx + 5] = z2; lindx += 6; } } if (loop === 0) { - if (nsegments === 0) return; + if (nsegments === 0) + return; lpos = new Float32Array(nsegments * 6); binindx = new Int32Array(nsegments / 3); } } - // create lines - const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), - material = new LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), - line = createLineSegments(lpos, material); - - line.painter = painter; - line.intersect_index = binindx; - line.zmin = zmin; - line.zmax = zmax; - line.tip_color = (histo.fLineColor === 3) ? 0xFF0000 : 0x00FF00; - - line.tooltip = function(intersect) { - const pos = Math.floor(intersect.index / 6); - if ((pos < 0) || (pos >= this.intersect_index.length)) return null; - const p = this.painter, - histo = p.getHisto(), - main = p.getFramePainter(), - tip = p.get3DToolTip(this.intersect_index[pos]), - x1 = Math.min(main.size_x3d, Math.max(-main.size_x3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix)))), - x2 = Math.min(main.size_x3d, Math.max(-main.size_x3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1)))), - y1 = Math.min(main.size_y3d, Math.max(-main.size_y3d, main.gry(histo.fYaxis.GetBinLowEdge(tip.iy)))), - y2 = Math.min(main.size_y3d, Math.max(-main.size_y3d, main.gry(histo.fYaxis.GetBinLowEdge(tip.iy+1)))); - - tip.x1 = Math.min(x1, x2); - tip.x2 = Math.max(x1, x2); - tip.y1 = Math.min(y1, y2); - tip.y2 = Math.max(y1, y2); - - tip.z1 = main.grz(tip.value-tip.error < this.zmin ? this.zmin : tip.value-tip.error); - tip.z2 = main.grz(tip.value+tip.error > this.zmax ? this.zmax : tip.value+tip.error); - - tip.color = this.tip_color; - - return tip; - }; - - main.add3DMesh(line); + // create lines + const lcolor = is_v7 ? painter.v7EvalColor('line_color', 'lightblue') : painter.getColor(histo.fLineColor), + material = new THREE.LineBasicMaterial(getMaterialArgs(lcolor, { linewidth: is_v7 ? painter.v7EvalAttr('line_width', 1) : histo.fLineWidth })), + line = createLineSegments(lpos, material); + + line.tip_painter = painter; + line.intersect_index = binindx; + line.zmin = zmin; + line.zmax = zmax; + line.tip_color = (histo.fLineColor === 3) ? 0xFF0000 : 0x00FF00; + line.tooltip = _lineErrToolTip; + + fp.add3DMesh(line); } /** @summary Draw TH2 as 3D contour plot * @private */ function drawBinsContour3D(painter, realz = false, is_v7 = false) { // for contour plots one requires handle with full range - const main = painter.getFramePainter(), + const fp = painter.getFramePainter(), handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 100, middle: 0 }), histo = painter.getHisto(), // get levels levels = painter.getContourLevels(), // init contour if not exists palette = painter.getHistPalette(), pnts = []; - let layerz = 2*main.size_z3d; - - buildHist2dContour(histo, handle, levels, palette, - (colindx, xp, yp, iminus, iplus, ilevel) => { - // ignore less than three points - if (iplus - iminus < 3) return; - - if (realz) { - layerz = main.grz(levels[ilevel]); - if ((layerz < 0) || (layerz > 2*main.size_z3d)) return; - } - - for (let i=iminus; i<iplus; ++i) { - pnts.push(xp[i], yp[i], layerz); - pnts.push(xp[i+1], yp[i+1], layerz); - } + let layerz = 2 * fp.size_z3d; + + buildHist2dContour(histo, handle, levels, palette, (colindx, xp, yp, iminus, iplus, ilevel) => { + // ignore less than three points + if (iplus - iminus < 3) + return; + + if (realz) { + layerz = fp.grz(levels[ilevel]); + if ((layerz < 0) || (layerz > 2 * fp.size_z3d)) + return; + } + + for (let i = iminus; i < iplus; ++i) { + pnts.push(xp[i], yp[i], layerz); + pnts.push(xp[i + 1], yp[i + 1], layerz); } - ); + }); const lines = createLineSegments(pnts, create3DLineMaterial(painter, is_v7 ? 'line_' : histo)); - main.add3DMesh(lines); + fp.add3DMesh(lines); } /** @summary Draw TH2 histograms in surf mode * @private */ function drawBinsSurf3D(painter, is_v7 = false) { const histo = painter.getHisto(), - main = painter.getFramePainter(), - axis_zmin = main.z_handle.getScaleMin(), - // axis_zmax = main.z_handle.getScaleMax(); - // first adjust ranges - main_grz = !main.logz ? main.grz : value => (value < axis_zmin) ? -0.1 : main.grz(value), - main_grz_min = 0, main_grz_max = 2*main.size_z3d; - - let handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 1, middle: 0.5, - cutg: isFunc(painter.options?.cutg?.IsInside) ? painter.options?.cutg : null }); - if ((handle.i2 - handle.i1 < 2) || (handle.j2 - handle.j1 < 2)) return; + fp = painter.getFramePainter(), + axis_zmin = fp.z_handle.getScaleMin(), + main_grz = !fp.logz ? fp.grz : value => { return (value < axis_zmin) ? -0.1 : fp.grz(value); }, + main_grz_min = 0, main_grz_max = 2 * fp.size_z3d; + + let handle = painter.prepareDraw({ + rounding: false, use3d: true, extra: 1, middle: 0.5, + cutg: isFunc(painter.options?.cutg?.IsInside) ? painter.options?.cutg : null + }); + if ((handle.i2 - handle.i1 < 2) || (handle.j2 - handle.j1 < 2)) + return; let ilevels = null, levels = null, palette = null; @@ -1976,30 +2130,62 @@ function drawBinsSurf3D(painter, is_v7 = false) { if (is_v7) { let need_palette = 0; switch (painter.options.Surf) { - case 11: need_palette = 2; break; + case 11: + need_palette = 2; + break; case 12: case 15: // make surf5 same as surf2 - case 17: need_palette = 2; handle.dolines = false; break; - case 14: handle.dolines = false; handle.donormals = true; break; - case 16: need_palette = 1; handle.dogrid = true; handle.dolines = false; break; - default: ilevels = main.z_handle.createTicks(true); handle.dogrid = true; break; + case 17: + need_palette = 2; + handle.dolines = false; + break; + case 14: + handle.dolines = false; + handle.donormals = true; + break; + case 16: + need_palette = 1; + handle.dogrid = true; + handle.dolines = false; + break; + default: + ilevels = fp.z_handle.createTicks(true); + handle.dogrid = true; + break; } if (need_palette > 0) { - palette = main.getHistPalette(); + palette = fp.getHistPalette(); if (need_palette === 2) - painter.createContour(main, palette, { full_z_range: true }); + painter.createContour(fp, palette, { full_z_range: true }); ilevels = palette.getContour(); } } else { switch (painter.options.Surf) { - case 11: ilevels = painter.getContourLevels(); palette = painter.getHistPalette(); break; + case 11: + ilevels = painter.getContourLevels(); + palette = painter.getHistPalette(); + break; case 12: case 15: // make surf5 same as surf2 - case 17: ilevels = painter.getContourLevels(); palette = painter.getHistPalette(); handle.dolines = false; break; - case 14: handle.dolines = false; handle.donormals = true; break; - case 16: ilevels = painter.getContourLevels(); handle.dogrid = true; handle.dolines = false; break; - default: ilevels = main.z_handle.createTicks(true); handle.dogrid = true; break; + case 17: + ilevels = painter.getContourLevels(); + palette = painter.getHistPalette(); + handle.dolines = false; + break; + case 14: + handle.dolines = false; + handle.donormals = true; + break; + case 16: + ilevels = painter.getContourLevels(); + handle.dogrid = true; + handle.dolines = false; + break; + default: + ilevels = fp.z_handle.createTicks(true); + handle.dogrid = true; + break; } } @@ -2024,9 +2210,10 @@ function drawBinsSurf3D(painter, is_v7 = false) { if (handle.donormals && (lvl === 1)) { for (let ii = handle.i1; ii < handle.i2; ++ii) { for (let jj = handle.j1; jj < handle.j2; ++jj) { - const bin = ((ii-handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; + const bin = ((ii - handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; - if (normindx[bin] === -1) continue; // nothing there + if (normindx[bin] === -1) + continue; // nothing there const beg = (normindx[bin] >= 0) ? bin : bin + 9 + normindx[bin], end = bin + 8; @@ -2034,19 +2221,22 @@ function drawBinsSurf3D(painter, is_v7 = false) { for (let kk = beg; kk < end; ++kk) { const indx = normindx[kk]; - if (indx < 0) return console.error('FAILURE in NORMALS RECALCULATIONS'); + if (indx < 0) + return console.error('FAILURE in NORMALS RECALCULATIONS'); sumx += normals[indx]; - sumy += normals[indx+1]; - sumz += normals[indx+2]; + sumy += normals[indx + 1]; + sumz += normals[indx + 2]; } - sumx = sumx/(end-beg); sumy = sumy/(end-beg); sumz = sumz/(end-beg); + sumx /= end - beg; + sumy /= end - beg; + sumz /= end - beg; for (let kk = beg; kk < end; ++kk) { const indx = normindx[kk]; normals[indx] = sumx; - normals[indx+1] = sumy; - normals[indx+2] = sumz; + normals[indx + 1] = sumy; + normals[indx + 2] = sumz; } } } @@ -2054,23 +2244,29 @@ function drawBinsSurf3D(painter, is_v7 = false) { let color, material; if (is_v7) - color = palette?.getColor(lvl-1) ?? painter.getColor(5); - else if (palette) + color = palette?.getColor(lvl - 1) ?? painter.getColor(5); + else if (palette) color = palette.calcColor(lvl, levels.length); - else { - color = histo.fFillColor > 1 ? painter.getColor(histo.fFillColor) : 'white'; - if ((painter.options.Surf === 14) && (histo.fFillColor < 2)) color = painter.getColor(48); + else { + const indx = painter.options.histoFillColor || histo.fFillColor; + if (painter.options.Surf === 13) + color = 'white'; + else if (painter.options.Surf === 14) + color = indx > 1 ? painter.getColor(indx) : 'grey'; + else + color = indx > 1 ? painter.getColor(indx) : 'white'; } - if (!color) color = 'white'; + if (!color) + color = 'white'; if (painter.options.Surf === 14) - material = new MeshLambertMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })); + material = new THREE.MeshLambertMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })); else - material = new MeshBasicMaterial(getMaterialArgs(color, { side: DoubleSide, vertexColors: false })); + material = new THREE.MeshBasicMaterial(getMaterialArgs(color, { side: THREE.DoubleSide, vertexColors: false })); - const mesh = new Mesh(geometry, material); + const mesh = new THREE.Mesh(geometry, material); - main.add3DMesh(mesh); + fp.add3DMesh(mesh); mesh.painter = painter; // to let use it with context menu }, (isgrid, lpos) => { @@ -2079,15 +2275,15 @@ function drawBinsSurf3D(painter, is_v7 = false) { if (isgrid) { material = (painter.options.Surf === 1) - ? new LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 }) - : new LineBasicMaterial(getMaterialArgs(color)); + ? new THREE.LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 }) + : new THREE.LineBasicMaterial(getMaterialArgs(color)); } else - material = new LineBasicMaterial(getMaterialArgs(color, { linewidth: histo.fLineWidth })); + material = new THREE.LineBasicMaterial(getMaterialArgs(color, { linewidth: histo.fLineWidth })); const line = createLineSegments(convertLegoBuf(painter, lpos, handle.i2 - handle.i1, handle.j2 - handle.j1), material); line.painter = painter; - main.add3DMesh(line); + fp.add3DMesh(line); }); if (painter.options.Surf === 17) @@ -2097,63 +2293,64 @@ function drawBinsSurf3D(painter, is_v7 = false) { handle = painter.prepareDraw({ rounding: false, use3d: true, extra: 100, middle: 0 }); // get levels - const levels = painter.getContourLevels(), // init contour - palette = painter.getHistPalette(); + const levels2 = painter.getContourLevels(), // init contour + palette2 = painter.getHistPalette(); let lastcolindx = -1, layerz = main_grz_max; - buildHist2dContour(histo, handle, levels, palette, - (colindx, xp, yp, iminus, iplus) => { - // no need for duplicated point - if ((xp[iplus] === xp[iminus]) && (yp[iplus] === yp[iminus])) iplus--; - - // ignore less than three points - if (iplus - iminus < 3) return; - - const pnts = []; - - for (let i = iminus; i <= iplus; ++i) { - if ((i === iminus) || (xp[i] !== xp[i-1]) || (yp[i] !== yp[i-1])) - pnts.push(new Vector2(xp[i], yp[i])); - } - - if (pnts.length < 3) return; + buildHist2dContour(histo, handle, levels2, palette2, (colindx, xp, yp, iminus, iplus) => { + // no need for duplicated point + if ((xp[iplus] === xp[iminus]) && (yp[iplus] === yp[iminus])) + iplus--; - const faces = ShapeUtils.triangulateShape(pnts, []); + // ignore less than three points + if (iplus - iminus < 3) + return; - if (!faces || (faces.length === 0)) return; + const pnts = []; - if ((lastcolindx < 0) || (lastcolindx !== colindx)) { - lastcolindx = colindx; - layerz += 5e-5 * main_grz_max; // change layers Z - } + for (let i = iminus; i <= iplus; ++i) { + if ((i === iminus) || (xp[i] !== xp[i - 1]) || (yp[i] !== yp[i - 1])) + pnts.push(new THREE.Vector2(xp[i], yp[i])); + } - const pos = new Float32Array(faces.length*9), - norm = new Float32Array(faces.length*9); - let indx = 0; + const faces = pnts.length < 3 ? null : THREE.ShapeUtils.triangulateShape(pnts, []); - for (let n = 0; n < faces.length; ++n) { - const face = faces[n]; - for (let v = 0; v < 3; ++v) { - const pnt = pnts[face[v]]; - pos[indx] = pnt.x; - pos[indx+1] = pnt.y; - pos[indx+2] = layerz; - norm[indx] = 0; - norm[indx+1] = 0; - norm[indx+2] = 1; + if (!faces?.length) + return; - indx += 3; - } - } + if ((lastcolindx < 0) || (lastcolindx !== colindx)) { + lastcolindx = colindx; + layerz += 5e-5 * main_grz_max; // change layers Z + } - const geometry = createLegoGeom(painter, pos, norm, handle.i2 - handle.i1, handle.j2 - handle.j1), - material = new MeshBasicMaterial(getMaterialArgs(palette.getColor(colindx), { side: DoubleSide, opacity: 0.5, vertexColors: false })), - mesh = new Mesh(geometry, material); - mesh.painter = painter; - main.add3DMesh(mesh); + const pos = new Float32Array(faces.length * 9), + norm = new Float32Array(faces.length * 9); + let indx = 0; + + for (let n = 0; n < faces.length; ++n) { + const face = faces[n]; + for (let v = 0; v < 3; ++v) { + const pnt = pnts[face[v]]; + pos[indx] = pnt.x; + pos[indx + 1] = pnt.y; + pos[indx + 2] = layerz; + norm[indx] = 0; + norm[indx + 1] = 0; + norm[indx + 2] = 1; + + indx += 3; + } } - ); + + const geometry = createLegoGeom(painter, pos, norm, handle.i2 - handle.i1, handle.j2 - handle.j1), + material = new THREE.MeshBasicMaterial(getMaterialArgs(palette2.getColor(colindx), { side: THREE.DoubleSide, opacity: 0.5, vertexColors: false })), + mesh = new THREE.Mesh(geometry, material); + mesh.painter = painter; + fp.add3DMesh(mesh); + }); } } -export { assignFrame3DMethods, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D, convertLegoBuf, createLegoGeom }; +export { assignFrame3DMethods, crete3DFrame, + drawBinsLego, drawBinsError3D, drawBinsContour3D, + drawBinsSurf3D, convertLegoBuf, createLegoGeom }; diff --git a/modules/hist2d/RH1Painter.mjs b/modules/hist2d/RH1Painter.mjs deleted file mode 100644 index 42a545257..000000000 --- a/modules/hist2d/RH1Painter.mjs +++ /dev/null @@ -1,1009 +0,0 @@ -import { gStyle, settings, constants, kInspect } from '../core.mjs'; -import { rgb as d3_rgb } from '../d3.mjs'; -import { floatToString, DrawOptions, buildSvgCurve, addHighlightStyle } from '../base/BasePainter.mjs'; -import { RHistPainter } from './RHistPainter.mjs'; -import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs'; - - -/** - * @summary Painter for RH1 classes - * - * @private - */ - -class RH1Painter extends RHistPainter { - - /** @summary Constructor - * @param {object|string} dom - DOM element or id - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = false; - } - - /** @summary Scan content */ - scanContent(when_axis_changed) { - // if when_axis_changed === true specified, content will be scanned after axis zoom changed - - const histo = this.getHisto(); - if (!histo) return; - - if (!this.nbinsx && when_axis_changed) when_axis_changed = false; - - if (!when_axis_changed) - this.extractAxesProperties(1); - - let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0; - - if (this.isDisplayItem()) { - // take min/max values from the display item - hmin = histo.fContMin; - hmin_nz = histo.fContMinPos; - hmax = histo.fContMax; - hsum = hmax; - } else { - const left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'); - - if (when_axis_changed) - if ((left === this.scan_xleft) && (right === this.scan_xright)) return; - - - this.scan_xleft = left; - this.scan_xright = right; - - let first = true, value, err; - - for (let i = 0; i < this.nbinsx; ++i) { - value = histo.getBinContent(i+1); - hsum += value; - - if ((i<left) || (i>=right)) continue; - - if (value > 0) - if ((hmin_nz === 0) || (value<hmin_nz)) hmin_nz = value; - if (first) { - hmin = hmax = value; - first = false; - } - - err = 0; - - hmin = Math.min(hmin, value - err); - hmax = Math.max(hmax, value + err); - } - } - - this.stat_entries = hsum; - - this.hmin = hmin; - this.hmax = hmax; - - this.ymin_nz = hmin_nz; // value can be used to show optimal log scale - - if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) - this.draw_content = false; - else - this.draw_content = true; - - if (this.draw_content) { - if (hmin >= hmax) { - if (hmin === 0) { - this.ymin = 0; - this.ymax = 1; - } else if (hmin < 0) { - this.ymin = 2 * hmin; - this.ymax = 0; - } else { - this.ymin = 0; - this.ymax = hmin * 2; - } - } else { - const dy = (hmax - hmin) * 0.05; - this.ymin = hmin - dy; - if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; - this.ymax = hmax + dy; - } - } - } - - /** @summary Count statistic */ - countStat(cond) { - const histo = this.getHisto(), xaxis = this.getAxis('x'), - left = this.getSelectIndex('x', 'left'), - right = this.getSelectIndex('x', 'right'), - stat_sumwy = 0, stat_sumwy2 = 0, - res = { name: 'histo', meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, entries: this.stat_entries, xmax: 0, wmax: 0 }; - let stat_sumw = 0, stat_sumwx = 0, stat_sumwx2 = 0, - i, xx = 0, w = 0, xmax = null, wmax = null; - - for (i = left; i < right; ++i) { - xx = xaxis.GetBinCoord(i+0.5); - - if (cond && !cond(xx)) continue; - - w = histo.getBinContent(i + 1); - - if ((xmax === null) || (w > wmax)) { xmax = xx; wmax = w; } - - stat_sumw += w; - stat_sumwx += w * xx; - stat_sumwx2 += w * xx**2; - } - - res.integral = stat_sumw; - - if (Math.abs(stat_sumw) > 1e-300) { - res.meanx = stat_sumwx / stat_sumw; - res.meany = stat_sumwy / stat_sumw; - res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany**2)); - } - - if (xmax !== null) { - res.xmax = xmax; - res.wmax = wmax; - } - - return res; - } - - /** @summary Fill statistic */ - fillStatistic(stat, dostat/*, dofit */) { - const histo = this.getHisto(), - data = this.countStat(), - print_name = dostat % 10, - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10; - - // make empty at the beginning - stat.clearStat(); - - if (print_name > 0) - stat.addText(data.name); - - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - - if (print_mean > 0) - stat.addText('Mean = ' + stat.format(data.meanx)); - - if (print_rms > 0) - stat.addText('Std Dev = ' + stat.format(data.rmsx)); - - if (print_under > 0) - stat.addText('Underflow = ' + stat.format(histo.getBinContent(0), 'entries')); - - if (print_over > 0) - stat.addText('Overflow = ' + stat.format(histo.getBinContent(this.nbinsx+1), 'entries')); - - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.integral, 'entries')); - - if (print_skew > 0) - stat.addText('Skew = <not avail>'); - - if (print_kurt > 0) - stat.addText('Kurt = <not avail>'); - - return true; - } - - /** @summary Draw histogram as bars */ - async drawBars(handle, funcs, width, height) { - this.createG(true); - - const left = handle.i1, right = handle.i2, di = handle.stepi, - pmain = this.getFramePainter(), - histo = this.getHisto(), xaxis = this.getAxis('x'); - let i, x1, x2, grx1, grx2, y, gry1, gry2, w, - bars = '', barsl = '', barsr = ''; - - gry2 = pmain.swap_xy ? 0 : height; - if (Number.isFinite(this.options.BaseLine)) { - if (this.options.BaseLine >= funcs.scale_ymin) - gry2 = Math.round(funcs.gry(this.options.BaseLine)); - } - - for (i = left; i < right; i += di) { - x1 = xaxis.GetBinCoord(i); - x2 = xaxis.GetBinCoord(i+di); - - if (funcs.logx && (x2 <= 0)) continue; - - grx1 = Math.round(funcs.grx(x1)); - grx2 = Math.round(funcs.grx(x2)); - - y = histo.getBinContent(i+1); - if (funcs.logy && (y < funcs.scale_ymin)) continue; - gry1 = Math.round(funcs.gry(y)); - - w = grx2 - grx1; - grx1 += Math.round(this.options.BarOffset*w); - w = Math.round(this.options.BarWidth*w); - - if (pmain.swap_xy) - bars += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - else - bars += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - - if (this.options.BarStyle > 0) { - grx2 = grx1 + w; - w = Math.round(w / 10); - if (pmain.swap_xy) { - barsl += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - barsr += `M${gry2},${grx2}h${gry1-gry2}v${-w}h${gry2-gry1}z`; - } else { - barsl += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - barsr += `M${grx2},${gry1}h${-w}v${gry2-gry1}h${w}z`; - } - } - } - - if (this.fillatt.empty()) this.fillatt.setSolidColor('blue'); - - if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) - .call(this.fillatt.func); - } - - if (barsl) { - this.draw_g.append('svg:path') - .attr('d', barsl) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); - } - - if (barsr) { - this.draw_g.append('svg:path') - .attr('d', barsr) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatHex()); - } - - return true; - } - - /** @summary Draw histogram as filled errors */ - async drawFilledErrors(handle, funcs /*, width, height */) { - this.createG(true); - - const left = handle.i1, right = handle.i2, di = handle.stepi, - histo = this.getHisto(), xaxis = this.getAxis('x'), - bins1 = [], bins2 = []; - let i, x, grx, y, yerr, gry; - - for (i = left; i < right; i += di) { - x = xaxis.GetBinCoord(i+0.5); - if (funcs.logx && (x <= 0)) continue; - grx = Math.round(funcs.grx(x)); - - y = histo.getBinContent(i+1); - yerr = histo.getBinError(i+1); - if (funcs.logy && (y-yerr < funcs.scale_ymin)) continue; - - gry = Math.round(funcs.gry(y + yerr)); - bins1.push({ grx, gry }); - - gry = Math.round(funcs.gry(y - yerr)); - bins2.unshift({ grx, gry }); - } - - const path1 = buildSvgCurve(bins1, { line: this.options.ErrorKind !== 4 }), - path2 = buildSvgCurve(bins2, { line: this.options.ErrorKind !== 4, cmd: 'L' }); - - if (this.fillatt.empty()) this.fillatt.setSolidColor('blue'); - - this.draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(this.fillatt.func); - - return true; - } - - /** @summary Draw 1D histogram as SVG */ - async draw1DBins() { - const pmain = this.getFramePainter(), - rect = pmain.getFrameRect(); - - if (!this.draw_content || (rect.width <= 0) || (rect.height <= 0)) { - this.removeG(); - return false; - } - - this.createHistDrawAttributes(); - - const handle = this.prepareDraw({ extra: 1, only_indexes: true }), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); - - if (this.options.Bar) - return this.drawBars(handle, funcs, rect.width, rect.height); - - if ((this.options.ErrorKind === 3) || (this.options.ErrorKind === 4)) - return this.drawFilledErrors(handle, funcs, rect.width, rect.height); - - return this.drawHistBins(handle, funcs, rect.width, rect.height); - } - - /** @summary Draw histogram bins */ - async drawHistBins(handle, funcs, width, height) { - this.createG(true); - - const options = this.options, - left = handle.i1, - right = handle.i2, - di = handle.stepi, - histo = this.getHisto(), - want_tooltip = !this.isBatchMode() && settings.Tooltip, - xaxis = this.getAxis('x'), - exclude_zero = !options.Zero, - show_errors = options.Error, - show_line = options.Line, - show_text = options.Text; - let show_markers = options.Mark, - res = '', lastbin = false, - startx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, - path_fill = null, path_err = null, path_marker = null, path_line = null, - hints_err = null, - endx = '', endy = '', dend = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx, - text_font; - - if (show_errors && !show_markers && (this.v7EvalAttr('marker_style', 1) > 1)) - show_markers = true; - - if (options.ErrorKind === 2) { - if (this.fillatt.empty()) show_markers = true; - else path_fill = ''; - } else if (options.Error) { - path_err = ''; - hints_err = want_tooltip ? '' : null; - } - - if (show_line) path_line = ''; - - if (show_markers) { - // draw markers also when e2 option was specified - this.createv7AttMarker(); - if (this.markeratt.size > 0) { - // simply use relative move from point, can optimize in the future - path_marker = ''; - this.markeratt.resetPos(); - } else - show_markers = false; - } - - if (show_text) { - text_font = this.v7EvalFont('text', { size: 20, color: 'black', align: 22 }); - - if (!text_font.angle && !options.TextKind) { - const space = width / (right - left + 1); - if (space < 3 * text_font.size) { - text_font.setAngle(270); - text_font.setSize(Math.round(space*0.7)); - } - } - - this.startTextDrawing(text_font, 'font'); - } - - // if there are too many points, exclude many vertical drawings at the same X position - // instead define min and max value and made min-max drawing - let use_minmax = ((right-left) > 3*width); - - if (options.ErrorKind === 1) { - const lw = this.lineatt.width + gStyle.fEndErrorSize; - endx = `m0,${lw}v${-2*lw}m0,${lw}`; - endy = `m${lw},0h${-2*lw}m${lw},0`; - dend = Math.floor((this.lineatt.width-1)/2); - } - - const draw_markers = show_errors || show_markers; - - if (draw_markers || show_text || show_line) use_minmax = true; - - const draw_bin = besti => { - bincont = histo.getBinContent(besti+1); - if (!exclude_zero || (bincont !== 0)) { - mx1 = Math.round(funcs.grx(xaxis.GetBinCoord(besti))); - mx2 = Math.round(funcs.grx(xaxis.GetBinCoord(besti+di))); - midx = Math.round((mx1+mx2)/2); - my = Math.round(funcs.gry(bincont)); - yerr1 = yerr2 = 20; - if (show_errors) { - binerr = histo.getBinError(besti+1); - yerr1 = Math.round(my - funcs.gry(bincont + binerr)); // up - yerr2 = Math.round(funcs.gry(bincont - binerr) - my); // down - } - - if (show_text && (bincont !== 0)) { - const lbl = (bincont === Math.round(bincont)) ? bincont.toString() : floatToString(bincont, gStyle.fPaintTextFormat); - - if (text_font.angle) - this.drawText({ align: 12, x: midx, y: Math.round(my - 2 - text_font.size / 5), text: lbl, latex: 0 }); - else - this.drawText({ x: Math.round(mx1 + (mx2 - mx1) * 0.1), y: Math.round(my - 2 - text_font.size), width: Math.round((mx2 - mx1) * 0.8), height: text_font.size, text: lbl, latex: 0 }); - } - - if (show_line && (path_line !== null)) - path_line += ((path_line.length === 0) ? 'M' : 'L') + midx + ',' + my; - - if (draw_markers) { - if ((my >= -yerr1) && (my <= height + yerr2)) { - if (path_fill !== null) - path_fill += `M${mx1},${my-yerr1}h${mx2-mx1}v${yerr1+yerr2+1}h${mx1-mx2}z`; - if (path_marker !== null) - path_marker += this.markeratt.create(midx, my); - if (path_err !== null) { - let edx = 5; - if (this.options.errorX > 0) { - edx = Math.round((mx2-mx1)*this.options.errorX); - const mmx1 = midx - edx, mmx2 = midx + edx; - path_err += `M${mmx1+dend},${my}${endx}h${mmx2-mmx1-2*dend}${endx}`; - } - path_err += `M${midx},${my-yerr1+dend}${endy}v${yerr1+yerr2-2*dend}${endy}`; - if (hints_err !== null) - hints_err += `M${midx-edx},${my-yerr1}h${2*edx}v${yerr1+yerr2}h${-2*edx}z`; - } - } - } - } - }; - - for (i = left; i <= right; i += di) { - x = xaxis.GetBinCoord(i); - - if (funcs.logx && (x <= 0)) continue; - - grx = Math.round(funcs.grx(x)); - - lastbin = (i > right - di); - - if (lastbin && (left < right)) - gry = curry; - else { - y = histo.getBinContent(i+1); - gry = Math.round(funcs.gry(y)); - } - - if (res.length === 0) { - bestimin = bestimax = i; - prevx = startx = currx = grx; - prevy = curry_min = curry_max = curry = gry; - res = 'M'+currx+','+curry; - } else - if (use_minmax) { - if ((grx === currx) && !lastbin) { - if (gry < curry_min) bestimax = i; else - if (gry > curry_max) bestimin = i; - curry_min = Math.min(curry_min, gry); - curry_max = Math.max(curry_max, gry); - curry = gry; - } else { - if (draw_markers || show_text || show_line) { - if (bestimin === bestimax) draw_bin(bestimin); else - if (bestimin < bestimax) { draw_bin(bestimin); draw_bin(bestimax); } else { - draw_bin(bestimax); draw_bin(bestimin); - } - } - - // when several points as same X differs, need complete logic - if (!draw_markers && ((curry_min !== curry_max) || (prevy !== curry_min))) { - if (prevx !== currx) - res += 'h'+(currx-prevx); - - if (curry === curry_min) { - if (curry_max !== prevy) - res += 'v' + (curry_max - prevy); - if (curry_min !== curry_max) - res += 'v' + (curry_min - curry_max); - } else { - if (curry_min !== prevy) - res += 'v' + (curry_min - prevy); - if (curry_max !== curry_min) - res += 'v' + (curry_max - curry_min); - if (curry !== curry_max) - res += 'v' + (curry - curry_max); - } - - prevx = currx; - prevy = curry; - } - - if (lastbin && (prevx !== grx)) - res += 'h'+(grx-prevx); - - bestimin = bestimax = i; - curry_min = curry_max = curry = gry; - currx = grx; - } - } else - if ((gry !== curry) || lastbin) { - if (grx !== currx) res += 'h'+(grx-currx); - if (gry !== curry) res += 'v'+(gry-curry); - curry = gry; - currx = grx; - } - } - - const fill_for_interactive = !this.isBatchMode() && this.fillatt.empty() && options.Hist && settings.Tooltip && !draw_markers && !show_line; - let h0 = height + 3; - if (!fill_for_interactive) { - const gry0 = Math.round(funcs.gry(0)); - if (gry0 <= 0) - h0 = -3; - else if (gry0 < height) - h0 = gry0; - } - const close_path = `L${currx},${h0}H${startx}Z`; - - if (draw_markers || show_line) { - if (path_fill) { - this.draw_g.append('svg:path') - .attr('d', path_fill) - .call(this.fillatt.func); - } - - if (path_err) { - this.draw_g.append('svg:path') - .attr('d', path_err) - .call(this.lineatt.func); - } - - if (hints_err) { - this.draw_g.append('svg:path') - .attr('d', hints_err) - .style('fill', 'none') - .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); - } - - if (path_line) { - if (!this.fillatt.empty() && !options.Hist) { - this.draw_g.append('svg:path') - .attr('d', path_line + close_path) - .call(this.fillatt.func); - } - - this.draw_g.append('svg:path') - .attr('d', path_line) - .style('fill', 'none') - .call(this.lineatt.func); - } - - if (path_marker) { - this.draw_g.append('svg:path') - .attr('d', path_marker) - .call(this.markeratt.func); - } - } else if (res && options.Hist) { - this.draw_g.append('svg:path') - .attr('d', res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) - .style('stroke-linejoin', 'miter') - .call(this.lineatt.func) - .call(this.fillatt.func); - } - - return show_text ? this.finishTextDrawing() : true; - } - - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(bin) { - const tips = [], - name = this.getObjectHint(), - pmain = this.getFramePainter(), - histo = this.getHisto(), - xaxis = this.getAxis('x'), - di = this.isDisplayItem() ? histo.stepx : 1, - x1 = xaxis.GetBinCoord(bin), - x2 = xaxis.GetBinCoord(bin+di), - xlbl = this.getAxisBinTip('x', bin, di); - - let cont = histo.getBinContent(bin+1); - - if (name) tips.push(name); - - if (this.options.Error || this.options.Mark) { - tips.push(`x = ${xlbl}`, `y = ${pmain.axisAsText('y', cont)}`); - if (this.options.Error) { - if (xlbl[0] === '[') tips.push('error x = ' + ((x2 - x1) / 2).toPrecision(4)); - tips.push('error y = ' + histo.getBinError(bin + 1).toPrecision(4)); - } - } else { - tips.push(`bin = ${bin+1}`, `x = ${xlbl}`); - if (histo.$baseh) cont -= histo.$baseh.getBinContent(bin+1); - const lbl = 'entries = ' + (di > 1 ? '~' : ''); - if (cont === Math.round(cont)) - tips.push(lbl + cont); - else - tips.push(lbl + floatToString(cont, gStyle.fStatFormat)); - } - - return tips; - } - - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); - - if (!pnt || !this.draw_content || this.options.Mode3D || !this.draw_g) { - ttrect?.remove(); - return null; - } - - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - width = pmain.getFrameWidth(), - height = pmain.getFrameHeight(), - histo = this.getHisto(), xaxis = this.getAxis('x'), - left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 2); - - let findbin = null, show_rect, - grx1, grx2, gry1, gry2, gapx = 2, - l = left, r = right; - - function GetBinGrX(i) { - const xx = xaxis.GetBinCoord(i); - return (funcs.logx && (xx <= 0)) ? null : funcs.grx(xx); - } - - function GetBinGrY(i) { - const yy = histo.getBinContent(i + 1); - if (funcs.logy && (yy < funcs.scale_ymin)) - return funcs.swap_xy ? -1000 : 10*height; - return Math.round(funcs.gry(yy)); - } - - const pnt_x = funcs.swap_xy ? pnt.y : pnt.x, - pnt_y = funcs.swap_xy ? pnt.x : pnt.y; - - while (l < r-1) { - const m = Math.round((l+r)*0.5), - xx = GetBinGrX(m); - if ((xx === null) || (xx < pnt_x - 0.5)) - if (funcs.swap_xy) r = m; else l = m; - else if (xx > pnt_x + 0.5) - if (funcs.swap_xy) l = m; else r = m; - else { l++; r--; } - } - - findbin = r = l; - grx1 = GetBinGrX(findbin); - - if (funcs.swap_xy) { - while ((l > left) && (GetBinGrX(l-1) < grx1 + 2)) --l; - while ((r < right) && (GetBinGrX(r+1) > grx1 - 2)) ++r; - } else { - while ((l > left) && (GetBinGrX(l-1) > grx1 - 2)) --l; - while ((r < right) && (GetBinGrX(r+1) < grx1 + 2)) ++r; - } - - if (l < r) { - // many points can be assigned with the same cursor position - // first try point around mouse y - let best = height; - for (let m = l; m <= r; m++) { - const dist = Math.abs(GetBinGrY(m) - pnt_y); - if (dist < best) { best = dist; findbin = m; } - } - - // if best distance still too far from mouse position, just take from between - if (best > height/10) - findbin = Math.round(l + (r-l) / height * pnt_y); - - grx1 = GetBinGrX(findbin); - } - - grx1 = Math.round(grx1); - grx2 = Math.round(GetBinGrX(findbin+1)); - - if (this.options.Bar) { - const w = grx2 - grx1; - grx1 += Math.round(this.options.BarOffset*w); - grx2 = grx1 + Math.round(this.options.BarWidth*w); - } - - if (grx1 > grx2) - [grx1, grx2] = [grx2, grx1]; - - if (this.isDisplayItem() && ((findbin <= histo.dx) || (findbin >= histo.dx + histo.nx))) { - // special case when zoomed out of scale and bin is not available - ttrect.remove(); - return null; - } - - const midx = Math.round((grx1 + grx2)/2), - midy = gry1 = gry2 = GetBinGrY(findbin); - - if (this.options.Bar) { - show_rect = true; - - gapx = 0; - - gry1 = Math.round(funcs.gry(((this.options.BaseLine!==false) && (this.options.BaseLine > funcs.scale_ymin)) ? this.options.BaseLine : funcs.scale_ymin)); - - if (gry1 > gry2) - [gry1, gry2] = [gry2, gry1]; - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if (this.options.Error || this.options.Mark) { - show_rect = true; - - let msize = 3; - if (this.markeratt) msize = Math.max(msize, this.markeratt.getFullSize()); - - if (this.options.Error) { - const cont = histo.getBinContent(findbin+1), - binerr = histo.getBinError(findbin+1); - - gry1 = Math.round(funcs.gry(cont + binerr)); // up - gry2 = Math.round(funcs.gry(cont - binerr)); // down - - const dx = (grx2-grx1)*this.options.errorX; - grx1 = Math.round(midx - dx); - grx2 = Math.round(midx + dx); - } - - // show at least 6 pixels as tooltip rect - if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; } - - gry1 = Math.min(gry1, midy - msize); - gry2 = Math.max(gry2, midy + msize); - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if (this.options.Line) - - show_rect = false; - - else { - // if histogram alone, use old-style with rects - // if there are too many points at pixel, use circle - show_rect = (pnt.nproc === 1) && (right-left < width); - - if (show_rect) { - gry2 = height; - - if (!this.fillatt.empty()) { - gry2 = Math.min(height, Math.max(0, Math.round(funcs.gry(0)))); - if (gry2 < gry1) - [gry1, gry2] = [gry2, gry1]; - } - - // for mouse events pointer should be between y1 and y2 - if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) findbin = null; - } - } - - if (findbin !== null) { - // if bin on boundary found, check that x position is ok - if ((findbin === left) && (grx1 > pnt_x + gapx)) findbin = null; else - if ((findbin === right-1) && (grx2 < pnt_x - gapx)) findbin = null; else - // if bars option used check that bar is not match - if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) findbin = null; else - // exclude empty bin if empty bins suppressed - if (!this.options.Zero && (histo.getBinContent(findbin+1) === 0)) findbin = null; - } - - if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) { - ttrect.remove(); - return null; - } - - const res = { name: 'histo', title: histo.fTitle, - x: midx, y: midy, exact: true, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(findbin) }; - - if (pnt.disabled) { - // case when tooltip should not highlight bin - - ttrect.remove(); - res.changed = true; - } else if (show_rect) { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } - - res.changed = ttrect.property('current_bin') !== findbin; - - if (res.changed) { - ttrect.attr('x', pmain.swap_xy ? gry1 : grx1) - .attr('width', pmain.swap_xy ? gry2-gry1 : grx2-grx1) - .attr('y', pmain.swap_xy ? grx1 : gry1) - .attr('height', pmain.swap_xy ? grx2-grx1 : gry2-gry1) - .style('opacity', '0.3') - .property('current_bin', findbin); - } - - res.exact = (Math.abs(midy - pnt_y) <= 5) || ((pnt_y>=gry1) && (pnt_y<=gry2)); - - res.menu = res.exact; // one could show context menu - // distance to middle point, use to decide which menu to activate - res.menu_dist = Math.sqrt((midx-pnt_x)**2 + (midy-pnt_y)**2); - } else { - const radius = this.lineatt.width + 3; - - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .attr('r', radius) - .call(this.lineatt.func) - .call(this.fillatt.func); - } - - res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius); - - res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram - res.menu_dist = Math.sqrt((midx-pnt.x)**2 + (midy-pnt.y)**2); - - res.changed = ttrect.property('current_bin') !== findbin; - - if (res.changed) { - ttrect.attr('cx', midx) - .attr('cy', midy) - .property('current_bin', findbin); - } - } - - if (res.changed) { - res.user_info = { obj: histo, name: 'histo', - bin: findbin, cont: histo.getBinContent(findbin+1), - grx: midx, gry: midy }; - } - - return res; - } - - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - menu.add('Auto zoom-in', () => this.autoZoom()); - - const opts = this.getSupportedDrawOptions(); - - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); - - this.decodeOptions(arg); // obsolete, should be implemented differently - - if (this.options.need_fillcol && this.fillatt?.empty()) - this.fillatt.change(5, 1001); - - // redraw all objects - this.interactiveRedraw('pad', 'drawopt'); - }); - } - - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - let left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 1); - const dist = right - left, histo = this.getHisto(), xaxis = this.getAxis('x'); - - if (dist === 0) return; - - // first find minimum - let min = histo.getBinContent(left + 1); - for (let indx = left; indx < right; ++indx) - min = Math.min(min, histo.getBinContent(indx+1)); - if (min > 0) return; // if all points positive, no chance for autoscale - - while ((left < right) && (histo.getBinContent(left+1) <= min)) ++left; - while ((left < right) && (histo.getBinContent(right) <= min)) --right; - - // if singular bin - if ((left === right-1) && (left > 2) && (right < this.nbinsx-2)) { - --left; ++right; - } - - if ((right - left < dist) && (left < right)) - return this.getFramePainter().zoom(xaxis.GetBinCoord(left), xaxis.GetBinCoord(right)); - } - - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - const xaxis = this.getAxis('x'); - - if ((axis === 'x') && (xaxis.FindBin(max, 0.5) - xaxis.FindBin(min, 0) > 1)) return true; - - if ((axis === 'y') && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true; - - return false; - } - - /** @summary Call appropriate draw function */ - async callDrawFunc(reason) { - const main = this.getFramePainter(); - - if (main && (main.mode3d !== this.options.Mode3D) && !this.isMainPainter()) - this.options.Mode3D = main.mode3d; - - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } - - /** @summary Draw in 2d */ - async draw2D(reason) { - this.clear3DScene(); - - return this.drawFrameAxes().then(res => { - return res ? this.drawingBins(reason) : false; - }).then(res => { - if (res) - return this.draw1DBins().then(() => this.addInteractivity()); - }).then(() => this); - } - - /** @summary Draw in 3d */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/RH1Painter.mjs'); - return this.draw2D(reason); - } - - /** @summary Readraw histogram */ - async redraw(reason) { - return this.callDrawFunc(reason); - } - - static async _draw(painter, opt) { - return ensureRCanvas(painter).then(() => { - painter.setAsMainPainter(); - - painter.options = { Hist: false, Bar: false, BarStyle: 0, - Error: false, ErrorKind: -1, errorX: gStyle.fErrorX, - Zero: false, Mark: false, - Line: false, Fill: false, Lego: 0, Surf: 0, - Text: false, TextAngle: 0, TextKind: '', AutoColor: 0, - BarOffset: 0, BarWidth: 1, BaseLine: false, - Mode3D: false, FrontBox: false, BackBox: false }; - - const d = new DrawOptions(opt); - if (d.check('R3D_', true)) - painter.options.Render3D = constants.Render3D.fromString(d.part.toLowerCase()); - - const kind = painter.v7EvalAttr('kind', 'hist'), - sub = painter.v7EvalAttr('sub', 0), - has_main = !!painter.getMainPainter(), - o = painter.options; - - o.Text = painter.v7EvalAttr('drawtext', false); - o.BarOffset = painter.v7EvalAttr('baroffset', 0.0); - o.BarWidth = painter.v7EvalAttr('barwidth', 1.0); - o.second_x = has_main && painter.v7EvalAttr('secondx', false); - o.second_y = has_main && painter.v7EvalAttr('secondy', false); - - switch (kind) { - case 'bar': o.Bar = true; o.BarStyle = sub; break; - case 'err': o.Error = true; o.ErrorKind = sub; break; - case 'p': o.Mark = true; break; - case 'l': o.Line = true; break; - case 'lego': o.Lego = sub > 0 ? 10+sub : 12; o.Mode3D = true; break; - default: o.Hist = true; - } - - painter.scanContent(); - - return painter.callDrawFunc(); - }); - } - - /** @summary draw RH1 object */ - static async draw(dom, histo, opt) { - return RH1Painter._draw(new RH1Painter(dom, histo), opt); - } - -} // class RH1Painter - -export { RH1Painter }; diff --git a/modules/hist2d/RH2Painter.mjs b/modules/hist2d/RH2Painter.mjs deleted file mode 100644 index f759adfb6..000000000 --- a/modules/hist2d/RH2Painter.mjs +++ /dev/null @@ -1,1189 +0,0 @@ -import { gStyle, isStr, kNoZoom, kInspect } from '../core.mjs'; -import { rgb as d3_rgb } from '../d3.mjs'; -import { floatToString, TRandom, addHighlightStyle } from '../base/BasePainter.mjs'; -import { RHistPainter } from './RHistPainter.mjs'; -import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs'; -import { buildHist2dContour } from '../hist2d/TH2Painter.mjs'; - -/** - * @summary Painter for RH2 classes - * - * @private - */ - -class RH2Painter extends RHistPainter { - - /** @summary constructor - * @param {object|string} dom - DOM element or id - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = true; - } - - /** @summary Cleanup painter */ - cleanup() { - delete this.tt_handle; - super.cleanup(); - } - - /** @summary Returns histogram dimension */ - getDimension() { return 2; } - - /** @summary Toggle projection */ - toggleProjection(kind, width) { - if ((kind === 'Projections') || (kind === 'Off')) - kind = ''; - - let widthX = width, widthY = width; - - if (isStr(kind) && (kind.indexOf('XY') === 0)) { - const ws = kind.length > 2 ? kind.slice(2) : ''; - kind = 'XY'; - widthX = widthY = parseInt(ws); - } else if (isStr(kind) && (kind.length > 1)) { - const ps = kind.indexOf('_'); - if ((ps > 0) && (kind[0] === 'X') && (kind[ps+1] === 'Y')) { - widthX = parseInt(kind.slice(1, ps)) || 1; - widthY = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps+1] === 'X')) { - widthY = parseInt(kind.slice(1, ps)) || 1; - widthX = parseInt(kind.slice(ps+2)) || 1; - kind = 'XY'; - } else { - widthX = widthY = parseInt(kind.slice(1)) || 1; - kind = kind[0]; - } - } - - if (!widthX && !widthY) - widthX = widthY = 1; - - if (kind && (this.is_projection === kind)) { - if ((this.projection_widthX === widthX) && (this.projection_widthY === widthY)) - kind = ''; - else { - this.projection_widthX = widthX; - this.projection_widthY = widthY; - return; - } - } - - delete this.proj_hist; - - const new_proj = (this.is_projection === kind) ? '' : kind; - this.projection_widthX = widthX; - this.projection_widthY = widthY; - this.is_projection = ''; // avoid projection handling until area is created - - this.provideSpecialDrawArea(new_proj).then(() => { this.is_projection = new_proj; return this.redrawProjection(); }); - } - - /** @summary Readraw projections */ - redrawProjection(/* ii1, ii2, jj1, jj2 */) { - // do nothing for the moment - // if (!this.is_projection) return; - } - - /** @summary Execute menu command */ - executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) return true; - - if ((method.fName === 'SetShowProjectionX') || (method.fName === 'SetShowProjectionY')) { - this.toggleProjection(method.fName[17], args && parseInt(args) ? parseInt(args) : 1); - return true; - } - - return false; - } - - /** @summary Fill histogram context menu */ - fillHistContextMenu(menu) { - if (this.getPadPainter()?.iscan) { - let kind = this.is_projection || ''; - if (kind) kind += this.projection_widthX; - if ((this.projection_widthX !== this.projection_widthY) && (this.is_projection === 'XY')) - kind = `X${this.projection_widthX}_Y${this.projection_widthY}`; - const kinds = ['X1', 'X2', 'X3', 'X5', 'X10', 'Y1', 'Y2', 'Y3', 'Y5', 'Y10', 'XY1', 'XY2', 'XY3', 'XY5', 'XY10']; - if (kind) kinds.unshift('Off'); - - menu.add('sub:Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); - for (let k = 0; k < kinds.length; ++k) - menu.addchk(kind === kinds[k], kinds[k], kinds[k], arg => this.toggleProjection(arg)); - menu.add('endsub:'); - } - - menu.add('Auto zoom-in', () => this.autoZoom()); - - const opts = this.getSupportedDrawOptions(); - - menu.addDrawMenu('Draw with', opts, arg => { - if (arg.indexOf(kInspect) === 0) - return this.showInspector(arg); - this.decodeOptions(arg); - this.interactiveRedraw('pad', 'drawopt'); - }); - - if (this.options.Color) - this.fillPaletteMenu(menu); - } - - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const res = super.clickButton(funcname); - if (res) return res; - - switch (funcname) { - case 'ToggleColor': return this.toggleColor(); - case 'Toggle3D': return this.toggleMode3D(); - } - - // all methods here should not be processed further - return false; - } - - /** @summary Fill pad toolbar with RH2-related functions */ - fillToolbar() { - super.fillToolbar(true); - - const pp = this.getPadPainter(); - if (!pp) return; - - pp.addPadButton('th2color', 'Toggle color', 'ToggleColor'); - pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); - pp.addPadButton('th2draw3d', 'Toggle 3D mode', 'Toggle3D'); - pp.showPadButtons(); - } - - /** @summary Toggle color drawing mode */ - toggleColor() { - if (this.options.Mode3D) { - this.options.Mode3D = false; - this.options.Color = true; - } else - this.options.Color = !this.options.Color; - - return this.redraw(); - } - - /** @summary Perform automatic zoom inside non-zero region of histogram */ - autoZoom() { - const i1 = this.getSelectIndex('x', 'left', -1), - i2 = this.getSelectIndex('x', 'right', 1), - j1 = this.getSelectIndex('y', 'left', -1), - j2 = this.getSelectIndex('y', 'right', 1), - histo = this.getHisto(), xaxis = this.getAxis('x'), yaxis = this.getAxis('y'); - - if ((i1 === i2) || (j1 === j2)) return; - - // first find minimum - let min = histo.getBinContent(i1 + 1, j1 + 1); - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) - min = Math.min(min, histo.getBinContent(i+1, j+1)); - } - if (min > 0) return; // if all points positive, no chance for autoscale - - let ileft = i2, iright = i1, jleft = j2, jright = j1; - - for (let i = i1; i < i2; ++i) { - for (let j = j1; j < j2; ++j) { - if (histo.getBinContent(i + 1, j + 1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; - } - } - } - - let xmin, xmax, ymin, ymax, isany = false; - - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } - - if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = xaxis.GetBinCoord(ileft); - xmax = xaxis.GetBinCoord(iright); - isany = true; - } - - if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = yaxis.GetBinCoord(jleft); - ymax = yaxis.GetBinCoord(jright); - isany = true; - } - - if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax); - } - - /** @summary Scan content of 2-dim histogram */ - scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy) return; - - const histo = this.getHisto(); - - this.extractAxesProperties(2); - - if (this.isDisplayItem()) { - // take min/max values from the display item - this.gminbin = histo.fContMin; - this.gminposbin = histo.fContMinPos > 0 ? histo.fContMinPos : null; - this.gmaxbin = histo.fContMax; - } else { - // global min/max, used at the moment in 3D drawing - this.gminbin = this.gmaxbin = histo.getBinContent(1, 1); - this.gminposbin = null; - for (let i = 0; i < this.nbinsx; ++i) { - for (let j = 0; j < this.nbinsy; ++j) { - const bin_content = histo.getBinContent(i+1, j+1); - if (bin_content < this.gminbin) this.gminbin = bin_content; else - if (bin_content > this.gmaxbin) this.gmaxbin = bin_content; - if (bin_content > 0) - if ((this.gminposbin === null) || (this.gminposbin > bin_content)) this.gminposbin = bin_content; - } - } - } - - this.zmin = this.gminbin; - this.zmax = this.gmaxbin; - - // this value used for logz scale drawing - if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; - - if (this.options.Axis > 0) // Paint histogram axis only - this.draw_content = false; - else - this.draw_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); - } - - /** @summary Count statistic */ - countStat(cond) { - const histo = this.getHisto(), - res = { name: 'histo', entries: 0, integral: 0, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], xmax: 0, ymax: 0, wmax: null }, - xleft = this.getSelectIndex('x', 'left'), - xright = this.getSelectIndex('x', 'right'), - yleft = this.getSelectIndex('y', 'left'), - yright = this.getSelectIndex('y', 'right'), - xaxis = this.getAxis('x'), yaxis = this.getAxis('y'); - let stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0, - stat_sumx2 = 0, stat_sumy2 = 0, - xside, yside, xx, yy, zz, - xi, yi; - - // TODO: account underflow/overflow bins, now stored in different array and only by histogram itself - for (xi = 1; xi <= this.nbinsx; ++xi) { - xside = (xi <= xleft+1) ? 0 : (xi > xright+1 ? 2 : 1); - xx = xaxis.GetBinCoord(xi - 0.5); - - for (yi = 1; yi <= this.nbinsy; ++yi) { - yside = (yi <= yleft+1) ? 0 : (yi > yright+1 ? 2 : 1); - yy = yaxis.GetBinCoord(yi - 0.5); - - zz = histo.getBinContent(xi, yi); - - res.entries += zz; - - res.matrix[yside * 3 + xside] += zz; - - if ((xside !== 1) || (yside !== 1)) continue; - - if (cond && !cond(xx, yy)) continue; - - if ((res.wmax === null) || (zz > res.wmax)) { res.wmax = zz; res.xmax = xx; res.ymax = yy; } - - stat_sum0 += zz; - stat_sumx1 += xx * zz; - stat_sumy1 += yy * zz; - stat_sumx2 += xx**2 * zz; - stat_sumy2 += yy**2 * zz; - } - } - - if (Math.abs(stat_sum0) > 1e-300) { - res.meanx = stat_sumx1 / stat_sum0; - res.meany = stat_sumy1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); - } - - if (res.wmax === null) res.wmax = 0; - res.integral = stat_sum0; - return res; - } - - /** @summary Fill statistic into statbox */ - fillStatistic(stat, dostat /*, dofit */) { - const data = this.countStat(), - print_name = Math.floor(dostat % 10), - print_entries = Math.floor(dostat / 10) % 10, - print_mean = Math.floor(dostat / 100) % 10, - print_rms = Math.floor(dostat / 1000) % 10, - print_under = Math.floor(dostat / 10000) % 10, - print_over = Math.floor(dostat / 100000) % 10, - print_integral = Math.floor(dostat / 1000000) % 10, - print_skew = Math.floor(dostat / 10000000) % 10, - print_kurt = Math.floor(dostat / 100000000) % 10; - - stat.clearStat(); - - if (print_name > 0) - stat.addText(data.name); - - if (print_entries > 0) - stat.addText('Entries = ' + stat.format(data.entries, 'entries')); - - if (print_mean > 0) { - stat.addText('Mean x = ' + stat.format(data.meanx)); - stat.addText('Mean y = ' + stat.format(data.meany)); - } - - if (print_rms > 0) { - stat.addText('Std Dev x = ' + stat.format(data.rmsx)); - stat.addText('Std Dev y = ' + stat.format(data.rmsy)); - } - - if (print_integral > 0) - stat.addText('Integral = ' + stat.format(data.matrix[4], 'entries')); - - if (print_skew > 0) { - stat.addText('Skewness x = <undef>'); - stat.addText('Skewness y = <undef>'); - } - - if (print_kurt > 0) - stat.addText('Kurt = <undef>'); - - if ((print_under > 0) || (print_over > 0)) { - const m = data.matrix; - - stat.addText('' + m[6].toFixed(0) + ' | ' + m[7].toFixed(0) + ' | ' + m[7].toFixed(0)); - stat.addText('' + m[3].toFixed(0) + ' | ' + m[4].toFixed(0) + ' | ' + m[5].toFixed(0)); - stat.addText('' + m[0].toFixed(0) + ' | ' + m[1].toFixed(0) + ' | ' + m[2].toFixed(0)); - } - - return true; - } - - /** @summary Draw histogram bins as color */ - drawBinsColor() { - const histo = this.getHisto(), - handle = this.prepareDraw(), - di = handle.stepi, dj = handle.stepj, - entries = [], - can_merge = true; - let colindx, cmd1, cmd2, i, j, binz, dx, dy, entry, last_entry; - - const flush_last_entry = () => { - last_entry.path += `h${dx}v${last_entry.y2-last_entry.y}h${-dx}z`; - last_entry.dy = 0; - last_entry = null; - }; - - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - dx = (handle.grx[i+di] - handle.grx[i]) || 1; - - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i+1, j+1); - colindx = handle.palette.getContourIndex(binz); - if (binz === 0) { - if (!this.options.Zero) - colindx = null; - else if ((colindx === null) && this._show_empty_bins) - colindx = 0; - } - if (colindx === null) { - if (last_entry) flush_last_entry(); - continue; - } - - cmd1 = `M${handle.grx[i]},${handle.gry[j]}`; - - dy = (handle.gry[j+dj] - handle.gry[j]) || -1; - - entry = entries[colindx]; - - if (entry === undefined) - entry = entries[colindx] = { path: cmd1 }; - else if (can_merge && (entry === last_entry)) { - entry.y2 = handle.gry[j] + dy; - continue; - } else { - cmd2 = `m${handle.grx[i]-entry.x},${handle.gry[j]-entry.y}`; - entry.path += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - } - if (last_entry) flush_last_entry(); - entry.x = handle.grx[i]; - entry.y = handle.gry[j]; - if (can_merge) { - entry.y2 = handle.gry[j] + dy; - last_entry = entry; - } else - entry.path += `h${dx}v${dy}h${-dx}z`; - } - if (last_entry) flush_last_entry(); - } - - entries.forEach((entry, colindx) => { - if (entry) { - this.draw_g - .append('svg:path') - .style('fill', handle.palette.getColor(colindx)) - .attr('d', entry.path); - } - }); - - this.updatePaletteDraw(); - - return handle; - } - - /** @summary Draw histogram bins as contour */ - drawBinsContour(funcs, frame_w, frame_h) { - const handle = this.prepareDraw({ rounding: false, extra: 100 }), - main = this.getFramePainter(), - palette = main.getHistPalette(), - levels = palette.getContour(), - func = main.getProjectionFunc(), - - BuildPath = (xp, yp, iminus, iplus, do_close) => { - let cmd = '', last, pnt, first, isany; - for (let i = iminus; i <= iplus; ++i) { - if (func) { - pnt = func(xp[i], yp[i]); - pnt.x = Math.round(funcs.grx(pnt.x)); - pnt.y = Math.round(funcs.gry(pnt.y)); - } else - pnt = { x: Math.round(xp[i]), y: Math.round(yp[i]) }; - - if (!cmd) { - cmd = `M${pnt.x},${pnt.y}`; first = pnt; - } else if ((i === iplus) && first && (pnt.x === first.x) && (pnt.y === first.y)) { - if (!isany) return ''; // all same points - cmd += 'z'; do_close = false; - } else if ((pnt.x !== last.x) && (pnt.y !== last.y)) { - cmd += `l${pnt.x - last.x},${pnt.y - last.y}`; isany = true; - } else if (pnt.x !== last.x) { - cmd += `h${pnt.x - last.x}`; isany = true; - } else if (pnt.y !== last.y) { - cmd += `v${pnt.y - last.y}`; isany = true; - } - last = pnt; - } - if (do_close) cmd += 'z'; - return cmd; - }; - - if (this.options.Contour === 14) { - this.draw_g - .append('svg:path') - .attr('d', `M0,0h${frame_w}v${frame_h}h${-frame_w}z`) - .style('fill', palette.getColor(0)); - } - - buildHist2dContour(this.getHisto(), handle, levels, palette, - (colindx, xp, yp, iminus, iplus) => { - const icol = palette.getColor(colindx); - let fillcolor = icol, lineatt; - - switch (this.options.Contour) { - case 1: break; - case 11: fillcolor = 'none'; lineatt = this.createAttLine({ color: icol, std: false }); break; - case 12: fillcolor = 'none'; lineatt = this.createAttLine({ color: 1, style: (colindx%5 + 1), width: 1, std: false }); break; - case 13: fillcolor = 'none'; lineatt = this.lineatt; break; - case 14: break; - } - - const dd = BuildPath(xp, yp, iminus, iplus, fillcolor !== 'none'); - if (!dd) return; - - const elem = this.draw_g - .append('svg:path') - .attr('d', dd) - .style('fill', fillcolor); - - if (lineatt) - elem.call(lineatt.func); - } - ); - - handle.hide_only_zeros = true; // text drawing suppress only zeros - - return handle; - } - - /** @summary Create polybin */ - createPolyBin() { - // see how TH2Painter is implemented - return ''; - } - - /** @summary Draw RH2 bins as text */ - drawBinsText(handle) { - if (handle === null) handle = this.prepareDraw({ rounding: false }); - - const histo = this.getHisto(), - textFont = this.v7EvalFont('text', { size: 20, color: 'black', align: 22 }), - text_offset = this.options.BarOffset || 0, - text_g = this.draw_g.append('svg:g').attr('class', 'th2_text'), - di = handle.stepi, dj = handle.stepj, - profile2d = false; - let i, j, binz, binw, binh, text, x, y, width, height; - - this.startTextDrawing(textFont, 'font', text_g); - - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i+1, j+1); - if ((binz === 0) && !this._show_empty_bins) continue; - - binw = handle.grx[i+di] - handle.grx[i]; - binh = handle.gry[j] - handle.gry[j+dj]; - - if (profile2d) - binz = histo.getBinEntries(i+1, j+1); - - text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); - - if (textFont.angle) { - x = Math.round(handle.grx[i] + binw*0.5); - y = Math.round(handle.gry[j+dj] + binh*(0.5 + text_offset)); - width = height = 0; - } else { - x = Math.round(handle.grx[i] + binw*0.1); - y = Math.round(handle.gry[j+dj] + binh*(0.1 + text_offset)); - width = Math.round(binw*0.8); - height = Math.round(binh*0.8); - } - - this.drawText({ align: 22, x, y, width, height, text, latex: 0, draw_g: text_g }); - } - } - - return this.finishTextDrawing(text_g, true).then(() => { - handle.hide_only_zeros = true; // text drawing suppress only zeros - return handle; - }); - } - - /** @summary Draw RH2 bins as arrows */ - drawBinsArrow() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: false }), - scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1])/(handle.i2 - handle.i1 + 1-0.03)/2, - scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1])/(handle.j2 - handle.j1 + 1-0.03)/2, - di = handle.stepi, dj = handle.stepj, - makeLine = (dx, dy) => dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); - let cmd = '', i, j, dn = 1e-30, dx, dy, xc, yc, - dxn, dyn, x1, x2, y1, y2, anr, si, co; - - for (let loop = 0; loop < 2; ++loop) { - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - if (i === handle.i1) - dx = histo.getBinContent(i+1+di, j+1) - histo.getBinContent(i+1, j+1); - else if (i >= handle.i2-di) - dx = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1-di, j+1); - else - dx = 0.5*(histo.getBinContent(i+1+di, j+1) - histo.getBinContent(i+1-di, j+1)); - - if (j === handle.j1) - dy = histo.getBinContent(i+1, j+1+dj) - histo.getBinContent(i+1, j+1); - else if (j >= handle.j2-dj) - dy = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1, j+1-dj); - else - dy = 0.5*(histo.getBinContent(i+1, j+1+dj) - histo.getBinContent(i+1, j+1-dj)); - - - if (loop === 0) - dn = Math.max(dn, Math.abs(dx), Math.abs(dy)); - else { - xc = (handle.grx[i] + handle.grx[i+di])/2; - yc = (handle.gry[j] + handle.gry[j+dj])/2; - dxn = scale_x*dx/dn; - dyn = scale_y*dy/dn; - x1 = xc - dxn; - x2 = xc + dxn; - y1 = yc - dyn; - y2 = yc + dyn; - dx = Math.round(x2-x1); - dy = Math.round(y2-y1); - - if ((dx !== 0) || (dy !== 0)) { - cmd += 'M'+Math.round(x1)+','+Math.round(y1) + makeLine(dx, dy); - - if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { - anr = Math.sqrt(2/(dx**2 + dy**2)); - si = Math.round(anr*(dx + dy)); - co = Math.round(anr*(dx - dy)); - if (si || co) - cmd += `m${-si},${co}` + makeLine(si, -co) + makeLine(-co, -si); - } - } - } - } - } - } - - this.draw_g - .append('svg:path') - .attr('d', cmd) - .style('fill', 'none') - .call(this.lineatt.func); - - return handle; - } - - /** @summary Draw RH2 bins as boxes */ - drawBinsBox() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: false }), - main = this.getFramePainter(); - - if (main.maxbin === main.minbin) { - main.maxbin = this.gmaxbin; - main.minbin = this.gminbin; - main.minposbin = this.gminposbin; - } - if (main.maxbin === main.minbin) - main.minbin = Math.min(0, main.maxbin-1); - - const absmax = Math.max(Math.abs(main.maxbin), Math.abs(main.minbin)), - absmin = Math.max(0, main.minbin), - di = handle.stepi, dj = handle.stepj; - let i, j, binz, absz, res = '', cross = '', btn1 = '', btn2 = '', - zdiff, dgrx, dgry, xx, yy, ww, hh, - xyfactor, uselogz = false, logmin = 0; - - if (main.logz && (absmax > 0)) { - uselogz = true; - const logmax = Math.log(absmax); - if (absmin > 0) - logmin = Math.log(absmin); - else if ((main.minposbin >= 1) && (main.minposbin < 100)) - logmin = Math.log(0.7); - else - logmin = (main.minposbin > 0) ? Math.log(0.7*main.minposbin) : logmax - 10; - if (logmin >= logmax) logmin = logmax - 10; - xyfactor = 1.0 / (logmax - logmin); - } else - xyfactor = 1.0 / (absmax - absmin); - - - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i + 1, j + 1); - absz = Math.abs(binz); - if ((absz === 0) || (absz < absmin)) continue; - - zdiff = uselogz ? ((absz > 0) ? Math.log(absz) - logmin : 0) : (absz - absmin); - // area of the box should be proportional to absolute bin content - zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor))); - // avoid oversized bins - if (zdiff < 0) zdiff = 0; - - ww = handle.grx[i+di] - handle.grx[i]; - hh = handle.gry[j] - handle.gry[j+dj]; - - dgrx = zdiff * ww; - dgry = zdiff * hh; - - xx = Math.round(handle.grx[i] + dgrx); - yy = Math.round(handle.gry[j+dj] + dgry); - - ww = Math.max(Math.round(ww - 2*dgrx), 1); - hh = Math.max(Math.round(hh - 2*dgry), 1); - - res += `M${xx},${yy}v${hh}h${ww}v${-hh}z`; - - if ((binz < 0) && (this.options.BoxStyle === 10)) - cross += `M${xx},${yy}l${ww},${hh}M${xx+ww},${yy}l${-ww},${hh}`; - - if ((this.options.BoxStyle === 11) && (ww>5) && (hh>5)) { - const pww = Math.round(ww*0.1), - phh = Math.round(hh*0.1), - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; - btn2 += (binz < 0) ? side1 : side2; - btn1 += (binz < 0) ? side2 : side1; - } - } - } - - if (res) { - const elem = this.draw_g - .append('svg:path') - .attr('d', res) - .call(this.fillatt.func); - if ((this.options.BoxStyle !== 11) && this.fillatt.empty()) - elem.call(this.lineatt.func); - } - - if (btn1 && this.fillatt.hasColor()) { - this.draw_g.append('svg:path') - .attr('d', btn1) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); - } - - if (btn2) { - this.draw_g.append('svg:path') - .attr('d', btn2) - .call(this.fillatt.func) - .style('fill', !this.fillatt.hasColor() ? 'red' : d3_rgb(this.fillatt.color).darker(0.5).formatHex()); - } - - if (cross) { - const elem = this.draw_g.append('svg:path') - .attr('d', cross) - .style('fill', 'none'); - if (!this.lineatt.empty()) - elem.call(this.lineatt.func); - } - - return handle; - } - - /** @summary Draw RH2 bins as scatter plot */ - drawBinsScatter() { - const histo = this.getHisto(), - handle = this.prepareDraw({ rounding: true, pixel_density: true, scatter_plot: true }), - colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [], - scale = this.options.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), - di = handle.stepi, dj = handle.stepj, - rnd = new TRandom(handle.sumz); - let colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1; - - if (scale*handle.sumz < 1e5) { - // one can use direct drawing of scatter plot without any patterns - - this.createv7AttMarker(); - - this.markeratt.resetPos(); - - let path = '', k, npix; - for (i = handle.i1; i < handle.i2; i += di) { - cw = handle.grx[i+di] - handle.grx[i]; - for (j = handle.j1; j < handle.j2; j += dj) { - ch = handle.gry[j] - handle.gry[j+dj]; - binz = histo.getBinContent(i + 1, j + 1); - - npix = Math.round(scale*binz); - if (npix <= 0) continue; - - for (k = 0; k < npix; ++k) { - path += this.markeratt.create( - Math.round(handle.grx[i] + cw * rnd.random()), - Math.round(handle.gry[j+1] + ch * rnd.random())); - } - } - } - - this.draw_g - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - - return handle; - } - - // limit filling factor, do not try to produce as many points as filled area; - if (this.maxbin > 0.7) factor = 0.7/this.maxbin; - - // let nlevels = Math.round(handle.max - handle.min); - - // now start build - for (i = handle.i1; i < handle.i2; i += di) { - for (j = handle.j1; j < handle.j2; j += dj) { - binz = histo.getBinContent(i + 1, j + 1); - if ((binz <= 0) || (binz < this.minbin)) continue; - - cw = handle.grx[i+di] - handle.grx[i]; - ch = handle.gry[j] - handle.gry[j+dj]; - if (cw*ch <= 0) continue; - - colindx = handle.palette.getContourIndex(binz/cw/ch); - if (colindx < 0) continue; - - cmd1 = `M${handle.grx[i]},${handle.gry[j+dj]}`; - if (colPaths[colindx] === undefined) { - colPaths[colindx] = cmd1; - cell_w[colindx] = cw; - cell_h[colindx] = ch; - } else { - cmd2 = `m${handle.grx[i]-currx[colindx]},${handle.gry[j+dj]-curry[colindx]}`; - colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1; - cell_w[colindx] = Math.max(cell_w[colindx], cw); - cell_h[colindx] = Math.max(cell_h[colindx], ch); - } - - currx[colindx] = handle.grx[i]; - curry[colindx] = handle.gry[j+dj]; - - colPaths[colindx] += `v${ch}h${cw}v${-ch}z`; - } - } - - const layer = this.getFrameSvg().selectChild('.main_layer'); - let defs = layer.selectChild('def'); - if (defs.empty() && (colPaths.length > 0)) - defs = layer.insert('svg:defs', ':first-child'); - - this.createv7AttMarker(); - - const cntr = handle.palette.getContour(); - - for (colindx = 0; colindx < colPaths.length; ++colindx) { - if ((colPaths[colindx] !== undefined) && (colindx<cntr.length)) { - const pattern_id = (this.pad_name || 'canv') + `_scatter_${colindx}`; - let pattern = defs.selectChild(`#${pattern_id}`); - if (pattern.empty()) { - pattern = defs.append('svg:pattern') - .attr('id', pattern_id) - .attr('patternUnits', 'userSpaceOnUse'); - } else - pattern.selectAll('*').remove(); - - let npix = Math.round(factor*cntr[colindx]*cell_w[colindx]*cell_h[colindx]); - if (npix < 1) npix = 1; - - const arrx = new Float32Array(npix), arry = new Float32Array(npix); - - if (npix === 1) - arrx[0] = arry[0] = 0.5; - else { - for (let n = 0; n < npix; ++n) { - arrx[n] = rnd.random(); - arry[n] = rnd.random(); - } - } - - this.markeratt.resetPos(); - - let path = ''; - - for (let n = 0; n < npix; ++n) - path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]); - - pattern.attr('width', cell_w[colindx]) - .attr('height', cell_h[colindx]) - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - - this.draw_g - .append('svg:path') - .attr('scatter-index', colindx) - .style('fill', `url(#${pattern_id})`) - .attr('d', colPaths[colindx]); - } -} - - return handle; - } - - /** @summary Draw RH2 bins in 2D mode */ - async draw2DBins() { - if (!this.draw_content) { - this.removeG(); - return false; - } - - this.createHistDrawAttributes(); - - this.createG(true); - - const pmain = this.getFramePainter(), - rect = pmain.getFrameRect(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); - let handle = null, pr = null; - - // if (this.lineatt.empty()) this.lineatt.color = 'cyan'; - - if (this.options.Scat) - handle = this.drawBinsScatter(); - else if (this.options.Color) - handle = this.drawBinsColor(); - else if (this.options.Box) - handle = this.drawBinsBox(); - else if (this.options.Arrow) - handle = this.drawBinsArrow(); - else if (this.options.Contour > 0) - handle = this.drawBinsContour(funcs, rect.width, rect.height); - - if (this.options.Text) - pr = this.drawBinsText(handle); - - if (!handle && !pr) - handle = this.drawBinsColor(); - - if (!pr) pr = Promise.resolve(handle); - - return pr.then(h => { - this.tt_handle = h; - return this; - }); - } - - /** @summary Provide text information (tooltips) for histogram bin */ - getBinTooltips(i, j) { - const lines = [], - histo = this.getHisto(); - let binz = histo.getBinContent(i+1, j+1), - di = 1, dj = 1; - - if (this.isDisplayItem()) { - di = histo.stepx || 1; - dj = histo.stepy || 1; - } - - lines.push(this.getObjectHint() || 'histo<2>', - 'x = ' + this.getAxisBinTip('x', i, di), - 'y = ' + this.getAxisBinTip('y', j, dj), - `bin = ${i+1}, ${j+1}`); - - if (histo.$baseh) binz -= histo.$baseh.getBinContent(i+1, j+1); - - const lbl = 'entries = ' + ((di > 1) || (dj > 1) ? '~' : ''); - - if (binz === Math.round(binz)) - lines.push(lbl + binz); - else - lines.push(lbl + floatToString(binz, gStyle.fStatFormat)); - - return lines; - } - - /** @summary Provide text information (tooltips) for poly bin */ - getPolyBinTooltips() { - // see how TH2Painter is implemented - return []; - } - - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const histo = this.getHisto(), - h = this.tt_handle; - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); - - if (!pnt || !this.draw_content || !this.draw_g || !h || this.options.Proj) { - ttrect?.remove(); - return null; - } - - if (h.poly) { - // process tooltips from TH2Poly - see TH2Painter - return null; - } - - let i, j, binz = 0, colindx = null; - - // search bins position - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x>=h.grx[i]) && (pnt.x<=h.grx[i+1])) break; - - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y>=h.gry[j+1]) && (pnt.y<=h.gry[j])) break; - - if ((i < h.i2) && (j < h.j2)) { - binz = histo.getBinContent(i+1, j+1); - if (this.is_projection) - colindx = 0; // just to avoid hide - else if (h.hide_only_zeros) - colindx = (binz === 0) && !this._show_empty_bins ? null : 0; - else { - colindx = h.palette.getContourIndex(binz); - if ((colindx === null) && (binz === 0) && this._show_empty_bins) colindx = 0; - } - } - - if (colindx === null) { - ttrect.remove(); - return null; - } - - const res = { name: 'histo', title: histo.fTitle || 'title', - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(i, j), exact: true, menu: true }; - - if (this.options.Color) - res.color2 = h.palette.getColor(colindx); - - if (pnt.disabled && !this.is_projection) { - ttrect.remove(); - res.changed = true; - } else { - if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); - } - - const pmain = this.getFramePainter(); - let i1 = i, i2 = i+1, - j1 = j, j2 = j+1, - x1 = h.grx[i1], x2 = h.grx[i2], - y1 = h.gry[j2], y2 = h.gry[j1], - binid = i*10000 + j, path; - - if (this.is_projection) { - const pwx = this.projection_widthX || 1, ddx = (pwx - 1) / 2; - if ((this.is_projection.indexOf('X')) >= 0 && (pwx > 1)) { - if (j2+ddx >= h.j2) { - j2 = Math.min(Math.round(j2+ddx), h.j2); - j1 = Math.max(j2-pwx, h.j1); - } else { - j1 = Math.max(Math.round(j1-ddx), h.j1); - j2 = Math.min(j1+pwx, h.j2); - } - } - const pwy = this.projection_widthY || 1, ddy = (pwy - 1) / 2; - if ((this.is_projection.indexOf('Y')) >= 0 && (pwy > 1)) { - if (i2+ddy >= h.i2) { - i2 = Math.min(Math.round(i2+ddy), h.i2); - i1 = Math.max(i2-pwy, h.i1); - } else { - i1 = Math.max(Math.round(i1-ddy), h.i1); - i2 = Math.min(i1+pwy, h.i2); - } - } - } - - if (this.is_projection === 'X') { - x1 = 0; x2 = pmain.getFrameWidth(); - y1 = h.gry[j2]; y2 = h.gry[j1]; - binid = j1*777 + j2*333; - } else if (this.is_projection === 'Y') { - y1 = 0; y2 = pmain.getFrameHeight(); - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*777 + i2*333; - } else if (this.is_projection === 'XY') { - y1 = h.gry[j2]; y2 = h.gry[j1]; - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*789 + i2*653 + j1*12345 + j2*654321; - path = `M${x1},0H${x2}V${y1}H${pmain.getFrameWidth()}V${y2}H${x2}V${pmain.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; - } - - res.changed = ttrect.property('current_bin') !== binid; - - if (res.changed) { - ttrect.attr('d', path || `M${x1},${y1}H${x2}V${y2}H${x1}Z`) - .style('opacity', '0.7') - .property('current_bin', binid); - } - - if (this.is_projection && res.changed) - this.redrawProjection(i1, i2, j1, j2); - } - - if (res.changed) { - res.user_info = { obj: histo, name: 'histo', - bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1, - grx: pnt.x, gry: pnt.y }; - } - - return res; - } - - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - if (axis === 'z') return true; - const obj = this.getAxis(axis); - return obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1; - } - - /** @summary Performs 2D drawing of histogram - * @return {Promise} when ready */ - async draw2D(reason) { - this.clear3DScene(); - - return this.drawFrameAxes().then(res => { - return res ? this.drawingBins(reason) : false; - }).then(res => { - if (res) return this.draw2DBins().then(() => this.addInteractivity()); - }).then(() => this); - } - - /** @summary Performs 3D drawing of histogram - * @return {Promise} when ready */ - async draw3D(reason) { - console.log('3D drawing is disabled, load ./hist/RH1Painter.mjs'); - return this.draw2D(reason); - } - - /** @summary Call drawing function depending from 3D mode */ - async callDrawFunc(reason) { - const main = this.getFramePainter(); - - if (main && (main.mode3d !== this.options.Mode3D) && !this.isMainPainter()) - this.options.Mode3D = main.mode3d; - - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } - - /** @summary Redraw histogram */ - async redraw(reason) { - return this.callDrawFunc(reason); - } - - /** @summary Draw histogram using painter instance - * @private */ - static async _draw(painter /* , opt */) { - return ensureRCanvas(painter).then(() => { - painter.setAsMainPainter(); - - painter.options = { Hist: false, Error: false, Zero: false, Mark: false, - Line: false, Fill: false, Lego: 0, Surf: 0, - Text: true, TextAngle: 0, TextKind: '', - BaseLine: false, Mode3D: false, AutoColor: 0, - Color: false, Scat: false, ScatCoef: 1, Box: false, BoxStyle: 0, Arrow: false, Contour: 0, Proj: 0, - BarOffset: 0, BarWidth: 1, minimum: kNoZoom, maximum: kNoZoom, - FrontBox: false, BackBox: false }; - - const kind = painter.v7EvalAttr('kind', ''), - sub = painter.v7EvalAttr('sub', 0), - o = painter.options; - - o.Text = painter.v7EvalAttr('drawtext', false); - - switch (kind) { - case 'lego': o.Lego = sub > 0 ? 10+sub : 12; o.Mode3D = true; break; - case 'surf': o.Surf = sub > 0 ? 10+sub : 1; o.Mode3D = true; break; - case 'box': o.Box = true; o.BoxStyle = 10 + sub; break; - case 'err': o.Error = true; o.Mode3D = true; break; - case 'cont': o.Contour = sub > 0 ? 10+sub : 1; break; - case 'arr': o.Arrow = true; break; - case 'scat': o.Scat = true; break; - case 'col': o.Color = true; break; - default: if (!o.Text) o.Color = true; - } - - // here we deciding how histogram will look like and how will be shown - // painter.decodeOptions(opt); - - painter._show_empty_bins = false; - - painter.scanContent(); - - return painter.callDrawFunc(); - }); - } - - /** @summary draw RH2 object */ - static async draw(dom, obj, opt) { - // create painter and add it to canvas - return RH2Painter._draw(new RH2Painter(dom, obj), opt); - } - -} // class RH2Painter - -export { RH2Painter }; diff --git a/modules/hist2d/RHistPainter.mjs b/modules/hist2d/RHistPainter.mjs deleted file mode 100644 index e95474452..000000000 --- a/modules/hist2d/RHistPainter.mjs +++ /dev/null @@ -1,853 +0,0 @@ -import { gStyle, settings, isObject, isFunc, isStr, nsREX, getPromise } from '../core.mjs'; -import { kAxisLabels, kAxisTime } from '../base/ObjectPainter.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; - - -/** @summary assign methods for the RAxis objects - * @private */ -function assignRAxisMethods(axis) { - if ((axis._typename === `${nsREX}RAxisEquidistant`) || (axis._typename === `${nsREX}RAxisLabels`)) { - if (axis.fInvBinWidth === 0) { - axis.$dummy = true; - axis.fInvBinWidth = 1; - axis.fNBinsNoOver = 0; - axis.fLow = 0; - } - - axis.min = axis.fLow; - axis.max = axis.fLow + axis.fNBinsNoOver/axis.fInvBinWidth; - axis.GetNumBins = function() { return this.fNBinsNoOver; }; - axis.GetBinCoord = function(bin) { return this.fLow + bin/this.fInvBinWidth; }; - axis.FindBin = function(x, add) { return Math.floor((x - this.fLow)*this.fInvBinWidth + add); }; - } else if (axis._typename === `${nsREX}RAxisIrregular`) { - axis.min = axis.fBinBorders[0]; - axis.max = axis.fBinBorders[axis.fBinBorders.length - 1]; - axis.GetNumBins = function() { return this.fBinBorders.length; }; - axis.GetBinCoord = function(bin) { - const indx = Math.round(bin); - if (indx <= 0) return this.fBinBorders[0]; - if (indx >= this.fBinBorders.length) return this.fBinBorders[this.fBinBorders.length - 1]; - if (indx === bin) return this.fBinBorders[indx]; - const indx2 = (bin < indx) ? indx - 1 : indx + 1; - return this.fBinBorders[indx] * Math.abs(bin-indx2) + this.fBinBorders[indx2] * Math.abs(bin-indx); - }; - axis.FindBin = function(x, add) { - for (let k = 1; k < this.fBinBorders.length; ++k) - if (x < this.fBinBorders[k]) return Math.floor(k-1+add); - return this.fBinBorders.length - 1; - }; - } - - // to support some code from ROOT6 drawing - - axis.GetBinCenter = function(bin) { return this.GetBinCoord(bin-0.5); }; - axis.GetBinLowEdge = function(bin) { return this.GetBinCoord(bin-1); }; -} - -/** @summary Returns real histogram impl - * @private */ -function getHImpl(obj) { - return obj?.fHistImpl?.fIO || null; -} - - -/** @summary Base painter class for RHist objects - * - * @private - */ - -class RHistPainter extends RObjectPainter { - - /** @summary Constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} histo - RHist object */ - constructor(dom, histo) { - super(dom, histo); - this.csstype = 'hist'; - this.draw_content = true; - this.nbinsx = 0; - this.nbinsy = 0; - this.accept_drops = true; // indicate that one can drop other objects like doing Draw('same') - this.mode3d = false; - - // initialize histogram methods - this.getHisto(true); - } - - /** @summary Returns true if RHistDisplayItem is used */ - isDisplayItem() { - return this.getObject()?.fAxes; - } - - /** @summary get histogram */ - getHisto(force) { - const obj = this.getObject(); - let histo = getHImpl(obj); - - if (histo && (!histo.getBinContent || force)) { - if (histo.fAxes._2) { - assignRAxisMethods(histo.fAxes._0); - assignRAxisMethods(histo.fAxes._1); - assignRAxisMethods(histo.fAxes._2); - histo.getBin = function(x, y, z) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1) + this.fAxes._0.GetNumBins()*this.fAxes._1.GetNumBins()*(z-1); }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x, y, z) { return this.fStatistics.fBinContent[this.getBin(x, y, z)]; }; - histo.getBinError = function(x, y, z) { - const bin = this.getBin(x, y, z); - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); - }; - } else if (histo.fAxes._1) { - assignRAxisMethods(histo.fAxes._0); - assignRAxisMethods(histo.fAxes._1); - histo.getBin = function(x, y) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1); }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x, y) { return this.fStatistics.fBinContent[this.getBin(x, y)]; }; - histo.getBinError = function(x, y) { - const bin = this.getBin(x, y); - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); - }; - } else { - assignRAxisMethods(histo.fAxes._0); - histo.getBin = function(x) { return x-1; }; - // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now - histo.getBinContent = function(x) { return this.fStatistics.fBinContent[x-1]; }; - histo.getBinError = function(x) { - if (this.fStatistics.fSumWeightsSquared) - return Math.sqrt(this.fStatistics.fSumWeightsSquared[x-1]); - return Math.sqrt(Math.abs(this.fStatistics.fBinContent[x-1])); - }; - } - } else if (!histo && obj?.fAxes) { - // case of RHistDisplayItem - - histo = obj; - - if (!histo.getBinContent || force) { - if (histo.fAxes.length === 3) { - assignRAxisMethods(histo.fAxes[0]); - assignRAxisMethods(histo.fAxes[1]); - assignRAxisMethods(histo.fAxes[2]); - - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; - - histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; - histo.dy = histo.fIndicies[3] + 1; - histo.stepy = histo.fIndicies[5]; - - histo.nz = histo.fIndicies[7] - histo.fIndicies[6]; - histo.dz = histo.fIndicies[6] + 1; - histo.stepz = histo.fIndicies[8]; - - // this is index in original histogram - histo.getBin = function(x, y, z) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1) + this.fAxes[0].GetNumBins()*this.fAxes[1].GetNumBins()*(z-1); }; - - // this is index in current available data - if ((histo.stepx > 1) || (histo.stepy > 1) || (histo.stepz > 1)) - histo.getBin0 = function(x, y, z) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy) + this.nx/this.stepx*this.ny/this.stepy*Math.floor((z-this.dz)/this.stepz); }; - else - histo.getBin0 = function(x, y, z) { return (x-this.dx) + this.nx*(y-this.dy) + this.nx*this.ny*(z-this.dz); }; - - histo.getBinContent = function(x, y, z) { return this.fBinContent[this.getBin0(x, y, z)]; }; - histo.getBinError = function(x, y, z) { return Math.sqrt(Math.abs(this.getBinContent(x, y, z))); }; - } else if (histo.fAxes.length === 2) { - assignRAxisMethods(histo.fAxes[0]); - assignRAxisMethods(histo.fAxes[1]); - - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; - - histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; - histo.dy = histo.fIndicies[3] + 1; - histo.stepy = histo.fIndicies[5]; - - // this is index in original histogram - histo.getBin = function(x, y) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1); }; - - // this is index in current available data - if ((histo.stepx > 1) || (histo.stepy > 1)) - histo.getBin0 = function(x, y) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy); }; - else - histo.getBin0 = function(x, y) { return (x-this.dx) + this.nx*(y-this.dy); }; - - histo.getBinContent = function(x, y) { return this.fBinContent[this.getBin0(x, y)]; }; - histo.getBinError = function(x, y) { return Math.sqrt(Math.abs(this.getBinContent(x, y))); }; - } else { - assignRAxisMethods(histo.fAxes[0]); - histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; - histo.dx = histo.fIndicies[0] + 1; - histo.stepx = histo.fIndicies[2]; - - histo.getBin = function(x) { return x-1; }; - if (histo.stepx > 1) - histo.getBin0 = function(x) { return Math.floor((x-this.dx)/this.stepx); }; - else - histo.getBin0 = function(x) { return x-this.dx; }; - histo.getBinContent = function(x) { return this.fBinContent[this.getBin0(x)]; }; - histo.getBinError = function(x) { return Math.sqrt(Math.abs(this.getBinContent(x))); }; - } - } - } - return histo; - } - - /** @summary Decode options */ - decodeOptions(/* opt */) { - if (!this.options) this.options = { Hist: 1, System: 1 }; - } - - /** @summary Copy draw options from other painter */ - copyOptionsFrom(src) { - if (src === this) return; - const o = this.options, o0 = src.options; - o.Mode3D = o0.Mode3D; - } - - /** @summary copy draw options to all other histograms in the pad */ - copyOptionsToOthers() { - this.forEachPainter(painter => { - if ((painter !== this) && isFunc(painter.copyOptionsFrom)) - painter.copyOptionsFrom(this); - }, 'objects'); - } - - /** @summary Clear 3d drawings - if any */ - clear3DScene() { - const fp = this.getFramePainter(); - if (isFunc(fp?.create3DScene)) - fp.create3DScene(-1); - this.mode3d = false; - } - - /** @summary Cleanup hist painter */ - cleanup() { - this.clear3DScene(); - - delete this.options; - - super.cleanup(); - } - - /** @summary Returns histogram dimension */ - getDimension() { return 1; } - - /** @summary Scan histogram content - * @abstract */ - scanContent(/* when_axis_changed */) { - // function will be called once new histogram or - // new histogram content is assigned - // one should find min,max,nbins, maxcontent values - // if when_axis_changed === true specified, content will be scanned after axis zoom changed - } - - /** @summary Draw axes */ - async drawFrameAxes() { - // return true when axes was drawn - const main = this.getFramePainter(); - if (!main) - return false; - - if (!this.draw_content) - return true; - - if (!this.isMainPainter()) { - if (!this.options.second_x && !this.options.second_y) - return true; - - main.setAxes2Ranges(this.options.second_x, this.getAxis('x'), this.xmin, this.xmax, this.options.second_y, this.getAxis('y'), this.ymin, this.ymax); - return main.drawAxes2(this.options.second_x, this.options.second_y); - } - - main.cleanupAxes(); - main.xmin = main.xmax = 0; - main.ymin = main.ymax = 0; - main.zmin = main.zmax = 0; - main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, this.getAxis('z'), this.zmin, this.zmax); - return main.drawAxes(); - } - - /** @summary create attributes */ - createHistDrawAttributes() { - this.createv7AttFill(); - this.createv7AttLine(); - } - - /** @summary update display item */ - updateDisplayItem(obj, src) { - if (!obj || !src) return false; - - obj.fAxes = src.fAxes; - obj.fIndicies = src.fIndicies; - obj.fBinContent = src.fBinContent; - obj.fContMin = src.fContMin; - obj.fContMinPos = src.fContMinPos; - obj.fContMax = src.fContMax; - - // update histogram attributes - this.getHisto(true); - - return true; - } - - /** @summary update histogram object */ - updateObject(obj /* , opt */) { - const origin = this.getObject(); - - if (obj !== origin) { - if (!this.matchObjectType(obj)) return false; - - if (this.isDisplayItem()) - - this.updateDisplayItem(origin, obj); - - else { - const horigin = getHImpl(origin), - hobj = getHImpl(obj); - - if (!horigin || !hobj) return false; - - // make it easy - copy statistics without axes - horigin.fStatistics = hobj.fStatistics; - - origin.fTitle = obj.fTitle; - } - } - - this.scanContent(); - - this.histogram_updated = true; // indicate that object updated - - return true; - } - - /** @summary Get axis object */ - getAxis(name) { - const histo = this.getHisto(), obj = this.getObject(); - let axis; - - if (obj?.fAxes) { - switch (name) { - case 'x': axis = obj.fAxes[0]; break; - case 'y': axis = obj.fAxes[1]; break; - case 'z': axis = obj.fAxes[2]; break; - default: axis = obj.fAxes[0]; break; - } - } else if (histo?.fAxes) { - switch (name) { - case 'x': axis = histo.fAxes._0; break; - case 'y': axis = histo.fAxes._1; break; - case 'z': axis = histo.fAxes._2; break; - default: axis = histo.fAxes._0; break; - } - } - - if (axis && !axis.GetBinCoord) - assignRAxisMethods(axis); - - return axis; - } - - /** @summary Get tip text for axis bin */ - getAxisBinTip(name, bin, step) { - const pmain = this.getFramePainter(), - handle = pmain[`${name}_handle`], - axis = this.getAxis(name), - x1 = axis.GetBinCoord(bin); - - if (handle.kind === kAxisLabels) - return pmain.axisAsText(name, x1); - - const x2 = axis.GetBinCoord(bin+(step || 1)); - - if (handle.kind === kAxisTime) - return pmain.axisAsText(name, (x1+x2)/2); - - return `[${pmain.axisAsText(name, x1)}, ${pmain.axisAsText(name, x2)})`; - } - - /** @summary Extract axes ranges and bins numbers - * @desc Also here ensured that all axes objects got their necessary methods */ - extractAxesProperties(ndim) { - const histo = this.getHisto(); - if (!histo) return; - - this.nbinsx = this.nbinsy = this.nbinsz = 0; - - let axis = this.getAxis('x'); - this.nbinsx = axis.GetNumBins(); - this.xmin = axis.min; - this.xmax = axis.max; - - if (ndim < 2) return; - axis = this.getAxis('y'); - this.nbinsy = axis.GetNumBins(); - this.ymin = axis.min; - this.ymax = axis.max; - - if (ndim < 3) return; - axis = this.getAxis('z'); - this.nbinsz = axis.GetNumBins(); - this.zmin = axis.min; - this.zmax = axis.max; - } - - /** @summary Add interactive features, only main painter does it */ - addInteractivity() { - // only first painter in list allowed to add interactive functionality to the frame - - const ismain = this.isMainPainter(), - second_axis = this.options.second_x || this.options.second_y, - fp = ismain || second_axis ? this.getFramePainter() : null; - return fp?.addInteractivity(!ismain && second_axis) ?? true; - } - - /** @summary Process item reply */ - processItemReply(reply, req) { - if (!this.isDisplayItem()) - return console.error('Get item when display normal histogram'); - - if (req.reqid === this.current_item_reqid) { - if (reply !== null) - this.updateDisplayItem(this.getObject(), reply.item); - - req.resolveFunc(true); - } - } - - /** @summary Special method to request bins from server if existing data insufficient - * @return {Promise} when ready */ - async drawingBins(reason) { - let is_axes_zoomed = false; - if (reason && isStr(reason) && (reason.indexOf('zoom') === 0)) { - if (reason.indexOf('0') > 0) is_axes_zoomed = true; - if ((this.getDimension() > 1) && (reason.indexOf('1') > 0)) is_axes_zoomed = true; - if ((this.getDimension() > 2) && (reason.indexOf('2') > 0)) is_axes_zoomed = true; - } - - if (this.isDisplayItem() && is_axes_zoomed && this.v7NormalMode()) { - const handle = this.prepareDraw({ only_indexes: true }); - - // submit request if histogram data not enough for display - if (handle.incomplete) { - return new Promise(resolveFunc => { - // use empty kind to always submit request - const req = this.v7SubmitRequest('', { _typename: `${nsREX}RHistDrawableBase::RRequest` }, - this.processItemReply.bind(this)); - if (req) { - this.current_item_reqid = req.reqid; // ignore all previous requests, only this one will be processed - req.resolveFunc = resolveFunc; - setTimeout(this.processItemReply.bind(this, null, req), 1000); // after 1 s draw something that we can - } else - resolveFunc(true); - }); - } - } - - return true; - } - - /** @summary Toggle statbox drawing - * @desc Not yet implemented */ - toggleStat(/* arg */) {} - - /** @summary get selected index for axis */ - getSelectIndex(axis, size, add) { - // be aware - here indexes starts from 0 - const taxis = this.getAxis(axis), - nbins = this['nbins'+axis] || 0; - let indx = 0; - - if (this.options.second_x && axis === 'x') axis = 'x2'; - if (this.options.second_y && axis === 'y') axis = 'y2'; - - const main = this.getFramePainter(), - min = main ? main[`zoom_${axis}min`] : 0, - max = main ? main[`zoom_${axis}max`] : 0; - - if ((min !== max) && taxis) { - if (size === 'left') - indx = taxis.FindBin(min, add || 0); - else - indx = taxis.FindBin(max, (add || 0) + 0.5); - if (indx < 0) - indx = 0; - else if (indx > nbins) - indx = nbins; - } else - indx = (size === 'left') ? 0 : nbins; - - - return indx; - } - - /** @summary Auto zoom into histogram non-empty range - * @abstract */ - autoZoom() {} - - /** @summary Process click on histogram-defined buttons */ - clickButton(funcname) { - const fp = this.getFramePainter(); - if (!fp) return false; - - switch (funcname) { - case 'ToggleZoom': - if ((this.zoom_xmin !== this.zoom_xmax) || (this.zoom_ymin !== this.zoom_ymax) || (this.zoom_zmin !== this.zoom_zmax)) { - const res = this.unzoom(); - fp.zoomChangedInteractive('reset'); - return res; - } - if (this.draw_content) - return this.autoZoom(); - break; - case 'ToggleLogX': return fp.toggleAxisLog('x'); - case 'ToggleLogY': return fp.toggleAxisLog('y'); - case 'ToggleLogZ': return fp.toggleAxisLog('z'); - case 'ToggleStatBox': return getPromise(this.toggleStat()); - } - return false; - } - - /** @summary Fill pad toolbar with hist-related functions */ - fillToolbar(not_shown) { - const pp = this.getPadPainter(); - if (!pp) return; - - pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); - pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); - pp.addPadButton('arrow_up', 'Toggle log y', 'ToggleLogY', 'PageUp'); - if (this.getDimension() > 1) - pp.addPadButton('arrow_diag', 'Toggle log z', 'ToggleLogZ'); - if (this.draw_content) - pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); - if (!not_shown) pp.showPadButtons(); - } - - /** @summary get tool tips used in 3d mode */ - get3DToolTip(indx) { - const histo = this.getHisto(), - tip = { bin: indx, name: histo.fName || 'histo', title: histo.fTitle }; - switch (this.getDimension()) { - case 1: - tip.ix = indx + 1; tip.iy = 1; - tip.value = histo.getBinContent(tip.ix); - tip.error = histo.getBinError(tip.ix); - tip.lines = this.getBinTooltips(indx-1); - break; - case 2: - tip.ix = (indx % this.nbinsx) + 1; - tip.iy = (indx - (tip.ix - 1)) / this.nbinsx + 1; - tip.value = histo.getBinContent(tip.ix, tip.iy); - tip.error = histo.getBinError(tip.ix, tip.iy); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1); - break; - case 3: - tip.ix = indx % this.nbinsx + 1; - tip.iy = ((indx - (tip.ix - 1)) / this.nbinsx) % this.nbinsy + 1; - tip.iz = (indx - (tip.ix - 1) - (tip.iy - 1) * this.nbinsx) / this.nbinsx / this.nbinsy + 1; - tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); - tip.error = histo.getBinError(tip.ix, tip.iy, tip.iz); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1, tip.iz-1); - break; - } - - return tip; - } - - /** @summary Create contour levels for currently selected Z range */ - createContour(main, palette, args) { - if (!main || !palette) return; - - if (!args) args = {}; - - let nlevels = gStyle.fNumberContours, - zmin = this.minbin, zmax = this.maxbin, zminpos = this.minposbin; - - if (args.scatter_plot) { - if (nlevels > 50) nlevels = 50; - zmin = this.minposbin; - } - - if (zmin === zmax) { zmin = this.gminbin; zmax = this.gmaxbin; zminpos = this.gminposbin; } - - if (this.getDimension() < 3) { - if (main.zoom_zmin !== main.zoom_zmax) { - zmin = main.zoom_zmin; - zmax = main.zoom_zmax; - } else if (args.full_z_range) { - zmin = main.zmin; - zmax = main.zmax; - } - } - - palette.setFullRange(main.zmin, main.zmax); - palette.createContour(main.logz, nlevels, zmin, zmax, zminpos); - - if (this.getDimension() < 3) { - main.scale_zmin = palette.colzmin; - main.scale_zmax = palette.colzmax; - } - } - - /** @summary Start dialog to modify range of axis where histogram values are displayed */ - changeValuesRange(menu, arg) { - const pmain = this.getFramePainter(); - if (!pmain) return; - const prefix = pmain.isAxisZoomed(arg) ? 'zoom_' + arg : arg, - curr = '[' + pmain[`${prefix}min`] + ',' + pmain[`${prefix}max`] + ']'; - menu.input('Enter values range for axis ' + arg + ' like [0,100] or empty string to unzoom', curr).then(res => { - res = res ? JSON.parse(res) : []; - if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) - pmain.unzoom(arg); - else - pmain.zoom(arg, res[0], res[1]); - }); - } - - /** @summary Fill histogram context menu */ - fillContextMenuItems(menu) { - if (this.draw_content) { - menu.addchk(this.toggleStat('only-check'), 'Show statbox', () => this.toggleStat()); - - if (this.getDimension() === 2) - menu.add('Values range', () => this.changeValuesRange(menu, 'z')); - - if (isFunc(this.fillHistContextMenu)) - this.fillHistContextMenu(menu); - } - - const fp = this.getFramePainter(); - - if (this.options.Mode3D) { - // menu for 3D drawings - - if (menu.size() > 0) - menu.add('separator'); - - const main = this.getMainPainter() || this; - - menu.addchk(main.isTooltipAllowed(), 'Show tooltips', () => main.setTooltipAllowed('toggle')); - - menu.addchk(fp?.enable_highlight, 'Highlight bins', () => { - fp.enable_highlight = !fp.enable_highlight; - if (!fp.enable_highlight && main.mode3d && isFunc(main.highlightBin3D)) - main.highlightBin3D(null); - }); - - if (isFunc(fp?.render3D)) { - menu.addchk(main.options.FrontBox, 'Front box', () => { - main.options.FrontBox = !main.options.FrontBox; - fp.render3D(); - }); - menu.addchk(main.options.BackBox, 'Back box', () => { - main.options.BackBox = !main.options.BackBox; - fp.render3D(); - }); - } - - if (this.draw_content) { - menu.addchk(!this.options.Zero, 'Suppress zeros', () => { - this.options.Zero = !this.options.Zero; - this.redrawPad(); - }); - - if ((this.options.Lego === 12) || (this.options.Lego === 14)) - this.fillPaletteMenu(menu); - } - - if (isFunc(main.control?.reset)) - menu.add('Reset camera', () => main.control.reset()); - } - - if (this.histogram_updated && fp.zoomChangedInteractive()) - menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); - } - - /** @summary Update palette drawing */ - updatePaletteDraw() { - if (this.isMainPainter()) - this.getPadPainter().findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`)?.drawPalette(); - } - - /** @summary Fill menu entries for palette */ - fillPaletteMenu(menu) { - menu.addPaletteMenu(this.options.Palette || settings.Palette, arg => { - // TODO: rewrite for RPalette functionality - this.options.Palette = parseInt(arg); - this.redraw(); // redraw histogram - }); - } - - /** @summary Toggle 3D drawing mode */ - toggleMode3D() { - this.options.Mode3D = !this.options.Mode3D; - - if (this.options.Mode3D) { - if (!this.options.Surf && !this.options.Lego && !this.options.Error) { - if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) - this.options.Lego = this.options.Color ? 14 : 13; - else - this.options.Lego = this.options.Color ? 12 : 1; - - this.options.Zero = false; // do not show zeros by default - } - } - - this.copyOptionsToOthers(); - return this.interactiveRedraw('pad', 'drawopt'); - } - - /** @summary Calculate histogram inidicies and axes values for each visible bin */ - prepareDraw(args) { - if (!args) args = { rounding: true, extra: 0, middle: 0 }; - - if (args.extra === undefined) args.extra = 0; - if (args.right_extra === undefined) args.right_extra = args.extra; - if (args.middle === undefined) args.middle = 0; - - const histo = this.getHisto(), xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), - pmain = this.getFramePainter(), - hdim = this.getDimension(), - res = { - i1: this.getSelectIndex('x', 'left', 0 - args.extra), - i2: this.getSelectIndex('x', 'right', 1 + args.right_extra), - j1: (hdim < 2) ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra), - j2: (hdim < 2) ? 1 : this.getSelectIndex('y', 'right', 1 + args.right_extra), - k1: (hdim < 3) ? 0 : this.getSelectIndex('z', 'left', 0 - args.extra), - k2: (hdim < 3) ? 1 : this.getSelectIndex('z', 'right', 1 + args.right_extra), - stepi: 1, stepj: 1, stepk: 1, - min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1 - }; - let i, j, x, y, binz, binarea; - - if (this.isDisplayItem() && histo.fIndicies) { - if (res.i1 < histo.fIndicies[0]) { res.i1 = histo.fIndicies[0]; res.incomplete = true; } - if (res.i2 > histo.fIndicies[1]) { res.i2 = histo.fIndicies[1]; res.incomplete = true; } - res.stepi = histo.fIndicies[2]; - if (res.stepi > 1) res.incomplete = true; - if ((hdim > 1) && (histo.fIndicies.length > 5)) { - if (res.j1 < histo.fIndicies[3]) { res.j1 = histo.fIndicies[3]; res.incomplete = true; } - if (res.j2 > histo.fIndicies[4]) { res.j2 = histo.fIndicies[4]; res.incomplete = true; } - res.stepj = histo.fIndicies[5]; - if (res.stepj > 1) res.incomplete = true; - } - if ((hdim > 2) && (histo.fIndicies.length > 8)) { - if (res.k1 < histo.fIndicies[6]) { res.k1 = histo.fIndicies[6]; res.incomplete = true; } - if (res.k2 > histo.fIndicies[7]) { res.k2 = histo.fIndicies[7]; res.incomplete = true; } - res.stepk = histo.fIndicies[8]; - if (res.stepk > 1) res.incomplete = true; - } - } - - if (args.only_indexes) return res; - - // no need for Float32Array, plain Array is 10% faster - // reserve more places to avoid complex boundary checks - - res.grx = new Array(res.i2+res.stepi+1); - res.gry = new Array(res.j2+res.stepj+1); - - if (args.original) { - res.original = true; - res.origx = new Array(res.i2+1); - res.origy = new Array(res.j2+1); - } - - if (args.pixel_density) args.rounding = true; - - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); - - // calculate graphical coordinates in advance - for (i = res.i1; i <= res.i2; ++i) { - x = xaxis.GetBinCoord(i + args.middle); - if (funcs.logx && (x <= 0)) { res.i1 = i+1; continue; } - if (res.origx) res.origx[i] = x; - res.grx[i] = funcs.grx(x); - if (args.rounding) res.grx[i] = Math.round(res.grx[i]); - - if (args.use3d) { - if (res.grx[i] < -pmain.size_x3d) { res.i1 = i; res.grx[i] = -pmain.size_x3d; } - if (res.grx[i] > pmain.size_x3d) { res.i2 = i; res.grx[i] = pmain.size_x3d; } - } - } - - if (args.use3d) { - if ((res.i1 < res.i2-2) && (res.grx[res.i1] === res.grx[res.i1+1])) res.i1++; - if ((res.i1 < res.i2-2) && (res.grx[res.i2-1] === res.grx[res.i2])) res.i2--; - } - - // copy last valid value to higher indicies - while (i < res.i2 + res.stepi + 1) - res.grx[i++] = res.grx[res.i2]; - - if (hdim === 1) { - res.gry[0] = funcs.gry(0); - res.gry[1] = funcs.gry(1); - } else { - for (j = res.j1; j <= res.j2; ++j) { - y = yaxis.GetBinCoord(j + args.middle); - if (funcs.logy && (y <= 0)) { res.j1 = j+1; continue; } - if (res.origy) res.origy[j] = y; - res.gry[j] = funcs.gry(y); - if (args.rounding) res.gry[j] = Math.round(res.gry[j]); - - if (args.use3d) { - if (res.gry[j] < -pmain.size_y3d) { res.j1 = j; res.gry[j] = -pmain.size_y3d; } - if (res.gry[j] > pmain.size_y3d) { res.j2 = j; res.gry[j] = pmain.size_y3d; } - } - } - } - - if (args.use3d && (hdim > 1)) { - if ((res.j1 < res.j2-2) && (res.gry[res.j1] === res.gry[res.j1+1])) res.j1++; - if ((res.j1 < res.j2-2) && (res.gry[res.j2-1] === res.gry[res.j2])) res.j2--; - } - - // copy last valid value to higher indicies - if (hdim > 1) { - while (j < res.j2 + res.stepj + 1) - res.gry[j++] = res.gry[res.j2]; - } - - // find min/max values in selected range - this.maxbin = this.minbin = this.minposbin = null; - - for (i = res.i1; i < res.i2; i += res.stepi) { - for (j = res.j1; j < res.j2; j += res.stepj) { - binz = histo.getBinContent(i + 1, j + 1); - if (!Number.isFinite(binz)) continue; - res.sumz += binz; - if (args.pixel_density) { - binarea = (res.grx[i+res.stepi]-res.grx[i])*(res.gry[j]-res.gry[j+res.stepj]); - if (binarea <= 0) continue; - res.max = Math.max(res.max, binz); - if ((binz > 0) && ((binz < res.min) || (res.min === 0))) res.min = binz; - binz = binz/binarea; - } - if (this.maxbin === null) - this.maxbin = this.minbin = binz; - else { - this.maxbin = Math.max(this.maxbin, binz); - this.minbin = Math.min(this.minbin, binz); - } - if (binz > 0) - if ((this.minposbin === null) || (binz < this.minposbin)) this.minposbin = binz; - } - } - - res.palette = pmain.getHistPalette(); - - if (res.palette) - this.createContour(pmain, res.palette, args); - - return res; - } - -} // class RHistPainter - -export { RHistPainter }; diff --git a/modules/hist2d/TGraphPainter.mjs b/modules/hist2d/TGraphPainter.mjs index 17b6c5e34..ff486c721 100644 --- a/modules/hist2d/TGraphPainter.mjs +++ b/modules/hist2d/TGraphPainter.mjs @@ -1,5 +1,5 @@ import { gStyle, BIT, settings, create, createHistogram, setHistogramTitle, isFunc, isStr, - clTPaveStats, clTCutG, clTH1I, clTH2I, clTF1, clTF2, clTPad, kNoZoom, kNoStats } from '../core.mjs'; + clTPaveStats, clTCutG, clTH1F, clTH2F, clTF1, clTF2, clTPad, kNoZoom, kNoStats } from '../core.mjs'; import { select as d3_select } from '../d3.mjs'; import { DrawOptions, buildSvgCurve, makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; import { ObjectPainter, kAxisNormal } from '../base/ObjectPainter.mjs'; @@ -7,7 +7,7 @@ import { FunctionsHandler } from './THistPainter.mjs'; import { TH1Painter, PadDrawOptions } from './TH1Painter.mjs'; import { kBlack, kWhite } from '../base/colors.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; +import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; const kNotEditable = BIT(18), // bit set if graph is non editable @@ -22,58 +22,104 @@ const kNotEditable = BIT(18), // bit set if graph is non editable * @private */ - class TGraphPainter extends ObjectPainter { + #bins; // extracted graph bins + #barwidth; // width of each bar + #baroffset; // offset of each bar + #redraw_hist; // indicate that histogram need to be redrawn + #auto_exec; // can be reused when sending option back to server + #funcs_handler; // special instance for functions drawing + #frame_layer; // frame layer used for drawing + #cutg; // is cutg object + #cutg_lastsame; // indicate that last point is same as first + #own_histogram; // if histogram created by TGraphPainter + #marker_size; // used marker size + #move_binindx; // index of moving bin + #move_funcs; // moving functions + #move_bin; // moving bin + #move_x0; // initial x position + #move_y0; // initial y position + #pos_dx; // accumulated x change + #pos_dy; // accumulated y change + #has_errors; // if has errors + #is_bent; // if graph has bent errors + #draw_kind; // way how graph is drawn + constructor(dom, graph) { super(dom, graph); this.axes_draw = false; // indicate if graph histogram was drawn for axes - this.bins = null; this.xmin = this.ymin = this.xmax = this.ymax = 0; - this.wheel_zoomy = true; - this.is_bent = (graph._typename === clTGraphBentErrors); - this.has_errors = (graph._typename === clTGraphErrors) || - (graph._typename === clTGraphMultiErrors) || - (graph._typename === clTGraphAsymmErrors) || - this.is_bent || graph._typename.match(/^RooHist/); + this.#is_bent = (graph._typename === clTGraphBentErrors); + this.#has_errors = (graph._typename === clTGraphErrors) || + (graph._typename === clTGraphMultiErrors) || + (graph._typename === clTGraphAsymmErrors) || + this.#is_bent || graph._typename.match(/^RooHist/); + this.#draw_kind = ''; } + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } + /** @summary Return drawn graph object */ getGraph() { return this.getObject(); } /** @summary Return histogram object used for axis drawings */ getHistogram() { return this.getObject()?.fHistogram; } + /** @summary Return true if histogram not present or has dummy ranges (for requested axis) */ + isDummyHistogram(check_axis) { + const histo = this.getHistogram(); + if (!histo) + return true; + + let is_normal = false; + if (check_axis !== 'y') + is_normal ||= (histo.fXaxis.fXmin !== 0.0011) || (histo.fXaxis.fXmax !== 1.1); + + if (check_axis !== 'x') { + is_normal ||= (histo.fYaxis.fXmin !== 0.0011) || (histo.fYaxis.fXmax !== 1.1) || + (histo.fMinimum !== 0.0011) || (histo.fMaximum !== 1.1); + } + + return !is_normal; + } + /** @summary Set histogram object to graph */ setHistogram(histo) { const obj = this.getObject(); - if (obj) obj.fHistogram = histo; + if (obj) + obj.fHistogram = histo; } + /** @summary Is TScatter object */ + isScatter() { return false; } + /** @summary Redraw graph * @desc may redraw histogram which was used to draw axes * @return {Promise} for ready */ async redraw() { let promise = Promise.resolve(true); - if (this.$redraw_hist) { - delete this.$redraw_hist; + if (this.#redraw_hist) { + this.#redraw_hist = undefined; const hist_painter = this.getMainPainter(); if (hist_painter?.isSecondary(this) && this.axes_draw) promise = hist_painter.redraw(); } return promise.then(() => this.drawGraph()).then(() => { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; return res; }); } /** @summary Cleanup graph painter */ cleanup() { - delete this.interactive_bin; // break mouse handling - delete this.bins; + this.#bins = undefined; + this.#own_histogram = undefined; super.cleanup(); } @@ -89,41 +135,88 @@ class TGraphPainter extends ObjectPainter { opt = opt.slice(5); const graph = this.getGraph(), - is_gme = !!this.get_gme(), - has_main = first_time ? !!this.getMainPainter() : !this.axes_draw; - let blocks_gme = []; - - if (!this.options) this.options = {}; + is_gme = Boolean(this.get_gme()), + has_main = first_time ? Boolean(this.getMainPainter()) : !this.axes_draw; - // decode main draw options for the graph - const decodeBlock = (d, res) => { + function decodeBlock(d, res) { Object.assign(res, { Line: 0, Curve: 0, Rect: 0, Mark: 0, Bar: 0, OutRange: 0, EF: 0, Fill: 0, MainError: 1, Ends: 1, ScaleErrX: 1 }); - if (is_gme && d.check('S=', true)) res.ScaleErrX = d.partAsFloat(); - - if (d.check('L')) res.Line = 1; - if (d.check('F')) res.Fill = 1; - if (d.check('CC')) res.Curve = 2; // draw all points without reduction - if (d.check('C')) res.Curve = 1; - if (d.check('*')) res.Mark = 103; - if (d.check('P0')) res.Mark = 104; - if (d.check('P')) res.Mark = 1; - if (d.check('B')) { res.Bar = 1; res.Errors = 0; } - if (d.check('Z')) { res.Errors = 1; res.Ends = 0; } - if (d.check('||')) { res.Errors = 1; res.MainError = 0; res.Ends = 1; } - if (d.check('[]')) { res.Errors = 1; res.MainError = 0; res.Ends = 2; } - if (d.check('|>')) { res.Errors = 1; res.Ends = 3; } - if (d.check('>')) { res.Errors = 1; res.Ends = 4; } - if (d.check('0')) { res.Mark = 1; res.Errors = 1; res.OutRange = 1; } - if (d.check('1')) if (res.Bar === 1) res.Bar = 2; - if (d.check('2')) { res.Rect = 1; res.Errors = 0; } - if (d.check('3')) { res.EF = 1; res.Errors = 0; } - if (d.check('4')) { res.EF = 2; res.Errors = 0; } - if (d.check('5')) { res.Rect = 2; res.Errors = 0; } - if (d.check('X')) res.Errors = 0; - }; + if (is_gme && d.check('S=', true)) + res.ScaleErrX = d.partAsFloat(); + + if (d.check('L')) + res.Line = 1; + if (d.check('F')) + res.Fill = 1; + if (d.check('CC')) + res.Curve = 2; // draw all points without reduction + if (d.check('C')) + res.Curve = 1; + if (d.check('*')) + res.Mark = 103; + if (d.check('P0')) + res.Mark = 104; + if (d.check('P')) + res.Mark = 1; + if (d.check('B')) { + res.Bar = 1; + res.Errors = 0; + } + if (d.check('Z')) { + res.Errors = 1; + res.Ends = 0; + } + if (d.check('||')) { + res.Errors = 1; + res.MainError = 0; + res.Ends = 1; + } + if (d.check('[]')) { + res.Errors = 1; + res.MainError = 0; + res.Ends = 2; + } + if (d.check('|>')) { + res.Errors = 1; + res.Ends = 3; + } + if (d.check('>')) { + res.Errors = 1; + res.Ends = 4; + } + if (d.check('0')) { + res.Mark = 1; + res.Errors = 1; + res.OutRange = 1; + } + if (d.check('1') && (res.Bar === 1)) + res.Bar = 2; + if (d.check('2')) { + res.Rect = 1; + res.Errors = 0; + } + if (d.check('3')) { + res.EF = 1; + res.Errors = 0; + } + if (d.check('4')) { + res.EF = 2; + res.Errors = 0; + } + if (d.check('5')) { + res.Rect = 2; + res.Errors = 0; + } + if (d.check('X')) + res.Errors = 0; + } + + const res = this.setOptions({ + Axis: '', NoOpt: 0, PadStats: false, PadPalette: false, original: opt, + second_x: false, second_y: false, individual_styles: false + }); - Object.assign(this.options, { Axis: '', NoOpt: 0, PadStats: false, PadPalette: false, original: opt, second_x: false, second_y: false, individual_styles: false }); + let blocks_gme = []; if (is_gme && opt) { if (opt.indexOf(';') > 0) { @@ -135,21 +228,41 @@ class TGraphPainter extends ObjectPainter { } } - const res = this.options; let d = new DrawOptions(opt), hopt = ''; - PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); - if (d.check('XAXIS_', true)) hopt += ';XAXIS_' + d.part; - if (d.check('YAXIS_', true)) hopt += ';YAXIS_' + d.part; + PadDrawOptions.forEach(name => { + if (d.check(name)) + hopt += ';' + name; + }); + if (d.check('XAXIS_', true)) + hopt += ';XAXIS_' + d.part; + if (d.check('YAXIS_', true)) + hopt += ';YAXIS_' + d.part; if (d.empty()) { res.original = has_main ? 'lp' : 'alp'; d = new DrawOptions(res.original); } - if (d.check('NOOPT')) res.NoOpt = 1; + if (d.check('FILL_', 'color')) { + res.graphFillColor = d.color; + res.graphFillPattern = 1001; + } + + if (d.check('FILLPAT_', true)) + res.graphFillPattern = d.partAsInt(); + + if (d.check('LINE_', 'color')) + res.graphLineColor = this.getColor(d.color); + + if (d.check('WIDTH_', true)) + res.graphLineWidth = d.partAsInt(); + + if (d.check('NOOPT')) + res.NoOpt = 1; - if (d.check('POS3D_', true)) res.pos3d = d.partAsInt() - 0.5; + if (d.check('POS3D_', true)) + res.pos3d = d.partAsInt() - 0.5; if (d.check('PFC') && !res._pfc) res._pfc = 2; @@ -158,37 +271,45 @@ class TGraphPainter extends ObjectPainter { if (d.check('PMC') && !res._pmc) res._pmc = 2; - if (d.check('A')) res.Axis = d.check('I') ? 'A;' : ' '; // I means invisible axis - if (d.check('X+')) { res.Axis += 'X+'; res.second_x = has_main; } - if (d.check('Y+')) { res.Axis += 'Y+'; res.second_y = has_main; } - if (d.check('RX')) res.Axis += 'RX'; - if (d.check('RY')) res.Axis += 'RY'; + if (d.check('A')) + res.Axis = d.check('I') ? 'A;' : ' '; // I means invisible axis + if (d.check('X+')) { + res.Axis += 'X+'; + res.second_x = has_main; + } + if (d.check('Y+')) { + res.Axis += 'Y+'; + res.second_y = has_main; + } + if (d.check('RX')) + res.Axis += 'RX'; + if (d.check('RY')) + res.Axis += 'RY'; if (is_gme) { res.blocks = []; res.skip_errors_x0 = res.skip_errors_y0 = false; - if (d.check('X0')) res.skip_errors_x0 = true; - if (d.check('Y0')) res.skip_errors_y0 = true; + if (d.check('X0')) + res.skip_errors_x0 = true; + if (d.check('Y0')) + res.skip_errors_y0 = true; } decodeBlock(d, res); - if (is_gme) - if (d.check('S')) res.individual_styles = true; - - - // if (d.check('E')) res.Errors = 1; // E option only defined for TGraphPolar + if (is_gme && d.check('S')) + res.individual_styles = true; if (res.Errors === undefined) - res.Errors = this.has_errors && (!is_gme || !blocks_gme.length) ? 1 : 0; + res.Errors = this.#has_errors && (!is_gme || !blocks_gme.length) ? 1 : 0; // special case - one could use svg:path to draw many pixels ( - if ((res.Mark === 1) && (graph.fMarkerStyle === 1)) res.Mark = 101; + if ((res.Mark === 1) && (graph.fMarkerStyle === 1)) + res.Mark = 101; // if no drawing option is selected and if opt === '' nothing is done. - if (res.Line + res.Fill + res.Curve + res.Mark + res.Bar + res.EF + res.Rect + res.Errors === 0) - if (d.empty()) res.Line = 1; - + if ((res.Line + res.Fill + res.Curve + res.Mark + res.Bar + res.EF + res.Rect + res.Errors === 0) && d.empty()) + res.Line = 1; if (this.matchObjectType(clTGraphErrors)) { const len = graph.fEX.length; @@ -199,16 +320,17 @@ class TGraphPainter extends ObjectPainter { res.Errors = 0; } - this._cutg = this.matchObjectType(clTCutG); - this._cutg_lastsame = this._cutg && (graph.fNpoints > 3) && - (graph.fX[0] === graph.fX[graph.fNpoints-1]) && (graph.fY[0] === graph.fY[graph.fNpoints-1]); + this.#cutg = this.matchObjectType(clTCutG); + this.#cutg_lastsame = this.#cutg && (graph.fNpoints > 3) && + (graph.fX[0] === graph.fX[graph.fNpoints - 1]) && (graph.fY[0] === graph.fY[graph.fNpoints - 1]); if (!res.Axis) { // check if axis should be drawn // either graph drawn directly or // graph is first object in list of primitives const pad = this.getPadPainter()?.getRootPad(true); - if (!pad || (pad?.fPrimitives?.arr[0] === this.getObject())) res.Axis = ' '; + if (!pad || (pad?.fPrimitives?.arr[0] === this.getObject())) + res.Axis = ' '; } res.Axis += hopt; @@ -222,23 +344,19 @@ class TGraphPainter extends ObjectPainter { } } - /** @summary Extract errors for TGraphMultiErrors */ - extractGmeErrors(nblock) { - if (!this.bins) return; - const gr = this.getGraph(); - this.bins.forEach(bin => { - bin.eylow = gr.fEyL[nblock][bin.indx]; - bin.eyhigh = gr.fEyH[nblock][bin.indx]; - }); - } + /** @summary Return prepared graph bins + * @protected */ + _getBins() { return this.#bins; } /** @summary Create bins for TF1 drawing */ createBins() { - const gr = this.getGraph(); - if (!gr) return; + const gr = this.getGraph(), + o = this.getOptions(); + if (!gr) + return; let kind = 0, npoints = gr.fNpoints; - if (this._cutg && this._cutg_lastsame) + if (this.#cutg && this.#cutg_lastsame) npoints--; if (gr._typename === clTGraphErrors) @@ -248,10 +366,10 @@ class TGraphPainter extends ObjectPainter { else if (gr._typename === clTGraphAsymmErrors || gr._typename === clTGraphBentErrors || gr._typename.match(/^RooHist/)) kind = 3; - this.bins = new Array(npoints); + this.#bins = new Array(npoints); for (let p = 0; p < npoints; ++p) { - const bin = this.bins[p] = { x: gr.fX[p], y: gr.fY[p], indx: p }; + const bin = this.#bins[p] = { x: gr.fX[p], y: gr.fY[p], indx: p }; switch (kind) { case 1: bin.exlow = bin.exhigh = gr.fEX[p]; @@ -288,6 +406,12 @@ class TGraphPainter extends ObjectPainter { this.ymax = Math.max(this.ymax, bin.y); } } + + // workaround, are there better way to show marker at 0,0 on the top of the frame? + this.#frame_layer = true; + if ((this.xmin === 0) && (this.ymin === 0) && (npoints > 0) && (this.#bins[0].x === 0) && (this.#bins[0].y === 0) && + o.Mark && !o.Line && !o.Curve && !o.Fill) + this.#frame_layer = 'upper_layer'; } /** @summary Return margins for histogram ranges */ @@ -297,22 +421,29 @@ class TGraphPainter extends ObjectPainter { * @desc graph bins should be created when calling this function * @param {boolean} [set_x] - set X axis range * @param {boolean} [set_y] - set Y axis range */ - createHistogram(set_x, set_y) { - if (!set_x && !set_y) - set_x = set_y = true; - + createHistogram(set_x = true, set_y = true) { const graph = this.getGraph(), xmin = this.xmin, margin = this.getHistRangeMargin(); let xmax = this.xmax, ymin = this.ymin, ymax = this.ymax; - if (xmin >= xmax) xmax = xmin + 1; - if (ymin >= ymax) ymax = ymin + 1; + if (xmin >= xmax) + xmax = xmin + 1; + if (ymin >= ymax) + ymax = ymin + 1; const dx = (xmax - xmin) * margin, dy = (ymax - ymin) * margin; let uxmin = xmin - dx, uxmax = xmax + dx, minimum = ymin - dy, maximum = ymax + dy; - if (!this._not_adjust_hrange) { + if ((ymin > 0) && (minimum <= 0)) + minimum = (1 - margin) * ymin; + if ((ymax < 0) && (maximum >= 0)) + maximum = (1 - margin) * ymax; + + const minimum0 = minimum, maximum0 = maximum; + let histo = this.getHistogram(); + + if (!this.isScatter() && !histo?.fXaxis.fTimeDisplay) { const pad_logx = this.getPadPainter()?.getPadLog('x'); if ((uxmin < 0) && (xmin >= 0)) @@ -321,23 +452,25 @@ class TGraphPainter extends ObjectPainter { uxmax = pad_logx ? (1 + margin) * xmax : 0; } - const minimum0 = minimum, maximum0 = maximum; - let histo = this.getHistogram(); - if (!histo) { - histo = this._need_2dhist ? createHistogram(clTH2I, 30, 30) : createHistogram(clTH1I, 100); + histo = this.isScatter() ? createHistogram(clTH2F, 30, 30) : createHistogram(clTH1F, 100); histo.fName = graph.fName + '_h'; histo.fBits |= kNoStats; - this._own_histogram = true; + this.#own_histogram = true; this.setHistogram(histo); - } else if ((histo.fMaximum !== kNoZoom) && (histo.fMinimum !== kNoZoom)) { + } else if ((histo.fMaximum !== kNoZoom) && (histo.fMinimum !== kNoZoom) && !this.isDummyHistogram('y')) { minimum = histo.fMinimum; maximum = histo.fMaximum; } - if (graph.fMinimum !== kNoZoom) minimum = ymin = graph.fMinimum; - if (graph.fMaximum !== kNoZoom) maximum = graph.fMaximum; - if ((minimum < 0) && (ymin >= 0)) minimum = (1 - margin)*ymin; + if (graph.fMinimum !== kNoZoom) + minimum = ymin = graph.fMinimum; + if (graph.fMaximum !== kNoZoom) + maximum = graph.fMaximum; + if ((minimum < 0) && (ymin >= 0)) + minimum = (1 - margin) * ymin; + if ((ymax < 0) && (maximum >= 0)) + maximum = (1 - margin) * ymax; setHistogramTitle(histo, this.getObject().fTitle); @@ -349,24 +482,31 @@ class TGraphPainter extends ObjectPainter { if (set_y && !histo.fYaxis.fLabels) { histo.fYaxis.fXmin = Math.min(minimum0, minimum); histo.fYaxis.fXmax = Math.max(maximum0, maximum); - histo.fMinimum = minimum; - histo.fMaximum = maximum; + if (!this.isScatter()) { + histo.fMinimum = minimum; + histo.fMaximum = maximum; + } } + histo.$xmin_nz = xmin > 0 ? xmin : undefined; + histo.$ymin_nz = ymin > 0 ? ymin : undefined; + return histo; } - /** @summary Check if user range can be unzommed + /** @summary Check if user range can be un-zommed * @desc Used when graph points covers larger range than provided histogram */ - unzoomUserRange(dox, doy /*, doz */) { + unzoomUserRange(dox, doy /* , doz */) { const graph = this.getGraph(); - if (this._own_histogram || !graph) return false; + if (this.#own_histogram || !graph) + return false; const histo = this.getHistogram(); dox = dox && histo && ((histo.fXaxis.fXmin > this.xmin) || (histo.fXaxis.fXmax < this.xmax)); doy = doy && histo && ((histo.fYaxis.fXmin > this.ymin) || (histo.fYaxis.fXmax < this.ymax)); - if (!dox && !doy) return false; + if (!dox && !doy) + return false; this.createHistogram(dox, doy); this.getMainPainter()?.extractAxesProperties(1); // just to enforce ranges extraction @@ -376,32 +516,34 @@ class TGraphPainter extends ObjectPainter { /** @summary Returns true if graph drawing can be optimize */ canOptimize() { - return (settings.OptimizeDraw > 0) && !this.options.NoOpt; + return (settings.OptimizeDraw > 0) && !this.getOptions().NoOpt; } /** @summary Returns optimized bins - if optimization enabled */ optimizeBins(maxpnt, filter_func) { - if ((this.bins.length < 30) && !filter_func) - return this.bins; + if ((this.#bins.length < 30) && !filter_func) + return this.#bins; let selbins = null; if (isFunc(filter_func)) { - for (let n = 0; n < this.bins.length; ++n) { - if (filter_func(this.bins[n], n)) { - if (!selbins) selbins = (n === 0) ? [] : this.bins.slice(0, n); - } else - if (selbins) selbins.push(this.bins[n]); + for (let n = 0; n < this.#bins.length; ++n) { + if (filter_func(this.#bins[n], n)) { + if (!selbins) + selbins = (n === 0) ? [] : this.#bins.slice(0, n); + } else if (selbins) + selbins.push(this.#bins[n]); } } - if (!selbins) selbins = this.bins; + if (!selbins) + selbins = this.#bins; - if (!maxpnt) maxpnt = 500000; + if (!maxpnt) + maxpnt = 500000; - if ((selbins.length < maxpnt) || !this.canOptimize()) return selbins; - let step = Math.floor(selbins.length / maxpnt); - if (step < 2) step = 2; - const optbins = []; - for (let n = 0; n < selbins.length; n+=step) + if ((selbins.length < maxpnt) || !this.canOptimize()) + return selbins; + const optbins = [], step = Math.max(2, Math.floor(selbins.length / maxpnt)); + for (let n = 0; n < selbins.length; n += step) optbins.push(selbins[n]); return optbins; @@ -410,19 +552,19 @@ class TGraphPainter extends ObjectPainter { /** @summary Check if such function should be drawn directly */ needDrawFunc(graph, func) { if (func._typename === clTPaveStats) - return (func.fName !== 'stats') || !graph.TestBit(kNoStats); // kNoStats is same for graph and histogram + return (func.fName !== 'stats') || !graph.TestBit(kNoStats); // kNoStats is same for graph and histogram - if ((func._typename === clTF1) || (func._typename === clTF2)) - return !func.TestBit(BIT(9)); // TF1::kNotDraw + if ((func._typename === clTF1) || (func._typename === clTF2)) + return !func.TestBit(BIT(9)); // TF1::kNotDraw - return true; + return true; } /** @summary Returns tooltip for specified bin */ getTooltips(d) { - const pmain = this.get_main(), lines = [], - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - gme = this.get_gme(); + const fp = this.get_fp(), lines = [], o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + gme = this.get_gme(); lines.push(this.getObjectHint()); @@ -432,97 +574,102 @@ class TGraphPainter extends ObjectPainter { lines.push('x = ' + funcs.axisAsText('x', d.x), 'y = ' + funcs.axisAsText('y', d.y)); if (gme) lines.push('error x = -' + funcs.axisAsText('x', gme.fExL[d.indx]) + '/+' + funcs.axisAsText('x', gme.fExH[d.indx])); - else if (this.options.Errors && (funcs.x_handle.kind === kAxisNormal) && (d.exlow || d.exhigh)) + else if (o.Errors && (funcs.x_handle.kind === kAxisNormal) && (d.exlow || d.exhigh)) lines.push('error x = -' + funcs.axisAsText('x', d.exlow) + '/+' + funcs.axisAsText('x', d.exhigh)); if (gme) { for (let ny = 0; ny < gme.fNYErrors; ++ny) lines.push(`error y${ny} = -${funcs.axisAsText('y', gme.fEyL[ny][d.indx])}/+${funcs.axisAsText('y', gme.fEyH[ny][d.indx])}`); - } else if ((this.options.Errors || (this.options.EF > 0)) && (funcs.y_handle.kind === kAxisNormal) && (d.eylow || d.eyhigh)) + } else if ((o.Errors || (o.EF > 0)) && (funcs.y_handle.kind === kAxisNormal) && (d.eylow || d.eyhigh)) lines.push('error y = -' + funcs.axisAsText('y', d.eylow) + '/+' + funcs.axisAsText('y', d.eyhigh)); } return lines; } /** @summary Provide frame painter for graph - * @desc If not exists, emulate its behaviour */ - get_main() { - let pmain = this.getFramePainter(); + * @desc If not exists, emulate its behavior */ + get_fp() { + let fp = this.getFramePainter(); - if (pmain?.grx && pmain?.gry) return pmain; + if (fp?.grx && fp?.gry) + return fp; // FIXME: check if needed, can be removed easily const pp = this.getPadPainter(), rect = pp?.getPadRect() || { width: 800, height: 600 }; - pmain = { - pad_layer: true, - pad: pp?.getRootPad(true) ?? create(clTPad), - pw: rect.width, - ph: rect.height, - fX1NDC: 0.1, fX2NDC: 0.9, fY1NDC: 0.1, fY2NDC: 0.9, - getFrameWidth() { return this.pw; }, - getFrameHeight() { return this.ph; }, - grx(value) { - if (this.pad.fLogx) - value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; - else - value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); - return value * this.pw; - }, - gry(value) { - if (this.pad.fLogv ?? this.pad.fLogy) - value = (value > 0) ? Math.log10(value) : this.pad.fUymin; - else - value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); - return (1 - value) * this.ph; - }, - revertAxis(name, v) { + fp = { + pad_layer: true, + pad: pp?.getRootPad(true) ?? create(clTPad), + pw: rect.width, + ph: rect.height, + fX1NDC: 0.1, fX2NDC: 0.9, fY1NDC: 0.1, fY2NDC: 0.9, + getFrameWidth() { return this.pw; }, + getFrameHeight() { return this.ph; }, + grx(value) { + if (this.pad.fLogx) + value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; + else + value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); + return value * this.pw; + }, + gry(value) { + if (this.pad.fLogv ?? this.pad.fLogy) + value = (value > 0) ? Math.log10(value) : this.pad.fUymin; + else + value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); + return (1 - value) * this.ph; + }, + revertAxis(name, v) { if (name === 'x') return v / this.pw * (this.pad.fX2 - this.pad.fX1) + this.pad.fX1; if (name === 'y') return (1 - v / this.ph) * (this.pad.fY2 - this.pad.fY1) + this.pad.fY1; return v; - }, - getGrFuncs() { return this; } + }, + getGrFuncs() { return this; } }; - return pmain.pad ? pmain : null; + return fp; } /** @summary append exclusion area to created path */ appendExclusion(is_curve, path, drawbins, excl_width) { const extrabins = []; - for (let n = drawbins.length-1; n >= 0; --n) { + for (let n = drawbins.length - 1; n >= 0; --n) { const bin = drawbins[n], - dlen = Math.sqrt(bin.dgrx**2 + bin.dgry**2); + dlen = Math.sqrt(bin.dgrx ** 2 + bin.dgry ** 2); if (dlen > 1e-10) { // shift point - bin.grx += excl_width*bin.dgry/dlen; - bin.gry -= excl_width*bin.dgrx/dlen; + bin.grx += excl_width * bin.dgry / dlen; + bin.gry -= excl_width * bin.dgrx / dlen; } extrabins.push(bin); } const path2 = buildSvgCurve(extrabins, { cmd: 'L', line: !is_curve }); - this.draw_g.append('svg:path') - .attr('d', path + path2 + 'Z') - .call(this.fillatt.func) - .style('opacity', 0.75); + this.appendPath(path + path2 + 'Z') + .call(this.fillatt.func) + .style('opacity', 0.75); } /** @summary draw TGraph bins with specified options * @desc Can be called several times */ drawBins(funcs, options, draw_g, w, h, lineatt, fillatt, main_block) { const graph = this.getGraph(); - if (!graph?.fNpoints) return; + if (!graph?.fNpoints) + return; let excl_width = 0, drawbins = null; + // if markers or errors drawn - no need handle events for line drawing + // this improves interactivity like zooming around graph points + const line_events_handling = !this.isBatchMode() && (options.Line || options.Errors) ? 'none' : null; if (main_block && lineatt.excl_side) { excl_width = lineatt.excl_width; - if ((lineatt.width > 0) && !options.Line && !options.Curve) options.Line = 1; + if ((lineatt.width > 0) && !options.Line && !options.Curve) + options.Line = 1; } if (options.EF) { @@ -536,9 +683,9 @@ class TGraphPainter extends ObjectPainter { } const path1 = buildSvgCurve(drawbins, { line: options.EF < 2, qubic: true }), - bins2 = []; + bins2 = []; - for (let n = drawbins.length-1; n >= 0; --n) { + for (let n = drawbins.length - 1; n >= 0; --n) { const bin = drawbins[n]; bin.gry = funcs.gry(bin.y + bin.eyhigh); bins2.push(bin); @@ -546,22 +693,23 @@ class TGraphPainter extends ObjectPainter { // build upper part (in reverse direction) const path2 = buildSvgCurve(bins2, { line: options.EF < 2, cmd: 'L', qubic: true }), - area = draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(fillatt.func); + area = draw_g.append('svg:path') + .attr('d', path1 + path2 + 'Z') + .call(fillatt.func); // Let behaves as ROOT - see JIRA ROOT-8131 if (fillatt.empty() && fillatt.colorindx) area.style('stroke', this.getColor(fillatt.colorindx)); if (main_block) - this.draw_kind = 'lines'; + this.#draw_kind = 'lines'; } if (options.Line || options.Fill) { let close_symbol = ''; - if (this._cutg) { + if (this.#cutg) { close_symbol = 'Z'; - if (!options.original) options.Fill = 1; + if (!options.original) + options.Fill = 1; } if (options.Fill) { @@ -569,7 +717,8 @@ class TGraphPainter extends ObjectPainter { excl_width = 0; } - if (!drawbins) drawbins = this.optimizeBins(0); + if (!drawbins) + drawbins = this.optimizeBins(0); for (let n = 0; n < drawbins.length; ++n) { const bin = drawbins[n]; @@ -580,9 +729,11 @@ class TGraphPainter extends ObjectPainter { const path = buildSvgCurve(drawbins, { line: true, calc: excl_width }); if (excl_width) - this.appendExclusion(false, path, drawbins, excl_width); + this.appendExclusion(false, path, drawbins, excl_width); - const elem = draw_g.append('svg:path').attr('d', path + close_symbol); + const elem = draw_g.append('svg:path') + .attr('d', path + close_symbol) + .style('pointer-events', line_events_handling); if (options.Line) elem.call(lineatt.func); @@ -592,12 +743,12 @@ class TGraphPainter extends ObjectPainter { elem.style('fill', 'none'); if (main_block) - this.draw_kind = 'lines'; + this.#draw_kind = 'lines'; } if (options.Curve) { let curvebins = drawbins; - if ((this.draw_kind !== 'lines') || !curvebins || ((options.Curve === 1) && (curvebins.length > 20000))) { + if ((this.#draw_kind !== 'lines') || !curvebins || ((options.Curve === 1) && (curvebins.length > 20000))) { curvebins = this.optimizeBins((options.Curve === 1) ? 20000 : 0); for (let n = 0; n < curvebins.length; ++n) { const bin = curvebins[n]; @@ -613,9 +764,10 @@ class TGraphPainter extends ObjectPainter { draw_g.append('svg:path') .attr('d', path) .call(lineatt.func) - .style('fill', 'none'); + .style('fill', 'none') + .style('pointer-events', line_events_handling); if (main_block) - this.draw_kind = 'lines'; // handled same way as lines + this.#draw_kind = 'lines'; // handled same way as lines } let nodes = null; @@ -625,22 +777,24 @@ class TGraphPainter extends ObjectPainter { const grx = funcs.grx(pnt.x); // when drawing bars, take all points - if (!options.Bar && ((grx < 0) || (grx > w))) return true; + if (!options.Bar && ((grx < 0) || (grx > w))) + return true; const gry = funcs.gry(pnt.y); - if (!options.Bar && !options.OutRange && ((gry < 0) || (gry > h))) return true; + if (!options.Bar && !options.OutRange && ((gry < 0) || (gry > h))) + return true; pnt.grx1 = Math.round(grx); pnt.gry1 = Math.round(gry); - if (this.has_errors) { - pnt.grx0 = Math.round(funcs.grx(pnt.x - options.ScaleErrX*pnt.exlow) - grx); - pnt.grx2 = Math.round(funcs.grx(pnt.x + options.ScaleErrX*pnt.exhigh) - grx); + if (this.#has_errors) { + pnt.grx0 = Math.round(funcs.grx(pnt.x - options.ScaleErrX * pnt.exlow) - grx); + pnt.grx2 = Math.round(funcs.grx(pnt.x + options.ScaleErrX * pnt.exhigh) - grx); pnt.gry0 = Math.round(funcs.gry(pnt.y - pnt.eylow) - gry); pnt.gry2 = Math.round(funcs.gry(pnt.y + pnt.eyhigh) - gry); - if (this.is_bent) { + if (this.#is_bent) { pnt.grdx0 = Math.round(funcs.gry(pnt.y + graph.fEXlowd[i]) - gry); pnt.grdx2 = Math.round(funcs.gry(pnt.y + graph.fEXhighd[i]) - gry); pnt.grdy0 = Math.round(funcs.grx(pnt.x + graph.fEYlowd[i]) - grx); @@ -653,7 +807,7 @@ class TGraphPainter extends ObjectPainter { }); if (main_block) - this.draw_kind = 'nodes'; + this.#draw_kind = 'nodes'; nodes = draw_g.selectAll('.grpoint') .data(drawbins) @@ -676,14 +830,10 @@ class TGraphPainter extends ObjectPainter { } } - if (drawbins.length === 1) - drawbins[0].width = w/4; // pathologic case of single bin - else { - for (let i = 0; i < drawbins.length; ++i) - drawbins[i].width = (xmax - xmin) / drawbins.length * gStyle.fBarWidth; - } - - const yy0 = Math.round(funcs.gry(0)); + const sz0 = drawbins.length < 2 ? w / 4 : (xmax - xmin) / drawbins.length, + bw = sz0 * gStyle.fBarWidth, + boff = sz0 * gStyle.fBarOffset, + yy0 = Math.round(funcs.gry(0)); let usefill = fillatt; if (main_block) { @@ -697,22 +847,25 @@ class TGraphPainter extends ObjectPainter { nodes.append('svg:path') .attr('d', d => { d.bar = true; // element drawn as bar - const dx = d.width > 1 ? Math.round(-d.width/2) : 0, - dw = d.width > 1 ? Math.round(d.width) : 1, - dy = (options.Bar !== 1) ? 0 : ((d.gry1 > yy0) ? yy0-d.gry1 : 0), + const dx = bw > 1 ? Math.round(boff - bw / 2) : 0, + dw = bw > 1 ? Math.round(bw) : 1, + dy = (options.Bar !== 1) ? 0 : ((d.gry1 > yy0) ? yy0 - d.gry1 : 0), dh = (options.Bar !== 1) ? (h > d.gry1 ? h - d.gry1 : 0) : Math.abs(yy0 - d.gry1); return `M${dx},${dy}h${dw}v${dh}h${-dw}z`; }) .call(usefill.func); + + this.#barwidth = bw; + this.#baroffset = boff; } if (options.Rect) { nodes.filter(d => (d.exlow > 0) && (d.exhigh > 0) && (d.eylow > 0) && (d.eyhigh > 0)) .append('svg:path') .attr('d', d => { - d.rect = true; - return `M${d.grx0},${d.gry0}H${d.grx2}V${d.gry2}H${d.grx0}Z`; - }) + d.rect = true; + return `M${d.grx0},${d.gry0}H${d.grx2}V${d.gry2}H${d.grx0}Z`; + }) .call(fillatt.func) .call(options.Rect === 2 ? lineatt.func : () => {}); } @@ -721,81 +874,83 @@ class TGraphPainter extends ObjectPainter { if (options.Errors) { // to show end of error markers, use line width attribute - let lw = lineatt.width + gStyle.fEndErrorSize, bb = 0; - const vv = options.Ends ? `m0,${lw}v${-2*lw}` : '', - hh = options.Ends ? `m${lw},0h${-2*lw}` : ''; - let vleft = vv, vright = vv, htop = hh, hbottom = hh; + let lw = lineatt.width + gStyle.fEndErrorSize; + const vv = options.Ends ? `m0,${lw}v${-2 * lw}` : '', + hh = options.Ends ? `m${lw},0h${-2 * lw}` : ''; + let vleft = vv, vright = vv, htop = hh, hbottom = hh, bb; const mainLine = (dx, dy) => { - if (!options.MainError) return `M${dx},${dy}`; + if (!options.MainError) + return `M${dx},${dy}`; const res = 'M0,0'; - if (dx) return res + (dy ? `L${dx},${dy}` : `H${dx}`); + if (dx) + return res + (dy ? `L${dx},${dy}` : `H${dx}`); return dy ? res + `V${dy}` : res; }; switch (options.Ends) { case 2: // option [] - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `m${bb},${lw}h${-bb}v${-2*lw}h${bb}`; - vright = `m${-bb},${lw}h${bb}v${-2*lw}h${-bb}`; - htop = `m${-lw},${bb}v${-bb}h${2*lw}v${bb}`; - hbottom = `m${-lw},${-bb}v${bb}h${2*lw}v${-bb}`; + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `m${bb},${lw}h${-bb}v${-2 * lw}h${bb}`; + vright = `m${-bb},${lw}h${bb}v${-2 * lw}h${-bb}`; + htop = `m${-lw},${bb}v${-bb}h${2 * lw}v${bb}`; + hbottom = `m${-lw},${-bb}v${bb}h${2 * lw}v${-bb}`; break; case 3: // option |> - lw = Math.max(lw, Math.round(graph.fMarkerSize*8*0.66)); - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `l${bb},${lw}v${-2*lw}l${-bb},${lw}`; - vright = `l${-bb},${lw}v${-2*lw}l${bb},${lw}`; - htop = `l${-lw},${bb}h${2*lw}l${-lw},${-bb}`; - hbottom = `l${-lw},${-bb}h${2*lw}l${-lw},${bb}`; + lw = Math.max(lw, Math.round(graph.fMarkerSize * 8 * 0.66)); + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `l${bb},${lw}v${-2 * lw}l${-bb},${lw}`; + vright = `l${-bb},${lw}v${-2 * lw}l${bb},${lw}`; + htop = `l${-lw},${bb}h${2 * lw}l${-lw},${-bb}`; + hbottom = `l${-lw},${-bb}h${2 * lw}l${-lw},${bb}`; break; case 4: // option > - lw = Math.max(lw, Math.round(graph.fMarkerSize*8*0.66)); - bb = Math.max(lineatt.width+1, Math.round(lw*0.66)); - vleft = `l${bb},${lw}m0,${-2*lw}l${-bb},${lw}`; - vright = `l${-bb},${lw}m0,${-2*lw}l${bb},${lw}`; - htop = `l${-lw},${bb}m${2*lw},0l${-lw},${-bb}`; - hbottom = `l${-lw},${-bb}m${2*lw},0l${-lw},${bb}`; + lw = Math.max(lw, Math.round(graph.fMarkerSize * 8 * 0.66)); + bb = Math.max(lineatt.width + 1, Math.round(lw * 0.66)); + vleft = `l${bb},${lw}m0,${-2 * lw}l${-bb},${lw}`; + vright = `l${-bb},${lw}m0,${-2 * lw}l${bb},${lw}`; + htop = `l${-lw},${bb}m${2 * lw},0l${-lw},${-bb}`; + hbottom = `l${-lw},${-bb}m${2 * lw},0l${-lw},${bb}`; break; } this.error_size = lw; - lw = Math.floor((lineatt.width-1)/2); // one should take into account half of end-cup line width + lw = Math.floor((lineatt.width - 1) / 2); // one should take into account half of end-cup line width let visible = nodes.filter(d => (d.exlow > 0) || (d.exhigh > 0) || (d.eylow > 0) || (d.eyhigh > 0)); if (options.skip_errors_x0 || options.skip_errors_y0) - visible = visible.filter(d => ((d.x !== 0) || !options.skip_errors_x0) && ((d.y !== 0) || !options.skip_errors_y0)); + visible = visible.filter(d => (d.x || !options.skip_errors_x0) && (d.y || !options.skip_errors_y0)); if (!this.isBatchMode() && settings.Tooltip && main_block) { visible.append('svg:path') + .attr('d', d => `M${d.grx0},${d.gry0}h${d.grx2 - d.grx0}v${d.gry2 - d.gry0}h${d.grx0 - d.grx2}z`) .style('fill', 'none') - .style('pointer-events', 'visibleFill') - .attr('d', d => `M${d.grx0},${d.gry0}h${d.grx2-d.grx0}v${d.gry2-d.gry0}h${d.grx0-d.grx2}z`); + .style('pointer-events', 'visibleFill'); } visible.append('svg:path') - .call(lineatt.func) - .style('fill', 'none') - .attr('d', d => { - d.error = true; - return ((d.exlow > 0) ? mainLine(d.grx0+lw, d.grdx0) + vleft : '') + - ((d.exhigh > 0) ? mainLine(d.grx2-lw, d.grdx2) + vright : '') + - ((d.eylow > 0) ? mainLine(d.grdy0, d.gry0-lw) + hbottom : '') + - ((d.eyhigh > 0) ? mainLine(d.grdy2, d.gry2+lw) + htop : ''); - }); + .attr('d', d => { + d.error = true; + return ((d.exlow > 0) ? mainLine(d.grx0 + lw, d.grdx0) + vleft : '') + + ((d.exhigh > 0) ? mainLine(d.grx2 - lw, d.grdx2) + vright : '') + + ((d.eylow > 0) ? mainLine(d.grdy0, d.gry0 - lw) + hbottom : '') + + ((d.eyhigh > 0) ? mainLine(d.grdy2, d.gry2 + lw) + htop : ''); + }) + .style('fill', 'none') + .call(lineatt.func); } if (options.Mark) { // for tooltips use markers only if nodes were not created this.createAttMarker({ attr: graph, style: options.Mark - 100 }); - this.marker_size = this.markeratt.getFullSize(); + this.#marker_size = this.markeratt.getFullSize(); this.markeratt.resetPos(); - const want_tooltip = !this.isBatchMode() && settings.Tooltip && (!this.markeratt.fill || (this.marker_size < 7)) && !nodes && main_block, - hsz = Math.max(5, Math.round(this.marker_size*0.7)), + const want_tooltip = !this.isBatchMode() && settings.Tooltip && (!this.markeratt.fill || (this.#marker_size < 7)) && !nodes && main_block, + hsz = Math.max(5, Math.round(this.#marker_size * 0.7)), maxnummarker = 1000000 / (this.markeratt.getMarkerLength() + 7); // let produce SVG at maximum 1MB let path = '', pnt, grx, gry, @@ -803,17 +958,18 @@ class TGraphPainter extends ObjectPainter { if (!drawbins) drawbins = this.optimizeBins(maxnummarker); - else if (this.canOptimize() && (drawbins.length > 1.5*maxnummarker)) - step = Math.min(2, Math.round(drawbins.length/maxnummarker)); + else if (this.canOptimize() && (drawbins.length > 1.5 * maxnummarker)) + step = Math.min(2, Math.round(drawbins.length / maxnummarker)); for (let n = 0; n < drawbins.length; n += step) { pnt = drawbins[n]; grx = funcs.grx(pnt.x); - if ((grx > -this.marker_size) && (grx < w + this.marker_size)) { + if ((grx > -this.#marker_size) && (grx < w + this.#marker_size)) { gry = funcs.gry(pnt.y); - if ((gry > -this.marker_size) && (gry < h + this.marker_size)) { + if ((gry > -this.#marker_size) && (gry < h + this.#marker_size)) { path += this.markeratt.create(grx, gry); - if (want_tooltip) hints_marker += `M${grx-hsz},${gry-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; + if (want_tooltip) + hints_marker += `M${grx - hsz},${gry - hsz}h${2 * hsz}v${2 * hsz}h${-2 * hsz}z`; } } } @@ -822,8 +978,8 @@ class TGraphPainter extends ObjectPainter { draw_g.append('svg:path') .attr('d', path) .call(this.markeratt.func); - if ((nodes === null) && (this.draw_kind === 'none') && main_block) - this.draw_kind = (options.Mark === 101) ? 'path' : 'mark'; + if ((nodes === null) && (this.#draw_kind === 'none') && main_block) + this.#draw_kind = (options.Mark === 101) ? 'path' : 'mark'; } if (want_tooltip && hints_marker) { draw_g.append('svg:path') @@ -841,36 +997,32 @@ class TGraphPainter extends ObjectPainter { yqmin = Math.max(funcs.scale_ymin, graph.fYq1), yqmax = Math.min(funcs.scale_ymax, graph.fYq2), makeLine = (x1, y1, x2, y2) => `M${funcs.grx(x1)},${funcs.gry(y1)}L${funcs.grx(x2)},${funcs.gry(y2)}`, - yxmin = (graph.fYq2 - graph.fYq1)*(funcs.scale_xmin-graph.fXq1)/(graph.fXq2-graph.fXq1) + graph.fYq1, - yxmax = (graph.fYq2-graph.fYq1)*(funcs.scale_xmax-graph.fXq1)/(graph.fXq2-graph.fXq1) + graph.fYq1; + yxmin = (graph.fYq2 - graph.fYq1) * (funcs.scale_xmin - graph.fXq1) / (graph.fXq2 - graph.fXq1) + graph.fYq1, + yxmax = (graph.fYq2 - graph.fYq1) * (funcs.scale_xmax - graph.fXq1) / (graph.fXq2 - graph.fXq1) + graph.fYq1; - let path2 = ''; + let path2; if (yxmin < funcs.scale_ymin) { - const xymin = (graph.fXq2 - graph.fXq1)*(funcs.scale_ymin-graph.fYq1)/(graph.fYq2-graph.fYq1) + graph.fXq1; + const xymin = (graph.fXq2 - graph.fXq1) * (funcs.scale_ymin - graph.fYq1) / (graph.fYq2 - graph.fYq1) + graph.fXq1; path2 = makeLine(xymin, funcs.scale_ymin, xqmin, yqmin); } else path2 = makeLine(funcs.scale_xmin, yxmin, xqmin, yqmin); - if (yxmax > funcs.scale_ymax) { - const xymax = (graph.fXq2-graph.fXq1)*(funcs.scale_ymax-graph.fYq1)/(graph.fYq2-graph.fYq1) + graph.fXq1; + const xymax = (graph.fXq2 - graph.fXq1) * (funcs.scale_ymax - graph.fYq1) / (graph.fYq2 - graph.fYq1) + graph.fXq1; path2 += makeLine(xqmax, yqmax, xymax, funcs.scale_ymax); } else path2 += makeLine(xqmax, yqmax, funcs.scale_xmax, yxmax); - const latt1 = this.createAttLine({ style: 1, width: 1, color: kBlack, std: false }), latt2 = this.createAttLine({ style: 2, width: 1, color: kBlack, std: false }); - this.draw_g.append('path') - .attr('d', makeLine(xqmin, yqmin, xqmax, yqmax)) - .call(latt1.func) - .style('fill', 'none'); + this.appendPath(makeLine(xqmin, yqmin, xqmax, yqmax)) + .call(latt1.func) + .style('fill', 'none'); - this.draw_g.append('path') - .attr('d', path2) - .call(latt2.func) - .style('fill', 'none'); + this.appendPath(path2) + .call(latt2.func) + .style('fill', 'none'); } drawBins3D(/* fp, graph */) { @@ -879,146 +1031,185 @@ class TGraphPainter extends ObjectPainter { /** @summary Create necessary histogram draw attributes */ createGraphDrawAttributes(only_check_auto) { - const graph = this.getGraph(), o = this.options; + const graph = this.getGraph(), o = this.getOptions(); if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { const pp = this.getPadPainter(); if (isFunc(pp?.getAutoColor)) { const icolor = pp.getAutoColor(graph.$num_graphs); - this._auto_exec = ''; // can be reused when sending option back to server - if (o._pfc > 1) { o._pfc = 1; graph.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (o._plc > 1) { o._plc = 1; graph.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (o._pmc > 1) { o._pmc = 1; graph.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } + this.#auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { + o._pfc = 1; + graph.fFillColor = icolor; + this.#auto_exec += `SetFillColor(${icolor});;`; + this.deleteAttr('fill'); + } + if (o._plc > 1) { + o._plc = 1; + graph.fLineColor = icolor; + this.#auto_exec += `SetLineColor(${icolor});;`; + this.deleteAttr('line'); + } + if (o._pmc > 1) { + o._pmc = 1; + graph.fMarkerColor = icolor; + this.#auto_exec += `SetMarkerColor(${icolor});;`; + this.deleteAttr('marker'); + } } } if (only_check_auto) this.deleteAttr(); else { - this.createAttLine({ attr: graph, can_excl: true }); - this.createAttFill({ attr: graph }); + this.createAttLine({ attr: graph, can_excl: true, color0: o.graphLineColor, width: o.graphLineWidth }); + this.createAttFill({ attr: graph, color: o.graphFillColor, pattern: o.graphFillPattern, }); } } /** @summary draw TGraph */ drawGraph() { - const pmain = this.get_main(), - graph = this.getGraph(); - if (!pmain) return; + const fp = this.get_fp(), + graph = this.getGraph(), + o = this.getOptions(); + if (!fp) + return; // special mode for TMultiGraph 3d drawing - if (this.options.pos3d) - return this.drawBins3D(pmain, graph); - - const is_gme = !!this.get_gme(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - w = pmain.getFrameWidth(), - h = pmain.getFrameHeight(); + if (o.pos3d) + return this.drawBins3D(fp, graph); - this.createG(!pmain.pad_layer); + const is_gme = Boolean(this.get_gme()), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + w = funcs.getFrameWidth(), + h = funcs.getFrameHeight(), + g = this.createG(fp.pad_layer ? false : this.#frame_layer); this.createGraphDrawAttributes(); this.fillatt.used = false; // mark used only when really used - this.draw_kind = 'none'; // indicate if special svg:g were created for each bin - this.marker_size = 0; // indicate if markers are drawn - const draw_g = is_gme ? this.draw_g.append('svg:g') : this.draw_g; + this.#draw_kind = 'none'; // indicate if special svg:g were created for each bin + this.#marker_size = 0; // indicate if markers are drawn + const draw_g = is_gme ? g.append('svg:g') : g; - this.drawBins(funcs, this.options, draw_g, w, h, this.lineatt, this.fillatt, true); + this.drawBins(funcs, o, draw_g, w, h, this.lineatt, this.fillatt, true); if (graph._typename === 'TGraphQQ') this.appendQQ(funcs, graph); if (is_gme) { + const extract_gme_errors = nblock => { + this.#bins?.forEach(bin => { + bin.eylow = graph.fEyL[nblock][bin.indx]; + bin.eyhigh = graph.fEyH[nblock][bin.indx]; + }); + }; + for (let k = 0; k < graph.fNYErrors; ++k) { - let lineatt = this.lineatt, fillatt = this.fillatt; - if (this.options.individual_styles) { - lineatt = this.createAttLine({ attr: graph.fAttLine[k], std: false }); - fillatt = this.createAttFill({ attr: graph.fAttFill[k], std: false }); - } - const sub_g = this.draw_g.append('svg:g'), - options = (k < this.options.blocks.length) ? this.options.blocks[k] : this.options; - this.extractGmeErrors(k); + const lineatt = !o.individual_styles ? this.lineatt : this.createAttLine({ attr: graph.fAttLine[k], std: false }), + fillatt = !o.individual_styles ? this.fillatt : this.createAttFill({ attr: graph.fAttFill[k], std: false }), + sub_g = g.append('svg:g'), + options = k < o.blocks.length ? o.blocks[k] : o; + extract_gme_errors(k); this.drawBins(funcs, options, sub_g, w, h, lineatt, fillatt); } - this.extractGmeErrors(0); // ensure that first block kept at the end + extract_gme_errors(0); // ensure that first block kept at the end } if (!this.isBatchMode()) { addMoveHandler(this, this.testEditable()); - assignContextMenu(this); + assignContextMenu(this, kNoReorder); } } /** @summary Provide tooltip at specified point */ extractTooltip(pnt) { - if (!pnt) return null; + if (!pnt) + return null; - if ((this.draw_kind === 'lines') || (this.draw_kind === 'path') || (this.draw_kind === 'mark')) + if ((this.#draw_kind === 'lines') || (this.#draw_kind === 'path') || (this.#draw_kind === 'mark')) return this.extractTooltipForPath(pnt); - if (this.draw_kind !== 'nodes') return null; + if (this.#draw_kind !== 'nodes') + return null; - const pmain = this.get_main(), - height = pmain.getFrameHeight(), + const fp = this.get_fp(), + o = this.getOptions(), + height = fp.getFrameHeight(), + bw = this.#barwidth, + boff = this.#baroffset, esz = this.error_size, - isbar1 = (this.options.Bar === 1), - funcs = isbar1 ? pmain.getGrFuncs(this.options.second_x, this.options.second_y) : null, - msize = this.marker_size ? Math.round(this.marker_size/2 + 1.5) : 0; + isbar1 = (o.Bar === 1), + funcs = isbar1 ? fp.getGrFuncs(o.second_x, o.second_y) : null, + msize = this.#marker_size ? Math.round(this.#marker_size / 2 + 1.5) : 0; let findbin = null, best_dist2 = 1e10, best = null; - this.draw_g.selectAll('.grpoint').each(function() { + this.getG().selectAll('.grpoint').each(function() { const d = d3_select(this).datum(); - if (d === undefined) return; + if (d === undefined) + return; let dist2 = (pnt.x - d.grx1) ** 2; - if (pnt.nproc === 1) dist2 += (pnt.y - d.gry1) ** 2; - if (dist2 >= best_dist2) return; + if (pnt.nproc === 1) + dist2 += (pnt.y - d.gry1) ** 2; + if (dist2 >= best_dist2) + return; let rect; if (d.error || d.rect || d.marker) { - rect = { x1: Math.min(-esz, d.grx0, -msize), - x2: Math.max(esz, d.grx2, msize), - y1: Math.min(-esz, d.gry2, -msize), - y2: Math.max(esz, d.gry0, msize) }; + rect = { + x1: Math.min(-esz, d.grx0, -msize), + x2: Math.max(esz, d.grx2, msize), + y1: Math.min(-esz, d.gry2, -msize), + y2: Math.max(esz, d.gry0, msize) + }; } else if (d.bar) { - rect = { x1: -d.width/2, x2: d.width/2, y1: 0, y2: height - d.gry1 }; - - if (isbar1) { - const yy0 = funcs.gry(0); - rect.y1 = (d.gry1 > yy0) ? yy0-d.gry1 : 0; - rect.y2 = (d.gry1 > yy0) ? 0 : yy0-d.gry1; - } - } else - rect = { x1: -5, x2: 5, y1: -5, y2: 5 }; + rect = { + x1: boff - bw / 2, + x2: boff + bw / 2, + y1: 0, + y2: height - d.gry1 + }; + + if (isbar1) { + const yy0 = funcs.gry(0); + rect.y1 = (d.gry1 > yy0) ? yy0 - d.gry1 : 0; + rect.y2 = (d.gry1 > yy0) ? 0 : yy0 - d.gry1; + } + } else + rect = { x1: -5, x2: 5, y1: -5, y2: 5 }; - const matchx = (pnt.x >= d.grx1 + rect.x1) && (pnt.x <= d.grx1 + rect.x2), - matchy = (pnt.y >= d.gry1 + rect.y1) && (pnt.y <= d.gry1 + rect.y2); + const matchx = (pnt.x >= d.grx1 + rect.x1) && (pnt.x <= d.grx1 + rect.x2), + matchy = (pnt.y >= d.gry1 + rect.y1) && (pnt.y <= d.gry1 + rect.y2); - if (matchx && (matchy || (pnt.nproc > 1))) { - best_dist2 = dist2; - findbin = this; - best = rect; - best.exact = /* matchx && */ matchy; - } - }); + if (matchx && (matchy || (pnt.nproc > 1))) { + best_dist2 = dist2; + findbin = this; + best = rect; + best.exact = /* matchx && */ matchy; + } + }); - if (findbin === null) return null; + if (findbin === null) + return null; const d = d3_select(findbin).datum(), gr = this.getGraph(), - res = { name: gr.fName, title: gr.fTitle, - x: d.grx1, y: d.gry1, - color1: this.lineatt.color, - lines: this.getTooltips(d), - rect: best, d3bin: findbin }; + res = { + name: gr.fName, title: gr.fTitle, + x: d.grx1, y: d.gry1, + color1: this.lineatt.color, + lines: this.getTooltips(d), + rect: best, d3bin: findbin + }; - res.user_info = { obj: gr, name: gr.fName, bin: d.indx, cont: d.y, grx: d.grx1, gry: d.gry1 }; + res.user_info = { obj: gr, name: gr.fName, bin: d.indx, cont: d.y, grx: d.grx1, gry: d.gry1 }; if (this.fillatt?.used && !this.fillatt?.empty()) res.color2 = this.fillatt.getFillColor(); - if (best.exact) res.exact = true; + if (best.exact) + res.exact = true; res.menu = res.exact; // activate menu only when exactly locate bin res.menu_dist = 3; // distance always fixed res.bin = d; @@ -1029,9 +1220,9 @@ class TGraphPainter extends ObjectPainter { /** @summary Show tooltip */ showTooltip(hint) { - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - if (!hint || !this.draw_g) { + if (!hint || !this.getG()) { ttrect?.remove(); return; } @@ -1042,7 +1233,7 @@ class TGraphPainter extends ObjectPainter { const d = d3_select(hint.d3bin).datum(); if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') + ttrect = this.getG().append('svg:rect') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .call(addHighlightStyle); @@ -1063,28 +1254,31 @@ class TGraphPainter extends ObjectPainter { /** @summary Process tooltip event */ processTooltipEvent(pnt) { const hint = this.extractTooltip(pnt); - if (!pnt || !pnt.disabled) this.showTooltip(hint); + if (!pnt || !pnt.disabled) + this.showTooltip(hint); return hint; } /** @summary Find best bin index for specified point */ findBestBin(pnt) { - if (!this.bins) return null; + if (!this.#bins) + return null; - const islines = (this.draw_kind === 'lines'), - funcs = this.get_main().getGrFuncs(this.options.second_x, this.options.second_y); + const islines = (this.#draw_kind === 'lines'), + o = this.getOptions(), + funcs = this.get_fp().getGrFuncs(o.second_x, o.second_y); let bestindx = -1, bestbin = null, bestdist = 1e10, dist, grx, gry, n, bin; - for (n = 0; n < this.bins.length; ++n) { - bin = this.bins[n]; + for (n = 0; n < this.#bins.length; ++n) { + bin = this.#bins[n]; grx = funcs.grx(bin.x); gry = funcs.gry(bin.y); - dist = (pnt.x-grx)**2 + (pnt.y-gry)**2; + dist = (pnt.x - grx) ** 2 + (pnt.y - gry) ** 2; if (dist < bestdist) { bestdist = dist; @@ -1094,32 +1288,33 @@ class TGraphPainter extends ObjectPainter { } // check last point - if ((bestdist > 100) && islines) bestbin = null; + if ((bestdist > 100) && islines) + bestbin = null; - let radius = Math.max(this.lineatt.width + 3, 4); - - if (this.marker_size > 0) radius = Math.max(this.marker_size, radius); + const radius = Math.max(this.lineatt.width + 3, 4, this.#marker_size); if (bestbin) - bestdist = Math.sqrt((pnt.x-funcs.grx(bestbin.x))**2 + (pnt.y-funcs.gry(bestbin.y))**2); + bestdist = Math.sqrt((pnt.x - funcs.grx(bestbin.x)) ** 2 + (pnt.y - funcs.gry(bestbin.y)) ** 2); - if (!islines && (bestdist > radius)) bestbin = null; + if (!islines && (bestdist > radius)) + bestbin = null; - if (!bestbin) bestindx = -1; + if (!bestbin) + bestindx = -1; const res = { bin: bestbin, indx: bestindx, dist: bestdist, radius: Math.round(radius) }; if (!bestbin && islines) { bestdist = 1e10; - const IsInside = (x, x1, x2) => ((x1 >= x) && (x >= x2)) || ((x1 <= x) && (x <= x2)); + const is_inside = (x, x1, x2) => ((x1 >= x) && (x >= x2)) || ((x1 <= x) && (x <= x2)); - let bin0 = this.bins[0], grx0 = funcs.grx(bin0.x), gry0, posy = 0; - for (n = 1; n < this.bins.length; ++n) { - bin = this.bins[n]; + let bin0 = this.#bins[0], grx0 = funcs.grx(bin0.x), gry0, posy; + for (n = 1; n < this.#bins.length; ++n) { + bin = this.#bins[n]; grx = funcs.grx(bin.x); - if (IsInside(pnt.x, grx0, grx)) { + if (is_inside(pnt.x, grx0, grx)) { // if inside interval, check Y distance gry0 = funcs.gry(bin0.y); gry = funcs.gry(bin.y); @@ -1127,7 +1322,7 @@ class TGraphPainter extends ObjectPainter { if (Math.abs(grx - grx0) < 1) { // very close x - check only y posy = pnt.y; - dist = IsInside(pnt.y, gry0, gry) ? 0 : Math.min(Math.abs(pnt.y-gry0), Math.abs(pnt.y-gry)); + dist = is_inside(pnt.y, gry0, gry) ? 0 : Math.min(Math.abs(pnt.y - gry0), Math.abs(pnt.y - gry)); } else { posy = gry0 + (pnt.x - grx0) / (grx - grx0) * (gry - gry0); dist = Math.abs(posy - pnt.y); @@ -1144,7 +1339,7 @@ class TGraphPainter extends ObjectPainter { grx0 = grx; } - if (bestdist < radius*0.5) { + if (bestdist < radius * 0.5) { res.linedist = bestdist; res.closeline = true; } @@ -1157,42 +1352,45 @@ class TGraphPainter extends ObjectPainter { * @desc if arg specified changes or toggles editable flag */ testEditable(arg) { const obj = this.getGraph(); - if (!obj) return false; - if ((arg === 'toggle') || ((arg !== undefined) && (!arg !== obj.TestBit(kNotEditable)))) - obj.InvertBit(kNotEditable); + if (!isFunc(obj?.TestBit)) + return false; + if ((arg === 'toggle') || (arg !== undefined)) + obj.SetBit(kNotEditable, !arg); return !obj.TestBit(kNotEditable); } /** @summary Provide tooltip at specified point for path-based drawing */ extractTooltipForPath(pnt) { - if (this.bins === null) return null; + if (!this.#bins) + return null; const best = this.findBestBin(pnt); - if (!best || (!best.bin && !best.closeline)) return null; + if (!best || (!best.bin && !best.closeline)) + return null; - const islines = (this.draw_kind === 'lines'), - ismark = (this.draw_kind === 'mark'), - pmain = this.get_main(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - gr = this.getGraph(), - res = { name: gr.fName, title: gr.fTitle, - x: best.bin ? funcs.grx(best.bin.x) : best.linex, - y: best.bin ? funcs.gry(best.bin.y) : best.liney, - color1: this.lineatt.color, - lines: this.getTooltips(best.bin), - usepath: true }; + const islines = (this.#draw_kind === 'lines'), + ismark = (this.#draw_kind === 'mark'), + fp = this.get_fp(), + o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), + gr = this.getGraph(), + res = { + name: gr.fName, title: gr.fTitle, + x: best.bin ? funcs.grx(best.bin.x) : best.linex, + y: best.bin ? funcs.gry(best.bin.y) : best.liney, + color1: this.lineatt.color, + lines: this.getTooltips(best.bin), + usepath: true, ismark, islines + }; res.user_info = { obj: gr, name: gr.fName, bin: 0, cont: 0, grx: res.x, gry: res.y }; - res.ismark = ismark; - res.islines = islines; - if (best.closeline) { res.menu = res.exact = true; res.menu_dist = best.linedist; } else if (best.bin) { - if (this.options.EF && islines) { + if (o.EF && islines) { res.gry1 = funcs.gry(best.bin.y - best.bin.eylow); res.gry2 = funcs.gry(best.bin.y + best.bin.eyhigh); } else @@ -1209,7 +1407,7 @@ class TGraphPainter extends ObjectPainter { ((Math.abs(pnt.y - res.gry1) <= best.radius) || (Math.abs(pnt.y - res.gry2) <= best.radius)); res.menu = res.exact; - res.menu_dist = Math.sqrt((pnt.x-res.x)**2 + Math.min(Math.abs(pnt.y-res.gry1), Math.abs(pnt.y-res.gry2))**2); + res.menu_dist = Math.sqrt((pnt.x - res.x) ** 2 + Math.min(Math.abs(pnt.y - res.gry1), Math.abs(pnt.y - res.gry2)) ** 2); } if (this.fillatt?.used && !this.fillatt?.empty()) @@ -1217,7 +1415,8 @@ class TGraphPainter extends ObjectPainter { if (!islines) { res.color1 = this.getColor(gr.fMarkerColor); - if (!res.color2) res.color2 = res.color1; + if (!res.color2) + res.color2 = res.color1; } return res; @@ -1225,15 +1424,15 @@ class TGraphPainter extends ObjectPainter { /** @summary Show tooltip for path drawing */ showTooltipForPath(hint) { - let ttbin = this.draw_g?.selectChild('.tooltip_bin'); + let ttbin = this.getG()?.selectChild('.tooltip_bin'); - if (!hint?.bin || !this.draw_g) { + if (!hint?.bin || !this.getG()) { ttbin?.remove(); return; } if (ttbin.empty()) - ttbin = this.draw_g.append('svg:g').attr('class', 'tooltip_bin'); + ttbin = this.getG().append('svg:g').attr('class', 'tooltip_bin'); hint.changed = ttbin.property('current_bin') !== hint.bin; @@ -1248,25 +1447,26 @@ class TGraphPainter extends ObjectPainter { .style('opacity', '0.3') .attr('x', Math.round(hint.x - hint.radius)) .attr('y', Math.round(hint.y - hint.radius)) - .attr('width', 2*hint.radius) - .attr('height', 2*hint.radius); + .attr('width', 2 * hint.radius) + .attr('height', 2 * hint.radius); } else { ttbin.append('svg:circle').attr('cy', Math.round(hint.gry1)); - if (Math.abs(hint.gry1-hint.gry2) > 1) + if (Math.abs(hint.gry1 - hint.gry2) > 1) ttbin.append('svg:circle').attr('cy', Math.round(hint.gry2)); - const elem = ttbin.selectAll('circle') + const o = this.getOptions(), + elem = ttbin.selectAll('circle') .attr('r', hint.radius) .attr('cx', Math.round(hint.x)); if (!hint.islines) elem.style('stroke', hint.color1 === 'black' ? 'green' : 'black').style('fill', 'none'); - else { - if (this.options.Line || this.options.Curve) + else { + if (o.Line || o.Curve) elem.call(this.lineatt.func); else elem.style('stroke', 'black'); - if (this.options.Fill) + if (o.Fill) elem.call(this.fillatt.func); else elem.style('fill', 'none'); @@ -1281,57 +1481,58 @@ class TGraphPainter extends ObjectPainter { } /** @summary Start moving of TGraph */ - moveStart(x, y) { - this.pos_dx = this.pos_dy = 0; - this.move_funcs = this.get_main().getGrFuncs(this.options.second_x, this.options.second_y); - const hint = this.extractTooltip({ x, y }); - if (hint && hint.exact && (hint.binindx !== undefined)) { - this.move_binindx = hint.binindx; - this.move_bin = hint.bin; - this.move_x0 = this.move_funcs.grx(this.move_bin.x); - this.move_y0 = this.move_funcs.gry(this.move_bin.y); + moveStart(x, y, evnt) { + const o = this.getOptions(); + this.#pos_dx = this.#pos_dy = 0; + this.#move_funcs = this.get_fp().getGrFuncs(o.second_x, o.second_y); + const hint = evnt?.shiftKey ? null : this.extractTooltip({ x, y }); + if (hint?.exact && (hint.binindx !== undefined)) { + this.#move_binindx = hint.binindx; + this.#move_bin = hint.bin; + this.#move_x0 = this.#move_funcs.grx(this.#move_bin.x); + this.#move_y0 = this.#move_funcs.gry(this.#move_bin.y); } else - delete this.move_binindx; + this.#move_binindx = undefined; } /** @summary Perform moving */ moveDrag(dx, dy) { - this.pos_dx += dx; - this.pos_dy += dy; - - if (this.move_binindx === undefined) - makeTranslate(this.draw_g, this.pos_dx, this.pos_dy); - else if (this.move_funcs && this.move_bin) { - this.move_bin.x = this.move_funcs.revertAxis('x', this.move_x0 + this.pos_dx); - this.move_bin.y = this.move_funcs.revertAxis('y', this.move_y0 + this.pos_dy); + this.#pos_dx += dx; + this.#pos_dy += dy; + + if (this.#move_binindx === undefined) + makeTranslate(this.getG(), this.#pos_dx, this.#pos_dy); + else if (this.#move_funcs && this.#move_bin) { + this.#move_bin.x = this.#move_funcs.revertAxis('x', this.#move_x0 + this.#pos_dx); + this.#move_bin.y = this.#move_funcs.revertAxis('y', this.#move_y0 + this.#pos_dy); this.drawGraph(); } } /** @summary Complete moving */ moveEnd(not_changed) { - const graph = this.getGraph(), last = graph?.fNpoints-1; + const graph = this.getGraph(), last = graph?.fNpoints - 1; let exec = ''; const changeBin = bin => { exec += `SetPoint(${bin.indx},${bin.x},${bin.y});;`; graph.fX[bin.indx] = bin.x; graph.fY[bin.indx] = bin.y; - if ((bin.indx === 0) && this._cutg_lastsame) { + if ((bin.indx === 0) && this.#cutg_lastsame) { exec += `SetPoint(${last},${bin.x},${bin.y});;`; graph.fX[last] = bin.x; graph.fY[last] = bin.y; } }; - if (this.move_binindx === undefined) { - this.draw_g.attr('transform', null); + if (this.#move_binindx === undefined) { + this.getG().attr('transform', null); - if (this.move_funcs && this.bins && !not_changed) { - for (let k = 0; k < this.bins.length; ++k) { - const bin = this.bins[k]; - bin.x = this.move_funcs.revertAxis('x', this.move_funcs.grx(bin.x) + this.pos_dx); - bin.y = this.move_funcs.revertAxis('y', this.move_funcs.gry(bin.y) + this.pos_dy); + if (this.#move_funcs && this.#bins && !not_changed) { + for (let k = 0; k < this.#bins.length; ++k) { + const bin = this.#bins[k]; + bin.x = this.#move_funcs.revertAxis('x', this.#move_funcs.grx(bin.x) + this.#pos_dx); + bin.y = this.#move_funcs.revertAxis('y', this.#move_funcs.gry(bin.y) + this.#pos_dy); changeBin(bin); } if (graph.$redraw_pad) @@ -1340,13 +1541,13 @@ class TGraphPainter extends ObjectPainter { this.drawGraph(); } } else { - changeBin(this.move_bin); - delete this.move_binindx; + changeBin(this.#move_bin); + this.#move_binindx = undefined; if (graph.$redraw_pad) this.redrawPad(); } - delete this.move_funcs; + this.#move_funcs = undefined; if (exec && !not_changed) this.submitCanvExec(exec); @@ -1354,34 +1555,52 @@ class TGraphPainter extends ObjectPainter { /** @summary Fill option object used in TWebCanvas */ fillWebObjectOptions(res) { - if (this._auto_exec && res) { - res.fcust = 'auto_exec:' + this._auto_exec; - delete this._auto_exec; + if (this.#auto_exec && res) { + res.fcust = 'auto_exec:' + this.#auto_exec; + this.#auto_exec = undefined; } } /** @summary Fill context menu */ fillContextMenuItems(menu) { - if (!this.snapid) - menu.addchk(this.testEditable(), 'Editable', () => { this.testEditable('toggle'); this.drawGraph(); }); + if (!this.hasSnapId()) { + menu.addchk(this.testEditable(), 'Editable', () => { + this.testEditable('toggle'); + this.drawGraph(); + }); + if (this.axes_draw) { + menu.add('Title', () => menu.input('Enter graph title', this.getObject().fTitle).then(res => { + this.getObject().fTitle = res; + const hist_painter = this.getMainPainter(); + if (hist_painter?.isSecondary(this)) { + setHistogramTitle(hist_painter.getHisto(), res); + this.interactiveRedraw('pad'); + } + })); + } + menu.addRedrawMenu(this.getPrimary()); + } } /** @summary Execute menu command * @private */ executeMenuCommand(method, args) { - if (super.executeMenuCommand(method, args)) return true; + if (super.executeMenuCommand(method, args)) + return true; - const canp = this.getCanvPainter(), pmain = this.get_main(); + const canp = this.getCanvPainter(), fp = this.get_fp(); if ((method.fName === 'RemovePoint') || (method.fName === 'InsertPoint')) { - if (!canp || canp._readonly) return true; // ignore function + if (!canp || canp.isReadonly()) + return true; // ignore function - const pnt = isFunc(pmain?.getLastEventPos) ? pmain.getLastEventPos() : null, - hint = this.extractTooltip(pnt); + const pnt = isFunc(fp?.getLastEventPos) ? fp.getLastEventPos() : null, + hint = this.extractTooltip(pnt); if (method.fName === 'InsertPoint') { if (pnt) { - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + const o = this.getOptions(), + funcs = fp.getGrFuncs(o.second_x, o.second_y), userx = funcs.revertAxis('x', pnt.x) ?? 0, usery = funcs.revertAxis('y', pnt.y) ?? 0; this.submitCanvExec(`AddPoint(${userx.toFixed(3)}, ${usery.toFixed(3)})`, method.$execid); @@ -1389,27 +1608,31 @@ class TGraphPainter extends ObjectPainter { } else if (method.$execid && (hint?.binindx !== undefined)) this.submitCanvExec(`RemovePoint(${hint.binindx})`, method.$execid); - return true; // call is processed } return false; } - /** @summary Update object members + /** @summary Update TGraph object members * @private */ _updateMembers(graph, obj) { graph.fBits = obj.fBits; graph.fTitle = obj.fTitle; graph.fX = obj.fX; graph.fY = obj.fY; + ['fEX', 'fEY', 'fExL', 'fExH', 'fEXlow', 'fEXhigh', 'fEYlow', 'fEYhigh', + 'fEXlowd', 'fEXhighd', 'fEYlowd', 'fEYhighd'].forEach(member => { + if (obj[member] !== undefined) + graph[member] = obj[member]; + }); graph.fNpoints = obj.fNpoints; graph.fMinimum = obj.fMinimum; graph.fMaximum = obj.fMaximum; - const o = this.options; + const o = this.getOptions(); - if (this.snapid !== undefined) + if (this.hasSnapId()) o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas if (!o._pfc) @@ -1423,32 +1646,37 @@ class TGraphPainter extends ObjectPainter { graph.fMarkerColor = obj.fMarkerColor; graph.fMarkerSize = obj.fMarkerSize; graph.fMarkerStyle = obj.fMarkerStyle; + + return obj.fFunctions; } /** @summary Update TGraph object */ updateObject(obj, opt) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; + + const o = this.getOptions(); - if (opt && (opt !== this.options.original)) + if (opt && (opt !== o.original)) this.decodeOptions(opt); - this._updateMembers(this.getObject(), obj); + const new_funcs = this._updateMembers(this.getObject(), obj); this.createBins(); - delete this.$redraw_hist; + this.#redraw_hist = undefined; // if our own histogram was used as axis drawing, we need update histogram as well if (this.axes_draw) { const histo = this.createHistogram(), hist_painter = this.getMainPainter(); if (hist_painter?.isSecondary(this)) { - hist_painter.updateObject(histo, this.options.Axis); - this.$redraw_hist = true; + hist_painter.updateObject(histo, o.Axis); + this.#redraw_hist = true; } } - this._funcHandler = new FunctionsHandler(this, this.getPadPainter(), obj.fFunctions); + this.#funcs_handler = new FunctionsHandler(this, this.getPadPainter(), new_funcs); return true; } @@ -1457,19 +1685,30 @@ class TGraphPainter extends ObjectPainter { * @desc allow to zoom TGraph only when at least one point in the range */ canZoomInside(axis, min, max) { const gr = this.getGraph(); - if (!gr || (axis !== (this.options.pos3d ? 'y' : 'x'))) return false; - - for (let n = 0; n < gr.fNpoints; ++n) - if ((min < gr.fX[n]) && (gr.fX[n] < max)) return true; + if (!gr || ((axis !== 'x') && (axis !== 'y'))) + return false; + + let arr = gr.fX; + if (this.isScatter()) + arr = (axis === 'x') ? gr.fX : gr.fY; + else if (axis !== (this.getOptions().pos3d ? 'y' : 'x')) + return false; + + for (let n = 0; n < gr.fNpoints; ++n) { + if ((min < arr[n]) && (arr[n] < max)) + return true; + } return false; } /** @summary Process click on graph-defined buttons */ clickButton(funcname) { - if (funcname !== 'ToggleZoom') return false; + if (funcname !== 'ToggleZoom') + return false; - if ((this.xmin === this.xmax) && (this.ymin === this.ymax)) return false; + if ((this.xmin === this.xmax) && (this.ymin === this.ymax)) + return false; return this.getFramePainter()?.zoom(this.xmin, this.xmax, this.ymin, this.ymax); } @@ -1487,22 +1726,25 @@ class TGraphPainter extends ObjectPainter { /** @summary Create stat box */ createStat() { const func = this.findFunc(); - if (!func) return null; + if (!func) + return null; let stats = this.findStat(); - if (stats) return stats; - - // do not create stats box when drawing canvas - if (this.getCanvPainter()?.normal_canvas) return null; - - this.create_stats = true; + if (stats) + return stats; const st = gStyle; + // do not create stats box when drawing canvas + if (!st.fOptFit || this.getCanvPainter()?.getRootPad(true)?.fPrimitives?.arr.length) + return null; + stats = create(clTPaveStats); - Object.assign(stats, { fName: 'stats', fOptStat: 0, fOptFit: st.fOptFit || 111, fBorderSize: 1, - fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, - fFillColor: st.fStatColor, fFillStyle: st.fStatStyle }); + Object.assign(stats, { + fName: 'stats', fOptStat: 0, fOptFit: st.fOptFit, fBorderSize: 1, + fX1NDC: st.fStatX - st.fStatW, fY1NDC: st.fStatY - st.fStatH, fX2NDC: st.fStatX, fY2NDC: st.fStatY, + fFillColor: st.fStatColor, fFillStyle: st.fStatStyle + }); stats.fTextAngle = 0; stats.fTextSize = st.fStatFontSize; // 9 ?? @@ -1521,21 +1763,21 @@ class TGraphPainter extends ObjectPainter { /** @summary Fill statistic */ fillStatistic(stat, _dostat, dofit) { const func = this.findFunc(); - - if (!func || !dofit) return false; + if (!func || !dofit) + return false; stat.clearPave(); - stat.fillFunctionStat(func, (dofit === 1) ? 111 : dofit, 1); - return true; } /** @summary Draw axis histogram * @private */ async drawAxisHisto() { - const histo = this.createHistogram(); - return TH1Painter.draw(this.getDom(), histo, this.options.Axis); + const set_x = this.isDummyHistogram('x'), + set_y = this.isDummyHistogram('y'), + histo = this.createHistogram(set_x, set_y); + return TH1Painter.draw(this.getDrawDom(), histo, this.getOptions().Axis); } /** @summary Draw TGraph @@ -1545,15 +1787,15 @@ class TGraphPainter extends ObjectPainter { painter.createBins(); painter.createStat(); const graph = painter.getGraph(); - if (!settings.DragGraphs && graph && !graph.TestBit(kNotEditable)) - graph.InvertBit(kNotEditable); + if (!settings.DragGraphs) + graph?.SetBit(kNotEditable, true); let promise = Promise.resolve(); if ((!painter.getMainPainter() || painter.options.second_x || painter.options.second_y) && painter.options.Axis) { promise = painter.drawAxisHisto().then(hist_painter => { hist_painter?.setSecondaryId(painter, 'hist'); - painter.axes_draw = !!hist_painter; + painter.axes_draw = Boolean(hist_painter); }); } @@ -1566,6 +1808,7 @@ class TGraphPainter extends ObjectPainter { }); } + /** @summary Draw TGraph in 2D only */ static async draw(dom, graph, opt) { return TGraphPainter._drawGraph(new TGraphPainter(dom, graph), opt); } diff --git a/modules/hist2d/TH1Painter.mjs b/modules/hist2d/TH1Painter.mjs index 41a2b896f..1f89fd899 100644 --- a/modules/hist2d/TH1Painter.mjs +++ b/modules/hist2d/TH1Painter.mjs @@ -1,4 +1,4 @@ -import { gStyle, settings, clTF1, kNoZoom, kInspect, isFunc } from '../core.mjs'; +import { gStyle, settings, clTF1, clTProfile, kNoZoom, kInspect, isFunc } from '../core.mjs'; import { rgb as d3_rgb } from '../d3.mjs'; import { floatToString, buildSvgCurve, addHighlightStyle } from '../base/BasePainter.mjs'; import { THistPainter } from './THistPainter.mjs'; @@ -6,7 +6,8 @@ import { getTF1Value } from '../base/func.mjs'; const PadDrawOptions = ['LOGXY', 'LOGX', 'LOGY', 'LOGZ', 'LOGV', 'LOG', 'LOG2X', 'LOG2Y', 'LOG2', - 'LNX', 'LNY', 'LN', 'GRIDXY', 'GRIDX', 'GRIDY', 'TICKXY', 'TICKX', 'TICKY', 'TICKZ', 'FB', 'GRAYSCALE']; + 'LNX', 'LNY', 'LN', 'GRIDXY', 'GRIDX', 'GRIDY', + 'TICKXY2', 'TICKX2', 'TICKY2', 'TICKXY', 'TICKX', 'TICKY', 'TICKZ', 'FB', 'GRAYSCALE']; /** * @summary Painter for TH1 classes @@ -15,18 +16,29 @@ const PadDrawOptions = ['LOGXY', 'LOGX', 'LOGY', 'LOGZ', 'LOGV', 'LOG', 'LOG2X', class TH1Painter extends THistPainter { - /** @summary Convert TH1K into normal binned histogram */ - convertTH1K() { - const histo = this.getObject(); - if (histo.fReady) return; - - const arr = histo.fArray, entries = histo.fEntries; // array of values - histo.fNcells = histo.fXaxis.fNbins + 2; - histo.fArray = new Float64Array(histo.fNcells).fill(0); - for (let n = 0; n < histo.fNIn; ++n) - histo.Fill(arr[n]); - histo.fReady = 1; - histo.fEntries = entries; + /** @summary Returns histogram + * @desc Also assigns custom getBinContent method for TProfile if PROJX options specified */ + getHisto() { + const histo = super.getHisto(); + if (histo?._typename === clTProfile) { + if (!histo.$getBinContent) + histo.$getBinContent = histo.getBinContent; + switch (this.getOptions().ProfileProj) { + case 'B': + histo.getBinContent = histo.getBinEntries; + break; + case 'C=E': + histo.getBinContent = histo.getBinError; + break; + case 'W': + histo.getBinContent = function(i) { return this.$getBinContent(i) * this.getBinEntries(i); }; + break; + default: + histo.getBinContent = histo.$getBinContent; + break; + } + } + return histo; } /** @summary Scan content of 1-D histogram @@ -36,34 +48,34 @@ class TH1Painter extends THistPainter { if (when_axis_changed && !this.nbinsx) when_axis_changed = false; - if (this.isTH1K()) - this.convertTH1K(); - - const histo = this.getHisto(); + const histo = this.getHisto(), + o = this.getOptions(); if (!when_axis_changed) this.extractAxesProperties(1); const left = this.getSelectIndex('x', 'left'), right = this.getSelectIndex('x', 'right'), - pad_logy = this.getPadPainter()?.getPadLog(this.options.BarStyle >= 20 ? 'x' : 'y'), - f1 = this.options.Func ? this.findFunction(clTF1) : null; + pad_logy = this.getPadPainter()?.getPadLog(o.swap_xy() ? 'x' : 'y'), + f1 = o.Func ? this.findFunction(clTF1) : null; if (when_axis_changed && (left === this.scan_xleft) && (right === this.scan_xright)) return; // Paint histogram axis only - this.draw_content = !(this.options.Axis > 0); + this.draw_content = !(o.Axis > 0); this.scan_xleft = left; this.scan_xright = right; - const profile = this.isTProfile(); - let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0, first = true, value, err; + const is_profile = this.isTProfile(), + imin = Math.min(0, left), + imax = Math.max(this.nbinsx, right); + let hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0, first = true, value, errs = { low: 0, up: 0 }; - for (let i = 0; i < this.nbinsx; ++i) { + for (let i = imin; i < imax; ++i) { value = histo.getBinContent(i + 1); - hsum += profile ? histo.fBinEntries[i + 1] : value; + hsum += is_profile ? histo.fBinEntries[i + 1] : value; if ((i < left) || (i >= right)) continue; @@ -76,10 +88,11 @@ class TH1Painter extends THistPainter { first = false; } - err = this.options.Error ? histo.getBinError(i + 1) : 0; + if (o.Error) + errs = this.getBinErrors(histo, i + 1, value); - hmin = Math.min(hmin, value - err); - hmax = Math.max(hmax, value + err); + hmin = Math.min(hmin, value - errs.low); + hmax = Math.max(hmax, value + errs.up); if (f1) { // similar code as in THistPainter, line 7196 @@ -94,12 +107,12 @@ class TH1Painter extends THistPainter { } // account overflow/underflow bins - if (profile) + if (is_profile) hsum += histo.fBinEntries[0] + histo.fBinEntries[this.nbinsx + 1]; else hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1); - this.stat_entries = (histo.fEntries > 1) ? histo.fEntries : hsum; + this.stat_entries = hsum; this.hmin = hmin; this.hmax = hmax; @@ -111,78 +124,95 @@ class TH1Painter extends THistPainter { let set_zoom = false; - if (this.draw_content || (this.isMainPainter() && (this.options.Axis > 0) && !this.options.ohmin && !this.options.ohmax && histo.fMinimum === kNoZoom && histo.fMaximum === kNoZoom)) { + if (this.draw_content || (this.isMainPainter() && (o.Axis > 0) && !o.ohmin && !o.ohmax && (histo.fMinimum === kNoZoom) && (histo.fMaximum === kNoZoom))) { if (hmin >= hmax) { if (hmin === 0) { - this.ymin = 0; this.ymax = 1; + this.ymin = 0; + this.ymax = 1; } else if (hmin < 0) { - this.ymin = 2 * hmin; this.ymax = 0; + this.ymin = 2 * hmin; + this.ymax = 0; } else { - this.ymin = 0; this.ymax = hmin * 2; + this.ymin = 0; + this.ymax = hmin * 2; } + } else if (pad_logy) { + this.ymin = (hmin_nz || hmin) * 0.5; + this.ymax = hmax * 2 * (0.9 / 0.95); } else { - if (pad_logy) { - this.ymin = (hmin_nz || hmin) * 0.5; - this.ymax = hmax*2*(0.9/0.95); - } else { - this.ymin = hmin; - this.ymax = hmax; - } + this.ymin = hmin; + this.ymax = hmax; } } - // final adjustment like in THistPainter.cxx line 7309 - if (!this._exact_y_range && !this._set_y_range && !pad_logy) { - if ((this.options.BaseLine !== false) && (this.ymin >= 0)) - this.ymin = 0; - else { - const positive = (this.ymin >= 0); - this.ymin -= gStyle.fHistTopMargin*(this.ymax-this.ymin); - if (positive && (this.ymin < 0)) - this.ymin = 0; - } - this.ymax += gStyle.fHistTopMargin*(this.ymax-this.ymin); - } - - hmin = this.options.minimum; - hmax = this.options.maximum; + hmin = o.minimum; + hmax = o.maximum; if ((hmin === hmax) && (hmin !== kNoZoom)) { if (hmin < 0) { - hmin *= 2; hmax = 0; + hmin *= 2; + hmax = 0; } else { - hmin = 0; hmax *= 2; - if (!hmax) hmax = 1; + hmin = 0; + hmax = 2 * hmax || 1; } } - this._set_y_range = false; + let fix_min = false, fix_max = false; - if ((hmin !== kNoZoom) && (hmax !== kNoZoom) && !this.draw_content && + if (o.ohmin && o.ohmax && !this.draw_content) { + // case of hstack drawing, zooming allowed only when flag is provided + + if (o.zoom_min_max) { + if ((hmin !== kNoZoom) && (hmin <= this.ymin)) + hmin = kNoZoom; + if ((hmax !== kNoZoom) && (hmax >= this.ymax)) + hmax = kNoZoom; + set_zoom = true; + } else + hmin = hmax = kNoZoom; + } else if ((hmin !== kNoZoom) && (hmax !== kNoZoom) && !this.draw_content && ((this.ymin === this.ymax) || (this.ymin > hmin) || (this.ymax < hmax))) { + // often appears with TF1 painter where Y range is not set properly this.ymin = hmin; this.ymax = hmax; - this._set_y_range = true; + fix_min = fix_max = true; } else { if (hmin !== kNoZoom) { - this._set_y_range = true; + fix_min = true; if (hmin < this.ymin) this.ymin = hmin; - set_zoom = true; + set_zoom = true; } if (hmax !== kNoZoom) { - this._set_y_range = true; + fix_max = true; if (hmax > this.ymax) this.ymax = hmax; set_zoom = true; } } + // final adjustment like in THistPainter.cxx line 7309 + if (!o.exact_values_range() && !pad_logy) { + if (!fix_min) { + if ((o.BaseLine !== false) && (this.ymin >= 0)) + this.ymin = 0; + else { + const positive = (this.ymin >= 0); + this.ymin -= gStyle.fHistTopMargin * (this.ymax - this.ymin); + if (positive && (this.ymin < 0)) + this.ymin = 0; + } + } + if (!fix_max) + this.ymax += gStyle.fHistTopMargin * (this.ymax - this.ymin); + } + // always set zoom when hmin/hmax is configured // fMinimum/fMaximum values is a way how ROOT handles Y scale zooming for TH1 if (!when_axis_changed) { - if (set_zoom) { + if (set_zoom && ((hmin !== kNoZoom) || (hmax !== kNoZoom))) { this.zoom_ymin = (hmin === kNoZoom) ? this.ymin : hmin; this.zoom_ymax = (hmax === kNoZoom) ? this.ymax : hmax; } else { @@ -190,9 +220,39 @@ class TH1Painter extends THistPainter { delete this.zoom_ymax; } } + } + + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return (this.getDimension() > 1) || !this.draw_content; } + + /** @summary Provide histogram min/max used to create canvas ranges + * @private */ + getUserRanges() { + const histo = this.getHisto(); + + let miny = 0, maxy = 0; + + for (let i = 0; i < histo.fXaxis.fNbins; ++i) { + const value = histo.getBinContent(i + 1); + if (i === 0) + miny = maxy = value; + else { + miny = Math.min(miny, value); + maxy = Math.max(maxy, value); + } + } + + if (histo.fMinimum !== kNoZoom) + miny = histo.fMinimum; + + if (histo.fMaximum !== kNoZoom) + maxy = histo.fMaximum; + + if (maxy <= miny) + maxy = miny + 1; - // used in FramePainter.isAllowedDefaultYZooming - this.wheel_zoomy = (this.getDimension() > 1) || !this.draw_content; + return { minx: histo.fXaxis.fXmin, maxx: histo.fXaxis.fXmax, miny, maxy }; } /** @summary Count histogram statistic */ @@ -202,18 +262,23 @@ class TH1Painter extends THistPainter { left = this.getSelectIndex('x', 'left'), right = this.getSelectIndex('x', 'right'), fp = this.getFramePainter(), - res = { name: histo.fName, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, - entries: this.stat_entries, eff_entries: 0, xmax: 0, wmax: 0, skewx: 0, skewd: 0, kurtx: 0, kurtd: 0 }, - has_counted_stat = !fp.isAxisZoomed('x') && (Math.abs(histo.fTsumw) > 1e-300); + res = { + name: histo.fName, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, + entries: (histo.fEntries > 0) ? histo.fEntries : this.stat_entries, + eff_entries: 0, xmax: 0, wmax: 0, skewx: 0, skewd: 0, kurtx: 0, kurtd: 0 + }, + has_counted_stat = !fp?.isAxisZoomed('x') && (Math.abs(histo.fTsumw) > 1e-300); let stat_sumw = 0, stat_sumw2 = 0, stat_sumwx = 0, stat_sumwx2 = 0, stat_sumwy = 0, stat_sumwy2 = 0, - i, xx = 0, w = 0, xmax = null, wmax = null; + i, xx, w, xmax = null, wmax = null; - if (!isFunc(cond)) cond = null; + if (!isFunc(cond)) + cond = null; for (i = left; i < right; ++i) { xx = xaxis.GetBinCoord(i + 0.5); - if (cond && !cond(xx)) continue; + if (cond && !cond(xx)) + continue; if (profile) { w = histo.fBinEntries[i + 1]; @@ -222,7 +287,6 @@ class TH1Painter extends THistPainter { } else w = histo.getBinContent(i + 1); - if ((xmax === null) || (w > wmax)) { xmax = xx; wmax = w; @@ -232,7 +296,7 @@ class TH1Painter extends THistPainter { stat_sumw += w; stat_sumw2 += w * w; stat_sumwx += w * xx; - stat_sumwx2 += w * xx**2; + stat_sumwx2 += w * xx ** 2; } } @@ -246,13 +310,13 @@ class TH1Painter extends THistPainter { res.integral = stat_sumw; - res.eff_entries = stat_sumw2 ? stat_sumw*stat_sumw/stat_sumw2 : Math.abs(stat_sumw); + res.eff_entries = stat_sumw2 ? stat_sumw * stat_sumw / stat_sumw2 : Math.abs(stat_sumw); if (Math.abs(stat_sumw) > 1e-300) { res.meanx = stat_sumwx / stat_sumw; res.meany = stat_sumwy / stat_sumw; - res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany**2)); + res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx ** 2)); + res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany ** 2)); } if (xmax !== null) { @@ -264,7 +328,8 @@ class TH1Painter extends THistPainter { let sum3 = 0, sum4 = 0, np = 0; for (i = left; i < right; ++i) { xx = xaxis.GetBinCoord(i + 0.5); - if (cond && !cond(xx)) continue; + if (cond && !cond(xx)) + continue; w = profile ? histo.fBinEntries[i + 1] : histo.getBinContent(i + 1); np += w; sum3 += w * Math.pow(xx - res.meanx, 3); @@ -272,12 +337,12 @@ class TH1Painter extends THistPainter { } const stddev3 = Math.pow(res.rmsx, 3), stddev4 = Math.pow(res.rmsx, 4); - if (np * stddev3 !== 0) + if (np * stddev3) res.skewx = sum3 / (np * stddev3); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; - if (np * stddev4 !== 0) + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; + if (np * stddev4) res.kurtx = sum4 / (np * stddev4) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; } return res; @@ -286,10 +351,13 @@ class TH1Painter extends THistPainter { /** @summary Fill stat box */ fillStatistic(stat, dostat, dofit) { // no need to refill statistic if histogram is dummy - if (this.isIgnoreStatsFill()) return false; + if (this.isIgnoreStatsFill()) + return false; - if (dostat === 1) dostat = 1111; - if (dofit === 1) dofit = 111; + if (dostat === 1) + dostat = 1111; + if (dofit === 1) + dofit = 111; const histo = this.getHisto(), print_name = dostat % 10, @@ -334,10 +402,10 @@ class TH1Painter extends THistPainter { stat.addText('Std Dev = ' + stat.format(data.rmsx)); if (print_under > 0) - stat.addText('Underflow = ' + stat.format((histo.fArray.length > 0) ? histo.fArray[0] : 0, 'entries')); + stat.addText('Underflow = ' + stat.format(histo.fArray.length ? histo.fArray[0] : 0, 'entries')); if (print_over > 0) - stat.addText('Overflow = ' + stat.format((histo.fArray.length > 0) ? histo.fArray[histo.fArray.length - 1] : 0, 'entries')); + stat.addText('Overflow = ' + stat.format(histo.fArray.length ? histo.fArray.at(-1) : 0, 'entries')); if (print_integral > 0) stat.addText('Integral = ' + stat.format(data.integral, 'entries')); @@ -353,108 +421,119 @@ class TH1Painter extends THistPainter { stat.addText(`Kurtosis = ${stat.format(data.kurtx)}`); } - if (dofit) stat.fillFunctionStat(this.findFunction(clTF1), dofit, 1); + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF1), dofit, 1); return true; } + /** @summary Get baseline for bar drawings */ + getBarBaseline(funcs, height) { + const o = this.getOptions(); + let gry = funcs.swap_xy() ? 0 : height; + if (Number.isFinite(o.BaseLine) && (o.BaseLine >= funcs.scale_ymin)) + gry = Math.round(funcs.gry(o.BaseLine)); + return gry; + } + /** @summary Draw histogram as bars */ async drawBars(funcs, height) { const left = this.getSelectIndex('x', 'left', -1), right = this.getSelectIndex('x', 'right', 1), - histo = this.getHisto(), xaxis = histo.fXaxis, - show_text = this.options.Text; + histo = this.getHisto(), + o = this.getOptions(), + xaxis = histo.fXaxis, + show_text = o.Text; let text_col, text_angle, text_size, - i, x1, x2, grx1, grx2, y, gry1, gry2, w, - bars = '', barsl = '', barsr = '', - side = (this.options.BarStyle > 10) ? this.options.BarStyle % 10 : 0; + side = (o.BarStyle > 10) ? o.BarStyle % 10 : 0, + pr = Promise.resolve(); - if (side > 4) side = 4; - gry2 = funcs.swap_xy ? 0 : height; - if (Number.isFinite(this.options.BaseLine)) { - if (this.options.BaseLine >= funcs.scale_ymin) - gry2 = Math.round(funcs.gry(this.options.BaseLine)); - } + if (side > 4) + side = 4; + const gry2 = this.getBarBaseline(funcs, height); if (show_text) { text_col = this.getColor(histo.fMarkerColor); - text_angle = -1*this.options.TextAngle; + text_angle = -1 * o.TextAngle; text_size = 20; if ((histo.fMarkerSize !== 1) && text_angle) - text_size = 0.02*height*histo.fMarkerSize; + text_size = 0.02 * height * histo.fMarkerSize; - this.startTextDrawing(42, text_size, this.draw_g, text_size); + pr = this.startTextDrawingAsync(42, text_size, undefined, text_size); } - for (i = left; i < right; ++i) { - x1 = xaxis.GetBinLowEdge(i+1); - x2 = xaxis.GetBinLowEdge(i+2); + return pr.then(() => { + let bars = '', barsl = '', barsr = ''; - if (funcs.logx && (x2 <= 0)) continue; + for (let i = left; i < right; ++i) { + const x1 = xaxis.GetBinLowEdge(i + 1), + x2 = xaxis.GetBinLowEdge(i + 2); - grx1 = Math.round(funcs.grx(x1)); - grx2 = Math.round(funcs.grx(x2)); + if (funcs.logx && (x2 <= 0)) + continue; - y = histo.getBinContent(i+1); - if (funcs.logy && (y < funcs.scale_ymin)) continue; - gry1 = Math.round(funcs.gry(y)); + let grx1 = Math.round(funcs.grx(x1)), + grx2 = Math.round(funcs.grx(x2)), + w = grx2 - grx1; + const y = histo.getBinContent(i + 1); - w = grx2 - grx1; - grx1 += Math.round(histo.fBarOffset/1000*w); - w = Math.round(histo.fBarWidth/1000*w); + if (funcs.logy && (y < funcs.scale_ymin)) + continue; + const gry1 = Math.round(funcs.gry(y)); - if (funcs.swap_xy) - bars += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - else - bars += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - - if (side > 0) { - grx2 = grx1 + w; - w = Math.round(w * side / 10); - if (funcs.swap_xy) { - barsl += `M${gry2},${grx1}h${gry1-gry2}v${w}h${gry2-gry1}z`; - barsr += `M${gry2},${grx2}h${gry1-gry2}v${-w}h${gry2-gry1}z`; - } else { - barsl += `M${grx1},${gry1}h${w}v${gry2-gry1}h${-w}z`; - barsr += `M${grx2},${gry1}h${-w}v${gry2-gry1}h${w}z`; + grx1 += Math.round(histo.fBarOffset / 1000 * w); + w = Math.round(histo.fBarWidth / 1000 * w); + + if (funcs.swap_xy()) + bars += `M${gry2},${grx1}h${gry1 - gry2}v${w}h${gry2 - gry1}z`; + else + bars += `M${grx1},${gry1}h${w}v${gry2 - gry1}h${-w}z`; + + if (side > 0) { + grx2 = grx1 + w; + w = Math.round(w * side / 10); + if (funcs.swap_xy()) { + barsl += `M${gry2},${grx1}h${gry1 - gry2}v${w}h${gry2 - gry1}z`; + barsr += `M${gry2},${grx2}h${gry1 - gry2}v${-w}h${gry2 - gry1}z`; + } else { + barsl += `M${grx1},${gry1}h${w}v${gry2 - gry1}h${-w}z`; + barsr += `M${grx2},${gry1}h${-w}v${gry2 - gry1}h${w}z`; + } } - } - if (show_text && y) { - const text = (y === Math.round(y)) ? y.toString() : floatToString(y, gStyle.fPaintTextFormat); + if (show_text && y) { + const text = (y === Math.round(y)) ? y.toString() : floatToString(y, gStyle.fPaintTextFormat); - if (funcs.swap_xy) - this.drawText({ align: 12, x: Math.round(gry1 + text_size/2), y: Math.round(grx1+0.1), height: Math.round(w*0.8), text, color: text_col, latex: 0 }); - else if (text_angle) - this.drawText({ align: 12, x: grx1+w/2, y: Math.round(gry1 - 2 - text_size/5), width: 0, height: 0, rotate: text_angle, text, color: text_col, latex: 0 }); - else - this.drawText({ align: 22, x: Math.round(grx1 + w*0.1), y: Math.round(gry1 - 2 - text_size), width: Math.round(w*0.8), height: text_size, text, color: text_col, latex: 0 }); + if (funcs.swap_xy()) + this.drawText({ align: 12, x: Math.round(gry1 + text_size / 2), y: Math.round(grx1 + 0.1), height: Math.round(w * 0.8), text, color: text_col, latex: 0 }); + else if (text_angle) + this.drawText({ align: 12, x: grx1 + w / 2, y: Math.round(gry1 - 2 - text_size / 5), width: 0, height: 0, rotate: text_angle, text, color: text_col, latex: 0 }); + else + this.drawText({ align: 22, x: Math.round(grx1 + w * 0.1), y: Math.round(gry1 - 2 - text_size), width: Math.round(w * 0.8), height: text_size, text, color: text_col, latex: 0 }); + } } - } - if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) - .call(this.fillatt.func); - } + if (bars) { + this.appendPath(bars) + .call(this.fillatt.func); + } - if (barsl) { - this.draw_g.append('svg:path') - .attr('d', barsl) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); - } + if (barsl) { + this.appendPath(barsl) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); + } - if (barsr) { - this.draw_g.append('svg:path') - .attr('d', barsr) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatHex()); - } + if (barsr) { + this.appendPath(barsr) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); + } - if (show_text) - return this.finishTextDrawing(); + if (show_text) + return this.finishTextDrawing(); + }); } /** @summary Draw histogram as filled errors */ @@ -464,56 +543,61 @@ class TH1Painter extends THistPainter { histo = this.getHisto(), bins1 = [], bins2 = []; for (let i = left; i < right; ++i) { - const x = histo.fXaxis.GetBinCoord(i+0.5); - if (funcs.logx && (x <= 0)) continue; + const x = histo.fXaxis.GetBinCoord(i + 0.5); + if (funcs.logx && (x <= 0)) + continue; const grx = Math.round(funcs.grx(x)), - y = histo.getBinContent(i+1), - yerr = histo.getBinError(i+1); - if (funcs.logy && (y-yerr < funcs.scale_ymin)) continue; + y = histo.getBinContent(i + 1), + yerrs = this.getBinErrors(histo, i + 1, y); + if (funcs.logy && (y - yerrs.low < funcs.scale_ymin)) + continue; - bins1.push({ grx, gry: Math.round(funcs.gry(y + yerr)) }); - bins2.unshift({ grx, gry: Math.round(funcs.gry(y - yerr)) }); + bins1.push({ grx, gry: Math.round(funcs.gry(y + yerrs.up)) }); + bins2.unshift({ grx, gry: Math.round(funcs.gry(y - yerrs.low)) }); } - const line = this.options.ErrorKind !== 4, + const line = this.getOptions().ErrorKind !== 4, path1 = buildSvgCurve(bins1, { line }), path2 = buildSvgCurve(bins2, { line, cmd: 'L' }); - this.draw_g.append('svg:path') - .attr('d', path1 + path2 + 'Z') - .call(this.fillatt.func); + this.appendPath(path1 + path2 + 'Z') + .call(this.fillatt.func); } /** @summary Draw TH1 as hist/line/curve * @return Promise or scalar value */ - drawNormal(funcs, width, height) { + async drawNormal(funcs, width, height) { const left = this.getSelectIndex('x', 'left', -1), right = this.getSelectIndex('x', 'right', 2), histo = this.getHisto(), + o = this.getOptions(), want_tooltip = !this.isBatchMode() && settings.Tooltip, xaxis = histo.fXaxis, - exclude_zero = !this.options.Zero, - show_errors = this.options.Error, - show_curve = this.options.Curve, - show_text = this.options.Text, - text_profile = show_text && (this.options.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, + exclude_zero = !o.Zero, + show_errors = o.Error, + show_curve = o.Curve, + show_text = o.Text, + text_profile = show_text && (o.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, grpnts = []; let res = '', lastbin = false, - show_markers = this.options.Mark, - show_line = this.options.Line, + show_markers = o.Mark, + show_line = o.Line, startx, startmidx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, path_fill = null, path_err = null, path_marker = null, path_line = '', - hints_err = null, hints_marker = null, hsz = 5, + hints_err = null, hints_text = null, hints_marker = null, hsz = 5, do_marker = false, do_err = false, dend = 0, dlw = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx, lx, ly, mmx1, mmx2, - text_col, text_angle, text_size; + text_col, text_angle, text_size, + pr = Promise.resolve(); if (show_errors && !show_markers && (histo.fMarkerStyle > 1)) show_markers = true; - if (this.options.ErrorKind === 2) { - if (this.fillatt.empty()) show_markers = true; - else path_fill = ''; + if (o.ErrorKind === 2) { + if (this.fillatt.empty()) + show_markers = true; + else + path_fill = ''; } else if (show_errors) { show_line = false; path_err = ''; @@ -522,14 +606,12 @@ class TH1Painter extends THistPainter { } dlw = this.lineatt.width + gStyle.fEndErrorSize; - if (this.options.ErrorKind === 1) - dend = Math.floor((this.lineatt.width-1)/2); + if (o.ErrorKind === 1) + dend = Math.floor((this.lineatt.width - 1) / 2); if (show_markers) { // draw markers also when e2 option was specified - let style = this.options.MarkStyle; - if (!style && (histo.fMarkerStyle === 1)) style = 8; // as in recent ROOT changes - this.createAttMarker({ attr: histo, style }); // when style not configured, it will be ignored + this.createAttMarker({ attr: histo, style: o.MarkStyle }); // when style not configured, it will be ignored if (this.markeratt.size > 0) { // simply use relative move from point, can optimize in the future path_marker = ''; @@ -537,322 +619,351 @@ class TH1Painter extends THistPainter { this.markeratt.resetPos(); if ((hints_err === null) && want_tooltip && (!this.markeratt.fill || (this.markeratt.getFullSize() < 7))) { hints_marker = ''; - hsz = Math.max(5, Math.round(this.markeratt.getFullSize()*0.7)); - } + hsz = Math.max(5, Math.round(this.markeratt.getFullSize() * 0.7)); + } } else show_markers = false; } const draw_markers = show_errors || show_markers, draw_any_but_hist = draw_markers || show_text || show_line || show_curve, - draw_hist = this.options.Hist && (!this.lineatt.empty() || !this.fillatt.empty()); + draw_hist = o.Hist && (!this.lineatt.empty() || !this.fillatt.empty()), + check_sumw2 = show_errors && histo.fSumw2?.length, + // if there are too many points, exclude many vertical drawings at the same X position + // instead define min and max value and made min-max drawing + use_minmax = draw_any_but_hist || ((right - left) > 3 * width); if (!draw_hist && !draw_any_but_hist) return this.removeG(); if (show_text) { text_col = this.getColor(histo.fMarkerColor); - text_angle = -1*this.options.TextAngle; + text_angle = -1 * o.TextAngle; text_size = 20; if ((histo.fMarkerSize !== 1) && text_angle) - text_size = 0.02*height*histo.fMarkerSize; - - if (!text_angle && !this.options.TextKind) { - const space = width / (right - left + 1); - if (space < 3 * text_size) { - text_angle = 270; - text_size = Math.round(space*0.7); - } - } + text_size = 0.02 * height * histo.fMarkerSize; - this.startTextDrawing(42, text_size, this.draw_g, text_size); - } - - // if there are too many points, exclude many vertical drawings at the same X position - // instead define min and max value and made min-max drawing - const use_minmax = draw_any_but_hist || ((right - left) > 3*width), - - // just to get correct values for the specified bin - extract_bin = bin => { - bincont = histo.getBinContent(bin+1); - if (exclude_zero && (bincont === 0)) return false; - mx1 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin+1))); - mx2 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin+2))); - midx = Math.round((mx1 + mx2) / 2); - if (startmidx === undefined) startmidx = midx; - my = Math.round(funcs.gry(bincont)); - if (show_errors) { - binerr = histo.getBinError(bin+1); - yerr1 = Math.round(my - funcs.gry(bincont + binerr)); // up - yerr2 = Math.round(funcs.gry(bincont - binerr) - my); // down - } else - yerr1 = yerr2 = 20; - - return true; - }, draw_errbin = () => { - let edx = 5; - if (this.options.errorX > 0) { - edx = Math.round((mx2 - mx1) * this.options.errorX); - mmx1 = midx - edx; - mmx2 = midx + edx; - if (this.options.ErrorKind === 1) - path_err += `M${mmx1+dend},${my-dlw}v${2*dlw}m0,-${dlw}h${mmx2-mmx1-2*dend}m0,-${dlw}v${2*dlw}`; - else - path_err += `M${mmx1+dend},${my}h${mmx2-mmx1-2*dend}`; - } - if (this.options.ErrorKind === 1) - path_err += `M${midx-dlw},${my-yerr1+dend}h${2*dlw}m${-dlw},0v${yerr1+yerr2-2*dend}m${-dlw},0h${2*dlw}`; - else - path_err += `M${midx},${my-yerr1+dend}v${yerr1+yerr2-2*dend}`; - if (hints_err !== null) { - const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); - hints_err += `M${midx-edx},${my-he1}h${2*edx}v${he1+he2}h${-2*edx}z`; + if (!text_angle && !o.TextKind) { + const space = width / (right - left + 1); + if (space < 3 * text_size) { + text_angle = 270; + text_size = Math.round(space * 0.7); + } } - }, draw_bin = bin => { - if (extract_bin(bin)) { - if (show_text) { - const cont = text_profile ? histo.fBinEntries[bin+1] : bincont; - if (cont !== 0) { - const lbl = (cont === Math.round(cont)) ? cont.toString() : floatToString(cont, gStyle.fPaintTextFormat); + if (want_tooltip && !draw_hist) + hints_text = ''; - if (text_angle) - this.drawText({ align: 12, x: midx, y: Math.round(my - 2 - text_size/5), width: 0, height: 0, rotate: text_angle, text: lbl, color: text_col, latex: 0 }); - else - this.drawText({ align: 22, x: Math.round(mx1 + (mx2-mx1)*0.1), y: Math.round(my-2-text_size), width: Math.round((mx2-mx1)*0.8), height: text_size, text: lbl, color: text_col, latex: 0 }); - } - } + pr = this.startTextDrawingAsync(42, text_size, undefined, text_size); + } - if (show_line) { - if (path_line.length === 0) - path_line = `M${midx},${my}`; - else if (lx === midx) - path_line += `v${my-ly}`; - else if (ly === my) - path_line += `h${midx-lx}`; + return pr.then(() => { + // just to get correct values for the specified bin + const extract_bin = bin => { + bincont = histo.getBinContent(bin + 1); + if (exclude_zero && (bincont === 0) && (!check_sumw2 || !histo.fSumw2[bin + 1])) + return false; + mx1 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin + 1))); + mx2 = Math.round(funcs.grx(xaxis.GetBinLowEdge(bin + 2))); + midx = Math.round((mx1 + mx2) / 2); + if (startmidx === undefined) + startmidx = midx; + my = Math.round(funcs.gry(bincont)); + if (show_errors) { + binerr = this.getBinErrors(histo, bin + 1, bincont); + yerr1 = Math.round(my - funcs.gry(bincont + binerr.up)); // up + yerr2 = Math.round(funcs.gry(bincont - binerr.low) - my); // low + } else + yerr1 = yerr2 = 20; + + return true; + }, draw_errbin = () => { + let edx = 5; + if (o.errorX > 0) { + edx = Math.round((mx2 - mx1) * o.errorX); + mmx1 = midx - edx; + mmx2 = midx + edx; + if (o.ErrorKind === 1) + path_err += `M${mmx1 + dend},${my - dlw}v${2 * dlw}m0,-${dlw}h${mmx2 - mmx1 - 2 * dend}m0,-${dlw}v${2 * dlw}`; else - path_line += `l${midx-lx},${my-ly}`; - lx = midx; ly = my; - } else if (show_curve) - grpnts.push({ grx: (mx1 + mx2) / 2, gry: funcs.gry(bincont) }); - - if (draw_markers) { - if ((my >= -yerr1) && (my <= height + yerr2)) { - if (path_fill !== null) - path_fill += `M${mx1},${my-yerr1}h${mx2-mx1}v${yerr1+yerr2+1}h${mx1-mx2}z`; - if ((path_marker !== null) && do_marker) { - path_marker += this.markeratt.create(midx, my); - if (hints_marker !== null) - hints_marker += `M${midx-hsz},${my-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; + path_err += `M${mmx1 + dend},${my}h${mmx2 - mmx1 - 2 * dend}`; + } + if (o.ErrorKind === 1) + path_err += `M${midx - dlw},${my - yerr1 + dend}h${2 * dlw}m${-dlw},0v${yerr1 + yerr2 - 2 * dend}m${-dlw},0h${2 * dlw}`; + else + path_err += `M${midx},${my - yerr1 + dend}v${yerr1 + yerr2 - 2 * dend}`; + if (hints_err !== null) { + const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); + hints_err += `M${midx - edx},${my - he1}h${2 * edx}v${he1 + he2}h${-2 * edx}z`; + } + }, draw_marker = () => { + if (funcs.swap_xy()) { + path_marker += this.markeratt.create(my, midx); + if (hints_marker !== null) + hints_marker += `M${my - hsz},${midx - hsz}v${2 * hsz}h${2 * hsz}v${-2 * hsz}z`; + } else { + path_marker += this.markeratt.create(midx, my); + if (hints_marker !== null) + hints_marker += `M${midx - hsz},${my - hsz}h${2 * hsz}v${2 * hsz}h${-2 * hsz}z`; + } + }, draw_bin = bin => { + if (extract_bin(bin)) { + if (show_text) { + const cont = text_profile ? histo.fBinEntries[bin + 1] : bincont; + + if (cont) { + const arg = text_angle + ? { align: 12, x: midx, y: Math.round(my - 2 - text_size / 5), width: 0, height: 0, rotate: text_angle } + : { align: 22, x: Math.round(mx1 + (mx2 - mx1) * 0.1), y: Math.round(my - 2 - text_size), width: Math.round((mx2 - mx1) * 0.8), height: text_size }; + arg.text = (cont === Math.round(cont)) ? cont.toString() : floatToString(cont, gStyle.fPaintTextFormat); + arg.color = text_col; + arg.latex = 0; + if (funcs.swap_xy()) { + arg.x = my; + arg.y = Math.round(midx - text_size / 2); + } + this.drawText(arg); + if (hints_text !== null) + hints_text += `M${mx1},${my - hsz}v${2 * hsz}h${mx2 - mx1}v${-2 * hsz}z`; } + } - if ((path_err !== null) && do_err) - draw_errbin(); + if (show_line) { + if (funcs.swap_xy()) + path_line += (path_line ? 'L' : 'M') + `${my},${midx}`; // no optimization + else if (!path_line) + path_line = `M${midx},${my}`; + else if (lx === midx) + path_line += `v${my - ly}`; + else if (ly === my) + path_line += `h${midx - lx}`; + else + path_line += `l${midx - lx},${my - ly}`; + lx = midx; + ly = my; + } else if (show_curve) + grpnts.push({ grx: (mx1 + mx2) / 2, gry: funcs.gry(bincont) }); + + if (draw_markers) { + if ((my >= -yerr1) && (my <= height + yerr2)) { + if (path_fill !== null) + path_fill += `M${mx1},${my - yerr1}h${mx2 - mx1}v${yerr1 + yerr2 + 1}h${mx1 - mx2}z`; + if ((path_marker !== null) && do_marker) + draw_marker(); + if ((path_err !== null) && do_err) + draw_errbin(); + } } } - } - }; - - // check if we should draw markers or error marks directly, skipping optimization - if (do_marker || do_err) { - if (!settings.OptimizeDraw || ((right-left < 50000) && (settings.OptimizeDraw === 1))) { - for (i = left; i < right; ++i) { - if (extract_bin(i)) { - if (path_marker !== null) - path_marker += this.markeratt.create(midx, my); - if (hints_marker !== null) - hints_marker += `M${midx-hsz},${my-hsz}h${2*hsz}v${2*hsz}h${-2*hsz}z`; - if (path_err !== null) - draw_errbin(); + }; + + // check if we should draw markers or error marks directly, skipping optimization + if (do_marker || do_err) { + if (!settings.OptimizeDraw || ((right - left < 50000) && (settings.OptimizeDraw === 1))) { + for (i = left; i < right; ++i) { + if (extract_bin(i)) { + if (path_marker !== null) + draw_marker(); + if (path_err !== null) + draw_errbin(); + } } + do_err = do_marker = false; } - do_err = do_marker = false; } - } + for (i = left; i <= right; ++i) { + x = xaxis.GetBinLowEdge(i + 1); - for (i = left; i <= right; ++i) { - x = xaxis.GetBinLowEdge(i+1); + if (this.logx && (x <= 0)) + continue; - if (this.logx && (x <= 0)) continue; + grx = Math.round(funcs.grx(x)); - grx = Math.round(funcs.grx(x)); + lastbin = (i === right); - lastbin = (i === right); - - if (lastbin && (left<right)) - gry = curry; - else { - y = histo.getBinContent(i+1); - gry = Math.round(funcs.gry(y)); - } + if (lastbin && (left < right)) + gry = curry; + else { + y = histo.getBinContent(i + 1); + gry = Math.round(funcs.gry(y)); + } - if (res.length === 0) { - bestimin = bestimax = i; - prevx = startx = currx = grx; - prevy = curry_min = curry_max = curry = gry; - res = `M${currx},${curry}`; - } else if (use_minmax) { - if ((grx === currx) && !lastbin) { - if (gry < curry_min) - bestimax = i; - else if (gry > curry_max) - bestimin = i; - - curry_min = Math.min(curry_min, gry); - curry_max = Math.max(curry_max, gry); - curry = gry; - } else { - if (draw_any_but_hist) { - if (bestimin === bestimax) - draw_bin(bestimin); - else if (bestimin < bestimax) { - draw_bin(bestimin); draw_bin(bestimax); - } else { - draw_bin(bestimax); draw_bin(bestimin); + if (!res) { + bestimin = bestimax = i; + prevx = startx = currx = grx; + prevy = curry_min = curry_max = curry = gry; + res = `M${currx},${curry}`; + } else if (use_minmax) { + if ((grx === currx) && !lastbin) { + if (gry < curry_min) + bestimax = i; + else if (gry > curry_max) + bestimin = i; + + curry_min = Math.min(curry_min, gry); + curry_max = Math.max(curry_max, gry); + curry = gry; + } else { + if (draw_any_but_hist) { + if (bestimin === bestimax) + draw_bin(bestimin); + else if (bestimin < bestimax) { + draw_bin(bestimin); + draw_bin(bestimax); + } else { + draw_bin(bestimax); + draw_bin(bestimin); + } } - } - // when several points at same X differs, need complete logic - if (draw_hist && ((curry_min !== curry_max) || (prevy !== curry_min))) { - if (prevx !== currx) - res += 'h'+(currx-prevx); - - if (curry === curry_min) { - if (curry_max !== prevy) - res += 'v' + (curry_max - prevy); - if (curry_min !== curry_max) - res += 'v' + (curry_min - curry_max); - } else { - if (curry_min !== prevy) - res += 'v' + (curry_min - prevy); - if (curry_max !== curry_min) - res += 'v' + (curry_max - curry_min); - if (curry !== curry_max) - res += 'v' + (curry - curry_max); + // when several points at same X differs, need complete logic + if (draw_hist && ((curry_min !== curry_max) || (prevy !== curry_min))) { + if (prevx !== currx) + res += 'h' + (currx - prevx); + + if (curry === curry_min) { + if (curry_max !== prevy) + res += 'v' + (curry_max - prevy); + if (curry_min !== curry_max) + res += 'v' + (curry_min - curry_max); + } else { + if (curry_min !== prevy) + res += 'v' + (curry_min - prevy); + if (curry_max !== curry_min) + res += 'v' + (curry_max - curry_min); + if (curry !== curry_max) + res += 'v' + (curry - curry_max); + } + + prevx = currx; + prevy = curry; } - prevx = currx; - prevy = curry; - } + if (lastbin && (prevx !== grx)) + res += 'h' + (grx - prevx); - if (lastbin && (prevx !== grx)) - res += 'h' + (grx-prevx); - - bestimin = bestimax = i; - curry_min = curry_max = curry = gry; + bestimin = bestimax = i; + curry_min = curry_max = curry = gry; + currx = grx; + } + // end of use_minmax + } else if ((gry !== curry) || lastbin) { + if (grx !== currx) + res += `h${grx - currx}`; + if (gry !== curry) + res += `v${gry - curry}`; + curry = gry; currx = grx; } - // end of use_minmax - } else if ((gry !== curry) || lastbin) { - if (grx !== currx) res += `h${grx-currx}`; - if (gry !== curry) res += `v${gry-curry}`; - curry = gry; - currx = grx; } - } - const fill_for_interactive = want_tooltip && this.fillatt.empty() && draw_hist && !draw_markers && !show_line && !show_curve, - add_hist = () => { - this.draw_g.append('svg:path') - .attr('d', res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) - .style('stroke-linejoin', 'miter') - .call(this.lineatt.func) - .call(this.fillatt.func); - }; - let h0 = height + 3; - if (!fill_for_interactive) { - const gry0 = Math.round(funcs.gry(0)); - if (gry0 <= 0) - h0 = -3; - else if (gry0 < height) - h0 = gry0; - } - const close_path = `L${currx},${h0}H${startx}Z`; - - if (res && draw_hist && !this.fillatt.empty()) { - add_hist(); - res = ''; - } - - if (draw_markers || show_line || show_curve) { - if (!path_line && grpnts.length) - path_line = buildSvgCurve(grpnts); - - if (path_fill) { - this.draw_g.append('svg:path') - .attr('d', path_fill) - .call(this.fillatt.func); - } else if (path_line && !this.fillatt.empty() && !draw_hist) { - this.draw_g.append('svg:path') - .attr('d', path_line + `L${midx},${h0}H${startmidx}Z`) + const fill_for_interactive = want_tooltip && this.fillatt.empty() && draw_hist && !draw_markers && !show_line && !show_curve && this.isUseFrame(); + let h0 = height + 3; + if (!fill_for_interactive) { + const gry0 = Math.round(funcs.gry(0)); + if (gry0 <= 0) + h0 = -3; + else if (gry0 < height) + h0 = gry0; + } + const close_path = `L${currx},${h0}H${startx}Z`, add_hist = () => { + this.appendPath(res + ((!this.fillatt.empty() || fill_for_interactive) ? close_path : '')) + .style('stroke-linejoin', 'miter') + .call(this.lineatt.func) .call(this.fillatt.func); + }; + + if (res && draw_hist && !this.fillatt.empty()) { + add_hist(); + res = ''; } - if (path_err) { - this.draw_g.append('svg:path') - .attr('d', path_err) + if (draw_markers || show_line || show_curve) { + if (!path_line && grpnts.length) { + if (funcs.swap_xy()) + grpnts.forEach(pnt => { [pnt.grx, pnt.gry] = [pnt.gry, pnt.grx]; }); + path_line = buildSvgCurve(grpnts); + } + + if (path_fill) { + this.appendPath(path_fill) + .call(this.fillatt.func); + } else if (path_line && !this.fillatt.empty() && !draw_hist) { + this.appendPath(path_line + `L${midx},${h0}H${startmidx}Z`) + .call(this.fillatt.func); + } + + if (path_err) { + this.appendPath(path_err) .call(this.lineatt.func); - } + } - if (hints_err) { - this.draw_g.append('svg:path') - .attr('d', hints_err) + if (hints_err) { + this.appendPath(hints_err) .style('fill', 'none') .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); - } + } - if (path_line) { - this.draw_g.append('svg:path') - .attr('d', path_line) - .style('fill', 'none') - .call(this.lineatt.func); - } + if (path_line) { + this.appendPath(path_line) + .style('fill', 'none') + .call(this.lineatt.func); + } - if (path_marker) { - this.draw_g.append('svg:path') - .attr('d', path_marker) - .call(this.markeratt.func); + if (path_marker) { + this.appendPath(path_marker) + .call(this.markeratt.func); + } + + if (hints_marker) { + this.appendPath(hints_marker) + .style('fill', 'none') + .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); + } } - if (hints_marker) { - this.draw_g.append('svg:path') - .attr('d', hints_marker) + if (res && draw_hist) + add_hist(); + + if (hints_text) { + this.appendPath(hints_text) .style('fill', 'none') .style('pointer-events', this.isBatchMode() ? null : 'visibleFill'); } - } - - if (res && draw_hist) - add_hist(); - if (show_text) - return this.finishTextDrawing(); + if (show_text) + return this.finishTextDrawing(); + }); } /** @summary Draw TH1 bins in SVG element * @return Promise or scalar value */ draw1DBins() { + const o = this.getOptions(); + if (o.Same && !this.isUseFrame()) + this.getPadPainter().getFrameSvg().style('display', 'none'); + this.createHistDrawAttributes(); - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - width = pmain.getFrameWidth(), height = pmain.getFrameHeight(); + const funcs = this.getHistGrFuncs(), + width = funcs.getFrameWidth(), + height = funcs.getFrameHeight(); if (!this.draw_content || (width <= 0) || (height <= 0)) - return this.removeG(); + return this.removeG(); - this.createG(true); + this.createG(this.isUseFrame()); - if (this.options.Bar) { + if (o.Bar) { return this.drawBars(funcs, height).then(() => { - if (this.options.ErrorKind === 1) + if (o.ErrorKind === 1) return this.drawNormal(funcs, width, height); }); } - if ((this.options.ErrorKind === 3) || (this.options.ErrorKind === 4)) + if ((o.ErrorKind === 3) || (o.ErrorKind === 4)) return this.drawFilledErrors(funcs); return this.drawNormal(funcs, width, height); @@ -862,25 +973,32 @@ class TH1Painter extends THistPainter { getBinTooltips(bin) { const tips = [], name = this.getObjectHint(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + funcs = this.getHistGrFuncs(), histo = this.getHisto(), - x1 = histo.fXaxis.GetBinLowEdge(bin+1), - x2 = histo.fXaxis.GetBinLowEdge(bin+2), + o = this.getOptions(), + x1 = histo.fXaxis.GetBinLowEdge(bin + 1), + x2 = histo.fXaxis.GetBinLowEdge(bin + 2), xlbl = this.getAxisBinTip('x', histo.fXaxis, bin); - let cont = histo.getBinContent(bin+1); + let cont = histo.getBinContent(bin + 1); - if (name) tips.push(name); + if (name) + tips.push(name); - if (this.options.Error || this.options.Mark || this.isTF1()) { + if (o.Error || o.Mark || this.isTF1()) { tips.push(`x = ${xlbl}`, `y = ${funcs.axisAsText('y', cont)}`); - if (this.options.Error) { - if (xlbl[0] === '[') tips.push(`error x = ${((x2 - x1) / 2).toPrecision(4)}`); - tips.push(`error y = ${histo.getBinError(bin + 1).toPrecision(4)}`); + if (o.Error) { + if (xlbl[0] === '[') + tips.push(`error x = ${((x2 - x1) / 2).toPrecision(4)}`); + const errs = this.getBinErrors(histo, bin + 1, cont); + if (errs.poisson) + tips.push(`error low = ${errs.low.toPrecision(4)}`, `error up = ${errs.up.toPrecision(4)}`); + else + tips.push(`error y = ${errs.up.toPrecision(4)}`); } } else { - tips.push(`bin = ${bin+1}`, `x = ${xlbl}`); - if (histo.$baseh) cont -= histo.$baseh.getBinContent(bin+1); + tips.push(`bin = ${bin + 1}`, `x = ${xlbl}`); + if (histo.$baseh) + cont -= histo.$baseh.getBinContent(bin + 1); if (cont === Math.round(cont)) tips.push(`entries = ${cont}`); else @@ -892,55 +1010,68 @@ class TH1Painter extends THistPainter { /** @summary Process tooltip event */ processTooltipEvent(pnt) { - if (!pnt || !this.draw_content || !this.draw_g || this.options.Mode3D) { - this.draw_g?.selectChild('.tooltip_bin').remove(); + const o = this.getOptions(); + if (!pnt || !this.draw_content || !this.getG() || o.Mode3D) { + this.getG()?.selectChild('.tooltip_bin').remove(); return null; } - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + const funcs = this.getHistGrFuncs(), histo = this.getHisto(), left = this.getSelectIndex('x', 'left', -1), - right = this.getSelectIndex('x', 'right', 2); - let width = pmain.getFrameWidth(), - height = pmain.getFrameHeight(), - findbin = null, show_rect, - grx1, grx2, gry1, gry2, gapx = 2, + right = this.getSelectIndex('x', 'right', 2), + draw_hist = this.options.Hist && (!this.lineatt.empty() || !this.fillatt.empty()); + let width = funcs.getFrameWidth(), + height = funcs.getFrameHeight(), + show_rect, grx1, grx2, gry1, gry2, gapx = 2, l = left, r = right, pnt_x = pnt.x, pnt_y = pnt.y; const GetBinGrX = i => { - const xx = histo.fXaxis.GetBinLowEdge(i+1); + const xx = histo.fXaxis.GetBinLowEdge(i + 1); return (funcs.logx && (xx <= 0)) ? null : funcs.grx(xx); }, GetBinGrY = i => { const yy = histo.getBinContent(i + 1); if (funcs.logy && (yy < funcs.scale_ymin)) - return funcs.swap_xy ? -1000 : 10*height; + return funcs.swap_xy() ? -1000 : 10 * height; return Math.round(funcs.gry(yy)); }; - if (funcs.swap_xy) + if (funcs.swap_xy()) [pnt_x, pnt_y, width, height] = [pnt_y, pnt_x, height, width]; - const descent_order = funcs.swap_xy !== pmain.x_handle.reverse; + const descent_order = funcs.x_handle && (funcs.swap_xy() !== funcs.x_handle.reverse); - while (l < r-1) { - const m = Math.round((l+r)*0.5), xx = GetBinGrX(m); - if ((xx === null) || (xx < pnt_x - 0.5)) - if (descent_order) r = m; else l = m; - else if (xx > pnt_x + 0.5) - if (descent_order) l = m; else r = m; - else { l++; r--; } + while (l < r - 1) { + const m = Math.round((l + r) * 0.5), xx = GetBinGrX(m); + if ((xx === null) || (xx < pnt_x - 0.5)) { + if (descent_order) + r = m; + else + l = m; + } else if (xx > pnt_x + 0.5) { + if (descent_order) + l = m; + else + r = m; + } else { + l++; + r--; + } } - findbin = r = l; + let findbin = r = l; grx1 = GetBinGrX(findbin); if (descent_order) { - while ((l > left) && (GetBinGrX(l-1) < grx1 + 2)) --l; - while ((r < right) && (GetBinGrX(r+1) > grx1 - 2)) ++r; + while ((l > left) && (GetBinGrX(l - 1) < grx1 + 2)) + --l; + while ((r < right) && (GetBinGrX(r + 1) > grx1 - 2)) + ++r; } else { - while ((l > left) && (GetBinGrX(l-1) > grx1 - 2)) --l; - while ((r < right) && (GetBinGrX(r+1) < grx1 + 2)) ++r; + while ((l > left) && (GetBinGrX(l - 1) > grx1 - 2)) + --l; + while ((r < right) && (GetBinGrX(r + 1) < grx1 + 2)) + ++r; } if (l < r) { @@ -949,20 +1080,23 @@ class TH1Painter extends THistPainter { let best = height; for (let m = l; m <= r; m++) { const dist = Math.abs(GetBinGrY(m) - pnt_y); - if (dist < best) { best = dist; findbin = m; } + if (dist < best) { + best = dist; + findbin = m; + } } // if best distance still too far from mouse position, just take from between - if (best > height/10) - findbin = Math.round(l + (r-l) / height * pnt_y); + if (best > height / 10) + findbin = Math.round(l + (r - l) / height * pnt_y); grx1 = GetBinGrX(findbin); } grx1 = Math.round(grx1); - grx2 = Math.round(GetBinGrX(findbin+1)); + grx2 = Math.round(GetBinGrX(findbin + 1)); - if (this.options.Bar) { + if (o.Bar) { const w = grx2 - grx1; grx1 += Math.round(histo.fBarOffset / 1000 * w); grx2 = grx1 + Math.round(histo.fBarWidth / 1000 * w); @@ -972,52 +1106,55 @@ class TH1Painter extends THistPainter { [grx1, grx2] = [grx2, grx1]; const midx = Math.round((grx1 + grx2) / 2), - midy = gry1 = gry2 = GetBinGrY(findbin); + midy = gry1 = gry2 = GetBinGrY(findbin); - if (this.options.Bar) { + if (o.Bar) { show_rect = true; - gapx = 0; - - gry1 = Math.round(funcs.gry(((this.options.BaseLine !== false) && (this.options.BaseLine > funcs.scale_ymin)) ? this.options.BaseLine : funcs.scale_ymin)); - + gry1 = this.getBarBaseline(funcs, height); if (gry1 > gry2) [gry1, gry2] = [gry2, gry1]; - - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; - } else if ((this.options.Error && (this.options.Hist !== true)) || this.options.Mark || this.options.Line || this.options.Curve) { + if (!pnt.touch && (pnt.nproc === 1) && ((pnt_y < gry1) || (pnt_y > gry2))) + findbin = null; + } else if ((o.Error && (o.Hist !== true)) || o.Mark || o.Line || o.Curve || (o.Text && !draw_hist)) { show_rect = !this.isTF1(); let msize = 3; - if (this.markeratt) msize = Math.max(msize, this.markeratt.getFullSize()); + if (this.markeratt) + msize = Math.max(msize, this.markeratt.getFullSize()); - if (this.options.Error) { - const cont = histo.getBinContent(findbin+1), - binerr = histo.getBinError(findbin+1); + if (o.Error) { + const cont = histo.getBinContent(findbin + 1), + binerrs = this.getBinErrors(histo, findbin + 1, cont); - gry1 = Math.round(funcs.gry(cont + binerr)); // up - gry2 = Math.round(funcs.gry(cont - binerr)); // down + gry1 = Math.round(funcs.gry(cont + binerrs.up)); // up + gry2 = Math.round(funcs.gry(cont - binerrs.low)); // low - if ((cont === 0) && this.isTProfile()) findbin = null; + if ((cont === 0) && this.isTProfile()) + findbin = null; - const dx = (grx2-grx1)*this.options.errorX; + const dx = (grx2 - grx1) * o.errorX; grx1 = Math.round(midx - dx); grx2 = Math.round(midx + dx); } // show at least 6 pixels as tooltip rect - if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; } + if (grx2 - grx1 < 2 * msize) { + grx1 = midx - msize; + grx2 = midx + msize; + } gry1 = Math.min(gry1, midy - msize); gry2 = Math.max(gry2, midy + msize); - if (!pnt.touch && (pnt.nproc === 1)) - if ((pnt_y < gry1) || (pnt_y > gry2)) findbin = null; + if (!pnt.touch && (pnt.nproc === 1)) { + if ((pnt_y < gry1) || (pnt_y > gry2)) + findbin = null; + } } else { // if histogram alone, use old-style with rects // if there are too many points at pixel, use circle - show_rect = (pnt.nproc === 1) && (right-left < width); + show_rect = (pnt.nproc === 1) && (right - left < width); if (show_rect) { gry2 = height; @@ -1025,11 +1162,12 @@ class TH1Painter extends THistPainter { if (!this.fillatt.empty()) { gry2 = Math.min(height, Math.max(0, Math.round(funcs.gry(0)))); if (gry2 < gry1) - [gry1, gry2] = [gry2, gry1]; + [gry1, gry2] = [gry2, gry1]; } // for mouse events pointer should be between y1 and y2 - if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) findbin = null; + if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) + findbin = null; } } @@ -1037,26 +1175,28 @@ class TH1Painter extends THistPainter { // if bin on boundary found, check that x position is ok if ((findbin === left) && (grx1 > pnt_x + gapx)) findbin = null; - else if ((findbin === right-1) && (grx2 < pnt_x - gapx)) + else if ((findbin === right - 1) && (grx2 < pnt_x - gapx)) findbin = null; else if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) findbin = null; // if bars option used check that bar is not match - else if (!this.options.Zero && (histo.getBinContent(findbin+1) === 0)) + else if (!o.Zero && (histo.getBinContent(findbin + 1) === 0) && (histo.getBinError(findbin + 1) === 0)) findbin = null; // exclude empty bin if empty bins suppressed } - let ttrect = this.draw_g.selectChild('.tooltip_bin'); + let ttrect = this.getG().selectChild('.tooltip_bin'); if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) { ttrect.remove(); return null; } - const res = { name: this.getObjectName(), title: histo.fTitle, - x: midx, y: midy, exact: true, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(findbin) }; + const res = { + name: this.getObjectName(), title: histo.fTitle, + x: midx, y: midy, exact: true, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getBinTooltips(findbin) + }; if (pnt.disabled) { // case when tooltip should not highlight bin @@ -1064,7 +1204,7 @@ class TH1Painter extends THistPainter { res.changed = true; } else if (show_rect) { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:rect') + ttrect = this.getG().append('svg:rect') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .call(addHighlightStyle); @@ -1073,10 +1213,10 @@ class TH1Painter extends THistPainter { res.changed = ttrect.property('current_bin') !== findbin; if (res.changed) { - ttrect.attr('x', funcs.swap_xy ? gry1 : grx1) - .attr('width', funcs.swap_xy ? gry2-gry1 : grx2-grx1) - .attr('y', funcs.swap_xy ? grx1 : gry1) - .attr('height', funcs.swap_xy ? grx2-grx1 : gry2-gry1) + ttrect.attr('x', funcs.swap_xy() ? gry1 : grx1) + .attr('width', funcs.swap_xy() ? gry2 - gry1 : grx2 - grx1) + .attr('y', funcs.swap_xy() ? grx1 : gry1) + .attr('height', funcs.swap_xy() ? grx2 - grx1 : gry2 - gry1) .style('opacity', '0.3') .property('current_bin', findbin); } @@ -1085,12 +1225,12 @@ class TH1Painter extends THistPainter { res.menu = res.exact; // one could show context menu when histogram is selected // distance to middle point, use to decide which menu to activate - res.menu_dist = Math.sqrt((midx-pnt_x)**2 + (midy-pnt_y)**2); + res.menu_dist = Math.sqrt((midx - pnt_x) ** 2 + (midy - pnt_y) ** 2); } else { const radius = this.lineatt.width + 3; if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:circle') + ttrect = this.getG().append('svg:circle') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .attr('r', radius) @@ -1101,7 +1241,7 @@ class TH1Painter extends THistPainter { res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius); res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram - res.menu_dist = Math.sqrt((midx-pnt.x)**2 + (midy-pnt.y)**2); + res.menu_dist = Math.sqrt((midx - pnt.x) ** 2 + (midy - pnt.y) ** 2); res.changed = ttrect.property('current_bin') !== findbin; @@ -1113,9 +1253,11 @@ class TH1Painter extends THistPainter { } if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: findbin, cont: histo.getBinContent(findbin+1), - grx: midx, gry: midy }; + res.user_info = { + obj: histo, name: histo.fName, + bin: findbin, cont: histo.getBinContent(findbin + 1), + grx: midx, gry: midy + }; } return res; @@ -1133,14 +1275,14 @@ class TH1Painter extends THistPainter { this.decodeOptions(arg); - if (this.options.need_fillcol && this.fillatt?.empty()) + if (this.getOptions().need_fillcol && this.fillatt?.empty()) this.fillatt.change(5, 1001); // redraw all objects in pad, inform dependent objects this.interactiveRedraw('pad', 'drawopt'); }); - if (!this.snapid && !this.isTProfile() && !this.isTF1()) + if (!this.hasSnapId() && !this.isTProfile() && !this.isTF1()) menu.addRebinMenu(sz => this.rebinHist(sz)); } @@ -1148,26 +1290,28 @@ class TH1Painter extends THistPainter { rebinHist(sz) { const histo = this.getHisto(), xaxis = histo.fXaxis, - nbins = Math.floor(xaxis.fNbins/ sz); - if (nbins < 2) return; + nbins = Math.floor(xaxis.fNbins / sz); + if (nbins < 2) + return; - const arr = new Array(nbins+2), - xbins = (xaxis.fXbins.length > 0) ? new Array(nbins) : null; + const arr = new Array(nbins + 2), + xbins = xaxis.fXbins.length ? new Array(nbins) : null; arr[0] = histo.fArray[0]; let indx = 1; for (let i = 1; i <= nbins; ++i) { - if (xbins) xbins[i-1] = xaxis.fXbins[indx-1]; + if (xbins) + xbins[i - 1] = xaxis.fXbins[indx - 1]; let sum = 0; for (let k = 0; k < sz; ++k) - sum += histo.fArray[indx++]; + sum += histo.fArray[indx++]; arr[i] = sum; } if (xbins) { if (indx <= xaxis.fXbins.length) - xaxis.fXmax = xaxis.fXbins[indx-1]; + xaxis.fXmax = xaxis.fXbins[indx - 1]; xaxis.fXbins = xbins; } else xaxis.fXmax = xaxis.fXmin + (xaxis.fXmax - xaxis.fXmin) / xaxis.fNbins * nbins * sz; @@ -1178,7 +1322,7 @@ class TH1Painter extends THistPainter { let overflow = 0; while (indx < histo.fArray.length) overflow += histo.fArray[indx++]; - arr[nbins+1] = overflow; + arr[nbins + 1] = overflow; histo.fArray = arr; histo.fSumw2 = []; @@ -1195,54 +1339,47 @@ class TH1Painter extends THistPainter { const dist = right - left, histo = this.getHisto(); - if ((dist === 0) || !histo) return; + if ((dist === 0) || !histo) + return; // first find minimum let min = histo.getBinContent(left + 1); for (let indx = left; indx < right; ++indx) - min = Math.min(min, histo.getBinContent(indx+1)); - if (min > 0) return; // if all points positive, no chance for autoscale + min = Math.min(min, histo.getBinContent(indx + 1)); + if (min > 0) + return; // if all points positive, no chance for auto-scale - while ((left < right) && (histo.getBinContent(left+1) <= min)) ++left; + while ((left < right) && (histo.getBinContent(left + 1) <= min)) ++left; while ((left < right) && (histo.getBinContent(right) <= min)) --right; // if singular bin - if ((left === right-1) && (left > 2) && (right < this.nbinsx-2)) { + if ((left === right - 1) && (left > 2) && (right < this.nbinsx - 2)) { --left; ++right; } if ((right - left < dist) && (left < right)) - return this.getFramePainter().zoom(histo.fXaxis.GetBinLowEdge(left+1), histo.fXaxis.GetBinLowEdge(right+1)); + return this.getFramePainter().zoom(histo.fXaxis.GetBinLowEdge(left + 1), histo.fXaxis.GetBinLowEdge(right + 1)); } /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis, min, max) { const histo = this.getHisto(); - if ((axis === 'x') && histo && (histo.fXaxis.FindBin(max, 0.5) - histo.fXaxis.FindBin(min, 0) > 1)) return true; + if ((axis === 'x') && histo && (histo.fXaxis.FindBin(max, 0.5) - histo.fXaxis.FindBin(min, 0) > 1)) + return true; - if ((axis === 'y') && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true; + if ((axis === 'y') && (Math.abs(max - min) > Math.abs(this.ymax - this.ymin) * 1e-6)) + return true; return false; } - /** @summary Call drawing function depending from 3D mode */ - async callDrawFunc(reason) { - const main = this.getMainPainter(), - fp = this.getFramePainter(); - - if ((main !== this) && fp && (fp.mode3d !== this.options.Mode3D)) - this.copyOptionsFrom(main); - - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); - } - /** @summary Performs 2D drawing of histogram * @return {Promise} when ready */ - async draw2D(/* reason */) { + async draw2D(reason) { this.clear3DScene(); - this.scanContent(true); + this.scanContent(reason === 'zoom'); const pr = this.isMainPainter() ? this.drawColorPalette(false) : Promise.resolve(true); @@ -1251,8 +1388,8 @@ class TH1Painter extends THistPainter { .then(() => this.updateFunctions()) .then(() => this.updateHistTitle()) .then(() => { - this.updateStatWebCanvas(); - return this.addInteractivity(); + this.updateStatWebCanvas(); + return this.addInteractivity(); }); } @@ -1264,12 +1401,35 @@ class TH1Painter extends THistPainter { return this.draw2D(reason); } + /** @summary Call drawing function depending from 3D mode */ + async callDrawFunc(reason) { + const main = this.getMainPainter(), + fp = this.getFramePainter(), + o = this.getOptions(); + + if ((main !== this) && fp && (fp.mode3d !== o.Mode3D)) + this.copyOptionsFrom(main); + + if (!o.Mode3D) + return this.draw2D(reason); + + return this.draw3D(reason).catch(err => { + const cp = this.getCanvPainter(); + if (isFunc(cp?.showConsoleError)) + cp.showConsoleError(err); + else + console.error('Fail to draw histogram in 3D - back to 2D'); + o.Mode3D = false; + return this.draw2D(reason); + }); + } + /** @summary Redraw histogram */ redraw(reason) { return this.callDrawFunc(reason); } - /** @summary draw TH1 object */ + /** @summary draw TH1 object in 2D only */ static async draw(dom, histo, opt) { return THistPainter._drawHist(new TH1Painter(dom, histo), opt); } diff --git a/modules/hist2d/TH2Painter.mjs b/modules/hist2d/TH2Painter.mjs index 6b364e358..6c3420236 100644 --- a/modules/hist2d/TH2Painter.mjs +++ b/modules/hist2d/TH2Painter.mjs @@ -1,10 +1,11 @@ -import { gStyle, createHistogram, createTPolyLine, isFunc, isStr, +import { gStyle, settings, createHistogram, createTPolyLine, isFunc, isStr, clTMultiGraph, clTH1D, clTF2, clTProfile2D, kInspect } from '../core.mjs'; -import { rgb as d3_rgb, chord as d3_chord, arc as d3_arc, ribbon as d3_ribbon } from '../d3.mjs'; +import { pointer as d3_pointer, rgb as d3_rgb, chord as d3_chord, arc as d3_arc, ribbon as d3_ribbon } from '../d3.mjs'; import { kBlack } from '../base/colors.mjs'; -import { TRandom, floatToString, makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; +import { TRandom, floatToString, makeTranslate, addHighlightStyle, getBoxDecorations } from '../base/BasePainter.mjs'; import { EAxisBits } from '../base/ObjectPainter.mjs'; -import { THistPainter } from './THistPainter.mjs'; +import { THistPainter, kPOLAR } from './THistPainter.mjs'; +import { assignContextMenu } from '../gui/menu.mjs'; /** @summary Build histogram contour lines @@ -13,9 +14,9 @@ function buildHist2dContour(histo, handle, levels, palette, contour_func) { const kMAXCONTOUR = 2004, kMAXCOUNT = 2000, // arguments used in the PaintContourLine - xarr = new Float32Array(2*kMAXCONTOUR), - yarr = new Float32Array(2*kMAXCONTOUR), - itarr = new Int32Array(2*kMAXCONTOUR), + xarr = new Float32Array(2 * kMAXCONTOUR), + yarr = new Float32Array(2 * kMAXCONTOUR), + itarr = new Int32Array(2 * kMAXCONTOUR), nlevels = levels.length, first_level = levels[0], last_level = levels[nlevels - 1], polys = [], @@ -23,51 +24,49 @@ function buildHist2dContour(histo, handle, levels, palette, contour_func) { arrx = handle.grx, arry = handle.gry; - let lj = 0, ipoly, poly, np, npmax = 0, - i, j, k, n, m, ljfill, count, - xsave, ysave, itars, ix, jx; + let lj = 0; - const LinearSearch = zc => { - if (zc >= last_level) - return nlevels-1; + const LinearSearch = zvalue => { + if (zvalue >= last_level) + return nlevels - 1; for (let kk = 0; kk < nlevels; ++kk) { - if (zc < levels[kk]) - return kk-1; - } - return nlevels-1; - }, BinarySearch = zc => { - if (zc < first_level) + if (zvalue < levels[kk]) + return kk - 1; + } + return nlevels - 1; + }, BinarySearch = zvalue => { + if (zvalue < first_level) return -1; - if (zc >= last_level) + if (zvalue >= last_level) return nlevels - 1; let l = 0, r = nlevels - 1, m; while (r - l > 1) { - m = Math.round((r + l) / 2); - if (zc < levels[m]) - r = m; - else - l = m; + m = Math.round((r + l) / 2); + if (zvalue < levels[m]) + r = m; + else + l = m; } return l; - }, - LevelSearch = nlevels < 10 ? LinearSearch : BinarySearch, - PaintContourLine = (elev1, icont1, x1, y1, elev2, icont2, x2, y2) => { + }, LevelSearch = nlevels < 10 ? LinearSearch : BinarySearch; + /* eslint-disable-next-line one-var */ + const PaintContourLine = (elev1, icont1, x1, y1, elev2, icont2, x2, y2) => { /* Double_t *xarr, Double_t *yarr, Int_t *itarr, Double_t *levels */ const vert = (x1 === x2), tlen = vert ? (y2 - y1) : (x2 - x1), tdif = elev2 - elev1; - let n = icont1 + 1, ii = lj-1, icount = 0, + let n = icont1 + 1, ii = lj - 1, icount = 0, xlen, pdif, diff, elev; - const maxii = ii + kMAXCONTOUR/2 -3; + const maxii = ii + kMAXCONTOUR / 2 - 3; while (n <= icont2 && ii <= maxii) { // elev = fH->GetContourLevel(n); elev = levels[n]; diff = elev - elev1; - pdif = diff/tdif; - xlen = tlen*pdif; + pdif = diff / tdif; + xlen = tlen * pdif; if (vert) { xarr[ii] = x1; yarr[ii] = y1 + xlen; @@ -83,44 +82,50 @@ function buildHist2dContour(histo, handle, levels, palette, contour_func) { return icount; }; - for (j = handle.j1; j < handle.j2-1; ++j) { - y[1] = y[0] = (arry[j] + arry[j+1])/2; - y[3] = y[2] = (arry[j+1] + arry[j+2])/2; + let ipoly, poly, npmax = 0, + i, j, k, m, n, ljfill, count, + xsave, ysave, itars, ix, jx; + + for (j = handle.j1; j < handle.j2 - 1; ++j) { + y[1] = y[0] = (arry[j] + arry[j + 1]) / 2; + y[3] = y[2] = (arry[j + 1] + arry[j + 2]) / 2; - for (i = handle.i1; i < handle.i2-1; ++i) { - zc[0] = histo.getBinContent(i+1, j+1); - zc[1] = histo.getBinContent(i+2, j+1); - zc[2] = histo.getBinContent(i+2, j+2); - zc[3] = histo.getBinContent(i+1, j+2); + for (i = handle.i1; i < handle.i2 - 1; ++i) { + zc[0] = histo.getBinContent(i + 1, j + 1); + zc[1] = histo.getBinContent(i + 2, j + 1); + zc[2] = histo.getBinContent(i + 2, j + 2); + zc[3] = histo.getBinContent(i + 1, j + 2); for (k = 0; k < 4; k++) ir[k] = LevelSearch(zc[k]); - if ((ir[0] !== ir[1]) || (ir[1] !== ir[2]) || (ir[2] !== ir[3]) || (ir[3] !== ir[0])) { // deepscan-disable-line - x[3] = x[0] = (arrx[i] + arrx[i+1])/2; - x[2] = x[1] = (arrx[i+1] + arrx[i+2])/2; + if ((ir[0] !== ir[1]) || (ir[1] !== ir[2]) || (ir[2] !== ir[3]) || (ir[3] !== ir[0])) { + x[3] = x[0] = (arrx[i] + arrx[i + 1]) / 2; + x[2] = x[1] = (arrx[i + 1] + arrx[i + 2]) / 2; - if (zc[0] <= zc[1]) n = 0; else n = 1; - if (zc[2] <= zc[3]) m = 2; else m = 3; - if (zc[n] > zc[m]) n = m; + n = zc[0] <= zc[1] ? 0 : 1; + m = zc[2] <= zc[3] ? 2 : 3; + if (zc[n] > zc[m]) + n = m; n++; - lj=1; - for (ix=1; ix<=4; ix++) { - m = n%4 + 1; - ljfill = PaintContourLine(zc[n-1], ir[n-1], x[n-1], y[n-1], zc[m-1], ir[m-1], x[m-1], y[m-1]); - lj += 2*ljfill; + lj = 1; + for (ix = 1; ix <= 4; ix++) { + m = n % 4 + 1; + ljfill = PaintContourLine(zc[n - 1], ir[n - 1], x[n - 1], y[n - 1], zc[m - 1], ir[m - 1], x[m - 1], y[m - 1]); + lj += 2 * ljfill; n = m; } - if (zc[0] <= zc[1]) n = 0; else n = 1; - if (zc[2] <= zc[3]) m = 2; else m = 3; - if (zc[n] > zc[m]) n = m; + n = zc[0] <= zc[1] ? 0 : 1; + m = zc[2] <= zc[3] ? 2 : 3; + if (zc[n] > zc[m]) + n = m; n++; - lj=2; - for (ix=1; ix<=4; ix++) { - m = (n === 1) ? 4 : n-1; - ljfill = PaintContourLine(zc[n-1], ir[n-1], x[n-1], y[n-1], zc[m-1], ir[m-1], x[m-1], y[m-1]); - lj += 2*ljfill; + lj = 2; + for (ix = 1; ix <= 4; ix++) { + m = (n === 1) ? 4 : n - 1; + ljfill = PaintContourLine(zc[n - 1], ir[n - 1], x[n - 1], y[n - 1], zc[m - 1], ir[m - 1], x[m - 1], y[m - 1]); + lj += 2 * ljfill; n = m; } // Re-order endpoints @@ -128,41 +133,43 @@ function buildHist2dContour(histo, handle, levels, palette, contour_func) { count = 0; for (ix = 1; ix <= lj - 5; ix += 2) { // count = 0; - while (itarr[ix-1] !== itarr[ix]) { + while (itarr[ix - 1] !== itarr[ix]) { xsave = xarr[ix]; ysave = yarr[ix]; itars = itarr[ix]; - for (jx=ix; jx<=lj-5; jx +=2) { - xarr[jx] = xarr[jx+2]; - yarr[jx] = yarr[jx+2]; - itarr[jx] = itarr[jx+2]; + for (jx = ix; jx <= lj - 5; jx += 2) { + xarr[jx] = xarr[jx + 2]; + yarr[jx] = yarr[jx + 2]; + itarr[jx] = itarr[jx + 2]; } - xarr[lj-3] = xsave; - yarr[lj-3] = ysave; - itarr[lj-3] = itars; - if (count > kMAXCOUNT) break; + xarr[lj - 3] = xsave; + yarr[lj - 3] = ysave; + itarr[lj - 3] = itars; + if (count > kMAXCOUNT) + break; count++; } } - if (count > 100) continue; + if (count > 100) + continue; for (ix = 1; ix <= lj - 2; ix += 2) { - ipoly = itarr[ix-1]; + ipoly = itarr[ix - 1]; if ((ipoly >= 0) && (ipoly < levels.length)) { poly = polys[ipoly]; if (!poly) - poly = polys[ipoly] = createTPolyLine(kMAXCONTOUR*4, true); - - np = poly.fLastPoint; - if (np < poly.fN-2) { - poly.fX[np+1] = Math.round(xarr[ix-1]); - poly.fY[np+1] = Math.round(yarr[ix-1]); - poly.fX[np+2] = Math.round(xarr[ix]); - poly.fY[np+2] = Math.round(yarr[ix]); - poly.fLastPoint = np+2; - npmax = Math.max(npmax, poly.fLastPoint+1); + poly = polys[ipoly] = createTPolyLine(kMAXCONTOUR * 4, true); + + const np = poly.fLastPoint; + if (np < poly.fN - 2) { + poly.fX[np + 1] = Math.round(xarr[ix - 1]); + poly.fY[np + 1] = Math.round(yarr[ix - 1]); + poly.fX[np + 2] = Math.round(xarr[ix]); + poly.fY[np + 2] = Math.round(yarr[ix]); + poly.fLastPoint = np + 2; + npmax = Math.max(npmax, poly.fLastPoint + 1); } else { // console.log(`reject point ${poly.fLastPoint}`); } @@ -175,68 +182,81 @@ function buildHist2dContour(histo, handle, levels, palette, contour_func) { const polysort = new Int32Array(levels.length); let first = 0; // find first positive contour - for (ipoly = 0; ipoly < levels.length; ipoly++) - if (levels[ipoly] >= 0) { first = ipoly; break; } + for (ipoly = 0; ipoly < levels.length; ipoly++) { + if (levels[ipoly] >= 0) { + first = ipoly; + break; + } + } // store negative contours from 0 to minimum, then all positive contours k = 0; - for (ipoly = first-1; ipoly >= 0; ipoly--) { polysort[k] = ipoly; k++; } - for (ipoly = first; ipoly < levels.length; ipoly++) { polysort[k] = ipoly; k++; } + for (ipoly = first - 1; ipoly >= 0; ipoly--) + polysort[k++] = ipoly; + for (ipoly = first; ipoly < levels.length; ipoly++) + polysort[k++] = ipoly; - const xp = new Float32Array(2*npmax), - yp = new Float32Array(2*npmax), + const xp = new Float32Array(2 * npmax), + yp = new Float32Array(2 * npmax), has_func = isFunc(palette.calcColorIndex); // rcanvas for v7 for (k = 0; k < levels.length; ++k) { ipoly = polysort[k]; poly = polys[ipoly]; - if (!poly) continue; + if (!poly) + continue; const colindx = has_func ? palette.calcColorIndex(ipoly, levels.length) : ipoly, - xx = poly.fX, yy = poly.fY, np = poly.fLastPoint+1, + xx = poly.fX, yy = poly.fY, np2 = poly.fLastPoint + 1, xmin = 0, ymin = 0; let istart = 0, iminus, iplus, nadd; while (true) { iminus = npmax; - iplus = iminus+1; - xp[iminus]= xx[istart]; yp[iminus] = yy[istart]; - xp[iplus] = xx[istart+1]; yp[iplus] = yy[istart+1]; - xx[istart] = xx[istart+1] = xmin; - yy[istart] = yy[istart+1] = ymin; + iplus = iminus + 1; + xp[iminus] = xx[istart]; + yp[iminus] = yy[istart]; + xp[iplus] = xx[istart + 1]; + yp[iplus] = yy[istart + 1]; + xx[istart] = xx[istart + 1] = xmin; + yy[istart] = yy[istart + 1] = ymin; while (true) { nadd = 0; - for (i = 2; i < np; i += 2) { - if ((iplus < 2*npmax-1) && (xx[i] === xp[iplus]) && (yy[i] === yp[iplus])) { + for (i = 2; i < np2; i += 2) { + if ((iplus < 2 * npmax - 1) && (xx[i] === xp[iplus]) && (yy[i] === yp[iplus])) { iplus++; - xp[iplus] = xx[i+1]; yp[iplus] = yy[i+1]; - xx[i] = xx[i+1] = xmin; - yy[i] = yy[i+1] = ymin; + xp[iplus] = xx[i + 1]; + yp[iplus] = yy[i + 1]; + xx[i] = xx[i + 1] = xmin; + yy[i] = yy[i + 1] = ymin; nadd++; } - if ((iminus > 0) && (xx[i+1] === xp[iminus]) && (yy[i+1] === yp[iminus])) { + if ((iminus > 0) && (xx[i + 1] === xp[iminus]) && (yy[i + 1] === yp[iminus])) { iminus--; - xp[iminus] = xx[i]; yp[iminus] = yy[i]; - xx[i] = xx[i+1] = xmin; - yy[i] = yy[i+1] = ymin; + xp[iminus] = xx[i]; + yp[iminus] = yy[i]; + xx[i] = xx[i + 1] = xmin; + yy[i] = yy[i + 1] = ymin; nadd++; } } - if (nadd === 0) break; + if (nadd === 0) + break; } - if ((iminus+1 < iplus) && (iminus >= 0)) + if ((iminus + 1 < iplus) && (iminus >= 0)) contour_func(colindx, xp, yp, iminus, iplus, ipoly); istart = 0; - for (i = 2; i < np; i += 2) { + for (i = 2; i < np2; i += 2) { if (xx[i] !== xmin && yy[i] !== ymin) { istart = i; break; } } - if (istart === 0) break; + if (istart === 0) + break; } } } @@ -260,9 +280,9 @@ class Triangles3DHandler { this.loop = 0; const nfaces = [], posbuf = [], posbufindx = [], // buffers for faces - pntbuf = new Float32Array(6*3), // maximal 6 points - gridpnts = new Float32Array(2*3), - levels_eps = (levels[levels.length-1] - levels[0]) / levels.length / 1e2; + pntbuf = new Float32Array(6 * 3), // maximal 6 points + gridpnts = new Float32Array(2 * 3), + levels_eps = (levels.at(-1) - levels.at(0)) / levels.length / 1e2; let nsegments = 0, lpos = null, lindx = 0, // buffer for lines ngridsegments = 0, grid = null, gindx = 0, // buffer for grid lines segments normindx = [], // buffer to remember place of vertex for each bin @@ -273,13 +293,14 @@ class Triangles3DHandler { } this.createNormIndex = function(handle) { - // for each bin maximal 8 points reserved + // for each bin maximal 8 points reserved if (handle.donormals) - normindx = new Int32Array((handle.i2-handle.i1)*(handle.j2-handle.j1)*8).fill(-1); + normindx = new Int32Array((handle.i2 - handle.i1) * (handle.j2 - handle.j1) * 8).fill(-1); }; this.createBuffers = function() { - if (!this.loop) return; + if (!this.loop) + return; for (let lvl = 1; lvl < levels.length; ++lvl) { if (nfaces[lvl]) { @@ -294,29 +315,36 @@ class Triangles3DHandler { }; this.addLineSegment = function(x1, y1, z1, x2, y2, z2) { - if (!this.dolines) return; + if (!this.dolines) + return; const side1 = checkSide(z1, this.grz_min, this.grz_max, 0), - side2 = checkSide(z2, this.grz_min, this.grz_max, 0); - if ((side1 === side2) && (side1 !== 0)) + side2 = checkSide(z2, this.grz_min, this.grz_max, 0); + if ((side1 === side2) && side1) return; if (!this.loop) return ++nsegments; - if (side1 !== 0) { + if (side1) { const diff = z2 - z1; z1 = (side1 < 0) ? this.grz_min : this.grz_max; x1 = x2 - (x2 - x1) / diff * (z2 - z1); y1 = y2 - (y2 - y1) / diff * (z2 - z1); } - if (side2 !== 0) { + if (side2) { const diff = z1 - z2; z2 = (side2 < 0) ? this.grz_min : this.grz_max; x2 = x1 - (x1 - x2) / diff * (z1 - z2); y2 = y1 - (y1 - y2) / diff * (z1 - z2); } - lpos[lindx] = x1; lpos[lindx+1] = y1; lpos[lindx+2] = z1; lindx+=3; - lpos[lindx] = x2; lpos[lindx+1] = y2; lpos[lindx+2] = z2; lindx+=3; + lpos[lindx] = x1; + lpos[lindx + 1] = y1; + lpos[lindx + 2] = z1; + lindx += 3; + lpos[lindx] = x2; + lpos[lindx + 1] = y2; + lpos[lindx + 2] = z2; + lindx += 3; }; function addCrossingPoint(xx1, yy1, zz1, xx2, yy2, zz2, crossz, with_grid) { @@ -325,22 +353,23 @@ class Triangles3DHandler { const part = (crossz - zz1) / (zz2 - zz1); let shift = 3; - if ((lastpart !== 0) && (Math.abs(part) < Math.abs(lastpart))) { + if (lastpart && (Math.abs(part) < Math.abs(lastpart))) { // while second crossing point closer than first to original, move it in memory - pntbuf[pntindx] = pntbuf[pntindx-3]; - pntbuf[pntindx+1] = pntbuf[pntindx-2]; - pntbuf[pntindx+2] = pntbuf[pntindx-1]; - pntindx-=3; shift = 6; + pntbuf[pntindx] = pntbuf[pntindx - 3]; + pntbuf[pntindx + 1] = pntbuf[pntindx - 2]; + pntbuf[pntindx + 2] = pntbuf[pntindx - 1]; + pntindx -= 3; + shift = 6; } - pntbuf[pntindx] = xx1 + part*(xx2-xx1); - pntbuf[pntindx+1] = yy1 + part*(yy2-yy1); - pntbuf[pntindx+2] = crossz; + pntbuf[pntindx] = xx1 + part * (xx2 - xx1); + pntbuf[pntindx + 1] = yy1 + part * (yy2 - yy1); + pntbuf[pntindx + 2] = crossz; if (with_grid && grid) { gridpnts[gridcnt] = pntbuf[pntindx]; - gridpnts[gridcnt+1] = pntbuf[pntindx+1]; - gridpnts[gridcnt+2] = pntbuf[pntindx+2]; + gridpnts[gridcnt + 1] = pntbuf[pntindx + 1]; + gridpnts[gridcnt + 2] = pntbuf[pntindx + 2]; gridcnt += 3; } @@ -349,7 +378,7 @@ class Triangles3DHandler { } function rememberVertex(indx, handle, ii, jj) { - const bin = ((ii-handle.i1) * (handle.j2-handle.j1) + (jj-handle.j1))*8; + const bin = ((ii - handle.i1) * (handle.j2 - handle.j1) + (jj - handle.j1)) * 8; if (normindx[bin] >= 0) return console.error('More than 8 vertexes for the bin'); @@ -361,9 +390,9 @@ class Triangles3DHandler { this.addMainTriangle = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, is_first, handle, i, j) { for (let lvl = 1; lvl < levels.length; ++lvl) { - let side1 = checkSide(z1, levels[lvl-1], levels[lvl], levels_eps), - side2 = checkSide(z2, levels[lvl-1], levels[lvl], levels_eps), - side3 = checkSide(z3, levels[lvl-1], levels[lvl], levels_eps), + let side1 = checkSide(z1, levels[lvl - 1], levels[lvl], levels_eps), + side2 = checkSide(z2, levels[lvl - 1], levels[lvl], levels_eps), + side3 = checkSide(z3, levels[lvl - 1], levels[lvl], levels_eps), side_sum = side1 + side2 + side3; // always show top segments @@ -371,27 +400,33 @@ class Triangles3DHandler { side1 = side2 = side3 = side_sum = 0; - if (side_sum === 3) continue; - if (side_sum === -3) return; + if (side_sum === 3) + continue; + if (side_sum === -3) + return; if (!this.loop) { - let npnts = Math.abs(side2-side1) + Math.abs(side3-side2) + Math.abs(side1-side3); - if (side1 === 0) ++npnts; - if (side2 === 0) ++npnts; - if (side3 === 0) ++npnts; + let npnts = Math.abs(side2 - side1) + Math.abs(side3 - side2) + Math.abs(side1 - side3); + if (side1 === 0) + ++npnts; + if (side2 === 0) + ++npnts; + if (side3 === 0) + ++npnts; - if ((npnts === 1) || (npnts === 2)) console.error(`FOUND npnts = ${npnts}`); + if ((npnts === 1) || (npnts === 2)) + console.error(`FOUND npnts = ${npnts}`); if (npnts > 2) { if (nfaces[lvl] === undefined) nfaces[lvl] = 0; - nfaces[lvl] += npnts-2; + nfaces[lvl] += npnts - 2; } // check if any(contours for given level exists if (((side1 > 0) || (side2 > 0) || (side3 > 0)) && - ((side1 !== side2) || (side2 !== side3) || (side3 !== side1))) // deepscan-disable-line - ++ngridsegments; + ((side1 !== side2) || (side2 !== side3) || (side3 !== side1))) + ++ngridsegments; continue; } @@ -399,39 +434,64 @@ class Triangles3DHandler { gridcnt = 0; pntindx = 0; - if (side1 === 0) { pntbuf[pntindx] = x1; pntbuf[pntindx+1] = y1; pntbuf[pntindx+2] = z1; pntindx += 3; } + if (side1 === 0) { + pntbuf[pntindx] = x1; + pntbuf[pntindx + 1] = y1; + pntbuf[pntindx + 2] = z1; + pntindx += 3; + } if (side1 !== side2) { // order is important, should move from 1->2 point, checked via lastpart lastpart = 0; - if ((side1 < 0) || (side2 < 0)) addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl-1]); - if ((side1 > 0) || (side2 > 0)) addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl], true); + if ((side1 < 0) || (side2 < 0)) + addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl - 1]); + if ((side1 > 0) || (side2 > 0)) + addCrossingPoint(x1, y1, z1, x2, y2, z2, levels[lvl], true); } - if (side2 === 0) { pntbuf[pntindx] = x2; pntbuf[pntindx+1] = y2; pntbuf[pntindx+2] = z2; pntindx += 3; } + if (side2 === 0) { + pntbuf[pntindx] = x2; + pntbuf[pntindx + 1] = y2; + pntbuf[pntindx + 2] = z2; + pntindx += 3; + } if (side2 !== side3) { // order is important, should move from 2->3 point, checked via lastpart lastpart = 0; - if ((side2 < 0) || (side3 < 0)) addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl-1]); - if ((side2 > 0) || (side3 > 0)) addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl], true); + if ((side2 < 0) || (side3 < 0)) + addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl - 1]); + if ((side2 > 0) || (side3 > 0)) + addCrossingPoint(x2, y2, z2, x3, y3, z3, levels[lvl], true); } - if (side3 === 0) { pntbuf[pntindx] = x3; pntbuf[pntindx+1] = y3; pntbuf[pntindx+2] = z3; pntindx += 3; } + if (side3 === 0) { + pntbuf[pntindx] = x3; + pntbuf[pntindx + 1] = y3; + pntbuf[pntindx + 2] = z3; + pntindx += 3; + } if (side3 !== side1) { // order is important, should move from 3->1 point, checked via lastpart lastpart = 0; - if ((side3 < 0) || (side1 < 0)) addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl-1]); - if ((side3 > 0) || (side1 > 0)) addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl], true); + if ((side3 < 0) || (side1 < 0)) + addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl - 1]); + if ((side3 > 0) || (side1 > 0)) + addCrossingPoint(x3, y3, z3, x1, y1, z1, levels[lvl], true); } - if (pntindx === 0) continue; - if (pntindx < 9) { console.log(`found ${pntindx/3} points, must be at least 3`); continue; } + if (pntindx === 0) + continue; + if (pntindx < 9) { + console.log(`found ${pntindx / 3} points, must be at least 3`); + continue; + } if (grid && (gridcnt === 6)) { for (let jj = 0; jj < 6; ++jj) - grid[gindx+jj] = gridpnts[jj]; + grid[gindx + jj] = gridpnts[jj]; gindx += 6; } @@ -441,14 +501,23 @@ class Triangles3DHandler { let s = posbufindx[lvl]; if (this.donormals && (pntindx === 9)) { rememberVertex(s, handle, i, j); - rememberVertex(s+3, handle, i+1, is_first ? j+1 : j); - rememberVertex(s+6, handle, is_first ? i : i+1, j+1); + rememberVertex(s + 3, handle, i + 1, is_first ? j + 1 : j); + rememberVertex(s + 6, handle, is_first ? i : i + 1, j + 1); } for (let k1 = 3; k1 < pntindx - 3; k1 += 3) { - buf[s] = pntbuf[0]; buf[s+1] = pntbuf[1]; buf[s+2] = pntbuf[2]; s+=3; - buf[s] = pntbuf[k1]; buf[s+1] = pntbuf[k1+1]; buf[s+2] = pntbuf[k1+2]; s+=3; - buf[s] = pntbuf[k1+3]; buf[s+1] = pntbuf[k1+4]; buf[s+2] = pntbuf[k1+5]; s+=3; + buf[s] = pntbuf[0]; + buf[s + 1] = pntbuf[1]; + buf[s + 2] = pntbuf[2]; + s += 3; + buf[s] = pntbuf[k1]; + buf[s + 1] = pntbuf[k1 + 1]; + buf[s + 2] = pntbuf[k1 + 2]; + s += 3; + buf[s] = pntbuf[k1 + 3]; + buf[s + 1] = pntbuf[k1 + 4]; + buf[s + 2] = pntbuf[k1 + 5]; + s += 3; } posbufindx[lvl] = s; } @@ -461,30 +530,30 @@ class Triangles3DHandler { } if (lpos && linesFunc) { - if (nsegments*6 !== lindx) - console.error(`SURF lines mismmatch nsegm=${nsegments} lindx=${lindx} diff=${nsegments*6 - lindx}`); + if (nsegments * 6 !== lindx) + console.error(`SURF lines mismmatch nsegm=${nsegments} lindx=${lindx} diff=${nsegments * 6 - lindx}`); linesFunc(false, lpos); } if (grid && linesFunc) { - if (ngridsegments*6 !== gindx) - console.error(`SURF grid draw mismatch ngridsegm=${ngridsegments} gindx=${gindx} diff=${ngridsegments*6 - gindx}`); + if (ngridsegments * 6 !== gindx) + console.error(`SURF grid draw mismatch ngridsegm=${ngridsegments} gindx=${gindx} diff=${ngridsegments * 6 - gindx}`); linesFunc(true, grid); } }; - } + } -} +} // class Triangles3DHandler /** @summary Build 3d surface - * @desc Make it indepependent from three.js to be able reuse it for 2d case + * @desc Make it independent from three.js to be able reuse it for 2D case * @private */ function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { const main_grz = handle.grz, - arrx = handle.original ? handle.origx : handle.grx, - arry = handle.original ? handle.origy : handle.gry, - triangles = new Triangles3DHandler(ilevels, handle.grz, handle.grz_min, handle.grz_max, handle.dolines, handle.donormals, handle.dogrid); + arrx = handle.original ? handle.origx : handle.grx, + arry = handle.original ? handle.origy : handle.gry, + triangles = new Triangles3DHandler(ilevels, handle.grz, handle.grz_min, handle.grz_max, handle.dolines, handle.donormals, handle.dogrid); let i, j, x1, x2, y1, y2, z11, z12, z21, z22; triangles.createNormIndex(handle); @@ -492,17 +561,17 @@ function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { for (triangles.loop = 0; triangles.loop < 2; ++triangles.loop) { triangles.createBuffers(); - for (i = handle.i1; i < handle.i2-1; ++i) { - x1 = handle.original ? 0.5 * (arrx[i] + arrx[i+1]) : arrx[i]; - x2 = handle.original ? 0.5 * (arrx[i+1] + arrx[i+2]) : arrx[i+1]; - for (j = handle.j1; j < handle.j2-1; ++j) { - y1 = handle.original ? 0.5 * (arry[j] + arry[j+1]) : arry[j]; - y2 = handle.original ? 0.5 * (arry[j+1] + arry[j+2]) : arry[j+1]; + for (i = handle.i1; i < handle.i2 - 1; ++i) { + x1 = handle.original ? 0.5 * (arrx[i] + arrx[i + 1]) : arrx[i]; + x2 = handle.original ? 0.5 * (arrx[i + 1] + arrx[i + 2]) : arrx[i + 1]; + for (j = handle.j1; j < handle.j2 - 1; ++j) { + y1 = handle.original ? 0.5 * (arry[j] + arry[j + 1]) : arry[j]; + y2 = handle.original ? 0.5 * (arry[j + 1] + arry[j + 2]) : arry[j + 1]; - z11 = main_grz(histo.getBinContent(i+1, j+1)); - z12 = main_grz(histo.getBinContent(i+1, j+2)); - z21 = main_grz(histo.getBinContent(i+2, j+1)); - z22 = main_grz(histo.getBinContent(i+2, j+2)); + z11 = main_grz(histo.getBinContent(i + 1, j + 1)); + z12 = main_grz(histo.getBinContent(i + 1, j + 2)); + z21 = main_grz(histo.getBinContent(i + 2, j + 1)); + z22 = main_grz(histo.getBinContent(i + 2, j + 2)); triangles.addMainTriangle(x1, y1, z11, x2, y2, z22, x1, y2, z12, true, handle, i, j); @@ -511,8 +580,10 @@ function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { triangles.addLineSegment(x1, y2, z12, x1, y1, z11); triangles.addLineSegment(x1, y1, z11, x2, y1, z21); - if (i === handle.i2 - 2) triangles.addLineSegment(x2, y1, z21, x2, y2, z22); - if (j === handle.j2 - 2) triangles.addLineSegment(x1, y2, z12, x2, y2, z22); + if (i === handle.i2 - 2) + triangles.addLineSegment(x2, y1, z21, x2, y2, z22); + if (j === handle.j2 - 2) + triangles.addLineSegment(x1, y2, z12, x2, y2, z22); } } } @@ -528,26 +599,64 @@ function buildSurf3D(histo, handle, ilevels, meshFunc, linesFunc) { class TH2Painter extends THistPainter { - /** @summary constructor - * @param {object} histo - histogram object */ - constructor(dom, histo) { - super(dom, histo); - this.wheel_zoomy = true; - this._show_empty_bins = false; - } + #projection_kind; // kind of enabled histogram projection + #projection_widthX; // X width of projection + #projection_widthY; // Y width of projection + #can_move_colz; // temporary flag for readjust palette positions + #hide_frame; // hide frame when drawing + #chord; // zooming for chord drawing + + /** @summary Use in frame painter to check zoom Y is allowed + * @protected */ + get _wheel_zoomy() { return true; } /** @summary cleanup painter */ cleanup() { delete this.tt_handle; - + this.#chord = undefined; super.cleanup(); } + /** @summary Returns histogram + * @desc Also assigns custom getBinContent method for TProfile2D if PROJXY options specified */ + getHisto() { + const histo = super.getHisto(); + if (histo?._typename === clTProfile2D) { + if (!histo.$getBinContent) + histo.$getBinContent = histo.getBinContent; + switch (this.getOptions().Profile2DProj) { + case 'B': + histo.getBinContent = histo.getBinEntries; + break; + case 'C=E': + histo.getBinContent = function(i, j) { return this.getBinError(this.getBin(i, j)); }; + break; + case 'W': + histo.getBinContent = function(i, j) { return this.$getBinContent(i, j) * this.getBinEntries(i, j); }; + break; + default: + histo.getBinContent = histo.$getBinContent; + break; + } + } + return histo; + } + + /** @summary Returns if projection is used */ + isProjection() { return this.#projection_kind; } + /** @summary Toggle projection */ toggleProjection(kind, width) { if ((kind === 'Projections') || (kind === 'Off')) kind = ''; + const parseWidth = arg => { + if ((arg === 'all') || (arg === 'ALL')) + return 10000; + const res = parseInt(arg); + return res && Number.isInteger(res) ? res : 1; + }; + let widthX = width, widthY = width; if (isStr(kind) && (kind.indexOf('XY') === 0)) { @@ -556,16 +665,16 @@ class TH2Painter extends THistPainter { widthX = widthY = parseInt(ws) || 1; } else if (isStr(kind) && (kind.length > 1)) { const ps = kind.indexOf('_'); - if ((ps > 0) && (kind[0] === 'X') && (kind[ps+1] === 'Y')) { - widthX = parseInt(kind.slice(1, ps)) || 1; - widthY = parseInt(kind.slice(ps+2)) || 1; + if ((ps > 0) && (kind[0] === 'X') && (kind[ps + 1] === 'Y')) { + widthX = parseWidth(kind.slice(1, ps)); + widthY = parseWidth(kind.slice(ps + 2)); kind = 'XY'; - } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps+1] === 'X')) { - widthY = parseInt(kind.slice(1, ps)) || 1; - widthX = parseInt(kind.slice(ps+2)) || 1; + } else if ((ps > 0) && (kind[0] === 'Y') && (kind[ps + 1] === 'X')) { + widthY = parseWidth(kind.slice(1, ps)); + widthX = parseWidth(kind.slice(ps + 2)); kind = 'XY'; } else { - widthX = widthY = parseInt(kind.slice(1)) || 1; + widthX = widthY = parseWidth(kind.slice(1)); kind = kind[0]; } } @@ -573,47 +682,53 @@ class TH2Painter extends THistPainter { if (!widthX && !widthY) widthX = widthY = 1; - if (kind && (this.is_projection === kind)) { - if ((this.projection_widthX === widthX) && (this.projection_widthY === widthY)) + if (kind && (this.#projection_kind === kind)) { + if ((this.#projection_widthX === widthX) && (this.#projection_widthY === widthY)) kind = ''; - else { - this.projection_widthX = widthX; - this.projection_widthY = widthY; + else { + this.#projection_widthX = widthX; + this.#projection_widthY = widthY; return; } } delete this.proj_hist; - const new_proj = (this.is_projection === kind) ? '' : kind; - this.projection_widthX = widthX; - this.projection_widthY = widthY; - this.is_projection = ''; // avoid projection handling until area is created + const new_proj = (this.#projection_kind === kind) ? '' : kind; + this.#projection_widthX = widthX; + this.#projection_widthY = widthY; + this.#projection_kind = ''; // avoid projection handling until area is created - this.provideSpecialDrawArea(new_proj).then(() => { this.is_projection = new_proj; return this.redrawProjection(); }); + return this.provideSpecialDrawArea(new_proj).then(() => { + this.#projection_kind = new_proj; + return this.redrawProjection(); + }); } /** @summary Redraw projection */ async redrawProjection(ii1, ii2, jj1, jj2) { - if (!this.is_projection) + if (!this.#projection_kind) return false; if (jj2 === undefined) { - if (!this.tt_handle) return; - ii1 = Math.round((this.tt_handle.i1 + this.tt_handle.i2)/2); ii2 = ii1+1; - jj1 = Math.round((this.tt_handle.j1 + this.tt_handle.j2)/2); jj2 = jj1+1; + if (!this.tt_handle) + return; + ii1 = Math.round((this.tt_handle.i1 + this.tt_handle.i2) / 2); + ii2 = ii1 + 1; + jj1 = Math.round((this.tt_handle.j1 + this.tt_handle.j2) / 2); + jj2 = jj1 + 1; } const canp = this.getCanvPainter(); - if (canp && !canp._readonly && (this.snapid !== undefined)) { + if (canp && !canp.isReadonly() && this.hasSnapId()) { // this is when projection should be created on the server side - if (((this.is_projection === 'X') || (this.is_projection === 'XY')) && !canp.websocketTimeout('projX')) { - if (canp.sendWebsocket(`EXECANDSEND:DXPROJ:${this.snapid}:ProjectionX("_projx",${jj1+1},${jj2},"")`)) + if (((this.#projection_kind === 'X') || (this.#projection_kind === 'XY')) && !canp.websocketTimeout('projX')) { + if (canp.sendWebsocket(`EXECANDSEND:DXPROJ:${this.getSnapId()}:ProjectionX("_projx",${jj1 + 1},${jj2},"")`)) canp.websocketTimeout('projX', 1000); } - if (((this.is_projection === 'Y') || (this.is_projection === 'XY')) && !canp.websocketTimeout('projY')) { - if (canp.sendWebsocket(`EXECANDSEND:DYPROJ:${this.snapid}:ProjectionY("_projy",${ii1+1},${ii2},"")`)) + if (((this.#projection_kind === 'Y') || (this.#projection_kind === 'XY')) && !canp.websocketTimeout('projY')) { + if (canp.sendWebsocket(`EXECANDSEND:DYPROJ:${this.getSnapId()}:ProjectionY("_projy",${ii1 + 1},${ii2},"")`)) canp.websocketTimeout('projY', 1000); } return true; @@ -625,58 +740,61 @@ class TH2Painter extends THistPainter { this.doing_projection = true; const histo = this.getHisto(), - createXProject = () => { - const p = createHistogram(clTH1D, this.nbinsx); - Object.assign(p.fXaxis, histo.fXaxis); - p.fName = 'xproj'; - p.fTitle = 'X projection'; - return p; - }, - createYProject = () => { - const p = createHistogram(clTH1D, this.nbinsy); - Object.assign(p.fXaxis, histo.fYaxis); - p.fName = 'yproj'; - p.fTitle = 'Y projection'; - return p; - }, - fillProjectHist = (kind, p) => { - let first = 0, last = -1; - if (kind === 'X') { - for (let i = 0; i < this.nbinsx; ++i) { - let sum = 0; - for (let j = jj1; j < jj2; ++j) - sum += histo.getBinContent(i+1, j+1); - p.setBinContent(i+1, sum); - } - p.fTitle = 'X projection ' + (jj1+1 === jj2 ? `bin ${jj2}` : `bins [${jj1+1} .. ${jj2}]`); - if (this.tt_handle) { first = this.tt_handle.i1+1; last = this.tt_handle.i2; } - } else { - for (let j = 0; j < this.nbinsy; ++j) { - let sum = 0; - for (let i = ii1; i < ii2; ++i) - sum += histo.getBinContent(i+1, j+1); - p.setBinContent(j+1, sum); - } - p.fTitle = 'Y projection ' + (ii1+1 === ii2 ? `bin ${ii2}` : `bins [${ii1+1} .. ${ii2}]`); - if (this.tt_handle) { first = this.tt_handle.j1+1; last = this.tt_handle.j2; } - } - - if (first < last) { - const axis = p.fXaxis; - axis.fFirst = first; - axis.fLast = last; + createXProject = () => { + const p = createHistogram(clTH1D, this.nbinsx); + Object.assign(p.fXaxis, histo.fXaxis); + p.fName = 'xproj'; + p.fTitle = 'X projection'; + return p; + }, + createYProject = () => { + const p = createHistogram(clTH1D, this.nbinsy); + Object.assign(p.fXaxis, histo.fYaxis); + p.fName = 'yproj'; + p.fTitle = 'Y projection'; + return p; + }, + fillProjectHist = (kind, p) => { + let first = 0, last = -1; + if (kind === 'X') { + for (let i = 0; i < this.nbinsx; ++i) { + let sum = 0; + for (let j = jj1; j < jj2; ++j) + sum += histo.getBinContent(i + 1, j + 1); + p.setBinContent(i + 1, sum); + } + p.fTitle = 'X projection ' + (jj1 + 1 === jj2 ? `bin ${jj2}` : `bins [${jj1 + 1} .. ${jj2}]`); + if (this.tt_handle) { + first = this.tt_handle.i1 + 1; + last = this.tt_handle.i2; + } + } else { + for (let j = 0; j < this.nbinsy; ++j) { + let sum = 0; + for (let i = ii1; i < ii2; ++i) + sum += histo.getBinContent(i + 1, j + 1); + p.setBinContent(j + 1, sum); + } + p.fTitle = 'Y projection ' + (ii1 + 1 === ii2 ? `bin ${ii2}` : `bins [${ii1 + 1} .. ${ii2}]`); + if (this.tt_handle) { + first = this.tt_handle.j1 + 1; + last = this.tt_handle.j2; + } + } - if (((axis.fFirst === 1) && (axis.fLast === axis.fNbins)) === axis.TestBit(EAxisBits.kAxisRange)) - axis.InvertBit(EAxisBits.kAxisRange); - } + if (first < last) { + p.fXaxis.fFirst = first; + p.fXaxis.fLast = last; + p.fXaxis.SetBit(EAxisBits.kAxisRange, (first !== 1) || (last !== p.fXaxis.fNbins)); + } - // reset statistic before display - p.fEntries = 0; - p.fTsumw = 0; - }; + // reset statistic before display + p.fEntries = 0; + p.fTsumw = 0; + }; if (!this.proj_hist) { - switch (this.is_projection) { + switch (this.#projection_kind) { case 'X': this.proj_hist = createXProject(); break; @@ -689,17 +807,23 @@ class TH2Painter extends THistPainter { } } - if (this.is_projection === 'XY') { + if (this.#projection_kind === 'XY') { fillProjectHist('X', this.proj_hist); fillProjectHist('Y', this.proj_hist2); return this.drawInSpecialArea(this.proj_hist, '', 'X') .then(() => this.drawInSpecialArea(this.proj_hist2, '', 'Y')) - .then(res => { delete this.doing_projection; return res; }); + .then(res => { + delete this.doing_projection; + return res; + }); } - fillProjectHist(this.is_projection, this.proj_hist); + fillProjectHist(this.#projection_kind, this.proj_hist); - return this.drawInSpecialArea(this.proj_hist).then(res => { delete this.doing_projection; return res; }); + return this.drawInSpecialArea(this.proj_hist).then(res => { + delete this.doing_projection; + return res; + }); } /** @summary Execute TH2 menu command @@ -723,46 +847,59 @@ class TH2Painter extends THistPainter { /** @summary Fill histogram context menu */ fillHistContextMenu(menu) { - if (!this.isTH2Poly() && this.getPadPainter()?.iscan) { - let kind = this.is_projection || ''; - if (kind) kind += this.projection_widthX; - if ((this.projection_widthX !== this.projection_widthY) && (this.is_projection === 'XY')) - kind = `X${this.projection_widthX}_Y${this.projection_widthY}`; - - const kinds = ['X1', 'X2', 'X3', 'X5', 'X10', 'Y1', 'Y2', 'Y3', 'Y5', 'Y10', 'XY1', 'XY2', 'XY3', 'XY5', 'XY10']; - if (kind) kinds.unshift('Off'); - - menu.add('sub:Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); - for (let k = 0; k < kinds.length; ++k) - menu.addchk(kind === kinds[k], kinds[k], kinds[k], arg => this.toggleProjection(arg)); - menu.add('endsub:'); + if (!this.isTH2Poly() && this.getPadPainter()?.isCanvas()) { + let kind = this.#projection_kind || ''; + if (kind) + kind += this.#projection_widthX; + if ((this.#projection_widthX !== this.#projection_widthY) && (this.#projection_kind === 'XY')) + kind = `X${this.#projection_widthX}_Y${this.#projection_widthY}`; + + const sizes = ['1', '2', '3', '5', '10', 'all']; + if (kind) + sizes.unshift(''); + + menu.sub('Projections', () => menu.input('Input projection kind X1 or XY2 or X3_Y4', kind, 'string').then(val => this.toggleProjection(val))); + ['X', 'Y', 'XY'].forEach(name => { + menu.column(); + sizes.forEach(sz => { + const id = sz ? name + sz : 'Off'; + menu.addchk(kind === id, id, id, arg => this.toggleProjection(arg)); + }); + menu.endcolumn(); + }); + menu.endsub(); } if (!this.isTH2Poly()) menu.add('Auto zoom-in', () => this.autoZoom()); - const opts = this.getSupportedDrawOptions(); + const opts = this.getSupportedDrawOptions(), + o = this.getOptions(); menu.addDrawMenu('Draw with', opts, arg => { if (arg.indexOf(kInspect) === 0) return this.showInspector(arg); + const oldProject = o.Project; this.decodeOptions(arg); - this.interactiveRedraw('pad', 'drawopt'); + if ((oldProject === o.Project) || this.mode3d) + this.interactiveRedraw('pad', 'drawopt'); + else + this.toggleProjection(o.Project); }); - if (this.options.Color || this.options.Contour || this.options.Hist || this.options.Surf || this.options.Lego === 12 || this.options.Lego === 14) + if (o.Color || o.Contour || o.Hist || o.Surf || o.Lego === 12 || o.Lego === 14) this.fillPaletteMenu(menu, true); } /** @summary Process click on histogram-defined buttons */ clickButton(funcname) { const res = super.clickButton(funcname); - if (res) return res; + if (res) + return res; if (this.isMainPainter()) { switch (funcname) { case 'ToggleColor': return this.toggleColor(); - case 'ToggleColorZ': return this.toggleColz(); case 'Toggle3D': return this.toggleMode3D(); } } @@ -775,12 +912,14 @@ class TH2Painter extends THistPainter { fillToolbar() { super.fillToolbar(true); - const pp = this.getPadPainter(); - if (!pp) return; + const pp = this.getPadPainter(), + o = this.getOptions(); + if (!pp) + return; - if (!this.isTH2Poly() && !this.options.Axis) + if (!this.isTH2Poly() && !o.Axis) pp.addPadButton('th2color', 'Toggle color', 'ToggleColor'); - if (!this.options.Axis) + if (!o.Axis) pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); pp.addPadButton('th2draw3d', 'Toggle 3D mode', 'Toggle3D'); pp.showPadButtons(); @@ -788,15 +927,16 @@ class TH2Painter extends THistPainter { /** @summary Toggle color drawing mode */ toggleColor() { - if (this.options.Mode3D) { - this.options.Mode3D = false; - this.options.Color = true; + const o = this.getOptions(); + if (o.Mode3D) { + o.Mode3D = false; + o.Color = true; } else { - this.options.Color = !this.options.Color; - this.options.Scat = !this.options.Color; + o.Color = !o.Color; + o.Scat = !o.Color; } - this._can_move_colz = true; // indicate that next redraw can move Z scale + this.#can_move_colz = true; // indicate that next redraw can move Z scale this.copyOptionsToOthers(); @@ -805,15 +945,17 @@ class TH2Painter extends THistPainter { /** @summary Perform automatic zoom inside non-zero region of histogram */ autoZoom() { - if (this.isTH2Poly()) return; // not implemented + if (this.isTH2Poly()) + return; // not implemented const i1 = this.getSelectIndex('x', 'left', -1), - i2 = this.getSelectIndex('x', 'right', 1), - j1 = this.getSelectIndex('y', 'left', -1), - j2 = this.getSelectIndex('y', 'right', 1), - histo = this.getObject(); + i2 = this.getSelectIndex('x', 'right', 1), + j1 = this.getSelectIndex('y', 'left', -1), + j2 = this.getSelectIndex('y', 'right', 1), + histo = this.getHisto(); - if ((i1 === i2) || (j1 === j2)) return; + if ((i1 === i2) || (j1 === j2)) + return; // first find minimum let min = histo.getBinContent(i1 + 1, j1 + 1); @@ -821,48 +963,61 @@ class TH2Painter extends THistPainter { for (let j = j1; j < j2; ++j) min = Math.min(min, histo.getBinContent(i + 1, j + 1)); } - if (min > 0) return; // if all points positive, no chance for autoscale + if (min > 0) + return; // if all points positive, no chance for auto-scale let ileft = i2, iright = i1, jleft = j2, jright = j1; for (let i = i1; i < i2; ++i) { for (let j = j1; j < j2; ++j) { if (histo.getBinContent(i + 1, j + 1) > min) { - if (i < ileft) ileft = i; - if (i >= iright) iright = i + 1; - if (j < jleft) jleft = j; - if (j >= jright) jright = j + 1; + if (i < ileft) + ileft = i; + if (i >= iright) + iright = i + 1; + if (j < jleft) + jleft = j; + if (j >= jright) + jright = j + 1; } } } let xmin, xmax, ymin, ymax, isany = false; - if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; } - if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; } + if ((ileft === iright - 1) && (ileft > i1 + 1) && (iright < i2 - 1)) { + ileft--; + iright++; + } + if ((jleft === jright - 1) && (jleft > j1 + 1) && (jright < j2 - 1)) { + jleft--; + jright++; + } if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) { - xmin = histo.fXaxis.GetBinLowEdge(ileft+1); - xmax = histo.fXaxis.GetBinLowEdge(iright+1); + xmin = histo.fXaxis.GetBinLowEdge(ileft + 1); + xmax = histo.fXaxis.GetBinLowEdge(iright + 1); isany = true; } if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) { - ymin = histo.fYaxis.GetBinLowEdge(jleft+1); - ymax = histo.fYaxis.GetBinLowEdge(jright+1); + ymin = histo.fYaxis.GetBinLowEdge(jleft + 1); + ymax = histo.fYaxis.GetBinLowEdge(jright + 1); isany = true; } if (isany) - return this.getFramePainter().zoom(xmin, xmax, ymin, ymax); + return this.getFramePainter()?.zoom(xmin, xmax, ymin, ymax); } /** @summary Scan TH2 histogram content */ scanContent(when_axis_changed) { - // no need to rescan histogram while result does not depend from axis selection - if (when_axis_changed && this.nbinsx && this.nbinsy) return; + // no need to re-scan histogram while result does not depend from axis selection + if (when_axis_changed && this.nbinsx && this.nbinsy) + return; - const histo = this.getObject(); + const histo = this.getObject(), + o = this.getOptions(); let i, j; this.extractAxesProperties(2); @@ -873,7 +1028,8 @@ class TH2Painter extends THistPainter { for (let n = 0, len = histo.fBins.arr.length; n < len; ++n) { const bin_content = histo.fBins.arr[n].fContent; - if (n === 0) this.gminbin = this.gmaxbin = bin_content; + if (n === 0) + this.gminbin = this.gmaxbin = bin_content; if (bin_content < this.gminbin) this.gminbin = bin_content; @@ -889,7 +1045,7 @@ class TH2Painter extends THistPainter { this.gminposbin = null; for (i = 0; i < this.nbinsx; ++i) { for (j = 0; j < this.nbinsy; ++j) { - const bin_content = histo.getBinContent(i+1, j+1); + const bin_content = histo.getBinContent(i + 1, j + 1); if (bin_content < this.gminbin) this.gminbin = bin_content; else if (bin_content > this.gmaxbin) @@ -904,40 +1060,61 @@ class TH2Painter extends THistPainter { // this value used for logz scale drawing if ((this.gminposbin === null) && (this.gmaxbin > 0)) - this.gminposbin = this.gmaxbin*1e-4; + this.gminposbin = this.gmaxbin * 1e-4; - const is_content = (this.gmaxbin !== 0) || (this.gminbin !== 0); + let is_content = this.gmaxbin || this.gminbin; - if (this.options.Axis > 0) { + // for TProfile2D show empty bin if there are entries for it + if (!is_content && (histo._typename === clTProfile2D)) { + for (i = 0; i < this.nbinsx && !is_content; ++i) { + for (j = 0; j < this.nbinsy; ++j) { + if (histo.getBinEntries(i + 1, j + 1)) { + is_content = true; + break; + } + } + } + } + + if (o.Axis > 0) { // Paint histogram axis only this.draw_content = false; } else if (this.isTH2Poly()) { - this.draw_content = is_content || this.options.Line || this.options.Fill || this.options.Mark; - if (!this.draw_content && this.options.Zero) { + this.draw_content = is_content || o.Line || o.Fill || o.Mark; + if (!this.draw_content && o.Zero) { this.draw_content = true; - this.options.Line = 1; + o.Line = 1; } } else - this.draw_content = is_content; + this.draw_content = is_content || o.ShowEmpty; + } + + /** @summary Provide histogram min/max used to create canvas ranges + * @private */ + getUserRanges() { + const histo = this.getHisto(); + return { minx: histo.fXaxis.fXmin, maxx: histo.fXaxis.fXmax, miny: histo.fYaxis.fXmin, maxy: histo.fYaxis.fXmax }; } /** @summary Count TH2 histogram statistic * @desc Optionally one could provide condition function to select special range */ countStat(cond, count_skew) { - if (!isFunc(cond)) - cond = this.options.cutg ? (x, y) => this.options.cutg.IsInside(x, y) : null; - - const histo = this.getHisto(), xaxis = histo.fXaxis, yaxis = histo.fYaxis, - fp = this.getFramePainter(), - funcs = fp.getGrFuncs(this.options.second_x, this.options.second_y), - res = { name: histo.fName, entries: 0, eff_entries: 0, integral: 0, - meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], - xmax: 0, ymax: 0, wmax: null, skewx: 0, skewy: 0, skewd: 0, kurtx: 0, kurty: 0, kurtd: 0 }, - has_counted_stat = !fp.isAxisZoomed('x') && !fp.isAxisZoomed('y') && (Math.abs(histo.fTsumw) > 1e-300) && !cond; + const histo = this.getHisto(), o = this.getOptions(), + xaxis = histo.fXaxis, yaxis = histo.fYaxis, + funcs = this.getHistGrFuncs(), + res = { + name: histo.fName, entries: 0, eff_entries: 0, integral: 0, + meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0, 0, 0, 0, 0, 0, 0, 0, 0], + xmax: 0, ymax: 0, wmax: null, skewx: 0, skewy: 0, skewd: 0, kurtx: 0, kurty: 0, kurtd: 0 + }, + has_counted_stat = !funcs.isAxisZoomed('x') && !funcs.isAxisZoomed('y') && (Math.abs(histo.fTsumw) > 1e-300) && !cond && !o.cutg; let stat_sum0 = 0, stat_sumw2 = 0, stat_sumx1 = 0, stat_sumy1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, xside, yside, xx, yy, zz, xleft, xright, yleft, yright; + if (!isFunc(cond) && o.cutg) + cond = (x, y) => o.cutg.IsInside(x, y); + if (this.isTH2Poly()) { const len = histo.fBins.arr.length; let i, bin, n, gr, ngr, numgraphs, numpoints; @@ -949,11 +1126,16 @@ class TH2Painter extends THistPainter { yside = (bin.fYmin > funcs.scale_ymax) ? 2 : (bin.fYmax < funcs.scale_ymin ? 0 : 1); xx = yy = numpoints = 0; - gr = bin.fPoly; numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } + gr = bin.fPoly; + numgraphs = 1; + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } for (ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; for (n = 0; n < gr.fNpoints; ++n) { ++numpoints; @@ -963,8 +1145,8 @@ class TH2Painter extends THistPainter { } if (numpoints > 1) { - xx = xx / numpoints; - yy = yy / numpoints; + xx /= numpoints; + yy /= numpoints; } zz = bin.fContent; @@ -973,7 +1155,8 @@ class TH2Painter extends THistPainter { res.matrix[yside * 3 + xside] += zz; - if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) continue; + if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) + continue; if ((res.wmax === null) || (zz > res.wmax)) { res.wmax = zz; @@ -1010,7 +1193,8 @@ class TH2Painter extends THistPainter { res.matrix[yside * 3 + xside] += zz; - if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) continue; + if ((xside !== 1) || (yside !== 1) || (cond && !cond(xx, yy))) + continue; if ((res.wmax === null) || (zz > res.wmax)) { res.wmax = zz; @@ -1023,9 +1207,8 @@ class TH2Painter extends THistPainter { stat_sumw2 += zz * zz; stat_sumx1 += xx * zz; stat_sumy1 += yy * zz; - stat_sumx2 += xx**2 * zz; - stat_sumy2 += yy**2 * zz; - // stat_sumxy += xx * yy * zz; + stat_sumx2 += xx ** 2 * zz; + stat_sumy2 += yy ** 2 * zz; } } } @@ -1038,32 +1221,32 @@ class TH2Painter extends THistPainter { stat_sumx2 = histo.fTsumwx2; stat_sumy1 = histo.fTsumwy; stat_sumy2 = histo.fTsumwy2; - // stat_sumxy = histo.fTsumwxy; } if (Math.abs(stat_sum0) > 1e-300) { res.meanx = stat_sumx1 / stat_sum0; res.meany = stat_sumy1 / stat_sum0; - res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx**2)); - res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany**2)); + res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx ** 2)); + res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany ** 2)); } if (res.wmax === null) res.wmax = 0; res.integral = stat_sum0; - if (histo.fEntries > 1) + if (histo.fEntries > 0) res.entries = histo.fEntries; - res.eff_entries = stat_sumw2 ? stat_sum0*stat_sum0/stat_sumw2 : Math.abs(stat_sum0); + res.eff_entries = stat_sumw2 ? stat_sum0 * stat_sum0 / stat_sumw2 : Math.abs(stat_sum0); if (count_skew && !this.isTH2Poly()) { - let sumx3 = 0, sumy3 = 0, sumx4 = 0, sumy4 = 0, np = 0, w = 0; + let sumx3 = 0, sumy3 = 0, sumx4 = 0, sumy4 = 0, np = 0, w; for (let xi = xleft; xi < xright; ++xi) { xx = xaxis.GetBinCoord(xi + 0.5); for (let yi = yleft; yi < yright; ++yi) { yy = yaxis.GetBinCoord(yi + 0.5); - if (cond && !cond(xx, yy)) continue; + if (cond && !cond(xx, yy)) + continue; w = histo.getBinContent(xi + 1, yi + 1); np += w; sumx3 += w * Math.pow(xx - res.meanx, 3); @@ -1077,16 +1260,16 @@ class TH2Painter extends THistPainter { stddev3y = Math.pow(res.rmsy, 3), stddev4x = Math.pow(res.rmsx, 4), stddev4y = Math.pow(res.rmsy, 4); - if (np * stddev3x !== 0) + if (np * stddev3x) res.skewx = sumx3 / (np * stddev3x); - if (np * stddev3y !== 0) + if (np * stddev3y) res.skewy = sumy3 / (np * stddev3y); - res.skewd = res.eff_entries > 0 ? Math.sqrt(6/res.eff_entries) : 0; - if (np * stddev4x !== 0) + res.skewd = res.eff_entries > 0 ? Math.sqrt(6 / res.eff_entries) : 0; + if (np * stddev4x) res.kurtx = sumx4 / (np * stddev4x) - 3; - if (np * stddev4y !== 0) + if (np * stddev4y) res.kurty = sumy4 / (np * stddev4y) - 3; - res.kurtd = res.eff_entries > 0 ? Math.sqrt(24/res.eff_entries) : 0; + res.kurtd = res.eff_entries > 0 ? Math.sqrt(24 / res.eff_entries) : 0; } return res; @@ -1095,9 +1278,11 @@ class TH2Painter extends THistPainter { /** @summary Fill TH2 statistic in stat box */ fillStatistic(stat, dostat, dofit) { // no need to refill statistic if histogram is dummy - if (this.isIgnoreStatsFill()) return false; + if (this.isIgnoreStatsFill()) + return false; - if (dostat === 1) dostat = 1111; + if (dostat === 1) + dostat = 1111; const print_name = Math.floor(dostat % 10), print_entries = Math.floor(dostat / 10) % 10, @@ -1155,7 +1340,8 @@ class TH2Painter extends THistPainter { stat.addText(`${get(0)} | ${get(1)} | ${get(2)}`); } - if (dofit) stat.fillFunctionStat(this.findFunction(clTF2), dofit, 2); + if (dofit) + stat.fillFunctionStat(this.findFunction(clTF2), dofit, 2); return true; } @@ -1163,68 +1349,81 @@ class TH2Painter extends THistPainter { /** @summary Draw TH2 bins as colors */ drawBinsColor() { const histo = this.getHisto(), + o = this.getOptions(), handle = this.prepareDraw(), cntr = this.getContour(), palette = this.getHistPalette(), entries = [], - show_empty = this._show_empty_bins, - can_merge_x = (handle.xbar2 === 1) && (handle.xbar1 === 0), - can_merge_y = (handle.ybar2 === 1) && (handle.ybar1 === 0); + has_sumw2 = histo.fSumw2?.length, + show_empty = o.ShowEmpty, + can_merge_x = (o.Color !== 7) || ((handle.xbar1 === 0) && (handle.xbar2 === 1)), + can_merge_y = (o.Color !== 7) || ((handle.ybar1 === 0) && (handle.ybar2 === 1)), + colindx0 = cntr.getPaletteIndex(palette, 0); let dx, dy, x1, y2, binz, is_zero, colindx, last_entry = null, - skip_zero = !this.options.Zero; + skip_zero = !o.Zero, skip_bin; - const test_cutg = this.options.cutg, + const test_cutg = o.cutg, flush_last_entry = () => { - last_entry.path += `h${dx}v${last_entry.y1-last_entry.y2}h${-dx}z`; - last_entry = null; - }; + last_entry.path += `h${dx}v${last_entry.y1 - last_entry.y2}h${-dx}z`; + last_entry = null; + }; // check in the beginning if zero can be skipped - if (!skip_zero && !show_empty && (cntr.getPaletteIndex(palette, 0) === null)) skip_zero = true; + if (!skip_zero && !show_empty && (colindx0 === null)) + skip_zero = true; + + // special check for TProfile2D - empty bin with no entries shown + if (skip_zero && (histo?._typename === clTProfile2D)) + skip_zero = 1; // now start build for (let i = handle.i1; i < handle.i2; ++i) { - dx = (handle.grx[i+1] - handle.grx[i]) || 1; + dx = (handle.grx[i + 1] - handle.grx[i]) || 1; if (can_merge_x) x1 = handle.grx[i]; else { - x1 = Math.round(handle.grx[i] + dx*handle.xbar1); - dx = Math.round(dx*(handle.xbar2 - handle.xbar1)) || 1; + x1 = Math.round(handle.grx[i] + dx * handle.xbar1); + dx = Math.round(dx * (handle.xbar2 - handle.xbar1)) || 1; } for (let j = handle.j2 - 1; j >= handle.j1; --j) { binz = histo.getBinContent(i + 1, j + 1); - is_zero = (binz === 0); + is_zero = (binz === 0) && (!has_sumw2 || histo.fSumw2[histo.getBin(i + 1, j + 1)] === 0); + + skip_bin = is_zero && ((skip_zero === 1) ? !histo.getBinEntries(i + 1, j + 1) : skip_zero); - if ((is_zero && skip_zero) || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) { - if (last_entry) flush_last_entry(); + if (skip_bin || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) { + if (last_entry) + flush_last_entry(); continue; } colindx = cntr.getPaletteIndex(palette, binz); + if (colindx === null) { - if (is_zero && show_empty) - colindx = 0; - else { - if (last_entry) flush_last_entry(); - continue; - } + if (is_zero && (show_empty || (skip_zero === 1))) + colindx = colindx0 || 0; + else { + if (last_entry) + flush_last_entry(); + continue; + } } - dy = (handle.gry[j] - handle.gry[j+1]) || 1; + dy = (handle.gry[j] - handle.gry[j + 1]) || 1; if (can_merge_y) - y2 = handle.gry[j+1]; + y2 = handle.gry[j + 1]; else { - y2 = Math.round(handle.gry[j] - dy*handle.ybar2); - dy = Math.round(dy*(handle.ybar2 - handle.ybar1)) || 1; + y2 = Math.round(handle.gry[j] - dy * handle.ybar2); + dy = Math.round(dy * (handle.ybar2 - handle.ybar1)) || 1; } const cmd1 = `M${x1},${y2}`; let entry = entries[colindx]; if (!entry) entry = entries[colindx] = { path: cmd1 }; - else if (can_merge_y && (entry === last_entry)) { + else if (can_merge_y && (entry === last_entry)) { entry.y1 = y2 + dy; continue; } else { @@ -1234,7 +1433,8 @@ class TH2Painter extends THistPainter { entry.path += (cmd2.length < cmd1.length) ? cmd2 : cmd1; } } - if (last_entry) flush_last_entry(); + if (last_entry) + flush_last_entry(); entry.x1 = x1; entry.y2 = y2; @@ -1245,16 +1445,134 @@ class TH2Painter extends THistPainter { } else entry.path += `h${dx}v${dy}h${-dx}z`; } - if (last_entry) flush_last_entry(); + if (last_entry) + flush_last_entry(); } - entries.forEach((entry, colindx) => { - if (entry) { - this.draw_g - .append('svg:path') - .attr('fill', palette.getColor(colindx)) - .attr('d', entry.path); + entries.forEach((entry, ecolindx) => { + if (entry) + this.appendPath(entry.path).attr('fill', palette.getColor(ecolindx)); + }); + + return handle; + } + + /** @summary Draw TH2 bins as colors in polar coordinates */ + drawBinsPolar() { + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw(), + cntr = this.getContour(), + palette = this.getHistPalette(), + entries = [], + has_sumw2 = histo.fSumw2?.length, + show_empty = o.ShowEmpty, + colindx0 = cntr.getPaletteIndex(palette, 0); + + let binz, is_zero, colindx, + skip_zero = !o.Zero, skip_bin; + + const test_cutg = o.cutg; + + // check in the beginning if zero can be skipped + if (!skip_zero && !show_empty && (colindx0 === null)) + skip_zero = true; + + // special check for TProfile2D - empty bin with no entries shown + if (skip_zero && (histo?._typename === clTProfile2D)) + skip_zero = 1; + + handle.getBinPath = function(i, j) { + const a1 = 2 * Math.PI * Math.max(0, this.grx[i]) / this.width, + a2 = 2 * Math.PI * Math.min(this.grx[i + 1], this.width) / this.width, + r2 = Math.min(this.gry[j], this.height) / this.height, + r1 = Math.max(0, this.gry[j + 1]) / this.height, + side = a2 - a1 > Math.PI ? 1 : 0; // handle very large sector + + // do not process bins outside visible range + if ((a2 <= a1) || (r2 <= r1)) + return ''; + + const x0 = this.width / 2, y0 = this.height / 2, + rx1 = r1 * this.width / 2, + rx2 = r2 * this.width / 2, + ry1 = r1 * this.height / 2, + ry2 = r2 * this.height / 2, + x11 = x0 + rx1 * Math.cos(a1), + x12 = x0 + rx1 * Math.cos(a2), + y11 = y0 + ry1 * Math.sin(a1), + y12 = y0 + ry1 * Math.sin(a2), + x21 = x0 + rx2 * Math.cos(a1), + x22 = x0 + rx2 * Math.cos(a2), + y21 = y0 + ry2 * Math.sin(a1), + y22 = y0 + ry2 * Math.sin(a2); + + return `M${x11.toFixed(2)},${y11.toFixed(2)}` + + `A${rx1.toFixed(2)},${ry1.toFixed(2)},0,${side},1,${x12.toFixed(2)},${y12.toFixed(2)}` + + `L${x22.toFixed(2)},${y22.toFixed(2)}` + + `A${rx2.toFixed(2)},${ry2.toFixed(2)},0,${side},0,${x21.toFixed(2)},${y21.toFixed(2)}Z`; + }; + + handle.findBin = function(x, y) { + const x0 = this.width / 2, y0 = this.height / 2; + let angle = Math.atan2((y - y0) / this.height, (x - x0) / this.width), i, j; + const radius = Math.abs(Math.cos(angle)) > 0.5 ? (x - x0) / Math.cos(angle) / this.width * 2 : (y - y0) / Math.sin(angle) / this.height * 2; + + if (angle < 0) + angle += 2 * Math.PI; + + for (i = this.i1; i < this.i2; ++i) { + const a1 = 2 * Math.PI * this.grx[i] / this.width, + a2 = 2 * Math.PI * this.grx[i + 1] / this.width; + if ((a1 <= angle) && (angle <= a2)) + break; + } + + for (j = this.j1; j < this.j2; ++j) { + const r2 = this.gry[j] / this.height, + r1 = this.gry[j + 1] / this.height; + if ((r1 <= radius) && (radius <= r2)) + break; } + + return { i, j }; + }; + + // now start build + for (let i = handle.i1; i < handle.i2; ++i) { + for (let j = handle.j2 - 1; j >= handle.j1; --j) { + binz = histo.getBinContent(i + 1, j + 1); + is_zero = (binz === 0) && (!has_sumw2 || histo.fSumw2[histo.getBin(i + 1, j + 1)] === 0); + + skip_bin = is_zero && ((skip_zero === 1) ? !histo.getBinEntries(i + 1, j + 1) : skip_zero); + + if (skip_bin || (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5)))) + continue; + + colindx = cntr.getPaletteIndex(palette, binz); + + if (colindx === null) { + if (is_zero && (show_empty || (skip_zero === 1))) + colindx = colindx0 || 0; + else + continue; + } + + const cmd = handle.getBinPath(i, j); + if (!cmd) + continue; + + const entry = entries[colindx]; + if (!entry) + entries[colindx] = { path: cmd }; + else + entry.path += cmd; + } + } + + entries.forEach((entry, ecolindx) => { + if (entry) + this.appendPath(entry.path).attr('fill', palette.getColor(ecolindx)); }); return handle; @@ -1263,45 +1581,43 @@ class TH2Painter extends THistPainter { /** @summary Draw histogram bins with projection function */ drawBinsProjected() { const handle = this.prepareDraw({ rounding: false, nozoom: true, extra: 100, original: true }), - main = this.getFramePainter(), - funcs = main.getGrFuncs(this.options.second_x, this.options.second_y), + funcs = this.getHistGrFuncs(), ilevels = this.getContourLevels(), palette = this.getHistPalette(), - func = main.getProjectionFunc(); + func = isFunc(funcs.getProjectionFunc) ? funcs.getProjectionFunc() : (x, y) => { return { x, y }; }; handle.grz = z => z; - handle.grz_min = ilevels[0]; - handle.grz_max = ilevels[ilevels.length - 1]; + handle.grz_min = ilevels.at(0); + handle.grz_max = ilevels.at(-1); buildSurf3D(this.getHisto(), handle, ilevels, (lvl, pos) => { let dd = '', lastx, lasty; for (let i = 0; i < pos.length; i += 3) { const pnt = func(pos[i], pos[i + 1]), - x = Math.round(funcs.grx(pnt.x)), - y = Math.round(funcs.gry(pnt.y)); + x = Math.round(funcs.grx(pnt.x)), + y = Math.round(funcs.gry(pnt.y)); if (i === 0) dd = `M${x},${y}`; - else { + else { if ((x === lastx) && (y === lasty)) continue; if (i % 9 === 0) - dd += `m${x-lastx},${y-lasty}`; + dd += `m${x - lastx},${y - lasty}`; else if (y === lasty) - dd += `h${x-lastx}`; + dd += `h${x - lastx}`; else if (x === lastx) - dd += `v${y-lasty}`; + dd += `v${y - lasty}`; else - dd += `l${x-lastx},${y-lasty}`; + dd += `l${x - lastx},${y - lasty}`; } - lastx = x; lasty = y; + lastx = x; + lasty = y; } - this.draw_g - .append('svg:path') - .attr('d', dd) + this.appendPath(dd) .style('fill', palette.calcColor(lvl, ilevels.length)); }); @@ -1311,47 +1627,54 @@ class TH2Painter extends THistPainter { /** @summary Draw histogram bins as contour */ drawBinsContour() { const handle = this.prepareDraw({ rounding: false, extra: 100 }), - main = this.getFramePainter(), - frame_w = main.getFrameWidth(), - frame_h = main.getFrameHeight(), levels = this.getContourLevels(), palette = this.getHistPalette(), + o = this.getOptions(); + + /* eslint-disable-next-line one-var */ + const get_segm_intersection = (segm1, segm2) => { + const s10_x = segm1.x2 - segm1.x1, + s10_y = segm1.y2 - segm1.y1, + s32_x = segm2.x2 - segm2.x1, + s32_y = segm2.y2 - segm2.y1, + denom = s10_x * s32_y - s32_x * s10_y; + + if (denom === 0) + return 0; // Collinear + const denomPositive = denom > 0, + s02_x = segm1.x1 - segm2.x1, + s02_y = segm1.y1 - segm2.y1, + s_numer = s10_x * s02_y - s10_y * s02_x; + if ((s_numer < 0) === denomPositive) + return null; // No collision + + const t_numer = s32_x * s02_y - s32_y * s02_x; + if ((t_numer < 0) === denomPositive) + return null; // No collision + + if (((s_numer > denom) === denomPositive) || ((t_numer > denom) === denomPositive)) + return null; // No collision + // Collision detected + const t = t_numer / denom; + return { x: Math.round(segm1.x1 + (t * s10_x)), y: Math.round(segm1.y1 + (t * s10_y)) }; + }; - get_segm_intersection = (segm1, segm2) => { - const s10_x = segm1.x2 - segm1.x1, - s10_y = segm1.y2 - segm1.y1, - s32_x = segm2.x2 - segm2.x1, - s32_y = segm2.y2 - segm2.y1, - denom = s10_x * s32_y - s32_x * s10_y; - - if (denom === 0) - return 0; // Collinear - const denomPositive = denom > 0, - s02_x = segm1.x1 - segm2.x1, - s02_y = segm1.y1 - segm2.y1, - s_numer = s10_x * s02_y - s10_y * s02_x; - if ((s_numer < 0) === denomPositive) - return null; // No collision - - const t_numer = s32_x * s02_y - s32_y * s02_x; - if ((t_numer < 0) === denomPositive) - return null; // No collision - - if (((s_numer > denom) === denomPositive) || ((t_numer > denom) === denomPositive)) - return null; // No collision - // Collision detected - const t = t_numer / denom; - return { x: Math.round(segm1.x1 + (t * s10_x)), y: Math.round(segm1.y1 + (t * s10_y)) }; - }, buildPath = (xp, yp, iminus, iplus, do_close, check_rapair) => { + /* eslint-disable-next-line one-var */ + const buildPath = (xp, yp, iminus, iplus, do_close, check_rapair) => { let cmd = '', lastx, lasty, x0, y0, isany = false, matched, x, y; for (let i = iminus; i <= iplus; ++i) { x = Math.round(xp[i]); y = Math.round(yp[i]); if (!cmd) { - cmd = `M${x},${y}`; x0 = x; y0 = y; + cmd = `M${x},${y}`; + x0 = x; + y0 = y; } else if ((i === iplus) && (iminus !== iplus) && (x === x0) && (y === y0)) { - if (!isany) return ''; // all same points - cmd += 'z'; do_close = false; matched = true; + if (!isany) + return ''; // all same points + cmd += 'z'; + do_close = false; + matched = true; } else { const dx = x - lastx, dy = y - lasty; if (dx) { @@ -1363,20 +1686,21 @@ class TH2Painter extends THistPainter { } } - lastx = x; lasty = y; + lastx = x; + lasty = y; } if (!do_close || matched || !check_rapair) return do_close ? cmd + 'z' : cmd; // try to build path which fills area to outside borders + const points = [{ x: 0, y: 0 }, { x: handle.width, y: 0 }, { x: handle.width, y: handle.height }, { x: 0, y: handle.height }]; - const points = [{ x: 0, y: 0 }, { x: frame_w, y: 0 }, { x: frame_w, y: frame_h }, { x: 0, y: frame_h }], - - get_intersect = (i, di) => { - const segm = { x1: xp[i], y1: yp[i], x2: 2*xp[i] - xp[i+di], y2: 2*yp[i] - yp[i+di] }; + /* eslint-disable-next-line one-var */ + const get_intersect = (indx, di) => { + const segm = { x1: xp[indx], y1: yp[indx], x2: 2 * xp[indx] - xp[indx + di], y2: 2 * yp[indx] - yp[indx + di] }; for (let i = 0; i < 4; ++i) { - const res = get_segm_intersection(segm, { x1: points[i].x, y1: points[i].y, x2: points[(i+1)%4].x, y2: points[(i+1)%4].y }); + const res = get_segm_intersection(segm, { x1: points[i].x, y1: points[i].y, x2: points[(i + 1) % 4].x, y2: points[(i + 1) % 4].y }); if (res) { res.indx = i + 0.5; return res; @@ -1389,17 +1713,19 @@ class TH2Painter extends THistPainter { iminus--; while ((iminus < iplus - 1) && !pnt1) pnt1 = get_intersect(++iminus, 1); - if (!pnt1) return ''; + if (!pnt1) + return ''; iplus++; while ((iminus < iplus - 1) && !pnt2) pnt2 = get_intersect(--iplus, -1); - if (!pnt2) return ''; + if (!pnt2) + return ''; // TODO: now side is always same direction, could be that side should be checked more precise let dd = buildPath(xp, yp, iminus, iplus), indx = pnt2.indx; - const side = 1, step = side*0.5; + const side = 1, step = side * 0.5; dd += `L${pnt2.x},${pnt2.y}`; @@ -1411,10 +1737,8 @@ class TH2Painter extends THistPainter { return dd + `L${pnt1.x},${pnt1.y}z`; }; - if (this.options.Contour === 14) { - this.draw_g - .append('svg:path') - .attr('d', `M0,0h${frame_w}v${frame_h}h${-frame_w}z`) + if (o.Contour === 14) { + this.appendPath(`M0,0h${handle.width}v${handle.height}h${-handle.width}z`) .style('fill', palette.calcColor(0, levels.length)); } @@ -1422,24 +1746,31 @@ class TH2Painter extends THistPainter { const icol = palette.getColor(colindx); let fillcolor = icol, lineatt; - switch (this.options.Contour) { - case 1: break; - case 11: fillcolor = 'none'; lineatt = this.createAttLine({ color: icol, std: false }); break; - case 12: fillcolor = 'none'; lineatt = this.createAttLine({ color: 1, style: (ipoly%5 + 1), width: 1, std: false }); break; - case 13: fillcolor = 'none'; lineatt = this.lineatt; break; - case 14: break; + switch (o.Contour) { + case 1: + break; + case 11: + fillcolor = 'none'; + lineatt = this.createAttLine({ color: icol, std: false }); + break; + case 12: + fillcolor = 'none'; + lineatt = this.createAttLine({ color: 1, style: (ipoly % 5 + 1), width: 1, std: false }); + break; + case 13: + fillcolor = 'none'; + lineatt = this.lineatt; + break; + case 14: + break; } const dd = buildPath(xp, yp, iminus, iplus, fillcolor !== 'none', true); - if (!dd) return; - - const elem = this.draw_g - .append('svg:path') - .attr('d', dd) - .style('fill', fillcolor); - - if (lineatt) - elem.call(lineatt.func); + if (dd) { + this.appendPath(dd) + .style('fill', fillcolor) + .call(lineatt ? lineatt.func : () => {}); + } }); handle.hide_only_zeros = true; // text drawing suppress only zeros @@ -1450,7 +1781,7 @@ class TH2Painter extends THistPainter { getGrNPoints(gr) { const x = gr.fX, y = gr.fY; let npnts = gr.fNpoints; - if ((npnts > 2) && (x[0] === x[npnts-1]) && (y[0] === y[npnts-1])) + if ((npnts > 2) && (x[0] === x[npnts - 1]) && (y[0] === y[npnts - 1])) npnts--; return npnts; } @@ -1459,16 +1790,21 @@ class TH2Painter extends THistPainter { createPolyGr(funcs, gr, textbin) { let grcmd = '', acc_x = 0, acc_y = 0; - const x = gr.fX, y = gr.fY, - flush = () => { - if (acc_x) { grcmd += 'h' + acc_x; acc_x = 0; } - if (acc_y) { grcmd += 'v' + acc_y; acc_y = 0; } - }, addPoint = (x1, y1, x2, y2) => { - const len = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2); - textbin.sumx += (x1 + x2) * len / 2; - textbin.sumy += (y1 + y2) * len / 2; - textbin.sum += len; - }, npnts = this.getGrNPoints(gr); + const x = gr.fX, y = gr.fY, flush = () => { + if (acc_x) { + grcmd += 'h' + acc_x; + acc_x = 0; + } + if (acc_y) { + grcmd += 'v' + acc_y; + acc_y = 0; + } + }, addPoint = (x1, y1, x2, y2) => { + const len = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2); + textbin.sumx += (x1 + x2) * len / 2; + textbin.sumy += (y1 + y2) * len / 2; + textbin.sum += len; + }, npnts = this.getGrNPoints(gr); if (npnts < 2) return ''; @@ -1483,24 +1819,29 @@ class TH2Painter extends THistPainter { dx = nextx - grx, dy = nexty - gry; - if (textbin) addPoint(grx, gry, nextx, nexty); + if (textbin) + addPoint(grx, gry, nextx, nexty); if (dx || dy) { if (dx === 0) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) flush(); + if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) + flush(); acc_y += dy; } else if (dy === 0) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) flush(); + if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) + flush(); acc_x += dx; } else { flush(); grcmd += `l${dx},${dy}`; } - grx = nextx; gry = nexty; + grx = nextx; + gry = nexty; } } - if (textbin) addPoint(grx, gry, grx0, gry0); + if (textbin) + addPoint(grx, gry, grx0, gry0); flush(); return grcmd ? `M${grx0},${gry0}` + grcmd + 'z' : ''; @@ -1517,22 +1858,22 @@ class TH2Painter extends THistPainter { /** @summary draw TH2Poly bins */ async drawPolyBins() { - const histo = this.getObject(), - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - draw_colors = this.options.Color || (!this.options.Line && !this.options.Fill && !this.options.Text && !this.options.Mark), - draw_lines = this.options.Line || (this.options.Text && !draw_colors), - draw_fill = this.options.Fill && !draw_colors, - draw_mark = this.options.Mark, - h = pmain.getFrameHeight(), + const histo = this.getHisto(), + o = this.getOptions(), + funcs = this.getHistGrFuncs(), + draw_colors = o.Color || (!o.Line && !o.Fill && !o.Text && !o.Mark), + draw_lines = o.Line || (o.Text && !draw_colors), + draw_fill = o.Fill && !draw_colors, + draw_mark = o.Mark, + h = funcs.getFrameHeight(), textbins = [], len = histo.fBins.arr.length; - let colindx, cmd, - full_cmd = '', allmarkers_cmd = '', - bin, item, i, gr0 = null, - lineatt_match = draw_lines, - fillatt_match = draw_fill, - markatt_match = draw_mark; + let colindx, cmd, + full_cmd = '', allmarkers_cmd = '', + bin, item, i, gr0 = null, + lineatt_match = draw_lines, + fillatt_match = draw_fill, + markatt_match = draw_mark; // force recalculations of contours // use global coordinates @@ -1542,21 +1883,25 @@ class TH2Painter extends THistPainter { const cntr = draw_colors ? this.getContour(true) : null, palette = cntr ? this.getHistPalette() : null, - rejectBin = bin => { + rejectBin = bin2 => { // check if bin outside visible range - return ((bin.fXmin > funcs.scale_xmax) || (bin.fXmax < funcs.scale_xmin) || - (bin.fYmin > funcs.scale_ymax) || (bin.fYmax < funcs.scale_ymin)); + return ((bin2.fXmin > funcs.scale_xmax) || (bin2.fXmax < funcs.scale_xmin) || + (bin2.fYmin > funcs.scale_ymax) || (bin2.fYmax < funcs.scale_ymin)); }; // check if similar fill attributes for (i = 0; i < len; ++i) { bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + if (rejectBin(bin)) + continue; const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; for (let k = 0; k < arr.length; ++k) { const gr = arr[k]; - if (!gr0) { gr0 = gr; continue; } + if (!gr0) { + gr0 = gr; + continue; + } if (lineatt_match && ((gr0.fLineColor !== gr.fLineColor) || (gr0.fLineWidth !== gr.fLineWidth) || (gr0.fLineStyle !== gr.fLineStyle))) lineatt_match = false; if (fillatt_match && ((gr0.fFillColor !== gr.fFillColor) || (gr0.fFillStyle !== gr.fFillStyle))) @@ -1569,24 +1914,25 @@ class TH2Painter extends THistPainter { } // do not try color draw optimization as with plain th2 while - // bins are not rectangular and drawings artefacts are nasty + // bins are not rectangular and drawings artifacts are nasty // therefore draw each bin separately when doing color draw const lineatt0 = lineatt_match && gr0 ? this.createAttLine(gr0) : null, fillatt0 = fillatt_match && gr0 ? this.createAttFill(gr0) : null, - markeratt0 = markatt_match && gr0 ? this.createAttMarker({ attr: gr0, style: this.options.MarkStyle, std: false }) : null, + markeratt0 = markatt_match && gr0 ? this.createAttMarker({ attr: gr0, style: o.MarkStyle, std: false }) : null, optimize_draw = !draw_colors && (draw_lines ? lineatt_match : true) && (draw_fill ? fillatt_match : true); // draw bins for (i = 0; i < len; ++i) { bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + if (rejectBin(bin)) + continue; - const draw_bin = bin.fContent || this.options.Zero, + const draw_bin = bin.fContent || o.Zero, arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; colindx = draw_colors && draw_bin ? cntr.getPaletteIndex(palette, bin.fContent) : null; - const textbin = this.options.Text && draw_bin ? { bin, sumx: 0, sumy: 0, sum: 0 } : null; + const textbin = o.Text && draw_bin ? { bin, sumx: 0, sumy: 0, sum: 0 } : null; for (let k = 0; k < arr.length; ++k) { const gr = arr[k]; @@ -1597,16 +1943,17 @@ class TH2Painter extends THistPainter { } cmd = this.createPolyGr(funcs, gr, textbin); - if (!cmd) continue; + if (!cmd) + continue; if (optimize_draw) full_cmd += cmd; else if ((colindx !== null) || draw_fill || draw_lines) { - item = this.draw_g.append('svg:path').attr('d', cmd); + item = this.appendPath(cmd); if (draw_colors && (colindx !== null)) - item.style('fill', this._color_palette.getColor(colindx)); + item.style('fill', palette.getColor(colindx)); else if (draw_fill) - item.call('fill', this.createAttFill(gr).func); + item.call(this.createAttFill(gr).func); else item.style('fill', 'none'); if (draw_lines) @@ -1619,7 +1966,7 @@ class TH2Painter extends THistPainter { } // loop over bins if (optimize_draw) { - item = this.draw_g.append('svg:path').attr('d', full_cmd); + item = this.appendPath(full_cmd); if (draw_fill && fillatt0) item.call(fillatt0.func); else @@ -1629,70 +1976,69 @@ class TH2Painter extends THistPainter { } if (markeratt0 && !markeratt0.empty() && allmarkers_cmd) { - this.draw_g.append('svg:path') - .attr('d', allmarkers_cmd) - .call(markeratt0.func); + this.appendPath(allmarkers_cmd) + .call(markeratt0.func); } else if (draw_mark) { for (i = 0; i < len; ++i) { bin = histo.fBins.arr[i]; - if (rejectBin(bin)) continue; + if (rejectBin(bin)) + continue; const arr = (bin.fPoly._typename === clTMultiGraph) ? bin.fPoly.fGraphs.arr : [bin.fPoly]; for (let k = 0; k < arr.length; ++k) { const gr = arr[k], npnts = this.getGrNPoints(gr), - markeratt = this.createAttMarker({ attr: gr, style: this.options.MarkStyle, std: false }); + markeratt = this.createAttMarker({ attr: gr, style: o.MarkStyle, std: false }); if (!npnts || markeratt.empty()) continue; - let cmd = ''; + let cmdm = ''; for (let n = 0; n < npnts; ++n) - cmd += markeratt.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); + cmdm += markeratt.create(funcs.grx(gr.fX[n]), funcs.gry(gr.fY[n])); - this.draw_g.append('svg:path') - .attr('d', cmd) + this.appendPath(cmdm) .call(markeratt.func); } // loop over graphs } // loop over bins } - let pr = Promise.resolve(true); + let pr = Promise.resolve(); - if (textbins.length > 0) { + if (textbins.length) { const color = this.getColor(histo.fMarkerColor), - rotate = -1*this.options.TextAngle, - text_g = this.draw_g.append('svg:g').attr('class', 'th2poly_text'), - text_size = ((histo.fMarkerSize !== 1) && rotate) ? Math.round(0.02*h*histo.fMarkerSize) : 12; + rotate = -1 * o.TextAngle, + text_g = this.getG().append('svg:g').attr('class', 'th2poly_text'), + text_size = ((histo.fMarkerSize !== 1) && rotate) ? Math.round(0.02 * h * histo.fMarkerSize) : 12; - this.startTextDrawing(42, text_size, text_g, text_size); + pr = this.startTextDrawingAsync(42, text_size, text_g, text_size).then(() => { + for (i = 0; i < textbins.length; ++i) { + const textbin = textbins[i]; - for (i = 0; i < textbins.length; ++i) { - const textbin = textbins[i]; + bin = textbin.bin; - bin = textbin.bin; + if (textbin.sum > 0) { + textbin.midx = Math.round(textbin.sumx / textbin.sum); + textbin.midy = Math.round(textbin.sumy / textbin.sum); + } else { + textbin.midx = Math.round(funcs.grx((bin.fXmin + bin.fXmax) / 2)); + textbin.midy = Math.round(funcs.gry((bin.fYmin + bin.fYmax) / 2)); + } - if (textbin.sum > 0) { - textbin.midx = Math.round(textbin.sumx / textbin.sum); - textbin.midy = Math.round(textbin.sumy / textbin.sum); - } else { - textbin.midx = Math.round(funcs.grx((bin.fXmin + bin.fXmax)/2)); - textbin.midy = Math.round(funcs.gry((bin.fYmin + bin.fYmax)/2)); - } + let text; - let text; + if (!o.TextKind) + text = (Math.round(bin.fContent) === bin.fContent) ? bin.fContent.toString() : floatToString(bin.fContent, gStyle.fPaintTextFormat); + else { + text = bin.fPoly?.fName; + if (!text || (text === 'Graph')) + text = bin.fNumber.toString(); + } - if (!this.options.TextKind) - text = (Math.round(bin.fContent) === bin.fContent) ? bin.fContent.toString() : floatToString(bin.fContent, gStyle.fPaintTextFormat); - else { - text = bin.fPoly?.fName; - if (!text || (text === 'Graph')) - text = bin.fNumber.toString(); + this.drawText({ align: 22, x: textbin.midx, y: textbin.midy, rotate, text, color, latex: 0, draw_g: text_g }); } - this.drawText({ align: 22, x: textbin.midx, y: textbin.midy, rotate, text, color, latex: 0, draw_g: text_g }); - } - - pr = this.finishTextDrawing(text_g, true); + return this.finishTextDrawing(text_g, true); + }); } return pr.then(() => { return { poly: true }; }); @@ -1700,161 +2046,186 @@ class TH2Painter extends THistPainter { /** @summary Draw TH2 bins as text */ async drawBinsText(handle) { - const histo = this.getObject(), - test_cutg = this.options.cutg, - color = this.getColor(histo.fMarkerColor), - rotate = -1*this.options.TextAngle, - draw_g = this.draw_g.append('svg:g').attr('class', 'th2_text'), - profile2d = this.matchObjectType(clTProfile2D) && isFunc(histo.getBinEntries), - show_err = (this.options.TextKind === 'E'), - latex = (show_err && !this.options.TextLine) ? 1 : 0; - let x, y, width, height, - text_size = 20, text_offset = 0; - - if (!handle) handle = this.prepareDraw({ rounding: false }); - - if ((histo.fMarkerSize !== 1) && rotate) - text_size = Math.round(0.02*histo.fMarkerSize*this.getFramePainter().getFrameHeight()); - - if (histo.fBarOffset !== 0) text_offset = histo.fBarOffset*1e-3; + if (!handle) + handle = this.prepareDraw({ rounding: false }); - this.startTextDrawing(42, text_size, draw_g, text_size); + const histo = this.getHisto(), + o = this.getOptions(), + test_cutg = o.cutg, + color = this.getColor(histo.fMarkerColor), + rotate = -1 * o.TextAngle, + draw_g = this.getG().append('svg:g').attr('class', 'th2_text'), + show_err = (o.TextKind === 'E'), + latex = (show_err && !o.TextLine) ? 1 : 0, + text_offset = histo.fBarOffset * 1e-3, + text_size = ((histo.fMarkerSize === 1) || !rotate) ? 20 : Math.round(0.02 * histo.fMarkerSize * handle.height); + + return this.startTextDrawingAsync(42, text_size, draw_g, text_size).then(() => { + for (let i = handle.i1; i < handle.i2; ++i) { + const binw = handle.grx[i + 1] - handle.grx[i]; + for (let j = handle.j1; j < handle.j2; ++j) { + const binz = histo.getBinContent(i + 1, j + 1); + if ((binz === 0) && !o.ShowEmpty) + continue; - for (let i = handle.i1; i < handle.i2; ++i) { - const binw = handle.grx[i+1] - handle.grx[i]; - for (let j = handle.j1; j < handle.j2; ++j) { - let binz = histo.getBinContent(i+1, j+1); - if ((binz === 0) && !this._show_empty_bins) continue; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + const binh = handle.gry[j] - handle.gry[j + 1]; - const binh = handle.gry[j] - handle.gry[j+1]; + let text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); - if (profile2d) - binz = histo.getBinEntries(i+1, j+1); + if (show_err) { + const errs = this.getBinErrors(histo, histo.getBin(i + 1, j + 1), binz); + if (errs.poisson) { + const lble = `-${floatToString(errs.low, gStyle.fPaintTextFormat)} +${floatToString(errs.up, gStyle.fPaintTextFormat)}`; + if (o.TextLine) + text += ' ' + lble; + else + text = `#splitmline{${text}}{${lble}}`; + } else { + const lble = (errs.up === Math.round(errs.up)) ? errs.up.toString() : floatToString(errs.up, gStyle.fPaintTextFormat); + if (o.TextLine) + text += '\xB1' + lble; + else + text = `#splitmline{${text}}{#pm${lble}}`; + } + } - let text = (binz === Math.round(binz)) ? binz.toString() : floatToString(binz, gStyle.fPaintTextFormat); + let x, y, width, height; - if (show_err) { - const errz = histo.getBinError(histo.getBin(i+1, j+1)), - lble = (errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat); - if (this.options.TextLine) - text += '\xB1' + lble; - else - text = `#splitline{${text}}{#pm${lble}}`; - } + if (rotate) { + x = Math.round(handle.grx[i] + binw * 0.5); + y = Math.round(handle.gry[j + 1] + binh * (0.5 + text_offset)); + width = height = 0; + } else { + x = Math.round(handle.grx[i] + binw * 0.1); + y = Math.round(handle.gry[j + 1] + binh * (0.1 + text_offset)); + width = Math.round(binw * 0.8); + height = Math.round(binh * 0.8); + } - if (rotate /* || (histo.fMarkerSize !== 1) */) { - x = Math.round(handle.grx[i] + binw*0.5); - y = Math.round(handle.gry[j+1] + binh*(0.5 + text_offset)); - width = height = 0; - } else { - x = Math.round(handle.grx[i] + binw*0.1); - y = Math.round(handle.gry[j+1] + binh*(0.1 + text_offset)); - width = Math.round(binw*0.8); - height = Math.round(binh*0.8); + this.drawText({ align: 22, x, y, width, height, rotate, text, color, latex, draw_g }); } - - this.drawText({ align: 22, x, y, width, height, rotate, text, color, latex, draw_g }); } - } - handle.hide_only_zeros = true; // text drawing suppress only zeros + handle.hide_only_zeros = true; // text drawing suppress only zeros - return this.finishTextDrawing(draw_g, true).then(() => handle); + return this.finishTextDrawing(draw_g, true); + }).then(() => handle); } /** @summary Draw TH2 bins as arrows */ drawBinsArrow() { - const histo = this.getObject(), - test_cutg = this.options.cutg, + const histo = this.getHisto(), + o = this.getOptions(), + test_cutg = o.cutg, handle = this.prepareDraw({ rounding: false }), - scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1])/(handle.i2 - handle.i1 + 1)/2, - scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1])/(handle.j2 - handle.j1 + 1)/2, - makeLine = (dx, dy) => dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); - let i, j, dn = 1e-30, dx, dy, xc, yc, cmd = '', - dxn, dyn, x1, x2, y1, y2, anr, si, co; + cntr = o.Color ? this.getContour() : null, + palette = o.Color ? this.getHistPalette() : null, + scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1]) / (handle.i2 - handle.i1 + 1) / 2, + scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1]) / (handle.j2 - handle.j1 + 1) / 2, + makeLine = (dx, dy) => { return dx ? (dy ? `l${dx},${dy}` : `h${dx}`) : (dy ? `v${dy}` : ''); }, + entries = []; + let dn = 1e-30, dx, dy, xc, yc, plain = '', + dxn, dyn, x1, x2, y1, y2; for (let loop = 0; loop < 2; ++loop) { - for (i = handle.i1; i < handle.i2; ++i) { - for (j = handle.j1; j < handle.j2; ++j) { + for (let i = handle.i1; i < handle.i2; ++i) { + for (let j = handle.j1; j < handle.j2; ++j) { if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; + + const bincont = histo.getBinContent(i + 1, j + 1); if (i === handle.i1) - dx = histo.getBinContent(i+2, j+1) - histo.getBinContent(i+1, j+1); - else if (i === handle.i2-1) - dx = histo.getBinContent(i+1, j+1) - histo.getBinContent(i, j+1); + dx = histo.getBinContent(i + 2, j + 1) - bincont; + else if (i === handle.i2 - 1) + dx = bincont - histo.getBinContent(i, j + 1); else - dx = 0.5*(histo.getBinContent(i+2, j+1) - histo.getBinContent(i, j+1)); + dx = 0.5 * (histo.getBinContent(i + 2, j + 1) - histo.getBinContent(i, j + 1)); if (j === handle.j1) - dy = histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j+1); - else if (j === handle.j2-1) - dy = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1, j); + dy = histo.getBinContent(i + 1, j + 2) - bincont; + else if (j === handle.j2 - 1) + dy = bincont - histo.getBinContent(i + 1, j); else - dy = 0.5*(histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j)); + dy = 0.5 * (histo.getBinContent(i + 1, j + 2) - histo.getBinContent(i + 1, j)); if (loop === 0) dn = Math.max(dn, Math.abs(dx), Math.abs(dy)); - else { - xc = (handle.grx[i] + handle.grx[i+1])/2; - yc = (handle.gry[j] + handle.gry[j+1])/2; - dxn = scale_x*dx/dn; - dyn = scale_y*dy/dn; + else { + xc = (handle.grx[i] + handle.grx[i + 1]) / 2; + yc = (handle.gry[j] + handle.gry[j + 1]) / 2; + dxn = scale_x * dx / dn; + dyn = scale_y * dy / dn; x1 = xc - dxn; x2 = xc + dxn; y1 = yc - dyn; y2 = yc + dyn; - dx = Math.round(x2-x1); - dy = Math.round(y2-y1); + dx = Math.round(x2 - x1); + dy = Math.round(y2 - y1); if (dx || dy) { - cmd += `M${Math.round(x1)},${Math.round(y1)}${makeLine(dx, dy)}`; + let cmd = `M${Math.round(x1)},${Math.round(y1)}${makeLine(dx, dy)}`; if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { - anr = Math.sqrt(9/(dx**2 + dy**2)); - si = Math.round(anr*(dx + dy)); - co = Math.round(anr*(dx - dy)); + const anr = Math.sqrt(9 / (dx ** 2 + dy ** 2)), + si = Math.round(anr * (dx + dy)), + co = Math.round(anr * (dx - dy)); if (si || co) cmd += `m${-si},${co}${makeLine(si, -co)}${makeLine(-co, -si)}`; } + + if (palette && cntr) { + const colindx = cntr.getPaletteIndex(palette, bincont); + if (colindx !== null) { + const entry = entries[colindx]; + if (!entry) + entries[colindx] = { path: cmd }; + else + entry.path += cmd; + } + } else + plain += cmd; } } } } } - this.draw_g - .append('svg:path') - .attr('d', cmd) - .style('fill', 'none') - .call(this.lineatt.func); + if (plain) { + this.appendPath(plain) + .style('fill', 'none') + .call(this.lineatt.func); + } + + entries.forEach((entry, colindx) => { + if (entry) { + const col0 = this.lineatt.color; + this.lineatt.color = palette.getColor(colindx); + this.appendPath(entry.path) + .attr('fill', 'none') + .call(this.lineatt.func); + this.lineatt.color = col0; + } + }); return handle; } /** @summary Draw TH2 bins as boxes */ drawBinsBox() { - const histo = this.getObject(), - handle = this.prepareDraw({ rounding: false }), - main = this.getMainPainter(); - - if (main === this) { - if (main.maxbin === main.minbin) { - main.maxbin = main.gmaxbin; - main.minbin = main.gminbin; - main.minposbin = main.gminposbin; - } - if (main.maxbin === main.minbin) - main.minbin = Math.min(0, main.maxbin-1); - } - - const absmax = Math.max(Math.abs(main.maxbin), Math.abs(main.minbin)), - absmin = Math.max(0, main.minbin), + const histo = this.getHisto(), + o = this.getOptions(), + handle = this.prepareDraw({ rounding: false, zrange: true }), + absmax = Math.max(Math.abs(handle.zmin), Math.abs(handle.zmax)), + absmin = Math.max(0, handle.zmin), pad = this.getPadPainter().getRootPad(true), - test_cutg = this.options.cutg; + test_cutg = o.cutg; + let i, j, binz, absz, res = '', cross = '', btn1 = '', btn2 = '', zdiff, dgrx, dgry, xx, yy, ww, hh, xyfactor, uselogz = false, logmin = 0; @@ -1864,11 +2235,12 @@ class TH2Painter extends THistPainter { const logmax = Math.log(absmax); if (absmin > 0) logmin = Math.log(absmin); - else if ((main.minposbin>=1) && (main.minposbin<100)) + else if ((handle.zminpos >= 1) && (handle.zminpos < 100)) logmin = Math.log(0.7); else - logmin = (main.minposbin > 0) ? Math.log(0.7*main.minposbin) : logmax - 10; - if (logmin >= logmax) logmin = logmax - 10; + logmin = (handle.zminpos > 0) ? Math.log(0.7 * handle.zminpos) : logmax - 10; + if (logmin >= logmax) + logmin = logmax - 10; xyfactor = 1.0 / (logmax - logmin); } else xyfactor = 1.0 / (absmax - absmin); @@ -1879,71 +2251,65 @@ class TH2Painter extends THistPainter { for (j = handle.j1; j < handle.j2; ++j) { binz = histo.getBinContent(i + 1, j + 1); absz = Math.abs(binz); - if ((absz === 0) || (absz < absmin)) continue; + if ((absz === 0) || (absz < absmin)) + continue; if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; zdiff = uselogz ? ((absz > 0) ? Math.log(absz) - logmin : 0) : (absz - absmin); // area of the box should be proportional to absolute bin content zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor))); // avoid oversized bins - if (zdiff < 0) zdiff = 0; + if (zdiff < 0) + zdiff = 0; - ww = handle.grx[i+1] - handle.grx[i]; - hh = handle.gry[j] - handle.gry[j+1]; + ww = handle.grx[i + 1] - handle.grx[i]; + hh = handle.gry[j] - handle.gry[j + 1]; dgrx = zdiff * ww; dgry = zdiff * hh; xx = Math.round(handle.grx[i] + dgrx); - yy = Math.round(handle.gry[j+1] + dgry); + yy = Math.round(handle.gry[j + 1] + dgry); - ww = Math.max(Math.round(ww - 2*dgrx), 1); - hh = Math.max(Math.round(hh - 2*dgry), 1); + ww = Math.max(Math.round(ww - 2 * dgrx), 1); + hh = Math.max(Math.round(hh - 2 * dgry), 1); res += `M${xx},${yy}v${hh}h${ww}v${-hh}z`; - if ((binz < 0) && (this.options.BoxStyle === 10)) + if ((binz < 0) && (o.BoxStyle === 10)) cross += `M${xx},${yy}l${ww},${hh}m0,${-hh}l${-ww},${hh}`; - if ((this.options.BoxStyle === 11) && (ww > 5) && (hh > 5)) { - const pww = Math.round(ww*0.1), - phh = Math.round(hh*0.1), - side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2*pww-ww}v${hh-2*phh}l${-pww},${phh}z`, - side2 = `M${xx+ww},${yy+hh}v${-hh}l${-pww},${phh}v${hh-2*phh}h${2*pww-ww}l${-pww},${phh}z`; - btn1 += (binz < 0) ? side2 : side1; - btn2 += (binz < 0) ? side1 : side2; + if ((o.BoxStyle === 11) && (ww > 5) && (hh > 5)) { + const arr = getBoxDecorations(xx, yy, ww, hh, binz < 0 ? -1 : 1, Math.round(ww * 0.1), Math.round(hh * 0.1)); + btn1 += arr[0]; + btn2 += arr[1]; } } } if (res) { - const elem = this.draw_g.append('svg:path') - .attr('d', res) - .call(this.fillatt.func); - if ((this.options.BoxStyle !== 11) && this.fillatt.empty()) + const elem = this.appendPath(res).call(this.fillatt.func); + if ((o.BoxStyle !== 11) && this.fillatt.empty()) elem.call(this.lineatt.func); } if (btn1 && this.fillatt.hasColor()) { - this.draw_g.append('svg:path') - .attr('d', btn1) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatHex()); + this.appendPath(btn1) + .call(this.fillatt.func) + .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); } if (btn2) { - this.draw_g.append('svg:path') - .attr('d', btn2) - .call(this.fillatt.func) - .style('fill', !this.fillatt.hasColor() ? 'red' : d3_rgb(this.fillatt.color).darker(0.5).formatHex()); + this.appendPath(btn2) + .call(this.fillatt.func) + .style('fill', !this.fillatt.hasColor() ? 'red' : d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); } if (cross) { - const elem = this.draw_g.append('svg:path') - .attr('d', cross) - .style('fill', 'none'); + const elem = this.appendPath(cross).style('fill', 'none'); if (!this.lineatt.empty()) elem.call(this.lineatt.func); else @@ -1978,19 +2344,24 @@ class TH2Painter extends THistPainter { let fOption = kNoOption; - const isOption = opt => { + const o = this.getOptions(), isOption = opt => { let mult = 1; - while (opt >= mult) mult *= 10; + while (opt >= mult) + mult *= 10; mult /= 10; - return Math.floor(fOption/mult) % 10 === Math.floor(opt/mult); + return Math.floor(fOption / mult) % 10 === Math.floor(opt / mult); }, parseOption = (opt, is_candle) => { let direction = '', preset = '', res = kNoOption; const c0 = opt[0], c1 = opt[1]; - if (c0 >= 'A' && c0 <= 'Z') direction = c0; - if (c0 >= '1' && c0 <= '9') preset = c0; - if (c1 >= 'A' && c1 <= 'Z' && preset) direction = c1; - if (c1 >= '1' && c1 <= '9' && direction) preset = c1; + if (c0 >= 'A' && c0 <= 'Z') + direction = c0; + if (c0 >= '1' && c0 <= '9') + preset = c0; + if (c1 >= 'A' && c1 <= 'Z' && preset) + direction = c1; + if (c1 >= '1' && c1 <= '9' && direction) + preset = c1; if (is_candle) { switch (preset) { @@ -2011,8 +2382,8 @@ class TH2Painter extends THistPainter { } const l = opt.indexOf('('), r = opt.lastIndexOf(')'); - if ((l >= 0) && (r > l+1)) - res = parseInt(opt.slice(l+1, r)); + if ((l >= 0) && (r > l + 1)) + res = parseInt(opt.slice(l + 1, r)); fOption = res; @@ -2025,16 +2396,18 @@ class TH2Painter extends THistPainter { for (let j = 0; j < proj.length; ++j) { if (proj[j] > 0) { res.max = Math.max(res.max, proj[j]); - if (res.first < 0) res.first = j; + if (res.first < 0) + res.first = j; res.last = j; } integral += proj[j]; - sum1 += proj[j]*(xx[j]+xx[j+1])/2; + sum1 += proj[j] * (xx[j] + xx[j + 1]) / 2; } - if (integral <= 0) return null; + if (integral <= 0) + return null; res.entries = integral; - res.mean = sum1/integral; + res.mean = sum1 / integral; res.quantiles = new Array(prob.length); res.indx = new Array(prob.length); @@ -2044,7 +2417,8 @@ class TH2Painter extends THistPainter { // special case - flat integral with const value if ((v === prob[cnt]) && (proj[j] === 0) && (v < 0.99)) { - while ((proj[j] === 0) && (j < proj.length)) j++; + while ((proj[j] === 0) && (j < proj.length)) + j++; x = (xx[j] + x) / 2; // this will be mid value } @@ -2053,40 +2427,37 @@ class TH2Painter extends THistPainter { while ((prob[cnt] >= v) && (prob[cnt] < nextv)) { res.indx[cnt] = j; res.quantiles[cnt] = x + ((prob[cnt] - v) / (nextv - v)) * (xx[j + 1] - x); - if (cnt++ === prob.length) return res; + if (cnt++ === prob.length) + return res; x = xx[j]; } } while (cnt < prob.length) { - res.indx[cnt] = proj.length-1; - res.quantiles[cnt++] = xx[xx.length-1]; + res.indx[cnt] = proj.length - 1; + res.quantiles[cnt++] = xx.at(-1); } return res; }; - if (this.options.Candle) - parseOption(this.options.Candle, true); - else if (this.options.Violin) - parseOption(this.options.Violin, false); + if (o.Candle) + parseOption(o.Candle, true); + else if (o.Violin) + parseOption(o.Violin, false); const histo = this.getHisto(), handle = this.prepareDraw(), - pmain = this.getFramePainter(), // used for axis values conversions cp = this.getCanvPainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + funcs = this.getHistGrFuncs(), swapXY = isOption(kHorizontal); - let bars = '', lines = '', dashed_lines = '', - hists = '', hlines = '', - markers = '', cmarkers = '', attrcmarkers = null, - xx, proj, - scaledViolin = gStyle.fViolinScaled, + let scaledViolin = gStyle.fViolinScaled, scaledCandle = gStyle.fCandleScaled, - maxContent = 0, maxIntegral = 0; + maxContent = 0, + markers = '', cmarkers = '', attrcmarkers = null; - if (this.options.Scaled !== null) - scaledViolin = scaledCandle = this.options.Scaled; + if (o.Scaled !== null) + scaledViolin = scaledCandle = o.Scaled; else if (cp?.online_canvas) { // console.log('ignore hist title in online canvas'); } else if (histo.fTitle.indexOf('unscaled') >= 0) @@ -2098,102 +2469,126 @@ class TH2Painter extends THistPainter { for (let i = 0; i < this.nbinsx; ++i) { for (let j = 0; j < this.nbinsy; ++j) maxContent = Math.max(maxContent, histo.getBinContent(i + 1, j + 1)); - } + } } const make_path = (...a) => { - if (a[1] === 'array') a = a[0]; + if (a[1] === 'array') + a = a[0]; const l = a.length; let i = 2, xx = a[0], yy = a[1], res = swapXY ? `M${yy},${xx}` : `M${xx},${yy}`; while (i < l) { switch (a[i]) { - case 'Z': return res + 'z'; - case 'V': if (yy !== a[i+1]) { res += (swapXY ? 'h' : 'v') + (a[i+1] - yy); yy = a[i+1]; } break; - case 'H': if (xx !== a[i+1]) { res += (swapXY ? 'v' : 'h') + (a[i+1] - xx); xx = a[i+1]; } break; - default: res += swapXY ? `l${a[i+1]-yy},${a[i]-xx}` : `l${a[i]-xx},${a[i+1]-yy}`; xx = a[i]; yy = a[i+1]; + case 'Z': + return res + 'z'; + case 'V': + if (yy !== a[i + 1]) { + res += (swapXY ? 'h' : 'v') + (a[i + 1] - yy); + yy = a[i + 1]; + } + break; + case 'H': + if (xx !== a[i + 1]) { + res += (swapXY ? 'v' : 'h') + (a[i + 1] - xx); + xx = a[i + 1]; + } + break; + default: + res += swapXY ? `l${a[i + 1] - yy},${a[i] - xx}` : `l${a[i] - xx},${a[i + 1] - yy}`; + xx = a[i]; + yy = a[i + 1]; } i += 2; } return res; }, make_marker = (x, y) => { if (!markers) { - this.createAttMarker({ attr: histo, style: isOption(kPointsAllScat) ? 0 : 5 }); + const mw = gStyle.fCandleCrossLineWidth ?? 1; + this.createAttMarker({ attr: histo, style: isOption(kPointsAllScat) ? 0 : (mw === 1 ? 5 : 18 * mw + 16) }); this.markeratt.resetPos(); } markers += swapXY ? this.markeratt.create(y, x) : this.markeratt.create(x, y); }, make_cmarker = (x, y) => { if (!attrcmarkers) { - attrcmarkers = this.createAttMarker({ attr: histo, style: 24, std: false }); + const mw = gStyle.fCandleCircleLineWidth ?? 1; + attrcmarkers = this.createAttMarker({ attr: histo, style: (mw === 1 ? 24 : 18 * mw + 17), std: false }); attrcmarkers.resetPos(); } cmarkers += swapXY ? attrcmarkers.create(y, x) : attrcmarkers.create(x, y); }; - // if ((histo.fFillStyle === 0) && (histo.fFillColor > 0) && (!this.fillatt || this.fillatt.empty())) - // this.createAttFill({ color: this.getColor(histo.fFillColor), pattern: 1001 }); - - if (histo.fMarkerColor === 1) histo.fMarkerColor = histo.fLineColor; + if (histo.fMarkerColor === 1) + histo.fMarkerColor = histo.fLineColor; handle.candle = []; // array of drawn points - // Determining the quantiles - const wRange = gStyle.fCandleWhiskerRange, bRange = gStyle.fCandleBoxRange, - prob = [(wRange >= 1) ? 1e-15 : 0.5 - wRange/2.0, - (bRange >= 1) ? 1E-14 : 0.5 - bRange/2.0, - 0.5, - (bRange >= 1) ? 1-1E-14 : 0.5 + bRange/2.0, - (wRange >= 1) ? 1-1e-15 : 0.5 + wRange/2.0], + let xx, bars = '', lines = '', dashed_lines = '', hists = '', hlines = '', + proj, maxIntegral = 0; - produceCandlePoint = (bin_indx, grx_left, grx_right, xindx1, xindx2) => { + // Determining the quintiles + const wRange = gStyle.fCandleWhiskerRange, bRange = gStyle.fCandleBoxRange, + prob = [(wRange >= 1) ? 1e-15 : 0.5 - wRange / 2.0, + (bRange >= 1) ? 1e-14 : 0.5 - bRange / 2.0, + 0.5, + (bRange >= 1) ? 1 - 1e-14 : 0.5 + bRange / 2.0, + (wRange >= 1) ? 1 - 1e-15 : 0.5 + wRange / 2.0]; + + /* eslint-disable-next-line one-var */ + const produceCandlePoint = (bin_indx, grx_left, grx_right, xindx1, xindx2) => { const res = extractQuantiles(xx, proj, prob); - if (!res) return; + if (!res) + return; const pnt = { bin: bin_indx, swapXY, fBoxDown: res.quantiles[1], fMedian: res.quantiles[2], fBoxUp: res.quantiles[3] }, iqr = pnt.fBoxUp - pnt.fBoxDown; let fWhiskerDown = res.quantiles[0], fWhiskerUp = res.quantiles[4]; if (isOption(kWhisker15)) { // Improved whisker definition, with 1.5*iqr - let pos = pnt.fBoxDown-1.5*iqr, indx = res.indx[1]; - while ((xx[indx] > pos) && (indx > 0)) indx--; - while (!proj[indx]) indx++; + let pos = pnt.fBoxDown - 1.5 * iqr, indx = res.indx[1]; + while ((xx[indx] > pos) && (indx > 0)) + indx--; + while (!proj[indx]) + indx++; fWhiskerDown = xx[indx]; // use lower edge here - pos = pnt.fBoxUp+1.5*iqr; indx = res.indx[3]; - while ((xx[indx] < pos) && (indx < proj.length)) indx++; - while (!proj[indx]) indx--; - fWhiskerUp = xx[indx+1]; // use upper index edge here + pos = pnt.fBoxUp + 1.5 * iqr; indx = res.indx[3]; + while ((xx[indx] < pos) && (indx < proj.length)) + indx++; + while (!proj[indx]) + indx--; + fWhiskerUp = xx[indx + 1]; // use upper index edge here } const fMean = res.mean, - fMedianErr = 1.57*iqr/Math.sqrt(res.entries); + fMedianErr = 1.57 * iqr / Math.sqrt(res.entries); // estimate quantiles... simple function... not so nice as GetQuantiles // exclude points with negative y when log scale is specified - if (fWhiskerDown <= 0) - if ((swapXY && funcs.logx) || (!swapXY && funcs.logy)) return; + if ((fWhiskerDown <= 0) && ((swapXY && funcs.logx) || (!swapXY && funcs.logy))) + return; const w = (grx_right - grx_left); let candleWidth, histoWidth, - center = (grx_left + grx_right) / 2 + histo.fBarOffset/1000*w; + center = (grx_left + grx_right) / 2 + histo.fBarOffset / 1000 * w; if ((histo.fBarWidth > 0) && (histo.fBarWidth !== 1000)) candleWidth = histoWidth = w * histo.fBarWidth / 1000; - else { - candleWidth = w*0.66; - histoWidth = w*0.8; + else { + candleWidth = w * 0.66; + histoWidth = w * 0.8; } if (scaledViolin && (maxContent > 0)) - histoWidth *= res.max/maxContent; + histoWidth *= res.max / maxContent; if (scaledCandle && (maxIntegral > 0)) - candleWidth *= res.entries/maxIntegral; + candleWidth *= res.entries / maxIntegral; - pnt.x1 = Math.round(center - candleWidth/2); - pnt.x2 = Math.round(center + candleWidth/2); + pnt.x1 = Math.round(center - candleWidth / 2); + pnt.x2 = Math.round(center + candleWidth / 2); center = Math.round(center); - const x1d = Math.round(center - candleWidth/3), - x2d = Math.round(center + candleWidth/3), + const x1d = Math.round(center - candleWidth / 3), + x2d = Math.round(center + candleWidth / 3), ff = swapXY ? funcs.grx : funcs.gry; pnt.yy1 = Math.round(ff(fWhiskerUp)); @@ -2228,7 +2623,7 @@ class TH2Painter extends THistPainter { bars += make_path(pnt.x1, pnt.y1, 'V', pnt.y2, 'H', pnt.x2, 'V', pnt.y1, 'Z'); } - if (isOption(kAnchor)) // Draw the anchor line + if (isOption(kAnchor)) // Draw the anchor line lines += make_path(pnt.x1, pnt.yy1, 'H', pnt.x2) + make_path(pnt.x1, pnt.yy2, 'H', pnt.x2); if (isOption(kWhiskerAll) && !isOption(kHistoZeroIndicator)) { // Whiskers are dashed @@ -2239,15 +2634,17 @@ class TH2Painter extends THistPainter { if (isOption(kPointsOutliers) || isOption(kPointsAll) || isOption(kPointsAllScat)) { // reset seed for each projection to have always same pixels - const rnd = new TRandom(bin_indx*7521 + Math.round(res.integral)), - show_all = !isOption(kPointsOutliers), - show_scat = isOption(kPointsAllScat); + const rnd = new TRandom(bin_indx * 7521 + Math.round(res.integral)), + show_all = !isOption(kPointsOutliers), + show_scat = isOption(kPointsAllScat); for (let ii = 0; ii < proj.length; ++ii) { - const bin_content = proj[ii], binx = (xx[ii] + xx[ii+1])/2; - let marker_x = center, marker_y = 0; + const bin_content = proj[ii], binx = (xx[ii] + xx[ii + 1]) / 2; + let marker_x = center, marker_y; - if (!bin_content) continue; - if (!show_all && (binx >= fWhiskerDown) && (binx <= fWhiskerUp)) continue; + if (!bin_content) + continue; + if (!show_all && (binx >= fWhiskerDown) && (binx <= fWhiskerUp)) + continue; for (let k = 0; k < bin_content; k++) { if (show_scat) @@ -2256,7 +2653,7 @@ class TH2Painter extends THistPainter { if ((bin_content === 1) && !show_scat) marker_y = Math.round(ff(binx)); else - marker_y = Math.round(ff(xx[ii] + rnd.random()*(xx[ii+1]-xx[ii]))); + marker_y = Math.round(ff(xx[ii] + rnd.random() * (xx[ii + 1] - xx[ii]))); make_marker(marker_x, marker_y); } @@ -2264,19 +2661,20 @@ class TH2Painter extends THistPainter { } if ((isOption(kHistoRight) || isOption(kHistoLeft) || isOption(kHistoViolin)) && (res.max > 0) && (res.first >= 0)) { - const arr = [], scale = (swapXY ? -0.5 : 0.5) *histoWidth/res.max; + const arr = [], scale = (swapXY ? -0.5 : 0.5) * histoWidth / res.max; xindx1 = Math.max(xindx1, res.first); - xindx2 = Math.min(xindx2-1, res.last); + xindx2 = Math.min(xindx2 - 1, res.last); if (isOption(kHistoRight) || isOption(kHistoViolin)) { let prev_x = center, prev_y = Math.round(ff(xx[xindx1])); arr.push(prev_x, prev_y); for (let ii = xindx1; ii <= xindx2; ii++) { - const curr_x = Math.round(center + scale*proj[ii]), - curr_y = Math.round(ff(xx[ii+1])); + const curr_x = Math.round(center + scale * proj[ii]), + curr_y = Math.round(ff(xx[ii + 1])); if (curr_x !== prev_x) { - if (ii !== xindx1) arr.push('V', prev_y); + if (ii !== xindx1) + arr.push('V', prev_y); arr.push('H', curr_x); } prev_x = curr_x; @@ -2286,14 +2684,15 @@ class TH2Painter extends THistPainter { } if (isOption(kHistoLeft) || isOption(kHistoViolin)) { - let prev_x = center, prev_y = Math.round(ff(xx[xindx2+1])); - if (arr.length === 0) + let prev_x = center, prev_y = Math.round(ff(xx[xindx2 + 1])); + if (!arr.length) arr.push(prev_x, prev_y); for (let ii = xindx2; ii >= xindx1; ii--) { - const curr_x = Math.round(center - scale*proj[ii]), - curr_y = Math.round(ff(xx[ii])); + const curr_x = Math.round(center - scale * proj[ii]), + curr_y = Math.round(ff(xx[ii])); if (curr_x !== prev_x) { - if (ii !== xindx2) arr.push('V', prev_y); + if (ii !== xindx2) + arr.push('V', prev_y); arr.push('H', curr_x); } prev_x = curr_x; @@ -2306,45 +2705,46 @@ class TH2Painter extends THistPainter { hists += make_path(arr, 'array'); - if (!this.fillatt.empty()) hists += 'Z'; + if (!this.fillatt.empty()) + hists += 'Z'; } handle.candle.push(pnt); // keep point for the tooltip }; if (swapXY) { - xx = new Array(this.nbinsx+1); + xx = new Array(this.nbinsx + 1); proj = new Array(this.nbinsx); - for (let i = 0; i < this.nbinsx+1; ++i) - xx[i] = histo.fXaxis.GetBinLowEdge(i+1); + for (let i = 0; i < this.nbinsx + 1; ++i) + xx[i] = histo.fXaxis.GetBinLowEdge(i + 1); if (scaledCandle) { for (let j = 0; j < this.nbinsy; ++j) { let sum = 0; for (let i = 0; i < this.nbinsx; ++i) - sum += histo.getBinContent(i+1, j+1); + sum += histo.getBinContent(i + 1, j + 1); maxIntegral = Math.max(maxIntegral, sum); } } for (let j = handle.j1; j < handle.j2; ++j) { for (let i = 0; i < this.nbinsx; ++i) - proj[i] = histo.getBinContent(i+1, j+1); + proj[i] = histo.getBinContent(i + 1, j + 1); - produceCandlePoint(j, handle.gry[j+1], handle.gry[j], handle.i1, handle.i2); + produceCandlePoint(j, handle.gry[j + 1], handle.gry[j], handle.i1, handle.i2); } } else { - xx = new Array(this.nbinsy+1); + xx = new Array(this.nbinsy + 1); proj = new Array(this.nbinsy); - for (let j = 0; j < this.nbinsy+1; ++j) - xx[j] = histo.fYaxis.GetBinLowEdge(j+1); + for (let j = 0; j < this.nbinsy + 1; ++j) + xx[j] = histo.fYaxis.GetBinLowEdge(j + 1); if (scaledCandle) { for (let i = 0; i < this.nbinsx; ++i) { let sum = 0; for (let j = 0; j < this.nbinsy; ++j) - sum += histo.getBinContent(i+1, j+1); + sum += histo.getBinContent(i + 1, j + 1); maxIntegral = Math.max(maxIntegral, sum); } } @@ -2352,58 +2752,51 @@ class TH2Painter extends THistPainter { // loop over visible x-bins for (let i = handle.i1; i < handle.i2; ++i) { for (let j = 0; j < this.nbinsy; ++j) - proj[j] = histo.getBinContent(i+1, j+1); + proj[j] = histo.getBinContent(i + 1, j + 1); - produceCandlePoint(i, handle.grx[i], handle.grx[i+1], handle.j1, handle.j2); + produceCandlePoint(i, handle.grx[i], handle.grx[i + 1], handle.j1, handle.j2); } } if (hlines && (histo.fFillColor > 0)) { - this.draw_g.append('svg:path') - .attr('d', hlines) + this.appendPath(hlines) .style('stroke', this.getColor(histo.fFillColor)); } - const hline_color = (isOption(kHistoZeroIndicator) && (histo.fFillStyle !== 0)) ? this.fillatt.color : this.lineatt.color; + const hline_color = (isOption(kHistoZeroIndicator) && histo.fFillStyle) ? this.fillatt.color : this.lineatt.color; if (hists && (!this.fillatt.empty() || (hline_color !== 'none'))) { - this.draw_g.append('svg:path') - .attr('d', hists) + this.appendPath(hists) .style('stroke', (hline_color !== 'none') ? hline_color : null) .style('pointer-events', this.isBatchMode() ? null : 'visibleFill') .call(this.fillatt.func); } if (bars) { - this.draw_g.append('svg:path') - .attr('d', bars) + this.appendPath(bars) .call(this.lineatt.func) .call(this.fillatt.func); } if (lines) { - this.draw_g.append('svg:path') - .attr('d', lines) + this.appendPath(lines) .call(this.lineatt.func) .style('fill', 'none'); } if (dashed_lines) { const dashed = this.createAttLine({ attr: histo, style: 2, std: false, color: kBlack }); - this.draw_g.append('svg:path') - .attr('d', dashed_lines) + this.appendPath(dashed_lines) .call(dashed.func) .style('fill', 'none'); } if (cmarkers) { - this.draw_g.append('svg:path') - .attr('d', cmarkers) + this.appendPath(cmarkers) .call(attrcmarkers.func); } if (markers) { - this.draw_g.append('svg:path') - .attr('d', markers) + this.appendPath(markers) .call(this.markeratt.func); } @@ -2412,17 +2805,18 @@ class TH2Painter extends THistPainter { /** @summary Draw TH2 bins as scatter plot */ drawBinsScatter() { - const histo = this.getObject(), + const histo = this.getHisto(), + o = this.getOptions(), handle = this.prepareDraw({ rounding: true, pixel_density: true }), - test_cutg = this.options.cutg, + test_cutg = o.cutg, colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [], - scale = this.options.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), + scale = o.ScatCoef * ((this.gmaxbin) > 2000 ? 2000 / this.gmaxbin : 1), rnd = new TRandom(handle.sumz); let colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1.0; handle.ScatterPlot = true; - if (scale*handle.sumz < 1e5) { + if (scale * handle.sumz < 1e5) { // one can use direct drawing of scatter plot without any patterns this.createAttMarker({ attr: histo }); @@ -2431,125 +2825,127 @@ class TH2Painter extends THistPainter { let path = ''; for (i = handle.i1; i < handle.i2; ++i) { - cw = handle.grx[i+1] - handle.grx[i]; + cw = handle.grx[i + 1] - handle.grx[i]; for (j = handle.j1; j < handle.j2; ++j) { - ch = handle.gry[j] - handle.gry[j+1]; + ch = handle.gry[j] - handle.gry[j + 1]; binz = histo.getBinContent(i + 1, j + 1); - const npix = Math.round(scale*binz); - if (npix <= 0) continue; + const npix = Math.round(scale * binz); + if (npix <= 0) + continue; if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + histo.fYaxis.GetBinCoord(j + 0.5))) + continue; for (let k = 0; k < npix; ++k) { path += this.markeratt.create( - Math.round(handle.grx[i] + cw * rnd.random()), - Math.round(handle.gry[j+1] + ch * rnd.random())); + Math.round(handle.grx[i] + cw * rnd.random()), + Math.round(handle.gry[j + 1] + ch * rnd.random())); } } } - this.draw_g - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + this.appendPath(path) + .call(this.markeratt.func); return handle; } // limit filling factor, do not try to produce as many points as filled area; - if (this.maxbin > 0.7) factor = 0.7/this.maxbin; + if (this.maxbin > 0.7) + factor = 0.7 / this.maxbin; const nlevels = Math.round(handle.max - handle.min), - cntr = this.createContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin); + cntr = this.createContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin); // now start build for (i = handle.i1; i < handle.i2; ++i) { for (j = handle.j1; j < handle.j2; ++j) { binz = histo.getBinContent(i + 1, j + 1); - if ((binz <= 0) || (binz < this.minbin)) continue; + if ((binz <= 0) || (binz < this.minbin)) + continue; - cw = handle.grx[i+1] - handle.grx[i]; - ch = handle.gry[j] - handle.gry[j+1]; - if (cw*ch <= 0) continue; + cw = handle.grx[i + 1] - handle.grx[i]; + ch = handle.gry[j] - handle.gry[j + 1]; + if (cw * ch <= 0) + continue; - colindx = cntr.getContourIndex(binz/cw/ch); - if (colindx < 0) continue; + colindx = cntr.getContourIndex(binz / cw / ch); + if (colindx < 0) + continue; - if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), - histo.fYaxis.GetBinCoord(j + 0.5))) continue; + if (test_cutg && !test_cutg.IsInside(histo.fXaxis.GetBinCoord(i + 0.5), histo.fYaxis.GetBinCoord(j + 0.5))) + continue; - cmd1 = `M${handle.grx[i]},${handle.gry[j+1]}`; + cmd1 = `M${handle.grx[i]},${handle.gry[j + 1]}`; if (colPaths[colindx] === undefined) { colPaths[colindx] = cmd1; cell_w[colindx] = cw; cell_h[colindx] = ch; } else { - cmd2 = `m${handle.grx[i]-currx[colindx]},${handle.gry[j+1]-curry[colindx]}`; + cmd2 = `m${handle.grx[i] - currx[colindx]},${handle.gry[j + 1] - curry[colindx]}`; colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1; cell_w[colindx] = Math.max(cell_w[colindx], cw); cell_h[colindx] = Math.max(cell_h[colindx], ch); } currx[colindx] = handle.grx[i]; - curry[colindx] = handle.gry[j+1]; + curry[colindx] = handle.gry[j + 1]; colPaths[colindx] += `v${ch}h${cw}v${-ch}z`; } } - const layer = this.getFrameSvg().selectChild('.main_layer'); + const pp = this.getPadPainter(), + layer = pp.selectChild('.main_layer'); let defs = layer.selectChild('defs'); - if (defs.empty() && (colPaths.length > 0)) + if (defs.empty() && colPaths.length) defs = layer.insert('svg:defs', ':first-child'); this.createAttMarker({ attr: histo }); for (colindx = 0; colindx < colPaths.length; ++colindx) { if ((colPaths[colindx] !== undefined) && (colindx < cntr.arr.length)) { - const pattern_id = (this.pad_name || 'canv') + `_scatter_${colindx}`; - let pattern = defs.selectChild(`#${pattern_id}`); - if (pattern.empty()) { - pattern = defs.append('svg:pattern') - .attr('id', pattern_id) - .attr('patternUnits', 'userSpaceOnUse'); - } else - pattern.selectAll('*').remove(); - - let npix = Math.round(factor*cntr.arr[colindx]*cell_w[colindx]*cell_h[colindx]); - if (npix < 1) npix = 1; - - const arrx = new Float32Array(npix), arry = new Float32Array(npix); - - if (npix === 1) - arrx[0] = arry[0] = 0.5; - else { - for (let n = 0; n < npix; ++n) { - arrx[n] = rnd.random(); - arry[n] = rnd.random(); - } - } + const pattern_id = (pp.getPadName() || 'canv') + `_scatter_${colindx}`; + let pattern = defs.selectChild(`#${pattern_id}`); + if (pattern.empty()) { + pattern = defs.append('svg:pattern') + .attr('id', pattern_id) + .attr('patternUnits', 'userSpaceOnUse'); + } else + pattern.selectAll('*').remove(); + + let npix = Math.round(factor * cntr.arr[colindx] * cell_w[colindx] * cell_h[colindx]); + npix = Math.max(1, npix); - this.markeratt.resetPos(); + const arrx = new Float32Array(npix), arry = new Float32Array(npix); - let path = ''; + if (npix === 1) + arrx[0] = arry[0] = 0.5; + else { + for (let n = 0; n < npix; ++n) { + arrx[n] = rnd.random(); + arry[n] = rnd.random(); + } + } + + this.markeratt.resetPos(); - for (let n = 0; n < npix; ++n) - path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]); + let path = ''; + for (let n = 0; n < npix; ++n) + path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]); - pattern.attr('width', cell_w[colindx]) - .attr('height', cell_h[colindx]) - .append('svg:path') - .attr('d', path) - .call(this.markeratt.func); + pattern.attr('width', cell_w[colindx]) + .attr('height', cell_h[colindx]) + .append('svg:path') + .attr('d', path) + .call(this.markeratt.func); - this.draw_g - .append('svg:path') - .attr('scatter-index', colindx) - .style('fill', `url(#${pattern_id})`) - .attr('d', colPaths[colindx]); - } + this.appendPath(colPaths[colindx]) + .attr('scatter-index', colindx) + .style('fill', `url(#${pattern_id})`); + } } return handle; @@ -2557,40 +2953,50 @@ class TH2Painter extends THistPainter { /** @summary Draw TH2 bins in 2D mode */ draw2DBins() { - if (this._hide_frame && this.isMainPainter()) { - this.getFrameSvg().style('display', null); - delete this._hide_frame; - } + const o = this.getOptions(); + + if (this.#hide_frame && this.isMainPainter()) { + this.getPadPainter().getFrameSvg().style('display', null); + this.#hide_frame = undefined; + } else if (o.Same && !this.isUseFrame()) + this.getPadPainter().getFrameSvg().style('display', 'none'); - if (!this.draw_content) + if (!this.draw_content) { + if (o.Zscale && o.ohmin && o.ohmax) { + this.getContour(true); + this.getHistPalette(); + } return this.removeG(); + } this.createHistDrawAttributes(); - this.createG(true); + this.createG(this.isUseFrame()); let handle, pr; if (this.isTH2Poly()) pr = this.drawPolyBins(); - else { - if (this.options.Scat) + else { + if (o.Scat) handle = this.drawBinsScatter(); - if (this.options.Color) + if (o.System === kPOLAR) + handle = this.drawBinsPolar(); + else if (o.Arrow) + handle = this.drawBinsArrow(); + else if (o.Color) handle = this.drawBinsColor(); - else if (this.options.Box) + else if (o.Box) handle = this.drawBinsBox(); - else if (this.options.Arrow) - handle = this.drawBinsArrow(); - else if (this.options.Proj) + else if (o.Proj) handle = this.drawBinsProjected(); - else if (this.options.Contour) + else if (o.Contour) handle = this.drawBinsContour(); - else if (this.options.Candle || this.options.Violin) + else if (o.Candle || o.Violin) handle = this.drawBinsCandle(); - if (this.options.Text) + if (o.Text) pr = this.drawBinsText(handle); if (!handle && !pr) @@ -2604,106 +3010,155 @@ class TH2Painter extends THistPainter { } /** @summary Draw TH2 in circular mode */ - drawBinsCircular() { - this.getFrameSvg().style('display', 'none'); - this._hide_frame = true; + async drawBinsCircular() { + this.#hide_frame = true; - const rect = this.getPadPainter().getFrameRect(), + const pp = this.getPadPainter(), + rect = pp.getFrameRect(), hist = this.getHisto(), - palette = this.options.Circular > 10 ? this.getHistPalette() : null, + circ = this.getOptions().Circular, + palette = circ > 10 ? this.getHistPalette() : null, text_size = 20, circle_size = 16, axis = hist.fXaxis, + g = this.createG(), getBinLabel = indx => { if (axis.fLabels) { for (let i = 0; i < axis.fLabels.arr.length; ++i) { const tstr = axis.fLabels.arr[i]; - if (tstr.fUniqueID === indx+1) return tstr.fString; + if (tstr.fUniqueID === indx + 1) + return tstr.fString; } } return indx.toString(); }; - this.createG(); + pp.getFrameSvg().style('display', 'none'); - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); + this.assignChordCircInteractive(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); const nbins = Math.min(this.nbinsx, this.nbinsy); - this.startTextDrawing(42, text_size, this.draw_g); - - const pnts = []; + return this.startTextDrawingAsync(42, text_size, g).then(() => { + const pnts = []; - for (let n = 0; n < nbins; n++) { - const a = (0.5 - n/nbins)*Math.PI*2, - cx = Math.round((0.9*rect.width/2 - 2*circle_size) * Math.cos(a)), - cy = Math.round((0.9*rect.height/2 - 2*circle_size) * Math.sin(a)), - x = Math.round(0.9*rect.width/2 * Math.cos(a)), - y = Math.round(0.9*rect.height/2 * Math.sin(a)), - color = palette?.calcColor(n, nbins) ?? 'black'; - let rotate = Math.round(a/Math.PI*180), align = 12; + for (let n = 0; n < nbins; n++) { + const a = (0.5 - n / nbins) * Math.PI * 2, + cx = Math.round((0.9 * rect.width / 2 - 2 * circle_size) * Math.cos(a)), + cy = Math.round((0.9 * rect.height / 2 - 2 * circle_size) * Math.sin(a)), + x = Math.round(0.9 * rect.width / 2 * Math.cos(a)), + y = Math.round(0.9 * rect.height / 2 * Math.sin(a)), + color = palette?.calcColor(n, nbins) ?? 'black'; + let rotate = Math.round(a / Math.PI * 180), align = 12; - pnts.push({ x: cx, y: cy, a, color }); // remember points coordinates + pnts.push({ x: cx, y: cy, a, color }); // remember points coordinates - if ((rotate < -90) || (rotate > 90)) { rotate += 180; align = 32; } + if ((rotate < -90) || (rotate > 90)) { + rotate += 180; + align = 32; + } - const s2 = Math.round(text_size/2), s1 = 2*s2; + const s2 = Math.round(text_size / 2), s1 = 2 * s2; - this.draw_g.append('path') - .attr('d', `M${cx-s2},${cy} a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,${-s1},0z`) - .style('stroke', color) - .style('fill', 'none'); + g.append('path') + .attr('d', `M${cx - s2},${cy} a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,${-s1},0z`) + .style('stroke', color) + .style('fill', 'none'); - this.drawText({ align, rotate, x, y, text: getBinLabel(n) }); - } + this.drawText({ align, rotate, x, y, text: getBinLabel(n) }); + } - const max_width = circle_size/2; - let max_value = 0, min_value = 0; - if (this.options.Circular > 11) { - for (let i = 0; i < nbins - 1; ++i) { - for (let j = i+1; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); - if (cont > 0) { - max_value = Math.max(max_value, cont); - if (!min_value || (cont < min_value)) min_value = cont; - } + const max_width = circle_size / 2; + let max_value = 0, min_value = 0; + if (circ > 11) { + for (let i = 0; i < nbins - 1; ++i) { + for (let j = i + 1; j < nbins; ++j) { + const cont = hist.getBinContent(i + 1, j + 1); + if (cont > 0) { + max_value = Math.max(max_value, cont); + if (!min_value || (cont < min_value)) + min_value = cont; + } + } } } - } - for (let i = 0; i < nbins-1; ++i) { - const pi = pnts[i]; - let path = ''; + for (let i = 0; i < nbins - 1; ++i) { + const pi = pnts[i]; + let path = ''; - for (let j = i+1; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); - if (cont <= 0) continue; + for (let j = i + 1; j < nbins; ++j) { + const cont = hist.getBinContent(i + 1, j + 1); + if (cont <= 0) + continue; - const pj = pnts[j], - a = (pi.a + pj.a)/2, - qr = 0.5*(1-Math.abs(pi.a - pj.a)/Math.PI), // how far Q point will be away from center - qx = Math.round(qr*rect.width/2 * Math.cos(a)), - qy = Math.round(qr*rect.height/2 * Math.sin(a)); + const pj = pnts[j], + a = (pi.a + pj.a) / 2, + qr = 0.5 * (1 - Math.abs(pi.a - pj.a) / Math.PI), // how far Q point will be away from center + qx = Math.round(qr * rect.width / 2 * Math.cos(a)), + qy = Math.round(qr * rect.height / 2 * Math.sin(a)); - path += `M${pi.x},${pi.y}Q${qx},${qy},${pj.x},${pj.y}`; + path += `M${pi.x},${pi.y}Q${qx},${qy},${pj.x},${pj.y}`; - if ((this.options.Circular > 11) && (max_value > min_value)) { - const width = Math.round((cont - min_value) / (max_value - min_value) * (max_width - 1) + 1); - this.draw_g.append('path').attr('d', path).style('stroke', pi.color).style('stroke-width', width).style('fill', 'none'); - path = ''; + if ((circ > 11) && (max_value > min_value)) { + const width = Math.round((cont - min_value) / (max_value - min_value) * (max_width - 1) + 1); + g.append('path').attr('d', path).style('stroke', pi.color).style('stroke-width', width).style('fill', 'none'); + path = ''; + } } + if (path) + g.append('path').attr('d', path).style('stroke', pi.color).style('fill', 'none'); } - if (path) - this.draw_g.append('path').attr('d', path).style('stroke', pi.color).style('fill', 'none'); + + return this.finishTextDrawing(); + }).then(() => { + if (!this.isBatchMode()) { + g.insert('path', ':first-child') + .attr('d', `M${-rect.width / 2},${-rect.height / 2}h${rect.width}v${rect.height}h${-rect.width}z`) + .style('opacity', 0) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } + + return this; + }); + } + + /** @summary Prepare translation and assign interactive handler */ + assignChordCircInteractive(midx, midy) { + if (!this.#chord) + this.#chord = { x: 0, y: 0, zoom: 1 }; + + makeTranslate(this.getG(), midx + this.#chord.x, midy + this.#chord.y, this.#chord.zoom); + + if (this.isBatchMode()) + return; + + if (settings.Zooming && settings.ZoomWheel) { + this.getG().on('wheel', evnt => { + const pos = d3_pointer(evnt, this.getG().node()), + delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail), + prev_zoom = this.#chord.zoom; + + this.#chord.zoom *= (delta > 0) ? 0.8 : 1.2; + this.#chord.x += pos[0] * (prev_zoom - this.#chord.zoom); + this.#chord.y += pos[1] * (prev_zoom - this.#chord.zoom); + + makeTranslate(this.getG(), midx + this.#chord.x, midy + this.#chord.y, this.#chord.zoom); + }).on('dblclick', () => { + this.#chord.x = this.#chord.y = 0; + this.#chord.zoom = 1; + makeTranslate(this.getG(), midx, midy); + }); } - return this.finishTextDrawing(); + assignContextMenu(this); } /** @summary Draw histogram bins as chord diagram */ async drawBinsChord() { - this.getFrameSvg().style('display', 'none'); - this._hide_frame = true; + this.getPadPainter().getFrameSvg().style('display', 'none'); + this.#hide_frame = true; const used = [], nbins = Math.min(this.nbinsx, this.nbinsy), @@ -2712,28 +3167,32 @@ class TH2Painter extends THistPainter { for (let i = 0; i < nbins; ++i) { let sum = 0; for (let j = 0; j < nbins; ++j) { - const cont = hist.getBinContent(i+1, j+1); + const cont = hist.getBinContent(i + 1, j + 1); if (cont > 0) { sum += cont; - if (isint && (Math.round(cont) !== cont)) isint = false; + if (isint && (Math.round(cont) !== cont)) + isint = false; } } - if (sum > 0) used.push(i); + if (sum > 0) + used.push(i); fullsum += sum; } // do not show less than 2 elements - if (used.length < 2) return true; + if (used.length < 2) + return true; let ndig = 0, tickStep = 1; const rect = this.getPadPainter().getFrameRect(), + midx = Math.round(rect.x + rect.width / 2), + midy = Math.round(rect.y + rect.height / 2), palette = this.getHistPalette(), outerRadius = Math.max(10, Math.min(rect.width, rect.height) * 0.5 - 60), innerRadius = Math.max(2, outerRadius - 10), data = [], labels = [], - getColor = indx => palette.calcColor(indx, used.length), formatValue = v => v.toString(), - formatTicks = v => ndig > 3 ? v.toExponential(0) : v.toFixed(ndig), + formatTicks = v => { return (ndig > 3) ? v.toExponential(0) : v.toFixed(ndig); }, d3_descending = (a, b) => { return b < a ? -1 : b > a ? 1 : b >= a ? 0 : Number.NaN; }; if (!isint && fullsum < 10) { @@ -2753,50 +3212,49 @@ class TH2Painter extends THistPainter { for (let i = 0; i < used.length; ++i) { data[i] = []; for (let j = 0; j < used.length; ++j) - data[i].push(hist.getBinContent(used[i]+1, used[j]+1)); + data[i].push(hist.getBinContent(used[i] + 1, used[j] + 1)); const axis = hist.fXaxis; let lbl = 'indx_' + used[i].toString(); if (axis.fLabels) { for (let k = 0; k < axis.fLabels.arr.length; ++k) { const tstr = axis.fLabels.arr[k]; - if (tstr.fUniqueID === used[i]+1) { lbl = tstr.fString; break; } + if (tstr.fUniqueID === used[i] + 1) { + lbl = tstr.fString; + break; + } } } labels.push(lbl); } - this.createG(); + const g = this.createG(); - makeTranslate(this.draw_g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2)); + this.assignChordCircInteractive(midx, midy); const chord = d3_chord() - .padAngle(10 / innerRadius) - .sortSubgroups(d3_descending) - .sortChords(d3_descending), - - chords = chord(data), - - group = this.draw_g.append('g') - .attr('font-size', 10) - .attr('font-family', 'sans-serif') - .selectAll('g') - .data(chords.groups) - .join('g'), - - arc = d3_arc().innerRadius(innerRadius).outerRadius(outerRadius), - - ribbon = d3_ribbon().radius(innerRadius - 1).padAngle(1 / innerRadius); + .padAngle(10 / innerRadius) + .sortSubgroups(d3_descending) + .sortChords(d3_descending), + chords = chord(data), + group = g.append('g') + .attr('font-size', 10) + .attr('font-family', 'sans-serif') + .selectAll('g') + .data(chords.groups) + .join('g'), + arc = d3_arc().innerRadius(innerRadius).outerRadius(outerRadius), + ribbon = d3_ribbon().radius(innerRadius - 1).padAngle(1 / innerRadius); function ticks({ startAngle, endAngle, value }) { const k = (endAngle - startAngle) / value, - arr = []; + arr = []; for (let z = 0; z <= value; z += tickStep) arr.push({ value: z, angle: z * k + startAngle }); return arr; } group.append('path') - .attr('fill', d => getColor(d.index)) + .attr('fill', d => palette.calcColor(d.index, used.length)) .attr('d', arc); group.append('title').text(d => `${labels[d.index]} ${formatValue(d.value)}`); @@ -2805,7 +3263,7 @@ class TH2Painter extends THistPainter { .selectAll('g') .data(ticks) .join('g') - .attr('transform', d => `rotate(${Math.round(d.angle*180/Math.PI-90)}) translate(${outerRadius})`); + .attr('transform', d => `rotate(${Math.round(d.angle * 180 / Math.PI - 90)}) translate(${outerRadius})`); groupTick.append('line') .attr('stroke', 'currentColor') .attr('x2', 6); @@ -2813,8 +3271,8 @@ class TH2Painter extends THistPainter { groupTick.append('text') .attr('x', 8) .attr('dy', '0.35em') - .attr('transform', d => d.angle > Math.PI ? 'rotate(180) translate(-16)' : null) - .attr('text-anchor', d => d.angle > Math.PI ? 'end' : null) + .attr('transform', d => { return (d.angle > Math.PI) ? 'rotate(180) translate(-16)' : null; }) + .attr('text-anchor', d => { return (d.angle > Math.PI) ? 'end' : null; }) .text(d => formatTicks(d.value)); group.select('text') @@ -2823,37 +3281,58 @@ class TH2Painter extends THistPainter { return this.getAttribute('text-anchor') === 'end' ? `↑ ${labels[d.index]}` : `${labels[d.index]} ↓`; }); - this.draw_g.append('g') - .attr('fill-opacity', 0.8) - .selectAll('path') - .data(chords) - .join('path') - .style('mix-blend-mode', 'multiply') - .attr('fill', d => getColor(d.source.index)) - .attr('d', ribbon) - .append('title') - .text(d => `${formatValue(d.source.value)} ${labels[d.target.index]} → ${labels[d.source.index]}${d.source.index === d.target.index ? '' : `\n${formatValue(d.target.value)} ${labels[d.source.index]} → ${labels[d.target.index]}`}`); + g.append('g') + .attr('fill-opacity', 0.8) + .selectAll('path') + .data(chords) + .join('path') + .style('mix-blend-mode', 'multiply') + .attr('fill', d => palette.calcColor(d.source.index, used.length)) + .attr('d', ribbon) + .append('title') + .text(d => `${formatValue(d.source.value)} ${labels[d.target.index]} → ${labels[d.source.index]}${d.source.index === d.target.index ? '' : `\n${formatValue(d.target.value)} ${labels[d.source.index]} → ${labels[d.target.index]}`}`); + + if (!this.isBatchMode()) { + g.insert('ellipse', ':first-child') + .attr('cx', 0) + .attr('cy', 0) + .attr('rx', outerRadius * 1.2) + .attr('ry', outerRadius * 1.2) + .style('opacity', 0) + .style('fill', 'none') + .style('pointer-events', 'visibleFill'); + } return true; } /** @summary Provide text information (tooltips) for histogram bin */ getBinTooltips(i, j) { - const histo = this.getHisto(); - let binz = histo.getBinContent(i+1, j+1); + const histo = this.getHisto(), + profile2d = this.matchObjectType(clTProfile2D) && isFunc(histo.getBinEntries), + bincontent = histo.getBinContent(i + 1, j + 1); + let binz = bincontent; if (histo.$baseh) - binz -= histo.$baseh.getBinContent(i+1, j+1); + binz -= histo.$baseh.getBinContent(i + 1, j + 1); const lines = [this.getObjectHint(), - 'x = ' + this.getAxisBinTip('x', histo.fXaxis, i), - 'y = ' + this.getAxisBinTip('y', histo.fYaxis, j), - `bin = ${histo.getBin(i+1, j+1)} x: ${i+1} y: ${j+1}`, - 'entries = ' + ((binz === Math.round(binz)) ? binz : floatToString(binz, gStyle.fStatFormat))]; + 'x = ' + this.getAxisBinTip('x', histo.fXaxis, i), + 'y = ' + this.getAxisBinTip('y', histo.fYaxis, j), + `bin = ${histo.getBin(i + 1, j + 1)} x: ${i + 1} y: ${j + 1}`, + 'content = ' + ((binz === Math.round(binz)) ? binz : floatToString(binz, gStyle.fStatFormat))]; + + if ((this.getOptions().TextKind === 'E') || profile2d || histo.fSumw2?.length) { + const errs = this.getBinErrors(histo, histo.getBin(i + 1, j + 1), bincontent); + if (errs.poisson) + lines.push('error low = ' + floatToString(errs.low, gStyle.fPaintTextFormat), 'error up = ' + floatToString(errs.up, gStyle.fPaintTextFormat)); + else + lines.push('error = ' + floatToString(errs.up, gStyle.fPaintTextFormat)); + } - if ((this.options.TextKind === 'E') || this.matchObjectType(clTProfile2D)) { - const errz = histo.getBinError(histo.getBin(i+1, j+1)); - lines.push('error = ' + ((errz === Math.round(errz)) ? errz.toString() : floatToString(errz, gStyle.fPaintTextFormat))); + if (profile2d) { + const entries = histo.getBinEntries(i + 1, j + 1); + lines.push('entries = ' + ((entries === Math.round(entries)) ? entries : floatToString(entries, gStyle.fStatFormat))); } return lines; @@ -2861,14 +3340,12 @@ class TH2Painter extends THistPainter { /** @summary Provide text information (tooltips) for candle bin */ getCandleTooltips(p) { - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + const funcs = this.getHistGrFuncs(), histo = this.getHisto(); return [this.getObjectHint(), - p.swapXY - ? 'y = ' + funcs.axisAsText('y', histo.fYaxis.GetBinLowEdge(p.bin+1)) - : 'x = ' + funcs.axisAsText('x', histo.fXaxis.GetBinLowEdge(p.bin+1)), + p.swapXY ? 'y = ' + funcs.axisAsText('y', histo.fYaxis.GetBinLowEdge(p.bin + 1)) + : 'x = ' + funcs.axisAsText('x', histo.fXaxis.GetBinLowEdge(p.bin + 1)), 'm-25% = ' + floatToString(p.fBoxDown, gStyle.fStatFormat), 'median = ' + floatToString(p.fMedian, gStyle.fStatFormat), 'm+25% = ' + floatToString(p.fBoxUp, gStyle.fStatFormat)]; @@ -2878,21 +3355,26 @@ class TH2Painter extends THistPainter { getPolyBinTooltips(binindx, realx, realy) { const histo = this.getHisto(), bin = histo.fBins.arr[binindx], - pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + funcs = this.getHistGrFuncs(), lines = []; let binname = bin.fPoly.fName, numpoints = 0; - if (binname === 'Graph') binname = ''; - if (binname.length === 0) binname = bin.fNumber; + if (binname === 'Graph') + binname = ''; + if (!binname) + binname = bin.fNumber; if ((realx === undefined) && (realy === undefined)) { realx = realy = 0; let gr = bin.fPoly, numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } for (let ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; for (let n = 0; n < gr.fNpoints; ++n) { ++numpoints; @@ -2902,8 +3384,8 @@ class TH2Painter extends THistPainter { } if (numpoints > 1) { - realx = realx / numpoints; - realy = realy / numpoints; + realx /= numpoints; + realy /= numpoints; } } @@ -2923,19 +3405,18 @@ class TH2Painter extends THistPainter { /** @summary Process tooltip event */ processTooltipEvent(pnt) { const histo = this.getHisto(), + o = this.getOptions(), h = this.tt_handle; - let ttrect = this.draw_g?.selectChild('.tooltip_bin'); + let ttrect = this.getG()?.selectChild('.tooltip_bin'); - if (!pnt || !this.draw_content || !this.draw_g || !h || this.options.Proj) { + if (!pnt || !this.draw_content || !this.getG() || !h || o.Proj) { ttrect?.remove(); return null; } if (h.poly) { // process tooltips from TH2Poly - - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + const funcs = this.getHistGrFuncs(), realx = funcs.revertAxis('x', pnt.x), realy = funcs.revertAxis('y', pnt.y); let foundindx = -1, bin; @@ -2947,17 +3428,22 @@ class TH2Painter extends THistPainter { bin = histo.fBins.arr[i]; // found potential bins candidate - if ((realx < bin.fXmin) || (realx > bin.fXmax) || - (realy < bin.fYmin) || (realy > bin.fYmax)) continue; + if ((realx < bin.fXmin) || (realx > bin.fXmax) || (realy < bin.fYmin) || (realy > bin.fYmax)) + continue; // ignore empty bins with col0 option - if (!bin.fContent && !this.options.Zero) continue; + if (!bin.fContent && !o.Zero) + continue; let gr = bin.fPoly, numgraphs = 1; - if (gr._typename === clTMultiGraph) { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; } + if (gr._typename === clTMultiGraph) { + numgraphs = bin.fPoly.fGraphs.arr.length; + gr = null; + } for (let ngr = 0; ngr < numgraphs; ++ngr) { - if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; + if (!gr || (ngr > 0)) + gr = bin.fPoly.fGraphs.arr[ngr]; if (gr.IsInside(realx, realy)) { foundindx = i; break; @@ -2971,19 +3457,21 @@ class TH2Painter extends THistPainter { return null; } - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - exact: true, menu: true, - lines: this.getPolyBinTooltips(foundindx, realx, realy) }; + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + exact: true, menu: true, + lines: this.getPolyBinTooltips(foundindx, realx, realy) + }; if (pnt.disabled) { ttrect.remove(); res.changed = true; } else { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') + ttrect = this.appendPath() .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .call(addHighlightStyle); @@ -2999,10 +3487,12 @@ class TH2Painter extends THistPainter { } if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: foundindx, - cont: bin.fContent, - grx: pnt.x, gry: pnt.y }; + res.user_info = { + obj: histo, name: histo.fName, + bin: foundindx, + cont: bin.fContent, + grx: pnt.x, gry: pnt.y + }; } return res; } else if (h.candle) { @@ -3015,7 +3505,8 @@ class TH2Painter extends THistPainter { match = p.swapXY ? ((p.x1 <= pnt.y) && (pnt.y <= p.x2) && (p.yy1 >= pnt.x) && (pnt.x >= p.yy2)) : ((p.x1 <= pnt.x) && (pnt.x <= p.x2) && (p.yy1 <= pnt.y) && (pnt.y <= p.yy2)); - if (match) break; + if (match) + break; } if (!match) { @@ -3023,22 +3514,24 @@ class TH2Painter extends THistPainter { return null; } - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getCandleTooltips(p), exact: true, menu: true }; + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getCandleTooltips(p), exact: true, menu: true + }; if (pnt.disabled) { ttrect.remove(); res.changed = true; } else { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle) - .style('opacity', '0.7'); + ttrect = this.appendPath() + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle) + .style('opacity', '0.7'); } res.changed = ttrect.property('current_bin') !== i; @@ -3050,70 +3543,83 @@ class TH2Painter extends THistPainter { } if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: i+1, cont: p.fMedian, binx: i+1, biny: 1, - grx: pnt.x, gry: pnt.y }; + res.user_info = { + obj: histo, name: histo.fName, + bin: i + 1, cont: p.fMedian, binx: i + 1, biny: 1, + grx: pnt.x, gry: pnt.y + }; } return res; } - const pmain = this.getFramePainter(); - let i, j, binz = 0, colindx = null, + const fp = this.getFramePainter(); + let i, j, binz = 0, colindx = null, is_pol = false, i1, i2, j1, j2, x1, x2, y1, y2; - // search bins position - if (pmain.reverse_x) { - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x <= h.grx[i]) && (pnt.x >= h.grx[i+1])) break; + if (isFunc(h.findBin)) { + const bin = h.findBin(pnt.x, pnt.y); + i = bin?.i ?? h.i2; + j = bin?.j ?? h.j2; + is_pol = true; } else { - for (i = h.i1; i < h.i2; ++i) - if ((pnt.x >= h.grx[i]) && (pnt.x <= h.grx[i+1])) break; - } + // search bins position + if (fp.reverse_x()) + for (i = h.i1; (i < h.i2) && ((pnt.x > h.grx[i]) || (pnt.x < h.grx[i + 1])); ++i); + else + for (i = h.i1; (i < h.i2) && ((pnt.x < h.grx[i]) || (pnt.x > h.grx[i + 1])); ++i); - if (pmain.reverse_y) { - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y <= h.gry[j+1]) && (pnt.y >= h.gry[j])) break; - } else { - for (j = h.j1; j < h.j2; ++j) - if ((pnt.y >= h.gry[j+1]) && (pnt.y <= h.gry[j])) break; + + if (fp.reverse_y()) + for (j = h.j1; (j < h.j2) && ((pnt.y > h.gry[j + 1]) || (pnt.y < h.gry[j])); ++j); + else + for (j = h.j1; (j < h.j2) && ((pnt.y < h.gry[j + 1]) || (pnt.y > h.gry[j])); ++j); } if ((i < h.i2) && (j < h.j2)) { - i1 = i; i2 = i+1; j1 = j; j2 = j+1; - x1 = h.grx[i1]; x2 = h.grx[i2]; - y1 = h.gry[j2]; y2 = h.gry[j1]; + i1 = i; + i2 = i + 1; + j1 = j; + j2 = j + 1; + x1 = h.grx[i1]; + x2 = h.grx[i2]; + y1 = h.gry[j2]; + y2 = h.gry[j1]; let match = true; - if (this.options.Color) { + if (o.Color && !is_pol) { // take into account bar settings const dx = x2 - x1, dy = y2 - y1; - x2 = Math.round(x1 + dx*h.xbar2); - x1 = Math.round(x1 + dx*h.xbar1); - y2 = Math.round(y1 + dy*h.ybar2); - y1 = Math.round(y1 + dy*h.ybar1); - if (pmain.reverse_x) { - if ((pnt.x > x1) || (pnt.x <= x2)) match = false; - } else - if ((pnt.x < x1) || (pnt.x >= x2)) match = false; - - if (pmain.reverse_y) { - if ((pnt.y > y1) || (pnt.y <= y2)) match = false; - } else - if ((pnt.y < y1) || (pnt.y >= y2)) match = false; - } - - binz = histo.getBinContent(i+1, j+1); - if (this.is_projection) + x2 = Math.round(x1 + dx * h.xbar2); + x1 = Math.round(x1 + dx * h.xbar1); + y2 = Math.round(y1 + dy * h.ybar2); + y1 = Math.round(y1 + dy * h.ybar1); + if (fp.reverse_x()) { + if ((pnt.x > x1) || (pnt.x <= x2)) + match = false; + } else if ((pnt.x < x1) || (pnt.x >= x2)) + match = false; + + if (fp.reverse_y()) { + if ((pnt.y > y1) || (pnt.y <= y2)) + match = false; + } else if ((pnt.y < y1) || (pnt.y >= y2)) + match = false; + } + + binz = histo.getBinContent(i + 1, j + 1); + if (this.#projection_kind) colindx = 0; // just to avoid hide - else if (!match) + else if (!match) colindx = null; - else if (h.hide_only_zeros) - colindx = (binz === 0) && !this._show_empty_bins ? null : 0; - else { + else if (h.hide_only_zeros) + colindx = (binz === 0) && !o.ShowEmpty ? null : 0; + else { colindx = this.getContour().getPaletteIndex(this.getHistPalette(), binz); - if ((colindx === null) && (binz === 0) && this._show_empty_bins) colindx = 0; + if ((colindx === null) && (binz === 0) && + (o.ShowEmpty || (histo._typename === clTProfile2D && histo.getBinEntries(i + 1, j + 1)))) + colindx = 0; } } @@ -3122,63 +3628,74 @@ class TH2Painter extends THistPainter { return null; } - const res = { name: histo.fName, title: histo.fTitle, - x: pnt.x, y: pnt.y, - color1: this.lineatt?.color ?? 'green', - color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', - lines: this.getBinTooltips(i, j), exact: true, menu: true }; + const res = { + name: histo.fName, title: histo.fTitle, + x: pnt.x, y: pnt.y, + color1: this.lineatt?.color ?? 'green', + color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', + lines: this.getBinTooltips(i, j), exact: true, menu: true + }; - if (this.options.Color) res.color2 = this.getHistPalette().getColor(colindx); + if (o.Color) + res.color2 = this.getHistPalette().getColor(colindx); - if (pnt.disabled && !this.is_projection) { + if (pnt.disabled && !this.#projection_kind) { ttrect.remove(); res.changed = true; } else { if (ttrect.empty()) { - ttrect = this.draw_g.append('svg:path') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .call(addHighlightStyle); + ttrect = this.appendPath() + .attr('class', 'tooltip_bin') + .style('pointer-events', 'none') + .call(addHighlightStyle); } - let binid = i*10000 + j, path; + let binid = i * 10000 + j, path; - if (this.is_projection) { - const pwx = this.projection_widthX || 1, ddx = (pwx - 1) / 2; - if ((this.is_projection.indexOf('X')) >= 0 && (pwx > 1)) { - if (j2+ddx >= h.j2) { - j2 = Math.min(Math.round(j2+ddx), h.j2); - j1 = Math.max(j2-pwx, h.j1); + if (this.#projection_kind) { + const pwx = this.#projection_widthX || 1, ddx = (pwx - 1) / 2; + if ((this.#projection_kind.indexOf('X')) >= 0 && (pwx > 1)) { + if (j2 + ddx >= h.j2) { + j2 = Math.min(Math.round(j2 + ddx), h.j2); + j1 = Math.max(j2 - pwx, h.j1); } else { - j1 = Math.max(Math.round(j1-ddx), h.j1); - j2 = Math.min(j1+pwx, h.j2); + j1 = Math.max(Math.round(j1 - ddx), h.j1); + j2 = Math.min(j1 + pwx, h.j2); } } - const pwy = this.projection_widthY || 1, ddy = (pwy - 1) / 2; - if ((this.is_projection.indexOf('Y')) >= 0 && (pwy > 1)) { - if (i2+ddy >= h.i2) { - i2 = Math.min(Math.round(i2+ddy), h.i2); - i1 = Math.max(i2-pwy, h.i1); + const pwy = this.#projection_widthY || 1, ddy = (pwy - 1) / 2; + if ((this.#projection_kind.indexOf('Y')) >= 0 && (pwy > 1)) { + if (i2 + ddy >= h.i2) { + i2 = Math.min(Math.round(i2 + ddy), h.i2); + i1 = Math.max(i2 - pwy, h.i1); } else { - i1 = Math.max(Math.round(i1-ddy), h.i1); - i2 = Math.min(i1+pwy, h.i2); + i1 = Math.max(Math.round(i1 - ddy), h.i1); + i2 = Math.min(i1 + pwy, h.i2); } } } - if (this.is_projection === 'X') { - x1 = 0; x2 = pmain.getFrameWidth(); - y1 = h.gry[j2]; y2 = h.gry[j1]; - binid = j1*777 + j2*333; - } else if (this.is_projection === 'Y') { - y1 = 0; y2 = pmain.getFrameHeight(); - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*777 + i2*333; - } else if (this.is_projection === 'XY') { - y1 = h.gry[j2]; y2 = h.gry[j1]; - x1 = h.grx[i1]; x2 = h.grx[i2]; - binid = i1*789 + i2*653 + j1*12345 + j2*654321; - path = `M${x1},0H${x2}V${y1}H${pmain.getFrameWidth()}V${y2}H${x2}V${pmain.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; + if (is_pol) + path = h.getBinPath(i, j); + else if (this.#projection_kind === 'X') { + x1 = 0; + x2 = fp.getFrameWidth(); + y1 = h.gry[j2]; + y2 = h.gry[j1]; + binid = j1 * 777 + j2 * 333; + } else if (this.#projection_kind === 'Y') { + y1 = 0; + y2 = fp.getFrameHeight(); + x1 = h.grx[i1]; + x2 = h.grx[i2]; + binid = i1 * 777 + i2 * 333; + } else if (this.#projection_kind === 'XY') { + y1 = h.gry[j2]; + y2 = h.gry[j1]; + x1 = h.grx[i1]; + x2 = h.grx[i2]; + binid = i1 * 789 + i2 * 653 + j1 * 12345 + j2 * 654321; + path = `M${x1},0H${x2}V${y1}H${fp.getFrameWidth()}V${y2}H${x2}V${fp.getFrameHeight()}H${x1}V${y2}H0V${y1}H${x1}Z`; } res.changed = ttrect.property('current_bin') !== binid; @@ -3189,14 +3706,17 @@ class TH2Painter extends THistPainter { .property('current_bin', binid); } - if (this.is_projection && res.changed) + if (this.#projection_kind && res.changed) this.redrawProjection(i1, i2, j1, j2); } if (res.changed) { - res.user_info = { obj: histo, name: histo.fName, - bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1, - grx: pnt.x, gry: pnt.y }; + res.user_info = { + obj: histo, name: histo.fName, + bin: histo.getBin(i + 1, j + 1), + cont: binz, binx: i + 1, biny: j + 1, + grx: pnt.x, gry: pnt.y + }; } return res; @@ -3204,24 +3724,50 @@ class TH2Painter extends THistPainter { /** @summary Checks if it makes sense to zoom inside specified axis range */ canZoomInside(axis, min, max) { - if ((axis === 'z') || this.options.Proj) + const o = this.getOptions(); + + if (o.Proj) return true; + // z-scale zooming allowed only if special ignore-palette is not provided + if (axis === 'z') { + if (this.mode3d) + return true; + if (o.IgnorePalette) + return false; + + const fp = this.getFramePainter(), + nlevels = Math.max(2 * gStyle.fNumberContours, 100), + pad = this.getPadPainter().getRootPad(true), + logv = pad?.fLogv ?? pad?.fLogz; + + if (!fp || (fp.zmin === fp.zmax)) + return true; + + if (logv && (fp.zmin > 0) && (min > 0)) + return nlevels * Math.log(max / min) > Math.log(fp.zmax / fp.zmin); + + return (fp.zmax - fp.zmin) < (max - min) * nlevels; + } + let obj = this.getHisto(); - if (obj) obj = (axis === 'y') ? obj.fYaxis : obj.fXaxis; + if (obj) + obj = (axis === 'y') ? obj.fYaxis : obj.fXaxis; return !obj || (obj.FindBin(max, 0.5) - obj.FindBin(min, 0) > 1); } - /** @summary Complete paletted drawing */ + /** @summary Complete palette drawing */ completePalette(pp) { - if (!pp) return true; + if (!pp) + return true; + const o = this.getOptions(); pp.$main_painter = this; - this.options.Zvert = pp._palette_vertical; + o.Zvert = pp.isPaletteVertical(); // redraw palette till the end when contours are available - return pp.drawPave(this.options.Cjust ? 'cjust' : ''); + return pp.drawPave(o.Cjust ? 'cjust' : ''); } /** @summary Performs 2D drawing of histogram @@ -3229,25 +3775,27 @@ class TH2Painter extends THistPainter { async draw2D(/* reason */) { this.clear3DScene(); - const need_palette = this.options.Zscale && this.options.canHavePalette(); + const o = this.getOptions(), + need_palette = o.Zscale && o.canHavePalette() && this.isUseFrame(); // draw new palette, resize frame if required - return this.drawColorPalette(need_palette, true).then(async pp => { + return this.drawColorPalette(need_palette, true, this.#can_move_colz).then(async pp => { + this.#can_move_colz = undefined; let pr; - if (this.options.Circular && this.isMainPainter()) + if (o.Circular && this.isMainPainter()) pr = this.drawBinsCircular(); - else if (this.options.Chord && this.isMainPainter()) + else if (o.Chord && this.isMainPainter()) pr = this.drawBinsChord(); - else + else pr = this.drawAxes().then(() => this.draw2DBins()); return pr.then(() => this.completePalette(pp)); }).then(() => this.updateFunctions()) .then(() => this.updateHistTitle()) .then(() => { - this.updateStatWebCanvas(); - return this.addInteractivity(); - }); + this.updateStatWebCanvas(); + return this.addInteractivity(); + }); } /** @summary Should performs 3D drawing of histogram @@ -3261,12 +3809,24 @@ class TH2Painter extends THistPainter { /** @summary Call drawing function depending from 3D mode */ async callDrawFunc(reason) { const main = this.getMainPainter(), - fp = this.getFramePainter(); + fp = this.getFramePainter(), + o = this.getOptions(); + + if ((main !== this) && fp && (fp.mode3d !== o.Mode3D)) + this.copyOptionsFrom(main); - if ((main !== this) && fp && (fp.mode3d !== this.options.Mode3D)) - this.copyOptionsFrom(main); + if (!o.Mode3D) + return this.draw2D(reason); - return this.options.Mode3D ? this.draw3D(reason) : this.draw2D(reason); + return this.draw3D(reason).catch(err => { + const cp = this.getCanvPainter(); + if (isFunc(cp?.showConsoleError)) + cp.showConsoleError(err); + else + console.error('Fail to draw histogram in 3D - back to 2D'); + o.Mode3D = false; + return this.draw2D(reason); + }); } /** @summary Redraw histogram */ @@ -3274,7 +3834,7 @@ class TH2Painter extends THistPainter { return this.callDrawFunc(reason); } - /** @summary draw TH2 object */ + /** @summary draw TH2 object in 2D only */ static async draw(dom, histo, opt) { return THistPainter._drawHist(new TH2Painter(dom, histo), opt); } diff --git a/modules/hist2d/THStackPainter.mjs b/modules/hist2d/THStackPainter.mjs new file mode 100644 index 000000000..dd2943734 --- /dev/null +++ b/modules/hist2d/THStackPainter.mjs @@ -0,0 +1,566 @@ +import { clone, create, createHistogram, setHistogramTitle, BIT, + gStyle, clTH1F, clTH2, clTH2F, clTObjArray, kNoZoom, kNoStats } from '../core.mjs'; +import { DrawOptions } from '../base/BasePainter.mjs'; +import { ObjectPainter, EAxisBits } from '../base/ObjectPainter.mjs'; +import { TH1Painter } from './TH1Painter.mjs'; +import { TH2Painter } from './TH2Painter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; + + +const kIsZoomed = BIT(16); // bit set when zooming on Y axis + +/** + * @summary Painter class for THStack + * + * @private + */ + +class THStackPainter extends ObjectPainter { + + #firstpainter; // first painter on stack + #painters; // array of sub-painters + #stack; // internal stack of histograms + #did_update; // flag used in update + + /** @summary constructor + * @param {object|string} dom - DOM element for drawing or element id + * @param {object} stack - THStack object + * @param {string} [opt] - draw options */ + constructor(dom, stack, opt) { + super(dom, stack, opt); + this.#firstpainter = null; + this.#painters = []; // keep painters to be able update objects + } + + /** @summary Cleanup THStack painter */ + cleanup() { + this.getPadPainter()?.cleanPrimitives(objp => { return (objp === this.#firstpainter) || (this.#painters.indexOf(objp) >= 0); }); + this.#firstpainter = null; + this.#painters = []; + this.#stack = undefined; + super.cleanup(); + } + + /** @summary Build sum of all histograms + * @desc Build a separate #stack containing the running sum of all histograms */ + buildStack(stack, pp) { + this.#stack = null; + + if (!stack.fHists) + return false; + const nhists = stack.fHists.arr.length; + if (nhists <= 0) + return false; + + let arr = pp?.findInPrimitives(undefined, clTObjArray); + if ((arr?.arr.length === nhists) && (arr?.name === stack.fName)) { + this.#stack = arr; + return true; + } + + arr = create(clTObjArray); + let hprev = clone(stack.fHists.arr[0]); + arr.arr.push(hprev); + for (let i = 1; i < nhists; ++i) { + const hnext = clone(stack.fHists.arr[i]), + xnext = hnext.fXaxis, xprev = hprev.fXaxis; + + let match = (xnext.fNbins === xprev.fNbins) && + (xnext.fXmin === xprev.fXmin) && + (xnext.fXmax === xprev.fXmax); + + if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && + (Math.abs((xnext.fXmax - xnext.fXmin) / xnext.fNbins - (xprev.fXmax - xprev.fXmin) / xprev.fNbins) < 0.0001)) { + // simple extension of histogram to make sum + const arr2 = new Array(hprev.fNcells).fill(0); + for (let n = 1; n <= xnext.fNbins; ++n) + arr2[n] = hnext.fArray[n]; + hnext.fNcells = hprev.fNcells; + Object.assign(xnext, xprev); + hnext.fArray = arr2; + match = true; + } + if (!match) { + console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); + return false; + } + + // trivial sum of histograms + for (let n = 0; n < hnext.fArray.length; ++n) + hnext.fArray[n] += hprev.fArray[n]; + + arr.arr.push(hnext); + hprev = hnext; + } + this.#stack = arr; + return true; + } + + /** @summary Returns stack min/max values */ + getMinMax(iserr) { + const stack = this.getObject(), + o = this.getOptions(), + pad = this.getPadPainter()?.getRootPad(true), + logscale = pad?.fLogv ?? (o.ndim === 1 ? pad?.fLogy : pad?.fLogz); + let themin = 0, themax = 0; + + const getHistMinMax = (hist, witherr) => { + const res = { min: 0, max: 0 }; + let domin = true, domax = true; + if (hist.fMinimum !== kNoZoom) { + res.min = hist.fMinimum; + domin = false; + } + if (hist.fMaximum !== kNoZoom) { + res.max = hist.fMaximum; + domax = false; + } + + if (!domin && !domax) + return res; + + let i1 = 1, i2 = hist.fXaxis.fNbins, j1 = 1, j2 = 1, first = true; + + if (hist.fXaxis.TestBit(EAxisBits.kAxisRange)) { + i1 = hist.fXaxis.fFirst; + i2 = hist.fXaxis.fLast; + } + + if (hist._typename.indexOf(clTH2) === 0) { + j2 = hist.fYaxis.fNbins; + if (hist.fYaxis.TestBit(EAxisBits.kAxisRange)) { + j1 = hist.fYaxis.fFirst; + j2 = hist.fYaxis.fLast; + } + } + let err = 0; + for (let j = j1; j <= j2; ++j) { + for (let i = i1; i <= i2; ++i) { + const val = hist.getBinContent(i, j); + if (witherr) + err = hist.getBinError(hist.getBin(i, j)); + if (logscale && (val - err <= 0)) + continue; + if (domin && (first || (val - err < res.min))) + res.min = val - err; + if (domax && (first || (val + err > res.max))) + res.max = val + err; + first = false; + } + } + + return res; + }; + + if (o.nostack) { + for (let i = 0; i < stack.fHists.arr.length; ++i) { + const resh = getHistMinMax(stack.fHists.arr[i], iserr); + if (i === 0) { + themin = resh.min; + themax = resh.max; + } else { + themin = Math.min(themin, resh.min); + themax = Math.max(themax, resh.max); + } + } + } else { + themin = getHistMinMax(this.#stack.arr.at(0), iserr).min; + themax = getHistMinMax(this.#stack.arr.at(-1), iserr).max; + } + + if (logscale) + themin = (themin > 0) ? themin * 0.9 : themax * 1e-3; + else if (themin > 0) + themin = 0; + + if (stack.fMaximum !== kNoZoom) + themax = stack.fMaximum; + + if (stack.fMinimum !== kNoZoom) + themin = stack.fMinimum; + + // redo code from THStack::BuildAndPaint + + if (!o.nostack || (stack.fMaximum === kNoZoom)) { + if (logscale) { + if (themin > 0) + themax *= (1 + 0.2 * Math.log10(themax / themin)); + } else if (stack.fMaximum === kNoZoom) + themax *= (1 + gStyle.fHistTopMargin); + } + if (!o.nostack || (stack.fMinimum === kNoZoom)) { + if (logscale) + themin = (themin > 0) ? themin / (1 + 0.5 * Math.log10(themax / themin)) : 1e-3 * themax; + } + + const res = { min: themin, max: themax, hopt: `;hmin:${themin};hmax:${themax}` }; + if (stack.fHistogram?.TestBit(kIsZoomed)) + res.hopt += ';zoom_min_max'; + + return res; + } + + /** @summary Provide draw options for the histogram */ + getHistDrawOption(hist, opt) { + const o = this.getOptions(); + let hopt = opt || hist.fOption || o.hopt; + if (hopt.toUpperCase().indexOf(o.hopt) < 0) + hopt += ' ' + o.hopt; + if (o.draw_errors && !hopt) + hopt = 'E'; + if (o.zscale) { + const p = hopt.toUpperCase().indexOf('COLZ'); + if (p >= 0) + hopt = hopt.slice(0, p + 3) + hopt.slice(p + 4); + } + if (!o.pads) + hopt += ' same nostat' + o.auto; + return hopt; + } + + /** @summary Draw next stack histogram */ + async drawNextHisto(indx, pad_painter) { + const stack = this.getObject(), + o = this.getOptions(), + hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length || 0; + + if (indx >= nhists) + return this; + + const rindx = o.horder ? indx : nhists - indx - 1, + subid = o.nostack ? `hists_${rindx}` : `stack_${rindx}`, + hist = hlst.arr[rindx], + hopt = this.getHistDrawOption(hist, stack.fHists.opt[rindx]); + + // handling of 'pads' draw option + if (pad_painter) { + const subpad_painter = pad_painter.getSubPadPainter(indx + 1); + if (!subpad_painter) + return this; + + subpad_painter.cleanPrimitives(true); + + return this.drawHist(subpad_painter, hist, hopt).then(subp => { + if (subp) { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + } + return this.drawNextHisto(indx + 1, pad_painter); + }); + } + + // special handling of stacked histograms + // also used to provide tooltips + if ((rindx > 0) && !o.nostack) + hist.$baseh = hlst.arr[rindx - 1]; + // this number used for auto colors creation + if (o.auto) + hist.$num_histos = nhists; + + const dom = this.#firstpainter?.getPadPainter() || this.getDrawDom(); + + return this.drawHist(dom, hist, hopt).then(subp => { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + return this.drawNextHisto(indx + 1, pad_painter); + }); + } + + /** @summary Decode draw options of THStack painter */ + decodeOptions(opt) { + const o = this.setOptions({ ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }), + stack = this.getObject(), + hist = stack.fHistogram || stack.fHists?.arr[0] || this.#stack?.arr[0]; + + if (hist?._typename.indexOf(clTH2) === 0) + o.ndim = 2; + + if ((o.ndim === 2) && !opt) + opt = 'lego1'; + + if (!o.nostack) { + stack.fHists?.arr.forEach(h => { + const len = h.fSumw2?.length ?? 0; + for (let n = 0; n < len; ++n) { + if (h.fSumw2[n] > 0) { + o.has_errors = true; + break; + } + } + }); + } + + o.nhist = stack.fHists?.arr?.length ?? 1; + + const d = new DrawOptions(opt); + + o.nostack = d.check('NOSTACK'); + if (d.check('STACK')) + o.nostack = false; + o.same = d.check('SAME'); + + d.check('NOCLEAR'); // ignore option + + ['PFC', 'PLC', 'PMC'].forEach(f => { + if (d.check(f)) + o.auto += ' ' + f; + }); + + o.pads = d.check('PADS'); + if (o.pads) + o.nostack = true; + + o.hopt = d.remain().trim(); // use remaining draw options for histogram draw + + const dolego = d.check('LEGO'); + + o.errors = d.check('E'); + + o.zscale = d.check('COLZ'); + + // if any histogram appears with pre-calculated errors, use E for all histograms + if (!o.nostack && o.has_errors && !dolego && !d.check('HIST') && (o.hopt.indexOf('E') < 0)) + o.draw_errors = true; + + o.horder = o.nostack || dolego; + } + + /** @summary Create main histogram for THStack axis drawing */ + createHistogram(stack) { + const o = this.getOptions(), + histos = stack.fHists, + numhistos = histos?.arr.length ?? 0; + + if (!numhistos) { + const histo = createHistogram(clTH1F, 100); + setHistogramTitle(histo, stack.fTitle); + histo.fBits |= kNoStats; + return histo; + } + + const h0 = histos.arr[0], + histo = createHistogram((o.ndim === 1) ? clTH1F : clTH2F, h0.fXaxis.fNbins, h0.fYaxis.fNbins); + + histo.fName = 'axis_hist'; + histo.fBits |= kNoStats; + Object.assign(histo.fXaxis, h0.fXaxis); + if (o.ndim === 2) + Object.assign(histo.fYaxis, h0.fYaxis); + + // this code is not exists in ROOT painter, can be skipped? + for (let n = 1; n < numhistos; ++n) { + const h = histos.arr[n]; + + if (!histo.fXaxis.fLabels) { + histo.fXaxis.fXmin = Math.min(histo.fXaxis.fXmin, h.fXaxis.fXmin); + histo.fXaxis.fXmax = Math.max(histo.fXaxis.fXmax, h.fXaxis.fXmax); + } + + if ((o.ndim === 2) && !histo.fYaxis.fLabels) { + histo.fYaxis.fXmin = Math.min(histo.fYaxis.fXmin, h.fYaxis.fXmin); + histo.fYaxis.fXmax = Math.max(histo.fYaxis.fXmax, h.fYaxis.fXmax); + } + } + + histo.fTitle = stack.fTitle; + + return histo; + } + + /** @summary Update THStack object */ + updateObject(obj) { + if (!this.matchObjectType(obj)) + return false; + + const stack = this.getObject(), + pp = this.getPadPainter(), + o = this.getOptions(); + + stack.fHists = obj.fHists; + stack.fTitle = obj.fTitle; + stack.fMinimum = obj.fMinimum; + stack.fMaximum = obj.fMaximum; + + if (!o.nostack) + o.nostack = !this.buildStack(stack, pp); + + if (this.#firstpainter) { + let src = obj.fHistogram; + if (!src) + src = stack.fHistogram = this.createHistogram(stack); + + const mm = this.getMinMax(o.errors || o.draw_errors); + this.#firstpainter.options.hmin = mm.min; + this.#firstpainter.options.hmax = mm.max; + + this.#firstpainter._checked_zooming = false; // force to check 3d zooming + + if (o.ndim === 1) { + this.#firstpainter.ymin = mm.min; + this.#firstpainter.ymax = mm.max; + } else { + this.#firstpainter.zmin = mm.min; + this.#firstpainter.zmax = mm.max; + } + + this.#firstpainter.updateObject(src); + + this.#firstpainter.options.zoom_min_max = src.TestBit(kIsZoomed); + } + + // and now update histograms + const hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length ?? 0; + + if (nhists !== this.#painters.length) { + this.#did_update = 1; + pp?.cleanPrimitives(objp => this.#painters.indexOf(objp) >= 0); + this.#painters = []; + } else { + this.#did_update = 2; + for (let indx = 0; indx < nhists; ++indx) { + const rindx = o.horder ? indx : nhists - indx - 1, + hist = hlst.arr[rindx]; + this.#painters[indx].updateObject(hist, this.getHistDrawOption(hist, stack.fHists.opt[rindx])); + } + } + + return true; + } + + /** @summary Redraw THStack + * @desc Do something if previous update had changed number of histograms */ + redraw(reason) { + if (!this.#did_update) + return; + + const full_redraw = this.#did_update === 1; + this.#did_update = undefined; + + let pr = Promise.resolve(this); + + const o = this.getOptions(); + + if (this.#firstpainter) { + const mm = this.getMinMax(o.errors || o.draw_errors); + this.#firstpainter.decodeOptions(o.hopt + mm.hopt); + pr = this.#firstpainter.redraw(reason); + } + + return pr.then(() => { + if (full_redraw) + return this.drawNextHisto(0, o.pads ? this.getPadPainter() : null); + + const redrawSub = indx => { + if (indx >= this.#painters.length) + return this; + return this.#painters[indx].redraw(reason).then(() => redrawSub(indx + 1)); + }; + return redrawSub(0); + }); + } + + /** @summary Fill THStack context menu */ + fillContextMenuItems(menu) { + const o = this.getOptions(); + menu.addRedrawMenu(this); + if (!o.pads) { + menu.addchk(o.draw_errors, 'Draw errors', flag => { + o.draw_errors = flag; + const stack = this.getObject(), + hlst = o.nostack ? stack.fHists : this.#stack, + nhists = hlst?.arr?.length ?? 0; + for (let indx = 0; indx < nhists; ++indx) { + const rindx = o.horder ? indx : nhists - indx - 1, + hist = hlst.arr[rindx]; + this.#painters[indx].decodeOptions(this.getHistDrawOption(hist, stack.fHists.opt[rindx])); + } + this.redrawPad(); + }, 'Change draw erros in the stack'); + } + } + + /** @summary Invoke histogram drawing */ + drawHist(dom, hist, hopt) { + const func = (this.getOptions().ndim === 1) ? TH1Painter.draw : TH2Painter.draw; + return func(dom, hist, hopt); + } + + /** @summary Access or modify histogram min/max + * @private */ + accessMM(ismin, v) { + const name = ismin ? 'fMinimum' : 'fMaximum', + stack = this.getObject(); + if (v === undefined) + return stack[name]; + + this.#did_update = 2; + + stack[name] = v; + + this.interactiveRedraw('pad', ismin ? `exec:SetMinimum(${v})` : `exec:SetMaximum(${v})`); + } + + /** @summary Full stack redraw with specified draw option */ + async redrawWith(opt, skip_cleanup) { + const pp = this.getPadPainter(), + o = this.getOptions(); + + if (!skip_cleanup && pp) { + this.#firstpainter = null; + this.#painters = []; + if (o.pads) + pp.divide(0, 0); + pp.removePrimitive(this, true); + } + + this.decodeOptions(opt); + + const stack = this.getObject(); + + let pr = Promise.resolve(this), pad_painter = null; + + if (o.pads) { + pr = ensureTCanvas(this, false).then(() => { + pad_painter = this.getPadPainter(); + return pad_painter.divide(o.nhist, 0, true); + }); + } else { + if (!o.nostack) + o.nostack = !this.buildStack(stack, pp); + + if (!o.same && stack.fHists?.arr.length) { + if (!stack.fHistogram) + stack.fHistogram = this.createHistogram(stack); + + const mm = this.getMinMax(o.errors || o.draw_errors); + + pr = this.drawHist(this.getDrawDom(), stack.fHistogram, o.hopt + mm.hopt).then(subp => { + this.#firstpainter = subp; + subp.$stack_hist = true; + subp.setSecondaryId(this, 'hist'); // mark hist painter as created by THStack + }); + } + } + + return pr.then(() => this.drawNextHisto(0, pad_painter)).then(() => { + if (!o.pads) + this.addToPadPrimitives(); + return this; + }); + } + + /** @summary draw THStack object in 2D only */ + static async draw(dom, stack, opt) { + if (!stack.fHists || !stack.fHists.arr) + return null; // drawing not needed + + const painter = new THStackPainter(dom, stack, opt); + + return painter.redrawWith(opt, true); + } + +} // class THStackPainter + +export { THStackPainter }; diff --git a/modules/hist2d/THistPainter.mjs b/modules/hist2d/THistPainter.mjs index 05a91790f..de6c58781 100644 --- a/modules/hist2d/THistPainter.mjs +++ b/modules/hist2d/THistPainter.mjs @@ -1,18 +1,21 @@ import { gStyle, BIT, settings, constants, create, isObject, isFunc, isStr, getPromise, - clTList, clTPaveText, clTPaveStats, clTPaletteAxis, clTProfile2D, clTProfile3D, clTPad, - clTAxis, clTF1, clTF2, clTProfile, kNoZoom, clTCutG, kNoStats, kTitle } from '../core.mjs'; -import { getColor, getColorPalette } from '../base/colors.mjs'; + clTList, clTPaveStats, clTPaletteAxis, clTProfile, clTProfile2D, clTProfile3D, clTPad, + clTAxis, clTF1, clTF2, kNoZoom, clTCutG, kNoStats, kTitle, setHistogramTitle } from '../core.mjs'; +import { getColorPalette } from '../base/colors.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter, EAxisBits, kAxisTime, kAxisLabels } from '../base/ObjectPainter.mjs'; -import { TPavePainter } from '../hist/TPavePainter.mjs'; +import { TPavePainter, drawObjectTitle } from '../hist/TPavePainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +import { gamma_quantile, gamma_quantile_c } from '../base/math.mjs'; -const kCARTESIAN = 1, kPOLAR = 2, kCYLINDRICAL = 3, kSPHERICAL = 4, kRAPIDITY = 5; - +const kCARTESIAN = 1, kPOLAR = 2, kCYLINDRICAL = 3, kSPHERICAL = 4, kRAPIDITY = 5, + kNormal = 0, kPoisson = 1, kPoisson2 = 2, + kOnlyCheck = 'only-check'; /** * @summary Class to decode histograms draw options - * + * @desc All options started from capital letter are major drawing options + * any other draw options are internal settings. * @private */ @@ -22,38 +25,44 @@ class THistDrawOptions { /** @summary Reset hist draw options */ reset() { - Object.assign(this, - { Axis: 0, RevX: false, RevY: false, SymlogX: 0, SymlogY: 0, - Bar: false, BarStyle: 0, Curve: false, - Hist: 1, Line: false, Fill: false, - Error: 0, ErrorKind: -1, errorX: gStyle.fErrorX, - Mark: false, Same: false, Scat: false, ScatCoef: 1.0, Func: true, AllFunc: false, - Arrow: false, Box: false, BoxStyle: 0, - Text: false, TextAngle: 0, TextKind: '', Char: 0, Color: false, Contour: 0, Cjust: false, - Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0, Ortho: gStyle.fOrthoCamera, - Spec: false, Pie: false, List: false, Zscale: false, Zvert: true, PadPalette: false, - Candle: '', Violin: '', Scaled: null, Circular: 0, - GLBox: 0, GLColor: false, Project: '', System: kCARTESIAN, - AutoColor: false, NoStat: false, ForceStat: false, PadStats: false, PadTitle: false, AutoZoom: false, - HighRes: 0, Zero: 1, Palette: 0, BaseLine: false, - Optimize: settings.OptimizeDraw, adjustFrame: false, - Mode3D: false, x3dscale: 1, y3dscale: 1, - Render3D: constants.Render3D.Default, - FrontBox: true, BackBox: true, - need_fillcol: false, - minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null, IgnoreMainScale: false }); + Object.assign(this, { + Axis: 0, RevX: false, RevY: false, SymlogX: 0, SymlogY: 0, xticks: null, yticks: null, + Bar: false, BarStyle: 0, Curve: false, + Hist: 1, Line: false, Fill: false, + Error: 0, ErrorKind: -1, errorX: gStyle.fErrorX, + Mark: false, Same: false, Scat: false, ScatCoef: 1.0, Func: true, AllFunc: false, + Arrow: false, Box: false, BoxStyle: 0, + Text: false, TextAngle: 0, TextKind: '', Char: 0, Color: false, Contour: 0, Cjust: false, + Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0, Ortho: gStyle.fOrthoCamera, + Spec: false, Pie: false, List: false, Zscale: false, Zvert: true, PadPalette: false, + Candle: '', Violin: '', Scaled: null, Circular: 0, Poisson: kNormal, + GLBox: 0, GLColor: false, Project: '', ProfileProj: '', Profile2DProj: '', System: kCARTESIAN, + AutoColor: false, NoStat: false, ForceStat: false, PadStats: false, PadTitle: false, AutoZoom: false, + HighRes: 0, Zero: 1, Palette: 0, BaseLine: false, ShowEmpty: false, + Optimize: settings.OptimizeDraw, + Mode3D: false, x3dscale: 1, y3dscale: 1, SwapXY: false, + Render3D: constants.Render3D.Default, + FrontBox: true, BackBox: true, + need_fillcol: false, + minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null, + IgnoreMainScale: false, IgnorePalette: false + }); } isCartesian() { return this.System === kCARTESIAN; } is3d() { return this.Lego || this.Surf; } - /** @summary Base on sumw2 values (re)set some bacis draw options, only for 1dim hist */ + /** @summary Base on sumw2 values (re)set some basic draw options, only for 1dim hist */ decodeSumw2(histo, force) { const len = histo.fSumw2?.length ?? 0; let isany = false; - for (let n = 0; n < len; ++n) - if (histo.fSumw2[n] > 0) { isany = true; break; } + for (let n = 0; n < len; ++n) { + if (histo.fSumw2[n] > 0) { + isany = true; + break; + } + } if (Number.isInteger(this.Error) || force) this.Error = isany ? 1 : 0; @@ -67,8 +76,11 @@ class THistDrawOptions { /** @summary Is palette can be used with current draw options */ canHavePalette() { - if (this.ndim !== 2) - return false; + if (this.ndim === 3) + return this.BoxStyle === 12 || this.BoxStyle === 13 || this.GLBox === 12; + + if (this.ndim === 1) + return this.Lego === 12 || this.Lego === 14; if (this.Mode3D) return this.Lego === 12 || this.Lego === 14 || this.Surf === 11 || this.Surf === 12; @@ -86,17 +98,19 @@ class THistDrawOptions { this.cutg_name = ''; if (isStr(opt) && (hdim === 2)) { const p1 = opt.lastIndexOf('['), p2 = opt.lastIndexOf(']'); - if ((p1 >= 0) && (p2 > p1+1)) { - this.cutg_name = opt.slice(p1+1, p2); - opt = opt.slice(0, p1) + opt.slice(p2+1); + if ((p1 >= 0) && (p2 > p1 + 1) && (opt.at(p1 - 1) !== ':')) { + this.cutg_name = opt.slice(p1 + 1, p2); + opt = opt.slice(0, p1) + opt.slice(p2 + 1); this.cutg = pp?.findInPrimitives(this.cutg_name, clTCutG); - if (this.cutg) this.cutg.$redraw_pad = true; + if (this.cutg) + this.cutg.$redraw_pad = true; } } const d = new DrawOptions(opt); - if (hdim === 1) this.decodeSumw2(histo, true); + if (hdim === 1) + this.decodeSumw2(histo, true); this.ndim = hdim || 1; // keep dimensions, used for now in GED @@ -106,9 +120,12 @@ class THistDrawOptions { d.check('USE_PAD_PALETTE'); d.check('USE_PAD_STATS'); + if (d.check('IGNORE_PALETTE')) + this.IgnorePalette = true; + if (d.check('PAL', true)) this.Palette = d.partAsInt(); - // this is zooming of histo content + // this is zooming of histogram content if (d.check('MINIMUM:', true)) { this.ominimum = true; this.minimum = parseFloat(d.part); @@ -123,75 +140,151 @@ class THistDrawOptions { this.omaximum = false; this.maximum = histo.fMaximum; } - if (d.check('HMIN:', true)) { - this.ohmin = true; - this.hmin = parseFloat(d.part); - } else { - this.ohmin = false; - delete this.hmin; - } - if (d.check('HMAX:', true)) { - this.ohmax = true; - this.hmax = parseFloat(d.part); - } else { - this.ohmax = false; - delete this.hmax; - } - - // let configure histogram titles - only for debug purposes - if (d.check('HTITLE:', true)) histo.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('XTITLE:', true)) histo.fXaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('YTITLE:', true)) histo.fYaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); - if (d.check('ZTITLE:', true)) histo.fZaxis.fTitle = decodeURIComponent(d.part.toLowerCase()); - - if (d.check('_ADJUST_FRAME_')) this.adjustFrame = true; - - if (d.check('NOOPTIMIZE')) this.Optimize = 0; - if (d.check('OPTIMIZE')) this.Optimize = 2; + if (!this.ominimum && !this.omaximum && this.minimum === this.maximum) + this.minimum = this.maximum = kNoZoom; - if (d.check('AUTOCOL')) this.AutoColor = true; - if (d.check('AUTOZOOM')) this.AutoZoom = true; + this.ohmin = d.check('HMIN:', true); + this.hmin = this.ohmin ? parseFloat(d.part) : undefined; + this.ohmax = d.check('HMAX:', true); + this.hmax = this.ohmax ? parseFloat(d.part) : undefined; + this.zoom_min_max = d.check('ZOOM_MIN_MAX'); - if (d.check('OPTSTAT', true)) this.optstat = d.partAsInt(); - if (d.check('OPTFIT', true)) this.optfit = d.partAsInt(); - - if ((this.optstat || this.optstat) && histo?.TestBit(kNoStats)) - histo?.InvertBit(kNoStats); + // let configure histogram titles - only for debug purposes + if (d.check('HTITLE:', true)) + histo.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('XTITLE:', true)) + histo.fXaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('YTITLE:', true)) + histo.fYaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('ZTITLE:', true)) + histo.fZaxis.fTitle = decodeURIComponent(d.getPart(true)); + if (d.check('POISSON2')) + this.Poisson = kPoisson2; + if (d.check('POISSON')) + this.Poisson = kPoisson; + + if (d.check('SHOWEMPTY')) + this.ShowEmpty = true; + + if (d.check('NOOPTIMIZE')) + this.Optimize = 0; + if (d.check('OPTIMIZE')) + this.Optimize = 2; + + if (d.check('AUTOCOL')) + this.AutoColor = true; + if (d.check('AUTOZOOM')) + this.AutoZoom = true; + + if (d.check('OPTSTAT', true)) + this.optstat = d.partAsInt(); + if (d.check('OPTFIT', true)) + this.optfit = d.partAsInt(); + + if (d.check('XTICKS:', 'array')) + this.xticks = d.array; + if ((this.ndim > 1) && d.check('YTICKS:', 'array')) + this.yticks = d.array; + + if (this.optstat || this.optfit) + histo?.SetBit(kNoStats, false); + + if (d.check('ALLBINS') && histo) { + histo.fXaxis.fFirst = 0; + histo.fXaxis.fLast = histo.fXaxis.fNbins + 1; + histo.fXaxis.SetBit(EAxisBits.kAxisRange); + if (this.ndim > 1) { + histo.fYaxis.fFirst = 0; + histo.fYaxis.fLast = histo.fYaxis.fNbins + 1; + histo.fYaxis.SetBit(EAxisBits.kAxisRange); + } + if (this.ndim > 2) { + histo.fZaxis.fFirst = 0; + histo.fZaxis.fLast = histo.fZaxis.fNbins + 1; + histo.fZaxis.SetBit(EAxisBits.kAxisRange); + } + } - if (d.check('NOSTAT')) this.NoStat = true; - if (d.check('STAT')) this.ForceStat = true; + if (d.check('NOSTAT')) + this.NoStat = true; + if (d.check('STAT')) + this.ForceStat = true; - if (d.check('NOTOOLTIP') && painter) painter.setTooltipAllowed(false); - if (d.check('TOOLTIP') && painter) painter.setTooltipAllowed(true); + if (d.check('NOTOOLTIP')) + painter?.setTooltipAllowed(false); + if (d.check('TOOLTIP')) + painter?.setTooltipAllowed(true); - if (d.check('SYMLOGX', true)) this.SymlogX = d.partAsInt(0, 3); - if (d.check('SYMLOGY', true)) this.SymlogY = d.partAsInt(0, 3); + if (d.check('SYMLOGX', true)) + this.SymlogX = d.partAsInt(0, 3); + if (d.check('SYMLOGY', true)) + this.SymlogY = d.partAsInt(0, 3); - if (d.check('X3DSC', true)) this.x3dscale = d.partAsInt(0, 100) / 100; - if (d.check('Y3DSC', true)) this.y3dscale = d.partAsInt(0, 100) / 100; + if (d.check('X3DSC', true)) + this.x3dscale = d.partAsInt(0, 100) / 100; + if (d.check('Y3DSC', true)) + this.y3dscale = d.partAsInt(0, 100) / 100; - if (d.check('PERSPECTIVE') || d.check('PERSP')) this.Ortho = false; - if (d.check('ORTHO')) this.Ortho = true; + if (d.check('PERSPECTIVE') || d.check('PERSP')) + this.Ortho = false; + if (d.check('ORTHO')) + this.Ortho = true; let lx = 0, ly = 0, check3dbox = ''; - if (d.check('LOG2XY')) lx = ly = 2; - if (d.check('LOGXY')) lx = ly = 1; - if (d.check('LOG2X')) lx = 2; - if (d.check('LOGX')) lx = 1; - if (d.check('LOG2Y')) ly = 2; - if (d.check('LOGY')) ly = 1; - if (lx && pad) { pad.fLogx = lx; pad.fUxmin = 0; pad.fUxmax = 1; pad.fX1 = 0; pad.fX2 = 1; } - if (ly && pad) { pad.fLogy = ly; pad.fUymin = 0; pad.fUymax = 1; pad.fY1 = 0; pad.fY2 = 1; } - if (d.check('LOG2Z') && pad) pad.fLogz = 2; - if (d.check('LOGZ') && pad) pad.fLogz = 1; - if (d.check('LOGV') && pad) pad.fLogv = 1; // ficitional member, can be introduced in ROOT - if (d.check('GRIDXY') && pad) pad.fGridx = pad.fGridy = 1; - if (d.check('GRIDX') && pad) pad.fGridx = 1; - if (d.check('GRIDY') && pad) pad.fGridy = 1; - if (d.check('TICKXY') && pad) pad.fTickx = pad.fTicky = 1; - if (d.check('TICKX') && pad) pad.fTickx = 1; - if (d.check('TICKY') && pad) pad.fTicky = 1; - if (d.check('TICKZ') && pad) pad.fTickz = 1; + if (d.check('LOG2XY')) + lx = ly = 2; + if (d.check('LOGXY')) + lx = ly = 1; + if (d.check('LOG2X')) + lx = 2; + if (d.check('LOGX')) + lx = 1; + if (d.check('LOG2Y')) + ly = 2; + if (d.check('LOGY')) + ly = 1; + if (lx && pad) { + pad.fLogx = lx; + pad.fUxmin = 0; + pad.fUxmax = 1; + pad.fX1 = 0; + pad.fX2 = 1; + } + if (ly && pad) { + pad.fLogy = ly; + pad.fUymin = 0; + pad.fUymax = 1; + pad.fY1 = 0; + pad.fY2 = 1; + } + if (d.check('LOG2Z') && pad) + pad.fLogz = 2; + if (d.check('LOGZ') && pad) + pad.fLogz = 1; + if (d.check('LOGV') && pad) + pad.fLogv = 1; // fictional member, can be introduced in ROOT + if (d.check('GRIDXY') && pad) + pad.fGridx = pad.fGridy = 1; + if (d.check('GRIDX') && pad) + pad.fGridx = 1; + if (d.check('GRIDY') && pad) + pad.fGridy = 1; + if (d.check('TICKXY2') && pad) + pad.fTickx = pad.fTicky = 2; + if (d.check('TICKX2') && pad) + pad.fTickx = 2; + if (d.check('TICKY2') && pad) + pad.fTicky = 2; + if (d.check('TICKZ2') && pad) + pad.fTickz = 2; + if (d.check('TICKXY') && pad) + pad.fTickx = pad.fTicky = 1; + if (d.check('TICKX') && pad) + pad.fTickx = 1; + if (d.check('TICKY') && pad) + pad.fTicky = 1; + if (d.check('TICKZ') && pad) + pad.fTickz = 1; if (d.check('GRAYSCALE')) pp?.setGrayscale(true); @@ -200,8 +293,14 @@ class THistDrawOptions { this.histoFillPattern = 1001; } + if (d.check('FILLPAT_', true)) + this.histoFillPattern = d.partAsInt(); + if (d.check('LINE_', 'color')) - this.histoLineColor = getColor(d.color); + this.histoLineColor = painter.getColor(d.color); + + if (d.check('WIDTH_', true)) + this.histoLineWidth = d.partAsInt(); if (d.check('XAXIS_', 'color')) histo.fXaxis.fAxisColor = histo.fXaxis.fLabelColor = histo.fXaxis.fTitleColor = d.color; @@ -209,84 +308,126 @@ class THistDrawOptions { if (d.check('YAXIS_', 'color')) histo.fYaxis.fAxisColor = histo.fYaxis.fLabelColor = histo.fYaxis.fTitleColor = d.color; - const has_main = painter ? !!painter.getMainPainter() : false; - - if (d.check('X+')) { this.AxisPos = 10; this.second_x = has_main; } - if (d.check('Y+')) { this.AxisPos += 1; this.second_y = has_main; } + if (d.check('X+')) { + this.AxisPos = 10; + this.second_x = Boolean(painter?.getMainPainter()); + } + if (d.check('Y+')) { + this.AxisPos += 1; + this.second_y = Boolean(painter?.getMainPainter()); + } - if (d.check('SAME0')) { this.Same = true; this.IgnoreMainScale = true; } - if (d.check('SAMES')) { this.Same = true; this.ForceStat = true; } - if (d.check('SAME')) { this.Same = true; this.Func = true; } + if (d.check('SAME0')) + this.Same = this.IgnoreMainScale = true; + if (d.check('SAMES')) + this.Same = this.ForceStat = true; + if (d.check('SAME')) + this.Same = this.Func = true; - if (d.check('SPEC')) this.Spec = true; // not used + if (d.check('SPEC')) + this.Spec = true; // not used if (d.check('BASE0') || d.check('MIN0')) this.BaseLine = 0; else if (gStyle.fHistMinimumZero) this.BaseLine = 0; - if (d.check('PIE')) this.Pie = true; // not used + if (d.check('PIE')) + this.Pie = true; // not used - if (d.check('CANDLE', true)) this.Candle = d.part || '1'; - if (d.check('VIOLIN', true)) { this.Violin = d.part || '1'; delete this.Candle; } - if (d.check('NOSCALED')) this.Scaled = false; - if (d.check('SCALED')) this.Scaled = true; + if (d.check('CANDLE', true)) + this.Candle = d.part || '1'; + if (d.check('VIOLIN', true)) { + this.Violin = d.part || '1'; + delete this.Candle; + } + if (d.check('NOSCALED')) + this.Scaled = false; + if (d.check('SCALED')) + this.Scaled = true; - if (d.check('GLBOX', true)) this.GLBox = 10 + d.partAsInt(); - if (d.check('GLCOL')) this.GLColor = true; + if (d.check('GLBOX', true)) + this.GLBox = 10 + d.partAsInt(); + if (d.check('GLCOL')) + this.GLColor = true; d.check('GL'); // suppress GL if (d.check('CIRCULAR', true) || d.check('CIRC', true)) { this.Circular = 11; - if (d.part.indexOf('0') >= 0) this.Circular = 10; // black and white - if (d.part.indexOf('1') >= 0) this.Circular = 11; // color - if (d.part.indexOf('2') >= 0) this.Circular = 12; // color and width + if (d.part.indexOf('0') >= 0) + this.Circular = 10; // black and white + if (d.part.indexOf('1') >= 0) + this.Circular = 11; // color + if (d.part.indexOf('2') >= 0) + this.Circular = 12; // color and width } this.Chord = d.check('CHORD'); if (d.check('LEGO', true)) { this.Lego = 1; - if (d.part.indexOf('0') >= 0) this.Zero = false; - if (d.part.indexOf('1') >= 0) this.Lego = 11; - if (d.part.indexOf('2') >= 0) this.Lego = 12; - if (d.part.indexOf('3') >= 0) this.Lego = 13; - if (d.part.indexOf('4') >= 0) this.Lego = 14; + if (d.part.indexOf('0') >= 0) + this.Zero = false; + if (d.part.indexOf('1') >= 0) + this.Lego = 11; + if (d.part.indexOf('2') >= 0) + this.Lego = 12; + if (d.part.indexOf('3') >= 0) + this.Lego = 13; + if (d.part.indexOf('4') >= 0) + this.Lego = 14; check3dbox = d.part; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; } if (d.check('R3D_', true)) this.Render3D = constants.Render3D.fromString(d.part.toLowerCase()); - if (d.check('POL')) this.System = kPOLAR; - if (d.check('CYL')) this.System = kCYLINDRICAL; - if (d.check('SPH')) this.System = kSPHERICAL; - if (d.check('PSR')) this.System = kRAPIDITY; + if (d.check('POL')) + this.System = kPOLAR; + if (d.check('CYL')) + this.System = kCYLINDRICAL; + if (d.check('SPH')) + this.System = kSPHERICAL; + if (d.check('PSR')) + this.System = kRAPIDITY; if (d.check('SURF', true)) { this.Surf = d.partAsInt(10, 1); check3dbox = d.part; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; } - if (d.check('TF3', true)) check3dbox = d.part; + if (d.check('TF3', true)) + check3dbox = d.part; - if (d.check('ISO', true)) check3dbox = d.part; + if (d.check('ISO', true)) + check3dbox = d.part; - if (d.check('LIST')) this.List = true; // not used + if (d.check('LIST')) + this.List = true; // not used if (d.check('CONT', true) && (hdim > 1)) { this.Contour = 1; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; - if (d.part.indexOf('1') >= 0) this.Contour = 11; else - if (d.part.indexOf('2') >= 0) this.Contour = 12; else - if (d.part.indexOf('3') >= 0) this.Contour = 13; else - if (d.part.indexOf('4') >= 0) this.Contour = 14; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; + if (d.part.indexOf('1') >= 0) + this.Contour = 11; + else if (d.part.indexOf('2') >= 0) + this.Contour = 12; + else if (d.part.indexOf('3') >= 0) + this.Contour = 13; + else if (d.part.indexOf('4') >= 0) + this.Contour = 14; } // decode bar/hbar option @@ -305,66 +446,128 @@ class THistDrawOptions { if (d.check('BOX', true)) { this.BoxStyle = 10; - if (d.part.indexOf('1') >= 0) this.BoxStyle = 11; else - if (d.part.indexOf('2') >= 0) this.BoxStyle = 12; else - if (d.part.indexOf('3') >= 0) this.BoxStyle = 13; - if (d.part.indexOf('Z') >= 0) this.Zscale = true; - if (d.part.indexOf('H') >= 0) this.Zvert = false; + if (d.part.indexOf('1') >= 0) + this.BoxStyle = 11; + else if (d.part.indexOf('2') >= 0) + this.BoxStyle = 12; + else if (d.part.indexOf('3') >= 0) + this.BoxStyle = 13; + if (d.part.indexOf('Z') >= 0) + this.Zscale = true; + if (d.part.indexOf('H') >= 0) + this.Zvert = false; } this.Box = this.BoxStyle > 0; - if (d.check('CJUST')) this.Cjust = true; - if (d.check('COL')) this.Color = true; - if (d.check('CHAR')) this.Char = 1; - if (d.check('ALLFUNC')) this.AllFunc = true; - if (d.check('FUNC')) { this.Func = true; this.Hist = false; } - if (d.check('AXIS')) this.Axis = 1; - if (d.check('AXIG')) this.Axis = 2; + if (d.check('CJUST')) + this.Cjust = true; + if (d.check('COL7')) + this.Color = 7; // special color mode with use of bar offset + if (d.check('COL')) + this.Color = true; + if (d.check('CHAR')) + this.Char = 1; + if (d.check('ALLFUNC')) + this.AllFunc = true; + if (d.check('FUNC')) { + this.Func = true; + this.Hist = false; + } + if (d.check('HAXISG')) { + this.Axis = 3; + this.SwapXY = 1; + } + if (d.check('HAXIS')) { + this.Axis = 1; + this.SwapXY = 1; + } + if (d.check('HAXIG')) { + this.Axis = 2; + this.SwapXY = 1; + } + if (d.check('AXISG')) + this.Axis = 3; + if (d.check('AXIS')) + this.Axis = 1; + if (d.check('AXIG')) + this.Axis = 2; if (d.check('TEXT', true)) { this.Text = true; this.Hist = false; this.TextAngle = Math.min(d.partAsInt(), 90); - if (d.part.indexOf('N') >= 0) this.TextKind = 'N'; - if (d.part.indexOf('E0') >= 0) this.TextLine = true; - if (d.part.indexOf('E') >= 0) this.TextKind = 'E'; + if (d.part.indexOf('N') >= 0) + this.TextKind = 'N'; + if (d.part.indexOf('E0') >= 0) + this.TextLine = true; + if (d.part.indexOf('E') >= 0) + this.TextKind = 'E'; } if (d.check('SCAT=', true)) { this.Scat = true; this.ScatCoef = parseFloat(d.part); - if (!Number.isFinite(this.ScatCoef) || (this.ScatCoef <= 0)) this.ScatCoef = 1.0; + if (!Number.isFinite(this.ScatCoef) || (this.ScatCoef <= 0)) + this.ScatCoef = 1.0; } - if (d.check('SCAT')) this.Scat = true; + if (d.check('SCAT')) + this.Scat = true; if (d.check('TRI', true)) { this.Color = false; this.Tri = 1; check3dbox = d.part; - if (d.part.indexOf('ERR') >= 0) this.Error = true; + if (d.part.indexOf('ERR') >= 0) + this.Error = true; + } + + if (d.check('AITOFF')) + this.Proj = 1; + if (d.check('MERCATOR')) + this.Proj = 2; + if (d.check('SINUSOIDAL')) + this.Proj = 3; + if (d.check('PARABOLIC')) + this.Proj = 4; + if (d.check('MOLLWEIDE')) + this.Proj = 5; + if (this.Proj > 0) + this.Contour = 14; + + if (d.check('PROJXY', true)) { + let flag = true; + if ((histo?._typename === clTProfile2D) && d.part && !Number.isInteger(Number.parseInt(d.part))) { + this.Profile2DProj = d.part; + flag = d.check('PROJXY', true); // allow projxy with projected profile2d + } + if (flag) + this.Project = 'XY' + d.partAsInt(0, 1); } - if (d.check('AITOFF')) this.Proj = 1; - if (d.check('MERCATOR')) this.Proj = 2; - if (d.check('SINUSOIDAL')) this.Proj = 3; - if (d.check('PARABOLIC')) this.Proj = 4; - if (d.check('MOLLWEIDE')) this.Proj = 5; - if (this.Proj > 0) this.Contour = 14; - - if (d.check('PROJXY', true)) this.Project = 'XY' + d.partAsInt(0, 1); - if (d.check('PROJX', true)) this.Project = 'X' + d.part; - if (d.check('PROJY', true)) this.Project = 'Y' + d.part; - if (d.check('PROJ')) this.Project = 'Y1'; + if (d.check('PROJX', true)) { + if (histo?._typename === clTProfile) + this.ProfileProj = d.part || 'B'; + else + this.Project = 'X' + d.part; + } + if (d.check('PROJY', true)) + this.Project = 'Y' + d.part; + if (d.check('PROJ')) + this.Project = 'Y1'; if (check3dbox) { - if (check3dbox.indexOf('FB') >= 0) this.FrontBox = false; - if (check3dbox.indexOf('BB') >= 0) this.BackBox = false; + if (check3dbox.indexOf('FB') >= 0) + this.FrontBox = false; + if (check3dbox.indexOf('BB') >= 0) + this.BackBox = false; } - if ((hdim === 3) && d.check('FB')) this.FrontBox = false; - if ((hdim === 3) && d.check('BB')) this.BackBox = false; + if ((hdim === 3) && d.check('FB')) + this.FrontBox = false; + if ((hdim === 3) && d.check('BB')) + this.BackBox = false; if (d.check('PFC') && !this._pfc) this._pfc = 2; @@ -373,16 +576,17 @@ class THistDrawOptions { if (d.check('PMC') && !this._pmc) this._pmc = 2; - const check_axis_bit = (opt, axis, bit) => { + const check_axis_bit = (aopt, axis, bit) => { // ignore Z scale options for 2D plots if ((axis === 'fZaxis') && (hdim < 3) && !this.Lego && !this.Surf) return; - let flag = d.check(opt); - if (pad && pad['$'+opt]) { flag = true; pad['$'+opt] = undefined; } - if (flag && histo) { - if (!histo[axis].TestBit(bit)) - histo[axis].InvertBit(bit); + let flag = d.check(aopt); + if (pad && pad['$' + aopt]) { + flag = true; + pad['$' + aopt] = undefined; } + if (flag && histo) + histo[axis].SetBit(bit, true); }; check_axis_bit('OTX', 'fXaxis', EAxisBits.kOppositeTitle); @@ -394,21 +598,35 @@ class THistDrawOptions { check_axis_bit('MLX', 'fXaxis', EAxisBits.kMoreLogLabels); check_axis_bit('MLY', 'fYaxis', EAxisBits.kMoreLogLabels); check_axis_bit('MLZ', 'fZaxis', EAxisBits.kMoreLogLabels); + check_axis_bit('NOEX', 'fXaxis', EAxisBits.kNoExponent); + check_axis_bit('NOEY', 'fYaxis', EAxisBits.kNoExponent); + check_axis_bit('NOEZ', 'fZaxis', EAxisBits.kNoExponent); - if (d.check('RX') || pad?.$RX) this.RevX = true; - if (d.check('RY') || pad?.$RY) this.RevY = true; + if (d.check('RX') || pad?.$RX) + this.RevX = true; + if (d.check('RY') || pad?.$RY) + this.RevY = true; - if (d.check('L')) { this.Line = true; this.Hist = false; } - if (d.check('F')) { this.Fill = true; this.need_fillcol = true; } + if (d.check('L')) { + this.Line = true; + this.Hist = false; + } + if (d.check('F')) { + this.Fill = true; + this.need_fillcol = true; + } - if (d.check('A')) this.Axis = -1; + if (d.check('A')) + this.Axis = -1; if (pad?.$ratio_pad === 'up') { - if (!this.Same) this.Axis = 0; // draw both axes + if (!this.Same) + this.Axis = 0; // draw both axes histo.fXaxis.fLabelSize = 0; histo.fXaxis.fTitle = ''; histo.fYaxis.$use_top_pad = true; } else if (pad?.$ratio_pad === 'low') { - if (!this.Same) this.Axis = 0; // draw both axes + if (!this.Same) + this.Axis = 0; // draw both axes histo.fXaxis.$use_top_pad = true; histo.fYaxis.$use_top_pad = true; histo.fXaxis.fTitle = 'x'; @@ -419,42 +637,86 @@ class THistDrawOptions { } } - if (d.check('B1')) { this.BarStyle = 1; this.BaseLine = 0; this.Hist = false; this.need_fillcol = true; } - if (d.check('B')) { this.BarStyle = 1; this.Hist = false; this.need_fillcol = true; } - if (d.check('C')) { this.Curve = true; this.Hist = false; } - if (d.check('][')) { this.Off = 1; this.Hist = true; } + if (d.check('B1')) { + this.BarStyle = 1; + this.BaseLine = 0; + this.Hist = false; + this.need_fillcol = true; + } + if (d.check('B')) { + this.BarStyle = 1; + this.Hist = false; + this.need_fillcol = true; + } + if (d.check('C')) { + this.Curve = true; + this.Hist = false; + } + if (d.check('][')) { + this.Off = 1; + this.Hist = true; + } - if (d.check('HIST')) { this.Hist = true; this.Func = true; this.Error = false; } + if (d.check('HIST')) { + this.Hist = true; + this.Func = true; + this.Error = false; + } this.Bar = (this.BarStyle > 0); delete this.MarkStyle; // remove mark style if any - if (d.check('P0')) { this.Mark = true; this.Hist = false; this.Zero = true; } - if (d.check('P')) { this.Mark = true; this.Hist = false; this.Zero = false; } - if (d.check('HZ')) { this.Zscale = true; this.Zvert = false; } - if (d.check('Z')) this.Zscale = true; - if (d.check('*')) { this.Mark = true; this.MarkStyle = 3; this.Hist = false; } - if (d.check('H')) this.Hist = true; + if (d.check('P0')) { + this.Mark = true; + this.Hist = false; + this.Zero = true; + } + if (d.check('P')) { + this.Mark = true; + this.Hist = false; + this.Zero = false; + } + if (d.check('HZ')) { + this.Zscale = true; + this.Zvert = false; + } + if (d.check('Z')) + this.Zscale = true; + if (d.check('*')) { + this.Mark = true; + this.MarkStyle = 3; + this.Hist = false; + } + if (d.check('H')) + this.Hist = true; if (d.check('E', true)) { this.Error = true; if (hdim === 1) { this.Zero = false; // do not draw empty bins with errors - if (this.Hist === 1) this.Hist = false; - if (Number.isInteger(parseInt(d.part[0]))) this.ErrorKind = parseInt(d.part[0]); - if ((this.ErrorKind === 3) || (this.ErrorKind === 4)) this.need_fillcol = true; - if (this.ErrorKind === 0) this.Zero = true; // enable drawing of empty bins - if (d.part.indexOf('X0') >= 0) this.errorX = 0; + if (this.Hist === 1) + this.Hist = false; + if (Number.isInteger(parseInt(d.part[0]))) + this.ErrorKind = parseInt(d.part[0]); + if ((this.ErrorKind === 3) || (this.ErrorKind === 4)) + this.need_fillcol = true; + if (this.ErrorKind === 0) + this.Zero = true; // enable drawing of empty bins + if (d.part.indexOf('X0') >= 0) + this.errorX = 0; } } - if (d.check('9')) this.HighRes = 1; - if (d.check('0')) this.Zero = false; - if (this.Color && d.check('1')) this.Zero = false; + if (d.check('9')) + this.HighRes = 1; + if (d.check('0')) + this.Zero = false; + if (this.Color && d.check('1')) + this.Zero = false; // flag identifies 3D drawing mode for histogram - if ((this.Lego > 0) || (hdim === 3) || - (((this.Surf > 0) || this.Error) && (hdim === 2))) this.Mode3D = true; + if ((this.Lego > 0) || (hdim === 3) || (((this.Surf > 0) || this.Error) && (hdim === 2))) + this.Mode3D = true; // default draw options for TF1 is line and fill if (painter?.isTF1() && (hdim === 1) && (this.Hist === 1) && !this.Line && !this.Fill && !this.Curve && !this.Mark) { @@ -468,6 +730,9 @@ class THistDrawOptions { this.Surf = 13; } + /** @summary Is X/Y swap is configured */ + swap_xy() { return this.BarStyle >= 20 || this.SwapXY; } + /** @summary Tries to reconstruct string with hist draw options */ asString(is_main_hist, pad) { let res = '', zopt = ''; @@ -476,52 +741,72 @@ class THistDrawOptions { if (this.Mode3D) { if (this.Lego) { res = 'LEGO'; - if (!this.Zero) res += '0'; - if (this.Lego > 10) res += (this.Lego-10); + if (!this.Zero) + res += '0'; + if (this.Lego > 10) + res += (this.Lego - 10); res += zopt; } else if (this.Surf) { - res = 'SURF' + (this.Surf-10); + res = 'SURF' + (this.Surf - 10); res += zopt; } - if (!this.FrontBox) res += 'FB'; - if (!this.BackBox) res += 'BB'; - - if (this.x3dscale !== 1) res += `_X3DSC${Math.round(this.x3dscale * 100)}`; - if (this.y3dscale !== 1) res += `_Y3DSC${Math.round(this.y3dscale * 100)}`; + if (!this.FrontBox) + res += 'FB'; + if (!this.BackBox) + res += 'BB'; + + if (this.x3dscale !== 1) + res += `_X3DSC${Math.round(this.x3dscale * 100)}`; + if (this.y3dscale !== 1) + res += `_Y3DSC${Math.round(this.y3dscale * 100)}`; } else { if (this.Candle) res = 'CANDLE' + this.Candle; - else if (this.Violin) + else if (this.Violin) res = 'VIOLIN' + this.Violin; - else if (this.Scat) + else if (this.Scat) res = 'SCAT'; - else if (this.Color) { + else if (this.Color) { res = 'COL'; - if (!this.Zero) res += '0'; + if (!this.Zero) + res += '0'; res += zopt; - if (this.Axis < 0) res += 'A'; + if (this.Axis < 0) + res += 'A'; } else if (this.Contour) { res = 'CONT'; - if (this.Contour > 10) res += (this.Contour-10); + if (this.Contour > 10) + res += (this.Contour - 10); res += zopt; } else if (this.Bar) res = (this.BaseLine === false) ? 'B' : 'B1'; - else if (this.Mark) + else if (this.Mark) res = this.Zero ? 'P0' : 'P'; // here invert logic with 0 - else if (this.Error) { - res = 'E'; - if (this.ErrorKind >= 0) res += this.ErrorKind; - } else if (this.Line) { + else if (this.Line) { res += 'L'; - if (this.Fill) res += 'F'; + if (this.Fill) + res += 'F'; } else if (this.Off) res = ']['; - if (this.Cjust) res += ' CJUST'; + if (this.Error) { + res += 'E'; + if (this.ErrorKind >= 0) + res += this.ErrorKind; + if (this.errorX === 0) + res += 'X0'; + } + + if (this.Cjust) + res += ' CJUST'; + + if (this.Hist === true) + res += 'HIST'; if (this.Text) { res += 'TEXT'; - if (this.TextAngle) res += this.TextAngle; + if (this.TextAngle) + res += this.TextAngle; res += this.TextKind; } } @@ -532,6 +817,18 @@ class THistDrawOptions { if (this.is3d() && this.Ortho && is_main_hist) res += '_ORTHO'; + if (this.ProfileProj) + res += '_PROJX' + this.ProfileProj; + + if (this.Profile2DProj) + res += '_PROJXY' + this.Profile2DProj; + + if (this.Proj) + res += '_PROJ' + this.Proj; + + if (this.ShowEmpty) + res += '_SHOWEMPTY'; + if (this.Same) res += this.ForceStat ? 'SAMES' : 'SAME'; else if (is_main_hist && res) { @@ -554,11 +851,22 @@ class THistDrawOptions { res += '_LOG2Z'; else if (pad.fLogz) res += '_LOGZ'; - if (pad.fGridx) res += '_GRIDX'; - if (pad.fGridy) res += '_GRIDY'; - if (pad.fTickx) res += '_TICKX'; - if (pad.fTicky) res += '_TICKY'; - if (pad.fTickz) res += '_TICKZ'; + if (pad.fGridx) + res += '_GRIDX'; + if (pad.fGridy) + res += '_GRIDY'; + if (pad.fTickx === 2) + res += '_TICKX2'; + else if (pad.fTickx) + res += '_TICKX'; + if (pad.fTicky === 2) + res += '_TICKY2'; + else if (pad.fTicky) + res += '_TICKY'; + if (pad.fTickz === 2) + res += '_TICKZ2'; + else if (pad.fTickz) + res += '_TICKZ'; } if (this.cutg_name) @@ -567,6 +875,9 @@ class THistDrawOptions { return res; } + /** @return true if hmin and hmax values where specified */ + exact_values_range() { return this.ohmin && this.ohmax; } + } // class THistDrawOptions @@ -596,29 +907,29 @@ class HistContour { this.colzmax = 1.0; if (this.colzmin <= 0) { if ((zminpositive === undefined) || (zminpositive <= 0)) - this.colzmin = 0.0001*this.colzmax; + this.colzmin = 0.0001 * this.colzmax; else - this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3*zminpositive : 1; + this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3 * zminpositive : 1; } if (this.colzmin >= this.colzmax) - this.colzmin = 0.0001*this.colzmax; + this.colzmin = 0.0001 * this.colzmax; - const logmin = Math.log(this.colzmin)/Math.log(10), - logmax = Math.log(this.colzmax)/Math.log(10), - dz = (logmax-logmin)/nlevels; + const logmin = Math.log(this.colzmin) / Math.log(10), + logmax = Math.log(this.colzmax) / Math.log(10), + dz = (logmax - logmin) / nlevels; this.arr.push(this.colzmin); for (let level = 1; level < nlevels; level++) - this.arr.push(Math.exp((logmin + dz*level)*Math.log(10))); + this.arr.push(Math.exp((logmin + dz * level) * Math.log(10))); this.arr.push(this.colzmax); this.custom = true; } else { - if ((this.colzmin === this.colzmax) && (this.colzmin !== 0)) { - this.colzmax += 0.01*Math.abs(this.colzmax); - this.colzmin -= 0.01*Math.abs(this.colzmin); + if ((this.colzmin === this.colzmax) && this.colzmin) { + this.colzmax += 0.01 * Math.abs(this.colzmax); + this.colzmin -= 0.01 * Math.abs(this.colzmin); } - const dz = (this.colzmax-this.colzmin)/nlevels; + const dz = (this.colzmax - this.colzmin) / nlevels; for (let level = 0; level <= nlevels; level++) - this.arr.push(this.colzmin + dz*level); + this.arr.push(this.colzmin + dz * level); } } @@ -628,11 +939,11 @@ class HistContour { for (let n = 0; n < levels.length; ++n) this.arr.push(levels[n]); - if (this.colzmax > this.arr[this.arr.length-1]) + if (this.colzmax > this.arr.at(-1)) this.arr.push(this.colzmax); } - /** @summary Configure indicies */ + /** @summary Configure indices */ configIndicies(below_min, exact_min) { this.below_min_indx = below_min; this.exact_min_indx = exact_min; @@ -641,20 +952,27 @@ class HistContour { /** @summary Get index based on z value */ getContourIndex(zc) { // bins less than zmin not drawn - if (zc < this.colzmin) return this.below_min_indx; + if (zc < this.colzmin) + return this.below_min_indx; // if bin content exactly zmin, draw it when col0 specified or when content is positive - if (zc === this.colzmin) return this.exact_min_indx; + if (zc === this.colzmin) + return this.exact_min_indx; if (!this.custom) - return Math.floor(0.01+(zc-this.colzmin)*(this.arr.length-1)/(this.colzmax-this.colzmin)); - - let l = 0, r = this.arr.length-1; - if (zc < this.arr[0]) return -1; - if (zc >= this.arr[r]) return r; - while (l < r-1) { - const mid = Math.round((l+r)/2); - if (this.arr[mid] > zc) r = mid; else l = mid; + return Math.floor(0.01 + (zc - this.colzmin) * (this.arr.length - 1) / (this.colzmax - this.colzmin)); + + let l = 0, r = this.arr.length - 1; + if (zc < this.arr[0]) + return -1; + if (zc >= this.arr[r]) + return r; + while (l < r - 1) { + const mid = Math.round((l + r) / 2); + if (this.arr[mid] > zc) + r = mid; + else + l = mid; } return l; } @@ -662,7 +980,8 @@ class HistContour { /** @summary Get palette color */ getPaletteColor(palette, zc) { const zindx = this.getContourIndex(zc); - if (zindx < 0) return null; + if (zindx < 0) + return null; const pindx = palette.calcColorIndex(zindx, this.arr.length); return palette.getColor(pindx); } @@ -676,35 +995,43 @@ class HistContour { } // class HistContour /** - * @summary Handle for updateing of secondary functions + * @summary Handle for updating of secondary functions * * @private */ class FunctionsHandler { - constructor(painter, pp, funcs, statpainter) { - this.painter = painter; - this.pp = pp; + #extra_painters; + #newfuncs; // array of functions + #newopts; // array of options + #painter; // object painter to which functions belongs + #pad_painter; // pad painter + + constructor(painter, pp, funcs, statpainter, update_statpainter) { + this.#painter = painter; + this.#pad_painter = pp; const painters = [], update_painters = [], only_draw = (statpainter === true); - this.newfuncs = []; - this.newopts = []; + this.#newfuncs = []; + this.#newopts = []; // find painters associated with histogram/graph/... if (!only_draw) { pp?.forEachPainterInPad(objp => { - if (objp.isSecondary(painter) && objp._secondary_id?.match(/^func_|^indx_/)) + if (objp.isSecondary(painter) && objp.getSecondaryId()?.match(/^func_|^indx_/)) painters.push(objp); }, 'objects'); } for (let n = 0; n < funcs?.arr.length; ++n) { const func = funcs.arr[n], fopt = funcs.opt[n]; - if (!func?._typename) continue; - if (isFunc(painter.needDrawFunc) && !painter.needDrawFunc(painter.getObject(), func)) continue; + if (!func?._typename) + continue; + if (isFunc(painter.needDrawFunc) && !painter.needDrawFunc(painter.getObject(), func)) + continue; let funcpainter = null, func_indx = -1; @@ -727,62 +1054,63 @@ class FunctionsHandler { if (func_indx >= 0) { painters.splice(func_indx, 1); update_painters.push(funcpainter); - } + } } else { // use arrays index while index is important - this.newfuncs[n] = func; - this.newopts[n] = fopt; + this.#newfuncs[n] = func; + this.#newopts[n] = fopt; } } // stat painter has to be kept even when no object exists in the list if (isObject(statpainter)) { const indx = painters.indexOf(statpainter); - if (indx >= 0) painters.splice(indx, 1); + if (indx >= 0) + painters.splice(indx, 1); + if (update_statpainter && (update_painters.indexOf(statpainter) < 0)) + update_painters.push(statpainter); } // remove all function which are not found in new list of functions - if (painters.length > 0) + if (painters.length) pp?.cleanPrimitives(p => painters.indexOf(p) >= 0); - if (update_painters.length > 0) - this._extraPainters = update_painters; + if (update_painters.length) + this.#extra_painters = update_painters; } /** @summary Draw/update functions selected before */ drawNext(indx) { - if (this._extraPainters) { - const p = this._extraPainters.shift(); - if (this._extraPainters.length === 0) - delete this._extraPainters; + if (this.#extra_painters) { + const p = this.#extra_painters.shift(); + if (!this.#extra_painters.length) + this.#extra_painters = undefined; return getPromise(p.redraw()).then(() => this.drawNext(0)); } - if (!this.newfuncs || (indx >= this.newfuncs.length)) { - delete this.newfuncs; - delete this.newopts; - return Promise.resolve(this.painter); // simplify drawing + if (!this.#newfuncs || (indx >= this.#newfuncs.length)) { + this.#newfuncs = this.#newopts = undefined; + return Promise.resolve(this.#painter); // simplify drawing } - const func = this.newfuncs[indx], fopt = this.newopts[indx]; + const func = this.#newfuncs[indx], fopt = this.#newopts[indx]; - if (!func || this.pp?.findPainterFor(func)) - return this.drawNext(indx+1); + if (!func || this.#pad_painter?.findPainterFor(func)) + return this.drawNext(indx + 1); - const func_secondary_id = func?.fName ? `func_${func.fName}` : `indx_${indx}`; + const func_id = func?.fName ? `func_${func.fName}` : `indx_${indx}`; // Required to correctly draw multiple stats boxes // TODO: set reference via weak pointer - func.$main_painter = this.painter; + func.$main_painter = this.#painter; const promise = TPavePainter.canDraw(func) - ? TPavePainter.draw(this.painter.getDom(), func, fopt) - : this.pp.drawObject(this.painter.getDom(), func, fopt); + ? TPavePainter.draw(this.#pad_painter, func, fopt) + : this.#pad_painter.drawObject(this.#pad_painter, func, fopt); return promise.then(fpainter => { - fpainter.setSecondaryId(this.painter, func_secondary_id); - - return this.drawNext(indx+1); + fpainter.setSecondaryId(this.#painter, func_id); + return this.drawNext(indx + 1); }); } @@ -794,7 +1122,7 @@ class FunctionsHandler { const 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 + 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) @@ -805,6 +1133,14 @@ const kUserContour = BIT(10), // user specified contour levels class THistPainter extends ObjectPainter { + #doing_redraw_palette; // set during redrawing of palette + #ignore_frame; // true when drawing without frame functionality + #color_palette; // color palette used in histogram + #auto_exec; // can be reused when sending option back to server + #funcs_handler; // special instance for functions drawing + #contour; // histogram colors contour + #create_stats; // if stats was created by painter + /** @summary Constructor * @param {object|string} dom - DOM element for drawing or element id * @param {object} histo - TH1 derived histogram object */ @@ -812,14 +1148,11 @@ class THistPainter extends ObjectPainter { super(dom, histo); this.draw_content = true; this.nbinsx = this.nbinsy = 0; - this.accept_drops = true; // indicate that one can drop other objects like doing Draw('same') this.mode3d = false; } /** @summary Returns histogram object */ - getHisto() { - return this.getObject(); - } + getHisto() { return this.getObject(); } /** @summary Returns histogram axis */ getAxis(name) { @@ -833,18 +1166,11 @@ class THistPainter extends ObjectPainter { } /** @summary Returns true if TProfile */ - isTProfile() { - return this.matchObjectType(clTProfile); - } + isTProfile() { return this.matchObjectType(clTProfile); } /** @summary Returns true if histogram drawn instead of TF1/TF2 object */ isTF1() { return false; } - /** @summary Returns true if TH1K */ - isTH1K() { - return this.matchObjectType('TH1K'); - } - /** @summary Returns true if TH2Poly */ isTH2Poly() { return this.matchObjectType(/^TH2Poly/) || this.matchObjectType(/^TProfile2Poly/); @@ -862,9 +1188,8 @@ class THistPainter extends ObjectPainter { cleanup() { this.clear3DScene(); - delete this._color_palette; - delete this.fContour; - delete this.options; + this.clearHistPalette(); + this.#contour = undefined; super.cleanup(); } @@ -872,12 +1197,18 @@ class THistPainter extends ObjectPainter { /** @summary Returns number of histogram dimensions */ getDimension() { const histo = this.getHisto(); - if (!histo) return 0; - if (histo._typename.match(/^TH2/)) return 2; - if (histo._typename === clTProfile2D) return 2; - if (histo._typename.match(/^TH3/)) return 3; - if (histo._typename === clTProfile3D) return 3; - if (this.isTH2Poly()) return 2; + if (!histo) + return 0; + if (histo._typename.match(/^TH2/)) + return 2; + if (histo._typename === clTProfile2D) + return 2; + if (histo._typename.match(/^TH3/)) + return 3; + if (histo._typename === clTProfile3D) + return 3; + if (this.isTH2Poly()) + return 2; return 1; } @@ -887,26 +1218,28 @@ class THistPainter extends ObjectPainter { hdim = this.getDimension(), pp = this.getPadPainter(), pad = pp?.getRootPad(true); + let o = this.getOptions(true); - if (!this.options) - this.options = new THistDrawOptions(); + if (!o?.reset) + o = this.setOptions(new THistDrawOptions(), true); else - this.options.reset(); + o.reset(); // when changing draw option, reset attributes usage this.lineatt?.setUsed(false); this.fillatt?.setUsed(false); this.markeratt?.setUsed(false); - this.options.decode(opt || histo.fOption, hdim, histo, pp, pad, this); + o.decode(opt || histo.fOption, hdim, histo, pp, pad, this); - this.storeDrawOpt(opt); // opt will be return as default draw option, used in webcanvas + this.storeDrawOpt(opt); // opt will be return as default draw option, used in web canvas } /** @summary Copy draw options from other painter */ copyOptionsFrom(src) { - if (src === this) return; - const o = this.options, o0 = src.options; + if (src === this) + return; + const o = this.getOptions(), o0 = src.getOptions(); o.Mode3D = o0.Mode3D; o.Zero = o0.Zero; @@ -932,7 +1265,7 @@ class THistPainter extends ObjectPainter { scanContent(/* when_axis_changed */) { // function will be called once new histogram or // new histogram content is assigned - // one should find min,max,nbins, maxcontent values + // one should find min, max, bins number, content min/max values // if when_axis_changed === true specified, content will be scanned after axis zoom changed } @@ -941,29 +1274,44 @@ class THistPainter extends ObjectPainter { * In all other cases configured range must be derived from histogram itself */ checkPadRange() { if (this.isMainPainter()) - this.check_pad_range = this.options.Same ? 'pad_range' : true; + this.check_pad_range = this.getOptions().Same ? 'pad_range' : true; } /** @summary Create necessary histogram draw attributes */ createHistDrawAttributes(only_check_auto) { - const histo = this.getHisto(), o = this.options; + const histo = this.getHisto(), o = this.getOptions(); if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { const pp = this.getPadPainter(); if (isFunc(pp?.getAutoColor)) { const icolor = pp.getAutoColor(histo.$num_histos); - this._auto_exec = ''; // can be reused when sending option back to server - if (o._pfc > 1) { o._pfc = 1; histo.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (o._plc > 1) { o._plc = 1; histo.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (o._pmc > 1) { o._pmc = 1; histo.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } + this.#auto_exec = ''; + if (o._pfc > 1) { + o._pfc = 1; + histo.fFillColor = icolor; + this.#auto_exec += `SetFillColor(${icolor});;`; + this.deleteAttr('fill'); + } + if (o._plc > 1) { + o._plc = 1; + histo.fLineColor = icolor; + this.#auto_exec += `SetLineColor(${icolor});;`; + this.deleteAttr('line'); + } + if (o._pmc > 1) { + o._pmc = 1; + histo.fMarkerColor = icolor; + this.#auto_exec += `SetMarkerColor(${icolor});;`; + this.deleteAttr('marker'); + } } } if (only_check_auto) this.deleteAttr(); else { - this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); - this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); + this.createAttFill({ attr: histo, color: o.histoFillColor, pattern: o.histoFillPattern, kind: 1 }); + this.createAttLine({ attr: histo, color0: o.histoLineColor, width: o.histoLineWidth }); } } @@ -995,9 +1343,9 @@ class THistPainter extends ObjectPainter { } }; - copyTAxisMembers(tgt_histo.fXaxis, src_histo.fXaxis, this.snapid && !fp?.zoomChangedInteractive('x')); - copyTAxisMembers(tgt_histo.fYaxis, src_histo.fYaxis, this.snapid && !fp?.zoomChangedInteractive('y')); - copyTAxisMembers(tgt_histo.fZaxis, src_histo.fZaxis, this.snapid && !fp?.zoomChangedInteractive('z')); + copyTAxisMembers(tgt_histo.fXaxis, src_histo.fXaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('x')); + copyTAxisMembers(tgt_histo.fYaxis, src_histo.fYaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('y')); + copyTAxisMembers(tgt_histo.fZaxis, src_histo.fZaxis, this.hasSnapId() && !fp?.zoomChangedInteractive('z')); } /** @summary Update histogram object @@ -1008,25 +1356,35 @@ class THistPainter extends ObjectPainter { const histo = this.getHisto(), fp = this.getFramePainter(), pp = this.getPadPainter(), - o = this.options; + o = this.getOptions(); if (obj !== histo) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; // simple replace of object does not help - one can have different - // complex relations between histo and stat box, histo and colz axis, + // complex relations between histogram and stat box, histogram and colz axis, // one could have THStack or TMultiGraph object // The only that could be done is update of content - // check only stats bit, later other settings can be monitored const statpainter = pp?.findPainterFor(this.findStat()); + + // copy histogram bits if (histo.TestBit(kNoStats) !== obj.TestBit(kNoStats)) { - histo.fBits = obj.fBits; - if (statpainter) statpainter.Enabled = !histo.TestBit(kNoStats); + histo.SetBit(kNoStats, obj.TestBit(kNoStats)); + // here check only stats bit + if (statpainter) { + statpainter.Enabled = !histo.TestBit(kNoStats) && !o.NoStat; // && (!o.Same || o.ForceStat) + // remove immediately when redraw not called for disabled stats + if (!statpainter.Enabled) + statpainter.removeG(); + } } - // special treatment for webcanvas - also name can be changed - if (this.snapid !== undefined) { + histo.SetBit(kIsZoomed, obj.TestBit(kIsZoomed)); + + // special treatment for web canvas - also name can be changed + if (this.hasSnapId()) { histo.fName = obj.fName; o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas } @@ -1071,19 +1429,21 @@ class THistPainter extends ObjectPainter { histo.fMaximum = obj.fMaximum; histo.fSumw2 = obj.fSumw2; + if (!o.ominimum) + o.minimum = histo.fMinimum; + if (!o.omaximum) + o.maximum = histo.fMaximum; + if (this.getDimension() === 1) o.decodeSumw2(histo); if (this.isTProfile()) histo.fBinEntries = obj.fBinEntries; - else if (this.isTH1K()) { - histo.fNIn = obj.fNIn; - histo.fReady = 0; - } else if (this.isTH2Poly()) + else if (this.isTH2Poly()) histo.fBins = obj.fBins; // remove old functions, update existing, prepare to draw new one - this._funcHandler = new FunctionsHandler(this, pp, obj.fFunctions, statpainter); + this.#funcs_handler = new FunctionsHandler(this, pp, obj.fFunctions, statpainter, this.#create_stats); const changed_opt = (histo.fOption !== obj.fOption); histo.fOption = obj.fOption; @@ -1097,6 +1457,9 @@ class THistPainter extends ObjectPainter { if (!o.omaximum) o.maximum = histo.fMaximum; + if (!o.ominimum && !o.omaximum && o.minimum === o.maximum) + o.minimum = o.maximum = kNoZoom; + if (!fp || !fp.zoomChangedInteractive()) this.checkPadRange(); @@ -1107,6 +1470,19 @@ class THistPainter extends ObjectPainter { return true; } + /** @summary Access or modify histogram min/max + * @private */ + accessMM(ismin, v) { + const name = ismin ? 'minimum' : 'maximum', + o = this.getOptions(); + if (v === undefined) + return o[name]; + + o[name] = v; + + this.interactiveRedraw('pad', ismin ? `exec:SetMinimum(${v})` : `exec:SetMaximum(${v})`); + } + /** @summary Extract axes bins and ranges * @desc here functions are defined to convert index to axis value and back * was introduced to support non-equidistant bins */ @@ -1115,77 +1491,103 @@ class THistPainter extends ObjectPainter { if (axis.fXbins.length >= axis.fNbins) { axis.GetBinCoord = function(bin) { const indx = Math.round(bin); - if (indx <= 0) return this.fXmin; - if (indx > this.fNbins) return this.fXmax; - if (indx === bin) return this.fXbins[indx]; + if (indx <= 0) + return this.fXmin; + if (indx > this.fNbins) + return this.fXmax; + if (indx === bin) + return this.fXbins[indx]; const indx2 = (bin < indx) ? indx - 1 : indx + 1; - return this.fXbins[indx] * Math.abs(bin-indx2) + this.fXbins[indx2] * Math.abs(bin-indx); + return this.fXbins[indx] * Math.abs(bin - indx2) + this.fXbins[indx2] * Math.abs(bin - indx); }; axis.FindBin = function(x, add) { - for (let k = 1; k < this.fXbins.length; ++k) - if (x < this.fXbins[k]) return Math.floor(k-1+add); + for (let k = 1; k < this.fXbins.length; ++k) { + if (x < this.fXbins[k]) + return Math.floor(k - 1 + add); + } return this.fNbins; }; } else { axis.$binwidth = (axis.fXmax - axis.fXmin) / (axis.fNbins || 1); - axis.GetBinCoord = function(bin) { return this.fXmin + bin*this.$binwidth; }; + axis.GetBinCoord = function(bin) { return this.fXmin + bin * this.$binwidth; }; axis.FindBin = function(x, add) { return Math.floor((x - this.fXmin) / this.$binwidth + add); }; } }; this.nbinsx = this.nbinsy = this.nbinsz = 0; - const histo = this.getHisto(); + const histo = this.getHisto(), + o = this.getOptions(); this.nbinsx = histo.fXaxis.fNbins; this.xmin = histo.fXaxis.fXmin; this.xmax = histo.fXaxis.fXmax; + if (histo.fXaxis.TestBit(EAxisBits.kAxisRange) && (histo.fXaxis.fFirst !== histo.fXaxis.fLast)) { + if (histo.fXaxis.fFirst === 0) + this.xmin = histo.fXaxis.GetBinLowEdge(0); + if (histo.fXaxis.fLast === this.nbinsx + 1) + this.xmax = histo.fXaxis.GetBinLowEdge(this.nbinsx + 2); + } + assignTAxisFuncs(histo.fXaxis); this.ymin = histo.fYaxis.fXmin; this.ymax = histo.fYaxis.fXmax; - this._exact_y_range = (ndim === 1) && this.options.ohmin && this.options.ohmax; - - if (this._exact_y_range) { - this.ymin = this.options.hmin; - this.ymax = this.options.hmax; + if (ndim === 1 && o.exact_values_range()) { + this.ymin = o.hmin; + this.ymax = o.hmax; } if (ndim > 1) { this.nbinsy = histo.fYaxis.fNbins; + if (histo.fYaxis.TestBit(EAxisBits.kAxisRange) && (histo.fYaxis.fFirst !== histo.fYaxis.fLast)) { + if (histo.fYaxis.fFirst === 0) + this.ymin = histo.fYaxis.GetBinLowEdge(0); + if (histo.fYaxis.fLast === this.nbinsy + 1) + this.ymax = histo.fYaxis.GetBinLowEdge(this.nbinsy + 2); + } assignTAxisFuncs(histo.fYaxis); this.zmin = histo.fZaxis.fXmin; this.zmax = histo.fZaxis.fXmax; - if ((ndim === 2) && this.options.ohmin && this.options.ohmax) { - this.zmin = this.options.hmin; - this.zmax = this.options.hmax; + if ((ndim === 2) && o.ohmin && o.ohmax) { + this.zmin = o.hmin; + this.zmax = o.hmax; } } if (ndim > 2) { this.nbinsz = histo.fZaxis.fNbins; + if (histo.fZaxis.TestBit(EAxisBits.kAxisRange) && (histo.fZaxis.fFirst !== histo.fZaxis.fLast)) { + if (histo.fZaxis.fFirst === 0) + this.zmin = histo.fZaxis.GetBinLowEdge(0); + if (histo.fZaxis.fLast === this.nbinsz + 1) + this.zmax = histo.fZaxis.GetBinLowEdge(this.nbinsz + 2); + } assignTAxisFuncs(histo.fZaxis); - } + } } - /** @summary Draw axes for histogram + /** @summary Draw axes for histogram * @desc axes can be drawn only for main histogram */ async drawAxes() { const fp = this.getFramePainter(); - if (!fp) return false; + if (!fp) + return false; - const histo = this.getHisto(); + const histo = this.getHisto(), + o = this.getOptions(); // artificially add y range to display axes - if (this.ymin === this.ymax) this.ymax += 1; + if (this.ymin === this.ymax) + this.ymax += 1; if (!this.isMainPainter()) { const opts = { - second_x: (this.options.AxisPos >= 10), - second_y: (this.options.AxisPos % 10) === 1, + second_x: (o.AxisPos >= 10), + second_y: (o.AxisPos % 10) === 1, hist_painter: this }; @@ -1199,51 +1601,28 @@ class THistPainter extends ObjectPainter { return fp.drawAxes2(opts.second_x, opts.second_y); } - if (this.options.adjustFrame) { - const pad = this.getPadPainter().getRootPad(); - if (pad) { - if (pad.fUxmin < pad.fUxmax) { - fp.fX1NDC = (this.xmin - pad.fUxmin) / (pad.fUxmax - pad.fUxmin); - fp.fX2NDC = (this.xmax - pad.fUxmin) / (pad.fUxmax - pad.fUxmin); - } - if (pad.fUymin < pad.fUymax) { - fp.fY1NDC = (this.ymin - pad.fUymin) / (pad.fUymax - pad.fUymin); - fp.fY2NDC = (this.ymax - pad.fUymin) / (pad.fUymax - pad.fUymin); - } - - pad.fLeftMargin = fp.fX1NDC; - pad.fRightMargin = 1 - fp.fX2NDC; - pad.fBottomMargin = fp.fY1NDC; - pad.fTopMargin = 1 - fp.fY2NDC; - pad.fFrameLineColor = 0; - pad.fFrameLineWidth = 0; - fp.setRootPadRange(pad); - - fp.fillatt.setSolidColor('none'); - - fp.redraw(); - } - - this.options.adjustFrame = false; - } - fp.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0); - fp.createXY({ ndim: this.getDimension(), - check_pad_range: this.check_pad_range, - zoom_xmin: this.zoom_xmin, - zoom_xmax: this.zoom_xmax, - zoom_ymin: this.zoom_ymin, - zoom_ymax: this.zoom_ymax, - ymin_nz: this.ymin_nz, - swap_xy: (this.options.BarStyle >= 20), - reverse_x: this.options.RevX, - reverse_y: this.options.RevY, - symlog_x: this.options.SymlogX, - symlog_y: this.options.SymlogY, - Proj: this.options.Proj, - extra_y_space: this.options.Text && (this.options.BarStyle > 0), - hist_painter: this }); + fp.createXY({ + ndim: this.getDimension(), + check_pad_range: this.check_pad_range, + zoom_xmin: this.zoom_xmin, + zoom_xmax: this.zoom_xmax, + zoom_ymin: this.zoom_ymin, + zoom_ymax: this.zoom_ymax, + xmin_nz: histo.$xmin_nz, + ymin_nz: this.ymin_nz ?? histo.$ymin_nz, + swap_xy: o.swap_xy(), + xticks: o.xticks, + yticks: o.yticks, + reverse_x: o.RevX, + reverse_y: o.RevY, + symlog_x: o.SymlogX, + symlog_y: o.SymlogY, + Proj: o.Proj, + extra_y_space: o.Text && (o.BarStyle > 0), + hist_painter: this + }); delete this.check_pad_range; delete this.zoom_xmin; @@ -1251,14 +1630,14 @@ class THistPainter extends ObjectPainter { delete this.zoom_ymin; delete this.zoom_ymax; - if (this.options.Same) + if (o.Same) return false; - const disable_axis_draw = (this.options.Axis < 0) || (this.options.Axis === 2); + const disable_axis_draw = (o.Axis < 0) || (o.Axis === 2); return fp.drawAxes(false, disable_axis_draw, disable_axis_draw, - this.options.AxisPos, this.options.Zscale && this.options.Zvert, - this.options.Zscale && !this.options.Zvert, this.options.Axis !== 1); + o.AxisPos, o.Zscale && o.Zvert, + o.Zscale && !o.Zvert, o.Axis !== 1); } /** @summary Inform web canvas that something changed in the histogram */ @@ -1270,9 +1649,9 @@ class THistPainter extends ObjectPainter { /** @summary Fill option object used in TWebCanvas */ fillWebObjectOptions(res) { - if (this._auto_exec && res) { - res.fcust = 'auto_exec:' + this._auto_exec; - delete this._auto_exec; + if (this.#auto_exec && res) { + res.fcust = 'auto_exec:' + this.#auto_exec; + this.#auto_exec = undefined; } } @@ -1281,57 +1660,23 @@ class THistPainter extends ObjectPainter { const histo = this.getHisto(); if (!this.isMainPainter() || !histo) return false; - if (arg === 'only-check') + if (arg === kOnlyCheck) return !histo.TestBit(kNoTitle); histo.InvertBit(kNoTitle); - this.updateHistTitle().then(() => this.processOnlineChange(`exec:SetBit(TH1::kNoTitle,${histo.TestBit(kNoTitle)?1:0})`)); + this.updateHistTitle().then(() => this.processOnlineChange(`exec:SetBit(TH1::kNoTitle,${histo.TestBit(kNoTitle) ? 1 : 0})`)); } /** @summary Only redraw histogram title * @return {Promise} with painter */ - async updateHistTitle() { - // case when histogram drawn over other histogram (same option) - if (!this.isMainPainter() || this.options.Same || (this.options.Axis > 0)) - return this; - - const tpainter = this.getPadPainter()?.findPainterFor(null, kTitle, clTPaveText), - pt = tpainter?.getObject(); - - if (!tpainter || !pt) - return this; - - const histo = this.getHisto(), st = gStyle, - draw_title = !histo.TestBit(kNoTitle) && (st.fOptTitle > 0); - - pt.Clear(); - if (draw_title) pt.AddText(histo.fTitle); - return tpainter.redraw().then(() => this); + async updateHistTitle(first_time) { + const o = this.getOptions(), + histo = this.getHisto(); + return drawObjectTitle(this, first_time, this.isMainPainter() && !o.Same && (o.Axis <= 0), !histo.TestBit(kNoTitle)); } /** @summary Draw histogram title * @return {Promise} with painter */ - async drawHistTitle() { - // case when histogram drawn over other histogram (same option) - if (!this.isMainPainter() || this.options.Same || (this.options.Axis > 0)) - return this; - - const histo = this.getHisto(), st = gStyle, - draw_title = !histo.TestBit(kNoTitle) && (st.fOptTitle > 0); - - let pt = this.getPadPainter()?.findInPrimitives(kTitle, clTPaveText); - - if (pt) { - pt.Clear(); - if (draw_title) pt.AddText(histo.fTitle); - return this; - } - - pt = create(clTPaveText); - Object.assign(pt, { fName: kTitle, fFillColor: st.fTitleColor, fFillStyle: st.fTitleStyle, fBorderSize: st.fTitleBorderSize, - fTextFont: st.fTitleFont, fTextSize: st.fTitleFontSize, fTextColor: st.fTitleTextColor, fTextAlign: st.fTitleAlign }); - if (draw_title) pt.AddText(histo.fTitle); - return TPavePainter.draw(this.getDom(), pt, 'postitle'); - } + async drawHistTitle() { return this.updateHistTitle(true); } /** @summary Live change and update of title drawing * @desc Used from the GED */ @@ -1339,10 +1684,11 @@ class THistPainter extends ObjectPainter { const histo = this.getHisto(), tpainter = this.getPadPainter()?.findPainterFor(null, kTitle); - if (!histo || !tpainter) return null; + if (!histo || !tpainter) + return null; if (arg === 'check') - return (!this.isMainPainter() || this.options.Same) ? null : histo; + return (!this.isMainPainter() || this.getOptions().Same) ? null : histo; tpainter.clearPave(); tpainter.addText(histo.fTitle); @@ -1354,12 +1700,14 @@ class THistPainter extends ObjectPainter { /** @summary Update statistics when web canvas is drawn */ updateStatWebCanvas() { - if (!this.snapid) return; + if (!this.hasSnapId()) + return; const stat = this.findStat(), statpainter = this.getPadPainter()?.findPainterFor(stat); - if (statpainter && !statpainter.snapid) statpainter.redraw(); + if (statpainter && !statpainter.hasSnapId()) + statpainter.redraw(); } /** @summary Find stats box in list of functions */ @@ -1367,29 +1715,33 @@ class THistPainter extends ObjectPainter { return this.findFunction(clTPaveStats, 'stats'); } - /** @summary Toggle statbox drawing + /** @summary Toggle stat box drawing * @private */ toggleStat(arg) { + const pp = this.getPadPainter(); let stat = this.findStat(), statpainter; - if (!arg) arg = ''; + if (!arg) + arg = ''; if (!stat) { - if (arg.indexOf('-check') > 0) return false; - // when statbox created first time, one need to draw it + if (arg.indexOf('-check') > 0) + return false; + // when stat box created first time, one need to draw it stat = this.createStat(true); } else - statpainter = this.getPadPainter()?.findPainterFor(stat); + statpainter = pp.findPainterFor(stat); - if (arg === 'only-check') + if (arg === kOnlyCheck) return statpainter?.Enabled || false; if (arg === 'fitpar-check') return stat?.fOptFit || false; if (arg === 'fitpar-toggle') { - if (!stat) return false; + if (!stat) + return false; stat.fOptFit = stat.fOptFit ? 0 : 1111; // for websocket command should be send to server statpainter?.redraw(); return true; @@ -1399,15 +1751,14 @@ class THistPainter extends ObjectPainter { if (statpainter) { statpainter.Enabled = !statpainter.Enabled; - this.options.StatEnabled = statpainter.Enabled; // used only for interactive + this.getOptions().StatEnabled = statpainter.Enabled; // used only for interactive // when stat box is drawn, it always can be drawn individually while it // should be last for colz redrawPad is used statpainter.redraw(); has_stats = statpainter.Enabled; } else { - const prev_name = this.selectCurrentPad(this.getPadName()); // return promise which will be used to process - has_stats = TPavePainter.draw(this.getDom(), stat).then(() => this.selectCurrentPad(prev_name)); + has_stats = TPavePainter.draw(pp, stat); } this.processOnlineChange(`exec:SetBit(TH1::kNoStats,${has_stats ? 0 : 1})`, this); @@ -1415,41 +1766,48 @@ class THistPainter extends ObjectPainter { return has_stats; } - /** @summary Returns true if stats box fill can be ingored */ + /** @summary Returns true if stats box fill can be ignored */ isIgnoreStatsFill() { - return !this.getObject() || (!this.draw_content && !this.create_stats && !this.snapid); // || (this.options.Axis > 0); + return !this.getObject() || (!this.draw_content && !this.#create_stats && !this.hasSnapId()); } /** @summary Create stat box for histogram if required */ createStat(force) { - const histo = this.getHisto(); - if (!histo) return null; + const histo = this.getHisto(), + o = this.getOptions(); + if (!histo) + return null; - if (!force && !this.options.ForceStat) { - if (this.options.NoStat || histo.TestBit(kNoStats) || !settings.AutoStat) return null; - if (!this.isMainPainter()) return null; + if (!force && !o.ForceStat) { + if (o.NoStat || histo.TestBit(kNoStats) || !settings.AutoStat) + return null; + if (!this.isMainPainter()) + return null; } const st = gStyle; let stats = this.findStat(), - optstat = this.options.optstat, - optfit = this.options.optfit; + optstat = o.optstat, + optfit = o.optfit; if (optstat !== undefined) { - if (stats) stats.fOptStat = optstat; - delete this.options.optstat; + if (stats) + stats.fOptStat = optstat; + o.optstat = undefined; } else optstat = histo.$custom_stat || st.fOptStat; if (optfit !== undefined) { - if (stats) stats.fOptFit = optfit; - delete this.options.optfit; + if (stats) + stats.fOptFit = optfit; + o.optfit = undefined; } else optfit = st.fOptFit; - if (!stats && !optstat && !optfit) return null; + if (!stats && !optstat && !optfit) + return null; - this.create_stats = true; + this.#create_stats = true; if (stats) return stats; @@ -1471,12 +1829,15 @@ class THistPainter extends ObjectPainter { /** @summary Find function in histogram list of functions */ findFunction(type_name, obj_name) { const funcs = this.getHisto()?.fFunctions?.arr; - if (!funcs) return null; + if (!funcs) + return null; for (let i = 0; i < funcs.length; ++i) { const f = funcs[i]; - if (obj_name && (f.fName !== obj_name)) continue; - if (f._typename === type_name) return f; + if (obj_name && (f.fName !== obj_name)) + continue; + if (f._typename === type_name) + return f; } return null; @@ -1485,7 +1846,8 @@ class THistPainter extends ObjectPainter { /** @summary Add function to histogram list of functions */ addFunction(obj, asfirst) { const histo = this.getHisto(); - if (!histo || !obj) return; + if (!histo || !obj) + return; if (!histo.fFunctions) histo.fFunctions = create(clTList); @@ -1498,16 +1860,18 @@ class THistPainter extends ObjectPainter { /** @summary Check if such function should be drawn directly */ needDrawFunc(histo, func) { + const o = this.getOptions(); + if (func._typename === clTPaveStats) - return (func.fName !== 'stats') || (!histo.TestBit(kNoStats) && !this.options.NoStat); + return (func.fName !== 'stats') || (!histo.TestBit(kNoStats) && !o.NoStat); // && (!o.Same || o.ForceStat)) - if ((func._typename === clTF1) || (func._typename === clTF2)) - return this.options.AllFunc || !func.TestBit(BIT(9)); // TF1::kNotDraw + if ((func._typename === clTF1) || (func._typename === clTF2)) + return o.AllFunc || !func.TestBit(BIT(9)); // TF1::kNotDraw - if ((func._typename === 'TGraphDelaunay') || (func._typename === 'TGraphDelaunay2D')) - return false; // do not try to draw delaunay classes + if ((func._typename === 'TGraphDelaunay') || (func._typename === 'TGraphDelaunay2D')) + return false; // do not try to draw delaunay classes - return func._typename !== clTPaletteAxis; + return func._typename !== clTPaletteAxis; } /** @summary Method draws functions from the histogram list of functions @@ -1520,22 +1884,25 @@ class THistPainter extends ObjectPainter { /** @summary Method used to update functions which are prepared before * @return {Promise} fulfilled when drawing is ready */ async updateFunctions() { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; return res; } /** @summary Returns selected index for specified axis * @desc be aware - here indexes starts from 0 */ getSelectIndex(axis, side, add) { - let indx = 0, taxis = this.getAxis(axis); - const nbin = this[`nbins${axis}`] ?? 0; - - if (this.options.second_x && axis === 'x') axis = 'x2'; - if (this.options.second_y && axis === 'y') axis = 'y2'; - const main = this.getFramePainter(), - min = main ? main[`zoom_${axis}min`] : 0, - max = main ? main[`zoom_${axis}max`] : 0; + let indx, taxis = this.getAxis(axis); + const nbin = this[`nbins${axis}`] ?? 0, + o = this.getOptions(); + + if (o.second_x && axis === 'x') + axis = 'x2'; + if (o.second_y && axis === 'y') + axis = 'y2'; + const fp = this.getFramePainter(), + min = fp ? fp[`zoom_${axis}min`] : 0, + max = fp ? fp[`zoom_${axis}max`] : 0; if ((min !== max) && taxis) { if (side === 'left') @@ -1543,25 +1910,31 @@ class THistPainter extends ObjectPainter { else indx = taxis.FindBin(max, (add || 0) + 0.5); if (indx < 0) - indx = 0; else - if (indx > nbin) + indx = 0; + else if (indx > nbin) indx = nbin; } else indx = (side === 'left') ? 0 : nbin; - // TAxis object of histogram, where user range can be stored if (taxis) { if ((taxis.fFirst === taxis.fLast) || !taxis.TestBit(EAxisBits.kAxisRange) || - ((taxis.fFirst <= 1) && (taxis.fLast >= nbin))) taxis = undefined; + ((taxis.fFirst === 1) && (taxis.fLast === nbin))) + taxis = null; } if (side === 'left') { - if (indx < 0) indx = 0; - if (taxis && (taxis.fFirst > 1) && (indx < taxis.fFirst)) indx = taxis.fFirst - 1; + indx = Math.max(indx, 0); + if (taxis && (taxis.fFirst > 1) && (indx < taxis.fFirst)) + indx = taxis.fFirst - 1; + else if (taxis?.fFirst === 0) // showing underflow bin + indx = -1; } else { - if (indx > nbin) indx = nbin; - if (taxis && (taxis.fLast <= nbin) && (indx>taxis.fLast)) indx = taxis.fLast; + indx = Math.min(indx, nbin); + if (taxis && (taxis.fLast <= nbin) && (indx > taxis.fLast)) + indx = taxis.fLast; + else if (taxis?.fLast === nbin + 1) + indx = nbin + 1; } return indx; @@ -1569,31 +1942,41 @@ class THistPainter extends ObjectPainter { /** @summary Unzoom user range if any */ unzoomUserRange(dox, doy, doz) { - const histo = this.getHisto(); - if (!histo) return false; + const histo = this.getHisto(), + o = this.getOptions(); + + if (!histo) + return false; let res = false; const unzoomTAxis = obj => { - if (!obj || !obj.TestBit(EAxisBits.kAxisRange)) return false; - if (obj.fFirst === obj.fLast) return false; - if ((obj.fFirst <= 1) && (obj.fLast >= obj.fNbins)) return false; + if (!obj || !obj.TestBit(EAxisBits.kAxisRange)) + return false; + if (obj.fFirst === obj.fLast) + return false; + if ((obj.fFirst <= 1) && (obj.fLast >= obj.fNbins)) + return false; obj.InvertBit(EAxisBits.kAxisRange); return true; - }, - - uzoomMinMax = ndim => { - if (this.getDimension() !== ndim) return false; - if ((this.options.minimum === kNoZoom) && (this.options.maximum === kNoZoom)) return false; - if (!this.draw_content) return false; // if not drawing content, not change min/max - this.options.minimum = this.options.maximum = kNoZoom; - this.scanContent(true); // to reset ymin/ymax + }, uzoomMinMax = ndim => { + if (this.getDimension() !== ndim) + return false; + if ((o.minimum === kNoZoom) && (o.maximum === kNoZoom)) + return false; + if (!this.draw_content) + return false; // if not drawing content, not change min/max + o.minimum = o.maximum = kNoZoom; + this.scanContent(); // to reset ymin/ymax return true; }; - if (dox && unzoomTAxis(histo.fXaxis)) res = true; - if (doy && (unzoomTAxis(histo.fYaxis) || uzoomMinMax(1))) res = true; - if (doz && (unzoomTAxis(histo.fZaxis) || uzoomMinMax(2))) res = true; + if (dox && unzoomTAxis(histo.fXaxis)) + res = true; + if (doy && (unzoomTAxis(histo.fYaxis) || uzoomMinMax(1))) + res = true; + if (doz && (unzoomTAxis(histo.fZaxis) || uzoomMinMax(2))) + res = true; return res; } @@ -1604,7 +1987,7 @@ class THistPainter extends ObjectPainter { * @return {Promise} for ready */ async addInteractivity() { const ismain = this.isMainPainter(), - second_axis = (this.options.AxisPos > 0), + second_axis = (this.getOptions().AxisPos > 0), fp = (ismain || second_axis) ? this.getFramePainter() : null; return fp?.addInteractivity(!ismain && second_axis) ?? false; } @@ -1613,24 +1996,26 @@ class THistPainter extends ObjectPainter { changeUserRange(menu, arg) { const histo = this.getHisto(), taxis = histo ? histo[`f${arg}axis`] : null; - if (!taxis) return; + if (!taxis) + return; let curr = `[1,${taxis.fNbins}]`; if (taxis.TestBit(EAxisBits.kAxisRange)) - curr = `[${taxis.fFirst},${taxis.fLast}]`; + curr = `[${taxis.fFirst},${taxis.fLast}]`; menu.input(`Enter user range for axis ${arg} like [1,${taxis.fNbins}]`, curr).then(res => { - if (!res) return; + if (!res) + return; res = JSON.parse(res); - if (!res || (res.length !== 2)) return; - const first = parseInt(res[0]), last = parseInt(res[1]); - if (!Number.isInteger(first) || !Number.isInteger(last)) return; + if (!res || (res.length !== 2)) + return; + const first = parseInt(res[0]), + last = parseInt(res[1]); + if (!Number.isInteger(first) || !Number.isInteger(last)) + return; taxis.fFirst = first; taxis.fLast = last; - - const newflag = (taxis.fFirst < taxis.fLast) && (taxis.fFirst >= 1) && (taxis.fLast <= taxis.fNbins); - if (newflag !== taxis.TestBit(EAxisBits.kAxisRange)) - taxis.InvertBit(EAxisBits.kAxisRange); + taxis.SetBit(EAxisBits.kAxisRange, (taxis.fFirst < taxis.fLast) && (taxis.fFirst >= 1) && (taxis.fLast <= taxis.fNbins)); this.interactiveRedraw(); }); @@ -1638,9 +2023,11 @@ class THistPainter extends ObjectPainter { /** @summary Start dialog to modify range of axis where histogram values are displayed */ changeValuesRange(menu) { + const o = this.getOptions(); + let curr; - if ((this.options.minimum !== kNoZoom) && (this.options.maximum !== kNoZoom)) - curr = `[${this.options.minimum},${this.options.maximum}]`; + if ((o.minimum !== kNoZoom) && (o.maximum !== kNoZoom)) + curr = `[${o.minimum},${o.maximum}]`; else curr = `[${this.gminbin},${this.gmaxbin}]`; @@ -1648,14 +2035,14 @@ class THistPainter extends ObjectPainter { res = res ? JSON.parse(res) : []; if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) - this.options.minimum = this.options.maximum = kNoZoom; - else { - this.options.minimum = res[0]; - this.options.maximum = res[1]; - } + o.minimum = o.maximum = kNoZoom; + else { + o.minimum = res[0]; + o.maximum = res[1]; + } this.interactiveRedraw(); - }); + }); } /** @summary Execute histogram menu command @@ -1666,7 +2053,7 @@ class THistPainter extends ObjectPainter { if (method.fClassName === clTAxis) { const p = isStr(method.$execid) ? method.$execid.indexOf('#') : -1, - kind = p > 0 ? method.$execid.slice(p+1) : 'x', + kind = p > 0 ? method.$execid.slice(p + 1) : 'x', fp = this.getFramePainter(); if (method.fName === 'UnZoom') { fp?.unzoom(kind); @@ -1674,7 +2061,7 @@ class THistPainter extends ObjectPainter { } else if (method.fName === 'SetRange') { const axis = fp?.getAxis(kind), bins = JSON.parse(`[${args}]`); if (axis && bins?.length === 2) - fp?.zoom(kind, axis.GetBinLowEdge(bins[0]), axis.GetBinLowEdge(bins[1]+1)); + fp?.zoom(kind, axis.GetBinLowEdge(bins[0]), axis.GetBinLowEdge(bins[1] + 1)); // let execute command on server } else if (method.fName === 'SetRangeUser') { const values = JSON.parse(`[${args}]`); @@ -1690,38 +2077,50 @@ class THistPainter extends ObjectPainter { /** @summary Fill histogram context menu */ fillContextMenuItems(menu) { const histo = this.getHisto(), - fp = this.getFramePainter(); - if (!histo) return; + fp = this.getFramePainter(), + o = this.getOptions(); + + if (!histo) + return; - if ((this.options.Axis <= 0) && !this.isTF1()) - menu.addchk(this.toggleStat('only-check'), 'Show statbox', () => this.toggleStat()); + if ((o.Axis <= 0) && !this.isTF1()) + menu.addchk(this.toggleStat(kOnlyCheck), 'Show statbox', () => this.toggleStat()); - if (histo.fTitle && this.isMainPainter()) - menu.addchk(this.toggleTitle('only-check'), 'Show title', () => this.toggleTitle()); + if (this.isMainPainter()) { + menu.sub('Title'); + menu.addchk(this.toggleTitle(kOnlyCheck), 'Show', () => this.toggleTitle()); + menu.add('Edit', () => menu.input('Enter histogram title', histo.fTitle).then(res => { + setHistogramTitle(histo, res); + this.interactiveRedraw(); + })); + menu.endsub(); + } if (this.draw_content) { if (this.getDimension() === 1) menu.add('User range X', () => this.changeUserRange(menu, 'X')); - else { - menu.add('sub:User ranges'); + else { + menu.sub('User ranges'); menu.add('X', () => this.changeUserRange(menu, 'X')); menu.add('Y', () => this.changeUserRange(menu, 'Y')); if (this.getDimension() > 2) menu.add('Z', () => this.changeUserRange(menu, 'Z')); else menu.add('Values', () => this.changeValuesRange(menu)); - menu.add('endsub:'); + menu.endsub(); } if (isFunc(this.fillHistContextMenu)) this.fillHistContextMenu(menu); + + menu.addRedrawMenu(this.getPrimary()); } - if (this.options.Mode3D) { + if (o.Mode3D) { // menu for 3D drawings if (menu.size() > 0) - menu.add('separator'); + menu.separator(); const main = this.getMainPainter() || this; @@ -1749,13 +2148,13 @@ class THistPainter extends ObjectPainter { } if (this.draw_content) { - menu.addchk(!this.options.Zero, 'Suppress zeros', () => { - this.options.Zero = !this.options.Zero; + menu.addchk(!o.Zero, 'Suppress zeros', () => { + o.Zero = !o.Zero; this.interactiveRedraw('pad'); }); - if ((this.options.Lego === 12) || (this.options.Lego === 14)) { - menu.addchk(this.options.Zscale, 'Z scale', () => this.toggleColz()); + if ((o.Lego === 12) || (o.Lego === 14)) { + menu.addchk(o.Zscale, 'Z scale', () => this.toggleColz()); this.fillPaletteMenu(menu, true); } } @@ -1768,19 +2167,12 @@ class THistPainter extends ObjectPainter { menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); } - /** @summary Returns snap id for object or subelement + /** @summary Returns snap id for object or sub-element * @private */ getSnapId(subelem) { - if (!this.snapid) - return ''; - let res = this.snapid.toString(); - if (subelem) { - res += '#'; - if (this.isTF1() && (subelem === 'x' || subelem === 'y' || subelem === 'z')) - res += 'hist#'; - res += subelem; - } - return res; + if (this.isTF1() && (subelem === 'x' || subelem === 'y' || subelem === 'z')) + subelem = 'hist#' + subelem; + return super.getSnapId(subelem); } /** @summary Auto zoom into histogram non-empty range @@ -1790,7 +2182,8 @@ class THistPainter extends ObjectPainter { /** @summary Process click on histogram-defined buttons */ clickButton(funcname) { const fp = this.getFramePainter(); - if (!this.isMainPainter() || !fp) return false; + if (!this.isMainPainter() || !fp) + return false; switch (funcname) { case 'ToggleZoom': @@ -1806,6 +2199,7 @@ class THistPainter extends ObjectPainter { case 'ToggleLogY': return fp.toggleAxisLog('y'); case 'ToggleLogZ': return fp.toggleAxisLog('z'); case 'ToggleStatBox': return getPromise(this.toggleStat()); + case 'ToggleColorZ': return this.toggleColz(); } return false; } @@ -1813,7 +2207,8 @@ class THistPainter extends ObjectPainter { /** @summary Fill pad toolbar with histogram-related functions */ fillToolbar(not_shown) { const pp = this.getPadPainter(); - if (!pp) return; + if (!pp) + return; pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); @@ -1831,25 +2226,26 @@ class THistPainter extends ObjectPainter { tip = { bin: indx, name: histo.fName, title: histo.fTitle }; switch (this.getDimension()) { case 1: - tip.ix = indx; tip.iy = 1; + tip.ix = indx; + tip.iy = 1; tip.value = histo.getBinContent(tip.ix); tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(indx-1); + tip.lines = this.getBinTooltips(indx - 1); break; case 2: tip.ix = indx % (this.nbinsx + 2); tip.iy = (indx - tip.ix) / (this.nbinsx + 2); tip.value = histo.getBinContent(tip.ix, tip.iy); tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1); + tip.lines = this.getBinTooltips(tip.ix - 1, tip.iy - 1); break; case 3: - tip.ix = indx % (this.nbinsx+2); - tip.iy = ((indx - tip.ix) / (this.nbinsx+2)) % (this.nbinsy+2); - tip.iz = (indx - tip.ix - tip.iy * (this.nbinsx+2)) / (this.nbinsx+2) / (this.nbinsy+2); + tip.ix = indx % (this.nbinsx + 2); + tip.iy = ((indx - tip.ix) / (this.nbinsx + 2)) % (this.nbinsy + 2); + tip.iz = (indx - tip.ix - tip.iy * (this.nbinsx + 2)) / (this.nbinsx + 2) / (this.nbinsy + 2); tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); tip.error = histo.getBinError(indx); - tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1, tip.iz-1); + tip.lines = this.getBinTooltips(tip.ix - 1, tip.iy - 1, tip.iz - 1); break; } @@ -1859,68 +2255,100 @@ class THistPainter extends ObjectPainter { /** @summary Create contour object for histogram */ createContour(nlevels, zmin, zmax, zminpositive, custom_levels) { const cntr = new HistContour(zmin, zmax), - ndim = this.getDimension(); + ndim = this.getDimension(), + is_th2poly = this.isTH2Poly(), + fp = this.getFramePainter(), + o = this.getOptions(); if (custom_levels) cntr.createCustom(custom_levels); else { - if (nlevels < 2) nlevels = gStyle.fNumberContours; - const pad = this.getPadPainter().getRootPad(true), + if (nlevels < 2) + nlevels = gStyle.fNumberContours; + const pad = this.getPadPainter()?.getRootPad(true), logv = pad?.fLogv ?? ((ndim === 2) && pad?.fLogz); cntr.createNormal(nlevels, logv ?? 0, zminpositive); } - cntr.configIndicies(this.options.Zero ? -1 : 0, (cntr.colzmin !== 0) || !this.options.Zero || this.isTH2Poly() ? 0 : -1); + cntr.configIndicies(o.Zero && !is_th2poly ? -1 : 0, cntr.colzmin || !o.Zero || is_th2poly ? 0 : -1); - const fp = this.getFramePainter(); if (fp && (ndim < 3) && !fp.mode3d) { fp.zmin = cntr.colzmin; fp.zmax = cntr.colzmax; } - this.fContour = cntr; + this.#contour = cntr; return cntr; } - /** @summary Return contour object */ - getContour(force_recreate) { - if (this.fContour && !force_recreate) - return this.fContour; - - const main = this.getMainPainter(), - fp = this.getFramePainter(); + /** @summary Return Z-scale ranges to create contour */ + #getContourRanges(main, fp) { + const o = this.getOptions(), + src = (this !== main) && ((main?.minbin !== undefined) || main?.options.ohmin) && !o.IgnoreMainScale && !main?.tt_handle?.ScatterPlot ? main : this; + let apply_min, zmin = src.minbin, zmax = src.maxbin, zminpos = src.minposbin; - if (main?.fContour && (main !== this) && !this.options.IgnoreMainScale) { - this.fContour = main.fContour; - return this.fContour; + if (zmin === zmax) { + if (src.options.ohmin && src.options.ohmax) { + zmin = src.options.hmin; + zmax = src.options.hmax; + zminpos = Math.max(zmin, zmax * 1e-10); + } else { + zmin = src.gminbin; + zmax = src.gmaxbin; + zminpos = src.gminposbin; + } } - // if not initialized, first create contour array - // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz - // when no same0 draw option specified, use main painter for creating contour, also ignore scatter drawing for main painer - const histo = this.getObject(), - src = (this !== main) && (main?.minbin !== undefined) && !this.options.IgnoreMainScale && !main?.tt_handle?.ScatterPlot ? main : this; - let nlevels = 0, apply_min, - zmin = src.minbin, zmax = src.maxbin, zminpos = src.minposbin, - custom_levels; - if (zmin === zmax) { zmin = src.gminbin; zmax = src.gmaxbin; zminpos = src.gminposbin; } - let gzmin = zmin, gzmax = zmax; - if (this.options.minimum !== kNoZoom) { zmin = this.options.minimum; gzmin = Math.min(gzmin, zmin); apply_min = true; } - if (this.options.maximum !== kNoZoom) { zmax = this.options.maximum; gzmax = Math.max(gzmax, zmax); apply_min = false; } + if (o.minimum !== kNoZoom) { + zmin = o.minimum; + gzmin = Math.min(gzmin, zmin); + apply_min = true; + } + if (o.maximum !== kNoZoom) { + zmax = o.maximum; + gzmax = Math.max(gzmax, zmax); + apply_min = false; + } + if (zmin >= zmax) { - if (apply_min) + if (apply_min || !zmin) zmax = zmin + 1; else zmin = zmax - 1; } - if (fp && (fp.zoom_zmin !== fp.zoom_zmax)) { - zmin = fp.zoom_zmin; - zmax = fp.zoom_zmax; + if (fp?.zoomChangedInteractive('z')) { + const mod = (fp.zoom_zmin !== fp.zoom_zmax); + zmin = mod ? fp.zoom_zmin : gzmin; + zmax = mod ? fp.zoom_zmax : gzmax; + } + + return { zmin, zmax, zminpos, gzmin, gzmax }; + } + + /** @summary Return contour object */ + getContour(force_recreate) { + if ((force_recreate === false) || (this.#contour && (force_recreate !== true))) + return this.#contour; + + const main = this.getMainPainter(), + fp = this.getFramePainter(), + main_cont = main?.getContour(false); + + if (main_cont && (main !== this) && !this.getOptions().IgnoreMainScale) { + this.#contour = main_cont; + return main_cont; } + // if not initialized, first create contour array + // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz + // when no same0 draw option specified, use main painter for creating contour, also ignore scatter drawing for main painter + const histo = this.getObject(), + r = this.#getContourRanges(main, fp); + let nlevels = 0, custom_levels; + if (histo.fContour?.length > 1) { if (histo.TestBit(kUserContour)) custom_levels = histo.fContour; @@ -1928,17 +2356,17 @@ class THistPainter extends ObjectPainter { nlevels = histo.fContour.length; } - const cntr = this.createContour(nlevels, zmin, zmax, zminpos, custom_levels); + const cntr = this.createContour(nlevels, r.zmin, r.zmax, r.zminpos, custom_levels); if ((this.getDimension() < 3) && fp) { - fp.zmin = gzmin; - fp.zmax = gzmax; + fp.zmin = r.gzmin; + fp.zmax = r.gzmax; - if ((gzmin !== cntr.colzmin) || (gzmax !== cntr.colzmax)) { + if ((r.gzmin !== cntr.colzmin) || (r.gzmax !== cntr.colzmax)) { fp.zoom_zmin = cntr.colzmin; fp.zoom_zmax = cntr.colzmax; } else - fp.zoom_zmin = fp.zoom_zmax = undefined; + fp.zoom_zmin = fp.zoom_zmax = 0; } return cntr; @@ -1952,35 +2380,47 @@ class THistPainter extends ObjectPainter { /** @summary Returns color palette associated with histogram * @desc Create if required, checks pad and canvas for custom palette */ getHistPalette(force) { - if (force) this._color_palette = null; - const pp = this.getPadPainter(); - if (!this._color_palette && !this.options.Palette) { + let pal = force ? null : this.#color_palette; + if (pal) + return pal; + const pp = this.getPadPainter(), + o = this.getOptions(); + + if (!o.Palette) { if (isFunc(pp?.getCustomPalette)) - this._color_palette = pp.getCustomPalette(); + pal = pp.getCustomPalette(); } - if (!this._color_palette) - this._color_palette = getColorPalette(this.options.Palette, pp?.isGrayscale()); - return this._color_palette; + if (!pal) + pal = getColorPalette(o.Palette, pp?.isGrayscale()); + this.#color_palette = pal; + return pal; + } + + /** @summary Remove palette */ + clearHistPalette() { + this.#color_palette = undefined; } /** @summary Fill menu entries for palette */ fillPaletteMenu(menu, only_palette) { - menu.addPaletteMenu(this.options.Palette || settings.Palette, arg => { - this.options.Palette = parseInt(arg); + const o = this.getOptions(); + + menu.addPaletteMenu(o.Palette || settings.Palette, arg => { + o.Palette = parseInt(arg); this.getHistPalette(true); this.redraw(); // redraw histogram }); if (!only_palette) { menu.add('Default position', () => { - this.drawColorPalette(this.options.Zscale, false, true) - .then(() => this.processOnlineChange('drawopt')); + this.drawColorPalette(o.Zscale, false, true) + .then(() => this.processOnlineChange('drawopt')); }, 'Set default position for palette'); const pal = this.findFunction(clTPaletteAxis), is_vert = !pal ? true : pal.fX2NDC - pal.fX1NDC < pal.fY2NDC - pal.fY1NDC; menu.addchk(is_vert, 'Vertical', flag => { - this.options.Zvert = flag; - this.drawColorPalette(this.options.Zscale, false, 'toggle') + o.Zvert = flag; + this.drawColorPalette(o.Zscale, false, 'toggle') .then(() => this.processOnlineChange('drawopt')); }, 'Toggle palette vertical/horizontal flag'); @@ -1991,25 +2431,25 @@ class THistPainter extends ObjectPainter { /** @summary draw color palette * @return {Promise} when done */ async drawColorPalette(enabled, postpone_draw, can_move) { + const o = this.getOptions(), + do_toggle = can_move === 'toggle'; + + // in special cases like scatter palette drawing is ignored + if (o.IgnorePalette) + return null; + // only when create new palette, one could change frame size - const mp = this.getMainPainter(), - pp = this.getPadPainter(); - if (mp !== this) { - if (mp && (mp.draw_content !== false) && mp.options.Zscale) - return null; - } + const mp = this.getMainPainter(); + if (mp && (mp !== this) && (mp.draw_content !== false) && mp.options.Zscale) + return null; + const pp = this.getPadPainter(); let pal = this.findFunction(clTPaletteAxis), pal_painter = pp?.findPainterFor(pal); - const found_in_func = !!pal; - - if (this._can_move_colz) { - delete this._can_move_colz; - if (!can_move) can_move = true; - } + const found_in_func = Boolean(pal); - if (!pal_painter && !pal && !this.options.Axis) { + if (!pal_painter && !pal && !o.Axis) { pal_painter = pp?.findPainterFor(undefined, undefined, clTPaletteAxis); if (pal_painter) { pal = pal_painter.getObject(); @@ -2019,8 +2459,8 @@ class THistPainter extends ObjectPainter { } if (!enabled) { - if (pal_painter) { - this.options.Zvert = pal_painter._palette_vertical; + if (pal_painter && !o.Same) { + o.Zvert = pal_painter.isPaletteVertical(); pal_painter.Enabled = false; pal_painter.removeG(); // completely remove drawing without need to redraw complete pad } @@ -2031,44 +2471,47 @@ class THistPainter extends ObjectPainter { if (!pal) { pal = create(clTPaletteAxis); + if (!can_move) + can_move = !o.Same; + pal.fInit = 1; - pal.$can_move = true; + pal.$can_move = can_move; pal.$generated = true; - if (!this.options.Zvert) - Object.assign(pal, { fX1NDC: gStyle.fPadLeftMargin, fX2NDC: 1 - gStyle.fPadRightMargin, fY1NDC: 1.005 - gStyle.fPadTopMargin, fY2NDC: 1.045 - gStyle.fPadTopMargin }); - else + if (o.Zvert) Object.assign(pal, { fX1NDC: 1.005 - gStyle.fPadRightMargin, fX2NDC: 1.045 - gStyle.fPadRightMargin, fY1NDC: gStyle.fPadBottomMargin, fY2NDC: 1 - gStyle.fPadTopMargin }); + else + Object.assign(pal, { fX1NDC: gStyle.fPadLeftMargin, fX2NDC: 1 - gStyle.fPadRightMargin, fY1NDC: 1.005 - gStyle.fPadTopMargin, fY2NDC: 1.045 - gStyle.fPadTopMargin }); Object.assign(pal.fAxis, { fChopt: '+', fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11 }); if (this.getDimension() === 2) { const zaxis = this.getHisto().fZaxis; - Object.assign(pal.fAxis, { fTitle: zaxis.fTitle, fTitleSize: zaxis.fTitleSize, - fTitleOffset: zaxis.fTitleOffset, fTitleColor: zaxis.fTitleColor, - fLineColor: zaxis.fAxisColor, fTextSize: zaxis.fLabelSize, - fTextColor: zaxis.fLabelColor, fTextFont: zaxis.fLabelFont, - fLabelOffset: zaxis.fLabelOffset }); + Object.assign(pal.fAxis, { + fTitle: zaxis.fTitle, fTitleSize: zaxis.fTitleSize, + fTitleOffset: zaxis.fTitleOffset, fTitleColor: zaxis.fTitleColor, + fLineColor: zaxis.fAxisColor, fTextSize: zaxis.fLabelSize, + fTextColor: zaxis.fLabelColor, fTextFont: zaxis.fLabelFont, + fLabelOffset: zaxis.fLabelOffset + }); } // place colz in the beginning, that stat box is always drawn on the top this.addFunction(pal, true); - - can_move = true; - } else if (pp?._palette_vertical !== undefined) - this.options.Zvert = pp._palette_vertical; + } else if ((pal_painter?.isPaletteVertical() !== undefined) && !do_toggle) + o.Zvert = pal_painter.isPaletteVertical(); const fp = this.getFramePainter(); // keep palette width if (can_move && fp && pal.$can_move) { - if (this.options.Zvert) { - if (can_move === 'toggle') { + if (o.Zvert) { + if (do_toggle) { const d = pal.fY2NDC - pal.fY1NDC; pal.fX1NDC = fp.fX2NDC + 0.005; pal.fX2NDC = pal.fX1NDC + d; } - if (pal.fX1NDC > (fp.fX1NDC + fp.fX2NDC)*0.5) { + if (pal.fX1NDC > (fp.fX1NDC + fp.fX2NDC) * 0.5) { pal.fX2NDC = fp.fX2NDC + 0.005 + (pal.fX2NDC - pal.fX1NDC); pal.fX1NDC = fp.fX2NDC + 0.005; } else { @@ -2078,7 +2521,7 @@ class THistPainter extends ObjectPainter { pal.fY1NDC = fp.fY1NDC; pal.fY2NDC = fp.fY2NDC; } else { - if (can_move === 'toggle') { + if (do_toggle) { const d = pal.fX2NDC - pal.fX1NDC; pal.fY1NDC = fp.fY2NDC + 0.005; pal.fY2NDC = pal.fY1NDC + d; @@ -2086,7 +2529,7 @@ class THistPainter extends ObjectPainter { pal.fX1NDC = fp.fX1NDC; pal.fX2NDC = fp.fX2NDC; - if (pal.fY2NDC > (fp.fY1NDC + fp.fY2NDC)*0.5) { + if (pal.fY2NDC > (fp.fY1NDC + fp.fY2NDC) * 0.5) { pal.fY2NDC = fp.fY2NDC + 0.005 + (pal.fY2NDC - pal.fY1NDC); pal.fY1NDC = fp.fY2NDC + 0.005; } else { @@ -2100,29 +2543,31 @@ class THistPainter extends ObjectPainter { // TODO: use weak reference (via pad list of painters and any kind of string) pal.$main_painter = this; - let arg = '', pr; - if (postpone_draw) arg += ';postpone'; - if (can_move && !this.do_redraw_palette) arg += ';can_move'; - if (this.options.Cjust) arg += ';cjust'; + let arg = 'bring_stats_front', pr; + if (postpone_draw) + arg += ';postpone'; + if (can_move && !this.#doing_redraw_palette) + arg += ';can_move'; + if (o.Cjust) + arg += ';cjust'; if (!pal_painter) { // when histogram drawn on sub pad, let draw new axis object on the same pad - const prev = this.selectCurrentPad(this.getPadName()); - pr = TPavePainter.draw(this.getDom(), pal, arg).then(_palp => { + pr = TPavePainter.draw(pp, pal, arg).then(_palp => { pal_painter = _palp; - this.selectCurrentPad(prev); pal_painter.setSecondaryId(this, found_in_func && !pal.$generated ? `func_${pal.fName}` : undefined); }); } else { pal_painter.Enabled = true; // real drawing will be perform at the end - if (postpone_draw) return pal_painter; + if (postpone_draw) + return pal_painter; pr = pal_painter.drawPave(arg); } return pr.then(() => { // mark painter as secondary - not in list of TCanvas primitives - this.options.Zvert = pal_painter._palette_vertical; + o.Zvert = pal_painter.isPaletteVertical(); // make dummy redraw, palette will be updated only from histogram painter pal_painter.redraw = () => {}; @@ -2130,33 +2575,37 @@ class THistPainter extends ObjectPainter { let need_redraw = false; // special code to adjust frame position to actual position of palette - if (can_move && fp && !this.do_redraw_palette) { + if (can_move && fp && !this.#doing_redraw_palette) { const pad = pp?.getRootPad(true); - if (this.options.Zvert) { + if (o.Zvert) { if ((pal.fX1NDC > 0.5) && (fp.fX2NDC > pal.fX1NDC)) { need_redraw = true; fp.fX2NDC = pal.fX1NDC - 0.01; - if (fp.fX1NDC > fp.fX2NDC - 0.1) fp.fX1NDC = Math.max(0, fp.fX2NDC - 0.1); - } else if ((pal.fX2NDC < 0.5) && (fp.fX1NDC < pal.fX2NDC)) { + if (fp.fX1NDC > fp.fX2NDC - 0.1) + fp.fX1NDC = Math.max(0, fp.fX2NDC - 0.1); + } else if ((pal.fX2NDC < 0.5) && (fp.fX1NDC < pal.fX2NDC)) { need_redraw = true; fp.fX1NDC = pal.fX2NDC + 0.05; - if (fp.fX2NDC < fp.fX1NDC + 0.1) fp.fX2NDC = Math.min(1, fp.fX1NDC + 0.1); - } - if (need_redraw && pad) { - pad.fLeftMargin = fp.fX1NDC; - pad.fRightMargin = 1 - fp.fX2NDC; - } + if (fp.fX2NDC < fp.fX1NDC + 0.1) + fp.fX2NDC = Math.min(1, fp.fX1NDC + 0.1); + } + if (need_redraw && pad) { + pad.fLeftMargin = fp.fX1NDC; + pad.fRightMargin = 1 - fp.fX2NDC; + } } else { if ((pal.fY1NDC > 0.5) && (fp.fY2NDC > pal.fY1NDC)) { need_redraw = true; fp.fY2NDC = pal.fY1NDC - 0.01; - if (fp.fY1NDC > fp.fY2NDC - 0.1) fp.fY1NDC = Math.max(0, fp.fXYNDC - 0.1); + if (fp.fY1NDC > fp.fY2NDC - 0.1) + fp.fY1NDC = Math.max(0, fp.fXYNDC - 0.1); } else if ((pal.fY2NDC < 0.5) && (fp.fY1NDC < pal.fY2NDC)) { need_redraw = true; fp.fY1NDC = pal.fY2NDC + 0.05; - if (fp.fXYNDC < fp.fY1NDC + 0.1) fp.fY2NDC = Math.min(1, fp.fY1NDC + 0.1); + if (fp.fXYNDC < fp.fY1NDC + 0.1) + fp.fY2NDC = Math.min(1, fp.fY1NDC + 0.1); } if (need_redraw && pad) { pad.fTopMargin = fp.fY1NDC; @@ -2168,39 +2617,43 @@ class THistPainter extends ObjectPainter { if (!need_redraw) return pal_painter; - this.do_redraw_palette = true; + this.#doing_redraw_palette = true; fp.redraw(); - const pr = !postpone_draw ? this.redraw() : Promise.resolve(true); - return pr.then(() => { - delete this.do_redraw_palette; - return pal_painter; + const pr2 = !postpone_draw ? this.redraw() : Promise.resolve(true); + return pr2.then(() => { + this.#doing_redraw_palette = undefined; + return pal_painter; }); }); } /** @summary Toggle color z palette drawing */ toggleColz() { - if (this.options.canHavePalette()) { - this.options.Zscale = !this.options.Zscale; - return this.drawColorPalette(this.options.Zscale, false, true) + const o = this.getOptions(); + + if (o.canHavePalette()) { + o.Zscale = !o.Zscale; + return this.drawColorPalette(o.Zscale, false, true) .then(() => this.processOnlineChange('drawopt')); } } /** @summary Toggle 3D drawing mode */ toggleMode3D() { - this.options.Mode3D = !this.options.Mode3D; + const o = this.getOptions(); + + o.Mode3D = !o.Mode3D; - if (this.options.Mode3D) { - if (!this.options.Surf && !this.options.Lego && !this.options.Error) { + if (o.Mode3D) { + if (!o.Surf && !o.Lego && !o.Error) { if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) - this.options.Lego = this.options.Scat ? 13 : 14; + o.Lego = o.Scat ? 13 : 14; else - this.options.Lego = this.options.Scat ? 1 : 12; + o.Lego = o.Scat ? 1 : 12; - this.options.Zero = false; // do not show zeros by default + o.Zero = false; // do not show zeros by default } } @@ -2208,98 +2661,135 @@ class THistPainter extends ObjectPainter { return this.interactiveRedraw('pad', 'drawopt'); } + /** @summary Get graphics conversion functions for this histogram */ + getHistGrFuncs(rounding = true) { + let funcs; + if (this.isUseFrame()) { + const o = this.getOptions(); + funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); + if (funcs) + return funcs; + } + + funcs = this.getAxisToSvgFunc(false, rounding, false) || { x: v => v, y: v => v }; + + funcs.$painter = this; + funcs.grx = funcs.x; + funcs.gry = funcs.y; + funcs.logx = funcs.pad?.fLogx; + funcs.logy = funcs.pad?.fLogy; + funcs.swap_xy = function() { return this.fp?.swap_xy() ?? false; }; + funcs.getFrameWidth = function() { return this.$painter.getPadPainter().getPadWidth(); }; + funcs.getFrameHeight = function() { return this.$painter.getPadPainter().getPadHeight(); }; + funcs.isAxisZoomed = function() { return false; }; + funcs.revertAxis = function(name, v) { return this.$painter.svgToAxis(name, v); }; + funcs.axisAsText = function(_name, v) { return v.toString(); }; + return funcs; + } + /** @summary Prepare handle for color draw */ prepareDraw(args) { - if (!args) args = { rounding: true, extra: 0, middle: 0 }; + if (!args) + args = { rounding: true, extra: 0, middle: 0 }; - if (args.extra === undefined) args.extra = 0; - if (args.middle === undefined) args.middle = 0; + if (args.extra === undefined) + args.extra = 0; + if (args.middle === undefined) + args.middle = 0; + if (args.pixel_density) + args.rounding = true; const histo = this.getHisto(), - xaxis = histo.fXaxis, yaxis = histo.fYaxis, - pmain = this.getFramePainter(), + o = this.getOptions(), + xaxis = histo.fXaxis, + yaxis = histo.fYaxis, + funcs = this.getHistGrFuncs(args.rounding), hdim = this.getDimension(), res = { i1: args.nozoom ? 0 : this.getSelectIndex('x', 'left', 0 - args.extra), i2: args.nozoom ? this.nbinsx : this.getSelectIndex('x', 'right', 1 + args.extra), j1: (hdim === 1) ? 0 : (args.nozoom ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra)), j2: (hdim === 1) ? 1 : (args.nozoom ? this.nbinsy : this.getSelectIndex('y', 'right', 1 + args.extra)), - min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1 + min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1, + width: funcs?.getFrameWidth() ?? 600, + height: funcs?.getFrameHeight() ?? 400 }; + if (args.use3d && !funcs.size_x3d || !funcs.size_y3d) + args.use3d = false; + if (args.cutg) { - // if using cutg - define rectengular region + // if using cutg - define rectangular region let i1 = res.i2, i2 = res.i1, j1 = res.j2, j2 = res.j1; for (let ii = res.i1; ii < res.i2; ++ii) { for (let jj = res.j1; jj < res.j2; ++jj) { if (args.cutg.IsInside(xaxis.GetBinCoord(ii + args.middle), yaxis.GetBinCoord(jj + args.middle))) { i1 = Math.min(i1, ii); - i2 = Math.max(i2, ii+1); + i2 = Math.max(i2, ii + 1); j1 = Math.min(j1, jj); - j2 = Math.max(j2, jj+1); + j2 = Math.max(j2, jj + 1); } } } - res.i1 = i1; res.i2 = i2; res.j1 = j1; res.j2 = j2; + Object.assign(res, { i1, i2, j1, j2 }); } let i, j, x, y, binz, binarea; - res.grx = new Float32Array(res.i2+1); - res.gry = new Float32Array(res.j2+1); - - if ((typeof histo.fBarOffset === 'number') && (typeof histo.fBarWidth === 'number') && - (histo.fBarOffset || histo.fBarWidth !== 1000)) { - if (histo.fBarOffset <= 1000) - res.xbar1 = res.ybar1 = 0.001*histo.fBarOffset; - else if (histo.fBarOffset <= 3000) - res.xbar1 = 0.001*(histo.fBarOffset-2000); - else if (histo.fBarOffset <= 5000) - res.ybar1 = 0.001*(histo.fBarOffset-4000); - - if (histo.fBarWidth <= 1000) { - res.xbar2 = Math.min(1, res.xbar1 + 0.001*histo.fBarWidth); - res.ybar2 = Math.min(1, res.ybar1 + 0.001*histo.fBarWidth); - } else if (histo.fBarWidth <= 3000) - res.xbar2 = Math.min(1, res.xbar1 + 0.001*(histo.fBarWidth-2000)); - else if (histo.fBarWidth <= 5000) - res.ybar2 = Math.min(1, res.ybar1 + 0.001*(histo.fBarWidth-4000)); - } - - if (args.original) { - res.original = true; - res.origx = new Float32Array(res.i2+1); - res.origy = new Float32Array(res.j2+1); - } + res.grx = res.i1 < 0 ? {} : new Float32Array(res.i2 + 1); + res.gry = res.j1 < 0 ? {} : new Float32Array(res.j2 + 1); - if (args.pixel_density) args.rounding = true; + if ((typeof histo.fBarOffset === 'number') && (typeof histo.fBarWidth === 'number') && (histo.fBarOffset || (histo.fBarWidth !== 1000))) { + if (histo.fBarOffset <= 1000) + res.xbar1 = res.ybar1 = 0.001 * histo.fBarOffset; + else if (histo.fBarOffset <= 3000) + res.xbar1 = 0.001 * (histo.fBarOffset - 2000); + else if (histo.fBarOffset <= 5000) + res.ybar1 = 0.001 * (histo.fBarOffset - 4000); - if (!pmain) { - console.warn('cannot draw histogram without frame'); - return res; + if (histo.fBarWidth <= 1000) { + res.xbar2 = Math.min(1, res.xbar1 + 0.001 * histo.fBarWidth); + res.ybar2 = Math.min(1, res.ybar1 + 0.001 * histo.fBarWidth); + } else if (histo.fBarWidth <= 3000) + res.xbar2 = Math.min(1, res.xbar1 + 0.001 * (histo.fBarWidth - 2000)); + else if (histo.fBarWidth <= 5000) + res.ybar2 = Math.min(1, res.ybar1 + 0.001 * (histo.fBarWidth - 4000)); } - const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); + if (args.original) { + res.original = true; + res.origx = res.i1 < 0 ? {} : new Float32Array(res.i2 + 1); + res.origy = res.j1 < 0 ? {} : new Float32Array(res.j2 + 1); + } // calculate graphical coordinates in advance for (i = res.i1; i <= res.i2; ++i) { x = xaxis.GetBinCoord(i + args.middle); - if (funcs.logx && (x <= 0)) { res.i1 = i+1; continue; } - if (res.origx) res.origx[i] = x; + if (funcs.logx && (x <= 0)) { + res.i1 = i + 1; + continue; + } + if (res.origx) + res.origx[i] = x; res.grx[i] = funcs.grx(x); - if (args.rounding) res.grx[i] = Math.round(res.grx[i]); + if (args.rounding) + res.grx[i] = Math.round(res.grx[i]); if (args.use3d) { - if (res.grx[i] < -pmain.size_x3d) { - res.grx[i] = -pmain.size_x3d; - if (this.options.RevX) res.i2 = i; - else res.i1 = i; + if (res.grx[i] < -funcs.size_x3d) { + res.grx[i] = -funcs.size_x3d; + if (o.RevX) + res.i2 = i; + else + res.i1 = i; } - if (res.grx[i] > pmain.size_x3d) { - res.grx[i] = pmain.size_x3d; - if (this.options.RevX) res.i1 = i; - else res.i2 = i; + if (res.grx[i] > funcs.size_x3d) { + res.grx[i] = funcs.size_x3d; + if (o.RevX) + res.i1 = i; + else + res.i2 = i; } } } @@ -2310,83 +2800,135 @@ class THistPainter extends ObjectPainter { } else { for (j = res.j1; j <= res.j2; ++j) { y = yaxis.GetBinCoord(j + args.middle); - if (funcs.logy && (y <= 0)) { res.j1 = j+1; continue; } - if (res.origy) res.origy[j] = y; + if (funcs.logy && (y <= 0)) { + res.j1 = j + 1; + continue; + } + if (res.origy) + res.origy[j] = y; res.gry[j] = funcs.gry(y); - if (args.rounding) res.gry[j] = Math.round(res.gry[j]); + if (args.rounding) + res.gry[j] = Math.round(res.gry[j]); if (args.use3d) { - if (res.gry[j] < -pmain.size_y3d) { - res.gry[j] = -pmain.size_y3d; - if (this.options.RevY) res.j2 = j; - else res.j1 = j; + if (res.gry[j] < -funcs.size_y3d) { + res.gry[j] = -funcs.size_y3d; + if (o.RevY) + res.j2 = j; + else + res.j1 = j; } - if (res.gry[j] > pmain.size_y3d) { - res.gry[j] = pmain.size_y3d; - if (this.options.RevY) res.j1 = j; - else res.j2 = j; + if (res.gry[j] > funcs.size_y3d) { + res.gry[j] = funcs.size_y3d; + if (o.RevY) + res.j1 = j; + else + res.j2 = j; } } } } // find min/max values in selected range - - this.maxbin = this.minbin = this.minposbin = null; + let is_first = true; + this.minposbin = 0; for (i = res.i1; i < res.i2; ++i) { for (j = res.j1; j < res.j2; ++j) { binz = histo.getBinContent(i + 1, j + 1); res.sumz += binz; if (args.pixel_density) { - binarea = (res.grx[i+1]-res.grx[i])*(res.gry[j]-res.gry[j+1]); - if (binarea <= 0) continue; + binarea = (res.grx[i + 1] - res.grx[i]) * (res.gry[j] - res.gry[j + 1]); + if (binarea <= 0) + continue; res.max = Math.max(res.max, binz); - if ((binz > 0) && ((binz<res.min) || (res.min === 0))) res.min = binz; - binz = binz/binarea; + if ((binz > 0) && ((binz < res.min) || (res.min === 0))) + res.min = binz; + binz /= binarea; } - if (this.maxbin === null) + if (is_first) { this.maxbin = this.minbin = binz; - else { + is_first = false; + } else { this.maxbin = Math.max(this.maxbin, binz); this.minbin = Math.min(this.minbin, binz); } - if (binz > 0) - if ((this.minposbin === null) || (binz < this.minposbin)) this.minposbin = binz; + if ((binz > 0) && ((this.minposbin === 0) || (binz < this.minposbin))) + this.minposbin = binz; } } + if (is_first) + this.maxbin = this.minbin = 0; + // force recalculation of z levels - this.fContour = null; + this.#contour = undefined; + + if (args.zrange) + Object.assign(res, this.#getContourRanges(this.getMainPainter(), this.getFramePainter())); return res; } /** @summary Get tip text for axis bin */ getAxisBinTip(name, axis, bin) { - const pmain = this.getFramePainter(), - funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), + const funcs = this.getHistGrFuncs(), handle = funcs[`${name}_handle`], - x1 = axis.GetBinLowEdge(bin+1); + x1 = axis.GetBinLowEdge(bin + 1); if (handle.kind === kAxisLabels) return funcs.axisAsText(name, x1); - const x2 = axis.GetBinLowEdge(bin+2); + const x2 = axis.GetBinLowEdge(bin + 2); if ((handle.kind === kAxisTime) || this.isTF1()) - return funcs.axisAsText(name, (x1+x2)/2); + return funcs.axisAsText(name, (x1 + x2) / 2); return `[${funcs.axisAsText(name, x1)}, ${funcs.axisAsText(name, x2)})`; } + /** @summary Internal method to extract up/down errors for the bin + * @private */ + getBinErrors(histo, bin, content) { + const err = histo.getBinError(bin), + res = { low: err, up: err }, + kind = this.getOptions().Poisson || histo.fBinStatErrOpt; + + if (!kind || (histo.fSumw2.fN && histo.fTsumw !== histo.fTsumw2) || (content < 0)) + return res; + + const alpha = (kind === kPoisson2) ? 0.05 : 1 - 0.682689492, + n = Math.round(content); + + res.poisson = true; // indicate poisson error + res.low = (n === 0) ? 0 : content - gamma_quantile(alpha / 2, n, 1); + res.up = gamma_quantile_c(alpha / 2, n + 1, 1) - content; + return res; + } + + /** @summary Check assign as main painter + * @private */ + _checkAssign() { + const has_main = this.getPadPainter()?.getMainPainter(); + if (this.getOptions().Same) + this.#ignore_frame = !has_main; + else if (!has_main) + this.setAsMainPainter(); + } + + /** @summary Return true when drawn normally on the frame + * @private */ + isUseFrame() { return !this.#ignore_frame; } + /** @summary generic draw function for histograms * @private */ static async _drawHist(painter, opt) { - return ensureTCanvas(painter).then(() => { - painter.setAsMainPainter(); + const need_frame = !isStr(opt) || (opt.toLowerCase().indexOf('same') < 0); + return ensureTCanvas(painter, need_frame).then(() => { painter.decodeOptions(opt); + painter._checkAssign(); + if (painter.isTH2Poly()) { if (painter.options.Mode3D) painter.options.Lego = 12; // lego always 12 @@ -2408,10 +2950,10 @@ class THistPainter extends ObjectPainter { return painter.autoZoom(); }).then(() => { if (painter.options.Project && !painter.mode3d && isFunc(painter.toggleProjection)) - return painter.toggleProjection(painter.options.Project); + return painter.toggleProjection(painter.options.Project); }).then(() => { - painter.fillToolbar(); - return painter; + painter.fillToolbar(); + return painter; }); } diff --git a/modules/hist2d/TMultiGraphPainter.mjs b/modules/hist2d/TMultiGraphPainter.mjs index 18004007c..9192b6497 100644 --- a/modules/hist2d/TMultiGraphPainter.mjs +++ b/modules/hist2d/TMultiGraphPainter.mjs @@ -1,11 +1,14 @@ -import { create, createHistogram, clTH1I, clTH2I, clTObjString, clTHashList, kNoZoom, kNoStats } from '../core.mjs'; +import { create, createHistogram, clTH1F, clTH2F, clTObjString, clTHashList, kNoZoom, kNoStats, BIT } from '../core.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { FunctionsHandler } from './THistPainter.mjs'; import { TH1Painter, PadDrawOptions } from './TH1Painter.mjs'; import { TGraphPainter } from './TGraphPainter.mjs'; +import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; +const kResetHisto = BIT(17); + /** * @summary Painter for TMultiGraph object. * @@ -14,25 +17,40 @@ import { TGraphPainter } from './TGraphPainter.mjs'; class TMultiGraphPainter extends ObjectPainter { + #firstpainter; // first painter + #painters; // array of sub-painters + #funcs_handler; // special instance for functions drawing + #restopt; // remaining part of draw options + #auto; // extra options for auto colors + #is3d; // if 3d drawing + #pads; // pads draw option + /** @summary Create painter * @param {object|string} dom - DOM element for drawing or element id * @param {object} obj - TMultiGraph object to draw */ constructor(dom, mgraph) { super(dom, mgraph); - this.firstpainter = null; - this.autorange = false; - this.painters = []; // keep painters to be able update objects + this.#firstpainter = null; + this.#painters = []; // keep painters to be able update objects } - /** @summary Cleanup multigraph painter */ + /** @summary Cleanup TMultiGraph painter */ cleanup() { - this.painters = []; + this.#painters = []; + this.#is3d = undefined; + this.#pads = undefined; + this.#auto = undefined; + this.#restopt = undefined; super.cleanup(); } - /** @summary Update multigraph object */ + /** @summary Return true if 3D drawing is used */ + is3d() { return this.#is3d; } + + /** @summary Update TMultiGraph object */ updateObject(obj) { - if (!this.matchObjectType(obj)) return false; + if (!this.matchObjectType(obj)) + return false; const mgraph = this.getObject(), graphs = obj.fGraphs, @@ -41,78 +59,74 @@ class TMultiGraphPainter extends ObjectPainter { mgraph.fTitle = obj.fTitle; let isany = false; - if (this.firstpainter) { - let histo = obj.fHistogram; - if (this.autorange && !histo) - histo = this.scanGraphsRange(graphs); - - if (this.firstpainter.updateObject(histo)) + if (this.#firstpainter) { + const histo = this.scanGraphsRange(graphs, obj.fHistogram, pp?.getRootPad(true), true); + if (this.#firstpainter.updateObject(histo)) isany = true; } - const ngr = Math.min(graphs.arr.length, this.painters.length); + const ngr = Math.min(graphs.arr.length, this.#painters.length); + // TODO: handle changing number of graphs for (let i = 0; i < ngr; ++i) { - if (this.painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this._restopt) + this._auto)) + if (this.#painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this.#restopt) + this.#auto)) isany = true; } - this._funcHandler = new FunctionsHandler(this, pp, obj.fFunctions); + this.#funcs_handler = new FunctionsHandler(this, pp, obj.fFunctions); return isany; } - /** @summary Redraw multigraph + /** @summary Redraw TMultiGraph * @desc may redraw histogram which was used to draw axes * @return {Promise} for ready */ - async redraw(reason) { - const promise = this.firstpainter?.redraw(reason) ?? Promise.resolve(true), - redrawNext = async indx => { - if (indx >= this.painters.length) - return this; - return this.painters[indx].redraw(reason).then(() => redrawNext(indx + 1)); - }; - - return promise.then(() => redrawNext(0)).then(() => { - const res = this._funcHandler?.drawNext(0) ?? this; - delete this._funcHandler; - return res; - }); - } + async redraw(reason) { + const promise = this.#firstpainter?.redraw(reason) ?? Promise.resolve(true), + redrawNext = async indx => { + if (indx >= this.#painters.length) + return this; + return this.#painters[indx].redraw(reason).then(() => redrawNext(indx + 1)); + }; + + return promise.then(() => redrawNext(0)).then(() => { + const res = this.#funcs_handler?.drawNext(0) ?? this; + this.#funcs_handler = undefined; + return res; + }); + } /** @summary Scan graphs range * @return {object} histogram for axes drawing */ - scanGraphsRange(graphs, histo, pad) { + scanGraphsRange(graphs, histo, pad, reset_histo) { const mgraph = this.getObject(), - rw = { xmin: 0, xmax: 0, ymin: 0, ymax: 0, first: true }; + rw = { xmin: 0, xmax: 0, ymin: 0, ymax: 0, first: true }, + test = (v1, v2) => { return Math.abs(v2 - v1) < 1e-6; }; let maximum, minimum, logx = false, logy = false, - time_display = false, time_format = ''; + src_hist, dummy_histo = false; if (pad) { logx = pad.fLogx; logy = pad.fLogv ?? pad.fLogy; - rw.xmin = pad.fUxmin; - rw.xmax = pad.fUxmax; - rw.ymin = pad.fUymin; - rw.ymax = pad.fUymax; - rw.first = false; } - // ignore existing histo in 3d case - if (this._3d && histo && !histo.fXaxis.fLabels) + // ignore existing histogram in 3d case + if (this.is3d() && histo && !histo.fXaxis.fLabels) histo = null; - if (!histo) { - this.autorange = true; - - if (graphs.arr[0]?.fHistogram?.fXaxis?.fTimeDisplay) { - time_display = true; - time_format = graphs.arr[0].fHistogram.fXaxis.fTimeFormat; - } + if (!histo) + src_hist = graphs.arr[0]?.fHistogram; + else { + dummy_histo = test(histo.fMinimum, -0.05) && test(histo.fMaximum, 1.05) && + test(histo.fXaxis.fXmin, -0.05) && test(histo.fXaxis.fXmax, 1.05); + src_hist = histo; } graphs.arr.forEach(gr => { - if (gr.fNpoints === 0) return; + if (gr.fNpoints === 0) + return; + if (gr.TestBit(kResetHisto)) + reset_histo = true; if (rw.first) { rw.xmin = rw.xmax = gr.fX[0]; rw.ymin = rw.ymax = gr.fY[0]; @@ -132,6 +146,7 @@ class TMultiGraphPainter extends ObjectPainter { rw.ymax += 1; const dx = 0.05 * (rw.xmax - rw.xmin), dy = 0.05 * (rw.ymax - rw.ymin); + let uxmin = rw.xmin - dx, uxmax = rw.xmax + dx; if (logy) { @@ -148,12 +163,12 @@ class TMultiGraphPainter extends ObjectPainter { if (maximum > 0 && rw.ymax <= 0) maximum = 0; - const glob_minimum = minimum, glob_maximum = maximum; + const glob_minimum = minimum, glob_maximum = maximum; if (uxmin < 0 && rw.xmin >= 0) uxmin = logx ? 0.9 * rw.xmin : 0; if (uxmax > 0 && rw.xmax <= 0) - uxmax = logx? 1.1 * rw.xmax : 0; + uxmax = logx ? 1.1 * rw.xmax : 0; if (mgraph.fMinimum !== kNoZoom) rw.ymin = minimum = mgraph.fMinimum; @@ -166,16 +181,16 @@ class TMultiGraphPainter extends ObjectPainter { maximum = 1.1 * rw.ymax; if (minimum <= 0 && logy) minimum = 0.001 * maximum; - if (!logy && minimum > 0 && minimum < 0.05*maximum) + if (!logy && minimum > 0 && minimum < 0.05 * maximum) minimum = 0; if (uxmin <= 0 && logx) uxmin = (uxmax > 1000) ? 1 : 0.001 * uxmax; // Create a temporary histogram to draw the axis (if necessary) - if (!histo) { + if (!histo || reset_histo || dummy_histo) { let xaxis, yaxis; - if (this._3d) { - histo = createHistogram(clTH2I, graphs.arr.length, 10); + if (this.is3d()) { + histo = createHistogram(clTH2F, graphs.arr.length, 10); xaxis = histo.fXaxis; xaxis.fXmin = 0; xaxis.fXmax = graphs.arr.length; @@ -189,47 +204,56 @@ class TMultiGraphPainter extends ObjectPainter { xaxis = histo.fYaxis; yaxis = histo.fZaxis; } else { - histo = createHistogram(clTH1I, 10); + histo = createHistogram(src_hist?._typename ?? clTH1F, src_hist?.fXaxis.fNbins ?? 10); xaxis = histo.fXaxis; yaxis = histo.fYaxis; } + + if (src_hist) { + Object.assign(xaxis, src_hist.fXaxis); + yaxis.fTitle = src_hist.fYaxis.fTitle; + } + histo.fTitle = mgraph.fTitle; if (histo.fTitle.indexOf(';') >= 0) { const t = histo.fTitle.split(';'); histo.fTitle = t[0]; - if (t[1]) xaxis.fTitle = t[1]; - if (t[2]) yaxis.fTitle = t[2]; + if (t[1]) + xaxis.fTitle = t[1]; + if (t[2]) + yaxis.fTitle = t[2]; + } + if (!xaxis.fLabels) { + xaxis.fXmin = uxmin; + xaxis.fXmax = uxmax; } - - xaxis.fXmin = uxmin; - xaxis.fXmax = uxmax; - xaxis.fTimeDisplay = time_display; - if (time_display) xaxis.fTimeFormat = time_format; } - const axis = this._3d ? histo.fZaxis : histo.fYaxis; + const axis = this.is3d() ? histo.fZaxis : histo.fYaxis; axis.fXmin = Math.min(minimum, glob_minimum); axis.fXmax = Math.max(maximum, glob_maximum); - histo.fMinimum = minimum; - histo.fMaximum = maximum; + if (histo.fMinimum === kNoZoom) + histo.fMinimum = minimum; + if (histo.fMaximum === kNoZoom) + histo.fMaximum = maximum; histo.fBits |= kNoStats; return histo; } - /** @summary draw speical histogram for axis + /** @summary draw special histogram for axis * @return {Promise} when ready */ async drawAxisHist(histo, hopt) { - return TH1Painter.draw(this.getDom(), histo, hopt); + return TH1Painter.draw(this.getDrawDom(), histo, hopt); } /** @summary Draw graph */ - async drawGraph(gr, opt /*, pos3d */) { - return TGraphPainter.draw(this.getDom(), gr, opt); + async drawGraph(dom, gr, opt /* , pos3d */) { + return TGraphPainter.draw(dom, gr, opt); } /** @summary method draws next graph */ - async drawNextGraph(indx) { + async drawNextGraph(indx, pad_painter) { const graphs = this.getObject().fGraphs; // at the end of graphs drawing draw functions (if any) @@ -237,60 +261,107 @@ class TMultiGraphPainter extends ObjectPainter { return this; const gr = graphs.arr[indx], - draw_opt = (graphs.opt[indx] || this._restopt) + this._auto; + draw_opt = (graphs.opt[indx] || this.#restopt) + this.#auto, + pos3d = graphs.arr.length - indx, + subid = `graphs_${indx}`; + + // handling of 'pads' draw option + if (pad_painter) { + const subpad_painter = pad_painter.getSubPadPainter(indx + 1); + if (!subpad_painter) + return this; + + subpad_painter.cleanPrimitives(true); + + return this.drawGraph(subpad_painter, gr, draw_opt, pos3d).then(subp => { + if (subp) { + subp.setSecondaryId(this, subid); + this.#painters.push(subp); + } + return this.drawNextGraph(indx + 1, pad_painter); + }); + } // used in automatic colors numbering - if (this._auto) + if (this.#auto) gr.$num_graphs = graphs.arr.length; - return this.drawGraph(gr, draw_opt, graphs.arr.length - indx).then(subp => { + return this.drawGraph(this.getPadPainter(), gr, draw_opt, pos3d).then(subp => { if (subp) { - subp.setSecondaryId(this, `graphs_${indx}`); - this.painters.push(subp); + subp.setSecondaryId(this, subid); + this.#painters.push(subp); } - - return this.drawNextGraph(indx+1); + return this.drawNextGraph(indx + 1); }); } - /** @summary Draw multigraph object using painter instance + /** @summary Fill TMultiGraph context menu */ + fillContextMenuItems(menu) { + menu.addRedrawMenu(this); + } + + /** @summary Redraw TMultiGraph object using provided option * @private */ - static async _drawMG(painter, opt) { - const d = new DrawOptions(opt); + async redrawWith(opt, skip_cleanup) { + if (!skip_cleanup) { + this.#firstpainter = null; + this.#painters = []; + const pp = this.getPadPainter(); + pp?.removePrimitive(this, true); + if (this.#pads) + pp?.divide(0, 0); + } + + const d = new DrawOptions(opt), + mgraph = this.getObject(); - painter._3d = d.check('3D'); - painter._auto = ''; // extra options for auto colors - ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) painter._auto += ' ' + f; }); + this.#is3d = d.check('3D'); + this.#auto = ''; + this.#pads = d.check('PADS'); + ['PFC', 'PLC', 'PMC'].forEach(f => { + if (d.check(f)) + this.#auto += ' ' + f; + }); - let hopt = ''; - if (d.check('FB') && painter._3d) hopt += 'FB'; // will be directly combined with LEGO - PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); + let hopt = '', pad_painter = null; + if (d.check('FB') && this.is3d()) + hopt += 'FB'; // will be directly combined with LEGO + PadDrawOptions.forEach(name => { + if (d.check(name)) + hopt += ';' + name; + }); - painter._restopt = d.remain(); + this.#restopt = d.remain(); let promise = Promise.resolve(true); - if (d.check('A') || !painter.getMainPainter()) { - const mgraph = painter.getObject(), - histo = painter.scanGraphsRange(mgraph.fGraphs, mgraph.fHistogram, painter.getPadPainter()?.getRootPad(true)); - - promise = painter.drawAxisHist(histo, hopt).then(ap => { - ap.setSecondaryId(painter, 'hist'); // mark that axis painter generated from mg - painter.firstpainter = ap; + if (this.#pads) { + promise = ensureTCanvas(this, false).then(() => { + pad_painter = this.getPadPainter(); + return pad_painter.divide(mgraph.fGraphs.arr.length, 0, true); + }); + } else if (d.check('A') || !this.getMainPainter()) { + const histo = this.scanGraphsRange(mgraph.fGraphs, mgraph.fHistogram, this.getPadPainter()?.getRootPad(true)); + promise = this.drawAxisHist(histo, hopt).then(ap => { + ap.setSecondaryId(this, 'hist'); // mark that axis painter generated from mg + this.#firstpainter = ap; }); } return promise.then(() => { - painter.addToPadPrimitives(); - return painter.drawNextGraph(0); + this.addToPadPrimitives(); + return this.drawNextGraph(0, pad_painter); }).then(() => { - const handler = new FunctionsHandler(painter, painter.getPadPainter(), painter.getObject().fFunctions, true); + if (this.#pads) + return this; + const handler = new FunctionsHandler(this, this.getPadPainter(), this.getObject().fFunctions, true); return handler.drawNext(0); // returns painter }); } - /** @summary Draw TMultiGraph object */ + /** @summary Draw TMultiGraph object in 2D only */ static async draw(dom, mgraph, opt) { - return TMultiGraphPainter._drawMG(new TMultiGraphPainter(dom, mgraph), opt); + const painter = new TMultiGraphPainter(dom, mgraph, opt); + return painter.redrawWith(opt, true); } } // class TMultiGraphPainter diff --git a/modules/hist2d/TScatterPainter.mjs b/modules/hist2d/TScatterPainter.mjs index 156194237..2b3725ff7 100644 --- a/modules/hist2d/TScatterPainter.mjs +++ b/modules/hist2d/TScatterPainter.mjs @@ -1,44 +1,59 @@ -import { clTPaletteAxis, isFunc, create } from '../core.mjs'; +import { clTPaletteAxis, isFunc, create, kNoZoom } from '../core.mjs'; import { getColorPalette } from '../base/colors.mjs'; import { TAttMarkerHandler } from '../base/TAttMarkerHandler.mjs'; import { TGraphPainter } from './TGraphPainter.mjs'; import { HistContour } from './THistPainter.mjs'; import { TH2Painter } from './TH2Painter.mjs'; +/** + * @summary Painter for TScatter object. + * + * @private + */ class TScatterPainter extends TGraphPainter { - constructor(dom, obj) { - super(dom, obj); - this._need_2dhist = true; - this._not_adjust_hrange = true; + #color_palette; // color palette + #contour; // colors contour + + /** @summary Cleanup painter */ + cleanup() { + this.clearHistPalette(); + this.#contour = undefined; + super.cleanup(); } /** @summary Return drawn graph object */ getGraph() { return this.getObject()?.fGraph; } + /** @summary Return colors contour */ + getContour() { return this.#contour; } + + /** @summary Is TScatter object */ + isScatter() { return true; } + /** @summary Return margins for histogram ranges */ getHistRangeMargin() { return this.getObject()?.fMargin ?? 0.1; } - /** @summary Draw axis histogram - * @private */ + /** @summary Draw axis histogram + * @private */ async drawAxisHisto() { - const histo = this.createHistogram(); - return TH2Painter.draw(this.getDom(), histo, this.options.Axis); + const set_x = this.isDummyHistogram('x'), + set_y = this.isDummyHistogram('y'), + histo = this.createHistogram(set_x, set_y); + return TH2Painter.draw(this.getDrawDom(), histo, this.getOptions().Axis + ';IGNORE_PALETTE'); } - /** @summary Provide palette, create if necessary - * @private */ + /** @summary Provide palette, create if necessary + * @private */ getPalette() { const gr = this.getGraph(); let pal = gr?.fFunctions?.arr?.find(func => (func._typename === clTPaletteAxis)); - if (pal) return pal; - - if (gr) { + if (!pal && gr) { pal = create(clTPaletteAxis); - const fp = this.get_main(); + const fp = this.get_fp(); Object.assign(pal, { fX1NDC: fp.fX2NDC + 0.005, fX2NDC: fp.fX2NDC + 0.05, fY1NDC: fp.fY1NDC, fY2NDC: fp.fY2NDC, fInit: 1, $can_move: true }); Object.assign(pal.fAxis, { fChopt: '+', fLineColor: 1, fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11, fNdiv: 510 }); gr.fFunctions.AddFirst(pal, ''); @@ -47,7 +62,8 @@ class TScatterPainter extends TGraphPainter { return pal; } - /** @summary Update TScatter members */ + /** @summary Update TScatter members + * @private */ _updateMembers(scatter, obj) { scatter.fBits = obj.fBits; scatter.fTitle = obj.fTitle; @@ -57,78 +73,131 @@ class TScatterPainter extends TGraphPainter { scatter.fMargin = obj.fMargin; scatter.fMinMarkerSize = obj.fMinMarkerSize; scatter.fMaxMarkerSize = obj.fMaxMarkerSize; - super._updateMembers(scatter.fGraph, obj.fGraph); + return super._updateMembers(scatter.fGraph, obj.fGraph); + } + + /** @summary Return Z axis used for palette drawing + * @private */ + getZaxis() { + return this.getHistogram()?.fZaxis; + } + + /** @summary Checks if it makes sense to zoom inside specified axis range */ + canZoomInside(axis, min, max) { + if (axis !== 'z') + return super.canZoomInside(axis, min, max); + + const levels = this.#contour?.getLevels(); + if (!levels) + return false; + // match at least full color level inside + for (let i = 0; i < levels.length - 1; ++i) { + if ((min <= levels[i]) && (max >= levels[i + 1])) + return true; + } + return false; + } + + /** @summary Returns color palette associated with histogram + * @desc Create if required, checks pad and canvas for custom palette */ + getHistPalette(force) { + let pal = force ? null : this.#color_palette; + if (pal) + return pal; + const pp = this.getPadPainter(); + if (isFunc(pp?.getCustomPalette)) + pal = pp.getCustomPalette(); + if (!pal) + pal = getColorPalette(this.getOptions().Palette, pp?.isGrayscale()); + this.#color_palette = pal; + return pal; + } + + /** @summary Remove palette */ + clearHistPalette() { + this.#color_palette = undefined; } /** @summary Actual drawing of TScatter */ async drawGraph() { - const fpainter = this.get_main(), - hpainter = this.getMainPainter(), - scatter = this.getObject(); - let scale = 1, offset = 0; - if (!fpainter || !hpainter || !scatter) return; + const fp = this.get_fp(), + hpainter = this.getMainPainter(), + scatter = this.getObject(), + hist = this.getHistogram(); + + let scale = 1, offset = 0, palette; + if (!fp || !hpainter || !scatter) + return; if (scatter.fColor) { const pal = this.getPalette(); if (pal) pal.$main_painter = this; - const pp = this.getPadPainter(); - if (!this._color_palette && isFunc(pp?.getCustomPalette)) - this._color_palette = pp.getCustomPalette(); - if (!this._color_palette) - this._color_palette = getColorPalette(this.options.Palette, pp?.isGrayscale()); + palette = this.getHistPalette(); let minc = scatter.fColor[0], maxc = scatter.fColor[0]; for (let i = 1; i < scatter.fColor.length; ++i) { - minc = Math.min(minc, scatter.fColor[i]); - maxc = Math.max(maxc, scatter.fColor[i]); + minc = Math.min(minc, scatter.fColor[i]); + maxc = Math.max(maxc, scatter.fColor[i]); } if (maxc <= minc) - maxc = minc < 0 ? 0.9*minc : (minc > 0 ? 1.1*minc : 1); - this.fContour = new HistContour(minc, maxc); - this.fContour.createNormal(30); - this.fContour.configIndicies(0, 0); - - fpainter.zmin = minc; - fpainter.zmax = maxc; + maxc = minc < 0 ? 0.9 * minc : (minc > 0 ? 1.1 * minc : 1); + else if ((minc > 0) && (minc < 0.3 * maxc)) + minc = 0; + this.#contour = new HistContour(minc, maxc); + this.#contour.createNormal(30); + this.#contour.configIndicies(0, 0); + + fp.zmin = minc; + fp.zmax = maxc; + + if (!fp.zoomChangedInteractive('z') && hist && hist.fMinimum !== kNoZoom && hist.fMaximum !== kNoZoom) { + fp.zoom_zmin = hist.fMinimum; + fp.zoom_zmax = hist.fMaximum; + } } if (scatter.fSize) { let mins = scatter.fSize[0], maxs = scatter.fSize[0]; for (let i = 1; i < scatter.fSize.length; ++i) { - mins = Math.min(mins, scatter.fSize[i]); - maxs = Math.max(maxs, scatter.fSize[i]); + mins = Math.min(mins, scatter.fSize[i]); + maxs = Math.max(maxs, scatter.fSize[i]); } if (maxs <= mins) - maxs = mins < 0 ? 0.9*mins : (mins > 0 ? 1.1*mins : 1); + maxs = mins < 0 ? 0.9 * mins : (mins > 0 ? 1.1 * mins : 1); scale = (scatter.fMaxMarkerSize - scatter.fMinMarkerSize) / (maxs - mins); offset = mins; } - this.createG(!fpainter.pad_layer); + const g = this.createG(!fp.pad_layer), + funcs = fp.getGrFuncs(), + is_zoom = (fp.zoom_zmin !== fp.zoom_zmax) && scatter.fColor, + bins = this._getBins(); - const funcs = fpainter.getGrFuncs(); + for (let i = 0; i < bins.length; ++i) { + if (is_zoom && ((scatter.fColor[i] < fp.zoom_zmin) || (scatter.fColor[i] > fp.zoom_zmax))) + continue; - for (let i = 0; i < this.bins.length; ++i) { - const pnt = this.bins[i], + const pnt = bins[i], grx = funcs.grx(pnt.x), gry = funcs.gry(pnt.y), size = scatter.fSize ? scatter.fMinMarkerSize + scale * (scatter.fSize[i] - offset) : scatter.fMarkerSize, - color = scatter.fColor ? this.fContour.getPaletteColor(this._color_palette, scatter.fColor[i]) : this.getColor(scatter.fMarkerColor), + color = scatter.fColor ? this.#contour.getPaletteColor(palette, scatter.fColor[i]) : this.getColor(scatter.fMarkerColor), handle = new TAttMarkerHandler({ color, size, style: scatter.fMarkerStyle }); - this.draw_g.append('svg:path') - .attr('d', handle.create(grx, gry)) - .call(handle.func); + g.append('svg:path') + .attr('d', handle.create(grx, gry)) + .call(handle.func); } return this; } + /** @summary Draw TScatter object */ static async draw(dom, obj, opt) { return TGraphPainter._drawGraph(new TScatterPainter(dom, obj), opt); } diff --git a/modules/hist2d/bundle.mjs b/modules/hist2d/bundle.mjs index dde8810cd..8cf35231e 100644 --- a/modules/hist2d/bundle.mjs +++ b/modules/hist2d/bundle.mjs @@ -4,3 +4,4 @@ export { version, parse } from '../core.mjs'; export { cleanup } from '../base/ObjectPainter.mjs'; export { TH1Painter } from './TH1Painter.mjs'; export { TH2Painter } from './TH2Painter.mjs'; +export { THStackPainter } from './THStackPainter.mjs'; diff --git a/modules/io.mjs b/modules/io.mjs index 5c1d20300..4d4c7b38b 100644 --- a/modules/io.mjs +++ b/modules/io.mjs @@ -1,8 +1,8 @@ import { createHttpRequest, BIT, internals, settings, browser, create, getMethods, addMethods, isNodeJs, isObject, isFunc, isStr, - clTObject, clTNamed, clTString, clTObjString, clTKey, clTFile, clTList, clTMap, clTObjArray, clTClonesArray, - clTAttLine, clTAttFill, clTAttMarker, clTStyle, clTImagePalette, - clTPad, clTCanvas, clTAttCanvas, clTPolyMarker3D, clTF1, clTF2 } from './core.mjs'; + clTObject, clTNamed, clTString, clTObjString, clTKey, clTFile, clTTree, clTList, clTMap, clTObjArray, clTClonesArray, + clTAttLine, clTAttFill, clTAttMarker, clTStyle, clTImagePalette, atob_func, + clTPad, clTCanvas, clTAttCanvas, clTPolyMarker3D, clTF1, clTF12, clTF2, clTF3 } from './core.mjs'; const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObject', clTStreamerSTL = 'TStreamerSTL', clTStreamerInfoList = 'TStreamerInfoList', @@ -22,7 +22,6 @@ const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObj /* kAnyPnoVT: 70, */ kSTLp = 71, /* kSkip = 100, kSkipL = 120, kSkipP = 140, kConv = 200, kConvL = 220, kConvP = 240, */ - kSTL = 300, /* kSTLstring = 365, */ kStreamer = 500, kStreamLoop = 501, @@ -37,7 +36,6 @@ const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObj kSTLset = 6, kSTLmultiset = 7, kSTLbitset = 8, // kSTLforwardlist = 9, kSTLunorderedset = 10, kSTLunorderedmultiset = 11, kSTLunorderedmap = 12, // kSTLunorderedmultimap = 13, kSTLend = 14 - kBaseClass = 'BASE', // name of base IO types @@ -50,45 +48,59 @@ const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObj // TObject bits kIsReferenced = BIT(4), kHasUUID = BIT(5), + // gap in http which can be merged into single http request + kMinimalHttpGap = 128; + /** @summary Custom streamers for root classes * @desc map of user-streamer function like func(buf,obj) * or alias (classname) which can be used to read that function * or list of read functions * @private */ -CustomStreamers = { +/* eslint-disable one-var */ +const CustomStreamers = { TObject(buf, obj) { obj.fUniqueID = buf.ntou4(); obj.fBits = buf.ntou4(); - if (obj.fBits & kIsReferenced) buf.ntou2(); // skip pid + if (obj.fBits & kIsReferenced) + buf.ntou2(); // skip pid }, - TNamed: [{ - basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTNamed; - buf.classStreamer(obj, clTObject); - } - }, - { name: 'fName', func(buf, obj) { obj.fName = buf.readTString(); } }, - { name: 'fTitle', func(buf, obj) { obj.fTitle = buf.readTString(); } } + TNamed: [ + { + basename: clTObject, base: 1, + func(buf, obj) { + if (!obj._typename) + obj._typename = clTNamed; + buf.classStreamer(obj, clTObject); + } + }, + { name: 'fName', func(buf, obj) { obj.fName = buf.readTString(); } }, + { name: 'fTitle', func(buf, obj) { obj.fTitle = buf.readTString(); } } ], - TObjString: [{ - basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTObjString; - buf.classStreamer(obj, clTObject); - } - }, - { name: 'fString', func(buf, obj) { obj.fString = buf.readTString(); } } + TObjString: [ + { + basename: clTObject, base: 1, + func(buf, obj) { + if (!obj._typename) + obj._typename = clTObjString; + buf.classStreamer(obj, clTObject); + } + }, + { name: 'fString', func(buf, obj) { obj.fString = buf.readTString(); } } ], TClonesArray(buf, list) { - if (!list._typename) list._typename = clTClonesArray; + if (!list._typename) + list._typename = clTClonesArray; list.$kind = clTClonesArray; list.name = ''; const ver = buf.last_read_version; - if (ver > 2) buf.classStreamer(list, clTObject); - if (ver > 1) list.name = buf.readTString(); + if (ver > 2) + buf.classStreamer(list, clTObject); + if (ver > 1) + list.name = buf.readTString(); let classv = buf.readTString(), clv = 0; const pos = classv.lastIndexOf(';'); @@ -98,7 +110,8 @@ CustomStreamers = { } let nobjects = buf.ntou4(); - if (nobjects < 0) nobjects = -nobjects; // for backward compatibility + if (nobjects < 0) + nobjects = -nobjects; // for backward compatibility list.arr = new Array(nobjects); list.fLast = nobjects - 1; @@ -123,12 +136,15 @@ CustomStreamers = { }, TMap(buf, map) { - if (!map._typename) map._typename = clTMap; + if (!map._typename) + map._typename = clTMap; map.name = ''; map.arr = []; const ver = buf.last_read_version; - if (ver > 2) buf.classStreamer(map, clTObject); - if (ver > 1) map.name = buf.readTString(); + if (ver > 2) + buf.classStreamer(map, clTObject); + if (ver > 1) + map.name = buf.readTString(); const nobjects = buf.ntou4(); // create objects @@ -136,7 +152,8 @@ CustomStreamers = { const obj = { _typename: 'TPair' }; obj.first = buf.readObjectAny(); obj.second = buf.readObjectAny(); - if (obj.first) map.arr.push(obj); + if (obj.first) + map.arr.push(obj); } }, @@ -148,7 +165,8 @@ CustomStreamers = { obj.fMinorName = buf.readTString(); obj.fN = buf.ntoi8(); obj.fIndexValues = buf.readFastArray(obj.fN, kLong64); - if (ver > 1) obj.fIndexValuesMinor = buf.readFastArray(obj.fN, kLong64); + if (ver > 1) + obj.fIndexValuesMinor = buf.readFastArray(obj.fN, kLong64); obj.fIndex = buf.readFastArray(obj.fN, kLong64); }, @@ -168,7 +186,7 @@ CustomStreamers = { buf.classStreamer(obj, clTPad); obj.fDISPLAY = buf.readTString(); obj.fDoubleBuffer = buf.ntoi4(); - obj.fRetained = (buf.ntou1() !== 0); + obj.fRetained = buf.ntobool(); obj.fXsizeUser = buf.ntoi4(); obj.fYsizeUser = buf.ntoi4(); obj.fXsizeReal = buf.ntoi4(); @@ -183,14 +201,15 @@ CustomStreamers = { buf.ntou1(); // ignore b << TestBit(kMoveOpaque); buf.ntou1(); // ignore b << TestBit(kResizeOpaque); obj.fHighLightColor = buf.ntoi2(); - obj.fBatch = (buf.ntou1() !== 0); + obj.fBatch = buf.ntobool(); buf.ntou1(); // ignore b << TestBit(kShowEventStatus); buf.ntou1(); // ignore b << TestBit(kAutoExec); buf.ntou1(); // ignore b << TestBit(kMenuBar); }, TObjArray(buf, list) { - if (!list._typename) list._typename = clTObjArray; + if (!list._typename) + list._typename = clTObjArray; list.$kind = clTObjArray; list.name = ''; const ver = buf.last_read_version; @@ -257,20 +276,27 @@ CustomStreamers = { const p2 = element.fTitle.indexOf(']', p1 + 1); if ((p1 >= 0) && (p2 >= p1 + 2)) { - const arr = element.fTitle.slice(p1+1, p2).split(','); + const arr = element.fTitle.slice(p1 + 1, p2).split(','); let nbits = 32; if (!arr || arr.length < 2) throw new Error(`Problem to decode range setting from streamer element title ${element.fTitle}`); - if (arr.length === 3) nbits = parseInt(arr[2]); - if (!Number.isInteger(nbits) || (nbits < 2) || (nbits > 32)) nbits = 32; + if (arr.length === 3) + nbits = parseInt(arr[2]); + if (!Number.isInteger(nbits) || (nbits < 2) || (nbits > 32)) + nbits = 32; const parse_range = val => { - if (!val) return 0; - if (val.indexOf('pi') < 0) return parseFloat(val); + if (!val) + return 0; + if (val.indexOf('pi') < 0) + return parseFloat(val); val = val.trim(); let sign = 1; - if (val[0] === '-') { sign = -1; val = val.slice(1); } + if (val[0] === '-') { + sign = -1; + val = val.slice(1); + } switch (val) { case '2pi': case '2*pi': @@ -297,7 +323,8 @@ CustomStreamers = { TStreamerBase(buf, elem) { const ver = buf.last_read_version; buf.classStreamer(elem, clTStreamerElement); - if (ver > 2) elem.fBaseVersion = buf.ntou4(); + if (ver > 2) + elem.fBaseVersion = buf.ntou4(); }, TStreamerSTL(buf, elem) { @@ -306,12 +333,12 @@ CustomStreamers = { elem.fCtype = buf.ntou4(); if ((elem.fSTLtype === kSTLmultimap) && - ((elem.fTypeName.indexOf('std::set') === 0) || - (elem.fTypeName.indexOf('set') === 0))) elem.fSTLtype = kSTLset; + ((elem.fTypeName.indexOf('std::set') === 0) || (elem.fTypeName.indexOf('set') === 0))) + elem.fSTLtype = kSTLset; if ((elem.fSTLtype === kSTLset) && - ((elem.fTypeName.indexOf('std::multimap') === 0) || - (elem.fTypeName.indexOf('multimap') === 0))) elem.fSTLtype = kSTLmultimap; + ((elem.fTypeName.indexOf('std::multimap') === 0) || (elem.fTypeName.indexOf('multimap') === 0))) + elem.fSTLtype = kSTLmultimap; }, TStreamerSTLstring(buf, elem) { @@ -321,7 +348,8 @@ CustomStreamers = { TList(buf, obj) { // stream all objects in the list from the I/O buffer - if (!obj._typename) obj._typename = this.typename; + if (!obj._typename) + obj._typename = this.typename; obj.$kind = clTList; // all derived classes will be marked as well if (buf.last_read_version > 3) { buf.classStreamer(obj, clTObject); @@ -370,19 +398,71 @@ CustomStreamers = { TTree: { name: '$file', - func(buf, obj) { obj.$kind = 'TTree'; obj.$file = buf.fFile; } + func(buf, obj) { + obj.$kind = clTTree; + obj.$file = buf.fFile; + } + }, + + TBranch(buf, obj) { + const v = buf.last_read_version; + if (v > 9) + buf.streamClassMembers(obj, 'TBranch', v); + else { + buf.classStreamer(obj, clTNamed); + if (v > 7) + buf.classStreamer(obj, clTAttFill); + obj.fCompress = buf.ntoi4(); + obj.fBasketSize = buf.ntoi4(); + obj.fEntryOffsetLen = buf.ntoi4(); + obj.fWriteBasket = buf.ntoi4(); + obj.fEntryNumber = buf.ntoi4(); + obj.fOffset = buf.ntoi4(); + obj.fMaxBaskets = buf.ntoi4(); + if (v > 6) + obj.fSplitLevel = buf.ntoi4(); + obj.fEntries = buf.ntod(); + obj.fTotBytes = buf.ntod(); + obj.fZipBytes = buf.ntod(); + obj.fBranches = buf.classStreamer({}, clTObjArray); + obj.fLeaves = buf.classStreamer({}, clTObjArray); + obj.fBaskets = buf.classStreamer({}, clTObjArray); + buf.ntoi1(); // isArray + obj.fBasketBytes = buf.readFastArray(obj.fMaxBaskets, kInt); + buf.ntoi1(); // isArray + obj.fBasketEntry = buf.readFastArray(obj.fMaxBaskets, kInt); + const isArray = buf.ntoi1(); + obj.fBasketSeek = buf.readFastArray(obj.fMaxBaskets, isArray === 2 ? kLong64 : kInt); + obj.fFileName = buf.readTString(); + } + }, + + 'ROOT::RNTuple': { + name: '$file', + func(buf, obj) { + obj.$kind = 'ROOT::RNTuple'; + obj.$file = buf.fFile; + } }, RooRealVar(buf, obj) { const v = buf.last_read_version; buf.classStreamer(obj, 'RooAbsRealLValue'); - if (v === 1) { buf.ntod(); buf.ntod(); buf.ntoi4(); } // skip fitMin, fitMax, fitBins + if (v === 1) { + // skip fitMin, fitMax, fitBins + buf.ntod(); + buf.ntod(); + buf.ntoi4(); + } obj._error = buf.ntod(); obj._asymErrLo = buf.ntod(); obj._asymErrHi = buf.ntod(); - if (v >= 2) obj._binning = buf.readObjectAny(); - if (v === 3) obj._sharedProp = buf.readObjectAny(); - if (v >= 4) obj._sharedProp = buf.classStreamer({}, 'RooRealVarSharedProperties'); + if (v >= 2) + obj._binning = buf.readObjectAny(); + if (v === 3) + obj._sharedProp = buf.readObjectAny(); + if (v >= 4) + obj._sharedProp = buf.classStreamer({}, 'RooRealVarSharedProperties'); }, RooAbsBinning(buf, obj) { @@ -400,7 +480,8 @@ CustomStreamers = { const sz = (buf.last_read_version === 2) ? 3 : 2; for (let i = 0; i < sz; ++i) { let cnt = buf.ntoi4() * ((i === 0) ? 4 : 3); - while (cnt--) buf.readTString(); + while (cnt--) + buf.readTString(); } }, @@ -411,13 +492,15 @@ CustomStreamers = { obj.arr = create(clTList); while (size--) obj.arr.Add(buf.readObjectAny()); - if (v > 1) obj._name = buf.readTString(); + if (v > 1) + obj._name = buf.readTString(); }, TImagePalette: [ { basename: clTObject, base: 1, func(buf, obj) { - if (!obj._typename) obj._typename = clTImagePalette; + if (!obj._typename) + obj._typename = clTImagePalette; buf.classStreamer(obj, clTObject); } }, @@ -432,7 +515,7 @@ CustomStreamers = { TAttImage: [ { name: 'fImageQuality', func(buf, obj) { obj.fImageQuality = buf.ntoi4(); } }, { name: 'fImageCompression', func(buf, obj) { obj.fImageCompression = buf.ntou4(); } }, - { name: 'fConstRatio', func(buf, obj) { obj.fConstRatio = (buf.ntou1() !== 0); } }, + { name: 'fConstRatio', func(buf, obj) { obj.fConstRatio = buf.ntobool(); } }, { name: 'fPalette', func(buf, obj) { obj.fPalette = buf.classStreamer({}, clTImagePalette); } } ], @@ -442,7 +525,7 @@ CustomStreamers = { buf.classStreamer(obj, clTNamed); - if (buf.ntou1() !== 0) { + if (buf.ntobool()) { const size = buf.ntoi4(); obj.fPngBuf = buf.readFastArray(size, kUChar); } else { @@ -533,46 +616,6 @@ const DirectStreamers = { // if ((version % 1000) > 2) buf.shift(18); // skip fUUID }, - TBasket(buf, obj) { - buf.classStreamer(obj, clTKey); - const ver = buf.readVersion(); - obj.fBufferSize = buf.ntoi4(); - obj.fNevBufSize = buf.ntoi4(); - obj.fNevBuf = buf.ntoi4(); - obj.fLast = buf.ntoi4(); - if (obj.fLast > obj.fBufferSize) obj.fBufferSize = obj.fLast; - const flag = buf.ntoi1(); - - if (flag === 0) return; - - if ((flag % 10) !== 2) { - if (obj.fNevBuf) { - obj.fEntryOffset = buf.readFastArray(buf.ntoi4(), kInt); - if ((flag > 20) && (flag < 40)) { - for (let i = 0, kDisplacementMask = 0xFF000000; i < obj.fNevBuf; ++i) - obj.fEntryOffset[i] &= ~kDisplacementMask; - } - } - - if (flag > 40) - obj.fDisplacement = buf.readFastArray(buf.ntoi4(), kInt); - } - - if ((flag === 1) || (flag > 10)) { - // here is reading of raw data - const sz = (ver.val <= 1) ? buf.ntoi4() : obj.fLast; - - if (sz > obj.fKeylen) { - // buffer includes again complete TKey data - exclude it - const blob = buf.extract([buf.o + obj.fKeylen, sz - obj.fKeylen]); - obj.fBufferRef = new TBuffer(blob, 0, buf.fFile, sz - obj.fKeylen); - obj.fBufferRef.fTagOffset = obj.fKeylen; - } - - buf.shift(sz); - } - }, - TRef(buf, obj) { buf.classStreamer(obj, clTObject); if (obj.fBits & kHasUUID) @@ -602,7 +645,6 @@ const DirectStreamers = { } }; - /** @summary Returns type id by its name * @private */ function getTypeId(typname, norecursion) { @@ -650,12 +692,40 @@ function getTypeId(typname, norecursion) { if (!norecursion) { const replace = CustomStreamers[typname]; - if (isStr(replace)) return getTypeId(replace, true); + if (isStr(replace)) + return getTypeId(replace, true); } return -1; } +/** @summary Analyze and returns arrays kind + * @return 0 if TString (or equivalent), positive value - some basic type, -1 - any other kind + * @private */ +function getArrayKind(type_name) { + if ((type_name === clTString) || (type_name === 'string') || + (CustomStreamers[type_name] === clTString)) + return 0; + if ((type_name.length < 7) || type_name.indexOf('TArray')) + return -1; + if (type_name.length === 7) { + switch (type_name[6]) { + case 'I': return kInt; + case 'D': return kDouble; + case 'F': return kFloat; + case 'S': return kShort; + case 'C': return kChar; + case 'L': return kLong; + default: return -1; + } + } + + return type_name === 'TArrayL64' ? kLong64 : -1; +} + +// eslint-disable-next-line prefer-const +let createPairStreamer; + /** @summary create element of the streamer * @private */ function createStreamerElement(name, typename, file) { @@ -674,7 +744,8 @@ function createStreamerElement(name, typename, file) { typename = elem.fTypeName = BasicTypeNames[elem.fType] || 'int'; } - if (elem.fType > 0) return elem; // basic type + if (elem.fType > 0) + return elem; // basic type // check if there are STL containers const pos = typename.indexOf('<'); @@ -682,7 +753,8 @@ function createStreamerElement(name, typename, file) { if ((pos > 0) && (typename.indexOf('>') > pos + 2)) { for (let stl = 1; stl < StlNames.length; ++stl) { if (typename.slice(0, pos) === StlNames[stl]) { - stltype = stl; break; + stltype = stl; + break; } } } @@ -695,7 +767,10 @@ function createStreamerElement(name, typename, file) { return elem; } - const isptr = (typename.lastIndexOf('*') === typename.length - 1); + if ((pos > 0) && (typename.slice(0, pos) === 'pair') && file && isFunc(createPairStreamer)) + createPairStreamer(typename, file); + + const isptr = typename.at(-1) === '*'; if (isptr) elem.fTypeName = typename = typename.slice(0, typename.length - 1); @@ -710,40 +785,125 @@ function createStreamerElement(name, typename, file) { return elem; } - -/** @summary Function creates streamer for std::pair object +/** @summary Function to read vector element in the streamer * @private */ -function getPairStreamer(si, typname, file) { - if (!si) { - if (typname.indexOf('pair') !== 0) return null; - - si = file.findStreamerInfo(typname); - - if (!si) { - let p1 = typname.indexOf('<'); - const p2 = typname.lastIndexOf('>'); - function GetNextName() { - let res = '', p = p1 + 1, cnt = 0; - while ((p < p2) && (cnt >= 0)) { - switch (typname[p]) { - case '<': cnt++; break; - case ',': if (cnt === 0) cnt--; break; - case '>': cnt--; break; - } - if (cnt >= 0) res += typname[p]; - p++; +function readVectorElement(buf) { + if (this.member_wise) { + const n = buf.ntou4(), ver = this.stl_version; + + if (n === 0) + return []; // for empty vector no need to search split streamers + + if (n > 1000000) + throw new Error(`member-wise streaming of ${this.conttype} num ${n} member ${this.name}`); + + let streamer; + + if ((ver.val === this.member_ver) && (ver.checksum === this.member_checksum)) + streamer = this.member_streamer; + else { + streamer = buf.fFile.getStreamer(this.conttype, ver); + + this.member_streamer = streamer = buf.fFile.getSplittedStreamer(streamer); + this.member_ver = ver.val; + this.member_checksum = ver.checksum; + } + + const res = new Array(n); + let i, k, member; + + for (i = 0; i < n; ++i) + res[i] = { _typename: this.conttype }; // create objects + if (!streamer) + console.error(`Fail to create split streamer for ${this.conttype} need to read ${n} objects version ${ver}`); + else { + for (k = 0; k < streamer.length; ++k) { + member = streamer[k]; + if (member.split_func) + member.split_func(buf, res, n); + else { + for (i = 0; i < n; ++i) + member.func(buf, res[i]); } - p1 = p - 1; - return res.trim(); } - si = { _typename: 'TStreamerInfo', fVersion: 1, fName: typname, fElements: create(clTList) }; - si.fElements.Add(createStreamerElement('first', GetNextName(), file)); - si.fElements.Add(createStreamerElement('second', GetNextName(), file)); } + return res; } + const n = buf.ntou4(), res = new Array(n); + let i = 0; + + if (n > 200000) { + console.error(`vector streaming for ${this.conttype} at ${n}`); + return res; + } + + if (this.arrkind > 0) { + while (i < n) + res[i++] = buf.readFastArray(buf.ntou4(), this.arrkind); + } else if (this.arrkind === 0) { + while (i < n) + res[i++] = buf.readTString(); + } else if (this.isptr) { + while (i < n) + res[i++] = buf.readObjectAny(); + } else if (this.submember) { + while (i < n) + res[i++] = this.submember.readelem(buf); + } else { + while (i < n) + res[i++] = buf.classStreamer({}, this.conttype); + } + + return res; +} + +/** @summary Create streamer info for pair object + * @private */ +createPairStreamer = function(typename, file) { + let si = file.findStreamerInfo(typename); + if (si) + return si; + let p1 = typename.indexOf('<'); + const p2 = typename.lastIndexOf('>'); + function getNextName() { + let res = '', p = p1 + 1, cnt = 0; + while ((p < p2) && (cnt >= 0)) { + switch (typename[p]) { + case '<': + cnt++; + break; + case ',': + if (cnt === 0) + cnt--; + break; + case '>': + cnt--; + break; + } + if (cnt >= 0) + res += typename[p]; + p++; + } + p1 = p - 1; + return res.trim(); + } + si = { _typename: 'TStreamerInfo', fClassVersion: 0, fName: typename, fElements: create(clTList) }; + si.fElements.Add(createStreamerElement('first', getNextName(), file)); + si.fElements.Add(createStreamerElement('second', getNextName(), file)); + file.fStreamerInfos.arr.push(si); + return si; +}; + +/** @summary Function creates streamer for std::pair object + * @private */ +function getPairStreamer(si, typname, file) { + if (!si) + si = createPairStreamer(typname, file); + const streamer = file.getStreamer(typname, null, si); - if (!streamer) return null; + if (!streamer) + return null; if (streamer.length !== 2) { console.error(`Streamer for pair class contains ${streamer.length} elements`); @@ -762,6 +922,59 @@ function getPairStreamer(si, typname, file) { return streamer; } +/** @summary Function used in streamer to read std::map object + * @private */ +function readMapElement(buf) { + let streamer = this.streamer; + + if (this.member_wise) { + // when member-wise streaming is used, version is written + const si = buf.fFile.findStreamerInfo(this.pairtype, this.stl_version.val, this.stl_version.checksum); + + if (si && (this.si !== si)) { + streamer = getPairStreamer(si, this.pairtype, buf.fFile); + if (streamer?.length !== 2) { + console.log(`Fail to produce streamer for ${this.pairtype}`); + return null; + } + } + } + + const n = buf.ntoi4(), res = new Array(n); + + // no extra data written for empty map + if (n === 0) + return res; + + if (this.member_wise && (buf.remain() >= 6)) { + if (buf.ntoi2() === kStreamedMemberWise) + buf.shift(4); // skip checksum + else + buf.shift(-2); // rewind + } + + for (let i = 0; i < n; ++i) { + res[i] = { _typename: this.pairtype }; + streamer[0].func(buf, res[i]); + if (!this.member_wise) + streamer[1].func(buf, res[i]); + } + + // due-to member-wise streaming second element read after first is completed + if (this.member_wise) { + if (buf.remain() >= 6) { + if (buf.ntoi2() === kStreamedMemberWise) + buf.shift(4); // skip checksum + else + buf.shift(-2); // rewind + } + for (let i = 0; i < n; ++i) + streamer[1].func(buf, res[i]); + } + + return res; +} + /** @summary create member entry for streamer element * @desc used for reading of data @@ -794,30 +1007,40 @@ function createMemberStreamer(element, file) { member.func = function(buf, obj) { buf.classStreamer(obj, this.basename); }; break; case kShort: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi2(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntoi2(); }; + break; case kInt: case kCounter: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi4(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntoi4(); }; + break; case kLong: case kLong64: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi8(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntoi8(); }; + break; case kDouble: - member.func = function(buf, obj) { obj[this.name] = buf.ntod(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntod(); }; + break; case kFloat: - member.func = function(buf, obj) { obj[this.name] = buf.ntof(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntof(); }; + break; case kLegacyChar: case kUChar: - member.func = function(buf, obj) { obj[this.name] = buf.ntou1(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntou1(); }; + break; case kUShort: - member.func = function(buf, obj) { obj[this.name] = buf.ntou2(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntou2(); }; + break; case kBits: case kUInt: - member.func = function(buf, obj) { obj[this.name] = buf.ntou4(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntou4(); }; + break; case kULong64: case kULong: - member.func = function(buf, obj) { obj[this.name] = buf.ntou8(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntou8(); }; + break; case kBool: - member.func = function(buf, obj) { obj[this.name] = buf.ntou1() !== 0; }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntobool(); }; + break; case kOffsetL + kBool: case kOffsetL + kInt: case kOffsetL + kCounter: @@ -841,8 +1064,8 @@ function createMemberStreamer(element, file) { member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; member.minus1 = true; member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => - buf.readFastArray(handle.arrlength, handle.type - kOffsetL)); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => + buf2.readFastArray(handle.arrlength, handle.type - kOffsetL)); }; } break; @@ -856,8 +1079,8 @@ function createMemberStreamer(element, file) { member.minus1 = true; // one dimension used for char* member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => - buf.readFastString(handle.arrlength)); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => + buf2.readFastString(handle.arrlength)); }; } break; @@ -889,11 +1112,11 @@ function createMemberStreamer(element, file) { case kOffsetL + kDouble32: case kOffsetP + kDouble32: member.double32 = true; - // eslint-disable-next-line no-fallthrough + // eslint-disable-next-line no-fallthrough case kFloat16: case kOffsetL + kFloat16: case kOffsetP + kFloat16: - if (element.fFactor !== 0) { + if (element.fFactor) { member.factor = 1 / element.fFactor; member.min = element.fXmin; member.read = function(buf) { return buf.ntou4() * this.factor + this.min; }; @@ -902,7 +1125,8 @@ function createMemberStreamer(element, file) { member.read = function(buf) { return buf.ntof(); }; else { member.nbits = Math.round(element.fXmin); - if (member.nbits === 0) member.nbits = 12; + if (member.nbits === 0) + member.nbits = 12; member.dv = new DataView(new ArrayBuffer(8), 0); // used to cast from uint32 to float32 member.read = function(buf) { const theExp = buf.ntou1(), theMan = buf.ntou2(); @@ -913,7 +1137,8 @@ function createMemberStreamer(element, file) { member.readarr = function(buf, len) { const arr = this.double32 ? new Float64Array(len) : new Float32Array(len); - for (let n = 0; n < len; ++n) arr[n] = this.read(buf); + for (let n = 0; n < len; ++n) + arr[n] = this.read(buf); return arr; }; @@ -933,7 +1158,7 @@ function createMemberStreamer(element, file) { member.arrlength = element.fMaxIndex[element.fArrayDim - 1]; member.minus1 = true; member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => handle.readarr(buf, handle.arrlength)); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => handle.readarr(buf2, handle.arrlength)); }; } break; @@ -941,7 +1166,7 @@ function createMemberStreamer(element, file) { case kAnyP: case kObjectP: member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, buf => buf.readObjectAny()); + obj[this.name] = buf.readNdimArray(this, buf2 => buf2.readObjectAny()); }; break; @@ -950,7 +1175,7 @@ function createMemberStreamer(element, file) { case kObjectp: case kObject: { let classname = (element.fTypeName === kBaseClass) ? element.fName : element.fTypeName; - if (classname[classname.length - 1] === '*') + if (classname.at(-1) === '*') classname = classname.slice(0, classname.length - 1); const arrkind = getArrayKind(classname); @@ -965,7 +1190,7 @@ function createMemberStreamer(element, file) { if (element.fArrayLength > 1) { member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => buf.classStreamer({}, handle.classname)); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => buf2.classStreamer({}, handle.classname)); }; } else { member.func = function(buf, obj) { @@ -980,22 +1205,26 @@ function createMemberStreamer(element, file) { case kOffsetL + kAnyp: case kOffsetL + kObjectp: { let classname = element.fTypeName; - if (classname[classname.length - 1] === '*') + if (classname.at(-1) === '*') classname = classname.slice(0, classname.length - 1); member.arrkind = getArrayKind(classname); - if (member.arrkind < 0) member.classname = classname; + if (member.arrkind < 0) + member.classname = classname; member.func = function(buf, obj) { - obj[this.name] = buf.readNdimArray(this, (buf, handle) => { - if (handle.arrkind > 0) return buf.readFastArray(buf.ntou4(), handle.arrkind); - if (handle.arrkind === 0) return buf.readTString(); - return buf.classStreamer({}, handle.classname); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => { + if (handle.arrkind > 0) + return buf2.readFastArray(buf.ntou4(), handle.arrkind); + if (handle.arrkind === 0) + return buf2.readTString(); + return buf2.classStreamer({}, handle.classname); }); }; break; } case kChar: - member.func = function(buf, obj) { obj[this.name] = buf.ntoi1(); }; break; + member.func = function(buf, obj) { obj[this.name] = buf.ntoi1(); }; + break; case kCharStar: member.func = function(buf, obj) { const len = buf.ntoi4(); @@ -1017,9 +1246,10 @@ function createMemberStreamer(element, file) { member.typename = element.fTypeName; member.func = function(buf, obj) { const ver = buf.readVersion(); - obj[this.name] = buf.readNdimArray(this, (buf, handle) => { - if (handle.typename === clTString) return buf.readTString(); - return buf.classStreamer({}, handle.typename); + obj[this.name] = buf.readNdimArray(this, (buf2, handle) => { + if (handle.typename === clTString) + return buf2.readTString(); + return buf2.classStreamer({}, handle.typename); }); buf.checkByteCount(ver, this.typename + '[]'); }; @@ -1117,7 +1347,7 @@ function createMemberStreamer(element, file) { member.typeid = getTypeId(member.conttype); if ((member.typeid < 0) && file.fBasicTypes[member.conttype]) { member.typeid = file.fBasicTypes[member.conttype]; - console.log(`!!! Reuse basic type ${member.conttype} from file streamer infos`); + console.log(`Reuse basic type ${member.conttype} from file streamer infos`); } // check @@ -1133,19 +1363,20 @@ function createMemberStreamer(element, file) { } else { member.isptr = false; - if (member.conttype.lastIndexOf('*') === member.conttype.length - 1) { + if (member.conttype.at(-1) === '*') { member.isptr = true; member.conttype = member.conttype.slice(0, member.conttype.length - 1); } - if (element.fCtype === kObjectp) member.isptr = true; + if (element.fCtype === kObjectp) + member.isptr = true; member.arrkind = getArrayKind(member.conttype); member.readelem = readVectorElement; if (!member.isptr && (member.arrkind < 0)) { - const subelem = createStreamerElement('temp', member.conttype); + const subelem = createStreamerElement('temp', member.conttype, file); if (subelem.fType === kStreamer) { subelem.$fictional = true; member.submember = createMemberStreamer(subelem, file); @@ -1169,7 +1400,8 @@ function createMemberStreamer(element, file) { delete member.streamer; } - if (member.streamer) member.readelem = readMapElement; + if (member.streamer) + member.readelem = readMapElement; } else if (stl === kSTLbitset) member.readelem = (buf /* , obj */) => buf.readFastArray(buf.ntou4(), kBool); @@ -1180,67 +1412,65 @@ function createMemberStreamer(element, file) { buf.checkByteCount(ver); obj[this.name] = null; }; - } else - if (!element.$fictional) { - member.read_version = function(buf, cnt) { - if (cnt === 0) return null; - const ver = buf.readVersion(); - this.member_wise = ((ver.val & kStreamedMemberWise) !== 0); - - this.stl_version = undefined; - if (this.member_wise) { - ver.val = ver.val & ~kStreamedMemberWise; - this.stl_version = { val: buf.ntoi2() }; - if (this.stl_version.val <= 0) this.stl_version.checksum = buf.ntou4(); - } - return ver; - }; - - member.func = function(buf, obj) { - const ver = this.read_version(buf); - - let res = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - - if (!buf.checkByteCount(ver, this.typename)) res = null; - obj[this.name] = res; - }; - - member.branch_func = function(buf, obj) { - // special function to read data from STL branch - const cnt = obj[this.stl_size], - ver = this.read_version(buf, cnt), - arr = new Array(cnt); + } else if (!element.$fictional) { + member.read_version = function(buf, cnt) { + if (cnt === 0) + return null; + const ver = buf.readVersion(); + this.member_wise = Boolean(ver.val & kStreamedMemberWise); + + this.stl_version = undefined; + if (this.member_wise) { + ver.val &= ~kStreamedMemberWise; + this.stl_version = { val: buf.ntoi2() }; + if (this.stl_version.val <= 0) + this.stl_version.checksum = buf.ntou4(); + } + return ver; + }; + member.func = function(buf, obj) { + const ver = this.read_version(buf), + res = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + obj[this.name] = buf.checkByteCount(ver, this.typename) ? res : null; + }; + member.branch_func = function(buf, obj) { + // special function to read data from STL branch + const cnt = obj[this.stl_size], + ver = this.read_version(buf, cnt), + arr = new Array(cnt); - for (let n = 0; n < cnt; ++n) - arr[n] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + for (let n = 0; n < cnt; ++n) + arr[n] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - if (ver) buf.checkByteCount(ver, `branch ${this.typename}`); + if (ver) + buf.checkByteCount(ver, `branch ${this.typename}`); - obj[this.name] = arr; - }; - member.split_func = function(buf, arr, n) { - // function to read array from member-wise streaming - const ver = this.read_version(buf); - for (let i = 0; i < n; ++i) - arr[i][this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - buf.checkByteCount(ver, this.typename); - }; - member.objs_branch_func = function(buf, obj) { - // special function when branch read as part of complete object - // objects already preallocated and only appropriate member must be set - // see code in JSRoot.tree.js for reference + obj[this.name] = arr; + }; + member.split_func = function(buf, arr, n) { + // function to read array from member-wise streaming + const ver = this.read_version(buf); + for (let i = 0; i < n; ++i) + arr[i][this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + buf.checkByteCount(ver, this.typename); + }; + member.objs_branch_func = function(buf, obj) { + // special function when branch read as part of complete object + // objects already preallocated and only appropriate member must be set + // see code in JSRoot.tree.js for reference - const arr = obj[this.name0], // objects array where reading is done - ver = this.read_version(buf, arr.length); + const arr = obj[this.name0], // objects array where reading is done + ver = this.read_version(buf, arr.length); - for (let n = 0; n < arr.length; ++n) { - const obj1 = this.get(arr, n); - obj1[this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); - } + for (let n = 0; n < arr.length; ++n) { + const obj1 = this.get(arr, n); + obj1[this.name] = buf.readNdimArray(this, (buf2, member2) => member2.readelem(buf2)); + } - if (ver) buf.checkByteCount(ver, `branch ${this.typename}`); - }; - } + if (ver) + buf.checkByteCount(ver, `branch ${this.typename}`); + }; + } break; } @@ -1254,32 +1484,11 @@ function createMemberStreamer(element, file) { } -/** @summary Analyze and returns arrays kind - * @return 0 if TString (or equivalent), positive value - some basic type, -1 - any other kind - * @private */ -function getArrayKind(type_name) { - if ((type_name === clTString) || (type_name === 'string') || - (CustomStreamers[type_name] === clTString)) return 0; - if ((type_name.length < 7) || (type_name.indexOf('TArray') !== 0)) return -1; - if (type_name.length === 7) { - switch (type_name[6]) { - case 'I': return kInt; - case 'D': return kDouble; - case 'F': return kFloat; - case 'S': return kShort; - case 'C': return kChar; - case 'L': return kLong; - default: return -1; - } - } - - return type_name === 'TArrayL64' ? kLong64 : -1; -} - /** @summary Let directly assign methods when doing I/O * @private */ function addClassMethods(clname, streamer) { - if (streamer === null) return streamer; + if (streamer === null) + return streamer; const methods = getMethods(clname); @@ -1301,36 +1510,34 @@ function addClassMethods(clname, streamer) { */ /* constant parameters */ -const zip_WSIZE = 32768, // Sliding Window size +const + zip_WSIZE = 32768, // Sliding Window size -/* constant tables (inflate) */ -zip_MASK_BITS = [ - 0x0000, - 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, - 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff], + /* constant tables (inflate) */ + zip_MASK_BITS = [0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff], -// Tables for deflate from PKZIP's appnote.txt. - zip_cplens = [ // Copy lengths for literal codes 257..285 - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], + // Tables for deflate from PKZIP's appnote.txt. + // Copy lengths for literal codes 257..285 + zip_cplens = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], -/* note: see note #13 above about the 258 in this list. */ - zip_cplext = [ // Extra bits for literal codes 257..285 - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99], // 99==invalid + /* note: see note #13 above about the 258 in this list. */ + // Extra bits for literal codes 257..285 + zip_cplext = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99], // 99==invalid - zip_cpdist = [ // Copy offsets for distance codes 0..29 - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577], + // Copy offsets for distance codes 0..29 + zip_cpdist = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577], - zip_cpdext = [ // Extra bits for distance codes - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, - 12, 12, 13, 13], + // Extra bits for distance codes + zip_cpdext = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13], - zip_border = [ // Order of the bit length code lengths - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + // Order of the bit length code lengths + zip_border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; function ZIP_inflate(arr, tgt) { /* variables (inflate) */ @@ -1375,21 +1582,21 @@ function ZIP_inflate(arr, tgt) { d, // list of base values for non-simple codes e, // list of extra bits for non-simple codes mm) { // maximum lookup bits - const res = { - status: 0, // 0: success, 1: incomplete table, 2: bad input - root: null, // (zip_HuftList) starting table - m: 0 // maximum lookup bits, returns actual - }, - BMAX = 16, // maximum bit length of any code - N_MAX = 288, // maximum number of codes in any set - c = Array(BMAX+1).fill(0), // bit length count table - lx = Array(BMAX+1).fill(0), // stack of bits per table - u = Array(BMAX).fill(null), // zip_HuftNode[BMAX][] table stack - v = Array(N_MAX).fill(0), // values in order of bit length - x = Array(BMAX+1).fill(0), // bit offsets, then code stack - r = { e: 0, b: 0, n: 0, t: null }, // new zip_HuftNode(), // table entry for structure assignment - el = (n > 256) ? b[256] : BMAX; // set length of EOB code, if any - let rr = null, // temporary variable, use in assignment + const BMAX = 16, // maximum bit length of any code + N_MAX = 288, // maximum number of codes in any set + c = Array(BMAX + 1).fill(0), // bit length count table + lx = Array(BMAX + 1).fill(0), // stack of bits per table + u = Array(BMAX).fill(null), // zip_HuftNode[BMAX][] table stack + v = Array(N_MAX).fill(0), // values in order of bit length + x = Array(BMAX + 1).fill(0), // bit offsets, then code stack + r = { e: 0, b: 0, n: 0, t: null }, // new zip_HuftNode(), // table entry for structure assignment + el = (n > 256) ? b[256] : BMAX, // set length of EOB code, if any + res = { + status: 0, // 0: success, 1: incomplete table, 2: bad input + root: null, // (zip_HuftList) starting table + m: 0 // maximum lookup bits, returns actual + }; + let rr, // temporary variable, use in assignment a, // counter for codes of length k f, // i repeats in table every f entries h, // table level @@ -1415,14 +1622,12 @@ function ZIP_inflate(arr, tgt) { return res; // Find minimum and maximum length, bound *m by those - for (j = 1; j <= BMAX; ++j) - if (c[j] !== 0) break; + for (j = 1; j <= BMAX && !c[j]; ++j); k = j; // minimum code length if (mm < j) mm = j; - for (i = BMAX; i !== 0; --i) - if (c[i] !== 0) break; + for (i = BMAX; i && !c[i]; --i); const g = i; // maximum code length if (mm > i) @@ -1452,17 +1657,19 @@ function ZIP_inflate(arr, tgt) { x[xp++] = (j += p[pidx++]); // Make a table of values in order of bit lengths - p = b; pidx = 0; + p = b; + pidx = 0; i = 0; do { - if ((j = p[pidx++]) !== 0) + if ((j = p[pidx++])) v[x[j]++] = i; } while (++i < n); n = x[g]; // set n to length of v // Generate the Huffman codes and for each, make the table entries x[0] = i = 0; // first Huffman code is zero - p = v; pidx = 0; // grab values in bit order + p = v; + pidx = 0; // grab values in bit order h = -1; // no tables yet--level -1 w = lx[0] = 0; // no bits decoded yet q = null; // ditto @@ -1499,7 +1706,7 @@ function ZIP_inflate(arr, tgt) { for (o = 0; o < z; ++o) q[o] = { e: 0, b: 0, n: 0, t: null }; // new zip_HuftNode - if (tail == null) + if (tail === null) tail = res.root = { next: null, list: null }; // new zip_HuftList(); else tail = tail.next = { next: null, list: null }; // new zip_HuftList(); @@ -1514,7 +1721,7 @@ function ZIP_inflate(arr, tgt) { r.e = 16 + j; // bits in this table r.t = q; // pointer to this table j = (i & ((1 << w) - 1)) >> (w - lx[h]); - rr = u[h-1][j]; + rr = u[h - 1][j]; rr.e = r.e; rr.b = r.b; rr.n = r.n; @@ -1545,7 +1752,7 @@ function ZIP_inflate(arr, tgt) { } // backwards increment the k-bit code i - for (j = 1 << (k - 1); (i & j) !== 0; j >>= 1) + for (j = 1 << (k - 1); (i & j); j >>= 1) i ^= j; i ^= j; @@ -1559,7 +1766,7 @@ function ZIP_inflate(arr, tgt) { res.m = lx[1]; /* Return true (1) if we were given an incomplete table */ - res.status = ((y !== 0 && g !== 1) ? 1 : 0); + res.status = (y && g !== 1) ? 1 : 0; return res; } @@ -1567,7 +1774,8 @@ function ZIP_inflate(arr, tgt) { /* routines (inflate) */ function zip_inflate_codes(buff, off, size) { - if (size === 0) return 0; + if (size === 0) + return 0; /* inflate (decompress) the codes in a deflated (compressed) block. Return an error code or zero if it all goes ok. */ @@ -1681,18 +1889,18 @@ function ZIP_inflate(arr, tgt) { function zip_inflate_fixed(buff, off, size) { /* decompress an inflated type 1 (fixed Huffman codes) block. We should - either replace this with a custom decoder, or at least precompute the + either replace this with a custom decoder, or at least pre-compute the Huffman tables. */ // if first time, set up tables for fixed blocks - if (zip_fixed_tl == null) { + if (zip_fixed_tl === null) { // literal table const l = Array(288).fill(8, 0, 144).fill(9, 144, 256).fill(7, 256, 280).fill(8, 280, 288); // make a complete, but wrong code set zip_fixed_bl = 7; let h = zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext, zip_fixed_bl); - if (h.status !== 0) + if (h.status) throw new Error('HufBuild error: ' + h.status); zip_fixed_tl = h.root; zip_fixed_bl = h.m; @@ -1704,7 +1912,7 @@ function ZIP_inflate(arr, tgt) { h = zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd); if (h.status > 1) { zip_fixed_tl = null; - throw new Error('HufBuild error: '+h.status); + throw new Error('HufBuild error: ' + h.status); } zip_fixed_td = h.root; zip_fixed_bd = h.m; @@ -1723,7 +1931,7 @@ function ZIP_inflate(arr, tgt) { l, // last length t, // (zip_HuftNode) literal/length code table h; // (zip_HuftBuild) - const ll = new Array(286+30).fill(0); // literal/length and distance code lengths + const ll = new Array(286 + 30).fill(0); // literal/length and distance code lengths // read in table lengths zip_NEEDBITS(5); @@ -1750,7 +1958,7 @@ function ZIP_inflate(arr, tgt) { // build decoding table for trees--single level, 7 bit lookup zip_bl = 7; h = zip_HuftBuild(ll, 19, 19, null, null, zip_bl); - if (h.status !== 0) + if (h.status) return -1; // incomplete code set zip_tl = h.root; @@ -1801,7 +2009,7 @@ function ZIP_inflate(arr, tgt) { h = zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl); if (zip_bl === 0) // no literals or lengths h.status = 1; - if (h.status !== 0) + if (h.status) return -1; // incomplete code set zip_tl = h.root; zip_bl = h.m; @@ -1814,7 +2022,7 @@ function ZIP_inflate(arr, tgt) { zip_bd = h.m; // incomplete distance tree - if ((zip_bd === 0 && nl > 257) || (h.status !== 0)) // lengths but no distances + if ((zip_bd === 0 && nl > 257) || h.status) // lengths but no distances return -1; // decompress until an end-of-block code @@ -1830,7 +2038,7 @@ function ZIP_inflate(arr, tgt) { return n; if (zip_copy_leng > 0) { - if (zip_method !== 0 /* zip_STORED_BLOCK */) { + if (zip_method /* zip_STORED_BLOCK */) { // STATIC_TREES or DYN_TREES while (zip_copy_leng > 0 && n < size) { --zip_copy_leng; @@ -1860,7 +2068,7 @@ function ZIP_inflate(arr, tgt) { // read in last block bit zip_NEEDBITS(1); - if (zip_GETBITS(1) !== 0) + if (zip_GETBITS(1)) zip_eof = true; zip_DUMPBITS(1); @@ -1878,14 +2086,14 @@ function ZIP_inflate(arr, tgt) { break; case 1: // zip_STATIC_TREES - if (zip_tl != null) + if (zip_tl !== null) i = zip_inflate_codes(buff, off + n, size - n); else i = zip_inflate_fixed(buff, off + n, size - n); break; case 2: // zip_DYN_TREES - if (zip_tl != null) + if (zip_tl !== null) i = zip_inflate_codes(buff, off + n, size - n); else i = zip_inflate_dynamic(buff, off + n, size - n); @@ -1904,7 +2112,7 @@ function ZIP_inflate(arr, tgt) { } let i, cnt = 0; - while ((i = zip_inflate_internal(tgt, cnt, Math.min(1024, tgt.byteLength-cnt))) > 0) + while ((i = zip_inflate_internal(tgt, cnt, Math.min(1024, tgt.byteLength - cnt))) > 0) cnt += i; return cnt; @@ -1922,7 +2130,7 @@ function ZIP_inflate(arr, tgt) { * Decode a block. Assumptions: input contains all sequences of a * chunk, output is large enough to receive the decoded data. * If the output buffer is too small, an error will be thrown. - * If the returned value is negative, an error occured at the returned offset. + * If the returned value is negative, an error occurred at the returned offset. * * @param input {Buffer} input data * @param output {Buffer} output data @@ -1948,10 +2156,12 @@ function LZ4_uncompress(input, output, sIdx, eIdx) { // Copy the literals const end = i + literals_length; - while (i < end) output[j++] = input[i++]; + while (i < end) + output[j++] = input[i++]; // End of buffer? - if (i === n) return j; + if (i === n) + return j; } // Match copy @@ -1959,7 +2169,8 @@ function LZ4_uncompress(input, output, sIdx, eIdx) { const offset = input[i++] | (input[i++] << 8); // 0 is an invalid offset value - if (offset === 0 || offset > j) return -(i-2); + if (offset === 0 || offset > j) + return -(i - 2); // length of match copy let match_length = (token & 0xf), @@ -1972,7 +2183,8 @@ function LZ4_uncompress(input, output, sIdx, eIdx) { // Copy the match let pos = j - offset; // position of the match copy in the current output const end = j + match_length + 4; // minmatch = 4; - while (j < end) output[j++] = output[pos++]; + while (j < end) + output[j++] = output[pos++]; } return j; @@ -1983,9 +2195,7 @@ function LZ4_uncompress(input, output, sIdx, eIdx) { * @return {Promise} with unzipped content * @private */ async function R__unzip(arr, tgtsize, noalert, src_shift) { - const HDRSIZE = 9, totallen = arr.byteLength, - checkChar = (o, symb) => { return String.fromCharCode(arr.getUint8(o)) === symb; }, - getCode = o => arr.getUint8(o); + const HDRSIZE = 9, totallen = arr.byteLength; let curr = src_shift || 0, fullres = 0, tgtbuf = null; @@ -1994,46 +2204,74 @@ async function R__unzip(arr, tgtsize, noalert, src_shift) { let fmt = 'unknown', off = 0, CHKSUM = 0; if (curr + HDRSIZE >= totallen) { - if (!noalert) console.error('Error R__unzip: header size exceeds buffer size'); + if (!noalert) + console.error('Error R__unzip: header size exceeds buffer size'); return Promise.resolve(null); } - if (checkChar(curr, 'Z') && checkChar(curr+1, 'L') && getCode(curr + 2) === 8) { fmt = 'new'; off = 2; } else - if (checkChar(curr, 'C') && checkChar(curr+1, 'S') && getCode(curr + 2) === 8) { fmt = 'old'; off = 0; } else - if (checkChar(curr, 'X') && checkChar(curr+1, 'Z') && getCode(curr + 2) === 0) { fmt = 'LZMA'; off = 0; } else - if (checkChar(curr, 'Z') && checkChar(curr+1, 'S') && getCode(curr + 2) === 1) fmt = 'ZSTD'; else - if (checkChar(curr, 'L') && checkChar(curr+1, '4')) { fmt = 'LZ4'; off = 0; CHKSUM = 8; } + const getCode = o => arr.getUint8(o), + checkChar = (o, symb) => { return getCode(o) === symb.charCodeAt(0); }, + checkFmt = (a, b, c) => { return checkChar(curr, a) && checkChar(curr + 1, b) && (getCode(curr + 2) === c); }; + + if (checkFmt('Z', 'L', 8)) { + fmt = 'new'; + off = 2; + } else if (checkFmt('C', 'S', 8)) + fmt = 'old'; + else if (checkFmt('X', 'Z', 0)) + fmt = 'LZMA'; + else if (checkFmt('Z', 'S', 1)) + fmt = 'ZSTD'; + else if (checkChar(curr, 'L') && checkChar(curr + 1, '4')) { + fmt = 'LZ4'; + CHKSUM = 8; + } /* C H E C K H E A D E R */ if ((fmt !== 'new') && (fmt !== 'old') && (fmt !== 'LZ4') && (fmt !== 'ZSTD') && (fmt !== 'LZMA')) { - if (!noalert) console.error(`R__unzip: ${fmt} format is not supported!`); + if (!noalert) + console.error(`R__unzip: ${fmt} format is not supported!`); return Promise.resolve(null); } const srcsize = HDRSIZE + ((getCode(curr + 3) & 0xff) | ((getCode(curr + 4) & 0xff) << 8) | ((getCode(curr + 5) & 0xff) << 16)), uint8arr = new Uint8Array(arr.buffer, arr.byteOffset + curr + HDRSIZE + off + CHKSUM, Math.min(arr.byteLength - curr - HDRSIZE - off - CHKSUM, srcsize - HDRSIZE - CHKSUM)); - if (!tgtbuf) tgtbuf = new ArrayBuffer(tgtsize); + if (!tgtbuf) + tgtbuf = new ArrayBuffer(tgtsize); const tgt8arr = new Uint8Array(tgtbuf, fullres); if (fmt === 'ZSTD') { - const promise = internals._ZstdStream - ? Promise.resolve(internals._ZstdStream) - : (isNodeJs() ? import('@oneidentity/zstd-js') : import(/* webpackIgnore: true */ './base/zstd.mjs')) - .then(({ ZstdInit }) => ZstdInit()).then(({ ZstdStream }) => { internals._ZstdStream = ZstdStream; return ZstdStream; }); + let promise; + if (internals._ZstdStream) + promise = Promise.resolve(internals._ZstdStream); + else if (internals._ZstdInit !== undefined) + promise = new Promise(resolveFunc => { internals._ZstdInit.push(resolveFunc); }); + else { + internals._ZstdInit = []; + promise = (isNodeJs() ? import('@oneidentity/zstd-js') : import('./base/zstd.mjs')) + .then(({ ZstdInit }) => ZstdInit()) + .then(({ ZstdStream }) => { + internals._ZstdStream = ZstdStream; + internals._ZstdInit.forEach(func => func(ZstdStream)); + delete internals._ZstdInit; + return ZstdStream; + }); + } + return promise.then(ZstdStream => { const data2 = ZstdStream.decompress(uint8arr), reslen = data2.length; for (let i = 0; i < reslen; ++i) - tgt8arr[i] = data2[i]; + tgt8arr[i] = data2[i]; fullres += reslen; curr += srcsize; return nextPortion(); }); } else if (fmt === 'LZMA') { - return import(/* webpackIgnore: true */ './base/lzma.mjs').then(lzma => { + return import('./base/lzma.mjs').then(lzma => { const expected_len = (getCode(curr + 6) & 0xff) | ((getCode(curr + 7) & 0xff) << 8) | ((getCode(curr + 8) & 0xff) << 16), reslen = lzma.decompress(uint8arr, tgt8arr, expected_len); fullres += reslen; @@ -2044,13 +2282,15 @@ async function R__unzip(arr, tgtsize, noalert, src_shift) { const reslen = (fmt === 'LZ4') ? LZ4_uncompress(uint8arr, tgt8arr) : ZIP_inflate(uint8arr, tgt8arr); - if (reslen <= 0) break; + if (reslen <= 0) + break; fullres += reslen; curr += srcsize; } if (fullres !== tgtsize) { - if (!noalert) console.error(`R__unzip: fail to unzip data expects ${tgtsize}, got ${fullres}`); + if (!noalert) + console.error(`R__unzip: fail to unzip data expects ${tgtsize}, got ${fullres}`); return Promise.resolve(null); } @@ -2093,7 +2333,10 @@ class TBuffer { getMappedObject(tag) { return this.fObjectMap[tag]; } /** @summary Map object */ - mapObject(tag, obj) { if (obj !== null) this.fObjectMap[tag] = obj; } + mapObject(tag, obj) { + if (obj !== null) + this.fObjectMap[tag] = obj; + } /** @summary Map class */ mapClass(tag, classname) { this.fClassMap[tag] = classname; } @@ -2138,7 +2381,7 @@ class TBuffer { checkByteCount(ver, where) { if ((ver.bytecnt !== undefined) && (ver.off + ver.bytecnt !== this.o)) { if (where) - console.log(`Missmatch in ${where} bytecount expected = ${ver.bytecnt} got = ${this.o - ver.off}`); + console.log(`Missmatch in ${where}:${ver.val} bytecount expected = ${ver.bytecnt} got = ${this.o - ver.off}`); this.o = ver.off + ver.bytecnt; return false; } @@ -2150,8 +2393,10 @@ class TBuffer { readTString() { let len = this.ntou1(); // large strings - if (len === 255) len = this.ntou4(); - if (len === 0) return ''; + if (len === 255) + len = this.ntou4(); + if (!len) + return ''; const pos = this.o; this.o += len; @@ -2159,15 +2404,24 @@ class TBuffer { return (this.codeAt(pos) === 0) ? '' : this.substring(pos, pos + len); } - /** @summary read Char_t array as string - * @desc string either contains all symbols or until 0 symbol */ + /** @summary read Char_t array as string + * @desc stops when 0 is found */ + readNullTerminatedString() { + let res = '', code; + while ((code = this.ntou1())) + res += String.fromCharCode(code); + return res; + } + + /** @summary read Char_t array as string */ readFastString(n) { - let res = '', code, closed = false; - // eslint-disable-next-line no-unmodified-loop-condition - for (let i = 0; (n < 0) || (i < n); ++i) { - code = this.ntou1(); - if (code === 0) { closed = true; if (n < 0) break; } - if (!closed) res += String.fromCharCode(code); + let res = '', reading = true; + for (let i = 0; i < n; ++i) { + const code = this.ntou1(); + if (code === 0) + reading = false; + if (reading) + res += String.fromCharCode(code); } return res; @@ -2176,22 +2430,29 @@ class TBuffer { /** @summary read uint8_t */ ntou1() { return this.arr.getUint8(this.o++); } + /** @summary read boolean */ + ntobool() { return Boolean(this.arr.getUint8(this.o++)); } + /** @summary read uint16_t */ ntou2() { - const o = this.o; this.o += 2; + const o = this.o; + this.o += 2; return this.arr.getUint16(o); } /** @summary read uint32_t */ ntou4() { - const o = this.o; this.o += 4; + const o = this.o; + this.o += 4; return this.arr.getUint32(o); } /** @summary read uint64_t */ ntou8() { - const high = this.arr.getUint32(this.o); this.o += 4; - const low = this.arr.getUint32(this.o); this.o += 4; + const high = this.arr.getUint32(this.o); + this.o += 4; + const low = this.arr.getUint32(this.o); + this.o += 4; return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); } @@ -2200,20 +2461,24 @@ class TBuffer { /** @summary read int16_t */ ntoi2() { - const o = this.o; this.o += 2; + const o = this.o; + this.o += 2; return this.arr.getInt16(o); } /** @summary read int32_t */ ntoi4() { - const o = this.o; this.o += 4; + const o = this.o; + this.o += 4; return this.arr.getInt32(o); } /** @summary read int64_t */ ntoi8() { - const high = this.arr.getUint32(this.o); this.o += 4; - const low = this.arr.getUint32(this.o); this.o += 4; + const high = this.arr.getUint32(this.o); + this.o += 4; + const low = this.arr.getUint32(this.o); + this.o += 4; if (high < 0x80000000) return (high < 0x200000) ? (high * 0x100000000 + low) : (BigInt(high) * BigInt(0x100000000) + BigInt(low)); return (~high < 0x200000) ? (-1 - ((~high) * 0x100000000 + ~low)) : (BigInt(-1) - (BigInt(~high) * BigInt(0x100000000) + BigInt(~low))); @@ -2221,13 +2486,15 @@ class TBuffer { /** @summary read float */ ntof() { - const o = this.o; this.o += 4; + const o = this.o; + this.o += 4; return this.arr.getFloat32(o); } /** @summary read double */ ntod() { - const o = this.o; this.o += 8; + const o = this.o; + this.o += 8; return this.arr.getFloat64(o); } @@ -2309,27 +2576,28 @@ class TBuffer { /** @summary Check if provided regions can be extracted from the buffer */ canExtract(place) { - for (let n = 0; n < place.length; n += 2) - if (place[n] + place[n + 1] > this.length) return false; + for (let n = 0; n < place.length; n += 2) { + if (place[n] + place[n + 1] > this.length) + return false; + } return true; } /** @summary Extract area */ extract(place) { - if (!this.arr || !this.arr.buffer || !this.canExtract(place)) return null; - if (place.length === 2) return new DataView(this.arr.buffer, this.arr.byteOffset + place[0], place[1]); + if (!this.arr?.buffer || !this.canExtract(place)) + return null; + if (place.length === 2) + return new DataView(this.arr.buffer, this.arr.byteOffset + place[0], place[1]); const res = new Array(place.length / 2); for (let n = 0; n < place.length; n += 2) res[n / 2] = new DataView(this.arr.buffer, this.arr.byteOffset + place[n], place[n + 1]); - return res; // return array of buffers } /** @summary Get code at buffer position */ - codeAt(pos) { - return this.arr.getUint8(pos); - } + codeAt(pos) { return this.arr.getUint8(pos); } /** @summary Get part of buffer as string */ substring(beg, end) { @@ -2342,10 +2610,15 @@ class TBuffer { /** @summary Read buffer as N-dim array */ readNdimArray(handle, func) { let ndim = handle.fArrayDim, maxindx = handle.fMaxIndex, res; - if ((ndim < 1) && (handle.fArrayLength > 0)) { ndim = 1; maxindx = [handle.fArrayLength]; } - if (handle.minus1) --ndim; + if ((ndim < 1) && (handle.fArrayLength > 0)) { + ndim = 1; + maxindx = [handle.fArrayLength]; + } + if (handle.minus1) + --ndim; - if (ndim < 1) return func(this, handle); + if (ndim < 1) + return func(this, handle); if (ndim === 1) { res = new Array(maxindx[0]); @@ -2382,7 +2655,8 @@ class TBuffer { /** @summary read TKey data */ readTKey(key) { - if (!key) key = {}; + if (!key) + key = {}; this.classStreamer(key, clTKey); const name = key.fName.replace(/['"]/g, ''); if (name !== key.fName) { @@ -2399,8 +2673,10 @@ class TBuffer { this.locate(basket.fLast - offset); if (this.remain() <= 0) { - if (!basket.fEntryOffset && (basket.fNevBuf <= 1)) basket.fEntryOffset = [basket.fKeylen]; - if (!basket.fEntryOffset) console.warn(`No fEntryOffset when expected for basket with ${basket.fNevBuf} entries`); + if (!basket.fEntryOffset && (basket.fNevBuf <= 1)) + basket.fEntryOffset = [basket.fKeylen]; + if (!basket.fEntryOffset) + console.warn(`No fEntryOffset when expected for basket with ${basket.fNevBuf} entries`); return; } @@ -2409,12 +2685,14 @@ class TBuffer { // it is workaround, but normally I/O should fail here if ((nentries < 0) || (nentries > this.remain() * 4)) { console.error(`Error when reading entries offset from basket fNevBuf ${basket.fNevBuf} remains ${this.remain()} want to read ${nentries}`); - if (basket.fNevBuf <= 1) basket.fEntryOffset = [basket.fKeylen]; + if (basket.fNevBuf <= 1) + basket.fEntryOffset = [basket.fKeylen]; return; } basket.fEntryOffset = this.readFastArray(nentries, kInt); - if (!basket.fEntryOffset) basket.fEntryOffset = [basket.fKeylen]; + if (!basket.fEntryOffset) + basket.fEntryOffset = [basket.fKeylen]; if (this.remain() > 0) basket.fDisplacement = this.readFastArray(this.ntoi4(), kInt); @@ -2424,13 +2702,10 @@ class TBuffer { /** @summary read class definition from I/O buffer */ readClass() { - const classInfo = { name: -1 }, bcnt = this.ntou4(), startpos = this.o; - let tag; - - if (!(bcnt & kByteCountMask) || (bcnt === kNewClassTag)) - tag = bcnt; - else - tag = this.ntou4(); + const classInfo = { name: -1 }, + bcnt = this.ntou4(), + startpos = this.o, + tag = !(bcnt & kByteCountMask) || (bcnt === kNewClassTag) ? bcnt : this.ntou4(); if (!(tag & kClassMask)) { classInfo.objtag = tag + this.fDisplacement; // indicate that we have deal with objects tag @@ -2438,7 +2713,7 @@ class TBuffer { } if (tag === kNewClassTag) { // got a new class description followed by a new object - classInfo.name = this.readFastString(-1); + classInfo.name = this.readNullTerminatedString(); if (this.getMappedClass(this.fTagOffset + startpos + kMapOffset) === -1) this.mapClass(this.fTagOffset + startpos + kMapOffset, classInfo.name); @@ -2460,10 +2735,11 @@ class TBuffer { clRef = this.readClass(); // class identified as object and should be handled so - if ('objtag' in clRef) + if (clRef.objtag !== undefined) return this.getMappedObject(clRef.objtag); - if (clRef.name === -1) return null; + if (clRef.name === -1) + return null; const arrkind = getArrayKind(clRef.name); let obj; @@ -2486,7 +2762,8 @@ class TBuffer { /** @summary Invoke streamer for specified class */ classStreamer(obj, classname) { - if (obj._typename === undefined) obj._typename = classname; + if (obj._typename === undefined) + obj._typename = classname; const direct = DirectStreamers[classname]; if (direct) { @@ -2512,10 +2789,75 @@ class TBuffer { return obj; } + /** @summary Stream class members using normal streamer */ + streamClassMembers(obj, classname, version) { + const streamer = this.fFile.getStreamer(classname, { val: version }, undefined, true); + if (streamer !== null) { + const len = streamer.length; + for (let n = 0; n < len; ++n) + streamer[n].func(this, obj); + } + return obj; + } + } // class TBuffer // ============================================================================== +/** @summary Direct streamer for TBasket, + * @desc uses TBuffer therefore defined later + * @private */ +DirectStreamers[clTBasket] = function(buf, obj) { + buf.classStreamer(obj, clTKey); + const ver = buf.readVersion(); + obj.fBufferSize = buf.ntoi4(); + obj.fNevBufSize = buf.ntoi4(); + + if (obj.fNevBufSize < 0) { + obj.fNevBufSize = -obj.fNevBufSize; + obj.fIOBits = buf.ntoi1(); + } + + obj.fNevBuf = buf.ntoi4(); + obj.fLast = buf.ntoi4(); + + if (obj.fLast > obj.fBufferSize) + obj.fBufferSize = obj.fLast; + const flag = buf.ntoi1(); + + if (flag === 0) + return; + + if ((flag % 10) !== 2) { + if (obj.fNevBuf) { + obj.fEntryOffset = buf.readFastArray(buf.ntoi4(), kInt); + if ((flag > 20) && (flag < 40)) { + for (let i = 0, kDisplacementMask = 0xFF000000; i < obj.fNevBuf; ++i) + obj.fEntryOffset[i] &= ~kDisplacementMask; + } + } + + if (flag > 40) + obj.fDisplacement = buf.readFastArray(buf.ntoi4(), kInt); + } + + if ((flag === 1) || (flag > 10)) { + // here is reading of raw data + const sz = (ver.val <= 1) ? buf.ntoi4() : obj.fLast; + + if (sz > obj.fKeylen) { + // buffer includes again complete TKey data - exclude it + const blob = buf.extract([buf.o + obj.fKeylen, sz - obj.fKeylen]); + obj.fBufferRef = new TBuffer(blob, 0, buf.fFile, sz - obj.fKeylen); + obj.fBufferRef.fTagOffset = obj.fKeylen; + } + + buf.shift(sz); + } +}; + +// ============================================================================== + /** * @summary A class that reads a TDirectory from a buffer. * @@ -2535,13 +2877,19 @@ class TDirectory { /** @summary retrieve a key by its name and cycle in the list of keys */ getKey(keyname, cycle, only_direct) { - if (typeof cycle !== 'number') cycle = -1; + if (typeof cycle !== 'number') + cycle = -1; let bestkey = null; for (let i = 0; i < this.fKeys.length; ++i) { const key = this.fKeys[i]; - if (!key || (key.fName !== keyname)) continue; - if (key.fCycle === cycle) { bestkey = key; break; } - if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) bestkey = key; + if (!key || (key.fName !== keyname)) + continue; + if (key.fCycle === cycle) { + bestkey = key; + break; + } + if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) + bestkey = key; } if (bestkey) return only_direct ? bestkey : Promise.resolve(bestkey); @@ -2550,7 +2898,7 @@ class TDirectory { // try to handle situation when object name contains slashed (bad practice anyway) while (pos > 0) { const dirname = keyname.slice(0, pos), - subname = keyname.slice(pos+1), + subname = keyname.slice(pos + 1), dirkey = this.getKey(dirname, undefined, true); if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) { @@ -2558,7 +2906,7 @@ class TDirectory { .then(newdir => newdir.getKey(subname, cycle)); } - pos = keyname.lastIndexOf('/', pos-1); + pos = keyname.lastIndexOf('/', pos - 1); } return only_direct ? null : Promise.reject(Error(`Key not found ${keyname}`)); @@ -2627,22 +2975,24 @@ class TFile { this.fStreamers = 0; this.fStreamerInfos = null; this.fFileName = ''; + this.fTimeout = settings.FilesTimeout ?? 0; this.fStreamers = []; this.fBasicTypes = {}; // custom basic types, in most case enumerations - if (!isStr(this.fURL)) return this; + if (!isStr(this.fURL)) + return; - if (this.fURL[this.fURL.length - 1] === '+') { + if (this.fURL.at(-1) === '+') { this.fURL = this.fURL.slice(0, this.fURL.length - 1); this.fAcceptRanges = false; } - if (this.fURL[this.fURL.length - 1] === '^') { + if (this.fURL.at(-1) === '^') { this.fURL = this.fURL.slice(0, this.fURL.length - 1); this.fSkipHeadRequest = true; } - if (this.fURL[this.fURL.length - 1] === '-') { + if (this.fURL.at(-1) === '-') { this.fURL = this.fURL.slice(0, this.fURL.length - 1); this.fUseStampPar = false; } @@ -2656,6 +3006,28 @@ class TFile { this.fFileName = pos >= 0 ? this.fURL.slice(pos + 1) : this.fURL; } + /** @summary Set timeout for File instance + * @desc Timeout used when submitting http requests to the server */ + setTimeout(v) { + this.fTimeout = v; + } + + /** @summary Assign remap for web servers + * @desc Allows to specify fallback server if main server fails + * @param {Object} remap - looks like { 'https://original.server/': 'https://fallback.server/' } */ + assignRemap(remap) { + if (!remap && !isObject(remap)) + return; + + for (const key in remap) { + if (this.fURL.indexOf(key) === 0) { + this.fURL2 = remap[key] + this.fURL.slice(key.length); + if (!this.fTimeout) + this.fTimeout = 10000; + } + } + } + /** @summary Assign BufferArray with file contentOpen file * @private */ assignFileContent(bufArray) { @@ -2670,6 +3042,63 @@ class TFile { * @private */ async _open() { return this.readKeys(); } + /** @summary check if requested segments can be reordered or merged + * @private */ + #checkNeedReorder(place) { + let res = false, resort = false; + for (let n = 0; n < place.length - 2; n += 2) { + if (place[n] > place[n + 2]) + res = resort = true; + if (place[n] + place[n + 1] > place[n + 2] - kMinimalHttpGap) + res = true; + } + if (!res) { + return { + place, + blobs: [], + expectedSize(indx) { return this.place[indx + 1]; }, + addBuffer(indx, buf, o) { + this.blobs[indx / 2] = new DataView(buf, o, this.place[indx + 1]); + } + }; + } + + res = { place, reorder: [], place_new: [], blobs: [] }; + + for (let n = 0; n < place.length; n += 2) + res.reorder.push({ pos: place[n], len: place[n + 1], indx: [n] }); + + if (resort) + res.reorder.sort((a, b) => { return a.pos - b.pos; }); + + for (let n = 0; n < res.reorder.length - 1; n++) { + const curr = res.reorder[n], + next = res.reorder[n + 1]; + if (curr.pos + curr.len + kMinimalHttpGap > next.pos) { + curr.indx.push(...next.indx); + curr.len = next.pos + next.len - curr.pos; + res.reorder.splice(n + 1, 1); // remove segment + n--; + } + } + + res.reorder.forEach(elem => res.place_new.push(elem.pos, elem.len)); + + res.expectedSize = function(indx) { + return this.reorder[indx / 2].len; + }; + + res.addBuffer = function(indx, buf, o) { + const elem = this.reorder[indx / 2], + pos0 = elem.pos; + elem.indx.forEach(indx0 => { + this.blobs[indx0 / 2] = new DataView(buf, o + this.place[indx0] - pos0, this.place[indx0 + 1]); + }); + }; + + return res; + } + /** @summary read buffer(s) from the file * @return {Promise} with read buffers * @private */ @@ -2677,28 +3106,47 @@ class TFile { if ((this.fFileContent !== null) && !filename && (!this.fAcceptRanges || this.fFileContent.canExtract(place))) return this.fFileContent.extract(place); + const reorder = this.#checkNeedReorder(place); + if (reorder?.place_new) + place = reorder?.place_new; + let resolveFunc, rejectFunc; const file = this, first_block = (place[0] === 0) && (place.length === 2), - blobs = [], // array of requested segments - promise = new Promise((resolve, reject) => { resolveFunc = resolve; rejectFunc = reject; }); + promise = new Promise((resolve, reject) => { + resolveFunc = resolve; + rejectFunc = reject; + }); - let fileurl = file.fURL, - first = 0, last = 0, + let fileurl, first = 0, last = 0, // eslint-disable-next-line prefer-const read_callback, first_req, first_block_retry = false; - if (isStr(filename) && filename) { - const pos = fileurl.lastIndexOf('/'); - fileurl = (pos < 0) ? filename : fileurl.slice(0, pos + 1) + filename; + function setFileUrl(use_second) { + if (use_second) { + console.log('Failure - try to repair with URL2', file.fURL2); + internals.RemapCounter = (internals.RemapCounter ?? 0) + 1; + file.fURL = file.fURL2; + delete file.fURL2; + } + + fileurl = file.fURL; + if (isStr(filename) && filename) { + const pos = fileurl.lastIndexOf('/'); + fileurl = (pos < 0) ? filename : fileurl.slice(0, pos + 1) + filename; + } } - function send_new_request(increment) { - if (increment) { + function send_new_request(arg) { + if (arg === 'noranges') { + file.fMaxRanges = 1; + last = Math.min(last, first + file.fMaxRanges * 2); + } else if (arg) { first = last; last = Math.min(first + file.fMaxRanges * 2, place.length); - if (first >= place.length) return resolveFunc(blobs); + if (first >= place.length) + return resolveFunc(reorder.blobs.length === 1 ? reorder.blobs[0] : reorder.blobs); } let fullurl = fileurl, ranges = 'bytes', totalsz = 0; @@ -2707,7 +3155,7 @@ class TFile { fullurl += ((fullurl.indexOf('?') < 0) ? '?' : '&') + file.fUseStampPar; for (let n = first; n < last; n += 2) { - ranges += (n > first ? ',' : '=') + `${place[n]}-${place[n]+place[n+1]-1}`; + ranges += (n > first ? ',' : '=') + `${place[n]}-${place[n] + place[n + 1] - 1}`; totalsz += place[n + 1]; // accumulated total size } if (last - first > 2) @@ -2715,7 +3163,7 @@ class TFile { // when read first block, allow to read more - maybe ranges are not supported and full file content will be returned if (file.fAcceptRanges && first_block) - totalsz = Math.max(totalsz, 1e7); + totalsz = Math.max(totalsz, 1e5); return createHttpRequest(fullurl, 'buf', read_callback, undefined, true).then(xhr => { if (file.fAcceptRanges) { @@ -2723,20 +3171,28 @@ class TFile { xhr.expected_size = Math.max(Math.round(1.1 * totalsz), totalsz + 200); // 200 if offset for the potential gzip } + if (file.fTimeout) + xhr.timeout = file.fTimeout; + if (isFunc(progress_callback) && isFunc(xhr.addEventListener)) { let sum1 = 0, sum2 = 0, sum_total = 0; for (let n = 1; n < place.length; n += 2) { sum_total += place[n]; - if (n < first) sum1 += place[n]; - if (n < last) sum2 += place[n]; + if (n < first) + sum1 += place[n]; + if (n < last) + sum2 += place[n]; } - if (!sum_total) sum_total = 1; + if (!sum_total) + sum_total = 1; const progress_offest = sum1 / sum_total, progress_this = (sum2 - sum1) / sum_total; xhr.addEventListener('progress', oEvent => { if (oEvent.lengthComputable) { - if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') + if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') { + xhr.did_abort = true; xhr.abort(); + } } }); } else if (first_block_retry && isFunc(xhr.addEventListener)) { @@ -2745,6 +3201,7 @@ class TFile { console.warn('Fail to get file size information'); else if (oEvent.total > 5e7) { console.error(`Try to load very large file ${oEvent.total} at once - abort`); + xhr.did_abort = 'large'; xhr.abort(); } }); @@ -2762,6 +3219,10 @@ class TFile { file.fUseStampPar = false; return send_new_request(); } + if (file.fURL2 && (this.did_abort !== 'large')) { + setFileUrl(true); + return send_new_request(); + } if (file.fAcceptRanges) { file.fAcceptRanges = false; first_block_retry = true; @@ -2796,9 +3257,12 @@ class TFile { } if (!res) { + if (file.fURL2 && (this.did_abort !== 'large')) { + setFileUrl(true); + return send_new_request(); + } if ((first === 0) && (last > 2) && (file.fMaxRanges > 1)) { // server return no response with multi request - try to decrease ranges count or fail - if (last / 2 > 200) file.fMaxRanges = 200; else if (last / 2 > 50) @@ -2810,79 +3274,43 @@ class TFile { else file.fMaxRanges = 1; last = Math.min(last, file.fMaxRanges * 2); - // console.log(`Change maxranges to ${file.fMaxRanges} last ${last}`); return send_new_request(); } - - return rejectFunc(Error('Fail to read with several ranges')); + return rejectFunc(Error(`Fail to read with ${place.length / 2} ranges max = ${file.fMaxRanges}`)); } // if only single segment requested, return result as is if (last - first === 2) { - const b = new DataView(res); - if (place.length === 2) return resolveFunc(b); - blobs.push(b); + reorder.addBuffer(first, res, 0); return send_new_request(true); } // object to access response data - const hdr = this.getResponseHeader('Content-Type'), - ismulti = isStr(hdr) && (hdr.indexOf('multipart') >= 0), - view = new DataView(res); - - if (!ismulti) { - // server may returns simple buffer, which combines all segments together - - const hdr_range = this.getResponseHeader('Content-Range'); - let segm_start = 0, segm_last = -1; - - if (isStr(hdr_range) && hdr_range.indexOf('bytes') >= 0) { - const parts = hdr_range.slice(hdr_range.indexOf('bytes') + 6).split(/[\s-/]+/); - if (parts.length === 3) { - segm_start = Number.parseInt(parts[0]); - segm_last = Number.parseInt(parts[1]); - if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) { - segm_start = 0; segm_last = -1; - } - } - } - - let canbe_single_segment = (segm_start <= segm_last); - for (let n = first; n < last; n += 2) { - if ((place[n] < segm_start) || (place[n] + place[n + 1] - 1 > segm_last)) - canbe_single_segment = false; - } - - if (canbe_single_segment) { - for (let n = first; n < last; n += 2) - blobs.push(new DataView(res, place[n] - segm_start, place[n + 1])); - return send_new_request(true); - } - - if ((file.fMaxRanges === 1) || (first !== 0)) - return rejectFunc(Error('Server returns normal response when multipart was requested, disable multirange support')); - - file.fMaxRanges = 1; - last = Math.min(last, file.fMaxRanges * 2); + const hdr = this.getResponseHeader('Content-Type'); - return send_new_request(); + if (!isStr(hdr) || (hdr.indexOf('multipart') < 0)) { + console.error('Did not found multipart in content-type - fallback to single range request'); + return send_new_request('noranges'); } // multipart messages requires special handling const indx = hdr.indexOf('boundary='); - let boundary = '', n = first, o = 0; - if (indx > 0) { - boundary = hdr.slice(indx + 9); - if ((boundary[0] === '"') && (boundary[boundary.length - 1] === '"')) - boundary = boundary.slice(1, boundary.length - 1); - boundary = '--' + boundary; - } else - console.error('Did not found boundary id in the response header'); + if (indx <= 0) { + console.error('Did not found boundary id in the response header - fallback to single range request'); + return send_new_request('noranges'); + } + + let boundary = hdr.slice(indx + 9); + if ((boundary[0] === '"') && (boundary.at(-1) === '"')) + boundary = boundary.slice(1, boundary.length - 1); + boundary = '--' + boundary; + + const view = new DataView(res); - while (n < last) { + for (let n = first, o = 0; n < last; n += 2) { let code1, code2 = view.getUint8(o), nline = 0, line = '', - finish_header = false, segm_start = 0, segm_last = -1; + finish_header = false, segm_start = 0, segm_last = -1; while ((o < view.byteLength - 1) && !finish_header && (nline < 5)) { code1 = code2; @@ -2899,45 +3327,46 @@ class TFile { if (parts.length === 3) { segm_start = Number.parseInt(parts[0]); segm_last = Number.parseInt(parts[1]); + // TODO: check for consistency if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) { - segm_start = 0; segm_last = -1; + segm_start = 0; + segm_last = -1; } } else console.error(`Fail to decode content-range ${line} ${parts}`); } - if ((nline > 1) && (line.length === 0)) finish_header = true; + if ((nline > 1) && !line) + finish_header = true; - nline++; line = ''; + nline++; + line = ''; if (code1 !== 10) { - o++; code2 = view.getUint8(o + 1); + o++; + code2 = view.getUint8(o + 1); } } else line += String.fromCharCode(code1); o++; } - if (!finish_header) - return rejectFunc(Error('Cannot decode header in multipart message')); + const segm_size = segm_last - segm_start + 1; - if (segm_start > segm_last) { - // fall-back solution, believe that segments same as requested - blobs.push(new DataView(res, o, place[n + 1])); - o += place[n + 1]; - n += 2; - } else { - while ((n < last) && (place[n] >= segm_start) && (place[n] + place[n + 1] - 1 <= segm_last)) { - blobs.push(new DataView(res, o + place[n] - segm_start, place[n + 1])); - n += 2; - } - - o += (segm_last - segm_start + 1); + if (!finish_header || (segm_size <= 0) || (reorder.expectedSize(n) !== segm_size)) { + console.error('Failure decoding multirange header - fallback to single range request'); + return send_new_request('noranges'); } + + reorder.addBuffer(n, res, o); + + o += segm_size; } send_new_request(true); }; + setFileUrl(); + return send_new_request(true).then(() => promise); } @@ -2958,8 +3387,10 @@ class TFile { for (let j = 0; j < this.fDirectories.length; ++j) { const dir = this.fDirectories[j]; - if (dir.dir_name !== dirname) continue; - if ((cycle !== undefined) && (dir.dir_cycle !== cycle)) continue; + if (dir.dir_name !== dirname) + continue; + if ((cycle !== undefined) && (dir.dir_cycle !== cycle)) + continue; return dir; } return null; @@ -2969,13 +3400,19 @@ class TFile { * @desc If only_direct not specified, returns Promise while key keys must be read first from the directory * @private */ getKey(keyname, cycle, only_direct) { - if (typeof cycle !== 'number') cycle = -1; + if (typeof cycle !== 'number') + cycle = -1; let bestkey = null; for (let i = 0; i < this.fKeys.length; ++i) { const key = this.fKeys[i]; - if (!key || (key.fName !== keyname)) continue; - if (key.fCycle === cycle) { bestkey = key; break; } - if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) bestkey = key; + if (!key || (key.fName !== keyname)) + continue; + if (key.fCycle === cycle) { + bestkey = key; + break; + } + if ((cycle < 0) && (!bestkey || (key.fCycle > bestkey.fCycle))) + bestkey = key; } if (bestkey) return only_direct ? bestkey : Promise.resolve(bestkey); @@ -2987,7 +3424,8 @@ class TFile { subname = keyname.slice(pos + 1), dir = this.getDir(dirname); - if (dir) return dir.getKey(subname, cycle, only_direct); + if (dir) + return dir.getKey(subname, cycle, only_direct); const dirkey = this.getKey(dirname, undefined, true); if (dirkey && !only_direct && (dirkey.fClassName.indexOf(clTDirectory) === 0)) @@ -3037,13 +3475,15 @@ class TFile { obj_name = obj_name.slice(0, pos); } - if (typeof cycle !== 'number') cycle = -1; + if (typeof cycle !== 'number') + cycle = -1; // remove leading slashes - while (obj_name.length && (obj_name[0] === '/')) obj_name = obj_name.slice(1); + while (obj_name.length && (obj_name[0] === '/')) + obj_name = obj_name.slice(1); // one uses Promises while in some cases we need to // read sub-directory to get list of keys - // in such situation calls are asynchrone + // in such situation calls are asynchronous return this.getKey(obj_name, cycle).then(key => { if ((obj_name === nameStreamerInfo) && (key.fClassName === clTList)) return this.fStreamerInfos; @@ -3052,7 +3492,8 @@ class TFile { if ((key.fClassName === clTDirectory || key.fClassName === clTDirectoryFile)) { const dir = this.getDir(obj_name, cycle); - if (dir) return dir; + if (dir) + return dir; isdir = true; } @@ -3070,8 +3511,8 @@ class TFile { buf.mapObject(1, obj); // tag object itself with id == 1 buf.classStreamer(obj, key.fClassName); - if ((key.fClassName === clTF1) || (key.fClassName === clTF2)) - return this._readFormulas(obj); + if ((key.fClassName === clTF1) || (key.fClassName === clTF12) || (key.fClassName === clTF2) || (key.fClassName === clTF3)) + return this.#readFormulas(obj); return obj; }); @@ -3080,7 +3521,7 @@ class TFile { /** @summary read formulas from the file and add them to TF1/TF2 objects * @private */ - async _readFormulas(tf1) { + async #readFormulas(tf1) { const arr = []; for (let indx = 0; indx < this.fKeys.length; ++indx) { if (this.fKeys[indx].fClassName === 'TFormula') @@ -3096,7 +3537,8 @@ class TFile { /** @summary extract streamer infos from the buffer * @private */ extractStreamerInfos(buf) { - if (!buf) return; + if (!buf) + return; const lst = {}; buf.mapObject(1, lst); @@ -3104,8 +3546,8 @@ class TFile { try { buf.classStreamer(lst, clTList); } catch (err) { - console.error('Fail extract streamer infos', err); - return; + console.error('Fail extract streamer infos', err); + return; } lst._typename = clTStreamerInfoList; @@ -3117,17 +3559,19 @@ class TFile { for (let k = 0; k < lst.arr.length; ++k) { const si = lst.arr[k]; - if (!si.fElements) continue; + if (!si.fElements) + continue; for (let l = 0; l < si.fElements.arr.length; ++l) { const elem = si.fElements.arr[l]; - if (!elem.fTypeName || !elem.fType) continue; + if (!elem.fTypeName || !elem.fType) + continue; let typ = elem.fType, typname = elem.fTypeName; if (typ >= 60) { if ((typ === kStreamer) && (elem._typename === clTStreamerSTL) && elem.fSTLtype && elem.fCtype && (elem.fCtype < 20)) { const prefix = (StlNames[elem.fSTLtype] || 'undef') + '<'; - if ((typname.indexOf(prefix) === 0) && (typname[typname.length - 1] === '>')) { + if ((typname.indexOf(prefix) === 0) && (typname.at(-1) === '>')) { typ = elem.fCtype; typname = typname.slice(prefix.length, typname.length - 1).trim(); @@ -3139,17 +3583,19 @@ class TFile { } } } - if (typ >= 60) continue; + if (typ >= 60) + continue; } else { - if ((typ > 20) && (typname[typname.length - 1] === '*')) typname = typname.slice(0, typname.length - 1); - typ = typ % 20; + if ((typ > 20) && (typname.at(-1) === '*')) + typname = typname.slice(0, typname.length - 1); + typ %= 20; } const kind = getTypeId(typname); - if (kind === typ) continue; - - if ((typ === kBits) && (kind === kUInt)) continue; - if ((typ === kCounter) && (kind === kInt)) continue; + if ((kind === typ) || + ((typ === kBits) && (kind === kUInt)) || + ((typ === kCounter) && (kind === kInt))) + continue; if (typname && typ && (this.fBasicTypes[typname] !== typ)) this.fBasicTypes[typname] = typ; @@ -3161,7 +3607,7 @@ class TFile { * @private */ async readKeys() { // with the first readbuffer we read bigger amount to create header cache - return this.readBuffer([0, 1024]).then(blob => { + return this.readBuffer([0, 400]).then(blob => { const buf = new TBuffer(blob, 0, this); if (buf.substring(0, 4) !== 'root') return Promise.reject(Error(`Not a ROOT file ${this.fURL}`)); @@ -3206,7 +3652,8 @@ class TFile { nbytes += 4; // fDatimeM.Sizeof(); nbytes += 18; // fUUID.Sizeof(); // assume that the file may be above 2 Gbytes if file version is > 4 - if (this.fVersion >= 40000) nbytes += 12; + if (this.fVersion >= 40000) + nbytes += 12; // this part typically read from the header, no need to optimize return this.readBuffer([this.fBEGIN, Math.max(300, nbytes)]); @@ -3229,7 +3676,7 @@ class TFile { }).then(blobs => { const buf4 = new TBuffer(blobs[0], 0, this); - buf4.readTKey(); // + buf4.readTKey(); const nkeys = buf4.ntoi4(); for (let i = 0; i < nkeys; ++i) this.fKeys.push(buf4.readTKey()); @@ -3242,8 +3689,8 @@ class TFile { this.fKeys.push(si_key); return this.readObjBuffer(si_key); }).then(blob6 => { - this.extractStreamerInfos(blob6); - return this; + this.extractStreamerInfos(blob6); + return this; }); } @@ -3263,28 +3710,35 @@ class TFile { * @param {number} [checksum] - streamer info checksum, have to match when specified * @private */ findStreamerInfo(clname, clversion, checksum) { - if (!this.fStreamerInfos) return null; + if (!this.fStreamerInfos) + return null; const arr = this.fStreamerInfos.arr, len = arr.length; if (checksum !== undefined) { let cache = this.fStreamerInfos.cache; - if (!cache) cache = this.fStreamerInfos.cache = {}; + if (!cache) + cache = this.fStreamerInfos.cache = {}; let si = cache[checksum]; - if (si !== undefined) return si; + if (si && (!clname || (si.fName === clname))) + return si; for (let i = 0; i < len; ++i) { si = arr[i]; if (si.fCheckSum === checksum) { cache[checksum] = si; - return si; + if (!clname || (si.fName === clname)) + return si; } } - cache[checksum] = null; // checksum didnot found, do not try again - } else { + cache[checksum] = null; // checksum did not found, do not try again + } + + if (clname) { for (let i = 0; i < len; ++i) { const si = arr[i]; - if ((si.fName === clname) && ((si.fClassVersion === clversion) || (clversion === undefined))) return si; + if ((si.fName === clname) && ((si.fClassVersion === clversion) || (clversion === undefined))) + return si; } } @@ -3294,39 +3748,41 @@ class TFile { /** @summary Returns streamer for the class 'clname', * @desc From the list of streamers or generate it from the streamer infos and add it to the list * @private */ - getStreamer(clname, ver, s_i) { + getStreamer(clname, ver, s_i, only_plain) { // these are special cases, which are handled separately - if (clname === clTQObject || clname === clTBasket) return null; + if (clname === clTQObject || clname === clTBasket) + return null; - let streamer, fullname = clname; + let fullname = clname; if (ver) { fullname += (ver.checksum ? `$chksum${ver.checksum}` : `$ver${ver.val}`); - streamer = this.fStreamers[fullname]; - if (streamer !== undefined) return streamer; + const streamer = this.fStreamers[fullname]; + if (streamer !== undefined) + return streamer; } - const custom = CustomStreamers[clname]; + const custom = only_plain ? null : CustomStreamers[clname]; // one can define in the user streamers just aliases if (isStr(custom)) return this.getStreamer(custom, ver, s_i); // streamer is just separate function - if (isFunc(custom)) { - streamer = [{ typename: clname, func: custom }]; - return addClassMethods(clname, streamer); - } + if (isFunc(custom)) + return addClassMethods(clname, [{ typename: clname, func: custom }]); - streamer = []; + const streamer = []; if (isObject(custom)) { - if (!custom.name && !custom.func) return custom; + if (!custom.name && !custom.func) + return custom; streamer.push(custom); // special read entry, add in the beginning of streamer } // check element in streamer infos, one can have special cases - if (!s_i) s_i = this.findStreamerInfo(clname, ver.val, ver.checksum); + if (!s_i) + s_i = this.findStreamerInfo(clname, ver.val, ver.checksum); if (!s_i) { delete this.fStreamers[fullname]; @@ -3336,17 +3792,15 @@ class TFile { } // special handling for TStyle which has duplicated member name fLineStyle - if ((s_i.fName === clTStyle) && s_i.fElements) { - s_i.fElements.arr.forEach(elem => { - if (elem.fName === 'fLineStyle') elem.fName = 'fLineStyles'; // like in ROOT JSON now + if (s_i.fName === clTStyle) { + s_i.fElements?.arr.forEach(elem => { + if (elem.fName === 'fLineStyle') + elem.fName = 'fLineStyles'; // like in ROOT JSON now }); } // for each entry in streamer info produce member function - if (s_i.fElements) { - for (let j = 0; j < s_i.fElements.arr.length; ++j) - streamer.push(createMemberStreamer(s_i.fElements.arr[j], this)); - } + s_i.fElements?.arr.forEach(elem => streamer.push(createMemberStreamer(elem, this))); this.fStreamers[fullname] = streamer; @@ -3356,9 +3810,11 @@ class TFile { /** @summary Here we produce list of members, resolving all base classes * @private */ getSplittedStreamer(streamer, tgt) { - if (!streamer) return tgt; + if (!streamer) + return tgt; - if (!tgt) tgt = []; + if (!tgt) + tgt = []; for (let n = 0; n < streamer.length; ++n) { const elem = streamer[n]; @@ -3374,7 +3830,8 @@ class TFile { buf.ntoi2(); // read version, why it here?? obj.fUniqueID = buf.ntou4(); obj.fBits = buf.ntou4(); - if (obj.fBits & kIsReferenced) buf.ntou2(); // skip pid + if (obj.fBits & kIsReferenced) + buf.ntou2(); // skip pid } }); continue; @@ -3388,13 +3845,14 @@ class TFile { } const parent = this.getStreamer(elem.basename, ver); - if (parent) this.getSplittedStreamer(parent, tgt); + if (parent) + this.getSplittedStreamer(parent, tgt); } return tgt; } - /** @summary Fully clenaup TFile data + /** @summary Fully cleanup TFile data * @private */ delete() { this.fDirectories = null; @@ -3410,7 +3868,7 @@ class TFile { /** @summary Reconstruct ROOT object from binary buffer * @desc Method can be used to reconstruct ROOT objects from binary buffer - * which can be requested from running THttpServer, using **root.bin** request + * which can be requested from running `THttpServer`, using **root.bin** request * To decode data, one has to request streamer infos data __after__ object data * as it shown in example. * @@ -3424,12 +3882,12 @@ class TFile { * @return {object} - created JavaScript object * @example * - * import { httpRequest } from 'http://localhost:8080/jsrootsys/modules/core.mjs'; - * import { reconstructObject } from 'http://localhost:8080/jsrootsys/modules/io.mjs'; + * import { httpRequest } from 'jsroot/core'; + * import { reconstructObject } from 'jsroot/io'; * - * let obj_data = await httpRequest('http://localhost:8080/Files/job1.root/hpx/root.bin', 'buf'); - * let si_data = await httpRequest('http://localhost:8080/StreamerInfo/root.bin', 'buf'); - * let histo = await reconstructObject('TH1F', obj_data, si_data); + * const obj_data = await httpRequest('http://localhost:8080/Files/job1.root/hpx/root.bin', 'buf'); + * const si_data = await httpRequest('http://localhost:8080/StreamerInfo/root.bin', 'buf'); + * const histo = await reconstructObject('TH1F', obj_data, si_data); * console.log(`Get histogram with title = ${histo.fTitle}`); * * // request same data via root.json request @@ -3448,123 +3906,6 @@ function reconstructObject(class_name, obj_rawdata, sinfo_rawdata) { return obj; } -/** @summary Function to read vector element in the streamer - * @private */ -function readVectorElement(buf) { - if (this.member_wise) { - const n = buf.ntou4(), ver = this.stl_version; - let streamer = null; - - if (n === 0) return []; // for empty vector no need to search split streamers - - if (n > 1000000) - throw new Error(`member-wise streaming of ${this.conttype} num ${n} member ${this.name}`); - - if ((ver.val === this.member_ver) && (ver.checksum === this.member_checksum)) - streamer = this.member_streamer; - else { - streamer = buf.fFile.getStreamer(this.conttype, ver); - - this.member_streamer = streamer = buf.fFile.getSplittedStreamer(streamer); - this.member_ver = ver.val; - this.member_checksum = ver.checksum; - } - - const res = new Array(n); - let i, k, member; - - for (i = 0; i < n; ++i) - res[i] = { _typename: this.conttype }; // create objects - if (!streamer) - console.error(`Fail to create split streamer for ${this.conttype} need to read ${n} objects version ${ver}`); - else { - for (k = 0; k < streamer.length; ++k) { - member = streamer[k]; - if (member.split_func) - member.split_func(buf, res, n); - else { - for (i = 0; i < n; ++i) - member.func(buf, res[i]); - } - } - } - return res; - } - - const n = buf.ntou4(), res = new Array(n); - let i = 0; - - if (n > 200000) { - console.error(`vector streaming for ${this.conttype} at ${n}`); - return res; - } - - if (this.arrkind > 0) - while (i < n) res[i++] = buf.readFastArray(buf.ntou4(), this.arrkind); - else if (this.arrkind === 0) - while (i < n) res[i++] = buf.readTString(); - else if (this.isptr) - while (i < n) res[i++] = buf.readObjectAny(); - else if (this.submember) - while (i < n) res[i++] = this.submember.readelem(buf); - else - while (i < n) res[i++] = buf.classStreamer({}, this.conttype); - - return res; -} - - -/** @summary Function used in streamer to read std::map object - * @private */ -function readMapElement(buf) { - let streamer = this.streamer; - - if (this.member_wise) { - // when member-wise streaming is used, version is written - const ver = this.stl_version; - - if (this.si) { - const si = buf.fFile.findStreamerInfo(this.pairtype, ver.val, ver.checksum); - - if (this.si !== si) { - streamer = getPairStreamer(si, this.pairtype, buf.fFile); - if (!streamer || streamer.length !== 2) { - console.log(`Fail to produce streamer for ${this.pairtype}`); - return null; - } - } - } - } - - const n = buf.ntoi4(), res = new Array(n); - if (this.member_wise && (buf.remain() >= 6)) { - if (buf.ntoi2() === kStreamedMemberWise) - buf.shift(4); // skip checksum - else - buf.shift(-2); // rewind - } - - for (let i = 0; i < n; ++i) { - res[i] = { _typename: this.pairtype }; - streamer[0].func(buf, res[i]); - if (!this.member_wise) streamer[1].func(buf, res[i]); - } - - // due-to member-wise streaming second element read after first is completed - if (this.member_wise) { - if (buf.remain() >= 6) { - if (buf.ntoi2() === kStreamedMemberWise) - buf.shift(4); // skip checksum - else - buf.shift(-2); // rewind - } - for (let i = 0; i < n; ++i) - streamer[1].func(buf, res[i]); - } - - return res; -} - // ============================================================= /** @@ -3593,23 +3934,31 @@ class TLocalFile extends TFile { /** @summary read buffer from local file * @return {Promise} with read data */ - async readBuffer(place, filename /*, progress_callback */) { + async readBuffer(place, filename /* , progress_callback */) { const file = this.fLocalFile; return new Promise((resolve, reject) => { - if (filename) - return reject(Error(`Cannot access other local file ${filename}`)); + if (filename) { + reject(Error(`Cannot access other local file ${filename}`)); + return; + } const reader = new FileReader(), blobs = []; let cnt = 0; reader.onload = function(evnt) { const res = new DataView(evnt.target.result); - if (place.length === 2) return resolve(res); + if (place.length === 2) { + resolve(res); + return; + } blobs.push(res); cnt += 2; - if (cnt >= place.length) return resolve(blobs); + if (cnt >= place.length) { + resolve(blobs); + return; + } reader.readAsArrayBuffer(file.slice(place[cnt], place[cnt] + place[cnt + 1])); }; @@ -3644,42 +3993,47 @@ class TNodejsFile extends TFile { return import('fs').then(fs => { this.fs = fs; - return new Promise((resolve, reject) => - + return new Promise((resolve, reject) => { this.fs.open(this.fFileName, 'r', (status, fd) => { if (status) { console.log(status.message); - return reject(Error(`Not possible to open ${this.fFileName} inside node.js`)); + reject(Error(`Not possible to open ${this.fFileName} inside node.js`)); + } else { + const stats = this.fs.fstatSync(fd); + this.fEND = stats.size; + this.fd = fd; + this.readKeys().then(resolve).catch(reject); } - const stats = this.fs.fstatSync(fd); - this.fEND = stats.size; - this.fd = fd; - this.readKeys().then(resolve).catch(reject); - }) - ); + }); + }); }); } /** @summary Read buffer from node.js file * @return {Promise} with requested blocks */ - async readBuffer(place, filename /*, progress_callback */) { + async readBuffer(place, filename /* , progress_callback */) { return new Promise((resolve, reject) => { - if (filename) - return reject(Error(`Cannot access other local file ${filename}`)); + if (filename) { + reject(Error(`Cannot access other local file ${filename}`)); + return; + } - if (!this.fs || !this.fd) - return reject(Error(`File is not opened ${this.fFileName}`)); + if (!this.fs || !this.fd) { + reject(Error(`File is not opened ${this.fFileName}`)); + return; + } const blobs = []; let cnt = 0; - // eslint-disable-next-line n/handle-callback-err const readfunc = (_err, _bytesRead, buf) => { const res = new DataView(buf.buffer, buf.byteOffset, place[cnt + 1]); - if (place.length === 2) return resolve(res); + if (place.length === 2) + return resolve(res); blobs.push(res); cnt += 2; - if (cnt >= place.length) return resolve(blobs); + if (cnt >= place.length) + return resolve(blobs); this.fs.read(this.fd, Buffer.alloc(place[cnt + 1]), 0, place[cnt + 1], place[cnt], readfunc); }; @@ -3690,7 +4044,7 @@ class TNodejsFile extends TFile { } // class TNodejsFile /** - * @summary Proxy to read file contenxt + * @summary Proxy to read file content * * @desc Should implement following methods: * @@ -3731,13 +4085,14 @@ class TProxyFile extends TFile { * @return {Promise} after file keys are read */ async _open() { return this.proxy.openFile().then(res => { - if (!res) return false; + if (!res) + return false; this.fEND = this.proxy.getFileSize(); this.fFullURL = this.fURL = this.fFileName = this.proxy.getFileName(); if (isStr(this.fFileName)) { const p = this.fFileName.lastIndexOf('/'); if ((p > 0) && (p < this.fFileName.length - 4)) - this.fFileName = this.fFileName.slice(p+1); + this.fFileName = this.fFileName.slice(p + 1); } return this.readKeys(); }); @@ -3745,19 +4100,25 @@ class TProxyFile extends TFile { /** @summary Read buffer from FileProxy * @return {Promise} with requested blocks */ - async readBuffer(place, filename /*, progress_callback */) { + async readBuffer(place, filename /* , progress_callback */) { if (filename) return Promise.reject(Error(`Cannot access other file ${filename}`)); if (!this.proxy) return Promise.reject(Error(`File is not opened ${this.fFileName}`)); + if (isFunc(this.proxy.readBuffers)) { + return this.proxy.readBuffers(place).then(arr => { + return arr?.length === 1 ? arr[0] : arr; + }); + } + if (place.length === 2) return this.proxy.readBuffer(place[0], place[1]); const arr = []; - for (let k = 0; k < place.length; k+=2) - arr.push(this.proxy.readBuffer(place[k], place[k+1])); + for (let k = 0; k < place.length; k += 2) + arr.push(this.proxy.readBuffer(place[k], place[k + 1])); return Promise.all(arr); } @@ -3772,19 +4133,22 @@ class TProxyFile extends TFile { * - [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer} instance with complete file content * - [FileProxy]{@link FileProxy} let access arbitrary files via tiny proxy API * @param {string|object} arg - argument for file open like url, see details + * @param {object} [opts] - extra arguments + * @param {Number} [opts.timeout=0] - read timeout for http requests in ms + * @param {Object} [opts.remap={}] - http server remap to fallback when main server fails, like { 'https://original.server/': 'https://fallback.server/' } * @return {object} - Promise with {@link TFile} instance when file is opened * @example * * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; * let f = await openFile('https://root.cern/js/files/hsimple.root'); * console.log(`Open file ${f.getFileName()}`); */ -function openFile(arg) { - let file; +function openFile(arg, opts) { + let file, plain_file; if (isNodeJs() && isStr(arg)) { - if (arg.indexOf('file://') === 0) + if (!arg.indexOf('file://')) file = new TNodejsFile(arg.slice(7)); - else if (arg.indexOf('http') !== 0) + else if (arg.indexOf('http')) file = new TNodejsFile(arg); } @@ -3799,12 +4163,43 @@ function openFile(arg) { if (!file && isObject(arg) && arg.size && arg.name) file = new TLocalFile(arg); - if (!file) + if (!file) { file = new TFile(arg); + plain_file = true; + file.assignRemap(settings.FilesRemap); + } + + if (opts && isObject(opts)) { + if (opts.timeout) + file.setTimeout(opts.timeout); + if (plain_file && opts.remap) + file.assignRemap(opts.remap); + } return file._open(); } +/** @summary Unzip JSON string + * @desc Should be used for buffer produced with TBufferJSON::zipJSON() method + * @param tgtsize - original length of json string + * @param src - string with data returned by TBufferJSON::zipJSON + * @return {Promise} with unzipped string */ + +async function unzipJSON(tgtsize, src) { + const bindata = atob_func(src), + buf = new ArrayBuffer(bindata.length), + bufView = new DataView(buf); + for (let i = 0; i < bindata.length; i++) + bufView.setUint8(i, bindata.charCodeAt(i)); + + return R__unzip(bufView, tgtsize).then(resView => { + let resstr = ''; + for (let i = 0; i < tgtsize; i++) + resstr += String.fromCharCode(resView.getUint8(i)); + return resstr; + }); +} + // special way to assign methods when streaming objects addClassMethods(clTNamed, CustomStreamers[clTNamed]); addClassMethods(clTObjString, CustomStreamers[clTObjString]); @@ -3816,5 +4211,5 @@ export { kChar, kShort, kInt, kLong, kFloat, kCounter, kBase, kOffsetL, kOffsetP, kObject, kAny, kObjectp, kObjectP, kTString, kAnyP, kStreamer, kStreamLoop, kSTLp, kSTL, kBaseClass, clTStreamerInfoList, clTDirectory, clTDirectoryFile, nameStreamerInfo, clTBasket, - R__unzip, addUserStreamer, createStreamerElement, createMemberStreamer, - openFile, reconstructObject, FileProxy, TBuffer }; /*, TDirectory, TFile, TLocalFile, TNodejsFile */ + R__unzip, unzipJSON, addUserStreamer, createStreamerElement, createMemberStreamer, + openFile, reconstructObject, FileProxy, TBuffer }; diff --git a/modules/main.mjs b/modules/main.mjs index ecddce21b..ce0b5dcae 100644 --- a/modules/main.mjs +++ b/modules/main.mjs @@ -1,5 +1,5 @@ -/// top module, export all major functions from JSROOT -/// Used by default in node.js +// top module, export all major functions from JSROOT +// Used by default in node.js export * from './core.mjs'; @@ -9,26 +9,40 @@ export * from './base/BasePainter.mjs'; export * from './base/ObjectPainter.mjs'; +export { getColor, extendRootColors, createRootColors } from './base/colors.mjs'; + +export { THREE } from './base/base3d.mjs'; + +export { loadMathjax } from './base/latex.mjs'; + export * from './hist/TH1Painter.mjs'; export * from './hist/TH2Painter.mjs'; export * from './hist/TH3Painter.mjs'; +export * from './hist/TGraphPainter.mjs'; + +export * from './draw/RTreeMapPainter.mjs'; + export { geoCfg } from './geom/geobase.mjs'; export { createGeoPainter, TGeoPainter } from './geom/TGeoPainter.mjs'; -export { loadOpenui5, registerForResize, setSaveFile } from './gui/utils.mjs'; +export { loadOpenui5, registerForResize, setSaveFile, addMoveHandler } from './gui/utils.mjs'; + +export { draw, redraw, cleanup, build3d, makeSVG, makeImage, addDrawFunc, setDefaultDrawOpt } from './draw.mjs'; -export { draw, redraw, makeSVG, makeImage, addDrawFunc, setDefaultDrawOpt } from './draw.mjs'; +export * from './gpad/TCanvasPainter.mjs'; -export { openFile, FileProxy } from './io.mjs'; +export { openFile, FileProxy, addUserStreamer, unzipJSON } from './io.mjs'; export * from './gui/display.mjs'; -export { HierarchyPainter, getHPainter } from './gui/HierarchyPainter.mjs'; +export * from './gui/menu.mjs'; + +export { HierarchyPainter } from './gui/HierarchyPainter.mjs'; export { readStyleFromURL, buildGUI } from './gui.mjs'; -export { TSelector, treeDraw } from './tree.mjs'; +export { TSelector, treeDraw, treeProcess } from './tree.mjs'; diff --git a/modules/rntuple.mjs b/modules/rntuple.mjs new file mode 100644 index 000000000..ee6bb0c76 --- /dev/null +++ b/modules/rntuple.mjs @@ -0,0 +1,1201 @@ +import { isStr, isObject } from './core.mjs'; +import { R__unzip } from './io.mjs'; +import { TDrawSelector, treeDraw } from './tree.mjs'; + +const LITTLE_ENDIAN = true; +class RBufferReader { + + constructor(buffer) { + if (buffer instanceof ArrayBuffer) { + this.buffer = buffer; + this.byteOffset = 0; + this.byteLength = buffer.byteLength; + } else if (ArrayBuffer.isView(buffer)) { + this.buffer = buffer.buffer; + this.byteOffset = buffer.byteOffset; + this.byteLength = buffer.byteLength; + } else + throw new TypeError('Invalid buffer type'); + + this.view = new DataView(this.buffer); + // important - offset should start from actual place in the buffer + this.offset = this.byteOffset; + } + + // Move to a specific position in the buffer + seek(position) { + if (typeof position === 'bigint') { + if (position > BigInt(Number.MAX_SAFE_INTEGER)) + throw new Error(`Offset too large to seek safely: ${position}`); + this.offset = Number(position); + } else + this.offset = position; + } + + + // Read unsigned 8-bit integer (1 BYTE) + readU8() { + const val = this.view.getUint8(this.offset); + this.offset += 1; + return val; + } + + // Read unsigned 16-bit integer (2 BYTES) + readU16() { + const val = this.view.getUint16(this.offset, LITTLE_ENDIAN); + this.offset += 2; + return val; + } + + // Read unsigned 32-bit integer (4 BYTES) + readU32() { + const val = this.view.getUint32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } + + // Read signed 8-bit integer (1 BYTE) + readS8() { + const val = this.view.getInt8(this.offset); + this.offset += 1; + return val; + } + + // Read signed 16-bit integer (2 BYTES) + readS16() { + const val = this.view.getInt16(this.offset, LITTLE_ENDIAN); + this.offset += 2; + return val; + } + + // Read signed 32-bit integer (4 BYTES) + readS32() { + const val = this.view.getInt32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } + + // Read 32-bit float (4 BYTES) + readF32() { + const val = this.view.getFloat32(this.offset, LITTLE_ENDIAN); + this.offset += 4; + return val; + } + + // Read 64-bit float (8 BYTES) + readF64() { + const val = this.view.getFloat64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } + + // Read a string with 32-bit length prefix + readString() { + const length = this.readU32(); + let str = ''; + for (let i = 0; i < length; i++) + str += String.fromCharCode(this.readU8()); + return str; + } + + // Read unsigned 64-bit integer (8 BYTES) + readU64() { + const val = this.view.getBigUint64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } + + // Read signed 64-bit integer (8 BYTES) + readS64() { + const val = this.view.getBigInt64(this.offset, LITTLE_ENDIAN); + this.offset += 8; + return val; + } + +} + +const ENTupleColumnType = { + kBit: 0x00, + kByte: 0x01, + kChar: 0x02, + kInt8: 0x03, + kUInt8: 0x04, + kInt16: 0x05, + kUInt16: 0x06, + kInt32: 0x07, + kUInt32: 0x08, + kInt64: 0x09, + kUInt64: 0x0A, + kReal16: 0x0B, + kReal32: 0x0C, + kReal64: 0x0D, + kIndex32: 0x0E, + kIndex64: 0x0F, + kSwitch: 0x10, + kSplitInt16: 0x11, + kSplitUInt16: 0x12, + kSplitInt32: 0x13, + kSplitUInt32: 0x14, + kSplitInt64: 0x15, + kSplitUInt64: 0x16, + kSplitReal16: 0x17, + kSplitReal32: 0x18, + kSplitReal64: 0x19, + kSplitIndex32: 0x1A, + kSplitIndex64: 0x1B, + kReal32Trunc: 0x1C, + kReal32Quant: 0x1D +}; + + +/** + * @summary Rearrange bytes from split format to normal format (row-wise) for decoding + */ +function recontructUnsplitBuffer(blob, columnDescriptor) { + const { coltype } = columnDescriptor; + + if ( + coltype === ENTupleColumnType.kSplitUInt16 || + coltype === ENTupleColumnType.kSplitUInt32 || + coltype === ENTupleColumnType.kSplitUInt64 || + coltype === ENTupleColumnType.kSplitReal16 || + coltype === ENTupleColumnType.kSplitReal32 || + coltype === ENTupleColumnType.kSplitReal64 || + coltype === ENTupleColumnType.kSplitIndex32 || + coltype === ENTupleColumnType.kSplitIndex64 || + coltype === ENTupleColumnType.kSplitInt16 || + coltype === ENTupleColumnType.kSplitInt32 || + coltype === ENTupleColumnType.kSplitInt64 + ) { + // Determine byte size based on column type + let byteSize; + switch (coltype) { + case ENTupleColumnType.kSplitReal64: + case ENTupleColumnType.kSplitInt64: + case ENTupleColumnType.kSplitUInt64: + case ENTupleColumnType.kSplitIndex64: + byteSize = 8; + break; + case ENTupleColumnType.kSplitReal32: + case ENTupleColumnType.kSplitInt32: + case ENTupleColumnType.kSplitIndex32: + case ENTupleColumnType.kSplitUInt32: + byteSize = 4; + break; + case ENTupleColumnType.kSplitInt16: + case ENTupleColumnType.kSplitUInt16: + case ENTupleColumnType.kSplitReal16: + byteSize = 2; + break; + default: + throw new Error(`Unsupported split coltype: ${coltype} (0x${coltype.toString(16).padStart(2, '0')})`); + } + + const splitView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), + count = blob.byteLength / byteSize, + outBuffer = new ArrayBuffer(blob.byteLength), + outBytes = new Uint8Array(outBuffer); + + for (let i = 0; i < count; ++i) { + for (let b = 0; b < byteSize; ++b) { + const splitIndex = b * count + i, + byte = splitView.getUint8(splitIndex), + writeIndex = i * byteSize + b; + outBytes[writeIndex] = byte; + } + } + + // Return updated blob and remapped coltype + const newBlob = outBuffer; + let newColtype; + switch (coltype) { + case ENTupleColumnType.kSplitUInt16: + newColtype = ENTupleColumnType.kUInt16; + break; + case ENTupleColumnType.kSplitUInt32: + newColtype = ENTupleColumnType.kUInt32; + break; + case ENTupleColumnType.kSplitUInt64: + newColtype = ENTupleColumnType.kUInt64; + break; + case ENTupleColumnType.kSplitIndex32: + newColtype = ENTupleColumnType.kIndex32; + break; + case ENTupleColumnType.kSplitIndex64: + newColtype = ENTupleColumnType.kIndex64; + break; + case ENTupleColumnType.kSplitReal16: + newColtype = ENTupleColumnType.kReal16; + break; + case ENTupleColumnType.kSplitReal32: + newColtype = ENTupleColumnType.kReal32; + break; + case ENTupleColumnType.kSplitReal64: + newColtype = ENTupleColumnType.kReal64; + break; + case ENTupleColumnType.kSplitInt16: + newColtype = ENTupleColumnType.kInt16; + break; + case ENTupleColumnType.kSplitInt32: + newColtype = ENTupleColumnType.kInt32; + break; + case ENTupleColumnType.kSplitInt64: + newColtype = ENTupleColumnType.kInt64; + break; + default: + throw new Error(`Unsupported split coltype for reassembly: ${coltype}`); + } + + return { blob: newBlob, coltype: newColtype }; + } + + // If no split type, return original blob and coltype + return { blob, coltype }; +} + + +/** + * @summary Decode a reconstructed index buffer (32- or 64-bit deltas to absolute indices) + */ +function DecodeDeltaIndex(blob, coltype) { + let deltas, result; + + if (coltype === ENTupleColumnType.kIndex32) { + deltas = new Int32Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 4); + result = new Int32Array(deltas.length); + } else if (coltype === ENTupleColumnType.kIndex64) { + deltas = new BigInt64Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 8); + result = new BigInt64Array(deltas.length); + } else + throw new Error(`DecodeDeltaIndex: unsupported column type ${coltype}`); + + if (deltas.length > 0) + result[0] = deltas[0]; + for (let i = 1; i < deltas.length; ++i) + result[i] = result[i - 1] + deltas[i]; + + return { blob: result, coltype }; +} + +/** + * @summary Decode a reconstructed signed integer buffer using ZigZag encoding + */ +function decodeZigzag(blob, coltype) { + let zigzag, result; + + if (coltype === ENTupleColumnType.kInt16) { + zigzag = new Uint16Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 2); + result = new Int16Array(zigzag.length); + } else if (coltype === ENTupleColumnType.kInt32) { + zigzag = new Uint32Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 4); + result = new Int32Array(zigzag.length); + } else if (coltype === ENTupleColumnType.kInt64) { + zigzag = new BigUint64Array(blob.buffer || blob, blob.byteOffset || 0, blob.byteLength / 8); + result = new BigInt64Array(zigzag.length); + } else + throw new Error(`decodeZigzag: unsupported column type ${coltype}`); + + for (let i = 0; i < zigzag.length; ++i) { + // ZigZag decode: (x >>> 1) ^ (-(x & 1)) + const x = zigzag[i]; + result[i] = (x >>> 1) ^ (-(x & 1)); + } + + return { blob: result, coltype }; +} + +// Envelope Types +// TODO: Define usage logic for envelope types in future +// const kEnvelopeTypeHeader = 0x01, +// kEnvelopeTypeFooter = 0x02, +// kEnvelopeTypePageList = 0x03, + +// Field Flags +const kFlagRepetitiveField = 0x01, + kFlagProjectedField = 0x02, + kFlagHasTypeChecksum = 0x04, + + // Column Flags + kFlagDeferredColumn = 0x01, + kFlagHasValueRange = 0x02; + +class RNTupleDescriptorBuilder { + + deserializeHeader(header_blob) { + if (!header_blob) + return; + + const reader = new RBufferReader(header_blob), + + payloadStart = reader.offset, + // Read the envelope metadata + { + envelopeLength + } = this._readEnvelopeMetadata(reader), + + // Seek to end of envelope to get checksum + checksumPos = payloadStart + envelopeLength - 8, + currentPos = reader.offset; + + reader.seek(checksumPos); + this.headerEnvelopeChecksum = reader.readU64(); + + reader.seek(currentPos); + + // Read feature flags list (may span multiple 64-bit words) + this._readFeatureFlags(reader); + + // Read metadata strings + this.name = reader.readString(); + this.description = reader.readString(); + this.writer = reader.readString(); + + // 4 list frames inside the header envelope + this._readSchemaDescription(reader); + } + + deserializeFooter(footer_blob) { + if (!footer_blob) + return; + + const reader = new RBufferReader(footer_blob); + + // Read the envelope metadata + this._readEnvelopeMetadata(reader); + + + // Feature flag(32 bits) + this._readFeatureFlags(reader); + // Header checksum (64-bit xxhash3) + const headerChecksumFromFooter = reader.readU64(); + if (headerChecksumFromFooter !== this.headerEnvelopeChecksum) + throw new Error('RNTuple corrupted: header checksum does not match footer checksum.'); + + const schemaExtensionSize = reader.readS64(); + + if (schemaExtensionSize < 0) + throw new Error('Schema extension frame is not a record frame, which is unexpected.'); + + // Schema extension record frame (4 list frames inside) + this._readSchemaDescription(reader); + + // Cluster Group record frame + this._readClusterGroups(reader); + } + + + _readEnvelopeMetadata(reader) { + const typeAndLength = reader.readU64(), + + // Envelope metadata + // The 16 bits are the envelope type ID, and the 48 bits are the envelope length + envelopeType = Number(typeAndLength & 0xFFFFn), + envelopeLength = Number((typeAndLength >> 16n) & 0xFFFFFFFFFFFFn); + + return { + envelopeType, + envelopeLength + }; + } + + _readSchemaDescription(reader) { + // Reading new descriptor arrays from the input + const newFields = this._readFieldDescriptors(reader), + newColumns = this._readColumnDescriptors(reader), + newAliases = this._readAliasColumn(reader), + newExtra = this._readExtraTypeInformation(reader); + + // Merging these new arrays into existing arrays + this.fieldDescriptors = (this.fieldDescriptors || []).concat(newFields); + this.columnDescriptors = (this.columnDescriptors || []).concat(newColumns); + this.aliasColumns = (this.aliasColumns || []).concat(newAliases); + this.extraTypeInfo = (this.extraTypeInfo || []).concat(newExtra); + } + + + _readFeatureFlags(reader) { + this.featureFlags = []; + while (true) { + const val = reader.readU64(); + this.featureFlags.push(val); + if ((val & 0x8000000000000000n) === 0n) + break; // MSB not set: end of list + } + + // verify all feature flags are zero + if (this.featureFlags.some(v => v !== 0n)) + throw new Error('Unexpected non-zero feature flags: ' + this.featureFlags); + } + + _readFieldDescriptors(reader) { + const startOffset = BigInt(reader.offset), + fieldListSize = reader.readS64(), // signed 64-bit + fieldListIsList = fieldListSize < 0; + + + if (!fieldListIsList) + throw new Error('Field list frame is not a list frame, which is required.'); + + const fieldListCount = reader.readU32(), // number of field entries + // List frame: list of field record frames + + fieldDescriptors = []; + for (let i = 0; i < fieldListCount; ++i) { + const recordStart = BigInt(reader.offset), + fieldRecordSize = reader.readS64(), + fieldVersion = reader.readU32(), + typeVersion = reader.readU32(), + parentFieldId = reader.readU32(), + structRole = reader.readU16(), + flags = reader.readU16(), + + fieldName = reader.readString(), + typeName = reader.readString(), + typeAlias = reader.readString(), + description = reader.readString(); + let arraySize = null, + sourceFieldId = null, + checksum = null; + + if (flags & kFlagRepetitiveField) + arraySize = reader.readU64(); + + if (flags & kFlagProjectedField) + sourceFieldId = reader.readU32(); + + if (flags & kFlagHasTypeChecksum) + checksum = reader.readU32(); + + + fieldDescriptors.push({ + fieldVersion, + typeVersion, + parentFieldId, + structRole, + flags, + fieldName, + typeName, + typeAlias, + description, + arraySize, + sourceFieldId, + checksum + }); + reader.seek(Number(recordStart + fieldRecordSize)); + } + reader.seek(Number(startOffset - fieldListSize)); + return fieldDescriptors; + } + + _readColumnDescriptors(reader) { + const startOffset = BigInt(reader.offset), + columnListSize = reader.readS64(), + columnListIsList = columnListSize < 0; + if (!columnListIsList) + throw new Error('Column list frame is not a list frame, which is required.'); + const columnListCount = reader.readU32(), // number of column entries + columnDescriptors = []; + for (let i = 0; i < columnListCount; ++i) { + const recordStart = BigInt(reader.offset), + columnRecordSize = reader.readS64(), + coltype = reader.readU16(), + bitsOnStorage = reader.readU16(), + fieldId = reader.readU32(), + flags = reader.readU16(), + representationIndex = reader.readU16(); + let firstElementIndex = null, + minValue = null, + maxValue = null; + + if (flags & kFlagDeferredColumn) + firstElementIndex = reader.readU64(); + + if (flags & kFlagHasValueRange) { + minValue = reader.readF64(); + maxValue = reader.readF64(); + } + + + const column = { + coltype, + bitsOnStorage, + fieldId, + flags, + representationIndex, + firstElementIndex, + minValue, + maxValue, + index: i + }; + column.isDeferred = function() { + return (this.flags & RNTupleDescriptorBuilder.kFlagDeferredColumn) !== 0; + }; + column.isSuppressed = function() { + return this.firstElementIndex !== null && this.firstElementIndex < 0; + }; + + columnDescriptors.push(column); + reader.seek(Number(recordStart + columnRecordSize)); + } + reader.seek(Number(startOffset - columnListSize)); + return columnDescriptors; + } + _readAliasColumn(reader) { + const startOffset = BigInt(reader.offset), + aliasColumnListSize = reader.readS64(), + aliasListisList = aliasColumnListSize < 0; + if (!aliasListisList) + throw new Error('Alias column list frame is not a list frame, which is required.'); + const aliasColumnCount = reader.readU32(), // number of alias column entries + aliasColumns = []; + for (let i = 0; i < aliasColumnCount; ++i) { + const recordStart = BigInt(reader.offset), + aliasColumnRecordSize = reader.readS64(), + physicalColumnId = reader.readU32(), + fieldId = reader.readU32(); + aliasColumns.push({ + physicalColumnId, + fieldId + }); + reader.seek(Number(recordStart + aliasColumnRecordSize)); + } + reader.seek(Number(startOffset - aliasColumnListSize)); + return aliasColumns; + } + _readExtraTypeInformation(reader) { + const startOffset = BigInt(reader.offset), + extraTypeInfoListSize = reader.readS64(), + isList = extraTypeInfoListSize < 0; + + if (!isList) + throw new Error('Extra type info frame is not a list frame, which is required.'); + + const entryCount = reader.readU32(), + + extraTypeInfo = []; + for (let i = 0; i < entryCount; ++i) { + const recordStart = BigInt(reader.offset), + extraTypeInfoRecordSize = reader.readS64(), + contentId = reader.readU32(), + typeVersion = reader.readU32(); + extraTypeInfo.push({ + contentId, + typeVersion + }); + reader.seek(Number(recordStart + extraTypeInfoRecordSize)); + } + reader.seek(Number(startOffset - extraTypeInfoListSize)); + return extraTypeInfo; + } + _readClusterGroups(reader) { + const startOffset = BigInt(reader.offset), + clusterGroupListSize = reader.readS64(), + isList = clusterGroupListSize < 0; + if (!isList) + throw new Error('Cluster group frame is not a list frame'); + + const groupCount = reader.readU32(), + + clusterGroups = []; + + for (let i = 0; i < groupCount; ++i) { + const recordStart = BigInt(reader.offset), + clusterRecordSize = reader.readS64(), + minEntry = reader.readU64(), + entrySpan = reader.readU64(), + numClusters = reader.readU32(), + pageListLength = reader.readU64(), + + + // Locator method to get the page list locator offset + pageListLocator = this._readLocator(reader), + + + group = { + minEntry, + entrySpan, + numClusters, + pageListLocator, + pageListLength + }; + clusterGroups.push(group); + reader.seek(Number(recordStart + clusterRecordSize)); + } + reader.seek(Number(startOffset - clusterGroupListSize)); + this.clusterGroups = clusterGroups; + } + + _readLocator(reader) { + const sizeAndType = reader.readU32(); // 4 bytes: size + T bit + if ((sizeAndType | 0) < 0) // | makes the sizeAndType as signed + throw new Error('Non-standard locators (T=1) not supported yet'); + const size = sizeAndType, + offset = reader.readU64(); // 8 bytes: offset + return { + size, + offset + }; + } + deserializePageList(page_list_blob) { + if (!page_list_blob) + throw new Error('deserializePageList: received an invalid or empty page list blob'); + + const reader = new RBufferReader(page_list_blob); + this._readEnvelopeMetadata(reader); + // Page list checksum (64-bit xxhash3) + const pageListHeaderChecksum = reader.readU64(); + if (pageListHeaderChecksum !== this.headerEnvelopeChecksum) + throw new Error('RNTuple corrupted: header checksum does not match Page List Header checksum.'); + + const listStartOffset = BigInt(reader.offset), + // Read cluster summaries list frame + clusterSummaryListSize = reader.readS64(); + if (clusterSummaryListSize >= 0) + throw new Error('Expected a list frame for cluster summaries'); + const clusterSummaryCount = reader.readU32(), + + clusterSummaries = []; + + for (let i = 0; i < clusterSummaryCount; ++i) { + const recordStart = BigInt(reader.offset), + clusterSummaryRecordSize = reader.readS64(), + firstEntry = reader.readU64(), + combined = reader.readU64(), + flags = combined >> 56n; + if (flags & 0x01n) + throw new Error('Cluster summary uses unsupported sharded flag (0x01)'); + const numEntries = Number(combined & 0x00FFFFFFFFFFFFFFn); + clusterSummaries.push({ + firstEntry, + numEntries, + flags + }); + reader.seek(Number(recordStart + clusterSummaryRecordSize)); + } + reader.seek(Number(listStartOffset - clusterSummaryListSize)); + this.clusterSummaries = clusterSummaries; + this._readNestedFrames(reader); + + /* const checksumPagelist = */ reader.readU64(); + } + + _readNestedFrames(reader) { + const clusterPageLocations = [], + numListClusters = reader.readS64(); + if (numListClusters >= 0) + throw new Error('Expected list frame for clusters'); + const numRecordCluster = reader.readU32(); + + for (let i = 0; i < numRecordCluster; ++i) { + const outerListSize = reader.readS64(); + if (outerListSize >= 0) + throw new Error('Expected outer list frame for columns'); + + const numColumns = reader.readU32(), + columns = []; + + for (let c = 0; c < numColumns; ++c) { + const innerListSize = reader.readS64(); + if (innerListSize >= 0) + throw new Error('Expected inner list frame for pages'); + + const numPages = reader.readU32(), + pages = []; + + for (let p = 0; p < numPages; ++p) { + const numElementsWithBit = reader.readS32(), + hasChecksum = numElementsWithBit < 0, + numElements = BigInt(Math.abs(Number(numElementsWithBit))), + + locator = this._readLocator(reader); + pages.push({ + numElements, + hasChecksum, + locator + }); + } + + const elementOffset = reader.readS64(), + isSuppressed = elementOffset < 0; + + let compression = null; + if (!isSuppressed) + compression = reader.readU32(); + + columns.push({ + pages, + elementOffset, + isSuppressed, + compression + }); + } + + clusterPageLocations.push(columns); + } + + this.pageLocations = clusterPageLocations; + } + + // Example Of Deserializing Page Content + deserializePage(blob, columnDescriptor, pageInfo) { + const originalColtype = columnDescriptor.coltype, + { + coltype + } = recontructUnsplitBuffer(blob, columnDescriptor); + let { + blob: processedBlob + } = recontructUnsplitBuffer(blob, columnDescriptor); + + + // Handle split index types + if (originalColtype === ENTupleColumnType.kSplitIndex32 || originalColtype === ENTupleColumnType.kSplitIndex64) { + const { + blob: decodedArray + } = DecodeDeltaIndex(processedBlob, coltype); + processedBlob = decodedArray; + } + + // Handle Split Signed Int types + if (originalColtype === ENTupleColumnType.kSplitInt16 || originalColtype === ENTupleColumnType.kSplitInt32 || originalColtype === ENTupleColumnType.kSplitInt64) { + const { + blob: decodedArray + } = decodeZigzag(processedBlob, coltype); + processedBlob = decodedArray; + } + + const reader = new RBufferReader(processedBlob), + values = [], + + // Use numElements from pageInfo parameter + numValues = Number(pageInfo.numElements), + // Helper for all simple types + extractValues = (readFunc) => { + for (let i = 0; i < numValues; ++i) + values.push(readFunc()); + }; + switch (coltype) { + case ENTupleColumnType.kBit: { + let bitCount = 0; + const totalBitsInBuffer = processedBlob.byteLength * 8; + if (totalBitsInBuffer < numValues) + throw new Error(`kBit: Not enough bits in buffer (${totalBitsInBuffer}) for numValues (${numValues})`); + + for (let byteIndex = 0; byteIndex < processedBlob.byteLength; ++byteIndex) { + const byte = reader.readU8(); + + // Extract 8 bits from this byte + for (let bitPos = 0; bitPos < 8 && bitCount < numValues; ++bitPos, ++bitCount) { + const bitValue = (byte >>> bitPos) & 1, + boolValue = bitValue === 1; + values.push(boolValue); + } + } + break; + } + + case ENTupleColumnType.kReal64: + extractValues(reader.readF64.bind(reader)); + break; + case ENTupleColumnType.kReal32: + extractValues(reader.readF32.bind(reader)); + break; + case ENTupleColumnType.kInt64: + extractValues(reader.readS64.bind(reader)); + break; + case ENTupleColumnType.kUInt64: + extractValues(reader.readU64.bind(reader)); + break; + case ENTupleColumnType.kInt32: + extractValues(reader.readS32.bind(reader)); + break; + case ENTupleColumnType.kUInt32: + extractValues(reader.readU32.bind(reader)); + break; + case ENTupleColumnType.kInt16: + extractValues(reader.readS16.bind(reader)); + break; + case ENTupleColumnType.kUInt16: + extractValues(reader.readU16.bind(reader)); + break; + case ENTupleColumnType.kInt8: + extractValues(reader.readS8.bind(reader)); + break; + case ENTupleColumnType.kUInt8: + case ENTupleColumnType.kByte: + extractValues(reader.readU8.bind(reader)); + break; + case ENTupleColumnType.kChar: + extractValues(() => String.fromCharCode(reader.readS8())); + break; + case ENTupleColumnType.kIndex32: + extractValues(reader.readS32.bind(reader)); + break; + case ENTupleColumnType.kIndex64: + extractValues(reader.readS64.bind(reader)); + break; + default: + throw new Error(`Unsupported column type: ${columnDescriptor.coltype}`); + } + return values; + } + +} // class RNTupleDescriptorBuilder + + +/** @summary Very preliminary function to read header/footer from RNTuple + * @private */ +async function readHeaderFooter(tuple) { + // if already read - return immediately, make possible to call several times + if (tuple?.builder) + return true; + + if (!tuple.$file) + return false; + + // request header and footer buffers from the file + return tuple.$file.readBuffer([tuple.fSeekHeader, tuple.fNBytesHeader, tuple.fSeekFooter, tuple.fNBytesFooter]).then(blobs => { + if (blobs?.length !== 2) + return false; + + // Handle both compressed and uncompressed cases + const processBlob = (blob, uncompressedSize) => { + // If uncompressedSize matches blob size, it's uncompressed + if (blob.byteLength === uncompressedSize) + return Promise.resolve(blob); + return R__unzip(blob, uncompressedSize); + }; + + return Promise.all([ + processBlob(blobs[0], tuple.fLenHeader), + processBlob(blobs[1], tuple.fLenFooter) + ]).then(unzip_blobs => { + const [header_blob, footer_blob] = unzip_blobs; + if (!header_blob || !footer_blob) + return false; + + tuple.builder = new RNTupleDescriptorBuilder; + tuple.builder.deserializeHeader(header_blob); + tuple.builder.deserializeFooter(footer_blob); + + // Build fieldToColumns mapping + tuple.fieldToColumns = {}; + for (const colDesc of tuple.builder.columnDescriptors) { + const fieldDesc = tuple.builder.fieldDescriptors[colDesc.fieldId], + fieldName = fieldDesc.fieldName; + if (!tuple.fieldToColumns[fieldName]) + tuple.fieldToColumns[fieldName] = []; + tuple.fieldToColumns[fieldName].push(colDesc); + } + + // Deserialize Page List + const group = tuple.builder.clusterGroups?.[0]; + if (!group || !group.pageListLocator) + throw new Error('No valid cluster group or page list locator found'); + + const offset = Number(group.pageListLocator.offset), + size = Number(group.pageListLocator.size), + uncompressedSize = Number(group.pageListLength); + + return tuple.$file.readBuffer([offset, size]).then(page_list_blob => { + if (!(page_list_blob instanceof DataView)) + throw new Error(`Expected DataView from readBuffer, got ${Object.prototype.toString.call(page_list_blob)}`); + + // Check if page list data is uncompressed + if (page_list_blob.byteLength === uncompressedSize) { + // Data is uncompressed, use directly + tuple.builder.deserializePageList(page_list_blob); + return true; + } + // Attempt to decompress the page list + return R__unzip(page_list_blob, uncompressedSize).then(unzipped_blob => { + if (!(unzipped_blob instanceof DataView)) + throw new Error(`Unzipped page list is not a DataView, got ${Object.prototype.toString.call(unzipped_blob)}`); + + tuple.builder.deserializePageList(unzipped_blob); + return true; + }); + }); + }); + }).catch(err => { + console.error('Error during readHeaderFooter execution:', err); + throw err; + }); +} + +function readEntry(rntuple, fieldName, entryIndex) { + const builder = rntuple.builder, + field = builder.fieldDescriptors.find(f => f.fieldName === fieldName), + fieldData = rntuple._clusterData[fieldName]; + + if (!field) + throw new Error(`No descriptor for field ${fieldName}`); + if (!fieldData) + throw new Error(`No data for field ${fieldName}`); + + // Detect and decode string fields + if (Array.isArray(fieldData) && fieldData.length === 2) { + const [offsets, payload] = fieldData, + start = entryIndex === 0 ? 0 : Number(offsets[entryIndex - 1]), + end = Number(offsets[entryIndex]), + decoded = payload.slice(start, end).join(''); // Convert to string + return decoded; + } + + // Fallback: primitive type (e.g. int, float) + return fieldData[0][entryIndex]; +} + +/** @summary Return field name for specified branch index + * @desc API let use field name in selector or field object itself */ +function getSelectorFieldName(selector, i) { + const br = selector.getBranch(i); + return isStr(br) ? br : br?.fieldName; +} + +// Read and process the next data cluster from the RNTuple +function readNextCluster(rntuple, selector) { + const builder = rntuple.builder; + + // Add validation + if (!builder.clusterSummaries || builder.clusterSummaries.length === 0) + throw new Error('No cluster summaries available - possibly incomplete file reading'); + + const clusterIndex = selector.currentCluster, + clusterSummary = builder.clusterSummaries[clusterIndex], + // Gather all pages for this cluster from selected fields only + pages = [], + // Collect only selected field names from selector + selectedFields = []; + + for (let i = 0; i < selector.numBranches(); ++i) + selectedFields.push(getSelectorFieldName(selector, i)); + + // For each selected field, collect its columns' pages + for (const fieldName of selectedFields) { + const columns = rntuple.fieldToColumns[fieldName]; + if (!columns) + throw new Error(`Selected field '${fieldName}' not found in RNTuple`); + + for (const colDesc of columns) { + const colEntry = builder.pageLocations[clusterIndex]?.[colDesc.index]; + + // When the data is missing or broken + if (!colEntry || !colEntry.pages) + throw new Error(`No pages for column ${colDesc.index} in cluster ${clusterIndex}`); + + for (const page of colEntry.pages) + pages.push({ page, colDesc, fieldName }); + } + } + + selector.currentCluster++; + + // Early exit if no pages to read (i.e., no selected fields matched) + if (pages.length === 0) { + selector.Terminate(false); + return Promise.resolve(); + } + + // Build flat array of [offset, size, offset, size, ...] to read pages + const dataToRead = pages.flatMap(p => + [Number(p.page.locator.offset), Number(p.page.locator.size)] + ); + + return rntuple.$file.readBuffer(dataToRead).then(blobsRaw => { + const blobs = Array.isArray(blobsRaw) ? blobsRaw : [blobsRaw], + unzipPromises = blobs.map((blob, idx) => { + const { page, colDesc } = pages[idx], + colEntry = builder.pageLocations[clusterIndex][colDesc.index], // Access column entry + numElements = Number(page.numElements), + elementSize = colDesc.bitsOnStorage / 8; + + // Check if data is compressed + if (colEntry.compression === 0) + return Promise.resolve(blob); // Uncompressed: use blob directly + const expectedSize = numElements * elementSize; + + // Special handling for boolean fields + if (colDesc.coltype === ENTupleColumnType.kBit) { + const expectedBoolSize = Math.ceil(numElements / 8); + if (blob.byteLength === expectedBoolSize) + return Promise.resolve(blob); + // Try decompression but catch errors for boolean fields + return R__unzip(blob, expectedBoolSize).catch(err => { + throw new Error(`Failed to unzip boolean page ${idx}: ${err.message}`); + }); + } + + // If the blob is already the expected size, treat as uncompressed + if (blob.byteLength === expectedSize) + return Promise.resolve(blob); + + // Try decompression + return R__unzip(blob, expectedSize).then(result => { + if (!result) + return blob; // Fallback to original blob + return result; + }).catch(err => { + throw new Error(`Failed to unzip page ${idx}: ${err.message}`); + }); + }); + + return Promise.all(unzipPromises).then(unzipBlobs => { + rntuple._clusterData = {}; // store deserialized data per field + + for (let i = 0; i < unzipBlobs.length; ++i) { + const blob = unzipBlobs[i]; + // Ensure blob is a DataView + if (!(blob instanceof DataView)) + throw new Error(`Invalid blob type for page ${i}: ${Object.prototype.toString.call(blob)}`); + const { + page, + colDesc + } = pages[i], + field = builder.fieldDescriptors[colDesc.fieldId], + values = builder.deserializePage(blob, colDesc, page); + + // Support multiple representations (e.g., string fields with offsets + payload) + if (!rntuple._clusterData[field.fieldName]) + rntuple._clusterData[field.fieldName] = []; + + // splitting string fields into offset and payload components + if (field.typeName === 'std::string') { + if ( + colDesc.coltype === ENTupleColumnType.kIndex64 || + colDesc.coltype === ENTupleColumnType.kIndex32 || + colDesc.coltype === ENTupleColumnType.kSplitIndex64 || + colDesc.coltype === ENTupleColumnType.kSplitIndex32 + ) // Index64/Index32 + rntuple._clusterData[field.fieldName][0] = values; // Offsets + else if (colDesc.coltype === ENTupleColumnType.kChar) + rntuple._clusterData[field.fieldName][1] = values; // Payload + else + throw new Error(`Unsupported column type for string field: ${colDesc.coltype}`); + } else + rntuple._clusterData[field.fieldName][0] = values; + } + + // Ensure string fields have ending offset for proper reconstruction of the last entry + for (const fieldName of selectedFields) { + const field = builder.fieldDescriptors.find(f => f.fieldName === fieldName), + colData = rntuple._clusterData[fieldName]; + if (field.typeName === 'std::string') { + if (!Array.isArray(colData) || colData.length !== 2) + throw new Error(`String field '${fieldName}' must have 2 columns`); + if (colData[0].length !== builder.clusterSummaries[clusterIndex].numEntries) + throw new Error(`Malformed string field '${fieldName}': missing final offset`); + } + } + + const numEntries = clusterSummary.numEntries; + for (let i = 0; i < numEntries; ++i) { + for (let b = 0; b < selector.numBranches(); ++b) { + const fieldName = getSelectorFieldName(selector, b), + tgtName = selector.nameOfBranch(b), + values = rntuple._clusterData[fieldName]; + + if (!values) + throw new Error(`Missing values for selected field: ${fieldName}`); + selector.tgtobj[tgtName] = readEntry(rntuple, fieldName, i); + } + selector.Process(); + } + + selector.Terminate(true); + }); + }); +} + +// TODO args can later be used to filter fields, limit entries, etc. +// Create reader and deserialize doubles from the buffer +function rntupleProcess(rntuple, selector, args) { + return readHeaderFooter(rntuple).then(() => { + selector.Begin(); + selector.currentCluster = 0; + return readNextCluster(rntuple, selector, args); + }).then(() => selector); +} + +class TDrawSelectorTuple extends TDrawSelector { + + /** @summary Return total number of entries + * @desc TODO: check implementation details ! */ + getNumEntries(tuple) { + let cnt = 0; + tuple?.builder.clusterSummaries.forEach(summary => { cnt += summary.numEntries; }); + return cnt; + } + + /** @summary Search for field in tuple + * @desc TODO: Can be more complex when name includes extra parts referencing member or collection size or more */ + findBranch(tuple, name) { + return tuple.builder?.fieldDescriptors.find(field => { + return field.fieldName === name; + }); + } + + /** @summary Returns true if field can be used as array */ + isArrayBranch(/* tuple, br */) { return false; } + +} // class TDrawSelectorTuple + + +/** @summary implementation of drawing for RNTuple + * @param {object|string} args - different setting or simply draw expression + * @param {string} args.expr - draw expression + * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') + * @param {string} [args.drawopt=undefined] - draw options for result histogram + * @param {number} [args.firstentry=0] - first entry to process + * @param {number} [args.numentries=undefined] - number of entries to process, all by default + * @param {Array} [args.elist=undefined] - array of entries id to process, all by default + * @param {boolean} [args.staged] - staged processing, first apply cut to select entries and then perform drawing for selected entries + * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing + * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument + * @return {Promise} with produced object */ + +async function rntupleDraw(rntuple, args) { + if (isStr(args)) + args = { expr: args }; + else if (!isObject(args)) + args = {}; + + args.SelectorClass = TDrawSelectorTuple; + args.processFunction = rntupleProcess; + + return readHeaderFooter(rntuple).then(res_header_footer => { + return res_header_footer ? treeDraw(rntuple, args) : null; + }); +} + + +/** @summary Create hierarchy of ROOT::RNTuple object + * @desc Used by hierarchy painter to explore sub-elements + * @private */ +async function tupleHierarchy(tuple_node, tuple) { + tuple_node._childs = []; + // tuple_node._tuple = tuple; // set reference, will be used later by RNTuple::Draw + + return readHeaderFooter(tuple).then(res => { + if (!res) + return res; + + tuple.builder?.fieldDescriptors.forEach(field => { + const item = { + _name: field.fieldName, + _typename: 'ROOT::RNTupleField', // pseudo class name, used in draw.mjs + _kind: 'ROOT::RNTupleField', + _title: `Filed of type ${field.typeName}`, + $tuple: tuple, // reference on tuple, need for drawing + $field: field + }; + + item._obj = item; + + tuple_node._childs.push(item); + }); + + return true; + }); +} + +export { tupleHierarchy, readHeaderFooter, RBufferReader, rntupleProcess, readEntry, rntupleDraw }; diff --git a/modules/testing.mjs b/modules/testing.mjs index 9dbae4a69..98eadb909 100644 --- a/modules/testing.mjs +++ b/modules/testing.mjs @@ -1,10 +1,11 @@ -import { isNodeJs, isBatchMode, setBatchMode, postponePromise } from './core.mjs'; +import { isNodeJs, isFunc, isBatchMode, setBatchMode, postponePromise } from './core.mjs'; import { select as d3_select } from './d3.mjs'; import { _loadJSDOM } from './base/BasePainter.mjs'; import { cleanup, getElementCanvPainter } from './base/ObjectPainter.mjs'; import { draw } from './draw.mjs'; import { closeMenu } from './gui/menu.mjs'; +/* eslint-disable no-await-in-loop */ async function _test_timeout(args, portion = 1) { if (!args?.timeout) @@ -31,8 +32,8 @@ class EmulationMouseEvent { this.$touch_arr = [[Math.round(x1), Math.round(y1)], [Math.round(x2), Math.round(y2)]]; } - preventDefault() {} - stopPropagation() {} + preventDefault() {} + stopPropagation() {} } // class EmulationMouseEvent @@ -42,7 +43,7 @@ function _getAllSubPads(cp) { if ((p !== cp) && p.getFramePainter()) sub.push(p); }, 'pads'); - return sub.length > 4 ? [] : sub; // do not test large canvas with many-many subpads + return sub.length > 4 ? [] : sub; // do not test large canvas with many-many sub-pads } /** @summary test zooming features @@ -50,7 +51,8 @@ function _getAllSubPads(cp) { async function testZooming(node, args, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { const sub_pads = _getAllSubPads(cp); @@ -59,20 +61,23 @@ async function testZooming(node, args, pp) { return; } - if ((typeof fp?.zoom !== 'function') || (typeof fp?.zoomSingle !== 'function')) return; - if (typeof fp.scale_xmin === 'undefined' || typeof fp.scale_ymax === 'undefined') return; + if (!isFunc(fp?.zoom) || !isFunc(fp?.zoomSingle)) + return; + if (typeof fp.scale_xmin === 'undefined' || typeof fp.scale_ymax === 'undefined') + return; const xmin = fp.scale_xmin, xmax = fp.scale_xmax, ymin = fp.scale_ymin, ymax = fp.scale_ymax; - if (args.debug) console.log(`test zooming in range: ${xmin} ${xmax} ${ymin} ${ymax}`); + if (args.debug) + console.log(`test zooming in range: ${xmin} ${xmax} ${ymin} ${ymax}`); - await fp.zoom(xmin + 0.2*(xmax - xmin), xmin + 0.8*(xmax - xmin), ymin + 0.2*(ymax - ymin), ymin + 0.8*(ymax - ymin)); + await fp.zoom(xmin + 0.2 * (xmax - xmin), xmin + 0.8 * (xmax - xmin), ymin + 0.2 * (ymax - ymin), ymin + 0.8 * (ymax - ymin)); await _test_timeout(args); await fp.unzoom(); await _test_timeout(args); - await fp.zoomSingle('x', xmin + 0.22*(xmax - xmin), xmin + 0.25*(xmax - xmin)); + await fp.zoomSingle('x', xmin + 0.22 * (xmax - xmin), xmin + 0.25 * (xmax - xmin)); await _test_timeout(args); - await fp.zoomSingle('y', ymin + 0.12*(ymax - ymin), ymin + 0.43*(ymax - ymin)); + await fp.zoomSingle('y', ymin + 0.12 * (ymax - ymin), ymin + 0.43 * (ymax - ymin)); await _test_timeout(args); await fp.unzoom(); } @@ -82,7 +87,8 @@ async function testZooming(node, args, pp) { async function testMouseZooming(node, args, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -92,28 +98,31 @@ async function testMouseZooming(node, args, pp) { return; } - if (fp?.mode3d) return; + if (fp?.mode3d) + return; if ((typeof fp?.startRectSel !== 'function') || (typeof fp?.moveRectSel !== 'function') || - (typeof fp?.endRectSel !== 'function')) return; + (typeof fp?.endRectSel !== 'function')) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(), evnt = new EmulationMouseEvent(), rect = fp.getFrameSvg().node().getBoundingClientRect(); - if (args.debug) console.log(`test mouse zooming in frame: ${fw} ${fh}`); + if (args.debug) + console.log(`test mouse zooming in frame: ${fw} ${fh}`); // region zooming for (let side = -1; side <= 1; side++) { - evnt.set(rect.x + (side > 0 ? -25 : fw*0.1), rect.y + (side < 0 ? fh + 25 : fh*0.1)); + evnt.set(rect.x + (side > 0 ? -25 : fw * 0.1), rect.y + (side < 0 ? fh + 25 : fh * 0.1)); fp.startRectSel(evnt); await _test_timeout(args); for (let i = 2; i < 10; ++i) { - evnt.set(rect.x + (side > 0 ? -5 : fw*0.1*i), rect.y + (side < 0 ? fh + 25 : fh*0.1*i)); + evnt.set(rect.x + (side > 0 ? -5 : fw * 0.1 * i), rect.y + (side < 0 ? fh + 25 : fh * 0.1 * i)); fp.moveRectSel(evnt); await _test_timeout(args, 0.2); } @@ -131,7 +140,8 @@ async function testMouseZooming(node, args, pp) { async function testTouchZooming(node, args, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -141,24 +151,23 @@ async function testTouchZooming(node, args, pp) { return; } - if (fp?.mode3d) return; - if ((typeof fp?.startTouchZoom !== 'function') || - (typeof fp?.moveTouchZoom !== 'function') || - (typeof fp?.endTouchZoom !== 'function')) return; + if (fp?.mode3d || !isFunc(fp?.startTouchZoom) || !isFunc(fp?.moveTouchZoom) || !isFunc(fp?.endTouchZoom)) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(), evnt = new EmulationMouseEvent(); - if (args.debug) console.log(`test touch zooming in frame: ${fw} ${fh}`); + if (args.debug) + console.log(`test touch zooming in frame: ${fw} ${fh}`); - evnt.setTouch(fw*0.4, fh*0.4, fw*0.6, fh*0.6); + evnt.setTouch(fw * 0.4, fh * 0.4, fw * 0.6, fh * 0.6); fp.startTouchZoom(evnt); await _test_timeout(args); for (let i = 2; i < 9; ++i) { - evnt.setTouch(fw*0.05*(10 - i), fh*0.05*(10 - i), fw*0.05*(10 + i), fh*0.05*(10 + i)); + evnt.setTouch(fw * 0.05 * (10 - i), fh * 0.05 * (10 - i), fw * 0.05 * (10 + i), fh * 0.05 * (10 + i)); fp.moveTouchZoom(evnt); await _test_timeout(args, 0.2); } @@ -175,7 +184,8 @@ async function testTouchZooming(node, args, pp) { async function testMouseWheel(node, args, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -185,14 +195,14 @@ async function testMouseWheel(node, args, pp) { return; } - if (fp?.mode3d) return; - if (typeof fp?.mouseWheel !== 'function') return; + if (fp?.mode3d || !isFunc(fp?.mouseWheel)) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(), evnt = new EmulationMouseEvent(), rect = fp.getFrameSvg().node().getBoundingClientRect(); - evnt.set(rect.x + fw*0.4, rect.y + fh*0.4); + evnt.set(rect.x + fw * 0.4, rect.y + fh * 0.4); // zoom inside for (let i = 0; i < 7; ++i) { @@ -217,7 +227,8 @@ async function testMouseWheel(node, args, pp) { async function testFrameClick(node, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -227,13 +238,14 @@ async function testFrameClick(node, pp) { return; } - if (fp?.mode3d || typeof fp?.processFrameClick !== 'function') return; + if (fp?.mode3d || !isFunc(fp?.processFrameClick)) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(); for (let i = 1; i < 15; i++) { for (let j = 1; j < 15; j++) { - const pnt = { x: Math.round(i/15*fw), y: Math.round(j/15*fh) }; + const pnt = { x: Math.round(i / 15 * fw), y: Math.round(j / 15 * fh) }; fp.processFrameClick(pnt); } } @@ -242,7 +254,8 @@ async function testFrameClick(node, pp) { async function testFrameMouseDoubleClick(node, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -252,7 +265,8 @@ async function testFrameMouseDoubleClick(node, pp) { return; } - if (fp?.mode3d || typeof fp?.mouseDoubleClick !== 'function') return; + if (fp?.mode3d || !isFunc(fp?.mouseDoubleClick)) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(), evnt = new EmulationMouseEvent(), @@ -260,7 +274,7 @@ async function testFrameMouseDoubleClick(node, pp) { for (let i = -2; i < 14; i++) { for (let j = -2; j < 14; j++) { - evnt.set(rect.x + i/10*fw, rect.y + j/10*fh); + evnt.set(rect.x + i / 10 * fw, rect.y + j / 10 * fh); await fp.mouseDoubleClick(evnt); } } @@ -269,7 +283,8 @@ async function testFrameMouseDoubleClick(node, pp) { async function testFrameContextMenu(node, args, pp) { const cp = getElementCanvPainter(node), pad_painter = pp ?? cp; - if (!pad_painter) return; + if (!pad_painter) + return; const fp = pad_painter.getFramePainter(); if (!fp && !pp) { @@ -279,7 +294,8 @@ async function testFrameContextMenu(node, args, pp) { return; } - if (fp?.mode3d || typeof fp?.showContextMenu !== 'function') return; + if (fp?.mode3d || !isFunc(fp?.showContextMenu)) + return; const fw = fp.getFrameWidth(), fh = fp.getFrameHeight(), evnt = new EmulationMouseEvent(), @@ -287,7 +303,7 @@ async function testFrameContextMenu(node, args, pp) { for (let i = 1; i < 10; i++) { for (let j = 1; j < 10; j++) { - evnt.set(rect.x + i/10*fw, rect.y + j/10*fh); + evnt.set(rect.x + i / 10 * fw, rect.y + j / 10 * fh); await fp.showContextMenu('', evnt); await _test_timeout(args, 0.03); closeMenu(); @@ -314,15 +330,16 @@ async function testPadContextMenu(node, args, pp) { await testPadContextMenu(node, args, sub_pads[k]); } - if (typeof cp?.padContextMenu !== 'function') return; + if (!isFunc(cp?.padContextMenu)) + return; const pw = cp.getPadWidth(), ph = cp.getPadHeight(), evnt = new EmulationMouseEvent(), - rect = cp.svg_this_pad().node().getBoundingClientRect(); + rect = cp.getPadSvg().node().getBoundingClientRect(); for (let i = 1; i < 10; i++) { for (let j = 1; j < 10; j++) { - evnt.set(rect.x + i/10*pw, rect.y + j/10*ph); + evnt.set(rect.x + i / 10 * pw, rect.y + j / 10 * ph); await cp.padContextMenu(evnt); await _test_timeout(args, 0.03); closeMenu(); @@ -339,13 +356,14 @@ async function testPadItemContextMenu(node, args, pp) { await testPadItemContextMenu(node, args, sub_pads[k]); } - if (typeof cp?.itemContextMenu !== 'function') return; + if (!isFunc(cp?.itemContextMenu)) + return; - const nprimitives = cp.painters?.length ?? 0, + const nprimitives = cp.getNumPainters() ?? 0, names = ['xaxis', 'yaxis', 'zaxis', 'pad', 'frame']; for (let i = -names.length; i < nprimitives; ++i) { - const name = (i < 0) ? names[i+names.length] : i.toString(); + const name = (i < 0) ? names[i + names.length] : i.toString(); await cp.itemContextMenu(name); await _test_timeout(args, 0.1); closeMenu(); @@ -354,7 +372,8 @@ async function testPadItemContextMenu(node, args, pp) { async function testPadButtons(node, args) { const cp = getElementCanvPainter(node); - if (typeof cp?.clickPadButton !== 'function') return; + if (!isFunc(cp?.clickPadButton)) + return; const evnt = new EmulationMouseEvent(50, 50), toggles = ['ToggleZoom', 'ToggleLogX', 'ToggleLogY', 'ToggleLogZ', 'Toggle3D', 'ToggleColorZ', 'ToggleStatBox']; @@ -427,7 +446,7 @@ async function testInteractivity(args) { main.remove(); setBatchMode(flag); return true; - }); + }); }); } diff --git a/modules/three.mjs b/modules/three.mjs index 3d9f50e29..6523c1307 100644 --- a/modules/three.mjs +++ b/modules/three.mjs @@ -1,228 +1,1714 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -const REVISION = '162'; +const REVISION = '180'; +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsMouse} + * @constant + */ const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; + +/** + * Represents touch interaction types in context of controls. + * + * @type {ConstantsTouch} + * @constant + */ const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; + +/** + * Disables face culling. + * + * @type {number} + * @constant + */ const CullFaceNone = 0; + +/** + * Culls back faces. + * + * @type {number} + * @constant + */ const CullFaceBack = 1; + +/** + * Culls front faces. + * + * @type {number} + * @constant + */ const CullFaceFront = 2; + +/** + * Culls both front and back faces. + * + * @type {number} + * @constant + */ const CullFaceFrontBack = 3; + +/** + * Gives unfiltered shadow maps - fastest, but lowest quality. + * + * @type {number} + * @constant + */ const BasicShadowMap = 0; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm. + * + * @type {number} + * @constant + */ const PCFShadowMap = 1; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with + * better soft shadows especially when using low-resolution shadow maps. + * + * @type {number} + * @constant + */ const PCFSoftShadowMap = 2; + +/** + * Filters shadow maps using the Variance Shadow Map (VSM) algorithm. + * When using VSMShadowMap all shadow receivers will also cast shadows. + * + * @type {number} + * @constant + */ const VSMShadowMap = 3; + +/** + * Only front faces are rendered. + * + * @type {number} + * @constant + */ const FrontSide = 0; + +/** + * Only back faces are rendered. + * + * @type {number} + * @constant + */ const BackSide = 1; + +/** + * Both front and back faces are rendered. + * + * @type {number} + * @constant + */ const DoubleSide = 2; + +/** + * No blending is performed which effectively disables + * alpha transparency. + * + * @type {number} + * @constant + */ const NoBlending = 0; + +/** + * The default blending. + * + * @type {number} + * @constant + */ const NormalBlending = 1; + +/** + * Represents additive blending. + * + * @type {number} + * @constant + */ const AdditiveBlending = 2; + +/** + * Represents subtractive blending. + * + * @type {number} + * @constant + */ const SubtractiveBlending = 3; + +/** + * Represents multiply blending. + * + * @type {number} + * @constant + */ const MultiplyBlending = 4; + +/** + * Represents custom blending. + * + * @type {number} + * @constant + */ const CustomBlending = 5; + +/** + * A `source + destination` blending equation. + * + * @type {number} + * @constant + */ const AddEquation = 100; + +/** + * A `source - destination` blending equation. + * + * @type {number} + * @constant + */ const SubtractEquation = 101; + +/** + * A `destination - source` blending equation. + * + * @type {number} + * @constant + */ const ReverseSubtractEquation = 102; + +/** + * A blend equation that uses the minimum of source and destination. + * + * @type {number} + * @constant + */ const MinEquation = 103; + +/** + * A blend equation that uses the maximum of source and destination. + * + * @type {number} + * @constant + */ const MaxEquation = 104; + +/** + * Multiplies all colors by `0`. + * + * @type {number} + * @constant + */ const ZeroFactor = 200; + +/** + * Multiplies all colors by `1`. + * + * @type {number} + * @constant + */ const OneFactor = 201; + +/** + * Multiplies all colors by the source colors. + * + * @type {number} + * @constant + */ const SrcColorFactor = 202; + +/** + * Multiplies all colors by `1` minus each source color. + * + * @type {number} + * @constant + */ const OneMinusSrcColorFactor = 203; + +/** + * Multiplies all colors by the source alpha value. + * + * @type {number} + * @constant + */ const SrcAlphaFactor = 204; + +/** + * Multiplies all colors by 1 minus the source alpha value. + * + * @type {number} + * @constant + */ const OneMinusSrcAlphaFactor = 205; + +/** + * Multiplies all colors by the destination alpha value. + * + * @type {number} + * @constant + */ const DstAlphaFactor = 206; + +/** + * Multiplies all colors by `1` minus the destination alpha value. + * + * @type {number} + * @constant + */ const OneMinusDstAlphaFactor = 207; + +/** + * Multiplies all colors by the destination color. + * + * @type {number} + * @constant + */ const DstColorFactor = 208; + +/** + * Multiplies all colors by `1` minus each destination color. + * + * @type {number} + * @constant + */ const OneMinusDstColorFactor = 209; + +/** + * Multiplies the RGB colors by the smaller of either the source alpha + * value or the value of `1` minus the destination alpha value. The alpha + * value is multiplied by `1`. + * + * @type {number} + * @constant + */ const SrcAlphaSaturateFactor = 210; + +/** + * Multiplies all colors by a constant color. + * + * @type {number} + * @constant + */ const ConstantColorFactor = 211; + +/** + * Multiplies all colors by `1` minus a constant color. + * + * @type {number} + * @constant + */ const OneMinusConstantColorFactor = 212; + +/** + * Multiplies all colors by a constant alpha value. + * + * @type {number} + * @constant + */ const ConstantAlphaFactor = 213; + +/** + * Multiplies all colors by 1 minus a constant alpha value. + * + * @type {number} + * @constant + */ const OneMinusConstantAlphaFactor = 214; + +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverDepth = 0; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysDepth = 1; + +/** + * Pass if the incoming value is less than the depth buffer value. + * + * @type {number} + * @constant + */ const LessDepth = 2; + +/** + * Pass if the incoming value is less than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const LessEqualDepth = 3; + +/** + * Pass if the incoming value equals the depth buffer value. + * + * @type {number} + * @constant + */ const EqualDepth = 4; + +/** + * Pass if the incoming value is greater than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterEqualDepth = 5; + +/** + * Pass if the incoming value is greater than the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterDepth = 6; + +/** + * Pass if the incoming value is not equal to the depth buffer value. + * + * @type {number} + * @constant + */ const NotEqualDepth = 7; + +/** + * Multiplies the environment map color with the surface color. + * + * @type {number} + * @constant + */ const MultiplyOperation = 0; + +/** + * Uses reflectivity to blend between the two colors. + * + * @type {number} + * @constant + */ const MixOperation = 1; + +/** + * Adds the two colors. + * + * @type {number} + * @constant + */ const AddOperation = 2; + +/** + * No tone mapping is applied. + * + * @type {number} + * @constant + */ const NoToneMapping = 0; + +/** + * Linear tone mapping. + * + * @type {number} + * @constant + */ const LinearToneMapping = 1; + +/** + * Reinhard tone mapping. + * + * @type {number} + * @constant + */ const ReinhardToneMapping = 2; + +/** + * Cineon tone mapping. + * + * @type {number} + * @constant + */ const CineonToneMapping = 3; + +/** + * ACES Filmic tone mapping. + * + * @type {number} + * @constant + */ const ACESFilmicToneMapping = 4; + +/** + * Custom tone mapping. + * + * Expects a custom implementation by modifying shader code of the material's fragment shader. + * + * @type {number} + * @constant + */ const CustomToneMapping = 5; + +/** + * AgX tone mapping. + * + * @type {number} + * @constant + */ const AgXToneMapping = 6; + +/** + * Neutral tone mapping. + * + * Implementation based on the Khronos 3D Commerce Group standard tone mapping. + * + * @type {number} + * @constant + */ const NeutralToneMapping = 7; + +/** + * The skinned mesh shares the same world space as the skeleton. + * + * @type {string} + * @constant + */ const AttachedBindMode = 'attached'; + +/** + * The skinned mesh does not share the same world space as the skeleton. + * This is useful when a skeleton is shared across multiple skinned meshes. + * + * @type {string} + * @constant + */ const DetachedBindMode = 'detached'; +/** + * Maps textures using the geometry's UV coordinates. + * + * @type {number} + * @constant + */ const UVMapping = 300; + +/** + * Reflection mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeReflectionMapping = 301; + +/** + * Refraction mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeRefractionMapping = 302; + +/** + * Reflection mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularReflectionMapping = 303; + +/** + * Refraction mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularRefractionMapping = 304; + +/** + * Reflection mapping for PMREM textures. + * + * @type {number} + * @constant + */ const CubeUVReflectionMapping = 306; + +/** + * The texture will simply repeat to infinity. + * + * @type {number} + * @constant + */ const RepeatWrapping = 1000; + +/** + * The last pixel of the texture stretches to the edge of the mesh. + * + * @type {number} + * @constant + */ const ClampToEdgeWrapping = 1001; + +/** + * The texture will repeats to infinity, mirroring on each repeat. + * + * @type {number} + * @constant + */ const MirroredRepeatWrapping = 1002; + +/** + * Returns the value of the texture element that is nearest (in Manhattan distance) + * to the specified texture coordinates. + * + * @type {number} + * @constant + */ const NearestFilter = 1003; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured + * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel) + * to produce a texture value. + * + * @type {number} + * @constant + */ const NearestMipmapNearestFilter = 1004; -const NearestMipMapNearestFilter = 1004; +const NearestMipMapNearestFilter = 1004; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and + * uses the `NearestFilter` criterion to produce a texture value from each mipmap. + * The final texture value is a weighted average of those two values. + * + * @type {number} + * @constant + */ const NearestMipmapLinearFilter = 1005; -const NearestMipMapLinearFilter = 1005; +const NearestMipMapLinearFilter = 1005; // legacy + +/** + * Returns the weighted average of the four texture elements that are closest to the specified + * texture coordinates, and can include items wrapped or repeated from other parts of a texture, + * depending on the values of `wrapS` and `wrapT`, and on the exact mapping. + * + * @type {number} + * @constant + */ const LinearFilter = 1006; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured and uses + * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the + * center of the pixel) to produce a texture value. + * + * @type {number} + * @constant + */ const LinearMipmapNearestFilter = 1007; -const LinearMipMapNearestFilter = 1007; +const LinearMipMapNearestFilter = 1007; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses + * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value + * is a weighted average of those two values. + * + * @type {number} + * @constant + */ const LinearMipmapLinearFilter = 1008; -const LinearMipMapLinearFilter = 1008; +const LinearMipMapLinearFilter = 1008; // legacy + +/** + * An unsigned byte data type for textures. + * + * @type {number} + * @constant + */ const UnsignedByteType = 1009; + +/** + * A byte data type for textures. + * + * @type {number} + * @constant + */ const ByteType = 1010; + +/** + * A short data type for textures. + * + * @type {number} + * @constant + */ const ShortType = 1011; + +/** + * An unsigned short data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShortType = 1012; + +/** + * An int data type for textures. + * + * @type {number} + * @constant + */ const IntType = 1013; + +/** + * An unsigned int data type for textures. + * + * @type {number} + * @constant + */ const UnsignedIntType = 1014; + +/** + * A float data type for textures. + * + * @type {number} + * @constant + */ const FloatType = 1015; + +/** + * A half float data type for textures. + * + * @type {number} + * @constant + */ const HalfFloatType = 1016; + +/** + * An unsigned short 4_4_4_4 (packed) data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShort4444Type = 1017; + +/** + * An unsigned short 5_5_5_1 (packed) data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShort5551Type = 1018; + +/** + * An unsigned int 24_8 data type for textures. + * + * @type {number} + * @constant + */ const UnsignedInt248Type = 1020; + +/** + * An unsigned int 5_9_9_9 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt5999Type = 35902; + +/** + * An unsigned int 10_11_11 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt101111Type = 35899; + +/** + * Discards the red, green and blue components and reads just the alpha component. + * + * @type {number} + * @constant + */ const AlphaFormat = 1021; + +/** + * Discards the alpha component and reads the red, green and blue component. + * + * @type {number} + * @constant + */ +const RGBFormat = 1022; + +/** + * Reads the red, green, blue and alpha components. + * + * @type {number} + * @constant + */ const RGBAFormat = 1023; -const LuminanceFormat = 1024; -const LuminanceAlphaFormat = 1025; + +/** + * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`. + * + * @type {number} + * @constant + */ const DepthFormat = 1026; + +/** + * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as + * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format. + * + * @type {number} + * @constant + */ const DepthStencilFormat = 1027; + +/** + * Discards the green, blue and alpha components and reads just the red component. + * + * @type {number} + * @constant + */ const RedFormat = 1028; + +/** + * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RedIntegerFormat = 1029; + +/** + * Discards the alpha, and blue components and reads the red, and green components. + * + * @type {number} + * @constant + */ const RGFormat = 1030; + +/** + * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGIntegerFormat = 1031; + +/** + * Discards the alpha component and reads the red, green and blue component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RGBIntegerFormat = 1032; + +/** + * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGBAIntegerFormat = 1033; +/** + * A DXT1-compressed image in an RGB image format. + * + * @type {number} + * @constant + */ const RGB_S3TC_DXT1_Format = 33776; + +/** + * A DXT1-compressed image in an RGB image format with a simple on/off alpha value. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT1_Format = 33777; + +/** + * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT3_Format = 33778; + +/** + * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3 + * compression in how the alpha compression is done. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT5_Format = 33779; + +/** + * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_4BPPV1_Format = 35840; + +/** + * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_2BPPV1_Format = 35841; + +/** + * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_4BPPV1_Format = 35842; + +/** + * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_2BPPV1_Format = 35843; + +/** + * ETC1 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC1_Format = 36196; + +/** + * ETC2 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC2_Format = 37492; + +/** + * ETC2 RGBA format. + * + * @type {number} + * @constant + */ const RGBA_ETC2_EAC_Format = 37496; + +/** + * ASTC RGBA 4x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_4x4_Format = 37808; + +/** + * ASTC RGBA 5x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x4_Format = 37809; + +/** + * ASTC RGBA 5x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x5_Format = 37810; + +/** + * ASTC RGBA 6x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x5_Format = 37811; + +/** + * ASTC RGBA 6x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x6_Format = 37812; + +/** + * ASTC RGBA 8x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x5_Format = 37813; + +/** + * ASTC RGBA 8x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x6_Format = 37814; + +/** + * ASTC RGBA 8x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x8_Format = 37815; + +/** + * ASTC RGBA 10x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x5_Format = 37816; + +/** + * ASTC RGBA 10x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x6_Format = 37817; + +/** + * ASTC RGBA 10x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x8_Format = 37818; + +/** + * ASTC RGBA 10x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x10_Format = 37819; + +/** + * ASTC RGBA 12x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x10_Format = 37820; + +/** + * ASTC RGBA 12x12 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x12_Format = 37821; + +/** + * BPTC RGBA format. + * + * @type {number} + * @constant + */ const RGBA_BPTC_Format = 36492; + +/** + * BPTC Signed RGB format. + * + * @type {number} + * @constant + */ const RGB_BPTC_SIGNED_Format = 36494; + +/** + * BPTC Unsigned RGB format. + * + * @type {number} + * @constant + */ const RGB_BPTC_UNSIGNED_Format = 36495; + +/** + * RGTC1 Red format. + * + * @type {number} + * @constant + */ const RED_RGTC1_Format = 36283; -const SIGNED_RED_RGTC1_Format = 36284; + +/** + * RGTC1 Signed Red format. + * + * @type {number} + * @constant + */ +const SIGNED_RED_RGTC1_Format = 36284; + +/** + * RGTC2 Red Green format. + * + * @type {number} + * @constant + */ const RED_GREEN_RGTC2_Format = 36285; + +/** + * RGTC2 Signed Red Green format. + * + * @type {number} + * @constant + */ const SIGNED_RED_GREEN_RGTC2_Format = 36286; + +/** + * Animations are played once. + * + * @type {number} + * @constant + */ const LoopOnce = 2200; + +/** + * Animations are played with a chosen number of repetitions, each time jumping from + * the end of the clip directly to its beginning. + * + * @type {number} + * @constant + */ const LoopRepeat = 2201; + +/** + * Animations are played with a chosen number of repetitions, alternately playing forward + * and backward. + * + * @type {number} + * @constant + */ const LoopPingPong = 2202; + +/** + * Discrete interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateDiscrete = 2300; + +/** + * Linear interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateLinear = 2301; + +/** + * Smooth interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateSmooth = 2302; + +/** + * Zero curvature ending for animations. + * + * @type {number} + * @constant + */ const ZeroCurvatureEnding = 2400; + +/** + * Zero slope ending for animations. + * + * @type {number} + * @constant + */ const ZeroSlopeEnding = 2401; + +/** + * Wrap around ending for animations. + * + * @type {number} + * @constant + */ const WrapAroundEnding = 2402; + +/** + * Default animation blend mode. + * + * @type {number} + * @constant + */ const NormalAnimationBlendMode = 2500; + +/** + * Additive animation blend mode. Can be used to layer motions on top of + * each other to build complex performances from smaller re-usable assets. + * + * @type {number} + * @constant + */ const AdditiveAnimationBlendMode = 2501; + +/** + * For every three vertices draw a single triangle. + * + * @type {number} + * @constant + */ const TrianglesDrawMode = 0; + +/** + * For each vertex draw a triangle from the last three vertices. + * + * @type {number} + * @constant + */ const TriangleStripDrawMode = 1; + +/** + * For each vertex draw a triangle from the first vertex and the last two vertices. + * + * @type {number} + * @constant + */ const TriangleFanDrawMode = 2; + +/** + * Basic depth packing. + * + * @type {number} + * @constant + */ const BasicDepthPacking = 3200; + +/** + * A depth value is packed into 32 bit RGBA. + * + * @type {number} + * @constant + */ const RGBADepthPacking = 3201; + +/** + * A depth value is packed into 24 bit RGB. + * + * @type {number} + * @constant + */ +const RGBDepthPacking = 3202; + +/** + * A depth value is packed into 16 bit RG. + * + * @type {number} + * @constant + */ +const RGDepthPacking = 3203; + +/** + * Normal information is relative to the underlying surface. + * + * @type {number} + * @constant + */ const TangentSpaceNormalMap = 0; + +/** + * Normal information is relative to the object orientation. + * + * @type {number} + * @constant + */ const ObjectSpaceNormalMap = 1; // Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. + +/** + * No color space. + * + * @type {string} + * @constant + */ const NoColorSpace = ''; + +/** + * sRGB color space. + * + * @type {string} + * @constant + */ const SRGBColorSpace = 'srgb'; + +/** + * sRGB-linear color space. + * + * @type {string} + * @constant + */ const LinearSRGBColorSpace = 'srgb-linear'; -const DisplayP3ColorSpace = 'display-p3'; -const LinearDisplayP3ColorSpace = 'display-p3-linear'; +/** + * Linear transfer function. + * + * @type {string} + * @constant + */ const LinearTransfer = 'linear'; -const SRGBTransfer = 'srgb'; -const Rec709Primaries = 'rec709'; -const P3Primaries = 'p3'; +/** + * sRGB transfer function. + * + * @type {string} + * @constant + */ +const SRGBTransfer = 'srgb'; +/** + * Sets the stencil buffer value to `0`. + * + * @type {number} + * @constant + */ const ZeroStencilOp = 0; + +/** + * Keeps the current value. + * + * @type {number} + * @constant + */ const KeepStencilOp = 7680; + +/** + * Sets the stencil buffer value to the specified reference value. + * + * @type {number} + * @constant + */ const ReplaceStencilOp = 7681; + +/** + * Increments the current stencil buffer value. Clamps to the maximum representable unsigned value. + * + * @type {number} + * @constant + */ const IncrementStencilOp = 7682; + +/** + * Decrements the current stencil buffer value. Clamps to `0`. + * + * @type {number} + * @constant + */ const DecrementStencilOp = 7683; + +/** + * Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing + * the maximum representable unsigned value. + * + * @type {number} + * @constant + */ const IncrementWrapStencilOp = 34055; + +/** + * Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable + * unsigned value when decrementing a stencil buffer value of `0`. + * + * @type {number} + * @constant + */ const DecrementWrapStencilOp = 34056; + +/** + * Inverts the current stencil buffer value bitwise. + * + * @type {number} + * @constant + */ const InvertStencilOp = 5386; +/** + * Will never return true. + * + * @type {number} + * @constant + */ const NeverStencilFunc = 512; + +/** + * Will return true if the stencil reference value is less than the current stencil value. + * + * @type {number} + * @constant + */ const LessStencilFunc = 513; + +/** + * Will return true if the stencil reference value is equal to the current stencil value. + * + * @type {number} + * @constant + */ const EqualStencilFunc = 514; + +/** + * Will return true if the stencil reference value is less than or equal to the current stencil value. + * + * @type {number} + * @constant + */ const LessEqualStencilFunc = 515; + +/** + * Will return true if the stencil reference value is greater than the current stencil value. + * + * @type {number} + * @constant + */ const GreaterStencilFunc = 516; + +/** + * Will return true if the stencil reference value is not equal to the current stencil value. + * + * @type {number} + * @constant + */ const NotEqualStencilFunc = 517; + +/** + * Will return true if the stencil reference value is greater than or equal to the current stencil value. + * + * @type {number} + * @constant + */ const GreaterEqualStencilFunc = 518; + +/** + * Will always return true. + * + * @type {number} + * @constant + */ const AlwaysStencilFunc = 519; +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverCompare = 512; + +/** + * Pass if the incoming value is less than the texture value. + * + * @type {number} + * @constant + */ const LessCompare = 513; + +/** + * Pass if the incoming value equals the texture value. + * + * @type {number} + * @constant + */ const EqualCompare = 514; + +/** + * Pass if the incoming value is less than or equal to the texture value. + * + * @type {number} + * @constant + */ const LessEqualCompare = 515; + +/** + * Pass if the incoming value is greater than the texture value. + * + * @type {number} + * @constant + */ const GreaterCompare = 516; + +/** + * Pass if the incoming value is not equal to the texture value. + * + * @type {number} + * @constant + */ const NotEqualCompare = 517; + +/** + * Pass if the incoming value is greater than or equal to the texture value. + * + * @type {number} + * @constant + */ const GreaterEqualCompare = 518; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysCompare = 519; +/** + * The contents are intended to be specified once by the application, and used many + * times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const StaticDrawUsage = 35044; + +/** + * The contents are intended to be respecified repeatedly by the application, and + * used many times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const DynamicDrawUsage = 35048; + +/** + * The contents are intended to be specified once by the application, and used at most + * a few times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const StreamDrawUsage = 35040; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ const StaticReadUsage = 35045; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ const DynamicReadUsage = 35049; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried at most + * a few times by the application + * + * @type {number} + * @constant + */ const StreamReadUsage = 35041; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used many times as + * the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const StaticCopyUsage = 35046; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and used many times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const DynamicCopyUsage = 35050; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used at most a few times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const StreamCopyUsage = 35042; +/** + * GLSL 1 shader code. + * + * @type {string} + * @constant + */ const GLSL1 = '100'; -const GLSL3 = '300 es'; -const _SRGBAFormat = 1035; // fallback for WebGL 1 +/** + * GLSL 3 shader code. + * + * @type {string} + * @constant + */ +const GLSL3 = '300 es'; +/** + * WebGL coordinate system. + * + * @type {number} + * @constant + */ const WebGLCoordinateSystem = 2000; + +/** + * WebGPU coordinate system. + * + * @type {number} + * @constant + */ const WebGPUCoordinateSystem = 2001; /** - * https://github.com/mrdoob/eventdispatcher.js/ + * Represents the different timestamp query types. + * + * @type {ConstantsTimestampQuery} + * @constant + */ +const TimestampQuery = { + COMPUTE: 'compute', + RENDER: 'render' +}; + +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsInterpolationSamplingType} + * @constant + */ +const InterpolationSamplingType = { + PERSPECTIVE: 'perspective', + LINEAR: 'linear', + FLAT: 'flat' +}; + +/** + * Represents the different interpolation sampling modes. + * + * @type {ConstantsInterpolationSamplingMode} + * @constant */ +const InterpolationSamplingMode = { + NORMAL: 'normal', + CENTROID: 'centroid', + SAMPLE: 'sample', + FIRST: 'first', + EITHER: 'either' +}; + +/** + * This type represents mouse buttons and interaction types in context of controls. + * + * @typedef {Object} ConstantsMouse + * @property {number} MIDDLE - The left mouse button. + * @property {number} LEFT - The middle mouse button. + * @property {number} RIGHT - The right mouse button. + * @property {number} ROTATE - A rotate interaction. + * @property {number} DOLLY - A dolly interaction. + * @property {number} PAN - A pan interaction. + **/ + +/** + * This type represents touch interaction types in context of controls. + * + * @typedef {Object} ConstantsTouch + * @property {number} ROTATE - A rotate interaction. + * @property {number} PAN - A pan interaction. + * @property {number} DOLLY_PAN - The dolly-pan interaction. + * @property {number} DOLLY_ROTATE - A dolly-rotate interaction. + **/ + +/** + * This type represents the different timestamp query types. + * + * @typedef {Object} ConstantsTimestampQuery + * @property {string} COMPUTE - A `compute` timestamp query. + * @property {string} RENDER - A `render` timestamp query. + **/ +/** + * Represents the different interpolation sampling types. + * + * @typedef {Object} ConstantsInterpolationSamplingType + * @property {string} PERSPECTIVE - Perspective-correct interpolation. + * @property {string} LINEAR - Linear interpolation. + * @property {string} FLAT - Flat interpolation. + */ + +/** + * Represents the different interpolation sampling modes. + * + * @typedef {Object} ConstantsInterpolationSamplingMode + * @property {string} NORMAL - Normal sampling mode. + * @property {string} CENTROID - Centroid sampling mode. + * @property {string} SAMPLE - Sample-specific sampling mode. + * @property {string} FIRST - Flat interpolation using the first vertex. + * @property {string} EITHER - Flat interpolation using either vertex. + */ + +/** + * This modules allows to dispatch event objects on custom JavaScript objects. + * + * Main repository: [eventdispatcher.js]{@link https://github.com/mrdoob/eventdispatcher.js/} + * + * Code Example: + * ```js + * class Car extends EventDispatcher { + * start() { + * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); + * } + *}; + * + * // Using events with the custom object + * const car = new Car(); + * car.addEventListener( 'start', function ( event ) { + * alert( event.message ); + * } ); + * + * car.start(); + * ``` + */ class EventDispatcher { + /** + * Adds the given event listener to the given event type. + * + * @param {string} type - The type of event to listen to. + * @param {Function} listener - The function that gets called when the event is fired. + */ addEventListener( type, listener ) { if ( this._listeners === undefined ) this._listeners = {}; @@ -235,7 +1721,7 @@ class EventDispatcher { } - if ( listeners[ type ].indexOf( listener ) === - 1 ) { + if ( listeners[ type ].indexOf( listener ) === -1 ) { listeners[ type ].push( listener ); @@ -243,4864 +1729,8419 @@ class EventDispatcher { } + /** + * Returns `true` if the given event listener has been added to the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to check. + * @return {boolean} Whether the given event listener has been added to the given event type. + */ hasEventListener( type, listener ) { - if ( this._listeners === undefined ) return false; - const listeners = this._listeners; - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; + if ( listeners === undefined ) return false; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1; } + /** + * Removes the given event listener from the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to remove. + */ removeEventListener( type, listener ) { - if ( this._listeners === undefined ) return; - const listeners = this._listeners; + + if ( listeners === undefined ) return; + const listenerArray = listeners[ type ]; if ( listenerArray !== undefined ) { - const index = listenerArray.indexOf( listener ); + const index = listenerArray.indexOf( listener ); + + if ( index !== -1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + } + + /** + * Dispatches an event object. + * + * @param {Object} event - The event that gets fired. + */ + dispatchEvent( event ) { + + const listeners = this._listeners; + + if ( listeners === undefined ) return; + + const listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + // Make a copy, in case listeners are removed while iterating. + const array = listenerArray.slice( 0 ); + + for ( let i = 0, l = array.length; i < l; i ++ ) { + + array[ i ].call( this, event ); + + } + + event.target = null; + + } + + } + +} + +const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; + +let _seed = 1234567; + + +const DEG2RAD = Math.PI / 180; +const RAD2DEG = 180 / Math.PI; + +/** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @return {string} The UUID. + */ +function generateUUID() { + + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 + + const d0 = Math.random() * 0xffffffff | 0; + const d1 = Math.random() * 0xffffffff | 0; + const d2 = Math.random() * 0xffffffff | 0; + const d3 = Math.random() * 0xffffffff | 0; + const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + + _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + + _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + + _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; + + // .toLowerCase() here flattens concatenated strings to save heap memory space. + return uuid.toLowerCase(); + +} + +/** + * Clamps the given value between min and max. + * + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ +function clamp( value, min, max ) { + + return Math.max( min, Math.min( max, value ) ); + +} + +/** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ +function euclideanModulo( n, m ) { + + // https://en.wikipedia.org/wiki/Modulo_operation + + return ( ( n % m ) + m ) % m; + +} + +/** + * Performs a linear mapping from range `<a1, a2>` to range `<b1, b2>` + * for the given value. + * + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ +function mapLinear( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + +} + +/** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ +function inverseLerp( x, y, value ) { + + // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ + + if ( x !== y ) { + + return ( value - x ) / ( y - x ); + + } else { + + return 0; + + } + +} + +/** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ +function lerp( x, y, t ) { + + return ( 1 - t ) * x + t * y; + +} + +/** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ +function damp( x, y, lambda, dt ) { + + return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); + +} + +/** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ +function pingpong( x, length = 1 ) { + + // https://www.desmos.com/calculator/vcsjnyz7x4 + + return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); + +} + +/** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ +function smoothstep( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * ( 3 - 2 * x ); + +} + +/** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ +function smootherstep( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + +} + +/** + * Returns a random integer from `<low, high>` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ +function randInt( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + +} + +/** + * Returns a random float from `<low, high>` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ +function randFloat( low, high ) { + + return low + Math.random() * ( high - low ); + +} + +/** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ +function randFloatSpread( range ) { + + return range * ( 0.5 - Math.random() ); + +} + +/** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ +function seededRandom( s ) { + + if ( s !== undefined ) _seed = s; + + // Mulberry32 generator + + let t = _seed += 0x6D2B79F5; + + t = Math.imul( t ^ t >>> 15, t | 1 ); + + t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); + + return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; + +} + +/** + * Converts degrees to radians. + * + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ +function degToRad( degrees ) { + + return degrees * DEG2RAD; + +} + +/** + * Converts radians to degrees. + * + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ +function radToDeg( radians ) { + + return radians * RAD2DEG; + +} + +/** + * Returns `true` if the given number is a power of two. + * + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ +function isPowerOfTwo( value ) { + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + +} + +/** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ +function ceilPowerOfTwo( value ) { + + return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + +} + +/** + * Returns the largest power of two that is less than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ +function floorPowerOfTwo( value ) { + + return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + +} + +/** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ +function setQuaternionFromProperEuler( q, a, b, c, order ) { + + const cos = Math.cos; + const sin = Math.sin; + + const c2 = cos( b / 2 ); + const s2 = sin( b / 2 ); + + const c13 = cos( ( a + c ) / 2 ); + const s13 = sin( ( a + c ) / 2 ); + + const c1_3 = cos( ( a - c ) / 2 ); + const s1_3 = sin( ( a - c ) / 2 ); + + const c3_1 = cos( ( c - a ) / 2 ); + const s3_1 = sin( ( c - a ) / 2 ); + + switch ( order ) { + + case 'XYX': + q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); + break; + + case 'YZY': + q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); + break; + + case 'ZXZ': + q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); + break; + + case 'XZX': + q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); + break; + + case 'YXY': + q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); + break; + + case 'ZYZ': + q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); + break; + + default: + console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); + + } + +} + +/** + * Denormalizes the given value according to the given typed array. + * + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ +function denormalize( value, array ) { + + switch ( array.constructor ) { + + case Float32Array: + + return value; + + case Uint32Array: + + return value / 4294967295.0; + + case Uint16Array: + + return value / 65535.0; + + case Uint8Array: + + return value / 255.0; + + case Int32Array: + + return Math.max( value / 2147483647.0, -1 ); + + case Int16Array: + + return Math.max( value / 32767.0, -1 ); + + case Int8Array: + + return Math.max( value / 127.0, -1 ); + + default: + + throw new Error( 'Invalid component type.' ); + + } + +} + +/** + * Normalizes the given value according to the given typed array. + * + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ +function normalize( value, array ) { + + switch ( array.constructor ) { + + case Float32Array: + + return value; + + case Uint32Array: + + return Math.round( value * 4294967295.0 ); + + case Uint16Array: + + return Math.round( value * 65535.0 ); + + case Uint8Array: + + return Math.round( value * 255.0 ); + + case Int32Array: + + return Math.round( value * 2147483647.0 ); + + case Int16Array: + + return Math.round( value * 32767.0 ); + + case Int8Array: + + return Math.round( value * 127.0 ); + + default: + + throw new Error( 'Invalid component type.' ); + + } + +} + +/** + * @class + * @classdesc A collection of math utility functions. + * @hideconstructor + */ +const MathUtils = { + DEG2RAD: DEG2RAD, + RAD2DEG: RAD2DEG, + /** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @static + * @method + * @return {string} The UUID. + */ + generateUUID: generateUUID, + /** + * Clamps the given value between min and max. + * + * @static + * @method + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ + clamp: clamp, + /** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @static + * @method + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ + euclideanModulo: euclideanModulo, + /** + * Performs a linear mapping from range `<a1, a2>` to range `<b1, b2>` + * for the given value. + * + * @static + * @method + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ + mapLinear: mapLinear, + /** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ + inverseLerp: inverseLerp, + /** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ + lerp: lerp, + /** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @static + * @method + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ + damp: damp, + /** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @static + * @method + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ + pingpong: pingpong, + /** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ + smoothstep: smoothstep, + /** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ + smootherstep: smootherstep, + /** + * Returns a random integer from `<low, high>` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ + randInt: randInt, + /** + * Returns a random float from `<low, high>` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ + randFloat: randFloat, + /** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @static + * @method + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ + randFloatSpread: randFloatSpread, + /** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @static + * @method + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ + seededRandom: seededRandom, + /** + * Converts degrees to radians. + * + * @static + * @method + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ + degToRad: degToRad, + /** + * Converts radians to degrees. + * + * @static + * @method + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ + radToDeg: radToDeg, + /** + * Returns `true` if the given number is a power of two. + * + * @static + * @method + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ + isPowerOfTwo: isPowerOfTwo, + /** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ + ceilPowerOfTwo: ceilPowerOfTwo, + /** + * Returns the largest power of two that is less than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ + floorPowerOfTwo: floorPowerOfTwo, + /** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @static + * @method + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ + setQuaternionFromProperEuler: setQuaternionFromProperEuler, + /** + * Normalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ + normalize: normalize, + /** + * Denormalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ + denormalize: denormalize +}; + +var MathUtils$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + DEG2RAD: DEG2RAD, + MathUtils: MathUtils, + RAD2DEG: RAD2DEG, + ceilPowerOfTwo: ceilPowerOfTwo, + clamp: clamp, + damp: damp, + degToRad: degToRad, + denormalize: denormalize, + euclideanModulo: euclideanModulo, + floorPowerOfTwo: floorPowerOfTwo, + generateUUID: generateUUID, + inverseLerp: inverseLerp, + isPowerOfTwo: isPowerOfTwo, + lerp: lerp, + mapLinear: mapLinear, + normalize: normalize, + pingpong: pingpong, + radToDeg: radToDeg, + randFloat: randFloat, + randFloatSpread: randFloatSpread, + randInt: randInt, + seededRandom: seededRandom, + setQuaternionFromProperEuler: setQuaternionFromProperEuler, + smootherstep: smootherstep, + smoothstep: smoothstep +}); + +/** + * Class representing a 2D vector. A 2D vector is an ordered pair of numbers + * (labeled x and y), which can be used to represent a number of things, such as: + * + * - A point in 2D space (i.e. a position on a plane). + * - A direction and length across a plane. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)` + * and the direction is also measured from `(0, 0)` towards `(x, y)`. + * - Any arbitrary ordered pair of numbers. + * + * There are other things a 2D vector can be used to represent, such as + * momentum vectors, complex numbers and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector2( 0, 1 ); + * + * //no arguments; will be initialised to (0, 0) + * const b = new THREE.Vector2( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector2 { + + /** + * Constructs a new 2D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + */ + constructor( x = 0, y = 0 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector2.prototype.isVector2 = true; + + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; + + } + + /** + * Alias for {@link Vector2#x}. + * + * @type {number} + */ + get width() { + + return this.x; + + } + + set width( value ) { + + this.x = value; + + } + + /** + * Alias for {@link Vector2#y}. + * + * @type {number} + */ + get height() { + + return this.y; + + } + + set height( value ) { + + this.y = value; + + } + + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @return {Vector2} A reference to this vector. + */ + set( x, y ) { + + this.x = x; + this.y = y; + + return this; + + } + + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector2} A reference to this vector. + */ + setScalar( scalar ) { + + this.x = scalar; + this.y = scalar; + + return this; + + } + + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector2} A reference to this vector. + */ + setX( x ) { + + this.x = x; + + return this; + + } + + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector2} A reference to this vector. + */ + setY( y ) { + + this.y = y; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @param {number} value - The value to set. + * @return {Vector2} A reference to this vector. + */ + setComponent( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + } + + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @return {number} A vector component value. + */ + getComponent( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); + + } + + } + + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector2} A clone of this instance. + */ + clone() { + + return new this.constructor( this.x, this.y ); + + } + + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector2} v - The vector to copy. + * @return {Vector2} A reference to this vector. + */ + copy( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + } + + /** + * Adds the given vector to this instance. + * + * @param {Vector2} v - The vector to add. + * @return {Vector2} A reference to this vector. + */ + add( v ) { + + this.x += v.x; + this.y += v.y; + + return this; - if ( index !== - 1 ) { + } - listenerArray.splice( index, 1 ); + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector2} A reference to this vector. + */ + addScalar( s ) { - } + this.x += s; + this.y += s; - } + return this; } - dispatchEvent( event ) { + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ + addVectors( a, b ) { - if ( this._listeners === undefined ) return; + this.x = a.x + b.x; + this.y = a.y + b.y; - const listeners = this._listeners; - const listenerArray = listeners[ event.type ]; + return this; - if ( listenerArray !== undefined ) { + } - event.target = this; + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector2} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector2} A reference to this vector. + */ + addScaledVector( v, s ) { - // Make a copy, in case listeners are removed while iterating. - const array = listenerArray.slice( 0 ); + this.x += v.x * s; + this.y += v.y * s; - for ( let i = 0, l = array.length; i < l; i ++ ) { + return this; - array[ i ].call( this, event ); + } - } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector2} v - The vector to subtract. + * @return {Vector2} A reference to this vector. + */ + sub( v ) { - event.target = null; + this.x -= v.x; + this.y -= v.y; - } + return this; } -} - -const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector2} A reference to this vector. + */ + subScalar( s ) { -let _seed = 1234567; + this.x -= s; + this.y -= s; + return this; -const DEG2RAD = Math.PI / 180; -const RAD2DEG = 180 / Math.PI; + } -// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 -function generateUUID() { + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ + subVectors( a, b ) { - const d0 = Math.random() * 0xffffffff | 0; - const d1 = Math.random() * 0xffffffff | 0; - const d2 = Math.random() * 0xffffffff | 0; - const d3 = Math.random() * 0xffffffff | 0; - const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + - _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + - _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + - _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; + this.x = a.x - b.x; + this.y = a.y - b.y; - // .toLowerCase() here flattens concatenated strings to save heap memory space. - return uuid.toLowerCase(); + return this; -} + } -function clamp( value, min, max ) { + /** + * Multiplies the given vector with this instance. + * + * @param {Vector2} v - The vector to multiply. + * @return {Vector2} A reference to this vector. + */ + multiply( v ) { - return Math.max( min, Math.min( max, value ) ); + this.x *= v.x; + this.y *= v.y; -} + return this; -// compute euclidean modulo of m % n -// https://en.wikipedia.org/wiki/Modulo_operation -function euclideanModulo( n, m ) { + } - return ( ( n % m ) + m ) % m; + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector2} A reference to this vector. + */ + multiplyScalar( scalar ) { -} + this.x *= scalar; + this.y *= scalar; -// Linear mapping from range <a1, a2> to range <b1, b2> -function mapLinear( x, a1, a2, b1, b2 ) { + return this; - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + } -} + /** + * Divides this instance by the given vector. + * + * @param {Vector2} v - The vector to divide. + * @return {Vector2} A reference to this vector. + */ + divide( v ) { -// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ -function inverseLerp( x, y, value ) { + this.x /= v.x; + this.y /= v.y; - if ( x !== y ) { + return this; - return ( value - x ) / ( y - x ); + } - } else { + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector2} A reference to this vector. + */ + divideScalar( scalar ) { - return 0; + return this.multiplyScalar( 1 / scalar ); } -} - -// https://en.wikipedia.org/wiki/Linear_interpolation -function lerp( x, y, t ) { + /** + * Multiplies this vector (with an implicit 1 as the 3rd component) by + * the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to apply. + * @return {Vector2} A reference to this vector. + */ + applyMatrix3( m ) { - return ( 1 - t ) * x + t * y; + const x = this.x, y = this.y; + const e = m.elements; -} + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; -// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ -function damp( x, y, lambda, dt ) { + return this; - return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); + } -} + /** + * If this vector's x or y value is greater than the given vector's x or y + * value, replace that value with the corresponding min value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ + min( v ) { -// https://www.desmos.com/calculator/vcsjnyz7x4 -function pingpong( x, length = 1 ) { + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); - return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); + return this; -} + } -// http://en.wikipedia.org/wiki/Smoothstep -function smoothstep( x, min, max ) { + /** + * If this vector's x or y value is less than the given vector's x or y + * value, replace that value with the corresponding max value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ + max( v ) { - if ( x <= min ) return 0; - if ( x >= max ) return 1; + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); - x = ( x - min ) / ( max - min ); + return this; - return x * x * ( 3 - 2 * x ); + } -} + /** + * If this vector's x or y value is greater than the max vector's x or y + * value, it is replaced by the corresponding value. + * If this vector's x or y value is less than the min vector's x or y value, + * it is replaced by the corresponding value. + * + * @param {Vector2} min - The minimum x and y values. + * @param {Vector2} max - The maximum x and y values in the desired range. + * @return {Vector2} A reference to this vector. + */ + clamp( min, max ) { -function smootherstep( x, min, max ) { + // assumes min < max, componentwise - if ( x <= min ) return 0; - if ( x >= max ) return 1; + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); - x = ( x - min ) / ( max - min ); + return this; - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + } -} + /** + * If this vector's x or y values are greater than the max value, they are + * replaced by the max value. + * If this vector's x or y values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector2} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { -// Random integer from <low, high> interval -function randInt( low, high ) { + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); - return low + Math.floor( Math.random() * ( high - low + 1 ) ); + return this; -} + } -// Random float from <low, high> interval -function randFloat( low, high ) { + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector2} A reference to this vector. + */ + clampLength( min, max ) { - return low + Math.random() * ( high - low ); + const length = this.length(); -} + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); -// Random float from <-range/2, range/2> interval -function randFloatSpread( range ) { + } - return range * ( 0.5 - Math.random() ); + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ + floor() { -} + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); -// Deterministic pseudo-random float in the interval [ 0, 1 ] -function seededRandom( s ) { + return this; - if ( s !== undefined ) _seed = s; + } - // Mulberry32 generator + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ + ceil() { - let t = _seed += 0x6D2B79F5; + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); - t = Math.imul( t ^ t >>> 15, t | 1 ); + return this; - t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); + } - return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector2} A reference to this vector. + */ + round() { -} + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); -function degToRad( degrees ) { + return this; - return degrees * DEG2RAD; + } -} + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector2} A reference to this vector. + */ + roundToZero() { -function radToDeg( radians ) { + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); - return radians * RAD2DEG; + return this; -} + } -function isPowerOfTwo( value ) { + /** + * Inverts this vector - i.e. sets x = -x and y = -y. + * + * @return {Vector2} A reference to this vector. + */ + negate() { - return ( value & ( value - 1 ) ) === 0 && value !== 0; + this.x = - this.x; + this.y = - this.y; -} + return this; -function ceilPowerOfTwo( value ) { + } - return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { -} + return this.x * v.x + this.y * v.y; -function floorPowerOfTwo( value ) { + } - return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the cross product with. + * @return {number} The result of the cross product. + */ + cross( v ) { -} + return this.x * v.y - this.y * v.x; -function setQuaternionFromProperEuler( q, a, b, c, order ) { + } - // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { - // rotations are applied to the axes in the order specified by 'order' - // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' - // angles are in radians + return this.x * this.x + this.y * this.y; - const cos = Math.cos; - const sin = Math.sin; + } - const c2 = cos( b / 2 ); - const s2 = sin( b / 2 ); + /** + * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y). + * + * @return {number} The length of this vector. + */ + length() { - const c13 = cos( ( a + c ) / 2 ); - const s13 = sin( ( a + c ) / 2 ); + return Math.sqrt( this.x * this.x + this.y * this.y ); - const c1_3 = cos( ( a - c ) / 2 ); - const s1_3 = sin( ( a - c ) / 2 ); + } - const c3_1 = cos( ( c - a ) / 2 ); - const s3_1 = sin( ( c - a ) / 2 ); + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { - switch ( order ) { + return Math.abs( this.x ) + Math.abs( this.y ); - case 'XYX': - q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); - break; + } - case 'YZY': - q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); - break; + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector2} A reference to this vector. + */ + normalize() { - case 'ZXZ': - q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); - break; + return this.divideScalar( this.length() || 1 ); - case 'XZX': - q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); - break; + } - case 'YXY': - q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); - break; + /** + * Computes the angle in radians of this vector with respect to the positive x-axis. + * + * @return {number} The angle in radians. + */ + angle() { - case 'ZYZ': - q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); - break; + const angle = Math.atan2( - this.y, - this.x ) + Math.PI; - default: - console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); + return angle; } -} - -function denormalize( value, array ) { + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector2} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { - switch ( array.constructor ) { + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - case Float32Array: + if ( denominator === 0 ) return Math.PI / 2; - return value; + const theta = this.dot( v ) / denominator; - case Uint32Array: + // clamp, to handle numerical problems - return value / 4294967295.0; + return Math.acos( clamp( theta, -1, 1 ) ); - case Uint16Array: + } - return value / 65535.0; + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { - case Uint8Array: + return Math.sqrt( this.distanceToSquared( v ) ); - return value / 255.0; + } - case Int32Array: + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector2} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { - return Math.max( value / 2147483647.0, - 1.0 ); + const dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; - case Int16Array: + } - return Math.max( value / 32767.0, - 1.0 ); + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { - case Int8Array: + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - return Math.max( value / 127.0, - 1.0 ); + } - default: + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector2} A reference to this vector. + */ + setLength( length ) { - throw new Error( 'Invalid component type.' ); + return this.normalize().multiplyScalar( length ); } -} - -function normalize( value, array ) { + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector2} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ + lerp( v, alpha ) { - switch ( array.constructor ) { + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; - case Float32Array: + return this; - return value; + } - case Uint32Array: + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector2} v1 - The first vector. + * @param {Vector2} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { - return Math.round( value * 4294967295.0 ); + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; - case Uint16Array: + return this; - return Math.round( value * 65535.0 ); + } - case Uint8Array: + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector2} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - return Math.round( value * 255.0 ); + return ( ( v.x === this.x ) && ( v.y === this.y ) ); - case Int32Array: + } - return Math.round( value * 2147483647.0 ); + /** + * Sets this vector's x value to be `array[ offset ]` and y + * value to be `array[ offset + 1 ]`. + * + * @param {Array<number>} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector2} A reference to this vector. + */ + fromArray( array, offset = 0 ) { - case Int16Array: + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; - return Math.round( value * 32767.0 ); + return this; - case Int8Array: + } - return Math.round( value * 127.0 ); + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The vector components. + */ + toArray( array = [], offset = 0 ) { - default: + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; - throw new Error( 'Invalid component type.' ); + return array; } -} + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector2} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { -const MathUtils = { - DEG2RAD: DEG2RAD, - RAD2DEG: RAD2DEG, - generateUUID: generateUUID, - clamp: clamp, - euclideanModulo: euclideanModulo, - mapLinear: mapLinear, - inverseLerp: inverseLerp, - lerp: lerp, - damp: damp, - pingpong: pingpong, - smoothstep: smoothstep, - smootherstep: smootherstep, - randInt: randInt, - randFloat: randFloat, - randFloatSpread: randFloatSpread, - seededRandom: seededRandom, - degToRad: degToRad, - radToDeg: radToDeg, - isPowerOfTwo: isPowerOfTwo, - ceilPowerOfTwo: ceilPowerOfTwo, - floorPowerOfTwo: floorPowerOfTwo, - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - normalize: normalize, - denormalize: denormalize -}; + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); -var MathUtils$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - DEG2RAD: DEG2RAD, - MathUtils: MathUtils, - RAD2DEG: RAD2DEG, - ceilPowerOfTwo: ceilPowerOfTwo, - clamp: clamp, - damp: damp, - degToRad: degToRad, - denormalize: denormalize, - euclideanModulo: euclideanModulo, - floorPowerOfTwo: floorPowerOfTwo, - generateUUID: generateUUID, - inverseLerp: inverseLerp, - isPowerOfTwo: isPowerOfTwo, - lerp: lerp, - mapLinear: mapLinear, - normalize: normalize, - pingpong: pingpong, - radToDeg: radToDeg, - randFloat: randFloat, - randFloatSpread: randFloatSpread, - randInt: randInt, - seededRandom: seededRandom, - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - smootherstep: smootherstep, - smoothstep: smoothstep -}); + return this; + + } + + /** + * Rotates this vector around the given center by the given angle. + * + * @param {Vector2} center - The point around which to rotate. + * @param {number} angle - The angle to rotate, in radians. + * @return {Vector2} A reference to this vector. + */ + rotateAround( center, angle ) { -class Vector2 { + const c = Math.cos( angle ), s = Math.sin( angle ); - constructor( x = 0, y = 0 ) { + const x = this.x - center.x; + const y = this.y - center.y; - Vector2.prototype.isVector2 = true; + this.x = x * c - y * s + center.x; + this.y = x * s + y * c + center.y; - this.x = x; - this.y = y; + return this; } - get width() { + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector2} A reference to this vector. + */ + random() { - return this.x; + this.x = Math.random(); + this.y = Math.random(); + + return this; } - set width( value ) { + *[ Symbol.iterator ]() { - this.x = value; + yield this.x; + yield this.y; } - get height() { +} - return this.y; +/** + * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * + * Note that three.js expects Quaternions to be normalized. + * ```js + * const quaternion = new THREE.Quaternion(); + * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); + * + * const vector = new THREE.Vector3( 1, 0, 0 ); + * vector.applyQuaternion( quaternion ); + * ``` + */ +class Quaternion { - } + /** + * Constructs a new quaternion. + * + * @param {number} [x=0] - The x value of this quaternion. + * @param {number} [y=0] - The y value of this quaternion. + * @param {number} [z=0] - The z value of this quaternion. + * @param {number} [w=1] - The w value of this quaternion. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { - set height( value ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuaternion = true; - this.y = value; + this._x = x; + this._y = y; + this._z = z; + this._w = w; } - set( x, y ) { + /** + * Interpolates between two quaternions via SLERP. This implementation assumes the + * quaternion data are managed in flat arrays. + * + * @param {Array<number>} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array<number>} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array<number>} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @param {number} t - The interpolation factor in the range `[0,1]`. + * @see {@link Quaternion#slerp} + */ + static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - this.x = x; - this.y = y; + // fuzz-free, array-based Quaternion SLERP operation - return this; + let x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ]; - } + const x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; - setScalar( scalar ) { + if ( t === 0 ) { - this.x = scalar; - this.y = scalar; + dst[ dstOffset + 0 ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + return; - return this; + } - } + if ( t === 1 ) { - setX( x ) { + dst[ dstOffset + 0 ] = x1; + dst[ dstOffset + 1 ] = y1; + dst[ dstOffset + 2 ] = z1; + dst[ dstOffset + 3 ] = w1; + return; - this.x = x; + } - return this; + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - } + let s = 1 - t; + const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + dir = ( cos >= 0 ? 1 : -1 ), + sqrSin = 1 - cos * cos; - setY( y ) { + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { - this.y = y; + const sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); - return this; + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; - } + } - setComponent( index, value ) { + const tDir = t * dir; - switch ( index ) { + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'index is out of range: ' + index ); + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { + + const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; + + } } - return this; + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; } - getComponent( index ) { + /** + * Multiplies two quaternions. This implementation assumes the quaternion data are managed + * in flat arrays. + * + * @param {Array<number>} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array<number>} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array<number>} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @return {Array<number>} The destination array. + * @see {@link Quaternion#multiplyQuaternions}. + */ + static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - switch ( index ) { + const x0 = src0[ srcOffset0 ]; + const y0 = src0[ srcOffset0 + 1 ]; + const z0 = src0[ srcOffset0 + 2 ]; + const w0 = src0[ srcOffset0 + 3 ]; - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'index is out of range: ' + index ); + const x1 = src1[ srcOffset1 ]; + const y1 = src1[ srcOffset1 + 1 ]; + const z1 = src1[ srcOffset1 + 2 ]; + const w1 = src1[ srcOffset1 + 3 ]; - } + dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; + dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; + dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; + dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; + + return dst; } - clone() { + /** + * The x value of this quaternion. + * + * @type {number} + * @default 0 + */ + get x() { - return new this.constructor( this.x, this.y ); + return this._x; } - copy( v ) { - - this.x = v.x; - this.y = v.y; + set x( value ) { - return this; + this._x = value; + this._onChangeCallback(); } - add( v ) { - - this.x += v.x; - this.y += v.y; + /** + * The y value of this quaternion. + * + * @type {number} + * @default 0 + */ + get y() { - return this; + return this._y; } - addScalar( s ) { - - this.x += s; - this.y += s; + set y( value ) { - return this; + this._y = value; + this._onChangeCallback(); } - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; + /** + * The z value of this quaternion. + * + * @type {number} + * @default 0 + */ + get z() { - return this; + return this._z; } - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; + set z( value ) { - return this; + this._z = value; + this._onChangeCallback(); } - sub( v ) { - - this.x -= v.x; - this.y -= v.y; + /** + * The w value of this quaternion. + * + * @type {number} + * @default 1 + */ + get w() { - return this; + return this._w; } - subScalar( s ) { - - this.x -= s; - this.y -= s; + set w( value ) { - return this; + this._w = value; + this._onChangeCallback(); } - subVectors( a, b ) { + /** + * Sets the quaternion components. + * + * @param {number} x - The x value of this quaternion. + * @param {number} y - The y value of this quaternion. + * @param {number} z - The z value of this quaternion. + * @param {number} w - The w value of this quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + set( x, y, z, w ) { - this.x = a.x - b.x; - this.y = a.y - b.y; + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this._onChangeCallback(); return this; } - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; + /** + * Returns a new quaternion with copied values from this instance. + * + * @return {Quaternion} A clone of this instance. + */ + clone() { - return this; + return new this.constructor( this._x, this._y, this._z, this._w ); } - multiplyScalar( scalar ) { + /** + * Copies the values of the given quaternion to this instance. + * + * @param {Quaternion} quaternion - The quaternion to copy. + * @return {Quaternion} A reference to this quaternion. + */ + copy( quaternion ) { - this.x *= scalar; - this.y *= scalar; + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; + + this._onChangeCallback(); return this; } - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - - return this; + /** + * Sets this quaternion from the rotation specified by the given + * Euler angles. + * + * @param {Euler} euler - The Euler angles. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Quaternion} A reference to this quaternion. + */ + setFromEuler( euler, update = true ) { - } + const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - divideScalar( scalar ) { + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m - return this.multiplyScalar( 1 / scalar ); + const cos = Math.cos; + const sin = Math.sin; - } + const c1 = cos( x / 2 ); + const c2 = cos( y / 2 ); + const c3 = cos( z / 2 ); - applyMatrix3( m ) { + const s1 = sin( x / 2 ); + const s2 = sin( y / 2 ); + const s3 = sin( z / 2 ); - const x = this.x, y = this.y; - const e = m.elements; + switch ( order ) { - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; + case 'XYZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - return this; + case 'YXZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - } + case 'ZXY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - min( v ) { + case 'ZYX': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); + case 'YZX': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - return this; + case 'XZY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - } + default: + console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - max( v ) { + } - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); + if ( update === true ) this._onChangeCallback(); return this; } - clamp( min, max ) { + /** + * Sets this quaternion from the given axis and angle. + * + * @param {Vector3} axis - The normalized axis. + * @param {number} angle - The angle in radians. + * @return {Quaternion} A reference to this quaternion. + */ + setFromAxisAngle( axis, angle ) { - // assumes min < max, componentwise + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + + const halfAngle = angle / 2, s = Math.sin( halfAngle ); + + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this._onChangeCallback(); return this; } - clampScalar( minVal, maxVal ) { + /** + * Sets this quaternion from the given rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @return {Quaternion} A reference to this quaternion. + */ + setFromRotationMatrix( m ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - return this; + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - } + const te = m.elements, - clampLength( min, max ) { + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - const length = this.length(); + trace = m11 + m22 + m33; - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + if ( trace > 0 ) { - } + const s = 0.5 / Math.sqrt( trace + 1.0 ); - floor() { + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); + } else if ( m11 > m22 && m11 > m33 ) { - return this; + const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - } + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; - ceil() { + } else if ( m22 > m33 ) { - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); + const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - return this; + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; - } + } else { - round() { + const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this._onChangeCallback(); return this; } - roundToZero() { + /** + * Sets this quaternion to the rotation required to rotate the direction vector + * `vFrom` to the direction vector `vTo`. + * + * @param {Vector3} vFrom - The first (normalized) direction vector. + * @param {Vector3} vTo - The second (normalized) direction vector. + * @return {Quaternion} A reference to this quaternion. + */ + setFromUnitVectors( vFrom, vTo ) { - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); + // assumes direction vectors vFrom and vTo are normalized - return this; + let r = vFrom.dot( vTo ) + 1; - } + if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286 - negate() { + // vFrom and vTo point in opposite directions - this.x = - this.x; - this.y = - this.y; + r = 0; - return this; + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - } + this._x = - vFrom.y; + this._y = vFrom.x; + this._z = 0; + this._w = r; - dot( v ) { + } else { - return this.x * v.x + this.y * v.y; + this._x = 0; + this._y = - vFrom.z; + this._z = vFrom.y; + this._w = r; - } + } - cross( v ) { + } else { - return this.x * v.y - this.y * v.x; + // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - } + this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; + this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; + this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; + this._w = r; - lengthSq() { + } - return this.x * this.x + this.y * this.y; + return this.normalize(); } - length() { + /** + * Returns the angle between this quaternion and the given one in radians. + * + * @param {Quaternion} q - The quaternion to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( q ) { - return Math.sqrt( this.x * this.x + this.y * this.y ); + return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) ); } - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ); + /** + * Rotates this quaternion by a given angular step to the given quaternion. + * The method ensures that the final quaternion will not overshoot `q`. + * + * @param {Quaternion} q - The target quaternion. + * @param {number} step - The angular step in radians. + * @return {Quaternion} A reference to this quaternion. + */ + rotateTowards( q, step ) { - } + const angle = this.angleTo( q ); - normalize() { + if ( angle === 0 ) return this; - return this.divideScalar( this.length() || 1 ); + const t = Math.min( 1, step / angle ); - } + this.slerp( q, t ); - angle() { + return this; - // computes the angle in radians with respect to the positive x-axis + } - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; + /** + * Sets this quaternion to the identity quaternion; that is, to the + * quaternion that represents "no rotation". + * + * @return {Quaternion} A reference to this quaternion. + */ + identity() { - return angle; + return this.set( 0, 0, 0, 1 ); } - angleTo( v ) { + /** + * Inverts this quaternion via {@link Quaternion#conjugate}. The + * quaternion is assumed to have unit length. + * + * @return {Quaternion} A reference to this quaternion. + */ + invert() { - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + return this.conjugate(); - if ( denominator === 0 ) return Math.PI / 2; + } - const theta = this.dot( v ) / denominator; + /** + * Returns the rotational conjugate of this quaternion. The conjugate of a + * quaternion represents the same rotation in the opposite direction about + * the rotational axis. + * + * @return {Quaternion} A reference to this quaternion. + */ + conjugate() { - // clamp, to handle numerical problems + this._x *= -1; + this._y *= -1; + this._z *= -1; + + this._onChangeCallback(); - return Math.acos( clamp( theta, - 1, 1 ) ); + return this; } - distanceTo( v ) { + /** + * Calculates the dot product of this quaternion and the given one. + * + * @param {Quaternion} v - The quaternion to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { - return Math.sqrt( this.distanceToSquared( v ) ); + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; } - distanceToSquared( v ) { + /** + * Computes the squared Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. This can be useful if you are comparing the + * lengths of two quaternions, as this is a slightly more efficient calculation than + * {@link Quaternion#length}. + * + * @return {number} The squared Euclidean length. + */ + lengthSq() { - const dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; } - manhattanDistanceTo( v ) { + /** + * Computes the Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. + * + * @return {number} The Euclidean length. + */ + length() { - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); } - setLength( length ) { + /** + * Normalizes this quaternion - that is, calculated the quaternion that performs + * the same rotation as this one, but has a length equal to `1`. + * + * @return {Quaternion} A reference to this quaternion. + */ + normalize() { - return this.normalize().multiplyScalar( length ); + let l = this.length(); - } + if ( l === 0 ) { - lerp( v, alpha ) { + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; + } else { - return this; + l = 1 / l; - } + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; - lerpVectors( v1, v2, alpha ) { + } - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; + this._onChangeCallback(); return this; } - equals( v ) { + /** + * Multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiply( q ) { - return ( ( v.x === this.x ) && ( v.y === this.y ) ); + return this.multiplyQuaternions( this, q ); } - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; + /** + * Pre-multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + premultiply( q ) { - return this; + return this.multiplyQuaternions( q, this ); } - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; + /** + * Multiplies the given quaternions and stores the result in this instance. + * + * @param {Quaternion} a - The first quaternion. + * @param {Quaternion} b - The second quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiplyQuaternions( a, b ) { - return array; + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - } + const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - fromBufferAttribute( attribute, index ) { + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); + this._onChangeCallback(); return this; } - rotateAround( center, angle ) { + /** + * Performs a spherical linear interpolation between quaternions. + * + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerp( qb, t ) { - const c = Math.cos( angle ), s = Math.sin( angle ); + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); - const x = this.x - center.x; - const y = this.y - center.y; + const x = this._x, y = this._y, z = this._z, w = this._w; - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - return this; + let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - } + if ( cosHalfTheta < 0 ) { - random() { + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; - this.x = Math.random(); - this.y = Math.random(); + cosHalfTheta = - cosHalfTheta; - return this; + } else { - } + this.copy( qb ); - *[ Symbol.iterator ]() { + } - yield this.x; - yield this.y; + if ( cosHalfTheta >= 1.0 ) { - } + this._w = w; + this._x = x; + this._y = y; + this._z = z; -} + return this; -class Matrix3 { + } - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - Matrix3.prototype.isMatrix3 = true; + if ( sqrSinHalfTheta <= Number.EPSILON ) { - this.elements = [ + const s = 1 - t; + this._w = s * w + t * this._w; + this._x = s * x + t * this._x; + this._y = s * y + t * this._y; + this._z = s * z + t * this._z; - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + this.normalize(); // normalize calls _onChangeCallback() - ]; + return this; - if ( n11 !== undefined ) { + } - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); + const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); + const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - } + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); - } + this._onChangeCallback(); - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + return this; - const te = this.elements; + } - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; + /** + * Performs a spherical linear interpolation between the given quaternions + * and stores the result in this quaternion. + * + * @param {Quaternion} qa - The source quaternion. + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerpQuaternions( qa, qb, t ) { - return this; + return this.copy( qa ).slerp( qb, t ); } - identity() { + /** + * Sets this quaternion to a uniformly random, normalized quaternion. + * + * @return {Quaternion} A reference to this quaternion. + */ + random() { - this.set( + // Ken Shoemake + // Uniform random rotations + // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + const theta1 = 2 * Math.PI * Math.random(); + const theta2 = 2 * Math.PI * Math.random(); + + const x0 = Math.random(); + const r1 = Math.sqrt( 1 - x0 ); + const r2 = Math.sqrt( x0 ); + return this.set( + r1 * Math.sin( theta1 ), + r1 * Math.cos( theta1 ), + r2 * Math.sin( theta2 ), + r2 * Math.cos( theta2 ), ); - return this; + } + + /** + * Returns `true` if this quaternion is equal with the given one. + * + * @param {Quaternion} quaternion - The quaternion to test for equality. + * @return {boolean} Whether this quaternion is equal with the given one. + */ + equals( quaternion ) { + + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); } - copy( m ) { + /** + * Sets this quaternion's components from the given array. + * + * @param {Array<number>} array - An array holding the quaternion component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Quaternion} A reference to this quaternion. + */ + fromArray( array, offset = 0 ) { - const te = this.elements; - const me = m.elements; + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; + this._onChangeCallback(); return this; } - extractBasis( xAxis, yAxis, zAxis ) { + /** + * Writes the components of this quaternion to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the quaternion components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The quaternion components. + */ + toArray( array = [], offset = 0 ) { - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; - return this; + return array; } - setFromMatrix4( m ) { - - const me = m.elements; - - this.set( + /** + * Sets the components of this quaternion from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data. + * @param {number} index - The index into the attribute. + * @return {Quaternion} A reference to this quaternion. + */ + fromBufferAttribute( attribute, index ) { - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] + this._x = attribute.getX( index ); + this._y = attribute.getY( index ); + this._z = attribute.getZ( index ); + this._w = attribute.getW( index ); - ); + this._onChangeCallback(); return this; } - multiply( m ) { + /** + * This methods defines the serialization result of this class. Returns the + * numerical elements of this quaternion in an array of format `[x, y, z, w]`. + * + * @return {Array<number>} The serialized quaternion. + */ + toJSON() { - return this.multiplyMatrices( this, m ); + return this.toArray(); } - premultiply( m ) { - - return this.multiplyMatrices( m, this ); + _onChange( callback ) { - } + this._onChangeCallback = callback; - multiplyMatrices( a, b ) { + return this; - const ae = a.elements; - const be = b.elements; - const te = this.elements; + } - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; + _onChangeCallback() {} - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; + *[ Symbol.iterator ]() { - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; + yield this._x; + yield this._y; + yield this._z; + yield this._w; - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; + } - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; +} - return this; +/** + * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers + * (labeled x, y and z), which can be used to represent a number of things, such as: + * + * - A point in 3D space. + * - A direction and length in 3D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)` + * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`. + * - Any arbitrary ordered triplet of numbers. + * + * There are other things a 3D vector can be used to represent, such as + * momentum vectors and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y, z)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector3( 0, 1, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0) + * const b = new THREE.Vector3( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector3 { - } + /** + * Constructs a new 3D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + */ + constructor( x = 0, y = 0, z = 0 ) { - multiplyScalar( s ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector3.prototype.isVector3 = true; - const te = this.elements; + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; - return this; + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; } - determinant() { + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @return {Vector3} A reference to this vector. + */ + set( x, y, z ) { - const te = this.elements; + if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + this.x = x; + this.y = y; + this.z = z; - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + return this; } - invert() { + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector3} A reference to this vector. + */ + setScalar( scalar ) { - const te = this.elements, + this.x = scalar; + this.y = scalar; + this.z = scalar; - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], + return this; - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, + } - det = n11 * t11 + n21 * t12 + n31 * t13; + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector3} A reference to this vector. + */ + setX( x ) { - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + this.x = x; - const detInv = 1 / det; + return this; - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + } - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector3} A reference to this vector. + */ + setY( y ) { - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; + this.y = y; return this; } - transpose() { - - let tmp; - const m = this.elements; + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector3} A reference to this vector. + */ + setZ( z ) { - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + this.z = z; return this; } - getNormalMatrix( matrix4 ) { - - return this.setFromMatrix4( matrix4 ).invert().transpose(); - - } + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @param {number} value - The value to set. + * @return {Vector3} A reference to this vector. + */ + setComponent( index, value ) { - transposeIntoArray( r ) { + switch ( index ) { - const m = this.elements; + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; + } return this; } - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @return {number} A vector component value. + */ + getComponent( index ) { - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); + switch ( index ) { - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); - return this; + } } - // - - scale( sx, sy ) { - - this.premultiply( _m3.makeScale( sx, sy ) ); + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector3} A clone of this instance. + */ + clone() { - return this; + return new this.constructor( this.x, this.y, this.z ); } - rotate( theta ) { + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3} v - The vector to copy. + * @return {Vector3} A reference to this vector. + */ + copy( v ) { - this.premultiply( _m3.makeRotation( - theta ) ); + this.x = v.x; + this.y = v.y; + this.z = v.z; return this; } - translate( tx, ty ) { + /** + * Adds the given vector to this instance. + * + * @param {Vector3} v - The vector to add. + * @return {Vector3} A reference to this vector. + */ + add( v ) { - this.premultiply( _m3.makeTranslation( tx, ty ) ); + this.x += v.x; + this.y += v.y; + this.z += v.z; return this; } - // for 2D Transforms - - makeTranslation( x, y ) { + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector3} A reference to this vector. + */ + addScalar( s ) { - if ( x.isVector2 ) { + this.x += s; + this.y += s; + this.z += s; - this.set( + return this; - 1, 0, x.x, - 0, 1, x.y, - 0, 0, 1 + } - ); + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + addVectors( a, b ) { - } else { + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; - this.set( + return this; - 1, 0, x, - 0, 1, y, - 0, 0, 1 + } - ); + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector3|Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector3} A reference to this vector. + */ + addScaledVector( v, s ) { - } + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; return this; } - makeRotation( theta ) { - - // counterclockwise - - const c = Math.cos( theta ); - const s = Math.sin( theta ); - - this.set( - - c, - s, 0, - s, c, 0, - 0, 0, 1 + /** + * Subtracts the given vector from this instance. + * + * @param {Vector3} v - The vector to subtract. + * @return {Vector3} A reference to this vector. + */ + sub( v ) { - ); + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; return this; } - makeScale( x, y ) { - - this.set( - - x, 0, 0, - 0, y, 0, - 0, 0, 1 + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector3} A reference to this vector. + */ + subScalar( s ) { - ); + this.x -= s; + this.y -= s; + this.z -= s; return this; } - // + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + subVectors( a, b ) { - equals( matrix ) { + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; - const te = this.elements; - const me = matrix.elements; + return this; - for ( let i = 0; i < 9; i ++ ) { + } - if ( te[ i ] !== me[ i ] ) return false; + /** + * Multiplies the given vector with this instance. + * + * @param {Vector3} v - The vector to multiply. + * @return {Vector3} A reference to this vector. + */ + multiply( v ) { - } + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; - return true; + return this; } - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector3} A reference to this vector. + */ + multiplyScalar( scalar ) { - } + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; return this; } - toArray( array = [], offset = 0 ) { + /** + * Multiplies the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + multiplyVectors( a, b ) { - const te = this.elements; + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; + return this; - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; + } - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; + /** + * Applies the given Euler rotation to this vector. + * + * @param {Euler} euler - The Euler angles. + * @return {Vector3} A reference to this vector. + */ + applyEuler( euler ) { - return array; + return this.applyQuaternion( _quaternion$2.setFromEuler( euler ) ); } - clone() { + /** + * Applies a rotation specified by an axis and an angle to this vector. + * + * @param {Vector3} axis - A normalized vector representing the rotation axis. + * @param {number} angle - The angle in radians. + * @return {Vector3} A reference to this vector. + */ + applyAxisAngle( axis, angle ) { - return new this.constructor().fromArray( this.elements ); + return this.applyQuaternion( _quaternion$2.setFromAxisAngle( axis, angle ) ); } -} + /** + * Multiplies this vector with the given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Vector3} A reference to this vector. + */ + applyMatrix3( m ) { -const _m3 = /*@__PURE__*/ new Matrix3(); + const x = this.x, y = this.y, z = this.z; + const e = m.elements; -function arrayNeedsUint32( array ) { + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - // assumes larger values usually on last + return this; - for ( let i = array.length - 1; i >= 0; -- i ) { + } - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 + /** + * Multiplies this vector by the given normal matrix and normalizes + * the result. + * + * @param {Matrix3} m - The normal matrix. + * @return {Vector3} A reference to this vector. + */ + applyNormalMatrix( m ) { - } + return this.applyMatrix3( m ).normalize(); - return false; + } -} + /** + * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and + * divides by perspective. + * + * @param {Matrix4} m - The matrix to apply. + * @return {Vector3} A reference to this vector. + */ + applyMatrix4( m ) { -const TYPED_ARRAYS = { - Int8Array: Int8Array, - Uint8Array: Uint8Array, - Uint8ClampedArray: Uint8ClampedArray, - Int16Array: Int16Array, - Uint16Array: Uint16Array, - Int32Array: Int32Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array -}; + const x = this.x, y = this.y, z = this.z; + const e = m.elements; -function getTypedArray( type, buffer ) { + const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - return new TYPED_ARRAYS[ type ]( buffer ); + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; -} + return this; -function createElementNS( name ) { + } - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); + /** + * Applies the given Quaternion to this vector. + * + * @param {Quaternion} q - The Quaternion. + * @return {Vector3} A reference to this vector. + */ + applyQuaternion( q ) { -} + // quaternion q is assumed to have unit length -function createCanvasElement() { + const vx = this.x, vy = this.y, vz = this.z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; + // t = 2 * cross( q.xyz, v ); + const tx = 2 * ( qy * vz - qz * vy ); + const ty = 2 * ( qz * vx - qx * vz ); + const tz = 2 * ( qx * vy - qy * vx ); -} + // v + q.w * t + cross( q.xyz, t ); + this.x = vx + qw * tx + qy * tz - qz * ty; + this.y = vy + qw * ty + qz * tx - qx * tz; + this.z = vz + qw * tz + qx * ty - qy * tx; -const _cache = {}; + return this; -function warnOnce( message ) { + } - if ( message in _cache ) return; + /** + * Projects this vector from world space into the camera's normalized + * device coordinate (NDC) space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + project( camera ) { - _cache[ message ] = true; + return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - console.warn( message ); + } -} + /** + * Unprojects this vector from the camera's normalized device coordinate (NDC) + * space into world space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + unproject( camera ) { -/** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ + return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( - 0.8224621, 0.177538, 0.0, - 0.0331941, 0.9668058, 0.0, - 0.0170827, 0.0723974, 0.9105199, -); + } -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().set( - 1.2249401, - 0.2249404, 0.0, - - 0.0420569, 1.0420571, 0.0, - - 0.0196376, - 0.0786361, 1.0982735 -); + /** + * Transforms the direction of this vector by a matrix (the upper left 3 x 3 + * subset of the given 4x4 matrix and then normalizes the result. + * + * @param {Matrix4} m - The matrix. + * @return {Vector3} A reference to this vector. + */ + transformDirection( m ) { -/** - * Defines supported color spaces by transfer function and primaries, - * and provides conversions to/from the Linear-sRGB reference space. - */ -const COLOR_SPACES = { - [ LinearSRGBColorSpace ]: { - transfer: LinearTransfer, - primaries: Rec709Primaries, - toReference: ( color ) => color, - fromReference: ( color ) => color, - }, - [ SRGBColorSpace ]: { - transfer: SRGBTransfer, - primaries: Rec709Primaries, - toReference: ( color ) => color.convertSRGBToLinear(), - fromReference: ( color ) => color.convertLinearToSRGB(), - }, - [ LinearDisplayP3ColorSpace ]: { - transfer: LinearTransfer, - primaries: P3Primaries, - toReference: ( color ) => color.applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ), - }, - [ DisplayP3ColorSpace ]: { - transfer: SRGBTransfer, - primaries: P3Primaries, - toReference: ( color ) => color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(), - }, -}; + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction -const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] ); + const x = this.x, y = this.y, z = this.z; + const e = m.elements; -const ColorManagement = { + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - enabled: true, + return this.normalize(); - _workingColorSpace: LinearSRGBColorSpace, + } - get workingColorSpace() { + /** + * Divides this instance by the given vector. + * + * @param {Vector3} v - The vector to divide. + * @return {Vector3} A reference to this vector. + */ + divide( v ) { - return this._workingColorSpace; + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; - }, + return this; - set workingColorSpace( colorSpace ) { + } - if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) { + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector3} A reference to this vector. + */ + divideScalar( scalar ) { - throw new Error( `Unsupported working color space, "${ colorSpace }".` ); + return this.multiplyScalar( 1 / scalar ); - } + } - this._workingColorSpace = colorSpace; + /** + * If this vector's x, y or z value is greater than the given vector's x, y or z + * value, replace that value with the corresponding min value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + min( v ) { - }, + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); - convert: function ( color, sourceColorSpace, targetColorSpace ) { + return this; - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { + } - return color; + /** + * If this vector's x, y or z value is less than the given vector's x, y or z + * value, replace that value with the corresponding max value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + max( v ) { - } + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); - const sourceToReference = COLOR_SPACES[ sourceColorSpace ].toReference; - const targetFromReference = COLOR_SPACES[ targetColorSpace ].fromReference; + return this; - return targetFromReference( sourceToReference( color ) ); + } - }, + /** + * If this vector's x, y or z value is greater than the max vector's x, y or z + * value, it is replaced by the corresponding value. + * If this vector's x, y or z value is less than the min vector's x, y or z value, + * it is replaced by the corresponding value. + * + * @param {Vector3} min - The minimum x, y and z values. + * @param {Vector3} max - The maximum x, y and z values in the desired range. + * @return {Vector3} A reference to this vector. + */ + clamp( min, max ) { - fromWorkingColorSpace: function ( color, targetColorSpace ) { + // assumes min < max, componentwise - return this.convert( color, this._workingColorSpace, targetColorSpace ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); - }, + return this; - toWorkingColorSpace: function ( color, sourceColorSpace ) { + } - return this.convert( color, sourceColorSpace, this._workingColorSpace ); + /** + * If this vector's x, y or z values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y or z values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { - }, + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); - getPrimaries: function ( colorSpace ) { + return this; - return COLOR_SPACES[ colorSpace ].primaries; + } - }, + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampLength( min, max ) { - getTransfer: function ( colorSpace ) { + const length = this.length(); - if ( colorSpace === NoColorSpace ) return LinearTransfer; + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - return COLOR_SPACES[ colorSpace ].transfer; + } - }, + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + floor() { -}; + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + return this; -function SRGBToLinear( c ) { + } - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + ceil() { -} + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); -function LinearToSRGB( c ) { + return this; - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; + } -} + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector3} A reference to this vector. + */ + round() { -let _canvas; + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); -class ImageUtils { + return this; - static getDataURL( image ) { + } - if ( /^data:/i.test( image.src ) ) { + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector3} A reference to this vector. + */ + roundToZero() { - return image.src; + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); - } + return this; - if ( typeof HTMLCanvasElement === 'undefined' ) { + } - return image.src; + /** + * Inverts this vector - i.e. sets x = -x, y = -y and z = -z. + * + * @return {Vector3} A reference to this vector. + */ + negate() { - } + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; - let canvas; + return this; - if ( image instanceof HTMLCanvasElement ) { + } - canvas = image; + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { - } else { + return this.x * v.x + this.y * v.y + this.z * v.z; - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); + } - _canvas.width = image.width; - _canvas.height = image.height; + // TODO lengthSquared? - const context = _canvas.getContext( '2d' ); + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { - if ( image instanceof ImageData ) { + return this.x * this.x + this.y * this.y + this.z * this.z; - context.putImageData( image, 0, 0 ); + } - } else { + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z). + * + * @return {number} The length of this vector. + */ + length() { - context.drawImage( image, 0, 0, image.width, image.height ); + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - } + } - canvas = _canvas; + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { - } + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - if ( canvas.width > 2048 || canvas.height > 2048 ) { + } - console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector3} A reference to this vector. + */ + normalize() { - return canvas.toDataURL( 'image/jpeg', 0.6 ); + return this.divideScalar( this.length() || 1 ); - } else { + } - return canvas.toDataURL( 'image/png' ); + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector3} A reference to this vector. + */ + setLength( length ) { - } + return this.normalize().multiplyScalar( length ); } - static sRGBToLinear( image ) { + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector3} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerp( v, alpha ) { - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; - const canvas = createElementNS( 'canvas' ); + return this; - canvas.width = image.width; - canvas.height = image.height; + } - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector3} v1 - The first vector. + * @param {Vector3} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; - for ( let i = 0; i < data.length; i ++ ) { + return this; - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; + } - } + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the cross product with. + * @return {Vector3} The result of the cross product. + */ + cross( v ) { - context.putImageData( imageData, 0, 0 ); + return this.crossVectors( this, v ); - return canvas; + } - } else if ( image.data ) { + /** + * Calculates the cross product of the given vectors and stores the result + * in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + crossVectors( a, b ) { - const data = image.data.slice( 0 ); + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; - for ( let i = 0; i < data.length; i ++ ) { + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { + return this; - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); + } - } else { + /** + * Projects this vector onto the given one. + * + * @param {Vector3} v - The vector to project to. + * @return {Vector3} A reference to this vector. + */ + projectOnVector( v ) { - // assuming float + const denominator = v.lengthSq(); - data[ i ] = SRGBToLinear( data[ i ] ); + if ( denominator === 0 ) return this.set( 0, 0, 0 ); - } + const scalar = v.dot( this ) / denominator; - } + return this.copy( v ).multiplyScalar( scalar ); - return { - data: data, - width: image.width, - height: image.height - }; + } - } else { + /** + * Projects this vector onto a plane by subtracting this + * vector projected onto the plane's normal from this vector. + * + * @param {Vector3} planeNormal - The plane normal. + * @return {Vector3} A reference to this vector. + */ + projectOnPlane( planeNormal ) { - console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; + _vector$8.copy( this ).projectOnVector( planeNormal ); - } + return this.sub( _vector$8 ); } -} - -let _sourceId = 0; + /** + * Reflects this vector off a plane orthogonal to the given normal vector. + * + * @param {Vector3} normal - The (normalized) normal vector. + * @return {Vector3} A reference to this vector. + */ + reflect( normal ) { -class Source { + return this.sub( _vector$8.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - constructor( data = null ) { + } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector3} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { - this.isSource = true; + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - Object.defineProperty( this, 'id', { value: _sourceId ++ } ); + if ( denominator === 0 ) return Math.PI / 2; - this.uuid = generateUUID(); + const theta = this.dot( v ) / denominator; - this.data = data; - this.dataReady = true; + // clamp, to handle numerical problems - this.version = 0; + return Math.acos( clamp( theta, -1, 1 ) ); } - set needsUpdate( value ) { + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { - if ( value === true ) this.version ++; + return Math.sqrt( this.distanceToSquared( v ) ); } - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector3} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - return meta.images[ this.uuid ]; + return dx * dx + dy * dy + dz * dz; - } + } - const output = { - uuid: this.uuid, - url: '' - }; + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { - const data = this.data; + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - if ( data !== null ) { + } - let url; + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {Spherical} s - The spherical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromSpherical( s ) { - if ( Array.isArray( data ) ) { + return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); - // cube texture + } - url = []; + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {number} radius - The radius. + * @param {number} phi - The phi angle in radians. + * @param {number} theta - The theta angle in radians. + * @return {Vector3} A reference to this vector. + */ + setFromSphericalCoords( radius, phi, theta ) { - for ( let i = 0, l = data.length; i < l; i ++ ) { + const sinPhiRadius = Math.sin( phi ) * radius; - if ( data[ i ].isDataTexture ) { + this.x = sinPhiRadius * Math.sin( theta ); + this.y = Math.cos( phi ) * radius; + this.z = sinPhiRadius * Math.cos( theta ); - url.push( serializeImage( data[ i ].image ) ); + return this; - } else { + } - url.push( serializeImage( data[ i ] ) ); + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {Cylindrical} c - The cylindrical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromCylindrical( c ) { - } + return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); - } + } - } else { + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle in radians. + * @param {number} y - The y value. + * @return {Vector3} A reference to this vector. + */ + setFromCylindricalCoords( radius, theta, y ) { - // texture + this.x = radius * Math.sin( theta ); + this.y = y; + this.z = radius * Math.cos( theta ); - url = serializeImage( data ); + return this; - } + } - output.url = url; + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixPosition( m ) { - } + const e = m.elements; - if ( ! isRootObject ) { + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; - meta.images[ this.uuid ] = output; + return this; - } + } - return output; + /** + * Sets the vector components to the scale elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixScale( m ) { - } + const sx = this.setFromMatrixColumn( m, 0 ).length(); + const sy = this.setFromMatrixColumn( m, 1 ).length(); + const sz = this.setFromMatrixColumn( m, 2 ).length(); -} + this.x = sx; + this.y = sy; + this.z = sz; -function serializeImage( image ) { + return this; - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + } - // default images + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix4} m - The 4x4 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixColumn( m, index ) { - return ImageUtils.getDataURL( image ); + return this.fromArray( m.elements, index * 4 ); - } else { + } - if ( image.data ) { + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix3} m - The 3x3 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrix3Column( m, index ) { - // images of DataTexture + return this.fromArray( m.elements, index * 3 ); - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; + } - } else { + /** + * Sets the vector components from the given Euler angles. + * + * @param {Euler} e - The Euler angles to set. + * @return {Vector3} A reference to this vector. + */ + setFromEuler( e ) { - console.warn( 'THREE.Texture: Unable to serialize Texture.' ); - return {}; + this.x = e._x; + this.y = e._y; + this.z = e._z; - } + return this; } -} + /** + * Sets the vector components from the RGB components of the + * given color. + * + * @param {Color} c - The color to set. + * @return {Vector3} A reference to this vector. + */ + setFromColor( c ) { -let _textureId = 0; + this.x = c.r; + this.y = c.g; + this.z = c.b; -class Texture extends EventDispatcher { + return this; - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { + } - super(); + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector3} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - this.isTexture = true; + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - Object.defineProperty( this, 'id', { value: _textureId ++ } ); + } - this.uuid = generateUUID(); + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]` + * and z value to be `array[ offset + 2 ]`. + * + * @param {Array<number>} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector3} A reference to this vector. + */ + fromArray( array, offset = 0 ) { - this.name = ''; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; - this.source = new Source( image ); - this.mipmaps = []; + return this; - this.mapping = mapping; - this.channel = 0; + } - this.wrapS = wrapS; - this.wrapT = wrapT; + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The vector components. + */ + toArray( array = [], offset = 0 ) { - this.magFilter = magFilter; - this.minFilter = minFilter; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; - this.anisotropy = anisotropy; + return array; - this.format = format; - this.internalFormat = null; - this.type = type; + } - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector3} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + return this; - this.colorSpace = colorSpace; + } - this.userData = {}; + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector3} A reference to this vector. + */ + random() { - this.version = 0; - this.onUpdate = null; + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); - this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not - this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) + return this; } - get image() { + /** + * Sets this vector to a uniformly random point on a unit sphere. + * + * @return {Vector3} A reference to this vector. + */ + randomDirection() { - return this.source.data; + // https://mathworld.wolfram.com/SpherePointPicking.html - } + const theta = Math.random() * Math.PI * 2; + const u = Math.random() * 2 - 1; + const c = Math.sqrt( 1 - u * u ); - set image( value = null ) { + this.x = c * Math.cos( theta ); + this.y = u; + this.z = c * Math.sin( theta ); - this.source.data = value; + return this; } - updateMatrix() { + *[ Symbol.iterator ]() { - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); + yield this.x; + yield this.y; + yield this.z; } - clone() { - - return new this.constructor().copy( this ); +} - } +const _vector$8 = /*@__PURE__*/ new Vector3(); +const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - copy( source ) { +/** + * Represents a 3x3 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix(); + * m.set( 11, 12, 13, + * 21, 22, 23, + * 31, 32, 33 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, + * 12, 22, 32, + * 13, 23, 33 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix3 { - this.name = source.name; + /** + * Constructs a new 3x3 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + */ + constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix3.prototype.isMatrix3 = true; - this.mapping = source.mapping; - this.channel = source.channel; + /** + * A column-major list of matrix values. + * + * @type {Array<number>} + */ + this.elements = [ - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; + ]; - this.anisotropy = source.anisotropy; + if ( n11 !== undefined ) { - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; + this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; + } - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); + } - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @return {Matrix3} A reference to this matrix. + */ + set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - this.userData = JSON.parse( JSON.stringify( source.userData ) ); + const te = this.elements; - this.needsUpdate = true; + te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; + te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; + te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; return this; } - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + /** + * Sets this matrix to the 3x3 identity matrix. + * + * @return {Matrix3} A reference to this matrix. + */ + identity() { - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { + this.set( - return meta.textures[ this.uuid ]; + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - } + ); - const output = { + return this; - metadata: { - version: 4.6, - type: 'Texture', - generator: 'Texture.toJSON' - }, + } - uuid: this.uuid, - name: this.name, + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix3} m - The matrix to copy. + * @return {Matrix3} A reference to this matrix. + */ + copy( m ) { - image: this.source.toJSON( meta ).uuid, + const te = this.elements; + const me = m.elements; - mapping: this.mapping, - channel: this.channel, + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; + te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; + te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, + return this; - wrap: [ this.wrapS, this.wrapT ], + } - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - colorSpace: this.colorSpace, + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix3} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, + xAxis.setFromMatrix3Column( this, 0 ); + yAxis.setFromMatrix3Column( this, 1 ); + zAxis.setFromMatrix3Column( this, 2 ); - flipY: this.flipY, + return this; - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment + } - }; + /** + * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + setFromMatrix4( m ) { - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; + const me = m.elements; - if ( ! isRootObject ) { + this.set( - meta.textures[ this.uuid ] = output; + me[ 0 ], me[ 4 ], me[ 8 ], + me[ 1 ], me[ 5 ], me[ 9 ], + me[ 2 ], me[ 6 ], me[ 10 ] - } + ); - return output; + return this; } - dispose() { + /** + * Post-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + multiply( m ) { - this.dispatchEvent( { type: 'dispose' } ); + return this.multiplyMatrices( this, m ); } - transformUv( uv ) { - - if ( this.mapping !== UVMapping ) return uv; - - uv.applyMatrix3( this.matrix ); - - if ( uv.x < 0 || uv.x > 1 ) { - - switch ( this.wrapS ) { + /** + * Pre-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + premultiply( m ) { - case RepeatWrapping: + return this.multiplyMatrices( m, this ); - uv.x = uv.x - Math.floor( uv.x ); - break; + } - case ClampToEdgeWrapping: + /** + * Multiples the given 3x3 matrices and stores the result + * in this matrix. + * + * @param {Matrix3} a - The first matrix. + * @param {Matrix3} b - The second matrix. + * @return {Matrix3} A reference to this matrix. + */ + multiplyMatrices( a, b ) { - uv.x = uv.x < 0 ? 0 : 1; - break; + const ae = a.elements; + const be = b.elements; + const te = this.elements; - case MirroredRepeatWrapping: + const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; + const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; + const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { + const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; + const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; + const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - uv.x = Math.ceil( uv.x ) - uv.x; + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; + te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; + te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - } else { + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; + te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; + te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - uv.x = uv.x - Math.floor( uv.x ); + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; + te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; + te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - } + return this; - break; + } - } + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix3} A reference to this matrix. + */ + multiplyScalar( s ) { - } + const te = this.elements; - if ( uv.y < 0 || uv.y > 1 ) { + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; - switch ( this.wrapT ) { + return this; - case RepeatWrapping: + } - uv.y = uv.y - Math.floor( uv.y ); - break; + /** + * Computes and returns the determinant of this matrix. + * + * @return {number} The determinant. + */ + determinant() { - case ClampToEdgeWrapping: + const te = this.elements; - uv.y = uv.y < 0 ? 0 : 1; - break; + const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; - case MirroredRepeatWrapping: + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + } - uv.y = Math.ceil( uv.y ) - uv.y; + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix3} A reference to this matrix. + */ + invert() { - } else { + const te = this.elements, - uv.y = uv.y - Math.floor( uv.y ); + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], + n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], + n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - } + t11 = n33 * n22 - n32 * n23, + t12 = n32 * n13 - n33 * n12, + t13 = n23 * n12 - n22 * n13, - break; + det = n11 * t11 + n21 * t12 + n31 * t13; - } + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - } + const detInv = 1 / det; - if ( this.flipY ) { + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; + te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - uv.y = 1 - uv.y; + te[ 3 ] = t12 * detInv; + te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; + te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - } + te[ 6 ] = t13 * detInv; + te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; + te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; - return uv; + return this; } - set needsUpdate( value ) { + /** + * Transposes this matrix in place. + * + * @return {Matrix3} A reference to this matrix. + */ + transpose() { - if ( value === true ) { + let tmp; + const m = this.elements; - this.version ++; - this.source.needsUpdate = true; + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; - } + return this; } -} + /** + * Computes the normal matrix which is the inverse transpose of the upper + * left 3x3 portion of the given 4x4 matrix. + * + * @param {Matrix4} matrix4 - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + getNormalMatrix( matrix4 ) { -Texture.DEFAULT_IMAGE = null; -Texture.DEFAULT_MAPPING = UVMapping; -Texture.DEFAULT_ANISOTROPY = 1; + return this.setFromMatrix4( matrix4 ).invert().transpose(); -class Vector4 { + } - constructor( x = 0, y = 0, z = 0, w = 1 ) { + /** + * Transposes this matrix into the supplied array, and returns itself unchanged. + * + * @param {Array<number>} r - An array to store the transposed matrix elements. + * @return {Matrix3} A reference to this matrix. + */ + transposeIntoArray( r ) { - Vector4.prototype.isVector4 = true; + const m = this.elements; - this.x = x; - this.y = y; - this.z = z; - this.w = w; + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; - } + return this; - get width() { + } - return this.z; + /** + * Sets the UV transform matrix from offset, repeat, rotation, and center. + * + * @param {number} tx - Offset x. + * @param {number} ty - Offset y. + * @param {number} sx - Repeat x. + * @param {number} sy - Repeat y. + * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise. + * @param {number} cx - Center x of rotation. + * @param {number} cy - Center y of rotation + * @return {Matrix3} A reference to this matrix. + */ + setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - } + const c = Math.cos( rotation ); + const s = Math.sin( rotation ); - set width( value ) { + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 1 + ); - this.z = value; + return this; } - get height() { + /** + * Scales this matrix with the given scalar values. + * + * @param {number} sx - The amount to scale in the X axis. + * @param {number} sy - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + scale( sx, sy ) { - return this.w; + this.premultiply( _m3.makeScale( sx, sy ) ); + + return this; } - set height( value ) { + /** + * Rotates this matrix by the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + rotate( theta ) { - this.w = value; + this.premultiply( _m3.makeRotation( - theta ) ); + + return this; } - set( x, y, z, w ) { + /** + * Translates this matrix by the given scalar values. + * + * @param {number} tx - The amount to translate in the X axis. + * @param {number} ty - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + translate( tx, ty ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + this.premultiply( _m3.makeTranslation( tx, ty ) ); return this; } - setScalar( scalar ) { + // for 2D Transforms - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; + /** + * Sets this matrix as a 2D translation transform. + * + * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeTranslation( x, y ) { - return this; + if ( x.isVector2 ) { - } + this.set( - setX( x ) { + 1, 0, x.x, + 0, 1, x.y, + 0, 0, 1 - this.x = x; + ); - return this; + } else { - } + this.set( - setY( y ) { + 1, 0, x, + 0, 1, y, + 0, 0, 1 - this.y = y; + ); + + } return this; } - setZ( z ) { + /** + * Sets this matrix as a 2D rotational transformation. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + makeRotation( theta ) { - this.z = z; + // counterclockwise - return this; + const c = Math.cos( theta ); + const s = Math.sin( theta ); - } + this.set( - setW( w ) { + c, - s, 0, + s, c, 0, + 0, 0, 1 - this.w = w; + ); return this; } - setComponent( index, value ) { + /** + * Sets this matrix as a 2D scale transform. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeScale( x, y ) { - switch ( index ) { + this.set( - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); + x, 0, 0, + 0, y, 0, + 0, 0, 1 - } + ); return this; } - getComponent( index ) { + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix3} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { - switch ( index ) { + const te = this.elements; + const me = matrix.elements; - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); + for ( let i = 0; i < 9; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; } - } + return true; - clone() { + } - return new this.constructor( this.x, this.y, this.z, this.w ); + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array<number>} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix3} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { - } + for ( let i = 0; i < 9; i ++ ) { - copy( v ) { + this.elements[ i ] = array[ i + offset ]; - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; + } return this; } - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The matrix elements in column-major order. + */ + toArray( array = [], offset = 0 ) { - return this; + const te = this.elements; - } + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; - addScalar( s ) { + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; - this.x += s; - this.y += s; - this.z += s; - this.w += s; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; - return this; + return array; } - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix3} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().fromArray( this.elements ); } - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; +} - return this; +const _m3 = /*@__PURE__*/ new Matrix3(); - } +function arrayNeedsUint32( array ) { - sub( v ) { + // assumes larger values usually on last - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; + for ( let i = array.length - 1; i >= 0; -- i ) { - return this; + if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 } - subScalar( s ) { + return false; - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; +} - return this; +const TYPED_ARRAYS = { + Int8Array: Int8Array, + Uint8Array: Uint8Array, + Uint8ClampedArray: Uint8ClampedArray, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array +}; - } +function getTypedArray( type, buffer ) { - subVectors( a, b ) { + return new TYPED_ARRAYS[ type ]( buffer ); - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; +} - return this; +function createElementNS( name ) { - } + return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - multiply( v ) { +} - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; +function createCanvasElement() { - return this; + const canvas = createElementNS( 'canvas' ); + canvas.style.display = 'block'; + return canvas; - } +} - multiplyScalar( scalar ) { +const _cache = {}; - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; +function warnOnce( message ) { - return this; + if ( message in _cache ) return; - } + _cache[ message ] = true; - applyMatrix4( m ) { + console.warn( message ); - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; +} - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; +function probeAsync( gl, sync, interval ) { - return this; + return new Promise( function ( resolve, reject ) { - } + function probe() { - divideScalar( scalar ) { + switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) { - return this.multiplyScalar( 1 / scalar ); + case gl.WAIT_FAILED: + reject(); + break; - } + case gl.TIMEOUT_EXPIRED: + setTimeout( probe, interval ); + break; - setAxisAngleFromQuaternion( q ) { + default: + resolve(); - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + } - // q is assumed to be normalized + } - this.w = 2 * Math.acos( q.w ); + setTimeout( probe, interval ); - const s = Math.sqrt( 1 - q.w * q.w ); + } ); - if ( s < 0.0001 ) { +} - this.x = 1; - this.y = 0; - this.z = 0; +const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4123908, 0.3575843, 0.1804808, + 0.2126390, 0.7151687, 0.0721923, + 0.0193308, 0.1191948, 0.9505322 +); - } else { +const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( + 3.2409699, -1.5373832, -0.4986108, + -0.9692436, 1.8759675, 0.0415551, + 0.0556301, -0.203977, 1.0569715 +); - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; +function createColorManagement() { - } + const ColorManagement = { - return this; + enabled: true, - } + workingColorSpace: LinearSRGBColorSpace, - setAxisAngleFromRotationMatrix( m ) { + /** + * Implementations of supported color spaces. + * + * Required: + * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] + * - whitePoint: reference white [ x y ] + * - transfer: transfer function (pre-defined) + * - toXYZ: Matrix3 RGB to XYZ transform + * - fromXYZ: Matrix3 XYZ to RGB transform + * - luminanceCoefficients: RGB luminance coefficients + * + * Optional: + * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace, toneMappingMode: 'extended' | 'standard' } + * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } + * + * Reference: + * - https://www.russellcottrell.com/photo/matrixCalculator.htm + */ + spaces: {}, - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + convert: function ( color, sourceColorSpace, targetColorSpace ) { - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + return color; - te = m.elements, + } - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { + color.r = SRGBToLinear( color.r ); + color.g = SRGBToLinear( color.g ); + color.b = SRGBToLinear( color.b ); - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms + } - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { - // this singularity is identity matrix so angle = 0 + color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); + color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); - this.set( 1, 0, 0, 0 ); + } - return this; // zero angle, arbitrary axis + if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { + + color.r = LinearToSRGB( color.r ); + color.g = LinearToSRGB( color.g ); + color.b = LinearToSRGB( color.b ); } - // otherwise this singularity is angle = 180 + return color; - angle = Math.PI; + }, - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; + workingToColorSpace: function ( color, targetColorSpace ) { - if ( ( xx > yy ) && ( xx > zz ) ) { + return this.convert( color, this.workingColorSpace, targetColorSpace ); - // m11 is the largest diagonal term + }, - if ( xx < epsilon ) { + colorSpaceToWorking: function ( color, sourceColorSpace ) { - x = 0; - y = 0.707106781; - z = 0.707106781; + return this.convert( color, sourceColorSpace, this.workingColorSpace ); - } else { + }, - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; + getPrimaries: function ( colorSpace ) { - } + return this.spaces[ colorSpace ].primaries; - } else if ( yy > zz ) { + }, - // m22 is the largest diagonal term + getTransfer: function ( colorSpace ) { - if ( yy < epsilon ) { + if ( colorSpace === NoColorSpace ) return LinearTransfer; - x = 0.707106781; - y = 0; - z = 0.707106781; + return this.spaces[ colorSpace ].transfer; - } else { + }, - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; + getToneMappingMode: function ( colorSpace ) { - } + return this.spaces[ colorSpace ].outputColorSpaceConfig.toneMappingMode || 'standard'; - } else { + }, - // m33 is the largest diagonal term so base result on this + getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { - if ( zz < epsilon ) { + return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); - x = 0.707106781; - y = 0.707106781; - z = 0; + }, - } else { + define: function ( colorSpaces ) { - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; + Object.assign( this.spaces, colorSpaces ); - } + }, - } + // Internal APIs - this.set( x, y, z, angle ); + _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { - return this; // return 180 deg rotation + return targetMatrix + .copy( this.spaces[ sourceColorSpace ].toXYZ ) + .multiply( this.spaces[ targetColorSpace ].fromXYZ ); - } + }, - // as we have reached here there are no singularities so we can handle normally + _getDrawingBufferColorSpace: function ( colorSpace ) { - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; - if ( Math.abs( s ) < 0.001 ) s = 1; + }, - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case + _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; - return this; + }, - } + // Deprecated - min( v ) { + fromWorkingColorSpace: function ( color, targetColorSpace ) { - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); + warnOnce( 'THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177 - return this; + return ColorManagement.workingToColorSpace( color, targetColorSpace ); - } + }, - max( v ) { + toWorkingColorSpace: function ( color, sourceColorSpace ) { - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); + warnOnce( 'THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177 - return this; + return ColorManagement.colorSpaceToWorking( color, sourceColorSpace ); - } + }, - clamp( min, max ) { + }; - // assumes min < max, componentwise + /****************************************************************************** + * sRGB definitions + */ - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); + const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; + const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; + const D65 = [ 0.3127, 0.3290 ]; + + ColorManagement.define( { + + [ LinearSRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace }, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - return this; + [ SRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - } + } ); - clampScalar( minVal, maxVal ) { + return ColorManagement; - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); +} - return this; +const ColorManagement = /*@__PURE__*/ createColorManagement(); - } +function SRGBToLinear( c ) { - clampLength( min, max ) { + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - const length = this.length(); +} - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); +function LinearToSRGB( c ) { - } + return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - floor() { +} + +let _canvas; + +/** + * A class containing utility functions for images. + * + * @hideconstructor + */ +class ImageUtils { + + /** + * Returns a data URI containing a representation of the given image. + * + * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object. + * @param {string} [type='image/png'] - Indicates the image format. + * @return {string} The data URI. + */ + static getDataURL( image, type = 'image/png' ) { - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); + if ( /^data:/i.test( image.src ) ) { - return this; + return image.src; - } + } - ceil() { + if ( typeof HTMLCanvasElement === 'undefined' ) { - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); + return image.src; - return this; + } - } + let canvas; - round() { + if ( image instanceof HTMLCanvasElement ) { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); + canvas = image; - return this; + } else { - } + if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - roundToZero() { + _canvas.width = image.width; + _canvas.height = image.height; - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - this.z = Math.trunc( this.z ); - this.w = Math.trunc( this.w ); + const context = _canvas.getContext( '2d' ); - return this; + if ( image instanceof ImageData ) { - } + context.putImageData( image, 0, 0 ); - negate() { + } else { - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; + context.drawImage( image, 0, 0, image.width, image.height ); - return this; + } - } + canvas = _canvas; - dot( v ) { + } - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + return canvas.toDataURL( type ); } - lengthSq() { + /** + * Converts the given sRGB image data to linear color space. + * + * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object. + * @return {HTMLCanvasElement|Object} The converted image. + */ + static sRGBToLinear( image ) { - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - } + const canvas = createElementNS( 'canvas' ); - length() { + canvas.width = image.width; + canvas.height = image.height; - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height ); - } + const imageData = context.getImageData( 0, 0, image.width, image.height ); + const data = imageData.data; - manhattanLength() { + for ( let i = 0; i < data.length; i ++ ) { - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - } + } - normalize() { + context.putImageData( imageData, 0, 0 ); - return this.divideScalar( this.length() || 1 ); + return canvas; - } + } else if ( image.data ) { - setLength( length ) { + const data = image.data.slice( 0 ); - return this.normalize().multiplyScalar( length ); + for ( let i = 0; i < data.length; i ++ ) { - } + if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - lerp( v, alpha ) { + data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; + } else { - return this; + // assuming float - } + data[ i ] = SRGBToLinear( data[ i ] ); - lerpVectors( v1, v2, alpha ) { + } - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; + } - return this; + return { + data: data, + width: image.width, + height: image.height + }; - } + } else { - equals( v ) { + console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); + return image; - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + } } - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; +} - return this; +let _sourceId = 0; - } +/** + * Represents the data source of a texture. + * + * The main purpose of this class is to decouple the data definition from the texture + * definition so the same data can be used with multiple texture instances. + */ +class Source { - toArray( array = [], offset = 0 ) { + /** + * Constructs a new video texture. + * + * @param {any} [data=null] - The data definition of a texture. + */ + constructor( data = null ) { - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSource = true; - return array; + /** + * The ID of the source. + * + * @name Source#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _sourceId ++ } ); - } + /** + * The UUID of the source. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - fromBufferAttribute( attribute, index ) { + /** + * The data definition of a texture. + * + * @type {any} + */ + this.data = data; - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); + /** + * This property is only relevant when {@link Source#needsUpdate} is set to `true` and + * provides more control on how texture data should be processed. When `dataReady` is set + * to `false`, the engine performs the memory allocation (if necessary) but does not transfer + * the data into the GPU memory. + * + * @type {boolean} + * @default true + */ + this.dataReady = true; - return this; + /** + * This starts at `0` and counts how many times {@link Source#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; } - random() { + /** + * Returns the dimensions of the source into the given target vector. + * + * @param {(Vector2|Vector3)} target - The target object the result is written into. + * @return {(Vector2|Vector3)} The dimensions of the source. + */ + getSize( target ) { - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); + const data = this.data; - return this; + if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( data instanceof HTMLVideoElement ) ) { - } + target.set( data.videoWidth, data.videoHeight, 0 ); - *[ Symbol.iterator ]() { + } else if ( data instanceof VideoFrame ) { - yield this.x; - yield this.y; - yield this.z; - yield this.w; + target.set( data.displayHeight, data.displayWidth, 0 ); - } + } else if ( data !== null ) { -} + target.set( data.width, data.height, data.depth || 0 ); -/* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers -*/ -class RenderTarget extends EventDispatcher { + } else { - constructor( width = 1, height = 1, options = {} ) { + target.set( 0, 0, 0 ); - super(); + } - this.isRenderTarget = true; + return target; - this.width = width; - this.height = height; - this.depth = 1; + } - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; + /** + * When the property is set to `true`, the engine allocates the memory + * for the texture (if necessary) and triggers the actual texture upload + * to the GPU next time the source is used. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - this.viewport = new Vector4( 0, 0, width, height ); + if ( value === true ) this.version ++; - const image = { width: width, height: height, depth: 1 }; + } - options = Object.assign( { - generateMipmaps: false, - internalFormat: null, - minFilter: LinearFilter, - depthBuffer: true, - stencilBuffer: false, - depthTexture: null, - samples: 0, - count: 1 - }, options ); + /** + * Serializes the source into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized source. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - const texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - texture.flipY = false; - texture.generateMipmaps = options.generateMipmaps; - texture.internalFormat = options.internalFormat; + if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - this.textures = []; + return meta.images[ this.uuid ]; - const count = options.count; - for ( let i = 0; i < count; i ++ ) { + } - this.textures[ i ] = texture.clone(); - this.textures[ i ].isRenderTargetTexture = true; + const output = { + uuid: this.uuid, + url: '' + }; - } + const data = this.data; - this.depthBuffer = options.depthBuffer; - this.stencilBuffer = options.stencilBuffer; + if ( data !== null ) { - this.depthTexture = options.depthTexture; + let url; - this.samples = options.samples; + if ( Array.isArray( data ) ) { - } + // cube texture - get texture() { + url = []; - return this.textures[ 0 ]; + for ( let i = 0, l = data.length; i < l; i ++ ) { - } + if ( data[ i ].isDataTexture ) { - set texture( value ) { + url.push( serializeImage( data[ i ].image ) ); - this.textures[ 0 ] = value; + } else { - } + url.push( serializeImage( data[ i ] ) ); - setSize( width, height, depth = 1 ) { + } - if ( this.width !== width || this.height !== height || this.depth !== depth ) { + } - this.width = width; - this.height = height; - this.depth = depth; + } else { - for ( let i = 0, il = this.textures.length; i < il; i ++ ) { + // texture - this.textures[ i ].image.width = width; - this.textures[ i ].image.height = height; - this.textures[ i ].image.depth = depth; + url = serializeImage( data ); } - this.dispose(); + output.url = url; } - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); + if ( ! isRootObject ) { - } + meta.images[ this.uuid ] = output; - clone() { + } - return new this.constructor().copy( this ); + return output; } - copy( source ) { - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; +} - this.viewport.copy( source.viewport ); +function serializeImage( image ) { - this.textures.length = 0; + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - for ( let i = 0, il = source.textures.length; i < il; i ++ ) { + // default images - this.textures[ i ] = source.textures[ i ].clone(); - this.textures[ i ].isRenderTargetTexture = true; + return ImageUtils.getDataURL( image ); - } + } else { - // ensure image object is not shared, see #20328 + if ( image.data ) { - const image = Object.assign( {}, source.texture.image ); - this.texture.source = new Source( image ); + // images of DataTexture - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; + return { + data: Array.from( image.data ), + width: image.width, + height: image.height, + type: image.data.constructor.name + }; - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + } else { - this.samples = source.samples; + console.warn( 'THREE.Texture: Unable to serialize Texture.' ); + return {}; - return this; + } } - dispose() { +} - this.dispatchEvent( { type: 'dispose' } ); +let _textureId = 0; - } +const _tempVec3 = /*@__PURE__*/ new Vector3(); -} +/** + * Base class for all textures. + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments EventDispatcher + */ +class Texture extends EventDispatcher { -class WebGLRenderTarget extends RenderTarget { + /** + * Constructs a new texture. + * + * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - constructor( width = 1, height = 1, options = {} ) { + super(); - super( width, height, options ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture = true; - this.isWebGLRenderTarget = true; + /** + * The ID of the texture. + * + * @name Texture#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _textureId ++ } ); - } + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); -} + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; -const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; + /** + * The data definition of a texture. A reference to the data source can be + * shared across textures. This is often useful in context of spritesheets + * where multiple textures render the same data but with different texture + * transformations. + * + * @type {Source} + */ + this.source = new Source( image ); -const _hslA = { h: 0, s: 0, l: 0 }; -const _hslB = { h: 0, s: 0, l: 0 }; + /** + * An array holding user-defined mipmaps. + * + * @type {Array<Object>} + */ + this.mipmaps = []; -function hue2rgb( p, q, t ) { + /** + * How the texture is applied to the object. The value `UVMapping` + * is the default, where texture or uv coordinates are used to apply the map. + * + * @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)} + * @default UVMapping + */ + this.mapping = mapping; - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; + /** + * Lets you select the uv attribute to map the texture to. `0` for `uv`, + * `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. + * + * @type {number} + * @default 0 + */ + this.channel = 0; -} + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *U* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapS = wrapS; -class Color { + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *V* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapT = wrapT; - constructor( r, g, b ) { + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearFilter + */ + this.magFilter = magFilter; - this.isColor = true; + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearMipmapLinearFilter + */ + this.minFilter = minFilter; - this.r = 1; - this.g = 1; - this.b = 1; + /** + * The number of samples taken along the axis through the pixel that has the + * highest density of texels. By default, this value is `1`. A higher value + * gives a less blurry result than a basic mipmap, at the cost of more + * texture samples being used. + * + * @type {number} + * @default 0 + */ + this.anisotropy = anisotropy; - return this.set( r, g, b ); + /** + * The format of the texture. + * + * @type {number} + * @default RGBAFormat + */ + this.format = format; - } + /** + * The default internal format is derived from {@link Texture#format} and {@link Texture#type} and + * defines how the texture data is going to be stored on the GPU. + * + * This property allows to overwrite the default format. + * + * @type {?string} + * @default null + */ + this.internalFormat = null; - set( r, g, b ) { + /** + * The data type of the texture. + * + * @type {number} + * @default UnsignedByteType + */ + this.type = type; - if ( g === undefined && b === undefined ) { + /** + * How much a single repetition of the texture is offset from the beginning, + * in each direction U and V. Typical range is `0.0` to `1.0`. + * + * @type {Vector2} + * @default (0,0) + */ + this.offset = new Vector2( 0, 0 ); - // r is THREE.Color, hex or string + /** + * How many times the texture is repeated across the surface, in each + * direction U and V. If repeat is set greater than `1` in either direction, + * the corresponding wrap parameter should also be set to `RepeatWrapping` + * or `MirroredRepeatWrapping` to achieve the desired tiling effect. + * + * @type {Vector2} + * @default (1,1) + */ + this.repeat = new Vector2( 1, 1 ); - const value = r; + /** + * The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds + * to the center of the texture. Default is `(0, 0)`, the lower left. + * + * @type {Vector2} + * @default (0,0) + */ + this.center = new Vector2( 0, 0 ); - if ( value && value.isColor ) { + /** + * How much the texture is rotated around the center point, in radians. + * Positive values are counter-clockwise. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; - this.copy( value ); + /** + * Whether to update the texture's uv-transformation {@link Texture#matrix} + * from the properties {@link Texture#offset}, {@link Texture#repeat}, + * {@link Texture#rotation}, and {@link Texture#center}. + * + * Set this to `false` if you are specifying the uv-transform matrix directly. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = true; - } else if ( typeof value === 'number' ) { + /** + * The uv-transformation matrix of the texture. + * + * @type {Matrix3} + */ + this.matrix = new Matrix3(); - this.setHex( value ); + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Set this to `false` if you are creating mipmaps manually. + * + * @type {boolean} + * @default true + */ + this.generateMipmaps = true; - } else if ( typeof value === 'string' ) { + /** + * If set to `true`, the alpha channel, if present, is multiplied into the + * color channels when the texture is uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure premultiply alpha on bitmap creation instead. + * + * @type {boolean} + * @default false + */ + this.premultiplyAlpha = false; - this.setStyle( value ); + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure the flip on bitmap creation instead. + * + * @type {boolean} + * @default true + */ + this.flipY = true; - } + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes), + * `4` (word-alignment), and `8` (rows start on double-word boundaries). + * + * @type {number} + * @default 4 + */ + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - } else { + /** + * Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`. + * + * @type {string} + * @default NoColorSpace + */ + this.colorSpace = colorSpace; - this.setRGB( r, g, b ); + /** + * An object that can be used to store custom data about the texture. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - } + /** + * This can be used to only update a subregion or specific rows of the texture (for example, just the + * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array<Object>} + */ + this.updateRanges = []; - return this; + /** + * This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - } + /** + * A callback function, called when the texture is updated (e.g., when + * {@link Texture#needsUpdate} has been set to true and then the texture is used). + * + * @type {?Function} + * @default null + */ + this.onUpdate = null; - setScalar( scalar ) { + /** + * An optional back reference to the textures render target. + * + * @type {?(RenderTarget|WebGLRenderTarget)} + * @default null + */ + this.renderTarget = null; - this.r = scalar; - this.g = scalar; - this.b = scalar; + /** + * Indicates whether a texture belongs to a render target or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isRenderTargetTexture = false; + + /** + * Indicates if a texture should be handled like a texture array. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isArrayTexture = image && image.depth && image.depth > 1 ? true : false; - return this; + /** + * Indicates whether this texture should be processed by `PMREMGenerator` or not + * (only relevant for render target textures). + * + * @type {number} + * @readonly + * @default 0 + */ + this.pmremVersion = 0; } - setHex( hex, colorSpace = SRGBColorSpace ) { + /** + * The width of the texture in pixels. + */ + get width() { - hex = Math.floor( hex ); + return this.source.getSize( _tempVec3 ).x; - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; + } - ColorManagement.toWorkingColorSpace( this, colorSpace ); + /** + * The height of the texture in pixels. + */ + get height() { - return this; + return this.source.getSize( _tempVec3 ).y; } - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - - this.r = r; - this.g = g; - this.b = b; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); + /** + * The depth of the texture in pixels. + */ + get depth() { - return this; + return this.source.getSize( _tempVec3 ).z; } - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); - - if ( s === 0 ) { + /** + * The image object holding the texture data. + * + * @type {?Object} + */ + get image() { - this.r = this.g = this.b = l; + return this.source.data; - } else { + } - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; + set image( value = null ) { - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); + this.source.data = value; - } + } - ColorManagement.toWorkingColorSpace( this, colorSpace ); + /** + * Updates the texture transformation matrix from the from the properties {@link Texture#offset}, + * {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}. + */ + updateMatrix() { - return this; + this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); } - setStyle( style, colorSpace = SRGBColorSpace ) { + /** + * Adds a range of data in the data texture to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { - function handleAlpha( string ) { + this.updateRanges.push( { start, count } ); - if ( string === undefined ) return; + } - if ( parseFloat( string ) < 1 ) { + /** + * Clears the update ranges. + */ + clearUpdateRanges() { - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); + this.updateRanges.length = 0; - } + } - } + /** + * Returns a new texture with copied values from this instance. + * + * @return {Texture} A clone of this instance. + */ + clone() { + return new this.constructor().copy( this ); - let m; + } - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { + /** + * Copies the values of the given texture to this instance. + * + * @param {Texture} source - The texture to copy. + * @return {Texture} A reference to this instance. + */ + copy( source ) { - // rgb / hsl + this.name = source.name; - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; + this.source = source.source; + this.mipmaps = source.mipmaps.slice( 0 ); - switch ( name ) { + this.mapping = source.mapping; + this.channel = source.channel; - case 'rgb': - case 'rgba': + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; - // rgb(255,0,0) rgba(255,0,0,0.5) + this.anisotropy = source.anisotropy; - handleAlpha( color[ 4 ] ); + this.format = source.format; + this.internalFormat = source.internalFormat; + this.type = source.type; - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); + this.center.copy( source.center ); + this.rotation = source.rotation; - } + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrix.copy( source.matrix ); - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; + this.colorSpace = source.colorSpace; - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) + this.renderTarget = source.renderTarget; + this.isRenderTargetTexture = source.isRenderTargetTexture; + this.isArrayTexture = source.isArrayTexture; - handleAlpha( color[ 4 ] ); + this.userData = JSON.parse( JSON.stringify( source.userData ) ); - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); + this.needsUpdate = true; - } + return this; - break; + } - case 'hsl': - case 'hsla': + /** + * Sets this texture's properties based on `values`. + * @param {Object} values - A container with texture parameters. + */ + setValues( values ) { - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + for ( const key in values ) { - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) + const newValue = values[ key ]; - handleAlpha( color[ 4 ] ); + if ( newValue === undefined ) { - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); + console.warn( `THREE.Texture.setValues(): parameter '${ key }' has value of undefined.` ); + continue; - } + } - break; + const currentValue = this[ key ]; - default: + if ( currentValue === undefined ) { - console.warn( 'THREE.Color: Unknown color model ' + style ); + console.warn( `THREE.Texture.setValues(): property '${ key }' does not exist.` ); + continue; } - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - - // hex color + if ( ( currentValue && newValue ) && ( currentValue.isVector2 && newValue.isVector2 ) ) { - const hex = m[ 1 ]; - const size = hex.length; + currentValue.copy( newValue ); - if ( size === 3 ) { + } else if ( ( currentValue && newValue ) && ( currentValue.isVector3 && newValue.isVector3 ) ) { - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); + currentValue.copy( newValue ); - } else if ( size === 6 ) { + } else if ( ( currentValue && newValue ) && ( currentValue.isMatrix3 && newValue.isMatrix3 ) ) { - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); + currentValue.copy( newValue ); } else { - console.warn( 'THREE.Color: Invalid hex color ' + style ); + this[ key ] = newValue; } - } else if ( style && style.length > 0 ) { - - return this.setColorName( style, colorSpace ); - } - return this; - } - setColorName( style, colorSpace = SRGBColorSpace ) { - - // color keywords - const hex = _colorKeywords[ style.toLowerCase() ]; - - if ( hex !== undefined ) { + /** + * Serializes the texture into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized texture. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - // red - this.setHex( hex, colorSpace ); + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - } else { + if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - // unknown color - console.warn( 'THREE.Color: Unknown color ' + style ); + return meta.textures[ this.uuid ]; } - return this; - - } - - clone() { - - return new this.constructor( this.r, this.g, this.b ); - - } - - copy( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; - - return this; + const output = { - } + metadata: { + version: 4.7, + type: 'Texture', + generator: 'Texture.toJSON' + }, - copySRGBToLinear( color ) { + uuid: this.uuid, + name: this.name, - this.r = SRGBToLinear( color.r ); - this.g = SRGBToLinear( color.g ); - this.b = SRGBToLinear( color.b ); + image: this.source.toJSON( meta ).uuid, - return this; + mapping: this.mapping, + channel: this.channel, - } + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + center: [ this.center.x, this.center.y ], + rotation: this.rotation, - copyLinearToSRGB( color ) { + wrap: [ this.wrapS, this.wrapT ], - this.r = LinearToSRGB( color.r ); - this.g = LinearToSRGB( color.g ); - this.b = LinearToSRGB( color.b ); + format: this.format, + internalFormat: this.internalFormat, + type: this.type, + colorSpace: this.colorSpace, - return this; + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy, - } + flipY: this.flipY, - convertSRGBToLinear() { + generateMipmaps: this.generateMipmaps, + premultiplyAlpha: this.premultiplyAlpha, + unpackAlignment: this.unpackAlignment - this.copySRGBToLinear( this ); + }; - return this; + if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; - } + if ( ! isRootObject ) { - convertLinearToSRGB() { + meta.textures[ this.uuid ] = output; - this.copyLinearToSRGB( this ); + } - return this; + return output; } - getHex( colorSpace = SRGBColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { - return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); + /** + * Fires when the texture has been disposed of. + * + * @event Texture#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); } - getHexString( colorSpace = SRGBColorSpace ) { + /** + * Transforms the given uv vector with the textures uv transformation matrix. + * + * @param {Vector2} uv - The uv vector. + * @return {Vector2} The transformed uv vector. + */ + transformUv( uv ) { - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); + if ( this.mapping !== UVMapping ) return uv; - } + uv.applyMatrix3( this.matrix ); - getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { + if ( uv.x < 0 || uv.x > 1 ) { - // h,s,l ranges are in 0.0 - 1.0 + switch ( this.wrapS ) { - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + case RepeatWrapping: - const r = _color.r, g = _color.g, b = _color.b; + uv.x = uv.x - Math.floor( uv.x ); + break; - const max = Math.max( r, g, b ); - const min = Math.min( r, g, b ); + case ClampToEdgeWrapping: - let hue, saturation; - const lightness = ( min + max ) / 2.0; + uv.x = uv.x < 0 ? 0 : 1; + break; - if ( min === max ) { + case MirroredRepeatWrapping: - hue = 0; - saturation = 0; + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - } else { + uv.x = Math.ceil( uv.x ) - uv.x; - const delta = max - min; + } else { - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + uv.x = uv.x - Math.floor( uv.x ); - switch ( max ) { + } - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; + break; } - hue /= 6; - } - target.h = hue; - target.s = saturation; - target.l = lightness; + if ( uv.y < 0 || uv.y > 1 ) { - return target; + switch ( this.wrapT ) { - } + case RepeatWrapping: - getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { + uv.y = uv.y - Math.floor( uv.y ); + break; - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + case ClampToEdgeWrapping: - target.r = _color.r; - target.g = _color.g; - target.b = _color.b; + uv.y = uv.y < 0 ? 0 : 1; + break; - return target; + case MirroredRepeatWrapping: - } + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - getStyle( colorSpace = SRGBColorSpace ) { + uv.y = Math.ceil( uv.y ) - uv.y; - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + } else { - const r = _color.r, g = _color.g, b = _color.b; + uv.y = uv.y - Math.floor( uv.y ); - if ( colorSpace !== SRGBColorSpace ) { + } - // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). - return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; + break; - } + } - return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; + } - } + if ( this.flipY ) { - offsetHSL( h, s, l ) { + uv.y = 1 - uv.y; - this.getHSL( _hslA ); + } - return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); + return uv; } - add( color ) { - - this.r += color.r; - this.g += color.g; - this.b += color.b; - - return this; - - } + /** + * Setting this property to `true` indicates the engine the texture + * must be updated in the next render. This triggers a texture upload + * to the GPU and ensures correct texture parameter configuration. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - addColors( color1, color2 ) { + if ( value === true ) { - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; + this.version ++; + this.source.needsUpdate = true; - return this; + } } - addScalar( s ) { + /** + * Setting this property to `true` indicates the engine the PMREM + * must be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsPMREMUpdate( value ) { - this.r += s; - this.g += s; - this.b += s; + if ( value === true ) { - return this; + this.pmremVersion ++; + + } } - sub( color ) { +} - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); +/** + * The default image for all textures. + * + * @static + * @type {?Image} + * @default null + */ +Texture.DEFAULT_IMAGE = null; - return this; +/** + * The default mapping for all textures. + * + * @static + * @type {number} + * @default UVMapping + */ +Texture.DEFAULT_MAPPING = UVMapping; - } +/** + * The default anisotropy value for all textures. + * + * @static + * @type {number} + * @default 1 + */ +Texture.DEFAULT_ANISOTROPY = 1; - multiply( color ) { +/** + * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers + * (labeled x, y, z and w), which can be used to represent a number of things, such as: + * + * - A point in 4D space. + * - A direction and length in 4D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` + * and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. + * - Any arbitrary ordered quadruplet of numbers. + * + * There are other things a 4D vector can be used to represent, however these + * are the most common uses in *three.js*. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector4( 0, 1, 0, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0, 1) + * const b = new THREE.Vector4( ); + * + * const d = a.dot( b ); + * ``` + */ +class Vector4 { - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; + /** + * Constructs a new 4D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + * @param {number} [w=1] - The w value of this vector. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { - return this; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector4.prototype.isVector4 = true; - } + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; - multiplyScalar( s ) { + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; - this.r *= s; - this.g *= s; - this.b *= s; + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; - return this; + /** + * The w value of this vector. + * + * @type {number} + */ + this.w = w; } - lerp( color, alpha ) { - - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; + /** + * Alias for {@link Vector4#z}. + * + * @type {number} + */ + get width() { - return this; + return this.z; } - lerpColors( color1, color2, alpha ) { - - this.r = color1.r + ( color2.r - color1.r ) * alpha; - this.g = color1.g + ( color2.g - color1.g ) * alpha; - this.b = color1.b + ( color2.b - color1.b ) * alpha; + set width( value ) { - return this; + this.z = value; } - lerpHSL( color, alpha ) { + /** + * Alias for {@link Vector4#w}. + * + * @type {number} + */ + get height() { - this.getHSL( _hslA ); - color.getHSL( _hslB ); + return this.w; - const h = lerp( _hslA.h, _hslB.h, alpha ); - const s = lerp( _hslA.s, _hslB.s, alpha ); - const l = lerp( _hslA.l, _hslB.l, alpha ); + } - this.setHSL( h, s, l ); + set height( value ) { - return this; + this.w = value; } - setFromVector3( v ) { - - this.r = v.x; - this.g = v.y; - this.b = v.z; + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @param {number} w - The value of the w component. + * @return {Vector4} A reference to this vector. + */ + set( x, y, z, w ) { + + this.x = x; + this.y = y; + this.z = z; + this.w = w; return this; } - applyMatrix3( m ) { - - const r = this.r, g = this.g, b = this.b; - const e = m.elements; + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector4} A reference to this vector. + */ + setScalar( scalar ) { - this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; - this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; - this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; + this.x = scalar; + this.y = scalar; + this.z = scalar; + this.w = scalar; return this; } - equals( c ) { + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector4} A reference to this vector. + */ + setX( x ) { - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + this.x = x; + + return this; } - fromArray( array, offset = 0 ) { + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector4} A reference to this vector. + */ + setY( y ) { - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; + this.y = y; return this; } - toArray( array = [], offset = 0 ) { + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector4} A reference to this vector. + */ + setZ( z ) { - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; + this.z = z; - return array; + return this; } - fromBufferAttribute( attribute, index ) { + /** + * Sets the vector's w component to the given value + * + * @param {number} w - The value to set. + * @return {Vector4} A reference to this vector. + */ + setW( w ) { - this.r = attribute.getX( index ); - this.g = attribute.getY( index ); - this.b = attribute.getZ( index ); + this.w = w; return this; } - toJSON() { + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @param {number} value - The value to set. + * @return {Vector4} A reference to this vector. + */ + setComponent( index, value ) { - return this.getHex(); + switch ( index ) { - } + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + case 3: this.w = value; break; + default: throw new Error( 'index is out of range: ' + index ); - *[ Symbol.iterator ]() { + } - yield this.r; - yield this.g; - yield this.b; + return this; } -} + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @return {number} A vector component value. + */ + getComponent( index ) { -const _color = /*@__PURE__*/ new Color(); + switch ( index ) { -Color.NAMES = _colorKeywords; + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + case 3: return this.w; + default: throw new Error( 'index is out of range: ' + index ); -class Quaternion { + } - constructor( x = 0, y = 0, z = 0, w = 1 ) { + } - this.isQuaternion = true; + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector4} A clone of this instance. + */ + clone() { - this._x = x; - this._y = y; - this._z = z; - this._w = w; + return new this.constructor( this.x, this.y, this.z, this.w ); } - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - // fuzz-free, array-based Quaternion SLERP operation + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3|Vector4} v - The vector to copy. + * @return {Vector4} A reference to this vector. + */ + copy( v ) { - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; + return this; - if ( t === 0 ) { + } - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; + /** + * Adds the given vector to this instance. + * + * @param {Vector4} v - The vector to add. + * @return {Vector4} A reference to this vector. + */ + add( v ) { - } + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; - if ( t === 1 ) { + return this; - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; + } - } + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector4} A reference to this vector. + */ + addScalar( s ) { - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + this.x += s; + this.y += s; + this.z += s; + this.w += s; - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; + return this; - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { + } - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ + addVectors( a, b ) { - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; - } + return this; - const tDir = t * dir; + } - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector4} A reference to this vector. + */ + addScaledVector( v, s ) { - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + this.w += v.w * s; - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + return this; - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; + } - } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector4} v - The vector to subtract. + * @return {Vector4} A reference to this vector. + */ + sub( v ) { - } + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; + return this; } - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector4} A reference to this vector. + */ + subScalar( s ) { - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; + this.x -= s; + this.y -= s; + this.z -= s; + this.w -= s; - return dst; + return this; } - get x() { + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ + subVectors( a, b ) { - return this._x; + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; + + return this; } - set x( value ) { + /** + * Multiplies the given vector with this instance. + * + * @param {Vector4} v - The vector to multiply. + * @return {Vector4} A reference to this vector. + */ + multiply( v ) { - this._x = value; - this._onChangeCallback(); + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + this.w *= v.w; + + return this; } - get y() { + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector4} A reference to this vector. + */ + multiplyScalar( scalar ) { - return this._y; + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; - } + return this; - set y( value ) { + } - this._y = value; - this._onChangeCallback(); + /** + * Multiplies this vector with the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + applyMatrix4( m ) { - } + const x = this.x, y = this.y, z = this.z, w = this.w; + const e = m.elements; - get z() { + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; - return this._z; + return this; } - set z( value ) { + /** + * Divides this instance by the given vector. + * + * @param {Vector4} v - The vector to divide. + * @return {Vector4} A reference to this vector. + */ + divide( v ) { - this._z = value; - this._onChangeCallback(); + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + this.w /= v.w; + + return this; } - get w() { + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector4} A reference to this vector. + */ + divideScalar( scalar ) { - return this._w; + return this.multiplyScalar( 1 / scalar ); } - set w( value ) { + /** + * Sets the x, y and z components of this + * vector to the quaternion's axis and w to the angle. + * + * @param {Quaternion} q - The Quaternion to set. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromQuaternion( q ) { - this._w = value; - this._onChangeCallback(); + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - } + // q is assumed to be normalized - set( x, y, z, w ) { + this.w = 2 * Math.acos( q.w ); - this._x = x; - this._y = y; - this._z = z; - this._w = w; + const s = Math.sqrt( 1 - q.w * q.w ); - this._onChangeCallback(); + if ( s < 0.0001 ) { - return this; + this.x = 1; + this.y = 0; + this.z = 0; - } + } else { - clone() { + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; - return new this.constructor( this._x, this._y, this._z, this._w ); + } + + return this; } - copy( quaternion ) { + /** + * Sets the x, y and z components of this + * vector to the axis of rotation and w to the angle. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper left 3x3 matrix is a pure rotation matrix. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromRotationMatrix( m ) { - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - this._onChangeCallback(); + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - return this; + let angle, x, y, z; // variables for result + const epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - } + te = m.elements, - setFromEuler( euler, update = true ) { + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; + if ( ( Math.abs( m12 - m21 ) < epsilon ) && + ( Math.abs( m13 - m31 ) < epsilon ) && + ( Math.abs( m23 - m32 ) < epsilon ) ) { - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms - const cos = Math.cos; - const sin = Math.sin; + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && + ( Math.abs( m13 + m31 ) < epsilon2 ) && + ( Math.abs( m23 + m32 ) < epsilon2 ) && + ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); + // this singularity is identity matrix so angle = 0 - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); + this.set( 1, 0, 0, 0 ); - switch ( order ) { + return this; // zero angle, arbitrary axis - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + } - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + // otherwise this singularity is angle = 180 - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + angle = Math.PI; - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + const xx = ( m11 + 1 ) / 2; + const yy = ( m22 + 1 ) / 2; + const zz = ( m33 + 1 ) / 2; + const xy = ( m12 + m21 ) / 4; + const xz = ( m13 + m31 ) / 4; + const yz = ( m23 + m32 ) / 4; - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + if ( ( xx > yy ) && ( xx > zz ) ) { - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + // m11 is the largest diagonal term - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); + if ( xx < epsilon ) { - } + x = 0; + y = 0.707106781; + z = 0.707106781; - if ( update === true ) this._onChangeCallback(); + } else { - return this; + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; - } + } - setFromAxisAngle( axis, angle ) { + } else if ( yy > zz ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + // m22 is the largest diagonal term - // assumes axis is normalized + if ( yy < epsilon ) { - const halfAngle = angle / 2, s = Math.sin( halfAngle ); + x = 0.707106781; + y = 0; + z = 0.707106781; - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); + } else { - this._onChangeCallback(); + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; - return this; + } - } + } else { - setFromRotationMatrix( m ) { + // m33 is the largest diagonal term so base result on this - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + if ( zz < epsilon ) { - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + x = 0.707106781; + y = 0.707106781; + z = 0; - const te = m.elements, + } else { - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; - trace = m11 + m22 + m33; + } - if ( trace > 0 ) { + } - const s = 0.5 / Math.sqrt( trace + 1.0 ); + this.set( x, y, z, angle ); - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; + return this; // return 180 deg rotation - } else if ( m11 > m22 && m11 > m33 ) { + } - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + // as we have reached here there are no singularities so we can handle normally - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; + let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - } else if ( m22 > m33 ) { + if ( Math.abs( s ) < 0.001 ) s = 1; - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - } else { + return this; - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + } - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + setFromMatrixPosition( m ) { - } + const e = m.elements; - this._onChangeCallback(); + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + this.w = e[ 15 ]; return this; } - setFromUnitVectors( vFrom, vTo ) { + /** + * If this vector's x, y, z or w value is greater than the given vector's x, y, z or w + * value, replace that value with the corresponding min value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ + min( v ) { - // assumes direction vectors vFrom and vTo are normalized + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); + this.w = Math.min( this.w, v.w ); - let r = vFrom.dot( vTo ) + 1; + return this; - if ( r < Number.EPSILON ) { + } - // vFrom and vTo point in opposite directions + /** + * If this vector's x, y, z or w value is less than the given vector's x, y, z or w + * value, replace that value with the corresponding max value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ + max( v ) { - r = 0; + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + this.w = Math.max( this.w, v.w ); - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + return this; - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; + } - } else { + /** + * If this vector's x, y, z or w value is greater than the max vector's x, y, z or w + * value, it is replaced by the corresponding value. + * If this vector's x, y, z or w value is less than the min vector's x, y, z or w value, + * it is replaced by the corresponding value. + * + * @param {Vector4} min - The minimum x, y and z values. + * @param {Vector4} max - The maximum x, y and z values in the desired range. + * @return {Vector4} A reference to this vector. + */ + clamp( min, max ) { - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; + // assumes min < max, componentwise - } + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); + this.w = clamp( this.w, min.w, max.w ); - } else { + return this; - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 + } - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; + /** + * If this vector's x, y, z or w values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y, z or w values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector4} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { - } + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); + this.w = clamp( this.w, minVal, maxVal ); - return this.normalize(); + return this; } - angleTo( q ) { + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector4} A reference to this vector. + */ + clampLength( min, max ) { - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); + const length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); } - rotateTowards( q, step ) { + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ + floor() { - const angle = this.angleTo( q ); + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); - if ( angle === 0 ) return this; + return this; - const t = Math.min( 1, step / angle ); + } - this.slerp( q, t ); + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ + ceil() { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); return this; } - identity() { + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector4} A reference to this vector. + */ + round() { - return this.set( 0, 0, 0, 1 ); + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + this.w = Math.round( this.w ); + + return this; } - invert() { + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector4} A reference to this vector. + */ + roundToZero() { - // quaternion is assumed to have unit length + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); + this.w = Math.trunc( this.w ); - return this.conjugate(); + return this; } - conjugate() { - - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; + /** + * Inverts this vector - i.e. sets x = -x, y = -y, z = -z, w = -w. + * + * @return {Vector4} A reference to this vector. + */ + negate() { - this._onChangeCallback(); + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + this.w = - this.w; return this; } + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector4} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ dot( v ) { - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; } + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0, 0) to (x, y, z, w). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ lengthSq() { - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; } + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0, 0) to (x, y, z, w). + * + * @return {number} The length of this vector. + */ length() { - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); } - normalize() { - - let l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; - - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; - - } - - this._onChangeCallback(); + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { - return this; + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); } - multiply( q ) { + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector4} A reference to this vector. + */ + normalize() { - return this.multiplyQuaternions( this, q ); + return this.divideScalar( this.length() || 1 ); } - premultiply( q ) { + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector4} A reference to this vector. + */ + setLength( length ) { - return this.multiplyQuaternions( q, this ); + return this.normalize().multiplyScalar( length ); } - multiplyQuaternions( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector4} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ + lerp( v, alpha ) { - this._onChangeCallback(); + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; return this; } - slerp( qb, t ) { - - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); - - const x = this._x, y = this._y, z = this._z, w = this._w; - - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector4} v1 - The first vector. + * @param {Vector4} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; + this.w = v1.w + ( v2.w - v1.w ) * alpha; - if ( cosHalfTheta < 0 ) { + return this; - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; + } - cosHalfTheta = - cosHalfTheta; + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector4} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - } else { + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - this.copy( qb ); + } - } + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`, + * z value to be `array[ offset + 2 ]`, w value to be `array[ offset + 3 ]`. + * + * @param {Array<number>} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector4} A reference to this vector. + */ + fromArray( array, offset = 0 ) { - if ( cosHalfTheta >= 1.0 ) { + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; - this._w = w; - this._x = x; - this._y = y; - this._z = z; + return this; - return this; + } - } + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The vector components. + */ + toArray( array = [], offset = 0 ) { - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; - if ( sqrSinHalfTheta <= Number.EPSILON ) { + return array; - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; + } - this.normalize(); // normalize calls _onChangeCallback() + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector4} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - return this; + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + this.w = attribute.getW( index ); - } + return this; - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + } - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector4} A reference to this vector. + */ + random() { - this._onChangeCallback(); + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); + this.w = Math.random(); return this; } - slerpQuaternions( qa, qb, t ) { + *[ Symbol.iterator ]() { - return this.copy( qa ).slerp( qb, t ); + yield this.x; + yield this.y; + yield this.z; + yield this.w; } - random() { +} - // sets this quaternion to a uniform random unit quaternnion +/** + * A render target is a buffer where the video card draws pixels for a scene + * that is being rendered in the background. It is used in different effects, + * such as applying postprocessing to a rendered image before displaying it + * on the screen. + * + * @augments EventDispatcher + */ +class RenderTarget extends EventDispatcher { - // Ken Shoemake - // Uniform random rotations - // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. + /** + * Render target options. + * + * @typedef {Object} RenderTarget~Options + * @property {boolean} [generateMipmaps=false] - Whether to generate mipmaps or not. + * @property {number} [magFilter=LinearFilter] - The mag filter. + * @property {number} [minFilter=LinearFilter] - The min filter. + * @property {number} [format=RGBAFormat] - The texture format. + * @property {number} [type=UnsignedByteType] - The texture type. + * @property {?string} [internalFormat=null] - The texture's internal format. + * @property {number} [wrapS=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [wrapT=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [anisotropy=1] - The texture's anisotropy value. + * @property {string} [colorSpace=NoColorSpace] - The texture's color space. + * @property {boolean} [depthBuffer=true] - Whether to allocate a depth buffer or not. + * @property {boolean} [stencilBuffer=false] - Whether to allocate a stencil buffer or not. + * @property {boolean} [resolveDepthBuffer=true] - Whether to resolve the depth buffer or not. + * @property {boolean} [resolveStencilBuffer=true] - Whether to resolve the stencil buffer or not. + * @property {?Texture} [depthTexture=null] - Reference to a depth texture. + * @property {number} [samples=0] - The MSAA samples count. + * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. + * @property {number} [depth=1] - The texture depth. + * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering. + */ - const theta1 = 2 * Math.PI * Math.random(); - const theta2 = 2 * Math.PI * Math.random(); + /** + * Constructs a new render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { - const x0 = Math.random(); - const r1 = Math.sqrt( 1 - x0 ); - const r2 = Math.sqrt( x0 ); + super(); - return this.set( - r1 * Math.sin( theta1 ), - r1 * Math.cos( theta1 ), - r2 * Math.sin( theta2 ), - r2 * Math.cos( theta2 ), - ); + options = Object.assign( { + generateMipmaps: false, + internalFormat: null, + minFilter: LinearFilter, + depthBuffer: true, + stencilBuffer: false, + resolveDepthBuffer: true, + resolveStencilBuffer: true, + depthTexture: null, + samples: 0, + count: 1, + depth: 1, + multiview: false + }, options ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget = true; - equals( quaternion ) { + /** + * The width of the render target. + * + * @type {number} + * @default 1 + */ + this.width = width; - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + /** + * The height of the render target. + * + * @type {number} + * @default 1 + */ + this.height = height; - } + /** + * The depth of the render target. + * + * @type {number} + * @default 1 + */ + this.depth = options.depth; - fromArray( array, offset = 0 ) { + /** + * A rectangular area inside the render target's viewport. Fragments that are + * outside the area will be discarded. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.scissor = new Vector4( 0, 0, width, height ); - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; + /** + * Indicates whether the scissor test should be enabled when rendering into + * this render target or not. + * + * @type {boolean} + * @default false + */ + this.scissorTest = false; - this._onChangeCallback(); + /** + * A rectangular area representing the render target's viewport. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.viewport = new Vector4( 0, 0, width, height ); - return this; + const image = { width: width, height: height, depth: options.depth }; - } + const texture = new Texture( image ); - toArray( array = [], offset = 0 ) { + /** + * An array of textures. Each color attachment is represented as a separate texture. + * Has at least a single entry for the default color attachment. + * + * @type {Array<Texture>} + */ + this.textures = []; - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; + const count = options.count; + for ( let i = 0; i < count; i ++ ) { - return array; + this.textures[ i ] = texture.clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; - } + } - fromBufferAttribute( attribute, index ) { + this._setTextureOptions( options ); - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); + /** + * Whether to allocate a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depthBuffer = options.depthBuffer; - this._onChangeCallback(); + /** + * Whether to allocate a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencilBuffer = options.stencilBuffer; - return this; + /** + * Whether to resolve the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveDepthBuffer = options.resolveDepthBuffer; - } + /** + * Whether to resolve the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveStencilBuffer = options.resolveStencilBuffer; - toJSON() { + this._depthTexture = null; + this.depthTexture = options.depthTexture; - return this.toArray(); + /** + * The number of MSAA samples. + * + * A value of `0` disables MSAA. + * + * @type {number} + * @default 0 + */ + this.samples = options.samples; - } + /** + * Whether to this target is used in multiview rendering. + * + * @type {boolean} + * @default false + */ + this.multiview = options.multiview; - _onChange( callback ) { + } - this._onChangeCallback = callback; + _setTextureOptions( options = {} ) { - return this; + const values = { + minFilter: LinearFilter, + generateMipmaps: false, + flipY: false, + internalFormat: null + }; - } + if ( options.mapping !== undefined ) values.mapping = options.mapping; + if ( options.wrapS !== undefined ) values.wrapS = options.wrapS; + if ( options.wrapT !== undefined ) values.wrapT = options.wrapT; + if ( options.wrapR !== undefined ) values.wrapR = options.wrapR; + if ( options.magFilter !== undefined ) values.magFilter = options.magFilter; + if ( options.minFilter !== undefined ) values.minFilter = options.minFilter; + if ( options.format !== undefined ) values.format = options.format; + if ( options.type !== undefined ) values.type = options.type; + if ( options.anisotropy !== undefined ) values.anisotropy = options.anisotropy; + if ( options.colorSpace !== undefined ) values.colorSpace = options.colorSpace; + if ( options.flipY !== undefined ) values.flipY = options.flipY; + if ( options.generateMipmaps !== undefined ) values.generateMipmaps = options.generateMipmaps; + if ( options.internalFormat !== undefined ) values.internalFormat = options.internalFormat; - _onChangeCallback() {} + for ( let i = 0; i < this.textures.length; i ++ ) { - *[ Symbol.iterator ]() { + const texture = this.textures[ i ]; + texture.setValues( values ); - yield this._x; - yield this._y; - yield this._z; - yield this._w; + } } -} + /** + * The texture representing the default color attachment. + * + * @type {Texture} + */ + get texture() { -class Vector3 { + return this.textures[ 0 ]; - constructor( x = 0, y = 0, z = 0 ) { + } - Vector3.prototype.isVector3 = true; + set texture( value ) { - this.x = x; - this.y = y; - this.z = z; + this.textures[ 0 ] = value; } - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) + set depthTexture( current ) { - this.x = x; - this.y = y; - this.z = z; + if ( this._depthTexture !== null ) this._depthTexture.renderTarget = null; + if ( current !== null ) current.renderTarget = this; - return this; + this._depthTexture = current; } - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; + /** + * Instead of saving the depth in a renderbuffer, a texture + * can be used instead which is useful for further processing + * e.g. in context of post-processing. + * + * @type {?DepthTexture} + * @default null + */ + get depthTexture() { - return this; + return this._depthTexture; } - setX( x ) { + /** + * Sets the size of this render target. + * + * @param {number} width - The width. + * @param {number} height - The height. + * @param {number} [depth=1] - The depth. + */ + setSize( width, height, depth = 1 ) { - this.x = x; + if ( this.width !== width || this.height !== height || this.depth !== depth ) { + + this.width = width; + this.height = height; + this.depth = depth; + + for ( let i = 0, il = this.textures.length; i < il; i ++ ) { - return this; + this.textures[ i ].image.width = width; + this.textures[ i ].image.height = height; + this.textures[ i ].image.depth = depth; + this.textures[ i ].isArrayTexture = this.textures[ i ].image.depth > 1; - } + } - setY( y ) { + this.dispose(); - this.y = y; + } - return this; + this.viewport.set( 0, 0, width, height ); + this.scissor.set( 0, 0, width, height ); } - setZ( z ) { - - this.z = z; + /** + * Returns a new render target with copied values from this instance. + * + * @return {RenderTarget} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().copy( this ); } - setComponent( index, value ) { + /** + * Copies the settings of the given render target. This is a structural copy so + * no resources are shared between render targets after the copy. That includes + * all MRT textures and the depth texture. + * + * @param {RenderTarget} source - The render target to copy. + * @return {RenderTarget} A reference to this instance. + */ + copy( source ) { - switch ( index ) { + this.width = source.width; + this.height = source.height; + this.depth = source.depth; - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'index is out of range: ' + index ); + this.scissor.copy( source.scissor ); + this.scissorTest = source.scissorTest; - } + this.viewport.copy( source.viewport ); - return this; + this.textures.length = 0; - } + for ( let i = 0, il = source.textures.length; i < il; i ++ ) { - getComponent( index ) { + this.textures[ i ] = source.textures[ i ].clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; - switch ( index ) { + // ensure image object is not shared, see #20328 - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'index is out of range: ' + index ); + const image = Object.assign( {}, source.textures[ i ].image ); + this.textures[ i ].source = new Source( image ); } - } - - clone() { - - return new this.constructor( this.x, this.y, this.z ); + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; - } + this.resolveDepthBuffer = source.resolveDepthBuffer; + this.resolveStencilBuffer = source.resolveStencilBuffer; - copy( v ) { + if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); - this.x = v.x; - this.y = v.y; - this.z = v.z; + this.samples = source.samples; return this; } - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires RenderTarget#dispose + */ + dispose() { - return this; + this.dispatchEvent( { type: 'dispose' } ); } - addScalar( s ) { +} - this.x += s; - this.y += s; - this.z += s; +/** + * A render target used in context of {@link WebGLRenderer}. + * + * @augments RenderTarget + */ +class WebGLRenderTarget extends RenderTarget { - return this; + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderTarget = true; } - addVectors( a, b ) { +} - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; +const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, + 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, + 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, + 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, + 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, + 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, + 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, + 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, + 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, + 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, + 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, + 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, + 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, + 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, + 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, + 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, + 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, + 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, + 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, + 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, + 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, + 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, + 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, + 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - return this; +const _hslA = { h: 0, s: 0, l: 0 }; +const _hslB = { h: 0, s: 0, l: 0 }; - } +function hue2rgb( p, q, t ) { - addScaledVector( v, s ) { + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; +} - return this; +/** + * A Color instance is represented by RGB components in the linear <i>working + * color space</i>, which defaults to `LinearSRGBColorSpace`. Inputs + * conventionally using `SRGBColorSpace` (such as hexadecimals and CSS + * strings) are converted to the working color space automatically. + * + * ```js + * // converted automatically from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setHex( 0x112233 ); + * ``` + * Source color spaces may be specified explicitly, to ensure correct conversions. + * ```js + * // assumed already LinearSRGBColorSpace; no conversion + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); + * + * // converted explicitly from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); + * ``` + * If THREE.ColorManagement is disabled, no conversions occur. For details, + * see <i>Color management</i>. Iterating through a Color instance will yield + * its components (r, g, b) in the corresponding order. A Color can be initialised + * in any of the following ways: + * ```js + * //empty constructor - will default white + * const color1 = new THREE.Color(); + * + * //Hexadecimal color (recommended) + * const color2 = new THREE.Color( 0xff0000 ); + * + * //RGB string + * const color3 = new THREE.Color("rgb(255, 0, 0)"); + * const color4 = new THREE.Color("rgb(100%, 0%, 0%)"); + * + * //X11 color name - all 140 color names are supported. + * //Note the lack of CamelCase in the name + * const color5 = new THREE.Color( 'skyblue' ); + * //HSL string + * const color6 = new THREE.Color("hsl(0, 100%, 50%)"); + * + * //Separate RGB values between 0 and 1 + * const color7 = new THREE.Color( 1, 0, 0 ); + * ``` + */ +class Color { - } + /** + * Constructs a new color. + * + * Note that standard method of specifying color in three.js is with a hexadecimal triplet, + * and that method is used throughout the rest of the documentation. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + */ + constructor( r, g, b ) { - sub( v ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColor = true; - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; + /** + * The red component. + * + * @type {number} + * @default 1 + */ + this.r = 1; - return this; + /** + * The green component. + * + * @type {number} + * @default 1 + */ + this.g = 1; + + /** + * The blue component. + * + * @type {number} + * @default 1 + */ + this.b = 1; + + return this.set( r, g, b ); } - subScalar( s ) { + /** + * Sets the colors's components from the given values. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + * @return {Color} A reference to this color. + */ + set( r, g, b ) { - this.x -= s; - this.y -= s; - this.z -= s; + if ( g === undefined && b === undefined ) { - return this; + // r is THREE.Color, hex or string - } + const value = r; - subVectors( a, b ) { + if ( value && value.isColor ) { - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; + this.copy( value ); - return this; + } else if ( typeof value === 'number' ) { - } + this.setHex( value ); - multiply( v ) { + } else if ( typeof value === 'string' ) { - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; + this.setStyle( value ); - return this; + } - } + } else { - multiplyScalar( scalar ) { + this.setRGB( r, g, b ); - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; + } return this; } - multiplyVectors( a, b ) { + /** + * Sets the colors's components to the given scalar value. + * + * @param {number} scalar - The scalar value. + * @return {Color} A reference to this color. + */ + setScalar( scalar ) { - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; + this.r = scalar; + this.g = scalar; + this.b = scalar; return this; } - applyEuler( euler ) { + /** + * Sets this color from a hexadecimal value. + * + * @param {number} hex - The hexadecimal value. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHex( hex, colorSpace = SRGBColorSpace ) { - return this.applyQuaternion( _quaternion$2.setFromEuler( euler ) ); + hex = Math.floor( hex ); - } + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; - applyAxisAngle( axis, angle ) { + ColorManagement.colorSpaceToWorking( this, colorSpace ); - return this.applyQuaternion( _quaternion$2.setFromAxisAngle( axis, angle ) ); + return this; } - applyMatrix3( m ) { + /** + * Sets this color from RGB values. + * + * @param {number} r - Red channel value between `0.0` and `1.0`. + * @param {number} g - Green channel value between `0.0` and `1.0`. + * @param {number} b - Blue channel value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + this.r = r; + this.g = g; + this.b = b; - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + ColorManagement.colorSpaceToWorking( this, colorSpace ); return this; } - applyNormalMatrix( m ) { + /** + * Sets this color from RGB values. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - return this.applyMatrix3( m ).normalize(); + // h,s,l ranges are in 0.0 - 1.0 + h = euclideanModulo( h, 1 ); + s = clamp( s, 0, 1 ); + l = clamp( l, 0, 1 ); - } + if ( s === 0 ) { - applyMatrix4( m ) { + this.r = this.g = this.b = l; - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + } else { - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + const q = ( 2 * l ) - p; - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); + + } + + ColorManagement.colorSpaceToWorking( this, colorSpace ); return this; } - applyQuaternion( q ) { - - // quaternion q is assumed to have unit length + /** + * Sets this color from a CSS-style string. For example, `rgb(250, 0,0)`, + * `rgb(100%, 0%, 0%)`, `hsl(0, 100%, 50%)`, `#ff0000`, `#f00`, or `red` ( or + * any [X11 color name]{@link https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart} - + * all 140 color names are supported). + * + * @param {string} style - Color as a CSS-style string. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setStyle( style, colorSpace = SRGBColorSpace ) { - const vx = this.x, vy = this.y, vz = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + function handleAlpha( string ) { - // t = 2 * cross( q.xyz, v ); - const tx = 2 * ( qy * vz - qz * vy ); - const ty = 2 * ( qz * vx - qx * vz ); - const tz = 2 * ( qx * vy - qy * vx ); + if ( string === undefined ) return; - // v + q.w * t + cross( q.xyz, t ); - this.x = vx + qw * tx + qy * tz - qz * ty; - this.y = vy + qw * ty + qz * tx - qx * tz; - this.z = vz + qw * tz + qx * ty - qy * tx; + if ( parseFloat( string ) < 1 ) { - return this; + console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - } + } - project( camera ) { + } - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - } + let m; - unproject( camera ) { + if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); + // rgb / hsl - } + let color; + const name = m[ 1 ]; + const components = m[ 2 ]; - transformDirection( m ) { + switch ( name ) { - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction + case 'rgb': + case 'rgba': - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + // rgb(255,0,0) rgba(255,0,0,0.5) - return this.normalize(); + handleAlpha( color[ 4 ] ); - } + return this.setRGB( + Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, + colorSpace + ); - divide( v ) { + } - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; + if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - return this; + // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - } + handleAlpha( color[ 4 ] ); - divideScalar( scalar ) { + return this.setRGB( + Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, + colorSpace + ); - return this.multiplyScalar( 1 / scalar ); + } - } + break; - min( v ) { + case 'hsl': + case 'hsla': - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); + if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - return this; + // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - } + handleAlpha( color[ 4 ] ); - max( v ) { + return this.setHSL( + parseFloat( color[ 1 ] ) / 360, + parseFloat( color[ 2 ] ) / 100, + parseFloat( color[ 3 ] ) / 100, + colorSpace + ); - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); + } - return this; + break; - } + default: - clamp( min, max ) { + console.warn( 'THREE.Color: Unknown color model ' + style ); - // assumes min < max, componentwise + } - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - return this; + // hex color - } + const hex = m[ 1 ]; + const size = hex.length; - clampScalar( minVal, maxVal ) { + if ( size === 3 ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); + // #ff0 + return this.setRGB( + parseInt( hex.charAt( 0 ), 16 ) / 15, + parseInt( hex.charAt( 1 ), 16 ) / 15, + parseInt( hex.charAt( 2 ), 16 ) / 15, + colorSpace + ); - return this; + } else if ( size === 6 ) { - } + // #ff0000 + return this.setHex( parseInt( hex, 16 ), colorSpace ); - clampLength( min, max ) { + } else { - const length = this.length(); + console.warn( 'THREE.Color: Invalid hex color ' + style ); - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + } - } + } else if ( style && style.length > 0 ) { - floor() { + return this.setColorName( style, colorSpace ); - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); + } return this; } - ceil() { + /** + * Sets this color from a color name. Faster than {@link Color#setStyle} if + * you don't need the other CSS-style formats. + * + * For convenience, the list of names is exposed in `Color.NAMES` as a hash. + * ```js + * Color.NAMES.aliceblue // returns 0xF0F8FF + * ``` + * + * @param {string} style - The color name. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setColorName( style, colorSpace = SRGBColorSpace ) { - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); + // color keywords + const hex = _colorKeywords[ style.toLowerCase() ]; - return this; + if ( hex !== undefined ) { - } + // red + this.setHex( hex, colorSpace ); - round() { + } else { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); + // unknown color + console.warn( 'THREE.Color: Unknown color ' + style ); + + } return this; } - roundToZero() { - - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - this.z = Math.trunc( this.z ); + /** + * Returns a new color with copied values from this instance. + * + * @return {Color} A clone of this instance. + */ + clone() { - return this; + return new this.constructor( this.r, this.g, this.b ); } - negate() { + /** + * Copies the values of the given color to this instance. + * + * @param {Color} color - The color to copy. + * @return {Color} A reference to this color. + */ + copy( color ) { - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; + this.r = color.r; + this.g = color.g; + this.b = color.b; return this; } - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z; - - } - - // TODO lengthSquared? + /** + * Copies the given color into this color, and then converts this color from + * `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copySRGBToLinear( color ) { - lengthSq() { + this.r = SRGBToLinear( color.r ); + this.g = SRGBToLinear( color.g ); + this.b = SRGBToLinear( color.b ); - return this.x * this.x + this.y * this.y + this.z * this.z; + return this; } - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - - } + /** + * Copies the given color into this color, and then converts this color from + * `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copyLinearToSRGB( color ) { - manhattanLength() { + this.r = LinearToSRGB( color.r ); + this.g = LinearToSRGB( color.g ); + this.b = LinearToSRGB( color.b ); - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + return this; } - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } + /** + * Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertSRGBToLinear() { - setLength( length ) { + this.copySRGBToLinear( this ); - return this.normalize().multiplyScalar( length ); + return this; } - lerp( v, alpha ) { + /** + * Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertLinearToSRGB() { - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; + this.copyLinearToSRGB( this ); return this; } - lerpVectors( v1, v2, alpha ) { + /** + * Returns the hexadecimal value of this color. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {number} The hexadecimal value. + */ + getHex( colorSpace = SRGBColorSpace ) { - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - return this; + return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); } - cross( v ) { + /** + * Returns the hexadecimal value of this color as a string (for example, 'FFFFFF'). + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The hexadecimal value as a string. + */ + getHexString( colorSpace = SRGBColorSpace ) { - return this.crossVectors( this, v ); + return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( -6 ); } - crossVectors( a, b ) { + /** + * Converts the colors RGB values into the HSL format and stores them into the + * given target object. + * + * @param {{h:number,s:number,l:number}} target - The target object that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {{h:number,s:number,l:number}} The HSL representation of this color. + */ + getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; + // h,s,l ranges are in 0.0 - 1.0 - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - return this; + const r = _color.r, g = _color.g, b = _color.b; - } + const max = Math.max( r, g, b ); + const min = Math.min( r, g, b ); - projectOnVector( v ) { + let hue, saturation; + const lightness = ( min + max ) / 2.0; - const denominator = v.lengthSq(); + if ( min === max ) { - if ( denominator === 0 ) return this.set( 0, 0, 0 ); + hue = 0; + saturation = 0; - const scalar = v.dot( this ) / denominator; + } else { - return this.copy( v ).multiplyScalar( scalar ); + const delta = max - min; - } + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - projectOnPlane( planeNormal ) { + switch ( max ) { - _vector$8.copy( this ).projectOnVector( planeNormal ); + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; - return this.sub( _vector$8 ); + } + + hue /= 6; + + } + + target.h = hue; + target.s = saturation; + target.l = lightness; + + return target; } - reflect( normal ) { + /** + * Returns the RGB values of this color and stores them into the given target object. + * + * @param {Color} target - The target color that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} The RGB representation of this color. + */ + getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - return this.sub( _vector$8.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + target.r = _color.r; + target.g = _color.g; + target.b = _color.b; + + return target; } - angleTo( v ) { + /** + * Returns the value of this color as a CSS style string. Example: `rgb(255,0,0)`. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The CSS representation of this color. + */ + getStyle( colorSpace = SRGBColorSpace ) { - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - if ( denominator === 0 ) return Math.PI / 2; + const r = _color.r, g = _color.g, b = _color.b; - const theta = this.dot( v ) / denominator; + if ( colorSpace !== SRGBColorSpace ) { - // clamp, to handle numerical problems + // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). + return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; - return Math.acos( clamp( theta, - 1, 1 ) ); + } + + return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; } - distanceTo( v ) { + /** + * Adds the given HSL values to this color's values. + * Internally, this converts the color's RGB values to HSL, adds HSL + * and then converts the color back to RGB. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @return {Color} A reference to this color. + */ + offsetHSL( h, s, l ) { - return Math.sqrt( this.distanceToSquared( v ) ); + this.getHSL( _hslA ); + + return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); } - distanceToSquared( v ) { + /** + * Adds the RGB values of the given color to the RGB values of this color. + * + * @param {Color} color - The color to add. + * @return {Color} A reference to this color. + */ + add( color ) { - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + this.r += color.r; + this.g += color.g; + this.b += color.b; - return dx * dx + dy * dy + dz * dz; + return this; } - manhattanDistanceTo( v ) { + /** + * Adds the RGB values of the given colors and stores the result in this instance. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @return {Color} A reference to this color. + */ + addColors( color1, color2 ) { - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; + + return this; } - setFromSpherical( s ) { + /** + * Adds the given scalar value to the RGB values of this color. + * + * @param {number} s - The scalar to add. + * @return {Color} A reference to this color. + */ + addScalar( s ) { - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); + this.r += s; + this.g += s; + this.b += s; - } + return this; - setFromSphericalCoords( radius, phi, theta ) { + } - const sinPhiRadius = Math.sin( phi ) * radius; + /** + * Subtracts the RGB values of the given color from the RGB values of this color. + * + * @param {Color} color - The color to subtract. + * @return {Color} A reference to this color. + */ + sub( color ) { - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); + this.r = Math.max( 0, this.r - color.r ); + this.g = Math.max( 0, this.g - color.g ); + this.b = Math.max( 0, this.b - color.b ); return this; } - setFromCylindrical( c ) { + /** + * Multiplies the RGB values of the given color with the RGB values of this color. + * + * @param {Color} color - The color to multiply. + * @return {Color} A reference to this color. + */ + multiply( color ) { - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + + return this; } - setFromCylindricalCoords( radius, theta, y ) { + /** + * Multiplies the given scalar value with the RGB values of this color. + * + * @param {number} s - The scalar to multiply. + * @return {Color} A reference to this color. + */ + multiplyScalar( s ) { - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); + this.r *= s; + this.g *= s; + this.b *= s; return this; } - setFromMatrixPosition( m ) { - - const e = m.elements; + /** + * Linearly interpolates this color's RGB values toward the RGB values of the + * given color. The alpha argument can be thought of as the ratio between + * the two colors, where `0.0` is this color and `1.0` is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerp( color, alpha ) { - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; return this; } - setFromMatrixScale( m ) { - - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); + /** + * Linearly interpolates between the given colors and stores the result in this instance. + * The alpha argument can be thought of as the ratio between the two colors, where `0.0` + * is the first and `1.0` is the second color. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpColors( color1, color2, alpha ) { - this.x = sx; - this.y = sy; - this.z = sz; + this.r = color1.r + ( color2.r - color1.r ) * alpha; + this.g = color1.g + ( color2.g - color1.g ) * alpha; + this.b = color1.b + ( color2.b - color1.b ) * alpha; return this; } - setFromMatrixColumn( m, index ) { + /** + * Linearly interpolates this color's HSL values toward the HSL values of the + * given color. It differs from {@link Color#lerp} by not interpolating straight + * from one color to the other, but instead going through all the hues in between + * those two colors. The alpha argument can be thought of as the ratio between + * the two colors, where 0.0 is this color and 1.0 is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpHSL( color, alpha ) { - return this.fromArray( m.elements, index * 4 ); + this.getHSL( _hslA ); + color.getHSL( _hslB ); - } + const h = lerp( _hslA.h, _hslB.h, alpha ); + const s = lerp( _hslA.s, _hslB.s, alpha ); + const l = lerp( _hslA.l, _hslB.l, alpha ); - setFromMatrix3Column( m, index ) { + this.setHSL( h, s, l ); - return this.fromArray( m.elements, index * 3 ); + return this; } - setFromEuler( e ) { + /** + * Sets the color's RGB components from the given 3D vector. + * + * @param {Vector3} v - The vector to set. + * @return {Color} A reference to this color. + */ + setFromVector3( v ) { - this.x = e._x; - this.y = e._y; - this.z = e._z; + this.r = v.x; + this.g = v.y; + this.b = v.z; return this; } - setFromColor( c ) { + /** + * Transforms this color with the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix. + * @return {Color} A reference to this color. + */ + applyMatrix3( m ) { - this.x = c.r; - this.y = c.g; - this.z = c.b; + const r = this.r, g = this.g, b = this.b; + const e = m.elements; + + this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; + this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; + this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; return this; } - equals( v ) { + /** + * Returns `true` if this color is equal with the given one. + * + * @param {Color} c - The color to test for equality. + * @return {boolean} Whether this bounding color is equal with the given one. + */ + equals( c ) { - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); } + /** + * Sets this color's RGB components from the given array. + * + * @param {Array<number>} array - An array holding the RGB values. + * @param {number} [offset=0] - The offset into the array. + * @return {Color} A reference to this color. + */ fromArray( array, offset = 0 ) { - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; + this.r = array[ offset ]; + this.g = array[ offset + 1 ]; + this.b = array[ offset + 2 ]; return this; } + /** + * Writes the RGB components of this color to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the color components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The color components. + */ toArray( array = [], offset = 0 ) { - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; + array[ offset ] = this.r; + array[ offset + 1 ] = this.g; + array[ offset + 2 ] = this.b; return array; } + /** + * Sets the components of this color from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding color data. + * @param {number} index - The index into the attribute. + * @return {Color} A reference to this color. + */ fromBufferAttribute( attribute, index ) { - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); + this.r = attribute.getX( index ); + this.g = attribute.getY( index ); + this.b = attribute.getZ( index ); return this; } - randomDirection() { - - // https://mathworld.wolfram.com/SpherePointPicking.html - - const theta = Math.random() * Math.PI * 2; - const u = Math.random() * 2 - 1; - const c = Math.sqrt( 1 - u * u ); - - this.x = c * Math.cos( theta ); - this.y = u; - this.z = c * Math.sin( theta ); + /** + * This methods defines the serialization result of this class. Returns the color + * as a hexadecimal value. + * + * @return {number} The hexadecimal value. + */ + toJSON() { - return this; + return this.getHex(); } *[ Symbol.iterator ]() { - yield this.x; - yield this.y; - yield this.z; + yield this.r; + yield this.g; + yield this.b; } } -const _vector$8 = /*@__PURE__*/ new Vector3(); -const _quaternion$2 = /*@__PURE__*/ new Quaternion(); +const _color = /*@__PURE__*/ new Color(); + +/** + * A dictionary with X11 color names. + * + * Note that multiple words such as Dark Orange become the string 'darkorange'. + * + * @static + * @type {Object} + */ +Color.NAMES = _colorKeywords; +/** + * Represents an axis-aligned bounding box (AABB) in 3D space. + */ class Box3 { + /** + * Constructs a new bounding box. + * + * @param {Vector3} [min=(Infinity,Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector3} [max=(-Infinity,-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBox3 = true; + /** + * The lower boundary of the box. + * + * @type {Vector3} + */ this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector3} + */ this.max = max; } + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector3} min - The lower boundary of the box. + * @param {Vector3} max - The upper boundary of the box. + * @return {Box3} A reference to this bounding box. + */ set( min, max ) { this.min.copy( min ); @@ -5110,6 +10151,13 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array<number>} array - An array holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ setFromArray( array ) { this.makeEmpty(); @@ -5124,6 +10172,13 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given buffer attribute. + * + * @param {BufferAttribute} attribute - A buffer attribute holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ setFromBufferAttribute( attribute ) { this.makeEmpty(); @@ -5138,6 +10193,13 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array<Vector3>} points - An array holding 3D position data as instances of {@link Vector3}. + * @return {Box3} A reference to this bounding box. + */ setFromPoints( points ) { this.makeEmpty(); @@ -5152,6 +10214,14 @@ class Box3 { } + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector3} center - The center of the box. + * @param {Vector3} size - The x, y and z dimensions of the box. + * @return {Box3} A reference to this bounding box. + */ setFromCenterAndSize( center, size ) { const halfSize = _vector$7.copy( size ).multiplyScalar( 0.5 ); @@ -5163,6 +10233,16 @@ class Box3 { } + /** + * Computes the world-axis-aligned bounding box for the given 3D object + * (including its children), accounting for the object's, and children's, + * world transforms. The function may result in a larger box than strictly necessary. + * + * @param {Object3D} object - The 3D object to compute the bounding box for. + * @param {boolean} [precise=false] - If set to `true`, the method computes the smallest + * world-axis-aligned bounding box at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ setFromObject( object, precise = false ) { this.makeEmpty(); @@ -5171,12 +10251,23 @@ class Box3 { } + /** + * Returns a new box with copied values from this instance. + * + * @return {Box3} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given box to this instance. + * + * @param {Box3} box - The box to copy. + * @return {Box3} A reference to this bounding box. + */ copy( box ) { this.min.copy( box.min ); @@ -5186,6 +10277,11 @@ class Box3 { } + /** + * Makes this box empty which means in encloses a zero space in 3D. + * + * @return {Box3} A reference to this bounding box. + */ makeEmpty() { this.min.x = this.min.y = this.min.z = + Infinity; @@ -5195,6 +10291,13 @@ class Box3 { } + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ isEmpty() { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes @@ -5203,18 +10306,36 @@ class Box3 { } + /** + * Returns the center point of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ getCenter( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } + /** + * Returns the dimensions of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The size. + */ getSize( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); } + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector3} point - The point that should be included by the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByPoint( point ) { this.min.min( point ); @@ -5224,6 +10345,16 @@ class Box3 { } + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. The depth of this box will be + * expanded by the z component of the vector in both directions. + * + * @param {Vector3} vector - The vector that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByVector( vector ) { this.min.sub( vector ); @@ -5233,6 +10364,13 @@ class Box3 { } + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByScalar( scalar ) { this.min.addScalar( - scalar ); @@ -5242,6 +10380,17 @@ class Box3 { } + /** + * Expands the boundaries of this box to include the given 3D object and + * its children, accounting for the object's, and children's, world + * transforms. The function may result in a larger box than strictly + * necessary (unless the precise parameter is set to true). + * + * @param {Object3D} object - The 3D object that should expand the bounding box. + * @param {boolean} precise - If set to `true`, the method expands the bounding box + * as little as necessary at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ expandByObject( object, precise = false ) { // Computes the world-axis-aligned bounding box of an object (including its children), @@ -5326,14 +10475,27 @@ class Box3 { } + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ containsPoint( point ) { - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y && + point.z >= this.min.z && point.z <= this.max.z; } + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ containsBox( box ) { return this.min.x <= box.min.x && box.max.x <= this.max.x && @@ -5342,6 +10504,13 @@ class Box3 { } + /** + * Returns a point as a proportion of this box's width, height and depth. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A point as a proportion of this box's width, height and depth. + */ getParameter( point, target ) { // This can potentially have a divide by zero if the box @@ -5355,15 +10524,27 @@ class Box3 { } + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ intersectsBox( box ) { // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y && + box.max.z >= this.min.z && box.min.z <= this.max.z; } + /** + * Returns `true` if the given bounding sphere intersects with this bounding box. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with this bounding box. + */ intersectsSphere( sphere ) { // Find the point on the AABB closest to the sphere center. @@ -5374,6 +10555,12 @@ class Box3 { } + /** + * Returns `true` if the given plane intersects with this bounding box. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether the given plane intersects with this bounding box. + */ intersectsPlane( plane ) { // We compute the minimum and maximum dot product values. If those values @@ -5421,6 +10608,12 @@ class Box3 { } + /** + * Returns `true` if the given triangle intersects with this bounding box. + * + * @param {Triangle} triangle - The triangle to test. + * @return {boolean} Whether the given triangle intersects with this bounding box. + */ intersectsTriangle( triangle ) { if ( this.isEmpty() ) { @@ -5434,14 +10627,14 @@ class Box3 { _extents.subVectors( this.max, _center ); // translate triangle to aabb origin - _v0$2.subVectors( triangle.a, _center ); + _v0$3.subVectors( triangle.a, _center ); _v1$6.subVectors( triangle.b, _center ); _v2$3.subVectors( triangle.c, _center ); // compute edge vectors for triangle - _f0.subVectors( _v1$6, _v0$2 ); + _f0.subVectors( _v1$6, _v0$3 ); _f1.subVectors( _v2$3, _v1$6 ); - _f2.subVectors( _v0$2, _v2$3 ); + _f2.subVectors( _v0$3, _v2$3 ); // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation @@ -5451,7 +10644,7 @@ class Box3 { _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 ]; - if ( ! satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ) ) { + if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { return false; @@ -5459,7 +10652,7 @@ class Box3 { // test 3 face normals from the aabb axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ) ) { + if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { return false; @@ -5470,22 +10663,42 @@ class Box3 { _triangleNormal.crossVectors( _f0, _f1 ); axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - return satForAxes( axes, _v0$2, _v1$6, _v2$3, _extents ); + return satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ); } + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector3} point - The point to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ clampPoint( point, target ) { return target.copy( point ).clamp( this.min, this.max ); } + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ distanceToPoint( point ) { return this.clampPoint( point, _vector$7 ).distanceTo( point ); } + /** + * Returns a bounding sphere that encloses this bounding box. + * + * @param {Sphere} target - The target sphere that is used to store the method's result. + * @return {Sphere} The bounding sphere that encloses this bounding box. + */ getBoundingSphere( target ) { if ( this.isEmpty() ) { @@ -5504,6 +10717,15 @@ class Box3 { } + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box3} box - The bounding box to intersect with. + * @return {Box3} A reference to this bounding box. + */ intersect( box ) { this.min.max( box.min ); @@ -5516,6 +10738,14 @@ class Box3 { } + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box3} box - The bounding box that will be unioned with this instance. + * @return {Box3} A reference to this bounding box. + */ union( box ) { this.min.min( box.min ); @@ -5525,6 +10755,12 @@ class Box3 { } + /** + * Transforms this bounding box by the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Box3} A reference to this bounding box. + */ applyMatrix4( matrix ) { // transform of empty box is an empty box. @@ -5546,6 +10782,13 @@ class Box3 { } + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 3D space. + * + * @param {Vector3} offset - The offset that should be used to translate the bounding box. + * @return {Box3} A reference to this bounding box. + */ translate( offset ) { this.min.add( offset ); @@ -5555,12 +10798,46 @@ class Box3 { } + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box3} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ equals( box ) { return box.min.equals( this.min ) && box.max.equals( this.max ); } + /** + * Returns a serialized structure of the bounding box. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + min: this.min.toArray(), + max: this.max.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding box. + * + * @param {Object} json - The serialized json to set the box from. + * @return {Box3} A reference to this bounding box. + */ + fromJSON( json ) { + + this.min.fromArray( json.min ); + this.max.fromArray( json.max ); + return this; + + } + } const _points = [ @@ -5580,7 +10857,7 @@ const _box$3 = /*@__PURE__*/ new Box3(); // triangle centered vertices -const _v0$2 = /*@__PURE__*/ new Vector3(); +const _v0$3 = /*@__PURE__*/ new Vector3(); const _v1$6 = /*@__PURE__*/ new Vector3(); const _v2$3 = /*@__PURE__*/ new Vector3(); @@ -5625,17 +10902,52 @@ const _box$2 = /*@__PURE__*/ new Box3(); const _v1$5 = /*@__PURE__*/ new Vector3(); const _v2$2 = /*@__PURE__*/ new Vector3(); +/** + * An analytical 3D sphere defined by a center and radius. This class is mainly + * used as a Bounding Sphere for 3D objects. + */ class Sphere { - constructor( center = new Vector3(), radius = - 1 ) { + /** + * Constructs a new sphere. + * + * @param {Vector3} [center=(0,0,0)] - The center of the sphere + * @param {number} [radius=-1] - The radius of the sphere. + */ + constructor( center = new Vector3(), radius = -1 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSphere = true; + /** + * The center of the sphere + * + * @type {Vector3} + */ this.center = center; + + /** + * The radius of the sphere. + * + * @type {number} + */ this.radius = radius; } + /** + * Sets the sphere's components by copying the given values. + * + * @param {Vector3} center - The center. + * @param {number} radius - The radius. + * @return {Sphere} A reference to this sphere. + */ set( center, radius ) { this.center.copy( center ); @@ -5645,6 +10957,16 @@ class Sphere { } + /** + * Computes the minimum bounding sphere for list of points. + * If the optional center point is given, it is used as the sphere's + * center. Otherwise, the center of the axis-aligned bounding box + * encompassing the points is calculated. + * + * @param {Array<Vector3>} points - A list of points in 3D space. + * @param {Vector3} [optionalCenter] - The center of the sphere. + * @return {Sphere} A reference to this sphere. + */ setFromPoints( points, optionalCenter ) { const center = this.center; @@ -5673,6 +10995,12 @@ class Sphere { } + /** + * Copies the values of the given sphere to this instance. + * + * @param {Sphere} sphere - The sphere to copy. + * @return {Sphere} A reference to this sphere. + */ copy( sphere ) { this.center.copy( sphere.center ); @@ -5682,33 +11010,67 @@ class Sphere { } + /** + * Returns `true` if the sphere is empty (the radius set to a negative number). + * + * Spheres with a radius of `0` contain only their center point and are not + * considered to be empty. + * + * @return {boolean} Whether this sphere is empty or not. + */ isEmpty() { return ( this.radius < 0 ); } + /** + * Makes this sphere empty which means in encloses a zero space in 3D. + * + * @return {Sphere} A reference to this sphere. + */ makeEmpty() { this.center.set( 0, 0, 0 ); - this.radius = - 1; + this.radius = -1; return this; } + /** + * Returns `true` if this sphere contains the given point inclusive of + * the surface of the sphere. + * + * @param {Vector3} point - The point to check. + * @return {boolean} Whether this sphere contains the given point or not. + */ containsPoint( point ) { return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); } + /** + * Returns the closest distance from the boundary of the sphere to the + * given point. If the sphere contains the point, the distance will + * be negative. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The distance to the point. + */ distanceToPoint( point ) { return ( point.distanceTo( this.center ) - this.radius ); } + /** + * Returns `true` if this sphere intersects with the given one. + * + * @param {Sphere} sphere - The sphere to test. + * @return {boolean} Whether this sphere intersects with the given one or not. + */ intersectsSphere( sphere ) { const radiusSum = this.radius + sphere.radius; @@ -5717,18 +11079,39 @@ class Sphere { } + /** + * Returns `true` if this sphere intersects with the given box. + * + * @param {Box3} box - The box to test. + * @return {boolean} Whether this sphere intersects with the given box or not. + */ intersectsBox( box ) { return box.intersectsSphere( this ); } + /** + * Returns `true` if this sphere intersects with the given plane. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether this sphere intersects with the given plane or not. + */ intersectsPlane( plane ) { return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; } + /** + * Clamps a point within the sphere. If the point is outside the sphere, it + * will clamp it to the closest point on the edge of the sphere. Points + * already inside the sphere will not be affected. + * + * @param {Vector3} point - The plane to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ clampPoint( point, target ) { const deltaLengthSq = this.center.distanceToSquared( point ); @@ -5746,6 +11129,12 @@ class Sphere { } + /** + * Returns a bounding box that encloses this sphere. + * + * @param {Box3} target - The target box that is used to store the method's result. + * @return {Box3} The bounding box that encloses this sphere. + */ getBoundingBox( target ) { if ( this.isEmpty() ) { @@ -5763,6 +11152,12 @@ class Sphere { } + /** + * Transforms this sphere with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Sphere} A reference to this sphere. + */ applyMatrix4( matrix ) { this.center.applyMatrix4( matrix ); @@ -5772,6 +11167,12 @@ class Sphere { } + /** + * Translates the sphere's center by the given offset. + * + * @param {Vector3} offset - The offset. + * @return {Sphere} A reference to this sphere. + */ translate( offset ) { this.center.add( offset ); @@ -5780,6 +11181,12 @@ class Sphere { } + /** + * Expands the boundaries of this sphere to include the given point. + * + * @param {Vector3} point - The point to include. + * @return {Sphere} A reference to this sphere. + */ expandByPoint( point ) { if ( this.isEmpty() ) { @@ -5814,6 +11221,12 @@ class Sphere { } + /** + * Expands this sphere to enclose both the original sphere and the given sphere. + * + * @param {Sphere} sphere - The sphere to include. + * @return {Sphere} A reference to this sphere. + */ union( sphere ) { if ( sphere.isEmpty() ) { @@ -5848,37 +11261,111 @@ class Sphere { } + /** + * Returns `true` if this sphere is equal with the given one. + * + * @param {Sphere} sphere - The sphere to test for equality. + * @return {boolean} Whether this bounding sphere is equal with the given one. + */ equals( sphere ) { return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); } + /** + * Returns a new sphere with copied values from this instance. + * + * @return {Sphere} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Returns a serialized structure of the bounding sphere. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + radius: this.radius, + center: this.center.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding sphere. + * + * @param {Object} json - The serialized json to set the sphere from. + * @return {Box3} A reference to this bounding sphere. + */ + fromJSON( json ) { + + this.radius = json.radius; + this.center.fromArray( json.center ); + return this; + + } + } const _vector1 = /*@__PURE__*/ new Vector3(); const _vector2$1 = /*@__PURE__*/ new Vector3(); const _normalMatrix = /*@__PURE__*/ new Matrix3(); +/** + * A two dimensional surface that extends infinitely in 3D space, represented + * in [Hessian normal form]{@link http://mathworld.wolfram.com/HessianNormalForm.html} + * by a unit length normal vector and a constant. + */ class Plane { + /** + * Constructs a new plane. + * + * @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane. + * @param {number} [constant=0] - The signed distance from the origin to the plane. + */ constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPlane = true; - // normal is assumed to be normalized - + /** + * A unit length vector defining the normal of the plane. + * + * @type {Vector3} + */ this.normal = normal; + + /** + * The signed distance from the origin to the plane. + * + * @type {number} + * @default 0 + */ this.constant = constant; } + /** + * Sets the plane components by copying the given values. + * + * @param {Vector3} normal - The normal. + * @param {number} constant - The constant. + * @return {Plane} A reference to this plane. + */ set( normal, constant ) { this.normal.copy( normal ); @@ -5888,6 +11375,16 @@ class Plane { } + /** + * Sets the plane components by defining `x`, `y`, `z` as the + * plane normal and `w` as the constant. + * + * @param {number} x - The value for the normal's x component. + * @param {number} y - The value for the normal's y component. + * @param {number} z - The value for the normal's z component. + * @param {number} w - The constant value. + * @return {Plane} A reference to this plane. + */ setComponents( x, y, z, w ) { this.normal.set( x, y, z ); @@ -5897,6 +11394,14 @@ class Plane { } + /** + * Sets the plane from the given normal and coplanar point (that is a point + * that lies onto the plane). + * + * @param {Vector3} normal - The normal. + * @param {Vector3} point - A coplanar point. + * @return {Plane} A reference to this plane. + */ setFromNormalAndCoplanarPoint( normal, point ) { this.normal.copy( normal ); @@ -5906,6 +11411,16 @@ class Plane { } + /** + * Sets the plane from three coplanar points. The winding order is + * assumed to be counter-clockwise, and determines the direction of + * the plane normal. + * + * @param {Vector3} a - The first coplanar point. + * @param {Vector3} b - The second coplanar point. + * @param {Vector3} c - The third coplanar point. + * @return {Plane} A reference to this plane. + */ setFromCoplanarPoints( a, b, c ) { const normal = _vector1.subVectors( c, b ).cross( _vector2$1.subVectors( a, b ) ).normalize(); @@ -5918,6 +11433,12 @@ class Plane { } + /** + * Copies the values of the given plane to this instance. + * + * @param {Plane} plane - The plane to copy. + * @return {Plane} A reference to this plane. + */ copy( plane ) { this.normal.copy( plane.normal ); @@ -5927,6 +11448,11 @@ class Plane { } + /** + * Normalizes the plane normal and adjusts the constant accordingly. + * + * @return {Plane} A reference to this plane. + */ normalize() { // Note: will lead to a divide by zero if the plane is invalid. @@ -5939,33 +11465,66 @@ class Plane { } + /** + * Negates both the plane normal and the constant. + * + * @return {Plane} A reference to this plane. + */ negate() { - this.constant *= - 1; + this.constant *= -1; this.normal.negate(); return this; } + /** + * Returns the signed distance from the given point to this plane. + * + * @param {Vector3} point - The point to compute the distance for. + * @return {number} The signed distance. + */ distanceToPoint( point ) { return this.normal.dot( point ) + this.constant; } + /** + * Returns the signed distance from the given sphere to this plane. + * + * @param {Sphere} sphere - The sphere to compute the distance for. + * @return {number} The signed distance. + */ distanceToSphere( sphere ) { return this.distanceToPoint( sphere.center ) - sphere.radius; } + /** + * Projects a the given point onto the plane. + * + * @param {Vector3} point - The point to project. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The projected point on the plane. + */ projectPoint( point, target ) { return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); } + /** + * Returns the intersection point of the passed line and the plane. Returns + * `null` if the line does not intersect. Returns the line's starting point if + * the line is coplanar with the plane. + * + * @param {Line3} line - The line to compute the intersection for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectLine( line, target ) { const direction = line.delta( _vector1 ); @@ -5998,6 +11557,12 @@ class Plane { } + /** + * Returns `true` if the given line segment intersects with (passes through) the plane. + * + * @param {Line3} line - The line to test. + * @return {boolean} Whether the given line segment intersects with the plane or not. + */ intersectsLine( line ) { // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. @@ -6009,24 +11574,55 @@ class Plane { } + /** + * Returns `true` if the given bounding box intersects with the plane. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with the plane or not. + */ intersectsBox( box ) { return box.intersectsPlane( this ); } + /** + * Returns `true` if the given bounding sphere intersects with the plane. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with the plane or not. + */ intersectsSphere( sphere ) { return sphere.intersectsPlane( this ); } + /** + * Returns a coplanar vector to the plane, by calculating the + * projection of the normal at the origin onto the plane. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The coplanar point. + */ coplanarPoint( target ) { return target.copy( this.normal ).multiplyScalar( - this.constant ); } + /** + * Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform. + * + * The optional normal matrix can be pre-computed like so: + * ```js + * const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + * ``` + * + * @param {Matrix4} matrix - The transformation matrix. + * @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix. + * @return {Plane} A reference to this plane. + */ applyMatrix4( matrix, optionalNormalMatrix ) { const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); @@ -6041,6 +11637,13 @@ class Plane { } + /** + * Translates the plane by the distance defined by the given offset vector. + * Note that this only affects the plane constant and will not affect the normal vector. + * + * @param {Vector3} offset - The offset vector. + * @return {Plane} A reference to this plane. + */ translate( offset ) { this.constant -= offset.dot( this.normal ); @@ -6049,12 +11652,23 @@ class Plane { } + /** + * Returns `true` if this plane is equal with the given one. + * + * @param {Plane} plane - The plane to test for equality. + * @return {boolean} Whether this plane is equal with the given one. + */ equals( plane ) { return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); } + /** + * Returns a new plane with copied values from this instance. + * + * @return {Plane} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -6064,16 +11678,50 @@ class Plane { } const _sphere$6 = /*@__PURE__*/ new Sphere(); +const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 ); const _vector$6 = /*@__PURE__*/ new Vector3(); +/** + * Frustums are used to determine what is inside the camera's field of view. + * They help speed up the rendering process - objects which lie outside a camera's + * frustum can safely be excluded from rendering. + * + * This class is mainly intended for use internally by a renderer. + */ class Frustum { + /** + * Constructs a new frustum. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + */ constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { + /** + * This array holds the planes that enclose the frustum. + * + * @type {Array<Plane>} + */ this.planes = [ p0, p1, p2, p3, p4, p5 ]; } + /** + * Sets the frustum planes by copying the given planes. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + * @return {Frustum} A reference to this frustum. + */ set( p0, p1, p2, p3, p4, p5 ) { const planes = this.planes; @@ -6089,6 +11737,12 @@ class Frustum { } + /** + * Copies the values of the given frustum to this instance. + * + * @param {Frustum} frustum - The frustum to copy. + * @return {Frustum} A reference to this frustum. + */ copy( frustum ) { const planes = this.planes; @@ -6103,7 +11757,15 @@ class Frustum { } - setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem ) { + /** + * Sets the frustum planes from the given projection matrix. + * + * @param {Matrix4} m - The projection matrix. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} coordinateSystem - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Frustum} A reference to this frustum. + */ + setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { const planes = this.planes; const me = m.elements; @@ -6116,19 +11778,29 @@ class Frustum { planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - if ( coordinateSystem === WebGLCoordinateSystem ) { + if ( reversedDepth ) { - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + planes[ 4 ].setComponents( me2, me6, me10, me14 ).normalize(); // far + planes[ 5 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // near - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + } else { - planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // far - } else { + if ( coordinateSystem === WebGLCoordinateSystem ) { + + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); // near + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); + planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); // near + + } else { + + throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); + + } } @@ -6136,6 +11808,14 @@ class Frustum { } + /** + * Returns `true` if the 3D object's bounding sphere is intersecting this frustum. + * + * Note that the 3D object must have a geometry so that the bounding sphere can be calculated. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object's bounding sphere is intersecting this frustum or not. + */ intersectsObject( object ) { if ( object.boundingSphere !== undefined ) { @@ -6158,16 +11838,31 @@ class Frustum { } + /** + * Returns `true` if the given sprite is intersecting this frustum. + * + * @param {Sprite} sprite - The sprite to test. + * @return {boolean} Whether the sprite is intersecting this frustum or not. + */ intersectsSprite( sprite ) { _sphere$6.center.set( 0, 0, 0 ); - _sphere$6.radius = 0.7071067811865476; + + const offset = _defaultSpriteCenter.distanceTo( sprite.center ); + + _sphere$6.radius = 0.7071067811865476 + offset; _sphere$6.applyMatrix4( sprite.matrixWorld ); return this.intersectsSphere( _sphere$6 ); } + /** + * Returns `true` if the given bounding sphere is intersecting this frustum. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the bounding sphere is intersecting this frustum or not. + */ intersectsSphere( sphere ) { const planes = this.planes; @@ -6190,6 +11885,12 @@ class Frustum { } + /** + * Returns `true` if the given bounding box is intersecting this frustum. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box is intersecting this frustum or not. + */ intersectsBox( box ) { const planes = this.planes; @@ -6216,6 +11917,12 @@ class Frustum { } + /** + * Returns `true` if the given point lies within the frustum. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the point lies within this frustum or not. + */ containsPoint( point ) { const planes = this.planes; @@ -6234,6 +11941,11 @@ class Frustum { } + /** + * Returns a new frustum with copied values from this instance. + * + * @return {Frustum} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -6242,12 +11954,84 @@ class Frustum { } +/** + * Represents a 4x4 matrix. + * + * The most common use of a 4x4 matrix in 3D computer graphics is as a transformation matrix. + * For an introduction to transformation matrices as used in WebGL, check out [this tutorial]{@link https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices} + * + * This allows a 3D vector representing a point in 3D space to undergo + * transformations such as translation, rotation, shear, scale, reflection, + * orthogonal or perspective projection and so on, by being multiplied by the + * matrix. This is known as `applying` the matrix to the vector. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix4(); + * m.set( 11, 12, 13, 14, + * 21, 22, 23, 24, + * 31, 32, 33, 34, + * 41, 42, 43, 44 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, 41, + * 12, 22, 32, 42, + * 13, 23, 33, 43, + * 14, 24, 34, 44 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ class Matrix4 { + /** + * Constructs a new 4x4 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + */ constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ Matrix4.prototype.isMatrix4 = true; + /** + * A column-major list of matrix values. + * + * @type {Array<number>} + */ this.elements = [ 1, 0, 0, 0, @@ -6265,6 +12049,28 @@ class Matrix4 { } + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + * @return {Matrix4} A reference to this matrix. + */ set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { const te = this.elements; @@ -6278,6 +12084,11 @@ class Matrix4 { } + /** + * Sets this matrix to the 4x4 identity matrix. + * + * @return {Matrix4} A reference to this matrix. + */ identity() { this.set( @@ -6293,12 +12104,23 @@ class Matrix4 { } + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix4} A clone of this instance. + */ clone() { return new Matrix4().fromArray( this.elements ); } + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix4} m - The matrix to copy. + * @return {Matrix4} A reference to this matrix. + */ copy( m ) { const te = this.elements; @@ -6313,6 +12135,13 @@ class Matrix4 { } + /** + * Copies the translation component of the given matrix + * into this matrix's translation component. + * + * @param {Matrix4} m - The matrix to copy the translation component. + * @return {Matrix4} A reference to this matrix. + */ copyPosition( m ) { const te = this.elements, me = m.elements; @@ -6325,6 +12154,12 @@ class Matrix4 { } + /** + * Set the upper 3x3 elements of this matrix to the values of given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Matrix4} A reference to this matrix. + */ setFromMatrix3( m ) { const me = m.elements; @@ -6342,6 +12177,14 @@ class Matrix4 { } + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ extractBasis( xAxis, yAxis, zAxis ) { xAxis.setFromMatrixColumn( this, 0 ); @@ -6352,6 +12195,14 @@ class Matrix4 { } + /** + * Sets the given basis vectors to this matrix. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ makeBasis( xAxis, yAxis, zAxis ) { this.set( @@ -6365,10 +12216,17 @@ class Matrix4 { } + /** + * Extracts the rotation component of the given matrix + * into this matrix's rotation component. + * + * Note: This method does not support reflection matrices. + * + * @param {Matrix4} m - The matrix. + * @return {Matrix4} A reference to this matrix. + */ extractRotation( m ) { - // this method does not support reflection matrices - const te = this.elements; const me = m.elements; @@ -6400,6 +12258,16 @@ class Matrix4 { } + /** + * Sets the rotation component (the upper left 3x3 matrix) of this matrix to + * the rotation specified by the given Euler angles. The rest of + * the matrix is set to the identity. Depending on the {@link Euler#order}, + * there are six possible outcomes. See [this page]{@link https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix} + * for a complete list. + * + * @param {Euler} euler - The Euler angles. + * @return {Matrix4} A reference to this matrix. + */ makeRotationFromEuler( euler ) { const te = this.elements; @@ -6522,12 +12390,29 @@ class Matrix4 { } + /** + * Sets the rotation component of this matrix to the rotation specified by + * the given Quaternion as outlined [here]{@link https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion} + * The rest of the matrix is set to the identity. + * + * @param {Quaternion} q - The Quaternion. + * @return {Matrix4} A reference to this matrix. + */ makeRotationFromQuaternion( q ) { return this.compose( _zero, q, _one ); } + /** + * Sets the rotation component of the transformation matrix, looking from `eye` towards + * `target`, and oriented by the up-direction. + * + * @param {Vector3} eye - The eye vector. + * @param {Vector3} target - The target vector. + * @param {Vector3} up - The up vector. + * @return {Matrix4} A reference to this matrix. + */ lookAt( eye, target, up ) { const te = this.elements; @@ -6575,18 +12460,38 @@ class Matrix4 { } + /** + * Post-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ multiply( m ) { return this.multiplyMatrices( this, m ); } + /** + * Pre-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ premultiply( m ) { return this.multiplyMatrices( m, this ); } + /** + * Multiples the given 4x4 matrices and stores the result + * in this matrix. + * + * @param {Matrix4} a - The first matrix. + * @param {Matrix4} b - The second matrix. + * @return {Matrix4} A reference to this matrix. + */ multiplyMatrices( a, b ) { const ae = a.elements; @@ -6627,6 +12532,12 @@ class Matrix4 { } + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix4} A reference to this matrix. + */ multiplyScalar( s ) { const te = this.elements; @@ -6640,6 +12551,13 @@ class Matrix4 { } + /** + * Computes and returns the determinant of this matrix. + * + * Based on the method outlined [here]{@link http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html}. + * + * @return {number} The determinant. + */ determinant() { const te = this.elements; @@ -6650,7 +12568,6 @@ class Matrix4 { const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) return ( n41 * ( @@ -6690,6 +12607,11 @@ class Matrix4 { } + /** + * Transposes this matrix in place. + * + * @return {Matrix4} A reference to this matrix. + */ transpose() { const te = this.elements; @@ -6707,6 +12629,15 @@ class Matrix4 { } + /** + * Sets the position component for this matrix from the given vector, + * without affecting the rest of the matrix. + * + * @param {number|Vector3} x - The x component of the vector or alternatively the vector object. + * @param {number} y - The y component of the vector. + * @param {number} z - The z component of the vector. + * @return {Matrix4} A reference to this matrix. + */ setPosition( x, y, z ) { const te = this.elements; @@ -6729,6 +12660,13 @@ class Matrix4 { } + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix4} A reference to this matrix. + */ invert() { // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm @@ -6774,6 +12712,12 @@ class Matrix4 { } + /** + * Multiplies the columns of this matrix by the given vector. + * + * @param {Vector3} v - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ scale( v ) { const te = this.elements; @@ -6788,6 +12732,11 @@ class Matrix4 { } + /** + * Gets the maximum scale value of the three axes. + * + * @return {number} The maximum scale. + */ getMaxScaleOnAxis() { const te = this.elements; @@ -6800,6 +12749,14 @@ class Matrix4 { } + /** + * Sets this matrix as a translation transform from the given vector. + * + * @param {number|Vector3} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @param {number} z - The amount to translate in the z axis. + * @return {Matrix4} A reference to this matrix. + */ makeTranslation( x, y, z ) { if ( x.isVector3 ) { @@ -6830,6 +12787,13 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the X axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationX( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6847,6 +12811,13 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the Y axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationY( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6864,6 +12835,13 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the Z axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationZ( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6881,6 +12859,17 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the given axis by + * the given angle. + * + * This is a somewhat controversial but mathematically sound alternative to + * rotating via Quaternions. See the discussion [here]{@link https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199}. + * + * @param {Vector3} axis - The normalized rotation axis. + * @param {number} angle - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationAxis( axis, angle ) { // Based on http://www.gamedev.net/reference/articles/article1199.asp @@ -6904,6 +12893,14 @@ class Matrix4 { } + /** + * Sets this matrix as a scale transformation. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @param {number} z - The amount to scale in the Z axis. + * @return {Matrix4} A reference to this matrix. + */ makeScale( x, y, z ) { this.set( @@ -6919,6 +12916,17 @@ class Matrix4 { } + /** + * Sets this matrix as a shear transformation. + * + * @param {number} xy - The amount to shear X by Y. + * @param {number} xz - The amount to shear X by Z. + * @param {number} yx - The amount to shear Y by X. + * @param {number} yz - The amount to shear Y by Z. + * @param {number} zx - The amount to shear Z by X. + * @param {number} zy - The amount to shear Z by Y. + * @return {Matrix4} A reference to this matrix. + */ makeShear( xy, xz, yx, yz, zx, zy ) { this.set( @@ -6934,6 +12942,15 @@ class Matrix4 { } + /** + * Sets this matrix to the transformation composed of the given position, + * rotation (Quaternion) and scale. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ compose( position, quaternion, scale ) { const te = this.elements; @@ -6970,6 +12987,19 @@ class Matrix4 { } + /** + * Decomposes this matrix into its position, rotation and scale components + * and provides the result in the given objects. + * + * Note: Not all matrices are decomposable in this way. For example, if an + * object has a non-uniformly scaled parent, then the object's world matrix + * may not be decomposable, and this method may not be appropriate. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ decompose( position, quaternion, scale ) { const te = this.elements; @@ -7015,9 +13045,24 @@ class Matrix4 { } - makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { + /** + * Creates a perspective projection matrix. This is used internally by + * {@link PerspectiveCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Matrix4} A reference to this matrix. + */ + makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { const te = this.elements; + const x = 2 * near / ( right - left ); const y = 2 * near / ( top - bottom ); @@ -7026,68 +13071,106 @@ class Matrix4 { let c, d; - if ( coordinateSystem === WebGLCoordinateSystem ) { + if ( reversedDepth ) { - c = - ( far + near ) / ( far - near ); - d = ( - 2 * far * near ) / ( far - near ); + c = near / ( far - near ); + d = ( far * near ) / ( far - near ); - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + } else { - c = - far / ( far - near ); - d = ( - far * near ) / ( far - near ); + if ( coordinateSystem === WebGLCoordinateSystem ) { - } else { + c = - ( far + near ) / ( far - near ); + d = ( -2 * far * near ) / ( far - near ); - throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + c = - far / ( far - near ); + d = ( - far * near ) / ( far - near ); + + } else { + + throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); + + } } te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = -1; te[ 15 ] = 0; return this; } - makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { + /** + * Creates a orthographic projection matrix. This is used internally by + * {@link OrthographicCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. + * @return {Matrix4} A reference to this matrix. + */ + makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { const te = this.elements; - const w = 1.0 / ( right - left ); - const h = 1.0 / ( top - bottom ); - const p = 1.0 / ( far - near ); - const x = ( right + left ) * w; - const y = ( top + bottom ) * h; + const x = 2 / ( right - left ); + const y = 2 / ( top - bottom ); - let z, zInv; - - if ( coordinateSystem === WebGLCoordinateSystem ) { + const a = - ( right + left ) / ( right - left ); + const b = - ( top + bottom ) / ( top - bottom ); - z = ( far + near ) * p; - zInv = - 2 * p; + let c, d; - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + if ( reversedDepth ) { - z = near * p; - zInv = - 1 * p; + c = 1 / ( far - near ); + d = far / ( far - near ); } else { - throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); + if ( coordinateSystem === WebGLCoordinateSystem ) { + + c = -2 / ( far - near ); + d = - ( far + near ) / ( far - near ); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + c = -1 / ( far - near ); + d = - near / ( far - near ); + + } else { + + throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); + + } } - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = zInv; te[ 14 ] = - z; + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = a; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = 0; te[ 13 ] = b; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; return this; } + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix4} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ equals( matrix ) { const te = this.elements; @@ -7103,6 +13186,13 @@ class Matrix4 { } + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array<number>} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix4} A reference to this matrix. + */ fromArray( array, offset = 0 ) { for ( let i = 0; i < 16; i ++ ) { @@ -7115,6 +13205,14 @@ class Matrix4 { } + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number>} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number>} The matrix elements in column-major order. + */ toArray( array = [], offset = 0 ) { const te = this.elements; @@ -7205,9 +13303,7 @@ function WebGLAnimation() { } -function WebGLAttributes( gl, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; +function WebGLAttributes( gl ) { const buffers = new WeakMap(); @@ -7230,19 +13326,15 @@ function WebGLAttributes( gl, capabilities ) { type = gl.FLOAT; - } else if ( array instanceof Uint16Array ) { + } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { - if ( attribute.isFloat16BufferAttribute ) { + type = gl.HALF_FLOAT; - if ( isWebGL2 ) { - - type = gl.HALF_FLOAT; - - } else { + } else if ( array instanceof Uint16Array ) { - throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); + if ( attribute.isFloat16BufferAttribute ) { - } + type = gl.HALF_FLOAT; } else { @@ -7293,57 +13385,71 @@ function WebGLAttributes( gl, capabilities ) { function updateBuffer( buffer, attribute, bufferType ) { const array = attribute.array; - const updateRange = attribute._updateRange; // @deprecated, r159 const updateRanges = attribute.updateRanges; gl.bindBuffer( bufferType, buffer ); - if ( updateRange.count === - 1 && updateRanges.length === 0 ) { + if ( updateRanges.length === 0 ) { // Not using update ranges gl.bufferSubData( bufferType, 0, array ); - } + } else { - if ( updateRanges.length !== 0 ) { + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.bufferSubData`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. - for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + updateRanges.sort( ( a, b ) => a.start - b.start ); + + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; + + for ( let i = 1; i < updateRanges.length; i ++ ) { + const previousRange = updateRanges[ mergeIndex ]; const range = updateRanges[ i ]; - if ( isWebGL2 ) { - gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, - array, range.start, range.count ); + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( range.start <= previousRange.start + previousRange.count + 1 ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); } else { - gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, - array.subarray( range.start, range.start + range.count ) ); + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; } } - attribute.clearUpdateRanges(); - - } + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - // @deprecated, r159 - if ( updateRange.count !== - 1 ) { - - if ( isWebGL2 ) { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array, updateRange.offset, updateRange.count ); + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - } else { + const range = updateRanges[ i ]; - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); } - updateRange.count = - 1; // reset range + attribute.clearUpdateRanges(); } @@ -7379,6 +13485,8 @@ function WebGLAttributes( gl, capabilities ) { function update( attribute, bufferType ) { + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + if ( attribute.isGLBufferAttribute ) { const cached = buffers.get( attribute ); @@ -7398,8 +13506,6 @@ function WebGLAttributes( gl, capabilities ) { } - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - const data = buffers.get( attribute ); if ( data === undefined ) { @@ -7453,7 +13559,7 @@ function _generateTables() { // very small number (0, -0) - if ( e < - 27 ) { + if ( e < -27 ) { baseTable[ i ] = 0x0000; baseTable[ i | 0x100 ] = 0x8000; @@ -7462,7 +13568,7 @@ function _generateTables() { // small number (denorm) - } else if ( e < - 14 ) { + } else if ( e < -14 ) { baseTable[ i ] = 0x0400 >> ( - e - 14 ); baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; @@ -7519,7 +13625,7 @@ function _generateTables() { } - m &= ~ 0x00800000; // clear leading 1 bit + m &= -8388609; // clear leading 1 bit e += 0x38800000; // adjust bias mantissaTable[ i ] = m | e; @@ -7571,13 +13677,18 @@ function _generateTables() { } -// float32 to float16 - +/** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ function toHalfFloat( val ) { if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); - val = clamp( val, - 65504, 65504 ); + val = clamp( val, -65504, 65504 ); _tables.floatView[ 0 ] = val; const f = _tables.uint32View[ 0 ]; @@ -7586,8 +13697,13 @@ function toHalfFloat( val ) { } -// float16 to float32 - +/** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ function fromHalfFloat( val ) { const m = val >> 10; @@ -7596,10 +13712,40 @@ function fromHalfFloat( val ) { } -const DataUtils = { - toHalfFloat: toHalfFloat, - fromHalfFloat: fromHalfFloat, -}; +/** + * A class containing utility functions for data. + * + * @hideconstructor + */ +class DataUtils { + + /** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ + static toHalfFloat( val ) { + + return toHalfFloat( val ); + + } + + /** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ + static fromHalfFloat( val ) { + + return fromHalfFloat( val ); + + } + +} var DataUtils$1 = /*#__PURE__*/Object.freeze({ __proto__: null, @@ -7611,8 +13757,25 @@ var DataUtils$1 = /*#__PURE__*/Object.freeze({ const _vector$5 = /*@__PURE__*/ new Vector3(); const _vector2 = /*@__PURE__*/ new Vector2(); +let _id$2 = 0; + +/** + * This class stores data for an attribute (such as vertex positions, face + * indices, normals, colors, UVs, and any custom attributes ) associated with + * a geometry, which allows for more efficient passing of data to the GPU. + * + * When working with vector-like data, the `fromBufferAttribute( attribute, index )` + * helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}. + */ class BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized = false ) { if ( Array.isArray( array ) ) { @@ -7621,39 +13784,133 @@ class BufferAttribute { } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBufferAttribute = true; + /** + * The ID of the buffer attribute. + * + * @name BufferAttribute#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$2 ++ } ); + + /** + * The name of the buffer attribute. + * + * @type {string} + */ this.name = ''; + /** + * The array holding the attribute data. It should have `itemSize * numVertices` + * elements, where `numVertices` is the number of vertices in the associated geometry. + * + * @type {TypedArray} + */ this.array = array; + + /** + * The number of values of the array that should be associated with a particular vertex. + * For instance, if this attribute is storing a 3-component vector (such as a position, + * normal, or color), then the value should be `3`. + * + * @type {number} + */ this.itemSize = itemSize; + + /** + * Represents the number of items this buffer attribute stores. It is internally computed + * by dividing the `array` length by the `itemSize`. + * + * @type {number} + * @readonly + */ this.count = array !== undefined ? array.length / itemSize : 0; + + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`, + * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ this.normalized = normalized; + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ this.usage = StaticDrawUsage; - this._updateRange = { offset: 0, count: - 1 }; + + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array<Object>} + */ this.updateRanges = []; + + /** + * Configures the bound GPU type for use in shaders. + * + * Note: this only has an effect for integer arrays and is not configurable for float arrays. + * For lower precision float types, use `Float16BufferAttribute`. + * + * @type {(FloatType|IntType)} + * @default FloatType + */ this.gpuType = FloatType; + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ this.version = 0; } + /** + * A callback function that is executed after the renderer has transferred the attribute + * array data to the GPU. + */ onUploadCallback() {} + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ set needsUpdate( value ) { if ( value === true ) this.version ++; } - get updateRange() { - - warnOnce( 'THREE.BufferAttribute: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead.' ); // @deprecated, r159 - return this._updateRange; - - } - + /** + * Sets the usage of this buffer attribute. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {BufferAttribute} A reference to this buffer attribute. + */ setUsage( value ) { this.usage = value; @@ -7662,18 +13919,33 @@ class BufferAttribute { } + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ addUpdateRange( start, count ) { this.updateRanges.push( { start, count } ); } + /** + * Clears the update ranges. + */ clearUpdateRanges() { this.updateRanges.length = 0; } + /** + * Copies the values of the given buffer attribute to this instance. + * + * @param {BufferAttribute} source - The buffer attribute to copy. + * @return {BufferAttribute} A reference to this instance. + */ copy( source ) { this.name = source.name; @@ -7689,6 +13961,16 @@ class BufferAttribute { } + /** + * Copies a vector from the given buffer attribute to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this buffer attribute. + * @param {BufferAttribute} attribute - The buffer attribute to copy from. + * @param {number} index2 - The source index into the given buffer attribute. + * @return {BufferAttribute} A reference to this instance. + */ copyAt( index1, attribute, index2 ) { index1 *= this.itemSize; @@ -7704,6 +13986,12 @@ class BufferAttribute { } + /** + * Copies the given array data into this buffer attribute. + * + * @param {(TypedArray|Array)} array - The array to copy. + * @return {BufferAttribute} A reference to this instance. + */ copyArray( array ) { this.array.set( array ); @@ -7712,6 +14000,13 @@ class BufferAttribute { } + /** + * Applies the given 3x3 matrix to the given attribute. Works with + * item size `2` and `3`. + * + * @param {Matrix3} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ applyMatrix3( m ) { if ( this.itemSize === 2 ) { @@ -7742,6 +14037,13 @@ class BufferAttribute { } + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ applyMatrix4( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { @@ -7758,6 +14060,13 @@ class BufferAttribute { } + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ applyNormalMatrix( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { @@ -7774,6 +14083,13 @@ class BufferAttribute { } + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ transformDirection( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { @@ -7790,6 +14106,13 @@ class BufferAttribute { } + /** + * Sets the given array data in the buffer attribute. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this buffer attribute's array. + * @return {BufferAttribute} A reference to this instance. + */ set( value, offset = 0 ) { // Matching BufferAttribute constructor, do not normalize the array. @@ -7799,6 +14122,13 @@ class BufferAttribute { } + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ getComponent( index, component ) { let value = this.array[ index * this.itemSize + component ]; @@ -7809,6 +14139,14 @@ class BufferAttribute { } + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ setComponent( index, component, value ) { if ( this.normalized ) value = normalize( value, this.array ); @@ -7819,6 +14157,12 @@ class BufferAttribute { } + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ getX( index ) { let x = this.array[ index * this.itemSize ]; @@ -7829,6 +14173,13 @@ class BufferAttribute { } + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ setX( index, x ) { if ( this.normalized ) x = normalize( x, this.array ); @@ -7839,6 +14190,12 @@ class BufferAttribute { } + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ getY( index ) { let y = this.array[ index * this.itemSize + 1 ]; @@ -7849,6 +14206,13 @@ class BufferAttribute { } + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ setY( index, y ) { if ( this.normalized ) y = normalize( y, this.array ); @@ -7859,6 +14223,12 @@ class BufferAttribute { } + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ getZ( index ) { let z = this.array[ index * this.itemSize + 2 ]; @@ -7869,6 +14239,13 @@ class BufferAttribute { } + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ setZ( index, z ) { if ( this.normalized ) z = normalize( z, this.array ); @@ -7879,6 +14256,12 @@ class BufferAttribute { } + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ getW( index ) { let w = this.array[ index * this.itemSize + 3 ]; @@ -7889,6 +14272,13 @@ class BufferAttribute { } + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ setW( index, w ) { if ( this.normalized ) w = normalize( w, this.array ); @@ -7899,6 +14289,14 @@ class BufferAttribute { } + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {BufferAttribute} A reference to this instance. + */ setXY( index, x, y ) { index *= this.itemSize; @@ -7917,6 +14315,15 @@ class BufferAttribute { } + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {BufferAttribute} A reference to this instance. + */ setXYZ( index, x, y, z ) { index *= this.itemSize; @@ -7937,6 +14344,16 @@ class BufferAttribute { } + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {BufferAttribute} A reference to this instance. + */ setXYZW( index, x, y, z, w ) { index *= this.itemSize; @@ -7959,6 +14376,14 @@ class BufferAttribute { } + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the attribute array data to the GPU. Can be used to perform clean-up operations after + * the upload when attribute data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {BufferAttribute} A reference to this instance. + */ onUpload( callback ) { this.onUploadCallback = callback; @@ -7967,12 +14392,22 @@ class BufferAttribute { } + /** + * Returns a new buffer attribute with copied values from this instance. + * + * @return {BufferAttribute} A clone of this instance. + */ clone() { return new this.constructor( this.array, this.itemSize ).copy( this ); } + /** + * Serializes the buffer attribute into JSON. + * + * @return {Object} A JSON object representing the serialized buffer attribute. + */ toJSON() { const data = { @@ -7991,10 +14426,21 @@ class BufferAttribute { } -// - +/** + * Convenient class that can be used when creating a `Int8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Int8BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Int8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Int8Array( array ), itemSize, normalized ); @@ -8003,8 +14449,21 @@ class Int8BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `UInt8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Uint8BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Uint8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Uint8Array( array ), itemSize, normalized ); @@ -8013,8 +14472,21 @@ class Uint8BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `UInt8Clamped` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Uint8ClampedBufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Uint8ClampedArray)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Uint8ClampedArray( array ), itemSize, normalized ); @@ -8023,8 +14495,21 @@ class Uint8ClampedBufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `Int16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Int16BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Int16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Int16Array( array ), itemSize, normalized ); @@ -8033,8 +14518,21 @@ class Int16BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `UInt16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Uint16BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Uint16Array( array ), itemSize, normalized ); @@ -8043,8 +14541,21 @@ class Uint16BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `Int32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Int32BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Int32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Int32Array( array ), itemSize, normalized ); @@ -8053,8 +14564,21 @@ class Int32BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `UInt32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Uint32BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Uint32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Uint32Array( array ), itemSize, normalized ); @@ -8063,8 +14587,24 @@ class Uint32BufferAttribute extends BufferAttribute { } +/** + * Convenient class that can be used when creating a `Float16` buffer attribute with + * a plain `Array` instance. + * + * This class automatically converts to and from FP16 via `Uint16Array` since `Float16Array` + * browser support is still problematic. + * + * @augments BufferAttribute + */ class Float16BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Uint16Array( array ), itemSize, normalized ); @@ -8215,9 +14755,21 @@ class Float16BufferAttribute extends BufferAttribute { } - +/** + * Convenient class that can be used when creating a `Float32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ class Float32BufferAttribute extends BufferAttribute { + /** + * Constructs a new buffer attribute. + * + * @param {(Array<number>|Float32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( array, itemSize, normalized ) { super( new Float32Array( array ), itemSize, normalized ); @@ -8229,10 +14781,41 @@ class Float32BufferAttribute extends BufferAttribute { const _matrix$2 = /*@__PURE__*/ new Matrix4(); const _quaternion$1 = /*@__PURE__*/ new Quaternion(); +/** + * A class representing Euler angles. + * + * Euler angles describe a rotational transformation by rotating an object on + * its various axes in specified amounts per axis, and a specified axis + * order. + * + * Iterating through an instance will yield its components (x, y, z, + * order) in the corresponding order. + * + * ```js + * const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); + * const b = new THREE.Vector3( 1, 0, 1 ); + * b.applyEuler(a); + * ``` + */ class Euler { + /** + * Constructs a new euler instance. + * + * @param {number} [x=0] - The angle of the x axis in radians. + * @param {number} [y=0] - The angle of the y axis in radians. + * @param {number} [z=0] - The angle of the z axis in radians. + * @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied. + */ constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isEuler = true; this._x = x; @@ -8242,6 +14825,12 @@ class Euler { } + /** + * The angle of the x axis in radians. + * + * @type {number} + * @default 0 + */ get x() { return this._x; @@ -8255,6 +14844,12 @@ class Euler { } + /** + * The angle of the y axis in radians. + * + * @type {number} + * @default 0 + */ get y() { return this._y; @@ -8268,6 +14863,12 @@ class Euler { } + /** + * The angle of the z axis in radians. + * + * @type {number} + * @default 0 + */ get z() { return this._z; @@ -8281,6 +14882,12 @@ class Euler { } + /** + * A string representing the order that the rotations are applied. + * + * @type {string} + * @default 'XYZ' + */ get order() { return this._order; @@ -8294,6 +14901,15 @@ class Euler { } + /** + * Sets the Euler components. + * + * @param {number} x - The angle of the x axis in radians. + * @param {number} y - The angle of the y axis in radians. + * @param {number} z - The angle of the z axis in radians. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ set( x, y, z, order = this._order ) { this._x = x; @@ -8307,12 +14923,23 @@ class Euler { } + /** + * Returns a new Euler instance with copied values from this instance. + * + * @return {Euler} A clone of this instance. + */ clone() { return new this.constructor( this._x, this._y, this._z, this._order ); } + /** + * Copies the values of the given Euler instance to this instance. + * + * @param {Euler} euler - The Euler instance to copy. + * @return {Euler} A reference to this Euler instance. + */ copy( euler ) { this._x = euler._x; @@ -8326,10 +14953,16 @@ class Euler { } + /** + * Sets the angles of this Euler instance from a pure rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ setFromRotationMatrix( m, order = this._order, update = true ) { - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - const te = m.elements; const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; @@ -8339,7 +14972,7 @@ class Euler { case 'XYZ': - this._y = Math.asin( clamp( m13, - 1, 1 ) ); + this._y = Math.asin( clamp( m13, -1, 1 ) ); if ( Math.abs( m13 ) < 0.9999999 ) { @@ -8357,7 +14990,7 @@ class Euler { case 'YXZ': - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); + this._x = Math.asin( - clamp( m23, -1, 1 ) ); if ( Math.abs( m23 ) < 0.9999999 ) { @@ -8375,7 +15008,7 @@ class Euler { case 'ZXY': - this._x = Math.asin( clamp( m32, - 1, 1 ) ); + this._x = Math.asin( clamp( m32, -1, 1 ) ); if ( Math.abs( m32 ) < 0.9999999 ) { @@ -8393,7 +15026,7 @@ class Euler { case 'ZYX': - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); + this._y = Math.asin( - clamp( m31, -1, 1 ) ); if ( Math.abs( m31 ) < 0.9999999 ) { @@ -8411,7 +15044,7 @@ class Euler { case 'YZX': - this._z = Math.asin( clamp( m21, - 1, 1 ) ); + this._z = Math.asin( clamp( m21, -1, 1 ) ); if ( Math.abs( m21 ) < 0.9999999 ) { @@ -8429,7 +15062,7 @@ class Euler { case 'XZY': - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); + this._z = Math.asin( - clamp( m12, -1, 1 ) ); if ( Math.abs( m12 ) < 0.9999999 ) { @@ -8459,6 +15092,14 @@ class Euler { } + /** + * Sets the angles of this Euler instance from a normalized quaternion. + * + * @param {Quaternion} q - A normalized Quaternion. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ setFromQuaternion( q, order, update ) { _matrix$2.makeRotationFromQuaternion( q ); @@ -8467,28 +15108,57 @@ class Euler { } + /** + * Sets the angles of this Euler instance from the given vector. + * + * @param {Vector3} v - The vector. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ setFromVector3( v, order = this._order ) { return this.set( v.x, v.y, v.z, order ); } + /** + * Resets the euler angle with a new order by creating a quaternion from this + * euler angle and then setting this euler angle with the quaternion and the + * new order. + * + * Warning: This discards revolution information. + * + * @param {string} [newOrder] - A string representing the new order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ reorder( newOrder ) { - // WARNING: this discards revolution information -bhouston - _quaternion$1.setFromEuler( this ); return this.setFromQuaternion( _quaternion$1, newOrder ); } + /** + * Returns `true` if this Euler instance is equal with the given one. + * + * @param {Euler} euler - The Euler instance to test for equality. + * @return {boolean} Whether this Euler instance is equal with the given one. + */ equals( euler ) { return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); } + /** + * Sets this Euler instance's components to values from the given array. The first three + * entries of the array are assign to the x,y and z components. An optional fourth entry + * defines the Euler order. + * + * @param {Array<number,number,number,?string>} array - An array holding the Euler component values. + * @return {Euler} A reference to this Euler instance. + */ fromArray( array ) { this._x = array[ 0 ]; @@ -8502,6 +15172,14 @@ class Euler { } + /** + * Writes the components of this Euler instance to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array<number,number,number,string>} [array=[]] - The target array holding the Euler components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array<number,number,number,string>} The Euler components. + */ toArray( array = [], offset = 0 ) { array[ offset ] = this._x; @@ -8534,61 +15212,129 @@ class Euler { } +/** + * The default Euler angle order. + * + * @static + * @type {string} + * @default 'XYZ' + */ Euler.DEFAULT_ORDER = 'XYZ'; +/** + * A layers object assigns an 3D object to 1 or more of 32 + * layers numbered `0` to `31` - internally the layers are stored as a + * bit mask], and by default all 3D objects are a member of layer `0`. + * + * This can be used to control visibility - an object must share a layer with + * a camera to be visible when that camera's view is + * rendered. + * + * All classes that inherit from {@link Object3D} have an `layers` property which + * is an instance of this class. + */ class Layers { + /** + * Constructs a new layers instance, with membership + * initially set to layer `0`. + */ constructor() { + /** + * A bit mask storing which of the 32 layers this layers object is currently + * a member of. + * + * @type {number} + */ this.mask = 1 | 0; } - set( channel ) { + /** + * Sets membership to the given layer, and remove membership all other layers. + * + * @param {number} layer - The layer to set. + */ + set( layer ) { - this.mask = ( 1 << channel | 0 ) >>> 0; + this.mask = ( 1 << layer | 0 ) >>> 0; } - enable( channel ) { + /** + * Adds membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + enable( layer ) { - this.mask |= 1 << channel | 0; + this.mask |= 1 << layer | 0; } + /** + * Adds membership to all layers. + */ enableAll() { this.mask = 0xffffffff | 0; } - toggle( channel ) { + /** + * Toggles the membership of the given layer. + * + * @param {number} layer - The layer to toggle. + */ + toggle( layer ) { - this.mask ^= 1 << channel | 0; + this.mask ^= 1 << layer | 0; } - disable( channel ) { + /** + * Removes membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + disable( layer ) { - this.mask &= ~ ( 1 << channel | 0 ); + this.mask &= ~ ( 1 << layer | 0 ); } + /** + * Removes the membership from all layers. + */ disableAll() { this.mask = 0; } + /** + * Returns `true` if this and the given layers object have at least one + * layer in common. + * + * @param {Layers} layers - The layers to test. + * @return {boolean } Whether this and the given layers object have at least one layer in common or not. + */ test( layers ) { return ( this.mask & layers.mask ) !== 0; } - isEnabled( channel ) { + /** + * Returns `true` if the given layer is enabled. + * + * @param {number} layer - The layer to test. + * @return {boolean } Whether the given layer is enabled or not. + */ + isEnabled( layer ) { - return ( this.mask & ( 1 << channel | 0 ) ) !== 0; + return ( this.mask & ( 1 << layer | 0 ) ) !== 0; } @@ -8609,30 +15355,118 @@ const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); +/** + * Fires when the object has been added to its parent object. + * + * @event Object3D#added + * @type {Object} + */ const _addedEvent = { type: 'added' }; + +/** + * Fires when the object has been removed from its parent object. + * + * @event Object3D#removed + * @type {Object} + */ const _removedEvent = { type: 'removed' }; +/** + * Fires when a new child object has been added. + * + * @event Object3D#childadded + * @type {Object} + */ const _childaddedEvent = { type: 'childadded', child: null }; + +/** + * Fires when a child object has been removed. + * + * @event Object3D#childremoved + * @type {Object} + */ const _childremovedEvent = { type: 'childremoved', child: null }; +/** + * This is the base class for most objects in three.js and provides a set of + * properties and methods for manipulating objects in 3D space. + * + * @augments EventDispatcher + */ class Object3D extends EventDispatcher { + /** + * Constructs a new 3D object. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isObject3D = true; + /** + * The ID of the 3D object. + * + * @name Object3D#id + * @type {number} + * @readonly + */ Object.defineProperty( this, 'id', { value: _object3DId ++ } ); + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); + /** + * The name of the 3D object. + * + * @type {string} + */ this.name = ''; + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ this.type = 'Object3D'; + /** + * A reference to the parent object. + * + * @type {?Object3D} + * @default null + */ this.parent = null; + + /** + * An array holding the child 3D objects of this instance. + * + * @type {Array<Object3D>} + */ this.children = []; + /** + * Defines the `up` direction of the 3D object which influences + * the orientation via methods like {@link Object3D#lookAt}. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. + * + * @type {Vector3} + */ this.up = Object3D.DEFAULT_UP.clone(); const position = new Vector3(); @@ -8656,65 +15490,268 @@ class Object3D extends EventDispatcher { quaternion._onChange( onQuaternionChange ); Object.defineProperties( this, { + /** + * Represents the object's local position. + * + * @name Object3D#position + * @type {Vector3} + * @default (0,0,0) + */ position: { configurable: true, enumerable: true, value: position }, + /** + * Represents the object's local rotation as Euler angles, in radians. + * + * @name Object3D#rotation + * @type {Euler} + * @default (0,0,0) + */ rotation: { configurable: true, enumerable: true, value: rotation }, + /** + * Represents the object's local rotation as Quaternions. + * + * @name Object3D#quaternion + * @type {Quaternion} + */ quaternion: { configurable: true, enumerable: true, value: quaternion }, + /** + * Represents the object's local scale. + * + * @name Object3D#scale + * @type {Vector3} + * @default (1,1,1) + */ scale: { configurable: true, enumerable: true, value: scale }, + /** + * Represents the object's model-view matrix. + * + * @name Object3D#modelViewMatrix + * @type {Matrix4} + */ modelViewMatrix: { value: new Matrix4() }, + /** + * Represents the object's normal matrix. + * + * @name Object3D#normalMatrix + * @type {Matrix3} + */ normalMatrix: { value: new Matrix3() } } ); + /** + * Represents the object's transformation matrix in local space. + * + * @type {Matrix4} + */ this.matrix = new Matrix4(); + + /** + * Represents the object's transformation matrix in world space. + * If the 3D object has no parent, then it's identical to the local transformation matrix + * + * @type {Matrix4} + */ this.matrixWorld = new Matrix4(); + /** + * When set to `true`, the engine automatically computes the local matrix from position, + * rotation and scale every frame. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; + /** + * When set to `true`, the engine automatically computes the world matrix from the current local + * matrix and the object's transformation hierarchy. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer + + /** + * When set to `true`, it calculates the world matrix in that frame and resets this property + * to `false`. + * + * @type {boolean} + * @default false + */ this.matrixWorldNeedsUpdate = false; + /** + * The layer membership of the 3D object. The 3D object is only visible if it has + * at least one layer in common with the camera in use. This property can also be + * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. + * + * @type {Layers} + */ this.layers = new Layers(); + + /** + * When set to `true`, the 3D object gets rendered. + * + * @type {boolean} + * @default true + */ this.visible = true; + /** + * When set to `true`, the 3D object gets rendered into shadow maps. + * + * @type {boolean} + * @default false + */ this.castShadow = false; + + /** + * When set to `true`, the 3D object is affected by shadows in the scene. + * + * @type {boolean} + * @default false + */ this.receiveShadow = false; + /** + * When set to `true`, the 3D object is honored by view frustum culling. + * + * @type {boolean} + * @default true + */ this.frustumCulled = true; + + /** + * This value allows the default rendering order of scene graph objects to be + * overridden although opaque and transparent objects remain sorted independently. + * When this property is set for an instance of {@link Group},all descendants + * objects will be sorted and rendered together. Sorting is from lowest to highest + * render order. + * + * @type {number} + * @default 0 + */ this.renderOrder = 0; + /** + * An array holding the animation clips of the 3D object. + * + * @type {Array<AnimationClip>} + */ this.animations = []; + /** + * Custom depth material to be used when rendering to the depth map. Can only be used + * in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight}, + * if you are modifying vertex positions in the vertex shader you must specify a custom depth + * material for proper shadows. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDepthMaterial = undefined; + + /** + * Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDistanceMaterial = undefined; + + /** + * An object that can be used to store custom data about the 3D object. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ this.userData = {}; } + /** + * A callback that is executed immediately before a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + /** + * A callback that is executed immediately after a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + /** + * A callback that is executed immediately before a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} + /** + * A callback that is executed immediately after a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} + /** + * Applies the given transformation matrix to the object and updates the object's position, + * rotation and scale. + * + * @param {Matrix4} matrix - The transformation matrix. + */ applyMatrix4( matrix ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); @@ -8725,6 +15762,12 @@ class Object3D extends EventDispatcher { } + /** + * Applies a rotation represented by given the quaternion to the 3D object. + * + * @param {Quaternion} q - The quaternion. + * @return {Object3D} A reference to this instance. + */ applyQuaternion( q ) { this.quaternion.premultiply( q ); @@ -8733,6 +15776,12 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as an axis/angle couple to the 3D object. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + */ setRotationFromAxisAngle( axis, angle ) { // assumes axis is normalized @@ -8741,12 +15790,23 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as Euler angles to the 3D object. + * + * @param {Euler} euler - The Euler angles. + */ setRotationFromEuler( euler ) { this.quaternion.setFromEuler( euler, true ); } + /** + * Sets the given rotation represented as rotation matrix to the 3D object. + * + * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be + * a pure rotation matrix (i.e, unscaled). + */ setRotationFromMatrix( m ) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) @@ -8755,6 +15815,11 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as a Quaternion to the 3D object. + * + * @param {Quaternion} q - The Quaternion + */ setRotationFromQuaternion( q ) { // assumes q is normalized @@ -8763,6 +15828,13 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object along an axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateOnAxis( axis, angle ) { // rotate object on axis in object space @@ -8776,6 +15848,13 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object along an axis in world space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateOnWorldAxis( axis, angle ) { // rotate object on axis in world space @@ -8790,24 +15869,49 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object around its X axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateX( angle ) { return this.rotateOnAxis( _xAxis, angle ); } + /** + * Rotates the 3D object around its Y axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateY( angle ) { return this.rotateOnAxis( _yAxis, angle ); } + /** + * Rotates the 3D object around its Z axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateZ( angle ) { return this.rotateOnAxis( _zAxis, angle ); } + /** + * Translate the 3D object by a distance along the given axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateOnAxis( axis, distance ) { // translate object by distance along axis in object space @@ -8821,24 +15925,48 @@ class Object3D extends EventDispatcher { } + /** + * Translate the 3D object by a distance along its X-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateX( distance ) { return this.translateOnAxis( _xAxis, distance ); } + /** + * Translate the 3D object by a distance along its Y-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateY( distance ) { return this.translateOnAxis( _yAxis, distance ); } + /** + * Translate the 3D object by a distance along its Z-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateZ( distance ) { return this.translateOnAxis( _zAxis, distance ); } + /** + * Converts the given vector from this 3D object's local space to world space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ localToWorld( vector ) { this.updateWorldMatrix( true, false ); @@ -8847,6 +15975,12 @@ class Object3D extends EventDispatcher { } + /** + * Converts the given vector from this 3D object's word space to local space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ worldToLocal( vector ) { this.updateWorldMatrix( true, false ); @@ -8855,6 +15989,15 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the object to face a point in world space. + * + * This method does not support objects having non-uniformly-scaled parent(s). + * + * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space + * @param {number} [y] - The y coordinate in world space. + * @param {number} [z] - The z coordinate in world space. + */ lookAt( x, y, z ) { // This method does not support objects having non-uniformly-scaled parent(s) @@ -8897,6 +16040,16 @@ class Object3D extends EventDispatcher { } + /** + * Adds the given 3D object as a child to this 3D object. An arbitrary number of + * objects may be added. Any current parent on an object passed in here will be + * removed, since an object can have at most one parent. + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to add. + * @return {Object3D} A reference to this instance. + */ add( object ) { if ( arguments.length > 1 ) { @@ -8920,12 +16073,7 @@ class Object3D extends EventDispatcher { if ( object && object.isObject3D ) { - if ( object.parent !== null ) { - - object.parent.remove( object ); - - } - + object.removeFromParent(); object.parent = this; this.children.push( object ); @@ -8945,6 +16093,15 @@ class Object3D extends EventDispatcher { } + /** + * Removes the given 3D object as child from this 3D object. + * An arbitrary number of objects may be removed. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @param {Object3D} object - The 3D object to remove. + * @return {Object3D} A reference to this instance. + */ remove( object ) { if ( arguments.length > 1 ) { @@ -8961,7 +16118,7 @@ class Object3D extends EventDispatcher { const index = this.children.indexOf( object ); - if ( index !== - 1 ) { + if ( index !== -1 ) { object.parent = null; this.children.splice( index, 1 ); @@ -8978,6 +16135,13 @@ class Object3D extends EventDispatcher { } + /** + * Removes this 3D object from its current parent. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ removeFromParent() { const parent = this.parent; @@ -8992,12 +16156,28 @@ class Object3D extends EventDispatcher { } + /** + * Removes all child objects. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ clear() { return this.remove( ... this.children ); } + /** + * Adds the given 3D object as a child of this 3D object, while maintaining the object's world + * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to attach. + * @return {Object3D} A reference to this instance. + */ attach( object ) { // adds object as a child of this, while maintaining the object's world transform @@ -9018,26 +16198,56 @@ class Object3D extends EventDispatcher { object.applyMatrix4( _m1$3 ); - this.add( object ); + object.removeFromParent(); + object.parent = this; + this.children.push( object ); object.updateWorldMatrix( false, true ); + object.dispatchEvent( _addedEvent ); + + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; + return this; } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching ID. + * + * @param {number} id - The id. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectById( id ) { return this.getObjectByProperty( 'id', id ); } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching name. + * + * @param {string} name - The name. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectByName( name ) { return this.getObjectByProperty( 'name', name ); } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectByProperty( name, value ) { if ( this[ name ] === value ) return this; @@ -9059,6 +16269,15 @@ class Object3D extends EventDispatcher { } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns all 3D objects with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @param {Array<Object3D>} result - The method stores the result in this array. + * @return {Array<Object3D>} The found 3D objects. + */ getObjectsByProperty( name, value, result = [] ) { if ( this[ name ] === value ) result.push( this ); @@ -9075,6 +16294,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the position of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's position in world space. + */ getWorldPosition( target ) { this.updateWorldMatrix( true, false ); @@ -9083,6 +16308,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a Quaternion representing the position of the 3D object in world space. + * + * @param {Quaternion} target - The target Quaternion the result is stored to. + * @return {Quaternion} The 3D object's rotation in world space. + */ getWorldQuaternion( target ) { this.updateWorldMatrix( true, false ); @@ -9093,6 +16324,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the scale of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's scale in world space. + */ getWorldScale( target ) { this.updateWorldMatrix( true, false ); @@ -9103,6 +16340,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ getWorldDirection( target ) { this.updateWorldMatrix( true, false ); @@ -9113,8 +16356,24 @@ class Object3D extends EventDispatcher { } + /** + * Abstract method to get intersections between a casted ray and this + * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} + * implement this method in order to use raycasting. + * + * @abstract + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - An array holding the result of the method. + */ raycast( /* raycaster, intersects */ ) {} + /** + * Executes the callback on this 3D object and all descendants. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverse( callback ) { callback( this ); @@ -9129,6 +16388,14 @@ class Object3D extends EventDispatcher { } + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. + * Descendants of invisible 3D objects are not traversed. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverseVisible( callback ) { if ( this.visible === false ) return; @@ -9145,6 +16412,13 @@ class Object3D extends EventDispatcher { } + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverseAncestors( callback ) { const parent = this.parent; @@ -9159,6 +16433,10 @@ class Object3D extends EventDispatcher { } + /** + * Updates the transformation matrix in local space by computing it from the current + * position, rotation and scale values. + */ updateMatrix() { this.matrix.compose( this.position, this.quaternion, this.scale ); @@ -9167,19 +16445,34 @@ class Object3D extends EventDispatcher { } + /** + * Updates the transformation matrix in world space of this 3D objects and its descendants. + * + * To ensure correct results, this method also recomputes the 3D object's transformation matrix in + * local space. The computation of the local and world matrix can be controlled with the + * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both + * `true` by default. Set these flags to `false` if you need more control over the update matrix process. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ updateMatrixWorld( force ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.matrixWorldNeedsUpdate || force ) { - if ( this.parent === null ) { + if ( this.matrixWorldAutoUpdate === true ) { - this.matrixWorld.copy( this.matrix ); + if ( this.parent === null ) { - } else { + this.matrixWorld.copy( this.matrix ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } } @@ -9189,7 +16482,7 @@ class Object3D extends EventDispatcher { } - // update children + // make sure descendants are updated if required const children = this.children; @@ -9197,21 +16490,24 @@ class Object3D extends EventDispatcher { const child = children[ i ]; - if ( child.matrixWorldAutoUpdate === true || force === true ) { - - child.updateMatrixWorld( force ); - - } + child.updateMatrixWorld( force ); } } + /** + * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the + * update of ancestor and descendant nodes. + * + * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. + * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. + */ updateWorldMatrix( updateParents, updateChildren ) { const parent = this.parent; - if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { + if ( updateParents === true && parent !== null ) { parent.updateWorldMatrix( true, false ); @@ -9219,17 +16515,21 @@ class Object3D extends EventDispatcher { if ( this.matrixAutoUpdate ) this.updateMatrix(); - if ( this.parent === null ) { + if ( this.matrixWorldAutoUpdate === true ) { + + if ( this.parent === null ) { - this.matrixWorld.copy( this.matrix ); + this.matrixWorld.copy( this.matrix ); - } else { + } else { - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } } - // update children + // make sure descendants are updated if ( updateChildren === true ) { @@ -9239,11 +16539,7 @@ class Object3D extends EventDispatcher { const child = children[ i ]; - if ( child.matrixWorldAutoUpdate === true ) { - - child.updateWorldMatrix( false, true ); - - } + child.updateWorldMatrix( false, true ); } @@ -9251,6 +16547,13 @@ class Object3D extends EventDispatcher { } + /** + * Serializes the 3D object into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized 3D object. + * @see {@link ObjectLoader#parse} + */ toJSON( meta ) { // meta is a string when called from JSON.stringify @@ -9276,7 +16579,7 @@ class Object3D extends EventDispatcher { }; output.metadata = { - version: 4.6, + version: 4.7, type: 'Object', generator: 'Object3D.toJSON' }; @@ -9324,42 +16627,45 @@ class Object3D extends EventDispatcher { object.drawRanges = this._drawRanges; object.reservedRanges = this._reservedRanges; - object.visibility = this._visibility; - object.active = this._active; - object.bounds = this._bounds.map( bound => ( { - boxInitialized: bound.boxInitialized, - boxMin: bound.box.min.toArray(), - boxMax: bound.box.max.toArray(), - - sphereInitialized: bound.sphereInitialized, - sphereRadius: bound.sphere.radius, - sphereCenter: bound.sphere.center.toArray() + object.geometryInfo = this._geometryInfo.map( info => ( { + ...info, + boundingBox: info.boundingBox ? info.boundingBox.toJSON() : undefined, + boundingSphere: info.boundingSphere ? info.boundingSphere.toJSON() : undefined } ) ); + object.instanceInfo = this._instanceInfo.map( info => ( { ...info } ) ); + + object.availableInstanceIds = this._availableInstanceIds.slice(); + object.availableGeometryIds = this._availableGeometryIds.slice(); + + object.nextIndexStart = this._nextIndexStart; + object.nextVertexStart = this._nextVertexStart; + object.geometryCount = this._geometryCount; - object.maxGeometryCount = this._maxGeometryCount; + object.maxInstanceCount = this._maxInstanceCount; object.maxVertexCount = this._maxVertexCount; object.maxIndexCount = this._maxIndexCount; object.geometryInitialized = this._geometryInitialized; - object.geometryCount = this._geometryCount; object.matricesTexture = this._matricesTexture.toJSON( meta ); + object.indirectTexture = this._indirectTexture.toJSON( meta ); + + if ( this._colorsTexture !== null ) { + + object.colorsTexture = this._colorsTexture.toJSON( meta ); + + } + if ( this.boundingSphere !== null ) { - object.boundingSphere = { - center: object.boundingSphere.center.toArray(), - radius: object.boundingSphere.radius - }; + object.boundingSphere = this.boundingSphere.toJSON(); } if ( this.boundingBox !== null ) { - object.boundingBox = { - min: object.boundingBox.min.toArray(), - max: object.boundingBox.max.toArray() - }; + object.boundingBox = this.boundingBox.toJSON(); } @@ -9544,12 +16850,25 @@ class Object3D extends EventDispatcher { } + /** + * Returns a new 3D object with copied values from this instance. + * + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. + * @return {Object3D} A clone of this instance. + */ clone( recursive ) { return new this.constructor().copy( this, recursive ); } + /** + * Copies the values of the given 3D object to this instance. + * + * @param {Object3D} source - The 3D object to copy. + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. + * @return {Object3D} A reference to this instance. + */ copy( source, recursive = true ) { this.name = source.name; @@ -9599,8 +16918,34 @@ class Object3D extends EventDispatcher { } +/** + * The default up direction for objects, also used as the default + * position for {@link DirectionalLight} and {@link HemisphereLight}. + * + * @static + * @type {Vector3} + * @default (0,1,0) + */ Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); + +/** + * The default setting for {@link Object3D#matrixAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; + +/** + * The default setting for {@link Object3D#matrixWorldAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; let _id$1 = 0; @@ -9612,44 +16957,191 @@ const _box$1 = /*@__PURE__*/ new Box3(); const _boxMorphTargets = /*@__PURE__*/ new Box3(); const _vector$4 = /*@__PURE__*/ new Vector3(); +/** + * A representation of mesh, line, or point geometry. Includes vertex + * positions, face indices, normals, colors, UVs, and custom attributes + * within buffers, reducing the cost of passing all this data to the GPU. + * + * ```js + * const geometry = new THREE.BufferGeometry(); + * // create a simple square shape. We duplicate the top left and bottom right + * // vertices because each vertex needs to appear once per triangle. + * const vertices = new Float32Array( [ + * -1.0, -1.0, 1.0, // v0 + * 1.0, -1.0, 1.0, // v1 + * 1.0, 1.0, 1.0, // v2 + * + * 1.0, 1.0, 1.0, // v3 + * -1.0, 1.0, 1.0, // v4 + * -1.0, -1.0, 1.0 // v5 + * ] ); + * // itemSize = 3 because there are 3 values (components) per vertex + * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * ``` + * + * @augments EventDispatcher + */ class BufferGeometry extends EventDispatcher { + /** + * Constructs a new geometry. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBufferGeometry = true; + /** + * The ID of the geometry. + * + * @name BufferGeometry#id + * @type {number} + * @readonly + */ Object.defineProperty( this, 'id', { value: _id$1 ++ } ); + /** + * The UUID of the geometry. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); + /** + * The name of the geometry. + * + * @type {string} + */ this.name = ''; this.type = 'BufferGeometry'; + /** + * Allows for vertices to be re-used across multiple triangles; this is + * called using "indexed triangles". Each triangle is associated with the + * indices of three vertices. This attribute therefore stores the index of + * each vertex for each triangular face. If this attribute is not set, the + * renderer assumes that each three contiguous positions represent a single triangle. + * + * @type {?BufferAttribute} + * @default null + */ this.index = null; + + /** + * A (storage) buffer attribute which was generated with a compute shader and + * now defines indirect draw calls. + * + * Can only be used with {@link WebGPURenderer} and a WebGPU backend. + * + * @type {?BufferAttribute} + * @default null + */ + this.indirect = null; + + /** + * This dictionary has as id the name of the attribute to be set and as value + * the buffer attribute to set it to. Rather than accessing this property directly, + * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. + * + * @type {Object<string,(BufferAttribute|InterleavedBufferAttribute)>} + */ this.attributes = {}; + /** + * This dictionary holds the morph targets of the geometry. + * + * Note: Once the geometry has been rendered, the morph attribute data cannot + * be changed. You will have to call `dispose()?, and create a new geometry instance. + * + * @type {Object} + */ this.morphAttributes = {}; + + /** + * Used to control the morph target behavior; when set to `true`, the morph + * target data is treated as relative offsets, rather than as absolute + * positions/normals. + * + * @type {boolean} + * @default false + */ this.morphTargetsRelative = false; + /** + * Split the geometry into groups, each of which will be rendered in a + * separate draw call. This allows an array of materials to be used with the geometry. + * + * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. + * + * Every vertex and index must belong to exactly one group — groups must not share vertices or + * indices, and must not leave vertices or indices unused. + * + * @type {Array<Object>} + */ this.groups = []; + /** + * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. + * + * @type {?Box3} + * @default null + */ this.boundingBox = null; + + /** + * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. + * + * @type {?Sphere} + * @default null + */ this.boundingSphere = null; + /** + * Determines the part of the geometry to render. This should not be set directly, + * instead use `setDrawRange()`. + * + * @type {{start:number,count:number}} + */ this.drawRange = { start: 0, count: Infinity }; + /** + * An object that can be used to store custom data about the geometry. + * It should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ this.userData = {}; } + /** + * Returns the index of this geometry. + * + * @return {?BufferAttribute} The index. Returns `null` if no index is defined. + */ getIndex() { return this.index; } + /** + * Sets the given index to this geometry. + * + * @param {Array<number>|BufferAttribute} index - The index to set. + * @return {BufferGeometry} A reference to this instance. + */ setIndex( index ) { if ( Array.isArray( index ) ) { @@ -9666,12 +17158,51 @@ class BufferGeometry extends EventDispatcher { } + /** + * Sets the given indirect attribute to this geometry. + * + * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. + * @return {BufferGeometry} A reference to this instance. + */ + setIndirect( indirect ) { + + this.indirect = indirect; + + return this; + + } + + /** + * Returns the indirect attribute of this geometry. + * + * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. + */ + getIndirect() { + + return this.indirect; + + } + + /** + * Returns the buffer attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. + * Returns `undefined` if not attribute has been found. + */ getAttribute( name ) { return this.attributes[ name ]; } + /** + * Sets the given attribute for the given name. + * + * @param {string} name - The attribute name. + * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. + * @return {BufferGeometry} A reference to this instance. + */ setAttribute( name, attribute ) { this.attributes[ name ] = attribute; @@ -9680,6 +17211,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Deletes the attribute for the given name. + * + * @param {string} name - The attribute name to delete. + * @return {BufferGeometry} A reference to this instance. + */ deleteAttribute( name ) { delete this.attributes[ name ]; @@ -9688,12 +17225,26 @@ class BufferGeometry extends EventDispatcher { } + /** + * Returns `true` if this geometry has an attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether this geometry has an attribute for the given name or not. + */ hasAttribute( name ) { return this.attributes[ name ] !== undefined; } + /** + * Adds a group to this geometry. + * + * @param {number} start - The first element in this draw call. That is the first + * vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - Specifies how many vertices (or indices) are part of this group. + * @param {number} [materialIndex=0] - The material array index to use. + */ addGroup( start, count, materialIndex = 0 ) { this.groups.push( { @@ -9706,12 +17257,22 @@ class BufferGeometry extends EventDispatcher { } + /** + * Clears all groups. + */ clearGroups() { this.groups = []; } + /** + * Sets the draw range for this geometry. + * + * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. + * For indexed BufferGeometry, `count` is the number of indices to render. + */ setDrawRange( start, count ) { this.drawRange.start = start; @@ -9719,6 +17280,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Applies the given 4x4 transformation matrix to the geometry. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {BufferGeometry} A reference to this instance. + */ applyMatrix4( matrix ) { const position = this.attributes.position; @@ -9769,6 +17336,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Applies the rotation represented by the Quaternion to the geometry. + * + * @param {Quaternion} q - The Quaternion to apply. + * @return {BufferGeometry} A reference to this instance. + */ applyQuaternion( q ) { _m1$2.makeRotationFromQuaternion( q ); @@ -9779,6 +17352,14 @@ class BufferGeometry extends EventDispatcher { } + /** + * Rotates the geometry about the X axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ rotateX( angle ) { // rotate geometry around world x-axis @@ -9791,6 +17372,14 @@ class BufferGeometry extends EventDispatcher { } + /** + * Rotates the geometry about the Y axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ rotateY( angle ) { // rotate geometry around world y-axis @@ -9803,6 +17392,14 @@ class BufferGeometry extends EventDispatcher { } + /** + * Rotates the geometry about the Z axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ rotateZ( angle ) { // rotate geometry around world z-axis @@ -9815,6 +17412,16 @@ class BufferGeometry extends EventDispatcher { } + /** + * Translates the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#position} for typical + * real-time mesh rotation. + * + * @param {number} x - The x offset. + * @param {number} y - The y offset. + * @param {number} z - The z offset. + * @return {BufferGeometry} A reference to this instance. + */ translate( x, y, z ) { // translate geometry @@ -9827,6 +17434,16 @@ class BufferGeometry extends EventDispatcher { } + /** + * Scales the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#scale} for typical + * real-time mesh rotation. + * + * @param {number} x - The x scale. + * @param {number} y - The y scale. + * @param {number} z - The z scale. + * @return {BufferGeometry} A reference to this instance. + */ scale( x, y, z ) { // scale geometry @@ -9839,6 +17456,14 @@ class BufferGeometry extends EventDispatcher { } + /** + * Rotates the geometry to face a point in 3D space. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#lookAt} for typical + * real-time mesh rotation. + * + * @param {Vector3} vector - The target point. + * @return {BufferGeometry} A reference to this instance. + */ lookAt( vector ) { _obj.lookAt( vector ); @@ -9851,6 +17476,11 @@ class BufferGeometry extends EventDispatcher { } + /** + * Center the geometry based on its bounding box. + * + * @return {BufferGeometry} A reference to this instance. + */ center() { this.computeBoundingBox(); @@ -9863,23 +17493,64 @@ class BufferGeometry extends EventDispatcher { } + /** + * Defines a geometry by creating a `position` attribute based on the given array of points. The array + * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is + * set to `0`. + * + * If the method is used with an existing `position` attribute, the vertex data are overwritten with the + * data from the array. The length of the array must match the vertex count. + * + * @param {Array<Vector2>|Array<Vector3>} points - The points. + * @return {BufferGeometry} A reference to this instance. + */ setFromPoints( points ) { - const position = []; + const positionAttribute = this.getAttribute( 'position' ); - for ( let i = 0, l = points.length; i < l; i ++ ) { + if ( positionAttribute === undefined ) { - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); + const position = []; - } + for ( let i = 0, l = points.length; i < l; i ++ ) { + + const point = points[ i ]; + position.push( point.x, point.y, point.z || 0 ); + + } + + this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + + } else { + + const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size + + for ( let i = 0; i < l; i ++ ) { + + const point = points[ i ]; + positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); + + } + + if ( points.length > positionAttribute.count ) { + + console.warn( 'THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); + + } + + positionAttribute.needsUpdate = true; - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + } return this; } + /** + * Computes the bounding box of the geometry, and updates the `boundingBox` member. + * The bounding box is not computed by the engine; it must be computed by your app. + * You may need to recompute the bounding box if the geometry vertices are modified. + */ computeBoundingBox() { if ( this.boundingBox === null ) { @@ -9950,6 +17621,11 @@ class BufferGeometry extends EventDispatcher { } + /** + * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if the geometry vertices are modified. + */ computeBoundingSphere() { if ( this.boundingSphere === null ) { @@ -10062,6 +17738,13 @@ class BufferGeometry extends EventDispatcher { } + /** + * Calculates and adds a tangent attribute to this geometry. + * + * The computation is only supported for indexed geometries and if position, normal, and uv attributes + * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by + * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. + */ computeTangents() { const index = this.index; @@ -10196,7 +17879,7 @@ class BufferGeometry extends EventDispatcher { tmp2.crossVectors( n2, t ); const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? - 1.0 : 1.0; + const w = ( test < 0.0 ) ? -1 : 1.0; tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); @@ -10221,6 +17904,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Computes vertex normals for the given vertex data. For indexed geometries, the method sets + * each vertex normal to be the average of the face normals of the faces that share that vertex. + * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal + * to be the same as the face normal. + */ computeVertexNormals() { const index = this.index; @@ -10313,6 +18002,10 @@ class BufferGeometry extends EventDispatcher { } + /** + * Ensures every normal vector in a geometry will have a magnitude of `1`. This will + * correct lighting on the geometry surfaces. + */ normalizeNormals() { const normals = this.attributes.normal; @@ -10329,6 +18022,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Return a new non-index version of this indexed geometry. If the geometry + * is already non-indexed, the method is a NOOP. + * + * @return {BufferGeometry} The non-indexed version of this indexed geometry. + */ toNonIndexed() { function convertBufferAttribute( attribute, indices ) { @@ -10431,11 +18130,16 @@ class BufferGeometry extends EventDispatcher { } + /** + * Serializes the geometry into JSON. + * + * @return {Object} A JSON object representing the serialized geometry. + */ toJSON() { const data = { metadata: { - version: 4.6, + version: 4.7, type: 'BufferGeometry', generator: 'BufferGeometry.toJSON' } @@ -10533,10 +18237,7 @@ class BufferGeometry extends EventDispatcher { if ( boundingSphere !== null ) { - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; + data.data.boundingSphere = boundingSphere.toJSON(); } @@ -10544,12 +18245,23 @@ class BufferGeometry extends EventDispatcher { } + /** + * Returns a new geometry with copied values from this instance. + * + * @return {BufferGeometry} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given geometry to this instance. + * + * @param {BufferGeometry} source - The geometry to copy. + * @return {BufferGeometry} A reference to this instance. + */ copy( source ) { // reset @@ -10575,7 +18287,7 @@ class BufferGeometry extends EventDispatcher { if ( index !== null ) { - this.setIndex( index.clone( data ) ); + this.setIndex( index.clone() ); } @@ -10655,6 +18367,12 @@ class BufferGeometry extends EventDispatcher { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires BufferGeometry#dispose + */ dispose() { this.dispatchEvent( { type: 'dispose' } ); @@ -10663,14 +18381,45 @@ class BufferGeometry extends EventDispatcher { } +/** + * A geometry class for a rectangular cuboid with a given width, height, and depth. + * On creation, the cuboid is centred on the origin, with each edge parallel to one + * of the axes. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const cube = new THREE.Mesh( geometry, material ); + * scene.add( cube ); + * ``` + * + * @augments BufferGeometry + */ class BoxGeometry extends BufferGeometry { + /** + * Constructs a new box geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [widthSegments=1] - Number of segmented rectangular faces along the width of the sides. + * @param {number} [heightSegments=1] - Number of segmented rectangular faces along the height of the sides. + * @param {number} [depthSegments=1] - Number of segmented rectangular faces along the depth of the sides. + */ constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { super(); this.type = 'BoxGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { width: width, height: height, @@ -10702,12 +18451,12 @@ class BoxGeometry extends BufferGeometry { // build each side of the box geometry - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx + buildPlane( 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0 ); // px + buildPlane( 'z', 'y', 'x', 1, -1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz + buildPlane( 'x', 'z', 'y', 1, -1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny + buildPlane( 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4 ); // pz + buildPlane( 'x', 'y', 'z', -1, -1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz // build geometry @@ -10757,7 +18506,7 @@ class BoxGeometry extends BufferGeometry { vector[ u ] = 0; vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; + vector[ w ] = depth > 0 ? 1 : -1; // now apply vector to normal buffer @@ -10830,6 +18579,13 @@ class BoxGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {BoxGeometry} A new instance. + */ static fromJSON( data ) { return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); @@ -10838,14 +18594,41 @@ class BoxGeometry extends BufferGeometry { } +/** + * A geometry class for representing a plane. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const plane = new THREE.Mesh( geometry, material ); + * scene.add( plane ); + * ``` + * + * @augments BufferGeometry + */ class PlaneGeometry extends BufferGeometry { + /** + * Constructs a new plane geometry. + * + * @param {number} [width=1] - The width along the X axis. + * @param {number} [height=1] - The height along the Y axis + * @param {number} [widthSegments=1] - The number of segments along the X axis. + * @param {number} [heightSegments=1] - The number of segments along the Y axis. + */ constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { super(); this.type = 'PlaneGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { width: width, height: height, @@ -10924,6 +18707,13 @@ class PlaneGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PlaneGeometry} A new instance. + */ static fromJSON( data ) { return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); @@ -10934,83 +18724,484 @@ class PlaneGeometry extends BufferGeometry { let _materialId = 0; +/** + * Abstract base class for materials. + * + * Materials define the appearance of renderable 3D objects. + * + * @abstract + * @augments EventDispatcher + */ class Material extends EventDispatcher { + /** + * Constructs a new material. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMaterial = true; + /** + * The ID of the material. + * + * @name Material#id + * @type {number} + * @readonly + */ Object.defineProperty( this, 'id', { value: _materialId ++ } ); + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); + /** + * The name of the material. + * + * @type {string} + */ this.name = ''; + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ this.type = 'Material'; + /** + * Defines the blending type of the material. + * + * It must be set to `CustomBlending` if custom blending properties like + * {@link Material#blendSrc}, {@link Material#blendDst} or {@link Material#blendEquation} + * should have any effect. + * + * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending)} + * @default NormalBlending + */ this.blending = NormalBlending; + + /** + * Defines which side of faces will be rendered - front, back or both. + * + * @type {(FrontSide|BackSide|DoubleSide)} + * @default FrontSide + */ this.side = FrontSide; + + /** + * If set to `true`, vertex colors should be used. + * + * The engine supports RGB and RGBA vertex colors depending on whether a three (RGB) or + * four (RGBA) component color buffer attribute is used. + * + * @type {boolean} + * @default false + */ this.vertexColors = false; + /** + * Defines how transparent the material is. + * A value of `0.0` indicates fully transparent, `1.0` is fully opaque. + * + * If the {@link Material#transparent} is not set to `true`, + * the material will remain fully opaque and this value will only affect its color. + * + * @type {number} + * @default 1 + */ this.opacity = 1; + + /** + * Defines whether this material is transparent. This has an effect on + * rendering as transparent objects need special treatment and are rendered + * after non-transparent objects. + * + * When set to true, the extent to which the material is transparent is + * controlled by {@link Material#opacity}. + * + * @type {boolean} + * @default false + */ this.transparent = false; + + /** + * Enables alpha hashed transparency, an alternative to {@link Material#transparent} or + * {@link Material#alphaTest}. The material will not be rendered if opacity is lower than + * a random threshold. Randomization introduces some grain or noise, but approximates alpha + * blending without the associated problems of sorting. Using TAA can reduce the resulting noise. + * + * @type {boolean} + * @default false + */ this.alphaHash = false; + /** + * Defines the blending source factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default SrcAlphaFactor + */ this.blendSrc = SrcAlphaFactor; + + /** + * Defines the blending destination factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default OneMinusSrcAlphaFactor + */ this.blendDst = OneMinusSrcAlphaFactor; + + /** + * Defines the blending equation. + * + * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default AddEquation + */ this.blendEquation = AddEquation; + + /** + * Defines the blending source alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ this.blendSrcAlpha = null; + + /** + * Defines the blending destination alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ this.blendDstAlpha = null; + + /** + * Defines the blending equation of the alpha channel. + * + * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default null + */ this.blendEquationAlpha = null; + + /** + * Represents the RGB values of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantColor` or `OneMinusConstantColor`. + * + * @type {Color} + * @default (0,0,0) + */ this.blendColor = new Color( 0, 0, 0 ); + + /** + * Represents the alpha value of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantAlpha` or `OneMinusConstantAlpha`. + * + * @type {number} + * @default 0 + */ this.blendAlpha = 0; + /** + * Defines the depth function. + * + * @type {(NeverDepth|AlwaysDepth|LessDepth|LessEqualDepth|EqualDepth|GreaterEqualDepth|GreaterDepth|NotEqualDepth)} + * @default LessEqualDepth + */ this.depthFunc = LessEqualDepth; + + /** + * Whether to have depth test enabled when rendering this material. + * When the depth test is disabled, the depth write will also be implicitly disabled. + * + * @type {boolean} + * @default true + */ this.depthTest = true; + + /** + * Whether rendering this material has any effect on the depth buffer. + * + * When drawing 2D overlays it can be useful to disable the depth writing in + * order to layer several things together without creating z-index artifacts. + * + * @type {boolean} + * @default true + */ this.depthWrite = true; + /** + * The bit mask to use when writing to the stencil buffer. + * + * @type {number} + * @default 0xff + */ this.stencilWriteMask = 0xff; + + /** + * The stencil comparison function to use. + * + * @type {NeverStencilFunc|LessStencilFunc|EqualStencilFunc|LessEqualStencilFunc|GreaterStencilFunc|NotEqualStencilFunc|GreaterEqualStencilFunc|AlwaysStencilFunc} + * @default AlwaysStencilFunc + */ this.stencilFunc = AlwaysStencilFunc; + + /** + * The value to use when performing stencil comparisons or stencil operations. + * + * @type {number} + * @default 0 + */ this.stencilRef = 0; + + /** + * The bit mask to use when comparing against the stencil buffer. + * + * @type {number} + * @default 0xff + */ this.stencilFuncMask = 0xff; + + /** + * Which stencil operation to perform when the comparison function returns `false`. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ this.stencilFail = KeepStencilOp; + + /** + * Which stencil operation to perform when the comparison function returns + * `true` but the depth test fails. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ this.stencilZFail = KeepStencilOp; + + /** + * Which stencil operation to perform when the comparison function returns + * `true` and the depth test passes. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ this.stencilZPass = KeepStencilOp; + + /** + * Whether stencil operations are performed against the stencil buffer. In + * order to perform writes or comparisons against the stencil buffer this + * value must be `true`. + * + * @type {boolean} + * @default false + */ this.stencilWrite = false; + /** + * User-defined clipping planes specified as THREE.Plane objects in world + * space. These planes apply to the objects this material is attached to. + * Points in space whose signed distance to the plane is negative are clipped + * (not rendered). This requires {@link WebGLRenderer#localClippingEnabled} to + * be `true`. + * + * @type {?Array<Plane>} + * @default null + */ this.clippingPlanes = null; + + /** + * Changes the behavior of clipping planes so that only their intersection is + * clipped, rather than their union. + * + * @type {boolean} + * @default false + */ this.clipIntersection = false; + + /** + * Defines whether to clip shadows according to the clipping planes specified + * on this material. + * + * @type {boolean} + * @default false + */ this.clipShadows = false; + /** + * Defines which side of faces cast shadows. If `null`, the side casting shadows + * is determined as follows: + * + * - When {@link Material#side} is set to `FrontSide`, the back side cast shadows. + * - When {@link Material#side} is set to `BackSide`, the front side cast shadows. + * - When {@link Material#side} is set to `DoubleSide`, both sides cast shadows. + * + * @type {?(FrontSide|BackSide|DoubleSide)} + * @default null + */ this.shadowSide = null; + /** + * Whether to render the material's color. + * + * This can be used in conjunction with {@link Object3D#renderOder} to create invisible + * objects that occlude other objects. + * + * @type {boolean} + * @default true + */ this.colorWrite = true; - this.precision = null; // override the renderer's default precision for this material + /** + * Override the renderer's default precision for this material. + * + * @type {?('highp'|'mediump'|'lowp')} + * @default null + */ + this.precision = null; + /** + * Whether to use polygon offset or not. When enabled, each fragment's depth value will + * be offset after it is interpolated from the depth values of the appropriate vertices. + * The offset is added before the depth test is performed and before the value is written + * into the depth buffer. + * + * Can be useful for rendering hidden-line images, for applying decals to surfaces, and for + * rendering solids with highlighted edges. + * + * @type {boolean} + * @default false + */ this.polygonOffset = false; + + /** + * Specifies a scale factor that is used to create a variable depth offset for each polygon. + * + * @type {number} + * @default 0 + */ this.polygonOffsetFactor = 0; + + /** + * Is multiplied by an implementation-specific value to create a constant depth offset. + * + * @type {number} + * @default 0 + */ this.polygonOffsetUnits = 0; + /** + * Whether to apply dithering to the color to remove the appearance of banding. + * + * @type {boolean} + * @default false + */ this.dithering = false; + /** + * Whether alpha to coverage should be enabled or not. Can only be used with MSAA-enabled contexts + * (meaning when the renderer was created with *antialias* parameter set to `true`). Enabling this + * will smooth aliasing on clip plane edges and alphaTest-clipped edges. + * + * @type {boolean} + * @default false + */ this.alphaToCoverage = false; + + /** + * Whether to premultiply the alpha (transparency) value. + * + * @type {boolean} + * @default false + */ this.premultipliedAlpha = false; + + /** + * Whether double-sided, transparent objects should be rendered with a single pass or not. + * + * The engine renders double-sided, transparent objects with two draw calls (back faces first, + * then front faces) to mitigate transparency artifacts. There are scenarios however where this + * approach produces no quality gains but still doubles draw calls e.g. when rendering flat + * vegetation like grass sprites. In these cases, set the `forceSinglePass` flag to `true` to + * disable the two pass rendering to avoid performance issues. + * + * @type {boolean} + * @default false + */ this.forceSinglePass = false; + /** + * Whether it's possible to override the material with {@link Scene#overrideMaterial} or not. + * + * @type {boolean} + * @default true + */ + this.allowOverride = true; + + /** + * Defines whether 3D objects using this material are visible. + * + * @type {boolean} + * @default true + */ this.visible = true; + /** + * Defines whether this material is tone mapped according to the renderer's tone mapping setting. + * + * It is ignored when rendering to a render target or using post processing or when using + * `WebGPURenderer`. In all these cases, all materials are honored by tone mapping. + * + * @type {boolean} + * @default true + */ this.toneMapped = true; + /** + * An object that can be used to store custom data about the Material. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ this.userData = {}; + /** + * This starts at `0` and counts how many times {@link Material#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ this.version = 0; this._alphaTest = 0; } + /** + * Sets the alpha value to be used when running an alpha test. The material + * will not be rendered if the opacity is lower than this value. + * + * @type {number} + * @readonly + * @default 0 + */ get alphaTest() { return this._alphaTest; @@ -11029,18 +19220,56 @@ class Material extends EventDispatcher { } - onBuild( /* shaderobject, renderer */ ) {} - + /** + * An optional callback that is executed immediately before the material is used to render a 3D object. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Object3D} object - The 3D object. + * @param {Object} group - The geometry group data. + */ onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} + /** + * An optional callback that is executed immediately before the shader + * program is compiled. This function is called with the shader source code + * as a parameter. Useful for the modification of built-in materials. + * + * This method can only be used when rendering with {@link WebGLRenderer}. The + * recommended approach when customizing materials is to use `WebGPURenderer` with the new + * Node Material system and [TSL]{@link https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language}. + * + * @param {{vertexShader:string,fragmentShader:string,uniforms:Object}} shaderobject - The object holds the uniforms and the vertex and fragment shader source. + * @param {WebGLRenderer} renderer - A reference to the renderer. + */ onBeforeCompile( /* shaderobject, renderer */ ) {} + /** + * In case {@link Material#onBeforeCompile} is used, this callback can be used to identify + * values of settings used in `onBeforeCompile()`, so three.js can reuse a cached + * shader or recompile the shader for this material as needed. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @return {string} The custom program cache key. + */ customProgramCacheKey() { return this.onBeforeCompile.toString(); } + /** + * This method can be used to set default values from parameter objects. + * It is a generic implementation so it can be used with different types + * of materials. + * + * @param {Object} [values] - The material values to set. + */ setValues( values ) { if ( values === undefined ) return; @@ -11083,6 +19312,13 @@ class Material extends EventDispatcher { } + /** + * Serializes the material into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized material. + * @see {@link ObjectLoader#parse} + */ toJSON( meta ) { const isRootObject = ( meta === undefined || typeof meta === 'string' ); @@ -11098,7 +19334,7 @@ class Material extends EventDispatcher { const data = { metadata: { - version: 4.6, + version: 4.7, type: 'Material', generator: 'Material.toJSON' } @@ -11147,6 +19383,20 @@ class Material extends EventDispatcher { } + if ( this.sheenColorMap && this.sheenColorMap.isTexture ) { + + data.sheenColorMap = this.sheenColorMap.toJSON( meta ).uuid; + + } + + if ( this.sheenRoughnessMap && this.sheenRoughnessMap.isTexture ) { + + data.sheenRoughnessMap = this.sheenRoughnessMap.toJSON( meta ).uuid; + + } + + if ( this.dispersion !== undefined ) data.dispersion = this.dispersion; + if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; @@ -11348,12 +19598,23 @@ class Material extends EventDispatcher { } + /** + * Returns a new material with copied values from this instance. + * + * @return {Material} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given material to this instance. + * + * @param {Material} source - The material to copy. + * @return {Material} A reference to this instance. + */ copy( source ) { this.name = source.name; @@ -11435,12 +19696,32 @@ class Material extends EventDispatcher { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Material#dispose + */ dispose() { + /** + * Fires when the material has been disposed of. + * + * @event Material#dispose + * @type {Object} + */ this.dispatchEvent( { type: 'dispose' } ); } + /** + * Setting this property to `true` indicates the engine the material + * needs to be recompiled. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ set needsUpdate( value ) { if ( value === true ) this.version ++; @@ -11449,9 +19730,7 @@ class Material extends EventDispatcher { } -/** - * Uniform Utilities - */ +// Uniform Utilities function cloneUniforms( src ) { @@ -11535,13 +19814,22 @@ function cloneUniformsGroups( src ) { function getUnlitUniformColorSpace( renderer ) { - if ( renderer.getRenderTarget() === null ) { + const currentRenderTarget = renderer.getRenderTarget(); + + if ( currentRenderTarget === null ) { // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 return renderer.outputColorSpace; } + // https://github.com/mrdoob/three.js/issues/27868 + if ( currentRenderTarget.isXRRenderTarget === true ) { + + return currentRenderTarget.texture.colorSpace; + + } + return ColorManagement.workingColorSpace; } @@ -11554,54 +19842,248 @@ var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelVie var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; +/** + * A material rendered with custom shaders. A shader is a small program written in GLSL. + * that runs on the GPU. You may want to use a custom shader if you need to implement an + * effect not included with any of the built-in materials. + * + * There are the following notes to bear in mind when using a `ShaderMaterial`: + * + * - `ShaderMaterial` can only be used with {@link WebGLRenderer}. + * - Built in attributes and uniforms are passed to the shaders along with your code. If + * you don't want that, use {@link RawShaderMaterial} instead. + * - You can use the directive `#pragma unroll_loop_start` and `#pragma unroll_loop_end` + * in order to unroll a `for` loop in GLSL by the shader preprocessor. The directive has + * to be placed right above the loop. The loop formatting has to correspond to a defined standard. + * - The loop has to be [normalized]{@link https://en.wikipedia.org/wiki/Normalized_loop}. + * - The loop variable has to be *i*. + * - The value `UNROLLED_LOOP_INDEX` will be replaced with the explicitly + * value of *i* for the given iteration and can be used in preprocessor + * statements. + * + * ```js + * const material = new THREE.ShaderMaterial( { + * uniforms: { + * time: { value: 1.0 }, + * resolution: { value: new THREE.Vector2() } + * }, + * vertexShader: document.getElementById( 'vertexShader' ).textContent, + * fragmentShader: document.getElementById( 'fragmentShader' ).textContent + * } ); + * ``` + * + * @augments Material + */ class ShaderMaterial extends Material { + /** + * Constructs a new shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isShaderMaterial = true; this.type = 'ShaderMaterial'; + /** + * Defines custom constants using `#define` directives within the GLSL code + * for both the vertex shader and the fragment shader; each key/value pair + * yields another directive. + * ```js + * defines: { + * FOO: 15, + * BAR: true + * } + * ``` + * Yields the lines: + * ``` + * #define FOO 15 + * #define BAR true + * ``` + * + * @type {Object} + */ this.defines = {}; + + /** + * An object of the form: + * ```js + * { + * "uniform1": { value: 1.0 }, + * "uniform2": { value: 2 } + * } + * ``` + * specifying the uniforms to be passed to the shader code; keys are uniform + * names, values are definitions of the form + * ``` + * { + * value: 1.0 + * } + * ``` + * where `value` is the value of the uniform. Names must match the name of + * the uniform, as defined in the GLSL code. Note that uniforms are refreshed + * on every frame, so updating the value of the uniform will immediately + * update the value available to the GLSL code. + * + * @type {Object} + */ this.uniforms = {}; + + /** + * An array holding uniforms groups for configuring UBOs. + * + * @type {Array<UniformsGroup>} + */ this.uniformsGroups = []; + /** + * Vertex shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ this.vertexShader = default_vertex; + + /** + * Fragment shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ this.fragmentShader = default_fragment; + /** + * Controls line thickness or lines. + * + * WebGL and WebGPU ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ this.linewidth = 1; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes + /** + * Define whether the material color is affected by global fog settings; `true` + * to pass fog uniforms to the shader. + * + * @type {boolean} + * @default false + */ + this.fog = false; + + /** + * Defines whether this material uses lighting; `true` to pass uniform data + * related to lighting to this shader. + * + * @type {boolean} + * @default false + */ + this.lights = false; + /** + * Defines whether this material supports clipping; `true` to let the renderer + * pass the clippingPlanes uniform. + * + * @type {boolean} + * @default false + */ + this.clipping = false; + + /** + * Overwritten and set to `true` by default. + * + * @type {boolean} + * @default true + */ this.forceSinglePass = true; + /** + * This object allows to enable certain WebGL 2 extensions. + * + * - clipCullDistance: set to `true` to use vertex shader clipping + * - multiDraw: set to `true` to use vertex shader multi_draw / enable gl_DrawID + * + * @type {{clipCullDistance:false,multiDraw:false}} + */ this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false, // set to use shader texture LOD clipCullDistance: false, // set to use vertex shader clipping multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID }; - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. + /** + * When the rendered geometry doesn't include these attributes but the + * material does, these default values will be passed to the shaders. This + * avoids errors when buffer data is missing. + * + * - color: [ 1, 1, 1 ] + * - uv: [ 0, 0 ] + * - uv1: [ 0, 0 ] + * + * @type {Object} + */ this.defaultAttributeValues = { 'color': [ 1, 1, 1 ], 'uv': [ 0, 0 ], 'uv1': [ 0, 0 ] }; + /** + * If set, this calls [gl.bindAttribLocation]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation} + * to bind a generic vertex index to an attribute variable. + * + * @type {string|undefined} + * @default undefined + */ this.index0AttributeName = undefined; + + /** + * Can be used to force a uniform update while changing uniforms in + * {@link Object3D#onBeforeRender}. + * + * @type {boolean} + * @default false + */ this.uniformsNeedUpdate = false; + /** + * Defines the GLSL version of custom shader code. + * + * @type {?(GLSL1|GLSL3)} + * @default null + */ this.glslVersion = null; if ( parameters !== undefined ) { @@ -11745,15 +20227,45 @@ const _edge1 = /*@__PURE__*/ new Vector3(); const _edge2 = /*@__PURE__*/ new Vector3(); const _normal$1 = /*@__PURE__*/ new Vector3(); +/** + * A ray that emits from an origin in a certain direction. The class is used by + * {@link Raycaster} to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3D space the mouse is over) + * amongst other things. + */ class Ray { - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { + /** + * Constructs a new ray. + * + * @param {Vector3} [origin=(0,0,0)] - The origin of the ray. + * @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray. + */ + constructor( origin = new Vector3(), direction = new Vector3( 0, 0, -1 ) ) { + /** + * The origin of the ray. + * + * @type {Vector3} + */ this.origin = origin; + + /** + * The (normalized) direction of the ray. + * + * @type {Vector3} + */ this.direction = direction; } + /** + * Sets the ray's components by copying the given values. + * + * @param {Vector3} origin - The origin. + * @param {Vector3} direction - The direction. + * @return {Ray} A reference to this ray. + */ set( origin, direction ) { this.origin.copy( origin ); @@ -11763,6 +20275,12 @@ class Ray { } + /** + * Copies the values of the given ray to this instance. + * + * @param {Ray} ray - The ray to copy. + * @return {Ray} A reference to this ray. + */ copy( ray ) { this.origin.copy( ray.origin ); @@ -11772,12 +20290,25 @@ class Ray { } + /** + * Returns a vector that is located at a given distance along this ray. + * + * @param {number} t - The distance along the ray to retrieve a position for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A position on the ray. + */ at( t, target ) { return target.copy( this.origin ).addScaledVector( this.direction, t ); } + /** + * Adjusts the direction of the ray to point at the given vector in world space. + * + * @param {Vector3} v - The target position. + * @return {Ray} A reference to this ray. + */ lookAt( v ) { this.direction.copy( v ).sub( this.origin ).normalize(); @@ -11786,6 +20317,12 @@ class Ray { } + /** + * Shift the origin of this ray along its direction by the given distance. + * + * @param {number} t - The distance along the ray to interpolate. + * @return {Ray} A reference to this ray. + */ recast( t ) { this.origin.copy( this.at( t, _vector$3 ) ); @@ -11794,6 +20331,13 @@ class Ray { } + /** + * Returns the point along this ray that is closest to the given point. + * + * @param {Vector3} point - A point in 3D space to get the closet location on the ray for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on this ray. + */ closestPointToPoint( point, target ) { target.subVectors( point, this.origin ); @@ -11810,12 +20354,24 @@ class Ray { } + /** + * Returns the distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The distance. + */ distanceToPoint( point ) { return Math.sqrt( this.distanceSqToPoint( point ) ); } + /** + * Returns the squared distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The squared distance. + */ distanceSqToPoint( point ) { const directionDistance = _vector$3.subVectors( point, this.origin ).dot( this.direction ); @@ -11834,6 +20390,15 @@ class Ray { } + /** + * Returns the squared distance between this ray and the given line segment. + * + * @param {Vector3} v0 - The start point of the line segment. + * @param {Vector3} v1 - The end point of the line segment. + * @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment. + * @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray. + * @return {number} The squared distance. + */ distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h @@ -11953,6 +20518,14 @@ class Ray { } + /** + * Intersects this ray with the given sphere, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Sphere} sphere - The sphere to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectSphere( sphere, target ) { _vector$3.subVectors( sphere.center, this.origin ); @@ -11983,12 +20556,27 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given sphere. + * + * @param {Sphere} sphere - The sphere to intersect. + * @return {boolean} Whether this ray intersects with the given sphere or not. + */ intersectsSphere( sphere ) { + if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187 + return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); } + /** + * Computes the distance from the ray's origin to the given plane. Returns `null` if the ray + * does not intersect with the plane. + * + * @param {Plane} plane - The plane to compute the distance to. + * @return {?number} Whether this ray intersects with the given sphere or not. + */ distanceToPlane( plane ) { const denominator = plane.normal.dot( this.direction ); @@ -12016,6 +20604,14 @@ class Ray { } + /** + * Intersects this ray with the given plane, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Plane} plane - The plane to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectPlane( plane, target ) { const t = this.distanceToPlane( plane ); @@ -12030,6 +20626,12 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given plane. + * + * @param {Plane} plane - The plane to intersect. + * @return {boolean} Whether this ray intersects with the given plane or not. + */ intersectsPlane( plane ) { // check if the ray lies on the plane first @@ -12056,6 +20658,14 @@ class Ray { } + /** + * Intersects this ray with the given bounding box, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Box3} box - The box to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectBox( box, target ) { let tmin, tmax, tymin, tymax, tzmin, tzmax; @@ -12122,12 +20732,29 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this ray intersects with the given box or not. + */ intersectsBox( box ) { return this.intersectBox( box, _vector$3 ) !== null; } + /** + * Intersects this ray with the given triangle, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Vector3} a - The first vertex of the triangle. + * @param {Vector3} b - The second vertex of the triangle. + * @param {Vector3} c - The third vertex of the triangle. + * @param {boolean} backfaceCulling - Whether to use backface culling or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectTriangle( a, b, c, backfaceCulling, target ) { // Compute the offset origin, edges, and normal. @@ -12153,7 +20780,7 @@ class Ray { } else if ( DdN < 0 ) { - sign = - 1; + sign = -1; DdN = - DdN; } else { @@ -12203,6 +20830,12 @@ class Ray { } + /** + * Transforms this ray with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix4 - The transformation matrix. + * @return {Ray} A reference to this ray. + */ applyMatrix4( matrix4 ) { this.origin.applyMatrix4( matrix4 ); @@ -12212,12 +20845,23 @@ class Ray { } + /** + * Returns `true` if this ray is equal with the given one. + * + * @param {Ray} ray - The ray to test for equality. + * @return {boolean} Whether this ray is equal with the given one. + */ equals( ray ) { return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); } + /** + * Returns a new ray with copied values from this instance. + * + * @return {Ray} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -12226,7 +20870,7 @@ class Ray { } -const _v0$1 = /*@__PURE__*/ new Vector3(); +const _v0$2 = /*@__PURE__*/ new Vector3(); const _v1$2 = /*@__PURE__*/ new Vector3(); const _v2$1 = /*@__PURE__*/ new Vector3(); const _v3$1 = /*@__PURE__*/ new Vector3(); @@ -12238,21 +20882,61 @@ const _vap = /*@__PURE__*/ new Vector3(); const _vbp = /*@__PURE__*/ new Vector3(); const _vcp = /*@__PURE__*/ new Vector3(); +const _v40 = /*@__PURE__*/ new Vector4(); +const _v41 = /*@__PURE__*/ new Vector4(); +const _v42 = /*@__PURE__*/ new Vector4(); + +/** + * A geometric triangle as defined by three vectors representing its three corners. + */ class Triangle { + /** + * Constructs a new triangle. + * + * @param {Vector3} [a=(0,0,0)] - The first corner of the triangle. + * @param {Vector3} [b=(0,0,0)] - The second corner of the triangle. + * @param {Vector3} [c=(0,0,0)] - The third corner of the triangle. + */ constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { + /** + * The first corner of the triangle. + * + * @type {Vector3} + */ this.a = a; + + /** + * The second corner of the triangle. + * + * @type {Vector3} + */ this.b = b; + + /** + * The third corner of the triangle. + * + * @type {Vector3} + */ this.c = c; } + /** + * Computes the normal vector of a triangle. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ static getNormal( a, b, c, target ) { target.subVectors( c, b ); - _v0$1.subVectors( a, b ); - target.cross( _v0$1 ); + _v0$2.subVectors( a, b ); + target.cross( _v0$2 ); const targetLengthSq = target.lengthSq(); if ( targetLengthSq > 0 ) { @@ -12265,17 +20949,28 @@ class Triangle { } - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ static getBarycoord( point, a, b, c, target ) { - _v0$1.subVectors( c, a ); + // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + + _v0$2.subVectors( c, a ); _v1$2.subVectors( b, a ); _v2$1.subVectors( point, a ); - const dot00 = _v0$1.dot( _v0$1 ); - const dot01 = _v0$1.dot( _v1$2 ); - const dot02 = _v0$1.dot( _v2$1 ); + const dot00 = _v0$2.dot( _v0$2 ); + const dot01 = _v0$2.dot( _v1$2 ); + const dot02 = _v0$2.dot( _v2$1 ); const dot11 = _v1$2.dot( _v1$2 ); const dot12 = _v1$2.dot( _v2$1 ); @@ -12298,6 +20993,17 @@ class Triangle { } + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ static containsPoint( point, a, b, c ) { // if the triangle is degenerate then we can't contain a point @@ -12311,6 +21017,20 @@ class Triangle { } + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} p1 - The first corner of the triangle. + * @param {Vector3} p2 - The second corner of the triangle. + * @param {Vector3} p3 - The third corner of the triangle. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { if ( this.getBarycoord( point, p1, p2, p3, _v3$1 ) === null ) { @@ -12332,16 +21052,63 @@ class Triangle { } + /** + * Computes the value barycentrically interpolated for the given attribute and indices. + * + * @param {BufferAttribute} attr - The attribute to interpolate. + * @param {number} i1 - Index of first vertex. + * @param {number} i2 - Index of second vertex. + * @param {number} i3 - Index of third vertex. + * @param {Vector3} barycoord - The barycoordinate value to use to interpolate. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The interpolated attribute value. + */ + static getInterpolatedAttribute( attr, i1, i2, i3, barycoord, target ) { + + _v40.setScalar( 0 ); + _v41.setScalar( 0 ); + _v42.setScalar( 0 ); + + _v40.fromBufferAttribute( attr, i1 ); + _v41.fromBufferAttribute( attr, i2 ); + _v42.fromBufferAttribute( attr, i3 ); + + target.setScalar( 0 ); + target.addScaledVector( _v40, barycoord.x ); + target.addScaledVector( _v41, barycoord.y ); + target.addScaledVector( _v42, barycoord.z ); + + return target; + + } + + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ static isFrontFacing( a, b, c, direction ) { - _v0$1.subVectors( c, b ); + _v0$2.subVectors( c, b ); _v1$2.subVectors( a, b ); // strictly front facing - return ( _v0$1.cross( _v1$2 ).dot( direction ) < 0 ) ? true : false; + return ( _v0$2.cross( _v1$2 ).dot( direction ) < 0 ) ? true : false; } + /** + * Sets the triangle's vertices by copying the given values. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ set( a, b, c ) { this.a.copy( a ); @@ -12352,6 +21119,15 @@ class Triangle { } + /** + * Sets the triangle's vertices by copying the given array values. + * + * @param {Array<Vector3>} points - An array with 3D points. + * @param {number} i0 - The array index representing the first corner of the triangle. + * @param {number} i1 - The array index representing the second corner of the triangle. + * @param {number} i2 - The array index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ setFromPointsAndIndices( points, i0, i1, i2 ) { this.a.copy( points[ i0 ] ); @@ -12362,6 +21138,15 @@ class Triangle { } + /** + * Sets the triangle's vertices by copying the given attribute values. + * + * @param {BufferAttribute} attribute - A buffer attribute with 3D points data. + * @param {number} i0 - The attribute index representing the first corner of the triangle. + * @param {number} i1 - The attribute index representing the second corner of the triangle. + * @param {number} i2 - The attribute index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ setFromAttributeAndIndices( attribute, i0, i1, i2 ) { this.a.fromBufferAttribute( attribute, i0 ); @@ -12372,12 +21157,23 @@ class Triangle { } + /** + * Returns a new triangle with copied values from this instance. + * + * @return {Triangle} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given triangle to this instance. + * + * @param {Triangle} triangle - The triangle to copy. + * @return {Triangle} A reference to this triangle. + */ copy( triangle ) { this.a.copy( triangle.a ); @@ -12388,63 +21184,132 @@ class Triangle { } + /** + * Computes the area of the triangle. + * + * @return {number} The triangle's area. + */ getArea() { - _v0$1.subVectors( this.c, this.b ); + _v0$2.subVectors( this.c, this.b ); _v1$2.subVectors( this.a, this.b ); - return _v0$1.cross( _v1$2 ).length() * 0.5; + return _v0$2.cross( _v1$2 ).length() * 0.5; } + /** + * Computes the midpoint of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's midpoint. + */ getMidpoint( target ) { return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); } + /** + * Computes the normal of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ getNormal( target ) { return Triangle.getNormal( this.a, this.b, this.c, target ); } + /** + * Computes a plane the triangle lies within. + * + * @param {Plane} target - The target vector that is used to store the method's result. + * @return {Plane} The plane the triangle lies within. + */ getPlane( target ) { return target.setFromCoplanarPoints( this.a, this.b, this.c ); } + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ getBarycoord( point, target ) { return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); } + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ getInterpolation( point, v1, v2, v3, target ) { return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); } + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ containsPoint( point ) { return Triangle.containsPoint( point, this.a, this.b, this.c ); } + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ isFrontFacing( direction ) { return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); } + /** + * Returns `true` if this triangle intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this triangle intersects with the given box or not. + */ intersectsBox( box ) { return box.intersectsTriangle( this ); } + /** + * Returns the closest point on the triangle to the given point. + * + * @param {Vector3} p - The point to compute the closest point for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the triangle. + */ closestPointToPoint( p, target ) { const a = this.a, b = this.b, c = this.c; @@ -12526,6 +21391,12 @@ class Triangle { } + /** + * Returns `true` if this triangle is equal with the given one. + * + * @param {Triangle} triangle - The triangle to test for equality. + * @return {boolean} Whether this triangle is equal with the given one. + */ equals( triangle ) { return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); @@ -12534,41 +21405,206 @@ class Triangle { } +/** + * A material for drawing geometries in a simple shaded (flat or wireframe) way. + * + * This material is not affected by lights. + * + * @augments Material + */ class MeshBasicMaterial extends Material { + /** + * Constructs a new mesh basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshBasicMaterial = true; this.type = 'MeshBasicMaterial'; - this.color = new Color( 0xffffff ); // emissive + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ this.lightMapIntensity = 1.0; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ this.aoMapIntensity = 1.0; + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ this.specularMap = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ this.refractionRatio = 0.98; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinejoin = 'round'; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -12624,30 +21660,88 @@ const _vC$1 = /*@__PURE__*/ new Vector3(); const _tempA = /*@__PURE__*/ new Vector3(); const _morphA = /*@__PURE__*/ new Vector3(); -const _uvA$1 = /*@__PURE__*/ new Vector2(); -const _uvB$1 = /*@__PURE__*/ new Vector2(); -const _uvC$1 = /*@__PURE__*/ new Vector2(); - -const _normalA = /*@__PURE__*/ new Vector3(); -const _normalB = /*@__PURE__*/ new Vector3(); -const _normalC = /*@__PURE__*/ new Vector3(); - const _intersectionPoint = /*@__PURE__*/ new Vector3(); const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); +/** + * Class representing triangular polygon mesh based objects. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments Object3D + */ class Mesh extends Object3D { + /** + * Constructs a new mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array<Material>} [material] - The mesh material. + */ constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMesh = true; this.type = 'Mesh'; + /** + * The mesh geometry. + * + * @type {BufferGeometry} + */ this.geometry = geometry; + + /** + * The mesh material. + * + * @type {Material|Array<Material>} + * @default MeshBasicMaterial + */ this.material = material; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object<String,number>|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array<number>|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + /** + * The number of instances of this mesh. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + this.updateMorphTargets(); } @@ -12675,6 +21769,10 @@ class Mesh extends Object3D { } + /** + * Sets the values of {@link Mesh#morphTargetDictionary} and {@link Mesh#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ updateMorphTargets() { const geometry = this.geometry; @@ -12706,6 +21804,14 @@ class Mesh extends Object3D { } + /** + * Returns the local-space position of the vertex at the given index, taking into + * account the current animation state of both morph targets and skinning. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * @return {Vector3} The vertex position in local space. + */ getVertexPosition( index, target ) { const geometry = this.geometry; @@ -12750,6 +21856,12 @@ class Mesh extends Object3D { } + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { const geometry = this.geometry; @@ -12933,7 +22045,7 @@ class Mesh extends Object3D { } -function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { +function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) { let intersect; @@ -12970,41 +22082,32 @@ function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, n object.getVertexPosition( b, _vB$1 ); object.getVertexPosition( c, _vC$1 ); - const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); + const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); if ( intersection ) { - if ( uv ) { + const barycoord = new Vector3(); + Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord ); - _uvA$1.fromBufferAttribute( uv, a ); - _uvB$1.fromBufferAttribute( uv, b ); - _uvC$1.fromBufferAttribute( uv, c ); + if ( uv ) { - intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); + intersection.uv = Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new Vector2() ); } if ( uv1 ) { - _uvA$1.fromBufferAttribute( uv1, a ); - _uvB$1.fromBufferAttribute( uv1, b ); - _uvC$1.fromBufferAttribute( uv1, c ); - - intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); + intersection.uv1 = Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new Vector2() ); } if ( normal ) { - _normalA.fromBufferAttribute( normal, a ); - _normalB.fromBufferAttribute( normal, b ); - _normalC.fromBufferAttribute( normal, c ); - - intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); + intersection.normal = Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new Vector3() ); if ( intersection.normal.dot( ray.direction ) > 0 ) { - intersection.normal.multiplyScalar( - 1 ); + intersection.normal.multiplyScalar( -1 ); } @@ -13021,6 +22124,7 @@ function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, n Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); intersection.face = face; + intersection.barycoord = barycoord; } @@ -13044,9 +22148,9 @@ var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; -var batching_pars_vertex = "#ifdef USE_BATCHING\n\tattribute float batchId;\n\tuniform highp sampler2D batchingTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; +var batching_pars_vertex = "#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec3 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 ).rgb;\n\t}\n#endif"; -var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( batchId );\n#endif"; +var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif"; var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; @@ -13070,11 +22174,11 @@ var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\ var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; -var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; +var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif"; -var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; +var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif"; -var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; +var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; @@ -13084,13 +22188,13 @@ var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; +var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; -var colorspace_pars_fragment = "\nconst mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3(\n\tvec3( 0.8224621, 0.177538, 0.0 ),\n\tvec3( 0.0331941, 0.9668058, 0.0 ),\n\tvec3( 0.0170827, 0.0723974, 0.9105199 )\n);\nconst mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.2249401, - 0.2249404, 0.0 ),\n\tvec3( - 0.0420569, 1.0420571, 0.0 ),\n\tvec3( - 0.0196376, - 0.0786361, 1.0982735 )\n);\nvec4 LinearSRGBToLinearDisplayP3( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a );\n}\nvec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a );\n}\nvec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn sRGBTransferOETF( value );\n}"; +var colorspace_pars_fragment = "vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; @@ -13112,15 +22216,13 @@ var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying flo var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; -var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; - var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; -var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; +var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; @@ -13132,25 +22234,25 @@ var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; +var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; -var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; +var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; -var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; +var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; -var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; +var logdepthbuf_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; -var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; +var logdepthbuf_pars_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; -var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; +var logdepthbuf_pars_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; -var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; +var logdepthbuf_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif"; -var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; +var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; @@ -13162,15 +22264,15 @@ var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_META var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; -var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[MORPHTARGETS_COUNT];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; +var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; -var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; +var morphcolor_vertex = "#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; +var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t#endif\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\t#ifndef USE_INSTANCING_MORPH\n\t\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\t#endif\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; +var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif"; -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; +var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; @@ -13194,7 +22296,7 @@ var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; +var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; @@ -13208,13 +22310,13 @@ var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUG var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; -var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; +var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\tfloat depth = unpackRGBAToDepth( texture2D( depths, uv ) );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\treturn step( depth, compare );\n\t\t#else\n\t\t\treturn step( compare, depth );\n\t\t#endif\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow( sampler2D shadow, vec2 uv, float compare ) {\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\tfloat hard_shadow = step( distribution.x, compare );\n\t\t#else\n\t\t\tfloat hard_shadow = step( compare, distribution.x );\n\t\t#endif\n\t\tif ( hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif"; -var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; +var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; +var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; @@ -13230,11 +22332,11 @@ var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D spe var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; -var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tfloat startCompression = 0.8 - 0.04;\n\tfloat desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min(color.r, min(color.g, color.b));\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max(color.r, max(color.g, color.b));\n\tif (peak < startCompression) return color;\n\tfloat d = 1. - startCompression;\n\tfloat newPeak = 1. - d * d / (peak + d - startCompression);\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);\n\treturn mix(color, vec3(1, 1, 1), g);\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; +var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; -var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; +var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; -var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; +var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; @@ -13258,7 +22360,7 @@ const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform fl const vertex$e = "#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#include <morphinstance_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvHighPrecisionZW = gl_Position.zw;\n}"; -const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <logdepthbuf_fragment>\n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; +const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <logdepthbuf_fragment>\n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\tfloat fragCoordZ = vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ];\n\t#else\n\t\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ] + 0.5;\n\t#endif\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}"; const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#include <morphinstance_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}"; @@ -13294,7 +22396,7 @@ const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive; const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphinstance_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; -const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <iridescence_fragment>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_physical_pars_fragment>\n#include <transmission_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_pars_fragment>\n#include <iridescence_pars_fragment>\n#include <roughnessmap_pars_fragment>\n#include <metalnessmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <clipping_planes_fragment>\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <roughnessmap_fragment>\n\t#include <metalnessmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_physical_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include <transmission_fragment>\n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; +const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <iridescence_fragment>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_physical_pars_fragment>\n#include <transmission_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_pars_fragment>\n#include <iridescence_pars_fragment>\n#include <roughnessmap_pars_fragment>\n#include <metalnessmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <clipping_planes_fragment>\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <roughnessmap_fragment>\n\t#include <metalnessmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_physical_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include <transmission_fragment>\n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphinstance_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}"; @@ -13308,7 +22410,7 @@ const vertex$2 = "#include <common>\n#include <batching_pars_vertex>\n#include < const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <logdepthbuf_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\nvoid main() {\n\t#include <logdepthbuf_fragment>\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n}"; -const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}"; +const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}"; const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n}"; @@ -13356,7 +22458,6 @@ const ShaderChunk = { fog_fragment: fog_fragment, fog_pars_fragment: fog_pars_fragment, gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, lightmap_pars_fragment: lightmap_pars_fragment, lights_lambert_fragment: lights_lambert_fragment, lights_lambert_pars_fragment: lights_lambert_pars_fragment, @@ -13458,10 +22559,7 @@ const ShaderChunk = { sprite_frag: fragment$1 }; -/** - * Uniforms library for shared webgl shaders - */ - +// Uniforms library for shared webgl shaders const UniformsLib = { common: { @@ -13490,7 +22588,7 @@ const UniformsLib = { envMap: { value: null }, envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, - flipEnvMap: { value: - 1 }, + flipEnvMap: { value: -1 }, reflectivity: { value: 1.0 }, // basic, lambert, phong ior: { value: 1.5 }, // physical refractionRatio: { value: 0.98 }, // basic, lambert, phong @@ -13586,6 +22684,7 @@ const UniformsLib = { } }, directionalLightShadows: { value: [], properties: { + shadowIntensity: 1, shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, @@ -13606,6 +22705,7 @@ const UniformsLib = { } }, spotLightShadows: { value: [], properties: { + shadowIntensity: 1, shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, @@ -13624,6 +22724,7 @@ const UniformsLib = { } }, pointLightShadows: { value: [], properties: { + shadowIntensity: 1, shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, @@ -13771,7 +22872,7 @@ const ShaderLib = { emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, roughness: { value: 1.0 }, metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } // temporary + envMapIntensity: { value: 1 } } ] ), @@ -13908,7 +23009,7 @@ const ShaderLib = { uniforms: { envMap: { value: null }, - flipEnvMap: { value: - 1 }, + flipEnvMap: { value: -1 }, backgroundBlurriness: { value: 0 }, backgroundIntensity: { value: 1 }, backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } @@ -13923,7 +23024,7 @@ const ShaderLib = { uniforms: { tCube: { value: null }, - tFlip: { value: - 1 }, + tFlip: { value: -1 }, opacity: { value: 1.0 } }, @@ -13992,6 +23093,7 @@ ShaderLib.physical = { clearcoatRoughness: { value: 0 }, clearcoatRoughnessMap: { value: null }, clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + dispersion: { value: 0 }, iridescence: { value: 0 }, iridescenceMap: { value: null }, iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, @@ -14050,9 +23152,8 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, let currentBackgroundVersion = 0; let currentTonemapping = null; - function render( renderList, scene ) { + function getBackground( scene ) { - let forceClear = false; let background = scene.isScene === true ? scene.background : null; if ( background && background.isTexture ) { @@ -14062,6 +23163,15 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, } + return background; + + } + + function render( scene ) { + + let forceClear = false; + const background = getBackground( scene ); + if ( background === null ) { setClear( clearColor, clearAlpha ); @@ -14087,10 +23197,22 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, if ( renderer.autoClear || forceClear ) { + // buffers might not be writable which is required to ensure a correct clear + + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); } + } + + function addToRenderList( renderList, scene ) { + + const background = getBackground( scene ); + if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { if ( boxMesh === undefined ) { @@ -14105,7 +23227,8 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, side: BackSide, depthTest: false, depthWrite: false, - fog: false + fog: false, + allowOverride: false } ) ); @@ -14136,18 +23259,18 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, _e1$1.copy( scene.backgroundRotation ); // accommodate left-handed frame - _e1$1.x *= - 1; _e1$1.y *= - 1; _e1$1.z *= - 1; + _e1$1.x *= -1; _e1$1.y *= -1; _e1$1.z *= -1; if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { // environment maps which are not cube render targets or PMREMs follow a different convention - _e1$1.y *= - 1; - _e1$1.z *= - 1; + _e1$1.y *= -1; + _e1$1.z *= -1; } boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; + boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? -1 : 1; boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( _e1$1 ) ); @@ -14184,7 +23307,8 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, side: FrontSide, depthTest: false, depthWrite: false, - fog: false + fog: false, + allowOverride: false } ) ); @@ -14246,6 +23370,28 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, } + function dispose() { + + if ( boxMesh !== undefined ) { + + boxMesh.geometry.dispose(); + boxMesh.material.dispose(); + + boxMesh = undefined; + + } + + if ( planeMesh !== undefined ) { + + planeMesh.geometry.dispose(); + planeMesh.material.dispose(); + + planeMesh = undefined; + + } + + } + return { getClearColor: function () { @@ -14271,19 +23417,18 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, setClear( clearColor, clearAlpha ); }, - render: render + render: render, + addToRenderList: addToRenderList, + dispose: dispose }; } -function WebGLBindingStates( gl, extensions, attributes, capabilities ) { +function WebGLBindingStates( gl, attributes ) { const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); - const vaoAvailable = capabilities.isWebGL2 || extension !== null; - const bindingStates = {}; const defaultState = createBindingState( null ); @@ -14294,38 +23439,18 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { let updateBuffers = false; - if ( vaoAvailable ) { - - const state = getBindingState( geometry, program, material ); - - if ( currentState !== state ) { - - currentState = state; - bindVertexArrayObject( currentState.object ); - - } - - updateBuffers = needsUpdate( object, geometry, program, index ); - - if ( updateBuffers ) saveCache( object, geometry, program, index ); - - } else { + const state = getBindingState( geometry, program, material ); - const wireframe = ( material.wireframe === true ); + if ( currentState !== state ) { - if ( currentState.geometry !== geometry.id || - currentState.program !== program.id || - currentState.wireframe !== wireframe ) { + currentState = state; + bindVertexArrayObject( currentState.object ); - currentState.geometry = geometry.id; - currentState.program = program.id; - currentState.wireframe = wireframe; - - updateBuffers = true; + } - } + updateBuffers = needsUpdate( object, geometry, program, index ); - } + if ( updateBuffers ) saveCache( object, geometry, program, index ); if ( index !== null ) { @@ -14351,25 +23476,19 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { function createVertexArrayObject() { - if ( capabilities.isWebGL2 ) return gl.createVertexArray(); - - return extension.createVertexArrayOES(); + return gl.createVertexArray(); } function bindVertexArrayObject( vao ) { - if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); - - return extension.bindVertexArrayOES( vao ); + return gl.bindVertexArray( vao ); } function deleteVertexArrayObject( vao ) { - if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); - - return extension.deleteVertexArrayOES( vao ); + return gl.deleteVertexArray( vao ); } @@ -14567,9 +23686,7 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); - - extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); + gl.vertexAttribDivisor( attribute, meshPerAttribute ); attributeDivisors[ attribute ] = meshPerAttribute; } @@ -14610,12 +23727,6 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { function setupVertexAttributes( object, material, program, geometry ) { - if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { - - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; - - } - initAttributes(); const geometryAttributes = geometry.attributes; @@ -14654,9 +23765,9 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { const type = attribute.type; const bytesPerElement = attribute.bytesPerElement; - // check for integer attributes (WebGL 2 only) + // check for integer attributes - const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); + const integer = ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ); if ( geometryAttribute.isInterleavedBufferAttribute ) { @@ -14904,9 +24015,7 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) { } -function WebGLBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; +function WebGLBufferRenderer( gl, extensions, info ) { let mode; @@ -14928,54 +24037,52 @@ function WebGLBufferRenderer( gl, extensions, info, capabilities ) { if ( primcount === 0 ) return; - let extension, methodName; + gl.drawArraysInstanced( mode, start, count, primcount ); - if ( isWebGL2 ) { + info.update( count, mode, primcount ); - extension = gl; - methodName = 'drawArraysInstanced'; + } - } else { + function renderMultiDraw( starts, counts, drawCount ) { - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawArraysInstancedANGLE'; + if ( drawCount === 0 ) return; - if ( extension === null ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - } + elementCount += counts[ i ]; } - extension[ methodName ]( mode, start, count, primcount ); - - info.update( count, mode, primcount ); + info.update( elementCount, mode, 1 ); } - function renderMultiDraw( starts, counts, drawCount ) { + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); + if ( extension === null ) { - for ( let i = 0; i < drawCount; i ++ ) { + for ( let i = 0; i < starts.length; i ++ ) { - this.render( starts[ i ], counts[ i ] ); + renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); } } else { - extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); let elementCount = 0; for ( let i = 0; i < drawCount; i ++ ) { - elementCount += counts[ i ]; + elementCount += counts[ i ] * primcount[ i ]; } @@ -14991,10 +24098,11 @@ function WebGLBufferRenderer( gl, extensions, info, capabilities ) { this.render = render; this.renderInstances = renderInstances; this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; } -function WebGLCapabilities( gl, extensions, parameters ) { +function WebGLCapabilities( gl, extensions, parameters, utils ) { let maxAnisotropy; @@ -15018,6 +24126,33 @@ function WebGLCapabilities( gl, extensions, parameters ) { } + function textureFormatReadable( textureFormat ) { + + if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { + + return false; + + } + + return true; + + } + + function textureTypeReadable( textureType ) { + + const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ); + + if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) + textureType !== FloatType && ! halfFloatSupportedByExt ) { + + return false; + + } + + return true; + + } + function getMaxPrecision( precision ) { if ( precision === 'highp' ) { @@ -15048,8 +24183,6 @@ function WebGLCapabilities( gl, extensions, parameters ) { } - const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; const maxPrecision = getMaxPrecision( precision ); @@ -15060,9 +24193,8 @@ function WebGLCapabilities( gl, extensions, parameters ) { } - const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + const reversedDepthBuffer = parameters.reversedDepthBuffer === true && extensions.has( 'EXT_clip_control' ); const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); @@ -15075,22 +24207,22 @@ function WebGLCapabilities( gl, extensions, parameters ) { const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); const vertexTextures = maxVertexTextures > 0; - const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); - const floatVertexTextures = vertexTextures && floatFragmentTextures; - const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; + const maxSamples = gl.getParameter( gl.MAX_SAMPLES ); return { - isWebGL2: isWebGL2, - - drawBuffers: drawBuffers, + isWebGL2: true, // keeping this for backwards compatibility getMaxAnisotropy: getMaxAnisotropy, getMaxPrecision: getMaxPrecision, + textureFormatReadable: textureFormatReadable, + textureTypeReadable: textureTypeReadable, + precision: precision, logarithmicDepthBuffer: logarithmicDepthBuffer, + reversedDepthBuffer: reversedDepthBuffer, maxTextures: maxTextures, maxVertexTextures: maxVertexTextures, @@ -15103,8 +24235,6 @@ function WebGLCapabilities( gl, extensions, parameters ) { maxFragmentUniforms: maxFragmentUniforms, vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures, maxSamples: maxSamples @@ -15278,23 +24408,75 @@ function WebGLClipping( properties ) { } +/** + * Abstract base class for cameras. This class should always be inherited + * when you build a new camera. + * + * @abstract + * @augments Object3D + */ class Camera extends Object3D { + /** + * Constructs a new camera. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCamera = true; this.type = 'Camera'; + /** + * The inverse of the camera's world matrix. + * + * @type {Matrix4} + */ this.matrixWorldInverse = new Matrix4(); + /** + * The camera's projection matrix. + * + * @type {Matrix4} + */ this.projectionMatrix = new Matrix4(); + + /** + * The inverse of the camera's projection matrix. + * + * @type {Matrix4} + */ this.projectionMatrixInverse = new Matrix4(); + /** + * The coordinate system in which the camera is used. + * + * @type {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + */ this.coordinateSystem = WebGLCoordinateSystem; + this._reversedDepth = false; + + } + + /** + * The flag that indicates whether the camera uses a reversed depth buffer. + * + * @type {boolean} + * @default false + */ + get reversedDepth() { + + return this._reversedDepth; + } copy( source, recursive ) { @@ -15312,6 +24494,15 @@ class Camera extends Object3D { } + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * This method is overwritten since cameras have a different forward vector compared to other + * 3D objects. A camera looks down its local, negative z-axis by default. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ getWorldDirection( target ) { return super.getWorldDirection( target ).negate(); @@ -15346,29 +24537,126 @@ const _v3 = /*@__PURE__*/ new Vector3(); const _minTarget = /*@__PURE__*/ new Vector2(); const _maxTarget = /*@__PURE__*/ new Vector2(); - +/** + * Camera that uses [perspective projection]{@link https://en.wikipedia.org/wiki/Perspective_(graphical)}. + * + * This projection mode is designed to mimic the way the human eye sees. It + * is the most common projection mode used for rendering a 3D scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ class PerspectiveCamera extends Camera { + /** + * Constructs a new perspective camera. + * + * @param {number} [fov=50] - The vertical field of view. + * @param {number} [aspect=1] - The aspect ratio. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPerspectiveCamera = true; this.type = 'PerspectiveCamera'; + /** + * The vertical field of view, from bottom to top of view, + * in degrees. + * + * @type {number} + * @default 50 + */ this.fov = fov; + + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ this.zoom = 1; + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link PerspectiveCamera#far}. + * + * Note that, unlike for the {@link OrthographicCamera}, `0` is <em>not</em> a + * valid value for a perspective camera's near plane. + * + * @type {number} + * @default 0.1 + */ this.near = near; + + /** + * The camera's far plane. Must be greater than the + * current value of {@link PerspectiveCamera#near}. + * + * @type {number} + * @default 2000 + */ this.far = far; + + /** + * Object distance used for stereoscopy and depth-of-field effects. This + * parameter does not influence the projection matrix unless a + * {@link StereoCamera} is being used. + * + * @type {number} + * @default 10 + */ this.focus = 10; + /** + * The aspect ratio, usually the canvas width / canvas height. + * + * @type {number} + * @default 1 + */ this.aspect = aspect; + + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ this.view = null; - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) + /** + * Film size used for the larger axis. Default is `35` (millimeters). This + * parameter does not influence the projection matrix unless {@link PerspectiveCamera#filmOffset} + * is set to a nonzero value. + * + * @type {number} + * @default 35 + */ + this.filmGauge = 35; + + /** + * Horizontal off-center offset in the same unit as {@link PerspectiveCamera#filmGauge}. + * + * @type {number} + * @default 0 + */ + this.filmOffset = 0; this.updateProjectionMatrix(); @@ -15396,12 +24684,12 @@ class PerspectiveCamera extends Camera { } /** - * Sets the FOV by focal length in respect to the current .filmGauge. + * Sets the FOV by focal length in respect to the current {@link PerspectiveCamera#filmGauge}. * * The default film gauge is 35, so that the focal length can be specified for * a 35mm (full frame) camera. * - * Values for focal length and film gauge must have the same unit. + * @param {number} focalLength - Values for focal length and film gauge must have the same unit. */ setFocalLength( focalLength ) { @@ -15414,7 +24702,10 @@ class PerspectiveCamera extends Camera { } /** - * Calculates the focal length from the current .fov and .filmGauge. + * Returns the focal length from the current {@link PerspectiveCamera#fov} and + * {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The computed focal length. */ getFocalLength() { @@ -15424,6 +24715,11 @@ class PerspectiveCamera extends Camera { } + /** + * Returns the current vertical field of view angle in degrees considering {@link PerspectiveCamera#zoom}. + * + * @return {number} The effective FOV. + */ getEffectiveFOV() { return RAD2DEG * 2 * Math.atan( @@ -15431,6 +24727,12 @@ class PerspectiveCamera extends Camera { } + /** + * Returns the width of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ getFilmWidth() { // film not completely covered in portrait format (aspect < 1) @@ -15438,6 +24740,12 @@ class PerspectiveCamera extends Camera { } + /** + * Returns the height of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ getFilmHeight() { // film not completely covered in landscape format (aspect > 1) @@ -15447,11 +24755,15 @@ class PerspectiveCamera extends Camera { /** * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. - * Sets minTarget and maxTarget to the coordinates of the lower-left and upper-right corners of the view rectangle. + * Sets `minTarget` and `maxTarget` to the coordinates of the lower-left and upper-right corners of the view rectangle. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} minTarget - The lower-left corner of the view rectangle is written into this vector. + * @param {Vector2} maxTarget - The upper-right corner of the view rectangle is written into this vector. */ getViewBounds( distance, minTarget, maxTarget ) { - _v3.set( - 1, - 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); + _v3.set( -1, -1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); minTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); @@ -15463,7 +24775,10 @@ class PerspectiveCamera extends Camera { /** * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. - * Copies the result into the target Vector2, where x is width and y is height. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} target - The target vector that is used to store result where x is width and y is height. + * @returns {Vector2} The view size. */ getViewSize( distance, target ) { @@ -15479,34 +24794,42 @@ class PerspectiveCamera extends Camera { * * For example, if you have 3x2 monitors and each monitor is 1920x1080 and * the monitors are in grid like this - * + *``` * +---+---+---+ * | A | B | C | * +---+---+---+ * | D | E | F | * +---+---+---+ + *``` + * then for each monitor you would call it like this: + *```js + * const w = 1920; + * const h = 1080; + * const fullWidth = w * 3; + * const fullHeight = h * 2; * - * then for each monitor you would call it like this - * - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; + * // --A-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * // --B-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * // --C-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * // --D-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * // --E-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * // --F-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * ``` * - * --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * Note there is no reason monitors have to be the same size or in a grid. * - * Note there is no reason monitors have to be the same size or in a grid. + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. */ setViewOffset( fullWidth, fullHeight, x, y, width, height ) { @@ -15538,6 +24861,9 @@ class PerspectiveCamera extends Camera { } + /** + * Removes the view offset from the projection matrix. + */ clearViewOffset() { if ( this.view !== null ) { @@ -15550,13 +24876,17 @@ class PerspectiveCamera extends Camera { } + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ updateProjectionMatrix() { const near = this.near; let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; let height = 2 * top; let width = this.aspect * height; - let left = - 0.5 * width; + let left = -0.5 * width; const view = this.view; if ( this.view !== null && this.view.enabled ) { @@ -15574,7 +24904,7 @@ class PerspectiveCamera extends Camera { const skew = this.filmOffset; if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem ); + this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem, this.reversedDepth ); this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); @@ -15604,19 +24934,75 @@ class PerspectiveCamera extends Camera { } -const fov = - 90; // negative fov is not an error +const fov = -90; // negative fov is not an error const aspect = 1; +/** + * A special type of camera that is positioned in 3D space to render its surroundings into a + * cube render target. The render target can then be used as an environment map for rendering + * realtime reflections in your scene. + * + * ```js + * // Create cube render target + * const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); + * + * // Create cube camera + * const cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); + * scene.add( cubeCamera ); + * + * // Create car + * const chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); + * const car = new THREE.Mesh( carGeometry, chromeMaterial ); + * scene.add( car ); + * + * // Update the render target cube + * car.visible = false; + * cubeCamera.position.copy( car.position ); + * cubeCamera.update( renderer, scene ); + * + * // Render the scene + * car.visible = true; + * renderer.render( scene, camera ); + * ``` + * + * @augments Object3D + */ class CubeCamera extends Object3D { + /** + * Constructs a new cube camera. + * + * @param {number} near - The camera's near plane. + * @param {number} far - The camera's far plane. + * @param {WebGLCubeRenderTarget} renderTarget - The cube render target. + */ constructor( near, far, renderTarget ) { super(); this.type = 'CubeCamera'; + /** + * A reference to the cube render target. + * + * @type {WebGLCubeRenderTarget} + */ this.renderTarget = renderTarget; + + /** + * The current active coordinate system. + * + * @type {?(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + * @default null + */ this.coordinateSystem = null; + + /** + * The current active mipmap level + * + * @type {number} + * @default 0 + */ this.activeMipmapLevel = 0; const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); @@ -15645,6 +25031,9 @@ class CubeCamera extends Object3D { } + /** + * Must be called when the coordinate system of the cube camera is changed. + */ updateCoordinateSystem() { const coordinateSystem = this.coordinateSystem; @@ -15661,39 +25050,39 @@ class CubeCamera extends Object3D { cameraPX.lookAt( 1, 0, 0 ); cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( - 1, 0, 0 ); + cameraNX.lookAt( -1, 0, 0 ); - cameraPY.up.set( 0, 0, - 1 ); + cameraPY.up.set( 0, 0, -1 ); cameraPY.lookAt( 0, 1, 0 ); cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, - 1, 0 ); + cameraNY.lookAt( 0, -1, 0 ); cameraPZ.up.set( 0, 1, 0 ); cameraPZ.lookAt( 0, 0, 1 ); cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); + cameraNZ.lookAt( 0, 0, -1 ); } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - cameraPX.up.set( 0, - 1, 0 ); - cameraPX.lookAt( - 1, 0, 0 ); + cameraPX.up.set( 0, -1, 0 ); + cameraPX.lookAt( -1, 0, 0 ); - cameraNX.up.set( 0, - 1, 0 ); + cameraNX.up.set( 0, -1, 0 ); cameraNX.lookAt( 1, 0, 0 ); cameraPY.up.set( 0, 0, 1 ); cameraPY.lookAt( 0, 1, 0 ); - cameraNY.up.set( 0, 0, - 1 ); - cameraNY.lookAt( 0, - 1, 0 ); + cameraNY.up.set( 0, 0, -1 ); + cameraNY.lookAt( 0, -1, 0 ); - cameraPZ.up.set( 0, - 1, 0 ); + cameraPZ.up.set( 0, -1, 0 ); cameraPZ.lookAt( 0, 0, 1 ); - cameraNZ.up.set( 0, - 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); + cameraNZ.up.set( 0, -1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); } else { @@ -15711,6 +25100,13 @@ class CubeCamera extends Object3D { } + /** + * Calling this method will render the given scene with the given renderer + * into the cube render target of the camera. + * + * @param {(Renderer|WebGLRenderer)} renderer - The renderer. + * @param {Scene} scene - The scene to render. + */ update( renderer, scene ) { if ( this.parent === null ) this.updateMatrixWorld(); @@ -15772,21 +25168,69 @@ class CubeCamera extends Object3D { } +/** + * Creates a cube texture made up of six images. + * + * ```js + * const loader = new THREE.CubeTextureLoader(); + * loader.setPath( 'textures/cube/pisa/' ); + * + * const textureCube = loader.load( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } ); + * ``` + * + * @augments Texture + */ class CubeTexture extends Texture { - constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { - - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; + /** + * Constructs a new cube texture. + * + * @param {Array<Image>} [images=[]] - An array holding a image for each side of a cube. + * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space value. + */ + constructor( images = [], mapping = CubeReflectionMapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCubeTexture = true; + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.flipY = false; } + /** + * Alias for {@link CubeTexture#image}. + * + * @type {Array<Image>} + */ get images() { return this.image; @@ -15801,18 +25245,42 @@ class CubeTexture extends Texture { } +/** + * A cube render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ class WebGLCubeRenderTarget extends WebGLRenderTarget { + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ constructor( size = 1, options = {} ) { super( size, size, options ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWebGLCubeRenderTarget = true; const image = { width: size, height: size, depth: 1 }; const images = [ image, image, image, image, image, image ]; - this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new CubeTexture( images ); + this._setTextureOptions( options ); // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, @@ -15824,11 +25292,15 @@ class WebGLCubeRenderTarget extends WebGLRenderTarget { this.texture.isRenderTargetTexture = true; - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; - } + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {WebGLCubeRenderTarget} A reference to this cube render target. + */ fromEquirectangularTexture( renderer, texture ) { this.texture.type = texture.type; @@ -15919,7 +25391,15 @@ class WebGLCubeRenderTarget extends WebGLRenderTarget { } - clear( renderer, color, depth, stencil ) { + /** + * Clears this cube render target. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + clear( renderer, color = true, depth = true, stencil = true ) { const currentRenderTarget = renderer.getRenderTarget(); @@ -16032,25 +25512,115 @@ function WebGLCubeMaps( renderer ) { } +/** + * Camera that uses [orthographic projection]{@link https://en.wikipedia.org/wiki/Orthographic_projection}. + * + * In this projection mode, an object's size in the rendered image stays + * constant regardless of its distance from the camera. This can be useful + * for rendering 2D scenes and UI elements, amongst other things. + * + * ```js + * const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ class OrthographicCamera extends Camera { - constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { + /** + * Constructs a new orthographic camera. + * + * @param {number} [left=-1] - The left plane of the camera's frustum. + * @param {number} [right=1] - The right plane of the camera's frustum. + * @param {number} [top=1] - The top plane of the camera's frustum. + * @param {number} [bottom=-1] - The bottom plane of the camera's frustum. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000 ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isOrthographicCamera = true; this.type = 'OrthographicCamera'; + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ this.zoom = 1; + + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ this.view = null; + /** + * The left plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ this.left = left; + + /** + * The right plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ this.right = right; + + /** + * The top plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ this.top = top; + + /** + * The bottom plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ this.bottom = bottom; + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link OrthographicCamera#far}. + * + * Note that, unlike for the {@link PerspectiveCamera}, `0` is a + * valid value for an orthographic camera's near plane. + * + * @type {number} + * @default 0.1 + */ this.near = near; + + /** + * The camera's far plane. Must be greater than the + * current value of {@link OrthographicCamera#near}. + * + * @type {number} + * @default 2000 + */ this.far = far; this.updateProjectionMatrix(); @@ -16075,6 +25645,18 @@ class OrthographicCamera extends Camera { } + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + * @see {@link PerspectiveCamera#setViewOffset} + */ setViewOffset( fullWidth, fullHeight, x, y, width, height ) { if ( this.view === null ) { @@ -16103,6 +25685,9 @@ class OrthographicCamera extends Camera { } + /** + * Removes the view offset from the projection matrix. + */ clearViewOffset() { if ( this.view !== null ) { @@ -16115,6 +25700,10 @@ class OrthographicCamera extends Camera { } + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ updateProjectionMatrix() { const dx = ( this.right - this.left ) / ( 2 * this.zoom ); @@ -16139,7 +25728,7 @@ class OrthographicCamera extends Camera { } - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem ); + this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem, this.reversedDepth ); this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); @@ -16182,6 +25771,7 @@ const _clearColor = /*@__PURE__*/ new Color(); let _oldTarget = null; let _oldActiveCubeFace = 0; let _oldActiveMipmapLevel = 0; +let _oldXrEnabled = false; // Golden Ratio const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; @@ -16190,16 +25780,18 @@ const INV_PHI = 1 / PHI; // Vertices of a dodecahedron (except the opposites, which represent the // same axis), used as axis directions evenly spread on a sphere. const _axisDirections = [ - /*@__PURE__*/ new Vector3( 1, 1, 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, 1 ), - /*@__PURE__*/ new Vector3( 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), - /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), - /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), - /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) ]; + +const _origin = /*@__PURE__*/ new Vector3(); /** * This class generates a Prefiltered, Mipmapped Radiance Environment Map @@ -16212,12 +25804,16 @@ const _axisDirections = [ * higher roughness levels. In this way we maintain resolution to smoothly * interpolate diffuse lighting while limiting sampling computation. * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} */ - class PMREMGenerator { + /** + * Constructs a new PMREM generator. + * + * @param {WebGLRenderer} renderer - The renderer. + */ constructor( renderer ) { this._renderer = renderer; @@ -16241,21 +25837,37 @@ class PMREMGenerator { * Generates a PMREM from a supplied Scene, which can be faster than using an * image if networking bandwidth is low. Optional sigma specifies a blur radius * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @return {WebGLRenderTarget} The resulting PMREM. */ - fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + const { + size = 256, + position = _origin, + } = options; _oldTarget = this._renderer.getRenderTarget(); _oldActiveCubeFace = this._renderer.getActiveCubeFace(); _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - this._setSize( 256 ); + this._renderer.xr.enabled = false; + + this._setSize( size ); const cubeUVRenderTarget = this._allocateTargets(); cubeUVRenderTarget.depthBuffer = true; - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); if ( sigma > 0 ) { @@ -16274,7 +25886,10 @@ class PMREMGenerator { * Generates a PMREM from an equirectangular texture, which can be either LDR * or HDR. The ideal input image size is 1k (1024 x 512), * as this matches best with the 256 x 256 cubemap output. - * The smallest supported equirectangular image size is 64 x 32. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. */ fromEquirectangular( equirectangular, renderTarget = null ) { @@ -16286,7 +25901,10 @@ class PMREMGenerator { * Generates a PMREM from an cubemap texture, which can be either LDR * or HDR. The ideal input cube size is 256 x 256, * as this matches best with the 256 x 256 cubemap output. - * The smallest supported cube size is 16 x 16. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. */ fromCubemap( cubemap, renderTarget = null ) { @@ -16364,6 +25982,8 @@ class PMREMGenerator { _cleanup( outputTarget ) { this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + this._renderer.xr.enabled = _oldXrEnabled; + outputTarget.scissorTest = false; _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); @@ -16384,6 +26004,9 @@ class PMREMGenerator { _oldTarget = this._renderer.getRenderTarget(); _oldActiveCubeFace = this._renderer.getActiveCubeFace(); _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; + + this._renderer.xr.enabled = false; const cubeUVRenderTarget = renderTarget || this._allocateTargets(); this._textureToCubeUV( texture, cubeUVRenderTarget ); @@ -16439,13 +26062,13 @@ class PMREMGenerator { } - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { const fov = 90; const aspect = 1; const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, - 1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; + const upSign = [ 1, -1, 1, 1, 1, 1 ]; + const forwardSign = [ 1, 1, 1, -1, -1, -1 ]; const renderer = this._renderer; const originalAutoClear = renderer.autoClear; @@ -16455,6 +26078,17 @@ class PMREMGenerator { renderer.toneMapping = NoToneMapping; renderer.autoClear = false; + // https://github.com/mrdoob/three.js/issues/31413#issuecomment-3095966812 + const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); + + if ( reversedDepthBuffer ) { + + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.clearDepth(); + renderer.setRenderTarget( null ); + + } + const backgroundMaterial = new MeshBasicMaterial( { name: 'PMREM.Background', side: BackSide, @@ -16491,17 +26125,21 @@ class PMREMGenerator { if ( col === 0 ) { cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); } else if ( col === 1 ) { cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); + } else { cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); } @@ -16544,7 +26182,7 @@ class PMREMGenerator { } - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; + this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? -1 : 1; } else { @@ -16577,12 +26215,13 @@ class PMREMGenerator { const renderer = this._renderer; const autoClear = renderer.autoClear; renderer.autoClear = false; + const n = this._lodPlanes.length; - for ( let i = 1; i < this._lodPlanes.length; i ++ ) { + for ( let i = 1; i < n; i ++ ) { const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); @@ -16598,6 +26237,13 @@ class PMREMGenerator { * the blur latitudinally (around the poles), and then longitudinally (towards * the poles) to approximate the orthogonally-separable blur. It is least * accurate at the poles, but still does a decent job. + * + * @private + * @param {WebGLRenderTarget} cubeUVRenderTarget + * @param {number} lodIn + * @param {number} lodOut + * @param {number} sigma + * @param {Vector3} [poleAxis] */ _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { @@ -16756,7 +26402,7 @@ function _createPlanes( lodMax ) { for ( let face = 0; face < cubeFaces; face ++ ) { const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : - 1; + const y = face > 2 ? 0 : -1; const coordinates = [ x, y, 0, x + 2 / 3, y, 0, @@ -16955,7 +26601,7 @@ function _getCubemapMaterial() { uniforms: { 'envMap': { value: null }, - 'flipEnvMap': { value: - 1 } + 'flipEnvMap': { value: -1 } }, vertexShader: _getCommonVertexShader(), @@ -17066,24 +26712,26 @@ function WebGLCubeUVMaps( renderer ) { if ( isEquirectMap || isCubeMap ) { - if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { + let renderTarget = cubeUVmaps.get( texture ); - texture.needsPMREMUpdate = false; + const currentPMREMVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : 0; - let renderTarget = cubeUVmaps.get( texture ); + if ( texture.isRenderTargetTexture && texture.pmremVersion !== currentPMREMVersion ) { if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); + renderTarget.texture.pmremVersion = texture.pmremVersion; + cubeUVmaps.set( texture, renderTarget ); return renderTarget.texture; } else { - if ( cubeUVmaps.has( texture ) ) { + if ( renderTarget !== undefined ) { - return cubeUVmaps.get( texture ).texture; + return renderTarget.texture; } else { @@ -17093,7 +26741,9 @@ function WebGLCubeUVMaps( renderer ) { if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); + renderTarget.texture.pmremVersion = texture.pmremVersion; + cubeUVmaps.set( texture, renderTarget ); texture.addEventListener( 'dispose', onTextureDispose ); @@ -17224,29 +26874,14 @@ function WebGLExtensions( gl ) { }, - init: function ( capabilities ) { - - if ( capabilities.isWebGL2 ) { - - getExtension( 'EXT_color_buffer_float' ); - getExtension( 'WEBGL_clip_cull_distance' ); - - } else { - - getExtension( 'WEBGL_depth_texture' ); - getExtension( 'OES_texture_float' ); - getExtension( 'OES_texture_half_float' ); - getExtension( 'OES_texture_half_float_linear' ); - getExtension( 'OES_standard_derivatives' ); - getExtension( 'OES_element_index_uint' ); - getExtension( 'OES_vertex_array_object' ); - getExtension( 'ANGLE_instanced_arrays' ); - - } + init: function () { + getExtension( 'EXT_color_buffer_float' ); + getExtension( 'WEBGL_clip_cull_distance' ); getExtension( 'OES_texture_float_linear' ); getExtension( 'EXT_color_buffer_half_float' ); getExtension( 'WEBGL_multisampled_render_to_texture' ); + getExtension( 'WEBGL_render_shared_exponent' ); }, @@ -17256,7 +26891,7 @@ function WebGLExtensions( gl ) { if ( extension === null ) { - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); + warnOnce( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); } @@ -17289,18 +26924,6 @@ function WebGLGeometries( gl, attributes, info, bindingStates ) { } - for ( const name in geometry.morphAttributes ) { - - const array = geometry.morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.remove( array[ i ] ); - - } - - } - geometry.removeEventListener( 'dispose', onGeometryDispose ); delete geometries[ geometry.id ]; @@ -17354,22 +26977,6 @@ function WebGLGeometries( gl, attributes, info, bindingStates ) { } - // morph targets - - const morphAttributes = geometry.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.update( array[ i ], gl.ARRAY_BUFFER ); - - } - - } - } function updateWireframeAttribute( geometry ) { @@ -17474,9 +27081,7 @@ function WebGLGeometries( gl, attributes, info, bindingStates ) { } -function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; +function WebGLIndexedBufferRenderer( gl, extensions, info ) { let mode; @@ -17507,54 +27112,53 @@ function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { if ( primcount === 0 ) return; - let extension, methodName; + gl.drawElementsInstanced( mode, count, type, start * bytesPerElement, primcount ); - if ( isWebGL2 ) { + info.update( count, mode, primcount ); - extension = gl; - methodName = 'drawElementsInstanced'; + } - } else { + function renderMultiDraw( starts, counts, drawCount ) { - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawElementsInstancedANGLE'; + if ( drawCount === 0 ) return; - if ( extension === null ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - } + elementCount += counts[ i ]; } - extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); + info.update( elementCount, mode, 1 ); - info.update( count, mode, primcount ); } - function renderMultiDraw( starts, counts, drawCount ) { + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); + if ( extension === null ) { - for ( let i = 0; i < drawCount; i ++ ) { + for ( let i = 0; i < starts.length; i ++ ) { - this.render( starts[ i ] / bytesPerElement, counts[ i ] ); + renderInstances( starts[ i ] / bytesPerElement, counts[ i ], primcount[ i ] ); } } else { - extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, type, starts, 0, primcount, 0, drawCount ); let elementCount = 0; for ( let i = 0; i < drawCount; i ++ ) { - elementCount += counts[ i ]; + elementCount += counts[ i ] * primcount[ i ]; } @@ -17571,6 +27175,7 @@ function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { this.render = render; this.renderInstances = renderInstances; this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; } @@ -17643,319 +27248,287 @@ function WebGLInfo( gl ) { } +/** + * Creates an array of textures directly from raw buffer data. + * + * @augments Texture + */ class DataArrayTexture extends Texture { + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ constructor( data = null, width = 1, height = 1, depth = 1 ) { super( null ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isDataArrayTexture = true; + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ this.image = { data, width, height, depth }; + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ this.minFilter = NearestFilter; + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ this.wrapR = ClampToEdgeWrapping; + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ this.unpackAlignment = 1; + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set<number>} + */ + this.layerUpdates = new Set(); + } -} + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire data texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { -function numericalSort( a, b ) { + this.layerUpdates.add( layerIndex ); - return a[ 0 ] - b[ 0 ]; + } -} + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { -function absNumericalSort( a, b ) { + this.layerUpdates.clear(); - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); + } } function WebGLMorphtargets( gl, capabilities, textures ) { - const influencesList = {}; - const morphInfluences = new Float32Array( 8 ); const morphTextures = new WeakMap(); const morph = new Vector4(); - const workInfluences = []; - - for ( let i = 0; i < 8; i ++ ) { - - workInfluences[ i ] = [ i, 0 ]; - - } - function update( object, geometry, program ) { const objectInfluences = object.morphTargetInfluences; - if ( capabilities.isWebGL2 === true ) { - - // instead of using attributes, the WebGL 2 code path encodes morph targets - // into an array of data textures. Each layer represents a single morph target. - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let entry = morphTextures.get( geometry ); - - if ( entry === undefined || entry.count !== morphTargetsCount ) { - - if ( entry !== undefined ) entry.texture.dispose(); + // the following encodes morph targets into an array of data textures. Each layer represents a single morph target. - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; - - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; - - let vertexDataCount = 0; - - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; - - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; - - if ( width > capabilities.maxTextureSize ) { + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; + let entry = morphTextures.get( geometry ); - } + if ( entry === undefined || entry.count !== morphTargetsCount ) { - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); + if ( entry !== undefined ) entry.texture.dispose(); - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; - // fill buffer + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; - const vertexDataStride = vertexDataCount * 4; + let vertexDataCount = 0; - for ( let i = 0; i < morphTargetsCount; i ++ ) { + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; - const offset = width * height * 4 * i; + if ( width > capabilities.maxTextureSize ) { - for ( let j = 0; j < morphTarget.count; j ++ ) { + height = Math.ceil( width / capabilities.maxTextureSize ); + width = capabilities.maxTextureSize; - const stride = j * vertexDataStride; + } - if ( hasMorphPosition === true ) { + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - morph.fromBufferAttribute( morphTarget, j ); + const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + texture.type = FloatType; + texture.needsUpdate = true; - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; + // fill buffer - } + const vertexDataStride = vertexDataCount * 4; - if ( hasMorphNormals === true ) { + for ( let i = 0; i < morphTargetsCount; i ++ ) { - morph.fromBufferAttribute( morphNormal, j ); + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; + const offset = width * height * 4 * i; - } + for ( let j = 0; j < morphTarget.count; j ++ ) { - if ( hasMorphColors === true ) { + const stride = j * vertexDataStride; - morph.fromBufferAttribute( morphColor, j ); + if ( hasMorphPosition === true ) { - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; + morph.fromBufferAttribute( morphTarget, j ); - } + buffer[ offset + stride + 0 ] = morph.x; + buffer[ offset + stride + 1 ] = morph.y; + buffer[ offset + stride + 2 ] = morph.z; + buffer[ offset + stride + 3 ] = 0; } - } - - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; - - morphTextures.set( geometry, entry ); - - function disposeTexture() { - - texture.dispose(); + if ( hasMorphNormals === true ) { - morphTextures.delete( geometry ); + morph.fromBufferAttribute( morphNormal, j ); - geometry.removeEventListener( 'dispose', disposeTexture ); + buffer[ offset + stride + 4 ] = morph.x; + buffer[ offset + stride + 5 ] = morph.y; + buffer[ offset + stride + 6 ] = morph.z; + buffer[ offset + stride + 7 ] = 0; - } - - geometry.addEventListener( 'dispose', disposeTexture ); - - } - - // - if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - - program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); + } - } else { + if ( hasMorphColors === true ) { - let morphInfluencesSum = 0; + morph.fromBufferAttribute( morphColor, j ); - for ( let i = 0; i < objectInfluences.length; i ++ ) { + buffer[ offset + stride + 8 ] = morph.x; + buffer[ offset + stride + 9 ] = morph.y; + buffer[ offset + stride + 10 ] = morph.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - morphInfluencesSum += objectInfluences[ i ]; + } } - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - } - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); - - } else { - - // When object doesn't have morph target influences defined, we treat it as a 0-length array - // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences - - const length = objectInfluences === undefined ? 0 : objectInfluences.length; - - let influences = influencesList[ geometry.id ]; - - if ( influences === undefined || influences.length !== length ) { - - // initialise list - - influences = []; - - for ( let i = 0; i < length; i ++ ) { - - influences[ i ] = [ i, 0 ]; - - } + entry = { + count: morphTargetsCount, + texture: texture, + size: new Vector2( width, height ) + }; - influencesList[ geometry.id ] = influences; + morphTextures.set( geometry, entry ); - } + function disposeTexture() { - // Collect influences + texture.dispose(); - for ( let i = 0; i < length; i ++ ) { + morphTextures.delete( geometry ); - const influence = influences[ i ]; - - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; + geometry.removeEventListener( 'dispose', disposeTexture ); } - influences.sort( absNumericalSort ); - - for ( let i = 0; i < 8; i ++ ) { + geometry.addEventListener( 'dispose', disposeTexture ); - if ( i < length && influences[ i ][ 1 ] ) { - - workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; - workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; - - } else { - - workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; - workInfluences[ i ][ 1 ] = 0; - - } + } - } + // + if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - workInfluences.sort( numericalSort ); + program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); - const morphTargets = geometry.morphAttributes.position; - const morphNormals = geometry.morphAttributes.normal; + } else { let morphInfluencesSum = 0; - for ( let i = 0; i < 8; i ++ ) { - - const influence = workInfluences[ i ]; - const index = influence[ 0 ]; - const value = influence[ 1 ]; - - if ( index !== Number.MAX_SAFE_INTEGER && value ) { - - if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { - - geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); - - } - - if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { - - geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); - - } - - morphInfluences[ i ] = value; - morphInfluencesSum += value; - - } else { - - if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { - - geometry.deleteAttribute( 'morphTarget' + i ); - - } - - if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { - - geometry.deleteAttribute( 'morphNormal' + i ); + for ( let i = 0; i < objectInfluences.length; i ++ ) { - } - - morphInfluences[ i ] = 0; - - } + morphInfluencesSum += objectInfluences[ i ]; } - // GLSL shader uses formula baseinfluence * base + sum(target * influence) - // This allows us to switch between absolute morphs and relative morphs without changing shader code - // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); + program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); } + program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); + program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); + } return { @@ -18056,42 +27629,138 @@ function WebGLObjects( gl, geometries, attributes, info ) { } +/** + * Creates a three-dimensional texture from raw data, with parameters to + * divide it into width, height, and depth. + * + * @augments Texture + */ class Data3DTexture extends Texture { + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ constructor( data = null, width = 1, height = 1, depth = 1 ) { // We're going to add .setXXX() methods for setting properties later. - // Users can still set in DataTexture3D directly. + // Users can still set in Data3DTexture directly. // - // const texture = new THREE.DataTexture3D( data, width, height, depth ); + // const texture = new THREE.Data3DTexture( data, width, height, depth ); // texture.anisotropy = 16; // // See #14839 super( null ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isData3DTexture = true; + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ this.image = { data, width, height, depth }; + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ this.minFilter = NearestFilter; + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ this.wrapR = ClampToEdgeWrapping; + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ this.unpackAlignment = 1; } } +/** + * This class can be used to automatically save the depth information of a + * rendering into a texture. + * + * @augments Texture + */ class DepthTexture extends Texture { - constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { - - format = format !== undefined ? format : DepthFormat; + /** + * Constructs a new depth texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [type=UnsignedIntType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {number} [format=DepthFormat] - The texture format. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) { if ( format !== DepthFormat && format !== DepthStencilFormat ) { @@ -18099,21 +27768,46 @@ class DepthTexture extends Texture { } - if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; + const image = { width: width, height: height, depth: depth }; - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + super( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isDepthTexture = true; - this.image = { width: width, height: height }; - - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; - + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.flipY = false; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.generateMipmaps = false; + /** + * Code corresponding to the depth compare function. + * + * @type {?(NeverCompare|LessCompare|EqualCompare|LessEqualCompare|GreaterCompare|NotEqualCompare|GreaterEqualCompare|AlwaysCompare)} + * @default null + */ this.compareFunction = null; } @@ -18123,6 +27817,7 @@ class DepthTexture extends Texture { super.copy( source ); + this.source = new Source( Object.assign( {}, source.image ) ); // see #30540 this.compareFunction = source.compareFunction; return this; @@ -18188,7 +27883,6 @@ class DepthTexture extends Texture { const emptyTexture = /*@__PURE__*/ new Texture(); const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); -emptyShadowTexture.compareFunction = LessEqualCompare; const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); @@ -18706,7 +28400,18 @@ function setValueT1( gl, v, textures ) { } - const emptyTexture2D = ( this.type === gl.SAMPLER_2D_SHADOW ) ? emptyShadowTexture : emptyTexture; + let emptyTexture2D; + + if ( this.type === gl.SAMPLER_2D_SHADOW ) { + + emptyShadowTexture.compareFunction = LessEqualCompare; // #28670 + emptyTexture2D = emptyShadowTexture; + + } else { + + emptyTexture2D = emptyTexture; + + } textures.setTexture2D( v || emptyTexture2D, unit ); @@ -19325,40 +29030,25 @@ function handleSource( string, errorLine ) { } -function getEncodingComponents( colorSpace ) { - - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const encodingPrimaries = ColorManagement.getPrimaries( colorSpace ); +const _m0 = /*@__PURE__*/ new Matrix3(); - let gamutMapping; - - if ( workingPrimaries === encodingPrimaries ) { - - gamutMapping = ''; - - } else if ( workingPrimaries === P3Primaries && encodingPrimaries === Rec709Primaries ) { - - gamutMapping = 'LinearDisplayP3ToLinearSRGB'; - - } else if ( workingPrimaries === Rec709Primaries && encodingPrimaries === P3Primaries ) { +function getEncodingComponents( colorSpace ) { - gamutMapping = 'LinearSRGBToLinearDisplayP3'; + ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - } + const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - switch ( colorSpace ) { + switch ( ColorManagement.getTransfer( colorSpace ) ) { - case LinearSRGBColorSpace: - case LinearDisplayP3ColorSpace: - return [ gamutMapping, 'LinearTransferOETF' ]; + case LinearTransfer: + return [ encodingMatrix, 'LinearTransferOETF' ]; - case SRGBColorSpace: - case DisplayP3ColorSpace: - return [ gamutMapping, 'sRGBTransferOETF' ]; + case SRGBTransfer: + return [ encodingMatrix, 'sRGBTransferOETF' ]; default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ gamutMapping, 'LinearTransferOETF' ]; + console.warn( 'THREE.WebGLProgram: Unsupported color space: ', colorSpace ); + return [ encodingMatrix, 'LinearTransferOETF' ]; } @@ -19367,7 +29057,9 @@ function getEncodingComponents( colorSpace ) { function getShaderErrors( gl, shader, type ) { const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - const errors = gl.getShaderInfoLog( shader ).trim(); + + const shaderInfoLog = gl.getShaderInfoLog( shader ) || ''; + const errors = shaderInfoLog.trim(); if ( status && errors === '' ) return ''; @@ -19391,7 +29083,16 @@ function getShaderErrors( gl, shader, type ) { function getTexelEncodingFunction( functionName, colorSpace ) { const components = getEncodingComponents( colorSpace ); - return `vec4 ${functionName}( vec4 value ) { return ${components[ 0 ]}( ${components[ 1 ]}( value ) ); }`; + + return [ + + `vec4 ${functionName}( vec4 value ) {`, + + ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, + + '}', + + ].join( '\n' ); } @@ -19410,7 +29111,7 @@ function getToneMappingFunction( functionName, toneMapping ) { break; case CineonToneMapping: - toneMappingName = 'OptimizedCineon'; + toneMappingName = 'Cineon'; break; case ACESFilmicToneMapping: @@ -19439,16 +29140,27 @@ function getToneMappingFunction( functionName, toneMapping ) { } -function generateExtensions( parameters ) { +const _v0$1 = /*@__PURE__*/ new Vector3(); + +function getLuminanceFunction() { - const chunks = [ - ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.alphaToCoverage || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', - ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; + ColorManagement.getLuminanceCoefficients( _v0$1 ); - return chunks.filter( filterEmptyLine ).join( '\n' ); + const r = _v0$1.x.toFixed( 4 ); + const g = _v0$1.y.toFixed( 4 ); + const b = _v0$1.z.toFixed( 4 ); + + return [ + + 'float luminance( const in vec3 rgb ) {', + + ` const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`, + + ' return dot( weights, rgb );', + + '}' + + ].join( '\n' ); } @@ -19554,11 +29266,7 @@ function resolveIncludes( string ) { } -const shaderChunkMap = new Map( [ - [ 'encodings_fragment', 'colorspace_fragment' ], // @deprecated, r154 - [ 'encodings_pars_fragment', 'colorspace_pars_fragment' ], // @deprecated, r154 - [ 'output_fragment', 'opaque_fragment' ], // @deprecated, r154 -] ); +const shaderChunkMap = new Map(); function includeReplacer( match, include ) { @@ -19619,27 +29327,21 @@ function generatePrecision( parameters ) { precision ${parameters.precision} int; precision ${parameters.precision} sampler2D; precision ${parameters.precision} samplerCube; + precision ${parameters.precision} sampler3D; + precision ${parameters.precision} sampler2DArray; + precision ${parameters.precision} sampler2DShadow; + precision ${parameters.precision} samplerCubeShadow; + precision ${parameters.precision} sampler2DArrayShadow; + precision ${parameters.precision} isampler2D; + precision ${parameters.precision} isampler3D; + precision ${parameters.precision} isamplerCube; + precision ${parameters.precision} isampler2DArray; + precision ${parameters.precision} usampler2D; + precision ${parameters.precision} usampler3D; + precision ${parameters.precision} usamplerCube; + precision ${parameters.precision} usampler2DArray; `; - if ( parameters.isWebGL2 ) { - - precisionstring += `precision ${parameters.precision} sampler3D; - precision ${parameters.precision} sampler2DArray; - precision ${parameters.precision} sampler2DShadow; - precision ${parameters.precision} samplerCubeShadow; - precision ${parameters.precision} sampler2DArrayShadow; - precision ${parameters.precision} isampler2D; - precision ${parameters.precision} isampler3D; - precision ${parameters.precision} isamplerCube; - precision ${parameters.precision} isampler2DArray; - precision ${parameters.precision} usampler2D; - precision ${parameters.precision} usampler3D; - precision ${parameters.precision} usamplerCube; - precision ${parameters.precision} usampler2DArray; - `; - - } - if ( parameters.precision === 'highp' ) { precisionstring += '\n#define HIGH_PRECISION'; @@ -19788,8 +29490,6 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); const envMapCubeUVSize = generateCubeUVSize( parameters ); - const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); - const customVertexExtensions = generateVertexExtensions( parameters ); const customDefines = generateDefines( defines ); @@ -19818,8 +29518,6 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { prefixFragment = [ - customExtensions, - '#define SHADER_TYPE ' + parameters.shaderType, '#define SHADER_NAME ' + parameters.shaderName, @@ -19846,6 +29544,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', parameters.batching ? '#define USE_BATCHING' : '', + parameters.batchingColor ? '#define USE_BATCHING_COLOR' : '', parameters.instancing ? '#define USE_INSTANCING' : '', parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', @@ -19941,10 +29640,9 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', + ( parameters.morphColors ) ? '#define USE_MORPHCOLORS' : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', parameters.doubleSided ? '#define DOUBLE_SIDED' : '', parameters.flipSided ? '#define FLIP_SIDED' : '', @@ -19955,10 +29653,8 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', + parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', 'uniform mat4 modelMatrix;', 'uniform mat4 modelViewMatrix;', @@ -20024,31 +29720,6 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { '#endif', - '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', - - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', - - ' #ifdef USE_MORPHNORMALS', - - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', - - ' #else', - - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', - - ' #endif', - - '#endif', - '#ifdef USE_SKINNING', ' attribute vec4 skinIndex;', @@ -20062,8 +29733,6 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { prefixFragment = [ - customExtensions, - generatePrecision( parameters ), '#define SHADER_TYPE ' + parameters.shaderType, @@ -20100,6 +29769,8 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', + parameters.dispersion ? '#define USE_DISPERSION' : '', + parameters.iridescence ? '#define USE_IRIDESCENCE' : '', parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', @@ -20124,7 +29795,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', + parameters.vertexColors || parameters.instancingColor || parameters.batchingColor ? '#define USE_COLOR' : '', parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', parameters.vertexUv1s ? '#define USE_UV1' : '', parameters.vertexUv2s ? '#define USE_UV2' : '', @@ -20146,12 +29817,11 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', - parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', + parameters.decodeVideoTextureEmissive ? '#define DECODE_VIDEO_TEXTURE_EMISSIVE' : '', - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', + parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', 'uniform mat4 viewMatrix;', 'uniform vec3 cameraPosition;', @@ -20166,6 +29836,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), + getLuminanceFunction(), parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', @@ -20186,7 +29857,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { vertexShader = unrollLoops( vertexShader ); fragmentShader = unrollLoops( fragmentShader ); - if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { + if ( parameters.isRawShaderMaterial !== true ) { // GLSL 3.0 conversion for built-in materials and ShaderMaterial @@ -20194,14 +29865,12 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { prefixVertex = [ customVertexExtensions, - 'precision mediump sampler2DArray;', '#define attribute in', '#define varying out', '#define texture2D texture' ].join( '\n' ) + '\n' + prefixVertex; prefixFragment = [ - 'precision mediump sampler2DArray;', '#define varying in', ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', @@ -20251,9 +29920,13 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { // check for link errors if ( renderer.debug.checkShaderErrors ) { - const programLog = gl.getProgramInfoLog( program ).trim(); - const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); - const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); + const programInfoLog = gl.getProgramInfoLog( program ) || ''; + const vertexShaderInfoLog = gl.getShaderInfoLog( glVertexShader ) || ''; + const fragmentShaderInfoLog = gl.getShaderInfoLog( glFragmentShader ) || ''; + + const programLog = programInfoLog.trim(); + const vertexLog = vertexShaderInfoLog.trim(); + const fragmentLog = fragmentShaderInfoLog.trim(); let runnable = true; let haveDiagnostics = true; @@ -20544,7 +30217,6 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities const _activeChannels = new Set(); const programs = []; - const IS_WEBGL2 = capabilities.isWebGL2; const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; @@ -20640,6 +30312,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities } const currentRenderTarget = renderer.getRenderTarget(); + const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); const IS_INSTANCEDMESH = object.isInstancedMesh === true; const IS_BATCHEDMESH = object.isBatchedMesh === true; @@ -20659,6 +30332,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities const HAS_ANISOTROPY = material.anisotropy > 0; const HAS_CLEARCOAT = material.clearcoat > 0; + const HAS_DISPERSION = material.dispersion > 0; const HAS_IRIDESCENCE = material.iridescence > 0; const HAS_SHEEN = material.sheen > 0; const HAS_TRANSMISSION = material.transmission > 0; @@ -20706,8 +30380,6 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities const parameters = { - isWebGL2: IS_WEBGL2, - shaderID: shaderID, shaderType: material.type, shaderName: material.name, @@ -20725,6 +30397,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities precision: precision, batching: IS_BATCHEDMESH, + batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null, instancing: IS_INSTANCEDMESH, instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, @@ -20759,6 +30432,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, + dispersion: HAS_DISPERSION, + iridescence: HAS_IRIDESCENCE, iridescenceMap: HAS_IRIDESCENCEMAP, iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, @@ -20831,10 +30506,11 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities useFog: material.fog === true, fogExp2: ( !! fog && fog.isFogExp2 ), - flatShading: material.flatShading === true, + flatShading: ( material.flatShading === true && material.wireframe === false ), sizeAttenuation: material.sizeAttenuation === true, logarithmicDepthBuffer: logarithmicDepthBuffer, + reversedDepthBuffer: reversedDepthBuffer, skinning: object.isSkinnedMesh === true, @@ -20867,9 +30543,9 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities shadowMapType: renderer.shadowMap.type, toneMapping: toneMapping, - useLegacyLights: renderer._useLegacyLights, decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), + decodeVideoTextureEmissive: HAS_EMISSIVEMAP && ( material.emissiveMap.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.emissiveMap.colorSpace ) === SRGBTransfer ), premultipliedAlpha: material.premultipliedAlpha, @@ -20881,16 +30557,9 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities index0AttributeName: material.index0AttributeName, - extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, - extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, - extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, - extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), - extensionMultiDraw: HAS_EXTENSIONS && material.extensions.multiDraw === true && extensions.has( 'WEBGL_multi_draw' ), + extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), - rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), - rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), - rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), customProgramCacheKey: material.customProgramCacheKey() @@ -21006,48 +30675,52 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities _programLayers.disableAll(); - if ( parameters.isWebGL2 ) - _programLayers.enable( 0 ); if ( parameters.supportsVertexTextures ) - _programLayers.enable( 1 ); + _programLayers.enable( 0 ); if ( parameters.instancing ) - _programLayers.enable( 2 ); + _programLayers.enable( 1 ); if ( parameters.instancingColor ) - _programLayers.enable( 3 ); + _programLayers.enable( 2 ); if ( parameters.instancingMorph ) - _programLayers.enable( 4 ); + _programLayers.enable( 3 ); if ( parameters.matcap ) - _programLayers.enable( 5 ); + _programLayers.enable( 4 ); if ( parameters.envMap ) - _programLayers.enable( 6 ); + _programLayers.enable( 5 ); if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 7 ); + _programLayers.enable( 6 ); if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 8 ); + _programLayers.enable( 7 ); if ( parameters.clearcoat ) - _programLayers.enable( 9 ); + _programLayers.enable( 8 ); if ( parameters.iridescence ) - _programLayers.enable( 10 ); + _programLayers.enable( 9 ); if ( parameters.alphaTest ) - _programLayers.enable( 11 ); + _programLayers.enable( 10 ); if ( parameters.vertexColors ) - _programLayers.enable( 12 ); + _programLayers.enable( 11 ); if ( parameters.vertexAlphas ) - _programLayers.enable( 13 ); + _programLayers.enable( 12 ); if ( parameters.vertexUv1s ) - _programLayers.enable( 14 ); + _programLayers.enable( 13 ); if ( parameters.vertexUv2s ) - _programLayers.enable( 15 ); + _programLayers.enable( 14 ); if ( parameters.vertexUv3s ) - _programLayers.enable( 16 ); + _programLayers.enable( 15 ); if ( parameters.vertexTangents ) - _programLayers.enable( 17 ); + _programLayers.enable( 16 ); if ( parameters.anisotropy ) - _programLayers.enable( 18 ); + _programLayers.enable( 17 ); if ( parameters.alphaHash ) - _programLayers.enable( 19 ); + _programLayers.enable( 18 ); if ( parameters.batching ) + _programLayers.enable( 19 ); + if ( parameters.dispersion ) _programLayers.enable( 20 ); + if ( parameters.batchingColor ) + _programLayers.enable( 21 ); + if ( parameters.gradientMap ) + _programLayers.enable( 22 ); array.push( _programLayers.mask ); _programLayers.disableAll(); @@ -21060,19 +30733,19 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities _programLayers.enable( 2 ); if ( parameters.logarithmicDepthBuffer ) _programLayers.enable( 3 ); - if ( parameters.skinning ) + if ( parameters.reversedDepthBuffer ) _programLayers.enable( 4 ); - if ( parameters.morphTargets ) + if ( parameters.skinning ) _programLayers.enable( 5 ); - if ( parameters.morphNormals ) + if ( parameters.morphTargets ) _programLayers.enable( 6 ); - if ( parameters.morphColors ) + if ( parameters.morphNormals ) _programLayers.enable( 7 ); - if ( parameters.premultipliedAlpha ) + if ( parameters.morphColors ) _programLayers.enable( 8 ); - if ( parameters.shadowMapEnabled ) + if ( parameters.premultipliedAlpha ) _programLayers.enable( 9 ); - if ( parameters.useLegacyLights ) + if ( parameters.shadowMapEnabled ) _programLayers.enable( 10 ); if ( parameters.doubleSided ) _programLayers.enable( 11 ); @@ -21092,8 +30765,10 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities _programLayers.enable( 18 ); if ( parameters.decodeVideoTexture ) _programLayers.enable( 19 ); - if ( parameters.alphaToCoverage ) + if ( parameters.decodeVideoTextureEmissive ) _programLayers.enable( 20 ); + if ( parameters.alphaToCoverage ) + _programLayers.enable( 21 ); array.push( _programLayers.mask ); @@ -21196,6 +30871,12 @@ function WebGLProperties() { let properties = new WeakMap(); + function has( object ) { + + return properties.has( object ); + + } + function get( object ) { let map = properties.get( object ); @@ -21230,6 +30911,7 @@ function WebGLProperties() { } return { + has: has, get: get, remove: remove, update: update, @@ -21569,6 +31251,7 @@ function ShadowUniformsCache() { case 'DirectionalLight': uniforms = { + shadowIntensity: 1, shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, @@ -21578,6 +31261,7 @@ function ShadowUniformsCache() { case 'SpotLight': uniforms = { + shadowIntensity: 1, shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, @@ -21587,6 +31271,7 @@ function ShadowUniformsCache() { case 'PointLight': uniforms = { + shadowIntensity: 1, shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, @@ -21620,7 +31305,7 @@ function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { } -function WebGLLights( extensions, capabilities ) { +function WebGLLights( extensions ) { const cache = new UniformsCache(); @@ -21631,18 +31316,18 @@ function WebGLLights( extensions, capabilities ) { version: 0, hash: { - directionalLength: - 1, - pointLength: - 1, - spotLength: - 1, - rectAreaLength: - 1, - hemiLength: - 1, - - numDirectionalShadows: - 1, - numPointShadows: - 1, - numSpotShadows: - 1, - numSpotMaps: - 1, - - numLightProbes: - 1 + directionalLength: -1, + pointLength: -1, + spotLength: -1, + rectAreaLength: -1, + hemiLength: -1, + + numDirectionalShadows: -1, + numPointShadows: -1, + numSpotShadows: -1, + numSpotMaps: -1, + + numLightProbes: -1 }, ambient: [ 0, 0, 0 ], @@ -21675,7 +31360,7 @@ function WebGLLights( extensions, capabilities ) { const matrix4 = new Matrix4(); const matrix42 = new Matrix4(); - function setup( lights, useLegacyLights ) { + function setup( lights ) { let r = 0, g = 0, b = 0; @@ -21698,9 +31383,6 @@ function WebGLLights( extensions, capabilities ) { // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] lights.sort( shadowCastingAndTexturingLightsFirst ); - // artist-friendly light intensity scaling factor - const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; - for ( let i = 0, l = lights.length; i < l; i ++ ) { const light = lights[ i ]; @@ -21713,9 +31395,9 @@ function WebGLLights( extensions, capabilities ) { if ( light.isAmbientLight ) { - r += color.r * intensity * scaleFactor; - g += color.g * intensity * scaleFactor; - b += color.b * intensity * scaleFactor; + r += color.r * intensity; + g += color.g * intensity; + b += color.b * intensity; } else if ( light.isLightProbe ) { @@ -21731,7 +31413,7 @@ function WebGLLights( extensions, capabilities ) { const uniforms = cache.get( light ); - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); if ( light.castShadow ) { @@ -21739,6 +31421,7 @@ function WebGLLights( extensions, capabilities ) { const shadowUniforms = shadowCache.get( light ); + shadowUniforms.shadowIntensity = shadow.intensity; shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; @@ -21762,7 +31445,7 @@ function WebGLLights( extensions, capabilities ) { uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); + uniforms.color.copy( color ).multiplyScalar( intensity ); uniforms.distance = distance; uniforms.coneCos = Math.cos( light.angle ); @@ -21792,6 +31475,7 @@ function WebGLLights( extensions, capabilities ) { const shadowUniforms = shadowCache.get( light ); + shadowUniforms.shadowIntensity = shadow.intensity; shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; @@ -21823,7 +31507,7 @@ function WebGLLights( extensions, capabilities ) { const uniforms = cache.get( light ); - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); uniforms.distance = light.distance; uniforms.decay = light.decay; @@ -21833,6 +31517,7 @@ function WebGLLights( extensions, capabilities ) { const shadowUniforms = shadowCache.get( light ); + shadowUniforms.shadowIntensity = shadow.intensity; shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; @@ -21856,8 +31541,8 @@ function WebGLLights( extensions, capabilities ) { const uniforms = cache.get( light ); - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); + uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); + uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); state.hemi[ hemiLength ] = uniforms; @@ -21869,41 +31554,15 @@ function WebGLLights( extensions, capabilities ) { if ( rectAreaLength > 0 ) { - if ( capabilities.isWebGL2 ) { - - // WebGL 2 + if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else { - - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - - } + state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; + state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; } else { - // WebGL 1 - - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - - } else { - - console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); - - } + state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; + state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; } @@ -22056,14 +31715,16 @@ function WebGLLights( extensions, capabilities ) { } -function WebGLRenderState( extensions, capabilities ) { +function WebGLRenderState( extensions ) { - const lights = new WebGLLights( extensions, capabilities ); + const lights = new WebGLLights( extensions ); const lightsArray = []; const shadowsArray = []; - function init() { + function init( camera ) { + + state.camera = camera; lightsArray.length = 0; shadowsArray.length = 0; @@ -22082,9 +31743,9 @@ function WebGLRenderState( extensions, capabilities ) { } - function setupLights( useLegacyLights ) { + function setupLights() { - lights.setup( lightsArray, useLegacyLights ); + lights.setup( lightsArray ); } @@ -22098,7 +31759,11 @@ function WebGLRenderState( extensions, capabilities ) { lightsArray: lightsArray, shadowsArray: shadowsArray, - lights: lights + camera: null, + + lights: lights, + + transmissionRenderTarget: {} }; return { @@ -22113,7 +31778,7 @@ function WebGLRenderState( extensions, capabilities ) { } -function WebGLRenderStates( extensions, capabilities ) { +function WebGLRenderStates( extensions ) { let renderStates = new WeakMap(); @@ -22124,14 +31789,14 @@ function WebGLRenderStates( extensions, capabilities ) { if ( renderStateArray === undefined ) { - renderState = new WebGLRenderState( extensions, capabilities ); + renderState = new WebGLRenderState( extensions ); renderStates.set( scene, [ renderState ] ); } else { if ( renderCallDepth >= renderStateArray.length ) { - renderState = new WebGLRenderState( extensions, capabilities ); + renderState = new WebGLRenderState( extensions ); renderStateArray.push( renderState ); } else { @@ -22159,27 +31824,120 @@ function WebGLRenderStates( extensions, capabilities ) { } +/** + * A material for drawing geometry by depth. Depth is based off of the camera + * near and far plane. White is nearest, black is farthest. + * + * @augments Material + */ class MeshDepthMaterial extends Material { + /** + * Constructs a new mesh depth material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshDepthMaterial = true; this.type = 'MeshDepthMaterial'; + /** + * Type for depth packing. + * + * @type {(BasicDepthPacking|RGBADepthPacking|RGBDepthPacking|RGDepthPacking)} + * @default BasicDepthPacking + */ this.depthPacking = BasicDepthPacking; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; this.setValues( parameters ); @@ -22209,22 +31967,98 @@ class MeshDepthMaterial extends Material { } +/** + * A material used internally for implementing shadow mapping with + * point lights. + * + * Can also be used to customize the shadow casting of an object by assigning + * an instance of `MeshDistanceMaterial` to {@link Object3D#customDistanceMaterial}. + * The following examples demonstrates this approach in order to ensure + * transparent parts of objects do not cast shadows. + * + * @augments Material + */ class MeshDistanceMaterial extends Material { + /** + * Constructs a new mesh distance material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshDistanceMaterial = true; this.type = 'MeshDistanceMaterial'; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; this.setValues( parameters ); @@ -22253,7 +32087,7 @@ const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; -function WebGLShadowMap( _renderer, _objects, _capabilities ) { +function WebGLShadowMap( renderer, objects, capabilities ) { let _frustum = new Frustum(); @@ -22267,7 +32101,7 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { _materialCache = {}, - _maxTextureSize = _capabilities.maxTextureSize; + _maxTextureSize = capabilities.maxTextureSize; const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; @@ -22293,7 +32127,7 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { fullScreenTri.setAttribute( 'position', new BufferAttribute( - new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), + new Float32Array( [ -1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5 ] ), 3 ) ); @@ -22317,15 +32151,25 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { if ( lights.length === 0 ) return; - const currentRenderTarget = _renderer.getRenderTarget(); - const activeCubeFace = _renderer.getActiveCubeFace(); - const activeMipmapLevel = _renderer.getActiveMipmapLevel(); + const currentRenderTarget = renderer.getRenderTarget(); + const activeCubeFace = renderer.getActiveCubeFace(); + const activeMipmapLevel = renderer.getActiveMipmapLevel(); - const _state = _renderer.state; + const _state = renderer.state; // Set GL state for depth map. _state.setBlending( NoBlending ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); + + if ( _state.buffers.depth.getReversed() === true ) { + + _state.buffers.color.setClear( 0, 0, 0, 0 ); + + } else { + + _state.buffers.color.setClear( 1, 1, 1, 1 ); + + } + _state.buffers.depth.setTest( true ); _state.setScissorTest( false ); @@ -22395,8 +32239,8 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { } - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); + renderer.setRenderTarget( shadow.map ); + renderer.clear(); const viewportCount = shadow.getViewportCount(); @@ -22437,13 +32281,13 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { scope.needsUpdate = false; - _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); + renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); }; function VSMPass( shadow, camera ) { - const geometry = _objects.update( fullScreenMesh ); + const geometry = objects.update( fullScreenMesh ); if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { @@ -22466,18 +32310,18 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; shadowMaterialVertical.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.mapPass ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); + renderer.setRenderTarget( shadow.mapPass ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); // horizontal pass shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); + renderer.setRenderTarget( shadow.map ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); } @@ -22495,10 +32339,11 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || + if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || ( material.displacementMap && material.displacementScale !== 0 ) || ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) ) { + ( material.map && material.alphaTest > 0 ) || + ( material.alphaToCoverage === true ) ) { // in this case we need a unique material instance reflecting the // appropriate state @@ -22544,7 +32389,7 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { } result.alphaMap = material.alphaMap; - result.alphaTest = material.alphaTest; + result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value result.map = material.map; result.clipShadows = material.clipShadows; @@ -22560,7 +32405,7 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - const materialProperties = _renderer.properties.get( result ); + const materialProperties = renderer.properties.get( result ); materialProperties.light = light; } @@ -22581,7 +32426,7 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - const geometry = _objects.update( object ); + const geometry = objects.update( object ); const material = object.material; if ( Array.isArray( material ) ) { @@ -22597,11 +32442,11 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); } @@ -22611,11 +32456,11 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { const depthMaterial = getDepthMaterial( object, material, light, type ); - object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); } @@ -22661,9 +32506,19 @@ function WebGLShadowMap( _renderer, _objects, _capabilities ) { } -function WebGLState( gl, extensions, capabilities ) { +const reversedFuncs = { + [ NeverDepth ]: AlwaysDepth, + [ LessDepth ]: GreaterDepth, + [ EqualDepth ]: NotEqualDepth, + [ LessEqualDepth ]: GreaterEqualDepth, + + [ AlwaysDepth ]: NeverDepth, + [ GreaterDepth ]: LessDepth, + [ NotEqualDepth ]: EqualDepth, + [ GreaterEqualDepth ]: LessEqualDepth, +}; - const isWebGL2 = capabilities.isWebGL2; +function WebGLState( gl, extensions ) { function ColorBuffer() { @@ -22716,7 +32571,7 @@ function WebGLState( gl, extensions, capabilities ) { locked = false; currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state + currentColorClear.set( -1, 0, 0, 0 ); // set to invalid state } @@ -22728,12 +32583,45 @@ function WebGLState( gl, extensions, capabilities ) { let locked = false; + let currentReversed = false; let currentDepthMask = null; let currentDepthFunc = null; let currentDepthClear = null; return { + setReversed: function ( reversed ) { + + if ( currentReversed !== reversed ) { + + const ext = extensions.get( 'EXT_clip_control' ); + + if ( reversed ) { + + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT ); + + } else { + + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT ); + + } + + currentReversed = reversed; + + const oldDepth = currentDepthClear; + currentDepthClear = null; + this.setClear( oldDepth ); + + } + + }, + + getReversed: function () { + + return currentReversed; + + }, + setTest: function ( depthTest ) { if ( depthTest ) { @@ -22761,6 +32649,8 @@ function WebGLState( gl, extensions, capabilities ) { setFunc: function ( depthFunc ) { + if ( currentReversed ) depthFunc = reversedFuncs[ depthFunc ]; + if ( currentDepthFunc !== depthFunc ) { switch ( depthFunc ) { @@ -22827,6 +32717,12 @@ function WebGLState( gl, extensions, capabilities ) { if ( currentDepthClear !== depth ) { + if ( currentReversed ) { + + depth = 1 - depth; + + } + gl.clearDepth( depth ); currentDepthClear = depth; @@ -22841,6 +32737,7 @@ function WebGLState( gl, extensions, capabilities ) { currentDepthMask = null; currentDepthFunc = null; currentDepthClear = null; + currentReversed = false; } @@ -23003,12 +32900,12 @@ function WebGLState( gl, extensions, capabilities ) { let version = 0; const glVersion = gl.getParameter( gl.VERSION ); - if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { + if ( glVersion.indexOf( 'WebGL' ) !== -1 ) { version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); lineWidthAvailable = ( version >= 1.0 ); - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { + } else if ( glVersion.indexOf( 'OpenGL ES' ) !== -1 ) { version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); lineWidthAvailable = ( version >= 2.0 ); @@ -23035,7 +32932,7 @@ function WebGLState( gl, extensions, capabilities ) { for ( let i = 0; i < count; i ++ ) { - if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { + if ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) { gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); @@ -23054,13 +32951,8 @@ function WebGLState( gl, extensions, capabilities ) { const emptyTextures = {}; emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - - if ( isWebGL2 ) { - - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - - } + emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); + emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); // init @@ -23109,21 +33001,17 @@ function WebGLState( gl, extensions, capabilities ) { currentBoundFramebuffers[ target ] = framebuffer; - if ( isWebGL2 ) { + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER + if ( target === gl.DRAW_FRAMEBUFFER ) { - if ( target === gl.DRAW_FRAMEBUFFER ) { + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - - } - - if ( target === gl.FRAMEBUFFER ) { + } - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; + if ( target === gl.FRAMEBUFFER ) { - } + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; } @@ -23182,23 +33070,10 @@ function WebGLState( gl, extensions, capabilities ) { if ( needsUpdate ) { - if ( capabilities.isWebGL2 ) { - - gl.drawBuffers( drawBuffers ); - - } else if ( extensions.has( 'WEBGL_draw_buffers' ) === true ) { - - extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); - - } else { - - throw new Error( 'THREE.WebGLState: Usage of gl.drawBuffers() require WebGL2 or WEBGL_draw_buffers extension' ); - - } + gl.drawBuffers( drawBuffers ); } - } function useProgram( program ) { @@ -23223,23 +33098,8 @@ function WebGLState( gl, extensions, capabilities ) { [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT }; - if ( isWebGL2 ) { - - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; - - } else { - - const extension = extensions.get( 'EXT_blend_minmax' ); - - if ( extension !== null ) { - - equationToGL[ MinEquation ] = extension.MIN_EXT; - equationToGL[ MaxEquation ] = extension.MAX_EXT; - - } - - } + equationToGL[ MinEquation ] = gl.MIN; + equationToGL[ MaxEquation ] = gl.MAX; const factorToGL = { [ ZeroFactor ]: gl.ZERO, @@ -23311,7 +33171,7 @@ function WebGLState( gl, extensions, capabilities ) { break; case MultiplyBlending: - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); break; default: @@ -23329,15 +33189,15 @@ function WebGLState( gl, extensions, capabilities ) { break; case AdditiveBlending: - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); break; case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); break; case MultiplyBlending: - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); break; default: @@ -23624,7 +33484,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.compressedTexImage2D.apply( gl, arguments ); + gl.compressedTexImage2D( ...arguments ); } catch ( error ) { @@ -23638,7 +33498,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.compressedTexImage3D.apply( gl, arguments ); + gl.compressedTexImage3D( ...arguments ); } catch ( error ) { @@ -23652,7 +33512,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texSubImage2D.apply( gl, arguments ); + gl.texSubImage2D( ...arguments ); } catch ( error ) { @@ -23666,7 +33526,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texSubImage3D.apply( gl, arguments ); + gl.texSubImage3D( ...arguments ); } catch ( error ) { @@ -23680,7 +33540,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.compressedTexSubImage2D.apply( gl, arguments ); + gl.compressedTexSubImage2D( ...arguments ); } catch ( error ) { @@ -23694,7 +33554,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.compressedTexSubImage3D.apply( gl, arguments ); + gl.compressedTexSubImage3D( ...arguments ); } catch ( error ) { @@ -23708,7 +33568,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texStorage2D.apply( gl, arguments ); + gl.texStorage2D( ...arguments ); } catch ( error ) { @@ -23722,7 +33582,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texStorage3D.apply( gl, arguments ); + gl.texStorage3D( ...arguments ); } catch ( error ) { @@ -23736,7 +33596,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texImage2D.apply( gl, arguments ); + gl.texImage2D( ...arguments ); } catch ( error ) { @@ -23750,7 +33610,7 @@ function WebGLState( gl, extensions, capabilities ) { try { - gl.texImage3D.apply( gl, arguments ); + gl.texImage3D( ...arguments ); } catch ( error ) { @@ -23848,6 +33708,9 @@ function WebGLState( gl, extensions, capabilities ) { gl.depthMask( true ); gl.depthFunc( gl.LESS ); + + depthBuffer.setReversed( false ); + gl.clearDepth( 1 ); gl.stencilMask( 0xffffffff ); @@ -23863,13 +33726,8 @@ function WebGLState( gl, extensions, capabilities ) { gl.activeTexture( gl.TEXTURE0 ); gl.bindFramebuffer( gl.FRAMEBUFFER, null ); - - if ( isWebGL2 === true ) { - - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - - } + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); gl.useProgram( null ); @@ -23974,9 +33832,144 @@ function WebGLState( gl, extensions, capabilities ) { } +/** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ +function getByteLength( width, height, format, type ) { + + const typeByteLength = getTextureTypeByteLength( type ); + + switch ( format ) { + + // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml + case AlphaFormat: + return width * height; + case RedFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RedIntegerFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGIntegerFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBFormat: + return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAIntegerFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ + case RGB_S3TC_DXT1_Format: + case RGBA_S3TC_DXT1_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_S3TC_DXT3_Format: + case RGBA_S3TC_DXT5_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ + case RGB_PVRTC_2BPPV1_Format: + case RGBA_PVRTC_2BPPV1_Format: + return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; + case RGB_PVRTC_4BPPV1_Format: + case RGBA_PVRTC_4BPPV1_Format: + return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ + case RGB_ETC1_Format: + case RGB_ETC2_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_ETC2_EAC_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ + case RGBA_ASTC_4x4_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x4_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x5_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x5_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x6_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x5_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_8x6_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x8_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x5_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_10x6_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_10x8_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x10_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x10_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x12_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ + case RGBA_BPTC_Format: + case RGB_BPTC_SIGNED_Format: + case RGB_BPTC_UNSIGNED_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ + case RED_RGTC1_Format: + case SIGNED_RED_RGTC1_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; + case RED_GREEN_RGTC2_Format: + case SIGNED_RED_GREEN_RGTC2_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + } + + throw new Error( + `Unable to determine texture byte length for ${format} format.`, + ); + +} + +function getTextureTypeByteLength( type ) { + + switch ( type ) { + + case UnsignedByteType: + case ByteType: + return { byteLength: 1, components: 1 }; + case UnsignedShortType: + case ShortType: + case HalfFloatType: + return { byteLength: 2, components: 1 }; + case UnsignedShort4444Type: + case UnsignedShort5551Type: + return { byteLength: 2, components: 4 }; + case UnsignedIntType: + case IntType: + case FloatType: + return { byteLength: 4, components: 1 }; + case UnsignedInt5999Type: + case UnsignedInt101111Type: + return { byteLength: 4, components: 3 }; + + } + + throw new Error( `Unknown texture type ${type}.` ); + +} + function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - const isWebGL2 = capabilities.isWebGL2; const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); @@ -24014,7 +34007,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { + function resizeImage( image, needsNewCanvas, maxSize ) { let scale = 1; @@ -24030,7 +34023,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // only perform resize if necessary - if ( scale < 1 || needsPowerOfTwo === true ) { + if ( scale < 1 ) { // only perform resize for certain image types @@ -24039,10 +34032,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { - const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; - - const width = floor( scale * dimensions.width ); - const height = floor( scale * dimensions.height ); + const width = Math.floor( scale * dimensions.width ); + const height = Math.floor( scale * dimensions.height ); if ( _canvas === undefined ) _canvas = createCanvas( width, height ); @@ -24078,40 +34069,29 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - function isPowerOfTwo$1( image ) { + function textureNeedsGenerateMipmaps( texture ) { - const dimensions = getDimensions( image ); - - return isPowerOfTwo( dimensions.width ) && isPowerOfTwo( dimensions.height ); + return texture.generateMipmaps; } - function textureNeedsPowerOfTwo( texture ) { - - if ( isWebGL2 ) return false; + function generateMipmap( target ) { - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); + _gl.generateMipmap( target ); } - function textureNeedsGenerateMipmaps( texture, supportsMips ) { + function getTargetType( texture ) { - return texture.generateMipmaps && supportsMips && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; - - } - - function generateMipmap( target ) { - - _gl.generateMipmap( target ); + if ( texture.isWebGLCubeRenderTarget ) return _gl.TEXTURE_CUBE_MAP; + if ( texture.isWebGL3DRenderTarget ) return _gl.TEXTURE_3D; + if ( texture.isWebGLArrayRenderTarget || texture.isCompressedArrayTexture ) return _gl.TEXTURE_2D_ARRAY; + return _gl.TEXTURE_2D; } function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - if ( isWebGL2 === false ) return glFormat; - if ( internalFormatName !== null ) { if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; @@ -24160,6 +34140,35 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + if ( glFormat === _gl.RGB_INTEGER ) { + + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGB8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGB16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGB32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGB8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGB16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGB32I; + + } + + if ( glFormat === _gl.RGBA_INTEGER ) { + + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGBA8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGBA16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGBA32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGBA8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGBA16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGBA32I; + + } + + if ( glFormat === _gl.RGB ) { + + if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; + if ( glType === _gl.UNSIGNED_INT_10F_11F_11F_REV ) internalFormat = _gl.R11F_G11F_B10F; + + } + if ( glFormat === _gl.RGBA ) { const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); @@ -24184,43 +34193,71 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - function getMipLevels( texture, image, supportsMips ) { + function getInternalDepthFormat( useStencil, depthType ) { - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { + let glInternalFormat; + if ( useStencil ) { - return Math.log2( Math.max( image.width, image.height ) ) + 1; + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { + glInternalFormat = _gl.DEPTH24_STENCIL8; - // user-defined mipmaps + } else if ( depthType === FloatType ) { - return texture.mipmaps.length; + glInternalFormat = _gl.DEPTH32F_STENCIL8; - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { + } else if ( depthType === UnsignedShortType ) { - return image.mipmaps.length; + glInternalFormat = _gl.DEPTH24_STENCIL8; + console.warn( 'DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.' ); + + } } else { - // texture without mipmaps (only base level) + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - return 1; + glInternalFormat = _gl.DEPTH_COMPONENT24; + + } else if ( depthType === FloatType ) { + + glInternalFormat = _gl.DEPTH_COMPONENT32F; + + } else if ( depthType === UnsignedShortType ) { + + glInternalFormat = _gl.DEPTH_COMPONENT16; + + } } + return glInternalFormat; + } - // Fallback filters for non-power-of-2 textures + function getMipLevels( texture, image ) { - function filterFallback( f ) { + if ( textureNeedsGenerateMipmaps( texture ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { + return Math.log2( Math.max( image.width, image.height ) ) + 1; - return _gl.NEAREST; + } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - } + // user-defined mipmaps + + return texture.mipmaps.length; + + } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { + + return image.mipmaps.length; - return _gl.LINEAR; + } else { + + // texture without mipmaps (only base level) + + return 1; + + } } @@ -24313,6 +34350,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, renderTarget.depthTexture.dispose(); + properties.remove( renderTarget.depthTexture ); + } if ( renderTarget.isWebGLCubeRenderTarget ) { @@ -24441,7 +34480,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( texture.isVideoTexture ) updateVideoTexture( texture ); - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { + if ( texture.isRenderTargetTexture === false && texture.isExternalTexture !== true && texture.version > 0 && textureProperties.__version !== texture.version ) { const image = texture.image; @@ -24460,6 +34499,10 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + } else if ( texture.isExternalTexture ) { + + textureProperties.__webglTexture = texture.sourceTexture ? texture.sourceTexture : null; + } state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); @@ -24470,7 +34513,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const textureProperties = properties.get( texture ); - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { uploadTexture( textureProperties, texture, slot ); return; @@ -24485,7 +34528,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const textureProperties = properties.get( texture ); - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { uploadTexture( textureProperties, texture, slot ); return; @@ -24538,7 +34581,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, [ NotEqualCompare ]: _gl.NOTEQUAL }; - function setTextureParameters( textureType, texture, supportsMips ) { + function setTextureParameters( textureType, texture ) { if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || @@ -24548,48 +34591,18 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - if ( supportsMips ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); + if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - - } else { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); - - } - - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); - - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); - - } + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); } + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); + if ( texture.compareFunction ) { _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); @@ -24601,8 +34614,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( texture.magFilter === NearestFilter ) return; if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 - if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { @@ -24696,6 +34708,115 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + function getRow( index, rowLength, componentStride ) { + + return Math.floor( Math.floor( index / componentStride ) / rowLength ); + + } + + function updateTexture( texture, image, glFormat, glType ) { + + const componentStride = 4; // only RGBA supported + + const updateRanges = texture.updateRanges; + + if ( updateRanges.length === 0 ) { + + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); + + } else { + + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. + + updateRanges.sort( ( a, b ) => a.start - b.start ); + + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; + + for ( let i = 1; i < updateRanges.length; i ++ ) { + + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; + + // Only merge if in the same row and overlapping/adjacent + const previousEnd = previousRange.start + previousRange.count; + const currentRow = getRow( range.start, image.width, componentStride ); + const previousRow = getRow( previousRange.start, image.width, componentStride ); + + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( + range.start <= previousEnd + 1 && + currentRow === previousRow && + getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill + ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); + + } else { + + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; + + } + + + } + + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; + + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); + + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); + + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + + const range = updateRanges[ i ]; + + const pixelStart = Math.floor( range.start / componentStride ); + const pixelCount = Math.ceil( range.count / componentStride ); + + const x = pixelStart % image.width; + const y = Math.floor( pixelStart / image.width ); + + // Assumes update ranges refer to contiguous memory + const width = pixelCount; + const height = 1; + + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y ); + + state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data ); + + } + + texture.clearUpdateRanges(); + + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + + } + + } + function uploadTexture( textureProperties, texture, slot ) { let textureType = _gl.TEXTURE_2D; @@ -24723,99 +34844,27 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; - let image = resizeImage( texture.image, needsPowerOfTwo, false, capabilities.maxTextureSize ); + let image = resizeImage( texture.image, false, capabilities.maxTextureSize ); image = verifyColorSpace( texture, image ); - const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ); + const glFormat = utils.convert( texture.format, texture.colorSpace ); - let glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + const glType = utils.convert( texture.type ); + let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); - setTextureParameters( textureType, texture, supportsMips ); + setTextureParameters( textureType, texture ); let mipmap; const mipmaps = texture.mipmaps; - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true && glInternalFormat !== RGB_ETC1_Format ); + const useTexStorage = ( texture.isVideoTexture !== true ); const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); const dataReady = source.dataReady; - const levels = getMipLevels( texture, image, supportsMips ); + const levels = getMipLevels( texture, image ); if ( texture.isDepthTexture ) { - // populate depth texture with dummy data - - glInternalFormat = _gl.DEPTH_COMPONENT; - - if ( isWebGL2 ) { - - if ( texture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( texture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } else if ( texture.type === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } else { - - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D - - } - - } else { - - if ( texture.type === FloatType ) { - - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); - - } - - } - - // validation checks for WebGL 1 - - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); - - texture.type = UnsignedIntType; - glType = utils.convert( texture.type ); - - } - - } - - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); - - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); - - } - - } + glInternalFormat = getInternalDepthFormat( texture.format === DepthStencilFormat, texture.type ); // @@ -24839,7 +34888,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels - if ( mipmaps.length > 0 && supportsMips ) { + if ( mipmaps.length > 0 ) { if ( useTexStorage && allocateMemory ) { @@ -24881,7 +34930,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( dataReady ) { - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); + updateTexture( texture, image, glFormat, glType ); } @@ -24915,7 +34964,27 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( dataReady ) { - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); + if ( texture.layerUpdates.size > 0 ) { + + const layerByteLength = getByteLength( mipmap.width, mipmap.height, texture.format, texture.type ); + + for ( const layerIndex of texture.layerUpdates ) { + + const layerData = mipmap.data.subarray( + layerIndex * layerByteLength / mipmap.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / mipmap.data.BYTES_PER_ELEMENT + ); + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, layerIndex, mipmap.width, mipmap.height, 1, glFormat, layerData ); + + } + + texture.clearLayerUpdates(); + + } else { + + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); + + } } @@ -25021,7 +35090,27 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( dataReady ) { - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + if ( texture.layerUpdates.size > 0 ) { + + const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type ); + + for ( const layerIndex of texture.layerUpdates ) { + + const layerData = image.data.subarray( + layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT + ); + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData ); + + } + + texture.clearLayerUpdates(); + + } else { + + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + + } } @@ -25086,7 +35175,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels - if ( mipmaps.length > 0 && supportsMips ) { + if ( mipmaps.length > 0 ) { if ( useTexStorage && allocateMemory ) { @@ -25146,7 +35235,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { generateMipmap( textureType ); @@ -25195,7 +35284,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( ! isCompressed && ! isDataTexture ) { - cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, capabilities.maxCubemapSize ); + cubeImage[ i ] = resizeImage( texture.image[ i ], true, capabilities.maxCubemapSize ); } else { @@ -25208,17 +35297,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } const image = cubeImage[ 0 ], - supportsMips = isPowerOfTwo$1( image ) || isWebGL2, glFormat = utils.convert( texture.format, texture.colorSpace ), glType = utils.convert( texture.type ), glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); + const useTexStorage = ( texture.isVideoTexture !== true ); const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); const dataReady = source.dataReady; - let levels = getMipLevels( texture, image, supportsMips ); + let levels = getMipLevels( texture, image ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); let mipmaps; @@ -25383,7 +35471,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { // We assume images for cube map have the same size. generateMipmap( _gl.TEXTURE_CUBE_MAP ); @@ -25409,6 +35497,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); + + textureProperties.__renderTarget = renderTarget; if ( ! renderTargetProperties.__hasExternalTextures ) { @@ -25431,11 +35522,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( useMultisampledRTT( renderTarget ) ) { - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, level ); } @@ -25443,74 +35534,37 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - - let glInternalFormat = ( isWebGL2 === true ) ? _gl.DEPTH_COMPONENT24 : _gl.DEPTH_COMPONENT16; - - if ( isMultisample || useMultisampledRTT( renderTarget ) ) { - - const depthTexture = renderTarget.depthTexture; - - if ( depthTexture && depthTexture.isDepthTexture ) { - - if ( depthTexture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( depthTexture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } - - } - - const samples = getRenderTargetSamples( renderTarget ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + if ( renderTarget.depthBuffer ) { - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + // retrieve the depth attachment types + const depthTexture = renderTarget.depthTexture; + const depthType = depthTexture && depthTexture.isDepthTexture ? depthTexture.type : null; + const glInternalFormat = getInternalDepthFormat( renderTarget.stencilBuffer, depthType ); + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + // set up the attachment const samples = getRenderTargetSamples( renderTarget ); + const isUseMultisampledRTT = useMultisampledRTT( renderTarget ); + if ( isUseMultisampledRTT ) { - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } else if ( useMultisampledRTT( renderTarget ) ) { + } else if ( isMultisample ) { - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else { - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); } - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); } else { @@ -25561,8 +35615,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + const textureProperties = properties.get( renderTarget.depthTexture ); + textureProperties.__renderTarget = renderTarget; + // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || + if ( ! textureProperties.__webglTexture || renderTarget.depthTexture.image.width !== renderTarget.width || renderTarget.depthTexture.image.height !== renderTarget.height ) { @@ -25574,7 +35631,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, setTexture2D( renderTarget.depthTexture, 0 ); - const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; + const webglDepthTexture = textureProperties.__webglTexture; const samples = getRenderTargetSamples( renderTarget ); if ( renderTarget.depthTexture.format === DepthFormat ) { @@ -25615,11 +35672,52 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const renderTargetProperties = properties.get( renderTarget ); const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + // if the bound depth texture has changed + if ( renderTargetProperties.__boundDepthTexture !== renderTarget.depthTexture ) { + + // fire the dispose event to get rid of stored state associated with the previously bound depth buffer + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__depthDisposeCallback ) { + + renderTargetProperties.__depthDisposeCallback(); + + } + + // set up dispose listeners to track when the currently attached buffer is implicitly unbound + if ( depthTexture ) { + + const disposeEvent = () => { + + delete renderTargetProperties.__boundDepthTexture; + delete renderTargetProperties.__depthDisposeCallback; + depthTexture.removeEventListener( 'dispose', disposeEvent ); + + }; + + depthTexture.addEventListener( 'dispose', disposeEvent ); + renderTargetProperties.__depthDisposeCallback = disposeEvent; + + } + + renderTargetProperties.__boundDepthTexture = depthTexture; + + } + if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); + const mipmaps = renderTarget.texture.mipmaps; + + if ( mipmaps && mipmaps.length > 0 ) { + + setupDepthTexture( renderTargetProperties.__webglFramebuffer[ 0 ], renderTarget ); + + } else { + + setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); + + } } else { @@ -25630,16 +35728,52 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, for ( let i = 0; i < 6; i ++ ) { state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); + + if ( renderTargetProperties.__webglDepthbuffer[ i ] === undefined ) { + + renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); + + } else { + + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer[ i ]; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); + + } } } else { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); + const mipmaps = renderTarget.texture.mipmaps; + + if ( mipmaps && mipmaps.length > 0 ) { + + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); + + } else { + + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + + } + + if ( renderTargetProperties.__webglDepthbuffer === undefined ) { + + renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); + + } else { + + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); + + } } @@ -25682,7 +35816,6 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); const isMultipleRenderTargets = ( textures.length > 1 ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; if ( ! isMultipleRenderTargets ) { @@ -25705,7 +35838,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, for ( let i = 0; i < 6; i ++ ) { - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { renderTargetProperties.__webglFramebuffer[ i ] = []; @@ -25725,7 +35858,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else { - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { renderTargetProperties.__webglFramebuffer = []; @@ -25743,31 +35876,23 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( isMultipleRenderTargets ) { - if ( capabilities.drawBuffers ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); + for ( let i = 0, il = textures.length; i < il; i ++ ) { - if ( attachmentProperties.__webglTexture === undefined ) { + const attachmentProperties = properties.get( textures[ i ] ); - attachmentProperties.__webglTexture = _gl.createTexture(); + if ( attachmentProperties.__webglTexture === undefined ) { - info.memory.textures ++; + attachmentProperties.__webglTexture = _gl.createTexture(); - } + info.memory.textures ++; } - } else { - - console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); - } } - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { + if ( ( renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); renderTargetProperties.__webglColorRenderbuffer = []; @@ -25811,11 +35936,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( isCube ) { state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); for ( let i = 0; i < 6; i ++ ) { - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { for ( let level = 0; level < texture.mipmaps.length; level ++ ) { @@ -25831,7 +35956,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { generateMipmap( _gl.TEXTURE_CUBE_MAP ); @@ -25846,13 +35971,21 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const attachment = textures[ i ]; const attachmentProperties = properties.get( attachment ); - state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 ); + let glTextureType = _gl.TEXTURE_2D; + + if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { + + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; + + } + + state.bindTexture( glTextureType, attachmentProperties.__webglTexture ); + setTextureParameters( glTextureType, attachment ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, glTextureType, 0 ); - if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( attachment ) ) { - generateMipmap( _gl.TEXTURE_2D ); + generateMipmap( glTextureType ); } @@ -25866,22 +35999,14 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - if ( isWebGL2 ) { - - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - - } else { - - console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); - - } + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; } state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture, supportsMips ); + setTextureParameters( glTextureType, texture ); - if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { for ( let level = 0; level < texture.mipmaps.length; level ++ ) { @@ -25895,7 +36020,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { generateMipmap( glTextureType ); @@ -25917,21 +36042,19 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function updateRenderTargetMipmap( renderTarget ) { - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; - const textures = renderTarget.textures; for ( let i = 0, il = textures.length; i < il; i ++ ) { const texture = textures[ i ]; - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { - const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; + const targetType = getTargetType( renderTarget ); const webglTexture = properties.get( texture ).__webglTexture; - state.bindTexture( target, webglTexture ); - generateMipmap( target ); + state.bindTexture( targetType, webglTexture ); + generateMipmap( targetType ); state.unbindTexture(); } @@ -25940,109 +36063,131 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + const invalidationArrayRead = []; + const invalidationArrayDraw = []; + function updateMultisampleRenderTarget( renderTarget ) { - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { + if ( renderTarget.samples > 0 ) { - const textures = renderTarget.textures; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const invalidationArray = []; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( textures.length > 1 ); + if ( useMultisampledRTT( renderTarget ) === false ) { - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { + const textures = renderTarget.textures; + const width = renderTarget.width; + const height = renderTarget.height; + let mask = _gl.COLOR_BUFFER_BIT; + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderTargetProperties = properties.get( renderTarget ); + const isMultipleRenderTargets = ( textures.length > 1 ); - for ( let i = 0; i < textures.length; i ++ ) { + // If MRT we need to remove FBO attachments + if ( isMultipleRenderTargets ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); + for ( let i = 0; i < textures.length; i ++ ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); + + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); + + } } - } + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + const mipmaps = renderTarget.texture.mipmaps; - for ( let i = 0; i < textures.length; i ++ ) { + if ( mipmaps && mipmaps.length > 0 ) { - invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - if ( renderTarget.depthBuffer ) { + } else { - invalidationArray.push( depthStyle ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); } - const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; + for ( let i = 0; i < textures.length; i ++ ) { - if ( ignoreDepthValues === false ) { + if ( renderTarget.resolveDepthBuffer ) { - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; + if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - } + // resolving stencil is slow with a D3D backend. disable it for all transmission render targets (see #27799) - if ( isMultipleRenderTargets ) { + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + } - } + if ( isMultipleRenderTargets ) { - if ( ignoreDepthValues === true ) { + _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); + const webglTexture = properties.get( textures[ i ] ).__webglTexture; + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - } + } - if ( isMultipleRenderTargets ) { + _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); + if ( supportsInvalidateFramebuffer === true ) { - } + invalidationArrayRead.length = 0; + invalidationArrayDraw.length = 0; + + invalidationArrayRead.push( _gl.COLOR_ATTACHMENT0 + i ); + + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false ) { - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); + invalidationArrayRead.push( depthStyle ); + invalidationArrayDraw.push( depthStyle ); - if ( supportsInvalidateFramebuffer ) { + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, invalidationArrayDraw ); - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); + } + + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArrayRead ); + + } } + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - } + // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments + if ( isMultipleRenderTargets ) { - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); + for ( let i = 0; i < textures.length; i ++ ) { - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - for ( let i = 0; i < textures.length; i ++ ) { + const webglTexture = properties.get( textures[ i ] ).__webglTexture; - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - const webglTexture = properties.get( textures[ i ] ).__webglTexture; + } - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); + } + + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + + } else { + + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false && supportsInvalidateFramebuffer ) { + + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); } } - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - } } @@ -26057,7 +36202,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const renderTargetProperties = properties.get( renderTarget ); - return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; + return renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; } @@ -26082,7 +36227,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const format = texture.format; const type = texture.type; - if ( texture.isCompressedTexture === true || texture.isVideoTexture === true || texture.format === _SRGBAFormat ) return image; + if ( texture.isCompressedTexture === true || texture.isVideoTexture === true ) return image; if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { @@ -26090,36 +36235,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { - if ( isWebGL2 === false ) { - - // in WebGL 1, try to use EXT_sRGB extension and unsized formats - - if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { - - texture.format = _SRGBAFormat; - - // it's not possible to generate mips in WebGL 1 with this extension - - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; - - } else { - - // slow fallback (CPU decode) - - image = ImageUtils.sRGBToLinear( image ); + // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - } - - } else { - - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - - if ( format !== RGBAFormat || type !== UnsignedByteType ) { + if ( format !== RGBAFormat || type !== UnsignedByteType ) { - console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - - } + console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); } @@ -26179,9 +36299,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } -function WebGLUtils( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; +function WebGLUtils( gl, extensions ) { function convert( p, colorSpace = NoColorSpace ) { @@ -26192,6 +36310,8 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; + if ( p === UnsignedInt101111Type ) return gl.UNSIGNED_INT_10F_11F_11F_REV; if ( p === ByteType ) return gl.BYTE; if ( p === ShortType ) return gl.SHORT; @@ -26199,50 +36319,14 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( p === IntType ) return gl.INT; if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; if ( p === FloatType ) return gl.FLOAT; - - if ( p === HalfFloatType ) { - - if ( isWebGL2 ) return gl.HALF_FLOAT; - - extension = extensions.get( 'OES_texture_half_float' ); - - if ( extension !== null ) { - - return extension.HALF_FLOAT_OES; - - } else { - - return null; - - } - - } + if ( p === HalfFloatType ) return gl.HALF_FLOAT; if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - // WebGL 1 sRGB fallback - - if ( p === _SRGBAFormat ) { - - extension = extensions.get( 'EXT_sRGB' ); - - if ( extension !== null ) { - - return extension.SRGB_ALPHA_EXT; - - } else { - - return null; - - } - - } - // WebGL2 formats. if ( p === RedFormat ) return gl.RED; @@ -26314,33 +36398,15 @@ function WebGLUtils( gl, extensions, capabilities ) { } - // ETC1 - - if ( p === RGB_ETC1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); - - if ( extension !== null ) { - - return extension.COMPRESSED_RGB_ETC1_WEBGL; - - } else { - - return null; - - } - - } - - // ETC2 + // ETC - if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { extension = extensions.get( 'WEBGL_compressed_texture_etc' ); if ( extension !== null ) { - if ( p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; } else { @@ -26414,7 +36480,7 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( extension !== null ) { - if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === RED_RGTC1_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; @@ -26429,23 +36495,7 @@ function WebGLUtils( gl, extensions, capabilities ) { // - if ( p === UnsignedInt248Type ) { - - if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; - - extension = extensions.get( 'WEBGL_depth_texture' ); - - if ( extension !== null ) { - - return extension.UNSIGNED_INT_24_8_WEBGL; - - } else { - - return null; - - } - - } + if ( p === UnsignedInt248Type ) return gl.UNSIGNED_INT_24_8; // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) @@ -26457,26 +36507,87 @@ function WebGLUtils( gl, extensions, capabilities ) { } +/** + * This type of camera can be used in order to efficiently render a scene with a + * predefined set of cameras. This is an important performance aspect for + * rendering VR scenes. + * + * An instance of `ArrayCamera` always has an array of sub cameras. It's mandatory + * to define for each sub camera the `viewport` property which determines the + * part of the viewport that is rendered with this camera. + * + * @augments PerspectiveCamera + */ class ArrayCamera extends PerspectiveCamera { + /** + * Constructs a new array camera. + * + * @param {Array<PerspectiveCamera>} [array=[]] - An array of perspective sub cameras. + */ constructor( array = [] ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isArrayCamera = true; + /** + * Whether this camera is used with multiview rendering or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isMultiViewCamera = false; + + /** + * An array of perspective sub cameras. + * + * @type {Array<PerspectiveCamera>} + */ this.cameras = array; } } +/** + * This is almost identical to an {@link Object3D}. Its purpose is to + * make working with groups of objects syntactically clearer. + * + * ```js + * // Create a group and add the two cubes. + * // These cubes can now be rotated / scaled etc as a group. + * const group = new THREE.Group(); + * + * group.add( meshA ); + * group.add( meshB ); + * + * scene.add( group ); + * ``` + * + * @augments Object3D + */ class Group extends Object3D { constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isGroup = true; this.type = 'Group'; @@ -26487,16 +36598,56 @@ class Group extends Object3D { const _moveEvent = { type: 'move' }; +/** + * Class for representing a XR controller with its + * different coordinate systems. + * + * @private + */ class WebXRController { + /** + * Constructs a new XR controller. + */ constructor() { + /** + * A group representing the target ray space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ this._targetRay = null; + + /** + * A group representing the grip space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ this._grip = null; + + /** + * A group representing the hand space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ this._hand = null; } + /** + * Returns a group representing the hand space of the XR controller. + * + * @return {Group} A group representing the hand space of the XR controller. + */ getHandSpace() { if ( this._hand === null ) { @@ -26514,6 +36665,11 @@ class WebXRController { } + /** + * Returns a group representing the target ray space of the XR controller. + * + * @return {Group} A group representing the target ray space of the XR controller. + */ getTargetRaySpace() { if ( this._targetRay === null ) { @@ -26532,6 +36688,11 @@ class WebXRController { } + /** + * Returns a group representing the grip space of the XR controller. + * + * @return {Group} A group representing the grip space of the XR controller. + */ getGripSpace() { if ( this._grip === null ) { @@ -26550,6 +36711,13 @@ class WebXRController { } + /** + * Dispatches the given event to the groups representing + * the different coordinate spaces of the XR controller. + * + * @param {Object} event - The event to dispatch. + * @return {WebXRController} A reference to this instance. + */ dispatchEvent( event ) { if ( this._targetRay !== null ) { @@ -26574,6 +36742,12 @@ class WebXRController { } + /** + * Connects the controller with the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ connect( inputSource ) { if ( inputSource && inputSource.hand ) { @@ -26599,6 +36773,12 @@ class WebXRController { } + /** + * Disconnects the controller from the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ disconnect( inputSource ) { this.dispatchEvent( { type: 'disconnected', data: inputSource } ); @@ -26625,6 +36805,16 @@ class WebXRController { } + /** + * Updates the controller with the given input source, XR frame and reference space. + * This updates the transformations of the groups that represent the different + * coordinate systems of the controller. + * + * @param {XRInputSource} inputSource - The input source. + * @param {XRFrame} frame - The XR frame. + * @param {XRReferenceSpace} referenceSpace - The reference space. + * @return {WebXRController} A reference to this instance. + */ update( inputSource, frame, referenceSpace ) { let inputPose = null; @@ -26802,8 +36992,14 @@ class WebXRController { } - // private method - + /** + * Returns a group representing the hand joint for the given input joint. + * + * @private + * @param {Group} hand - The group representing the hand space. + * @param {XRJointSpace} inputjoint - The hand joint data. + * @return {Group} A group representing the hand joint for the given input joint. + */ _getHandJoint( hand, inputjoint ) { if ( hand.joints[ inputjoint.jointName ] === undefined ) { @@ -26823,6 +37019,59 @@ class WebXRController { } +/** + * Represents a texture created externally with the same renderer context. + * + * This may be a texture from a protected media stream, device camera feed, + * or other data feeds like a depth sensor. + * + * Note that this class is only supported in {@link WebGLRenderer}, and in + * the {@link WebGPURenderer} WebGPU backend. + * + * @augments Texture + */ +class ExternalTexture extends Texture { + + /** + * Creates a new raw texture. + * + * @param {?(WebGLTexture|GPUTexture)} [sourceTexture=null] - The external texture. + */ + constructor( sourceTexture = null ) { + + super(); + + /** + * The external source texture. + * + * @type {?(WebGLTexture|GPUTexture)} + * @default null + */ + this.sourceTexture = sourceTexture; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isExternalTexture = true; + + } + + copy( source ) { + + super.copy( source ); + + this.sourceTexture = source.sourceTexture; + + return this; + + } + +} + const _occlusion_vertex = ` void main() { @@ -26841,38 +37090,69 @@ void main() { if ( coord.x >= 1.0 ) { - gl_FragDepthEXT = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; + gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; } else { - gl_FragDepthEXT = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; + gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; } }`; +/** + * A XR module that manages the access to the Depth Sensing API. + */ class WebXRDepthSensing { + /** + * Constructs a new depth sensing module. + */ constructor() { + /** + * An opaque texture representing the depth of the user's environment. + * + * @type {?ExternalTexture} + */ this.texture = null; + + /** + * A plane mesh for visualizing the depth texture. + * + * @type {?Mesh} + */ this.mesh = null; + /** + * The depth near value. + * + * @type {number} + */ this.depthNear = 0; + + /** + * The depth near far. + * + * @type {number} + */ this.depthFar = 0; } - init( renderer, depthData, renderState ) { + /** + * Inits the depth sensing module + * + * @param {XRWebGLDepthInformation} depthData - The XR depth data. + * @param {XRRenderState} renderState - The XR render state. + */ + init( depthData, renderState ) { if ( this.texture === null ) { - const texture = new Texture(); - - const texProps = renderer.properties.get( texture ); - texProps.__webglTexture = depthData.texture; + const texture = new ExternalTexture( depthData.texture ); - if ( ( depthData.depthNear != renderState.depthNear ) || ( depthData.depthFar != renderState.depthFar ) ) { + if ( ( depthData.depthNear !== renderState.depthNear ) || ( depthData.depthFar !== renderState.depthFar ) ) { this.depthNear = depthData.depthNear; this.depthFar = depthData.depthFar; @@ -26885,7 +37165,13 @@ class WebXRDepthSensing { } - render( renderer, cameraXR ) { + /** + * Returns a plane mesh that visualizes the depth texture. + * + * @param {ArrayCamera} cameraXR - The XR camera. + * @return {?Mesh} The plane mesh. + */ + getMesh( cameraXR ) { if ( this.texture !== null ) { @@ -26893,7 +37179,6 @@ class WebXRDepthSensing { const viewport = cameraXR.cameras[ 0 ].viewport; const material = new ShaderMaterial( { - extensions: { fragDepth: true }, vertexShader: _occlusion_vertex, fragmentShader: _occlusion_fragment, uniforms: { @@ -26907,12 +37192,15 @@ class WebXRDepthSensing { } - renderer.render( this.mesh, cameraXR ); - } + return this.mesh; + } + /** + * Resets the module + */ reset() { this.texture = null; @@ -26920,10 +37208,36 @@ class WebXRDepthSensing { } + /** + * Returns a texture representing the depth of the user's environment. + * + * @return {?ExternalTexture} The depth texture. + */ + getDepthTexture() { + + return this.texture; + + } + } +/** + * This class represents an abstraction of the WebXR Device API and is + * internally used by {@link WebGLRenderer}. `WebXRManager` also provides a public + * interface that allows users to enable/disable XR and perform XR related + * tasks like for instance retrieving controllers. + * + * @augments EventDispatcher + * @hideconstructor + */ class WebXRManager extends EventDispatcher { + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGL2RenderingContext} gl - The rendering context. + */ constructor( renderer, gl ) { super(); @@ -26946,7 +37260,10 @@ class WebXRManager extends EventDispatcher { let glBaseLayer = null; let xrFrame = null; + const supportsGlBinding = typeof XRWebGLBinding !== 'undefined'; + const depthSensing = new WebXRDepthSensing(); + const cameraAccessTextures = {}; const attributes = gl.getContextAttributes(); let initialRenderTarget = null; @@ -26961,29 +37278,54 @@ class WebXRManager extends EventDispatcher { // const cameraL = new PerspectiveCamera(); - cameraL.layers.enable( 1 ); cameraL.viewport = new Vector4(); const cameraR = new PerspectiveCamera(); - cameraR.layers.enable( 2 ); cameraR.viewport = new Vector4(); const cameras = [ cameraL, cameraR ]; const cameraXR = new ArrayCamera(); - cameraXR.layers.enable( 1 ); - cameraXR.layers.enable( 2 ); let _currentDepthNear = null; let _currentDepthFar = null; // + /** + * Whether the manager's XR camera should be automatically updated or not. + * + * @type {boolean} + * @default true + */ this.cameraAutoUpdate = true; + + /** + * This flag notifies the renderer to be ready for XR rendering. Set it to `true` + * if you are going to use XR in your app. + * + * @type {boolean} + * @default false + */ this.enabled = false; + /** + * Whether XR presentation is active or not. + * + * @type {boolean} + * @readonly + * @default false + */ this.isPresenting = false; + /** + * Returns a group representing the `target ray` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `target ray` space. + */ this.getController = function ( index ) { let controller = controllers[ index ]; @@ -26999,6 +37341,21 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns a group representing the `grip` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * Note: If you want to show something in the user's hand AND offer a + * pointing ray at the same time, you'll want to attached the handheld object + * to the group returned by `getControllerGrip()` and the ray to the + * group returned by `getController()`. The idea is to have two + * different groups in two different coordinate spaces for the same WebXR + * controller. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `grip` space. + */ this.getControllerGrip = function ( index ) { let controller = controllers[ index ]; @@ -27014,6 +37371,14 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns a group representing the `hand` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `hand` space. + */ this.getHand = function ( index ) { let controller = controllers[ index ]; @@ -27035,7 +37400,7 @@ class WebXRManager extends EventDispatcher { const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - if ( controllerIndex === - 1 ) { + if ( controllerIndex === -1 ) { return; @@ -27079,6 +37444,11 @@ class WebXRManager extends EventDispatcher { _currentDepthFar = null; depthSensing.reset(); + for ( const key in cameraAccessTextures ) { + + delete cameraAccessTextures[ key ]; + + } // restore framebuffer/rendering state @@ -27103,6 +37473,13 @@ class WebXRManager extends EventDispatcher { } + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} value - The framebuffer scale factor. + */ this.setFramebufferScaleFactor = function ( value ) { framebufferScaleFactor = value; @@ -27115,6 +37492,16 @@ class WebXRManager extends EventDispatcher { }; + /** + * Sets the reference space type. Can be used to configure a spatial relationship with the user's physical + * environment. Depending on how the user moves in 3D space, setting an appropriate reference space can + * improve tracking. Default is `local-floor`. Valid values can be found here + * https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace#reference_space_types. + * + * This method can not be used during a XR session. + * + * @param {string} value - The reference space type. + */ this.setReferenceSpaceType = function ( value ) { referenceSpaceType = value; @@ -27127,42 +37514,93 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ this.getReferenceSpace = function () { return customReferenceSpace || referenceSpace; }; + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ this.setReferenceSpace = function ( space ) { customReferenceSpace = space; }; + /** + * Returns the current base layer. + * + * This is an `XRProjectionLayer` when the targeted XR device supports the + * WebXR Layers API, or an `XRWebGLLayer` otherwise. + * + * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. + */ this.getBaseLayer = function () { return glProjLayer !== null ? glProjLayer : glBaseLayer; }; + /** + * Returns the current XR binding. + * + * Creates a new binding if needed and the browser is + * capable of doing so. + * + * @return {?XRWebGLBinding} The XR binding. Returns `null` if one cannot be created. + */ this.getBinding = function () { + if ( glBinding === null && supportsGlBinding ) { + + glBinding = new XRWebGLBinding( session, gl ); + + } + return glBinding; }; + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ this.getFrame = function () { return xrFrame; }; + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ this.getSession = function () { return session; }; + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} value - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ this.setSession = async function ( value ) { session = value; @@ -27189,10 +37627,15 @@ class WebXRManager extends EventDispatcher { currentPixelRatio = renderer.getPixelRatio(); renderer.getSize( currentSize ); - if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { + + // Check that the browser implements the necessary APIs to use an + // XRProjectionLayer rather than an XRWebGLLayer + const supportsLayers = supportsGlBinding && 'createProjectionLayer' in XRWebGLBinding.prototype; + + if ( ! supportsLayers ) { const layerInit = { - antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, + antialias: attributes.antialias, alpha: true, depth: attributes.depth, stencil: attributes.stencil, @@ -27213,7 +37656,10 @@ class WebXRManager extends EventDispatcher { format: RGBAFormat, type: UnsignedByteType, colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil + stencilBuffer: attributes.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ) + } ); @@ -27237,7 +37683,7 @@ class WebXRManager extends EventDispatcher { scaleFactor: framebufferScaleFactor }; - glBinding = new XRWebGLBinding( session, gl ); + glBinding = this.getBinding(); glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); @@ -27255,12 +37701,11 @@ class WebXRManager extends EventDispatcher { depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), stencilBuffer: attributes.stencil, colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ) } ); - const renderTargetProperties = renderer.properties.get( newRenderTarget ); - renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; - } newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 @@ -27281,6 +37726,11 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ this.getEnvironmentBlendMode = function () { if ( session !== null ) { @@ -27291,6 +37741,19 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns the current depth texture computed via depth sensing. + * + * See {@link WebXRDepthSensing#getDepthTexture}. + * + * @return {?Texture} The depth texture. + */ + this.getDepthTexture = function () { + + return depthSensing.getDepthTexture(); + + }; + function onInputSourcesChange( event ) { // Notify disconnected @@ -27317,7 +37780,7 @@ class WebXRManager extends EventDispatcher { let controllerIndex = controllerInputSources.indexOf( inputSource ); - if ( controllerIndex === - 1 ) { + if ( controllerIndex === -1 ) { // Assign input source a controller that currently has no input source @@ -27341,7 +37804,7 @@ class WebXRManager extends EventDispatcher { // If all controllers do currently receive input we ignore new ones - if ( controllerIndex === - 1 ) break; + if ( controllerIndex === -1 ) break; } @@ -27367,6 +37830,10 @@ class WebXRManager extends EventDispatcher { * the cameras' projection and world matrices have already been set. * And that near and far planes are identical for both cameras. * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. */ function setProjectionFromUnion( camera, cameraL, cameraR ) { @@ -27403,18 +37870,31 @@ class WebXRManager extends EventDispatcher { camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); + + } else { + + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; + + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + + } } @@ -27434,19 +37914,31 @@ class WebXRManager extends EventDispatcher { } + /** + * Updates the state of the XR camera. Use this method on app level if you + * set `cameraAutoUpdate` to `false`. The method requires the non-XR + * camera of the scene as a parameter. The passed in camera's transformation + * is automatically adjusted to the position of the XR camera when calling + * this method. + * + * @param {Camera} camera - The camera. + */ this.updateCamera = function ( camera ) { if ( session === null ) return; + let depthNear = camera.near; + let depthFar = camera.far; + if ( depthSensing.texture !== null ) { - camera.near = depthSensing.depthNear; - camera.far = depthSensing.depthFar; + if ( depthSensing.depthNear > 0 ) depthNear = depthSensing.depthNear; + if ( depthSensing.depthFar > 0 ) depthFar = depthSensing.depthFar; } - cameraXR.near = cameraR.near = cameraL.near = camera.near; - cameraXR.far = cameraR.far = cameraL.far = camera.far; + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { @@ -27460,17 +37952,13 @@ class WebXRManager extends EventDispatcher { _currentDepthNear = cameraXR.near; _currentDepthFar = cameraXR.far; - cameraL.near = _currentDepthNear; - cameraL.far = _currentDepthFar; - cameraR.near = _currentDepthNear; - cameraR.far = _currentDepthFar; - - cameraL.updateProjectionMatrix(); - cameraR.updateProjectionMatrix(); - camera.updateProjectionMatrix(); - } + // inherit camera layers and enable eye layers (1 = left, 2 = right) + cameraXR.layers.mask = camera.layers.mask | 0b110; + cameraL.layers.mask = cameraXR.layers.mask & 0b011; + cameraR.layers.mask = cameraXR.layers.mask & 0b101; + const parent = camera.parent; const cameras = cameraXR.cameras; @@ -27531,12 +38019,27 @@ class WebXRManager extends EventDispatcher { } + /** + * Returns an instance of {@link ArrayCamera} which represents the XR camera + * of the active XR session. For each view it holds a separate camera object. + * + * The camera's `fov` is currently not used and does not reflect the fov of + * the XR camera. If you need the fov on app level, you have to compute in + * manually from the XR camera's projection matrices. + * + * @return {ArrayCamera} The XR camera. + */ this.getCamera = function () { return cameraXR; }; + /** + * Returns the amount of foveation used by the XR compositor for the projection layer. + * + * @return {number|undefined} The amount of foveation. + */ this.getFoveation = function () { if ( glProjLayer === null && glBaseLayer === null ) { @@ -27549,6 +38052,12 @@ class WebXRManager extends EventDispatcher { }; + /** + * Sets the foveation value. + * + * @param {number} value - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ this.setFoveation = function ( value ) { // 0 = no foveation = full resolution @@ -27570,12 +38079,43 @@ class WebXRManager extends EventDispatcher { }; + /** + * Returns `true` if depth sensing is supported. + * + * @return {boolean} Whether depth sensing is supported or not. + */ this.hasDepthSensing = function () { return depthSensing.texture !== null; }; + /** + * Returns the depth sensing mesh. + * + * See {@link WebXRDepthSensing#getMesh}. + * + * @return {Mesh} The depth sensing mesh. + */ + this.getDepthSensingMesh = function () { + + return depthSensing.getMesh( cameraXR ); + + }; + + /** + * Retrieves an opaque texture from the view-aligned {@link XRCamera}. + * Only available during the current animation loop. + * + * @param {XRCamera} xrCamera - The camera to query. + * @return {?Texture} An opaque texture representing the current raw camera frame. + */ + this.getCameraTexture = function ( xrCamera ) { + + return cameraAccessTextures[ xrCamera ]; + + }; + // Animation Loop let onAnimationFrameCallback = null; @@ -27628,7 +38168,7 @@ class WebXRManager extends EventDispatcher { renderer.setRenderTargetTextures( newRenderTarget, glSubImage.colorTexture, - glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); + glSubImage.depthStencilTexture ); renderer.setRenderTarget( newRenderTarget ); @@ -27671,14 +38211,52 @@ class WebXRManager extends EventDispatcher { // const enabledFeatures = session.enabledFeatures; + const gpuDepthSensingEnabled = enabledFeatures && + enabledFeatures.includes( 'depth-sensing' ) && + session.depthUsage == 'gpu-optimized'; - if ( enabledFeatures && enabledFeatures.includes( 'depth-sensing' ) ) { + if ( gpuDepthSensingEnabled && supportsGlBinding ) { + + glBinding = scope.getBinding(); const depthData = glBinding.getDepthInformation( views[ 0 ] ); if ( depthData && depthData.isValid && depthData.texture ) { - depthSensing.init( renderer, depthData, session.renderState ); + depthSensing.init( depthData, session.renderState ); + + } + + } + + const cameraAccessEnabled = enabledFeatures && + enabledFeatures.includes( 'camera-access' ); + + if ( cameraAccessEnabled && supportsGlBinding ) { + + renderer.state.unbindTexture(); + + glBinding = scope.getBinding(); + + for ( let i = 0; i < views.length; i ++ ) { + + const camera = views[ i ].camera; + + if ( camera ) { + + let cameraTex = cameraAccessTextures[ camera ]; + + if ( ! cameraTex ) { + + cameraTex = new ExternalTexture(); + cameraAccessTextures[ camera ] = cameraTex; + + } + + const glTexture = glBinding.getCameraImage( camera ); + cameraTex.sourceTexture = glTexture; + + } } @@ -27701,8 +38279,6 @@ class WebXRManager extends EventDispatcher { } - depthSensing.render( renderer, cameraXR ); - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); if ( frame.detectedPlanes ) { @@ -27887,7 +38463,7 @@ function WebGLMaterials( renderer, properties ) { if ( material.side === BackSide ) { - uniforms.bumpScale.value *= - 1; + uniforms.bumpScale.value *= -1; } @@ -27954,19 +38530,19 @@ function WebGLMaterials( renderer, properties ) { _e1.copy( envMapRotation ); // accommodate left-handed frame - _e1.x *= - 1; _e1.y *= - 1; _e1.z *= - 1; + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { // environment maps which are not cube render targets or PMREMs follow a different convention - _e1.y *= - 1; - _e1.z *= - 1; + _e1.y *= -1; + _e1.z *= -1; } uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( _e1 ) ); - uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; uniforms.reflectivity.value = material.reflectivity; uniforms.ior.value = material.ior; @@ -27977,11 +38553,7 @@ function WebGLMaterials( renderer, properties ) { if ( material.lightMap ) { uniforms.lightMap.value = material.lightMap; - - // artist-friendly light intensity scaling factor - const scaleFactor = ( renderer._useLegacyLights === true ) ? Math.PI : 1; - - uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; + uniforms.lightMapIntensity.value = material.lightMapIntensity; refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); @@ -28121,11 +38693,10 @@ function WebGLMaterials( renderer, properties ) { } - const envMap = properties.get( material ).envMap; - - if ( envMap ) { + if ( material.envMap ) { //uniforms.envMap.value = material.envMap; // part of uniforms common + uniforms.envMapIntensity.value = material.envMapIntensity; } @@ -28199,6 +38770,12 @@ function WebGLMaterials( renderer, properties ) { } + if ( material.dispersion > 0 ) { + + uniforms.dispersion.value = material.dispersion; + + } + if ( material.iridescence > 0 ) { uniforms.iridescence.value = material.iridescence; @@ -28321,7 +38898,7 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { let updateList = {}; let allocatedBindingPoints = []; - const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program + const maxBindingPoints = gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ); // binding points are global whereas block indices are per shader program function bind( uniformsGroup, program ) { @@ -28388,7 +38965,7 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { for ( let i = 0; i < maxBindingPoints; i ++ ) { - if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { + if ( allocatedBindingPoints.indexOf( i ) === -1 ) { allocatedBindingPoints.push( i ); return i; @@ -28557,27 +39134,27 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { const info = getUniformSize( value ); - // Calculate the chunk offset - const chunkOffsetUniform = offset % chunkSize; + const chunkOffset = offset % chunkSize; // offset in the current chunk + const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data + + offset += chunkPadding; // Check for chunk overflow - if ( chunkOffsetUniform !== 0 && ( chunkSize - chunkOffsetUniform ) < info.boundary ) { + if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) { // Add padding and adjust offset - offset += ( chunkSize - chunkOffsetUniform ); + offset += ( chunkSize - chunkStart ); } // the following two properties will be used for partial buffer updates - uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); uniform.__offset = offset; - // Update the global offset offset += info.storage; - } } @@ -28705,29 +39282,53 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) { } +/** + * This renderer uses WebGL 2 to display scenes. + * + * WebGL 1 is not supported since `r163`. + */ class WebGLRenderer { + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer~Options} [parameters] - The configuration parameter. + */ constructor( parameters = {} ) { const { canvas = createCanvasElement(), context = null, depth = true, - stencil = true, + stencil = false, alpha = false, antialias = false, premultipliedAlpha = true, preserveDrawingBuffer = false, powerPreference = 'default', failIfMajorPerformanceCaveat = false, + reversedDepthBuffer = false, } = parameters; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWebGLRenderer = true; let _alpha; if ( context !== null ) { + if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { + + throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); + + } + _alpha = context.getContextAttributes().alpha; } else { @@ -28750,13 +39351,36 @@ class WebGLRenderer { // public properties + /** + * A canvas where the renderer draws its output.This is automatically created by the renderer + * in the constructor (if not provided already); you just need to add it to your page like so: + * ```js + * document.body.appendChild( renderer.domElement ); + * ``` + * + * @type {DOMElement} + */ this.domElement = canvas; - // Debug configuration container + /** + * A object with debug configuration settings. + * + * - `checkShaderErrors`: If it is `true`, defines whether material shader programs are + * checked for errors during compilation and linkage process. It may be useful to disable + * this check in production for performance gain. It is strongly recommended to keep these + * checks enabled during development. If the shader does not compile and link - it will not + * work and associated material will not render. + * - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that + * can be used for custom error reporting. The callback receives the WebGL context, an instance + * of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader. + * Assigning a custom function disables the default error reporting. + * + * @type {Object} + */ this.debug = { /** - * Enables error checking and reporting when shader programs are being compiled + * Enables error checking and reporting when shader programs are being compiled. * @type {boolean} */ checkShaderErrors: true, @@ -28769,33 +39393,105 @@ class WebGLRenderer { // clearing + /** + * Whether the renderer should automatically clear its output before rendering a frame or not. + * + * @type {boolean} + * @default true + */ this.autoClear = true; + + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the color buffer or not. + * + * @type {boolean} + * @default true + */ this.autoClearColor = true; + + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the depth buffer or not. + * + * @type {boolean} + * @default true + */ this.autoClearDepth = true; + + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the stencil buffer or not. + * + * @type {boolean} + * @default true + */ this.autoClearStencil = true; // scene graph + /** + * Whether the renderer should sort objects or not. + * + * Note: Sorting is used to attempt to properly render objects that have some + * degree of transparency. By definition, sorting objects may not work in all + * cases. Depending on the needs of application, it may be necessary to turn + * off sorting and use other methods to deal with transparency rendering e.g. + * manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ this.sortObjects = true; // user-defined clipping + /** + * User-defined clipping planes specified in world space. These planes apply globally. + * Points in space whose dot product with the plane is negative are cut away. + * + * @type {Array<Plane>} + */ this.clippingPlanes = []; - this.localClippingEnabled = false; - - // physically based shading - - this._outputColorSpace = SRGBColorSpace; - // physical lights - - this._useLegacyLights = false; + /** + * Whether the renderer respects object-level clipping planes or not. + * + * @type {boolean} + * @default false + */ + this.localClippingEnabled = false; // tone mapping + /** + * The tone mapping technique of the renderer. + * + * @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)} + * @default NoToneMapping + */ this.toneMapping = NoToneMapping; + + /** + * Exposure level of tone mapping. + * + * @type {number} + * @default 1 + */ this.toneMappingExposure = 1.0; + // transmission + + /** + * The normalized resolution scale for the transmission render target, measured in percentage + * of viewport dimensions. Lowering this value can result in significant performance improvements + * when using {@link MeshPhysicalMaterial#transmission}. + * + * @type {number} + * @default 1 + */ + this.transmissionResolutionScale = 1.0; + // internal properties const _this = this; @@ -28804,10 +39500,12 @@ class WebGLRenderer { // internal state cache + this._outputColorSpace = SRGBColorSpace; + let _currentActiveCubeFace = 0; let _currentActiveMipmapLevel = 0; let _currentRenderTarget = null; - let _currentMaterialId = - 1; + let _currentMaterialId = -1; let _currentCamera = null; @@ -28840,19 +39538,18 @@ class WebGLRenderer { let _clippingEnabled = false; let _localClippingEnabled = false; - // transmission - - let _transmissionRenderTarget = null; - // camera matrices cache const _projScreenMatrix = new Matrix4(); - const _vector2 = new Vector2(); const _vector3 = new Vector3(); + const _vector4 = new Vector4(); + const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; + let _renderBackground = false; + function getTargetPixelRatio() { return _currentRenderTarget === null ? _pixelRatio : 1; @@ -28863,17 +39560,9 @@ class WebGLRenderer { let _gl = context; - function getContext( contextNames, contextAttributes ) { + function getContext( contextName, contextAttributes ) { - for ( let i = 0; i < contextNames.length; i ++ ) { - - const contextName = contextNames[ i ]; - const context = canvas.getContext( contextName, contextAttributes ); - if ( context !== null ) return context; - - } - - return null; + return canvas.getContext( contextName, contextAttributes ); } @@ -28900,19 +39589,13 @@ class WebGLRenderer { if ( _gl === null ) { - const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; - - if ( _this.isWebGL1Renderer === true ) { - - contextNames.shift(); - - } + const contextName = 'webgl2'; - _gl = getContext( contextNames, contextAttributes ); + _gl = getContext( contextName, contextAttributes ); if ( _gl === null ) { - if ( getContext( contextNames ) ) { + if ( getContext( contextName ) ) { throw new Error( 'Error creating WebGL context with your selected attributes.' ); @@ -28926,24 +39609,6 @@ class WebGLRenderer { } - if ( typeof WebGLRenderingContext !== 'undefined' && _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 - - console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); - - } - - // Some experimental-webgl implementations do not have getShaderPrecisionFormat - - if ( _gl.getShaderPrecisionFormat === undefined ) { - - _gl.getShaderPrecisionFormat = function () { - - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; - - }; - - } - } catch ( error ) { console.error( 'THREE.WebGLRenderer: ' + error.message ); @@ -28962,22 +39627,27 @@ class WebGLRenderer { function initGLContext() { extensions = new WebGLExtensions( _gl ); + extensions.init(); - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); + utils = new WebGLUtils( _gl, extensions ); - extensions.init( capabilities ); + capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); - utils = new WebGLUtils( _gl, extensions, capabilities ); + state = new WebGLState( _gl, extensions ); - state = new WebGLState( _gl, extensions, capabilities ); + if ( capabilities.reversedDepthBuffer && reversedDepthBuffer ) { + + state.buffers.depth.setReversed( true ); + + } info = new WebGLInfo( _gl ); properties = new WebGLProperties(); textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); cubemaps = new WebGLCubeMaps( _this ); cubeuvmaps = new WebGLCubeUVMaps( _this ); - attributes = new WebGLAttributes( _gl, capabilities ); - bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); + attributes = new WebGLAttributes( _gl ); + bindingStates = new WebGLBindingStates( _gl, attributes ); geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); objects = new WebGLObjects( _gl, geometries, attributes, info ); morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); @@ -28985,22 +39655,89 @@ class WebGLRenderer { programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); materials = new WebGLMaterials( _this, properties ); renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions, capabilities ); + renderStates = new WebGLRenderStates( extensions ); background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); shadowMap = new WebGLShadowMap( _this, objects, capabilities ); uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); + bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); + indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); info.programs = programCache.programs; + /** + * Holds details about the capabilities of the current rendering context. + * + * @name WebGLRenderer#capabilities + * @type {WebGLRenderer~Capabilities} + */ _this.capabilities = capabilities; + + /** + * Provides methods for retrieving and testing WebGL extensions. + * + * - `get(extensionName:string)`: Used to check whether a WebGL extension is supported + * and return the extension object if available. + * - `has(extensionName:string)`: returns `true` if the extension is supported. + * + * @name WebGLRenderer#extensions + * @type {Object} + */ _this.extensions = extensions; + + /** + * Used to track properties of other objects like native WebGL objects. + * + * @name WebGLRenderer#properties + * @type {Object} + */ _this.properties = properties; + + /** + * Manages the render lists of the renderer. + * + * @name WebGLRenderer#renderLists + * @type {Object} + */ _this.renderLists = renderLists; + + + + /** + * Interface for managing shadows. + * + * @name WebGLRenderer#shadowMap + * @type {WebGLRenderer~ShadowMap} + */ _this.shadowMap = shadowMap; + + /** + * Interface for managing the WebGL state. + * + * @name WebGLRenderer#state + * @type {Object} + */ _this.state = state; + + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * By default these data are reset at each render call but when having + * multiple render passes per frame (e.g. when using post processing) it can + * be preferred to reset with a custom pattern. First, set `autoReset` to + * `false`. + * ```js + * renderer.info.autoReset = false; + * ``` + * Call `reset()` whenever you have finished to render a single frame. + * ```js + * renderer.info.reset(); + * ``` + * + * @name WebGLRenderer#info + * @type {WebGLRenderer~Info} + */ _this.info = info; } @@ -29011,22 +39748,38 @@ class WebGLRenderer { const xr = new WebXRManager( _this, _gl ); + /** + * A reference to the XR manager. + * + * @type {WebXRManager} + */ this.xr = xr; - // API - + /** + * Returns the rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ this.getContext = function () { return _gl; }; + /** + * Returns the rendering context attributes. + * + * @return {WebGLContextAttributes} The rendering context attributes. + */ this.getContextAttributes = function () { return _gl.getContextAttributes(); }; + /** + * Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ this.forceContextLoss = function () { const extension = extensions.get( 'WEBGL_lose_context' ); @@ -29034,6 +39787,9 @@ class WebGLRenderer { }; + /** + * Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ this.forceContextRestore = function () { const extension = extensions.get( 'WEBGL_lose_context' ); @@ -29041,12 +39797,22 @@ class WebGLRenderer { }; + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ this.getPixelRatio = function () { return _pixelRatio; }; + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} value - The pixel ratio. + */ this.setPixelRatio = function ( value ) { if ( value === undefined ) return; @@ -29057,12 +39823,27 @@ class WebGLRenderer { }; + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ this.getSize = function ( target ) { return target.set( _width, _height ); }; + /** + * Resizes the output canvas to (width, height) with device pixel ratio taken + * into account, and also sets the viewport to fit that size, starting in (0, + * 0). Setting `updateStyle` to false prevents any style changes to the output canvas. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ this.setSize = function ( width, height, updateStyle = true ) { if ( xr.isPresenting ) { @@ -29089,12 +39870,31 @@ class WebGLRenderer { }; + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ this.getDrawingBufferSize = function ( target ) { return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); }; + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ this.setDrawingBufferSize = function ( width, height, pixelRatio ) { _width = width; @@ -29109,18 +39909,39 @@ class WebGLRenderer { }; + /** + * Returns the current viewport definition. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The current viewport definition. + */ this.getCurrentViewport = function ( target ) { return target.copy( _currentViewport ); }; + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ this.getViewport = function ( target ) { return target.copy( _viewport ); }; + /** + * Sets the viewport to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the viewport. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + */ this.setViewport = function ( x, y, width, height ) { if ( x.isVector4 ) { @@ -29137,12 +39958,27 @@ class WebGLRenderer { }; + /** + * Returns the scissor region. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor region. + */ this.getScissor = function ( target ) { return target.copy( _scissor ); }; + /** + * Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the scissor region. + * @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * @param {number} width - The width of the scissor region in logical pixel unit. + * @param {number} height - The height of the scissor region in logical pixel unit. + */ this.setScissor = function ( x, y, width, height ) { if ( x.isVector4 ) { @@ -29159,24 +39995,48 @@ class WebGLRenderer { }; + /** + * Returns `true` if the scissor test is enabled. + * + * @return {boolean} Whether the scissor test is enabled or not. + */ this.getScissorTest = function () { return _scissorTest; }; + /** + * Enable or disable the scissor test. When this is enabled, only the pixels + * within the defined scissor area will be affected by further renderer + * actions. + * + * @param {boolean} boolean - Whether the scissor test is enabled or not. + */ this.setScissorTest = function ( boolean ) { state.setScissorTest( _scissorTest = boolean ); }; + /** + * Sets a custom opaque sort function for the render lists. Pass `null` + * to use the default `painterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ this.setOpaqueSort = function ( method ) { _opaqueSort = method; }; + /** + * Sets a custom transparent sort function for the render lists. Pass `null` + * to use the default `reversePainterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ this.setTransparentSort = function ( method ) { _transparentSort = method; @@ -29185,30 +40045,60 @@ class WebGLRenderer { // Clearing + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ this.getClearColor = function ( target ) { return target.copy( background.getClearColor() ); }; + /** + * Sets the clear color and alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ this.setClearColor = function () { - background.setClearColor.apply( background, arguments ); + background.setClearColor( ...arguments ); }; + /** + * Returns the clear alpha. Ranges within `[0,1]`. + * + * @return {number} The clear alpha. + */ this.getClearAlpha = function () { return background.getClearAlpha(); }; + /** + * Sets the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ this.setClearAlpha = function () { - background.setClearAlpha.apply( background, arguments ); + background.setClearAlpha( ...arguments ); }; + /** + * Tells the renderer to clear its color, depth or stencil drawing buffer(s). + * This method initializes the buffers to the current clear color values. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ this.clear = function ( color = true, depth = true, stencil = true ) { let bits = 0; @@ -29270,7 +40160,12 @@ class WebGLRenderer { } - if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; + if ( depth ) { + + bits |= _gl.DEPTH_BUFFER_BIT; + + } + if ( stencil ) { bits |= _gl.STENCIL_BUFFER_BIT; @@ -29282,32 +40177,44 @@ class WebGLRenderer { }; + /** + * Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`. + */ this.clearColor = function () { this.clear( true, false, false ); }; + /** + * Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`. + */ this.clearDepth = function () { this.clear( false, true, false ); }; + /** + * Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`. + */ this.clearStencil = function () { this.clear( false, false, true ); }; - // - + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = function () { canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); + background.dispose(); renderLists.dispose(); renderStates.dispose(); properties.dispose(); @@ -29323,13 +40230,6 @@ class WebGLRenderer { xr.removeEventListener( 'sessionstart', onXRSessionStart ); xr.removeEventListener( 'sessionend', onXRSessionEnd ); - if ( _transmissionRenderTarget ) { - - _transmissionRenderTarget.dispose(); - _transmissionRenderTarget = null; - - } - animation.stop(); }; @@ -29540,7 +40440,35 @@ class WebGLRenderer { if ( object.isBatchedMesh ) { - renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + if ( object._multiDrawInstances !== null ) { + + // @deprecated, r174 + warnOnce( 'THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); + + } else { + + if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { + + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; + const uniforms = properties.get( material ).currentProgram.getUniforms(); + for ( let i = 0; i < drawCount; i ++ ) { + + uniforms.setValue( _gl, '_gl_DrawID', i ); + renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); + + } + + } else { + + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + + } + + } } else if ( object.isInstancedMesh ) { @@ -29585,12 +40513,24 @@ class WebGLRenderer { } + /** + * Compiles all materials in the scene with the camera. This is useful to precompile shaders + * before the first rendering. If you want to add a 3D object to an existing scene, use the third + * optional parameter for applying the target scene. + * + * Note that the (target) scene's lighting and environment must be configured before calling this method. + * + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Set<Material>} The precompiled materials. + */ this.compile = function ( scene, camera, targetScene = null ) { if ( targetScene === null ) targetScene = scene; currentRenderState = renderStates.get( targetScene ); - currentRenderState.init(); + currentRenderState.init( camera ); renderStateStack.push( currentRenderState ); @@ -29632,7 +40572,7 @@ class WebGLRenderer { } - currentRenderState.setupLights( _this._useLegacyLights ); + currentRenderState.setupLights(); // Only initialize materials in the new scene, not the targetScene. @@ -29640,6 +40580,12 @@ class WebGLRenderer { scene.traverse( function ( object ) { + if ( ! ( object.isMesh || object.isPoints || object.isLine || object.isSprite ) ) { + + return; + + } + const material = object.material; if ( material ) { @@ -29666,8 +40612,7 @@ class WebGLRenderer { } ); - renderStateStack.pop(); - currentRenderState = null; + currentRenderState = renderStateStack.pop(); return materials; @@ -29675,6 +40620,18 @@ class WebGLRenderer { // compileAsync + /** + * Asynchronous version of {@link WebGLRenderer#compile}. + * + * This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence, + * it is recommended to use this version of `compile()` whenever possible. + * + * @async + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation. + */ this.compileAsync = function ( scene, camera, targetScene = null ) { const materials = this.compile( scene, camera, targetScene ); @@ -29776,6 +40733,20 @@ class WebGLRenderer { // Rendering + /** + * Renders the given scene (or other type of 3D object) using the given camera. + * + * The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget} + * or to the canvas as usual. + * + * By default render buffers are cleared before rendering but you can prevent + * this by setting the property `autoClear` to `false`. If you want to prevent + * only certain buffers being cleared you can `autoClearColor`, `autoClearDepth` + * or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( camera !== undefined && camera.isCamera !== true ) { @@ -29807,12 +40778,12 @@ class WebGLRenderer { if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init(); + currentRenderState.init( camera ); renderStateStack.push( currentRenderState ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); + _frustum.setFromProjectionMatrix( _projScreenMatrix, WebGLCoordinateSystem, camera.reversedDepth ); _localClippingEnabled = this.localClippingEnabled; _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); @@ -29822,6 +40793,18 @@ class WebGLRenderer { renderListStack.push( currentRenderList ); + if ( xr.enabled === true && xr.isPresenting === true ) { + + const depthSensingMesh = _this.xr.getDepthSensingMesh(); + + if ( depthSensingMesh !== null ) { + + projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); + + } + + } + projectObject( scene, camera, 0, _this.sortObjects ); currentRenderList.finish(); @@ -29832,6 +40815,13 @@ class WebGLRenderer { } + _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; + if ( _renderBackground ) { + + background.addToRenderList( currentRenderList, scene ); + + } + // this.info.render.frame ++; @@ -29848,22 +40838,30 @@ class WebGLRenderer { if ( this.info.autoReset === true ) this.info.reset(); + // render scene - // + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; - if ( xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false ) { + currentRenderState.setupLights(); - background.render( currentRenderList, scene ); + if ( camera.isArrayCamera ) { - } + const cameras = camera.cameras; - // render scene + if ( transmissiveObjects.length > 0 ) { - currentRenderState.setupLights( _this._useLegacyLights ); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - if ( camera.isArrayCamera ) { + const camera2 = cameras[ i ]; - const cameras = camera.cameras; + renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); + + } + + } + + if ( _renderBackground ) background.render( scene ); for ( let i = 0, l = cameras.length; i < l; i ++ ) { @@ -29875,13 +40873,17 @@ class WebGLRenderer { } else { + if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); + + if ( _renderBackground ) background.render( scene ); + renderScene( currentRenderList, scene, camera ); } // - if ( _currentRenderTarget !== null ) { + if ( _currentRenderTarget !== null && _currentActiveMipmapLevel === 0 ) { // resolve multisample renderbuffers to a single-sample texture if necessary @@ -29900,7 +40902,7 @@ class WebGLRenderer { // _gl.finish(); bindingStates.resetDefaultState(); - _currentMaterialId = - 1; + _currentMaterialId = -1; _currentCamera = null; renderStateStack.pop(); @@ -29909,6 +40911,8 @@ class WebGLRenderer { currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); + } else { currentRenderState = null; @@ -29961,7 +40965,7 @@ class WebGLRenderer { if ( sortObjects ) { - _vector3.setFromMatrixPosition( object.matrixWorld ) + _vector4.setFromMatrixPosition( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); } @@ -29971,7 +40975,7 @@ class WebGLRenderer { if ( material.visible ) { - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); } @@ -29989,16 +40993,16 @@ class WebGLRenderer { if ( object.boundingSphere !== undefined ) { if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector3.copy( object.boundingSphere.center ); + _vector4.copy( object.boundingSphere.center ); } else { if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector3.copy( geometry.boundingSphere.center ); + _vector4.copy( geometry.boundingSphere.center ); } - _vector3 + _vector4 .applyMatrix4( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); @@ -30015,7 +41019,7 @@ class WebGLRenderer { if ( groupMaterial && groupMaterial.visible ) { - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); + currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group ); } @@ -30023,7 +41027,7 @@ class WebGLRenderer { } else if ( material.visible ) { - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); } @@ -30053,8 +41057,6 @@ class WebGLRenderer { if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); @@ -30081,15 +41083,17 @@ class WebGLRenderer { } - const isWebGL2 = capabilities.isWebGL2; - - if ( _transmissionRenderTarget === null ) { + if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { - _transmissionRenderTarget = new WebGLRenderTarget( 1, 1, { + currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { generateMipmaps: true, - type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, + type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType, minFilter: LinearMipmapLinearFilter, - samples: ( isWebGL2 ) ? 4 : 0 + samples: 4, + stencilBuffer: stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false, + colorSpace: ColorManagement.workingColorSpace, } ); // debug @@ -30104,22 +41108,18 @@ class WebGLRenderer { } - _this.getDrawingBufferSize( _vector2 ); - - if ( isWebGL2 ) { - - _transmissionRenderTarget.setSize( _vector2.x, _vector2.y ); - - } else { - - _transmissionRenderTarget.setSize( floorPowerOfTwo( _vector2.x ), floorPowerOfTwo( _vector2.y ) ); + const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; - } + const activeViewport = camera.viewport || _currentViewport; + transmissionRenderTarget.setSize( activeViewport.z * _this.transmissionResolutionScale, activeViewport.w * _this.transmissionResolutionScale ); // const currentRenderTarget = _this.getRenderTarget(); - _this.setRenderTarget( _transmissionRenderTarget ); + const currentActiveCubeFace = _this.getActiveCubeFace(); + const currentActiveMipmapLevel = _this.getActiveMipmapLevel(); + + _this.setRenderTarget( transmissionRenderTarget ); _this.getClearColor( _currentClearColor ); _currentClearAlpha = _this.getClearAlpha(); @@ -30127,56 +41127,73 @@ class WebGLRenderer { _this.clear(); + if ( _renderBackground ) background.render( scene ); + // Turn off the features which can affect the frag color for opaque objects pass. // Otherwise they are applied twice in opaque objects pass and transmission objects pass. const currentToneMapping = _this.toneMapping; _this.toneMapping = NoToneMapping; + // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). + // Transmission render pass requires viewport to match the transmissionRenderTarget. + const currentCameraViewport = camera.viewport; + if ( camera.viewport !== undefined ) camera.viewport = undefined; + + currentRenderState.setupLightsView( camera ); + + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); + renderObjects( opaqueObjects, scene, camera ); - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - let renderTargetNeedsUpdate = false; + if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { + let renderTargetNeedsUpdate = false; - const renderItem = transmissiveObjects[ i ]; + for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = renderItem.material; - const group = renderItem.group; + const renderItem = transmissiveObjects[ i ]; - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { + const object = renderItem.object; + const geometry = renderItem.geometry; + const material = renderItem.material; + const group = renderItem.group; - const currentSide = material.side; + if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - material.side = BackSide; - material.needsUpdate = true; + const currentSide = material.side; - renderObject( object, scene, camera, geometry, material, group ); + material.side = BackSide; + material.needsUpdate = true; - material.side = currentSide; - material.needsUpdate = true; + renderObject( object, scene, camera, geometry, material, group ); - renderTargetNeedsUpdate = true; + material.side = currentSide; + material.needsUpdate = true; + + renderTargetNeedsUpdate = true; + + } } - } + if ( renderTargetNeedsUpdate === true ) { - if ( renderTargetNeedsUpdate === true ) { + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + } } - _this.setRenderTarget( currentRenderTarget ); + _this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); _this.setClearColor( _currentClearColor, _currentClearAlpha ); + if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; + _this.toneMapping = currentToneMapping; } @@ -30191,8 +41208,14 @@ class WebGLRenderer { const object = renderItem.object; const geometry = renderItem.geometry; - const material = overrideMaterial === null ? renderItem.material : overrideMaterial; const group = renderItem.group; + let material = renderItem.material; + + if ( material.allowOverride === true && overrideMaterial !== null ) { + + material = overrideMaterial; + + } if ( object.layers.test( camera.layers ) ) { @@ -30287,8 +41310,6 @@ class WebGLRenderer { parameters.uniforms = programCache.getUniforms( material ); - material.onBuild( object, parameters, _this ); - material.onBeforeCompile( parameters, _this ); program = programCache.acquireProgram( parameters, programCacheKey ); @@ -30367,6 +41388,7 @@ class WebGLRenderer { materialProperties.outputColorSpace = parameters.outputColorSpace; materialProperties.batching = parameters.batching; + materialProperties.batchingColor = parameters.batchingColor; materialProperties.instancing = parameters.instancing; materialProperties.instancingColor = parameters.instancingColor; materialProperties.instancingMorph = parameters.instancingMorph; @@ -30456,6 +41478,14 @@ class WebGLRenderer { needsProgramChange = true; + } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { + + needsProgramChange = true; + + } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { + + needsProgramChange = true; + } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { needsProgramChange = true; @@ -30526,7 +41556,7 @@ class WebGLRenderer { needsProgramChange = true; - } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { + } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { needsProgramChange = true; @@ -30576,7 +41606,17 @@ class WebGLRenderer { // common camera uniforms + const reversedDepthBuffer = state.buffers.depth.getReversed(); + + if ( reversedDepthBuffer && camera.reversedDepth !== true ) { + + camera._reversedDepth = true; + camera.updateProjectionMatrix(); + + } + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); const uCamPos = p_uniforms.map.cameraPosition; @@ -30635,17 +41675,9 @@ class WebGLRenderer { if ( skeleton ) { - if ( capabilities.floatVertexTextures ) { - - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - - } else { - - console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); - - } + p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); } @@ -30656,11 +41688,21 @@ class WebGLRenderer { p_uniforms.setOptional( _gl, object, 'batchingTexture' ); p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); + p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); + p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); + + p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); + if ( object._colorsTexture !== null ) { + + p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); + + } + } const morphAttributes = geometry.morphAttributes; - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { + if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { morphtargets.update( object, geometry, program ); @@ -30679,7 +41721,13 @@ class WebGLRenderer { m_uniforms.envMap.value = envMap; - m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; + + } + + if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) { + + m_uniforms.envMapIntensity.value = scene.environmentIntensity; } @@ -30710,7 +41758,7 @@ class WebGLRenderer { } - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); + materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); @@ -30743,18 +41791,10 @@ class WebGLRenderer { for ( let i = 0, l = groups.length; i < l; i ++ ) { - if ( capabilities.isWebGL2 ) { - - const group = groups[ i ]; - - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); + const group = groups[ i ]; - } + uniformsGroups.update( group, program ); + uniformsGroups.bind( group, program ); } @@ -30790,18 +41830,34 @@ class WebGLRenderer { } + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ this.getActiveCubeFace = function () { return _currentActiveCubeFace; }; + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ this.getActiveMipmapLevel = function () { return _currentActiveMipmapLevel; }; + /** + * Returns the active render target. + * + * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target + * is currently set. + */ this.getRenderTarget = function () { return _currentRenderTarget; @@ -30810,26 +41866,21 @@ class WebGLRenderer { this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__hasExternalTextures = true; - renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; - - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { + renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false; + if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) { // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { + renderTargetProperties.__useRenderToTexture = false; - console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); - renderTargetProperties.__useRenderToTexture = false; + } - } + properties.get( renderTarget.texture ).__webglTexture = colorTexture; + properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture; - } + renderTargetProperties.__hasExternalTextures = true; }; @@ -30841,6 +41892,17 @@ class WebGLRenderer { }; + const _scratchFrameBuffer = _gl.createFramebuffer(); + + /** + * Sets the active rendertarget. + * + * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given, + * the canvas is set as the active render target instead. + * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target. + * Indicates the z layer to render in to when using 3D or array render targets. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { _currentRenderTarget = renderTarget; @@ -30871,6 +41933,28 @@ class WebGLRenderer { // Color and depth texture must be rebound in order for the swapchain to update. textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); + } else if ( renderTarget.depthBuffer ) { + + // check if the depth texture is already bound to the frame buffer and that it's been initialized + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__boundDepthTexture !== depthTexture ) { + + // check if the depth texture is compatible + if ( + depthTexture !== null && + properties.has( depthTexture ) && + ( renderTarget.width !== depthTexture.image.width || renderTarget.height !== depthTexture.image.height ) + ) { + + throw new Error( 'WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.' ); + + } + + // Swap the depth buffer to the currently attached one + textures.setupDepthRenderbuffer( renderTarget ); + + } + } const texture = renderTarget.texture; @@ -30897,7 +41981,7 @@ class WebGLRenderer { isCube = true; - } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { + } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; @@ -30927,9 +42011,17 @@ class WebGLRenderer { } + // Use a scratch frame buffer if rendering to a mip level to avoid depth buffers + // being bound that are different sizes. + if ( activeMipmapLevel !== 0 ) { + + framebuffer = _scratchFrameBuffer; + + } + const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { + if ( framebufferBound && useDefaultFramebuffer ) { state.drawBuffers( renderTarget, framebuffer ); @@ -30946,17 +42038,42 @@ class WebGLRenderer { } else if ( isRenderTarget3D ) { + const layer = activeCubeFace; + + for ( let i = 0; i < renderTarget.textures.length; i ++ ) { + + const textureProperties = properties.get( renderTarget.textures[ i ] ); + + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, textureProperties.__webglTexture, activeMipmapLevel, layer ); + + } + + } else if ( renderTarget !== null && activeMipmapLevel !== 0 ) { + + // Only bind the frame buffer if we are using a scratch frame buffer to render to a mipmap. + // If we rebind the texture when using a multi sample buffer then an error about inconsistent samples will be thrown. const textureProperties = properties.get( renderTarget.texture ); - const layer = activeCubeFace || 0; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, textureProperties.__webglTexture, activeMipmapLevel ); } - _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings + _currentMaterialId = -1; // reset current material to ensure correct uniform bindings }; - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { + /** + * Reads the pixel data from the given render target into the given buffer. + * + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + */ + this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { @@ -30979,22 +42096,18 @@ class WebGLRenderer { try { - const texture = renderTarget.texture; + const texture = renderTarget.textures[ textureIndex ]; const textureFormat = texture.format; const textureType = texture.type; - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); return; } - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); - - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! halfFloatSupportedByExt ) { + if ( ! capabilities.textureTypeReadable( textureType ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); return; @@ -31005,6 +42118,10 @@ class WebGLRenderer { if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + // when using MRT, select the correct color buffer for the subsequent read command + + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); } @@ -31022,72 +42139,213 @@ class WebGLRenderer { }; - this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { + /** + * Asynchronous, non-blocking version of {@link WebGLRenderer#readRenderTargetPixels}. + * + * It is recommended to use this version of `readRenderTargetPixels()` whenever possible. + * + * @async + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + * @return {Promise<TypedArray>} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { + + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { + + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + + } + + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { + + framebuffer = framebuffer[ activeCubeFaceIndex ]; + + } + + if ( framebuffer ) { + + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + + // set the active frame buffer to the one we want to read + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; + + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { + + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); + + } + + if ( ! capabilities.textureTypeReadable( textureType ) ) { + + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); + + } + + const glBuffer = _gl.createBuffer(); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); + + // when using MRT, select the correct color buffer for the subsequent read command + + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); + + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); + + // reset the frame buffer to the currently set buffer before waiting + const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, currFramebuffer ); + + // check if the commands have finished every 8 ms + const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); + + _gl.flush(); + + await probeAsync( _gl, sync, 4 ); + + // read the data and delete the buffer + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); + _gl.deleteBuffer( glBuffer ); + _gl.deleteSync( sync ); + + return buffer; + + } else { + + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' ); + + } + + } + + }; + + /** + * Copies pixels from the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} texture - The texture. + * @param {?Vector2} [position=null] - The start position of the copy operation. + * @param {number} [level=0] - The mip level. The default represents the base mip. + */ + this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { const levelScale = Math.pow( 2, - level ); const width = Math.floor( texture.image.width * levelScale ); const height = Math.floor( texture.image.height * levelScale ); + const x = position !== null ? position.x : 0; + const y = position !== null ? position.y : 0; + textures.setTexture2D( texture, 0 ); - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); + _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); state.unbindTexture(); }; - this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { + const _srcFramebuffer = _gl.createFramebuffer(); + const _dstFramebuffer = _gl.createFramebuffer(); - const width = srcTexture.image.width; - const height = srcTexture.image.height; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); + /** + * Copies data of the given source texture into a destination texture. + * + * When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized + * {@link WebGLRenderer#initRenderTarget}. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box2|Box3)} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {?(Vector2|Vector3)} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mipmap level to copy. + * @param {?number} [dstLevel=null] - The destination mipmap level. + */ + this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = null ) { - textures.setTexture2D( dstTexture, 0 ); + // support the previous signature with just a single dst mipmap level + if ( dstLevel === null ) { - // As another texture upload may have changed pixelStorei - // parameters, make sure they are correct for the dstTexture - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + if ( srcLevel !== 0 ) { + + // @deprecated, r171 + warnOnce( 'WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels.' ); + dstLevel = srcLevel; + srcLevel = 0; + + } else { + + dstLevel = 0; + + } - if ( srcTexture.isDataTexture ) { + } + + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + if ( srcRegion !== null ) { - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; } else { - if ( srcTexture.isCompressedTexture ) { + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + if ( srcTexture.isDataArrayTexture ) { + + depth = image.depth; - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); + } else if ( srcTexture.isData3DTexture ) { + + depth = Math.floor( image.depth * levelScale ); } else { - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); + depth = 1; } - } + minX = 0; + minY = 0; + minZ = 0; - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); + } - state.unbindTexture(); + if ( dstPosition !== null ) { - }; + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; - this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { - - if ( _this.isWebGL1Renderer ) { + } else { - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); - return; + dstX = 0; + dstY = 0; + dstZ = 0; } - const width = Math.round( sourceBox.max.x - sourceBox.min.x ); - const height = Math.round( sourceBox.max.y - sourceBox.min.y ); - const depth = sourceBox.max.z - sourceBox.min.z + 1; + // Set up the destination target const glFormat = utils.convert( dstTexture.format ); const glType = utils.convert( dstTexture.type ); let glTarget; @@ -31104,8 +42362,8 @@ class WebGLRenderer { } else { - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); - return; + textures.setTexture2D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D; } @@ -31113,51 +42371,183 @@ class WebGLRenderer { _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); - const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); - - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ level ] : srcTexture.image; + // used for copying data from cpu + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); + + // set up the src texture + const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture; + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; + if ( srcTexture.isDepthTexture ) { + + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); + const srcRenderTargetProperties = properties.get( srcTextureProperties.__renderTarget ); + const dstRenderTargetProperties = properties.get( dstTextureProperties.__renderTarget ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, srcRenderTargetProperties.__webglFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, dstRenderTargetProperties.__webglFramebuffer ); + + for ( let i = 0; i < depth; i ++ ) { + + // if the source or destination are a 3d target then a layer needs to be bound + if ( isSrc3D ) { + + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( srcTexture ).__webglTexture, srcLevel, minZ + i ); + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( dstTexture ).__webglTexture, dstLevel, dstZ + i ); + + } + + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.DEPTH_BUFFER_BIT, _gl.NEAREST ); + + } + + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); + + } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || properties.has( srcTexture ) ) { - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + // get the appropriate frame buffers + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); + // bind the frame buffer targets + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, _srcFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, _dstFramebuffer ); + + for ( let i = 0; i < depth; i ++ ) { + + // assign the correct layers and mip maps to the frame buffers + if ( isSrc3D ) { + + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, srcTextureProperties.__webglTexture, srcLevel, minZ + i ); + + } else { + + _gl.framebufferTexture2D( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, srcTextureProperties.__webglTexture, srcLevel ); + + } + + if ( isDst3D ) { + + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, dstTextureProperties.__webglTexture, dstLevel, dstZ + i ); + + } else { + + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, dstTextureProperties.__webglTexture, dstLevel ); + + } + + // copy the data using the fastest function that can achieve the copy + if ( srcLevel !== 0 ) { + + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.COLOR_BUFFER_BIT, _gl.NEAREST ); + + } else if ( isDst3D ) { + + _gl.copyTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height ); + + } else { + + _gl.copyTexSubImage2D( glTarget, dstLevel, dstX, dstY, minX, minY, width, height ); + + } + + } + + // unbind read, draw buffers + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); } else { - if ( dstTexture.isCompressedArrayTexture ) { + if ( isDst3D ) { - _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); + + } else if ( dstTexture.isCompressedArrayTexture ) { + + _gl.compressedTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); + + } else { + + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); + + } } else { - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { + + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); + + } else if ( srcTexture.isCompressedTexture ) { + + _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); + + } else { + + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); + + } } } - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); + // reset values + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + _gl.generateMipmap( glTarget ); + + } state.unbindTexture(); }; + /** + * Initializes the given WebGLRenderTarget memory. Useful for initializing a render target so data + * can be copied into it using {@link WebGLRenderer#copyTextureToTexture} before it has been + * rendered to. + * + * @param {WebGLRenderTarget} target - The render target. + */ + this.initRenderTarget = function ( target ) { + + if ( properties.get( target ).__webglFramebuffer === undefined ) { + + textures.setupRenderTarget( target ); + + } + + }; + + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first + * render (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @param {Texture} texture - The texture. + */ this.initTexture = function ( texture ) { if ( texture.isCubeTexture ) { @@ -31182,6 +42572,11 @@ class WebGLRenderer { }; + /** + * Can be used to reset the internal WebGL state. This method is mostly + * relevant for applications which share a single WebGL context across + * multiple WebGL libraries. + */ this.resetState = function () { _currentActiveCubeFace = 0; @@ -31201,12 +42596,27 @@ class WebGLRenderer { } + /** + * Defines the coordinate system of the renderer. + * + * In `WebGLRenderer`, the value is always `WebGLCoordinateSystem`. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + * @readonly + */ get coordinateSystem() { return WebGLCoordinateSystem; } + /** + * Defines the output color space of the renderer. + * + * @type {SRGBColorSpace|LinearSRGBColorSpace} + * @default SRGBColorSpace + */ get outputColorSpace() { return this._outputColorSpace; @@ -31218,48 +42628,94 @@ class WebGLRenderer { this._outputColorSpace = colorSpace; const gl = this.getContext(); - gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; - gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; - - } - - get useLegacyLights() { // @deprecated, r155 - - console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); - return this._useLegacyLights; - - } - - set useLegacyLights( value ) { // @deprecated, r155 - - console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); - this._useLegacyLights = value; + gl.drawingBufferColorSpace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); + gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); } } +/** + * This class can be used to define a linear fog that grows linearly denser + * with the distance. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + * ``` + */ class Fog { + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [near=1] - The minimum distance to start applying fog. + * @param {number} [far=1000] - The maximum distance at which fog stops being calculated and applied. + */ constructor( color, near = 1, far = 1000 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isFog = true; + /** + * The name of the fog. + * + * @type {string} + */ this.name = ''; + /** + * The fog's color. + * + * @type {Color} + */ this.color = new Color( color ); + /** + * The minimum distance to start applying fog. Objects that are less than + * `near` units from the active camera won't be affected by fog. + * + * @type {number} + * @default 1 + */ this.near = near; + + /** + * The maximum distance at which fog stops being calculated and applied. + * Objects that are more than `far` units away from the active camera won't + * be affected by fog. + * + * @type {number} + * @default 1000 + */ this.far = far; } + /** + * Returns a new fog with copied values from this instance. + * + * @return {Fog} A clone of this instance. + */ clone() { return new Fog( this.color, this.near, this.far ); } + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ toJSON( /* meta */ ) { return { @@ -31274,25 +42730,115 @@ class Fog { } +/** + * Scenes allow you to set up what is to be rendered and where by three.js. + * This is where you place 3D objects like meshes, lines or lights. + * + * @augments Object3D + */ class Scene extends Object3D { + /** + * Constructs a new scene. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isScene = true; this.type = 'Scene'; + /** + * Defines the background of the scene. Valid inputs are: + * + * - A color for defining a uniform colored background. + * - A texture for defining a (flat) textured background. + * - Cube textures or equirectangular textures for defining a skybox. + * + * @type {?(Color|Texture)} + * @default null + */ this.background = null; + + /** + * Sets the environment map for all physical materials in the scene. However, + * it's not possible to overwrite an existing texture assigned to the `envMap` + * material property. + * + * @type {?Texture} + * @default null + */ this.environment = null; + + /** + * A fog instance defining the type of fog that affects everything + * rendered in the scene. + * + * @type {?(Fog|FogExp2)} + * @default null + */ this.fog = null; + /** + * Sets the blurriness of the background. Only influences environment maps + * assigned to {@link Scene#background}. Valid input is a float between `0` + * and `1`. + * + * @type {number} + * @default 0 + */ this.backgroundBlurriness = 0; + + /** + * Attenuates the color of the background. Only applies to background textures. + * + * @type {number} + * @default 1 + */ this.backgroundIntensity = 1; + + /** + * The rotation of the background in radians. Only influences environment maps + * assigned to {@link Scene#background}. + * + * @type {Euler} + * @default (0,0,0) + */ this.backgroundRotation = new Euler(); + + /** + * Attenuates the color of the environment. Only influences environment maps + * assigned to {@link Scene#environment}. + * + * @type {number} + * @default 1 + */ + this.environmentIntensity = 1; + + /** + * The rotation of the environment map in radians. Only influences physical materials + * in the scene when {@link Scene#environment} is used. + * + * @type {Euler} + * @default (0,0,0) + */ this.environmentRotation = new Euler(); + /** + * Forces everything in the scene to be rendered with the defined material. It is possible + * to exclude materials from override by setting {@link Material#allowOverride} to `false`. + * + * @type {?Material} + * @default null + */ this.overrideMaterial = null; if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { @@ -31314,6 +42860,8 @@ class Scene extends Object3D { this.backgroundBlurriness = source.backgroundBlurriness; this.backgroundIntensity = source.backgroundIntensity; this.backgroundRotation.copy( source.backgroundRotation ); + + this.environmentIntensity = source.environmentIntensity; this.environmentRotation.copy( source.environmentRotation ); if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); @@ -31329,10 +42877,12 @@ class Scene extends Object3D { const data = super.toJSON( meta ); if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); + if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; - data.object.backgroundRotation = this.backgroundRotation.toArray(); + + if ( this.environmentIntensity !== 1 ) data.object.environmentIntensity = this.environmentIntensity; data.object.environmentRotation = this.environmentRotation.toArray(); return data; @@ -31341,14 +42891,43 @@ class Scene extends Object3D { } +/** + * An instanced version of a buffer attribute. + * + * @augments BufferAttribute + */ class InstancedBufferAttribute extends BufferAttribute { + /** + * Constructs a new instanced buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + * @param {number} [meshPerAttribute=1] - How often a value of this buffer attribute should be repeated. + */ constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { super( array, itemSize, normalized ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInstancedBufferAttribute = true; + /** + * Defines how often a value of this buffer attribute should be repeated. A + * value of one means that each value of the instanced attribute is used for + * a single instance. A value of two means that each value is used for two + * consecutive instances (and so on). + * + * @type {number} + * @default 1 + */ this.meshPerAttribute = meshPerAttribute; } @@ -31377,18 +42956,83 @@ class InstancedBufferAttribute extends BufferAttribute { } +/** + * Creates a texture directly from raw buffer data. + * + * The interpretation of the data depends on type and format: If the type is + * `UnsignedByteType`, a `Uint8Array` will be useful for addressing the + * texel data. If the format is `RGBAFormat`, data needs four values for + * one texel; Red, Green, Blue and Alpha (typically the opacity). + * + * @augments Texture + */ class DataTexture extends Texture { + /** + * Constructs a new data texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=NearestFilter] - The mag filter value. + * @param {number} [minFilter=NearestFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isDataTexture = true; + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number}} + */ this.image = { data: data, width: width, height: height }; + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ this.unpackAlignment = 1; } @@ -31405,21 +43049,87 @@ const _identity = /*@__PURE__*/ new Matrix4(); const _mesh$1 = /*@__PURE__*/ new Mesh(); const _sphere$4 = /*@__PURE__*/ new Sphere(); +/** + * A special version of a mesh with instanced rendering support. Use + * this class if you have to render a large number of objects with the same + * geometry and material(s) but with different world transformations. The usage + * of 'InstancedMesh' will help you to reduce the number of draw calls and thus + * improve the overall rendering performance in your application. + * + * @augments Mesh + */ class InstancedMesh extends Mesh { + /** + * Constructs a new instanced mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array<Material>} [material] - The mesh material. + * @param {number} count - The number of instances. + */ constructor( geometry, material, count ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInstancedMesh = true; + /** + * Represents the local transformation of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMatrixAt}. + * + * @type {InstancedBufferAttribute} + */ this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); + + /** + * Represents the color of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setColorAt}. + * + * @type {?InstancedBufferAttribute} + * @default null + */ this.instanceColor = null; + + /** + * Represents the morph target weights of all instances. You have to set its + * {@link Texture#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMorphAt}. + * + * @type {?DataTexture} + * @default null + */ this.morphTexture = null; + /** + * The number of instances. + * + * @type {number} + */ this.count = count; + /** + * The bounding box of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ this.boundingBox = null; + + /** + * The bounding sphere of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ this.boundingSphere = null; for ( let i = 0; i < count; i ++ ) { @@ -31430,6 +43140,11 @@ class InstancedMesh extends Mesh { } + /** + * Computes the bounding box of the instanced mesh, and updates {@link InstancedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * You may need to recompute the bounding box if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ computeBoundingBox() { const geometry = this.geometry; @@ -31461,6 +43176,11 @@ class InstancedMesh extends Mesh { } + /** + * Computes the bounding sphere of the instanced mesh, and updates {@link InstancedMesh#boundingSphere} + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ computeBoundingSphere() { const geometry = this.geometry; @@ -31498,6 +43218,7 @@ class InstancedMesh extends Mesh { this.instanceMatrix.copy( source.instanceMatrix ); + if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); this.count = source.count; @@ -31509,18 +43230,36 @@ class InstancedMesh extends Mesh { } + /** + * Gets the color of the defined instance. + * + * @param {number} index - The instance index. + * @param {Color} color - The target object that is used to store the method's result. + */ getColorAt( index, color ) { color.fromArray( this.instanceColor.array, index * 3 ); } + /** + * Gets the local transformation matrix of the defined instance. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + */ getMatrixAt( index, matrix ) { matrix.fromArray( this.instanceMatrix.array, index * 16 ); } + /** + * Gets the morph target weights of the defined instance. + * + * @param {number} index - The instance index. + * @param {Mesh} object - The target object that is used to store the method's result. + */ getMorphAt( index, object ) { const objectInfluences = object.morphTargetInfluences; @@ -31591,11 +43330,18 @@ class InstancedMesh extends Mesh { } + /** + * Sets the given color to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceColor} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Color} color - The instance color. + */ setColorAt( index, color ) { if ( this.instanceColor === null ) { - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); + this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ).fill( 1 ), 3 ); } @@ -31603,12 +43349,27 @@ class InstancedMesh extends Mesh { } + /** + * Sets the given local transformation matrix to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceMatrix} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The local transformation. + */ setMatrixAt( index, matrix ) { matrix.toArray( this.instanceMatrix.array, index * 16 ); } + /** + * Sets the morph target weights to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#morphTexture} to `true` after updating all the influences. + * + * @param {number} index - The instance index. + * @param {Mesh} object - A mesh which `morphTargetInfluences` property containing the morph target weights + * of a single instance. + */ setMorphAt( index, object ) { const objectInfluences = object.morphTargetInfluences; @@ -31645,39 +43406,123 @@ class InstancedMesh extends Mesh { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.dispatchEvent( { type: 'dispose' } ); + if ( this.morphTexture !== null ) { + + this.morphTexture.dispose(); + this.morphTexture = null; + + } + } } +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * ``` + * + * @augments Material + */ class LineBasicMaterial extends Material { + /** + * Constructs a new line basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineBasicMaterial = true; this.type = 'LineBasicMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); + /** + * Sets the color of the lines using data from a texture. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * Controls line thickness or lines. + * + * Can only be used with {@link SVGRenderer}. WebGL and WebGPU + * ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ this.linewidth = 1; + + /** + * Defines appearance of line ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('butt'|'round'|'square')} + * @default 'round' + */ this.linecap = 'round'; + + /** + * Defines appearance of line joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.linejoin = 'round'; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); } - copy( source ) { super.copy( source ); @@ -31698,25 +43543,94 @@ class LineBasicMaterial extends Material { } -const _start$1 = /*@__PURE__*/ new Vector3(); -const _end$1 = /*@__PURE__*/ new Vector3(); +const _vStart = /*@__PURE__*/ new Vector3(); +const _vEnd = /*@__PURE__*/ new Vector3(); + const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); const _ray$2 = /*@__PURE__*/ new Ray(); const _sphere$3 = /*@__PURE__*/ new Sphere(); +const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); +const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); + +/** + * A continuous line. The line are rendered by connecting consecutive + * vertices with straight lines. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); + * + * const points = []; + * points.push( new THREE.Vector3( - 10, 0, 0 ) ); + * points.push( new THREE.Vector3( 0, 10, 0 ) ); + * points.push( new THREE.Vector3( 10, 0, 0 ) ); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments Object3D + */ class Line extends Object3D { + /** + * Constructs a new line. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array<Material>} [material] - The line material. + */ constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLine = true; this.type = 'Line'; + /** + * The line geometry. + * + * @type {BufferGeometry} + */ this.geometry = geometry; + + /** + * The line material. + * + * @type {Material|Array<Material>} + * @default LineBasicMaterial + */ this.material = material; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object<String,number>|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array<number>|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + this.updateMorphTargets(); } @@ -31732,6 +43646,13 @@ class Line extends Object3D { } + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Line} A reference to this line. + */ computeLineDistances() { const geometry = this.geometry; @@ -31745,11 +43666,11 @@ class Line extends Object3D { for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - _start$1.fromBufferAttribute( positionAttribute, i - 1 ); - _end$1.fromBufferAttribute( positionAttribute, i ); + _vStart.fromBufferAttribute( positionAttribute, i - 1 ); + _vEnd.fromBufferAttribute( positionAttribute, i ); lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _start$1.distanceTo( _end$1 ); + lineDistances[ i ] += _vStart.distanceTo( _vEnd ); } @@ -31765,6 +43686,12 @@ class Line extends Object3D { } + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { const geometry = this.geometry; @@ -31790,10 +43717,6 @@ class Line extends Object3D { const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); const localThresholdSq = localThreshold * localThreshold; - const vStart = new Vector3(); - const vEnd = new Vector3(); - const interSegment = new Vector3(); - const interRay = new Vector3(); const step = this.isLineSegments ? 2 : 1; const index = geometry.index; @@ -31810,31 +43733,28 @@ class Line extends Object3D { const a = index.getX( i ); const b = index.getX( i + 1 ); - vStart.fromBufferAttribute( positionAttribute, a ); - vEnd.fromBufferAttribute( positionAttribute, b ); + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, i ); - const distSq = _ray$2.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + if ( intersect ) { - if ( distSq > localThresholdSq ) continue; + intersects.push( intersect ); - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + } - const distance = raycaster.ray.origin.distanceTo( interRay ); + } - if ( distance < raycaster.near || distance > raycaster.far ) continue; + if ( this.isLineLoop ) { - intersects.push( { + const a = index.getX( end - 1 ); + const b = index.getX( start ); - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, end - 1 ); - } ); + if ( intersect ) { + + intersects.push( intersect ); + + } } @@ -31845,31 +43765,25 @@ class Line extends Object3D { for ( let i = start, l = end - 1; i < l; i += step ) { - vStart.fromBufferAttribute( positionAttribute, i ); - vEnd.fromBufferAttribute( positionAttribute, i + 1 ); + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, i, i + 1, i ); - const distSq = _ray$2.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + if ( intersect ) { - if ( distSq > localThresholdSq ) continue; + intersects.push( intersect ); - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + } - const distance = raycaster.ray.origin.distanceTo( interRay ); + } - if ( distance < raycaster.near || distance > raycaster.far ) continue; + if ( this.isLineLoop ) { - intersects.push( { + const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, end - 1, start, end - 1 ); - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + if ( intersect ) { - } ); + intersects.push( intersect ); + + } } @@ -31877,6 +43791,10 @@ class Line extends Object3D { } + /** + * Sets the values of {@link Line#morphTargetDictionary} and {@link Line#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ updateMorphTargets() { const geometry = this.geometry; @@ -31910,15 +43828,66 @@ class Line extends Object3D { } +function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) { + + const positionAttribute = object.geometry.attributes.position; + + _vStart.fromBufferAttribute( positionAttribute, a ); + _vEnd.fromBufferAttribute( positionAttribute, b ); + + const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment ); + + if ( distSq > thresholdSq ) return; + + _intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation + + const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + return { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ), + index: i, + face: null, + faceIndex: null, + barycoord: null, + object: object + + }; + +} + const _start = /*@__PURE__*/ new Vector3(); const _end = /*@__PURE__*/ new Vector3(); +/** + * A series of lines drawn between pairs of vertices. + * + * @augments Line + */ class LineSegments extends Line { + /** + * Constructs a new line segments. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array<Material>} [material] - The line material. + */ constructor( geometry, material ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineSegments = true; this.type = 'LineSegments'; @@ -31960,12 +43929,32 @@ class LineSegments extends Line { } +/** + * A continuous line. This is nearly the same as {@link Line} the only difference + * is that the last vertex is connected with the first vertex in order to close + * the line to form a loop. + * + * @augments Line + */ class LineLoop extends Line { + /** + * Constructs a new line loop. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array<Material>} [material] - The line material. + */ constructor( geometry, material ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineLoop = true; this.type = 'LineLoop'; @@ -31974,25 +43963,114 @@ class LineLoop extends Line { } +/** + * A material for rendering point primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const vertices = []; + * + * for ( let i = 0; i < 10000; i ++ ) { + * const x = THREE.MathUtils.randFloatSpread( 2000 ); + * const y = THREE.MathUtils.randFloatSpread( 2000 ); + * const z = THREE.MathUtils.randFloatSpread( 2000 ); + * + * vertices.push( x, y, z ); + * } + * + * const geometry = new THREE.BufferGeometry(); + * geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + * const material = new THREE.PointsMaterial( { color: 0x888888 } ); + * const points = new THREE.Points( geometry, material ); + * scene.add( points ); + * ``` + * + * @augments Material + */ class PointsMaterial extends Material { + /** + * Constructs a new points material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPointsMaterial = true; this.type = 'PointsMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * Defines the size of the points in pixels. + * + * Might be capped if the value exceeds hardware dependent parameters like [gl.ALIASED_POINT_SIZE_RANGE]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParamete}. + * + * @type {number} + * @default 1 + */ this.size = 1; + + /** + * Specifies whether size of individual points is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ this.sizeAttenuation = true; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -32025,19 +44103,69 @@ const _ray$1 = /*@__PURE__*/ new Ray(); const _sphere$2 = /*@__PURE__*/ new Sphere(); const _position = /*@__PURE__*/ new Vector3(); +/** + * A class for displaying points or point clouds. + * + * @augments Object3D + */ class Points extends Object3D { + /** + * Constructs a new point cloud. + * + * @param {BufferGeometry} [geometry] - The points geometry. + * @param {Material|Array<Material>} [material] - The points material. + */ constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPoints = true; this.type = 'Points'; + /** + * The points geometry. + * + * @type {BufferGeometry} + */ this.geometry = geometry; + + /** + * The line material. + * + * @type {Material|Array<Material>} + * @default PointsMaterial + */ this.material = material; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object<String,number>|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array<number>|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + this.updateMorphTargets(); } @@ -32053,6 +44181,12 @@ class Points extends Object3D { } + /** + * Computes intersection points between a casted ray and this point cloud. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { const geometry = this.geometry; @@ -32114,6 +44248,10 @@ class Points extends Object3D { } + /** + * Sets the values of {@link Points#morphTargetDictionary} and {@link Points#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ updateMorphTargets() { const geometry = this.geometry; @@ -32169,6 +44307,8 @@ function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, inte point: intersectPoint, index: index, face: null, + faceIndex: null, + barycoord: null, object: object } ); @@ -32177,12 +44317,40 @@ function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, inte } +/** + * Creates a texture from a canvas element. + * + * This is almost the same as the base texture class, except that it sets {@link Texture#needsUpdate} + * to `true` immediately since a canvas can directly be used for rendering. + * + * @augments Texture + */ class CanvasTexture extends Texture { + /** + * Constructs a new texture. + * + * @param {HTMLCanvasElement} [canvas] - The HTML canvas element. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCanvasTexture = true; this.needsUpdate = true; @@ -32192,938 +44360,1056 @@ class CanvasTexture extends Texture { } /** - * Extensible curve object. - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: + * A geometry class for representing a capsule. * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve + * ```js + * const geometry = new THREE.CapsuleGeometry( 1, 1, 4, 8, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const capsule = new THREE.Mesh( geometry, material ); + * scene.add( capsule ); + * ``` * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ - -class Curve { - - constructor() { - - this.type = 'Curve'; - - this.arcLengthDivisions = 200; - - } - - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] - - getPoint( /* t, optionalTarget */ ) { - - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; - - } - - // Get point at relative position in curve according to arc length - // - u [0 .. 1] - - getPointAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); - - } + * @augments BufferGeometry + */ +class CapsuleGeometry extends BufferGeometry { - // Get sequence of points using getPoint( t ) + /** + * Constructs a new capsule geometry. + * + * @param {number} [radius=1] - Radius of the capsule. + * @param {number} [height=1] - Height of the middle section. + * @param {number} [capSegments=4] - Number of curve segments used to build each cap. + * @param {number} [radialSegments=8] - Number of segmented faces around the circumference of the capsule. Must be an integer >= 3. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the middle section. Must be an integer >= 1. + */ + constructor( radius = 1, height = 1, capSegments = 4, radialSegments = 8, heightSegments = 1 ) { - getPoints( divisions = 5 ) { + super(); - const points = []; + this.type = 'CapsuleGeometry'; - for ( let d = 0; d <= divisions; d ++ ) { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + capSegments: capSegments, + radialSegments: radialSegments, + heightSegments: heightSegments, + }; - points.push( this.getPoint( d / divisions ) ); + height = Math.max( 0, height ); + capSegments = Math.max( 1, Math.floor( capSegments ) ); + radialSegments = Math.max( 3, Math.floor( radialSegments ) ); + heightSegments = Math.max( 1, Math.floor( heightSegments ) ); - } + // buffers - return points; + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - } + // helper variables - // Get sequence of points using getPointAt( u ) + const halfHeight = height / 2; + const capArcLength = ( Math.PI / 2 ) * radius; + const cylinderPartLength = height; + const totalArcLength = 2 * capArcLength + cylinderPartLength; - getSpacedPoints( divisions = 5 ) { + const numVerticalSegments = capSegments * 2 + heightSegments; + const verticesPerRow = radialSegments + 1; - const points = []; + const normal = new Vector3(); + const vertex = new Vector3(); - for ( let d = 0; d <= divisions; d ++ ) { + // generate vertices, normals, and uvs - points.push( this.getPointAt( d / divisions ) ); + for ( let iy = 0; iy <= numVerticalSegments; iy ++ ) { - } + let currentArcLength = 0; + let profileY = 0; + let profileRadius = 0; + let normalYComponent = 0; - return points; + if ( iy <= capSegments ) { - } + // bottom cap + const segmentProgress = iy / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = - halfHeight - radius * Math.cos( angle ); + profileRadius = radius * Math.sin( angle ); + normalYComponent = - radius * Math.cos( angle ); + currentArcLength = segmentProgress * capArcLength; - // Get total curve arc length + } else if ( iy <= capSegments + heightSegments ) { - getLength() { + // middle section + const segmentProgress = ( iy - capSegments ) / heightSegments; + profileY = - halfHeight + segmentProgress * height; + profileRadius = radius; + normalYComponent = 0; + currentArcLength = capArcLength + segmentProgress * cylinderPartLength; - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; + } else { - } + // top cap + const segmentProgress = + ( iy - capSegments - heightSegments ) / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = halfHeight + radius * Math.sin( angle ); + profileRadius = radius * Math.cos( angle ); + normalYComponent = radius * Math.sin( angle ); + currentArcLength = + capArcLength + cylinderPartLength + segmentProgress * capArcLength; - // Get list of cumulative segment lengths + } - getLengths( divisions = this.arcLengthDivisions ) { + const v = Math.max( 0, Math.min( 1, currentArcLength / totalArcLength ) ); - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { - return this.cacheArcLengths; + // special case for the poles - } + let uOffset = 0; - this.needsUpdate = false; + if ( iy === 0 ) { - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; + uOffset = 0.5 / radialSegments; - cache.push( 0 ); + } else if ( iy === numVerticalSegments ) { - for ( let p = 1; p <= divisions; p ++ ) { + uOffset = -0.5 / radialSegments; - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; + } - } + for ( let ix = 0; ix <= radialSegments; ix ++ ) { - this.cacheArcLengths = cache; + const u = ix / radialSegments; + const theta = u * Math.PI * 2; - return cache; // { sums: cache, sum: sum }; Sum is in the last element. + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); - } + // vertex - updateArcLengths() { + vertex.x = - profileRadius * cosTheta; + vertex.y = profileY; + vertex.z = profileRadius * sinTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); - this.needsUpdate = true; - this.getLengths(); + // normal - } + normal.set( + - profileRadius * cosTheta, + normalYComponent, + profileRadius * sinTheta + ); + normal.normalize(); + normals.push( normal.x, normal.y, normal.z ); - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant + // uv - getUtoTmapping( u, distance ) { + uvs.push( u + uOffset, v ); - const arcLengths = this.getLengths(); + } - let i = 0; - const il = arcLengths.length; + if ( iy > 0 ) { - let targetArcLength; // The targeted u distance value to get + const prevIndexRow = ( iy - 1 ) * verticesPerRow; + for ( let ix = 0; ix < radialSegments; ix ++ ) { - if ( distance ) { + const i1 = prevIndexRow + ix; + const i2 = prevIndexRow + ix + 1; + const i3 = iy * verticesPerRow + ix; + const i4 = iy * verticesPerRow + ix + 1; - targetArcLength = distance; + indices.push( i1, i2, i3 ); + indices.push( i2, i4, i3 ); - } else { + } - targetArcLength = u * arcLengths[ il - 1 ]; + } } - // binary search for the index with largest value smaller than target u distance - - let low = 0, high = il - 1, comparison; - - while ( low <= high ) { + // build geometry - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - comparison = arcLengths[ i ] - targetArcLength; + } - if ( comparison < 0 ) { + copy( source ) { - low = i + 1; + super.copy( source ); - } else if ( comparison > 0 ) { + this.parameters = Object.assign( {}, source.parameters ); - high = i - 1; + return this; - } else { + } - high = i; - break; + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CapsuleGeometry} A new instance. + */ + static fromJSON( data ) { - // DONE + return new CapsuleGeometry( data.radius, data.height, data.capSegments, data.radialSegments, data.heightSegments ); - } + } - } +} - i = high; +/** + * A simple shape of Euclidean geometry. It is constructed from a + * number of triangular segments that are oriented around a central point and + * extend as far out as a given radius. It is built counter-clockwise from a + * start angle and a given central angle. It can also be used to create + * regular polygons, where the number of segments determines the number of + * sides. + * + * ```js + * const geometry = new THREE.CircleGeometry( 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const circle = new THREE.Mesh( geometry, material ); + * scene.add( circle ) + * ``` + * + * @augments BufferGeometry + */ +class CircleGeometry extends BufferGeometry { - if ( arcLengths[ i ] === targetArcLength ) { + /** + * Constructs a new circle geometry. + * + * @param {number} [radius=1] - Radius of the circle. + * @param {number} [segments=32] - Number of segments (triangles), minimum = `3`. + * @param {number} [thetaStart=0] - Start angle for first segment in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, + * of the circular sector in radians. The default value results in a complete circle. + */ + constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { - return i / ( il - 1 ); + super(); - } + this.type = 'CircleGeometry'; - // we could get finer grain at lengths, or use simple interpolation between two points + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; + segments = Math.max( 3, segments ); - const segmentLength = lengthAfter - lengthBefore; + // buffers - // determine where we are between the 'before' and 'after' points + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + // helper variables - // add that fractional amount to t + const vertex = new Vector3(); + const uv = new Vector2(); - const t = ( i + segmentFraction ) / ( il - 1 ); + // center point - return t; + vertices.push( 0, 0, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( 0.5, 0.5 ); - } + for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation + const segment = thetaStart + s / segments * thetaLength; - getTangent( t, optionalTarget ) { + // vertex - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); - // Capping in case of danger + vertices.push( vertex.x, vertex.y, vertex.z ); - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; + // normal - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); + normals.push( 0, 0, 1 ); - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); + // uvs - tangent.copy( pt2 ).sub( pt1 ).normalize(); + uv.x = ( vertices[ i ] / radius + 1 ) / 2; + uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; - return tangent; + uvs.push( uv.x, uv.y ); - } + } - getTangentAt( u, optionalTarget ) { + // indices - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); + for ( let i = 1; i <= segments; i ++ ) { - } + indices.push( i, i + 1, 0 ); - computeFrenetFrames( segments, closed ) { + } - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf + // build geometry - const normal = new Vector3(); + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - const tangents = []; - const normals = []; - const binormals = []; + } - const vec = new Vector3(); - const mat = new Matrix4(); + copy( source ) { - // compute the tangent vectors for each segment on the curve + super.copy( source ); - for ( let i = 0; i <= segments; i ++ ) { + this.parameters = Object.assign( {}, source.parameters ); - const u = i / segments; + return this; - tangents[ i ] = this.getTangentAt( u, new Vector3() ); + } - } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CircleGeometry} A new instance. + */ + static fromJSON( data ) { - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component + return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); + } - if ( tx <= min ) { +} - min = tx; - normal.set( 1, 0, 0 ); +/** + * A geometry class for representing a cylinder. + * + * ```js + * const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cylinder = new THREE.Mesh( geometry, material ); + * scene.add( cylinder ); + * ``` + * + * @augments BufferGeometry + */ +class CylinderGeometry extends BufferGeometry { - } + /** + * Constructs a new cylinder geometry. + * + * @param {number} [radiusTop=1] - Radius of the cylinder at the top. + * @param {number} [radiusBottom=1] - Radius of the cylinder at the bottom. + * @param {number} [height=1] - Height of the cylinder. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cylinder. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cylinder. + * @param {boolean} [openEnded=false] - Whether the base of the cylinder is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cylinder. + */ + constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { - if ( ty <= min ) { + super(); - min = ty; - normal.set( 0, 1, 0 ); + this.type = 'CylinderGeometry'; - } + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - if ( tz <= min ) { + const scope = this; - normal.set( 0, 0, 1 ); + radialSegments = Math.floor( radialSegments ); + heightSegments = Math.floor( heightSegments ); - } + // buffers - vec.crossVectors( tangents[ 0 ], normal ).normalize(); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + // helper variables + let index = 0; + const indexArray = []; + const halfHeight = height / 2; + let groupStart = 0; - // compute the slowly-varying normal and binormal vectors for each segment on the curve + // generate geometry - for ( let i = 1; i <= segments; i ++ ) { + generateTorso(); - normals[ i ] = normals[ i - 1 ].clone(); + if ( openEnded === false ) { - binormals[ i ] = binormals[ i - 1 ].clone(); + if ( radiusTop > 0 ) generateCap( true ); + if ( radiusBottom > 0 ) generateCap( false ); - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); + } - if ( vec.length() > Number.EPSILON ) { + // build geometry - vec.normalize(); + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors + function generateTorso() { - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + const normal = new Vector3(); + const vertex = new Vector3(); - } + let groupCount = 0; - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + // this will be used to calculate the normal + const slope = ( radiusBottom - radiusTop ) / height; - } + // generate vertices, normals and uvs - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + for ( let y = 0; y <= heightSegments; y ++ ) { - if ( closed === true ) { + const indexRow = []; - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; + const v = y / heightSegments; - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { + // calculate the radius of the current row - theta = - theta; + const radius = v * ( radiusBottom - radiusTop ) + radiusTop; - } + for ( let x = 0; x <= radialSegments; x ++ ) { - for ( let i = 1; i <= segments; i ++ ) { + const u = x / radialSegments; - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + const theta = u * thetaLength + thetaStart; - } + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); - } + // vertex - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; + vertex.x = radius * sinTheta; + vertex.y = - v * height + halfHeight; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); - } + // normal - clone() { + normal.set( sinTheta, slope, cosTheta ).normalize(); + normals.push( normal.x, normal.y, normal.z ); - return new this.constructor().copy( this ); + // uv - } + uvs.push( u, 1 - v ); - copy( source ) { + // save index of vertex in respective row - this.arcLengthDivisions = source.arcLengthDivisions; + indexRow.push( index ++ ); - return this; + } - } + // now save vertices of the row in our index array - toJSON() { + indexArray.push( indexRow ); - const data = { - metadata: { - version: 4.6, - type: 'Curve', - generator: 'Curve.toJSON' } - }; - - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; - return data; + // generate indices - } + for ( let x = 0; x < radialSegments; x ++ ) { - fromJSON( json ) { + for ( let y = 0; y < heightSegments; y ++ ) { - this.arcLengthDivisions = json.arcLengthDivisions; + // we use the index array to access the correct indices - return this; + const a = indexArray[ y ][ x ]; + const b = indexArray[ y + 1 ][ x ]; + const c = indexArray[ y + 1 ][ x + 1 ]; + const d = indexArray[ y ][ x + 1 ]; - } + // faces -} + if ( radiusTop > 0 || y !== 0 ) { -class EllipseCurve extends Curve { + indices.push( a, b, d ); + groupCount += 3; - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { + } - super(); + if ( radiusBottom > 0 || y !== heightSegments - 1 ) { - this.isEllipseCurve = true; + indices.push( b, c, d ); + groupCount += 3; - this.type = 'EllipseCurve'; + } - this.aX = aX; - this.aY = aY; + } - this.xRadius = xRadius; - this.yRadius = yRadius; + } - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; + // add a group to the geometry. this will ensure multi material support - this.aClockwise = aClockwise; + scope.addGroup( groupStart, groupCount, 0 ); - this.aRotation = aRotation; + // calculate new start value for groups - } + groupStart += groupCount; - getPoint( t, optionalTarget = new Vector2() ) { + } - const point = optionalTarget; + function generateCap( top ) { - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; + // save the index of the first center vertex + const centerIndexStart = index; - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; + const uv = new Vector2(); + const vertex = new Vector3(); - if ( deltaAngle < Number.EPSILON ) { + let groupCount = 0; - if ( samePoints ) { + const radius = ( top === true ) ? radiusTop : radiusBottom; + const sign = ( top === true ) ? 1 : -1; - deltaAngle = 0; + // first we generate the center vertex data of the cap. + // because the geometry needs one set of uvs per face, + // we must generate a center vertex per face/segment - } else { + for ( let x = 1; x <= radialSegments; x ++ ) { - deltaAngle = twoPi; + // vertex - } + vertices.push( 0, halfHeight * sign, 0 ); - } + // normal - if ( this.aClockwise === true && ! samePoints ) { + normals.push( 0, sign, 0 ); - if ( deltaAngle === twoPi ) { + // uv - deltaAngle = - twoPi; + uvs.push( 0.5, 0.5 ); - } else { + // increase index - deltaAngle = deltaAngle - twoPi; + index ++; } - } - - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); - - if ( this.aRotation !== 0 ) { - - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); - - const tx = x - this.aX; - const ty = y - this.aY; + // save the index of the last center vertex + const centerIndexEnd = index; - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; + // now we generate the surrounding vertices, normals and uvs - } + for ( let x = 0; x <= radialSegments; x ++ ) { - return point.set( x, y ); + const u = x / radialSegments; + const theta = u * thetaLength + thetaStart; - } + const cosTheta = Math.cos( theta ); + const sinTheta = Math.sin( theta ); - copy( source ) { + // vertex - super.copy( source ); + vertex.x = radius * sinTheta; + vertex.y = halfHeight * sign; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); - this.aX = source.aX; - this.aY = source.aY; + // normal - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; + normals.push( 0, sign, 0 ); - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; + // uv - this.aClockwise = source.aClockwise; + uv.x = ( cosTheta * 0.5 ) + 0.5; + uv.y = ( sinTheta * 0.5 * sign ) + 0.5; + uvs.push( uv.x, uv.y ); - this.aRotation = source.aRotation; + // increase index - return this; + index ++; - } + } - toJSON() { + // generate indices - const data = super.toJSON(); + for ( let x = 0; x < radialSegments; x ++ ) { - data.aX = this.aX; - data.aY = this.aY; + const c = centerIndexStart + x; + const i = centerIndexEnd + x; - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; + if ( top === true ) { - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; + // face top - data.aClockwise = this.aClockwise; + indices.push( i, i + 1, c ); - data.aRotation = this.aRotation; + } else { - return data; + // face bottom - } + indices.push( i + 1, i, c ); - fromJSON( json ) { + } - super.fromJSON( json ); + groupCount += 3; - this.aX = json.aX; - this.aY = json.aY; + } - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; + // add a group to the geometry. this will ensure multi material support - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; + scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); - this.aClockwise = json.aClockwise; + // calculate new start value for groups - this.aRotation = json.aRotation; + groupStart += groupCount; - return this; + } } -} + copy( source ) { -class ArcCurve extends EllipseCurve { + super.copy( source ); - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + this.parameters = Object.assign( {}, source.parameters ); - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + return this; - this.isArcCurve = true; + } - this.type = 'ArcCurve'; + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CylinderGeometry} A new instance. + */ + static fromJSON( data ) { + + return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } } /** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * A geometry class for representing a cone. + * + * ```js + * const geometry = new THREE.ConeGeometry( 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cone = new THREE.Mesh(geometry, material ); + * scene.add( cone ); + * ``` * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 + * @augments CylinderGeometry */ +class ConeGeometry extends CylinderGeometry { + /** + * Constructs a new cone geometry. + * + * @param {number} [radius=1] - Radius of the cone base. + * @param {number} [height=1] - Height of the cone. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cone. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cone. + * @param {boolean} [openEnded=false] - Whether the base of the cone is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cone. + */ + constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { -/* -Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM + super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); -This CubicPoly class could be used for reusing some variables and calculations, -but for three.js curve use, it could be possible inlined and flatten into a single function call -which can be placed in CurveUtils. -*/ + this.type = 'ConeGeometry'; -function CubicPoly() { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; + } - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {ConeGeometry} A new instance. */ - function init( x0, x1, t0, t1 ) { + static fromJSON( data ) { - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; + return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } - return { +} - initCatmullRom: function ( x0, x1, x2, x3, tension ) { +/** + * A polyhedron is a solid in three dimensions with flat faces. This class + * will take an array of vertices, project them onto a sphere, and then + * divide them up to the desired level of detail. + * + * @augments BufferGeometry + */ +class PolyhedronGeometry extends BufferGeometry { - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); + /** + * Constructs a new polyhedron geometry. + * + * @param {Array<number>} [vertices] - A flat array of vertices describing the base shape. + * @param {Array<number>} [indices] - A flat array of indices describing the base shape. + * @param {number} [radius=1] - The radius of the shape. + * @param {number} [detail=0] - How many levels to subdivide the geometry. The more detail, the smoother the shape. + */ + constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { - }, + super(); - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { + this.type = 'PolyhedronGeometry'; - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; + // default buffer data - init( x1, x2, t1, t2 ); + const vertexBuffer = []; + const uvBuffer = []; - }, + // the subdivision creates the vertex buffer data - calc: function ( t ) { + subdivide( detail ); - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; + // all vertices should lie on a conceptual sphere with a given radius - } + applyRadius( radius ); - }; + // finally, create the uv data -} + generateUVs(); -// + // build non-indexed geometry -const tmp = /*@__PURE__*/ new Vector3(); -const px = /*@__PURE__*/ new CubicPoly(); -const py = /*@__PURE__*/ new CubicPoly(); -const pz = /*@__PURE__*/ new CubicPoly(); + this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); -class CatmullRomCurve3 extends Curve { + if ( detail === 0 ) { - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { + this.computeVertexNormals(); // flat normals - super(); + } else { - this.isCatmullRomCurve3 = true; + this.normalizeNormals(); // smooth normals - this.type = 'CatmullRomCurve3'; + } - this.points = points; - this.closed = closed; - this.curveType = curveType; - this.tension = tension; + // helper functions - } + function subdivide( detail ) { - getPoint( t, optionalTarget = new Vector3() ) { + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); - const point = optionalTarget; + // iterate over all faces and apply a subdivision with the given detail value - const points = this.points; - const l = points.length; + for ( let i = 0; i < indices.length; i += 3 ) { - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; + // get the vertices of the face - if ( this.closed ) { + getVertexByIndex( indices[ i + 0 ], a ); + getVertexByIndex( indices[ i + 1 ], b ); + getVertexByIndex( indices[ i + 2 ], c ); - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; + // perform subdivision - } else if ( weight === 0 && intPoint === l - 1 ) { + subdivideFace( a, b, c, detail ); - intPoint = l - 2; - weight = 1; + } } - let p0, p3; // 4 points (p1 & p2 defined below) + function subdivideFace( a, b, c, detail ) { - if ( this.closed || intPoint > 0 ) { + const cols = detail + 1; - p0 = points[ ( intPoint - 1 ) % l ]; + // we use this multidimensional array as a data structure for creating the subdivision - } else { + const v = []; - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; + // construct all of the vertices for this subdivision - } + for ( let i = 0; i <= cols; i ++ ) { - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; + v[ i ] = []; - if ( this.closed || intPoint + 2 < l ) { + const aj = a.clone().lerp( c, i / cols ); + const bj = b.clone().lerp( c, i / cols ); - p3 = points[ ( intPoint + 2 ) % l ]; + const rows = cols - i; - } else { + for ( let j = 0; j <= rows; j ++ ) { - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; + if ( j === 0 && i === cols ) { - } + v[ i ][ j ] = aj; - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { + } else { - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); + v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; + } - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); + } - } else if ( this.curveType === 'catmullrom' ) { + } - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); + // construct all of the faces - } + for ( let i = 0; i < cols; i ++ ) { - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); + for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { - return point; + const k = Math.floor( j / 2 ); - } + if ( j % 2 === 0 ) { - copy( source ) { + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + pushVertex( v[ i ][ k ] ); - super.copy( source ); + } else { - this.points = []; + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); - for ( let i = 0, l = source.points.length; i < l; i ++ ) { + } - const point = source.points[ i ]; + } - this.points.push( point.clone() ); + } } - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; + function applyRadius( radius ) { - return this; + const vertex = new Vector3(); - } + // iterate over the entire buffer and apply the radius to each vertex - toJSON() { + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - const data = super.toJSON(); + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; - data.points = []; + vertex.normalize().multiplyScalar( radius ); - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + vertexBuffer[ i + 0 ] = vertex.x; + vertexBuffer[ i + 1 ] = vertex.y; + vertexBuffer[ i + 2 ] = vertex.z; - const point = this.points[ i ]; - data.points.push( point.toArray() ); + } } - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; + function generateUVs() { - return data; + const vertex = new Vector3(); - } + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - fromJSON( json ) { + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; - super.fromJSON( json ); + const u = azimuth( vertex ) / 2 / Math.PI + 0.5; + const v = inclination( vertex ) / Math.PI + 0.5; + uvBuffer.push( u, 1 - v ); - this.points = []; + } - for ( let i = 0, l = json.points.length; i < l; i ++ ) { + correctUVs(); - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); + correctSeam(); } - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; - - return this; + function correctSeam() { - } + // handle case when face straddles the seam, see #3269 -} + for ( let i = 0; i < uvBuffer.length; i += 6 ) { -/** - * Bezier Curves formulas obtained from - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve - */ + // uv data of a single face -function CatmullRom( t, p0, p1, p2, p3 ) { + const x0 = uvBuffer[ i + 0 ]; + const x1 = uvBuffer[ i + 2 ]; + const x2 = uvBuffer[ i + 4 ]; - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + const max = Math.max( x0, x1, x2 ); + const min = Math.min( x0, x1, x2 ); -} + // 0.9 is somewhat arbitrary -// + if ( max > 0.9 && min < 0.1 ) { -function QuadraticBezierP0( t, p ) { + if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; + if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; + if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; - const k = 1 - t; - return k * k * p; + } -} + } -function QuadraticBezierP1( t, p ) { + } - return 2 * ( 1 - t ) * t * p; + function pushVertex( vertex ) { -} + vertexBuffer.push( vertex.x, vertex.y, vertex.z ); -function QuadraticBezierP2( t, p ) { + } - return t * t * p; + function getVertexByIndex( index, vertex ) { -} + const stride = index * 3; -function QuadraticBezier( t, p0, p1, p2 ) { + vertex.x = vertices[ stride + 0 ]; + vertex.y = vertices[ stride + 1 ]; + vertex.z = vertices[ stride + 2 ]; - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); + } -} + function correctUVs() { -// + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); -function CubicBezierP0( t, p ) { + const centroid = new Vector3(); - const k = 1 - t; - return k * k * k * p; + const uvA = new Vector2(); + const uvB = new Vector2(); + const uvC = new Vector2(); -} + for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { -function CubicBezierP1( t, p ) { + a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); + b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); + c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); - const k = 1 - t; - return 3 * k * k * t * p; + uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); + uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); + uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); -} + centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); -function CubicBezierP2( t, p ) { + const azi = azimuth( centroid ); - return 3 * ( 1 - t ) * t * t * p; + correctUV( uvA, j + 0, a, azi ); + correctUV( uvB, j + 2, b, azi ); + correctUV( uvC, j + 4, c, azi ); -} + } -function CubicBezierP3( t, p ) { + } - return t * t * t * p; + function correctUV( uv, stride, vector, azimuth ) { -} + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { -function CubicBezier( t, p0, p1, p2, p3 ) { + uvBuffer[ stride ] = uv.x - 1; - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); + } -} + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { -class CubicBezierCurve extends Curve { + uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { + } - super(); + } - this.isCubicBezierCurve = true; + // Angle around the Y axis, counter-clockwise when looking from above. - this.type = 'CubicBezierCurve'; + function azimuth( vector ) { - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + return Math.atan2( vector.z, - vector.x ); - } + } - getPoint( t, optionalTarget = new Vector2() ) { - const point = optionalTarget; + // Angle above the XZ plane. - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + function inclination( vector ) { - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); + return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); - return point; + } } @@ -33131,198 +45417,287 @@ class CubicBezierCurve extends Curve { super.copy( source ); - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + this.parameters = Object.assign( {}, source.parameters ); return this; } - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PolyhedronGeometry} A new instance. + */ + static fromJSON( data ) { - return this; + return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); } } -class CubicBezierCurve3 extends Curve { +/** + * A geometry class for representing a dodecahedron. + * + * ```js + * const geometry = new THREE.DodecahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const dodecahedron = new THREE.Mesh( geometry, material ); + * scene.add( dodecahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class DodecahedronGeometry extends PolyhedronGeometry { - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { + /** + * Constructs a new dodecahedron geometry. + * + * @param {number} [radius=1] - Radius of the dodecahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a dodecahedron. + */ + constructor( radius = 1, detail = 0 ) { - super(); + const t = ( 1 + Math.sqrt( 5 ) ) / 2; + const r = 1 / t; - this.isCubicBezierCurve3 = true; + const vertices = [ - this.type = 'CubicBezierCurve3'; + // (±1, ±1, ±1) + -1, -1, -1, -1, -1, 1, + -1, 1, -1, -1, 1, 1, + 1, -1, -1, 1, -1, 1, + 1, 1, -1, 1, 1, 1, - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + // (0, ±1/φ, ±φ) + 0, - r, - t, 0, - r, t, + 0, r, - t, 0, r, t, - } + // (±1/φ, ±φ, 0) + - r, - t, 0, - r, t, 0, + r, - t, 0, r, t, 0, - getPoint( t, optionalTarget = new Vector3() ) { + // (±φ, 0, ±1/φ) + - t, 0, - r, t, 0, - r, + - t, 0, r, t, 0, r + ]; - const point = optionalTarget; + const indices = [ + 3, 11, 7, 3, 7, 15, 3, 15, 13, + 7, 19, 17, 7, 17, 6, 7, 6, 15, + 17, 4, 8, 17, 8, 10, 17, 10, 6, + 8, 0, 16, 8, 16, 2, 8, 2, 10, + 0, 12, 1, 0, 1, 18, 0, 18, 16, + 6, 10, 2, 6, 2, 13, 6, 13, 15, + 2, 16, 18, 2, 18, 3, 2, 3, 13, + 18, 1, 9, 18, 9, 11, 18, 11, 3, + 4, 14, 12, 4, 12, 0, 4, 0, 8, + 11, 9, 5, 11, 5, 19, 11, 19, 7, + 19, 5, 14, 19, 14, 4, 19, 4, 17, + 1, 12, 14, 1, 14, 5, 1, 5, 9 + ]; - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + super( vertices, indices, radius, detail ); - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); + this.type = 'DodecahedronGeometry'; - return point; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; } - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {DodecahedronGeometry} A new instance. + */ + static fromJSON( data ) { - return this; + return new DodecahedronGeometry( data.radius, data.detail ); } - toJSON() { +} - const data = super.toJSON(); +const _v0 = /*@__PURE__*/ new Vector3(); +const _v1$1 = /*@__PURE__*/ new Vector3(); +const _normal = /*@__PURE__*/ new Vector3(); +const _triangle = /*@__PURE__*/ new Triangle(); - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); +/** + * Can be used as a helper object to view the edges of a geometry. + * + * ```js + * const geometry = new THREE.BoxGeometry(); + * const edges = new THREE.EdgesGeometry( geometry ); + * const line = new THREE.LineSegments( edges ); + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ +class EdgesGeometry extends BufferGeometry { - return data; + /** + * Constructs a new edges geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + * @param {number} [thresholdAngle=1] - An edge is only rendered if the angle (in degrees) + * between the face normals of the adjoining faces exceeds this value. + */ + constructor( geometry = null, thresholdAngle = 1 ) { - } + super(); - fromJSON( json ) { + this.type = 'EdgesGeometry'; - super.fromJSON( json ); + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + geometry: geometry, + thresholdAngle: thresholdAngle + }; - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); + if ( geometry !== null ) { - return this; + const precisionPoints = 4; + const precision = Math.pow( 10, precisionPoints ); + const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); - } + const indexAttr = geometry.getIndex(); + const positionAttr = geometry.getAttribute( 'position' ); + const indexCount = indexAttr ? indexAttr.count : positionAttr.count; -} + const indexArr = [ 0, 0, 0 ]; + const vertKeys = [ 'a', 'b', 'c' ]; + const hashes = new Array( 3 ); -class LineCurve extends Curve { + const edgeData = {}; + const vertices = []; + for ( let i = 0; i < indexCount; i += 3 ) { - constructor( v1 = new Vector2(), v2 = new Vector2() ) { + if ( indexAttr ) { - super(); + indexArr[ 0 ] = indexAttr.getX( i ); + indexArr[ 1 ] = indexAttr.getX( i + 1 ); + indexArr[ 2 ] = indexAttr.getX( i + 2 ); - this.isLineCurve = true; + } else { - this.type = 'LineCurve'; + indexArr[ 0 ] = i; + indexArr[ 1 ] = i + 1; + indexArr[ 2 ] = i + 2; - this.v1 = v1; - this.v2 = v2; + } - } + const { a, b, c } = _triangle; + a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); + b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); + c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); + _triangle.getNormal( _normal ); - getPoint( t, optionalTarget = new Vector2() ) { + // create hashes for the edge from the vertices + hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; + hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; + hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; - const point = optionalTarget; + // skip degenerate triangles + if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { - if ( t === 1 ) { + continue; - point.copy( this.v2 ); + } - } else { + // iterate over every edge + for ( let j = 0; j < 3; j ++ ) { - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); + // get the first and next vertex making up the edge + const jNext = ( j + 1 ) % 3; + const vecHash0 = hashes[ j ]; + const vecHash1 = hashes[ jNext ]; + const v0 = _triangle[ vertKeys[ j ] ]; + const v1 = _triangle[ vertKeys[ jNext ] ]; - } + const hash = `${ vecHash0 }_${ vecHash1 }`; + const reverseHash = `${ vecHash1 }_${ vecHash0 }`; - return point; + if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { - } + // if we found a sibling edge add it into the vertex array if + // it meets the angle threshold and delete the edge from the map. + if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { + vertices.push( v0.x, v0.y, v0.z ); + vertices.push( v1.x, v1.y, v1.z ); - return this.getPoint( u, optionalTarget ); + } - } + edgeData[ reverseHash ] = null; - getTangent( t, optionalTarget = new Vector2() ) { + } else if ( ! ( hash in edgeData ) ) { - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + // if we've already got an edge here then skip adding a new one + edgeData[ hash ] = { - } + index0: indexArr[ j ], + index1: indexArr[ jNext ], + normal: _normal.clone(), - getTangentAt( u, optionalTarget ) { + }; - return this.getTangent( u, optionalTarget ); + } - } + } - copy( source ) { + } - super.copy( source ); + // iterate over all remaining, unmatched edges and add them to the vertex array + for ( const key in edgeData ) { - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + if ( edgeData[ key ] ) { - return this; + const { index0, index1 } = edgeData[ key ]; + _v0.fromBufferAttribute( positionAttr, index0 ); + _v1$1.fromBufferAttribute( positionAttr, index1 ); - } + vertices.push( _v0.x, _v0.y, _v0.z ); + vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); - toJSON() { + } - const data = super.toJSON(); + } - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - return data; + } } - fromJSON( json ) { + copy( source ) { - super.fromJSON( json ); + super.copy( source ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + this.parameters = Object.assign( {}, source.parameters ); return this; @@ -33330,528 +45705,701 @@ class LineCurve extends Curve { } -class LineCurve3 extends Curve { +/** + * An abstract base class for creating an analytic curve object that contains methods + * for interpolation. + * + * @abstract + */ +class Curve { - constructor( v1 = new Vector3(), v2 = new Vector3() ) { + /** + * Constructs a new curve. + */ + constructor() { - super(); + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Curve'; - this.isLineCurve3 = true; + /** + * This value determines the amount of divisions when calculating the + * cumulative segment lengths of a curve via {@link Curve#getLengths}. To ensure + * precision when using methods like {@link Curve#getSpacedPoints}, it is + * recommended to increase the value of this property if the curve is very large. + * + * @type {number} + * @default 200 + */ + this.arcLengthDivisions = 200; - this.type = 'LineCurve3'; + /** + * Must be set to `true` if the curve parameters have changed. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; - this.v1 = v1; - this.v2 = v2; + /** + * An internal cache that holds precomputed curve length values. + * + * @private + * @type {?Array<number>} + * @default null + */ + this.cacheArcLengths = null; } - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. + * + * @abstract + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( /* t, optionalTarget */ ) { - return point; + console.warn( 'THREE.Curve: .getPoint() not implemented.' ); } - // Line curve is linear, so we can overwrite default getPointAt + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. Unlike {@link Curve#getPoint}, this method honors the length + * of the curve which equidistant samples. + * + * @param {number} u - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ getPointAt( u, optionalTarget ) { - return this.getPoint( u, optionalTarget ); + const t = this.getUtoTmapping( u ); + return this.getPoint( t, optionalTarget ); } - getTangent( t, optionalTarget = new Vector3() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + /** + * This method samples the curve via {@link Curve#getPoint} and returns an array of points representing + * the curve shape. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getPoints( divisions = 5 ) { - } + const points = []; - getTangentAt( u, optionalTarget ) { + for ( let d = 0; d <= divisions; d ++ ) { - return this.getTangent( u, optionalTarget ); + points.push( this.getPoint( d / divisions ) ); - } + } - copy( source ) { + return points; - super.copy( source ); + } - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + // Get sequence of points using getPointAt( u ) - return this; + /** + * This method samples the curve via {@link Curve#getPointAt} and returns an array of points representing + * the curve shape. Unlike {@link Curve#getPoints}, this method returns equi-spaced points across the entire + * curve. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getSpacedPoints( divisions = 5 ) { - } + const points = []; - toJSON() { + for ( let d = 0; d <= divisions; d ++ ) { - const data = super.toJSON(); + points.push( this.getPointAt( d / divisions ) ); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + } - return data; + return points; } - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + /** + * Returns the total arc length of the curve. + * + * @return {number} The length of the curve. + */ + getLength() { - return this; + const lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; } -} - -class QuadraticBezierCurve extends Curve { + /** + * Returns an array of cumulative segment lengths of the curve. + * + * @param {number} [divisions=this.arcLengthDivisions] - The number of divisions. + * @return {Array<number>} An array holding the cumulative segment lengths. + */ + getLengths( divisions = this.arcLengthDivisions ) { - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { + if ( this.cacheArcLengths && + ( this.cacheArcLengths.length === divisions + 1 ) && + ! this.needsUpdate ) { - super(); + return this.cacheArcLengths; - this.isQuadraticBezierCurve = true; + } - this.type = 'QuadraticBezierCurve'; + this.needsUpdate = false; - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + const cache = []; + let current, last = this.getPoint( 0 ); + let sum = 0; - } + cache.push( 0 ); - getPoint( t, optionalTarget = new Vector2() ) { + for ( let p = 1; p <= divisions; p ++ ) { - const point = optionalTarget; + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + } - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); + this.cacheArcLengths = cache; - return point; + return cache; // { sums: cache, sum: sum }; Sum is in the last element. } - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + /** + * Update the cumulative segment distance cache. The method must be called + * every time curve parameters are changed. If an updated curve is part of a + * composed curve like {@link CurvePath}, this method must be called on the + * composed curve, too. + */ + updateArcLengths() { - return this; + this.needsUpdate = true; + this.getLengths(); } - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; + /** + * Given an interpolation factor in the range `[0,1]`, this method returns an updated + * interpolation factor in the same range that can be ued to sample equidistant points + * from a curve. + * + * @param {number} u - The interpolation factor. + * @param {?number} distance - An optional distance on the curve. + * @return {number} The updated interpolation factor. + */ + getUtoTmapping( u, distance = null ) { - } + const arcLengths = this.getLengths(); - fromJSON( json ) { + let i = 0; + const il = arcLengths.length; - super.fromJSON( json ); + let targetArcLength; // The targeted u distance value to get - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + if ( distance ) { - return this; + targetArcLength = distance; - } + } else { -} + targetArcLength = u * arcLengths[ il - 1 ]; -class QuadraticBezierCurve3 extends Curve { + } - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { + // binary search for the index with largest value smaller than target u distance - super(); + let low = 0, high = il - 1, comparison; - this.isQuadraticBezierCurve3 = true; + while ( low <= high ) { - this.type = 'QuadraticBezierCurve3'; + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + comparison = arcLengths[ i ] - targetArcLength; - } + if ( comparison < 0 ) { - getPoint( t, optionalTarget = new Vector3() ) { + low = i + 1; - const point = optionalTarget; + } else if ( comparison > 0 ) { - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + high = i - 1; - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); + } else { - return point; + high = i; + break; - } + // DONE - copy( source ) { + } - super.copy( source ); + } - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + i = high; - return this; + if ( arcLengths[ i ] === targetArcLength ) { - } + return i / ( il - 1 ); - toJSON() { + } - const data = super.toJSON(); + // we could get finer grain at lengths, or use simple interpolation between two points - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + const lengthBefore = arcLengths[ i ]; + const lengthAfter = arcLengths[ i + 1 ]; - return data; + const segmentLength = lengthAfter - lengthBefore; - } + // determine where we are between the 'before' and 'after' points - fromJSON( json ) { + const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - super.fromJSON( json ); + // add that fractional amount to t - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + const t = ( i + segmentFraction ) / ( il - 1 ); - return this; + return t; } -} - -class SplineCurve extends Curve { + /** + * Returns a unit vector tangent for the given interpolation factor. + * If the derived curve does not implement its tangent derivation, + * two points a small delta apart will be used to find its gradient + * which seems to give a reasonable approximation. + * + * @param {number} t - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + */ + getTangent( t, optionalTarget ) { - constructor( points = [] ) { + const delta = 0.0001; + let t1 = t - delta; + let t2 = t + delta; - super(); + // Capping in case of danger - this.isSplineCurve = true; + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; - this.type = 'SplineCurve'; + const pt1 = this.getPoint( t1 ); + const pt2 = this.getPoint( t2 ); - this.points = points; + const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - } + tangent.copy( pt2 ).sub( pt1 ).normalize(); - getPoint( t, optionalTarget = new Vector2() ) { + return tangent; - const point = optionalTarget; + } - const points = this.points; - const p = ( points.length - 1 ) * t; + /** + * Same as {@link Curve#getTangent} but with equidistant samples. + * + * @param {number} u - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + * @see {@link Curve#getPointAt} + */ + getTangentAt( u, optionalTarget ) { - const intPoint = Math.floor( p ); - const weight = p - intPoint; + const t = this.getUtoTmapping( u ); + return this.getTangent( t, optionalTarget ); - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + } - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); + /** + * Generates the Frenet Frames. Requires a curve definition in 3D space. Used + * in geometries like {@link TubeGeometry} or {@link ExtrudeGeometry}. + * + * @param {number} segments - The number of segments. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @return {{tangents: Array<Vector3>, normals: Array<Vector3>, binormals: Array<Vector3>}} The Frenet Frames. + */ + computeFrenetFrames( segments, closed = false ) { - return point; + // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - } + const normal = new Vector3(); - copy( source ) { + const tangents = []; + const normals = []; + const binormals = []; - super.copy( source ); + const vec = new Vector3(); + const mat = new Matrix4(); - this.points = []; + // compute the tangent vectors for each segment on the curve - for ( let i = 0, l = source.points.length; i < l; i ++ ) { + for ( let i = 0; i <= segments; i ++ ) { - const point = source.points[ i ]; + const u = i / segments; - this.points.push( point.clone() ); + tangents[ i ] = this.getTangentAt( u, new Vector3() ); } - return this; + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the minimum tangent xyz component - } + normals[ 0 ] = new Vector3(); + binormals[ 0 ] = new Vector3(); + let min = Number.MAX_VALUE; + const tx = Math.abs( tangents[ 0 ].x ); + const ty = Math.abs( tangents[ 0 ].y ); + const tz = Math.abs( tangents[ 0 ].z ); - toJSON() { + if ( tx <= min ) { - const data = super.toJSON(); + min = tx; + normal.set( 1, 0, 0 ); - data.points = []; + } - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + if ( ty <= min ) { - const point = this.points[ i ]; - data.points.push( point.toArray() ); + min = ty; + normal.set( 0, 1, 0 ); } - return data; + if ( tz <= min ) { - } + normal.set( 0, 0, 1 ); - fromJSON( json ) { + } - super.fromJSON( json ); + vec.crossVectors( tangents[ 0 ], normal ).normalize(); - this.points = []; + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); + // compute the slowly-varying normal and binormal vectors for each segment on the curve - } + for ( let i = 1; i <= segments; i ++ ) { - return this; + normals[ i ] = normals[ i - 1 ].clone(); - } + binormals[ i ] = binormals[ i - 1 ].clone(); -} + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); -var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve -}); + if ( vec.length() > Number.EPSILON ) { -/************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ + vec.normalize(); -class CurvePath extends Curve { + const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors - constructor() { + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - super(); + } - this.type = 'CurvePath'; + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - this.curves = []; - this.autoClose = false; // Automatically closes the path + } - } + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - add( curve ) { + if ( closed === true ) { - this.curves.push( curve ); + let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), -1, 1 ) ); + theta /= segments; - } + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - closePath() { + theta = - theta; - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); + } - if ( ! startPoint.equals( endPoint ) ) { + for ( let i = 1; i <= segments; i ++ ) { - const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; - this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } } - return this; + return { + tangents: tangents, + normals: normals, + binormals: binormals + }; } - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: - - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') - - getPoint( t, optionalTarget ) { + /** + * Returns a new curve with copied values from this instance. + * + * @return {Curve} A clone of this instance. + */ + clone() { - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; + return new this.constructor().copy( this ); - // To think about boundaries points. + } - while ( i < curveLengths.length ) { + /** + * Copies the values of the given curve to this instance. + * + * @param {Curve} source - The curve to copy. + * @return {Curve} A reference to this curve. + */ + copy( source ) { - if ( curveLengths[ i ] >= d ) { + this.arcLengthDivisions = source.arcLengthDivisions; - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; + return this; - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + } - return curve.getPointAt( u, optionalTarget ); + /** + * Serializes the curve into JSON. + * + * @return {Object} A JSON object representing the serialized curve. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + const data = { + metadata: { + version: 4.7, + type: 'Curve', + generator: 'Curve.toJSON' } + }; - i ++; - - } - - return null; + data.arcLengthDivisions = this.arcLengthDivisions; + data.type = this.type; - // loop where sum != 0, sum > d , sum+1 <d + return data; } - // We cannot use the default THREE.Curve getPoint() with getLength() because in - // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath - // getPoint() depends on getLength + /** + * Deserializes the curve from the given JSON. + * + * @param {Object} json - The JSON holding the serialized curve. + * @return {Curve} A reference to this curve. + */ + fromJSON( json ) { - getLength() { + this.arcLengthDivisions = json.arcLengthDivisions; - const lens = this.getCurveLengths(); - return lens[ lens.length - 1 ]; + return this; } - // cacheLengths must be recalculated. - updateArcLengths() { - - this.needsUpdate = true; - this.cacheLengths = null; - this.getCurveLengths(); - - } +} - // Compute lengths and cache them - // We cannot overwrite getLengths() because UtoT mapping uses it. +/** + * A curve representing an ellipse. + * + * ```js + * const curve = new THREE.EllipseCurve( + * 0, 0, + * 10, 10, + * 0, 2 * Math.PI, + * false, + * 0 + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const ellipse = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class EllipseCurve extends Curve { - getCurveLengths() { + /** + * Constructs a new ellipse curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [yRadius=1] - The radius of the ellipse in the y direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + */ + constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - // We use cache values if curves and cache array are same length + super(); - if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEllipseCurve = true; - return this.cacheLengths; + this.type = 'EllipseCurve'; - } + /** + * The X center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aX = aX; - // Get length of sub-curve - // Push sums into cached array + /** + * The Y center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aY = aY; - const lengths = []; - let sums = 0; + /** + * The radius of the ellipse in the x direction. + * Setting the this value equal to the {@link EllipseCurve#yRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.xRadius = xRadius; - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + /** + * The radius of the ellipse in the y direction. + * Setting the this value equal to the {@link EllipseCurve#xRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.yRadius = yRadius; - sums += this.curves[ i ].getLength(); - lengths.push( sums ); + /** + * The start angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aStartAngle = aStartAngle; - } + /** + * The end angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default Math.PI*2 + */ + this.aEndAngle = aEndAngle; - this.cacheLengths = lengths; + /** + * Whether the ellipse is drawn clockwise or not. + * + * @type {boolean} + * @default false + */ + this.aClockwise = aClockwise; - return lengths; + /** + * The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aRotation = aRotation; } - getSpacedPoints( divisions = 40 ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - const points = []; + const point = optionalTarget; - for ( let i = 0; i <= divisions; i ++ ) { + const twoPi = Math.PI * 2; + let deltaAngle = this.aEndAngle - this.aStartAngle; + const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - points.push( this.getPoint( i / divisions ) ); + // ensures that deltaAngle is 0 .. 2 PI + while ( deltaAngle < 0 ) deltaAngle += twoPi; + while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - } + if ( deltaAngle < Number.EPSILON ) { - if ( this.autoClose ) { + if ( samePoints ) { - points.push( points[ 0 ] ); + deltaAngle = 0; - } + } else { - return points; + deltaAngle = twoPi; - } + } - getPoints( divisions = 12 ) { + } - const points = []; - let last; + if ( this.aClockwise === true && ! samePoints ) { - for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { + if ( deltaAngle === twoPi ) { - const curve = curves[ i ]; - const resolution = curve.isEllipseCurve ? divisions * 2 - : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 - : curve.isSplineCurve ? divisions * curve.points.length - : divisions; + deltaAngle = - twoPi; - const pts = curve.getPoints( resolution ); + } else { - for ( let j = 0; j < pts.length; j ++ ) { + deltaAngle = deltaAngle - twoPi; - const point = pts[ j ]; + } - if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates + } - points.push( point ); - last = point; + const angle = this.aStartAngle + t * deltaAngle; + let x = this.aX + this.xRadius * Math.cos( angle ); + let y = this.aY + this.yRadius * Math.sin( angle ); - } + if ( this.aRotation !== 0 ) { - } + const cos = Math.cos( this.aRotation ); + const sin = Math.sin( this.aRotation ); - if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { + const tx = x - this.aX; + const ty = y - this.aY; - points.push( points[ 0 ] ); + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + this.aX; + y = tx * sin + ty * cos + this.aY; } - return points; + return point.set( x, y ); } @@ -33859,17 +46407,18 @@ class CurvePath extends Curve { super.copy( source ); - this.curves = []; - - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { + this.aX = source.aX; + this.aY = source.aY; - const curve = source.curves[ i ]; + this.xRadius = source.xRadius; + this.yRadius = source.yRadius; - this.curves.push( curve.clone() ); + this.aStartAngle = source.aStartAngle; + this.aEndAngle = source.aEndAngle; - } + this.aClockwise = source.aClockwise; - this.autoClose = source.autoClose; + this.aRotation = source.aRotation; return this; @@ -33879,15 +46428,18 @@ class CurvePath extends Curve { const data = super.toJSON(); - data.autoClose = this.autoClose; - data.curves = []; + data.aX = this.aX; + data.aY = this.aY; - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + data.xRadius = this.xRadius; + data.yRadius = this.yRadius; - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); + data.aStartAngle = this.aStartAngle; + data.aEndAngle = this.aEndAngle; - } + data.aClockwise = this.aClockwise; + + data.aRotation = this.aRotation; return data; @@ -33897,15 +46449,18 @@ class CurvePath extends Curve { super.fromJSON( json ); - this.autoClose = json.autoClose; - this.curves = []; + this.aX = json.aX; + this.aY = json.aY; - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { + this.xRadius = json.xRadius; + this.yRadius = json.yRadius; - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); + this.aStartAngle = json.aStartAngle; + this.aEndAngle = json.aEndAngle; - } + this.aClockwise = json.aClockwise; + + this.aRotation = json.aRotation; return this; @@ -33913,157 +46468,295 @@ class CurvePath extends Curve { } -class Path extends CurvePath { - - constructor( points ) { +/** + * A curve representing an arc. + * + * @augments EllipseCurve + */ +class ArcCurve extends EllipseCurve { - super(); + /** + * Constructs a new arc curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [aRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + */ + constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - this.type = 'Path'; + super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - this.currentPoint = new Vector2(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArcCurve = true; - if ( points ) { + this.type = 'ArcCurve'; - this.setFromPoints( points ); + } - } +} - } +function CubicPoly() { - setFromPoints( points ) { + /** + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ - this.moveTo( points[ 0 ].x, points[ 0 ].y ); + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM - for ( let i = 1, l = points.length; i < l; i ++ ) { + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ - this.lineTo( points[ i ].x, points[ i ].y ); + let c0 = 0, c1 = 0, c2 = 0, c3 = 0; - } + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + function init( x0, x1, t0, t1 ) { - return this; + c0 = x0; + c1 = t0; + c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1; + c3 = 2 * x0 - 2 * x1 + t0 + t1; } - moveTo( x, y ) { + return { - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? + initCatmullRom: function ( x0, x1, x2, x3, tension ) { - return this; + init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - } + }, - lineTo( x, y ) { + initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); + // compute tangents when parameterized in [t1,t2] + let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - this.currentPoint.set( x, y ); + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; - return this; + init( x1, x2, t1, t2 ); - } + }, - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + calc: function ( t ) { - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); + const t2 = t * t; + const t3 = t2 * t; + return c0 + c1 * t + c2 * t2 + c3 * t3; - this.curves.push( curve ); + } - this.currentPoint.set( aX, aY ); + }; - return this; +} - } +// - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { +const tmp = /*@__PURE__*/ new Vector3(); +const px = /*@__PURE__*/ new CubicPoly(); +const py = /*@__PURE__*/ new CubicPoly(); +const pz = /*@__PURE__*/ new CubicPoly(); - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); +/** + * A curve representing a Catmull-Rom spline. + * + * ```js + * //Create a closed wavey loop + * const curve = new THREE.CatmullRomCurve3( [ + * new THREE.Vector3( -10, 0, 10 ), + * new THREE.Vector3( -5, 5, 5 ), + * new THREE.Vector3( 0, 0, 0 ), + * new THREE.Vector3( 5, -5, 5 ), + * new THREE.Vector3( 10, 0, 10 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CatmullRomCurve3 extends Curve { - this.curves.push( curve ); + /** + * Constructs a new Catmull-Rom curve. + * + * @param {Array<Vector3>} [points] - An array of 3D points defining the curve. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @param {('centripetal'|'chordal'|'catmullrom')} [curveType='centripetal'] - The curve type. + * @param {number} [tension=0.5] - Tension of the curve. + */ + constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { - this.currentPoint.set( aX, aY ); + super(); - return this; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCatmullRomCurve3 = true; + + this.type = 'CatmullRomCurve3'; + + /** + * An array of 3D points defining the curve. + * + * @type {Array<Vector3>} + */ + this.points = points; + + /** + * Whether the curve is closed or not. + * + * @type {boolean} + * @default false + */ + this.closed = closed; + + /** + * The curve type. + * + * @type {('centripetal'|'chordal'|'catmullrom')} + * @default 'centripetal' + */ + this.curveType = curveType; + + /** + * Tension of the curve. + * + * @type {number} + * @default 0.5 + */ + this.tension = tension; } - splineThru( pts /*Array of Vector*/ ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - const npts = [ this.currentPoint.clone() ].concat( pts ); + const point = optionalTarget; - const curve = new SplineCurve( npts ); - this.curves.push( curve ); + const points = this.points; + const l = points.length; - this.currentPoint.copy( pts[ pts.length - 1 ] ); + const p = ( l - ( this.closed ? 0 : 1 ) ) * t; + let intPoint = Math.floor( p ); + let weight = p - intPoint; - return this; + if ( this.closed ) { - } + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + } else if ( weight === 0 && intPoint === l - 1 ) { - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; + intPoint = l - 2; + weight = 1; - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); + } - return this; + let p0, p3; // 4 points (p1 & p2 defined below) - } + if ( this.closed || intPoint > 0 ) { - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + p0 = points[ ( intPoint - 1 ) % l ]; - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + } else { - return this; + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; - } + } - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + const p1 = points[ intPoint % l ]; + const p2 = points[ ( intPoint + 1 ) % l ]; - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; + if ( this.closed || intPoint + 2 < l ) { - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + p3 = points[ ( intPoint + 2 ) % l ]; - return this; + } else { - } + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); + p3 = tmp; - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + } - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - if ( this.curves.length > 0 ) { + // init Centripetal / Chordal Catmull-Rom + const pow = this.curveType === 'chordal' ? 0.5 : 0.25; + let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; - if ( ! firstPoint.equals( this.currentPoint ) ) { + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - this.lineTo( firstPoint.x, firstPoint.y ); + } else if ( this.curveType === 'catmullrom' ) { - } + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); } - this.curves.push( curve ); - - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); + point.set( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); - return this; + return point; } @@ -34071,7 +46764,19 @@ class Path extends CurvePath { super.copy( source ); - this.currentPoint.copy( source.currentPoint ); + this.points = []; + + for ( let i = 0, l = source.points.length; i < l; i ++ ) { + + const point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + this.closed = source.closed; + this.curveType = source.curveType; + this.tension = source.tension; return this; @@ -34081,7 +46786,18 @@ class Path extends CurvePath { const data = super.toJSON(); - data.currentPoint = this.currentPoint.toArray(); + data.points = []; + + for ( let i = 0, l = this.points.length; i < l; i ++ ) { + + const point = this.points[ i ]; + data.points.push( point.toArray() ); + + } + + data.closed = this.closed; + data.curveType = this.curveType; + data.tension = this.tension; return data; @@ -34091,576 +46807,841 @@ class Path extends CurvePath { super.fromJSON( json ); - this.currentPoint.fromArray( json.currentPoint ); + this.points = []; - return this; + for ( let i = 0, l = json.points.length; i < l; i ++ ) { - } + const point = json.points[ i ]; + this.points.push( new Vector3().fromArray( point ) ); -} + } -class LatheGeometry extends BufferGeometry { + this.closed = json.closed; + this.curveType = json.curveType; + this.tension = json.tension; - constructor( points = [ new Vector2( 0, - 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { + return this; - super(); + } - this.type = 'LatheGeometry'; +} - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; +// Bezier Curves formulas obtained from: https://en.wikipedia.org/wiki/B%C3%A9zier_curve - segments = Math.floor( segments ); +/** + * Computes a point on a Catmull-Rom spline. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Catmull-Rom spline. + */ +function CatmullRom( t, p0, p1, p2, p3 ) { - // clamp phiLength so it's in range of [ 0, 2PI ] + const v0 = ( p2 - p0 ) * 0.5; + const v1 = ( p3 - p1 ) * 0.5; + const t2 = t * t; + const t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( -3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - phiLength = clamp( phiLength, 0, Math.PI * 2 ); +} - // buffers +// - const indices = []; - const vertices = []; - const uvs = []; - const initNormals = []; - const normals = []; +function QuadraticBezierP0( t, p ) { - // helper variables + const k = 1 - t; + return k * k * p; - const inverseSegments = 1.0 / segments; - const vertex = new Vector3(); - const uv = new Vector2(); - const normal = new Vector3(); - const curNormal = new Vector3(); - const prevNormal = new Vector3(); - let dx = 0; - let dy = 0; +} - // pre-compute normals for initial "meridian" +function QuadraticBezierP1( t, p ) { - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + return 2 * ( 1 - t ) * t * p; - switch ( j ) { +} - case 0: // special handling for 1st vertex on path +function QuadraticBezierP2( t, p ) { - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; + return t * t * p; - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; +} - prevNormal.copy( normal ); +/** + * Computes a point on a Quadratic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @return {number} The calculated point on a Quadratic Bezier curve. + */ +function QuadraticBezier( t, p0, p1, p2 ) { - normal.normalize(); + return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + + QuadraticBezierP2( t, p2 ); - initNormals.push( normal.x, normal.y, normal.z ); +} - break; +// - case ( points.length - 1 ): // special handling for last Vertex on path +function CubicBezierP0( t, p ) { - initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); + const k = 1 - t; + return k * k * k * p; - break; +} - default: // default handling for all vertices in between +function CubicBezierP1( t, p ) { - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; + const k = 1 - t; + return 3 * k * k * t * p; - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; +} - curNormal.copy( normal ); +function CubicBezierP2( t, p ) { - normal.x += prevNormal.x; - normal.y += prevNormal.y; - normal.z += prevNormal.z; + return 3 * ( 1 - t ) * t * t * p; - normal.normalize(); +} - initNormals.push( normal.x, normal.y, normal.z ); +function CubicBezierP3( t, p ) { - prevNormal.copy( curNormal ); + return t * t * t * p; - } +} - } +/** + * Computes a point on a Cubic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Cubic Bezier curve. + */ +function CubicBezier( t, p0, p1, p2, p3 ) { - // generate vertices, uvs and normals + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); - for ( let i = 0; i <= segments; i ++ ) { +} - const phi = phiStart + i * inverseSegments * phiLength; +/** + * A curve representing a 2D Cubic Bezier curve. + * + * ```js + * const curve = new THREE.CubicBezierCurve( + * new THREE.Vector2( - 0, 0 ), + * new THREE.Vector2( - 5, 15 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CubicBezierCurve extends Curve { - const sin = Math.sin( phi ); - const cos = Math.cos( phi ); + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The first control point. + * @param {Vector2} [v2] - The second control point. + * @param {Vector2} [v3] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + super(); - // vertex + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve = true; - vertex.x = points[ j ].x * sin; - vertex.y = points[ j ].y; - vertex.z = points[ j ].x * cos; + this.type = 'CubicBezierCurve'; - vertices.push( vertex.x, vertex.y, vertex.z ); + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - // uv + /** + * The first control point. + * + * @type {Vector2} + */ + this.v1 = v1; - uv.x = i / segments; - uv.y = j / ( points.length - 1 ); + /** + * The second control point. + * + * @type {Vector2} + */ + this.v2 = v2; - uvs.push( uv.x, uv.y ); + /** + * The end point. + * + * @type {Vector2} + */ + this.v3 = v3; - // normal + } - const x = initNormals[ 3 * j + 0 ] * sin; - const y = initNormals[ 3 * j + 1 ]; - const z = initNormals[ 3 * j + 0 ] * cos; + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - normals.push( x, y, z ); + const point = optionalTarget; - } + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - } + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) + ); - // indices + return point; - for ( let i = 0; i < segments; i ++ ) { + } - for ( let j = 0; j < ( points.length - 1 ); j ++ ) { + copy( source ) { - const base = j + i * points.length; + super.copy( source ); - const a = base; - const b = base + points.length; - const c = base + points.length + 1; - const d = base + 1; + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); - // faces + return this; - indices.push( a, b, d ); - indices.push( c, d, b ); + } - } + toJSON() { - } + const data = super.toJSON(); - // build geometry + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + return data; } - copy( source ) { + fromJSON( json ) { - super.copy( source ); + super.fromJSON( json ); - this.parameters = Object.assign( {}, source.parameters ); + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); return this; } - static fromJSON( data ) { +} - return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); +/** + * A curve representing a 3D Cubic Bezier curve. + * + * @augments Curve + */ +class CubicBezierCurve3 extends Curve { - } + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The first control point. + * @param {Vector3} [v2] - The second control point. + * @param {Vector3} [v3] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { -} + super(); -class CapsuleGeometry extends LatheGeometry { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve3 = true; - constructor( radius = 1, length = 1, capSegments = 4, radialSegments = 8 ) { + this.type = 'CubicBezierCurve3'; - const path = new Path(); - path.absarc( 0, - length / 2, radius, Math.PI * 1.5, 0 ); - path.absarc( 0, length / 2, radius, 0, Math.PI * 0.5 ); + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - super( path.getPoints( capSegments ), radialSegments ); + /** + * The first control point. + * + * @type {Vector3} + */ + this.v1 = v1; - this.type = 'CapsuleGeometry'; + /** + * The second control point. + * + * @type {Vector3} + */ + this.v2 = v2; - this.parameters = { - radius: radius, - length: length, - capSegments: capSegments, - radialSegments: radialSegments, - }; + /** + * The end point. + * + * @type {Vector3} + */ + this.v3 = v3; } - static fromJSON( data ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - return new CapsuleGeometry( data.radius, data.length, data.capSegments, data.radialSegments ); + const point = optionalTarget; - } + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; -} + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), + CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) + ); -class CircleGeometry extends BufferGeometry { + return point; - constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { + } - super(); + copy( source ) { - this.type = 'CircleGeometry'; + super.copy( source ); - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); - segments = Math.max( 3, segments ); + return this; - // buffers + } - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + toJSON() { - // helper variables + const data = super.toJSON(); - const vertex = new Vector3(); - const uv = new Vector2(); + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - // center point + return data; - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); + } - for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { + fromJSON( json ) { - const segment = thetaStart + s / segments * thetaLength; + super.fromJSON( json ); - // vertex + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); + return this; - vertices.push( vertex.x, vertex.y, vertex.z ); + } - // normal +} - normals.push( 0, 0, 1 ); +/** + * A curve representing a 2D line segment. + * + * @augments Curve + */ +class LineCurve extends Curve { - // uvs + /** + * Constructs a new line curve. + * + * @param {Vector2} [v1] - The start point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v1 = new Vector2(), v2 = new Vector2() ) { - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; + super(); - uvs.push( uv.x, uv.y ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve = true; - } + this.type = 'LineCurve'; - // indices + /** + * The start point. + * + * @type {Vector2} + */ + this.v1 = v1; - for ( let i = 1; i <= segments; i ++ ) { + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - indices.push( i, i + 1, 0 ); + } - } + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the line. + */ + getPoint( t, optionalTarget = new Vector2() ) { - // build geometry + const point = optionalTarget; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + if ( t === 1 ) { - } + point.copy( this.v2 ); - copy( source ) { + } else { - super.copy( source ); + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + return point; } - static fromJSON( data ) { + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); + return this.getPoint( u, optionalTarget ); } -} + getTangent( t, optionalTarget = new Vector2() ) { -class CylinderGeometry extends BufferGeometry { + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + } - super(); + getTangentAt( u, optionalTarget ) { - this.type = 'CylinderGeometry'; + return this.getTangent( u, optionalTarget ); - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + } - const scope = this; + copy( source ) { - radialSegments = Math.floor( radialSegments ); - heightSegments = Math.floor( heightSegments ); + super.copy( source ); - // buffers + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + return this; - // helper variables + } - let index = 0; - const indexArray = []; - const halfHeight = height / 2; - let groupStart = 0; + toJSON() { - // generate geometry + const data = super.toJSON(); + + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - generateTorso(); + return data; - if ( openEnded === false ) { + } - if ( radiusTop > 0 ) generateCap( true ); - if ( radiusBottom > 0 ) generateCap( false ); + fromJSON( json ) { - } + super.fromJSON( json ); - // build geometry + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + return this; - function generateTorso() { + } - const normal = new Vector3(); - const vertex = new Vector3(); +} - let groupCount = 0; +/** + * A curve representing a 3D line segment. + * + * @augments Curve + */ +class LineCurve3 extends Curve { - // this will be used to calculate the normal - const slope = ( radiusBottom - radiusTop ) / height; + /** + * Constructs a new line curve. + * + * @param {Vector3} [v1] - The start point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v1 = new Vector3(), v2 = new Vector3() ) { - // generate vertices, normals and uvs + super(); - for ( let y = 0; y <= heightSegments; y ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve3 = true; - const indexRow = []; + this.type = 'LineCurve3'; - const v = y / heightSegments; + /** + * The start point. + * + * @type {Vector3} + */ + this.v1 = v1; - // calculate the radius of the current row + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - const radius = v * ( radiusBottom - radiusTop ) + radiusTop; + } - for ( let x = 0; x <= radialSegments; x ++ ) { + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the line. + */ + getPoint( t, optionalTarget = new Vector3() ) { - const u = x / radialSegments; + const point = optionalTarget; - const theta = u * thetaLength + thetaStart; + if ( t === 1 ) { - const sinTheta = Math.sin( theta ); - const cosTheta = Math.cos( theta ); + point.copy( this.v2 ); - // vertex + } else { - vertex.x = radius * sinTheta; - vertex.y = - v * height + halfHeight; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - // normal + } - normal.set( sinTheta, slope, cosTheta ).normalize(); - normals.push( normal.x, normal.y, normal.z ); + return point; - // uv + } - uvs.push( u, 1 - v ); + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - // save index of vertex in respective row + return this.getPoint( u, optionalTarget ); - indexRow.push( index ++ ); + } - } + getTangent( t, optionalTarget = new Vector3() ) { - // now save vertices of the row in our index array + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - indexArray.push( indexRow ); + } - } + getTangentAt( u, optionalTarget ) { - // generate indices + return this.getTangent( u, optionalTarget ); - for ( let x = 0; x < radialSegments; x ++ ) { + } - for ( let y = 0; y < heightSegments; y ++ ) { + copy( source ) { - // we use the index array to access the correct indices + super.copy( source ); - const a = indexArray[ y ][ x ]; - const b = indexArray[ y + 1 ][ x ]; - const c = indexArray[ y + 1 ][ x + 1 ]; - const d = indexArray[ y ][ x + 1 ]; + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - // faces + return this; - indices.push( a, b, d ); - indices.push( b, c, d ); + } - // update group counter + toJSON() { - groupCount += 6; + const data = super.toJSON(); - } + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - } + return data; - // add a group to the geometry. this will ensure multi material support + } - scope.addGroup( groupStart, groupCount, 0 ); + fromJSON( json ) { - // calculate new start value for groups + super.fromJSON( json ); - groupStart += groupCount; + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - } + return this; - function generateCap( top ) { + } - // save the index of the first center vertex - const centerIndexStart = index; +} - const uv = new Vector2(); - const vertex = new Vector3(); +/** + * A curve representing a 2D Quadratic Bezier curve. + * + * ```js + * const curve = new THREE.QuadraticBezierCurve( + * new THREE.Vector2( - 10, 0 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ) + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class QuadraticBezierCurve extends Curve { - let groupCount = 0; + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The control point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - const radius = ( top === true ) ? radiusTop : radiusBottom; - const sign = ( top === true ) ? 1 : - 1; + super(); - // first we generate the center vertex data of the cap. - // because the geometry needs one set of uvs per face, - // we must generate a center vertex per face/segment + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve = true; - for ( let x = 1; x <= radialSegments; x ++ ) { + this.type = 'QuadraticBezierCurve'; - // vertex + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - vertices.push( 0, halfHeight * sign, 0 ); + /** + * The control point. + * + * @type {Vector2} + */ + this.v1 = v1; - // normal + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - normals.push( 0, sign, 0 ); + } - // uv + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - uvs.push( 0.5, 0.5 ); + const point = optionalTarget; - // increase index + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - index ++; + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ) + ); - } + return point; - // save the index of the last center vertex - const centerIndexEnd = index; + } - // now we generate the surrounding vertices, normals and uvs + copy( source ) { - for ( let x = 0; x <= radialSegments; x ++ ) { + super.copy( source ); - const u = x / radialSegments; - const theta = u * thetaLength + thetaStart; + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - const cosTheta = Math.cos( theta ); - const sinTheta = Math.sin( theta ); + return this; - // vertex + } - vertex.x = radius * sinTheta; - vertex.y = halfHeight * sign; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); + toJSON() { - // normal + const data = super.toJSON(); - normals.push( 0, sign, 0 ); + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - // uv + return data; - uv.x = ( cosTheta * 0.5 ) + 0.5; - uv.y = ( sinTheta * 0.5 * sign ) + 0.5; - uvs.push( uv.x, uv.y ); + } - // increase index + fromJSON( json ) { - index ++; + super.fromJSON( json ); - } + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - // generate indices + return this; - for ( let x = 0; x < radialSegments; x ++ ) { + } - const c = centerIndexStart + x; - const i = centerIndexEnd + x; +} - if ( top === true ) { +/** + * A curve representing a 3D Quadratic Bezier curve. + * + * @augments Curve + */ +class QuadraticBezierCurve3 extends Curve { - // face top + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The control point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - indices.push( i, i + 1, c ); + super(); - } else { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve3 = true; - // face bottom + this.type = 'QuadraticBezierCurve3'; - indices.push( i + 1, i, c ); + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - } + /** + * The control point. + * + * @type {Vector3} + */ + this.v1 = v1; - groupCount += 3; + /** + * The end point. + * + * @type {Vector3} + */ + this.v2 = v2; - } + } - // add a group to the geometry. this will ensure multi material support + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); + const point = optionalTarget; - // calculate new start value for groups + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - groupStart += groupCount; + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ), + QuadraticBezier( t, v0.z, v1.z, v2.z ) + ); - } + return point; } @@ -34668,561 +47649,797 @@ class CylinderGeometry extends BufferGeometry { super.copy( source ); - this.parameters = Object.assign( {}, source.parameters ); + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); return this; } - static fromJSON( data ) { + toJSON() { - return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + const data = super.toJSON(); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; } } -class ConeGeometry extends CylinderGeometry { +/** + * A curve representing a 2D spline curve. + * + * ```js + * // Create a sine-like wave + * const curve = new THREE.SplineCurve( [ + * new THREE.Vector2( -10, 0 ), + * new THREE.Vector2( -5, 5 ), + * new THREE.Vector2( 0, 0 ), + * new THREE.Vector2( 5, -5 ), + * new THREE.Vector2( 10, 0 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const splineObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class SplineCurve extends Curve { - constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + /** + * Constructs a new 2D spline curve. + * + * @param {Array<Vector2>} [points] - An array of 2D points defining the curve. + */ + constructor( points = [] ) { - super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + super(); - this.type = 'ConeGeometry'; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplineCurve = true; - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + this.type = 'SplineCurve'; + + /** + * An array of 2D points defining the curve. + * + * @type {Array<Vector2>} + */ + this.points = points; } - static fromJSON( data ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + const point = optionalTarget; + + const points = this.points; + const p = ( points.length - 1 ) * t; + + const intPoint = Math.floor( p ); + const weight = p - intPoint; + + const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + const p1 = points[ intPoint ]; + const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + + point.set( + CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), + CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) + ); + + return point; } -} + copy( source ) { -class PolyhedronGeometry extends BufferGeometry { + super.copy( source ); - constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { + this.points = []; - super(); + for ( let i = 0, l = source.points.length; i < l; i ++ ) { - this.type = 'PolyhedronGeometry'; + const point = source.points[ i ]; - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; + this.points.push( point.clone() ); - // default buffer data + } - const vertexBuffer = []; - const uvBuffer = []; + return this; - // the subdivision creates the vertex buffer data + } - subdivide( detail ); + toJSON() { - // all vertices should lie on a conceptual sphere with a given radius + const data = super.toJSON(); - applyRadius( radius ); + data.points = []; - // finally, create the uv data + for ( let i = 0, l = this.points.length; i < l; i ++ ) { - generateUVs(); + const point = this.points[ i ]; + data.points.push( point.toArray() ); - // build non-indexed geometry + } - this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); + return data; - if ( detail === 0 ) { + } - this.computeVertexNormals(); // flat normals + fromJSON( json ) { - } else { + super.fromJSON( json ); - this.normalizeNormals(); // smooth normals + this.points = []; + + for ( let i = 0, l = json.points.length; i < l; i ++ ) { + + const point = json.points[ i ]; + this.points.push( new Vector2().fromArray( point ) ); } - // helper functions + return this; - function subdivide( detail ) { + } - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); +} - // iterate over all faces and apply a subdivision with the given detail value +var Curves = /*#__PURE__*/Object.freeze({ + __proto__: null, + ArcCurve: ArcCurve, + CatmullRomCurve3: CatmullRomCurve3, + CubicBezierCurve: CubicBezierCurve, + CubicBezierCurve3: CubicBezierCurve3, + EllipseCurve: EllipseCurve, + LineCurve: LineCurve, + LineCurve3: LineCurve3, + QuadraticBezierCurve: QuadraticBezierCurve, + QuadraticBezierCurve3: QuadraticBezierCurve3, + SplineCurve: SplineCurve +}); - for ( let i = 0; i < indices.length; i += 3 ) { +/** + * A base class extending {@link Curve}. `CurvePath` is simply an + * array of connected curves, but retains the API of a curve. + * + * @augments Curve + */ +class CurvePath extends Curve { - // get the vertices of the face + /** + * Constructs a new curve path. + */ + constructor() { - getVertexByIndex( indices[ i + 0 ], a ); - getVertexByIndex( indices[ i + 1 ], b ); - getVertexByIndex( indices[ i + 2 ], c ); + super(); - // perform subdivision + this.type = 'CurvePath'; - subdivideFace( a, b, c, detail ); + /** + * An array of curves defining the + * path. + * + * @type {Array<Curve>} + */ + this.curves = []; - } + /** + * Whether the path should automatically be closed + * by a line curve. + * + * @type {boolean} + * @default false + */ + this.autoClose = false; - } + } - function subdivideFace( a, b, c, detail ) { + /** + * Adds a curve to this curve path. + * + * @param {Curve} curve - The curve to add. + */ + add( curve ) { - const cols = detail + 1; + this.curves.push( curve ); - // we use this multidimensional array as a data structure for creating the subdivision + } - const v = []; + /** + * Adds a line curve to close the path. + * + * @return {CurvePath} A reference to this curve path. + */ + closePath() { - // construct all of the vertices for this subdivision + // Add a line curve if start and end of lines are not connected + const startPoint = this.curves[ 0 ].getPoint( 0 ); + const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - for ( let i = 0; i <= cols; i ++ ) { + if ( ! startPoint.equals( endPoint ) ) { - v[ i ] = []; + const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; + this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); - const aj = a.clone().lerp( c, i / cols ); - const bj = b.clone().lerp( c, i / cols ); + } + + return this; + + } + + /** + * This method returns a vector in 2D or 3D space (depending on the curve definitions) + * for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( t, optionalTarget ) { + + // To get accurate point with reference to + // entire path distance at time t, + // following has to be done: - const rows = cols - i; + // 1. Length of each sub path have to be known + // 2. Locate and identify type of curve + // 3. Get t for the curve + // 4. Return curve.getPointAt(t') - for ( let j = 0; j <= rows; j ++ ) { + const d = t * this.getLength(); + const curveLengths = this.getCurveLengths(); + let i = 0; - if ( j === 0 && i === cols ) { + // To think about boundaries points. - v[ i ][ j ] = aj; + while ( i < curveLengths.length ) { - } else { + if ( curveLengths[ i ] >= d ) { - v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); + const diff = curveLengths[ i ] - d; + const curve = this.curves[ i ]; - } + const segmentLength = curve.getLength(); + const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - } + return curve.getPointAt( u, optionalTarget ); } - // construct all of the faces + i ++; - for ( let i = 0; i < cols; i ++ ) { + } - for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { + return null; - const k = Math.floor( j / 2 ); + // loop where sum != 0, sum > d , sum+1 <d - if ( j % 2 === 0 ) { + } - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - pushVertex( v[ i ][ k ] ); + getLength() { - } else { + // We cannot use the default THREE.Curve getPoint() with getLength() because in + // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath + // getPoint() depends on getLength - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); + const lens = this.getCurveLengths(); + return lens[ lens.length - 1 ]; - } + } - } + updateArcLengths() { - } + // cacheLengths must be recalculated. - } + this.needsUpdate = true; + this.cacheLengths = null; + this.getCurveLengths(); - function applyRadius( radius ) { + } - const vertex = new Vector3(); + /** + * Returns list of cumulative curve lengths of the defined curves. + * + * @return {Array<number>} The curve lengths. + */ + getCurveLengths() { - // iterate over the entire buffer and apply the radius to each vertex + // Compute lengths and cache them + // We cannot overwrite getLengths() because UtoT mapping uses it. + // We use cache values if curves and cache array are same length - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + return this.cacheLengths; - vertex.normalize().multiplyScalar( radius ); + } - vertexBuffer[ i + 0 ] = vertex.x; - vertexBuffer[ i + 1 ] = vertex.y; - vertexBuffer[ i + 2 ] = vertex.z; + // Get length of sub-curve + // Push sums into cached array - } + const lengths = []; + let sums = 0; + + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + + sums += this.curves[ i ].getLength(); + lengths.push( sums ); } - function generateUVs() { + this.cacheLengths = lengths; - const vertex = new Vector3(); + return lengths; - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + } - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + getSpacedPoints( divisions = 40 ) { - const u = azimuth( vertex ) / 2 / Math.PI + 0.5; - const v = inclination( vertex ) / Math.PI + 0.5; - uvBuffer.push( u, 1 - v ); + const points = []; - } + for ( let i = 0; i <= divisions; i ++ ) { - correctUVs(); + points.push( this.getPoint( i / divisions ) ); - correctSeam(); + } + + if ( this.autoClose ) { + + points.push( points[ 0 ] ); } - function correctSeam() { + return points; - // handle case when face straddles the seam, see #3269 + } - for ( let i = 0; i < uvBuffer.length; i += 6 ) { + getPoints( divisions = 12 ) { - // uv data of a single face + const points = []; + let last; - const x0 = uvBuffer[ i + 0 ]; - const x1 = uvBuffer[ i + 2 ]; - const x2 = uvBuffer[ i + 4 ]; + for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { - const max = Math.max( x0, x1, x2 ); - const min = Math.min( x0, x1, x2 ); + const curve = curves[ i ]; + const resolution = curve.isEllipseCurve ? divisions * 2 + : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 + : curve.isSplineCurve ? divisions * curve.points.length + : divisions; - // 0.9 is somewhat arbitrary + const pts = curve.getPoints( resolution ); - if ( max > 0.9 && min < 0.1 ) { + for ( let j = 0; j < pts.length; j ++ ) { - if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; - if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; - if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; + const point = pts[ j ]; - } + if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates + + points.push( point ); + last = point; } } - function pushVertex( vertex ) { + if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - vertexBuffer.push( vertex.x, vertex.y, vertex.z ); + points.push( points[ 0 ] ); } - function getVertexByIndex( index, vertex ) { + return points; - const stride = index * 3; + } - vertex.x = vertices[ stride + 0 ]; - vertex.y = vertices[ stride + 1 ]; - vertex.z = vertices[ stride + 2 ]; + copy( source ) { - } + super.copy( source ); - function correctUVs() { + this.curves = []; - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); + for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - const centroid = new Vector3(); + const curve = source.curves[ i ]; - const uvA = new Vector2(); - const uvB = new Vector2(); - const uvC = new Vector2(); + this.curves.push( curve.clone() ); - for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { + } - a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); - b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); - c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); + this.autoClose = source.autoClose; - uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); - uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); - uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); + return this; - centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + } - const azi = azimuth( centroid ); + toJSON() { - correctUV( uvA, j + 0, a, azi ); - correctUV( uvB, j + 2, b, azi ); - correctUV( uvC, j + 4, c, azi ); + const data = super.toJSON(); - } + data.autoClose = this.autoClose; + data.curves = []; + + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + + const curve = this.curves[ i ]; + data.curves.push( curve.toJSON() ); } - function correctUV( uv, stride, vector, azimuth ) { + return data; - if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { + } - uvBuffer[ stride ] = uv.x - 1; + fromJSON( json ) { - } + super.fromJSON( json ); - if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { + this.autoClose = json.autoClose; + this.curves = []; - uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; + for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - } + const curve = json.curves[ i ]; + this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); } - // Angle around the Y axis, counter-clockwise when looking from above. + return this; - function azimuth( vector ) { + } - return Math.atan2( vector.z, - vector.x ); +} - } +/** + * A 2D path representation. The class provides methods for creating paths + * and contours of 2D shapes similar to the 2D Canvas API. + * + * ```js + * const path = new THREE.Path(); + * + * path.lineTo( 0, 0.8 ); + * path.quadraticCurveTo( 0, 1, 0.2, 1 ); + * path.lineTo( 1, 1 ); + * + * const points = path.getPoints(); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments CurvePath + */ +class Path extends CurvePath { + /** + * Constructs a new path. + * + * @param {Array<Vector2>} [points] - An array of 2D points defining the path. + */ + constructor( points ) { - // Angle above the XZ plane. + super(); - function inclination( vector ) { + this.type = 'Path'; - return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + /** + * The current offset of the path. Any new curve added will start here. + * + * @type {Vector2} + */ + this.currentPoint = new Vector2(); + + if ( points ) { + + this.setFromPoints( points ); } } - copy( source ) { - - super.copy( source ); + /** + * Creates a path from the given list of points. The points are added + * to the path as instances of {@link LineCurve}. + * + * @param {Array<Vector2>} points - An array of 2D points. + * @return {Path} A reference to this path. + */ + setFromPoints( points ) { - this.parameters = Object.assign( {}, source.parameters ); + this.moveTo( points[ 0 ].x, points[ 0 ].y ); - return this; + for ( let i = 1, l = points.length; i < l; i ++ ) { - } + this.lineTo( points[ i ].x, points[ i ].y ); - static fromJSON( data ) { + } - return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); + return this; } -} + /** + * Moves {@link Path#currentPoint} to the given point. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {Path} A reference to this path. + */ + moveTo( x, y ) { -class DodecahedronGeometry extends PolyhedronGeometry { + this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - constructor( radius = 1, detail = 0 ) { + return this; - const t = ( 1 + Math.sqrt( 5 ) ) / 2; - const r = 1 / t; + } - const vertices = [ + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + lineTo( x, y ) { - // (±1, ±1, ±1) - - 1, - 1, - 1, - 1, - 1, 1, - - 1, 1, - 1, - 1, 1, 1, - 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, 1, 1, 1, + const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); + this.curves.push( curve ); - // (0, ±1/φ, ±φ) - 0, - r, - t, 0, - r, t, - 0, r, - t, 0, r, t, + this.currentPoint.set( x, y ); - // (±1/φ, ±φ, 0) - - r, - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, + return this; - // (±φ, 0, ±1/φ) - - t, 0, - r, t, 0, - r, - - t, 0, r, t, 0, r - ]; + } - const indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { - super( vertices, indices, radius, detail ); + const curve = new QuadraticBezierCurve( + this.currentPoint.clone(), + new Vector2( aCPx, aCPy ), + new Vector2( aX, aY ) + ); - this.type = 'DodecahedronGeometry'; + this.curves.push( curve ); - this.parameters = { - radius: radius, - detail: detail - }; + this.currentPoint.set( aX, aY ); - } + return this; - static fromJSON( data ) { + } - return new DodecahedronGeometry( data.radius, data.detail ); + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - } + const curve = new CubicBezierCurve( + this.currentPoint.clone(), + new Vector2( aCP1x, aCP1y ), + new Vector2( aCP2x, aCP2y ), + new Vector2( aX, aY ) + ); -} + this.curves.push( curve ); -const _v0 = /*@__PURE__*/ new Vector3(); -const _v1$1 = /*@__PURE__*/ new Vector3(); -const _normal = /*@__PURE__*/ new Vector3(); -const _triangle = /*@__PURE__*/ new Triangle(); + this.currentPoint.set( aX, aY ); -class EdgesGeometry extends BufferGeometry { + return this; - constructor( geometry = null, thresholdAngle = 1 ) { + } - super(); + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array<Vector2>} pts - An array of points in 2D space. + * @return {Path} A reference to this path. + */ + splineThru( pts ) { - this.type = 'EdgesGeometry'; + const npts = [ this.currentPoint.clone() ].concat( pts ); - this.parameters = { - geometry: geometry, - thresholdAngle: thresholdAngle - }; + const curve = new SplineCurve( npts ); + this.curves.push( curve ); - if ( geometry !== null ) { + this.currentPoint.copy( pts[ pts.length - 1 ] ); - const precisionPoints = 4; - const precision = Math.pow( 10, precisionPoints ); - const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); + return this; - const indexAttr = geometry.getIndex(); - const positionAttr = geometry.getAttribute( 'position' ); - const indexCount = indexAttr ? indexAttr.count : positionAttr.count; + } - const indexArr = [ 0, 0, 0 ]; - const vertKeys = [ 'a', 'b', 'c' ]; - const hashes = new Array( 3 ); + /** + * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - const edgeData = {}; - const vertices = []; - for ( let i = 0; i < indexCount; i += 3 ) { + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; - if ( indexAttr ) { + this.absarc( aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); - indexArr[ 0 ] = indexAttr.getX( i ); - indexArr[ 1 ] = indexAttr.getX( i + 1 ); - indexArr[ 2 ] = indexAttr.getX( i + 2 ); + return this; - } else { + } - indexArr[ 0 ] = i; - indexArr[ 1 ] = i + 1; - indexArr[ 2 ] = i + 2; + /** + * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc. + * @param {number} [aY=0] - The y coordinate of the center of the arc. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - } + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - const { a, b, c } = _triangle; - a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); - b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); - c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); - _triangle.getNormal( _normal ); + return this; - // create hashes for the edge from the vertices - hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; - hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; - hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; + } - // skip degenerate triangles - if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { + /** + * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point + * + * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - continue; + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; - } + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - // iterate over every edge - for ( let j = 0; j < 3; j ++ ) { + return this; - // get the first and next vertex making up the edge - const jNext = ( j + 1 ) % 3; - const vecHash0 = hashes[ j ]; - const vecHash1 = hashes[ jNext ]; - const v0 = _triangle[ vertKeys[ j ] ]; - const v1 = _triangle[ vertKeys[ jNext ] ]; + } - const hash = `${ vecHash0 }_${ vecHash1 }`; - const reverseHash = `${ vecHash1 }_${ vecHash0 }`; + /** + * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse. + * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { + const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - // if we found a sibling edge add it into the vertex array if - // it meets the angle threshold and delete the edge from the map. - if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { + if ( this.curves.length > 0 ) { - vertices.push( v0.x, v0.y, v0.z ); - vertices.push( v1.x, v1.y, v1.z ); + // if a previous curve is present, attempt to join + const firstPoint = curve.getPoint( 0 ); - } + if ( ! firstPoint.equals( this.currentPoint ) ) { - edgeData[ reverseHash ] = null; + this.lineTo( firstPoint.x, firstPoint.y ); - } else if ( ! ( hash in edgeData ) ) { + } - // if we've already got an edge here then skip adding a new one - edgeData[ hash ] = { + } - index0: indexArr[ j ], - index1: indexArr[ jNext ], - normal: _normal.clone(), + this.curves.push( curve ); - }; + const lastPoint = curve.getPoint( 1 ); + this.currentPoint.copy( lastPoint ); - } + return this; - } + } - } + copy( source ) { - // iterate over all remaining, unmatched edges and add them to the vertex array - for ( const key in edgeData ) { + super.copy( source ); - if ( edgeData[ key ] ) { + this.currentPoint.copy( source.currentPoint ); - const { index0, index1 } = edgeData[ key ]; - _v0.fromBufferAttribute( positionAttr, index0 ); - _v1$1.fromBufferAttribute( positionAttr, index1 ); + return this; - vertices.push( _v0.x, _v0.y, _v0.z ); - vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); + } - } + toJSON() { - } + const data = super.toJSON(); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + data.currentPoint = this.currentPoint.toArray(); - } + return data; } - copy( source ) { + fromJSON( json ) { - super.copy( source ); + super.fromJSON( json ); - this.parameters = Object.assign( {}, source.parameters ); + this.currentPoint.fromArray( json.currentPoint ); return this; @@ -35230,20 +48447,76 @@ class EdgesGeometry extends BufferGeometry { } +/** + * Defines an arbitrary 2d shape plane using paths with optional holes. It + * can be used with {@link ExtrudeGeometry}, {@link ShapeGeometry}, to get + * points, or to get triangulated faces. + * + * ```js + * const heartShape = new THREE.Shape(); + * + * heartShape.moveTo( 25, 25 ); + * heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); + * heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); + * heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); + * heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); + * heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); + * heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); + * + * const extrudeSettings = { + * depth: 8, + * bevelEnabled: true, + * bevelSegments: 2, + * steps: 2, + * bevelSize: 1, + * bevelThickness: 1 + * }; + * + * const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); + * const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() ); + * ``` + * + * @augments Path + */ class Shape extends Path { + /** + * Constructs a new shape. + * + * @param {Array<Vector2>} [points] - An array of 2D points defining the shape. + */ constructor( points ) { super( points ); + /** + * The UUID of the shape. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); this.type = 'Shape'; + /** + * Defines the holes in the shape. Hole definitions must use the + * opposite winding order (CW/CCW) than the outer shape. + * + * @type {Array<Path>} + * @readonly + */ this.holes = []; } + /** + * Returns an array representing each contour of the holes + * as a list of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {Array<Array<Vector2>>} The holes as a series of 2D points. + */ getPointsHoles( divisions ) { const holesPts = []; @@ -35260,6 +48533,13 @@ class Shape extends Path { // get points of shape and holes (keypoints based on segments parameter) + /** + * Returns an object that holds contour data for the shape and its holes as + * arrays of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {{shape:Array<Vector2>,holes:Array<Array<Vector2>>}} An object with contour data. + */ extractPoints( divisions ) { return { @@ -35327,798 +48607,674 @@ class Shape extends Path { } -/** - * Port from https://github.com/mapbox/earcut (v2.2.4) - */ - -const Earcut = { - - triangulate: function ( data, holeIndices, dim = 2 ) { - - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; - let outerNode = linkedList( data, 0, outerLen, dim, true ); - const triangles = []; +/* eslint-disable */ +// copy of mapbox/earcut version 3.0.1 +// https://github.com/mapbox/earcut/tree/v3.0.1 - if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; +function earcut(data, holeIndices, dim = 2) { - let minX, minY, maxX, maxY, x, y, invSize; + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; - if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); + if (!outerNode || outerNode.next === outerNode.prev) return triangles; - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if ( data.length > 80 * dim ) { - - minX = maxX = data[ 0 ]; - minY = maxY = data[ 1 ]; - - for ( let i = dim; i < outerLen; i += dim ) { - - x = data[ i ]; - y = data[ i + 1 ]; - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; - - } + let minX, minY, invSize; - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max( maxX - minX, maxY - minY ); - invSize = invSize !== 0 ? 32767 / invSize : 0; + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); - } + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = Infinity; + minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; - earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); + for (let i = dim; i < outerLen; i += dim) { + const x = data[i]; + const y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } - return triangles; + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } - } + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); -}; + return triangles; +} // create a circular doubly linked list from polygon points in the specified winding order -function linkedList( data, start, end, dim, clockwise ) { - - let i, last; - - if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { - - for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); - - } else { - - for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); +function linkedList(data, start, end, dim, clockwise) { + let last; - } - - if ( last && equals( last, last.next ) ) { - - removeNode( last ); - last = last.next; - - } + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } else { + for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } - return last; + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + return last; } // eliminate colinear or duplicate points -function filterPoints( start, end ) { - - if ( ! start ) return start; - if ( ! end ) end = start; - - let p = start, - again; - do { - - again = false; - - if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { - - removeNode( p ); - p = end = p.prev; - if ( p === p.next ) break; - again = true; - - } else { - - p = p.next; - - } - - } while ( again || p !== end ); - - return end; - +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + let p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; } // main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { - - if ( ! ear ) return; - - // interlink polygon nodes in z-order - if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; - let stop = ear, - prev, next; + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); - // iterate through ears, slicing them one by one - while ( ear.prev !== ear.next ) { + let stop = ear; - prev = ear.prev; - next = ear.next; + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + const prev = ear.prev; + const next = ear.next; - if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + triangles.push(prev.i, ear.i, next.i); // cut off the triangle - // cut off the triangle - triangles.push( prev.i / dim | 0 ); - triangles.push( ear.i / dim | 0 ); - triangles.push( next.i / dim | 0 ); + removeNode(ear); - removeNode( ear ); + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; + continue; + } - continue; + ear = next; - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if ( ear === stop ) { - - // try filtering points and slicing again - if ( ! pass ) { - - earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); - - // if this didn't work, try curing all small self-intersections locally - - } else if ( pass === 1 ) { - - ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); - earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); - - // as a last resort, try splitting the remaining polygon into two - - } else if ( pass === 2 ) { + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); - splitEarcut( ear, triangles, dim, minX, minY, invSize ); + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); - } - - break; - - } - - } + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + break; + } + } } // check whether a polygon node forms a valid ear with adjacent nodes -function isEar( ear ) { - - const a = ear.prev, - b = ear, - c = ear.next; - - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); - - let p = c.next; - while ( p !== a ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && - area( p.prev, p, p.next ) >= 0 ) return false; - p = p.next; - - } - - return true; - +function isEar(ear) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + let p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; } -function isEarHashed( ear, minX, minY, invSize ) { - - const a = ear.prev, - b = ear, - c = ear.next; - - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear - - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); - - // z-order range for the current triangle bbox; - const minZ = zOrder( x0, y0, minX, minY, invSize ), - maxZ = zOrder( x1, y1, minX, minY, invSize ); - - let p = ear.prevZ, - n = ear.nextZ; - - // look for points inside the triangle in both directions - while ( p && p.z >= minZ && n && n.z <= maxZ ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; - - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; - - } - - // look for remaining points in decreasing z-order - while ( p && p.z >= minZ ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; - - } - - // look for remaining points in increasing z-order - while ( n && n.z <= maxZ ) { - - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; - - } - - return true; - +function isEarHashed(ear, minX, minY, invSize) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + let p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + return true; } // go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections( start, triangles, dim ) { +function cureLocalIntersections(start, triangles) { + let p = start; + do { + const a = p.prev, + b = p.next.next; - let p = start; - do { + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { - const a = p.prev, - b = p.next.next; + triangles.push(a.i, p.i, b.i); - if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + // remove two nodes involved + removeNode(p); + removeNode(p.next); - triangles.push( a.i / dim | 0 ); - triangles.push( p.i / dim | 0 ); - triangles.push( b.i / dim | 0 ); - - // remove two nodes involved - removeNode( p ); - removeNode( p.next ); - - p = start = b; - - } - - p = p.next; - - } while ( p !== start ); - - return filterPoints( p ); + p = start = b; + } + p = p.next; + } while (p !== start); + return filterPoints(p); } // try splitting polygon into two and triangulate them independently -function splitEarcut( start, triangles, dim, minX, minY, invSize ) { - - // look for a valid diagonal that divides the polygon into two - let a = start; - do { - - let b = a.next.next; - while ( b !== a.prev ) { - - if ( a.i !== b.i && isValidDiagonal( a, b ) ) { - - // split the polygon in two by the diagonal - let c = splitPolygon( a, b ); - - // filter colinear points around the cuts - a = filterPoints( a, a.next ); - c = filterPoints( c, c.next ); - - // run earcut on each half - earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); - earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); - return; - - } - - b = b.next; - - } - - a = a.next; - - } while ( a !== start ); - +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + let b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); } // link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles( data, holeIndices, outerNode, dim ) { - - const queue = []; - let i, len, start, end, list; - - for ( i = 0, len = holeIndices.length; i < len; i ++ ) { - - start = holeIndices[ i ] * dim; - end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; - list = linkedList( data, start, end, dim, false ); - if ( list === list.next ) list.steiner = true; - queue.push( getLeftmost( list ) ); +function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; - } - - queue.sort( compareX ); - - // process holes from left to right - for ( i = 0; i < queue.length; i ++ ) { + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + const list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } - outerNode = eliminateHole( queue[ i ], outerNode ); - - } + queue.sort(compareXYSlope); - return outerNode; + // process holes from left to right + for (let i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + return outerNode; } -function compareX( a, b ) { - - return a.x - b.x; - +function compareXYSlope(a, b) { + let result = a.x - b.x; + // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find + // the bridge to the outer shell is always the point that they meet at. + if (result === 0) { + result = a.y - b.y; + if (result === 0) { + const aSlope = (a.next.y - a.y) / (a.next.x - a.x); + const bSlope = (b.next.y - b.y) / (b.next.x - b.x); + result = aSlope - bSlope; + } + } + return result; } -// find a bridge between vertices that connects hole with an outer ring and link it -function eliminateHole( hole, outerNode ) { - - const bridge = findHoleBridge( hole, outerNode ); - if ( ! bridge ) { - - return outerNode; - - } - - const bridgeReverse = splitPolygon( bridge, hole ); +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } - // filter collinear points around the cuts - filterPoints( bridgeReverse, bridgeReverse.next ); - return filterPoints( bridge, bridge.next ); + const bridgeReverse = splitPolygon(bridge, hole); + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); } // David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge( hole, outerNode ) { - - let p = outerNode, - qx = - Infinity, - m; - - const hx = hole.x, hy = hole.y; - - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { - - if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { - - const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); - if ( x <= hx && x > qx ) { - - qx = x; - m = p.x < p.next.x ? p : p.next; - if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint - - } - - } - - p = p.next; - - } while ( p !== outerNode ); - - if ( ! m ) return null; - - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point - - const stop = m, - mx = m.x, - my = m.y; - let tanMin = Infinity, tan; - - p = m; - - do { - - if ( hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { - - tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential - - if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { - - m = p; - tanMin = tan; - - } - - } - - p = p.next; - - } while ( p !== stop ); - - return m; - +function findHoleBridge(hole, outerNode) { + let p = outerNode; + const hx = hole.x; + const hy = hole.y; + let qx = -Infinity; + let m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + // unless they intersect at a vertex, then choose the vertex + if (equals(hole, p)) return p; + do { + if (equals(hole, p.next)) return p.next; + else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + const stop = m; + const mx = m.x; + const my = m.y; + let tanMin = Infinity; + + p = m; + + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } while (p !== stop); + + return m; } // whether sector in vertex m contains sector in vertex p in the same coordinates -function sectorContainsSector( m, p ) { - - return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; - +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; } // interlink polygon nodes in z-order -function indexCurve( start, minX, minY, invSize ) { - - let p = start; - do { - - if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; - - } while ( p !== start ); - - p.prevZ.nextZ = null; - p.prevZ = null; - - sortLinked( p ); - +function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); } // Simon Tatham's linked list merge sort algorithm // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked( list ) { - - let i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; - - do { - - p = list; - list = null; - tail = null; - numMerges = 0; - - while ( p ) { - - numMerges ++; - q = p; - pSize = 0; - for ( i = 0; i < inSize; i ++ ) { - - pSize ++; - q = q.nextZ; - if ( ! q ) break; - - } - - qSize = inSize; - - while ( pSize > 0 || ( qSize > 0 && q ) ) { - - if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { - - e = p; - p = p.nextZ; - pSize --; - - } else { - - e = q; - q = q.nextZ; - qSize --; - - } - - if ( tail ) tail.nextZ = e; - else list = e; - - e.prevZ = tail; - tail = e; - - } - - p = q; - - } - - tail.nextZ = null; - inSize *= 2; - - } while ( numMerges > 1 ); - - return list; - +function sortLinked(list) { + let numMerges; + let inSize = 1; + + do { + let p = list; + let e; + list = null; + let tail = null; + numMerges = 0; + + while (p) { + numMerges++; + let q = p; + let pSize = 0; + for (let i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + let qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; } // z-order of a point given coords and inverse of the longer side of data bbox -function zOrder( x, y, minX, minY, invSize ) { - - // coords are transformed into non-negative 15-bit integer range - x = ( x - minX ) * invSize | 0; - y = ( y - minY ) * invSize | 0; - - x = ( x | ( x << 8 ) ) & 0x00FF00FF; - x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; - x = ( x | ( x << 2 ) ) & 0x33333333; - x = ( x | ( x << 1 ) ) & 0x55555555; - - y = ( y | ( y << 8 ) ) & 0x00FF00FF; - y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; - y = ( y | ( y << 2 ) ) & 0x33333333; - y = ( y | ( y << 1 ) ) & 0x55555555; - - return x | ( y << 1 ); - +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); } // find the leftmost node of a polygon ring -function getLeftmost( start ) { - - let p = start, - leftmost = start; - do { - - if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; - p = p.next; - - } while ( p !== start ); - - return leftmost; - +function getLeftmost(start) { + let p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; } // check if a point lies within a convex triangle -function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { - - return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && - ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && - ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} +// check if a point lies within a convex triangle but false if its equal to the first point of the triangle +function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { + return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py); } // check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal( a, b ) { - - return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges - ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible - ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors - equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case - +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case } // signed area of a triangle -function area( p, q, r ) { - - return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); - +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); } // check if two points are equal -function equals( p1, p2 ) { - - return p1.x === p2.x && p1.y === p2.y; - +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; } // check if two segments intersect -function intersects( p1, q1, p2, q2 ) { +function intersects(p1, q1, p2, q2) { + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); - const o1 = sign( area( p1, q1, p2 ) ); - const o2 = sign( area( p1, q1, q2 ) ); - const o3 = sign( area( p2, q2, p1 ) ); - const o4 = sign( area( p2, q2, q1 ) ); + if (o1 !== o2 && o3 !== o4) return true; // general case - if ( o1 !== o2 && o3 !== o4 ) return true; // general case - - if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - - return false; + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + return false; } // for collinear points p, q, r, check if point q lies on segment pr -function onSegment( p, q, r ) { - - return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); - +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); } -function sign( num ) { - - return num > 0 ? 1 : num < 0 ? - 1 : 0; - +function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; } // check if a polygon diagonal intersects any polygon segments -function intersectsPolygon( a, b ) { - - let p = a; - do { - - if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects( p, p.next, a, b ) ) return true; - p = p.next; - - } while ( p !== a ); - - return false; - +function intersectsPolygon(a, b) { + let p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; } // check if a polygon diagonal is locally inside the polygon -function locallyInside( a, b ) { - - return area( a.prev, a, a.next ) < 0 ? - area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : - area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; - +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; } // check if the middle point of a polygon diagonal is inside the polygon -function middleInside( a, b ) { - - let p = a, - inside = false; - const px = ( a.x + b.x ) / 2, - py = ( a.y + b.y ) / 2; - do { - - if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && - ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) - inside = ! inside; - p = p.next; - - } while ( p !== a ); - - return inside; - +function middleInside(a, b) { + let p = a; + let inside = false; + const px = (a.x + b.x) / 2; + const py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; } // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; // if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon( a, b ) { +function splitPolygon(a, b) { + const a2 = createNode(a.i, a.x, a.y), + b2 = createNode(b.i, b.x, b.y), + an = a.next, + bp = b.prev; - const a2 = new Node( a.i, a.x, a.y ), - b2 = new Node( b.i, b.x, b.y ), - an = a.next, - bp = b.prev; + a.next = b; + b.prev = a; - a.next = b; - b.prev = a; + a2.next = an; + an.prev = a2; - a2.next = an; - an.prev = a2; + b2.next = a2; + a2.prev = b2; - b2.next = a2; - a2.prev = b2; - - bp.next = b2; - b2.prev = bp; - - return b2; + bp.next = b2; + b2.prev = bp; + return b2; } // create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode( i, x, y, last ) { - - const p = new Node( i, x, y ); - - if ( ! last ) { - - p.prev = p; - p.next = p; - - } else { - - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; - - } - - return p; - +function insertNode(i, x, y, last) { + const p = createNode(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; } -function removeNode( p ) { - - p.next.prev = p.prev; - p.prev.next = p.next; - - if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; - if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; } -function Node( i, x, y ) { - - // vertex index in coordinates array - this.i = i; - - // vertex coordinates - this.x = x; - this.y = y; - - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; - - // z-order curve value - this.z = 0; - - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; - - // indicates whether this is a steiner point - this.steiner = false; +function createNode(i, x, y) { + return { + i, // vertex index in coordinates array + x, y, // vertex coordinates + prev: null, // previous and next vertex nodes in a polygon ring + next: null, + z: 0, // z-order curve value + prevZ: null, // previous and next nodes in z-order + nextZ: null, + steiner: false // indicates whether this is a steiner point + }; +} +function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; } -function signedArea( data, start, end, dim ) { +class Earcut { - let sum = 0; - for ( let i = start, j = end - dim; i < end; i += dim ) { + /** + * Triangulates the given shape definition by returning an array of triangles. + * + * @param {Array<number>} data - An array with 2D points. + * @param {Array<number>} holeIndices - An array with indices defining holes. + * @param {number} [dim=2] - The number of coordinates per vertex in the input array. + * @return {Array<number>} An array representing the triangulated faces. Each face is defined by three consecutive numbers + * representing vertex indices. + */ + static triangulate( data, holeIndices, dim = 2 ) { - sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); - j = i; + return earcut( data, holeIndices, dim ); } - return sum; - } +/** + * A class containing utility functions for shapes. + * + * @hideconstructor + */ class ShapeUtils { - // calculate area of the contour polygon - + /** + * Calculate area of a ( 2D ) contour polygon. + * + * @param {Array<Vector2>} contour - An array of 2D points. + * @return {number} The area. + */ static area( contour ) { const n = contour.length; @@ -36134,12 +49290,25 @@ class ShapeUtils { } + /** + * Returns `true` if the given contour uses a clockwise winding order. + * + * @param {Array<Vector2>} pts - An array of 2D points defining a polygon. + * @return {boolean} Whether the given contour uses a clockwise winding order or not. + */ static isClockWise( pts ) { return ShapeUtils.area( pts ) < 0; } + /** + * Triangulates the given shape definition. + * + * @param {Array<Vector2>} contour - An array of 2D points defining the contour. + * @param {Array<Array<Vector2>>} holes - An array that holds arrays of 2D points defining the holes. + * @return {Array<Array<number>>} An array that holds for each face definition an array with three indices. + */ static triangulateShape( contour, holes ) { const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] @@ -36207,34 +49376,45 @@ function addContour( vertices, contour ) { /** * Creates extruded geometry from a path shape. * - * parameters = { - * - * curveSegments: <int>, // number of points on the curves - * steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * depth: <float>, // Depth to extrude the shape + * ```js + * const length = 12, width = 8; * - * bevelEnabled: <bool>, // turn on bevel - * bevelThickness: <float>, // how deep into the original shape bevel goes - * bevelSize: <float>, // how far from shape outline (including bevelOffset) is bevel - * bevelOffset: <float>, // how far from shape outline does bevel start - * bevelSegments: <int>, // number of bevel layers + * const shape = new THREE.Shape(); + * shape.moveTo( 0,0 ); + * shape.lineTo( 0, width ); + * shape.lineTo( length, width ); + * shape.lineTo( length, 0 ); + * shape.lineTo( 0, 0 ); * - * extrudePath: <THREE.Curve> // curve to extrude shape along + * const geometry = new THREE.ExtrudeGeometry( shape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` * - * UVGenerator: <Object> // object that provides UV generator functions - * - * } + * @augments BufferGeometry */ - - class ExtrudeGeometry extends BufferGeometry { - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { + /** + * Constructs a new extrude geometry. + * + * @param {Shape|Array<Shape>} [shapes] - A shape or an array of shapes. + * @param {ExtrudeGeometry~Options} [options] - The extrude settings. + */ + constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( -0.5, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), options = {} ) { super(); this.type = 'ExtrudeGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { shapes: shapes, options: options @@ -36349,14 +49529,53 @@ class ExtrudeGeometry extends BufferGeometry { } + /**Merges index-adjacent points that are within a threshold distance of each other. Array is modified in-place. Threshold distance is empirical, and scaled based on the magnitude of point coordinates. + * @param {Array<Vector2>} points + */ + function mergeOverlappingPoints( points ) { + + const THRESHOLD = 1e-10; + const THRESHOLD_SQ = THRESHOLD * THRESHOLD; + let prevPos = points[ 0 ]; + for ( let i = 1; i <= points.length; i ++ ) { + + const currentIndex = i % points.length; + const currentPos = points[ currentIndex ]; + const dx = currentPos.x - prevPos.x; + const dy = currentPos.y - prevPos.y; + const distSq = dx * dx + dy * dy; + + const scalingFactorSqrt = Math.max( + Math.abs( currentPos.x ), + Math.abs( currentPos.y ), + Math.abs( prevPos.x ), + Math.abs( prevPos.y ) + ); + const thresholdSqScaled = THRESHOLD_SQ * scalingFactorSqrt * scalingFactorSqrt; + if ( distSq <= thresholdSqScaled ) { + + points.splice( currentIndex, 1 ); + i --; + continue; + + } + + prevPos = currentPos; + + } + + } + + mergeOverlappingPoints( vertices ); + holes.forEach( mergeOverlappingPoints ); - const faces = ShapeUtils.triangulateShape( vertices, holes ); + const numHoles = holes.length; /* Vertices */ const contour = vertices; // vertices has all points but contour has only points of circumference - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + for ( let h = 0; h < numHoles; h ++ ) { const ahole = holes[ h ]; @@ -36373,7 +49592,7 @@ class ExtrudeGeometry extends BufferGeometry { } - const vlen = vertices.length, flen = faces.length; + const vlen = vertices.length; // Find directions for point movement @@ -36520,7 +49739,7 @@ class ExtrudeGeometry extends BufferGeometry { const holesMovements = []; let oneHoleMovements, verticesMovements = contourMovements.concat(); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { const ahole = holes[ h ]; @@ -36541,46 +49760,66 @@ class ExtrudeGeometry extends BufferGeometry { } + let faces; - // Loop bevelSegments, 1 for the front, 1 for the back + if ( bevelSegments === 0 ) { - for ( let b = 0; b < bevelSegments; b ++ ) { + faces = ShapeUtils.triangulateShape( contour, holes ); - //for ( b = bevelSegments; b > 0; b -- ) { + } else { - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + const contractedContourVertices = []; + const expandedHoleVertices = []; - // contract shape + // Loop bevelSegments, 1 for the front, 1 for the back - for ( let i = 0, il = contour.length; i < il; i ++ ) { + for ( let b = 0; b < bevelSegments; b ++ ) { - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + //for ( b = bevelSegments; b > 0; b -- ) { - v( vert.x, vert.y, - z ); + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - } + // contract shape - // expand holes + for ( let i = 0, il = contour.length; i < il; i ++ ) { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + v( vert.x, vert.y, - z ); + if ( t === 0 ) contractedContourVertices.push( vert ); - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + } - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + // expand holes - v( vert.x, vert.y, - z ); + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { + + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + const oneHoleVertices = []; + for ( let i = 0, il = ahole.length; i < il; i ++ ) { + + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + if ( t === 0 ) oneHoleVertices.push( vert ); + + } + + if ( t === 0 ) expandedHoleVertices.push( oneHoleVertices ); } } + faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices ); + } + const flen = faces.length; + const bs = bevelSize + bevelOffset; // Back facing vertices @@ -36897,6 +50136,14 @@ class ExtrudeGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array<Shape>} shapes - An array of shapes. + * @return {ExtrudeGeometry} A new instance. + */ static fromJSON( data, shapes ) { const geometryShapes = []; @@ -37009,16 +50256,34 @@ function toJSON$1( shapes, options, data ) { } +/** + * A geometry class for representing an icosahedron. + * + * ```js + * const geometry = new THREE.IcosahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const icosahedron = new THREE.Mesh( geometry, material ); + * scene.add( icosahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ class IcosahedronGeometry extends PolyhedronGeometry { + /** + * Constructs a new icosahedron geometry. + * + * @param {number} [radius=1] - Radius of the icosahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a icosahedron. + */ constructor( radius = 1, detail = 0 ) { const t = ( 1 + Math.sqrt( 5 ) ) / 2; const vertices = [ - - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, - 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, - t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 + -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, 0, + 0, -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, + t, 0, -1, t, 0, 1, - t, 0, -1, - t, 0, 1 ]; const indices = [ @@ -37032,6 +50297,13 @@ class IcosahedronGeometry extends PolyhedronGeometry { this.type = 'IcosahedronGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, detail: detail @@ -37039,6 +50311,13 @@ class IcosahedronGeometry extends PolyhedronGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {IcosahedronGeometry} A new instance. + */ static fromJSON( data ) { return new IcosahedronGeometry( data.radius, data.detail ); @@ -37047,13 +50326,252 @@ class IcosahedronGeometry extends PolyhedronGeometry { } +/** + * Creates meshes with axial symmetry like vases. The lathe rotates around the Y axis. + * + * ```js + * const points = []; + * for ( let i = 0; i < 10; i ++ ) { + * points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * 10 + 5, ( i - 5 ) * 2 ) ); + * } + * const geometry = new THREE.LatheGeometry( points ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const lathe = new THREE.Mesh( geometry, material ); + * scene.add( lathe ); + * ``` + * + * @augments BufferGeometry + */ +class LatheGeometry extends BufferGeometry { + + /** + * Constructs a new lathe geometry. + * + * @param {Array<Vector2|Vector3>} [points] - An array of points in 2D space. The x-coordinate of each point + * must be greater than zero. + * @param {number} [segments=12] - The number of circumference segments to generate. + * @param {number} [phiStart=0] - The starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The radian (0 to 2PI) range of the lathed section 2PI is a + * closed lathe, less than 2PI is a portion. + */ + constructor( points = [ new Vector2( 0, -0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { + + super(); + + this.type = 'LatheGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; + + segments = Math.floor( segments ); + + // clamp phiLength so it's in range of [ 0, 2PI ] + + phiLength = clamp( phiLength, 0, Math.PI * 2 ); + + // buffers + + const indices = []; + const vertices = []; + const uvs = []; + const initNormals = []; + const normals = []; + + // helper variables + + const inverseSegments = 1.0 / segments; + const vertex = new Vector3(); + const uv = new Vector2(); + const normal = new Vector3(); + const curNormal = new Vector3(); + const prevNormal = new Vector3(); + let dx = 0; + let dy = 0; + + // pre-compute normals for initial "meridian" + + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + + switch ( j ) { + + case 0: // special handling for 1st vertex on path + + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; + + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; + + prevNormal.copy( normal ); + + normal.normalize(); + + initNormals.push( normal.x, normal.y, normal.z ); + + break; + + case ( points.length - 1 ): // special handling for last Vertex on path + + initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); + + break; + + default: // default handling for all vertices in between + + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; + + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; + + curNormal.copy( normal ); + + normal.x += prevNormal.x; + normal.y += prevNormal.y; + normal.z += prevNormal.z; + + normal.normalize(); + + initNormals.push( normal.x, normal.y, normal.z ); + + prevNormal.copy( curNormal ); + + } + + } + + // generate vertices, uvs and normals + + for ( let i = 0; i <= segments; i ++ ) { + + const phi = phiStart + i * inverseSegments * phiLength; + + const sin = Math.sin( phi ); + const cos = Math.cos( phi ); + + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + + // vertex + + vertex.x = points[ j ].x * sin; + vertex.y = points[ j ].y; + vertex.z = points[ j ].x * cos; + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // uv + + uv.x = i / segments; + uv.y = j / ( points.length - 1 ); + + uvs.push( uv.x, uv.y ); + + // normal + + const x = initNormals[ 3 * j + 0 ] * sin; + const y = initNormals[ 3 * j + 1 ]; + const z = initNormals[ 3 * j + 0 ] * cos; + + normals.push( x, y, z ); + + } + + } + + // indices + + for ( let i = 0; i < segments; i ++ ) { + + for ( let j = 0; j < ( points.length - 1 ); j ++ ) { + + const base = j + i * points.length; + + const a = base; + const b = base + points.length; + const c = base + points.length + 1; + const d = base + 1; + + // faces + + indices.push( a, b, d ); + indices.push( c, d, b ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {LatheGeometry} A new instance. + */ + static fromJSON( data ) { + + return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); + + } + +} + +/** + * A geometry class for representing an octahedron. + * + * ```js + * const geometry = new THREE.OctahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const octahedron = new THREE.Mesh( geometry, material ); + * scene.add( octahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ class OctahedronGeometry extends PolyhedronGeometry { + /** + * Constructs a new octahedron geometry. + * + * @param {number} [radius=1] - Radius of the octahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a octahedron. + */ constructor( radius = 1, detail = 0 ) { const vertices = [ - 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, - 1, 0, 0, 0, 1, 0, 0, - 1 + 1, 0, 0, -1, 0, 0, 0, 1, 0, + 0, -1, 0, 0, 0, 1, 0, 0, -1 ]; const indices = [ @@ -37066,6 +50584,13 @@ class OctahedronGeometry extends PolyhedronGeometry { this.type = 'OctahedronGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, detail: detail @@ -37073,6 +50598,13 @@ class OctahedronGeometry extends PolyhedronGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {OctahedronGeometry} A new instance. + */ static fromJSON( data ) { return new OctahedronGeometry( data.radius, data.detail ); @@ -37081,14 +50613,43 @@ class OctahedronGeometry extends PolyhedronGeometry { } +/** + * A class for generating a two-dimensional ring geometry. + * + * ```js + * const geometry = new THREE.RingGeometry( 1, 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ class RingGeometry extends BufferGeometry { + /** + * Constructs a new ring geometry. + * + * @param {number} [innerRadius=0.5] - The inner radius of the ring. + * @param {number} [outerRadius=1] - The outer radius of the ring. + * @param {number} [thetaSegments=32] - Number of segments. A higher number means the ring will be more round. Minimum is `3`. + * @param {number} [phiSegments=1] - Number of segments per ring segment. Minimum is `1`. + * @param {number} [thetaStart=0] - Starting angle in radians. + * @param {number} [thetaLength=Math.PI*2] - Central angle in radians. + */ constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'RingGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { innerRadius: innerRadius, outerRadius: outerRadius, @@ -37194,6 +50755,13 @@ class RingGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {RingGeometry} A new instance. + */ static fromJSON( data ) { return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); @@ -37202,14 +50770,43 @@ class RingGeometry extends BufferGeometry { } +/** + * Creates an one-sided polygonal geometry from one or more path shapes. + * + * ```js + * const arcShape = new THREE.Shape() + * .moveTo( 5, 1 ) + * .absarc( 1, 1, 4, 0, Math.PI * 2, false ); + * + * const geometry = new THREE.ShapeGeometry( arcShape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ class ShapeGeometry extends BufferGeometry { - constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) { + /** + * Constructs a new shape geometry. + * + * @param {Shape|Array<Shape>} [shapes] - A shape or an array of shapes. + * @param {number} [curveSegments=12] - Number of segments per shape. + */ + constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), curveSegments = 12 ) { super(); this.type = 'ShapeGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { shapes: shapes, curveSegments: curveSegments @@ -37348,6 +50945,14 @@ class ShapeGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array<Shape>} shapes - An array of shapes. + * @return {ShapeGeometry} A new instance. + */ static fromJSON( data, shapes ) { const geometryShapes = []; @@ -37390,14 +50995,44 @@ function toJSON( shapes, data ) { } +/** + * A class for generating a sphere geometry. + * + * ```js + * const geometry = new THREE.SphereGeometry( 15, 32, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const sphere = new THREE.Mesh( geometry, material ); + * scene.add( sphere ); + * ``` + * + * @augments BufferGeometry + */ class SphereGeometry extends BufferGeometry { + /** + * Constructs a new sphere geometry. + * + * @param {number} [radius=1] - The sphere radius. + * @param {number} [widthSegments=32] - The number of horizontal segments. Minimum value is `3`. + * @param {number} [heightSegments=16] - The number of vertical segments. Minimum value is `2`. + * @param {number} [phiStart=0] - The horizontal starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The horizontal sweep angle size. + * @param {number} [thetaStart=0] - The vertical starting angle in radians. + * @param {number} [thetaLength=Math.PI] - The vertical sweep angle size. + */ constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { super(); this.type = 'SphereGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, widthSegments: widthSegments, @@ -37444,7 +51079,7 @@ class SphereGeometry extends BufferGeometry { } else if ( iy === heightSegments && thetaEnd === Math.PI ) { - uOffset = - 0.5 / widthSegments; + uOffset = -0.5 / widthSegments; } @@ -37514,6 +51149,13 @@ class SphereGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {SphereGeometry} A new instance. + */ static fromJSON( data ) { return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); @@ -37522,12 +51164,30 @@ class SphereGeometry extends BufferGeometry { } +/** + * A geometry class for representing an tetrahedron. + * + * ```js + * const geometry = new THREE.TetrahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const tetrahedron = new THREE.Mesh( geometry, material ); + * scene.add( tetrahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ class TetrahedronGeometry extends PolyhedronGeometry { + /** + * Constructs a new tetrahedron geometry. + * + * @param {number} [radius=1] - Radius of the tetrahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a tetrahedron. + */ constructor( radius = 1, detail = 0 ) { const vertices = [ - 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 + 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 ]; const indices = [ @@ -37538,6 +51198,13 @@ class TetrahedronGeometry extends PolyhedronGeometry { this.type = 'TetrahedronGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, detail: detail @@ -37545,6 +51212,13 @@ class TetrahedronGeometry extends PolyhedronGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TetrahedronGeometry} A new instance. + */ static fromJSON( data ) { return new TetrahedronGeometry( data.radius, data.detail ); @@ -37553,14 +51227,42 @@ class TetrahedronGeometry extends PolyhedronGeometry { } +/** + * A geometry class for representing an torus. + * + * ```js + * const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torus = new THREE.Mesh( geometry, material ); + * scene.add( torus ); + * ``` + * + * @augments BufferGeometry + */ class TorusGeometry extends BufferGeometry { + /** + * Constructs a new torus geometry. + * + * @param {number} [radius=1] - Radius of the torus, from the center of the torus to the center of the tube. + * @param {number} [tube=0.4] - Radius of the tube. Must be smaller than `radius`. + * @param {number} [radialSegments=12] - The number of radial segments. + * @param {number} [tubularSegments=48] - The number of tubular segments. + * @param {number} [arc=Math.PI*2] - Central angle in radians. + */ constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { super(); this.type = 'TorusGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, tube: tube, @@ -37660,6 +51362,13 @@ class TorusGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusGeometry} A new instance. + */ static fromJSON( data ) { return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); @@ -37668,14 +51377,45 @@ class TorusGeometry extends BufferGeometry { } +/** + * Creates a torus knot, the particular shape of which is defined by a pair + * of coprime integers, p and q. If p and q are not coprime, the result will + * be a torus link. + * + * ```js + * const geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torusKnot = new THREE.Mesh( geometry, material ); + * scene.add( torusKnot ); + * ``` + * + * @augments BufferGeometry + */ class TorusKnotGeometry extends BufferGeometry { + /** + * Constructs a new torus knot geometry. + * + * @param {number} [radius=1] - Radius of the torus knot. + * @param {number} [tube=0.4] - Radius of the tube. + * @param {number} [tubularSegments=64] - The number of tubular segments. + * @param {number} [radialSegments=8] - The number of radial segments. + * @param {number} [p=2] - This value determines, how many times the geometry winds around its axis of rotational symmetry. + * @param {number} [q=3] - This value determines, how many times the geometry winds around a circle in the interior of the torus. + */ constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { super(); this.type = 'TorusKnotGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { radius: radius, tube: tube, @@ -37822,6 +51562,13 @@ class TorusKnotGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusKnotGeometry} A new instance. + */ static fromJSON( data ) { return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); @@ -37830,14 +51577,56 @@ class TorusKnotGeometry extends BufferGeometry { } +/** + * Creates a tube that extrudes along a 3D curve. + * + * ```js + * class CustomSinCurve extends THREE.Curve { + * + * getPoint( t, optionalTarget = new THREE.Vector3() ) { + * + * const tx = t * 3 - 1.5; + * const ty = Math.sin( 2 * Math.PI * t ); + * const tz = 0; + * + * return optionalTarget.set( tx, ty, tz ); + * } + * + * } + * + * const path = new CustomSinCurve( 10 ); + * const geometry = new THREE.TubeGeometry( path, 20, 2, 8, false ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ class TubeGeometry extends BufferGeometry { - constructor( path = new QuadraticBezierCurve3( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { + /** + * Constructs a new tube geometry. + * + * @param {Curve} [path=QuadraticBezierCurve3] - A 3D curve defining the path of the tube. + * @param {number} [tubularSegments=64] - The number of segments that make up the tube. + * @param {number} [radius=1] -The radius of the tube. + * @param {number} [radialSegments=8] - The number of segments that make up the cross-section. + * @param {boolean} [closed=false] - Whether the tube is closed or not. + */ + constructor( path = new QuadraticBezierCurve3( new Vector3( -1, -1, 0 ), new Vector3( -1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { super(); this.type = 'TubeGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { path: path, tubularSegments: tubularSegments, @@ -38009,6 +51798,13 @@ class TubeGeometry extends BufferGeometry { } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TubeGeometry} A new instance. + */ static fromJSON( data ) { // This only works for built-in curves (e.g. CatmullRomCurve3). @@ -38025,14 +51821,46 @@ class TubeGeometry extends BufferGeometry { } +/** + * Can be used as a helper object to visualize a geometry as a wireframe. + * + * ```js + * const geometry = new THREE.SphereGeometry(); + * + * const wireframe = new THREE.WireframeGeometry( geometry ); + * + * const line = new THREE.LineSegments( wireframe ); + * line.material.depthWrite = false; + * line.material.opacity = 0.25; + * line.material.transparent = true; + * + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ class WireframeGeometry extends BufferGeometry { + /** + * Constructs a new wireframe geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + */ constructor( geometry = null ) { super(); this.type = 'WireframeGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { geometry: geometry }; @@ -38191,19 +52019,73 @@ var Geometries = /*#__PURE__*/Object.freeze({ WireframeGeometry: WireframeGeometry }); +/** + * This material can receive shadows, but otherwise is completely transparent. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 2000, 2000 ); + * geometry.rotateX( - Math.PI / 2 ); + * + * const material = new THREE.ShadowMaterial(); + * material.opacity = 0.2; + * + * const plane = new THREE.Mesh( geometry, material ); + * plane.position.y = -200; + * plane.receiveShadow = true; + * scene.add( plane ); + * ``` + * + * @augments Material + */ class ShadowMaterial extends Material { + /** + * Constructs a new shadow material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isShadowMaterial = true; this.type = 'ShadowMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (0,0,0) + */ this.color = new Color( 0x000000 ); + + /** + * Overwritten since shadow materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ this.transparent = true; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -38224,28 +52106,110 @@ class ShadowMaterial extends Material { } +/** + * A material for rendering instances of {@link Sprite}. + * + * ```js + * const map = new THREE.TextureLoader().load( 'textures/sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map, color: 0xffffff } ); + * + * const sprite = new THREE.Sprite( material ); + * sprite.scale.set(200, 200, 1) + * scene.add( sprite ); + * ``` + * + * @augments Material + */ class SpriteMaterial extends Material { + /** + * Constructs a new sprite material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSpriteMaterial = true; this.type = 'SpriteMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The rotation of the sprite in radians. + * + * @type {number} + * @default 0 + */ this.rotation = 0; + /** + * Specifies whether size of the sprite is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ this.sizeAttenuation = true; + /** + * Overwritten since sprite materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ this.transparent = true; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -38274,12 +52238,37 @@ class SpriteMaterial extends Material { } +/** + * This class works just like {@link ShaderMaterial}, except that definitions + * of built-in uniforms and attributes are not automatically prepended to the + * GLSL shader code. + * + * `RawShaderMaterial` can only be used with {@link WebGLRenderer}. + * + * @augments ShaderMaterial + */ class RawShaderMaterial extends ShaderMaterial { + /** + * Constructs a new raw shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super( parameters ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isRawShaderMaterial = true; this.type = 'RawShaderMaterial'; @@ -38288,62 +52277,359 @@ class RawShaderMaterial extends ShaderMaterial { } +/** + * A standard physically based material, using Metallic-Roughness workflow. + * + * Physically based rendering (PBR) has recently become the standard in many + * 3D applications, such as [Unity]{@link https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/}, + * [Unreal]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/} and + * [3D Studio Max]{@link http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017}. + * + * This approach differs from older approaches in that instead of using + * approximations for the way in which light interacts with a surface, a + * physically correct model is used. The idea is that, instead of tweaking + * materials to look good under specific lighting, a material can be created + * that will react 'correctly' under all lighting scenarios. + * + * In practice this gives a more accurate and realistic looking result than + * the {@link MeshLambertMaterial} or {@link MeshPhongMaterial}, at the cost of + * being somewhat more computationally expensive. `MeshStandardMaterial` uses per-fragment + * shading. + * + * Note that for best results you should always specify an environment map when using this material. + * + * For a non-technical introduction to the concept of PBR and how to set up a + * PBR material, check out these articles by the people at [marmoset]{@link https://www.marmoset.co}: + * + * - [Basic Theory of Physically Based Rendering]{@link https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/} + * - [Physically Based Rendering and You Can Too]{@link https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/} + * + * Technical details of the approach used in three.js (and most other PBR systems) can be found is this + * [paper from Disney]{@link https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf} + * (pdf), by Brent Burley. + * + * @augments Material + */ class MeshStandardMaterial extends Material { + /** + * Constructs a new mesh standard material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshStandardMaterial = true; - this.defines = { 'STANDARD': '' }; - this.type = 'MeshStandardMaterial'; + this.defines = { 'STANDARD': '' }; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); // diffuse + + /** + * How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` + * means fully diffuse. If `roughnessMap` is also provided, + * both values are multiplied. + * + * @type {number} + * @default 1 + */ this.roughness = 1.0; + + /** + * How much the material is like a metal. Non-metallic materials such as wood + * or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. + * A value between `0.0` and `1.0` could be used for a rusty metal look. + * If `metalnessMap` is also provided, both values are multiplied. + * + * @type {number} + * @default 0 + */ this.metalness = 0.0; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ this.lightMapIntensity = 1.0; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ this.aoMapIntensity = 1.0; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ this.emissiveMap = null; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * The green channel of this texture is used to alter the roughness of the + * material. + * + * @type {?Texture} + * @default null + */ this.roughnessMap = null; + /** + * The blue channel of this texture is used to alter the metalness of the + * material. + * + * @type {?Texture} + * @default null + */ this.metalnessMap = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The environment map. To ensure a physically correct rendering, environment maps + * are internally pre-processed with {@link PMREMGenerator}. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ this.envMapRotation = new Euler(); + + /** + * Scales the effect of the environment map by multiplying its color. + * + * @type {number} + * @default 1 + */ this.envMapIntensity = 1.0; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinejoin = 'round'; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ this.flatShading = false; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -38408,12 +52694,53 @@ class MeshStandardMaterial extends Material { } +/** + * An extension of the {@link MeshStandardMaterial}, providing more advanced + * physically-based rendering properties: + * + * - Anisotropy: Ability to represent the anisotropic property of materials + * as observable with brushed metals. + * - Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require + * a clear, reflective layer on top of another layer that may be irregular or rough. + * Clearcoat approximates this effect, without the need for a separate transparent surface. + * - Iridescence: Allows to render the effect where hue varies depending on the viewing + * angle and illumination angle. This can be seen on soap bubbles, oil films, or on the + * wings of many insects. + * - Physically-based transparency: One limitation of {@link Material#opacity} is that highly + * transparent materials are less reflective. Physically-based transmission provides a more + * realistic option for thin, transparent surfaces like glass. + * - Advanced reflectivity: More flexible reflectivity for non-metallic materials. + * - Sheen: Can be used for representing cloth and fabric materials. + * + * As a result of these complex shading features, `MeshPhysicalMaterial` has a + * higher performance cost, per pixel, than other three.js materials. Most + * effects are disabled by default, and add cost as they are enabled. For + * best results, always specify an environment map when using this material. + * + * @augments MeshStandardMaterial + */ class MeshPhysicalMaterial extends MeshStandardMaterial { + /** + * Constructs a new mesh physical material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshPhysicalMaterial = true; this.defines = { @@ -38425,17 +52752,88 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { this.type = 'MeshPhysicalMaterial'; + /** + * The rotation of the anisotropy in tangent, bitangent space, measured in radians + * counter-clockwise from the tangent. When `anisotropyMap` is present, this + * property provides additional rotation to the vectors in the texture. + * + * @type {number} + * @default 1 + */ this.anisotropyRotation = 0; + + /** + * Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, + * bitangent space, to be rotated by `anisotropyRotation`. The blue channel + * contains strength as `[0, 1]` to be multiplied by `anisotropy`. + * + * @type {?Texture} + * @default null + */ this.anisotropyMap = null; + /** + * The red channel of this texture is multiplied against `clearcoat`, + * for per-pixel control over a coating's intensity. + * + * @type {?Texture} + * @default null + */ this.clearcoatMap = null; + + /** + * Roughness of the clear coat layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ this.clearcoatRoughness = 0.0; + + /** + * The green channel of this texture is multiplied against + * `clearcoatRoughness`, for per-pixel control over a coating's roughness. + * + * @type {?Texture} + * @default null + */ this.clearcoatRoughnessMap = null; + + /** + * How much `clearcoatNormalMap` affects the clear coat layer, from + * `(0,0)` to `(1,1)`. + * + * @type {Vector2} + * @default (1,1) + */ this.clearcoatNormalScale = new Vector2( 1, 1 ); + + /** + * Can be used to enable independent normals for the clear coat layer. + * + * @type {?Texture} + * @default null + */ this.clearcoatNormalMap = null; + /** + * Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. + * + * @type {number} + * @default 1.5 + */ this.ior = 1.5; + /** + * Degree of reflectivity, from `0.0` to `1.0`. Default is `0.5`, which + * corresponds to an index-of-refraction of `1.5`. + * + * This models the reflectivity of non-metallic materials. It has no effect + * when `metalness` is `1.0` + * + * @name MeshPhysicalMaterial#reflectivity + * @type {number} + * @default 0.5 + */ Object.defineProperty( this, 'reflectivity', { get: function () { @@ -38449,30 +52847,164 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } } ); + /** + * The red channel of this texture is multiplied against `iridescence`, for per-pixel + * control over iridescence. + * + * @type {?Texture} + * @default null + */ this.iridescenceMap = null; + + /** + * Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. + * Between `1.0` to `2.333`. + * + * @type {number} + * @default 1.3 + */ this.iridescenceIOR = 1.3; + + /** + *Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. + Thickness of iridescence layer has an equivalent effect of the one `thickness` has on `ior`. + * + * @type {Array<number,number>} + * @default [100,400] + */ this.iridescenceThicknessRange = [ 100, 400 ]; + + /** + * A texture that defines the thickness of the iridescence layer, stored in the green channel. + * Minimum and maximum values of thickness are defined by `iridescenceThicknessRange` array: + * - `0.0` in the green channel will result in thickness equal to first element of the array. + * - `1.0` in the green channel will result in thickness equal to second element of the array. + * - Values in-between will linearly interpolate between the elements of the array. + * + * @type {?Texture} + * @default null + */ this.iridescenceThicknessMap = null; + /** + * The sheen tint. + * + * @type {Color} + * @default (0,0,0) + */ this.sheenColor = new Color( 0x000000 ); + + /** + * The RGB channels of this texture are multiplied against `sheenColor`, for per-pixel control + * over sheen tint. + * + * @type {?Texture} + * @default null + */ this.sheenColorMap = null; + + /** + * Roughness of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ this.sheenRoughness = 1.0; + + /** + * The alpha channel of this texture is multiplied against `sheenRoughness`, for per-pixel control + * over sheen roughness. + * + * @type {?Texture} + * @default null + */ this.sheenRoughnessMap = null; + /** + * The red channel of this texture is multiplied against `transmission`, for per-pixel control over + * optical transparency. + * + * @type {?Texture} + * @default null + */ this.transmissionMap = null; + /** + * The thickness of the volume beneath the surface. The value is given in the + * coordinate space of the mesh. If the value is `0` the material is + * thin-walled. Otherwise the material is a volume boundary. + * + * @type {number} + * @default 0 + */ this.thickness = 0; + + /** + * A texture that defines the thickness, stored in the green channel. This will + * be multiplied by `thickness`. + * + * @type {?Texture} + * @default null + */ this.thicknessMap = null; + + /** + * Density of the medium given as the average distance that light travels in + * the medium before interacting with a particle. The value is given in world + * space units, and must be greater than zero. + * + * @type {number} + * @default Infinity + */ this.attenuationDistance = Infinity; + + /** + * The color that white light turns into due to absorption when reaching the + * attenuation distance. + * + * @type {Color} + * @default (1,1,1) + */ this.attenuationColor = new Color( 1, 1, 1 ); + /** + * A float that scales the amount of specular reflection for non-metals only. + * When set to zero, the model is effectively Lambertian. From `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ this.specularIntensity = 1.0; + + /** + * The alpha channel of this texture is multiplied against `specularIntensity`, + * for per-pixel control over specular intensity. + * + * @type {?Texture} + * @default null + */ this.specularIntensityMap = null; + + /** + * Tints the specular reflection at normal incidence for non-metals only. + * + * @type {Color} + * @default (1,1,1) + */ this.specularColor = new Color( 1, 1, 1 ); + + /** + * The RGB channels of this texture are multiplied against `specularColor`, + * for per-pixel control over specular color. + * + * @type {?Texture} + * @default null + */ this.specularColorMap = null; this._anisotropy = 0; this._clearcoat = 0; + this._dispersion = 0; this._iridescence = 0; this._sheen = 0.0; this._transmission = 0; @@ -38481,6 +53013,12 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } + /** + * The anisotropy strength. + * + * @type {number} + * @default 0 + */ get anisotropy() { return this._anisotropy; @@ -38499,6 +53037,14 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } + /** + * Represents the intensity of the clear coat layer, from `0.0` to `1.0`. Use + * clear coat related properties to enable multilayer materials that have a + * thin translucent layer over the base layer. + * + * @type {number} + * @default 0 + */ get clearcoat() { return this._clearcoat; @@ -38516,7 +53062,13 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { this._clearcoat = value; } - + /** + * The intensity of the iridescence layer, simulating RGB color shift based on the angle between + * the surface and the viewer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ get iridescence() { return this._iridescence; @@ -38535,6 +53087,38 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } + /** + * Defines the strength of the angular separation of colors (chromatic aberration) transmitting + * through a relatively clear volume. Any value zero or larger is valid, the typical range of + * realistic values is `[0, 1]`. This property can be only be used with transmissive objects. + * + * @type {number} + * @default 0 + */ + get dispersion() { + + return this._dispersion; + + } + + set dispersion( value ) { + + if ( this._dispersion > 0 !== value > 0 ) { + + this.version ++; + + } + + this._dispersion = value; + + } + + /** + * The intensity of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ get sheen() { return this._sheen; @@ -38553,6 +53137,18 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } + /** + * Degree of transmission (or optical transparency), from `0.0` to `1.0`. + * + * Thin, transparent or semitransparent, plastic or glass materials remain + * largely reflective even if they are fully transmissive. The transmission + * property can be used to model these materials. + * + * When transmission is non-zero, `opacity` should be set to `1`. + * + * @type {number} + * @default 0 + */ get transmission() { return this._transmission; @@ -38593,6 +53189,7 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { this.clearcoatNormalMap = source.clearcoatNormalMap; this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); + this.dispersion = source.dispersion; this.ior = source.ior; this.iridescence = source.iridescence; @@ -38626,60 +53223,348 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } +/** + * A material for shiny surfaces with specular highlights. + * + * The material uses a non-physically based [Blinn-Phong]{@link https://en.wikipedia.org/wiki/Blinn-Phong_shading_model} + * model for calculating reflectance. Unlike the Lambertian model used in the + * {@link MeshLambertMaterial} this can simulate shiny surfaces with specular + * highlights (such as varnished wood). `MeshPhongMaterial` uses per-fragment shading. + * + * Performance will generally be greater when using this material over the + * {@link MeshStandardMaterial} or {@link MeshPhysicalMaterial}, at the cost of + * some graphical accuracy. + * + * @augments Material + */ class MeshPhongMaterial extends Material { + /** + * Constructs a new mesh phong material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshPhongMaterial = true; this.type = 'MeshPhongMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); // diffuse + + /** + * Specular color of the material. The default color is set to `0x111111` (very dark grey) + * + * This defines how shiny the material is and the color of its shine. + * + * @type {Color} + */ this.specular = new Color( 0x111111 ); + + /** + * How shiny the specular highlight is; a higher value gives a sharper highlight. + * + * @type {number} + * @default 30 + */ this.shininess = 30; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ this.lightMapIntensity = 1.0; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ this.aoMapIntensity = 1.0; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ this.emissiveMap = null; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * The specular map value affects both how much the specular surface + * highlight contributes and how much of the environment map affects the + * surface. + * + * @type {?Texture} + * @default null + */ this.specularMap = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ this.refractionRatio = 0.98; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinejoin = 'round'; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ this.flatShading = false; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -38742,51 +53627,269 @@ class MeshPhongMaterial extends Material { } +/** + * A material implementing toon shading. + * + * @augments Material + */ class MeshToonMaterial extends Material { + /** + * Constructs a new mesh toon material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshToonMaterial = true; this.defines = { 'TOON': '' }; this.type = 'MeshToonMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + + /** + * Gradient map for toon shading. It's required to set + * {@link Texture#minFilter} and {@link Texture#magFilter} to {@linkNearestFilter} + * when using this type of texture. + * + * @type {?Texture} + * @default null + */ this.gradientMap = null; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ this.lightMapIntensity = 1.0; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ this.aoMapIntensity = 1.0; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ this.emissiveMap = null; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinejoin = 'round'; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -38838,30 +53941,143 @@ class MeshToonMaterial extends Material { } +/** + * A material that maps the normal vectors to RGB colors. + * + * @augments Material + */ class MeshNormalMaterial extends Material { + /** + * Constructs a new mesh normal material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshNormalMaterial = true; this.type = 'MeshNormalMaterial'; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ this.flatShading = false; this.setValues( parameters ); @@ -38894,58 +54110,331 @@ class MeshNormalMaterial extends Material { } +/** + * A material for non-shiny surfaces, without specular highlights. + * + * The material uses a non-physically based [Lambertian]{@link https://en.wikipedia.org/wiki/Lambertian_reflectance} + * model for calculating reflectance. This can simulate some surfaces (such + * as untreated wood or stone) well, but cannot simulate shiny surfaces with + * specular highlights (such as varnished wood). `MeshLambertMaterial` uses per-fragment + * shading. + * + * Due to the simplicity of the reflectance and illumination models, + * performance will be greater when using this material over the + * {@link MeshPhongMaterial}, {@link MeshStandardMaterial} or + * {@link MeshPhysicalMaterial}, at the cost of some graphical accuracy. + * + * @augments Material + */ class MeshLambertMaterial extends Material { + /** + * Constructs a new mesh lambert material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshLambertMaterial = true; this.type = 'MeshLambertMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); // diffuse + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ this.lightMapIntensity = 1.0; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ this.aoMapIntensity = 1.0; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ this.emissiveMap = null; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ this.specularMap = null; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ this.refractionRatio = 0.98; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ this.wireframeLinejoin = 'round'; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ this.flatShading = false; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -39006,39 +54495,181 @@ class MeshLambertMaterial extends Material { } +/** + * This material is defined by a MatCap (or Lit Sphere) texture, which encodes the + * material color and shading. + * + * `MeshMatcapMaterial` does not respond to lights since the matcap image file encodes + * baked lighting. It will cast a shadow onto an object that receives shadows + * (and shadow clipping works), but it will not self-shadow or receive + * shadows. + * + * @augments Material + */ class MeshMatcapMaterial extends Material { + /** + * Constructs a new mesh matcap material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMeshMatcapMaterial = true; this.defines = { 'MATCAP': '' }; this.type = 'MeshMatcapMaterial'; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ this.color = new Color( 0xffffff ); // diffuse + /** + * The matcap map. + * + * @type {?Texture} + * @default null + */ this.matcap = null; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ this.bumpScale = 1; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ this.normalScale = new Vector2( 1, 1 ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ this.displacementBias = 0; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ this.alphaMap = null; + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ this.flatShading = false; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ this.fog = true; this.setValues( parameters ); @@ -39081,18 +54712,69 @@ class MeshMatcapMaterial extends Material { } +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineDashedMaterial( { + * color: 0xffffff, + * scale: 1, + * dashSize: 3, + * gapSize: 1, + * } ); + * ``` + * + * @augments LineBasicMaterial + */ class LineDashedMaterial extends LineBasicMaterial { + /** + * Constructs a new line dashed material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineDashedMaterial = true; - this.type = 'LineDashedMaterial'; + /** + * The scale of the dashed part of a line. + * + * @type {number} + * @default 1 + */ this.scale = 1; + + /** + * The size of the dash. This is both the gap with the stroke. + * + * @type {number} + * @default 3 + */ this.dashSize = 3; + + /** + * The size of the gap. + * + * @type {number} + * @default 1 + */ this.gapSize = 1; this.setValues( parameters ); @@ -39113,12 +54795,39 @@ class LineDashedMaterial extends LineBasicMaterial { } +/** + * @class + * @classdesc A simple caching system, used internally by {@link FileLoader}. + * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app. + * @hideconstructor + */ const Cache = { + /** + * Whether caching is enabled or not. + * + * @static + * @type {boolean} + * @default false + */ enabled: false, + /** + * A dictionary that holds cached files. + * + * @static + * @type {Object<string,Object>} + */ files: {}, + /** + * Adds a cache entry with a key to reference the file. If this key already + * holds a file, it is overwritten. + * + * @static + * @param {string} key - The key to reference the cached file. + * @param {Object} file - The file to be cached. + */ add: function ( key, file ) { if ( this.enabled === false ) return; @@ -39129,6 +54838,13 @@ const Cache = { }, + /** + * Gets the cached value for the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned. + */ get: function ( key ) { if ( this.enabled === false ) return; @@ -39139,12 +54855,23 @@ const Cache = { }, + /** + * Removes the cached file associated with the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + */ remove: function ( key ) { delete this.files[ key ]; }, + /** + * Remove all values from the cache. + * + * @static + */ clear: function () { this.files = {}; @@ -39153,8 +54880,32 @@ const Cache = { }; +/** + * Handles and keeps track of loaded and pending data. A default global + * instance of this class is created and used by loaders if not supplied + * manually. + * + * In general that should be sufficient, however there are times when it can + * be useful to have separate loaders - for example if you want to show + * separate loading bars for objects and textures. + * + * ```js + * const manager = new THREE.LoadingManager(); + * manager.onLoad = () => console.log( 'Loading complete!' ); + * + * const loader1 = new OBJLoader( manager ); + * const loader2 = new ColladaLoader( manager ); + * ``` + */ class LoadingManager { + /** + * Constructs a new loading manager. + * + * @param {Function} [onLoad] - Executes when all items have been loaded. + * @param {Function} [onProgress] - Executes when single items have been loaded. + * @param {Function} [onError] - Executes when an error occurs. + */ constructor( onLoad, onProgress, onError ) { const scope = this; @@ -39168,11 +54919,51 @@ class LoadingManager { // Refer to #5689 for the reason why we don't set .onStart // in the constructor + /** + * Executes when an item starts loading. + * + * @type {Function|undefined} + * @default undefined + */ this.onStart = undefined; + + /** + * Executes when all items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ this.onLoad = onLoad; + + /** + * Executes when single items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ this.onProgress = onProgress; + + /** + * Executes when an error occurs. + * + * @type {Function|undefined} + * @default undefined + */ this.onError = onError; + /** + * Used for aborting ongoing requests in loaders using this manager. + * + * @type {AbortController} + */ + this.abortController = new AbortController(); + + /** + * This should be called by any loader using the manager when the loader + * starts loading an item. + * + * @param {string} url - The URL to load. + */ this.itemStart = function ( url ) { itemsTotal ++; @@ -39191,6 +54982,12 @@ class LoadingManager { }; + /** + * This should be called by any loader using the manager when the loader + * ended loading an item. + * + * @param {string} url - The URL of the loaded item. + */ this.itemEnd = function ( url ) { itemsLoaded ++; @@ -39215,6 +55012,12 @@ class LoadingManager { }; + /** + * This should be called by any loader using the manager when the loader + * encounters an error when loading an item. + * + * @param {string} url - The URL of the item that produces an error. + */ this.itemError = function ( url ) { if ( scope.onError !== undefined ) { @@ -39225,6 +55028,13 @@ class LoadingManager { }; + /** + * Given a URL, uses the URL modifier callback (if any) and returns a + * resolved URL. If no URL modifier is set, returns the original URL. + * + * @param {string} url - The URL to load. + * @return {string} The resolved URL. + */ this.resolveURL = function ( url ) { if ( urlModifier ) { @@ -39237,6 +55047,40 @@ class LoadingManager { }; + /** + * If provided, the callback will be passed each resource URL before a + * request is sent. The callback may return the original URL, or a new URL to + * override loading behavior. This behavior can be used to load assets from + * .ZIP files, drag-and-drop APIs, and Data URIs. + * + * ```js + * const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + * + * const manager = new THREE.LoadingManager(); + * + * // Initialize loading manager with URL callback. + * const objectURLs = []; + * manager.setURLModifier( ( url ) => { + * + * url = URL.createObjectURL( blobs[ url ] ); + * objectURLs.push( url ); + * return url; + * + * } ); + * + * // Load as usual, then revoke the blob URLs. + * const loader = new GLTFLoader( manager ); + * loader.load( 'fish.gltf', (gltf) => { + * + * scene.add( gltf.scene ); + * objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + * + * } ); + * ``` + * + * @param {function(string):string} transform - URL modifier callback. Called with an URL and must return a resolved URL. + * @return {LoadingManager} A reference to this loading manager. + */ this.setURLModifier = function ( transform ) { urlModifier = transform; @@ -39245,6 +55089,20 @@ class LoadingManager { }; + /** + * Registers a loader with the given regular expression. Can be used to + * define what loader should be used in order to load specific files. A + * typical use case is to overwrite the default loader for textures. + * + * ```js + * // add handler for TGA textures + * manager.addHandler( /\.tga$/i, new TGALoader() ); + * ``` + * + * @param {string} regex - A regular expression. + * @param {Loader} loader - A loader that should handle matched cases. + * @return {LoadingManager} A reference to this loading manager. + */ this.addHandler = function ( regex, loader ) { handlers.push( regex, loader ); @@ -39253,11 +55111,17 @@ class LoadingManager { }; + /** + * Removes the loader for the given regular expression. + * + * @param {string} regex - A regular expression. + * @return {LoadingManager} A reference to this loading manager. + */ this.removeHandler = function ( regex ) { const index = handlers.indexOf( regex ); - if ( index !== - 1 ) { + if ( index !== -1 ) { handlers.splice( index, 2 ); @@ -39267,6 +55131,12 @@ class LoadingManager { }; + /** + * Can be used to retrieve the registered loader for the given file path. + * + * @param {string} file - The file path. + * @return {?Loader} The registered loader. Returns `null` if no loader was found. + */ this.getHandler = function ( file ) { for ( let i = 0, l = handlers.length; i < l; i += 2 ) { @@ -39288,28 +55158,116 @@ class LoadingManager { }; + /** + * Can be used to abort ongoing loading requests in loaders using this manager. + * The abort only works if the loaders implement {@link Loader#abort} and `AbortSignal.any()` + * is supported in the browser. + * + * @return {LoadingManager} A reference to this loading manager. + */ + this.abort = function () { + + this.abortController.abort(); + this.abortController = new AbortController(); + + return this; + + }; + } } +/** + * The global default loading manager. + * + * @constant + * @type {LoadingManager} + */ const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); +/** + * Abstract base class for loaders. + * + * @abstract + */ class Loader { + /** + * Constructs a new loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { + /** + * The loading manager. + * + * @type {LoadingManager} + * @default DefaultLoadingManager + */ this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + /** + * The crossOrigin string to implement CORS for loading the url from a + * different domain that allows CORS. + * + * @type {string} + * @default 'anonymous' + */ this.crossOrigin = 'anonymous'; + + /** + * Whether the XMLHttpRequest uses credentials. + * + * @type {boolean} + * @default false + */ this.withCredentials = false; + + /** + * The base path from which the asset will be loaded. + * + * @type {string} + */ this.path = ''; + + /** + * The base path from which additional resources like textures will be loaded. + * + * @type {string} + */ this.resourcePath = ''; + + /** + * The [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * used in HTTP request. + * + * @type {Object<string, any>} + */ this.requestHeader = {}; } + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for loading assets from the backend. + * + * @abstract + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + */ load( /* url, onLoad, onProgress, onError */ ) {} + /** + * A async version of {@link Loader#load}. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves when the asset has been loaded. + */ loadAsync( url, onProgress ) { const scope = this; @@ -39322,8 +55280,22 @@ class Loader { } + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for parsing the asset into three.js entities. + * + * @abstract + * @param {any} data - The data to parse. + */ parse( /* data */ ) {} + /** + * Sets the `crossOrigin` String to implement CORS for loading the URL + * from a different domain that allows CORS. + * + * @param {string} crossOrigin - The `crossOrigin` value. + * @return {Loader} A reference to this instance. + */ setCrossOrigin( crossOrigin ) { this.crossOrigin = crossOrigin; @@ -39331,6 +55303,15 @@ class Loader { } + /** + * Whether the XMLHttpRequest uses credentials such as cookies, authorization + * headers or TLS client certificates, see [XMLHttpRequest.withCredentials]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials}. + * + * Note: This setting has no effect if you are loading files locally or from the same domain. + * + * @param {boolean} value - The `withCredentials` value. + * @return {Loader} A reference to this instance. + */ setWithCredentials( value ) { this.withCredentials = value; @@ -39338,6 +55319,12 @@ class Loader { } + /** + * Sets the base path for the asset. + * + * @param {string} path - The base path. + * @return {Loader} A reference to this instance. + */ setPath( path ) { this.path = path; @@ -39345,6 +55332,12 @@ class Loader { } + /** + * Sets the base path for dependent resources like textures. + * + * @param {string} resourcePath - The resource path. + * @return {Loader} A reference to this instance. + */ setResourcePath( resourcePath ) { this.resourcePath = resourcePath; @@ -39352,6 +55345,13 @@ class Loader { } + /** + * Sets the given request header. + * + * @param {Object} requestHeader - A [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * for configuring the HTTP request. + * @return {Loader} A reference to this instance. + */ setRequestHeader( requestHeader ) { this.requestHeader = requestHeader; @@ -39359,8 +55359,44 @@ class Loader { } + /** + * This method can be implemented in loaders for aborting ongoing requests. + * + * @abstract + * @return {Loader} A reference to this instance. + */ + abort() { + + return this; + + } + } +/** + * Callback for onProgress in loaders. + * + * @callback onProgressCallback + * @param {ProgressEvent} event - An instance of `ProgressEvent` that represents the current loading status. + */ + +/** + * Callback for onError in loaders. + * + * @callback onErrorCallback + * @param {Error} error - The error which occurred during the loading process. + */ + +/** + * The default material name that is used by loaders + * when creating materials for loaded 3D objects. + * + * Note: Not all loaders might honor this setting. + * + * @static + * @type {string} + * @default '__DEFAULT' + */ Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; const loading = {}; @@ -39376,14 +55412,67 @@ class HttpError extends Error { } +/** + * A low level class for loading resources with the Fetch API, used internally by + * most loaders. It can also be used directly to load any file type that does + * not have a loader. + * + * This loader supports caching. If you want to use it, add `THREE.Cache.enabled = true;` + * once to your application. + * + * ```js + * const loader = new THREE.FileLoader(); + * const data = await loader.loadAsync( 'example.txt' ); + * ``` + * + * @augments Loader + */ class FileLoader extends Loader { + /** + * Constructs a new file loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The expected mime type. Valid values can be found + * [here]{@link hhttps://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#mimetype} + * + * @type {string} + */ + this.mimeType = ''; + + /** + * The expected response type. + * + * @type {('arraybuffer'|'blob'|'document'|'json'|'')} + * @default '' + */ + this.responseType = ''; + + /** + * Used for aborting requests. + * + * @private + * @type {AbortController} + */ + this._abortController = new AbortController(); + } + /** + * Starts loading from the given URL and pass the loaded response to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(any)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + * @return {any|undefined} The cached resource if available. + */ load( url, onLoad, onProgress, onError ) { if ( url === undefined ) url = ''; @@ -39392,7 +55481,7 @@ class FileLoader extends Loader { url = this.manager.resolveURL( url ); - const cached = Cache.get( url ); + const cached = Cache.get( `file:${url}` ); if ( cached !== undefined ) { @@ -39439,7 +55528,7 @@ class FileLoader extends Loader { const req = new Request( url, { headers: new Headers( this.requestHeader ), credentials: this.withCredentials ? 'include' : 'same-origin', - // An abort controller could be added within a future PR + signal: ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal } ); // record states ( avoid data race ) @@ -39474,7 +55563,7 @@ class FileLoader extends Loader { // Nginx needs X-File-Size check // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content - const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' ); + const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' ); const total = contentLength ? parseInt( contentLength ) : 0; const lengthComputable = total !== 0; let loaded = 0; @@ -39510,6 +55599,10 @@ class FileLoader extends Loader { } + }, ( e ) => { + + controller.error( e ); + } ); } @@ -39555,7 +55648,7 @@ class FileLoader extends Loader { default: - if ( mimeType === undefined ) { + if ( mimeType === '' ) { return response.text(); @@ -39577,7 +55670,7 @@ class FileLoader extends Loader { // Add to cache only on HTTP success, so that we do not cache // error response bodies as proper responses to requests. - Cache.add( url, data ); + Cache.add( `file:${url}`, data ); const callbacks = loading[ url ]; delete loading[ url ]; @@ -39626,6 +55719,12 @@ class FileLoader extends Loader { } + /** + * Sets the expected response type. + * + * @param {('arraybuffer'|'blob'|'document'|'json'|'')} value - The response type. + * @return {FileLoader} A reference to this file loader. + */ setResponseType( value ) { this.responseType = value; @@ -39633,6 +55732,12 @@ class FileLoader extends Loader { } + /** + * Sets the expected mime type of the loaded file. + * + * @param {string} value - The mime type. + * @return {FileLoader} A reference to this file loader. + */ setMimeType( value ) { this.mimeType = value; @@ -39640,27 +55745,99 @@ class FileLoader extends Loader { } + /** + * Aborts ongoing fetch requests. + * + * @return {FileLoader} A reference to this instance. + */ + abort() { + + this._abortController.abort(); + this._abortController = new AbortController(); + + return this; + + } + } +/** + * Creates a texture based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments Texture + */ class CompressedTexture extends Texture { + /** + * Constructs a new compressed texture. + * + * @param {Array<Object>} mipmaps - This array holds for all mipmaps (including the bases mip) + * the data and dimensions. + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCompressedTexture = true; + /** + * The image property of a compressed texture just defines its dimensions. + * + * @type {{width:number,height:number}} + */ this.image = { width: width, height: height }; + + /** + * This array holds for all mipmaps (including the bases mip) the data and dimensions. + * + * @type {Array<Object>} + */ this.mipmaps = mipmaps; - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) - + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default since it is not possible to + * flip compressed textures. + * + * @type {boolean} + * @default false + * @readonly + */ this.flipY = false; - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files - + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default since it is not + * possible to generate mipmaps for compressed data. Mipmaps + * must be embedded in the compressed texture file. + * + * @type {boolean} + * @default false + * @readonly + */ this.generateMipmaps = false; } @@ -39668,19 +55845,40 @@ class CompressedTexture extends Texture { } /** - * Abstract Base class to block based textures loader (dds, pvr, ...) + * Abstract base class for loading compressed texture formats S3TC, ASTC or ETC. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. * - * Sub classes have to implement the parse() method which will be used in load(). + * @abstract + * @augments Loader */ - class CompressedTextureLoader extends Loader { + /** + * Constructs a new compressed texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded compressed texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(CompressedTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CompressedTexture} The compressed texture. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -39794,14 +55992,46 @@ class CompressedTextureLoader extends Loader { } +const _loading = new WeakMap(); + +/** + * A loader for loading images. The class loads images with the HTML `Image` API. + * + * ```js + * const loader = new THREE.ImageLoader(); + * const image = await loader.loadAsync( 'image.png' ); + * ``` + * Please note that `ImageLoader` has dropped support for progress + * events in `r84`. For an `ImageLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639}. + * + * @augments Loader + */ class ImageLoader extends Loader { + /** + * Constructs a new image loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded image + * to the `onLoad()` callback. The method also returns a new `Image` object which can + * directly be used for texture creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Image)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Image} The image. + */ load( url, onLoad, onProgress, onError ) { if ( this.path !== undefined ) url = this.path + url; @@ -39810,19 +56040,36 @@ class ImageLoader extends Loader { const scope = this; - const cached = Cache.get( url ); + const cached = Cache.get( `image:${url}` ); if ( cached !== undefined ) { - scope.manager.itemStart( url ); + if ( cached.complete === true ) { - setTimeout( function () { + scope.manager.itemStart( url ); - if ( onLoad ) onLoad( cached ); + setTimeout( function () { - scope.manager.itemEnd( url ); + if ( onLoad ) onLoad( cached ); - }, 0 ); + scope.manager.itemEnd( url ); + + }, 0 ); + + } else { + + let arr = _loading.get( cached ); + + if ( arr === undefined ) { + + arr = []; + _loading.set( cached, arr ); + + } + + arr.push( { onLoad, onError } ); + + } return cached; @@ -39834,10 +56081,21 @@ class ImageLoader extends Loader { removeEventListeners(); - Cache.add( url, this ); - if ( onLoad ) onLoad( this ); + // + + const callbacks = _loading.get( this ) || []; + + for ( let i = 0; i < callbacks.length; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( this ); + + } + + _loading.delete( this ); + scope.manager.itemEnd( url ); } @@ -39848,6 +56106,22 @@ class ImageLoader extends Loader { if ( onError ) onError( event ); + Cache.remove( `image:${url}` ); + + // + + const callbacks = _loading.get( this ) || []; + + for ( let i = 0; i < callbacks.length; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); + + } + + _loading.delete( this ); + + scope.manager.itemError( url ); scope.manager.itemEnd( url ); @@ -39869,6 +56143,7 @@ class ImageLoader extends Loader { } + Cache.add( `image:${url}`, image ); scope.manager.itemStart( url ); image.src = url; @@ -39879,14 +56154,59 @@ class ImageLoader extends Loader { } +/** + * Class for loading cube textures. Images are internally loaded via {@link ImageLoader}. + * + * The loader returns an instance of {@link CubeTexture} and expects the cube map to + * be defined as six separate images representing the sides of a cube. Other cube map definitions + * like vertical and horizontal cross, column and row layouts are not supported. + * + * Note that, by convention, cube maps are specified in a coordinate system + * in which positive-x is to the right when looking up the positive-z axis -- + * in other words, using a left-handed coordinate system. Since three.js uses + * a right-handed coordinate system, environment maps used in three.js will + * have pos-x and neg-x swapped. + * + * The loaded cube texture is in sRGB color space. Meaning {@link Texture#colorSpace} + * is set to `SRGBColorSpace` by default. + * + * ```js + * const loader = new THREE.CubeTextureLoader().setPath( 'textures/cubeMaps/' ); + * const cubeTexture = await loader.loadAsync( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * scene.background = cubeTexture; + * ``` + * + * @augments Loader + */ class CubeTextureLoader extends Loader { + /** + * Constructs a new cube texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and pass the fully loaded cube texture + * to the `onLoad()` callback. The method also returns a new cube texture object which can + * directly be used for material creation. If you do it this way, the cube texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {Array<string>} urls - Array of 6 URLs to images, one for each side of the + * cube texture. The urls should be specified in the following order: pos-x, + * neg-x, pos-y, neg-y, pos-z, neg-z. An array of data URIs are allowed as well. + * @param {function(CubeTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CubeTexture} The cube texture. + */ load( urls, onLoad, onProgress, onError ) { const texture = new CubeTexture(); @@ -39931,19 +56251,40 @@ class CubeTextureLoader extends Loader { } /** - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) + * Abstract base class for loading binary texture formats RGBE, EXR or TGA. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. * - * Sub classes have to implement the parse() method which will be used in load(). + * @abstract + * @augments Loader */ - class DataTextureLoader extends Loader { + /** + * Constructs a new data texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded data texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(DataTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {DataTexture} The data texture. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -40054,14 +56395,47 @@ class DataTextureLoader extends Loader { } +/** + * Class for loading textures. Images are internally + * loaded via {@link ImageLoader}. + * + * ```js + * const loader = new THREE.TextureLoader(); + * const texture = await loader.loadAsync( 'textures/land_ocean_ice_cloud_2048.jpg' ); + * + * const material = new THREE.MeshBasicMaterial( { map:texture } ); + * ``` + * Please note that `TextureLoader` has dropped support for progress + * events in `r84`. For a `TextureLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145}. + * + * @augments Loader + */ class TextureLoader extends Loader { + /** + * Constructs a new texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and pass the fully loaded texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Texture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Texture} The texture. + */ load( url, onLoad, onProgress, onError ) { const texture = new Texture(); @@ -40089,6 +56463,265 @@ class TextureLoader extends Loader { } +const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _frustum$1 = /*@__PURE__*/ new Frustum(); + +/** + * FrustumArray is used to determine if an object is visible in at least one camera + * from an array of cameras. This is particularly useful for multi-view renderers. +*/ +class FrustumArray { + + /** + * Constructs a new frustum array. + * + */ + constructor() { + + /** + * The coordinate system to use. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + */ + this.coordinateSystem = WebGLCoordinateSystem; + + } + + /** + * Returns `true` if the 3D object's bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Object3D} object - The 3D object to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the 3D object is visible in any camera. + */ + intersectsObject( object, cameraArray ) { + + if ( ! cameraArray.isArrayCamera || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + camera.coordinateSystem, + camera.reversedDepth + ); + + if ( _frustum$1.intersectsObject( object ) ) { + + return true; // Object is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given sprite is intersecting any frustum + * from the camera array. + * + * @param {Sprite} sprite - The sprite to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sprite is visible in any camera. + */ + intersectsSprite( sprite, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + camera.coordinateSystem, + camera.reversedDepth + ); + + if ( _frustum$1.intersectsSprite( sprite ) ) { + + return true; // Sprite is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sphere is visible in any camera. + */ + intersectsSphere( sphere, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + camera.coordinateSystem, + camera.reversedDepth + ); + + if ( _frustum$1.intersectsSphere( sphere ) ) { + + return true; // Sphere is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding box is intersecting any frustum + * from the camera array. + * + * @param {Box3} box - The bounding box to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the box is visible in any camera. + */ + intersectsBox( box, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + camera.coordinateSystem, + camera.reversedDepth + ); + + if ( _frustum$1.intersectsBox( box ) ) { + + return true; // Box is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given point lies within any frustum + * from the camera array. + * + * @param {Vector3} point - The point to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the point is visible in any camera. + */ + containsPoint( point, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + camera.coordinateSystem, + camera.reversedDepth + ); + + if ( _frustum$1.containsPoint( point ) ) { + + return true; // Point is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns a new frustum array with copied values from this instance. + * + * @return {FrustumArray} A clone of this instance. + */ + clone() { + + return new FrustumArray(); + + } + +} + +function ascIdSort( a, b ) { + + return a - b; + +} + function sortOpaque( a, b ) { return a.z - b.z; @@ -40111,7 +56744,7 @@ class MultiDrawRenderList { } - push( drawRange, z ) { + push( start, count, z, index ) { const pool = this.pool; const list = this.list; @@ -40119,9 +56752,10 @@ class MultiDrawRenderList { pool.push( { - start: - 1, - count: - 1, - z: - 1, + start: -1, + count: -1, + z: -1, + index: -1, } ); @@ -40131,9 +56765,10 @@ class MultiDrawRenderList { list.push( item ); this.index ++; - item.start = drawRange.start; - item.count = drawRange.count; + item.start = start; + item.count = count; item.z = z; + item.index = index; } @@ -40146,26 +56781,19 @@ class MultiDrawRenderList { } -const ID_ATTR_NAME = 'batchId'; const _matrix$1 = /*@__PURE__*/ new Matrix4(); -const _invMatrixWorld = /*@__PURE__*/ new Matrix4(); -const _identityMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _whiteColor = /*@__PURE__*/ new Color( 1, 1, 1 ); const _frustum = /*@__PURE__*/ new Frustum(); +const _frustumArray = /*@__PURE__*/ new FrustumArray(); const _box = /*@__PURE__*/ new Box3(); const _sphere$1 = /*@__PURE__*/ new Sphere(); const _vector$2 = /*@__PURE__*/ new Vector3(); +const _forward = /*@__PURE__*/ new Vector3(); +const _temp = /*@__PURE__*/ new Vector3(); const _renderList = /*@__PURE__*/ new MultiDrawRenderList(); const _mesh = /*@__PURE__*/ new Mesh(); const _batchIntersects = []; -// @TODO: SkinnedMesh support? -// @TODO: geometry.groups support? -// @TODO: geometry.drawRange support? -// @TODO: geometry.morphAttributes support? -// @TODO: Support uniform parameter per geometry -// @TODO: Add an "optimize" function to pack geometry and remove data gaps - // copies data from attribute "src" into "target" starting at "targetOffset" function copyAttributeData( src, target, targetOffset = 0 ) { @@ -40196,47 +56824,215 @@ function copyAttributeData( src, target, targetOffset = 0 ) { } -class BatchedMesh extends Mesh { +// safely copies array contents to a potentially smaller array +function copyArrayContents( src, target ) { + + if ( src.constructor !== target.constructor ) { + + // if arrays are of a different type (eg due to index size increasing) then data must be per-element copied + const len = Math.min( src.length, target.length ); + for ( let i = 0; i < len; i ++ ) { + + target[ i ] = src[ i ]; + + } - get maxGeometryCount() { + } else { - return this._maxGeometryCount; + // if the arrays use the same data layout we can use a fast block copy + const len = Math.min( src.length, target.length ); + target.set( new src.constructor( src.buffer, 0, len ) ); } - constructor( maxGeometryCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { +} + +/** + * A special version of a mesh with multi draw batch rendering support. Use + * this class if you have to render a large number of objects with the same + * material but with different geometries or world transformations. The usage of + * `BatchedMesh` will help you to reduce the number of draw calls and thus improve the overall + * rendering performance in your application. + * + * ```js + * const box = new THREE.BoxGeometry( 1, 1, 1 ); + * const sphere = new THREE.SphereGeometry( 1, 12, 12 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * + * // initialize and add geometries into the batched mesh + * const batchedMesh = new BatchedMesh( 10, 5000, 10000, material ); + * const boxGeometryId = batchedMesh.addGeometry( box ); + * const sphereGeometryId = batchedMesh.addGeometry( sphere ); + * + * // create instances of those geometries + * const boxInstancedId1 = batchedMesh.addInstance( boxGeometryId ); + * const boxInstancedId2 = batchedMesh.addInstance( boxGeometryId ); + * + * const sphereInstancedId1 = batchedMesh.addInstance( sphereGeometryId ); + * const sphereInstancedId2 = batchedMesh.addInstance( sphereGeometryId ); + * + * // position the geometries + * batchedMesh.setMatrixAt( boxInstancedId1, boxMatrix1 ); + * batchedMesh.setMatrixAt( boxInstancedId2, boxMatrix2 ); + * + * batchedMesh.setMatrixAt( sphereInstancedId1, sphereMatrix1 ); + * batchedMesh.setMatrixAt( sphereInstancedId2, sphereMatrix2 ); + * + * scene.add( batchedMesh ); + * ``` + * + * @augments Mesh + */ +class BatchedMesh extends Mesh { + + /** + * Constructs a new batched mesh. + * + * @param {number} maxInstanceCount - The maximum number of individual instances planned to be added and rendered. + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries. + * @param {number} [maxIndexCount=maxVertexCount*2] - The maximum number of indices to be used by all unique geometries + * @param {Material|Array<Material>} [material] - The mesh material. + */ + constructor( maxInstanceCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { super( new BufferGeometry(), material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBatchedMesh = true; + + /** + * When set ot `true`, the individual objects of a batch are frustum culled. + * + * @type {boolean} + * @default true + */ this.perObjectFrustumCulled = true; + + /** + * When set to `true`, the individual objects of a batch are sorted to improve overdraw-related artifacts. + * If the material is marked as "transparent" objects are rendered back to front and if not then they are + * rendered front to back. + * + * @type {boolean} + * @default true + */ this.sortObjects = true; + + /** + * The bounding box of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ this.boundingBox = null; + + /** + * The bounding sphere of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ this.boundingSphere = null; + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered + * sort with. + * + * @type {?Function} + * @default null + */ this.customSort = null; - this._drawRanges = []; - this._reservedRanges = []; + // stores visible, active, and geometry id per instance and reserved buffer ranges for geometries + this._instanceInfo = []; + this._geometryInfo = []; - this._visibility = []; - this._active = []; - this._bounds = []; + // instance, geometry ids that have been set as inactive, and are available to be overwritten + this._availableInstanceIds = []; + this._availableGeometryIds = []; + + // used to track where the next point is that geometry should be inserted + this._nextIndexStart = 0; + this._nextVertexStart = 0; + this._geometryCount = 0; + + // flags + this._visibilityChanged = true; + this._geometryInitialized = false; - this._maxGeometryCount = maxGeometryCount; + // cached user options + this._maxInstanceCount = maxInstanceCount; this._maxVertexCount = maxVertexCount; this._maxIndexCount = maxIndexCount; - this._geometryInitialized = false; - this._geometryCount = 0; - this._multiDrawCounts = new Int32Array( maxGeometryCount ); - this._multiDrawStarts = new Int32Array( maxGeometryCount ); + // buffers for multi draw + this._multiDrawCounts = new Int32Array( maxInstanceCount ); + this._multiDrawStarts = new Int32Array( maxInstanceCount ); this._multiDrawCount = 0; - this._visibilityChanged = true; + this._multiDrawInstances = null; // Local matrix per geometry by using data texture this._matricesTexture = null; + this._indirectTexture = null; + this._colorsTexture = null; this._initMatricesTexture(); + this._initIndirectTexture(); + + } + + /** + * The maximum number of individual instances that can be stored in the batch. + * + * @type {number} + * @readonly + */ + get maxInstanceCount() { + + return this._maxInstanceCount; + + } + + /** + * The instance count. + * + * @type {number} + * @readonly + */ + get instanceCount() { + + return this._instanceInfo.length - this._availableInstanceIds.length; + + } + + /** + * The number of unused vertices. + * + * @type {number} + * @readonly + */ + get unusedVertexCount() { + + return this._maxVertexCount - this._nextVertexStart; + + } + + /** + * The number of unused indices. + * + * @type {number} + * @readonly + */ + get unusedIndexCount() { + + return this._maxIndexCount - this._nextIndexStart; } @@ -40249,7 +57045,7 @@ class BatchedMesh extends Mesh { // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) - let size = Math.sqrt( this._maxGeometryCount * 4 ); // 4 pixels needed for 1 matrix + let size = Math.sqrt( this._maxInstanceCount * 4 ); // 4 pixels needed for 1 matrix size = Math.ceil( size / 4 ) * 4; size = Math.max( size, 4 ); @@ -40260,11 +57056,36 @@ class BatchedMesh extends Mesh { } + _initIndirectTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + const indirectArray = new Uint32Array( size * size ); + const indirectTexture = new DataTexture( indirectArray, size, size, RedIntegerFormat, UnsignedIntType ); + + this._indirectTexture = indirectTexture; + + } + + _initColorsTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + // 4 floats per RGBA pixel initialized to white + const colorsArray = new Float32Array( size * size * 4 ).fill( 1 ); + const colorsTexture = new DataTexture( colorsArray, size, size, RGBAFormat, FloatType ); + colorsTexture.colorSpace = ColorManagement.workingColorSpace; + + this._colorsTexture = colorsTexture; + + } + _initializeGeometry( reference ) { const geometry = this.geometry; const maxVertexCount = this._maxVertexCount; - const maxGeometryCount = this._maxGeometryCount; const maxIndexCount = this._maxIndexCount; if ( this._geometryInitialized === false ) { @@ -40274,8 +57095,7 @@ class BatchedMesh extends Mesh { const { array, itemSize, normalized } = srcAttribute; const dstArray = new array.constructor( maxVertexCount * itemSize ); - const dstAttribute = new srcAttribute.constructor( dstArray, itemSize, normalized ); - dstAttribute.setUsage( srcAttribute.usage ); + const dstAttribute = new BufferAttribute( dstArray, itemSize, normalized ); geometry.setAttribute( attributeName, dstAttribute ); @@ -40283,7 +57103,8 @@ class BatchedMesh extends Mesh { if ( reference.getIndex() !== null ) { - const indexArray = maxVertexCount > 65536 + // Reserve last u16 index for primitive restart. + const indexArray = maxVertexCount > 65535 ? new Uint32Array( maxIndexCount ) : new Uint16Array( maxIndexCount ); @@ -40291,46 +57112,28 @@ class BatchedMesh extends Mesh { } - const idArray = maxGeometryCount > 65536 - ? new Uint32Array( maxVertexCount ) - : new Uint16Array( maxVertexCount ); - geometry.setAttribute( ID_ATTR_NAME, new BufferAttribute( idArray, 1 ) ); - this._geometryInitialized = true; } } - // Make sure the geometry is compatible with the existing combined geometry atributes + // Make sure the geometry is compatible with the existing combined geometry attributes _validateGeometry( geometry ) { - // check that the geometry doesn't have a version of our reserved id attribute - if ( geometry.getAttribute( ID_ATTR_NAME ) ) { - - throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` ); - - } - // check to ensure the geometries are using consistent attributes and indices const batchGeometry = this.geometry; if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) { - throw new Error( 'BatchedMesh: All geometries must consistently have "index".' ); + throw new Error( 'THREE.BatchedMesh: All geometries must consistently have "index".' ); } for ( const attributeName in batchGeometry.attributes ) { - if ( attributeName === ID_ATTR_NAME ) { - - continue; - - } - if ( ! geometry.hasAttribute( attributeName ) ) { - throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); + throw new Error( `THREE.BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); } @@ -40338,7 +57141,7 @@ class BatchedMesh extends Mesh { const dstAttribute = batchGeometry.getAttribute( attributeName ); if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) { - throw new Error( 'BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); + throw new Error( 'THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); } @@ -40346,6 +57149,45 @@ class BatchedMesh extends Mesh { } + /** + * Validates the instance defined by the given ID. + * + * @param {number} instanceId - The instance to validate. + */ + validateInstanceId( instanceId ) { + + const instanceInfo = this._instanceInfo; + if ( instanceId < 0 || instanceId >= instanceInfo.length || instanceInfo[ instanceId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid instanceId ${instanceId}. Instance is either out of range or has been deleted.` ); + + } + + } + + /** + * Validates the geometry defined by the given ID. + * + * @param {number} geometryId - The geometry to validate. + */ + validateGeometryId( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId < 0 || geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid geometryId ${geometryId}. Geometry is either out of range or has been deleted.` ); + + } + + } + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered sort with. + * + * @param {Function} func - The custom sort function. + * @return {BatchedMesh} A reference to this batched mesh. + */ setCustomSort( func ) { this.customSort = func; @@ -40353,6 +57195,11 @@ class BatchedMesh extends Mesh { } + /** + * Computes the bounding box, updating {@link BatchedMesh#boundingBox}. + * Bounding boxes aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ computeBoundingBox() { if ( this.boundingBox === null ) { @@ -40361,23 +57208,28 @@ class BatchedMesh extends Mesh { } - const geometryCount = this._geometryCount; const boundingBox = this.boundingBox; - const active = this._active; + const instanceInfo = this._instanceInfo; boundingBox.makeEmpty(); - for ( let i = 0; i < geometryCount; i ++ ) { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { - if ( active[ i ] === false ) continue; + if ( instanceInfo[ i ].active === false ) continue; + const geometryId = instanceInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix$1 ); - this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix$1 ); + this.getBoundingBoxAt( geometryId, _box ).applyMatrix4( _matrix$1 ); boundingBox.union( _box ); } } + /** + * Computes the bounding sphere, updating {@link BatchedMesh#boundingSphere}. + * Bounding spheres aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ computeBoundingSphere() { if ( this.boundingSphere === null ) { @@ -40386,163 +57238,188 @@ class BatchedMesh extends Mesh { } - const geometryCount = this._geometryCount; const boundingSphere = this.boundingSphere; - const active = this._active; + const instanceInfo = this._instanceInfo; boundingSphere.makeEmpty(); - for ( let i = 0; i < geometryCount; i ++ ) { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { - if ( active[ i ] === false ) continue; + if ( instanceInfo[ i ].active === false ) continue; + const geometryId = instanceInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix$1 ); - this.getBoundingSphereAt( i, _sphere$1 ).applyMatrix4( _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$1 ).applyMatrix4( _matrix$1 ); boundingSphere.union( _sphere$1 ); } } - addGeometry( geometry, vertexCount = - 1, indexCount = - 1 ) { - - this._initializeGeometry( geometry ); + /** + * Adds a new instance to the batch using the geometry of the given ID and returns + * a new id referring to the new instance to be used by other functions. + * + * @param {number} geometryId - The ID of a previously added geometry via {@link BatchedMesh#addGeometry}. + * @return {number} The instance ID. + */ + addInstance( geometryId ) { - this._validateGeometry( geometry ); + const atCapacity = this._instanceInfo.length >= this.maxInstanceCount; // ensure we're not over geometry - if ( this._geometryCount >= this._maxGeometryCount ) { + if ( atCapacity && this._availableInstanceIds.length === 0 ) { - throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); + throw new Error( 'THREE.BatchedMesh: Maximum item count reached.' ); } - // get the necessary range fo the geometry - const reservedRange = { - vertexStart: - 1, - vertexCount: - 1, - indexStart: - 1, - indexCount: - 1, + const instanceInfo = { + visible: true, + active: true, + geometryIndex: geometryId, }; - let lastRange = null; - const reservedRanges = this._reservedRanges; - const drawRanges = this._drawRanges; - const bounds = this._bounds; - if ( this._geometryCount !== 0 ) { - - lastRange = reservedRanges[ reservedRanges.length - 1 ]; + let drawId = null; - } + // Prioritize using previously freed instance ids + if ( this._availableInstanceIds.length > 0 ) { - if ( vertexCount === - 1 ) { + this._availableInstanceIds.sort( ascIdSort ); - reservedRange.vertexCount = geometry.getAttribute( 'position' ).count; + drawId = this._availableInstanceIds.shift(); + this._instanceInfo[ drawId ] = instanceInfo; } else { - reservedRange.vertexCount = vertexCount; + drawId = this._instanceInfo.length; + this._instanceInfo.push( instanceInfo ); } - if ( lastRange === null ) { - - reservedRange.vertexStart = 0; + const matricesTexture = this._matricesTexture; + _matrix$1.identity().toArray( matricesTexture.image.data, drawId * 16 ); + matricesTexture.needsUpdate = true; - } else { + const colorsTexture = this._colorsTexture; + if ( colorsTexture ) { - reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount; + _whiteColor.toArray( colorsTexture.image.data, drawId * 4 ); + colorsTexture.needsUpdate = true; } - const index = geometry.getIndex(); - const hasIndex = index !== null; - if ( hasIndex ) { + this._visibilityChanged = true; + return drawId; + + } - if ( indexCount === - 1 ) { + /** + * Adds the given geometry to the batch and returns the associated + * geometry id referring to it to be used in other functions. + * + * @param {BufferGeometry} geometry - The geometry to add. + * @param {number} [reservedVertexCount=-1] - Optional parameter specifying the amount of + * vertex buffer space to reserve for the added geometry. This is necessary if it is planned + * to set a new geometry at this index at a later time that is larger than the original geometry. + * Defaults to the length of the given geometry vertex buffer. + * @param {number} [reservedIndexCount=-1] - Optional parameter specifying the amount of index + * buffer space to reserve for the added geometry. This is necessary if it is planned to set a + * new geometry at this index at a later time that is larger than the original geometry. Defaults to + * the length of the given geometry index buffer. + * @return {number} The geometry ID. + */ + addGeometry( geometry, reservedVertexCount = -1, reservedIndexCount = -1 ) { - reservedRange.indexCount = index.count; + this._initializeGeometry( geometry ); - } else { + this._validateGeometry( geometry ); - reservedRange.indexCount = indexCount; + const geometryInfo = { + // geometry information + vertexStart: -1, + vertexCount: -1, + reservedVertexCount: -1, - } + indexStart: -1, + indexCount: -1, + reservedIndexCount: -1, - if ( lastRange === null ) { + // draw range information + start: -1, + count: -1, - reservedRange.indexStart = 0; + // state + boundingBox: null, + boundingSphere: null, + active: true, + }; - } else { + const geometryInfoList = this._geometryInfo; + geometryInfo.vertexStart = this._nextVertexStart; + geometryInfo.reservedVertexCount = reservedVertexCount === -1 ? geometry.getAttribute( 'position' ).count : reservedVertexCount; - reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount; + const index = geometry.getIndex(); + const hasIndex = index !== null; + if ( hasIndex ) { - } + geometryInfo.indexStart = this._nextIndexStart; + geometryInfo.reservedIndexCount = reservedIndexCount === -1 ? index.count : reservedIndexCount; } if ( - reservedRange.indexStart !== - 1 && - reservedRange.indexStart + reservedRange.indexCount > this._maxIndexCount || - reservedRange.vertexStart + reservedRange.vertexCount > this._maxVertexCount + geometryInfo.indexStart !== -1 && + geometryInfo.indexStart + geometryInfo.reservedIndexCount > this._maxIndexCount || + geometryInfo.vertexStart + geometryInfo.reservedVertexCount > this._maxVertexCount ) { - throw new Error( 'BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); + throw new Error( 'THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); } - const visibility = this._visibility; - const active = this._active; - const matricesTexture = this._matricesTexture; - const matricesArray = this._matricesTexture.image.data; - - // push new visibility states - visibility.push( true ); - active.push( true ); - // update id - const geometryId = this._geometryCount; - this._geometryCount ++; + let geometryId; + if ( this._availableGeometryIds.length > 0 ) { - // initialize matrix information - _identityMatrix$1.toArray( matricesArray, geometryId * 16 ); - matricesTexture.needsUpdate = true; + this._availableGeometryIds.sort( ascIdSort ); - // add the reserved range and draw range objects - reservedRanges.push( reservedRange ); - drawRanges.push( { - start: hasIndex ? reservedRange.indexStart : reservedRange.vertexStart, - count: - 1 - } ); - bounds.push( { - boxInitialized: false, - box: new Box3(), + geometryId = this._availableGeometryIds.shift(); + geometryInfoList[ geometryId ] = geometryInfo; - sphereInitialized: false, - sphere: new Sphere() - } ); - // set the id for the geometry - const idAttribute = this.geometry.getAttribute( ID_ATTR_NAME ); - for ( let i = 0; i < reservedRange.vertexCount; i ++ ) { + } else { - idAttribute.setX( reservedRange.vertexStart + i, geometryId ); + geometryId = this._geometryCount; + this._geometryCount ++; + geometryInfoList.push( geometryInfo ); } - idAttribute.needsUpdate = true; - // update the geometry this.setGeometryAt( geometryId, geometry ); + // increment the next geometry position + this._nextIndexStart = geometryInfo.indexStart + geometryInfo.reservedIndexCount; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + return geometryId; } - setGeometryAt( id, geometry ) { + /** + * Replaces the geometry at the given ID with the provided geometry. Throws an error if there + * is not enough space reserved for geometry. Calling this will change all instances that are + * rendering that geometry. + * + * @param {number} geometryId - The ID of the geometry that should be replaced with the given geometry. + * @param {BufferGeometry} geometry - The new geometry. + * @return {number} The geometry ID. + */ + setGeometryAt( geometryId, geometry ) { - if ( id >= this._geometryCount ) { + if ( geometryId >= this._geometryCount ) { - throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); + throw new Error( 'THREE.BatchedMesh: Maximum geometry count reached.' ); } @@ -40552,27 +57429,23 @@ class BatchedMesh extends Mesh { const hasIndex = batchGeometry.getIndex() !== null; const dstIndex = batchGeometry.getIndex(); const srcIndex = geometry.getIndex(); - const reservedRange = this._reservedRanges[ id ]; + const geometryInfo = this._geometryInfo[ geometryId ]; if ( hasIndex && - srcIndex.count > reservedRange.indexCount || - geometry.attributes.position.count > reservedRange.vertexCount + srcIndex.count > geometryInfo.reservedIndexCount || + geometry.attributes.position.count > geometryInfo.reservedVertexCount ) { - throw new Error( 'BatchedMesh: Reserved space not large enough for provided geometry.' ); + throw new Error( 'THREE.BatchedMesh: Reserved space not large enough for provided geometry.' ); } - // copy geometry over - const vertexStart = reservedRange.vertexStart; - const vertexCount = reservedRange.vertexCount; - for ( const attributeName in batchGeometry.attributes ) { - - if ( attributeName === ID_ATTR_NAME ) { - - continue; + // copy geometry buffer data over + const vertexStart = geometryInfo.vertexStart; + const reservedVertexCount = geometryInfo.reservedVertexCount; + geometryInfo.vertexCount = geometry.getAttribute( 'position' ).count; - } + for ( const attributeName in batchGeometry.attributes ) { // copy attribute data const srcAttribute = geometry.getAttribute( attributeName ); @@ -40581,7 +57454,7 @@ class BatchedMesh extends Mesh { // fill the rest in with zeroes const itemSize = srcAttribute.itemSize; - for ( let i = srcAttribute.count, l = vertexCount; i < l; i ++ ) { + for ( let i = srcAttribute.count, l = reservedVertexCount; i < l; i ++ ) { const index = vertexStart + i; for ( let c = 0; c < itemSize; c ++ ) { @@ -40593,13 +57466,16 @@ class BatchedMesh extends Mesh { } dstAttribute.needsUpdate = true; + dstAttribute.addUpdateRange( vertexStart * itemSize, reservedVertexCount * itemSize ); } // copy index if ( hasIndex ) { - const indexStart = reservedRange.indexStart; + const indexStart = geometryInfo.indexStart; + const reservedIndexCount = geometryInfo.reservedIndexCount; + geometryInfo.indexCount = geometry.getIndex().count; // copy index data over for ( let i = 0; i < srcIndex.count; i ++ ) { @@ -40609,90 +57485,215 @@ class BatchedMesh extends Mesh { } // fill the rest in with zeroes - for ( let i = srcIndex.count, l = reservedRange.indexCount; i < l; i ++ ) { + for ( let i = srcIndex.count, l = reservedIndexCount; i < l; i ++ ) { dstIndex.setX( indexStart + i, vertexStart ); } dstIndex.needsUpdate = true; + dstIndex.addUpdateRange( indexStart, geometryInfo.reservedIndexCount ); } + // update the draw range + geometryInfo.start = hasIndex ? geometryInfo.indexStart : geometryInfo.vertexStart; + geometryInfo.count = hasIndex ? geometryInfo.indexCount : geometryInfo.vertexCount; + // store the bounding boxes - const bound = this._bounds[ id ]; + geometryInfo.boundingBox = null; if ( geometry.boundingBox !== null ) { - bound.box.copy( geometry.boundingBox ); - bound.boxInitialized = true; + geometryInfo.boundingBox = geometry.boundingBox.clone(); - } else { + } + + geometryInfo.boundingSphere = null; + if ( geometry.boundingSphere !== null ) { - bound.boxInitialized = false; + geometryInfo.boundingSphere = geometry.boundingSphere.clone(); } - if ( geometry.boundingSphere !== null ) { + this._visibilityChanged = true; + return geometryId; - bound.sphere.copy( geometry.boundingSphere ); - bound.sphereInitialized = true; + } - } else { + /** + * Deletes the geometry defined by the given ID from this batch. Any instances referencing + * this geometry will also be removed as a side effect. + * + * @param {number} geometryId - The ID of the geometry to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteGeometry( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + return this; + + } + + // delete any instances associated with this geometry + const instanceInfo = this._instanceInfo; + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active && instanceInfo[ i ].geometryIndex === geometryId ) { - bound.sphereInitialized = false; + this.deleteInstance( i ); + + } } - // set drawRange count - const drawRange = this._drawRanges[ id ]; - const posAttr = geometry.getAttribute( 'position' ); - drawRange.count = hasIndex ? srcIndex.count : posAttr.count; + geometryInfoList[ geometryId ].active = false; + this._availableGeometryIds.push( geometryId ); this._visibilityChanged = true; - return id; + return this; } - deleteGeometry( geometryId ) { + /** + * Deletes an existing instance from the batch using the given ID. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteInstance( instanceId ) { - // Note: User needs to call optimize() afterward to pack the data. + this.validateInstanceId( instanceId ); - const active = this._active; - if ( geometryId >= active.length || active[ geometryId ] === false ) { + this._instanceInfo[ instanceId ].active = false; + this._availableInstanceIds.push( instanceId ); + this._visibilityChanged = true; - return this; + return this; - } + } - active[ geometryId ] = false; - this._visibilityChanged = true; + /** + * Repacks the sub geometries in [name] to remove any unused space remaining from + * previously deleted geometry, freeing up space to add new geometry. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + optimize() { + + // track the next indices to copy data to + let nextVertexStart = 0; + let nextIndexStart = 0; + + // Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest + // in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order. + const geometryInfoList = this._geometryInfo; + const indices = geometryInfoList + .map( ( e, i ) => i ) + .sort( ( a, b ) => { + + return geometryInfoList[ a ].vertexStart - geometryInfoList[ b ].vertexStart; + + } ); + + const geometry = this.geometry; + for ( let i = 0, l = geometryInfoList.length; i < l; i ++ ) { + + // if a geometry range is inactive then don't copy anything + const index = indices[ i ]; + const geometryInfo = geometryInfoList[ index ]; + if ( geometryInfo.active === false ) { + + continue; + + } + + // if a geometry contains an index buffer then shift it, as well + if ( geometry.index !== null ) { + + if ( geometryInfo.indexStart !== nextIndexStart ) { + + const { indexStart, vertexStart, reservedIndexCount } = geometryInfo; + const index = geometry.index; + const array = index.array; + + // shift the index pointers based on how the vertex data will shift + // adjusting the index must happen first so the original vertex start value is available + const elementDelta = nextVertexStart - vertexStart; + for ( let j = indexStart; j < indexStart + reservedIndexCount; j ++ ) { + + array[ j ] = array[ j ] + elementDelta; + + } + + index.array.copyWithin( nextIndexStart, indexStart, indexStart + reservedIndexCount ); + index.addUpdateRange( nextIndexStart, reservedIndexCount ); + + geometryInfo.indexStart = nextIndexStart; + + } + + nextIndexStart += geometryInfo.reservedIndexCount; + + } + + // if a geometry needs to be moved then copy attribute data to overwrite unused space + if ( geometryInfo.vertexStart !== nextVertexStart ) { + + const { vertexStart, reservedVertexCount } = geometryInfo; + const attributes = geometry.attributes; + for ( const key in attributes ) { + + const attribute = attributes[ key ]; + const { array, itemSize } = attribute; + array.copyWithin( nextVertexStart * itemSize, vertexStart * itemSize, ( vertexStart + reservedVertexCount ) * itemSize ); + attribute.addUpdateRange( nextVertexStart * itemSize, reservedVertexCount * itemSize ); + + } + + geometryInfo.vertexStart = nextVertexStart; + + } + + nextVertexStart += geometryInfo.reservedVertexCount; + geometryInfo.start = geometry.index ? geometryInfo.indexStart : geometryInfo.vertexStart; + + // step the next geometry points to the shifted position + this._nextIndexStart = geometry.index ? geometryInfo.indexStart + geometryInfo.reservedIndexCount : 0; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + + } return this; } - // get bounding box and compute it if it doesn't exist - getBoundingBoxAt( id, target ) { + /** + * Returns the bounding box for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding box for. + * @param {Box3} target - The target object that is used to store the method's result. + * @return {?Box3} The geometry's bounding box. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingBoxAt( geometryId, target ) { - const active = this._active; - if ( active[ id ] === false ) { + if ( geometryId >= this._geometryCount ) { return null; } // compute bounding box - const bound = this._bounds[ id ]; - const box = bound.box; const geometry = this.geometry; - if ( bound.boxInitialized === false ) { - - box.makeEmpty(); + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingBox === null ) { + const box = new Box3(); const index = geometry.index; const position = geometry.attributes.position; - const drawRange = this._drawRanges[ id ]; - for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { let iv = i; if ( index ) { @@ -40705,42 +57706,44 @@ class BatchedMesh extends Mesh { } - bound.boxInitialized = true; + geometryInfo.boundingBox = box; } - target.copy( box ); + target.copy( geometryInfo.boundingBox ); return target; } - // get bounding sphere and compute it if it doesn't exist - getBoundingSphereAt( id, target ) { + /** + * Returns the bounding sphere for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding sphere for. + * @param {Sphere} target - The target object that is used to store the method's result. + * @return {?Sphere} The geometry's bounding sphere. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingSphereAt( geometryId, target ) { - const active = this._active; - if ( active[ id ] === false ) { + if ( geometryId >= this._geometryCount ) { return null; } // compute bounding sphere - const bound = this._bounds[ id ]; - const sphere = bound.sphere; const geometry = this.geometry; - if ( bound.sphereInitialized === false ) { - - sphere.makeEmpty(); + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingSphere === null ) { - this.getBoundingBoxAt( id, _box ); + const sphere = new Sphere(); + this.getBoundingBoxAt( geometryId, _box ); _box.getCenter( sphere.center ); const index = geometry.index; const position = geometry.attributes.position; - const drawRange = this._drawRanges[ id ]; let maxRadiusSq = 0; - for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { let iv = i; if ( index ) { @@ -40755,100 +57758,319 @@ class BatchedMesh extends Mesh { } sphere.radius = Math.sqrt( maxRadiusSq ); - bound.sphereInitialized = true; + geometryInfo.boundingSphere = sphere; } - target.copy( sphere ); + target.copy( geometryInfo.boundingSphere ); return target; } - setMatrixAt( geometryId, matrix ) { + /** + * Sets the given local transformation matrix to the defined instance. + * Negatively scaled matrices are not supported. + * + * @param {number} instanceId - The ID of an instance to set the matrix of. + * @param {Matrix4} matrix - A 4x4 matrix representing the local transformation of a single instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setMatrixAt( instanceId, matrix ) { - // @TODO: Map geometryId to index of the arrays because - // optimize() can make geometryId mismatch the index + this.validateInstanceId( instanceId ); - const active = this._active; const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + matrix.toArray( matricesArray, instanceId * 16 ); + matricesTexture.needsUpdate = true; - return this; + return this; - } + } - matrix.toArray( matricesArray, geometryId * 16 ); - matricesTexture.needsUpdate = true; + /** + * Returns the local transformation matrix of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the matrix of. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + * @return {Matrix4} The instance's local transformation matrix. + */ + getMatrixAt( instanceId, matrix ) { - return this; + this.validateInstanceId( instanceId ); + return matrix.fromArray( this._matricesTexture.image.data, instanceId * 16 ); } - getMatrixAt( geometryId, matrix ) { + /** + * Sets the given color to the defined instance. + * + * @param {number} instanceId - The ID of an instance to set the color of. + * @param {Color} color - The color to set the instance to. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setColorAt( instanceId, color ) { - const active = this._active; - const matricesArray = this._matricesTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + this.validateInstanceId( instanceId ); - return null; + if ( this._colorsTexture === null ) { + + this._initColorsTexture(); } - return matrix.fromArray( matricesArray, geometryId * 16 ); + color.toArray( this._colorsTexture.image.data, instanceId * 4 ); + this._colorsTexture.needsUpdate = true; + + return this; } - setVisibleAt( geometryId, value ) { + /** + * Returns the color of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the color of. + * @param {Color} color - The target object that is used to store the method's result. + * @return {Color} The instance's color. + */ + getColorAt( instanceId, color ) { - const visibility = this._visibility; - const active = this._active; - const geometryCount = this._geometryCount; + this.validateInstanceId( instanceId ); + return color.fromArray( this._colorsTexture.image.data, instanceId * 4 ); - // if the geometry is out of range, not active, or visibility state - // does not change then return early - if ( - geometryId >= geometryCount || - active[ geometryId ] === false || - visibility[ geometryId ] === value - ) { + } + + /** + * Sets the visibility of the instance. + * + * @param {number} instanceId - The id of the instance to set the visibility of. + * @param {boolean} visible - Whether the instance is visible or not. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setVisibleAt( instanceId, visible ) { + + this.validateInstanceId( instanceId ); + + if ( this._instanceInfo[ instanceId ].visible === visible ) { return this; } - visibility[ geometryId ] = value; + this._instanceInfo[ instanceId ].visible = visible; this._visibilityChanged = true; return this; } - getVisibleAt( geometryId ) { + /** + * Returns the visibility state of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the visibility state of. + * @return {boolean} Whether the instance is visible or not. + */ + getVisibleAt( instanceId ) { - const visibility = this._visibility; - const active = this._active; - const geometryCount = this._geometryCount; + this.validateInstanceId( instanceId ); - // return early if the geometry is out of range or not active - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + return this._instanceInfo[ instanceId ].visible; - return false; + } + + /** + * Sets the geometry ID of the instance at the given index. + * + * @param {number} instanceId - The ID of the instance to set the geometry ID of. + * @param {number} geometryId - The geometry ID to be use by the instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setGeometryIdAt( instanceId, geometryId ) { + + this.validateInstanceId( instanceId ); + this.validateGeometryId( geometryId ); + + this._instanceInfo[ instanceId ].geometryIndex = geometryId; + + return this; + + } + + /** + * Returns the geometry ID of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the geometry ID of. + * @return {number} The instance's geometry ID. + */ + getGeometryIdAt( instanceId ) { + + this.validateInstanceId( instanceId ); + + return this._instanceInfo[ instanceId ].geometryIndex; + + } + + /** + * Get the range representing the subset of triangles related to the attached geometry, + * indicating the starting offset and count, or `null` if invalid. + * + * @param {number} geometryId - The id of the geometry to get the range of. + * @param {Object} [target] - The target object that is used to store the method's result. + * @return {{ + * vertexStart:number,vertexCount:number,reservedVertexCount:number, + * indexStart:number,indexCount:number,reservedIndexCount:number, + * start:number,count:number + * }} The result object with range data. + */ + getGeometryRangeAt( geometryId, target = {} ) { + + this.validateGeometryId( geometryId ); + + const geometryInfo = this._geometryInfo[ geometryId ]; + target.vertexStart = geometryInfo.vertexStart; + target.vertexCount = geometryInfo.vertexCount; + target.reservedVertexCount = geometryInfo.reservedVertexCount; + + target.indexStart = geometryInfo.indexStart; + target.indexCount = geometryInfo.indexCount; + target.reservedIndexCount = geometryInfo.reservedIndexCount; + + target.start = geometryInfo.start; + target.count = geometryInfo.count; + + return target; + + } + + /** + * Resizes the necessary buffers to support the provided number of instances. + * If the provided arguments shrink the number of instances but there are not enough + * unused Ids at the end of the list then an error is thrown. + * + * @param {number} maxInstanceCount - The max number of individual instances that can be added and rendered by the batch. + */ + setInstanceCount( maxInstanceCount ) { + + // shrink the available instances as much as possible + const availableInstanceIds = this._availableInstanceIds; + const instanceInfo = this._instanceInfo; + availableInstanceIds.sort( ascIdSort ); + while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === instanceInfo.length - 1 ) { + + instanceInfo.pop(); + availableInstanceIds.pop(); + + } + + // throw an error if it can't be shrunk to the desired size + if ( maxInstanceCount < instanceInfo.length ) { + + throw new Error( `BatchedMesh: Instance ids outside the range ${ maxInstanceCount } are being used. Cannot shrink instance count.` ); + + } + + // copy the multi draw counts + const multiDrawCounts = new Int32Array( maxInstanceCount ); + const multiDrawStarts = new Int32Array( maxInstanceCount ); + copyArrayContents( this._multiDrawCounts, multiDrawCounts ); + copyArrayContents( this._multiDrawStarts, multiDrawStarts ); + + this._multiDrawCounts = multiDrawCounts; + this._multiDrawStarts = multiDrawStarts; + this._maxInstanceCount = maxInstanceCount; + + // update texture data for instance sampling + const indirectTexture = this._indirectTexture; + const matricesTexture = this._matricesTexture; + const colorsTexture = this._colorsTexture; + + indirectTexture.dispose(); + this._initIndirectTexture(); + copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data ); + + matricesTexture.dispose(); + this._initMatricesTexture(); + copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data ); + + if ( colorsTexture ) { + + colorsTexture.dispose(); + this._initColorsTexture(); + copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data ); + + } + + } + + /** + * Resizes the available space in the batch's vertex and index buffer attributes to the provided sizes. + * If the provided arguments shrink the geometry buffers but there is not enough unused space at the + * end of the geometry attributes then an error is thrown. + * + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries to resize to. + * @param {number} maxIndexCount - The maximum number of indices to be used by all unique geometries to resize to. + */ + setGeometrySize( maxVertexCount, maxIndexCount ) { + + // Check if we can shrink to the requested vertex attribute size + const validRanges = [ ...this._geometryInfo ].filter( info => info.active ); + const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.reservedVertexCount ) ); + if ( requiredVertexLength > maxVertexCount ) { + + throw new Error( `BatchedMesh: Geometry vertex values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); + + } + + // Check if we can shrink to the requested index attribute size + if ( this.geometry.index ) { + + const requiredIndexLength = Math.max( ...validRanges.map( range => range.indexStart + range.reservedIndexCount ) ); + if ( requiredIndexLength > maxIndexCount ) { + + throw new Error( `BatchedMesh: Geometry index values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); + + } + + } + + // + + // dispose of the previous geometry + const oldGeometry = this.geometry; + oldGeometry.dispose(); + + // recreate the geometry needed based on the previous variant + this._maxVertexCount = maxVertexCount; + this._maxIndexCount = maxIndexCount; + + if ( this._geometryInitialized ) { + + this._geometryInitialized = false; + this.geometry = new BufferGeometry(); + this._initializeGeometry( oldGeometry ); } - return visibility[ geometryId ]; + // copy data from the previous geometry + const geometry = this.geometry; + if ( oldGeometry.index ) { + + copyArrayContents( oldGeometry.index.array, geometry.index.array ); + + } + + for ( const key in oldGeometry.attributes ) { + + copyArrayContents( oldGeometry.attributes[ key ].array, geometry.attributes[ key ].array ); + + } } raycast( raycaster, intersects ) { - const visibility = this._visibility; - const active = this._active; - const drawRanges = this._drawRanges; - const geometryCount = this._geometryCount; + const instanceInfo = this._instanceInfo; + const geometryInfoList = this._geometryInfo; const matrixWorld = this.matrixWorld; const batchGeometry = this.geometry; @@ -40868,21 +58090,22 @@ class BatchedMesh extends Mesh { } - for ( let i = 0; i < geometryCount; i ++ ) { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { - if ( ! visibility[ i ] || ! active[ i ] ) { + if ( ! instanceInfo[ i ].visible || ! instanceInfo[ i ].active ) { continue; } - const drawRange = drawRanges[ i ]; - _mesh.geometry.setDrawRange( drawRange.start, drawRange.count ); + const geometryId = instanceInfo[ i ].geometryIndex; + const geometryInfo = geometryInfoList[ geometryId ]; + _mesh.geometry.setDrawRange( geometryInfo.start, geometryInfo.count ); - // ge the intersects + // get the intersects this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); - this.getBoundingBoxAt( i, _mesh.geometry.boundingBox ); - this.getBoundingSphereAt( i, _mesh.geometry.boundingSphere ); + this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); + this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere ); _mesh.raycast( raycaster, _batchIntersects ); // add batch id to the intersects @@ -40916,35 +58139,50 @@ class BatchedMesh extends Mesh { this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; - this._drawRanges = source._drawRanges.map( range => ( { ...range } ) ); - this._reservedRanges = source._reservedRanges.map( range => ( { ...range } ) ); - - this._visibility = source._visibility.slice(); - this._active = source._active.slice(); - this._bounds = source._bounds.map( bound => ( { - boxInitialized: bound.boxInitialized, - box: bound.box.clone(), + this._geometryInfo = source._geometryInfo.map( info => ( { + ...info, - sphereInitialized: bound.sphereInitialized, - sphere: bound.sphere.clone() + boundingBox: info.boundingBox !== null ? info.boundingBox.clone() : null, + boundingSphere: info.boundingSphere !== null ? info.boundingSphere.clone() : null, } ) ); + this._instanceInfo = source._instanceInfo.map( info => ( { ...info } ) ); - this._maxGeometryCount = source._maxGeometryCount; + this._availableInstanceIds = source._availableInstanceIds.slice(); + this._availableGeometryIds = source._availableGeometryIds.slice(); + + this._nextIndexStart = source._nextIndexStart; + this._nextVertexStart = source._nextVertexStart; + this._geometryCount = source._geometryCount; + + this._maxInstanceCount = source._maxInstanceCount; this._maxVertexCount = source._maxVertexCount; this._maxIndexCount = source._maxIndexCount; this._geometryInitialized = source._geometryInitialized; - this._geometryCount = source._geometryCount; this._multiDrawCounts = source._multiDrawCounts.slice(); this._multiDrawStarts = source._multiDrawStarts.slice(); + this._indirectTexture = source._indirectTexture.clone(); + this._indirectTexture.image.data = this._indirectTexture.image.data.slice(); + this._matricesTexture = source._matricesTexture.clone(); - this._matricesTexture.image.data = this._matricesTexture.image.slice(); + this._matricesTexture.image.data = this._matricesTexture.image.data.slice(); + + if ( this._colorsTexture !== null ) { + + this._colorsTexture = source._colorsTexture.clone(); + this._colorsTexture.image.data = this._colorsTexture.image.data.slice(); + + } return this; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { // Assuming the geometry is not shared with other meshes @@ -40952,7 +58190,16 @@ class BatchedMesh extends Mesh { this._matricesTexture.dispose(); this._matricesTexture = null; - return this; + + this._indirectTexture.dispose(); + this._indirectTexture = null; + + if ( this._colorsTexture !== null ) { + + this._colorsTexture.dispose(); + this._colorsTexture = null; + + } } @@ -40971,54 +58218,62 @@ class BatchedMesh extends Mesh { const index = geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; - const active = this._active; - const visibility = this._visibility; + const instanceInfo = this._instanceInfo; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; - const drawRanges = this._drawRanges; + const geometryInfoList = this._geometryInfo; const perObjectFrustumCulled = this.perObjectFrustumCulled; + const indirectTexture = this._indirectTexture; + const indirectArray = indirectTexture.image.data; + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; // prepare the frustum in the local frame - if ( perObjectFrustumCulled ) { + if ( perObjectFrustumCulled && ! camera.isArrayCamera ) { - _projScreenMatrix$2 + _matrix$1 .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) .multiply( this.matrixWorld ); + _frustum.setFromProjectionMatrix( - _projScreenMatrix$2, - renderer.coordinateSystem + _matrix$1, + camera.coordinateSystem, + camera.reversedDepth ); } - let count = 0; + let multiDrawCount = 0; if ( this.sortObjects ) { // get the camera position in the local frame - _invMatrixWorld.copy( this.matrixWorld ).invert(); - _vector$2.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _invMatrixWorld ); + _matrix$1.copy( this.matrixWorld ).invert(); + _vector$2.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _matrix$1 ); + _forward.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ).transformDirection( _matrix$1 ); - for ( let i = 0, l = visibility.length; i < l; i ++ ) { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { - if ( visibility[ i ] && active[ i ] ) { + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { + + const geometryId = instanceInfo[ i ].geometryIndex; // get the bounds in world space this.getMatrixAt( i, _matrix$1 ); - this.getBoundingSphereAt( i, _sphere$1 ).applyMatrix4( _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$1 ).applyMatrix4( _matrix$1 ); // determine whether the batched geometry is within the frustum let culled = false; if ( perObjectFrustumCulled ) { - culled = ! _frustum.intersectsSphere( _sphere$1 ); + culled = ! frustum.intersectsSphere( _sphere$1, camera ); } if ( ! culled ) { // get the distance from camera used for sorting - const z = _vector$2.distanceTo( _sphere$1.center ); - _renderList.push( drawRanges[ i ], z ); + const geometryInfo = geometryInfoList[ geometryId ]; + const z = _temp.subVectors( _sphere$1.center, _vector$2 ).dot( _forward ); + _renderList.push( geometryInfo.start, geometryInfo.count, z, i ); } @@ -41042,9 +58297,10 @@ class BatchedMesh extends Mesh { for ( let i = 0, l = list.length; i < l; i ++ ) { const item = list[ i ]; - multiDrawStarts[ count ] = item.start * bytesPerElement; - multiDrawCounts[ count ] = item.count; - count ++; + multiDrawStarts[ multiDrawCount ] = item.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = item.count; + indirectArray[ multiDrawCount ] = item.index; + multiDrawCount ++; } @@ -41052,9 +58308,11 @@ class BatchedMesh extends Mesh { } else { - for ( let i = 0, l = visibility.length; i < l; i ++ ) { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { - if ( visibility[ i ] && active[ i ] ) { + const geometryId = instanceInfo[ i ].geometryIndex; // determine whether the batched geometry is within the frustum let culled = false; @@ -41062,17 +58320,18 @@ class BatchedMesh extends Mesh { // get the bounds in world space this.getMatrixAt( i, _matrix$1 ); - this.getBoundingSphereAt( i, _sphere$1 ).applyMatrix4( _matrix$1 ); - culled = ! _frustum.intersectsSphere( _sphere$1 ); + this.getBoundingSphereAt( geometryId, _sphere$1 ).applyMatrix4( _matrix$1 ); + culled = ! frustum.intersectsSphere( _sphere$1, camera ); } if ( ! culled ) { - const range = drawRanges[ i ]; - multiDrawStarts[ count ] = range.start * bytesPerElement; - multiDrawCounts[ count ] = range.count; - count ++; + const geometryInfo = geometryInfoList[ geometryId ]; + multiDrawStarts[ multiDrawCount ] = geometryInfo.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = geometryInfo.count; + indirectArray[ multiDrawCount ] = i; + multiDrawCount ++; } @@ -41082,7 +58341,8 @@ class BatchedMesh extends Mesh { } - this._multiDrawCount = count; + indirectTexture.needsUpdate = true; + this._multiDrawCount = multiDrawCount; this._visibilityChanged = false; } @@ -41095,41 +58355,115 @@ class BatchedMesh extends Mesh { } +/** + * "Interleaved" means that multiple attributes, possibly of different types, + * (e.g., position, normal, uv, color) are packed into a single array buffer. + * + * An introduction into interleaved arrays can be found here: [Interleaved array basics]{@link https://blog.tojicode.com/2011/05/interleaved-array-basics.html} + */ class InterleavedBuffer { + /** + * Constructs a new interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + */ constructor( array, stride ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInterleavedBuffer = true; + /** + * A typed array with a shared buffer storing attribute data. + * + * @type {TypedArray} + */ this.array = array; + + /** + * The number of typed-array elements per vertex. + * + * @type {number} + */ this.stride = stride; + + /** + * The total number of elements in the array + * + * @type {number} + * @readonly + */ this.count = array !== undefined ? array.length / stride : 0; + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ this.usage = StaticDrawUsage; - this._updateRange = { offset: 0, count: - 1 }; + + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array<Object>} + */ this.updateRanges = []; + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ this.version = 0; + /** + * The UUID of the interleaved buffer. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); } + /** + * A callback function that is executed after the renderer has transferred the attribute array + * data to the GPU. + */ onUploadCallback() {} + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ set needsUpdate( value ) { if ( value === true ) this.version ++; } - get updateRange() { - - warnOnce( 'THREE.InterleavedBuffer: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead.' ); // @deprecated, r159 - return this._updateRange; - - } - + /** + * Sets the usage of this interleaved buffer. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {InterleavedBuffer} A reference to this interleaved buffer. + */ setUsage( value ) { this.usage = value; @@ -41138,18 +58472,33 @@ class InterleavedBuffer { } + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ addUpdateRange( start, count ) { this.updateRanges.push( { start, count } ); } + /** + * Clears the update ranges. + */ clearUpdateRanges() { this.updateRanges.length = 0; } + /** + * Copies the values of the given interleaved buffer to this instance. + * + * @param {InterleavedBuffer} source - The interleaved buffer to copy. + * @return {InterleavedBuffer} A reference to this instance. + */ copy( source ) { this.array = new source.array.constructor( source.array ); @@ -41161,14 +58510,24 @@ class InterleavedBuffer { } - copyAt( index1, attribute, index2 ) { + /** + * Copies a vector from the given interleaved buffer to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this interleaved buffer. + * @param {InterleavedBuffer} interleavedBuffer - The interleaved buffer to copy from. + * @param {number} index2 - The source index into the given interleaved buffer. + * @return {InterleavedBuffer} A reference to this instance. + */ + copyAt( index1, interleavedBuffer, index2 ) { index1 *= this.stride; - index2 *= attribute.stride; + index2 *= interleavedBuffer.stride; for ( let i = 0, l = this.stride; i < l; i ++ ) { - this.array[ index1 + i ] = attribute.array[ index2 + i ]; + this.array[ index1 + i ] = interleavedBuffer.array[ index2 + i ]; } @@ -41176,6 +58535,13 @@ class InterleavedBuffer { } + /** + * Sets the given array data in the interleaved buffer. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this interleaved buffer's array. + * @return {InterleavedBuffer} A reference to this instance. + */ set( value, offset = 0 ) { this.array.set( value, offset ); @@ -41184,6 +58550,12 @@ class InterleavedBuffer { } + /** + * Returns a new interleaved buffer with copied values from this instance. + * + * @param {Object} [data] - An object with shared array buffers that allows to retain shared structures. + * @return {InterleavedBuffer} A clone of this instance. + */ clone( data ) { if ( data.arrayBuffers === undefined ) { @@ -41213,6 +58585,14 @@ class InterleavedBuffer { } + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the array data to the GPU. Can be used to perform clean-up operations after + * the upload when data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {InterleavedBuffer} A reference to this instance. + */ onUpload( callback ) { this.onUploadCallback = callback; @@ -41221,6 +58601,12 @@ class InterleavedBuffer { } + /** + * Serializes the interleaved buffer into JSON. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized interleaved buffer. + */ toJSON( data ) { if ( data.arrayBuffers === undefined ) { @@ -41258,40 +58644,113 @@ class InterleavedBuffer { const _vector$1 = /*@__PURE__*/ new Vector3(); +/** + * An alternative version of a buffer attribute with interleaved data. Interleaved + * attributes share a common interleaved data storage ({@link InterleavedBuffer}) and refer with + * different offsets into the buffer. + */ class InterleavedBufferAttribute { + /** + * Constructs a new interleaved buffer attribute. + * + * @param {InterleavedBuffer} interleavedBuffer - The buffer holding the interleaved data. + * @param {number} itemSize - The item size. + * @param {number} offset - The attribute offset into the buffer. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ constructor( interleavedBuffer, itemSize, offset, normalized = false ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInterleavedBufferAttribute = true; + /** + * The name of the buffer attribute. + * + * @type {string} + */ this.name = ''; + /** + * The buffer holding the interleaved data. + * + * @type {InterleavedBuffer} + */ this.data = interleavedBuffer; + + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ this.itemSize = itemSize; + + /** + * The attribute offset into the buffer. + * + * @type {number} + */ this.offset = offset; + /** + * Whether the data are normalized or not, see {@link BufferAttribute#normalized} + * + * @type {InterleavedBuffer} + */ this.normalized = normalized; } + /** + * The item count of this buffer attribute. + * + * @type {number} + * @readonly + */ get count() { return this.data.count; } + /** + * The array holding the interleaved buffer attribute data. + * + * @type {TypedArray} + */ get array() { return this.data.array; } + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ set needsUpdate( value ) { this.data.needsUpdate = value; } + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ applyMatrix4( m ) { for ( let i = 0, l = this.data.count; i < l; i ++ ) { @@ -41308,6 +58767,13 @@ class InterleavedBufferAttribute { } + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ applyNormalMatrix( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { @@ -41324,6 +58790,13 @@ class InterleavedBufferAttribute { } + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ transformDirection( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { @@ -41340,6 +58813,13 @@ class InterleavedBufferAttribute { } + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ getComponent( index, component ) { let value = this.array[ index * this.data.stride + this.offset + component ]; @@ -41350,6 +58830,14 @@ class InterleavedBufferAttribute { } + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setComponent( index, component, value ) { if ( this.normalized ) value = normalize( value, this.array ); @@ -41360,6 +58848,13 @@ class InterleavedBufferAttribute { } + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setX( index, x ) { if ( this.normalized ) x = normalize( x, this.array ); @@ -41370,6 +58865,13 @@ class InterleavedBufferAttribute { } + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setY( index, y ) { if ( this.normalized ) y = normalize( y, this.array ); @@ -41380,6 +58882,13 @@ class InterleavedBufferAttribute { } + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setZ( index, z ) { if ( this.normalized ) z = normalize( z, this.array ); @@ -41390,6 +58899,13 @@ class InterleavedBufferAttribute { } + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setW( index, w ) { if ( this.normalized ) w = normalize( w, this.array ); @@ -41400,6 +58916,12 @@ class InterleavedBufferAttribute { } + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ getX( index ) { let x = this.data.array[ index * this.data.stride + this.offset ]; @@ -41410,6 +58932,12 @@ class InterleavedBufferAttribute { } + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ getY( index ) { let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; @@ -41420,6 +58948,12 @@ class InterleavedBufferAttribute { } + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ getZ( index ) { let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; @@ -41430,6 +58964,12 @@ class InterleavedBufferAttribute { } + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ getW( index ) { let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; @@ -41440,6 +58980,14 @@ class InterleavedBufferAttribute { } + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setXY( index, x, y ) { index = index * this.data.stride + this.offset; @@ -41458,6 +59006,15 @@ class InterleavedBufferAttribute { } + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setXYZ( index, x, y, z ) { index = index * this.data.stride + this.offset; @@ -41478,6 +59035,16 @@ class InterleavedBufferAttribute { } + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ setXYZW( index, x, y, z, w ) { index = index * this.data.stride + this.offset; @@ -41500,6 +59067,14 @@ class InterleavedBufferAttribute { } + /** + * Returns a new buffer attribute with copied values from this instance. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An object with interleaved buffers that allows to retain the interleaved property. + * @return {BufferAttribute|InterleavedBufferAttribute} A clone of this instance. + */ clone( data ) { if ( data === undefined ) { @@ -41542,6 +59117,14 @@ class InterleavedBufferAttribute { } + /** + * Serializes the buffer attribute into JSON. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized buffer attribute. + */ toJSON( data ) { if ( data === undefined ) { @@ -41619,12 +59202,41 @@ const _uvA = /*@__PURE__*/ new Vector2(); const _uvB = /*@__PURE__*/ new Vector2(); const _uvC = /*@__PURE__*/ new Vector2(); +/** + * A sprite is a plane that always faces towards the camera, generally with a + * partially transparent texture applied. + * + * Sprites do not cast shadows, setting {@link Object3D#castShadow} to `true` will + * have no effect. + * + * ```js + * const map = new THREE.TextureLoader().load( 'sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map } ); + * + * const sprite = new THREE.Sprite( material ); + * scene.add( sprite ); + * ``` + * + * @augments Object3D + */ class Sprite extends Object3D { + /** + * Constructs a new sprite. + * + * @param {(SpriteMaterial|SpriteNodeMaterial)} [material] - The sprite material. + */ constructor( material = new SpriteMaterial() ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSprite = true; this.type = 'Sprite'; @@ -41634,10 +59246,10 @@ class Sprite extends Object3D { _geometry = new BufferGeometry(); const float32Array = new Float32Array( [ - - 0.5, - 0.5, 0, 0, 0, - 0.5, - 0.5, 0, 1, 0, + -0.5, -0.5, 0, 0, 0, + 0.5, -0.5, 0, 1, 0, 0.5, 0.5, 0, 1, 1, - - 0.5, 0.5, 0, 0, 1 + -0.5, 0.5, 0, 0, 1 ] ); const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); @@ -41648,13 +59260,47 @@ class Sprite extends Object3D { } + /** + * The sprite geometry. + * + * @type {BufferGeometry} + */ this.geometry = _geometry; + + /** + * The sprite material. + * + * @type {(SpriteMaterial|SpriteNodeMaterial)} + */ this.material = material; + /** + * The sprite's anchor point, and the point around which the sprite rotates. + * A value of `(0.5, 0.5)` corresponds to the midpoint of the sprite. A value + * of `(0, 0)` corresponds to the lower left corner of the sprite. + * + * @type {Vector2} + * @default (0.5,0.5) + */ this.center = new Vector2( 0.5, 0.5 ); + /** + * The number of instances of this sprite. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + } + /** + * Computes intersection points between a casted ray and this sprite. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { if ( raycaster.camera === null ) { @@ -41688,8 +59334,8 @@ class Sprite extends Object3D { const center = this.center; - transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vA.set( -0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vB.set( 0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); _uvA.set( 0, 0 ); @@ -41702,7 +59348,7 @@ class Sprite extends Object3D { if ( intersect === null ) { // check second triangle - transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vB.set( -0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); _uvB.set( 0, 1 ); intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); @@ -41774,26 +59420,82 @@ function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) const _v1 = /*@__PURE__*/ new Vector3(); const _v2 = /*@__PURE__*/ new Vector3(); +/** + * A component for providing a basic Level of Detail (LOD) mechanism. + * + * Every LOD level is associated with an object, and rendering can be switched + * between them at the distances specified. Typically you would create, say, + * three meshes, one for far away (low detail), one for mid range (medium + * detail) and one for close up (high detail). + * + * ```js + * const lod = new THREE.LOD(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * + * //Create spheres with 3 levels of detail and create new LOD levels for them + * for( let i = 0; i < 3; i++ ) { + * + * const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); + * const mesh = new THREE.Mesh( geometry, material ); + * lod.addLevel( mesh, i * 75 ); + * + * } + * + * scene.add( lod ); + * ``` + * + * @augments Object3D + */ class LOD extends Object3D { + /** + * Constructs a new LOD. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLOD = true; + + /** + * The current LOD index. + * + * @private + * @type {number} + * @default 0 + */ this._currentLevel = 0; this.type = 'LOD'; Object.defineProperties( this, { + /** + * This array holds the LOD levels. + * + * @name LOD#levels + * @type {Array<{object:Object3D,distance:number,hysteresis:number}>} + */ levels: { enumerable: true, value: [] - }, - isLOD: { - value: true, } } ); + /** + * Whether the LOD object is updated automatically by the renderer per frame + * or not. If set to `false`, you have to call {@link LOD#update} in the + * render loop by yourself. + * + * @type {boolean} + * @default true + */ this.autoUpdate = true; } @@ -41818,6 +59520,15 @@ class LOD extends Object3D { } + /** + * Adds a mesh that will display at a certain distance and greater. Typically + * the further away the distance, the lower the detail on the mesh. + * + * @param {Object3D} object - The 3D object to display at this level. + * @param {number} [distance=0] - The distance at which to display this level of detail. + * @param {number} [hysteresis=0] - Threshold used to avoid flickering at LOD boundaries, as a fraction of distance. + * @return {LOD} A reference to this instance. + */ addLevel( object, distance = 0, hysteresis = 0 ) { distance = Math.abs( distance ); @@ -41844,14 +59555,52 @@ class LOD extends Object3D { } - getCurrentLevel() { + /** + * Removes an existing level, based on the distance from the camera. + * Returns `true` when the level has been removed. Otherwise `false`. + * + * @param {number} distance - Distance of the level to remove. + * @return {boolean} Whether the level has been removed or not. + */ + removeLevel( distance ) { - return this._currentLevel; + const levels = this.levels; + + for ( let i = 0; i < levels.length; i ++ ) { + + if ( levels[ i ].distance === distance ) { + + const removedElements = levels.splice( i, 1 ); + this.remove( removedElements[ 0 ].object ); + + return true; + + } + + } + + return false; } + /** + * Returns the currently active LOD level index. + * + * @return {number} The current active LOD level index. + */ + getCurrentLevel() { + return this._currentLevel; + } + + /** + * Returns a reference to the first 3D object that is greater than + * the given distance. + * + * @param {number} distance - The LOD distance. + * @return {?Object3D} The found 3D object. `null` if no 3D object has been found. + */ getObjectForDistance( distance ) { const levels = this.levels; @@ -41886,6 +59635,12 @@ class LOD extends Object3D { } + /** + * Computes intersection points between a casted ray and this LOD. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array<Object>} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { const levels = this.levels; @@ -41902,6 +59657,12 @@ class LOD extends Object3D { } + /** + * Updates the LOD by computing which LOD level should be visible according + * to the current distance of the given camera. + * + * @param {Camera} camera - The camera the scene is rendered with. + */ update( camera ) { const levels = this.levels; @@ -41993,25 +59754,90 @@ const _sphere = /*@__PURE__*/ new Sphere(); const _inverseMatrix = /*@__PURE__*/ new Matrix4(); const _ray = /*@__PURE__*/ new Ray(); +/** + * A mesh that has a {@link Skeleton} that can then be used to animate the + * vertices of the geometry with skinning/skeleton animation. + * + * Next to a valid skeleton, the skinned mesh requires skin indices and weights + * as buffer attributes in its geometry. These attribute define which bones affect a single + * vertex to a certain extend. + * + * Typically skinned meshes are not created manually but loaders like {@link GLTFLoader} + * or {@link FBXLoader } import respective models. + * + * @augments Mesh + */ class SkinnedMesh extends Mesh { + /** + * Constructs a new skinned mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array<Material>} [material] - The mesh material. + */ constructor( geometry, material ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSkinnedMesh = true; this.type = 'SkinnedMesh'; + /** + * `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. + * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton + * across multiple skinned meshes. + * + * @type {(AttachedBindMode|DetachedBindMode)} + * @default AttachedBindMode + */ this.bindMode = AttachedBindMode; + + /** + * The base matrix that is used for the bound bone transforms. + * + * @type {Matrix4} + */ this.bindMatrix = new Matrix4(); + + /** + * The base matrix that is used for resetting the bound bone transforms. + * + * @type {Matrix4} + */ this.bindMatrixInverse = new Matrix4(); + /** + * The bounding box of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ this.boundingBox = null; + + /** + * The bounding sphere of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ this.boundingSphere = null; } + /** + * Computes the bounding box of the skinned mesh, and updates {@link SkinnedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * If the skinned mesh is animated, the bounding box should be recomputed per frame in order to reflect + * the current animation state. + */ computeBoundingBox() { const geometry = this.geometry; @@ -42035,6 +59861,12 @@ class SkinnedMesh extends Mesh { } + /** + * Computes the bounding sphere of the skinned mesh, and updates {@link SkinnedMesh#boundingSphere}. + * The bounding sphere is automatically computed by the engine once when it is needed, e.g., for ray casting + * and view frustum culling. If the skinned mesh is animated, the bounding sphere should be recomputed + * per frame in order to reflect the current animation state. + */ computeBoundingSphere() { const geometry = this.geometry; @@ -42120,6 +59952,13 @@ class SkinnedMesh extends Mesh { } + /** + * Binds the given skeleton to the skinned mesh. + * + * @param {Skeleton} skeleton - The skeleton to bind. + * @param {Matrix4} [bindMatrix] - The bind matrix. If no bind matrix is provided, + * the skinned mesh's world matrix will be used instead. + */ bind( skeleton, bindMatrix ) { this.skeleton = skeleton; @@ -42139,12 +59978,19 @@ class SkinnedMesh extends Mesh { } + /** + * This method sets the skinned mesh in the rest pose). + */ pose() { this.skeleton.pose(); } + /** + * Normalizes the skin weights which are defined as a buffer attribute + * in the skinned mesh's geometry. + */ normalizeSkinWeights() { const vector = new Vector4(); @@ -42193,7 +60039,16 @@ class SkinnedMesh extends Mesh { } - applyBoneTransform( index, vector ) { + /** + * Applies the bone transform associated with the given index to the given + * vertex position. Returns the updated vector. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * the skinned mesh's world matrix will be used instead. + * @return {Vector3} The updated vertex position. + */ + applyBoneTransform( index, target ) { const skeleton = this.skeleton; const geometry = this.geometry; @@ -42201,9 +60056,9 @@ class SkinnedMesh extends Mesh { _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); - _basePosition.copy( vector ).applyMatrix4( this.bindMatrix ); + _basePosition.copy( target ).applyMatrix4( this.bindMatrix ); - vector.set( 0, 0, 0 ); + target.set( 0, 0, 0 ); for ( let i = 0; i < 4; i ++ ) { @@ -42215,24 +60070,48 @@ class SkinnedMesh extends Mesh { _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); - vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); + target.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); } } - return vector.applyMatrix4( this.bindMatrixInverse ); + return target.applyMatrix4( this.bindMatrixInverse ); } } +/** + * A bone which is part of a {@link Skeleton}. The skeleton in turn is used by + * the {@link SkinnedMesh}. + * + * ```js + * const root = new THREE.Bone(); + * const child = new THREE.Bone(); + * + * root.add( child ); + * child.position.y = 5; + * ``` + * + * @augments Object3D + */ class Bone extends Object3D { + /** + * Constructs a new bone. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBone = true; this.type = 'Bone'; @@ -42244,22 +60123,83 @@ class Bone extends Object3D { const _offsetMatrix = /*@__PURE__*/ new Matrix4(); const _identityMatrix = /*@__PURE__*/ new Matrix4(); +/** + * Class for representing the armatures in `three.js`. The skeleton + * is defined by a hierarchy of bones. + * + * ```js + * const bones = []; + * + * const shoulder = new THREE.Bone(); + * const elbow = new THREE.Bone(); + * const hand = new THREE.Bone(); + * + * shoulder.add( elbow ); + * elbow.add( hand ); + * + * bones.push( shoulder , elbow, hand); + * + * shoulder.position.y = -5; + * elbow.position.y = 0; + * hand.position.y = 5; + * + * const armSkeleton = new THREE.Skeleton( bones ); + * ``` + */ class Skeleton { + /** + * Constructs a new skeleton. + * + * @param {Array<Bone>} [bones] - An array of bones. + * @param {Array<Matrix4>} [boneInverses] - An array of bone inverse matrices. + * If not provided, these matrices will be computed automatically via {@link Skeleton#calculateInverses}. + */ constructor( bones = [], boneInverses = [] ) { this.uuid = generateUUID(); + /** + * An array of bones defining the skeleton. + * + * @type {Array<Bone>} + */ this.bones = bones.slice( 0 ); + + /** + * An array of bone inverse matrices. + * + * @type {Array<Matrix4>} + */ this.boneInverses = boneInverses; + + /** + * An array buffer holding the bone data. + * Input data for {@link Skeleton#boneTexture}. + * + * @type {?Float32Array} + * @default null + */ this.boneMatrices = null; + /** + * A texture holding the bone data for use + * in the vertex shader. + * + * @type {?DataTexture} + * @default null + */ this.boneTexture = null; this.init(); } + /** + * Initializes the skeleton. This method gets automatically called by the constructor + * but depending on how the skeleton is created it might be necessary to call this method + * manually. + */ init() { const bones = this.bones; @@ -42295,6 +60235,10 @@ class Skeleton { } + /** + * Computes the bone inverse matrices. This method resets {@link Skeleton#boneInverses} + * and fills it with new matrices. + */ calculateInverses() { this.boneInverses.length = 0; @@ -42315,6 +60259,9 @@ class Skeleton { } + /** + * Resets the skeleton to the base pose. + */ pose() { // recover the bind-time world matrices @@ -42358,6 +60305,9 @@ class Skeleton { } + /** + * Resets the skeleton to the base pose. + */ update() { const bones = this.bones; @@ -42386,12 +60336,22 @@ class Skeleton { } + /** + * Returns a new skeleton with copied values from this instance. + * + * @return {Skeleton} A clone of this instance. + */ clone() { return new Skeleton( this.bones, this.boneInverses ); } + /** + * Computes a data texture for passing bone data to the vertex shader. + * + * @return {Skeleton} A reference of this instance. + */ computeBoneTexture() { // layout (1 matrix = 4 pixels) @@ -42418,6 +60378,13 @@ class Skeleton { } + /** + * Searches through the skeleton's bone array and returns the first with a + * matching name. + * + * @param {string} name - The name of the bone. + * @return {Bone|undefined} The found bone. `undefined` if no bone has been found. + */ getBoneByName( name ) { for ( let i = 0, il = this.bones.length; i < il; i ++ ) { @@ -42436,6 +60403,10 @@ class Skeleton { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose( ) { if ( this.boneTexture !== null ) { @@ -42448,6 +60419,13 @@ class Skeleton { } + /** + * Setups the skeleton by the given JSON and bones. + * + * @param {Object} json - The skeleton as serialized JSON. + * @param {Object<string, Bone>} bones - An array of bones. + * @return {Skeleton} A reference of this instance. + */ fromJSON( json, bones ) { this.uuid = json.uuid; @@ -42475,11 +60453,17 @@ class Skeleton { } + /** + * Serializes the skeleton into JSON. + * + * @return {Object} A JSON object representing the serialized skeleton. + * @see {@link ObjectLoader#parse} + */ toJSON() { const data = { metadata: { - version: 4.6, + version: 4.7, type: 'Skeleton', generator: 'Skeleton.toJSON' }, @@ -42508,25 +60492,76 @@ class Skeleton { } +/** + * This class can be used to define an exponential squared fog, + * which gives a clear view near the camera and a faster than exponentially + * densening fog farther from the camera. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + * ``` + */ class FogExp2 { + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [density=0.00025] - Defines how fast the fog will grow dense. + */ constructor( color, density = 0.00025 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isFogExp2 = true; + /** + * The name of the fog. + * + * @type {string} + */ this.name = ''; + /** + * The fog's color. + * + * @type {Color} + */ this.color = new Color( color ); + + /** + * Defines how fast the fog will grow dense. + * + * @type {number} + * @default 0.00025 + */ this.density = density; } + /** + * Returns a new fog with copied values from this instance. + * + * @return {FogExp2} A clone of this instance. + */ clone() { return new FogExp2( this.color, this.density ); } + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ toJSON( /* meta */ ) { return { @@ -42540,21 +60575,57 @@ class FogExp2 { } +/** + * Abstract base class for lights - all other light types inherit the + * properties and methods described here. + * + * @abstract + * @augments Object3D + */ class Light extends Object3D { + /** + * Constructs a new light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ constructor( color, intensity = 1 ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLight = true; this.type = 'Light'; + /** + * The light's color. + * + * @type {Color} + */ this.color = new Color( color ); + + /** + * The light's intensity. + * + * @type {number} + * @default 1 + */ this.intensity = intensity; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { // Empty here in base class; some subclasses override. @@ -42587,6 +60658,7 @@ class Light extends Object3D { if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + if ( this.target !== undefined ) data.object.target = this.target.uuid; return data; @@ -42594,12 +60666,39 @@ class Light extends Object3D { } +/** + * A light source positioned directly above the scene, with color fading from + * the sky color to the ground color. + * + * This light cannot be used to cast shadows. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ class HemisphereLight extends Light { + /** + * Constructs a new hemisphere light. + * + * @param {(number|Color|string)} [skyColor=0xffffff] - The light's sky color. + * @param {(number|Color|string)} [groundColor=0xffffff] - The light's ground color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ constructor( skyColor, groundColor, intensity ) { super( skyColor, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isHemisphereLight = true; this.type = 'HemisphereLight'; @@ -42607,6 +60706,11 @@ class HemisphereLight extends Light { this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); + /** + * The light's ground color. + * + * @type {Color} + */ this.groundColor = new Color( groundColor ); } @@ -42627,24 +60731,145 @@ const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); const _lookTarget$1 = /*@__PURE__*/ new Vector3(); +/** + * Abstract base class for light shadow classes. These classes + * represent the shadow configuration for different light types. + * + * @abstract + */ class LightShadow { + /** + * Constructs a new light shadow. + * + * @param {Camera} camera - The light's view of the world. + */ constructor( camera ) { + /** + * The light's view of the world. + * + * @type {Camera} + */ this.camera = camera; + /** + * The intensity of the shadow. The default is `1`. + * Valid values are in the range `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.intensity = 1; + + /** + * Shadow map bias, how much to add or subtract from the normalized depth + * when deciding whether a surface is in shadow. + * + * The default is `0`. Very tiny adjustments here (in the order of `0.0001`) + * may help reduce artifacts in shadows. + * + * @type {number} + * @default 0 + */ this.bias = 0; + + /** + * Defines how much the position used to query the shadow map is offset along + * the object normal. The default is `0`. Increasing this value can be used to + * reduce shadow acne especially in large scenes where light shines onto + * geometry at a shallow angle. The cost is that shadows may appear distorted. + * + * @type {number} + * @default 0 + */ this.normalBias = 0; + + /** + * Setting this to values greater than 1 will blur the edges of the shadow. + * High values will cause unwanted banding effects in the shadows - a greater + * map size will allow for a higher value to be used here before these effects + * become visible. + * + * The property has no effect when the shadow map type is `PCFSoftShadowMap` and + * and it is recommended to increase softness by decreasing the shadow map size instead. + * + * The property has no effect when the shadow map type is `BasicShadowMap`. + * + * @type {number} + * @default 1 + */ this.radius = 1; + + /** + * The amount of samples to use when blurring a VSM shadow map. + * + * @type {number} + * @default 8 + */ this.blurSamples = 8; + /** + * Defines the width and height of the shadow map. Higher values give better quality + * shadows at the cost of computation time. Values must be powers of two. + * + * @type {Vector2} + * @default (512,512) + */ this.mapSize = new Vector2( 512, 512 ); + /** + * The type of shadow texture. The default is `UnsignedByteType`. + * + * @type {number} + * @default UnsignedByteType + */ + this.mapType = UnsignedByteType; + + /** + * The depth map generated using the internal camera; a location beyond a + * pixel's depth is in shadow. Computed internally during rendering. + * + * @type {?RenderTarget} + * @default null + */ this.map = null; + + /** + * The distribution map generated using the internal camera; an occlusion is + * calculated based on the distribution of depths. Computed internally during + * rendering. + * + * @type {?RenderTarget} + * @default null + */ this.mapPass = null; + + /** + * Model to shadow camera space, to compute location and depth in shadow map. + * This is computed internally during rendering. + * + * @type {Matrix4} + */ this.matrix = new Matrix4(); + /** + * Enables automatic updates of the light's shadow. If you do not require dynamic + * lighting / shadows, you may set this to `false`. + * + * @type {boolean} + * @default true + */ this.autoUpdate = true; + + /** + * When set to `true`, shadow maps will be updated in the next `render` call. + * If you have set {@link LightShadow#autoUpdate} to `false`, you will need to + * set this property to `true` and then make a render call to update the light's shadow. + * + * @type {boolean} + * @default false + */ this.needsUpdate = false; this._frustum = new Frustum(); @@ -42660,18 +60885,34 @@ class LightShadow { } + /** + * Used internally by the renderer to get the number of viewports that need + * to be rendered for this shadow. + * + * @return {number} The viewport count. + */ getViewportCount() { return this._viewportCount; } + /** + * Gets the shadow cameras frustum. Used internally by the renderer to cull objects. + * + * @return {Frustum} The shadow camera frustum. + */ getFrustum() { return this._frustum; } + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + */ updateMatrices( light ) { const shadowCamera = this.camera; @@ -42685,31 +60926,59 @@ class LightShadow { shadowCamera.updateMatrixWorld(); _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix$1, shadowCamera.coordinateSystem, shadowCamera.reversedDepth ); - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + if ( shadowCamera.reversedDepth ) { + + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + + } else { + + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + + } shadowMatrix.multiply( _projScreenMatrix$1 ); } + /** + * Returns a viewport definition for the given viewport index. + * + * @param {number} viewportIndex - The viewport index. + * @return {Vector4} The viewport. + */ getViewport( viewportIndex ) { return this._viewports[ viewportIndex ]; } + /** + * Returns the frame extends. + * + * @return {Vector2} The frame extends. + */ getFrameExtents() { return this._frameExtents; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { if ( this.map ) { @@ -42726,29 +60995,54 @@ class LightShadow { } + /** + * Copies the values of the given light shadow instance to this instance. + * + * @param {LightShadow} source - The light shadow to copy. + * @return {LightShadow} A reference to this light shadow instance. + */ copy( source ) { this.camera = source.camera.clone(); + this.intensity = source.intensity; + this.bias = source.bias; this.radius = source.radius; + this.autoUpdate = source.autoUpdate; + this.needsUpdate = source.needsUpdate; + this.normalBias = source.normalBias; + this.blurSamples = source.blurSamples; + this.mapSize.copy( source.mapSize ); return this; } + /** + * Returns a new light shadow instance with copied values from this instance. + * + * @return {LightShadow} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Serializes the light shadow into JSON. + * + * @return {Object} A JSON object representing the serialized light shadow. + * @see {@link ObjectLoader#parse} + */ toJSON() { const object = {}; + if ( this.intensity !== 1 ) object.intensity = this.intensity; if ( this.bias !== 0 ) object.bias = this.bias; if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; if ( this.radius !== 1 ) object.radius = this.radius; @@ -42763,16 +61057,46 @@ class LightShadow { } +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ class SpotLightShadow extends LightShadow { + /** + * Constructs a new spot light shadow. + */ constructor() { super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSpotLightShadow = true; + /** + * Used to focus the shadow camera. The camera's field of view is set as a + * percentage of the spotlight's field-of-view. Range is `[0, 1]`. + * + * @type {number} + * @default 1 + */ this.focus = 1; + /** + * Texture aspect ratio. + * + * @type {number} + * @default 1 + */ + this.aspect = 1; + } updateMatrices( light ) { @@ -42780,7 +61104,7 @@ class SpotLightShadow extends LightShadow { const camera = this.camera; const fov = RAD2DEG * 2 * light.angle * this.focus; - const aspect = this.mapSize.width / this.mapSize.height; + const aspect = ( this.mapSize.width / this.mapSize.height ) * this.aspect; const far = light.distance || camera.far; if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { @@ -42808,12 +61132,51 @@ class SpotLightShadow extends LightShadow { } +/** + * This light gets emitted from a single point in one direction, along a cone + * that increases in size the further from the light it gets. + * + * This light can cast shadows - see the {@link SpotLightShadow} for details. + * + * ```js + * // white spotlight shining from the side, modulated by a texture + * const spotLight = new THREE.SpotLight( 0xffffff ); + * spotLight.position.set( 100, 1000, 100 ); + * spotLight.map = new THREE.TextureLoader().load( url ); + * + * spotLight.castShadow = true; + * spotLight.shadow.mapSize.width = 1024; + * spotLight.shadow.mapSize.height = 1024; + * spotLight.shadow.camera.near = 500; + * spotLight.shadow.camera.far = 4000; + * spotLight.shadow.camera.fov = 30;s + * ``` + * + * @augments Light + */ class SpotLight extends Light { + /** + * Constructs a new spot light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { super( color, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSpotLight = true; this.type = 'SpotLight'; @@ -42821,19 +61184,82 @@ class SpotLight extends Light { this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); + /** + * The spot light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ this.target = new Object3D(); + /** + * Maximum range of the light. `0` means no limit. + * + * @type {number} + * @default 0 + */ this.distance = distance; + + /** + * Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * + * @type {number} + * @default Math.PI/3 + */ this.angle = angle; + + /** + * Percent of the spotlight cone that is attenuated due to penumbra. + * Value range is `[0,1]`. + * + * @type {number} + * @default 0 + */ this.penumbra = penumbra; + + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ this.decay = decay; + /** + * A texture used to modulate the color of the light. The spot light + * color is mixed with the RGB value of this texture, with a ratio + * corresponding to its alpha value. The cookie-like masking effect is + * reproduced using pixel values (0, 0, 0, 1-cookie_value). + * + * *Warning*: This property is disabled if {@link Object3D#castShadow} is set to `false`. + * + * @type {?Texture} + * @default null + */ this.map = null; + /** + * This property holds the light's shadow configuration. + * + * @type {SpotLightShadow} + */ this.shadow = new SpotLightShadow(); } + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ get power() { // compute the light's luminous power (in lumens) from its intensity (in candela) @@ -42878,12 +61304,27 @@ const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); const _lightPositionWorld = /*@__PURE__*/ new Vector3(); const _lookTarget = /*@__PURE__*/ new Vector3(); +/** + * Represents the shadow configuration of point lights. + * + * @augments LightShadow + */ class PointLightShadow extends LightShadow { + /** + * Constructs a new point light shadow. + */ constructor() { super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPointLightShadow = true; this._frameExtents = new Vector2( 4, 2 ); @@ -42919,17 +61360,23 @@ class PointLightShadow extends LightShadow { ]; this._cubeDirections = [ - new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), - new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) + new Vector3( 1, 0, 0 ), new Vector3( -1, 0, 0 ), new Vector3( 0, 0, 1 ), + new Vector3( 0, 0, -1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, -1, 0 ) ]; this._cubeUps = [ new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), - new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) + new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, -1 ) ]; } + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + * @param {number} [viewportIndex=0] - The viewport index. + */ updateMatrices( light, viewportIndex = 0 ) { const camera = this.camera; @@ -42956,29 +61403,88 @@ class PointLightShadow extends LightShadow { shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); } } +/** + * A light that gets emitted from a single point in all directions. A common + * use case for this is to replicate the light emitted from a bare + * lightbulb. + * + * This light can cast shadows - see the {@link PointLightShadow} for details. + * + * ```js + * const light = new THREE.PointLight( 0xff0000, 1, 100 ); + * light.position.set( 50, 50, 50 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ class PointLight extends Light { + /** + * Constructs a new point light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ constructor( color, intensity, distance = 0, decay = 2 ) { super( color, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPointLight = true; this.type = 'PointLight'; + /** + * When distance is zero, light will attenuate according to inverse-square + * law to infinite distance. When distance is non-zero, light will attenuate + * according to inverse-square law until near the distance cutoff, where it + * will then attenuate quickly and smoothly to 0. Inherently, cutoffs are not + * physically correct. + * + * @type {number} + * @default 0 + */ this.distance = distance; + + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ this.decay = decay; + /** + * This property holds the light's shadow configuration. + * + * @type {PointLightShadow} + */ this.shadow = new PointLightShadow(); } + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ get power() { // compute the light's luminous power (in lumens) from its intensity (in candela) @@ -43015,24 +61521,79 @@ class PointLight extends Light { } +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ class DirectionalLightShadow extends LightShadow { + /** + * Constructs a new directional light shadow. + */ constructor() { - super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); + super( new OrthographicCamera( -5, 5, 5, -5, 0.5, 500 ) ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isDirectionalLightShadow = true; } } +/** + * A light that gets emitted in a specific direction. This light will behave + * as though it is infinitely far away and the rays produced from it are all + * parallel. The common use case for this is to simulate daylight; the sun is + * far enough away that its position can be considered to be infinite, and + * all light rays coming from it are parallel. + * + * A common point of confusion for directional lights is that setting the + * rotation has no effect. This is because three.js's DirectionalLight is the + * equivalent to what is often called a 'Target Direct Light' in other + * applications. + * + * This means that its direction is calculated as pointing from the light's + * {@link Object3D#position} to the {@link DirectionalLight#target} position + * (as opposed to a 'Free Direct Light' that just has a rotation + * component). + * + * This light can cast shadows - see the {@link DirectionalLightShadow} for details. + * + * ```js + * // White directional light at half intensity shining from the top. + * const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + * scene.add( directionalLight ); + * ``` + * + * @augments Light + */ class DirectionalLight extends Light { + /** + * Constructs a new directional light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ constructor( color, intensity ) { super( color, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isDirectionalLight = true; this.type = 'DirectionalLight'; @@ -43040,8 +61601,25 @@ class DirectionalLight extends Light { this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); + /** + * The directional light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ this.target = new Object3D(); + /** + * This property holds the light's shadow configuration. + * + * @type {DirectionalLightShadow} + */ this.shadow = new DirectionalLightShadow(); } @@ -43065,12 +61643,37 @@ class DirectionalLight extends Light { } +/** + * This light globally illuminates all objects in the scene equally. + * + * It cannot be used to cast shadows as it does not have a direction. + * + * ```js + * const light = new THREE.AmbientLight( 0x404040 ); // soft white light + * scene.add( light ); + * ``` + * + * @augments Light + */ class AmbientLight extends Light { + /** + * Constructs a new ambient light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ constructor( color, intensity ) { super( color, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isAmbientLight = true; this.type = 'AmbientLight'; @@ -43079,21 +61682,80 @@ class AmbientLight extends Light { } +/** + * This class emits light uniformly across the face a rectangular plane. + * This light type can be used to simulate light sources such as bright + * windows or strip lighting. + * + * Important Notes: + * + * - There is no shadow support. + * - Only PBR materials are supported. + * - You have to include `RectAreaLightUniformsLib` (`WebGLRenderer`) or `RectAreaLightTexturesLib` (`WebGPURenderer`) + * into your app and init the uniforms/textures. + * + * ```js + * RectAreaLightUniformsLib.init(); // only relevant for WebGLRenderer + * THREE.RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() ); // only relevant for WebGPURenderer + * + * const intensity = 1; const width = 10; const height = 10; + * const rectLight = new THREE.RectAreaLight( 0xffffff, intensity, width, height ); + * rectLight.position.set( 5, 5, 0 ); + * rectLight.lookAt( 0, 0, 0 ); + * scene.add( rectLight ) + * ``` + * + * @augments Light + */ class RectAreaLight extends Light { + /** + * Constructs a new area light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + * @param {number} [width=10] - The width of the light. + * @param {number} [height=10] - The height of the light. + */ constructor( color, intensity, width = 10, height = 10 ) { super( color, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isRectAreaLight = true; this.type = 'RectAreaLight'; + /** + * The width of the light. + * + * @type {number} + * @default 10 + */ this.width = width; + + /** + * The height of the light. + * + * @type {number} + * @default 10 + */ this.height = height; } + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ get power() { // compute the light's luminous power (in lumens) from its intensity (in nits) @@ -43133,21 +61795,33 @@ class RectAreaLight extends Light { } /** - * Primary reference: - * https://graphics.stanford.edu/papers/envmap/envmap.pdf + * Represents a third-order spherical harmonics (SH). Light probes use this class + * to encode lighting information. * - * Secondary reference: - * https://www.ppsloan.org/publications/StupidSH36.pdf + * - Primary reference: {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf} + * - Secondary reference: {@link https://www.ppsloan.org/publications/StupidSH36.pdf} */ - -// 3-band SH defined by 9 coefficients - class SphericalHarmonics3 { + /** + * Constructs a new spherical harmonics. + */ constructor() { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSphericalHarmonics3 = true; + /** + * An array holding the (9) SH coefficients. + * + * @type {Array<Vector3>} + */ this.coefficients = []; for ( let i = 0; i < 9; i ++ ) { @@ -43158,6 +61832,13 @@ class SphericalHarmonics3 { } + /** + * Sets the given SH coefficients to this instance by copying + * the values. + * + * @param {Array<Vector3>} coefficients - The SH coefficients. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ set( coefficients ) { for ( let i = 0; i < 9; i ++ ) { @@ -43170,6 +61851,11 @@ class SphericalHarmonics3 { } + /** + * Sets all SH coefficients to `0`. + * + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ zero() { for ( let i = 0; i < 9; i ++ ) { @@ -43182,8 +61868,13 @@ class SphericalHarmonics3 { } - // get the radiance in the direction of the normal - // target is a Vector3 + /** + * Returns the radiance in the direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The radiance. + */ getAt( normal, target ) { // normal is assumed to be unit length @@ -43211,9 +61902,14 @@ class SphericalHarmonics3 { } - // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal - // target is a Vector3 - // https://graphics.stanford.edu/papers/envmap/envmap.pdf + /** + * Returns the irradiance (radiance convolved with cosine lobe) in the + * direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The irradiance. + */ getIrradianceAt( normal, target ) { // normal is assumed to be unit length @@ -43241,6 +61937,12 @@ class SphericalHarmonics3 { } + /** + * Adds the given SH to this instance. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ add( sh ) { for ( let i = 0; i < 9; i ++ ) { @@ -43253,6 +61955,14 @@ class SphericalHarmonics3 { } + /** + * A convenience method for performing {@link SphericalHarmonics3#add} and + * {@link SphericalHarmonics3#scale} at once. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ addScaledSH( sh, s ) { for ( let i = 0; i < 9; i ++ ) { @@ -43265,6 +61975,12 @@ class SphericalHarmonics3 { } + /** + * Scales this SH by the given scale factor. + * + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ scale( s ) { for ( let i = 0; i < 9; i ++ ) { @@ -43277,6 +61993,14 @@ class SphericalHarmonics3 { } + /** + * Linear interpolates between the given SH and this instance by the given + * alpha factor. + * + * @param {SphericalHarmonics3} sh - The SH to interpolate with. + * @param {number} alpha - The alpha factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ lerp( sh, alpha ) { for ( let i = 0; i < 9; i ++ ) { @@ -43289,6 +62013,12 @@ class SphericalHarmonics3 { } + /** + * Returns `true` if this spherical harmonics is equal with the given one. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to test for equality. + * @return {boolean} Whether this spherical harmonics is equal with the given one. + */ equals( sh ) { for ( let i = 0; i < 9; i ++ ) { @@ -43305,18 +62035,36 @@ class SphericalHarmonics3 { } + /** + * Copies the values of the given spherical harmonics to this instance. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to copy. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ copy( sh ) { return this.set( sh.coefficients ); } + /** + * Returns a new spherical harmonics with copied values from this instance. + * + * @return {SphericalHarmonics3} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Sets the SH coefficients of this instance from the given array. + * + * @param {Array<number>} array - An array holding the SH coefficients. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {SphericalHarmonics3} A clone of this instance. + */ fromArray( array, offset = 0 ) { const coefficients = this.coefficients; @@ -43331,6 +62079,14 @@ class SphericalHarmonics3 { } + /** + * Returns an array with the SH coefficients, or copies them into the provided + * array. The coefficients are represented as numbers. + * + * @param {Array<number>} [array=[]] - The target array. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {Array<number>} An array with flat SH coefficients. + */ toArray( array = [], offset = 0 ) { const coefficients = this.coefficients; @@ -43345,8 +62101,12 @@ class SphericalHarmonics3 { } - // evaluate the basis functions - // shBasis is an Array[ 9 ] + /** + * Computes the SH basis for the given normal vector. + * + * @param {Vector3} normal - The normal. + * @param {Array<number>} shBasis - The target array holding the SH basis. + */ static getBasisAt( normal, shBasis ) { // normal is assumed to be unit length @@ -43372,14 +62132,51 @@ class SphericalHarmonics3 { } +/** + * Light probes are an alternative way of adding light to a 3D scene. Unlike + * classical light sources (e.g. directional, point or spot lights), light + * probes do not emit light. Instead they store information about light + * passing through 3D space. During rendering, the light that hits a 3D + * object is approximated by using the data from the light probe. + * + * Light probes are usually created from (radiance) environment maps. The + * class {@link LightProbeGenerator} can be used to create light probes from + * cube textures or render targets. However, light estimation data could also + * be provided in other forms e.g. by WebXR. This enables the rendering of + * augmented reality content that reacts to real world lighting. + * + * The current probe implementation in three.js supports so-called diffuse + * light probes. This type of light probe is functionally equivalent to an + * irradiance environment map. + * + * @augments Light + */ class LightProbe extends Light { + /** + * Constructs a new light probe. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics which represents encoded lighting information. + * @param {number} [intensity=1] - The light's strength/intensity. + */ constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { super( undefined, intensity ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLightProbe = true; + /** + * A light probe uses spherical harmonics to encode lighting information. + * + * @type {SphericalHarmonics3} + */ this.sh = sh; } @@ -43394,6 +62191,12 @@ class LightProbe extends Light { } + /** + * Deserializes the light prove from the given JSON. + * + * @param {Object} json - The JSON holding the serialized light probe. + * @return {LightProbe} A reference to this light probe. + */ fromJSON( json ) { this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); @@ -43415,11 +62218,16 @@ class LightProbe extends Light { } -// converts an array to a specific type -function convertArray( array, type, forceClone ) { +/** + * Converts an array to a specific type. + * + * @param {TypedArray|Array} array - The array to convert. + * @param {TypedArray.constructor} type - The constructor of a typed array that defines the new type. + * @return {TypedArray} The converted array. + */ +function convertArray( array, type ) { - if ( ! array || // let 'undefined' and 'null' pass - ! forceClone && array.constructor === type ) return array; + if ( ! array || array.constructor === type ) return array; if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { @@ -43431,14 +62239,24 @@ function convertArray( array, type, forceClone ) { } +/** + * Returns `true` if the given object is a typed array. + * + * @param {any} object - The object to check. + * @return {boolean} Whether the given object is a typed array. + */ function isTypedArray( object ) { - return ArrayBuffer.isView( object ) && - ! ( object instanceof DataView ); + return ArrayBuffer.isView( object ) && ! ( object instanceof DataView ); } -// returns an array by which times and values can be sorted +/** + * Returns an array by which times and values can be sorted. + * + * @param {Array<number>} times - The keyframe time values. + * @return {Array<number>} The array. + */ function getKeyframeOrder( times ) { function compareTime( i, j ) { @@ -43457,7 +62275,14 @@ function getKeyframeOrder( times ) { } -// uses the array previously returned by 'getKeyframeOrder' to sort data +/** + * Sorts the given array by the previously computed order via `getKeyframeOrder()`. + * + * @param {Array<number>} values - The values to sort. + * @param {number} stride - The stride. + * @param {Array<number>} order - The sort order. + * @return {Array<number>} The sorted values. + */ function sortedArray( values, stride, order ) { const nValues = values.length; @@ -43479,7 +62304,14 @@ function sortedArray( values, stride, order ) { } -// function for parsing AOS keyframe formats +/** + * Used for parsing AOS keyframe formats. + * + * @param {Array<number>} jsonKeys - A list of JSON keyframes. + * @param {Array<number>} times - This array will be filled with keyframe times by this function. + * @param {Array<number>} values - This array will be filled with keyframe values by this function. + * @param {string} valuePropertyName - The name of the property to use. + */ function flattenJSON( jsonKeys, times, values, valuePropertyName ) { let i = 1, key = jsonKeys[ 0 ]; @@ -43504,7 +62336,7 @@ function flattenJSON( jsonKeys, times, values, valuePropertyName ) { if ( value !== undefined ) { times.push( key.time ); - values.push.apply( values, value ); // push all elements + values.push( ...value ); // push all elements } @@ -43569,29 +62401,82 @@ function flattenJSON( jsonKeys, times, values, valuePropertyName ) { * Time complexity is O(1) for linear access crossing at most two points * and O(log N) for random access, where N is the number of positions. * - * References: - * - * http://www.oodesign.com/template-method-pattern.html + * References: {@link http://www.oodesign.com/template-method-pattern.html} * + * @abstract */ - class Interpolant { + /** + * Constructs a new interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + /** + * The parameter positions. + * + * @type {TypedArray} + */ this.parameterPositions = parameterPositions; + + /** + * A cache index. + * + * @private + * @type {number} + * @default 0 + */ this._cachedIndex = 0; - this.resultBuffer = resultBuffer !== undefined ? - resultBuffer : new sampleValues.constructor( sampleSize ); + /** + * The result buffer. + * + * @type {TypedArray} + */ + this.resultBuffer = resultBuffer !== undefined ? resultBuffer : new sampleValues.constructor( sampleSize ); + + /** + * The sample values. + * + * @type {TypedArray} + */ this.sampleValues = sampleValues; + + /** + * The value size. + * + * @type {TypedArray} + */ this.valueSize = sampleSize; + /** + * The interpolation settings. + * + * @type {?Object} + * @default null + */ this.settings = null; + + /** + * The default settings object. + * + * @type {Object} + */ this.DefaultSettings_ = {}; } + /** + * Evaluate the interpolant at position `t`. + * + * @param {number} t - The interpolation factor. + * @return {TypedArray} The result buffer. + */ evaluate( t ) { const pp = this.parameterPositions; @@ -43752,12 +62637,23 @@ class Interpolant { } + /** + * Returns the interpolation settings. + * + * @return {Object} The interpolation settings. + */ getSettings_() { return this.settings || this.DefaultSettings_; } + /** + * Copies a sample value to the result buffer. + * + * @param {number} index - An index into the sample value buffer. + * @return {TypedArray} The result buffer. + */ copySampleValue_( index ) { // copies a sample value to the result buffer @@ -43777,8 +62673,16 @@ class Interpolant { } - // Template methods for derived classes: - + /** + * Copies a sample value to the result buffer. + * + * @abstract + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + * @param {number} t1 - The next interpolation factor. + * @return {TypedArray} The result buffer. + */ interpolate_( /* i1, t0, t, t1 */ ) { throw new Error( 'call to abstract method' ); @@ -43786,6 +62690,13 @@ class Interpolant { } + /** + * Optional method that is executed when the interval has changed. + * + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + */ intervalChanged_( /* i1, t0, t1 */ ) { // empty @@ -43800,18 +62711,27 @@ class Interpolant { * It was derived from a Hermitian construction setting the first derivative * at each sample position to the linear slope between neighboring positions * over their parameter interval. + * + * @augments Interpolant */ - class CubicInterpolant extends Interpolant { + /** + * Constructs a new cubic interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - this._weightPrev = - 0; - this._offsetPrev = - 0; - this._weightNext = - 0; - this._offsetNext = - 0; + this._weightPrev = -0; + this._offsetPrev = -0; + this._weightNext = -0; + this._offsetNext = -0; this.DefaultSettings_ = { @@ -43918,8 +62838,8 @@ class CubicInterpolant extends Interpolant { // evaluate polynomials const sP = - wP * ppp + 2 * wP * pp - wP * p; - const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; - const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; + const s0 = ( 1 + wP ) * ppp + ( -1.5 - 2 * wP ) * pp + ( -0.5 + wP ) * p + 1; + const s1 = ( -1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; const sN = wN * ppp - wN * pp; // combine data linearly @@ -43940,8 +62860,21 @@ class CubicInterpolant extends Interpolant { } +/** + * A basic linear interpolant. + * + * @augments Interpolant + */ class LinearInterpolant extends Interpolant { + /** + * Constructs a new linear interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); @@ -43975,13 +62908,21 @@ class LinearInterpolant extends Interpolant { } /** - * * Interpolant that evaluates to the sample value at the position preceding * the parameter. + * + * @augments Interpolant */ - class DiscreteInterpolant extends Interpolant { + /** + * Constructs a new discrete interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); @@ -43996,25 +62937,60 @@ class DiscreteInterpolant extends Interpolant { } +/** + * Represents s a timed sequence of keyframes, which are composed of lists of + * times and related values, and which are used to animate a specific property + * of an object. + */ class KeyframeTrack { + /** + * Constructs a new keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<number|string|boolean>} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ constructor( name, times, values, interpolation ) { if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); + /** + * The track's name can refer to morph targets or bones or + * possibly other values within an animated object. See {@link PropertyBinding#parseTrackName} + * for the forms of strings that can be parsed for property binding. + * + * @type {string} + */ this.name = name; + /** + * The keyframe times. + * + * @type {Float32Array} + */ this.times = convertArray( times, this.TimeBufferType ); + + /** + * The keyframe values. + * + * @type {Float32Array} + */ this.values = convertArray( values, this.ValueBufferType ); this.setInterpolation( interpolation || this.DefaultInterpolation ); } - // Serialization (in static context, because of constructor invocation - // and automatic invocation of .toJSON): - + /** + * Converts the keyframe track to JSON. + * + * @static + * @param {KeyframeTrack} track - The keyframe track to serialize. + * @return {Object} The serialized keyframe track as JSON. + */ static toJSON( track ) { const trackType = track.constructor; @@ -44053,24 +63029,51 @@ class KeyframeTrack { } + /** + * Factory method for creating a new discrete interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {DiscreteInterpolant} The new interpolant. + */ InterpolantFactoryMethodDiscrete( result ) { return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); } + /** + * Factory method for creating a new linear interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {LinearInterpolant} The new interpolant. + */ InterpolantFactoryMethodLinear( result ) { return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); } + /** + * Factory method for creating a new smooth interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {CubicInterpolant} The new interpolant. + */ InterpolantFactoryMethodSmooth( result ) { return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); } + /** + * Defines the interpolation factor method for this keyframe track. + * + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type. + * @return {KeyframeTrack} A reference to this keyframe track. + */ setInterpolation( interpolation ) { let factoryMethod; @@ -44128,6 +63131,11 @@ class KeyframeTrack { } + /** + * Returns the current interpolation type. + * + * @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type. + */ getInterpolation() { switch ( this.createInterpolant ) { @@ -44148,13 +63156,23 @@ class KeyframeTrack { } + /** + * Returns the value size. + * + * @return {number} The value size. + */ getValueSize() { return this.values.length / this.times.length; } - // move all keyframes either forwards or backwards in time + /** + * Moves all keyframes either forward or backward in time. + * + * @param {number} timeOffset - The offset to move the time values. + * @return {KeyframeTrack} A reference to this keyframe track. + */ shift( timeOffset ) { if ( timeOffset !== 0.0 ) { @@ -44173,7 +63191,12 @@ class KeyframeTrack { } - // scale all keyframe times by a factor (useful for frame <-> seconds conversions) + /** + * Scale all keyframe times by a factor (useful for frame - seconds conversions). + * + * @param {number} timeScale - The time scale. + * @return {KeyframeTrack} A reference to this keyframe track. + */ scale( timeScale ) { if ( timeScale !== 1.0 ) { @@ -44192,8 +63215,16 @@ class KeyframeTrack { } - // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. - // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values + /** + * Removes keyframes before and after animation without changing any values within the defined time range. + * + * Note: The method does not shift around keys to the start of the track time, because for interpolated + * keys this will change their values + * + * @param {number} startTime - The start time. + * @param {number} endTime - The end time. + * @return {KeyframeTrack} A reference to this keyframe track. + */ trim( startTime, endTime ) { const times = this.times, @@ -44208,7 +63239,7 @@ class KeyframeTrack { } - while ( to !== - 1 && times[ to ] > endTime ) { + while ( to !== -1 && times[ to ] > endTime ) { -- to; @@ -44236,7 +63267,12 @@ class KeyframeTrack { } - // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable + /** + * Performs minimal validation on the keyframe track. Returns `true` if the values + * are valid. + * + * @return {boolean} Whether the keyframes are valid or not. + */ validate() { let valid = true; @@ -44313,10 +63349,16 @@ class KeyframeTrack { } - // removes equivalent sequential keys as common in morph target sequences - // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) + /** + * Optimizes this keyframe track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ optimize() { + // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) + // times or values may be shared with other tracks, so overwriting is unsafe const times = this.times.slice(), values = this.values.slice(), @@ -44426,6 +63468,11 @@ class KeyframeTrack { } + /** + * Returns a new keyframe track with copied values from this instance. + * + * @return {KeyframeTrack} A clone of this instance. + */ clone() { const times = this.times.slice(); @@ -44443,41 +63490,166 @@ class KeyframeTrack { } +/** + * The value type name. + * + * @type {String} + * @default '' + */ +KeyframeTrack.prototype.ValueTypeName = ''; + +/** + * The time buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ KeyframeTrack.prototype.TimeBufferType = Float32Array; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ KeyframeTrack.prototype.ValueBufferType = Float32Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateLinear + */ KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; /** - * A Track of Boolean keyframe values. + * A track for boolean keyframe values. + * + * @augments KeyframeTrack */ -class BooleanKeyframeTrack extends KeyframeTrack {} +class BooleanKeyframeTrack extends KeyframeTrack { + /** + * Constructs a new boolean keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<boolean>} values - A list of keyframe values. + */ + constructor( name, times, values ) { + + super( name, times, values ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'bool' + */ BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ BooleanKeyframeTrack.prototype.ValueBufferType = Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** - * A Track of keyframe values that represent color. + * A track for color keyframe values. + * + * @augments KeyframeTrack */ -class ColorKeyframeTrack extends KeyframeTrack {} +class ColorKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new color keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<number>} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'color' + */ ColorKeyframeTrack.prototype.ValueTypeName = 'color'; /** - * A Track of numeric keyframe values. + * A track for numeric keyframe values. + * + * @augments KeyframeTrack */ -class NumberKeyframeTrack extends KeyframeTrack {} +class NumberKeyframeTrack extends KeyframeTrack { + /** + * Constructs a new number keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<number>} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'number' + */ NumberKeyframeTrack.prototype.ValueTypeName = 'number'; /** * Spherical linear unit quaternion interpolant. + * + * @augments Interpolant */ - class QuaternionLinearInterpolant extends Interpolant { + /** + * Constructs a new SLERP interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); @@ -44507,10 +63679,33 @@ class QuaternionLinearInterpolant extends Interpolant { } /** - * A Track of quaternion keyframe values. + * A track for Quaternion keyframe values. + * + * @augments KeyframeTrack */ class QuaternionKeyframeTrack extends KeyframeTrack { + /** + * Constructs a new Quaternion keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<number>} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + + /** + * Overwritten so the method returns Quaternion based interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {QuaternionLinearInterpolant} The new interpolant. + */ InterpolantFactoryMethodLinear( result ) { return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); @@ -44519,40 +63714,165 @@ class QuaternionKeyframeTrack extends KeyframeTrack { } +/** + * The value type name. + * + * @type {String} + * @default 'quaternion' + */ QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; // ValueBufferType is inherited -QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; +// DefaultInterpolation is inherited; QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** - * A Track that interpolates Strings + * A track for string keyframe values. + * + * @augments KeyframeTrack */ -class StringKeyframeTrack extends KeyframeTrack {} +class StringKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new string keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<string>} values - A list of keyframe values. + */ + constructor( name, times, values ) { + super( name, times, values ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'string' + */ StringKeyframeTrack.prototype.ValueTypeName = 'string'; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ StringKeyframeTrack.prototype.ValueBufferType = Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** - * A Track of vectored keyframe values. + * A track for vector keyframe values. + * + * @augments KeyframeTrack */ -class VectorKeyframeTrack extends KeyframeTrack {} +class VectorKeyframeTrack extends KeyframeTrack { + /** + * Constructs a new vector keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array<number>} times - A list of keyframe times. + * @param {Array<number>} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'vector' + */ VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; +/** + * A reusable set of keyframe tracks which represent an animation. + */ class AnimationClip { - constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) { + /** + * Constructs a new animation clip. + * + * Note: Instead of instantiating an AnimationClip directly with the constructor, you can + * use the static interface of this class for creating clips. In most cases though, animation clips + * will automatically be created by loaders when importing animated 3D assets. + * + * @param {string} [name=''] - The clip's name. + * @param {number} [duration=-1] - The clip's duration in seconds. If a negative value is passed, + * the duration will be calculated from the passed keyframes. + * @param {Array<KeyframeTrack>} tracks - An array of keyframe tracks. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode=NormalAnimationBlendMode] - Defines how the animation + * is blended/combined when two or more animations are simultaneously played. + */ + constructor( name = '', duration = -1, tracks = [], blendMode = NormalAnimationBlendMode ) { + /** + * The clip's name. + * + * @type {string} + */ this.name = name; + + /** + * An array of keyframe tracks. + * + * @type {Array<KeyframeTrack>} + */ this.tracks = tracks; + + /** + * The clip's duration in seconds. + * + * @type {number} + */ this.duration = duration; + + /** + * Defines how the animation is blended/combined when two or more animations + * are simultaneously played. + * + * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} + */ this.blendMode = blendMode; + /** + * The UUID of the animation clip. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); + /** + * An object that can be used to store custom data about the animation clip. + * It should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; + // this means it should figure out its duration by scanning the tracks if ( this.duration < 0 ) { @@ -44562,7 +63882,13 @@ class AnimationClip { } - + /** + * Factory method for creating an animation clip from the given JSON. + * + * @static + * @param {Object} json - The serialized animation clip. + * @return {AnimationClip} The new animation clip. + */ static parse( json ) { const tracks = [], @@ -44578,10 +63904,19 @@ class AnimationClip { const clip = new this( json.name, json.duration, tracks, json.blendMode ); clip.uuid = json.uuid; + clip.userData = JSON.parse( json.userData || '{}' ); + return clip; } + /** + * Serializes the given animation clip into JSON. + * + * @static + * @param {AnimationClip} clip - The animation clip to serialize. + * @return {Object} The JSON object. + */ static toJSON( clip ) { const tracks = [], @@ -44593,7 +63928,8 @@ class AnimationClip { 'duration': clip.duration, 'tracks': tracks, 'uuid': clip.uuid, - 'blendMode': clip.blendMode + 'blendMode': clip.blendMode, + 'userData': JSON.stringify( clip.userData ), }; @@ -44607,6 +63943,20 @@ class AnimationClip { } + /** + * Returns a new animation clip from the passed morph targets array of a + * geometry, taking a name and the number of frames per second. + * + * Note: The fps parameter is required, but the animation speed can be + * overridden via {@link AnimationAction#setDuration}. + * + * @static + * @param {string} name - The name of the animation clip. + * @param {Array<Object>} morphTargetSequence - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {AnimationClip} The new animation clip. + */ static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { const numMorphTargets = morphTargetSequence.length; @@ -44645,10 +63995,20 @@ class AnimationClip { } - return new this( name, - 1, tracks ); + return new this( name, -1, tracks ); } + /** + * Searches for an animation clip by name, taking as its first parameter + * either an array of clips, or a mesh or geometry that contains an + * array named "animations" property. + * + * @static + * @param {(Array<AnimationClip>|Object3D)} objectOrClipArray - The array or object to search through. + * @param {string} name - The name to search for. + * @return {?AnimationClip} The found animation clip. Returns `null` if no clip has been found. + */ static findByName( objectOrClipArray, name ) { let clipArray = objectOrClipArray; @@ -44674,6 +64034,19 @@ class AnimationClip { } + /** + * Returns an array of new AnimationClips created from the morph target + * sequences of a geometry, trying to sort morph target names into + * animation-group-based patterns like "Walk_001, Walk_002, Run_001, Run_002...". + * + * See {@link MD2Loader#parse} as an example for how the method should be used. + * + * @static + * @param {Array<Object>} morphTargets - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {Array<AnimationClip>} An array of new animation clips. + */ static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { const animationToMorphTargets = {}; @@ -44719,9 +64092,19 @@ class AnimationClip { } - // parse the animation.hierarchy format + /** + * Parses the `animation.hierarchy` format and returns a new animation clip. + * + * @static + * @deprecated since r175. + * @param {Object} animation - A serialized animation clip as JSON. + * @param {Array<Bones>} bones - An array of bones. + * @return {?AnimationClip} The new animation clip. + */ static parseAnimation( animation, bones ) { + console.warn( 'THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185' ); + if ( ! animation ) { console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); @@ -44757,7 +64140,7 @@ class AnimationClip { const blendMode = animation.blendMode; // automatic length determination in AnimationClip. - let duration = animation.length || - 1; + let duration = animation.length || -1; const hierarchyTracks = animation.hierarchy || []; @@ -44782,7 +64165,7 @@ class AnimationClip { for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; + morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = -1; } @@ -44847,6 +64230,11 @@ class AnimationClip { } + /** + * Sets the duration of this clip to the duration of its longest keyframe track. + * + * @return {AnimationClip} A reference to this animation clip. + */ resetDuration() { const tracks = this.tracks; @@ -44866,6 +64254,11 @@ class AnimationClip { } + /** + * Trims all tracks to the clip's duration. + * + * @return {AnimationClip} A reference to this animation clip. + */ trim() { for ( let i = 0; i < this.tracks.length; i ++ ) { @@ -44878,6 +64271,12 @@ class AnimationClip { } + /** + * Performs minimal validation on each track in the clip. Returns `true` if all + * tracks are valid. + * + * @return {boolean} Whether the clip's keyframes are valid or not. + */ validate() { let valid = true; @@ -44892,6 +64291,12 @@ class AnimationClip { } + /** + * Optimizes each track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ optimize() { for ( let i = 0; i < this.tracks.length; i ++ ) { @@ -44904,6 +64309,11 @@ class AnimationClip { } + /** + * Returns a new animation clip with copied values from this instance. + * + * @return {AnimationClip} A clone of this instance. + */ clone() { const tracks = []; @@ -44914,10 +64324,19 @@ class AnimationClip { } - return new this.constructor( this.name, this.duration, tracks, this.blendMode ); + const clip = new this.constructor( this.name, this.duration, tracks, this.blendMode ); + + clip.userData = JSON.parse( JSON.stringify( this.userData ) ); + + return clip; } + /** + * Serializes this animation clip into JSON. + * + * @return {Object} The JSON object. + */ toJSON() { return this.constructor.toJSON( this ); @@ -45003,15 +64422,46 @@ function parseKeyframeTrack( json ) { } +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.MaterialLoader(); + * const material = await loader.loadAsync( 'material.json' ); + * ``` + * This loader does not support node materials. Use {@link NodeMaterialLoader} instead. + * + * @augments Loader + */ class MaterialLoader extends Loader { + /** + * Constructs a new material loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + + /** + * A dictionary holding textures used by the material. + * + * @type {Object<string,Texture>} + */ this.textures = {}; } + /** + * Starts loading from the given URL and pass the loaded material to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Material)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -45046,6 +64496,12 @@ class MaterialLoader extends Loader { } + /** + * Parses the given JSON object and returns a material. + * + * @param {Object} json - The serialized material. + * @return {Material} The parsed material. + */ parse( json ) { const textures = this.textures; @@ -45062,7 +64518,7 @@ class MaterialLoader extends Loader { } - const material = MaterialLoader.createMaterialFromType( json.type ); + const material = this.createMaterialFromType( json.type ); if ( json.uuid !== undefined ) material.uuid = json.uuid; if ( json.name !== undefined ) material.name = json.name; @@ -45079,6 +64535,7 @@ class MaterialLoader extends Loader { if ( json.shininess !== undefined ) material.shininess = json.shininess; if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; + if ( json.dispersion !== undefined ) material.dispersion = json.dispersion; if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; @@ -45310,6 +64767,13 @@ class MaterialLoader extends Loader { } + /** + * Textures are not embedded in the material JSON so they have + * to be injected before the loading process starts. + * + * @param {Object} value - A dictionary holding textures for material properties. + * @return {MaterialLoader} A reference to this material loader. + */ setTextures( value ) { this.textures = value; @@ -45317,6 +64781,25 @@ class MaterialLoader extends Loader { } + /** + * Creates a material for the given type. + * + * @param {string} type - The material type. + * @return {Material} The new material. + */ + createMaterialFromType( type ) { + + return MaterialLoader.createMaterialFromType( type ); + + } + + /** + * Creates a material for the given type. + * + * @static + * @param {string} type - The material type. + * @return {Material} The new material. + */ static createMaterialFromType( type ) { const materialLib = { @@ -45346,52 +64829,36 @@ class MaterialLoader extends Loader { } +/** + * A class with loader utility functions. + */ class LoaderUtils { - static decodeText( array ) { - - if ( typeof TextDecoder !== 'undefined' ) { - - return new TextDecoder().decode( array ); - - } - - // Avoid the String.fromCharCode.apply(null, array) shortcut, which - // throws a "maximum call stack size exceeded" error for large arrays. - - let s = ''; - - for ( let i = 0, il = array.length; i < il; i ++ ) { - - // Implicitly assumes little-endian. - s += String.fromCharCode( array[ i ] ); - - } - - try { - - // merges multi-byte utf-8 characters. - - return decodeURIComponent( escape( s ) ); - - } catch ( e ) { // see #16358 - - return s; - - } - - } - + /** + * Extracts the base URL from the given URL. + * + * @param {string} url -The URL to extract the base URL from. + * @return {string} The extracted base URL. + */ static extractUrlBase( url ) { const index = url.lastIndexOf( '/' ); - if ( index === - 1 ) return './'; + if ( index === -1 ) return './'; return url.slice( 0, index + 1 ); } + /** + * Resolves relative URLs against the given path. Absolute paths, data urls, + * and blob URLs will be returned as is. Invalid URLs will return an empty + * string. + * + * @param {string} url -The URL to resolve. + * @param {string} path - The base path for relative URLs to be resolved against. + * @return {string} The resolved URL. + */ static resolveURL( url, path ) { // Invalid URL @@ -45420,15 +64887,35 @@ class LoaderUtils { } +/** + * An instanced version of a geometry. + */ class InstancedBufferGeometry extends BufferGeometry { + /** + * Constructs a new instanced buffer geometry. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInstancedBufferGeometry = true; this.type = 'InstancedBufferGeometry'; + + /** + * The instance count. + * + * @type {number} + * @default Infinity + */ this.instanceCount = Infinity; } @@ -45457,14 +64944,42 @@ class InstancedBufferGeometry extends BufferGeometry { } +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.BufferGeometryLoader(); + * const geometry = await loader.loadAsync( 'models/json/pressure.json' ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xF5F5F5 } ); + * const object = new THREE.Mesh( geometry, material ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ class BufferGeometryLoader extends Loader { + /** + * Constructs a new geometry loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and pass the loaded geometry to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -45499,6 +65014,12 @@ class BufferGeometryLoader extends Loader { } + /** + * Parses the given JSON object and returns a geometry. + * + * @param {Object} json - The serialized geometry. + * @return {BufferGeometry} The parsed geometry. + */ parse( json ) { const interleavedBufferMap = {}; @@ -45640,15 +65161,7 @@ class BufferGeometryLoader extends Loader { if ( boundingSphere !== undefined ) { - const center = new Vector3(); - - if ( boundingSphere.center !== undefined ) { - - center.fromArray( boundingSphere.center ); - - } - - geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); + geometry.boundingSphere = new Sphere().fromJSON( boundingSphere ); } @@ -45661,14 +65174,43 @@ class BufferGeometryLoader extends Loader { } +/** + * A loader for loading a JSON resource in the [JSON Object/Scene format]{@link https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4}. + * The files are internally loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.ObjectLoader(); + * const obj = await loader.loadAsync( 'models/json/example.json' ); + * scene.add( obj ); + * + * // Alternatively, to parse a previously loaded JSON structure + * const object = await loader.parseAsync( a_json_object ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ class ObjectLoader extends Loader { + /** + * Constructs a new object loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and pass the loaded 3D object to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object3D)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -45715,6 +65257,14 @@ class ObjectLoader extends Loader { } + /** + * Async version of {@link ObjectLoader#load}. + * + * @async + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @return {Promise<Object3D>} A Promise that resolves with the loaded 3D object. + */ async loadAsync( url, onProgress ) { const scope = this; @@ -45743,6 +65293,14 @@ class ObjectLoader extends Loader { } + /** + * Parses the given JSON. This is used internally by {@link ObjectLoader#load} + * but can also be used directly to parse a previously loaded JSON structure. + * + * @param {Object} json - The serialized 3D object. + * @param {onLoad} onLoad - Executed when all resources (e.g. textures) have been fully loaded. + * @return {Object3D} The parsed 3D object. + */ parse( json, onLoad ) { const animations = this.parseAnimations( json.animations ); @@ -45762,6 +65320,7 @@ class ObjectLoader extends Loader { const skeletons = this.parseSkeletons( json.skeletons, object ); this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); // @@ -45788,6 +65347,12 @@ class ObjectLoader extends Loader { } + /** + * Async version of {@link ObjectLoader#parse}. + * + * @param {Object} json - The serialized 3D object. + * @return {Promise<Object3D>} A Promise that resolves with the parsed 3D object. + */ async parseAsync( json ) { const animations = this.parseAnimations( json.animations ); @@ -45803,11 +65368,14 @@ class ObjectLoader extends Loader { const skeletons = this.parseSkeletons( json.skeletons, object ); this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); return object; } + // internals + parseShapes( json ) { const shapes = {}; @@ -46396,6 +65964,8 @@ class ObjectLoader extends Loader { if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; if ( data.backgroundRotation !== undefined ) object.backgroundRotation.fromArray( data.backgroundRotation ); + + if ( data.environmentIntensity !== undefined ) object.environmentIntensity = data.environmentIntensity; if ( data.environmentRotation !== undefined ) object.environmentRotation.fromArray( data.environmentRotation ); break; @@ -46430,6 +66000,7 @@ class ObjectLoader extends Loader { case 'DirectionalLight': object = new DirectionalLight( data.color, data.intensity ); + object.target = data.target || ''; break; @@ -46448,6 +66019,7 @@ class ObjectLoader extends Loader { case 'SpotLight': object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + object.target = data.target || ''; break; @@ -46504,7 +66076,7 @@ class ObjectLoader extends Loader { geometry = getGeometry( data.geometry ); material = getMaterial( data.material ); - object = new BatchedMesh( data.maxGeometryCount, data.maxVertexCount, data.maxIndexCount, material ); + object = new BatchedMesh( data.maxInstanceCount, data.maxVertexCount, data.maxIndexCount, material ); object.geometry = geometry; object.perObjectFrustumCulled = data.perObjectFrustumCulled; object.sortObjects = data.sortObjects; @@ -46512,37 +66084,66 @@ class ObjectLoader extends Loader { object._drawRanges = data.drawRanges; object._reservedRanges = data.reservedRanges; - object._visibility = data.visibility; - object._active = data.active; - object._bounds = data.bounds.map( bound => { + object._geometryInfo = data.geometryInfo.map( info => { - const box = new Box3(); - box.min.fromArray( bound.boxMin ); - box.max.fromArray( bound.boxMax ); + let box = null; + let sphere = null; + if ( info.boundingBox !== undefined ) { - const sphere = new Sphere(); - sphere.radius = bound.sphereRadius; - sphere.center.fromArray( bound.sphereCenter ); + box = new Box3().fromJSON( info.boundingBox ); - return { - boxInitialized: bound.boxInitialized, - box: box, + } + + if ( info.boundingSphere !== undefined ) { - sphereInitialized: bound.sphereInitialized, - sphere: sphere + sphere = new Sphere().fromJSON( info.boundingSphere ); + + } + + return { + ...info, + boundingBox: box, + boundingSphere: sphere }; } ); + object._instanceInfo = data.instanceInfo; - object._maxGeometryCount = data.maxGeometryCount; + object._availableInstanceIds = data._availableInstanceIds; + object._availableGeometryIds = data._availableGeometryIds; + + object._nextIndexStart = data.nextIndexStart; + object._nextVertexStart = data.nextVertexStart; + object._geometryCount = data.geometryCount; + + object._maxInstanceCount = data.maxInstanceCount; object._maxVertexCount = data.maxVertexCount; object._maxIndexCount = data.maxIndexCount; object._geometryInitialized = data.geometryInitialized; - object._geometryCount = data.geometryCount; object._matricesTexture = getTexture( data.matricesTexture.uuid ); + object._indirectTexture = getTexture( data.indirectTexture.uuid ); + + if ( data.colorsTexture !== undefined ) { + + object._colorsTexture = getTexture( data.colorsTexture.uuid ); + + } + + if ( data.boundingSphere !== undefined ) { + + object.boundingSphere = new Sphere().fromJSON( data.boundingSphere ); + + } + + if ( data.boundingBox !== undefined ) { + + object.boundingBox = new Box3().fromJSON( data.boundingBox ); + + } + break; case 'LOD': @@ -46627,6 +66228,7 @@ class ObjectLoader extends Loader { if ( data.shadow ) { + if ( data.shadow.intensity !== undefined ) object.shadow.intensity = data.shadow.intensity; if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; @@ -46718,6 +66320,32 @@ class ObjectLoader extends Loader { } + bindLightTargets( object ) { + + object.traverse( function ( child ) { + + if ( child.isDirectionalLight || child.isSpotLight ) { + + const uuid = child.target; + + const target = object.getObjectByProperty( 'uuid', uuid ); + + if ( target !== undefined ) { + + child.target = target; + + } else { + + child.target = new Object3D(); + + } + + } + + } ); + + } + } const TEXTURE_MAPPING = { @@ -46744,12 +66372,49 @@ const TEXTURE_FILTER = { LinearMipmapLinearFilter: LinearMipmapLinearFilter }; +const _errorMap = new WeakMap(); + +/** + * A loader for loading images as an [ImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap}. + * An `ImageBitmap` provides an asynchronous and resource efficient pathway to prepare + * textures for rendering. + * + * Note that {@link Texture#flipY} and {@link Texture#premultiplyAlpha} are ignored with image bitmaps. + * They needs these configuration on bitmap creation unlike regular images need them on uploading to GPU. + * + * You need to set the equivalent options via {@link ImageBitmapLoader#setOptions} instead. + * + * Also note that unlike {@link FileLoader}, this loader avoids multiple concurrent requests to the same URL only if `Cache` is enabled. + * + * ```js + * const loader = new THREE.ImageBitmapLoader(); + * loader.setOptions( { imageOrientation: 'flipY' } ); // set options if needed + * const imageBitmap = await loader.loadAsync( 'image.png' ); + * + * const texture = new THREE.Texture( imageBitmap ); + * texture.needsUpdate = true; + * ``` + * + * @augments Loader + */ class ImageBitmapLoader extends Loader { + /** + * Constructs a new image bitmap loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isImageBitmapLoader = true; if ( typeof createImageBitmap === 'undefined' ) { @@ -46764,10 +66429,31 @@ class ImageBitmapLoader extends Loader { } + /** + * Represents the loader options. + * + * @type {Object} + * @default {premultiplyAlpha:'none'} + */ this.options = { premultiplyAlpha: 'none' }; + /** + * Used for aborting requests. + * + * @private + * @type {AbortController} + */ + this._abortController = new AbortController(); + } + /** + * Sets the given loader options. The structure of the object must match the `options` parameter of + * [createImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap}. + * + * @param {Object} options - The loader options to set. + * @return {ImageBitmapLoader} A reference to this image bitmap loader. + */ setOptions( options ) { this.options = options; @@ -46776,6 +66462,15 @@ class ImageBitmapLoader extends Loader { } + /** + * Starts loading from the given URL and pass the loaded image bitmap to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(ImageBitmap)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {ImageBitmap|undefined} The image bitmap. + */ load( url, onLoad, onProgress, onError ) { if ( url === undefined ) url = ''; @@ -46786,7 +66481,7 @@ class ImageBitmapLoader extends Loader { const scope = this; - const cached = Cache.get( url ); + const cached = Cache.get( `image-bitmap:${url}` ); if ( cached !== undefined ) { @@ -46797,15 +66492,27 @@ class ImageBitmapLoader extends Loader { cached.then( imageBitmap => { - if ( onLoad ) onLoad( imageBitmap ); + // check if there is an error for the cached promise - scope.manager.itemEnd( url ); + if ( _errorMap.has( cached ) === true ) { - } ).catch( e => { + if ( onError ) onError( _errorMap.get( cached ) ); - if ( onError ) onError( e ); + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } else { + + if ( onLoad ) onLoad( imageBitmap ); + + scope.manager.itemEnd( url ); + + return imageBitmap; + + } } ); + return; } @@ -46826,6 +66533,7 @@ class ImageBitmapLoader extends Loader { const fetchOptions = {}; fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; fetchOptions.headers = this.requestHeader; + fetchOptions.signal = ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal; const promise = fetch( url, fetchOptions ).then( function ( res ) { @@ -46837,7 +66545,7 @@ class ImageBitmapLoader extends Loader { } ).then( function ( imageBitmap ) { - Cache.add( url, imageBitmap ); + Cache.add( `image-bitmap:${url}`, imageBitmap ); if ( onLoad ) onLoad( imageBitmap ); @@ -46849,24 +66557,50 @@ class ImageBitmapLoader extends Loader { if ( onError ) onError( e ); - Cache.remove( url ); + _errorMap.set( promise, e ); + + Cache.remove( `image-bitmap:${url}` ); scope.manager.itemError( url ); scope.manager.itemEnd( url ); } ); - Cache.add( url, promise ); + Cache.add( `image-bitmap:${url}`, promise ); scope.manager.itemStart( url ); } + /** + * Aborts ongoing fetch requests. + * + * @return {ImageBitmapLoader} A reference to this instance. + */ + abort() { + + this._abortController.abort(); + this._abortController = new AbortController(); + + return this; + + } + } let _context; +/** + * Manages the global audio context in the engine. + * + * @hideconstructor + */ class AudioContext { + /** + * Returns the global native audio context. + * + * @return {AudioContext} The native audio context. + */ static getContext() { if ( _context === undefined ) { @@ -46879,6 +66613,11 @@ class AudioContext { } + /** + * Allows to set the global native audio context from outside. + * + * @param {AudioContext} value - The native context to set. + */ static setContext( value ) { _context = value; @@ -46887,14 +66626,45 @@ class AudioContext { } +/** + * Class for loading audio buffers. Audios are internally + * loaded via {@link FileLoader}. + * + * ```js + * const audioListener = new THREE.AudioListener(); + * const ambientSound = new THREE.Audio( audioListener ); + * + * const loader = new THREE.AudioLoader(); + * const audioBuffer = await loader.loadAsync( 'audio/ambient_ocean.ogg' ); + * + * ambientSound.setBuffer( audioBuffer ); + * ambientSound.play(); + * ``` + * + * @augments Loader + */ class AudioLoader extends Loader { + /** + * Constructs a new audio loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded audio buffer + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(AudioBuffer)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -46951,20 +66721,61 @@ const _eyeRight = /*@__PURE__*/ new Matrix4(); const _eyeLeft = /*@__PURE__*/ new Matrix4(); const _projectionMatrix = /*@__PURE__*/ new Matrix4(); +/** + * A special type of camera that uses two perspective cameras with + * stereoscopic projection. Can be used for rendering stereo effects + * like [3D Anaglyph]{@link https://en.wikipedia.org/wiki/Anaglyph_3D} or + * [Parallax Barrier]{@link https://en.wikipedia.org/wiki/parallax_barrier}. + */ class StereoCamera { + /** + * Constructs a new stereo camera. + */ constructor() { + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ this.type = 'StereoCamera'; + /** + * The aspect. + * + * @type {number} + * @default 1 + */ this.aspect = 1; + /** + * The eye separation which represents the distance + * between the left and right camera. + * + * @type {number} + * @default 0.064 + */ this.eyeSep = 0.064; + /** + * The camera representing the left eye. This is added to layer `1` so objects to be + * rendered by the left camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ this.cameraL = new PerspectiveCamera(); this.cameraL.layers.enable( 1 ); this.cameraL.matrixAutoUpdate = false; + /** + * The camera representing the right eye. This is added to layer `2` so objects to be + * rendered by the right camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ this.cameraR = new PerspectiveCamera(); this.cameraR.layers.enable( 2 ); this.cameraR.matrixAutoUpdate = false; @@ -46981,6 +66792,11 @@ class StereoCamera { } + /** + * Updates the stereo camera based on the given perspective camera. + * + * @param {PerspectiveCamera} camera - The perspective camera. + */ update( camera ) { const cache = this._cache; @@ -47042,14 +66858,43 @@ class StereoCamera { } +/** + * Represents a uniform which is a global shader variable. They are passed to shader programs. + * + * When declaring a uniform of a {@link ShaderMaterial}, it is declared by value or by object. + * ```js + * uniforms: { + * time: { value: 1.0 }, + * resolution: new Uniform( new Vector2() ) + * }; + * ``` + * Since this class can only be used in context of {@link ShaderMaterial}, it is only supported + * in {@link WebGLRenderer}. + */ class Uniform { + /** + * Constructs a new uniform. + * + * @param {any} value - The uniform value. + */ constructor( value ) { + /** + * The uniform value. + * + * @type {any} + */ this.value = value; } + /** + * Returns a new uniform with copied values from this instance. + * If the value has a `clone()` method, the value is cloned as well. + * + * @return {Uniform} A clone of this instance. + */ clone() { return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); @@ -47058,14 +66903,40 @@ class Uniform { } +/** + * An instanced version of an interleaved buffer. + * + * @augments InterleavedBuffer + */ class InstancedInterleavedBuffer extends InterleavedBuffer { + /** + * Constructs a new instanced interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + * @param {number} [meshPerAttribute=1] - Defines how often a value of this interleaved buffer should be repeated. + */ constructor( array, stride, meshPerAttribute = 1 ) { super( array, stride ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isInstancedInterleavedBuffer = true; + /** + * Defines how often a value of this buffer attribute should be repeated, + * see {@link InstancedBufferAttribute#meshPerAttribute}. + * + * @type {number} + * @default 1 + */ this.meshPerAttribute = meshPerAttribute; } @@ -47103,30 +66974,122 @@ class InstancedInterleavedBuffer extends InterleavedBuffer { } +/** + * An alternative version of a buffer attribute with more control over the VBO. + * + * The renderer does not construct a VBO for this kind of attribute. Instead, it uses + * whatever VBO is passed in constructor and can later be altered via the `buffer` property. + * + * The most common use case for this class is when some kind of GPGPU calculation interferes + * or even produces the VBOs in question. + * + * Notice that this class can only be used with {@link WebGLRenderer}. + */ class GLBufferAttribute { - constructor( buffer, type, itemSize, elementSize, count ) { + /** + * Constructs a new GL buffer attribute. + * + * @param {WebGLBuffer} buffer - The native WebGL buffer. + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} itemSize - The item size. + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @param {number} count - The expected number of vertices in VBO. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( buffer, type, itemSize, elementSize, count, normalized = false ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isGLBufferAttribute = true; + /** + * The name of the buffer attribute. + * + * @type {string} + */ this.name = ''; + /** + * The native WebGL buffer. + * + * @type {WebGLBuffer} + */ this.buffer = buffer; + + /** + * The native data type. + * + * @type {number} + */ this.type = type; + + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ this.itemSize = itemSize; + + /** + * The corresponding size (in bytes) for the given `type` parameter. + * + * @type {number} + */ this.elementSize = elementSize; + + /** + * The expected number of vertices in VBO. + * + * @type {number} + */ this.count = count; + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `buffer` contains data of `gl.UNSIGNED_SHORT`, + * and `normalized` is `true`, the values `0 - +65535` in the buffer data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; + + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ this.version = 0; } + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ set needsUpdate( value ) { if ( value === true ) this.version ++; } + /** + * Sets the given native WebGL buffer. + * + * @param {WebGLBuffer} buffer - The buffer to set. + * @return {BufferAttribute} A reference to this instance. + */ setBuffer( buffer ) { this.buffer = buffer; @@ -47135,6 +67098,13 @@ class GLBufferAttribute { } + /** + * Sets the given native data type and element size. + * + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @return {BufferAttribute} A reference to this instance. + */ setType( type, elementSize ) { this.type = type; @@ -47144,6 +67114,12 @@ class GLBufferAttribute { } + /** + * Sets the item size. + * + * @param {number} itemSize - The item size. + * @return {BufferAttribute} A reference to this instance. + */ setItemSize( itemSize ) { this.itemSize = itemSize; @@ -47152,6 +67128,12 @@ class GLBufferAttribute { } + /** + * Sets the count (the expected number of vertices in VBO). + * + * @param {number} count - The count. + * @return {BufferAttribute} A reference to this instance. + */ setCount( count ) { this.count = count; @@ -47164,18 +67146,86 @@ class GLBufferAttribute { const _matrix = /*@__PURE__*/ new Matrix4(); +/** + * This class is designed to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3d space the mouse is over) + * amongst other things. + */ class Raycaster { + /** + * Constructs a new raycaster. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + * @param {number} [near=0] - All results returned are further away than near. Near can't be negative. + * @param {number} [far=Infinity] - All results returned are closer than far. Far can't be lower than near. + */ constructor( origin, direction, near = 0, far = Infinity ) { + /** + * The ray used for raycasting. + * + * @type {Ray} + */ this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default 0 + */ this.near = near; + + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default Infinity + */ this.far = far; + + /** + * The camera to use when raycasting against view-dependent objects such as + * billboarded objects like sprites. This field can be set manually or + * is set when calling `setFromCamera()`. + * + * @type {?Camera} + * @default null + */ this.camera = null; + + /** + * Allows to selectively ignore 3D objects when performing intersection tests. + * The following code example ensures that only 3D objects on layer `1` will be + * honored by raycaster. + * ```js + * raycaster.layers.set( 1 ); + * object.layers.enable( 1 ); + * ``` + * + * @type {Layers} + */ this.layers = new Layers(); + + /** + * A parameter object that configures the raycasting. It has the structure: + * + * ``` + * { + * Mesh: {}, + * Line: { threshold: 1 }, + * LOD: {}, + * Points: { threshold: 1 }, + * Sprite: {} + * } + * ``` + * Where `threshold` is the precision of the raycaster when intersecting objects, in world units. + * + * @type {Object} + */ this.params = { Mesh: {}, Line: { threshold: 1 }, @@ -47186,6 +67236,12 @@ class Raycaster { } + /** + * Updates the ray with a new origin and direction by copying the values from the arguments. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + */ set( origin, direction ) { // direction is assumed to be normalized (for accurate distance calculations) @@ -47194,6 +67250,13 @@ class Raycaster { } + /** + * Uses the given coordinates and camera to compute a new origin and direction for the internal ray. + * + * @param {Vector2} coords - 2D coordinates of the mouse, in normalized device coordinates (NDC). + * X and Y components should be between `-1` and `1`. + * @param {Camera} camera - The camera from which the ray should originate. + */ setFromCamera( coords, camera ) { if ( camera.isPerspectiveCamera ) { @@ -47205,7 +67268,7 @@ class Raycaster { } else if ( camera.isOrthographicCamera ) { this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); + this.ray.direction.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ); this.camera = camera; } else { @@ -47216,17 +67279,59 @@ class Raycaster { } + /** + * Uses the given WebXR controller to compute a new origin and direction for the internal ray. + * + * @param {WebXRController} controller - The controller to copy the position and direction from. + * @return {Raycaster} A reference to this raycaster. + */ setFromXRController( controller ) { _matrix.identity().extractRotation( controller.matrixWorld ); this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); - this.ray.direction.set( 0, 0, - 1 ).applyMatrix4( _matrix ); + this.ray.direction.set( 0, 0, -1 ).applyMatrix4( _matrix ); return this; } + /** + * The intersection point of a raycaster intersection test. + * @typedef {Object} Raycaster~Intersection + * @property {number} distance - The distance from the ray's origin to the intersection point. + * @property {number} distanceToRay - Some 3D objects e.g. {@link Points} provide the distance of the + * intersection to the nearest point on the ray. For other objects it will be `undefined`. + * @property {Vector3} point - The intersection point, in world coordinates. + * @property {Object} face - The face that has been intersected. + * @property {number} faceIndex - The face index. + * @property {Object3D} object - The 3D object that has been intersected. + * @property {Vector2} uv - U,V coordinates at point of intersection. + * @property {Vector2} uv1 - Second set of U,V coordinates at point of intersection. + * @property {Vector3} uv1 - Interpolated normal vector at point of intersection. + * @property {number} instanceId - The index number of the instance where the ray + * intersects the {@link InstancedMesh}. + */ + + /** + * Checks all intersection between the ray and the object with or without the + * descendants. Intersections are returned sorted by distance, closest first. + * + * `Raycaster` delegates to the `raycast()` method of the passed 3D object, when + * evaluating whether the ray intersects the object or not. This allows meshes to respond + * differently to ray casting than lines or points. + * + * Note that for meshes, faces must be pointed towards the origin of the ray in order + * to be detected; intersections of the ray passing through the back of a face will not + * be detected. To raycast against both faces of an object, you'll want to set {@link Material#side} + * to `THREE.DoubleSide`. + * + * @param {Object3D} object - The 3D object to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array<Raycaster~Intersection>} [intersects=[]] The target array that holds the result of the method. + * @return {Array<Raycaster~Intersection>} An array holding the intersection points. + */ intersectObject( object, recursive = true, intersects = [] ) { intersect( object, this, intersects, recursive ); @@ -47237,6 +67342,16 @@ class Raycaster { } + /** + * Checks all intersection between the ray and the objects with or without + * the descendants. Intersections are returned sorted by distance, closest first. + * + * @param {Array<Object3D>} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array<Raycaster~Intersection>} [intersects=[]] The target array that holds the result of the method. + * @return {Array<Raycaster~Intersection>} An array holding the intersection points. + */ intersectObjects( objects, recursive = true, intersects = [] ) { for ( let i = 0, l = objects.length; i < l; i ++ ) { @@ -47261,13 +67376,17 @@ function ascSort( a, b ) { function intersect( object, raycaster, intersects, recursive ) { + let propagate = true; + if ( object.layers.test( raycaster.layers ) ) { - object.raycast( raycaster, intersects ); + const result = object.raycast( raycaster, intersects ); + + if ( result === false ) propagate = false; } - if ( recursive === true ) { + if ( propagate === true && recursive === true ) { const children = object.children; @@ -47281,23 +67400,70 @@ function intersect( object, raycaster, intersects, recursive ) { } +/** + * Class for keeping track of time. + */ class Clock { + /** + * Constructs a new clock. + * + * @param {boolean} [autoStart=true] - Whether to automatically start the clock when + * `getDelta()` is called for the first time. + */ constructor( autoStart = true ) { + /** + * If set to `true`, the clock starts automatically when `getDelta()` is called + * for the first time. + * + * @type {boolean} + * @default true + */ this.autoStart = autoStart; + /** + * Holds the time at which the clock's `start()` method was last called. + * + * @type {number} + * @default 0 + */ this.startTime = 0; + + /** + * Holds the time at which the clock's `start()`, `getElapsedTime()` or + * `getDelta()` methods were last called. + * + * @type {number} + * @default 0 + */ this.oldTime = 0; + + /** + * Keeps track of the total time that the clock has been running. + * + * @type {number} + * @default 0 + */ this.elapsedTime = 0; + /** + * Whether the clock is running or not. + * + * @type {boolean} + * @default true + */ this.running = false; } + /** + * Starts the clock. When `autoStart` is set to `true`, the method is automatically + * called by the class. + */ start() { - this.startTime = now(); + this.startTime = performance.now(); this.oldTime = this.startTime; this.elapsedTime = 0; @@ -47305,6 +67471,9 @@ class Clock { } + /** + * Stops the clock. + */ stop() { this.getElapsedTime(); @@ -47313,6 +67482,11 @@ class Clock { } + /** + * Returns the elapsed time in seconds. + * + * @return {number} The elapsed time. + */ getElapsedTime() { this.getDelta(); @@ -47320,6 +67494,11 @@ class Clock { } + /** + * Returns the delta time in seconds. + * + * @return {number} The delta time. + */ getDelta() { let diff = 0; @@ -47333,7 +67512,7 @@ class Clock { if ( this.running ) { - const newTime = now(); + const newTime = performance.now(); diff = ( newTime - this.oldTime ) / 1000; this.oldTime = newTime; @@ -47348,32 +67527,55 @@ class Clock { } -function now() { - - return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 - -} - /** - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. - * The azimuthal angle (theta) is measured from the positive z-axis. + * This class can be used to represent points in 3D space as + * [Spherical coordinates]{@link https://en.wikipedia.org/wiki/Spherical_coordinate_system}. */ - - class Spherical { + /** + * Constructs a new spherical. + * + * @param {number} [radius=1] - The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * @param {number} [phi=0] - The polar angle in radians from the y (up) axis. + * @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis. + */ constructor( radius = 1, phi = 0, theta = 0 ) { + /** + * The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * + * @type {number} + * @default 1 + */ this.radius = radius; - this.phi = phi; // polar angle - this.theta = theta; // azimuthal angle - return this; + /** + * The polar angle in radians from the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.phi = phi; + + /** + * The equator/azimuthal angle in radians around the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; } + /** + * Sets the spherical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} phi - The polar angle. + * @param {number} theta - The azimuthal angle. + * @return {Spherical} A reference to this spherical. + */ set( radius, phi, theta ) { this.radius = radius; @@ -47384,6 +67586,12 @@ class Spherical { } + /** + * Copies the values of the given spherical to this instance. + * + * @param {Spherical} other - The spherical to copy. + * @return {Spherical} A reference to this spherical. + */ copy( other ) { this.radius = other.radius; @@ -47394,22 +67602,42 @@ class Spherical { } - // restrict phi to be between EPS and PI-EPS + /** + * Restricts the polar angle [page:.phi phi] to be between `0.000001` and pi - + * `0.000001`. + * + * @return {Spherical} A reference to this spherical. + */ makeSafe() { const EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); + this.phi = clamp( this.phi, EPS, Math.PI - EPS ); return this; } + /** + * Sets the spherical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Spherical} A reference to this spherical. + */ setFromVector3( v ) { return this.setFromCartesianCoords( v.x, v.y, v.z ); } + /** + * Sets the spherical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The y value. + * @param {number} z - The z value. + * @return {Spherical} A reference to this spherical. + */ setFromCartesianCoords( x, y, z ) { this.radius = Math.sqrt( x * x + y * y + z * z ); @@ -47422,7 +67650,7 @@ class Spherical { } else { this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); + this.phi = Math.acos( clamp( y / this.radius, -1, 1 ) ); } @@ -47430,6 +67658,11 @@ class Spherical { } + /** + * Returns a new spherical with copied values from this instance. + * + * @return {Spherical} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -47439,21 +67672,54 @@ class Spherical { } /** - * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system + * This class can be used to represent points in 3D space as + * [Cylindrical coordinates]{@link https://en.wikipedia.org/wiki/Cylindrical_coordinate_system}. */ - class Cylindrical { + /** + * Constructs a new cylindrical. + * + * @param {number} [radius=1] - The distance from the origin to a point in the x-z plane. + * @param {number} [theta=0] - A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * @param {number} [y=0] - The height above the x-z plane. + */ constructor( radius = 1, theta = 0, y = 0 ) { - this.radius = radius; // distance from the origin to a point in the x-z plane - this.theta = theta; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis - this.y = y; // height above the x-z plane + /** + * The distance from the origin to a point in the x-z plane. + * + * @type {number} + * @default 1 + */ + this.radius = radius; + + /** + * A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; - return this; + /** + * The height above the x-z plane. + * + * @type {number} + * @default 0 + */ + this.y = y; } + /** + * Sets the cylindrical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle. + * @param {number} y - The height value. + * @return {Cylindrical} A reference to this cylindrical. + */ set( radius, theta, y ) { this.radius = radius; @@ -47464,6 +67730,12 @@ class Cylindrical { } + /** + * Copies the values of the given cylindrical to this instance. + * + * @param {Cylindrical} other - The cylindrical to copy. + * @return {Cylindrical} A reference to this cylindrical. + */ copy( other ) { this.radius = other.radius; @@ -47474,12 +67746,27 @@ class Cylindrical { } + /** + * Sets the cylindrical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Cylindrical} A reference to this cylindrical. + */ setFromVector3( v ) { return this.setFromCartesianCoords( v.x, v.y, v.z ); } + /** + * Sets the cylindrical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The x value. + * @param {number} z - The x value. + * @return {Cylindrical} A reference to this cylindrical. + */ setFromCartesianCoords( x, y, z ) { this.radius = Math.sqrt( x * x + z * z ); @@ -47490,6 +67777,11 @@ class Cylindrical { } + /** + * Returns a new cylindrical with copied values from this instance. + * + * @return {Cylindrical} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -47500,17 +67792,52 @@ class Cylindrical { const _vector = /*@__PURE__*/ new Vector2(); +/** + * Represents an axis-aligned bounding box (AABB) in 2D space. + */ class Box2 { + /** + * Constructs a new bounding box. + * + * @param {Vector2} [min=(Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector2} [max=(-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBox2 = true; + /** + * The lower boundary of the box. + * + * @type {Vector2} + */ this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector2} + */ this.max = max; } + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector2} min - The lower boundary of the box. + * @param {Vector2} max - The upper boundary of the box. + * @return {Box2} A reference to this bounding box. + */ set( min, max ) { this.min.copy( min ); @@ -47520,6 +67847,13 @@ class Box2 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array<Vector2>} points - An array holding 2D position data as instances of {@link Vector2}. + * @return {Box2} A reference to this bounding box. + */ setFromPoints( points ) { this.makeEmpty(); @@ -47534,6 +67868,14 @@ class Box2 { } + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector2} center - The center of the box. + * @param {Vector2} size - The x and y dimensions of the box. + * @return {Box2} A reference to this bounding box. + */ setFromCenterAndSize( center, size ) { const halfSize = _vector.copy( size ).multiplyScalar( 0.5 ); @@ -47544,12 +67886,23 @@ class Box2 { } + /** + * Returns a new box with copied values from this instance. + * + * @return {Box2} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given box to this instance. + * + * @param {Box2} box - The box to copy. + * @return {Box2} A reference to this bounding box. + */ copy( box ) { this.min.copy( box.min ); @@ -47559,6 +67912,11 @@ class Box2 { } + /** + * Makes this box empty which means in encloses a zero space in 2D. + * + * @return {Box2} A reference to this bounding box. + */ makeEmpty() { this.min.x = this.min.y = + Infinity; @@ -47568,6 +67926,13 @@ class Box2 { } + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ isEmpty() { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes @@ -47576,18 +67941,36 @@ class Box2 { } + /** + * Returns the center point of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The center point. + */ getCenter( target ) { return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } + /** + * Returns the dimensions of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The size. + */ getSize( target ) { return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); } + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector2} point - The point that should be included by the bounding box. + * @return {Box2} A reference to this bounding box. + */ expandByPoint( point ) { this.min.min( point ); @@ -47597,6 +67980,15 @@ class Box2 { } + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. + * + * @param {Vector2} vector - The vector that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ expandByVector( vector ) { this.min.sub( vector ); @@ -47606,6 +67998,13 @@ class Box2 { } + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ expandByScalar( scalar ) { this.min.addScalar( - scalar ); @@ -47615,13 +68014,26 @@ class Box2 { } + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector2} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ containsPoint( point ) { - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y; } + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ containsBox( box ) { return this.min.x <= box.min.x && box.max.x <= this.max.x && @@ -47629,6 +68041,13 @@ class Box2 { } + /** + * Returns a point as a proportion of this box's width and height. + * + * @param {Vector2} point - A point in 2D space. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} A point as a proportion of this box's width and height. + */ getParameter( point, target ) { // This can potentially have a divide by zero if the box @@ -47641,27 +68060,56 @@ class Box2 { } + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ intersectsBox( box ) { // using 4 splitting planes to rule out intersections - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y; } + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector2} point - The point to clamp. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The clamped point. + */ clampPoint( point, target ) { return target.copy( point ).clamp( this.min, this.max ); } + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector2} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ distanceToPoint( point ) { return this.clampPoint( point, _vector ).distanceTo( point ); } + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box2} box - The bounding box to intersect with. + * @return {Box2} A reference to this bounding box. + */ intersect( box ) { this.min.max( box.min ); @@ -47673,6 +68121,14 @@ class Box2 { } + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box2} box - The bounding box that will be unioned with this instance. + * @return {Box2} A reference to this bounding box. + */ union( box ) { this.min.min( box.min ); @@ -47682,6 +68138,13 @@ class Box2 { } + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 2D space. + * + * @param {Vector2} offset - The offset that should be used to translate the bounding box. + * @return {Box2} A reference to this bounding box. + */ translate( offset ) { this.min.add( offset ); @@ -47691,6 +68154,12 @@ class Box2 { } + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box2} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ equals( box ) { return box.min.equals( this.min ) && box.max.equals( this.max ); @@ -47702,15 +68171,48 @@ class Box2 { const _startP = /*@__PURE__*/ new Vector3(); const _startEnd = /*@__PURE__*/ new Vector3(); +const _d1 = /*@__PURE__*/ new Vector3(); +const _d2 = /*@__PURE__*/ new Vector3(); +const _r = /*@__PURE__*/ new Vector3(); +const _c1 = /*@__PURE__*/ new Vector3(); +const _c2 = /*@__PURE__*/ new Vector3(); + +/** + * An analytical line segment in 3D space represented by a start and end point. + */ class Line3 { + /** + * Constructs a new line segment. + * + * @param {Vector3} [start=(0,0,0)] - Start of the line segment. + * @param {Vector3} [end=(0,0,0)] - End of the line segment. + */ constructor( start = new Vector3(), end = new Vector3() ) { + /** + * Start of the line segment. + * + * @type {Vector3} + */ this.start = start; + + /** + * End of the line segment. + * + * @type {Vector3} + */ this.end = end; } + /** + * Sets the start and end values by copying the given vectors. + * + * @param {Vector3} start - The start point. + * @param {Vector3} end - The end point. + * @return {Line3} A reference to this line segment. + */ set( start, end ) { this.start.copy( start ); @@ -47720,6 +68222,12 @@ class Line3 { } + /** + * Copies the values of the given line segment to this instance. + * + * @param {Line3} line - The line segment to copy. + * @return {Line3} A reference to this line segment. + */ copy( line ) { this.start.copy( line.start ); @@ -47729,36 +68237,72 @@ class Line3 { } + /** + * Returns the center of the line segment. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ getCenter( target ) { return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); } + /** + * Returns the delta vector of the line segment's start and end point. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ delta( target ) { return target.subVectors( this.end, this.start ); } + /** + * Returns the squared Euclidean distance between the line' start and end point. + * + * @return {number} The squared Euclidean distance. + */ distanceSq() { return this.start.distanceToSquared( this.end ); } + /** + * Returns the Euclidean distance between the line' start and end point. + * + * @return {number} The Euclidean distance. + */ distance() { return this.start.distanceTo( this.end ); } + /** + * Returns a vector at a certain position along the line segment. + * + * @param {number} t - A value between `[0,1]` to represent a position along the line segment. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ at( t, target ) { return this.delta( target ).multiplyScalar( t ).add( this.start ); } + /** + * Returns a point parameter based on the closest point as projected on the line segment. + * + * @param {Vector3} point - The point for which to return a point parameter. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @return {number} The point parameter. + */ closestPointToPointParameter( point, clampToLine ) { _startP.subVectors( point, this.start ); @@ -47779,6 +68323,14 @@ class Line3 { } + /** + * Returns the closest point on the line for a given point. + * + * @param {Vector3} point - The point to compute the closest point on the line for. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the line. + */ closestPointToPoint( point, clampToLine, target ) { const t = this.closestPointToPointParameter( point, clampToLine ); @@ -47787,6 +68339,133 @@ class Line3 { } + /** + * Returns the closest squared distance between this line segment and the given one. + * + * @param {Line3} line - The line segment to compute the closest squared distance to. + * @param {Vector3} [c1] - The closest point on this line segment. + * @param {Vector3} [c2] - The closest point on the given line segment. + * @return {number} The squared distance between this line segment and the given one. + */ + distanceSqToLine3( line, c1 = _c1, c2 = _c2 ) { + + // from Real-Time Collision Detection by Christer Ericson, chapter 5.1.9 + + // Computes closest points C1 and C2 of S1(s)=P1+s*(Q1-P1) and + // S2(t)=P2+t*(Q2-P2), returning s and t. Function result is squared + // distance between between S1(s) and S2(t) + + const EPSILON = 1e-8 * 1e-8; // must be squared since we compare squared length + let s, t; + + const p1 = this.start; + const p2 = line.start; + const q1 = this.end; + const q2 = line.end; + + _d1.subVectors( q1, p1 ); // Direction vector of segment S1 + _d2.subVectors( q2, p2 ); // Direction vector of segment S2 + _r.subVectors( p1, p2 ); + + const a = _d1.dot( _d1 ); // Squared length of segment S1, always nonnegative + const e = _d2.dot( _d2 ); // Squared length of segment S2, always nonnegative + const f = _d2.dot( _r ); + + // Check if either or both segments degenerate into points + + if ( a <= EPSILON && e <= EPSILON ) { + + // Both segments degenerate into points + + c1.copy( p1 ); + c2.copy( p2 ); + + c1.sub( c2 ); + + return c1.dot( c1 ); + + } + + if ( a <= EPSILON ) { + + // First segment degenerates into a point + + s = 0; + t = f / e; // s = 0 => t = (b*s + f) / e = f / e + t = clamp( t, 0, 1 ); + + + } else { + + const c = _d1.dot( _r ); + + if ( e <= EPSILON ) { + + // Second segment degenerates into a point + + t = 0; + s = clamp( - c / a, 0, 1 ); // t = 0 => s = (b*t - c) / a = -c / a + + } else { + + // The general nondegenerate case starts here + + const b = _d1.dot( _d2 ); + const denom = a * e - b * b; // Always nonnegative + + // If segments not parallel, compute closest point on L1 to L2 and + // clamp to segment S1. Else pick arbitrary s (here 0) + + if ( denom !== 0 ) { + + s = clamp( ( b * f - c * e ) / denom, 0, 1 ); + + } else { + + s = 0; + + } + + // Compute point on L2 closest to S1(s) using + // t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e + + t = ( b * s + f ) / e; + + // If t in [0,1] done. Else clamp t, recompute s for the new value + // of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a + // and clamp s to [0, 1] + + if ( t < 0 ) { + + t = 0.; + s = clamp( - c / a, 0, 1 ); + + } else if ( t > 1 ) { + + t = 1; + s = clamp( ( b - c ) / a, 0, 1 ); + + } + + } + + } + + c1.copy( p1 ).add( _d1.multiplyScalar( s ) ); + c2.copy( p2 ).add( _d2.multiplyScalar( t ) ); + + c1.sub( c2 ); + + return c1.dot( c1 ); + + } + + /** + * Applies a 4x4 transformation matrix to this line segment. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Line3} A reference to this line segment. + */ applyMatrix4( matrix ) { this.start.applyMatrix4( matrix ); @@ -47796,12 +68475,23 @@ class Line3 { } + /** + * Returns `true` if this line segment is equal with the given one. + * + * @param {Line3} line - The line segment to test for equality. + * @return {boolean} Whether this line segment is equal with the given one. + */ equals( line ) { return line.start.equals( this.start ) && line.end.equals( this.end ); } + /** + * Returns a new line segment with copied values from this instance. + * + * @return {Line3} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -47810,13 +68500,31 @@ class Line3 { } +/** + * A helper object to visualize an instance of {@link Plane}. + * + * ```js + * const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); + * const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); + * scene.add( helper ); + * ``` + * + * @augments Line + */ class PlaneHelper extends Line { + /** + * Constructs a new plane helper. + * + * @param {Plane} plane - The plane to be visualized. + * @param {number} [size=1] - The side length of plane helper. + * @param {number|Color|string} [hex=0xffff00] - The helper's color. + */ constructor( plane, size = 1, hex = 0xffff00 ) { const color = hex; - const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + const positions = [ 1, -1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0 ]; const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); @@ -47826,11 +68534,22 @@ class PlaneHelper extends Line { this.type = 'PlaneHelper'; + /** + * The plane being visualized. + * + * @type {Plane} + */ this.plane = plane; + /** + * The side length of plane helper. + * + * @type {number} + * @default 1 + */ this.size = size; - const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + const positions2 = [ 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0 ]; const geometry2 = new BufferGeometry(); geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); @@ -47854,6 +68573,10 @@ class PlaneHelper extends Line { } + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ dispose() { this.geometry.dispose(); @@ -47865,19 +68588,51 @@ class PlaneHelper extends Line { } +/** + * This class is used to convert a series of paths to an array of + * shapes. It is specifically used in context of fonts and SVG. + */ class ShapePath { + /** + * Constructs a new shape path. + */ constructor() { this.type = 'ShapePath'; + /** + * The color of the shape. + * + * @type {Color} + */ this.color = new Color(); + /** + * The paths that have been generated for this shape. + * + * @type {Array<Path>} + * @default null + */ this.subPaths = []; + + /** + * The current path that is being generated. + * + * @type {?Path} + * @default null + */ this.currentPath = null; } + /** + * Creates a new path and moves it current point to the given one. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {ShapePath} A reference to this shape path. + */ moveTo( x, y ) { this.currentPath = new Path(); @@ -47888,6 +68643,14 @@ class ShapePath { } + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ lineTo( x, y ) { this.currentPath.lineTo( x, y ); @@ -47896,6 +68659,16 @@ class ShapePath { } + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ quadraticCurveTo( aCPx, aCPy, aX, aY ) { this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); @@ -47904,6 +68677,18 @@ class ShapePath { } + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); @@ -47912,6 +68697,13 @@ class ShapePath { } + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array<Vector2>} pts - An array of points in 2D space. + * @return {ShapePath} A reference to this shape path. + */ splineThru( pts ) { this.currentPath.splineThru( pts ); @@ -47920,6 +68712,13 @@ class ShapePath { } + /** + * Converts the paths into an array of shapes. + * + * @param {boolean} isCCW - By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). + * If this flag is set to `true`, then those are flipped. + * @return {Array<Shape>} An array of shapes. + */ toShapes( isCCW ) { function toShapesNoHoles( inSubpaths ) { @@ -48149,6 +68948,122 @@ class ShapePath { } +/** + * Abstract base class for controls. + * + * @abstract + * @augments EventDispatcher + */ +class Controls extends EventDispatcher { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super(); + + /** + * The object that is managed by the controls. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The HTML element used for event listeners. + * + * @type {?HTMLDOMElement} + * @default null + */ + this.domElement = domElement; + + /** + * Whether the controls responds to user input or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * The internal state of the controls. + * + * @type {number} + * @default -1 + */ + this.state = -1; + + /** + * This object defines the keyboard input of the controls. + * + * @type {Object} + */ + this.keys = {}; + + /** + * This object defines what type of actions are assigned to the available mouse buttons. + * It depends on the control implementation what kind of mouse buttons and actions are supported. + * + * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}} + */ + this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null }; + + /** + * This object defines what type of actions are assigned to what kind of touch interaction. + * It depends on the control implementation what kind of touch interaction and actions are supported. + * + * @type {{ONE: ?number, TWO: ?number}} + */ + this.touches = { ONE: null, TWO: null }; + + } + + /** + * Connects the controls to the DOM. This method has so called "side effects" since + * it adds the module's event listeners to the DOM. + * + * @param {HTMLDOMElement} element - The DOM element to connect to. + */ + connect( element ) { + + if ( element === undefined ) { + + console.warn( 'THREE.Controls: connect() now requires an element.' ); // @deprecated, the warning can be removed with r185 + return; + + } + + if ( this.domElement !== null ) this.disconnect(); + + this.domElement = element; + + } + + /** + * Disconnects the controls from the DOM. + */ + disconnect() {} + + /** + * Call this method if you no longer want use to the controls. It frees all internal + * resources and removes all event listeners. + */ + dispose() {} + + /** + * Controls should implement this method if they have to update their internal state + * per simulation step. + * + * @param {number} [delta] - The time delta in seconds. + */ + update( /* delta */ ) {} + +} + // export * from './Three.Legacy.js'; if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { @@ -48175,4 +69090,4 @@ if ( typeof window !== 'undefined' ) { } -export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, ArcCurve, ArrayCamera, AttachedBindMode, AudioLoader, BackSide, BasicDepthPacking, BasicShadowMap, Box2, Box3, BoxGeometry, BufferAttribute, BufferGeometry, BufferGeometryLoader, ByteType, Cache, Camera, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CineonToneMapping, CircleGeometry, ClampToEdgeWrapping, Clock, Color, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTextureLoader, CubeUVReflectionMapping, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceBack, CullFaceFront, CullFaceFrontBack, CullFaceNone, Curve, CurvePath, CustomBlending, CustomToneMapping, CylinderGeometry, Cylindrical, DataTexture, DataTextureLoader, DataUtils$1 as DataUtils, DecrementStencilOp, DecrementWrapStencilOp, DefaultLoadingManager, DepthFormat, DepthStencilFormat, DepthTexture, DetachedBindMode, DirectionalLight, DiscreteInterpolant, DisplayP3ColorSpace, DodecahedronGeometry, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicCopyUsage, DynamicDrawUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExtrudeGeometry, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, Fog, FrontSide, Frustum, GLBufferAttribute, GLSL1, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, Group, HalfFloatType, HemisphereLight, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, IncrementStencilOp, IncrementWrapStencilOp, InstancedBufferAttribute, InstancedBufferGeometry, InstancedInterleavedBuffer, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, InterleavedBuffer, InterleavedBufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InvertStencilOp, KeepStencilOp, LatheGeometry, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, Light, Line, Line3, LineBasicMaterial, LineCurve, LineCurve3, LineDashedMaterial, LineLoop, LineSegments, LinearDisplayP3ColorSpace, LinearFilter, LinearInterpolant, LinearMipMapLinearFilter, LinearMipMapNearestFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, LuminanceAlphaFormat, LuminanceFormat, MOUSE, Material, MaterialLoader, MathUtils$1 as MathUtils, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, NormalAnimationBlendMode, NormalBlending, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, Object3D, ObjectLoader, ObjectSpaceNormalMap, OctahedronGeometry, OneFactor, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, P3Primaries, PCFShadowMap, PCFSoftShadowMap, Path, PerspectiveCamera, Plane, PlaneGeometry, PlaneHelper, PointLight, Points, PointsMaterial, PolyhedronGeometry, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RawShaderMaterial, Ray, Raycaster, Rec709Primaries, RectAreaLight, RedFormat, RedIntegerFormat, ReinhardToneMapping, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RingGeometry, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, ShortType, Sphere, SphereGeometry, Spherical, SplineCurve, SpotLight, SpriteMaterial, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StaticCopyUsage, StaticDrawUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, SubtractEquation, SubtractiveBlending, TOUCH, TangentSpaceNormalMap, TetrahedronGeometry, Texture, TextureLoader, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsUtils, UnsignedByteType, UnsignedInt248Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, WebGLCoordinateSystem, WebGLRenderTarget, WebGLRenderer, WebGPUCoordinateSystem, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroFactor, ZeroSlopeEnding, ZeroStencilOp, _SRGBAFormat }; +export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, ArcCurve, ArrayCamera, AttachedBindMode, AudioLoader, BackSide, BasicDepthPacking, BasicShadowMap, Box2, Box3, BoxGeometry, BufferAttribute, BufferGeometry, BufferGeometryLoader, ByteType, Cache, Camera, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CineonToneMapping, CircleGeometry, ClampToEdgeWrapping, Clock, Color, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, Controls, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTextureLoader, CubeUVReflectionMapping, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceBack, CullFaceFront, CullFaceFrontBack, CullFaceNone, Curve, CurvePath, CustomBlending, CustomToneMapping, CylinderGeometry, Cylindrical, DataTexture, DataTextureLoader, DataUtils$1 as DataUtils, DecrementStencilOp, DecrementWrapStencilOp, DefaultLoadingManager, DepthFormat, DepthStencilFormat, DepthTexture, DetachedBindMode, DirectionalLight, DiscreteInterpolant, DodecahedronGeometry, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicCopyUsage, DynamicDrawUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExtrudeGeometry, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, Fog, FrontSide, Frustum, GLBufferAttribute, GLSL1, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, Group, HalfFloatType, HemisphereLight, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, IncrementStencilOp, IncrementWrapStencilOp, InstancedBufferAttribute, InstancedBufferGeometry, InstancedInterleavedBuffer, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, InterleavedBuffer, InterleavedBufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InterpolationSamplingMode, InterpolationSamplingType, InvertStencilOp, KeepStencilOp, LatheGeometry, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, Light, Line, Line3, LineBasicMaterial, LineCurve, LineCurve3, LineDashedMaterial, LineLoop, LineSegments, LinearFilter, LinearInterpolant, LinearMipMapLinearFilter, LinearMipMapNearestFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, MOUSE, Material, MaterialLoader, MathUtils$1 as MathUtils, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, NormalAnimationBlendMode, NormalBlending, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, Object3D, ObjectLoader, ObjectSpaceNormalMap, OctahedronGeometry, OneFactor, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, PCFShadowMap, PCFSoftShadowMap, Path, PerspectiveCamera, Plane, PlaneGeometry, PlaneHelper, PointLight, Points, PointsMaterial, PolyhedronGeometry, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBDepthPacking, RGBFormat, RGBIntegerFormat, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGDepthPacking, RGFormat, RGIntegerFormat, RawShaderMaterial, Ray, Raycaster, RectAreaLight, RedFormat, RedIntegerFormat, ReinhardToneMapping, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RingGeometry, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, ShortType, Sphere, SphereGeometry, Spherical, SplineCurve, SpotLight, SpriteMaterial, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StaticCopyUsage, StaticDrawUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, SubtractEquation, SubtractiveBlending, TOUCH, TangentSpaceNormalMap, TetrahedronGeometry, Texture, TextureLoader, TimestampQuery, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsUtils, UnsignedByteType, UnsignedInt101111Type, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, WebGLCoordinateSystem, WebGLRenderTarget, WebGLRenderer, WebGPUCoordinateSystem, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroFactor, ZeroSlopeEnding, ZeroStencilOp }; diff --git a/modules/three_addons.mjs b/modules/three_addons.mjs index 81a1ae140..0ecb16ee0 100644 --- a/modules/three_addons.mjs +++ b/modules/three_addons.mjs @@ -1,30 +1,41 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -import { ExtrudeGeometry, ShapePath, Ray, Plane, MathUtils, EventDispatcher, Vector3, MOUSE, TOUCH, Quaternion, Spherical, Vector2, OrthographicCamera, BufferGeometry, Float32BufferAttribute, Mesh, ShaderMaterial, UniformsUtils, WebGLRenderTarget, HalfFloatType, NoBlending, Clock, Color, AdditiveBlending, MeshBasicMaterial, Vector4, Box3, Matrix4, Frustum, Matrix3, DoubleSide, Box2, SRGBColorSpace, Camera } from './three.mjs'; +import { ExtrudeGeometry, ShapePath, Ray, Plane, MathUtils, Vector3, Controls, MOUSE, TOUCH, Quaternion, Spherical, Vector2, OrthographicCamera, BufferGeometry, Float32BufferAttribute, Mesh, ShaderMaterial, UniformsUtils, WebGLRenderTarget, HalfFloatType, NoBlending, Clock, Color, AdditiveBlending, MeshBasicMaterial, Vector4, Box3, Matrix4, Frustum, Matrix3, DoubleSide, Box2, SRGBColorSpace, Camera } from './three.mjs'; /** - * Text = 3D Text + * A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of + * parameters consisting of a loaded font and extrude settings. * - * parameters = { - * font: <THREE.Font>, // font + * See the {@link FontLoader} page for additional details. * - * size: <float>, // size of the text - * height: <float>, // thickness to extrude text - * curveSegments: <int>, // number of points on the curves + * `TextGeometry` uses [typeface.json]{@link http://gero3.github.io/facetype.js/} generated fonts. + * Some existing fonts can be found located in `/examples/fonts`. * - * bevelEnabled: <bool>, // turn on bevel - * bevelThickness: <float>, // how deep into text bevel goes - * bevelSize: <float>, // how far from text outline (including bevelOffset) is bevel - * bevelOffset: <float> // how far from text outline does bevel start - * } + * ```js + * const loader = new FontLoader(); + * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' ); + * const geometry = new TextGeometry( 'Hello three.js!', { + * font: font, + * size: 80, + * depth: 5, + * curveSegments: 12 + * } ); + * ``` + * + * @augments ExtrudeGeometry + * @three_import import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; */ - - class TextGeometry extends ExtrudeGeometry { + /** + * Constructs a new text geometry. + * + * @param {string} text - The text that should be transformed into a geometry. + * @param {TextGeometry~Options} [parameters] - The text settings. + */ constructor( text, parameters = {} ) { const font = parameters.font; @@ -37,12 +48,9 @@ class TextGeometry extends ExtrudeGeometry { const shapes = font.generateShapes( text, parameters.size ); - // translate parameters to ExtrudeGeometry API - - parameters.depth = parameters.height !== undefined ? parameters.height : 50; - // defaults + if ( parameters.depth === undefined ) parameters.depth = 50; if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; @@ -57,20 +65,46 @@ class TextGeometry extends ExtrudeGeometry { } -// - +/** + * Class representing a font. + */ class Font { + /** + * Constructs a new font. + * + * @param {Object} data - The font data as JSON. + */ constructor( data ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isFont = true; this.type = 'Font'; + /** + * The font data as JSON. + * + * @type {Object} + */ this.data = data; } + /** + * Generates geometry shapes from the given text and size. The result of this method + * should be used with {@link ShapeGeometry} to generate the actual geometry data. + * + * @param {string} text - The text. + * @param {number} [size=100] - The text size. + * @return {Array<Shape>} An array of shapes representing the text. + */ generateShapes( text, size = 100 ) { const shapes = []; @@ -199,1534 +233,1863 @@ function createPath( char, scale, offsetX, offsetY, data ) { } -// OrbitControls performs orbiting, dollying (zooming), and panning. -// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). -// -// Orbit - left mouse / touch: one-finger move -// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish -// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move - +/** + * Fires when the camera has been transformed by the controls. + * + * @event OrbitControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; + +/** + * Fires when an interaction was initiated. + * + * @event OrbitControls#start + * @type {Object} + */ const _startEvent = { type: 'start' }; + +/** + * Fires when an interaction has finished. + * + * @event OrbitControls#end + * @type {Object} + */ const _endEvent = { type: 'end' }; + const _ray = new Ray(); const _plane = new Plane(); -const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); - -class OrbitControls extends EventDispatcher { - - constructor( object, domElement ) { - - super(); - - this.object = object; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll +const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); + +const _v = new Vector3(); +const _twoPI = 2 * Math.PI; + +const _STATE = { + NONE: -1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 +}; +const _EPS = 0.000001; - // Set to false to disable this control - this.enabled = true; - // "target" sets the location of focus, where the object orbits around +/** + * Orbit controls allow the camera to orbit around a target. + * + * OrbitControls performs orbiting, dollying (zooming), and panning. Unlike {@link TrackballControls}, + * it maintains the "up" direction `object.up` (+Y by default). + * + * - Orbit: Left mouse / touch: one-finger move. + * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. + * - Pan: Right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move. + * + * ```js + * const controls = new OrbitControls( camera, renderer.domElement ); + * + * // controls.update() must be called after any manual changes to the camera's transform + * camera.position.set( 0, 20, 100 ); + * controls.update(); + * + * function animate() { + * + * // required if controls.enableDamping or controls.autoRotate are set to true + * controls.update(); + * + * renderer.render( scene, camera ); + * + * } + * ``` + * + * @augments Controls + * @three_import import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + */ +class OrbitControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super( object, domElement ); + + this.state = _STATE.NONE; + + /** + * The focus point of the controls, the `object` orbits around this. + * It can be updated manually at any point to change the focus of the controls. + * + * @type {Vector3} + */ this.target = new Vector3(); - // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect + /** + * The focus point of the `minTargetRadius` and `maxTargetRadius` limits. + * It can be updated manually at any point to change the center of interest + * for the `target`. + * + * @type {Vector3} + */ this.cursor = new Vector3(); - // How far you can dolly in and out ( PerspectiveCamera only ) + /** + * How far you can dolly in (perspective camera only). + * + * @type {number} + * @default 0 + */ this.minDistance = 0; + + /** + * How far you can dolly out (perspective camera only). + * + * @type {number} + * @default Infinity + */ this.maxDistance = Infinity; - // How far you can zoom in and out ( OrthographicCamera only ) + /** + * How far you can zoom in (orthographic camera only). + * + * @type {number} + * @default 0 + */ this.minZoom = 0; + + /** + * How far you can zoom out (orthographic camera only). + * + * @type {number} + * @default Infinity + */ this.maxZoom = Infinity; - // Limit camera target within a spherical area around the cursor + /** + * How close you can get the target to the 3D `cursor`. + * + * @type {number} + * @default 0 + */ this.minTargetRadius = 0; - this.maxTargetRadius = Infinity; - - // How far you can orbit vertically, upper and lower limits. - // Range is 0 to Math.PI radians. - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - // How far you can orbit horizontally, upper and lower limits. - // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians + /** + * How far you can move the target from the 3D `cursor`. + * + * @type {number} + * @default Infinity + */ + this.maxTargetRadius = Infinity; - // Set to true to enable damping (inertia) - // If damping is enabled, you must call controls.update() in your animation loop + /** + * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default 0 + */ + this.minPolarAngle = 0; + + /** + * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default Math.PI + */ + this.maxPolarAngle = Math.PI; + + /** + * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.minAzimuthAngle = - Infinity; + + /** + * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.maxAzimuthAngle = Infinity; + + /** + * Set to `true` to enable damping (inertia), which can be used to give a sense of weight + * to the controls. Note that if this is enabled, you must call `update()` in your animation + * loop. + * + * @type {boolean} + * @default false + */ this.enableDamping = false; + + /** + * The damping inertia used if `enableDamping` is set to `true`. + * + * Note that for this to work, you must call `update()` in your animation loop. + * + * @type {number} + * @default 0.05 + */ this.dampingFactor = 0.05; - // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. - // Set to false to disable zooming + /** + * Enable or disable zooming (dollying) of the camera. + * + * @type {boolean} + * @default true + */ this.enableZoom = true; + + /** + * Speed of zooming / dollying. + * + * @type {number} + * @default 1 + */ this.zoomSpeed = 1.0; - // Set to false to disable rotating + /** + * Enable or disable horizontal and vertical rotation of the camera. + * + * Note that it is possible to disable a single axis by setting the min and max of the + * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical + * or horizontal rotation to be fixed at that value. + * + * @type {boolean} + * @default true + */ this.enableRotate = true; + + /** + * Speed of rotation. + * + * @type {number} + * @default 1 + */ this.rotateSpeed = 1.0; - // Set to false to disable panning + /** + * How fast to rotate the camera when the keyboard is used. + * + * @type {number} + * @default 1 + */ + this.keyRotateSpeed = 1.0; + + /** + * Enable or disable camera panning. + * + * @type {boolean} + * @default true + */ this.enablePan = true; + + /** + * Speed of panning. + * + * @type {number} + * @default 1 + */ this.panSpeed = 1.0; - this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up - this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + /** + * Defines how the camera's position is translated when panning. If `true`, the camera pans + * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up + * direction. + * + * @type {boolean} + * @default true + */ + this.screenSpacePanning = true; + + /** + * How fast to pan the camera when the keyboard is used in + * pixels per keypress. + * + * @type {number} + * @default 7 + */ + this.keyPanSpeed = 7.0; + + /** + * Setting this property to `true` allows to zoom to the cursor's position. + * + * @type {boolean} + * @default false + */ this.zoomToCursor = false; - // Set to true to automatically rotate around the target - // If auto-rotate is enabled, you must call controls.update() in your animation loop + /** + * Set to true to automatically rotate around the target + * + * Note that if this is enabled, you must call `update()` in your animation loop. + * If you want the auto-rotate speed to be independent of the frame rate (the refresh + * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`. + * + * @type {boolean} + * @default false + */ this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 - // The four arrow keys + /** + * How fast to rotate around the target if `autoRotate` is `true`. The default equates to 30 seconds + * per orbit at 60fps. + * + * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop. + * + * @type {number} + * @default 2 + */ + this.autoRotateSpeed = 2.0; + + /** + * This object contains references to the keycodes for controlling camera panning. + * + * ```js + * controls.keys = { + * LEFT: 'ArrowLeft', //left arrow + * UP: 'ArrowUp', // up arrow + * RIGHT: 'ArrowRight', // right arrow + * BOTTOM: 'ArrowDown' // down arrow + * } + * ``` + * @type {Object} + */ this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - // Mouse buttons + /** + * This object contains references to the mouse actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * LEFT: THREE.MOUSE.ROTATE, + * MIDDLE: THREE.MOUSE.DOLLY, + * RIGHT: THREE.MOUSE.PAN + * } + * ``` + * @type {Object} + */ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - // Touch fingers + /** + * This object contains references to the touch actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * ONE: THREE.TOUCH.ROTATE, + * TWO: THREE.TOUCH.DOLLY_PAN + * } + * ``` + * @type {Object} + */ this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - // for reset + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ this.target0 = this.target.clone(); + + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ this.position0 = this.object.position.clone(); + + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {number} + */ this.zoom0 = this.object.zoom; // the target DOM element for key events this._domElementKeyEvents = null; - // - // public methods - // + // internals - this.getPolarAngle = function () { + this._lastPosition = new Vector3(); + this._lastQuaternion = new Quaternion(); + this._lastTargetPosition = new Vector3(); - return spherical.phi; + // so camera.up is the orbit axis + this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + this._quatInverse = this._quat.clone().invert(); - }; + // current position in spherical coordinates + this._spherical = new Spherical(); + this._sphericalDelta = new Spherical(); - this.getAzimuthalAngle = function () { + this._scale = 1; + this._panOffset = new Vector3(); - return spherical.theta; + this._rotateStart = new Vector2(); + this._rotateEnd = new Vector2(); + this._rotateDelta = new Vector2(); - }; + this._panStart = new Vector2(); + this._panEnd = new Vector2(); + this._panDelta = new Vector2(); - this.getDistance = function () { + this._dollyStart = new Vector2(); + this._dollyEnd = new Vector2(); + this._dollyDelta = new Vector2(); - return this.object.position.distanceTo( this.target ); + this._dollyDirection = new Vector3(); + this._mouse = new Vector2(); + this._performCursorZoom = false; - }; + this._pointers = []; + this._pointerPositions = {}; - this.listenToKeyEvents = function ( domElement ) { + this._controlActive = false; - domElement.addEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = domElement; + // event listeners - }; + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); + this._onMouseWheel = onMouseWheel.bind( this ); + this._onKeyDown = onKeyDown.bind( this ); - this.stopListenToKeyEvents = function () { + this._onTouchStart = onTouchStart.bind( this ); + this._onTouchMove = onTouchMove.bind( this ); - this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = null; + this._onMouseDown = onMouseDown.bind( this ); + this._onMouseMove = onMouseMove.bind( this ); - }; + this._interceptControlDown = interceptControlDown.bind( this ); + this._interceptControlUp = interceptControlUp.bind( this ); - this.saveState = function () { + // - scope.target0.copy( scope.target ); - scope.position0.copy( scope.object.position ); - scope.zoom0 = scope.object.zoom; + if ( this.domElement !== null ) { - }; + this.connect( this.domElement ); - this.reset = function () { + } - scope.target.copy( scope.target0 ); - scope.object.position.copy( scope.position0 ); - scope.object.zoom = scope.zoom0; + this.update(); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( _changeEvent ); + } - scope.update(); + connect( element ) { - state = STATE.NONE; + super.connect( element ); - }; + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this._onPointerUp ); - this.resetOrthoPanZoom = function () { - panOffset.set(0,0,0); - scope.object.zoom = 1; - scope.object.updateProjectionMatrix(); - }; + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); + this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); - // this method is exposed, but perhaps it would be better if we can make it private... - this.update = function () { + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } ); - const offset = new Vector3(); + this.domElement.style.touchAction = 'none'; // disable touch scroll - // so camera.up is the orbit axis - const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - const quatInverse = quat.clone().invert(); + } - const lastPosition = new Vector3(); - const lastQuaternion = new Quaternion(); - const lastTargetPosition = new Vector3(); + disconnect() { - const twoPI = 2 * Math.PI; + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerUp ); - return function update( deltaTime = null ) { + this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - const position = scope.object.position; + this.stopListenToKeyEvents(); - offset.copy( position ).sub( scope.target ); + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } ); - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); + this.domElement.style.touchAction = 'auto'; - // angle from z-axis around y-axis - spherical.setFromVector3( offset ); + } - if ( scope.autoRotate && state === STATE.NONE ) { + dispose() { - rotateLeft( getAutoRotationAngle( deltaTime ) ); + this.disconnect(); - } + } - if ( scope.enableDamping ) { + /** + * Get the current vertical rotation, in radians. + * + * @return {number} The current vertical rotation, in radians. + */ + getPolarAngle() { - spherical.theta += sphericalDelta.theta * scope.dampingFactor; - spherical.phi += sphericalDelta.phi * scope.dampingFactor; + return this._spherical.phi; - } else { + } - spherical.theta += sphericalDelta.theta; - spherical.phi += sphericalDelta.phi; + /** + * Get the current horizontal rotation, in radians. + * + * @return {number} The current horizontal rotation, in radians. + */ + getAzimuthalAngle() { - } + return this._spherical.theta; - // restrict theta to be between desired limits + } - let min = scope.minAzimuthAngle; - let max = scope.maxAzimuthAngle; + /** + * Returns the distance from the camera to the target. + * + * @return {number} The distance from the camera to the target. + */ + getDistance() { - if ( isFinite( min ) && isFinite( max ) ) { + return this.object.position.distanceTo( this.target ); - if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + } - if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + /** + * Adds key event listeners to the given DOM element. + * `window` is a recommended argument for using this method. + * + * @param {HTMLDOMElement} domElement - The DOM element + */ + listenToKeyEvents( domElement ) { - if ( min <= max ) { + domElement.addEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = domElement; - spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + } - } else { + /** + * Removes the key event listener previously defined with `listenToKeyEvents()`. + */ + stopListenToKeyEvents() { - spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, spherical.theta ) : - Math.min( max, spherical.theta ); + if ( this._domElementKeyEvents !== null ) { - } + this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = null; - } + } - // restrict phi to be between desired limits - spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + } - spherical.makeSafe(); + /** + * Save the current state of the controls. This can later be recovered with `reset()`. + */ + saveState() { + this.target0.copy( this.target ); + this.position0.copy( this.object.position ); + this.zoom0 = this.object.zoom; - // move target to panned location + } - if ( scope.enableDamping === true ) { + /** + * Reset the controls to their state from either the last time the `saveState()` + * was called, or the initial state. + */ + reset() { - scope.target.addScaledVector( panOffset, scope.dampingFactor ); + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + this.object.zoom = this.zoom0; - } else { + this.object.updateProjectionMatrix(); + this.dispatchEvent( _changeEvent ); - scope.target.add( panOffset ); + this.update(); - } + this.state = _STATE.NONE; - // Limit the target distance from the cursor to create a sphere around the center of interest - scope.target.sub( scope.cursor ); - scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius ); - scope.target.add( scope.cursor ); + } - let zoomChanged = false; - // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera - // we adjust zoom later in these cases - if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) { + update( deltaTime = null ) { - spherical.radius = clampDistance( spherical.radius ); + const position = this.object.position; - } else { + _v.copy( position ).sub( this.target ); - const prevRadius = spherical.radius; - spherical.radius = clampDistance( spherical.radius * scale ); - zoomChanged = prevRadius != spherical.radius; + // rotate offset to "y-axis-is-up" space + _v.applyQuaternion( this._quat ); - } + // angle from z-axis around y-axis + this._spherical.setFromVector3( _v ); - offset.setFromSpherical( spherical ); + if ( this.autoRotate && this.state === _STATE.NONE ) { - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); + this._rotateLeft( this._getAutoRotationAngle( deltaTime ) ); - position.copy( scope.target ).add( offset ); + } - scope.object.lookAt( scope.target ); + if ( this.enableDamping ) { - if ( scope.enableDamping === true ) { + this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor; + this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor; - sphericalDelta.theta *= ( 1 - scope.dampingFactor ); - sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + } else { - panOffset.multiplyScalar( 1 - scope.dampingFactor ); + this._spherical.theta += this._sphericalDelta.theta; + this._spherical.phi += this._sphericalDelta.phi; - } else { + } - sphericalDelta.set( 0, 0, 0 ); + // restrict theta to be between desired limits - panOffset.set( 0, 0, 0 ); + let min = this.minAzimuthAngle; + let max = this.maxAzimuthAngle; - } + if ( isFinite( min ) && isFinite( max ) ) { - // adjust camera position - if ( scope.zoomToCursor && performCursorZoom ) { + if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI; - let newRadius = null; - if ( scope.object.isPerspectiveCamera ) { + if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; - // move the camera down the pointer ray - // this method avoids floating point error - const prevRadius = offset.length(); - newRadius = clampDistance( prevRadius * scale ); + if ( min <= max ) { - const radiusDelta = prevRadius - newRadius; - scope.object.position.addScaledVector( dollyDirection, radiusDelta ); - scope.object.updateMatrixWorld(); + this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); - zoomChanged = !! radiusDelta; + } else { - } else if ( scope.object.isOrthographicCamera ) { + this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, this._spherical.theta ) : + Math.min( max, this._spherical.theta ); - // adjust the ortho camera position based on zoom changes - const mouseBefore = new Vector3( mouse.x, mouse.y, 0 ); - mouseBefore.unproject( scope.object ); + } - const prevZoom = scope.object.zoom; - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); - scope.object.updateProjectionMatrix(); + } - zoomChanged = prevZoom !== scope.object.zoom; + // restrict phi to be between desired limits + this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); - const mouseAfter = new Vector3( mouse.x, mouse.y, 0 ); - mouseAfter.unproject( scope.object ); + this._spherical.makeSafe(); - scope.object.position.sub( mouseAfter ).add( mouseBefore ); - scope.object.updateMatrixWorld(); - newRadius = offset.length(); + // move target to panned location - } else { + if ( this.enableDamping === true ) { - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); - scope.zoomToCursor = false; + this.target.addScaledVector( this._panOffset, this.dampingFactor ); - } + } else { - // handle the placement of the target - if ( newRadius !== null ) { + this.target.add( this._panOffset ); - if ( this.screenSpacePanning ) { + } - // position the orbit target in front of the new camera position - scope.target.set( 0, 0, - 1 ) - .transformDirection( scope.object.matrix ) - .multiplyScalar( newRadius ) - .add( scope.object.position ); + // Limit the target distance from the cursor to create a sphere around the center of interest + this.target.sub( this.cursor ); + this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); + this.target.add( this.cursor ); - } else { + let zoomChanged = false; + // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera + // we adjust zoom later in these cases + if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { - // get the ray and translation plane to compute target - _ray.origin.copy( scope.object.position ); - _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix ); + this._spherical.radius = this._clampDistance( this._spherical.radius ); - // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid - // extremely large values - if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) { + } else { - object.lookAt( scope.target ); + const prevRadius = this._spherical.radius; + this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); + zoomChanged = prevRadius != this._spherical.radius; - } else { + } - _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target ); - _ray.intersectPlane( _plane, scope.target ); + _v.setFromSpherical( this._spherical ); - } + // rotate offset back to "camera-up-vector-is-up" space + _v.applyQuaternion( this._quatInverse ); - } + position.copy( this.target ).add( _v ); - } + this.object.lookAt( this.target ); - } else if ( scope.object.isOrthographicCamera ) { + if ( this.enableDamping === true ) { - const prevZoom = scope.object.zoom; - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); + this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); + this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); - if ( prevZoom !== scope.object.zoom ) { + this._panOffset.multiplyScalar( 1 - this.dampingFactor ); - scope.object.updateProjectionMatrix(); - zoomChanged = true; + } else { - } + this._sphericalDelta.set( 0, 0, 0 ); - } + this._panOffset.set( 0, 0, 0 ); - scale = 1; - performCursorZoom = false; + } - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + // adjust camera position + if ( this.zoomToCursor && this._performCursorZoom ) { - if ( zoomChanged || - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS || - lastTargetPosition.distanceToSquared( scope.target ) > EPS ) { + let newRadius = null; + if ( this.object.isPerspectiveCamera ) { - scope.dispatchEvent( _changeEvent ); + // move the camera down the pointer ray + // this method avoids floating point error + const prevRadius = _v.length(); + newRadius = this._clampDistance( prevRadius * this._scale ); - lastPosition.copy( scope.object.position ); - lastQuaternion.copy( scope.object.quaternion ); - lastTargetPosition.copy( scope.target ); + const radiusDelta = prevRadius - newRadius; + this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); + this.object.updateMatrixWorld(); - return true; + zoomChanged = !! radiusDelta; - } + } else if ( this.object.isOrthographicCamera ) { - return false; + // adjust the ortho camera position based on zoom changes + const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseBefore.unproject( this.object ); - }; + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); + this.object.updateProjectionMatrix(); - }(); + zoomChanged = prevZoom !== this.object.zoom; - this.dispose = function () { + const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseAfter.unproject( this.object ); - scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + this.object.position.sub( mouseAfter ).add( mouseBefore ); + this.object.updateMatrixWorld(); - scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); - scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + newRadius = _v.length(); - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + } else { - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); + this.zoomToCursor = false; - document.removeEventListener( 'keydown', interceptControlDown, { capture: true } ); + } - if ( scope._domElementKeyEvents !== null ) { + // handle the placement of the target + if ( newRadius !== null ) { - scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - scope._domElementKeyEvents = null; + if ( this.screenSpacePanning ) { - } + // position the orbit target in front of the new camera position + this.target.set( 0, 0, -1 ) + .transformDirection( this.object.matrix ) + .multiplyScalar( newRadius ) + .add( this.object.position ); - //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + } else { - }; + // get the ray and translation plane to compute target + _ray.origin.copy( this.object.position ); + _ray.direction.set( 0, 0, -1 ).transformDirection( this.object.matrix ); - // - // internals - // + // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid + // extremely large values + if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { - const scope = this; - - const STATE = { - NONE: - 1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 - }; + this.object.lookAt( this.target ); - let state = STATE.NONE; + } else { - const EPS = 0.000001; + _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); + _ray.intersectPlane( _plane, this.target ); - // current position in spherical coordinates - const spherical = new Spherical(); - const sphericalDelta = new Spherical(); + } - let scale = 1; - const panOffset = new Vector3(); + } - const rotateStart = new Vector2(); - const rotateEnd = new Vector2(); - const rotateDelta = new Vector2(); + } - const panStart = new Vector2(); - const panEnd = new Vector2(); - const panDelta = new Vector2(); + } else if ( this.object.isOrthographicCamera ) { - const dollyStart = new Vector2(); - const dollyEnd = new Vector2(); - const dollyDelta = new Vector2(); + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - const dollyDirection = new Vector3(); - const mouse = new Vector2(); - let performCursorZoom = false; + if ( prevZoom !== this.object.zoom ) { - const pointers = []; - const pointerPositions = {}; + this.object.updateProjectionMatrix(); + zoomChanged = true; - let controlActive = false; + } - function getAutoRotationAngle( deltaTime ) { + } - if ( deltaTime !== null ) { + this._scale = 1; + this._performCursorZoom = false; - return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime; + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - } else { + if ( zoomChanged || + this._lastPosition.distanceToSquared( this.object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || + this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + this.dispatchEvent( _changeEvent ); - } + this._lastPosition.copy( this.object.position ); + this._lastQuaternion.copy( this.object.quaternion ); + this._lastTargetPosition.copy( this.target ); - } + return true; - function getZoomScale( delta ) { + } - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); + return false; - } + } - function rotateLeft( angle ) { + _getAutoRotationAngle( deltaTime ) { - sphericalDelta.theta -= angle; + if ( deltaTime !== null ) { - } + return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; - function rotateUp( angle ) { + } else { - sphericalDelta.phi -= angle; + return _twoPI / 60 / 60 * this.autoRotateSpeed; } - const panLeft = function () { + } - const v = new Vector3(); + _getZoomScale( delta ) { - return function panLeft( distance, objectMatrix ) { + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); - v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - v.multiplyScalar( - distance ); + } - panOffset.add( v ); + _rotateLeft( angle ) { - }; + this._sphericalDelta.theta -= angle; - }(); + } - const panUp = function () { + _rotateUp( angle ) { - const v = new Vector3(); + this._sphericalDelta.phi -= angle; - return function panUp( distance, objectMatrix ) { + } - if ( scope.screenSpacePanning === true ) { + _panLeft( distance, objectMatrix ) { - v.setFromMatrixColumn( objectMatrix, 1 ); + _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + _v.multiplyScalar( - distance ); - } else { + this._panOffset.add( _v ); - v.setFromMatrixColumn( objectMatrix, 0 ); - v.crossVectors( scope.object.up, v ); + } - } + _panUp( distance, objectMatrix ) { - v.multiplyScalar( distance ); + if ( this.screenSpacePanning === true ) { - panOffset.add( v ); + _v.setFromMatrixColumn( objectMatrix, 1 ); - }; + } else { - }(); + _v.setFromMatrixColumn( objectMatrix, 0 ); + _v.crossVectors( this.object.up, _v ); - // deltaX and deltaY are in pixels; right and down are positive - const pan = function () { + } - const offset = new Vector3(); + _v.multiplyScalar( distance ); - return function pan( deltaX, deltaY ) { + this._panOffset.add( _v ); - const element = scope.domElement; + } - if ( scope.object.isPerspectiveCamera ) { + // deltaX and deltaY are in pixels; right and down are positive + _pan( deltaX, deltaY ) { - // perspective - const position = scope.object.position; - offset.copy( position ).sub( scope.target ); - let targetDistance = offset.length(); + const element = this.domElement; - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + if ( this.object.isPerspectiveCamera ) { - // we use only clientHeight here so aspect ratio does not distort speed - panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); - panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + // perspective + const position = this.object.position; + _v.copy( position ).sub( this.target ); + let targetDistance = _v.length(); - } else if ( scope.object.isOrthographicCamera ) { + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); - // orthographic - panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); - panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + // we use only clientHeight here so aspect ratio does not distort speed + this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); + this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); - } else { + } else if ( this.object.isOrthographicCamera ) { - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - scope.enablePan = false; + // orthographic + this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); + this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); - } + } else { - }; + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + this.enablePan = false; - }(); + } - function dollyOut( dollyScale ) { + } - if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { + _dollyOut( dollyScale ) { - scale /= dollyScale; + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - } else { + this._scale /= dollyScale; - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; + } else { - } + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; } - function dollyIn( dollyScale ) { + } - if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { + _dollyIn( dollyScale ) { - scale *= dollyScale; + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - } else { + this._scale *= dollyScale; - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; + } else { - } + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; } - function updateZoomParameters( x, y ) { - - if ( ! scope.zoomToCursor ) { + } - return; + _updateZoomParameters( x, y ) { - } + if ( ! this.zoomToCursor ) { - performCursorZoom = true; + return; - const rect = scope.domElement.getBoundingClientRect(); - const dx = x - rect.left; - const dy = y - rect.top; - const w = rect.width; - const h = rect.height; + } - mouse.x = ( dx / w ) * 2 - 1; - mouse.y = - ( dy / h ) * 2 + 1; + this._performCursorZoom = true; - dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize(); + const rect = this.domElement.getBoundingClientRect(); + const dx = x - rect.left; + const dy = y - rect.top; + const w = rect.width; + const h = rect.height; - } + this._mouse.x = ( dx / w ) * 2 - 1; + this._mouse.y = - ( dy / h ) * 2 + 1; - function clampDistance( dist ) { + this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); - return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) ); + } - } + _clampDistance( dist ) { - // - // event callbacks - update the object state - // + return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); - function handleMouseDownRotate( event ) { + } - rotateStart.set( event.clientX, event.clientY ); + // + // event callbacks - update the object state + // - } + _handleMouseDownRotate( event ) { - function handleMouseDownDolly( event ) { + this._rotateStart.set( event.clientX, event.clientY ); - updateZoomParameters( event.clientX, event.clientX ); - dollyStart.set( event.clientX, event.clientY ); + } - } + _handleMouseDownDolly( event ) { - function handleMouseDownPan( event ) { + this._updateZoomParameters( event.clientX, event.clientX ); + this._dollyStart.set( event.clientX, event.clientY ); - panStart.set( event.clientX, event.clientY ); + } - } + _handleMouseDownPan( event ) { - function handleMouseMoveRotate( event ) { + this._panStart.set( event.clientX, event.clientY ); - rotateEnd.set( event.clientX, event.clientY ); + } - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + _handleMouseMoveRotate( event ) { - const element = scope.domElement; + this._rotateEnd.set( event.clientX, event.clientY ); - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + const element = this.domElement; - rotateStart.copy( rotateEnd ); + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - scope.update(); + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - } + this._rotateStart.copy( this._rotateEnd ); - function handleMouseMoveDolly( event ) { - - dollyEnd.set( event.clientX, event.clientY ); + this.update(); - dollyDelta.subVectors( dollyEnd, dollyStart ); + } - if ( dollyDelta.y > 0 ) { + _handleMouseMoveDolly( event ) { - dollyOut( getZoomScale( dollyDelta.y ) ); + this._dollyEnd.set( event.clientX, event.clientY ); - } else if ( dollyDelta.y < 0 ) { + this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); - dollyIn( getZoomScale( dollyDelta.y ) ); + if ( this._dollyDelta.y > 0 ) { - } + this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); - dollyStart.copy( dollyEnd ); + } else if ( this._dollyDelta.y < 0 ) { - scope.update(); + this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); } - function handleMouseMovePan( event ) { + this._dollyStart.copy( this._dollyEnd ); - panEnd.set( event.clientX, event.clientY ); + this.update(); + + } - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + _handleMouseMovePan( event ) { - pan( panDelta.x, panDelta.y ); + this._panEnd.set( event.clientX, event.clientY ); - panStart.copy( panEnd ); + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - scope.update(); + this._pan( this._panDelta.x, this._panDelta.y ); - } + this._panStart.copy( this._panEnd ); - function handleMouseWheel( event ) { + this.update(); - updateZoomParameters( event.clientX, event.clientY ); + } - if ( event.deltaY < 0 ) { + _handleMouseWheel( event ) { - dollyIn( getZoomScale( event.deltaY ) ); + this._updateZoomParameters( event.clientX, event.clientY ); - } else if ( event.deltaY > 0 ) { + if ( event.deltaY < 0 ) { - dollyOut( getZoomScale( event.deltaY ) ); + this._dollyIn( this._getZoomScale( event.deltaY ) ); - } + } else if ( event.deltaY > 0 ) { - scope.update(); + this._dollyOut( this._getZoomScale( event.deltaY ) ); } - function handleKeyDown( event ) { + this.update(); + + } - let needsUpdate = false; + _handleKeyDown( event ) { - switch ( event.code ) { + let needsUpdate = false; - case scope.keys.UP: + switch ( event.code ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + case this.keys.UP: - rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } else { + if ( this.enableRotate ) { - pan( 0, scope.keyPanSpeed ); + this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); } - needsUpdate = true; - break; + } else { - case scope.keys.BOTTOM: + if ( this.enablePan ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this._pan( 0, this.keyPanSpeed ); - rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + } - } else { + } - pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; - } + case this.keys.BOTTOM: - needsUpdate = true; - break; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - case scope.keys.LEFT: + if ( this.enableRotate ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + } - } else { + } else { + + if ( this.enablePan ) { - pan( scope.keyPanSpeed, 0 ); + this._pan( 0, - this.keyPanSpeed ); } - needsUpdate = true; - break; + } - case scope.keys.RIGHT: + needsUpdate = true; + break; - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + case this.keys.LEFT: - rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } else { + if ( this.enableRotate ) { - pan( - scope.keyPanSpeed, 0 ); + this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); } - needsUpdate = true; - break; + } else { - } + if ( this.enablePan ) { - if ( needsUpdate ) { + this._pan( this.keyPanSpeed, 0 ); - // prevent the browser from scrolling on cursor keys - event.preventDefault(); + } - scope.update(); + } - } + needsUpdate = true; + break; + case this.keys.RIGHT: - } + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - function handleTouchStartRotate( event ) { + if ( this.enableRotate ) { - if ( pointers.length === 1 ) { + this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - rotateStart.set( event.pageX, event.pageY ); + } - } else { + } else { + + if ( this.enablePan ) { - const position = getSecondPointerPosition( event ); + this._pan( - this.keyPanSpeed, 0 ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + } - rotateStart.set( x, y ); + } - } + needsUpdate = true; + break; } - function handleTouchStartPan( event ) { + if ( needsUpdate ) { - if ( pointers.length === 1 ) { + // prevent the browser from scrolling on cursor keys + event.preventDefault(); - panStart.set( event.pageX, event.pageY ); + this.update(); - } else { + } - const position = getSecondPointerPosition( event ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + } - panStart.set( x, y ); + _handleTouchStartRotate( event ) { - } + if ( this._pointers.length === 1 ) { - } - - function handleTouchStartDolly( event ) { + this._rotateStart.set( event.pageX, event.pageY ); - const position = getSecondPointerPosition( event ); + } else { - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; + const position = this._getSecondPointerPosition( event ); - const distance = Math.sqrt( dx * dx + dy * dy ); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - dollyStart.set( 0, distance ); + this._rotateStart.set( x, y ); } - function handleTouchStartDollyPan( event ) { + } + + _handleTouchStartPan( event ) { - if ( scope.enableZoom ) handleTouchStartDolly( event ); + if ( this._pointers.length === 1 ) { - if ( scope.enablePan ) handleTouchStartPan( event ); + this._panStart.set( event.pageX, event.pageY ); - } + } else { - function handleTouchStartDollyRotate( event ) { + const position = this._getSecondPointerPosition( event ); - if ( scope.enableZoom ) handleTouchStartDolly( event ); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - if ( scope.enableRotate ) handleTouchStartRotate( event ); + this._panStart.set( x, y ); } - function handleTouchMoveRotate( event ) { + } - if ( pointers.length == 1 ) { + _handleTouchStartDolly( event ) { - rotateEnd.set( event.pageX, event.pageY ); + const position = this._getSecondPointerPosition( event ); - } else { + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - const position = getSecondPointerPosition( event ); + const distance = Math.sqrt( dx * dx + dy * dy ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + this._dollyStart.set( 0, distance ); - rotateEnd.set( x, y ); + } - } + _handleTouchStartDollyPan( event ) { - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + if ( this.enableZoom ) this._handleTouchStartDolly( event ); - const element = scope.domElement; + if ( this.enablePan ) this._handleTouchStartPan( event ); - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + } - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + _handleTouchStartDollyRotate( event ) { - rotateStart.copy( rotateEnd ); + if ( this.enableZoom ) this._handleTouchStartDolly( event ); - } + if ( this.enableRotate ) this._handleTouchStartRotate( event ); - function handleTouchMovePan( event ) { + } - if ( pointers.length === 1 ) { + _handleTouchMoveRotate( event ) { - panEnd.set( event.pageX, event.pageY ); + if ( this._pointers.length == 1 ) { - } else { + this._rotateEnd.set( event.pageX, event.pageY ); - const position = getSecondPointerPosition( event ); + } else { - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + const position = this._getSecondPointerPosition( event ); - panEnd.set( x, y ); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - } + this._rotateEnd.set( x, y ); - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + } - pan( panDelta.x, panDelta.y ); + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - panStart.copy( panEnd ); + const element = this.domElement; - } + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - function handleTouchMoveDolly( event ) { + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - const position = getSecondPointerPosition( event ); + this._rotateStart.copy( this._rotateEnd ); - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; + } - const distance = Math.sqrt( dx * dx + dy * dy ); + _handleTouchMovePan( event ) { - dollyEnd.set( 0, distance ); + if ( this._pointers.length === 1 ) { - dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + this._panEnd.set( event.pageX, event.pageY ); - dollyOut( dollyDelta.y ); + } else { - dollyStart.copy( dollyEnd ); + const position = this._getSecondPointerPosition( event ); - const centerX = ( event.pageX + position.x ) * 0.5; - const centerY = ( event.pageY + position.y ) * 0.5; + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - updateZoomParameters( centerX, centerY ); + this._panEnd.set( x, y ); } - function handleTouchMoveDollyPan( event ) { + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + this._pan( this._panDelta.x, this._panDelta.y ); - if ( scope.enablePan ) handleTouchMovePan( event ); + this._panStart.copy( this._panEnd ); - } + } - function handleTouchMoveDollyRotate( event ) { + _handleTouchMoveDolly( event ) { - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + const position = this._getSecondPointerPosition( event ); - if ( scope.enableRotate ) handleTouchMoveRotate( event ); + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - } + const distance = Math.sqrt( dx * dx + dy * dy ); - // - // event handlers - FSM: listen for events and reset state - // + this._dollyEnd.set( 0, distance ); - function onPointerDown( event ) { + this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); - if ( scope.enabled === false ) return; + this._dollyOut( this._dollyDelta.y ); - if ( pointers.length === 0 ) { + this._dollyStart.copy( this._dollyEnd ); - scope.domElement.setPointerCapture( event.pointerId ); + const centerX = ( event.pageX + position.x ) * 0.5; + const centerY = ( event.pageY + position.y ) * 0.5; - scope.domElement.addEventListener( 'pointermove', onPointerMove ); - scope.domElement.addEventListener( 'pointerup', onPointerUp ); + this._updateZoomParameters( centerX, centerY ); - } + } - // + _handleTouchMoveDollyPan( event ) { - if ( isTrackingPointer( event ) ) return; + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - // + if ( this.enablePan ) this._handleTouchMovePan( event ); + + } - addPointer( event ); + _handleTouchMoveDollyRotate( event ) { - if ( event.pointerType === 'touch' ) { + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - onTouchStart( event ); + if ( this.enableRotate ) this._handleTouchMoveRotate( event ); - } else { + } - onMouseDown( event ); + // pointers - } + _addPointer( event ) { - } + this._pointers.push( event.pointerId ); - function onPointerMove( event ) { + } - if ( scope.enabled === false ) return; + _removePointer( event ) { - if ( event.pointerType === 'touch' ) { + delete this._pointerPositions[ event.pointerId ]; - onTouchMove( event ); + for ( let i = 0; i < this._pointers.length; i ++ ) { - } else { + if ( this._pointers[ i ] == event.pointerId ) { - onMouseMove( event ); + this._pointers.splice( i, 1 ); + return; } } - function onPointerUp( event ) { + } - removePointer( event ); + _isTrackingPointer( event ) { - switch ( pointers.length ) { + for ( let i = 0; i < this._pointers.length; i ++ ) { - case 0: + if ( this._pointers[ i ] == event.pointerId ) return true; - scope.domElement.releasePointerCapture( event.pointerId ); + } - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + return false; - scope.dispatchEvent( _endEvent ); + } - state = STATE.NONE; + _trackPointer( event ) { - break; + let position = this._pointerPositions[ event.pointerId ]; - case 1: + if ( position === undefined ) { - const pointerId = pointers[ 0 ]; - const position = pointerPositions[ pointerId ]; + position = new Vector2(); + this._pointerPositions[ event.pointerId ] = position; - // minimal placeholder event - allows state correction on pointer-up - onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); + } - break; + position.set( event.pageX, event.pageY ); - } + } - } + _getSecondPointerPosition( event ) { - function onMouseDown( event ) { + const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - let mouseAction; + return this._pointerPositions[ pointerId ]; - switch ( event.button ) { + } - case 0: + // - mouseAction = scope.mouseButtons.LEFT; - break; + _customWheelEvent( event ) { - case 1: + const mode = event.deltaMode; - mouseAction = scope.mouseButtons.MIDDLE; - break; + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; - case 2: + switch ( mode ) { - mouseAction = scope.mouseButtons.RIGHT; - break; + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; - default: + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; - mouseAction = - 1; + } - } + // detect if event was triggered by pinching + if ( event.ctrlKey && ! this._controlActive ) { - switch ( mouseAction ) { + newEvent.deltaY *= 10; - case MOUSE.DOLLY: + } - if ( scope.enableZoom === false ) return; + return newEvent; - handleMouseDownDolly( event ); + } - state = STATE.DOLLY; +} - break; +function onPointerDown( event ) { - case MOUSE.ROTATE: + if ( this.enabled === false ) return; - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + if ( this._pointers.length === 0 ) { - if ( scope.enablePan === false ) return; + this.domElement.setPointerCapture( event.pointerId ); - handleMouseDownPan( event ); + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - state = STATE.PAN; + } - } else { + // - if ( scope.enableRotate === false ) return; + if ( this._isTrackingPointer( event ) ) return; - handleMouseDownRotate( event ); + // - state = STATE.ROTATE; + this._addPointer( event ); - } + if ( event.pointerType === 'touch' ) { - break; + this._onTouchStart( event ); - case MOUSE.PAN: + } else { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this._onMouseDown( event ); - if ( scope.enableRotate === false ) return; + } - handleMouseDownRotate( event ); +} - state = STATE.ROTATE; +function onPointerMove( event ) { - } else { + if ( this.enabled === false ) return; - if ( scope.enablePan === false ) return; + if ( event.pointerType === 'touch' ) { - handleMouseDownPan( event ); + this._onTouchMove( event ); - state = STATE.PAN; + } else { - } + this._onMouseMove( event ); - break; + } - default: +} - state = STATE.NONE; +function onPointerUp( event ) { - } + this._removePointer( event ); - if ( state !== STATE.NONE ) { + switch ( this._pointers.length ) { - scope.dispatchEvent( _startEvent ); + case 0: - } + this.domElement.releasePointerCapture( event.pointerId ); - } + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - function onMouseMove( event ) { + this.dispatchEvent( _endEvent ); - switch ( state ) { + this.state = _STATE.NONE; - case STATE.ROTATE: + break; - if ( scope.enableRotate === false ) return; + case 1: - handleMouseMoveRotate( event ); + const pointerId = this._pointers[ 0 ]; + const position = this._pointerPositions[ pointerId ]; - break; + // minimal placeholder event - allows state correction on pointer-up + this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); - case STATE.DOLLY: + break; - if ( scope.enableZoom === false ) return; + } - handleMouseMoveDolly( event ); +} - break; +function onMouseDown( event ) { - case STATE.PAN: + let mouseAction; - if ( scope.enablePan === false ) return; + switch ( event.button ) { - handleMouseMovePan( event ); + case 0: - break; + mouseAction = this.mouseButtons.LEFT; + break; - } + case 1: - } + mouseAction = this.mouseButtons.MIDDLE; + break; - function onMouseWheel( event ) { + case 2: - if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + mouseAction = this.mouseButtons.RIGHT; + break; - event.preventDefault(); + default: - scope.dispatchEvent( _startEvent ); + mouseAction = -1; - handleMouseWheel( customWheelEvent( event ) ); + } - scope.dispatchEvent( _endEvent ); + switch ( mouseAction ) { - } + case MOUSE.DOLLY: - function customWheelEvent( event ) { + if ( this.enableZoom === false ) return; - const mode = event.deltaMode; + this._handleMouseDownDolly( event ); - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; + this.state = _STATE.DOLLY; - switch ( mode ) { + break; - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; + case MOUSE.ROTATE: - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } + if ( this.enablePan === false ) return; - // detect if event was triggered by pinching - if ( event.ctrlKey && ! controlActive ) { + this._handleMouseDownPan( event ); - newEvent.deltaY *= 10; + this.state = _STATE.PAN; - } + } else { - return newEvent; + if ( this.enableRotate === false ) return; - } + this._handleMouseDownRotate( event ); - function interceptControlDown( event ) { + this.state = _STATE.ROTATE; - if ( event.key === 'Control' ) { + } - controlActive = true; + break; + case MOUSE.PAN: - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); + if ( this.enableRotate === false ) return; - } + this._handleMouseDownRotate( event ); - } + this.state = _STATE.ROTATE; - function interceptControlUp( event ) { + } else { - if ( event.key === 'Control' ) { + if ( this.enablePan === false ) return; - controlActive = false; + this._handleMouseDownPan( event ); + this.state = _STATE.PAN; - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + } - document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); + break; - } + default: - } + this.state = _STATE.NONE; - function onKeyDown( event ) { + } - if ( scope.enabled === false || scope.enablePan === false ) return; + if ( this.state !== _STATE.NONE ) { - handleKeyDown( event ); + this.dispatchEvent( _startEvent ); - } + } + +} - function onTouchStart( event ) { +function onMouseMove( event ) { - trackPointer( event ); + switch ( this.state ) { - switch ( pointers.length ) { + case _STATE.ROTATE: - case 1: + if ( this.enableRotate === false ) return; - switch ( scope.touches.ONE ) { + this._handleMouseMoveRotate( event ); - case TOUCH.ROTATE: + break; - if ( scope.enableRotate === false ) return; + case _STATE.DOLLY: - handleTouchStartRotate( event ); + if ( this.enableZoom === false ) return; - state = STATE.TOUCH_ROTATE; + this._handleMouseMoveDolly( event ); - break; + break; - case TOUCH.PAN: + case _STATE.PAN: - if ( scope.enablePan === false ) return; + if ( this.enablePan === false ) return; - handleTouchStartPan( event ); + this._handleMouseMovePan( event ); - state = STATE.TOUCH_PAN; + break; - break; + } - default: +} - state = STATE.NONE; +function onMouseWheel( event ) { - } + if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; - break; + event.preventDefault(); - case 2: + this.dispatchEvent( _startEvent ); - switch ( scope.touches.TWO ) { + this._handleMouseWheel( this._customWheelEvent( event ) ); - case TOUCH.DOLLY_PAN: + this.dispatchEvent( _endEvent ); - if ( scope.enableZoom === false && scope.enablePan === false ) return; +} - handleTouchStartDollyPan( event ); +function onKeyDown( event ) { - state = STATE.TOUCH_DOLLY_PAN; + if ( this.enabled === false ) return; - break; + this._handleKeyDown( event ); - case TOUCH.DOLLY_ROTATE: +} - if ( scope.enableZoom === false && scope.enableRotate === false ) return; +function onTouchStart( event ) { - handleTouchStartDollyRotate( event ); + this._trackPointer( event ); - state = STATE.TOUCH_DOLLY_ROTATE; + switch ( this._pointers.length ) { - break; + case 1: - default: + switch ( this.touches.ONE ) { - state = STATE.NONE; + case TOUCH.ROTATE: - } + if ( this.enableRotate === false ) return; + + this._handleTouchStartRotate( event ); + + this.state = _STATE.TOUCH_ROTATE; break; - default: + case TOUCH.PAN: - state = STATE.NONE; + if ( this.enablePan === false ) return; - } + this._handleTouchStartPan( event ); - if ( state !== STATE.NONE ) { + this.state = _STATE.TOUCH_PAN; - scope.dispatchEvent( _startEvent ); + break; - } + default: - } + this.state = _STATE.NONE; - function onTouchMove( event ) { + } + + break; - trackPointer( event ); + case 2: - switch ( state ) { + switch ( this.touches.TWO ) { - case STATE.TOUCH_ROTATE: + case TOUCH.DOLLY_PAN: - if ( scope.enableRotate === false ) return; + if ( this.enableZoom === false && this.enablePan === false ) return; - handleTouchMoveRotate( event ); + this._handleTouchStartDollyPan( event ); - scope.update(); + this.state = _STATE.TOUCH_DOLLY_PAN; break; - case STATE.TOUCH_PAN: + case TOUCH.DOLLY_ROTATE: - if ( scope.enablePan === false ) return; + if ( this.enableZoom === false && this.enableRotate === false ) return; - handleTouchMovePan( event ); + this._handleTouchStartDollyRotate( event ); - scope.update(); + this.state = _STATE.TOUCH_DOLLY_ROTATE; break; - case STATE.TOUCH_DOLLY_PAN: + default: - if ( scope.enableZoom === false && scope.enablePan === false ) return; + this.state = _STATE.NONE; - handleTouchMoveDollyPan( event ); + } - scope.update(); + break; - break; + default: - case STATE.TOUCH_DOLLY_ROTATE: + this.state = _STATE.NONE; - if ( scope.enableZoom === false && scope.enableRotate === false ) return; + } - handleTouchMoveDollyRotate( event ); + if ( this.state !== _STATE.NONE ) { - scope.update(); + this.dispatchEvent( _startEvent ); - break; + } - default: +} - state = STATE.NONE; +function onTouchMove( event ) { - } + this._trackPointer( event ); - } + switch ( this.state ) { - function onContextMenu( event ) { + case _STATE.TOUCH_ROTATE: - if ( scope.enabled === false ) return; + if ( this.enableRotate === false ) return; - event.preventDefault(); + this._handleTouchMoveRotate( event ); - } + this.update(); - function addPointer( event ) { + break; - pointers.push( event.pointerId ); + case _STATE.TOUCH_PAN: - } + if ( this.enablePan === false ) return; - function removePointer( event ) { + this._handleTouchMovePan( event ); - delete pointerPositions[ event.pointerId ]; + this.update(); - for ( let i = 0; i < pointers.length; i ++ ) { + break; - if ( pointers[ i ] == event.pointerId ) { + case _STATE.TOUCH_DOLLY_PAN: - pointers.splice( i, 1 ); - return; + if ( this.enableZoom === false && this.enablePan === false ) return; - } + this._handleTouchMoveDollyPan( event ); - } + this.update(); - } + break; - function isTrackingPointer( event ) { + case _STATE.TOUCH_DOLLY_ROTATE: - for ( let i = 0; i < pointers.length; i ++ ) { + if ( this.enableZoom === false && this.enableRotate === false ) return; - if ( pointers[ i ] == event.pointerId ) return true; + this._handleTouchMoveDollyRotate( event ); - } + this.update(); - return false; + break; - } + default: - function trackPointer( event ) { + this.state = _STATE.NONE; - let position = pointerPositions[ event.pointerId ]; + } - if ( position === undefined ) { +} - position = new Vector2(); - pointerPositions[ event.pointerId ] = position; +function onContextMenu( event ) { - } + if ( this.enabled === false ) return; - position.set( event.pageX, event.pageY ); + event.preventDefault(); - } +} - function getSecondPointerPosition( event ) { +function interceptControlDown( event ) { - const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ]; + if ( event.key === 'Control' ) { - return pointerPositions[ pointerId ]; + this._controlActive = true; - } + const document = this.domElement.getRootNode(); // offscreen canvas compatibility - // + document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + } + +} - scope.domElement.addEventListener( 'pointerdown', onPointerDown ); - scope.domElement.addEventListener( 'pointercancel', onPointerUp ); - scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); +function interceptControlUp( event ) { - const document = scope.domElement.getRootNode(); // offscreen canvas compatibility + if ( event.key === 'Control' ) { - document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); + this._controlActive = false; - // force an update at start + const document = this.domElement.getRootNode(); // offscreen canvas compatibility - this.update(); + document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); } } /** - * Full-screen textured quad shader + * @module CopyShader + * @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js'; */ +/** + * Full-screen copy shader pass. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const CopyShader = { name: 'CopyShader', @@ -1767,41 +2130,105 @@ const CopyShader = { }; +/** + * Abstract base class for all post processing passes. + * + * This module is only relevant for post processing with {@link WebGLRenderer}. + * + * @abstract + * @three_import import { Pass } from 'three/addons/postprocessing/Pass.js'; + */ class Pass { + /** + * Constructs a new pass. + */ constructor() { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPass = true; - // if set to true, the pass is processed by the composer + /** + * If set to `true`, the pass is processed by the composer. + * + * @type {boolean} + * @default true + */ this.enabled = true; - // if set to true, the pass indicates to swap read and write buffer after rendering + /** + * If set to `true`, the pass indicates to swap read and write buffer after rendering. + * + * @type {boolean} + * @default true + */ this.needsSwap = true; - // if set to true, the pass clears its buffer before rendering + /** + * If set to `true`, the pass clears its buffer before rendering + * + * @type {boolean} + * @default false + */ this.clear = false; - // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. + /** + * If set to `true`, the result of the pass is rendered to screen. The last pass in the composers + * pass chain gets automatically rendered to screen, no matter how this property is configured. + * + * @type {boolean} + * @default false + */ this.renderToScreen = false; } + /** + * Sets the size of the pass. + * + * @abstract + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( /* width, height */ ) {} + /** + * This method holds the render logic of a pass. It must be implemented in all derived classes. + * + * @abstract + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + * + * @abstract + */ dispose() {} } // Helper for passes that need to fill the viewport with a single quad. -const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); +const _camera = new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); // https://github.com/mrdoob/three.js/pull/21358 @@ -1811,7 +2238,7 @@ class FullscreenTriangleGeometry extends BufferGeometry { super(); - this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); + this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); } @@ -1820,26 +2247,58 @@ class FullscreenTriangleGeometry extends BufferGeometry { const _geometry = new FullscreenTriangleGeometry(); + +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single full screen quad for rendering + * subsequent passes by just reassigning the `material` reference. + * + * This module can only be used with {@link WebGLRenderer}. + * + * @augments Mesh + * @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; + */ class FullScreenQuad { + /** + * Constructs a new full screen quad. + * + * @param {?Material} material - The material to render te full screen quad with. + */ constructor( material ) { this._mesh = new Mesh( _geometry, material ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the instance is no longer used in your app. + */ dispose() { this._mesh.geometry.dispose(); } + /** + * Renders the full screen quad. + * + * @param {WebGLRenderer} renderer - The renderer. + */ render( renderer ) { renderer.render( this._mesh, _camera ); } + /** + * The quad's material. + * + * @type {?Material} + */ get material() { return this._mesh.material; @@ -1854,13 +2313,54 @@ class FullScreenQuad { } +/** + * This pass can be used to create a post processing effect + * with a raw GLSL shader object. Useful for implementing custom + * effects. + * + * ```js + * const fxaaPass = new ShaderPass( FXAAShader ); + * composer.addPass( fxaaPass ); + * ``` + * + * @augments Pass + * @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; + */ class ShaderPass extends Pass { - constructor( shader, textureID ) { + /** + * Constructs a new shader pass. + * + * @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as + * defines and uniforms. It's also valid to pass a custom shader material. + * @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample + * the read buffer. + */ + constructor( shader, textureID = 'tDiffuse' ) { super(); - this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; + /** + * The name of the texture uniform that should sample the read buffer. + * + * @type {string} + * @default 'tDiffuse' + */ + this.textureID = textureID; + + /** + * The pass uniforms. + * + * @type {?Object} + */ + this.uniforms = null; + + /** + * The pass material. + * + * @type {?ShaderMaterial} + */ + this.material = null; if ( shader instanceof ShaderMaterial ) { @@ -1884,10 +2384,23 @@ class ShaderPass extends Pass { } - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the shader pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { if ( this.uniforms[ this.textureID ] ) { @@ -1896,50 +2409,115 @@ class ShaderPass extends Pass { } - this.fsQuad.material = this.material; + this._fsQuad.material = this.material; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } } +/** + * This pass can be used to define a mask during post processing. + * Meaning only areas of subsequent post processing are affected + * which lie in the masking area of this pass. Internally, the masking + * is implemented with the stencil buffer. + * + * ```js + * const maskPass = new MaskPass( scene, camera ); + * composer.addPass( maskPass ); + * ``` + * + * @augments Pass + * @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js'; + */ class MaskPass extends Pass { + /** + * Constructs a new mask pass. + * + * @param {Scene} scene - The 3D objects in this scene will define the mask. + * @param {Camera} camera - The camera. + */ constructor( scene, camera ) { super(); + /** + * The scene that defines the mask. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; + /** + * Whether to inverse the mask or not. + * + * @type {boolean} + * @default false + */ this.inverse = false; } + /** + * Performs a mask pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const context = renderer.getContext(); @@ -2006,16 +2584,46 @@ class MaskPass extends Pass { } +/** + * This pass can be used to clear a mask previously defined with {@link MaskPass}. + * + * ```js + * const clearPass = new ClearMaskPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + */ class ClearMaskPass extends Pass { + /** + * Constructs a new clear mask pass. + */ constructor() { super(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; } + /** + * Performs the clear of the currently defined mask. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { renderer.state.buffers.stencil.setLocked( false ); @@ -2025,10 +2633,53 @@ class ClearMaskPass extends Pass { } +/** + * Used to implement post-processing effects in three.js. + * The class manages a chain of post-processing passes to produce the final visual result. + * Post-processing passes are executed in order of their addition/insertion. + * The last pass is automatically rendered to screen. + * + * This module can only be used with {@link WebGLRenderer}. + * + * ```js + * const composer = new EffectComposer( renderer ); + * + * // adding some passes + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * + * const outputPass = new OutputPass() + * composer.addPass( outputPass ); + * + * function animate() { + * + * composer.render(); // instead of renderer.render() + * + * } + * ``` + * + * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; + */ class EffectComposer { + /** + * Constructs a new effect composer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will + * be used as the internal read and write buffers. If not given, the composer creates + * the buffers automatically. + */ constructor( renderer, renderTarget ) { + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; this._pixelRatio = renderer.getPixelRatio(); @@ -2053,20 +2704,59 @@ class EffectComposer { this.renderTarget2 = renderTarget.clone(); this.renderTarget2.texture.name = 'EffectComposer.rt2'; + /** + * A reference to the internal write buffer. Passes usually write + * their result into this buffer. + * + * @type {WebGLRenderTarget} + */ this.writeBuffer = this.renderTarget1; + + /** + * A reference to the internal read buffer. Passes usually read + * the previous render result from this buffer. + * + * @type {WebGLRenderTarget} + */ this.readBuffer = this.renderTarget2; + /** + * Whether the final pass is rendered to the screen (default framebuffer) or not. + * + * @type {boolean} + * @default true + */ this.renderToScreen = true; + /** + * An array representing the (ordered) chain of post-processing passes. + * + * @type {Array<Pass>} + */ this.passes = []; + /** + * A copy pass used for internal swap operations. + * + * @private + * @type {ShaderPass} + */ this.copyPass = new ShaderPass( CopyShader ); this.copyPass.material.blending = NoBlending; + /** + * The internal clock for managing time data. + * + * @private + * @type {Clock} + */ this.clock = new Clock(); } + /** + * Swaps the internal read/write buffers. + */ swapBuffers() { const tmp = this.readBuffer; @@ -2075,6 +2765,11 @@ class EffectComposer { } + /** + * Adds the given pass to the pass chain. + * + * @param {Pass} pass - The pass to add. + */ addPass( pass ) { this.passes.push( pass ); @@ -2082,6 +2777,12 @@ class EffectComposer { } + /** + * Inserts the given pass at a given index. + * + * @param {Pass} pass - The pass to insert. + * @param {number} index - The index into the pass chain. + */ insertPass( pass, index ) { this.passes.splice( index, 0, pass ); @@ -2089,11 +2790,16 @@ class EffectComposer { } + /** + * Removes the given pass from the pass chain. + * + * @param {Pass} pass - The pass to remove. + */ removePass( pass ) { const index = this.passes.indexOf( pass ); - if ( index !== - 1 ) { + if ( index !== -1 ) { this.passes.splice( index, 1 ); @@ -2101,6 +2807,12 @@ class EffectComposer { } + /** + * Returns `true` if the pass for the given index is the last enabled pass in the pass chain. + * + * @param {number} passIndex - The pass index. + * @return {boolean} Whether the pass for the given index is the last pass in the pass chain. + */ isLastEnabledPass( passIndex ) { for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { @@ -2117,6 +2829,12 @@ class EffectComposer { } + /** + * Executes all enabled post-processing passes in order to produce the final frame. + * + * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes + * its own time delta value. + */ render( deltaTime ) { // deltaTime value is in seconds @@ -2181,6 +2899,12 @@ class EffectComposer { } + /** + * Resets the internal state of the EffectComposer. + * + * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like + * the one from the constructor. If set, it is used to setup the read and write buffers. + */ reset( renderTarget ) { if ( renderTarget === undefined ) { @@ -2205,6 +2929,13 @@ class EffectComposer { } + /** + * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize}, + * this method honors the current pixel ration. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + */ setSize( width, height ) { this._width = width; @@ -2224,6 +2955,12 @@ class EffectComposer { } + /** + * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output. + * Setting the pixel ratio will automatically resize the composer. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ setPixelRatio( pixelRatio ) { this._pixelRatio = pixelRatio; @@ -2232,6 +2969,10 @@ class EffectComposer { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the composer is no longer used in your app. + */ dispose() { this.renderTarget1.dispose(); @@ -2243,27 +2984,111 @@ class EffectComposer { } +/** + * This class represents a render pass. It takes a camera and a scene and produces + * a beauty pass for subsequent post processing effects. + * + * ```js + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * ``` + * + * @augments Pass + * @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; + */ class RenderPass extends Pass { + /** + * Constructs a new render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used + * for all objects in the scene. + * @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass. + * @param {?number} [clearAlpha=null] - The clear alpha of the render pass. + */ constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { super(); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * The override material. If set, this material is used + * for all objects in the scene. + * + * @type {?Material} + * @default null + */ this.overrideMaterial = overrideMaterial; + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default null + */ this.clearColor = clearColor; + + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default null + */ this.clearAlpha = clearAlpha; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * If set to `true`, only the depth can be cleared when `clear` is to `false`. + * + * @type {boolean} + * @default false + */ this.clearDepth = false; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; this._oldClearColor = new Color(); } + /** + * Performs a beauty pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const oldAutoClear = renderer.autoClear; @@ -2282,7 +3107,7 @@ class RenderPass extends Pass { if ( this.clearColor !== null ) { renderer.getClearColor( this._oldClearColor ); - renderer.setClearColor( this.clearColor ); + renderer.setClearColor( this.clearColor, renderer.getClearAlpha() ); } @@ -2336,34 +3161,36 @@ class RenderPass extends Pass { } -// Ported from Stefan Gustavson's java implementation -// http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf -// Read Stefan's excellent paper for details on how this code works. -// -// Sean McCullough banksean@gmail.com -// -// Added 4D noise - /** - * You can pass in a random number generator object if you like. - * It is assumed to have a random() method. + * A utility class providing noise functions. + * + * The code is based on [Simplex noise demystified]{@link https://web.archive.org/web/20210210162332/http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf} + * by Stefan Gustavson, 2005. + * + * @three_import import { SimplexNoise } from 'three/addons/math/SimplexNoise.js'; */ class SimplexNoise { + /** + * Constructs a new simplex noise object. + * + * @param {Object} [r=Math] - A math utility class that holds a `random()` method. This makes it + * possible to pass in custom random number generator. + */ constructor( r = Math ) { - this.grad3 = [[ 1, 1, 0 ], [ - 1, 1, 0 ], [ 1, - 1, 0 ], [ - 1, - 1, 0 ], - [ 1, 0, 1 ], [ - 1, 0, 1 ], [ 1, 0, - 1 ], [ - 1, 0, - 1 ], - [ 0, 1, 1 ], [ 0, - 1, 1 ], [ 0, 1, - 1 ], [ 0, - 1, - 1 ]]; + this.grad3 = [[ 1, 1, 0 ], [ -1, 1, 0 ], [ 1, -1, 0 ], [ -1, -1, 0 ], + [ 1, 0, 1 ], [ -1, 0, 1 ], [ 1, 0, -1 ], [ -1, 0, -1 ], + [ 0, 1, 1 ], [ 0, -1, 1 ], [ 0, 1, -1 ], [ 0, -1, -1 ]]; - this.grad4 = [[ 0, 1, 1, 1 ], [ 0, 1, 1, - 1 ], [ 0, 1, - 1, 1 ], [ 0, 1, - 1, - 1 ], - [ 0, - 1, 1, 1 ], [ 0, - 1, 1, - 1 ], [ 0, - 1, - 1, 1 ], [ 0, - 1, - 1, - 1 ], - [ 1, 0, 1, 1 ], [ 1, 0, 1, - 1 ], [ 1, 0, - 1, 1 ], [ 1, 0, - 1, - 1 ], - [ - 1, 0, 1, 1 ], [ - 1, 0, 1, - 1 ], [ - 1, 0, - 1, 1 ], [ - 1, 0, - 1, - 1 ], - [ 1, 1, 0, 1 ], [ 1, 1, 0, - 1 ], [ 1, - 1, 0, 1 ], [ 1, - 1, 0, - 1 ], - [ - 1, 1, 0, 1 ], [ - 1, 1, 0, - 1 ], [ - 1, - 1, 0, 1 ], [ - 1, - 1, 0, - 1 ], - [ 1, 1, 1, 0 ], [ 1, 1, - 1, 0 ], [ 1, - 1, 1, 0 ], [ 1, - 1, - 1, 0 ], - [ - 1, 1, 1, 0 ], [ - 1, 1, - 1, 0 ], [ - 1, - 1, 1, 0 ], [ - 1, - 1, - 1, 0 ]]; + this.grad4 = [[ 0, 1, 1, 1 ], [ 0, 1, 1, -1 ], [ 0, 1, -1, 1 ], [ 0, 1, -1, -1 ], + [ 0, -1, 1, 1 ], [ 0, -1, 1, -1 ], [ 0, -1, -1, 1 ], [ 0, -1, -1, -1 ], + [ 1, 0, 1, 1 ], [ 1, 0, 1, -1 ], [ 1, 0, -1, 1 ], [ 1, 0, -1, -1 ], + [ -1, 0, 1, 1 ], [ -1, 0, 1, -1 ], [ -1, 0, -1, 1 ], [ -1, 0, -1, -1 ], + [ 1, 1, 0, 1 ], [ 1, 1, 0, -1 ], [ 1, -1, 0, 1 ], [ 1, -1, 0, -1 ], + [ -1, 1, 0, 1 ], [ -1, 1, 0, -1 ], [ -1, -1, 0, 1 ], [ -1, -1, 0, -1 ], + [ 1, 1, 1, 0 ], [ 1, 1, -1, 0 ], [ 1, -1, 1, 0 ], [ 1, -1, -1, 0 ], + [ -1, 1, 1, 0 ], [ -1, 1, -1, 0 ], [ -1, -1, 1, 0 ], [ -1, -1, -1, 0 ]]; this.p = []; @@ -2396,24 +3223,13 @@ class SimplexNoise { } - dot( g, x, y ) { - - return g[ 0 ] * x + g[ 1 ] * y; - - } - - dot3( g, x, y, z ) { - - return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z; - - } - - dot4( g, x, y, z, w ) { - - return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z + g[ 3 ] * w; - - } - + /** + * A 2D simplex noise method. + * + * @param {number} xin - The x coordinate. + * @param {number} yin - The y coordinate. + * @return {number} The noise value. + */ noise( xin, yin ) { let n0; // Noise contributions from the three corners @@ -2467,7 +3283,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot( this.grad3[ gi0 ], x0, y0 ); // (x,y) of grad3 used for 2D gradient + n0 = t0 * t0 * this._dot( this.grad3[ gi0 ], x0, y0 ); // (x,y) of grad3 used for 2D gradient } @@ -2476,7 +3292,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot( this.grad3[ gi1 ], x1, y1 ); + n1 = t1 * t1 * this._dot( this.grad3[ gi1 ], x1, y1 ); } @@ -2485,7 +3301,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot( this.grad3[ gi2 ], x2, y2 ); + n2 = t2 * t2 * this._dot( this.grad3[ gi2 ], x2, y2 ); } @@ -2495,7 +3311,14 @@ class SimplexNoise { } - // 3D simplex noise + /** + * A 3D simplex noise method. + * + * @param {number} xin - The x coordinate. + * @param {number} yin - The y coordinate. + * @param {number} zin - The z coordinate. + * @return {number} The noise value. + */ noise3d( xin, yin, zin ) { let n0; // Noise contributions from the four corners @@ -2595,7 +3418,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot3( this.grad3[ gi0 ], x0, y0, z0 ); + n0 = t0 * t0 * this._dot3( this.grad3[ gi0 ], x0, y0, z0 ); } @@ -2604,7 +3427,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot3( this.grad3[ gi1 ], x1, y1, z1 ); + n1 = t1 * t1 * this._dot3( this.grad3[ gi1 ], x1, y1, z1 ); } @@ -2613,7 +3436,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot3( this.grad3[ gi2 ], x2, y2, z2 ); + n2 = t2 * t2 * this._dot3( this.grad3[ gi2 ], x2, y2, z2 ); } @@ -2622,7 +3445,7 @@ class SimplexNoise { else { t3 *= t3; - n3 = t3 * t3 * this.dot3( this.grad3[ gi3 ], x3, y3, z3 ); + n3 = t3 * t3 * this._dot3( this.grad3[ gi3 ], x3, y3, z3 ); } @@ -2632,7 +3455,15 @@ class SimplexNoise { } - // 4D simplex noise + /** + * A 4D simplex noise method. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @param {number} z - The z coordinate. + * @param {number} w - The w coordinate. + * @return {number} The noise value. + */ noise4d( x, y, z, w ) { // For faster and easier lookups @@ -2732,7 +3563,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot4( grad4[ gi0 ], x0, y0, z0, w0 ); + n0 = t0 * t0 * this._dot4( grad4[ gi0 ], x0, y0, z0, w0 ); } @@ -2741,7 +3572,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot4( grad4[ gi1 ], x1, y1, z1, w1 ); + n1 = t1 * t1 * this._dot4( grad4[ gi1 ], x1, y1, z1, w1 ); } @@ -2750,7 +3581,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot4( grad4[ gi2 ], x2, y2, z2, w2 ); + n2 = t2 * t2 * this._dot4( grad4[ gi2 ], x2, y2, z2, w2 ); } @@ -2759,7 +3590,7 @@ class SimplexNoise { else { t3 *= t3; - n3 = t3 * t3 * this.dot4( grad4[ gi3 ], x3, y3, z3, w3 ); + n3 = t3 * t3 * this._dot4( grad4[ gi3 ], x3, y3, z3, w3 ); } @@ -2768,7 +3599,7 @@ class SimplexNoise { else { t4 *= t4; - n4 = t4 * t4 * this.dot4( grad4[ gi4 ], x4, y4, z4, w4 ); + n4 = t4 * t4 * this._dot4( grad4[ gi4 ], x4, y4, z4, w4 ); } @@ -2777,19 +3608,43 @@ class SimplexNoise { } + // private + + _dot( g, x, y ) { + + return g[ 0 ] * x + g[ 1 ] * y; + + } + + _dot3( g, x, y, z ) { + + return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z; + + } + + _dot4( g, x, y, z, w ) { + + return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z + g[ 3 ] * w; + + } + } /** - * Luminosity - * http://en.wikipedia.org/wiki/Luminosity + * @module LuminosityHighPassShader + * @three_import import { LuminosityHighPassShader } from 'three/addons/shaders/LuminosityHighPassShader.js'; */ +/** + * Luminosity high pass shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const LuminosityHighPassShader = { name: 'LuminosityHighPassShader', - shaderID: 'luminosityHighPass', - uniforms: { 'tDiffuse': { value: null }, @@ -2826,9 +3681,7 @@ const LuminosityHighPassShader = { vec4 texel = texture2D( tDiffuse, vUv ); - vec3 luma = vec3( 0.299, 0.587, 0.114 ); - - float v = dot( texel.xyz, luma ); + float v = luminance( texel.xyz ); vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity ); @@ -2841,28 +3694,87 @@ const LuminosityHighPassShader = { }; /** - * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a + * This pass is inspired by the bloom pass of Unreal Engine. It creates a * mip map chain of bloom textures and blurs them with different radii. Because * of the weighted combination of mips, and because larger blurs are done on * higher mips, this effect provides good quality and performance. * + * When using this pass, tone mapping must be enabled in the renderer settings. + * * Reference: - * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ + * - [Bloom in Unreal Engine]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/} + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ); + * composer.addPass( bloomPass ); + * ``` + * + * @augments Pass + * @three_import import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; */ class UnrealBloomPass extends Pass { - constructor( resolution, strength, radius, threshold ) { + /** + * Constructs a new Unreal Bloom pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {number} [strength=1] - The Bloom strength. + * @param {number} radius - The Bloom radius. + * @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect. + */ + constructor( resolution, strength = 1, radius, threshold ) { super(); - this.strength = ( strength !== undefined ) ? strength : 1; + /** + * The Bloom strength. + * + * @type {number} + * @default 1 + */ + this.strength = strength; + + /** + * The Bloom radius. + * + * @type {number} + */ this.radius = radius; + + /** + * The luminance threshold limits which bright areas contribute to the Bloom effect. + * + * @type {number} + */ this.threshold = threshold; + + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - // create color only once here, reuse it later inside the render function + /** + * The effect's clear color + * + * @type {Color} + * @default (0,0,0) + */ this.clearColor = new Color( 0, 0, 0 ); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + // internals + // render targets this.renderTargetsHorizontal = []; this.renderTargetsVertical = []; @@ -2876,12 +3788,12 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - const renderTargetHorizonal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); + const renderTargetHorizontal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizonal.texture.generateMipmaps = false; + renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizontal.texture.generateMipmaps = false; - this.renderTargetsHorizontal.push( renderTargetHorizonal ); + this.renderTargetsHorizontal.push( renderTargetHorizontal ); const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); @@ -2919,7 +3831,7 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); + this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); @@ -2931,7 +3843,7 @@ class UnrealBloomPass extends Pass { // composite material - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); + this.compositeMaterial = this._getCompositeMaterial( this.nMips ); this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; @@ -2947,32 +3859,31 @@ class UnrealBloomPass extends Pass { // blend material - const copyShader = CopyShader; - - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); this.blendMaterial = new ShaderMaterial( { uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, blending: AdditiveBlending, depthTest: false, depthWrite: false, transparent: true } ); - this.enabled = true; - this.needsSwap = false; - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearAlpha = 1; - this.basic = new MeshBasicMaterial(); + this._basic = new MeshBasicMaterial(); - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { @@ -2999,14 +3910,20 @@ class UnrealBloomPass extends Pass { this.compositeMaterial.dispose(); this.blendMaterial.dispose(); - this.basic.dispose(); + this._basic.dispose(); // - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { let resx = Math.round( width / 2 ); @@ -3028,10 +3945,21 @@ class UnrealBloomPass extends Pass { } + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); + this._oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.autoClear = false; @@ -3043,12 +3971,12 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + this._fsQuad.material = this._basic; + this._basic.map = readBuffer.texture; renderer.setRenderTarget( null ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -3056,11 +3984,11 @@ class UnrealBloomPass extends Pass { this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + this._fsQuad.material = this.materialHighPassFilter; renderer.setRenderTarget( this.renderTargetBright ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // 2. Blur All the mips progressively @@ -3068,19 +3996,19 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.fsQuad.material = this.separableBlurMaterials[ i ]; + this._fsQuad.material = this.separableBlurMaterials[ i ]; this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); inputRenderTarget = this.renderTargetsVertical[ i ]; @@ -3088,18 +4016,18 @@ class UnrealBloomPass extends Pass { // Composite All the mips - this.fsQuad.material = this.compositeMaterial; + this._fsQuad.material = this.compositeMaterial; this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Blend it additively over the input texture - this.fsQuad.material = this.blendMaterial; + this._fsQuad.material = this.blendMaterial; this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); @@ -3107,29 +4035,31 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } // Restore renderer settings - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); renderer.autoClear = oldAutoClear; } - getSeperableBlurMaterial( kernelRadius ) { + // internals + + _getSeparableBlurMaterial( kernelRadius ) { const coefficients = []; for ( let i = 0; i < kernelRadius; i ++ ) { - coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + coefficients.push( 0.39894 * Math.exp( -0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); } @@ -3179,7 +4109,7 @@ class UnrealBloomPass extends Pass { } - getCompositeMaterial( nMips ) { + _getCompositeMaterial( nMips ) { return new ShaderMaterial( { @@ -3349,10 +4279,18 @@ class RenderableSprite { } -// - +/** + * This class can project a given scene in 3D space into a 2D representation + * used for rendering with a 2D API. `Projector` is currently used by {@link SVGRenderer} + * and was previously used by the legacy `CanvasRenderer`. + * + * @three_import import { Projector } from 'three/addons/renderers/Projector.js'; + */ class Projector { + /** + * Constructs a new projector. + */ constructor() { let _object, _objectCount, _objectPoolLength = 0, @@ -3369,7 +4307,7 @@ class Projector { _vector3 = new Vector3(), _vector4 = new Vector4(), - _clipBox = new Box3( new Vector3( - 1, - 1, - 1 ), new Vector3( 1, 1, 1 ) ), + _clipBox = new Box3( new Vector3( -1, -1, -1 ), new Vector3( 1, 1, 1 ) ), _boundingBox = new Box3(), _points3 = new Array( 3 ), @@ -3421,9 +4359,9 @@ class Projector { positionScreen.y *= invW; positionScreen.z *= invW; - vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 && - positionScreen.y >= - 1 && positionScreen.y <= 1 && - positionScreen.z >= - 1 && positionScreen.z <= 1; + vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 && + positionScreen.y >= -1 && positionScreen.y <= 1 && + positionScreen.z >= -1 && positionScreen.z <= 1; } @@ -3632,6 +4570,16 @@ class Projector { } + /** + * Projects the given scene in 3D space into a 2D representation. The result + * is an object with renderable items. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + * @param {boolean} sortObjects - Whether to sort objects or not. + * @param {boolean} sortElements - Whether to sort elements (faces, lines and sprites) or not. + * @return {{objects:Array<Objects>,lights:Array<Objects>,elements:Array<Objects>}} The projected scene as renderable objects. + */ this.projectScene = function ( scene, camera, sortObjects, sortElements ) { _faceCount = 0; @@ -3939,7 +4887,7 @@ class Projector { _vector4.z *= invW; - if ( _vector4.z >= - 1 && _vector4.z <= 1 ) { + if ( _vector4.z >= -1 && _vector4.z <= 1 ) { _sprite = getNextSpriteInPool(); _sprite.id = object.id; @@ -4144,8 +5092,32 @@ class Projector { } +/** + * This renderer an be used to render geometric data using SVG. The produced vector + * graphics are particular useful in the following use cases: + * + * - Animated logos or icons. + * - Interactive 2D/3D diagrams or graphs. + * - Interactive maps. + * - Complex or animated user interfaces. + * + * `SVGRenderer` has various advantages. It produces crystal-clear and sharp output which + * is independent of the actual viewport resolution.SVG elements can be styled via CSS. + * And they have good accessibility since it's possible to add metadata like title or description + * (useful for search engines or screen readers). + * + * There are, however, some important limitations: + * - No advanced shading. + * - No texture support. + * - No shadow support. + * + * @three_import import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js'; + */ class SVGRenderer { + /** + * Constructs a new SVG renderer. + */ constructor() { let _renderData, _elements, _lights, @@ -4185,16 +5157,60 @@ class SVGRenderer { _projector = new Projector(), _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + /** + * The DOM where the renderer appends its child-elements. + * + * @type {DOMElement} + */ this.domElement = _svg; + /** + * Whether to automatically perform a clear before a render call or not. + * + * @type {boolean} + * @default true + */ this.autoClear = true; + + /** + * Whether to sort 3D objects or not. + * + * @type {boolean} + * @default true + */ this.sortObjects = true; + + /** + * Whether to sort elements or not. + * + * @type {boolean} + * @default true + */ this.sortElements = true; + /** + * Number of fractional pixels to enlarge polygons in order to + * prevent anti-aliasing gaps. Range is `[0,1]`. + * + * @type {number} + * @default 0.5 + */ this.overdraw = 0.5; + /** + * The output color space. + * + * @type {(SRGBColorSpace|LinearSRGBColorSpace)} + * @default SRGBColorSpace + */ this.outputColorSpace = SRGBColorSpace; + /** + * Provides information about the number of + * rendered vertices and faces. + * + * @type {Object} + */ this.info = { render: { @@ -4206,6 +5222,12 @@ class SVGRenderer { }; + /** + * Sets the render quality. Setting to `high` means This value indicates that the browser + * tries to improve the SVG quality over rendering speed and geometric precision. + * + * @param {('low'|'high')} quality - The quality. + */ this.setQuality = function ( quality ) { switch ( quality ) { @@ -4217,6 +5239,11 @@ class SVGRenderer { }; + /** + * Sets the clear color. + * + * @param {(number|Color|string)} color - The clear color to set. + */ this.setClearColor = function ( color ) { _clearColor.set( color ); @@ -4225,6 +5252,12 @@ class SVGRenderer { this.setPixelRatio = function () {}; + /** + * Resizes the renderer to the given width and height. + * + * @param {number} width - The width of the renderer. + * @param {number} height - The height of the renderer. + */ this.setSize = function ( width, height ) { _svgWidth = width; _svgHeight = height; @@ -4239,6 +5272,11 @@ class SVGRenderer { }; + /** + * Returns an object containing the width and height of the renderer. + * + * @return {{width:number,height:number}} The size of the renderer. + */ this.getSize = function () { return { @@ -4248,6 +5286,11 @@ class SVGRenderer { }; + /** + * Sets the precision of the data used to create a paths. + * + * @param {number} precision - The precision to set. + */ this.setPrecision = function ( precision ) { _precision = precision; @@ -4272,6 +5315,9 @@ class SVGRenderer { } + /** + * Performs a manual clear with the defined clear color. + */ this.clear = function () { removeChildNodes(); @@ -4279,6 +5325,12 @@ class SVGRenderer { }; + /** + * Renders the given scene using the given camera. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( camera instanceof Camera === false ) { @@ -4355,9 +5407,9 @@ class SVGRenderer { _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; - if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue; - if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue; - if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue; + if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue; + if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue; + if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue; _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; @@ -4396,7 +5448,7 @@ class SVGRenderer { _vector3.setFromMatrixPosition( object.matrixWorld ); _vector3.applyMatrix4( _viewProjectionMatrix ); - if ( _vector3.z < - 1 || _vector3.z > 1 ) return; + if ( _vector3.z < -1 || _vector3.z > 1 ) return; const x = _vector3.x * _svgWidthHalf; const y = - _vector3.y * _svgHeightHalf; diff --git a/modules/tree.mjs b/modules/tree.mjs index 933760581..99f499732 100644 --- a/modules/tree.mjs +++ b/modules/tree.mjs @@ -1,5 +1,5 @@ -import { BIT, isArrayProto, isRootCollection, isObject, isFunc, isStr, getMethods, - create, createHistogram, createTGraph, prROOT, +import { BIT, settings, isArrayProto, isRootCollection, isObject, isFunc, isStr, getMethods, + create, createHistogram, createTGraph, getKindForType, clTObject, clTObjString, clTHashList, clTPolyMarker3D, clTH1, clTH2, clTH3, kNoStats } from './core.mjs'; import { kChar, kShort, kInt, kFloat, kCharStar, kDouble, kDouble32, @@ -106,17 +106,22 @@ class TSelector { * 2 - when plain (1-dim) array with same-type content * @private */ function checkArrayPrototype(arr, check_content) { - if (!isObject(arr)) return 0; + if (!isObject(arr)) + return 0; const arr_kind = isArrayProto(Object.prototype.toString.apply(arr)); - if (!check_content || (arr_kind !== 1)) return arr_kind; + if (!check_content || (arr_kind !== 1)) + return arr_kind; let typ, plain = true; for (let k = 0; k < arr.length; ++k) { const sub = typeof arr[k]; - if (!typ) typ = sub; - if (sub !== typ) { plain = false; break; } - if (isObject(sub) && checkArrayPrototype(arr[k])) { plain = false; break; } + if (!typ) + typ = sub; + if ((sub !== typ) || (isObject(sub) && checkArrayPrototype(arr[k]))) { + plain = false; + break; + } } return plain ? 2 : 1; @@ -159,7 +164,8 @@ class ArrayIterator { if ((this.select[cnt] === undefined) && (++this.indx[cnt] < this.arr[cnt].length)) break; } - if (cnt < 0) return false; + if (cnt < 0) + return false; } while (true) { @@ -194,11 +200,15 @@ class ArrayIterator { continue; } - if ((typ === 'array') && ((obj.length > 0) || (this.select[cnt + 1] === '$size$'))) { + if ((typ === 'array') && (obj.length || (this.select[cnt + 1] === '$size$'))) { this.arr[++cnt] = obj; switch (this.select[cnt]) { - case undefined: this.indx[cnt] = 0; break; - case '$last$': this.indx[cnt] = obj.length - 1; break; + case undefined: + this.indx[cnt] = 0; + break; + case '$last$': + this.indx[cnt] = obj.length - 1; + break; case '$size$': this.value = obj.length; this.fastindx = this.fastlimit = 0; @@ -207,7 +217,8 @@ class ArrayIterator { default: if (Number.isInteger(this.select[cnt])) { this.indx[cnt] = this.select[cnt]; - if (this.indx[cnt] < 0) this.indx[cnt] = obj.length - 1; + if (this.indx[cnt] < 0) + this.indx[cnt] = obj.length - 1; } else { // this is compile variable as array index - can be any expression this.select[cnt].produce(this.tgtobj); @@ -215,7 +226,8 @@ class ArrayIterator { } } } else { - if (cnt < 0) return false; + if (cnt < 0) + return false; this.value = obj; if (this.select[cnt] === undefined) { @@ -246,10 +258,63 @@ class ArrayIterator { } // class ArrayIterator +/** @summary return TStreamerElement associated with the branch - if any + * @desc unfortunately, branch.fID is not number of element in streamer info + * @private */ +function findBrachStreamerElement(branch, file) { + if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) + return null; + + const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum), + arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null; + if (!arr) + return null; + + let match_name = branch.fName, + pos = match_name.indexOf('['); + if (pos > 0) + match_name = match_name.slice(0, pos); + pos = match_name.lastIndexOf('.'); + if (pos > 0) + match_name = match_name.slice(pos + 1); + + function match_elem(elem) { + if (!elem) + return false; + if (elem.fName !== match_name) + return false; + if (elem.fType === branch.fStreamerType) + return true; + if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) + return true; + if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) || + (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) && + (elem.fType === kStreamer)) + return true; + console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`); + return false; + } + + // first check branch fID - in many cases gut guess + if (match_elem(arr[branch.fID])) + return arr[branch.fID]; + + for (let k = 0; k < arr.length; ++k) { + if ((k !== branch.fID) && match_elem(arr[k])) + return arr[k]; + } + + console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`); + + return null; +} + + /** @summary return class name of the object, stored in the branch * @private */ function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = false) { - if (!branch || (branch._typename !== clTBranchElement)) return ''; + if (!branch || (branch._typename !== clTBranchElement)) + return ''; if ((branch.fType === kLeafNode) && (branch.fID === -2) && (branch.fStreamerType === -1)) { // object where all sub-branches will be collected @@ -270,8 +335,10 @@ function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = fa } if ((branch.fType === kLeafNode) && s_elem && with_leafs) { - if ((s_elem.fType === kObject) || (s_elem.fType === kAny)) return s_elem.fTypeName; - if (s_elem.fType === kObjectp) return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1); + if ((s_elem.fType === kObject) || (s_elem.fType === kAny)) + return s_elem.fTypeName; + if (s_elem.fType === kObjectp) + return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1); } return ''; @@ -283,12 +350,15 @@ function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = fa * @return {Object} branch * @private */ function getTreeBranch(tree, id) { - if (!Number.isInteger(id)) return; + if (!Number.isInteger(id)) + return; let res, seq = 0; function scan(obj) { obj?.fBranches?.arr.forEach(br => { - if (seq++ === id) res = br; - if (!res) scan(br); + if (seq++ === id) + res = br; + if (!res) + scan(br); }); } @@ -309,51 +379,62 @@ function findBranchComplex(tree, name, lst = undefined, only_search = false) { top_search = true; lst = tree.fBranches; const pos = search.indexOf('['); - if (pos > 0) search = search.slice(0, pos); + if (pos > 0) + search = search.slice(0, pos); } - if (!lst || (lst.arr.length === 0)) return null; + if (!lst?.arr.length) + return null; for (let n = 0; n < lst.arr.length; ++n) { let brname = lst.arr[n].fName; - if (brname[brname.length - 1] === ']') + if (brname.at(-1) === ']') brname = brname.slice(0, brname.indexOf('[')); // special case when branch name includes STL map name - if ((search.indexOf(brname) !== 0) && (brname.indexOf('<') > 0)) { + if (search.indexOf(brname) && (brname.indexOf('<') > 0)) { const p1 = brname.indexOf('<'), p2 = brname.lastIndexOf('>'); brname = brname.slice(0, p1) + brname.slice(p2 + 1); } - if (brname === search) { res = { branch: lst.arr[n], rest: '' }; break; } + if (brname === search) { + res = { branch: lst.arr[n], rest: '' }; + break; + } - if (search.indexOf(brname) !== 0) continue; + if (search.indexOf(brname)) + continue; // this is a case when branch name is in the begin of the search string // check where point is let pnt = brname.length; - if (brname[pnt - 1] === '.') pnt--; - if (search[pnt] !== '.') continue; - - res = findBranchComplex(tree, search, lst.arr[n].fBranches); - if (!res) res = findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches); + if (brname[pnt - 1] === '.') + pnt--; + if (search[pnt] !== '.') + continue; - if (!res) res = { branch: lst.arr[n], rest: search.slice(pnt) }; + res = findBranchComplex(tree, search, lst.arr[n].fBranches) || + findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches) || + { branch: lst.arr[n], rest: search.slice(pnt) }; break; } if (top_search && !only_search && !res && (search.indexOf('br_') === 0)) { let p = 3; - while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) ++p; + while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) + ++p; const br = (p > 3) ? getTreeBranch(tree, parseInt(search.slice(3, p))) : null; - if (br) res = { branch: br, rest: search.slice(p) }; + if (br) + res = { branch: br, rest: search.slice(p) }; } - if (!top_search || !res) return res; + if (!top_search || !res) + return res; - if (name.length > search.length) res.rest += name.slice(search.length); + if (name.length > search.length) + res.rest += name.slice(search.length); return res; } @@ -375,7 +456,8 @@ function findBranch(tree, name) { * private function getNumBranches(tree) { function count(obj) { - if (!obj?.fBranches) return 0; + if (!obj?.fBranches) + return 0; let nchld = 0; obj.fBranches.arr.forEach(sub => { nchld += count(sub); }); return obj.fBranches.arr.length + nchld; @@ -411,20 +493,25 @@ class TDrawVariable { * @desc when only_branch specified, its placed in the front of the expression */ parse(tree, selector, code, only_branch, branch_mode) { const is_start_symbol = symb => { - if ((symb >= 'A') && (symb <= 'Z')) return true; - if ((symb >= 'a') && (symb <= 'z')) return true; + if ((symb >= 'A') && (symb <= 'Z')) + return true; + if ((symb >= 'a') && (symb <= 'z')) + return true; return (symb === '_'); }, is_next_symbol = symb => { - if (is_start_symbol(symb)) return true; - if ((symb >= '0') && (symb <= '9')) return true; + if (is_start_symbol(symb)) + return true; + if ((symb >= '0') && (symb <= '9')) + return true; return false; }; - if (!code) code = ''; // should be empty string at least + if (!code) + code = ''; // should be empty string at least this.code = (only_branch?.fName ?? '') + code; - let pos = 0, pos2 = 0, br = null; + let pos = 0, pos2 = 0, br; while ((pos < code.length) || only_branch) { let arriter = []; @@ -434,7 +521,8 @@ class TDrawVariable { } else { // first try to find branch pos2 = pos; - while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) pos2++; + while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) + pos2++; if (code[pos2] === '$') { let repl = ''; switch (code.slice(pos, pos2)) { @@ -444,13 +532,16 @@ class TDrawVariable { } if (repl) { code = code.slice(0, pos) + repl + code.slice(pos2 + 1); - pos = pos + repl.length; + pos += repl.length; continue; } } - br = findBranchComplex(tree, code.slice(pos, pos2)); - if (!br) { pos = pos2 + 1; continue; } + br = selector.findBranch(tree, code.slice(pos, pos2)); + if (!br) { + pos = pos2 + 1; + continue; + } // when full id includes branch name, replace only part of extracted expression if (br.branch && (br.rest !== undefined)) { @@ -461,7 +552,7 @@ class TDrawVariable { // when code ends with the point - means object itself will be accessed // sometime branch name itself ends with the point - if ((pos2 >= code.length - 1) && (code[code.length - 1] === '.')) { + if ((pos2 >= code.length - 1) && (code.at(-1) === '.')) { arriter.push('$self$'); pos2 = code.length; } @@ -469,7 +560,7 @@ class TDrawVariable { // now extract all levels of iterators while (pos2 < code.length) { - if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && (arriter.length === 0)) { + if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && !arriter.length) { pos2 += 5; branch_mode = true; break; @@ -490,35 +581,40 @@ class TDrawVariable { break; } - while ((pos2 < code.length) && is_next_symbol(code[pos2])) pos2++; + while ((pos2 < code.length) && is_next_symbol(code[pos2])) + pos2++; // this is looks like function call - do not need to extract member with - if (code[pos2] === '(') { pos2 = prev - 1; break; } + if (code[pos2] === '(') { + pos2 = prev - 1; + break; + } // this is selection of member, but probably we need to activate iterator for ROOT collection - if (arriter.length === 0) { - // TODO: if selected member is simple data type - no need to make other checks - just break here - if ((br.fType === kClonesNode) || (br.fType === kSTLNode)) - arriter.push(undefined); - else { - const objclass = getBranchObjectClass(br, tree, false, true); - if (objclass && isRootCollection(null, objclass)) - arriter.push(undefined); - } - } + // TODO: if selected member is simple data type - no need to make other checks - just break here + if (!arriter.length && selector.isArrayBranch(tree, br)) + arriter.push(undefined); arriter.push(code.slice(prev, pos2)); continue; } - if (code[pos2] !== '[') break; + if (code[pos2] !== '[') + break; // simple [] - if (code[pos2 + 1] === ']') { arriter.push(undefined); pos2 += 2; continue; } + if (code[pos2 + 1] === ']') { + arriter.push(undefined); + pos2 += 2; + continue; + } const prev = pos2++; let cnt = 0; while ((pos2 < code.length) && ((code[pos2] !== ']') || (cnt > 0))) { - if (code[pos2] === '[') cnt++; else if (code[pos2] === ']') cnt--; + if (code[pos2] === '[') + cnt++; + else if (code[pos2] === ']') + cnt--; pos2++; } const sub = code.slice(prev + 1, pos2); @@ -534,20 +630,22 @@ class TDrawVariable { else { // try to compile code as draw variable const subvar = new TDrawVariable(this.globals); - if (!subvar.parse(tree, selector, sub)) return false; + if (!subvar.parse(tree, selector, sub)) + return false; arriter.push(subvar); } } pos2++; } - if (arriter.length === 0) + if (!arriter.length) arriter = undefined; else if ((arriter.length === 1) && (arriter[0] === undefined)) arriter = true; let indx = selector.indexOfBranch(br); - if (indx < 0) indx = selector.addBranch(br, undefined, branch_mode); + if (indx < 0) + indx = selector.addBranch(br, undefined, branch_mode); branch_mode = undefined; @@ -561,9 +659,9 @@ class TDrawVariable { return true; } - const replace = 'arg.var' + (this.branches.length - 1); + const replace = `arg.var${this.branches.length - 1}`; code = code.slice(0, pos) + replace + code.slice(pos2); - pos = pos + replace.length; + pos += replace.length; } // support usage of some standard TMath functions @@ -578,10 +676,10 @@ class TDrawVariable { } /** @summary Check if it is dummy variable */ - is_dummy() { return (this.branches.length === 0) && !this.func; } + is_dummy() { return !this.branches.length && !this.func; } /** @summary Produce variable - * @desc after reading tree braches into the object, calculate variable value */ + * @desc after reading tree branches into the object, calculate variable value */ produce(obj) { this.length = 1; this.isarray = false; @@ -603,7 +701,8 @@ class TDrawVariable { this.brarray[n] = (checkArrayPrototype(arg[name]) > 0) || isRootCollection(arg[name]); // no array - no pain - if (this.brarray[n] === false) continue; + if (this.brarray[n] === false) + continue; // check if array can be used as is - one dimension and normal values if ((this.brarray[n] === true) && (checkArrayPrototype(arg[name], true) === 2)) { @@ -612,14 +711,17 @@ class TDrawVariable { } else { const iter = new ArrayIterator(arg[name], this.brarray[n], obj); arrs[n] = []; - while (iter.next()) arrs[n].push(iter.value); + while (iter.next()) + arrs[n].push(iter.value); } - if ((usearrlen < 0) || (usearrlen < arrs[n].length)) usearrlen = arrs[n].length; + if ((usearrlen < 0) || (usearrlen < arrs[n].length)) + usearrlen = arrs[n].length; } if (usearrlen < 0) { this.value = this.direct_branch ? arg.var0 : this.func(arg); - if (!this.kind) this.kind = typeof this.value; + if (!this.kind) + this.kind = typeof this.value; return; } @@ -647,7 +749,8 @@ class TDrawVariable { } } - if (!this.kind) this.kind = typeof this.value[0]; + if (!this.kind) + this.kind = typeof this.value[0]; } /** @summary Get variable */ @@ -675,9 +778,9 @@ class TDrawSelector extends TSelector { this.vars = []; // array of expression variables this.cut = null; // cut variable this.hist = null; - this.histo_drawopt = ''; + this.drawopt = ''; this.hist_name = '$htemp'; - this.hist_title = 'Result of TTree::Draw'; + this.draw_title = 'Result of TTree::Draw'; this.graph = false; this.hist_args = []; // arguments for histogram creation this.arr_limit = 1000; // number of accumulated items before create histogram @@ -688,6 +791,21 @@ class TDrawSelector extends TSelector { this.aver_diff = 0; } + /** @summary Return number of entries in the tree */ + getNumEntries(tree) { return tree?.fEntries || 0; } + + /** @summary Find branch in the tree */ + findBranch(tree, name) { return findBranchComplex(tree, name); } + + /** @summary Returns true if one can use branch as array */ + isArrayBranch(tree, br) { + if ((br.fType === kClonesNode) || (br.fType === kSTLNode)) + return true; + const objclass = getBranchObjectClass(br, tree, false, true); + if (objclass && isRootCollection(null, objclass)) + return true; + } + /** @summary Set draw selector callbacks */ setCallback(result_callback, progress_callback) { this.result_callback = result_callback; @@ -696,7 +814,8 @@ class TDrawSelector extends TSelector { /** @summary Parse parameters */ parseParameters(tree, args, expr) { - if (!expr || !isStr(expr)) return ''; + if (!expr || !isStr(expr)) + return ''; // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value let pos = expr.lastIndexOf(';'); @@ -706,28 +825,73 @@ class TDrawSelector extends TSelector { pos = expr.lastIndexOf(';'); const separ = parname.indexOf(':'); - if (separ > 0) { parvalue = parname.slice(separ + 1); parname = parname.slice(0, separ); } + if (separ > 0) { + parvalue = parname.slice(separ + 1); + parname = parname.slice(0, separ); + } let intvalue = parseInt(parvalue); - if (!parvalue || !Number.isInteger(intvalue)) intvalue = undefined; + if (!parvalue || !Number.isInteger(intvalue)) + intvalue = undefined; switch (parname) { - case 'num': + case 'elist': + if ((parvalue.at(0) === '[') && (parvalue.at(-1) === ']')) { + parvalue = parvalue.slice(1, parvalue.length - 1).replaceAll(/\s/g, ''); + args.elist = []; + let p = 0, last_v = -1; + const getInt = () => { + const p0 = p; + while ((p < parvalue.length) && (parvalue.charCodeAt(p) >= 48) && (parvalue.charCodeAt(p) < 58)) + p++; + return parseInt(parvalue.slice(p0, p)); + }; + + while (p < parvalue.length) { + const v1 = getInt(); + if (v1 <= last_v) { + console.log('position', p); + throw Error(`Wrong entry id ${v1} in elist last ${last_v}`); + } + let v2 = v1; + if (parvalue[p] === '.' && parvalue[p + 1] === '.') { + p += 2; + v2 = getInt(); + if (v2 < v1) + throw Error(`Wrong entry id ${v2} in range ${v1}`); + } + if (parvalue[p] === ',' || p === parvalue.length) { + for (let v = v1; v <= v2; ++v) { + args.elist.push(v); + last_v = v; + } + p++; + } else + throw Error('Wrong syntax for elist'); + } + } + break; case 'entries': + case 'num': case 'numentries': if (parvalue === 'all') - args.numentries = tree.fEntries; + args.numentries = this.getNumEntries(tree); else if (parvalue === 'half') - args.numentries = Math.round(tree.fEntries / 2); + args.numentries = Math.round(this.getNumEntries(tree) / 2); else if (intvalue !== undefined) args.numentries = intvalue; break; case 'first': - if (intvalue !== undefined) args.firstentry = intvalue; + if (intvalue !== undefined) + args.firstentry = intvalue; + break; + case 'nmatch': + if (intvalue !== undefined) + this.nmatch = intvalue; break; case 'mon': case 'monitor': - args.monitoring = (intvalue !== undefined) ? intvalue : 5000; + args.monitoring = intvalue ?? 5000; break; case 'player': args.player = true; @@ -735,12 +899,17 @@ class TDrawSelector extends TSelector { case 'dump': args.dump = true; break; + case 'staged': + args.staged = true; + break; case 'maxseg': case 'maxrange': - if (intvalue) tree.$file.fMaxRanges = intvalue; + if (intvalue) + tree.$file.fMaxRanges = intvalue; break; case 'accum': - if (intvalue) this.arr_limit = intvalue; + if (intvalue) + this.arr_limit = intvalue; break; case 'htype': if (parvalue && (parvalue.length === 1)) { @@ -776,94 +945,78 @@ class TDrawSelector extends TSelector { } if (harg === 'dump') args.dump = true; + else if (harg === 'elist') + args.dump_entries = true; else if (harg.indexOf('Graph') === 0) args.graph = true; else if (pos < 0) { this.want_hist = true; this.hist_name = harg; - } else if ((harg[0] === '(') && (harg[harg.length - 1] === ')')) { + } else if ((harg[0] === '(') && (harg.at(-1) === ')')) { this.want_hist = true; harg = harg.slice(1, harg.length - 1).split(','); let isok = true; for (let n = 0; n < harg.length; ++n) { harg[n] = (n % 3 === 0) ? parseInt(harg[n]) : parseFloat(harg[n]); - if (!Number.isFinite(harg[n])) isok = false; + if (!Number.isFinite(harg[n])) + isok = false; } - if (isok) this.hist_args = harg; + if (isok) + this.hist_args = harg; } } if (args.dump) { this.dump_values = true; args.reallocate_objects = true; - if (args.numentries === undefined) args.numentries = 10; + if ((args.numentries === undefined) && !args.elist) { + args.numentries = 10; + args._dflt_entries = true; + } } return expr; } - /** @summary Parse draw expression */ - parseDrawExpression(tree, args) { - // parse complete expression - let expr = this.parseParameters(tree, args, args.expr), cut = ''; - - // parse option for histogram creation - this.hist_title = `drawing '${expr}' from ${tree.fName}`; - - let pos = 0; - if (args.cut) - cut = args.cut; - else { - pos = expr.replace(/TMath::/g, 'TMath__').lastIndexOf('::'); // avoid confusion due-to :: in the namespace - if (pos > 0) { - cut = expr.slice(pos + 2).trim(); - expr = expr.slice(0, pos).trim(); - } + /** @summary Create draw expression for N-dim with cut */ + createDrawExpression(tree, names, cut, args) { + if (args.dump && names.length === 1 && names[0] === 'Entry$') { + args.dump_entries = true; + args.dump = false; } - args.parse_expr = expr; - args.parse_cut = cut; - - // let names = expr.split(':'); // to allow usage of ? operator, we need to handle : as well - const names = []; - let nbr1 = 0, nbr2 = 0, prev = 0; - for (pos = 0; pos < expr.length; ++pos) { - switch (expr[pos]) { - case '(': nbr1++; break; - case ')': nbr1--; break; - case '[': nbr2++; break; - case ']': nbr2--; break; - case ':': - if (expr[pos + 1] === ':') { pos++; continue; } - if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos)); - prev = pos + 1; - break; + if (args.dump_entries) { + this.dump_entries = true; + this.hist = []; + if (args._dflt_entries) { + delete args._dflt_entries; + delete args.numentries; } } - if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos)); - if ((names.length < 1) || (names.length > 3)) return false; + let is_direct = !cut && !this.dump_entries; this.ndim = names.length; - let is_direct = !cut; - for (let n = 0; n < this.ndim; ++n) { this.vars[n] = new TDrawVariable(this.globals); - if (!this.vars[n].parse(tree, this, names[n])) return false; - if (!this.vars[n].direct_branch) is_direct = false; + if (!this.vars[n].parse(tree, this, names[n])) + return false; + if (!this.vars[n].direct_branch) + is_direct = false; } this.cut = new TDrawVariable(this.globals); - if (cut) - if (!this.cut.parse(tree, this, cut)) return false; + if (cut && !this.cut.parse(tree, this, cut)) + return false; if (!this.numBranches()) { console.warn('no any branch is selected'); return false; } - if (is_direct) this.ProcessArrays = this.ProcessArraysFunc; + if (is_direct) + this.ProcessArrays = this.ProcessArraysFunc; this.monitoring = args.monitoring; @@ -874,18 +1027,85 @@ class TDrawSelector extends TSelector { this.graph = args.graph; if (args.drawopt !== undefined) - this.histo_drawopt = args.drawopt; + this.drawopt = args.drawopt; else - this.histo_drawopt = (this.ndim === 2) ? 'col' : ''; + args.drawopt = this.drawopt = this.graph ? 'P' : ''; return true; } + /** @summary Parse draw expression */ + parseDrawExpression(tree, args) { + // parse complete expression + let expr = this.parseParameters(tree, args, args.expr), cut = ''; + + // parse option for histogram creation + this.draw_title = `drawing '${expr}'`; + if (tree?.fName) + this.draw_title += ` from ${tree.fName}`; + + let pos; + if (args.cut) + cut = args.cut; + else { + pos = expr.replace(/TMath::/g, 'TMath__').lastIndexOf('::'); // avoid confusion due-to :: in the namespace + if (pos >= 0) { + cut = expr.slice(pos + 2).trim(); + expr = expr.slice(0, pos).trim(); + } + } + + args.parse_expr = expr; + args.parse_cut = cut; + + // let names = expr.split(':'); // to allow usage of ? operator, we need to handle : as well + let names = [], nbr1 = 0, nbr2 = 0, prev = 0; + for (pos = 0; pos < expr.length; ++pos) { + switch (expr[pos]) { + case '(': + nbr1++; + break; + case ')': + nbr1--; + break; + case '[': + nbr2++; + break; + case ']': + nbr2--; + break; + case ':': + if (expr[pos + 1] === ':') { + pos++; + continue; + } + if (!nbr1 && !nbr2 && (pos > prev)) + names.push(expr.slice(prev, pos)); + prev = pos + 1; + break; + } + } + if (!nbr1 && !nbr2 && (pos > prev)) + names.push(expr.slice(prev, pos)); + + if (args.staged) { + args.staged_names = names; + names = ['Entry$']; + args.dump_entries = true; + } else if (cut && args.dump_entries) + names = ['Entry$']; + else if ((names.length < 1) || (names.length > 3)) + return false; + + return this.createDrawExpression(tree, names, cut, args); + } + /** @summary Draw only specified branch */ drawOnlyBranch(tree, branch, expr, args) { this.ndim = 1; - if (expr.indexOf('dump') === 0) expr = ';' + expr; + if (expr.indexOf('dump') === 0) + expr = ';' + expr; expr = this.parseParameters(tree, args, expr); @@ -898,12 +1118,10 @@ class TDrawSelector extends TSelector { if (this.dump_values) { this.hist = []; // array of dump objects - this.leaf = args.leaf; // branch object remains, therefore we need to copy fields to see them all - this.copy_fields = ((args.branch.fLeaves && (args.branch.fLeaves.arr.length > 1)) || - (args.branch.fBranches && (args.branch.fBranches.arr.length > 0))) && !args.leaf; + this.copy_fields = ((args.branch.fLeaves?.arr.length > 1) || args.branch.fBranches?.arr.length) && !args.leaf; this.addBranch(branch, 'br0', args.direct_branch); // add branch @@ -913,19 +1131,21 @@ class TDrawSelector extends TSelector { } this.vars[0] = new TDrawVariable(this.globals); - if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch)) return false; - this.hist_title = `drawing branch ${branch.fName} ${expr?' expr:'+expr:''} from ${tree.fName}`; + if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch)) + return false; + this.draw_title = `drawing branch ${branch.fName} ${expr ? ' expr:' + expr : ''} from ${tree.fName ?? ''}`; this.cut = new TDrawVariable(this.globals); - if (this.vars[0].direct_branch) this.ProcessArrays = this.ProcessArraysFunc; + if (this.vars[0].direct_branch) + this.ProcessArrays = this.ProcessArraysFunc; return true; } /** @summary Begin processing */ Begin(tree) { - this.globals.entries = tree.fEntries; + this.globals.entries = this.getNumEntries(tree); if (this.monitoring) this.lasttm = new Date().getTime(); @@ -950,26 +1170,31 @@ class TDrawSelector extends TSelector { /** @summary Get min.max bins */ getMinMaxBins(axisid, nbins) { const res = { min: 0, max: 0, nbins, k: 1, fLabels: null, title: '' }; - if (axisid >= this.ndim) return res; + if (axisid >= this.ndim) + return res; const arr = this.vars[axisid].buf; - res.title = this.vars[axisid].code || ''; if (this.vars[axisid].kind === 'object') { // this is any object type let typename, similar = true, maxbits = 8; for (let k = 0; k < arr.length; ++k) { - if (!arr[k]) continue; - if (!typename) typename = arr[k]._typename; - if (typename !== arr[k]._typename) similar = false; // check all object types - if (arr[k].fNbits) maxbits = Math.max(maxbits, arr[k].fNbits + 1); + if (!arr[k]) + continue; + if (!typename) + typename = arr[k]._typename; + if (typename !== arr[k]._typename) + similar = false; // check all object types + if (arr[k].fNbits) + maxbits = Math.max(maxbits, arr[k].fNbits + 1); } if (typename && similar) { if ((typename === 'TBits') && (axisid === 0)) { this.fill1DHistogram = this.fillTBitsHistogram; - if (maxbits % 8) maxbits = (maxbits & 0xfff0) + 8; + if (maxbits % 8) + maxbits = (maxbits & 0xfff0) + 8; if ((this.hist_name === 'bits') && (this.hist_args.length === 1) && this.hist_args[0]) maxbits = this.hist_args[0]; @@ -979,27 +1204,18 @@ class TDrawSelector extends TSelector { } } - if (this.vars[axisid].kind === 'string') { + if (this.vars[axisid].kind === 'boolean') { + res.lbls = ['false', 'true']; + this.fill1DHistogram = this.fillBooleanHistogram; + } else if (this.vars[axisid].kind === 'string') { res.lbls = []; // all labels - for (let k = 0; k < arr.length; ++k) { if (res.lbls.indexOf(arr[k]) < 0) res.lbls.push(arr[k]); } - res.lbls.sort(); - res.max = res.nbins = res.lbls.length; - - res.fLabels = create(clTHashList); - for (let k = 0; k < res.lbls.length; ++k) { - const s = create(clTObjString); - s.fString = res.lbls[k]; - s.fUniqueID = k + 1; - if (s.fString === '') s.fString = '<empty>'; - res.fLabels.Add(s); - } } else if ((axisid === 0) && (this.hist_name === 'bits') && (this.hist_args.length <= 1)) { - this.fill1DHistogram = this.FillBitsHistogram; + this.fill1DHistogram = this.fillBitsHistogram; return this.getBitsBins(this.hist_args[0] || 32, res); } else if (axisid * 3 + 2 < this.hist_args.length) { res.nbins = this.hist_args[axisid * 3]; @@ -1009,7 +1225,8 @@ class TDrawSelector extends TSelector { let is_any = false; for (let i = 1; i < arr.length; ++i) { const v = arr[i]; - if (!Number.isFinite(v)) continue; + if (!Number.isFinite(v)) + continue; if (is_any) { res.min = Math.min(res.min, v); res.max = Math.max(res.max, v); @@ -1018,15 +1235,22 @@ class TDrawSelector extends TSelector { is_any = true; } } - if (!is_any) { res.min = 0; res.max = 1; } + if (!is_any) { + res.min = 0; + res.max = 1; + } if (this.hist_nbins) nbins = res.nbins = this.hist_nbins; res.isinteger = (Math.round(res.min) === res.min) && (Math.round(res.max) === res.max); if (res.isinteger) { - for (let k = 0; k < arr.length; ++k) - if (arr[k] !== Math.round(arr[k])) { res.isinteger = false; break; } + for (let k = 0; k < arr.length; ++k) { + if (arr[k] !== Math.round(arr[k])) { + res.isinteger = false; + break; + } + } } if (res.isinteger) { @@ -1039,21 +1263,44 @@ class TDrawSelector extends TSelector { } else { const range = (res.max - res.min + 2); let step = Math.floor(range / nbins); - while (step * nbins < range) step++; + while (step * nbins < range) + step++; res.max = res.min + nbins * step; } } else if (res.min >= res.max) { res.max = res.min; - if (Math.abs(res.min) < 100) { res.min -= 1; res.max += 1; } else - if (res.min > 0) { res.min *= 0.9; res.max *= 1.1; } else { res.min *= 1.1; res.max *= 0.9; } + if (Math.abs(res.min) < 100) { + res.min -= 1; + res.max += 1; + } else if (res.min > 0) { + res.min *= 0.9; + res.max *= 1.1; + } else { + res.min *= 1.1; + res.max *= 0.9; + } } else res.max += (res.max - res.min) / res.nbins; } + if (res.lbls) { + res.max = res.nbins = res.lbls.length; + + res.fLabels = create(clTHashList); + for (let k = 0; k < res.lbls.length; ++k) { + const s = create(clTObjString); + s.fString = res.lbls[k]; + s.fUniqueID = k + 1; + if (s.fString === '') + s.fString = '<empty>'; + res.fLabels.Add(s); + } + } + res.k = res.nbins / (res.max - res.min); res.GetBin = function(value) { - const bin = this.lbls?.indexOf(value) ?? Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1; + const bin = this.lbls?.indexOf(value) ?? (Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1); return bin < 0 ? 0 : ((bin > this.nbins) ? this.nbins + 1 : bin + 1); }; @@ -1062,7 +1309,8 @@ class TDrawSelector extends TSelector { /** @summary Create histogram which matches value in dimensions */ createHistogram(nbins, set_hist = false) { - if (!nbins) nbins = 20; + if (!nbins) + nbins = 20; const x = this.getMinMaxBins(0, nbins), y = this.getMinMaxBins(1, nbins), @@ -1080,19 +1328,21 @@ class TDrawSelector extends TSelector { hist.fXaxis.fXmax = x.max; hist.fXaxis.fLabels = x.fLabels; - if (this.ndim > 1) hist.fYaxis.fTitle = y.title; + if (this.ndim > 1) + hist.fYaxis.fTitle = y.title; hist.fYaxis.fXmin = y.min; hist.fYaxis.fXmax = y.max; hist.fYaxis.fLabels = y.fLabels; - if (this.ndim > 2) hist.fZaxis.fTitle = z.title; + if (this.ndim > 2) + hist.fZaxis.fTitle = z.title; hist.fZaxis.fXmin = z.min; hist.fZaxis.fXmax = z.max; hist.fZaxis.fLabels = z.fLabels; hist.fName = this.hist_name; - hist.fTitle = this.hist_title; - hist.fOption = this.histo_drawopt; + hist.fTitle = this.draw_title; + hist.fOption = this.drawopt; hist.$custom_stat = (this.hist_name === '$htemp') ? 111110 : 111111; if (set_hist) { @@ -1101,17 +1351,18 @@ class TDrawSelector extends TSelector { this.y = y; this.z = z; } else - hist.fBits = hist.fBits | kNoStats; + hist.fBits |= kNoStats; return hist; } /** @summary Create output object - histogram, graph, dump array */ createOutputObject() { - if (this.hist || !this.vars[0].buf) return; + if (this.hist || !this.vars[0].buf) + return; if (this.dump_values) { - // just create array where dumped valus will be collected + // just create array where dumped values will be collected this.hist = []; // reassign fill method @@ -1124,21 +1375,21 @@ class TDrawSelector extends TSelector { // A 1-dimensional graph will just have the x axis as an index res = createTGraph(N, Array.from(Array(N).keys()), this.vars[0].buf); res.fName = 'Graph'; - res.fTitle = this.hist_title; + res.fTitle = this.draw_title; } else if (this.ndim === 2) { res = createTGraph(N, this.vars[0].buf, this.vars[1].buf); res.fName = 'Graph'; - res.fTitle = this.hist_title; + res.fTitle = this.draw_title; delete this.vars[1].buf; } else if (this.ndim === 3) { res = create(clTPolyMarker3D); res.fN = N; res.fLastPoint = N - 1; - const arr = new Array(N*3); - for (let k = 0; k< N; ++k) { - arr[k*3] = this.vars[0].buf[k]; - arr[k*3+1] = this.vars[1].buf[k]; - arr[k*3+2] = this.vars[2].buf[k]; + const arr = new Array(N * 3); + for (let k = 0; k < N; ++k) { + arr[k * 3] = this.vars[0].buf[k]; + arr[k * 3 + 1] = this.vars[1].buf[k]; + arr[k * 3 + 2] = this.vars[2].buf[k]; } res.fP = arr; res.$hist = this.createHistogram(10); @@ -1186,7 +1437,8 @@ class TDrawSelector extends TSelector { /** @summary Fill TBits histogram */ fillTBitsHistogram(xvalue, weight) { - if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return; + if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) + return; const sz = Math.min(xvalue.fNbits + 1, xvalue.fNbytes * 8); @@ -1199,20 +1451,36 @@ class TDrawSelector extends TSelector { } mask *= 2; - if (mask >= 0x100) { mask = 1; ++b; } + if (mask >= 0x100) { + mask = 1; + ++b; + } } } /** @summary Fill bits histogram */ - FillBitsHistogram(xvalue, weight) { - if (!weight) return; + fillBitsHistogram(xvalue, weight) { + if (!weight) + return; for (let bit = 0, mask = 1; bit < this.x.nbins; ++bit) { - if (xvalue & mask) this.hist.fArray[bit + 1] += weight; + if (xvalue & mask) + this.hist.fArray[bit + 1] += weight; mask *= 2; } } + /** @summary Fill boolean histogram */ + fillBooleanHistogram(boolvalue, weight) { + if (!weight) + return; + const xvalue = boolvalue ? 1 : 0; + this.hist.fArray[xvalue + 1] += weight; + this.hist.fTsumw += weight; + this.hist.fTsumwx += weight * xvalue; + this.hist.fTsumwx2 += weight * xvalue * xvalue; + } + /** @summary Fill 1D histogram */ fill1DHistogram(xvalue, weight) { const bin = this.x.GetBin(xvalue); @@ -1281,29 +1549,35 @@ class TDrawSelector extends TSelector { this.hist.push(obj); } - /** @summary function used when all branches can be read as array - * @desc most typical usage - histogramming of single branch */ + /** @summary function used when all branches can be read as array + * @desc most typical usage - histogram filling of single branch */ ProcessArraysFunc(/* entry */) { if (this.arr_limit || this.graph) { const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], len = this.tgtarr.br0.length; - if ((var0.buf.length === 0) && (len >= this.arr_limit) && !this.graph) { + if (!var0.buf.length && (len >= this.arr_limit) && !this.graph) { // special use case - first array large enough to create histogram directly base on it var0.buf = this.tgtarr.br0; - if (var1) var1.buf = this.tgtarr.br1; - if (var2) var2.buf = this.tgtarr.br2; + if (var1) + var1.buf = this.tgtarr.br1; + if (var2) + var2.buf = this.tgtarr.br2; } else { for (let k = 0; k < len; ++k) { var0.buf.push(this.tgtarr.br0[k]); - if (var1) var1.buf.push(this.tgtarr.br1[k]); - if (var2) var2.buf.push(this.tgtarr.br2[k]); + if (var1) + var1.buf.push(this.tgtarr.br1[k]); + if (var2) + var2.buf.push(this.tgtarr.br2[k]); } } var0.kind = 'number'; - if (var1) var1.kind = 'number'; - if (var2) var2.kind = 'number'; + if (var1) + var1.kind = 'number'; + if (var2) + var2.kind = 'number'; this.cut.buf = null; // do not create buffer for cuts if (!this.graph && (var0.buf.length >= this.arr_limit)) { this.createOutputObject(); @@ -1351,14 +1625,17 @@ class TDrawSelector extends TSelector { this.globals.entry = entry; // can be used in any expression this.cut.produce(this.tgtobj); - if (!this.dump_values && !this.cut.value) return; + if (!this.dump_values && !this.cut.value) + return; for (let n = 0; n < this.ndim; ++n) this.vars[n].produce(this.tgtobj); const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut; - if (this.graph || this.arr_limit) { + if (this.dump_entries) + this.hist.push(entry); + else if (this.graph || this.arr_limit) { switch (this.ndim) { case 1: for (let n0 = 0; n0 < var0.length; ++n0) { @@ -1423,6 +1700,12 @@ class TDrawSelector extends TSelector { this.progress_callback(this.hist); } } + + if ((this.nmatch !== undefined) && (--this.nmatch <= 0)) { + if (!this.hist) + this.createOutputObject(); + this.Abort(); + } } /** @summary Normal TSelector Terminate handler */ @@ -1439,78 +1722,37 @@ class TDrawSelector extends TSelector { } // class TDrawSelector -/** @summary return TStreamerElement associated with the branch - if any - * @desc unfortunately, branch.fID is not number of element in streamer info - * @private */ -function findBrachStreamerElement(branch, file) { - if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) return null; - - const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum), - arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null; - if (!arr) return null; - - let match_name = branch.fName, - pos = match_name.indexOf('['); - if (pos > 0) match_name = match_name.slice(0, pos); - pos = match_name.lastIndexOf('.'); - if (pos > 0) match_name = match_name.slice(pos + 1); - - function match_elem(elem) { - if (!elem) return false; - if (elem.fName !== match_name) return false; - if (elem.fType === branch.fStreamerType) return true; - if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) return true; - if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) || - (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) && - (elem.fType === kStreamer)) return true; - console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`); - return false; - } - - // first check branch fID - in many cases gut guess - if (match_elem(arr[branch.fID])) - return arr[branch.fID]; - - for (let k = 0; k < arr.length; ++k) { - if ((k !== branch.fID) && match_elem(arr[k])) - return arr[k]; - } - - console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`); - - return null; -} - /** @summary return type name of given member in the class * @private */ function defineMemberTypeName(file, parent_class, member_name) { const s_i = file.findStreamerInfo(parent_class), arr = s_i?.fElements?.arr; - if (!arr) return ''; + if (!arr) + return ''; let elem = null; for (let k = 0; k < arr.length; ++k) { if (arr[k].fTypeName === kBaseClass) { const res = defineMemberTypeName(file, arr[k].fName, member_name); - if (res) return res; - } else - if (arr[k].fName === member_name) { elem = arr[k]; break; } + if (res) + return res; + } else if (arr[k].fName === member_name) { + elem = arr[k]; + break; + } } - if (!elem) return ''; + if (!elem) + return ''; - let clname = elem.fTypeName; - if (clname[clname.length - 1] === '*') - clname = clname.slice(0, clname.length - 1); - - return clname; + const clname = elem.fTypeName; + return clname.at(-1) === '*' ? clname.slice(0, clname.length - 1) : clname; } /** @summary create fast list to assign all methods to the object * @private */ function makeMethodsList(typename) { - const methods = getMethods(typename), - res = { + const methods = getMethods(typename), res = { names: [], values: [], Create() { @@ -1548,9 +1790,11 @@ function detectBranchMemberClass(brlst, prefix, start) { * @param {object} [args] - different arguments * @param {number} [args.firstentry] - first entry to process, 0 when not specified * @param {number} [args.numentries] - number of entries to process, all when not specified + * @param {Array} [args.elist] - arrays of entries id to process * @return {Promise} with TSelector instance */ async function treeProcess(tree, selector, args) { - if (!args) args = {}; + if (!args) + args = {}; if (!selector || !tree.$file || !selector.numBranches()) { selector?.Terminate(false); @@ -1569,7 +1813,7 @@ async function treeProcess(tree, selector, args) { process_arrays: true // one can process all branches as arrays }, createLeafElem = (leaf, name) => { // function creates TStreamerElement which corresponds to the elementary leaf - let datakind = 0; + let datakind; switch (leaf._typename) { case 'TLeafF': datakind = kFloat; break; case 'TLeafD': datakind = kDouble; break; @@ -1582,7 +1826,7 @@ async function treeProcess(tree, selector, args) { default: return null; } const elem = createStreamerElement(name || leaf.fName, datakind); - if (leaf.fLen > 1) { + if ((leaf.fLen > 1) && (datakind !== kTString)) { elem.fType += kOffsetL; elem.fArrayLength = leaf.fLen; } @@ -1590,30 +1834,34 @@ async function treeProcess(tree, selector, args) { }, findInHandle = branch => { for (let k = 0; k < handle.arr.length; ++k) { if (handle.arr[k].branch === branch) - return handle.arr[k]; + return handle.arr[k]; } return null; }; let namecnt = 0; - function AddBranchForReading(branch, target_object, target_name, read_mode) { + function addBranchForReading(branch, target_object, target_name, read_mode) { // central method to add branch for reading // read_mode == true - read only this branch - // read_mode == '$child$' is just member of object from for STL or clonesarray - // read_mode == '<any class name>' is sub-object from STL or clonesarray, happens when such new object need to be created + // read_mode == '$child$' is just member of object from for STL or clones array + // read_mode == '<any class name>' is sub-object from STL or clones array, happens when such new object need to be created // read_mode == '.member_name' select only reading of member_name instead of complete object if (isStr(branch)) branch = findBranch(handle.tree, branch); - if (!branch) { console.error('Did not found branch'); return null; } + if (!branch) { + console.error('Did not found branch'); + return null; + } let item = findInHandle(branch); if (item) { console.error(`Branch ${branch.fName} already configured for reading`); - if (item.tgt !== target_object) console.error('Target object differs'); + if (item.tgt !== target_object) + console.error('Target object differs'); return null; } @@ -1622,8 +1870,6 @@ async function treeProcess(tree, selector, args) { return null; } - // console.log(`Add branch ${branch.fName}`); - item = { branch, tgt: target_object, // used target object - can be differ for object members @@ -1639,6 +1885,8 @@ async function treeProcess(tree, selector, args) { staged_entry: -1, // entry which is staged for reading first_readentry: -1, // first entry to read staged_basket: 0, // last basket staged for reading + eindx: 0, // index of last checked entry when selecting baskets + selected_baskets: [], // array of selected baskets, used when specific events are selected numentries: branch.fEntries, numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file counters: null, // branch indexes used as counters @@ -1648,18 +1896,22 @@ async function treeProcess(tree, selector, args) { staged_now: 0, // entry limit of current I/O request progress_showtm: 0, // last time when progress was showed getBasketEntry(k) { - if (!this.branch || (k > this.branch.fMaxBaskets)) return 0; + if (!this.branch || (k > this.branch.fMaxBaskets)) + return 0; const res = (k < this.branch.fMaxBaskets) ? this.branch.fBasketEntry[k] : 0; - if (res) return res; + if (res) + return res; const bskt = (k > 0) ? this.branch.fBaskets.arr[k - 1] : null; return bskt ? (this.branch.fBasketEntry[k - 1] + bskt.fNevBuf) : 0; }, getTarget(tgtobj) { // returns target object which should be used for the branch reading - if (!this.tgt) return tgtobj; + if (!this.tgt) + return tgtobj; for (let k = 0; k < this.tgt.length; ++k) { const sub = this.tgt[k]; - if (!tgtobj[sub.name]) tgtobj[sub.name] = sub.lst.Create(); + if (!tgtobj[sub.name]) + tgtobj[sub.name] = sub.lst.Create(); tgtobj = tgtobj[sub.name]; } return tgtobj; @@ -1683,7 +1935,8 @@ async function treeProcess(tree, selector, args) { }; // last basket can be stored directly with the branch - while (item.getBasketEntry(item.numbaskets + 1)) item.numbaskets++; + while (item.getBasketEntry(item.numbaskets + 1)) + item.numbaskets++; // check all counters if we const nb_leaves = branch.fLeaves?.arr?.length ?? 0, @@ -1692,15 +1945,18 @@ async function treeProcess(tree, selector, args) { let elem = null, // TStreamerElement used to create reader member = null, // member for actual reading of the branch child_scan = 0, // scan child branches after main branch is appended - item_cnt = null, item_cnt2 = null, object_class = ''; + item_cnt = null, item_cnt2 = null, object_class; if (branch.fBranchCount) { item_cnt = findInHandle(branch.fBranchCount); if (!item_cnt) - item_cnt = AddBranchForReading(branch.fBranchCount, target_object, '$counter' + namecnt++, true); + item_cnt = addBranchForReading(branch.fBranchCount, target_object, '$counter' + namecnt++, true); - if (!item_cnt) { console.error(`Cannot add counter branch ${branch.fBranchCount.fName}`); return null; } + if (!item_cnt) { + console.error(`Cannot add counter branch ${branch.fBranchCount.fName}`); + return null; + } let BranchCount2 = branch.fBranchCount2; @@ -1719,50 +1975,66 @@ async function treeProcess(tree, selector, args) { } } - if (!BranchCount2) console.error('Did not found branch for second counter of kStreamLoop element'); + if (!BranchCount2) + console.error('Did not found branch for second counter of kStreamLoop element'); } if (BranchCount2) { item_cnt2 = findInHandle(BranchCount2); - if (!item_cnt2) item_cnt2 = AddBranchForReading(BranchCount2, target_object, '$counter' + namecnt++, true); + if (!item_cnt2) + item_cnt2 = addBranchForReading(BranchCount2, target_object, '$counter' + namecnt++, true); - if (!item_cnt2) { console.error(`Cannot add counter branch2 ${BranchCount2.fName}`); return null; } + if (!item_cnt2) { + console.error(`Cannot add counter branch2 ${BranchCount2.fName}`); + return null; + } } - } else if (nb_leaves === 1 && leaf && leaf.fLeafCount) { + } else if (nb_leaves === 1 && leaf?.fLeafCount) { const br_cnt = findBranch(handle.tree, leaf.fLeafCount.fName); if (br_cnt) { item_cnt = findInHandle(br_cnt); - if (!item_cnt) item_cnt = AddBranchForReading(br_cnt, target_object, '$counter' + namecnt++, true); + if (!item_cnt) + item_cnt = addBranchForReading(br_cnt, target_object, '$counter' + namecnt++, true); - if (!item_cnt) { console.error(`Cannot add counter branch ${br_cnt.fName}`); return null; } + if (!item_cnt) { + console.error(`Cannot add counter branch ${br_cnt.fName}`); + return null; + } } } - function ScanBranches(lst, master_target, chld_kind) { - if (!lst || !lst.arr.length) return true; + function scanBranches(lst, master_target, chld_kind) { + if (!lst?.arr.length) + return true; let match_prefix = branch.fName; - if (match_prefix[match_prefix.length - 1] === '.') match_prefix = match_prefix.slice(0, match_prefix.length - 1); - if (isStr(read_mode) && (read_mode[0] === '.')) match_prefix += read_mode; + if (match_prefix.at(-1) === '.') + match_prefix = match_prefix.slice(0, match_prefix.length - 1); + if (isStr(read_mode) && (read_mode[0] === '.')) + match_prefix += read_mode; match_prefix += '.'; for (let k = 0; k < lst.arr.length; ++k) { const br = lst.arr[k]; - if ((chld_kind > 0) && (br.fType !== chld_kind)) continue; + if ((chld_kind > 0) && (br.fType !== chld_kind)) + continue; if (br.fType === kBaseClassNode) { - if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false; + if (!scanBranches(br.fBranches, master_target, chld_kind)) + return false; continue; } - const elem = findBrachStreamerElement(br, handle.file); - if (elem?.fTypeName === kBaseClass) { + const elem2 = findBrachStreamerElement(br, handle.file); + if (elem2?.fTypeName === kBaseClass) { // if branch is data of base class, map it to original target - if (br.fTotBytes && !AddBranchForReading(br, target_object, target_name, read_mode)) return false; - if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false; + if (br.fTotBytes && !addBranchForReading(br, target_object, target_name, read_mode)) + return false; + if (!scanBranches(br.fBranches, master_target, chld_kind)) + return false; continue; } @@ -1774,17 +2046,21 @@ async function treeProcess(tree, selector, args) { continue; // for defined children names prefix must be present let p = subname.indexOf('['); - if (p > 0) subname = subname.slice(0, p); + if (p > 0) + subname = subname.slice(0, p); p = subname.indexOf('<'); - if (p > 0) subname = subname.slice(0, p); + if (p > 0) + subname = subname.slice(0, p); if (chld_kind > 0) { chld_direct = '$child$'; const pp = subname.indexOf('.'); - if (pp > 0) chld_direct = detectBranchMemberClass(lst, branch.fName + '.' + subname.slice(0, pp + 1), k) || clTObject; + if (pp > 0) + chld_direct = detectBranchMemberClass(lst, branch.fName + '.' + subname.slice(0, pp + 1), k) || clTObject; } - if (!AddBranchForReading(br, master_target, subname, chld_direct)) return false; + if (!addBranchForReading(br, master_target, subname, chld_direct)) + return false; } return true; @@ -1796,8 +2072,7 @@ async function treeProcess(tree, selector, args) { typename: branch.fClassName, virtual: leaf.fVirtual, func(buf, obj) { - let clname = this.typename; - if (this.virtual) clname = buf.readFastString(buf.ntou1() + 1); + const clname = this.virtual ? buf.readFastString(buf.ntou1() + 1) : this.typename; obj[this.name] = buf.classStreamer({}, clname); } }; @@ -1821,7 +2096,8 @@ async function treeProcess(tree, selector, args) { arr.length = size; // reallocate array } - while (n < size) arr[n++] = this.methods.Create(); // create new objects + while (n < size) + arr[n++] = this.methods.Create(); // create new objects } }; @@ -1845,14 +2121,13 @@ async function treeProcess(tree, selector, args) { handle.process_arrays = false; - const newtgt = new Array(target_object ? (target_object.length + 1) : 1); + const newtgt = new Array((target_object?.length || 0) + 1); for (let l = 0; l < newtgt.length - 1; ++l) newtgt[l] = target_object[l]; newtgt[newtgt.length - 1] = { name: target_name, lst: makeMethodsList(object_class) }; - if (!ScanBranches(branch.fBranches, newtgt, 0)) return null; - - return item; // this kind of branch does not have baskets and not need to be read + // this kind of branch does not have baskets and not need to be read + return scanBranches(branch.fBranches, newtgt, 0) ? item : null; } else if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID === -1)) { elem = createStreamerElement(target_name, branch.fClassName); @@ -1861,7 +2136,7 @@ async function treeProcess(tree, selector, args) { if (!streamer) { elem = null; console.warn('not found streamer!'); - } else { + } else { member = { name: target_name, typename: branch.fClassName, @@ -1897,7 +2172,8 @@ async function treeProcess(tree, selector, args) { let isok = true; for (let l = 0; l < nb_leaves; ++l) { leaves[l] = createMemberStreamer(createLeafElem(branch.fLeaves.arr[l]), handle.file); - if (!leaves[l]) isok = false; + if (!leaves[l]) + isok = false; } if (isok) { @@ -1906,7 +2182,8 @@ async function treeProcess(tree, selector, args) { leaves, func(buf, obj) { let tgt = obj[this.name], l = 0; - if (!tgt) obj[this.name] = tgt = {}; + if (!tgt) + obj[this.name] = tgt = {}; while (l < this.leaves.length) this.leaves[l++].func(buf, tgt); } @@ -1925,7 +2202,8 @@ async function treeProcess(tree, selector, args) { if ((member.base !== undefined) && member.basename) { // when element represent base class, we need handling which differ from normal IO member.func = function(buf, obj) { - if (!obj[this.name]) obj[this.name] = { _typename: this.basename }; + if (!obj[this.name]) + obj[this.name] = { _typename: this.basename }; buf.classStreamer(obj[this.name], this.basename); }; } @@ -1949,7 +2227,8 @@ async function treeProcess(tree, selector, args) { member.methods1 = makeMethodsList(member.subtype1); member.get = function(arr, n) { let obj1 = arr[n][this.name1]; - if (!obj1) obj1 = arr[n][this.name1] = this.methods1.Create(); + if (!obj1) + obj1 = arr[n][this.name1] = this.methods1.Create(); return obj1; }; } else { @@ -1975,10 +2254,12 @@ async function treeProcess(tree, selector, args) { } member.get = function(arr, n) { let obj1 = arr[n][this.snames[0]]; - if (!obj1) obj1 = arr[n][this.snames[0]] = this.smethods[0].Create(); + if (!obj1) + obj1 = arr[n][this.snames[0]] = this.smethods[0].Create(); for (let k = 1; k < this.snames.length; ++k) { let obj2 = obj1[this.snames[k]]; - if (!obj2) obj2 = obj1[this.snames[k]] = this.smethods[k].Create(); + if (!obj2) + obj2 = obj1[this.snames[k]] = this.smethods[k].Create(); obj1 = obj2; } return obj1; @@ -2082,7 +2363,8 @@ async function treeProcess(tree, selector, args) { func(buf, obj) { const cnt = obj[this.stl_size], arr = new Array(cnt); for (let n = 0; n < cnt; ++n) { - if (this.loop_size) obj.$loop_size = obj[this.loop_size][n]; + if (this.loop_size) + obj.$loop_size = obj[this.loop_size][n]; this.member0.func(buf, obj); arr[n] = obj.$stl_member; } @@ -2100,7 +2382,8 @@ async function treeProcess(tree, selector, args) { member.name = target_name; item.member = member; // member for reading - if (elem) item.type = elem.fType; + if (elem) + item.type = elem.fType; item.index = handle.arr.length; // index in the global list of branches if (item_cnt) { @@ -2116,15 +2399,15 @@ async function treeProcess(tree, selector, args) { handle.arr.push(item); // now one should add all other child branches - if (child_scan) - if (!ScanBranches(branch.fBranches, target_object, child_scan)) return null; + if (child_scan && !scanBranches(branch.fBranches, target_object, child_scan)) + return null; return item; } // main loop to add all branches from selector for reading for (let nn = 0; nn < selector.numBranches(); ++nn) { - const item = AddBranchForReading(selector.getBranch(nn), undefined, selector.nameOfBranch(nn), selector._directs[nn]); + const item = addBranchForReading(selector.getBranch(nn), undefined, selector.nameOfBranch(nn), selector._directs[nn]); if (!item) { selector.Terminate(false); @@ -2137,7 +2420,8 @@ async function treeProcess(tree, selector, args) { for (let h = 1; (h < handle.arr.length) && handle.simple_read; ++h) { const item = handle.arr[h], item0 = handle.arr[0]; - if ((item.numentries !== item0.numentries) || (item.numbaskets !== item0.numbaskets)) handle.simple_read = false; + if ((item.numentries !== item0.numentries) || (item.numbaskets !== item0.numbaskets)) + handle.simple_read = false; for (let n = 0; n < item.numbaskets; ++n) { if (item.getBasketEntry(n) !== item0.getBasketEntry(n)) handle.simple_read = false; @@ -2164,15 +2448,21 @@ async function treeProcess(tree, selector, args) { let resolveFunc, rejectFunc; // Promise methods + if (args.elist) { + args.firstentry = args.elist.at(0); + args.numentries = args.elist.at(-1) - args.elist.at(0) + 1; + handle.process_entries = args.elist; + handle.process_entries_indx = 0; + handle.process_arrays = false; // do not use arrays process for selected entries + } + if (Number.isInteger(args.firstentry) && (args.firstentry > handle.firstentry) && (args.firstentry < handle.lastentry)) handle.process_min = args.firstentry; handle.current_entry = handle.staged_now = handle.process_min; - if (Number.isInteger(args.numentries) && (args.numentries > 0)) { - const max = handle.process_min + args.numentries; - if (max < handle.process_max) handle.process_max = max; - } + if (Number.isInteger(args.numentries) && (args.numentries > 0)) + handle.process_max = Math.min(handle.process_max, handle.process_min + args.numentries); if (isFunc(selector.ProcessArrays) && handle.simple_read) { // this is indication that selector can process arrays of values @@ -2205,19 +2495,20 @@ async function treeProcess(tree, selector, args) { handle.process_arrays = false; /** read basket with tree data, selecting different files */ - function ReadBaskets(bitems) { - function ExtractPlaces() { + function readBaskets(bitems) { + function extractPlaces() { // extract places to read and define file name const places = []; let filename = ''; for (let n = 0; n < bitems.length; ++n) { - if (bitems[n].done) continue; + if (bitems[n].done) + continue; const branch = bitems[n].branch; - if (places.length === 0) + if (!places.length) filename = branch.fFileName; else if (filename !== branch.fFileName) continue; @@ -2227,31 +2518,34 @@ async function treeProcess(tree, selector, args) { places.push(branch.fBasketSeek[bitems[n].basket], branch.fBasketBytes[bitems[n].basket]); } - return places.length > 0 ? { places, filename } : null; + return places.length ? { places, filename } : null; } - function ReadProgress(value) { - if ((handle.staged_prev === handle.staged_now) || - (handle.process_max <= handle.process_min)) return; + function readProgress(value) { + if ((handle.staged_prev === handle.staged_now) || (handle.process_max <= handle.process_min)) + return; const tm = new Date().getTime(); - if (tm - handle.progress_showtm < 500) return; // no need to show very often + if (tm - handle.progress_showtm < 500) + return; // no need to show very often handle.progress_showtm = tm; const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) / (handle.process_max - handle.process_min); - return handle.selector.ShowProgress(portion); + return handle.selector.ShowProgress(portion); } - function ProcessBlobs(blobs, places) { + function processBlobs(blobs, places) { if (!blobs || ((places.length > 2) && (blobs.length * 2 !== places.length))) return Promise.resolve(null); - if (places.length === 2) blobs = [blobs]; + if (places.length === 2) + blobs = [blobs]; - function DoProcessing(k) { + function doProcessing(k) { for (; k < bitems.length; ++k) { - if (!bitems[k].selected) continue; + if (!bitems[k].selected) + continue; bitems[k].selected = false; bitems[k].done = true; @@ -2273,10 +2567,10 @@ async function treeProcess(tree, selector, args) { bitems[k].raw = buf; // here already unpacked buffer - if (bitems[k].branch.fEntryOffsetLen > 0) + if (bitems[k].branch.fEntryOffsetLen > 0) buf.readBasketEntryOffset(basket, buf.raw_shift); - continue; + continue; } // unpack data and create new blob @@ -2293,34 +2587,37 @@ async function treeProcess(tree, selector, args) { if (bitems[k].branch.fEntryOffsetLen > 0) buf.readBasketEntryOffset(basket, buf.raw_shift); - return DoProcessing(k+1); // continue processing + return doProcessing(k + 1); // continue processing }); } - const req = ExtractPlaces(); + const req = extractPlaces(); if (req) - return handle.file.readBuffer(req.places, req.filename, ReadProgress).then(blobs => ProcessBlobs(blobs)).catch(() => { return null; }); + return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs2 => processBlobs(blobs2)).catch(() => null); return Promise.resolve(bitems); - } + } - return DoProcessing(0); + return doProcessing(0); } - const req = ExtractPlaces(); + const req = extractPlaces(); // extract places where to read if (req) - return handle.file.readBuffer(req.places, req.filename, ReadProgress).then(blobs => ProcessBlobs(blobs, req.places)).catch(() => { return null; }); + return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs => processBlobs(blobs, req.places)).catch(() => { return null; }); return Promise.resolve(null); } - function ReadNextBaskets() { - const bitems = []; - let totalsz = 0, isany = true, is_direct = false, min_staged = handle.process_max; + let processBaskets = null; + + function readNextBaskets() { + const bitems = [], max_ranges = tree.$file?.fMaxRanges || settings.MaxRanges, + select_entries = handle.process_entries !== undefined; + let total_size = 0, total_nsegm = 0, isany = true, is_direct = false, min_staged = handle.process_max; - while ((totalsz < 1e6) && isany) { + while (isany && (total_size < settings.TreeReadBunchSize) && (!total_nsegm || (total_nsegm + handle.arr.length <= max_ranges))) { isany = false; // very important, loop over branches in reverse order // let check counter branch after reading of normal branch is prepared @@ -2328,26 +2625,45 @@ async function treeProcess(tree, selector, args) { const elem = handle.arr[n]; while (elem.staged_basket < elem.numbaskets) { - const k = elem.staged_basket++; + const k = elem.staged_basket++, + bskt_emin = elem.getBasketEntry(k), + bskt_emax = k < elem.numbaskets - 1 ? elem.getBasketEntry(k + 1) : bskt_emin + 1e6; + + // first baskets can be ignored + if (bskt_emax <= handle.process_min) + continue; // no need to read more baskets, process_max is not included - if (elem.getBasketEntry(k) >= handle.process_max) break; + if (bskt_emin >= handle.process_max) + break; - // check which baskets need to be read if (elem.first_readentry < 0) { - const lmt = elem.getBasketEntry(k + 1), - not_needed = (lmt <= handle.process_min); + // basket where reading will start + elem.curr_basket = k; + + elem.first_readentry = elem.getBasketEntry(k); // remember which entry will be read first + } else if (select_entries) { + // all entries from process entries are analyzed + if (elem.eindx >= handle.process_entries.length) + break; - // for (let d=0;d<elem.ascounter.length;++d) { - // let dep = handle.arr[elem.ascounter[d]]; // dependent element - // if (dep.first_readentry < lmt) not_needed = false; // check that counter provide required data - // } + // check if this basket required + if ((handle.process_entries[elem.eindx] < bskt_emin) || (handle.process_entries[elem.eindx] >= bskt_emax)) + continue; - if (not_needed) continue; // if that basket not required, check next + // when all previous baskets were processed, continue with selected + if (elem.curr_basket < 0) + elem.curr_basket = k; + } - elem.curr_basket = k; // basket where reading will start + if (select_entries) { + // also check next entries which may belong to this basket + do + elem.eindx++; + while ((elem.eindx < handle.process_entries.length) && (handle.process_entries[elem.eindx] >= bskt_emin) && (handle.process_entries[elem.eindx] < bskt_emax)); - elem.first_readentry = elem.getBasketEntry(k); // remember which entry will be read first + // remember which baskets are required + elem.selected_baskets.push(k); } // check if basket already loaded in the branch @@ -2374,7 +2690,8 @@ async function treeProcess(tree, selector, args) { elem.baskets[k] = bitem; } else { bitems.push(bitem); - totalsz += elem.branch.fBasketBytes[k]; + total_size += elem.branch.fBasketBytes[k]; + total_nsegm++; isany = true; } @@ -2387,7 +2704,7 @@ async function treeProcess(tree, selector, args) { } } - if ((totalsz === 0) && !is_direct) { + if ((total_size === 0) && !is_direct) { handle.selector.Terminate(true); return resolveFunc(handle.selector); } @@ -2406,18 +2723,19 @@ async function treeProcess(tree, selector, args) { handle.progress_showtm = new Date().getTime(); - if (totalsz > 0) - return ReadBaskets(bitems).then(ProcessBaskets); + if (total_size > 0) + return readBaskets(bitems).then(processBaskets); - if (is_direct) return ProcessBaskets([]); // directly process baskets + if (is_direct) + return processBaskets([]); // directly process baskets throw new Error('No any data is requested - never come here'); } - function ProcessBaskets(bitems) { + processBaskets = function(bitems) { // this is call-back when next baskets are read - if ((handle.selector._break !== 0) || (bitems === null)) { + if (handle.selector._break || (bitems === null)) { handle.selector.Terminate(false); return resolveFunc(handle.selector); } @@ -2449,7 +2767,7 @@ async function treeProcess(tree, selector, args) { continue; // ignore non-master branch } - // this is single response from the tree, includes branch, bakset number, raw data + // this is single response from the tree, includes branch, basket number, raw data const bitem = elem.baskets[elem.curr_basket]; // basket not read @@ -2461,7 +2779,7 @@ async function treeProcess(tree, selector, args) { } // try to read next portion of tree data - return ReadNextBaskets(); + return readNextBaskets(); } elem.raw = bitem.raw; @@ -2473,6 +2791,12 @@ async function treeProcess(tree, selector, args) { bitem.branch = null; // remove reference on the branch bitem.bskt_obj = null; // remove reference on the branch elem.baskets[elem.curr_basket++] = undefined; // remove from array + + if (handle.process_entries !== undefined) { + elem.selected_baskets.shift(); + // -1 means that basket is not yet found for following entries + elem.curr_basket = elem.selected_baskets.length ? elem.selected_baskets[0] : -1; + } } // define how much entries can be processed before next raw buffer will be finished @@ -2506,7 +2830,7 @@ async function treeProcess(tree, selector, args) { isanyprocessed = true; } else { // main processing loop - while (loopentries--) { + while (loopentries > 0) { for (n = 0; n < handle.arr.length; ++n) { elem = handle.arr[n]; @@ -2518,9 +2842,23 @@ async function treeProcess(tree, selector, args) { handle.selector.Process(handle.current_entry); - handle.current_entry++; - isanyprocessed = true; + + if (handle.process_entries) { + handle.process_entries_indx++; + if (handle.process_entries_indx >= handle.process_entries.length) { + handle.current_entry++; + loopentries = 0; + } else { + const next_entry = handle.process_entries[handle.process_entries_indx], + diff = next_entry - handle.current_entry; + handle.current_entry = next_entry; + loopentries -= diff; + } + } else { + handle.current_entry++; + loopentries--; + } } } @@ -2529,7 +2867,7 @@ async function treeProcess(tree, selector, args) { return resolveFunc(handle.selector); } } - } + }; return new Promise((resolve, reject) => { resolveFunc = resolve; @@ -2538,38 +2876,62 @@ async function treeProcess(tree, selector, args) { // call begin before first entry is read handle.selector.Begin(tree); - ReadNextBaskets(); + readNextBaskets(); }); } /** @summary implementation of TTree::Draw * @param {object|string} args - different setting or simply draw expression * @param {string} args.expr - draw expression - * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') + * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::') * @param {string} [args.drawopt=undefined] - draw options for result histogram * @param {number} [args.firstentry=0] - first entry to process * @param {number} [args.numentries=undefined] - number of entries to process, all by default + * @param {Array} [args.elist=undefined] - array of entries id to process, all by default + * @param {Boolean} [args.dump] - dump values of expression instead creating histogram + * @param {Boolean} [args.dump_entries] - if array of entries should be return which match cut condition + * @param {Boolean} [args.staged] - staged processing, first apply cut to select entries and then perform drawing for selected entries * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument - * @return {Promise} with produced object */ + * @return {Promise} with produced object */ async function treeDraw(tree, args) { - if (isStr(args)) args = { expr: args }; + if (isStr(args)) + args = { expr: args }; + + if (!isStr(args.expr)) + args.expr = ''; - if (!isStr(args.expr)) args.expr = ''; + if (!args.SelectorClass) + args.SelectorClass = TDrawSelector; + if (!args.processFunction) + args.processFunction = treeProcess; - const selector = new TDrawSelector(); + const selector = new args.SelectorClass(); if (args.branch) { if (!selector.drawOnlyBranch(tree, args.branch, args.expr, args)) - return Promise.reject(Error(`Fail to create draw expression ${args.expr} for branch ${args.branch.fName}`)); - } else { - if (!selector.parseDrawExpression(tree, args)) - return Promise.reject(Error(`Fail to create draw expression ${args.expr}`)); - } + return Promise.reject(Error(`Fail to create draw expression ${args.expr} for branch ${args.branch.fName}`)); + } else if (!selector.parseDrawExpression(tree, args)) + return Promise.reject(Error(`Fail to create draw expression ${args.expr}`)); selector.setCallback(null, args.progress); - return treeProcess(tree, selector, args).then(() => selector.hist); + return args.processFunction(tree, selector, args).then(sel => { + if (!args.staged) + return sel; + + delete args.dump_entries; + + const selector2 = new args.SelectorClass(), + args2 = Object.assign({}, args); + args2.staged = false; + args2.elist = sel.hist; // assign entries found in first selection + if (!selector2.createDrawExpression(tree, args.staged_names, '', args2)) + return Promise.reject(Error(`Fail to create final draw expression ${args.expr}`)); + ['arr_limit', 'htype', 'nmatch', 'want_hist', 'hist_nbins', 'hist_name', 'hist_args', 'draw_title'] + .forEach(name => { selector2[name] = selector[name]; }); + return args.processFunction(tree, selector2, args2); + }).then(sel => sel.hist); } /** @summary Performs generic I/O test for all branches in the TTree @@ -2579,7 +2941,8 @@ function treeIOTest(tree, args) { const branches = [], names = [], nchilds = []; function collectBranches(obj, prntname = '') { - if (!obj?.fBranches) return 0; + if (!obj?.fBranches) + return 0; let cnt = 0; @@ -2633,7 +2996,7 @@ function treeIOTest(tree, args) { skip_branch = object_class ? (nchilds[nbr] > 100) : !br.fLeaves?.arr?.length; if (skip_branch || (num <= 0)) - return testBranch(nbr+1); + return testBranch(nbr + 1); const drawargs = { numentries: 10 }, first = br.fFirstEntry || 0, @@ -2650,7 +3013,7 @@ function treeIOTest(tree, args) { if (isFunc(args.showProgress)) args.showProgress(`br ${nbr}/${branches.length} ${br.fName}`); - return treeProcess(tree, selector, drawargs).then(() => testBranch(nbr+1)); + return treeProcess(tree, selector, drawargs).then(() => testBranch(nbr + 1)); } return testBranch(0).then(() => { @@ -2663,19 +3026,22 @@ function treeIOTest(tree, args) { /** @summary Create hierarchy of TTree object * @private */ -function treeHierarchy(node, obj) { +function treeHierarchy(tree_node, obj) { function createBranchItem(node, branch, tree, parent_branch) { - if (!node || !branch) return false; + if (!node || !branch) + return false; const nb_branches = branch.fBranches?.arr?.length ?? 0, nb_leaves = branch.fLeaves?.arr?.length ?? 0; function ClearName(arg) { const pos = arg.indexOf('['); - if (pos > 0) arg = arg.slice(0, pos); + if (pos > 0) + arg = arg.slice(0, pos); if (parent_branch && arg.indexOf(parent_branch.fName) === 0) { arg = arg.slice(parent_branch.fName.length); - if (arg[0] === '.') arg = arg.slice(1); + if (arg[0] === '.') + arg = arg.slice(1); } return arg; } @@ -2684,12 +3050,13 @@ function treeHierarchy(node, obj) { const subitem = { _name: ClearName(branch.fName), - _kind: prROOT + branch._typename, + _kind: getKindForType(branch._typename), _title: branch.fTitle, _obj: branch }; - if (!node._childs) node._childs = []; + if (!node._childs) + node._childs = []; node._childs.push(subitem); @@ -2700,22 +3067,24 @@ function treeHierarchy(node, obj) { subitem._more = true; subitem._expand = function(bnode, bobj) { // really create all sub-branch items - if (!bobj) return false; + if (!bobj) + return false; - if (!bnode._childs) bnode._childs = []; + if (!bnode._childs) + bnode._childs = []; if ((bobj.fLeaves?.arr?.length === 1) && ((bobj.fType === kClonesNode) || (bobj.fType === kSTLNode))) { - bobj.fLeaves.arr[0].$branch = bobj; - bnode._childs.push({ - _name: '@size', - _title: 'container size', - _kind: prROOT + 'TLeafElement', - _icon: 'img_leaf', - _obj: bobj.fLeaves.arr[0], - _more: false - }); - } + bobj.fLeaves.arr[0].$branch = bobj; + bnode._childs.push({ + _name: '@size', + _title: 'container size', + _kind: getKindForType('TLeafElement'), + _icon: 'img_leaf', + _obj: bobj.fLeaves.arr[0], + _more: false + }); + } for (let i = 0; i < bobj.fBranches.arr.length; ++i) createBranchItem(bnode, bobj.fBranches.arr[i], bobj.$tree, bobj); @@ -2723,15 +3092,16 @@ function treeHierarchy(node, obj) { const object_class = getBranchObjectClass(bobj, bobj.$tree, true), methods = object_class ? getMethods(object_class) : null; - if (methods && (bobj.fBranches.arr.length > 0)) { + if (methods && bobj.fBranches.arr.length) { for (const key in methods) { - if (!isFunc(methods[key])) continue; + if (!isFunc(methods[key])) + continue; const s = methods[key].toString(); if ((s.indexOf('return') > 0) && (s.indexOf('function ()') === 0)) { bnode._childs.push({ - _name: key+'()', + _name: key + '()', _title: `function ${key} of class ${object_class}`, - _kind: prROOT + clTBranchFunc, // fictional class, only for drawing + _kind: getKindForType(clTBranchFunc), // fictional class, only for drawing _obj: { _typename: clTBranchFunc, branch: bobj, func: key }, _more: false }); @@ -2751,7 +3121,7 @@ function treeHierarchy(node, obj) { branch.fLeaves.arr[j].$branch = branch; // keep branch pointer for drawing const leafitem = { _name: ClearName(branch.fLeaves.arr[j].fName), - _kind: prROOT + branch.fLeaves.arr[j]._typename, + _kind: getKindForType(branch.fLeaves.arr[j]._typename), _obj: branch.fLeaves.arr[j] }; subitem._childs.push(leafitem); @@ -2765,14 +3135,14 @@ function treeHierarchy(node, obj) { if (obj.fBranches === undefined) return false; - node._childs = []; - node._tree = obj; // set reference, will be used later by TTree::Draw + tree_node._childs = []; + tree_node._tree = obj; // set reference, will be used later by TTree::Draw - for (let i = 0; i < obj.fBranches.arr?.length; ++i) - createBranchItem(node, obj.fBranches.arr[i], obj); + obj.fBranches?.arr.forEach(branch => createBranchItem(tree_node, branch, obj)); return true; } export { kClonesNode, kSTLNode, clTBranchFunc, - TSelector, TDrawVariable, TDrawSelector, treeHierarchy, treeProcess, treeDraw, treeIOTest }; + TSelector, TDrawVariable, TDrawSelector, + treeHierarchy, treeProcess, treeDraw, treeIOTest }; diff --git a/modules/webwindow.mjs b/modules/webwindow.mjs index c6fcf6a6c..38b29a12e 100644 --- a/modules/webwindow.mjs +++ b/modules/webwindow.mjs @@ -1,4 +1,4 @@ -import { httpRequest, createHttpRequest, loadScript, decodeUrl, +import { settings, httpRequest, createHttpRequest, loadScript, decodeUrl, browser, setBatchMode, isBatchMode, isObject, isFunc, isStr, btoa_func } from './core.mjs'; import { closeCurrentWindow, showProgress, loadOpenui5 } from './gui/utils.mjs'; import { sha256, sha256_2 } from './base/sha256.mjs'; @@ -53,21 +53,26 @@ class LongPollSocket { let url = this.path, reqmode = 'buf', post = null; if (kind === 'connect') { url += this.raw ? '?raw_connect' : '?txt_connect'; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); - console.log(`longpoll connect ${url} raw = ${this.raw}`); + if (this.handle) + url += '&' + this.handle.getConnArgs(this.counter++); this.connid = 'connect'; } else if (kind === 'close') { - if ((this.connid === null) || (this.connid === 'close')) return; + if ((this.connid === null) || (this.connid === 'close')) + return; url += `?connection=${this.connid}&close`; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); + if (this.handle) + url += '&' + this.handle.getConnArgs(this.counter++); this.connid = 'close'; reqmode = 'text;sync'; // use sync mode to close connection before browser window closed } else if ((this.connid === null) || (typeof this.connid !== 'number')) { - if (!browser.qt5) console.error('No connection'); + if (!browser.qt6) + console.error('No connection'); } else { url += '?connection=' + this.connid; - if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); - if (kind === 'dummy') url += '&dummy'; + if (this.handle) + url += '&' + this.handle.getConnArgs(this.counter++); + if (kind === 'dummy') + url += '&dummy'; } if (data) { @@ -94,16 +99,18 @@ class LongPollSocket { // raw mode - all kind of reply data packed into binary buffer // first 4 bytes header 'txt:' or 'bin:' // after the 'bin:' there is length of optional text argument like 'bin:14 :optional_text' - // and immedaitely after text binary data. Server sends binary data so, that offset should be multiple of 8 + // and immediately after text binary data. Server sends binary data so, that offset should be multiple of 8 const u8Arr = new Uint8Array(res); let str = '', i = 0, offset = u8Arr.length; if (offset < 4) { - if (!browser.qt5) console.error(`longpoll got short message in raw mode ${offset}`); + if (!browser.qt6) + console.error(`longpoll got short message in raw mode ${offset}`); return this.handle.processRequest(null); } - while (i < 4) str += String.fromCharCode(u8Arr[i++]); + while (i < 4) + str += String.fromCharCode(u8Arr[i++]); if (str !== 'txt:') { str = ''; while ((i < offset) && (String.fromCharCode(u8Arr[i]) !== ':')) @@ -113,20 +120,22 @@ class LongPollSocket { } str = ''; - while (i < offset) str += String.fromCharCode(u8Arr[i++]); + while (i < offset) + str += String.fromCharCode(u8Arr[i++]); if (str) { if (str === '<<nope>>') this.handle.processRequest(-1111); else - this.handle.processRequest(str); + this.handle.processRequest(str); } if (offset < u8Arr.length) this.handle.processRequest(res, offset); } else if (this.getResponseHeader('Content-Type') === 'application/x-binary') { // binary reply with optional header const extra_hdr = this.getResponseHeader('LongpollHeader'); - if (extra_hdr) this.handle.processRequest(extra_hdr); + if (extra_hdr) + this.handle.processRequest(extra_hdr); this.handle.processRequest(res, 0); } else { // text reply @@ -176,21 +185,24 @@ class LongPollSocket { this.connid = parseInt(res); dummy_tmout = 100; // when establishing connection, wait a bit longer to submit dummy package - console.log(`Get new longpoll connection with id ${this.connid}`); + if (settings.Debug) + console.log(`Get new longpoll connection with id ${this.connid}`); if (isFunc(this.onopen)) this.onopen(); } else if (this.connid === 'close') { if (isFunc(this.onclose)) this.onclose(); return; - } else { - if (isFunc(this.onmessage) && res) - this.onmessage({ data: res, offset: _offset }); - } + } else if (isFunc(this.onmessage) && res) + this.onmessage({ data: res, offset: _offset }); // minimal timeout to reduce load, generate dummy only if client not submit new request immediately - if (!this.req) - setTimeout(() => { if (!this.req) this.nextRequest('', 'dummy'); }, dummy_tmout); + if (!this.req) { + setTimeout(() => { + if (!this.req) + this.nextRequest('', 'dummy'); + }, dummy_tmout); + } } /** @summary Send data */ @@ -211,27 +223,34 @@ class LongPollSocket { class FileDumpSocket { + #wait_for_file; + #receiver; + constructor(receiver) { - this.receiver = receiver; + this.#receiver = receiver; this.protocol = []; this.cnt = 0; + this.sendcnt = 0; httpRequest('protocol.json', 'text').then(res => this.getProtocol(res)); } /** @summary Get stored protocol */ getProtocol(res) { - if (!res) return; + if (!res) + return; this.protocol = JSON.parse(res); - if (isFunc(this.onopen)) this.onopen(); + if (isFunc(this.onopen)) + this.onopen(); this.nextOperation(); } - /** @summary Emulate send - just cound operation */ + /** @summary Emulate send - just count operation */ send(/* str */) { if (this.protocol[this.cnt] === 'send') { this.cnt++; setTimeout(() => this.nextOperation(), 10); - } + } else + this.sendcnt++; } /** @summary Emulate close */ @@ -240,20 +259,31 @@ class FileDumpSocket { /** @summary Read data for next operation */ nextOperation() { // when file request running - just ignore - if (this.wait_for_file) return; + if (this.#wait_for_file) + return; const fname = this.protocol[this.cnt]; + if (!fname) + return; + + if (fname === 'send') { + if (this.sendcnt > 0) { + this.sendcnt--; + this.cnt++; + this.nextOperation(); + } + return; + } - if (!fname) return; - if (fname === 'send') return; // waiting for send - this.wait_for_file = true; + this.#wait_for_file = true; this.cnt++; httpRequest(fname, (fname.indexOf('.bin') > 0 ? 'buf' : 'text')).then(res => { - this.wait_for_file = false; - if (!res) return; + this.#wait_for_file = false; + if (!res) + return; const p = fname.indexOf('_ch'), - chid = (p > 0) ? Number.parseInt(fname.slice(p+3, fname.indexOf('.', p))) : 1; - if (isFunc(this.receiver.provideData)) - this.receiver.provideData(chid, res, 0); + chid = (p > 0) ? Number.parseInt(fname.slice(p + 3, fname.indexOf('.', p))) : 1; + if (isFunc(this.#receiver?.provideData)) + this.#receiver.provideData(chid, res, 0); setTimeout(() => this.nextOperation(), 10); }); } @@ -269,14 +299,48 @@ class FileDumpSocket { class WebWindowHandle { - constructor(socket_kind, credits) { - this.kind = socket_kind; - this.state = 0; - this.credits = credits || 10; - this.cansend = this.credits; - this.ackn = this.credits; - this.send_seq = 1; // sequence counter of send messages - this.recv_seq = 0; // sequence counter of received messages + #state; // 0 - init, 1 - connected, -1 - closed + #kind; // websocket kind - websocket, file, longpoll, rawlongpoll + #key; // connection key + #token; // connection token (alternative to key) + #new_key; // new key for reconnect + #can_modify_url; // if widget URL can be modified to store new key + #ws; // websocket or emulation + #href; // widget url + #receiver; // receiver of messages + #channels; // sub-channels + #master; // master connection + #channelid; // channel id if this is sub-channel in master connection + #ask_reload; // flag set when page reload is triggered + #credits; // configured number of credits + #ackn; // number of acknowledged packets, regularly send to server to keep sending + #cansend; // number of packets client can send + #send_seq; // sequence counter of send messages + #recv_seq; // sequence counter of received messages + #secondary; // true when created as extra connection, not need to use keys + #msgqueue; // messages queue + #loop_msgqueue; // flag when looping in message queue + #timerid; // keep alive timer + #next_binary; // set to channel id when next binary message expected + #next_binary_hash; // hash of next binary message + #wait_first_recv; // first received message via the channel is confirmation of established connection + #user_args; // user arguments for web socket + #handling_reload; // true when Ctrl-R handler was installed + + constructor(socket_kind, credits, master, chid) { + this.#kind = socket_kind; + this.#state = 0; + this.#credits = Math.max(3, credits || 10); + this.#cansend = this.#credits; + this.#ackn = this.#credits; + this.#send_seq = 1; + this.#recv_seq = 0; + + if (socket_kind === 'channel') { + this.#wait_first_recv = true; + this.#master = master; + this.#channelid = chid; + } } /** @summary Returns arguments specified in the RWebWindow::SetUserArgs() method @@ -285,74 +349,86 @@ class WebWindowHandle { * @return user arguments object */ getUserArgs(field) { if (field && isStr(field)) - return isObject(this.user_args) ? this.user_args[field] : undefined; + return isObject(this.#user_args) ? this.#user_args[field] : undefined; - return this.user_args; + return this.#user_args; } /** @summary Set user args * @desc Normally set via RWebWindow::SetUserArgs() method */ - setUserArgs(args) { this.user_args = args; } + setUserArgs(args) { this.#user_args = args; } + + /** @summary Set key and token + * @private */ + setKeyToken(key, token, can_modify_url) { + this.#key = key; + this.#token = token; + if (can_modify_url !== undefined) + this.#can_modify_url = can_modify_url; + } /** @summary Set callbacks receiver. * @param {object} obj - object with receiver functions - * @param {function} obj.onWebsocketMsg - called when new data receieved from RWebWindow + * @param {function} obj.onWebsocketMsg - called when new data received from RWebWindow * @param {function} obj.onWebsocketOpened - called when connection established * @param {function} obj.onWebsocketClosed - called when connection closed * @param {function} obj.onWebsocketError - called when get error via the connection */ - setReceiver(obj) { this.receiver = obj; } + setReceiver(obj) { this.#receiver = obj; } /** @summary Cleanup and close connection. */ cleanup() { - delete this.receiver; + this.#receiver = undefined; this.close(true); } /** @summary Invoke method in the receiver. * @private */ invokeReceiver(brdcst, method, arg, arg2) { - if (this.receiver && isFunc(this.receiver[method])) - this.receiver[method](this, arg, arg2); + if (this.#receiver && isFunc(this.#receiver[method])) + this.#receiver[method](this, arg, arg2); - if (brdcst && this.channels) { - const ks = Object.keys(this.channels); + if (brdcst && this.#channels) { + const ks = Object.keys(this.#channels); for (let n = 0; n < ks.length; ++n) - this.channels[ks[n]].invokeReceiver(false, method, arg, arg2); + this.#channels[ks[n]].invokeReceiver(false, method, arg, arg2); } } /** @summary Provide data for receiver. When no queue - do it directly. * @private */ provideData(chid, msg, len) { - if (this.wait_first_recv) { + if (this.#wait_first_recv) { // here dummy first recv like EMBED_DONE is handled - delete this.wait_first_recv; - this.state = 1; + this.#wait_first_recv = undefined; + this.#state = 1; return this.invokeReceiver(false, 'onWebsocketOpened'); } - if ((chid > 1) && this.channels) { - const channel = this.channels[chid]; + if ((chid > 1) && this.#channels) { + const channel = this.#channels[chid]; if (channel) return channel.provideData(1, msg, len); } const force_queue = len && (len < 0); - if (!force_queue && (!this.msgqueue || !this.msgqueue.length)) + if (!force_queue && !this.#msgqueue?.length) return this.invokeReceiver(false, 'onWebsocketMsg', msg, len); - if (!this.msgqueue) this.msgqueue = []; - if (force_queue) len = undefined; + if (!this.#msgqueue) + this.#msgqueue = []; + if (force_queue) + len = undefined; - this.msgqueue.push({ ready: true, msg, len }); + this.#msgqueue.push({ ready: true, msg, len }); } /** @summary Reserve entry in queue for data, which is not yet decoded. * @private */ reserveQueueItem() { - if (!this.msgqueue) this.msgqueue = []; + if (!this.#msgqueue) + this.#msgqueue = []; const item = { ready: false, msg: null, len: 0 }; - this.msgqueue.push(item); + this.#msgqueue.push(item); return item; } @@ -368,88 +444,96 @@ class WebWindowHandle { /** @summary Process completed messages in the queue * @private */ processQueue() { - if (this._loop_msgqueue || !this.msgqueue) return; - this._loop_msgqueue = true; - while ((this.msgqueue.length > 0) && this.msgqueue[0].ready) { - const front = this.msgqueue.shift(); + if (this.#loop_msgqueue || !this.#msgqueue) + return; + this.#loop_msgqueue = true; + while (this.#msgqueue.length && this.#msgqueue[0].ready) { + const front = this.#msgqueue.shift(); this.invokeReceiver(false, 'onWebsocketMsg', front.msg, front.len); } - if (this.msgqueue.length === 0) - delete this.msgqueue; - delete this._loop_msgqueue; + if (!this.#msgqueue.length) + this.#msgqueue = undefined; + this.#loop_msgqueue = undefined; } /** @summary Close connection */ close(force) { - if (this.master) { - this.master.send(`CLOSECH=${this.channelid}`, 0); - delete this.master.channels[this.channelid]; - delete this.master; + if (this.#master) { + this.#master.send(`CLOSECH=${this.#channelid}`, 0); + this.#master.removeChannel(this.#channelid); + this.#master = undefined; return; } - if (this.timerid) { - clearTimeout(this.timerid); - delete this.timerid; + if (this.#timerid) { + clearTimeout(this.#timerid); + this.#timerid = undefined; } - if (this._websocket && (this.state > 0)) { - this.state = force ? -1 : 0; // -1 prevent socket from reopening - this._websocket.onclose = null; // hide normal handler - this._websocket.close(); - delete this._websocket; + if (this.#ws && (this.#state > 0)) { + this.#state = force ? -1 : 0; // -1 prevent socket from reopening + this.#ws.onclose = null; // hide normal handler + this.#ws.close(); + this.#ws = undefined; } } /** @summary Checks number of credits for send operation * @param {number} [numsend = 1] - number of required send operations * @return true if one allow to send specified number of text message to server */ - canSend(numsend) { return this.cansend >= (numsend || 1); } + canSend(numsend) { return this.#cansend >= (numsend || 1); } /** @summary Returns number of possible send operations relative to number of credits */ - getRelCanSend() { return !this.credits ? 1 : this.cansend / this.credits; } + getRelCanSend() { return !this.#credits ? 1 : this.#cansend / this.#credits; } /** @summary Send text message via the connection. * @param {string} msg - text message to send * @param {number} [chid] - channel id, 1 by default, 0 used only for internal communication */ send(msg, chid) { - if (this.master) - return this.master.send(msg, this.channelid); + if (this.#master) + return this.#master.send(msg, this.#channelid); - if (!this._websocket || (this.state <= 0)) return false; + if (!this.#ws || (this.#state <= 0)) + return false; - if (!Number.isInteger(chid)) chid = 1; // when not configured, channel 1 is used - main widget + if (!Number.isInteger(chid)) + chid = 1; // when not configured, channel 1 is used - main widget - if (this.cansend <= 0) console.error(`should be queued before sending cansend: ${this.cansend}`); + if (this.#cansend === 0) + console.error('No credits for send, increase "WebGui.ConnCredits" value on server'); - const prefix = `${this.send_seq++}:${this.ackn}:${this.cansend}:${chid}:`; - this.ackn = 0; - this.cansend--; // decrease number of allowed send packets + const prefix = `${this.#send_seq++}:${this.#ackn}:${this.#cansend}:${chid}:`, + hash = this.#key && sessionKey ? HMAC(this.#key, `${prefix}${msg}`) : 'none'; - let hash = 'none'; - if (this.key && sessionKey) - hash = HMAC(this.key, `${prefix}${msg}`); + this.#ackn = 0; + this.#cansend--; // decrease number of allowed send packets - this._websocket.send(`${hash}:${prefix}${msg}`); + this.#ws.send(`${hash}:${prefix}${msg}`); - if ((this.kind === 'websocket') || (this.kind === 'longpoll')) { - if (this.timerid) clearTimeout(this.timerid); - this.timerid = setTimeout(() => this.keepAlive(), 10000); + if ((this.#kind === 'websocket') || (this.#kind === 'longpoll')) { + if (this.#timerid) + clearTimeout(this.#timerid); + this.#timerid = setTimeout(() => this.keepAlive(), 10000); } return true; } /** @summary Send only last message of specified kind during defined time interval. - * @desc Idea is to prvent sending multiple messages of similar kind and overload connection + * @desc Idea is to prevent sending multiple messages of similar kind and overload connection * Instead timeout is started after which only last specified message will be send * @private */ sendLast(kind, tmout, msg) { let d = this._delayed; - if (!d) d = this._delayed = {}; + if (!d) + d = this._delayed = {}; d[kind] = msg; - if (!d[`${kind}_handler`]) - d[`${kind}_handler`] = setTimeout(() => { delete d[`${kind}_handler`]; this.send(d[kind]); }, tmout); + if (!d[`${kind}_handler`]) { + d[`${kind}_handler`] = setTimeout(() => { + delete d[`${kind}_handler`]; + this.send(d[kind]); + }, tmout); + } } /** @summary Inject message(s) into input queue, for debug purposes only @@ -459,7 +543,8 @@ class WebWindowHandle { if (!immediate) return setTimeout(this.inject.bind(this, msg, chid, true), 0); - if (chid === undefined) chid = 1; + if (chid === undefined) + chid = 1; if (Array.isArray(msg)) { for (let k = 0; k < msg.length; ++k) @@ -470,65 +555,78 @@ class WebWindowHandle { } /** @summary Send keep-alive message. - * @desc Only for internal use, only when used with websockets + * @desc Only for internal use, only when used with web sockets * @private */ keepAlive() { - delete this.timerid; + this.#timerid = undefined; this.send('KEEPALIVE', 0); } /** @summary Request server to resize window * @desc For local displays like CEF or qt5 only server can do this */ resizeWindow(w, h) { - if (browser.qt5 || browser.cef3) + if (browser.qt6 || browser.cef3) this.send(`RESIZE=${w},${h}`, 0); else if ((typeof window !== 'undefined') && isFunc(window?.resizeTo)) window.resizeTo(w, h); } + /** @summary Remove existing channel. + * @private */ + removeChannel(chid) { + if (this.#channels) + this.#channels[chid] = undefined; + } + /** @summary Method open channel, which will share same connection, but can be used independently from main + * If @param url is provided - creates fully independent instance and perform connection with it * @private */ - createChannel() { - if (this.master) - return this.master.createChannel(); + createChannel(url) { + if (url) + return this.createNewInstance(url); - const channel = new WebWindowHandle('channel', this.credits); - channel.wait_first_recv = true; // first received message via the channel is confirmation of established connection + if (this.#master) + return this.#master.createChannel(); - if (!this.channels) { - this.channels = {}; - this.freechannelid = 2; - } + if (!this.#channels) + this.#channels = { freeid: 2 }; - channel.master = this; - channel.channelid = this.freechannelid++; + const channel = new WebWindowHandle('channel', this.#credits, this, this.#channels.freeid++); // register - this.channels[channel.channelid] = channel; + this.#channels[channel.getChannelId()] = channel; // now server-side entity should be initialized and init message send from server side! return channel; } /** @summary Returns true if socket connected */ - isConnected() { return this.state > 0; } + isConnected() { return this.#state > 0; } + + /** @summary Return websocket kind - like 'websocket' or 'longpoll' */ + getKind() { return this.#kind; } + + /** @summary Standalone mode without server connection */ + isStandalone() { return this.#kind === 'file'; } /** @summary Returns used channel ID, 1 by default */ - getChannelId() { return this.channelid && this.master ? this.channelid : 1; } + getChannelId() { return this.#channelid && this.#master ? this.#channelid : 1; } + + /** @summary Returns true if sub-channel in master connection */ + isChannel() { return this.getChannelId() > 1; } /** @summary Assign href parameter * @param {string} [path] - absolute path, when not specified window.location.url will be used * @private */ - setHRef(path) { + setHRef(path, secondary) { + this.#secondary = secondary; if (isStr(path) && (path.indexOf('?') > 0)) { - this.href = path.slice(0, path.indexOf('?')); + this.#href = path.slice(0, path.indexOf('?')); const d = decodeUrl(path); - this.key = d.get('key'); - this.token = d.get('token'); + this.setKeyToken(d.get('key'), d.get('token')); } else { - this.href = path; - delete this.key; - delete this.token; + this.#href = path; + this.setKeyToken(); } } @@ -536,11 +634,11 @@ class WebWindowHandle { * @param {string} [relative_path] - relative path to the handle * @private */ getHRef(relative_path) { - if (!relative_path || !this.kind || !this.href) - return this.href; - let addr = this.href; + if (!relative_path || !this.#kind || !this.#href) + return this.#href; + let addr = this.#href; if (relative_path.indexOf('../') === 0) { - const ddd = addr.lastIndexOf('/', addr.length-2); + const ddd = addr.lastIndexOf('/', addr.length - 2); addr = addr.slice(0, ddd) + relative_path.slice(2); } else addr += relative_path; @@ -552,83 +650,100 @@ class WebWindowHandle { * @private */ getConnArgs(ntry) { let args = ''; - if (this.key) { - const k = HMAC(this.key, `attempt_${ntry}`); + if (this.#key) { + const k = HMAC(this.#key, `attempt_${ntry}`); args += `key=${k}&ntry=${ntry}`; } - if (this.token) { - if (args) args += '&'; - args += `token=${this.token}`; + if (this.#token) { + if (args) + args += '&'; + args += `token=${this.#token}`; } return args; } - /** @summary Create configured socket for current object. + /** @summary Connect to the server + * @param [href] - optional URL to widget, use document URL instead * @private */ connect(href) { + // ignore connect if channel from master connection configured + if (this.isChannel()) + return; + this.close(); - if (!href && this.href) href = this.href; + + if (href) + this.setHRef(href, true); + + href = this.#href; let ntry = 0; const retry_open = first_time => { - if (this.state !== 0) return; + if (this.#state) + return; - if (!first_time) console.log(`try connect window again ${new Date().toString()}`); + if (!first_time && settings.Debug) + console.log(`try connect window again ${new Date().toString()}`); - if (this._websocket) { - this._websocket.close(); - delete this._websocket; + if (this.#ws) { + this.#ws.close(); + this.#ws = undefined; } if (!href) { href = window.location.href; - if (href && href.indexOf('#') > 0) href = href.slice(0, href.indexOf('#')); - if (href && href.lastIndexOf('/') > 0) href = href.slice(0, href.lastIndexOf('/') + 1); + if (href && href.indexOf('#') > 0) + href = href.slice(0, href.indexOf('#')); + if (href && href.lastIndexOf('/') > 0) + href = href.slice(0, href.lastIndexOf('/') + 1); } - this.href = href; + this.#href = href; ntry++; - if (first_time) console.log(`Opening web socket at ${href}`); + if (first_time && settings.Debug) + console.log(`Opening websocket at ${href}`); - if (ntry > 2) showProgress(`Trying to connect ${href}`); + if (ntry > 2) + showProgress(`Trying to connect ${href}`); let path = href; - if (this.kind === 'file') { + if (this.isStandalone()) { path += 'root.filedump'; - this._websocket = new FileDumpSocket(this); - console.log(`configure protocol log ${path}`); - } else if ((this.kind === 'websocket') && first_time) { + this.#ws = new FileDumpSocket(this); + console.log(`Configure FileDumpSocket ${path}`); + } else if ((this.#kind === 'websocket') && first_time) { path = path.replace('http://', 'ws://').replace('https://', 'wss://') + 'root.websocket'; + console.log(`Configure websocket ${path}`); path += '?' + this.getConnArgs(ntry); - console.log(`configure websocket ${path}`); - this._websocket = new WebSocket(path); + this.#ws = new WebSocket(path); } else { path += 'root.longpoll'; - console.log(`configure longpoll ${path}`); - this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), this, ntry); + console.log(`Configure longpoll ${path}`); + this.#ws = new LongPollSocket(path, (this.#kind === 'rawlongpoll'), this, ntry); } - if (!this._websocket) return; + if (!this.#ws) + return; - this._websocket.onopen = () => { - if (ntry > 2) showProgress(); - this.state = 1; + this.#ws.onopen = () => { + if (ntry > 2) + showProgress(); + this.#state = 1; - const key = this.key || ''; - this.send(`READY=${key}`, 0); // need to confirm connection + const reply = (this.#secondary ? '' : 'generate_key;') + (this.#key || ''); + this.send(`READY=${reply}`, 0); // need to confirm connection and request new key this.invokeReceiver(false, 'onWebsocketOpened'); }; - this._websocket.onmessage = e => { + this.#ws.onmessage = e => { let msg = e.data; - if (this.next_binary) { - const binchid = this.next_binary, - server_hash = this.next_binary_hash; - delete this.next_binary; - delete this.next_binary_hash; + if (this.#next_binary) { + const binchid = this.#next_binary, + server_hash = this.#next_binary_hash; + this.#next_binary = this.#next_binary_hash = undefined; if (msg instanceof Blob) { // convert Blob object to BufferArray @@ -636,8 +751,8 @@ class WebWindowHandle { // The file's text will be printed here reader.onload = event => { let result = event.target.result; - if (this.key && sessionKey) { - const hash = HMAC(this.key, result, 0); + if (this.#key && sessionKey) { + const hash = HMAC(this.#key, result, 0); if (hash !== server_hash) { console.log('Discard binary buffer because of HMAC mismatch'); result = new ArrayBuffer(0); @@ -650,8 +765,8 @@ class WebWindowHandle { } else { // this is from CEF or LongPoll handler let result = msg; - if (this.key && sessionKey) { - const hash = HMAC(this.key, result, e.offset || 0); + if (this.#key && sessionKey) { + const hash = HMAC(this.#key, result, e.offset || 0); if (hash !== server_hash) { console.log('Discard binary buffer because of HMAC mismatch'); result = new ArrayBuffer(0); @@ -680,64 +795,58 @@ class WebWindowHandle { // for authentication HMAC checksum and sequence id is important // HMAC used to authenticate server // sequence id is necessary to exclude submission of same packet again - if (this.key && sessionKey) { - const client_hash = HMAC(this.key, msg.slice(i0+1)); + if (this.#key && sessionKey) { + const client_hash = HMAC(this.#key, msg.slice(i0 + 1)); if (server_hash !== client_hash) - return console.log(`Failure checking server md5 sum ${server_hash}`); + return console.log(`Failure checking server HMAC sum ${server_hash}`); } - if (seq_id <= this.recv_seq) - return console.log(`Failure with packet sequence ${seq_id} <= ${this.recv_seq}`); + if (seq_id <= this.#recv_seq) + return console.log(`Failure with packet sequence ${seq_id} <= ${this.#recv_seq}`); - this.recv_seq = seq_id; // sequence id of received packet - this.ackn++; // count number of received packets, - this.cansend += credit; // how many packets client can send + this.#recv_seq = seq_id; // sequence id of received packet + this.#ackn++; // count number of received packets, + this.#cansend += credit; // how many packets client can send msg = msg.slice(i4 + 1); if (chid === 0) { - console.log(`GET chid=0 message ${msg}`); if (msg === 'CLOSE') { this.close(true); // force closing of socket this.invokeReceiver(true, 'onWebsocketClosed'); } else if (msg.indexOf('NEW_KEY=') === 0) { - const newkey = msg.slice(8); - this.close(true); - let href = (typeof document !== 'undefined') ? document.URL : null; - if (isStr(href) && (typeof window !== 'undefined') && window?.history) { - const p = href.indexOf('?key='); - if (p > 0) href = href.slice(0, p); - window.history.replaceState(window.history.state, undefined, `${href}?key=${newkey}`); - } else if (typeof sessionStorage !== 'undefined') - sessionStorage.setItem('RWebWindow_Key', newkey); - location.reload(true); + this.#new_key = msg.slice(8); + this.storeKeyInUrl(); + if (this.#ask_reload) + this.askReload(true); } } else if (msg.slice(0, 10) === '$$binary$$') { - this.next_binary = chid; - this.next_binary_hash = msg.slice(10); + this.#next_binary = chid; + this.#next_binary_hash = msg.slice(10); } else if (msg === '$$nullbinary$$') this.provideData(chid, new ArrayBuffer(0), 0); else this.provideData(chid, msg); - if (this.ackn > 7) + if (this.#ackn > Math.max(2, this.#credits * 0.7)) this.send('READY', 0); // send dummy message to server }; - this._websocket.onclose = arg => { - delete this._websocket; - if ((this.state > 0) || (arg === 'force_close')) { - console.log('websocket closed'); - this.state = 0; + this.#ws.onclose = arg => { + this.#ws = undefined; + if ((this.#state > 0) || (arg === 'force_close')) { + if (settings.Debug) + console.log('websocket closed'); + this.#state = 0; this.invokeReceiver(true, 'onWebsocketClosed'); } }; - this._websocket.onerror = err => { - console.log(`websocket error ${err} state ${this.state}`); - if (this.state > 0) { + this.#ws.onerror = err => { + console.log(`websocket error ${err} state ${this.#state}`); + if (this.#state > 0) { this.invokeReceiver(true, 'onWebsocketError', err); - this.state = 0; + this.#state = 0; } }; @@ -749,21 +858,33 @@ class WebWindowHandle { retry_open(true); // call for the first time } - /** @summary Send newkey request to application - * @desc If server creates newkey and response - webpage will be reaload - * After key generation done, connection will not be working any longer - * WARNING - only call when you know that you are doing + /** @summary Ask to reload web widget + * @desc If new key already exists - reload immediately + * Otherwise request server to generate new key - and then reload page + * WARNING - call only when knowing that you are doing * @private */ - askReload() { - this.send('GENERATE_KEY', 0); + askReload(force) { + if (this.#new_key || force) { + this.close(true); + if (typeof location !== 'undefined') + location.reload(true); + } else { + this.#ask_reload = true; + this.send('GENERATE_KEY', 0); + } } - /** @summary Instal Ctrl-R handler to realod web window + /** @summary Instal Ctrl-R handler to reload web window * @desc Instead of default window reload invokes {@link askReload} method * WARNING - only call when you know that you are doing * @private */ addReloadKeyHandler() { - if (this.kind === 'file') return; + if (this.isStandalone() || this.#handling_reload) + return; + + // this websocket will handle reload + // embed widgets should not call this method + this.#handling_reload = true; window.addEventListener('keydown', evnt => { if (((evnt.key === 'R') || (evnt.key === 'r')) && evnt.ctrlKey) { @@ -771,10 +892,47 @@ class WebWindowHandle { evnt.preventDefault(); console.log('Prevent Ctrl-R propogation - ask reload RWebWindow!'); this.askReload(); - } + } }); } + /** @summary Replace widget URL with new key + * @private */ + storeKeyInUrl() { + // do not modify document URLs by secondary widgets + if (this.#secondary) + return; + + let href = (typeof document !== 'undefined') ? document.URL : null; + + if (this.#can_modify_url && isStr(href) && (typeof window !== 'undefined')) { + let prefix = '&key=', p = href.indexOf(prefix); + if (p < 0) { + prefix = '?key='; + p = href.indexOf(prefix); + } + if ((p > 0) && this.#new_key) { + const p1 = href.indexOf('#', p + 1), p2 = href.indexOf('&', p + 1), + pp = (p1 < 0) ? p2 : (p2 < 0 ? p1 : Math.min(p1, p2)); + href = href.slice(0, p) + prefix + this.#new_key + (pp < 0 ? '' : href.slice(pp)); + window.history?.replaceState(window.history.state, undefined, href); + } + } + + if (typeof sessionStorage !== 'undefined') { + sessionStorage.setItem('RWebWindow_SessionKey', sessionKey); + sessionStorage.setItem('RWebWindow_Key', this.#new_key); + } + } + + /** @summary Create new instance of same kind + * @private */ + createNewInstance(url) { + const handle = new WebWindowHandle(this.#kind); + handle.setHRef(this.getHRef(url), true); + return handle; + } + } // class WebWindowHandle @@ -785,7 +943,7 @@ class WebWindowHandle { * @param {object} arg.receiver - instance of receiver for websocket events, allows to initiate connection immediately * @param {string} [arg.first_recv] - required prefix in the first message from RWebWindow, remain part of message will be returned in handle.first_msg * @param {string} [arg.href] - URL to RWebWindow, using window.location.href by default - * @return {Promise} for ready-to-use {@link WebWindowHandle} instance */ + * @return {Promise} {@link WebWindowHandle} instance */ async function connectWebWindow(arg) { // mark that jsroot used with RWebWindow browser.webwindow = true; @@ -798,10 +956,10 @@ async function connectWebWindow(arg) { let d_key, d_token, new_key; if (!arg.href) { - let href = (typeof document !== 'undefined') ? document.URL : ''; + let href = (typeof document !== 'undefined') ? document.URL : '', s_key; const p = href.indexOf('#'); if (p > 0) { - sessionKey = href.slice(p+1); + s_key = href.slice(p + 1); href = href.slice(0, p); } @@ -809,10 +967,16 @@ async function connectWebWindow(arg) { d_key = d.get('key'); d_token = d.get('token'); + if (d_key && s_key && (s_key.length > 20)) { + sessionKey = s_key; + + if (typeof window !== 'undefined') + window.history?.replaceState(window.history.state, undefined, href); + } + if (typeof sessionStorage !== 'undefined') { new_key = sessionStorage.getItem('RWebWindow_Key'); sessionStorage.removeItem('RWebWindow_Key'); - if (new_key) console.log(`Use key ${new_key} from session storage`); if (sessionKey) sessionStorage.setItem('RWebWindow_SessionKey', sessionKey); @@ -820,28 +984,29 @@ async function connectWebWindow(arg) { sessionKey = sessionStorage.getItem('RWebWindow_SessionKey') || ''; } - // hide key and any following parameters from URL, chrome do not allows to close browser with changed URL - if (d_key && !d.has('headless') && isStr(href) && (typeof window !== 'undefined') && window?.history) { - const p = href.indexOf('?key='); - if (p > 0) window.history.replaceState(window.history.state, undefined, href.slice(0, p)); - } - // special holder script, prevents headless chrome browser from too early exit if (d.has('headless') && d_key && (browser.isChromeHeadless || browser.isChrome) && !arg.ignore_chrome_batch_holder) loadScript('root_batch_holder.js?key=' + (new_key || d_key)); + if (arg.debug || d.has('debug')) + settings.Debug = true; + if (!arg.platform) arg.platform = d.get('platform'); - if (arg.platform === 'qt5') - browser.qt5 = true; + if (arg.platform === 'qt6') + browser.qt6 = true; else if (arg.platform === 'cef3') browser.cef3 = true; if (arg.batch === undefined) arg.batch = d.has('headless'); - if (arg.batch) setBatchMode(true); + if (arg.batch) + setBatchMode(true); + + if (arg.dark) + settings.DarkMode = true; if (!arg.socket_kind) arg.socket_kind = d.get('ws'); @@ -854,7 +1019,7 @@ async function connectWebWindow(arg) { } if (!arg.socket_kind) { - if (browser.qt5) + if (browser.qt6) arg.socket_kind = 'rawlongpoll'; else if (browser.cef3) arg.socket_kind = 'longpoll'; @@ -862,43 +1027,44 @@ async function connectWebWindow(arg) { arg.socket_kind = 'websocket'; } - // only for debug purposes - // arg.socket_kind = 'longpoll'; + if (arg.settings) + Object.assign(settings, arg.settings); const main = new Promise(resolveFunc => { const handle = new WebWindowHandle(arg.socket_kind, arg.credits); handle.setUserArgs(arg.user_args); if (arg.href) handle.setHRef(arg.href); // apply href now while connect can be called from other place - else { - handle.key = new_key || d_key; - handle.token = d_token; - } + else + handle.setKeyToken(new_key || d_key, d_token, Boolean(d_key)); - if (window) { + if (typeof window !== 'undefined') { window.onbeforeunload = () => handle.close(true); - if (browser.qt5) window.onqt5unload = window.onbeforeunload; + if (browser.qt6) + window.onqt6unload = window.onbeforeunload; } - if (arg.receiver) { // when receiver exists, it handles itself callbacks handle.setReceiver(arg.receiver); handle.connect(); - return resolveFunc(handle); + resolveFunc(handle); + return; } - if (!arg.first_recv) - return resolveFunc(handle); + if (!arg.first_recv) { + resolveFunc(handle); + return; + } handle.setReceiver({ onWebsocketOpened() {}, // dummy function when websocket connected - onWebsocketMsg(handle, msg) { - if (msg.indexOf(arg.first_recv) !== 0) - return handle.close(); - handle.first_msg = msg.slice(arg.first_recv.length); - resolveFunc(handle); + onWebsocketMsg(h, msg) { + if (msg.indexOf(arg.first_recv)) + return h.close(); + h.first_msg = msg.slice(arg.first_recv.length); + resolveFunc(h); }, onWebsocketClosed() { closeCurrentWindow(); } // when connection closed, close panel as well @@ -907,7 +1073,8 @@ async function connectWebWindow(arg) { handle.connect(); }); - if (!arg.ui5) return main; + if (!arg.ui5) + return main; return Promise.all([main, loadOpenui5(arg)]).then(arr => arr[0]); } diff --git a/package-lock.json b/package-lock.json index 60021b097..820dbfeec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,67 +1,107 @@ { "name": "jsroot", - "version": "7.7.99", + "version": "7.10.99", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jsroot", - "version": "7.7.99", + "version": "7.10.99", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.14.0", "@oneidentity/zstd-js": "^1.0.3", - "atob": "^2.1.2", - "btoa": "^1.2.1", - "canvas": "^2.11.2", - "cssesc": "^3.0.0", - "fflate": "^0.4.8", - "font-family-papandreou": "^0.2.0-patch1", - "gl": "^8.0.2", - "jsdom": "^22.1.0", + "canvas": "^3.2.1", + "jsdom": "^27.4.0", "mathjax": "3.2.2", - "specificity": "^0.4.1", - "svgpath": "^2.3.0", - "tmp": "^0.2.1", + "three": "0.162.0", + "tmp": "^0.2.5", "xhr2": "^0.2.1" }, "devDependencies": { - "@rollup/plugin-json": "6", - "@rollup/plugin-node-resolve": "15", - "@rollup/plugin-terser": "0", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "16.0.1", + "@rollup/plugin-replace": "6.0.2", + "@rollup/plugin-terser": "0.4.4", + "@stylistic/eslint-plugin": "^4.4.1", "docdash": "^2.0.2", - "eslint": "^8.57.0", - "eslint-config-semistandard": "^17.0.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1", - "jsdoc": "^4.0.2", - "magic-string": "^0.30.0", - "mocha": "9", - "rollup": "3", - "rollup-plugin-ascii": "0.0", - "rollup-plugin-ignore": "1.0.10", - "rollup-plugin-modify": "^3.0.0" + "eslint": "^9.39.2", + "gl": "9.0.0-rc.9", + "jsdoc": "^4.0.5", + "rollup": "4.56.0", + "rollup-plugin-ascii": "0.0.3", + "rollup-plugin-ignore": "1.0.10" }, "engines": { "node": ">= 0.18.0" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", + "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -69,207 +109,454 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.14.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10.10.0" + "node": "*" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12.22" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://eslint.org/donate" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@exodus/bytes": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.10.0.tgz", + "integrity": "sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg==", + "license": "MIT", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18.18.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=12" + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18.18" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12" + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -277,50 +564,46 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jsdoc/salty": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", - "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "lodash": "^4.17.21" }, @@ -328,143 +611,51 @@ "node": ">=v12.0.0" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@oneidentity/zstd-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@oneidentity/zstd-js/-/zstd-js-1.0.3.tgz", "integrity": "sha512-Jm6sawqxLzBrjC4sg2BeXToa33yPzUmq20CKsehKY2++D/gHb/oSwVjNgT+RH4vys+r8FynrgcNzGwhZWMLzfQ==", + "license": "SEE LICENSE IN LICENSE", "dependencies": { "@types/emscripten": "^1.39.4" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.1.0" }, @@ -481,15 +672,15 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", "is-module": "^1.0.0", "resolve": "^1.22.1" }, @@ -505,11 +696,34 @@ } } }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-terser": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, + "license": "MIT", "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", @@ -528,14 +742,15 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" @@ -549,358 +764,659 @@ } } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/emscripten": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.10.tgz", - "integrity": "sha512-TB/6hBkYQJxsZHSqyeuO1Jt0AB/bW6G7rHt9g7lML7SOF6lbgcHvw/Lr+69iqN0qxgXLhWKScAon73JNnptuDw==" + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", + "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@typescript-eslint/utils": "^8.32.1", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "eslint": ">=9.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@types/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, - "node_modules/aproba": { + "node_modules/@types/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "dev": true, + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "node_modules/@typescript-eslint/utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", + "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "@typescript-eslint/types": "8.53.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, "bin": { - "atob": "bin/atob.js" + "acorn": "bin/acorn" }, "engines": { - "node": ">= 4.5.0" + "node": ">=0.4.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -919,24 +1435,24 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" } }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", "dependencies": { "file-uri-to-path": "1.0.0" } @@ -944,12 +1460,15 @@ "node_modules/bit-twiddle": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", - "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==" + "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==", + "dev": true, + "license": "MIT" }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -960,44 +1479,17 @@ "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" + "balanced-match": "^1.0.0" } }, "node_modules/buffer": { @@ -1018,6 +1510,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1027,111 +1520,30 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dev": true, - "dependencies": { - "semver": "^7.0.0" - } + "license": "MIT" }, "node_modules/cacache": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz", - "integrity": "sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", + "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/callsites": { @@ -1139,34 +1551,24 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/canvas": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", - "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.1.tgz", + "integrity": "sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==", "hasInstallScript": true, + "license": "MIT", + "peer": true, "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.17.0", - "simple-get": "^3.0.3" + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" }, "engines": { - "node": ">=6" + "node": "^18.12.0 || >= 20.9.0" } }, "node_modules/catharsis": { @@ -1174,6 +1576,7 @@ "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.15" }, @@ -1186,6 +1589,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1197,76 +1601,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1277,52 +1627,37 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1332,98 +1667,63 @@ "node": ">= 8" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=4" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "license": "MIT", "dependencies": { - "rrweb-cssom": "^0.6.0" + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/data-urls": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20" } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1434,38 +1734,32 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" }, "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { - "mimic-response": "^2.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -1474,146 +1768,67 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/docdash": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/docdash/-/docdash-2.0.2.tgz", "integrity": "sha512-3SDDheh9ddrwjzf6dPFe1a16M6ftstqTNjik2+1fx46l24H9dD2osT2q9y+nBEC1wWz4GIqA48JmicOLQ0R8xA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jsdoc/salty": "^0.2.1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -1622,6 +1837,8 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1629,523 +1846,161 @@ "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-semistandard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-semistandard/-/eslint-config-semistandard-17.0.0.tgz", - "integrity": "sha512-tLi0JYmfiiJgtmRhoES55tENatR7y/5aXOh6cBeW+qjzl1+WwyV0arDqR65XN3/xrPZt+/1EG+xNLknV/0jWsQ==", - "dev": true, - "peerDependencies": { - "eslint": "^8.13.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=12.0.0" + "url": "https://eslint.org/donate" }, "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" + "jiti": "*" }, "peerDependenciesMeta": { - "eslint": { + "jiti": { "optional": true } } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", - "dev": true, - "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": "*" } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -2158,6 +2013,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2170,6 +2026,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -2178,13 +2035,15 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -2193,81 +2052,83 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" } }, "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" - }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -2279,97 +2140,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, - "node_modules/font-family-papandreou": { - "version": "0.2.0-patch2", - "resolved": "https://registry.npmjs.org/font-family-papandreou/-/font-family-papandreou-0.2.0-patch2.tgz", - "integrity": "sha512-l/YiRdBSH/eWv6OF3sLGkwErL+n0MqCICi9mppTZBOCL5vixWGDqCYvRcuxB2h7RGCTzaTKOHT2caHvCXQPRlw==" + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -2377,17 +2180,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2401,97 +2200,7 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2499,40 +2208,41 @@ "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" }, "node_modules/gl": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/gl/-/gl-8.0.2.tgz", - "integrity": "sha512-bAQg+aXnz/uBDwWEld/6Fifj0KBN3H3XElQgoz/F9hmBhIYKRohZP/41y43tHBQ6+LqVt1JKM1vts7t+Nzc6oA==", + "version": "9.0.0-rc.9", + "resolved": "https://registry.npmjs.org/gl/-/gl-9.0.0-rc.9.tgz", + "integrity": "sha512-rR9PHdCXwB+/KPkWVz13GHdRU9JzC5k1GSG+sLrfXPcWGvlYcqg9LuKDo74ZagirbL8/PPzar36pEn/pJjCQPA==", + "dev": true, "hasInstallScript": true, + "license": "BSD-2-Clause", "dependencies": { "bindings": "^1.5.0", "bit-twiddle": "^1.0.2", "glsl-tokenizer": "^2.1.5", - "nan": "^2.18.0", - "node-abi": "^3.56.0", - "node-gyp": "^10.0.1", - "prebuild-install": "^7.1.1" + "nan": "^2.24.0", + "node-gyp": "^12.1.0", + "prebuild-install": "^7.1.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2543,6 +2253,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -2550,155 +2261,68 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "type-fest": "^0.20.2" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/glsl-tokenizer": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", - "dependencies": { - "through2": "^0.6.3" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "through2": "^0.6.3" } }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2706,60 +2330,58 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", "dependencies": { - "whatwg-encoding": "^2.0.0" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -2784,22 +2406,25 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2815,71 +2440,42 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -2888,347 +2484,62 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" - }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3241,31 +2552,28 @@ "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "xmlcreate": "^2.0.4" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" - }, "node_modules/jsdoc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", - "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz", + "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", @@ -3284,44 +2592,43 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/jsdom": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", - "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", - "dependencies": { - "abab": "^2.0.6", - "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", - "decimal.js": "^10.4.3", - "domexception": "^4.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=16" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -3334,6 +2641,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -3345,37 +2653,29 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } + "license": "MIT" }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -3385,6 +2685,7 @@ "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.9" } @@ -3394,6 +2695,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3403,12 +2705,13 @@ } }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/locate-path": { @@ -3416,6 +2719,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -3427,110 +2731,78 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "license": "BlueOak-1.0.0", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/magic-string": { - "version": "0.30.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", - "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-fetch-happen": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", - "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", + "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-anchor": { @@ -3538,6 +2810,7 @@ "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true, + "license": "Unlicense", "peerDependencies": { "@types/markdown-it": "*", "markdown-it": "*" @@ -3548,6 +2821,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -3558,67 +2832,65 @@ "node_modules/mathjax": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.2.tgz", - "integrity": "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw==" + "integrity": "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw==", + "license": "Apache-2.0" + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" }, "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -3627,6 +2899,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -3635,16 +2909,18 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", + "dev": true, + "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -3654,6 +2930,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3665,6 +2943,8 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -3672,10 +2952,19 @@ "node": ">=8" } }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3687,6 +2976,8 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -3694,10 +2985,19 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -3709,534 +3009,208 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz", - "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/node-gyp/node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "engines": { - "node": ">=16" - } - }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" + "yallist": "^4.0.0" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" + "minipass": "^7.1.2" }, "engines": { - "node": ">=6" + "node": ">= 18" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" + "node_modules/nan": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz", + "integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==", + "dev": true, + "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" + "semver": "^7.3.5" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz", + "integrity": "sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.2", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "node-gyp": "bin/node-gyp.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">= 0.4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "abbrev": "^4.0.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "nopt": "bin/nopt.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/ospec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ospec/-/ospec-3.1.0.tgz", - "integrity": "sha512-+nGtjV3vlADp+UGfL51miAh/hB4awPBkQrArhcgG4trAaoA2gKt5bf9w0m9ch9zOr555cHWaCHZEDiBOkNZSxw==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "ospec": "bin/ospec" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -4252,6 +3226,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -4263,14 +3238,13 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4281,6 +3255,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -4289,20 +3264,22 @@ } }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -4315,22 +3292,17 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4339,55 +3311,51 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", + "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", @@ -4402,76 +3370,32 @@ "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -4480,15 +3404,11 @@ "node": ">=10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4498,40 +3418,27 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "license": "MIT", + "engines": { + "node": ">=6" + } }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -4540,6 +3447,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -4554,6 +3462,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4562,98 +3471,52 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": ">= 6" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.21" } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4663,6 +3526,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -4671,47 +3535,55 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "license": "MIT", + "peer": true, "dependencies": { - "glob": "^7.1.3" + "@types/estree": "1.0.8" }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" } }, @@ -4720,6 +3592,7 @@ "resolved": "https://registry.npmjs.org/rollup-plugin-ascii/-/rollup-plugin-ascii-0.0.3.tgz", "integrity": "sha512-4sx0tYyy3Fc7qKwJ7zpBk+wGeWzB1QzkADWNfwuDZ6AHV8vgPn42+cwjzl5RuiQpU+wRSeOhnfST+h4oivQc0w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "acorn": "^3.2.0", "estree-walker": "^0.2.1", @@ -4733,6 +3606,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", "integrity": "sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4744,13 +3618,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz", "integrity": "sha512-6/I1dwNKk0N9iGOU3ydzAAurz4NPo/ttxZNCqgIVbWFvWyzWBSNonRrJ5CpjDuyBfmM7ENN7WCzUi9aT/UPXXQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/rollup-plugin-ascii/node_modules/magic-string": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.15.2.tgz", "integrity": "sha512-xkHb690SyIbvr1x6PiHoP2M2rTkIt9La8gZLjoXIjcm7CI0a+9V9JqQWVQPsBLgt9qzkgtCuYN+/Thqw6crg6w==", "dev": true, + "license": "MIT", "dependencies": { "vlq": "^0.2.1" } @@ -4759,87 +3635,49 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/rollup-plugin-ignore/-/rollup-plugin-ignore-1.0.10.tgz", "integrity": "sha512-VsbnfwwaTv2Dxl2onubetX/3RnSnplNnjdix0hvF8y2YpqdzlZrjIq6zkcuVJ08XysS8zqW3gt3ORBndFDgsrg==", - "dev": true - }, - "node_modules/rollup-plugin-modify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-modify/-/rollup-plugin-modify-3.0.0.tgz", - "integrity": "sha512-p/ffs0Y2jz2dEnWjq1oVC7SY37tuS+aP7whoNaQz1EAAOPg+k3vKJo8cMMWx6xpdd0NzhX4y2YF9o/NPu5YR0Q==", - "dev": true, - "dependencies": { - "magic-string": "0.25.2", - "ospec": "3.1.0" - } - }, - "node_modules/rollup-plugin-modify/node_modules/magic-string": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", - "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.4" - } + "license": "MIT" }, "node_modules/rollup-pluginutils": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz", "integrity": "sha512-SjdWWWO/CUoMpDy8RUbZ/pSpG68YHmhk5ROKNIoi2En9bJ8bTt3IhYi254RWiTclQmL7Awmrq+rZFOhZkJAHmQ==", "dev": true, + "license": "MIT", "dependencies": { "estree-walker": "^0.2.1", "minimatch": "^3.0.2" } }, + "node_modules/rollup-pluginutils/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rollup-pluginutils/node_modules/estree-walker": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz", "integrity": "sha512-6/I1dwNKk0N9iGOU3ydzAAurz4NPo/ttxZNCqgIVbWFvWyzWBSNonRrJ5CpjDuyBfmM7ENN7WCzUi9aT/UPXXQ==", - "dev": true - }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } + "license": "MIT" }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "node_modules/rollup-pluginutils/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, "node_modules/safe-buffer": { @@ -4859,34 +3697,22 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + ], + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" }, @@ -4895,12 +3721,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4908,67 +3732,22 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4980,33 +3759,12 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -5024,14 +3782,30 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "decompress-response": "^4.2.0", + "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -5040,6 +3814,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -5049,14 +3825,17 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -5065,24 +3844,15 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", - "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -5093,6 +3863,16 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -5102,156 +3882,32 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, - "node_modules/specificity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", - "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", - "bin": { - "specificity": "bin/specificity" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" - }, - "node_modules/ssri": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", - "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", + "dev": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/strip-json-comments": { @@ -5259,6 +3915,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5271,6 +3928,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -5283,6 +3941,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5290,39 +3949,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svgpath": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.6.0.tgz", - "integrity": "sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg==", - "funding": { - "url": "https://github.com/fontello/svg2ttf?sponsor=1" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -5333,12 +3987,14 @@ "node_modules/tar-fs/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -5350,44 +4006,15 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/terser": { - "version": "5.30.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", - "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -5398,30 +4025,29 @@ "node": ">=10" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "node_modules/three": { + "version": "0.162.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.162.0.tgz", + "integrity": "sha512-xfCYj4RnlozReCmUd+XQzj6/5OjDNHBy5nT6rVwrOKGENAvpXe2z1jL+DZYaMu4/9pNsjH/4Os/VvS9IrH7IOQ==", + "license": "MIT" }, "node_modules/through2": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "dev": true, + "license": "MIT", "dependencies": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" } }, - "node_modules/through2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, "node_modules/through2/node_modules/readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -5432,69 +4058,96 @@ "node_modules/through2/node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" }, - "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, "engines": { - "node": ">=14.14" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "tldts-core": "^7.0.19" }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">=14.14" } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^7.0.5" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=20" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -5507,6 +4160,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -5514,146 +4168,59 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=14.17" } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", - "dev": true + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" }, "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", + "dev": true, + "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", + "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/uri-js": { @@ -5661,84 +4228,73 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/vlq": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", "dependencies": { - "xml-name-validator": "^4.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dependencies": { - "iconv-lite": "0.6.3" - }, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "license": "MIT", "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -5749,98 +4305,27 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=0.10.0" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -5861,91 +4346,51 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.4" } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/yocto-queue": { @@ -5953,6 +4398,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index aef2e6983..1edbfb113 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jsroot", - "version": "7.7.99", + "version": "7.10.99", "engines": { "node": ">= 0.18.0" }, @@ -11,15 +11,22 @@ "module": "./modules/main.mjs", "types": "./types.d.ts", "exports": { - ".": "./modules/main.mjs", + ".": { + "import": "./modules/main.mjs", + "require": "./build/jsroot.js", + "types": "./types.d.ts" + }, "./core": "./modules/core.mjs", "./draw": "./modules/draw.mjs", "./io": "./modules/io.mjs", "./tree": "./modules/tree.mjs", + "./rntuple": "./modules/rntuple.mjs", "./colors": "./modules/base/colors.mjs", "./hierarchy": "./modules/gui/HierarchyPainter.mjs", "./latex": "./modules/base/latex.mjs", "./geom": "./modules/geom/TGeoPainter.mjs", + "./geom_nothreejs": "./build/geom_nothreejs.mjs", + "./base3d": "./modules/base/base3d.mjs", "./testing": "./modules/testing.mjs" }, "contributors": [ @@ -32,23 +39,6 @@ "email": "s.linev@gsi.de" } ], - "dependencies": { - "@oneidentity/zstd-js": "^1.0.3", - "canvas": "^2.11.2", - "gl": "^8.0.2", - "jsdom": "^22.1.0", - "mathjax": "3.2.2", - "tmp": "^0.2.1", - "xhr2": "^0.2.1", - "cssesc": "^3.0.0", - "font-family-papandreou": "^0.2.0-patch1", - "svgpath": "^2.3.0", - "specificity": "^0.4.1", - "@babel/runtime": "^7.14.0", - "atob": "^2.1.2", - "btoa": "^1.2.1", - "fflate": "^0.4.8" - }, "repository": { "type": "git", "url": "https://github.com/root-project/jsroot.git" @@ -66,29 +56,34 @@ "types.d.ts", "LICENSE" ], + "dependencies": { + "@oneidentity/zstd-js": "^1.0.3", + "canvas": "^3.2.1", + "jsdom": "^27.4.0", + "mathjax": "3.2.2", + "tmp": "^0.2.5", + "xhr2": "^0.2.1", + "three": "0.162.0" + }, "devDependencies": { - "@rollup/plugin-json": "6", - "@rollup/plugin-node-resolve": "15", - "@rollup/plugin-terser": "0", + "gl": "9.0.0-rc.9", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "16.0.1", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-replace": "6.0.2", + "eslint": "^9.39.2", + "@stylistic/eslint-plugin": "^4.4.1", "docdash": "^2.0.2", - "eslint": "^8.57.0", - "eslint-config-semistandard": "^17.0.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1", - "jsdoc": "^4.0.2", - "magic-string": "^0.30.0", - "mocha": "9", - "rollup": "3", - "rollup-plugin-ascii": "0.0", - "rollup-plugin-ignore": "1.0.10", - "rollup-plugin-modify": "^3.0.0" + "jsdoc": "^4.0.5", + "rollup": "4.56.0", + "rollup-plugin-ascii": "0.0.3", + "rollup-plugin-ignore": "1.0.10" }, "scripts": { "build": "rollup -c build/rollup.config.js", "doc": "jsdoc -c docs/jsdoc.json -R docs/main.md && jsdoc -c docs/jsdoc.json -p -d docs/jsdocfull -R docs/main.md", - "check": "eslint modules/*.mjs modules/*/*.mjs" + "check": "eslint modules/*.mjs modules/*/*.mjs --ignore-pattern modules/three.mjs --ignore-pattern modules/three_addons.mjs --ignore-pattern modules/d3.mjs --ignore-pattern modules/base/jspdf.mjs --ignore-pattern modules/base/svg2pdf.mjs --ignore-pattern modules/base/lzma.mjs --ignore-pattern modules/base/zstd.mjs --ignore-pattern modules/base/sha256.mjs --ignore-pattern modules/gui/lil-gui.mjs --no-warn-ignored", + "fix": "eslint --fix modules/*.mjs modules/*/*.mjs --ignore-pattern modules/three.mjs --ignore-pattern modules/three_addons.mjs --ignore-pattern modules/d3.mjs --ignore-pattern modules/base/jspdf.mjs --ignore-pattern modules/base/svg2pdf.mjs --ignore-pattern modules/base/lzma.mjs --ignore-pattern modules/base/zstd.mjs --ignore-pattern modules/base/sha256.mjs --ignore-pattern modules/gui/lil-gui.mjs --no-warn-ignored" }, "keywords": [ "ROOT", @@ -101,6 +96,7 @@ "fs": false, "crypto": false, "@oneidentity/zstd-js": false, + "node:worker_threads": false, "zlib": false, "stream": false, "url": false, @@ -112,8 +108,6 @@ "net": false, "tls": false, "assert": false, - "atob": false, - "btoa": false, "canvas": false, "gl": false, "jsdom": false, @@ -122,13 +116,6 @@ "xhr": false, "xhr2": false, "child_process": false, - "cssesc": false, - "font-family-papandreou": false, - "svgpath": false, - "specificity": false, - "@babel/runtime": false, - "fflate": false, - "canvg": false, "html2canvas": false, "core-js": false, "dompurify": false diff --git a/readme.md b/readme.md index 02f93f7e7..c9a049852 100644 --- a/readme.md +++ b/readme.md @@ -6,14 +6,14 @@ Data can be read and displayed from ROOT binary and JSON files. ## Examples -[![Color draw for TH2](https://root.cern/js/files/img/th2.png)](https://root.cern/js/latest/?nobrowser&file=../files/hsimple.root&item=hpxpy;1&opt=colz) [![2-dimensional TTree::Draw with cut options](https://root.cern/js/files/img/ttree.png)](https://root.cern/js/latest/?nobrowser&file=../files/hsimple.root&item=ntuple;1&opt=px:py::pz%3E4) [![Several variants of THStack drawing](https://root.cern/js/files/img/thstack.png)](https://root.cern/js/latest/?nobrowser&file=../files/histpainter6.root&item=draw_hstack;1) [![Drawing of TGeo model superimposed with tracks and hits](https://root.cern/js/files/img/geo_tracks.png)](https://root.cern/js/latest/?nobrowser&json=../files/geom/simple_alice.json.gz&file=../files/geom/tracks_hits.root&item=simple_alice.json.gz+tracks_hits.root/tracks;1+tracks_hits.root/hits;1) +[![Color draw for TH2](https://root.cern/js/files/img/th2.png)](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js/files/hsimple.root&item=hpxpy;1&opt=colz) [![2-dimensional TTree::Draw with cut options](https://root.cern/js/files/img/ttree.png)](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js/files/hsimple.root&item=ntuple;1&opt=px:py::pz%3E4) [![Several variants of THStack drawing](https://root.cern/js/files/img/thstack.png)](https://root.cern/js/latest/?nobrowser&file=https://root.cern/js/files/histpainter6.root&item=draw_hstack;1) [![Drawing of TGeo model superimposed with tracks and hits](https://root.cern/js/files/img/geo_tracks.png)](https://root.cern/js/latest/?nobrowser&json=https://root.cern/js/files/geom/simple_alice.json.gz&file=https://root.cern/js/files/geom/tracks_hits.root&item=simple_alice.json.gz+tracks_hits.root/tracks;1+tracks_hits.root/hits;1) ### In web browser ```javascript ... <body> - <div id="drawing" style="width:800px; height:600px"></div> + <div id="drawing" style="position: relative; width: 800px; height: 600px;"></div> </body> <script type='module'> import { openFile, draw } from 'https://root.cern/js/latest/modules/main.mjs'; @@ -30,7 +30,7 @@ import { openFile, makeSVG } from 'jsroot'; import { writeFileSync } from 'fs'; let file = await openFile('https://root.cern/js/files/hsimple.root'); let obj = await file.readObject('hpxpy;1'); -let svg = await makeSVG({ object: obj, option: 'lego2,pal50', width: 1200, height: 800 }); +let svg = await makeSVG({ object: obj, option: 'lego2,pal50', width: 800, height: 600 }); writeFileSync('lego2.svg', svg); ``` @@ -44,6 +44,11 @@ writeFileSync('lego2.svg', svg); ``` npm install jsroot ``` +For 3D rendering jsroot uses [gl](https://www.npmjs.com/package/gl) package which may have problems +during installation. To use jsroot without `gl` one can omit development packages: +``` +npm install jsroot --omit=dev +``` ## Documentation diff --git a/scripts/JSRoot.core.js b/scripts/JSRoot.core.js index b9431dec8..5af4a9ba1 100644 --- a/scripts/JSRoot.core.js +++ b/scripts/JSRoot.core.js @@ -30,11 +30,13 @@ function _sync() { function loadPainter() { - if (jsrp) return Promise.resolve(jsrp); + if (jsrp) + return Promise.resolve(jsrp); return Promise.all([import('../modules/d3.mjs'), import('../modules/draw.mjs'), import('../modules/base/colors.mjs'), import('../modules/base/BasePainter.mjs'), import('../modules/base/ObjectPainter.mjs'), import('../modules/base/TAttLineHandler.mjs'), import('../modules/gui/menu.mjs')]).then(res => { - if (jsrp) return jsrp; + if (jsrp) + return jsrp; globalThis.d3 = res[0]; // assign global d3 jsrp = Object.assign({}, res[1], res[2], res[3], res[4], res[5], res[6]); globalThis.JSROOT.Painter = jsrp; @@ -109,7 +111,7 @@ function v6_require(need) { if (typeof need == 'string') need = need.split(';'); - need.forEach((name,indx) => { + need.forEach((name, indx) => { if ((name.indexOf('load:') == 0) || (name.indexOf('user:') == 0)) need[indx] = name.slice(5); else if (name == '2d') @@ -156,7 +158,8 @@ function v6_require(need) { arr.push(geo ? Promise.resolve(geo) : loadPainter().then(() => Promise.all([import('../modules/geom/geobase.mjs'), import('../modules/geom/TGeoPainter.mjs'), import('../modules/base/base3d.mjs'), import('../modules/three.mjs'), import('../modules/three_addons.mjs')])).then(res => { - if (geo) return geo; + if (geo) + return geo; globalThis.JSROOT.GEO = geo = Object.assign({}, res[0], res[1]); globalThis.JSROOT.TGeoPainter = res[1].TGeoPainter; @@ -180,8 +183,8 @@ function v6_require(need) { else if (name == 'painter') arr.push(loadPainter()); else if (name == 'hierarchy') - arr.push(Promise.all([import('../modules/gui/HierarchyPainter.mjs'), import('../modules/draw/TTree.mjs')]).then(arr => { - Object.assign(globalThis.JSROOT, arr[0], arr[1]); + arr.push(Promise.all([import('../modules/gui/display.mjs'), import('../modules/gui/HierarchyPainter.mjs'), import('../modules/draw/TTree.mjs')]).then(arr => { + Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]); getHPainter = arr[0].getHPainter; globalThis.JSROOT.hpainter = getHPainter(); return globalThis.JSROOT; @@ -221,28 +224,30 @@ exports.define = function(req, factoryFunc) { sync_promises.push(pr); // will wait until other PRs are finished } -/// duplicate function here, used before loading any other functionality +// duplicate function here, used before loading any other functionality exports.decodeUrl = function(url) { let res = { opts: {}, has(opt) { return this.opts[opt] !== undefined; }, - get(opt,dflt) { let v = this.opts[opt]; return v !== undefined ? v : dflt; } + get(opt,dflt) { return this.opts[opt] ?? dflt; } }; if (!url || (typeof url !== 'string')) { - if (typeof document === 'undefined') return res; + if (typeof document === 'undefined') + return res; url = document.URL; } res.url = url; let p1 = url.indexOf('?'); - if (p1 < 0) return res; + if (p1 < 0) + return res; url = decodeURI(url.slice(p1+1)); - while (url.length > 0) { + while (url) { // try to correctly handle quotes in the URL let pos = 0, nq = 0, eq = -1, firstq = -1; - while ((pos < url.length) && ((nq !== 0) || ((url[pos] !== '&') && (url[pos] !== '#')))) { + while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) { switch (url[pos]) { case "'": if (nq >= 0) nq = (nq+1)%2; if (firstq < 0) firstq = pos; break; case '"': if (nq <= 0) nq = (nq-1)%2; if (firstq < 0) firstq = pos; break; @@ -254,10 +259,12 @@ exports.decodeUrl = function(url) { res.opts[url.slice(0,pos)] = ''; } if (eq > 0) { let val = url.slice(eq+1, pos); - if (((val[0] === "'") || (val[0] === '"')) && (val[0] === val[val.length-1])) val = val.slice(1, val.length-1); + if (((val[0] === "'") || (val[0] === '"')) && (val.at(0) === val.at(-1))) + val = val.slice(1, val.length - 1); res.opts[url.slice(0,eq)] = val; } - if ((pos >= url.length) || (url[pos] == '#')) break; + if ((pos >= url.length) || (url[pos] == '#')) + break; url = url.slice(pos+1); } @@ -289,51 +296,46 @@ exports.connectWebWindow = function(arg) { } return _sync().then(() => { - let prereq = ''; - if (arg.prereq) prereq = arg.prereq; - if (arg.prereq2) prereq += ';' + arg.prereq2; - - if (!prereq) return; + if (arg.prereq) + prereq = arg.prereq; + if (arg.prereq2) + prereq += ';' + arg.prereq2; + if (!prereq) + return; return v6_require(prereq).then(() => { - delete arg.prereq; - delete arg.prereq2; - - if (arg.prereq_logdiv && document) { - let elem = document.getElementById(arg.prereq_logdiv); - if (elem) elem.innerHTML = ''; - delete arg.prereq_logdiv; - } - }); + delete arg.prereq; + delete arg.prereq2; + + if (arg.prereq_logdiv && document) { + let elem = document.getElementById(arg.prereq_logdiv); + if (elem) elem.innerHTML = ''; + delete arg.prereq_logdiv; + } + }); }).then(() => import('../modules/webwindow.mjs')).then(h => { globalThis.JSROOT.WebWindowHandle = h.WebWindowHandle; return h.connectWebWindow(arg); }); } - // try to define global JSROOT if ((typeof globalThis !== 'undefined') && !globalThis.JSROOT) { - - console.warn('Usage of JSRoot.core.js script is obsolete. Please swicth to modules. See https://github.com/root-project/jsroot/blob/master/docs/JSROOT.md#migration-v6---v7'); + console.warn('Usage of JSRoot.core.js script is obsolete. Please swicth to ES6 modules. See https://github.com/root-project/jsroot/blob/master/docs/JSROOT.md#migration-v6---v7'); globalThis.JSROOT = exports; - globalThis.JSROOT.extend = Object.assign; - globalThis.JSROOT._complete_loading = _sync; - let pr = Promise.all([import('../modules/core.mjs'), import('../modules/draw.mjs'), import('../modules/gui/HierarchyPainter.mjs')]).then(arr => { - + let pr = Promise.all([import('../modules/core.mjs'), + import('../modules/draw.mjs'), + import('../modules/gui/HierarchyPainter.mjs'), + import('../modules/gui/display.mjs')]).then(arr => { Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]); - Object.assign(globalThis.JSROOT.settings, workaround_settings); - globalThis.JSROOT._ = arr[0].internals; - - getHPainter = arr[2].getHPainter; - + getHPainter = arr[3].getHPainter; globalThis.JSROOT.hpainter = getHPainter(); }); diff --git a/scripts/geoworker.js b/scripts/geoworker.js deleted file mode 100644 index d633263c6..000000000 --- a/scripts/geoworker.js +++ /dev/null @@ -1,124 +0,0 @@ - -let THREE, ClonedNodes, createFrustum; - -import('../modules/three.mjs').then(handle => { - THREE = handle; - if (console) console.log(`geoworker started three.js r${THREE.REVISION}`); -}); - -import('../modules/geom/geobase.mjs').then(handle => { - ClonedNodes = handle.ClonedNodes; - createFrustum = handle.createFrustum; -}); - -// importScripts("three.min.js", "JSRoot.csg.js", "JSRoot.geobase.js"); - - -let clones = null; - -onmessage = function(e) { - - if (typeof e.data == 'string') { - console.log(`Worker get message ${e.data}`); - return; - } - - if (typeof e.data != 'object') return; - - // simple workaround to wait until modules are loaded - if (!THREE || !ClonedNodes) - return setTimeout(() => onmessage(e), 100); - - e.data.tm1 = new Date().getTime(); - - if (e.data.init) { - // console.log(`start worker ${e.data.tm1 - e.data.tm0}`); - - let nodes = e.data.clones; - if (nodes) { - // console.log(`get clones ${nodes.length}`); - clones = new ClonedNodes(null, nodes); - clones.setVisLevel(e.data.vislevel); - clones.setMaxVisNodes(e.data.maxvisnodes); - delete e.data.clones; - clones.sortmap = e.data.sortmap; - } - - e.data.tm2 = new Date().getTime(); - - return postMessage(e.data); - } - - if (e.data.shapes) { - // this is task to create geometries in the worker - - let shapes = e.data.shapes, transferables = []; - - // build all shapes up to specified limit, also limit execution time - for (let n = 0; n < 100; ++n) { - let res = clones.buildShapes(shapes, e.data.limit, 1000); - if (res.done) break; - postMessage({ progress: "Worker creating: " + res.shapes + " / " + shapes.length + " shapes, " + res.faces + " faces" }); - } - - for (let n=0;n<shapes.length;++n) { - let item = shapes[n]; - - if (item.geom) { - let bufgeom; - if (item.geom instanceof THREE.BufferGeometry) { - bufgeom = item.geom; - } else { - let bufgeom = new THREE.BufferGeometry(); - bufgeom.fromGeometry(item.geom); - } - - item.buf_pos = bufgeom.attributes.position.array; - item.buf_norm = bufgeom.attributes.normal.array; - - // use nice feature of HTML workers with transferable - // we allow to take ownership of buffer from local array - // therefore buffer content not need to be copied - transferables.push(item.buf_pos.buffer, item.buf_norm.buffer); - - delete item.geom; - } - - delete item.shape; // no need to send back shape - } - - e.data.tm2 = new Date().getTime(); - - return postMessage(e.data, transferables); - } - - if (e.data.collect !== undefined) { - // this is task to collect visible nodes using camera position - - // first mark all visible flags - clones.setVisibleFlags(e.data.flags); - clones.setVisLevel(e.data.vislevel); - clones.setMaxVisNodes(e.data.maxvisnodes); - - delete e.data.flags; - - clones.produceIdShifts(); - - let matrix = null; - if (e.data.matrix) - matrix = new THREE.Matrix4().fromArray(e.data.matrix); - delete e.data.matrix; - - let res = clones.collectVisibles(e.data.collect, createFrustum(matrix)); - - e.data.new_nodes = res.lst; - e.data.complete = res.complete; // inform if all nodes are selected - - e.data.tm2 = new Date().getTime(); - - // console.log(`Collect visibles in worker ${e.data.new_nodes.length} takes ${e.data.tm2-e.data.tm1}`); - - return postMessage(e.data); - } - -} diff --git a/scripts/jspdf.es.min.js b/scripts/jspdf.es.min.js deleted file mode 100644 index f6a55cad2..000000000 --- a/scripts/jspdf.es.min.js +++ /dev/null @@ -1,332 +0,0 @@ -/** @license - * - * jsPDF - PDF Document creation from JavaScript - * Version 2.5.1 Built on 2023-11-22T13:26:15.457Z - * CommitID 00000000 - * - * Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF - * 2015-2021 yWorks GmbH, http://www.yworks.com - * 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX - * 2016-2018 Aras Abbasi <aras.abbasi@gmail.com> - * 2010 Aaron Spike, https://github.com/acspike - * 2012 Willow Systems Corporation, https://github.com/willowsystems - * 2012 Pablo Hess, https://github.com/pablohess - * 2012 Florian Jenett, https://github.com/fjenett - * 2013 Warren Weckesser, https://github.com/warrenweckesser - * 2013 Youssef Beddad, https://github.com/lifof - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2013 Stefan Slonevskiy, https://github.com/stefslon - * 2013 Jeremy Morel, https://github.com/jmorel - * 2013 Christoph Hartmann, https://github.com/chris-rock - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Makes, https://github.com/dollaruw - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 Steven Spungin, https://github.com/Flamenco - * 2014 Kenneth Glassey, https://github.com/Gavvers - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Contributor(s): - * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, - * kim3er, mfo, alnorth, Flamenco - */ - -import t from"@babel/runtime/helpers/typeof";import{zlibSync as e,unzlibSync as r}from"fflate";var n=function(){return"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this}();function i(){n.console&&"function"==typeof n.console.log&&n.console.log.apply(n.console,arguments)}var a={log:i,warn:function(t){n.console&&("function"==typeof n.console.warn?n.console.warn.apply(n.console,arguments):i.call(null,arguments))},error:function(t){n.console&&("function"==typeof n.console.error?n.console.error.apply(n.console,arguments):i(t))}};function o(t,e,r){var n=new XMLHttpRequest;n.open("GET",t),n.responseType="blob",n.onload=function(){l(n.response,e,r)},n.onerror=function(){a.error("could not download file")},n.send()}function s(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return e.status>=200&&e.status<=299}function c(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(r){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var u,h,l=n.saveAs||("object"!==("undefined"==typeof window?"undefined":t(window))||window!==n?function(){}:"undefined"!=typeof HTMLAnchorElement&&"download"in HTMLAnchorElement.prototype?function(t,e,r){var i=n.URL||n.webkitURL,a=document.createElement("a");e=e||t.name||"download",a.download=e,a.rel="noopener","string"==typeof t?(a.href=t,a.origin!==location.origin?s(a.href)?o(t,e,r):c(a,a.target="_blank"):c(a)):(a.href=i.createObjectURL(t),setTimeout((function(){i.revokeObjectURL(a.href)}),4e4),setTimeout((function(){c(a)}),0))}:"msSaveOrOpenBlob"in navigator?function(e,r,n){if(r=r||e.name||"download","string"==typeof e)if(s(e))o(e,r,n);else{var i=document.createElement("a");i.href=e,i.target="_blank",setTimeout((function(){c(i)}))}else navigator.msSaveOrOpenBlob(function(e,r){return void 0===r?r={autoBom:!1}:"object"!==t(r)&&(a.warn("Deprecated: Expected third argument to be a object"),r={autoBom:!r}),r.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e}(e,n),r)}:function(e,r,i,a){if((a=a||open("","_blank"))&&(a.document.title=a.document.body.innerText="downloading..."),"string"==typeof e)return o(e,r,i);var s="application/octet-stream"===e.type,c=/constructor/i.test(n.HTMLElement)||n.safari,u=/CriOS\/[\d]+/.test(navigator.userAgent);if((u||s&&c)&&"object"===("undefined"==typeof FileReader?"undefined":t(FileReader))){var h=new FileReader;h.onloadend=function(){var t=h.result;t=u?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),a?a.location.href=t:location=t,a=null},h.readAsDataURL(e)}else{var l=n.URL||n.webkitURL,f=l.createObjectURL(e);a?a.location=f:location.href=f,a=null,setTimeout((function(){l.revokeObjectURL(f)}),4e4)}}); -/** - * A class to parse color values - * @author Stoyan Stefanov <sstoo@gmail.com> - * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} - * @license Use it if you like it - */function f(t){var e;t=t||"",this.ok=!1,"#"==t.charAt(0)&&(t=t.substr(1,6));t={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"}[t=(t=t.replace(/ /g,"")).toLowerCase()]||t;for(var r=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}],n=0;n<r.length;n++){var i=r[n].re,a=r[n].process,o=i.exec(t);o&&(e=a(o),this.r=e[0],this.g=e[1],this.b=e[2],this.ok=!0)}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r}} -/** - * @license - * Joseph Myers does not specify a particular license for his work. - * - * Author: Joseph Myers - * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js - * - * Modified by: Owen Leong - */ -function d(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r=g(r,n,i,a,e[0],7,-680876936),a=g(a,r,n,i,e[1],12,-389564586),i=g(i,a,r,n,e[2],17,606105819),n=g(n,i,a,r,e[3],22,-1044525330),r=g(r,n,i,a,e[4],7,-176418897),a=g(a,r,n,i,e[5],12,1200080426),i=g(i,a,r,n,e[6],17,-1473231341),n=g(n,i,a,r,e[7],22,-45705983),r=g(r,n,i,a,e[8],7,1770035416),a=g(a,r,n,i,e[9],12,-1958414417),i=g(i,a,r,n,e[10],17,-42063),n=g(n,i,a,r,e[11],22,-1990404162),r=g(r,n,i,a,e[12],7,1804603682),a=g(a,r,n,i,e[13],12,-40341101),i=g(i,a,r,n,e[14],17,-1502002290),r=m(r,n=g(n,i,a,r,e[15],22,1236535329),i,a,e[1],5,-165796510),a=m(a,r,n,i,e[6],9,-1069501632),i=m(i,a,r,n,e[11],14,643717713),n=m(n,i,a,r,e[0],20,-373897302),r=m(r,n,i,a,e[5],5,-701558691),a=m(a,r,n,i,e[10],9,38016083),i=m(i,a,r,n,e[15],14,-660478335),n=m(n,i,a,r,e[4],20,-405537848),r=m(r,n,i,a,e[9],5,568446438),a=m(a,r,n,i,e[14],9,-1019803690),i=m(i,a,r,n,e[3],14,-187363961),n=m(n,i,a,r,e[8],20,1163531501),r=m(r,n,i,a,e[13],5,-1444681467),a=m(a,r,n,i,e[2],9,-51403784),i=m(i,a,r,n,e[7],14,1735328473),r=v(r,n=m(n,i,a,r,e[12],20,-1926607734),i,a,e[5],4,-378558),a=v(a,r,n,i,e[8],11,-2022574463),i=v(i,a,r,n,e[11],16,1839030562),n=v(n,i,a,r,e[14],23,-35309556),r=v(r,n,i,a,e[1],4,-1530992060),a=v(a,r,n,i,e[4],11,1272893353),i=v(i,a,r,n,e[7],16,-155497632),n=v(n,i,a,r,e[10],23,-1094730640),r=v(r,n,i,a,e[13],4,681279174),a=v(a,r,n,i,e[0],11,-358537222),i=v(i,a,r,n,e[3],16,-722521979),n=v(n,i,a,r,e[6],23,76029189),r=v(r,n,i,a,e[9],4,-640364487),a=v(a,r,n,i,e[12],11,-421815835),i=v(i,a,r,n,e[15],16,530742520),r=b(r,n=v(n,i,a,r,e[2],23,-995338651),i,a,e[0],6,-198630844),a=b(a,r,n,i,e[7],10,1126891415),i=b(i,a,r,n,e[14],15,-1416354905),n=b(n,i,a,r,e[5],21,-57434055),r=b(r,n,i,a,e[12],6,1700485571),a=b(a,r,n,i,e[3],10,-1894986606),i=b(i,a,r,n,e[10],15,-1051523),n=b(n,i,a,r,e[1],21,-2054922799),r=b(r,n,i,a,e[8],6,1873313359),a=b(a,r,n,i,e[15],10,-30611744),i=b(i,a,r,n,e[6],15,-1560198380),n=b(n,i,a,r,e[13],21,1309151649),r=b(r,n,i,a,e[4],6,-145523070),a=b(a,r,n,i,e[11],10,-1120210379),i=b(i,a,r,n,e[2],15,718787259),n=b(n,i,a,r,e[9],21,-343485551),t[0]=_(r,t[0]),t[1]=_(n,t[1]),t[2]=_(i,t[2]),t[3]=_(a,t[3])}function p(t,e,r,n,i,a){return e=_(_(e,t),_(n,a)),_(e<<i|e>>>32-i,r)}function g(t,e,r,n,i,a,o){return p(e&r|~e&n,t,e,i,a,o)}function m(t,e,r,n,i,a,o){return p(e&n|r&~n,t,e,i,a,o)}function v(t,e,r,n,i,a,o){return p(e^r^n,t,e,i,a,o)}function b(t,e,r,n,i,a,o){return p(r^(e|~n),t,e,i,a,o)}function y(t){var e,r=t.length,n=[1732584193,-271733879,-1732584194,271733878];for(e=64;e<=t.length;e+=64)d(n,w(t.substring(e-64,e)));t=t.substring(e-64);var i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<t.length;e++)i[e>>2]|=t.charCodeAt(e)<<(e%4<<3);if(i[e>>2]|=128<<(e%4<<3),e>55)for(d(n,i),e=0;e<16;e++)i[e]=0;return i[14]=8*r,d(n,i),n}function w(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}u=n.atob.bind(n),h=n.btoa.bind(n);var N="0123456789abcdef".split("");function L(t){for(var e="",r=0;r<4;r++)e+=N[t>>8*r+4&15]+N[t>>8*r&15];return e}function A(t){return String.fromCharCode((255&t)>>0,(65280&t)>>8,(16711680&t)>>16,(4278190080&t)>>24)}function x(t){return y(t).map(A).join("")}var S="5d41402abc4b2a76b9719d911017c592"!=function(t){for(var e=0;e<t.length;e++)t[e]=L(t[e]);return t.join("")}(y("hello"));function _(t,e){if(S){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r}return t+e&4294967295} -/** - * @license - * FPDF is released under a permissive license: there is no usage restriction. - * You may embed it freely in your application (commercial or not), with or - * without modifications. - * - * Reference: http://www.fpdf.org/en/script/script37.php - */function P(t,e){var r,n,i,a;if(t!==r){for(var o=(i=t,a=1+(256/t.length>>0),new Array(a+1).join(i)),s=[],c=0;c<256;c++)s[c]=c;var u=0;for(c=0;c<256;c++){var h=s[c];u=(u+h+o.charCodeAt(c))%256,s[c]=s[u],s[u]=h}r=t,n=s}else s=n;var l=e.length,f=0,d=0,p="";for(c=0;c<l;c++)d=(d+(h=s[f=(f+1)%256]))%256,s[f]=s[d],s[d]=h,o=s[(s[f]+s[d])%256],p+=String.fromCharCode(e.charCodeAt(c)^o);return p} -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - * Author: Owen Leong (@owenl131) - * Date: 15 Oct 2020 - * References: - * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt - * https://github.com/foliojs/pdfkit/blob/master/lib/security.js - * http://www.fpdf.org/en/script/script37.php - */var k={print:4,modify:8,copy:16,"annot-forms":32};function I(t,e,r,n){this.v=1,this.r=2;var i=192;t.forEach((function(t){if(void 0!==k.perm)throw new Error("Invalid permission: "+t);i+=k[t]})),this.padding="(¿N^NuŠAd\0NVÿú\b..\0¶Ðh>€/\f©þdSiz";var a=(e+this.padding).substr(0,32),o=(r+this.padding).substr(0,32);this.O=this.processOwnerPassword(a,o),this.P=-(1+(255^i)),this.encryptionKey=x(a+this.O+this.lsbFirstWord(this.P)+this.hexToBytes(n)).substr(0,5),this.U=P(this.encryptionKey,this.padding)}function F(t){if(/[^\u0000-\u00ff]/.test(t))throw new Error("Invalid PDF Name Object: "+t+", Only accept ASCII characters.");for(var e="",r=t.length,n=0;n<r;n++){var i=t.charCodeAt(n);if(i<33||35===i||37===i||40===i||41===i||47===i||60===i||62===i||91===i||93===i||123===i||125===i||i>126)e+="#"+("0"+i.toString(16)).slice(-2);else e+=t[n]}return e}function C(e){if("object"!==t(e))throw new Error("Invalid Context passed to initialize PubSub (jsPDF-module)");var r={};this.subscribe=function(t,e,n){if(n=n||!1,"string"!=typeof t||"function"!=typeof e||"boolean"!=typeof n)throw new Error("Invalid arguments passed to PubSub.subscribe (jsPDF-module)");r.hasOwnProperty(t)||(r[t]={});var i=Math.random().toString(35);return r[t][i]=[e,!!n],i},this.unsubscribe=function(t){for(var e in r)if(r[e][t])return delete r[e][t],0===Object.keys(r[e]).length&&delete r[e],!0;return!1},this.publish=function(t){if(r.hasOwnProperty(t)){var i=Array.prototype.slice.call(arguments,1),o=[];for(var s in r[t]){var c=r[t][s];try{c[0].apply(e,i)}catch(t){n.console&&a.error("jsPDF PubSub Error",t.message,t)}c[1]&&o.push(s)}o.length&&o.forEach(this.unsubscribe)}},this.getTopics=function(){return r}}function j(t){if(!(this instanceof j))return new j(t);var e="opacity,stroke-opacity".split(",");for(var r in t)t.hasOwnProperty(r)&&e.indexOf(r)>=0&&(this[r]=t[r]);this.id="",this.objectNumber=-1}function O(t,e){this.gState=t,this.matrix=e,this.id="",this.objectNumber=-1}function B(t,e,r,n,i){if(!(this instanceof B))return new B(t,e,r,n,i);this.type="axial"===t?2:3,this.coords=e,this.colors=r,O.call(this,n,i)}function M(t,e,r,n,i){if(!(this instanceof M))return new M(t,e,r,n,i);this.boundingBox=t,this.xStep=e,this.yStep=r,this.stream="",this.cloneIndex=0,O.call(this,n,i)}function E(e){var r,i="string"==typeof arguments[0]?arguments[0]:"p",o=arguments[1],s=arguments[2],c=arguments[3],u=[],d=1,p=16,g="S",m=null;"object"===t(e=e||{})&&(i=e.orientation,o=e.unit||o,s=e.format||s,c=e.compress||e.compressPdf||c,null!==(m=e.encryption||null)&&(m.userPassword=m.userPassword||"",m.ownerPassword=m.ownerPassword||"",m.userPermissions=m.userPermissions||[]),d="number"==typeof e.userUnit?Math.abs(e.userUnit):1,void 0!==e.precision&&(r=e.precision),void 0!==e.floatPrecision&&(p=e.floatPrecision),g=e.defaultPathOperation||"S"),u=e.filters||(!0===c?["FlateEncode"]:u),o=o||"mm",i=(""+(i||"P")).toLowerCase();var v=e.putOnlyUsedFonts||!1,b={},y={internal:{},__private__:{}};y.__private__.PubSub=C;var w="1.3",N=y.__private__.getPdfVersion=function(){return w};y.__private__.setPdfVersion=function(t){w=t};var L={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};y.__private__.getPageFormats=function(){return L};var A=y.__private__.getPageFormat=function(t){return L[t]};s=s||"a4";var x={COMPAT:"compat",ADVANCED:"advanced"},S=x.COMPAT;function _(){this.saveGraphicsState(),lt(new Vt(_t,0,0,-_t,0,Rr()*_t).toString()+" cm"),this.setFontSize(this.getFontSize()/_t),g="n",S=x.ADVANCED}function P(){this.restoreGraphicsState(),g="S",S=x.COMPAT}var k=y.__private__.combineFontStyleAndFontWeight=function(t,e){if("bold"==t&&"normal"==e||"bold"==t&&400==e||"normal"==t&&"italic"==e||"bold"==t&&"italic"==e)throw new Error("Invalid Combination of fontweight and fontstyle");return e&&(t=400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"),t};y.advancedAPI=function(t){var e=S===x.COMPAT;return e&&_.call(this),"function"!=typeof t||(t(this),e&&P.call(this)),this},y.compatAPI=function(t){var e=S===x.ADVANCED;return e&&P.call(this),"function"!=typeof t||(t(this),e&&_.call(this)),this},y.isAdvancedAPI=function(){return S===x.ADVANCED};var O,q=function(t){if(S!==x.ADVANCED)throw new Error(t+" is only available in 'advanced' API mode. You need to call advancedAPI() first.")},D=y.roundToPrecision=y.__private__.roundToPrecision=function(t,e){var n=r||e;if(isNaN(t)||isNaN(n))throw new Error("Invalid argument passed to jsPDF.roundToPrecision");return t.toFixed(n).replace(/0+$/,"")};O=y.hpf=y.__private__.hpf="number"==typeof p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,p)}:"smart"===p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,t>-1&&t<1?16:5)}:function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,16)};var R=y.f2=y.__private__.f2=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f2");return D(t,2)},T=y.__private__.f3=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f3");return D(t,3)},U=y.scale=y.__private__.scale=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.scale");return S===x.COMPAT?t*_t:S===x.ADVANCED?t:void 0},z=function(t){return S===x.COMPAT?Rr()-t:S===x.ADVANCED?t:void 0},H=function(t){return U(z(t))};y.__private__.setPrecision=y.setPrecision=function(t){"number"==typeof parseInt(t,10)&&(r=parseInt(t,10))};var W,V="00000000000000000000000000000000",G=y.__private__.getFileId=function(){return V},Y=y.__private__.setFileId=function(t){return V=void 0!==t&&/^[a-fA-F0-9]{32}$/.test(t)?t.toUpperCase():V.split("").map((function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))})).join(""),null!==m&&(Ye=new I(m.userPermissions,m.userPassword,m.ownerPassword,V)),V};y.setFileId=function(t){return Y(t),this},y.getFileId=function(){return G()};var J=y.__private__.convertDateToPDFDate=function(t){var e=t.getTimezoneOffset(),r=e<0?"+":"-",n=Math.floor(Math.abs(e/60)),i=Math.abs(e%60),a=[r,Q(n),"'",Q(i),"'"].join("");return["D:",t.getFullYear(),Q(t.getMonth()+1),Q(t.getDate()),Q(t.getHours()),Q(t.getMinutes()),Q(t.getSeconds()),a].join("")},X=y.__private__.convertPDFDateToDate=function(t){var e=parseInt(t.substr(2,4),10),r=parseInt(t.substr(6,2),10)-1,n=parseInt(t.substr(8,2),10),i=parseInt(t.substr(10,2),10),a=parseInt(t.substr(12,2),10),o=parseInt(t.substr(14,2),10);return new Date(e,r,n,i,a,o,0)},K=y.__private__.setCreationDate=function(t){var e;if(void 0===t&&(t=new Date),t instanceof Date)e=J(t);else{if(!/^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/.test(t))throw new Error("Invalid argument passed to jsPDF.setCreationDate");e=t}return W=e},Z=y.__private__.getCreationDate=function(t){var e=W;return"jsDate"===t&&(e=X(W)),e};y.setCreationDate=function(t){return K(t),this},y.getCreationDate=function(t){return Z(t)};var $,Q=y.__private__.padd2=function(t){return("0"+parseInt(t)).slice(-2)},tt=y.__private__.padd2Hex=function(t){return("00"+(t=t.toString())).substr(t.length)},et=0,rt=[],nt=[],it=0,at=[],ot=[],st=!1,ct=nt,ut=function(){et=0,it=0,nt=[],rt=[],at=[],Qt=Kt(),te=Kt()};y.__private__.setCustomOutputDestination=function(t){st=!0,ct=t};var ht=function(t){st||(ct=t)};y.__private__.resetCustomOutputDestination=function(){st=!1,ct=nt};var lt=y.__private__.out=function(t){return t=t.toString(),it+=t.length+1,ct.push(t),ct},ft=y.__private__.write=function(t){return lt(1===arguments.length?t.toString():Array.prototype.join.call(arguments," "))},dt=y.__private__.getArrayBuffer=function(t){for(var e=t.length,r=new ArrayBuffer(e),n=new Uint8Array(r);e--;)n[e]=t.charCodeAt(e);return r},pt=[["Helvetica","helvetica","normal","WinAnsiEncoding"],["Helvetica-Bold","helvetica","bold","WinAnsiEncoding"],["Helvetica-Oblique","helvetica","italic","WinAnsiEncoding"],["Helvetica-BoldOblique","helvetica","bolditalic","WinAnsiEncoding"],["Courier","courier","normal","WinAnsiEncoding"],["Courier-Bold","courier","bold","WinAnsiEncoding"],["Courier-Oblique","courier","italic","WinAnsiEncoding"],["Courier-BoldOblique","courier","bolditalic","WinAnsiEncoding"],["Times-Roman","times","normal","WinAnsiEncoding"],["Times-Bold","times","bold","WinAnsiEncoding"],["Times-Italic","times","italic","WinAnsiEncoding"],["Times-BoldItalic","times","bolditalic","WinAnsiEncoding"],["ZapfDingbats","zapfdingbats","normal",null],["Symbol","symbol","normal",null]];y.__private__.getStandardFonts=function(){return pt};var gt=e.fontSize||16;y.__private__.setFontSize=y.setFontSize=function(t){return gt=S===x.ADVANCED?t/_t:t,this};var mt,vt=y.__private__.getFontSize=y.getFontSize=function(){return S===x.COMPAT?gt:gt*_t},bt=e.R2L||!1;y.__private__.setR2L=y.setR2L=function(t){return bt=t,this},y.__private__.getR2L=y.getR2L=function(){return bt};var yt,wt=y.__private__.setZoomMode=function(t){var e=[void 0,null,"fullwidth","fullheight","fullpage","original"];if(/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(t))mt=t;else if(isNaN(t)){if(-1===e.indexOf(t))throw new Error('zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "'+t+'" is not recognized.');mt=t}else mt=parseInt(t,10)};y.__private__.getZoomMode=function(){return mt};var Nt,Lt=y.__private__.setPageMode=function(t){if(-1==[void 0,null,"UseNone","UseOutlines","UseThumbs","FullScreen"].indexOf(t))throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "'+t+'" is not recognized.');yt=t};y.__private__.getPageMode=function(){return yt};var At=y.__private__.setLayoutMode=function(t){if(-1==[void 0,null,"continuous","single","twoleft","tworight","two"].indexOf(t))throw new Error('Layout mode must be one of continuous, single, twoleft, tworight. "'+t+'" is not recognized.');Nt=t};y.__private__.getLayoutMode=function(){return Nt},y.__private__.setDisplayMode=y.setDisplayMode=function(t,e,r){return wt(t),At(e),Lt(r),this};var xt={title:"",subject:"",author:"",keywords:"",creator:""};y.__private__.getDocumentProperty=function(t){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");return xt[t]},y.__private__.getDocumentProperties=function(){return xt},y.__private__.setDocumentProperties=y.setProperties=y.setDocumentProperties=function(t){for(var e in xt)xt.hasOwnProperty(e)&&t[e]&&(xt[e]=t[e]);return this},y.__private__.setDocumentProperty=function(t,e){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");return xt[t]=e};var St,_t,Pt,kt,It,Ft={},Ct={},jt=[],Ot={},Bt={},Mt={},Et={},qt=null,Dt=0,Rt=[],Tt=new C(y),Ut=e.hotfixes||[],zt={},Ht={},Wt=[],Vt=function t(e,r,n,i,a,o){if(!(this instanceof t))return new t(e,r,n,i,a,o);isNaN(e)&&(e=1),isNaN(r)&&(r=0),isNaN(n)&&(n=0),isNaN(i)&&(i=1),isNaN(a)&&(a=0),isNaN(o)&&(o=0),this._matrix=[e,r,n,i,a,o]};Object.defineProperty(Vt.prototype,"sx",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"shy",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"shx",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"sy",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"tx",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"ty",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"a",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"b",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"c",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"d",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"e",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"f",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"rotation",{get:function(){return Math.atan2(this.shx,this.sx)}}),Object.defineProperty(Vt.prototype,"scaleX",{get:function(){return this.decompose().scale.sx}}),Object.defineProperty(Vt.prototype,"scaleY",{get:function(){return this.decompose().scale.sy}}),Object.defineProperty(Vt.prototype,"isIdentity",{get:function(){return 1===this.sx&&(0===this.shy&&(0===this.shx&&(1===this.sy&&(0===this.tx&&0===this.ty))))}}),Vt.prototype.join=function(t){return[this.sx,this.shy,this.shx,this.sy,this.tx,this.ty].map(O).join(t)},Vt.prototype.multiply=function(t){var e=t.sx*this.sx+t.shy*this.shx,r=t.sx*this.shy+t.shy*this.sy,n=t.shx*this.sx+t.sy*this.shx,i=t.shx*this.shy+t.sy*this.sy,a=t.tx*this.sx+t.ty*this.shx+this.tx,o=t.tx*this.shy+t.ty*this.sy+this.ty;return new Vt(e,r,n,i,a,o)},Vt.prototype.decompose=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty,o=Math.sqrt(t*t+e*e),s=(t/=o)*r+(e/=o)*n;r-=t*s,n-=e*s;var c=Math.sqrt(r*r+n*n);return s/=c,t*(n/=c)<e*(r/=c)&&(t=-t,e=-e,s=-s,o=-o),{scale:new Vt(o,0,0,c,0,0),translate:new Vt(1,0,0,1,i,a),rotate:new Vt(t,e,-e,t,0,0),skew:new Vt(1,0,s,1,0,0)}},Vt.prototype.toString=function(t){return this.join(" ")},Vt.prototype.inversed=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty,o=1/(t*n-e*r),s=n*o,c=-e*o,u=-r*o,h=t*o;return new Vt(s,c,u,h,-s*i-u*a,-c*i-h*a)},Vt.prototype.applyToPoint=function(t){var e=t.x*this.sx+t.y*this.shx+this.tx,r=t.x*this.shy+t.y*this.sy+this.ty;return new Cr(e,r)},Vt.prototype.applyToRectangle=function(t){var e=this.applyToPoint(t),r=this.applyToPoint(new Cr(t.x+t.w,t.y+t.h));return new jr(e.x,e.y,r.x-e.x,r.y-e.y)},Vt.prototype.clone=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty;return new Vt(t,e,r,n,i,a)},y.Matrix=Vt;var Gt=y.matrixMult=function(t,e){return e.multiply(t)},Yt=new Vt(1,0,0,1,0,0);y.unitMatrix=y.identityMatrix=Yt;var Jt=function(t,e){if(!Bt[t]){var r=(e instanceof B?"Sh":"P")+(Object.keys(Ot).length+1).toString(10);e.id=r,Bt[t]=r,Ot[r]=e,Tt.publish("addPattern",e)}};y.ShadingPattern=B,y.TilingPattern=M,y.addShadingPattern=function(t,e){return q("addShadingPattern()"),Jt(t,e),this},y.beginTilingPattern=function(t){q("beginTilingPattern()"),Br(t.boundingBox[0],t.boundingBox[1],t.boundingBox[2]-t.boundingBox[0],t.boundingBox[3]-t.boundingBox[1],t.matrix)},y.endTilingPattern=function(t,e){q("endTilingPattern()"),e.stream=ot[$].join("\n"),Jt(t,e),Tt.publish("endTilingPattern",e),Wt.pop().restore()};var Xt=y.__private__.newObject=function(){var t=Kt();return Zt(t,!0),t},Kt=y.__private__.newObjectDeferred=function(){return et++,rt[et]=function(){return it},et},Zt=function(t,e){return e="boolean"==typeof e&&e,rt[t]=it,e&<(t+" 0 obj"),t},$t=y.__private__.newAdditionalObject=function(){var t={objId:Kt(),content:""};return at.push(t),t},Qt=Kt(),te=Kt(),ee=y.__private__.decodeColorString=function(t){var e=t.split(" ");if(2!==e.length||"g"!==e[1]&&"G"!==e[1]){if(5===e.length&&("k"===e[4]||"K"===e[4])){e=[(1-e[0])*(1-e[3]),(1-e[1])*(1-e[3]),(1-e[2])*(1-e[3]),"r"]}}else{var r=parseFloat(e[0]);e=[r,r,r,"r"]}for(var n="#",i=0;i<3;i++)n+=("0"+Math.floor(255*parseFloat(e[i])).toString(16)).slice(-2);return n},re=y.__private__.encodeColorString=function(e){var r;"string"==typeof e&&(e={ch1:e});var n=e.ch1,i=e.ch2,a=e.ch3,o=e.ch4,s="draw"===e.pdfColorType?["G","RG","K"]:["g","rg","k"];if("string"==typeof n&&"#"!==n.charAt(0)){var c=new f(n);if(c.ok)n=c.toHex();else if(!/^\d*\.?\d*$/.test(n))throw new Error('Invalid color "'+n+'" passed to jsPDF.encodeColorString.')}if("string"==typeof n&&/^#[0-9A-Fa-f]{3}$/.test(n)&&(n="#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]),"string"==typeof n&&/^#[0-9A-Fa-f]{6}$/.test(n)){var u=parseInt(n.substr(1),16);n=u>>16&255,i=u>>8&255,a=255&u}if(void 0===i||void 0===o&&n===i&&i===a)if("string"==typeof n)r=n+" "+s[0];else switch(e.precision){case 2:r=R(n/255)+" "+s[0];break;case 3:default:r=T(n/255)+" "+s[0]}else if(void 0===o||"object"===t(o)){if(o&&!isNaN(o.a)&&0===o.a)return r=["1.","1.","1.",s[1]].join(" ");if("string"==typeof n)r=[n,i,a,s[1]].join(" ");else switch(e.precision){case 2:r=[R(n/255),R(i/255),R(a/255),s[1]].join(" ");break;default:case 3:r=[T(n/255),T(i/255),T(a/255),s[1]].join(" ")}}else if("string"==typeof n)r=[n,i,a,o,s[2]].join(" ");else switch(e.precision){case 2:r=[R(n),R(i),R(a),R(o),s[2]].join(" ");break;case 3:default:r=[T(n),T(i),T(a),T(o),s[2]].join(" ")}return r},ne=y.__private__.getFilters=function(){return u},ie=y.__private__.putStream=function(t){var e=(t=t||{}).data||"",r=t.filters||ne(),n=t.alreadyAppliedFilters||[],i=t.addLength1||!1,a=e.length,o=t.objectId,s=function(t){return t};if(null!==m&&void 0===o)throw new Error("ObjectId must be passed to putStream for file encryption");null!==m&&(s=Ye.encryptor(o,0));var c={};!0===r&&(r=["FlateEncode"]);var u=t.additionalKeyValues||[],h=(c=void 0!==E.API.processDataByFilters?E.API.processDataByFilters(e,r):{data:e,reverseChain:[]}).reverseChain+(Array.isArray(n)?n.join(" "):n.toString());if(0!==c.data.length&&(u.push({key:"Length",value:c.data.length}),!0===i&&u.push({key:"Length1",value:a})),0!=h.length)if(h.split("/").length-1==1)u.push({key:"Filter",value:h});else{u.push({key:"Filter",value:"["+h+"]"});for(var l=0;l<u.length;l+=1)if("DecodeParms"===u[l].key){for(var f=[],d=0;d<c.reverseChain.split("/").length-1;d+=1)f.push("null");f.push(u[l].value),u[l].value="["+f.join(" ")+"]"}}lt("<<");for(var p=0;p<u.length;p++)lt("/"+u[p].key+" "+u[p].value);lt(">>"),0!==c.data.length&&(lt("stream"),lt(s(c.data)),lt("endstream"))},ae=y.__private__.putPage=function(t){var e=t.number,r=t.data,n=t.objId,i=t.contentsObjId;Zt(n,!0),lt("<</Type /Page"),lt("/Parent "+t.rootDictionaryObjId+" 0 R"),lt("/Resources "+t.resourceDictionaryObjId+" 0 R"),lt("/MediaBox ["+parseFloat(O(t.mediaBox.bottomLeftX))+" "+parseFloat(O(t.mediaBox.bottomLeftY))+" "+O(t.mediaBox.topRightX)+" "+O(t.mediaBox.topRightY)+"]"),null!==t.cropBox&<("/CropBox ["+O(t.cropBox.bottomLeftX)+" "+O(t.cropBox.bottomLeftY)+" "+O(t.cropBox.topRightX)+" "+O(t.cropBox.topRightY)+"]"),null!==t.bleedBox&<("/BleedBox ["+O(t.bleedBox.bottomLeftX)+" "+O(t.bleedBox.bottomLeftY)+" "+O(t.bleedBox.topRightX)+" "+O(t.bleedBox.topRightY)+"]"),null!==t.trimBox&<("/TrimBox ["+O(t.trimBox.bottomLeftX)+" "+O(t.trimBox.bottomLeftY)+" "+O(t.trimBox.topRightX)+" "+O(t.trimBox.topRightY)+"]"),null!==t.artBox&<("/ArtBox ["+O(t.artBox.bottomLeftX)+" "+O(t.artBox.bottomLeftY)+" "+O(t.artBox.topRightX)+" "+O(t.artBox.topRightY)+"]"),"number"==typeof t.userUnit&&1!==t.userUnit&<("/UserUnit "+t.userUnit),Tt.publish("putPage",{objId:n,pageContext:Rt[e],pageNumber:e,page:r}),lt("/Contents "+i+" 0 R"),lt(">>"),lt("endobj");var a=r.join("\n");return S===x.ADVANCED&&(a+="\nQ"),Zt(i,!0),ie({data:a,filters:ne(),objectId:i}),lt("endobj"),n},oe=y.__private__.putPages=function(){var t,e,r=[];for(t=1;t<=Dt;t++)Rt[t].objId=Kt(),Rt[t].contentsObjId=Kt();for(t=1;t<=Dt;t++)r.push(ae({number:t,data:ot[t],objId:Rt[t].objId,contentsObjId:Rt[t].contentsObjId,mediaBox:Rt[t].mediaBox,cropBox:Rt[t].cropBox,bleedBox:Rt[t].bleedBox,trimBox:Rt[t].trimBox,artBox:Rt[t].artBox,userUnit:Rt[t].userUnit,rootDictionaryObjId:Qt,resourceDictionaryObjId:te}));Zt(Qt,!0),lt("<</Type /Pages");var n="/Kids [";for(e=0;e<Dt;e++)n+=r[e]+" 0 R ";lt(n+"]"),lt("/Count "+Dt),lt(">>"),lt("endobj"),Tt.publish("postPutPages")},se=function(t){Tt.publish("putFont",{font:t,out:lt,newObject:Xt,putStream:ie}),!0!==t.isAlreadyPutted&&(t.objectNumber=Xt(),lt("<<"),lt("/Type /Font"),lt("/BaseFont /"+F(t.postScriptName)),lt("/Subtype /Type1"),"string"==typeof t.encoding&<("/Encoding /"+t.encoding),lt("/FirstChar 32"),lt("/LastChar 255"),lt(">>"),lt("endobj"))},ce=function(){for(var t in Ft)Ft.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&&se(Ft[t])},ue=function(t){t.objectNumber=Xt();var e=[];e.push({key:"Type",value:"/XObject"}),e.push({key:"Subtype",value:"/Form"}),e.push({key:"BBox",value:"["+[O(t.x),O(t.y),O(t.x+t.width),O(t.y+t.height)].join(" ")+"]"}),e.push({key:"Matrix",value:"["+t.matrix.toString()+"]"});var r=t.pages[1].join("\n");ie({data:r,additionalKeyValues:e,objectId:t.objectNumber}),lt("endobj")},he=function(){for(var t in zt)zt.hasOwnProperty(t)&&ue(zt[t])},le=function(t,e){var r,n=[],i=1/(e-1);for(r=0;r<1;r+=i)n.push(r);if(n.push(1),0!=t[0].offset){var a={offset:0,color:t[0].color};t.unshift(a)}if(1!=t[t.length-1].offset){var o={offset:1,color:t[t.length-1].color};t.push(o)}for(var s="",c=0,u=0;u<n.length;u++){for(r=n[u];r>t[c+1].offset;)c++;var h=t[c].offset,l=(r-h)/(t[c+1].offset-h),f=t[c].color,d=t[c+1].color;s+=tt(Math.round((1-l)*f[0]+l*d[0]).toString(16))+tt(Math.round((1-l)*f[1]+l*d[1]).toString(16))+tt(Math.round((1-l)*f[2]+l*d[2]).toString(16))}return s.trim()},fe=function(t,e){e||(e=21);var r=Xt(),n=le(t.colors,e),i=[];i.push({key:"FunctionType",value:"0"}),i.push({key:"Domain",value:"[0.0 1.0]"}),i.push({key:"Size",value:"["+e+"]"}),i.push({key:"BitsPerSample",value:"8"}),i.push({key:"Range",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),i.push({key:"Decode",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),ie({data:n,additionalKeyValues:i,alreadyAppliedFilters:["/ASCIIHexDecode"],objectId:r}),lt("endobj"),t.objectNumber=Xt(),lt("<< /ShadingType "+t.type),lt("/ColorSpace /DeviceRGB");var a="/Coords ["+O(parseFloat(t.coords[0]))+" "+O(parseFloat(t.coords[1]))+" ";2===t.type?a+=O(parseFloat(t.coords[2]))+" "+O(parseFloat(t.coords[3])):a+=O(parseFloat(t.coords[2]))+" "+O(parseFloat(t.coords[3]))+" "+O(parseFloat(t.coords[4]))+" "+O(parseFloat(t.coords[5])),lt(a+="]"),t.matrix&<("/Matrix ["+t.matrix.toString()+"]"),lt("/Function "+r+" 0 R"),lt("/Extend [true true]"),lt(">>"),lt("endobj")},de=function(t,e){var r=Kt(),n=Xt();e.push({resourcesOid:r,objectOid:n}),t.objectNumber=n;var i=[];i.push({key:"Type",value:"/Pattern"}),i.push({key:"PatternType",value:"1"}),i.push({key:"PaintType",value:"1"}),i.push({key:"TilingType",value:"1"}),i.push({key:"BBox",value:"["+t.boundingBox.map(O).join(" ")+"]"}),i.push({key:"XStep",value:O(t.xStep)}),i.push({key:"YStep",value:O(t.yStep)}),i.push({key:"Resources",value:r+" 0 R"}),t.matrix&&i.push({key:"Matrix",value:"["+t.matrix.toString()+"]"}),ie({data:t.stream,additionalKeyValues:i,objectId:t.objectNumber}),lt("endobj")},pe=function(t){var e;for(e in Ot)Ot.hasOwnProperty(e)&&(Ot[e]instanceof B?fe(Ot[e]):Ot[e]instanceof M&&de(Ot[e],t))},ge=function(t){for(var e in t.objectNumber=Xt(),lt("<<"),t)switch(e){case"opacity":lt("/ca "+R(t[e]));break;case"stroke-opacity":lt("/CA "+R(t[e]))}lt(">>"),lt("endobj")},me=function(){var t;for(t in Mt)Mt.hasOwnProperty(t)&&ge(Mt[t])},ve=function(){for(var t in lt("/XObject <<"),zt)zt.hasOwnProperty(t)&&zt[t].objectNumber>=0&<("/"+t+" "+zt[t].objectNumber+" 0 R");Tt.publish("putXobjectDict"),lt(">>")},be=function(){Ye.oid=Xt(),lt("<<"),lt("/Filter /Standard"),lt("/V "+Ye.v),lt("/R "+Ye.r),lt("/U <"+Ye.toHexString(Ye.U)+">"),lt("/O <"+Ye.toHexString(Ye.O)+">"),lt("/P "+Ye.P),lt(">>"),lt("endobj")},ye=function(){for(var t in lt("/Font <<"),Ft)Ft.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&<("/"+t+" "+Ft[t].objectNumber+" 0 R");lt(">>")},we=function(){if(Object.keys(Ot).length>0){for(var t in lt("/Shading <<"),Ot)Ot.hasOwnProperty(t)&&Ot[t]instanceof B&&Ot[t].objectNumber>=0&<("/"+t+" "+Ot[t].objectNumber+" 0 R");Tt.publish("putShadingPatternDict"),lt(">>")}},Ne=function(t){if(Object.keys(Ot).length>0){for(var e in lt("/Pattern <<"),Ot)Ot.hasOwnProperty(e)&&Ot[e]instanceof y.TilingPattern&&Ot[e].objectNumber>=0&&Ot[e].objectNumber<t&<("/"+e+" "+Ot[e].objectNumber+" 0 R");Tt.publish("putTilingPatternDict"),lt(">>")}},Le=function(){if(Object.keys(Mt).length>0){var t;for(t in lt("/ExtGState <<"),Mt)Mt.hasOwnProperty(t)&&Mt[t].objectNumber>=0&<("/"+t+" "+Mt[t].objectNumber+" 0 R");Tt.publish("putGStateDict"),lt(">>")}},Ae=function(t){Zt(t.resourcesOid,!0),lt("<<"),lt("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),ye(),we(),Ne(t.objectOid),Le(),ve(),lt(">>"),lt("endobj")},xe=function(){var t=[];ce(),me(),he(),pe(t),Tt.publish("putResources"),t.forEach(Ae),Ae({resourcesOid:te,objectOid:Number.MAX_SAFE_INTEGER}),Tt.publish("postPutResources")},Se=function(){Tt.publish("putAdditionalObjects");for(var t=0;t<at.length;t++){var e=at[t];Zt(e.objId,!0),lt(e.content),lt("endobj")}Tt.publish("postPutAdditionalObjects")},_e=function(t){Ct[t.fontName]=Ct[t.fontName]||{},Ct[t.fontName][t.fontStyle]=t.id},Pe=function(t,e,r,n,i){var a={id:"F"+(Object.keys(Ft).length+1).toString(10),postScriptName:t,fontName:e,fontStyle:r,encoding:n,isStandardFont:i||!1,metadata:{}};return Tt.publish("addFont",{font:a,instance:this}),Ft[a.id]=a,_e(a),a.id},ke=function(t){for(var e=0,r=pt.length;e<r;e++){var n=Pe.call(this,t[e][0],t[e][1],t[e][2],pt[e][3],!0);!1===v&&(b[n]=!0);var i=t[e][0].split("-");_e({id:n,fontName:i[0],fontStyle:i[1]||""})}Tt.publish("addFonts",{fonts:Ft,dictionary:Ct})},Ie=function(t){return t.foo=function(){try{return t.apply(this,arguments)}catch(t){var e=t.stack||"";~e.indexOf(" at ")&&(e=e.split(" at ")[1]);var r="Error in function "+e.split("\n")[0].split("<")[0]+": "+t.message;if(!n.console)throw new Error(r);n.console.error(r,t),n.alert&&alert(r)}},t.foo.bar=t,t.foo},Fe=function(t,e){var r,n,i,a,o,s,c,u,h;if(i=(e=e||{}).sourceEncoding||"Unicode",o=e.outputEncoding,(e.autoencode||o)&&Ft[St].metadata&&Ft[St].metadata[i]&&Ft[St].metadata[i].encoding&&(a=Ft[St].metadata[i].encoding,!o&&Ft[St].encoding&&(o=Ft[St].encoding),!o&&a.codePages&&(o=a.codePages[0]),"string"==typeof o&&(o=a[o]),o)){for(c=!1,s=[],r=0,n=t.length;r<n;r++)(u=o[t.charCodeAt(r)])?s.push(String.fromCharCode(u)):s.push(t[r]),s[r].charCodeAt(0)>>8&&(c=!0);t=s.join("")}for(r=t.length;void 0===c&&0!==r;)t.charCodeAt(r-1)>>8&&(c=!0),r--;if(!c)return t;for(s=e.noBOM?[]:[254,255],r=0,n=t.length;r<n;r++){if((h=(u=t.charCodeAt(r))>>8)>>8)throw new Error("Character at position "+r+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");s.push(h),s.push(u-(h<<8))}return String.fromCharCode.apply(void 0,s)},Ce=y.__private__.pdfEscape=y.pdfEscape=function(t,e){return Fe(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},je=y.__private__.beginPage=function(t){ot[++Dt]=[],Rt[Dt]={objId:0,contentsObjId:0,userUnit:Number(d),artBox:null,bleedBox:null,cropBox:null,trimBox:null,mediaBox:{bottomLeftX:0,bottomLeftY:0,topRightX:Number(t[0]),topRightY:Number(t[1])}},Me(Dt),ht(ot[$])},Oe=function(t,e){var r,n,o;switch(i=e||i,"string"==typeof t&&(r=A(t.toLowerCase()),Array.isArray(r)&&(n=r[0],o=r[1])),Array.isArray(t)&&(n=t[0]*_t,o=t[1]*_t),isNaN(n)&&(n=s[0],o=s[1]),(n>14400||o>14400)&&(a.warn("A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"),n=Math.min(14400,n),o=Math.min(14400,o)),s=[n,o],i.substr(0,1)){case"l":o>n&&(s=[o,n]);break;case"p":n>o&&(s=[o,n])}je(s),pr(fr),lt(Lr),0!==kr&<(kr+" J"),0!==Ir&<(Ir+" j"),Tt.publish("addPage",{pageNumber:Dt})},Be=function(t){t>0&&t<=Dt&&(ot.splice(t,1),Rt.splice(t,1),Dt--,$>Dt&&($=Dt),this.setPage($))},Me=function(t){t>0&&t<=Dt&&($=t)},Ee=y.__private__.getNumberOfPages=y.getNumberOfPages=function(){return ot.length-1},qe=function(t,e,r){var n,i=void 0;return r=r||{},t=void 0!==t?t:Ft[St].fontName,e=void 0!==e?e:Ft[St].fontStyle,n=t.toLowerCase(),void 0!==Ct[n]&&void 0!==Ct[n][e]?i=Ct[n][e]:void 0!==Ct[t]&&void 0!==Ct[t][e]?i=Ct[t][e]:!1===r.disableWarning&&a.warn("Unable to look up font label for font '"+t+"', '"+e+"'. Refer to getFontList() for available fonts."),i||r.noFallback||null==(i=Ct.times[e])&&(i=Ct.times.normal),i},De=y.__private__.putInfo=function(){var t=Xt(),e=function(t){return t};for(var r in null!==m&&(e=Ye.encryptor(t,0)),lt("<<"),lt("/Producer ("+Ce(e("jsPDF "+E.version))+")"),xt)xt.hasOwnProperty(r)&&xt[r]&<("/"+r.substr(0,1).toUpperCase()+r.substr(1)+" ("+Ce(e(xt[r]))+")");lt("/CreationDate ("+Ce(e(W))+")"),lt(">>"),lt("endobj")},Re=y.__private__.putCatalog=function(t){var e=(t=t||{}).rootDictionaryObjId||Qt;switch(Xt(),lt("<<"),lt("/Type /Catalog"),lt("/Pages "+e+" 0 R"),mt||(mt="fullwidth"),mt){case"fullwidth":lt("/OpenAction [3 0 R /FitH null]");break;case"fullheight":lt("/OpenAction [3 0 R /FitV null]");break;case"fullpage":lt("/OpenAction [3 0 R /Fit]");break;case"original":lt("/OpenAction [3 0 R /XYZ null null 1]");break;default:var r=""+mt;"%"===r.substr(r.length-1)&&(mt=parseInt(mt)/100),"number"==typeof mt&<("/OpenAction [3 0 R /XYZ null null "+R(mt)+"]")}switch(Nt||(Nt="continuous"),Nt){case"continuous":lt("/PageLayout /OneColumn");break;case"single":lt("/PageLayout /SinglePage");break;case"two":case"twoleft":lt("/PageLayout /TwoColumnLeft");break;case"tworight":lt("/PageLayout /TwoColumnRight")}yt&<("/PageMode /"+yt),Tt.publish("putCatalog"),lt(">>"),lt("endobj")},Te=y.__private__.putTrailer=function(){lt("trailer"),lt("<<"),lt("/Size "+(et+1)),lt("/Root "+et+" 0 R"),lt("/Info "+(et-1)+" 0 R"),null!==m&<("/Encrypt "+Ye.oid+" 0 R"),lt("/ID [ <"+V+"> <"+V+"> ]"),lt(">>")},Ue=y.__private__.putHeader=function(){lt("%PDF-"+w),lt("%ºß¬à")},ze=y.__private__.putXRef=function(){var t="0000000000";lt("xref"),lt("0 "+(et+1)),lt("0000000000 65535 f ");for(var e=1;e<=et;e++){"function"==typeof rt[e]?lt((t+rt[e]()).slice(-10)+" 00000 n "):void 0!==rt[e]?lt((t+rt[e]).slice(-10)+" 00000 n "):lt("0000000000 00000 n ")}},He=y.__private__.buildDocument=function(){ut(),ht(nt),Tt.publish("buildDocument"),Ue(),oe(),Se(),xe(),null!==m&&be(),De(),Re();var t=it;return ze(),Te(),lt("startxref"),lt(""+t),lt("%%EOF"),ht(ot[$]),nt.join("\n")},We=y.__private__.getBlob=function(t){return new Blob([dt(t)],{type:"application/pdf"})},Ve=y.output=y.__private__.output=Ie((function(t,e){switch("string"==typeof(e=e||{})?e={filename:e}:e.filename=e.filename||"generated.pdf",t){case void 0:return He();case"save":y.save(e.filename);break;case"arraybuffer":return dt(He());case"blob":return We(He());case"bloburi":case"bloburl":if(void 0!==n.URL&&"function"==typeof n.URL.createObjectURL)return n.URL&&n.URL.createObjectURL(We(He()))||void 0;a.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");break;case"datauristring":case"dataurlstring":var r="",i=He();try{r=h(i)}catch(t){r=h(unescape(encodeURIComponent(i)))}return"data:application/pdf;filename="+e.filename+";base64,"+r;case"pdfobjectnewwindow":if("[object Window]"===Object.prototype.toString.call(n)){var o="https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js",s=' crossorigin="anonymous"';e.pdfObjectUrl&&(o=e.pdfObjectUrl,s="");var c='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><script src="'+o+'"'+s+'><\/script><script >PDFObject.embed("'+this.output("dataurlstring")+'", '+JSON.stringify(e)+");<\/script></body></html>",u=n.open();return null!==u&&u.document.write(c),u}throw new Error("The option pdfobjectnewwindow just works in a browser-environment.");case"pdfjsnewwindow":if("[object Window]"===Object.prototype.toString.call(n)){var l='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><iframe id="pdfViewer" src="'+(e.pdfJsUrl||"examples/PDF.js/web/viewer.html")+"?file=&downloadName="+e.filename+'" width="500px" height="400px" /></body></html>',f=n.open();if(null!==f){f.document.write(l);var d=this;f.document.documentElement.querySelector("#pdfViewer").onload=function(){f.document.title=e.filename,f.document.documentElement.querySelector("#pdfViewer").contentWindow.PDFViewerApplication.open(d.output("bloburl"))}}return f}throw new Error("The option pdfjsnewwindow just works in a browser-environment.");case"dataurlnewwindow":if("[object Window]"!==Object.prototype.toString.call(n))throw new Error("The option dataurlnewwindow just works in a browser-environment.");var p='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><iframe src="'+this.output("datauristring",e)+'"></iframe></body></html>',g=n.open();if(null!==g&&(g.document.write(p),g.document.title=e.filename),g||"undefined"==typeof safari)return g;break;case"datauri":case"dataurl":return n.document.location.href=this.output("datauristring",e);default:return null}})),Ge=function(t){return!0===Array.isArray(Ut)&&Ut.indexOf(t)>-1};switch(o){case"pt":_t=1;break;case"mm":_t=72/25.4;break;case"cm":_t=72/2.54;break;case"in":_t=72;break;case"px":_t=1==Ge("px_scaling")?.75:96/72;break;case"pc":case"em":_t=12;break;case"ex":_t=6;break;default:if("number"!=typeof o)throw new Error("Invalid unit: "+o);_t=o}var Ye=null;K(),Y();var Je=function(t){return null!==m?Ye.encryptor(t,0):function(t){return t}},Xe=y.__private__.getPageInfo=y.getPageInfo=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfo");return{objId:Rt[t].objId,pageNumber:t,pageContext:Rt[t]}},Ke=y.__private__.getPageInfoByObjId=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId");for(var e in Rt)if(Rt[e].objId===t)break;return Xe(e)},Ze=y.__private__.getCurrentPageInfo=y.getCurrentPageInfo=function(){return{objId:Rt[$].objId,pageNumber:$,pageContext:Rt[$]}};y.addPage=function(){return Oe.apply(this,arguments),this},y.setPage=function(){return Me.apply(this,arguments),ht.call(this,ot[$]),this},y.insertPage=function(t){return this.addPage(),this.movePage($,t),this},y.movePage=function(t,e){var r,n;if(t>e){r=ot[t],n=Rt[t];for(var i=t;i>e;i--)ot[i]=ot[i-1],Rt[i]=Rt[i-1];ot[e]=r,Rt[e]=n,this.setPage(e)}else if(t<e){r=ot[t],n=Rt[t];for(var a=t;a<e;a++)ot[a]=ot[a+1],Rt[a]=Rt[a+1];ot[e]=r,Rt[e]=n,this.setPage(e)}return this},y.deletePage=function(){return Be.apply(this,arguments),this},y.__private__.text=y.text=function(e,r,n,i,a){var o,s,c,u,h,l,f,d,p,g=(i=i||{}).scope||this;if("number"==typeof e&&"number"==typeof r&&("string"==typeof n||Array.isArray(n))){var m=n;n=r,r=e,e=m}if(arguments[3]instanceof Vt==!1?(c=arguments[4],u=arguments[5],"object"===t(f=arguments[3])&&null!==f||("string"==typeof c&&(u=c,c=null),"string"==typeof f&&(u=f,f=null),"number"==typeof f&&(c=f,f=null),i={flags:f,angle:c,align:u})):(q("The transform parameter of text() with a Matrix value"),p=a),isNaN(r)||isNaN(n)||null==e)throw new Error("Invalid arguments passed to jsPDF.text");if(0===e.length)return g;var v="",y=!1,w="number"==typeof i.lineHeightFactor?i.lineHeightFactor:lr,N=g.internal.scaleFactor;function L(t){return t=t.split("\t").join(Array(i.TabLen||9).join(" ")),Ce(t,f)}function A(t){for(var e,r=t.concat(),n=[],i=r.length;i--;)"string"==typeof(e=r.shift())?n.push(e):Array.isArray(t)&&(1===e.length||void 0===e[1]&&void 0===e[2])?n.push(e[0]):n.push([e[0],e[1],e[2]]);return n}function _(t,e){var r;if("string"==typeof t)r=e(t)[0];else if(Array.isArray(t)){for(var n,i,a=t.concat(),o=[],s=a.length;s--;)"string"==typeof(n=a.shift())?o.push(e(n)[0]):Array.isArray(n)&&"string"==typeof n[0]&&(i=e(n[0],n[1],n[2]),o.push([i[0],i[1],i[2]]));r=o}return r}var P=!1,k=!0;if("string"==typeof e)P=!0;else if(Array.isArray(e)){var I=e.concat();s=[];for(var F,C=I.length;C--;)("string"!=typeof(F=I.shift())||Array.isArray(F)&&"string"!=typeof F[0])&&(k=!1);P=k}if(!1===P)throw new Error('Type of text must be string or Array. "'+e+'" is not recognized.');"string"==typeof e&&(e=e.match(/[\r?\n]/)?e.split(/\r\n|\r|\n/g):[e]);var j=gt/g.internal.scaleFactor,B=j*(w-1);switch(i.baseline){case"bottom":n-=B;break;case"top":n+=j-B;break;case"hanging":n+=j-2*B;break;case"middle":n+=j/2-B}if((l=i.maxWidth||0)>0&&("string"==typeof e?e=g.splitTextToSize(e,l):"[object Array]"===Object.prototype.toString.call(e)&&(e=e.reduce((function(t,e){return t.concat(g.splitTextToSize(e,l))}),[]))),o={text:e,x:r,y:n,options:i,mutex:{pdfEscape:Ce,activeFontKey:St,fonts:Ft,activeFontSize:gt}},Tt.publish("preProcessText",o),e=o.text,c=(i=o.options).angle,p instanceof Vt==!1&&c&&"number"==typeof c){c*=Math.PI/180,0===i.rotationDirection&&(c=-c),S===x.ADVANCED&&(c=-c);var M=Math.cos(c),E=Math.sin(c);p=new Vt(M,E,-E,M,0,0)}else c&&c instanceof Vt&&(p=c);S!==x.ADVANCED||p||(p=Yt),void 0!==(h=i.charSpace||_r)&&(v+=O(U(h))+" Tc\n",this.setCharSpace(this.getCharSpace()||0)),void 0!==(d=i.horizontalScale)&&(v+=O(100*d)+" Tz\n");i.lang;var D=-1,R=void 0!==i.renderingMode?i.renderingMode:i.stroke,T=g.internal.getCurrentPageInfo().pageContext;switch(R){case 0:case!1:case"fill":D=0;break;case 1:case!0:case"stroke":D=1;break;case 2:case"fillThenStroke":D=2;break;case 3:case"invisible":D=3;break;case 4:case"fillAndAddForClipping":D=4;break;case 5:case"strokeAndAddPathForClipping":D=5;break;case 6:case"fillThenStrokeAndAddToPathForClipping":D=6;break;case 7:case"addToPathForClipping":D=7}var z=void 0!==T.usedRenderingMode?T.usedRenderingMode:-1;-1!==D?v+=D+" Tr\n":-1!==z&&(v+="0 Tr\n"),-1!==D&&(T.usedRenderingMode=D),u=i.align||"left";var H,W=gt*w,V=g.internal.pageSize.getWidth(),G=Ft[St];h=i.charSpace||_r,l=i.maxWidth||0,f=Object.assign({autoencode:!0,noBOM:!0},i.flags);var Y=[],J=function(t){return g.getStringUnitWidth(t,{font:G,charSpace:h,fontSize:gt,doKerning:!1})*gt/N};if("[object Array]"===Object.prototype.toString.call(e)){var X;s=A(e),"left"!==u&&(H=s.map(J));var K,Z=0;if("right"===u){r-=H[0],e=[],C=s.length;for(var $=0;$<C;$++)0===$?(K=br(r),X=yr(n)):(K=U(Z-H[$]),X=-W),e.push([s[$],K,X]),Z=H[$]}else if("center"===u){r-=H[0]/2,e=[],C=s.length;for(var Q=0;Q<C;Q++)0===Q?(K=br(r),X=yr(n)):(K=U((Z-H[Q])/2),X=-W),e.push([s[Q],K,X]),Z=H[Q]}else if("left"===u){e=[],C=s.length;for(var tt=0;tt<C;tt++)e.push(s[tt])}else if("justify"===u&&"Identity-H"===G.encoding){e=[],C=s.length,l=0!==l?l:V;for(var et=0,rt=0;rt<C;rt++)if(X=0===rt?yr(n):-W,K=0===rt?br(r):et,rt<C-1){var nt=U((l-H[rt])/(s[rt].split(" ").length-1)),it=s[rt].split(" ");e.push([it[0]+" ",K,X]),et=0;for(var at=1;at<it.length;at++){var ot=(J(it[at-1]+" "+it[at])-J(it[at]))*N+nt;at==it.length-1?e.push([it[at],ot,0]):e.push([it[at]+" ",ot,0]),et-=ot}}else e.push([s[rt],K,X]);e.push(["",et,0])}else{if("justify"!==u)throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".');e=[],C=s.length,l=0!==l?l:V;for(rt=0;rt<C;rt++)X=0===rt?yr(n):-W,K=0===rt?br(r):0,rt<C-1?Y.push(O(U((l-H[rt])/(s[rt].split(" ").length-1)))):Y.push(0),e.push([s[rt],K,X])}}var st="boolean"==typeof i.R2L?i.R2L:bt;!0===st&&(e=_(e,(function(t,e,r){return[t.split("").reverse().join(""),e,r]}))),o={text:e,x:r,y:n,options:i,mutex:{pdfEscape:Ce,activeFontKey:St,fonts:Ft,activeFontSize:gt}},Tt.publish("postProcessText",o),e=o.text,y=o.mutex.isHex||!1;var ct=Ft[St].encoding;"WinAnsiEncoding"!==ct&&"StandardEncoding"!==ct||(e=_(e,(function(t,e,r){return[L(t),e,r]}))),s=A(e),e=[];for(var ut,ht,ft,dt=0,pt=1,mt=Array.isArray(s[0])?pt:dt,vt="",yt=function(t,e,r){var n="";return r instanceof Vt?(r="number"==typeof i.angle?Gt(r,new Vt(1,0,0,1,t,e)):Gt(new Vt(1,0,0,1,t,e),r),S===x.ADVANCED&&(r=Gt(new Vt(1,0,0,-1,0,0),r)),n=r.join(" ")+" Tm\n"):n=O(t)+" "+O(e)+" Td\n",n},wt=0;wt<s.length;wt++){switch(vt="",mt){case pt:ft=(y?"<":"(")+s[wt][0]+(y?">":")"),ut=parseFloat(s[wt][1]),ht=parseFloat(s[wt][2]);break;case dt:ft=(y?"<":"(")+s[wt]+(y?">":")"),ut=br(r),ht=yr(n)}void 0!==Y&&void 0!==Y[wt]&&(vt=Y[wt]+" Tw\n"),0===wt?e.push(vt+yt(ut,ht,p)+ft):mt===dt?e.push(vt+ft):mt===pt&&e.push(vt+yt(ut,ht,p)+ft)}e=mt===dt?e.join(" Tj\nT* "):e.join(" Tj\n"),e+=" Tj\n";var Nt="BT\n/";return Nt+=St+" "+gt+" Tf\n",Nt+=O(gt*w)+" TL\n",Nt+=xr+"\n",Nt+=v,Nt+=e,lt(Nt+="ET"),b[St]=!0,g};var $e=y.__private__.clip=y.clip=function(t){return lt("evenodd"===t?"W*":"W"),this};y.clipEvenOdd=function(){return $e("evenodd")},y.__private__.discardPath=y.discardPath=function(){return lt("n"),this};var Qe=y.__private__.isValidStyle=function(t){var e=!1;return-1!==[void 0,null,"S","D","F","DF","FD","f","f*","B","B*","n"].indexOf(t)&&(e=!0),e};y.__private__.setDefaultPathOperation=y.setDefaultPathOperation=function(t){return Qe(t)&&(g=t),this};var tr=y.__private__.getStyle=y.getStyle=function(t){var e=g;switch(t){case"D":case"S":e="S";break;case"F":e="f";break;case"FD":case"DF":e="B";break;case"f":case"f*":case"B":case"B*":e=t}return e},er=y.close=function(){return lt("h"),this};y.stroke=function(){return lt("S"),this},y.fill=function(t){return rr("f",t),this},y.fillEvenOdd=function(t){return rr("f*",t),this},y.fillStroke=function(t){return rr("B",t),this},y.fillStrokeEvenOdd=function(t){return rr("B*",t),this};var rr=function(e,r){"object"===t(r)?ar(r,e):lt(e)},nr=function(t){null===t||S===x.ADVANCED&&void 0===t||(t=tr(t),lt(t))};function ir(t,e,r,n,i){var a=new M(e||this.boundingBox,r||this.xStep,n||this.yStep,this.gState,i||this.matrix);a.stream=this.stream;var o=t+"$$"+this.cloneIndex+++"$$";return Jt(o,a),a}var ar=function(t,e){var r=Bt[t.key],n=Ot[r];if(n instanceof B)lt("q"),lt(or(e)),n.gState&&y.setGState(n.gState),lt(t.matrix.toString()+" cm"),lt("/"+r+" sh"),lt("Q");else if(n instanceof M){var i=new Vt(1,0,0,-1,0,Rr());t.matrix&&(i=i.multiply(t.matrix||Yt),r=ir.call(n,t.key,t.boundingBox,t.xStep,t.yStep,i).id),lt("q"),lt("/Pattern cs"),lt("/"+r+" scn"),n.gState&&y.setGState(n.gState),lt(e),lt("Q")}},or=function(t){switch(t){case"f":case"F":return"W n";case"f*":return"W* n";case"B":return"W S";case"B*":return"W* S";case"S":return"W S";case"n":return"W n"}},sr=y.moveTo=function(t,e){return lt(O(U(t))+" "+O(H(e))+" m"),this},cr=y.lineTo=function(t,e){return lt(O(U(t))+" "+O(H(e))+" l"),this},ur=y.curveTo=function(t,e,r,n,i,a){return lt([O(U(t)),O(H(e)),O(U(r)),O(H(n)),O(U(i)),O(H(a)),"c"].join(" ")),this};y.__private__.line=y.line=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.line");return S===x.COMPAT?this.lines([[r-t,n-e]],t,e,[1,1],i||"S"):this.lines([[r-t,n-e]],t,e,[1,1]).stroke()},y.__private__.lines=y.lines=function(t,e,r,n,i,a){var o,s,c,u,h,l,f,d,p,g,m,v;if("number"==typeof t&&(v=r,r=e,e=t,t=v),n=n||[1,1],a=a||!1,isNaN(e)||isNaN(r)||!Array.isArray(t)||!Array.isArray(n)||!Qe(i)||"boolean"!=typeof a)throw new Error("Invalid arguments passed to jsPDF.lines");for(sr(e,r),o=n[0],s=n[1],u=t.length,g=e,m=r,c=0;c<u;c++)2===(h=t[c]).length?(g=h[0]*o+g,m=h[1]*s+m,cr(g,m)):(l=h[0]*o+g,f=h[1]*s+m,d=h[2]*o+g,p=h[3]*s+m,g=h[4]*o+g,m=h[5]*s+m,ur(l,f,d,p,g,m));return a&&er(),nr(i),this},y.path=function(t){for(var e=0;e<t.length;e++){var r=t[e],n=r.c;switch(r.op){case"m":sr(n[0],n[1]);break;case"l":cr(n[0],n[1]);break;case"c":ur.apply(this,n);break;case"h":er()}}return this},y.__private__.rect=y.rect=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.rect");return S===x.COMPAT&&(n=-n),lt([O(U(t)),O(H(e)),O(U(r)),O(U(n)),"re"].join(" ")),nr(i),this},y.__private__.triangle=y.triangle=function(t,e,r,n,i,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i)||isNaN(a)||!Qe(o))throw new Error("Invalid arguments passed to jsPDF.triangle");return this.lines([[r-t,n-e],[i-r,a-n],[t-i,e-a]],t,e,[1,1],o,!0),this},y.__private__.roundedRect=y.roundedRect=function(t,e,r,n,i,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i)||isNaN(a)||!Qe(o))throw new Error("Invalid arguments passed to jsPDF.roundedRect");var s=4/3*(Math.SQRT2-1);return i=Math.min(i,.5*r),a=Math.min(a,.5*n),this.lines([[r-2*i,0],[i*s,0,i,a-a*s,i,a],[0,n-2*a],[0,a*s,-i*s,a,-i,a],[2*i-r,0],[-i*s,0,-i,-a*s,-i,-a],[0,2*a-n],[0,-a*s,i*s,-a,i,-a]],t+i,e,[1,1],o,!0),this},y.__private__.ellipse=y.ellipse=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.ellipse");var a=4/3*(Math.SQRT2-1)*r,o=4/3*(Math.SQRT2-1)*n;return sr(t+r,e),ur(t+r,e-o,t+a,e-n,t,e-n),ur(t-a,e-n,t-r,e-o,t-r,e),ur(t-r,e+o,t-a,e+n,t,e+n),ur(t+a,e+n,t+r,e+o,t+r,e),nr(i),this},y.__private__.circle=y.circle=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||!Qe(n))throw new Error("Invalid arguments passed to jsPDF.circle");return this.ellipse(t,e,r,r,n)},y.setFont=function(t,e,r){return r&&(e=k(e,r)),St=qe(t,e,{disableWarning:!1}),this};var hr=y.__private__.getFont=y.getFont=function(){return Ft[qe.apply(y,arguments)]};y.__private__.getFontList=y.getFontList=function(){var t,e,r={};for(t in Ct)if(Ct.hasOwnProperty(t))for(e in r[t]=[],Ct[t])Ct[t].hasOwnProperty(e)&&r[t].push(e);return r},y.addFont=function(t,e,r,n,i){var a=["StandardEncoding","MacRomanEncoding","Identity-H","WinAnsiEncoding"];return arguments[3]&&-1!==a.indexOf(arguments[3])?i=arguments[3]:arguments[3]&&-1==a.indexOf(arguments[3])&&(r=k(r,n)),i=i||"Identity-H",Pe.call(this,t,e,r,i)};var lr,fr=e.lineWidth||.200025,dr=y.__private__.getLineWidth=y.getLineWidth=function(){return fr},pr=y.__private__.setLineWidth=y.setLineWidth=function(t){return fr=t,lt(O(U(t))+" w"),this};y.__private__.setLineDash=E.API.setLineDash=E.API.setLineDashPattern=function(t,e){if(t=t||[],e=e||0,isNaN(e)||!Array.isArray(t))throw new Error("Invalid arguments passed to jsPDF.setLineDash");return t=t.map((function(t){return O(U(t))})).join(" "),e=O(U(e)),lt("["+t+"] "+e+" d"),this};var gr=y.__private__.getLineHeight=y.getLineHeight=function(){return gt*lr};y.__private__.getLineHeight=y.getLineHeight=function(){return gt*lr};var mr=y.__private__.setLineHeightFactor=y.setLineHeightFactor=function(t){return"number"==typeof(t=t||1.15)&&(lr=t),this},vr=y.__private__.getLineHeightFactor=y.getLineHeightFactor=function(){return lr};mr(e.lineHeight);var br=y.__private__.getHorizontalCoordinate=function(t){return U(t)},yr=y.__private__.getVerticalCoordinate=function(t){return S===x.ADVANCED?t:Rt[$].mediaBox.topRightY-Rt[$].mediaBox.bottomLeftY-U(t)},wr=y.__private__.getHorizontalCoordinateString=y.getHorizontalCoordinateString=function(t){return O(br(t))},Nr=y.__private__.getVerticalCoordinateString=y.getVerticalCoordinateString=function(t){return O(yr(t))},Lr=e.strokeColor||"0 G";y.__private__.getStrokeColor=y.getDrawColor=function(){return ee(Lr)},y.__private__.setStrokeColor=y.setDrawColor=function(t,e,r,n){return Lr=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"draw",precision:2}),lt(Lr),this};var Ar=e.fillColor||"0 g";y.__private__.getFillColor=y.getFillColor=function(){return ee(Ar)},y.__private__.setFillColor=y.setFillColor=function(t,e,r,n){return Ar=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"fill",precision:2}),lt(Ar),this};var xr=e.textColor||"0 g",Sr=y.__private__.getTextColor=y.getTextColor=function(){return ee(xr)};y.__private__.setTextColor=y.setTextColor=function(t,e,r,n){return xr=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"text",precision:3}),this};var _r=e.charSpace,Pr=y.__private__.getCharSpace=y.getCharSpace=function(){return parseFloat(_r||0)};y.__private__.setCharSpace=y.setCharSpace=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.setCharSpace");return _r=t,this};var kr=0;y.CapJoinStyles={0:0,butt:0,but:0,miter:0,1:1,round:1,rounded:1,circle:1,2:2,projecting:2,project:2,square:2,bevel:2},y.__private__.setLineCap=y.setLineCap=function(t){var e=y.CapJoinStyles[t];if(void 0===e)throw new Error("Line cap style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return kr=e,lt(e+" J"),this};var Ir=0;y.__private__.setLineJoin=y.setLineJoin=function(t){var e=y.CapJoinStyles[t];if(void 0===e)throw new Error("Line join style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return Ir=e,lt(e+" j"),this},y.__private__.setLineMiterLimit=y.__private__.setMiterLimit=y.setLineMiterLimit=y.setMiterLimit=function(t){if(t=t||0,isNaN(t))throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit");return lt(O(U(t))+" M"),this},y.GState=j,y.setGState=function(t){(t="string"==typeof t?Mt[Et[t]]:Fr(null,t)).equals(qt)||(lt("/"+t.id+" gs"),qt=t)};var Fr=function(t,e){if(!t||!Et[t]){var r=!1;for(var n in Mt)if(Mt.hasOwnProperty(n)&&Mt[n].equals(e)){r=!0;break}if(r)e=Mt[n];else{var i="GS"+(Object.keys(Mt).length+1).toString(10);Mt[i]=e,e.id=i}return t&&(Et[t]=e.id),Tt.publish("addGState",e),e}};y.addGState=function(t,e){return Fr(t,e),this},y.saveGraphicsState=function(){return lt("q"),jt.push({key:St,size:gt,color:xr}),this},y.restoreGraphicsState=function(){lt("Q");var t=jt.pop();return St=t.key,gt=t.size,xr=t.color,qt=null,this},y.setCurrentTransformationMatrix=function(t){return lt(t.toString()+" cm"),this},y.comment=function(t){return lt("#"+t),this};var Cr=function(t,e){var r=t||0;Object.defineProperty(this,"x",{enumerable:!0,get:function(){return r},set:function(t){isNaN(t)||(r=parseFloat(t))}});var n=e||0;Object.defineProperty(this,"y",{enumerable:!0,get:function(){return n},set:function(t){isNaN(t)||(n=parseFloat(t))}});var i="pt";return Object.defineProperty(this,"type",{enumerable:!0,get:function(){return i},set:function(t){i=t.toString()}}),this},jr=function(t,e,r,n){Cr.call(this,t,e),this.type="rect";var i=r||0;Object.defineProperty(this,"w",{enumerable:!0,get:function(){return i},set:function(t){isNaN(t)||(i=parseFloat(t))}});var a=n||0;return Object.defineProperty(this,"h",{enumerable:!0,get:function(){return a},set:function(t){isNaN(t)||(a=parseFloat(t))}}),this},Or=function(){this.page=Dt,this.currentPage=$,this.pages=ot.slice(0),this.pagesContext=Rt.slice(0),this.x=Pt,this.y=kt,this.matrix=It,this.width=qr($),this.height=Rr($),this.outputDestination=ct,this.id="",this.objectNumber=-1};Or.prototype.restore=function(){Dt=this.page,$=this.currentPage,Rt=this.pagesContext,ot=this.pages,Pt=this.x,kt=this.y,It=this.matrix,Dr($,this.width),Tr($,this.height),ct=this.outputDestination};var Br=function(t,e,r,n,i){Wt.push(new Or),Dt=$=0,ot=[],Pt=t,kt=e,It=i,je([r,n])},Mr=function(t){if(Ht[t])Wt.pop().restore();else{var e=new Or,r="Xo"+(Object.keys(zt).length+1).toString(10);e.id=r,Ht[t]=r,zt[r]=e,Tt.publish("addFormObject",e),Wt.pop().restore()}};for(var Er in y.beginFormObject=function(t,e,r,n,i){return Br(t,e,r,n,i),this},y.endFormObject=function(t){return Mr(t),this},y.doFormObject=function(t,e){var r=zt[Ht[t]];return lt("q"),lt(e.toString()+" cm"),lt("/"+r.id+" Do"),lt("Q"),this},y.getFormObject=function(t){var e=zt[Ht[t]];return{x:e.x,y:e.y,width:e.width,height:e.height,matrix:e.matrix}},y.save=function(t,e){return t=t||"generated.pdf",(e=e||{}).returnPromise=e.returnPromise||!1,!1===e.returnPromise?(l(We(He()),t),"function"==typeof l.unload&&n.setTimeout&&setTimeout(l.unload,911),this):new Promise((function(e,r){try{var i=l(We(He()),t);"function"==typeof l.unload&&n.setTimeout&&setTimeout(l.unload,911),e(i)}catch(t){r(t.message)}}))},E.API)E.API.hasOwnProperty(Er)&&("events"===Er&&E.API.events.length?function(t,e){var r,n,i;for(i=e.length-1;-1!==i;i--)r=e[i][0],n=e[i][1],t.subscribe.apply(t,[r].concat("function"==typeof n?[n]:n))}(Tt,E.API.events):y[Er]=E.API[Er]);var qr=y.getPageWidth=function(t){return(Rt[t=t||$].mediaBox.topRightX-Rt[t].mediaBox.bottomLeftX)/_t},Dr=y.setPageWidth=function(t,e){Rt[t].mediaBox.topRightX=e*_t+Rt[t].mediaBox.bottomLeftX},Rr=y.getPageHeight=function(t){return(Rt[t=t||$].mediaBox.topRightY-Rt[t].mediaBox.bottomLeftY)/_t},Tr=y.setPageHeight=function(t,e){Rt[t].mediaBox.topRightY=e*_t+Rt[t].mediaBox.bottomLeftY};return y.internal={pdfEscape:Ce,getStyle:tr,getFont:hr,getFontSize:vt,getCharSpace:Pr,getTextColor:Sr,getLineHeight:gr,getLineHeightFactor:vr,getLineWidth:dr,write:ft,getHorizontalCoordinate:br,getVerticalCoordinate:yr,getCoordinateString:wr,getVerticalCoordinateString:Nr,collections:{},newObject:Xt,newAdditionalObject:$t,newObjectDeferred:Kt,newObjectDeferredBegin:Zt,getFilters:ne,putStream:ie,events:Tt,scaleFactor:_t,pageSize:{getWidth:function(){return qr($)},setWidth:function(t){Dr($,t)},getHeight:function(){return Rr($)},setHeight:function(t){Tr($,t)}},encryptionOptions:m,encryption:Ye,getEncryptor:Je,output:Ve,getNumberOfPages:Ee,pages:ot,out:lt,f2:R,f3:T,getPageInfo:Xe,getPageInfoByObjId:Ke,getCurrentPageInfo:Ze,getPDFVersion:N,Point:Cr,Rectangle:jr,Matrix:Vt,hasHotfix:Ge},Object.defineProperty(y.internal.pageSize,"width",{get:function(){return qr($)},set:function(t){Dr($,t)},enumerable:!0,configurable:!0}),Object.defineProperty(y.internal.pageSize,"height",{get:function(){return Rr($)},set:function(t){Tr($,t)},enumerable:!0,configurable:!0}),ke.call(y,pt),St="F1",Oe(s,i),Tt.publish("initialized"),y}I.prototype.lsbFirstWord=function(t){return String.fromCharCode(t>>0&255,t>>8&255,t>>16&255,t>>24&255)},I.prototype.toHexString=function(t){return t.split("").map((function(t){return("0"+(255&t.charCodeAt(0)).toString(16)).slice(-2)})).join("")},I.prototype.hexToBytes=function(t){for(var e=[],r=0;r<t.length;r+=2)e.push(String.fromCharCode(parseInt(t.substr(r,2),16)));return e.join("")},I.prototype.processOwnerPassword=function(t,e){return P(x(e).substr(0,5),t)},I.prototype.encryptor=function(t,e){var r=x(this.encryptionKey+String.fromCharCode(255&t,t>>8&255,t>>16&255,255&e,e>>8&255)).substr(0,10);return function(t){return P(r,t)}},j.prototype.equals=function(e){var r,n="id,objectNumber,equals";if(!e||t(e)!==t(this))return!1;var i=0;for(r in this)if(!(n.indexOf(r)>=0)){if(this.hasOwnProperty(r)&&!e.hasOwnProperty(r))return!1;if(this[r]!==e[r])return!1;i++}for(r in e)e.hasOwnProperty(r)&&n.indexOf(r)<0&&i--;return 0===i},E.API={events:[]},E.version="2.5.1";var q=E.API,D=1,R=function(t){return t.replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},T=function(t){return t.replace(/\\\\/g,"\\").replace(/\\\(/g,"(").replace(/\\\)/g,")")},U=function(t){return t.toFixed(2)},z=function(t){return t.toFixed(5)};q.__acroform__={};var H=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t},W=function(t){return t*D},V=function(t){var e=new ut,r=At.internal.getHeight(t)||0,n=At.internal.getWidth(t)||0;return e.BBox=[0,0,Number(U(n)),Number(U(r))],e},G=q.__acroform__.setBit=function(t,e){if(t=t||0,e=e||0,isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.setBit");return t|=1<<e},Y=q.__acroform__.clearBit=function(t,e){if(t=t||0,e=e||0,isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.clearBit");return t&=~(1<<e)},J=q.__acroform__.getBit=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.getBit");return 0==(t&1<<e)?0:1},X=q.__acroform__.getBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.getBitForPdf");return J(t,e-1)},K=q.__acroform__.setBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.setBitForPdf");return G(t,e-1)},Z=q.__acroform__.clearBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.clearBitForPdf");return Y(t,e-1)},$=q.__acroform__.calculateCoordinates=function(t,e){var r=e.internal.getHorizontalCoordinate,n=e.internal.getVerticalCoordinate,i=t[0],a=t[1],o=t[2],s=t[3],c={};return c.lowerLeft_X=r(i)||0,c.lowerLeft_Y=n(a+s)||0,c.upperRight_X=r(i+o)||0,c.upperRight_Y=n(a)||0,[Number(U(c.lowerLeft_X)),Number(U(c.lowerLeft_Y)),Number(U(c.upperRight_X)),Number(U(c.upperRight_Y))]},Q=function(t){if(t.appearanceStreamContent)return t.appearanceStreamContent;if(t.V||t.DV){var e=[],r=t._V||t.DV,n=tt(t,r),i=t.scope.internal.getFont(t.fontName,t.fontStyle).id;e.push("/Tx BMC"),e.push("q"),e.push("BT"),e.push(t.scope.__private__.encodeColorString(t.color)),e.push("/"+i+" "+U(n.fontSize)+" Tf"),e.push("1 0 0 1 0 0 Tm"),e.push(n.text),e.push("ET"),e.push("Q"),e.push("EMC");var a=V(t);return a.scope=t.scope,a.stream=e.join("\n"),a}},tt=function(t,e){var r=0===t.fontSize?t.maxFontSize:t.fontSize,n={text:"",fontSize:""},i=(e=")"==(e="("==e.substr(0,1)?e.substr(1):e).substr(e.length-1)?e.substr(0,e.length-1):e).split(" ");i=t.multiline?i.map((function(t){return t.split("\n")})):i.map((function(t){return[t]}));var a=r,o=At.internal.getHeight(t)||0;o=o<0?-o:o;var s=At.internal.getWidth(t)||0;s=s<0?-s:s;var c=function(e,r,n){if(e+1<i.length){var a=r+" "+i[e+1][0];return et(a,t,n).width<=s-4}return!1};a++;t:for(;a>0;){e="",a--;var u,h,l=et("3",t,a).height,f=t.multiline?o-a:(o-l)/2,d=f+=2,p=0,g=0,m=0;if(a<=0){e="(...) Tj\n",e+="% Width of Text: "+et(e,t,a=12).width+", FieldWidth:"+s+"\n";break}for(var v="",b=0,y=0;y<i.length;y++)if(i.hasOwnProperty(y)){var w=!1;if(1!==i[y].length&&m!==i[y].length-1){if((l+2)*(b+2)+2>o)continue t;v+=i[y][m],w=!0,g=y,y--}else{v=" "==(v+=i[y][m]+" ").substr(v.length-1)?v.substr(0,v.length-1):v;var N=parseInt(y),L=c(N,v,a),A=y>=i.length-1;if(L&&!A){v+=" ",m=0;continue}if(L||A){if(A)g=N;else if(t.multiline&&(l+2)*(b+2)+2>o)continue t}else{if(!t.multiline)continue t;if((l+2)*(b+2)+2>o)continue t;g=N}}for(var x="",S=p;S<=g;S++){var _=i[S];if(t.multiline){if(S===g){x+=_[m]+" ",m=(m+1)%_.length;continue}if(S===p){x+=_[_.length-1]+" ";continue}}x+=_[0]+" "}switch(x=" "==x.substr(x.length-1)?x.substr(0,x.length-1):x,h=et(x,t,a).width,t.textAlign){case"right":u=s-h-2;break;case"center":u=(s-h)/2;break;case"left":default:u=2}e+=U(u)+" "+U(d)+" Td\n",e+="("+R(x)+") Tj\n",e+=-U(u)+" 0 Td\n",d=-(a+2),h=0,p=w?g:g+1,b++,v=""}else;break}return n.text=e,n.fontSize=a,n},et=function(t,e,r){var n=e.scope.internal.getFont(e.fontName,e.fontStyle),i=e.scope.getStringUnitWidth(t,{font:n,fontSize:parseFloat(r),charSpace:0})*parseFloat(r);return{height:e.scope.getStringUnitWidth("3",{font:n,fontSize:parseFloat(r),charSpace:0})*parseFloat(r)*1.5,width:i}},rt={fields:[],xForms:[],acroFormDictionaryRoot:null,printedOut:!1,internal:null,isInitialized:!1},nt=function(t,e){var r={type:"reference",object:t};void 0===e.internal.getPageInfo(t.page).pageContext.annotations.find((function(t){return t.type===r.type&&t.object===r.object}))&&e.internal.getPageInfo(t.page).pageContext.annotations.push(r)},it=function(e,r){for(var n in e)if(e.hasOwnProperty(n)){var i=n,a=e[n];r.internal.newObjectDeferredBegin(a.objId,!0),"object"===t(a)&&"function"==typeof a.putStream&&a.putStream(),delete e[i]}},at=function(e,r){if(r.scope=e,void 0!==e.internal&&(void 0===e.internal.acroformPlugin||!1===e.internal.acroformPlugin.isInitialized)){if(lt.FieldNum=0,e.internal.acroformPlugin=JSON.parse(JSON.stringify(rt)),e.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("Exception while creating AcroformDictionary");D=e.internal.scaleFactor,e.internal.acroformPlugin.acroFormDictionaryRoot=new ht,e.internal.acroformPlugin.acroFormDictionaryRoot.scope=e,e.internal.acroformPlugin.acroFormDictionaryRoot._eventID=e.internal.events.subscribe("postPutResources",(function(){!function(t){t.internal.events.unsubscribe(t.internal.acroformPlugin.acroFormDictionaryRoot._eventID),delete t.internal.acroformPlugin.acroFormDictionaryRoot._eventID,t.internal.acroformPlugin.printedOut=!0}(e)})),e.internal.events.subscribe("buildDocument",(function(){!function(t){t.internal.acroformPlugin.acroFormDictionaryRoot.objId=void 0;var e=t.internal.acroformPlugin.acroFormDictionaryRoot.Fields;for(var r in e)if(e.hasOwnProperty(r)){var n=e[r];n.objId=void 0,n.hasAnnotation&&nt(n,t)}}(e)})),e.internal.events.subscribe("putCatalog",(function(){!function(t){if(void 0===t.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("putCatalogCallback: Root missing.");t.internal.write("/AcroForm "+t.internal.acroformPlugin.acroFormDictionaryRoot.objId+" 0 R")}(e)})),e.internal.events.subscribe("postPutPages",(function(r){!function(e,r){var n=!e;for(var i in e||(r.internal.newObjectDeferredBegin(r.internal.acroformPlugin.acroFormDictionaryRoot.objId,!0),r.internal.acroformPlugin.acroFormDictionaryRoot.putStream()),e=e||r.internal.acroformPlugin.acroFormDictionaryRoot.Kids)if(e.hasOwnProperty(i)){var a=e[i],o=[],s=a.Rect;if(a.Rect&&(a.Rect=$(a.Rect,r)),r.internal.newObjectDeferredBegin(a.objId,!0),a.DA=At.createDefaultAppearanceStream(a),"object"===t(a)&&"function"==typeof a.getKeyValueListForStream&&(o=a.getKeyValueListForStream()),a.Rect=s,a.hasAppearanceStream&&!a.appearanceStreamContent){var c=Q(a);o.push({key:"AP",value:"<</N "+c+">>"}),r.internal.acroformPlugin.xForms.push(c)}if(a.appearanceStreamContent){var u="";for(var h in a.appearanceStreamContent)if(a.appearanceStreamContent.hasOwnProperty(h)){var l=a.appearanceStreamContent[h];if(u+="/"+h+" ",u+="<<",Object.keys(l).length>=1||Array.isArray(l)){for(var i in l)if(l.hasOwnProperty(i)){var f=l[i];"function"==typeof f&&(f=f.call(r,a)),u+="/"+i+" "+f+" ",r.internal.acroformPlugin.xForms.indexOf(f)>=0||r.internal.acroformPlugin.xForms.push(f)}}else"function"==typeof(f=l)&&(f=f.call(r,a)),u+="/"+i+" "+f,r.internal.acroformPlugin.xForms.indexOf(f)>=0||r.internal.acroformPlugin.xForms.push(f);u+=">>"}o.push({key:"AP",value:"<<\n"+u+">>"})}r.internal.putStream({additionalKeyValues:o,objectId:a.objId}),r.internal.out("endobj")}n&&it(r.internal.acroformPlugin.xForms,r)}(r,e)})),e.internal.acroformPlugin.isInitialized=!0}},ot=q.__acroform__.arrayToPdfArray=function(e,r,n){var i=function(t){return t};if(Array.isArray(e)){for(var a="[",o=0;o<e.length;o++)switch(0!==o&&(a+=" "),t(e[o])){case"boolean":case"number":case"object":a+=e[o].toString();break;case"string":"/"!==e[o].substr(0,1)?(void 0!==r&&n&&(i=n.internal.getEncryptor(r)),a+="("+R(i(e[o].toString()))+")"):a+=e[o].toString()}return a+="]"}throw new Error("Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray")};var st=function(t,e,r){var n=function(t){return t};return void 0!==e&&r&&(n=r.internal.getEncryptor(e)),(t=t||"").toString(),t="("+R(n(t))+")"},ct=function(){this._objId=void 0,this._scope=void 0,Object.defineProperty(this,"objId",{get:function(){if(void 0===this._objId){if(void 0===this.scope)return;this._objId=this.scope.internal.newObjectDeferred()}return this._objId},set:function(t){this._objId=t}}),Object.defineProperty(this,"scope",{value:this._scope,writable:!0})};ct.prototype.toString=function(){return this.objId+" 0 R"},ct.prototype.putStream=function(){var t=this.getKeyValueListForStream();this.scope.internal.putStream({data:this.stream,additionalKeyValues:t,objectId:this.objId}),this.scope.internal.out("endobj")},ct.prototype.getKeyValueListForStream=function(){var t=[],e=Object.getOwnPropertyNames(this).filter((function(t){return"content"!=t&&"appearanceStreamContent"!=t&&"scope"!=t&&"objId"!=t&&"_"!=t.substring(0,1)}));for(var r in e)if(!1===Object.getOwnPropertyDescriptor(this,e[r]).configurable){var n=e[r],i=this[n];i&&(Array.isArray(i)?t.push({key:n,value:ot(i,this.objId,this.scope)}):i instanceof ct?(i.scope=this.scope,t.push({key:n,value:i.objId+" 0 R"})):"function"!=typeof i&&t.push({key:n,value:i}))}return t};var ut=function(){ct.call(this),Object.defineProperty(this,"Type",{value:"/XObject",configurable:!1,writable:!0}),Object.defineProperty(this,"Subtype",{value:"/Form",configurable:!1,writable:!0}),Object.defineProperty(this,"FormType",{value:1,configurable:!1,writable:!0});var t,e=[];Object.defineProperty(this,"BBox",{configurable:!1,get:function(){return e},set:function(t){e=t}}),Object.defineProperty(this,"Resources",{value:"2 0 R",configurable:!1,writable:!0}),Object.defineProperty(this,"stream",{enumerable:!1,configurable:!0,set:function(e){t=e.trim()},get:function(){return t||null}})};H(ut,ct);var ht=function(){ct.call(this);var t,e=[];Object.defineProperty(this,"Kids",{enumerable:!1,configurable:!0,get:function(){return e.length>0?e:void 0}}),Object.defineProperty(this,"Fields",{enumerable:!1,configurable:!1,get:function(){return e}}),Object.defineProperty(this,"DA",{enumerable:!1,configurable:!1,get:function(){if(t){var e=function(t){return t};return this.scope&&(e=this.scope.internal.getEncryptor(this.objId)),"("+R(e(t))+")"}},set:function(e){t=e}})};H(ht,ct);var lt=function t(){ct.call(this);var e=4;Object.defineProperty(this,"F",{enumerable:!1,configurable:!1,get:function(){return e},set:function(t){if(isNaN(t))throw new Error('Invalid value "'+t+'" for attribute F supplied.');e=t}}),Object.defineProperty(this,"showWhenPrinted",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(e,3))},set:function(t){!0===Boolean(t)?this.F=K(e,3):this.F=Z(e,3)}});var r=0;Object.defineProperty(this,"Ff",{enumerable:!1,configurable:!1,get:function(){return r},set:function(t){if(isNaN(t))throw new Error('Invalid value "'+t+'" for attribute Ff supplied.');r=t}});var n=[];Object.defineProperty(this,"Rect",{enumerable:!1,configurable:!1,get:function(){if(0!==n.length)return n},set:function(t){n=void 0!==t?t:[]}}),Object.defineProperty(this,"x",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[0])?0:n[0]},set:function(t){n[0]=t}}),Object.defineProperty(this,"y",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[1])?0:n[1]},set:function(t){n[1]=t}}),Object.defineProperty(this,"width",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[2])?0:n[2]},set:function(t){n[2]=t}}),Object.defineProperty(this,"height",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[3])?0:n[3]},set:function(t){n[3]=t}});var i="";Object.defineProperty(this,"FT",{enumerable:!0,configurable:!1,get:function(){return i},set:function(t){switch(t){case"/Btn":case"/Tx":case"/Ch":case"/Sig":i=t;break;default:throw new Error('Invalid value "'+t+'" for attribute FT supplied.')}}});var a=null;Object.defineProperty(this,"T",{enumerable:!0,configurable:!1,get:function(){if(!a||a.length<1){if(this instanceof yt)return;a="FieldObject"+t.FieldNum++}var e=function(t){return t};return this.scope&&(e=this.scope.internal.getEncryptor(this.objId)),"("+R(e(a))+")"},set:function(t){a=t.toString()}}),Object.defineProperty(this,"fieldName",{configurable:!0,enumerable:!0,get:function(){return a},set:function(t){a=t}});var o="helvetica";Object.defineProperty(this,"fontName",{enumerable:!0,configurable:!0,get:function(){return o},set:function(t){o=t}});var s="normal";Object.defineProperty(this,"fontStyle",{enumerable:!0,configurable:!0,get:function(){return s},set:function(t){s=t}});var c=0;Object.defineProperty(this,"fontSize",{enumerable:!0,configurable:!0,get:function(){return c},set:function(t){c=t}});var u=void 0;Object.defineProperty(this,"maxFontSize",{enumerable:!0,configurable:!0,get:function(){return void 0===u?50/D:u},set:function(t){u=t}});var h="black";Object.defineProperty(this,"color",{enumerable:!0,configurable:!0,get:function(){return h},set:function(t){h=t}});var l="/F1 0 Tf 0 g";Object.defineProperty(this,"DA",{enumerable:!0,configurable:!1,get:function(){if(!(!l||this instanceof yt||this instanceof Nt))return st(l,this.objId,this.scope)},set:function(t){t=t.toString(),l=t}});var f=null;Object.defineProperty(this,"DV",{enumerable:!1,configurable:!1,get:function(){if(f)return this instanceof mt==!1?st(f,this.objId,this.scope):f},set:function(t){t=t.toString(),f=this instanceof mt==!1?"("===t.substr(0,1)?T(t.substr(1,t.length-2)):T(t):t}}),Object.defineProperty(this,"defaultValue",{enumerable:!0,configurable:!0,get:function(){return this instanceof mt==!0?T(f.substr(1,f.length-1)):f},set:function(t){t=t.toString(),f=this instanceof mt==!0?"/"+t:t}});var d=null;Object.defineProperty(this,"_V",{enumerable:!1,configurable:!1,get:function(){if(d)return d},set:function(t){this.V=t}}),Object.defineProperty(this,"V",{enumerable:!1,configurable:!1,get:function(){if(d)return this instanceof mt==!1?st(d,this.objId,this.scope):d},set:function(t){t=t.toString(),d=this instanceof mt==!1?"("===t.substr(0,1)?T(t.substr(1,t.length-2)):T(t):t}}),Object.defineProperty(this,"value",{enumerable:!0,configurable:!0,get:function(){return this instanceof mt==!0?T(d.substr(1,d.length-1)):d},set:function(t){t=t.toString(),d=this instanceof mt==!0?"/"+t:t}}),Object.defineProperty(this,"hasAnnotation",{enumerable:!0,configurable:!0,get:function(){return this.Rect}}),Object.defineProperty(this,"Type",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Annot":null}}),Object.defineProperty(this,"Subtype",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Widget":null}});var p,g=!1;Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return g},set:function(t){t=Boolean(t),g=t}}),Object.defineProperty(this,"page",{enumerable:!0,configurable:!0,get:function(){if(p)return p},set:function(t){p=t}}),Object.defineProperty(this,"readOnly",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,1))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,1):this.Ff=Z(this.Ff,1)}}),Object.defineProperty(this,"required",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,2))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,2):this.Ff=Z(this.Ff,2)}}),Object.defineProperty(this,"noExport",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,3))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,3):this.Ff=Z(this.Ff,3)}});var m=null;Object.defineProperty(this,"Q",{enumerable:!0,configurable:!1,get:function(){if(null!==m)return m},set:function(t){if(-1===[0,1,2].indexOf(t))throw new Error('Invalid value "'+t+'" for attribute Q supplied.');m=t}}),Object.defineProperty(this,"textAlign",{get:function(){var t;switch(m){case 0:default:t="left";break;case 1:t="center";break;case 2:t="right"}return t},configurable:!0,enumerable:!0,set:function(t){switch(t){case"right":case 2:m=2;break;case"center":case 1:m=1;break;case"left":case 0:default:m=0}}})};H(lt,ct);var ft=function(){lt.call(this),this.FT="/Ch",this.V="()",this.fontName="zapfdingbats";var t=0;Object.defineProperty(this,"TI",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"topIndex",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){t=e}});var e=[];Object.defineProperty(this,"Opt",{enumerable:!0,configurable:!1,get:function(){return ot(e,this.objId,this.scope)},set:function(t){var r,n;n=[],"string"==typeof(r=t)&&(n=function(t,e,r){r||(r=1);for(var n,i=[];n=e.exec(t);)i.push(n[r]);return i}(r,/\((.*?)\)/g)),e=n}}),this.getOptions=function(){return e},this.setOptions=function(t){e=t,this.sort&&e.sort()},this.addOption=function(t){t=(t=t||"").toString(),e.push(t),this.sort&&e.sort()},this.removeOption=function(t,r){for(r=r||!1,t=(t=t||"").toString();-1!==e.indexOf(t)&&(e.splice(e.indexOf(t),1),!1!==r););},Object.defineProperty(this,"combo",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,18))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,18):this.Ff=Z(this.Ff,18)}}),Object.defineProperty(this,"edit",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,19))},set:function(t){!0===this.combo&&(!0===Boolean(t)?this.Ff=K(this.Ff,19):this.Ff=Z(this.Ff,19))}}),Object.defineProperty(this,"sort",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,20))},set:function(t){!0===Boolean(t)?(this.Ff=K(this.Ff,20),e.sort()):this.Ff=Z(this.Ff,20)}}),Object.defineProperty(this,"multiSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,22))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,22):this.Ff=Z(this.Ff,22)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,23):this.Ff=Z(this.Ff,23)}}),Object.defineProperty(this,"commitOnSelChange",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,27))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,27):this.Ff=Z(this.Ff,27)}}),this.hasAppearanceStream=!1};H(ft,lt);var dt=function(){ft.call(this),this.fontName="helvetica",this.combo=!1};H(dt,ft);var pt=function(){dt.call(this),this.combo=!0};H(pt,dt);var gt=function(){pt.call(this),this.edit=!0};H(gt,pt);var mt=function(){lt.call(this),this.FT="/Btn",Object.defineProperty(this,"noToggleToOff",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,15))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,15):this.Ff=Z(this.Ff,15)}}),Object.defineProperty(this,"radio",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,16))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,16):this.Ff=Z(this.Ff,16)}}),Object.defineProperty(this,"pushButton",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,17))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,17):this.Ff=Z(this.Ff,17)}}),Object.defineProperty(this,"radioIsUnison",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,26):this.Ff=Z(this.Ff,26)}});var e,r={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};if(this.scope&&(t=this.scope.internal.getEncryptor(this.objId)),0!==Object.keys(r).length){var e,n=[];for(e in n.push("<<"),r)n.push("/"+e+" ("+R(t(r[e]))+")");return n.push(">>"),n.join("\n")}},set:function(e){"object"===t(e)&&(r=e)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return r.CA||""},set:function(t){"string"==typeof t&&(r.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return e},set:function(t){e=t}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return e.substr(1,e.length-1)},set:function(t){e="/"+t}})};H(mt,lt);var vt=function(){mt.call(this),this.pushButton=!0};H(vt,mt);var bt=function(){mt.call(this),this.radio=!0,this.pushButton=!1;var t=[];Object.defineProperty(this,"Kids",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=void 0!==e?e:[]}})};H(bt,mt);var yt=function(){var e,r;lt.call(this),Object.defineProperty(this,"Parent",{enumerable:!1,configurable:!1,get:function(){return e},set:function(t){e=t}}),Object.defineProperty(this,"optionName",{enumerable:!1,configurable:!0,get:function(){return r},set:function(t){r=t}});var n,i={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};this.scope&&(t=this.scope.internal.getEncryptor(this.objId));var e,r=[];for(e in r.push("<<"),i)r.push("/"+e+" ("+R(t(i[e]))+")");return r.push(">>"),r.join("\n")},set:function(e){"object"===t(e)&&(i=e)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return i.CA||""},set:function(t){"string"==typeof t&&(i.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return n},set:function(t){n=t}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return n.substr(1,n.length-1)},set:function(t){n="/"+t}}),this.caption="l",this.appearanceState="Off",this._AppearanceType=At.RadioButton.Circle,this.appearanceStreamContent=this._AppearanceType.createAppearanceStream(this.optionName)};H(yt,lt),bt.prototype.setAppearance=function(t){if(!("createAppearanceStream"in t)||!("getCA"in t))throw new Error("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");for(var e in this.Kids)if(this.Kids.hasOwnProperty(e)){var r=this.Kids[e];r.appearanceStreamContent=t.createAppearanceStream(r.optionName),r.caption=t.getCA()}},bt.prototype.createOption=function(t){var e=new yt;return e.Parent=this,e.optionName=t,this.Kids.push(e),xt.call(this.scope,e),e};var wt=function(){mt.call(this),this.fontName="zapfdingbats",this.caption="3",this.appearanceState="On",this.value="On",this.textAlign="center",this.appearanceStreamContent=At.CheckBox.createAppearanceStream()};H(wt,mt);var Nt=function(){lt.call(this),this.FT="/Tx",Object.defineProperty(this,"multiline",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,13))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,13):this.Ff=Z(this.Ff,13)}}),Object.defineProperty(this,"fileSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,21))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,21):this.Ff=Z(this.Ff,21)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,23):this.Ff=Z(this.Ff,23)}}),Object.defineProperty(this,"doNotScroll",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,24))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,24):this.Ff=Z(this.Ff,24)}}),Object.defineProperty(this,"comb",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,25))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,25):this.Ff=Z(this.Ff,25)}}),Object.defineProperty(this,"richText",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,26):this.Ff=Z(this.Ff,26)}});var t=null;Object.defineProperty(this,"MaxLen",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"maxLength",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){Number.isInteger(e)&&(t=e)}}),Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return this.V||this.DV}})};H(Nt,lt);var Lt=function(){Nt.call(this),Object.defineProperty(this,"password",{enumerable:!0,configurable:!0,get:function(){return Boolean(X(this.Ff,14))},set:function(t){!0===Boolean(t)?this.Ff=K(this.Ff,14):this.Ff=Z(this.Ff,14)}}),this.password=!0};H(Lt,Nt);var At={CheckBox:{createAppearanceStream:function(){return{N:{On:At.CheckBox.YesNormal},D:{On:At.CheckBox.YesPushDown,Off:At.CheckBox.OffPushDown}}},YesPushDown:function(t){var e=V(t);e.scope=t.scope;var r=[],n=t.scope.internal.getFont(t.fontName,t.fontStyle).id,i=t.scope.__private__.encodeColorString(t.color),a=tt(t,t.caption);return r.push("0.749023 g"),r.push("0 0 "+U(At.internal.getWidth(t))+" "+U(At.internal.getHeight(t))+" re"),r.push("f"),r.push("BMC"),r.push("q"),r.push("0 0 1 rg"),r.push("/"+n+" "+U(a.fontSize)+" Tf "+i),r.push("BT"),r.push(a.text),r.push("ET"),r.push("Q"),r.push("EMC"),e.stream=r.join("\n"),e},YesNormal:function(t){var e=V(t);e.scope=t.scope;var r=t.scope.internal.getFont(t.fontName,t.fontStyle).id,n=t.scope.__private__.encodeColorString(t.color),i=[],a=At.internal.getHeight(t),o=At.internal.getWidth(t),s=tt(t,t.caption);return i.push("1 g"),i.push("0 0 "+U(o)+" "+U(a)+" re"),i.push("f"),i.push("q"),i.push("0 0 1 rg"),i.push("0 0 "+U(o-1)+" "+U(a-1)+" re"),i.push("W"),i.push("n"),i.push("0 g"),i.push("BT"),i.push("/"+r+" "+U(s.fontSize)+" Tf "+n),i.push(s.text),i.push("ET"),i.push("Q"),e.stream=i.join("\n"),e},OffPushDown:function(t){var e=V(t);e.scope=t.scope;var r=[];return r.push("0.749023 g"),r.push("0 0 "+U(At.internal.getWidth(t))+" "+U(At.internal.getHeight(t))+" re"),r.push("f"),e.stream=r.join("\n"),e}},RadioButton:{Circle:{createAppearanceStream:function(t){var e={D:{Off:At.RadioButton.Circle.OffPushDown},N:{}};return e.N[t]=At.RadioButton.Circle.YesNormal,e.D[t]=At.RadioButton.Circle.YesPushDown,e},getCA:function(){return"l"},YesNormal:function(t){var e=V(t);e.scope=t.scope;var r=[],n=At.internal.getWidth(t)<=At.internal.getHeight(t)?At.internal.getWidth(t)/4:At.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=At.internal.Bezier_C,a=Number((n*i).toFixed(5));return r.push("q"),r.push("1 0 0 1 "+z(At.internal.getWidth(t)/2)+" "+z(At.internal.getHeight(t)/2)+" cm"),r.push(n+" 0 m"),r.push(n+" "+a+" "+a+" "+n+" 0 "+n+" c"),r.push("-"+a+" "+n+" -"+n+" "+a+" -"+n+" 0 c"),r.push("-"+n+" -"+a+" -"+a+" -"+n+" 0 -"+n+" c"),r.push(a+" -"+n+" "+n+" -"+a+" "+n+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e},YesPushDown:function(t){var e=V(t);e.scope=t.scope;var r=[],n=At.internal.getWidth(t)<=At.internal.getHeight(t)?At.internal.getWidth(t)/4:At.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=Number((2*n).toFixed(5)),a=Number((i*At.internal.Bezier_C).toFixed(5)),o=Number((n*At.internal.Bezier_C).toFixed(5));return r.push("0.749023 g"),r.push("q"),r.push("1 0 0 1 "+z(At.internal.getWidth(t)/2)+" "+z(At.internal.getHeight(t)/2)+" cm"),r.push(i+" 0 m"),r.push(i+" "+a+" "+a+" "+i+" 0 "+i+" c"),r.push("-"+a+" "+i+" -"+i+" "+a+" -"+i+" 0 c"),r.push("-"+i+" -"+a+" -"+a+" -"+i+" 0 -"+i+" c"),r.push(a+" -"+i+" "+i+" -"+a+" "+i+" 0 c"),r.push("f"),r.push("Q"),r.push("0 g"),r.push("q"),r.push("1 0 0 1 "+z(At.internal.getWidth(t)/2)+" "+z(At.internal.getHeight(t)/2)+" cm"),r.push(n+" 0 m"),r.push(n+" "+o+" "+o+" "+n+" 0 "+n+" c"),r.push("-"+o+" "+n+" -"+n+" "+o+" -"+n+" 0 c"),r.push("-"+n+" -"+o+" -"+o+" -"+n+" 0 -"+n+" c"),r.push(o+" -"+n+" "+n+" -"+o+" "+n+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e},OffPushDown:function(t){var e=V(t);e.scope=t.scope;var r=[],n=At.internal.getWidth(t)<=At.internal.getHeight(t)?At.internal.getWidth(t)/4:At.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=Number((2*n).toFixed(5)),a=Number((i*At.internal.Bezier_C).toFixed(5));return r.push("0.749023 g"),r.push("q"),r.push("1 0 0 1 "+z(At.internal.getWidth(t)/2)+" "+z(At.internal.getHeight(t)/2)+" cm"),r.push(i+" 0 m"),r.push(i+" "+a+" "+a+" "+i+" 0 "+i+" c"),r.push("-"+a+" "+i+" -"+i+" "+a+" -"+i+" 0 c"),r.push("-"+i+" -"+a+" -"+a+" -"+i+" 0 -"+i+" c"),r.push(a+" -"+i+" "+i+" -"+a+" "+i+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e}},Cross:{createAppearanceStream:function(t){var e={D:{Off:At.RadioButton.Cross.OffPushDown},N:{}};return e.N[t]=At.RadioButton.Cross.YesNormal,e.D[t]=At.RadioButton.Cross.YesPushDown,e},getCA:function(){return"8"},YesNormal:function(t){var e=V(t);e.scope=t.scope;var r=[],n=At.internal.calculateCross(t);return r.push("q"),r.push("1 1 "+U(At.internal.getWidth(t)-2)+" "+U(At.internal.getHeight(t)-2)+" re"),r.push("W"),r.push("n"),r.push(U(n.x1.x)+" "+U(n.x1.y)+" m"),r.push(U(n.x2.x)+" "+U(n.x2.y)+" l"),r.push(U(n.x4.x)+" "+U(n.x4.y)+" m"),r.push(U(n.x3.x)+" "+U(n.x3.y)+" l"),r.push("s"),r.push("Q"),e.stream=r.join("\n"),e},YesPushDown:function(t){var e=V(t);e.scope=t.scope;var r=At.internal.calculateCross(t),n=[];return n.push("0.749023 g"),n.push("0 0 "+U(At.internal.getWidth(t))+" "+U(At.internal.getHeight(t))+" re"),n.push("f"),n.push("q"),n.push("1 1 "+U(At.internal.getWidth(t)-2)+" "+U(At.internal.getHeight(t)-2)+" re"),n.push("W"),n.push("n"),n.push(U(r.x1.x)+" "+U(r.x1.y)+" m"),n.push(U(r.x2.x)+" "+U(r.x2.y)+" l"),n.push(U(r.x4.x)+" "+U(r.x4.y)+" m"),n.push(U(r.x3.x)+" "+U(r.x3.y)+" l"),n.push("s"),n.push("Q"),e.stream=n.join("\n"),e},OffPushDown:function(t){var e=V(t);e.scope=t.scope;var r=[];return r.push("0.749023 g"),r.push("0 0 "+U(At.internal.getWidth(t))+" "+U(At.internal.getHeight(t))+" re"),r.push("f"),e.stream=r.join("\n"),e}}},createDefaultAppearanceStream:function(t){var e=t.scope.internal.getFont(t.fontName,t.fontStyle).id,r=t.scope.__private__.encodeColorString(t.color);return"/"+e+" "+t.fontSize+" Tf "+r}};At.internal={Bezier_C:.551915024494,calculateCross:function(t){var e=At.internal.getWidth(t),r=At.internal.getHeight(t),n=Math.min(e,r);return{x1:{x:(e-n)/2,y:(r-n)/2+n},x2:{x:(e-n)/2+n,y:(r-n)/2},x3:{x:(e-n)/2,y:(r-n)/2},x4:{x:(e-n)/2+n,y:(r-n)/2+n}}}},At.internal.getWidth=function(e){var r=0;return"object"===t(e)&&(r=W(e.Rect[2])),r},At.internal.getHeight=function(e){var r=0;return"object"===t(e)&&(r=W(e.Rect[3])),r};var xt=q.addField=function(t){if(at(this,t),!(t instanceof lt))throw new Error("Invalid argument passed to jsPDF.addField.");var e;return(e=t).scope.internal.acroformPlugin.printedOut&&(e.scope.internal.acroformPlugin.printedOut=!1,e.scope.internal.acroformPlugin.acroFormDictionaryRoot=null),e.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(e),t.page=t.scope.internal.getCurrentPageInfo().pageNumber,this};q.AcroFormChoiceField=ft,q.AcroFormListBox=dt,q.AcroFormComboBox=pt,q.AcroFormEditBox=gt,q.AcroFormButton=mt,q.AcroFormPushButton=vt,q.AcroFormRadioButton=bt,q.AcroFormCheckBox=wt,q.AcroFormTextField=Nt,q.AcroFormPasswordField=Lt,q.AcroFormAppearance=At,q.AcroForm={ChoiceField:ft,ListBox:dt,ComboBox:pt,EditBox:gt,Button:mt,PushButton:vt,RadioButton:bt,CheckBox:wt,TextField:Nt,PasswordField:Lt,Appearance:At},E.AcroForm={ChoiceField:ft,ListBox:dt,ComboBox:pt,EditBox:gt,Button:mt,PushButton:vt,RadioButton:bt,CheckBox:wt,TextField:Nt,PasswordField:Lt,Appearance:At};var St=E.AcroForm;function _t(t){return t.reduce((function(t,e,r){return t[e]=r,t}),{})}!function(e){e.__addimage__={};var r="UNKNOWN",n={PNG:[[137,80,78,71]],TIFF:[[77,77,0,42],[73,73,42,0]],JPEG:[[255,216,255,224,void 0,void 0,74,70,73,70,0],[255,216,255,225,void 0,void 0,69,120,105,102,0,0],[255,216,255,219],[255,216,255,238]],JPEG2000:[[0,0,0,12,106,80,32,32]],GIF87a:[[71,73,70,56,55,97]],GIF89a:[[71,73,70,56,57,97]],WEBP:[[82,73,70,70,void 0,void 0,void 0,void 0,87,69,66,80]],BMP:[[66,77],[66,65],[67,73],[67,80],[73,67],[80,84]]},i=e.__addimage__.getImageFileTypeByImageData=function(t,e){var i,a,o,s,c,u=r;if("RGBA"===(e=e||r)||void 0!==t.data&&t.data instanceof Uint8ClampedArray&&"height"in t&&"width"in t)return"RGBA";if(x(t))for(c in n)for(o=n[c],i=0;i<o.length;i+=1){for(s=!0,a=0;a<o[i].length;a+=1)if(void 0!==o[i][a]&&o[i][a]!==t[a]){s=!1;break}if(!0===s){u=c;break}}else for(c in n)for(o=n[c],i=0;i<o.length;i+=1){for(s=!0,a=0;a<o[i].length;a+=1)if(void 0!==o[i][a]&&o[i][a]!==t.charCodeAt(a)){s=!1;break}if(!0===s){u=c;break}}return u===r&&e!==r&&(u=e),u},a=function t(e){for(var r=this.internal.write,n=this.internal.putStream,i=(0,this.internal.getFilters)();-1!==i.indexOf("FlateEncode");)i.splice(i.indexOf("FlateEncode"),1);e.objectId=this.internal.newObject();var a=[];if(a.push({key:"Type",value:"/XObject"}),a.push({key:"Subtype",value:"/Image"}),a.push({key:"Width",value:e.width}),a.push({key:"Height",value:e.height}),e.colorSpace===b.INDEXED?a.push({key:"ColorSpace",value:"[/Indexed /DeviceRGB "+(e.palette.length/3-1)+" "+("sMask"in e&&void 0!==e.sMask?e.objectId+2:e.objectId+1)+" 0 R]"}):(a.push({key:"ColorSpace",value:"/"+e.colorSpace}),e.colorSpace===b.DEVICE_CMYK&&a.push({key:"Decode",value:"[1 0 1 0 1 0 1 0]"})),a.push({key:"BitsPerComponent",value:e.bitsPerComponent}),"decodeParameters"in e&&void 0!==e.decodeParameters&&a.push({key:"DecodeParms",value:"<<"+e.decodeParameters+">>"}),"transparency"in e&&Array.isArray(e.transparency)){for(var o="",s=0,c=e.transparency.length;s<c;s++)o+=e.transparency[s]+" "+e.transparency[s]+" ";a.push({key:"Mask",value:"["+o+"]"})}void 0!==e.sMask&&a.push({key:"SMask",value:e.objectId+1+" 0 R"});var u=void 0!==e.filter?["/"+e.filter]:void 0;if(n({data:e.data,additionalKeyValues:a,alreadyAppliedFilters:u,objectId:e.objectId}),r("endobj"),"sMask"in e&&void 0!==e.sMask){var h="/Predictor "+e.predictor+" /Colors 1 /BitsPerComponent "+e.bitsPerComponent+" /Columns "+e.width,l={width:e.width,height:e.height,colorSpace:"DeviceGray",bitsPerComponent:e.bitsPerComponent,decodeParameters:h,data:e.sMask};"filter"in e&&(l.filter=e.filter),t.call(this,l)}if(e.colorSpace===b.INDEXED){var f=this.internal.newObject();n({data:_(new Uint8Array(e.palette)),objectId:f}),r("endobj")}},o=function(){var t=this.internal.collections.addImage_images;for(var e in t)a.call(this,t[e])},s=function(){var t,e=this.internal.collections.addImage_images,r=this.internal.write;for(var n in e)r("/I"+(t=e[n]).index,t.objectId,"0","R")},c=function(){this.internal.collections.addImage_images||(this.internal.collections.addImage_images={},this.internal.events.subscribe("putResources",o),this.internal.events.subscribe("putXobjectDict",s))},h=function(){var t=this.internal.collections.addImage_images;return c.call(this),t},l=function(){return Object.keys(this.internal.collections.addImage_images).length},f=function(t){return"function"==typeof e["process"+t.toUpperCase()]},d=function(e){return"object"===t(e)&&1===e.nodeType},p=function(t,r){if("IMG"===t.nodeName&&t.hasAttribute("src")){var n=""+t.getAttribute("src");if(0===n.indexOf("data:image/"))return u(unescape(n).split("base64,").pop());var i=e.loadFile(n,!0);if(void 0!==i)return i}if("CANVAS"===t.nodeName){if(0===t.width||0===t.height)throw new Error("Given canvas must have data. Canvas width: "+t.width+", height: "+t.height);var a;switch(r){case"PNG":a="image/png";break;case"WEBP":a="image/webp";break;case"JPEG":case"JPG":default:a="image/jpeg"}return u(t.toDataURL(a,1).split("base64,").pop())}},g=function(t){var e=this.internal.collections.addImage_images;if(e)for(var r in e)if(t===e[r].alias)return e[r]},m=function(t,e,r){return t||e||(t=-96,e=-96),t<0&&(t=-1*r.width*72/t/this.internal.scaleFactor),e<0&&(e=-1*r.height*72/e/this.internal.scaleFactor),0===t&&(t=e*r.width/r.height),0===e&&(e=t*r.height/r.width),[t,e]},v=function(t,e,r,n,i,a){var o=m.call(this,r,n,i),s=this.internal.getCoordinateString,c=this.internal.getVerticalCoordinateString,u=h.call(this);if(r=o[0],n=o[1],u[i.index]=i,a){a*=Math.PI/180;var l=Math.cos(a),f=Math.sin(a),d=function(t){return t.toFixed(4)},p=[d(l),d(f),d(-1*f),d(l),0,0,"cm"]}this.internal.write("q"),a?(this.internal.write([1,"0","0",1,s(t),c(e+n),"cm"].join(" ")),this.internal.write(p.join(" ")),this.internal.write([s(r),"0","0",s(n),"0","0","cm"].join(" "))):this.internal.write([s(r),"0","0",s(n),s(t),c(e+n),"cm"].join(" ")),this.isAdvancedAPI()&&this.internal.write([1,0,0,-1,0,0,"cm"].join(" ")),this.internal.write("/I"+i.index+" Do"),this.internal.write("Q")},b=e.color_spaces={DEVICE_RGB:"DeviceRGB",DEVICE_GRAY:"DeviceGray",DEVICE_CMYK:"DeviceCMYK",CAL_GREY:"CalGray",CAL_RGB:"CalRGB",LAB:"Lab",ICC_BASED:"ICCBased",INDEXED:"Indexed",PATTERN:"Pattern",SEPARATION:"Separation",DEVICE_N:"DeviceN"};e.decode={DCT_DECODE:"DCTDecode",FLATE_DECODE:"FlateDecode",LZW_DECODE:"LZWDecode",JPX_DECODE:"JPXDecode",JBIG2_DECODE:"JBIG2Decode",ASCII85_DECODE:"ASCII85Decode",ASCII_HEX_DECODE:"ASCIIHexDecode",RUN_LENGTH_DECODE:"RunLengthDecode",CCITT_FAX_DECODE:"CCITTFaxDecode"};var y=e.image_compression={NONE:"NONE",FAST:"FAST",MEDIUM:"MEDIUM",SLOW:"SLOW"},w=e.__addimage__.sHashCode=function(t){var e,r,n=0;if("string"==typeof t)for(r=t.length,e=0;e<r;e++)n=(n<<5)-n+t.charCodeAt(e),n|=0;else if(x(t))for(r=t.byteLength/2,e=0;e<r;e++)n=(n<<5)-n+t[e],n|=0;return n},N=e.__addimage__.validateStringAsBase64=function(t){(t=t||"").toString().trim();var e=!0;return 0===t.length&&(e=!1),t.length%4!=0&&(e=!1),!1===/^[A-Za-z0-9+/]+$/.test(t.substr(0,t.length-2))&&(e=!1),!1===/^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test(t.substr(-2))&&(e=!1),e},L=e.__addimage__.extractImageFromDataUrl=function(t){var e=(t=t||"").split("base64,"),r=null;if(2===e.length){var n=/^data:(\w*\/\w*);*(charset=(?!charset=)[\w=-]*)*;*$/.exec(e[0]);Array.isArray(n)&&(r={mimeType:n[1],charset:n[2],data:e[1]})}return r},A=e.__addimage__.supportsArrayBuffer=function(){return"undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array};e.__addimage__.isArrayBuffer=function(t){return A()&&t instanceof ArrayBuffer};var x=e.__addimage__.isArrayBufferView=function(t){return A()&&"undefined"!=typeof Uint32Array&&(t instanceof Int8Array||t instanceof Uint8Array||"undefined"!=typeof Uint8ClampedArray&&t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array)},S=e.__addimage__.binaryStringToUint8Array=function(t){for(var e=t.length,r=new Uint8Array(e),n=0;n<e;n++)r[n]=t.charCodeAt(n);return r},_=e.__addimage__.arrayBufferToBinaryString=function(t){for(var e="",r=x(t)?t:new Uint8Array(t),n=0;n<r.length;n+=8192)e+=String.fromCharCode.apply(null,r.subarray(n,n+8192));return e};e.addImage=function(){var e,n,i,a,o,s,u,h,l;if("number"==typeof arguments[1]?(n=r,i=arguments[1],a=arguments[2],o=arguments[3],s=arguments[4],u=arguments[5],h=arguments[6],l=arguments[7]):(n=arguments[1],i=arguments[2],a=arguments[3],o=arguments[4],s=arguments[5],u=arguments[6],h=arguments[7],l=arguments[8]),"object"===t(e=arguments[0])&&!d(e)&&"imageData"in e){var f=e;e=f.imageData,n=f.format||n||r,i=f.x||i||0,a=f.y||a||0,o=f.w||f.width||o,s=f.h||f.height||s,u=f.alias||u,h=f.compression||h,l=f.rotation||f.angle||l}var p=this.internal.getFilters();if(void 0===h&&-1!==p.indexOf("FlateEncode")&&(h="SLOW"),isNaN(i)||isNaN(a))throw new Error("Invalid coordinates passed to jsPDF.addImage");c.call(this);var g=P.call(this,e,n,u,h);return v.call(this,i,a,o,s,g,l),this};var P=function(t,n,a,o){var s,c,u;if("string"==typeof t&&i(t)===r){t=unescape(t);var h=k(t,!1);(""!==h||void 0!==(h=e.loadFile(t,!0)))&&(t=h)}if(d(t)&&(t=p(t,n)),n=i(t,n),!f(n))throw new Error("addImage does not support files of type '"+n+"', please ensure that a plugin for '"+n+"' support is added.");if((null==(u=a)||0===u.length)&&(a=function(t){return"string"==typeof t||x(t)?w(t):x(t.data)?w(t.data):null}(t)),(s=g.call(this,a))||(A()&&(t instanceof Uint8Array||"RGBA"===n||(c=t,t=S(t))),s=this["process"+n.toUpperCase()](t,l.call(this),a,function(t){return t&&"string"==typeof t&&(t=t.toUpperCase()),t in e.image_compression?t:y.NONE}(o),c)),!s)throw new Error("An unknown error occurred whilst processing the image.");return s},k=e.__addimage__.convertBase64ToBinaryString=function(t,e){var r;e="boolean"!=typeof e||e;var n,i="";if("string"==typeof t){n=null!==(r=L(t))?r.data:t;try{i=u(n)}catch(t){if(e)throw N(n)?new Error("atob-Error in jsPDF.convertBase64ToBinaryString "+t.message):new Error("Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString ")}}return i};e.getImageProperties=function(t){var n,a,o="";if(d(t)&&(t=p(t)),"string"==typeof t&&i(t)===r&&(""===(o=k(t,!1))&&(o=e.loadFile(t)||""),t=o),a=i(t),!f(a))throw new Error("addImage does not support files of type '"+a+"', please ensure that a plugin for '"+a+"' support is added.");if(!A()||t instanceof Uint8Array||(t=S(t)),!(n=this["process"+a.toUpperCase()](t)))throw new Error("An unknown error occurred whilst processing the image");return n.fileType=a,n}}(E.API), -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(t){if(void 0!==t&&""!=t)return!0};E.API.events.push(["addPage",function(t){this.internal.getPageInfo(t.pageNumber).pageContext.annotations=[]}]),t.events.push(["putPage",function(t){for(var r,n,i,a=this.internal.getCoordinateString,o=this.internal.getVerticalCoordinateString,s=this.internal.getPageInfoByObjId(t.objId),c=t.pageContext.annotations,u=!1,h=0;h<c.length&&!u;h++)switch((r=c[h]).type){case"link":(e(r.options.url)||e(r.options.pageNumber))&&(u=!0);break;case"reference":case"text":case"freetext":u=!0}if(0!=u){this.internal.write("/Annots [");for(var l=0;l<c.length;l++){r=c[l];var f=this.internal.pdfEscape,d=this.internal.getEncryptor(t.objId);switch(r.type){case"reference":this.internal.write(" "+r.object.objId+" 0 R ");break;case"text":var p=this.internal.newAdditionalObject(),g=this.internal.newAdditionalObject(),m=this.internal.getEncryptor(p.objId),v=r.title||"Note";i="<</Type /Annot /Subtype /Text "+(n="/Rect ["+a(r.bounds.x)+" "+o(r.bounds.y+r.bounds.h)+" "+a(r.bounds.x+r.bounds.w)+" "+o(r.bounds.y)+"] ")+"/Contents ("+f(m(r.contents))+")",i+=" /Popup "+g.objId+" 0 R",i+=" /P "+s.objId+" 0 R",i+=" /T ("+f(m(v))+") >>",p.content=i;var b=p.objId+" 0 R";i="<</Type /Annot /Subtype /Popup "+(n="/Rect ["+a(r.bounds.x+30)+" "+o(r.bounds.y+r.bounds.h)+" "+a(r.bounds.x+r.bounds.w+30)+" "+o(r.bounds.y)+"] ")+" /Parent "+b,r.open&&(i+=" /Open true"),i+=" >>",g.content=i,this.internal.write(p.objId,"0 R",g.objId,"0 R");break;case"freetext":n="/Rect ["+a(r.bounds.x)+" "+o(r.bounds.y)+" "+a(r.bounds.x+r.bounds.w)+" "+o(r.bounds.y+r.bounds.h)+"] ";var y=r.color||"#000000";i="<</Type /Annot /Subtype /FreeText "+n+"/Contents ("+f(d(r.contents))+")",i+=" /DS(font: Helvetica,sans-serif 12.0pt; text-align:left; color:#"+y+")",i+=" /Border [0 0 0]",i+=" >>",this.internal.write(i);break;case"link":if(r.options.name){var w=this.annotations._nameMap[r.options.name];r.options.pageNumber=w.page,r.options.top=w.y}else r.options.top||(r.options.top=0);if(n="/Rect ["+r.finalBounds.x+" "+r.finalBounds.y+" "+r.finalBounds.w+" "+r.finalBounds.h+"] ",i="",r.options.url)i="<</Type /Annot /Subtype /Link "+n+"/Border [0 0 0] /A <</S /URI /URI ("+f(d(r.options.url))+") >>";else if(r.options.pageNumber){switch(i="<</Type /Annot /Subtype /Link "+n+"/Border [0 0 0] /Dest ["+this.internal.getPageInfo(r.options.pageNumber).objId+" 0 R",r.options.magFactor=r.options.magFactor||"XYZ",r.options.magFactor){case"Fit":i+=" /Fit]";break;case"FitH":i+=" /FitH "+r.options.top+"]";break;case"FitV":r.options.left=r.options.left||0,i+=" /FitV "+r.options.left+"]";break;case"XYZ":default:var N=o(r.options.top);r.options.left=r.options.left||0,void 0===r.options.zoom&&(r.options.zoom=0),i+=" /XYZ "+r.options.left+" "+N+" "+r.options.zoom+"]"}}""!=i&&(i+=" >>",this.internal.write(i))}}this.internal.write("]")}}]),t.createAnnotation=function(t){var e=this.internal.getCurrentPageInfo();switch(t.type){case"link":this.link(t.bounds.x,t.bounds.y,t.bounds.w,t.bounds.h,t);break;case"text":case"freetext":e.pageContext.annotations.push(t)}},t.link=function(t,e,r,n,i){var a=this.internal.getCurrentPageInfo(),o=this.internal.getCoordinateString,s=this.internal.getVerticalCoordinateString;a.pageContext.annotations.push({finalBounds:{x:o(t),y:s(e),w:o(t+r),h:s(e+n)},options:i,type:"link"})},t.textWithLink=function(t,e,r,n){var i,a,o=this.getTextWidth(t),s=this.internal.getLineHeight()/this.internal.scaleFactor;if(void 0!==n.maxWidth){a=n.maxWidth;var c=this.splitTextToSize(t,a).length;i=Math.ceil(s*c)}else a=o,i=s;return this.text(t,e,r,n),r+=.2*s,"center"===n.align&&(e-=o/2),"right"===n.align&&(e-=o),this.link(e,r-s,a,i,n),o},t.getTextWidth=function(t){var e=this.internal.getFontSize();return this.getStringUnitWidth(t)*e/this.internal.scaleFactor}}(E.API), -/** - * @license - * Copyright (c) 2017 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e={1569:[65152],1570:[65153,65154],1571:[65155,65156],1572:[65157,65158],1573:[65159,65160],1574:[65161,65162,65163,65164],1575:[65165,65166],1576:[65167,65168,65169,65170],1577:[65171,65172],1578:[65173,65174,65175,65176],1579:[65177,65178,65179,65180],1580:[65181,65182,65183,65184],1581:[65185,65186,65187,65188],1582:[65189,65190,65191,65192],1583:[65193,65194],1584:[65195,65196],1585:[65197,65198],1586:[65199,65200],1587:[65201,65202,65203,65204],1588:[65205,65206,65207,65208],1589:[65209,65210,65211,65212],1590:[65213,65214,65215,65216],1591:[65217,65218,65219,65220],1592:[65221,65222,65223,65224],1593:[65225,65226,65227,65228],1594:[65229,65230,65231,65232],1601:[65233,65234,65235,65236],1602:[65237,65238,65239,65240],1603:[65241,65242,65243,65244],1604:[65245,65246,65247,65248],1605:[65249,65250,65251,65252],1606:[65253,65254,65255,65256],1607:[65257,65258,65259,65260],1608:[65261,65262],1609:[65263,65264,64488,64489],1610:[65265,65266,65267,65268],1649:[64336,64337],1655:[64477],1657:[64358,64359,64360,64361],1658:[64350,64351,64352,64353],1659:[64338,64339,64340,64341],1662:[64342,64343,64344,64345],1663:[64354,64355,64356,64357],1664:[64346,64347,64348,64349],1667:[64374,64375,64376,64377],1668:[64370,64371,64372,64373],1670:[64378,64379,64380,64381],1671:[64382,64383,64384,64385],1672:[64392,64393],1676:[64388,64389],1677:[64386,64387],1678:[64390,64391],1681:[64396,64397],1688:[64394,64395],1700:[64362,64363,64364,64365],1702:[64366,64367,64368,64369],1705:[64398,64399,64400,64401],1709:[64467,64468,64469,64470],1711:[64402,64403,64404,64405],1713:[64410,64411,64412,64413],1715:[64406,64407,64408,64409],1722:[64414,64415],1723:[64416,64417,64418,64419],1726:[64426,64427,64428,64429],1728:[64420,64421],1729:[64422,64423,64424,64425],1733:[64480,64481],1734:[64473,64474],1735:[64471,64472],1736:[64475,64476],1737:[64482,64483],1739:[64478,64479],1740:[64508,64509,64510,64511],1744:[64484,64485,64486,64487],1746:[64430,64431],1747:[64432,64433]},r={65247:{65154:65269,65156:65271,65160:65273,65166:65275},65248:{65154:65270,65156:65272,65160:65274,65166:65276},65165:{65247:{65248:{65258:65010}}},1617:{1612:64606,1613:64607,1614:64608,1615:64609,1616:64610}},n={1612:64606,1613:64607,1614:64608,1615:64609,1616:64610},i=[1570,1571,1573,1575];t.__arabicParser__={};var a=t.__arabicParser__.isInArabicSubstitutionA=function(t){return void 0!==e[t.charCodeAt(0)]},o=t.__arabicParser__.isArabicLetter=function(t){return"string"==typeof t&&/^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test(t)},s=t.__arabicParser__.isArabicEndLetter=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length<=2},c=t.__arabicParser__.isArabicAlfLetter=function(t){return o(t)&&i.indexOf(t.charCodeAt(0))>=0};t.__arabicParser__.arabicLetterHasIsolatedForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=1};var u=t.__arabicParser__.arabicLetterHasFinalForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=2};t.__arabicParser__.arabicLetterHasInitialForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=3};var h=t.__arabicParser__.arabicLetterHasMedialForm=function(t){return o(t)&&a(t)&&4==e[t.charCodeAt(0)].length},l=t.__arabicParser__.resolveLigatures=function(t){var e=0,n=r,i="",a=0;for(e=0;e<t.length;e+=1)void 0!==n[t.charCodeAt(e)]?(a++,"number"==typeof(n=n[t.charCodeAt(e)])&&(i+=String.fromCharCode(n),n=r,a=0),e===t.length-1&&(n=r,i+=t.charAt(e-(a-1)),e-=a-1,a=0)):(n=r,i+=t.charAt(e-a),e-=a,a=0);return i};t.__arabicParser__.isArabicDiacritic=function(t){return void 0!==t&&void 0!==n[t.charCodeAt(0)]};var f=t.__arabicParser__.getCorrectForm=function(t,e,r){return o(t)?!1===a(t)?-1:!u(t)||!o(e)&&!o(r)||!o(r)&&s(e)||s(t)&&!o(e)||s(t)&&c(e)||s(t)&&s(e)?0:h(t)&&o(e)&&!s(e)&&o(r)&&u(r)?3:s(t)||!o(r)?1:2:-1},d=function(t){var r=0,n=0,i=0,a="",s="",c="",u=(t=t||"").split("\\s+"),h=[];for(r=0;r<u.length;r+=1){for(h.push(""),n=0;n<u[r].length;n+=1)a=u[r][n],s=u[r][n-1],c=u[r][n+1],o(a)?(i=f(a,s,c),h[r]+=-1!==i?String.fromCharCode(e[a.charCodeAt(0)][i]):a):h[r]+=a;h[r]=l(h[r])}return h.join(" ")},p=t.__arabicParser__.processArabic=t.processArabic=function(){var t,e="string"==typeof arguments[0]?arguments[0]:arguments[0].text,r=[];if(Array.isArray(e)){var n=0;for(r=[],n=0;n<e.length;n+=1)Array.isArray(e[n])?r.push([d(e[n][0]),e[n][1],e[n][2]]):r.push([d(e[n])]);t=r}else t=d(e);return"string"==typeof arguments[0]?t:(arguments[0].text=t,arguments[0])};t.events.push(["preProcessText",p])}(E.API),E.API.autoPrint=function(t){var e;switch((t=t||{}).variant=t.variant||"non-conform",t.variant){case"javascript":this.addJS("print({});");break;case"non-conform":default:this.internal.events.subscribe("postPutResources",(function(){e=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/S /Named"),this.internal.out("/Type /Action"),this.internal.out("/N /Print"),this.internal.out(">>"),this.internal.out("endobj")})),this.internal.events.subscribe("putCatalog",(function(){this.internal.out("/OpenAction "+e+" 0 R")}))}return this}, -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(){var t=void 0;Object.defineProperty(this,"pdf",{get:function(){return t},set:function(e){t=e}});var e=150;Object.defineProperty(this,"width",{get:function(){return e},set:function(t){e=isNaN(t)||!1===Number.isInteger(t)||t<0?150:t,this.getContext("2d").pageWrapXEnabled&&(this.getContext("2d").pageWrapX=e+1)}});var r=300;Object.defineProperty(this,"height",{get:function(){return r},set:function(t){r=isNaN(t)||!1===Number.isInteger(t)||t<0?300:t,this.getContext("2d").pageWrapYEnabled&&(this.getContext("2d").pageWrapY=r+1)}});var n=[];Object.defineProperty(this,"childNodes",{get:function(){return n},set:function(t){n=t}});var i={};Object.defineProperty(this,"style",{get:function(){return i},set:function(t){i=t}}),Object.defineProperty(this,"parentNode",{})};e.prototype.getContext=function(t,e){var r;if("2d"!==(t=t||"2d"))return null;for(r in e)this.pdf.context2d.hasOwnProperty(r)&&(this.pdf.context2d[r]=e[r]);return this.pdf.context2d._canvas=this,this.pdf.context2d},e.prototype.toDataURL=function(){throw new Error("toDataURL is not implemented.")},t.events.push(["initialized",function(){this.canvas=new e,this.canvas.pdf=this}])}(E.API),function(e){var r={left:0,top:0,bottom:0,right:0},n=!1,i=function(){void 0===this.internal.__cell__&&(this.internal.__cell__={},this.internal.__cell__.padding=3,this.internal.__cell__.headerFunction=void 0,this.internal.__cell__.margins=Object.assign({},r),this.internal.__cell__.margins.width=this.getPageWidth(),a.call(this))},a=function(){this.internal.__cell__.lastCell=new o,this.internal.__cell__.pages=1},o=function(){var t=arguments[0];Object.defineProperty(this,"x",{enumerable:!0,get:function(){return t},set:function(e){t=e}});var e=arguments[1];Object.defineProperty(this,"y",{enumerable:!0,get:function(){return e},set:function(t){e=t}});var r=arguments[2];Object.defineProperty(this,"width",{enumerable:!0,get:function(){return r},set:function(t){r=t}});var n=arguments[3];Object.defineProperty(this,"height",{enumerable:!0,get:function(){return n},set:function(t){n=t}});var i=arguments[4];Object.defineProperty(this,"text",{enumerable:!0,get:function(){return i},set:function(t){i=t}});var a=arguments[5];Object.defineProperty(this,"lineNumber",{enumerable:!0,get:function(){return a},set:function(t){a=t}});var o=arguments[6];return Object.defineProperty(this,"align",{enumerable:!0,get:function(){return o},set:function(t){o=t}}),this};o.prototype.clone=function(){return new o(this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align)},o.prototype.toArray=function(){return[this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align]},e.setHeaderFunction=function(t){return i.call(this),this.internal.__cell__.headerFunction="function"==typeof t?t:void 0,this},e.getTextDimensions=function(t,e){i.call(this);var r=(e=e||{}).fontSize||this.getFontSize(),n=e.font||this.getFont(),a=e.scaleFactor||this.internal.scaleFactor,o=0,s=0,c=0,u=this;if(!Array.isArray(t)&&"string"!=typeof t){if("number"!=typeof t)throw new Error("getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings.");t=String(t)}var h=e.maxWidth;h>0?"string"==typeof t?t=this.splitTextToSize(t,h):"[object Array]"===Object.prototype.toString.call(t)&&(t=t.reduce((function(t,e){return t.concat(u.splitTextToSize(e,h))}),[])):t=Array.isArray(t)?t:[t];for(var l=0;l<t.length;l++)o<(c=this.getStringUnitWidth(t[l],{font:n})*r)&&(o=c);return 0!==o&&(s=t.length),{w:o/=a,h:Math.max((s*r*this.getLineHeightFactor()-r*(this.getLineHeightFactor()-1))/a,0)}},e.cellAddPage=function(){i.call(this),this.addPage();var t=this.internal.__cell__.margins||r;return this.internal.__cell__.lastCell=new o(t.left,t.top,void 0,void 0),this.internal.__cell__.pages+=1,this};var s=e.cell=function(){var t;t=arguments[0]instanceof o?arguments[0]:new o(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]),i.call(this);var e=this.internal.__cell__.lastCell,a=this.internal.__cell__.padding,s=this.internal.__cell__.margins||r,c=this.internal.__cell__.tableHeaderRow,u=this.internal.__cell__.printHeaders;return void 0!==e.lineNumber&&(e.lineNumber===t.lineNumber?(t.x=(e.x||0)+(e.width||0),t.y=e.y||0):e.y+e.height+t.height+s.bottom>this.getPageHeight()?(this.cellAddPage(),t.y=s.top,u&&c&&(this.printHeaderRow(t.lineNumber,!0),t.y+=c[0].height)):t.y=e.y+e.height||t.y),void 0!==t.text[0]&&(this.rect(t.x,t.y,t.width,t.height,!0===n?"FD":void 0),"right"===t.align?this.text(t.text,t.x+t.width-a,t.y+a,{align:"right",baseline:"top"}):"center"===t.align?this.text(t.text,t.x+t.width/2,t.y+a,{align:"center",baseline:"top",maxWidth:t.width-a-a}):this.text(t.text,t.x+a,t.y+a,{align:"left",baseline:"top",maxWidth:t.width-a-a})),this.internal.__cell__.lastCell=t,this};e.table=function(e,n,u,h,l){if(i.call(this),!u)throw new Error("No data for PDF table.");var f,d,p,g,m=[],v=[],b=[],y={},w={},N=[],L=[],A=(l=l||{}).autoSize||!1,x=!1!==l.printHeaders,S=l.css&&void 0!==l.css["font-size"]?16*l.css["font-size"]:l.fontSize||12,_=l.margins||Object.assign({width:this.getPageWidth()},r),P="number"==typeof l.padding?l.padding:3,k=l.headerBackgroundColor||"#c8c8c8",I=l.headerTextColor||"#000";if(a.call(this),this.internal.__cell__.printHeaders=x,this.internal.__cell__.margins=_,this.internal.__cell__.table_font_size=S,this.internal.__cell__.padding=P,this.internal.__cell__.headerBackgroundColor=k,this.internal.__cell__.headerTextColor=I,this.setFontSize(S),null==h)v=m=Object.keys(u[0]),b=m.map((function(){return"left"}));else if(Array.isArray(h)&&"object"===t(h[0]))for(m=h.map((function(t){return t.name})),v=h.map((function(t){return t.prompt||t.name||""})),b=h.map((function(t){return t.align||"left"})),f=0;f<h.length;f+=1)w[h[f].name]=h[f].width*(19.049976/25.4);else Array.isArray(h)&&"string"==typeof h[0]&&(v=m=h,b=m.map((function(){return"left"})));if(A||Array.isArray(h)&&"string"==typeof h[0])for(f=0;f<m.length;f+=1){for(y[g=m[f]]=u.map((function(t){return t[g]})),this.setFont(void 0,"bold"),N.push(this.getTextDimensions(v[f],{fontSize:this.internal.__cell__.table_font_size,scaleFactor:this.internal.scaleFactor}).w),d=y[g],this.setFont(void 0,"normal"),p=0;p<d.length;p+=1)N.push(this.getTextDimensions(d[p],{fontSize:this.internal.__cell__.table_font_size,scaleFactor:this.internal.scaleFactor}).w);w[g]=Math.max.apply(null,N)+P+P,N=[]}if(x){var F={};for(f=0;f<m.length;f+=1)F[m[f]]={},F[m[f]].text=v[f],F[m[f]].align=b[f];var C=c.call(this,F,w);L=m.map((function(t){return new o(e,n,w[t],C,F[t].text,void 0,F[t].align)})),this.setTableHeaderRow(L),this.printHeaderRow(1,!1)}var j=h.reduce((function(t,e){return t[e.name]=e.align,t}),{});for(f=0;f<u.length;f+=1){"rowStart"in l&&l.rowStart instanceof Function&&l.rowStart({row:f,data:u[f]},this);var O=c.call(this,u[f],w);for(p=0;p<m.length;p+=1){var B=u[f][m[p]];"cellStart"in l&&l.cellStart instanceof Function&&l.cellStart({row:f,col:p,data:B},this),s.call(this,new o(e,n,w[m[p]],O,B,f+2,j[m[p]]))}}return this.internal.__cell__.table_x=e,this.internal.__cell__.table_y=n,this};var c=function(t,e){var r=this.internal.__cell__.padding,n=this.internal.__cell__.table_font_size,i=this.internal.scaleFactor;return Object.keys(t).map((function(n){var i=t[n];return this.splitTextToSize(i.hasOwnProperty("text")?i.text:i,e[n]-r-r)}),this).map((function(t){return this.getLineHeightFactor()*t.length*n/i+r+r}),this).reduce((function(t,e){return Math.max(t,e)}),0)};e.setTableHeaderRow=function(t){i.call(this),this.internal.__cell__.tableHeaderRow=t},e.printHeaderRow=function(t,e){if(i.call(this),!this.internal.__cell__.tableHeaderRow)throw new Error("Property tableHeaderRow does not exist.");var r;if(n=!0,"function"==typeof this.internal.__cell__.headerFunction){var a=this.internal.__cell__.headerFunction(this,this.internal.__cell__.pages);this.internal.__cell__.lastCell=new o(a[0],a[1],a[2],a[3],void 0,-1)}this.setFont(void 0,"bold");for(var c=[],u=0;u<this.internal.__cell__.tableHeaderRow.length;u+=1){r=this.internal.__cell__.tableHeaderRow[u].clone(),e&&(r.y=this.internal.__cell__.margins.top||0,c.push(r)),r.lineNumber=t;var h=this.getTextColor();this.setTextColor(this.internal.__cell__.headerTextColor),this.setFillColor(this.internal.__cell__.headerBackgroundColor),s.call(this,r),this.setTextColor(h)}c.length>0&&this.setTableHeaderRow(c),this.setFont(void 0,"normal"),n=!1}}(E.API);var Pt={italic:["italic","oblique","normal"],oblique:["oblique","italic","normal"],normal:["normal","oblique","italic"]},kt=["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded"],It=_t(kt),Ft=[100,200,300,400,500,600,700,800,900],Ct=_t(Ft);function jt(t){var e=t.family.replace(/"|'/g,"").toLowerCase(),r=function(t){return Pt[t=t||"normal"]?t:"normal"}(t.style),n=function(t){if(!t)return 400;if("number"==typeof t)return t>=100&&t<=900&&t%100==0?t:400;if(/^\d00$/.test(t))return parseInt(t);switch(t){case"bold":return 700;case"normal":default:return 400}}(t.weight),i=function(t){return"number"==typeof It[t=t||"normal"]?t:"normal"}(t.stretch);return{family:e,style:r,weight:n,stretch:i,src:t.src||[],ref:t.ref||{name:e,style:[i,r,n].join(" ")}}}function Ot(t,e,r,n){var i;for(i=r;i>=0&&i<e.length;i+=n)if(t[e[i]])return t[e[i]];for(i=r;i>=0&&i<e.length;i-=n)if(t[e[i]])return t[e[i]]}var Bt={"sans-serif":"helvetica",fixed:"courier",monospace:"courier",terminal:"courier",cursive:"times",fantasy:"times",serif:"times"},Mt={caption:"times",icon:"times",menu:"times","message-box":"times","small-caption":"times","status-bar":"times"};function Et(t){return[t.stretch,t.style,t.weight,t.family].join(" ")}function qt(t,e,r){for(var n=(r=r||{}).defaultFontFamily||"times",i=Object.assign({},Bt,r.genericFontFamilies||{}),a=null,o=null,s=0;s<e.length;++s)if(i[(a=jt(e[s])).family]&&(a.family=i[a.family]),t.hasOwnProperty(a.family)){o=t[a.family];break}if(!(o=o||t[n]))throw new Error("Could not find a font-family for the rule '"+Et(a)+"' and default family '"+n+"'.");if(o=function(t,e){if(e[t])return e[t];var r=It[t],n=r<=It.normal?-1:1,i=Ot(e,kt,r,n);if(!i)throw new Error("Could not find a matching font-stretch value for "+t);return i}(a.stretch,o),o=function(t,e){if(e[t])return e[t];for(var r=Pt[t],n=0;n<r.length;++n)if(e[r[n]])return e[r[n]];throw new Error("Could not find a matching font-style for "+t)}(a.style,o),!(o=function(t,e){if(e[t])return e[t];if(400===t&&e[500])return e[500];if(500===t&&e[400])return e[400];var r=Ct[t],n=Ot(e,Ft,r,t<400?-1:1);if(!n)throw new Error("Could not find a matching font-weight for value "+t);return n}(a.weight,o)))throw new Error("Failed to resolve a font for the rule '"+Et(a)+"'.");return o}function Dt(t){return t.trimLeft()}function Rt(t,e){for(var r=0;r<t.length;){if(t.charAt(r)===e)return[t.substring(0,r),t.substring(r+1)];r+=1}return null}function Tt(t){var e=t.match(/^(-[a-z_]|[a-z_])[a-z0-9_-]*/i);return null===e?null:[e[0],t.substring(e[0].length)]}var Ut,zt,Ht,Wt=["times"];!function(e){var r,n,i,o,s,c,u,h,l,d=function(t){return t=t||{},this.isStrokeTransparent=t.isStrokeTransparent||!1,this.strokeOpacity=t.strokeOpacity||1,this.strokeStyle=t.strokeStyle||"#000000",this.fillStyle=t.fillStyle||"#000000",this.isFillTransparent=t.isFillTransparent||!1,this.fillOpacity=t.fillOpacity||1,this.font=t.font||"10px sans-serif",this.textBaseline=t.textBaseline||"alphabetic",this.textAlign=t.textAlign||"left",this.lineWidth=t.lineWidth||1,this.lineJoin=t.lineJoin||"miter",this.lineCap=t.lineCap||"butt",this.path=t.path||[],this.transform=void 0!==t.transform?t.transform.clone():new h,this.globalCompositeOperation=t.globalCompositeOperation||"normal",this.globalAlpha=t.globalAlpha||1,this.clip_path=t.clip_path||[],this.currentPoint=t.currentPoint||new c,this.miterLimit=t.miterLimit||10,this.lastPoint=t.lastPoint||new c,this.lineDashOffset=t.lineDashOffset||0,this.lineDash=t.lineDash||[],this.margin=t.margin||[0,0,0,0],this.prevPageLastElemOffset=t.prevPageLastElemOffset||0,this.ignoreClearRect="boolean"!=typeof t.ignoreClearRect||t.ignoreClearRect,this};e.events.push(["initialized",function(){this.context2d=new p(this),r=this.internal.f2,n=this.internal.getCoordinateString,i=this.internal.getVerticalCoordinateString,o=this.internal.getHorizontalCoordinate,s=this.internal.getVerticalCoordinate,c=this.internal.Point,u=this.internal.Rectangle,h=this.internal.Matrix,l=new d}]);var p=function(t){Object.defineProperty(this,"canvas",{get:function(){return{parentNode:!1,style:!1}}});var e=t;Object.defineProperty(this,"pdf",{get:function(){return e}});var r=!1;Object.defineProperty(this,"pageWrapXEnabled",{get:function(){return r},set:function(t){r=Boolean(t)}});var n=!1;Object.defineProperty(this,"pageWrapYEnabled",{get:function(){return n},set:function(t){n=Boolean(t)}});var i=0;Object.defineProperty(this,"posX",{get:function(){return i},set:function(t){isNaN(t)||(i=t)}});var a=0;Object.defineProperty(this,"posY",{get:function(){return a},set:function(t){isNaN(t)||(a=t)}}),Object.defineProperty(this,"margin",{get:function(){return l.margin},set:function(t){var e;"number"==typeof t?e=[t,t,t,t]:((e=new Array(4))[0]=t[0],e[1]=t.length>=2?t[1]:e[0],e[2]=t.length>=3?t[2]:e[0],e[3]=t.length>=4?t[3]:e[1]),l.margin=e}});var o=!1;Object.defineProperty(this,"autoPaging",{get:function(){return o},set:function(t){o=t}});var s=0;Object.defineProperty(this,"lastBreak",{get:function(){return s},set:function(t){s=t}});var c=[];Object.defineProperty(this,"pageBreaks",{get:function(){return c},set:function(t){c=t}}),Object.defineProperty(this,"ctx",{get:function(){return l},set:function(t){t instanceof d&&(l=t)}}),Object.defineProperty(this,"path",{get:function(){return l.path},set:function(t){l.path=t}});var u=[];Object.defineProperty(this,"ctxStack",{get:function(){return u},set:function(t){u=t}}),Object.defineProperty(this,"fillStyle",{get:function(){return this.ctx.fillStyle},set:function(t){var e;e=g(t),this.ctx.fillStyle=e.style,this.ctx.isFillTransparent=0===e.a,this.ctx.fillOpacity=e.a,this.pdf.setFillColor(e.r,e.g,e.b,{a:e.a}),this.pdf.setTextColor(e.r,e.g,e.b,{a:e.a})}}),Object.defineProperty(this,"strokeStyle",{get:function(){return this.ctx.strokeStyle},set:function(t){var e=g(t);this.ctx.strokeStyle=e.style,this.ctx.isStrokeTransparent=0===e.a,this.ctx.strokeOpacity=e.a,0===e.a?this.pdf.setDrawColor(255,255,255):(e.a,this.pdf.setDrawColor(e.r,e.g,e.b))}}),Object.defineProperty(this,"lineCap",{get:function(){return this.ctx.lineCap},set:function(t){-1!==["butt","round","square"].indexOf(t)&&(this.ctx.lineCap=t,this.pdf.setLineCap(t))}}),Object.defineProperty(this,"lineWidth",{get:function(){return this.ctx.lineWidth},set:function(t){isNaN(t)||(this.ctx.lineWidth=t,this.pdf.setLineWidth(t))}}),Object.defineProperty(this,"lineJoin",{get:function(){return this.ctx.lineJoin},set:function(t){-1!==["bevel","round","miter"].indexOf(t)&&(this.ctx.lineJoin=t,this.pdf.setLineJoin(t))}}),Object.defineProperty(this,"miterLimit",{get:function(){return this.ctx.miterLimit},set:function(t){isNaN(t)||(this.ctx.miterLimit=t,this.pdf.setMiterLimit(t))}}),Object.defineProperty(this,"textBaseline",{get:function(){return this.ctx.textBaseline},set:function(t){this.ctx.textBaseline=t}}),Object.defineProperty(this,"textAlign",{get:function(){return this.ctx.textAlign},set:function(t){-1!==["right","end","center","left","start"].indexOf(t)&&(this.ctx.textAlign=t)}});var h=null;function f(t,e){if(null===h){var r=function(t){var e=[];return Object.keys(t).forEach((function(r){t[r].forEach((function(t){var n=null;switch(t){case"bold":n={family:r,weight:"bold"};break;case"italic":n={family:r,style:"italic"};break;case"bolditalic":n={family:r,weight:"bold",style:"italic"};break;case"":case"normal":n={family:r}}null!==n&&(n.ref={name:r,style:t},e.push(n))}))})),e}(t.getFontList());h=function(t){for(var e={},r=0;r<t.length;++r){var n=jt(t[r]),i=n.family,a=n.stretch,o=n.style,s=n.weight;e[i]=e[i]||{},e[i][a]=e[i][a]||{},e[i][a][o]=e[i][a][o]||{},e[i][a][o][s]=n}return e}(r.concat(e))}return h}var p=null;Object.defineProperty(this,"fontFaces",{get:function(){return p},set:function(t){h=null,p=t}}),Object.defineProperty(this,"font",{get:function(){return this.ctx.font},set:function(t){var e;if(this.ctx.font=t,null!==(e=/^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z]+?)\s*$/i.exec(t))){var r=e[1],n=(e[2],e[3]),i=e[4],a=(e[5],e[6]),o=/^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i.exec(i)[2];i="px"===o?Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor):"em"===o?Math.floor(parseFloat(i)*this.pdf.getFontSize()):Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor),this.pdf.setFontSize(i);var s=function(t){var e,r,n=[],i=t.trim();if(""===i)return Wt;if(i in Mt)return[Mt[i]];for(;""!==i;){switch(r=null,e=(i=Dt(i)).charAt(0)){case'"':case"'":r=Rt(i.substring(1),e);break;default:r=Tt(i)}if(null===r)return Wt;if(n.push(r[0]),""!==(i=Dt(r[1]))&&","!==i.charAt(0))return Wt;i=i.replace(/^,/,"")}return n}(a);if(this.fontFaces){var c=qt(f(this.pdf,this.fontFaces),s.map((function(t){return{family:t,stretch:"normal",weight:n,style:r}})));this.pdf.setFont(c.ref.name,c.ref.style)}else{var u="";("bold"===n||parseInt(n,10)>=700||"bold"===r)&&(u="bold"),"italic"===r&&(u+="italic"),0===u.length&&(u="normal");for(var h="",l={arial:"Helvetica",Arial:"Helvetica",verdana:"Helvetica",Verdana:"Helvetica",helvetica:"Helvetica",Helvetica:"Helvetica","sans-serif":"Helvetica",fixed:"Courier",monospace:"Courier",terminal:"Courier",cursive:"Times",fantasy:"Times",serif:"Times"},d=0;d<s.length;d++){if(void 0!==this.pdf.internal.getFont(s[d],u,{noFallback:!0,disableWarning:!0})){h=s[d];break}if("bolditalic"===u&&void 0!==this.pdf.internal.getFont(s[d],"bold",{noFallback:!0,disableWarning:!0}))h=s[d],u="bold";else if(void 0!==this.pdf.internal.getFont(s[d],"normal",{noFallback:!0,disableWarning:!0})){h=s[d],u="normal";break}}if(""===h)for(var p=0;p<s.length;p++)if(l[s[p]]){h=l[s[p]];break}h=""===h?"Times":h,this.pdf.setFont(h,u)}}}}),Object.defineProperty(this,"globalCompositeOperation",{get:function(){return this.ctx.globalCompositeOperation},set:function(t){this.ctx.globalCompositeOperation=t}}),Object.defineProperty(this,"globalAlpha",{get:function(){return this.ctx.globalAlpha},set:function(t){this.ctx.globalAlpha=t}}),Object.defineProperty(this,"lineDashOffset",{get:function(){return this.ctx.lineDashOffset},set:function(t){this.ctx.lineDashOffset=t,T.call(this)}}),Object.defineProperty(this,"lineDash",{get:function(){return this.ctx.lineDash},set:function(t){this.ctx.lineDash=t,T.call(this)}}),Object.defineProperty(this,"ignoreClearRect",{get:function(){return this.ctx.ignoreClearRect},set:function(t){this.ctx.ignoreClearRect=Boolean(t)}})};p.prototype.setLineDash=function(t){this.lineDash=t},p.prototype.getLineDash=function(){return this.lineDash.length%2?this.lineDash.concat(this.lineDash):this.lineDash.slice()},p.prototype.fill=function(){A.call(this,"fill",!1)},p.prototype.stroke=function(){A.call(this,"stroke",!1)},p.prototype.beginPath=function(){this.path=[{type:"begin"}]},p.prototype.moveTo=function(t,e){if(isNaN(t)||isNaN(e))throw a.error("jsPDF.context2d.moveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.moveTo");var r=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"mt",x:r.x,y:r.y}),this.ctx.lastPoint=new c(t,e)},p.prototype.closePath=function(){var e=new c(0,0),r=0;for(r=this.path.length-1;-1!==r;r--)if("begin"===this.path[r].type&&"object"===t(this.path[r+1])&&"number"==typeof this.path[r+1].x){e=new c(this.path[r+1].x,this.path[r+1].y);break}this.path.push({type:"close"}),this.ctx.lastPoint=new c(e.x,e.y)},p.prototype.lineTo=function(t,e){if(isNaN(t)||isNaN(e))throw a.error("jsPDF.context2d.lineTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.lineTo");var r=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"lt",x:r.x,y:r.y}),this.ctx.lastPoint=new c(r.x,r.y)},p.prototype.clip=function(){this.ctx.clip_path=JSON.parse(JSON.stringify(this.path)),A.call(this,null,!0)},p.prototype.quadraticCurveTo=function(t,e,r,n){if(isNaN(r)||isNaN(n)||isNaN(t)||isNaN(e))throw a.error("jsPDF.context2d.quadraticCurveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.quadraticCurveTo");var i=this.ctx.transform.applyToPoint(new c(r,n)),o=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"qct",x1:o.x,y1:o.y,x:i.x,y:i.y}),this.ctx.lastPoint=new c(i.x,i.y)},p.prototype.bezierCurveTo=function(t,e,r,n,i,o){if(isNaN(i)||isNaN(o)||isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw a.error("jsPDF.context2d.bezierCurveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.bezierCurveTo");var s=this.ctx.transform.applyToPoint(new c(i,o)),u=this.ctx.transform.applyToPoint(new c(t,e)),h=this.ctx.transform.applyToPoint(new c(r,n));this.path.push({type:"bct",x1:u.x,y1:u.y,x2:h.x,y2:h.y,x:s.x,y:s.y}),this.ctx.lastPoint=new c(s.x,s.y)},p.prototype.arc=function(t,e,r,n,i,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i))throw a.error("jsPDF.context2d.arc: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.arc");if(o=Boolean(o),!this.ctx.transform.isIdentity){var s=this.ctx.transform.applyToPoint(new c(t,e));t=s.x,e=s.y;var u=this.ctx.transform.applyToPoint(new c(0,r)),h=this.ctx.transform.applyToPoint(new c(0,0));r=Math.sqrt(Math.pow(u.x-h.x,2)+Math.pow(u.y-h.y,2))}Math.abs(i-n)>=2*Math.PI&&(n=0,i=2*Math.PI),this.path.push({type:"arc",x:t,y:e,radius:r,startAngle:n,endAngle:i,counterclockwise:o})},p.prototype.arcTo=function(t,e,r,n,i){throw new Error("arcTo not implemented.")},p.prototype.rect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw a.error("jsPDF.context2d.rect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.rect");this.moveTo(t,e),this.lineTo(t+r,e),this.lineTo(t+r,e+n),this.lineTo(t,e+n),this.lineTo(t,e),this.lineTo(t+r,e),this.lineTo(t,e)},p.prototype.fillRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw a.error("jsPDF.context2d.fillRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.fillRect");if(!m.call(this)){var i={};"butt"!==this.lineCap&&(i.lineCap=this.lineCap,this.lineCap="butt"),"miter"!==this.lineJoin&&(i.lineJoin=this.lineJoin,this.lineJoin="miter"),this.beginPath(),this.rect(t,e,r,n),this.fill(),i.hasOwnProperty("lineCap")&&(this.lineCap=i.lineCap),i.hasOwnProperty("lineJoin")&&(this.lineJoin=i.lineJoin)}},p.prototype.strokeRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw a.error("jsPDF.context2d.strokeRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.strokeRect");v.call(this)||(this.beginPath(),this.rect(t,e,r,n),this.stroke())},p.prototype.clearRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw a.error("jsPDF.context2d.clearRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.clearRect");this.ignoreClearRect||(this.fillStyle="#ffffff",this.fillRect(t,e,r,n))},p.prototype.save=function(t){t="boolean"!=typeof t||t;for(var e=this.pdf.internal.getCurrentPageInfo().pageNumber,r=0;r<this.pdf.internal.getNumberOfPages();r++)this.pdf.setPage(r+1),this.pdf.internal.out("q");if(this.pdf.setPage(e),t){this.ctx.fontSize=this.pdf.internal.getFontSize();var n=new d(this.ctx);this.ctxStack.push(this.ctx),this.ctx=n}},p.prototype.restore=function(t){t="boolean"!=typeof t||t;for(var e=this.pdf.internal.getCurrentPageInfo().pageNumber,r=0;r<this.pdf.internal.getNumberOfPages();r++)this.pdf.setPage(r+1),this.pdf.internal.out("Q");this.pdf.setPage(e),t&&0!==this.ctxStack.length&&(this.ctx=this.ctxStack.pop(),this.fillStyle=this.ctx.fillStyle,this.strokeStyle=this.ctx.strokeStyle,this.font=this.ctx.font,this.lineCap=this.ctx.lineCap,this.lineWidth=this.ctx.lineWidth,this.lineJoin=this.ctx.lineJoin,this.lineDash=this.ctx.lineDash,this.lineDashOffset=this.ctx.lineDashOffset)},p.prototype.toDataURL=function(){throw new Error("toDataUrl not implemented.")};var g=function(t){var e,r,n,i;if(!0===t.isCanvasGradient&&(t=t.getColor()),!t)return{r:0,g:0,b:0,a:0,style:t};if(/transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/.test(t))e=0,r=0,n=0,i=0;else{var a=/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(t);if(null!==a)e=parseInt(a[1]),r=parseInt(a[2]),n=parseInt(a[3]),i=1;else if(null!==(a=/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/.exec(t)))e=parseInt(a[1]),r=parseInt(a[2]),n=parseInt(a[3]),i=parseFloat(a[4]);else{if(i=1,"string"==typeof t&&"#"!==t.charAt(0)){var o=new f(t);t=o.ok?o.toHex():"#000000"}4===t.length?(e=t.substring(1,2),e+=e,r=t.substring(2,3),r+=r,n=t.substring(3,4),n+=n):(e=t.substring(1,3),r=t.substring(3,5),n=t.substring(5,7)),e=parseInt(e,16),r=parseInt(r,16),n=parseInt(n,16)}}return{r:e,g:r,b:n,a:i,style:t}},m=function(){return this.ctx.isFillTransparent||0==this.globalAlpha},v=function(){return Boolean(this.ctx.isStrokeTransparent||0==this.globalAlpha)};p.prototype.fillText=function(t,e,r,n){if(isNaN(e)||isNaN(r)||"string"!=typeof t)throw a.error("jsPDF.context2d.fillText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.fillText");if(n=isNaN(n)?void 0:n,!m.call(this)){var i=q(this.ctx.transform.rotation),o=this.ctx.transform.scaleX;C.call(this,{text:t,x:e,y:r,scale:o,angle:i,align:this.textAlign,maxWidth:n})}},p.prototype.strokeText=function(t,e,r,n){if(isNaN(e)||isNaN(r)||"string"!=typeof t)throw a.error("jsPDF.context2d.strokeText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.strokeText");if(!v.call(this)){n=isNaN(n)?void 0:n;var i=q(this.ctx.transform.rotation),o=this.ctx.transform.scaleX;C.call(this,{text:t,x:e,y:r,scale:o,renderingMode:"stroke",angle:i,align:this.textAlign,maxWidth:n})}},p.prototype.measureText=function(t){if("string"!=typeof t)throw a.error("jsPDF.context2d.measureText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.measureText");var e=this.pdf,r=this.pdf.internal.scaleFactor,n=e.internal.getFontSize(),i=e.getStringUnitWidth(t)*n/e.internal.scaleFactor,o=function(t){var e=(t=t||{}).width||0;return Object.defineProperty(this,"width",{get:function(){return e}}),this};return new o({width:i*=Math.round(96*r/72*1e4)/1e4})},p.prototype.scale=function(t,e){if(isNaN(t)||isNaN(e))throw a.error("jsPDF.context2d.scale: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.scale");var r=new h(t,0,0,e,0,0);this.ctx.transform=this.ctx.transform.multiply(r)},p.prototype.rotate=function(t){if(isNaN(t))throw a.error("jsPDF.context2d.rotate: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.rotate");var e=new h(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0);this.ctx.transform=this.ctx.transform.multiply(e)},p.prototype.translate=function(t,e){if(isNaN(t)||isNaN(e))throw a.error("jsPDF.context2d.translate: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.translate");var r=new h(1,0,0,1,t,e);this.ctx.transform=this.ctx.transform.multiply(r)},p.prototype.transform=function(t,e,r,n,i,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i)||isNaN(o))throw a.error("jsPDF.context2d.transform: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.transform");var s=new h(t,e,r,n,i,o);this.ctx.transform=this.ctx.transform.multiply(s)},p.prototype.setTransform=function(t,e,r,n,i,a){t=isNaN(t)?1:t,e=isNaN(e)?0:e,r=isNaN(r)?0:r,n=isNaN(n)?1:n,i=isNaN(i)?0:i,a=isNaN(a)?0:a,this.ctx.transform=new h(t,e,r,n,i,a)};var b=function(){return this.margin[0]>0||this.margin[1]>0||this.margin[2]>0||this.margin[3]>0};p.prototype.drawImage=function(t,e,r,n,i,a,o,s,c){var l=this.pdf.getImageProperties(t),f=1,d=1,p=1,g=1;void 0!==n&&void 0!==s&&(p=s/n,g=c/i,f=l.width/n*s/n,d=l.height/i*c/i),void 0===a&&(a=e,o=r,e=0,r=0),void 0!==n&&void 0===s&&(s=n,c=i),void 0===n&&void 0===s&&(s=l.width,c=l.height);for(var m,v=this.ctx.transform.decompose(),w=q(v.rotate.shx),A=new h,S=(A=(A=(A=A.multiply(v.translate)).multiply(v.skew)).multiply(v.scale)).applyToRectangle(new u(a-e*p,o-r*g,n*f,i*d)),_=y.call(this,S),P=[],k=0;k<_.length;k+=1)-1===P.indexOf(_[k])&&P.push(_[k]);if(L(P),this.autoPaging)for(var I=P[0],F=P[P.length-1],C=I;C<F+1;C++){this.pdf.setPage(C);var j=this.pdf.internal.pageSize.width-this.margin[3]-this.margin[1],O=1===C?this.posY+this.margin[0]:this.margin[0],B=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],M=this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2],E=1===C?0:B+(C-2)*M;if(0!==this.ctx.clip_path.length){var D=this.path;m=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(m,this.posX+this.margin[3],-E+O+this.ctx.prevPageLastElemOffset),x.call(this,"fill",!0),this.path=D}var R=JSON.parse(JSON.stringify(S));R=N([R],this.posX+this.margin[3],-E+O+this.ctx.prevPageLastElemOffset)[0];var T=(C>I||C<F)&&b.call(this);T&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],j,M,null).clip().discardPath()),this.pdf.addImage(t,"JPEG",R.x,R.y,R.w,R.h,null,null,w),T&&this.pdf.restoreGraphicsState()}else this.pdf.addImage(t,"JPEG",S.x,S.y,S.w,S.h,null,null,w)};var y=function(t,e,r){var n=[];e=e||this.pdf.internal.pageSize.width,r=r||this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2];var i=this.posY+this.ctx.prevPageLastElemOffset;switch(t.type){default:case"mt":case"lt":n.push(Math.floor((t.y+i)/r)+1);break;case"arc":n.push(Math.floor((t.y+i-t.radius)/r)+1),n.push(Math.floor((t.y+i+t.radius)/r)+1);break;case"qct":var a=D(this.ctx.lastPoint.x,this.ctx.lastPoint.y,t.x1,t.y1,t.x,t.y);n.push(Math.floor((a.y+i)/r)+1),n.push(Math.floor((a.y+a.h+i)/r)+1);break;case"bct":var o=R(this.ctx.lastPoint.x,this.ctx.lastPoint.y,t.x1,t.y1,t.x2,t.y2,t.x,t.y);n.push(Math.floor((o.y+i)/r)+1),n.push(Math.floor((o.y+o.h+i)/r)+1);break;case"rect":n.push(Math.floor((t.y+i)/r)+1),n.push(Math.floor((t.y+t.h+i)/r)+1)}for(var s=0;s<n.length;s+=1)for(;this.pdf.internal.getNumberOfPages()<n[s];)w.call(this);return n},w=function(){var t=this.fillStyle,e=this.strokeStyle,r=this.font,n=this.lineCap,i=this.lineWidth,a=this.lineJoin;this.pdf.addPage(),this.fillStyle=t,this.strokeStyle=e,this.font=r,this.lineCap=n,this.lineWidth=i,this.lineJoin=a},N=function(t,e,r){for(var n=0;n<t.length;n++)switch(t[n].type){case"bct":t[n].x2+=e,t[n].y2+=r;case"qct":t[n].x1+=e,t[n].y1+=r;case"mt":case"lt":case"arc":default:t[n].x+=e,t[n].y+=r}return t},L=function(t){return t.sort((function(t,e){return t-e}))},A=function(t,e){for(var r,n,i=this.fillStyle,a=this.strokeStyle,o=this.lineCap,s=this.lineWidth,c=Math.abs(s*this.ctx.transform.scaleX),u=this.lineJoin,h=JSON.parse(JSON.stringify(this.path)),l=JSON.parse(JSON.stringify(this.path)),f=[],d=0;d<l.length;d++)if(void 0!==l[d].x)for(var p=y.call(this,l[d]),g=0;g<p.length;g+=1)-1===f.indexOf(p[g])&&f.push(p[g]);for(var m=0;m<f.length;m++)for(;this.pdf.internal.getNumberOfPages()<f[m];)w.call(this);if(L(f),this.autoPaging)for(var v=f[0],A=f[f.length-1],S=v;S<A+1;S++){this.pdf.setPage(S),this.fillStyle=i,this.strokeStyle=a,this.lineCap=o,this.lineWidth=c,this.lineJoin=u;var _=this.pdf.internal.pageSize.width-this.margin[3]-this.margin[1],P=1===S?this.posY+this.margin[0]:this.margin[0],k=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],I=this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2],F=1===S?0:k+(S-2)*I;if(0!==this.ctx.clip_path.length){var C=this.path;r=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(r,this.posX+this.margin[3],-F+P+this.ctx.prevPageLastElemOffset),x.call(this,t,!0),this.path=C}if(n=JSON.parse(JSON.stringify(h)),this.path=N(n,this.posX+this.margin[3],-F+P+this.ctx.prevPageLastElemOffset),!1===e||0===S){var j=(S>v||S<A)&&b.call(this);j&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],_,I,null).clip().discardPath()),x.call(this,t,e),j&&this.pdf.restoreGraphicsState()}this.lineWidth=s}else this.lineWidth=c,x.call(this,t,e),this.lineWidth=s;this.path=h},x=function(t,e){if(("stroke"!==t||e||!v.call(this))&&("stroke"===t||e||!m.call(this))){for(var r,n,i=[],a=this.path,o=0;o<a.length;o++){var s=a[o];switch(s.type){case"begin":i.push({begin:!0});break;case"close":i.push({close:!0});break;case"mt":i.push({start:s,deltas:[],abs:[]});break;case"lt":var c=i.length;if(a[o-1]&&!isNaN(a[o-1].x)&&(r=[s.x-a[o-1].x,s.y-a[o-1].y],c>0))for(;c>=0;c--)if(!0!==i[c-1].close&&!0!==i[c-1].begin){i[c-1].deltas.push(r),i[c-1].abs.push(s);break}break;case"bct":r=[s.x1-a[o-1].x,s.y1-a[o-1].y,s.x2-a[o-1].x,s.y2-a[o-1].y,s.x-a[o-1].x,s.y-a[o-1].y],i[i.length-1].deltas.push(r);break;case"qct":var u=a[o-1].x+2/3*(s.x1-a[o-1].x),h=a[o-1].y+2/3*(s.y1-a[o-1].y),l=s.x+2/3*(s.x1-s.x),f=s.y+2/3*(s.y1-s.y),d=s.x,p=s.y;r=[u-a[o-1].x,h-a[o-1].y,l-a[o-1].x,f-a[o-1].y,d-a[o-1].x,p-a[o-1].y],i[i.length-1].deltas.push(r);break;case"arc":i.push({deltas:[],abs:[],arc:!0}),Array.isArray(i[i.length-1].abs)&&i[i.length-1].abs.push(s)}}n=e?null:"stroke"===t?"stroke":"fill";for(var g=!1,b=0;b<i.length;b++)if(i[b].arc)for(var y=i[b].abs,w=0;w<y.length;w++){var N=y[w];"arc"===N.type?P.call(this,N.x,N.y,N.radius,N.startAngle,N.endAngle,N.counterclockwise,void 0,e,!g):j.call(this,N.x,N.y),g=!0}else if(!0===i[b].close)this.pdf.internal.out("h"),g=!1;else if(!0!==i[b].begin){var L=i[b].start.x,A=i[b].start.y;O.call(this,i[b].deltas,L,A),g=!0}n&&k.call(this,n),e&&I.call(this)}},S=function(t){var e=this.pdf.internal.getFontSize()/this.pdf.internal.scaleFactor,r=e*(this.pdf.internal.getLineHeightFactor()-1);switch(this.ctx.textBaseline){case"bottom":return t-r;case"top":return t+e-r;case"hanging":return t+e-2*r;case"middle":return t+e/2-r;case"ideographic":return t;case"alphabetic":default:return t}},_=function(t){return t+this.pdf.internal.getFontSize()/this.pdf.internal.scaleFactor*(this.pdf.internal.getLineHeightFactor()-1)};p.prototype.createLinearGradient=function(){var t=function(){};return t.colorStops=[],t.addColorStop=function(t,e){this.colorStops.push([t,e])},t.getColor=function(){return 0===this.colorStops.length?"#000000":this.colorStops[0][1]},t.isCanvasGradient=!0,t},p.prototype.createPattern=function(){return this.createLinearGradient()},p.prototype.createRadialGradient=function(){return this.createLinearGradient()};var P=function(t,e,r,n,i,a,o,s,c){for(var u=M.call(this,r,n,i,a),h=0;h<u.length;h++){var l=u[h];0===h&&(c?F.call(this,l.x1+t,l.y1+e):j.call(this,l.x1+t,l.y1+e)),B.call(this,t,e,l.x2,l.y2,l.x3,l.y3,l.x4,l.y4)}s?I.call(this):k.call(this,o)},k=function(t){switch(t){case"stroke":this.pdf.internal.out("S");break;case"fill":this.pdf.internal.out("f")}},I=function(){this.pdf.clip(),this.pdf.discardPath()},F=function(t,e){this.pdf.internal.out(n(t)+" "+i(e)+" m")},C=function(t){var e;switch(t.align){case"right":case"end":e="right";break;case"center":e="center";break;case"left":case"start":default:e="left"}var r=this.pdf.getTextDimensions(t.text),n=S.call(this,t.y),i=_.call(this,n)-r.h,a=this.ctx.transform.applyToPoint(new c(t.x,n)),o=this.ctx.transform.decompose(),s=new h;s=(s=(s=s.multiply(o.translate)).multiply(o.skew)).multiply(o.scale);for(var l,f,d,p=this.ctx.transform.applyToRectangle(new u(t.x,n,r.w,r.h)),g=s.applyToRectangle(new u(t.x,i,r.w,r.h)),m=y.call(this,g),v=[],w=0;w<m.length;w+=1)-1===v.indexOf(m[w])&&v.push(m[w]);if(L(v),this.autoPaging)for(var A=v[0],P=v[v.length-1],k=A;k<P+1;k++){this.pdf.setPage(k);var I=1===k?this.posY+this.margin[0]:this.margin[0],F=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],C=this.pdf.internal.pageSize.height-this.margin[2],j=C-this.margin[0],O=this.pdf.internal.pageSize.width-this.margin[1],B=O-this.margin[3],M=1===k?0:F+(k-2)*j;if(0!==this.ctx.clip_path.length){var E=this.path;l=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(l,this.posX+this.margin[3],-1*M+I),x.call(this,"fill",!0),this.path=E}var q=N([JSON.parse(JSON.stringify(g))],this.posX+this.margin[3],-M+I+this.ctx.prevPageLastElemOffset)[0];t.scale>=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale);var D="text"!==this.autoPaging;if(D||q.y+q.h<=C){if(D||q.y>=I&&q.x<=O){var R=D?t.text:this.pdf.splitTextToSize(t.text,t.maxWidth||O-q.x)[0],T=N([JSON.parse(JSON.stringify(p))],this.posX+this.margin[3],-M+I+this.ctx.prevPageLastElemOffset)[0],U=D&&(k>A||k<P)&&b.call(this);U&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],B,j,null).clip().discardPath()),this.pdf.text(R,T.x,T.y,{angle:t.angle,align:e,renderingMode:t.renderingMode}),U&&this.pdf.restoreGraphicsState()}}else q.y<C&&(this.ctx.prevPageLastElemOffset+=C-q.y);t.scale>=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)}else t.scale>=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale),this.pdf.text(t.text,a.x+this.posX,a.y+this.posY,{angle:t.angle,align:e,renderingMode:t.renderingMode,maxWidth:t.maxWidth}),t.scale>=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)},j=function(t,e,r,a){r=r||0,a=a||0,this.pdf.internal.out(n(t+r)+" "+i(e+a)+" l")},O=function(t,e,r){return this.pdf.lines(t,e,r,null,null)},B=function(t,e,n,i,a,c,u,h){this.pdf.internal.out([r(o(n+t)),r(s(i+e)),r(o(a+t)),r(s(c+e)),r(o(u+t)),r(s(h+e)),"c"].join(" "))},M=function(t,e,r,n){for(var i=2*Math.PI,a=Math.PI/2;e>r;)e-=i;var o=Math.abs(r-e);o<i&&n&&(o=i-o);for(var s=[],c=n?-1:1,u=e;o>1e-5;){var h=u+c*Math.min(o,a);s.push(E.call(this,t,u,h)),o-=Math.abs(h-u),u=h}return s},E=function(t,e,r){var n=(r-e)/2,i=t*Math.cos(n),a=t*Math.sin(n),o=i,s=-a,c=o*o+s*s,u=c+o*i+s*a,h=4/3*(Math.sqrt(2*c*u)-u)/(o*a-s*i),l=o-h*s,f=s+h*o,d=l,p=-f,g=n+e,m=Math.cos(g),v=Math.sin(g);return{x1:t*Math.cos(e),y1:t*Math.sin(e),x2:l*m-f*v,y2:l*v+f*m,x3:d*m-p*v,y3:d*v+p*m,x4:t*Math.cos(r),y4:t*Math.sin(r)}},q=function(t){return 180*t/Math.PI},D=function(t,e,r,n,i,a){var o=t+.5*(r-t),s=e+.5*(n-e),c=i+.5*(r-i),h=a+.5*(n-a),l=Math.min(t,i,o,c),f=Math.max(t,i,o,c),d=Math.min(e,a,s,h),p=Math.max(e,a,s,h);return new u(l,d,f-l,p-d)},R=function(t,e,r,n,i,a,o,s){var c,h,l,f,d,p,g,m,v,b,y,w,N,L,A=r-t,x=n-e,S=i-r,_=a-n,P=o-i,k=s-a;for(h=0;h<41;h++)v=(g=(l=t+(c=h/40)*A)+c*((d=r+c*S)-l))+c*(d+c*(i+c*P-d)-g),b=(m=(f=e+c*x)+c*((p=n+c*_)-f))+c*(p+c*(a+c*k-p)-m),0==h?(y=v,w=b,N=v,L=b):(y=Math.min(y,v),w=Math.min(w,b),N=Math.max(N,v),L=Math.max(L,b));return new u(Math.round(y),Math.round(w),Math.round(N-y),Math.round(L-w))},T=function(){if(this.prevLineDash||this.ctx.lineDash.length||this.ctx.lineDashOffset){var t,e,r=(t=this.ctx.lineDash,e=this.ctx.lineDashOffset,JSON.stringify({lineDash:t,lineDashOffset:e}));this.prevLineDash!==r&&(this.pdf.setLineDash(this.ctx.lineDash,this.ctx.lineDashOffset),this.prevLineDash=r)}}}(E.API), -/** - * @license - * jsPDF filters PlugIn - * Copyright (c) 2014 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var r=function(t){var e,r,n,i,a,o,s,c,u,h;for(/[^\x00-\xFF]/.test(t),r=[],n=0,i=(t+=e="\0\0\0\0".slice(t.length%4||4)).length;i>n;n+=4)0!==(a=(t.charCodeAt(n)<<24)+(t.charCodeAt(n+1)<<16)+(t.charCodeAt(n+2)<<8)+t.charCodeAt(n+3))?(o=(a=((a=((a=((a=(a-(h=a%85))/85)-(u=a%85))/85)-(c=a%85))/85)-(s=a%85))/85)%85,r.push(o+33,s+33,c+33,u+33,h+33)):r.push(122);return function(t,e){for(var r=e;r>0;r--)t.pop()}(r,e.length),String.fromCharCode.apply(String,r)+"~>"},n=function(t){var e,r,n,i,a,o=String,s="length",c=255,u="charCodeAt",h="slice",l="replace";for(t[h](-2),t=t[h](0,-2)[l](/\s/g,"")[l]("z","!!!!!"),n=[],i=0,a=(t+=e="uuuuu"[h](t[s]%5||5))[s];a>i;i+=5)r=52200625*(t[u](i)-33)+614125*(t[u](i+1)-33)+7225*(t[u](i+2)-33)+85*(t[u](i+3)-33)+(t[u](i+4)-33),n.push(c&r>>24,c&r>>16,c&r>>8,c&r);return function(t,e){for(var r=e;r>0;r--)t.pop()}(n,e[s]),o.fromCharCode.apply(o,n)},i=function(t){var e=new RegExp(/^([0-9A-Fa-f]{2})+$/);if(-1!==(t=t.replace(/\s/g,"")).indexOf(">")&&(t=t.substr(0,t.indexOf(">"))),t.length%2&&(t+="0"),!1===e.test(t))return"";for(var r="",n=0;n<t.length;n+=2)r+=String.fromCharCode("0x"+(t[n]+t[n+1]));return r},a=function(t){for(var r=new Uint8Array(t.length),n=t.length;n--;)r[n]=t.charCodeAt(n);return t=(r=e(r)).reduce((function(t,e){return t+String.fromCharCode(e)}),"")};t.processDataByFilters=function(t,e){var o=0,s=t||"",c=[];for("string"==typeof(e=e||[])&&(e=[e]),o=0;o<e.length;o+=1)switch(e[o]){case"ASCII85Decode":case"/ASCII85Decode":s=n(s),c.push("/ASCII85Encode");break;case"ASCII85Encode":case"/ASCII85Encode":s=r(s),c.push("/ASCII85Decode");break;case"ASCIIHexDecode":case"/ASCIIHexDecode":s=i(s),c.push("/ASCIIHexEncode");break;case"ASCIIHexEncode":case"/ASCIIHexEncode":s=s.split("").map((function(t){return("0"+t.charCodeAt().toString(16)).slice(-2)})).join("")+">",c.push("/ASCIIHexDecode");break;case"FlateEncode":case"/FlateEncode":s=a(s),c.push("/FlateDecode");break;default:throw new Error('The filter: "'+e[o]+'" is not implemented')}return{data:s,reverseChain:c.reverse().join(" ")}}}(E.API), -/** - * @license - * jsPDF fileloading PlugIn - * Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com) - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.loadFile=function(t,e,r){return function(t,e,r){e=!1!==e,r="function"==typeof r?r:function(){};var n=void 0;try{n=function(t,e,r){var n=new XMLHttpRequest,i=0,a=function(t){var e=t.length,r=[],n=String.fromCharCode;for(i=0;i<e;i+=1)r.push(n(255&t.charCodeAt(i)));return r.join("")};if(n.open("GET",t,!e),n.overrideMimeType("text/plain; charset=x-user-defined"),!1===e&&(n.onload=function(){200===n.status?r(a(this.responseText)):r(void 0)}),n.send(null),e&&200===n.status)return a(n.responseText)}(t,e,r)}catch(t){}return n}(t,e,r)},t.loadImageFile=t.loadFile}(E.API),function(e){function r(){return(n.html2canvas?Promise.resolve(n.html2canvas):import("html2canvas")).catch((function(t){return Promise.reject(new Error("Could not load html2canvas: "+t))})).then((function(t){return t.default?t.default:t}))}function i(){return(n.DOMPurify?Promise.resolve(n.DOMPurify):import("dompurify")).catch((function(t){return Promise.reject(new Error("Could not load dompurify: "+t))})).then((function(t){return t.default?t.default:t}))}var a=function(e){var r=t(e);return"undefined"===r?"undefined":"string"===r||e instanceof String?"string":"number"===r||e instanceof Number?"number":"function"===r||e instanceof Function?"function":e&&e.constructor===Array?"array":e&&1===e.nodeType?"element":"object"===r?"object":"unknown"},o=function(t,e){var r=document.createElement(t);for(var n in e.className&&(r.className=e.className),e.innerHTML&&e.dompurify&&(r.innerHTML=e.dompurify.sanitize(e.innerHTML)),e.style)r.style[n]=e.style[n];return r},s=function t(e){var r=Object.assign(t.convert(Promise.resolve()),JSON.parse(JSON.stringify(t.template))),n=t.convert(Promise.resolve(),r);return n=(n=n.setProgress(1,t,1,[t])).set(e)};(s.prototype=Object.create(Promise.prototype)).constructor=s,s.convert=function(t,e){return t.__proto__=e||s.prototype,t},s.template={prop:{src:null,container:null,overlay:null,canvas:null,img:null,pdf:null,pageSize:null,callback:function(){}},progress:{val:0,state:null,n:0,stack:[]},opt:{filename:"file.pdf",margin:[0,0,0,0],enableLinks:!0,x:0,y:0,html2canvas:{},jsPDF:{},backgroundColor:"transparent"}},s.prototype.from=function(t,e){return this.then((function(){switch(e=e||function(t){switch(a(t)){case"string":return"string";case"element":return"canvas"===t.nodeName.toLowerCase()?"canvas":"element";default:return"unknown"}}(t)){case"string":return this.then(i).then((function(e){return this.set({src:o("div",{innerHTML:t,dompurify:e})})}));case"element":return this.set({src:t});case"canvas":return this.set({canvas:t});case"img":return this.set({img:t});default:return this.error("Unknown source type.")}}))},s.prototype.to=function(t){switch(t){case"container":return this.toContainer();case"canvas":return this.toCanvas();case"img":return this.toImg();case"pdf":return this.toPdf();default:return this.error("Invalid target.")}},s.prototype.toContainer=function(){return this.thenList([function(){return this.prop.src||this.error("Cannot duplicate - no source HTML.")},function(){return this.prop.pageSize||this.setPageSize()}]).then((function(){var t={position:"relative",display:"inline-block",width:("number"!=typeof this.opt.width||isNaN(this.opt.width)||"number"!=typeof this.opt.windowWidth||isNaN(this.opt.windowWidth)?Math.max(this.prop.src.clientWidth,this.prop.src.scrollWidth,this.prop.src.offsetWidth):this.opt.windowWidth)+"px",left:0,right:0,top:0,margin:"auto",backgroundColor:this.opt.backgroundColor},e=function t(e,r){for(var n=3===e.nodeType?document.createTextNode(e.nodeValue):e.cloneNode(!1),i=e.firstChild;i;i=i.nextSibling)!0!==r&&1===i.nodeType&&"SCRIPT"===i.nodeName||n.appendChild(t(i,r));return 1===e.nodeType&&("CANVAS"===e.nodeName?(n.width=e.width,n.height=e.height,n.getContext("2d").drawImage(e,0,0)):"TEXTAREA"!==e.nodeName&&"SELECT"!==e.nodeName||(n.value=e.value),n.addEventListener("load",(function(){n.scrollTop=e.scrollTop,n.scrollLeft=e.scrollLeft}),!0)),n}(this.prop.src,this.opt.html2canvas.javascriptEnabled);"BODY"===e.tagName&&(t.height=Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)+"px"),this.prop.overlay=o("div",{className:"html2pdf__overlay",style:{position:"fixed",overflow:"hidden",zIndex:1e3,left:"-100000px",right:0,bottom:0,top:0}}),this.prop.container=o("div",{className:"html2pdf__container",style:t}),this.prop.container.appendChild(e),this.prop.container.firstChild.appendChild(o("div",{style:{clear:"both",border:"0 none transparent",margin:0,padding:0,height:0}})),this.prop.container.style.float="none",this.prop.overlay.appendChild(this.prop.container),document.body.appendChild(this.prop.overlay),this.prop.container.firstChild.style.position="relative",this.prop.container.height=Math.max(this.prop.container.firstChild.clientHeight,this.prop.container.firstChild.scrollHeight,this.prop.container.firstChild.offsetHeight)+"px"}))},s.prototype.toCanvas=function(){var t=[function(){return document.body.contains(this.prop.container)||this.toContainer()}];return this.thenList(t).then(r).then((function(t){var e=Object.assign({},this.opt.html2canvas);return delete e.onrendered,t(this.prop.container,e)})).then((function(t){(this.opt.html2canvas.onrendered||function(){})(t),this.prop.canvas=t,document.body.removeChild(this.prop.overlay)}))},s.prototype.toContext2d=function(){var t=[function(){return document.body.contains(this.prop.container)||this.toContainer()}];return this.thenList(t).then(r).then((function(t){var e=this.opt.jsPDF,r=this.opt.fontFaces,n="number"!=typeof this.opt.width||isNaN(this.opt.width)||"number"!=typeof this.opt.windowWidth||isNaN(this.opt.windowWidth)?1:this.opt.width/this.opt.windowWidth,i=Object.assign({async:!0,allowTaint:!0,scale:n,scrollX:this.opt.scrollX||0,scrollY:this.opt.scrollY||0,backgroundColor:"#ffffff",imageTimeout:15e3,logging:!0,proxy:null,removeContainer:!0,foreignObjectRendering:!1,useCORS:!1},this.opt.html2canvas);if(delete i.onrendered,e.context2d.autoPaging=void 0===this.opt.autoPaging||this.opt.autoPaging,e.context2d.posX=this.opt.x,e.context2d.posY=this.opt.y,e.context2d.margin=this.opt.margin,e.context2d.fontFaces=r,r)for(var a=0;a<r.length;++a){var o=r[a],s=o.src.find((function(t){return"truetype"===t.format}));s&&e.addFont(s.url,o.ref.name,o.ref.style)}return i.windowHeight=i.windowHeight||0,i.windowHeight=0==i.windowHeight?Math.max(this.prop.container.clientHeight,this.prop.container.scrollHeight,this.prop.container.offsetHeight):i.windowHeight,e.context2d.save(!0),t(this.prop.container,i)})).then((function(t){this.opt.jsPDF.context2d.restore(!0),(this.opt.html2canvas.onrendered||function(){})(t),this.prop.canvas=t,document.body.removeChild(this.prop.overlay)}))},s.prototype.toImg=function(){return this.thenList([function(){return this.prop.canvas||this.toCanvas()}]).then((function(){var t=this.prop.canvas.toDataURL("image/"+this.opt.image.type,this.opt.image.quality);this.prop.img=document.createElement("img"),this.prop.img.src=t}))},s.prototype.toPdf=function(){return this.thenList([function(){return this.toContext2d()}]).then((function(){this.prop.pdf=this.prop.pdf||this.opt.jsPDF}))},s.prototype.output=function(t,e,r){return"img"===(r=r||"pdf").toLowerCase()||"image"===r.toLowerCase()?this.outputImg(t,e):this.outputPdf(t,e)},s.prototype.outputPdf=function(t,e){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).then((function(){return this.prop.pdf.output(t,e)}))},s.prototype.outputImg=function(t){return this.thenList([function(){return this.prop.img||this.toImg()}]).then((function(){switch(t){case void 0:case"img":return this.prop.img;case"datauristring":case"dataurlstring":return this.prop.img.src;case"datauri":case"dataurl":return document.location.href=this.prop.img.src;default:throw'Image output type "'+t+'" is not supported.'}}))},s.prototype.save=function(t){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).set(t?{filename:t}:null).then((function(){this.prop.pdf.save(this.opt.filename)}))},s.prototype.doCallback=function(){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).then((function(){this.prop.callback(this.prop.pdf)}))},s.prototype.set=function(t){if("object"!==a(t))return this;var e=Object.keys(t||{}).map((function(e){if(e in s.template.prop)return function(){this.prop[e]=t[e]};switch(e){case"margin":return this.setMargin.bind(this,t.margin);case"jsPDF":return function(){return this.opt.jsPDF=t.jsPDF,this.setPageSize()};case"pageSize":return this.setPageSize.bind(this,t.pageSize);default:return function(){this.opt[e]=t[e]}}}),this);return this.then((function(){return this.thenList(e)}))},s.prototype.get=function(t,e){return this.then((function(){var r=t in s.template.prop?this.prop[t]:this.opt[t];return e?e(r):r}))},s.prototype.setMargin=function(t){return this.then((function(){switch(a(t)){case"number":t=[t,t,t,t];case"array":if(2===t.length&&(t=[t[0],t[1],t[0],t[1]]),4===t.length)break;default:return this.error("Invalid margin array.")}this.opt.margin=t})).then(this.setPageSize)},s.prototype.setPageSize=function(t){function e(t,e){return Math.floor(t*e/72*96)}return this.then((function(){(t=t||E.getPageSize(this.opt.jsPDF)).hasOwnProperty("inner")||(t.inner={width:t.width-this.opt.margin[1]-this.opt.margin[3],height:t.height-this.opt.margin[0]-this.opt.margin[2]},t.inner.px={width:e(t.inner.width,t.k),height:e(t.inner.height,t.k)},t.inner.ratio=t.inner.height/t.inner.width),this.prop.pageSize=t}))},s.prototype.setProgress=function(t,e,r,n){return null!=t&&(this.progress.val=t),null!=e&&(this.progress.state=e),null!=r&&(this.progress.n=r),null!=n&&(this.progress.stack=n),this.progress.ratio=this.progress.val/this.progress.state,this},s.prototype.updateProgress=function(t,e,r,n){return this.setProgress(t?this.progress.val+t:null,e||null,r?this.progress.n+r:null,n?this.progress.stack.concat(n):null)},s.prototype.then=function(t,e){var r=this;return this.thenCore(t,e,(function(t,e){return r.updateProgress(null,null,1,[t]),Promise.prototype.then.call(this,(function(e){return r.updateProgress(null,t),e})).then(t,e).then((function(t){return r.updateProgress(1),t}))}))},s.prototype.thenCore=function(t,e,r){r=r||Promise.prototype.then;t&&(t=t.bind(this)),e&&(e=e.bind(this));var n=-1!==Promise.toString().indexOf("[native code]")&&"Promise"===Promise.name?this:s.convert(Object.assign({},this),Promise.prototype),i=r.call(n,t,e);return s.convert(i,this.__proto__)},s.prototype.thenExternal=function(t,e){return Promise.prototype.then.call(this,t,e)},s.prototype.thenList=function(t){var e=this;return t.forEach((function(t){e=e.thenCore(t)})),e},s.prototype.catch=function(t){t&&(t=t.bind(this));var e=Promise.prototype.catch.call(this,t);return s.convert(e,this)},s.prototype.catchExternal=function(t){return Promise.prototype.catch.call(this,t)},s.prototype.error=function(t){return this.then((function(){throw new Error(t)}))},s.prototype.using=s.prototype.set,s.prototype.saveAs=s.prototype.save,s.prototype.export=s.prototype.output,s.prototype.run=s.prototype.then,E.getPageSize=function(e,r,n){if("object"===t(e)){var i=e;e=i.orientation,r=i.unit||r,n=i.format||n}r=r||"mm",n=n||"a4",e=(""+(e||"P")).toLowerCase();var a,o=(""+n).toLowerCase(),s={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};switch(r){case"pt":a=1;break;case"mm":a=72/25.4;break;case"cm":a=72/2.54;break;case"in":a=72;break;case"px":a=.75;break;case"pc":case"em":a=12;break;case"ex":a=6;break;default:throw"Invalid unit: "+r}var c,u=0,h=0;if(s.hasOwnProperty(o))u=s[o][1]/a,h=s[o][0]/a;else try{u=n[1],h=n[0]}catch(t){throw new Error("Invalid format: "+n)}if("p"===e||"portrait"===e)e="p",h>u&&(c=h,h=u,u=c);else{if("l"!==e&&"landscape"!==e)throw"Invalid orientation: "+e;e="l",u>h&&(c=h,h=u,u=c)}return{width:h,height:u,unit:r,k:a,orientation:e}},e.html=function(t,e){(e=e||{}).callback=e.callback||function(){},e.html2canvas=e.html2canvas||{},e.html2canvas.canvas=e.html2canvas.canvas||this.canvas,e.jsPDF=e.jsPDF||this,e.fontFaces=e.fontFaces?e.fontFaces.map(jt):null;var r=new s(e);return e.worker?r:r.from(t).doCallback()}}(E.API),E.API.addJS=function(t){return Ht=t,this.internal.events.subscribe("postPutResources",(function(){Ut=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/Names [(EmbeddedJS) "+(Ut+1)+" 0 R]"),this.internal.out(">>"),this.internal.out("endobj"),zt=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/S /JavaScript"),this.internal.out("/JS ("+Ht+")"),this.internal.out(">>"),this.internal.out("endobj")})),this.internal.events.subscribe("putCatalog",(function(){void 0!==Ut&&void 0!==zt&&this.internal.out("/Names <</JavaScript "+Ut+" 0 R>>")})),this}, -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e;t.events.push(["postPutResources",function(){var t=this,r=/^(\d+) 0 obj$/;if(this.outline.root.children.length>0)for(var n=t.outline.render().split(/\r\n/),i=0;i<n.length;i++){var a=n[i],o=r.exec(a);if(null!=o){var s=o[1];t.internal.newObjectDeferredBegin(s,!1)}t.internal.write(a)}if(this.outline.createNamedDestinations){var c=this.internal.pages.length,u=[];for(i=0;i<c;i++){var h=t.internal.newObject();u.push(h);var l=t.internal.getPageInfo(i+1);t.internal.write("<< /D["+l.objId+" 0 R /XYZ null null null]>> endobj")}var f=t.internal.newObject();t.internal.write("<< /Names [ ");for(i=0;i<u.length;i++)t.internal.write("(page_"+(i+1)+")"+u[i]+" 0 R");t.internal.write(" ] >>","endobj"),e=t.internal.newObject(),t.internal.write("<< /Dests "+f+" 0 R"),t.internal.write(">>","endobj")}}]),t.events.push(["putCatalog",function(){this.outline.root.children.length>0&&(this.internal.write("/Outlines",this.outline.makeRef(this.outline.root)),this.outline.createNamedDestinations&&this.internal.write("/Names "+e+" 0 R"))}]),t.events.push(["initialized",function(){var t=this;t.outline={createNamedDestinations:!1,root:{children:[]}},t.outline.add=function(t,e,r){var n={title:e,options:r,children:[]};return null==t&&(t=this.root),t.children.push(n),n},t.outline.render=function(){return this.ctx={},this.ctx.val="",this.ctx.pdf=t,this.genIds_r(this.root),this.renderRoot(this.root),this.renderItems(this.root),this.ctx.val},t.outline.genIds_r=function(e){e.id=t.internal.newObjectDeferred();for(var r=0;r<e.children.length;r++)this.genIds_r(e.children[r])},t.outline.renderRoot=function(t){this.objStart(t),this.line("/Type /Outlines"),t.children.length>0&&(this.line("/First "+this.makeRef(t.children[0])),this.line("/Last "+this.makeRef(t.children[t.children.length-1]))),this.line("/Count "+this.count_r({count:0},t)),this.objEnd()},t.outline.renderItems=function(e){for(var r=this.ctx.pdf.internal.getVerticalCoordinateString,n=0;n<e.children.length;n++){var i=e.children[n];this.objStart(i),this.line("/Title "+this.makeString(i.title)),this.line("/Parent "+this.makeRef(e)),n>0&&this.line("/Prev "+this.makeRef(e.children[n-1])),n<e.children.length-1&&this.line("/Next "+this.makeRef(e.children[n+1])),i.children.length>0&&(this.line("/First "+this.makeRef(i.children[0])),this.line("/Last "+this.makeRef(i.children[i.children.length-1])));var a=this.count=this.count_r({count:0},i);if(a>0&&this.line("/Count "+a),i.options&&i.options.pageNumber){var o=t.internal.getPageInfo(i.options.pageNumber);this.line("/Dest ["+o.objId+" 0 R /XYZ 0 "+r(0)+" 0]")}this.objEnd()}for(var s=0;s<e.children.length;s++)this.renderItems(e.children[s])},t.outline.line=function(t){this.ctx.val+=t+"\r\n"},t.outline.makeRef=function(t){return t.id+" 0 R"},t.outline.makeString=function(e){return"("+t.internal.pdfEscape(e)+")"},t.outline.objStart=function(t){this.ctx.val+="\r\n"+t.id+" 0 obj\r\n<<\r\n"},t.outline.objEnd=function(){this.ctx.val+=">> \r\nendobj\r\n"},t.outline.count_r=function(t,e){for(var r=0;r<e.children.length;r++)t.count++,this.count_r(t,e.children[r]);return t.count}}])}(E.API), -/** - * @license - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=[192,193,194,195,196,197,198,199];t.processJPEG=function(t,r,n,i,a,o){var s,c=this.decode.DCT_DECODE,u=null;if("string"==typeof t||this.__addimage__.isArrayBuffer(t)||this.__addimage__.isArrayBufferView(t)){switch(t=a||t,t=this.__addimage__.isArrayBuffer(t)?new Uint8Array(t):t,(s=function(t){for(var r,n=256*t.charCodeAt(4)+t.charCodeAt(5),i=t.length,a={width:0,height:0,numcomponents:1},o=4;o<i;o+=2){if(o+=n,-1!==e.indexOf(t.charCodeAt(o+1))){r=256*t.charCodeAt(o+5)+t.charCodeAt(o+6),a={width:256*t.charCodeAt(o+7)+t.charCodeAt(o+8),height:r,numcomponents:t.charCodeAt(o+9)};break}n=256*t.charCodeAt(o+2)+t.charCodeAt(o+3)}return a}(t=this.__addimage__.isArrayBufferView(t)?this.__addimage__.arrayBufferToBinaryString(t):t)).numcomponents){case 1:o=this.color_spaces.DEVICE_GRAY;break;case 4:o=this.color_spaces.DEVICE_CMYK;break;case 3:o=this.color_spaces.DEVICE_RGB}u={data:t,width:s.width,height:s.height,colorSpace:o,bitsPerComponent:8,filter:c,index:r,alias:n}}return u}}(E.API);var Vt,Gt,Yt,Jt,Xt,Kt=function(){var t,e,i;function a(t){var e,r,n,i,a,o,s,c,u,h,l,f,d,p;for(this.data=t,this.pos=8,this.palette=[],this.imgData=[],this.transparency={},this.animation=null,this.text={},o=null;;){switch(e=this.readUInt32(),u=function(){var t,e;for(e=[],t=0;t<4;++t)e.push(String.fromCharCode(this.data[this.pos++]));return e}.call(this).join("")){case"IHDR":this.width=this.readUInt32(),this.height=this.readUInt32(),this.bits=this.data[this.pos++],this.colorType=this.data[this.pos++],this.compressionMethod=this.data[this.pos++],this.filterMethod=this.data[this.pos++],this.interlaceMethod=this.data[this.pos++];break;case"acTL":this.animation={numFrames:this.readUInt32(),numPlays:this.readUInt32()||1/0,frames:[]};break;case"PLTE":this.palette=this.read(e);break;case"fcTL":o&&this.animation.frames.push(o),this.pos+=4,o={width:this.readUInt32(),height:this.readUInt32(),xOffset:this.readUInt32(),yOffset:this.readUInt32()},a=this.readUInt16(),i=this.readUInt16()||100,o.delay=1e3*a/i,o.disposeOp=this.data[this.pos++],o.blendOp=this.data[this.pos++],o.data=[];break;case"IDAT":case"fdAT":for("fdAT"===u&&(this.pos+=4,e-=4),t=(null!=o?o.data:void 0)||this.imgData,f=0;0<=e?f<e:f>e;0<=e?++f:--f)t.push(this.data[this.pos++]);break;case"tRNS":switch(this.transparency={},this.colorType){case 3:if(n=this.palette.length/3,this.transparency.indexed=this.read(e),this.transparency.indexed.length>n)throw new Error("More transparent colors than palette size");if((h=n-this.transparency.indexed.length)>0)for(d=0;0<=h?d<h:d>h;0<=h?++d:--d)this.transparency.indexed.push(255);break;case 0:this.transparency.grayscale=this.read(e)[0];break;case 2:this.transparency.rgb=this.read(e)}break;case"tEXt":s=(l=this.read(e)).indexOf(0),c=String.fromCharCode.apply(String,l.slice(0,s)),this.text[c]=String.fromCharCode.apply(String,l.slice(s+1));break;case"IEND":return o&&this.animation.frames.push(o),this.colors=function(){switch(this.colorType){case 0:case 3:case 4:return 1;case 2:case 6:return 3}}.call(this),this.hasAlphaChannel=4===(p=this.colorType)||6===p,r=this.colors+(this.hasAlphaChannel?1:0),this.pixelBitlength=this.bits*r,this.colorSpace=function(){switch(this.colors){case 1:return"DeviceGray";case 3:return"DeviceRGB"}}.call(this),void(this.imgData=new Uint8Array(this.imgData));default:this.pos+=e}if(this.pos+=4,this.pos>this.data.length)throw new Error("Incomplete or corrupt PNG file")}}a.prototype.read=function(t){var e,r;for(r=[],e=0;0<=t?e<t:e>t;0<=t?++e:--e)r.push(this.data[this.pos++]);return r},a.prototype.readUInt32=function(){return this.data[this.pos++]<<24|this.data[this.pos++]<<16|this.data[this.pos++]<<8|this.data[this.pos++]},a.prototype.readUInt16=function(){return this.data[this.pos++]<<8|this.data[this.pos++]},a.prototype.decodePixels=function(t){var e=this.pixelBitlength/8,n=new Uint8Array(this.width*this.height*e),i=0,a=this;if(null==t&&(t=this.imgData),0===t.length)return new Uint8Array(0);function o(r,o,s,c){var u,h,l,f,d,p,g,m,v,b,y,w,N,L,A,x,S,_,P,k,I,F=Math.ceil((a.width-r)/s),C=Math.ceil((a.height-o)/c),j=a.width==F&&a.height==C;for(L=e*F,w=j?n:new Uint8Array(L*C),p=t.length,N=0,h=0;N<C&&i<p;){switch(t[i++]){case 0:for(f=S=0;S<L;f=S+=1)w[h++]=t[i++];break;case 1:for(f=_=0;_<L;f=_+=1)u=t[i++],d=f<e?0:w[h-e],w[h++]=(u+d)%256;break;case 2:for(f=P=0;P<L;f=P+=1)u=t[i++],l=(f-f%e)/e,A=N&&w[(N-1)*L+l*e+f%e],w[h++]=(A+u)%256;break;case 3:for(f=k=0;k<L;f=k+=1)u=t[i++],l=(f-f%e)/e,d=f<e?0:w[h-e],A=N&&w[(N-1)*L+l*e+f%e],w[h++]=(u+Math.floor((d+A)/2))%256;break;case 4:for(f=I=0;I<L;f=I+=1)u=t[i++],l=(f-f%e)/e,d=f<e?0:w[h-e],0===N?A=x=0:(A=w[(N-1)*L+l*e+f%e],x=l&&w[(N-1)*L+(l-1)*e+f%e]),g=d+A-x,m=Math.abs(g-d),b=Math.abs(g-A),y=Math.abs(g-x),v=m<=b&&m<=y?d:b<=y?A:x,w[h++]=(u+v)%256;break;default:throw new Error("Invalid filter algorithm: "+t[i-1])}if(!j){var O=((o+N*c)*a.width+r)*e,B=N*L;for(f=0;f<F;f+=1){for(var M=0;M<e;M+=1)n[O++]=w[B++];O+=(s-1)*e}}N++}}return t=r(t),1==a.interlaceMethod?(o(0,0,8,8),o(4,0,8,8),o(0,4,4,8),o(2,0,4,4),o(0,2,2,4),o(1,0,2,2),o(0,1,1,2)):o(0,0,1,1),n},a.prototype.decodePalette=function(){var t,e,r,n,i,a,o,s,c;for(r=this.palette,a=this.transparency.indexed||[],i=new Uint8Array((a.length||0)+r.length),n=0,t=0,e=o=0,s=r.length;o<s;e=o+=3)i[n++]=r[e],i[n++]=r[e+1],i[n++]=r[e+2],i[n++]=null!=(c=a[t++])?c:255;return i},a.prototype.copyToImageData=function(t,e){var r,n,i,a,o,s,c,u,h,l,f;if(n=this.colors,h=null,r=this.hasAlphaChannel,this.palette.length&&(h=null!=(f=this._decodedPalette)?f:this._decodedPalette=this.decodePalette(),n=4,r=!0),u=(i=t.data||t).length,o=h||e,a=s=0,1===n)for(;a<u;)c=h?4*e[a/4]:s,l=o[c++],i[a++]=l,i[a++]=l,i[a++]=l,i[a++]=r?o[c++]:255,s=c;else for(;a<u;)c=h?4*e[a/4]:s,i[a++]=o[c++],i[a++]=o[c++],i[a++]=o[c++],i[a++]=r?o[c++]:255,s=c},a.prototype.decode=function(){var t;return t=new Uint8Array(this.width*this.height*4),this.copyToImageData(t,this.decodePixels()),t};var o=function(){if("[object Window]"===Object.prototype.toString.call(n)){try{e=n.document.createElement("canvas"),i=e.getContext("2d")}catch(t){return!1}return!0}return!1};return o(),t=function(t){var r;if(!0===o())return i.width=t.width,i.height=t.height,i.clearRect(0,0,t.width,t.height),i.putImageData(t,0,0),(r=new Image).src=e.toDataURL(),r;throw new Error("This method requires a Browser with Canvas-capability.")},a.prototype.decodeFrames=function(e){var r,n,i,a,o,s,c,u;if(this.animation){for(u=[],n=o=0,s=(c=this.animation.frames).length;o<s;n=++o)r=c[n],i=e.createImageData(r.width,r.height),a=this.decodePixels(new Uint8Array(r.data)),this.copyToImageData(i,a),r.imageData=i,u.push(r.image=t(i));return u}},a.prototype.renderFrame=function(t,e){var r,n,i;return r=(n=this.animation.frames)[e],i=n[e-1],0===e&&t.clearRect(0,0,this.width,this.height),1===(null!=i?i.disposeOp:void 0)?t.clearRect(i.xOffset,i.yOffset,i.width,i.height):2===(null!=i?i.disposeOp:void 0)&&t.putImageData(i.imageData,i.xOffset,i.yOffset),0===r.blendOp&&t.clearRect(r.xOffset,r.yOffset,r.width,r.height),t.drawImage(r.image,r.xOffset,r.yOffset)},a.prototype.animate=function(t){var e,r,n,i,a,o,s=this;return r=0,o=this.animation,i=o.numFrames,n=o.frames,a=o.numPlays,(e=function(){var o,c;if(o=r++%i,c=n[o],s.renderFrame(t,o),i>1&&r/i<a)return s.animation._timeout=setTimeout(e,c.delay)})()},a.prototype.stopAnimation=function(){var t;return clearTimeout(null!=(t=this.animation)?t._timeout:void 0)},a.prototype.render=function(t){var e,r;return t._png&&t._png.stopAnimation(),t._png=this,t.width=this.width,t.height=this.height,e=t.getContext("2d"),this.animation?(this.decodeFrames(e),this.animate(e)):(r=e.createImageData(this.width,this.height),this.copyToImageData(r,this.decodePixels()),e.putImageData(r,0,0))},a}(); -/** - * @license - * - * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -/** - * @license - * (c) Dean McNamee <dean@gmail.com>, 2013. - * - * https://github.com/deanm/omggif - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - * omggif is a JavaScript implementation of a GIF 89a encoder and decoder, - * including animation and compression. It does not rely on any specific - * underlying system, so should run in the browser, Node, or Plask. - */ -function Zt(t){var e=0;if(71!==t[e++]||73!==t[e++]||70!==t[e++]||56!==t[e++]||56!=(t[e++]+1&253)||97!==t[e++])throw new Error("Invalid GIF 87a/89a header.");var r=t[e++]|t[e++]<<8,n=t[e++]|t[e++]<<8,i=t[e++],a=i>>7,o=1<<(7&i)+1;t[e++];t[e++];var s=null,c=null;a&&(s=e,c=o,e+=3*o);var u=!0,h=[],l=0,f=null,d=0,p=null;for(this.width=r,this.height=n;u&&e<t.length;)switch(t[e++]){case 33:switch(t[e++]){case 255:if(11!==t[e]||78==t[e+1]&&69==t[e+2]&&84==t[e+3]&&83==t[e+4]&&67==t[e+5]&&65==t[e+6]&&80==t[e+7]&&69==t[e+8]&&50==t[e+9]&&46==t[e+10]&&48==t[e+11]&&3==t[e+12]&&1==t[e+13]&&0==t[e+16])e+=14,p=t[e++]|t[e++]<<8,e++;else for(e+=12;;){if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}break;case 249:if(4!==t[e++]||0!==t[e+4])throw new Error("Invalid graphics extension block.");var g=t[e++];l=t[e++]|t[e++]<<8,f=t[e++],0==(1&g)&&(f=null),d=g>>2&7,e++;break;case 254:for(;;){if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}break;default:throw new Error("Unknown graphic control label: 0x"+t[e-1].toString(16))}break;case 44:var m=t[e++]|t[e++]<<8,v=t[e++]|t[e++]<<8,b=t[e++]|t[e++]<<8,y=t[e++]|t[e++]<<8,w=t[e++],N=w>>6&1,L=1<<(7&w)+1,A=s,x=c,S=!1;if(w>>7){S=!0;A=e,x=L,e+=3*L}var _=e;for(e++;;){var P;if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}h.push({x:m,y:v,width:b,height:y,has_local_palette:S,palette_offset:A,palette_size:x,data_offset:_,data_length:e-_,transparent_index:f,interlaced:!!N,delay:l,disposal:d});break;case 59:u=!1;break;default:throw new Error("Unknown gif block: 0x"+t[e-1].toString(16))}this.numFrames=function(){return h.length},this.loopCount=function(){return p},this.frameInfo=function(t){if(t<0||t>=h.length)throw new Error("Frame index out of range.");return h[t]},this.decodeAndBlitFrameBGRA=function(e,n){var i=this.frameInfo(e),a=i.width*i.height,o=new Uint8Array(a);$t(t,i.data_offset,o,a);var s=i.palette_offset,c=i.transparent_index;null===c&&(c=256);var u=i.width,h=r-u,l=u,f=4*(i.y*r+i.x),d=4*((i.y+i.height)*r+i.x),p=f,g=4*h;!0===i.interlaced&&(g+=4*r*7);for(var m=8,v=0,b=o.length;v<b;++v){var y=o[v];if(0===l&&(l=u,(p+=g)>=d&&(g=4*h+4*r*(m-1),p=f+(u+h)*(m<<1),m>>=1)),y===c)p+=4;else{var w=t[s+3*y],N=t[s+3*y+1],L=t[s+3*y+2];n[p++]=L,n[p++]=N,n[p++]=w,n[p++]=255}--l}},this.decodeAndBlitFrameRGBA=function(e,n){var i=this.frameInfo(e),a=i.width*i.height,o=new Uint8Array(a);$t(t,i.data_offset,o,a);var s=i.palette_offset,c=i.transparent_index;null===c&&(c=256);var u=i.width,h=r-u,l=u,f=4*(i.y*r+i.x),d=4*((i.y+i.height)*r+i.x),p=f,g=4*h;!0===i.interlaced&&(g+=4*r*7);for(var m=8,v=0,b=o.length;v<b;++v){var y=o[v];if(0===l&&(l=u,(p+=g)>=d&&(g=4*h+4*r*(m-1),p=f+(u+h)*(m<<1),m>>=1)),y===c)p+=4;else{var w=t[s+3*y],N=t[s+3*y+1],L=t[s+3*y+2];n[p++]=w,n[p++]=N,n[p++]=L,n[p++]=255}--l}}}function $t(t,e,r,n){for(var i=t[e++],o=1<<i,s=o+1,c=s+1,u=i+1,h=(1<<u)-1,l=0,f=0,d=0,p=t[e++],g=new Int32Array(4096),m=null;;){for(;l<16&&0!==p;)f|=t[e++]<<l,l+=8,1===p?p=t[e++]:--p;if(l<u)break;var v=f&h;if(f>>=u,l-=u,v!==o){if(v===s)break;for(var b=v<c?v:m,y=0,w=b;w>o;)w=g[w]>>8,++y;var N=w;if(d+y+(b!==v?1:0)>n)return void a.log("Warning, gif stream longer than expected.");r[d++]=N;var L=d+=y;for(b!==v&&(r[d++]=N),w=b;y--;)w=g[w],r[--L]=255&w,w>>=8;null!==m&&c<4096&&(g[c++]=m<<8|N,c>=h+1&&u<12&&(++u,h=h<<1|1)),m=v}else c=s+1,h=(1<<(u=i+1))-1,m=null}return d!==n&&a.log("Warning, gif stream shorter than expected."),r} -/** - * @license - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/function Qt(t){var e,r,n,i,a,o=Math.floor,s=new Array(64),c=new Array(64),u=new Array(64),h=new Array(64),l=new Array(65535),f=new Array(65535),d=new Array(64),p=new Array(64),g=[],m=0,v=7,b=new Array(64),y=new Array(64),w=new Array(64),N=new Array(256),L=new Array(2048),A=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],x=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],S=[0,1,2,3,4,5,6,7,8,9,10,11],_=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],P=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],k=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],I=[0,1,2,3,4,5,6,7,8,9,10,11],F=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],C=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];function j(t,e){for(var r=0,n=0,i=new Array,a=1;a<=16;a++){for(var o=1;o<=t[a];o++)i[e[n]]=[],i[e[n]][0]=r,i[e[n]][1]=a,n++,r++;r*=2}return i}function O(t){for(var e=t[0],r=t[1]-1;r>=0;)e&1<<r&&(m|=1<<v),r--,--v<0&&(255==m?(B(255),B(0)):B(m),v=7,m=0)}function B(t){g.push(t)}function M(t){B(t>>8&255),B(255&t)}function E(t,e,r,n,i){for(var a,o=i[0],s=i[240],c=function(t,e){var r,n,i,a,o,s,c,u,h,l,f=0;for(h=0;h<8;++h){r=t[f],n=t[f+1],i=t[f+2],a=t[f+3],o=t[f+4],s=t[f+5],c=t[f+6];var p=r+(u=t[f+7]),g=r-u,m=n+c,v=n-c,b=i+s,y=i-s,w=a+o,N=a-o,L=p+w,A=p-w,x=m+b,S=m-b;t[f]=L+x,t[f+4]=L-x;var _=.707106781*(S+A);t[f+2]=A+_,t[f+6]=A-_;var P=.382683433*((L=N+y)-(S=v+g)),k=.5411961*L+P,I=1.306562965*S+P,F=.707106781*(x=y+v),C=g+F,j=g-F;t[f+5]=j+k,t[f+3]=j-k,t[f+1]=C+I,t[f+7]=C-I,f+=8}for(f=0,h=0;h<8;++h){r=t[f],n=t[f+8],i=t[f+16],a=t[f+24],o=t[f+32],s=t[f+40],c=t[f+48];var O=r+(u=t[f+56]),B=r-u,M=n+c,E=n-c,q=i+s,D=i-s,R=a+o,T=a-o,U=O+R,z=O-R,H=M+q,W=M-q;t[f]=U+H,t[f+32]=U-H;var V=.707106781*(W+z);t[f+16]=z+V,t[f+48]=z-V;var G=.382683433*((U=T+D)-(W=E+B)),Y=.5411961*U+G,J=1.306562965*W+G,X=.707106781*(H=D+E),K=B+X,Z=B-X;t[f+40]=Z+Y,t[f+24]=Z-Y,t[f+8]=K+J,t[f+56]=K-J,f++}for(h=0;h<64;++h)l=t[h]*e[h],d[h]=l>0?l+.5|0:l-.5|0;return d}(t,e),u=0;u<64;++u)p[A[u]]=c[u];var h=p[0]-r;r=p[0],0==h?O(n[0]):(O(n[f[a=32767+h]]),O(l[a]));for(var g=63;g>0&&0==p[g];)g--;if(0==g)return O(o),r;for(var m,v=1;v<=g;){for(var b=v;0==p[v]&&v<=g;)++v;var y=v-b;if(y>=16){m=y>>4;for(var w=1;w<=m;++w)O(s);y&=15}a=32767+p[v],O(i[(y<<4)+f[a]]),O(l[a]),v++}return 63!=g&&O(o),r}function q(t){(t=Math.min(Math.max(t,1),100),a!=t)&&(!function(t){for(var e=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],r=0;r<64;r++){var n=o((e[r]*t+50)/100);n=Math.min(Math.max(n,1),255),s[A[r]]=n}for(var i=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],a=0;a<64;a++){var l=o((i[a]*t+50)/100);l=Math.min(Math.max(l,1),255),c[A[a]]=l}for(var f=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],d=0,p=0;p<8;p++)for(var g=0;g<8;g++)u[d]=1/(s[A[d]]*f[p]*f[g]*8),h[d]=1/(c[A[d]]*f[p]*f[g]*8),d++}(t<50?Math.floor(5e3/t):Math.floor(200-2*t)),a=t)}this.encode=function(t,a){a&&q(a),g=new Array,m=0,v=7,M(65496),M(65504),M(16),B(74),B(70),B(73),B(70),B(0),B(1),B(1),B(0),M(1),M(1),B(0),B(0),function(){M(65499),M(132),B(0);for(var t=0;t<64;t++)B(s[t]);B(1);for(var e=0;e<64;e++)B(c[e])}(),function(t,e){M(65472),M(17),B(8),M(e),M(t),B(3),B(1),B(17),B(0),B(2),B(17),B(1),B(3),B(17),B(1)}(t.width,t.height),function(){M(65476),M(418),B(0);for(var t=0;t<16;t++)B(x[t+1]);for(var e=0;e<=11;e++)B(S[e]);B(16);for(var r=0;r<16;r++)B(_[r+1]);for(var n=0;n<=161;n++)B(P[n]);B(1);for(var i=0;i<16;i++)B(k[i+1]);for(var a=0;a<=11;a++)B(I[a]);B(17);for(var o=0;o<16;o++)B(F[o+1]);for(var s=0;s<=161;s++)B(C[s])}(),M(65498),M(12),B(3),B(1),B(0),B(2),B(17),B(3),B(17),B(0),B(63),B(0);var o=0,l=0,f=0;m=0,v=7,this.encode.displayName="_encode_";for(var d,p,N,A,j,D,R,T,U,z=t.data,H=t.width,W=t.height,V=4*H,G=0;G<W;){for(d=0;d<V;){for(j=V*G+d,R=-1,T=0,U=0;U<64;U++)D=j+(T=U>>3)*V+(R=4*(7&U)),G+T>=W&&(D-=V*(G+1+T-W)),d+R>=V&&(D-=d+R-V+4),p=z[D++],N=z[D++],A=z[D++],b[U]=(L[p]+L[N+256>>0]+L[A+512>>0]>>16)-128,y[U]=(L[p+768>>0]+L[N+1024>>0]+L[A+1280>>0]>>16)-128,w[U]=(L[p+1280>>0]+L[N+1536>>0]+L[A+1792>>0]>>16)-128;o=E(b,u,o,e,n),l=E(y,h,l,r,i),f=E(w,h,f,r,i),d+=32}G+=8}if(v>=0){var Y=[];Y[1]=v+1,Y[0]=(1<<v+1)-1,O(Y)}return M(65497),new Uint8Array(g)},t=t||50,function(){for(var t=String.fromCharCode,e=0;e<256;e++)N[e]=t(e)}(),e=j(x,S),r=j(k,I),n=j(_,P),i=j(F,C),function(){for(var t=1,e=2,r=1;r<=15;r++){for(var n=t;n<e;n++)f[32767+n]=r,l[32767+n]=[],l[32767+n][1]=r,l[32767+n][0]=n;for(var i=-(e-1);i<=-t;i++)f[32767+i]=r,l[32767+i]=[],l[32767+i][1]=r,l[32767+i][0]=e-1+i;t<<=1,e<<=1}}(),function(){for(var t=0;t<256;t++)L[t]=19595*t,L[t+256>>0]=38470*t,L[t+512>>0]=7471*t+32768,L[t+768>>0]=-11059*t,L[t+1024>>0]=-21709*t,L[t+1280>>0]=32768*t+8421375,L[t+1536>>0]=-27439*t,L[t+1792>>0]=-5329*t}(),q(t)} -/** - * @license - * Copyright (c) 2017 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */function te(t,e){if(this.pos=0,this.buffer=t,this.datav=new DataView(t.buffer),this.is_with_alpha=!!e,this.bottom_up=!0,this.flag=String.fromCharCode(this.buffer[0])+String.fromCharCode(this.buffer[1]),this.pos+=2,-1===["BM","BA","CI","CP","IC","PT"].indexOf(this.flag))throw new Error("Invalid BMP File");this.parseHeader(),this.parseBGR()}function ee(t){function e(t){if(!t)throw Error("assert :P")}function r(t,e,r){for(var n=0;4>n;n++)if(t[e+n]!=r.charCodeAt(n))return!0;return!1}function n(t,e,r,n,i){for(var a=0;a<i;a++)t[e+a]=r[n+a]}function i(t,e,r,n){for(var i=0;i<n;i++)t[e+i]=r}function a(t){return new Int32Array(t)}function o(t,e){for(var r=[],n=0;n<t;n++)r.push(new e);return r}function s(t,e){var r=[];return function t(r,n,i){for(var a=i[n],o=0;o<a&&(r.push(i.length>n+1?[]:new e),!(i.length<n+1));o++)t(r[o],n+1,i)}(r,0,t),r}var c=function(){var t=this;function c(t,e){for(var r=1<<e-1>>>0;t&r;)r>>>=1;return r?(t&r-1)+r:t}function u(t,r,n,i,a){e(!(i%n));do{t[r+(i-=n)]=a}while(0<i)}function h(t,r,n,i,o){if(e(2328>=o),512>=o)var s=a(512);else if(null==(s=a(o)))return 0;return function(t,r,n,i,o,s){var h,f,d=r,p=1<<n,g=a(16),m=a(16);for(e(0!=o),e(null!=i),e(null!=t),e(0<n),f=0;f<o;++f){if(15<i[f])return 0;++g[i[f]]}if(g[0]==o)return 0;for(m[1]=0,h=1;15>h;++h){if(g[h]>1<<h)return 0;m[h+1]=m[h]+g[h]}for(f=0;f<o;++f)h=i[f],0<i[f]&&(s[m[h]++]=f);if(1==m[15])return(i=new l).g=0,i.value=s[0],u(t,d,1,p,i),p;var v,b=-1,y=p-1,w=0,N=1,L=1,A=1<<n;for(f=0,h=1,o=2;h<=n;++h,o<<=1){if(N+=L<<=1,0>(L-=g[h]))return 0;for(;0<g[h];--g[h])(i=new l).g=h,i.value=s[f++],u(t,d+w,o,A,i),w=c(w,h)}for(h=n+1,o=2;15>=h;++h,o<<=1){if(N+=L<<=1,0>(L-=g[h]))return 0;for(;0<g[h];--g[h]){if(i=new l,(w&y)!=b){for(d+=A,v=1<<(b=h)-n;15>b&&!(0>=(v-=g[b]));)++b,v<<=1;p+=A=1<<(v=b-n),t[r+(b=w&y)].g=v+n,t[r+b].value=d-r-b}i.g=h-n,i.value=s[f++],u(t,d+(w>>n),o,A,i),w=c(w,h)}}return N!=2*m[15]-1?0:p}(t,r,n,i,o,s)}function l(){this.value=this.g=0}function f(){this.value=this.g=0}function d(){this.G=o(5,l),this.H=a(5),this.jc=this.Qb=this.qb=this.nd=0,this.pd=o(Dr,f)}function p(t,r,n,i){e(null!=t),e(null!=r),e(2147483648>i),t.Ca=254,t.I=0,t.b=-8,t.Ka=0,t.oa=r,t.pa=n,t.Jd=r,t.Yc=n+i,t.Zc=4<=i?n+i-4+1:n,_(t)}function g(t,e){for(var r=0;0<e--;)r|=k(t,128)<<e;return r}function m(t,e){var r=g(t,e);return P(t)?-r:r}function v(t,r,n,i){var a,o=0;for(e(null!=t),e(null!=r),e(4294967288>i),t.Sb=i,t.Ra=0,t.u=0,t.h=0,4<i&&(i=4),a=0;a<i;++a)o+=r[n+a]<<8*a;t.Ra=o,t.bb=i,t.oa=r,t.pa=n}function b(t){for(;8<=t.u&&t.bb<t.Sb;)t.Ra>>>=8,t.Ra+=t.oa[t.pa+t.bb]<<Ur-8>>>0,++t.bb,t.u-=8;A(t)&&(t.h=1,t.u=0)}function y(t,r){if(e(0<=r),!t.h&&r<=Tr){var n=L(t)&Rr[r];return t.u+=r,b(t),n}return t.h=1,t.u=0}function w(){this.b=this.Ca=this.I=0,this.oa=[],this.pa=0,this.Jd=[],this.Yc=0,this.Zc=[],this.Ka=0}function N(){this.Ra=0,this.oa=[],this.h=this.u=this.bb=this.Sb=this.pa=0}function L(t){return t.Ra>>>(t.u&Ur-1)>>>0}function A(t){return e(t.bb<=t.Sb),t.h||t.bb==t.Sb&&t.u>Ur}function x(t,e){t.u=e,t.h=A(t)}function S(t){t.u>=zr&&(e(t.u>=zr),b(t))}function _(t){e(null!=t&&null!=t.oa),t.pa<t.Zc?(t.I=(t.oa[t.pa++]|t.I<<8)>>>0,t.b+=8):(e(null!=t&&null!=t.oa),t.pa<t.Yc?(t.b+=8,t.I=t.oa[t.pa++]|t.I<<8):t.Ka?t.b=0:(t.I<<=8,t.b+=8,t.Ka=1))}function P(t){return g(t,1)}function k(t,e){var r=t.Ca;0>t.b&&_(t);var n=t.b,i=r*e>>>8,a=(t.I>>>n>i)+0;for(a?(r-=i,t.I-=i+1<<n>>>0):r=i+1,n=r,i=0;256<=n;)i+=8,n>>=8;return n=7^i+Hr[n],t.b-=n,t.Ca=(r<<n)-1,a}function I(t,e,r){t[e+0]=r>>24&255,t[e+1]=r>>16&255,t[e+2]=r>>8&255,t[e+3]=r>>0&255}function F(t,e){return t[e+0]<<0|t[e+1]<<8}function C(t,e){return F(t,e)|t[e+2]<<16}function j(t,e){return F(t,e)|F(t,e+2)<<16}function O(t,r){var n=1<<r;return e(null!=t),e(0<r),t.X=a(n),null==t.X?0:(t.Mb=32-r,t.Xa=r,1)}function B(t,r){e(null!=t),e(null!=r),e(t.Xa==r.Xa),n(r.X,0,t.X,0,1<<r.Xa)}function M(){this.X=[],this.Xa=this.Mb=0}function E(t,r,n,i){e(null!=n),e(null!=i);var a=n[0],o=i[0];return 0==a&&(a=(t*o+r/2)/r),0==o&&(o=(r*a+t/2)/t),0>=a||0>=o?0:(n[0]=a,i[0]=o,1)}function q(t,e){return t+(1<<e)-1>>>e}function D(t,e){return((4278255360&t)+(4278255360&e)>>>0&4278255360)+((16711935&t)+(16711935&e)>>>0&16711935)>>>0}function R(e,r){t[r]=function(r,n,i,a,o,s,c){var u;for(u=0;u<o;++u){var h=t[e](s[c+u-1],i,a+u);s[c+u]=D(r[n+u],h)}}}function T(){this.ud=this.hd=this.jd=0}function U(t,e){return((4278124286&(t^e))>>>1)+(t&e)>>>0}function z(t){return 0<=t&&256>t?t:0>t?0:255<t?255:void 0}function H(t,e){return z(t+(t-e+.5>>1))}function W(t,e,r){return Math.abs(e-r)-Math.abs(t-r)}function V(t,e,r,n,i,a,o){for(n=a[o-1],r=0;r<i;++r)a[o+r]=n=D(t[e+r],n)}function G(t,e,r,n,i){var a;for(a=0;a<r;++a){var o=t[e+a],s=o>>8&255,c=16711935&(c=(c=16711935&o)+((s<<16)+s));n[i+a]=(4278255360&o)+c>>>0}}function Y(t,e){e.jd=t>>0&255,e.hd=t>>8&255,e.ud=t>>16&255}function J(t,e,r,n,i,a){var o;for(o=0;o<n;++o){var s=e[r+o],c=s>>>8,u=s,h=255&(h=(h=s>>>16)+((t.jd<<24>>24)*(c<<24>>24)>>>5));u=255&(u=(u=u+((t.hd<<24>>24)*(c<<24>>24)>>>5))+((t.ud<<24>>24)*(h<<24>>24)>>>5));i[a+o]=(4278255360&s)+(h<<16)+u}}function X(e,r,n,i,a){t[r]=function(t,e,r,n,o,s,c,u,h){for(n=c;n<u;++n)for(c=0;c<h;++c)o[s++]=a(r[i(t[e++])])},t[e]=function(e,r,o,s,c,u,h){var l=8>>e.b,f=e.Ea,d=e.K[0],p=e.w;if(8>l)for(e=(1<<e.b)-1,p=(1<<l)-1;r<o;++r){var g,m=0;for(g=0;g<f;++g)g&e||(m=i(s[c++])),u[h++]=a(d[m&p]),m>>=l}else t["VP8LMapColor"+n](s,c,d,p,u,h,r,o,f)}}function K(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>16&255,n[i++]=a>>8&255,n[i++]=a>>0&255}}function Z(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>16&255,n[i++]=a>>8&255,n[i++]=a>>0&255,n[i++]=a>>24&255}}function $(t,e,r,n,i){for(r=e+r;e<r;){var a=(o=t[e++])>>16&240|o>>12&15,o=o>>0&240|o>>28&15;n[i++]=a,n[i++]=o}}function Q(t,e,r,n,i){for(r=e+r;e<r;){var a=(o=t[e++])>>16&248|o>>13&7,o=o>>5&224|o>>3&31;n[i++]=a,n[i++]=o}}function tt(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>0&255,n[i++]=a>>8&255,n[i++]=a>>16&255}}function et(t,e,r,i,a,o){if(0==o)for(r=e+r;e<r;)I(i,((o=t[e++])[0]>>24|o[1]>>8&65280|o[2]<<8&16711680|o[3]<<24)>>>0),a+=32;else n(i,a,t,e,r)}function rt(e,r){t[r][0]=t[e+"0"],t[r][1]=t[e+"1"],t[r][2]=t[e+"2"],t[r][3]=t[e+"3"],t[r][4]=t[e+"4"],t[r][5]=t[e+"5"],t[r][6]=t[e+"6"],t[r][7]=t[e+"7"],t[r][8]=t[e+"8"],t[r][9]=t[e+"9"],t[r][10]=t[e+"10"],t[r][11]=t[e+"11"],t[r][12]=t[e+"12"],t[r][13]=t[e+"13"],t[r][14]=t[e+"0"],t[r][15]=t[e+"0"]}function nt(t){return t==Hn||t==Wn||t==Vn||t==Gn}function it(){this.eb=[],this.size=this.A=this.fb=0}function at(){this.y=[],this.f=[],this.ea=[],this.F=[],this.Tc=this.Ed=this.Cd=this.Fd=this.lb=this.Db=this.Ab=this.fa=this.J=this.W=this.N=this.O=0}function ot(){this.Rd=this.height=this.width=this.S=0,this.f={},this.f.RGBA=new it,this.f.kb=new at,this.sd=null}function st(){this.width=[0],this.height=[0],this.Pd=[0],this.Qd=[0],this.format=[0]}function ct(){this.Id=this.fd=this.Md=this.hb=this.ib=this.da=this.bd=this.cd=this.j=this.v=this.Da=this.Sd=this.ob=0}function ut(t){return alert("todo:WebPSamplerProcessPlane"),t.T}function ht(t,e){var r=t.T,i=e.ba.f.RGBA,a=i.eb,o=i.fb+t.ka*i.A,s=vi[e.ba.S],c=t.y,u=t.O,h=t.f,l=t.N,f=t.ea,d=t.W,p=e.cc,g=e.dc,m=e.Mc,v=e.Nc,b=t.ka,y=t.ka+t.T,w=t.U,N=w+1>>1;for(0==b?s(c,u,null,null,h,l,f,d,h,l,f,d,a,o,null,null,w):(s(e.ec,e.fc,c,u,p,g,m,v,h,l,f,d,a,o-i.A,a,o,w),++r);b+2<y;b+=2)p=h,g=l,m=f,v=d,l+=t.Rc,d+=t.Rc,o+=2*i.A,s(c,(u+=2*t.fa)-t.fa,c,u,p,g,m,v,h,l,f,d,a,o-i.A,a,o,w);return u+=t.fa,t.j+y<t.o?(n(e.ec,e.fc,c,u,w),n(e.cc,e.dc,h,l,N),n(e.Mc,e.Nc,f,d,N),r--):1&y||s(c,u,null,null,h,l,f,d,h,l,f,d,a,o+i.A,null,null,w),r}function lt(t,r,n){var i=t.F,a=[t.J];if(null!=i){var o=t.U,s=r.ba.S,c=s==Tn||s==Vn;r=r.ba.f.RGBA;var u=[0],h=t.ka;u[0]=t.T,t.Kb&&(0==h?--u[0]:(--h,a[0]-=t.width),t.j+t.ka+t.T==t.o&&(u[0]=t.o-t.j-h));var l=r.eb;h=r.fb+h*r.A;t=Sn(i,a[0],t.width,o,u,l,h+(c?0:3),r.A),e(n==u),t&&nt(s)&&An(l,h,c,o,u,r.A)}return 0}function ft(t){var e=t.ma,r=e.ba.S,n=11>r,i=r==qn||r==Rn||r==Tn||r==Un||12==r||nt(r);if(e.memory=null,e.Ib=null,e.Jb=null,e.Nd=null,!Mr(e.Oa,t,i?11:12))return 0;if(i&&nt(r)&&br(),t.da)alert("todo:use_scaling");else{if(n){if(e.Ib=ut,t.Kb){if(r=t.U+1>>1,e.memory=a(t.U+2*r),null==e.memory)return 0;e.ec=e.memory,e.fc=0,e.cc=e.ec,e.dc=e.fc+t.U,e.Mc=e.cc,e.Nc=e.dc+r,e.Ib=ht,br()}}else alert("todo:EmitYUV");i&&(e.Jb=lt,n&&mr())}if(n&&!Ci){for(t=0;256>t;++t)ji[t]=89858*(t-128)+_i>>Si,Mi[t]=-22014*(t-128)+_i,Bi[t]=-45773*(t-128),Oi[t]=113618*(t-128)+_i>>Si;for(t=Pi;t<ki;++t)e=76283*(t-16)+_i>>Si,Ei[t-Pi]=Vt(e,255),qi[t-Pi]=Vt(e+8>>4,15);Ci=1}return 1}function dt(t){var r=t.ma,n=t.U,i=t.T;return e(!(1&t.ka)),0>=n||0>=i?0:(n=r.Ib(t,r),null!=r.Jb&&r.Jb(t,r,n),r.Dc+=n,1)}function pt(t){t.ma.memory=null}function gt(t,e,r,n){return 47!=y(t,8)?0:(e[0]=y(t,14)+1,r[0]=y(t,14)+1,n[0]=y(t,1),0!=y(t,3)?0:!t.h)}function mt(t,e){if(4>t)return t+1;var r=t-2>>1;return(2+(1&t)<<r)+y(e,r)+1}function vt(t,e){return 120<e?e-120:1<=(r=((r=$n[e-1])>>4)*t+(8-(15&r)))?r:1;var r}function bt(t,e,r){var n=L(r),i=t[e+=255&n].g-8;return 0<i&&(x(r,r.u+8),n=L(r),e+=t[e].value,e+=n&(1<<i)-1),x(r,r.u+t[e].g),t[e].value}function yt(t,r,n){return n.g+=t.g,n.value+=t.value<<r>>>0,e(8>=n.g),t.g}function wt(t,r,n){var i=t.xc;return e((r=0==i?0:t.vc[t.md*(n>>i)+(r>>i)])<t.Wb),t.Ya[r]}function Nt(t,r,i,a){var o=t.ab,s=t.c*r,c=t.C;r=c+r;var u=i,h=a;for(a=t.Ta,i=t.Ua;0<o--;){var l=t.gc[o],f=c,d=r,p=u,g=h,m=(h=a,u=i,l.Ea);switch(e(f<d),e(d<=l.nc),l.hc){case 2:Gr(p,g,(d-f)*m,h,u);break;case 0:var v=f,b=d,y=h,w=u,N=(_=l).Ea;0==v&&(Wr(p,g,null,null,1,y,w),V(p,g+1,0,0,N-1,y,w+1),g+=N,w+=N,++v);for(var L=1<<_.b,A=L-1,x=q(N,_.b),S=_.K,_=_.w+(v>>_.b)*x;v<b;){var P=S,k=_,I=1;for(Vr(p,g,y,w-N,1,y,w);I<N;){var F=(I&~A)+L;F>N&&(F=N),(0,Zr[P[k++]>>8&15])(p,g+ +I,y,w+I-N,F-I,y,w+I),I=F}g+=N,w+=N,++v&A||(_+=x)}d!=l.nc&&n(h,u-m,h,u+(d-f-1)*m,m);break;case 1:for(m=p,b=g,N=(p=l.Ea)-(w=p&~(y=(g=1<<l.b)-1)),v=q(p,l.b),L=l.K,l=l.w+(f>>l.b)*v;f<d;){for(A=L,x=l,S=new T,_=b+w,P=b+p;b<_;)Y(A[x++],S),$r(S,m,b,g,h,u),b+=g,u+=g;b<P&&(Y(A[x++],S),$r(S,m,b,N,h,u),b+=N,u+=N),++f&y||(l+=v)}break;case 3:if(p==h&&g==u&&0<l.b){for(b=h,p=m=u+(d-f)*m-(w=(d-f)*q(l.Ea,l.b)),g=h,y=u,v=[],w=(N=w)-1;0<=w;--w)v[w]=g[y+w];for(w=N-1;0<=w;--w)b[p+w]=v[w];Yr(l,f,d,h,m,h,u)}else Yr(l,f,d,p,g,h,u)}u=a,h=i}h!=i&&n(a,i,u,h,s)}function Lt(t,r){var n=t.V,i=t.Ba+t.c*t.C,a=r-t.C;if(e(r<=t.l.o),e(16>=a),0<a){var o=t.l,s=t.Ta,c=t.Ua,u=o.width;if(Nt(t,a,n,i),a=c=[c],e((n=t.C)<(i=r)),e(o.v<o.va),i>o.o&&(i=o.o),n<o.j){var h=o.j-n;n=o.j;a[0]+=h*u}if(n>=i?n=0:(a[0]+=4*o.v,o.ka=n-o.j,o.U=o.va-o.v,o.T=i-n,n=1),n){if(c=c[0],11>(n=t.ca).S){var l=n.f.RGBA,f=(i=n.S,a=o.U,o=o.T,h=l.eb,l.A),d=o;for(l=l.fb+t.Ma*l.A;0<d--;){var p=s,g=c,m=a,v=h,b=l;switch(i){case En:Qr(p,g,m,v,b);break;case qn:tn(p,g,m,v,b);break;case Hn:tn(p,g,m,v,b),An(v,b,0,m,1,0);break;case Dn:nn(p,g,m,v,b);break;case Rn:et(p,g,m,v,b,1);break;case Wn:et(p,g,m,v,b,1),An(v,b,0,m,1,0);break;case Tn:et(p,g,m,v,b,0);break;case Vn:et(p,g,m,v,b,0),An(v,b,1,m,1,0);break;case Un:en(p,g,m,v,b);break;case Gn:en(p,g,m,v,b),xn(v,b,m,1,0);break;case zn:rn(p,g,m,v,b);break;default:e(0)}c+=u,l+=f}t.Ma+=o}else alert("todo:EmitRescaledRowsYUVA");e(t.Ma<=n.height)}}t.C=r,e(t.C<=t.i)}function At(t){var e;if(0<t.ua)return 0;for(e=0;e<t.Wb;++e){var r=t.Ya[e].G,n=t.Ya[e].H;if(0<r[1][n[1]+0].g||0<r[2][n[2]+0].g||0<r[3][n[3]+0].g)return 0}return 1}function xt(t,r,n,i,a,o){if(0!=t.Z){var s=t.qd,c=t.rd;for(e(null!=mi[t.Z]);r<n;++r)mi[t.Z](s,c,i,a,i,a,o),s=i,c=a,a+=o;t.qd=s,t.rd=c}}function St(t,r){var n=t.l.ma,i=0==n.Z||1==n.Z?t.l.j:t.C;i=t.C<i?i:t.C;if(e(r<=t.l.o),r>i){var a=t.l.width,o=n.ca,s=n.tb+a*i,c=t.V,u=t.Ba+t.c*i,h=t.gc;e(1==t.ab),e(3==h[0].hc),Xr(h[0],i,r,c,u,o,s),xt(n,i,r,o,s,a)}t.C=t.Ma=r}function _t(t,r,n,i,a,o,s){var c=t.$/i,u=t.$%i,h=t.m,l=t.s,f=n+t.$,d=f;a=n+i*a;var p=n+i*o,g=280+l.ua,m=t.Pb?c:16777216,v=0<l.ua?l.Wa:null,b=l.wc,y=f<p?wt(l,u,c):null;e(t.C<o),e(p<=a);var w=!1;t:for(;;){for(;w||f<p;){var N=0;if(c>=m){var _=f-n;e((m=t).Pb),m.wd=m.m,m.xd=_,0<m.s.ua&&B(m.s.Wa,m.s.vb),m=c+ti}if(u&b||(y=wt(l,u,c)),e(null!=y),y.Qb&&(r[f]=y.qb,w=!0),!w)if(S(h),y.jc){N=h,_=r;var P=f,k=y.pd[L(N)&Dr-1];e(y.jc),256>k.g?(x(N,N.u+k.g),_[P]=k.value,N=0):(x(N,N.u+k.g-256),e(256<=k.value),N=k.value),0==N&&(w=!0)}else N=bt(y.G[0],y.H[0],h);if(h.h)break;if(w||256>N){if(!w)if(y.nd)r[f]=(y.qb|N<<8)>>>0;else{if(S(h),w=bt(y.G[1],y.H[1],h),S(h),_=bt(y.G[2],y.H[2],h),P=bt(y.G[3],y.H[3],h),h.h)break;r[f]=(P<<24|w<<16|N<<8|_)>>>0}if(w=!1,++f,++u>=i&&(u=0,++c,null!=s&&c<=o&&!(c%16)&&s(t,c),null!=v))for(;d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N}else if(280>N){if(N=mt(N-256,h),_=bt(y.G[4],y.H[4],h),S(h),_=vt(i,_=mt(_,h)),h.h)break;if(f-n<_||a-f<N)break t;for(P=0;P<N;++P)r[f+P]=r[f+P-_];for(f+=N,u+=N;u>=i;)u-=i,++c,null!=s&&c<=o&&!(c%16)&&s(t,c);if(e(f<=a),u&b&&(y=wt(l,u,c)),null!=v)for(;d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N}else{if(!(N<g))break t;for(w=N-280,e(null!=v);d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N;N=f,e(!(w>>>(_=v).Xa)),r[N]=_.X[w],w=!0}w||e(h.h==A(h))}if(t.Pb&&h.h&&f<a)e(t.m.h),t.a=5,t.m=t.wd,t.$=t.xd,0<t.s.ua&&B(t.s.vb,t.s.Wa);else{if(h.h)break t;null!=s&&s(t,c>o?o:c),t.a=0,t.$=f-n}return 1}return t.a=3,0}function Pt(t){e(null!=t),t.vc=null,t.yc=null,t.Ya=null;var r=t.Wa;null!=r&&(r.X=null),t.vb=null,e(null!=t)}function kt(){var e=new or;return null==e?null:(e.a=0,e.xb=gi,rt("Predictor","VP8LPredictors"),rt("Predictor","VP8LPredictors_C"),rt("PredictorAdd","VP8LPredictorsAdd"),rt("PredictorAdd","VP8LPredictorsAdd_C"),Gr=G,$r=J,Qr=K,tn=Z,en=$,rn=Q,nn=tt,t.VP8LMapColor32b=Jr,t.VP8LMapColor8b=Kr,e)}function It(t,r,n,s,c){var u=1,f=[t],p=[r],g=s.m,m=s.s,v=null,b=0;t:for(;;){if(n)for(;u&&y(g,1);){var w=f,N=p,A=s,_=1,P=A.m,k=A.gc[A.ab],I=y(P,2);if(A.Oc&1<<I)u=0;else{switch(A.Oc|=1<<I,k.hc=I,k.Ea=w[0],k.nc=N[0],k.K=[null],++A.ab,e(4>=A.ab),I){case 0:case 1:k.b=y(P,3)+2,_=It(q(k.Ea,k.b),q(k.nc,k.b),0,A,k.K),k.K=k.K[0];break;case 3:var F,C=y(P,8)+1,j=16<C?0:4<C?1:2<C?2:3;if(w[0]=q(k.Ea,j),k.b=j,F=_=It(C,1,0,A,k.K)){var B,M=C,E=k,R=1<<(8>>E.b),T=a(R);if(null==T)F=0;else{var U=E.K[0],z=E.w;for(T[0]=E.K[0][0],B=1;B<1*M;++B)T[B]=D(U[z+B],T[B-1]);for(;B<4*R;++B)T[B]=0;E.K[0]=null,E.K[0]=T,F=1}}_=F;break;case 2:break;default:e(0)}u=_}}if(f=f[0],p=p[0],u&&y(g,1)&&!(u=1<=(b=y(g,4))&&11>=b)){s.a=3;break t}var H;if(H=u)e:{var W,V,G,Y=s,J=f,X=p,K=b,Z=n,$=Y.m,Q=Y.s,tt=[null],et=1,rt=0,nt=Qn[K];r:for(;;){if(Z&&y($,1)){var it=y($,3)+2,at=q(J,it),ot=q(X,it),st=at*ot;if(!It(at,ot,0,Y,tt))break r;for(tt=tt[0],Q.xc=it,W=0;W<st;++W){var ct=tt[W]>>8&65535;tt[W]=ct,ct>=et&&(et=ct+1)}}if($.h)break r;for(V=0;5>V;++V){var ut=Xn[V];!V&&0<K&&(ut+=1<<K),rt<ut&&(rt=ut)}var ht=o(et*nt,l),lt=et,ft=o(lt,d);if(null==ft)var dt=null;else e(65536>=lt),dt=ft;var pt=a(rt);if(null==dt||null==pt||null==ht){Y.a=1;break r}var gt=ht;for(W=G=0;W<et;++W){var mt=dt[W],vt=mt.G,bt=mt.H,wt=0,Nt=1,Lt=0;for(V=0;5>V;++V){ut=Xn[V],vt[V]=gt,bt[V]=G,!V&&0<K&&(ut+=1<<K);n:{var At,xt=ut,St=Y,kt=pt,Ft=gt,Ct=G,jt=0,Ot=St.m,Bt=y(Ot,1);if(i(kt,0,0,xt),Bt){var Mt=y(Ot,1)+1,Et=y(Ot,1),qt=y(Ot,0==Et?1:8);kt[qt]=1,2==Mt&&(kt[qt=y(Ot,8)]=1);var Dt=1}else{var Rt=a(19),Tt=y(Ot,4)+4;if(19<Tt){St.a=3;var Ut=0;break n}for(At=0;At<Tt;++At)Rt[Zn[At]]=y(Ot,3);var zt=void 0,Ht=void 0,Wt=St,Vt=Rt,Gt=xt,Yt=kt,Jt=0,Xt=Wt.m,Kt=8,Zt=o(128,l);i:for(;h(Zt,0,7,Vt,19);){if(y(Xt,1)){var $t=2+2*y(Xt,3);if((zt=2+y(Xt,$t))>Gt)break i}else zt=Gt;for(Ht=0;Ht<Gt&&zt--;){S(Xt);var Qt=Zt[0+(127&L(Xt))];x(Xt,Xt.u+Qt.g);var te=Qt.value;if(16>te)Yt[Ht++]=te,0!=te&&(Kt=te);else{var ee=16==te,re=te-16,ne=Jn[re],ie=y(Xt,Yn[re])+ne;if(Ht+ie>Gt)break i;for(var ae=ee?Kt:0;0<ie--;)Yt[Ht++]=ae}}Jt=1;break i}Jt||(Wt.a=3),Dt=Jt}(Dt=Dt&&!Ot.h)&&(jt=h(Ft,Ct,8,kt,xt)),Dt&&0!=jt?Ut=jt:(St.a=3,Ut=0)}if(0==Ut)break r;if(Nt&&1==Kn[V]&&(Nt=0==gt[G].g),wt+=gt[G].g,G+=Ut,3>=V){var oe,se=pt[0];for(oe=1;oe<ut;++oe)pt[oe]>se&&(se=pt[oe]);Lt+=se}}if(mt.nd=Nt,mt.Qb=0,Nt&&(mt.qb=(vt[3][bt[3]+0].value<<24|vt[1][bt[1]+0].value<<16|vt[2][bt[2]+0].value)>>>0,0==wt&&256>vt[0][bt[0]+0].value&&(mt.Qb=1,mt.qb+=vt[0][bt[0]+0].value<<8)),mt.jc=!mt.Qb&&6>Lt,mt.jc){var ce,ue=mt;for(ce=0;ce<Dr;++ce){var he=ce,le=ue.pd[he],fe=ue.G[0][ue.H[0]+he];256<=fe.value?(le.g=fe.g+256,le.value=fe.value):(le.g=0,le.value=0,he>>=yt(fe,8,le),he>>=yt(ue.G[1][ue.H[1]+he],16,le),he>>=yt(ue.G[2][ue.H[2]+he],0,le),yt(ue.G[3][ue.H[3]+he],24,le))}}}Q.vc=tt,Q.Wb=et,Q.Ya=dt,Q.yc=ht,H=1;break e}H=0}if(!(u=H)){s.a=3;break t}if(0<b){if(m.ua=1<<b,!O(m.Wa,b)){s.a=1,u=0;break t}}else m.ua=0;var de=s,pe=f,ge=p,me=de.s,ve=me.xc;if(de.c=pe,de.i=ge,me.md=q(pe,ve),me.wc=0==ve?-1:(1<<ve)-1,n){s.xb=pi;break t}if(null==(v=a(f*p))){s.a=1,u=0;break t}u=(u=_t(s,v,0,f,p,p,null))&&!g.h;break t}return u?(null!=c?c[0]=v:(e(null==v),e(n)),s.$=0,n||Pt(m)):Pt(m),u}function Ft(t,r){var n=t.c*t.i,i=n+r+16*r;return e(t.c<=r),t.V=a(i),null==t.V?(t.Ta=null,t.Ua=0,t.a=1,0):(t.Ta=t.V,t.Ua=t.Ba+n+r,1)}function Ct(t,r){var n=t.C,i=r-n,a=t.V,o=t.Ba+t.c*n;for(e(r<=t.l.o);0<i;){var s=16<i?16:i,c=t.l.ma,u=t.l.width,h=u*s,l=c.ca,f=c.tb+u*n,d=t.Ta,p=t.Ua;Nt(t,s,a,o),_n(d,p,l,f,h),xt(c,n,n+s,l,f,u),i-=s,a+=s*t.c,n+=s}e(n==r),t.C=t.Ma=r}function jt(){this.ub=this.yd=this.td=this.Rb=0}function Ot(){this.Kd=this.Ld=this.Ud=this.Td=this.i=this.c=0}function Bt(){this.Fb=this.Bb=this.Cb=0,this.Zb=a(4),this.Lb=a(4)}function Mt(){this.Yb=function(){var t=[];return function t(e,r,n){for(var i=n[r],a=0;a<i&&(e.push(n.length>r+1?[]:0),!(n.length<r+1));a++)t(e[a],r+1,n)}(t,0,[3,11]),t}()}function Et(){this.jb=a(3),this.Wc=s([4,8],Mt),this.Xc=s([4,17],Mt)}function qt(){this.Pc=this.wb=this.Tb=this.zd=0,this.vd=new a(4),this.od=new a(4)}function Dt(){this.ld=this.La=this.dd=this.tc=0}function Rt(){this.Na=this.la=0}function Tt(){this.Sc=[0,0],this.Eb=[0,0],this.Qc=[0,0],this.ia=this.lc=0}function Ut(){this.ad=a(384),this.Za=0,this.Ob=a(16),this.$b=this.Ad=this.ia=this.Gc=this.Hc=this.Dd=0}function zt(){this.uc=this.M=this.Nb=0,this.wa=Array(new Dt),this.Y=0,this.ya=Array(new Ut),this.aa=0,this.l=new Gt}function Ht(){this.y=a(16),this.f=a(8),this.ea=a(8)}function Wt(){this.cb=this.a=0,this.sc="",this.m=new w,this.Od=new jt,this.Kc=new Ot,this.ed=new qt,this.Qa=new Bt,this.Ic=this.$c=this.Aa=0,this.D=new zt,this.Xb=this.Va=this.Hb=this.zb=this.yb=this.Ub=this.za=0,this.Jc=o(8,w),this.ia=0,this.pb=o(4,Tt),this.Pa=new Et,this.Bd=this.kc=0,this.Ac=[],this.Bc=0,this.zc=[0,0,0,0],this.Gd=Array(new Ht),this.Hd=0,this.rb=Array(new Rt),this.sb=0,this.wa=Array(new Dt),this.Y=0,this.oc=[],this.pc=0,this.sa=[],this.ta=0,this.qa=[],this.ra=0,this.Ha=[],this.B=this.R=this.Ia=0,this.Ec=[],this.M=this.ja=this.Vb=this.Fc=0,this.ya=Array(new Ut),this.L=this.aa=0,this.gd=s([4,2],Dt),this.ga=null,this.Fa=[],this.Cc=this.qc=this.P=0,this.Gb=[],this.Uc=0,this.mb=[],this.nb=0,this.rc=[],this.Ga=this.Vc=0}function Vt(t,e){return 0>t?0:t>e?e:t}function Gt(){this.T=this.U=this.ka=this.height=this.width=0,this.y=[],this.f=[],this.ea=[],this.Rc=this.fa=this.W=this.N=this.O=0,this.ma="void",this.put="VP8IoPutHook",this.ac="VP8IoSetupHook",this.bc="VP8IoTeardownHook",this.ha=this.Kb=0,this.data=[],this.hb=this.ib=this.da=this.o=this.j=this.va=this.v=this.Da=this.ob=this.w=0,this.F=[],this.J=0}function Yt(){var t=new Wt;return null!=t&&(t.a=0,t.sc="OK",t.cb=0,t.Xb=0,ni||(ni=Zt)),t}function Jt(t,e,r){return 0==t.a&&(t.a=e,t.sc=r,t.cb=0),0}function Xt(t,e,r){return 3<=r&&157==t[e+0]&&1==t[e+1]&&42==t[e+2]}function Kt(t,r){if(null==t)return 0;if(t.a=0,t.sc="OK",null==r)return Jt(t,2,"null VP8Io passed to VP8GetHeaders()");var n=r.data,a=r.w,o=r.ha;if(4>o)return Jt(t,7,"Truncated header.");var s=n[a+0]|n[a+1]<<8|n[a+2]<<16,c=t.Od;if(c.Rb=!(1&s),c.td=s>>1&7,c.yd=s>>4&1,c.ub=s>>5,3<c.td)return Jt(t,3,"Incorrect keyframe parameters.");if(!c.yd)return Jt(t,4,"Frame not displayable.");a+=3,o-=3;var u=t.Kc;if(c.Rb){if(7>o)return Jt(t,7,"cannot parse picture header");if(!Xt(n,a,o))return Jt(t,3,"Bad code word");u.c=16383&(n[a+4]<<8|n[a+3]),u.Td=n[a+4]>>6,u.i=16383&(n[a+6]<<8|n[a+5]),u.Ud=n[a+6]>>6,a+=7,o-=7,t.za=u.c+15>>4,t.Ub=u.i+15>>4,r.width=u.c,r.height=u.i,r.Da=0,r.j=0,r.v=0,r.va=r.width,r.o=r.height,r.da=0,r.ib=r.width,r.hb=r.height,r.U=r.width,r.T=r.height,i((s=t.Pa).jb,0,255,s.jb.length),e(null!=(s=t.Qa)),s.Cb=0,s.Bb=0,s.Fb=1,i(s.Zb,0,0,s.Zb.length),i(s.Lb,0,0,s.Lb)}if(c.ub>o)return Jt(t,7,"bad partition length");p(s=t.m,n,a,c.ub),a+=c.ub,o-=c.ub,c.Rb&&(u.Ld=P(s),u.Kd=P(s)),u=t.Qa;var h,l=t.Pa;if(e(null!=s),e(null!=u),u.Cb=P(s),u.Cb){if(u.Bb=P(s),P(s)){for(u.Fb=P(s),h=0;4>h;++h)u.Zb[h]=P(s)?m(s,7):0;for(h=0;4>h;++h)u.Lb[h]=P(s)?m(s,6):0}if(u.Bb)for(h=0;3>h;++h)l.jb[h]=P(s)?g(s,8):255}else u.Bb=0;if(s.Ka)return Jt(t,3,"cannot parse segment header");if((u=t.ed).zd=P(s),u.Tb=g(s,6),u.wb=g(s,3),u.Pc=P(s),u.Pc&&P(s)){for(l=0;4>l;++l)P(s)&&(u.vd[l]=m(s,6));for(l=0;4>l;++l)P(s)&&(u.od[l]=m(s,6))}if(t.L=0==u.Tb?0:u.zd?1:2,s.Ka)return Jt(t,3,"cannot parse filter header");var f=o;if(o=h=a,a=h+f,u=f,t.Xb=(1<<g(t.m,2))-1,f<3*(l=t.Xb))n=7;else{for(h+=3*l,u-=3*l,f=0;f<l;++f){var d=n[o+0]|n[o+1]<<8|n[o+2]<<16;d>u&&(d=u),p(t.Jc[+f],n,h,d),h+=d,u-=d,o+=3}p(t.Jc[+l],n,h,u),n=h<a?0:5}if(0!=n)return Jt(t,n,"cannot parse partitions");for(n=g(h=t.m,7),o=P(h)?m(h,4):0,a=P(h)?m(h,4):0,u=P(h)?m(h,4):0,l=P(h)?m(h,4):0,h=P(h)?m(h,4):0,f=t.Qa,d=0;4>d;++d){if(f.Cb){var v=f.Zb[d];f.Fb||(v+=n)}else{if(0<d){t.pb[d]=t.pb[0];continue}v=n}var b=t.pb[d];b.Sc[0]=ei[Vt(v+o,127)],b.Sc[1]=ri[Vt(v+0,127)],b.Eb[0]=2*ei[Vt(v+a,127)],b.Eb[1]=101581*ri[Vt(v+u,127)]>>16,8>b.Eb[1]&&(b.Eb[1]=8),b.Qc[0]=ei[Vt(v+l,117)],b.Qc[1]=ri[Vt(v+h,127)],b.lc=v+h}if(!c.Rb)return Jt(t,4,"Not a key frame.");for(P(s),c=t.Pa,n=0;4>n;++n){for(o=0;8>o;++o)for(a=0;3>a;++a)for(u=0;11>u;++u)l=k(s,ui[n][o][a][u])?g(s,8):si[n][o][a][u],c.Wc[n][o].Yb[a][u]=l;for(o=0;17>o;++o)c.Xc[n][o]=c.Wc[n][hi[o]]}return t.kc=P(s),t.kc&&(t.Bd=g(s,8)),t.cb=1}function Zt(t,e,r,n,i,a,o){var s=e[i].Yb[r];for(r=0;16>i;++i){if(!k(t,s[r+0]))return i;for(;!k(t,s[r+1]);)if(s=e[++i].Yb[0],r=0,16==i)return 16;var c=e[i+1].Yb;if(k(t,s[r+2])){var u=t,h=0;if(k(u,(f=s)[(l=r)+3]))if(k(u,f[l+6])){for(s=0,l=2*(h=k(u,f[l+8]))+(f=k(u,f[l+9+h])),h=0,f=ii[l];f[s];++s)h+=h+k(u,f[s]);h+=3+(8<<l)}else k(u,f[l+7])?(h=7+2*k(u,165),h+=k(u,145)):h=5+k(u,159);else h=k(u,f[l+4])?3+k(u,f[l+5]):2;s=c[2]}else h=1,s=c[1];c=o+ai[i],0>(u=t).b&&_(u);var l,f=u.b,d=(l=u.Ca>>1)-(u.I>>f)>>31;--u.b,u.Ca+=d,u.Ca|=1,u.I-=(l+1&d)<<f,a[c]=((h^d)-d)*n[(0<i)+0]}return 16}function $t(t){var e=t.rb[t.sb-1];e.la=0,e.Na=0,i(t.zc,0,0,t.zc.length),t.ja=0}function Qt(t,r){if(null==t)return 0;if(null==r)return Jt(t,2,"NULL VP8Io parameter in VP8Decode().");if(!t.cb&&!Kt(t,r))return 0;if(e(t.cb),null==r.ac||r.ac(r)){r.ob&&(t.L=0);var s=Ri[t.L];if(2==t.L?(t.yb=0,t.zb=0):(t.yb=r.v-s>>4,t.zb=r.j-s>>4,0>t.yb&&(t.yb=0),0>t.zb&&(t.zb=0)),t.Va=r.o+15+s>>4,t.Hb=r.va+15+s>>4,t.Hb>t.za&&(t.Hb=t.za),t.Va>t.Ub&&(t.Va=t.Ub),0<t.L){var c=t.ed;for(s=0;4>s;++s){var u;if(t.Qa.Cb){var h=t.Qa.Lb[s];t.Qa.Fb||(h+=c.Tb)}else h=c.Tb;for(u=0;1>=u;++u){var l=t.gd[s][u],f=h;if(c.Pc&&(f+=c.vd[0],u&&(f+=c.od[0])),0<(f=0>f?0:63<f?63:f)){var d=f;0<c.wb&&((d=4<c.wb?d>>2:d>>1)>9-c.wb&&(d=9-c.wb)),1>d&&(d=1),l.dd=d,l.tc=2*f+d,l.ld=40<=f?2:15<=f?1:0}else l.tc=0;l.La=u}}}s=0}else Jt(t,6,"Frame setup failed"),s=t.a;if(s=0==s){if(s){t.$c=0,0<t.Aa||(t.Ic=Ui);t:{s=t.Ic;c=4*(d=t.za);var p=32*d,g=d+1,m=0<t.L?d*(0<t.Aa?2:1):0,v=(2==t.Aa?2:1)*d;if((l=c+832+(u=3*(16*s+Ri[t.L])/2*p)+(h=null!=t.Fa&&0<t.Fa.length?t.Kc.c*t.Kc.i:0))!=l)s=0;else{if(l>t.Vb){if(t.Vb=0,t.Ec=a(l),t.Fc=0,null==t.Ec){s=Jt(t,1,"no memory during frame initialization.");break t}t.Vb=l}l=t.Ec,f=t.Fc,t.Ac=l,t.Bc=f,f+=c,t.Gd=o(p,Ht),t.Hd=0,t.rb=o(g+1,Rt),t.sb=1,t.wa=m?o(m,Dt):null,t.Y=0,t.D.Nb=0,t.D.wa=t.wa,t.D.Y=t.Y,0<t.Aa&&(t.D.Y+=d),e(!0),t.oc=l,t.pc=f,f+=832,t.ya=o(v,Ut),t.aa=0,t.D.ya=t.ya,t.D.aa=t.aa,2==t.Aa&&(t.D.aa+=d),t.R=16*d,t.B=8*d,d=(p=Ri[t.L])*t.R,p=p/2*t.B,t.sa=l,t.ta=f+d,t.qa=t.sa,t.ra=t.ta+16*s*t.R+p,t.Ha=t.qa,t.Ia=t.ra+8*s*t.B+p,t.$c=0,f+=u,t.mb=h?l:null,t.nb=h?f:null,e(f+h<=t.Fc+t.Vb),$t(t),i(t.Ac,t.Bc,0,c),s=1}}if(s){if(r.ka=0,r.y=t.sa,r.O=t.ta,r.f=t.qa,r.N=t.ra,r.ea=t.Ha,r.Vd=t.Ia,r.fa=t.R,r.Rc=t.B,r.F=null,r.J=0,!Cn){for(s=-255;255>=s;++s)Pn[255+s]=0>s?-s:s;for(s=-1020;1020>=s;++s)kn[1020+s]=-128>s?-128:127<s?127:s;for(s=-112;112>=s;++s)In[112+s]=-16>s?-16:15<s?15:s;for(s=-255;510>=s;++s)Fn[255+s]=0>s?0:255<s?255:s;Cn=1}an=ue,on=ae,cn=oe,un=se,hn=ce,sn=ie,ln=Je,fn=Xe,dn=$e,pn=Qe,gn=Ke,mn=Ze,vn=tr,bn=er,yn=ze,wn=He,Nn=We,Ln=Ve,fi[0]=xe,fi[1]=le,fi[2]=Le,fi[3]=Ae,fi[4]=Se,fi[5]=Pe,fi[6]=_e,fi[7]=ke,fi[8]=Fe,fi[9]=Ie,li[0]=ve,li[1]=de,li[2]=pe,li[3]=ge,li[4]=be,li[5]=ye,li[6]=we,di[0]=Be,di[1]=fe,di[2]=Ce,di[3]=je,di[4]=Ee,di[5]=Me,di[6]=qe,s=1}else s=0}s&&(s=function(t,r){for(t.M=0;t.M<t.Va;++t.M){var o,s=t.Jc[t.M&t.Xb],c=t.m,u=t;for(o=0;o<u.za;++o){var h=c,l=u,f=l.Ac,d=l.Bc+4*o,p=l.zc,g=l.ya[l.aa+o];if(l.Qa.Bb?g.$b=k(h,l.Pa.jb[0])?2+k(h,l.Pa.jb[2]):k(h,l.Pa.jb[1]):g.$b=0,l.kc&&(g.Ad=k(h,l.Bd)),g.Za=!k(h,145)+0,g.Za){var m=g.Ob,v=0;for(l=0;4>l;++l){var b,y=p[0+l];for(b=0;4>b;++b){y=ci[f[d+b]][y];for(var w=oi[k(h,y[0])];0<w;)w=oi[2*w+k(h,y[w])];y=-w,f[d+b]=y}n(m,v,f,d,4),v+=4,p[0+l]=y}}else y=k(h,156)?k(h,128)?1:3:k(h,163)?2:0,g.Ob[0]=y,i(f,d,y,4),i(p,0,y,4);g.Dd=k(h,142)?k(h,114)?k(h,183)?1:3:2:0}if(u.m.Ka)return Jt(t,7,"Premature end-of-partition0 encountered.");for(;t.ja<t.za;++t.ja){if(u=s,h=(c=t).rb[c.sb-1],f=c.rb[c.sb+c.ja],o=c.ya[c.aa+c.ja],d=c.kc?o.Ad:0)h.la=f.la=0,o.Za||(h.Na=f.Na=0),o.Hc=0,o.Gc=0,o.ia=0;else{var N,L;h=f,f=u,d=c.Pa.Xc,p=c.ya[c.aa+c.ja],g=c.pb[p.$b];if(l=p.ad,m=0,v=c.rb[c.sb-1],y=b=0,i(l,m,0,384),p.Za)var A=0,x=d[3];else{w=a(16);var S=h.Na+v.Na;if(S=ni(f,d[1],S,g.Eb,0,w,0),h.Na=v.Na=(0<S)+0,1<S)an(w,0,l,m);else{var _=w[0]+3>>3;for(w=0;256>w;w+=16)l[m+w]=_}A=1,x=d[0]}var P=15&h.la,I=15&v.la;for(w=0;4>w;++w){var F=1&I;for(_=L=0;4>_;++_)P=P>>1|(F=(S=ni(f,x,S=F+(1&P),g.Sc,A,l,m))>A)<<7,L=L<<2|(3<S?3:1<S?2:0!=l[m+0]),m+=16;P>>=4,I=I>>1|F<<7,b=(b<<8|L)>>>0}for(x=P,A=I>>4,N=0;4>N;N+=2){for(L=0,P=h.la>>4+N,I=v.la>>4+N,w=0;2>w;++w){for(F=1&I,_=0;2>_;++_)S=F+(1&P),P=P>>1|(F=0<(S=ni(f,d[2],S,g.Qc,0,l,m)))<<3,L=L<<2|(3<S?3:1<S?2:0!=l[m+0]),m+=16;P>>=2,I=I>>1|F<<5}y|=L<<4*N,x|=P<<4<<N,A|=(240&I)<<N}h.la=x,v.la=A,p.Hc=b,p.Gc=y,p.ia=43690&y?0:g.ia,d=!(b|y)}if(0<c.L&&(c.wa[c.Y+c.ja]=c.gd[o.$b][o.Za],c.wa[c.Y+c.ja].La|=!d),u.Ka)return Jt(t,7,"Premature end-of-file encountered.")}if($t(t),c=r,u=1,o=(s=t).D,h=0<s.L&&s.M>=s.zb&&s.M<=s.Va,0==s.Aa)t:{if(o.M=s.M,o.uc=h,Or(s,o),u=1,o=(L=s.D).Nb,h=(y=Ri[s.L])*s.R,f=y/2*s.B,w=16*o*s.R,_=8*o*s.B,d=s.sa,p=s.ta-h+w,g=s.qa,l=s.ra-f+_,m=s.Ha,v=s.Ia-f+_,I=0==(P=L.M),b=P>=s.Va-1,2==s.Aa&&Or(s,L),L.uc)for(F=(S=s).D.M,e(S.D.uc),L=S.yb;L<S.Hb;++L){A=L,x=F;var C=(j=(U=S).D).Nb;N=U.R;var j=j.wa[j.Y+A],O=U.sa,B=U.ta+16*C*N+16*A,M=j.dd,E=j.tc;if(0!=E)if(e(3<=E),1==U.L)0<A&&wn(O,B,N,E+4),j.La&&Ln(O,B,N,E),0<x&&yn(O,B,N,E+4),j.La&&Nn(O,B,N,E);else{var q=U.B,D=U.qa,R=U.ra+8*C*q+8*A,T=U.Ha,U=U.Ia+8*C*q+8*A;C=j.ld;0<A&&(fn(O,B,N,E+4,M,C),pn(D,R,T,U,q,E+4,M,C)),j.La&&(mn(O,B,N,E,M,C),bn(D,R,T,U,q,E,M,C)),0<x&&(ln(O,B,N,E+4,M,C),dn(D,R,T,U,q,E+4,M,C)),j.La&&(gn(O,B,N,E,M,C),vn(D,R,T,U,q,E,M,C))}}if(s.ia&&alert("todo:DitherRow"),null!=c.put){if(L=16*P,P=16*(P+1),I?(c.y=s.sa,c.O=s.ta+w,c.f=s.qa,c.N=s.ra+_,c.ea=s.Ha,c.W=s.Ia+_):(L-=y,c.y=d,c.O=p,c.f=g,c.N=l,c.ea=m,c.W=v),b||(P-=y),P>c.o&&(P=c.o),c.F=null,c.J=null,null!=s.Fa&&0<s.Fa.length&&L<P&&(c.J=lr(s,c,L,P-L),c.F=s.mb,null==c.F&&0==c.F.length)){u=Jt(s,3,"Could not decode alpha data.");break t}L<c.j&&(y=c.j-L,L=c.j,e(!(1&y)),c.O+=s.R*y,c.N+=s.B*(y>>1),c.W+=s.B*(y>>1),null!=c.F&&(c.J+=c.width*y)),L<P&&(c.O+=c.v,c.N+=c.v>>1,c.W+=c.v>>1,null!=c.F&&(c.J+=c.v),c.ka=L-c.j,c.U=c.va-c.v,c.T=P-L,u=c.put(c))}o+1!=s.Ic||b||(n(s.sa,s.ta-h,d,p+16*s.R,h),n(s.qa,s.ra-f,g,l+8*s.B,f),n(s.Ha,s.Ia-f,m,v+8*s.B,f))}if(!u)return Jt(t,6,"Output aborted.")}return 1}(t,r)),null!=r.bc&&r.bc(r),s&=1}return s?(t.cb=0,s):0}function te(t,e,r,n,i){i=t[e+r+32*n]+(i>>3),t[e+r+32*n]=-256&i?0>i?0:255:i}function ee(t,e,r,n,i,a){te(t,e,0,r,n+i),te(t,e,1,r,n+a),te(t,e,2,r,n-a),te(t,e,3,r,n-i)}function re(t){return(20091*t>>16)+t}function ne(t,e,r,n){var i,o=0,s=a(16);for(i=0;4>i;++i){var c=t[e+0]+t[e+8],u=t[e+0]-t[e+8],h=(35468*t[e+4]>>16)-re(t[e+12]),l=re(t[e+4])+(35468*t[e+12]>>16);s[o+0]=c+l,s[o+1]=u+h,s[o+2]=u-h,s[o+3]=c-l,o+=4,e++}for(i=o=0;4>i;++i)c=(t=s[o+0]+4)+s[o+8],u=t-s[o+8],h=(35468*s[o+4]>>16)-re(s[o+12]),te(r,n,0,0,c+(l=re(s[o+4])+(35468*s[o+12]>>16))),te(r,n,1,0,u+h),te(r,n,2,0,u-h),te(r,n,3,0,c-l),o++,n+=32}function ie(t,e,r,n){var i=t[e+0]+4,a=35468*t[e+4]>>16,o=re(t[e+4]),s=35468*t[e+1]>>16;ee(r,n,0,i+o,t=re(t[e+1]),s),ee(r,n,1,i+a,t,s),ee(r,n,2,i-a,t,s),ee(r,n,3,i-o,t,s)}function ae(t,e,r,n,i){ne(t,e,r,n),i&&ne(t,e+16,r,n+4)}function oe(t,e,r,n){on(t,e+0,r,n,1),on(t,e+32,r,n+128,1)}function se(t,e,r,n){var i;for(t=t[e+0]+4,i=0;4>i;++i)for(e=0;4>e;++e)te(r,n,e,i,t)}function ce(t,e,r,n){t[e+0]&&un(t,e+0,r,n),t[e+16]&&un(t,e+16,r,n+4),t[e+32]&&un(t,e+32,r,n+128),t[e+48]&&un(t,e+48,r,n+128+4)}function ue(t,e,r,n){var i,o=a(16);for(i=0;4>i;++i){var s=t[e+0+i]+t[e+12+i],c=t[e+4+i]+t[e+8+i],u=t[e+4+i]-t[e+8+i],h=t[e+0+i]-t[e+12+i];o[0+i]=s+c,o[8+i]=s-c,o[4+i]=h+u,o[12+i]=h-u}for(i=0;4>i;++i)s=(t=o[0+4*i]+3)+o[3+4*i],c=o[1+4*i]+o[2+4*i],u=o[1+4*i]-o[2+4*i],h=t-o[3+4*i],r[n+0]=s+c>>3,r[n+16]=h+u>>3,r[n+32]=s-c>>3,r[n+48]=h-u>>3,n+=64}function he(t,e,r){var n,i=e-32,a=Bn,o=255-t[i-1];for(n=0;n<r;++n){var s,c=a,u=o+t[e-1];for(s=0;s<r;++s)t[e+s]=c[u+t[i+s]];e+=32}}function le(t,e){he(t,e,4)}function fe(t,e){he(t,e,8)}function de(t,e){he(t,e,16)}function pe(t,e){var r;for(r=0;16>r;++r)n(t,e+32*r,t,e-32,16)}function ge(t,e){var r;for(r=16;0<r;--r)i(t,e,t[e-1],16),e+=32}function me(t,e,r){var n;for(n=0;16>n;++n)i(e,r+32*n,t,16)}function ve(t,e){var r,n=16;for(r=0;16>r;++r)n+=t[e-1+32*r]+t[e+r-32];me(n>>5,t,e)}function be(t,e){var r,n=8;for(r=0;16>r;++r)n+=t[e-1+32*r];me(n>>4,t,e)}function ye(t,e){var r,n=8;for(r=0;16>r;++r)n+=t[e+r-32];me(n>>4,t,e)}function we(t,e){me(128,t,e)}function Ne(t,e,r){return t+2*e+r+2>>2}function Le(t,e){var r,i=e-32;i=new Uint8Array([Ne(t[i-1],t[i+0],t[i+1]),Ne(t[i+0],t[i+1],t[i+2]),Ne(t[i+1],t[i+2],t[i+3]),Ne(t[i+2],t[i+3],t[i+4])]);for(r=0;4>r;++r)n(t,e+32*r,i,0,i.length)}function Ae(t,e){var r=t[e-1],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96];I(t,e+0,16843009*Ne(t[e-1-32],r,n)),I(t,e+32,16843009*Ne(r,n,i)),I(t,e+64,16843009*Ne(n,i,a)),I(t,e+96,16843009*Ne(i,a,a))}function xe(t,e){var r,n=4;for(r=0;4>r;++r)n+=t[e+r-32]+t[e-1+32*r];for(n>>=3,r=0;4>r;++r)i(t,e+32*r,n,4)}function Se(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1-32],o=t[e+0-32],s=t[e+1-32],c=t[e+2-32],u=t[e+3-32];t[e+0+96]=Ne(n,i,t[e-1+96]),t[e+1+96]=t[e+0+64]=Ne(r,n,i),t[e+2+96]=t[e+1+64]=t[e+0+32]=Ne(a,r,n),t[e+3+96]=t[e+2+64]=t[e+1+32]=t[e+0+0]=Ne(o,a,r),t[e+3+64]=t[e+2+32]=t[e+1+0]=Ne(s,o,a),t[e+3+32]=t[e+2+0]=Ne(c,s,o),t[e+3+0]=Ne(u,c,s)}function _e(t,e){var r=t[e+1-32],n=t[e+2-32],i=t[e+3-32],a=t[e+4-32],o=t[e+5-32],s=t[e+6-32],c=t[e+7-32];t[e+0+0]=Ne(t[e+0-32],r,n),t[e+1+0]=t[e+0+32]=Ne(r,n,i),t[e+2+0]=t[e+1+32]=t[e+0+64]=Ne(n,i,a),t[e+3+0]=t[e+2+32]=t[e+1+64]=t[e+0+96]=Ne(i,a,o),t[e+3+32]=t[e+2+64]=t[e+1+96]=Ne(a,o,s),t[e+3+64]=t[e+2+96]=Ne(o,s,c),t[e+3+96]=Ne(s,c,c)}function Pe(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1-32],o=t[e+0-32],s=t[e+1-32],c=t[e+2-32],u=t[e+3-32];t[e+0+0]=t[e+1+64]=a+o+1>>1,t[e+1+0]=t[e+2+64]=o+s+1>>1,t[e+2+0]=t[e+3+64]=s+c+1>>1,t[e+3+0]=c+u+1>>1,t[e+0+96]=Ne(i,n,r),t[e+0+64]=Ne(n,r,a),t[e+0+32]=t[e+1+96]=Ne(r,a,o),t[e+1+32]=t[e+2+96]=Ne(a,o,s),t[e+2+32]=t[e+3+96]=Ne(o,s,c),t[e+3+32]=Ne(s,c,u)}function ke(t,e){var r=t[e+0-32],n=t[e+1-32],i=t[e+2-32],a=t[e+3-32],o=t[e+4-32],s=t[e+5-32],c=t[e+6-32],u=t[e+7-32];t[e+0+0]=r+n+1>>1,t[e+1+0]=t[e+0+64]=n+i+1>>1,t[e+2+0]=t[e+1+64]=i+a+1>>1,t[e+3+0]=t[e+2+64]=a+o+1>>1,t[e+0+32]=Ne(r,n,i),t[e+1+32]=t[e+0+96]=Ne(n,i,a),t[e+2+32]=t[e+1+96]=Ne(i,a,o),t[e+3+32]=t[e+2+96]=Ne(a,o,s),t[e+3+64]=Ne(o,s,c),t[e+3+96]=Ne(s,c,u)}function Ie(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96];t[e+0+0]=r+n+1>>1,t[e+2+0]=t[e+0+32]=n+i+1>>1,t[e+2+32]=t[e+0+64]=i+a+1>>1,t[e+1+0]=Ne(r,n,i),t[e+3+0]=t[e+1+32]=Ne(n,i,a),t[e+3+32]=t[e+1+64]=Ne(i,a,a),t[e+3+64]=t[e+2+64]=t[e+0+96]=t[e+1+96]=t[e+2+96]=t[e+3+96]=a}function Fe(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96],o=t[e-1-32],s=t[e+0-32],c=t[e+1-32],u=t[e+2-32];t[e+0+0]=t[e+2+32]=r+o+1>>1,t[e+0+32]=t[e+2+64]=n+r+1>>1,t[e+0+64]=t[e+2+96]=i+n+1>>1,t[e+0+96]=a+i+1>>1,t[e+3+0]=Ne(s,c,u),t[e+2+0]=Ne(o,s,c),t[e+1+0]=t[e+3+32]=Ne(r,o,s),t[e+1+32]=t[e+3+64]=Ne(n,r,o),t[e+1+64]=t[e+3+96]=Ne(i,n,r),t[e+1+96]=Ne(a,i,n)}function Ce(t,e){var r;for(r=0;8>r;++r)n(t,e+32*r,t,e-32,8)}function je(t,e){var r;for(r=0;8>r;++r)i(t,e,t[e-1],8),e+=32}function Oe(t,e,r){var n;for(n=0;8>n;++n)i(e,r+32*n,t,8)}function Be(t,e){var r,n=8;for(r=0;8>r;++r)n+=t[e+r-32]+t[e-1+32*r];Oe(n>>4,t,e)}function Me(t,e){var r,n=4;for(r=0;8>r;++r)n+=t[e+r-32];Oe(n>>3,t,e)}function Ee(t,e){var r,n=4;for(r=0;8>r;++r)n+=t[e-1+32*r];Oe(n>>3,t,e)}function qe(t,e){Oe(128,t,e)}function De(t,e,r){var n=t[e-r],i=t[e+0],a=3*(i-n)+jn[1020+t[e-2*r]-t[e+r]],o=On[112+(a+4>>3)];t[e-r]=Bn[255+n+On[112+(a+3>>3)]],t[e+0]=Bn[255+i-o]}function Re(t,e,r,n){var i=t[e+0],a=t[e+r];return Mn[255+t[e-2*r]-t[e-r]]>n||Mn[255+a-i]>n}function Te(t,e,r,n){return 4*Mn[255+t[e-r]-t[e+0]]+Mn[255+t[e-2*r]-t[e+r]]<=n}function Ue(t,e,r,n,i){var a=t[e-3*r],o=t[e-2*r],s=t[e-r],c=t[e+0],u=t[e+r],h=t[e+2*r],l=t[e+3*r];return 4*Mn[255+s-c]+Mn[255+o-u]>n?0:Mn[255+t[e-4*r]-a]<=i&&Mn[255+a-o]<=i&&Mn[255+o-s]<=i&&Mn[255+l-h]<=i&&Mn[255+h-u]<=i&&Mn[255+u-c]<=i}function ze(t,e,r,n){var i=2*n+1;for(n=0;16>n;++n)Te(t,e+n,r,i)&&De(t,e+n,r)}function He(t,e,r,n){var i=2*n+1;for(n=0;16>n;++n)Te(t,e+n*r,1,i)&&De(t,e+n*r,1)}function We(t,e,r,n){var i;for(i=3;0<i;--i)ze(t,e+=4*r,r,n)}function Ve(t,e,r,n){var i;for(i=3;0<i;--i)He(t,e+=4,r,n)}function Ge(t,e,r,n,i,a,o,s){for(a=2*a+1;0<i--;){if(Ue(t,e,r,a,o))if(Re(t,e,r,s))De(t,e,r);else{var c=t,u=e,h=r,l=c[u-2*h],f=c[u-h],d=c[u+0],p=c[u+h],g=c[u+2*h],m=27*(b=jn[1020+3*(d-f)+jn[1020+l-p]])+63>>7,v=18*b+63>>7,b=9*b+63>>7;c[u-3*h]=Bn[255+c[u-3*h]+b],c[u-2*h]=Bn[255+l+v],c[u-h]=Bn[255+f+m],c[u+0]=Bn[255+d-m],c[u+h]=Bn[255+p-v],c[u+2*h]=Bn[255+g-b]}e+=n}}function Ye(t,e,r,n,i,a,o,s){for(a=2*a+1;0<i--;){if(Ue(t,e,r,a,o))if(Re(t,e,r,s))De(t,e,r);else{var c=t,u=e,h=r,l=c[u-h],f=c[u+0],d=c[u+h],p=On[112+((g=3*(f-l))+4>>3)],g=On[112+(g+3>>3)],m=p+1>>1;c[u-2*h]=Bn[255+c[u-2*h]+m],c[u-h]=Bn[255+l+g],c[u+0]=Bn[255+f-p],c[u+h]=Bn[255+d-m]}e+=n}}function Je(t,e,r,n,i,a){Ge(t,e,r,1,16,n,i,a)}function Xe(t,e,r,n,i,a){Ge(t,e,1,r,16,n,i,a)}function Ke(t,e,r,n,i,a){var o;for(o=3;0<o;--o)Ye(t,e+=4*r,r,1,16,n,i,a)}function Ze(t,e,r,n,i,a){var o;for(o=3;0<o;--o)Ye(t,e+=4,1,r,16,n,i,a)}function $e(t,e,r,n,i,a,o,s){Ge(t,e,i,1,8,a,o,s),Ge(r,n,i,1,8,a,o,s)}function Qe(t,e,r,n,i,a,o,s){Ge(t,e,1,i,8,a,o,s),Ge(r,n,1,i,8,a,o,s)}function tr(t,e,r,n,i,a,o,s){Ye(t,e+4*i,i,1,8,a,o,s),Ye(r,n+4*i,i,1,8,a,o,s)}function er(t,e,r,n,i,a,o,s){Ye(t,e+4,1,i,8,a,o,s),Ye(r,n+4,1,i,8,a,o,s)}function rr(){this.ba=new ot,this.ec=[],this.cc=[],this.Mc=[],this.Dc=this.Nc=this.dc=this.fc=0,this.Oa=new ct,this.memory=0,this.Ib="OutputFunc",this.Jb="OutputAlphaFunc",this.Nd="OutputRowFunc"}function nr(){this.data=[],this.offset=this.kd=this.ha=this.w=0,this.na=[],this.xa=this.gb=this.Ja=this.Sa=this.P=0}function ir(){this.nc=this.Ea=this.b=this.hc=0,this.K=[],this.w=0}function ar(){this.ua=0,this.Wa=new M,this.vb=new M,this.md=this.xc=this.wc=0,this.vc=[],this.Wb=0,this.Ya=new d,this.yc=new l}function or(){this.xb=this.a=0,this.l=new Gt,this.ca=new ot,this.V=[],this.Ba=0,this.Ta=[],this.Ua=0,this.m=new N,this.Pb=0,this.wd=new N,this.Ma=this.$=this.C=this.i=this.c=this.xd=0,this.s=new ar,this.ab=0,this.gc=o(4,ir),this.Oc=0}function sr(){this.Lc=this.Z=this.$a=this.i=this.c=0,this.l=new Gt,this.ic=0,this.ca=[],this.tb=0,this.qd=null,this.rd=0}function cr(t,e,r,n,i,a,o){for(t=null==t?0:t[e+0],e=0;e<o;++e)i[a+e]=t+r[n+e]&255,t=i[a+e]}function ur(t,e,r,n,i,a,o){var s;if(null==t)cr(null,null,r,n,i,a,o);else for(s=0;s<o;++s)i[a+s]=t[e+s]+r[n+s]&255}function hr(t,e,r,n,i,a,o){if(null==t)cr(null,null,r,n,i,a,o);else{var s,c=t[e+0],u=c,h=c;for(s=0;s<o;++s)u=h+(c=t[e+s])-u,h=r[n+s]+(-256&u?0>u?0:255:u)&255,u=c,i[a+s]=h}}function lr(t,r,i,o){var s=r.width,c=r.o;if(e(null!=t&&null!=r),0>i||0>=o||i+o>c)return null;if(!t.Cc){if(null==t.ga){var u;if(t.ga=new sr,(u=null==t.ga)||(u=r.width*r.o,e(0==t.Gb.length),t.Gb=a(u),t.Uc=0,null==t.Gb?u=0:(t.mb=t.Gb,t.nb=t.Uc,t.rc=null,u=1),u=!u),!u){u=t.ga;var h=t.Fa,l=t.P,f=t.qc,d=t.mb,p=t.nb,g=l+1,m=f-1,b=u.l;if(e(null!=h&&null!=d&&null!=r),mi[0]=null,mi[1]=cr,mi[2]=ur,mi[3]=hr,u.ca=d,u.tb=p,u.c=r.width,u.i=r.height,e(0<u.c&&0<u.i),1>=f)r=0;else if(u.$a=h[l+0]>>0&3,u.Z=h[l+0]>>2&3,u.Lc=h[l+0]>>4&3,l=h[l+0]>>6&3,0>u.$a||1<u.$a||4<=u.Z||1<u.Lc||l)r=0;else if(b.put=dt,b.ac=ft,b.bc=pt,b.ma=u,b.width=r.width,b.height=r.height,b.Da=r.Da,b.v=r.v,b.va=r.va,b.j=r.j,b.o=r.o,u.$a)t:{e(1==u.$a),r=kt();e:for(;;){if(null==r){r=0;break t}if(e(null!=u),u.mc=r,r.c=u.c,r.i=u.i,r.l=u.l,r.l.ma=u,r.l.width=u.c,r.l.height=u.i,r.a=0,v(r.m,h,g,m),!It(u.c,u.i,1,r,null))break e;if(1==r.ab&&3==r.gc[0].hc&&At(r.s)?(u.ic=1,h=r.c*r.i,r.Ta=null,r.Ua=0,r.V=a(h),r.Ba=0,null==r.V?(r.a=1,r=0):r=1):(u.ic=0,r=Ft(r,u.c)),!r)break e;r=1;break t}u.mc=null,r=0}else r=m>=u.c*u.i;u=!r}if(u)return null;1!=t.ga.Lc?t.Ga=0:o=c-i}e(null!=t.ga),e(i+o<=c);t:{if(r=(h=t.ga).c,c=h.l.o,0==h.$a){if(g=t.rc,m=t.Vc,b=t.Fa,l=t.P+1+i*r,f=t.mb,d=t.nb+i*r,e(l<=t.P+t.qc),0!=h.Z)for(e(null!=mi[h.Z]),u=0;u<o;++u)mi[h.Z](g,m,b,l,f,d,r),g=f,m=d,d+=r,l+=r;else for(u=0;u<o;++u)n(f,d,b,l,r),g=f,m=d,d+=r,l+=r;t.rc=g,t.Vc=m}else{if(e(null!=h.mc),r=i+o,e(null!=(u=h.mc)),e(r<=u.i),u.C>=r)r=1;else if(h.ic||mr(),h.ic){h=u.V,g=u.Ba,m=u.c;var y=u.i,w=(b=1,l=u.$/m,f=u.$%m,d=u.m,p=u.s,u.$),N=m*y,L=m*r,x=p.wc,_=w<L?wt(p,f,l):null;e(w<=N),e(r<=y),e(At(p));e:for(;;){for(;!d.h&&w<L;){if(f&x||(_=wt(p,f,l)),e(null!=_),S(d),256>(y=bt(_.G[0],_.H[0],d)))h[g+w]=y,++w,++f>=m&&(f=0,++l<=r&&!(l%16)&&St(u,l));else{if(!(280>y)){b=0;break e}y=mt(y-256,d);var P,k=bt(_.G[4],_.H[4],d);if(S(d),!(w>=(k=vt(m,k=mt(k,d)))&&N-w>=y)){b=0;break e}for(P=0;P<y;++P)h[g+w+P]=h[g+w+P-k];for(w+=y,f+=y;f>=m;)f-=m,++l<=r&&!(l%16)&&St(u,l);w<L&&f&x&&(_=wt(p,f,l))}e(d.h==A(d))}St(u,l>r?r:l);break e}!b||d.h&&w<N?(b=0,u.a=d.h?5:3):u.$=w,r=b}else r=_t(u,u.V,u.Ba,u.c,u.i,r,Ct);if(!r){o=0;break t}}i+o>=c&&(t.Cc=1),o=1}if(!o)return null;if(t.Cc&&(null!=(o=t.ga)&&(o.mc=null),t.ga=null,0<t.Ga))return alert("todo:WebPDequantizeLevels"),null}return t.nb+i*s}function fr(t,e,r,n,i,a){for(;0<i--;){var o,s=t,c=e+(r?1:0),u=t,h=e+(r?0:3);for(o=0;o<n;++o){var l=u[h+4*o];255!=l&&(l*=32897,s[c+4*o+0]=s[c+4*o+0]*l>>23,s[c+4*o+1]=s[c+4*o+1]*l>>23,s[c+4*o+2]=s[c+4*o+2]*l>>23)}e+=a}}function dr(t,e,r,n,i){for(;0<n--;){var a;for(a=0;a<r;++a){var o=t[e+2*a+0],s=15&(u=t[e+2*a+1]),c=4369*s,u=(240&u|u>>4)*c>>16;t[e+2*a+0]=(240&o|o>>4)*c>>16&240|(15&o|o<<4)*c>>16>>4&15,t[e+2*a+1]=240&u|s}e+=i}}function pr(t,e,r,n,i,a,o,s){var c,u,h=255;for(u=0;u<i;++u){for(c=0;c<n;++c){var l=t[e+c];a[o+4*c]=l,h&=l}e+=r,o+=s}return 255!=h}function gr(t,e,r,n,i){var a;for(a=0;a<i;++a)r[n+a]=t[e+a]>>8}function mr(){An=fr,xn=dr,Sn=pr,_n=gr}function vr(r,n,i){t[r]=function(t,r,a,o,s,c,u,h,l,f,d,p,g,m,v,b,y){var w,N=y-1>>1,L=s[c+0]|u[h+0]<<16,A=l[f+0]|d[p+0]<<16;e(null!=t);var x=3*L+A+131074>>2;for(n(t[r+0],255&x,x>>16,g,m),null!=a&&(x=3*A+L+131074>>2,n(a[o+0],255&x,x>>16,v,b)),w=1;w<=N;++w){var S=s[c+w]|u[h+w]<<16,_=l[f+w]|d[p+w]<<16,P=L+S+A+_+524296,k=P+2*(S+A)>>3;x=k+L>>1,L=(P=P+2*(L+_)>>3)+S>>1,n(t[r+2*w-1],255&x,x>>16,g,m+(2*w-1)*i),n(t[r+2*w-0],255&L,L>>16,g,m+(2*w-0)*i),null!=a&&(x=P+A>>1,L=k+_>>1,n(a[o+2*w-1],255&x,x>>16,v,b+(2*w-1)*i),n(a[o+2*w+0],255&L,L>>16,v,b+(2*w+0)*i)),L=S,A=_}1&y||(x=3*L+A+131074>>2,n(t[r+y-1],255&x,x>>16,g,m+(y-1)*i),null!=a&&(x=3*A+L+131074>>2,n(a[o+y-1],255&x,x>>16,v,b+(y-1)*i)))}}function br(){vi[En]=bi,vi[qn]=wi,vi[Dn]=yi,vi[Rn]=Ni,vi[Tn]=Li,vi[Un]=Ai,vi[zn]=xi,vi[Hn]=wi,vi[Wn]=Ni,vi[Vn]=Li,vi[Gn]=Ai}function yr(t){return t&~Fi?0>t?0:255:t>>Ii}function wr(t,e){return yr((19077*t>>8)+(26149*e>>8)-14234)}function Nr(t,e,r){return yr((19077*t>>8)-(6419*e>>8)-(13320*r>>8)+8708)}function Lr(t,e){return yr((19077*t>>8)+(33050*e>>8)-17685)}function Ar(t,e,r,n,i){n[i+0]=wr(t,r),n[i+1]=Nr(t,e,r),n[i+2]=Lr(t,e)}function xr(t,e,r,n,i){n[i+0]=Lr(t,e),n[i+1]=Nr(t,e,r),n[i+2]=wr(t,r)}function Sr(t,e,r,n,i){var a=Nr(t,e,r);e=a<<3&224|Lr(t,e)>>3,n[i+0]=248&wr(t,r)|a>>5,n[i+1]=e}function _r(t,e,r,n,i){var a=240&Lr(t,e)|15;n[i+0]=240&wr(t,r)|Nr(t,e,r)>>4,n[i+1]=a}function Pr(t,e,r,n,i){n[i+0]=255,Ar(t,e,r,n,i+1)}function kr(t,e,r,n,i){xr(t,e,r,n,i),n[i+3]=255}function Ir(t,e,r,n,i){Ar(t,e,r,n,i),n[i+3]=255}function Vt(t,e){return 0>t?0:t>e?e:t}function Fr(e,r,n){t[e]=function(t,e,i,a,o,s,c,u,h){for(var l=u+(-2&h)*n;u!=l;)r(t[e+0],i[a+0],o[s+0],c,u),r(t[e+1],i[a+0],o[s+0],c,u+n),e+=2,++a,++s,u+=2*n;1&h&&r(t[e+0],i[a+0],o[s+0],c,u)}}function Cr(t,e,r){return 0==r?0==t?0==e?6:5:0==e?4:0:r}function jr(t,e,r,n,i){switch(t>>>30){case 3:on(e,r,n,i,0);break;case 2:sn(e,r,n,i);break;case 1:un(e,r,n,i)}}function Or(t,e){var r,a,o=e.M,s=e.Nb,c=t.oc,u=t.pc+40,h=t.oc,l=t.pc+584,f=t.oc,d=t.pc+600;for(r=0;16>r;++r)c[u+32*r-1]=129;for(r=0;8>r;++r)h[l+32*r-1]=129,f[d+32*r-1]=129;for(0<o?c[u-1-32]=h[l-1-32]=f[d-1-32]=129:(i(c,u-32-1,127,21),i(h,l-32-1,127,9),i(f,d-32-1,127,9)),a=0;a<t.za;++a){var p=e.ya[e.aa+a];if(0<a){for(r=-1;16>r;++r)n(c,u+32*r-4,c,u+32*r+12,4);for(r=-1;8>r;++r)n(h,l+32*r-4,h,l+32*r+4,4),n(f,d+32*r-4,f,d+32*r+4,4)}var g=t.Gd,m=t.Hd+a,v=p.ad,b=p.Hc;if(0<o&&(n(c,u-32,g[m].y,0,16),n(h,l-32,g[m].f,0,8),n(f,d-32,g[m].ea,0,8)),p.Za){var y=c,w=u-32+16;for(0<o&&(a>=t.za-1?i(y,w,g[m].y[15],4):n(y,w,g[m+1].y,0,4)),r=0;4>r;r++)y[w+128+r]=y[w+256+r]=y[w+384+r]=y[w+0+r];for(r=0;16>r;++r,b<<=2)y=c,w=u+Di[r],fi[p.Ob[r]](y,w),jr(b,v,16*+r,y,w)}else if(y=Cr(a,o,p.Ob[0]),li[y](c,u),0!=b)for(r=0;16>r;++r,b<<=2)jr(b,v,16*+r,c,u+Di[r]);for(r=p.Gc,y=Cr(a,o,p.Dd),di[y](h,l),di[y](f,d),b=v,y=h,w=l,255&(p=r>>0)&&(170&p?cn(b,256,y,w):hn(b,256,y,w)),p=f,b=d,255&(r>>=8)&&(170&r?cn(v,320,p,b):hn(v,320,p,b)),o<t.Ub-1&&(n(g[m].y,0,c,u+480,16),n(g[m].f,0,h,l+224,8),n(g[m].ea,0,f,d+224,8)),r=8*s*t.B,g=t.sa,m=t.ta+16*a+16*s*t.R,v=t.qa,p=t.ra+8*a+r,b=t.Ha,y=t.Ia+8*a+r,r=0;16>r;++r)n(g,m+r*t.R,c,u+32*r,16);for(r=0;8>r;++r)n(v,p+r*t.B,h,l+32*r,8),n(b,y+r*t.B,f,d+32*r,8)}}function Br(t,n,i,a,o,s,c,u,h){var l=[0],f=[0],d=0,p=null!=h?h.kd:0,g=null!=h?h:new nr;if(null==t||12>i)return 7;g.data=t,g.w=n,g.ha=i,n=[n],i=[i],g.gb=[g.gb];t:{var m=n,b=i,y=g.gb;if(e(null!=t),e(null!=b),e(null!=y),y[0]=0,12<=b[0]&&!r(t,m[0],"RIFF")){if(r(t,m[0]+8,"WEBP")){y=3;break t}var w=j(t,m[0]+4);if(12>w||4294967286<w){y=3;break t}if(p&&w>b[0]-8){y=7;break t}y[0]=w,m[0]+=12,b[0]-=12}y=0}if(0!=y)return y;for(w=0<g.gb[0],i=i[0];;){t:{var L=t;b=n,y=i;var A=l,x=f,S=m=[0];if((k=d=[d])[0]=0,8>y[0])y=7;else{if(!r(L,b[0],"VP8X")){if(10!=j(L,b[0]+4)){y=3;break t}if(18>y[0]){y=7;break t}var _=j(L,b[0]+8),P=1+C(L,b[0]+12);if(2147483648<=P*(L=1+C(L,b[0]+15))){y=3;break t}null!=S&&(S[0]=_),null!=A&&(A[0]=P),null!=x&&(x[0]=L),b[0]+=18,y[0]-=18,k[0]=1}y=0}}if(d=d[0],m=m[0],0!=y)return y;if(b=!!(2&m),!w&&d)return 3;if(null!=s&&(s[0]=!!(16&m)),null!=c&&(c[0]=b),null!=u&&(u[0]=0),c=l[0],m=f[0],d&&b&&null==h){y=0;break}if(4>i){y=7;break}if(w&&d||!w&&!d&&!r(t,n[0],"ALPH")){i=[i],g.na=[g.na],g.P=[g.P],g.Sa=[g.Sa];t:{_=t,y=n,w=i;var k=g.gb;A=g.na,x=g.P,S=g.Sa;P=22,e(null!=_),e(null!=w),L=y[0];var I=w[0];for(e(null!=A),e(null!=S),A[0]=null,x[0]=null,S[0]=0;;){if(y[0]=L,w[0]=I,8>I){y=7;break t}var F=j(_,L+4);if(4294967286<F){y=3;break t}var O=8+F+1&-2;if(P+=O,0<k&&P>k){y=3;break t}if(!r(_,L,"VP8 ")||!r(_,L,"VP8L")){y=0;break t}if(I[0]<O){y=7;break t}r(_,L,"ALPH")||(A[0]=_,x[0]=L+8,S[0]=F),L+=O,I-=O}}if(i=i[0],g.na=g.na[0],g.P=g.P[0],g.Sa=g.Sa[0],0!=y)break}i=[i],g.Ja=[g.Ja],g.xa=[g.xa];t:if(k=t,y=n,w=i,A=g.gb[0],x=g.Ja,S=g.xa,_=y[0],L=!r(k,_,"VP8 "),P=!r(k,_,"VP8L"),e(null!=k),e(null!=w),e(null!=x),e(null!=S),8>w[0])y=7;else{if(L||P){if(k=j(k,_+4),12<=A&&k>A-12){y=3;break t}if(p&&k>w[0]-8){y=7;break t}x[0]=k,y[0]+=8,w[0]-=8,S[0]=P}else S[0]=5<=w[0]&&47==k[_+0]&&!(k[_+4]>>5),x[0]=w[0];y=0}if(i=i[0],g.Ja=g.Ja[0],g.xa=g.xa[0],n=n[0],0!=y)break;if(4294967286<g.Ja)return 3;if(null==u||b||(u[0]=g.xa?2:1),c=[c],m=[m],g.xa){if(5>i){y=7;break}u=c,p=m,b=s,null==t||5>i?t=0:5<=i&&47==t[n+0]&&!(t[n+4]>>5)?(w=[0],k=[0],A=[0],v(x=new N,t,n,i),gt(x,w,k,A)?(null!=u&&(u[0]=w[0]),null!=p&&(p[0]=k[0]),null!=b&&(b[0]=A[0]),t=1):t=0):t=0}else{if(10>i){y=7;break}u=m,null==t||10>i||!Xt(t,n+3,i-3)?t=0:(p=t[n+0]|t[n+1]<<8|t[n+2]<<16,b=16383&(t[n+7]<<8|t[n+6]),t=16383&(t[n+9]<<8|t[n+8]),1&p||3<(p>>1&7)||!(p>>4&1)||p>>5>=g.Ja||!b||!t?t=0:(c&&(c[0]=b),u&&(u[0]=t),t=1))}if(!t)return 3;if(c=c[0],m=m[0],d&&(l[0]!=c||f[0]!=m))return 3;null!=h&&(h[0]=g,h.offset=n-h.w,e(4294967286>n-h.w),e(h.offset==h.ha-i));break}return 0==y||7==y&&d&&null==h?(null!=s&&(s[0]|=null!=g.na&&0<g.na.length),null!=a&&(a[0]=c),null!=o&&(o[0]=m),0):y}function Mr(t,e,r){var n=e.width,i=e.height,a=0,o=0,s=n,c=i;if(e.Da=null!=t&&0<t.Da,e.Da&&(s=t.cd,c=t.bd,a=t.v,o=t.j,11>r||(a&=-2,o&=-2),0>a||0>o||0>=s||0>=c||a+s>n||o+c>i))return 0;if(e.v=a,e.j=o,e.va=a+s,e.o=o+c,e.U=s,e.T=c,e.da=null!=t&&0<t.da,e.da){if(!E(s,c,r=[t.ib],a=[t.hb]))return 0;e.ib=r[0],e.hb=a[0]}return e.ob=null!=t&&t.ob,e.Kb=null==t||!t.Sd,e.da&&(e.ob=e.ib<3*n/4&&e.hb<3*i/4,e.Kb=0),1}function Er(t){if(null==t)return 2;if(11>t.S){var e=t.f.RGBA;e.fb+=(t.height-1)*e.A,e.A=-e.A}else e=t.f.kb,t=t.height,e.O+=(t-1)*e.fa,e.fa=-e.fa,e.N+=(t-1>>1)*e.Ab,e.Ab=-e.Ab,e.W+=(t-1>>1)*e.Db,e.Db=-e.Db,null!=e.F&&(e.J+=(t-1)*e.lb,e.lb=-e.lb);return 0}function qr(t,e,r,n){if(null==n||0>=t||0>=e)return 2;if(null!=r){if(r.Da){var i=r.cd,o=r.bd,s=-2&r.v,c=-2&r.j;if(0>s||0>c||0>=i||0>=o||s+i>t||c+o>e)return 2;t=i,e=o}if(r.da){if(!E(t,e,i=[r.ib],o=[r.hb]))return 2;t=i[0],e=o[0]}}n.width=t,n.height=e;t:{var u=n.width,h=n.height;if(t=n.S,0>=u||0>=h||!(t>=En&&13>t))t=2;else{if(0>=n.Rd&&null==n.sd){s=o=i=e=0;var l=(c=u*zi[t])*h;if(11>t||(o=(h+1)/2*(e=(u+1)/2),12==t&&(s=(i=u)*h)),null==(h=a(l+2*o+s))){t=1;break t}n.sd=h,11>t?((u=n.f.RGBA).eb=h,u.fb=0,u.A=c,u.size=l):((u=n.f.kb).y=h,u.O=0,u.fa=c,u.Fd=l,u.f=h,u.N=0+l,u.Ab=e,u.Cd=o,u.ea=h,u.W=0+l+o,u.Db=e,u.Ed=o,12==t&&(u.F=h,u.J=0+l+2*o),u.Tc=s,u.lb=i)}if(e=1,i=n.S,o=n.width,s=n.height,i>=En&&13>i)if(11>i)t=n.f.RGBA,e&=(c=Math.abs(t.A))*(s-1)+o<=t.size,e&=c>=o*zi[i],e&=null!=t.eb;else{t=n.f.kb,c=(o+1)/2,l=(s+1)/2,u=Math.abs(t.fa);h=Math.abs(t.Ab);var f=Math.abs(t.Db),d=Math.abs(t.lb),p=d*(s-1)+o;e&=u*(s-1)+o<=t.Fd,e&=h*(l-1)+c<=t.Cd,e=(e&=f*(l-1)+c<=t.Ed)&u>=o&h>=c&f>=c,e&=null!=t.y,e&=null!=t.f,e&=null!=t.ea,12==i&&(e&=d>=o,e&=p<=t.Tc,e&=null!=t.F)}else e=0;t=e?0:2}}return 0!=t||null!=r&&r.fd&&(t=Er(n)),t}var Dr=64,Rr=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535,131071,262143,524287,1048575,2097151,4194303,8388607,16777215],Tr=24,Ur=32,zr=8,Hr=[0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7];R("Predictor0","PredictorAdd0"),t.Predictor0=function(){return 4278190080},t.Predictor1=function(t){return t},t.Predictor2=function(t,e,r){return e[r+0]},t.Predictor3=function(t,e,r){return e[r+1]},t.Predictor4=function(t,e,r){return e[r-1]},t.Predictor5=function(t,e,r){return U(U(t,e[r+1]),e[r+0])},t.Predictor6=function(t,e,r){return U(t,e[r-1])},t.Predictor7=function(t,e,r){return U(t,e[r+0])},t.Predictor8=function(t,e,r){return U(e[r-1],e[r+0])},t.Predictor9=function(t,e,r){return U(e[r+0],e[r+1])},t.Predictor10=function(t,e,r){return U(U(t,e[r-1]),U(e[r+0],e[r+1]))},t.Predictor11=function(t,e,r){var n=e[r+0];return 0>=W(n>>24&255,t>>24&255,(e=e[r-1])>>24&255)+W(n>>16&255,t>>16&255,e>>16&255)+W(n>>8&255,t>>8&255,e>>8&255)+W(255&n,255&t,255&e)?n:t},t.Predictor12=function(t,e,r){var n=e[r+0];return(z((t>>24&255)+(n>>24&255)-((e=e[r-1])>>24&255))<<24|z((t>>16&255)+(n>>16&255)-(e>>16&255))<<16|z((t>>8&255)+(n>>8&255)-(e>>8&255))<<8|z((255&t)+(255&n)-(255&e)))>>>0},t.Predictor13=function(t,e,r){var n=e[r-1];return(H((t=U(t,e[r+0]))>>24&255,n>>24&255)<<24|H(t>>16&255,n>>16&255)<<16|H(t>>8&255,n>>8&255)<<8|H(t>>0&255,n>>0&255))>>>0};var Wr=t.PredictorAdd0;t.PredictorAdd1=V,R("Predictor2","PredictorAdd2"),R("Predictor3","PredictorAdd3"),R("Predictor4","PredictorAdd4"),R("Predictor5","PredictorAdd5"),R("Predictor6","PredictorAdd6"),R("Predictor7","PredictorAdd7"),R("Predictor8","PredictorAdd8"),R("Predictor9","PredictorAdd9"),R("Predictor10","PredictorAdd10"),R("Predictor11","PredictorAdd11"),R("Predictor12","PredictorAdd12"),R("Predictor13","PredictorAdd13");var Vr=t.PredictorAdd2;X("ColorIndexInverseTransform","MapARGB","32b",(function(t){return t>>8&255}),(function(t){return t})),X("VP8LColorIndexInverseTransformAlpha","MapAlpha","8b",(function(t){return t}),(function(t){return t>>8&255}));var Gr,Yr=t.ColorIndexInverseTransform,Jr=t.MapARGB,Xr=t.VP8LColorIndexInverseTransformAlpha,Kr=t.MapAlpha,Zr=t.VP8LPredictorsAdd=[];Zr.length=16,(t.VP8LPredictors=[]).length=16,(t.VP8LPredictorsAdd_C=[]).length=16,(t.VP8LPredictors_C=[]).length=16;var $r,Qr,tn,en,rn,nn,an,on,sn,cn,un,hn,ln,fn,dn,pn,gn,mn,vn,bn,yn,wn,Nn,Ln,An,xn,Sn,_n,Pn=a(511),kn=a(2041),In=a(225),Fn=a(767),Cn=0,jn=kn,On=In,Bn=Fn,Mn=Pn,En=0,qn=1,Dn=2,Rn=3,Tn=4,Un=5,zn=6,Hn=7,Wn=8,Vn=9,Gn=10,Yn=[2,3,7],Jn=[3,3,11],Xn=[280,256,256,256,40],Kn=[0,1,1,1,0],Zn=[17,18,0,1,2,3,4,5,16,6,7,8,9,10,11,12,13,14,15],$n=[24,7,23,25,40,6,39,41,22,26,38,42,56,5,55,57,21,27,54,58,37,43,72,4,71,73,20,28,53,59,70,74,36,44,88,69,75,52,60,3,87,89,19,29,86,90,35,45,68,76,85,91,51,61,104,2,103,105,18,30,102,106,34,46,84,92,67,77,101,107,50,62,120,1,119,121,83,93,17,31,100,108,66,78,118,122,33,47,117,123,49,63,99,109,82,94,0,116,124,65,79,16,32,98,110,48,115,125,81,95,64,114,126,97,111,80,113,127,96,112],Qn=[2954,2956,2958,2962,2970,2986,3018,3082,3212,3468,3980,5004],ti=8,ei=[4,5,6,7,8,9,10,10,11,12,13,14,15,16,17,17,18,19,20,20,21,21,22,22,23,23,24,25,25,26,27,28,29,30,31,32,33,34,35,36,37,37,38,39,40,41,42,43,44,45,46,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,76,77,78,79,80,81,82,83,84,85,86,87,88,89,91,93,95,96,98,100,101,102,104,106,108,110,112,114,116,118,122,124,126,128,130,132,134,136,138,140,143,145,148,151,154,157],ri=[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,177,181,185,189,193,197,201,205,209,213,217,221,225,229,234,239,245,249,254,259,264,269,274,279,284],ni=null,ii=[[173,148,140,0],[176,155,140,135,0],[180,157,141,134,130,0],[254,254,243,230,196,177,153,140,133,130,129,0]],ai=[0,1,4,8,5,2,3,6,9,12,13,10,7,11,14,15],oi=[-0,1,-1,2,-2,3,4,6,-3,5,-4,-5,-6,7,-7,8,-8,-9],si=[[[[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128]],[[253,136,254,255,228,219,128,128,128,128,128],[189,129,242,255,227,213,255,219,128,128,128],[106,126,227,252,214,209,255,255,128,128,128]],[[1,98,248,255,236,226,255,255,128,128,128],[181,133,238,254,221,234,255,154,128,128,128],[78,134,202,247,198,180,255,219,128,128,128]],[[1,185,249,255,243,255,128,128,128,128,128],[184,150,247,255,236,224,128,128,128,128,128],[77,110,216,255,236,230,128,128,128,128,128]],[[1,101,251,255,241,255,128,128,128,128,128],[170,139,241,252,236,209,255,255,128,128,128],[37,116,196,243,228,255,255,255,128,128,128]],[[1,204,254,255,245,255,128,128,128,128,128],[207,160,250,255,238,128,128,128,128,128,128],[102,103,231,255,211,171,128,128,128,128,128]],[[1,152,252,255,240,255,128,128,128,128,128],[177,135,243,255,234,225,128,128,128,128,128],[80,129,211,255,194,224,128,128,128,128,128]],[[1,1,255,128,128,128,128,128,128,128,128],[246,1,255,128,128,128,128,128,128,128,128],[255,128,128,128,128,128,128,128,128,128,128]]],[[[198,35,237,223,193,187,162,160,145,155,62],[131,45,198,221,172,176,220,157,252,221,1],[68,47,146,208,149,167,221,162,255,223,128]],[[1,149,241,255,221,224,255,255,128,128,128],[184,141,234,253,222,220,255,199,128,128,128],[81,99,181,242,176,190,249,202,255,255,128]],[[1,129,232,253,214,197,242,196,255,255,128],[99,121,210,250,201,198,255,202,128,128,128],[23,91,163,242,170,187,247,210,255,255,128]],[[1,200,246,255,234,255,128,128,128,128,128],[109,178,241,255,231,245,255,255,128,128,128],[44,130,201,253,205,192,255,255,128,128,128]],[[1,132,239,251,219,209,255,165,128,128,128],[94,136,225,251,218,190,255,255,128,128,128],[22,100,174,245,186,161,255,199,128,128,128]],[[1,182,249,255,232,235,128,128,128,128,128],[124,143,241,255,227,234,128,128,128,128,128],[35,77,181,251,193,211,255,205,128,128,128]],[[1,157,247,255,236,231,255,255,128,128,128],[121,141,235,255,225,227,255,255,128,128,128],[45,99,188,251,195,217,255,224,128,128,128]],[[1,1,251,255,213,255,128,128,128,128,128],[203,1,248,255,255,128,128,128,128,128,128],[137,1,177,255,224,255,128,128,128,128,128]]],[[[253,9,248,251,207,208,255,192,128,128,128],[175,13,224,243,193,185,249,198,255,255,128],[73,17,171,221,161,179,236,167,255,234,128]],[[1,95,247,253,212,183,255,255,128,128,128],[239,90,244,250,211,209,255,255,128,128,128],[155,77,195,248,188,195,255,255,128,128,128]],[[1,24,239,251,218,219,255,205,128,128,128],[201,51,219,255,196,186,128,128,128,128,128],[69,46,190,239,201,218,255,228,128,128,128]],[[1,191,251,255,255,128,128,128,128,128,128],[223,165,249,255,213,255,128,128,128,128,128],[141,124,248,255,255,128,128,128,128,128,128]],[[1,16,248,255,255,128,128,128,128,128,128],[190,36,230,255,236,255,128,128,128,128,128],[149,1,255,128,128,128,128,128,128,128,128]],[[1,226,255,128,128,128,128,128,128,128,128],[247,192,255,128,128,128,128,128,128,128,128],[240,128,255,128,128,128,128,128,128,128,128]],[[1,134,252,255,255,128,128,128,128,128,128],[213,62,250,255,255,128,128,128,128,128,128],[55,93,255,128,128,128,128,128,128,128,128]],[[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128]]],[[[202,24,213,235,186,191,220,160,240,175,255],[126,38,182,232,169,184,228,174,255,187,128],[61,46,138,219,151,178,240,170,255,216,128]],[[1,112,230,250,199,191,247,159,255,255,128],[166,109,228,252,211,215,255,174,128,128,128],[39,77,162,232,172,180,245,178,255,255,128]],[[1,52,220,246,198,199,249,220,255,255,128],[124,74,191,243,183,193,250,221,255,255,128],[24,71,130,219,154,170,243,182,255,255,128]],[[1,182,225,249,219,240,255,224,128,128,128],[149,150,226,252,216,205,255,171,128,128,128],[28,108,170,242,183,194,254,223,255,255,128]],[[1,81,230,252,204,203,255,192,128,128,128],[123,102,209,247,188,196,255,233,128,128,128],[20,95,153,243,164,173,255,203,128,128,128]],[[1,222,248,255,216,213,128,128,128,128,128],[168,175,246,252,235,205,255,255,128,128,128],[47,116,215,255,211,212,255,255,128,128,128]],[[1,121,236,253,212,214,255,255,128,128,128],[141,84,213,252,201,202,255,219,128,128,128],[42,80,160,240,162,185,255,205,128,128,128]],[[1,1,255,128,128,128,128,128,128,128,128],[244,1,255,128,128,128,128,128,128,128,128],[238,1,255,128,128,128,128,128,128,128,128]]]],ci=[[[231,120,48,89,115,113,120,152,112],[152,179,64,126,170,118,46,70,95],[175,69,143,80,85,82,72,155,103],[56,58,10,171,218,189,17,13,152],[114,26,17,163,44,195,21,10,173],[121,24,80,195,26,62,44,64,85],[144,71,10,38,171,213,144,34,26],[170,46,55,19,136,160,33,206,71],[63,20,8,114,114,208,12,9,226],[81,40,11,96,182,84,29,16,36]],[[134,183,89,137,98,101,106,165,148],[72,187,100,130,157,111,32,75,80],[66,102,167,99,74,62,40,234,128],[41,53,9,178,241,141,26,8,107],[74,43,26,146,73,166,49,23,157],[65,38,105,160,51,52,31,115,128],[104,79,12,27,217,255,87,17,7],[87,68,71,44,114,51,15,186,23],[47,41,14,110,182,183,21,17,194],[66,45,25,102,197,189,23,18,22]],[[88,88,147,150,42,46,45,196,205],[43,97,183,117,85,38,35,179,61],[39,53,200,87,26,21,43,232,171],[56,34,51,104,114,102,29,93,77],[39,28,85,171,58,165,90,98,64],[34,22,116,206,23,34,43,166,73],[107,54,32,26,51,1,81,43,31],[68,25,106,22,64,171,36,225,114],[34,19,21,102,132,188,16,76,124],[62,18,78,95,85,57,50,48,51]],[[193,101,35,159,215,111,89,46,111],[60,148,31,172,219,228,21,18,111],[112,113,77,85,179,255,38,120,114],[40,42,1,196,245,209,10,25,109],[88,43,29,140,166,213,37,43,154],[61,63,30,155,67,45,68,1,209],[100,80,8,43,154,1,51,26,71],[142,78,78,16,255,128,34,197,171],[41,40,5,102,211,183,4,1,221],[51,50,17,168,209,192,23,25,82]],[[138,31,36,171,27,166,38,44,229],[67,87,58,169,82,115,26,59,179],[63,59,90,180,59,166,93,73,154],[40,40,21,116,143,209,34,39,175],[47,15,16,183,34,223,49,45,183],[46,17,33,183,6,98,15,32,183],[57,46,22,24,128,1,54,17,37],[65,32,73,115,28,128,23,128,205],[40,3,9,115,51,192,18,6,223],[87,37,9,115,59,77,64,21,47]],[[104,55,44,218,9,54,53,130,226],[64,90,70,205,40,41,23,26,57],[54,57,112,184,5,41,38,166,213],[30,34,26,133,152,116,10,32,134],[39,19,53,221,26,114,32,73,255],[31,9,65,234,2,15,1,118,73],[75,32,12,51,192,255,160,43,51],[88,31,35,67,102,85,55,186,85],[56,21,23,111,59,205,45,37,192],[55,38,70,124,73,102,1,34,98]],[[125,98,42,88,104,85,117,175,82],[95,84,53,89,128,100,113,101,45],[75,79,123,47,51,128,81,171,1],[57,17,5,71,102,57,53,41,49],[38,33,13,121,57,73,26,1,85],[41,10,67,138,77,110,90,47,114],[115,21,2,10,102,255,166,23,6],[101,29,16,10,85,128,101,196,26],[57,18,10,102,102,213,34,20,43],[117,20,15,36,163,128,68,1,26]],[[102,61,71,37,34,53,31,243,192],[69,60,71,38,73,119,28,222,37],[68,45,128,34,1,47,11,245,171],[62,17,19,70,146,85,55,62,70],[37,43,37,154,100,163,85,160,1],[63,9,92,136,28,64,32,201,85],[75,15,9,9,64,255,184,119,16],[86,6,28,5,64,255,25,248,1],[56,8,17,132,137,255,55,116,128],[58,15,20,82,135,57,26,121,40]],[[164,50,31,137,154,133,25,35,218],[51,103,44,131,131,123,31,6,158],[86,40,64,135,148,224,45,183,128],[22,26,17,131,240,154,14,1,209],[45,16,21,91,64,222,7,1,197],[56,21,39,155,60,138,23,102,213],[83,12,13,54,192,255,68,47,28],[85,26,85,85,128,128,32,146,171],[18,11,7,63,144,171,4,4,246],[35,27,10,146,174,171,12,26,128]],[[190,80,35,99,180,80,126,54,45],[85,126,47,87,176,51,41,20,32],[101,75,128,139,118,146,116,128,85],[56,41,15,176,236,85,37,9,62],[71,30,17,119,118,255,17,18,138],[101,38,60,138,55,70,43,26,142],[146,36,19,30,171,255,97,27,20],[138,45,61,62,219,1,81,188,64],[32,41,20,117,151,142,20,21,163],[112,19,12,61,195,128,48,4,24]]],ui=[[[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[176,246,255,255,255,255,255,255,255,255,255],[223,241,252,255,255,255,255,255,255,255,255],[249,253,253,255,255,255,255,255,255,255,255]],[[255,244,252,255,255,255,255,255,255,255,255],[234,254,254,255,255,255,255,255,255,255,255],[253,255,255,255,255,255,255,255,255,255,255]],[[255,246,254,255,255,255,255,255,255,255,255],[239,253,254,255,255,255,255,255,255,255,255],[254,255,254,255,255,255,255,255,255,255,255]],[[255,248,254,255,255,255,255,255,255,255,255],[251,255,254,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[251,254,254,255,255,255,255,255,255,255,255],[254,255,254,255,255,255,255,255,255,255,255]],[[255,254,253,255,254,255,255,255,255,255,255],[250,255,254,255,254,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[217,255,255,255,255,255,255,255,255,255,255],[225,252,241,253,255,255,254,255,255,255,255],[234,250,241,250,253,255,253,254,255,255,255]],[[255,254,255,255,255,255,255,255,255,255,255],[223,254,254,255,255,255,255,255,255,255,255],[238,253,254,254,255,255,255,255,255,255,255]],[[255,248,254,255,255,255,255,255,255,255,255],[249,254,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,255,255,255,255,255,255,255,255,255],[247,254,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[252,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,254,255,255,255,255,255,255,255,255],[253,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,253,255,255,255,255,255,255,255,255],[250,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[186,251,250,255,255,255,255,255,255,255,255],[234,251,244,254,255,255,255,255,255,255,255],[251,251,243,253,254,255,254,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[236,253,254,255,255,255,255,255,255,255,255],[251,253,253,254,254,255,255,255,255,255,255]],[[255,254,254,255,255,255,255,255,255,255,255],[254,254,254,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,255,255,255,255,255,255,255,255,255],[254,254,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[248,255,255,255,255,255,255,255,255,255,255],[250,254,252,254,255,255,255,255,255,255,255],[248,254,249,253,255,255,255,255,255,255,255]],[[255,253,253,255,255,255,255,255,255,255,255],[246,253,253,255,255,255,255,255,255,255,255],[252,254,251,254,254,255,255,255,255,255,255]],[[255,254,252,255,255,255,255,255,255,255,255],[248,254,253,255,255,255,255,255,255,255,255],[253,255,254,254,255,255,255,255,255,255,255]],[[255,251,254,255,255,255,255,255,255,255,255],[245,251,254,255,255,255,255,255,255,255,255],[253,253,254,255,255,255,255,255,255,255,255]],[[255,251,253,255,255,255,255,255,255,255,255],[252,253,254,255,255,255,255,255,255,255,255],[255,254,255,255,255,255,255,255,255,255,255]],[[255,252,255,255,255,255,255,255,255,255,255],[249,255,254,255,255,255,255,255,255,255,255],[255,255,254,255,255,255,255,255,255,255,255]],[[255,255,253,255,255,255,255,255,255,255,255],[250,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]]],hi=[0,1,2,3,6,4,5,6,6,6,6,6,6,6,6,7,0],li=[],fi=[],di=[],pi=1,gi=2,mi=[],vi=[];vr("UpsampleRgbLinePair",Ar,3),vr("UpsampleBgrLinePair",xr,3),vr("UpsampleRgbaLinePair",Ir,4),vr("UpsampleBgraLinePair",kr,4),vr("UpsampleArgbLinePair",Pr,4),vr("UpsampleRgba4444LinePair",_r,2),vr("UpsampleRgb565LinePair",Sr,2);var bi=t.UpsampleRgbLinePair,yi=t.UpsampleBgrLinePair,wi=t.UpsampleRgbaLinePair,Ni=t.UpsampleBgraLinePair,Li=t.UpsampleArgbLinePair,Ai=t.UpsampleRgba4444LinePair,xi=t.UpsampleRgb565LinePair,Si=16,_i=1<<Si-1,Pi=-227,ki=482,Ii=6,Fi=(256<<Ii)-1,Ci=0,ji=a(256),Oi=a(256),Bi=a(256),Mi=a(256),Ei=a(ki-Pi),qi=a(ki-Pi);Fr("YuvToRgbRow",Ar,3),Fr("YuvToBgrRow",xr,3),Fr("YuvToRgbaRow",Ir,4),Fr("YuvToBgraRow",kr,4),Fr("YuvToArgbRow",Pr,4),Fr("YuvToRgba4444Row",_r,2),Fr("YuvToRgb565Row",Sr,2);var Di=[0,4,8,12,128,132,136,140,256,260,264,268,384,388,392,396],Ri=[0,2,8],Ti=[8,7,6,4,4,2,2,2,1,1,1,1],Ui=1;this.WebPDecodeRGBA=function(t,r,n,i,a){var o=qn,s=new rr,c=new ot;s.ba=c,c.S=o,c.width=[c.width],c.height=[c.height];var u=c.width,h=c.height,l=new st;if(null==l||null==t)var f=2;else e(null!=l),f=Br(t,r,n,l.width,l.height,l.Pd,l.Qd,l.format,null);if(0!=f?u=0:(null!=u&&(u[0]=l.width[0]),null!=h&&(h[0]=l.height[0]),u=1),u){c.width=c.width[0],c.height=c.height[0],null!=i&&(i[0]=c.width),null!=a&&(a[0]=c.height);t:{if(i=new Gt,(a=new nr).data=t,a.w=r,a.ha=n,a.kd=1,r=[0],e(null!=a),(0==(t=Br(a.data,a.w,a.ha,null,null,null,r,null,a))||7==t)&&r[0]&&(t=4),0==(r=t)){if(e(null!=s),i.data=a.data,i.w=a.w+a.offset,i.ha=a.ha-a.offset,i.put=dt,i.ac=ft,i.bc=pt,i.ma=s,a.xa){if(null==(t=kt())){s=1;break t}if(function(t,r){var n=[0],i=[0],a=[0];e:for(;;){if(null==t)return 0;if(null==r)return t.a=2,0;if(t.l=r,t.a=0,v(t.m,r.data,r.w,r.ha),!gt(t.m,n,i,a)){t.a=3;break e}if(t.xb=gi,r.width=n[0],r.height=i[0],!It(n[0],i[0],1,t,null))break e;return 1}return e(0!=t.a),0}(t,i)){if(i=0==(r=qr(i.width,i.height,s.Oa,s.ba))){e:{i=t;r:for(;;){if(null==i){i=0;break e}if(e(null!=i.s.yc),e(null!=i.s.Ya),e(0<i.s.Wb),e(null!=(n=i.l)),e(null!=(a=n.ma)),0!=i.xb){if(i.ca=a.ba,i.tb=a.tb,e(null!=i.ca),!Mr(a.Oa,n,Rn)){i.a=2;break r}if(!Ft(i,n.width))break r;if(n.da)break r;if((n.da||nt(i.ca.S))&&mr(),11>i.ca.S||(alert("todo:WebPInitConvertARGBToYUV"),null!=i.ca.f.kb.F&&mr()),i.Pb&&0<i.s.ua&&null==i.s.vb.X&&!O(i.s.vb,i.s.Wa.Xa)){i.a=1;break r}i.xb=0}if(!_t(i,i.V,i.Ba,i.c,i.i,n.o,Lt))break r;a.Dc=i.Ma,i=1;break e}e(0!=i.a),i=0}i=!i}i&&(r=t.a)}else r=t.a}else{if(null==(t=new Yt)){s=1;break t}if(t.Fa=a.na,t.P=a.P,t.qc=a.Sa,Kt(t,i)){if(0==(r=qr(i.width,i.height,s.Oa,s.ba))){if(t.Aa=0,n=s.Oa,e(null!=(a=t)),null!=n){if(0<(u=0>(u=n.Md)?0:100<u?255:255*u/100)){for(h=l=0;4>h;++h)12>(f=a.pb[h]).lc&&(f.ia=u*Ti[0>f.lc?0:f.lc]>>3),l|=f.ia;l&&(alert("todo:VP8InitRandom"),a.ia=1)}a.Ga=n.Id,100<a.Ga?a.Ga=100:0>a.Ga&&(a.Ga=0)}Qt(t,i)||(r=t.a)}}else r=t.a}0==r&&null!=s.Oa&&s.Oa.fd&&(r=Er(s.ba))}s=r}o=0!=s?null:11>o?c.f.RGBA.eb:c.f.kb.y}else o=null;return o};var zi=[3,4,3,4,4,2,2,4,4,4,2,1,1]};function u(t,e){for(var r="",n=0;n<4;n++)r+=String.fromCharCode(t[e++]);return r}function h(t,e){return(t[e+0]<<0|t[e+1]<<8|t[e+2]<<16)>>>0}function l(t,e){return(t[e+0]<<0|t[e+1]<<8|t[e+2]<<16|t[e+3]<<24)>>>0}new c;var f=[0],d=[0],p=[],g=new c,m=t,v=function(t,e){var r={},n=0,i=!1,a=0,o=0;if(r.frames=[],! -/** @license - * Copyright (c) 2017 Dominik Homberger - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - https://webpjs.appspot.com - WebPRiffParser dominikhlbg@gmail.com - */ -function(t,e,r,n){for(var i=0;i<n;i++)if(t[e+i]!=r.charCodeAt(i))return!0;return!1}(t,e,"RIFF",4)){var s,c;l(t,e+=4);for(e+=8;e<t.length;){var f=u(t,e),d=l(t,e+=4);e+=4;var p=d+(1&d);switch(f){case"VP8 ":case"VP8L":void 0===r.frames[n]&&(r.frames[n]={});(v=r.frames[n]).src_off=i?o:e-8,v.src_size=a+d+8,n++,i&&(i=!1,a=0,o=0);break;case"VP8X":(v=r.header={}).feature_flags=t[e];var g=e+4;v.canvas_width=1+h(t,g);g+=3;v.canvas_height=1+h(t,g);g+=3;break;case"ALPH":i=!0,a=p+8,o=e-8;break;case"ANIM":(v=r.header).bgcolor=l(t,e);g=e+4;v.loop_count=(s=t)[(c=g)+0]<<0|s[c+1]<<8;g+=2;break;case"ANMF":var m,v;(v=r.frames[n]={}).offset_x=2*h(t,e),e+=3,v.offset_y=2*h(t,e),e+=3,v.width=1+h(t,e),e+=3,v.height=1+h(t,e),e+=3,v.duration=h(t,e),e+=3,m=t[e++],v.dispose=1&m,v.blend=m>>1&1}"ANMF"!=f&&(e+=p)}return r}}(m,0);v.response=m,v.rgbaoutput=!0,v.dataurl=!1;var b=v.header?v.header:null,y=v.frames?v.frames:null;if(b){b.loop_counter=b.loop_count,f=[b.canvas_height],d=[b.canvas_width];for(var w=0;w<y.length&&0!=y[w].blend;w++);}var N=y[0],L=g.WebPDecodeRGBA(m,N.src_off,N.src_size,d,f);N.rgba=L,N.imgwidth=d[0],N.imgheight=f[0];for(var A=0;A<d[0]*f[0]*4;A++)p[A]=L[A];return this.width=d,this.height=f,this.data=p,this}!function(t){var r=function(){return"function"==typeof e},n=function(r,n,a,h){var l=4,f=s;switch(h){case t.image_compression.FAST:l=1,f=o;break;case t.image_compression.MEDIUM:l=6,f=c;break;case t.image_compression.SLOW:l=9,f=u}r=i(r,n,a,f);var d=e(r,{level:l});return t.__addimage__.arrayBufferToBinaryString(d)},i=function(t,e,r,n){for(var i,a,o,s=t.length/e,c=new Uint8Array(t.length+s),u=l(),h=0;h<s;h+=1){if(o=h*e,i=t.subarray(o,o+e),n)c.set(n(i,r,a),o+h);else{for(var d,p=u.length,g=[];d<p;d+=1)g[d]=u[d](i,r,a);var m=f(g.concat());c.set(g[m],o+h)}a=i}return c},a=function(t){var e=Array.apply([],t);return e.unshift(0),e},o=function(t,e){var r,n=[],i=t.length;n[0]=1;for(var a=0;a<i;a+=1)r=t[a-e]||0,n[a+1]=t[a]-r+256&255;return n},s=function(t,e,r){var n,i=[],a=t.length;i[0]=2;for(var o=0;o<a;o+=1)n=r&&r[o]||0,i[o+1]=t[o]-n+256&255;return i},c=function(t,e,r){var n,i,a=[],o=t.length;a[0]=3;for(var s=0;s<o;s+=1)n=t[s-e]||0,i=r&&r[s]||0,a[s+1]=t[s]+256-(n+i>>>1)&255;return a},u=function(t,e,r){var n,i,a,o,s=[],c=t.length;s[0]=4;for(var u=0;u<c;u+=1)n=t[u-e]||0,i=r&&r[u]||0,a=r&&r[u-e]||0,o=h(n,i,a),s[u+1]=t[u]-o+256&255;return s},h=function(t,e,r){if(t===e&&e===r)return t;var n=Math.abs(e-r),i=Math.abs(t-r),a=Math.abs(t+e-r-r);return n<=i&&n<=a?t:i<=a?e:r},l=function(){return[a,o,s,c,u]},f=function(t){var e=t.map((function(t){return t.reduce((function(t,e){return t+Math.abs(e)}),0)}));return e.indexOf(Math.min.apply(null,e))};t.processPNG=function(e,i,a,o){var s,c,u,h,l,f,d,p,g,m,v,b,y,w,N,L=this.decode.FLATE_DECODE,A="";if(this.__addimage__.isArrayBuffer(e)&&(e=new Uint8Array(e)),this.__addimage__.isArrayBufferView(e)){if(e=(u=new Kt(e)).imgData,c=u.bits,s=u.colorSpace,l=u.colors,-1!==[4,6].indexOf(u.colorType)){if(8===u.bits){g=(p=32==u.pixelBitlength?new Uint32Array(u.decodePixels().buffer):16==u.pixelBitlength?new Uint16Array(u.decodePixels().buffer):new Uint8Array(u.decodePixels().buffer)).length,v=new Uint8Array(g*u.colors),m=new Uint8Array(g);var x,S=u.pixelBitlength-u.bits;for(w=0,N=0;w<g;w++){for(y=p[w],x=0;x<S;)v[N++]=y>>>x&255,x+=u.bits;m[w]=y>>>x&255}}if(16===u.bits){g=(p=new Uint32Array(u.decodePixels().buffer)).length,v=new Uint8Array(g*(32/u.pixelBitlength)*u.colors),m=new Uint8Array(g*(32/u.pixelBitlength)),b=u.colors>1,w=0,N=0;for(var _=0;w<g;)y=p[w++],v[N++]=y>>>0&255,b&&(v[N++]=y>>>16&255,y=p[w++],v[N++]=y>>>0&255),m[_++]=y>>>16&255;c=8}o!==t.image_compression.NONE&&r()?(e=n(v,u.width*u.colors,u.colors,o),d=n(m,u.width,1,o)):(e=v,d=m,L=void 0)}if(3===u.colorType&&(s=this.color_spaces.INDEXED,f=u.palette,u.transparency.indexed)){var P=u.transparency.indexed,k=0;for(w=0,g=P.length;w<g;++w)k+=P[w];if((k/=255)===g-1&&-1!==P.indexOf(0))h=[P.indexOf(0)];else if(k!==g){for(p=u.decodePixels(),m=new Uint8Array(p.length),w=0,g=p.length;w<g;w++)m[w]=P[p[w]];d=n(m,u.width,1)}}var I=function(e){var r;switch(e){case t.image_compression.FAST:r=11;break;case t.image_compression.MEDIUM:r=13;break;case t.image_compression.SLOW:r=14;break;default:r=12}return r}(o);return L===this.decode.FLATE_DECODE&&(A="/Predictor "+I+" "),A+="/Colors "+l+" /BitsPerComponent "+c+" /Columns "+u.width,(this.__addimage__.isArrayBuffer(e)||this.__addimage__.isArrayBufferView(e))&&(e=this.__addimage__.arrayBufferToBinaryString(e)),(d&&this.__addimage__.isArrayBuffer(d)||this.__addimage__.isArrayBufferView(d))&&(d=this.__addimage__.arrayBufferToBinaryString(d)),{alias:a,data:e,index:i,filter:L,decodeParameters:A,transparency:h,palette:f,sMask:d,predictor:I,width:u.width,height:u.height,bitsPerComponent:c,colorSpace:s}}}}(E.API),function(t){t.processGIF89A=function(e,r,n,i){var a=new Zt(e),o=a.width,s=a.height,c=[];a.decodeAndBlitFrameRGBA(0,c);var u={data:c,width:o,height:s},h=new Qt(100).encode(u,100);return t.processJPEG.call(this,h,r,n,i)},t.processGIF87A=t.processGIF89A}(E.API),te.prototype.parseHeader=function(){if(this.fileSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.reserved=this.datav.getUint32(this.pos,!0),this.pos+=4,this.offset=this.datav.getUint32(this.pos,!0),this.pos+=4,this.headerSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.width=this.datav.getUint32(this.pos,!0),this.pos+=4,this.height=this.datav.getInt32(this.pos,!0),this.pos+=4,this.planes=this.datav.getUint16(this.pos,!0),this.pos+=2,this.bitPP=this.datav.getUint16(this.pos,!0),this.pos+=2,this.compress=this.datav.getUint32(this.pos,!0),this.pos+=4,this.rawSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.hr=this.datav.getUint32(this.pos,!0),this.pos+=4,this.vr=this.datav.getUint32(this.pos,!0),this.pos+=4,this.colors=this.datav.getUint32(this.pos,!0),this.pos+=4,this.importantColors=this.datav.getUint32(this.pos,!0),this.pos+=4,16===this.bitPP&&this.is_with_alpha&&(this.bitPP=15),this.bitPP<15){var t=0===this.colors?1<<this.bitPP:this.colors;this.palette=new Array(t);for(var e=0;e<t;e++){var r=this.datav.getUint8(this.pos++,!0),n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0);this.palette[e]={red:i,green:n,blue:r,quad:a}}}this.height<0&&(this.height*=-1,this.bottom_up=!1)},te.prototype.parseBGR=function(){this.pos=this.offset;try{var t="bit"+this.bitPP,e=this.width*this.height*4;this.data=new Uint8Array(e),this[t]()}catch(t){a.log("bit decode error:"+t)}},te.prototype.bit1=function(){var t,e=Math.ceil(this.width/8),r=e%4;for(t=this.height-1;t>=0;t--){for(var n=this.bottom_up?t:this.height-1-t,i=0;i<e;i++)for(var a=this.datav.getUint8(this.pos++,!0),o=n*this.width*4+8*i*4,s=0;s<8&&8*i+s<this.width;s++){var c=this.palette[a>>7-s&1];this.data[o+4*s]=c.blue,this.data[o+4*s+1]=c.green,this.data[o+4*s+2]=c.red,this.data[o+4*s+3]=255}0!==r&&(this.pos+=4-r)}},te.prototype.bit4=function(){for(var t=Math.ceil(this.width/2),e=t%4,r=this.height-1;r>=0;r--){for(var n=this.bottom_up?r:this.height-1-r,i=0;i<t;i++){var a=this.datav.getUint8(this.pos++,!0),o=n*this.width*4+2*i*4,s=a>>4,c=15&a,u=this.palette[s];if(this.data[o]=u.blue,this.data[o+1]=u.green,this.data[o+2]=u.red,this.data[o+3]=255,2*i+1>=this.width)break;u=this.palette[c],this.data[o+4]=u.blue,this.data[o+4+1]=u.green,this.data[o+4+2]=u.red,this.data[o+4+3]=255}0!==e&&(this.pos+=4-e)}},te.prototype.bit8=function(){for(var t=this.width%4,e=this.height-1;e>=0;e--){for(var r=this.bottom_up?e:this.height-1-e,n=0;n<this.width;n++){var i=this.datav.getUint8(this.pos++,!0),a=r*this.width*4+4*n;if(i<this.palette.length){var o=this.palette[i];this.data[a]=o.red,this.data[a+1]=o.green,this.data[a+2]=o.blue,this.data[a+3]=255}else this.data[a]=255,this.data[a+1]=255,this.data[a+2]=255,this.data[a+3]=255}0!==t&&(this.pos+=4-t)}},te.prototype.bit15=function(){for(var t=this.width%3,e=parseInt("11111",2),r=this.height-1;r>=0;r--){for(var n=this.bottom_up?r:this.height-1-r,i=0;i<this.width;i++){var a=this.datav.getUint16(this.pos,!0);this.pos+=2;var o=(a&e)/e*255|0,s=(a>>5&e)/e*255|0,c=(a>>10&e)/e*255|0,u=a>>15?255:0,h=n*this.width*4+4*i;this.data[h]=c,this.data[h+1]=s,this.data[h+2]=o,this.data[h+3]=u}this.pos+=t}},te.prototype.bit16=function(){for(var t=this.width%3,e=parseInt("11111",2),r=parseInt("111111",2),n=this.height-1;n>=0;n--){for(var i=this.bottom_up?n:this.height-1-n,a=0;a<this.width;a++){var o=this.datav.getUint16(this.pos,!0);this.pos+=2;var s=(o&e)/e*255|0,c=(o>>5&r)/r*255|0,u=(o>>11)/e*255|0,h=i*this.width*4+4*a;this.data[h]=u,this.data[h+1]=c,this.data[h+2]=s,this.data[h+3]=255}this.pos+=t}},te.prototype.bit24=function(){for(var t=this.height-1;t>=0;t--){for(var e=this.bottom_up?t:this.height-1-t,r=0;r<this.width;r++){var n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0),o=e*this.width*4+4*r;this.data[o]=a,this.data[o+1]=i,this.data[o+2]=n,this.data[o+3]=255}this.pos+=this.width%4}},te.prototype.bit32=function(){for(var t=this.height-1;t>=0;t--)for(var e=this.bottom_up?t:this.height-1-t,r=0;r<this.width;r++){var n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0),o=this.datav.getUint8(this.pos++,!0),s=e*this.width*4+4*r;this.data[s]=a,this.data[s+1]=i,this.data[s+2]=n,this.data[s+3]=o}},te.prototype.getData=function(){return this.data}, -/** - * @license - * Copyright (c) 2018 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.processBMP=function(e,r,n,i){var a=new te(e,!1),o=a.width,s=a.height,c={data:a.getData(),width:o,height:s},u=new Qt(100).encode(c,100);return t.processJPEG.call(this,u,r,n,i)}}(E.API),ee.prototype.getData=function(){return this.data}, -/** - * @license - * Copyright (c) 2019 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.processWEBP=function(e,r,n,i){var a=new ee(e,!1),o=a.width,s=a.height,c={data:a.getData(),width:o,height:s},u=new Qt(100).encode(c,100);return t.processJPEG.call(this,u,r,n,i)}}(E.API),E.API.processRGBA=function(t,e,r){for(var n=t.data,i=n.length,a=new Uint8Array(i/4*3),o=new Uint8Array(i/4),s=0,c=0,u=0;u<i;u+=4){var h=n[u],l=n[u+1],f=n[u+2],d=n[u+3];a[s++]=h,a[s++]=l,a[s++]=f,o[c++]=d}var p=this.__addimage__.arrayBufferToBinaryString(a);return{alpha:this.__addimage__.arrayBufferToBinaryString(o),data:p,index:e,alias:r,colorSpace:"DeviceRGB",bitsPerComponent:8,width:t.width,height:t.height}},E.API.setLanguage=function(t){return void 0===this.internal.languageSettings&&(this.internal.languageSettings={},this.internal.languageSettings.isSubscribed=!1),void 0!=={af:"Afrikaans",sq:"Albanian",ar:"Arabic (Standard)","ar-DZ":"Arabic (Algeria)","ar-BH":"Arabic (Bahrain)","ar-EG":"Arabic (Egypt)","ar-IQ":"Arabic (Iraq)","ar-JO":"Arabic (Jordan)","ar-KW":"Arabic (Kuwait)","ar-LB":"Arabic (Lebanon)","ar-LY":"Arabic (Libya)","ar-MA":"Arabic (Morocco)","ar-OM":"Arabic (Oman)","ar-QA":"Arabic (Qatar)","ar-SA":"Arabic (Saudi Arabia)","ar-SY":"Arabic (Syria)","ar-TN":"Arabic (Tunisia)","ar-AE":"Arabic (U.A.E.)","ar-YE":"Arabic (Yemen)",an:"Aragonese",hy:"Armenian",as:"Assamese",ast:"Asturian",az:"Azerbaijani",eu:"Basque",be:"Belarusian",bn:"Bengali",bs:"Bosnian",br:"Breton",bg:"Bulgarian",my:"Burmese",ca:"Catalan",ch:"Chamorro",ce:"Chechen",zh:"Chinese","zh-HK":"Chinese (Hong Kong)","zh-CN":"Chinese (PRC)","zh-SG":"Chinese (Singapore)","zh-TW":"Chinese (Taiwan)",cv:"Chuvash",co:"Corsican",cr:"Cree",hr:"Croatian",cs:"Czech",da:"Danish",nl:"Dutch (Standard)","nl-BE":"Dutch (Belgian)",en:"English","en-AU":"English (Australia)","en-BZ":"English (Belize)","en-CA":"English (Canada)","en-IE":"English (Ireland)","en-JM":"English (Jamaica)","en-NZ":"English (New Zealand)","en-PH":"English (Philippines)","en-ZA":"English (South Africa)","en-TT":"English (Trinidad & Tobago)","en-GB":"English (United Kingdom)","en-US":"English (United States)","en-ZW":"English (Zimbabwe)",eo:"Esperanto",et:"Estonian",fo:"Faeroese",fj:"Fijian",fi:"Finnish",fr:"French (Standard)","fr-BE":"French (Belgium)","fr-CA":"French (Canada)","fr-FR":"French (France)","fr-LU":"French (Luxembourg)","fr-MC":"French (Monaco)","fr-CH":"French (Switzerland)",fy:"Frisian",fur:"Friulian",gd:"Gaelic (Scots)","gd-IE":"Gaelic (Irish)",gl:"Galacian",ka:"Georgian",de:"German (Standard)","de-AT":"German (Austria)","de-DE":"German (Germany)","de-LI":"German (Liechtenstein)","de-LU":"German (Luxembourg)","de-CH":"German (Switzerland)",el:"Greek",gu:"Gujurati",ht:"Haitian",he:"Hebrew",hi:"Hindi",hu:"Hungarian",is:"Icelandic",id:"Indonesian",iu:"Inuktitut",ga:"Irish",it:"Italian (Standard)","it-CH":"Italian (Switzerland)",ja:"Japanese",kn:"Kannada",ks:"Kashmiri",kk:"Kazakh",km:"Khmer",ky:"Kirghiz",tlh:"Klingon",ko:"Korean","ko-KP":"Korean (North Korea)","ko-KR":"Korean (South Korea)",la:"Latin",lv:"Latvian",lt:"Lithuanian",lb:"Luxembourgish",mk:"North Macedonia",ms:"Malay",ml:"Malayalam",mt:"Maltese",mi:"Maori",mr:"Marathi",mo:"Moldavian",nv:"Navajo",ng:"Ndonga",ne:"Nepali",no:"Norwegian",nb:"Norwegian (Bokmal)",nn:"Norwegian (Nynorsk)",oc:"Occitan",or:"Oriya",om:"Oromo",fa:"Persian","fa-IR":"Persian/Iran",pl:"Polish",pt:"Portuguese","pt-BR":"Portuguese (Brazil)",pa:"Punjabi","pa-IN":"Punjabi (India)","pa-PK":"Punjabi (Pakistan)",qu:"Quechua",rm:"Rhaeto-Romanic",ro:"Romanian","ro-MO":"Romanian (Moldavia)",ru:"Russian","ru-MO":"Russian (Moldavia)",sz:"Sami (Lappish)",sg:"Sango",sa:"Sanskrit",sc:"Sardinian",sd:"Sindhi",si:"Singhalese",sr:"Serbian",sk:"Slovak",sl:"Slovenian",so:"Somani",sb:"Sorbian",es:"Spanish","es-AR":"Spanish (Argentina)","es-BO":"Spanish (Bolivia)","es-CL":"Spanish (Chile)","es-CO":"Spanish (Colombia)","es-CR":"Spanish (Costa Rica)","es-DO":"Spanish (Dominican Republic)","es-EC":"Spanish (Ecuador)","es-SV":"Spanish (El Salvador)","es-GT":"Spanish (Guatemala)","es-HN":"Spanish (Honduras)","es-MX":"Spanish (Mexico)","es-NI":"Spanish (Nicaragua)","es-PA":"Spanish (Panama)","es-PY":"Spanish (Paraguay)","es-PE":"Spanish (Peru)","es-PR":"Spanish (Puerto Rico)","es-ES":"Spanish (Spain)","es-UY":"Spanish (Uruguay)","es-VE":"Spanish (Venezuela)",sx:"Sutu",sw:"Swahili",sv:"Swedish","sv-FI":"Swedish (Finland)","sv-SV":"Swedish (Sweden)",ta:"Tamil",tt:"Tatar",te:"Teluga",th:"Thai",tig:"Tigre",ts:"Tsonga",tn:"Tswana",tr:"Turkish",tk:"Turkmen",uk:"Ukrainian",hsb:"Upper Sorbian",ur:"Urdu",ve:"Venda",vi:"Vietnamese",vo:"Volapuk",wa:"Walloon",cy:"Welsh",xh:"Xhosa",ji:"Yiddish",zu:"Zulu"}[t]&&(this.internal.languageSettings.languageCode=t,!1===this.internal.languageSettings.isSubscribed&&(this.internal.events.subscribe("putCatalog",(function(){this.internal.write("/Lang ("+this.internal.languageSettings.languageCode+")")})),this.internal.languageSettings.isSubscribed=!0)),this},Vt=E.API,Gt=Vt.getCharWidthsArray=function(e,r){var n,i,a=(r=r||{}).font||this.internal.getFont(),o=r.fontSize||this.internal.getFontSize(),s=r.charSpace||this.internal.getCharSpace(),c=r.widths?r.widths:a.metadata.Unicode.widths,u=c.fof?c.fof:1,h=r.kerning?r.kerning:a.metadata.Unicode.kerning,l=h.fof?h.fof:1,f=!1!==r.doKerning,d=0,p=e.length,g=0,m=c[0]||u,v=[];for(n=0;n<p;n++)i=e.charCodeAt(n),"function"==typeof a.metadata.widthOfString?v.push((a.metadata.widthOfGlyph(a.metadata.characterToGlyph(i))+s*(1e3/o)||0)/1e3):(d=f&&"object"===t(h[i])&&!isNaN(parseInt(h[i][g],10))?h[i][g]/l:0,v.push((c[i]||m)/u+d)),g=i;return v},Yt=Vt.getStringUnitWidth=function(t,e){var r=(e=e||{}).fontSize||this.internal.getFontSize(),n=e.font||this.internal.getFont(),i=e.charSpace||this.internal.getCharSpace();return Vt.processArabic&&(t=Vt.processArabic(t)),"function"==typeof n.metadata.widthOfString?n.metadata.widthOfString(t,r,i)/r:Gt.apply(this,arguments).reduce((function(t,e){return t+e}),0)},Jt=function(t,e,r,n){for(var i=[],a=0,o=t.length,s=0;a!==o&&s+e[a]<r;)s+=e[a],a++;i.push(t.slice(0,a));var c=a;for(s=0;a!==o;)s+e[a]>n&&(i.push(t.slice(c,a)),s=0,c=a),s+=e[a],a++;return c!==a&&i.push(t.slice(c,a)),i},Xt=function(t,e,r){r||(r={});var n,i,a,o,s,c,u,h=[],l=[h],f=r.textIndent||0,d=0,p=0,g=t.split(" "),m=Gt.apply(this,[" ",r])[0];if(c=-1===r.lineIndent?g[0].length+2:r.lineIndent||0){var v=Array(c).join(" "),b=[];g.map((function(t){(t=t.split(/\s*\n/)).length>1?b=b.concat(t.map((function(t,e){return(e&&t.length?"\n":"")+t}))):b.push(t[0])})),g=b,c=Yt.apply(this,[v,r])}for(a=0,o=g.length;a<o;a++){var y=0;if(n=g[a],c&&"\n"==n[0]&&(n=n.substr(1),y=1),f+d+(p=(i=Gt.apply(this,[n,r])).reduce((function(t,e){return t+e}),0))>e||y){if(p>e){for(s=Jt.apply(this,[n,i,e-(f+d),e]),h.push(s.shift()),h=[s.pop()];s.length;)l.push([s.shift()]);p=i.slice(n.length-(h[0]?h[0].length:0)).reduce((function(t,e){return t+e}),0)}else h=[n];l.push(h),f=p+c,d=m}else h.push(n),f+=d+p,d=m}return u=c?function(t,e){return(e?v:"")+t.join(" ")}:function(t){return t.join(" ")},l.map(u)},Vt.splitTextToSize=function(t,e,r){var n,i=(r=r||{}).fontSize||this.internal.getFontSize(),a=function(t){if(t.widths&&t.kerning)return{widths:t.widths,kerning:t.kerning};var e=this.internal.getFont(t.fontName,t.fontStyle);return e.metadata.Unicode?{widths:e.metadata.Unicode.widths||{0:1},kerning:e.metadata.Unicode.kerning||{}}:{font:e.metadata,fontSize:this.internal.getFontSize(),charSpace:this.internal.getCharSpace()}}.call(this,r);n=Array.isArray(t)?t:String(t).split(/\r?\n/);var o=1*this.internal.scaleFactor*e/i;a.textIndent=r.textIndent?1*r.textIndent*this.internal.scaleFactor/i:0,a.lineIndent=r.lineIndent;var s,c,u=[];for(s=0,c=n.length;s<c;s++)u=u.concat(Xt.apply(this,[n[s],o,a]));return u},function(e){e.__fontmetrics__=e.__fontmetrics__||{};for(var r="klmnopqrstuvwxyz",n={},i={},a=0;a<r.length;a++)n[r[a]]="0123456789abcdef"[a],i["0123456789abcdef"[a]]=r[a];var o=function(t){return"0x"+parseInt(t,10).toString(16)},s=e.__fontmetrics__.compress=function(e){var r,n,a,c,u=["{"];for(var h in e){if(r=e[h],isNaN(parseInt(h,10))?n="'"+h+"'":(h=parseInt(h,10),n=(n=o(h).slice(2)).slice(0,-1)+i[n.slice(-1)]),"number"==typeof r)r<0?(a=o(r).slice(3),c="-"):(a=o(r).slice(2),c=""),a=c+a.slice(0,-1)+i[a.slice(-1)];else{if("object"!==t(r))throw new Error("Don't know what to do with value type "+t(r)+".");a=s(r)}u.push(n+a)}return u.push("}"),u.join("")},c=e.__fontmetrics__.uncompress=function(t){if("string"!=typeof t)throw new Error("Invalid argument passed to uncompress.");for(var e,r,i,a,o={},s=1,c=o,u=[],h="",l="",f=t.length-1,d=1;d<f;d+=1)"'"==(a=t[d])?e?(i=e.join(""),e=void 0):e=[]:e?e.push(a):"{"==a?(u.push([c,i]),c={},i=void 0):"}"==a?((r=u.pop())[0][r[1]]=c,i=void 0,c=r[0]):"-"==a?s=-1:void 0===i?n.hasOwnProperty(a)?(h+=n[a],i=parseInt(h,16)*s,s=1,h=""):h+=a:n.hasOwnProperty(a)?(l+=n[a],c[i]=parseInt(l,16)*s,s=1,i=void 0,l=""):l+=a;return o},u={codePages:["WinAnsiEncoding"],WinAnsiEncoding:c("{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}")},h={Unicode:{Courier:u,"Courier-Bold":u,"Courier-BoldOblique":u,"Courier-Oblique":u,Helvetica:u,"Helvetica-Bold":u,"Helvetica-BoldOblique":u,"Helvetica-Oblique":u,"Times-Roman":u,"Times-Bold":u,"Times-BoldItalic":u,"Times-Italic":u}},l={Unicode:{"Courier-Oblique":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-BoldItalic":c("{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}"),"Helvetica-Bold":c("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),Courier:c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Courier-BoldOblique":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-Bold":c("{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}"),Symbol:c("{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}"),Helvetica:c("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}"),"Helvetica-BoldOblique":c("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),ZapfDingbats:c("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"),"Courier-Bold":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-Italic":c("{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}"),"Times-Roman":c("{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}"),"Helvetica-Oblique":c("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}")}};e.events.push(["addFont",function(t){var e=t.font,r=l.Unicode[e.postScriptName];r&&(e.metadata.Unicode={},e.metadata.Unicode.widths=r.widths,e.metadata.Unicode.kerning=r.kerning);var n=h.Unicode[e.postScriptName];n&&(e.metadata.Unicode.encoding=n,e.encoding=n.codePages[0])}])}(E.API), -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(t){for(var e=t.length,r=new Uint8Array(e),n=0;n<e;n++)r[n]=t.charCodeAt(n);return r};t.API.events.push(["addFont",function(r){var n=void 0,i=r.font,a=r.instance;if(!i.isStandardFont){if(void 0===a)throw new Error("Font does not exist in vFS, import fonts or remove declaration doc.addFont('"+i.postScriptName+"').");if("string"!=typeof(n=!1===a.existsFileInVFS(i.postScriptName)?a.loadFile(i.postScriptName):a.getFileFromVFS(i.postScriptName)))throw new Error("Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('"+i.postScriptName+"').");!function(r,n){n=/^\x00\x01\x00\x00/.test(n)?e(n):e(u(n)),r.metadata=t.API.TTFFont.open(n),r.metadata.Unicode=r.metadata.Unicode||{encoding:{},kerning:{},widths:[]},r.metadata.glyIdsUsed=[0]}(i,n)}}])}(E), -/** @license - * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){function e(){return(n.canvg?Promise.resolve(n.canvg):import("canvg")).catch((function(t){return Promise.reject(new Error("Could not load canvg: "+t))})).then((function(t){return t.default?t.default:t}))}E.API.addSvgAsImage=function(t,r,n,i,o,s,c,u){if(isNaN(r)||isNaN(n))throw a.error("jsPDF.addSvgAsImage: Invalid coordinates",arguments),new Error("Invalid coordinates passed to jsPDF.addSvgAsImage");if(isNaN(i)||isNaN(o))throw a.error("jsPDF.addSvgAsImage: Invalid measurements",arguments),new Error("Invalid measurements (width and/or height) passed to jsPDF.addSvgAsImage");var h=document.createElement("canvas");h.width=i,h.height=o;var l=h.getContext("2d");l.fillStyle="#fff",l.fillRect(0,0,h.width,h.height);var f={ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0},d=this;return e().then((function(e){return e.fromString(l,t,f)}),(function(){return Promise.reject(new Error("Could not load canvg."))})).then((function(t){return t.render(f)})).then((function(){d.addImage(h.toDataURL("image/jpeg",1),r,n,i,o,c,u)}))}}(),E.API.putTotalPages=function(t){var e,r=0;parseInt(this.internal.getFont().id.substr(1),10)<15?(e=new RegExp(t,"g"),r=this.internal.getNumberOfPages()):(e=new RegExp(this.pdfEscape16(t,this.internal.getFont()),"g"),r=this.pdfEscape16(this.internal.getNumberOfPages()+"",this.internal.getFont()));for(var n=1;n<=this.internal.getNumberOfPages();n++)for(var i=0;i<this.internal.pages[n].length;i++)this.internal.pages[n][i]=this.internal.pages[n][i].replace(e,r);return this},E.API.viewerPreferences=function(e,r){var n;e=e||{},r=r||!1;var i,a,o,s={HideToolbar:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},HideMenubar:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},HideWindowUI:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},FitWindow:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},CenterWindow:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},DisplayDocTitle:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.4},NonFullScreenPageMode:{defaultValue:"UseNone",value:"UseNone",type:"name",explicitSet:!1,valueSet:["UseNone","UseOutlines","UseThumbs","UseOC"],pdfVersion:1.3},Direction:{defaultValue:"L2R",value:"L2R",type:"name",explicitSet:!1,valueSet:["L2R","R2L"],pdfVersion:1.3},ViewArea:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},ViewClip:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintArea:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintClip:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintScaling:{defaultValue:"AppDefault",value:"AppDefault",type:"name",explicitSet:!1,valueSet:["AppDefault","None"],pdfVersion:1.6},Duplex:{defaultValue:"",value:"none",type:"name",explicitSet:!1,valueSet:["Simplex","DuplexFlipShortEdge","DuplexFlipLongEdge","none"],pdfVersion:1.7},PickTrayByPDFSize:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.7},PrintPageRange:{defaultValue:"",value:"",type:"array",explicitSet:!1,valueSet:null,pdfVersion:1.7},NumCopies:{defaultValue:1,value:1,type:"integer",explicitSet:!1,valueSet:null,pdfVersion:1.7}},c=Object.keys(s),u=[],h=0,l=0,f=0;function d(t,e){var r,n=!1;for(r=0;r<t.length;r+=1)t[r]===e&&(n=!0);return n}if(void 0===this.internal.viewerpreferences&&(this.internal.viewerpreferences={},this.internal.viewerpreferences.configuration=JSON.parse(JSON.stringify(s)),this.internal.viewerpreferences.isSubscribed=!1),n=this.internal.viewerpreferences.configuration,"reset"===e||!0===r){var p=c.length;for(f=0;f<p;f+=1)n[c[f]].value=n[c[f]].defaultValue,n[c[f]].explicitSet=!1}if("object"===t(e))for(a in e)if(o=e[a],d(c,a)&&void 0!==o){if("boolean"===n[a].type&&"boolean"==typeof o)n[a].value=o;else if("name"===n[a].type&&d(n[a].valueSet,o))n[a].value=o;else if("integer"===n[a].type&&Number.isInteger(o))n[a].value=o;else if("array"===n[a].type){for(h=0;h<o.length;h+=1)if(i=!0,1===o[h].length&&"number"==typeof o[h][0])u.push(String(o[h]-1));else if(o[h].length>1){for(l=0;l<o[h].length;l+=1)"number"!=typeof o[h][l]&&(i=!1);!0===i&&u.push([o[h][0]-1,o[h][1]-1].join(" "))}n[a].value="["+u.join(" ")+"]"}else n[a].value=n[a].defaultValue;n[a].explicitSet=!0}return!1===this.internal.viewerpreferences.isSubscribed&&(this.internal.events.subscribe("putCatalog",(function(){var t,e=[];for(t in n)!0===n[t].explicitSet&&("name"===n[t].type?e.push("/"+t+" /"+n[t].value):e.push("/"+t+" "+n[t].value));0!==e.length&&this.internal.write("/ViewerPreferences\n<<\n"+e.join("\n")+"\n>>")})),this.internal.viewerpreferences.isSubscribed=!0),this.internal.viewerpreferences.configuration=n,this}, -/** ==================================================================== - * @license - * jsPDF XMP metadata plugin - * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){var e=function(){var t='<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:jspdf="'+this.internal.__metadata__.namespaceuri+'"><jspdf:metadata>',e=unescape(encodeURIComponent('<x:xmpmeta xmlns:x="adobe:ns:meta/">')),r=unescape(encodeURIComponent(t)),n=unescape(encodeURIComponent(this.internal.__metadata__.metadata)),i=unescape(encodeURIComponent("</jspdf:metadata></rdf:Description></rdf:RDF>")),a=unescape(encodeURIComponent("</x:xmpmeta>")),o=r.length+n.length+i.length+e.length+a.length;this.internal.__metadata__.metadata_object_number=this.internal.newObject(),this.internal.write("<< /Type /Metadata /Subtype /XML /Length "+o+" >>"),this.internal.write("stream"),this.internal.write(e+r+n+i+a),this.internal.write("endstream"),this.internal.write("endobj")},r=function(){this.internal.__metadata__.metadata_object_number&&this.internal.write("/Metadata "+this.internal.__metadata__.metadata_object_number+" 0 R")};t.addMetadata=function(t,n){return void 0===this.internal.__metadata__&&(this.internal.__metadata__={metadata:t,namespaceuri:n||"http://jspdf.default.namespaceuri/"},this.internal.events.subscribe("putCatalog",r),this.internal.events.subscribe("postPutResources",e)),this}}(E.API),function(t){var e=t.API,r=e.pdfEscape16=function(t,e){for(var r,n=e.metadata.Unicode.widths,i=["","0","00","000","0000"],a=[""],o=0,s=t.length;o<s;++o){if(r=e.metadata.characterToGlyph(t.charCodeAt(o)),e.metadata.glyIdsUsed.push(r),e.metadata.toUnicode[r]=t.charCodeAt(o),-1==n.indexOf(r)&&(n.push(r),n.push([parseInt(e.metadata.widthOfGlyph(r),10)])),"0"==r)return a.join("");r=r.toString(16),a.push(i[4-r.length],r)}return a.join("")},n=function(t){var e,r,n,i,a,o,s;for(a="/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000><ffff>\nendcodespacerange",n=[],o=0,s=(r=Object.keys(t).sort((function(t,e){return t-e}))).length;o<s;o++)e=r[o],n.length>=100&&(a+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar",n=[]),void 0!==t[e]&&null!==t[e]&&"function"==typeof t[e].toString&&(i=("0000"+t[e].toString(16)).slice(-4),e=("0000"+(+e).toString(16)).slice(-4),n.push("<"+e+"><"+i+">"));return n.length&&(a+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar\n"),a+="endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"};e.events.push(["putFont",function(e){!function(e){var r=e.font,i=e.out,a=e.newObject,o=e.putStream;if(r.metadata instanceof t.API.TTFFont&&"Identity-H"===r.encoding){for(var s=r.metadata.Unicode.widths,c=r.metadata.subset.encode(r.metadata.glyIdsUsed,1),u="",h=0;h<c.length;h++)u+=String.fromCharCode(c[h]);var l=a();o({data:u,addLength1:!0,objectId:l}),i("endobj");var f=a();o({data:n(r.metadata.toUnicode),addLength1:!0,objectId:f}),i("endobj");var d=a();i("<<"),i("/Type /FontDescriptor"),i("/FontName /"+F(r.fontName)),i("/FontFile2 "+l+" 0 R"),i("/FontBBox "+t.API.PDFObject.convert(r.metadata.bbox)),i("/Flags "+r.metadata.flags),i("/StemV "+r.metadata.stemV),i("/ItalicAngle "+r.metadata.italicAngle),i("/Ascent "+r.metadata.ascender),i("/Descent "+r.metadata.decender),i("/CapHeight "+r.metadata.capHeight),i(">>"),i("endobj");var p=a();i("<<"),i("/Type /Font"),i("/BaseFont /"+F(r.fontName)),i("/FontDescriptor "+d+" 0 R"),i("/W "+t.API.PDFObject.convert(s)),i("/CIDToGIDMap /Identity"),i("/DW 1000"),i("/Subtype /CIDFontType2"),i("/CIDSystemInfo"),i("<<"),i("/Supplement 0"),i("/Registry (Adobe)"),i("/Ordering ("+r.encoding+")"),i(">>"),i(">>"),i("endobj"),r.objectNumber=a(),i("<<"),i("/Type /Font"),i("/Subtype /Type0"),i("/ToUnicode "+f+" 0 R"),i("/BaseFont /"+F(r.fontName)),i("/Encoding /"+r.encoding),i("/DescendantFonts ["+p+" 0 R]"),i(">>"),i("endobj"),r.isAlreadyPutted=!0}}(e)}]);e.events.push(["putFont",function(e){!function(e){var r=e.font,i=e.out,a=e.newObject,o=e.putStream;if(r.metadata instanceof t.API.TTFFont&&"WinAnsiEncoding"===r.encoding){for(var s=r.metadata.rawData,c="",u=0;u<s.length;u++)c+=String.fromCharCode(s[u]);var h=a();o({data:c,addLength1:!0,objectId:h}),i("endobj");var l=a();o({data:n(r.metadata.toUnicode),addLength1:!0,objectId:l}),i("endobj");var f=a();i("<<"),i("/Descent "+r.metadata.decender),i("/CapHeight "+r.metadata.capHeight),i("/StemV "+r.metadata.stemV),i("/Type /FontDescriptor"),i("/FontFile2 "+h+" 0 R"),i("/Flags 96"),i("/FontBBox "+t.API.PDFObject.convert(r.metadata.bbox)),i("/FontName /"+F(r.fontName)),i("/ItalicAngle "+r.metadata.italicAngle),i("/Ascent "+r.metadata.ascender),i(">>"),i("endobj"),r.objectNumber=a();for(var d=0;d<r.metadata.hmtx.widths.length;d++)r.metadata.hmtx.widths[d]=parseInt(r.metadata.hmtx.widths[d]*(1e3/r.metadata.head.unitsPerEm));i("<</Subtype/TrueType/Type/Font/ToUnicode "+l+" 0 R/BaseFont/"+F(r.fontName)+"/FontDescriptor "+f+" 0 R/Encoding/"+r.encoding+" /FirstChar 29 /LastChar 255 /Widths "+t.API.PDFObject.convert(r.metadata.hmtx.widths)+">>"),i("endobj"),r.isAlreadyPutted=!0}}(e)}]);var i=function(t){var e,n=t.text||"",i=t.x,a=t.y,o=t.options||{},s=t.mutex||{},c=s.pdfEscape,u=s.activeFontKey,h=s.fonts,l=u,f="",d=0,p="",g=h[l].encoding;if("Identity-H"!==h[l].encoding)return{text:n,x:i,y:a,options:o,mutex:s};for(p=n,l=u,Array.isArray(n)&&(p=n[0]),d=0;d<p.length;d+=1)h[l].metadata.hasOwnProperty("cmap")&&(e=h[l].metadata.cmap.unicode.codeMap[p[d].charCodeAt(0)]),e||p[d].charCodeAt(0)<256&&h[l].metadata.hasOwnProperty("Unicode")?f+=p[d]:f+="";var m="";return parseInt(l.slice(1))<14||"WinAnsiEncoding"===g?m=c(f,l).split("").map((function(t){return t.charCodeAt(0).toString(16)})).join(""):"Identity-H"===g&&(m=r(f,h[l])),s.isHex=!0,{text:m,x:i,y:a,options:o,mutex:s}};e.events.push(["postProcessText",function(t){var e=t.text||"",r=[],n={text:e,x:t.x,y:t.y,options:t.options,mutex:t.mutex};if(Array.isArray(e)){var a=0;for(a=0;a<e.length;a+=1)Array.isArray(e[a])&&3===e[a].length?r.push([i(Object.assign({},n,{text:e[a][0]})).text,e[a][1],e[a][2]]):r.push(i(Object.assign({},n,{text:e[a]})).text);t.text=r}else t.text=i(Object.assign({},n,{text:e})).text}])}(E), -/** - * @license - * jsPDF virtual FileSystem functionality - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(){return void 0===this.internal.vFS&&(this.internal.vFS={}),!0};t.existsFileInVFS=function(t){return e.call(this),void 0!==this.internal.vFS[t]},t.addFileToVFS=function(t,r){return e.call(this),this.internal.vFS[t]=r,this},t.getFileFromVFS=function(t){return e.call(this),void 0!==this.internal.vFS[t]?this.internal.vFS[t]:null}}(E.API), -/** - * @license - * Unicode Bidi Engine based on the work of Alex Shensis (@asthensis) - * MIT License - */ -function(t){t.__bidiEngine__=t.prototype.__bidiEngine__=function(t){var r,n,i,a,o,s,c,u=e,h=[[0,3,0,1,0,0,0],[0,3,0,1,2,2,0],[0,3,0,17,2,0,1],[0,3,5,5,4,1,0],[0,3,21,21,4,0,1],[0,3,5,5,4,2,0]],l=[[2,0,1,1,0,1,0],[2,0,1,1,0,2,0],[2,0,2,1,3,2,0],[2,0,2,33,3,1,1]],f={L:0,R:1,EN:2,AN:3,N:4,B:5,S:6},d={0:0,5:1,6:2,7:3,32:4,251:5,254:6,255:7},p=["(",")","(","<",">","<","[","]","[","{","}","{","«","»","«","‹","›","‹","⁅","⁆","⁅","⁽","⁾","⁽","₍","₎","₍","≤","≥","≤","〈","〉","〈","﹙","﹚","﹙","﹛","﹜","﹛","﹝","﹞","﹝","﹤","﹥","﹤"],g=new RegExp(/^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/),m=!1,v=0;this.__bidiEngine__={};var b=function(t){var e=t.charCodeAt(),r=e>>8,n=d[r];return void 0!==n?u[256*n+(255&e)]:252===r||253===r?"AL":g.test(r)?"L":8===r?"R":"N"},y=function(t){for(var e,r=0;r<t.length;r++){if("L"===(e=b(t.charAt(r))))return!1;if("R"===e)return!0}return!1},w=function(t,e,o,s){var c,u,h,l,f=e[s];switch(f){case"L":case"R":m=!1;break;case"N":case"AN":break;case"EN":m&&(f="AN");break;case"AL":m=!0,f="R";break;case"WS":f="N";break;case"CS":s<1||s+1>=e.length||"EN"!==(c=o[s-1])&&"AN"!==c||"EN"!==(u=e[s+1])&&"AN"!==u?f="N":m&&(u="AN"),f=u===c?u:"N";break;case"ES":f="EN"===(c=s>0?o[s-1]:"B")&&s+1<e.length&&"EN"===e[s+1]?"EN":"N";break;case"ET":if(s>0&&"EN"===o[s-1]){f="EN";break}if(m){f="N";break}for(h=s+1,l=e.length;h<l&&"ET"===e[h];)h++;f=h<l&&"EN"===e[h]?"EN":"N";break;case"NSM":if(i&&!a){for(l=e.length,h=s+1;h<l&&"NSM"===e[h];)h++;if(h<l){var d=t[s],p=d>=1425&&d<=2303||64286===d;if(c=e[h],p&&("R"===c||"AL"===c)){f="R";break}}}f=s<1||"B"===(c=e[s-1])?"N":o[s-1];break;case"B":m=!1,r=!0,f=v;break;case"S":n=!0,f="N";break;case"LRE":case"RLE":case"LRO":case"RLO":case"PDF":m=!1;break;case"BN":f="N"}return f},N=function(t,e,r){var n=t.split("");return r&&L(n,r,{hiLevel:v}),n.reverse(),e&&e.reverse(),n.join("")},L=function(t,e,i){var a,o,s,c,u,d=-1,p=t.length,g=0,y=[],N=v?l:h,L=[];for(m=!1,r=!1,n=!1,o=0;o<p;o++)L[o]=b(t[o]);for(s=0;s<p;s++){if(u=g,y[s]=w(t,L,y,s),a=240&(g=N[u][f[y[s]]]),g&=15,e[s]=c=N[g][5],a>0)if(16===a){for(o=d;o<s;o++)e[o]=1;d=-1}else d=-1;if(N[g][6])-1===d&&(d=s);else if(d>-1){for(o=d;o<s;o++)e[o]=c;d=-1}"B"===L[s]&&(e[s]=0),i.hiLevel|=c}n&&function(t,e,r){for(var n=0;n<r;n++)if("S"===t[n]){e[n]=v;for(var i=n-1;i>=0&&"WS"===t[i];i--)e[i]=v}}(L,e,p)},A=function(t,e,n,i,a){if(!(a.hiLevel<t)){if(1===t&&1===v&&!r)return e.reverse(),void(n&&n.reverse());for(var o,s,c,u,h=e.length,l=0;l<h;){if(i[l]>=t){for(c=l+1;c<h&&i[c]>=t;)c++;for(u=l,s=c-1;u<s;u++,s--)o=e[u],e[u]=e[s],e[s]=o,n&&(o=n[u],n[u]=n[s],n[s]=o);l=c}l++}}},x=function(t,e,r){var n=t.split(""),i={hiLevel:v};return r||(r=[]),L(n,r,i),function(t,e,r){if(0!==r.hiLevel&&c)for(var n,i=0;i<t.length;i++)1===e[i]&&(n=p.indexOf(t[i]))>=0&&(t[i]=p[n+1])}(n,r,i),A(2,n,e,r,i),A(1,n,e,r,i),n.join("")};return this.__bidiEngine__.doBidiReorder=function(t,e,r){if(function(t,e){if(e)for(var r=0;r<t.length;r++)e[r]=r;void 0===a&&(a=y(t)),void 0===s&&(s=y(t))}(t,e),i||!o||s)if(i&&o&&a^s)v=a?1:0,t=N(t,e,r);else if(!i&&o&&s)v=a?1:0,t=x(t,e,r),t=N(t,e);else if(!i||a||o||s){if(i&&!o&&a^s)t=N(t,e),a?(v=0,t=x(t,e,r)):(v=1,t=x(t,e,r),t=N(t,e));else if(i&&a&&!o&&s)v=1,t=x(t,e,r),t=N(t,e);else if(!i&&!o&&a^s){var n=c;a?(v=1,t=x(t,e,r),v=0,c=!1,t=x(t,e,r),c=n):(v=0,t=x(t,e,r),t=N(t,e),v=1,c=!1,t=x(t,e,r),c=n,t=N(t,e))}}else v=0,t=x(t,e,r);else v=a?1:0,t=x(t,e,r);return t},this.__bidiEngine__.setOptions=function(t){t&&(i=t.isInputVisual,o=t.isOutputVisual,a=t.isInputRtl,s=t.isOutputRtl,c=t.isSymmetricSwapping)},this.__bidiEngine__.setOptions(t),this.__bidiEngine__};var e=["BN","BN","BN","BN","BN","BN","BN","BN","BN","S","B","S","WS","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","B","B","B","S","WS","N","N","ET","ET","ET","N","N","N","N","N","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","BN","BN","BN","BN","BN","BN","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","CS","N","ET","ET","ET","ET","N","N","N","N","L","N","N","BN","N","N","ET","ET","EN","EN","N","L","N","N","N","EN","L","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","N","N","N","N","N","ET","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","R","NSM","R","NSM","NSM","R","NSM","NSM","R","NSM","N","N","N","N","N","N","N","N","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","N","N","N","N","N","R","R","R","R","R","N","N","N","N","N","N","N","N","N","N","N","AN","AN","AN","AN","AN","AN","N","N","AL","ET","ET","AL","CS","AL","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","AN","AN","AN","AN","AN","AN","AN","AN","AN","ET","AN","AN","AL","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","N","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","NSM","NSM","N","NSM","NSM","NSM","NSM","AL","AL","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","N","N","N","N","N","N","N","N","N","N","N","N","N","N","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","R","R","N","N","N","N","R","N","N","N","N","N","WS","WS","WS","WS","WS","WS","WS","WS","WS","WS","WS","BN","BN","BN","L","R","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","WS","B","LRE","RLE","PDF","LRO","RLO","CS","ET","ET","ET","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","CS","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","WS","BN","BN","BN","BN","BN","N","LRI","RLI","FSI","PDI","BN","BN","BN","BN","BN","BN","EN","L","N","N","EN","EN","EN","EN","EN","EN","ES","ES","N","N","N","L","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","ES","ES","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","L","L","N","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","N","N","N","N","N","R","NSM","R","R","R","R","R","R","R","R","R","R","ES","R","R","R","R","R","R","R","R","R","R","R","R","R","N","R","R","R","R","R","N","R","N","R","R","N","R","R","N","R","R","R","R","R","R","R","R","R","R","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","CS","N","CS","N","N","CS","N","N","N","N","N","N","N","N","N","ET","N","N","ES","ES","N","N","N","N","N","ET","ET","N","N","N","N","N","AL","AL","AL","AL","AL","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","N","BN","N","N","N","ET","ET","ET","N","N","N","N","N","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","L","L","L","L","L","L","N","N","L","L","L","L","L","L","N","N","L","L","L","L","L","L","N","N","L","L","L","N","N","N","ET","ET","N","N","N","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N"],r=new t.__bidiEngine__({isInputVisual:!0});t.API.events.push(["postProcessText",function(t){var e=t.text,n=(t.x,t.y,t.options||{}),i=(t.mutex,n.lang,[]);if(n.isInputVisual="boolean"!=typeof n.isInputVisual||n.isInputVisual,r.setOptions(n),"[object Array]"===Object.prototype.toString.call(e)){var a=0;for(i=[],a=0;a<e.length;a+=1)"[object Array]"===Object.prototype.toString.call(e[a])?i.push([r.doBidiReorder(e[a][0]),e[a][1],e[a][2]]):i.push([r.doBidiReorder(e[a])]);t.text=i}else t.text=r.doBidiReorder(e);r.setOptions({isInputVisual:!0})}])}(E),E.API.TTFFont=function(){function t(t){var e;if(this.rawData=t,e=this.contents=new ne(t),this.contents.pos=4,"ttcf"===e.readString(4))throw new Error("TTCF not supported.");e.pos=0,this.parse(),this.subset=new Le(this),this.registerTTF()}return t.open=function(e){return new t(e)},t.prototype.parse=function(){return this.directory=new ie(this.contents),this.head=new se(this),this.name=new pe(this),this.cmap=new ue(this),this.toUnicode={},this.hhea=new he(this),this.maxp=new ge(this),this.hmtx=new me(this),this.post=new fe(this),this.os2=new le(this),this.loca=new Ne(this),this.glyf=new be(this),this.ascender=this.os2.exists&&this.os2.ascender||this.hhea.ascender,this.decender=this.os2.exists&&this.os2.decender||this.hhea.decender,this.lineGap=this.os2.exists&&this.os2.lineGap||this.hhea.lineGap,this.bbox=[this.head.xMin,this.head.yMin,this.head.xMax,this.head.yMax]},t.prototype.registerTTF=function(){var t,e,r,n,i;this.scaleFactor=1e3/this.head.unitsPerEm,this.bbox=function(){var e,r,n,i;for(i=[],e=0,r=(n=this.bbox).length;e<r;e++)t=n[e],i.push(Math.round(t*this.scaleFactor));return i}.call(this),this.stemV=0,this.post.exists?(r=255&(n=this.post.italic_angle),0!=(32768&(e=n>>16))&&(e=-(1+(65535^e))),this.italicAngle=+(e+"."+r)):this.italicAngle=0,this.ascender=Math.round(this.ascender*this.scaleFactor),this.decender=Math.round(this.decender*this.scaleFactor),this.lineGap=Math.round(this.lineGap*this.scaleFactor),this.capHeight=this.os2.exists&&this.os2.capHeight||this.ascender,this.xHeight=this.os2.exists&&this.os2.xHeight||0,this.familyClass=(this.os2.exists&&this.os2.familyClass||0)>>8,this.isSerif=1===(i=this.familyClass)||2===i||3===i||4===i||5===i||7===i,this.isScript=10===this.familyClass,this.flags=0,this.post.isFixedPitch&&(this.flags|=1),this.isSerif&&(this.flags|=2),this.isScript&&(this.flags|=8),0!==this.italicAngle&&(this.flags|=64),this.flags|=32,this.cmap.unicode||console.log("No unicode cmap for font")},t.prototype.characterToGlyph=function(t){var e;return(null!=(e=this.cmap.unicode)?e.codeMap[t]:void 0)||0},t.prototype.widthOfGlyph=function(t){var e;return e=1e3/this.head.unitsPerEm,this.hmtx.forGlyph(t).advance*e},t.prototype.widthOfString=function(t,e,r){var n,i,a,o;for(a=0,i=0,o=(t=""+t).length;0<=o?i<o:i>o;i=0<=o?++i:--i)n=t.charCodeAt(i),a+=this.widthOfGlyph(this.characterToGlyph(n))+r*(1e3/e)||0;return a*(e/1e3)},t.prototype.lineHeight=function(t,e){var r;return null==e&&(e=!1),r=e?this.lineGap:0,(this.ascender+r-this.decender)/1e3*t},t}();var re,ne=function(){function t(t){this.data=null!=t?t:[],this.pos=0,this.length=this.data.length}return t.prototype.readByte=function(){return this.data[this.pos++]},t.prototype.writeByte=function(t){return this.data[this.pos++]=t},t.prototype.readUInt32=function(){return 16777216*this.readByte()+(this.readByte()<<16)+(this.readByte()<<8)+this.readByte()},t.prototype.writeUInt32=function(t){return this.writeByte(t>>>24&255),this.writeByte(t>>16&255),this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt32=function(){var t;return(t=this.readUInt32())>=2147483648?t-4294967296:t},t.prototype.writeInt32=function(t){return t<0&&(t+=4294967296),this.writeUInt32(t)},t.prototype.readUInt16=function(){return this.readByte()<<8|this.readByte()},t.prototype.writeUInt16=function(t){return this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt16=function(){var t;return(t=this.readUInt16())>=32768?t-65536:t},t.prototype.writeInt16=function(t){return t<0&&(t+=65536),this.writeUInt16(t)},t.prototype.readString=function(t){var e,r;for(r=[],e=0;0<=t?e<t:e>t;e=0<=t?++e:--e)r[e]=String.fromCharCode(this.readByte());return r.join("")},t.prototype.writeString=function(t){var e,r,n;for(n=[],e=0,r=t.length;0<=r?e<r:e>r;e=0<=r?++e:--e)n.push(this.writeByte(t.charCodeAt(e)));return n},t.prototype.readShort=function(){return this.readInt16()},t.prototype.writeShort=function(t){return this.writeInt16(t)},t.prototype.readLongLong=function(){var t,e,r,n,i,a,o,s;return t=this.readByte(),e=this.readByte(),r=this.readByte(),n=this.readByte(),i=this.readByte(),a=this.readByte(),o=this.readByte(),s=this.readByte(),128&t?-1*(72057594037927940*(255^t)+281474976710656*(255^e)+1099511627776*(255^r)+4294967296*(255^n)+16777216*(255^i)+65536*(255^a)+256*(255^o)+(255^s)+1):72057594037927940*t+281474976710656*e+1099511627776*r+4294967296*n+16777216*i+65536*a+256*o+s},t.prototype.writeLongLong=function(t){var e,r;return e=Math.floor(t/4294967296),r=4294967295&t,this.writeByte(e>>24&255),this.writeByte(e>>16&255),this.writeByte(e>>8&255),this.writeByte(255&e),this.writeByte(r>>24&255),this.writeByte(r>>16&255),this.writeByte(r>>8&255),this.writeByte(255&r)},t.prototype.readInt=function(){return this.readInt32()},t.prototype.writeInt=function(t){return this.writeInt32(t)},t.prototype.read=function(t){var e,r;for(e=[],r=0;0<=t?r<t:r>t;r=0<=t?++r:--r)e.push(this.readByte());return e},t.prototype.write=function(t){var e,r,n,i;for(i=[],r=0,n=t.length;r<n;r++)e=t[r],i.push(this.writeByte(e));return i},t}(),ie=function(){var t;function e(t){var e,r,n;for(this.scalarType=t.readInt(),this.tableCount=t.readShort(),this.searchRange=t.readShort(),this.entrySelector=t.readShort(),this.rangeShift=t.readShort(),this.tables={},r=0,n=this.tableCount;0<=n?r<n:r>n;r=0<=n?++r:--r)e={tag:t.readString(4),checksum:t.readInt(),offset:t.readInt(),length:t.readInt()},this.tables[e.tag]=e}return e.prototype.encode=function(e){var r,n,i,a,o,s,c,u,h,l,f,d,p;for(p in f=Object.keys(e).length,s=Math.log(2),h=16*Math.floor(Math.log(f)/s),a=Math.floor(h/s),u=16*f-h,(n=new ne).writeInt(this.scalarType),n.writeShort(f),n.writeShort(h),n.writeShort(a),n.writeShort(u),i=16*f,c=n.pos+i,o=null,d=[],e)for(l=e[p],n.writeString(p),n.writeInt(t(l)),n.writeInt(c),n.writeInt(l.length),d=d.concat(l),"head"===p&&(o=c),c+=l.length;c%4;)d.push(0),c++;return n.write(d),r=2981146554-t(n.data),n.pos=o+8,n.writeUInt32(r),n.data},t=function(t){var e,r,n,i;for(t=ve.call(t);t.length%4;)t.push(0);for(n=new ne(t),r=0,e=0,i=t.length;e<i;e=e+=4)r+=n.readUInt32();return 4294967295&r},e}(),ae={}.hasOwnProperty,oe=function(t,e){for(var r in e)ae.call(e,r)&&(t[r]=e[r]);function n(){this.constructor=t}return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t};re=function(){function t(t){var e;this.file=t,e=this.file.directory.tables[this.tag],this.exists=!!e,e&&(this.offset=e.offset,this.length=e.length,this.parse(this.file.contents))}return t.prototype.parse=function(){},t.prototype.encode=function(){},t.prototype.raw=function(){return this.exists?(this.file.contents.pos=this.offset,this.file.contents.read(this.length)):null},t}();var se=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="head",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.revision=t.readInt(),this.checkSumAdjustment=t.readInt(),this.magicNumber=t.readInt(),this.flags=t.readShort(),this.unitsPerEm=t.readShort(),this.created=t.readLongLong(),this.modified=t.readLongLong(),this.xMin=t.readShort(),this.yMin=t.readShort(),this.xMax=t.readShort(),this.yMax=t.readShort(),this.macStyle=t.readShort(),this.lowestRecPPEM=t.readShort(),this.fontDirectionHint=t.readShort(),this.indexToLocFormat=t.readShort(),this.glyphDataFormat=t.readShort()},e.prototype.encode=function(t){var e;return(e=new ne).writeInt(this.version),e.writeInt(this.revision),e.writeInt(this.checkSumAdjustment),e.writeInt(this.magicNumber),e.writeShort(this.flags),e.writeShort(this.unitsPerEm),e.writeLongLong(this.created),e.writeLongLong(this.modified),e.writeShort(this.xMin),e.writeShort(this.yMin),e.writeShort(this.xMax),e.writeShort(this.yMax),e.writeShort(this.macStyle),e.writeShort(this.lowestRecPPEM),e.writeShort(this.fontDirectionHint),e.writeShort(t),e.writeShort(this.glyphDataFormat),e.data},e}(),ce=function(){function t(t,e){var r,n,i,a,o,s,c,u,h,l,f,d,p,g,m,v,b;switch(this.platformID=t.readUInt16(),this.encodingID=t.readShort(),this.offset=e+t.readInt(),h=t.pos,t.pos=this.offset,this.format=t.readUInt16(),this.length=t.readUInt16(),this.language=t.readUInt16(),this.isUnicode=3===this.platformID&&1===this.encodingID&&4===this.format||0===this.platformID&&4===this.format,this.codeMap={},this.format){case 0:for(s=0;s<256;++s)this.codeMap[s]=t.readByte();break;case 4:for(f=t.readUInt16(),l=f/2,t.pos+=6,i=function(){var e,r;for(r=[],s=e=0;0<=l?e<l:e>l;s=0<=l?++e:--e)r.push(t.readUInt16());return r}(),t.pos+=2,p=function(){var e,r;for(r=[],s=e=0;0<=l?e<l:e>l;s=0<=l?++e:--e)r.push(t.readUInt16());return r}(),c=function(){var e,r;for(r=[],s=e=0;0<=l?e<l:e>l;s=0<=l?++e:--e)r.push(t.readUInt16());return r}(),u=function(){var e,r;for(r=[],s=e=0;0<=l?e<l:e>l;s=0<=l?++e:--e)r.push(t.readUInt16());return r}(),n=(this.length-t.pos+this.offset)/2,o=function(){var e,r;for(r=[],s=e=0;0<=n?e<n:e>n;s=0<=n?++e:--e)r.push(t.readUInt16());return r}(),s=m=0,b=i.length;m<b;s=++m)for(g=i[s],r=v=d=p[s];d<=g?v<=g:v>=g;r=d<=g?++v:--v)0===u[s]?a=r+c[s]:0!==(a=o[u[s]/2+(r-d)-(l-s)]||0)&&(a+=c[s]),this.codeMap[r]=65535&a}t.pos=h}return t.encode=function(t,e){var r,n,i,a,o,s,c,u,h,l,f,d,p,g,m,v,b,y,w,N,L,A,x,S,_,P,k,I,F,C,j,O,B,M,E,q,D,R,T,U,z,H,W,V,G,Y;switch(I=new ne,a=Object.keys(t).sort((function(t,e){return t-e})),e){case"macroman":for(p=0,g=function(){var t=[];for(d=0;d<256;++d)t.push(0);return t}(),v={0:0},i={},F=0,B=a.length;F<B;F++)null==v[W=t[n=a[F]]]&&(v[W]=++p),i[n]={old:t[n],new:v[t[n]]},g[n]=v[t[n]];return I.writeUInt16(1),I.writeUInt16(0),I.writeUInt32(12),I.writeUInt16(0),I.writeUInt16(262),I.writeUInt16(0),I.write(g),{charMap:i,subtable:I.data,maxGlyphID:p+1};case"unicode":for(P=[],h=[],b=0,v={},r={},m=c=null,C=0,M=a.length;C<M;C++)null==v[w=t[n=a[C]]]&&(v[w]=++b),r[n]={old:w,new:v[w]},o=v[w]-n,null!=m&&o===c||(m&&h.push(m),P.push(n),c=o),m=n;for(m&&h.push(m),h.push(65535),P.push(65535),S=2*(x=P.length),A=2*Math.pow(Math.log(x)/Math.LN2,2),l=Math.log(A/2)/Math.LN2,L=2*x-A,s=[],N=[],f=[],d=j=0,E=P.length;j<E;d=++j){if(_=P[d],u=h[d],65535===_){s.push(0),N.push(0);break}if(_-(k=r[_].new)>=32768)for(s.push(0),N.push(2*(f.length+x-d)),n=O=_;_<=u?O<=u:O>=u;n=_<=u?++O:--O)f.push(r[n].new);else s.push(k-_),N.push(0)}for(I.writeUInt16(3),I.writeUInt16(1),I.writeUInt32(12),I.writeUInt16(4),I.writeUInt16(16+8*x+2*f.length),I.writeUInt16(0),I.writeUInt16(S),I.writeUInt16(A),I.writeUInt16(l),I.writeUInt16(L),z=0,q=h.length;z<q;z++)n=h[z],I.writeUInt16(n);for(I.writeUInt16(0),H=0,D=P.length;H<D;H++)n=P[H],I.writeUInt16(n);for(V=0,R=s.length;V<R;V++)o=s[V],I.writeUInt16(o);for(G=0,T=N.length;G<T;G++)y=N[G],I.writeUInt16(y);for(Y=0,U=f.length;Y<U;Y++)p=f[Y],I.writeUInt16(p);return{charMap:r,subtable:I.data,maxGlyphID:b+1}}},t}(),ue=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="cmap",e.prototype.parse=function(t){var e,r,n;for(t.pos=this.offset,this.version=t.readUInt16(),n=t.readUInt16(),this.tables=[],this.unicode=null,r=0;0<=n?r<n:r>n;r=0<=n?++r:--r)e=new ce(t,this.offset),this.tables.push(e),e.isUnicode&&null==this.unicode&&(this.unicode=e);return!0},e.encode=function(t,e){var r,n;return null==e&&(e="macroman"),r=ce.encode(t,e),(n=new ne).writeUInt16(0),n.writeUInt16(1),r.table=n.data.concat(r.subtable),r},e}(),he=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="hhea",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.ascender=t.readShort(),this.decender=t.readShort(),this.lineGap=t.readShort(),this.advanceWidthMax=t.readShort(),this.minLeftSideBearing=t.readShort(),this.minRightSideBearing=t.readShort(),this.xMaxExtent=t.readShort(),this.caretSlopeRise=t.readShort(),this.caretSlopeRun=t.readShort(),this.caretOffset=t.readShort(),t.pos+=8,this.metricDataFormat=t.readShort(),this.numberOfMetrics=t.readUInt16()},e}(),le=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="OS/2",e.prototype.parse=function(t){if(t.pos=this.offset,this.version=t.readUInt16(),this.averageCharWidth=t.readShort(),this.weightClass=t.readUInt16(),this.widthClass=t.readUInt16(),this.type=t.readShort(),this.ySubscriptXSize=t.readShort(),this.ySubscriptYSize=t.readShort(),this.ySubscriptXOffset=t.readShort(),this.ySubscriptYOffset=t.readShort(),this.ySuperscriptXSize=t.readShort(),this.ySuperscriptYSize=t.readShort(),this.ySuperscriptXOffset=t.readShort(),this.ySuperscriptYOffset=t.readShort(),this.yStrikeoutSize=t.readShort(),this.yStrikeoutPosition=t.readShort(),this.familyClass=t.readShort(),this.panose=function(){var e,r;for(r=[],e=0;e<10;++e)r.push(t.readByte());return r}(),this.charRange=function(){var e,r;for(r=[],e=0;e<4;++e)r.push(t.readInt());return r}(),this.vendorID=t.readString(4),this.selection=t.readShort(),this.firstCharIndex=t.readShort(),this.lastCharIndex=t.readShort(),this.version>0&&(this.ascent=t.readShort(),this.descent=t.readShort(),this.lineGap=t.readShort(),this.winAscent=t.readShort(),this.winDescent=t.readShort(),this.codePageRange=function(){var e,r;for(r=[],e=0;e<2;e=++e)r.push(t.readInt());return r}(),this.version>1))return this.xHeight=t.readShort(),this.capHeight=t.readShort(),this.defaultChar=t.readShort(),this.breakChar=t.readShort(),this.maxContext=t.readShort()},e}(),fe=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="post",e.prototype.parse=function(t){var e,r,n;switch(t.pos=this.offset,this.format=t.readInt(),this.italicAngle=t.readInt(),this.underlinePosition=t.readShort(),this.underlineThickness=t.readShort(),this.isFixedPitch=t.readInt(),this.minMemType42=t.readInt(),this.maxMemType42=t.readInt(),this.minMemType1=t.readInt(),this.maxMemType1=t.readInt(),this.format){case 65536:break;case 131072:var i;for(r=t.readUInt16(),this.glyphNameIndex=[],i=0;0<=r?i<r:i>r;i=0<=r?++i:--i)this.glyphNameIndex.push(t.readUInt16());for(this.names=[],n=[];t.pos<this.offset+this.length;)e=t.readByte(),n.push(this.names.push(t.readString(e)));return n;case 151552:return r=t.readUInt16(),this.offsets=t.read(r);case 196608:break;case 262144:return this.map=function(){var e,r,n;for(n=[],i=e=0,r=this.file.maxp.numGlyphs;0<=r?e<r:e>r;i=0<=r?++e:--e)n.push(t.readUInt32());return n}.call(this)}},e}(),de=function(t,e){this.raw=t,this.length=t.length,this.platformID=e.platformID,this.encodingID=e.encodingID,this.languageID=e.languageID},pe=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="name",e.prototype.parse=function(t){var e,r,n,i,a,o,s,c,u,h,l;for(t.pos=this.offset,t.readShort(),e=t.readShort(),o=t.readShort(),r=[],i=0;0<=e?i<e:i>e;i=0<=e?++i:--i)r.push({platformID:t.readShort(),encodingID:t.readShort(),languageID:t.readShort(),nameID:t.readShort(),length:t.readShort(),offset:this.offset+o+t.readShort()});for(s={},i=u=0,h=r.length;u<h;i=++u)n=r[i],t.pos=n.offset,c=t.readString(n.length),a=new de(c,n),null==s[l=n.nameID]&&(s[l]=[]),s[n.nameID].push(a);this.strings=s,this.copyright=s[0],this.fontFamily=s[1],this.fontSubfamily=s[2],this.uniqueSubfamily=s[3],this.fontName=s[4],this.version=s[5];try{this.postscriptName=s[6][0].raw.replace(/[\x00-\x19\x80-\xff]/g,"")}catch(t){this.postscriptName=s[4][0].raw.replace(/[\x00-\x19\x80-\xff]/g,"")}return this.trademark=s[7],this.manufacturer=s[8],this.designer=s[9],this.description=s[10],this.vendorUrl=s[11],this.designerUrl=s[12],this.license=s[13],this.licenseUrl=s[14],this.preferredFamily=s[15],this.preferredSubfamily=s[17],this.compatibleFull=s[18],this.sampleText=s[19]},e}(),ge=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="maxp",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.numGlyphs=t.readUInt16(),this.maxPoints=t.readUInt16(),this.maxContours=t.readUInt16(),this.maxCompositePoints=t.readUInt16(),this.maxComponentContours=t.readUInt16(),this.maxZones=t.readUInt16(),this.maxTwilightPoints=t.readUInt16(),this.maxStorage=t.readUInt16(),this.maxFunctionDefs=t.readUInt16(),this.maxInstructionDefs=t.readUInt16(),this.maxStackElements=t.readUInt16(),this.maxSizeOfInstructions=t.readUInt16(),this.maxComponentElements=t.readUInt16(),this.maxComponentDepth=t.readUInt16()},e}(),me=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="hmtx",e.prototype.parse=function(t){var e,r,n,i,a,o,s;for(t.pos=this.offset,this.metrics=[],e=0,o=this.file.hhea.numberOfMetrics;0<=o?e<o:e>o;e=0<=o?++e:--e)this.metrics.push({advance:t.readUInt16(),lsb:t.readInt16()});for(n=this.file.maxp.numGlyphs-this.file.hhea.numberOfMetrics,this.leftSideBearings=function(){var r,i;for(i=[],e=r=0;0<=n?r<n:r>n;e=0<=n?++r:--r)i.push(t.readInt16());return i}(),this.widths=function(){var t,e,r,n;for(n=[],t=0,e=(r=this.metrics).length;t<e;t++)i=r[t],n.push(i.advance);return n}.call(this),r=this.widths[this.widths.length-1],s=[],e=a=0;0<=n?a<n:a>n;e=0<=n?++a:--a)s.push(this.widths.push(r));return s},e.prototype.forGlyph=function(t){return t in this.metrics?this.metrics[t]:{advance:this.metrics[this.metrics.length-1].advance,lsb:this.leftSideBearings[t-this.metrics.length]}},e}(),ve=[].slice,be=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="glyf",e.prototype.parse=function(){return this.cache={}},e.prototype.glyphFor=function(t){var e,r,n,i,a,o,s,c,u,h;return t in this.cache?this.cache[t]:(i=this.file.loca,e=this.file.contents,r=i.indexOf(t),0===(n=i.lengthOf(t))?this.cache[t]=null:(e.pos=this.offset+r,a=(o=new ne(e.read(n))).readShort(),c=o.readShort(),h=o.readShort(),s=o.readShort(),u=o.readShort(),this.cache[t]=-1===a?new we(o,c,h,s,u):new ye(o,a,c,h,s,u),this.cache[t]))},e.prototype.encode=function(t,e,r){var n,i,a,o,s;for(a=[],i=[],o=0,s=e.length;o<s;o++)n=t[e[o]],i.push(a.length),n&&(a=a.concat(n.encode(r)));return i.push(a.length),{table:a,offsets:i}},e}(),ye=function(){function t(t,e,r,n,i,a){this.raw=t,this.numberOfContours=e,this.xMin=r,this.yMin=n,this.xMax=i,this.yMax=a,this.compound=!1}return t.prototype.encode=function(){return this.raw.data},t}(),we=function(){function t(t,e,r,n,i){var a,o;for(this.raw=t,this.xMin=e,this.yMin=r,this.xMax=n,this.yMax=i,this.compound=!0,this.glyphIDs=[],this.glyphOffsets=[],a=this.raw;o=a.readShort(),this.glyphOffsets.push(a.pos),this.glyphIDs.push(a.readUInt16()),32&o;)a.pos+=1&o?4:2,128&o?a.pos+=8:64&o?a.pos+=4:8&o&&(a.pos+=2)}return 1,8,32,64,128,t.prototype.encode=function(){var t,e,r;for(e=new ne(ve.call(this.raw.data)),t=0,r=this.glyphIDs.length;t<r;++t)e.pos=this.glyphOffsets[t];return e.data},t}(),Ne=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return oe(e,re),e.prototype.tag="loca",e.prototype.parse=function(t){var e,r;return t.pos=this.offset,e=this.file.head.indexToLocFormat,this.offsets=0===e?function(){var e,n;for(n=[],r=0,e=this.length;r<e;r+=2)n.push(2*t.readUInt16());return n}.call(this):function(){var e,n;for(n=[],r=0,e=this.length;r<e;r+=4)n.push(t.readUInt32());return n}.call(this)},e.prototype.indexOf=function(t){return this.offsets[t]},e.prototype.lengthOf=function(t){return this.offsets[t+1]-this.offsets[t]},e.prototype.encode=function(t,e){for(var r=new Uint32Array(this.offsets.length),n=0,i=0,a=0;a<r.length;++a)if(r[a]=n,i<e.length&&e[i]==a){++i,r[a]=n;var o=this.offsets[a],s=this.offsets[a+1]-o;s>0&&(n+=s)}for(var c=new Array(4*r.length),u=0;u<r.length;++u)c[4*u+3]=255&r[u],c[4*u+2]=(65280&r[u])>>8,c[4*u+1]=(16711680&r[u])>>16,c[4*u]=(4278190080&r[u])>>24;return c},e}(),Le=function(){function t(t){this.font=t,this.subset={},this.unicodes={},this.next=33}return t.prototype.generateCmap=function(){var t,e,r,n,i;for(e in n=this.font.cmap.tables[0].codeMap,t={},i=this.subset)r=i[e],t[e]=n[r];return t},t.prototype.glyphsFor=function(t){var e,r,n,i,a,o,s;for(n={},a=0,o=t.length;a<o;a++)n[i=t[a]]=this.font.glyf.glyphFor(i);for(i in e=[],n)(null!=(r=n[i])?r.compound:void 0)&&e.push.apply(e,r.glyphIDs);if(e.length>0)for(i in s=this.glyphsFor(e))r=s[i],n[i]=r;return n},t.prototype.encode=function(t,e){var r,n,i,a,o,s,c,u,h,l,f,d,p,g,m;for(n in r=ue.encode(this.generateCmap(),"unicode"),a=this.glyphsFor(t),f={0:0},m=r.charMap)f[(s=m[n]).old]=s.new;for(d in l=r.maxGlyphID,a)d in f||(f[d]=l++);return u=function(t){var e,r;for(e in r={},t)r[t[e]]=e;return r}(f),h=Object.keys(u).sort((function(t,e){return t-e})),p=function(){var t,e,r;for(r=[],t=0,e=h.length;t<e;t++)o=h[t],r.push(u[o]);return r}(),i=this.font.glyf.encode(a,p,f),c=this.font.loca.encode(i.offsets,p),g={cmap:this.font.cmap.raw(),glyf:i.table,loca:c,hmtx:this.font.hmtx.raw(),hhea:this.font.hhea.raw(),maxp:this.font.maxp.raw(),post:this.font.post.raw(),name:this.font.name.raw(),head:this.font.head.encode(e)},this.font.os2.exists&&(g["OS/2"]=this.font.os2.raw()),this.font.directory.encode(g)},t}();E.API.PDFObject=function(){var t;function e(){}return t=function(t,e){return(Array(e+1).join("0")+t).slice(-e)},e.convert=function(r){var n,i,a,o;if(Array.isArray(r))return"["+function(){var t,i,a;for(a=[],t=0,i=r.length;t<i;t++)n=r[t],a.push(e.convert(n));return a}().join(" ")+"]";if("string"==typeof r)return"/"+r;if(null!=r?r.isString:void 0)return"("+r+")";if(r instanceof Date)return"(D:"+t(r.getUTCFullYear(),4)+t(r.getUTCMonth(),2)+t(r.getUTCDate(),2)+t(r.getUTCHours(),2)+t(r.getUTCMinutes(),2)+t(r.getUTCSeconds(),2)+"Z)";if("[object Object]"==={}.toString.call(r)){for(i in a=["<<"],r)o=r[i],a.push("/"+i+" "+e.convert(o));return a.push(">>"),a.join("\n")}return""+r},e}();export default E;export{St as AcroForm,At as AcroFormAppearance,mt as AcroFormButton,wt as AcroFormCheckBox,ft as AcroFormChoiceField,pt as AcroFormComboBox,gt as AcroFormEditBox,dt as AcroFormListBox,Lt as AcroFormPasswordField,vt as AcroFormPushButton,bt as AcroFormRadioButton,Nt as AcroFormTextField,j as GState,B as ShadingPattern,M as TilingPattern,E as jsPDF}; - diff --git a/scripts/jspdf.umd.min.js b/scripts/jspdf.umd.min.js deleted file mode 100644 index dd8f69078..000000000 --- a/scripts/jspdf.umd.min.js +++ /dev/null @@ -1,398 +0,0 @@ -/** @license - * - * jsPDF - PDF Document creation from JavaScript - * Version 2.5.1 Built on 2023-11-22T13:26:15.455Z - * CommitID 00000000 - * - * Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF - * 2015-2021 yWorks GmbH, http://www.yworks.com - * 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX - * 2016-2018 Aras Abbasi <aras.abbasi@gmail.com> - * 2010 Aaron Spike, https://github.com/acspike - * 2012 Willow Systems Corporation, https://github.com/willowsystems - * 2012 Pablo Hess, https://github.com/pablohess - * 2012 Florian Jenett, https://github.com/fjenett - * 2013 Warren Weckesser, https://github.com/warrenweckesser - * 2013 Youssef Beddad, https://github.com/lifof - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2013 Stefan Slonevskiy, https://github.com/stefslon - * 2013 Jeremy Morel, https://github.com/jmorel - * 2013 Christoph Hartmann, https://github.com/chris-rock - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Makes, https://github.com/dollaruw - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 Steven Spungin, https://github.com/Flamenco - * 2014 Kenneth Glassey, https://github.com/Gavvers - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Contributor(s): - * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, - * kim3er, mfo, alnorth, Flamenco - */ - -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).jspdf={})}(this,(function(t){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var r=function(){return"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this}();function n(){r.console&&"function"==typeof r.console.log&&r.console.log.apply(r.console,arguments)}var i={log:n,warn:function(t){r.console&&("function"==typeof r.console.warn?r.console.warn.apply(r.console,arguments):n.call(null,arguments))},error:function(t){r.console&&("function"==typeof r.console.error?r.console.error.apply(r.console,arguments):n(t))}};function a(t,e,r){var n=new XMLHttpRequest;n.open("GET",t),n.responseType="blob",n.onload=function(){l(n.response,e,r)},n.onerror=function(){i.error("could not download file")},n.send()}function o(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return e.status>=200&&e.status<=299}function s(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(r){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var c,u,l=r.saveAs||("object"!==("undefined"==typeof window?"undefined":e(window))||window!==r?function(){}:"undefined"!=typeof HTMLAnchorElement&&"download"in HTMLAnchorElement.prototype?function(t,e,n){var i=r.URL||r.webkitURL,c=document.createElement("a");e=e||t.name||"download",c.download=e,c.rel="noopener","string"==typeof t?(c.href=t,c.origin!==location.origin?o(c.href)?a(t,e,n):s(c,c.target="_blank"):s(c)):(c.href=i.createObjectURL(t),setTimeout((function(){i.revokeObjectURL(c.href)}),4e4),setTimeout((function(){s(c)}),0))}:"msSaveOrOpenBlob"in navigator?function(t,r,n){if(r=r||t.name||"download","string"==typeof t)if(o(t))a(t,r,n);else{var c=document.createElement("a");c.href=t,c.target="_blank",setTimeout((function(){s(c)}))}else navigator.msSaveOrOpenBlob(function(t,r){return void 0===r?r={autoBom:!1}:"object"!==e(r)&&(i.warn("Deprecated: Expected third argument to be a object"),r={autoBom:!r}),r.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t}(t,n),r)}:function(t,n,i,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof t)return a(t,n,i);var s="application/octet-stream"===t.type,c=/constructor/i.test(r.HTMLElement)||r.safari,u=/CriOS\/[\d]+/.test(navigator.userAgent);if((u||s&&c)&&"object"===("undefined"==typeof FileReader?"undefined":e(FileReader))){var l=new FileReader;l.onloadend=function(){var t=l.result;t=u?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=t:location=t,o=null},l.readAsDataURL(t)}else{var h=r.URL||r.webkitURL,f=h.createObjectURL(t);o?o.location=f:location.href=f,o=null,setTimeout((function(){h.revokeObjectURL(f)}),4e4)}}); -/** - * A class to parse color values - * @author Stoyan Stefanov <sstoo@gmail.com> - * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} - * @license Use it if you like it - */function h(t){var e;t=t||"",this.ok=!1,"#"==t.charAt(0)&&(t=t.substr(1,6));t={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"}[t=(t=t.replace(/ /g,"")).toLowerCase()]||t;for(var r=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}],n=0;n<r.length;n++){var i=r[n].re,a=r[n].process,o=i.exec(t);o&&(e=a(o),this.r=e[0],this.g=e[1],this.b=e[2],this.ok=!0)}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r}} -/** - * @license - * Joseph Myers does not specify a particular license for his work. - * - * Author: Joseph Myers - * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js - * - * Modified by: Owen Leong - */ -function f(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r=p(r,n,i,a,e[0],7,-680876936),a=p(a,r,n,i,e[1],12,-389564586),i=p(i,a,r,n,e[2],17,606105819),n=p(n,i,a,r,e[3],22,-1044525330),r=p(r,n,i,a,e[4],7,-176418897),a=p(a,r,n,i,e[5],12,1200080426),i=p(i,a,r,n,e[6],17,-1473231341),n=p(n,i,a,r,e[7],22,-45705983),r=p(r,n,i,a,e[8],7,1770035416),a=p(a,r,n,i,e[9],12,-1958414417),i=p(i,a,r,n,e[10],17,-42063),n=p(n,i,a,r,e[11],22,-1990404162),r=p(r,n,i,a,e[12],7,1804603682),a=p(a,r,n,i,e[13],12,-40341101),i=p(i,a,r,n,e[14],17,-1502002290),r=g(r,n=p(n,i,a,r,e[15],22,1236535329),i,a,e[1],5,-165796510),a=g(a,r,n,i,e[6],9,-1069501632),i=g(i,a,r,n,e[11],14,643717713),n=g(n,i,a,r,e[0],20,-373897302),r=g(r,n,i,a,e[5],5,-701558691),a=g(a,r,n,i,e[10],9,38016083),i=g(i,a,r,n,e[15],14,-660478335),n=g(n,i,a,r,e[4],20,-405537848),r=g(r,n,i,a,e[9],5,568446438),a=g(a,r,n,i,e[14],9,-1019803690),i=g(i,a,r,n,e[3],14,-187363961),n=g(n,i,a,r,e[8],20,1163531501),r=g(r,n,i,a,e[13],5,-1444681467),a=g(a,r,n,i,e[2],9,-51403784),i=g(i,a,r,n,e[7],14,1735328473),r=m(r,n=g(n,i,a,r,e[12],20,-1926607734),i,a,e[5],4,-378558),a=m(a,r,n,i,e[8],11,-2022574463),i=m(i,a,r,n,e[11],16,1839030562),n=m(n,i,a,r,e[14],23,-35309556),r=m(r,n,i,a,e[1],4,-1530992060),a=m(a,r,n,i,e[4],11,1272893353),i=m(i,a,r,n,e[7],16,-155497632),n=m(n,i,a,r,e[10],23,-1094730640),r=m(r,n,i,a,e[13],4,681279174),a=m(a,r,n,i,e[0],11,-358537222),i=m(i,a,r,n,e[3],16,-722521979),n=m(n,i,a,r,e[6],23,76029189),r=m(r,n,i,a,e[9],4,-640364487),a=m(a,r,n,i,e[12],11,-421815835),i=m(i,a,r,n,e[15],16,530742520),r=v(r,n=m(n,i,a,r,e[2],23,-995338651),i,a,e[0],6,-198630844),a=v(a,r,n,i,e[7],10,1126891415),i=v(i,a,r,n,e[14],15,-1416354905),n=v(n,i,a,r,e[5],21,-57434055),r=v(r,n,i,a,e[12],6,1700485571),a=v(a,r,n,i,e[3],10,-1894986606),i=v(i,a,r,n,e[10],15,-1051523),n=v(n,i,a,r,e[1],21,-2054922799),r=v(r,n,i,a,e[8],6,1873313359),a=v(a,r,n,i,e[15],10,-30611744),i=v(i,a,r,n,e[6],15,-1560198380),n=v(n,i,a,r,e[13],21,1309151649),r=v(r,n,i,a,e[4],6,-145523070),a=v(a,r,n,i,e[11],10,-1120210379),i=v(i,a,r,n,e[2],15,718787259),n=v(n,i,a,r,e[9],21,-343485551),t[0]=S(r,t[0]),t[1]=S(n,t[1]),t[2]=S(i,t[2]),t[3]=S(a,t[3])}function d(t,e,r,n,i,a){return e=S(S(e,t),S(n,a)),S(e<<i|e>>>32-i,r)}function p(t,e,r,n,i,a,o){return d(e&r|~e&n,t,e,i,a,o)}function g(t,e,r,n,i,a,o){return d(e&n|r&~n,t,e,i,a,o)}function m(t,e,r,n,i,a,o){return d(e^r^n,t,e,i,a,o)}function v(t,e,r,n,i,a,o){return d(r^(e|~n),t,e,i,a,o)}function b(t){var e,r=t.length,n=[1732584193,-271733879,-1732584194,271733878];for(e=64;e<=t.length;e+=64)f(n,y(t.substring(e-64,e)));t=t.substring(e-64);var i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<t.length;e++)i[e>>2]|=t.charCodeAt(e)<<(e%4<<3);if(i[e>>2]|=128<<(e%4<<3),e>55)for(f(n,i),e=0;e<16;e++)i[e]=0;return i[14]=8*r,f(n,i),n}function y(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}c=r.atob.bind(r),u=r.btoa.bind(r);var w="0123456789abcdef".split("");function N(t){for(var e="",r=0;r<4;r++)e+=w[t>>8*r+4&15]+w[t>>8*r&15];return e}function L(t){return String.fromCharCode((255&t)>>0,(65280&t)>>8,(16711680&t)>>16,(4278190080&t)>>24)}function A(t){return function(t){return t.map(L).join("")}(b(t))}var x="5d41402abc4b2a76b9719d911017c592"!=function(t){for(var e=0;e<t.length;e++)t[e]=N(t[e]);return t.join("")}(b("hello"));function S(t,e){if(x){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r}return t+e&4294967295} -/** - * @license - * FPDF is released under a permissive license: there is no usage restriction. - * You may embed it freely in your application (commercial or not), with or - * without modifications. - * - * Reference: http://www.fpdf.org/en/script/script37.php - */function _(t,e){var r,n,i,a;if(t!==r){for(var o=(i=t,a=1+(256/t.length>>0),new Array(a+1).join(i)),s=[],c=0;c<256;c++)s[c]=c;var u=0;for(c=0;c<256;c++){var l=s[c];u=(u+l+o.charCodeAt(c))%256,s[c]=s[u],s[u]=l}r=t,n=s}else s=n;var h=e.length,f=0,d=0,p="";for(c=0;c<h;c++)d=(d+(l=s[f=(f+1)%256]))%256,s[f]=s[d],s[d]=l,o=s[(s[f]+s[d])%256],p+=String.fromCharCode(e.charCodeAt(c)^o);return p} -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - * Author: Owen Leong (@owenl131) - * Date: 15 Oct 2020 - * References: - * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt - * https://github.com/foliojs/pdfkit/blob/master/lib/security.js - * http://www.fpdf.org/en/script/script37.php - */var P={print:4,modify:8,copy:16,"annot-forms":32};function k(t,e,r,n){this.v=1,this.r=2;var i=192;t.forEach((function(t){if(void 0!==P.perm)throw new Error("Invalid permission: "+t);i+=P[t]})),this.padding="(¿N^NuŠAd\0NVÿú\b..\0¶Ðh>€/\f©þdSiz";var a=(e+this.padding).substr(0,32),o=(r+this.padding).substr(0,32);this.O=this.processOwnerPassword(a,o),this.P=-(1+(255^i)),this.encryptionKey=A(a+this.O+this.lsbFirstWord(this.P)+this.hexToBytes(n)).substr(0,5),this.U=_(this.encryptionKey,this.padding)}function F(t){if(/[^\u0000-\u00ff]/.test(t))throw new Error("Invalid PDF Name Object: "+t+", Only accept ASCII characters.");for(var e="",r=t.length,n=0;n<r;n++){var i=t.charCodeAt(n);if(i<33||35===i||37===i||40===i||41===i||47===i||60===i||62===i||91===i||93===i||123===i||125===i||i>126)e+="#"+("0"+i.toString(16)).slice(-2);else e+=t[n]}return e}function I(t){if("object"!==e(t))throw new Error("Invalid Context passed to initialize PubSub (jsPDF-module)");var n={};this.subscribe=function(t,e,r){if(r=r||!1,"string"!=typeof t||"function"!=typeof e||"boolean"!=typeof r)throw new Error("Invalid arguments passed to PubSub.subscribe (jsPDF-module)");n.hasOwnProperty(t)||(n[t]={});var i=Math.random().toString(35);return n[t][i]=[e,!!r],i},this.unsubscribe=function(t){for(var e in n)if(n[e][t])return delete n[e][t],0===Object.keys(n[e]).length&&delete n[e],!0;return!1},this.publish=function(e){if(n.hasOwnProperty(e)){var a=Array.prototype.slice.call(arguments,1),o=[];for(var s in n[e]){var c=n[e][s];try{c[0].apply(t,a)}catch(t){r.console&&i.error("jsPDF PubSub Error",t.message,t)}c[1]&&o.push(s)}o.length&&o.forEach(this.unsubscribe)}},this.getTopics=function(){return n}}function C(t){if(!(this instanceof C))return new C(t);var e="opacity,stroke-opacity".split(",");for(var r in t)t.hasOwnProperty(r)&&e.indexOf(r)>=0&&(this[r]=t[r]);this.id="",this.objectNumber=-1}function j(t,e){this.gState=t,this.matrix=e,this.id="",this.objectNumber=-1}function O(t,e,r,n,i){if(!(this instanceof O))return new O(t,e,r,n,i);this.type="axial"===t?2:3,this.coords=e,this.colors=r,j.call(this,n,i)}function B(t,e,r,n,i){if(!(this instanceof B))return new B(t,e,r,n,i);this.boundingBox=t,this.xStep=e,this.yStep=r,this.stream="",this.cloneIndex=0,j.call(this,n,i)}function M(t){var n,a="string"==typeof arguments[0]?arguments[0]:"p",o=arguments[1],s=arguments[2],c=arguments[3],f=[],d=1,p=16,g="S",m=null;"object"===e(t=t||{})&&(a=t.orientation,o=t.unit||o,s=t.format||s,c=t.compress||t.compressPdf||c,null!==(m=t.encryption||null)&&(m.userPassword=m.userPassword||"",m.ownerPassword=m.ownerPassword||"",m.userPermissions=m.userPermissions||[]),d="number"==typeof t.userUnit?Math.abs(t.userUnit):1,void 0!==t.precision&&(n=t.precision),void 0!==t.floatPrecision&&(p=t.floatPrecision),g=t.defaultPathOperation||"S"),f=t.filters||(!0===c?["FlateEncode"]:f),o=o||"mm",a=(""+(a||"P")).toLowerCase();var v=t.putOnlyUsedFonts||!1,b={},y={internal:{},__private__:{}};y.__private__.PubSub=I;var w="1.3",N=y.__private__.getPdfVersion=function(){return w};y.__private__.setPdfVersion=function(t){w=t};var L={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};y.__private__.getPageFormats=function(){return L};var A=y.__private__.getPageFormat=function(t){return L[t]};s=s||"a4";var x={COMPAT:"compat",ADVANCED:"advanced"},S=x.COMPAT;function _(){this.saveGraphicsState(),ht(new Vt(_t,0,0,-_t,0,Rr()*_t).toString()+" cm"),this.setFontSize(this.getFontSize()/_t),g="n",S=x.ADVANCED}function P(){this.restoreGraphicsState(),g="S",S=x.COMPAT}var j=y.__private__.combineFontStyleAndFontWeight=function(t,e){if("bold"==t&&"normal"==e||"bold"==t&&400==e||"normal"==t&&"italic"==e||"bold"==t&&"italic"==e)throw new Error("Invalid Combination of fontweight and fontstyle");return e&&(t=400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"),t};y.advancedAPI=function(t){var e=S===x.COMPAT;return e&&_.call(this),"function"!=typeof t||(t(this),e&&P.call(this)),this},y.compatAPI=function(t){var e=S===x.ADVANCED;return e&&P.call(this),"function"!=typeof t||(t(this),e&&_.call(this)),this},y.isAdvancedAPI=function(){return S===x.ADVANCED};var E,q=function(t){if(S!==x.ADVANCED)throw new Error(t+" is only available in 'advanced' API mode. You need to call advancedAPI() first.")},D=y.roundToPrecision=y.__private__.roundToPrecision=function(t,e){var r=n||e;if(isNaN(t)||isNaN(r))throw new Error("Invalid argument passed to jsPDF.roundToPrecision");return t.toFixed(r).replace(/0+$/,"")};E=y.hpf=y.__private__.hpf="number"==typeof p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,p)}:"smart"===p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,t>-1&&t<1?16:5)}:function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,16)};var R=y.f2=y.__private__.f2=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f2");return D(t,2)},T=y.__private__.f3=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f3");return D(t,3)},U=y.scale=y.__private__.scale=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.scale");return S===x.COMPAT?t*_t:S===x.ADVANCED?t:void 0},z=function(t){return S===x.COMPAT?Rr()-t:S===x.ADVANCED?t:void 0},H=function(t){return U(z(t))};y.__private__.setPrecision=y.setPrecision=function(t){"number"==typeof parseInt(t,10)&&(n=parseInt(t,10))};var W,V="00000000000000000000000000000000",G=y.__private__.getFileId=function(){return V},Y=y.__private__.setFileId=function(t){return V=void 0!==t&&/^[a-fA-F0-9]{32}$/.test(t)?t.toUpperCase():V.split("").map((function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))})).join(""),null!==m&&(Ye=new k(m.userPermissions,m.userPassword,m.ownerPassword,V)),V};y.setFileId=function(t){return Y(t),this},y.getFileId=function(){return G()};var J=y.__private__.convertDateToPDFDate=function(t){var e=t.getTimezoneOffset(),r=e<0?"+":"-",n=Math.floor(Math.abs(e/60)),i=Math.abs(e%60),a=[r,Q(n),"'",Q(i),"'"].join("");return["D:",t.getFullYear(),Q(t.getMonth()+1),Q(t.getDate()),Q(t.getHours()),Q(t.getMinutes()),Q(t.getSeconds()),a].join("")},X=y.__private__.convertPDFDateToDate=function(t){var e=parseInt(t.substr(2,4),10),r=parseInt(t.substr(6,2),10)-1,n=parseInt(t.substr(8,2),10),i=parseInt(t.substr(10,2),10),a=parseInt(t.substr(12,2),10),o=parseInt(t.substr(14,2),10);return new Date(e,r,n,i,a,o,0)},K=y.__private__.setCreationDate=function(t){var e;if(void 0===t&&(t=new Date),t instanceof Date)e=J(t);else{if(!/^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/.test(t))throw new Error("Invalid argument passed to jsPDF.setCreationDate");e=t}return W=e},Z=y.__private__.getCreationDate=function(t){var e=W;return"jsDate"===t&&(e=X(W)),e};y.setCreationDate=function(t){return K(t),this},y.getCreationDate=function(t){return Z(t)};var $,Q=y.__private__.padd2=function(t){return("0"+parseInt(t)).slice(-2)},tt=y.__private__.padd2Hex=function(t){return("00"+(t=t.toString())).substr(t.length)},et=0,rt=[],nt=[],it=0,at=[],ot=[],st=!1,ct=nt,ut=function(){et=0,it=0,nt=[],rt=[],at=[],Qt=Kt(),te=Kt()};y.__private__.setCustomOutputDestination=function(t){st=!0,ct=t};var lt=function(t){st||(ct=t)};y.__private__.resetCustomOutputDestination=function(){st=!1,ct=nt};var ht=y.__private__.out=function(t){return t=t.toString(),it+=t.length+1,ct.push(t),ct},ft=y.__private__.write=function(t){return ht(1===arguments.length?t.toString():Array.prototype.join.call(arguments," "))},dt=y.__private__.getArrayBuffer=function(t){for(var e=t.length,r=new ArrayBuffer(e),n=new Uint8Array(r);e--;)n[e]=t.charCodeAt(e);return r},pt=[["Helvetica","helvetica","normal","WinAnsiEncoding"],["Helvetica-Bold","helvetica","bold","WinAnsiEncoding"],["Helvetica-Oblique","helvetica","italic","WinAnsiEncoding"],["Helvetica-BoldOblique","helvetica","bolditalic","WinAnsiEncoding"],["Courier","courier","normal","WinAnsiEncoding"],["Courier-Bold","courier","bold","WinAnsiEncoding"],["Courier-Oblique","courier","italic","WinAnsiEncoding"],["Courier-BoldOblique","courier","bolditalic","WinAnsiEncoding"],["Times-Roman","times","normal","WinAnsiEncoding"],["Times-Bold","times","bold","WinAnsiEncoding"],["Times-Italic","times","italic","WinAnsiEncoding"],["Times-BoldItalic","times","bolditalic","WinAnsiEncoding"],["ZapfDingbats","zapfdingbats","normal",null],["Symbol","symbol","normal",null]];y.__private__.getStandardFonts=function(){return pt};var gt=t.fontSize||16;y.__private__.setFontSize=y.setFontSize=function(t){return gt=S===x.ADVANCED?t/_t:t,this};var mt,vt=y.__private__.getFontSize=y.getFontSize=function(){return S===x.COMPAT?gt:gt*_t},bt=t.R2L||!1;y.__private__.setR2L=y.setR2L=function(t){return bt=t,this},y.__private__.getR2L=y.getR2L=function(){return bt};var yt,wt=y.__private__.setZoomMode=function(t){var e=[void 0,null,"fullwidth","fullheight","fullpage","original"];if(/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(t))mt=t;else if(isNaN(t)){if(-1===e.indexOf(t))throw new Error('zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "'+t+'" is not recognized.');mt=t}else mt=parseInt(t,10)};y.__private__.getZoomMode=function(){return mt};var Nt,Lt=y.__private__.setPageMode=function(t){if(-1==[void 0,null,"UseNone","UseOutlines","UseThumbs","FullScreen"].indexOf(t))throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "'+t+'" is not recognized.');yt=t};y.__private__.getPageMode=function(){return yt};var At=y.__private__.setLayoutMode=function(t){if(-1==[void 0,null,"continuous","single","twoleft","tworight","two"].indexOf(t))throw new Error('Layout mode must be one of continuous, single, twoleft, tworight. "'+t+'" is not recognized.');Nt=t};y.__private__.getLayoutMode=function(){return Nt},y.__private__.setDisplayMode=y.setDisplayMode=function(t,e,r){return wt(t),At(e),Lt(r),this};var xt={title:"",subject:"",author:"",keywords:"",creator:""};y.__private__.getDocumentProperty=function(t){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");return xt[t]},y.__private__.getDocumentProperties=function(){return xt},y.__private__.setDocumentProperties=y.setProperties=y.setDocumentProperties=function(t){for(var e in xt)xt.hasOwnProperty(e)&&t[e]&&(xt[e]=t[e]);return this},y.__private__.setDocumentProperty=function(t,e){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");return xt[t]=e};var St,_t,Pt,kt,Ft,It={},Ct={},jt=[],Ot={},Bt={},Mt={},Et={},qt=null,Dt=0,Rt=[],Tt=new I(y),Ut=t.hotfixes||[],zt={},Ht={},Wt=[],Vt=function t(e,r,n,i,a,o){if(!(this instanceof t))return new t(e,r,n,i,a,o);isNaN(e)&&(e=1),isNaN(r)&&(r=0),isNaN(n)&&(n=0),isNaN(i)&&(i=1),isNaN(a)&&(a=0),isNaN(o)&&(o=0),this._matrix=[e,r,n,i,a,o]};Object.defineProperty(Vt.prototype,"sx",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"shy",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"shx",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"sy",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"tx",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"ty",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"a",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"b",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"c",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"d",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"e",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"f",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"rotation",{get:function(){return Math.atan2(this.shx,this.sx)}}),Object.defineProperty(Vt.prototype,"scaleX",{get:function(){return this.decompose().scale.sx}}),Object.defineProperty(Vt.prototype,"scaleY",{get:function(){return this.decompose().scale.sy}}),Object.defineProperty(Vt.prototype,"isIdentity",{get:function(){return 1===this.sx&&(0===this.shy&&(0===this.shx&&(1===this.sy&&(0===this.tx&&0===this.ty))))}}),Vt.prototype.join=function(t){return[this.sx,this.shy,this.shx,this.sy,this.tx,this.ty].map(E).join(t)},Vt.prototype.multiply=function(t){var e=t.sx*this.sx+t.shy*this.shx,r=t.sx*this.shy+t.shy*this.sy,n=t.shx*this.sx+t.sy*this.shx,i=t.shx*this.shy+t.sy*this.sy,a=t.tx*this.sx+t.ty*this.shx+this.tx,o=t.tx*this.shy+t.ty*this.sy+this.ty;return new Vt(e,r,n,i,a,o)},Vt.prototype.decompose=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty,o=Math.sqrt(t*t+e*e),s=(t/=o)*r+(e/=o)*n;r-=t*s,n-=e*s;var c=Math.sqrt(r*r+n*n);return s/=c,t*(n/=c)<e*(r/=c)&&(t=-t,e=-e,s=-s,o=-o),{scale:new Vt(o,0,0,c,0,0),translate:new Vt(1,0,0,1,i,a),rotate:new Vt(t,e,-e,t,0,0),skew:new Vt(1,0,s,1,0,0)}},Vt.prototype.toString=function(t){return this.join(" ")},Vt.prototype.inversed=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty,o=1/(t*n-e*r),s=n*o,c=-e*o,u=-r*o,l=t*o;return new Vt(s,c,u,l,-s*i-u*a,-c*i-l*a)},Vt.prototype.applyToPoint=function(t){var e=t.x*this.sx+t.y*this.shx+this.tx,r=t.x*this.shy+t.y*this.sy+this.ty;return new Cr(e,r)},Vt.prototype.applyToRectangle=function(t){var e=this.applyToPoint(t),r=this.applyToPoint(new Cr(t.x+t.w,t.y+t.h));return new jr(e.x,e.y,r.x-e.x,r.y-e.y)},Vt.prototype.clone=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty;return new Vt(t,e,r,n,i,a)},y.Matrix=Vt;var Gt=y.matrixMult=function(t,e){return e.multiply(t)},Yt=new Vt(1,0,0,1,0,0);y.unitMatrix=y.identityMatrix=Yt;var Jt=function(t,e){if(!Bt[t]){var r=(e instanceof O?"Sh":"P")+(Object.keys(Ot).length+1).toString(10);e.id=r,Bt[t]=r,Ot[r]=e,Tt.publish("addPattern",e)}};y.ShadingPattern=O,y.TilingPattern=B,y.addShadingPattern=function(t,e){return q("addShadingPattern()"),Jt(t,e),this},y.beginTilingPattern=function(t){q("beginTilingPattern()"),Br(t.boundingBox[0],t.boundingBox[1],t.boundingBox[2]-t.boundingBox[0],t.boundingBox[3]-t.boundingBox[1],t.matrix)},y.endTilingPattern=function(t,e){q("endTilingPattern()"),e.stream=ot[$].join("\n"),Jt(t,e),Tt.publish("endTilingPattern",e),Wt.pop().restore()};var Xt=y.__private__.newObject=function(){var t=Kt();return Zt(t,!0),t},Kt=y.__private__.newObjectDeferred=function(){return et++,rt[et]=function(){return it},et},Zt=function(t,e){return e="boolean"==typeof e&&e,rt[t]=it,e&&ht(t+" 0 obj"),t},$t=y.__private__.newAdditionalObject=function(){var t={objId:Kt(),content:""};return at.push(t),t},Qt=Kt(),te=Kt(),ee=y.__private__.decodeColorString=function(t){var e=t.split(" ");if(2!==e.length||"g"!==e[1]&&"G"!==e[1]){if(5===e.length&&("k"===e[4]||"K"===e[4])){e=[(1-e[0])*(1-e[3]),(1-e[1])*(1-e[3]),(1-e[2])*(1-e[3]),"r"]}}else{var r=parseFloat(e[0]);e=[r,r,r,"r"]}for(var n="#",i=0;i<3;i++)n+=("0"+Math.floor(255*parseFloat(e[i])).toString(16)).slice(-2);return n},re=y.__private__.encodeColorString=function(t){var r;"string"==typeof t&&(t={ch1:t});var n=t.ch1,i=t.ch2,a=t.ch3,o=t.ch4,s="draw"===t.pdfColorType?["G","RG","K"]:["g","rg","k"];if("string"==typeof n&&"#"!==n.charAt(0)){var c=new h(n);if(c.ok)n=c.toHex();else if(!/^\d*\.?\d*$/.test(n))throw new Error('Invalid color "'+n+'" passed to jsPDF.encodeColorString.')}if("string"==typeof n&&/^#[0-9A-Fa-f]{3}$/.test(n)&&(n="#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]),"string"==typeof n&&/^#[0-9A-Fa-f]{6}$/.test(n)){var u=parseInt(n.substr(1),16);n=u>>16&255,i=u>>8&255,a=255&u}if(void 0===i||void 0===o&&n===i&&i===a)if("string"==typeof n)r=n+" "+s[0];else switch(t.precision){case 2:r=R(n/255)+" "+s[0];break;case 3:default:r=T(n/255)+" "+s[0]}else if(void 0===o||"object"===e(o)){if(o&&!isNaN(o.a)&&0===o.a)return r=["1.","1.","1.",s[1]].join(" ");if("string"==typeof n)r=[n,i,a,s[1]].join(" ");else switch(t.precision){case 2:r=[R(n/255),R(i/255),R(a/255),s[1]].join(" ");break;default:case 3:r=[T(n/255),T(i/255),T(a/255),s[1]].join(" ")}}else if("string"==typeof n)r=[n,i,a,o,s[2]].join(" ");else switch(t.precision){case 2:r=[R(n),R(i),R(a),R(o),s[2]].join(" ");break;case 3:default:r=[T(n),T(i),T(a),T(o),s[2]].join(" ")}return r},ne=y.__private__.getFilters=function(){return f},ie=y.__private__.putStream=function(t){var e=(t=t||{}).data||"",r=t.filters||ne(),n=t.alreadyAppliedFilters||[],i=t.addLength1||!1,a=e.length,o=t.objectId,s=function(t){return t};if(null!==m&&void 0===o)throw new Error("ObjectId must be passed to putStream for file encryption");null!==m&&(s=Ye.encryptor(o,0));var c={};!0===r&&(r=["FlateEncode"]);var u=t.additionalKeyValues||[],l=(c=void 0!==M.API.processDataByFilters?M.API.processDataByFilters(e,r):{data:e,reverseChain:[]}).reverseChain+(Array.isArray(n)?n.join(" "):n.toString());if(0!==c.data.length&&(u.push({key:"Length",value:c.data.length}),!0===i&&u.push({key:"Length1",value:a})),0!=l.length)if(l.split("/").length-1==1)u.push({key:"Filter",value:l});else{u.push({key:"Filter",value:"["+l+"]"});for(var h=0;h<u.length;h+=1)if("DecodeParms"===u[h].key){for(var f=[],d=0;d<c.reverseChain.split("/").length-1;d+=1)f.push("null");f.push(u[h].value),u[h].value="["+f.join(" ")+"]"}}ht("<<");for(var p=0;p<u.length;p++)ht("/"+u[p].key+" "+u[p].value);ht(">>"),0!==c.data.length&&(ht("stream"),ht(s(c.data)),ht("endstream"))},ae=y.__private__.putPage=function(t){var e=t.number,r=t.data,n=t.objId,i=t.contentsObjId;Zt(n,!0),ht("<</Type /Page"),ht("/Parent "+t.rootDictionaryObjId+" 0 R"),ht("/Resources "+t.resourceDictionaryObjId+" 0 R"),ht("/MediaBox ["+parseFloat(E(t.mediaBox.bottomLeftX))+" "+parseFloat(E(t.mediaBox.bottomLeftY))+" "+E(t.mediaBox.topRightX)+" "+E(t.mediaBox.topRightY)+"]"),null!==t.cropBox&&ht("/CropBox ["+E(t.cropBox.bottomLeftX)+" "+E(t.cropBox.bottomLeftY)+" "+E(t.cropBox.topRightX)+" "+E(t.cropBox.topRightY)+"]"),null!==t.bleedBox&&ht("/BleedBox ["+E(t.bleedBox.bottomLeftX)+" "+E(t.bleedBox.bottomLeftY)+" "+E(t.bleedBox.topRightX)+" "+E(t.bleedBox.topRightY)+"]"),null!==t.trimBox&&ht("/TrimBox ["+E(t.trimBox.bottomLeftX)+" "+E(t.trimBox.bottomLeftY)+" "+E(t.trimBox.topRightX)+" "+E(t.trimBox.topRightY)+"]"),null!==t.artBox&&ht("/ArtBox ["+E(t.artBox.bottomLeftX)+" "+E(t.artBox.bottomLeftY)+" "+E(t.artBox.topRightX)+" "+E(t.artBox.topRightY)+"]"),"number"==typeof t.userUnit&&1!==t.userUnit&&ht("/UserUnit "+t.userUnit),Tt.publish("putPage",{objId:n,pageContext:Rt[e],pageNumber:e,page:r}),ht("/Contents "+i+" 0 R"),ht(">>"),ht("endobj");var a=r.join("\n");return S===x.ADVANCED&&(a+="\nQ"),Zt(i,!0),ie({data:a,filters:ne(),objectId:i}),ht("endobj"),n},oe=y.__private__.putPages=function(){var t,e,r=[];for(t=1;t<=Dt;t++)Rt[t].objId=Kt(),Rt[t].contentsObjId=Kt();for(t=1;t<=Dt;t++)r.push(ae({number:t,data:ot[t],objId:Rt[t].objId,contentsObjId:Rt[t].contentsObjId,mediaBox:Rt[t].mediaBox,cropBox:Rt[t].cropBox,bleedBox:Rt[t].bleedBox,trimBox:Rt[t].trimBox,artBox:Rt[t].artBox,userUnit:Rt[t].userUnit,rootDictionaryObjId:Qt,resourceDictionaryObjId:te}));Zt(Qt,!0),ht("<</Type /Pages");var n="/Kids [";for(e=0;e<Dt;e++)n+=r[e]+" 0 R ";ht(n+"]"),ht("/Count "+Dt),ht(">>"),ht("endobj"),Tt.publish("postPutPages")},se=function(t){Tt.publish("putFont",{font:t,out:ht,newObject:Xt,putStream:ie}),!0!==t.isAlreadyPutted&&(t.objectNumber=Xt(),ht("<<"),ht("/Type /Font"),ht("/BaseFont /"+F(t.postScriptName)),ht("/Subtype /Type1"),"string"==typeof t.encoding&&ht("/Encoding /"+t.encoding),ht("/FirstChar 32"),ht("/LastChar 255"),ht(">>"),ht("endobj"))},ce=function(){for(var t in It)It.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&&se(It[t])},ue=function(t){t.objectNumber=Xt();var e=[];e.push({key:"Type",value:"/XObject"}),e.push({key:"Subtype",value:"/Form"}),e.push({key:"BBox",value:"["+[E(t.x),E(t.y),E(t.x+t.width),E(t.y+t.height)].join(" ")+"]"}),e.push({key:"Matrix",value:"["+t.matrix.toString()+"]"});var r=t.pages[1].join("\n");ie({data:r,additionalKeyValues:e,objectId:t.objectNumber}),ht("endobj")},le=function(){for(var t in zt)zt.hasOwnProperty(t)&&ue(zt[t])},he=function(t,e){var r,n=[],i=1/(e-1);for(r=0;r<1;r+=i)n.push(r);if(n.push(1),0!=t[0].offset){var a={offset:0,color:t[0].color};t.unshift(a)}if(1!=t[t.length-1].offset){var o={offset:1,color:t[t.length-1].color};t.push(o)}for(var s="",c=0,u=0;u<n.length;u++){for(r=n[u];r>t[c+1].offset;)c++;var l=t[c].offset,h=(r-l)/(t[c+1].offset-l),f=t[c].color,d=t[c+1].color;s+=tt(Math.round((1-h)*f[0]+h*d[0]).toString(16))+tt(Math.round((1-h)*f[1]+h*d[1]).toString(16))+tt(Math.round((1-h)*f[2]+h*d[2]).toString(16))}return s.trim()},fe=function(t,e){e||(e=21);var r=Xt(),n=he(t.colors,e),i=[];i.push({key:"FunctionType",value:"0"}),i.push({key:"Domain",value:"[0.0 1.0]"}),i.push({key:"Size",value:"["+e+"]"}),i.push({key:"BitsPerSample",value:"8"}),i.push({key:"Range",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),i.push({key:"Decode",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),ie({data:n,additionalKeyValues:i,alreadyAppliedFilters:["/ASCIIHexDecode"],objectId:r}),ht("endobj"),t.objectNumber=Xt(),ht("<< /ShadingType "+t.type),ht("/ColorSpace /DeviceRGB");var a="/Coords ["+E(parseFloat(t.coords[0]))+" "+E(parseFloat(t.coords[1]))+" ";2===t.type?a+=E(parseFloat(t.coords[2]))+" "+E(parseFloat(t.coords[3])):a+=E(parseFloat(t.coords[2]))+" "+E(parseFloat(t.coords[3]))+" "+E(parseFloat(t.coords[4]))+" "+E(parseFloat(t.coords[5])),ht(a+="]"),t.matrix&&ht("/Matrix ["+t.matrix.toString()+"]"),ht("/Function "+r+" 0 R"),ht("/Extend [true true]"),ht(">>"),ht("endobj")},de=function(t,e){var r=Kt(),n=Xt();e.push({resourcesOid:r,objectOid:n}),t.objectNumber=n;var i=[];i.push({key:"Type",value:"/Pattern"}),i.push({key:"PatternType",value:"1"}),i.push({key:"PaintType",value:"1"}),i.push({key:"TilingType",value:"1"}),i.push({key:"BBox",value:"["+t.boundingBox.map(E).join(" ")+"]"}),i.push({key:"XStep",value:E(t.xStep)}),i.push({key:"YStep",value:E(t.yStep)}),i.push({key:"Resources",value:r+" 0 R"}),t.matrix&&i.push({key:"Matrix",value:"["+t.matrix.toString()+"]"}),ie({data:t.stream,additionalKeyValues:i,objectId:t.objectNumber}),ht("endobj")},pe=function(t){var e;for(e in Ot)Ot.hasOwnProperty(e)&&(Ot[e]instanceof O?fe(Ot[e]):Ot[e]instanceof B&&de(Ot[e],t))},ge=function(t){for(var e in t.objectNumber=Xt(),ht("<<"),t)switch(e){case"opacity":ht("/ca "+R(t[e]));break;case"stroke-opacity":ht("/CA "+R(t[e]))}ht(">>"),ht("endobj")},me=function(){var t;for(t in Mt)Mt.hasOwnProperty(t)&&ge(Mt[t])},ve=function(){for(var t in ht("/XObject <<"),zt)zt.hasOwnProperty(t)&&zt[t].objectNumber>=0&&ht("/"+t+" "+zt[t].objectNumber+" 0 R");Tt.publish("putXobjectDict"),ht(">>")},be=function(){Ye.oid=Xt(),ht("<<"),ht("/Filter /Standard"),ht("/V "+Ye.v),ht("/R "+Ye.r),ht("/U <"+Ye.toHexString(Ye.U)+">"),ht("/O <"+Ye.toHexString(Ye.O)+">"),ht("/P "+Ye.P),ht(">>"),ht("endobj")},ye=function(){for(var t in ht("/Font <<"),It)It.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&&ht("/"+t+" "+It[t].objectNumber+" 0 R");ht(">>")},we=function(){if(Object.keys(Ot).length>0){for(var t in ht("/Shading <<"),Ot)Ot.hasOwnProperty(t)&&Ot[t]instanceof O&&Ot[t].objectNumber>=0&&ht("/"+t+" "+Ot[t].objectNumber+" 0 R");Tt.publish("putShadingPatternDict"),ht(">>")}},Ne=function(t){if(Object.keys(Ot).length>0){for(var e in ht("/Pattern <<"),Ot)Ot.hasOwnProperty(e)&&Ot[e]instanceof y.TilingPattern&&Ot[e].objectNumber>=0&&Ot[e].objectNumber<t&&ht("/"+e+" "+Ot[e].objectNumber+" 0 R");Tt.publish("putTilingPatternDict"),ht(">>")}},Le=function(){if(Object.keys(Mt).length>0){var t;for(t in ht("/ExtGState <<"),Mt)Mt.hasOwnProperty(t)&&Mt[t].objectNumber>=0&&ht("/"+t+" "+Mt[t].objectNumber+" 0 R");Tt.publish("putGStateDict"),ht(">>")}},Ae=function(t){Zt(t.resourcesOid,!0),ht("<<"),ht("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),ye(),we(),Ne(t.objectOid),Le(),ve(),ht(">>"),ht("endobj")},xe=function(){var t=[];ce(),me(),le(),pe(t),Tt.publish("putResources"),t.forEach(Ae),Ae({resourcesOid:te,objectOid:Number.MAX_SAFE_INTEGER}),Tt.publish("postPutResources")},Se=function(){Tt.publish("putAdditionalObjects");for(var t=0;t<at.length;t++){var e=at[t];Zt(e.objId,!0),ht(e.content),ht("endobj")}Tt.publish("postPutAdditionalObjects")},_e=function(t){Ct[t.fontName]=Ct[t.fontName]||{},Ct[t.fontName][t.fontStyle]=t.id},Pe=function(t,e,r,n,i){var a={id:"F"+(Object.keys(It).length+1).toString(10),postScriptName:t,fontName:e,fontStyle:r,encoding:n,isStandardFont:i||!1,metadata:{}};return Tt.publish("addFont",{font:a,instance:this}),It[a.id]=a,_e(a),a.id},ke=function(t){for(var e=0,r=pt.length;e<r;e++){var n=Pe.call(this,t[e][0],t[e][1],t[e][2],pt[e][3],!0);!1===v&&(b[n]=!0);var i=t[e][0].split("-");_e({id:n,fontName:i[0],fontStyle:i[1]||""})}Tt.publish("addFonts",{fonts:It,dictionary:Ct})},Fe=function(t){return t.foo=function(){try{return t.apply(this,arguments)}catch(t){var e=t.stack||"";~e.indexOf(" at ")&&(e=e.split(" at ")[1]);var n="Error in function "+e.split("\n")[0].split("<")[0]+": "+t.message;if(!r.console)throw new Error(n);r.console.error(n,t),r.alert&&alert(n)}},t.foo.bar=t,t.foo},Ie=function(t,e){var r,n,i,a,o,s,c,u,l;if(i=(e=e||{}).sourceEncoding||"Unicode",o=e.outputEncoding,(e.autoencode||o)&&It[St].metadata&&It[St].metadata[i]&&It[St].metadata[i].encoding&&(a=It[St].metadata[i].encoding,!o&&It[St].encoding&&(o=It[St].encoding),!o&&a.codePages&&(o=a.codePages[0]),"string"==typeof o&&(o=a[o]),o)){for(c=!1,s=[],r=0,n=t.length;r<n;r++)(u=o[t.charCodeAt(r)])?s.push(String.fromCharCode(u)):s.push(t[r]),s[r].charCodeAt(0)>>8&&(c=!0);t=s.join("")}for(r=t.length;void 0===c&&0!==r;)t.charCodeAt(r-1)>>8&&(c=!0),r--;if(!c)return t;for(s=e.noBOM?[]:[254,255],r=0,n=t.length;r<n;r++){if((l=(u=t.charCodeAt(r))>>8)>>8)throw new Error("Character at position "+r+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");s.push(l),s.push(u-(l<<8))}return String.fromCharCode.apply(void 0,s)},Ce=y.__private__.pdfEscape=y.pdfEscape=function(t,e){return Ie(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},je=y.__private__.beginPage=function(t){ot[++Dt]=[],Rt[Dt]={objId:0,contentsObjId:0,userUnit:Number(d),artBox:null,bleedBox:null,cropBox:null,trimBox:null,mediaBox:{bottomLeftX:0,bottomLeftY:0,topRightX:Number(t[0]),topRightY:Number(t[1])}},Me(Dt),lt(ot[$])},Oe=function(t,e){var r,n,o;switch(a=e||a,"string"==typeof t&&(r=A(t.toLowerCase()),Array.isArray(r)&&(n=r[0],o=r[1])),Array.isArray(t)&&(n=t[0]*_t,o=t[1]*_t),isNaN(n)&&(n=s[0],o=s[1]),(n>14400||o>14400)&&(i.warn("A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"),n=Math.min(14400,n),o=Math.min(14400,o)),s=[n,o],a.substr(0,1)){case"l":o>n&&(s=[o,n]);break;case"p":n>o&&(s=[o,n])}je(s),pr(fr),ht(Lr),0!==kr&&ht(kr+" J"),0!==Fr&&ht(Fr+" j"),Tt.publish("addPage",{pageNumber:Dt})},Be=function(t){t>0&&t<=Dt&&(ot.splice(t,1),Rt.splice(t,1),Dt--,$>Dt&&($=Dt),this.setPage($))},Me=function(t){t>0&&t<=Dt&&($=t)},Ee=y.__private__.getNumberOfPages=y.getNumberOfPages=function(){return ot.length-1},qe=function(t,e,r){var n,a=void 0;return r=r||{},t=void 0!==t?t:It[St].fontName,e=void 0!==e?e:It[St].fontStyle,n=t.toLowerCase(),void 0!==Ct[n]&&void 0!==Ct[n][e]?a=Ct[n][e]:void 0!==Ct[t]&&void 0!==Ct[t][e]?a=Ct[t][e]:!1===r.disableWarning&&i.warn("Unable to look up font label for font '"+t+"', '"+e+"'. Refer to getFontList() for available fonts."),a||r.noFallback||null==(a=Ct.times[e])&&(a=Ct.times.normal),a},De=y.__private__.putInfo=function(){var t=Xt(),e=function(t){return t};for(var r in null!==m&&(e=Ye.encryptor(t,0)),ht("<<"),ht("/Producer ("+Ce(e("jsPDF "+M.version))+")"),xt)xt.hasOwnProperty(r)&&xt[r]&&ht("/"+r.substr(0,1).toUpperCase()+r.substr(1)+" ("+Ce(e(xt[r]))+")");ht("/CreationDate ("+Ce(e(W))+")"),ht(">>"),ht("endobj")},Re=y.__private__.putCatalog=function(t){var e=(t=t||{}).rootDictionaryObjId||Qt;switch(Xt(),ht("<<"),ht("/Type /Catalog"),ht("/Pages "+e+" 0 R"),mt||(mt="fullwidth"),mt){case"fullwidth":ht("/OpenAction [3 0 R /FitH null]");break;case"fullheight":ht("/OpenAction [3 0 R /FitV null]");break;case"fullpage":ht("/OpenAction [3 0 R /Fit]");break;case"original":ht("/OpenAction [3 0 R /XYZ null null 1]");break;default:var r=""+mt;"%"===r.substr(r.length-1)&&(mt=parseInt(mt)/100),"number"==typeof mt&&ht("/OpenAction [3 0 R /XYZ null null "+R(mt)+"]")}switch(Nt||(Nt="continuous"),Nt){case"continuous":ht("/PageLayout /OneColumn");break;case"single":ht("/PageLayout /SinglePage");break;case"two":case"twoleft":ht("/PageLayout /TwoColumnLeft");break;case"tworight":ht("/PageLayout /TwoColumnRight")}yt&&ht("/PageMode /"+yt),Tt.publish("putCatalog"),ht(">>"),ht("endobj")},Te=y.__private__.putTrailer=function(){ht("trailer"),ht("<<"),ht("/Size "+(et+1)),ht("/Root "+et+" 0 R"),ht("/Info "+(et-1)+" 0 R"),null!==m&&ht("/Encrypt "+Ye.oid+" 0 R"),ht("/ID [ <"+V+"> <"+V+"> ]"),ht(">>")},Ue=y.__private__.putHeader=function(){ht("%PDF-"+w),ht("%ºß¬à")},ze=y.__private__.putXRef=function(){var t="0000000000";ht("xref"),ht("0 "+(et+1)),ht("0000000000 65535 f ");for(var e=1;e<=et;e++){"function"==typeof rt[e]?ht((t+rt[e]()).slice(-10)+" 00000 n "):void 0!==rt[e]?ht((t+rt[e]).slice(-10)+" 00000 n "):ht("0000000000 00000 n ")}},He=y.__private__.buildDocument=function(){ut(),lt(nt),Tt.publish("buildDocument"),Ue(),oe(),Se(),xe(),null!==m&&be(),De(),Re();var t=it;return ze(),Te(),ht("startxref"),ht(""+t),ht("%%EOF"),lt(ot[$]),nt.join("\n")},We=y.__private__.getBlob=function(t){return new Blob([dt(t)],{type:"application/pdf"})},Ve=y.output=y.__private__.output=Fe((function(t,e){switch("string"==typeof(e=e||{})?e={filename:e}:e.filename=e.filename||"generated.pdf",t){case void 0:return He();case"save":y.save(e.filename);break;case"arraybuffer":return dt(He());case"blob":return We(He());case"bloburi":case"bloburl":if(void 0!==r.URL&&"function"==typeof r.URL.createObjectURL)return r.URL&&r.URL.createObjectURL(We(He()))||void 0;i.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");break;case"datauristring":case"dataurlstring":var n="",a=He();try{n=u(a)}catch(t){n=u(unescape(encodeURIComponent(a)))}return"data:application/pdf;filename="+e.filename+";base64,"+n;case"pdfobjectnewwindow":if("[object Window]"===Object.prototype.toString.call(r)){var o="https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js",s=' crossorigin="anonymous"';e.pdfObjectUrl&&(o=e.pdfObjectUrl,s="");var c='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><script src="'+o+'"'+s+'><\/script><script >PDFObject.embed("'+this.output("dataurlstring")+'", '+JSON.stringify(e)+");<\/script></body></html>",l=r.open();return null!==l&&l.document.write(c),l}throw new Error("The option pdfobjectnewwindow just works in a browser-environment.");case"pdfjsnewwindow":if("[object Window]"===Object.prototype.toString.call(r)){var h='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><iframe id="pdfViewer" src="'+(e.pdfJsUrl||"examples/PDF.js/web/viewer.html")+"?file=&downloadName="+e.filename+'" width="500px" height="400px" /></body></html>',f=r.open();if(null!==f){f.document.write(h);var d=this;f.document.documentElement.querySelector("#pdfViewer").onload=function(){f.document.title=e.filename,f.document.documentElement.querySelector("#pdfViewer").contentWindow.PDFViewerApplication.open(d.output("bloburl"))}}return f}throw new Error("The option pdfjsnewwindow just works in a browser-environment.");case"dataurlnewwindow":if("[object Window]"!==Object.prototype.toString.call(r))throw new Error("The option dataurlnewwindow just works in a browser-environment.");var p='<html><style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><iframe src="'+this.output("datauristring",e)+'"></iframe></body></html>',g=r.open();if(null!==g&&(g.document.write(p),g.document.title=e.filename),g||"undefined"==typeof safari)return g;break;case"datauri":case"dataurl":return r.document.location.href=this.output("datauristring",e);default:return null}})),Ge=function(t){return!0===Array.isArray(Ut)&&Ut.indexOf(t)>-1};switch(o){case"pt":_t=1;break;case"mm":_t=72/25.4;break;case"cm":_t=72/2.54;break;case"in":_t=72;break;case"px":_t=1==Ge("px_scaling")?.75:96/72;break;case"pc":case"em":_t=12;break;case"ex":_t=6;break;default:if("number"!=typeof o)throw new Error("Invalid unit: "+o);_t=o}var Ye=null;K(),Y();var Je=function(t){return null!==m?Ye.encryptor(t,0):function(t){return t}},Xe=y.__private__.getPageInfo=y.getPageInfo=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfo");return{objId:Rt[t].objId,pageNumber:t,pageContext:Rt[t]}},Ke=y.__private__.getPageInfoByObjId=function(t){if(isNaN(t)||t%1!=0)throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId");for(var e in Rt)if(Rt[e].objId===t)break;return Xe(e)},Ze=y.__private__.getCurrentPageInfo=y.getCurrentPageInfo=function(){return{objId:Rt[$].objId,pageNumber:$,pageContext:Rt[$]}};y.addPage=function(){return Oe.apply(this,arguments),this},y.setPage=function(){return Me.apply(this,arguments),lt.call(this,ot[$]),this},y.insertPage=function(t){return this.addPage(),this.movePage($,t),this},y.movePage=function(t,e){var r,n;if(t>e){r=ot[t],n=Rt[t];for(var i=t;i>e;i--)ot[i]=ot[i-1],Rt[i]=Rt[i-1];ot[e]=r,Rt[e]=n,this.setPage(e)}else if(t<e){r=ot[t],n=Rt[t];for(var a=t;a<e;a++)ot[a]=ot[a+1],Rt[a]=Rt[a+1];ot[e]=r,Rt[e]=n,this.setPage(e)}return this},y.deletePage=function(){return Be.apply(this,arguments),this},y.__private__.text=y.text=function(t,r,n,i,a){var o,s,c,u,l,h,f,d,p,g=(i=i||{}).scope||this;if("number"==typeof t&&"number"==typeof r&&("string"==typeof n||Array.isArray(n))){var m=n;n=r,r=t,t=m}if(arguments[3]instanceof Vt==!1?(c=arguments[4],u=arguments[5],"object"===e(f=arguments[3])&&null!==f||("string"==typeof c&&(u=c,c=null),"string"==typeof f&&(u=f,f=null),"number"==typeof f&&(c=f,f=null),i={flags:f,angle:c,align:u})):(q("The transform parameter of text() with a Matrix value"),p=a),isNaN(r)||isNaN(n)||null==t)throw new Error("Invalid arguments passed to jsPDF.text");if(0===t.length)return g;var v="",y=!1,w="number"==typeof i.lineHeightFactor?i.lineHeightFactor:hr,N=g.internal.scaleFactor;function L(t){return t=t.split("\t").join(Array(i.TabLen||9).join(" ")),Ce(t,f)}function A(t){for(var e,r=t.concat(),n=[],i=r.length;i--;)"string"==typeof(e=r.shift())?n.push(e):Array.isArray(t)&&(1===e.length||void 0===e[1]&&void 0===e[2])?n.push(e[0]):n.push([e[0],e[1],e[2]]);return n}function _(t,e){var r;if("string"==typeof t)r=e(t)[0];else if(Array.isArray(t)){for(var n,i,a=t.concat(),o=[],s=a.length;s--;)"string"==typeof(n=a.shift())?o.push(e(n)[0]):Array.isArray(n)&&"string"==typeof n[0]&&(i=e(n[0],n[1],n[2]),o.push([i[0],i[1],i[2]]));r=o}return r}var P=!1,k=!0;if("string"==typeof t)P=!0;else if(Array.isArray(t)){var F=t.concat();s=[];for(var I,C=F.length;C--;)("string"!=typeof(I=F.shift())||Array.isArray(I)&&"string"!=typeof I[0])&&(k=!1);P=k}if(!1===P)throw new Error('Type of text must be string or Array. "'+t+'" is not recognized.');"string"==typeof t&&(t=t.match(/[\r?\n]/)?t.split(/\r\n|\r|\n/g):[t]);var j=gt/g.internal.scaleFactor,O=j*(w-1);switch(i.baseline){case"bottom":n-=O;break;case"top":n+=j-O;break;case"hanging":n+=j-2*O;break;case"middle":n+=j/2-O}if((h=i.maxWidth||0)>0&&("string"==typeof t?t=g.splitTextToSize(t,h):"[object Array]"===Object.prototype.toString.call(t)&&(t=t.reduce((function(t,e){return t.concat(g.splitTextToSize(e,h))}),[]))),o={text:t,x:r,y:n,options:i,mutex:{pdfEscape:Ce,activeFontKey:St,fonts:It,activeFontSize:gt}},Tt.publish("preProcessText",o),t=o.text,c=(i=o.options).angle,p instanceof Vt==!1&&c&&"number"==typeof c){c*=Math.PI/180,0===i.rotationDirection&&(c=-c),S===x.ADVANCED&&(c=-c);var B=Math.cos(c),M=Math.sin(c);p=new Vt(B,M,-M,B,0,0)}else c&&c instanceof Vt&&(p=c);S!==x.ADVANCED||p||(p=Yt),void 0!==(l=i.charSpace||_r)&&(v+=E(U(l))+" Tc\n",this.setCharSpace(this.getCharSpace()||0)),void 0!==(d=i.horizontalScale)&&(v+=E(100*d)+" Tz\n");i.lang;var D=-1,R=void 0!==i.renderingMode?i.renderingMode:i.stroke,T=g.internal.getCurrentPageInfo().pageContext;switch(R){case 0:case!1:case"fill":D=0;break;case 1:case!0:case"stroke":D=1;break;case 2:case"fillThenStroke":D=2;break;case 3:case"invisible":D=3;break;case 4:case"fillAndAddForClipping":D=4;break;case 5:case"strokeAndAddPathForClipping":D=5;break;case 6:case"fillThenStrokeAndAddToPathForClipping":D=6;break;case 7:case"addToPathForClipping":D=7}var z=void 0!==T.usedRenderingMode?T.usedRenderingMode:-1;-1!==D?v+=D+" Tr\n":-1!==z&&(v+="0 Tr\n"),-1!==D&&(T.usedRenderingMode=D),u=i.align||"left";var H,W=gt*w,V=g.internal.pageSize.getWidth(),G=It[St];l=i.charSpace||_r,h=i.maxWidth||0,f=Object.assign({autoencode:!0,noBOM:!0},i.flags);var Y=[],J=function(t){return g.getStringUnitWidth(t,{font:G,charSpace:l,fontSize:gt,doKerning:!1})*gt/N};if("[object Array]"===Object.prototype.toString.call(t)){var X;s=A(t),"left"!==u&&(H=s.map(J));var K,Z=0;if("right"===u){r-=H[0],t=[],C=s.length;for(var $=0;$<C;$++)0===$?(K=br(r),X=yr(n)):(K=U(Z-H[$]),X=-W),t.push([s[$],K,X]),Z=H[$]}else if("center"===u){r-=H[0]/2,t=[],C=s.length;for(var Q=0;Q<C;Q++)0===Q?(K=br(r),X=yr(n)):(K=U((Z-H[Q])/2),X=-W),t.push([s[Q],K,X]),Z=H[Q]}else if("left"===u){t=[],C=s.length;for(var tt=0;tt<C;tt++)t.push(s[tt])}else if("justify"===u&&"Identity-H"===G.encoding){t=[],C=s.length,h=0!==h?h:V;for(var et=0,rt=0;rt<C;rt++)if(X=0===rt?yr(n):-W,K=0===rt?br(r):et,rt<C-1){var nt=U((h-H[rt])/(s[rt].split(" ").length-1)),it=s[rt].split(" ");t.push([it[0]+" ",K,X]),et=0;for(var at=1;at<it.length;at++){var ot=(J(it[at-1]+" "+it[at])-J(it[at]))*N+nt;at==it.length-1?t.push([it[at],ot,0]):t.push([it[at]+" ",ot,0]),et-=ot}}else t.push([s[rt],K,X]);t.push(["",et,0])}else{if("justify"!==u)throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".');t=[],C=s.length,h=0!==h?h:V;for(rt=0;rt<C;rt++)X=0===rt?yr(n):-W,K=0===rt?br(r):0,rt<C-1?Y.push(E(U((h-H[rt])/(s[rt].split(" ").length-1)))):Y.push(0),t.push([s[rt],K,X])}}var st="boolean"==typeof i.R2L?i.R2L:bt;!0===st&&(t=_(t,(function(t,e,r){return[t.split("").reverse().join(""),e,r]}))),o={text:t,x:r,y:n,options:i,mutex:{pdfEscape:Ce,activeFontKey:St,fonts:It,activeFontSize:gt}},Tt.publish("postProcessText",o),t=o.text,y=o.mutex.isHex||!1;var ct=It[St].encoding;"WinAnsiEncoding"!==ct&&"StandardEncoding"!==ct||(t=_(t,(function(t,e,r){return[L(t),e,r]}))),s=A(t),t=[];for(var ut,lt,ft,dt=0,pt=1,mt=Array.isArray(s[0])?pt:dt,vt="",yt=function(t,e,r){var n="";return r instanceof Vt?(r="number"==typeof i.angle?Gt(r,new Vt(1,0,0,1,t,e)):Gt(new Vt(1,0,0,1,t,e),r),S===x.ADVANCED&&(r=Gt(new Vt(1,0,0,-1,0,0),r)),n=r.join(" ")+" Tm\n"):n=E(t)+" "+E(e)+" Td\n",n},wt=0;wt<s.length;wt++){switch(vt="",mt){case pt:ft=(y?"<":"(")+s[wt][0]+(y?">":")"),ut=parseFloat(s[wt][1]),lt=parseFloat(s[wt][2]);break;case dt:ft=(y?"<":"(")+s[wt]+(y?">":")"),ut=br(r),lt=yr(n)}void 0!==Y&&void 0!==Y[wt]&&(vt=Y[wt]+" Tw\n"),0===wt?t.push(vt+yt(ut,lt,p)+ft):mt===dt?t.push(vt+ft):mt===pt&&t.push(vt+yt(ut,lt,p)+ft)}t=mt===dt?t.join(" Tj\nT* "):t.join(" Tj\n"),t+=" Tj\n";var Nt="BT\n/";return Nt+=St+" "+gt+" Tf\n",Nt+=E(gt*w)+" TL\n",Nt+=xr+"\n",Nt+=v,Nt+=t,ht(Nt+="ET"),b[St]=!0,g};var $e=y.__private__.clip=y.clip=function(t){return ht("evenodd"===t?"W*":"W"),this};y.clipEvenOdd=function(){return $e("evenodd")},y.__private__.discardPath=y.discardPath=function(){return ht("n"),this};var Qe=y.__private__.isValidStyle=function(t){var e=!1;return-1!==[void 0,null,"S","D","F","DF","FD","f","f*","B","B*","n"].indexOf(t)&&(e=!0),e};y.__private__.setDefaultPathOperation=y.setDefaultPathOperation=function(t){return Qe(t)&&(g=t),this};var tr=y.__private__.getStyle=y.getStyle=function(t){var e=g;switch(t){case"D":case"S":e="S";break;case"F":e="f";break;case"FD":case"DF":e="B";break;case"f":case"f*":case"B":case"B*":e=t}return e},er=y.close=function(){return ht("h"),this};y.stroke=function(){return ht("S"),this},y.fill=function(t){return rr("f",t),this},y.fillEvenOdd=function(t){return rr("f*",t),this},y.fillStroke=function(t){return rr("B",t),this},y.fillStrokeEvenOdd=function(t){return rr("B*",t),this};var rr=function(t,r){"object"===e(r)?ar(r,t):ht(t)},nr=function(t){null===t||S===x.ADVANCED&&void 0===t||(t=tr(t),ht(t))};function ir(t,e,r,n,i){var a=new B(e||this.boundingBox,r||this.xStep,n||this.yStep,this.gState,i||this.matrix);a.stream=this.stream;var o=t+"$$"+this.cloneIndex+++"$$";return Jt(o,a),a}var ar=function(t,e){var r=Bt[t.key],n=Ot[r];if(n instanceof O)ht("q"),ht(or(e)),n.gState&&y.setGState(n.gState),ht(t.matrix.toString()+" cm"),ht("/"+r+" sh"),ht("Q");else if(n instanceof B){var i=new Vt(1,0,0,-1,0,Rr());t.matrix&&(i=i.multiply(t.matrix||Yt),r=ir.call(n,t.key,t.boundingBox,t.xStep,t.yStep,i).id),ht("q"),ht("/Pattern cs"),ht("/"+r+" scn"),n.gState&&y.setGState(n.gState),ht(e),ht("Q")}},or=function(t){switch(t){case"f":case"F":return"W n";case"f*":return"W* n";case"B":return"W S";case"B*":return"W* S";case"S":return"W S";case"n":return"W n"}},sr=y.moveTo=function(t,e){return ht(E(U(t))+" "+E(H(e))+" m"),this},cr=y.lineTo=function(t,e){return ht(E(U(t))+" "+E(H(e))+" l"),this},ur=y.curveTo=function(t,e,r,n,i,a){return ht([E(U(t)),E(H(e)),E(U(r)),E(H(n)),E(U(i)),E(H(a)),"c"].join(" ")),this};y.__private__.line=y.line=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.line");return S===x.COMPAT?this.lines([[r-t,n-e]],t,e,[1,1],i||"S"):this.lines([[r-t,n-e]],t,e,[1,1]).stroke()},y.__private__.lines=y.lines=function(t,e,r,n,i,a){var o,s,c,u,l,h,f,d,p,g,m,v;if("number"==typeof t&&(v=r,r=e,e=t,t=v),n=n||[1,1],a=a||!1,isNaN(e)||isNaN(r)||!Array.isArray(t)||!Array.isArray(n)||!Qe(i)||"boolean"!=typeof a)throw new Error("Invalid arguments passed to jsPDF.lines");for(sr(e,r),o=n[0],s=n[1],u=t.length,g=e,m=r,c=0;c<u;c++)2===(l=t[c]).length?(g=l[0]*o+g,m=l[1]*s+m,cr(g,m)):(h=l[0]*o+g,f=l[1]*s+m,d=l[2]*o+g,p=l[3]*s+m,g=l[4]*o+g,m=l[5]*s+m,ur(h,f,d,p,g,m));return a&&er(),nr(i),this},y.path=function(t){for(var e=0;e<t.length;e++){var r=t[e],n=r.c;switch(r.op){case"m":sr(n[0],n[1]);break;case"l":cr(n[0],n[1]);break;case"c":ur.apply(this,n);break;case"h":er()}}return this},y.__private__.rect=y.rect=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.rect");return S===x.COMPAT&&(n=-n),ht([E(U(t)),E(H(e)),E(U(r)),E(U(n)),"re"].join(" ")),nr(i),this},y.__private__.triangle=y.triangle=function(t,e,r,n,i,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i)||isNaN(a)||!Qe(o))throw new Error("Invalid arguments passed to jsPDF.triangle");return this.lines([[r-t,n-e],[i-r,a-n],[t-i,e-a]],t,e,[1,1],o,!0),this},y.__private__.roundedRect=y.roundedRect=function(t,e,r,n,i,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(i)||isNaN(a)||!Qe(o))throw new Error("Invalid arguments passed to jsPDF.roundedRect");var s=4/3*(Math.SQRT2-1);return i=Math.min(i,.5*r),a=Math.min(a,.5*n),this.lines([[r-2*i,0],[i*s,0,i,a-a*s,i,a],[0,n-2*a],[0,a*s,-i*s,a,-i,a],[2*i-r,0],[-i*s,0,-i,-a*s,-i,-a],[0,2*a-n],[0,-a*s,i*s,-a,i,-a]],t+i,e,[1,1],o,!0),this},y.__private__.ellipse=y.ellipse=function(t,e,r,n,i){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||!Qe(i))throw new Error("Invalid arguments passed to jsPDF.ellipse");var a=4/3*(Math.SQRT2-1)*r,o=4/3*(Math.SQRT2-1)*n;return sr(t+r,e),ur(t+r,e-o,t+a,e-n,t,e-n),ur(t-a,e-n,t-r,e-o,t-r,e),ur(t-r,e+o,t-a,e+n,t,e+n),ur(t+a,e+n,t+r,e+o,t+r,e),nr(i),this},y.__private__.circle=y.circle=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||!Qe(n))throw new Error("Invalid arguments passed to jsPDF.circle");return this.ellipse(t,e,r,r,n)},y.setFont=function(t,e,r){return r&&(e=j(e,r)),St=qe(t,e,{disableWarning:!1}),this};var lr=y.__private__.getFont=y.getFont=function(){return It[qe.apply(y,arguments)]};y.__private__.getFontList=y.getFontList=function(){var t,e,r={};for(t in Ct)if(Ct.hasOwnProperty(t))for(e in r[t]=[],Ct[t])Ct[t].hasOwnProperty(e)&&r[t].push(e);return r},y.addFont=function(t,e,r,n,i){var a=["StandardEncoding","MacRomanEncoding","Identity-H","WinAnsiEncoding"];return arguments[3]&&-1!==a.indexOf(arguments[3])?i=arguments[3]:arguments[3]&&-1==a.indexOf(arguments[3])&&(r=j(r,n)),i=i||"Identity-H",Pe.call(this,t,e,r,i)};var hr,fr=t.lineWidth||.200025,dr=y.__private__.getLineWidth=y.getLineWidth=function(){return fr},pr=y.__private__.setLineWidth=y.setLineWidth=function(t){return fr=t,ht(E(U(t))+" w"),this};y.__private__.setLineDash=M.API.setLineDash=M.API.setLineDashPattern=function(t,e){if(t=t||[],e=e||0,isNaN(e)||!Array.isArray(t))throw new Error("Invalid arguments passed to jsPDF.setLineDash");return t=t.map((function(t){return E(U(t))})).join(" "),e=E(U(e)),ht("["+t+"] "+e+" d"),this};var gr=y.__private__.getLineHeight=y.getLineHeight=function(){return gt*hr};y.__private__.getLineHeight=y.getLineHeight=function(){return gt*hr};var mr=y.__private__.setLineHeightFactor=y.setLineHeightFactor=function(t){return"number"==typeof(t=t||1.15)&&(hr=t),this},vr=y.__private__.getLineHeightFactor=y.getLineHeightFactor=function(){return hr};mr(t.lineHeight);var br=y.__private__.getHorizontalCoordinate=function(t){return U(t)},yr=y.__private__.getVerticalCoordinate=function(t){return S===x.ADVANCED?t:Rt[$].mediaBox.topRightY-Rt[$].mediaBox.bottomLeftY-U(t)},wr=y.__private__.getHorizontalCoordinateString=y.getHorizontalCoordinateString=function(t){return E(br(t))},Nr=y.__private__.getVerticalCoordinateString=y.getVerticalCoordinateString=function(t){return E(yr(t))},Lr=t.strokeColor||"0 G";y.__private__.getStrokeColor=y.getDrawColor=function(){return ee(Lr)},y.__private__.setStrokeColor=y.setDrawColor=function(t,e,r,n){return Lr=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"draw",precision:2}),ht(Lr),this};var Ar=t.fillColor||"0 g";y.__private__.getFillColor=y.getFillColor=function(){return ee(Ar)},y.__private__.setFillColor=y.setFillColor=function(t,e,r,n){return Ar=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"fill",precision:2}),ht(Ar),this};var xr=t.textColor||"0 g",Sr=y.__private__.getTextColor=y.getTextColor=function(){return ee(xr)};y.__private__.setTextColor=y.setTextColor=function(t,e,r,n){return xr=re({ch1:t,ch2:e,ch3:r,ch4:n,pdfColorType:"text",precision:3}),this};var _r=t.charSpace,Pr=y.__private__.getCharSpace=y.getCharSpace=function(){return parseFloat(_r||0)};y.__private__.setCharSpace=y.setCharSpace=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.setCharSpace");return _r=t,this};var kr=0;y.CapJoinStyles={0:0,butt:0,but:0,miter:0,1:1,round:1,rounded:1,circle:1,2:2,projecting:2,project:2,square:2,bevel:2},y.__private__.setLineCap=y.setLineCap=function(t){var e=y.CapJoinStyles[t];if(void 0===e)throw new Error("Line cap style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return kr=e,ht(e+" J"),this};var Fr=0;y.__private__.setLineJoin=y.setLineJoin=function(t){var e=y.CapJoinStyles[t];if(void 0===e)throw new Error("Line join style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return Fr=e,ht(e+" j"),this},y.__private__.setLineMiterLimit=y.__private__.setMiterLimit=y.setLineMiterLimit=y.setMiterLimit=function(t){if(t=t||0,isNaN(t))throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit");return ht(E(U(t))+" M"),this},y.GState=C,y.setGState=function(t){(t="string"==typeof t?Mt[Et[t]]:Ir(null,t)).equals(qt)||(ht("/"+t.id+" gs"),qt=t)};var Ir=function(t,e){if(!t||!Et[t]){var r=!1;for(var n in Mt)if(Mt.hasOwnProperty(n)&&Mt[n].equals(e)){r=!0;break}if(r)e=Mt[n];else{var i="GS"+(Object.keys(Mt).length+1).toString(10);Mt[i]=e,e.id=i}return t&&(Et[t]=e.id),Tt.publish("addGState",e),e}};y.addGState=function(t,e){return Ir(t,e),this},y.saveGraphicsState=function(){return ht("q"),jt.push({key:St,size:gt,color:xr}),this},y.restoreGraphicsState=function(){ht("Q");var t=jt.pop();return St=t.key,gt=t.size,xr=t.color,qt=null,this},y.setCurrentTransformationMatrix=function(t){return ht(t.toString()+" cm"),this},y.comment=function(t){return ht("#"+t),this};var Cr=function(t,e){var r=t||0;Object.defineProperty(this,"x",{enumerable:!0,get:function(){return r},set:function(t){isNaN(t)||(r=parseFloat(t))}});var n=e||0;Object.defineProperty(this,"y",{enumerable:!0,get:function(){return n},set:function(t){isNaN(t)||(n=parseFloat(t))}});var i="pt";return Object.defineProperty(this,"type",{enumerable:!0,get:function(){return i},set:function(t){i=t.toString()}}),this},jr=function(t,e,r,n){Cr.call(this,t,e),this.type="rect";var i=r||0;Object.defineProperty(this,"w",{enumerable:!0,get:function(){return i},set:function(t){isNaN(t)||(i=parseFloat(t))}});var a=n||0;return Object.defineProperty(this,"h",{enumerable:!0,get:function(){return a},set:function(t){isNaN(t)||(a=parseFloat(t))}}),this},Or=function(){this.page=Dt,this.currentPage=$,this.pages=ot.slice(0),this.pagesContext=Rt.slice(0),this.x=Pt,this.y=kt,this.matrix=Ft,this.width=qr($),this.height=Rr($),this.outputDestination=ct,this.id="",this.objectNumber=-1};Or.prototype.restore=function(){Dt=this.page,$=this.currentPage,Rt=this.pagesContext,ot=this.pages,Pt=this.x,kt=this.y,Ft=this.matrix,Dr($,this.width),Tr($,this.height),ct=this.outputDestination};var Br=function(t,e,r,n,i){Wt.push(new Or),Dt=$=0,ot=[],Pt=t,kt=e,Ft=i,je([r,n])},Mr=function(t){if(Ht[t])Wt.pop().restore();else{var e=new Or,r="Xo"+(Object.keys(zt).length+1).toString(10);e.id=r,Ht[t]=r,zt[r]=e,Tt.publish("addFormObject",e),Wt.pop().restore()}};for(var Er in y.beginFormObject=function(t,e,r,n,i){return Br(t,e,r,n,i),this},y.endFormObject=function(t){return Mr(t),this},y.doFormObject=function(t,e){var r=zt[Ht[t]];return ht("q"),ht(e.toString()+" cm"),ht("/"+r.id+" Do"),ht("Q"),this},y.getFormObject=function(t){var e=zt[Ht[t]];return{x:e.x,y:e.y,width:e.width,height:e.height,matrix:e.matrix}},y.save=function(t,e){return t=t||"generated.pdf",(e=e||{}).returnPromise=e.returnPromise||!1,!1===e.returnPromise?(l(We(He()),t),"function"==typeof l.unload&&r.setTimeout&&setTimeout(l.unload,911),this):new Promise((function(e,n){try{var i=l(We(He()),t);"function"==typeof l.unload&&r.setTimeout&&setTimeout(l.unload,911),e(i)}catch(t){n(t.message)}}))},M.API)M.API.hasOwnProperty(Er)&&("events"===Er&&M.API.events.length?function(t,e){var r,n,i;for(i=e.length-1;-1!==i;i--)r=e[i][0],n=e[i][1],t.subscribe.apply(t,[r].concat("function"==typeof n?[n]:n))}(Tt,M.API.events):y[Er]=M.API[Er]);var qr=y.getPageWidth=function(t){return(Rt[t=t||$].mediaBox.topRightX-Rt[t].mediaBox.bottomLeftX)/_t},Dr=y.setPageWidth=function(t,e){Rt[t].mediaBox.topRightX=e*_t+Rt[t].mediaBox.bottomLeftX},Rr=y.getPageHeight=function(t){return(Rt[t=t||$].mediaBox.topRightY-Rt[t].mediaBox.bottomLeftY)/_t},Tr=y.setPageHeight=function(t,e){Rt[t].mediaBox.topRightY=e*_t+Rt[t].mediaBox.bottomLeftY};return y.internal={pdfEscape:Ce,getStyle:tr,getFont:lr,getFontSize:vt,getCharSpace:Pr,getTextColor:Sr,getLineHeight:gr,getLineHeightFactor:vr,getLineWidth:dr,write:ft,getHorizontalCoordinate:br,getVerticalCoordinate:yr,getCoordinateString:wr,getVerticalCoordinateString:Nr,collections:{},newObject:Xt,newAdditionalObject:$t,newObjectDeferred:Kt,newObjectDeferredBegin:Zt,getFilters:ne,putStream:ie,events:Tt,scaleFactor:_t,pageSize:{getWidth:function(){return qr($)},setWidth:function(t){Dr($,t)},getHeight:function(){return Rr($)},setHeight:function(t){Tr($,t)}},encryptionOptions:m,encryption:Ye,getEncryptor:Je,output:Ve,getNumberOfPages:Ee,pages:ot,out:ht,f2:R,f3:T,getPageInfo:Xe,getPageInfoByObjId:Ke,getCurrentPageInfo:Ze,getPDFVersion:N,Point:Cr,Rectangle:jr,Matrix:Vt,hasHotfix:Ge},Object.defineProperty(y.internal.pageSize,"width",{get:function(){return qr($)},set:function(t){Dr($,t)},enumerable:!0,configurable:!0}),Object.defineProperty(y.internal.pageSize,"height",{get:function(){return Rr($)},set:function(t){Tr($,t)},enumerable:!0,configurable:!0}),ke.call(y,pt),St="F1",Oe(s,a),Tt.publish("initialized"),y}k.prototype.lsbFirstWord=function(t){return String.fromCharCode(t>>0&255,t>>8&255,t>>16&255,t>>24&255)},k.prototype.toHexString=function(t){return t.split("").map((function(t){return("0"+(255&t.charCodeAt(0)).toString(16)).slice(-2)})).join("")},k.prototype.hexToBytes=function(t){for(var e=[],r=0;r<t.length;r+=2)e.push(String.fromCharCode(parseInt(t.substr(r,2),16)));return e.join("")},k.prototype.processOwnerPassword=function(t,e){return _(A(e).substr(0,5),t)},k.prototype.encryptor=function(t,e){var r=A(this.encryptionKey+String.fromCharCode(255&t,t>>8&255,t>>16&255,255&e,e>>8&255)).substr(0,10);return function(t){return _(r,t)}},C.prototype.equals=function(t){var r,n="id,objectNumber,equals";if(!t||e(t)!==e(this))return!1;var i=0;for(r in this)if(!(n.indexOf(r)>=0)){if(this.hasOwnProperty(r)&&!t.hasOwnProperty(r))return!1;if(this[r]!==t[r])return!1;i++}for(r in t)t.hasOwnProperty(r)&&n.indexOf(r)<0&&i--;return 0===i},M.API={events:[]},M.version="2.5.1";var E=M.API,q=1,D=function(t){return t.replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},R=function(t){return t.replace(/\\\\/g,"\\").replace(/\\\(/g,"(").replace(/\\\)/g,")")},T=function(t){return t.toFixed(2)},U=function(t){return t.toFixed(5)};E.__acroform__={};var z=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t},H=function(t){return t*q},W=function(t){var e=new ct,r=Lt.internal.getHeight(t)||0,n=Lt.internal.getWidth(t)||0;return e.BBox=[0,0,Number(T(n)),Number(T(r))],e},V=E.__acroform__.setBit=function(t,e){if(t=t||0,e=e||0,isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.setBit");return t|=1<<e},G=E.__acroform__.clearBit=function(t,e){if(t=t||0,e=e||0,isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.clearBit");return t&=~(1<<e)},Y=E.__acroform__.getBit=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.getBit");return 0==(t&1<<e)?0:1},J=E.__acroform__.getBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.getBitForPdf");return Y(t,e-1)},X=E.__acroform__.setBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.setBitForPdf");return V(t,e-1)},K=E.__acroform__.clearBitForPdf=function(t,e){if(isNaN(t)||isNaN(e))throw new Error("Invalid arguments passed to jsPDF.API.__acroform__.clearBitForPdf");return G(t,e-1)},Z=E.__acroform__.calculateCoordinates=function(t,e){var r=e.internal.getHorizontalCoordinate,n=e.internal.getVerticalCoordinate,i=t[0],a=t[1],o=t[2],s=t[3],c={};return c.lowerLeft_X=r(i)||0,c.lowerLeft_Y=n(a+s)||0,c.upperRight_X=r(i+o)||0,c.upperRight_Y=n(a)||0,[Number(T(c.lowerLeft_X)),Number(T(c.lowerLeft_Y)),Number(T(c.upperRight_X)),Number(T(c.upperRight_Y))]},$=function(t){if(t.appearanceStreamContent)return t.appearanceStreamContent;if(t.V||t.DV){var e=[],r=t._V||t.DV,n=Q(t,r),i=t.scope.internal.getFont(t.fontName,t.fontStyle).id;e.push("/Tx BMC"),e.push("q"),e.push("BT"),e.push(t.scope.__private__.encodeColorString(t.color)),e.push("/"+i+" "+T(n.fontSize)+" Tf"),e.push("1 0 0 1 0 0 Tm"),e.push(n.text),e.push("ET"),e.push("Q"),e.push("EMC");var a=W(t);return a.scope=t.scope,a.stream=e.join("\n"),a}},Q=function(t,e){var r=0===t.fontSize?t.maxFontSize:t.fontSize,n={text:"",fontSize:""},i=(e=")"==(e="("==e.substr(0,1)?e.substr(1):e).substr(e.length-1)?e.substr(0,e.length-1):e).split(" ");i=t.multiline?i.map((function(t){return t.split("\n")})):i.map((function(t){return[t]}));var a=r,o=Lt.internal.getHeight(t)||0;o=o<0?-o:o;var s=Lt.internal.getWidth(t)||0;s=s<0?-s:s;var c=function(e,r,n){if(e+1<i.length){var a=r+" "+i[e+1][0];return tt(a,t,n).width<=s-4}return!1};a++;t:for(;a>0;){e="",a--;var u,l,h=tt("3",t,a).height,f=t.multiline?o-a:(o-h)/2,d=f+=2,p=0,g=0,m=0;if(a<=0){e="(...) Tj\n",e+="% Width of Text: "+tt(e,t,a=12).width+", FieldWidth:"+s+"\n";break}for(var v="",b=0,y=0;y<i.length;y++)if(i.hasOwnProperty(y)){var w=!1;if(1!==i[y].length&&m!==i[y].length-1){if((h+2)*(b+2)+2>o)continue t;v+=i[y][m],w=!0,g=y,y--}else{v=" "==(v+=i[y][m]+" ").substr(v.length-1)?v.substr(0,v.length-1):v;var N=parseInt(y),L=c(N,v,a),A=y>=i.length-1;if(L&&!A){v+=" ",m=0;continue}if(L||A){if(A)g=N;else if(t.multiline&&(h+2)*(b+2)+2>o)continue t}else{if(!t.multiline)continue t;if((h+2)*(b+2)+2>o)continue t;g=N}}for(var x="",S=p;S<=g;S++){var _=i[S];if(t.multiline){if(S===g){x+=_[m]+" ",m=(m+1)%_.length;continue}if(S===p){x+=_[_.length-1]+" ";continue}}x+=_[0]+" "}switch(x=" "==x.substr(x.length-1)?x.substr(0,x.length-1):x,l=tt(x,t,a).width,t.textAlign){case"right":u=s-l-2;break;case"center":u=(s-l)/2;break;case"left":default:u=2}e+=T(u)+" "+T(d)+" Td\n",e+="("+D(x)+") Tj\n",e+=-T(u)+" 0 Td\n",d=-(a+2),l=0,p=w?g:g+1,b++,v=""}else;break}return n.text=e,n.fontSize=a,n},tt=function(t,e,r){var n=e.scope.internal.getFont(e.fontName,e.fontStyle),i=e.scope.getStringUnitWidth(t,{font:n,fontSize:parseFloat(r),charSpace:0})*parseFloat(r);return{height:e.scope.getStringUnitWidth("3",{font:n,fontSize:parseFloat(r),charSpace:0})*parseFloat(r)*1.5,width:i}},et={fields:[],xForms:[],acroFormDictionaryRoot:null,printedOut:!1,internal:null,isInitialized:!1},rt=function(t,e){var r={type:"reference",object:t};void 0===e.internal.getPageInfo(t.page).pageContext.annotations.find((function(t){return t.type===r.type&&t.object===r.object}))&&e.internal.getPageInfo(t.page).pageContext.annotations.push(r)},nt=function(t,r){for(var n in t)if(t.hasOwnProperty(n)){var i=n,a=t[n];r.internal.newObjectDeferredBegin(a.objId,!0),"object"===e(a)&&"function"==typeof a.putStream&&a.putStream(),delete t[i]}},it=function(t,r){if(r.scope=t,void 0!==t.internal&&(void 0===t.internal.acroformPlugin||!1===t.internal.acroformPlugin.isInitialized)){if(lt.FieldNum=0,t.internal.acroformPlugin=JSON.parse(JSON.stringify(et)),t.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("Exception while creating AcroformDictionary");q=t.internal.scaleFactor,t.internal.acroformPlugin.acroFormDictionaryRoot=new ut,t.internal.acroformPlugin.acroFormDictionaryRoot.scope=t,t.internal.acroformPlugin.acroFormDictionaryRoot._eventID=t.internal.events.subscribe("postPutResources",(function(){!function(t){t.internal.events.unsubscribe(t.internal.acroformPlugin.acroFormDictionaryRoot._eventID),delete t.internal.acroformPlugin.acroFormDictionaryRoot._eventID,t.internal.acroformPlugin.printedOut=!0}(t)})),t.internal.events.subscribe("buildDocument",(function(){!function(t){t.internal.acroformPlugin.acroFormDictionaryRoot.objId=void 0;var e=t.internal.acroformPlugin.acroFormDictionaryRoot.Fields;for(var r in e)if(e.hasOwnProperty(r)){var n=e[r];n.objId=void 0,n.hasAnnotation&&rt(n,t)}}(t)})),t.internal.events.subscribe("putCatalog",(function(){!function(t){if(void 0===t.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("putCatalogCallback: Root missing.");t.internal.write("/AcroForm "+t.internal.acroformPlugin.acroFormDictionaryRoot.objId+" 0 R")}(t)})),t.internal.events.subscribe("postPutPages",(function(r){!function(t,r){var n=!t;for(var i in t||(r.internal.newObjectDeferredBegin(r.internal.acroformPlugin.acroFormDictionaryRoot.objId,!0),r.internal.acroformPlugin.acroFormDictionaryRoot.putStream()),t=t||r.internal.acroformPlugin.acroFormDictionaryRoot.Kids)if(t.hasOwnProperty(i)){var a=t[i],o=[],s=a.Rect;if(a.Rect&&(a.Rect=Z(a.Rect,r)),r.internal.newObjectDeferredBegin(a.objId,!0),a.DA=Lt.createDefaultAppearanceStream(a),"object"===e(a)&&"function"==typeof a.getKeyValueListForStream&&(o=a.getKeyValueListForStream()),a.Rect=s,a.hasAppearanceStream&&!a.appearanceStreamContent){var c=$(a);o.push({key:"AP",value:"<</N "+c+">>"}),r.internal.acroformPlugin.xForms.push(c)}if(a.appearanceStreamContent){var u="";for(var l in a.appearanceStreamContent)if(a.appearanceStreamContent.hasOwnProperty(l)){var h=a.appearanceStreamContent[l];if(u+="/"+l+" ",u+="<<",Object.keys(h).length>=1||Array.isArray(h)){for(var i in h)if(h.hasOwnProperty(i)){var f=h[i];"function"==typeof f&&(f=f.call(r,a)),u+="/"+i+" "+f+" ",r.internal.acroformPlugin.xForms.indexOf(f)>=0||r.internal.acroformPlugin.xForms.push(f)}}else"function"==typeof(f=h)&&(f=f.call(r,a)),u+="/"+i+" "+f,r.internal.acroformPlugin.xForms.indexOf(f)>=0||r.internal.acroformPlugin.xForms.push(f);u+=">>"}o.push({key:"AP",value:"<<\n"+u+">>"})}r.internal.putStream({additionalKeyValues:o,objectId:a.objId}),r.internal.out("endobj")}n&&nt(r.internal.acroformPlugin.xForms,r)}(r,t)})),t.internal.acroformPlugin.isInitialized=!0}},at=E.__acroform__.arrayToPdfArray=function(t,r,n){var i=function(t){return t};if(Array.isArray(t)){for(var a="[",o=0;o<t.length;o++)switch(0!==o&&(a+=" "),e(t[o])){case"boolean":case"number":case"object":a+=t[o].toString();break;case"string":"/"!==t[o].substr(0,1)?(void 0!==r&&n&&(i=n.internal.getEncryptor(r)),a+="("+D(i(t[o].toString()))+")"):a+=t[o].toString()}return a+="]"}throw new Error("Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray")};var ot=function(t,e,r){var n=function(t){return t};return void 0!==e&&r&&(n=r.internal.getEncryptor(e)),(t=t||"").toString(),t="("+D(n(t))+")"},st=function(){this._objId=void 0,this._scope=void 0,Object.defineProperty(this,"objId",{get:function(){if(void 0===this._objId){if(void 0===this.scope)return;this._objId=this.scope.internal.newObjectDeferred()}return this._objId},set:function(t){this._objId=t}}),Object.defineProperty(this,"scope",{value:this._scope,writable:!0})};st.prototype.toString=function(){return this.objId+" 0 R"},st.prototype.putStream=function(){var t=this.getKeyValueListForStream();this.scope.internal.putStream({data:this.stream,additionalKeyValues:t,objectId:this.objId}),this.scope.internal.out("endobj")},st.prototype.getKeyValueListForStream=function(){var t=[],e=Object.getOwnPropertyNames(this).filter((function(t){return"content"!=t&&"appearanceStreamContent"!=t&&"scope"!=t&&"objId"!=t&&"_"!=t.substring(0,1)}));for(var r in e)if(!1===Object.getOwnPropertyDescriptor(this,e[r]).configurable){var n=e[r],i=this[n];i&&(Array.isArray(i)?t.push({key:n,value:at(i,this.objId,this.scope)}):i instanceof st?(i.scope=this.scope,t.push({key:n,value:i.objId+" 0 R"})):"function"!=typeof i&&t.push({key:n,value:i}))}return t};var ct=function(){st.call(this),Object.defineProperty(this,"Type",{value:"/XObject",configurable:!1,writable:!0}),Object.defineProperty(this,"Subtype",{value:"/Form",configurable:!1,writable:!0}),Object.defineProperty(this,"FormType",{value:1,configurable:!1,writable:!0});var t,e=[];Object.defineProperty(this,"BBox",{configurable:!1,get:function(){return e},set:function(t){e=t}}),Object.defineProperty(this,"Resources",{value:"2 0 R",configurable:!1,writable:!0}),Object.defineProperty(this,"stream",{enumerable:!1,configurable:!0,set:function(e){t=e.trim()},get:function(){return t||null}})};z(ct,st);var ut=function(){st.call(this);var t,e=[];Object.defineProperty(this,"Kids",{enumerable:!1,configurable:!0,get:function(){return e.length>0?e:void 0}}),Object.defineProperty(this,"Fields",{enumerable:!1,configurable:!1,get:function(){return e}}),Object.defineProperty(this,"DA",{enumerable:!1,configurable:!1,get:function(){if(t){var e=function(t){return t};return this.scope&&(e=this.scope.internal.getEncryptor(this.objId)),"("+D(e(t))+")"}},set:function(e){t=e}})};z(ut,st);var lt=function t(){st.call(this);var e=4;Object.defineProperty(this,"F",{enumerable:!1,configurable:!1,get:function(){return e},set:function(t){if(isNaN(t))throw new Error('Invalid value "'+t+'" for attribute F supplied.');e=t}}),Object.defineProperty(this,"showWhenPrinted",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(e,3))},set:function(t){!0===Boolean(t)?this.F=X(e,3):this.F=K(e,3)}});var r=0;Object.defineProperty(this,"Ff",{enumerable:!1,configurable:!1,get:function(){return r},set:function(t){if(isNaN(t))throw new Error('Invalid value "'+t+'" for attribute Ff supplied.');r=t}});var n=[];Object.defineProperty(this,"Rect",{enumerable:!1,configurable:!1,get:function(){if(0!==n.length)return n},set:function(t){n=void 0!==t?t:[]}}),Object.defineProperty(this,"x",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[0])?0:n[0]},set:function(t){n[0]=t}}),Object.defineProperty(this,"y",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[1])?0:n[1]},set:function(t){n[1]=t}}),Object.defineProperty(this,"width",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[2])?0:n[2]},set:function(t){n[2]=t}}),Object.defineProperty(this,"height",{enumerable:!0,configurable:!0,get:function(){return!n||isNaN(n[3])?0:n[3]},set:function(t){n[3]=t}});var i="";Object.defineProperty(this,"FT",{enumerable:!0,configurable:!1,get:function(){return i},set:function(t){switch(t){case"/Btn":case"/Tx":case"/Ch":case"/Sig":i=t;break;default:throw new Error('Invalid value "'+t+'" for attribute FT supplied.')}}});var a=null;Object.defineProperty(this,"T",{enumerable:!0,configurable:!1,get:function(){if(!a||a.length<1){if(this instanceof bt)return;a="FieldObject"+t.FieldNum++}var e=function(t){return t};return this.scope&&(e=this.scope.internal.getEncryptor(this.objId)),"("+D(e(a))+")"},set:function(t){a=t.toString()}}),Object.defineProperty(this,"fieldName",{configurable:!0,enumerable:!0,get:function(){return a},set:function(t){a=t}});var o="helvetica";Object.defineProperty(this,"fontName",{enumerable:!0,configurable:!0,get:function(){return o},set:function(t){o=t}});var s="normal";Object.defineProperty(this,"fontStyle",{enumerable:!0,configurable:!0,get:function(){return s},set:function(t){s=t}});var c=0;Object.defineProperty(this,"fontSize",{enumerable:!0,configurable:!0,get:function(){return c},set:function(t){c=t}});var u=void 0;Object.defineProperty(this,"maxFontSize",{enumerable:!0,configurable:!0,get:function(){return void 0===u?50/q:u},set:function(t){u=t}});var l="black";Object.defineProperty(this,"color",{enumerable:!0,configurable:!0,get:function(){return l},set:function(t){l=t}});var h="/F1 0 Tf 0 g";Object.defineProperty(this,"DA",{enumerable:!0,configurable:!1,get:function(){if(!(!h||this instanceof bt||this instanceof wt))return ot(h,this.objId,this.scope)},set:function(t){t=t.toString(),h=t}});var f=null;Object.defineProperty(this,"DV",{enumerable:!1,configurable:!1,get:function(){if(f)return this instanceof gt==!1?ot(f,this.objId,this.scope):f},set:function(t){t=t.toString(),f=this instanceof gt==!1?"("===t.substr(0,1)?R(t.substr(1,t.length-2)):R(t):t}}),Object.defineProperty(this,"defaultValue",{enumerable:!0,configurable:!0,get:function(){return this instanceof gt==!0?R(f.substr(1,f.length-1)):f},set:function(t){t=t.toString(),f=this instanceof gt==!0?"/"+t:t}});var d=null;Object.defineProperty(this,"_V",{enumerable:!1,configurable:!1,get:function(){if(d)return d},set:function(t){this.V=t}}),Object.defineProperty(this,"V",{enumerable:!1,configurable:!1,get:function(){if(d)return this instanceof gt==!1?ot(d,this.objId,this.scope):d},set:function(t){t=t.toString(),d=this instanceof gt==!1?"("===t.substr(0,1)?R(t.substr(1,t.length-2)):R(t):t}}),Object.defineProperty(this,"value",{enumerable:!0,configurable:!0,get:function(){return this instanceof gt==!0?R(d.substr(1,d.length-1)):d},set:function(t){t=t.toString(),d=this instanceof gt==!0?"/"+t:t}}),Object.defineProperty(this,"hasAnnotation",{enumerable:!0,configurable:!0,get:function(){return this.Rect}}),Object.defineProperty(this,"Type",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Annot":null}}),Object.defineProperty(this,"Subtype",{enumerable:!0,configurable:!1,get:function(){return this.hasAnnotation?"/Widget":null}});var p,g=!1;Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return g},set:function(t){t=Boolean(t),g=t}}),Object.defineProperty(this,"page",{enumerable:!0,configurable:!0,get:function(){if(p)return p},set:function(t){p=t}}),Object.defineProperty(this,"readOnly",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,1))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,1):this.Ff=K(this.Ff,1)}}),Object.defineProperty(this,"required",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,2))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,2):this.Ff=K(this.Ff,2)}}),Object.defineProperty(this,"noExport",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,3))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,3):this.Ff=K(this.Ff,3)}});var m=null;Object.defineProperty(this,"Q",{enumerable:!0,configurable:!1,get:function(){if(null!==m)return m},set:function(t){if(-1===[0,1,2].indexOf(t))throw new Error('Invalid value "'+t+'" for attribute Q supplied.');m=t}}),Object.defineProperty(this,"textAlign",{get:function(){var t;switch(m){case 0:default:t="left";break;case 1:t="center";break;case 2:t="right"}return t},configurable:!0,enumerable:!0,set:function(t){switch(t){case"right":case 2:m=2;break;case"center":case 1:m=1;break;case"left":case 0:default:m=0}}})};z(lt,st);var ht=function(){lt.call(this),this.FT="/Ch",this.V="()",this.fontName="zapfdingbats";var t=0;Object.defineProperty(this,"TI",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"topIndex",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){t=e}});var e=[];Object.defineProperty(this,"Opt",{enumerable:!0,configurable:!1,get:function(){return at(e,this.objId,this.scope)},set:function(t){var r,n;n=[],"string"==typeof(r=t)&&(n=function(t,e,r){r||(r=1);for(var n,i=[];n=e.exec(t);)i.push(n[r]);return i}(r,/\((.*?)\)/g)),e=n}}),this.getOptions=function(){return e},this.setOptions=function(t){e=t,this.sort&&e.sort()},this.addOption=function(t){t=(t=t||"").toString(),e.push(t),this.sort&&e.sort()},this.removeOption=function(t,r){for(r=r||!1,t=(t=t||"").toString();-1!==e.indexOf(t)&&(e.splice(e.indexOf(t),1),!1!==r););},Object.defineProperty(this,"combo",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,18))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,18):this.Ff=K(this.Ff,18)}}),Object.defineProperty(this,"edit",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,19))},set:function(t){!0===this.combo&&(!0===Boolean(t)?this.Ff=X(this.Ff,19):this.Ff=K(this.Ff,19))}}),Object.defineProperty(this,"sort",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,20))},set:function(t){!0===Boolean(t)?(this.Ff=X(this.Ff,20),e.sort()):this.Ff=K(this.Ff,20)}}),Object.defineProperty(this,"multiSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,22))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,22):this.Ff=K(this.Ff,22)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,23):this.Ff=K(this.Ff,23)}}),Object.defineProperty(this,"commitOnSelChange",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,27))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,27):this.Ff=K(this.Ff,27)}}),this.hasAppearanceStream=!1};z(ht,lt);var ft=function(){ht.call(this),this.fontName="helvetica",this.combo=!1};z(ft,ht);var dt=function(){ft.call(this),this.combo=!0};z(dt,ft);var pt=function(){dt.call(this),this.edit=!0};z(pt,dt);var gt=function(){lt.call(this),this.FT="/Btn",Object.defineProperty(this,"noToggleToOff",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,15))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,15):this.Ff=K(this.Ff,15)}}),Object.defineProperty(this,"radio",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,16))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,16):this.Ff=K(this.Ff,16)}}),Object.defineProperty(this,"pushButton",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,17))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,17):this.Ff=K(this.Ff,17)}}),Object.defineProperty(this,"radioIsUnison",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,26):this.Ff=K(this.Ff,26)}});var t,r={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};if(this.scope&&(t=this.scope.internal.getEncryptor(this.objId)),0!==Object.keys(r).length){var e,n=[];for(e in n.push("<<"),r)n.push("/"+e+" ("+D(t(r[e]))+")");return n.push(">>"),n.join("\n")}},set:function(t){"object"===e(t)&&(r=t)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return r.CA||""},set:function(t){"string"==typeof t&&(r.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return t.substr(1,t.length-1)},set:function(e){t="/"+e}})};z(gt,lt);var mt=function(){gt.call(this),this.pushButton=!0};z(mt,gt);var vt=function(){gt.call(this),this.radio=!0,this.pushButton=!1;var t=[];Object.defineProperty(this,"Kids",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=void 0!==e?e:[]}})};z(vt,gt);var bt=function(){var t,r;lt.call(this),Object.defineProperty(this,"Parent",{enumerable:!1,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"optionName",{enumerable:!1,configurable:!0,get:function(){return r},set:function(t){r=t}});var n,i={};Object.defineProperty(this,"MK",{enumerable:!1,configurable:!1,get:function(){var t=function(t){return t};this.scope&&(t=this.scope.internal.getEncryptor(this.objId));var e,r=[];for(e in r.push("<<"),i)r.push("/"+e+" ("+D(t(i[e]))+")");return r.push(">>"),r.join("\n")},set:function(t){"object"===e(t)&&(i=t)}}),Object.defineProperty(this,"caption",{enumerable:!0,configurable:!0,get:function(){return i.CA||""},set:function(t){"string"==typeof t&&(i.CA=t)}}),Object.defineProperty(this,"AS",{enumerable:!1,configurable:!1,get:function(){return n},set:function(t){n=t}}),Object.defineProperty(this,"appearanceState",{enumerable:!0,configurable:!0,get:function(){return n.substr(1,n.length-1)},set:function(t){n="/"+t}}),this.caption="l",this.appearanceState="Off",this._AppearanceType=Lt.RadioButton.Circle,this.appearanceStreamContent=this._AppearanceType.createAppearanceStream(this.optionName)};z(bt,lt),vt.prototype.setAppearance=function(t){if(!("createAppearanceStream"in t)||!("getCA"in t))throw new Error("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");for(var e in this.Kids)if(this.Kids.hasOwnProperty(e)){var r=this.Kids[e];r.appearanceStreamContent=t.createAppearanceStream(r.optionName),r.caption=t.getCA()}},vt.prototype.createOption=function(t){var e=new bt;return e.Parent=this,e.optionName=t,this.Kids.push(e),At.call(this.scope,e),e};var yt=function(){gt.call(this),this.fontName="zapfdingbats",this.caption="3",this.appearanceState="On",this.value="On",this.textAlign="center",this.appearanceStreamContent=Lt.CheckBox.createAppearanceStream()};z(yt,gt);var wt=function(){lt.call(this),this.FT="/Tx",Object.defineProperty(this,"multiline",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,13))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,13):this.Ff=K(this.Ff,13)}}),Object.defineProperty(this,"fileSelect",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,21))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,21):this.Ff=K(this.Ff,21)}}),Object.defineProperty(this,"doNotSpellCheck",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,23))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,23):this.Ff=K(this.Ff,23)}}),Object.defineProperty(this,"doNotScroll",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,24))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,24):this.Ff=K(this.Ff,24)}}),Object.defineProperty(this,"comb",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,25))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,25):this.Ff=K(this.Ff,25)}}),Object.defineProperty(this,"richText",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,26))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,26):this.Ff=K(this.Ff,26)}});var t=null;Object.defineProperty(this,"MaxLen",{enumerable:!0,configurable:!1,get:function(){return t},set:function(e){t=e}}),Object.defineProperty(this,"maxLength",{enumerable:!0,configurable:!0,get:function(){return t},set:function(e){Number.isInteger(e)&&(t=e)}}),Object.defineProperty(this,"hasAppearanceStream",{enumerable:!0,configurable:!0,get:function(){return this.V||this.DV}})};z(wt,lt);var Nt=function(){wt.call(this),Object.defineProperty(this,"password",{enumerable:!0,configurable:!0,get:function(){return Boolean(J(this.Ff,14))},set:function(t){!0===Boolean(t)?this.Ff=X(this.Ff,14):this.Ff=K(this.Ff,14)}}),this.password=!0};z(Nt,wt);var Lt={CheckBox:{createAppearanceStream:function(){return{N:{On:Lt.CheckBox.YesNormal},D:{On:Lt.CheckBox.YesPushDown,Off:Lt.CheckBox.OffPushDown}}},YesPushDown:function(t){var e=W(t);e.scope=t.scope;var r=[],n=t.scope.internal.getFont(t.fontName,t.fontStyle).id,i=t.scope.__private__.encodeColorString(t.color),a=Q(t,t.caption);return r.push("0.749023 g"),r.push("0 0 "+T(Lt.internal.getWidth(t))+" "+T(Lt.internal.getHeight(t))+" re"),r.push("f"),r.push("BMC"),r.push("q"),r.push("0 0 1 rg"),r.push("/"+n+" "+T(a.fontSize)+" Tf "+i),r.push("BT"),r.push(a.text),r.push("ET"),r.push("Q"),r.push("EMC"),e.stream=r.join("\n"),e},YesNormal:function(t){var e=W(t);e.scope=t.scope;var r=t.scope.internal.getFont(t.fontName,t.fontStyle).id,n=t.scope.__private__.encodeColorString(t.color),i=[],a=Lt.internal.getHeight(t),o=Lt.internal.getWidth(t),s=Q(t,t.caption);return i.push("1 g"),i.push("0 0 "+T(o)+" "+T(a)+" re"),i.push("f"),i.push("q"),i.push("0 0 1 rg"),i.push("0 0 "+T(o-1)+" "+T(a-1)+" re"),i.push("W"),i.push("n"),i.push("0 g"),i.push("BT"),i.push("/"+r+" "+T(s.fontSize)+" Tf "+n),i.push(s.text),i.push("ET"),i.push("Q"),e.stream=i.join("\n"),e},OffPushDown:function(t){var e=W(t);e.scope=t.scope;var r=[];return r.push("0.749023 g"),r.push("0 0 "+T(Lt.internal.getWidth(t))+" "+T(Lt.internal.getHeight(t))+" re"),r.push("f"),e.stream=r.join("\n"),e}},RadioButton:{Circle:{createAppearanceStream:function(t){var e={D:{Off:Lt.RadioButton.Circle.OffPushDown},N:{}};return e.N[t]=Lt.RadioButton.Circle.YesNormal,e.D[t]=Lt.RadioButton.Circle.YesPushDown,e},getCA:function(){return"l"},YesNormal:function(t){var e=W(t);e.scope=t.scope;var r=[],n=Lt.internal.getWidth(t)<=Lt.internal.getHeight(t)?Lt.internal.getWidth(t)/4:Lt.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=Lt.internal.Bezier_C,a=Number((n*i).toFixed(5));return r.push("q"),r.push("1 0 0 1 "+U(Lt.internal.getWidth(t)/2)+" "+U(Lt.internal.getHeight(t)/2)+" cm"),r.push(n+" 0 m"),r.push(n+" "+a+" "+a+" "+n+" 0 "+n+" c"),r.push("-"+a+" "+n+" -"+n+" "+a+" -"+n+" 0 c"),r.push("-"+n+" -"+a+" -"+a+" -"+n+" 0 -"+n+" c"),r.push(a+" -"+n+" "+n+" -"+a+" "+n+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e},YesPushDown:function(t){var e=W(t);e.scope=t.scope;var r=[],n=Lt.internal.getWidth(t)<=Lt.internal.getHeight(t)?Lt.internal.getWidth(t)/4:Lt.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=Number((2*n).toFixed(5)),a=Number((i*Lt.internal.Bezier_C).toFixed(5)),o=Number((n*Lt.internal.Bezier_C).toFixed(5));return r.push("0.749023 g"),r.push("q"),r.push("1 0 0 1 "+U(Lt.internal.getWidth(t)/2)+" "+U(Lt.internal.getHeight(t)/2)+" cm"),r.push(i+" 0 m"),r.push(i+" "+a+" "+a+" "+i+" 0 "+i+" c"),r.push("-"+a+" "+i+" -"+i+" "+a+" -"+i+" 0 c"),r.push("-"+i+" -"+a+" -"+a+" -"+i+" 0 -"+i+" c"),r.push(a+" -"+i+" "+i+" -"+a+" "+i+" 0 c"),r.push("f"),r.push("Q"),r.push("0 g"),r.push("q"),r.push("1 0 0 1 "+U(Lt.internal.getWidth(t)/2)+" "+U(Lt.internal.getHeight(t)/2)+" cm"),r.push(n+" 0 m"),r.push(n+" "+o+" "+o+" "+n+" 0 "+n+" c"),r.push("-"+o+" "+n+" -"+n+" "+o+" -"+n+" 0 c"),r.push("-"+n+" -"+o+" -"+o+" -"+n+" 0 -"+n+" c"),r.push(o+" -"+n+" "+n+" -"+o+" "+n+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e},OffPushDown:function(t){var e=W(t);e.scope=t.scope;var r=[],n=Lt.internal.getWidth(t)<=Lt.internal.getHeight(t)?Lt.internal.getWidth(t)/4:Lt.internal.getHeight(t)/4;n=Number((.9*n).toFixed(5));var i=Number((2*n).toFixed(5)),a=Number((i*Lt.internal.Bezier_C).toFixed(5));return r.push("0.749023 g"),r.push("q"),r.push("1 0 0 1 "+U(Lt.internal.getWidth(t)/2)+" "+U(Lt.internal.getHeight(t)/2)+" cm"),r.push(i+" 0 m"),r.push(i+" "+a+" "+a+" "+i+" 0 "+i+" c"),r.push("-"+a+" "+i+" -"+i+" "+a+" -"+i+" 0 c"),r.push("-"+i+" -"+a+" -"+a+" -"+i+" 0 -"+i+" c"),r.push(a+" -"+i+" "+i+" -"+a+" "+i+" 0 c"),r.push("f"),r.push("Q"),e.stream=r.join("\n"),e}},Cross:{createAppearanceStream:function(t){var e={D:{Off:Lt.RadioButton.Cross.OffPushDown},N:{}};return e.N[t]=Lt.RadioButton.Cross.YesNormal,e.D[t]=Lt.RadioButton.Cross.YesPushDown,e},getCA:function(){return"8"},YesNormal:function(t){var e=W(t);e.scope=t.scope;var r=[],n=Lt.internal.calculateCross(t);return r.push("q"),r.push("1 1 "+T(Lt.internal.getWidth(t)-2)+" "+T(Lt.internal.getHeight(t)-2)+" re"),r.push("W"),r.push("n"),r.push(T(n.x1.x)+" "+T(n.x1.y)+" m"),r.push(T(n.x2.x)+" "+T(n.x2.y)+" l"),r.push(T(n.x4.x)+" "+T(n.x4.y)+" m"),r.push(T(n.x3.x)+" "+T(n.x3.y)+" l"),r.push("s"),r.push("Q"),e.stream=r.join("\n"),e},YesPushDown:function(t){var e=W(t);e.scope=t.scope;var r=Lt.internal.calculateCross(t),n=[];return n.push("0.749023 g"),n.push("0 0 "+T(Lt.internal.getWidth(t))+" "+T(Lt.internal.getHeight(t))+" re"),n.push("f"),n.push("q"),n.push("1 1 "+T(Lt.internal.getWidth(t)-2)+" "+T(Lt.internal.getHeight(t)-2)+" re"),n.push("W"),n.push("n"),n.push(T(r.x1.x)+" "+T(r.x1.y)+" m"),n.push(T(r.x2.x)+" "+T(r.x2.y)+" l"),n.push(T(r.x4.x)+" "+T(r.x4.y)+" m"),n.push(T(r.x3.x)+" "+T(r.x3.y)+" l"),n.push("s"),n.push("Q"),e.stream=n.join("\n"),e},OffPushDown:function(t){var e=W(t);e.scope=t.scope;var r=[];return r.push("0.749023 g"),r.push("0 0 "+T(Lt.internal.getWidth(t))+" "+T(Lt.internal.getHeight(t))+" re"),r.push("f"),e.stream=r.join("\n"),e}}},createDefaultAppearanceStream:function(t){var e=t.scope.internal.getFont(t.fontName,t.fontStyle).id,r=t.scope.__private__.encodeColorString(t.color);return"/"+e+" "+t.fontSize+" Tf "+r}};Lt.internal={Bezier_C:.551915024494,calculateCross:function(t){var e=Lt.internal.getWidth(t),r=Lt.internal.getHeight(t),n=Math.min(e,r);return{x1:{x:(e-n)/2,y:(r-n)/2+n},x2:{x:(e-n)/2+n,y:(r-n)/2},x3:{x:(e-n)/2,y:(r-n)/2},x4:{x:(e-n)/2+n,y:(r-n)/2+n}}}},Lt.internal.getWidth=function(t){var r=0;return"object"===e(t)&&(r=H(t.Rect[2])),r},Lt.internal.getHeight=function(t){var r=0;return"object"===e(t)&&(r=H(t.Rect[3])),r};var At=E.addField=function(t){if(it(this,t),!(t instanceof lt))throw new Error("Invalid argument passed to jsPDF.addField.");var e;return(e=t).scope.internal.acroformPlugin.printedOut&&(e.scope.internal.acroformPlugin.printedOut=!1,e.scope.internal.acroformPlugin.acroFormDictionaryRoot=null),e.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(e),t.page=t.scope.internal.getCurrentPageInfo().pageNumber,this};E.AcroFormChoiceField=ht,E.AcroFormListBox=ft,E.AcroFormComboBox=dt,E.AcroFormEditBox=pt,E.AcroFormButton=gt,E.AcroFormPushButton=mt,E.AcroFormRadioButton=vt,E.AcroFormCheckBox=yt,E.AcroFormTextField=wt,E.AcroFormPasswordField=Nt,E.AcroFormAppearance=Lt,E.AcroForm={ChoiceField:ht,ListBox:ft,ComboBox:dt,EditBox:pt,Button:gt,PushButton:mt,RadioButton:vt,CheckBox:yt,TextField:wt,PasswordField:Nt,Appearance:Lt},M.AcroForm={ChoiceField:ht,ListBox:ft,ComboBox:dt,EditBox:pt,Button:gt,PushButton:mt,RadioButton:vt,CheckBox:yt,TextField:wt,PasswordField:Nt,Appearance:Lt};var xt=M.AcroForm;function St(t){return t.reduce((function(t,e,r){return t[e]=r,t}),{})}!function(t){t.__addimage__={};var r="UNKNOWN",n={PNG:[[137,80,78,71]],TIFF:[[77,77,0,42],[73,73,42,0]],JPEG:[[255,216,255,224,void 0,void 0,74,70,73,70,0],[255,216,255,225,void 0,void 0,69,120,105,102,0,0],[255,216,255,219],[255,216,255,238]],JPEG2000:[[0,0,0,12,106,80,32,32]],GIF87a:[[71,73,70,56,55,97]],GIF89a:[[71,73,70,56,57,97]],WEBP:[[82,73,70,70,void 0,void 0,void 0,void 0,87,69,66,80]],BMP:[[66,77],[66,65],[67,73],[67,80],[73,67],[80,84]]},i=t.__addimage__.getImageFileTypeByImageData=function(t,e){var i,a,o,s,c,u=r;if("RGBA"===(e=e||r)||void 0!==t.data&&t.data instanceof Uint8ClampedArray&&"height"in t&&"width"in t)return"RGBA";if(x(t))for(c in n)for(o=n[c],i=0;i<o.length;i+=1){for(s=!0,a=0;a<o[i].length;a+=1)if(void 0!==o[i][a]&&o[i][a]!==t[a]){s=!1;break}if(!0===s){u=c;break}}else for(c in n)for(o=n[c],i=0;i<o.length;i+=1){for(s=!0,a=0;a<o[i].length;a+=1)if(void 0!==o[i][a]&&o[i][a]!==t.charCodeAt(a)){s=!1;break}if(!0===s){u=c;break}}return u===r&&e!==r&&(u=e),u},a=function t(e){for(var r=this.internal.write,n=this.internal.putStream,i=(0,this.internal.getFilters)();-1!==i.indexOf("FlateEncode");)i.splice(i.indexOf("FlateEncode"),1);e.objectId=this.internal.newObject();var a=[];if(a.push({key:"Type",value:"/XObject"}),a.push({key:"Subtype",value:"/Image"}),a.push({key:"Width",value:e.width}),a.push({key:"Height",value:e.height}),e.colorSpace===b.INDEXED?a.push({key:"ColorSpace",value:"[/Indexed /DeviceRGB "+(e.palette.length/3-1)+" "+("sMask"in e&&void 0!==e.sMask?e.objectId+2:e.objectId+1)+" 0 R]"}):(a.push({key:"ColorSpace",value:"/"+e.colorSpace}),e.colorSpace===b.DEVICE_CMYK&&a.push({key:"Decode",value:"[1 0 1 0 1 0 1 0]"})),a.push({key:"BitsPerComponent",value:e.bitsPerComponent}),"decodeParameters"in e&&void 0!==e.decodeParameters&&a.push({key:"DecodeParms",value:"<<"+e.decodeParameters+">>"}),"transparency"in e&&Array.isArray(e.transparency)){for(var o="",s=0,c=e.transparency.length;s<c;s++)o+=e.transparency[s]+" "+e.transparency[s]+" ";a.push({key:"Mask",value:"["+o+"]"})}void 0!==e.sMask&&a.push({key:"SMask",value:e.objectId+1+" 0 R"});var u=void 0!==e.filter?["/"+e.filter]:void 0;if(n({data:e.data,additionalKeyValues:a,alreadyAppliedFilters:u,objectId:e.objectId}),r("endobj"),"sMask"in e&&void 0!==e.sMask){var l="/Predictor "+e.predictor+" /Colors 1 /BitsPerComponent "+e.bitsPerComponent+" /Columns "+e.width,h={width:e.width,height:e.height,colorSpace:"DeviceGray",bitsPerComponent:e.bitsPerComponent,decodeParameters:l,data:e.sMask};"filter"in e&&(h.filter=e.filter),t.call(this,h)}if(e.colorSpace===b.INDEXED){var f=this.internal.newObject();n({data:_(new Uint8Array(e.palette)),objectId:f}),r("endobj")}},o=function(){var t=this.internal.collections.addImage_images;for(var e in t)a.call(this,t[e])},s=function(){var t,e=this.internal.collections.addImage_images,r=this.internal.write;for(var n in e)r("/I"+(t=e[n]).index,t.objectId,"0","R")},u=function(){this.internal.collections.addImage_images||(this.internal.collections.addImage_images={},this.internal.events.subscribe("putResources",o),this.internal.events.subscribe("putXobjectDict",s))},l=function(){var t=this.internal.collections.addImage_images;return u.call(this),t},h=function(){return Object.keys(this.internal.collections.addImage_images).length},f=function(e){return"function"==typeof t["process"+e.toUpperCase()]},d=function(t){return"object"===e(t)&&1===t.nodeType},p=function(e,r){if("IMG"===e.nodeName&&e.hasAttribute("src")){var n=""+e.getAttribute("src");if(0===n.indexOf("data:image/"))return c(unescape(n).split("base64,").pop());var i=t.loadFile(n,!0);if(void 0!==i)return i}if("CANVAS"===e.nodeName){if(0===e.width||0===e.height)throw new Error("Given canvas must have data. Canvas width: "+e.width+", height: "+e.height);var a;switch(r){case"PNG":a="image/png";break;case"WEBP":a="image/webp";break;case"JPEG":case"JPG":default:a="image/jpeg"}return c(e.toDataURL(a,1).split("base64,").pop())}},g=function(t){var e=this.internal.collections.addImage_images;if(e)for(var r in e)if(t===e[r].alias)return e[r]},m=function(t,e,r){return t||e||(t=-96,e=-96),t<0&&(t=-1*r.width*72/t/this.internal.scaleFactor),e<0&&(e=-1*r.height*72/e/this.internal.scaleFactor),0===t&&(t=e*r.width/r.height),0===e&&(e=t*r.height/r.width),[t,e]},v=function(t,e,r,n,i,a){var o=m.call(this,r,n,i),s=this.internal.getCoordinateString,c=this.internal.getVerticalCoordinateString,u=l.call(this);if(r=o[0],n=o[1],u[i.index]=i,a){a*=Math.PI/180;var h=Math.cos(a),f=Math.sin(a),d=function(t){return t.toFixed(4)},p=[d(h),d(f),d(-1*f),d(h),0,0,"cm"]}this.internal.write("q"),a?(this.internal.write([1,"0","0",1,s(t),c(e+n),"cm"].join(" ")),this.internal.write(p.join(" ")),this.internal.write([s(r),"0","0",s(n),"0","0","cm"].join(" "))):this.internal.write([s(r),"0","0",s(n),s(t),c(e+n),"cm"].join(" ")),this.isAdvancedAPI()&&this.internal.write([1,0,0,-1,0,0,"cm"].join(" ")),this.internal.write("/I"+i.index+" Do"),this.internal.write("Q")},b=t.color_spaces={DEVICE_RGB:"DeviceRGB",DEVICE_GRAY:"DeviceGray",DEVICE_CMYK:"DeviceCMYK",CAL_GREY:"CalGray",CAL_RGB:"CalRGB",LAB:"Lab",ICC_BASED:"ICCBased",INDEXED:"Indexed",PATTERN:"Pattern",SEPARATION:"Separation",DEVICE_N:"DeviceN"};t.decode={DCT_DECODE:"DCTDecode",FLATE_DECODE:"FlateDecode",LZW_DECODE:"LZWDecode",JPX_DECODE:"JPXDecode",JBIG2_DECODE:"JBIG2Decode",ASCII85_DECODE:"ASCII85Decode",ASCII_HEX_DECODE:"ASCIIHexDecode",RUN_LENGTH_DECODE:"RunLengthDecode",CCITT_FAX_DECODE:"CCITTFaxDecode"};var y=t.image_compression={NONE:"NONE",FAST:"FAST",MEDIUM:"MEDIUM",SLOW:"SLOW"},w=t.__addimage__.sHashCode=function(t){var e,r,n=0;if("string"==typeof t)for(r=t.length,e=0;e<r;e++)n=(n<<5)-n+t.charCodeAt(e),n|=0;else if(x(t))for(r=t.byteLength/2,e=0;e<r;e++)n=(n<<5)-n+t[e],n|=0;return n},N=t.__addimage__.validateStringAsBase64=function(t){(t=t||"").toString().trim();var e=!0;return 0===t.length&&(e=!1),t.length%4!=0&&(e=!1),!1===/^[A-Za-z0-9+/]+$/.test(t.substr(0,t.length-2))&&(e=!1),!1===/^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test(t.substr(-2))&&(e=!1),e},L=t.__addimage__.extractImageFromDataUrl=function(t){var e=(t=t||"").split("base64,"),r=null;if(2===e.length){var n=/^data:(\w*\/\w*);*(charset=(?!charset=)[\w=-]*)*;*$/.exec(e[0]);Array.isArray(n)&&(r={mimeType:n[1],charset:n[2],data:e[1]})}return r},A=t.__addimage__.supportsArrayBuffer=function(){return"undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array};t.__addimage__.isArrayBuffer=function(t){return A()&&t instanceof ArrayBuffer};var x=t.__addimage__.isArrayBufferView=function(t){return A()&&"undefined"!=typeof Uint32Array&&(t instanceof Int8Array||t instanceof Uint8Array||"undefined"!=typeof Uint8ClampedArray&&t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array)},S=t.__addimage__.binaryStringToUint8Array=function(t){for(var e=t.length,r=new Uint8Array(e),n=0;n<e;n++)r[n]=t.charCodeAt(n);return r},_=t.__addimage__.arrayBufferToBinaryString=function(t){for(var e="",r=x(t)?t:new Uint8Array(t),n=0;n<r.length;n+=8192)e+=String.fromCharCode.apply(null,r.subarray(n,n+8192));return e};t.addImage=function(){var t,n,i,a,o,s,c,l,h;if("number"==typeof arguments[1]?(n=r,i=arguments[1],a=arguments[2],o=arguments[3],s=arguments[4],c=arguments[5],l=arguments[6],h=arguments[7]):(n=arguments[1],i=arguments[2],a=arguments[3],o=arguments[4],s=arguments[5],c=arguments[6],l=arguments[7],h=arguments[8]),"object"===e(t=arguments[0])&&!d(t)&&"imageData"in t){var f=t;t=f.imageData,n=f.format||n||r,i=f.x||i||0,a=f.y||a||0,o=f.w||f.width||o,s=f.h||f.height||s,c=f.alias||c,l=f.compression||l,h=f.rotation||f.angle||h}var p=this.internal.getFilters();if(void 0===l&&-1!==p.indexOf("FlateEncode")&&(l="SLOW"),isNaN(i)||isNaN(a))throw new Error("Invalid coordinates passed to jsPDF.addImage");u.call(this);var g=P.call(this,t,n,c,l);return v.call(this,i,a,o,s,g,h),this};var P=function(e,n,a,o){var s,c,u;if("string"==typeof e&&i(e)===r){e=unescape(e);var l=k(e,!1);(""!==l||void 0!==(l=t.loadFile(e,!0)))&&(e=l)}if(d(e)&&(e=p(e,n)),n=i(e,n),!f(n))throw new Error("addImage does not support files of type '"+n+"', please ensure that a plugin for '"+n+"' support is added.");if((null==(u=a)||0===u.length)&&(a=function(t){return"string"==typeof t||x(t)?w(t):x(t.data)?w(t.data):null}(e)),(s=g.call(this,a))||(A()&&(e instanceof Uint8Array||"RGBA"===n||(c=e,e=S(e))),s=this["process"+n.toUpperCase()](e,h.call(this),a,function(e){return e&&"string"==typeof e&&(e=e.toUpperCase()),e in t.image_compression?e:y.NONE}(o),c)),!s)throw new Error("An unknown error occurred whilst processing the image.");return s},k=t.__addimage__.convertBase64ToBinaryString=function(t,e){var r;e="boolean"!=typeof e||e;var n,i="";if("string"==typeof t){n=null!==(r=L(t))?r.data:t;try{i=c(n)}catch(t){if(e)throw N(n)?new Error("atob-Error in jsPDF.convertBase64ToBinaryString "+t.message):new Error("Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString ")}}return i};t.getImageProperties=function(e){var n,a,o="";if(d(e)&&(e=p(e)),"string"==typeof e&&i(e)===r&&(""===(o=k(e,!1))&&(o=t.loadFile(e)||""),e=o),a=i(e),!f(a))throw new Error("addImage does not support files of type '"+a+"', please ensure that a plugin for '"+a+"' support is added.");if(!A()||e instanceof Uint8Array||(e=S(e)),!(n=this["process"+a.toUpperCase()](e)))throw new Error("An unknown error occurred whilst processing the image");return n.fileType=a,n}}(M.API), -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(t){if(void 0!==t&&""!=t)return!0};M.API.events.push(["addPage",function(t){this.internal.getPageInfo(t.pageNumber).pageContext.annotations=[]}]),t.events.push(["putPage",function(t){for(var r,n,i,a=this.internal.getCoordinateString,o=this.internal.getVerticalCoordinateString,s=this.internal.getPageInfoByObjId(t.objId),c=t.pageContext.annotations,u=!1,l=0;l<c.length&&!u;l++)switch((r=c[l]).type){case"link":(e(r.options.url)||e(r.options.pageNumber))&&(u=!0);break;case"reference":case"text":case"freetext":u=!0}if(0!=u){this.internal.write("/Annots [");for(var h=0;h<c.length;h++){r=c[h];var f=this.internal.pdfEscape,d=this.internal.getEncryptor(t.objId);switch(r.type){case"reference":this.internal.write(" "+r.object.objId+" 0 R ");break;case"text":var p=this.internal.newAdditionalObject(),g=this.internal.newAdditionalObject(),m=this.internal.getEncryptor(p.objId),v=r.title||"Note";i="<</Type /Annot /Subtype /Text "+(n="/Rect ["+a(r.bounds.x)+" "+o(r.bounds.y+r.bounds.h)+" "+a(r.bounds.x+r.bounds.w)+" "+o(r.bounds.y)+"] ")+"/Contents ("+f(m(r.contents))+")",i+=" /Popup "+g.objId+" 0 R",i+=" /P "+s.objId+" 0 R",i+=" /T ("+f(m(v))+") >>",p.content=i;var b=p.objId+" 0 R";i="<</Type /Annot /Subtype /Popup "+(n="/Rect ["+a(r.bounds.x+30)+" "+o(r.bounds.y+r.bounds.h)+" "+a(r.bounds.x+r.bounds.w+30)+" "+o(r.bounds.y)+"] ")+" /Parent "+b,r.open&&(i+=" /Open true"),i+=" >>",g.content=i,this.internal.write(p.objId,"0 R",g.objId,"0 R");break;case"freetext":n="/Rect ["+a(r.bounds.x)+" "+o(r.bounds.y)+" "+a(r.bounds.x+r.bounds.w)+" "+o(r.bounds.y+r.bounds.h)+"] ";var y=r.color||"#000000";i="<</Type /Annot /Subtype /FreeText "+n+"/Contents ("+f(d(r.contents))+")",i+=" /DS(font: Helvetica,sans-serif 12.0pt; text-align:left; color:#"+y+")",i+=" /Border [0 0 0]",i+=" >>",this.internal.write(i);break;case"link":if(r.options.name){var w=this.annotations._nameMap[r.options.name];r.options.pageNumber=w.page,r.options.top=w.y}else r.options.top||(r.options.top=0);if(n="/Rect ["+r.finalBounds.x+" "+r.finalBounds.y+" "+r.finalBounds.w+" "+r.finalBounds.h+"] ",i="",r.options.url)i="<</Type /Annot /Subtype /Link "+n+"/Border [0 0 0] /A <</S /URI /URI ("+f(d(r.options.url))+") >>";else if(r.options.pageNumber){switch(i="<</Type /Annot /Subtype /Link "+n+"/Border [0 0 0] /Dest ["+this.internal.getPageInfo(r.options.pageNumber).objId+" 0 R",r.options.magFactor=r.options.magFactor||"XYZ",r.options.magFactor){case"Fit":i+=" /Fit]";break;case"FitH":i+=" /FitH "+r.options.top+"]";break;case"FitV":r.options.left=r.options.left||0,i+=" /FitV "+r.options.left+"]";break;case"XYZ":default:var N=o(r.options.top);r.options.left=r.options.left||0,void 0===r.options.zoom&&(r.options.zoom=0),i+=" /XYZ "+r.options.left+" "+N+" "+r.options.zoom+"]"}}""!=i&&(i+=" >>",this.internal.write(i))}}this.internal.write("]")}}]),t.createAnnotation=function(t){var e=this.internal.getCurrentPageInfo();switch(t.type){case"link":this.link(t.bounds.x,t.bounds.y,t.bounds.w,t.bounds.h,t);break;case"text":case"freetext":e.pageContext.annotations.push(t)}},t.link=function(t,e,r,n,i){var a=this.internal.getCurrentPageInfo(),o=this.internal.getCoordinateString,s=this.internal.getVerticalCoordinateString;a.pageContext.annotations.push({finalBounds:{x:o(t),y:s(e),w:o(t+r),h:s(e+n)},options:i,type:"link"})},t.textWithLink=function(t,e,r,n){var i,a,o=this.getTextWidth(t),s=this.internal.getLineHeight()/this.internal.scaleFactor;if(void 0!==n.maxWidth){a=n.maxWidth;var c=this.splitTextToSize(t,a).length;i=Math.ceil(s*c)}else a=o,i=s;return this.text(t,e,r,n),r+=.2*s,"center"===n.align&&(e-=o/2),"right"===n.align&&(e-=o),this.link(e,r-s,a,i,n),o},t.getTextWidth=function(t){var e=this.internal.getFontSize();return this.getStringUnitWidth(t)*e/this.internal.scaleFactor}}(M.API), -/** - * @license - * Copyright (c) 2017 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e={1569:[65152],1570:[65153,65154],1571:[65155,65156],1572:[65157,65158],1573:[65159,65160],1574:[65161,65162,65163,65164],1575:[65165,65166],1576:[65167,65168,65169,65170],1577:[65171,65172],1578:[65173,65174,65175,65176],1579:[65177,65178,65179,65180],1580:[65181,65182,65183,65184],1581:[65185,65186,65187,65188],1582:[65189,65190,65191,65192],1583:[65193,65194],1584:[65195,65196],1585:[65197,65198],1586:[65199,65200],1587:[65201,65202,65203,65204],1588:[65205,65206,65207,65208],1589:[65209,65210,65211,65212],1590:[65213,65214,65215,65216],1591:[65217,65218,65219,65220],1592:[65221,65222,65223,65224],1593:[65225,65226,65227,65228],1594:[65229,65230,65231,65232],1601:[65233,65234,65235,65236],1602:[65237,65238,65239,65240],1603:[65241,65242,65243,65244],1604:[65245,65246,65247,65248],1605:[65249,65250,65251,65252],1606:[65253,65254,65255,65256],1607:[65257,65258,65259,65260],1608:[65261,65262],1609:[65263,65264,64488,64489],1610:[65265,65266,65267,65268],1649:[64336,64337],1655:[64477],1657:[64358,64359,64360,64361],1658:[64350,64351,64352,64353],1659:[64338,64339,64340,64341],1662:[64342,64343,64344,64345],1663:[64354,64355,64356,64357],1664:[64346,64347,64348,64349],1667:[64374,64375,64376,64377],1668:[64370,64371,64372,64373],1670:[64378,64379,64380,64381],1671:[64382,64383,64384,64385],1672:[64392,64393],1676:[64388,64389],1677:[64386,64387],1678:[64390,64391],1681:[64396,64397],1688:[64394,64395],1700:[64362,64363,64364,64365],1702:[64366,64367,64368,64369],1705:[64398,64399,64400,64401],1709:[64467,64468,64469,64470],1711:[64402,64403,64404,64405],1713:[64410,64411,64412,64413],1715:[64406,64407,64408,64409],1722:[64414,64415],1723:[64416,64417,64418,64419],1726:[64426,64427,64428,64429],1728:[64420,64421],1729:[64422,64423,64424,64425],1733:[64480,64481],1734:[64473,64474],1735:[64471,64472],1736:[64475,64476],1737:[64482,64483],1739:[64478,64479],1740:[64508,64509,64510,64511],1744:[64484,64485,64486,64487],1746:[64430,64431],1747:[64432,64433]},r={65247:{65154:65269,65156:65271,65160:65273,65166:65275},65248:{65154:65270,65156:65272,65160:65274,65166:65276},65165:{65247:{65248:{65258:65010}}},1617:{1612:64606,1613:64607,1614:64608,1615:64609,1616:64610}},n={1612:64606,1613:64607,1614:64608,1615:64609,1616:64610},i=[1570,1571,1573,1575];t.__arabicParser__={};var a=t.__arabicParser__.isInArabicSubstitutionA=function(t){return void 0!==e[t.charCodeAt(0)]},o=t.__arabicParser__.isArabicLetter=function(t){return"string"==typeof t&&/^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test(t)},s=t.__arabicParser__.isArabicEndLetter=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length<=2},c=t.__arabicParser__.isArabicAlfLetter=function(t){return o(t)&&i.indexOf(t.charCodeAt(0))>=0};t.__arabicParser__.arabicLetterHasIsolatedForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=1};var u=t.__arabicParser__.arabicLetterHasFinalForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=2};t.__arabicParser__.arabicLetterHasInitialForm=function(t){return o(t)&&a(t)&&e[t.charCodeAt(0)].length>=3};var l=t.__arabicParser__.arabicLetterHasMedialForm=function(t){return o(t)&&a(t)&&4==e[t.charCodeAt(0)].length},h=t.__arabicParser__.resolveLigatures=function(t){var e=0,n=r,i="",a=0;for(e=0;e<t.length;e+=1)void 0!==n[t.charCodeAt(e)]?(a++,"number"==typeof(n=n[t.charCodeAt(e)])&&(i+=String.fromCharCode(n),n=r,a=0),e===t.length-1&&(n=r,i+=t.charAt(e-(a-1)),e-=a-1,a=0)):(n=r,i+=t.charAt(e-a),e-=a,a=0);return i};t.__arabicParser__.isArabicDiacritic=function(t){return void 0!==t&&void 0!==n[t.charCodeAt(0)]};var f=t.__arabicParser__.getCorrectForm=function(t,e,r){return o(t)?!1===a(t)?-1:!u(t)||!o(e)&&!o(r)||!o(r)&&s(e)||s(t)&&!o(e)||s(t)&&c(e)||s(t)&&s(e)?0:l(t)&&o(e)&&!s(e)&&o(r)&&u(r)?3:s(t)||!o(r)?1:2:-1},d=function(t){var r=0,n=0,i=0,a="",s="",c="",u=(t=t||"").split("\\s+"),l=[];for(r=0;r<u.length;r+=1){for(l.push(""),n=0;n<u[r].length;n+=1)a=u[r][n],s=u[r][n-1],c=u[r][n+1],o(a)?(i=f(a,s,c),l[r]+=-1!==i?String.fromCharCode(e[a.charCodeAt(0)][i]):a):l[r]+=a;l[r]=h(l[r])}return l.join(" ")},p=t.__arabicParser__.processArabic=t.processArabic=function(){var t,e="string"==typeof arguments[0]?arguments[0]:arguments[0].text,r=[];if(Array.isArray(e)){var n=0;for(r=[],n=0;n<e.length;n+=1)Array.isArray(e[n])?r.push([d(e[n][0]),e[n][1],e[n][2]]):r.push([d(e[n])]);t=r}else t=d(e);return"string"==typeof arguments[0]?t:(arguments[0].text=t,arguments[0])};t.events.push(["preProcessText",p])}(M.API), -/** @license - * jsPDF Autoprint Plugin - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.autoPrint=function(t){var e;switch((t=t||{}).variant=t.variant||"non-conform",t.variant){case"javascript":this.addJS("print({});");break;case"non-conform":default:this.internal.events.subscribe("postPutResources",(function(){e=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/S /Named"),this.internal.out("/Type /Action"),this.internal.out("/N /Print"),this.internal.out(">>"),this.internal.out("endobj")})),this.internal.events.subscribe("putCatalog",(function(){this.internal.out("/OpenAction "+e+" 0 R")}))}return this}}(M.API), -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(){var t=void 0;Object.defineProperty(this,"pdf",{get:function(){return t},set:function(e){t=e}});var e=150;Object.defineProperty(this,"width",{get:function(){return e},set:function(t){e=isNaN(t)||!1===Number.isInteger(t)||t<0?150:t,this.getContext("2d").pageWrapXEnabled&&(this.getContext("2d").pageWrapX=e+1)}});var r=300;Object.defineProperty(this,"height",{get:function(){return r},set:function(t){r=isNaN(t)||!1===Number.isInteger(t)||t<0?300:t,this.getContext("2d").pageWrapYEnabled&&(this.getContext("2d").pageWrapY=r+1)}});var n=[];Object.defineProperty(this,"childNodes",{get:function(){return n},set:function(t){n=t}});var i={};Object.defineProperty(this,"style",{get:function(){return i},set:function(t){i=t}}),Object.defineProperty(this,"parentNode",{})};e.prototype.getContext=function(t,e){var r;if("2d"!==(t=t||"2d"))return null;for(r in e)this.pdf.context2d.hasOwnProperty(r)&&(this.pdf.context2d[r]=e[r]);return this.pdf.context2d._canvas=this,this.pdf.context2d},e.prototype.toDataURL=function(){throw new Error("toDataURL is not implemented.")},t.events.push(["initialized",function(){this.canvas=new e,this.canvas.pdf=this}])}(M.API),function(t){var r={left:0,top:0,bottom:0,right:0},n=!1,i=function(){void 0===this.internal.__cell__&&(this.internal.__cell__={},this.internal.__cell__.padding=3,this.internal.__cell__.headerFunction=void 0,this.internal.__cell__.margins=Object.assign({},r),this.internal.__cell__.margins.width=this.getPageWidth(),a.call(this))},a=function(){this.internal.__cell__.lastCell=new o,this.internal.__cell__.pages=1},o=function(){var t=arguments[0];Object.defineProperty(this,"x",{enumerable:!0,get:function(){return t},set:function(e){t=e}});var e=arguments[1];Object.defineProperty(this,"y",{enumerable:!0,get:function(){return e},set:function(t){e=t}});var r=arguments[2];Object.defineProperty(this,"width",{enumerable:!0,get:function(){return r},set:function(t){r=t}});var n=arguments[3];Object.defineProperty(this,"height",{enumerable:!0,get:function(){return n},set:function(t){n=t}});var i=arguments[4];Object.defineProperty(this,"text",{enumerable:!0,get:function(){return i},set:function(t){i=t}});var a=arguments[5];Object.defineProperty(this,"lineNumber",{enumerable:!0,get:function(){return a},set:function(t){a=t}});var o=arguments[6];return Object.defineProperty(this,"align",{enumerable:!0,get:function(){return o},set:function(t){o=t}}),this};o.prototype.clone=function(){return new o(this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align)},o.prototype.toArray=function(){return[this.x,this.y,this.width,this.height,this.text,this.lineNumber,this.align]},t.setHeaderFunction=function(t){return i.call(this),this.internal.__cell__.headerFunction="function"==typeof t?t:void 0,this},t.getTextDimensions=function(t,e){i.call(this);var r=(e=e||{}).fontSize||this.getFontSize(),n=e.font||this.getFont(),a=e.scaleFactor||this.internal.scaleFactor,o=0,s=0,c=0,u=this;if(!Array.isArray(t)&&"string"!=typeof t){if("number"!=typeof t)throw new Error("getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings.");t=String(t)}var l=e.maxWidth;l>0?"string"==typeof t?t=this.splitTextToSize(t,l):"[object Array]"===Object.prototype.toString.call(t)&&(t=t.reduce((function(t,e){return t.concat(u.splitTextToSize(e,l))}),[])):t=Array.isArray(t)?t:[t];for(var h=0;h<t.length;h++)o<(c=this.getStringUnitWidth(t[h],{font:n})*r)&&(o=c);return 0!==o&&(s=t.length),{w:o/=a,h:Math.max((s*r*this.getLineHeightFactor()-r*(this.getLineHeightFactor()-1))/a,0)}},t.cellAddPage=function(){i.call(this),this.addPage();var t=this.internal.__cell__.margins||r;return this.internal.__cell__.lastCell=new o(t.left,t.top,void 0,void 0),this.internal.__cell__.pages+=1,this};var s=t.cell=function(){var t;t=arguments[0]instanceof o?arguments[0]:new o(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]),i.call(this);var e=this.internal.__cell__.lastCell,a=this.internal.__cell__.padding,s=this.internal.__cell__.margins||r,c=this.internal.__cell__.tableHeaderRow,u=this.internal.__cell__.printHeaders;return void 0!==e.lineNumber&&(e.lineNumber===t.lineNumber?(t.x=(e.x||0)+(e.width||0),t.y=e.y||0):e.y+e.height+t.height+s.bottom>this.getPageHeight()?(this.cellAddPage(),t.y=s.top,u&&c&&(this.printHeaderRow(t.lineNumber,!0),t.y+=c[0].height)):t.y=e.y+e.height||t.y),void 0!==t.text[0]&&(this.rect(t.x,t.y,t.width,t.height,!0===n?"FD":void 0),"right"===t.align?this.text(t.text,t.x+t.width-a,t.y+a,{align:"right",baseline:"top"}):"center"===t.align?this.text(t.text,t.x+t.width/2,t.y+a,{align:"center",baseline:"top",maxWidth:t.width-a-a}):this.text(t.text,t.x+a,t.y+a,{align:"left",baseline:"top",maxWidth:t.width-a-a})),this.internal.__cell__.lastCell=t,this};t.table=function(t,n,u,l,h){if(i.call(this),!u)throw new Error("No data for PDF table.");var f,d,p,g,m=[],v=[],b=[],y={},w={},N=[],L=[],A=(h=h||{}).autoSize||!1,x=!1!==h.printHeaders,S=h.css&&void 0!==h.css["font-size"]?16*h.css["font-size"]:h.fontSize||12,_=h.margins||Object.assign({width:this.getPageWidth()},r),P="number"==typeof h.padding?h.padding:3,k=h.headerBackgroundColor||"#c8c8c8",F=h.headerTextColor||"#000";if(a.call(this),this.internal.__cell__.printHeaders=x,this.internal.__cell__.margins=_,this.internal.__cell__.table_font_size=S,this.internal.__cell__.padding=P,this.internal.__cell__.headerBackgroundColor=k,this.internal.__cell__.headerTextColor=F,this.setFontSize(S),null==l)v=m=Object.keys(u[0]),b=m.map((function(){return"left"}));else if(Array.isArray(l)&&"object"===e(l[0]))for(m=l.map((function(t){return t.name})),v=l.map((function(t){return t.prompt||t.name||""})),b=l.map((function(t){return t.align||"left"})),f=0;f<l.length;f+=1)w[l[f].name]=l[f].width*(19.049976/25.4);else Array.isArray(l)&&"string"==typeof l[0]&&(v=m=l,b=m.map((function(){return"left"})));if(A||Array.isArray(l)&&"string"==typeof l[0])for(f=0;f<m.length;f+=1){for(y[g=m[f]]=u.map((function(t){return t[g]})),this.setFont(void 0,"bold"),N.push(this.getTextDimensions(v[f],{fontSize:this.internal.__cell__.table_font_size,scaleFactor:this.internal.scaleFactor}).w),d=y[g],this.setFont(void 0,"normal"),p=0;p<d.length;p+=1)N.push(this.getTextDimensions(d[p],{fontSize:this.internal.__cell__.table_font_size,scaleFactor:this.internal.scaleFactor}).w);w[g]=Math.max.apply(null,N)+P+P,N=[]}if(x){var I={};for(f=0;f<m.length;f+=1)I[m[f]]={},I[m[f]].text=v[f],I[m[f]].align=b[f];var C=c.call(this,I,w);L=m.map((function(e){return new o(t,n,w[e],C,I[e].text,void 0,I[e].align)})),this.setTableHeaderRow(L),this.printHeaderRow(1,!1)}var j=l.reduce((function(t,e){return t[e.name]=e.align,t}),{});for(f=0;f<u.length;f+=1){"rowStart"in h&&h.rowStart instanceof Function&&h.rowStart({row:f,data:u[f]},this);var O=c.call(this,u[f],w);for(p=0;p<m.length;p+=1){var B=u[f][m[p]];"cellStart"in h&&h.cellStart instanceof Function&&h.cellStart({row:f,col:p,data:B},this),s.call(this,new o(t,n,w[m[p]],O,B,f+2,j[m[p]]))}}return this.internal.__cell__.table_x=t,this.internal.__cell__.table_y=n,this};var c=function(t,e){var r=this.internal.__cell__.padding,n=this.internal.__cell__.table_font_size,i=this.internal.scaleFactor;return Object.keys(t).map((function(n){var i=t[n];return this.splitTextToSize(i.hasOwnProperty("text")?i.text:i,e[n]-r-r)}),this).map((function(t){return this.getLineHeightFactor()*t.length*n/i+r+r}),this).reduce((function(t,e){return Math.max(t,e)}),0)};t.setTableHeaderRow=function(t){i.call(this),this.internal.__cell__.tableHeaderRow=t},t.printHeaderRow=function(t,e){if(i.call(this),!this.internal.__cell__.tableHeaderRow)throw new Error("Property tableHeaderRow does not exist.");var r;if(n=!0,"function"==typeof this.internal.__cell__.headerFunction){var a=this.internal.__cell__.headerFunction(this,this.internal.__cell__.pages);this.internal.__cell__.lastCell=new o(a[0],a[1],a[2],a[3],void 0,-1)}this.setFont(void 0,"bold");for(var c=[],u=0;u<this.internal.__cell__.tableHeaderRow.length;u+=1){r=this.internal.__cell__.tableHeaderRow[u].clone(),e&&(r.y=this.internal.__cell__.margins.top||0,c.push(r)),r.lineNumber=t;var l=this.getTextColor();this.setTextColor(this.internal.__cell__.headerTextColor),this.setFillColor(this.internal.__cell__.headerBackgroundColor),s.call(this,r),this.setTextColor(l)}c.length>0&&this.setTableHeaderRow(c),this.setFont(void 0,"normal"),n=!1}}(M.API);var _t={italic:["italic","oblique","normal"],oblique:["oblique","italic","normal"],normal:["normal","oblique","italic"]},Pt=["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded"],kt=St(Pt),Ft=[100,200,300,400,500,600,700,800,900],It=St(Ft);function Ct(t){var e=t.family.replace(/"|'/g,"").toLowerCase(),r=function(t){return _t[t=t||"normal"]?t:"normal"}(t.style),n=function(t){if(!t)return 400;if("number"==typeof t)return t>=100&&t<=900&&t%100==0?t:400;if(/^\d00$/.test(t))return parseInt(t);switch(t){case"bold":return 700;case"normal":default:return 400}}(t.weight),i=function(t){return"number"==typeof kt[t=t||"normal"]?t:"normal"}(t.stretch);return{family:e,style:r,weight:n,stretch:i,src:t.src||[],ref:t.ref||{name:e,style:[i,r,n].join(" ")}}}function jt(t,e,r,n){var i;for(i=r;i>=0&&i<e.length;i+=n)if(t[e[i]])return t[e[i]];for(i=r;i>=0&&i<e.length;i-=n)if(t[e[i]])return t[e[i]]}var Ot={"sans-serif":"helvetica",fixed:"courier",monospace:"courier",terminal:"courier",cursive:"times",fantasy:"times",serif:"times"},Bt={caption:"times",icon:"times",menu:"times","message-box":"times","small-caption":"times","status-bar":"times"};function Mt(t){return[t.stretch,t.style,t.weight,t.family].join(" ")}function Et(t,e,r){for(var n=(r=r||{}).defaultFontFamily||"times",i=Object.assign({},Ot,r.genericFontFamilies||{}),a=null,o=null,s=0;s<e.length;++s)if(i[(a=Ct(e[s])).family]&&(a.family=i[a.family]),t.hasOwnProperty(a.family)){o=t[a.family];break}if(!(o=o||t[n]))throw new Error("Could not find a font-family for the rule '"+Mt(a)+"' and default family '"+n+"'.");if(o=function(t,e){if(e[t])return e[t];var r=kt[t],n=r<=kt.normal?-1:1,i=jt(e,Pt,r,n);if(!i)throw new Error("Could not find a matching font-stretch value for "+t);return i}(a.stretch,o),o=function(t,e){if(e[t])return e[t];for(var r=_t[t],n=0;n<r.length;++n)if(e[r[n]])return e[r[n]];throw new Error("Could not find a matching font-style for "+t)}(a.style,o),!(o=function(t,e){if(e[t])return e[t];if(400===t&&e[500])return e[500];if(500===t&&e[400])return e[400];var r=It[t],n=jt(e,Ft,r,t<400?-1:1);if(!n)throw new Error("Could not find a matching font-weight for value "+t);return n}(a.weight,o)))throw new Error("Failed to resolve a font for the rule '"+Mt(a)+"'.");return o}function qt(t){return t.trimLeft()}function Dt(t,e){for(var r=0;r<t.length;){if(t.charAt(r)===e)return[t.substring(0,r),t.substring(r+1)];r+=1}return null}function Rt(t){var e=t.match(/^(-[a-z_]|[a-z_])[a-z0-9_-]*/i);return null===e?null:[e[0],t.substring(e[0].length)]}var Tt=["times"];!function(t){var r,n,a,o,s,c,u,l,f,d=function(t){return t=t||{},this.isStrokeTransparent=t.isStrokeTransparent||!1,this.strokeOpacity=t.strokeOpacity||1,this.strokeStyle=t.strokeStyle||"#000000",this.fillStyle=t.fillStyle||"#000000",this.isFillTransparent=t.isFillTransparent||!1,this.fillOpacity=t.fillOpacity||1,this.font=t.font||"10px sans-serif",this.textBaseline=t.textBaseline||"alphabetic",this.textAlign=t.textAlign||"left",this.lineWidth=t.lineWidth||1,this.lineJoin=t.lineJoin||"miter",this.lineCap=t.lineCap||"butt",this.path=t.path||[],this.transform=void 0!==t.transform?t.transform.clone():new l,this.globalCompositeOperation=t.globalCompositeOperation||"normal",this.globalAlpha=t.globalAlpha||1,this.clip_path=t.clip_path||[],this.currentPoint=t.currentPoint||new c,this.miterLimit=t.miterLimit||10,this.lastPoint=t.lastPoint||new c,this.lineDashOffset=t.lineDashOffset||0,this.lineDash=t.lineDash||[],this.margin=t.margin||[0,0,0,0],this.prevPageLastElemOffset=t.prevPageLastElemOffset||0,this.ignoreClearRect="boolean"!=typeof t.ignoreClearRect||t.ignoreClearRect,this};t.events.push(["initialized",function(){this.context2d=new p(this),r=this.internal.f2,n=this.internal.getCoordinateString,a=this.internal.getVerticalCoordinateString,o=this.internal.getHorizontalCoordinate,s=this.internal.getVerticalCoordinate,c=this.internal.Point,u=this.internal.Rectangle,l=this.internal.Matrix,f=new d}]);var p=function(t){Object.defineProperty(this,"canvas",{get:function(){return{parentNode:!1,style:!1}}});var e=t;Object.defineProperty(this,"pdf",{get:function(){return e}});var r=!1;Object.defineProperty(this,"pageWrapXEnabled",{get:function(){return r},set:function(t){r=Boolean(t)}});var n=!1;Object.defineProperty(this,"pageWrapYEnabled",{get:function(){return n},set:function(t){n=Boolean(t)}});var i=0;Object.defineProperty(this,"posX",{get:function(){return i},set:function(t){isNaN(t)||(i=t)}});var a=0;Object.defineProperty(this,"posY",{get:function(){return a},set:function(t){isNaN(t)||(a=t)}}),Object.defineProperty(this,"margin",{get:function(){return f.margin},set:function(t){var e;"number"==typeof t?e=[t,t,t,t]:((e=new Array(4))[0]=t[0],e[1]=t.length>=2?t[1]:e[0],e[2]=t.length>=3?t[2]:e[0],e[3]=t.length>=4?t[3]:e[1]),f.margin=e}});var o=!1;Object.defineProperty(this,"autoPaging",{get:function(){return o},set:function(t){o=t}});var s=0;Object.defineProperty(this,"lastBreak",{get:function(){return s},set:function(t){s=t}});var c=[];Object.defineProperty(this,"pageBreaks",{get:function(){return c},set:function(t){c=t}}),Object.defineProperty(this,"ctx",{get:function(){return f},set:function(t){t instanceof d&&(f=t)}}),Object.defineProperty(this,"path",{get:function(){return f.path},set:function(t){f.path=t}});var u=[];Object.defineProperty(this,"ctxStack",{get:function(){return u},set:function(t){u=t}}),Object.defineProperty(this,"fillStyle",{get:function(){return this.ctx.fillStyle},set:function(t){var e;e=g(t),this.ctx.fillStyle=e.style,this.ctx.isFillTransparent=0===e.a,this.ctx.fillOpacity=e.a,this.pdf.setFillColor(e.r,e.g,e.b,{a:e.a}),this.pdf.setTextColor(e.r,e.g,e.b,{a:e.a})}}),Object.defineProperty(this,"strokeStyle",{get:function(){return this.ctx.strokeStyle},set:function(t){var e=g(t);this.ctx.strokeStyle=e.style,this.ctx.isStrokeTransparent=0===e.a,this.ctx.strokeOpacity=e.a,0===e.a?this.pdf.setDrawColor(255,255,255):(e.a,this.pdf.setDrawColor(e.r,e.g,e.b))}}),Object.defineProperty(this,"lineCap",{get:function(){return this.ctx.lineCap},set:function(t){-1!==["butt","round","square"].indexOf(t)&&(this.ctx.lineCap=t,this.pdf.setLineCap(t))}}),Object.defineProperty(this,"lineWidth",{get:function(){return this.ctx.lineWidth},set:function(t){isNaN(t)||(this.ctx.lineWidth=t,this.pdf.setLineWidth(t))}}),Object.defineProperty(this,"lineJoin",{get:function(){return this.ctx.lineJoin},set:function(t){-1!==["bevel","round","miter"].indexOf(t)&&(this.ctx.lineJoin=t,this.pdf.setLineJoin(t))}}),Object.defineProperty(this,"miterLimit",{get:function(){return this.ctx.miterLimit},set:function(t){isNaN(t)||(this.ctx.miterLimit=t,this.pdf.setMiterLimit(t))}}),Object.defineProperty(this,"textBaseline",{get:function(){return this.ctx.textBaseline},set:function(t){this.ctx.textBaseline=t}}),Object.defineProperty(this,"textAlign",{get:function(){return this.ctx.textAlign},set:function(t){-1!==["right","end","center","left","start"].indexOf(t)&&(this.ctx.textAlign=t)}});var l=null;function h(t,e){if(null===l){var r=function(t){var e=[];return Object.keys(t).forEach((function(r){t[r].forEach((function(t){var n=null;switch(t){case"bold":n={family:r,weight:"bold"};break;case"italic":n={family:r,style:"italic"};break;case"bolditalic":n={family:r,weight:"bold",style:"italic"};break;case"":case"normal":n={family:r}}null!==n&&(n.ref={name:r,style:t},e.push(n))}))})),e}(t.getFontList());l=function(t){for(var e={},r=0;r<t.length;++r){var n=Ct(t[r]),i=n.family,a=n.stretch,o=n.style,s=n.weight;e[i]=e[i]||{},e[i][a]=e[i][a]||{},e[i][a][o]=e[i][a][o]||{},e[i][a][o][s]=n}return e}(r.concat(e))}return l}var p=null;Object.defineProperty(this,"fontFaces",{get:function(){return p},set:function(t){l=null,p=t}}),Object.defineProperty(this,"font",{get:function(){return this.ctx.font},set:function(t){var e;if(this.ctx.font=t,null!==(e=/^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z]+?)\s*$/i.exec(t))){var r=e[1],n=(e[2],e[3]),i=e[4],a=(e[5],e[6]),o=/^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i.exec(i)[2];i="px"===o?Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor):"em"===o?Math.floor(parseFloat(i)*this.pdf.getFontSize()):Math.floor(parseFloat(i)*this.pdf.internal.scaleFactor),this.pdf.setFontSize(i);var s=function(t){var e,r,n=[],i=t.trim();if(""===i)return Tt;if(i in Bt)return[Bt[i]];for(;""!==i;){switch(r=null,e=(i=qt(i)).charAt(0)){case'"':case"'":r=Dt(i.substring(1),e);break;default:r=Rt(i)}if(null===r)return Tt;if(n.push(r[0]),""!==(i=qt(r[1]))&&","!==i.charAt(0))return Tt;i=i.replace(/^,/,"")}return n}(a);if(this.fontFaces){var c=Et(h(this.pdf,this.fontFaces),s.map((function(t){return{family:t,stretch:"normal",weight:n,style:r}})));this.pdf.setFont(c.ref.name,c.ref.style)}else{var u="";("bold"===n||parseInt(n,10)>=700||"bold"===r)&&(u="bold"),"italic"===r&&(u+="italic"),0===u.length&&(u="normal");for(var l="",f={arial:"Helvetica",Arial:"Helvetica",verdana:"Helvetica",Verdana:"Helvetica",helvetica:"Helvetica",Helvetica:"Helvetica","sans-serif":"Helvetica",fixed:"Courier",monospace:"Courier",terminal:"Courier",cursive:"Times",fantasy:"Times",serif:"Times"},d=0;d<s.length;d++){if(void 0!==this.pdf.internal.getFont(s[d],u,{noFallback:!0,disableWarning:!0})){l=s[d];break}if("bolditalic"===u&&void 0!==this.pdf.internal.getFont(s[d],"bold",{noFallback:!0,disableWarning:!0}))l=s[d],u="bold";else if(void 0!==this.pdf.internal.getFont(s[d],"normal",{noFallback:!0,disableWarning:!0})){l=s[d],u="normal";break}}if(""===l)for(var p=0;p<s.length;p++)if(f[s[p]]){l=f[s[p]];break}l=""===l?"Times":l,this.pdf.setFont(l,u)}}}}),Object.defineProperty(this,"globalCompositeOperation",{get:function(){return this.ctx.globalCompositeOperation},set:function(t){this.ctx.globalCompositeOperation=t}}),Object.defineProperty(this,"globalAlpha",{get:function(){return this.ctx.globalAlpha},set:function(t){this.ctx.globalAlpha=t}}),Object.defineProperty(this,"lineDashOffset",{get:function(){return this.ctx.lineDashOffset},set:function(t){this.ctx.lineDashOffset=t,T.call(this)}}),Object.defineProperty(this,"lineDash",{get:function(){return this.ctx.lineDash},set:function(t){this.ctx.lineDash=t,T.call(this)}}),Object.defineProperty(this,"ignoreClearRect",{get:function(){return this.ctx.ignoreClearRect},set:function(t){this.ctx.ignoreClearRect=Boolean(t)}})};p.prototype.setLineDash=function(t){this.lineDash=t},p.prototype.getLineDash=function(){return this.lineDash.length%2?this.lineDash.concat(this.lineDash):this.lineDash.slice()},p.prototype.fill=function(){A.call(this,"fill",!1)},p.prototype.stroke=function(){A.call(this,"stroke",!1)},p.prototype.beginPath=function(){this.path=[{type:"begin"}]},p.prototype.moveTo=function(t,e){if(isNaN(t)||isNaN(e))throw i.error("jsPDF.context2d.moveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.moveTo");var r=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"mt",x:r.x,y:r.y}),this.ctx.lastPoint=new c(t,e)},p.prototype.closePath=function(){var t=new c(0,0),r=0;for(r=this.path.length-1;-1!==r;r--)if("begin"===this.path[r].type&&"object"===e(this.path[r+1])&&"number"==typeof this.path[r+1].x){t=new c(this.path[r+1].x,this.path[r+1].y);break}this.path.push({type:"close"}),this.ctx.lastPoint=new c(t.x,t.y)},p.prototype.lineTo=function(t,e){if(isNaN(t)||isNaN(e))throw i.error("jsPDF.context2d.lineTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.lineTo");var r=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"lt",x:r.x,y:r.y}),this.ctx.lastPoint=new c(r.x,r.y)},p.prototype.clip=function(){this.ctx.clip_path=JSON.parse(JSON.stringify(this.path)),A.call(this,null,!0)},p.prototype.quadraticCurveTo=function(t,e,r,n){if(isNaN(r)||isNaN(n)||isNaN(t)||isNaN(e))throw i.error("jsPDF.context2d.quadraticCurveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.quadraticCurveTo");var a=this.ctx.transform.applyToPoint(new c(r,n)),o=this.ctx.transform.applyToPoint(new c(t,e));this.path.push({type:"qct",x1:o.x,y1:o.y,x:a.x,y:a.y}),this.ctx.lastPoint=new c(a.x,a.y)},p.prototype.bezierCurveTo=function(t,e,r,n,a,o){if(isNaN(a)||isNaN(o)||isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw i.error("jsPDF.context2d.bezierCurveTo: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.bezierCurveTo");var s=this.ctx.transform.applyToPoint(new c(a,o)),u=this.ctx.transform.applyToPoint(new c(t,e)),l=this.ctx.transform.applyToPoint(new c(r,n));this.path.push({type:"bct",x1:u.x,y1:u.y,x2:l.x,y2:l.y,x:s.x,y:s.y}),this.ctx.lastPoint=new c(s.x,s.y)},p.prototype.arc=function(t,e,r,n,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(a))throw i.error("jsPDF.context2d.arc: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.arc");if(o=Boolean(o),!this.ctx.transform.isIdentity){var s=this.ctx.transform.applyToPoint(new c(t,e));t=s.x,e=s.y;var u=this.ctx.transform.applyToPoint(new c(0,r)),l=this.ctx.transform.applyToPoint(new c(0,0));r=Math.sqrt(Math.pow(u.x-l.x,2)+Math.pow(u.y-l.y,2))}Math.abs(a-n)>=2*Math.PI&&(n=0,a=2*Math.PI),this.path.push({type:"arc",x:t,y:e,radius:r,startAngle:n,endAngle:a,counterclockwise:o})},p.prototype.arcTo=function(t,e,r,n,i){throw new Error("arcTo not implemented.")},p.prototype.rect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw i.error("jsPDF.context2d.rect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.rect");this.moveTo(t,e),this.lineTo(t+r,e),this.lineTo(t+r,e+n),this.lineTo(t,e+n),this.lineTo(t,e),this.lineTo(t+r,e),this.lineTo(t,e)},p.prototype.fillRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw i.error("jsPDF.context2d.fillRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.fillRect");if(!m.call(this)){var a={};"butt"!==this.lineCap&&(a.lineCap=this.lineCap,this.lineCap="butt"),"miter"!==this.lineJoin&&(a.lineJoin=this.lineJoin,this.lineJoin="miter"),this.beginPath(),this.rect(t,e,r,n),this.fill(),a.hasOwnProperty("lineCap")&&(this.lineCap=a.lineCap),a.hasOwnProperty("lineJoin")&&(this.lineJoin=a.lineJoin)}},p.prototype.strokeRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw i.error("jsPDF.context2d.strokeRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.strokeRect");v.call(this)||(this.beginPath(),this.rect(t,e,r,n),this.stroke())},p.prototype.clearRect=function(t,e,r,n){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n))throw i.error("jsPDF.context2d.clearRect: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.clearRect");this.ignoreClearRect||(this.fillStyle="#ffffff",this.fillRect(t,e,r,n))},p.prototype.save=function(t){t="boolean"!=typeof t||t;for(var e=this.pdf.internal.getCurrentPageInfo().pageNumber,r=0;r<this.pdf.internal.getNumberOfPages();r++)this.pdf.setPage(r+1),this.pdf.internal.out("q");if(this.pdf.setPage(e),t){this.ctx.fontSize=this.pdf.internal.getFontSize();var n=new d(this.ctx);this.ctxStack.push(this.ctx),this.ctx=n}},p.prototype.restore=function(t){t="boolean"!=typeof t||t;for(var e=this.pdf.internal.getCurrentPageInfo().pageNumber,r=0;r<this.pdf.internal.getNumberOfPages();r++)this.pdf.setPage(r+1),this.pdf.internal.out("Q");this.pdf.setPage(e),t&&0!==this.ctxStack.length&&(this.ctx=this.ctxStack.pop(),this.fillStyle=this.ctx.fillStyle,this.strokeStyle=this.ctx.strokeStyle,this.font=this.ctx.font,this.lineCap=this.ctx.lineCap,this.lineWidth=this.ctx.lineWidth,this.lineJoin=this.ctx.lineJoin,this.lineDash=this.ctx.lineDash,this.lineDashOffset=this.ctx.lineDashOffset)},p.prototype.toDataURL=function(){throw new Error("toDataUrl not implemented.")};var g=function(t){var e,r,n,i;if(!0===t.isCanvasGradient&&(t=t.getColor()),!t)return{r:0,g:0,b:0,a:0,style:t};if(/transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/.test(t))e=0,r=0,n=0,i=0;else{var a=/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(t);if(null!==a)e=parseInt(a[1]),r=parseInt(a[2]),n=parseInt(a[3]),i=1;else if(null!==(a=/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/.exec(t)))e=parseInt(a[1]),r=parseInt(a[2]),n=parseInt(a[3]),i=parseFloat(a[4]);else{if(i=1,"string"==typeof t&&"#"!==t.charAt(0)){var o=new h(t);t=o.ok?o.toHex():"#000000"}4===t.length?(e=t.substring(1,2),e+=e,r=t.substring(2,3),r+=r,n=t.substring(3,4),n+=n):(e=t.substring(1,3),r=t.substring(3,5),n=t.substring(5,7)),e=parseInt(e,16),r=parseInt(r,16),n=parseInt(n,16)}}return{r:e,g:r,b:n,a:i,style:t}},m=function(){return this.ctx.isFillTransparent||0==this.globalAlpha},v=function(){return Boolean(this.ctx.isStrokeTransparent||0==this.globalAlpha)};p.prototype.fillText=function(t,e,r,n){if(isNaN(e)||isNaN(r)||"string"!=typeof t)throw i.error("jsPDF.context2d.fillText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.fillText");if(n=isNaN(n)?void 0:n,!m.call(this)){var a=q(this.ctx.transform.rotation),o=this.ctx.transform.scaleX;C.call(this,{text:t,x:e,y:r,scale:o,angle:a,align:this.textAlign,maxWidth:n})}},p.prototype.strokeText=function(t,e,r,n){if(isNaN(e)||isNaN(r)||"string"!=typeof t)throw i.error("jsPDF.context2d.strokeText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.strokeText");if(!v.call(this)){n=isNaN(n)?void 0:n;var a=q(this.ctx.transform.rotation),o=this.ctx.transform.scaleX;C.call(this,{text:t,x:e,y:r,scale:o,renderingMode:"stroke",angle:a,align:this.textAlign,maxWidth:n})}},p.prototype.measureText=function(t){if("string"!=typeof t)throw i.error("jsPDF.context2d.measureText: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.measureText");var e=this.pdf,r=this.pdf.internal.scaleFactor,n=e.internal.getFontSize(),a=e.getStringUnitWidth(t)*n/e.internal.scaleFactor,o=function(t){var e=(t=t||{}).width||0;return Object.defineProperty(this,"width",{get:function(){return e}}),this};return new o({width:a*=Math.round(96*r/72*1e4)/1e4})},p.prototype.scale=function(t,e){if(isNaN(t)||isNaN(e))throw i.error("jsPDF.context2d.scale: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.scale");var r=new l(t,0,0,e,0,0);this.ctx.transform=this.ctx.transform.multiply(r)},p.prototype.rotate=function(t){if(isNaN(t))throw i.error("jsPDF.context2d.rotate: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.rotate");var e=new l(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0);this.ctx.transform=this.ctx.transform.multiply(e)},p.prototype.translate=function(t,e){if(isNaN(t)||isNaN(e))throw i.error("jsPDF.context2d.translate: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.translate");var r=new l(1,0,0,1,t,e);this.ctx.transform=this.ctx.transform.multiply(r)},p.prototype.transform=function(t,e,r,n,a,o){if(isNaN(t)||isNaN(e)||isNaN(r)||isNaN(n)||isNaN(a)||isNaN(o))throw i.error("jsPDF.context2d.transform: Invalid arguments",arguments),new Error("Invalid arguments passed to jsPDF.context2d.transform");var s=new l(t,e,r,n,a,o);this.ctx.transform=this.ctx.transform.multiply(s)},p.prototype.setTransform=function(t,e,r,n,i,a){t=isNaN(t)?1:t,e=isNaN(e)?0:e,r=isNaN(r)?0:r,n=isNaN(n)?1:n,i=isNaN(i)?0:i,a=isNaN(a)?0:a,this.ctx.transform=new l(t,e,r,n,i,a)};var b=function(){return this.margin[0]>0||this.margin[1]>0||this.margin[2]>0||this.margin[3]>0};p.prototype.drawImage=function(t,e,r,n,i,a,o,s,c){var h=this.pdf.getImageProperties(t),f=1,d=1,p=1,g=1;void 0!==n&&void 0!==s&&(p=s/n,g=c/i,f=h.width/n*s/n,d=h.height/i*c/i),void 0===a&&(a=e,o=r,e=0,r=0),void 0!==n&&void 0===s&&(s=n,c=i),void 0===n&&void 0===s&&(s=h.width,c=h.height);for(var m,v=this.ctx.transform.decompose(),w=q(v.rotate.shx),A=new l,S=(A=(A=(A=A.multiply(v.translate)).multiply(v.skew)).multiply(v.scale)).applyToRectangle(new u(a-e*p,o-r*g,n*f,i*d)),_=y.call(this,S),P=[],k=0;k<_.length;k+=1)-1===P.indexOf(_[k])&&P.push(_[k]);if(L(P),this.autoPaging)for(var F=P[0],I=P[P.length-1],C=F;C<I+1;C++){this.pdf.setPage(C);var j=this.pdf.internal.pageSize.width-this.margin[3]-this.margin[1],O=1===C?this.posY+this.margin[0]:this.margin[0],B=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],M=this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2],E=1===C?0:B+(C-2)*M;if(0!==this.ctx.clip_path.length){var D=this.path;m=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(m,this.posX+this.margin[3],-E+O+this.ctx.prevPageLastElemOffset),x.call(this,"fill",!0),this.path=D}var R=JSON.parse(JSON.stringify(S));R=N([R],this.posX+this.margin[3],-E+O+this.ctx.prevPageLastElemOffset)[0];var T=(C>F||C<I)&&b.call(this);T&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],j,M,null).clip().discardPath()),this.pdf.addImage(t,"JPEG",R.x,R.y,R.w,R.h,null,null,w),T&&this.pdf.restoreGraphicsState()}else this.pdf.addImage(t,"JPEG",S.x,S.y,S.w,S.h,null,null,w)};var y=function(t,e,r){var n=[];e=e||this.pdf.internal.pageSize.width,r=r||this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2];var i=this.posY+this.ctx.prevPageLastElemOffset;switch(t.type){default:case"mt":case"lt":n.push(Math.floor((t.y+i)/r)+1);break;case"arc":n.push(Math.floor((t.y+i-t.radius)/r)+1),n.push(Math.floor((t.y+i+t.radius)/r)+1);break;case"qct":var a=D(this.ctx.lastPoint.x,this.ctx.lastPoint.y,t.x1,t.y1,t.x,t.y);n.push(Math.floor((a.y+i)/r)+1),n.push(Math.floor((a.y+a.h+i)/r)+1);break;case"bct":var o=R(this.ctx.lastPoint.x,this.ctx.lastPoint.y,t.x1,t.y1,t.x2,t.y2,t.x,t.y);n.push(Math.floor((o.y+i)/r)+1),n.push(Math.floor((o.y+o.h+i)/r)+1);break;case"rect":n.push(Math.floor((t.y+i)/r)+1),n.push(Math.floor((t.y+t.h+i)/r)+1)}for(var s=0;s<n.length;s+=1)for(;this.pdf.internal.getNumberOfPages()<n[s];)w.call(this);return n},w=function(){var t=this.fillStyle,e=this.strokeStyle,r=this.font,n=this.lineCap,i=this.lineWidth,a=this.lineJoin;this.pdf.addPage(),this.fillStyle=t,this.strokeStyle=e,this.font=r,this.lineCap=n,this.lineWidth=i,this.lineJoin=a},N=function(t,e,r){for(var n=0;n<t.length;n++)switch(t[n].type){case"bct":t[n].x2+=e,t[n].y2+=r;case"qct":t[n].x1+=e,t[n].y1+=r;case"mt":case"lt":case"arc":default:t[n].x+=e,t[n].y+=r}return t},L=function(t){return t.sort((function(t,e){return t-e}))},A=function(t,e){for(var r,n,i=this.fillStyle,a=this.strokeStyle,o=this.lineCap,s=this.lineWidth,c=Math.abs(s*this.ctx.transform.scaleX),u=this.lineJoin,l=JSON.parse(JSON.stringify(this.path)),h=JSON.parse(JSON.stringify(this.path)),f=[],d=0;d<h.length;d++)if(void 0!==h[d].x)for(var p=y.call(this,h[d]),g=0;g<p.length;g+=1)-1===f.indexOf(p[g])&&f.push(p[g]);for(var m=0;m<f.length;m++)for(;this.pdf.internal.getNumberOfPages()<f[m];)w.call(this);if(L(f),this.autoPaging)for(var v=f[0],A=f[f.length-1],S=v;S<A+1;S++){this.pdf.setPage(S),this.fillStyle=i,this.strokeStyle=a,this.lineCap=o,this.lineWidth=c,this.lineJoin=u;var _=this.pdf.internal.pageSize.width-this.margin[3]-this.margin[1],P=1===S?this.posY+this.margin[0]:this.margin[0],k=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],F=this.pdf.internal.pageSize.height-this.margin[0]-this.margin[2],I=1===S?0:k+(S-2)*F;if(0!==this.ctx.clip_path.length){var C=this.path;r=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(r,this.posX+this.margin[3],-I+P+this.ctx.prevPageLastElemOffset),x.call(this,t,!0),this.path=C}if(n=JSON.parse(JSON.stringify(l)),this.path=N(n,this.posX+this.margin[3],-I+P+this.ctx.prevPageLastElemOffset),!1===e||0===S){var j=(S>v||S<A)&&b.call(this);j&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],_,F,null).clip().discardPath()),x.call(this,t,e),j&&this.pdf.restoreGraphicsState()}this.lineWidth=s}else this.lineWidth=c,x.call(this,t,e),this.lineWidth=s;this.path=l},x=function(t,e){if(("stroke"!==t||e||!v.call(this))&&("stroke"===t||e||!m.call(this))){for(var r,n,i=[],a=this.path,o=0;o<a.length;o++){var s=a[o];switch(s.type){case"begin":i.push({begin:!0});break;case"close":i.push({close:!0});break;case"mt":i.push({start:s,deltas:[],abs:[]});break;case"lt":var c=i.length;if(a[o-1]&&!isNaN(a[o-1].x)&&(r=[s.x-a[o-1].x,s.y-a[o-1].y],c>0))for(;c>=0;c--)if(!0!==i[c-1].close&&!0!==i[c-1].begin){i[c-1].deltas.push(r),i[c-1].abs.push(s);break}break;case"bct":r=[s.x1-a[o-1].x,s.y1-a[o-1].y,s.x2-a[o-1].x,s.y2-a[o-1].y,s.x-a[o-1].x,s.y-a[o-1].y],i[i.length-1].deltas.push(r);break;case"qct":var u=a[o-1].x+2/3*(s.x1-a[o-1].x),l=a[o-1].y+2/3*(s.y1-a[o-1].y),h=s.x+2/3*(s.x1-s.x),f=s.y+2/3*(s.y1-s.y),d=s.x,p=s.y;r=[u-a[o-1].x,l-a[o-1].y,h-a[o-1].x,f-a[o-1].y,d-a[o-1].x,p-a[o-1].y],i[i.length-1].deltas.push(r);break;case"arc":i.push({deltas:[],abs:[],arc:!0}),Array.isArray(i[i.length-1].abs)&&i[i.length-1].abs.push(s)}}n=e?null:"stroke"===t?"stroke":"fill";for(var g=!1,b=0;b<i.length;b++)if(i[b].arc)for(var y=i[b].abs,w=0;w<y.length;w++){var N=y[w];"arc"===N.type?P.call(this,N.x,N.y,N.radius,N.startAngle,N.endAngle,N.counterclockwise,void 0,e,!g):j.call(this,N.x,N.y),g=!0}else if(!0===i[b].close)this.pdf.internal.out("h"),g=!1;else if(!0!==i[b].begin){var L=i[b].start.x,A=i[b].start.y;O.call(this,i[b].deltas,L,A),g=!0}n&&k.call(this,n),e&&F.call(this)}},S=function(t){var e=this.pdf.internal.getFontSize()/this.pdf.internal.scaleFactor,r=e*(this.pdf.internal.getLineHeightFactor()-1);switch(this.ctx.textBaseline){case"bottom":return t-r;case"top":return t+e-r;case"hanging":return t+e-2*r;case"middle":return t+e/2-r;case"ideographic":return t;case"alphabetic":default:return t}},_=function(t){return t+this.pdf.internal.getFontSize()/this.pdf.internal.scaleFactor*(this.pdf.internal.getLineHeightFactor()-1)};p.prototype.createLinearGradient=function(){var t=function(){};return t.colorStops=[],t.addColorStop=function(t,e){this.colorStops.push([t,e])},t.getColor=function(){return 0===this.colorStops.length?"#000000":this.colorStops[0][1]},t.isCanvasGradient=!0,t},p.prototype.createPattern=function(){return this.createLinearGradient()},p.prototype.createRadialGradient=function(){return this.createLinearGradient()};var P=function(t,e,r,n,i,a,o,s,c){for(var u=M.call(this,r,n,i,a),l=0;l<u.length;l++){var h=u[l];0===l&&(c?I.call(this,h.x1+t,h.y1+e):j.call(this,h.x1+t,h.y1+e)),B.call(this,t,e,h.x2,h.y2,h.x3,h.y3,h.x4,h.y4)}s?F.call(this):k.call(this,o)},k=function(t){switch(t){case"stroke":this.pdf.internal.out("S");break;case"fill":this.pdf.internal.out("f")}},F=function(){this.pdf.clip(),this.pdf.discardPath()},I=function(t,e){this.pdf.internal.out(n(t)+" "+a(e)+" m")},C=function(t){var e;switch(t.align){case"right":case"end":e="right";break;case"center":e="center";break;case"left":case"start":default:e="left"}var r=this.pdf.getTextDimensions(t.text),n=S.call(this,t.y),i=_.call(this,n)-r.h,a=this.ctx.transform.applyToPoint(new c(t.x,n)),o=this.ctx.transform.decompose(),s=new l;s=(s=(s=s.multiply(o.translate)).multiply(o.skew)).multiply(o.scale);for(var h,f,d,p=this.ctx.transform.applyToRectangle(new u(t.x,n,r.w,r.h)),g=s.applyToRectangle(new u(t.x,i,r.w,r.h)),m=y.call(this,g),v=[],w=0;w<m.length;w+=1)-1===v.indexOf(m[w])&&v.push(m[w]);if(L(v),this.autoPaging)for(var A=v[0],P=v[v.length-1],k=A;k<P+1;k++){this.pdf.setPage(k);var F=1===k?this.posY+this.margin[0]:this.margin[0],I=this.pdf.internal.pageSize.height-this.posY-this.margin[0]-this.margin[2],C=this.pdf.internal.pageSize.height-this.margin[2],j=C-this.margin[0],O=this.pdf.internal.pageSize.width-this.margin[1],B=O-this.margin[3],M=1===k?0:I+(k-2)*j;if(0!==this.ctx.clip_path.length){var E=this.path;h=JSON.parse(JSON.stringify(this.ctx.clip_path)),this.path=N(h,this.posX+this.margin[3],-1*M+F),x.call(this,"fill",!0),this.path=E}var q=N([JSON.parse(JSON.stringify(g))],this.posX+this.margin[3],-M+F+this.ctx.prevPageLastElemOffset)[0];t.scale>=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale);var D="text"!==this.autoPaging;if(D||q.y+q.h<=C){if(D||q.y>=F&&q.x<=O){var R=D?t.text:this.pdf.splitTextToSize(t.text,t.maxWidth||O-q.x)[0],T=N([JSON.parse(JSON.stringify(p))],this.posX+this.margin[3],-M+F+this.ctx.prevPageLastElemOffset)[0],U=D&&(k>A||k<P)&&b.call(this);U&&(this.pdf.saveGraphicsState(),this.pdf.rect(this.margin[3],this.margin[0],B,j,null).clip().discardPath()),this.pdf.text(R,T.x,T.y,{angle:t.angle,align:e,renderingMode:t.renderingMode}),U&&this.pdf.restoreGraphicsState()}}else q.y<C&&(this.ctx.prevPageLastElemOffset+=C-q.y);t.scale>=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)}else t.scale>=.01&&(f=this.pdf.internal.getFontSize(),this.pdf.setFontSize(f*t.scale),d=this.lineWidth,this.lineWidth=d*t.scale),this.pdf.text(t.text,a.x+this.posX,a.y+this.posY,{angle:t.angle,align:e,renderingMode:t.renderingMode,maxWidth:t.maxWidth}),t.scale>=.01&&(this.pdf.setFontSize(f),this.lineWidth=d)},j=function(t,e,r,i){r=r||0,i=i||0,this.pdf.internal.out(n(t+r)+" "+a(e+i)+" l")},O=function(t,e,r){return this.pdf.lines(t,e,r,null,null)},B=function(t,e,n,i,a,c,u,l){this.pdf.internal.out([r(o(n+t)),r(s(i+e)),r(o(a+t)),r(s(c+e)),r(o(u+t)),r(s(l+e)),"c"].join(" "))},M=function(t,e,r,n){for(var i=2*Math.PI,a=Math.PI/2;e>r;)e-=i;var o=Math.abs(r-e);o<i&&n&&(o=i-o);for(var s=[],c=n?-1:1,u=e;o>1e-5;){var l=u+c*Math.min(o,a);s.push(E.call(this,t,u,l)),o-=Math.abs(l-u),u=l}return s},E=function(t,e,r){var n=(r-e)/2,i=t*Math.cos(n),a=t*Math.sin(n),o=i,s=-a,c=o*o+s*s,u=c+o*i+s*a,l=4/3*(Math.sqrt(2*c*u)-u)/(o*a-s*i),h=o-l*s,f=s+l*o,d=h,p=-f,g=n+e,m=Math.cos(g),v=Math.sin(g);return{x1:t*Math.cos(e),y1:t*Math.sin(e),x2:h*m-f*v,y2:h*v+f*m,x3:d*m-p*v,y3:d*v+p*m,x4:t*Math.cos(r),y4:t*Math.sin(r)}},q=function(t){return 180*t/Math.PI},D=function(t,e,r,n,i,a){var o=t+.5*(r-t),s=e+.5*(n-e),c=i+.5*(r-i),l=a+.5*(n-a),h=Math.min(t,i,o,c),f=Math.max(t,i,o,c),d=Math.min(e,a,s,l),p=Math.max(e,a,s,l);return new u(h,d,f-h,p-d)},R=function(t,e,r,n,i,a,o,s){var c,l,h,f,d,p,g,m,v,b,y,w,N,L,A=r-t,x=n-e,S=i-r,_=a-n,P=o-i,k=s-a;for(l=0;l<41;l++)v=(g=(h=t+(c=l/40)*A)+c*((d=r+c*S)-h))+c*(d+c*(i+c*P-d)-g),b=(m=(f=e+c*x)+c*((p=n+c*_)-f))+c*(p+c*(a+c*k-p)-m),0==l?(y=v,w=b,N=v,L=b):(y=Math.min(y,v),w=Math.min(w,b),N=Math.max(N,v),L=Math.max(L,b));return new u(Math.round(y),Math.round(w),Math.round(N-y),Math.round(L-w))},T=function(){if(this.prevLineDash||this.ctx.lineDash.length||this.ctx.lineDashOffset){var t,e,r=(t=this.ctx.lineDash,e=this.ctx.lineDashOffset,JSON.stringify({lineDash:t,lineDashOffset:e}));this.prevLineDash!==r&&(this.pdf.setLineDash(this.ctx.lineDash,this.ctx.lineDashOffset),this.prevLineDash=r)}}}(M.API);try{require("worker_threads").Worker}catch(t){}var Ut=Uint8Array,zt=Uint16Array,Ht=Uint32Array,Wt=new Ut([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Vt=new Ut([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Gt=new Ut([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Yt=function(t,e){for(var r=new zt(31),n=0;n<31;++n)r[n]=e+=1<<t[n-1];var i=new Ht(r[30]);for(n=1;n<30;++n)for(var a=r[n];a<r[n+1];++a)i[a]=a-r[n]<<5|n;return[r,i]},Jt=Yt(Wt,2),Xt=Jt[0],Kt=Jt[1];Xt[28]=258,Kt[258]=28;for(var Zt=Yt(Vt,0),$t=Zt[0],Qt=Zt[1],te=new zt(32768),ee=0;ee<32768;++ee){var re=(43690&ee)>>>1|(21845&ee)<<1;re=(61680&(re=(52428&re)>>>2|(13107&re)<<2))>>>4|(3855&re)<<4,te[ee]=((65280&re)>>>8|(255&re)<<8)>>>1}var ne=function(t,e,r){for(var n=t.length,i=0,a=new zt(e);i<n;++i)++a[t[i]-1];var o,s=new zt(e);for(i=0;i<e;++i)s[i]=s[i-1]+a[i-1]<<1;if(r){o=new zt(1<<e);var c=15-e;for(i=0;i<n;++i)if(t[i])for(var u=i<<4|t[i],l=e-t[i],h=s[t[i]-1]++<<l,f=h|(1<<l)-1;h<=f;++h)o[te[h]>>>c]=u}else for(o=new zt(n),i=0;i<n;++i)o[i]=te[s[t[i]-1]++]>>>15-t[i];return o},ie=new Ut(288);for(ee=0;ee<144;++ee)ie[ee]=8;for(ee=144;ee<256;++ee)ie[ee]=9;for(ee=256;ee<280;++ee)ie[ee]=7;for(ee=280;ee<288;++ee)ie[ee]=8;var ae=new Ut(32);for(ee=0;ee<32;++ee)ae[ee]=5;var oe=ne(ie,9,0),se=ne(ie,9,1),ce=ne(ae,5,0),ue=ne(ae,5,1),le=function(t){for(var e=t[0],r=1;r<t.length;++r)t[r]>e&&(e=t[r]);return e},he=function(t,e,r){var n=e/8>>0;return(t[n]|t[n+1]<<8)>>>(7&e)&r},fe=function(t,e){var r=e/8>>0;return(t[r]|t[r+1]<<8|t[r+2]<<16)>>>(7&e)},de=function(t){return(t/8>>0)+(7&t&&1)},pe=function(t,e,r){(null==e||e<0)&&(e=0),(null==r||r>t.length)&&(r=t.length);var n=new(t instanceof zt?zt:t instanceof Ht?Ht:Ut)(r-e);return n.set(t.subarray(e,r)),n},ge=function(t,e,r){r<<=7&e;var n=e/8>>0;t[n]|=r,t[n+1]|=r>>>8},me=function(t,e,r){r<<=7&e;var n=e/8>>0;t[n]|=r,t[n+1]|=r>>>8,t[n+2]|=r>>>16},ve=function(t,e){for(var r=[],n=0;n<t.length;++n)t[n]&&r.push({s:n,f:t[n]});var i=r.length,a=r.slice();if(!i)return[new Ut(0),0];if(1==i){var o=new Ut(r[0].s+1);return o[r[0].s]=1,[o,1]}r.sort((function(t,e){return t.f-e.f})),r.push({s:-1,f:25001});var s=r[0],c=r[1],u=0,l=1,h=2;for(r[0]={s:-1,f:s.f+c.f,l:s,r:c};l!=i-1;)s=r[r[u].f<r[h].f?u++:h++],c=r[u!=l&&r[u].f<r[h].f?u++:h++],r[l++]={s:-1,f:s.f+c.f,l:s,r:c};var f=a[0].s;for(n=1;n<i;++n)a[n].s>f&&(f=a[n].s);var d=new zt(f+1),p=be(r[l-1],d,0);if(p>e){n=0;var g=0,m=p-e,v=1<<m;for(a.sort((function(t,e){return d[e.s]-d[t.s]||t.f-e.f}));n<i;++n){var b=a[n].s;if(!(d[b]>e))break;g+=v-(1<<p-d[b]),d[b]=e}for(g>>>=m;g>0;){var y=a[n].s;d[y]<e?g-=1<<e-d[y]++-1:++n}for(;n>=0&&g;--n){var w=a[n].s;d[w]==e&&(--d[w],++g)}p=e}return[new Ut(d),p]},be=function(t,e,r){return-1==t.s?Math.max(be(t.l,e,r+1),be(t.r,e,r+1)):e[t.s]=r},ye=function(t){for(var e=t.length;e&&!t[--e];);for(var r=new zt(++e),n=0,i=t[0],a=1,o=function(t){r[n++]=t},s=1;s<=e;++s)if(t[s]==i&&s!=e)++a;else{if(!i&&a>2){for(;a>138;a-=138)o(32754);a>2&&(o(a>10?a-11<<5|28690:a-3<<5|12305),a=0)}else if(a>3){for(o(i),--a;a>6;a-=6)o(8304);a>2&&(o(a-3<<5|8208),a=0)}for(;a--;)o(i);a=1,i=t[s]}return[r.subarray(0,n),e]},we=function(t,e){for(var r=0,n=0;n<e.length;++n)r+=t[n]*e[n];return r},Ne=function(t,e,r){var n=r.length,i=de(e+2);t[i]=255&n,t[i+1]=n>>>8,t[i+2]=255^t[i],t[i+3]=255^t[i+1];for(var a=0;a<n;++a)t[i+a+4]=r[a];return 8*(i+4+n)},Le=function(t,e,r,n,i,a,o,s,c,u,l){ge(e,l++,r),++i[256];for(var h=ve(i,15),f=h[0],d=h[1],p=ve(a,15),g=p[0],m=p[1],v=ye(f),b=v[0],y=v[1],w=ye(g),N=w[0],L=w[1],A=new zt(19),x=0;x<b.length;++x)A[31&b[x]]++;for(x=0;x<N.length;++x)A[31&N[x]]++;for(var S=ve(A,7),_=S[0],P=S[1],k=19;k>4&&!_[Gt[k-1]];--k);var F,I,C,j,O=u+5<<3,B=we(i,ie)+we(a,ae)+o,M=we(i,f)+we(a,g)+o+14+3*k+we(A,_)+(2*A[16]+3*A[17]+7*A[18]);if(O<=B&&O<=M)return Ne(e,l,t.subarray(c,c+u));if(ge(e,l,1+(M<B)),l+=2,M<B){F=ne(f,d,0),I=f,C=ne(g,m,0),j=g;var E=ne(_,P,0);ge(e,l,y-257),ge(e,l+5,L-1),ge(e,l+10,k-4),l+=14;for(x=0;x<k;++x)ge(e,l+3*x,_[Gt[x]]);l+=3*k;for(var q=[b,N],D=0;D<2;++D){var R=q[D];for(x=0;x<R.length;++x){var T=31&R[x];ge(e,l,E[T]),l+=_[T],T>15&&(ge(e,l,R[x]>>>5&127),l+=R[x]>>>12)}}}else F=oe,I=ie,C=ce,j=ae;for(x=0;x<s;++x)if(n[x]>255){T=n[x]>>>18&31;me(e,l,F[T+257]),l+=I[T+257],T>7&&(ge(e,l,n[x]>>>23&31),l+=Wt[T]);var U=31&n[x];me(e,l,C[U]),l+=j[U],U>3&&(me(e,l,n[x]>>>5&8191),l+=Vt[U])}else me(e,l,F[n[x]]),l+=I[n[x]];return me(e,l,F[256]),l+I[256]},Ae=new Ht([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),xe=new Ut(0),Se=function(t,e,r,n,i){return function(t,e,r,n,i,a){var o=t.length,s=new Ut(n+o+5*(1+Math.floor(o/7e3))+i),c=s.subarray(n,s.length-i),u=0;if(!e||o<8)for(var l=0;l<=o;l+=65535){var h=l+65535;h<o?u=Ne(c,u,t.subarray(l,h)):(c[l]=a,u=Ne(c,u,t.subarray(l,o)))}else{for(var f=Ae[e-1],d=f>>>13,p=8191&f,g=(1<<r)-1,m=new zt(32768),v=new zt(g+1),b=Math.ceil(r/3),y=2*b,w=function(e){return(t[e]^t[e+1]<<b^t[e+2]<<y)&g},N=new Ht(25e3),L=new zt(288),A=new zt(32),x=0,S=0,_=(l=0,0),P=0,k=0;l<o;++l){var F=w(l),I=32767&l,C=v[F];if(m[I]=C,v[F]=I,P<=l){var j=o-l;if((x>7e3||_>24576)&&j>423){u=Le(t,c,0,N,L,A,S,_,k,l-k,u),_=x=S=0,k=l;for(var O=0;O<286;++O)L[O]=0;for(O=0;O<30;++O)A[O]=0}var B=2,M=0,E=p,q=I-C&32767;if(j>2&&F==w(l-q))for(var D=Math.min(d,j)-1,R=Math.min(32767,l),T=Math.min(258,j);q<=R&&--E&&I!=C;){if(t[l+B]==t[l+B-q]){for(var U=0;U<T&&t[l+U]==t[l+U-q];++U);if(U>B){if(B=U,M=q,U>D)break;var z=Math.min(q,U-2),H=0;for(O=0;O<z;++O){var W=l-q+O+32768&32767,V=W-m[W]+32768&32767;V>H&&(H=V,C=W)}}}q+=(I=C)-(C=m[I])+32768&32767}if(M){N[_++]=268435456|Kt[B]<<18|Qt[M];var G=31&Kt[B],Y=31&Qt[M];S+=Wt[G]+Vt[Y],++L[257+G],++A[Y],P=l+B,++x}else N[_++]=t[l],++L[t[l]]}}u=Le(t,c,a,N,L,A,S,_,k,l-k,u),a||(u=Ne(c,u,xe))}return pe(s,0,n+de(u)+i)}(t,null==e.level?6:e.level,null==e.mem?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(t.length)))):12+e.mem,r,n,!i)};function _e(t,e){void 0===e&&(e={});var r=function(){var t=1,e=0;return{p:function(r){for(var n=t,i=e,a=r.length,o=0;o!=a;){for(var s=Math.min(o+5552,a);o<s;++o)i+=n+=r[o];n%=65521,i%=65521}t=n,e=i},d:function(){return(t>>>8<<16|(255&e)<<8|e>>>8)+2*((255&t)<<23)}}}();r.p(t);var n=Se(t,e,2,4);return function(t,e){var r=e.level,n=0==r?0:r<6?1:9==r?3:2;t[0]=120,t[1]=n<<6|(n?32-2*n:1)}(n,e),function(t,e,r){for(;r;++e)t[e]=r,r>>>=8}(n,n.length-4,r.d()),n}function Pe(t,e){return function(t,e,r){var n=t.length,i=!e||r,a=!r||r.i;r||(r={}),e||(e=new Ut(3*n));var o=function(t){var r=e.length;if(t>r){var n=new Ut(Math.max(2*r,t));n.set(e),e=n}},s=r.f||0,c=r.p||0,u=r.b||0,l=r.l,h=r.d,f=r.m,d=r.n,p=8*n;do{if(!l){r.f=s=he(t,c,1);var g=he(t,c+1,3);if(c+=3,!g){var m=t[(P=de(c)+4)-4]|t[P-3]<<8,v=P+m;if(v>n){if(a)throw"unexpected EOF";break}i&&o(u+m),e.set(t.subarray(P,v),u),r.b=u+=m,r.p=c=8*v;continue}if(1==g)l=se,h=ue,f=9,d=5;else{if(2!=g)throw"invalid block type";var b=he(t,c,31)+257,y=he(t,c+10,15)+4,w=b+he(t,c+5,31)+1;c+=14;for(var N=new Ut(w),L=new Ut(19),A=0;A<y;++A)L[Gt[A]]=he(t,c+3*A,7);c+=3*y;var x=le(L),S=(1<<x)-1;if(!a&&c+w*(x+7)>p)break;var _=ne(L,x,1);for(A=0;A<w;){var P,k=_[he(t,c,S)];if(c+=15&k,(P=k>>>4)<16)N[A++]=P;else{var F=0,I=0;for(16==P?(I=3+he(t,c,3),c+=2,F=N[A-1]):17==P?(I=3+he(t,c,7),c+=3):18==P&&(I=11+he(t,c,127),c+=7);I--;)N[A++]=F}}var C=N.subarray(0,b),j=N.subarray(b);f=le(C),d=le(j),l=ne(C,f,1),h=ne(j,d,1)}if(c>p)throw"unexpected EOF"}i&&o(u+131072);for(var O=(1<<f)-1,B=(1<<d)-1,M=f+d+18;a||c+M<p;){var E=(F=l[fe(t,c)&O])>>>4;if((c+=15&F)>p)throw"unexpected EOF";if(!F)throw"invalid length/literal";if(E<256)e[u++]=E;else{if(256==E){l=null;break}var q=E-254;if(E>264){var D=Wt[A=E-257];q=he(t,c,(1<<D)-1)+Xt[A],c+=D}var R=h[fe(t,c)&B],T=R>>>4;if(!R)throw"invalid distance";c+=15&R;j=$t[T];if(T>3){D=Vt[T];j+=fe(t,c)&(1<<D)-1,c+=D}if(c>p)throw"unexpected EOF";i&&o(u+131072);for(var U=u+q;u<U;u+=4)e[u]=e[u-j],e[u+1]=e[u+1-j],e[u+2]=e[u+2-j],e[u+3]=e[u+3-j];u=U}}r.l=l,r.p=c,r.b=u,l&&(s=1,r.m=f,r.d=h,r.n=d)}while(!s);return u==e.length?e:pe(e,0,u)}((function(t){if(8!=(15&t[0])||t[0]>>>4>7||(t[0]<<8|t[1])%31)throw"invalid zlib data";if(32&t[1])throw"invalid zlib data: preset dictionaries not supported"}(t),t.subarray(2,-4)),e)} -/** - * @license - * jsPDF filters PlugIn - * Copyright (c) 2014 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */!function(t){var e=function(t){var e,r,n,i,a,o,s,c,u,l;for(/[^\x00-\xFF]/.test(t),r=[],n=0,i=(t+=e="\0\0\0\0".slice(t.length%4||4)).length;i>n;n+=4)0!==(a=(t.charCodeAt(n)<<24)+(t.charCodeAt(n+1)<<16)+(t.charCodeAt(n+2)<<8)+t.charCodeAt(n+3))?(o=(a=((a=((a=((a=(a-(l=a%85))/85)-(u=a%85))/85)-(c=a%85))/85)-(s=a%85))/85)%85,r.push(o+33,s+33,c+33,u+33,l+33)):r.push(122);return function(t,e){for(var r=e;r>0;r--)t.pop()}(r,e.length),String.fromCharCode.apply(String,r)+"~>"},r=function(t){var e,r,n,i,a,o=String,s="length",c=255,u="charCodeAt",l="slice",h="replace";for(t[l](-2),t=t[l](0,-2)[h](/\s/g,"")[h]("z","!!!!!"),n=[],i=0,a=(t+=e="uuuuu"[l](t[s]%5||5))[s];a>i;i+=5)r=52200625*(t[u](i)-33)+614125*(t[u](i+1)-33)+7225*(t[u](i+2)-33)+85*(t[u](i+3)-33)+(t[u](i+4)-33),n.push(c&r>>24,c&r>>16,c&r>>8,c&r);return function(t,e){for(var r=e;r>0;r--)t.pop()}(n,e[s]),o.fromCharCode.apply(o,n)},n=function(t){var e=new RegExp(/^([0-9A-Fa-f]{2})+$/);if(-1!==(t=t.replace(/\s/g,"")).indexOf(">")&&(t=t.substr(0,t.indexOf(">"))),t.length%2&&(t+="0"),!1===e.test(t))return"";for(var r="",n=0;n<t.length;n+=2)r+=String.fromCharCode("0x"+(t[n]+t[n+1]));return r},i=function(t){for(var e=new Uint8Array(t.length),r=t.length;r--;)e[r]=t.charCodeAt(r);return t=(e=_e(e)).reduce((function(t,e){return t+String.fromCharCode(e)}),"")};t.processDataByFilters=function(t,a){var o=0,s=t||"",c=[];for("string"==typeof(a=a||[])&&(a=[a]),o=0;o<a.length;o+=1)switch(a[o]){case"ASCII85Decode":case"/ASCII85Decode":s=r(s),c.push("/ASCII85Encode");break;case"ASCII85Encode":case"/ASCII85Encode":s=e(s),c.push("/ASCII85Decode");break;case"ASCIIHexDecode":case"/ASCIIHexDecode":s=n(s),c.push("/ASCIIHexEncode");break;case"ASCIIHexEncode":case"/ASCIIHexEncode":s=s.split("").map((function(t){return("0"+t.charCodeAt().toString(16)).slice(-2)})).join("")+">",c.push("/ASCIIHexDecode");break;case"FlateEncode":case"/FlateEncode":s=i(s),c.push("/FlateDecode");break;default:throw new Error('The filter: "'+a[o]+'" is not implemented')}return{data:s,reverseChain:c.reverse().join(" ")}}}(M.API), -/** - * @license - * jsPDF fileloading PlugIn - * Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com) - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.loadFile=function(t,e,r){return function(t,e,r){e=!1!==e,r="function"==typeof r?r:function(){};var n=void 0;try{n=function(t,e,r){var n=new XMLHttpRequest,i=0,a=function(t){var e=t.length,r=[],n=String.fromCharCode;for(i=0;i<e;i+=1)r.push(n(255&t.charCodeAt(i)));return r.join("")};if(n.open("GET",t,!e),n.overrideMimeType("text/plain; charset=x-user-defined"),!1===e&&(n.onload=function(){200===n.status?r(a(this.responseText)):r(void 0)}),n.send(null),e&&200===n.status)return a(n.responseText)}(t,e,r)}catch(t){}return n}(t,e,r)},t.loadImageFile=t.loadFile}(M.API),function(n){function i(){return(r.html2canvas?Promise.resolve(r.html2canvas):"object"===(void 0===t?"undefined":e(t))&&"undefined"!=typeof module?new Promise((function(t,e){try{t(require("html2canvas"))}catch(t){e(t)}})):"function"==typeof define&&define.amd?new Promise((function(t,e){try{require(["html2canvas"],t)}catch(t){e(t)}})):Promise.reject(new Error("Could not load html2canvas"))).catch((function(t){return Promise.reject(new Error("Could not load html2canvas: "+t))})).then((function(t){return t.default?t.default:t}))}function a(){return(r.DOMPurify?Promise.resolve(r.DOMPurify):"object"===(void 0===t?"undefined":e(t))&&"undefined"!=typeof module?new Promise((function(t,e){try{t(require("dompurify"))}catch(t){e(t)}})):"function"==typeof define&&define.amd?new Promise((function(t,e){try{require(["dompurify"],t)}catch(t){e(t)}})):Promise.reject(new Error("Could not load dompurify"))).catch((function(t){return Promise.reject(new Error("Could not load dompurify: "+t))})).then((function(t){return t.default?t.default:t}))}var o=function(t){var r=e(t);return"undefined"===r?"undefined":"string"===r||t instanceof String?"string":"number"===r||t instanceof Number?"number":"function"===r||t instanceof Function?"function":t&&t.constructor===Array?"array":t&&1===t.nodeType?"element":"object"===r?"object":"unknown"},s=function(t,e){var r=document.createElement(t);for(var n in e.className&&(r.className=e.className),e.innerHTML&&e.dompurify&&(r.innerHTML=e.dompurify.sanitize(e.innerHTML)),e.style)r.style[n]=e.style[n];return r},c=function t(e){var r=Object.assign(t.convert(Promise.resolve()),JSON.parse(JSON.stringify(t.template))),n=t.convert(Promise.resolve(),r);return n=(n=n.setProgress(1,t,1,[t])).set(e)};(c.prototype=Object.create(Promise.prototype)).constructor=c,c.convert=function(t,e){return t.__proto__=e||c.prototype,t},c.template={prop:{src:null,container:null,overlay:null,canvas:null,img:null,pdf:null,pageSize:null,callback:function(){}},progress:{val:0,state:null,n:0,stack:[]},opt:{filename:"file.pdf",margin:[0,0,0,0],enableLinks:!0,x:0,y:0,html2canvas:{},jsPDF:{},backgroundColor:"transparent"}},c.prototype.from=function(t,e){return this.then((function(){switch(e=e||function(t){switch(o(t)){case"string":return"string";case"element":return"canvas"===t.nodeName.toLowerCase()?"canvas":"element";default:return"unknown"}}(t)){case"string":return this.then(a).then((function(e){return this.set({src:s("div",{innerHTML:t,dompurify:e})})}));case"element":return this.set({src:t});case"canvas":return this.set({canvas:t});case"img":return this.set({img:t});default:return this.error("Unknown source type.")}}))},c.prototype.to=function(t){switch(t){case"container":return this.toContainer();case"canvas":return this.toCanvas();case"img":return this.toImg();case"pdf":return this.toPdf();default:return this.error("Invalid target.")}},c.prototype.toContainer=function(){return this.thenList([function(){return this.prop.src||this.error("Cannot duplicate - no source HTML.")},function(){return this.prop.pageSize||this.setPageSize()}]).then((function(){var t={position:"relative",display:"inline-block",width:("number"!=typeof this.opt.width||isNaN(this.opt.width)||"number"!=typeof this.opt.windowWidth||isNaN(this.opt.windowWidth)?Math.max(this.prop.src.clientWidth,this.prop.src.scrollWidth,this.prop.src.offsetWidth):this.opt.windowWidth)+"px",left:0,right:0,top:0,margin:"auto",backgroundColor:this.opt.backgroundColor},e=function t(e,r){for(var n=3===e.nodeType?document.createTextNode(e.nodeValue):e.cloneNode(!1),i=e.firstChild;i;i=i.nextSibling)!0!==r&&1===i.nodeType&&"SCRIPT"===i.nodeName||n.appendChild(t(i,r));return 1===e.nodeType&&("CANVAS"===e.nodeName?(n.width=e.width,n.height=e.height,n.getContext("2d").drawImage(e,0,0)):"TEXTAREA"!==e.nodeName&&"SELECT"!==e.nodeName||(n.value=e.value),n.addEventListener("load",(function(){n.scrollTop=e.scrollTop,n.scrollLeft=e.scrollLeft}),!0)),n}(this.prop.src,this.opt.html2canvas.javascriptEnabled);"BODY"===e.tagName&&(t.height=Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)+"px"),this.prop.overlay=s("div",{className:"html2pdf__overlay",style:{position:"fixed",overflow:"hidden",zIndex:1e3,left:"-100000px",right:0,bottom:0,top:0}}),this.prop.container=s("div",{className:"html2pdf__container",style:t}),this.prop.container.appendChild(e),this.prop.container.firstChild.appendChild(s("div",{style:{clear:"both",border:"0 none transparent",margin:0,padding:0,height:0}})),this.prop.container.style.float="none",this.prop.overlay.appendChild(this.prop.container),document.body.appendChild(this.prop.overlay),this.prop.container.firstChild.style.position="relative",this.prop.container.height=Math.max(this.prop.container.firstChild.clientHeight,this.prop.container.firstChild.scrollHeight,this.prop.container.firstChild.offsetHeight)+"px"}))},c.prototype.toCanvas=function(){var t=[function(){return document.body.contains(this.prop.container)||this.toContainer()}];return this.thenList(t).then(i).then((function(t){var e=Object.assign({},this.opt.html2canvas);return delete e.onrendered,t(this.prop.container,e)})).then((function(t){(this.opt.html2canvas.onrendered||function(){})(t),this.prop.canvas=t,document.body.removeChild(this.prop.overlay)}))},c.prototype.toContext2d=function(){var t=[function(){return document.body.contains(this.prop.container)||this.toContainer()}];return this.thenList(t).then(i).then((function(t){var e=this.opt.jsPDF,r=this.opt.fontFaces,n="number"!=typeof this.opt.width||isNaN(this.opt.width)||"number"!=typeof this.opt.windowWidth||isNaN(this.opt.windowWidth)?1:this.opt.width/this.opt.windowWidth,i=Object.assign({async:!0,allowTaint:!0,scale:n,scrollX:this.opt.scrollX||0,scrollY:this.opt.scrollY||0,backgroundColor:"#ffffff",imageTimeout:15e3,logging:!0,proxy:null,removeContainer:!0,foreignObjectRendering:!1,useCORS:!1},this.opt.html2canvas);if(delete i.onrendered,e.context2d.autoPaging=void 0===this.opt.autoPaging||this.opt.autoPaging,e.context2d.posX=this.opt.x,e.context2d.posY=this.opt.y,e.context2d.margin=this.opt.margin,e.context2d.fontFaces=r,r)for(var a=0;a<r.length;++a){var o=r[a],s=o.src.find((function(t){return"truetype"===t.format}));s&&e.addFont(s.url,o.ref.name,o.ref.style)}return i.windowHeight=i.windowHeight||0,i.windowHeight=0==i.windowHeight?Math.max(this.prop.container.clientHeight,this.prop.container.scrollHeight,this.prop.container.offsetHeight):i.windowHeight,e.context2d.save(!0),t(this.prop.container,i)})).then((function(t){this.opt.jsPDF.context2d.restore(!0),(this.opt.html2canvas.onrendered||function(){})(t),this.prop.canvas=t,document.body.removeChild(this.prop.overlay)}))},c.prototype.toImg=function(){return this.thenList([function(){return this.prop.canvas||this.toCanvas()}]).then((function(){var t=this.prop.canvas.toDataURL("image/"+this.opt.image.type,this.opt.image.quality);this.prop.img=document.createElement("img"),this.prop.img.src=t}))},c.prototype.toPdf=function(){return this.thenList([function(){return this.toContext2d()}]).then((function(){this.prop.pdf=this.prop.pdf||this.opt.jsPDF}))},c.prototype.output=function(t,e,r){return"img"===(r=r||"pdf").toLowerCase()||"image"===r.toLowerCase()?this.outputImg(t,e):this.outputPdf(t,e)},c.prototype.outputPdf=function(t,e){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).then((function(){return this.prop.pdf.output(t,e)}))},c.prototype.outputImg=function(t){return this.thenList([function(){return this.prop.img||this.toImg()}]).then((function(){switch(t){case void 0:case"img":return this.prop.img;case"datauristring":case"dataurlstring":return this.prop.img.src;case"datauri":case"dataurl":return document.location.href=this.prop.img.src;default:throw'Image output type "'+t+'" is not supported.'}}))},c.prototype.save=function(t){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).set(t?{filename:t}:null).then((function(){this.prop.pdf.save(this.opt.filename)}))},c.prototype.doCallback=function(){return this.thenList([function(){return this.prop.pdf||this.toPdf()}]).then((function(){this.prop.callback(this.prop.pdf)}))},c.prototype.set=function(t){if("object"!==o(t))return this;var e=Object.keys(t||{}).map((function(e){if(e in c.template.prop)return function(){this.prop[e]=t[e]};switch(e){case"margin":return this.setMargin.bind(this,t.margin);case"jsPDF":return function(){return this.opt.jsPDF=t.jsPDF,this.setPageSize()};case"pageSize":return this.setPageSize.bind(this,t.pageSize);default:return function(){this.opt[e]=t[e]}}}),this);return this.then((function(){return this.thenList(e)}))},c.prototype.get=function(t,e){return this.then((function(){var r=t in c.template.prop?this.prop[t]:this.opt[t];return e?e(r):r}))},c.prototype.setMargin=function(t){return this.then((function(){switch(o(t)){case"number":t=[t,t,t,t];case"array":if(2===t.length&&(t=[t[0],t[1],t[0],t[1]]),4===t.length)break;default:return this.error("Invalid margin array.")}this.opt.margin=t})).then(this.setPageSize)},c.prototype.setPageSize=function(t){function e(t,e){return Math.floor(t*e/72*96)}return this.then((function(){(t=t||M.getPageSize(this.opt.jsPDF)).hasOwnProperty("inner")||(t.inner={width:t.width-this.opt.margin[1]-this.opt.margin[3],height:t.height-this.opt.margin[0]-this.opt.margin[2]},t.inner.px={width:e(t.inner.width,t.k),height:e(t.inner.height,t.k)},t.inner.ratio=t.inner.height/t.inner.width),this.prop.pageSize=t}))},c.prototype.setProgress=function(t,e,r,n){return null!=t&&(this.progress.val=t),null!=e&&(this.progress.state=e),null!=r&&(this.progress.n=r),null!=n&&(this.progress.stack=n),this.progress.ratio=this.progress.val/this.progress.state,this},c.prototype.updateProgress=function(t,e,r,n){return this.setProgress(t?this.progress.val+t:null,e||null,r?this.progress.n+r:null,n?this.progress.stack.concat(n):null)},c.prototype.then=function(t,e){var r=this;return this.thenCore(t,e,(function(t,e){return r.updateProgress(null,null,1,[t]),Promise.prototype.then.call(this,(function(e){return r.updateProgress(null,t),e})).then(t,e).then((function(t){return r.updateProgress(1),t}))}))},c.prototype.thenCore=function(t,e,r){r=r||Promise.prototype.then;t&&(t=t.bind(this)),e&&(e=e.bind(this));var n=-1!==Promise.toString().indexOf("[native code]")&&"Promise"===Promise.name?this:c.convert(Object.assign({},this),Promise.prototype),i=r.call(n,t,e);return c.convert(i,this.__proto__)},c.prototype.thenExternal=function(t,e){return Promise.prototype.then.call(this,t,e)},c.prototype.thenList=function(t){var e=this;return t.forEach((function(t){e=e.thenCore(t)})),e},c.prototype.catch=function(t){t&&(t=t.bind(this));var e=Promise.prototype.catch.call(this,t);return c.convert(e,this)},c.prototype.catchExternal=function(t){return Promise.prototype.catch.call(this,t)},c.prototype.error=function(t){return this.then((function(){throw new Error(t)}))},c.prototype.using=c.prototype.set,c.prototype.saveAs=c.prototype.save,c.prototype.export=c.prototype.output,c.prototype.run=c.prototype.then,M.getPageSize=function(t,r,n){if("object"===e(t)){var i=t;t=i.orientation,r=i.unit||r,n=i.format||n}r=r||"mm",n=n||"a4",t=(""+(t||"P")).toLowerCase();var a,o=(""+n).toLowerCase(),s={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};switch(r){case"pt":a=1;break;case"mm":a=72/25.4;break;case"cm":a=72/2.54;break;case"in":a=72;break;case"px":a=.75;break;case"pc":case"em":a=12;break;case"ex":a=6;break;default:throw"Invalid unit: "+r}var c,u=0,l=0;if(s.hasOwnProperty(o))u=s[o][1]/a,l=s[o][0]/a;else try{u=n[1],l=n[0]}catch(t){throw new Error("Invalid format: "+n)}if("p"===t||"portrait"===t)t="p",l>u&&(c=l,l=u,u=c);else{if("l"!==t&&"landscape"!==t)throw"Invalid orientation: "+t;t="l",u>l&&(c=l,l=u,u=c)}return{width:l,height:u,unit:r,k:a,orientation:t}},n.html=function(t,e){(e=e||{}).callback=e.callback||function(){},e.html2canvas=e.html2canvas||{},e.html2canvas.canvas=e.html2canvas.canvas||this.canvas,e.jsPDF=e.jsPDF||this,e.fontFaces=e.fontFaces?e.fontFaces.map(Ct):null;var r=new c(e);return e.worker?r:r.from(t).doCallback()}}(M.API), -/** - * @license - * ==================================================================== - * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){var e,r,n;t.addJS=function(t){return n=t,this.internal.events.subscribe("postPutResources",(function(){e=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/Names [(EmbeddedJS) "+(e+1)+" 0 R]"),this.internal.out(">>"),this.internal.out("endobj"),r=this.internal.newObject(),this.internal.out("<<"),this.internal.out("/S /JavaScript"),this.internal.out("/JS ("+n+")"),this.internal.out(">>"),this.internal.out("endobj")})),this.internal.events.subscribe("putCatalog",(function(){void 0!==e&&void 0!==r&&this.internal.out("/Names <</JavaScript "+e+" 0 R>>")})),this}}(M.API), -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e;t.events.push(["postPutResources",function(){var t=this,r=/^(\d+) 0 obj$/;if(this.outline.root.children.length>0)for(var n=t.outline.render().split(/\r\n/),i=0;i<n.length;i++){var a=n[i],o=r.exec(a);if(null!=o){var s=o[1];t.internal.newObjectDeferredBegin(s,!1)}t.internal.write(a)}if(this.outline.createNamedDestinations){var c=this.internal.pages.length,u=[];for(i=0;i<c;i++){var l=t.internal.newObject();u.push(l);var h=t.internal.getPageInfo(i+1);t.internal.write("<< /D["+h.objId+" 0 R /XYZ null null null]>> endobj")}var f=t.internal.newObject();t.internal.write("<< /Names [ ");for(i=0;i<u.length;i++)t.internal.write("(page_"+(i+1)+")"+u[i]+" 0 R");t.internal.write(" ] >>","endobj"),e=t.internal.newObject(),t.internal.write("<< /Dests "+f+" 0 R"),t.internal.write(">>","endobj")}}]),t.events.push(["putCatalog",function(){this.outline.root.children.length>0&&(this.internal.write("/Outlines",this.outline.makeRef(this.outline.root)),this.outline.createNamedDestinations&&this.internal.write("/Names "+e+" 0 R"))}]),t.events.push(["initialized",function(){var t=this;t.outline={createNamedDestinations:!1,root:{children:[]}},t.outline.add=function(t,e,r){var n={title:e,options:r,children:[]};return null==t&&(t=this.root),t.children.push(n),n},t.outline.render=function(){return this.ctx={},this.ctx.val="",this.ctx.pdf=t,this.genIds_r(this.root),this.renderRoot(this.root),this.renderItems(this.root),this.ctx.val},t.outline.genIds_r=function(e){e.id=t.internal.newObjectDeferred();for(var r=0;r<e.children.length;r++)this.genIds_r(e.children[r])},t.outline.renderRoot=function(t){this.objStart(t),this.line("/Type /Outlines"),t.children.length>0&&(this.line("/First "+this.makeRef(t.children[0])),this.line("/Last "+this.makeRef(t.children[t.children.length-1]))),this.line("/Count "+this.count_r({count:0},t)),this.objEnd()},t.outline.renderItems=function(e){for(var r=this.ctx.pdf.internal.getVerticalCoordinateString,n=0;n<e.children.length;n++){var i=e.children[n];this.objStart(i),this.line("/Title "+this.makeString(i.title)),this.line("/Parent "+this.makeRef(e)),n>0&&this.line("/Prev "+this.makeRef(e.children[n-1])),n<e.children.length-1&&this.line("/Next "+this.makeRef(e.children[n+1])),i.children.length>0&&(this.line("/First "+this.makeRef(i.children[0])),this.line("/Last "+this.makeRef(i.children[i.children.length-1])));var a=this.count=this.count_r({count:0},i);if(a>0&&this.line("/Count "+a),i.options&&i.options.pageNumber){var o=t.internal.getPageInfo(i.options.pageNumber);this.line("/Dest ["+o.objId+" 0 R /XYZ 0 "+r(0)+" 0]")}this.objEnd()}for(var s=0;s<e.children.length;s++)this.renderItems(e.children[s])},t.outline.line=function(t){this.ctx.val+=t+"\r\n"},t.outline.makeRef=function(t){return t.id+" 0 R"},t.outline.makeString=function(e){return"("+t.internal.pdfEscape(e)+")"},t.outline.objStart=function(t){this.ctx.val+="\r\n"+t.id+" 0 obj\r\n<<\r\n"},t.outline.objEnd=function(){this.ctx.val+=">> \r\nendobj\r\n"},t.outline.count_r=function(t,e){for(var r=0;r<e.children.length;r++)t.count++,this.count_r(t,e.children[r]);return t.count}}])}(M.API), -/** - * @license - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=[192,193,194,195,196,197,198,199];t.processJPEG=function(t,r,n,i,a,o){var s,c=this.decode.DCT_DECODE,u=null;if("string"==typeof t||this.__addimage__.isArrayBuffer(t)||this.__addimage__.isArrayBufferView(t)){switch(t=a||t,t=this.__addimage__.isArrayBuffer(t)?new Uint8Array(t):t,(s=function(t){for(var r,n=256*t.charCodeAt(4)+t.charCodeAt(5),i=t.length,a={width:0,height:0,numcomponents:1},o=4;o<i;o+=2){if(o+=n,-1!==e.indexOf(t.charCodeAt(o+1))){r=256*t.charCodeAt(o+5)+t.charCodeAt(o+6),a={width:256*t.charCodeAt(o+7)+t.charCodeAt(o+8),height:r,numcomponents:t.charCodeAt(o+9)};break}n=256*t.charCodeAt(o+2)+t.charCodeAt(o+3)}return a}(t=this.__addimage__.isArrayBufferView(t)?this.__addimage__.arrayBufferToBinaryString(t):t)).numcomponents){case 1:o=this.color_spaces.DEVICE_GRAY;break;case 4:o=this.color_spaces.DEVICE_CMYK;break;case 3:o=this.color_spaces.DEVICE_RGB}u={data:t,width:s.width,height:s.height,colorSpace:o,bitsPerComponent:8,filter:c,index:r,alias:n}}return u}}(M.API);var ke,Fe,Ie,Ce,je,Oe=function(){var t,e,n;function i(t){var e,r,n,i,a,o,s,c,u,l,h,f,d,p;for(this.data=t,this.pos=8,this.palette=[],this.imgData=[],this.transparency={},this.animation=null,this.text={},o=null;;){switch(e=this.readUInt32(),u=function(){var t,e;for(e=[],t=0;t<4;++t)e.push(String.fromCharCode(this.data[this.pos++]));return e}.call(this).join("")){case"IHDR":this.width=this.readUInt32(),this.height=this.readUInt32(),this.bits=this.data[this.pos++],this.colorType=this.data[this.pos++],this.compressionMethod=this.data[this.pos++],this.filterMethod=this.data[this.pos++],this.interlaceMethod=this.data[this.pos++];break;case"acTL":this.animation={numFrames:this.readUInt32(),numPlays:this.readUInt32()||1/0,frames:[]};break;case"PLTE":this.palette=this.read(e);break;case"fcTL":o&&this.animation.frames.push(o),this.pos+=4,o={width:this.readUInt32(),height:this.readUInt32(),xOffset:this.readUInt32(),yOffset:this.readUInt32()},a=this.readUInt16(),i=this.readUInt16()||100,o.delay=1e3*a/i,o.disposeOp=this.data[this.pos++],o.blendOp=this.data[this.pos++],o.data=[];break;case"IDAT":case"fdAT":for("fdAT"===u&&(this.pos+=4,e-=4),t=(null!=o?o.data:void 0)||this.imgData,f=0;0<=e?f<e:f>e;0<=e?++f:--f)t.push(this.data[this.pos++]);break;case"tRNS":switch(this.transparency={},this.colorType){case 3:if(n=this.palette.length/3,this.transparency.indexed=this.read(e),this.transparency.indexed.length>n)throw new Error("More transparent colors than palette size");if((l=n-this.transparency.indexed.length)>0)for(d=0;0<=l?d<l:d>l;0<=l?++d:--d)this.transparency.indexed.push(255);break;case 0:this.transparency.grayscale=this.read(e)[0];break;case 2:this.transparency.rgb=this.read(e)}break;case"tEXt":s=(h=this.read(e)).indexOf(0),c=String.fromCharCode.apply(String,h.slice(0,s)),this.text[c]=String.fromCharCode.apply(String,h.slice(s+1));break;case"IEND":return o&&this.animation.frames.push(o),this.colors=function(){switch(this.colorType){case 0:case 3:case 4:return 1;case 2:case 6:return 3}}.call(this),this.hasAlphaChannel=4===(p=this.colorType)||6===p,r=this.colors+(this.hasAlphaChannel?1:0),this.pixelBitlength=this.bits*r,this.colorSpace=function(){switch(this.colors){case 1:return"DeviceGray";case 3:return"DeviceRGB"}}.call(this),void(this.imgData=new Uint8Array(this.imgData));default:this.pos+=e}if(this.pos+=4,this.pos>this.data.length)throw new Error("Incomplete or corrupt PNG file")}}i.prototype.read=function(t){var e,r;for(r=[],e=0;0<=t?e<t:e>t;0<=t?++e:--e)r.push(this.data[this.pos++]);return r},i.prototype.readUInt32=function(){return this.data[this.pos++]<<24|this.data[this.pos++]<<16|this.data[this.pos++]<<8|this.data[this.pos++]},i.prototype.readUInt16=function(){return this.data[this.pos++]<<8|this.data[this.pos++]},i.prototype.decodePixels=function(t){var e=this.pixelBitlength/8,r=new Uint8Array(this.width*this.height*e),n=0,i=this;if(null==t&&(t=this.imgData),0===t.length)return new Uint8Array(0);function a(a,o,s,c){var u,l,h,f,d,p,g,m,v,b,y,w,N,L,A,x,S,_,P,k,F,I=Math.ceil((i.width-a)/s),C=Math.ceil((i.height-o)/c),j=i.width==I&&i.height==C;for(L=e*I,w=j?r:new Uint8Array(L*C),p=t.length,N=0,l=0;N<C&&n<p;){switch(t[n++]){case 0:for(f=S=0;S<L;f=S+=1)w[l++]=t[n++];break;case 1:for(f=_=0;_<L;f=_+=1)u=t[n++],d=f<e?0:w[l-e],w[l++]=(u+d)%256;break;case 2:for(f=P=0;P<L;f=P+=1)u=t[n++],h=(f-f%e)/e,A=N&&w[(N-1)*L+h*e+f%e],w[l++]=(A+u)%256;break;case 3:for(f=k=0;k<L;f=k+=1)u=t[n++],h=(f-f%e)/e,d=f<e?0:w[l-e],A=N&&w[(N-1)*L+h*e+f%e],w[l++]=(u+Math.floor((d+A)/2))%256;break;case 4:for(f=F=0;F<L;f=F+=1)u=t[n++],h=(f-f%e)/e,d=f<e?0:w[l-e],0===N?A=x=0:(A=w[(N-1)*L+h*e+f%e],x=h&&w[(N-1)*L+(h-1)*e+f%e]),g=d+A-x,m=Math.abs(g-d),b=Math.abs(g-A),y=Math.abs(g-x),v=m<=b&&m<=y?d:b<=y?A:x,w[l++]=(u+v)%256;break;default:throw new Error("Invalid filter algorithm: "+t[n-1])}if(!j){var O=((o+N*c)*i.width+a)*e,B=N*L;for(f=0;f<I;f+=1){for(var M=0;M<e;M+=1)r[O++]=w[B++];O+=(s-1)*e}}N++}}return t=Pe(t),1==i.interlaceMethod?(a(0,0,8,8),a(4,0,8,8),a(0,4,4,8),a(2,0,4,4),a(0,2,2,4),a(1,0,2,2),a(0,1,1,2)):a(0,0,1,1),r},i.prototype.decodePalette=function(){var t,e,r,n,i,a,o,s,c;for(r=this.palette,a=this.transparency.indexed||[],i=new Uint8Array((a.length||0)+r.length),n=0,t=0,e=o=0,s=r.length;o<s;e=o+=3)i[n++]=r[e],i[n++]=r[e+1],i[n++]=r[e+2],i[n++]=null!=(c=a[t++])?c:255;return i},i.prototype.copyToImageData=function(t,e){var r,n,i,a,o,s,c,u,l,h,f;if(n=this.colors,l=null,r=this.hasAlphaChannel,this.palette.length&&(l=null!=(f=this._decodedPalette)?f:this._decodedPalette=this.decodePalette(),n=4,r=!0),u=(i=t.data||t).length,o=l||e,a=s=0,1===n)for(;a<u;)c=l?4*e[a/4]:s,h=o[c++],i[a++]=h,i[a++]=h,i[a++]=h,i[a++]=r?o[c++]:255,s=c;else for(;a<u;)c=l?4*e[a/4]:s,i[a++]=o[c++],i[a++]=o[c++],i[a++]=o[c++],i[a++]=r?o[c++]:255,s=c},i.prototype.decode=function(){var t;return t=new Uint8Array(this.width*this.height*4),this.copyToImageData(t,this.decodePixels()),t};var a=function(){if("[object Window]"===Object.prototype.toString.call(r)){try{e=r.document.createElement("canvas"),n=e.getContext("2d")}catch(t){return!1}return!0}return!1};return a(),t=function(t){var r;if(!0===a())return n.width=t.width,n.height=t.height,n.clearRect(0,0,t.width,t.height),n.putImageData(t,0,0),(r=new Image).src=e.toDataURL(),r;throw new Error("This method requires a Browser with Canvas-capability.")},i.prototype.decodeFrames=function(e){var r,n,i,a,o,s,c,u;if(this.animation){for(u=[],n=o=0,s=(c=this.animation.frames).length;o<s;n=++o)r=c[n],i=e.createImageData(r.width,r.height),a=this.decodePixels(new Uint8Array(r.data)),this.copyToImageData(i,a),r.imageData=i,u.push(r.image=t(i));return u}},i.prototype.renderFrame=function(t,e){var r,n,i;return r=(n=this.animation.frames)[e],i=n[e-1],0===e&&t.clearRect(0,0,this.width,this.height),1===(null!=i?i.disposeOp:void 0)?t.clearRect(i.xOffset,i.yOffset,i.width,i.height):2===(null!=i?i.disposeOp:void 0)&&t.putImageData(i.imageData,i.xOffset,i.yOffset),0===r.blendOp&&t.clearRect(r.xOffset,r.yOffset,r.width,r.height),t.drawImage(r.image,r.xOffset,r.yOffset)},i.prototype.animate=function(t){var e,r,n,i,a,o,s=this;return r=0,o=this.animation,i=o.numFrames,n=o.frames,a=o.numPlays,(e=function(){var o,c;if(o=r++%i,c=n[o],s.renderFrame(t,o),i>1&&r/i<a)return s.animation._timeout=setTimeout(e,c.delay)})()},i.prototype.stopAnimation=function(){var t;return clearTimeout(null!=(t=this.animation)?t._timeout:void 0)},i.prototype.render=function(t){var e,r;return t._png&&t._png.stopAnimation(),t._png=this,t.width=this.width,t.height=this.height,e=t.getContext("2d"),this.animation?(this.decodeFrames(e),this.animate(e)):(r=e.createImageData(this.width,this.height),this.copyToImageData(r,this.decodePixels()),e.putImageData(r,0,0))},i}(); -/** - * @license - * - * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -/** - * @license - * (c) Dean McNamee <dean@gmail.com>, 2013. - * - * https://github.com/deanm/omggif - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - * omggif is a JavaScript implementation of a GIF 89a encoder and decoder, - * including animation and compression. It does not rely on any specific - * underlying system, so should run in the browser, Node, or Plask. - */ -function Be(t){var e=0;if(71!==t[e++]||73!==t[e++]||70!==t[e++]||56!==t[e++]||56!=(t[e++]+1&253)||97!==t[e++])throw new Error("Invalid GIF 87a/89a header.");var r=t[e++]|t[e++]<<8,n=t[e++]|t[e++]<<8,i=t[e++],a=i>>7,o=1<<(7&i)+1;t[e++];t[e++];var s=null,c=null;a&&(s=e,c=o,e+=3*o);var u=!0,l=[],h=0,f=null,d=0,p=null;for(this.width=r,this.height=n;u&&e<t.length;)switch(t[e++]){case 33:switch(t[e++]){case 255:if(11!==t[e]||78==t[e+1]&&69==t[e+2]&&84==t[e+3]&&83==t[e+4]&&67==t[e+5]&&65==t[e+6]&&80==t[e+7]&&69==t[e+8]&&50==t[e+9]&&46==t[e+10]&&48==t[e+11]&&3==t[e+12]&&1==t[e+13]&&0==t[e+16])e+=14,p=t[e++]|t[e++]<<8,e++;else for(e+=12;;){if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}break;case 249:if(4!==t[e++]||0!==t[e+4])throw new Error("Invalid graphics extension block.");var g=t[e++];h=t[e++]|t[e++]<<8,f=t[e++],0==(1&g)&&(f=null),d=g>>2&7,e++;break;case 254:for(;;){if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}break;default:throw new Error("Unknown graphic control label: 0x"+t[e-1].toString(16))}break;case 44:var m=t[e++]|t[e++]<<8,v=t[e++]|t[e++]<<8,b=t[e++]|t[e++]<<8,y=t[e++]|t[e++]<<8,w=t[e++],N=w>>6&1,L=1<<(7&w)+1,A=s,x=c,S=!1;if(w>>7){S=!0;A=e,x=L,e+=3*L}var _=e;for(e++;;){var P;if(!((P=t[e++])>=0))throw Error("Invalid block size");if(0===P)break;e+=P}l.push({x:m,y:v,width:b,height:y,has_local_palette:S,palette_offset:A,palette_size:x,data_offset:_,data_length:e-_,transparent_index:f,interlaced:!!N,delay:h,disposal:d});break;case 59:u=!1;break;default:throw new Error("Unknown gif block: 0x"+t[e-1].toString(16))}this.numFrames=function(){return l.length},this.loopCount=function(){return p},this.frameInfo=function(t){if(t<0||t>=l.length)throw new Error("Frame index out of range.");return l[t]},this.decodeAndBlitFrameBGRA=function(e,n){var i=this.frameInfo(e),a=i.width*i.height,o=new Uint8Array(a);Me(t,i.data_offset,o,a);var s=i.palette_offset,c=i.transparent_index;null===c&&(c=256);var u=i.width,l=r-u,h=u,f=4*(i.y*r+i.x),d=4*((i.y+i.height)*r+i.x),p=f,g=4*l;!0===i.interlaced&&(g+=4*r*7);for(var m=8,v=0,b=o.length;v<b;++v){var y=o[v];if(0===h&&(h=u,(p+=g)>=d&&(g=4*l+4*r*(m-1),p=f+(u+l)*(m<<1),m>>=1)),y===c)p+=4;else{var w=t[s+3*y],N=t[s+3*y+1],L=t[s+3*y+2];n[p++]=L,n[p++]=N,n[p++]=w,n[p++]=255}--h}},this.decodeAndBlitFrameRGBA=function(e,n){var i=this.frameInfo(e),a=i.width*i.height,o=new Uint8Array(a);Me(t,i.data_offset,o,a);var s=i.palette_offset,c=i.transparent_index;null===c&&(c=256);var u=i.width,l=r-u,h=u,f=4*(i.y*r+i.x),d=4*((i.y+i.height)*r+i.x),p=f,g=4*l;!0===i.interlaced&&(g+=4*r*7);for(var m=8,v=0,b=o.length;v<b;++v){var y=o[v];if(0===h&&(h=u,(p+=g)>=d&&(g=4*l+4*r*(m-1),p=f+(u+l)*(m<<1),m>>=1)),y===c)p+=4;else{var w=t[s+3*y],N=t[s+3*y+1],L=t[s+3*y+2];n[p++]=w,n[p++]=N,n[p++]=L,n[p++]=255}--h}}}function Me(t,e,r,n){for(var a=t[e++],o=1<<a,s=o+1,c=s+1,u=a+1,l=(1<<u)-1,h=0,f=0,d=0,p=t[e++],g=new Int32Array(4096),m=null;;){for(;h<16&&0!==p;)f|=t[e++]<<h,h+=8,1===p?p=t[e++]:--p;if(h<u)break;var v=f&l;if(f>>=u,h-=u,v!==o){if(v===s)break;for(var b=v<c?v:m,y=0,w=b;w>o;)w=g[w]>>8,++y;var N=w;if(d+y+(b!==v?1:0)>n)return void i.log("Warning, gif stream longer than expected.");r[d++]=N;var L=d+=y;for(b!==v&&(r[d++]=N),w=b;y--;)w=g[w],r[--L]=255&w,w>>=8;null!==m&&c<4096&&(g[c++]=m<<8|N,c>=l+1&&u<12&&(++u,l=l<<1|1)),m=v}else c=s+1,l=(1<<(u=a+1))-1,m=null}return d!==n&&i.log("Warning, gif stream shorter than expected."),r} -/** - * @license - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */function Ee(t){var e,r,n,i,a,o=Math.floor,s=new Array(64),c=new Array(64),u=new Array(64),l=new Array(64),h=new Array(65535),f=new Array(65535),d=new Array(64),p=new Array(64),g=[],m=0,v=7,b=new Array(64),y=new Array(64),w=new Array(64),N=new Array(256),L=new Array(2048),A=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],x=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],S=[0,1,2,3,4,5,6,7,8,9,10,11],_=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],P=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],k=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],F=[0,1,2,3,4,5,6,7,8,9,10,11],I=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],C=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];function j(t,e){for(var r=0,n=0,i=new Array,a=1;a<=16;a++){for(var o=1;o<=t[a];o++)i[e[n]]=[],i[e[n]][0]=r,i[e[n]][1]=a,n++,r++;r*=2}return i}function O(t){for(var e=t[0],r=t[1]-1;r>=0;)e&1<<r&&(m|=1<<v),r--,--v<0&&(255==m?(B(255),B(0)):B(m),v=7,m=0)}function B(t){g.push(t)}function M(t){B(t>>8&255),B(255&t)}function E(t,e,r,n,i){for(var a,o=i[0],s=i[240],c=function(t,e){var r,n,i,a,o,s,c,u,l,h,f=0;for(l=0;l<8;++l){r=t[f],n=t[f+1],i=t[f+2],a=t[f+3],o=t[f+4],s=t[f+5],c=t[f+6];var p=r+(u=t[f+7]),g=r-u,m=n+c,v=n-c,b=i+s,y=i-s,w=a+o,N=a-o,L=p+w,A=p-w,x=m+b,S=m-b;t[f]=L+x,t[f+4]=L-x;var _=.707106781*(S+A);t[f+2]=A+_,t[f+6]=A-_;var P=.382683433*((L=N+y)-(S=v+g)),k=.5411961*L+P,F=1.306562965*S+P,I=.707106781*(x=y+v),C=g+I,j=g-I;t[f+5]=j+k,t[f+3]=j-k,t[f+1]=C+F,t[f+7]=C-F,f+=8}for(f=0,l=0;l<8;++l){r=t[f],n=t[f+8],i=t[f+16],a=t[f+24],o=t[f+32],s=t[f+40],c=t[f+48];var O=r+(u=t[f+56]),B=r-u,M=n+c,E=n-c,q=i+s,D=i-s,R=a+o,T=a-o,U=O+R,z=O-R,H=M+q,W=M-q;t[f]=U+H,t[f+32]=U-H;var V=.707106781*(W+z);t[f+16]=z+V,t[f+48]=z-V;var G=.382683433*((U=T+D)-(W=E+B)),Y=.5411961*U+G,J=1.306562965*W+G,X=.707106781*(H=D+E),K=B+X,Z=B-X;t[f+40]=Z+Y,t[f+24]=Z-Y,t[f+8]=K+J,t[f+56]=K-J,f++}for(l=0;l<64;++l)h=t[l]*e[l],d[l]=h>0?h+.5|0:h-.5|0;return d}(t,e),u=0;u<64;++u)p[A[u]]=c[u];var l=p[0]-r;r=p[0],0==l?O(n[0]):(O(n[f[a=32767+l]]),O(h[a]));for(var g=63;g>0&&0==p[g];)g--;if(0==g)return O(o),r;for(var m,v=1;v<=g;){for(var b=v;0==p[v]&&v<=g;)++v;var y=v-b;if(y>=16){m=y>>4;for(var w=1;w<=m;++w)O(s);y&=15}a=32767+p[v],O(i[(y<<4)+f[a]]),O(h[a]),v++}return 63!=g&&O(o),r}function q(t){(t=Math.min(Math.max(t,1),100),a!=t)&&(!function(t){for(var e=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],r=0;r<64;r++){var n=o((e[r]*t+50)/100);n=Math.min(Math.max(n,1),255),s[A[r]]=n}for(var i=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],a=0;a<64;a++){var h=o((i[a]*t+50)/100);h=Math.min(Math.max(h,1),255),c[A[a]]=h}for(var f=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],d=0,p=0;p<8;p++)for(var g=0;g<8;g++)u[d]=1/(s[A[d]]*f[p]*f[g]*8),l[d]=1/(c[A[d]]*f[p]*f[g]*8),d++}(t<50?Math.floor(5e3/t):Math.floor(200-2*t)),a=t)}this.encode=function(t,a){a&&q(a),g=new Array,m=0,v=7,M(65496),M(65504),M(16),B(74),B(70),B(73),B(70),B(0),B(1),B(1),B(0),M(1),M(1),B(0),B(0),function(){M(65499),M(132),B(0);for(var t=0;t<64;t++)B(s[t]);B(1);for(var e=0;e<64;e++)B(c[e])}(),function(t,e){M(65472),M(17),B(8),M(e),M(t),B(3),B(1),B(17),B(0),B(2),B(17),B(1),B(3),B(17),B(1)}(t.width,t.height),function(){M(65476),M(418),B(0);for(var t=0;t<16;t++)B(x[t+1]);for(var e=0;e<=11;e++)B(S[e]);B(16);for(var r=0;r<16;r++)B(_[r+1]);for(var n=0;n<=161;n++)B(P[n]);B(1);for(var i=0;i<16;i++)B(k[i+1]);for(var a=0;a<=11;a++)B(F[a]);B(17);for(var o=0;o<16;o++)B(I[o+1]);for(var s=0;s<=161;s++)B(C[s])}(),M(65498),M(12),B(3),B(1),B(0),B(2),B(17),B(3),B(17),B(0),B(63),B(0);var o=0,h=0,f=0;m=0,v=7,this.encode.displayName="_encode_";for(var d,p,N,A,j,D,R,T,U,z=t.data,H=t.width,W=t.height,V=4*H,G=0;G<W;){for(d=0;d<V;){for(j=V*G+d,R=-1,T=0,U=0;U<64;U++)D=j+(T=U>>3)*V+(R=4*(7&U)),G+T>=W&&(D-=V*(G+1+T-W)),d+R>=V&&(D-=d+R-V+4),p=z[D++],N=z[D++],A=z[D++],b[U]=(L[p]+L[N+256>>0]+L[A+512>>0]>>16)-128,y[U]=(L[p+768>>0]+L[N+1024>>0]+L[A+1280>>0]>>16)-128,w[U]=(L[p+1280>>0]+L[N+1536>>0]+L[A+1792>>0]>>16)-128;o=E(b,u,o,e,n),h=E(y,l,h,r,i),f=E(w,l,f,r,i),d+=32}G+=8}if(v>=0){var Y=[];Y[1]=v+1,Y[0]=(1<<v+1)-1,O(Y)}return M(65497),new Uint8Array(g)},t=t||50,function(){for(var t=String.fromCharCode,e=0;e<256;e++)N[e]=t(e)}(),e=j(x,S),r=j(k,F),n=j(_,P),i=j(I,C),function(){for(var t=1,e=2,r=1;r<=15;r++){for(var n=t;n<e;n++)f[32767+n]=r,h[32767+n]=[],h[32767+n][1]=r,h[32767+n][0]=n;for(var i=-(e-1);i<=-t;i++)f[32767+i]=r,h[32767+i]=[],h[32767+i][1]=r,h[32767+i][0]=e-1+i;t<<=1,e<<=1}}(),function(){for(var t=0;t<256;t++)L[t]=19595*t,L[t+256>>0]=38470*t,L[t+512>>0]=7471*t+32768,L[t+768>>0]=-11059*t,L[t+1024>>0]=-21709*t,L[t+1280>>0]=32768*t+8421375,L[t+1536>>0]=-27439*t,L[t+1792>>0]=-5329*t}(),q(t)} -/** - * @license - * Copyright (c) 2017 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */function qe(t,e){if(this.pos=0,this.buffer=t,this.datav=new DataView(t.buffer),this.is_with_alpha=!!e,this.bottom_up=!0,this.flag=String.fromCharCode(this.buffer[0])+String.fromCharCode(this.buffer[1]),this.pos+=2,-1===["BM","BA","CI","CP","IC","PT"].indexOf(this.flag))throw new Error("Invalid BMP File");this.parseHeader(),this.parseBGR()}function De(t){function e(t){if(!t)throw Error("assert :P")}function r(t,e,r){for(var n=0;4>n;n++)if(t[e+n]!=r.charCodeAt(n))return!0;return!1}function n(t,e,r,n,i){for(var a=0;a<i;a++)t[e+a]=r[n+a]}function i(t,e,r,n){for(var i=0;i<n;i++)t[e+i]=r}function a(t){return new Int32Array(t)}function o(t,e){for(var r=[],n=0;n<t;n++)r.push(new e);return r}function s(t,e){var r=[];return function t(r,n,i){for(var a=i[n],o=0;o<a&&(r.push(i.length>n+1?[]:new e),!(i.length<n+1));o++)t(r[o],n+1,i)}(r,0,t),r}var c=function(){var t=this;function c(t,e){for(var r=1<<e-1>>>0;t&r;)r>>>=1;return r?(t&r-1)+r:t}function u(t,r,n,i,a){e(!(i%n));do{t[r+(i-=n)]=a}while(0<i)}function l(t,r,n,i,o){if(e(2328>=o),512>=o)var s=a(512);else if(null==(s=a(o)))return 0;return function(t,r,n,i,o,s){var l,f,d=r,p=1<<n,g=a(16),m=a(16);for(e(0!=o),e(null!=i),e(null!=t),e(0<n),f=0;f<o;++f){if(15<i[f])return 0;++g[i[f]]}if(g[0]==o)return 0;for(m[1]=0,l=1;15>l;++l){if(g[l]>1<<l)return 0;m[l+1]=m[l]+g[l]}for(f=0;f<o;++f)l=i[f],0<i[f]&&(s[m[l]++]=f);if(1==m[15])return(i=new h).g=0,i.value=s[0],u(t,d,1,p,i),p;var v,b=-1,y=p-1,w=0,N=1,L=1,A=1<<n;for(f=0,l=1,o=2;l<=n;++l,o<<=1){if(N+=L<<=1,0>(L-=g[l]))return 0;for(;0<g[l];--g[l])(i=new h).g=l,i.value=s[f++],u(t,d+w,o,A,i),w=c(w,l)}for(l=n+1,o=2;15>=l;++l,o<<=1){if(N+=L<<=1,0>(L-=g[l]))return 0;for(;0<g[l];--g[l]){if(i=new h,(w&y)!=b){for(d+=A,v=1<<(b=l)-n;15>b&&!(0>=(v-=g[b]));)++b,v<<=1;p+=A=1<<(v=b-n),t[r+(b=w&y)].g=v+n,t[r+b].value=d-r-b}i.g=l-n,i.value=s[f++],u(t,d+(w>>n),o,A,i),w=c(w,l)}}return N!=2*m[15]-1?0:p}(t,r,n,i,o,s)}function h(){this.value=this.g=0}function f(){this.value=this.g=0}function d(){this.G=o(5,h),this.H=a(5),this.jc=this.Qb=this.qb=this.nd=0,this.pd=o(Dr,f)}function p(t,r,n,i){e(null!=t),e(null!=r),e(2147483648>i),t.Ca=254,t.I=0,t.b=-8,t.Ka=0,t.oa=r,t.pa=n,t.Jd=r,t.Yc=n+i,t.Zc=4<=i?n+i-4+1:n,_(t)}function g(t,e){for(var r=0;0<e--;)r|=k(t,128)<<e;return r}function m(t,e){var r=g(t,e);return P(t)?-r:r}function v(t,r,n,i){var a,o=0;for(e(null!=t),e(null!=r),e(4294967288>i),t.Sb=i,t.Ra=0,t.u=0,t.h=0,4<i&&(i=4),a=0;a<i;++a)o+=r[n+a]<<8*a;t.Ra=o,t.bb=i,t.oa=r,t.pa=n}function b(t){for(;8<=t.u&&t.bb<t.Sb;)t.Ra>>>=8,t.Ra+=t.oa[t.pa+t.bb]<<Ur-8>>>0,++t.bb,t.u-=8;A(t)&&(t.h=1,t.u=0)}function y(t,r){if(e(0<=r),!t.h&&r<=Tr){var n=L(t)&Rr[r];return t.u+=r,b(t),n}return t.h=1,t.u=0}function w(){this.b=this.Ca=this.I=0,this.oa=[],this.pa=0,this.Jd=[],this.Yc=0,this.Zc=[],this.Ka=0}function N(){this.Ra=0,this.oa=[],this.h=this.u=this.bb=this.Sb=this.pa=0}function L(t){return t.Ra>>>(t.u&Ur-1)>>>0}function A(t){return e(t.bb<=t.Sb),t.h||t.bb==t.Sb&&t.u>Ur}function x(t,e){t.u=e,t.h=A(t)}function S(t){t.u>=zr&&(e(t.u>=zr),b(t))}function _(t){e(null!=t&&null!=t.oa),t.pa<t.Zc?(t.I=(t.oa[t.pa++]|t.I<<8)>>>0,t.b+=8):(e(null!=t&&null!=t.oa),t.pa<t.Yc?(t.b+=8,t.I=t.oa[t.pa++]|t.I<<8):t.Ka?t.b=0:(t.I<<=8,t.b+=8,t.Ka=1))}function P(t){return g(t,1)}function k(t,e){var r=t.Ca;0>t.b&&_(t);var n=t.b,i=r*e>>>8,a=(t.I>>>n>i)+0;for(a?(r-=i,t.I-=i+1<<n>>>0):r=i+1,n=r,i=0;256<=n;)i+=8,n>>=8;return n=7^i+Hr[n],t.b-=n,t.Ca=(r<<n)-1,a}function F(t,e,r){t[e+0]=r>>24&255,t[e+1]=r>>16&255,t[e+2]=r>>8&255,t[e+3]=r>>0&255}function I(t,e){return t[e+0]<<0|t[e+1]<<8}function C(t,e){return I(t,e)|t[e+2]<<16}function j(t,e){return I(t,e)|I(t,e+2)<<16}function O(t,r){var n=1<<r;return e(null!=t),e(0<r),t.X=a(n),null==t.X?0:(t.Mb=32-r,t.Xa=r,1)}function B(t,r){e(null!=t),e(null!=r),e(t.Xa==r.Xa),n(r.X,0,t.X,0,1<<r.Xa)}function M(){this.X=[],this.Xa=this.Mb=0}function E(t,r,n,i){e(null!=n),e(null!=i);var a=n[0],o=i[0];return 0==a&&(a=(t*o+r/2)/r),0==o&&(o=(r*a+t/2)/t),0>=a||0>=o?0:(n[0]=a,i[0]=o,1)}function q(t,e){return t+(1<<e)-1>>>e}function D(t,e){return((4278255360&t)+(4278255360&e)>>>0&4278255360)+((16711935&t)+(16711935&e)>>>0&16711935)>>>0}function R(e,r){t[r]=function(r,n,i,a,o,s,c){var u;for(u=0;u<o;++u){var l=t[e](s[c+u-1],i,a+u);s[c+u]=D(r[n+u],l)}}}function T(){this.ud=this.hd=this.jd=0}function U(t,e){return((4278124286&(t^e))>>>1)+(t&e)>>>0}function z(t){return 0<=t&&256>t?t:0>t?0:255<t?255:void 0}function H(t,e){return z(t+(t-e+.5>>1))}function W(t,e,r){return Math.abs(e-r)-Math.abs(t-r)}function V(t,e,r,n,i,a,o){for(n=a[o-1],r=0;r<i;++r)a[o+r]=n=D(t[e+r],n)}function G(t,e,r,n,i){var a;for(a=0;a<r;++a){var o=t[e+a],s=o>>8&255,c=16711935&(c=(c=16711935&o)+((s<<16)+s));n[i+a]=(4278255360&o)+c>>>0}}function Y(t,e){e.jd=t>>0&255,e.hd=t>>8&255,e.ud=t>>16&255}function J(t,e,r,n,i,a){var o;for(o=0;o<n;++o){var s=e[r+o],c=s>>>8,u=s,l=255&(l=(l=s>>>16)+((t.jd<<24>>24)*(c<<24>>24)>>>5));u=255&(u=(u=u+((t.hd<<24>>24)*(c<<24>>24)>>>5))+((t.ud<<24>>24)*(l<<24>>24)>>>5));i[a+o]=(4278255360&s)+(l<<16)+u}}function X(e,r,n,i,a){t[r]=function(t,e,r,n,o,s,c,u,l){for(n=c;n<u;++n)for(c=0;c<l;++c)o[s++]=a(r[i(t[e++])])},t[e]=function(e,r,o,s,c,u,l){var h=8>>e.b,f=e.Ea,d=e.K[0],p=e.w;if(8>h)for(e=(1<<e.b)-1,p=(1<<h)-1;r<o;++r){var g,m=0;for(g=0;g<f;++g)g&e||(m=i(s[c++])),u[l++]=a(d[m&p]),m>>=h}else t["VP8LMapColor"+n](s,c,d,p,u,l,r,o,f)}}function K(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>16&255,n[i++]=a>>8&255,n[i++]=a>>0&255}}function Z(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>16&255,n[i++]=a>>8&255,n[i++]=a>>0&255,n[i++]=a>>24&255}}function $(t,e,r,n,i){for(r=e+r;e<r;){var a=(o=t[e++])>>16&240|o>>12&15,o=o>>0&240|o>>28&15;n[i++]=a,n[i++]=o}}function Q(t,e,r,n,i){for(r=e+r;e<r;){var a=(o=t[e++])>>16&248|o>>13&7,o=o>>5&224|o>>3&31;n[i++]=a,n[i++]=o}}function tt(t,e,r,n,i){for(r=e+r;e<r;){var a=t[e++];n[i++]=a>>0&255,n[i++]=a>>8&255,n[i++]=a>>16&255}}function et(t,e,r,i,a,o){if(0==o)for(r=e+r;e<r;)F(i,((o=t[e++])[0]>>24|o[1]>>8&65280|o[2]<<8&16711680|o[3]<<24)>>>0),a+=32;else n(i,a,t,e,r)}function rt(e,r){t[r][0]=t[e+"0"],t[r][1]=t[e+"1"],t[r][2]=t[e+"2"],t[r][3]=t[e+"3"],t[r][4]=t[e+"4"],t[r][5]=t[e+"5"],t[r][6]=t[e+"6"],t[r][7]=t[e+"7"],t[r][8]=t[e+"8"],t[r][9]=t[e+"9"],t[r][10]=t[e+"10"],t[r][11]=t[e+"11"],t[r][12]=t[e+"12"],t[r][13]=t[e+"13"],t[r][14]=t[e+"0"],t[r][15]=t[e+"0"]}function nt(t){return t==Hn||t==Wn||t==Vn||t==Gn}function it(){this.eb=[],this.size=this.A=this.fb=0}function at(){this.y=[],this.f=[],this.ea=[],this.F=[],this.Tc=this.Ed=this.Cd=this.Fd=this.lb=this.Db=this.Ab=this.fa=this.J=this.W=this.N=this.O=0}function ot(){this.Rd=this.height=this.width=this.S=0,this.f={},this.f.RGBA=new it,this.f.kb=new at,this.sd=null}function st(){this.width=[0],this.height=[0],this.Pd=[0],this.Qd=[0],this.format=[0]}function ct(){this.Id=this.fd=this.Md=this.hb=this.ib=this.da=this.bd=this.cd=this.j=this.v=this.Da=this.Sd=this.ob=0}function ut(t){return alert("todo:WebPSamplerProcessPlane"),t.T}function lt(t,e){var r=t.T,i=e.ba.f.RGBA,a=i.eb,o=i.fb+t.ka*i.A,s=vi[e.ba.S],c=t.y,u=t.O,l=t.f,h=t.N,f=t.ea,d=t.W,p=e.cc,g=e.dc,m=e.Mc,v=e.Nc,b=t.ka,y=t.ka+t.T,w=t.U,N=w+1>>1;for(0==b?s(c,u,null,null,l,h,f,d,l,h,f,d,a,o,null,null,w):(s(e.ec,e.fc,c,u,p,g,m,v,l,h,f,d,a,o-i.A,a,o,w),++r);b+2<y;b+=2)p=l,g=h,m=f,v=d,h+=t.Rc,d+=t.Rc,o+=2*i.A,s(c,(u+=2*t.fa)-t.fa,c,u,p,g,m,v,l,h,f,d,a,o-i.A,a,o,w);return u+=t.fa,t.j+y<t.o?(n(e.ec,e.fc,c,u,w),n(e.cc,e.dc,l,h,N),n(e.Mc,e.Nc,f,d,N),r--):1&y||s(c,u,null,null,l,h,f,d,l,h,f,d,a,o+i.A,null,null,w),r}function ht(t,r,n){var i=t.F,a=[t.J];if(null!=i){var o=t.U,s=r.ba.S,c=s==Tn||s==Vn;r=r.ba.f.RGBA;var u=[0],l=t.ka;u[0]=t.T,t.Kb&&(0==l?--u[0]:(--l,a[0]-=t.width),t.j+t.ka+t.T==t.o&&(u[0]=t.o-t.j-l));var h=r.eb;l=r.fb+l*r.A;t=Sn(i,a[0],t.width,o,u,h,l+(c?0:3),r.A),e(n==u),t&&nt(s)&&An(h,l,c,o,u,r.A)}return 0}function ft(t){var e=t.ma,r=e.ba.S,n=11>r,i=r==qn||r==Rn||r==Tn||r==Un||12==r||nt(r);if(e.memory=null,e.Ib=null,e.Jb=null,e.Nd=null,!Mr(e.Oa,t,i?11:12))return 0;if(i&&nt(r)&&br(),t.da)alert("todo:use_scaling");else{if(n){if(e.Ib=ut,t.Kb){if(r=t.U+1>>1,e.memory=a(t.U+2*r),null==e.memory)return 0;e.ec=e.memory,e.fc=0,e.cc=e.ec,e.dc=e.fc+t.U,e.Mc=e.cc,e.Nc=e.dc+r,e.Ib=lt,br()}}else alert("todo:EmitYUV");i&&(e.Jb=ht,n&&mr())}if(n&&!Ci){for(t=0;256>t;++t)ji[t]=89858*(t-128)+_i>>Si,Mi[t]=-22014*(t-128)+_i,Bi[t]=-45773*(t-128),Oi[t]=113618*(t-128)+_i>>Si;for(t=Pi;t<ki;++t)e=76283*(t-16)+_i>>Si,Ei[t-Pi]=Vt(e,255),qi[t-Pi]=Vt(e+8>>4,15);Ci=1}return 1}function dt(t){var r=t.ma,n=t.U,i=t.T;return e(!(1&t.ka)),0>=n||0>=i?0:(n=r.Ib(t,r),null!=r.Jb&&r.Jb(t,r,n),r.Dc+=n,1)}function pt(t){t.ma.memory=null}function gt(t,e,r,n){return 47!=y(t,8)?0:(e[0]=y(t,14)+1,r[0]=y(t,14)+1,n[0]=y(t,1),0!=y(t,3)?0:!t.h)}function mt(t,e){if(4>t)return t+1;var r=t-2>>1;return(2+(1&t)<<r)+y(e,r)+1}function vt(t,e){return 120<e?e-120:1<=(r=((r=$n[e-1])>>4)*t+(8-(15&r)))?r:1;var r}function bt(t,e,r){var n=L(r),i=t[e+=255&n].g-8;return 0<i&&(x(r,r.u+8),n=L(r),e+=t[e].value,e+=n&(1<<i)-1),x(r,r.u+t[e].g),t[e].value}function yt(t,r,n){return n.g+=t.g,n.value+=t.value<<r>>>0,e(8>=n.g),t.g}function wt(t,r,n){var i=t.xc;return e((r=0==i?0:t.vc[t.md*(n>>i)+(r>>i)])<t.Wb),t.Ya[r]}function Nt(t,r,i,a){var o=t.ab,s=t.c*r,c=t.C;r=c+r;var u=i,l=a;for(a=t.Ta,i=t.Ua;0<o--;){var h=t.gc[o],f=c,d=r,p=u,g=l,m=(l=a,u=i,h.Ea);switch(e(f<d),e(d<=h.nc),h.hc){case 2:Gr(p,g,(d-f)*m,l,u);break;case 0:var v=f,b=d,y=l,w=u,N=(_=h).Ea;0==v&&(Wr(p,g,null,null,1,y,w),V(p,g+1,0,0,N-1,y,w+1),g+=N,w+=N,++v);for(var L=1<<_.b,A=L-1,x=q(N,_.b),S=_.K,_=_.w+(v>>_.b)*x;v<b;){var P=S,k=_,F=1;for(Vr(p,g,y,w-N,1,y,w);F<N;){var I=(F&~A)+L;I>N&&(I=N),(0,Zr[P[k++]>>8&15])(p,g+ +F,y,w+F-N,I-F,y,w+F),F=I}g+=N,w+=N,++v&A||(_+=x)}d!=h.nc&&n(l,u-m,l,u+(d-f-1)*m,m);break;case 1:for(m=p,b=g,N=(p=h.Ea)-(w=p&~(y=(g=1<<h.b)-1)),v=q(p,h.b),L=h.K,h=h.w+(f>>h.b)*v;f<d;){for(A=L,x=h,S=new T,_=b+w,P=b+p;b<_;)Y(A[x++],S),$r(S,m,b,g,l,u),b+=g,u+=g;b<P&&(Y(A[x++],S),$r(S,m,b,N,l,u),b+=N,u+=N),++f&y||(h+=v)}break;case 3:if(p==l&&g==u&&0<h.b){for(b=l,p=m=u+(d-f)*m-(w=(d-f)*q(h.Ea,h.b)),g=l,y=u,v=[],w=(N=w)-1;0<=w;--w)v[w]=g[y+w];for(w=N-1;0<=w;--w)b[p+w]=v[w];Yr(h,f,d,l,m,l,u)}else Yr(h,f,d,p,g,l,u)}u=a,l=i}l!=i&&n(a,i,u,l,s)}function Lt(t,r){var n=t.V,i=t.Ba+t.c*t.C,a=r-t.C;if(e(r<=t.l.o),e(16>=a),0<a){var o=t.l,s=t.Ta,c=t.Ua,u=o.width;if(Nt(t,a,n,i),a=c=[c],e((n=t.C)<(i=r)),e(o.v<o.va),i>o.o&&(i=o.o),n<o.j){var l=o.j-n;n=o.j;a[0]+=l*u}if(n>=i?n=0:(a[0]+=4*o.v,o.ka=n-o.j,o.U=o.va-o.v,o.T=i-n,n=1),n){if(c=c[0],11>(n=t.ca).S){var h=n.f.RGBA,f=(i=n.S,a=o.U,o=o.T,l=h.eb,h.A),d=o;for(h=h.fb+t.Ma*h.A;0<d--;){var p=s,g=c,m=a,v=l,b=h;switch(i){case En:Qr(p,g,m,v,b);break;case qn:tn(p,g,m,v,b);break;case Hn:tn(p,g,m,v,b),An(v,b,0,m,1,0);break;case Dn:nn(p,g,m,v,b);break;case Rn:et(p,g,m,v,b,1);break;case Wn:et(p,g,m,v,b,1),An(v,b,0,m,1,0);break;case Tn:et(p,g,m,v,b,0);break;case Vn:et(p,g,m,v,b,0),An(v,b,1,m,1,0);break;case Un:en(p,g,m,v,b);break;case Gn:en(p,g,m,v,b),xn(v,b,m,1,0);break;case zn:rn(p,g,m,v,b);break;default:e(0)}c+=u,h+=f}t.Ma+=o}else alert("todo:EmitRescaledRowsYUVA");e(t.Ma<=n.height)}}t.C=r,e(t.C<=t.i)}function At(t){var e;if(0<t.ua)return 0;for(e=0;e<t.Wb;++e){var r=t.Ya[e].G,n=t.Ya[e].H;if(0<r[1][n[1]+0].g||0<r[2][n[2]+0].g||0<r[3][n[3]+0].g)return 0}return 1}function xt(t,r,n,i,a,o){if(0!=t.Z){var s=t.qd,c=t.rd;for(e(null!=mi[t.Z]);r<n;++r)mi[t.Z](s,c,i,a,i,a,o),s=i,c=a,a+=o;t.qd=s,t.rd=c}}function St(t,r){var n=t.l.ma,i=0==n.Z||1==n.Z?t.l.j:t.C;i=t.C<i?i:t.C;if(e(r<=t.l.o),r>i){var a=t.l.width,o=n.ca,s=n.tb+a*i,c=t.V,u=t.Ba+t.c*i,l=t.gc;e(1==t.ab),e(3==l[0].hc),Xr(l[0],i,r,c,u,o,s),xt(n,i,r,o,s,a)}t.C=t.Ma=r}function _t(t,r,n,i,a,o,s){var c=t.$/i,u=t.$%i,l=t.m,h=t.s,f=n+t.$,d=f;a=n+i*a;var p=n+i*o,g=280+h.ua,m=t.Pb?c:16777216,v=0<h.ua?h.Wa:null,b=h.wc,y=f<p?wt(h,u,c):null;e(t.C<o),e(p<=a);var w=!1;t:for(;;){for(;w||f<p;){var N=0;if(c>=m){var _=f-n;e((m=t).Pb),m.wd=m.m,m.xd=_,0<m.s.ua&&B(m.s.Wa,m.s.vb),m=c+ti}if(u&b||(y=wt(h,u,c)),e(null!=y),y.Qb&&(r[f]=y.qb,w=!0),!w)if(S(l),y.jc){N=l,_=r;var P=f,k=y.pd[L(N)&Dr-1];e(y.jc),256>k.g?(x(N,N.u+k.g),_[P]=k.value,N=0):(x(N,N.u+k.g-256),e(256<=k.value),N=k.value),0==N&&(w=!0)}else N=bt(y.G[0],y.H[0],l);if(l.h)break;if(w||256>N){if(!w)if(y.nd)r[f]=(y.qb|N<<8)>>>0;else{if(S(l),w=bt(y.G[1],y.H[1],l),S(l),_=bt(y.G[2],y.H[2],l),P=bt(y.G[3],y.H[3],l),l.h)break;r[f]=(P<<24|w<<16|N<<8|_)>>>0}if(w=!1,++f,++u>=i&&(u=0,++c,null!=s&&c<=o&&!(c%16)&&s(t,c),null!=v))for(;d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N}else if(280>N){if(N=mt(N-256,l),_=bt(y.G[4],y.H[4],l),S(l),_=vt(i,_=mt(_,l)),l.h)break;if(f-n<_||a-f<N)break t;for(P=0;P<N;++P)r[f+P]=r[f+P-_];for(f+=N,u+=N;u>=i;)u-=i,++c,null!=s&&c<=o&&!(c%16)&&s(t,c);if(e(f<=a),u&b&&(y=wt(h,u,c)),null!=v)for(;d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N}else{if(!(N<g))break t;for(w=N-280,e(null!=v);d<f;)N=r[d++],v.X[(506832829*N&4294967295)>>>v.Mb]=N;N=f,e(!(w>>>(_=v).Xa)),r[N]=_.X[w],w=!0}w||e(l.h==A(l))}if(t.Pb&&l.h&&f<a)e(t.m.h),t.a=5,t.m=t.wd,t.$=t.xd,0<t.s.ua&&B(t.s.vb,t.s.Wa);else{if(l.h)break t;null!=s&&s(t,c>o?o:c),t.a=0,t.$=f-n}return 1}return t.a=3,0}function Pt(t){e(null!=t),t.vc=null,t.yc=null,t.Ya=null;var r=t.Wa;null!=r&&(r.X=null),t.vb=null,e(null!=t)}function kt(){var e=new or;return null==e?null:(e.a=0,e.xb=gi,rt("Predictor","VP8LPredictors"),rt("Predictor","VP8LPredictors_C"),rt("PredictorAdd","VP8LPredictorsAdd"),rt("PredictorAdd","VP8LPredictorsAdd_C"),Gr=G,$r=J,Qr=K,tn=Z,en=$,rn=Q,nn=tt,t.VP8LMapColor32b=Jr,t.VP8LMapColor8b=Kr,e)}function Ft(t,r,n,s,c){var u=1,f=[t],p=[r],g=s.m,m=s.s,v=null,b=0;t:for(;;){if(n)for(;u&&y(g,1);){var w=f,N=p,A=s,_=1,P=A.m,k=A.gc[A.ab],F=y(P,2);if(A.Oc&1<<F)u=0;else{switch(A.Oc|=1<<F,k.hc=F,k.Ea=w[0],k.nc=N[0],k.K=[null],++A.ab,e(4>=A.ab),F){case 0:case 1:k.b=y(P,3)+2,_=Ft(q(k.Ea,k.b),q(k.nc,k.b),0,A,k.K),k.K=k.K[0];break;case 3:var I,C=y(P,8)+1,j=16<C?0:4<C?1:2<C?2:3;if(w[0]=q(k.Ea,j),k.b=j,I=_=Ft(C,1,0,A,k.K)){var B,M=C,E=k,R=1<<(8>>E.b),T=a(R);if(null==T)I=0;else{var U=E.K[0],z=E.w;for(T[0]=E.K[0][0],B=1;B<1*M;++B)T[B]=D(U[z+B],T[B-1]);for(;B<4*R;++B)T[B]=0;E.K[0]=null,E.K[0]=T,I=1}}_=I;break;case 2:break;default:e(0)}u=_}}if(f=f[0],p=p[0],u&&y(g,1)&&!(u=1<=(b=y(g,4))&&11>=b)){s.a=3;break t}var H;if(H=u)e:{var W,V,G,Y=s,J=f,X=p,K=b,Z=n,$=Y.m,Q=Y.s,tt=[null],et=1,rt=0,nt=Qn[K];r:for(;;){if(Z&&y($,1)){var it=y($,3)+2,at=q(J,it),ot=q(X,it),st=at*ot;if(!Ft(at,ot,0,Y,tt))break r;for(tt=tt[0],Q.xc=it,W=0;W<st;++W){var ct=tt[W]>>8&65535;tt[W]=ct,ct>=et&&(et=ct+1)}}if($.h)break r;for(V=0;5>V;++V){var ut=Xn[V];!V&&0<K&&(ut+=1<<K),rt<ut&&(rt=ut)}var lt=o(et*nt,h),ht=et,ft=o(ht,d);if(null==ft)var dt=null;else e(65536>=ht),dt=ft;var pt=a(rt);if(null==dt||null==pt||null==lt){Y.a=1;break r}var gt=lt;for(W=G=0;W<et;++W){var mt=dt[W],vt=mt.G,bt=mt.H,wt=0,Nt=1,Lt=0;for(V=0;5>V;++V){ut=Xn[V],vt[V]=gt,bt[V]=G,!V&&0<K&&(ut+=1<<K);n:{var At,xt=ut,St=Y,kt=pt,It=gt,Ct=G,jt=0,Ot=St.m,Bt=y(Ot,1);if(i(kt,0,0,xt),Bt){var Mt=y(Ot,1)+1,Et=y(Ot,1),qt=y(Ot,0==Et?1:8);kt[qt]=1,2==Mt&&(kt[qt=y(Ot,8)]=1);var Dt=1}else{var Rt=a(19),Tt=y(Ot,4)+4;if(19<Tt){St.a=3;var Ut=0;break n}for(At=0;At<Tt;++At)Rt[Zn[At]]=y(Ot,3);var zt=void 0,Ht=void 0,Wt=St,Vt=Rt,Gt=xt,Yt=kt,Jt=0,Xt=Wt.m,Kt=8,Zt=o(128,h);i:for(;l(Zt,0,7,Vt,19);){if(y(Xt,1)){var $t=2+2*y(Xt,3);if((zt=2+y(Xt,$t))>Gt)break i}else zt=Gt;for(Ht=0;Ht<Gt&&zt--;){S(Xt);var Qt=Zt[0+(127&L(Xt))];x(Xt,Xt.u+Qt.g);var te=Qt.value;if(16>te)Yt[Ht++]=te,0!=te&&(Kt=te);else{var ee=16==te,re=te-16,ne=Jn[re],ie=y(Xt,Yn[re])+ne;if(Ht+ie>Gt)break i;for(var ae=ee?Kt:0;0<ie--;)Yt[Ht++]=ae}}Jt=1;break i}Jt||(Wt.a=3),Dt=Jt}(Dt=Dt&&!Ot.h)&&(jt=l(It,Ct,8,kt,xt)),Dt&&0!=jt?Ut=jt:(St.a=3,Ut=0)}if(0==Ut)break r;if(Nt&&1==Kn[V]&&(Nt=0==gt[G].g),wt+=gt[G].g,G+=Ut,3>=V){var oe,se=pt[0];for(oe=1;oe<ut;++oe)pt[oe]>se&&(se=pt[oe]);Lt+=se}}if(mt.nd=Nt,mt.Qb=0,Nt&&(mt.qb=(vt[3][bt[3]+0].value<<24|vt[1][bt[1]+0].value<<16|vt[2][bt[2]+0].value)>>>0,0==wt&&256>vt[0][bt[0]+0].value&&(mt.Qb=1,mt.qb+=vt[0][bt[0]+0].value<<8)),mt.jc=!mt.Qb&&6>Lt,mt.jc){var ce,ue=mt;for(ce=0;ce<Dr;++ce){var le=ce,he=ue.pd[le],fe=ue.G[0][ue.H[0]+le];256<=fe.value?(he.g=fe.g+256,he.value=fe.value):(he.g=0,he.value=0,le>>=yt(fe,8,he),le>>=yt(ue.G[1][ue.H[1]+le],16,he),le>>=yt(ue.G[2][ue.H[2]+le],0,he),yt(ue.G[3][ue.H[3]+le],24,he))}}}Q.vc=tt,Q.Wb=et,Q.Ya=dt,Q.yc=lt,H=1;break e}H=0}if(!(u=H)){s.a=3;break t}if(0<b){if(m.ua=1<<b,!O(m.Wa,b)){s.a=1,u=0;break t}}else m.ua=0;var de=s,pe=f,ge=p,me=de.s,ve=me.xc;if(de.c=pe,de.i=ge,me.md=q(pe,ve),me.wc=0==ve?-1:(1<<ve)-1,n){s.xb=pi;break t}if(null==(v=a(f*p))){s.a=1,u=0;break t}u=(u=_t(s,v,0,f,p,p,null))&&!g.h;break t}return u?(null!=c?c[0]=v:(e(null==v),e(n)),s.$=0,n||Pt(m)):Pt(m),u}function It(t,r){var n=t.c*t.i,i=n+r+16*r;return e(t.c<=r),t.V=a(i),null==t.V?(t.Ta=null,t.Ua=0,t.a=1,0):(t.Ta=t.V,t.Ua=t.Ba+n+r,1)}function Ct(t,r){var n=t.C,i=r-n,a=t.V,o=t.Ba+t.c*n;for(e(r<=t.l.o);0<i;){var s=16<i?16:i,c=t.l.ma,u=t.l.width,l=u*s,h=c.ca,f=c.tb+u*n,d=t.Ta,p=t.Ua;Nt(t,s,a,o),_n(d,p,h,f,l),xt(c,n,n+s,h,f,u),i-=s,a+=s*t.c,n+=s}e(n==r),t.C=t.Ma=r}function jt(){this.ub=this.yd=this.td=this.Rb=0}function Ot(){this.Kd=this.Ld=this.Ud=this.Td=this.i=this.c=0}function Bt(){this.Fb=this.Bb=this.Cb=0,this.Zb=a(4),this.Lb=a(4)}function Mt(){this.Yb=function(){var t=[];return function t(e,r,n){for(var i=n[r],a=0;a<i&&(e.push(n.length>r+1?[]:0),!(n.length<r+1));a++)t(e[a],r+1,n)}(t,0,[3,11]),t}()}function Et(){this.jb=a(3),this.Wc=s([4,8],Mt),this.Xc=s([4,17],Mt)}function qt(){this.Pc=this.wb=this.Tb=this.zd=0,this.vd=new a(4),this.od=new a(4)}function Dt(){this.ld=this.La=this.dd=this.tc=0}function Rt(){this.Na=this.la=0}function Tt(){this.Sc=[0,0],this.Eb=[0,0],this.Qc=[0,0],this.ia=this.lc=0}function Ut(){this.ad=a(384),this.Za=0,this.Ob=a(16),this.$b=this.Ad=this.ia=this.Gc=this.Hc=this.Dd=0}function zt(){this.uc=this.M=this.Nb=0,this.wa=Array(new Dt),this.Y=0,this.ya=Array(new Ut),this.aa=0,this.l=new Gt}function Ht(){this.y=a(16),this.f=a(8),this.ea=a(8)}function Wt(){this.cb=this.a=0,this.sc="",this.m=new w,this.Od=new jt,this.Kc=new Ot,this.ed=new qt,this.Qa=new Bt,this.Ic=this.$c=this.Aa=0,this.D=new zt,this.Xb=this.Va=this.Hb=this.zb=this.yb=this.Ub=this.za=0,this.Jc=o(8,w),this.ia=0,this.pb=o(4,Tt),this.Pa=new Et,this.Bd=this.kc=0,this.Ac=[],this.Bc=0,this.zc=[0,0,0,0],this.Gd=Array(new Ht),this.Hd=0,this.rb=Array(new Rt),this.sb=0,this.wa=Array(new Dt),this.Y=0,this.oc=[],this.pc=0,this.sa=[],this.ta=0,this.qa=[],this.ra=0,this.Ha=[],this.B=this.R=this.Ia=0,this.Ec=[],this.M=this.ja=this.Vb=this.Fc=0,this.ya=Array(new Ut),this.L=this.aa=0,this.gd=s([4,2],Dt),this.ga=null,this.Fa=[],this.Cc=this.qc=this.P=0,this.Gb=[],this.Uc=0,this.mb=[],this.nb=0,this.rc=[],this.Ga=this.Vc=0}function Vt(t,e){return 0>t?0:t>e?e:t}function Gt(){this.T=this.U=this.ka=this.height=this.width=0,this.y=[],this.f=[],this.ea=[],this.Rc=this.fa=this.W=this.N=this.O=0,this.ma="void",this.put="VP8IoPutHook",this.ac="VP8IoSetupHook",this.bc="VP8IoTeardownHook",this.ha=this.Kb=0,this.data=[],this.hb=this.ib=this.da=this.o=this.j=this.va=this.v=this.Da=this.ob=this.w=0,this.F=[],this.J=0}function Yt(){var t=new Wt;return null!=t&&(t.a=0,t.sc="OK",t.cb=0,t.Xb=0,ni||(ni=Zt)),t}function Jt(t,e,r){return 0==t.a&&(t.a=e,t.sc=r,t.cb=0),0}function Xt(t,e,r){return 3<=r&&157==t[e+0]&&1==t[e+1]&&42==t[e+2]}function Kt(t,r){if(null==t)return 0;if(t.a=0,t.sc="OK",null==r)return Jt(t,2,"null VP8Io passed to VP8GetHeaders()");var n=r.data,a=r.w,o=r.ha;if(4>o)return Jt(t,7,"Truncated header.");var s=n[a+0]|n[a+1]<<8|n[a+2]<<16,c=t.Od;if(c.Rb=!(1&s),c.td=s>>1&7,c.yd=s>>4&1,c.ub=s>>5,3<c.td)return Jt(t,3,"Incorrect keyframe parameters.");if(!c.yd)return Jt(t,4,"Frame not displayable.");a+=3,o-=3;var u=t.Kc;if(c.Rb){if(7>o)return Jt(t,7,"cannot parse picture header");if(!Xt(n,a,o))return Jt(t,3,"Bad code word");u.c=16383&(n[a+4]<<8|n[a+3]),u.Td=n[a+4]>>6,u.i=16383&(n[a+6]<<8|n[a+5]),u.Ud=n[a+6]>>6,a+=7,o-=7,t.za=u.c+15>>4,t.Ub=u.i+15>>4,r.width=u.c,r.height=u.i,r.Da=0,r.j=0,r.v=0,r.va=r.width,r.o=r.height,r.da=0,r.ib=r.width,r.hb=r.height,r.U=r.width,r.T=r.height,i((s=t.Pa).jb,0,255,s.jb.length),e(null!=(s=t.Qa)),s.Cb=0,s.Bb=0,s.Fb=1,i(s.Zb,0,0,s.Zb.length),i(s.Lb,0,0,s.Lb)}if(c.ub>o)return Jt(t,7,"bad partition length");p(s=t.m,n,a,c.ub),a+=c.ub,o-=c.ub,c.Rb&&(u.Ld=P(s),u.Kd=P(s)),u=t.Qa;var l,h=t.Pa;if(e(null!=s),e(null!=u),u.Cb=P(s),u.Cb){if(u.Bb=P(s),P(s)){for(u.Fb=P(s),l=0;4>l;++l)u.Zb[l]=P(s)?m(s,7):0;for(l=0;4>l;++l)u.Lb[l]=P(s)?m(s,6):0}if(u.Bb)for(l=0;3>l;++l)h.jb[l]=P(s)?g(s,8):255}else u.Bb=0;if(s.Ka)return Jt(t,3,"cannot parse segment header");if((u=t.ed).zd=P(s),u.Tb=g(s,6),u.wb=g(s,3),u.Pc=P(s),u.Pc&&P(s)){for(h=0;4>h;++h)P(s)&&(u.vd[h]=m(s,6));for(h=0;4>h;++h)P(s)&&(u.od[h]=m(s,6))}if(t.L=0==u.Tb?0:u.zd?1:2,s.Ka)return Jt(t,3,"cannot parse filter header");var f=o;if(o=l=a,a=l+f,u=f,t.Xb=(1<<g(t.m,2))-1,f<3*(h=t.Xb))n=7;else{for(l+=3*h,u-=3*h,f=0;f<h;++f){var d=n[o+0]|n[o+1]<<8|n[o+2]<<16;d>u&&(d=u),p(t.Jc[+f],n,l,d),l+=d,u-=d,o+=3}p(t.Jc[+h],n,l,u),n=l<a?0:5}if(0!=n)return Jt(t,n,"cannot parse partitions");for(n=g(l=t.m,7),o=P(l)?m(l,4):0,a=P(l)?m(l,4):0,u=P(l)?m(l,4):0,h=P(l)?m(l,4):0,l=P(l)?m(l,4):0,f=t.Qa,d=0;4>d;++d){if(f.Cb){var v=f.Zb[d];f.Fb||(v+=n)}else{if(0<d){t.pb[d]=t.pb[0];continue}v=n}var b=t.pb[d];b.Sc[0]=ei[Vt(v+o,127)],b.Sc[1]=ri[Vt(v+0,127)],b.Eb[0]=2*ei[Vt(v+a,127)],b.Eb[1]=101581*ri[Vt(v+u,127)]>>16,8>b.Eb[1]&&(b.Eb[1]=8),b.Qc[0]=ei[Vt(v+h,117)],b.Qc[1]=ri[Vt(v+l,127)],b.lc=v+l}if(!c.Rb)return Jt(t,4,"Not a key frame.");for(P(s),c=t.Pa,n=0;4>n;++n){for(o=0;8>o;++o)for(a=0;3>a;++a)for(u=0;11>u;++u)h=k(s,ui[n][o][a][u])?g(s,8):si[n][o][a][u],c.Wc[n][o].Yb[a][u]=h;for(o=0;17>o;++o)c.Xc[n][o]=c.Wc[n][li[o]]}return t.kc=P(s),t.kc&&(t.Bd=g(s,8)),t.cb=1}function Zt(t,e,r,n,i,a,o){var s=e[i].Yb[r];for(r=0;16>i;++i){if(!k(t,s[r+0]))return i;for(;!k(t,s[r+1]);)if(s=e[++i].Yb[0],r=0,16==i)return 16;var c=e[i+1].Yb;if(k(t,s[r+2])){var u=t,l=0;if(k(u,(f=s)[(h=r)+3]))if(k(u,f[h+6])){for(s=0,h=2*(l=k(u,f[h+8]))+(f=k(u,f[h+9+l])),l=0,f=ii[h];f[s];++s)l+=l+k(u,f[s]);l+=3+(8<<h)}else k(u,f[h+7])?(l=7+2*k(u,165),l+=k(u,145)):l=5+k(u,159);else l=k(u,f[h+4])?3+k(u,f[h+5]):2;s=c[2]}else l=1,s=c[1];c=o+ai[i],0>(u=t).b&&_(u);var h,f=u.b,d=(h=u.Ca>>1)-(u.I>>f)>>31;--u.b,u.Ca+=d,u.Ca|=1,u.I-=(h+1&d)<<f,a[c]=((l^d)-d)*n[(0<i)+0]}return 16}function $t(t){var e=t.rb[t.sb-1];e.la=0,e.Na=0,i(t.zc,0,0,t.zc.length),t.ja=0}function Qt(t,r){if(null==t)return 0;if(null==r)return Jt(t,2,"NULL VP8Io parameter in VP8Decode().");if(!t.cb&&!Kt(t,r))return 0;if(e(t.cb),null==r.ac||r.ac(r)){r.ob&&(t.L=0);var s=Ri[t.L];if(2==t.L?(t.yb=0,t.zb=0):(t.yb=r.v-s>>4,t.zb=r.j-s>>4,0>t.yb&&(t.yb=0),0>t.zb&&(t.zb=0)),t.Va=r.o+15+s>>4,t.Hb=r.va+15+s>>4,t.Hb>t.za&&(t.Hb=t.za),t.Va>t.Ub&&(t.Va=t.Ub),0<t.L){var c=t.ed;for(s=0;4>s;++s){var u;if(t.Qa.Cb){var l=t.Qa.Lb[s];t.Qa.Fb||(l+=c.Tb)}else l=c.Tb;for(u=0;1>=u;++u){var h=t.gd[s][u],f=l;if(c.Pc&&(f+=c.vd[0],u&&(f+=c.od[0])),0<(f=0>f?0:63<f?63:f)){var d=f;0<c.wb&&((d=4<c.wb?d>>2:d>>1)>9-c.wb&&(d=9-c.wb)),1>d&&(d=1),h.dd=d,h.tc=2*f+d,h.ld=40<=f?2:15<=f?1:0}else h.tc=0;h.La=u}}}s=0}else Jt(t,6,"Frame setup failed"),s=t.a;if(s=0==s){if(s){t.$c=0,0<t.Aa||(t.Ic=Ui);t:{s=t.Ic;c=4*(d=t.za);var p=32*d,g=d+1,m=0<t.L?d*(0<t.Aa?2:1):0,v=(2==t.Aa?2:1)*d;if((h=c+832+(u=3*(16*s+Ri[t.L])/2*p)+(l=null!=t.Fa&&0<t.Fa.length?t.Kc.c*t.Kc.i:0))!=h)s=0;else{if(h>t.Vb){if(t.Vb=0,t.Ec=a(h),t.Fc=0,null==t.Ec){s=Jt(t,1,"no memory during frame initialization.");break t}t.Vb=h}h=t.Ec,f=t.Fc,t.Ac=h,t.Bc=f,f+=c,t.Gd=o(p,Ht),t.Hd=0,t.rb=o(g+1,Rt),t.sb=1,t.wa=m?o(m,Dt):null,t.Y=0,t.D.Nb=0,t.D.wa=t.wa,t.D.Y=t.Y,0<t.Aa&&(t.D.Y+=d),e(!0),t.oc=h,t.pc=f,f+=832,t.ya=o(v,Ut),t.aa=0,t.D.ya=t.ya,t.D.aa=t.aa,2==t.Aa&&(t.D.aa+=d),t.R=16*d,t.B=8*d,d=(p=Ri[t.L])*t.R,p=p/2*t.B,t.sa=h,t.ta=f+d,t.qa=t.sa,t.ra=t.ta+16*s*t.R+p,t.Ha=t.qa,t.Ia=t.ra+8*s*t.B+p,t.$c=0,f+=u,t.mb=l?h:null,t.nb=l?f:null,e(f+l<=t.Fc+t.Vb),$t(t),i(t.Ac,t.Bc,0,c),s=1}}if(s){if(r.ka=0,r.y=t.sa,r.O=t.ta,r.f=t.qa,r.N=t.ra,r.ea=t.Ha,r.Vd=t.Ia,r.fa=t.R,r.Rc=t.B,r.F=null,r.J=0,!Cn){for(s=-255;255>=s;++s)Pn[255+s]=0>s?-s:s;for(s=-1020;1020>=s;++s)kn[1020+s]=-128>s?-128:127<s?127:s;for(s=-112;112>=s;++s)Fn[112+s]=-16>s?-16:15<s?15:s;for(s=-255;510>=s;++s)In[255+s]=0>s?0:255<s?255:s;Cn=1}an=ue,on=ae,cn=oe,un=se,ln=ce,sn=ie,hn=Je,fn=Xe,dn=$e,pn=Qe,gn=Ke,mn=Ze,vn=tr,bn=er,yn=ze,wn=He,Nn=We,Ln=Ve,fi[0]=xe,fi[1]=he,fi[2]=Le,fi[3]=Ae,fi[4]=Se,fi[5]=Pe,fi[6]=_e,fi[7]=ke,fi[8]=Ie,fi[9]=Fe,hi[0]=ve,hi[1]=de,hi[2]=pe,hi[3]=ge,hi[4]=be,hi[5]=ye,hi[6]=we,di[0]=Be,di[1]=fe,di[2]=Ce,di[3]=je,di[4]=Ee,di[5]=Me,di[6]=qe,s=1}else s=0}s&&(s=function(t,r){for(t.M=0;t.M<t.Va;++t.M){var o,s=t.Jc[t.M&t.Xb],c=t.m,u=t;for(o=0;o<u.za;++o){var l=c,h=u,f=h.Ac,d=h.Bc+4*o,p=h.zc,g=h.ya[h.aa+o];if(h.Qa.Bb?g.$b=k(l,h.Pa.jb[0])?2+k(l,h.Pa.jb[2]):k(l,h.Pa.jb[1]):g.$b=0,h.kc&&(g.Ad=k(l,h.Bd)),g.Za=!k(l,145)+0,g.Za){var m=g.Ob,v=0;for(h=0;4>h;++h){var b,y=p[0+h];for(b=0;4>b;++b){y=ci[f[d+b]][y];for(var w=oi[k(l,y[0])];0<w;)w=oi[2*w+k(l,y[w])];y=-w,f[d+b]=y}n(m,v,f,d,4),v+=4,p[0+h]=y}}else y=k(l,156)?k(l,128)?1:3:k(l,163)?2:0,g.Ob[0]=y,i(f,d,y,4),i(p,0,y,4);g.Dd=k(l,142)?k(l,114)?k(l,183)?1:3:2:0}if(u.m.Ka)return Jt(t,7,"Premature end-of-partition0 encountered.");for(;t.ja<t.za;++t.ja){if(u=s,l=(c=t).rb[c.sb-1],f=c.rb[c.sb+c.ja],o=c.ya[c.aa+c.ja],d=c.kc?o.Ad:0)l.la=f.la=0,o.Za||(l.Na=f.Na=0),o.Hc=0,o.Gc=0,o.ia=0;else{var N,L;l=f,f=u,d=c.Pa.Xc,p=c.ya[c.aa+c.ja],g=c.pb[p.$b];if(h=p.ad,m=0,v=c.rb[c.sb-1],y=b=0,i(h,m,0,384),p.Za)var A=0,x=d[3];else{w=a(16);var S=l.Na+v.Na;if(S=ni(f,d[1],S,g.Eb,0,w,0),l.Na=v.Na=(0<S)+0,1<S)an(w,0,h,m);else{var _=w[0]+3>>3;for(w=0;256>w;w+=16)h[m+w]=_}A=1,x=d[0]}var P=15&l.la,F=15&v.la;for(w=0;4>w;++w){var I=1&F;for(_=L=0;4>_;++_)P=P>>1|(I=(S=ni(f,x,S=I+(1&P),g.Sc,A,h,m))>A)<<7,L=L<<2|(3<S?3:1<S?2:0!=h[m+0]),m+=16;P>>=4,F=F>>1|I<<7,b=(b<<8|L)>>>0}for(x=P,A=F>>4,N=0;4>N;N+=2){for(L=0,P=l.la>>4+N,F=v.la>>4+N,w=0;2>w;++w){for(I=1&F,_=0;2>_;++_)S=I+(1&P),P=P>>1|(I=0<(S=ni(f,d[2],S,g.Qc,0,h,m)))<<3,L=L<<2|(3<S?3:1<S?2:0!=h[m+0]),m+=16;P>>=2,F=F>>1|I<<5}y|=L<<4*N,x|=P<<4<<N,A|=(240&F)<<N}l.la=x,v.la=A,p.Hc=b,p.Gc=y,p.ia=43690&y?0:g.ia,d=!(b|y)}if(0<c.L&&(c.wa[c.Y+c.ja]=c.gd[o.$b][o.Za],c.wa[c.Y+c.ja].La|=!d),u.Ka)return Jt(t,7,"Premature end-of-file encountered.")}if($t(t),c=r,u=1,o=(s=t).D,l=0<s.L&&s.M>=s.zb&&s.M<=s.Va,0==s.Aa)t:{if(o.M=s.M,o.uc=l,Or(s,o),u=1,o=(L=s.D).Nb,l=(y=Ri[s.L])*s.R,f=y/2*s.B,w=16*o*s.R,_=8*o*s.B,d=s.sa,p=s.ta-l+w,g=s.qa,h=s.ra-f+_,m=s.Ha,v=s.Ia-f+_,F=0==(P=L.M),b=P>=s.Va-1,2==s.Aa&&Or(s,L),L.uc)for(I=(S=s).D.M,e(S.D.uc),L=S.yb;L<S.Hb;++L){A=L,x=I;var C=(j=(U=S).D).Nb;N=U.R;var j=j.wa[j.Y+A],O=U.sa,B=U.ta+16*C*N+16*A,M=j.dd,E=j.tc;if(0!=E)if(e(3<=E),1==U.L)0<A&&wn(O,B,N,E+4),j.La&&Ln(O,B,N,E),0<x&&yn(O,B,N,E+4),j.La&&Nn(O,B,N,E);else{var q=U.B,D=U.qa,R=U.ra+8*C*q+8*A,T=U.Ha,U=U.Ia+8*C*q+8*A;C=j.ld;0<A&&(fn(O,B,N,E+4,M,C),pn(D,R,T,U,q,E+4,M,C)),j.La&&(mn(O,B,N,E,M,C),bn(D,R,T,U,q,E,M,C)),0<x&&(hn(O,B,N,E+4,M,C),dn(D,R,T,U,q,E+4,M,C)),j.La&&(gn(O,B,N,E,M,C),vn(D,R,T,U,q,E,M,C))}}if(s.ia&&alert("todo:DitherRow"),null!=c.put){if(L=16*P,P=16*(P+1),F?(c.y=s.sa,c.O=s.ta+w,c.f=s.qa,c.N=s.ra+_,c.ea=s.Ha,c.W=s.Ia+_):(L-=y,c.y=d,c.O=p,c.f=g,c.N=h,c.ea=m,c.W=v),b||(P-=y),P>c.o&&(P=c.o),c.F=null,c.J=null,null!=s.Fa&&0<s.Fa.length&&L<P&&(c.J=hr(s,c,L,P-L),c.F=s.mb,null==c.F&&0==c.F.length)){u=Jt(s,3,"Could not decode alpha data.");break t}L<c.j&&(y=c.j-L,L=c.j,e(!(1&y)),c.O+=s.R*y,c.N+=s.B*(y>>1),c.W+=s.B*(y>>1),null!=c.F&&(c.J+=c.width*y)),L<P&&(c.O+=c.v,c.N+=c.v>>1,c.W+=c.v>>1,null!=c.F&&(c.J+=c.v),c.ka=L-c.j,c.U=c.va-c.v,c.T=P-L,u=c.put(c))}o+1!=s.Ic||b||(n(s.sa,s.ta-l,d,p+16*s.R,l),n(s.qa,s.ra-f,g,h+8*s.B,f),n(s.Ha,s.Ia-f,m,v+8*s.B,f))}if(!u)return Jt(t,6,"Output aborted.")}return 1}(t,r)),null!=r.bc&&r.bc(r),s&=1}return s?(t.cb=0,s):0}function te(t,e,r,n,i){i=t[e+r+32*n]+(i>>3),t[e+r+32*n]=-256&i?0>i?0:255:i}function ee(t,e,r,n,i,a){te(t,e,0,r,n+i),te(t,e,1,r,n+a),te(t,e,2,r,n-a),te(t,e,3,r,n-i)}function re(t){return(20091*t>>16)+t}function ne(t,e,r,n){var i,o=0,s=a(16);for(i=0;4>i;++i){var c=t[e+0]+t[e+8],u=t[e+0]-t[e+8],l=(35468*t[e+4]>>16)-re(t[e+12]),h=re(t[e+4])+(35468*t[e+12]>>16);s[o+0]=c+h,s[o+1]=u+l,s[o+2]=u-l,s[o+3]=c-h,o+=4,e++}for(i=o=0;4>i;++i)c=(t=s[o+0]+4)+s[o+8],u=t-s[o+8],l=(35468*s[o+4]>>16)-re(s[o+12]),te(r,n,0,0,c+(h=re(s[o+4])+(35468*s[o+12]>>16))),te(r,n,1,0,u+l),te(r,n,2,0,u-l),te(r,n,3,0,c-h),o++,n+=32}function ie(t,e,r,n){var i=t[e+0]+4,a=35468*t[e+4]>>16,o=re(t[e+4]),s=35468*t[e+1]>>16;ee(r,n,0,i+o,t=re(t[e+1]),s),ee(r,n,1,i+a,t,s),ee(r,n,2,i-a,t,s),ee(r,n,3,i-o,t,s)}function ae(t,e,r,n,i){ne(t,e,r,n),i&&ne(t,e+16,r,n+4)}function oe(t,e,r,n){on(t,e+0,r,n,1),on(t,e+32,r,n+128,1)}function se(t,e,r,n){var i;for(t=t[e+0]+4,i=0;4>i;++i)for(e=0;4>e;++e)te(r,n,e,i,t)}function ce(t,e,r,n){t[e+0]&&un(t,e+0,r,n),t[e+16]&&un(t,e+16,r,n+4),t[e+32]&&un(t,e+32,r,n+128),t[e+48]&&un(t,e+48,r,n+128+4)}function ue(t,e,r,n){var i,o=a(16);for(i=0;4>i;++i){var s=t[e+0+i]+t[e+12+i],c=t[e+4+i]+t[e+8+i],u=t[e+4+i]-t[e+8+i],l=t[e+0+i]-t[e+12+i];o[0+i]=s+c,o[8+i]=s-c,o[4+i]=l+u,o[12+i]=l-u}for(i=0;4>i;++i)s=(t=o[0+4*i]+3)+o[3+4*i],c=o[1+4*i]+o[2+4*i],u=o[1+4*i]-o[2+4*i],l=t-o[3+4*i],r[n+0]=s+c>>3,r[n+16]=l+u>>3,r[n+32]=s-c>>3,r[n+48]=l-u>>3,n+=64}function le(t,e,r){var n,i=e-32,a=Bn,o=255-t[i-1];for(n=0;n<r;++n){var s,c=a,u=o+t[e-1];for(s=0;s<r;++s)t[e+s]=c[u+t[i+s]];e+=32}}function he(t,e){le(t,e,4)}function fe(t,e){le(t,e,8)}function de(t,e){le(t,e,16)}function pe(t,e){var r;for(r=0;16>r;++r)n(t,e+32*r,t,e-32,16)}function ge(t,e){var r;for(r=16;0<r;--r)i(t,e,t[e-1],16),e+=32}function me(t,e,r){var n;for(n=0;16>n;++n)i(e,r+32*n,t,16)}function ve(t,e){var r,n=16;for(r=0;16>r;++r)n+=t[e-1+32*r]+t[e+r-32];me(n>>5,t,e)}function be(t,e){var r,n=8;for(r=0;16>r;++r)n+=t[e-1+32*r];me(n>>4,t,e)}function ye(t,e){var r,n=8;for(r=0;16>r;++r)n+=t[e+r-32];me(n>>4,t,e)}function we(t,e){me(128,t,e)}function Ne(t,e,r){return t+2*e+r+2>>2}function Le(t,e){var r,i=e-32;i=new Uint8Array([Ne(t[i-1],t[i+0],t[i+1]),Ne(t[i+0],t[i+1],t[i+2]),Ne(t[i+1],t[i+2],t[i+3]),Ne(t[i+2],t[i+3],t[i+4])]);for(r=0;4>r;++r)n(t,e+32*r,i,0,i.length)}function Ae(t,e){var r=t[e-1],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96];F(t,e+0,16843009*Ne(t[e-1-32],r,n)),F(t,e+32,16843009*Ne(r,n,i)),F(t,e+64,16843009*Ne(n,i,a)),F(t,e+96,16843009*Ne(i,a,a))}function xe(t,e){var r,n=4;for(r=0;4>r;++r)n+=t[e+r-32]+t[e-1+32*r];for(n>>=3,r=0;4>r;++r)i(t,e+32*r,n,4)}function Se(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1-32],o=t[e+0-32],s=t[e+1-32],c=t[e+2-32],u=t[e+3-32];t[e+0+96]=Ne(n,i,t[e-1+96]),t[e+1+96]=t[e+0+64]=Ne(r,n,i),t[e+2+96]=t[e+1+64]=t[e+0+32]=Ne(a,r,n),t[e+3+96]=t[e+2+64]=t[e+1+32]=t[e+0+0]=Ne(o,a,r),t[e+3+64]=t[e+2+32]=t[e+1+0]=Ne(s,o,a),t[e+3+32]=t[e+2+0]=Ne(c,s,o),t[e+3+0]=Ne(u,c,s)}function _e(t,e){var r=t[e+1-32],n=t[e+2-32],i=t[e+3-32],a=t[e+4-32],o=t[e+5-32],s=t[e+6-32],c=t[e+7-32];t[e+0+0]=Ne(t[e+0-32],r,n),t[e+1+0]=t[e+0+32]=Ne(r,n,i),t[e+2+0]=t[e+1+32]=t[e+0+64]=Ne(n,i,a),t[e+3+0]=t[e+2+32]=t[e+1+64]=t[e+0+96]=Ne(i,a,o),t[e+3+32]=t[e+2+64]=t[e+1+96]=Ne(a,o,s),t[e+3+64]=t[e+2+96]=Ne(o,s,c),t[e+3+96]=Ne(s,c,c)}function Pe(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1-32],o=t[e+0-32],s=t[e+1-32],c=t[e+2-32],u=t[e+3-32];t[e+0+0]=t[e+1+64]=a+o+1>>1,t[e+1+0]=t[e+2+64]=o+s+1>>1,t[e+2+0]=t[e+3+64]=s+c+1>>1,t[e+3+0]=c+u+1>>1,t[e+0+96]=Ne(i,n,r),t[e+0+64]=Ne(n,r,a),t[e+0+32]=t[e+1+96]=Ne(r,a,o),t[e+1+32]=t[e+2+96]=Ne(a,o,s),t[e+2+32]=t[e+3+96]=Ne(o,s,c),t[e+3+32]=Ne(s,c,u)}function ke(t,e){var r=t[e+0-32],n=t[e+1-32],i=t[e+2-32],a=t[e+3-32],o=t[e+4-32],s=t[e+5-32],c=t[e+6-32],u=t[e+7-32];t[e+0+0]=r+n+1>>1,t[e+1+0]=t[e+0+64]=n+i+1>>1,t[e+2+0]=t[e+1+64]=i+a+1>>1,t[e+3+0]=t[e+2+64]=a+o+1>>1,t[e+0+32]=Ne(r,n,i),t[e+1+32]=t[e+0+96]=Ne(n,i,a),t[e+2+32]=t[e+1+96]=Ne(i,a,o),t[e+3+32]=t[e+2+96]=Ne(a,o,s),t[e+3+64]=Ne(o,s,c),t[e+3+96]=Ne(s,c,u)}function Fe(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96];t[e+0+0]=r+n+1>>1,t[e+2+0]=t[e+0+32]=n+i+1>>1,t[e+2+32]=t[e+0+64]=i+a+1>>1,t[e+1+0]=Ne(r,n,i),t[e+3+0]=t[e+1+32]=Ne(n,i,a),t[e+3+32]=t[e+1+64]=Ne(i,a,a),t[e+3+64]=t[e+2+64]=t[e+0+96]=t[e+1+96]=t[e+2+96]=t[e+3+96]=a}function Ie(t,e){var r=t[e-1+0],n=t[e-1+32],i=t[e-1+64],a=t[e-1+96],o=t[e-1-32],s=t[e+0-32],c=t[e+1-32],u=t[e+2-32];t[e+0+0]=t[e+2+32]=r+o+1>>1,t[e+0+32]=t[e+2+64]=n+r+1>>1,t[e+0+64]=t[e+2+96]=i+n+1>>1,t[e+0+96]=a+i+1>>1,t[e+3+0]=Ne(s,c,u),t[e+2+0]=Ne(o,s,c),t[e+1+0]=t[e+3+32]=Ne(r,o,s),t[e+1+32]=t[e+3+64]=Ne(n,r,o),t[e+1+64]=t[e+3+96]=Ne(i,n,r),t[e+1+96]=Ne(a,i,n)}function Ce(t,e){var r;for(r=0;8>r;++r)n(t,e+32*r,t,e-32,8)}function je(t,e){var r;for(r=0;8>r;++r)i(t,e,t[e-1],8),e+=32}function Oe(t,e,r){var n;for(n=0;8>n;++n)i(e,r+32*n,t,8)}function Be(t,e){var r,n=8;for(r=0;8>r;++r)n+=t[e+r-32]+t[e-1+32*r];Oe(n>>4,t,e)}function Me(t,e){var r,n=4;for(r=0;8>r;++r)n+=t[e+r-32];Oe(n>>3,t,e)}function Ee(t,e){var r,n=4;for(r=0;8>r;++r)n+=t[e-1+32*r];Oe(n>>3,t,e)}function qe(t,e){Oe(128,t,e)}function De(t,e,r){var n=t[e-r],i=t[e+0],a=3*(i-n)+jn[1020+t[e-2*r]-t[e+r]],o=On[112+(a+4>>3)];t[e-r]=Bn[255+n+On[112+(a+3>>3)]],t[e+0]=Bn[255+i-o]}function Re(t,e,r,n){var i=t[e+0],a=t[e+r];return Mn[255+t[e-2*r]-t[e-r]]>n||Mn[255+a-i]>n}function Te(t,e,r,n){return 4*Mn[255+t[e-r]-t[e+0]]+Mn[255+t[e-2*r]-t[e+r]]<=n}function Ue(t,e,r,n,i){var a=t[e-3*r],o=t[e-2*r],s=t[e-r],c=t[e+0],u=t[e+r],l=t[e+2*r],h=t[e+3*r];return 4*Mn[255+s-c]+Mn[255+o-u]>n?0:Mn[255+t[e-4*r]-a]<=i&&Mn[255+a-o]<=i&&Mn[255+o-s]<=i&&Mn[255+h-l]<=i&&Mn[255+l-u]<=i&&Mn[255+u-c]<=i}function ze(t,e,r,n){var i=2*n+1;for(n=0;16>n;++n)Te(t,e+n,r,i)&&De(t,e+n,r)}function He(t,e,r,n){var i=2*n+1;for(n=0;16>n;++n)Te(t,e+n*r,1,i)&&De(t,e+n*r,1)}function We(t,e,r,n){var i;for(i=3;0<i;--i)ze(t,e+=4*r,r,n)}function Ve(t,e,r,n){var i;for(i=3;0<i;--i)He(t,e+=4,r,n)}function Ge(t,e,r,n,i,a,o,s){for(a=2*a+1;0<i--;){if(Ue(t,e,r,a,o))if(Re(t,e,r,s))De(t,e,r);else{var c=t,u=e,l=r,h=c[u-2*l],f=c[u-l],d=c[u+0],p=c[u+l],g=c[u+2*l],m=27*(b=jn[1020+3*(d-f)+jn[1020+h-p]])+63>>7,v=18*b+63>>7,b=9*b+63>>7;c[u-3*l]=Bn[255+c[u-3*l]+b],c[u-2*l]=Bn[255+h+v],c[u-l]=Bn[255+f+m],c[u+0]=Bn[255+d-m],c[u+l]=Bn[255+p-v],c[u+2*l]=Bn[255+g-b]}e+=n}}function Ye(t,e,r,n,i,a,o,s){for(a=2*a+1;0<i--;){if(Ue(t,e,r,a,o))if(Re(t,e,r,s))De(t,e,r);else{var c=t,u=e,l=r,h=c[u-l],f=c[u+0],d=c[u+l],p=On[112+((g=3*(f-h))+4>>3)],g=On[112+(g+3>>3)],m=p+1>>1;c[u-2*l]=Bn[255+c[u-2*l]+m],c[u-l]=Bn[255+h+g],c[u+0]=Bn[255+f-p],c[u+l]=Bn[255+d-m]}e+=n}}function Je(t,e,r,n,i,a){Ge(t,e,r,1,16,n,i,a)}function Xe(t,e,r,n,i,a){Ge(t,e,1,r,16,n,i,a)}function Ke(t,e,r,n,i,a){var o;for(o=3;0<o;--o)Ye(t,e+=4*r,r,1,16,n,i,a)}function Ze(t,e,r,n,i,a){var o;for(o=3;0<o;--o)Ye(t,e+=4,1,r,16,n,i,a)}function $e(t,e,r,n,i,a,o,s){Ge(t,e,i,1,8,a,o,s),Ge(r,n,i,1,8,a,o,s)}function Qe(t,e,r,n,i,a,o,s){Ge(t,e,1,i,8,a,o,s),Ge(r,n,1,i,8,a,o,s)}function tr(t,e,r,n,i,a,o,s){Ye(t,e+4*i,i,1,8,a,o,s),Ye(r,n+4*i,i,1,8,a,o,s)}function er(t,e,r,n,i,a,o,s){Ye(t,e+4,1,i,8,a,o,s),Ye(r,n+4,1,i,8,a,o,s)}function rr(){this.ba=new ot,this.ec=[],this.cc=[],this.Mc=[],this.Dc=this.Nc=this.dc=this.fc=0,this.Oa=new ct,this.memory=0,this.Ib="OutputFunc",this.Jb="OutputAlphaFunc",this.Nd="OutputRowFunc"}function nr(){this.data=[],this.offset=this.kd=this.ha=this.w=0,this.na=[],this.xa=this.gb=this.Ja=this.Sa=this.P=0}function ir(){this.nc=this.Ea=this.b=this.hc=0,this.K=[],this.w=0}function ar(){this.ua=0,this.Wa=new M,this.vb=new M,this.md=this.xc=this.wc=0,this.vc=[],this.Wb=0,this.Ya=new d,this.yc=new h}function or(){this.xb=this.a=0,this.l=new Gt,this.ca=new ot,this.V=[],this.Ba=0,this.Ta=[],this.Ua=0,this.m=new N,this.Pb=0,this.wd=new N,this.Ma=this.$=this.C=this.i=this.c=this.xd=0,this.s=new ar,this.ab=0,this.gc=o(4,ir),this.Oc=0}function sr(){this.Lc=this.Z=this.$a=this.i=this.c=0,this.l=new Gt,this.ic=0,this.ca=[],this.tb=0,this.qd=null,this.rd=0}function cr(t,e,r,n,i,a,o){for(t=null==t?0:t[e+0],e=0;e<o;++e)i[a+e]=t+r[n+e]&255,t=i[a+e]}function ur(t,e,r,n,i,a,o){var s;if(null==t)cr(null,null,r,n,i,a,o);else for(s=0;s<o;++s)i[a+s]=t[e+s]+r[n+s]&255}function lr(t,e,r,n,i,a,o){if(null==t)cr(null,null,r,n,i,a,o);else{var s,c=t[e+0],u=c,l=c;for(s=0;s<o;++s)u=l+(c=t[e+s])-u,l=r[n+s]+(-256&u?0>u?0:255:u)&255,u=c,i[a+s]=l}}function hr(t,r,i,o){var s=r.width,c=r.o;if(e(null!=t&&null!=r),0>i||0>=o||i+o>c)return null;if(!t.Cc){if(null==t.ga){var u;if(t.ga=new sr,(u=null==t.ga)||(u=r.width*r.o,e(0==t.Gb.length),t.Gb=a(u),t.Uc=0,null==t.Gb?u=0:(t.mb=t.Gb,t.nb=t.Uc,t.rc=null,u=1),u=!u),!u){u=t.ga;var l=t.Fa,h=t.P,f=t.qc,d=t.mb,p=t.nb,g=h+1,m=f-1,b=u.l;if(e(null!=l&&null!=d&&null!=r),mi[0]=null,mi[1]=cr,mi[2]=ur,mi[3]=lr,u.ca=d,u.tb=p,u.c=r.width,u.i=r.height,e(0<u.c&&0<u.i),1>=f)r=0;else if(u.$a=l[h+0]>>0&3,u.Z=l[h+0]>>2&3,u.Lc=l[h+0]>>4&3,h=l[h+0]>>6&3,0>u.$a||1<u.$a||4<=u.Z||1<u.Lc||h)r=0;else if(b.put=dt,b.ac=ft,b.bc=pt,b.ma=u,b.width=r.width,b.height=r.height,b.Da=r.Da,b.v=r.v,b.va=r.va,b.j=r.j,b.o=r.o,u.$a)t:{e(1==u.$a),r=kt();e:for(;;){if(null==r){r=0;break t}if(e(null!=u),u.mc=r,r.c=u.c,r.i=u.i,r.l=u.l,r.l.ma=u,r.l.width=u.c,r.l.height=u.i,r.a=0,v(r.m,l,g,m),!Ft(u.c,u.i,1,r,null))break e;if(1==r.ab&&3==r.gc[0].hc&&At(r.s)?(u.ic=1,l=r.c*r.i,r.Ta=null,r.Ua=0,r.V=a(l),r.Ba=0,null==r.V?(r.a=1,r=0):r=1):(u.ic=0,r=It(r,u.c)),!r)break e;r=1;break t}u.mc=null,r=0}else r=m>=u.c*u.i;u=!r}if(u)return null;1!=t.ga.Lc?t.Ga=0:o=c-i}e(null!=t.ga),e(i+o<=c);t:{if(r=(l=t.ga).c,c=l.l.o,0==l.$a){if(g=t.rc,m=t.Vc,b=t.Fa,h=t.P+1+i*r,f=t.mb,d=t.nb+i*r,e(h<=t.P+t.qc),0!=l.Z)for(e(null!=mi[l.Z]),u=0;u<o;++u)mi[l.Z](g,m,b,h,f,d,r),g=f,m=d,d+=r,h+=r;else for(u=0;u<o;++u)n(f,d,b,h,r),g=f,m=d,d+=r,h+=r;t.rc=g,t.Vc=m}else{if(e(null!=l.mc),r=i+o,e(null!=(u=l.mc)),e(r<=u.i),u.C>=r)r=1;else if(l.ic||mr(),l.ic){l=u.V,g=u.Ba,m=u.c;var y=u.i,w=(b=1,h=u.$/m,f=u.$%m,d=u.m,p=u.s,u.$),N=m*y,L=m*r,x=p.wc,_=w<L?wt(p,f,h):null;e(w<=N),e(r<=y),e(At(p));e:for(;;){for(;!d.h&&w<L;){if(f&x||(_=wt(p,f,h)),e(null!=_),S(d),256>(y=bt(_.G[0],_.H[0],d)))l[g+w]=y,++w,++f>=m&&(f=0,++h<=r&&!(h%16)&&St(u,h));else{if(!(280>y)){b=0;break e}y=mt(y-256,d);var P,k=bt(_.G[4],_.H[4],d);if(S(d),!(w>=(k=vt(m,k=mt(k,d)))&&N-w>=y)){b=0;break e}for(P=0;P<y;++P)l[g+w+P]=l[g+w+P-k];for(w+=y,f+=y;f>=m;)f-=m,++h<=r&&!(h%16)&&St(u,h);w<L&&f&x&&(_=wt(p,f,h))}e(d.h==A(d))}St(u,h>r?r:h);break e}!b||d.h&&w<N?(b=0,u.a=d.h?5:3):u.$=w,r=b}else r=_t(u,u.V,u.Ba,u.c,u.i,r,Ct);if(!r){o=0;break t}}i+o>=c&&(t.Cc=1),o=1}if(!o)return null;if(t.Cc&&(null!=(o=t.ga)&&(o.mc=null),t.ga=null,0<t.Ga))return alert("todo:WebPDequantizeLevels"),null}return t.nb+i*s}function fr(t,e,r,n,i,a){for(;0<i--;){var o,s=t,c=e+(r?1:0),u=t,l=e+(r?0:3);for(o=0;o<n;++o){var h=u[l+4*o];255!=h&&(h*=32897,s[c+4*o+0]=s[c+4*o+0]*h>>23,s[c+4*o+1]=s[c+4*o+1]*h>>23,s[c+4*o+2]=s[c+4*o+2]*h>>23)}e+=a}}function dr(t,e,r,n,i){for(;0<n--;){var a;for(a=0;a<r;++a){var o=t[e+2*a+0],s=15&(u=t[e+2*a+1]),c=4369*s,u=(240&u|u>>4)*c>>16;t[e+2*a+0]=(240&o|o>>4)*c>>16&240|(15&o|o<<4)*c>>16>>4&15,t[e+2*a+1]=240&u|s}e+=i}}function pr(t,e,r,n,i,a,o,s){var c,u,l=255;for(u=0;u<i;++u){for(c=0;c<n;++c){var h=t[e+c];a[o+4*c]=h,l&=h}e+=r,o+=s}return 255!=l}function gr(t,e,r,n,i){var a;for(a=0;a<i;++a)r[n+a]=t[e+a]>>8}function mr(){An=fr,xn=dr,Sn=pr,_n=gr}function vr(r,n,i){t[r]=function(t,r,a,o,s,c,u,l,h,f,d,p,g,m,v,b,y){var w,N=y-1>>1,L=s[c+0]|u[l+0]<<16,A=h[f+0]|d[p+0]<<16;e(null!=t);var x=3*L+A+131074>>2;for(n(t[r+0],255&x,x>>16,g,m),null!=a&&(x=3*A+L+131074>>2,n(a[o+0],255&x,x>>16,v,b)),w=1;w<=N;++w){var S=s[c+w]|u[l+w]<<16,_=h[f+w]|d[p+w]<<16,P=L+S+A+_+524296,k=P+2*(S+A)>>3;x=k+L>>1,L=(P=P+2*(L+_)>>3)+S>>1,n(t[r+2*w-1],255&x,x>>16,g,m+(2*w-1)*i),n(t[r+2*w-0],255&L,L>>16,g,m+(2*w-0)*i),null!=a&&(x=P+A>>1,L=k+_>>1,n(a[o+2*w-1],255&x,x>>16,v,b+(2*w-1)*i),n(a[o+2*w+0],255&L,L>>16,v,b+(2*w+0)*i)),L=S,A=_}1&y||(x=3*L+A+131074>>2,n(t[r+y-1],255&x,x>>16,g,m+(y-1)*i),null!=a&&(x=3*A+L+131074>>2,n(a[o+y-1],255&x,x>>16,v,b+(y-1)*i)))}}function br(){vi[En]=bi,vi[qn]=wi,vi[Dn]=yi,vi[Rn]=Ni,vi[Tn]=Li,vi[Un]=Ai,vi[zn]=xi,vi[Hn]=wi,vi[Wn]=Ni,vi[Vn]=Li,vi[Gn]=Ai}function yr(t){return t&~Ii?0>t?0:255:t>>Fi}function wr(t,e){return yr((19077*t>>8)+(26149*e>>8)-14234)}function Nr(t,e,r){return yr((19077*t>>8)-(6419*e>>8)-(13320*r>>8)+8708)}function Lr(t,e){return yr((19077*t>>8)+(33050*e>>8)-17685)}function Ar(t,e,r,n,i){n[i+0]=wr(t,r),n[i+1]=Nr(t,e,r),n[i+2]=Lr(t,e)}function xr(t,e,r,n,i){n[i+0]=Lr(t,e),n[i+1]=Nr(t,e,r),n[i+2]=wr(t,r)}function Sr(t,e,r,n,i){var a=Nr(t,e,r);e=a<<3&224|Lr(t,e)>>3,n[i+0]=248&wr(t,r)|a>>5,n[i+1]=e}function _r(t,e,r,n,i){var a=240&Lr(t,e)|15;n[i+0]=240&wr(t,r)|Nr(t,e,r)>>4,n[i+1]=a}function Pr(t,e,r,n,i){n[i+0]=255,Ar(t,e,r,n,i+1)}function kr(t,e,r,n,i){xr(t,e,r,n,i),n[i+3]=255}function Fr(t,e,r,n,i){Ar(t,e,r,n,i),n[i+3]=255}function Vt(t,e){return 0>t?0:t>e?e:t}function Ir(e,r,n){t[e]=function(t,e,i,a,o,s,c,u,l){for(var h=u+(-2&l)*n;u!=h;)r(t[e+0],i[a+0],o[s+0],c,u),r(t[e+1],i[a+0],o[s+0],c,u+n),e+=2,++a,++s,u+=2*n;1&l&&r(t[e+0],i[a+0],o[s+0],c,u)}}function Cr(t,e,r){return 0==r?0==t?0==e?6:5:0==e?4:0:r}function jr(t,e,r,n,i){switch(t>>>30){case 3:on(e,r,n,i,0);break;case 2:sn(e,r,n,i);break;case 1:un(e,r,n,i)}}function Or(t,e){var r,a,o=e.M,s=e.Nb,c=t.oc,u=t.pc+40,l=t.oc,h=t.pc+584,f=t.oc,d=t.pc+600;for(r=0;16>r;++r)c[u+32*r-1]=129;for(r=0;8>r;++r)l[h+32*r-1]=129,f[d+32*r-1]=129;for(0<o?c[u-1-32]=l[h-1-32]=f[d-1-32]=129:(i(c,u-32-1,127,21),i(l,h-32-1,127,9),i(f,d-32-1,127,9)),a=0;a<t.za;++a){var p=e.ya[e.aa+a];if(0<a){for(r=-1;16>r;++r)n(c,u+32*r-4,c,u+32*r+12,4);for(r=-1;8>r;++r)n(l,h+32*r-4,l,h+32*r+4,4),n(f,d+32*r-4,f,d+32*r+4,4)}var g=t.Gd,m=t.Hd+a,v=p.ad,b=p.Hc;if(0<o&&(n(c,u-32,g[m].y,0,16),n(l,h-32,g[m].f,0,8),n(f,d-32,g[m].ea,0,8)),p.Za){var y=c,w=u-32+16;for(0<o&&(a>=t.za-1?i(y,w,g[m].y[15],4):n(y,w,g[m+1].y,0,4)),r=0;4>r;r++)y[w+128+r]=y[w+256+r]=y[w+384+r]=y[w+0+r];for(r=0;16>r;++r,b<<=2)y=c,w=u+Di[r],fi[p.Ob[r]](y,w),jr(b,v,16*+r,y,w)}else if(y=Cr(a,o,p.Ob[0]),hi[y](c,u),0!=b)for(r=0;16>r;++r,b<<=2)jr(b,v,16*+r,c,u+Di[r]);for(r=p.Gc,y=Cr(a,o,p.Dd),di[y](l,h),di[y](f,d),b=v,y=l,w=h,255&(p=r>>0)&&(170&p?cn(b,256,y,w):ln(b,256,y,w)),p=f,b=d,255&(r>>=8)&&(170&r?cn(v,320,p,b):ln(v,320,p,b)),o<t.Ub-1&&(n(g[m].y,0,c,u+480,16),n(g[m].f,0,l,h+224,8),n(g[m].ea,0,f,d+224,8)),r=8*s*t.B,g=t.sa,m=t.ta+16*a+16*s*t.R,v=t.qa,p=t.ra+8*a+r,b=t.Ha,y=t.Ia+8*a+r,r=0;16>r;++r)n(g,m+r*t.R,c,u+32*r,16);for(r=0;8>r;++r)n(v,p+r*t.B,l,h+32*r,8),n(b,y+r*t.B,f,d+32*r,8)}}function Br(t,n,i,a,o,s,c,u,l){var h=[0],f=[0],d=0,p=null!=l?l.kd:0,g=null!=l?l:new nr;if(null==t||12>i)return 7;g.data=t,g.w=n,g.ha=i,n=[n],i=[i],g.gb=[g.gb];t:{var m=n,b=i,y=g.gb;if(e(null!=t),e(null!=b),e(null!=y),y[0]=0,12<=b[0]&&!r(t,m[0],"RIFF")){if(r(t,m[0]+8,"WEBP")){y=3;break t}var w=j(t,m[0]+4);if(12>w||4294967286<w){y=3;break t}if(p&&w>b[0]-8){y=7;break t}y[0]=w,m[0]+=12,b[0]-=12}y=0}if(0!=y)return y;for(w=0<g.gb[0],i=i[0];;){t:{var L=t;b=n,y=i;var A=h,x=f,S=m=[0];if((k=d=[d])[0]=0,8>y[0])y=7;else{if(!r(L,b[0],"VP8X")){if(10!=j(L,b[0]+4)){y=3;break t}if(18>y[0]){y=7;break t}var _=j(L,b[0]+8),P=1+C(L,b[0]+12);if(2147483648<=P*(L=1+C(L,b[0]+15))){y=3;break t}null!=S&&(S[0]=_),null!=A&&(A[0]=P),null!=x&&(x[0]=L),b[0]+=18,y[0]-=18,k[0]=1}y=0}}if(d=d[0],m=m[0],0!=y)return y;if(b=!!(2&m),!w&&d)return 3;if(null!=s&&(s[0]=!!(16&m)),null!=c&&(c[0]=b),null!=u&&(u[0]=0),c=h[0],m=f[0],d&&b&&null==l){y=0;break}if(4>i){y=7;break}if(w&&d||!w&&!d&&!r(t,n[0],"ALPH")){i=[i],g.na=[g.na],g.P=[g.P],g.Sa=[g.Sa];t:{_=t,y=n,w=i;var k=g.gb;A=g.na,x=g.P,S=g.Sa;P=22,e(null!=_),e(null!=w),L=y[0];var F=w[0];for(e(null!=A),e(null!=S),A[0]=null,x[0]=null,S[0]=0;;){if(y[0]=L,w[0]=F,8>F){y=7;break t}var I=j(_,L+4);if(4294967286<I){y=3;break t}var O=8+I+1&-2;if(P+=O,0<k&&P>k){y=3;break t}if(!r(_,L,"VP8 ")||!r(_,L,"VP8L")){y=0;break t}if(F[0]<O){y=7;break t}r(_,L,"ALPH")||(A[0]=_,x[0]=L+8,S[0]=I),L+=O,F-=O}}if(i=i[0],g.na=g.na[0],g.P=g.P[0],g.Sa=g.Sa[0],0!=y)break}i=[i],g.Ja=[g.Ja],g.xa=[g.xa];t:if(k=t,y=n,w=i,A=g.gb[0],x=g.Ja,S=g.xa,_=y[0],L=!r(k,_,"VP8 "),P=!r(k,_,"VP8L"),e(null!=k),e(null!=w),e(null!=x),e(null!=S),8>w[0])y=7;else{if(L||P){if(k=j(k,_+4),12<=A&&k>A-12){y=3;break t}if(p&&k>w[0]-8){y=7;break t}x[0]=k,y[0]+=8,w[0]-=8,S[0]=P}else S[0]=5<=w[0]&&47==k[_+0]&&!(k[_+4]>>5),x[0]=w[0];y=0}if(i=i[0],g.Ja=g.Ja[0],g.xa=g.xa[0],n=n[0],0!=y)break;if(4294967286<g.Ja)return 3;if(null==u||b||(u[0]=g.xa?2:1),c=[c],m=[m],g.xa){if(5>i){y=7;break}u=c,p=m,b=s,null==t||5>i?t=0:5<=i&&47==t[n+0]&&!(t[n+4]>>5)?(w=[0],k=[0],A=[0],v(x=new N,t,n,i),gt(x,w,k,A)?(null!=u&&(u[0]=w[0]),null!=p&&(p[0]=k[0]),null!=b&&(b[0]=A[0]),t=1):t=0):t=0}else{if(10>i){y=7;break}u=m,null==t||10>i||!Xt(t,n+3,i-3)?t=0:(p=t[n+0]|t[n+1]<<8|t[n+2]<<16,b=16383&(t[n+7]<<8|t[n+6]),t=16383&(t[n+9]<<8|t[n+8]),1&p||3<(p>>1&7)||!(p>>4&1)||p>>5>=g.Ja||!b||!t?t=0:(c&&(c[0]=b),u&&(u[0]=t),t=1))}if(!t)return 3;if(c=c[0],m=m[0],d&&(h[0]!=c||f[0]!=m))return 3;null!=l&&(l[0]=g,l.offset=n-l.w,e(4294967286>n-l.w),e(l.offset==l.ha-i));break}return 0==y||7==y&&d&&null==l?(null!=s&&(s[0]|=null!=g.na&&0<g.na.length),null!=a&&(a[0]=c),null!=o&&(o[0]=m),0):y}function Mr(t,e,r){var n=e.width,i=e.height,a=0,o=0,s=n,c=i;if(e.Da=null!=t&&0<t.Da,e.Da&&(s=t.cd,c=t.bd,a=t.v,o=t.j,11>r||(a&=-2,o&=-2),0>a||0>o||0>=s||0>=c||a+s>n||o+c>i))return 0;if(e.v=a,e.j=o,e.va=a+s,e.o=o+c,e.U=s,e.T=c,e.da=null!=t&&0<t.da,e.da){if(!E(s,c,r=[t.ib],a=[t.hb]))return 0;e.ib=r[0],e.hb=a[0]}return e.ob=null!=t&&t.ob,e.Kb=null==t||!t.Sd,e.da&&(e.ob=e.ib<3*n/4&&e.hb<3*i/4,e.Kb=0),1}function Er(t){if(null==t)return 2;if(11>t.S){var e=t.f.RGBA;e.fb+=(t.height-1)*e.A,e.A=-e.A}else e=t.f.kb,t=t.height,e.O+=(t-1)*e.fa,e.fa=-e.fa,e.N+=(t-1>>1)*e.Ab,e.Ab=-e.Ab,e.W+=(t-1>>1)*e.Db,e.Db=-e.Db,null!=e.F&&(e.J+=(t-1)*e.lb,e.lb=-e.lb);return 0}function qr(t,e,r,n){if(null==n||0>=t||0>=e)return 2;if(null!=r){if(r.Da){var i=r.cd,o=r.bd,s=-2&r.v,c=-2&r.j;if(0>s||0>c||0>=i||0>=o||s+i>t||c+o>e)return 2;t=i,e=o}if(r.da){if(!E(t,e,i=[r.ib],o=[r.hb]))return 2;t=i[0],e=o[0]}}n.width=t,n.height=e;t:{var u=n.width,l=n.height;if(t=n.S,0>=u||0>=l||!(t>=En&&13>t))t=2;else{if(0>=n.Rd&&null==n.sd){s=o=i=e=0;var h=(c=u*zi[t])*l;if(11>t||(o=(l+1)/2*(e=(u+1)/2),12==t&&(s=(i=u)*l)),null==(l=a(h+2*o+s))){t=1;break t}n.sd=l,11>t?((u=n.f.RGBA).eb=l,u.fb=0,u.A=c,u.size=h):((u=n.f.kb).y=l,u.O=0,u.fa=c,u.Fd=h,u.f=l,u.N=0+h,u.Ab=e,u.Cd=o,u.ea=l,u.W=0+h+o,u.Db=e,u.Ed=o,12==t&&(u.F=l,u.J=0+h+2*o),u.Tc=s,u.lb=i)}if(e=1,i=n.S,o=n.width,s=n.height,i>=En&&13>i)if(11>i)t=n.f.RGBA,e&=(c=Math.abs(t.A))*(s-1)+o<=t.size,e&=c>=o*zi[i],e&=null!=t.eb;else{t=n.f.kb,c=(o+1)/2,h=(s+1)/2,u=Math.abs(t.fa);l=Math.abs(t.Ab);var f=Math.abs(t.Db),d=Math.abs(t.lb),p=d*(s-1)+o;e&=u*(s-1)+o<=t.Fd,e&=l*(h-1)+c<=t.Cd,e=(e&=f*(h-1)+c<=t.Ed)&u>=o&l>=c&f>=c,e&=null!=t.y,e&=null!=t.f,e&=null!=t.ea,12==i&&(e&=d>=o,e&=p<=t.Tc,e&=null!=t.F)}else e=0;t=e?0:2}}return 0!=t||null!=r&&r.fd&&(t=Er(n)),t}var Dr=64,Rr=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535,131071,262143,524287,1048575,2097151,4194303,8388607,16777215],Tr=24,Ur=32,zr=8,Hr=[0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7];R("Predictor0","PredictorAdd0"),t.Predictor0=function(){return 4278190080},t.Predictor1=function(t){return t},t.Predictor2=function(t,e,r){return e[r+0]},t.Predictor3=function(t,e,r){return e[r+1]},t.Predictor4=function(t,e,r){return e[r-1]},t.Predictor5=function(t,e,r){return U(U(t,e[r+1]),e[r+0])},t.Predictor6=function(t,e,r){return U(t,e[r-1])},t.Predictor7=function(t,e,r){return U(t,e[r+0])},t.Predictor8=function(t,e,r){return U(e[r-1],e[r+0])},t.Predictor9=function(t,e,r){return U(e[r+0],e[r+1])},t.Predictor10=function(t,e,r){return U(U(t,e[r-1]),U(e[r+0],e[r+1]))},t.Predictor11=function(t,e,r){var n=e[r+0];return 0>=W(n>>24&255,t>>24&255,(e=e[r-1])>>24&255)+W(n>>16&255,t>>16&255,e>>16&255)+W(n>>8&255,t>>8&255,e>>8&255)+W(255&n,255&t,255&e)?n:t},t.Predictor12=function(t,e,r){var n=e[r+0];return(z((t>>24&255)+(n>>24&255)-((e=e[r-1])>>24&255))<<24|z((t>>16&255)+(n>>16&255)-(e>>16&255))<<16|z((t>>8&255)+(n>>8&255)-(e>>8&255))<<8|z((255&t)+(255&n)-(255&e)))>>>0},t.Predictor13=function(t,e,r){var n=e[r-1];return(H((t=U(t,e[r+0]))>>24&255,n>>24&255)<<24|H(t>>16&255,n>>16&255)<<16|H(t>>8&255,n>>8&255)<<8|H(t>>0&255,n>>0&255))>>>0};var Wr=t.PredictorAdd0;t.PredictorAdd1=V,R("Predictor2","PredictorAdd2"),R("Predictor3","PredictorAdd3"),R("Predictor4","PredictorAdd4"),R("Predictor5","PredictorAdd5"),R("Predictor6","PredictorAdd6"),R("Predictor7","PredictorAdd7"),R("Predictor8","PredictorAdd8"),R("Predictor9","PredictorAdd9"),R("Predictor10","PredictorAdd10"),R("Predictor11","PredictorAdd11"),R("Predictor12","PredictorAdd12"),R("Predictor13","PredictorAdd13");var Vr=t.PredictorAdd2;X("ColorIndexInverseTransform","MapARGB","32b",(function(t){return t>>8&255}),(function(t){return t})),X("VP8LColorIndexInverseTransformAlpha","MapAlpha","8b",(function(t){return t}),(function(t){return t>>8&255}));var Gr,Yr=t.ColorIndexInverseTransform,Jr=t.MapARGB,Xr=t.VP8LColorIndexInverseTransformAlpha,Kr=t.MapAlpha,Zr=t.VP8LPredictorsAdd=[];Zr.length=16,(t.VP8LPredictors=[]).length=16,(t.VP8LPredictorsAdd_C=[]).length=16,(t.VP8LPredictors_C=[]).length=16;var $r,Qr,tn,en,rn,nn,an,on,sn,cn,un,ln,hn,fn,dn,pn,gn,mn,vn,bn,yn,wn,Nn,Ln,An,xn,Sn,_n,Pn=a(511),kn=a(2041),Fn=a(225),In=a(767),Cn=0,jn=kn,On=Fn,Bn=In,Mn=Pn,En=0,qn=1,Dn=2,Rn=3,Tn=4,Un=5,zn=6,Hn=7,Wn=8,Vn=9,Gn=10,Yn=[2,3,7],Jn=[3,3,11],Xn=[280,256,256,256,40],Kn=[0,1,1,1,0],Zn=[17,18,0,1,2,3,4,5,16,6,7,8,9,10,11,12,13,14,15],$n=[24,7,23,25,40,6,39,41,22,26,38,42,56,5,55,57,21,27,54,58,37,43,72,4,71,73,20,28,53,59,70,74,36,44,88,69,75,52,60,3,87,89,19,29,86,90,35,45,68,76,85,91,51,61,104,2,103,105,18,30,102,106,34,46,84,92,67,77,101,107,50,62,120,1,119,121,83,93,17,31,100,108,66,78,118,122,33,47,117,123,49,63,99,109,82,94,0,116,124,65,79,16,32,98,110,48,115,125,81,95,64,114,126,97,111,80,113,127,96,112],Qn=[2954,2956,2958,2962,2970,2986,3018,3082,3212,3468,3980,5004],ti=8,ei=[4,5,6,7,8,9,10,10,11,12,13,14,15,16,17,17,18,19,20,20,21,21,22,22,23,23,24,25,25,26,27,28,29,30,31,32,33,34,35,36,37,37,38,39,40,41,42,43,44,45,46,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,76,77,78,79,80,81,82,83,84,85,86,87,88,89,91,93,95,96,98,100,101,102,104,106,108,110,112,114,116,118,122,124,126,128,130,132,134,136,138,140,143,145,148,151,154,157],ri=[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,177,181,185,189,193,197,201,205,209,213,217,221,225,229,234,239,245,249,254,259,264,269,274,279,284],ni=null,ii=[[173,148,140,0],[176,155,140,135,0],[180,157,141,134,130,0],[254,254,243,230,196,177,153,140,133,130,129,0]],ai=[0,1,4,8,5,2,3,6,9,12,13,10,7,11,14,15],oi=[-0,1,-1,2,-2,3,4,6,-3,5,-4,-5,-6,7,-7,8,-8,-9],si=[[[[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128]],[[253,136,254,255,228,219,128,128,128,128,128],[189,129,242,255,227,213,255,219,128,128,128],[106,126,227,252,214,209,255,255,128,128,128]],[[1,98,248,255,236,226,255,255,128,128,128],[181,133,238,254,221,234,255,154,128,128,128],[78,134,202,247,198,180,255,219,128,128,128]],[[1,185,249,255,243,255,128,128,128,128,128],[184,150,247,255,236,224,128,128,128,128,128],[77,110,216,255,236,230,128,128,128,128,128]],[[1,101,251,255,241,255,128,128,128,128,128],[170,139,241,252,236,209,255,255,128,128,128],[37,116,196,243,228,255,255,255,128,128,128]],[[1,204,254,255,245,255,128,128,128,128,128],[207,160,250,255,238,128,128,128,128,128,128],[102,103,231,255,211,171,128,128,128,128,128]],[[1,152,252,255,240,255,128,128,128,128,128],[177,135,243,255,234,225,128,128,128,128,128],[80,129,211,255,194,224,128,128,128,128,128]],[[1,1,255,128,128,128,128,128,128,128,128],[246,1,255,128,128,128,128,128,128,128,128],[255,128,128,128,128,128,128,128,128,128,128]]],[[[198,35,237,223,193,187,162,160,145,155,62],[131,45,198,221,172,176,220,157,252,221,1],[68,47,146,208,149,167,221,162,255,223,128]],[[1,149,241,255,221,224,255,255,128,128,128],[184,141,234,253,222,220,255,199,128,128,128],[81,99,181,242,176,190,249,202,255,255,128]],[[1,129,232,253,214,197,242,196,255,255,128],[99,121,210,250,201,198,255,202,128,128,128],[23,91,163,242,170,187,247,210,255,255,128]],[[1,200,246,255,234,255,128,128,128,128,128],[109,178,241,255,231,245,255,255,128,128,128],[44,130,201,253,205,192,255,255,128,128,128]],[[1,132,239,251,219,209,255,165,128,128,128],[94,136,225,251,218,190,255,255,128,128,128],[22,100,174,245,186,161,255,199,128,128,128]],[[1,182,249,255,232,235,128,128,128,128,128],[124,143,241,255,227,234,128,128,128,128,128],[35,77,181,251,193,211,255,205,128,128,128]],[[1,157,247,255,236,231,255,255,128,128,128],[121,141,235,255,225,227,255,255,128,128,128],[45,99,188,251,195,217,255,224,128,128,128]],[[1,1,251,255,213,255,128,128,128,128,128],[203,1,248,255,255,128,128,128,128,128,128],[137,1,177,255,224,255,128,128,128,128,128]]],[[[253,9,248,251,207,208,255,192,128,128,128],[175,13,224,243,193,185,249,198,255,255,128],[73,17,171,221,161,179,236,167,255,234,128]],[[1,95,247,253,212,183,255,255,128,128,128],[239,90,244,250,211,209,255,255,128,128,128],[155,77,195,248,188,195,255,255,128,128,128]],[[1,24,239,251,218,219,255,205,128,128,128],[201,51,219,255,196,186,128,128,128,128,128],[69,46,190,239,201,218,255,228,128,128,128]],[[1,191,251,255,255,128,128,128,128,128,128],[223,165,249,255,213,255,128,128,128,128,128],[141,124,248,255,255,128,128,128,128,128,128]],[[1,16,248,255,255,128,128,128,128,128,128],[190,36,230,255,236,255,128,128,128,128,128],[149,1,255,128,128,128,128,128,128,128,128]],[[1,226,255,128,128,128,128,128,128,128,128],[247,192,255,128,128,128,128,128,128,128,128],[240,128,255,128,128,128,128,128,128,128,128]],[[1,134,252,255,255,128,128,128,128,128,128],[213,62,250,255,255,128,128,128,128,128,128],[55,93,255,128,128,128,128,128,128,128,128]],[[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128],[128,128,128,128,128,128,128,128,128,128,128]]],[[[202,24,213,235,186,191,220,160,240,175,255],[126,38,182,232,169,184,228,174,255,187,128],[61,46,138,219,151,178,240,170,255,216,128]],[[1,112,230,250,199,191,247,159,255,255,128],[166,109,228,252,211,215,255,174,128,128,128],[39,77,162,232,172,180,245,178,255,255,128]],[[1,52,220,246,198,199,249,220,255,255,128],[124,74,191,243,183,193,250,221,255,255,128],[24,71,130,219,154,170,243,182,255,255,128]],[[1,182,225,249,219,240,255,224,128,128,128],[149,150,226,252,216,205,255,171,128,128,128],[28,108,170,242,183,194,254,223,255,255,128]],[[1,81,230,252,204,203,255,192,128,128,128],[123,102,209,247,188,196,255,233,128,128,128],[20,95,153,243,164,173,255,203,128,128,128]],[[1,222,248,255,216,213,128,128,128,128,128],[168,175,246,252,235,205,255,255,128,128,128],[47,116,215,255,211,212,255,255,128,128,128]],[[1,121,236,253,212,214,255,255,128,128,128],[141,84,213,252,201,202,255,219,128,128,128],[42,80,160,240,162,185,255,205,128,128,128]],[[1,1,255,128,128,128,128,128,128,128,128],[244,1,255,128,128,128,128,128,128,128,128],[238,1,255,128,128,128,128,128,128,128,128]]]],ci=[[[231,120,48,89,115,113,120,152,112],[152,179,64,126,170,118,46,70,95],[175,69,143,80,85,82,72,155,103],[56,58,10,171,218,189,17,13,152],[114,26,17,163,44,195,21,10,173],[121,24,80,195,26,62,44,64,85],[144,71,10,38,171,213,144,34,26],[170,46,55,19,136,160,33,206,71],[63,20,8,114,114,208,12,9,226],[81,40,11,96,182,84,29,16,36]],[[134,183,89,137,98,101,106,165,148],[72,187,100,130,157,111,32,75,80],[66,102,167,99,74,62,40,234,128],[41,53,9,178,241,141,26,8,107],[74,43,26,146,73,166,49,23,157],[65,38,105,160,51,52,31,115,128],[104,79,12,27,217,255,87,17,7],[87,68,71,44,114,51,15,186,23],[47,41,14,110,182,183,21,17,194],[66,45,25,102,197,189,23,18,22]],[[88,88,147,150,42,46,45,196,205],[43,97,183,117,85,38,35,179,61],[39,53,200,87,26,21,43,232,171],[56,34,51,104,114,102,29,93,77],[39,28,85,171,58,165,90,98,64],[34,22,116,206,23,34,43,166,73],[107,54,32,26,51,1,81,43,31],[68,25,106,22,64,171,36,225,114],[34,19,21,102,132,188,16,76,124],[62,18,78,95,85,57,50,48,51]],[[193,101,35,159,215,111,89,46,111],[60,148,31,172,219,228,21,18,111],[112,113,77,85,179,255,38,120,114],[40,42,1,196,245,209,10,25,109],[88,43,29,140,166,213,37,43,154],[61,63,30,155,67,45,68,1,209],[100,80,8,43,154,1,51,26,71],[142,78,78,16,255,128,34,197,171],[41,40,5,102,211,183,4,1,221],[51,50,17,168,209,192,23,25,82]],[[138,31,36,171,27,166,38,44,229],[67,87,58,169,82,115,26,59,179],[63,59,90,180,59,166,93,73,154],[40,40,21,116,143,209,34,39,175],[47,15,16,183,34,223,49,45,183],[46,17,33,183,6,98,15,32,183],[57,46,22,24,128,1,54,17,37],[65,32,73,115,28,128,23,128,205],[40,3,9,115,51,192,18,6,223],[87,37,9,115,59,77,64,21,47]],[[104,55,44,218,9,54,53,130,226],[64,90,70,205,40,41,23,26,57],[54,57,112,184,5,41,38,166,213],[30,34,26,133,152,116,10,32,134],[39,19,53,221,26,114,32,73,255],[31,9,65,234,2,15,1,118,73],[75,32,12,51,192,255,160,43,51],[88,31,35,67,102,85,55,186,85],[56,21,23,111,59,205,45,37,192],[55,38,70,124,73,102,1,34,98]],[[125,98,42,88,104,85,117,175,82],[95,84,53,89,128,100,113,101,45],[75,79,123,47,51,128,81,171,1],[57,17,5,71,102,57,53,41,49],[38,33,13,121,57,73,26,1,85],[41,10,67,138,77,110,90,47,114],[115,21,2,10,102,255,166,23,6],[101,29,16,10,85,128,101,196,26],[57,18,10,102,102,213,34,20,43],[117,20,15,36,163,128,68,1,26]],[[102,61,71,37,34,53,31,243,192],[69,60,71,38,73,119,28,222,37],[68,45,128,34,1,47,11,245,171],[62,17,19,70,146,85,55,62,70],[37,43,37,154,100,163,85,160,1],[63,9,92,136,28,64,32,201,85],[75,15,9,9,64,255,184,119,16],[86,6,28,5,64,255,25,248,1],[56,8,17,132,137,255,55,116,128],[58,15,20,82,135,57,26,121,40]],[[164,50,31,137,154,133,25,35,218],[51,103,44,131,131,123,31,6,158],[86,40,64,135,148,224,45,183,128],[22,26,17,131,240,154,14,1,209],[45,16,21,91,64,222,7,1,197],[56,21,39,155,60,138,23,102,213],[83,12,13,54,192,255,68,47,28],[85,26,85,85,128,128,32,146,171],[18,11,7,63,144,171,4,4,246],[35,27,10,146,174,171,12,26,128]],[[190,80,35,99,180,80,126,54,45],[85,126,47,87,176,51,41,20,32],[101,75,128,139,118,146,116,128,85],[56,41,15,176,236,85,37,9,62],[71,30,17,119,118,255,17,18,138],[101,38,60,138,55,70,43,26,142],[146,36,19,30,171,255,97,27,20],[138,45,61,62,219,1,81,188,64],[32,41,20,117,151,142,20,21,163],[112,19,12,61,195,128,48,4,24]]],ui=[[[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[176,246,255,255,255,255,255,255,255,255,255],[223,241,252,255,255,255,255,255,255,255,255],[249,253,253,255,255,255,255,255,255,255,255]],[[255,244,252,255,255,255,255,255,255,255,255],[234,254,254,255,255,255,255,255,255,255,255],[253,255,255,255,255,255,255,255,255,255,255]],[[255,246,254,255,255,255,255,255,255,255,255],[239,253,254,255,255,255,255,255,255,255,255],[254,255,254,255,255,255,255,255,255,255,255]],[[255,248,254,255,255,255,255,255,255,255,255],[251,255,254,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[251,254,254,255,255,255,255,255,255,255,255],[254,255,254,255,255,255,255,255,255,255,255]],[[255,254,253,255,254,255,255,255,255,255,255],[250,255,254,255,254,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[217,255,255,255,255,255,255,255,255,255,255],[225,252,241,253,255,255,254,255,255,255,255],[234,250,241,250,253,255,253,254,255,255,255]],[[255,254,255,255,255,255,255,255,255,255,255],[223,254,254,255,255,255,255,255,255,255,255],[238,253,254,254,255,255,255,255,255,255,255]],[[255,248,254,255,255,255,255,255,255,255,255],[249,254,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,255,255,255,255,255,255,255,255,255],[247,254,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[252,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,254,255,255,255,255,255,255,255,255],[253,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,253,255,255,255,255,255,255,255,255],[250,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[186,251,250,255,255,255,255,255,255,255,255],[234,251,244,254,255,255,255,255,255,255,255],[251,251,243,253,254,255,254,255,255,255,255]],[[255,253,254,255,255,255,255,255,255,255,255],[236,253,254,255,255,255,255,255,255,255,255],[251,253,253,254,254,255,255,255,255,255,255]],[[255,254,254,255,255,255,255,255,255,255,255],[254,254,254,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,254,255,255,255,255,255,255,255,255,255],[254,254,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]],[[[248,255,255,255,255,255,255,255,255,255,255],[250,254,252,254,255,255,255,255,255,255,255],[248,254,249,253,255,255,255,255,255,255,255]],[[255,253,253,255,255,255,255,255,255,255,255],[246,253,253,255,255,255,255,255,255,255,255],[252,254,251,254,254,255,255,255,255,255,255]],[[255,254,252,255,255,255,255,255,255,255,255],[248,254,253,255,255,255,255,255,255,255,255],[253,255,254,254,255,255,255,255,255,255,255]],[[255,251,254,255,255,255,255,255,255,255,255],[245,251,254,255,255,255,255,255,255,255,255],[253,253,254,255,255,255,255,255,255,255,255]],[[255,251,253,255,255,255,255,255,255,255,255],[252,253,254,255,255,255,255,255,255,255,255],[255,254,255,255,255,255,255,255,255,255,255]],[[255,252,255,255,255,255,255,255,255,255,255],[249,255,254,255,255,255,255,255,255,255,255],[255,255,254,255,255,255,255,255,255,255,255]],[[255,255,253,255,255,255,255,255,255,255,255],[250,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]],[[255,255,255,255,255,255,255,255,255,255,255],[254,255,255,255,255,255,255,255,255,255,255],[255,255,255,255,255,255,255,255,255,255,255]]]],li=[0,1,2,3,6,4,5,6,6,6,6,6,6,6,6,7,0],hi=[],fi=[],di=[],pi=1,gi=2,mi=[],vi=[];vr("UpsampleRgbLinePair",Ar,3),vr("UpsampleBgrLinePair",xr,3),vr("UpsampleRgbaLinePair",Fr,4),vr("UpsampleBgraLinePair",kr,4),vr("UpsampleArgbLinePair",Pr,4),vr("UpsampleRgba4444LinePair",_r,2),vr("UpsampleRgb565LinePair",Sr,2);var bi=t.UpsampleRgbLinePair,yi=t.UpsampleBgrLinePair,wi=t.UpsampleRgbaLinePair,Ni=t.UpsampleBgraLinePair,Li=t.UpsampleArgbLinePair,Ai=t.UpsampleRgba4444LinePair,xi=t.UpsampleRgb565LinePair,Si=16,_i=1<<Si-1,Pi=-227,ki=482,Fi=6,Ii=(256<<Fi)-1,Ci=0,ji=a(256),Oi=a(256),Bi=a(256),Mi=a(256),Ei=a(ki-Pi),qi=a(ki-Pi);Ir("YuvToRgbRow",Ar,3),Ir("YuvToBgrRow",xr,3),Ir("YuvToRgbaRow",Fr,4),Ir("YuvToBgraRow",kr,4),Ir("YuvToArgbRow",Pr,4),Ir("YuvToRgba4444Row",_r,2),Ir("YuvToRgb565Row",Sr,2);var Di=[0,4,8,12,128,132,136,140,256,260,264,268,384,388,392,396],Ri=[0,2,8],Ti=[8,7,6,4,4,2,2,2,1,1,1,1],Ui=1;this.WebPDecodeRGBA=function(t,r,n,i,a){var o=qn,s=new rr,c=new ot;s.ba=c,c.S=o,c.width=[c.width],c.height=[c.height];var u=c.width,l=c.height,h=new st;if(null==h||null==t)var f=2;else e(null!=h),f=Br(t,r,n,h.width,h.height,h.Pd,h.Qd,h.format,null);if(0!=f?u=0:(null!=u&&(u[0]=h.width[0]),null!=l&&(l[0]=h.height[0]),u=1),u){c.width=c.width[0],c.height=c.height[0],null!=i&&(i[0]=c.width),null!=a&&(a[0]=c.height);t:{if(i=new Gt,(a=new nr).data=t,a.w=r,a.ha=n,a.kd=1,r=[0],e(null!=a),(0==(t=Br(a.data,a.w,a.ha,null,null,null,r,null,a))||7==t)&&r[0]&&(t=4),0==(r=t)){if(e(null!=s),i.data=a.data,i.w=a.w+a.offset,i.ha=a.ha-a.offset,i.put=dt,i.ac=ft,i.bc=pt,i.ma=s,a.xa){if(null==(t=kt())){s=1;break t}if(function(t,r){var n=[0],i=[0],a=[0];e:for(;;){if(null==t)return 0;if(null==r)return t.a=2,0;if(t.l=r,t.a=0,v(t.m,r.data,r.w,r.ha),!gt(t.m,n,i,a)){t.a=3;break e}if(t.xb=gi,r.width=n[0],r.height=i[0],!Ft(n[0],i[0],1,t,null))break e;return 1}return e(0!=t.a),0}(t,i)){if(i=0==(r=qr(i.width,i.height,s.Oa,s.ba))){e:{i=t;r:for(;;){if(null==i){i=0;break e}if(e(null!=i.s.yc),e(null!=i.s.Ya),e(0<i.s.Wb),e(null!=(n=i.l)),e(null!=(a=n.ma)),0!=i.xb){if(i.ca=a.ba,i.tb=a.tb,e(null!=i.ca),!Mr(a.Oa,n,Rn)){i.a=2;break r}if(!It(i,n.width))break r;if(n.da)break r;if((n.da||nt(i.ca.S))&&mr(),11>i.ca.S||(alert("todo:WebPInitConvertARGBToYUV"),null!=i.ca.f.kb.F&&mr()),i.Pb&&0<i.s.ua&&null==i.s.vb.X&&!O(i.s.vb,i.s.Wa.Xa)){i.a=1;break r}i.xb=0}if(!_t(i,i.V,i.Ba,i.c,i.i,n.o,Lt))break r;a.Dc=i.Ma,i=1;break e}e(0!=i.a),i=0}i=!i}i&&(r=t.a)}else r=t.a}else{if(null==(t=new Yt)){s=1;break t}if(t.Fa=a.na,t.P=a.P,t.qc=a.Sa,Kt(t,i)){if(0==(r=qr(i.width,i.height,s.Oa,s.ba))){if(t.Aa=0,n=s.Oa,e(null!=(a=t)),null!=n){if(0<(u=0>(u=n.Md)?0:100<u?255:255*u/100)){for(l=h=0;4>l;++l)12>(f=a.pb[l]).lc&&(f.ia=u*Ti[0>f.lc?0:f.lc]>>3),h|=f.ia;h&&(alert("todo:VP8InitRandom"),a.ia=1)}a.Ga=n.Id,100<a.Ga?a.Ga=100:0>a.Ga&&(a.Ga=0)}Qt(t,i)||(r=t.a)}}else r=t.a}0==r&&null!=s.Oa&&s.Oa.fd&&(r=Er(s.ba))}s=r}o=0!=s?null:11>o?c.f.RGBA.eb:c.f.kb.y}else o=null;return o};var zi=[3,4,3,4,4,2,2,4,4,4,2,1,1]};function u(t,e){for(var r="",n=0;n<4;n++)r+=String.fromCharCode(t[e++]);return r}function l(t,e){return(t[e+0]<<0|t[e+1]<<8|t[e+2]<<16)>>>0}function h(t,e){return(t[e+0]<<0|t[e+1]<<8|t[e+2]<<16|t[e+3]<<24)>>>0}new c;var f=[0],d=[0],p=[],g=new c,m=t,v=function(t,e){var r={},n=0,i=!1,a=0,o=0;if(r.frames=[],! -/** @license - * Copyright (c) 2017 Dominik Homberger - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - https://webpjs.appspot.com - WebPRiffParser dominikhlbg@gmail.com - */ -function(t,e,r,n){for(var i=0;i<n;i++)if(t[e+i]!=r.charCodeAt(i))return!0;return!1}(t,e,"RIFF",4)){var s,c;h(t,e+=4);for(e+=8;e<t.length;){var f=u(t,e),d=h(t,e+=4);e+=4;var p=d+(1&d);switch(f){case"VP8 ":case"VP8L":void 0===r.frames[n]&&(r.frames[n]={});(v=r.frames[n]).src_off=i?o:e-8,v.src_size=a+d+8,n++,i&&(i=!1,a=0,o=0);break;case"VP8X":(v=r.header={}).feature_flags=t[e];var g=e+4;v.canvas_width=1+l(t,g);g+=3;v.canvas_height=1+l(t,g);g+=3;break;case"ALPH":i=!0,a=p+8,o=e-8;break;case"ANIM":(v=r.header).bgcolor=h(t,e);g=e+4;v.loop_count=(s=t)[(c=g)+0]<<0|s[c+1]<<8;g+=2;break;case"ANMF":var m,v;(v=r.frames[n]={}).offset_x=2*l(t,e),e+=3,v.offset_y=2*l(t,e),e+=3,v.width=1+l(t,e),e+=3,v.height=1+l(t,e),e+=3,v.duration=l(t,e),e+=3,m=t[e++],v.dispose=1&m,v.blend=m>>1&1}"ANMF"!=f&&(e+=p)}return r}}(m,0);v.response=m,v.rgbaoutput=!0,v.dataurl=!1;var b=v.header?v.header:null,y=v.frames?v.frames:null;if(b){b.loop_counter=b.loop_count,f=[b.canvas_height],d=[b.canvas_width];for(var w=0;w<y.length&&0!=y[w].blend;w++);}var N=y[0],L=g.WebPDecodeRGBA(m,N.src_off,N.src_size,d,f);N.rgba=L,N.imgwidth=d[0],N.imgheight=f[0];for(var A=0;A<d[0]*f[0]*4;A++)p[A]=L[A];return this.width=d,this.height=f,this.data=p,this}!function(t){var e=function(){return!0},r=function(e,r,i,u){var l=4,h=o;switch(u){case t.image_compression.FAST:l=1,h=a;break;case t.image_compression.MEDIUM:l=6,h=s;break;case t.image_compression.SLOW:l=9,h=c}var f=_e(e=n(e,r,i,h),{level:l});return t.__addimage__.arrayBufferToBinaryString(f)},n=function(t,e,r,n){for(var i,a,o,s=t.length/e,c=new Uint8Array(t.length+s),u=l(),f=0;f<s;f+=1){if(o=f*e,i=t.subarray(o,o+e),n)c.set(n(i,r,a),o+f);else{for(var d,p=u.length,g=[];d<p;d+=1)g[d]=u[d](i,r,a);var m=h(g.concat());c.set(g[m],o+f)}a=i}return c},i=function(t){var e=Array.apply([],t);return e.unshift(0),e},a=function(t,e){var r,n=[],i=t.length;n[0]=1;for(var a=0;a<i;a+=1)r=t[a-e]||0,n[a+1]=t[a]-r+256&255;return n},o=function(t,e,r){var n,i=[],a=t.length;i[0]=2;for(var o=0;o<a;o+=1)n=r&&r[o]||0,i[o+1]=t[o]-n+256&255;return i},s=function(t,e,r){var n,i,a=[],o=t.length;a[0]=3;for(var s=0;s<o;s+=1)n=t[s-e]||0,i=r&&r[s]||0,a[s+1]=t[s]+256-(n+i>>>1)&255;return a},c=function(t,e,r){var n,i,a,o,s=[],c=t.length;s[0]=4;for(var l=0;l<c;l+=1)n=t[l-e]||0,i=r&&r[l]||0,a=r&&r[l-e]||0,o=u(n,i,a),s[l+1]=t[l]-o+256&255;return s},u=function(t,e,r){if(t===e&&e===r)return t;var n=Math.abs(e-r),i=Math.abs(t-r),a=Math.abs(t+e-r-r);return n<=i&&n<=a?t:i<=a?e:r},l=function(){return[i,a,o,s,c]},h=function(t){var e=t.map((function(t){return t.reduce((function(t,e){return t+Math.abs(e)}),0)}));return e.indexOf(Math.min.apply(null,e))};t.processPNG=function(n,i,a,o){var s,c,u,l,h,f,d,p,g,m,v,b,y,w,N,L=this.decode.FLATE_DECODE,A="";if(this.__addimage__.isArrayBuffer(n)&&(n=new Uint8Array(n)),this.__addimage__.isArrayBufferView(n)){if(n=(u=new Oe(n)).imgData,c=u.bits,s=u.colorSpace,h=u.colors,-1!==[4,6].indexOf(u.colorType)){if(8===u.bits){g=(p=32==u.pixelBitlength?new Uint32Array(u.decodePixels().buffer):16==u.pixelBitlength?new Uint16Array(u.decodePixels().buffer):new Uint8Array(u.decodePixels().buffer)).length,v=new Uint8Array(g*u.colors),m=new Uint8Array(g);var x,S=u.pixelBitlength-u.bits;for(w=0,N=0;w<g;w++){for(y=p[w],x=0;x<S;)v[N++]=y>>>x&255,x+=u.bits;m[w]=y>>>x&255}}if(16===u.bits){g=(p=new Uint32Array(u.decodePixels().buffer)).length,v=new Uint8Array(g*(32/u.pixelBitlength)*u.colors),m=new Uint8Array(g*(32/u.pixelBitlength)),b=u.colors>1,w=0,N=0;for(var _=0;w<g;)y=p[w++],v[N++]=y>>>0&255,b&&(v[N++]=y>>>16&255,y=p[w++],v[N++]=y>>>0&255),m[_++]=y>>>16&255;c=8}o!==t.image_compression.NONE&&e()?(n=r(v,u.width*u.colors,u.colors,o),d=r(m,u.width,1,o)):(n=v,d=m,L=void 0)}if(3===u.colorType&&(s=this.color_spaces.INDEXED,f=u.palette,u.transparency.indexed)){var P=u.transparency.indexed,k=0;for(w=0,g=P.length;w<g;++w)k+=P[w];if((k/=255)===g-1&&-1!==P.indexOf(0))l=[P.indexOf(0)];else if(k!==g){for(p=u.decodePixels(),m=new Uint8Array(p.length),w=0,g=p.length;w<g;w++)m[w]=P[p[w]];d=r(m,u.width,1)}}var F=function(e){var r;switch(e){case t.image_compression.FAST:r=11;break;case t.image_compression.MEDIUM:r=13;break;case t.image_compression.SLOW:r=14;break;default:r=12}return r}(o);return L===this.decode.FLATE_DECODE&&(A="/Predictor "+F+" "),A+="/Colors "+h+" /BitsPerComponent "+c+" /Columns "+u.width,(this.__addimage__.isArrayBuffer(n)||this.__addimage__.isArrayBufferView(n))&&(n=this.__addimage__.arrayBufferToBinaryString(n)),(d&&this.__addimage__.isArrayBuffer(d)||this.__addimage__.isArrayBufferView(d))&&(d=this.__addimage__.arrayBufferToBinaryString(d)),{alias:a,data:n,index:i,filter:L,decodeParameters:A,transparency:l,palette:f,sMask:d,predictor:F,width:u.width,height:u.height,bitsPerComponent:c,colorSpace:s}}}}(M.API),function(t){t.processGIF89A=function(e,r,n,i){var a=new Be(e),o=a.width,s=a.height,c=[];a.decodeAndBlitFrameRGBA(0,c);var u={data:c,width:o,height:s},l=new Ee(100).encode(u,100);return t.processJPEG.call(this,l,r,n,i)},t.processGIF87A=t.processGIF89A}(M.API),qe.prototype.parseHeader=function(){if(this.fileSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.reserved=this.datav.getUint32(this.pos,!0),this.pos+=4,this.offset=this.datav.getUint32(this.pos,!0),this.pos+=4,this.headerSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.width=this.datav.getUint32(this.pos,!0),this.pos+=4,this.height=this.datav.getInt32(this.pos,!0),this.pos+=4,this.planes=this.datav.getUint16(this.pos,!0),this.pos+=2,this.bitPP=this.datav.getUint16(this.pos,!0),this.pos+=2,this.compress=this.datav.getUint32(this.pos,!0),this.pos+=4,this.rawSize=this.datav.getUint32(this.pos,!0),this.pos+=4,this.hr=this.datav.getUint32(this.pos,!0),this.pos+=4,this.vr=this.datav.getUint32(this.pos,!0),this.pos+=4,this.colors=this.datav.getUint32(this.pos,!0),this.pos+=4,this.importantColors=this.datav.getUint32(this.pos,!0),this.pos+=4,16===this.bitPP&&this.is_with_alpha&&(this.bitPP=15),this.bitPP<15){var t=0===this.colors?1<<this.bitPP:this.colors;this.palette=new Array(t);for(var e=0;e<t;e++){var r=this.datav.getUint8(this.pos++,!0),n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0);this.palette[e]={red:i,green:n,blue:r,quad:a}}}this.height<0&&(this.height*=-1,this.bottom_up=!1)},qe.prototype.parseBGR=function(){this.pos=this.offset;try{var t="bit"+this.bitPP,e=this.width*this.height*4;this.data=new Uint8Array(e),this[t]()}catch(t){i.log("bit decode error:"+t)}},qe.prototype.bit1=function(){var t,e=Math.ceil(this.width/8),r=e%4;for(t=this.height-1;t>=0;t--){for(var n=this.bottom_up?t:this.height-1-t,i=0;i<e;i++)for(var a=this.datav.getUint8(this.pos++,!0),o=n*this.width*4+8*i*4,s=0;s<8&&8*i+s<this.width;s++){var c=this.palette[a>>7-s&1];this.data[o+4*s]=c.blue,this.data[o+4*s+1]=c.green,this.data[o+4*s+2]=c.red,this.data[o+4*s+3]=255}0!==r&&(this.pos+=4-r)}},qe.prototype.bit4=function(){for(var t=Math.ceil(this.width/2),e=t%4,r=this.height-1;r>=0;r--){for(var n=this.bottom_up?r:this.height-1-r,i=0;i<t;i++){var a=this.datav.getUint8(this.pos++,!0),o=n*this.width*4+2*i*4,s=a>>4,c=15&a,u=this.palette[s];if(this.data[o]=u.blue,this.data[o+1]=u.green,this.data[o+2]=u.red,this.data[o+3]=255,2*i+1>=this.width)break;u=this.palette[c],this.data[o+4]=u.blue,this.data[o+4+1]=u.green,this.data[o+4+2]=u.red,this.data[o+4+3]=255}0!==e&&(this.pos+=4-e)}},qe.prototype.bit8=function(){for(var t=this.width%4,e=this.height-1;e>=0;e--){for(var r=this.bottom_up?e:this.height-1-e,n=0;n<this.width;n++){var i=this.datav.getUint8(this.pos++,!0),a=r*this.width*4+4*n;if(i<this.palette.length){var o=this.palette[i];this.data[a]=o.red,this.data[a+1]=o.green,this.data[a+2]=o.blue,this.data[a+3]=255}else this.data[a]=255,this.data[a+1]=255,this.data[a+2]=255,this.data[a+3]=255}0!==t&&(this.pos+=4-t)}},qe.prototype.bit15=function(){for(var t=this.width%3,e=parseInt("11111",2),r=this.height-1;r>=0;r--){for(var n=this.bottom_up?r:this.height-1-r,i=0;i<this.width;i++){var a=this.datav.getUint16(this.pos,!0);this.pos+=2;var o=(a&e)/e*255|0,s=(a>>5&e)/e*255|0,c=(a>>10&e)/e*255|0,u=a>>15?255:0,l=n*this.width*4+4*i;this.data[l]=c,this.data[l+1]=s,this.data[l+2]=o,this.data[l+3]=u}this.pos+=t}},qe.prototype.bit16=function(){for(var t=this.width%3,e=parseInt("11111",2),r=parseInt("111111",2),n=this.height-1;n>=0;n--){for(var i=this.bottom_up?n:this.height-1-n,a=0;a<this.width;a++){var o=this.datav.getUint16(this.pos,!0);this.pos+=2;var s=(o&e)/e*255|0,c=(o>>5&r)/r*255|0,u=(o>>11)/e*255|0,l=i*this.width*4+4*a;this.data[l]=u,this.data[l+1]=c,this.data[l+2]=s,this.data[l+3]=255}this.pos+=t}},qe.prototype.bit24=function(){for(var t=this.height-1;t>=0;t--){for(var e=this.bottom_up?t:this.height-1-t,r=0;r<this.width;r++){var n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0),o=e*this.width*4+4*r;this.data[o]=a,this.data[o+1]=i,this.data[o+2]=n,this.data[o+3]=255}this.pos+=this.width%4}},qe.prototype.bit32=function(){for(var t=this.height-1;t>=0;t--)for(var e=this.bottom_up?t:this.height-1-t,r=0;r<this.width;r++){var n=this.datav.getUint8(this.pos++,!0),i=this.datav.getUint8(this.pos++,!0),a=this.datav.getUint8(this.pos++,!0),o=this.datav.getUint8(this.pos++,!0),s=e*this.width*4+4*r;this.data[s]=a,this.data[s+1]=i,this.data[s+2]=n,this.data[s+3]=o}},qe.prototype.getData=function(){return this.data}, -/** - * @license - * Copyright (c) 2018 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.processBMP=function(e,r,n,i){var a=new qe(e,!1),o=a.width,s=a.height,c={data:a.getData(),width:o,height:s},u=new Ee(100).encode(c,100);return t.processJPEG.call(this,u,r,n,i)}}(M.API),De.prototype.getData=function(){return this.data}, -/** - * @license - * Copyright (c) 2019 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.processWEBP=function(e,r,n,i){var a=new De(e,!1),o=a.width,s=a.height,c={data:a.getData(),width:o,height:s},u=new Ee(100).encode(c,100);return t.processJPEG.call(this,u,r,n,i)}}(M.API), -/** - * @license - * - * Copyright (c) 2021 Antti Palola, https://github.com/Pantura - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){t.processRGBA=function(t,e,r){for(var n=t.data,i=n.length,a=new Uint8Array(i/4*3),o=new Uint8Array(i/4),s=0,c=0,u=0;u<i;u+=4){var l=n[u],h=n[u+1],f=n[u+2],d=n[u+3];a[s++]=l,a[s++]=h,a[s++]=f,o[c++]=d}var p=this.__addimage__.arrayBufferToBinaryString(a);return{alpha:this.__addimage__.arrayBufferToBinaryString(o),data:p,index:e,alias:r,colorSpace:"DeviceRGB",bitsPerComponent:8,width:t.width,height:t.height}}}(M.API), -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){t.setLanguage=function(t){return void 0===this.internal.languageSettings&&(this.internal.languageSettings={},this.internal.languageSettings.isSubscribed=!1),void 0!=={af:"Afrikaans",sq:"Albanian",ar:"Arabic (Standard)","ar-DZ":"Arabic (Algeria)","ar-BH":"Arabic (Bahrain)","ar-EG":"Arabic (Egypt)","ar-IQ":"Arabic (Iraq)","ar-JO":"Arabic (Jordan)","ar-KW":"Arabic (Kuwait)","ar-LB":"Arabic (Lebanon)","ar-LY":"Arabic (Libya)","ar-MA":"Arabic (Morocco)","ar-OM":"Arabic (Oman)","ar-QA":"Arabic (Qatar)","ar-SA":"Arabic (Saudi Arabia)","ar-SY":"Arabic (Syria)","ar-TN":"Arabic (Tunisia)","ar-AE":"Arabic (U.A.E.)","ar-YE":"Arabic (Yemen)",an:"Aragonese",hy:"Armenian",as:"Assamese",ast:"Asturian",az:"Azerbaijani",eu:"Basque",be:"Belarusian",bn:"Bengali",bs:"Bosnian",br:"Breton",bg:"Bulgarian",my:"Burmese",ca:"Catalan",ch:"Chamorro",ce:"Chechen",zh:"Chinese","zh-HK":"Chinese (Hong Kong)","zh-CN":"Chinese (PRC)","zh-SG":"Chinese (Singapore)","zh-TW":"Chinese (Taiwan)",cv:"Chuvash",co:"Corsican",cr:"Cree",hr:"Croatian",cs:"Czech",da:"Danish",nl:"Dutch (Standard)","nl-BE":"Dutch (Belgian)",en:"English","en-AU":"English (Australia)","en-BZ":"English (Belize)","en-CA":"English (Canada)","en-IE":"English (Ireland)","en-JM":"English (Jamaica)","en-NZ":"English (New Zealand)","en-PH":"English (Philippines)","en-ZA":"English (South Africa)","en-TT":"English (Trinidad & Tobago)","en-GB":"English (United Kingdom)","en-US":"English (United States)","en-ZW":"English (Zimbabwe)",eo:"Esperanto",et:"Estonian",fo:"Faeroese",fj:"Fijian",fi:"Finnish",fr:"French (Standard)","fr-BE":"French (Belgium)","fr-CA":"French (Canada)","fr-FR":"French (France)","fr-LU":"French (Luxembourg)","fr-MC":"French (Monaco)","fr-CH":"French (Switzerland)",fy:"Frisian",fur:"Friulian",gd:"Gaelic (Scots)","gd-IE":"Gaelic (Irish)",gl:"Galacian",ka:"Georgian",de:"German (Standard)","de-AT":"German (Austria)","de-DE":"German (Germany)","de-LI":"German (Liechtenstein)","de-LU":"German (Luxembourg)","de-CH":"German (Switzerland)",el:"Greek",gu:"Gujurati",ht:"Haitian",he:"Hebrew",hi:"Hindi",hu:"Hungarian",is:"Icelandic",id:"Indonesian",iu:"Inuktitut",ga:"Irish",it:"Italian (Standard)","it-CH":"Italian (Switzerland)",ja:"Japanese",kn:"Kannada",ks:"Kashmiri",kk:"Kazakh",km:"Khmer",ky:"Kirghiz",tlh:"Klingon",ko:"Korean","ko-KP":"Korean (North Korea)","ko-KR":"Korean (South Korea)",la:"Latin",lv:"Latvian",lt:"Lithuanian",lb:"Luxembourgish",mk:"North Macedonia",ms:"Malay",ml:"Malayalam",mt:"Maltese",mi:"Maori",mr:"Marathi",mo:"Moldavian",nv:"Navajo",ng:"Ndonga",ne:"Nepali",no:"Norwegian",nb:"Norwegian (Bokmal)",nn:"Norwegian (Nynorsk)",oc:"Occitan",or:"Oriya",om:"Oromo",fa:"Persian","fa-IR":"Persian/Iran",pl:"Polish",pt:"Portuguese","pt-BR":"Portuguese (Brazil)",pa:"Punjabi","pa-IN":"Punjabi (India)","pa-PK":"Punjabi (Pakistan)",qu:"Quechua",rm:"Rhaeto-Romanic",ro:"Romanian","ro-MO":"Romanian (Moldavia)",ru:"Russian","ru-MO":"Russian (Moldavia)",sz:"Sami (Lappish)",sg:"Sango",sa:"Sanskrit",sc:"Sardinian",sd:"Sindhi",si:"Singhalese",sr:"Serbian",sk:"Slovak",sl:"Slovenian",so:"Somani",sb:"Sorbian",es:"Spanish","es-AR":"Spanish (Argentina)","es-BO":"Spanish (Bolivia)","es-CL":"Spanish (Chile)","es-CO":"Spanish (Colombia)","es-CR":"Spanish (Costa Rica)","es-DO":"Spanish (Dominican Republic)","es-EC":"Spanish (Ecuador)","es-SV":"Spanish (El Salvador)","es-GT":"Spanish (Guatemala)","es-HN":"Spanish (Honduras)","es-MX":"Spanish (Mexico)","es-NI":"Spanish (Nicaragua)","es-PA":"Spanish (Panama)","es-PY":"Spanish (Paraguay)","es-PE":"Spanish (Peru)","es-PR":"Spanish (Puerto Rico)","es-ES":"Spanish (Spain)","es-UY":"Spanish (Uruguay)","es-VE":"Spanish (Venezuela)",sx:"Sutu",sw:"Swahili",sv:"Swedish","sv-FI":"Swedish (Finland)","sv-SV":"Swedish (Sweden)",ta:"Tamil",tt:"Tatar",te:"Teluga",th:"Thai",tig:"Tigre",ts:"Tsonga",tn:"Tswana",tr:"Turkish",tk:"Turkmen",uk:"Ukrainian",hsb:"Upper Sorbian",ur:"Urdu",ve:"Venda",vi:"Vietnamese",vo:"Volapuk",wa:"Walloon",cy:"Welsh",xh:"Xhosa",ji:"Yiddish",zu:"Zulu"}[t]&&(this.internal.languageSettings.languageCode=t,!1===this.internal.languageSettings.isSubscribed&&(this.internal.events.subscribe("putCatalog",(function(){this.internal.write("/Lang ("+this.internal.languageSettings.languageCode+")")})),this.internal.languageSettings.isSubscribed=!0)),this}}(M.API),ke=M.API,Fe=ke.getCharWidthsArray=function(t,r){var n,i,a=(r=r||{}).font||this.internal.getFont(),o=r.fontSize||this.internal.getFontSize(),s=r.charSpace||this.internal.getCharSpace(),c=r.widths?r.widths:a.metadata.Unicode.widths,u=c.fof?c.fof:1,l=r.kerning?r.kerning:a.metadata.Unicode.kerning,h=l.fof?l.fof:1,f=!1!==r.doKerning,d=0,p=t.length,g=0,m=c[0]||u,v=[];for(n=0;n<p;n++)i=t.charCodeAt(n),"function"==typeof a.metadata.widthOfString?v.push((a.metadata.widthOfGlyph(a.metadata.characterToGlyph(i))+s*(1e3/o)||0)/1e3):(d=f&&"object"===e(l[i])&&!isNaN(parseInt(l[i][g],10))?l[i][g]/h:0,v.push((c[i]||m)/u+d)),g=i;return v},Ie=ke.getStringUnitWidth=function(t,e){var r=(e=e||{}).fontSize||this.internal.getFontSize(),n=e.font||this.internal.getFont(),i=e.charSpace||this.internal.getCharSpace();return ke.processArabic&&(t=ke.processArabic(t)),"function"==typeof n.metadata.widthOfString?n.metadata.widthOfString(t,r,i)/r:Fe.apply(this,arguments).reduce((function(t,e){return t+e}),0)},Ce=function(t,e,r,n){for(var i=[],a=0,o=t.length,s=0;a!==o&&s+e[a]<r;)s+=e[a],a++;i.push(t.slice(0,a));var c=a;for(s=0;a!==o;)s+e[a]>n&&(i.push(t.slice(c,a)),s=0,c=a),s+=e[a],a++;return c!==a&&i.push(t.slice(c,a)),i},je=function(t,e,r){r||(r={});var n,i,a,o,s,c,u,l=[],h=[l],f=r.textIndent||0,d=0,p=0,g=t.split(" "),m=Fe.apply(this,[" ",r])[0];if(c=-1===r.lineIndent?g[0].length+2:r.lineIndent||0){var v=Array(c).join(" "),b=[];g.map((function(t){(t=t.split(/\s*\n/)).length>1?b=b.concat(t.map((function(t,e){return(e&&t.length?"\n":"")+t}))):b.push(t[0])})),g=b,c=Ie.apply(this,[v,r])}for(a=0,o=g.length;a<o;a++){var y=0;if(n=g[a],c&&"\n"==n[0]&&(n=n.substr(1),y=1),f+d+(p=(i=Fe.apply(this,[n,r])).reduce((function(t,e){return t+e}),0))>e||y){if(p>e){for(s=Ce.apply(this,[n,i,e-(f+d),e]),l.push(s.shift()),l=[s.pop()];s.length;)h.push([s.shift()]);p=i.slice(n.length-(l[0]?l[0].length:0)).reduce((function(t,e){return t+e}),0)}else l=[n];h.push(l),f=p+c,d=m}else l.push(n),f+=d+p,d=m}return u=c?function(t,e){return(e?v:"")+t.join(" ")}:function(t){return t.join(" ")},h.map(u)},ke.splitTextToSize=function(t,e,r){var n,i=(r=r||{}).fontSize||this.internal.getFontSize(),a=function(t){if(t.widths&&t.kerning)return{widths:t.widths,kerning:t.kerning};var e=this.internal.getFont(t.fontName,t.fontStyle);return e.metadata.Unicode?{widths:e.metadata.Unicode.widths||{0:1},kerning:e.metadata.Unicode.kerning||{}}:{font:e.metadata,fontSize:this.internal.getFontSize(),charSpace:this.internal.getCharSpace()}}.call(this,r);n=Array.isArray(t)?t:String(t).split(/\r?\n/);var o=1*this.internal.scaleFactor*e/i;a.textIndent=r.textIndent?1*r.textIndent*this.internal.scaleFactor/i:0,a.lineIndent=r.lineIndent;var s,c,u=[];for(s=0,c=n.length;s<c;s++)u=u.concat(je.apply(this,[n[s],o,a]));return u},function(t){t.__fontmetrics__=t.__fontmetrics__||{};for(var r="klmnopqrstuvwxyz",n={},i={},a=0;a<r.length;a++)n[r[a]]="0123456789abcdef"[a],i["0123456789abcdef"[a]]=r[a];var o=function(t){return"0x"+parseInt(t,10).toString(16)},s=t.__fontmetrics__.compress=function(t){var r,n,a,c,u=["{"];for(var l in t){if(r=t[l],isNaN(parseInt(l,10))?n="'"+l+"'":(l=parseInt(l,10),n=(n=o(l).slice(2)).slice(0,-1)+i[n.slice(-1)]),"number"==typeof r)r<0?(a=o(r).slice(3),c="-"):(a=o(r).slice(2),c=""),a=c+a.slice(0,-1)+i[a.slice(-1)];else{if("object"!==e(r))throw new Error("Don't know what to do with value type "+e(r)+".");a=s(r)}u.push(n+a)}return u.push("}"),u.join("")},c=t.__fontmetrics__.uncompress=function(t){if("string"!=typeof t)throw new Error("Invalid argument passed to uncompress.");for(var e,r,i,a,o={},s=1,c=o,u=[],l="",h="",f=t.length-1,d=1;d<f;d+=1)"'"==(a=t[d])?e?(i=e.join(""),e=void 0):e=[]:e?e.push(a):"{"==a?(u.push([c,i]),c={},i=void 0):"}"==a?((r=u.pop())[0][r[1]]=c,i=void 0,c=r[0]):"-"==a?s=-1:void 0===i?n.hasOwnProperty(a)?(l+=n[a],i=parseInt(l,16)*s,s=1,l=""):l+=a:n.hasOwnProperty(a)?(h+=n[a],c[i]=parseInt(h,16)*s,s=1,i=void 0,h=""):h+=a;return o},u={codePages:["WinAnsiEncoding"],WinAnsiEncoding:c("{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}")},l={Unicode:{Courier:u,"Courier-Bold":u,"Courier-BoldOblique":u,"Courier-Oblique":u,Helvetica:u,"Helvetica-Bold":u,"Helvetica-BoldOblique":u,"Helvetica-Oblique":u,"Times-Roman":u,"Times-Bold":u,"Times-BoldItalic":u,"Times-Italic":u}},h={Unicode:{"Courier-Oblique":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-BoldItalic":c("{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}"),"Helvetica-Bold":c("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),Courier:c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Courier-BoldOblique":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-Bold":c("{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}"),Symbol:c("{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}"),Helvetica:c("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}"),"Helvetica-BoldOblique":c("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),ZapfDingbats:c("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"),"Courier-Bold":c("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),"Times-Italic":c("{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}"),"Times-Roman":c("{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}"),"Helvetica-Oblique":c("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}")}};t.events.push(["addFont",function(t){var e=t.font,r=h.Unicode[e.postScriptName];r&&(e.metadata.Unicode={},e.metadata.Unicode.widths=r.widths,e.metadata.Unicode.kerning=r.kerning);var n=l.Unicode[e.postScriptName];n&&(e.metadata.Unicode.encoding=n,e.encoding=n.codePages[0])}])}(M.API), -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(t){for(var e=t.length,r=new Uint8Array(e),n=0;n<e;n++)r[n]=t.charCodeAt(n);return r};t.API.events.push(["addFont",function(r){var n=void 0,i=r.font,a=r.instance;if(!i.isStandardFont){if(void 0===a)throw new Error("Font does not exist in vFS, import fonts or remove declaration doc.addFont('"+i.postScriptName+"').");if("string"!=typeof(n=!1===a.existsFileInVFS(i.postScriptName)?a.loadFile(i.postScriptName):a.getFileFromVFS(i.postScriptName)))throw new Error("Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('"+i.postScriptName+"').");!function(r,n){n=/^\x00\x01\x00\x00/.test(n)?e(n):e(c(n)),r.metadata=t.API.TTFFont.open(n),r.metadata.Unicode=r.metadata.Unicode||{encoding:{},kerning:{},widths:[]},r.metadata.glyIdsUsed=[0]}(i,n)}}])}(M),function(n){function a(){return(r.canvg?Promise.resolve(r.canvg):"object"===(void 0===t?"undefined":e(t))&&"undefined"!=typeof module?new Promise((function(t,e){try{t(require("canvg"))}catch(t){e(t)}})):"function"==typeof define&&define.amd?new Promise((function(t,e){try{require(["canvg"],t)}catch(t){e(t)}})):Promise.reject(new Error("Could not load canvg"))).catch((function(t){return Promise.reject(new Error("Could not load canvg: "+t))})).then((function(t){return t.default?t.default:t}))}n.addSvgAsImage=function(t,e,r,n,o,s,c,u){if(isNaN(e)||isNaN(r))throw i.error("jsPDF.addSvgAsImage: Invalid coordinates",arguments),new Error("Invalid coordinates passed to jsPDF.addSvgAsImage");if(isNaN(n)||isNaN(o))throw i.error("jsPDF.addSvgAsImage: Invalid measurements",arguments),new Error("Invalid measurements (width and/or height) passed to jsPDF.addSvgAsImage");var l=document.createElement("canvas");l.width=n,l.height=o;var h=l.getContext("2d");h.fillStyle="#fff",h.fillRect(0,0,l.width,l.height);var f={ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0},d=this;return a().then((function(e){return e.fromString(h,t,f)}),(function(){return Promise.reject(new Error("Could not load canvg."))})).then((function(t){return t.render(f)})).then((function(){d.addImage(l.toDataURL("image/jpeg",1),e,r,n,o,c,u)}))}}(M.API), -/** - * @license - * ==================================================================== - * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){t.putTotalPages=function(t){var e,r=0;parseInt(this.internal.getFont().id.substr(1),10)<15?(e=new RegExp(t,"g"),r=this.internal.getNumberOfPages()):(e=new RegExp(this.pdfEscape16(t,this.internal.getFont()),"g"),r=this.pdfEscape16(this.internal.getNumberOfPages()+"",this.internal.getFont()));for(var n=1;n<=this.internal.getNumberOfPages();n++)for(var i=0;i<this.internal.pages[n].length;i++)this.internal.pages[n][i]=this.internal.pages[n][i].replace(e,r);return this}}(M.API),function(t){t.viewerPreferences=function(t,r){var n;t=t||{},r=r||!1;var i,a,o,s={HideToolbar:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},HideMenubar:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},HideWindowUI:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},FitWindow:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},CenterWindow:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.3},DisplayDocTitle:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.4},NonFullScreenPageMode:{defaultValue:"UseNone",value:"UseNone",type:"name",explicitSet:!1,valueSet:["UseNone","UseOutlines","UseThumbs","UseOC"],pdfVersion:1.3},Direction:{defaultValue:"L2R",value:"L2R",type:"name",explicitSet:!1,valueSet:["L2R","R2L"],pdfVersion:1.3},ViewArea:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},ViewClip:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintArea:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintClip:{defaultValue:"CropBox",value:"CropBox",type:"name",explicitSet:!1,valueSet:["MediaBox","CropBox","TrimBox","BleedBox","ArtBox"],pdfVersion:1.4},PrintScaling:{defaultValue:"AppDefault",value:"AppDefault",type:"name",explicitSet:!1,valueSet:["AppDefault","None"],pdfVersion:1.6},Duplex:{defaultValue:"",value:"none",type:"name",explicitSet:!1,valueSet:["Simplex","DuplexFlipShortEdge","DuplexFlipLongEdge","none"],pdfVersion:1.7},PickTrayByPDFSize:{defaultValue:!1,value:!1,type:"boolean",explicitSet:!1,valueSet:[!0,!1],pdfVersion:1.7},PrintPageRange:{defaultValue:"",value:"",type:"array",explicitSet:!1,valueSet:null,pdfVersion:1.7},NumCopies:{defaultValue:1,value:1,type:"integer",explicitSet:!1,valueSet:null,pdfVersion:1.7}},c=Object.keys(s),u=[],l=0,h=0,f=0;function d(t,e){var r,n=!1;for(r=0;r<t.length;r+=1)t[r]===e&&(n=!0);return n}if(void 0===this.internal.viewerpreferences&&(this.internal.viewerpreferences={},this.internal.viewerpreferences.configuration=JSON.parse(JSON.stringify(s)),this.internal.viewerpreferences.isSubscribed=!1),n=this.internal.viewerpreferences.configuration,"reset"===t||!0===r){var p=c.length;for(f=0;f<p;f+=1)n[c[f]].value=n[c[f]].defaultValue,n[c[f]].explicitSet=!1}if("object"===e(t))for(a in t)if(o=t[a],d(c,a)&&void 0!==o){if("boolean"===n[a].type&&"boolean"==typeof o)n[a].value=o;else if("name"===n[a].type&&d(n[a].valueSet,o))n[a].value=o;else if("integer"===n[a].type&&Number.isInteger(o))n[a].value=o;else if("array"===n[a].type){for(l=0;l<o.length;l+=1)if(i=!0,1===o[l].length&&"number"==typeof o[l][0])u.push(String(o[l]-1));else if(o[l].length>1){for(h=0;h<o[l].length;h+=1)"number"!=typeof o[l][h]&&(i=!1);!0===i&&u.push([o[l][0]-1,o[l][1]-1].join(" "))}n[a].value="["+u.join(" ")+"]"}else n[a].value=n[a].defaultValue;n[a].explicitSet=!0}return!1===this.internal.viewerpreferences.isSubscribed&&(this.internal.events.subscribe("putCatalog",(function(){var t,e=[];for(t in n)!0===n[t].explicitSet&&("name"===n[t].type?e.push("/"+t+" /"+n[t].value):e.push("/"+t+" "+n[t].value));0!==e.length&&this.internal.write("/ViewerPreferences\n<<\n"+e.join("\n")+"\n>>")})),this.internal.viewerpreferences.isSubscribed=!0),this.internal.viewerpreferences.configuration=n,this}}(M.API), -/** ==================================================================== - * @license - * jsPDF XMP metadata plugin - * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ -function(t){var e=function(){var t='<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:jspdf="'+this.internal.__metadata__.namespaceuri+'"><jspdf:metadata>',e=unescape(encodeURIComponent('<x:xmpmeta xmlns:x="adobe:ns:meta/">')),r=unescape(encodeURIComponent(t)),n=unescape(encodeURIComponent(this.internal.__metadata__.metadata)),i=unescape(encodeURIComponent("</jspdf:metadata></rdf:Description></rdf:RDF>")),a=unescape(encodeURIComponent("</x:xmpmeta>")),o=r.length+n.length+i.length+e.length+a.length;this.internal.__metadata__.metadata_object_number=this.internal.newObject(),this.internal.write("<< /Type /Metadata /Subtype /XML /Length "+o+" >>"),this.internal.write("stream"),this.internal.write(e+r+n+i+a),this.internal.write("endstream"),this.internal.write("endobj")},r=function(){this.internal.__metadata__.metadata_object_number&&this.internal.write("/Metadata "+this.internal.__metadata__.metadata_object_number+" 0 R")};t.addMetadata=function(t,n){return void 0===this.internal.__metadata__&&(this.internal.__metadata__={metadata:t,namespaceuri:n||"http://jspdf.default.namespaceuri/"},this.internal.events.subscribe("putCatalog",r),this.internal.events.subscribe("postPutResources",e)),this}}(M.API),function(t){var e=t.API,r=e.pdfEscape16=function(t,e){for(var r,n=e.metadata.Unicode.widths,i=["","0","00","000","0000"],a=[""],o=0,s=t.length;o<s;++o){if(r=e.metadata.characterToGlyph(t.charCodeAt(o)),e.metadata.glyIdsUsed.push(r),e.metadata.toUnicode[r]=t.charCodeAt(o),-1==n.indexOf(r)&&(n.push(r),n.push([parseInt(e.metadata.widthOfGlyph(r),10)])),"0"==r)return a.join("");r=r.toString(16),a.push(i[4-r.length],r)}return a.join("")},n=function(t){var e,r,n,i,a,o,s;for(a="/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000><ffff>\nendcodespacerange",n=[],o=0,s=(r=Object.keys(t).sort((function(t,e){return t-e}))).length;o<s;o++)e=r[o],n.length>=100&&(a+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar",n=[]),void 0!==t[e]&&null!==t[e]&&"function"==typeof t[e].toString&&(i=("0000"+t[e].toString(16)).slice(-4),e=("0000"+(+e).toString(16)).slice(-4),n.push("<"+e+"><"+i+">"));return n.length&&(a+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar\n"),a+="endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"};e.events.push(["putFont",function(e){!function(e){var r=e.font,i=e.out,a=e.newObject,o=e.putStream;if(r.metadata instanceof t.API.TTFFont&&"Identity-H"===r.encoding){for(var s=r.metadata.Unicode.widths,c=r.metadata.subset.encode(r.metadata.glyIdsUsed,1),u="",l=0;l<c.length;l++)u+=String.fromCharCode(c[l]);var h=a();o({data:u,addLength1:!0,objectId:h}),i("endobj");var f=a();o({data:n(r.metadata.toUnicode),addLength1:!0,objectId:f}),i("endobj");var d=a();i("<<"),i("/Type /FontDescriptor"),i("/FontName /"+F(r.fontName)),i("/FontFile2 "+h+" 0 R"),i("/FontBBox "+t.API.PDFObject.convert(r.metadata.bbox)),i("/Flags "+r.metadata.flags),i("/StemV "+r.metadata.stemV),i("/ItalicAngle "+r.metadata.italicAngle),i("/Ascent "+r.metadata.ascender),i("/Descent "+r.metadata.decender),i("/CapHeight "+r.metadata.capHeight),i(">>"),i("endobj");var p=a();i("<<"),i("/Type /Font"),i("/BaseFont /"+F(r.fontName)),i("/FontDescriptor "+d+" 0 R"),i("/W "+t.API.PDFObject.convert(s)),i("/CIDToGIDMap /Identity"),i("/DW 1000"),i("/Subtype /CIDFontType2"),i("/CIDSystemInfo"),i("<<"),i("/Supplement 0"),i("/Registry (Adobe)"),i("/Ordering ("+r.encoding+")"),i(">>"),i(">>"),i("endobj"),r.objectNumber=a(),i("<<"),i("/Type /Font"),i("/Subtype /Type0"),i("/ToUnicode "+f+" 0 R"),i("/BaseFont /"+F(r.fontName)),i("/Encoding /"+r.encoding),i("/DescendantFonts ["+p+" 0 R]"),i(">>"),i("endobj"),r.isAlreadyPutted=!0}}(e)}]);e.events.push(["putFont",function(e){!function(e){var r=e.font,i=e.out,a=e.newObject,o=e.putStream;if(r.metadata instanceof t.API.TTFFont&&"WinAnsiEncoding"===r.encoding){for(var s=r.metadata.rawData,c="",u=0;u<s.length;u++)c+=String.fromCharCode(s[u]);var l=a();o({data:c,addLength1:!0,objectId:l}),i("endobj");var h=a();o({data:n(r.metadata.toUnicode),addLength1:!0,objectId:h}),i("endobj");var f=a();i("<<"),i("/Descent "+r.metadata.decender),i("/CapHeight "+r.metadata.capHeight),i("/StemV "+r.metadata.stemV),i("/Type /FontDescriptor"),i("/FontFile2 "+l+" 0 R"),i("/Flags 96"),i("/FontBBox "+t.API.PDFObject.convert(r.metadata.bbox)),i("/FontName /"+F(r.fontName)),i("/ItalicAngle "+r.metadata.italicAngle),i("/Ascent "+r.metadata.ascender),i(">>"),i("endobj"),r.objectNumber=a();for(var d=0;d<r.metadata.hmtx.widths.length;d++)r.metadata.hmtx.widths[d]=parseInt(r.metadata.hmtx.widths[d]*(1e3/r.metadata.head.unitsPerEm));i("<</Subtype/TrueType/Type/Font/ToUnicode "+h+" 0 R/BaseFont/"+F(r.fontName)+"/FontDescriptor "+f+" 0 R/Encoding/"+r.encoding+" /FirstChar 29 /LastChar 255 /Widths "+t.API.PDFObject.convert(r.metadata.hmtx.widths)+">>"),i("endobj"),r.isAlreadyPutted=!0}}(e)}]);var i=function(t){var e,n=t.text||"",i=t.x,a=t.y,o=t.options||{},s=t.mutex||{},c=s.pdfEscape,u=s.activeFontKey,l=s.fonts,h=u,f="",d=0,p="",g=l[h].encoding;if("Identity-H"!==l[h].encoding)return{text:n,x:i,y:a,options:o,mutex:s};for(p=n,h=u,Array.isArray(n)&&(p=n[0]),d=0;d<p.length;d+=1)l[h].metadata.hasOwnProperty("cmap")&&(e=l[h].metadata.cmap.unicode.codeMap[p[d].charCodeAt(0)]),e||p[d].charCodeAt(0)<256&&l[h].metadata.hasOwnProperty("Unicode")?f+=p[d]:f+="";var m="";return parseInt(h.slice(1))<14||"WinAnsiEncoding"===g?m=c(f,h).split("").map((function(t){return t.charCodeAt(0).toString(16)})).join(""):"Identity-H"===g&&(m=r(f,l[h])),s.isHex=!0,{text:m,x:i,y:a,options:o,mutex:s}};e.events.push(["postProcessText",function(t){var e=t.text||"",r=[],n={text:e,x:t.x,y:t.y,options:t.options,mutex:t.mutex};if(Array.isArray(e)){var a=0;for(a=0;a<e.length;a+=1)Array.isArray(e[a])&&3===e[a].length?r.push([i(Object.assign({},n,{text:e[a][0]})).text,e[a][1],e[a][2]]):r.push(i(Object.assign({},n,{text:e[a]})).text);t.text=r}else t.text=i(Object.assign({},n,{text:e})).text}])}(M), -/** - * @license - * jsPDF virtual FileSystem functionality - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ -function(t){var e=function(){return void 0===this.internal.vFS&&(this.internal.vFS={}),!0};t.existsFileInVFS=function(t){return e.call(this),void 0!==this.internal.vFS[t]},t.addFileToVFS=function(t,r){return e.call(this),this.internal.vFS[t]=r,this},t.getFileFromVFS=function(t){return e.call(this),void 0!==this.internal.vFS[t]?this.internal.vFS[t]:null}}(M.API), -/** - * @license - * Unicode Bidi Engine based on the work of Alex Shensis (@asthensis) - * MIT License - */ -function(t){t.__bidiEngine__=t.prototype.__bidiEngine__=function(t){var r,n,i,a,o,s,c,u=e,l=[[0,3,0,1,0,0,0],[0,3,0,1,2,2,0],[0,3,0,17,2,0,1],[0,3,5,5,4,1,0],[0,3,21,21,4,0,1],[0,3,5,5,4,2,0]],h=[[2,0,1,1,0,1,0],[2,0,1,1,0,2,0],[2,0,2,1,3,2,0],[2,0,2,33,3,1,1]],f={L:0,R:1,EN:2,AN:3,N:4,B:5,S:6},d={0:0,5:1,6:2,7:3,32:4,251:5,254:6,255:7},p=["(",")","(","<",">","<","[","]","[","{","}","{","«","»","«","‹","›","‹","⁅","⁆","⁅","⁽","⁾","⁽","₍","₎","₍","≤","≥","≤","〈","〉","〈","﹙","﹚","﹙","﹛","﹜","﹛","﹝","﹞","﹝","﹤","﹥","﹤"],g=new RegExp(/^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/),m=!1,v=0;this.__bidiEngine__={};var b=function(t){var e=t.charCodeAt(),r=e>>8,n=d[r];return void 0!==n?u[256*n+(255&e)]:252===r||253===r?"AL":g.test(r)?"L":8===r?"R":"N"},y=function(t){for(var e,r=0;r<t.length;r++){if("L"===(e=b(t.charAt(r))))return!1;if("R"===e)return!0}return!1},w=function(t,e,o,s){var c,u,l,h,f=e[s];switch(f){case"L":case"R":m=!1;break;case"N":case"AN":break;case"EN":m&&(f="AN");break;case"AL":m=!0,f="R";break;case"WS":f="N";break;case"CS":s<1||s+1>=e.length||"EN"!==(c=o[s-1])&&"AN"!==c||"EN"!==(u=e[s+1])&&"AN"!==u?f="N":m&&(u="AN"),f=u===c?u:"N";break;case"ES":f="EN"===(c=s>0?o[s-1]:"B")&&s+1<e.length&&"EN"===e[s+1]?"EN":"N";break;case"ET":if(s>0&&"EN"===o[s-1]){f="EN";break}if(m){f="N";break}for(l=s+1,h=e.length;l<h&&"ET"===e[l];)l++;f=l<h&&"EN"===e[l]?"EN":"N";break;case"NSM":if(i&&!a){for(h=e.length,l=s+1;l<h&&"NSM"===e[l];)l++;if(l<h){var d=t[s],p=d>=1425&&d<=2303||64286===d;if(c=e[l],p&&("R"===c||"AL"===c)){f="R";break}}}f=s<1||"B"===(c=e[s-1])?"N":o[s-1];break;case"B":m=!1,r=!0,f=v;break;case"S":n=!0,f="N";break;case"LRE":case"RLE":case"LRO":case"RLO":case"PDF":m=!1;break;case"BN":f="N"}return f},N=function(t,e,r){var n=t.split("");return r&&L(n,r,{hiLevel:v}),n.reverse(),e&&e.reverse(),n.join("")},L=function(t,e,i){var a,o,s,c,u,d=-1,p=t.length,g=0,y=[],N=v?h:l,L=[];for(m=!1,r=!1,n=!1,o=0;o<p;o++)L[o]=b(t[o]);for(s=0;s<p;s++){if(u=g,y[s]=w(t,L,y,s),a=240&(g=N[u][f[y[s]]]),g&=15,e[s]=c=N[g][5],a>0)if(16===a){for(o=d;o<s;o++)e[o]=1;d=-1}else d=-1;if(N[g][6])-1===d&&(d=s);else if(d>-1){for(o=d;o<s;o++)e[o]=c;d=-1}"B"===L[s]&&(e[s]=0),i.hiLevel|=c}n&&function(t,e,r){for(var n=0;n<r;n++)if("S"===t[n]){e[n]=v;for(var i=n-1;i>=0&&"WS"===t[i];i--)e[i]=v}}(L,e,p)},A=function(t,e,n,i,a){if(!(a.hiLevel<t)){if(1===t&&1===v&&!r)return e.reverse(),void(n&&n.reverse());for(var o,s,c,u,l=e.length,h=0;h<l;){if(i[h]>=t){for(c=h+1;c<l&&i[c]>=t;)c++;for(u=h,s=c-1;u<s;u++,s--)o=e[u],e[u]=e[s],e[s]=o,n&&(o=n[u],n[u]=n[s],n[s]=o);h=c}h++}}},x=function(t,e,r){var n=t.split(""),i={hiLevel:v};return r||(r=[]),L(n,r,i),function(t,e,r){if(0!==r.hiLevel&&c)for(var n,i=0;i<t.length;i++)1===e[i]&&(n=p.indexOf(t[i]))>=0&&(t[i]=p[n+1])}(n,r,i),A(2,n,e,r,i),A(1,n,e,r,i),n.join("")};return this.__bidiEngine__.doBidiReorder=function(t,e,r){if(function(t,e){if(e)for(var r=0;r<t.length;r++)e[r]=r;void 0===a&&(a=y(t)),void 0===s&&(s=y(t))}(t,e),i||!o||s)if(i&&o&&a^s)v=a?1:0,t=N(t,e,r);else if(!i&&o&&s)v=a?1:0,t=x(t,e,r),t=N(t,e);else if(!i||a||o||s){if(i&&!o&&a^s)t=N(t,e),a?(v=0,t=x(t,e,r)):(v=1,t=x(t,e,r),t=N(t,e));else if(i&&a&&!o&&s)v=1,t=x(t,e,r),t=N(t,e);else if(!i&&!o&&a^s){var n=c;a?(v=1,t=x(t,e,r),v=0,c=!1,t=x(t,e,r),c=n):(v=0,t=x(t,e,r),t=N(t,e),v=1,c=!1,t=x(t,e,r),c=n,t=N(t,e))}}else v=0,t=x(t,e,r);else v=a?1:0,t=x(t,e,r);return t},this.__bidiEngine__.setOptions=function(t){t&&(i=t.isInputVisual,o=t.isOutputVisual,a=t.isInputRtl,s=t.isOutputRtl,c=t.isSymmetricSwapping)},this.__bidiEngine__.setOptions(t),this.__bidiEngine__};var e=["BN","BN","BN","BN","BN","BN","BN","BN","BN","S","B","S","WS","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","B","B","B","S","WS","N","N","ET","ET","ET","N","N","N","N","N","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","BN","BN","BN","BN","BN","BN","B","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","BN","CS","N","ET","ET","ET","ET","N","N","N","N","L","N","N","BN","N","N","ET","ET","EN","EN","N","L","N","N","N","EN","L","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","L","L","L","L","L","L","L","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","L","N","N","N","N","N","ET","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","R","NSM","R","NSM","NSM","R","NSM","NSM","R","NSM","N","N","N","N","N","N","N","N","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","N","N","N","N","N","R","R","R","R","R","N","N","N","N","N","N","N","N","N","N","N","AN","AN","AN","AN","AN","AN","N","N","AL","ET","ET","AL","CS","AL","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","AN","AN","AN","AN","AN","AN","AN","AN","AN","ET","AN","AN","AL","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AN","N","NSM","NSM","NSM","NSM","NSM","NSM","AL","AL","NSM","NSM","N","NSM","NSM","NSM","NSM","AL","AL","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","AL","AL","NSM","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","AL","N","N","N","N","N","N","N","N","N","N","N","N","N","N","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","R","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","R","R","N","N","N","N","R","N","N","N","N","N","WS","WS","WS","WS","WS","WS","WS","WS","WS","WS","WS","BN","BN","BN","L","R","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","WS","B","LRE","RLE","PDF","LRO","RLO","CS","ET","ET","ET","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","CS","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","WS","BN","BN","BN","BN","BN","N","LRI","RLI","FSI","PDI","BN","BN","BN","BN","BN","BN","EN","L","N","N","EN","EN","EN","EN","EN","EN","ES","ES","N","N","N","L","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","ES","ES","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","L","L","N","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","N","N","N","N","N","R","NSM","R","R","R","R","R","R","R","R","R","R","ES","R","R","R","R","R","R","R","R","R","R","R","R","R","N","R","R","R","R","R","N","R","N","R","R","N","R","R","N","R","R","R","R","R","R","R","R","R","R","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","NSM","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","CS","N","CS","N","N","CS","N","N","N","N","N","N","N","N","N","ET","N","N","ES","ES","N","N","N","N","N","ET","ET","N","N","N","N","N","AL","AL","AL","AL","AL","N","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","AL","N","N","BN","N","N","N","ET","ET","ET","N","N","N","N","N","ES","CS","ES","CS","CS","EN","EN","EN","EN","EN","EN","EN","EN","EN","EN","CS","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","N","N","N","N","N","N","N","N","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","L","N","N","N","L","L","L","L","L","L","N","N","L","L","L","L","L","L","N","N","L","L","L","L","L","L","N","N","L","L","L","N","N","N","ET","ET","N","N","N","ET","ET","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N","N"],r=new t.__bidiEngine__({isInputVisual:!0});t.API.events.push(["postProcessText",function(t){var e=t.text,n=(t.x,t.y,t.options||{}),i=(t.mutex,n.lang,[]);if(n.isInputVisual="boolean"!=typeof n.isInputVisual||n.isInputVisual,r.setOptions(n),"[object Array]"===Object.prototype.toString.call(e)){var a=0;for(i=[],a=0;a<e.length;a+=1)"[object Array]"===Object.prototype.toString.call(e[a])?i.push([r.doBidiReorder(e[a][0]),e[a][1],e[a][2]]):i.push([r.doBidiReorder(e[a])]);t.text=i}else t.text=r.doBidiReorder(e);r.setOptions({isInputVisual:!0})}])}(M),M.API.TTFFont=function(){function t(t){var e;if(this.rawData=t,e=this.contents=new Te(t),this.contents.pos=4,"ttcf"===e.readString(4))throw new Error("TTCF not supported.");e.pos=0,this.parse(),this.subset=new ar(this),this.registerTTF()}return t.open=function(e){return new t(e)},t.prototype.parse=function(){return this.directory=new Ue(this.contents),this.head=new We(this),this.name=new Ze(this),this.cmap=new Ge(this),this.toUnicode={},this.hhea=new Ye(this),this.maxp=new $e(this),this.hmtx=new Qe(this),this.post=new Xe(this),this.os2=new Je(this),this.loca=new ir(this),this.glyf=new er(this),this.ascender=this.os2.exists&&this.os2.ascender||this.hhea.ascender,this.decender=this.os2.exists&&this.os2.decender||this.hhea.decender,this.lineGap=this.os2.exists&&this.os2.lineGap||this.hhea.lineGap,this.bbox=[this.head.xMin,this.head.yMin,this.head.xMax,this.head.yMax]},t.prototype.registerTTF=function(){var t,e,r,n,i;this.scaleFactor=1e3/this.head.unitsPerEm,this.bbox=function(){var e,r,n,i;for(i=[],e=0,r=(n=this.bbox).length;e<r;e++)t=n[e],i.push(Math.round(t*this.scaleFactor));return i}.call(this),this.stemV=0,this.post.exists?(r=255&(n=this.post.italic_angle),0!=(32768&(e=n>>16))&&(e=-(1+(65535^e))),this.italicAngle=+(e+"."+r)):this.italicAngle=0,this.ascender=Math.round(this.ascender*this.scaleFactor),this.decender=Math.round(this.decender*this.scaleFactor),this.lineGap=Math.round(this.lineGap*this.scaleFactor),this.capHeight=this.os2.exists&&this.os2.capHeight||this.ascender,this.xHeight=this.os2.exists&&this.os2.xHeight||0,this.familyClass=(this.os2.exists&&this.os2.familyClass||0)>>8,this.isSerif=1===(i=this.familyClass)||2===i||3===i||4===i||5===i||7===i,this.isScript=10===this.familyClass,this.flags=0,this.post.isFixedPitch&&(this.flags|=1),this.isSerif&&(this.flags|=2),this.isScript&&(this.flags|=8),0!==this.italicAngle&&(this.flags|=64),this.flags|=32,this.cmap.unicode||console.log("No unicode cmap for font")},t.prototype.characterToGlyph=function(t){var e;return(null!=(e=this.cmap.unicode)?e.codeMap[t]:void 0)||0},t.prototype.widthOfGlyph=function(t){var e;return e=1e3/this.head.unitsPerEm,this.hmtx.forGlyph(t).advance*e},t.prototype.widthOfString=function(t,e,r){var n,i,a,o;for(a=0,i=0,o=(t=""+t).length;0<=o?i<o:i>o;i=0<=o?++i:--i)n=t.charCodeAt(i),a+=this.widthOfGlyph(this.characterToGlyph(n))+r*(1e3/e)||0;return a*(e/1e3)},t.prototype.lineHeight=function(t,e){var r;return null==e&&(e=!1),r=e?this.lineGap:0,(this.ascender+r-this.decender)/1e3*t},t}();var Re,Te=function(){function t(t){this.data=null!=t?t:[],this.pos=0,this.length=this.data.length}return t.prototype.readByte=function(){return this.data[this.pos++]},t.prototype.writeByte=function(t){return this.data[this.pos++]=t},t.prototype.readUInt32=function(){return 16777216*this.readByte()+(this.readByte()<<16)+(this.readByte()<<8)+this.readByte()},t.prototype.writeUInt32=function(t){return this.writeByte(t>>>24&255),this.writeByte(t>>16&255),this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt32=function(){var t;return(t=this.readUInt32())>=2147483648?t-4294967296:t},t.prototype.writeInt32=function(t){return t<0&&(t+=4294967296),this.writeUInt32(t)},t.prototype.readUInt16=function(){return this.readByte()<<8|this.readByte()},t.prototype.writeUInt16=function(t){return this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt16=function(){var t;return(t=this.readUInt16())>=32768?t-65536:t},t.prototype.writeInt16=function(t){return t<0&&(t+=65536),this.writeUInt16(t)},t.prototype.readString=function(t){var e,r;for(r=[],e=0;0<=t?e<t:e>t;e=0<=t?++e:--e)r[e]=String.fromCharCode(this.readByte());return r.join("")},t.prototype.writeString=function(t){var e,r,n;for(n=[],e=0,r=t.length;0<=r?e<r:e>r;e=0<=r?++e:--e)n.push(this.writeByte(t.charCodeAt(e)));return n},t.prototype.readShort=function(){return this.readInt16()},t.prototype.writeShort=function(t){return this.writeInt16(t)},t.prototype.readLongLong=function(){var t,e,r,n,i,a,o,s;return t=this.readByte(),e=this.readByte(),r=this.readByte(),n=this.readByte(),i=this.readByte(),a=this.readByte(),o=this.readByte(),s=this.readByte(),128&t?-1*(72057594037927940*(255^t)+281474976710656*(255^e)+1099511627776*(255^r)+4294967296*(255^n)+16777216*(255^i)+65536*(255^a)+256*(255^o)+(255^s)+1):72057594037927940*t+281474976710656*e+1099511627776*r+4294967296*n+16777216*i+65536*a+256*o+s},t.prototype.writeLongLong=function(t){var e,r;return e=Math.floor(t/4294967296),r=4294967295&t,this.writeByte(e>>24&255),this.writeByte(e>>16&255),this.writeByte(e>>8&255),this.writeByte(255&e),this.writeByte(r>>24&255),this.writeByte(r>>16&255),this.writeByte(r>>8&255),this.writeByte(255&r)},t.prototype.readInt=function(){return this.readInt32()},t.prototype.writeInt=function(t){return this.writeInt32(t)},t.prototype.read=function(t){var e,r;for(e=[],r=0;0<=t?r<t:r>t;r=0<=t?++r:--r)e.push(this.readByte());return e},t.prototype.write=function(t){var e,r,n,i;for(i=[],r=0,n=t.length;r<n;r++)e=t[r],i.push(this.writeByte(e));return i},t}(),Ue=function(){var t;function e(t){var e,r,n;for(this.scalarType=t.readInt(),this.tableCount=t.readShort(),this.searchRange=t.readShort(),this.entrySelector=t.readShort(),this.rangeShift=t.readShort(),this.tables={},r=0,n=this.tableCount;0<=n?r<n:r>n;r=0<=n?++r:--r)e={tag:t.readString(4),checksum:t.readInt(),offset:t.readInt(),length:t.readInt()},this.tables[e.tag]=e}return e.prototype.encode=function(e){var r,n,i,a,o,s,c,u,l,h,f,d,p;for(p in f=Object.keys(e).length,s=Math.log(2),l=16*Math.floor(Math.log(f)/s),a=Math.floor(l/s),u=16*f-l,(n=new Te).writeInt(this.scalarType),n.writeShort(f),n.writeShort(l),n.writeShort(a),n.writeShort(u),i=16*f,c=n.pos+i,o=null,d=[],e)for(h=e[p],n.writeString(p),n.writeInt(t(h)),n.writeInt(c),n.writeInt(h.length),d=d.concat(h),"head"===p&&(o=c),c+=h.length;c%4;)d.push(0),c++;return n.write(d),r=2981146554-t(n.data),n.pos=o+8,n.writeUInt32(r),n.data},t=function(t){var e,r,n,i;for(t=tr.call(t);t.length%4;)t.push(0);for(n=new Te(t),r=0,e=0,i=t.length;e<i;e=e+=4)r+=n.readUInt32();return 4294967295&r},e}(),ze={}.hasOwnProperty,He=function(t,e){for(var r in e)ze.call(e,r)&&(t[r]=e[r]);function n(){this.constructor=t}return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},We=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="head",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.revision=t.readInt(),this.checkSumAdjustment=t.readInt(),this.magicNumber=t.readInt(),this.flags=t.readShort(),this.unitsPerEm=t.readShort(),this.created=t.readLongLong(),this.modified=t.readLongLong(),this.xMin=t.readShort(),this.yMin=t.readShort(),this.xMax=t.readShort(),this.yMax=t.readShort(),this.macStyle=t.readShort(),this.lowestRecPPEM=t.readShort(),this.fontDirectionHint=t.readShort(),this.indexToLocFormat=t.readShort(),this.glyphDataFormat=t.readShort()},e.prototype.encode=function(t){var e;return(e=new Te).writeInt(this.version),e.writeInt(this.revision),e.writeInt(this.checkSumAdjustment),e.writeInt(this.magicNumber),e.writeShort(this.flags),e.writeShort(this.unitsPerEm),e.writeLongLong(this.created),e.writeLongLong(this.modified),e.writeShort(this.xMin),e.writeShort(this.yMin),e.writeShort(this.xMax),e.writeShort(this.yMax),e.writeShort(this.macStyle),e.writeShort(this.lowestRecPPEM),e.writeShort(this.fontDirectionHint),e.writeShort(t),e.writeShort(this.glyphDataFormat),e.data},e}(Re=function(){function t(t){var e;this.file=t,e=this.file.directory.tables[this.tag],this.exists=!!e,e&&(this.offset=e.offset,this.length=e.length,this.parse(this.file.contents))}return t.prototype.parse=function(){},t.prototype.encode=function(){},t.prototype.raw=function(){return this.exists?(this.file.contents.pos=this.offset,this.file.contents.read(this.length)):null},t}()),Ve=function(){function t(t,e){var r,n,i,a,o,s,c,u,l,h,f,d,p,g,m,v,b;switch(this.platformID=t.readUInt16(),this.encodingID=t.readShort(),this.offset=e+t.readInt(),l=t.pos,t.pos=this.offset,this.format=t.readUInt16(),this.length=t.readUInt16(),this.language=t.readUInt16(),this.isUnicode=3===this.platformID&&1===this.encodingID&&4===this.format||0===this.platformID&&4===this.format,this.codeMap={},this.format){case 0:for(s=0;s<256;++s)this.codeMap[s]=t.readByte();break;case 4:for(f=t.readUInt16(),h=f/2,t.pos+=6,i=function(){var e,r;for(r=[],s=e=0;0<=h?e<h:e>h;s=0<=h?++e:--e)r.push(t.readUInt16());return r}(),t.pos+=2,p=function(){var e,r;for(r=[],s=e=0;0<=h?e<h:e>h;s=0<=h?++e:--e)r.push(t.readUInt16());return r}(),c=function(){var e,r;for(r=[],s=e=0;0<=h?e<h:e>h;s=0<=h?++e:--e)r.push(t.readUInt16());return r}(),u=function(){var e,r;for(r=[],s=e=0;0<=h?e<h:e>h;s=0<=h?++e:--e)r.push(t.readUInt16());return r}(),n=(this.length-t.pos+this.offset)/2,o=function(){var e,r;for(r=[],s=e=0;0<=n?e<n:e>n;s=0<=n?++e:--e)r.push(t.readUInt16());return r}(),s=m=0,b=i.length;m<b;s=++m)for(g=i[s],r=v=d=p[s];d<=g?v<=g:v>=g;r=d<=g?++v:--v)0===u[s]?a=r+c[s]:0!==(a=o[u[s]/2+(r-d)-(h-s)]||0)&&(a+=c[s]),this.codeMap[r]=65535&a}t.pos=l}return t.encode=function(t,e){var r,n,i,a,o,s,c,u,l,h,f,d,p,g,m,v,b,y,w,N,L,A,x,S,_,P,k,F,I,C,j,O,B,M,E,q,D,R,T,U,z,H,W,V,G,Y;switch(F=new Te,a=Object.keys(t).sort((function(t,e){return t-e})),e){case"macroman":for(p=0,g=function(){var t=[];for(d=0;d<256;++d)t.push(0);return t}(),v={0:0},i={},I=0,B=a.length;I<B;I++)null==v[W=t[n=a[I]]]&&(v[W]=++p),i[n]={old:t[n],new:v[t[n]]},g[n]=v[t[n]];return F.writeUInt16(1),F.writeUInt16(0),F.writeUInt32(12),F.writeUInt16(0),F.writeUInt16(262),F.writeUInt16(0),F.write(g),{charMap:i,subtable:F.data,maxGlyphID:p+1};case"unicode":for(P=[],l=[],b=0,v={},r={},m=c=null,C=0,M=a.length;C<M;C++)null==v[w=t[n=a[C]]]&&(v[w]=++b),r[n]={old:w,new:v[w]},o=v[w]-n,null!=m&&o===c||(m&&l.push(m),P.push(n),c=o),m=n;for(m&&l.push(m),l.push(65535),P.push(65535),S=2*(x=P.length),A=2*Math.pow(Math.log(x)/Math.LN2,2),h=Math.log(A/2)/Math.LN2,L=2*x-A,s=[],N=[],f=[],d=j=0,E=P.length;j<E;d=++j){if(_=P[d],u=l[d],65535===_){s.push(0),N.push(0);break}if(_-(k=r[_].new)>=32768)for(s.push(0),N.push(2*(f.length+x-d)),n=O=_;_<=u?O<=u:O>=u;n=_<=u?++O:--O)f.push(r[n].new);else s.push(k-_),N.push(0)}for(F.writeUInt16(3),F.writeUInt16(1),F.writeUInt32(12),F.writeUInt16(4),F.writeUInt16(16+8*x+2*f.length),F.writeUInt16(0),F.writeUInt16(S),F.writeUInt16(A),F.writeUInt16(h),F.writeUInt16(L),z=0,q=l.length;z<q;z++)n=l[z],F.writeUInt16(n);for(F.writeUInt16(0),H=0,D=P.length;H<D;H++)n=P[H],F.writeUInt16(n);for(V=0,R=s.length;V<R;V++)o=s[V],F.writeUInt16(o);for(G=0,T=N.length;G<T;G++)y=N[G],F.writeUInt16(y);for(Y=0,U=f.length;Y<U;Y++)p=f[Y],F.writeUInt16(p);return{charMap:r,subtable:F.data,maxGlyphID:b+1}}},t}(),Ge=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="cmap",e.prototype.parse=function(t){var e,r,n;for(t.pos=this.offset,this.version=t.readUInt16(),n=t.readUInt16(),this.tables=[],this.unicode=null,r=0;0<=n?r<n:r>n;r=0<=n?++r:--r)e=new Ve(t,this.offset),this.tables.push(e),e.isUnicode&&null==this.unicode&&(this.unicode=e);return!0},e.encode=function(t,e){var r,n;return null==e&&(e="macroman"),r=Ve.encode(t,e),(n=new Te).writeUInt16(0),n.writeUInt16(1),r.table=n.data.concat(r.subtable),r},e}(Re),Ye=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="hhea",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.ascender=t.readShort(),this.decender=t.readShort(),this.lineGap=t.readShort(),this.advanceWidthMax=t.readShort(),this.minLeftSideBearing=t.readShort(),this.minRightSideBearing=t.readShort(),this.xMaxExtent=t.readShort(),this.caretSlopeRise=t.readShort(),this.caretSlopeRun=t.readShort(),this.caretOffset=t.readShort(),t.pos+=8,this.metricDataFormat=t.readShort(),this.numberOfMetrics=t.readUInt16()},e}(Re),Je=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="OS/2",e.prototype.parse=function(t){if(t.pos=this.offset,this.version=t.readUInt16(),this.averageCharWidth=t.readShort(),this.weightClass=t.readUInt16(),this.widthClass=t.readUInt16(),this.type=t.readShort(),this.ySubscriptXSize=t.readShort(),this.ySubscriptYSize=t.readShort(),this.ySubscriptXOffset=t.readShort(),this.ySubscriptYOffset=t.readShort(),this.ySuperscriptXSize=t.readShort(),this.ySuperscriptYSize=t.readShort(),this.ySuperscriptXOffset=t.readShort(),this.ySuperscriptYOffset=t.readShort(),this.yStrikeoutSize=t.readShort(),this.yStrikeoutPosition=t.readShort(),this.familyClass=t.readShort(),this.panose=function(){var e,r;for(r=[],e=0;e<10;++e)r.push(t.readByte());return r}(),this.charRange=function(){var e,r;for(r=[],e=0;e<4;++e)r.push(t.readInt());return r}(),this.vendorID=t.readString(4),this.selection=t.readShort(),this.firstCharIndex=t.readShort(),this.lastCharIndex=t.readShort(),this.version>0&&(this.ascent=t.readShort(),this.descent=t.readShort(),this.lineGap=t.readShort(),this.winAscent=t.readShort(),this.winDescent=t.readShort(),this.codePageRange=function(){var e,r;for(r=[],e=0;e<2;e=++e)r.push(t.readInt());return r}(),this.version>1))return this.xHeight=t.readShort(),this.capHeight=t.readShort(),this.defaultChar=t.readShort(),this.breakChar=t.readShort(),this.maxContext=t.readShort()},e}(Re),Xe=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="post",e.prototype.parse=function(t){var e,r,n;switch(t.pos=this.offset,this.format=t.readInt(),this.italicAngle=t.readInt(),this.underlinePosition=t.readShort(),this.underlineThickness=t.readShort(),this.isFixedPitch=t.readInt(),this.minMemType42=t.readInt(),this.maxMemType42=t.readInt(),this.minMemType1=t.readInt(),this.maxMemType1=t.readInt(),this.format){case 65536:break;case 131072:var i;for(r=t.readUInt16(),this.glyphNameIndex=[],i=0;0<=r?i<r:i>r;i=0<=r?++i:--i)this.glyphNameIndex.push(t.readUInt16());for(this.names=[],n=[];t.pos<this.offset+this.length;)e=t.readByte(),n.push(this.names.push(t.readString(e)));return n;case 151552:return r=t.readUInt16(),this.offsets=t.read(r);case 196608:break;case 262144:return this.map=function(){var e,r,n;for(n=[],i=e=0,r=this.file.maxp.numGlyphs;0<=r?e<r:e>r;i=0<=r?++e:--e)n.push(t.readUInt32());return n}.call(this)}},e}(Re),Ke=function(t,e){this.raw=t,this.length=t.length,this.platformID=e.platformID,this.encodingID=e.encodingID,this.languageID=e.languageID},Ze=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="name",e.prototype.parse=function(t){var e,r,n,i,a,o,s,c,u,l,h;for(t.pos=this.offset,t.readShort(),e=t.readShort(),o=t.readShort(),r=[],i=0;0<=e?i<e:i>e;i=0<=e?++i:--i)r.push({platformID:t.readShort(),encodingID:t.readShort(),languageID:t.readShort(),nameID:t.readShort(),length:t.readShort(),offset:this.offset+o+t.readShort()});for(s={},i=u=0,l=r.length;u<l;i=++u)n=r[i],t.pos=n.offset,c=t.readString(n.length),a=new Ke(c,n),null==s[h=n.nameID]&&(s[h]=[]),s[n.nameID].push(a);this.strings=s,this.copyright=s[0],this.fontFamily=s[1],this.fontSubfamily=s[2],this.uniqueSubfamily=s[3],this.fontName=s[4],this.version=s[5];try{this.postscriptName=s[6][0].raw.replace(/[\x00-\x19\x80-\xff]/g,"")}catch(t){this.postscriptName=s[4][0].raw.replace(/[\x00-\x19\x80-\xff]/g,"")}return this.trademark=s[7],this.manufacturer=s[8],this.designer=s[9],this.description=s[10],this.vendorUrl=s[11],this.designerUrl=s[12],this.license=s[13],this.licenseUrl=s[14],this.preferredFamily=s[15],this.preferredSubfamily=s[17],this.compatibleFull=s[18],this.sampleText=s[19]},e}(Re),$e=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="maxp",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.numGlyphs=t.readUInt16(),this.maxPoints=t.readUInt16(),this.maxContours=t.readUInt16(),this.maxCompositePoints=t.readUInt16(),this.maxComponentContours=t.readUInt16(),this.maxZones=t.readUInt16(),this.maxTwilightPoints=t.readUInt16(),this.maxStorage=t.readUInt16(),this.maxFunctionDefs=t.readUInt16(),this.maxInstructionDefs=t.readUInt16(),this.maxStackElements=t.readUInt16(),this.maxSizeOfInstructions=t.readUInt16(),this.maxComponentElements=t.readUInt16(),this.maxComponentDepth=t.readUInt16()},e}(Re),Qe=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="hmtx",e.prototype.parse=function(t){var e,r,n,i,a,o,s;for(t.pos=this.offset,this.metrics=[],e=0,o=this.file.hhea.numberOfMetrics;0<=o?e<o:e>o;e=0<=o?++e:--e)this.metrics.push({advance:t.readUInt16(),lsb:t.readInt16()});for(n=this.file.maxp.numGlyphs-this.file.hhea.numberOfMetrics,this.leftSideBearings=function(){var r,i;for(i=[],e=r=0;0<=n?r<n:r>n;e=0<=n?++r:--r)i.push(t.readInt16());return i}(),this.widths=function(){var t,e,r,n;for(n=[],t=0,e=(r=this.metrics).length;t<e;t++)i=r[t],n.push(i.advance);return n}.call(this),r=this.widths[this.widths.length-1],s=[],e=a=0;0<=n?a<n:a>n;e=0<=n?++a:--a)s.push(this.widths.push(r));return s},e.prototype.forGlyph=function(t){return t in this.metrics?this.metrics[t]:{advance:this.metrics[this.metrics.length-1].advance,lsb:this.leftSideBearings[t-this.metrics.length]}},e}(Re),tr=[].slice,er=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="glyf",e.prototype.parse=function(){return this.cache={}},e.prototype.glyphFor=function(t){var e,r,n,i,a,o,s,c,u,l;return t in this.cache?this.cache[t]:(i=this.file.loca,e=this.file.contents,r=i.indexOf(t),0===(n=i.lengthOf(t))?this.cache[t]=null:(e.pos=this.offset+r,a=(o=new Te(e.read(n))).readShort(),c=o.readShort(),l=o.readShort(),s=o.readShort(),u=o.readShort(),this.cache[t]=-1===a?new nr(o,c,l,s,u):new rr(o,a,c,l,s,u),this.cache[t]))},e.prototype.encode=function(t,e,r){var n,i,a,o,s;for(a=[],i=[],o=0,s=e.length;o<s;o++)n=t[e[o]],i.push(a.length),n&&(a=a.concat(n.encode(r)));return i.push(a.length),{table:a,offsets:i}},e}(Re),rr=function(){function t(t,e,r,n,i,a){this.raw=t,this.numberOfContours=e,this.xMin=r,this.yMin=n,this.xMax=i,this.yMax=a,this.compound=!1}return t.prototype.encode=function(){return this.raw.data},t}(),nr=function(){function t(t,e,r,n,i){var a,o;for(this.raw=t,this.xMin=e,this.yMin=r,this.xMax=n,this.yMax=i,this.compound=!0,this.glyphIDs=[],this.glyphOffsets=[],a=this.raw;o=a.readShort(),this.glyphOffsets.push(a.pos),this.glyphIDs.push(a.readUInt16()),32&o;)a.pos+=1&o?4:2,128&o?a.pos+=8:64&o?a.pos+=4:8&o&&(a.pos+=2)}return 1,8,32,64,128,t.prototype.encode=function(){var t,e,r;for(e=new Te(tr.call(this.raw.data)),t=0,r=this.glyphIDs.length;t<r;++t)e.pos=this.glyphOffsets[t];return e.data},t}(),ir=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return He(e,t),e.prototype.tag="loca",e.prototype.parse=function(t){var e,r;return t.pos=this.offset,e=this.file.head.indexToLocFormat,this.offsets=0===e?function(){var e,n;for(n=[],r=0,e=this.length;r<e;r+=2)n.push(2*t.readUInt16());return n}.call(this):function(){var e,n;for(n=[],r=0,e=this.length;r<e;r+=4)n.push(t.readUInt32());return n}.call(this)},e.prototype.indexOf=function(t){return this.offsets[t]},e.prototype.lengthOf=function(t){return this.offsets[t+1]-this.offsets[t]},e.prototype.encode=function(t,e){for(var r=new Uint32Array(this.offsets.length),n=0,i=0,a=0;a<r.length;++a)if(r[a]=n,i<e.length&&e[i]==a){++i,r[a]=n;var o=this.offsets[a],s=this.offsets[a+1]-o;s>0&&(n+=s)}for(var c=new Array(4*r.length),u=0;u<r.length;++u)c[4*u+3]=255&r[u],c[4*u+2]=(65280&r[u])>>8,c[4*u+1]=(16711680&r[u])>>16,c[4*u]=(4278190080&r[u])>>24;return c},e}(Re),ar=function(){function t(t){this.font=t,this.subset={},this.unicodes={},this.next=33}return t.prototype.generateCmap=function(){var t,e,r,n,i;for(e in n=this.font.cmap.tables[0].codeMap,t={},i=this.subset)r=i[e],t[e]=n[r];return t},t.prototype.glyphsFor=function(t){var e,r,n,i,a,o,s;for(n={},a=0,o=t.length;a<o;a++)n[i=t[a]]=this.font.glyf.glyphFor(i);for(i in e=[],n)(null!=(r=n[i])?r.compound:void 0)&&e.push.apply(e,r.glyphIDs);if(e.length>0)for(i in s=this.glyphsFor(e))r=s[i],n[i]=r;return n},t.prototype.encode=function(t,e){var r,n,i,a,o,s,c,u,l,h,f,d,p,g,m;for(n in r=Ge.encode(this.generateCmap(),"unicode"),a=this.glyphsFor(t),f={0:0},m=r.charMap)f[(s=m[n]).old]=s.new;for(d in h=r.maxGlyphID,a)d in f||(f[d]=h++);return u=function(t){var e,r;for(e in r={},t)r[t[e]]=e;return r}(f),l=Object.keys(u).sort((function(t,e){return t-e})),p=function(){var t,e,r;for(r=[],t=0,e=l.length;t<e;t++)o=l[t],r.push(u[o]);return r}(),i=this.font.glyf.encode(a,p,f),c=this.font.loca.encode(i.offsets,p),g={cmap:this.font.cmap.raw(),glyf:i.table,loca:c,hmtx:this.font.hmtx.raw(),hhea:this.font.hhea.raw(),maxp:this.font.maxp.raw(),post:this.font.post.raw(),name:this.font.name.raw(),head:this.font.head.encode(e)},this.font.os2.exists&&(g["OS/2"]=this.font.os2.raw()),this.font.directory.encode(g)},t}();M.API.PDFObject=function(){var t;function e(){}return t=function(t,e){return(Array(e+1).join("0")+t).slice(-e)},e.convert=function(r){var n,i,a,o;if(Array.isArray(r))return"["+function(){var t,i,a;for(a=[],t=0,i=r.length;t<i;t++)n=r[t],a.push(e.convert(n));return a}().join(" ")+"]";if("string"==typeof r)return"/"+r;if(null!=r?r.isString:void 0)return"("+r+")";if(r instanceof Date)return"(D:"+t(r.getUTCFullYear(),4)+t(r.getUTCMonth(),2)+t(r.getUTCDate(),2)+t(r.getUTCHours(),2)+t(r.getUTCMinutes(),2)+t(r.getUTCSeconds(),2)+"Z)";if("[object Object]"==={}.toString.call(r)){for(i in a=["<<"],r)o=r[i],a.push("/"+i+" "+e.convert(o));return a.push(">>"),a.join("\n")}return""+r},e}(),t.AcroForm=xt,t.AcroFormAppearance=Lt,t.AcroFormButton=gt,t.AcroFormCheckBox=yt,t.AcroFormChoiceField=ht,t.AcroFormComboBox=dt,t.AcroFormEditBox=pt,t.AcroFormListBox=ft,t.AcroFormPasswordField=Nt,t.AcroFormPushButton=mt,t.AcroFormRadioButton=vt,t.AcroFormTextField=wt,t.GState=C,t.ShadingPattern=O,t.TilingPattern=B,t.default=M,t.jsPDF=M,Object.defineProperty(t,"__esModule",{value:!0})})); - diff --git a/scripts/svg2pdf.es.min.js b/scripts/svg2pdf.es.min.js deleted file mode 100644 index 5e9611b22..000000000 --- a/scripts/svg2pdf.es.min.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2015-2023 yWorks GmbH - * Copyright (c) 2013-2015 by Vitaly Puzrin - * - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import t from"cssesc";import e from"font-family-papandreou";import r,{GState as i,ShadingPattern as n,TilingPattern as a,jsPDF as s}from"./jspdf.es.min.js";import o from"svgpath";import{compare as l}from"specificity";var u=function(t,e){return(u=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])})(t,e)};function h(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}u(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}var f=function(){return(f=Object.assign||function(t){for(var e,r=1,i=arguments.length;r<i;r++)for(var n in e=arguments[r])Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}).apply(this,arguments)};function c(t,e,r,i){return new(r||(r=Promise))((function(n,a){function s(t){try{l(i.next(t))}catch(t){a(t)}}function o(t){try{l(i.throw(t))}catch(t){a(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,o)}l((i=i.apply(t,e||[])).next())}))}function p(t,e){var r,i,n,a,s={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return a={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function o(a){return function(o){return function(a){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,i&&(n=2&a[0]?i.return:a[0]?i.throw||((n=i.return)&&n.call(i),0):i.next)&&!(n=n.call(i,a[1])).done)return n;switch(i=0,n&&(a=[2&a[0],n.value]),a[0]){case 0:case 1:n=a;break;case 4:return s.label++,{value:a[1],done:!1};case 5:s.label++,i=a[1],a=[0];continue;case 7:a=s.ops.pop(),s.trys.pop();continue;default:if(!(n=s.trys,(n=n.length>0&&n[n.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!n||a[1]>n[0]&&a[1]<n[3])){s.label=a[1];break}if(6===a[0]&&s.label<n[1]){s.label=n[1],n=a;break}if(n&&s.label<n[2]){s.label=n[2],s.ops.push(a);break}n[2]&&s.ops.pop(),s.trys.pop();continue}a=e.call(t,s)}catch(t){a=[6,t],i=0}finally{r=n=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,o])}}}var d=function(){function t(t){if(this.a=void 0,this.r=0,this.g=0,this.b=0,this.simpleColors={},this.colorDefs=[],this.ok=!1,t){for(var e in"#"==t.charAt(0)&&(t=t.substr(1,6)),t=(t=t.replace(/ /g,"")).toLowerCase(),this.simpleColors={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgrey:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",grey:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightslategrey:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"},this.simpleColors)t==e&&(t=this.simpleColors[e]);this.colorDefs=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}];for(var r=0;r<this.colorDefs.length;r++){var i=this.colorDefs[r].re,n=this.colorDefs[r].process,a=i.exec(t);if(a){var s=n(a);this.r=s[0],this.g=s[1],this.b=s[2],this.ok=!0}}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b}}return t.prototype.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},t.prototype.toRGBA=function(){return"rgba("+this.r+", "+this.g+", "+this.b+", "+(this.a||"1")+")"},t.prototype.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r},t.prototype.getHelpXML=function(){for(var e=[],r=0;r<this.colorDefs.length;r++)for(var i=this.colorDefs[r].example,n=0;n<i.length;n++)e[e.length]=i[n];for(var a in this.simpleColors)e[e.length]=a;var s=document.createElement("ul");s.setAttribute("id","rgbcolor-examples");for(r=0;r<e.length;r++)try{var o=document.createElement("li"),l=new t(e[r]),u=document.createElement("div");u.style.cssText="margin: 3px; border: 1px solid black; background:"+l.toHex()+"; color:"+l.toHex(),u.appendChild(document.createTextNode("test"));var h=document.createTextNode(" "+e[r]+" -> "+l.toRGB()+" -> "+l.toHex());o.appendChild(u),o.appendChild(h),s.appendChild(o)}catch(t){}return s},t}(),m=function(){function t(t){this.color=t}return t.prototype.getFillData=function(t,e){return c(this,void 0,void 0,(function(){return p(this,(function(t){return[2,void 0]}))}))},t}(),g=function(){function t(){this.xmlSpace="",this.fill=null,this.fillOpacity=1,this.fontFamily="",this.fontSize=16,this.fontStyle="",this.fontWeight="",this.opacity=1,this.stroke=null,this.strokeDasharray=null,this.strokeDashoffset=0,this.strokeLinecap="",this.strokeLinejoin="",this.strokeMiterlimit=4,this.strokeOpacity=1,this.strokeWidth=1,this.alignmentBaseline="",this.textAnchor="",this.visibility="",this.color=null}return t.prototype.clone=function(){var e=new t;return e.xmlSpace=this.xmlSpace,e.fill=this.fill,e.fillOpacity=this.fillOpacity,e.fontFamily=this.fontFamily,e.fontSize=this.fontSize,e.fontStyle=this.fontStyle,e.fontWeight=this.fontWeight,e.opacity=this.opacity,e.stroke=this.stroke,e.strokeDasharray=this.strokeDasharray,e.strokeDashoffset=this.strokeDashoffset,e.strokeLinecap=this.strokeLinecap,e.strokeLinejoin=this.strokeLinejoin,e.strokeMiterlimit=this.strokeMiterlimit,e.strokeOpacity=this.strokeOpacity,e.strokeWidth=this.strokeWidth,e.textAnchor=this.textAnchor,e.alignmentBaseline=this.alignmentBaseline,e.visibility=this.visibility,e.color=this.color,e},t.default=function(){var e=new t;return e.xmlSpace="default",e.fill=new m(new d("rgb(0, 0, 0)")),e.fillOpacity=1,e.fontFamily="times",e.fontSize=16,e.fontStyle="normal",e.fontWeight="normal",e.opacity=1,e.stroke=null,e.strokeDasharray=null,e.strokeDashoffset=0,e.strokeLinecap="butt",e.strokeLinejoin="miter",e.strokeMiterlimit=4,e.strokeOpacity=1,e.strokeWidth=1,e.alignmentBaseline="baseline",e.textAnchor="start",e.visibility="visible",e.color=new d("rgb(0, 0, 0)"),e},t}(),y=function(){function t(t,e){var r,i,n;this.pdf=t,this.svg2pdfParameters=e.svg2pdfParameters,this.attributeState=e.attributeState?e.attributeState.clone():g.default(),this.viewport=e.viewport,this.refsHandler=e.refsHandler,this.styleSheets=e.styleSheets,this.textMeasure=e.textMeasure,this.transform=null!==(r=e.transform)&&void 0!==r?r:this.pdf.unitMatrix,this.withinClipPath=null!==(i=e.withinClipPath)&&void 0!==i&&i,this.withinUse=null!==(n=e.withinUse)&&void 0!==n&&n}return t.prototype.clone=function(e){var r,i,n,a;return void 0===e&&(e={}),new t(this.pdf,{svg2pdfParameters:this.svg2pdfParameters,attributeState:e.attributeState?e.attributeState.clone():this.attributeState.clone(),viewport:null!==(r=e.viewport)&&void 0!==r?r:this.viewport,refsHandler:this.refsHandler,styleSheets:this.styleSheets,textMeasure:this.textMeasure,transform:null!==(i=e.transform)&&void 0!==i?i:this.transform,withinClipPath:null!==(n=e.withinClipPath)&&void 0!==n?n:this.withinClipPath,withinUse:null!==(a=e.withinUse)&&void 0!==a?a:this.withinUse})},t}(),b=function(){function e(t){this.renderedElements={},this.idMap=t,this.idPrefix=String(e.instanceCounter++)}return e.prototype.getRendered=function(t,e,r){return c(this,void 0,void 0,(function(){var i,n;return p(this,(function(a){switch(a.label){case 0:return i=this.generateKey(t,e),this.renderedElements.hasOwnProperty(i)?[2,this.renderedElements[t]]:(n=this.get(t),this.renderedElements[i]=n,[4,r(n)]);case 1:return a.sent(),[2,n]}}))}))},e.prototype.get=function(e){return this.idMap[t(e,{isIdentifier:!0})]},e.prototype.generateKey=function(t,e){return this.idPrefix+"|"+t+"|"+(e||new d("rgb(0,0,0)")).toRGBA()},e.instanceCounter=0,e}();function v(t,e){return Math.atan2(e[1]-t[1],e[0]-t[0])}function x(t,e){return[2/3*(e[0]-t[0])+t[0],2/3*(e[1]-t[1])+t[1]]}function S(t){var e=Math.sqrt(t[0]*t[0]+t[1]*t[1]);return[t[0]/e,t[1]/e]}function w(t,e){return S([e[0]-t[0],e[1]-t[1]])}function k(t,e){return[t[0]+e[0],t[1]+e[1]]}function M(t,e){var r=t[0],i=t[1];return[e.a*r+e.c*i+e.e,e.b*r+e.d*i+e.f]}var T=function(){function t(){this.segments=[]}return t.prototype.moveTo=function(t,e){return this.segments.push(new C(t,e)),this},t.prototype.lineTo=function(t,e){return this.segments.push(new F(t,e)),this},t.prototype.curveTo=function(t,e,r,i,n,a){return this.segments.push(new A(t,e,r,i,n,a)),this},t.prototype.close=function(){return this.segments.push(new P),this},t.prototype.transform=function(t){this.segments.forEach((function(e){if(e instanceof C||e instanceof F||e instanceof A){var r=M([e.x,e.y],t);e.x=r[0],e.y=r[1]}if(e instanceof A){var i=M([e.x1,e.y1],t),n=M([e.x2,e.y2],t);e.x1=i[0],e.y1=i[1],e.x2=n[0],e.y2=n[1]}}))},t.prototype.draw=function(t){var e=t.pdf;this.segments.forEach((function(t){t instanceof C?e.moveTo(t.x,t.y):t instanceof F?e.lineTo(t.x,t.y):t instanceof A?e.curveTo(t.x1,t.y1,t.x2,t.y2,t.x,t.y):e.close()}))},t}(),C=function(t,e){this.x=t,this.y=e},F=function(t,e){this.x=t,this.y=e},A=function(t,e,r,i,n,a){this.x1=t,this.y1=e,this.x2=r,this.y2=i,this.x=n,this.y=a},P=function(){};function B(t,e){return e.split(",").indexOf((t.nodeName||t.tagName).toLowerCase())>=0}function N(t,e,r,i){var n;void 0===i&&(i=r);var a=null===(n=t.style)||void 0===n?void 0:n.getPropertyValue(i);if(a)return a;var s=e.getPropertyValue(t,i);return s||(t.hasAttribute(r)&&t.getAttribute(r)||void 0)}function O(t,e,r){if("none"===N(t.element,r.styleSheets,"display"))return!1;var i=e,n=N(t.element,r.styleSheets,"visibility");return n&&(i="hidden"!==n),i}function L(t,e,r){var i=O(t,e,r);return 0!==t.element.childNodes.length&&(t.children.forEach((function(t){t.isVisible(i,r)&&(i=!0)})),i)}var E=function(){function t(){this.markers=[]}return t.prototype.addMarker=function(t){this.markers.push(t)},t.prototype.draw=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,a,s,o;return p(this,(function(l){switch(l.label){case 0:e=0,l.label=1;case 1:return e<this.markers.length?(r=this.markers[e],i=void 0,n=r.angle,a=r.anchor,s=Math.cos(n),o=Math.sin(n),i=t.pdf.Matrix(s,o,-o,s,a[0],a[1]),i=t.pdf.matrixMult(t.pdf.Matrix(t.attributeState.strokeWidth,0,0,t.attributeState.strokeWidth,0,0),i),i=t.pdf.matrixMult(i,t.transform),t.pdf.saveGraphicsState(),[4,t.refsHandler.getRendered(r.id,null,(function(e){return e.apply(t)}))]):[3,4];case 2:l.sent(),t.pdf.doFormObject(r.id,i),t.pdf.restoreGraphicsState(),l.label=3;case 3:return e++,[3,1];case 4:return[2]}}))}))},t}(),I=function(t,e,r){this.id=t,this.anchor=e,this.angle=r},D=/url\(["']?#([^"']+)["']?\)/,H={bottom:"bottom","text-bottom":"bottom",top:"top","text-top":"top",hanging:"hanging",middle:"middle",central:"middle",center:"middle",mathematical:"middle",ideographic:"ideographic",alphabetic:"alphabetic",baseline:"alphabetic"};function R(t,e){var r;return(r=t&&t.toString().match(/^([\-0-9.]+)em$/))?parseFloat(r[1])*e:(r=t&&t.toString().match(/^([\-0-9.]+)(px|)$/))?parseFloat(r[1]):0}function V(t){return H[t]||"alphabetic"}function W(t){for(var e,r=[],i=/[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.?\d+))(?:[eE][+-]?\d+)?/g;e=i.exec(t);)r.push(parseFloat(e[0]));return r}function j(t,e){if("transparent"===t){var r=new d("rgb(0,0,0)");return r.a=0,r}if("currentcolor"===t.toLowerCase())return e||new d("rgb(0,0,0)");var i=/\s*rgba\(((?:[^,\)]*,){3}[^,\)]*)\)\s*/.exec(t);if(i){var n=W(i[1]),a=new d("rgb("+n.slice(0,3).join(",")+")");return a.a=n[3],a}return new d(t)}var G={"sans-serif":"helvetica",verdana:"helvetica",arial:"helvetica",fixed:"courier",monospace:"courier",terminal:"courier",serif:"times",cursive:"times",fantasy:"times"};var U,z=(U=r.version.split("."),2===parseFloat(U[0])&&3===parseFloat(U[1]));function Y(t,e){return z?400==e?"italic"==t?"italic":"normal":700==e&&"italic"!==t?"bold":t+""+e:400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"}function X(t,e){if("none"===N(e.element,t.styleSheets,"display"))return[0,0,0,0];var r=[0,0,0,0];return e.children.forEach((function(e){var i=e.getBoundingBox(t);r=[Math.min(r[0],i[0]),Math.min(r[1],i[1]),Math.max(r[0]+r[2],i[0]+i[2])-Math.min(r[0],i[0]),Math.max(r[1]+r[3],i[1]+i[3])-Math.min(r[1],i[1])]})),r}function q(t,e){var r=parseFloat,i=r(t.getAttribute("x1"))||r(N(t,e.styleSheets,"x"))||r(N(t,e.styleSheets,"cx"))-r(N(t,e.styleSheets,"r"))||0,n=r(t.getAttribute("x2"))||i+r(N(t,e.styleSheets,"width"))||r(N(t,e.styleSheets,"cx"))+r(N(t,e.styleSheets,"r"))||0,a=r(t.getAttribute("y1"))||r(N(t,e.styleSheets,"y"))||r(N(t,e.styleSheets,"cy"))-r(N(t,e.styleSheets,"r"))||0,s=r(t.getAttribute("y2"))||a+r(N(t,e.styleSheets,"height"))||r(N(t,e.styleSheets,"cy"))+r(N(t,e.styleSheets,"r"))||0;return[Math.min(i,n),Math.min(a,s),Math.max(i,n)-Math.min(i,n),Math.max(a,s)-Math.min(a,s)]}function _(t,e,r,i,n,a,s,o){void 0===o&&(o=!1);var l,u,h=e[0],f=e[1],c=e[2],p=e[3],d=n/c,m=a/p,g=t.getAttribute("preserveAspectRatio");if(g){var y=g.split(" ");"defer"===y[0]&&(y=y.slice(1)),l=y[0],u=y[1]||"meet"}else l="xMidYMid",u="meet";if("none"!==l&&("meet"===u?d=m=Math.min(d,m):"slice"===u&&(d=m=Math.max(d,m))),o)return s.pdf.Matrix(d,0,0,m,0,0);var b=r-h*d,v=i-f*m;l.indexOf("xMid")>=0?b+=(n-c*d)/2:l.indexOf("xMax")>=0&&(b+=n-c*d),l.indexOf("YMid")>=0?v+=(a-p*m)/2:l.indexOf("YMax")>=0&&(v+=a-p*m);var x=s.pdf.Matrix(1,0,0,1,b,v),S=s.pdf.Matrix(d,0,0,m,0,0);return s.pdf.matrixMult(S,x)}function $(t,e){if(!t||"none"===t)return e.pdf.unitMatrix;for(var r,i,n=/^[\s,]*matrix\(([^)]+)\)\s*/,a=/^[\s,]*translate\(([^)]+)\)\s*/,s=/^[\s,]*rotate\(([^)]+)\)\s*/,o=/^[\s,]*scale\(([^)]+)\)\s*/,l=/^[\s,]*skewX\(([^)]+)\)\s*/,u=/^[\s,]*skewY\(([^)]+)\)\s*/,h=e.pdf.unitMatrix;t.length>0&&t.length!==i;){i=t.length;var f=n.exec(t);if(f&&(r=W(f[1]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],r[1],r[2],r[3],r[4],r[5]),h),t=t.substr(f[0].length)),f=s.exec(t)){r=W(f[1]);var c=Math.PI*r[0]/180;if(h=e.pdf.matrixMult(e.pdf.Matrix(Math.cos(c),Math.sin(c),-Math.sin(c),Math.cos(c),0,0),h),r[1]||r[2]){var p=e.pdf.Matrix(1,0,0,1,r[1],r[2]),d=e.pdf.Matrix(1,0,0,1,-r[1],-r[2]);h=e.pdf.matrixMult(d,e.pdf.matrixMult(h,p))}t=t.substr(f[0].length)}(f=a.exec(t))&&(r=W(f[1]),h=e.pdf.matrixMult(e.pdf.Matrix(1,0,0,1,r[0],r[1]||0),h),t=t.substr(f[0].length)),(f=o.exec(t))&&((r=W(f[1]))[1]||(r[1]=r[0]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],0,0,r[1],0,0),h),t=t.substr(f[0].length)),(f=l.exec(t))&&(r=parseFloat(f[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,0,Math.tan(r),1,0,0),h),t=t.substr(f[0].length)),(f=u.exec(t))&&(r=parseFloat(f[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,Math.tan(r),0,1,0,0),h),t=t.substr(f[0].length))}return h}var Q=function(){function t(t,e){this.element=t,this.children=e,this.parent=null}return t.prototype.setParent=function(t){this.parent=t},t.prototype.getParent=function(){return this.parent},t.prototype.getBoundingBox=function(t){return"none"===N(this.element,t.styleSheets,"display")?[0,0,0,0]:this.getBoundingBoxCore(t)},t.prototype.computeNodeTransform=function(t){var e=this.computeNodeTransformCore(t),r=N(this.element,t.styleSheets,"transform");return r?t.pdf.matrixMult(e,$(r,t)):e},t}(),K=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.render=function(t){return Promise.resolve()},e.prototype.getBoundingBoxCore=function(t){return[]},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(Q),J=function(t){function e(e,r,i){var n=t.call(this,r,i)||this;return n.pdfGradientType=e,n.contextColor=void 0,n}return h(e,t),e.prototype.apply=function(t){return c(this,void 0,void 0,(function(){var e,r,a,s,o,l;return p(this,(function(u){return(e=this.element.getAttribute("id"))?(r=this.getStops(t.styleSheets),a=0,s=!1,r.forEach((function(t){var e=t.opacity;e&&1!==e&&(a+=e,s=!0)})),s&&(o=new i({opacity:a/r.length})),l=new n(this.pdfGradientType,this.getCoordinates(),r,o),t.pdf.addShadingPattern(e,l),[2]):[2]}))}))},e.prototype.getStops=function(t){var r=this;if(this.stops)return this.stops;if(void 0===this.contextColor){this.contextColor=null;for(var i=this;i;){var n=N(i.element,t,"color");if(n){this.contextColor=j(n,null);break}i=i.getParent()}}var a=[];return this.children.forEach((function(i){if("stop"===i.element.tagName.toLowerCase()){var n=N(i.element,t,"color"),s=j(N(i.element,t,"stop-color")||"",n?j(n,null):r.contextColor),o=parseFloat(N(i.element,t,"stop-opacity")||"1");a.push({offset:e.parseGradientOffset(i.element.getAttribute("offset")||"0"),color:[s.r,s.g,s.b],opacity:o})}})),this.stops=a},e.prototype.getBoundingBoxCore=function(t){return q(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return L(this,t,e)},e.parseGradientOffset=function(t){var e=parseFloat(t);return!isNaN(e)&&t.indexOf("%")>=0?e/100:e},e}(K),Z=function(t){function e(e,r){return t.call(this,"axial",e,r)||this}return h(e,t),e.prototype.getCoordinates=function(){return[parseFloat(this.element.getAttribute("x1")||"0"),parseFloat(this.element.getAttribute("y1")||"0"),parseFloat(this.element.getAttribute("x2")||"1"),parseFloat(this.element.getAttribute("y2")||"0")]},e}(J),tt=function(t){function e(e,r){return t.call(this,"radial",e,r)||this}return h(e,t),e.prototype.getCoordinates=function(){var t=this.element.getAttribute("cx"),e=this.element.getAttribute("cy"),r=this.element.getAttribute("fx"),i=this.element.getAttribute("fy");return[parseFloat(r||t||"0.5"),parseFloat(i||e||"0.5"),0,parseFloat(t||"0.5"),parseFloat(e||"0.5"),parseFloat(this.element.getAttribute("r")||"0.5")]},e}(J),et=function(){function t(t,e){this.key=t,this.gradient=e}return t.prototype.getFillData=function(t,e){return c(this,void 0,void 0,(function(){var r,i,n;return p(this,(function(a){switch(a.label){case 0:return[4,e.refsHandler.getRendered(this.key,null,(function(t){return t.apply(new y(e.pdf,{refsHandler:e.refsHandler,textMeasure:e.textMeasure,styleSheets:e.styleSheets,viewport:e.viewport,svg2pdfParameters:e.svg2pdfParameters}))}))];case 1:return a.sent(),this.gradient.element.hasAttribute("gradientUnits")&&"objectboundingbox"!==this.gradient.element.getAttribute("gradientUnits").toLowerCase()?r=e.pdf.unitMatrix:(i=t.getBoundingBox(e),r=e.pdf.Matrix(i[2],0,0,i[3],i[0],i[1])),n=$(N(this.gradient.element,e.styleSheets,"gradientTransform","transform"),e),[2,{key:this.key,matrix:e.pdf.matrixMult(n,r)}]}}))}))},t}(),rt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.apply=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,s;return p(this,(function(o){switch(o.label){case 0:if(!(e=this.element.getAttribute("id")))return[2];r=this.getBoundingBox(t),i=new a([r[0],r[1],r[0]+r[2],r[1]+r[3]],r[2],r[3]),t.pdf.beginTilingPattern(i),n=0,s=this.children,o.label=1;case 1:return n<s.length?[4,s[n].render(new y(t.pdf,{attributeState:t.attributeState,refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]:[3,4];case 2:o.sent(),o.label=3;case 3:return n++,[3,1];case 4:return t.pdf.endTilingPattern(e,i),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return q(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return L(this,t,e)},e}(K),it=function(){function t(t,e){this.key=t,this.pattern=e}return t.prototype.getFillData=function(t,e){return c(this,void 0,void 0,(function(){var r,i,n,a,s,o,l,u,h,f,c,d;return p(this,(function(p){switch(p.label){case 0:return[4,e.refsHandler.getRendered(this.key,null,(function(t){return t.apply(new y(e.pdf,{refsHandler:e.refsHandler,textMeasure:e.textMeasure,styleSheets:e.styleSheets,viewport:e.viewport,svg2pdfParameters:e.svg2pdfParameters}))}))];case 1:return p.sent(),r={key:this.key,boundingBox:void 0,xStep:0,yStep:0,matrix:void 0},n=e.pdf.unitMatrix,this.pattern.element.hasAttribute("patternUnits")&&"objectboundingbox"!==this.pattern.element.getAttribute("patternUnits").toLowerCase()||(i=t.getBoundingBox(e),n=e.pdf.Matrix(1,0,0,1,i[0],i[1]),s=this.pattern.getBoundingBox(e),o=s[0]*i[0]||0,l=s[1]*i[1]||0,u=s[2]*i[2]||0,h=s[3]*i[3]||0,r.boundingBox=[o,l,o+u,l+h],r.xStep=u,r.yStep=h),a=e.pdf.unitMatrix,this.pattern.element.hasAttribute("patternContentUnits")&&"objectboundingbox"===this.pattern.element.getAttribute("patternContentUnits").toLowerCase()&&(i||(i=t.getBoundingBox(e)),a=e.pdf.Matrix(i[2],0,0,i[3],0,0),s=r.boundingBox||this.pattern.getBoundingBox(e),o=s[0]/i[0]||0,l=s[1]/i[1]||0,u=s[2]/i[2]||0,h=s[3]/i[3]||0,r.boundingBox=[o,l,o+u,l+h],r.xStep=u,r.yStep=h),f=e.pdf.unitMatrix,(c=N(this.pattern.element,e.styleSheets,"patternTransform","transform"))&&(f=$(c,e)),d=a,d=e.pdf.matrixMult(d,n),d=e.pdf.matrixMult(d,f),d=e.pdf.matrixMult(d,e.transform),r.matrix=d,[2,r]}}))}))},t}();function nt(t,e){var r=D.exec(t);if(r){var i=r[1],n=e.refsHandler.get(i);return n&&(n instanceof Z||n instanceof tt)?function(t,e,r){var i=e.getStops(r.styleSheets);if(0===i.length)return null;if(1===i.length){var n=i[0].color,a=new d;return a.ok=!0,a.r=n[0],a.g=n[1],a.b=n[2],a.a=i[0].opacity,new m(a)}return new et(t,e)}(i,n,e):n&&n instanceof rt?new it(i,n):new m(new d("rgb(0, 0, 0)"))}var a=j(t,e.attributeState.color);return a.ok?new m(a):null}function at(t,r,i){var n=i||r.element,a=N(n,t.styleSheets,"color");if(a){var s=j(a,t.attributeState.color);s.ok?t.attributeState.color=s:t.attributeState.color=new d("rgb(0,0,0)")}var o=N(n,t.styleSheets,"visibility");o&&(t.attributeState.visibility=o);var l=N(n,t.styleSheets,"fill");l&&(t.attributeState.fill=nt(l,t));var u=N(n,t.styleSheets,"fill-opacity");u&&(t.attributeState.fillOpacity=parseFloat(u));var h=N(n,t.styleSheets,"stroke-opacity");h&&(t.attributeState.strokeOpacity=parseFloat(h));var f=N(n,t.styleSheets,"opacity");f&&(t.attributeState.opacity=parseFloat(f));var c=N(n,t.styleSheets,"stroke-width");void 0!==c&&""!==c&&(t.attributeState.strokeWidth=Math.abs(parseFloat(c)));var p=N(n,t.styleSheets,"stroke");if(p)if("none"===p)t.attributeState.stroke=null;else{var g=j(p,t.attributeState.color);g.ok&&(t.attributeState.stroke=new m(g))}var y=N(n,t.styleSheets,"stroke-linecap");y&&(t.attributeState.strokeLinecap=y);var b=N(n,t.styleSheets,"stroke-linejoin");b&&(t.attributeState.strokeLinejoin=b);var v=N(n,t.styleSheets,"stroke-dasharray");if(v){var x=parseInt(N(n,t.styleSheets,"stroke-dashoffset")||"0");t.attributeState.strokeDasharray=W(v),t.attributeState.strokeDashoffset=x}var S=N(n,t.styleSheets,"stroke-miterlimit");void 0!==S&&""!==S&&(t.attributeState.strokeMiterlimit=parseFloat(S));var w=n.getAttribute("xml:space");w&&(t.attributeState.xmlSpace=w);var k=N(n,t.styleSheets,"font-weight");k&&(t.attributeState.fontWeight=k);var M=N(n,t.styleSheets,"font-style");M&&(t.attributeState.fontStyle=M);var T=N(n,t.styleSheets,"font-family");if(T){var C=e.parse(T);t.attributeState.fontFamily=function(t,e,r){var i=Y(t.fontStyle,t.fontWeight),n=r.pdf.getFontList(),a="";return e.some((function(t){var e=n[t];return e&&e.indexOf(i)>=0?(a=t,!0):(t=t.toLowerCase(),!!G.hasOwnProperty(t)&&(a=t,!0))}))||(a="times"),a}(t.attributeState,C,t)}var F=N(n,t.styleSheets,"font-size");if(F){var A=t.pdf.getFontSize();t.attributeState.fontSize=R(F,A)}var P=N(n,t.styleSheets,"vertical-align")||N(n,t.styleSheets,"alignment-baseline");if(P){var B=P.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/);B&&(t.attributeState.alignmentBaseline=B[0])}var O=N(n,t.styleSheets,"text-anchor");O&&(t.attributeState.textAnchor=O)}function st(t,e,r){var n=1,a=1;n*=t.attributeState.fillOpacity,n*=t.attributeState.opacity,t.attributeState.fill instanceof m&&void 0!==t.attributeState.fill.color.a&&(n*=t.attributeState.fill.color.a),a*=t.attributeState.strokeOpacity,a*=t.attributeState.opacity,t.attributeState.stroke instanceof m&&void 0!==t.attributeState.stroke.color.a&&(a*=t.attributeState.stroke.color.a);var s,o,l=n<1,u=a<1;if(B(r,"use")?(l=!0,u=!0,n*=t.attributeState.fill?1:0,a*=t.attributeState.stroke?1:0):t.withinUse&&(t.attributeState.fill!==e.attributeState.fill?(l=!0,n*=t.attributeState.fill?1:0):l&&!t.attributeState.fill&&(n=0),t.attributeState.stroke!==e.attributeState.stroke?(u=!0,a*=t.attributeState.stroke?1:0):u&&!t.attributeState.stroke&&(a=0)),l||u){var h={};l&&(h.opacity=n),u&&(h["stroke-opacity"]=a),t.pdf.setGState(new i(h))}if(t.attributeState.fill&&t.attributeState.fill!==e.attributeState.fill&&t.attributeState.fill instanceof m&&t.attributeState.fill.color.ok&&!B(r,"text")&&t.pdf.setFillColor(t.attributeState.fill.color.r,t.attributeState.fill.color.g,t.attributeState.fill.color.b),t.attributeState.strokeWidth!==e.attributeState.strokeWidth&&t.pdf.setLineWidth(t.attributeState.strokeWidth),t.attributeState.stroke!==e.attributeState.stroke&&t.attributeState.stroke instanceof m&&t.pdf.setDrawColor(t.attributeState.stroke.color.r,t.attributeState.stroke.color.g,t.attributeState.stroke.color.b),t.attributeState.strokeLinecap!==e.attributeState.strokeLinecap&&t.pdf.setLineCap(t.attributeState.strokeLinecap),t.attributeState.strokeLinejoin!==e.attributeState.strokeLinejoin&&t.pdf.setLineJoin(t.attributeState.strokeLinejoin),t.attributeState.strokeDasharray===e.attributeState.strokeDasharray&&t.attributeState.strokeDashoffset===e.attributeState.strokeDashoffset||!t.attributeState.strokeDasharray||t.pdf.setLineDashPattern(t.attributeState.strokeDasharray,t.attributeState.strokeDashoffset),t.attributeState.strokeMiterlimit!==e.attributeState.strokeMiterlimit&&t.pdf.setLineMiterLimit(t.attributeState.strokeMiterlimit),t.attributeState.fontFamily!==e.attributeState.fontFamily&&(s=G.hasOwnProperty(t.attributeState.fontFamily)?G[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.attributeState.fill&&t.attributeState.fill!==e.attributeState.fill&&t.attributeState.fill instanceof m&&t.attributeState.fill.color.ok){var f=t.attributeState.fill.color;t.pdf.setTextColor(f.r,f.g,f.b)}t.attributeState.fontWeight===e.attributeState.fontWeight&&t.attributeState.fontStyle===e.attributeState.fontStyle||(o=Y(t.attributeState.fontStyle,t.attributeState.fontWeight)),void 0===s&&void 0===o||(void 0===s&&(s=G.hasOwnProperty(t.attributeState.fontFamily)?G[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.pdf.setFont(s,o)),t.attributeState.fontSize!==e.attributeState.fontSize&&t.pdf.setFontSize(t.attributeState.fontSize*t.pdf.internal.scaleFactor)}function ot(t,e,r){var i=D.exec(t);if(i){var n=i[1];return r.refsHandler.get(n)||void 0}}function lt(t,e,r){return c(this,void 0,void 0,(function(){var i,n;return p(this,(function(a){switch(a.label){case 0:return i=r.clone(),e.element.hasAttribute("clipPathUnits")&&"objectboundingbox"===e.element.getAttribute("clipPathUnits").toLowerCase()&&(n=t.getBoundingBox(r),i.transform=r.pdf.matrixMult(r.pdf.Matrix(n[2],0,0,n[3],n[0],n[1]),r.transform)),[4,e.apply(i)];case 1:return a.sent(),[2]}}))}))}var ut=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.render=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n;return p(this,(function(a){switch(a.label){case 0:return this.isVisible("hidden"!==t.attributeState.visibility,t)?((e=t.clone()).transform=e.pdf.matrixMult(this.computeNodeTransform(e),t.transform),at(e,this),r=N(this.element,e.styleSheets,"clip-path"),(i=r&&"none"!==r)?(n=ot(r,0,e))?n.isVisible(!0,e)?(e.pdf.saveGraphicsState(),[4,lt(this,n,e)]):[3,2]:[3,4]:[3,5]):[2];case 1:return a.sent(),[3,3];case 2:return[2];case 3:return[3,5];case 4:i=!1,a.label=5;case 5:return e.withinClipPath||e.pdf.saveGraphicsState(),st(e,t,this.element),[4,this.renderCore(e)];case 6:return a.sent(),e.withinClipPath||e.pdf.restoreGraphicsState(),i&&e.pdf.restoreGraphicsState(),[2]}}))}))},e}(Q),ht=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e}(ut),ft=function(t){function e(e,r,i){var n=t.call(this,r,i)||this;return n.cachedPath=null,n.hasMarkers=e,n}return h(e,t),e.prototype.renderCore=function(t){return c(this,void 0,void 0,(function(){var e;return p(this,(function(r){switch(r.label){case 0:return null===(e=this.getCachedPath(t))||0===e.segments.length?[2]:(t.withinClipPath?e.transform(t.transform):t.pdf.setCurrentTransformationMatrix(t.transform),e.draw(t),[4,this.fillOrStroke(t)]);case 1:return r.sent(),this.hasMarkers?[4,this.drawMarkers(t,e)]:[3,3];case 2:r.sent(),r.label=3;case 3:return[2]}}))}))},e.prototype.getCachedPath=function(t){return this.cachedPath||(this.cachedPath=this.getPath(t))},e.prototype.drawMarkers=function(t,e){return c(this,void 0,void 0,(function(){return p(this,(function(r){switch(r.label){case 0:return[4,this.getMarkers(e,t).draw(t.clone({transform:t.pdf.unitMatrix}))];case 1:return r.sent(),[2]}}))}))},e.prototype.fillOrStroke=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,a;return p(this,(function(s){switch(s.label){case 0:return t.withinClipPath?[2]:(e=t.attributeState.fill,r=t.attributeState.stroke&&0!==t.attributeState.strokeWidth,e?[4,e.getFillData(this,t)]:[3,2]);case 1:return n=s.sent(),[3,3];case 2:n=void 0,s.label=3;case 3:return i=n,a="evenodd"===N(this.element,t.styleSheets,"fill-rule"),e&&r||t.withinUse?a?t.pdf.fillStrokeEvenOdd(i):t.pdf.fillStroke(i):e?a?t.pdf.fillEvenOdd(i):t.pdf.fill(i):r?t.pdf.stroke():t.pdf.discardPath(),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){var e=this.getCachedPath(t);if(!e)return[0,0,0,0];for(var r=Number.POSITIVE_INFINITY,i=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,a=Number.NEGATIVE_INFINITY,s=0,o=0,l=0;l<e.segments.length;l++){var u=e.segments[l];(u instanceof C||u instanceof F||u instanceof A)&&(s=u.x,o=u.y),u instanceof A?(r=Math.min(r,s,u.x1,u.x2,u.x),n=Math.max(n,s,u.x1,u.x2,u.x),i=Math.min(i,o,u.y1,u.y2,u.y),a=Math.max(a,o,u.y1,u.y2,u.y)):(r=Math.min(r,s),n=Math.max(n,s),i=Math.min(i,o),a=Math.max(a,o))}return[r,i,n-r,a-i]},e.prototype.getMarkers=function(t,e){var r=N(this.element,e.styleSheets,"marker-start"),i=N(this.element,e.styleSheets,"marker-mid"),n=N(this.element,e.styleSheets,"marker-end"),a=new E;if(r||i||n){n&&(n=ct(n)),r&&(r=ct(r)),i&&(i=ct(i));for(var s=t.segments,o=[1,0],l=void 0,u=!1,h=[1,0],f=!1,c=function(t){var e=s[t],c=r&&(1===t||!(s[t]instanceof C)&&s[t-1]instanceof C);c&&s.forEach((function(e,r){if(!f&&e instanceof P&&r>t){var i=s[r-1];f=(i instanceof C||i instanceof F||i instanceof A)&&i}}));var p=n&&(t===s.length-1||!(s[t]instanceof C)&&s[t+1]instanceof C),d=i&&t>0&&!(1===t&&s[t-1]instanceof C),m=s[t-1]||null;if(m instanceof C||m instanceof F||m instanceof A){if(e instanceof A)c&&a.addMarker(new I(r,[m.x,m.y],v(f?[f.x,f.y]:[m.x,m.y],[e.x1,e.y1]))),p&&a.addMarker(new I(n,[e.x,e.y],v([e.x2,e.y2],[e.x,e.y]))),d&&(l=w([m.x,m.y],[e.x1,e.y1]),l=m instanceof C?l:S(k(o,l)),a.addMarker(new I(i,[m.x,m.y],Math.atan2(l[1],l[0])))),o=w([e.x2,e.y2],[e.x,e.y]);else if(e instanceof C||e instanceof F){if(l=w([m.x,m.y],[e.x,e.y]),c){var g=f?w([f.x,f.y],[e.x,e.y]):l;a.addMarker(new I(r,[m.x,m.y],Math.atan2(g[1],g[0])))}if(p&&a.addMarker(new I(n,[e.x,e.y],Math.atan2(l[1],l[0]))),d){g=e instanceof C?o:m instanceof C?l:S(k(o,l));a.addMarker(new I(i,[m.x,m.y],Math.atan2(g[1],g[0])))}o=l}else if(e instanceof P){if(l=w([m.x,m.y],[u.x,u.y]),d){g=m instanceof C?l:S(k(o,l));a.addMarker(new I(i,[m.x,m.y],Math.atan2(g[1],g[0])))}if(p){g=S(k(l,h));a.addMarker(new I(n,[u.x,u.y],Math.atan2(g[1],g[0])))}o=l}}else{u=e instanceof C&&e;var y=s[t+1];(y instanceof C||y instanceof F||y instanceof A)&&(h=w([u.x,u.y],[y.x,y.y]))}},p=0;p<s.length;p++)c(p)}return a},e}(ht);function ct(t){var e=D.exec(t);return e&&e[1]||void 0}var pt=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return h(e,t),e.prototype.getPath=function(t){if(t.withinClipPath||null===t.attributeState.stroke)return null;var e=parseFloat(this.element.getAttribute("x1")||"0"),r=parseFloat(this.element.getAttribute("y1")||"0"),i=parseFloat(this.element.getAttribute("x2")||"0"),n=parseFloat(this.element.getAttribute("y2")||"0");return e||i||r||n?(new T).moveTo(e,r).lineTo(i,n):null},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e.prototype.fillOrStroke=function(e){return c(this,void 0,void 0,(function(){return p(this,(function(r){switch(r.label){case 0:return e.attributeState.fill=null,[4,t.prototype.fillOrStroke.call(this,e)];case 1:return r.sent(),[2]}}))}))},e}(ft),dt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.apply=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,a;return p(this,(function(s){switch(s.label){case 0:return this.isVisible("hidden"!==t.attributeState.visibility,t)?((e=t.clone()).transform=e.pdf.unitMatrix,at(e,this),r=N(this.element,e.styleSheets,"clip-path"),r&&"none"!==r&&(i=ot(r,0,e))?i.isVisible(!0,e)?[4,lt(this,i,e)]:[3,2]:[3,3]):[2];case 1:return s.sent(),[3,3];case 2:return[2];case 3:st(e,t,this.element),n=0,a=this.children,s.label=4;case 4:return n<a.length?[4,a[n].render(e)]:[3,7];case 5:s.sent(),s.label=6;case 6:return n++,[3,4];case 7:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return X(t,this)},e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.computeNodeTransformCore=function(t){var e=parseFloat(N(this.element,t.styleSheets,"x")||"0"),r=parseFloat(N(this.element,t.styleSheets,"y")||"0"),i=this.element.getAttribute("viewBox");if(i){var n=W(i),a=parseFloat(N(this.element,t.styleSheets,"width")||N(this.element.ownerSVGElement,t.styleSheets,"width")||i[2]),s=parseFloat(N(this.element,t.styleSheets,"height")||N(this.element.ownerSVGElement,t.styleSheets,"height")||i[3]);return _(this.element,n,e,r,a,s,t)}return t.pdf.Matrix(1,0,0,1,e,r)},e}(K),mt=function(t,e){this.width=t,this.height=e},gt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.renderCore=function(t){return c(this,void 0,void 0,(function(){var r,i,n,a,s,o,l,u,h,f,c,d,m;return p(this,(function(p){switch(p.label){case 0:return r=parseFloat,(i=this.element.getAttribute("href")||this.element.getAttribute("xlink:href"))?(n=i.substring(1),a=t.refsHandler.get(n),s=B(a.element,"symbol,svg")&&a.element.hasAttribute("viewBox"),o=r(N(this.element,t.styleSheets,"x")||"0"),l=r(N(this.element,t.styleSheets,"y")||"0"),u=void 0,h=void 0,s?(u=r(N(this.element,t.styleSheets,"width")||N(a.element,t.styleSheets,"width")||"0"),h=r(N(this.element,t.styleSheets,"height")||N(a.element,t.styleSheets,"height")||"0"),o+=r(N(a.element,t.styleSheets,"x")||"0"),l+=r(N(a.element,t.styleSheets,"y")||"0"),c=W(a.element.getAttribute("viewBox")),f=_(a.element,c,o,l,u,h,t)):f=t.pdf.Matrix(1,0,0,1,o,l),d=new y(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,withinUse:!0,viewport:s?new mt(u,h):t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}),m=t.attributeState.color,[4,t.refsHandler.getRendered(n,m,(function(t){return e.renderReferencedNode(t,n,m,d)}))]):[2];case 1:return p.sent(),t.pdf.saveGraphicsState(),t.pdf.setCurrentTransformationMatrix(t.transform),s&&"visible"!==N(a.element,t.styleSheets,"overflow")&&(t.pdf.rect(o,l,u,h),t.pdf.clip().discardPath()),t.pdf.doFormObject(t.refsHandler.generateKey(n,m),f),t.pdf.restoreGraphicsState(),[2]}}))}))},e.renderReferencedNode=function(t,e,r,i){return c(this,void 0,void 0,(function(){var n;return p(this,(function(a){switch(a.label){case 0:return n=[(n=t.getBoundingBox(i))[0]-.5*n[2],n[1]-.5*n[3],2*n[2],2*n[3]],i.attributeState.color=r,i.pdf.beginFormObject(n[0],n[1],n[2],n[3],i.pdf.unitMatrix),t instanceof dt?[4,t.apply(i)]:[3,2];case 1:return a.sent(),[3,4];case 2:return[4,t.render(i)];case 3:a.sent(),a.label=4;case 4:return i.pdf.endFormObject(i.refsHandler.generateKey(e,r)),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return q(this.element,t)},e.prototype.isVisible=function(t,e){return O(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(ht),yt=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return h(e,t),e.prototype.getPath=function(t){var e=parseFloat(N(this.element,t.styleSheets,"width")||"0"),r=parseFloat(N(this.element,t.styleSheets,"height")||"0");if(!isFinite(e)||e<=0||!isFinite(r)||r<=0)return null;var i=N(this.element,t.styleSheets,"rx"),n=N(this.element,t.styleSheets,"ry"),a=Math.min(parseFloat(i||n||"0"),.5*e),s=Math.min(parseFloat(n||i||"0"),.5*r),o=parseFloat(N(this.element,t.styleSheets,"x")||"0"),l=parseFloat(N(this.element,t.styleSheets,"y")||"0"),u=4/3*(Math.SQRT2-1);return 0===a&&0===s?(new T).moveTo(o,l).lineTo(o+e,l).lineTo(o+e,l+r).lineTo(o,l+r).close():(new T).moveTo(o+=a,l).lineTo(o+=e-2*a,l).curveTo(o+a*u,l,o+a,l+(s-s*u),o+=a,l+=s).lineTo(o,l+=r-2*s).curveTo(o,l+s*u,o-a*u,l+s,o-=a,l+=s).lineTo(o+=2*a-e,l).curveTo(o-a*u,l,o-a,l-s*u,o-=a,l-=s).lineTo(o,l+=2*s-r).curveTo(o,l-s*u,o+a*u,l-s,o+=a,l-=s).close()},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e}(ft),bt=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return h(e,t),e.prototype.getPath=function(t){var e=this.getRx(t),r=this.getRy(t);if(!isFinite(e)||r<=0||!isFinite(r)||r<=0)return null;var i=parseFloat(N(this.element,t.styleSheets,"cx")||"0"),n=parseFloat(N(this.element,t.styleSheets,"cy")||"0"),a=4/3*(Math.SQRT2-1)*e,s=4/3*(Math.SQRT2-1)*r;return(new T).moveTo(i+e,n).curveTo(i+e,n-s,i+a,n-r,i,n-r).curveTo(i-a,n-r,i-e,n-s,i-e,n).curveTo(i-e,n+s,i-a,n+r,i,n+r).curveTo(i+a,n+r,i+e,n+s,i+e,n)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e}(ft),vt=function(t){function e(e,r){return t.call(this,e,r)||this}return h(e,t),e.prototype.getRx=function(t){return parseFloat(N(this.element,t.styleSheets,"rx")||"0")},e.prototype.getRy=function(t){return parseFloat(N(this.element,t.styleSheets,"ry")||"0")},e}(bt);function xt(t){var e="invisible",r=t.stroke&&0!==t.strokeWidth,i=t.fill;return i&&r?e="fillThenStroke":i?e="fill":r&&(e="stroke"),e}function St(t){return t.replace(/[\n\r]/g,"")}function wt(t){return t.replace(/[\t]/g," ")}function kt(t){return t.replace(/ +/g," ")}function Mt(t,e,r){switch(N(t,r.styleSheets,"text-transform")){case"uppercase":return e.toUpperCase();case"lowercase":return e.toLowerCase();default:return e}}var Tt=function(){function t(t,e,r,i){this.textNode=t,this.texts=[],this.textNodes=[],this.contexts=[],this.textAnchor=e,this.originX=r,this.originY=i,this.textMeasures=[]}return t.prototype.setX=function(t){this.originX=t},t.prototype.setY=function(t){this.originY=t},t.prototype.add=function(t,e,r){this.texts.push(e),this.textNodes.push(t),this.contexts.push(r)},t.prototype.rightTrimText=function(){for(var t=this.texts.length-1;t>=0;t--)if("default"===this.contexts[t].attributeState.xmlSpace&&(this.texts[t]=this.texts[t].replace(/\s+$/,"")),this.texts[t].match(/[^\s]/))return!1;return!0},t.prototype.measureText=function(t){for(var e=0;e<this.texts.length;e++)this.textMeasures.push({width:t.textMeasure.measureTextWidth(this.texts[e],this.contexts[e].attributeState),length:this.texts[e].length})},t.prototype.put=function(e,r){var i,n,a,s,o=[],l=[],u=[],h=this.originX,f=this.originY,c=h,p=h;for(i=0;i<this.textNodes.length;i++){n=this.textNodes[i],a=this.contexts[i],s=this.textMeasures[i]||{width:e.textMeasure.measureTextWidth(this.texts[i],this.contexts[i].attributeState),length:this.texts[i].length};var d=h,m=f;if("#text"!==n.nodeName&&!o.includes(n)){o.push(n);var g=t.resolveRelativePositionAttribute(n,"dx");null!==g&&(d+=R(g,a.attributeState.fontSize));var y=t.resolveRelativePositionAttribute(n,"dy");null!==y&&(m+=R(y,a.attributeState.fontSize))}l[i]=d,u[i]=m,h=d+s.width+s.length*r,f=m,c=Math.min(c,d),p=Math.max(p,h)}var b=0;switch(this.textAnchor){case"start":b=0;break;case"middle":b=(p-c)/2;break;case"end":b=p-c}for(i=0;i<this.textNodes.length;i++)if(n=this.textNodes[i],a=this.contexts[i],"#text"===n.nodeName||"hidden"!==a.attributeState.visibility){e.pdf.saveGraphicsState(),st(a,e,n);var v=a.attributeState.alignmentBaseline,x=xt(a.attributeState);e.pdf.text(this.texts[i],l[i]-b,u[i],{baseline:V(v),angle:e.transform,renderingMode:"fill"===x?void 0:x,charSpace:0===r?void 0:r}),e.pdf.restoreGraphicsState()}return[h,f]},t.resolveRelativePositionAttribute=function(t,e){for(var r,i=t;i&&B(i,"tspan");){if(i.hasAttribute(e))return i.getAttribute(e);if((null===(r=t.parentElement)||void 0===r?void 0:r.firstChild)!==t)break;i=i.parentElement}return null},t}(),Ct=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.processTSpans=function(t,e,r,i,n,a){for(var s=r.pdf.getFontSize(),o=r.attributeState.xmlSpace,l=!0,u=!1,h=0;h<e.childNodes.length;h++){var f=e.childNodes[h];if(f.textContent){var c=f.textContent;if("#text"===f.nodeName){var p=St(c);p=wt(p),"default"===o&&(p=kt(p),l&&p.match(/^\s/)&&(u=!0),p.match(/[^\s]/)&&(l=!1),a.prevText.match(/\s$/)&&(p=p.replace(/^\s+/,"")));var d=Mt(e,p,r);n.add(e,d,r),a.prevText=c,a.prevContext=r}else if(B(f,"title"));else if(B(f,"tspan")){var m=f,g=m.getAttribute("x");if(null!==g){var y=R(g,s);n=new Tt(this,N(m,r.styleSheets,"text-anchor")||r.attributeState.textAnchor,y,0),i.push({type:"y",chunk:n})}var b=m.getAttribute("y");if(null!==b){var v=R(b,s);n=new Tt(this,N(m,r.styleSheets,"text-anchor")||r.attributeState.textAnchor,0,v),i.push({type:"x",chunk:n})}var x=r.clone();at(x,t,m),this.processTSpans(t,m,x,i,n,a)}}}return u},e.prototype.renderCore=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,a,s,o,l,u,h,f,c,d,m,g,y,b,v,x,S,w,k,M;return p(this,(function(p){if(t.pdf.saveGraphicsState(),e=0,r=0,i=1,n=t.pdf.getFontSize(),a=R(this.element.getAttribute("x"),n),s=R(this.element.getAttribute("y"),n),o=R(this.element.getAttribute("dx"),n),l=R(this.element.getAttribute("dy"),n),u=parseFloat(this.element.getAttribute("textLength")||"0"),h=t.attributeState.visibility,0===this.element.childElementCount)f=this.element.textContent||"",c=function(t,e){return t=wt(t=St(t)),"default"===e.xmlSpace&&(t=kt(t=t.trim())),t}(f,t.attributeState),d=Mt(this.element,c,t),e=t.textMeasure.getTextOffset(d,t.attributeState),u>0&&(m=t.textMeasure.measureTextWidth(d,t.attributeState),"default"===t.attributeState.xmlSpace&&f.match(/^\s/)&&(i=0),r=(u-m)/(d.length-i)||0),"visible"===h&&(g=t.attributeState.alignmentBaseline,y=xt(t.attributeState),t.pdf.text(d,a+o-e,s+l,{baseline:V(g),angle:t.transform,renderingMode:"fill"===y?void 0:y,charSpace:0===r?void 0:r}));else{for(b=[],v=new Tt(this,t.attributeState.textAnchor,a+o,s+l),b.push({type:"",chunk:v}),x=this.processTSpans(this,this.element,t,b,v,{prevText:" ",prevContext:t}),i=x?0:1,S=!0,w=b.length-1;w>=0;w--)S&&(S=b[w].chunk.rightTrimText());u>0&&(k=0,M=0,b.forEach((function(e){var r=e.chunk;r.measureText(t),r.textMeasures.forEach((function(t){var e=t.width,r=t.length;k+=e,M+=r}))})),r=(u-k)/(M-i)),b.reduce((function(e,i){var n=i.type,a=i.chunk;return"x"===n?a.setX(e[0]):"y"===n&&a.setY(e[1]),a.put(t,r)}),[0,0])}return t.pdf.restoreGraphicsState(),[2]}))}))},e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.getBoundingBoxCore=function(t){return q(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(ht),Ft=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return h(e,t),e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e.prototype.getPath=function(t){var e,r,i=new o(N(this.element,t.styleSheets,"d")||"").unshort().unarc().abs(),n=new T;return i.iterate((function(t){switch(t[0]){case"M":n.moveTo(t[1],t[2]);break;case"L":n.lineTo(t[1],t[2]);break;case"H":n.lineTo(t[1],r);break;case"V":n.lineTo(e,t[1]);break;case"C":n.curveTo(t[1],t[2],t[3],t[4],t[5],t[6]);break;case"Q":var i=x([e,r],[t[1],t[2]]),a=x([t[3],t[4]],[t[1],t[2]]);n.curveTo(i[0],i[1],a[0],a[1],t[3],t[4]);break;case"Z":n.close()}switch(t[0]){case"M":case"L":e=t[1],r=t[2];break;case"H":e=t[1];break;case"V":r=t[1];break;case"C":e=t[5],r=t[6];break;case"Q":e=t[3],r=t[4]}})),n},e}(ft),At=/^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,((?:.|\s)*)$/i,Pt=function(t){function e(r,i){var n=t.call(this,r,i)||this;return n.imageLoadingPromise=null,n.imageUrl=n.element.getAttribute("xlink:href")||n.element.getAttribute("href"),n.imageUrl&&(n.imageLoadingPromise=e.fetchImageData(n.imageUrl)),n}return h(e,t),e.prototype.renderCore=function(t){return c(this,void 0,void 0,(function(){var e,r,i,n,a,s,o,l,u,h,f,c;return p(this,(function(p){switch(p.label){case 0:return this.imageLoadingPromise?(t.pdf.setCurrentTransformationMatrix(t.transform),e=parseFloat(N(this.element,t.styleSheets,"width")||"0"),r=parseFloat(N(this.element,t.styleSheets,"height")||"0"),i=parseFloat(N(this.element,t.styleSheets,"x")||"0"),n=parseFloat(N(this.element,t.styleSheets,"y")||"0"),!isFinite(e)||e<=0||!isFinite(r)||r<=0?[2]:[4,this.imageLoadingPromise]):[2];case 1:return a=p.sent(),s=a.data,0!==(o=a.format).indexOf("svg")?[3,3]:(l=new DOMParser,u=l.parseFromString(s,"image/svg+xml").firstElementChild,(!(h=this.element.getAttribute("preserveAspectRatio"))||h.indexOf("defer")<0||!u.getAttribute("preserveAspectRatio"))&&u.setAttribute("preserveAspectRatio",h||""),u.setAttribute("x",String(i)),u.setAttribute("y",String(n)),u.setAttribute("width",String(e)),u.setAttribute("height",String(r)),[4,Wt(u,f={}).render(new y(t.pdf,{refsHandler:new b(f),styleSheets:t.styleSheets,viewport:new mt(e,r),svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]);case 2:return p.sent(),[2];case 3:c="data:image/"+o+";base64,"+btoa(s);try{t.pdf.addImage(c,"",i,n,e,r)}catch(t){"object"==typeof console&&console.warn&&console.warn("Could not load image "+this.imageUrl+". \n"+t)}p.label=4;case 4:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return q(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e.fetchImageData=function(t){return c(this,void 0,void 0,(function(){var r,i,n,a,s;return p(this,(function(o){switch(o.label){case 0:if(!(n=t.match(At)))return[3,1];if(a=n[2],"image"!==(s=a.split("/"))[0])throw new Error("Unsupported image URL: "+t);return i=s[1],r=n[5],"base64"===n[4]?(r=r.replace(/\s/g,""),r=atob(r)):r=decodeURIComponent(r),[3,3];case 1:return[4,e.fetchImage(t)];case 2:r=o.sent(),i=t.substring(t.lastIndexOf(".")+1),o.label=3;case 3:return[2,{data:r,format:i}]}}))}))},e.fetchImage=function(t){return new Promise((function(e,r){var i=new XMLHttpRequest;i.open("GET",t,!0),i.responseType="arraybuffer",i.onload=function(){if(200!==i.status)throw new Error("Error "+i.status+": Failed to load image '"+t+"'");for(var r=new Uint8Array(i.response),n="",a=0;a<r.length;a++)n+=String.fromCharCode(r[a]);e(n)},i.onerror=r,i.onabort=r,i.send(null)}))},e.getMimeType=function(t){switch(t=t.toLowerCase()){case"jpg":case"jpeg":return"image/jpeg";default:return"image/"+t}},e}(ht),Bt=function(t){function e(e,r,i){var n=t.call(this,!0,r,i)||this;return n.closed=e,n}return h(e,t),e.prototype.getPath=function(t){if(!this.element.hasAttribute("points")||""===this.element.getAttribute("points"))return null;var r=e.parsePointsString(this.element.getAttribute("points")),i=new T;if(r.length<1)return i;i.moveTo(r[0][0],r[0][1]);for(var n=1;n<r.length;n++)i.lineTo(r[n][0],r[n][1]);return this.closed&&i.close(),i},e.prototype.isVisible=function(t,e){return O(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.parsePointsString=function(t){for(var e=W(t),r=[],i=0;i<e.length-1;i+=2){var n=e[i],a=e[i+1];r.push([n,a])}return r},e}(ft),Nt=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return h(e,t),e}(Bt),Ot=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.render=function(t){return Promise.resolve()},e.prototype.getBoundingBoxCore=function(t){return[0,0,0,0]},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return O(this,t,e)},e}(Q),Lt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.apply=function(t){return c(this,void 0,void 0,(function(){var e,r,n,a,s;return p(this,(function(o){switch(o.label){case 0:e=this.computeNodeTransform(t),r=this.getBoundingBox(t),t.pdf.beginFormObject(r[0],r[1],r[2],r[3],e),function(t){var e=t.attributeState,r=t.pdf,n=1,a=1;n*=e.fillOpacity,n*=e.opacity,e.fill instanceof m&&void 0!==e.fill.color.a&&(n*=e.fill.color.a),a*=e.strokeOpacity,a*=e.opacity,e.stroke instanceof m&&void 0!==e.stroke.color.a&&(a*=e.stroke.color.a);var s,o={};if(o.opacity=n,o["stroke-opacity"]=a,r.setGState(new i(o)),e.fill&&e.fill instanceof m&&e.fill.color.ok?r.setFillColor(e.fill.color.r,e.fill.color.g,e.fill.color.b):r.setFillColor(0,0,0),r.setLineWidth(e.strokeWidth),e.stroke instanceof m?r.setDrawColor(e.stroke.color.r,e.stroke.color.g,e.stroke.color.b):r.setDrawColor(0,0,0),r.setLineCap(e.strokeLinecap),r.setLineJoin(e.strokeLinejoin),e.strokeDasharray?r.setLineDashPattern(e.strokeDasharray,e.strokeDashoffset):r.setLineDashPattern([],0),r.setLineMiterLimit(e.strokeMiterlimit),s=G.hasOwnProperty(e.fontFamily)?G[e.fontFamily]:e.fontFamily,e.fill&&e.fill instanceof m&&e.fill.color.ok){var l=e.fill.color;r.setTextColor(l.r,l.g,l.b)}else r.setTextColor(0,0,0);var u="";"bold"===e.fontWeight&&(u="bold"),"italic"===e.fontStyle&&(u+="italic"),""===u&&(u="normal"),void 0!==s||void 0!==u?(void 0===s&&(s=G.hasOwnProperty(e.fontFamily)?G[e.fontFamily]:e.fontFamily),r.setFont(s,u)):r.setFont("helvetica",u),r.setFontSize(e.fontSize*r.internal.scaleFactor)}(n=new y(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure})),a=0,s=this.children,o.label=1;case 1:return a<s.length?[4,s[a].render(n)]:[3,4];case 2:o.sent(),o.label=3;case 3:return a++,[3,1];case 4:return t.pdf.endFormObject(this.element.getAttribute("id")),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){var e,r=this.element.getAttribute("viewBox");return r&&(e=W(r)),[e&&e[0]||0,e&&e[1]||0,e&&e[2]||parseFloat(this.element.getAttribute("markerWidth")||"3"),e&&e[3]||parseFloat(this.element.getAttribute("markerHeight")||"3")]},e.prototype.computeNodeTransformCore=function(t){var e,r=parseFloat(this.element.getAttribute("refX")||"0"),i=parseFloat(this.element.getAttribute("refY")||"0"),n=this.element.getAttribute("viewBox");if(n){var a=W(n);e=_(this.element,a,0,0,parseFloat(this.element.getAttribute("markerWidth")||"3"),parseFloat(this.element.getAttribute("markerHeight")||"3"),t,!0),e=t.pdf.matrixMult(t.pdf.Matrix(1,0,0,1,-r,-i),e)}else e=t.pdf.Matrix(1,0,0,1,-r,-i);return e},e.prototype.isVisible=function(t,e){return L(this,t,e)},e}(K),Et=function(t){function e(e,r){return t.call(this,e,r)||this}return h(e,t),e.prototype.getR=function(t){var e;return null!==(e=this.r)&&void 0!==e?e:this.r=parseFloat(N(this.element,t.styleSheets,"r")||"0")},e.prototype.getRx=function(t){return this.getR(t)},e.prototype.getRy=function(t){return this.getR(t)},e}(bt),It=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return h(e,t),e}(Bt),Dt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.renderCore=function(t){return c(this,void 0,void 0,(function(){var e,r;return p(this,(function(i){switch(i.label){case 0:e=0,r=this.children,i.label=1;case 1:return e<r.length?[4,r[e].render(t)]:[3,4];case 2:i.sent(),i.label=3;case 3:return e++,[3,1];case 4:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return X(t,this)},e}(ut),Ht=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.render=function(e){return c(this,void 0,void 0,(function(){var r,i,n,a,s;return p(this,(function(o){switch(o.label){case 0:return this.isVisible("hidden"!==e.attributeState.visibility,e)?(r=this.getX(e),i=this.getY(e),n=this.getWidth(e),a=this.getHeight(e),e.pdf.saveGraphicsState(),s=e.transform,this.element.hasAttribute("transform")&&(s=e.pdf.matrixMult($(this.element.getAttribute("transform"),e),s)),e.pdf.setCurrentTransformationMatrix(s),e.withinUse||"visible"===N(this.element,e.styleSheets,"overflow")||e.pdf.rect(r,i,n,a).clip().discardPath(),[4,t.prototype.render.call(this,e.clone({transform:e.pdf.unitMatrix,viewport:e.withinUse?e.viewport:new mt(n,a)}))]):[2];case 1:return o.sent(),e.pdf.restoreGraphicsState(),[2]}}))}))},e.prototype.computeNodeTransform=function(t){return this.computeNodeTransformCore(t)},e.prototype.computeNodeTransformCore=function(t){if(t.withinUse)return t.pdf.unitMatrix;var e,r=this.getX(t),i=this.getY(t),n=this.getViewBox();if(n){var a=this.getWidth(t),s=this.getHeight(t);e=_(this.element,n,r,i,a,s,t)}else e=t.pdf.Matrix(1,0,0,1,r,i);return e},e.prototype.getWidth=function(t){if(void 0!==this.width)return this.width;var e,r,i=t.svg2pdfParameters;if(this.isOutermostSvg(t))if(null!=i.width)e=i.width;else if(r=N(this.element,t.styleSheets,"width"))e=parseFloat(r);else{var n=this.getViewBox();if(n&&(null!=i.height||N(this.element,t.styleSheets,"height"))){var a=n[2]/n[3];e=this.getHeight(t)*a}else e=Math.min(300,t.viewport.width,2*t.viewport.height)}else e=(r=N(this.element,t.styleSheets,"width"))?parseFloat(r):t.viewport.width;return this.width=e},e.prototype.getHeight=function(t){if(void 0!==this.height)return this.height;var e,r,i=t.svg2pdfParameters;if(this.isOutermostSvg(t))if(null!=i.height)e=i.height;else if(r=N(this.element,t.styleSheets,"height"))e=parseFloat(r);else{var n=this.getViewBox();if(n){var a=n[2]/n[3];e=this.getWidth(t)/a}else e=Math.min(150,t.viewport.width/2,t.viewport.height)}else e=(r=N(this.element,t.styleSheets,"height"))?parseFloat(r):t.viewport.height;return this.height=e},e.prototype.getX=function(t){if(void 0!==this.x)return this.x;if(this.isOutermostSvg(t))return this.x=0;var e=N(this.element,t.styleSheets,"x");return this.x=e?parseFloat(e):0},e.prototype.getY=function(t){if(void 0!==this.y)return this.y;if(this.isOutermostSvg(t))return this.y=0;var e=N(this.element,t.styleSheets,"y");return this.y=e?parseFloat(e):0},e.prototype.getViewBox=function(){if(void 0!==this.viewBox)return this.viewBox;var t=this.element.getAttribute("viewBox");return this.viewBox=t?W(t):void 0},e.prototype.isOutermostSvg=function(t){return t.svg2pdfParameters.element===this.element},e}(Dt),Rt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(Dt),Vt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return h(e,t),e.prototype.apply=function(t){return c(this,void 0,void 0,(function(){var e,r,i;return p(this,(function(n){switch(n.label){case 0:if(!this.isVisible(!0,t))return[2];e=t.pdf.matrixMult(this.computeNodeTransform(t),t.transform),t.pdf.setCurrentTransformationMatrix(e),r=0,i=this.children,n.label=1;case 1:return r<i.length?[4,i[r].render(new y(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,withinClipPath:!0,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]:[3,4];case 2:n.sent(),n.label=3;case 3:return r++,[3,1];case 4:return t.pdf.clip().discardPath(),t.pdf.setCurrentTransformationMatrix(e.inversed()),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return X(t,this)},e.prototype.isVisible=function(t,e){return L(this,t,e)},e}(K);function Wt(e,r){var i,n=[];switch(function(t,e){for(var r=[],i=0;i<t.childNodes.length;i++){var n=t.childNodes[i];"#"!==n.nodeName.charAt(0)&&r.push(n)}for(i=0;i<r.length;i++)e(i,r[i])}(e,(function(t,e){return n.push(Wt(e,r))})),e.tagName.toLowerCase()){case"a":case"g":i=new Rt(e,n);break;case"circle":i=new Et(e,n);break;case"clippath":i=new Vt(e,n);break;case"ellipse":i=new vt(e,n);break;case"lineargradient":i=new Z(e,n);break;case"image":i=new Pt(e,n);break;case"line":i=new pt(e,n);break;case"marker":i=new Lt(e,n);break;case"path":i=new Ft(e,n);break;case"pattern":i=new rt(e,n);break;case"polygon":i=new Nt(e,n);break;case"polyline":i=new It(e,n);break;case"radialgradient":i=new tt(e,n);break;case"rect":i=new yt(e,n);break;case"svg":i=new Ht(e,n);break;case"symbol":i=new dt(e,n);break;case"text":i=new Ct(e,n);break;case"use":i=new gt(e,n);break;default:i=new Ot(e,n)}if(null!=r&&i.element.hasAttribute("id")){var a=t(i.element.id,{isIdentifier:!0});r[a]=r[a]||i}return i.children.forEach((function(t){return t.setParent(i)})),i}var jt=function(){function t(t,e){this.rootSvg=t,this.loadExternalSheets=e,this.styleSheets=[]}return t.prototype.load=function(){return c(this,void 0,void 0,(function(){var t;return p(this,(function(e){switch(e.label){case 0:return[4,this.collectStyleSheetTexts()];case 1:return t=e.sent(),this.parseCssSheets(t),[2]}}))}))},t.prototype.collectStyleSheetTexts=function(){return c(this,void 0,void 0,(function(){var e,r,i,n,a;return p(this,(function(s){switch(s.label){case 0:if(e=[],this.loadExternalSheets&&this.rootSvg.ownerDocument)for(n=0;n<this.rootSvg.ownerDocument.childNodes.length;n++)"xml-stylesheet"===(r=this.rootSvg.ownerDocument.childNodes[n]).nodeName&&"string"==typeof r.data&&e.push(t.loadSheet(r.data.match(/href=["'].*?["']/)[0].split("=")[1].slice(1,-1)));for(i=this.rootSvg.querySelectorAll("style,link"),n=0;n<i.length;n++)B(a=i[n],"style")?e.push(a.textContent):this.loadExternalSheets&&B(a,"link")&&"stylesheet"===a.getAttribute("rel")&&a.hasAttribute("href")&&e.push(t.loadSheet(a.getAttribute("href")));return[4,Promise.all(e)];case 1:return[2,s.sent().filter((function(t){return null!==t}))]}}))}))},t.prototype.parseCssSheets=function(e){for(var r=document.implementation.createHTMLDocument(""),i=0,n=e;i<n.length;i++){var a=n[i],s=r.createElement("style");s.textContent=a,r.body.appendChild(s);var o=s.sheet;if(o instanceof CSSStyleSheet){for(var l=o.cssRules.length-1;l>=0;l--){var u=o.cssRules[l];if(u instanceof CSSStyleRule){var h=u;if(h.selectorText.indexOf(",")>=0){o.deleteRule(l);for(var f=h.cssText.substring(h.selectorText.length),c=t.splitSelectorAtCommas(h.selectorText),p=0;p<c.length;p++)o.insertRule(c[p]+f,l+p)}}else o.deleteRule(l)}this.styleSheets.push(o)}}},t.splitSelectorAtCommas=function(t){for(var e,r=/,|["']/g,i=/[^\\]["]/g,n=/[^\\][']/g,a=[],s="initial",o=-1,l=i,u=0;u<t.length;)switch(s){case"initial":r.lastIndex=u,(e=r.exec(t))?(","===e[0]?(a.push(t.substring(o+1,r.lastIndex-1).trim()),o=r.lastIndex-1):(s="withinQuotes",l='"'===e[0]?i:n),u=r.lastIndex):(a.push(t.substring(o+1).trim()),u=t.length);break;case"withinQuotes":l.lastIndex=u,(e=l.exec(t))&&(u=l.lastIndex,s="initial")}return a},t.loadSheet=function(t){return new Promise((function(e,r){var i=new XMLHttpRequest;i.open("GET",t,!0),i.responseType="text",i.onload=function(){200!==i.status&&r(new Error("Error "+i.status+": Failed to load '"+t+"'")),e(i.responseText)},i.onerror=r,i.onabort=r,i.send(null)})).catch((function(){return null}))},t.prototype.getPropertyValue=function(t,e){for(var r=[],i=0,n=this.styleSheets;i<n.length;i++)for(var a=n[i],s=0;s<a.cssRules.length;s++){var o=a.cssRules[s];o.style.getPropertyValue(e)&&t.matches(o.selectorText)&&r.push(o)}if(0!==r.length){return r.reduce((function(t,r){return 1===(n=r,(a=(i=t).style.getPropertyPriority(e))!==n.style.getPropertyPriority(e)?"important"===a?1:-1:l(i.selectorText,n.selectorText))?t:r;var i,n,a})).style.getPropertyValue(e)||void 0}},t}(),Gt=function(){function t(){this.measureMethods={}}return t.prototype.getTextOffset=function(t,e){var r=e.textAnchor;if("start"===r)return 0;var i=this.measureTextWidth(t,e),n=0;switch(r){case"end":n=i;break;case"middle":n=i/2}return n},t.prototype.measureTextWidth=function(t,e){if(0===t.length)return 0;var r=e.fontFamily;return this.getMeasureFunction(r).call(this,t,e.fontFamily,e.fontSize+"px",e.fontStyle,e.fontWeight)},t.prototype.getMeasurementTextNode=function(){if(!this.textMeasuringTextElement){this.textMeasuringTextElement=document.createElementNS("http://www.w3.org/2000/svg","text");var t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.appendChild(this.textMeasuringTextElement),t.style.setProperty("position","absolute"),t.style.setProperty("visibility","hidden"),document.body.appendChild(t)}return this.textMeasuringTextElement},t.prototype.canvasTextMeasure=function(t,e,r,i,n){var a=document.createElement("canvas").getContext("2d");return null!=a?(a.font=[i,n,r,e].join(" "),a.measureText(t).width):0},t.prototype.svgTextMeasure=function(t,e,r,i,n,a){void 0===a&&(a=this.getMeasurementTextNode());var s=a;return s.setAttribute("font-family",e),s.setAttribute("font-size",r),s.setAttribute("font-style",i),s.setAttribute("font-weight",n),s.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),s.textContent=t,s.getBBox().width},t.prototype.getMeasureFunction=function(e){var r=this.measureMethods[e];if(!r){var i=this.canvasTextMeasure(t.testString,e,"16px","normal","normal"),n=this.svgTextMeasure(t.testString,e,"16px","normal","normal");r=Math.abs(i-n)<t.epsilon?this.canvasTextMeasure:this.svgTextMeasure,this.measureMethods[e]=r}return r},t.prototype.cleanupTextMeasuring=function(){if(this.textMeasuringTextElement){var t=this.textMeasuringTextElement.parentNode;t&&document.body.removeChild(t),this.textMeasuringTextElement=void 0}},t.testString="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!\"$%&/()=?'\\+*-_.:,;^}][{#~|<>",t.epsilon=.1,t}();function Ut(t,e,r){var i,n,a;return void 0===r&&(r={}),c(this,void 0,void 0,(function(){var s,o,l,u,h,c,d,m,g,v,x;return p(this,(function(p){switch(p.label){case 0:return s=null!==(i=r.x)&&void 0!==i?i:0,o=null!==(n=r.y)&&void 0!==n?n:0,l=null!==(a=r.loadExternalStyleSheets)&&void 0!==a&&a,h=new b(u={}),[4,(c=new jt(t,l)).load()];case 1:return p.sent(),d=new mt(e.internal.pageSize.getWidth(),e.internal.pageSize.getHeight()),m=f(f({},r),{element:t}),g=new Gt,v=new y(e,{refsHandler:h,styleSheets:c,viewport:d,svg2pdfParameters:m,textMeasure:g}),e.advancedAPI(),e.saveGraphicsState(),e.setCurrentTransformationMatrix(e.Matrix(1,0,0,1,s,o)),e.setLineWidth(v.attributeState.strokeWidth),x=v.attributeState.fill.color,e.setFillColor(x.r,x.g,x.b),e.setFont(v.attributeState.fontFamily),e.setFontSize(v.attributeState.fontSize*e.internal.scaleFactor),[4,Wt(t,u).render(v)];case 2:return p.sent(),e.restoreGraphicsState(),e.compatAPI(),v.textMeasure.cleanupTextMeasuring(),[2,e]}}))}))}s.API.svg=function(t,e){return void 0===e&&(e={}),Ut(t,this,e)};export{Ut as svg2pdf}; - diff --git a/scripts/svg2pdf.umd.min.js b/scripts/svg2pdf.umd.min.js deleted file mode 100644 index d235cd1d0..000000000 --- a/scripts/svg2pdf.umd.min.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2015-2023 yWorks GmbH - * Copyright (c) 2013-2015 by Vitaly Puzrin - * - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jspdf")):"function"==typeof define&&define.amd?define(["exports","jspdf"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).svg2pdf={},t.jspdf)}(this,(function(t,e){"use strict";var r="default"in e?e.default:e,i=function(t,e){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])})(t,e)};function n(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}i(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}var a=function(){return(a=Object.assign||function(t){for(var e,r=1,i=arguments.length;r<i;r++)for(var n in e=arguments[r])Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}).apply(this,arguments)};function s(t,e,r,i){return new(r||(r=Promise))((function(n,a){function s(t){try{l(i.next(t))}catch(t){a(t)}}function o(t){try{l(i.throw(t))}catch(t){a(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,o)}l((i=i.apply(t,e||[])).next())}))}function o(t,e){var r,i,n,a,s={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return a={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function o(a){return function(o){return function(a){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,i&&(n=2&a[0]?i.return:a[0]?i.throw||((n=i.return)&&n.call(i),0):i.next)&&!(n=n.call(i,a[1])).done)return n;switch(i=0,n&&(a=[2&a[0],n.value]),a[0]){case 0:case 1:n=a;break;case 4:return s.label++,{value:a[1],done:!1};case 5:s.label++,i=a[1],a=[0];continue;case 7:a=s.ops.pop(),s.trys.pop();continue;default:if(!(n=s.trys,(n=n.length>0&&n[n.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!n||a[1]>n[0]&&a[1]<n[3])){s.label=a[1];break}if(6===a[0]&&s.label<n[1]){s.label=n[1],n=a;break}if(n&&s.label<n[2]){s.label=n[2],s.ops.push(a);break}n[2]&&s.ops.pop(),s.trys.pop();continue}a=e.call(t,s)}catch(t){a=[6,t],i=0}finally{r=n=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,o])}}}var l=function(){function t(t){if(this.a=void 0,this.r=0,this.g=0,this.b=0,this.simpleColors={},this.colorDefs=[],this.ok=!1,t){for(var e in"#"==t.charAt(0)&&(t=t.substr(1,6)),t=(t=t.replace(/ /g,"")).toLowerCase(),this.simpleColors={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgrey:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",grey:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightslategrey:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"},this.simpleColors)t==e&&(t=this.simpleColors[e]);this.colorDefs=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}];for(var r=0;r<this.colorDefs.length;r++){var i=this.colorDefs[r].re,n=this.colorDefs[r].process,a=i.exec(t);if(a){var s=n(a);this.r=s[0],this.g=s[1],this.b=s[2],this.ok=!0}}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b}}return t.prototype.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},t.prototype.toRGBA=function(){return"rgba("+this.r+", "+this.g+", "+this.b+", "+(this.a||"1")+")"},t.prototype.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r},t.prototype.getHelpXML=function(){for(var e=[],r=0;r<this.colorDefs.length;r++)for(var i=this.colorDefs[r].example,n=0;n<i.length;n++)e[e.length]=i[n];for(var a in this.simpleColors)e[e.length]=a;var s=document.createElement("ul");s.setAttribute("id","rgbcolor-examples");for(r=0;r<e.length;r++)try{var o=document.createElement("li"),l=new t(e[r]),u=document.createElement("div");u.style.cssText="margin: 3px; border: 1px solid black; background:"+l.toHex()+"; color:"+l.toHex(),u.appendChild(document.createTextNode("test"));var h=document.createTextNode(" "+e[r]+" -> "+l.toRGB()+" -> "+l.toHex());o.appendChild(u),o.appendChild(h),s.appendChild(o)}catch(t){}return s},t}(),u=function(){function t(t){this.color=t}return t.prototype.getFillData=function(t,e){return s(this,void 0,void 0,(function(){return o(this,(function(t){return[2,void 0]}))}))},t}(),h=function(){function t(){this.xmlSpace="",this.fill=null,this.fillOpacity=1,this.fontFamily="",this.fontSize=16,this.fontStyle="",this.fontWeight="",this.opacity=1,this.stroke=null,this.strokeDasharray=null,this.strokeDashoffset=0,this.strokeLinecap="",this.strokeLinejoin="",this.strokeMiterlimit=4,this.strokeOpacity=1,this.strokeWidth=1,this.alignmentBaseline="",this.textAnchor="",this.visibility="",this.color=null}return t.prototype.clone=function(){var e=new t;return e.xmlSpace=this.xmlSpace,e.fill=this.fill,e.fillOpacity=this.fillOpacity,e.fontFamily=this.fontFamily,e.fontSize=this.fontSize,e.fontStyle=this.fontStyle,e.fontWeight=this.fontWeight,e.opacity=this.opacity,e.stroke=this.stroke,e.strokeDasharray=this.strokeDasharray,e.strokeDashoffset=this.strokeDashoffset,e.strokeLinecap=this.strokeLinecap,e.strokeLinejoin=this.strokeLinejoin,e.strokeMiterlimit=this.strokeMiterlimit,e.strokeOpacity=this.strokeOpacity,e.strokeWidth=this.strokeWidth,e.textAnchor=this.textAnchor,e.alignmentBaseline=this.alignmentBaseline,e.visibility=this.visibility,e.color=this.color,e},t.default=function(){var e=new t;return e.xmlSpace="default",e.fill=new u(new l("rgb(0, 0, 0)")),e.fillOpacity=1,e.fontFamily="times",e.fontSize=16,e.fontStyle="normal",e.fontWeight="normal",e.opacity=1,e.stroke=null,e.strokeDasharray=null,e.strokeDashoffset=0,e.strokeLinecap="butt",e.strokeLinejoin="miter",e.strokeMiterlimit=4,e.strokeOpacity=1,e.strokeWidth=1,e.alignmentBaseline="baseline",e.textAnchor="start",e.visibility="visible",e.color=new l("rgb(0, 0, 0)"),e},t}(),f=function(){function t(t,e){var r,i,n;this.pdf=t,this.svg2pdfParameters=e.svg2pdfParameters,this.attributeState=e.attributeState?e.attributeState.clone():h.default(),this.viewport=e.viewport,this.refsHandler=e.refsHandler,this.styleSheets=e.styleSheets,this.textMeasure=e.textMeasure,this.transform=null!==(r=e.transform)&&void 0!==r?r:this.pdf.unitMatrix,this.withinClipPath=null!==(i=e.withinClipPath)&&void 0!==i&&i,this.withinUse=null!==(n=e.withinUse)&&void 0!==n&&n}return t.prototype.clone=function(e){var r,i,n,a;return void 0===e&&(e={}),new t(this.pdf,{svg2pdfParameters:this.svg2pdfParameters,attributeState:e.attributeState?e.attributeState.clone():this.attributeState.clone(),viewport:null!==(r=e.viewport)&&void 0!==r?r:this.viewport,refsHandler:this.refsHandler,styleSheets:this.styleSheets,textMeasure:this.textMeasure,transform:null!==(i=e.transform)&&void 0!==i?i:this.transform,withinClipPath:null!==(n=e.withinClipPath)&&void 0!==n?n:this.withinClipPath,withinUse:null!==(a=e.withinUse)&&void 0!==a?a:this.withinUse})},t}(),c={}.hasOwnProperty,p=/[ -,\.\/:-@\[-\^`\{-~]/,d=/[ -,\.\/:-@\[\]\^`\{-~]/,g=/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g,m=function t(e,r){"single"!=(r=function(t,e){if(!t)return e;var r={};for(var i in e)r[i]=c.call(t,i)?t[i]:e[i];return r}(r,t.options)).quotes&&"double"!=r.quotes&&(r.quotes="single");for(var i="double"==r.quotes?'"':"'",n=r.isIdentifier,a=e.charAt(0),s="",o=0,l=e.length;o<l;){var u=e.charAt(o++),h=u.charCodeAt(),f=void 0;if(h<32||h>126){if(h>=55296&&h<=56319&&o<l){var m=e.charCodeAt(o++);56320==(64512&m)?h=((1023&h)<<10)+(1023&m)+65536:o--}f="\\"+h.toString(16).toUpperCase()+" "}else f=r.escapeEverything?p.test(u)?"\\"+u:"\\"+h.toString(16).toUpperCase()+" ":/[\t\n\f\r\x0B]/.test(u)?"\\"+h.toString(16).toUpperCase()+" ":"\\"==u||!n&&('"'==u&&i==u||"'"==u&&i==u)||n&&d.test(u)?"\\"+u:u;s+=f}return n&&(/^-[-\d]/.test(s)?s="\\-"+s.slice(1):/\d/.test(a)&&(s="\\3"+a+" "+s.slice(1))),s=s.replace(g,(function(t,e,r){return e&&e.length%2?t:(e||"")+r})),!n&&r.wrap?i+s+i:s};m.options={escapeEverything:!1,isIdentifier:!1,quotes:"single",wrap:!1},m.version="3.0.0";var y=m,v=function(){function t(e){this.renderedElements={},this.idMap=e,this.idPrefix=String(t.instanceCounter++)}return t.prototype.getRendered=function(t,e,r){return s(this,void 0,void 0,(function(){var i,n;return o(this,(function(a){switch(a.label){case 0:return i=this.generateKey(t,e),this.renderedElements.hasOwnProperty(i)?[2,this.renderedElements[t]]:(n=this.get(t),this.renderedElements[i]=n,[4,r(n)]);case 1:return a.sent(),[2,n]}}))}))},t.prototype.get=function(t){return this.idMap[y(t,{isIdentifier:!0})]},t.prototype.generateKey=function(t,e){return this.idPrefix+"|"+t+"|"+(e||new l("rgb(0,0,0)")).toRGBA()},t.instanceCounter=0,t}();function b(t,e){return Math.atan2(e[1]-t[1],e[0]-t[0])}function x(t,e){return[2/3*(e[0]-t[0])+t[0],2/3*(e[1]-t[1])+t[1]]}function S(t){var e=Math.sqrt(t[0]*t[0]+t[1]*t[1]);return[t[0]/e,t[1]/e]}function w(t,e){return S([e[0]-t[0],e[1]-t[1]])}function k(t,e){return[t[0]+e[0],t[1]+e[1]]}function M(t,e){var r=t[0],i=t[1];return[e.a*r+e.c*i+e.e,e.b*r+e.d*i+e.f]}var C=function(){function t(){this.segments=[]}return t.prototype.moveTo=function(t,e){return this.segments.push(new A(t,e)),this},t.prototype.lineTo=function(t,e){return this.segments.push(new F(t,e)),this},t.prototype.curveTo=function(t,e,r,i,n,a){return this.segments.push(new T(t,e,r,i,n,a)),this},t.prototype.close=function(){return this.segments.push(new P),this},t.prototype.transform=function(t){this.segments.forEach((function(e){if(e instanceof A||e instanceof F||e instanceof T){var r=M([e.x,e.y],t);e.x=r[0],e.y=r[1]}if(e instanceof T){var i=M([e.x1,e.y1],t),n=M([e.x2,e.y2],t);e.x1=i[0],e.y1=i[1],e.x2=n[0],e.y2=n[1]}}))},t.prototype.draw=function(t){var e=t.pdf;this.segments.forEach((function(t){t instanceof A?e.moveTo(t.x,t.y):t instanceof F?e.lineTo(t.x,t.y):t instanceof T?e.curveTo(t.x1,t.y1,t.x2,t.y2,t.x,t.y):e.close()}))},t}(),A=function(t,e){this.x=t,this.y=e},F=function(t,e){this.x=t,this.y=e},T=function(t,e,r,i,n,a){this.x1=t,this.y1=e,this.x2=r,this.y2=i,this.x=n,this.y=a},P=function(){};function B(t,e){return e.split(",").indexOf((t.nodeName||t.tagName).toLowerCase())>=0}function N(t,e,r,i){var n;void 0===i&&(i=r);var a=null===(n=t.style)||void 0===n?void 0:n.getPropertyValue(i);if(a)return a;var s=e.getPropertyValue(t,i);return s||(t.hasAttribute(r)&&t.getAttribute(r)||void 0)}function E(t,e,r){if("none"===N(t.element,r.styleSheets,"display"))return!1;var i=e,n=N(t.element,r.styleSheets,"visibility");return n&&(i="hidden"!==n),i}function L(t,e,r){var i=E(t,e,r);return 0!==t.element.childNodes.length&&(t.children.forEach((function(t){t.isVisible(i,r)&&(i=!0)})),i)}var O=function(){function t(){this.markers=[]}return t.prototype.addMarker=function(t){this.markers.push(t)},t.prototype.draw=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n,a,s,l;return o(this,(function(o){switch(o.label){case 0:e=0,o.label=1;case 1:return e<this.markers.length?(r=this.markers[e],i=void 0,n=r.angle,a=r.anchor,s=Math.cos(n),l=Math.sin(n),i=t.pdf.Matrix(s,l,-l,s,a[0],a[1]),i=t.pdf.matrixMult(t.pdf.Matrix(t.attributeState.strokeWidth,0,0,t.attributeState.strokeWidth,0,0),i),i=t.pdf.matrixMult(i,t.transform),t.pdf.saveGraphicsState(),[4,t.refsHandler.getRendered(r.id,null,(function(e){return e.apply(t)}))]):[3,4];case 2:o.sent(),t.pdf.doFormObject(r.id,i),t.pdf.restoreGraphicsState(),o.label=3;case 3:return e++,[3,1];case 4:return[2]}}))}))},t}(),I=function(t,e,r){this.id=t,this.anchor=e,this.angle=r},_=/url\(["']?#([^"']+)["']?\)/,H={bottom:"bottom","text-bottom":"bottom",top:"top","text-top":"top",hanging:"hanging",middle:"middle",central:"middle",center:"middle",mathematical:"middle",ideographic:"ideographic",alphabetic:"alphabetic",baseline:"alphabetic"};function D(t,e){var r;return(r=t&&t.toString().match(/^([\-0-9.]+)em$/))?parseFloat(r[1])*e:(r=t&&t.toString().match(/^([\-0-9.]+)(px|)$/))?parseFloat(r[1]):0}function q(t){return H[t]||"alphabetic"}function V(t){for(var e,r=[],i=/[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.?\d+))(?:[eE][+-]?\d+)?/g;e=i.exec(t);)r.push(parseFloat(e[0]));return r}function R(t,e){if("transparent"===t){var r=new l("rgb(0,0,0)");return r.a=0,r}if("currentcolor"===t.toLowerCase())return e||new l("rgb(0,0,0)");var i=/\s*rgba\(((?:[^,\)]*,){3}[^,\)]*)\)\s*/.exec(t);if(i){var n=V(i[1]),a=new l("rgb("+n.slice(0,3).join(",")+")");return a.a=n[3],a}return new l(t)}var j=/[a-z0-9_-]/i,W=/[\s\t]/,G=function(t){for(var e,r,i=!0,n=0,a="",s=0,o=[];;){if(r=t[s],0===n){if(!r&&i)break;if(!r&&!i)throw new Error("Parse error");if('"'===r||"'"===r)e=r,n=1,i=!1;else if(W.test(r));else{if(!j.test(r))throw new Error("Parse error");n=3,i=!1,s--}}else if(1===n){if(!r)throw new Error("Parse Error");"\\"===r?n=2:r===e?(o.push(a),a="",n=4):a+=r}else if(2===n){if(r!==e&&"\\"!==r)throw new Error("Parse error");a+=r,n=1}else if(3===n){if(!r){o.push(a);break}if(j.test(r))a+=r;else if(","===r)o.push(a),a="",n=0;else{if(!W.test(r))throw new Error("Parse error");n=5}}else if(5===n){if(!r){o.push(a);break}if(j.test(r))a+=" "+r,n=3;else if(","===r)o.push(a),a="",n=0;else if(!W.test(r))throw new Error("Parse error")}else if(4===n){if(!r)break;if(","===r)n=0;else if(!W.test(r))throw new Error("Parse error")}s++}return o},U={"sans-serif":"helvetica",verdana:"helvetica",arial:"helvetica",fixed:"courier",monospace:"courier",terminal:"courier",serif:"times",cursive:"times",fantasy:"times"};var z,Y=(z=r.version.split("."),2===parseFloat(z[0])&&3===parseFloat(z[1]));function X(t,e){return Y?400==e?"italic"==t?"italic":"normal":700==e&&"italic"!==t?"bold":t+""+e:400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"}function Q(t,e){if("none"===N(e.element,t.styleSheets,"display"))return[0,0,0,0];var r=[0,0,0,0];return e.children.forEach((function(e){var i=e.getBoundingBox(t);r=[Math.min(r[0],i[0]),Math.min(r[1],i[1]),Math.max(r[0]+r[2],i[0]+i[2])-Math.min(r[0],i[0]),Math.max(r[1]+r[3],i[1]+i[3])-Math.min(r[1],i[1])]})),r}function $(t,e){var r=parseFloat,i=r(t.getAttribute("x1"))||r(N(t,e.styleSheets,"x"))||r(N(t,e.styleSheets,"cx"))-r(N(t,e.styleSheets,"r"))||0,n=r(t.getAttribute("x2"))||i+r(N(t,e.styleSheets,"width"))||r(N(t,e.styleSheets,"cx"))+r(N(t,e.styleSheets,"r"))||0,a=r(t.getAttribute("y1"))||r(N(t,e.styleSheets,"y"))||r(N(t,e.styleSheets,"cy"))-r(N(t,e.styleSheets,"r"))||0,s=r(t.getAttribute("y2"))||a+r(N(t,e.styleSheets,"height"))||r(N(t,e.styleSheets,"cy"))+r(N(t,e.styleSheets,"r"))||0;return[Math.min(i,n),Math.min(a,s),Math.max(i,n)-Math.min(i,n),Math.max(a,s)-Math.min(a,s)]}function K(t,e,r,i,n,a,s,o){void 0===o&&(o=!1);var l,u,h=e[0],f=e[1],c=e[2],p=e[3],d=n/c,g=a/p,m=t.getAttribute("preserveAspectRatio");if(m){var y=m.split(" ");"defer"===y[0]&&(y=y.slice(1)),l=y[0],u=y[1]||"meet"}else l="xMidYMid",u="meet";if("none"!==l&&("meet"===u?d=g=Math.min(d,g):"slice"===u&&(d=g=Math.max(d,g))),o)return s.pdf.Matrix(d,0,0,g,0,0);var v=r-h*d,b=i-f*g;l.indexOf("xMid")>=0?v+=(n-c*d)/2:l.indexOf("xMax")>=0&&(v+=n-c*d),l.indexOf("YMid")>=0?b+=(a-p*g)/2:l.indexOf("YMax")>=0&&(b+=a-p*g);var x=s.pdf.Matrix(1,0,0,1,v,b),S=s.pdf.Matrix(d,0,0,g,0,0);return s.pdf.matrixMult(S,x)}function Z(t,e){if(!t||"none"===t)return e.pdf.unitMatrix;for(var r,i,n=/^[\s,]*matrix\(([^)]+)\)\s*/,a=/^[\s,]*translate\(([^)]+)\)\s*/,s=/^[\s,]*rotate\(([^)]+)\)\s*/,o=/^[\s,]*scale\(([^)]+)\)\s*/,l=/^[\s,]*skewX\(([^)]+)\)\s*/,u=/^[\s,]*skewY\(([^)]+)\)\s*/,h=e.pdf.unitMatrix;t.length>0&&t.length!==i;){i=t.length;var f=n.exec(t);if(f&&(r=V(f[1]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],r[1],r[2],r[3],r[4],r[5]),h),t=t.substr(f[0].length)),f=s.exec(t)){r=V(f[1]);var c=Math.PI*r[0]/180;if(h=e.pdf.matrixMult(e.pdf.Matrix(Math.cos(c),Math.sin(c),-Math.sin(c),Math.cos(c),0,0),h),r[1]||r[2]){var p=e.pdf.Matrix(1,0,0,1,r[1],r[2]),d=e.pdf.Matrix(1,0,0,1,-r[1],-r[2]);h=e.pdf.matrixMult(d,e.pdf.matrixMult(h,p))}t=t.substr(f[0].length)}(f=a.exec(t))&&(r=V(f[1]),h=e.pdf.matrixMult(e.pdf.Matrix(1,0,0,1,r[0],r[1]||0),h),t=t.substr(f[0].length)),(f=o.exec(t))&&((r=V(f[1]))[1]||(r[1]=r[0]),h=e.pdf.matrixMult(e.pdf.Matrix(r[0],0,0,r[1],0,0),h),t=t.substr(f[0].length)),(f=l.exec(t))&&(r=parseFloat(f[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,0,Math.tan(r),1,0,0),h),t=t.substr(f[0].length)),(f=u.exec(t))&&(r=parseFloat(f[1]),r*=Math.PI/180,h=e.pdf.matrixMult(e.pdf.Matrix(1,Math.tan(r),0,1,0,0),h),t=t.substr(f[0].length))}return h}var J=function(){function t(t,e){this.element=t,this.children=e,this.parent=null}return t.prototype.setParent=function(t){this.parent=t},t.prototype.getParent=function(){return this.parent},t.prototype.getBoundingBox=function(t){return"none"===N(this.element,t.styleSheets,"display")?[0,0,0,0]:this.getBoundingBoxCore(t)},t.prototype.computeNodeTransform=function(t){var e=this.computeNodeTransformCore(t),r=N(this.element,t.styleSheets,"transform");return r?t.pdf.matrixMult(e,Z(r,t)):e},t}(),tt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t){return Promise.resolve()},e.prototype.getBoundingBoxCore=function(t){return[]},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(J),et=function(t){function r(e,r,i){var n=t.call(this,r,i)||this;return n.pdfGradientType=e,n.contextColor=void 0,n}return n(r,t),r.prototype.apply=function(t){return s(this,void 0,void 0,(function(){var r,i,n,a,s,l;return o(this,(function(o){return(r=this.element.getAttribute("id"))?(i=this.getStops(t.styleSheets),n=0,a=!1,i.forEach((function(t){var e=t.opacity;e&&1!==e&&(n+=e,a=!0)})),a&&(s=new e.GState({opacity:n/i.length})),l=new e.ShadingPattern(this.pdfGradientType,this.getCoordinates(),i,s),t.pdf.addShadingPattern(r,l),[2]):[2]}))}))},r.prototype.getStops=function(t){var e=this;if(this.stops)return this.stops;if(void 0===this.contextColor){this.contextColor=null;for(var i=this;i;){var n=N(i.element,t,"color");if(n){this.contextColor=R(n,null);break}i=i.getParent()}}var a=[];return this.children.forEach((function(i){if("stop"===i.element.tagName.toLowerCase()){var n=N(i.element,t,"color"),s=R(N(i.element,t,"stop-color")||"",n?R(n,null):e.contextColor),o=parseFloat(N(i.element,t,"stop-opacity")||"1");a.push({offset:r.parseGradientOffset(i.element.getAttribute("offset")||"0"),color:[s.r,s.g,s.b],opacity:o})}})),this.stops=a},r.prototype.getBoundingBoxCore=function(t){return $(this.element,t)},r.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},r.prototype.isVisible=function(t,e){return L(this,t,e)},r.parseGradientOffset=function(t){var e=parseFloat(t);return!isNaN(e)&&t.indexOf("%")>=0?e/100:e},r}(tt),rt=function(t){function e(e,r){return t.call(this,"axial",e,r)||this}return n(e,t),e.prototype.getCoordinates=function(){return[parseFloat(this.element.getAttribute("x1")||"0"),parseFloat(this.element.getAttribute("y1")||"0"),parseFloat(this.element.getAttribute("x2")||"1"),parseFloat(this.element.getAttribute("y2")||"0")]},e}(et),it=function(t){function e(e,r){return t.call(this,"radial",e,r)||this}return n(e,t),e.prototype.getCoordinates=function(){var t=this.element.getAttribute("cx"),e=this.element.getAttribute("cy"),r=this.element.getAttribute("fx"),i=this.element.getAttribute("fy");return[parseFloat(r||t||"0.5"),parseFloat(i||e||"0.5"),0,parseFloat(t||"0.5"),parseFloat(e||"0.5"),parseFloat(this.element.getAttribute("r")||"0.5")]},e}(et),nt=function(){function t(t,e){this.key=t,this.gradient=e}return t.prototype.getFillData=function(t,e){return s(this,void 0,void 0,(function(){var r,i,n;return o(this,(function(a){switch(a.label){case 0:return[4,e.refsHandler.getRendered(this.key,null,(function(t){return t.apply(new f(e.pdf,{refsHandler:e.refsHandler,textMeasure:e.textMeasure,styleSheets:e.styleSheets,viewport:e.viewport,svg2pdfParameters:e.svg2pdfParameters}))}))];case 1:return a.sent(),this.gradient.element.hasAttribute("gradientUnits")&&"objectboundingbox"!==this.gradient.element.getAttribute("gradientUnits").toLowerCase()?r=e.pdf.unitMatrix:(i=t.getBoundingBox(e),r=e.pdf.Matrix(i[2],0,0,i[3],i[0],i[1])),n=Z(N(this.gradient.element,e.styleSheets,"gradientTransform","transform"),e),[2,{key:this.key,matrix:e.pdf.matrixMult(n,r)}]}}))}))},t}(),at=function(t){function r(){return null!==t&&t.apply(this,arguments)||this}return n(r,t),r.prototype.apply=function(t){return s(this,void 0,void 0,(function(){var r,i,n,a,s;return o(this,(function(o){switch(o.label){case 0:if(!(r=this.element.getAttribute("id")))return[2];i=this.getBoundingBox(t),n=new e.TilingPattern([i[0],i[1],i[0]+i[2],i[1]+i[3]],i[2],i[3]),t.pdf.beginTilingPattern(n),a=0,s=this.children,o.label=1;case 1:return a<s.length?[4,s[a].render(new f(t.pdf,{attributeState:t.attributeState,refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]:[3,4];case 2:o.sent(),o.label=3;case 3:return a++,[3,1];case 4:return t.pdf.endTilingPattern(r,n),[2]}}))}))},r.prototype.getBoundingBoxCore=function(t){return $(this.element,t)},r.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},r.prototype.isVisible=function(t,e){return L(this,t,e)},r}(tt),st=function(){function t(t,e){this.key=t,this.pattern=e}return t.prototype.getFillData=function(t,e){return s(this,void 0,void 0,(function(){var r,i,n,a,s,l,u,h,c,p,d,g;return o(this,(function(o){switch(o.label){case 0:return[4,e.refsHandler.getRendered(this.key,null,(function(t){return t.apply(new f(e.pdf,{refsHandler:e.refsHandler,textMeasure:e.textMeasure,styleSheets:e.styleSheets,viewport:e.viewport,svg2pdfParameters:e.svg2pdfParameters}))}))];case 1:return o.sent(),r={key:this.key,boundingBox:void 0,xStep:0,yStep:0,matrix:void 0},n=e.pdf.unitMatrix,this.pattern.element.hasAttribute("patternUnits")&&"objectboundingbox"!==this.pattern.element.getAttribute("patternUnits").toLowerCase()||(i=t.getBoundingBox(e),n=e.pdf.Matrix(1,0,0,1,i[0],i[1]),s=this.pattern.getBoundingBox(e),l=s[0]*i[0]||0,u=s[1]*i[1]||0,h=s[2]*i[2]||0,c=s[3]*i[3]||0,r.boundingBox=[l,u,l+h,u+c],r.xStep=h,r.yStep=c),a=e.pdf.unitMatrix,this.pattern.element.hasAttribute("patternContentUnits")&&"objectboundingbox"===this.pattern.element.getAttribute("patternContentUnits").toLowerCase()&&(i||(i=t.getBoundingBox(e)),a=e.pdf.Matrix(i[2],0,0,i[3],0,0),s=r.boundingBox||this.pattern.getBoundingBox(e),l=s[0]/i[0]||0,u=s[1]/i[1]||0,h=s[2]/i[2]||0,c=s[3]/i[3]||0,r.boundingBox=[l,u,l+h,u+c],r.xStep=h,r.yStep=c),p=e.pdf.unitMatrix,(d=N(this.pattern.element,e.styleSheets,"patternTransform","transform"))&&(p=Z(d,e)),g=a,g=e.pdf.matrixMult(g,n),g=e.pdf.matrixMult(g,p),g=e.pdf.matrixMult(g,e.transform),r.matrix=g,[2,r]}}))}))},t}();function ot(t,e){var r=_.exec(t);if(r){var i=r[1],n=e.refsHandler.get(i);return n&&(n instanceof rt||n instanceof it)?function(t,e,r){var i=e.getStops(r.styleSheets);if(0===i.length)return null;if(1===i.length){var n=i[0].color,a=new l;return a.ok=!0,a.r=n[0],a.g=n[1],a.b=n[2],a.a=i[0].opacity,new u(a)}return new nt(t,e)}(i,n,e):n&&n instanceof at?new st(i,n):new u(new l("rgb(0, 0, 0)"))}var a=R(t,e.attributeState.color);return a.ok?new u(a):null}function lt(t,e,r){var i=r||e.element,n=N(i,t.styleSheets,"color");if(n){var a=R(n,t.attributeState.color);a.ok?t.attributeState.color=a:t.attributeState.color=new l("rgb(0,0,0)")}var s=N(i,t.styleSheets,"visibility");s&&(t.attributeState.visibility=s);var o=N(i,t.styleSheets,"fill");o&&(t.attributeState.fill=ot(o,t));var h=N(i,t.styleSheets,"fill-opacity");h&&(t.attributeState.fillOpacity=parseFloat(h));var f=N(i,t.styleSheets,"stroke-opacity");f&&(t.attributeState.strokeOpacity=parseFloat(f));var c=N(i,t.styleSheets,"opacity");c&&(t.attributeState.opacity=parseFloat(c));var p=N(i,t.styleSheets,"stroke-width");void 0!==p&&""!==p&&(t.attributeState.strokeWidth=Math.abs(parseFloat(p)));var d=N(i,t.styleSheets,"stroke");if(d)if("none"===d)t.attributeState.stroke=null;else{var g=R(d,t.attributeState.color);g.ok&&(t.attributeState.stroke=new u(g))}var m=N(i,t.styleSheets,"stroke-linecap");m&&(t.attributeState.strokeLinecap=m);var y=N(i,t.styleSheets,"stroke-linejoin");y&&(t.attributeState.strokeLinejoin=y);var v=N(i,t.styleSheets,"stroke-dasharray");if(v){var b=parseInt(N(i,t.styleSheets,"stroke-dashoffset")||"0");t.attributeState.strokeDasharray=V(v),t.attributeState.strokeDashoffset=b}var x=N(i,t.styleSheets,"stroke-miterlimit");void 0!==x&&""!==x&&(t.attributeState.strokeMiterlimit=parseFloat(x));var S=i.getAttribute("xml:space");S&&(t.attributeState.xmlSpace=S);var w=N(i,t.styleSheets,"font-weight");w&&(t.attributeState.fontWeight=w);var k=N(i,t.styleSheets,"font-style");k&&(t.attributeState.fontStyle=k);var M=N(i,t.styleSheets,"font-family");if(M){var C=G(M);t.attributeState.fontFamily=function(t,e,r){var i=X(t.fontStyle,t.fontWeight),n=r.pdf.getFontList(),a="";return e.some((function(t){var e=n[t];return e&&e.indexOf(i)>=0?(a=t,!0):(t=t.toLowerCase(),!!U.hasOwnProperty(t)&&(a=t,!0))}))||(a="times"),a}(t.attributeState,C,t)}var A=N(i,t.styleSheets,"font-size");if(A){var F=t.pdf.getFontSize();t.attributeState.fontSize=D(A,F)}var T=N(i,t.styleSheets,"vertical-align")||N(i,t.styleSheets,"alignment-baseline");if(T){var P=T.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/);P&&(t.attributeState.alignmentBaseline=P[0])}var B=N(i,t.styleSheets,"text-anchor");B&&(t.attributeState.textAnchor=B)}function ut(t,r,i){var n=1,a=1;n*=t.attributeState.fillOpacity,n*=t.attributeState.opacity,t.attributeState.fill instanceof u&&void 0!==t.attributeState.fill.color.a&&(n*=t.attributeState.fill.color.a),a*=t.attributeState.strokeOpacity,a*=t.attributeState.opacity,t.attributeState.stroke instanceof u&&void 0!==t.attributeState.stroke.color.a&&(a*=t.attributeState.stroke.color.a);var s,o,l=n<1,h=a<1;if(B(i,"use")?(l=!0,h=!0,n*=t.attributeState.fill?1:0,a*=t.attributeState.stroke?1:0):t.withinUse&&(t.attributeState.fill!==r.attributeState.fill?(l=!0,n*=t.attributeState.fill?1:0):l&&!t.attributeState.fill&&(n=0),t.attributeState.stroke!==r.attributeState.stroke?(h=!0,a*=t.attributeState.stroke?1:0):h&&!t.attributeState.stroke&&(a=0)),l||h){var f={};l&&(f.opacity=n),h&&(f["stroke-opacity"]=a),t.pdf.setGState(new e.GState(f))}if(t.attributeState.fill&&t.attributeState.fill!==r.attributeState.fill&&t.attributeState.fill instanceof u&&t.attributeState.fill.color.ok&&!B(i,"text")&&t.pdf.setFillColor(t.attributeState.fill.color.r,t.attributeState.fill.color.g,t.attributeState.fill.color.b),t.attributeState.strokeWidth!==r.attributeState.strokeWidth&&t.pdf.setLineWidth(t.attributeState.strokeWidth),t.attributeState.stroke!==r.attributeState.stroke&&t.attributeState.stroke instanceof u&&t.pdf.setDrawColor(t.attributeState.stroke.color.r,t.attributeState.stroke.color.g,t.attributeState.stroke.color.b),t.attributeState.strokeLinecap!==r.attributeState.strokeLinecap&&t.pdf.setLineCap(t.attributeState.strokeLinecap),t.attributeState.strokeLinejoin!==r.attributeState.strokeLinejoin&&t.pdf.setLineJoin(t.attributeState.strokeLinejoin),t.attributeState.strokeDasharray===r.attributeState.strokeDasharray&&t.attributeState.strokeDashoffset===r.attributeState.strokeDashoffset||!t.attributeState.strokeDasharray||t.pdf.setLineDashPattern(t.attributeState.strokeDasharray,t.attributeState.strokeDashoffset),t.attributeState.strokeMiterlimit!==r.attributeState.strokeMiterlimit&&t.pdf.setLineMiterLimit(t.attributeState.strokeMiterlimit),t.attributeState.fontFamily!==r.attributeState.fontFamily&&(s=U.hasOwnProperty(t.attributeState.fontFamily)?U[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.attributeState.fill&&t.attributeState.fill!==r.attributeState.fill&&t.attributeState.fill instanceof u&&t.attributeState.fill.color.ok){var c=t.attributeState.fill.color;t.pdf.setTextColor(c.r,c.g,c.b)}t.attributeState.fontWeight===r.attributeState.fontWeight&&t.attributeState.fontStyle===r.attributeState.fontStyle||(o=X(t.attributeState.fontStyle,t.attributeState.fontWeight)),void 0===s&&void 0===o||(void 0===s&&(s=U.hasOwnProperty(t.attributeState.fontFamily)?U[t.attributeState.fontFamily]:t.attributeState.fontFamily),t.pdf.setFont(s,o)),t.attributeState.fontSize!==r.attributeState.fontSize&&t.pdf.setFontSize(t.attributeState.fontSize*t.pdf.internal.scaleFactor)}function ht(t,e,r){var i=_.exec(t);if(i){var n=i[1];return r.refsHandler.get(n)||void 0}}function ft(t,e,r){return s(this,void 0,void 0,(function(){var i,n;return o(this,(function(a){switch(a.label){case 0:return i=r.clone(),e.element.hasAttribute("clipPathUnits")&&"objectboundingbox"===e.element.getAttribute("clipPathUnits").toLowerCase()&&(n=t.getBoundingBox(r),i.transform=r.pdf.matrixMult(r.pdf.Matrix(n[2],0,0,n[3],n[0],n[1]),r.transform)),[4,e.apply(i)];case 1:return a.sent(),[2]}}))}))}var ct=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n;return o(this,(function(a){switch(a.label){case 0:return this.isVisible("hidden"!==t.attributeState.visibility,t)?((e=t.clone()).transform=e.pdf.matrixMult(this.computeNodeTransform(e),t.transform),lt(e,this),r=N(this.element,e.styleSheets,"clip-path"),(i=r&&"none"!==r)?(n=ht(r,0,e))?n.isVisible(!0,e)?(e.pdf.saveGraphicsState(),[4,ft(this,n,e)]):[3,2]:[3,4]:[3,5]):[2];case 1:return a.sent(),[3,3];case 2:return[2];case 3:return[3,5];case 4:i=!1,a.label=5;case 5:return e.withinClipPath||e.pdf.saveGraphicsState(),ut(e,t,this.element),[4,this.renderCore(e)];case 6:return a.sent(),e.withinClipPath||e.pdf.restoreGraphicsState(),i&&e.pdf.restoreGraphicsState(),[2]}}))}))},e}(J),pt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(ct),dt=function(t){function e(e,r,i){var n=t.call(this,r,i)||this;return n.cachedPath=null,n.hasMarkers=e,n}return n(e,t),e.prototype.renderCore=function(t){return s(this,void 0,void 0,(function(){var e;return o(this,(function(r){switch(r.label){case 0:return null===(e=this.getCachedPath(t))||0===e.segments.length?[2]:(t.withinClipPath?e.transform(t.transform):t.pdf.setCurrentTransformationMatrix(t.transform),e.draw(t),[4,this.fillOrStroke(t)]);case 1:return r.sent(),this.hasMarkers?[4,this.drawMarkers(t,e)]:[3,3];case 2:r.sent(),r.label=3;case 3:return[2]}}))}))},e.prototype.getCachedPath=function(t){return this.cachedPath||(this.cachedPath=this.getPath(t))},e.prototype.drawMarkers=function(t,e){return s(this,void 0,void 0,(function(){return o(this,(function(r){switch(r.label){case 0:return[4,this.getMarkers(e,t).draw(t.clone({transform:t.pdf.unitMatrix}))];case 1:return r.sent(),[2]}}))}))},e.prototype.fillOrStroke=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n,a;return o(this,(function(s){switch(s.label){case 0:return t.withinClipPath?[2]:(e=t.attributeState.fill,r=t.attributeState.stroke&&0!==t.attributeState.strokeWidth,e?[4,e.getFillData(this,t)]:[3,2]);case 1:return n=s.sent(),[3,3];case 2:n=void 0,s.label=3;case 3:return i=n,a="evenodd"===N(this.element,t.styleSheets,"fill-rule"),e&&r||t.withinUse?a?t.pdf.fillStrokeEvenOdd(i):t.pdf.fillStroke(i):e?a?t.pdf.fillEvenOdd(i):t.pdf.fill(i):r?t.pdf.stroke():t.pdf.discardPath(),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){var e=this.getCachedPath(t);if(!e)return[0,0,0,0];for(var r=Number.POSITIVE_INFINITY,i=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,a=Number.NEGATIVE_INFINITY,s=0,o=0,l=0;l<e.segments.length;l++){var u=e.segments[l];(u instanceof A||u instanceof F||u instanceof T)&&(s=u.x,o=u.y),u instanceof T?(r=Math.min(r,s,u.x1,u.x2,u.x),n=Math.max(n,s,u.x1,u.x2,u.x),i=Math.min(i,o,u.y1,u.y2,u.y),a=Math.max(a,o,u.y1,u.y2,u.y)):(r=Math.min(r,s),n=Math.max(n,s),i=Math.min(i,o),a=Math.max(a,o))}return[r,i,n-r,a-i]},e.prototype.getMarkers=function(t,e){var r=N(this.element,e.styleSheets,"marker-start"),i=N(this.element,e.styleSheets,"marker-mid"),n=N(this.element,e.styleSheets,"marker-end"),a=new O;if(r||i||n){n&&(n=gt(n)),r&&(r=gt(r)),i&&(i=gt(i));for(var s=t.segments,o=[1,0],l=void 0,u=!1,h=[1,0],f=!1,c=function(t){var e=s[t],c=r&&(1===t||!(s[t]instanceof A)&&s[t-1]instanceof A);c&&s.forEach((function(e,r){if(!f&&e instanceof P&&r>t){var i=s[r-1];f=(i instanceof A||i instanceof F||i instanceof T)&&i}}));var p=n&&(t===s.length-1||!(s[t]instanceof A)&&s[t+1]instanceof A),d=i&&t>0&&!(1===t&&s[t-1]instanceof A),g=s[t-1]||null;if(g instanceof A||g instanceof F||g instanceof T){if(e instanceof T)c&&a.addMarker(new I(r,[g.x,g.y],b(f?[f.x,f.y]:[g.x,g.y],[e.x1,e.y1]))),p&&a.addMarker(new I(n,[e.x,e.y],b([e.x2,e.y2],[e.x,e.y]))),d&&(l=w([g.x,g.y],[e.x1,e.y1]),l=g instanceof A?l:S(k(o,l)),a.addMarker(new I(i,[g.x,g.y],Math.atan2(l[1],l[0])))),o=w([e.x2,e.y2],[e.x,e.y]);else if(e instanceof A||e instanceof F){if(l=w([g.x,g.y],[e.x,e.y]),c){var m=f?w([f.x,f.y],[e.x,e.y]):l;a.addMarker(new I(r,[g.x,g.y],Math.atan2(m[1],m[0])))}if(p&&a.addMarker(new I(n,[e.x,e.y],Math.atan2(l[1],l[0]))),d){m=e instanceof A?o:g instanceof A?l:S(k(o,l));a.addMarker(new I(i,[g.x,g.y],Math.atan2(m[1],m[0])))}o=l}else if(e instanceof P){if(l=w([g.x,g.y],[u.x,u.y]),d){m=g instanceof A?l:S(k(o,l));a.addMarker(new I(i,[g.x,g.y],Math.atan2(m[1],m[0])))}if(p){m=S(k(l,h));a.addMarker(new I(n,[u.x,u.y],Math.atan2(m[1],m[0])))}o=l}}else{u=e instanceof A&&e;var y=s[t+1];(y instanceof A||y instanceof F||y instanceof T)&&(h=w([u.x,u.y],[y.x,y.y]))}},p=0;p<s.length;p++)c(p)}return a},e}(pt);function gt(t){var e=_.exec(t);return e&&e[1]||void 0}var mt=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return n(e,t),e.prototype.getPath=function(t){if(t.withinClipPath||null===t.attributeState.stroke)return null;var e=parseFloat(this.element.getAttribute("x1")||"0"),r=parseFloat(this.element.getAttribute("y1")||"0"),i=parseFloat(this.element.getAttribute("x2")||"0"),n=parseFloat(this.element.getAttribute("y2")||"0");return e||i||r||n?(new C).moveTo(e,r).lineTo(i,n):null},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e.prototype.fillOrStroke=function(e){return s(this,void 0,void 0,(function(){return o(this,(function(r){switch(r.label){case 0:return e.attributeState.fill=null,[4,t.prototype.fillOrStroke.call(this,e)];case 1:return r.sent(),[2]}}))}))},e}(dt),yt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.apply=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n,a;return o(this,(function(s){switch(s.label){case 0:return this.isVisible("hidden"!==t.attributeState.visibility,t)?((e=t.clone()).transform=e.pdf.unitMatrix,lt(e,this),r=N(this.element,e.styleSheets,"clip-path"),r&&"none"!==r&&(i=ht(r,0,e))?i.isVisible(!0,e)?[4,ft(this,i,e)]:[3,2]:[3,3]):[2];case 1:return s.sent(),[3,3];case 2:return[2];case 3:ut(e,t,this.element),n=0,a=this.children,s.label=4;case 4:return n<a.length?[4,a[n].render(e)]:[3,7];case 5:s.sent(),s.label=6;case 6:return n++,[3,4];case 7:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return Q(t,this)},e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.computeNodeTransformCore=function(t){var e=parseFloat(N(this.element,t.styleSheets,"x")||"0"),r=parseFloat(N(this.element,t.styleSheets,"y")||"0"),i=this.element.getAttribute("viewBox");if(i){var n=V(i),a=parseFloat(N(this.element,t.styleSheets,"width")||N(this.element.ownerSVGElement,t.styleSheets,"width")||i[2]),s=parseFloat(N(this.element,t.styleSheets,"height")||N(this.element.ownerSVGElement,t.styleSheets,"height")||i[3]);return K(this.element,n,e,r,a,s,t)}return t.pdf.Matrix(1,0,0,1,e,r)},e}(tt),vt=function(t,e){this.width=t,this.height=e},bt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.renderCore=function(t){return s(this,void 0,void 0,(function(){var r,i,n,a,s,l,u,h,c,p,d,g,m;return o(this,(function(o){switch(o.label){case 0:return r=parseFloat,(i=this.element.getAttribute("href")||this.element.getAttribute("xlink:href"))?(n=i.substring(1),a=t.refsHandler.get(n),s=B(a.element,"symbol,svg")&&a.element.hasAttribute("viewBox"),l=r(N(this.element,t.styleSheets,"x")||"0"),u=r(N(this.element,t.styleSheets,"y")||"0"),h=void 0,c=void 0,s?(h=r(N(this.element,t.styleSheets,"width")||N(a.element,t.styleSheets,"width")||"0"),c=r(N(this.element,t.styleSheets,"height")||N(a.element,t.styleSheets,"height")||"0"),l+=r(N(a.element,t.styleSheets,"x")||"0"),u+=r(N(a.element,t.styleSheets,"y")||"0"),d=V(a.element.getAttribute("viewBox")),p=K(a.element,d,l,u,h,c,t)):p=t.pdf.Matrix(1,0,0,1,l,u),g=new f(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,withinUse:!0,viewport:s?new vt(h,c):t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}),m=t.attributeState.color,[4,t.refsHandler.getRendered(n,m,(function(t){return e.renderReferencedNode(t,n,m,g)}))]):[2];case 1:return o.sent(),t.pdf.saveGraphicsState(),t.pdf.setCurrentTransformationMatrix(t.transform),s&&"visible"!==N(a.element,t.styleSheets,"overflow")&&(t.pdf.rect(l,u,h,c),t.pdf.clip().discardPath()),t.pdf.doFormObject(t.refsHandler.generateKey(n,m),p),t.pdf.restoreGraphicsState(),[2]}}))}))},e.renderReferencedNode=function(t,e,r,i){return s(this,void 0,void 0,(function(){var n;return o(this,(function(a){switch(a.label){case 0:return n=[(n=t.getBoundingBox(i))[0]-.5*n[2],n[1]-.5*n[3],2*n[2],2*n[3]],i.attributeState.color=r,i.pdf.beginFormObject(n[0],n[1],n[2],n[3],i.pdf.unitMatrix),t instanceof yt?[4,t.apply(i)]:[3,2];case 1:return a.sent(),[3,4];case 2:return[4,t.render(i)];case 3:a.sent(),a.label=4;case 4:return i.pdf.endFormObject(i.refsHandler.generateKey(e,r)),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return $(this.element,t)},e.prototype.isVisible=function(t,e){return E(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(pt),xt=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return n(e,t),e.prototype.getPath=function(t){var e=parseFloat(N(this.element,t.styleSheets,"width")||"0"),r=parseFloat(N(this.element,t.styleSheets,"height")||"0");if(!isFinite(e)||e<=0||!isFinite(r)||r<=0)return null;var i=N(this.element,t.styleSheets,"rx"),n=N(this.element,t.styleSheets,"ry"),a=Math.min(parseFloat(i||n||"0"),.5*e),s=Math.min(parseFloat(n||i||"0"),.5*r),o=parseFloat(N(this.element,t.styleSheets,"x")||"0"),l=parseFloat(N(this.element,t.styleSheets,"y")||"0"),u=4/3*(Math.SQRT2-1);return 0===a&&0===s?(new C).moveTo(o,l).lineTo(o+e,l).lineTo(o+e,l+r).lineTo(o,l+r).close():(new C).moveTo(o+=a,l).lineTo(o+=e-2*a,l).curveTo(o+a*u,l,o+a,l+(s-s*u),o+=a,l+=s).lineTo(o,l+=r-2*s).curveTo(o,l+s*u,o-a*u,l+s,o-=a,l+=s).lineTo(o+=2*a-e,l).curveTo(o-a*u,l,o-a,l-s*u,o-=a,l-=s).lineTo(o,l+=2*s-r).curveTo(o,l-s*u,o+a*u,l-s,o+=a,l-=s).close()},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e}(dt),St=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return n(e,t),e.prototype.getPath=function(t){var e=this.getRx(t),r=this.getRy(t);if(!isFinite(e)||r<=0||!isFinite(r)||r<=0)return null;var i=parseFloat(N(this.element,t.styleSheets,"cx")||"0"),n=parseFloat(N(this.element,t.styleSheets,"cy")||"0"),a=4/3*(Math.SQRT2-1)*e,s=4/3*(Math.SQRT2-1)*r;return(new C).moveTo(i+e,n).curveTo(i+e,n-s,i+a,n-r,i,n-r).curveTo(i-a,n-r,i-e,n-s,i-e,n).curveTo(i-e,n+s,i-a,n+r,i,n+r).curveTo(i+a,n+r,i+e,n+s,i+e,n)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e}(dt),wt=function(t){function e(e,r){return t.call(this,e,r)||this}return n(e,t),e.prototype.getRx=function(t){return parseFloat(N(this.element,t.styleSheets,"rx")||"0")},e.prototype.getRy=function(t){return parseFloat(N(this.element,t.styleSheets,"ry")||"0")},e}(St);function kt(t){var e="invisible",r=t.stroke&&0!==t.strokeWidth,i=t.fill;return i&&r?e="fillThenStroke":i?e="fill":r&&(e="stroke"),e}function Mt(t){return t.replace(/[\n\r]/g,"")}function Ct(t){return t.replace(/[\t]/g," ")}function At(t){return t.replace(/ +/g," ")}function Ft(t,e,r){switch(N(t,r.styleSheets,"text-transform")){case"uppercase":return e.toUpperCase();case"lowercase":return e.toLowerCase();default:return e}}var Tt=function(){function t(t,e,r,i){this.textNode=t,this.texts=[],this.textNodes=[],this.contexts=[],this.textAnchor=e,this.originX=r,this.originY=i,this.textMeasures=[]}return t.prototype.setX=function(t){this.originX=t},t.prototype.setY=function(t){this.originY=t},t.prototype.add=function(t,e,r){this.texts.push(e),this.textNodes.push(t),this.contexts.push(r)},t.prototype.rightTrimText=function(){for(var t=this.texts.length-1;t>=0;t--)if("default"===this.contexts[t].attributeState.xmlSpace&&(this.texts[t]=this.texts[t].replace(/\s+$/,"")),this.texts[t].match(/[^\s]/))return!1;return!0},t.prototype.measureText=function(t){for(var e=0;e<this.texts.length;e++)this.textMeasures.push({width:t.textMeasure.measureTextWidth(this.texts[e],this.contexts[e].attributeState),length:this.texts[e].length})},t.prototype.put=function(e,r){var i,n,a,s,o=[],l=[],u=[],h=this.originX,f=this.originY,c=h,p=h;for(i=0;i<this.textNodes.length;i++){n=this.textNodes[i],a=this.contexts[i],s=this.textMeasures[i]||{width:e.textMeasure.measureTextWidth(this.texts[i],this.contexts[i].attributeState),length:this.texts[i].length};var d=h,g=f;if("#text"!==n.nodeName&&!o.includes(n)){o.push(n);var m=t.resolveRelativePositionAttribute(n,"dx");null!==m&&(d+=D(m,a.attributeState.fontSize));var y=t.resolveRelativePositionAttribute(n,"dy");null!==y&&(g+=D(y,a.attributeState.fontSize))}l[i]=d,u[i]=g,h=d+s.width+s.length*r,f=g,c=Math.min(c,d),p=Math.max(p,h)}var v=0;switch(this.textAnchor){case"start":v=0;break;case"middle":v=(p-c)/2;break;case"end":v=p-c}for(i=0;i<this.textNodes.length;i++)if(n=this.textNodes[i],a=this.contexts[i],"#text"===n.nodeName||"hidden"!==a.attributeState.visibility){e.pdf.saveGraphicsState(),ut(a,e,n);var b=a.attributeState.alignmentBaseline,x=kt(a.attributeState);e.pdf.text(this.texts[i],l[i]-v,u[i],{baseline:q(b),angle:e.transform,renderingMode:"fill"===x?void 0:x,charSpace:0===r?void 0:r}),e.pdf.restoreGraphicsState()}return[h,f]},t.resolveRelativePositionAttribute=function(t,e){for(var r,i=t;i&&B(i,"tspan");){if(i.hasAttribute(e))return i.getAttribute(e);if((null===(r=t.parentElement)||void 0===r?void 0:r.firstChild)!==t)break;i=i.parentElement}return null},t}(),Pt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.processTSpans=function(t,e,r,i,n,a){for(var s=r.pdf.getFontSize(),o=r.attributeState.xmlSpace,l=!0,u=!1,h=0;h<e.childNodes.length;h++){var f=e.childNodes[h];if(f.textContent){var c=f.textContent;if("#text"===f.nodeName){var p=Mt(c);p=Ct(p),"default"===o&&(p=At(p),l&&p.match(/^\s/)&&(u=!0),p.match(/[^\s]/)&&(l=!1),a.prevText.match(/\s$/)&&(p=p.replace(/^\s+/,"")));var d=Ft(e,p,r);n.add(e,d,r),a.prevText=c,a.prevContext=r}else if(B(f,"title"));else if(B(f,"tspan")){var g=f,m=g.getAttribute("x");if(null!==m){var y=D(m,s);n=new Tt(this,N(g,r.styleSheets,"text-anchor")||r.attributeState.textAnchor,y,0),i.push({type:"y",chunk:n})}var v=g.getAttribute("y");if(null!==v){var b=D(v,s);n=new Tt(this,N(g,r.styleSheets,"text-anchor")||r.attributeState.textAnchor,0,b),i.push({type:"x",chunk:n})}var x=r.clone();lt(x,t,g),this.processTSpans(t,g,x,i,n,a)}}}return u},e.prototype.renderCore=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n,a,s,l,u,h,f,c,p,d,g,m,y,v,b,x,S,w,k,M;return o(this,(function(o){if(t.pdf.saveGraphicsState(),e=0,r=0,i=1,n=t.pdf.getFontSize(),a=D(this.element.getAttribute("x"),n),s=D(this.element.getAttribute("y"),n),l=D(this.element.getAttribute("dx"),n),u=D(this.element.getAttribute("dy"),n),h=parseFloat(this.element.getAttribute("textLength")||"0"),f=t.attributeState.visibility,0===this.element.childElementCount)c=this.element.textContent||"",p=function(t,e){return t=Ct(t=Mt(t)),"default"===e.xmlSpace&&(t=At(t=t.trim())),t}(c,t.attributeState),d=Ft(this.element,p,t),e=t.textMeasure.getTextOffset(d,t.attributeState),h>0&&(g=t.textMeasure.measureTextWidth(d,t.attributeState),"default"===t.attributeState.xmlSpace&&c.match(/^\s/)&&(i=0),r=(h-g)/(d.length-i)||0),"visible"===f&&(m=t.attributeState.alignmentBaseline,y=kt(t.attributeState),t.pdf.text(d,a+l-e,s+u,{baseline:q(m),angle:t.transform,renderingMode:"fill"===y?void 0:y,charSpace:0===r?void 0:r}));else{for(v=[],b=new Tt(this,t.attributeState.textAnchor,a+l,s+u),v.push({type:"",chunk:b}),x=this.processTSpans(this,this.element,t,v,b,{prevText:" ",prevContext:t}),i=x?0:1,S=!0,w=v.length-1;w>=0;w--)S&&(S=v[w].chunk.rightTrimText());h>0&&(k=0,M=0,v.forEach((function(e){var r=e.chunk;r.measureText(t),r.textMeasures.forEach((function(t){var e=t.width,r=t.length;k+=e,M+=r}))})),r=(h-k)/(M-i)),v.reduce((function(e,i){var n=i.type,a=i.chunk;return"x"===n?a.setX(e[0]):"y"===n&&a.setY(e[1]),a.put(t,r)}),[0,0])}return t.pdf.restoreGraphicsState(),[2]}))}))},e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.getBoundingBoxCore=function(t){return $(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(pt),Bt={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},Nt=[5760,6158,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8239,8287,12288,65279];function Et(t){return t>=48&&t<=57}function Lt(t){return t>=48&&t<=57||43===t||45===t||46===t}function Ot(t){this.index=0,this.path=t,this.max=t.length,this.result=[],this.param=0,this.err="",this.segmentStart=0,this.data=[]}function It(t){for(;t.index<t.max&&(10===(e=t.path.charCodeAt(t.index))||13===e||8232===e||8233===e||32===e||9===e||11===e||12===e||160===e||e>=5760&&Nt.indexOf(e)>=0);)t.index++;var e}function _t(t){var e=t.path.charCodeAt(t.index);return 48===e?(t.param=0,void t.index++):49===e?(t.param=1,void t.index++):void(t.err="SvgPath: arc flag can be 0 or 1 only (at pos "+t.index+")")}function Ht(t){var e,r=t.index,i=r,n=t.max,a=!1,s=!1,o=!1,l=!1;if(i>=n)t.err="SvgPath: missed param (at pos "+i+")";else if(43!==(e=t.path.charCodeAt(i))&&45!==e||(e=++i<n?t.path.charCodeAt(i):0),Et(e)||46===e){if(46!==e){if(a=48===e,e=++i<n?t.path.charCodeAt(i):0,a&&i<n&&e&&Et(e))return void(t.err="SvgPath: numbers started with `0` such as `09` are illegal (at pos "+r+")");for(;i<n&&Et(t.path.charCodeAt(i));)i++,s=!0;e=i<n?t.path.charCodeAt(i):0}if(46===e){for(l=!0,i++;Et(t.path.charCodeAt(i));)i++,o=!0;e=i<n?t.path.charCodeAt(i):0}if(101===e||69===e){if(l&&!s&&!o)return void(t.err="SvgPath: invalid float exponent (at pos "+i+")");if(43!==(e=++i<n?t.path.charCodeAt(i):0)&&45!==e||i++,!(i<n&&Et(t.path.charCodeAt(i))))return void(t.err="SvgPath: invalid float exponent (at pos "+i+")");for(;i<n&&Et(t.path.charCodeAt(i));)i++}t.index=i,t.param=parseFloat(t.path.slice(r,i))+0}else t.err="SvgPath: param should start with 0..9 or `.` (at pos "+i+")"}function Dt(t){var e,r;r=(e=t.path[t.segmentStart]).toLowerCase();var i=t.data;if("m"===r&&i.length>2&&(t.result.push([e,i[0],i[1]]),i=i.slice(2),r="l",e="m"===e?"l":"L"),"r"===r)t.result.push([e].concat(i));else for(;i.length>=Bt[r]&&(t.result.push([e].concat(i.splice(0,Bt[r]))),Bt[r]););}function qt(t){var e,r,i,n,a,s=t.max;if(t.segmentStart=t.index,e=t.path.charCodeAt(t.index),r=97==(32|e),function(t){switch(32|t){case 109:case 122:case 108:case 104:case 118:case 99:case 115:case 113:case 116:case 97:case 114:return!0}return!1}(e))if(n=Bt[t.path[t.index].toLowerCase()],t.index++,It(t),t.data=[],n){for(i=!1;;){for(a=n;a>0;a--){if(!r||3!==a&&4!==a?Ht(t):_t(t),t.err.length)return;t.data.push(t.param),It(t),i=!1,t.index<s&&44===t.path.charCodeAt(t.index)&&(t.index++,It(t),i=!0)}if(!i){if(t.index>=t.max)break;if(!Lt(t.path.charCodeAt(t.index)))break}}Dt(t)}else Dt(t);else t.err="SvgPath: bad command "+t.path[t.index]+" (at pos "+t.index+")"}function Vt(){if(!(this instanceof Vt))return new Vt;this.queue=[],this.cache=null}Vt.prototype.matrix=function(t){return 1===t[0]&&0===t[1]&&0===t[2]&&1===t[3]&&0===t[4]&&0===t[5]||(this.cache=null,this.queue.push(t)),this},Vt.prototype.translate=function(t,e){return 0===t&&0===e||(this.cache=null,this.queue.push([1,0,0,1,t,e])),this},Vt.prototype.scale=function(t,e){return 1===t&&1===e||(this.cache=null,this.queue.push([t,0,0,e,0,0])),this},Vt.prototype.rotate=function(t,e,r){var i,n,a;return 0!==t&&(this.translate(e,r),i=t*Math.PI/180,n=Math.cos(i),a=Math.sin(i),this.queue.push([n,a,-a,n,0,0]),this.cache=null,this.translate(-e,-r)),this},Vt.prototype.skewX=function(t){return 0!==t&&(this.cache=null,this.queue.push([1,0,Math.tan(t*Math.PI/180),1,0,0])),this},Vt.prototype.skewY=function(t){return 0!==t&&(this.cache=null,this.queue.push([1,Math.tan(t*Math.PI/180),0,1,0,0])),this},Vt.prototype.toArray=function(){if(this.cache)return this.cache;if(!this.queue.length)return this.cache=[1,0,0,1,0,0],this.cache;if(this.cache=this.queue[0],1===this.queue.length)return this.cache;for(var t=1;t<this.queue.length;t++)this.cache=(e=this.cache,r=this.queue[t],[e[0]*r[0]+e[2]*r[1],e[1]*r[0]+e[3]*r[1],e[0]*r[2]+e[2]*r[3],e[1]*r[2]+e[3]*r[3],e[0]*r[4]+e[2]*r[5]+e[4],e[1]*r[4]+e[3]*r[5]+e[5]]);var e,r;return this.cache},Vt.prototype.calc=function(t,e,r){var i;return this.queue.length?(this.cache||(this.cache=this.toArray()),[t*(i=this.cache)[0]+e*i[2]+(r?0:i[4]),t*i[1]+e*i[3]+(r?0:i[5])]):[t,e]};var Rt=Vt,jt={matrix:!0,scale:!0,rotate:!0,translate:!0,skewX:!0,skewY:!0},Wt=/\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,Gt=/[\s,]+/,Ut=2*Math.PI;function zt(t,e,r,i){var n=t*r+e*i;return n>1&&(n=1),n<-1&&(n=-1),(t*i-e*r<0?-1:1)*Math.acos(n)}function Yt(t,e){var r=4/3*Math.tan(e/4),i=Math.cos(t),n=Math.sin(t),a=Math.cos(t+e),s=Math.sin(t+e);return[i,n,i-n*r,n+i*r,a+s*r,s-a*r,a,s]}var Xt=function(t,e,r,i,n,a,s,o,l){var u=Math.sin(l*Ut/360),h=Math.cos(l*Ut/360),f=h*(t-r)/2+u*(e-i)/2,c=-u*(t-r)/2+h*(e-i)/2;if(0===f&&0===c)return[];if(0===s||0===o)return[];s=Math.abs(s),o=Math.abs(o);var p=f*f/(s*s)+c*c/(o*o);p>1&&(s*=Math.sqrt(p),o*=Math.sqrt(p));var d=function(t,e,r,i,n,a,s,o,l,u){var h=u*(t-r)/2+l*(e-i)/2,f=-l*(t-r)/2+u*(e-i)/2,c=s*s,p=o*o,d=h*h,g=f*f,m=c*p-c*g-p*d;m<0&&(m=0),m/=c*g+p*d;var y=(m=Math.sqrt(m)*(n===a?-1:1))*s/o*f,v=m*-o/s*h,b=u*y-l*v+(t+r)/2,x=l*y+u*v+(e+i)/2,S=(h-y)/s,w=(f-v)/o,k=(-h-y)/s,M=(-f-v)/o,C=zt(1,0,S,w),A=zt(S,w,k,M);return 0===a&&A>0&&(A-=Ut),1===a&&A<0&&(A+=Ut),[b,x,C,A]}(t,e,r,i,n,a,s,o,u,h),g=[],m=d[2],y=d[3],v=Math.max(Math.ceil(Math.abs(y)/(Ut/4)),1);y/=v;for(var b=0;b<v;b++)g.push(Yt(m,y)),m+=y;return g.map((function(t){for(var e=0;e<t.length;e+=2){var r=t[e+0],i=t[e+1],n=h*(r*=s)-u*(i*=o),a=u*r+h*i;t[e+0]=n+d[0],t[e+1]=a+d[1]}return t}))},Qt=Math.PI/180;function $t(t,e,r){if(!(this instanceof $t))return new $t(t,e,r);this.rx=t,this.ry=e,this.ax=r}$t.prototype.transform=function(t){var e=Math.cos(this.ax*Qt),r=Math.sin(this.ax*Qt),i=[this.rx*(t[0]*e+t[2]*r),this.rx*(t[1]*e+t[3]*r),this.ry*(-t[0]*r+t[2]*e),this.ry*(-t[1]*r+t[3]*e)],n=i[0]*i[0]+i[2]*i[2],a=i[1]*i[1]+i[3]*i[3],s=((i[0]-i[3])*(i[0]-i[3])+(i[2]+i[1])*(i[2]+i[1]))*((i[0]+i[3])*(i[0]+i[3])+(i[2]-i[1])*(i[2]-i[1])),o=(n+a)/2;if(s<1e-10*o)return this.rx=this.ry=Math.sqrt(o),this.ax=0,this;var l=i[0]*i[1]+i[2]*i[3],u=o+(s=Math.sqrt(s))/2,h=o-s/2;return this.ax=Math.abs(l)<1e-10&&Math.abs(u-a)<1e-10?90:180*Math.atan(Math.abs(l)>Math.abs(u-a)?(u-n)/l:l/(u-a))/Math.PI,this.ax>=0?(this.rx=Math.sqrt(u),this.ry=Math.sqrt(h)):(this.ax+=90,this.rx=Math.sqrt(h),this.ry=Math.sqrt(u)),this},$t.prototype.isDegenerate=function(){return this.rx<1e-10*this.ry||this.ry<1e-10*this.rx};var Kt=$t;function Zt(t){if(!(this instanceof Zt))return new Zt(t);var e=function(t){var e=new Ot(t),r=e.max;for(It(e);e.index<r&&!e.err.length;)qt(e);return e.err.length?e.result=[]:e.result.length&&("mM".indexOf(e.result[0][0])<0?(e.err="SvgPath: string should start with `M` or `m`",e.result=[]):e.result[0][0]="M"),{err:e.err,segments:e.result}}(t);this.segments=e.segments,this.err=e.err,this.__stack=[]}Zt.from=function(t){if("string"==typeof t)return new Zt(t);if(t instanceof Zt){var e=new Zt("");return e.err=t.err,e.segments=t.segments.map((function(t){return t.slice()})),e.__stack=t.__stack.map((function(t){return Rt().matrix(t.toArray())})),e}throw new Error("SvgPath.from: invalid param type "+t)},Zt.prototype.__matrix=function(t){var e,r=this;t.queue.length&&this.iterate((function(i,n,a,s){var o,l,u,h;switch(i[0]){case"v":l=0===(o=t.calc(0,i[1],!0))[0]?["v",o[1]]:["l",o[0],o[1]];break;case"V":l=(o=t.calc(a,i[1],!1))[0]===t.calc(a,s,!1)[0]?["V",o[1]]:["L",o[0],o[1]];break;case"h":l=0===(o=t.calc(i[1],0,!0))[1]?["h",o[0]]:["l",o[0],o[1]];break;case"H":l=(o=t.calc(i[1],s,!1))[1]===t.calc(a,s,!1)[1]?["H",o[0]]:["L",o[0],o[1]];break;case"a":case"A":var f=t.toArray(),c=Kt(i[1],i[2],i[3]).transform(f);if(f[0]*f[3]-f[1]*f[2]<0&&(i[5]=i[5]?"0":"1"),o=t.calc(i[6],i[7],"a"===i[0]),"A"===i[0]&&i[6]===a&&i[7]===s||"a"===i[0]&&0===i[6]&&0===i[7]){l=["a"===i[0]?"l":"L",o[0],o[1]];break}l=c.isDegenerate()?["a"===i[0]?"l":"L",o[0],o[1]]:[i[0],c.rx,c.ry,c.ax,i[4],i[5],o[0],o[1]];break;case"m":h=n>0,l=["m",(o=t.calc(i[1],i[2],h))[0],o[1]];break;default:for(l=[u=i[0]],h=u.toLowerCase()===u,e=1;e<i.length;e+=2)o=t.calc(i[e],i[e+1],h),l.push(o[0],o[1])}r.segments[n]=l}),!0)},Zt.prototype.__evaluateStack=function(){var t,e;if(this.__stack.length){if(1===this.__stack.length)return this.__matrix(this.__stack[0]),void(this.__stack=[]);for(t=Rt(),e=this.__stack.length;--e>=0;)t.matrix(this.__stack[e].toArray());this.__matrix(t),this.__stack=[]}},Zt.prototype.toString=function(){var t="",e="",r=!1;this.__evaluateStack();for(var i=0,n=this.segments.length;i<n;i++){var a=this.segments[i],s=a[0];s!==e||"m"===s||"M"===s?("m"===s&&"z"===e&&(t+=" "),t+=s,r=!1):r=!0;for(var o=1;o<a.length;o++){var l=a[o];1===o?r&&l>=0&&(t+=" "):l>=0&&(t+=" "),t+=l}e=s}return t},Zt.prototype.translate=function(t,e){return this.__stack.push(Rt().translate(t,e||0)),this},Zt.prototype.scale=function(t,e){return this.__stack.push(Rt().scale(t,e||0===e?e:t)),this},Zt.prototype.rotate=function(t,e,r){return this.__stack.push(Rt().rotate(t,e||0,r||0)),this},Zt.prototype.skewX=function(t){return this.__stack.push(Rt().skewX(t)),this},Zt.prototype.skewY=function(t){return this.__stack.push(Rt().skewY(t)),this},Zt.prototype.matrix=function(t){return this.__stack.push(Rt().matrix(t)),this},Zt.prototype.transform=function(t){return t.trim()?(this.__stack.push(function(t){var e,r,i=new Rt;return t.split(Wt).forEach((function(t){if(t.length)if(void 0===jt[t])switch(r=t.split(Gt).map((function(t){return+t||0})),e){case"matrix":return void(6===r.length&&i.matrix(r));case"scale":return void(1===r.length?i.scale(r[0],r[0]):2===r.length&&i.scale(r[0],r[1]));case"rotate":return void(1===r.length?i.rotate(r[0],0,0):3===r.length&&i.rotate(r[0],r[1],r[2]));case"translate":return void(1===r.length?i.translate(r[0],0):2===r.length&&i.translate(r[0],r[1]));case"skewX":return void(1===r.length&&i.skewX(r[0]));case"skewY":return void(1===r.length&&i.skewY(r[0]))}else e=t})),i}(t)),this):this},Zt.prototype.round=function(t){var e,r=0,i=0,n=0,a=0;return t=t||0,this.__evaluateStack(),this.segments.forEach((function(s){var o=s[0].toLowerCase()===s[0];switch(s[0]){case"H":case"h":return o&&(s[1]+=n),n=s[1]-s[1].toFixed(t),void(s[1]=+s[1].toFixed(t));case"V":case"v":return o&&(s[1]+=a),a=s[1]-s[1].toFixed(t),void(s[1]=+s[1].toFixed(t));case"Z":case"z":return n=r,void(a=i);case"M":case"m":return o&&(s[1]+=n,s[2]+=a),n=s[1]-s[1].toFixed(t),a=s[2]-s[2].toFixed(t),r=n,i=a,s[1]=+s[1].toFixed(t),void(s[2]=+s[2].toFixed(t));case"A":case"a":return o&&(s[6]+=n,s[7]+=a),n=s[6]-s[6].toFixed(t),a=s[7]-s[7].toFixed(t),s[1]=+s[1].toFixed(t),s[2]=+s[2].toFixed(t),s[3]=+s[3].toFixed(t+2),s[6]=+s[6].toFixed(t),void(s[7]=+s[7].toFixed(t));default:return e=s.length,o&&(s[e-2]+=n,s[e-1]+=a),n=s[e-2]-s[e-2].toFixed(t),a=s[e-1]-s[e-1].toFixed(t),void s.forEach((function(e,r){r&&(s[r]=+s[r].toFixed(t))}))}})),this},Zt.prototype.iterate=function(t,e){var r,i,n,a=this.segments,s={},o=!1,l=0,u=0,h=0,f=0;if(e||this.__evaluateStack(),a.forEach((function(e,r){var i=t(e,r,l,u);Array.isArray(i)&&(s[r]=i,o=!0);var n=e[0]===e[0].toLowerCase();switch(e[0]){case"m":case"M":return l=e[1]+(n?l:0),u=e[2]+(n?u:0),h=l,void(f=u);case"h":case"H":return void(l=e[1]+(n?l:0));case"v":case"V":return void(u=e[1]+(n?u:0));case"z":case"Z":return l=h,void(u=f);default:l=e[e.length-2]+(n?l:0),u=e[e.length-1]+(n?u:0)}})),!o)return this;for(n=[],r=0;r<a.length;r++)if(void 0!==s[r])for(i=0;i<s[r].length;i++)n.push(s[r][i]);else n.push(a[r]);return this.segments=n,this},Zt.prototype.abs=function(){return this.iterate((function(t,e,r,i){var n,a=t[0],s=a.toUpperCase();if(a!==s)switch(t[0]=s,a){case"v":return void(t[1]+=i);case"a":return t[6]+=r,void(t[7]+=i);default:for(n=1;n<t.length;n++)t[n]+=n%2?r:i}}),!0),this},Zt.prototype.rel=function(){return this.iterate((function(t,e,r,i){var n,a=t[0],s=a.toLowerCase();if(a!==s&&(0!==e||"M"!==a))switch(t[0]=s,a){case"V":return void(t[1]-=i);case"A":return t[6]-=r,void(t[7]-=i);default:for(n=1;n<t.length;n++)t[n]-=n%2?r:i}}),!0),this},Zt.prototype.unarc=function(){return this.iterate((function(t,e,r,i){var n,a,s,o=[],l=t[0];return"A"!==l&&"a"!==l?null:("a"===l?(a=r+t[6],s=i+t[7]):(a=t[6],s=t[7]),0===(n=Xt(r,i,a,s,t[4],t[5],t[1],t[2],t[3])).length?[["a"===t[0]?"l":"L",t[6],t[7]]]:(n.forEach((function(t){o.push(["C",t[2],t[3],t[4],t[5],t[6],t[7]])})),o))})),this},Zt.prototype.unshort=function(){var t,e,r,i,n,a=this.segments;return this.iterate((function(s,o,l,u){var h,f=s[0],c=f.toUpperCase();o&&("T"===c?(h="t"===f,"Q"===(r=a[o-1])[0]?(t=r[1]-l,e=r[2]-u):"q"===r[0]?(t=r[1]-r[3],e=r[2]-r[4]):(t=0,e=0),i=-t,n=-e,h||(i+=l,n+=u),a[o]=[h?"q":"Q",i,n,s[1],s[2]]):"S"===c&&(h="s"===f,"C"===(r=a[o-1])[0]?(t=r[3]-l,e=r[4]-u):"c"===r[0]?(t=r[3]-r[5],e=r[4]-r[6]):(t=0,e=0),i=-t,n=-e,h||(i+=l,n+=u),a[o]=[h?"c":"C",i,n,s[1],s[2],s[3],s[4]]))})),this};var Jt=Zt,te=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return n(e,t),e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e.prototype.getPath=function(t){var e,r,i=new Jt(N(this.element,t.styleSheets,"d")||"").unshort().unarc().abs(),n=new C;return i.iterate((function(t){switch(t[0]){case"M":n.moveTo(t[1],t[2]);break;case"L":n.lineTo(t[1],t[2]);break;case"H":n.lineTo(t[1],r);break;case"V":n.lineTo(e,t[1]);break;case"C":n.curveTo(t[1],t[2],t[3],t[4],t[5],t[6]);break;case"Q":var i=x([e,r],[t[1],t[2]]),a=x([t[3],t[4]],[t[1],t[2]]);n.curveTo(i[0],i[1],a[0],a[1],t[3],t[4]);break;case"Z":n.close()}switch(t[0]){case"M":case"L":e=t[1],r=t[2];break;case"H":e=t[1];break;case"V":r=t[1];break;case"C":e=t[5],r=t[6];break;case"Q":e=t[3],r=t[4]}})),n},e}(dt),ee=/^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,((?:.|\s)*)$/i,re=function(t){function e(r,i){var n=t.call(this,r,i)||this;return n.imageLoadingPromise=null,n.imageUrl=n.element.getAttribute("xlink:href")||n.element.getAttribute("href"),n.imageUrl&&(n.imageLoadingPromise=e.fetchImageData(n.imageUrl)),n}return n(e,t),e.prototype.renderCore=function(t){return s(this,void 0,void 0,(function(){var e,r,i,n,a,s,l,u,h,c,p,d;return o(this,(function(o){switch(o.label){case 0:return this.imageLoadingPromise?(t.pdf.setCurrentTransformationMatrix(t.transform),e=parseFloat(N(this.element,t.styleSheets,"width")||"0"),r=parseFloat(N(this.element,t.styleSheets,"height")||"0"),i=parseFloat(N(this.element,t.styleSheets,"x")||"0"),n=parseFloat(N(this.element,t.styleSheets,"y")||"0"),!isFinite(e)||e<=0||!isFinite(r)||r<=0?[2]:[4,this.imageLoadingPromise]):[2];case 1:return a=o.sent(),s=a.data,0!==(l=a.format).indexOf("svg")?[3,3]:(u=new DOMParser,h=u.parseFromString(s,"image/svg+xml").firstElementChild,(!(c=this.element.getAttribute("preserveAspectRatio"))||c.indexOf("defer")<0||!h.getAttribute("preserveAspectRatio"))&&h.setAttribute("preserveAspectRatio",c||""),h.setAttribute("x",String(i)),h.setAttribute("y",String(n)),h.setAttribute("width",String(e)),h.setAttribute("height",String(r)),[4,pe(h,p={}).render(new f(t.pdf,{refsHandler:new v(p),styleSheets:t.styleSheets,viewport:new vt(e,r),svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]);case 2:return o.sent(),[2];case 3:d="data:image/"+l+";base64,"+btoa(s);try{t.pdf.addImage(d,"",i,n,e,r)}catch(t){"object"==typeof console&&console.warn&&console.warn("Could not load image "+this.imageUrl+". \n"+t)}o.label=4;case 4:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return $(this.element,t)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e.fetchImageData=function(t){return s(this,void 0,void 0,(function(){var r,i,n,a,s;return o(this,(function(o){switch(o.label){case 0:if(!(n=t.match(ee)))return[3,1];if(a=n[2],"image"!==(s=a.split("/"))[0])throw new Error("Unsupported image URL: "+t);return i=s[1],r=n[5],"base64"===n[4]?(r=r.replace(/\s/g,""),r=atob(r)):r=decodeURIComponent(r),[3,3];case 1:return[4,e.fetchImage(t)];case 2:r=o.sent(),i=t.substring(t.lastIndexOf(".")+1),o.label=3;case 3:return[2,{data:r,format:i}]}}))}))},e.fetchImage=function(t){return new Promise((function(e,r){var i=new XMLHttpRequest;i.open("GET",t,!0),i.responseType="arraybuffer",i.onload=function(){if(200!==i.status)throw new Error("Error "+i.status+": Failed to load image '"+t+"'");for(var r=new Uint8Array(i.response),n="",a=0;a<r.length;a++)n+=String.fromCharCode(r[a]);e(n)},i.onerror=r,i.onabort=r,i.send(null)}))},e.getMimeType=function(t){switch(t=t.toLowerCase()){case"jpg":case"jpeg":return"image/jpeg";default:return"image/"+t}},e}(pt),ie=function(t){function e(e,r,i){var n=t.call(this,!0,r,i)||this;return n.closed=e,n}return n(e,t),e.prototype.getPath=function(t){if(!this.element.hasAttribute("points")||""===this.element.getAttribute("points"))return null;var r=e.parsePointsString(this.element.getAttribute("points")),i=new C;if(r.length<1)return i;i.moveTo(r[0][0],r[0][1]);for(var n=1;n<r.length;n++)i.lineTo(r[n][0],r[n][1]);return this.closed&&i.close(),i},e.prototype.isVisible=function(t,e){return E(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.parsePointsString=function(t){for(var e=V(t),r=[],i=0;i<e.length-1;i+=2){var n=e[i],a=e[i+1];r.push([n,a])}return r},e}(dt),ne=function(t){function e(e,r){return t.call(this,!0,e,r)||this}return n(e,t),e}(ie),ae=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t){return Promise.resolve()},e.prototype.getBoundingBoxCore=function(t){return[0,0,0,0]},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e.prototype.isVisible=function(t,e){return E(this,t,e)},e}(J),se=function(t){function r(){return null!==t&&t.apply(this,arguments)||this}return n(r,t),r.prototype.apply=function(t){return s(this,void 0,void 0,(function(){var r,i,n,a,s;return o(this,(function(o){switch(o.label){case 0:r=this.computeNodeTransform(t),i=this.getBoundingBox(t),t.pdf.beginFormObject(i[0],i[1],i[2],i[3],r),function(t){var r=t.attributeState,i=t.pdf,n=1,a=1;n*=r.fillOpacity,n*=r.opacity,r.fill instanceof u&&void 0!==r.fill.color.a&&(n*=r.fill.color.a),a*=r.strokeOpacity,a*=r.opacity,r.stroke instanceof u&&void 0!==r.stroke.color.a&&(a*=r.stroke.color.a);var s,o={};if(o.opacity=n,o["stroke-opacity"]=a,i.setGState(new e.GState(o)),r.fill&&r.fill instanceof u&&r.fill.color.ok?i.setFillColor(r.fill.color.r,r.fill.color.g,r.fill.color.b):i.setFillColor(0,0,0),i.setLineWidth(r.strokeWidth),r.stroke instanceof u?i.setDrawColor(r.stroke.color.r,r.stroke.color.g,r.stroke.color.b):i.setDrawColor(0,0,0),i.setLineCap(r.strokeLinecap),i.setLineJoin(r.strokeLinejoin),r.strokeDasharray?i.setLineDashPattern(r.strokeDasharray,r.strokeDashoffset):i.setLineDashPattern([],0),i.setLineMiterLimit(r.strokeMiterlimit),s=U.hasOwnProperty(r.fontFamily)?U[r.fontFamily]:r.fontFamily,r.fill&&r.fill instanceof u&&r.fill.color.ok){var l=r.fill.color;i.setTextColor(l.r,l.g,l.b)}else i.setTextColor(0,0,0);var h="";"bold"===r.fontWeight&&(h="bold"),"italic"===r.fontStyle&&(h+="italic"),""===h&&(h="normal"),void 0!==s||void 0!==h?(void 0===s&&(s=U.hasOwnProperty(r.fontFamily)?U[r.fontFamily]:r.fontFamily),i.setFont(s,h)):i.setFont("helvetica",h),i.setFontSize(r.fontSize*i.internal.scaleFactor)}(n=new f(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure})),a=0,s=this.children,o.label=1;case 1:return a<s.length?[4,s[a].render(n)]:[3,4];case 2:o.sent(),o.label=3;case 3:return a++,[3,1];case 4:return t.pdf.endFormObject(this.element.getAttribute("id")),[2]}}))}))},r.prototype.getBoundingBoxCore=function(t){var e,r=this.element.getAttribute("viewBox");return r&&(e=V(r)),[e&&e[0]||0,e&&e[1]||0,e&&e[2]||parseFloat(this.element.getAttribute("markerWidth")||"3"),e&&e[3]||parseFloat(this.element.getAttribute("markerHeight")||"3")]},r.prototype.computeNodeTransformCore=function(t){var e,r=parseFloat(this.element.getAttribute("refX")||"0"),i=parseFloat(this.element.getAttribute("refY")||"0"),n=this.element.getAttribute("viewBox");if(n){var a=V(n);e=K(this.element,a,0,0,parseFloat(this.element.getAttribute("markerWidth")||"3"),parseFloat(this.element.getAttribute("markerHeight")||"3"),t,!0),e=t.pdf.matrixMult(t.pdf.Matrix(1,0,0,1,-r,-i),e)}else e=t.pdf.Matrix(1,0,0,1,-r,-i);return e},r.prototype.isVisible=function(t,e){return L(this,t,e)},r}(tt),oe=function(t){function e(e,r){return t.call(this,e,r)||this}return n(e,t),e.prototype.getR=function(t){var e;return null!==(e=this.r)&&void 0!==e?e:this.r=parseFloat(N(this.element,t.styleSheets,"r")||"0")},e.prototype.getRx=function(t){return this.getR(t)},e.prototype.getRy=function(t){return this.getR(t)},e}(St),le=function(t){function e(e,r){return t.call(this,!1,e,r)||this}return n(e,t),e}(ie),ue=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.renderCore=function(t){return s(this,void 0,void 0,(function(){var e,r;return o(this,(function(i){switch(i.label){case 0:e=0,r=this.children,i.label=1;case 1:return e<r.length?[4,r[e].render(t)]:[3,4];case 2:i.sent(),i.label=3;case 3:return e++,[3,1];case 4:return[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return Q(t,this)},e}(ct),he=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.render=function(e){return s(this,void 0,void 0,(function(){var r,i,n,a,s;return o(this,(function(o){switch(o.label){case 0:return this.isVisible("hidden"!==e.attributeState.visibility,e)?(r=this.getX(e),i=this.getY(e),n=this.getWidth(e),a=this.getHeight(e),e.pdf.saveGraphicsState(),s=e.transform,this.element.hasAttribute("transform")&&(s=e.pdf.matrixMult(Z(this.element.getAttribute("transform"),e),s)),e.pdf.setCurrentTransformationMatrix(s),e.withinUse||"visible"===N(this.element,e.styleSheets,"overflow")||e.pdf.rect(r,i,n,a).clip().discardPath(),[4,t.prototype.render.call(this,e.clone({transform:e.pdf.unitMatrix,viewport:e.withinUse?e.viewport:new vt(n,a)}))]):[2];case 1:return o.sent(),e.pdf.restoreGraphicsState(),[2]}}))}))},e.prototype.computeNodeTransform=function(t){return this.computeNodeTransformCore(t)},e.prototype.computeNodeTransformCore=function(t){if(t.withinUse)return t.pdf.unitMatrix;var e,r=this.getX(t),i=this.getY(t),n=this.getViewBox();if(n){var a=this.getWidth(t),s=this.getHeight(t);e=K(this.element,n,r,i,a,s,t)}else e=t.pdf.Matrix(1,0,0,1,r,i);return e},e.prototype.getWidth=function(t){if(void 0!==this.width)return this.width;var e,r,i=t.svg2pdfParameters;if(this.isOutermostSvg(t))if(null!=i.width)e=i.width;else if(r=N(this.element,t.styleSheets,"width"))e=parseFloat(r);else{var n=this.getViewBox();if(n&&(null!=i.height||N(this.element,t.styleSheets,"height"))){var a=n[2]/n[3];e=this.getHeight(t)*a}else e=Math.min(300,t.viewport.width,2*t.viewport.height)}else e=(r=N(this.element,t.styleSheets,"width"))?parseFloat(r):t.viewport.width;return this.width=e},e.prototype.getHeight=function(t){if(void 0!==this.height)return this.height;var e,r,i=t.svg2pdfParameters;if(this.isOutermostSvg(t))if(null!=i.height)e=i.height;else if(r=N(this.element,t.styleSheets,"height"))e=parseFloat(r);else{var n=this.getViewBox();if(n){var a=n[2]/n[3];e=this.getWidth(t)/a}else e=Math.min(150,t.viewport.width/2,t.viewport.height)}else e=(r=N(this.element,t.styleSheets,"height"))?parseFloat(r):t.viewport.height;return this.height=e},e.prototype.getX=function(t){if(void 0!==this.x)return this.x;if(this.isOutermostSvg(t))return this.x=0;var e=N(this.element,t.styleSheets,"x");return this.x=e?parseFloat(e):0},e.prototype.getY=function(t){if(void 0!==this.y)return this.y;if(this.isOutermostSvg(t))return this.y=0;var e=N(this.element,t.styleSheets,"y");return this.y=e?parseFloat(e):0},e.prototype.getViewBox=function(){if(void 0!==this.viewBox)return this.viewBox;var t=this.element.getAttribute("viewBox");return this.viewBox=t?V(t):void 0},e.prototype.isOutermostSvg=function(t){return t.svg2pdfParameters.element===this.element},e}(ue),fe=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.isVisible=function(t,e){return L(this,t,e)},e.prototype.computeNodeTransformCore=function(t){return t.pdf.unitMatrix},e}(ue),ce=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.apply=function(t){return s(this,void 0,void 0,(function(){var e,r,i;return o(this,(function(n){switch(n.label){case 0:if(!this.isVisible(!0,t))return[2];e=t.pdf.matrixMult(this.computeNodeTransform(t),t.transform),t.pdf.setCurrentTransformationMatrix(e),r=0,i=this.children,n.label=1;case 1:return r<i.length?[4,i[r].render(new f(t.pdf,{refsHandler:t.refsHandler,styleSheets:t.styleSheets,viewport:t.viewport,withinClipPath:!0,svg2pdfParameters:t.svg2pdfParameters,textMeasure:t.textMeasure}))]:[3,4];case 2:n.sent(),n.label=3;case 3:return r++,[3,1];case 4:return t.pdf.clip().discardPath(),t.pdf.setCurrentTransformationMatrix(e.inversed()),[2]}}))}))},e.prototype.getBoundingBoxCore=function(t){return Q(t,this)},e.prototype.isVisible=function(t,e){return L(this,t,e)},e}(tt);function pe(t,e){var r,i=[];switch(function(t,e){for(var r=[],i=0;i<t.childNodes.length;i++){var n=t.childNodes[i];"#"!==n.nodeName.charAt(0)&&r.push(n)}for(i=0;i<r.length;i++)e(i,r[i])}(t,(function(t,r){return i.push(pe(r,e))})),t.tagName.toLowerCase()){case"a":case"g":r=new fe(t,i);break;case"circle":r=new oe(t,i);break;case"clippath":r=new ce(t,i);break;case"ellipse":r=new wt(t,i);break;case"lineargradient":r=new rt(t,i);break;case"image":r=new re(t,i);break;case"line":r=new mt(t,i);break;case"marker":r=new se(t,i);break;case"path":r=new te(t,i);break;case"pattern":r=new at(t,i);break;case"polygon":r=new ne(t,i);break;case"polyline":r=new le(t,i);break;case"radialgradient":r=new it(t,i);break;case"rect":r=new xt(t,i);break;case"svg":r=new he(t,i);break;case"symbol":r=new yt(t,i);break;case"text":r=new Pt(t,i);break;case"use":r=new bt(t,i);break;default:r=new ae(t,i)}if(null!=e&&r.element.hasAttribute("id")){var n=y(r.element.id,{isIdentifier:!0});e[n]=e[n]||r}return r.children.forEach((function(t){return t.setParent(r)})),r}var de=function(t){var e,r,i=t,n={a:0,b:0,c:0},a=[];return e=function(e,r){var s,o,l,u,h,f;if(e.test(i))for(o=0,l=(s=i.match(e)).length;o<l;o+=1)n[r]+=1,u=s[o],h=i.indexOf(u),f=u.length,a.push({selector:t.substr(h,f),type:r,index:h,length:f}),i=i.replace(u,Array(f+1).join(" "))},(r=function(t){var e,r,n,a;if(t.test(i))for(r=0,n=(e=i.match(t)).length;r<n;r+=1)a=e[r],i=i.replace(a,Array(a.length+1).join("A"))})(/\\[0-9A-Fa-f]{6}\s?/g),r(/\\[0-9A-Fa-f]{1,5}\s/g),r(/\\./g),function(){var t,e,r,n,a=/{[^]*/gm;if(a.test(i))for(e=0,r=(t=i.match(a)).length;e<r;e+=1)n=t[e],i=i.replace(n,Array(n.length+1).join(" "))}(),e(/(\[[^\]]+\])/g,"b"),e(/(#[^\#\s\+>~\.\[:\)]+)/g,"a"),e(/(\.[^\s\+>~\.\[:\)]+)/g,"b"),e(/(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,"c"),e(/(:(?!not|global|local)[\w-]+\([^\)]*\))/gi,"b"),e(/(:(?!not|global|local)[^\s\+>~\.\[:]+)/g,"b"),i=(i=(i=(i=(i=(i=i.replace(/[\*\s\+>~]/g," ")).replace(/[#\.]/g," ")).replace(/:not/g," ")).replace(/:local/g," ")).replace(/:global/g," ")).replace(/[\(\)]/g," "),e(/([^\s\+>~\.\[:]+)/g,"c"),a.sort((function(t,e){return t.index-e.index})),{selector:t,specificity:"0,"+n.a.toString()+","+n.b.toString()+","+n.c.toString(),specificityArray:[0,n.a,n.b,n.c],parts:a}},ge=function(){function t(t,e){this.rootSvg=t,this.loadExternalSheets=e,this.styleSheets=[]}return t.prototype.load=function(){return s(this,void 0,void 0,(function(){var t;return o(this,(function(e){switch(e.label){case 0:return[4,this.collectStyleSheetTexts()];case 1:return t=e.sent(),this.parseCssSheets(t),[2]}}))}))},t.prototype.collectStyleSheetTexts=function(){return s(this,void 0,void 0,(function(){var e,r,i,n,a;return o(this,(function(s){switch(s.label){case 0:if(e=[],this.loadExternalSheets&&this.rootSvg.ownerDocument)for(n=0;n<this.rootSvg.ownerDocument.childNodes.length;n++)"xml-stylesheet"===(r=this.rootSvg.ownerDocument.childNodes[n]).nodeName&&"string"==typeof r.data&&e.push(t.loadSheet(r.data.match(/href=["'].*?["']/)[0].split("=")[1].slice(1,-1)));for(i=this.rootSvg.querySelectorAll("style,link"),n=0;n<i.length;n++)B(a=i[n],"style")?e.push(a.textContent):this.loadExternalSheets&&B(a,"link")&&"stylesheet"===a.getAttribute("rel")&&a.hasAttribute("href")&&e.push(t.loadSheet(a.getAttribute("href")));return[4,Promise.all(e)];case 1:return[2,s.sent().filter((function(t){return null!==t}))]}}))}))},t.prototype.parseCssSheets=function(e){for(var r=document.implementation.createHTMLDocument(""),i=0,n=e;i<n.length;i++){var a=n[i],s=r.createElement("style");s.textContent=a,r.body.appendChild(s);var o=s.sheet;if(o instanceof CSSStyleSheet){for(var l=o.cssRules.length-1;l>=0;l--){var u=o.cssRules[l];if(u instanceof CSSStyleRule){var h=u;if(h.selectorText.indexOf(",")>=0){o.deleteRule(l);for(var f=h.cssText.substring(h.selectorText.length),c=t.splitSelectorAtCommas(h.selectorText),p=0;p<c.length;p++)o.insertRule(c[p]+f,l+p)}}else o.deleteRule(l)}this.styleSheets.push(o)}}},t.splitSelectorAtCommas=function(t){for(var e,r=/,|["']/g,i=/[^\\]["]/g,n=/[^\\][']/g,a=[],s="initial",o=-1,l=i,u=0;u<t.length;)switch(s){case"initial":r.lastIndex=u,(e=r.exec(t))?(","===e[0]?(a.push(t.substring(o+1,r.lastIndex-1).trim()),o=r.lastIndex-1):(s="withinQuotes",l='"'===e[0]?i:n),u=r.lastIndex):(a.push(t.substring(o+1).trim()),u=t.length);break;case"withinQuotes":l.lastIndex=u,(e=l.exec(t))&&(u=l.lastIndex,s="initial")}return a},t.loadSheet=function(t){return new Promise((function(e,r){var i=new XMLHttpRequest;i.open("GET",t,!0),i.responseType="text",i.onload=function(){200!==i.status&&r(new Error("Error "+i.status+": Failed to load '"+t+"'")),e(i.responseText)},i.onerror=r,i.onabort=r,i.send(null)})).catch((function(){return null}))},t.prototype.getPropertyValue=function(t,e){for(var r=[],i=0,n=this.styleSheets;i<n.length;i++)for(var a=n[i],s=0;s<a.cssRules.length;s++){var o=a.cssRules[s];o.style.getPropertyValue(e)&&t.matches(o.selectorText)&&r.push(o)}if(0!==r.length){var l=function(t,r){var i=t.style.getPropertyPriority(e);return i!==r.style.getPropertyPriority(e)?"important"===i?1:-1:function(t,e){var r,i,n;if("string"==typeof t){if(-1!==t.indexOf(","))throw"Invalid CSS selector";r=de(t).specificityArray}else{if(!Array.isArray(t))throw"Invalid CSS selector or specificity array";if(4!==t.filter((function(t){return"number"==typeof t})).length)throw"Invalid specificity array";r=t}if("string"==typeof e){if(-1!==e.indexOf(","))throw"Invalid CSS selector";i=de(e).specificityArray}else{if(!Array.isArray(e))throw"Invalid CSS selector or specificity array";if(4!==e.filter((function(t){return"number"==typeof t})).length)throw"Invalid specificity array";i=e}for(n=0;n<4;n+=1){if(r[n]<i[n])return-1;if(r[n]>i[n])return 1}return 0}(t.selectorText,r.selectorText)};return r.reduce((function(t,e){return 1===l(t,e)?t:e})).style.getPropertyValue(e)||void 0}},t}(),me=function(){function t(){this.measureMethods={}}return t.prototype.getTextOffset=function(t,e){var r=e.textAnchor;if("start"===r)return 0;var i=this.measureTextWidth(t,e),n=0;switch(r){case"end":n=i;break;case"middle":n=i/2}return n},t.prototype.measureTextWidth=function(t,e){if(0===t.length)return 0;var r=e.fontFamily;return this.getMeasureFunction(r).call(this,t,e.fontFamily,e.fontSize+"px",e.fontStyle,e.fontWeight)},t.prototype.getMeasurementTextNode=function(){if(!this.textMeasuringTextElement){this.textMeasuringTextElement=document.createElementNS("http://www.w3.org/2000/svg","text");var t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.appendChild(this.textMeasuringTextElement),t.style.setProperty("position","absolute"),t.style.setProperty("visibility","hidden"),document.body.appendChild(t)}return this.textMeasuringTextElement},t.prototype.canvasTextMeasure=function(t,e,r,i,n){var a=document.createElement("canvas").getContext("2d");return null!=a?(a.font=[i,n,r,e].join(" "),a.measureText(t).width):0},t.prototype.svgTextMeasure=function(t,e,r,i,n,a){void 0===a&&(a=this.getMeasurementTextNode());var s=a;return s.setAttribute("font-family",e),s.setAttribute("font-size",r),s.setAttribute("font-style",i),s.setAttribute("font-weight",n),s.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),s.textContent=t,s.getBBox().width},t.prototype.getMeasureFunction=function(e){var r=this.measureMethods[e];if(!r){var i=this.canvasTextMeasure(t.testString,e,"16px","normal","normal"),n=this.svgTextMeasure(t.testString,e,"16px","normal","normal");r=Math.abs(i-n)<t.epsilon?this.canvasTextMeasure:this.svgTextMeasure,this.measureMethods[e]=r}return r},t.prototype.cleanupTextMeasuring=function(){if(this.textMeasuringTextElement){var t=this.textMeasuringTextElement.parentNode;t&&document.body.removeChild(t),this.textMeasuringTextElement=void 0}},t.testString="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!\"$%&/()=?'\\+*-_.:,;^}][{#~|<>",t.epsilon=.1,t}();function ye(t,e,r){var i,n,l;return void 0===r&&(r={}),s(this,void 0,void 0,(function(){var s,u,h,c,p,d,g,m,y,b,x;return o(this,(function(o){switch(o.label){case 0:return s=null!==(i=r.x)&&void 0!==i?i:0,u=null!==(n=r.y)&&void 0!==n?n:0,h=null!==(l=r.loadExternalStyleSheets)&&void 0!==l&&l,p=new v(c={}),[4,(d=new ge(t,h)).load()];case 1:return o.sent(),g=new vt(e.internal.pageSize.getWidth(),e.internal.pageSize.getHeight()),m=a(a({},r),{element:t}),y=new me,b=new f(e,{refsHandler:p,styleSheets:d,viewport:g,svg2pdfParameters:m,textMeasure:y}),e.advancedAPI(),e.saveGraphicsState(),e.setCurrentTransformationMatrix(e.Matrix(1,0,0,1,s,u)),e.setLineWidth(b.attributeState.strokeWidth),x=b.attributeState.fill.color,e.setFillColor(x.r,x.g,x.b),e.setFont(b.attributeState.fontFamily),e.setFontSize(b.attributeState.fontSize*e.internal.scaleFactor),[4,pe(t,c).render(b)];case 2:return o.sent(),e.restoreGraphicsState(),e.compatAPI(),b.textMeasure.cleanupTextMeasuring(),[2,e]}}))}))}e.jsPDF.API.svg=function(t,e){return void 0===e&&(e={}),ye(t,this,e)},t.svg2pdf=ye,Object.defineProperty(t,"__esModule",{value:!0})})); - diff --git a/server/readme.md b/server/readme.md index bb4529f83..ff55ee2f3 100644 --- a/server/readme.md +++ b/server/readme.md @@ -20,7 +20,7 @@ Can be used to serve JSROOT and ROOT files on localhost httpserver. ``` 4. Open ROOT file in JSROOT with the link: ``` - http://localhost:8000/jsroot/?file=../files/hsimple.root&item=hpxpy&opt=colz + http://localhost:8000/jsroot/?file=https://root.cern/js/files/hsimple.root&item=hpxpy&opt=colz ``` 5. Also test examples.htm and api.htm: ``` diff --git a/types.d.ts b/types.d.ts index c1b5d5ce8..c0cde6bf6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4,7 +4,9 @@ declare module "jsroot/draw"; declare module "jsroot/io"; declare module "jsroot/tree"; declare module "jsroot/colors"; +declare module "jsroot/base3d"; declare module "jsroot/hierarchy"; declare module "jsroot/latex"; declare module "jsroot/geom"; +declare module "jsroot/geom_nothreejs"; declare module "jsroot/testing";